From e9eab22e5ccadc4c6ea36406e38d2dbc3d6786c2 Mon Sep 17 00:00:00 2001 From: Leonardo de Moura Date: Tue, 2 Oct 2012 11:35:25 -0700 Subject: [PATCH] Z3 sources Signed-off-by: Leonardo de Moura --- lib/act_cache.cpp | 224 + lib/act_cache.h | 56 + lib/add_bounds.cpp | 164 + lib/add_bounds.h | 50 + lib/add_bounds_tactic.cpp | 198 + lib/add_bounds_tactic.h | 34 + lib/aig.cpp | 1812 +++++++ lib/aig.h | 85 + lib/aig_tactic.cpp | 126 + lib/aig_tactic.h | 27 + lib/algebraic_numbers.cpp | 3117 +++++++++++ lib/algebraic_numbers.h | 493 ++ lib/api.py | 1236 +++++ lib/api_arith.cpp | 219 + lib/api_array.cpp | 233 + lib/api_ast.cpp | 1156 ++++ lib/api_ast_map.cpp | 169 + lib/api_ast_map.h | 35 + lib/api_ast_vector.cpp | 140 + lib/api_ast_vector.h | 33 + lib/api_bv.cpp | 320 ++ lib/api_commands.cpp | 3478 ++++++++++++ lib/api_config_params.cpp | 122 + lib/api_config_params.h | 36 + lib/api_context.cpp | 571 ++ lib/api_context.h | 216 + lib/api_datalog.cpp | 561 ++ lib/api_datalog.h | 84 + lib/api_datatype.cpp | 621 +++ lib/api_goal.cpp | 181 + lib/api_goal.h | 33 + lib/api_log.cpp | 54 + lib/api_log_macros.cpp | 3311 ++++++++++++ lib/api_log_macros.h | 975 ++++ lib/api_model.cpp | 670 +++ lib/api_model.h | 58 + lib/api_numeral.cpp | 340 ++ lib/api_params.cpp | 182 + lib/api_parsers.cpp | 426 ++ lib/api_quant.cpp | 511 ++ lib/api_solver.cpp | 322 ++ lib/api_solver.h | 35 + lib/api_solver_old.cpp | 365 ++ lib/api_stats.cpp | 133 + lib/api_stats.h | 33 + lib/api_tactic.cpp | 528 ++ lib/api_tactic.h | 54 + lib/api_user_theory.cpp | 333 ++ lib/api_util.h | 149 + lib/approx_nat.cpp | 61 + lib/approx_nat.h | 45 + lib/approx_set.cpp | 52 + lib/approx_set.h | 236 + lib/arith_bounds_tactic.cpp | 161 + lib/arith_bounds_tactic.h | 37 + lib/arith_decl_plugin.cpp | 558 ++ lib/arith_decl_plugin.h | 348 ++ lib/arith_eq_adapter.cpp | 316 ++ lib/arith_eq_adapter.h | 97 + lib/arith_eq_solver.cpp | 632 +++ lib/arith_eq_solver.h | 108 + lib/arith_rewriter.cpp | 1493 ++++++ lib/arith_rewriter.h | 183 + lib/arith_simplifier_params.cpp | 26 + lib/arith_simplifier_params.h | 37 + lib/arith_simplifier_plugin.cpp | 442 ++ lib/arith_simplifier_plugin.h | 95 + lib/arith_solver_plugin.cpp | 104 + lib/arith_solver_plugin.h | 34 + lib/array.h | 205 + lib/array_decl_plugin.cpp | 564 ++ lib/array_decl_plugin.h | 170 + lib/array_factory.cpp | 206 + lib/array_factory.h | 45 + lib/array_map.h | 162 + lib/array_property_expander.cpp | 208 + lib/array_property_expander.h | 33 + lib/array_property_recognizer.cpp | 103 + lib/array_property_recognizer.h | 33 + lib/array_rewriter.cpp | 359 ++ lib/array_rewriter.h | 65 + lib/array_simplifier_plugin.cpp | 864 +++ lib/array_simplifier_plugin.h | 154 + lib/asserted_formulas.cpp | 1494 ++++++ lib/asserted_formulas.h | 200 + lib/assertion_set.cpp | 430 ++ lib/assertion_set.h | 98 + lib/assertion_set.txt | 78 + lib/assertion_set2sat.cpp | 717 +++ lib/assertion_set2sat.h | 95 + lib/assertion_set_rewriter.cpp | 122 + lib/assertion_set_rewriter.h | 56 + lib/assertion_set_strategy.cpp | 1414 +++++ lib/assertion_set_strategy.h | 228 + lib/assertion_set_util.cpp | 220 + lib/assertion_set_util.h | 91 + lib/assertion_stack.cpp | 314 ++ lib/assertion_stack.h | 141 + lib/ast.cpp | 2903 ++++++++++ lib/ast.h | 2315 ++++++++ lib/ast_dag_pp.cpp | 353 ++ lib/ast_dag_pp.h | 31 + lib/ast_list.h | 160 + lib/ast_ll_pp.cpp | 315 ++ lib/ast_ll_pp.h | 57 + lib/ast_lt.cpp | 144 + lib/ast_lt.h | 35 + lib/ast_pp.cpp | 516 ++ lib/ast_pp.h | 53 + lib/ast_smt2_pp.cpp | 1132 ++++ lib/ast_smt2_pp.h | 116 + lib/ast_smt_pp.cpp | 1214 +++++ lib/ast_smt_pp.h | 103 + lib/ast_translation.cpp | 331 ++ lib/ast_translation.h | 97 + lib/ast_util.cpp | 140 + lib/ast_util.h | 99 + lib/atom2bool_var.cpp | 121 + lib/atom2bool_var.h | 45 + lib/backtrackable_set.h | 114 + lib/base_simplifier.h | 54 + lib/basic_cmds.cpp | 766 +++ lib/basic_cmds.h | 26 + lib/basic_interval.h | 363 ++ lib/basic_simplifier_plugin.cpp | 142 + lib/basic_simplifier_plugin.h | 78 + lib/big_rational.h | 33 + lib/bit2int.cpp | 421 ++ lib/bit2int.h | 96 + lib/bit_blaster.cpp | 128 + lib/bit_blaster.h | 65 + lib/bit_blaster_model_converter.cpp | 187 + lib/bit_blaster_model_converter.h | 27 + lib/bit_blaster_params.h | 41 + lib/bit_blaster_rewriter.cpp | 649 +++ lib/bit_blaster_rewriter.h | 42 + lib/bit_blaster_tactic.cpp | 165 + lib/bit_blaster_tactic.h | 28 + lib/bit_blaster_tpl.h | 135 + lib/bit_blaster_tpl_def.h | 1201 +++++ lib/bit_util.cpp | 389 ++ lib/bit_util.h | 132 + lib/bit_vector.cpp | 220 + lib/bit_vector.h | 234 + lib/bool_rewriter.cpp | 989 ++++ lib/bool_rewriter.h | 199 + lib/bound_manager.cpp | 240 + lib/bound_manager.h | 111 + lib/bound_propagator.cpp | 956 ++++ lib/bound_propagator.h | 265 + lib/buffer.h | 256 + lib/bv1_blaster_tactic.cpp | 508 ++ lib/bv1_blaster_tactic.h | 35 + lib/bv2int_rewriter.cpp | 604 +++ lib/bv2int_rewriter.h | 120 + lib/bv2real_rewriter.cpp | 695 +++ lib/bv2real_rewriter.h | 233 + lib/bv_decl_plugin.cpp | 840 +++ lib/bv_decl_plugin.h | 405 ++ lib/bv_elim.cpp | 113 + lib/bv_elim.h | 45 + lib/bv_rewriter.cpp | 2076 ++++++++ lib/bv_rewriter.h | 171 + lib/bv_simplifier_params.h | 39 + lib/bv_simplifier_plugin.cpp | 2226 ++++++++ lib/bv_simplifier_plugin.h | 187 + lib/bv_size_reduction_tactic.cpp | 366 ++ lib/bv_size_reduction_tactic.h | 33 + lib/cached_var_subst.cpp | 79 + lib/cached_var_subst.h | 53 + lib/cancel_eh.h | 37 + lib/chashtable.h | 672 +++ lib/check_logic.cpp | 495 ++ lib/check_logic.h | 37 + lib/check_sat_result.h | 69 + lib/cmd_context.cpp | 1599 ++++++ lib/cmd_context.h | 404 ++ lib/cmd_context_types.cpp | 44 + lib/cmd_context_types.h | 119 + lib/cmd_util.cpp | 38 + lib/cmd_util.h | 80 + lib/cnf.cpp | 526 ++ lib/cnf.h | 121 + lib/cnf_params.cpp | 26 + lib/cnf_params.h | 56 + lib/cofactor_elim_term_ite.cpp | 712 +++ lib/cofactor_elim_term_ite.h | 44 + lib/cofactor_term_ite_tactic.cpp | 82 + lib/cofactor_term_ite_tactic.h | 29 + lib/contains_var.h | 27 + lib/converter.h | 142 + lib/cooperate.cpp | 87 + lib/cooperate.h | 50 + lib/cost_evaluator.cpp | 104 + lib/cost_evaluator.h | 43 + lib/cost_parser.cpp | 70 + lib/cost_parser.h | 39 + lib/critical_flet.h | 45 + lib/ctx_simplify_tactic.cpp | 555 ++ lib/ctx_simplify_tactic.h | 56 + lib/ctx_solver_simplify_tactic.cpp | 284 + lib/ctx_solver_simplify_tactic.h | 26 + lib/database.h | 316 ++ lib/database.smt | 314 ++ lib/datalog_parser.cpp | 1509 ++++++ lib/datalog_parser.h | 48 + lib/datatype_decl_plugin.cpp | 944 ++++ lib/datatype_decl_plugin.h | 200 + lib/datatype_factory.cpp | 214 + lib/datatype_factory.h | 40 + lib/datatype_rewriter.cpp | 111 + lib/datatype_rewriter.h | 35 + lib/datatype_simplifier_plugin.cpp | 113 + lib/datatype_simplifier_plugin.h | 42 + lib/dbg_cmds.cpp | 383 ++ lib/dbg_cmds.h | 25 + lib/debug.cpp | 107 + lib/debug.h | 99 + lib/dec_ref_util.h | 68 + lib/decl_collector.cpp | 102 + lib/decl_collector.h | 55 + lib/default_tactic.cpp | 48 + lib/default_tactic.h | 28 + lib/defined_names.cpp | 226 + lib/defined_names.h | 151 + lib/degree_shift_tactic.cpp | 349 ++ lib/degree_shift_tactic.h | 31 + lib/demodulator.cpp | 906 ++++ lib/demodulator.h | 265 + lib/dependency.h | 326 ++ lib/der.cpp | 639 +++ lib/der.h | 215 + lib/dictionary.h | 29 + lib/diff_logic.h | 1650 ++++++ lib/diff_neq_tactic.cpp | 429 ++ lib/diff_neq_tactic.h | 32 + lib/dimacs.cpp | 130 + lib/dimacs.h | 27 + lib/distribute_forall.cpp | 170 + lib/distribute_forall.h | 82 + lib/distribute_forall_tactic.cpp | 149 + lib/distribute_forall_tactic.h | 26 + lib/dl_base.cpp | 490 ++ lib/dl_base.h | 1250 +++++ lib/dl_bmc_engine.cpp | 691 +++ lib/dl_bmc_engine.h | 122 + lib/dl_bound_relation.cpp | 706 +++ lib/dl_bound_relation.h | 174 + lib/dl_check_table.cpp | 332 ++ lib/dl_check_table.h | 133 + lib/dl_cmds.cpp | 437 ++ lib/dl_cmds.h | 47 + lib/dl_compiler.cpp | 1190 +++++ lib/dl_compiler.h | 272 + lib/dl_context.cpp | 1685 ++++++ lib/dl_context.h | 464 ++ lib/dl_costs.cpp | 160 + lib/dl_costs.h | 115 + lib/dl_decl_plugin.cpp | 831 +++ lib/dl_decl_plugin.h | 260 + lib/dl_external_relation.cpp | 456 ++ lib/dl_external_relation.h | 154 + lib/dl_finite_product_relation.cpp | 2372 +++++++++ lib/dl_finite_product_relation.h | 366 ++ lib/dl_instruction.cpp | 1056 ++++ lib/dl_instruction.h | 342 ++ lib/dl_interval_relation.cpp | 655 +++ lib/dl_interval_relation.h | 140 + lib/dl_mk_bit_blast.cpp | 217 + lib/dl_mk_bit_blast.h | 53 + lib/dl_mk_coi_filter.cpp | 107 + lib/dl_mk_coi_filter.h | 50 + lib/dl_mk_explanations.cpp | 893 ++++ lib/dl_mk_explanations.h | 92 + lib/dl_mk_filter_rules.cpp | 168 + lib/dl_mk_filter_rules.h | 82 + lib/dl_mk_interp_tail_simplifier.cpp | 569 ++ lib/dl_mk_interp_tail_simplifier.h | 101 + lib/dl_mk_magic_sets.cpp | 395 ++ lib/dl_mk_magic_sets.h | 130 + lib/dl_mk_partial_equiv.cpp | 153 + lib/dl_mk_partial_equiv.h | 50 + lib/dl_mk_rule_inliner.cpp | 884 +++ lib/dl_mk_rule_inliner.h | 198 + lib/dl_mk_similarity_compressor.cpp | 537 ++ lib/dl_mk_similarity_compressor.h | 78 + lib/dl_mk_simple_joins.cpp | 734 +++ lib/dl_mk_simple_joins.h | 62 + lib/dl_mk_slice.cpp | 839 +++ lib/dl_mk_slice.h | 107 + lib/dl_mk_subsumption_checker.cpp | 367 ++ lib/dl_mk_subsumption_checker.h | 93 + lib/dl_mk_unbound_compressor.cpp | 383 ++ lib/dl_mk_unbound_compressor.h | 91 + lib/dl_product_relation.cpp | 1119 ++++ lib/dl_product_relation.h | 176 + lib/dl_relation_manager.cpp | 1622 ++++++ lib/dl_relation_manager.h | 683 +++ lib/dl_rewriter.cpp | 57 + lib/dl_rewriter.h | 33 + lib/dl_rule.cpp | 1084 ++++ lib/dl_rule.h | 265 + lib/dl_rule_set.cpp | 691 +++ lib/dl_rule_set.h | 260 + lib/dl_rule_subsumption_index.cpp | 84 + lib/dl_rule_subsumption_index.h | 65 + lib/dl_rule_transformer.cpp | 133 + lib/dl_rule_transformer.h | 108 + lib/dl_sieve_relation.cpp | 666 +++ lib/dl_sieve_relation.h | 197 + lib/dl_simplifier_plugin.cpp | 64 + lib/dl_simplifier_plugin.h | 38 + lib/dl_skip_table.cpp | 620 +++ lib/dl_skip_table.h | 161 + lib/dl_smt_relation.cpp | 760 +++ lib/dl_smt_relation.h | 141 + lib/dl_sparse_table.cpp | 1249 +++++ lib/dl_sparse_table.h | 480 ++ lib/dl_table.cpp | 772 +++ lib/dl_table.h | 265 + lib/dl_table_plugin.h | 193 + lib/dl_table_relation.cpp | 475 ++ lib/dl_table_relation.h | 131 + lib/dl_util.cpp | 953 ++++ lib/dl_util.h | 878 +++ lib/dl_vector_relation.h | 408 ++ lib/dlist.h | 137 + lib/double_manager.h | 103 + lib/dummy_big_rational.h | 54 + lib/dyn_ack.cpp | 524 ++ lib/dyn_ack.h | 137 + lib/dyn_ack_params.cpp | 31 + lib/dyn_ack_params.h | 53 + lib/eager_bit_blaster.cpp | 436 ++ lib/eager_bit_blaster.h | 107 + lib/elim_bounds.cpp | 222 + lib/elim_bounds.h | 69 + lib/elim_distinct.cpp | 232 + lib/elim_distinct.h | 54 + lib/elim_term_ite.cpp | 160 + lib/elim_term_ite.h | 50 + lib/elim_term_ite_strategy.cpp | 174 + lib/elim_term_ite_strategy.h | 53 + lib/elim_term_ite_tactic.cpp | 198 + lib/elim_term_ite_tactic.h | 29 + lib/elim_uncnstr_tactic.cpp | 1076 ++++ lib/elim_uncnstr_tactic.h | 30 + lib/elim_var_model_converter.cpp | 120 + lib/elim_var_model_converter.h | 56 + lib/error_codes.h | 37 + lib/euclidean_solver.cpp | 855 +++ lib/euclidean_solver.h | 106 + lib/eval_cmd.cpp | 87 + lib/eval_cmd.h | 26 + lib/event_handler.h | 28 + lib/expr2dot.cpp | 84 + lib/expr2dot.h | 27 + lib/expr2polynomial.cpp | 481 ++ lib/expr2polynomial.h | 93 + lib/expr2subpaving.cpp | 395 ++ lib/expr2subpaving.h | 58 + lib/expr2var.cpp | 77 + lib/expr2var.h | 75 + lib/expr_abstract.cpp | 97 + lib/expr_abstract.h | 28 + lib/expr_context_simplifier.cpp | 753 +++ lib/expr_context_simplifier.h | 86 + lib/expr_delta.cpp | 77 + lib/expr_delta.h | 52 + lib/expr_delta_pair.h | 36 + lib/expr_functors.cpp | 142 + lib/expr_functors.h | 121 + lib/expr_map.cpp | 94 + lib/expr_map.h | 49 + lib/expr_offset.h | 53 + lib/expr_offset_map.h | 94 + lib/expr_pattern_match.cpp | 494 ++ lib/expr_pattern_match.h | 148 + lib/expr_rand.cpp | 268 + lib/expr_rand.h | 61 + lib/expr_replacer.cpp | 156 + lib/expr_replacer.h | 62 + lib/expr_stat.cpp | 88 + lib/expr_stat.h | 50 + lib/expr_substitution.cpp | 157 + lib/expr_substitution.h | 54 + lib/expr_weight.cpp | 32 + lib/expr_weight.h | 35 + lib/ext_gcd.h | 52 + lib/ext_numeral.h | 335 ++ lib/extension_model_converter.cpp | 121 + lib/extension_model_converter.h | 57 + lib/f2n.h | 167 + lib/factor_rewriter.cpp | 352 ++ lib/factor_rewriter.h | 78 + lib/factor_tactic.cpp | 361 ++ lib/factor_tactic.h | 28 + lib/filter_model_converter.cpp | 66 + lib/filter_model_converter.h | 48 + lib/fingerprints.cpp | 156 + lib/fingerprints.h | 85 + lib/fix_dl_var_tactic.cpp | 358 ++ lib/fix_dl_var_tactic.h | 33 + lib/float_decl_plugin.cpp | 545 ++ lib/float_decl_plugin.h | 243 + lib/float_rewriter.cpp | 441 ++ lib/float_rewriter.h | 72 + lib/fm_tactic.cpp | 1715 ++++++ lib/fm_tactic.h | 33 + lib/for_each_ast.cpp | 45 + lib/for_each_ast.h | 274 + lib/for_each_expr.cpp | 65 + lib/for_each_expr.h | 157 + lib/format.cpp | 212 + lib/format.h | 197 + lib/fpa2bv_converter.cpp | 2112 ++++++++ lib/fpa2bv_converter.h | 200 + lib/fpa2bv_tactic.cpp | 310 ++ lib/fpa2bv_tactic.h | 28 + lib/front_end_params.cpp | 108 + lib/front_end_params.h | 139 + lib/func_decl_dependencies.cpp | 225 + lib/func_decl_dependencies.h | 110 + lib/func_interp.cpp | 360 ++ lib/func_interp.h | 114 + lib/fvi.h | 214 + lib/fvi_def.h | 199 + lib/gaussian_elim.cpp | 775 +++ lib/gaussian_elim.h | 66 + lib/gl_tactic.cpp | 1047 ++++ lib/gl_tactic.h | 29 + lib/gmp_big_rational.cpp | 117 + lib/gmp_big_rational.h | 193 + lib/goal.cpp | 606 +++ lib/goal.h | 254 + lib/goal2nlsat.cpp | 309 ++ lib/goal2nlsat.h | 75 + lib/goal2sat.cpp | 711 +++ lib/goal2sat.h | 86 + lib/goal_shared_occs.cpp | 29 + lib/goal_shared_occs.h | 45 + lib/goal_util.cpp | 33 + lib/goal_util.h | 25 + lib/grobner.cpp | 961 ++++ lib/grobner.h | 281 + lib/has_free_vars.cpp | 94 + lib/has_free_vars.h | 27 + lib/hash.cpp | 85 + lib/hash.h | 249 + lib/hashtable.h | 643 +++ lib/heap.h | 245 + lib/horn_subsume_model_converter.cpp | 221 + lib/horn_subsume_model_converter.h | 67 + lib/hwf.cpp | 646 +++ lib/hwf.h | 174 + lib/id_gen.h | 83 + lib/imdd.cpp | 3137 +++++++++++ lib/imdd.h | 745 +++ lib/inf_int_rational.cpp | 41 + lib/inf_int_rational.h | 372 ++ lib/inf_rational.cpp | 179 + lib/inf_rational.h | 470 ++ lib/inf_s_integer.cpp | 25 + lib/inf_s_integer.h | 351 ++ lib/ini_file.cpp | 1564 ++++++ lib/ini_file.h | 117 + lib/inj_axiom.cpp | 142 + lib/inj_axiom.h | 27 + lib/install_tactics.cpp | 234 + lib/install_tactics.h | 24 + lib/instruction_count.cpp | 41 + lib/instruction_count.h | 43 + lib/interval.h | 353 ++ lib/interval_def.h | 1985 +++++++ lib/interval_skip_list.h | 1876 +++++++ lib/kbo.cpp | 276 + lib/kbo.h | 70 + lib/lbool.cpp | 36 + lib/lbool.h | 42 + lib/lia2pb_tactic.cpp | 366 ++ lib/lia2pb_tactic.h | 28 + lib/lib.vcxproj | 2161 ++++++++ lib/lib.vcxproj.user | 3 + lib/linear_eq_solver.h | 152 + lib/linear_equation.cpp | 279 + lib/linear_equation.h | 85 + lib/list.h | 96 + lib/lockless_queue.h | 115 + lib/lpo.cpp | 184 + lib/lpo.h | 49 + lib/lru_cache.cpp | 424 ++ lib/lru_cache.h | 80 + lib/lu.cpp | 1792 +++++++ lib/lu.h | 404 ++ lib/luby.cpp | 33 + lib/luby.h | 31 + lib/machine.h | 30 + lib/macro_finder.cpp | 223 + lib/macro_finder.h | 55 + lib/macro_manager.cpp | 319 ++ lib/macro_manager.h | 99 + lib/macro_substitution.cpp | 184 + lib/macro_substitution.h | 59 + lib/macro_util.cpp | 928 ++++ lib/macro_util.h | 144 + lib/mam.cpp | 3987 ++++++++++++++ lib/mam.h | 74 + lib/map.h | 286 + lib/marker.h | 56 + lib/matcher.cpp | 81 + lib/matcher.h | 64 + lib/max_bv_sharing_tactic.cpp | 337 ++ lib/max_bv_sharing_tactic.h | 32 + lib/maximise_ac_sharing.cpp | 192 + lib/maximise_ac_sharing.h | 124 + lib/mem_stat.cpp | 56 + lib/mem_stat.h | 25 + lib/memory_manager.cpp | 260 + lib/memory_manager.h | 105 + lib/mip_tactic.cpp | 135 + lib/mip_tactic.h | 30 + lib/mk_database.sh | 3 + lib/mk_simplified_app.cpp | 105 + lib/mk_simplified_app.h | 37 + lib/model.cpp | 234 + lib/model.h | 69 + lib/model2expr.cpp | 157 + lib/model2expr.h | 43 + lib/model_converter.cpp | 150 + lib/model_converter.h | 59 + lib/model_core.cpp | 34 + lib/model_core.h | 73 + lib/model_evaluator.cpp | 274 + lib/model_evaluator.h | 51 + lib/model_evaluator_params.cpp | 76 + lib/model_params.cpp | 37 + lib/model_params.h | 47 + lib/model_pp.cpp | 102 + lib/model_pp.h | 28 + lib/model_smt2_pp.cpp | 348 ++ lib/model_smt2_pp.h | 28 + lib/model_v2_pp.cpp | 82 + lib/model_v2_pp.h | 26 + lib/mpbq.cpp | 907 ++++ lib/mpbq.h | 362 ++ lib/mpbqi.h | 50 + lib/mpf.cpp | 1572 ++++++ lib/mpf.h | 278 + lib/mpff.cpp | 1413 +++++ lib/mpff.h | 479 ++ lib/mpfx.cpp | 866 +++ lib/mpfx.h | 398 ++ lib/mpn.cpp | 439 ++ lib/mpn.h | 196 + lib/mpq.cpp | 317 ++ lib/mpq.h | 848 +++ lib/mpq_inf.cpp | 42 + lib/mpq_inf.h | 256 + lib/mpz.cpp | 2116 ++++++++ lib/mpz.h | 798 +++ lib/mpzzp.h | 285 + lib/name_exprs.cpp | 167 + lib/name_exprs.h | 65 + lib/nat_set.h | 88 + lib/ni_solver.cpp | 230 + lib/ni_solver.h | 30 + lib/nla2bv_tactic.cpp | 480 ++ lib/nla2bv_tactic.h | 30 + lib/nlarith_util.cpp | 2024 +++++++ lib/nlarith_util.h | 151 + lib/nlsat_assignment.h | 78 + lib/nlsat_clause.cpp | 49 + lib/nlsat_clause.h | 61 + lib/nlsat_evaluator.cpp | 679 +++ lib/nlsat_evaluator.h | 61 + lib/nlsat_explain.cpp | 1343 +++++ lib/nlsat_explain.h | 64 + lib/nlsat_interval_set.cpp | 762 +++ lib/nlsat_interval_set.h | 123 + lib/nlsat_justification.h | 90 + lib/nlsat_scoped_literal_vector.h | 89 + lib/nlsat_solver.cpp | 2683 ++++++++++ lib/nlsat_solver.h | 187 + lib/nlsat_tactic.cpp | 258 + lib/nlsat_tactic.h | 28 + lib/nlsat_types.cpp | 70 + lib/nlsat_types.h | 150 + lib/nnf.cpp | 1030 ++++ lib/nnf.h | 68 + lib/nnf_params.cpp | 26 + lib/nnf_params.h | 74 + lib/nnf_tactic.cpp | 126 + lib/nnf_tactic.h | 30 + lib/normalize_bounds_tactic.cpp | 211 + lib/normalize_bounds_tactic.h | 30 + lib/normalize_vars.cpp | 88 + lib/normalize_vars.h | 47 + lib/nra_tactic.cpp | 47 + lib/nra_tactic.h | 24 + lib/num_occurs.cpp | 92 + lib/num_occurs.h | 59 + lib/numeral_buffer.h | 91 + lib/numeral_factory.cpp | 52 + lib/numeral_factory.h | 57 + lib/obj_hashtable.h | 219 + lib/obj_mark.h | 53 + lib/obj_pair_hashtable.h | 174 + lib/obj_pair_set.h | 50 + lib/obj_ref.h | 139 + lib/obj_triple_hashtable.h | 180 + lib/object_allocator.h | 289 + lib/occf_tactic.cpp | 252 + lib/occf_tactic.h | 34 + lib/occurs.cpp | 77 + lib/occurs.h | 36 + lib/old_interval.cpp | 642 +++ lib/old_interval.h | 138 + lib/optional.h | 165 + lib/order.cpp | 51 + lib/order.h | 87 + lib/order_params.cpp | 27 + lib/order_params.h | 44 + lib/page.cpp | 68 + lib/page.h | 42 + lib/parameters.h | 32 + lib/parametric_cmd.cpp | 64 + lib/parametric_cmd.h | 76 + lib/params.cpp | 863 +++ lib/params.h | 128 + lib/params2front_end_params.cpp | 82 + lib/params2front_end_params.h | 31 + lib/parray.h | 621 +++ lib/parser_params.cpp | 14 + lib/parser_params.h | 33 + lib/pattern_inference.cpp | 744 +++ lib/pattern_inference.h | 248 + lib/pattern_inference_params.cpp | 37 + lib/pattern_inference_params.h | 59 + lib/pattern_validation.cpp | 105 + lib/pattern_validation.h | 44 + lib/pb2bv_model_converter.cpp | 102 + lib/pb2bv_model_converter.h | 39 + lib/pb2bv_tactic.cpp | 1051 ++++ lib/pb2bv_tactic.h | 30 + lib/pdecl.cpp | 958 ++++ lib/pdecl.h | 329 ++ lib/pdr_context.cpp | 1746 ++++++ lib/pdr_context.h | 403 ++ lib/pdr_dl_interface.cpp | 217 + lib/pdr_dl_interface.h | 74 + lib/pdr_farkas_learner.cpp | 934 ++++ lib/pdr_farkas_learner.h | 112 + lib/pdr_generalizers.cpp | 807 +++ lib/pdr_generalizers.h | 104 + lib/pdr_interpolant_provider.cpp | 380 ++ lib/pdr_interpolant_provider.h | 54 + lib/pdr_manager.cpp | 342 ++ lib/pdr_manager.h | 307 ++ lib/pdr_prop_solver.cpp | 297 ++ lib/pdr_prop_solver.h | 125 + lib/pdr_quantifiers.cpp | 503 ++ lib/pdr_quantifiers.h | 111 + lib/pdr_reachable_cache.cpp | 126 + lib/pdr_reachable_cache.h | 59 + lib/pdr_smt_context_manager.cpp | 133 + lib/pdr_smt_context_manager.h | 105 + lib/pdr_sym_mux.cpp | 562 ++ lib/pdr_sym_mux.h | 238 + lib/pdr_util.cpp | 841 +++ lib/pdr_util.h | 236 + lib/permutation.cpp | 76 + lib/permutation.h | 93 + lib/plugin_manager.h | 66 + lib/poly_rewriter.h | 167 + lib/poly_rewriter_def.h | 932 ++++ lib/poly_simplifier_plugin.cpp | 812 +++ lib/poly_simplifier_plugin.h | 151 + lib/polynomial.cpp | 7386 ++++++++++++++++++++++++++ lib/polynomial.h | 1380 +++++ lib/polynomial_cache.cpp | 235 + lib/polynomial_cache.h | 44 + lib/polynomial_cmds.cpp | 241 + lib/polynomial_cmds.h | 24 + lib/polynomial_factorization.cpp | 1143 ++++ lib/polynomial_factorization.h | 65 + lib/polynomial_primes.h | 72 + lib/polynomial_var2value.h | 50 + lib/pool.h | 50 + lib/pop_scopes.h | 37 + lib/pp.cpp | 148 + lib/pp.h | 34 + lib/pp_params.cpp | 57 + lib/pp_params.h | 46 + lib/precedence.cpp | 191 + lib/precedence.h | 142 + lib/preprocessor.cpp | 155 + lib/preprocessor.h | 51 + lib/preprocessor_params.h | 140 + lib/prime_generator.cpp | 129 + lib/prime_generator.h | 53 + lib/probe.cpp | 449 ++ lib/probe.h | 91 + lib/probe_arith.cpp | 421 ++ lib/probe_arith.h | 39 + lib/progress_callback.h | 33 + lib/proof_checker.cpp | 1335 +++++ lib/proof_checker.h | 116 + lib/proof_converter.cpp | 162 + lib/proof_converter.h | 52 + lib/propagate_ineqs_tactic.cpp | 563 ++ lib/propagate_ineqs_tactic.h | 42 + lib/propagate_values_tactic.cpp | 275 + lib/propagate_values_tactic.h | 29 + lib/proto_model.cpp | 608 +++ lib/proto_model.h | 119 + lib/ptr_scoped_buffer.h | 56 + lib/pull_ite_tree.cpp | 213 + lib/pull_ite_tree.h | 101 + lib/pull_quant.cpp | 385 ++ lib/pull_quant.h | 67 + lib/purify_arith_tactic.cpp | 909 ++++ lib/purify_arith_tactic.h | 58 + lib/push_app_ite.cpp | 220 + lib/push_app_ite.h | 63 + lib/qe.cpp | 2617 +++++++++ lib/qe.h | 357 ++ lib/qe_arith_plugin.cpp | 2541 +++++++++ lib/qe_array_plugin.cpp | 299 ++ lib/qe_bool_plugin.cpp | 180 + lib/qe_bv_plugin.cpp | 97 + lib/qe_cmd.cpp | 64 + lib/qe_cmd.h | 25 + lib/qe_datatype_plugin.cpp | 843 +++ lib/qe_dl_plugin.cpp | 233 + lib/qe_sat_tactic.cpp | 706 +++ lib/qe_sat_tactic.h | 34 + lib/qe_tactic.cpp | 153 + lib/qe_tactic.h | 28 + lib/qfaufbv_tactic.cpp | 65 + lib/qfaufbv_tactic.h | 28 + lib/qfauflia_tactic.cpp | 52 + lib/qfauflia_tactic.h | 28 + lib/qfbv_tactic.cpp | 118 + lib/qfbv_tactic.h | 28 + lib/qffpa_tactic.cpp | 38 + lib/qffpa_tactic.h | 29 + lib/qfidl_tactic.cpp | 111 + lib/qfidl_tactic.h | 28 + lib/qflia_tactic.cpp | 218 + lib/qflia_tactic.h | 28 + lib/qflra_tactic.cpp | 71 + lib/qflra_tactic.h | 28 + lib/qfnia_tactic.cpp | 93 + lib/qfnia_tactic.h | 28 + lib/qfnra_nlsat_tactic.cpp | 63 + lib/qfnra_nlsat_tactic.h | 30 + lib/qfnra_tactic.cpp | 53 + lib/qfnra_tactic.h | 28 + lib/qfuf_tactic.cpp | 40 + lib/qfuf_tactic.h | 29 + lib/qfufbv_tactic.cpp | 53 + lib/qfufbv_tactic.h | 28 + lib/qi_params.h | 139 + lib/qi_queue.cpp | 484 ++ lib/qi_queue.h | 105 + lib/quant_hoist.cpp | 239 + lib/quant_hoist.h | 64 + lib/quant_tactics.cpp | 106 + lib/quant_tactics.h | 33 + lib/quasi_macros.cpp | 317 ++ lib/quasi_macros.h | 69 + lib/rational.cpp | 41 + lib/rational.h | 446 ++ lib/recover_01_tactic.cpp | 446 ++ lib/recover_01_tactic.h | 42 + lib/recurse_expr.h | 47 + lib/recurse_expr_def.h | 109 + lib/reduce_args.cpp | 521 ++ lib/reduce_args.h | 92 + lib/reduce_args_tactic.cpp | 558 ++ lib/reduce_args_tactic.h | 28 + lib/ref.h | 124 + lib/ref_buffer.h | 169 + lib/ref_util.h | 91 + lib/ref_vector.h | 349 ++ lib/region.cpp | 124 + lib/region.h | 121 + lib/replace_proof_converter.cpp | 86 + lib/replace_proof_converter.h | 47 + lib/resource_limit.h | 168 + lib/rewriter.cpp | 402 ++ lib/rewriter.h | 399 ++ lib/rewriter.txt | 145 + lib/rewriter_def.h | 642 +++ lib/rewriter_types.h | 51 + lib/rpolynomial.cpp | 800 +++ lib/rpolynomial.h | 208 + lib/s_integer.cpp | 70 + lib/s_integer.h | 149 + lib/sat_asymm_branch.cpp | 218 + lib/sat_asymm_branch.h | 60 + lib/sat_clause.cpp | 236 + lib/sat_clause.h | 179 + lib/sat_clause_set.cpp | 84 + lib/sat_clause_set.h | 56 + lib/sat_clause_use_list.cpp | 57 + lib/sat_clause_use_list.h | 118 + lib/sat_cleaner.cpp | 214 + lib/sat_cleaner.h | 53 + lib/sat_config.cpp | 126 + lib/sat_config.h | 91 + lib/sat_elim_eqs.cpp | 220 + lib/sat_elim_eqs.h | 40 + lib/sat_extension.h | 46 + lib/sat_iff3_finder.cpp | 214 + lib/sat_iff3_finder.h | 48 + lib/sat_integrity_checker.cpp | 221 + lib/sat_integrity_checker.h | 42 + lib/sat_justification.h | 57 + lib/sat_model_converter.cpp | 240 + lib/sat_model_converter.h | 81 + lib/sat_probing.cpp | 269 + lib/sat_probing.h | 92 + lib/sat_scc.cpp | 241 + lib/sat_scc.h | 48 + lib/sat_simplifier.cpp | 1489 ++++++ lib/sat_simplifier.h | 188 + lib/sat_solver.cpp | 2331 ++++++++ lib/sat_solver.h | 428 ++ lib/sat_solver_strategy.cpp | 187 + lib/sat_solver_strategy.h | 65 + lib/sat_tactic.cpp | 218 + lib/sat_tactic.h | 30 + lib/sat_types.h | 237 + lib/sat_var_queue.h | 67 + lib/sat_watched.cpp | 71 + lib/sat_watched.h | 136 + lib/scanner.cpp | 496 ++ lib/scanner.h | 92 + lib/scoped_ctrl_c.cpp | 58 + lib/scoped_ctrl_c.h | 42 + lib/scoped_numeral.h | 163 + lib/scoped_numeral_buffer.h | 68 + lib/scoped_numeral_vector.h | 67 + lib/scoped_ptr_vector.h | 55 + lib/scoped_timer.cpp | 212 + lib/scoped_timer.h | 32 + lib/seq_decl_plugin.cpp | 265 + lib/seq_decl_plugin.h | 120 + lib/sexpr.cpp | 291 + lib/sexpr.h | 84 + lib/sexpr2upolynomial.cpp | 112 + lib/sexpr2upolynomial.h | 33 + lib/shallow_context_simplifier.cpp | 257 + lib/shallow_context_simplifier.h | 59 + lib/shared_occs.cpp | 140 + lib/shared_occs.h | 81 + lib/simple_parser.cpp | 149 + lib/simple_parser.h | 64 + lib/simplifier.cpp | 949 ++++ lib/simplifier.h | 232 + lib/simplifier_plugin.cpp | 46 + lib/simplifier_plugin.h | 94 + lib/simplify_cmd.cpp | 128 + lib/simplify_cmd.h | 25 + lib/simplify_tactic.cpp | 138 + lib/simplify_tactic.h | 54 + lib/skip_list_base.h | 871 +++ lib/sls_strategy.h | 61 + lib/sls_tactic.cpp | 1913 +++++++ lib/sls_tactic.h | 30 + lib/small_object_allocator.cpp | 235 + lib/small_object_allocator.h | 59 + lib/smt2parser.cpp | 2491 +++++++++ lib/smt2parser.h | 26 + lib/smt2scanner.cpp | 330 ++ lib/smt2scanner.h | 105 + lib/smt_almost_cg_table.cpp | 127 + lib/smt_almost_cg_table.h | 72 + lib/smt_arith.cpp | 3605 +++++++++++++ lib/smt_arith.h | 56 + lib/smt_b_justification.h | 100 + lib/smt_bool_var_data.h | 128 + lib/smt_case_split_queue.cpp | 1124 ++++ lib/smt_case_split_queue.h | 55 + lib/smt_cg_table.cpp | 250 + lib/smt_cg_table.h | 354 ++ lib/smt_checker.cpp | 188 + lib/smt_checker.h | 57 + lib/smt_classifier.h | 46 + lib/smt_clause.cpp | 118 + lib/smt_clause.h | 258 + lib/smt_conflict_resolution.cpp | 1430 +++++ lib/smt_conflict_resolution.h | 278 + lib/smt_context.cpp | 4207 +++++++++++++++ lib/smt_context.h | 1462 +++++ lib/smt_context_inv.cpp | 403 ++ lib/smt_context_pp.cpp | 621 +++ lib/smt_context_stat.cpp | 159 + lib/smt_enode.cpp | 453 ++ lib/smt_enode.h | 382 ++ lib/smt_eq_justification.h | 85 + lib/smt_euf.cpp | 187 + lib/smt_euf.h | 55 + lib/smt_failure.h | 40 + lib/smt_for_each_relevant_expr.cpp | 298 ++ lib/smt_for_each_relevant_expr.h | 114 + lib/smt_formula_compiler.cpp | 218 + lib/smt_formula_compiler.h | 63 + lib/smt_implied_equalities.cpp | 619 +++ lib/smt_implied_equalities.h | 40 + lib/smt_internalizer.cpp | 1700 ++++++ lib/smt_justification.cpp | 419 ++ lib/smt_justification.h | 412 ++ lib/smt_literal.cpp | 100 + lib/smt_literal.h | 122 + lib/smt_model_checker.cpp | 419 ++ lib/smt_model_checker.h | 98 + lib/smt_model_finder.cpp | 3471 ++++++++++++ lib/smt_model_finder.h | 125 + lib/smt_model_generator.cpp | 631 +++ lib/smt_model_generator.h | 229 + lib/smt_params.cpp | 108 + lib/smt_params.h | 257 + lib/smt_quantifier.cpp | 609 +++ lib/smt_quantifier.h | 167 + lib/smt_quantifier_instances.h | 66 + lib/smt_quantifier_stat.cpp | 115 + lib/smt_quantifier_stat.h | 140 + lib/smt_quick_checker.cpp | 421 ++ lib/smt_quick_checker.h | 106 + lib/smt_relevancy.cpp | 669 +++ lib/smt_relevancy.h | 204 + lib/smt_setup.cpp | 901 ++++ lib/smt_setup.h | 114 + lib/smt_solver.cpp | 357 ++ lib/smt_solver.h | 247 + lib/smt_solver_exp.cpp | 232 + lib/smt_solver_exp.h | 130 + lib/smt_solver_strategy.cpp | 96 + lib/smt_solver_strategy.h | 55 + lib/smt_solver_types.h | 47 + lib/smt_statistics.cpp | 29 + lib/smt_statistics.h | 60 + lib/smt_strategic_solver.cpp | 104 + lib/smt_strategic_solver.h | 29 + lib/smt_tactic.cpp | 317 ++ lib/smt_tactic.h | 30 + lib/smt_theory.cpp | 139 + lib/smt_theory.h | 454 ++ lib/smt_theory_var_list.h | 76 + lib/smt_trail.h | 131 + lib/smt_types.h | 78 + lib/smtlib.cpp | 252 + lib/smtlib.h | 232 + lib/smtlib_solver.cpp | 117 + lib/smtlib_solver.h | 48 + lib/smtparser.cpp | 5320 +++++++++++++++++++ lib/smtparser.h | 51 + lib/solve_eqs_tactic.cpp | 791 +++ lib/solve_eqs_tactic.h | 30 + lib/solver.cpp | 186 + lib/solver.h | 63 + lib/solver_plugin.h | 51 + lib/sparse_use_list.h | 107 + lib/spc_asserted_literals.cpp | 170 + lib/spc_asserted_literals.h | 69 + lib/spc_clause.cpp | 287 + lib/spc_clause.h | 152 + lib/spc_clause_pos_set.h | 58 + lib/spc_clause_selection.cpp | 121 + lib/spc_clause_selection.h | 85 + lib/spc_context.cpp | 504 ++ lib/spc_context.h | 122 + lib/spc_decl_plugin.cpp | 135 + lib/spc_decl_plugin.h | 61 + lib/spc_der.cpp | 80 + lib/spc_der.h | 52 + lib/spc_eq_resolution.cpp | 44 + lib/spc_eq_resolution.h | 50 + lib/spc_factoring.cpp | 156 + lib/spc_factoring.h | 66 + lib/spc_justification.cpp | 184 + lib/spc_justification.h | 337 ++ lib/spc_literal.cpp | 432 ++ lib/spc_literal.h | 212 + lib/spc_literal_selection.cpp | 107 + lib/spc_literal_selection.h | 95 + lib/spc_params.cpp | 37 + lib/spc_params.h | 49 + lib/spc_prover.cpp | 132 + lib/spc_prover.h | 59 + lib/spc_rewriter.cpp | 269 + lib/spc_rewriter.h | 122 + lib/spc_semantic_tautology.cpp | 234 + lib/spc_semantic_tautology.h | 114 + lib/spc_statistics.cpp | 54 + lib/spc_statistics.h | 49 + lib/spc_subsumption.cpp | 698 +++ lib/spc_subsumption.h | 156 + lib/spc_superposition.cpp | 531 ++ lib/spc_superposition.h | 147 + lib/spc_unary_inference.cpp | 52 + lib/spc_unary_inference.h | 48 + lib/splay_tree.h | 180 + lib/splay_tree_def.h | 152 + lib/splay_tree_map.h | 114 + lib/split_clause_tactic.cpp | 150 + lib/split_clause_tactic.h | 28 + lib/st2tactic.cpp | 77 + lib/st2tactic.h | 27 + lib/st_cmds.h | 26 + lib/stack.cpp | 148 + lib/stack.h | 50 + lib/static_features.cpp | 601 +++ lib/static_features.h | 169 + lib/statistics.cpp | 228 + lib/statistics.h | 45 + lib/stats.h | 37 + lib/stopwatch.h | 180 + lib/str_hashtable.h | 34 + lib/strategic_solver.cpp | 469 ++ lib/strategic_solver.h | 143 + lib/strategy_exception.cpp | 26 + lib/strategy_exception.h | 53 + lib/stream_buffer.h | 46 + lib/string_buffer.h | 181 + lib/struct_factory.cpp | 78 + lib/struct_factory.h | 56 + lib/subpaving.cpp | 288 + lib/subpaving.h | 124 + lib/subpaving_cmds.cpp | 56 + lib/subpaving_cmds.h | 21 + lib/subpaving_hwf.cpp | 23 + lib/subpaving_hwf.h | 48 + lib/subpaving_mpf.cpp | 23 + lib/subpaving_mpf.h | 49 + lib/subpaving_mpff.cpp | 23 + lib/subpaving_mpff.h | 45 + lib/subpaving_mpfx.cpp | 23 + lib/subpaving_mpfx.h | 45 + lib/subpaving_mpq.cpp | 23 + lib/subpaving_mpq.h | 43 + lib/subpaving_t.h | 853 +++ lib/subpaving_t_def.h | 1942 +++++++ lib/subpaving_tactic.cpp | 308 ++ lib/subpaving_tactic.h | 28 + lib/subpaving_types.h | 52 + lib/substitution.cpp | 286 + lib/substitution.h | 202 + lib/substitution_tree.cpp | 899 ++++ lib/substitution_tree.h | 148 + lib/symbol.cpp | 166 + lib/symbol.h | 150 + lib/symbol_table.h | 213 + lib/symmetry_reduce_tactic.cpp | 657 +++ lib/symmetry_reduce_tactic.h | 28 + lib/tactic.cpp | 331 ++ lib/tactic.h | 155 + lib/tactic2solver.cpp | 249 + lib/tactic2solver.h | 110 + lib/tactic_cmds.cpp | 814 +++ lib/tactic_cmds.h | 67 + lib/tactic_exception.cpp | 26 + lib/tactic_exception.h | 47 + lib/tactic_manager.cpp | 62 + lib/tactic_manager.h | 57 + lib/tactical.cpp | 1434 +++++ lib/tactical.h | 83 + lib/th_rewriter.cpp | 770 +++ lib/th_rewriter.h | 65 + lib/theory_arith.cpp | 30 + lib/theory_arith.h | 1222 +++++ lib/theory_arith_aux.h | 1481 ++++++ lib/theory_arith_core.h | 3080 +++++++++++ lib/theory_arith_def.h | 32 + lib/theory_arith_eq.h | 351 ++ lib/theory_arith_int.h | 1413 +++++ lib/theory_arith_inv.h | 207 + lib/theory_arith_nl.h | 2406 +++++++++ lib/theory_arith_params.cpp | 76 + lib/theory_arith_params.h | 158 + lib/theory_arith_pp.h | 516 ++ lib/theory_array.cpp | 479 ++ lib/theory_array.h | 117 + lib/theory_array_base.cpp | 926 ++++ lib/theory_array_base.h | 199 + lib/theory_array_full.cpp | 812 +++ lib/theory_array_full.h | 109 + lib/theory_array_params.h | 76 + lib/theory_bv.cpp | 1635 ++++++ lib/theory_bv.h | 282 + lib/theory_bv_params.h | 55 + lib/theory_datatype.cpp | 755 +++ lib/theory_datatype.h | 116 + lib/theory_datatype_params.h | 38 + lib/theory_dense_diff_logic.cpp | 26 + lib/theory_dense_diff_logic.h | 278 + lib/theory_dense_diff_logic_def.h | 824 +++ lib/theory_diff_logic.cpp | 38 + lib/theory_diff_logic.h | 523 ++ lib/theory_diff_logic_def.h | 1148 ++++ lib/theory_dl.cpp | 278 + lib/theory_dl.h | 30 + lib/theory_dummy.cpp | 73 + lib/theory_dummy.h | 58 + lib/theory_instgen.cpp | 1494 ++++++ lib/theory_instgen.h | 45 + lib/theory_seq_empty.h | 41 + lib/timeit.cpp | 58 + lib/timeit.h | 33 + lib/timeout.cpp | 57 + lib/timeout.h | 26 + lib/timer.cpp | 40 + lib/timer.h | 39 + lib/total_order.h | 415 ++ lib/tptr.h | 46 + lib/trace.cpp | 68 + lib/trace.h | 46 + lib/trail.h | 404 ++ lib/tseitin_cnf_tactic.cpp | 942 ++++ lib/tseitin_cnf_tactic.h | 31 + lib/ufbv_strategy.cpp | 492 ++ lib/ufbv_strategy.h | 28 + lib/uint_map.h | 60 + lib/uint_set.h | 243 + lib/unifier.cpp | 183 + lib/unifier.h | 70 + lib/union_find.h | 160 + lib/unit_subsumption_tactic.cpp | 151 + lib/unit_subsumption_tactic.h | 30 + lib/upolynomial.cpp | 3131 +++++++++++ lib/upolynomial.h | 922 ++++ lib/upolynomial_factorization.cpp | 1300 +++++ lib/upolynomial_factorization.h | 96 + lib/upolynomial_factorization_int.h | 420 ++ lib/use_list.cpp | 90 + lib/use_list.h | 120 + lib/used_symbols.h | 105 + lib/used_vars.cpp | 113 + lib/used_vars.h | 59 + lib/user_decl_plugin.cpp | 97 + lib/user_decl_plugin.h | 60 + lib/user_rewriter.cpp | 24 + lib/user_rewriter.h | 58 + lib/user_simplifier_plugin.cpp | 81 + lib/user_simplifier_plugin.h | 66 + lib/user_smt_theory.cpp | 661 +++ lib/user_smt_theory.h | 324 ++ lib/uses_theory.cpp | 49 + lib/uses_theory.h | 37 + lib/util.cpp | 179 + lib/util.h | 405 ++ lib/value.cpp | 42 + lib/value.h | 162 + lib/value_compiler_extension.h | 46 + lib/value_factory.cpp | 117 + lib/value_factory.h | 257 + lib/var_offset_map.h | 114 + lib/var_subst.cpp | 211 + lib/var_subst.h | 78 + lib/vector.h | 469 ++ lib/version.h | 12 + lib/vsubst_tactic.cpp | 170 + lib/vsubst_tactic.h | 30 + lib/warning.cpp | 172 + lib/warning.h | 64 + lib/watch_list.cpp | 110 + lib/watch_list.h | 186 + lib/well_sorted.cpp | 83 + lib/well_sorted.h | 28 + lib/z3.h | 39 + lib/z3_api.h | 6533 +++++++++++++++++++++++ lib/z3_exception.cpp | 74 + lib/z3_exception.h | 49 + lib/z3_logger.h | 70 + lib/z3_macros.h | 54 + lib/z3_omp.h | 38 + lib/z3_private.h | 80 + lib/z3_replayer.cpp | 711 +++ lib/z3_replayer.h | 66 + lib/z3_solver.cpp | 1304 +++++ lib/z3_solver.h | 1007 ++++ lib/z3_solver_params.cpp | 30 + lib/z3_solver_params.h | 44 + lib/z3_v1.h | 64 + 1186 files changed, 381859 insertions(+) create mode 100644 lib/act_cache.cpp create mode 100644 lib/act_cache.h create mode 100644 lib/add_bounds.cpp create mode 100644 lib/add_bounds.h create mode 100644 lib/add_bounds_tactic.cpp create mode 100644 lib/add_bounds_tactic.h create mode 100644 lib/aig.cpp create mode 100644 lib/aig.h create mode 100644 lib/aig_tactic.cpp create mode 100644 lib/aig_tactic.h create mode 100644 lib/algebraic_numbers.cpp create mode 100644 lib/algebraic_numbers.h create mode 100644 lib/api.py create mode 100644 lib/api_arith.cpp create mode 100644 lib/api_array.cpp create mode 100644 lib/api_ast.cpp create mode 100644 lib/api_ast_map.cpp create mode 100644 lib/api_ast_map.h create mode 100644 lib/api_ast_vector.cpp create mode 100644 lib/api_ast_vector.h create mode 100644 lib/api_bv.cpp create mode 100644 lib/api_commands.cpp create mode 100644 lib/api_config_params.cpp create mode 100644 lib/api_config_params.h create mode 100644 lib/api_context.cpp create mode 100644 lib/api_context.h create mode 100644 lib/api_datalog.cpp create mode 100644 lib/api_datalog.h create mode 100644 lib/api_datatype.cpp create mode 100644 lib/api_goal.cpp create mode 100644 lib/api_goal.h create mode 100644 lib/api_log.cpp create mode 100644 lib/api_log_macros.cpp create mode 100644 lib/api_log_macros.h create mode 100644 lib/api_model.cpp create mode 100644 lib/api_model.h create mode 100644 lib/api_numeral.cpp create mode 100644 lib/api_params.cpp create mode 100644 lib/api_parsers.cpp create mode 100644 lib/api_quant.cpp create mode 100644 lib/api_solver.cpp create mode 100644 lib/api_solver.h create mode 100644 lib/api_solver_old.cpp create mode 100644 lib/api_stats.cpp create mode 100644 lib/api_stats.h create mode 100644 lib/api_tactic.cpp create mode 100644 lib/api_tactic.h create mode 100644 lib/api_user_theory.cpp create mode 100644 lib/api_util.h create mode 100644 lib/approx_nat.cpp create mode 100644 lib/approx_nat.h create mode 100644 lib/approx_set.cpp create mode 100644 lib/approx_set.h create mode 100644 lib/arith_bounds_tactic.cpp create mode 100644 lib/arith_bounds_tactic.h create mode 100644 lib/arith_decl_plugin.cpp create mode 100644 lib/arith_decl_plugin.h create mode 100644 lib/arith_eq_adapter.cpp create mode 100644 lib/arith_eq_adapter.h create mode 100644 lib/arith_eq_solver.cpp create mode 100644 lib/arith_eq_solver.h create mode 100644 lib/arith_rewriter.cpp create mode 100644 lib/arith_rewriter.h create mode 100644 lib/arith_simplifier_params.cpp create mode 100644 lib/arith_simplifier_params.h create mode 100644 lib/arith_simplifier_plugin.cpp create mode 100644 lib/arith_simplifier_plugin.h create mode 100644 lib/arith_solver_plugin.cpp create mode 100644 lib/arith_solver_plugin.h create mode 100644 lib/array.h create mode 100644 lib/array_decl_plugin.cpp create mode 100644 lib/array_decl_plugin.h create mode 100644 lib/array_factory.cpp create mode 100644 lib/array_factory.h create mode 100644 lib/array_map.h create mode 100644 lib/array_property_expander.cpp create mode 100644 lib/array_property_expander.h create mode 100644 lib/array_property_recognizer.cpp create mode 100644 lib/array_property_recognizer.h create mode 100644 lib/array_rewriter.cpp create mode 100644 lib/array_rewriter.h create mode 100644 lib/array_simplifier_plugin.cpp create mode 100644 lib/array_simplifier_plugin.h create mode 100644 lib/asserted_formulas.cpp create mode 100644 lib/asserted_formulas.h create mode 100644 lib/assertion_set.cpp create mode 100644 lib/assertion_set.h create mode 100644 lib/assertion_set.txt create mode 100644 lib/assertion_set2sat.cpp create mode 100644 lib/assertion_set2sat.h create mode 100644 lib/assertion_set_rewriter.cpp create mode 100644 lib/assertion_set_rewriter.h create mode 100644 lib/assertion_set_strategy.cpp create mode 100644 lib/assertion_set_strategy.h create mode 100644 lib/assertion_set_util.cpp create mode 100644 lib/assertion_set_util.h create mode 100644 lib/assertion_stack.cpp create mode 100644 lib/assertion_stack.h create mode 100644 lib/ast.cpp create mode 100644 lib/ast.h create mode 100644 lib/ast_dag_pp.cpp create mode 100644 lib/ast_dag_pp.h create mode 100644 lib/ast_list.h create mode 100644 lib/ast_ll_pp.cpp create mode 100644 lib/ast_ll_pp.h create mode 100644 lib/ast_lt.cpp create mode 100644 lib/ast_lt.h create mode 100644 lib/ast_pp.cpp create mode 100644 lib/ast_pp.h create mode 100644 lib/ast_smt2_pp.cpp create mode 100644 lib/ast_smt2_pp.h create mode 100644 lib/ast_smt_pp.cpp create mode 100644 lib/ast_smt_pp.h create mode 100644 lib/ast_translation.cpp create mode 100644 lib/ast_translation.h create mode 100644 lib/ast_util.cpp create mode 100644 lib/ast_util.h create mode 100644 lib/atom2bool_var.cpp create mode 100644 lib/atom2bool_var.h create mode 100644 lib/backtrackable_set.h create mode 100644 lib/base_simplifier.h create mode 100644 lib/basic_cmds.cpp create mode 100644 lib/basic_cmds.h create mode 100644 lib/basic_interval.h create mode 100644 lib/basic_simplifier_plugin.cpp create mode 100644 lib/basic_simplifier_plugin.h create mode 100644 lib/big_rational.h create mode 100644 lib/bit2int.cpp create mode 100644 lib/bit2int.h create mode 100644 lib/bit_blaster.cpp create mode 100644 lib/bit_blaster.h create mode 100644 lib/bit_blaster_model_converter.cpp create mode 100644 lib/bit_blaster_model_converter.h create mode 100644 lib/bit_blaster_params.h create mode 100644 lib/bit_blaster_rewriter.cpp create mode 100644 lib/bit_blaster_rewriter.h create mode 100644 lib/bit_blaster_tactic.cpp create mode 100644 lib/bit_blaster_tactic.h create mode 100644 lib/bit_blaster_tpl.h create mode 100644 lib/bit_blaster_tpl_def.h create mode 100644 lib/bit_util.cpp create mode 100644 lib/bit_util.h create mode 100644 lib/bit_vector.cpp create mode 100644 lib/bit_vector.h create mode 100644 lib/bool_rewriter.cpp create mode 100644 lib/bool_rewriter.h create mode 100644 lib/bound_manager.cpp create mode 100644 lib/bound_manager.h create mode 100644 lib/bound_propagator.cpp create mode 100644 lib/bound_propagator.h create mode 100644 lib/buffer.h create mode 100644 lib/bv1_blaster_tactic.cpp create mode 100644 lib/bv1_blaster_tactic.h create mode 100644 lib/bv2int_rewriter.cpp create mode 100644 lib/bv2int_rewriter.h create mode 100644 lib/bv2real_rewriter.cpp create mode 100644 lib/bv2real_rewriter.h create mode 100644 lib/bv_decl_plugin.cpp create mode 100644 lib/bv_decl_plugin.h create mode 100644 lib/bv_elim.cpp create mode 100644 lib/bv_elim.h create mode 100644 lib/bv_rewriter.cpp create mode 100644 lib/bv_rewriter.h create mode 100644 lib/bv_simplifier_params.h create mode 100644 lib/bv_simplifier_plugin.cpp create mode 100644 lib/bv_simplifier_plugin.h create mode 100644 lib/bv_size_reduction_tactic.cpp create mode 100644 lib/bv_size_reduction_tactic.h create mode 100644 lib/cached_var_subst.cpp create mode 100644 lib/cached_var_subst.h create mode 100644 lib/cancel_eh.h create mode 100644 lib/chashtable.h create mode 100644 lib/check_logic.cpp create mode 100644 lib/check_logic.h create mode 100644 lib/check_sat_result.h create mode 100644 lib/cmd_context.cpp create mode 100644 lib/cmd_context.h create mode 100644 lib/cmd_context_types.cpp create mode 100644 lib/cmd_context_types.h create mode 100644 lib/cmd_util.cpp create mode 100644 lib/cmd_util.h create mode 100644 lib/cnf.cpp create mode 100644 lib/cnf.h create mode 100644 lib/cnf_params.cpp create mode 100644 lib/cnf_params.h create mode 100644 lib/cofactor_elim_term_ite.cpp create mode 100644 lib/cofactor_elim_term_ite.h create mode 100644 lib/cofactor_term_ite_tactic.cpp create mode 100644 lib/cofactor_term_ite_tactic.h create mode 100644 lib/contains_var.h create mode 100644 lib/converter.h create mode 100644 lib/cooperate.cpp create mode 100644 lib/cooperate.h create mode 100644 lib/cost_evaluator.cpp create mode 100644 lib/cost_evaluator.h create mode 100644 lib/cost_parser.cpp create mode 100644 lib/cost_parser.h create mode 100644 lib/critical_flet.h create mode 100644 lib/ctx_simplify_tactic.cpp create mode 100644 lib/ctx_simplify_tactic.h create mode 100644 lib/ctx_solver_simplify_tactic.cpp create mode 100644 lib/ctx_solver_simplify_tactic.h create mode 100644 lib/database.h create mode 100644 lib/database.smt create mode 100644 lib/datalog_parser.cpp create mode 100644 lib/datalog_parser.h create mode 100644 lib/datatype_decl_plugin.cpp create mode 100644 lib/datatype_decl_plugin.h create mode 100644 lib/datatype_factory.cpp create mode 100644 lib/datatype_factory.h create mode 100644 lib/datatype_rewriter.cpp create mode 100644 lib/datatype_rewriter.h create mode 100644 lib/datatype_simplifier_plugin.cpp create mode 100644 lib/datatype_simplifier_plugin.h create mode 100644 lib/dbg_cmds.cpp create mode 100644 lib/dbg_cmds.h create mode 100644 lib/debug.cpp create mode 100644 lib/debug.h create mode 100644 lib/dec_ref_util.h create mode 100644 lib/decl_collector.cpp create mode 100644 lib/decl_collector.h create mode 100644 lib/default_tactic.cpp create mode 100644 lib/default_tactic.h create mode 100644 lib/defined_names.cpp create mode 100644 lib/defined_names.h create mode 100644 lib/degree_shift_tactic.cpp create mode 100644 lib/degree_shift_tactic.h create mode 100644 lib/demodulator.cpp create mode 100644 lib/demodulator.h create mode 100644 lib/dependency.h create mode 100644 lib/der.cpp create mode 100644 lib/der.h create mode 100644 lib/dictionary.h create mode 100644 lib/diff_logic.h create mode 100644 lib/diff_neq_tactic.cpp create mode 100644 lib/diff_neq_tactic.h create mode 100644 lib/dimacs.cpp create mode 100644 lib/dimacs.h create mode 100644 lib/distribute_forall.cpp create mode 100644 lib/distribute_forall.h create mode 100644 lib/distribute_forall_tactic.cpp create mode 100644 lib/distribute_forall_tactic.h create mode 100644 lib/dl_base.cpp create mode 100644 lib/dl_base.h create mode 100644 lib/dl_bmc_engine.cpp create mode 100644 lib/dl_bmc_engine.h create mode 100644 lib/dl_bound_relation.cpp create mode 100644 lib/dl_bound_relation.h create mode 100644 lib/dl_check_table.cpp create mode 100644 lib/dl_check_table.h create mode 100644 lib/dl_cmds.cpp create mode 100644 lib/dl_cmds.h create mode 100644 lib/dl_compiler.cpp create mode 100644 lib/dl_compiler.h create mode 100644 lib/dl_context.cpp create mode 100644 lib/dl_context.h create mode 100644 lib/dl_costs.cpp create mode 100644 lib/dl_costs.h create mode 100644 lib/dl_decl_plugin.cpp create mode 100644 lib/dl_decl_plugin.h create mode 100644 lib/dl_external_relation.cpp create mode 100644 lib/dl_external_relation.h create mode 100644 lib/dl_finite_product_relation.cpp create mode 100644 lib/dl_finite_product_relation.h create mode 100644 lib/dl_instruction.cpp create mode 100644 lib/dl_instruction.h create mode 100644 lib/dl_interval_relation.cpp create mode 100644 lib/dl_interval_relation.h create mode 100644 lib/dl_mk_bit_blast.cpp create mode 100644 lib/dl_mk_bit_blast.h create mode 100644 lib/dl_mk_coi_filter.cpp create mode 100644 lib/dl_mk_coi_filter.h create mode 100644 lib/dl_mk_explanations.cpp create mode 100644 lib/dl_mk_explanations.h create mode 100644 lib/dl_mk_filter_rules.cpp create mode 100644 lib/dl_mk_filter_rules.h create mode 100644 lib/dl_mk_interp_tail_simplifier.cpp create mode 100644 lib/dl_mk_interp_tail_simplifier.h create mode 100644 lib/dl_mk_magic_sets.cpp create mode 100644 lib/dl_mk_magic_sets.h create mode 100644 lib/dl_mk_partial_equiv.cpp create mode 100644 lib/dl_mk_partial_equiv.h create mode 100644 lib/dl_mk_rule_inliner.cpp create mode 100644 lib/dl_mk_rule_inliner.h create mode 100644 lib/dl_mk_similarity_compressor.cpp create mode 100644 lib/dl_mk_similarity_compressor.h create mode 100644 lib/dl_mk_simple_joins.cpp create mode 100644 lib/dl_mk_simple_joins.h create mode 100644 lib/dl_mk_slice.cpp create mode 100644 lib/dl_mk_slice.h create mode 100644 lib/dl_mk_subsumption_checker.cpp create mode 100644 lib/dl_mk_subsumption_checker.h create mode 100644 lib/dl_mk_unbound_compressor.cpp create mode 100644 lib/dl_mk_unbound_compressor.h create mode 100644 lib/dl_product_relation.cpp create mode 100644 lib/dl_product_relation.h create mode 100644 lib/dl_relation_manager.cpp create mode 100644 lib/dl_relation_manager.h create mode 100644 lib/dl_rewriter.cpp create mode 100644 lib/dl_rewriter.h create mode 100644 lib/dl_rule.cpp create mode 100644 lib/dl_rule.h create mode 100644 lib/dl_rule_set.cpp create mode 100644 lib/dl_rule_set.h create mode 100644 lib/dl_rule_subsumption_index.cpp create mode 100644 lib/dl_rule_subsumption_index.h create mode 100644 lib/dl_rule_transformer.cpp create mode 100644 lib/dl_rule_transformer.h create mode 100644 lib/dl_sieve_relation.cpp create mode 100644 lib/dl_sieve_relation.h create mode 100644 lib/dl_simplifier_plugin.cpp create mode 100644 lib/dl_simplifier_plugin.h create mode 100644 lib/dl_skip_table.cpp create mode 100644 lib/dl_skip_table.h create mode 100644 lib/dl_smt_relation.cpp create mode 100644 lib/dl_smt_relation.h create mode 100644 lib/dl_sparse_table.cpp create mode 100644 lib/dl_sparse_table.h create mode 100644 lib/dl_table.cpp create mode 100644 lib/dl_table.h create mode 100644 lib/dl_table_plugin.h create mode 100644 lib/dl_table_relation.cpp create mode 100644 lib/dl_table_relation.h create mode 100644 lib/dl_util.cpp create mode 100644 lib/dl_util.h create mode 100644 lib/dl_vector_relation.h create mode 100644 lib/dlist.h create mode 100644 lib/double_manager.h create mode 100644 lib/dummy_big_rational.h create mode 100644 lib/dyn_ack.cpp create mode 100644 lib/dyn_ack.h create mode 100644 lib/dyn_ack_params.cpp create mode 100644 lib/dyn_ack_params.h create mode 100644 lib/eager_bit_blaster.cpp create mode 100644 lib/eager_bit_blaster.h create mode 100644 lib/elim_bounds.cpp create mode 100644 lib/elim_bounds.h create mode 100644 lib/elim_distinct.cpp create mode 100644 lib/elim_distinct.h create mode 100644 lib/elim_term_ite.cpp create mode 100644 lib/elim_term_ite.h create mode 100644 lib/elim_term_ite_strategy.cpp create mode 100644 lib/elim_term_ite_strategy.h create mode 100644 lib/elim_term_ite_tactic.cpp create mode 100644 lib/elim_term_ite_tactic.h create mode 100644 lib/elim_uncnstr_tactic.cpp create mode 100644 lib/elim_uncnstr_tactic.h create mode 100644 lib/elim_var_model_converter.cpp create mode 100644 lib/elim_var_model_converter.h create mode 100644 lib/error_codes.h create mode 100644 lib/euclidean_solver.cpp create mode 100644 lib/euclidean_solver.h create mode 100644 lib/eval_cmd.cpp create mode 100644 lib/eval_cmd.h create mode 100644 lib/event_handler.h create mode 100644 lib/expr2dot.cpp create mode 100644 lib/expr2dot.h create mode 100644 lib/expr2polynomial.cpp create mode 100644 lib/expr2polynomial.h create mode 100644 lib/expr2subpaving.cpp create mode 100644 lib/expr2subpaving.h create mode 100644 lib/expr2var.cpp create mode 100644 lib/expr2var.h create mode 100644 lib/expr_abstract.cpp create mode 100644 lib/expr_abstract.h create mode 100644 lib/expr_context_simplifier.cpp create mode 100644 lib/expr_context_simplifier.h create mode 100644 lib/expr_delta.cpp create mode 100644 lib/expr_delta.h create mode 100644 lib/expr_delta_pair.h create mode 100644 lib/expr_functors.cpp create mode 100644 lib/expr_functors.h create mode 100644 lib/expr_map.cpp create mode 100644 lib/expr_map.h create mode 100644 lib/expr_offset.h create mode 100644 lib/expr_offset_map.h create mode 100644 lib/expr_pattern_match.cpp create mode 100644 lib/expr_pattern_match.h create mode 100644 lib/expr_rand.cpp create mode 100644 lib/expr_rand.h create mode 100644 lib/expr_replacer.cpp create mode 100644 lib/expr_replacer.h create mode 100644 lib/expr_stat.cpp create mode 100644 lib/expr_stat.h create mode 100644 lib/expr_substitution.cpp create mode 100644 lib/expr_substitution.h create mode 100644 lib/expr_weight.cpp create mode 100644 lib/expr_weight.h create mode 100644 lib/ext_gcd.h create mode 100644 lib/ext_numeral.h create mode 100644 lib/extension_model_converter.cpp create mode 100644 lib/extension_model_converter.h create mode 100644 lib/f2n.h create mode 100644 lib/factor_rewriter.cpp create mode 100644 lib/factor_rewriter.h create mode 100644 lib/factor_tactic.cpp create mode 100644 lib/factor_tactic.h create mode 100644 lib/filter_model_converter.cpp create mode 100644 lib/filter_model_converter.h create mode 100644 lib/fingerprints.cpp create mode 100644 lib/fingerprints.h create mode 100644 lib/fix_dl_var_tactic.cpp create mode 100644 lib/fix_dl_var_tactic.h create mode 100644 lib/float_decl_plugin.cpp create mode 100644 lib/float_decl_plugin.h create mode 100644 lib/float_rewriter.cpp create mode 100644 lib/float_rewriter.h create mode 100644 lib/fm_tactic.cpp create mode 100644 lib/fm_tactic.h create mode 100644 lib/for_each_ast.cpp create mode 100644 lib/for_each_ast.h create mode 100644 lib/for_each_expr.cpp create mode 100644 lib/for_each_expr.h create mode 100644 lib/format.cpp create mode 100644 lib/format.h create mode 100644 lib/fpa2bv_converter.cpp create mode 100644 lib/fpa2bv_converter.h create mode 100644 lib/fpa2bv_tactic.cpp create mode 100644 lib/fpa2bv_tactic.h create mode 100644 lib/front_end_params.cpp create mode 100644 lib/front_end_params.h create mode 100644 lib/func_decl_dependencies.cpp create mode 100644 lib/func_decl_dependencies.h create mode 100644 lib/func_interp.cpp create mode 100644 lib/func_interp.h create mode 100644 lib/fvi.h create mode 100644 lib/fvi_def.h create mode 100644 lib/gaussian_elim.cpp create mode 100644 lib/gaussian_elim.h create mode 100644 lib/gl_tactic.cpp create mode 100644 lib/gl_tactic.h create mode 100644 lib/gmp_big_rational.cpp create mode 100644 lib/gmp_big_rational.h create mode 100644 lib/goal.cpp create mode 100644 lib/goal.h create mode 100644 lib/goal2nlsat.cpp create mode 100644 lib/goal2nlsat.h create mode 100644 lib/goal2sat.cpp create mode 100644 lib/goal2sat.h create mode 100644 lib/goal_shared_occs.cpp create mode 100644 lib/goal_shared_occs.h create mode 100644 lib/goal_util.cpp create mode 100644 lib/goal_util.h create mode 100644 lib/grobner.cpp create mode 100644 lib/grobner.h create mode 100644 lib/has_free_vars.cpp create mode 100644 lib/has_free_vars.h create mode 100644 lib/hash.cpp create mode 100644 lib/hash.h create mode 100644 lib/hashtable.h create mode 100644 lib/heap.h create mode 100644 lib/horn_subsume_model_converter.cpp create mode 100644 lib/horn_subsume_model_converter.h create mode 100644 lib/hwf.cpp create mode 100644 lib/hwf.h create mode 100644 lib/id_gen.h create mode 100644 lib/imdd.cpp create mode 100644 lib/imdd.h create mode 100644 lib/inf_int_rational.cpp create mode 100644 lib/inf_int_rational.h create mode 100644 lib/inf_rational.cpp create mode 100644 lib/inf_rational.h create mode 100644 lib/inf_s_integer.cpp create mode 100644 lib/inf_s_integer.h create mode 100644 lib/ini_file.cpp create mode 100644 lib/ini_file.h create mode 100644 lib/inj_axiom.cpp create mode 100644 lib/inj_axiom.h create mode 100644 lib/install_tactics.cpp create mode 100644 lib/install_tactics.h create mode 100644 lib/instruction_count.cpp create mode 100644 lib/instruction_count.h create mode 100644 lib/interval.h create mode 100644 lib/interval_def.h create mode 100644 lib/interval_skip_list.h create mode 100644 lib/kbo.cpp create mode 100644 lib/kbo.h create mode 100644 lib/lbool.cpp create mode 100644 lib/lbool.h create mode 100644 lib/lia2pb_tactic.cpp create mode 100644 lib/lia2pb_tactic.h create mode 100644 lib/lib.vcxproj create mode 100644 lib/lib.vcxproj.user create mode 100644 lib/linear_eq_solver.h create mode 100644 lib/linear_equation.cpp create mode 100644 lib/linear_equation.h create mode 100644 lib/list.h create mode 100644 lib/lockless_queue.h create mode 100644 lib/lpo.cpp create mode 100644 lib/lpo.h create mode 100644 lib/lru_cache.cpp create mode 100644 lib/lru_cache.h create mode 100644 lib/lu.cpp create mode 100644 lib/lu.h create mode 100644 lib/luby.cpp create mode 100644 lib/luby.h create mode 100644 lib/machine.h create mode 100644 lib/macro_finder.cpp create mode 100644 lib/macro_finder.h create mode 100644 lib/macro_manager.cpp create mode 100644 lib/macro_manager.h create mode 100644 lib/macro_substitution.cpp create mode 100644 lib/macro_substitution.h create mode 100644 lib/macro_util.cpp create mode 100644 lib/macro_util.h create mode 100644 lib/mam.cpp create mode 100644 lib/mam.h create mode 100644 lib/map.h create mode 100644 lib/marker.h create mode 100644 lib/matcher.cpp create mode 100644 lib/matcher.h create mode 100644 lib/max_bv_sharing_tactic.cpp create mode 100644 lib/max_bv_sharing_tactic.h create mode 100644 lib/maximise_ac_sharing.cpp create mode 100644 lib/maximise_ac_sharing.h create mode 100644 lib/mem_stat.cpp create mode 100644 lib/mem_stat.h create mode 100644 lib/memory_manager.cpp create mode 100644 lib/memory_manager.h create mode 100644 lib/mip_tactic.cpp create mode 100644 lib/mip_tactic.h create mode 100644 lib/mk_database.sh create mode 100644 lib/mk_simplified_app.cpp create mode 100644 lib/mk_simplified_app.h create mode 100644 lib/model.cpp create mode 100644 lib/model.h create mode 100644 lib/model2expr.cpp create mode 100644 lib/model2expr.h create mode 100644 lib/model_converter.cpp create mode 100644 lib/model_converter.h create mode 100644 lib/model_core.cpp create mode 100644 lib/model_core.h create mode 100644 lib/model_evaluator.cpp create mode 100644 lib/model_evaluator.h create mode 100644 lib/model_evaluator_params.cpp create mode 100644 lib/model_params.cpp create mode 100644 lib/model_params.h create mode 100644 lib/model_pp.cpp create mode 100644 lib/model_pp.h create mode 100644 lib/model_smt2_pp.cpp create mode 100644 lib/model_smt2_pp.h create mode 100644 lib/model_v2_pp.cpp create mode 100644 lib/model_v2_pp.h create mode 100644 lib/mpbq.cpp create mode 100644 lib/mpbq.h create mode 100644 lib/mpbqi.h create mode 100644 lib/mpf.cpp create mode 100644 lib/mpf.h create mode 100644 lib/mpff.cpp create mode 100644 lib/mpff.h create mode 100644 lib/mpfx.cpp create mode 100644 lib/mpfx.h create mode 100644 lib/mpn.cpp create mode 100644 lib/mpn.h create mode 100644 lib/mpq.cpp create mode 100644 lib/mpq.h create mode 100644 lib/mpq_inf.cpp create mode 100644 lib/mpq_inf.h create mode 100644 lib/mpz.cpp create mode 100644 lib/mpz.h create mode 100644 lib/mpzzp.h create mode 100644 lib/name_exprs.cpp create mode 100644 lib/name_exprs.h create mode 100644 lib/nat_set.h create mode 100644 lib/ni_solver.cpp create mode 100644 lib/ni_solver.h create mode 100644 lib/nla2bv_tactic.cpp create mode 100644 lib/nla2bv_tactic.h create mode 100644 lib/nlarith_util.cpp create mode 100644 lib/nlarith_util.h create mode 100644 lib/nlsat_assignment.h create mode 100644 lib/nlsat_clause.cpp create mode 100644 lib/nlsat_clause.h create mode 100644 lib/nlsat_evaluator.cpp create mode 100644 lib/nlsat_evaluator.h create mode 100644 lib/nlsat_explain.cpp create mode 100644 lib/nlsat_explain.h create mode 100644 lib/nlsat_interval_set.cpp create mode 100644 lib/nlsat_interval_set.h create mode 100644 lib/nlsat_justification.h create mode 100644 lib/nlsat_scoped_literal_vector.h create mode 100644 lib/nlsat_solver.cpp create mode 100644 lib/nlsat_solver.h create mode 100644 lib/nlsat_tactic.cpp create mode 100644 lib/nlsat_tactic.h create mode 100644 lib/nlsat_types.cpp create mode 100644 lib/nlsat_types.h create mode 100644 lib/nnf.cpp create mode 100644 lib/nnf.h create mode 100644 lib/nnf_params.cpp create mode 100644 lib/nnf_params.h create mode 100644 lib/nnf_tactic.cpp create mode 100644 lib/nnf_tactic.h create mode 100644 lib/normalize_bounds_tactic.cpp create mode 100644 lib/normalize_bounds_tactic.h create mode 100644 lib/normalize_vars.cpp create mode 100644 lib/normalize_vars.h create mode 100644 lib/nra_tactic.cpp create mode 100644 lib/nra_tactic.h create mode 100644 lib/num_occurs.cpp create mode 100644 lib/num_occurs.h create mode 100644 lib/numeral_buffer.h create mode 100644 lib/numeral_factory.cpp create mode 100644 lib/numeral_factory.h create mode 100644 lib/obj_hashtable.h create mode 100644 lib/obj_mark.h create mode 100644 lib/obj_pair_hashtable.h create mode 100644 lib/obj_pair_set.h create mode 100644 lib/obj_ref.h create mode 100644 lib/obj_triple_hashtable.h create mode 100644 lib/object_allocator.h create mode 100644 lib/occf_tactic.cpp create mode 100644 lib/occf_tactic.h create mode 100644 lib/occurs.cpp create mode 100644 lib/occurs.h create mode 100644 lib/old_interval.cpp create mode 100644 lib/old_interval.h create mode 100644 lib/optional.h create mode 100644 lib/order.cpp create mode 100644 lib/order.h create mode 100644 lib/order_params.cpp create mode 100644 lib/order_params.h create mode 100644 lib/page.cpp create mode 100644 lib/page.h create mode 100644 lib/parameters.h create mode 100644 lib/parametric_cmd.cpp create mode 100644 lib/parametric_cmd.h create mode 100644 lib/params.cpp create mode 100644 lib/params.h create mode 100644 lib/params2front_end_params.cpp create mode 100644 lib/params2front_end_params.h create mode 100644 lib/parray.h create mode 100644 lib/parser_params.cpp create mode 100644 lib/parser_params.h create mode 100644 lib/pattern_inference.cpp create mode 100644 lib/pattern_inference.h create mode 100644 lib/pattern_inference_params.cpp create mode 100644 lib/pattern_inference_params.h create mode 100644 lib/pattern_validation.cpp create mode 100644 lib/pattern_validation.h create mode 100644 lib/pb2bv_model_converter.cpp create mode 100644 lib/pb2bv_model_converter.h create mode 100644 lib/pb2bv_tactic.cpp create mode 100644 lib/pb2bv_tactic.h create mode 100644 lib/pdecl.cpp create mode 100644 lib/pdecl.h create mode 100644 lib/pdr_context.cpp create mode 100644 lib/pdr_context.h create mode 100644 lib/pdr_dl_interface.cpp create mode 100644 lib/pdr_dl_interface.h create mode 100644 lib/pdr_farkas_learner.cpp create mode 100644 lib/pdr_farkas_learner.h create mode 100644 lib/pdr_generalizers.cpp create mode 100644 lib/pdr_generalizers.h create mode 100644 lib/pdr_interpolant_provider.cpp create mode 100644 lib/pdr_interpolant_provider.h create mode 100644 lib/pdr_manager.cpp create mode 100644 lib/pdr_manager.h create mode 100644 lib/pdr_prop_solver.cpp create mode 100644 lib/pdr_prop_solver.h create mode 100644 lib/pdr_quantifiers.cpp create mode 100644 lib/pdr_quantifiers.h create mode 100644 lib/pdr_reachable_cache.cpp create mode 100644 lib/pdr_reachable_cache.h create mode 100644 lib/pdr_smt_context_manager.cpp create mode 100644 lib/pdr_smt_context_manager.h create mode 100644 lib/pdr_sym_mux.cpp create mode 100644 lib/pdr_sym_mux.h create mode 100644 lib/pdr_util.cpp create mode 100644 lib/pdr_util.h create mode 100644 lib/permutation.cpp create mode 100644 lib/permutation.h create mode 100644 lib/plugin_manager.h create mode 100644 lib/poly_rewriter.h create mode 100644 lib/poly_rewriter_def.h create mode 100644 lib/poly_simplifier_plugin.cpp create mode 100644 lib/poly_simplifier_plugin.h create mode 100644 lib/polynomial.cpp create mode 100644 lib/polynomial.h create mode 100644 lib/polynomial_cache.cpp create mode 100644 lib/polynomial_cache.h create mode 100644 lib/polynomial_cmds.cpp create mode 100644 lib/polynomial_cmds.h create mode 100644 lib/polynomial_factorization.cpp create mode 100644 lib/polynomial_factorization.h create mode 100644 lib/polynomial_primes.h create mode 100644 lib/polynomial_var2value.h create mode 100644 lib/pool.h create mode 100644 lib/pop_scopes.h create mode 100644 lib/pp.cpp create mode 100644 lib/pp.h create mode 100644 lib/pp_params.cpp create mode 100644 lib/pp_params.h create mode 100644 lib/precedence.cpp create mode 100644 lib/precedence.h create mode 100644 lib/preprocessor.cpp create mode 100644 lib/preprocessor.h create mode 100644 lib/preprocessor_params.h create mode 100644 lib/prime_generator.cpp create mode 100644 lib/prime_generator.h create mode 100644 lib/probe.cpp create mode 100644 lib/probe.h create mode 100644 lib/probe_arith.cpp create mode 100644 lib/probe_arith.h create mode 100644 lib/progress_callback.h create mode 100644 lib/proof_checker.cpp create mode 100644 lib/proof_checker.h create mode 100644 lib/proof_converter.cpp create mode 100644 lib/proof_converter.h create mode 100644 lib/propagate_ineqs_tactic.cpp create mode 100644 lib/propagate_ineqs_tactic.h create mode 100644 lib/propagate_values_tactic.cpp create mode 100644 lib/propagate_values_tactic.h create mode 100644 lib/proto_model.cpp create mode 100644 lib/proto_model.h create mode 100644 lib/ptr_scoped_buffer.h create mode 100644 lib/pull_ite_tree.cpp create mode 100644 lib/pull_ite_tree.h create mode 100644 lib/pull_quant.cpp create mode 100644 lib/pull_quant.h create mode 100644 lib/purify_arith_tactic.cpp create mode 100644 lib/purify_arith_tactic.h create mode 100644 lib/push_app_ite.cpp create mode 100644 lib/push_app_ite.h create mode 100644 lib/qe.cpp create mode 100644 lib/qe.h create mode 100644 lib/qe_arith_plugin.cpp create mode 100644 lib/qe_array_plugin.cpp create mode 100644 lib/qe_bool_plugin.cpp create mode 100644 lib/qe_bv_plugin.cpp create mode 100644 lib/qe_cmd.cpp create mode 100644 lib/qe_cmd.h create mode 100644 lib/qe_datatype_plugin.cpp create mode 100644 lib/qe_dl_plugin.cpp create mode 100644 lib/qe_sat_tactic.cpp create mode 100644 lib/qe_sat_tactic.h create mode 100644 lib/qe_tactic.cpp create mode 100644 lib/qe_tactic.h create mode 100644 lib/qfaufbv_tactic.cpp create mode 100644 lib/qfaufbv_tactic.h create mode 100644 lib/qfauflia_tactic.cpp create mode 100644 lib/qfauflia_tactic.h create mode 100644 lib/qfbv_tactic.cpp create mode 100644 lib/qfbv_tactic.h create mode 100644 lib/qffpa_tactic.cpp create mode 100644 lib/qffpa_tactic.h create mode 100644 lib/qfidl_tactic.cpp create mode 100644 lib/qfidl_tactic.h create mode 100644 lib/qflia_tactic.cpp create mode 100644 lib/qflia_tactic.h create mode 100644 lib/qflra_tactic.cpp create mode 100644 lib/qflra_tactic.h create mode 100644 lib/qfnia_tactic.cpp create mode 100644 lib/qfnia_tactic.h create mode 100644 lib/qfnra_nlsat_tactic.cpp create mode 100644 lib/qfnra_nlsat_tactic.h create mode 100644 lib/qfnra_tactic.cpp create mode 100644 lib/qfnra_tactic.h create mode 100644 lib/qfuf_tactic.cpp create mode 100644 lib/qfuf_tactic.h create mode 100644 lib/qfufbv_tactic.cpp create mode 100644 lib/qfufbv_tactic.h create mode 100644 lib/qi_params.h create mode 100644 lib/qi_queue.cpp create mode 100644 lib/qi_queue.h create mode 100644 lib/quant_hoist.cpp create mode 100644 lib/quant_hoist.h create mode 100644 lib/quant_tactics.cpp create mode 100644 lib/quant_tactics.h create mode 100644 lib/quasi_macros.cpp create mode 100644 lib/quasi_macros.h create mode 100644 lib/rational.cpp create mode 100644 lib/rational.h create mode 100644 lib/recover_01_tactic.cpp create mode 100644 lib/recover_01_tactic.h create mode 100644 lib/recurse_expr.h create mode 100644 lib/recurse_expr_def.h create mode 100644 lib/reduce_args.cpp create mode 100644 lib/reduce_args.h create mode 100644 lib/reduce_args_tactic.cpp create mode 100644 lib/reduce_args_tactic.h create mode 100644 lib/ref.h create mode 100644 lib/ref_buffer.h create mode 100644 lib/ref_util.h create mode 100644 lib/ref_vector.h create mode 100644 lib/region.cpp create mode 100644 lib/region.h create mode 100644 lib/replace_proof_converter.cpp create mode 100644 lib/replace_proof_converter.h create mode 100644 lib/resource_limit.h create mode 100644 lib/rewriter.cpp create mode 100644 lib/rewriter.h create mode 100644 lib/rewriter.txt create mode 100644 lib/rewriter_def.h create mode 100644 lib/rewriter_types.h create mode 100644 lib/rpolynomial.cpp create mode 100644 lib/rpolynomial.h create mode 100644 lib/s_integer.cpp create mode 100644 lib/s_integer.h create mode 100644 lib/sat_asymm_branch.cpp create mode 100644 lib/sat_asymm_branch.h create mode 100644 lib/sat_clause.cpp create mode 100644 lib/sat_clause.h create mode 100644 lib/sat_clause_set.cpp create mode 100644 lib/sat_clause_set.h create mode 100644 lib/sat_clause_use_list.cpp create mode 100644 lib/sat_clause_use_list.h create mode 100644 lib/sat_cleaner.cpp create mode 100644 lib/sat_cleaner.h create mode 100644 lib/sat_config.cpp create mode 100644 lib/sat_config.h create mode 100644 lib/sat_elim_eqs.cpp create mode 100644 lib/sat_elim_eqs.h create mode 100644 lib/sat_extension.h create mode 100644 lib/sat_iff3_finder.cpp create mode 100644 lib/sat_iff3_finder.h create mode 100644 lib/sat_integrity_checker.cpp create mode 100644 lib/sat_integrity_checker.h create mode 100644 lib/sat_justification.h create mode 100644 lib/sat_model_converter.cpp create mode 100644 lib/sat_model_converter.h create mode 100644 lib/sat_probing.cpp create mode 100644 lib/sat_probing.h create mode 100644 lib/sat_scc.cpp create mode 100644 lib/sat_scc.h create mode 100644 lib/sat_simplifier.cpp create mode 100644 lib/sat_simplifier.h create mode 100644 lib/sat_solver.cpp create mode 100644 lib/sat_solver.h create mode 100644 lib/sat_solver_strategy.cpp create mode 100644 lib/sat_solver_strategy.h create mode 100644 lib/sat_tactic.cpp create mode 100644 lib/sat_tactic.h create mode 100644 lib/sat_types.h create mode 100644 lib/sat_var_queue.h create mode 100644 lib/sat_watched.cpp create mode 100644 lib/sat_watched.h create mode 100644 lib/scanner.cpp create mode 100644 lib/scanner.h create mode 100644 lib/scoped_ctrl_c.cpp create mode 100644 lib/scoped_ctrl_c.h create mode 100644 lib/scoped_numeral.h create mode 100644 lib/scoped_numeral_buffer.h create mode 100644 lib/scoped_numeral_vector.h create mode 100644 lib/scoped_ptr_vector.h create mode 100644 lib/scoped_timer.cpp create mode 100644 lib/scoped_timer.h create mode 100644 lib/seq_decl_plugin.cpp create mode 100644 lib/seq_decl_plugin.h create mode 100644 lib/sexpr.cpp create mode 100644 lib/sexpr.h create mode 100644 lib/sexpr2upolynomial.cpp create mode 100644 lib/sexpr2upolynomial.h create mode 100644 lib/shallow_context_simplifier.cpp create mode 100644 lib/shallow_context_simplifier.h create mode 100644 lib/shared_occs.cpp create mode 100644 lib/shared_occs.h create mode 100644 lib/simple_parser.cpp create mode 100644 lib/simple_parser.h create mode 100644 lib/simplifier.cpp create mode 100644 lib/simplifier.h create mode 100644 lib/simplifier_plugin.cpp create mode 100644 lib/simplifier_plugin.h create mode 100644 lib/simplify_cmd.cpp create mode 100644 lib/simplify_cmd.h create mode 100644 lib/simplify_tactic.cpp create mode 100644 lib/simplify_tactic.h create mode 100644 lib/skip_list_base.h create mode 100644 lib/sls_strategy.h create mode 100644 lib/sls_tactic.cpp create mode 100644 lib/sls_tactic.h create mode 100644 lib/small_object_allocator.cpp create mode 100644 lib/small_object_allocator.h create mode 100644 lib/smt2parser.cpp create mode 100644 lib/smt2parser.h create mode 100644 lib/smt2scanner.cpp create mode 100644 lib/smt2scanner.h create mode 100644 lib/smt_almost_cg_table.cpp create mode 100644 lib/smt_almost_cg_table.h create mode 100644 lib/smt_arith.cpp create mode 100644 lib/smt_arith.h create mode 100644 lib/smt_b_justification.h create mode 100644 lib/smt_bool_var_data.h create mode 100644 lib/smt_case_split_queue.cpp create mode 100644 lib/smt_case_split_queue.h create mode 100644 lib/smt_cg_table.cpp create mode 100644 lib/smt_cg_table.h create mode 100644 lib/smt_checker.cpp create mode 100644 lib/smt_checker.h create mode 100644 lib/smt_classifier.h create mode 100644 lib/smt_clause.cpp create mode 100644 lib/smt_clause.h create mode 100644 lib/smt_conflict_resolution.cpp create mode 100644 lib/smt_conflict_resolution.h create mode 100644 lib/smt_context.cpp create mode 100644 lib/smt_context.h create mode 100644 lib/smt_context_inv.cpp create mode 100644 lib/smt_context_pp.cpp create mode 100644 lib/smt_context_stat.cpp create mode 100644 lib/smt_enode.cpp create mode 100644 lib/smt_enode.h create mode 100644 lib/smt_eq_justification.h create mode 100644 lib/smt_euf.cpp create mode 100644 lib/smt_euf.h create mode 100644 lib/smt_failure.h create mode 100644 lib/smt_for_each_relevant_expr.cpp create mode 100644 lib/smt_for_each_relevant_expr.h create mode 100644 lib/smt_formula_compiler.cpp create mode 100644 lib/smt_formula_compiler.h create mode 100644 lib/smt_implied_equalities.cpp create mode 100644 lib/smt_implied_equalities.h create mode 100644 lib/smt_internalizer.cpp create mode 100644 lib/smt_justification.cpp create mode 100644 lib/smt_justification.h create mode 100644 lib/smt_literal.cpp create mode 100644 lib/smt_literal.h create mode 100644 lib/smt_model_checker.cpp create mode 100644 lib/smt_model_checker.h create mode 100644 lib/smt_model_finder.cpp create mode 100644 lib/smt_model_finder.h create mode 100644 lib/smt_model_generator.cpp create mode 100644 lib/smt_model_generator.h create mode 100644 lib/smt_params.cpp create mode 100644 lib/smt_params.h create mode 100644 lib/smt_quantifier.cpp create mode 100644 lib/smt_quantifier.h create mode 100644 lib/smt_quantifier_instances.h create mode 100644 lib/smt_quantifier_stat.cpp create mode 100644 lib/smt_quantifier_stat.h create mode 100644 lib/smt_quick_checker.cpp create mode 100644 lib/smt_quick_checker.h create mode 100644 lib/smt_relevancy.cpp create mode 100644 lib/smt_relevancy.h create mode 100644 lib/smt_setup.cpp create mode 100644 lib/smt_setup.h create mode 100644 lib/smt_solver.cpp create mode 100644 lib/smt_solver.h create mode 100644 lib/smt_solver_exp.cpp create mode 100644 lib/smt_solver_exp.h create mode 100644 lib/smt_solver_strategy.cpp create mode 100644 lib/smt_solver_strategy.h create mode 100644 lib/smt_solver_types.h create mode 100644 lib/smt_statistics.cpp create mode 100644 lib/smt_statistics.h create mode 100644 lib/smt_strategic_solver.cpp create mode 100644 lib/smt_strategic_solver.h create mode 100644 lib/smt_tactic.cpp create mode 100644 lib/smt_tactic.h create mode 100644 lib/smt_theory.cpp create mode 100644 lib/smt_theory.h create mode 100644 lib/smt_theory_var_list.h create mode 100644 lib/smt_trail.h create mode 100644 lib/smt_types.h create mode 100644 lib/smtlib.cpp create mode 100644 lib/smtlib.h create mode 100644 lib/smtlib_solver.cpp create mode 100644 lib/smtlib_solver.h create mode 100644 lib/smtparser.cpp create mode 100644 lib/smtparser.h create mode 100644 lib/solve_eqs_tactic.cpp create mode 100644 lib/solve_eqs_tactic.h create mode 100644 lib/solver.cpp create mode 100644 lib/solver.h create mode 100644 lib/solver_plugin.h create mode 100644 lib/sparse_use_list.h create mode 100644 lib/spc_asserted_literals.cpp create mode 100644 lib/spc_asserted_literals.h create mode 100644 lib/spc_clause.cpp create mode 100644 lib/spc_clause.h create mode 100644 lib/spc_clause_pos_set.h create mode 100644 lib/spc_clause_selection.cpp create mode 100644 lib/spc_clause_selection.h create mode 100644 lib/spc_context.cpp create mode 100644 lib/spc_context.h create mode 100644 lib/spc_decl_plugin.cpp create mode 100644 lib/spc_decl_plugin.h create mode 100644 lib/spc_der.cpp create mode 100644 lib/spc_der.h create mode 100644 lib/spc_eq_resolution.cpp create mode 100644 lib/spc_eq_resolution.h create mode 100644 lib/spc_factoring.cpp create mode 100644 lib/spc_factoring.h create mode 100644 lib/spc_justification.cpp create mode 100644 lib/spc_justification.h create mode 100644 lib/spc_literal.cpp create mode 100644 lib/spc_literal.h create mode 100644 lib/spc_literal_selection.cpp create mode 100644 lib/spc_literal_selection.h create mode 100644 lib/spc_params.cpp create mode 100644 lib/spc_params.h create mode 100644 lib/spc_prover.cpp create mode 100644 lib/spc_prover.h create mode 100644 lib/spc_rewriter.cpp create mode 100644 lib/spc_rewriter.h create mode 100644 lib/spc_semantic_tautology.cpp create mode 100644 lib/spc_semantic_tautology.h create mode 100644 lib/spc_statistics.cpp create mode 100644 lib/spc_statistics.h create mode 100644 lib/spc_subsumption.cpp create mode 100644 lib/spc_subsumption.h create mode 100644 lib/spc_superposition.cpp create mode 100644 lib/spc_superposition.h create mode 100644 lib/spc_unary_inference.cpp create mode 100644 lib/spc_unary_inference.h create mode 100644 lib/splay_tree.h create mode 100644 lib/splay_tree_def.h create mode 100644 lib/splay_tree_map.h create mode 100644 lib/split_clause_tactic.cpp create mode 100644 lib/split_clause_tactic.h create mode 100644 lib/st2tactic.cpp create mode 100644 lib/st2tactic.h create mode 100644 lib/st_cmds.h create mode 100644 lib/stack.cpp create mode 100644 lib/stack.h create mode 100644 lib/static_features.cpp create mode 100644 lib/static_features.h create mode 100644 lib/statistics.cpp create mode 100644 lib/statistics.h create mode 100644 lib/stats.h create mode 100644 lib/stopwatch.h create mode 100644 lib/str_hashtable.h create mode 100644 lib/strategic_solver.cpp create mode 100644 lib/strategic_solver.h create mode 100644 lib/strategy_exception.cpp create mode 100644 lib/strategy_exception.h create mode 100644 lib/stream_buffer.h create mode 100644 lib/string_buffer.h create mode 100644 lib/struct_factory.cpp create mode 100644 lib/struct_factory.h create mode 100644 lib/subpaving.cpp create mode 100644 lib/subpaving.h create mode 100644 lib/subpaving_cmds.cpp create mode 100644 lib/subpaving_cmds.h create mode 100644 lib/subpaving_hwf.cpp create mode 100644 lib/subpaving_hwf.h create mode 100644 lib/subpaving_mpf.cpp create mode 100644 lib/subpaving_mpf.h create mode 100644 lib/subpaving_mpff.cpp create mode 100644 lib/subpaving_mpff.h create mode 100644 lib/subpaving_mpfx.cpp create mode 100644 lib/subpaving_mpfx.h create mode 100644 lib/subpaving_mpq.cpp create mode 100644 lib/subpaving_mpq.h create mode 100644 lib/subpaving_t.h create mode 100644 lib/subpaving_t_def.h create mode 100644 lib/subpaving_tactic.cpp create mode 100644 lib/subpaving_tactic.h create mode 100644 lib/subpaving_types.h create mode 100644 lib/substitution.cpp create mode 100644 lib/substitution.h create mode 100644 lib/substitution_tree.cpp create mode 100644 lib/substitution_tree.h create mode 100644 lib/symbol.cpp create mode 100644 lib/symbol.h create mode 100644 lib/symbol_table.h create mode 100644 lib/symmetry_reduce_tactic.cpp create mode 100644 lib/symmetry_reduce_tactic.h create mode 100644 lib/tactic.cpp create mode 100644 lib/tactic.h create mode 100644 lib/tactic2solver.cpp create mode 100644 lib/tactic2solver.h create mode 100644 lib/tactic_cmds.cpp create mode 100644 lib/tactic_cmds.h create mode 100644 lib/tactic_exception.cpp create mode 100644 lib/tactic_exception.h create mode 100644 lib/tactic_manager.cpp create mode 100644 lib/tactic_manager.h create mode 100644 lib/tactical.cpp create mode 100644 lib/tactical.h create mode 100644 lib/th_rewriter.cpp create mode 100644 lib/th_rewriter.h create mode 100644 lib/theory_arith.cpp create mode 100644 lib/theory_arith.h create mode 100644 lib/theory_arith_aux.h create mode 100644 lib/theory_arith_core.h create mode 100644 lib/theory_arith_def.h create mode 100644 lib/theory_arith_eq.h create mode 100644 lib/theory_arith_int.h create mode 100644 lib/theory_arith_inv.h create mode 100644 lib/theory_arith_nl.h create mode 100644 lib/theory_arith_params.cpp create mode 100644 lib/theory_arith_params.h create mode 100644 lib/theory_arith_pp.h create mode 100644 lib/theory_array.cpp create mode 100644 lib/theory_array.h create mode 100644 lib/theory_array_base.cpp create mode 100644 lib/theory_array_base.h create mode 100644 lib/theory_array_full.cpp create mode 100644 lib/theory_array_full.h create mode 100644 lib/theory_array_params.h create mode 100644 lib/theory_bv.cpp create mode 100644 lib/theory_bv.h create mode 100644 lib/theory_bv_params.h create mode 100644 lib/theory_datatype.cpp create mode 100644 lib/theory_datatype.h create mode 100644 lib/theory_datatype_params.h create mode 100644 lib/theory_dense_diff_logic.cpp create mode 100644 lib/theory_dense_diff_logic.h create mode 100644 lib/theory_dense_diff_logic_def.h create mode 100644 lib/theory_diff_logic.cpp create mode 100644 lib/theory_diff_logic.h create mode 100644 lib/theory_diff_logic_def.h create mode 100644 lib/theory_dl.cpp create mode 100644 lib/theory_dl.h create mode 100644 lib/theory_dummy.cpp create mode 100644 lib/theory_dummy.h create mode 100644 lib/theory_instgen.cpp create mode 100644 lib/theory_instgen.h create mode 100644 lib/theory_seq_empty.h create mode 100644 lib/timeit.cpp create mode 100644 lib/timeit.h create mode 100644 lib/timeout.cpp create mode 100644 lib/timeout.h create mode 100644 lib/timer.cpp create mode 100644 lib/timer.h create mode 100644 lib/total_order.h create mode 100644 lib/tptr.h create mode 100644 lib/trace.cpp create mode 100644 lib/trace.h create mode 100644 lib/trail.h create mode 100644 lib/tseitin_cnf_tactic.cpp create mode 100644 lib/tseitin_cnf_tactic.h create mode 100644 lib/ufbv_strategy.cpp create mode 100644 lib/ufbv_strategy.h create mode 100644 lib/uint_map.h create mode 100644 lib/uint_set.h create mode 100644 lib/unifier.cpp create mode 100644 lib/unifier.h create mode 100644 lib/union_find.h create mode 100644 lib/unit_subsumption_tactic.cpp create mode 100644 lib/unit_subsumption_tactic.h create mode 100644 lib/upolynomial.cpp create mode 100644 lib/upolynomial.h create mode 100644 lib/upolynomial_factorization.cpp create mode 100644 lib/upolynomial_factorization.h create mode 100644 lib/upolynomial_factorization_int.h create mode 100644 lib/use_list.cpp create mode 100644 lib/use_list.h create mode 100644 lib/used_symbols.h create mode 100644 lib/used_vars.cpp create mode 100644 lib/used_vars.h create mode 100644 lib/user_decl_plugin.cpp create mode 100644 lib/user_decl_plugin.h create mode 100644 lib/user_rewriter.cpp create mode 100644 lib/user_rewriter.h create mode 100644 lib/user_simplifier_plugin.cpp create mode 100644 lib/user_simplifier_plugin.h create mode 100644 lib/user_smt_theory.cpp create mode 100644 lib/user_smt_theory.h create mode 100644 lib/uses_theory.cpp create mode 100644 lib/uses_theory.h create mode 100644 lib/util.cpp create mode 100644 lib/util.h create mode 100644 lib/value.cpp create mode 100644 lib/value.h create mode 100644 lib/value_compiler_extension.h create mode 100644 lib/value_factory.cpp create mode 100644 lib/value_factory.h create mode 100644 lib/var_offset_map.h create mode 100644 lib/var_subst.cpp create mode 100644 lib/var_subst.h create mode 100644 lib/vector.h create mode 100644 lib/version.h create mode 100644 lib/vsubst_tactic.cpp create mode 100644 lib/vsubst_tactic.h create mode 100644 lib/warning.cpp create mode 100644 lib/warning.h create mode 100644 lib/watch_list.cpp create mode 100644 lib/watch_list.h create mode 100644 lib/well_sorted.cpp create mode 100644 lib/well_sorted.h create mode 100644 lib/z3.h create mode 100644 lib/z3_api.h create mode 100644 lib/z3_exception.cpp create mode 100644 lib/z3_exception.h create mode 100644 lib/z3_logger.h create mode 100644 lib/z3_macros.h create mode 100644 lib/z3_omp.h create mode 100644 lib/z3_private.h create mode 100644 lib/z3_replayer.cpp create mode 100644 lib/z3_replayer.h create mode 100644 lib/z3_solver.cpp create mode 100644 lib/z3_solver.h create mode 100644 lib/z3_solver_params.cpp create mode 100644 lib/z3_solver_params.h create mode 100644 lib/z3_v1.h diff --git a/lib/act_cache.cpp b/lib/act_cache.cpp new file mode 100644 index 000000000..0ee0afaac --- /dev/null +++ b/lib/act_cache.cpp @@ -0,0 +1,224 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + act_cache.cpp + +Abstract: + + expr -> expr activity cache + It maintains at most N unused entries + +Author: + + Leonardo (leonardo) 2011-04-12 + +Notes: + +--*/ +#include"act_cache.h" + +#define MIN_MAX_UNUSED 1024 +#define INITIAL_CAPACITY 128 + +/* + This cache is a mapping from expr -> tagged expressions + A tagged expression is essentially a pair (expr, flag) + Thus, an entry + t -> (s, 0) + maps the key t to value s, and says that key t was never accessed. + That is, client code never executed find(t) + Similarly, an entry + t -> (s, 1) + also maps the key t to value s, but signs that key t was already accessed + by client code. + + When a new key/value pair is inserted the flag is 0. + The flag is set to 1 after the key is accessed. + + The number of unused entries (m_unused) is equal to the number of entries + of the form + t -> (s, 0) + That is, it is the number of keys that were never accessed by cliend code. + + The cache maintains at most m_max_unused entries. + When the maximum number of unused entries exceeds m_max_unused, then + the cache will delete the oldest unused entry. +*/ + +/** + m_queue stores the recently added keys. + The queue is implemented as pair: m_queue (vector), m_qhead (unsigned). + The "active" part of m_queue is the range [m_qhead, m_queue.size()) + The "inactive" part [0, m_qhead) contains keys that were already used by client code. + This procedure, deletes the inactive part, and makes m_qhead == 0. +*/ +void act_cache::compress_queue() { + SASSERT(m_qhead > 0); + unsigned sz = m_queue.size(); + unsigned j = 0; + for (unsigned i = m_qhead; i < sz; i++, j++) { + m_queue[j] = m_queue[i]; + } + m_queue.shrink(j); + m_qhead = 0; +} + +void act_cache::init() { + if (m_max_unused < MIN_MAX_UNUSED) + m_max_unused = MIN_MAX_UNUSED; + m_unused = 0; + m_qhead = 0; +} + +void act_cache::dec_refs() { + map::iterator it = m_table.begin(); + map::iterator end = m_table.end(); + for (; it != end; ++it) { + m_manager.dec_ref((*it).m_key); + m_manager.dec_ref(UNTAG(expr*, (*it).m_value)); + } +} + +act_cache::act_cache(ast_manager & m): + m_manager(m), + m_max_unused(m.get_num_asts()) { + init(); +} + +act_cache::act_cache(ast_manager & m, unsigned max_unused): + m_manager(m), + m_max_unused(max_unused) { + init(); +} + +act_cache::~act_cache() { + dec_refs(); +} + +/** + \brief Search m_queue from [m_qhead, m_queue.size()) until it finds + an unused key. That is a key associated with an entry + key -> (value, 0) +*/ +void act_cache::del_unused() { + unsigned sz = m_queue.size(); + while (m_qhead < sz) { + expr * k = m_queue[m_qhead]; + m_qhead++; + SASSERT(m_table.contains(k)); + map::key_value * entry = m_table.find_core(k); + SASSERT(entry); + if (GET_TAG(entry->m_value) == 0) { + // Key k was never accessed by client code. + // That is, find(k) was never executed by client code. + m_unused--; + expr * v = entry->m_value; + m_table.erase(k); + m_manager.dec_ref(k); + m_manager.dec_ref(v); + break; + } + } + if (m_qhead == sz) { + // The "active" part of the queue is empty. + // So, we perform a "cheap" compress. + m_queue.reset(); + m_qhead = 0; + } + else if (m_qhead > m_max_unused) { + compress_queue(); + } +} + +/** + \brief Insert a new entry k -> v into the cache. +*/ +void act_cache::insert(expr * k, expr * v) { + SASSERT(k); + if (m_unused >= m_max_unused) + del_unused(); + expr * dummy = reinterpret_cast(1); + map::key_value & entry = m_table.insert_if_not_there(k, dummy); +#if 0 + unsigned static counter = 0; + counter++; + if (counter % 100000 == 0) + verbose_stream() << "[act-cache] counter: " << counter << " used_slots: " << m_table.used_slots() << " capacity: " << m_table.capacity() << " size: " << m_table.size() << " collisions: " << m_table.collisions() << "\n"; +#endif + +#ifdef Z3DEBUG + unsigned expected_tag; +#endif + if (entry.m_value == dummy) { + // new entry; + m_manager.inc_ref(k); + m_manager.inc_ref(v); + entry.m_value = v; + m_queue.push_back(k); + m_unused++; + DEBUG_CODE(expected_tag = 0;); // new entry + } + else if (UNTAG(expr*, entry.m_value) == v) { + // already there + DEBUG_CODE(expected_tag = GET_TAG(entry.m_value);); + } + else { + // replacing old entry + m_manager.inc_ref(v); + m_manager.dec_ref(UNTAG(expr*, entry.m_value)); + entry.m_value = v; + SASSERT(GET_TAG(entry.m_value) == 0); + // replaced old entry, and reset the tag. + DEBUG_CODE(expected_tag = 0;); + } + DEBUG_CODE({ + expr * v2; + SASSERT(m_table.find(k, v2)); + SASSERT(v == UNTAG(expr*, v2)); + SASSERT(expected_tag == GET_TAG(v2)); + }); +} + +/** + \brief Search for key k in the cache. + If entry k -> (v, tag) is found, we set tag to 1. +*/ +expr * act_cache::find(expr * k) { + map::key_value * entry = m_table.find_core(k); + if (entry == 0) + return 0; + if (GET_TAG(entry->m_value) == 0) { + entry->m_value = TAG(expr*, entry->m_value, 1); + SASSERT(GET_TAG(entry->m_value) == 1); + SASSERT(m_unused > 0); + m_unused--; + DEBUG_CODE({ + expr * v; + SASSERT(m_table.find(k, v)); + SASSERT(GET_TAG(v) == 1); + }); + } + return UNTAG(expr*, entry->m_value); +} + +void act_cache::reset() { + dec_refs(); + m_table.reset(); + m_queue.reset(); + m_unused = 0; + m_qhead = 0; +} + +void act_cache::cleanup() { + dec_refs(); + m_table.finalize(); + m_queue.finalize(); + m_unused = 0; + m_qhead = 0; +} + +bool act_cache::check_invariant() const { + return true; +} diff --git a/lib/act_cache.h b/lib/act_cache.h new file mode 100644 index 000000000..0a95f0f40 --- /dev/null +++ b/lib/act_cache.h @@ -0,0 +1,56 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + act_cache.h + +Abstract: + + expr -> expr activity cache + It maintains at most N unused entries + +Author: + + Leonardo (leonardo) 2011-04-12 + +Notes: + +--*/ +#ifndef _ACT_CACHE_H_ +#define _ACT_CACHE_H_ + +#include"ast.h" +#include"obj_hashtable.h" +#include"chashtable.h" + +class act_cache { + ast_manager & m_manager; + typedef cmap, default_eq > map; + map m_table; + ptr_vector m_queue; // recently created queue + unsigned m_qhead; + unsigned m_unused; + unsigned m_max_unused; + + void compress_queue(); + void init(); + void dec_refs(); + void del_unused(); + +public: + act_cache(ast_manager & m); + act_cache(ast_manager & m, unsigned max_unused); + ~act_cache(); + void insert(expr * k, expr * v); + expr * find(expr * k); + void reset(); + void cleanup(); + unsigned size() const { return m_table.size(); } + unsigned capacity() const { return m_table.capacity(); } + bool empty() const { return m_table.empty(); } + bool check_invariant() const; + +}; + +#endif diff --git a/lib/add_bounds.cpp b/lib/add_bounds.cpp new file mode 100644 index 000000000..6702783f3 --- /dev/null +++ b/lib/add_bounds.cpp @@ -0,0 +1,164 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + add_bounds.h + +Abstract: + + Strategy for bounding unbounded variables. + +Author: + + Leonardo de Moura (leonardo) 2011-06-30. + +Revision History: + +--*/ +#include"add_bounds.h" +#include"arith_decl_plugin.h" +#include"ast_smt2_pp.h" +#include"bound_manager.h" +#include"for_each_expr.h" +#include"assertion_set_util.h" + +struct is_unbounded_proc { + struct found {}; + arith_util m_util; + bound_manager & m_bm; + + is_unbounded_proc(bound_manager & bm):m_util(bm.m()), m_bm(bm) {} + + void operator()(app * t) { + if (is_uninterp_const(t) && (m_util.is_int(t) || m_util.is_real(t)) && (!m_bm.has_lower(t) || !m_bm.has_upper(t))) + throw found(); + } + + void operator()(var *) {} + + void operator()(quantifier*) {} +}; + +bool is_unbounded(assertion_set const & s) { + ast_manager & m = s.m(); + bound_manager bm(m); + bm(s); + is_unbounded_proc proc(bm); + return test(s, proc); +} + +struct add_bounds::imp { + ast_manager & m; + rational m_lower; + rational m_upper; + volatile bool m_cancel; + + imp(ast_manager & _m, params_ref const & p): + m(_m) { + updt_params(p); + } + + void updt_params(params_ref const & p) { + m_lower = p.get_rat(":add-bound-lower", rational(-2)); + m_upper = p.get_rat(":add-bound-upper", rational(2)); + } + + void set_cancel(bool f) { + m_cancel = f; + } + + struct add_bound_proc { + arith_util m_util; + bound_manager & m_bm; + assertion_set & m_set; + rational const & m_lower; + rational const & m_upper; + unsigned m_num_bounds; + + add_bound_proc(bound_manager & bm, assertion_set & s, rational const & l, rational const & u): + m_util(bm.m()), + m_bm(bm), + m_set(s), + m_lower(l), + m_upper(u) { + m_num_bounds = 0; + } + + void operator()(app * t) { + if (is_uninterp_const(t) && (m_util.is_int(t) || m_util.is_real(t))) { + if (!m_bm.has_lower(t)) { + m_set.assert_expr(m_util.mk_le(t, m_util.mk_numeral(m_upper, m_util.is_int(t)))); + m_num_bounds++; + } + if (!m_bm.has_upper(t)) { + m_set.assert_expr(m_util.mk_ge(t, m_util.mk_numeral(m_lower, m_util.is_int(t)))); + m_num_bounds++; + } + } + } + + void operator()(var *) {} + + void operator()(quantifier*) {} + }; + + void operator()(assertion_set & s, model_converter_ref & mc) { + mc = 0; + if (s.inconsistent()) + return; + as_st_report report("add-bounds", s); + bound_manager bm(m); + expr_fast_mark1 visited; + add_bound_proc proc(bm, s, m_lower, m_upper); + unsigned sz = s.size(); + for (unsigned i = 0; i < sz; i++) + quick_for_each_expr(proc, visited, s.form(i)); + visited.reset(); + report_st_progress(":added-bounds", proc.m_num_bounds); + TRACE("add_bounds", s.display(tout);); + } +}; + +add_bounds::add_bounds(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); +} + +add_bounds::~add_bounds() { + dealloc(m_imp); +} + +void add_bounds::updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); +} + +void add_bounds::get_param_descrs(param_descrs & r) { + r.insert(":add-bound-lower", CPK_NUMERAL, "(default: -2) lower bound to be added to unbounded variables."); + r.insert(":add-bound-upper", CPK_NUMERAL, "(default: 2) upper bound to be added to unbounded variables."); +} + +void add_bounds::operator()(assertion_set & s, model_converter_ref & mc) { + m_imp->operator()(s, mc); +} + +void add_bounds::set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); +} + +void add_bounds::cleanup() { + ast_manager & m = m_imp->m; + imp * d = m_imp; + #pragma omp critical (as_st_cancel) + { + d = m_imp; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (as_st_cancel) + { + m_imp = d; + } +} diff --git a/lib/add_bounds.h b/lib/add_bounds.h new file mode 100644 index 000000000..5cd1ac484 --- /dev/null +++ b/lib/add_bounds.h @@ -0,0 +1,50 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + add_bounds.h + +Abstract: + + Strategy for bounding unbounded variables. + +Author: + + Leonardo de Moura (leonardo) 2011-06-30. + +Revision History: + +--*/ +#ifndef _ADD_BOUNDS_H_ +#define _ADD_BOUNDS_H_ + +#include"assertion_set_strategy.h" + +bool is_unbounded(assertion_set const & s); + +class add_bounds : public assertion_set_strategy { + struct imp; + imp * m_imp; + params_ref m_params; +public: + add_bounds(ast_manager & m, params_ref const & p = params_ref()); + virtual ~add_bounds(); + + virtual void updt_params(params_ref const & p); + static void get_param_descrs(param_descrs & r); + virtual void collect_param_descrs(param_descrs & r) { get_param_descrs(r); } + + virtual void operator()(assertion_set & s, model_converter_ref & mc); + + virtual void cleanup(); + +protected: + virtual void set_cancel(bool f); +}; + +inline as_st * mk_add_bounds(ast_manager & m, params_ref const & p = params_ref()) { + return clean(alloc(add_bounds, m, p)); +} + +#endif diff --git a/lib/add_bounds_tactic.cpp b/lib/add_bounds_tactic.cpp new file mode 100644 index 000000000..81e9c26cf --- /dev/null +++ b/lib/add_bounds_tactic.cpp @@ -0,0 +1,198 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + add_bounds_tactic.h + +Abstract: + + Tactic for bounding unbounded variables. + +Author: + + Leonardo de Moura (leonardo) 2011-10-22. + +Revision History: + +--*/ +#include"tactical.h" +#include"arith_decl_plugin.h" +#include"ast_smt2_pp.h" +#include"bound_manager.h" + +struct is_unbounded_proc { + struct found {}; + arith_util m_util; + bound_manager & m_bm; + + is_unbounded_proc(bound_manager & bm):m_util(bm.m()), m_bm(bm) {} + + void operator()(app * t) { + if (is_uninterp_const(t) && (m_util.is_int(t) || m_util.is_real(t)) && (!m_bm.has_lower(t) || !m_bm.has_upper(t))) + throw found(); + } + + void operator()(var *) {} + + void operator()(quantifier*) {} +}; + +bool is_unbounded(goal const & g) { + ast_manager & m = g.m(); + bound_manager bm(m); + bm(g); + is_unbounded_proc proc(bm); + return test(g, proc); +} + +class is_unbounded_probe : public probe { +public: + virtual result operator()(goal const & g) { + return is_unbounded(g); + } +}; + +probe * mk_is_unbounded_probe() { + return alloc(is_unbounded_probe); +} + +class add_bounds_tactic : public tactic { + struct imp { + ast_manager & m; + rational m_lower; + rational m_upper; + volatile bool m_cancel; + + imp(ast_manager & _m, params_ref const & p): + m(_m) { + updt_params(p); + } + + void updt_params(params_ref const & p) { + m_lower = p.get_rat(":add-bound-lower", rational(-2)); + m_upper = p.get_rat(":add-bound-upper", rational(2)); + } + + void set_cancel(bool f) { + m_cancel = f; + } + + struct add_bound_proc { + arith_util m_util; + bound_manager & m_bm; + goal & m_goal; + rational const & m_lower; + rational const & m_upper; + unsigned m_num_bounds; + + add_bound_proc(bound_manager & bm, goal & g, rational const & l, rational const & u): + m_util(bm.m()), + m_bm(bm), + m_goal(g), + m_lower(l), + m_upper(u) { + m_num_bounds = 0; + } + + void operator()(app * t) { + if (is_uninterp_const(t) && (m_util.is_int(t) || m_util.is_real(t))) { + if (!m_bm.has_lower(t)) { + m_goal.assert_expr(m_util.mk_le(t, m_util.mk_numeral(m_upper, m_util.is_int(t)))); + m_num_bounds++; + } + if (!m_bm.has_upper(t)) { + m_goal.assert_expr(m_util.mk_ge(t, m_util.mk_numeral(m_lower, m_util.is_int(t)))); + m_num_bounds++; + } + } + } + + void operator()(var *) {} + + void operator()(quantifier*) {} + }; + + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + mc = 0; pc = 0; core = 0; + tactic_report report("add-bounds", *g); + bound_manager bm(m); + expr_fast_mark1 visited; + add_bound_proc proc(bm, *(g.get()), m_lower, m_upper); + unsigned sz = g->size(); + for (unsigned i = 0; i < sz; i++) + quick_for_each_expr(proc, visited, g->form(i)); + visited.reset(); + g->inc_depth(); + result.push_back(g.get()); + if (proc.m_num_bounds > 0) + g->updt_prec(goal::UNDER); + report_tactic_progress(":added-bounds", proc.m_num_bounds); + TRACE("add_bounds", g->display(tout);); + } + }; + + imp * m_imp; + params_ref m_params; + +public: + add_bounds_tactic(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(add_bounds_tactic, m, m_params); + } + + virtual ~add_bounds_tactic() { + dealloc(m_imp); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + r.insert(":add-bound-lower", CPK_NUMERAL, "(default: -2) lower bound to be added to unbounded variables."); + r.insert(":add-bound-upper", CPK_NUMERAL, "(default: 2) upper bound to be added to unbounded variables."); + } + + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + (*m_imp)(g, result, mc, pc, core); + } + + virtual void cleanup() { + ast_manager & m = m_imp->m; + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + d = m_imp; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + } + +protected: + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + +tactic * mk_add_bounds_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(add_bounds_tactic, m, p)); +} diff --git a/lib/add_bounds_tactic.h b/lib/add_bounds_tactic.h new file mode 100644 index 000000000..97b63e1ad --- /dev/null +++ b/lib/add_bounds_tactic.h @@ -0,0 +1,34 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + add_bounds.h + +Abstract: + + Tactic for bounding unbounded variables. + +Author: + + Leonardo de Moura (leonardo) 2011-06-30. + +Revision History: + +--*/ +#ifndef _ADD_BOUNDS_H_ +#define _ADD_BOUNDS_H_ + +#include"params.h" + +class ast_manager; +class goal; +class tactic; +class probe; + +bool is_unbounded(goal const & g); +probe * mk_is_unbounded_probe(); + +tactic * mk_add_bounds_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/aig.cpp b/lib/aig.cpp new file mode 100644 index 000000000..d79f28903 --- /dev/null +++ b/lib/aig.cpp @@ -0,0 +1,1812 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + aig.cpp + +Abstract: + + And-inverted graphs + +Author: + + Leonardo (leonardo) 2011-05-13 + +Notes: + +--*/ +#include"aig.h" +#include"assertion_set.h" // TODO delete +#include"goal.h" +#include"ast_smt2_pp.h" +#include"cooperate.h" + +#define USE_TWO_LEVEL_RULES +#define FIRST_NODE_ID (UINT_MAX/2) + +struct aig; + +class aig_lit { + friend class aig_ref; + aig * m_ref; +public: + aig_lit(aig * n = 0):m_ref(n) {} + aig_lit(aig_ref const & r):m_ref(static_cast(r.m_ref)) {} + bool is_inverted() const { return (reinterpret_cast(m_ref) & static_cast(1)) == static_cast(1); } + void invert() { m_ref = reinterpret_cast(reinterpret_cast(m_ref) ^ static_cast(1)); } + aig * ptr() const { return reinterpret_cast(reinterpret_cast(m_ref) & ~static_cast(1)); } + aig * ptr_non_inverted() const { SASSERT(!is_inverted()); return m_ref; } + bool is_null() const { return m_ref == 0; } + friend bool operator==(aig_lit const & r1, aig_lit const & r2) { return r1.m_ref == r2.m_ref; } + friend bool operator!=(aig_lit const & r1, aig_lit const & r2) { return r1.m_ref != r2.m_ref; } + aig_lit & operator=(aig_lit const & r) { m_ref = r.m_ref; return *this; } + static aig_lit null; +}; + +aig_lit aig_lit::null; + +struct aig { + unsigned m_id; + unsigned m_ref_count; + aig_lit m_children[2]; + unsigned m_mark:1; + aig() {} +}; + +inline bool is_true(aig_lit const & r) { return !r.is_inverted() && r.ptr_non_inverted()->m_id == 0; } +inline bool is_false(aig_lit const & r) { return r.is_inverted() && r.ptr()->m_id == 0; } +inline bool is_var(aig * n) { return n->m_children[0].is_null(); } +inline bool is_var(aig_lit const & n) { return is_var(n.ptr()); } +inline unsigned id(aig_lit const & n) { return n.ptr()->m_id; } +inline unsigned ref_count(aig_lit const & n) { return n.ptr()->m_ref_count; } +inline aig_lit left(aig * n) { return n->m_children[0]; } +inline aig_lit right(aig * n) { return n->m_children[1]; } +inline aig_lit left(aig_lit const & n) { return left(n.ptr()); } +inline aig_lit right(aig_lit const & n) { return right(n.ptr()); } + +inline unsigned to_idx(aig * p) { SASSERT(!is_var(p)); return p->m_id - FIRST_NODE_ID; } + +void unmark(unsigned sz, aig_lit const * ns) { + for (unsigned i = 0; i < sz; i++) { + ns[i].ptr()->m_mark = false; + } +} + +void unmark(unsigned sz, aig * const * ns) { + for (unsigned i = 0; i < sz; i++) { + ns[i]->m_mark = false; + } +} + +struct aig_hash { + unsigned operator()(aig * n) const { + SASSERT(!is_var(n)); + return hash_u_u(id(n->m_children[0]), id(n->m_children[1])); + } +}; + +struct aig_eq { + bool operator()(aig * n1, aig * n2) const { + SASSERT(!is_var(n1)); + SASSERT(!is_var(n2)); + return + n1->m_children[0] == n2->m_children[0] && + n1->m_children[1] == n2->m_children[1]; + } +}; + +class aig_table : public chashtable { +public: +}; + +struct aig_lit_lt { + bool operator()(aig_lit const & l1, aig_lit const & l2) const { + if (id(l1) < id(l2)) return true; + if (id(l1) == id(l2)) return l1.is_inverted() && !l2.is_inverted(); + return false; + } +}; + +struct aig_manager::imp { + id_gen m_var_id_gen; + id_gen m_node_id_gen; + aig_table m_table; + unsigned m_num_aigs; + expr_ref_vector m_var2exprs; + small_object_allocator m_allocator; + ptr_vector m_to_delete; + aig_lit m_true; + aig_lit m_false; + bool m_default_gate_encoding; + unsigned long long m_max_memory; + volatile bool m_cancel; + + void dec_ref_core(aig * n) { + SASSERT(n->m_ref_count > 0); + n->m_ref_count--; + if (n->m_ref_count == 0) + m_to_delete.push_back(n); + } + + void checkpoint() { + if (memory::get_allocation_size() > m_max_memory) + throw aig_exception(TACTIC_MAX_MEMORY_MSG); + if (m_cancel) + throw aig_exception(TACTIC_CANCELED_MSG); + cooperate("aig"); + } + + void dec_ref_core(aig_lit const & r) { dec_ref_core(r.ptr()); } + + void dec_ref_result(aig * n) { SASSERT(n->m_ref_count > 0); n->m_ref_count--; } + void dec_ref_result(aig_lit const & r) { dec_ref_result(r.ptr()); } + + void process_to_delete() { + while (!m_to_delete.empty()) { + aig * n = m_to_delete.back(); + m_to_delete.pop_back(); + delete_node(n); + } + } + + void delete_node(aig * n) { + TRACE("aig_lit_count", tout << "deleting: "; display_ref(tout, n); tout << "\n";); + SASSERT(m_num_aigs > 0); + m_num_aigs--; + if (is_var(n)) { + m_var_id_gen.recycle(n->m_id); + m_var2exprs.set(n->m_id, 0); + } + else { + m_table.erase(n); + m_node_id_gen.recycle(n->m_id); + dec_ref_core(n->m_children[0]); + dec_ref_core(n->m_children[1]); + } + m_allocator.deallocate(sizeof(aig), n); + } + + aig * allocate_node() { + return static_cast(m_allocator.allocate(sizeof(aig))); + } + + aig * mk_var(expr * t) { + m_num_aigs++; + aig * r = allocate_node(); + r->m_id = m_var_id_gen.mk(); + r->m_ref_count = 0; + r->m_mark = false; + r->m_children[0] = aig_lit(); + SASSERT(r->m_id <= m_var2exprs.size()); + if (r->m_id == m_var2exprs.size()) + m_var2exprs.push_back(t); + else + m_var2exprs.set(r->m_id, t); + return r; + } + + aig_lit mk_node_core(aig_lit const & l, aig_lit const & r) { + aig * new_node = allocate_node(); + new_node->m_children[0] = l; + new_node->m_children[1] = r; + aig * old_node = m_table.insert_if_not_there(new_node); + if (old_node != new_node) { + m_allocator.deallocate(sizeof(aig), new_node); + return aig_lit(old_node); + } + m_num_aigs++; + new_node->m_id = m_node_id_gen.mk(); + new_node->m_ref_count = 0; + new_node->m_mark = false; + SASSERT(new_node->m_ref_count == 0); + inc_ref(l); + inc_ref(r); + return aig_lit(new_node); + } + + bool is_not_eq(aig_lit const & l, aig_lit const & r) const { + return l.ptr() == r.ptr() && l.is_inverted() != r.is_inverted(); + } + + /** + \brief Create an AIG representing (l and r) + Apply two-level minimization rules that guarantee that + locally the size is decreasing, and globally is not increasing. + */ + aig_lit mk_node(aig_lit l, aig_lit r) { + start: + bool sign1 = l.is_inverted(); + aig * n1 = l.ptr(); + bool sign2 = r.is_inverted(); + aig * n2 = r.ptr(); + + if (n1->m_id == 0) { + if (sign1) + return m_false; // false and r + else + return r; // true and r + } + + if (n2->m_id == 0) { + if (sign2) + return m_false; // l and false; + else + return l; // l and true; + } + + if (n1 == n2) { + if (sign1 == sign2) + return l; // l and l; + else + return m_false; // l and not l + } + +#ifdef USE_TWO_LEVEL_RULES + if (!is_var(n1)) { + aig_lit a = n1->m_children[0]; + aig_lit b = n1->m_children[1]; + + if (is_not_eq(a, r) || is_not_eq(b, r)) { + if (sign1) { + // substitution + return r; // (not (a and b)) and r --> r IF a = (not r) or b = (not r) + } + else { + // contradiction + return m_false; // (a and b) and r --> false IF a = (not r) or b = (not r) + } + } + if (a == r) { + if (sign1) { + // substitution + // not (a and b) and r --> (not b) and r IF a == r + l = b; + l.invert(); + goto start; + } + else { + // substitution + return l; // (a and b) and r --> (a and b) IF a = r + } + } + if (b == r) { + if (sign1) { + // subsitution + // not (a and b) and r --> (not a) and r IF b == r + l = a; + l.invert(); + goto start; + } + else { + // substitution + return l; // (a and b) and r --> (a and b) IF b = r; + } + } + + if (!is_var(n2)) { + aig_lit c = n2->m_children[0]; + aig_lit d = n2->m_children[1]; + + if (!sign1 && !sign2) { + // contradiction + if (is_not_eq(a, c) || is_not_eq(a, d) || is_not_eq(b, c) || is_not_eq(b, d)) + return m_false; // (a and b) and (c and d) --> false IF a = not(c) OR a = not(d) or b = not(c) or b = not(d) + // idempotence + if (a == c || b == c) { + r = d; // (a and b) and (c and d) --> (a and b) and d IF a == c or b == c + goto start; + } + // idempotence + if (b == c || b == d) { + l = a; // (a and b) and (c and d) --> a and (c and d) IF b == c or b == d + goto start; + } + // idempotence + if (a == d || b == d) { + r = c; + goto start; // (a and b) and (c and d) --> (a and b) and c IF a == d or b == d + } + // idempotence + if (a == c || a == d) { + l = b; // (a and b) and (c and d) --> b and (c and d) IF a == c or a == d + goto start; + } + } + + if (sign1 && !sign2) { + // subsumption + if (is_not_eq(a, c) || is_not_eq(a, d) || is_not_eq(b, c) || is_not_eq(b, d)) + return r; // not (a and b) and (c and d) --> (c and d) + + // substitution + if (b == c || b == d) { + // not (a and b) and (c and d) --> (not a) and (c and d) + a.invert(); + l = a; + goto start; + } + + // substitution + if (a == c || a == d) { + // not (a and b) and (c and d) --> (not b) and (c and d) + b.invert(); + l = b; + goto start; + } + } + + if (!sign1 && sign2) { + // subsumption + if (is_not_eq(a, c) || is_not_eq(a, d) || is_not_eq(b, c) || is_not_eq(b, d)) + return l; // (a and b) and not (c and d) --> (a and b) + + // substitution + if (c == a || c == b) { + // (a and b) and not (c and d) --> (a and b) and (not d); + d.invert(); + r = d; + goto start; + } + + // substitution + if (d == a || d == b) { + // (a and b) and not (c and d) --> (a and b) and (not c); + c.invert(); + r = c; + goto start; + } + } + + if (sign1 && sign2) { + // resolution + if (a == c && is_not_eq(b, d)) { + a.invert(); // (not (a and b)) and (not (a and (not b))) --> not a + return a; + } + SASSERT(!(a == d && is_not_eq(b, c))); // cannot happen because we sort args + // resolution + if (is_not_eq(a, c) && b == d) { + b.invert(); // (not (a and b)) and (not (a and (not b))) --> not b + return b; + } + SASSERT(!(is_not_eq(a, d) && b == c)); // cannot happen because we sort args + } + } + } + + if (!is_var(n2)) { + aig_lit a = n2->m_children[0]; + aig_lit b = n2->m_children[1]; + if (is_not_eq(l, a) || is_not_eq(l, b)) { + if (sign2) { + // substitution + return l; // l and (not (a and b)) --> l IF a = (not l) or b = (not l) + } + else { + // contradiction + return m_false; // l and (a and b) --> false IF a = (not l) or b = (not l) + } + } + if (a == l) { + if (sign2) { + // substitution + // l and not (a and b) and r --> l and (not b) IF a == l + r = b; + r.invert(); + goto start; + } + else { + // substitution + return r; // l and (a and b) --> (a and b) IF a = l; + } + } + if (b == l) { + if (sign2) { + // substitution + // l and not (a and b) --> l and (not a) IF b == l + r = a; + r.invert(); + goto start; + } + else { + // substitution + return r; // l and (a and b) --> (a and b) IF b = l; + } + } + } +#endif + + if (n1->m_id > n2->m_id) + return mk_node_core(r, l); + else + return mk_node_core(l, r); + } + + struct expr2aig { + struct frame { + app * m_t; + unsigned m_idx; + unsigned m_spos; + frame(app * t, unsigned spos):m_t(t), m_idx(0), m_spos(spos) {} + }; + imp & m; + svector m_frame_stack; + svector m_result_stack; + obj_map m_cache; + + 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); + } + restore_result_stack(0); + } + + void save_result(aig_lit & r) { + m.inc_ref(r); + m_result_stack.push_back(r); + } + + 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";); + SASSERT(!m_cache.contains(t)); + m.inc_ref(r); + m_cache.insert(t, r); + } + + bool is_cached(expr * t) { + aig_lit r; + if (m_cache.find(t, r)) { + save_result(r); + return true; + } + return false; + } + + void process_var(expr * t) { + if (is_cached(t)) + return; + aig_lit r(m.mk_var(t)); + SASSERT(ref_count(r) == 0); + cache_result(t, r); + save_result(r); + } + + void mk_frame(app * t) { + m_frame_stack.push_back(frame(t, m_result_stack.size())); + } + + bool visit(expr * t) { + if (is_app(t)) { + app * tapp = to_app(t); + if (tapp->get_family_id() == m.m().get_basic_family_id()) { + switch (tapp->get_decl_kind()) { + case OP_TRUE: save_result(m.m_true); return true; + case OP_FALSE: save_result(m.m_false); return true; + case OP_EQ: + if (!m.m().is_bool(tapp->get_arg(0))) + break; + case OP_NOT: + case OP_OR: + case OP_AND: + case OP_IFF: + case OP_XOR: + case OP_IMPLIES: + case OP_ITE: + if (tapp->get_ref_count() > 1 && is_cached(tapp)) + return true; + mk_frame(tapp); + return false; + default: + break; + } + } + process_var(t); + return true; + } + else { + // quantifiers and free variables are handled as aig variables + process_var(t); + return true; + } + } + + void restore_result_stack(unsigned old_sz) { + unsigned sz = m_result_stack.size(); + SASSERT(old_sz <= sz); + for (unsigned i = old_sz; i < sz; i++) + m.dec_ref(m_result_stack[i]); + m_result_stack.shrink(old_sz); + } + + void save_node_result(unsigned spos, aig_lit r) { + m.inc_ref(r); + restore_result_stack(spos); + save_result(r); + SASSERT(ref_count(r) >= 2); + m.dec_ref(r); + } + + void mk_or(unsigned spos) { + SASSERT(spos <= m_result_stack.size()); + unsigned num = m_result_stack.size() - spos; + aig_lit r = m.mk_or(num, m_result_stack.begin() + spos); + save_node_result(spos, r); + } + + void mk_and(unsigned spos) { + SASSERT(spos <= m_result_stack.size()); + unsigned num = m_result_stack.size() - spos; + aig_lit r = m.mk_and(num, m_result_stack.begin() + spos); + save_node_result(spos, r); + } + + void mk_ite(unsigned spos) { + SASSERT(spos + 3 == m_result_stack.size()); + aig_lit r = m.mk_ite(m_result_stack[spos], m_result_stack[spos+1], m_result_stack[spos+2]); + save_node_result(spos, r); + } + + void mk_iff(unsigned spos) { + SASSERT(spos + 2 == m_result_stack.size()); + aig_lit r = m.mk_iff(m_result_stack[spos], m_result_stack[spos+1]); + save_node_result(spos, r); + } + + void mk_xor(unsigned spos) { + SASSERT(spos + 2 == m_result_stack.size()); + aig_lit r = m.mk_xor(m_result_stack[spos], m_result_stack[spos+1]); + save_node_result(spos, r); + } + + void mk_implies(unsigned spos) { + SASSERT(spos + 2 == m_result_stack.size()); + aig_lit r = m.mk_implies(m_result_stack[spos], m_result_stack[spos+1]); + save_node_result(spos, r); + } + + void mk_aig(frame & fr) { + SASSERT(fr.m_t->get_family_id() == m.m().get_basic_family_id()); + switch (fr.m_t->get_decl_kind()) { + case OP_NOT: + m_result_stack[fr.m_spos].invert(); + break; + case OP_OR: + mk_or(fr.m_spos); + break; + case OP_AND: + mk_and(fr.m_spos); + break; + case OP_EQ: + SASSERT(m.m().is_bool(fr.m_t->get_arg(0))); + mk_iff(fr.m_spos); + break; + case OP_IFF: + mk_iff(fr.m_spos); + break; + case OP_XOR: + mk_xor(fr.m_spos); + break; + case OP_IMPLIES: + mk_implies(fr.m_spos); + break; + case OP_ITE: + mk_ite(fr.m_spos); + break; + default: + UNREACHABLE(); + } + if (fr.m_t->get_ref_count() > 1) { + cache_result(fr.m_t, m_result_stack.back()); + } + } + + aig_lit operator()(expr * n) { + SASSERT(check_cache()); + if (!visit(n)) { + while (!m_frame_stack.empty()) { + loop: + m.checkpoint(); + frame & fr = m_frame_stack.back(); + if (fr.m_idx == 0 && fr.m_t->get_ref_count() > 1) { + if (is_cached(fr.m_t)) { + m_frame_stack.pop_back(); + continue; + } + } + unsigned num_args = fr.m_t->get_num_args(); + while (fr.m_idx < num_args) { + expr * arg = fr.m_t->get_arg(fr.m_idx); + fr.m_idx++; + if (!visit(arg)) + goto loop; + } + mk_aig(fr); + m_frame_stack.pop_back(); + } + } + SASSERT(m_result_stack.size() == 1); + aig_lit r = m_result_stack.back(); + m_result_stack.pop_back(); + m.dec_ref_result(r); + SASSERT(check_cache()); + return r; + } + + bool check_cache() const { + obj_map::iterator it = m_cache.begin(); + obj_map::iterator end = m_cache.end(); + for (; it != end; ++it) { + SASSERT(ref_count(it->m_value) > 0); + } + return true; + } + }; + + /** + \brief Return true if the AIG represents an if-then-else + */ + template + bool is_ite_core(aig * n, aig_lit & c, aig_lit & t, aig_lit & e) const { + if (is_var(n)) + return false; + aig_lit l = left(n); + aig_lit r = right(n); + if (l.is_inverted() && r.is_inverted()) { + aig * l_ptr = l.ptr(); + aig * r_ptr = r.ptr(); + if (is_var(l_ptr) || is_var(r_ptr)) + return false; + aig_lit l1 = left(l_ptr); + aig_lit l2 = right(l_ptr); + aig_lit r1 = left(r_ptr); + aig_lit r2 = right(r_ptr); + if (is_not_eq(l1, r1)) { + if (Collect) { + l1.invert(); l2.invert(); r1.invert(); r2.invert(); + if (l1.is_inverted()) { + c = r1; t = l2; e = r2; + } + else { + c = l1; t = r2; e = l2; + } + } + return true; + } + else if (is_not_eq(l1, r2)) { + if (Collect) { + l1.invert(); l2.invert(); r1.invert(); r2.invert(); + if (l1.is_inverted()) { + c = r2; t = l2; e = r1; + } + else { + c = l1; t = r1; e = l2; + } + } + return true; + } + else if (is_not_eq(l2, r1)) { + if (Collect) { + l1.invert(); l2.invert(); r1.invert(); r2.invert(); + if (l2.is_inverted()) { + c = r1; t = l1; e = r2; + } + else { + c = l2; t = r2; e = l1; + } + } + return true; + } + else if (is_not_eq(l2, r2)) { + if (Collect) { + l1.invert(); l2.invert(); r1.invert(); r2.invert(); + if (l2.is_inverted()) { + c = r2; t = l1; e = r1; + } + else { + c = l2; t = r1; e = l1; + } + } + return true; + } + } + return false; + } + + bool is_ite(aig * n, aig_lit & c, aig_lit & t, aig_lit & e) const { + return is_ite_core(n, c, t, e); + } + + bool is_ite(aig * n) const { + static aig_lit c, t, e; + return is_ite_core(n, c, t, e); + } + + /** + \brief Return true if the AIG represents an iff + */ + bool is_iff(aig * n) const { + if (is_var(n)) + return false; + aig_lit l = left(n); + aig_lit r = right(n); + if (l.is_inverted() && r.is_inverted()) { + if (is_var(l) || is_var(r)) + return false; + return is_not_eq(left(l), left(r)) && is_not_eq(right(l), right(r)); + } + return false; + } + + expr * var2expr(aig * n) const { + SASSERT(is_var(n)); + return m_var2exprs[n->m_id]; + } + + struct aig2expr { + imp & m; + ast_manager & ast_mng; + enum kind { AIG_AND, + AIG_AUX_AND, // does not have an associated expr + AIG_ITE + }; + struct frame { + aig * m_node; + unsigned m_kind:2; + unsigned m_first:1; + frame(aig * n, kind k):m_node(n), m_kind(k), m_first(true) {} + }; + expr_ref_vector m_cache; + svector m_frame_stack; + + aig2expr(imp & _m):m(_m), ast_mng(m.m()), m_cache(ast_mng) {} + + expr * get_cached(aig * n) { + if (is_var(n)) { + return n->m_id == 0 ? ast_mng.mk_true() : m.var2expr(n); + } + else { + CTRACE("aig2expr", !is_cached(n), tout << "invalid get_cached for "; m.display_ref(tout, n); tout << "\n";); + SASSERT(is_cached(n)); + return m_cache.get(to_idx(n)); + } + } + + expr * invert(expr * n) { + if (ast_mng.is_not(n)) + return to_app(n)->get_arg(0); + if (ast_mng.is_true(n)) + return ast_mng.mk_false(); + SASSERT(!ast_mng.is_false(n)); + return ast_mng.mk_not(n); + } + + expr * get_cached(aig_lit const & n) { + if (n.is_inverted()) + return invert(get_cached(n.ptr())); + else + return get_cached(n.ptr()); + } + + bool is_cached(aig * n) { + if (is_var(n)) + return true; + unsigned idx = to_idx(n); + if (idx >= m_cache.size()) { + m_cache.resize(idx+1); + return false; + } + return m_cache.get(idx) != 0; + } + + void cache_result(aig * n, expr * t) { + unsigned idx = to_idx(n); + SASSERT(idx < m_cache.size()); + SASSERT(m_cache.get(idx) == 0); + m_cache.set(idx, t); + } + + void visit_and_child(aig_lit c, bool & visited) { + aig * n = c.ptr(); + if (is_cached(n)) + return; + if (m.is_ite(n)) + m_frame_stack.push_back(frame(n, AIG_ITE)); + else if (!c.is_inverted() && n->m_ref_count == 1) + m_frame_stack.push_back(frame(n, AIG_AUX_AND)); + else + m_frame_stack.push_back(frame(n, AIG_AND)); + visited = false; + } + + void visit_ite_child(aig_lit c, bool & visited) { + aig * n = c.ptr(); + if (is_cached(n)) + return; + m_frame_stack.push_back(frame(n, m.is_ite(n) ? AIG_ITE : AIG_AND)); + visited = false; + } + + ptr_vector m_and_children; + ptr_vector m_and_todo; + + void add_child(aig_lit c) { + aig * n = c.ptr(); + if (c.is_inverted()) { + // adding (not c) since I build an OR node + m_and_children.push_back(get_cached(n)); + return; + } + if (is_cached(n)) { + m_and_children.push_back(invert(get_cached(n))); + return; + } + SASSERT(n->m_ref_count == 1); + m_and_todo.push_back(n); + } + + void mk_and(aig * n) { + m_and_children.reset(); + m_and_todo.reset(); + add_child(left(n)); + add_child(right(n)); + while (!m_and_todo.empty()) { + aig * t = m_and_todo.back(); + SASSERT(!is_var(t)); + m_and_todo.pop_back(); + add_child(left(t)); + add_child(right(t)); + } + expr * r = ast_mng.mk_not(ast_mng.mk_or(m_and_children.size(), m_and_children.c_ptr())); + cache_result(n, r); + TRACE("aig2expr", tout << "caching AND "; m.display_ref(tout, n); tout << "\n";); + } + + void mk_ite(aig * n) { + aig_lit c, t, e; +#ifdef Z3DEBUG + bool ok = +#endif + m.is_ite(n, c, t, e); + SASSERT(ok); + if (c.is_inverted()) { + c.invert(); + std::swap(t, e); + } + expr * r; + if (m.is_not_eq(t, e)) { + r = ast_mng.mk_iff(get_cached(c), get_cached(t)); + } + else { + r = ast_mng.mk_ite(get_cached(c), get_cached(t), get_cached(e)); + } + cache_result(n, r); + TRACE("aig2expr", tout << "caching ITE/IFF "; m.display_ref(tout, n); tout << "\n";); + } + + /** + \brief Return an expression representing the negation of p. + */ + expr * process_root(aig * r) { + if (is_cached(r)) + return get_cached(r); + m_frame_stack.push_back(frame(r, m.is_ite(r) ? AIG_ITE : AIG_AND)); + while (!m_frame_stack.empty()) { + m.checkpoint(); + frame & fr = m_frame_stack.back(); + aig * n = fr.m_node; + if (is_cached(n)) { + m_frame_stack.pop_back(); + continue; + } + if (fr.m_first) { + fr.m_first = false; + bool visited = true; + switch (fr.m_kind) { + case AIG_AND: + case AIG_AUX_AND: + visit_and_child(left(n), visited); + visit_and_child(right(n), visited); + break; + case AIG_ITE: { + aig_lit a = left(left(n)); + aig_lit b = right(left(n)); + aig_lit c = left(right(n)); + aig_lit d = right(right(n)); + visit_ite_child(a, visited); + visit_ite_child(b, visited); + if (c.ptr() != a.ptr() && c.ptr() != b.ptr()) + visit_ite_child(c, visited); + if (d.ptr() != a.ptr() && d.ptr() != b.ptr()) + visit_ite_child(d, visited); + break; + } + default: + UNREACHABLE(); + break; + } + if (!visited) + continue; + } + switch (fr.m_kind){ + case AIG_AUX_AND: + // do nothing + TRACE("aig2expr", tout << "skipping aux AND "; m.display_ref(tout, n); tout << "\n";); + break; + case AIG_AND: + mk_and(n); + break; + case AIG_ITE: + mk_ite(n); + break; + default: + UNREACHABLE(); + break; + } + m_frame_stack.pop_back(); + } + return get_cached(r); + } + + /** + \brief (Debugging) Naive AIG -> EXPR + */ + void naive(aig_lit const & l, expr_ref & r) { + expr_ref_vector cache(ast_mng); + ptr_vector todo; + todo.push_back(l.ptr()); + while (!todo.empty()) { + aig * t = todo.back(); + if (is_var(t)) { + todo.pop_back(); + continue; + } + unsigned idx = to_idx(t); + cache.reserve(idx+1); + if (cache.get(idx) != 0) { + todo.pop_back(); + continue; + } + bool ok = true; + for (unsigned i = 0; i < 2; i++) { + aig * c = t->m_children[i].ptr(); + if (!is_var(c) && cache.get(to_idx(c), 0) == 0) { + todo.push_back(c); + ok = false; + } + } + if (!ok) + continue; + expr * args[2]; + for (unsigned i = 0; i < 2; i++) { + aig_lit l = t->m_children[i]; + aig * c = l.ptr(); + if (is_var(c)) + args[i] = m.m_var2exprs.get(c->m_id); + else + args[i] = cache.get(to_idx(c), 0); + if (!l.is_inverted()) + args[i] = invert(args[i]); + } + cache.set(idx, ast_mng.mk_not(ast_mng.mk_or(args[0], args[1]))); + todo.pop_back(); + } + aig * c = l.ptr(); + if (is_var(c)) + r = m.m_var2exprs.get(c->m_id); + else + r = cache.get(to_idx(c)); + if (l.is_inverted()) + r = invert(r); + } + + + void operator()(aig_lit const & l, assertion_set & s) { +#if 1 + s.reset(); + sbuffer roots; + roots.push_back(l); + while (!roots.empty()) { + aig_lit n = roots.back(); + roots.pop_back(); + if (n.is_inverted()) { + s.assert_expr(invert(process_root(n.ptr()))); + continue; + } + aig * p = n.ptr(); + if (m.is_ite(p)) { + s.assert_expr(process_root(p)); + continue; + } + if (is_var(p)) { + s.assert_expr(m.var2expr(p)); + continue; + } + roots.push_back(left(p)); + roots.push_back(right(p)); + } +#else + s.reset(); + expr_ref r(ast_mng); + naive(l, r); + s.assert_expr(r); +#endif + } + + void operator()(aig_lit const & l, expr_ref & r) { + naive(l, r); + } + + void operator()(aig_lit const & l, goal & g) { + g.reset(); + sbuffer roots; + 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())), 0, 0); + continue; + } + aig * p = n.ptr(); + if (m.is_ite(p)) { + g.assert_expr(process_root(p), 0, 0); + continue; + } + if (is_var(p)) { + g.assert_expr(m.var2expr(p), 0, 0); + continue; + } + roots.push_back(left(p)); + roots.push_back(right(p)); + } + } + + }; + + struct max_sharing_proc { + struct frame { + aig * m_node; + unsigned short m_idx; + frame(aig * n):m_node(n), m_idx(0) {} + }; + imp & m; + svector m_frame_stack; + svector m_result_stack; + svector m_cache; + ptr_vector m_saved; + + max_sharing_proc(imp & _m):m(_m) {} + + ~max_sharing_proc() { + reset_saved(); + } + + void reset_saved() { + m.dec_array_ref(m_saved.size(), m_saved.c_ptr()); + m_saved.finalize(); + } + + void reset_cache() { + m_cache.finalize(); + reset_saved(); + } + + void push_result(aig_lit n) { + m_result_stack.push_back(n); + if (!n.is_null()) + m.inc_ref(n); + } + + bool is_cached(aig * p) { + SASSERT(!is_var(p)); + if (p->m_ref_count <= 1) + return false; + unsigned idx = to_idx(p); + if (idx >= m_cache.size()) { + m_cache.resize(idx+1, aig_lit::null); + return false; + } + aig_lit c = m_cache[idx]; + if (!c.is_null()) { + push_result(c); + return true; + } + return false; + } + + bool visit(aig * p) { + if (is_var(p)) { + push_result(0); + return true; + } + if (is_cached(p)) + return true; + m_frame_stack.push_back(frame(p)); + return false; + } + + bool visit(aig_lit l) { return visit(l.ptr()); } + + void save_result(aig * o, aig_lit n) { + SASSERT(!is_var(o)); + if (o->m_ref_count > 1) { + unsigned idx = to_idx(o); + if (idx >= m_cache.size()) + m_cache.resize(idx+1, aig_lit::null); + m_cache[idx] = n; + m_saved.push_back(o); + m_saved.push_back(n.ptr()); + m.inc_ref(o); + m.inc_ref(n); + } + if (o != n.ptr()) { + push_result(n); + } + else { + SASSERT(!n.is_inverted()); + push_result(aig_lit::null); + } + } + + void pop2_result() { + aig_lit r1 = m_result_stack.back(); + m_result_stack.pop_back(); + aig_lit r2 = m_result_stack.back(); + m_result_stack.pop_back(); + if (!r1.is_null()) m.dec_ref(r1); + if (!r2.is_null()) m.dec_ref(r2); + } + + bool improve_sharing_left(aig * o, aig_lit n) { + SASSERT(!left(n).is_inverted()); + SASSERT(!is_var(left(n))); + aig_lit a = left(left(n)); + aig_lit b = right(left(n)); + aig_lit c = right(n); + TRACE("max_sharing", + tout << "trying (and "; m.display_ref(tout, a); + tout << " (and "; m.display_ref(tout, b); + tout << " "; m.display_ref(tout, c); + tout << "))\n";); + aig_lit bc = m.mk_and(b, c); + m.inc_ref(bc); + if (ref_count(bc) > 1) { + aig_lit r = m.mk_and(a, bc); + if (n.is_inverted()) + r.invert(); + save_result(o, r); + m.dec_ref(bc); + TRACE("max_sharing", tout << "improved:\n"; m.display(tout, o); tout << "---->\n"; m.display(tout, r);); + return true; + } + m.dec_ref(bc); + + TRACE("max_sharing", + tout << "trying (and "; m.display_ref(tout, a); + tout << " (and "; m.display_ref(tout, c); + tout << " "; m.display_ref(tout, b); + tout << "))\n";); + aig_lit ac = m.mk_and(a, c); + m.inc_ref(ac); + if (ref_count(ac) > 1) { + aig_lit r = m.mk_and(b, ac); + if (n.is_inverted()) + r.invert(); + save_result(o, r); + m.dec_ref(ac); + TRACE("max_sharing", tout << "improved:\n"; m.display(tout, o); tout << "---->\n"; m.display(tout, r);); + return true; + } + m.dec_ref(ac); + + return false; + } + + bool improve_sharing_right(aig * o, aig_lit n) { + SASSERT(!right(n).is_inverted()); + SASSERT(!is_var(right(n))); + aig_lit a = left(n); + aig_lit b = left(right(n)); + aig_lit c = right(right(n)); + TRACE("max_sharing", + tout << "trying (and (and "; m.display_ref(tout, a); + tout << " "; m.display_ref(tout, b); + tout << ") "; m.display_ref(tout, c); + tout << ")\n";); + aig_lit ab = m.mk_and(a, b); + m.inc_ref(ab); + if (ref_count(ab) > 1) { + aig_lit r = m.mk_and(ab, c); + if (n.is_inverted()) + r.invert(); + save_result(o, r); + m.dec_ref(ab); + TRACE("max_sharing", tout << "improved:\n"; m.display(tout, o); tout << "---->\n"; m.display(tout, r);); + return true; + } + m.dec_ref(ab); + + aig_lit ac = m.mk_and(a, c); + TRACE("max_sharing", + tout << "trying (and (and "; m.display_ref(tout, a); + tout << " "; m.display_ref(tout, c); + tout << ") "; m.display_ref(tout, b); + tout << ")\n";); + m.inc_ref(ac); + if (ref_count(ac) > 1) { + aig_lit r = m.mk_and(ac, b); + if (n.is_inverted()) + r.invert(); + save_result(o, r); + m.dec_ref(ac); + TRACE("max_sharing", tout << "improved:\n"; m.display(tout, o); tout << "---->\n"; m.display(tout, r);); + return true; + } + m.dec_ref(ac); + return false; + } + + void improve_sharing_core(aig * o, aig_lit n) { + if (!is_var(n)) { + aig_lit l = left(n); + if (!l.is_inverted() && ref_count(l) == 1 && !is_var(l) && improve_sharing_left(o, n)) + return; + aig_lit r = right(n); + if (!r.is_inverted() && ref_count(r) == 1 && !is_var(r) && improve_sharing_right(o, n)) + return; + } + save_result(o, n); + } + + void improve_sharing(aig * p) { + unsigned sz = m_result_stack.size(); + aig_lit new_l = m_result_stack[sz-2]; + aig_lit new_r = m_result_stack[sz-1]; + if (new_l.is_null() && new_r.is_null()) { + pop2_result(); + improve_sharing_core(p, aig_lit(p)); + return; + } + aig_lit l = left(p); + aig_lit r = right(p); + if (!new_l.is_null()) { + if (l.is_inverted()) + new_l.invert(); + l = new_l; + } + if (!new_r.is_null()) { + if (r.is_inverted()) + new_r.invert(); + r = new_r; + } + aig_lit n = m.mk_and(l, r); + m.inc_ref(n); + pop2_result(); + improve_sharing_core(p, n); + m.dec_ref(n); + } + + void process(aig * p) { + if (visit(p)) + return; + while (!m_frame_stack.empty()) { + start: + frame & fr = m_frame_stack.back(); + aig * n = fr.m_node; + TRACE("max_sharing", tout << "processing "; m.display_ref(tout, n); tout << " idx: " << fr.m_idx << "\n";); + switch (fr.m_idx) { + case 0: + fr.m_idx++; + if (!visit(left(n))) + goto start; + case 1: + fr.m_idx++; + if (!visit(right(n))) + goto start; + default: + if (!is_cached(n)) + improve_sharing(n); + m_frame_stack.pop_back(); + break; + } + } + } + + aig_lit operator()(aig_lit p) { + process(p.ptr()); + SASSERT(m_result_stack.size() == 1); + aig_lit r = m_result_stack.back(); + TRACE("max_sharing", tout << "r.is_null(): " << r.is_null() << "\n";); + SASSERT(r.is_null() || ref_count(r) >= 1); + reset_cache(); + if (r.is_null()) { + r = p; + m.inc_ref(r); + } + else if (p.is_inverted()) { + r.invert(); + } + m_result_stack.pop_back(); + TRACE("max_sharing", tout << "result:\n"; m.display(tout, r);); + m.dec_ref_result(r); + return r; + } + }; + +public: + imp(ast_manager & m, unsigned long long max_memory, bool default_gate_encoding): + m_var_id_gen(0), + m_node_id_gen(FIRST_NODE_ID), + m_num_aigs(0), + m_var2exprs(m), + m_allocator("aig"), + m_true(mk_var(m.mk_true())), + m_cancel(false) { + SASSERT(is_true(m_true)); + m_false = m_true; + m_false.invert(); + inc_ref(m_true); + inc_ref(m_false); + m_max_memory = max_memory; + m_default_gate_encoding = default_gate_encoding; + } + + ~imp() { + dec_ref(m_true); + dec_ref(m_false); + SASSERT(m_num_aigs == 0); + } + + ast_manager & m() const { return m_var2exprs.get_manager(); } + + void set_cancel(bool f) { m_cancel = f; } + + void inc_ref(aig * n) { n->m_ref_count++; } + void inc_ref(aig_lit const & r) { inc_ref(r.ptr()); } + void dec_ref(aig * n) { + dec_ref_core(n); + process_to_delete(); + } + void dec_ref(aig_lit const & r) { dec_ref(r.ptr()); } + + void dec_array_ref(unsigned sz, aig * const * ns) { + for (unsigned i = 0; i < sz; i++) + if (ns[i]) + dec_ref(ns[i]); + } + + aig_lit mk_and(aig_lit r1, aig_lit r2) { + aig_lit r = mk_node(r1, r2); + TRACE("mk_and_bug", + display(tout, r1); + tout << "AND\n"; + display(tout, r2); + tout << "-->\n"; + display(tout, r); + tout << "\n";); + return r; + } + + aig_lit mk_and(unsigned num, aig_lit * args) { + switch (num) { + case 0: + return m_true; + case 1: + return args[0]; + case 2: + return mk_and(args[0], args[1]); + default: + // No need to use stable_sort, aig_lit_lt is a total order on AIG nodes + std::sort(args, args+num, aig_lit_lt()); + aig_lit r = mk_and(args[0], args[1]); + inc_ref(r); + for (unsigned i = 2; i < num; i++) { + aig_lit new_r = mk_and(r, args[i]); + inc_ref(new_r); + dec_ref(r); + r = new_r; + } + dec_ref_result(r); + return r; + } + } + + aig_lit mk_or(aig_lit r1, aig_lit r2) { + r1.invert(); + r2.invert(); + aig_lit r = mk_and(r1, r2); + r.invert(); + return r; + } + + aig_lit mk_or(unsigned num, aig_lit * args) { + switch (num) { + case 0: + return m_false; + case 1: + return args[0]; + case 2: + return mk_or(args[0], args[1]); + default: + std::sort(args, args+num, aig_lit_lt()); + aig_lit r = mk_or(args[0], args[1]); + inc_ref(r); + for (unsigned i = 2; i < num; i++) { + aig_lit new_r = mk_or(r, args[i]); + inc_ref(new_r); + dec_ref(r); + r = new_r; + } + dec_ref_result(r); + return r; + } + } + + aig_lit mk_ite(aig_lit c, aig_lit t, aig_lit e) { + if (m_default_gate_encoding) { + t.invert(); + aig_lit n1 = mk_and(c, t); // c and (not t) + c.invert(); + e.invert(); + aig_lit n2 = mk_and(c, e); // (not c) and (not e) + inc_ref(n1); + inc_ref(n2); + n1.invert(); + n2.invert(); + aig_lit r = mk_and(n1, n2); + inc_ref(r); + dec_ref(n1); + dec_ref(n2); + dec_ref_result(r); + return r; + } + else { + aig_lit n1 = mk_and(c, t); + inc_ref(n1); + c.invert(); + aig_lit n2 = mk_and(c, e); + inc_ref(n2); + n1.invert(); + n2.invert(); + aig_lit r = mk_and(n1, n2); + r.invert(); + inc_ref(r); + dec_ref(n1); + dec_ref(n2); + dec_ref_result(r); + return r; + } + } + + aig_lit mk_iff(aig_lit lhs, aig_lit rhs) { + if (m_default_gate_encoding) { + rhs.invert(); + aig_lit n1 = mk_and(lhs, rhs); // lhs and (not rhs) + lhs.invert(); + rhs.invert(); + aig_lit n2 = mk_and(lhs, rhs); // (not lhs) and rhs + inc_ref(n1); + inc_ref(n2); + n1.invert(); + n2.invert(); + aig_lit r = mk_and(n1, n2); + inc_ref(r); + dec_ref(n1); + dec_ref(n2); + dec_ref_result(r); + return r; + } + else { + aig_lit n1 = mk_and(lhs, rhs); // lhs and rhs + inc_ref(n1); + lhs.invert(); + rhs.invert(); + aig_lit n2 = mk_and(lhs, rhs); // not lhs and not rhs + inc_ref(n2); + n1.invert(); + n2.invert(); + aig_lit r = mk_and(n1, n2); + r.invert(); + inc_ref(r); + dec_ref(n1); + dec_ref(n2); + dec_ref_result(r); + return r; + } + } + + aig_lit mk_xor(aig_lit lhs, aig_lit rhs) { + lhs.invert(); + return mk_iff(lhs, rhs); + } + + aig_lit mk_implies(aig_lit lhs, aig_lit rhs) { + lhs.invert(); + return mk_or(lhs, rhs); + } + + aig_lit mk_aig(expr * t) { + aig_lit r; + { + expr2aig proc(*this); + r = proc(t); + inc_ref(r); + } + dec_ref_result(r); + return r; + } + + template + aig_lit mk_aig(S const & s) { + aig_lit r; + r = m_true; + inc_ref(r); + try { + expr2aig proc(*this); + unsigned sz = s.size(); + for (unsigned i = 0; i < sz; i++) { + SASSERT(ref_count(r) >= 1); + expr * t = s.form(i); + aig_lit n = proc(t); + inc_ref(n); + aig_lit new_r = mk_and(r, n); + SASSERT(proc.check_cache()); + inc_ref(new_r); + dec_ref(r); + dec_ref(n); + SASSERT(proc.check_cache()); + r = new_r; + } + SASSERT(ref_count(r) >= 1); + } + catch (aig_exception ex) { + dec_ref(r); + throw ex; + } + dec_ref_result(r); + return r; + } + + void to_formula(aig_lit const & r, assertion_set & s) { + aig2expr proc(*this); + proc(r, s); + } + + 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); + proc(r, result); + } + + aig_lit max_sharing(aig_lit l) { + max_sharing_proc p(*this); + return p(l); + } + + void display_ref(std::ostream & out, aig * r) const { + if (is_var(r)) + out << "#" << r->m_id; + else + out << "@" << (r->m_id - FIRST_NODE_ID); + } + + void display_ref(std::ostream & out, aig_lit const & r) const { + if (r.is_inverted()) + out << "-"; + display_ref(out, r.ptr()); + } + + void display(std::ostream & out, aig_lit const & r) const { + display_ref(out, r); + out << "\n"; + ptr_vector queue; + unsigned qhead = 0; + queue.push_back(r.ptr()); + while (qhead < queue.size()) { + aig * n = queue[qhead]; + qhead++; + display_ref(out, n); out << ": "; + if (is_var(n)) { + out << mk_ismt2_pp(m_var2exprs[n->m_id], m()) << "\n"; + } + else { + display_ref(out, n->m_children[0]); + out << " "; + display_ref(out, n->m_children[1]); + out << "\n"; + aig * c1 = n->m_children[0].ptr(); + aig * c2 = n->m_children[1].ptr(); + if (!c1->m_mark) { + c1->m_mark = true; + queue.push_back(c1); + } + if (!c2->m_mark) { + c2->m_mark = true; + queue.push_back(c2); + } + } + } + unmark(queue.size(), queue.c_ptr()); + } + + void display_smt2_ref(std::ostream & out, aig_lit const & r) const { + if (r.is_inverted()) + out << "(not "; + if (is_var(r)) { + out << mk_ismt2_pp(var2expr(r.ptr()), m()); + } + else { + out << "aig" << to_idx(r.ptr()); + } + if (r.is_inverted()) + out << ")"; + } + + void display_smt2(std::ostream & out, aig_lit const & r) const { + ptr_vector to_unmark; + ptr_vector todo; + todo.push_back(r.ptr()); + while (!todo.empty()) { + aig * t = todo.back(); + if (t->m_mark) { + todo.pop_back(); + continue; + } + if (is_var(t)) { + to_unmark.push_back(t); + t->m_mark = true; + todo.pop_back(); + continue; + } + bool visited = true; + for (unsigned i = 0; i < 2; i++) { + aig_lit c = t->m_children[i]; + aig * c_ptr = c.ptr(); + if (!c_ptr->m_mark) { + todo.push_back(c_ptr); + visited = false; + } + } + if (!visited) + continue; + to_unmark.push_back(t); + t->m_mark = true; + out << "(define-fun aig" << to_idx(t) << " () Bool (and"; + for (unsigned i = 0; i < 2; i++) { + out << " "; + display_smt2_ref(out, t->m_children[i]); + } + out << "))\n"; + todo.pop_back(); + } + out << "(assert "; + display_smt2_ref(out, r); + out << ")\n"; + unmark(to_unmark.size(), to_unmark.c_ptr()); + } + + unsigned get_num_aigs() const { + return m_num_aigs; + } +}; + + +aig_ref::aig_ref(): + m_manager(0), + m_ref(0) { +} + +aig_ref::aig_ref(aig_manager & m, aig_lit const & l): + m_manager(&m), + m_ref(l.m_ref) { + m.m_imp->inc_ref(l); +} + +aig_ref::~aig_ref() { + if (m_ref != 0) { + m_manager->m_imp->dec_ref(aig_lit(*this)); + } +} + +aig_ref & aig_ref::operator=(aig_ref const & r) { + if (r.m_ref != 0) + r.m_manager->m_imp->inc_ref(aig_lit(r)); + if (m_ref != 0) + m_manager->m_imp->dec_ref(aig_lit(*this)); + m_ref = r.m_ref; + m_manager = r.m_manager; + return *this; +} + +aig_manager::aig_manager(ast_manager & m, unsigned long long max, bool default_gate_encoding) { + m_imp = alloc(imp, m, max, default_gate_encoding); +} + +aig_manager::~aig_manager() { + dealloc(m_imp); +} + +void aig_manager::set_max_memory(unsigned long long max) { + m_imp->m_max_memory = max; +} + +aig_ref aig_manager::mk_aig(expr * n) { + return aig_ref(*this, m_imp->mk_aig(n)); +} + +aig_ref aig_manager::mk_aig(assertion_set const & s) { + return aig_ref(*this, m_imp->mk_aig(s)); +} + +aig_ref aig_manager::mk_aig(goal const & s) { + return aig_ref(*this, m_imp->mk_aig(s)); +} + +aig_ref aig_manager::mk_not(aig_ref const & r) { + aig_lit l(r); + l.invert(); + return aig_ref(*this, l); +} + +aig_ref aig_manager::mk_and(aig_ref const & r1, aig_ref const & r2) { + return aig_ref(*this, m_imp->mk_and(aig_lit(r1), aig_lit(r2))); +} + +aig_ref aig_manager::mk_or(aig_ref const & r1, aig_ref const & r2) { + return aig_ref(*this, m_imp->mk_or(aig_lit(r1), aig_lit(r2))); +} + +aig_ref aig_manager::mk_iff(aig_ref const & r1, aig_ref const & r2) { + return aig_ref(*this, m_imp->mk_iff(aig_lit(r1), aig_lit(r2))); +} + +aig_ref aig_manager::mk_ite(aig_ref const & r1, aig_ref const & r2, aig_ref const & r3) { + return aig_ref(*this, m_imp->mk_ite(aig_lit(r1), aig_lit(r2), aig_lit(r3))); +} + +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, assertion_set & s) { + return m_imp->to_formula(aig_lit(r), s); +} + +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); +} + +void aig_manager::display(std::ostream & out, aig_ref const & r) const { + m_imp->display(out, aig_lit(r)); +} + +void aig_manager::display_smt2(std::ostream & out, aig_ref const & r) const { + m_imp->display_smt2(out, aig_lit(r)); +} + +unsigned aig_manager::get_num_aigs() const { + return m_imp->get_num_aigs(); +} + +void aig_manager::set_cancel(bool f) { + m_imp->set_cancel(f); +} + + diff --git a/lib/aig.h b/lib/aig.h new file mode 100644 index 000000000..f8000b4d6 --- /dev/null +++ b/lib/aig.h @@ -0,0 +1,85 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + aig.h + +Abstract: + + And-inverted graphs + +Author: + + Leonardo (leonardo) 2011-05-13 + +Notes: + +--*/ +#ifndef _AIG_H_ +#define _AIG_H_ + +#include"ast.h" +#include"tactic_exception.h" + +class assertion_set; +class goal; +class aig_lit; +class aig_manager; + +class aig_exception : public tactic_exception { +public: + aig_exception(char const * msg):tactic_exception(msg) {} +}; + +class aig_ref { + friend class aig_lit; + friend class aig_manager; + aig_manager * m_manager; + void * m_ref; + aig_ref(aig_manager & m, aig_lit const & l); +public: + aig_ref(); + ~aig_ref(); + aig_ref & operator=(aig_ref const & r); + bool operator==(aig_ref const & r) const { return m_ref == r.m_ref; } + bool operator!=(aig_ref const & r) const { return m_ref != r.m_ref; } +}; + +class aig_manager { + struct imp; + imp * m_imp; + friend class aig_ref; +public: + // If default_gate_encoding == true, then + // ite(a, b, c) is encoded as (NOT a OR b) AND (a OR c) + // iff(a, b) is encoded as (NOT a OR b) AND (a OR NOT b) + // + // If default_gate_encoding == false, then + // ite(a, b, c) is encoded as (a AND b) OR (NOT a AND c) + // iff(a, b) is encoded as (a AND b) OR (NOT a AND NOT b) + aig_manager(ast_manager & m, unsigned long long max_memory = UINT64_MAX, bool default_gate_encoding = true); + ~aig_manager(); + void set_max_memory(unsigned long long max); + aig_ref mk_aig(expr * n); + aig_ref mk_aig(assertion_set const & s); // TODO delete + aig_ref mk_aig(goal const & g); + aig_ref mk_not(aig_ref const & r); + aig_ref mk_and(aig_ref const & r1, aig_ref const & r2); + aig_ref mk_or(aig_ref const & r1, aig_ref const & r2); + aig_ref mk_iff(aig_ref const & r1, aig_ref const & r2); + aig_ref mk_ite(aig_ref const & r1, aig_ref const & r2, aig_ref const & r3); + void max_sharing(aig_ref & r); + void to_formula(aig_ref const & r, assertion_set & s); // TODO delete + void to_formula(aig_ref const & r, expr_ref & result); + void to_formula(aig_ref const & r, goal & result); + void to_cnf(aig_ref const & r, goal & result); + void display(std::ostream & out, aig_ref const & r) const; + void display_smt2(std::ostream & out, aig_ref const & r) const; + unsigned get_num_aigs() const; + void cancel() { set_cancel(true); } + void reset_cancel() { set_cancel(false); } + void set_cancel(bool f); +}; + +#endif diff --git a/lib/aig_tactic.cpp b/lib/aig_tactic.cpp new file mode 100644 index 000000000..617db0b8c --- /dev/null +++ b/lib/aig_tactic.cpp @@ -0,0 +1,126 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + aig_tactic.cpp + +Abstract: + + Tactic for minimizing circuits using AIGs. + +Author: + + Leonardo (leonardo) 2011-10-24 + +Notes: + +--*/ +#include"tactical.h" +#include"aig.h" + +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 { + aig_tactic & m_owner; + + mk_aig_manager(aig_tactic & o, ast_manager & m):m_owner(o) { + aig_manager * mng = alloc(aig_manager, m, o.m_max_memory, o.m_aig_gate_encoding); + #pragma omp critical (aig_tactic) + { + m_owner.m_aig_manager = mng; + } + } + + ~mk_aig_manager() { + aig_manager * mng = m_owner.m_aig_manager; + #pragma omp critical (aig_tactic) + { + m_owner.m_aig_manager = 0; + } + dealloc(mng); + } + }; + +public: + aig_tactic(params_ref const & p = params_ref()):m_aig_manager(0) { + updt_params(p); + } + + virtual tactic * translate(ast_manager & m) { + 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; + } + + virtual void updt_params(params_ref const & p) { + 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); + } + + virtual void collect_param_descrs(param_descrs & r) { + 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) { + SASSERT(g->is_well_sorted()); + tactic_report report("aig", *g); + + mk_aig_manager mk(*this, g->m()); + if (m_aig_per_assertion) { + unsigned size = g->size(); + for (unsigned i = 0; i < size; i++) { + aig_ref r = m_aig_manager->mk_aig(g->form(i)); + m_aig_manager->max_sharing(r); + expr_ref new_f(g->m()); + m_aig_manager->to_formula(r, new_f); + g->update(i, new_f, 0, g->dep(i)); + } + } + else { + fail_if_unsat_core_generation("aig", g); + aig_ref r = m_aig_manager->mk_aig(*(g.get())); + g->reset(); // save memory + m_aig_manager->max_sharing(r); + m_aig_manager->to_formula(r, *(g.get())); + } + SASSERT(g->is_well_sorted()); + } + + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + fail_if_proof_generation("aig", g); + mc = 0; pc = 0; core = 0; + operator()(g); + g->inc_depth(); + result.push_back(g.get()); + } + + virtual void cleanup() {} + +protected: + virtual void set_cancel(bool f) { + #pragma omp critical (aig_tactic) + { + if (m_aig_manager) + m_aig_manager->set_cancel(f); + } + } +}; + +tactic * mk_aig_tactic(params_ref const & p) { + return clean(alloc(aig_tactic, p)); +} diff --git a/lib/aig_tactic.h b/lib/aig_tactic.h new file mode 100644 index 000000000..f635a8865 --- /dev/null +++ b/lib/aig_tactic.h @@ -0,0 +1,27 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + aig_tactic.h + +Abstract: + + Tactic for minimizing circuits using AIGs. + +Author: + + Leonardo (leonardo) 2011-10-24 + +Notes: + +--*/ +#ifndef _AIG_TACTIC_H_ +#define _AIG_TACTIC_H_ + +#include"params.h" +class tactic; + +tactic * mk_aig_tactic(params_ref const & p = params_ref()); + +#endif diff --git a/lib/algebraic_numbers.cpp b/lib/algebraic_numbers.cpp new file mode 100644 index 000000000..f7de4054c --- /dev/null +++ b/lib/algebraic_numbers.cpp @@ -0,0 +1,3117 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + algebraic_numbers.cpp + +Abstract: + + Real Algebraic Numbers + +Author: + + Leonardo (leonardo) 2011-11-22 + +Notes: + +--*/ +#include"algebraic_numbers.h" +#include"upolynomial.h" +#include"mpbq.h" +#include"basic_interval.h" +#include"cooperate.h" +#include"sexpr2upolynomial.h" +#include"scoped_ptr_vector.h" +#include"mpbqi.h" +#include"timeit.h" + +namespace algebraic_numbers { + + struct basic_cell { + mpq m_value; + }; + + // Each algebraic number is associated with two + // isolating (refinable) intervals. The second + // interval just caches refinements of the first one. + + struct algebraic_cell { + // polynomial + unsigned m_p_sz; + mpz * m_p; + mpbqi m_interval; // isolating/refinable interval + // sign of p at the lower and upper bounds of m_interval + unsigned m_minimal:1; // true if p is a minimal polynomial for representing the number + unsigned m_sign_lower:1; + unsigned m_not_rational:1; // if true we know for sure it is not a rational + unsigned m_i:29; // number is the i-th root of p, 0 if it is not known which root of p the number is. + algebraic_cell():m_p_sz(0), m_p(0), m_minimal(false), m_not_rational(false), m_i(0) {} + bool is_minimal() const { return m_minimal != 0; } + }; + + typedef polynomial::manager poly_manager; + typedef upolynomial::manager upoly_manager; + typedef upolynomial::numeral_vector upoly; + typedef upolynomial::scoped_numeral_vector scoped_upoly; + typedef upolynomial::factors factors; + + void manager::get_param_descrs(param_descrs & r) { + r.insert(":algebraic-zero-accuracy", CPK_UINT, "(default: 0) 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)."); + r.insert(":algebraic-min-mag", CPK_UINT, "(default: 16) 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"); + r.insert(":algebraic-factor", CPK_BOOL, "(default: true) use polynomial factorization to simplify polynomials representing algebraic numbers."); + r.insert(":algebraic-factor-max-prime", CPK_UINT, "(default: 31), 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."); + r.insert(":algebraic-factor-num-primes", CPK_UINT, "(default: 1), 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."); + r.insert(":algebraic-factor-search-size", CPK_UINT, "(default: 5000), 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."); + } + + struct manager::imp { + manager & m_wrapper; + small_object_allocator & m_allocator; + unsynch_mpq_manager & m_qmanager; + mpbq_manager m_bqmanager; + mpbqi_manager m_bqimanager; + poly_manager m_pmanager; + upoly_manager m_upmanager; + mpq m_zero; + scoped_mpz m_is_rational_tmp; + scoped_upoly m_isolate_tmp1; + scoped_upoly m_isolate_tmp2; + scoped_upoly m_isolate_tmp3; + scoped_upoly m_eval_sign_tmp; + factors m_isolate_factors; + scoped_mpbq_vector m_isolate_roots; + scoped_mpbq_vector m_isolate_lowers; + scoped_mpbq_vector m_isolate_uppers; + scoped_upoly m_add_tmp; + polynomial::var m_x; + polynomial::var m_y; + volatile bool m_cancel; + + // configuration + int m_min_magnitude; + bool m_factor; + polynomial::factor_params m_factor_params; + int m_zero_accuracy; + + // statistics + unsigned m_compare_cheap; + unsigned m_compare_sturm; + unsigned m_compare_refine; + unsigned m_compare_poly_eq; + + imp(manager & w, unsynch_mpq_manager & m, params_ref const & p, small_object_allocator & a): + m_wrapper(w), + m_allocator(a), + m_qmanager(m), + m_bqmanager(m), + m_bqimanager(m_bqmanager), + m_pmanager(m, &a), + m_upmanager(m), + m_is_rational_tmp(m), + m_isolate_tmp1(upm()), + m_isolate_tmp2(upm()), + m_isolate_tmp3(upm()), + m_eval_sign_tmp(upm()), + m_isolate_factors(upm()), + m_isolate_roots(bqm()), + m_isolate_lowers(bqm()), + m_isolate_uppers(bqm()), + m_add_tmp(upm()) { + updt_params(p); + reset_statistics(); + m_cancel = false; + m_x = pm().mk_var(); + m_y = pm().mk_var(); + } + + ~imp() { + } + + void set_cancel(bool f) { + m_cancel = f; + pm().set_cancel(f); + upm().set_cancel(f); + } + + void checkpoint() { + if (m_cancel) + throw algebraic_exception("canceled"); + cooperate("algebraic"); + } + + void reset_statistics() { + m_compare_cheap = 0; + m_compare_sturm = 0; + m_compare_refine = 0; + m_compare_poly_eq = 0; + } + + void collect_statistics(statistics & st) { +#ifndef _EXTERNAL_RELEASE + st.update("algebraic compare cheap", m_compare_cheap); + st.update("algebraic compare sturm", m_compare_sturm); + st.update("algebraic compare refine", m_compare_refine); + st.update("algebraic compare poly", m_compare_poly_eq); +#endif + } + + void updt_params(params_ref const & p) { + m_min_magnitude = -static_cast(p.get_uint(":algebraic-min-mag", 16)); + m_factor = p.get_bool(":algebraic-factor", true); // use polynomial factorization + m_factor_params.m_max_p = p.get_uint(":algebraic-factor-max-prime", 31); + m_factor_params.m_p_trials = p.get_uint(":algebraic-factor-num-primes", 1); + m_factor_params.m_max_search_size = p.get_uint(":algebraic-factor-max-search-size", 5000); + m_zero_accuracy = -static_cast(p.get_uint(":algebraic-zero-accuracy", 0)); + } + + unsynch_mpq_manager & qm() { + return m_qmanager; + } + + mpbq_manager & bqm() { + return m_bqmanager; + } + + mpbqi_manager & bqim() { + return m_bqimanager; + } + + poly_manager & pm() { + return m_pmanager; + } + + upoly_manager & upm() { + return m_upmanager; + } + + void del(basic_cell * c) { + qm().del(c->m_value); + m_allocator.deallocate(sizeof(basic_cell), c); + } + + void del_poly(algebraic_cell * c) { + for (unsigned i = 0; i < c->m_p_sz; i++) + qm().del(c->m_p[i]); + m_allocator.deallocate(sizeof(mpz)*c->m_p_sz, c->m_p); + c->m_p = 0; + c->m_p_sz = 0; + } + + void del_interval(algebraic_cell * c) { + bqim().del(c->m_interval); + } + + void del(algebraic_cell * c) { + del_poly(c); + del_interval(c); + m_allocator.deallocate(sizeof(algebraic_cell), c); + } + + void del(numeral & a) { + if (a.m_cell == 0) + return; + if (a.is_basic()) + del(a.to_basic()); + else + del(a.to_algebraic()); + a.m_cell = 0; + } + + void reset(numeral & a) { + del(a); + } + + bool is_zero(numeral const & a) { + return a.m_cell == 0; + } + + bool is_pos(numeral const & a) { + if (a.is_basic()) + return qm().is_pos(basic_value(a)); + else + return bqim().is_pos(a.to_algebraic()->m_interval); + } + + bool is_neg(numeral const & a) { + if (a.is_basic()) + return qm().is_neg(basic_value(a)); + else + return bqim().is_neg(a.to_algebraic()->m_interval); + } + + mpq const & basic_value(numeral const & a) { + SASSERT(a.is_basic()); + if (is_zero(a)) + return m_zero; + else + return a.to_basic()->m_value; + } + + bool is_int(numeral & a) { + if (a.is_basic()) + return qm().is_int(basic_value(a)); + if (a.to_algebraic()->m_not_rational) + return false; // we know for sure a is not a rational (and consequently an integer) + + // make sure the isolating interval has at most one integer + if (!refine_until_prec(a, 1)) { + SASSERT(a.is_basic()); // a became basic + return qm().is_int(basic_value(a)); + } + + // Find unique integer in the isolating interval + algebraic_cell * c = a.to_algebraic(); + scoped_mpz candidate(qm()); + bqm().floor(qm(), upper(c), candidate); + + SASSERT(bqm().ge(upper(c), candidate)); + + if (bqm().lt(lower(c), candidate) && upm().eval_sign_at(c->m_p_sz, c->m_p, candidate) == 0) { + m_wrapper.set(a, candidate); + return true; + } + return false; + } + + /* + In our representation, non-basic numbers are encoded by + polynomials of the form: a_n * x^n + ... + a_0 where + a_0 != 0. + + Thus, we can find whether a non-basic number is actually a rational + by using the Rational root theorem. + + p/q is a root of a_n * x^n + ... + a_0 + If p is a factor of a_0, and q is a factor of a_n. + + If the isolating interval (lower, upper) has size less than 1/a_n, then + (a_n*lower, a_n*upper) contains at most one integer. + Let u be this integer, then the non-basic number is a rational iff + u/a_n is the actual root. + */ + bool is_rational(numeral & a) { + if (a.is_basic()) + return true; + if (a.to_algebraic()->m_not_rational) + return false; // we know for sure a is not a rational + TRACE("algebraic_bug", tout << "is_rational(a):\n"; display_root(tout, a); tout << "\n"; display_interval(tout, a); tout << "\n";); + algebraic_cell * c = a.to_algebraic(); + save_intervals saved_a(*this, c); + mpz & a_n = c->m_p[c->m_p_sz - 1]; + scoped_mpz & abs_a_n = m_is_rational_tmp; + qm().set(abs_a_n, a_n); + qm().abs(abs_a_n); + + // 1/2^{log2(a_n)+1} <= 1/a_n + unsigned k = qm().log2(abs_a_n); + k++; + + TRACE("algebraic_bug", tout << "abs(an): " << qm().to_string(abs_a_n) << ", k: " << k << "\n";); + + // make sure the isolating interval size is less than 1/2^k + if (!refine_until_prec(a, k)) { + SASSERT(a.is_basic()); // a became basic + return true; + } + + TRACE("algebraic_bug", tout << "interval after refinement: "; display_interval(tout, a); tout << "\n";); + + // Find unique candidate rational in the isolating interval + scoped_mpbq a_n_lower(bqm()); + scoped_mpbq a_n_upper(bqm()); + bqm().mul(lower(c), abs_a_n, a_n_lower); + bqm().mul(upper(c), abs_a_n, a_n_upper); + + scoped_mpz zcandidate(qm()); + bqm().floor(qm(), a_n_upper, zcandidate); + scoped_mpq candidate(qm()); + qm().set(candidate, zcandidate, abs_a_n); + SASSERT(bqm().ge(upper(c), candidate)); + + // Find if candidate is an actual root + if (bqm().lt(lower(c), candidate) && upm().eval_sign_at(c->m_p_sz, c->m_p, candidate) == 0) { + saved_a.restore_if_too_small(); + set(a, candidate); + return true; + } + else { + saved_a.restore_if_too_small(); + c->m_not_rational = true; + return false; + } + } + + void to_rational(numeral & a, mpq & r) { + VERIFY(is_rational(a)); + SASSERT(a.is_basic()); + qm().set(r, basic_value(a)); + } + + void to_rational(numeral & a, rational & r) { + scoped_mpq tmp(qm()); + to_rational(a, tmp); + rational tmp2(tmp); + r = tmp2; + } + + unsigned degree(numeral const & a) { + if (is_zero(a)) + return 0; + if (a.is_basic()) + return 1; + return a.to_algebraic()->m_p_sz - 1; + } + + void swap(numeral & a, numeral & b) { + std::swap(a.m_cell, b.m_cell); + } + + basic_cell * mk_basic_cell(mpq & n) { + if (qm().is_zero(n)) + return 0; + void * mem = static_cast(m_allocator.allocate(sizeof(basic_cell))); + basic_cell * c = new (mem) basic_cell(); + qm().swap(c->m_value, n); + return c; + } + + int sign_lower(algebraic_cell * c) { + return c->m_sign_lower == 0 ? 1 : -1; + } + + mpbq & lower(algebraic_cell * c) { + return c->m_interval.lower(); + } + + mpbq & upper(algebraic_cell * c) { + return c->m_interval.upper(); + } + + void update_sign_lower(algebraic_cell * c) { + int sl = upm().eval_sign_at(c->m_p_sz, c->m_p, lower(c)); + // The isolating intervals are refinable. Thus, the polynomial has opposite signs at lower and upper. + SASSERT(sl != 0); + SASSERT(upm().eval_sign_at(c->m_p_sz, c->m_p, upper(c)) == -sl); + c->m_sign_lower = sl < 0; + } + + // Make sure the GCD of the coefficients is one and the leading coefficient is positive + void normalize_coeffs(algebraic_cell * c) { + SASSERT(c->m_p_sz > 2); + upm().normalize(c->m_p_sz, c->m_p); + if (upm().m().is_neg(c->m_p[c->m_p_sz-1])) { + upm().neg(c->m_p_sz, c->m_p); + c->m_sign_lower = !(c->m_sign_lower); + } + } + + algebraic_cell * mk_algebraic_cell(unsigned sz, mpz const * p, mpbq const & lower, mpbq const & upper, bool minimal) { + SASSERT(sz > 2); + void * mem = static_cast(m_allocator.allocate(sizeof(algebraic_cell))); + algebraic_cell * c = new (mem) algebraic_cell(); + c->m_p_sz = sz; + c->m_p = static_cast(m_allocator.allocate(sizeof(mpz)*sz)); + for (unsigned i = 0; i < sz; i++) { + new (c->m_p + i) mpz(); + qm().set(c->m_p[i], p[i]); + } + bqim().set(c->m_interval, lower, upper); + update_sign_lower(c); + c->m_minimal = minimal; + SASSERT(c->m_i == 0); + SASSERT(c->m_not_rational == false); + if (c->m_minimal) + c->m_not_rational = true; + normalize_coeffs(c); + return c; + } + + void set(numeral & a, mpq & n) { + if (qm().is_zero(n)) { + reset(a); + SASSERT(is_zero(a)); + return; + } + if (a.is_basic()) { + if (is_zero(a)) + a.m_cell = mk_basic_cell(n); + else + qm().set(a.to_basic()->m_value, n); + } + else { + del(a); + a.m_cell = mk_basic_cell(n); + } + } + + void set(numeral & a, mpq const & n) { + scoped_mpq tmp(qm()); + qm().set(tmp, n); + set(a, tmp); + } + + void copy_poly(algebraic_cell * c, unsigned sz, mpz const * p) { + SASSERT(c->m_p == 0); + SASSERT(c->m_p_sz == 0); + c->m_p_sz = sz; + c->m_p = static_cast(m_allocator.allocate(sizeof(mpz)*sz)); + for (unsigned i = 0; i < sz; i++) { + new (c->m_p + i) mpz(); + qm().set(c->m_p[i], p[i]); + } + } + + void set_interval(algebraic_cell * c, mpbqi const & i) { + bqim().set(c->m_interval, i); + } + + void set_interval(algebraic_cell * c, mpbq const & l, mpbq const & u) { + bqim().set(c->m_interval, l, u); + } + + // Copy fields from source to target. + // It assumes that fields target->m_p is NULL or was deleted. + void copy(algebraic_cell * target, algebraic_cell const * source) { + copy_poly(target, source->m_p_sz, source->m_p); + set_interval(target, source->m_interval); + target->m_minimal = source->m_minimal; + target->m_sign_lower = source->m_sign_lower; + target->m_not_rational = source->m_not_rational; + target->m_i = source->m_i; + } + + void set(numeral & a, unsigned sz, mpz const * p, mpbq const & lower, mpbq const & upper, bool minimal) { + SASSERT(sz > 1); + if (sz == 2) { + // it is linear + scoped_mpq tmp(qm()); + qm().set(tmp, p[0], p[1]); + qm().neg(tmp); + set(a, tmp); + } + else { + if (a.is_basic()) { + del(a); + a.m_cell = TAG(void*, mk_algebraic_cell(sz, p, lower, upper, minimal), ROOT); + } + else { + SASSERT(sz > 2); + algebraic_cell * c = a.to_algebraic(); + del_poly(c); + copy_poly(c, sz, p); + set_interval(c, lower, upper); + c->m_minimal = minimal; + c->m_not_rational = false; + if (c->m_minimal) + c->m_not_rational = true; + c->m_i = 0; + update_sign_lower(c); + normalize_coeffs(c); + } + SASSERT(sign_lower(a.to_algebraic()) == upm().eval_sign_at(a.to_algebraic()->m_p_sz, a.to_algebraic()->m_p, a.to_algebraic()->m_interval.lower())); + } + TRACE("algebraic", tout << "a: "; display_root(tout, a); tout << "\n";); + } + + void set(numeral & a, numeral const & b) { + if (&a == &b) + return; + if (a.is_basic()) { + if (b.is_basic()) { + SASSERT(a.is_basic() && b.is_basic()); + set(a, basic_value(b)); + } + else { + SASSERT(a.is_basic() && !b.is_basic()); + del(a); + void * mem = m_allocator.allocate(sizeof(algebraic_cell)); + algebraic_cell * c = new (mem) algebraic_cell(); + a.m_cell = TAG(void *, c, ROOT); + copy(c, b.to_algebraic()); + } + } + else { + if (b.is_basic()) { + SASSERT(!a.is_basic() && b.is_basic()); + del(a); + set(a, basic_value(b)); + } + else { + SASSERT(!a.is_basic() && !b.is_basic()); + del_poly(a.to_algebraic()); + del_interval(a.to_algebraic()); + copy(a.to_algebraic(), b.to_algebraic()); + } + } + } + + bool factor(scoped_upoly const & up, factors & r) { + // std::cout << "factor: "; upm().display(std::cout, up); std::cout << std::endl; + if (m_factor) { + return upm().factor(up, r, m_factor_params); + } + else { + scoped_upoly & up_sqf = m_isolate_tmp3; + up_sqf.reset(); + upm().square_free(up.size(), up.c_ptr(), up_sqf); + TRACE("anum_bug", upm().display(tout, up_sqf.size(), up_sqf.c_ptr()); tout << "\n";); + r.push_back(up_sqf, 1); + return false; + } + } + + struct lt_proc { + manager & m; + lt_proc(manager & _m):m(_m) {} + bool operator()(numeral const & a1, numeral const & a2) const { + return m.lt(a1, a2); + } + }; + + void sort_roots(numeral_vector & r) { + std::sort(r.begin(), r.end(), lt_proc(m_wrapper)); + } + + void isolate_roots(scoped_upoly const & up, numeral_vector & roots) { + if (up.empty()) + return; // ignore the zero polynomial + factors & fs = m_isolate_factors; + fs.reset(); + bool full_fact; + if (upm().has_zero_roots(up.size(), up.c_ptr())) { + roots.push_back(numeral()); + scoped_upoly & nz_up = m_isolate_tmp2; + upm().remove_zero_roots(up.size(), up.c_ptr(), nz_up); + full_fact = factor(nz_up, fs); + } + else { + full_fact = factor(up, fs); + } + + unsigned num_factors = fs.distinct_factors(); + for (unsigned i = 0; i < num_factors; i++) { + upolynomial::numeral_vector const & f = fs[i]; + // polynomial f contains the non zero roots + unsigned d = upm().degree(f); + if (d == 0) + continue; // found all roots of f + scoped_mpq r(qm()); + if (d == 1) { + TRACE("algebraic", tout << "linear polynomial...\n";); + // f is a linear polynomial ax + b + // set r <- -b/a + qm().set(r, f[0]); + qm().div(r, f[1], r); + qm().neg(r); + roots.push_back(numeral(mk_basic_cell(r))); + continue; + } + SASSERT(m_isolate_roots.empty() && m_isolate_lowers.empty() && m_isolate_uppers.empty()); + upm().sqf_isolate_roots(f.size(), f.c_ptr(), bqm(), m_isolate_roots, m_isolate_lowers, m_isolate_uppers); + // collect rational/basic roots + unsigned sz = m_isolate_roots.size(); + for (unsigned i = 0; i < sz; i++) { + to_mpq(qm(), m_isolate_roots[i], r); + roots.push_back(numeral(mk_basic_cell(r))); + } + SASSERT(m_isolate_uppers.size() == m_isolate_lowers.size()); + // collect non-basic roots + sz = m_isolate_lowers.size(); + for (unsigned i = 0; i < sz; i++) { + mpbq & lower = m_isolate_lowers[i]; + mpbq & upper = m_isolate_uppers[i]; + if (!upm().isolating2refinable(f.size(), f.c_ptr(), bqm(), lower, upper)) { + // found rational root... it is stored in lower + to_mpq(qm(), lower, r); + roots.push_back(numeral(mk_basic_cell(r))); + } + else { + algebraic_cell * c = mk_algebraic_cell(f.size(), f.c_ptr(), lower, upper, full_fact); + roots.push_back(numeral(c)); + } + } + m_isolate_roots.reset(); + m_isolate_lowers.reset(); + m_isolate_uppers.reset(); + } + sort_roots(roots); + } + + void isolate_roots(polynomial_ref const & p, numeral_vector & roots) { + SASSERT(is_univariate(p)); + TRACE("algebraic", tout << "isolating roots of: " << p << "\n";); + if (::is_zero(p)) + return; // ignore the zero polynomial + scoped_upoly & up = m_isolate_tmp1; + upm().to_numeral_vector(p, up); + isolate_roots(up, roots); + } + + void mk_root(scoped_upoly const & up, unsigned i, numeral & r) { + // TODO: implement version that finds i-th root without isolating all roots. + if (i == 0) + throw algebraic_exception("invalid root object, root index must be greater than 0"); + if (up.empty()) + throw algebraic_exception("invalid root object, polynomial must not be the zero polynomial"); + SASSERT(i != 0); + scoped_numeral_vector roots(m_wrapper); + isolate_roots(up, roots); + unsigned num_roots = roots.size(); + TRACE("algebraic", tout << "num-roots: " << num_roots << "\n"; + for (unsigned i = 0; i < num_roots; i++) { + display_interval(tout, roots[i]); + tout << "\n"; + }); + if (i > num_roots) + throw algebraic_exception("invalid root object, polynomial does have sufficient roots"); + set(r, roots[i-1]); + } + + void mk_root(polynomial_ref const & p, unsigned i, numeral & r) { + SASSERT(i != 0); + SASSERT(is_univariate(p)); + TRACE("algebraic", tout << "isolating roots of: " << p << "\n";); + scoped_upoly & up = m_isolate_tmp1; + upm().to_numeral_vector(p, up); + mk_root(up, i, r); + } + + void mk_root(sexpr const * p, unsigned i, numeral & r) { + SASSERT(i != 0); + scoped_upoly & up = m_isolate_tmp1; + sexpr2upolynomial(upm(), p, up); + TRACE("algebraic", tout << "mk_root " << i << "\n"; upm().display(tout, up); tout << "\n";); + mk_root(up, i, r); + } + + /** + \brief Make sure that if a is 0, then a.m_cell == 0 + */ + void normalize(numeral & a) { + if (is_zero(a)) + return; + if (a.is_basic()) { + if (qm().is_zero(a.to_basic()->m_value)) + reset(a); + } + else { + algebraic_cell * c = a.to_algebraic(); + if (!upm().normalize_interval_core(c->m_p_sz, c->m_p, sign_lower(c), bqm(), lower(c), upper(c))) + reset(a); + } + } + + /** + \brief Return the magnitude of the given interval. + The magnitude is an approximation of the size of the interval. + */ + int magnitude(mpbq const & l, mpbq const & u) { + SASSERT(bqm().is_nonneg(l) || bqm().is_nonpos(u)); + int l_k = l.k(); + int u_k = u.k(); + if (l_k == u_k) + return bqm().magnitude_ub(l); + if (bqm().is_nonneg(l)) + return qm().log2(u.numerator()) - qm().log2(l.numerator()) - u_k + l_k - u_k; + else + return qm().mlog2(u.numerator()) - qm().mlog2(l.numerator()) - u_k + l_k - u_k; + } + + /** + \brief Return the magnitude of the isolating interval associated with the given algebraic_cell + */ + int magnitude(algebraic_cell * c) { + return magnitude(lower(c), upper(c)); + } + + /** + \brief Refine isolating interval associated with algebraic number. + The new interval will half of the size of the original one. + + Return TRUE, if interval was refined + Return FALSE, if actual root was found. + */ + bool refine_core(algebraic_cell * c) { + return upm().refine_core(c->m_p_sz, c->m_p, sign_lower(c), bqm(), lower(c), upper(c)); + } + + /** + \brief Refine isolating interval associated with algebraic number. + This procedure is a noop if algebraic number is basic. + + This method essentially updates the field m_interval + The new interval will half of the size of the original one. + + Remark: a root object may become basic when invoking this method, + since we may find the actual rational root. + This can only happen when non minimal polynomials are used to + encode root objects. + */ + bool refine(numeral & a) { + if (a.is_basic()) + return false; + algebraic_cell * c = a.to_algebraic(); + if (!refine_core(c)) { + // root was found + scoped_mpq r(qm()); + to_mpq(qm(), lower(c), r); + del(c); + a.m_cell = mk_basic_cell(r); + return false; + } + return true; + } + + bool refine(numeral & a, unsigned k) { + for (unsigned i = 0; i < k; i++) + if (!refine(a)) + return false; + return true; + } + + bool refine_until_prec(numeral & a, unsigned prec) { + if (a.is_basic()) + return true; + algebraic_cell * c = a.to_algebraic(); + if (!upm().refine(c->m_p_sz, c->m_p, bqm(), lower(c), upper(c), prec)) { + // actual root was found + scoped_mpq r(qm()); + to_mpq(qm(), lower(c), r); + del(c); + a.m_cell = mk_basic_cell(r); + return false; + } + return true; + } + + /** + Functor for computing the polynomial + resultant_y(pa(x-y), pb(y)) + where + pa and pb are the polynomials for algebraic cells: a and b. + + Remark: If alpha and beta are roots of pa and pb, then + alpha + beta is a root of the new polynomial. + + Remark: If the argument IsAdd == false, then the + functor computes resultant_y(pa(x+y), pb(y)) + */ + template + struct mk_add_polynomial { + imp & m; + + mk_add_polynomial(imp & _m):m(_m) {} + + void operator()(algebraic_cell * a, algebraic_cell * b, scoped_upoly & r) const { + polynomial_ref pa_x(m.pm()); // pa(x) + polynomial_ref pa_x_y(m.pm()); // pa(x-y) for addition and pa(x+y) for subtraction + polynomial_ref pb_y(m.pm()); // pb(y) + polynomial_ref r_x(m.pm()); // r(x) = resultant_y(pa(x-y), pb(y)) + pa_x = m.pm().to_polynomial(a->m_p_sz, a->m_p, m.m_x); + pb_y = m.pm().to_polynomial(b->m_p_sz, b->m_p, m.m_y); + if (IsAdd) + m.pm().compose_x_minus_y(pa_x, m.m_y, pa_x_y); + else + m.pm().compose_x_plus_y(pa_x, m.m_y, pa_x_y); + m.pm().resultant(pa_x_y, pb_y, m.m_y, r_x); + m.upm().to_numeral_vector(r_x, r); + } + }; + + /** + Functor for computing the polynomial + resultant_y(y^n * pa(x/y), pb(y)) + where + pa and pb are the polynomials for algebraic cells: a and b. + n is degree of pa. + */ + struct mk_mul_polynomial { + imp & m; + + mk_mul_polynomial(imp & _m):m(_m) {} + + void operator()(algebraic_cell * a, algebraic_cell * b, scoped_upoly & r) const { + polynomial_ref pa_x(m.pm()); // pa(x) + polynomial_ref pa_x_div_y(m.pm()); // y^n * pa(x/y) + polynomial_ref pb_y(m.pm()); // pb(y) + polynomial_ref r_x(m.pm()); // r(x) = resultant_y(y^n * pa(x/y), pb(y)) + pa_x = m.pm().to_polynomial(a->m_p_sz, a->m_p, m.m_x); + pb_y = m.pm().to_polynomial(b->m_p_sz, b->m_p, m.m_y); + pa_x_div_y = m.pm().compose_x_div_y(pa_x, m.m_y); + m.pm().resultant(pa_x_div_y, pb_y, m.m_y, r_x); + m.upm().to_numeral_vector(r_x, r); + } + }; + + /** + \brief Return the sum (interval) of the intervals of algebraic cells a and b. + */ + template + struct add_interval_proc { + imp & m; + add_interval_proc(imp & _m):m(_m) {} + + void operator()(algebraic_cell * a, algebraic_cell * b, mpbqi & r) const { + if (IsAdd) + m.bqim().add(a->m_interval, b->m_interval, r); + else + m.bqim().sub(a->m_interval, b->m_interval, r); + } + }; + + /** + \brief Return the product of the intervals of algebraic cells a and b. + */ + struct mul_interval_proc { + imp & m; + mul_interval_proc(imp & _m):m(_m) {} + + void operator()(algebraic_cell * a, algebraic_cell * b, mpbqi & r) const { + m.bqim().mul(a->m_interval, b->m_interval, r); + } + }; + + /** + \brief Functor for c <- a + b + */ + struct add_proc { + imp & m; + add_proc(imp & _m):m(_m) {} + void operator()(numeral & a, numeral & b, numeral & c) const { return m.add(a, b, c); } + }; + + /** + \brief Functor for c <- a - b + */ + struct sub_proc { + imp & m; + sub_proc(imp & _m):m(_m) {} + void operator()(numeral & a, numeral & b, numeral & c) const { return m.sub(a, b, c); } + }; + + /** + \brief Functor for c <- a * b + */ + struct mul_proc { + imp & m; + mul_proc(imp & _m):m(_m) {} + void operator()(numeral & a, numeral & b, numeral & c) const { return m.mul(a, b, c); } + }; + + // Save the isolating interval of an algebraic cell. + struct save_intervals { + imp & m_owner; + numeral const & m_num; + mpbqi m_old_interval; + bool m_restore_invoked; // true if restore_if_too_small was invoked + + save_intervals(imp & o, numeral const & num): + m_owner(o), + m_num(num), + m_restore_invoked(false) { + SASSERT(!num.is_basic()); + m_owner.bqim().set(m_old_interval, num.to_algebraic()->m_interval); + } + + ~save_intervals() { + if (!m_restore_invoked) + restore_if_too_small(); + m_owner.bqim().del(m_old_interval); + } + + // Restore the intervals of m_cell, if its current magnitude is too small + void restore_if_too_small() { + m_restore_invoked = true; + if (m_num.is_basic()) + return; // m_num is not algebraic anymore + algebraic_cell * cell = m_num.to_algebraic(); + if (m_owner.magnitude(cell) < m_owner.m_min_magnitude) { + // restore old interval + m_owner.bqim().swap(cell->m_interval, m_old_interval); + } + } + }; + + /** + \brief Set c with the algebraic number associated with polynomial p and isolating interval r_i == (l, u). + The isolating interval is not normalized, that is, it may contain zero. + + The method also requires the following (redundant) additional information: + - seq: The Sturm sequence for p + - lV: The Number of sign variations (in seq) at l + - lU: The Number of sign variations (in seq) at u + + \pre p must be square free + \pre r_i must be an isolating interval for p + \pre seq must be the Sturm sequence for p + \pre lV and uV are the sign variations (in seq) for r_i.lower() and r_i.upper() + */ + void set_core(numeral & c, scoped_upoly & p, mpbqi & r_i, upolynomial::scoped_upolynomial_sequence & seq, int lV, int uV, bool minimal) { + TRACE("algebraic", tout << "set_core p: "; upm().display(tout, p); tout << "\n";); + if (bqim().contains_zero(r_i)) { + if (upm().has_zero_roots(p.size(), p.c_ptr())) { + // zero is a root of p, and r_i is an isolating interval containing zero, + // then c is zero + reset(c); + TRACE("algebraic", tout << "reseting\nresult: "; display_root(tout, c); tout << "\n";); + return; + } + int zV = upm().sign_variations_at_zero(seq); + if (lV == zV) { + // root is in the second half + bqim().set_lower(r_i, mpbq()); + } + else { + SASSERT(zV == uV); + // root is in the first half + bqim().set_upper(r_i, mpbq()); + } + SASSERT(bqm().lt(r_i.lower(), r_i.upper())); + } + + // make sure 0 is not a root of p + scoped_upoly & nz_p = m_add_tmp; + if (upm().has_zero_roots(p.size(), p.c_ptr())) { + // remove zero root + upm().remove_zero_roots(p.size(), p.c_ptr(), nz_p); + } + else { + p.swap(nz_p); + } + + if (!upm().isolating2refinable(nz_p.size(), nz_p.c_ptr(), bqm(), r_i.lower(), r_i.upper())) { + // found actual root + scoped_mpq r(qm()); + to_mpq(qm(), r_i.lower(), r); + set(c, r); + } + else { + TRACE("algebraic", tout << "set_core...\n";); + set(c, nz_p.size(), nz_p.c_ptr(), r_i.lower(), r_i.upper(), minimal); + } + } + + /** + \brief Apply a binary operation on the given algebraic numbers. + + \pre !a.is_basic() and !b.is_basic() + + The template arguments: + + MkResultPoly: functor for constructing a polynomial p(x) s.t. p(u) = 0 (where u is the result of the operation). + MkResultInterval: functor for computing an approximation of the resultant interval using the interval of the arguments. + The functor must be "monotonic". That is, if we provide better (smaller) input intervals, it produces a better + (smaller) output interval. + MkBasic: functor for applying the operation if a or b become a basic cell. The numerals a and b may become basic + during refinement. + */ + template + void mk_binary(numeral & a, numeral & b, numeral & c, MkResultPoly const & mk_poly, MkResultInterval const & mk_interval, MkBasic const & mk_basic) { + SASSERT(!a.is_basic()); + SASSERT(!b.is_basic()); + algebraic_cell * cell_a = a.to_algebraic(); + algebraic_cell * cell_b = b.to_algebraic(); + scoped_upoly p(upm()); + scoped_upoly f(upm()); + mk_poly(cell_a, cell_b, p); + TRACE("anum_mk_binary", tout << "a: "; display_root(tout, a); tout << "\nb: "; display_root(tout, b); tout << "\np: "; + upm().display(tout, p); tout << "\n";); + factors fs(upm()); + bool full_fact = factor(p, fs); + unsigned num_fs = fs.distinct_factors(); + scoped_ptr_vector seqs; + for (unsigned i = 0; i < num_fs; i++) { + TRACE("anum_mk_binary", tout << "factor " << i << "\n"; upm().display(tout, fs[i]); tout << "\n";); + typename upolynomial::scoped_upolynomial_sequence * seq = alloc(typename upolynomial::scoped_upolynomial_sequence, upm()); + upm().sturm_seq(fs[i].size(), fs[i].c_ptr(), *seq); + seqs.push_back(seq); + } + SASSERT(seqs.size() == num_fs); + + save_intervals saved_a(*this, a); + save_intervals saved_b(*this, b); + scoped_mpbqi r_i(bqim()); + + while (true) { + checkpoint(); + SASSERT(!a.is_basic()); + SASSERT(!b.is_basic()); + mk_interval(cell_a, cell_b, r_i); + + unsigned num_rem = 0; // number of remaining sequences + unsigned target_i = UINT_MAX; // index of sequence that is isolating + int target_lV, target_uV; + for (unsigned i = 0; i < num_fs; i++) { + if (seqs[i] == 0) + continue; // sequence was discarded because it does not contain the root. + TRACE("anum_mk_binary", tout << "sequence " << i << "\n"; upm().display(tout, *(seqs[i])); tout << "\n";); + int lV = upm().sign_variations_at(*(seqs[i]), r_i.lower()); + int uV = upm().sign_variations_at(*(seqs[i]), r_i.upper()); + int V = lV - uV; + TRACE("algebraic", tout << "r_i: "; bqim().display(tout, r_i); tout << "\n"; + tout << "lV: " << lV << ", uV: " << uV << "\n"; + tout << "a.m_interval: "; bqim().display(tout, cell_a->m_interval); tout << "\n"; + tout << "b.m_interval: "; bqim().display(tout, cell_b->m_interval); tout << "\n"; + ); + if (V <= 0) { + // discard sequence, since factor does not contain the root + seqs.set(i, 0); + } + else if (V == 1) { + target_i = i; + target_lV = lV; + target_uV = uV; + num_rem++; + } + else { + num_rem++; + } + } + + if (num_rem == 1 && target_i != UINT_MAX) { + // found isolating interval + TRACE("anum_mk_binary", tout << "target_i: " << target_i << "\n";); + saved_a.restore_if_too_small(); + saved_b.restore_if_too_small(); + upm().set(fs[target_i].size(), fs[target_i].c_ptr(), f); + set_core(c, f, r_i, *(seqs[target_i]), target_lV, target_uV, full_fact); + return; + } + + if (!refine(a) || !refine(b)) { + // a or b became basic + SASSERT(a.is_basic() || b.is_basic()); + saved_a.restore_if_too_small(); + saved_a.restore_if_too_small(); + return mk_basic(a, b, c); + } + } + } + + template + void mk_unary(numeral & a, numeral & b, MkResultPoly const & mk_poly, MkResultInterval const & mk_interval, MkBasic const & mk_basic) { + SASSERT(!a.is_basic()); + algebraic_cell * cell_a = a.to_algebraic(); + + scoped_upoly p(upm()); + scoped_upoly f(upm()); + mk_poly(cell_a, p); + + factors fs(upm()); + bool full_fact = factor(p, fs); + unsigned num_fs = fs.distinct_factors(); + scoped_ptr_vector seqs; + for (unsigned i = 0; i < num_fs; i++) { + typename upolynomial::scoped_upolynomial_sequence * seq = alloc(typename upolynomial::scoped_upolynomial_sequence, upm()); + upm().sturm_seq(fs[i].size(), fs[i].c_ptr(), *seq); + seqs.push_back(seq); + } + SASSERT(seqs.size() == num_fs); + + save_intervals saved_a(*this, a); + scoped_mpbqi r_i(bqim()); + + while (true) { + checkpoint(); + SASSERT(!a.is_basic()); + mk_interval(cell_a, r_i); + + unsigned num_rem = 0; // number of remaining sequences + unsigned target_i = UINT_MAX; // index of sequence that is isolating + int target_lV, target_uV; + for (unsigned i = 0; i < num_fs; i++) { + if (seqs[i] == 0) + continue; // sequence was discarded because it does not contain the root. + int lV = upm().sign_variations_at(*(seqs[i]), r_i.lower()); + int uV = upm().sign_variations_at(*(seqs[i]), r_i.upper()); + int V = lV - uV; + TRACE("algebraic", tout << "r_i: "; bqim().display(tout, r_i); tout << "\n"; + tout << "lV: " << lV << ", uV: " << uV << "\n"; + tout << "a.m_interval: "; bqim().display(tout, cell_a->m_interval); tout << "\n"; + ); + if (V <= 0) { + // discard sequence, since factor does not contain the root + seqs.set(i, 0); + } + else if (V == 1) { + target_i = i; + target_lV = lV; + target_uV = uV; + num_rem++; + } + else { + num_rem++; + } + } + + if (num_rem == 1 && target_i != UINT_MAX) { + // found isolating interval + saved_a.restore_if_too_small(); + upm().set(fs[target_i].size(), fs[target_i].c_ptr(), f); + set_core(b, f, r_i, *(seqs[target_i]), target_lV, target_uV, full_fact); + return; + } + + if (!refine(a)) { + // a became basic + SASSERT(a.is_basic()); + saved_a.restore_if_too_small(); + return mk_basic(a, b); + } + } + } + + /** + Functor for computing the polynomial + resultant_y(x^k - y, pa(y)) + where + pa is the polynomial for algebraic cell: a. + k is a parameter. + */ + struct mk_root_polynomial { + imp & m; + unsigned k; + + mk_root_polynomial(imp & _m, unsigned _k):m(_m), k(_k) {} + + void operator()(algebraic_cell * a, scoped_upoly & r) const { + // Let p be the polynomial associated with a. + // Then, r(x) := Resultant(x^k - y, p(y), y) + // is a polynomial s.t. a^{1/k} is a root of r(x). + + // Create r(x) + polynomial_ref p_y(m.pm()); + polynomial_ref xk_y(m.pm()); + polynomial_ref y(m.pm()); + polynomial_ref r_x(m.pm()); + p_y = m.pm().to_polynomial(a->m_p_sz, a->m_p, m.m_y); + y = m.pm().mk_polynomial(m.m_y); + xk_y = m.pm().mk_polynomial(m.m_x, k); + xk_y = xk_y - y; + m.pm().resultant(xk_y, p_y, m.m_y, r_x); + m.upm().to_numeral_vector(r_x, r); + } + }; + + /** + \brief Return the k-th root of the interval of an algebraic cell a. + */ + struct root_interval_proc { + imp & m; + unsigned k; + root_interval_proc(imp & _m, unsigned _k):m(_m), k(_k) {} + + void operator()(algebraic_cell * a, mpbqi & r) const { + m.bqm().root_lower(m.lower(a), k, r.lower()); + m.bqm().root_upper(m.upper(a), k, r.upper()); + } + }; + + /** + \brief Functor for b <- a^{1/k} + */ + struct root_proc { + imp & m; + unsigned k; + root_proc(imp & _m, unsigned _k):m(_m), k(_k) {} + void operator()(numeral & a, numeral & b) const { + return m.root(a, k, b); + } + }; + + /** + Functor for computing the polynomial + resultant_y(x - y^k, pa(y)) + where + pa is the polynomial for algebraic cell: a. + k is a parameter. + */ + struct mk_power_polynomial { + imp & m; + unsigned k; + + mk_power_polynomial(imp & _m, unsigned _k):m(_m), k(_k) {} + + void operator()(algebraic_cell * a, scoped_upoly & r) const { + polynomial_ref p_y(m.pm()); + polynomial_ref x(m.pm()); + polynomial_ref x_yk(m.pm()); + polynomial_ref r_x(m.pm()); + p_y = m.pm().to_polynomial(a->m_p_sz, a->m_p, m.m_y); + x = m.pm().mk_polynomial(m.m_x); + x_yk = m.pm().mk_polynomial(m.m_y, k); + x_yk = x - x_yk; + m.pm().resultant(x_yk, p_y, m.m_y, r_x); + m.upm().to_numeral_vector(r_x, r); + } + }; + + /** + \brief Return the ^k of the interval of an algebraic cell a. + */ + struct power_interval_proc { + imp & m; + unsigned k; + power_interval_proc(imp & _m, unsigned _k):m(_m), k(_k) {} + + void operator()(algebraic_cell * a, mpbqi & r) const { + m.bqim().power(a->m_interval, k, r); + } + }; + + /** + \brief Functor for b <- a^k + */ + struct power_proc { + imp & m; + unsigned k; + + power_proc(imp & _m, unsigned _k):m(_m), k(_k) {} + void operator()(numeral & a, numeral & b) const { + return m.power(a, k, b); + } + }; + + + void root_core(basic_cell * a, unsigned k, numeral & b) { + SASSERT(!qm().is_zero(a->m_value)); + SASSERT(k > 1); + mpq & a_val = a->m_value; + scoped_mpq r_a_val(qm()); + + if (qm().root(a_val, k, r_a_val)) { + // the result is rational + TRACE("root_core", tout << "r_a_val: " << r_a_val << " a_val: "; qm().display(tout, a_val); tout << "\n";); + set(b, r_a_val); + return; + } + + // Let a_val be of the form n/d + // create polynomial p: d*x^k - n + // a_val > 0 --> (0, a_val+1) is an isolating interval + // a_val < 0 --> (a_val-1, 0) is an isolating interval + + // Create p + scoped_upoly p(upm()); + p.push_back(mpz()); + qm().set(p.back(), a_val.numerator()); + qm().neg(p.back()); + for (unsigned i = 0; i < k; i++) + p.push_back(mpz()); + qm().set(p.back(), a_val.denominator()); + + // Create isolating interval + scoped_mpbq lower(bqm()); + scoped_mpbq upper(bqm()); + if (qm().is_neg(a_val)) { + if (!bqm().to_mpbq(a_val, lower)) { + // a_val is not a binary rational, lower is just an approximation. + // lower == a_val.numerator() / 2^{log2(a_val.denominator()) + 1} + // Thus, 2*lower <= a_val <= lower + bqm().mul2(lower); // make sure lower <= a_val + } + bqm().sub(lower, mpz(1), lower); // make sure lower < (a_val)^{1/k} + } + else { + if (!bqm().to_mpbq(a_val, upper)) { + // a_val is not a binary rational, upper is just an approximation. + // upper == a_val.numerator() / 2^{log2(a_val.denominator()) + 1} + // Thus, upper <= a_val <= 2*upper + bqm().mul2(upper); // make sure a_val <= upper + } + bqm().add(upper, mpz(1), upper); // make sure (a_val)^{1/k} < upper + } + SASSERT(bqm().lt(lower, upper)); + TRACE("algebraic", tout << "root_core:\n"; upm().display(tout, p.size(), p.c_ptr()); tout << "\n";); + // p is not necessarily a minimal polynomial. + // So, we set the m_minimal flag to false. TODO: try to factor. + set(b, p.size(), p.c_ptr(), lower, upper, false); + } + + void root(numeral & a, unsigned k, numeral & b) { + if (k == 0) + throw algebraic_exception("0-th root is indeterminate"); + if (k == 1 || is_zero(a)) { + set(b, a); + return; + } + + if (is_neg(a) && k % 2 == 0) { + // Remark: some computer algebra systems (e.g., Mathematica) define the + // k-th root of a negative number as a complex number for any k. + // We should check if our definition will not confuse users. + throw algebraic_exception("even root of negative number is not real"); + } + + if (a.is_basic()) + root_core(a.to_basic(), k, b); + else + mk_unary(a, b, mk_root_polynomial(*this, k), root_interval_proc(*this, k), root_proc(*this, k)); + } + + void power_core(basic_cell * a, unsigned k, numeral & b) { + scoped_mpq r(qm()); + qm().power(basic_value(a), k, r); + set(b, r); + } + + void power(numeral & a, unsigned k, numeral & b) { + if (is_zero(a) && k == 0) + throw algebraic_exception("0^0 is indeterminate"); + if (k == 0) { + set(b, 1); + return; + } + if (k == 1) { + set(b, a); + return; + } + if (is_zero(a)) { + reset(b); + return; + } + if (a.is_basic()) { + scoped_mpq r(qm()); + qm().power(basic_value(a), k, r); + set(b, r); + } + else { + mk_unary(a, b, mk_power_polynomial(*this, k), power_interval_proc(*this, k), power_proc(*this, k)); + } + } + + void add(basic_cell * a, basic_cell * b, numeral & c) { + scoped_mpq r(qm()); + qm().add(basic_value(a), basic_value(b), r); + set(c, r); + normalize(c); + } + + void sub(basic_cell * a, basic_cell * b, numeral & c) { + scoped_mpq r(qm()); + qm().sub(basic_value(a), basic_value(b), r); + set(c, r); + normalize(c); + } + + template + void add(algebraic_cell * a, basic_cell * b, numeral & c) { + TRACE("algebraic", tout << "adding algebraic and basic cells:\n"; + tout << "a: "; upm().display(tout, a->m_p_sz, a->m_p); tout << " "; bqim().display(tout, a->m_interval); tout << "\n"; + tout << "b: "; qm().display(tout, b->m_value); tout << "\n";); + scoped_mpq nbv(qm()); + qm().set(nbv, b->m_value); + if (IsAdd) + qm().neg(nbv); + m_add_tmp.reset(); + upm().translate_q(a->m_p_sz, a->m_p, nbv, m_add_tmp); + mpbqi const & i = a->m_interval; + scoped_mpbq l(bqm()); + scoped_mpbq u(bqm()); + qm().neg(nbv); + if (bqm().to_mpbq(nbv, l)) { + bqm().add(i.upper(), l, u); + bqm().add(i.lower(), l, l); + } + else { + // failed to convert to binary rational + scoped_mpq il(qm()); + scoped_mpq iu(qm()); + to_mpq(qm(), i.lower(), il); + to_mpq(qm(), i.upper(), iu); + TRACE("algebraic", + tout << "nbv: " << nbv << "\n"; + tout << "il: " << il << ", iu: " << iu << "\n";); + qm().add(il, nbv, il); + qm().add(iu, nbv, iu); + // (il, iu) is an isolating refinable (rational) interval for the new polynomial. + upm().convert_q2bq_interval(m_add_tmp.size(), m_add_tmp.c_ptr(), il, iu, bqm(), l, u); + } + TRACE("algebraic", + upm().display(tout, m_add_tmp.size(), m_add_tmp.c_ptr()); + tout << ", l: " << l << ", u: " << u << "\n"; + tout << "l_sign: " << upm().eval_sign_at(m_add_tmp.size(), m_add_tmp.c_ptr(), l) << "\n"; + tout << "u_sign: " << upm().eval_sign_at(m_add_tmp.size(), m_add_tmp.c_ptr(), u) << "\n"; + ); + set(c, m_add_tmp.size(), m_add_tmp.c_ptr(), l, u, a->m_minimal /* minimality is preserved */); + normalize(c); + } + + void add(numeral & a, numeral & b, numeral & c) { + if (is_zero(a)) { + set(c, b); + return; + } + + if (is_zero(b)) { + set(c, a); + return; + } + + if (a.is_basic()) { + if (b.is_basic()) + add(a.to_basic(), b.to_basic(), c); + else + add(b.to_algebraic(), a.to_basic(), c); + } + else { + if (b.is_basic()) + add(a.to_algebraic(), b.to_basic(), c); + else + mk_binary(a, b, c, mk_add_polynomial(*this), add_interval_proc(*this), add_proc(*this)); + } + } + + void sub(numeral & a, numeral & b, numeral & c) { + if (is_zero(a)) { + set(c, b); + neg(c); + return; + } + + if (is_zero(b)) { + set(c, a); + return; + } + + if (a.is_basic()) { + if (b.is_basic()) + sub(a.to_basic(), b.to_basic(), c); + else { + // c <- b - a + add(b.to_algebraic(), a.to_basic(), c); + // c <- -c = a - b + neg(c); + } + } + else { + if (b.is_basic()) + add(a.to_algebraic(), b.to_basic(), c); + else + mk_binary(a, b, c, mk_add_polynomial(*this), add_interval_proc(*this), sub_proc(*this)); + } + } + + void mul(basic_cell * a, basic_cell * b, numeral & c) { + scoped_mpq r(qm()); + qm().mul(basic_value(a), basic_value(b), r); + set(c, r); + normalize(c); + } + + void mul(algebraic_cell * a, basic_cell * b, numeral & c) { + TRACE("algebraic", tout << "mult algebraic and basic cells:\n"; + tout << "a: "; upm().display(tout, a->m_p_sz, a->m_p); tout << " "; bqim().display(tout, a->m_interval); tout << "\n"; + tout << "b: "; qm().display(tout, b->m_value); tout << "\n";); + SASSERT(upm().eval_sign_at(a->m_p_sz, a->m_p, lower(a)) == -upm().eval_sign_at(a->m_p_sz, a->m_p, upper(a))); + scoped_mpq nbv(qm()); + qm().set(nbv, b->m_value); + qm().inv(nbv); + scoped_upoly & mulp = m_add_tmp; + upm().set(a->m_p_sz, a->m_p, mulp); + upm().compose_p_q_x(mulp.size(), mulp.c_ptr(), nbv); + mpbqi const & i = a->m_interval; + scoped_mpbq l(bqm()); + scoped_mpbq u(bqm()); + qm().inv(nbv); + bool is_neg = qm().is_neg(nbv); + if (bqm().to_mpbq(nbv, l)) { + bqm().mul(i.upper(), l, u); + bqm().mul(i.lower(), l, l); + if (is_neg) + bqm().swap(l, u); + } + else { + // failed to convert to binary rational + scoped_mpq il(qm()); + scoped_mpq iu(qm()); + to_mpq(qm(), i.lower(), il); + to_mpq(qm(), i.upper(), iu); + TRACE("algebraic", + tout << "nbv: " << nbv << "\n"; + tout << "il: " << il << ", iu: " << iu << "\n";); + qm().mul(il, nbv, il); + qm().mul(iu, nbv, iu); + if (is_neg) + qm().swap(il, iu); + // (il, iu) is an isolating refinable (rational) interval for the new polynomial. + upm().convert_q2bq_interval(mulp.size(), mulp.c_ptr(), il, iu, bqm(), l, u); + } + TRACE("algebraic", + upm().display(tout, mulp.size(), mulp.c_ptr()); + tout << ", l: " << l << ", u: " << u << "\n"; + tout << "l_sign: " << upm().eval_sign_at(mulp.size(), mulp.c_ptr(), l) << "\n"; + tout << "u_sign: " << upm().eval_sign_at(mulp.size(), mulp.c_ptr(), u) << "\n"; + ); + set(c, mulp.size(), mulp.c_ptr(), l, u, a->m_minimal /* minimality is preserved */); + normalize(c); + } + + void mul(numeral & a, numeral & b, numeral & c) { + if (is_zero(a) || is_zero(b)) { + reset(c); + return; + } + + if (a.is_basic()) { + if (b.is_basic()) + mul(a.to_basic(), b.to_basic(), c); + else + mul(b.to_algebraic(), a.to_basic(), c); + } + else { + if (b.is_basic()) + mul(a.to_algebraic(), b.to_basic(), c); + else + mk_binary(a, b, c, mk_mul_polynomial(*this), mul_interval_proc(*this), mul_proc(*this)); + } + } + + void neg(numeral & a) { + if (is_zero(a)) + return; + if (a.is_basic()) { + qm().neg(a.to_basic()->m_value); + } + else { + algebraic_cell * c = a.to_algebraic(); + upm().p_minus_x(c->m_p_sz, c->m_p); + bqim().neg(c->m_interval); + update_sign_lower(c); + } + } + + /** + Make sure lower != 0 and upper != 0 if a is non-basic algebraic number. + */ + void refine_nz_bound(numeral & a) { + if (a.is_basic()) + return; + algebraic_cell * cell_a = a.to_algebraic(); + mpbq & lower = cell_a->m_interval.lower(); + mpbq & upper = cell_a->m_interval.upper(); + if (!bqm().is_zero(lower) && !bqm().is_zero(upper)) + return; + int sign_l = sign_lower(cell_a); + SASSERT(sign_l != 0); + int sign_u = -sign_l; + +#define REFINE_LOOP(BOUND, TARGET_SIGN) \ + while (true) { \ + bqm().div2(BOUND); \ + int new_sign = upm().eval_sign_at(cell_a->m_p_sz, cell_a->m_p, BOUND); \ + if (new_sign == 0) { \ + /* found actual root */ \ + scoped_mpq r(qm()); \ + to_mpq(qm(), BOUND, r); \ + set(a, r); \ + return; \ + } \ + if (new_sign == TARGET_SIGN) \ + return; \ + } + + if (bqm().is_zero(lower)) { + bqm().set(lower, upper); + REFINE_LOOP(lower, sign_l); + } + else { + SASSERT(bqm().is_zero(upper)); + bqm().set(upper, lower); + REFINE_LOOP(upper, sign_u); + } + } + + void inv(numeral & a) { + if (is_zero(a)) { + UNREACHABLE(); + throw algebraic_exception("division by zero"); + } + refine_nz_bound(a); + if (a.is_basic()) { + qm().inv(a.to_basic()->m_value); + } + else { + TRACE("algebraic_bug", tout << "before inv: "; display_root(tout, a); tout << "\n"; display_interval(tout, a); tout << "\n";); + algebraic_cell * cell_a = a.to_algebraic(); + upm().p_1_div_x(cell_a->m_p_sz, cell_a->m_p); + // convert binary rational bounds into rational bounds + scoped_mpq inv_lower(qm()), inv_upper(qm()); + to_mpq(qm(), lower(cell_a), inv_lower); + to_mpq(qm(), upper(cell_a), inv_upper); + // (1/upper, 1/lower) is an isolating interval for the new polynomial + qm().inv(inv_lower); + qm().inv(inv_upper); + qm().swap(inv_lower, inv_upper); + TRACE("algebraic_bug", tout << "inv new_bounds: " << qm().to_string(inv_lower) << ", " << qm().to_string(inv_upper) << "\n";); + // convert isolating interval back as a binary rational bound + upm().convert_q2bq_interval(cell_a->m_p_sz, cell_a->m_p, inv_lower, inv_upper, bqm(), lower(cell_a), upper(cell_a)); + TRACE("algebraic_bug", tout << "after inv: "; display_root(tout, a); tout << "\n"; display_interval(tout, a); tout << "\n";); + } + } + + void div(numeral & a, numeral & b, numeral & c) { + if (is_zero(b)) { + UNREACHABLE(); + throw algebraic_exception("division by zero"); + } + // div is not used by the nonlinear procedure. + // I implemented just to make sure that all field operations are available in the algebraic number module. + // It is also useful to allow users to evaluate expressions containing algebraic numbers. + // + // We can avoid computing invb, by having a procedure similar to mul + // that uses + // Resultant(pa(xy), pb(y), y) instead of + // Resultant(y^n * pa(x/y), pb(y), y) + // + scoped_anum invb(m_wrapper); + set(invb, b); + inv(invb); + mul(a, invb, c); + } + + // Todo: move to MPQ + int compare(mpq const & a, mpq const & b) { + if (qm().eq(a, b)) + return 0; + return qm().lt(a, b) ? -1 : 1; + } + + /** + Comparing algebraic_cells with rationals + Given an algebraic cell c with isolating interval (l, u) for p and a rational b + Then, + u <= b implies c < b + b <= l implies c > b + Otherwise, l < b < u, and + p(b) < 0 --> If p(l) < 0 then c > b else c < b + p(b) == 0 --> c = b + p(b) > 0 --> if p(l) < 0 then c < b else c > b + + We can simplify the rules above as: + p(b) == 0 then c == b + (p(b) < 0) == (p(l) < 0) then c > b else c < b + */ + int compare(algebraic_cell * c, mpq const & b) { + mpbq const & l = lower(c); + mpbq const & u = upper(c); + if (bqm().le(u, b)) + return -1; + if (bqm().ge(l, b)) + return 1; + // b is in the isolating interval (l, u) + int sign_b = upm().eval_sign_at(c->m_p_sz, c->m_p, b); + if (sign_b == 0) + return 0; + return sign_b == sign_lower(c) ? 1 : -1; + } + + // Return true if the polynomials of cell_a and cell_b are the same. + bool compare_p(algebraic_cell const * cell_a, algebraic_cell const * cell_b) { + return upm().eq(cell_a->m_p_sz, cell_a->m_p, cell_b->m_p_sz, cell_b->m_p); + } + + int compare_core(numeral & a, numeral & b) { + SASSERT(!a.is_basic() && !b.is_basic()); + algebraic_cell * cell_a = a.to_algebraic(); + algebraic_cell * cell_b = b.to_algebraic(); + mpbq const & a_lower = lower(cell_a); + mpbq const & a_upper = upper(cell_a); + mpbq const & b_lower = lower(cell_b); + mpbq const & b_upper = upper(cell_b); + + #define COMPARE_INTERVAL() \ + if (bqm().le(a_upper, b_lower)) { \ + m_compare_cheap++; \ + return -1; \ + } \ + if (bqm().ge(a_lower, b_upper)) { \ + m_compare_cheap++; \ + return 1; \ + } + + COMPARE_INTERVAL(); + + // if cell_a and cell_b, contain the same polynomial, + // and the intervals are overlaping, then they are + // the same root. + if (compare_p(cell_a, cell_b)) { + m_compare_poly_eq++; + return 0; + } + + TRACE("algebraic", tout << "comparing\n"; + tout << "a: "; upm().display(tout, cell_a->m_p_sz, cell_a->m_p); tout << "\n"; bqim().display(tout, cell_a->m_interval); + tout << "\ncell_a->m_minimal: " << cell_a->m_minimal << "\n"; + tout << "b: "; upm().display(tout, cell_b->m_p_sz, cell_b->m_p); tout << "\n"; bqim().display(tout, cell_b->m_interval); + tout << "\ncell_b->m_minimal: " << cell_b->m_minimal << "\n";); + + if (cell_a->m_minimal && cell_b->m_minimal) { + // Minimal polynomial special case. + // This branch is only executed when polynomial + // factorization is turned on. + + // If a and b are defined by minimal distinct polynomials, + // then they MUST BE DIFFERENT. + // Thus, if we keep refining the interval of a and b, + // eventually they will not overlap + while (true) { + checkpoint(); + refine(a); + refine(b); + m_compare_refine++; + // refine can't reduce a and b to rationals, + // since the polynomial is minimal and it is not linear. + // So, the roots are NOT rational. + SASSERT(!a.is_basic()); + SASSERT(!b.is_basic()); + COMPARE_INTERVAL(); + } + } + + // make sure that intervals of a and b have the same magnitude + int a_m = magnitude(a_lower, a_upper); + int b_m = magnitude(b_lower, b_upper); + int target_m = std::max(m_min_magnitude, std::min(a_m, b_m)); + if (b_m > target_m) { + if (!refine(b, b_m - target_m)) + return compare(a, b); + m_compare_refine += b_m - target_m; + COMPARE_INTERVAL(); + } + if (a_m > target_m) { + if (!refine(a, a_m - target_m)) + return compare(a, b); + m_compare_refine += a_m - target_m; + COMPARE_INTERVAL(); + } + + if (target_m > m_min_magnitude) { + int num_refinements = target_m - m_min_magnitude; + for (int i = 0; i < num_refinements; i++) { + if (!refine(a) || !refine(b)) + return compare(a, b); + m_compare_refine++; + COMPARE_INTERVAL(); + } + } + + // EXPENSIVE CASE + // Let seq be the Sturm-Tarski sequence for + // p_a, p_a' * p_b + // Let s_l be the number of sign variations at a_lower. + // Let s_u be the number of sign variations at a_upper. + // By Sturm-Tarski Theorem, we have that + // V = s_l - s_u = #(p_b(r) > 0) - #(p_b(r) < 0) at roots r of p_a + // Since there is only one root of p_a in (a_lower, b_lower), + // we are evaluating the sign of p_b at a. + // That is V is the sign of p_b at a. + // + // We have + // V < 0 -> p_b(a) < 0 -> if p_b(b_lower) < 0 then b > a else b < a + // V == 0 -> p_b(a) == 0 -> a = b + // V > 0 -> p_b(a) > 0 -> if p_b(b_lower) > 0 then b > a else b < a + // Simplifying we have: + // V == 0 --> a = b + // if (V < 0) == (p_b(b_lower) < 0) then b > a else b < a + // + m_compare_sturm++; + upolynomial::scoped_upolynomial_sequence seq(upm()); + upm().sturm_tarski_seq(cell_a->m_p_sz, cell_a->m_p, cell_b->m_p_sz, cell_b->m_p, seq); + int V = upm().sign_variations_at(seq, a_lower) - upm().sign_variations_at(seq, a_upper); + TRACE("algebraic", tout << "comparing using sturm\n"; display_interval(tout, a); tout << "\n"; display_interval(tout, b); tout << "\n"; + tout << "V: " << V << ", sign_lower(a): " << sign_lower(cell_a) << ", sign_lower(b): " << sign_lower(cell_b) << "\n";); + if (V == 0) + return 0; + if ((V < 0) == (sign_lower(cell_b) < 0)) + return -1; + else + return 1; + + // Here is an unexplored option for comparing numbers. + // + // The isolating intervals of a and b are still overlaping + // Then we compute + // r(x) = Resultant(x - y1 + y2, p1(y1), p2(y2)) + // where p1(y1) and p2(y2) are the polynomials defining a and b. + // Remarks: + // 1) The resultant r(x) must not be the zero polynomial, + // since the polynomial x - y1 + y2 does not vanish in any of the roots of p1(y1) and p2(y2) + // + // 2) By resultant calculus, If alpha, beta1, beta2 are roots of x - y1 + y2, p1(y1), p2(y2) + // then alpha is a root of r(x). + // Thus, we have that a - b is a root of r(x) + // + // 3) If 0 is not a root of r(x), then a != b (by remark 2) + // + // 4) Let (l1, u1) and (l2, u2) be the intervals of a and b. + // Then, a - b must be in (l1 - u2, u1 - l2) + // + // 5) Assume a != b, then if we keep refining the isolating intervals for a and b, + // then eventually, (l1, u1) and (l2, u2) will not overlap. + // Thus, if 0 is not a root of r(x), we can keep refining until + // the intervals do not overlap. + // + // 6) If 0 is a root of r(x), we have two possibilities: + // a) Isolate roots of r(x) in the interval (l1 - u2, u1 - l2), + // and then keep refining (l1, u1) and (l2, u2) until they + // (l1 - u2, u1 - l2) "convers" only one root. + // + // b) Compute the sturm sequence for r(x), + // keep refining the (l1, u1) and (l2, u2) until + // (l1 - u2, u1 - l2) contains only one root of r(x) + } + + int compare(numeral & a, numeral & b) { + TRACE("algebraic", tout << "comparing: "; display_interval(tout, a); tout << " "; display_interval(tout, b); tout << "\n";); + if (a.is_basic()) { + if (b.is_basic()) + return compare(basic_value(a), basic_value(b)); + else + return -compare(b.to_algebraic(), basic_value(a)); + } + else { + if (b.is_basic()) + return compare(a.to_algebraic(), basic_value(b)); + else + return compare_core(a, b); + } + } + + bool eq(numeral & a, numeral & b) { + return compare(a, b) == 0; + } + + bool eq(numeral & a, mpq const & b) { + if (a.is_basic()) + return qm().eq(basic_value(a), b); + else + return compare(a.to_algebraic(), b) == 0; + } + + bool lt(numeral & a, numeral & b) { + return compare(a, b) < 0; + } + + bool lt(numeral & a, mpq const & b) { + if (a.is_basic()) + return qm().lt(basic_value(a), b); + else + return compare(a.to_algebraic(), b) < 0; + } + + bool gt(numeral & a, mpq const & b) { + if (a.is_basic()) + return qm().gt(basic_value(a), b); + else + return compare(a.to_algebraic(), b) > 0; + } + + void get_polynomial(numeral const & a, svector & r) { + if (a.is_basic()) { + r.reserve(2); + if (is_zero(a)) { + qm().set(r[0], 0); + qm().set(r[1], 1); + } + else { + mpq const & v = basic_value(a); + qm().set(r[0], v.numerator()); + qm().set(r[1], v.denominator()); + qm().neg(r[0]); + } + upm().set_size(2, r); + } + else { + algebraic_cell * c = a.to_algebraic(); + upm().set(c->m_p_sz, c->m_p, r); + } + } + + /** + \brief "Optimistic" mapping: it assumes all variables are mapped to + basic_values (rationals). Throws an exception if that is not the case. + */ + struct opt_var2basic : public polynomial::var2mpq { + struct failed {}; + imp & m_imp; + polynomial::var2anum const & m_x2v; + opt_var2basic(imp & i, polynomial::var2anum const & x2v):m_imp(i), m_x2v(x2v) {} + virtual unsynch_mpq_manager & m() const { return m_imp.qm(); } + virtual bool contains(polynomial::var x) const { return m_x2v.contains(x); } + virtual mpq const & operator()(polynomial::var x) const { + anum const & v = m_x2v(x); + if (!v.is_basic()) + throw failed(); + return m_imp.basic_value(v); + } + }; + + /** + \brief Reduced mapping which contains only the rational values + */ + struct var2basic : public polynomial::var2mpq { + imp & m_imp; + polynomial::var2anum const & m_x2v; + var2basic(imp & i, polynomial::var2anum const & x2v):m_imp(i), m_x2v(x2v) {} + virtual unsynch_mpq_manager & m() const { return m_imp.qm(); } + virtual bool contains(polynomial::var x) const { return m_x2v.contains(x) && m_x2v(x).is_basic(); } + virtual mpq const & operator()(polynomial::var x) const { + anum const & v = m_x2v(x); + SASSERT(v.is_basic()); + TRACE("var2basic", tout << "getting value of x" << x << " -> " << m().to_string(m_imp.basic_value(v)) << "\n";); + return m_imp.basic_value(v); + } + }; + + /** + \brief Reduced mapping which contains only the non-basic values as intervals + */ + struct var2interval : public polynomial::var2mpbqi { + imp & m_imp; + polynomial::var2anum const & m_x2v; + var2interval(imp & i, polynomial::var2anum const & x2v):m_imp(i), m_x2v(x2v) {} + virtual mpbqi_manager & m() const { return m_imp.bqim(); } + virtual bool contains(polynomial::var x) const { return m_x2v.contains(x) && !m_x2v(x).is_basic(); } + virtual mpbqi const & operator()(polynomial::var x) const { + anum const & v = m_x2v(x); + SASSERT(!v.is_basic()); + return v.to_algebraic()->m_interval; + } + }; + + polynomial::var_vector m_eval_sign_vars; + int eval_sign_at(polynomial_ref const & p, polynomial::var2anum const & x2v) { + polynomial::manager & ext_pm = p.m(); + TRACE("anum_eval_sign", tout << "evaluating sign of: " << p << "\n";); + while (true) { + bool restart = false; + // Optimistic: maybe x2v contains only rational values + try { + opt_var2basic x2v_basic(*this, x2v); + scoped_mpq r(qm()); + ext_pm.eval(p, x2v_basic, r); + TRACE("anum_eval_sign", tout << "all variables are assigned to rationals, value of p: " << r << "\n";); + return qm().sign(r); + } + catch (opt_var2basic::failed) { + // continue + } + + // Eliminate rational values from p + polynomial_ref p_prime(ext_pm); + var2basic x2v_basic(*this, x2v); + p_prime = ext_pm.substitute(p, x2v_basic); + TRACE("anum_eval_sign", tout << "p after eliminating rationals: " << p_prime << "\n";); + + if (ext_pm.is_zero(p_prime)) { + // polynomial vanished after substituting rational values. + return 0; + } + + if (is_const(p_prime)) { + // polynomial became the constant polynomial after substitution. + SASSERT(size(p_prime) == 1); + return ext_pm.m().sign(ext_pm.coeff(p_prime, 0)); + } + + // Try to find sign using intervals + polynomial::var_vector & xs = m_eval_sign_vars; + xs.reset(); + ext_pm.vars(p_prime, xs); + SASSERT(!xs.empty()); + var2interval x2v_interval(*this, x2v); + scoped_mpbqi ri(bqim()); + + while (true) { + checkpoint(); + ext_pm.eval(p_prime, x2v_interval, ri); + TRACE("anum_eval_sign", tout << "evaluating using intervals: " << ri << "\n";); + if (!bqim().contains_zero(ri)) { + return bqim().is_pos(ri) ? 1 : -1; + } + // refine intervals if magnitude > m_min_magnitude + bool refined = false; + for (unsigned i = 0; i < xs.size(); i++) { + polynomial::var x = xs[i]; + SASSERT(x2v.contains(x)); + anum const & v = x2v(x); + SASSERT(!v.is_basic()); + algebraic_cell * c = v.to_algebraic(); + int m = magnitude(c); + if (m > m_min_magnitude || (m_zero_accuracy > 0 && m > m_zero_accuracy)) { + if (!refine(const_cast(v))) { + // v became a basic value + restart = true; + break; + } + TRACE("anum_eval_sign", tout << "refined algebraic interval\n";); + SASSERT(!v.is_basic()); + refined = true; + } + } + if (!refined || restart) { + // Stop if no interval was refined OR some algebraic cell became basic + break; + } + } + + if (restart) { + // Some non-basic value became basic. + // So, restarting the whole process + TRACE("anum_eval_sign", tout << "restarting some algebraic_cell became basic\n";); + continue; + } + + // At this point, we are almost sure that p is zero at x2n + // That is, rin is probably a very small interval that contains zero. + + // Remark: m_zero_accuracy == 0 means use precise computation. + if (m_zero_accuracy > 0) { + // assuming the value is 0, since the result is in (-1/2^k, 1/2^k), where m_zero_accuracy = k + return 0; + } +#if 0 + // Evaluating sign using algebraic arithmetic + scoped_anum ra(m_wrapper); + ext_pm.eval(p_prime, x2v, ra); + TRACE("anum_eval_sign", tout << "value of p as algebraic number " << ra << "\n";); + if (is_zero(ra)) + return 0; + return is_pos(ra) ? 1 : -1; +#else + // Evaluating the sign using Resultants + // Basic idea: + // We want to evaluate the sign of + // p(x_1, ..., x_n) + // at x_1 -> v_1, ..., x_n -> v_n + // + // Let v be p(v_1, ..., v_n). + // We want to know the sign of v. + // + // Assume v_i's are defined by the polynomials q_i(x_i) + // Then, we have that + // the polynomials + // y - p(x_1, ..., x_n), q_1(x_1), ..., q_n(x_n) + // are zero at y -> v, x_1 -> v_1, ..., x_n -> v_n + // + // Thus, by resultant theory, v is also a root of + // R(y) = Resultant(p(x_1, ..., x_n), q_1(x_1), ..., q_n(x_n)) + // Remark: R(y) is not the zero polynomial, since + // the coefficient of y in the polynomial y - p(x_1, ..., x_n) + // is (the constant polynomial) one. + // + // Now, let L be a lower bound on the nonzero roots of R(y). + // Thus, any root alpha of R(y) is zero or |alpha| > L + // + // Therefore, we have that |v| > L + // Now, using L, we can keep refining the interval ri which contains v. + // Eventually, ri will not contain zero (and consequently v != 0), + // or ri is in (-L, L), and consequently v = 0. + polynomial::var y = get_max_var(xs) + 1; + ensure_num_vars(y+1); + // we create all polynomials in the local polynomial manager because we need an extra variable y + polynomial_ref yp(pm()); + yp = pm().mk_polynomial(y); + polynomial_ref p_prime_aux(pm()); + p_prime_aux = convert(ext_pm, p_prime, pm()); + polynomial_ref R(pm()); + R = yp - p_prime_aux; + // compute the resultants + polynomial_ref q_i(pm()); + std::stable_sort(xs.begin(), xs.end(), var_degree_lt(*this, x2v)); + // std::cout << "R: " << R << "\n"; + for (unsigned i = 0; i < xs.size(); i++) { + checkpoint(); + polynomial::var x_i = xs[i]; + SASSERT(x2v.contains(x_i)); + anum const & v_i = x2v(x_i); + SASSERT(!v_i.is_basic()); + algebraic_cell * c = v_i.to_algebraic(); + q_i = pm().to_polynomial(c->m_p_sz, c->m_p, x_i); + // std::cout << "q_i: " << q_i << std::endl; + pm().resultant(R, q_i, x_i, R); + SASSERT(!pm().is_zero(R)); + } + SASSERT(pm().is_univariate(R)); + scoped_upoly & _R = m_eval_sign_tmp; + upm().to_numeral_vector(R, _R); + unsigned k = upm().nonzero_root_lower_bound(_R.size(), _R.c_ptr()); + TRACE("anum_eval_sign", tout << "R: " << R << "\nk: " << k << "\nri: "<< ri << "\n";); + // std::cout << "R: " << R << "\n"; + scoped_mpbq mL(bqm()), L(bqm()); + bqm().set(mL, -1); + bqm().set(L, 1); + bqm().div2k(mL, k); + bqm().div2k(L, k); + if (bqm().lt(mL, ri.lower()) && bqm().lt(ri.upper(), L)) + return 0; + // keep refining ri until ri is inside (-L, L) or + // ri does not contain zero. + + // The invervals (for the values of the variables in xs) are going to get too small. + // So, we save them before refining... + scoped_ptr_vector saved_intervals; + for (unsigned i = 0; i < xs.size(); i++) { + polynomial::var x_i = xs[i]; + SASSERT(x2v.contains(x_i)); + anum const & v_i = x2v(x_i); + SASSERT(!v_i.is_basic()); + saved_intervals.push_back(alloc(save_intervals, *this, v_i)); + } + + // Actual refinement loop + restart = false; + while (!restart) { + checkpoint(); + ext_pm.eval(p_prime, x2v_interval, ri); + TRACE("anum_eval_sign", tout << "evaluating using intervals: " << ri << "\n"; + tout << "zero lower bound is: " << L << "\n";); + if (!bqim().contains_zero(ri)) { + return bqim().is_pos(ri) ? 1 : -1; + } + + if (bqm().lt(mL, ri.lower()) && bqm().lt(ri.upper(), L)) + return 0; + + for (unsigned i = 0; i < xs.size(); i++) { + polynomial::var x = xs[i]; + SASSERT(x2v.contains(x)); + anum const & v = x2v(x); + SASSERT(!v.is_basic()); + if (!refine(const_cast(v))) { + // v became a basic value + restart = true; + break; + } + TRACE("anum_eval_sign", tout << "refined algebraic interval\n";); + SASSERT(!v.is_basic()); + } + } +#endif + } + } + + // Functor used to sort variables by the degree of the polynomial used to represent their value. + struct var_degree_lt { + imp & m_imp; + polynomial::var2anum const & m_x2v; + + var_degree_lt(imp & i, polynomial::var2anum const & x2v): + m_imp(i), + m_x2v(x2v) { + } + + unsigned degree(polynomial::var x) const { + if (!m_x2v.contains(x)) + return UINT_MAX; + return m_imp.degree(m_x2v(x)); + } + + bool operator()(polynomial::var x1, polynomial::var x2) const { + return degree(x1) < degree(x2); + } + }; + + // Add entry x->v to existing mapping + struct ext_var2num : public polynomial::var2anum { + manager & m_am; + polynomial::var2anum const & m_x2v; + polynomial::var m_x; + anum const & m_v; + ext_var2num(manager & am, polynomial::var2anum const & x2v, polynomial::var x, anum const & v): + m_am(am), + m_x2v(x2v), + m_x(x), + m_v(v) { + } + virtual manager & m() const { return m_am; } + virtual bool contains(polynomial::var x) const { return x == m_x || m_x2v.contains(x); } + virtual anum const & operator()(polynomial::var x) const { + if (x == m_x) + return m_v; + else + return m_x2v(x); + } + }; + + // Remove from roots any solution r such that p does not evaluate to 0 at x2v extended with x->r. + void filter_roots(polynomial_ref const & p, polynomial::var2anum const & x2v, polynomial::var x, numeral_vector & roots) { + TRACE("isolate_roots", tout << "before filtering roots, x: x" << x << "\n"; + for (unsigned i = 0; i < roots.size(); i++) { + display_root(tout, roots[i]); tout << "\n"; + }); + + unsigned sz = roots.size(); + unsigned j = 0; + // std::cout << "p: " << p << "\n"; + // std::cout << "sz: " << sz << "\n"; + for (unsigned i = 0; i < sz; i++) { + checkpoint(); + // display_root(std::cout, roots[i]); std::cout << std::endl; + ext_var2num ext_x2v(m_wrapper, x2v, x, roots[i]); + TRACE("isolate_roots", tout << "filter_roots i: " << i << ", ext_x2v: x" << x << " -> "; display_root(tout, roots[i]); tout << "\n";); + int sign = eval_sign_at(p, ext_x2v); + TRACE("isolate_roots", tout << "filter_roots i: " << i << ", result sign: " << sign << "\n";); + if (sign != 0) + continue; + // display_decimal(std::cout, roots[i], 10); std::cout << " is root" << std::endl; + if (i != j) + set(roots[j], roots[i]); + j++; + } + for (unsigned i = j; i < sz; i++) + del(roots[i]); + roots.shrink(j); + + TRACE("isolate_roots", tout << "after filtering roots:\n"; + for (unsigned i = 0; i < roots.size(); i++) { + display_root(tout, roots[i]); tout << "\n"; + }); + } + + // Return the maximal variable in xs. + static polynomial::var get_max_var(polynomial::var_vector const & xs) { + SASSERT(!xs.empty()); + polynomial::var x = xs[0]; + for (unsigned i = 1; i < xs.size(); i++) { + if (xs[i] > x) + x = xs[i]; + } + return x; + } + + // Ensure that local polynomial manager has at least sz vars + void ensure_num_vars(unsigned sz) { + while (sz > pm().num_vars()) + pm().mk_var(); + SASSERT(pm().num_vars() >= sz); + } + + polynomial::var_vector m_isolate_roots_vars; + void isolate_roots(polynomial_ref const & p, polynomial::var2anum const & x2v, numeral_vector & roots, bool nested_call = false) { + TRACE("isolate_roots", tout << "isolating roots of: " << p << "\n";); + SASSERT(roots.empty()); + polynomial::manager & ext_pm = p.m(); + if (ext_pm.is_zero(p) || ext_pm.is_const(p)) { + TRACE("isolate_roots", tout << "p is zero or the constant polynomial\n";); + return; + } + + if (ext_pm.is_univariate(p)) { + TRACE("isolate_roots", tout << "p is univariate, using univariate procedure\n";); + isolate_roots(p, roots); + return; + } + + // eliminate rationals + polynomial_ref p_prime(ext_pm); + var2basic x2v_basic(*this, x2v); + p_prime = ext_pm.substitute(p, x2v_basic); + TRACE("isolate_roots", tout << "p after applying (rational fragment of) x2v:\n" << p_prime << "\n";); + + if (ext_pm.is_zero(p_prime) || ext_pm.is_const(p_prime)) { + TRACE("isolate_roots", tout << "p is zero or the constant polynomial after applying (rational fragment of) x2v\n";); + return; + } + + if (ext_pm.is_univariate(p_prime)) { + polynomial::var x = ext_pm.max_var(p_prime); + if (x2v.contains(x)) { + // The remaining variable is assigned, the actual unassigned variable vanished when we replaced rational values. + // So, the polynomial does not have any roots + return; + } + TRACE("isolate_roots", tout << "p is univariate after applying (rational fragment of) x2v... using univariate procedure\n";); + isolate_roots(p_prime, roots); + return; + } + + polynomial::var_vector & xs = m_isolate_roots_vars; + xs.reset(); + ext_pm.vars(p_prime, xs); + SASSERT(xs.size() > 1); + + // sort variables by the degree of the values + std::stable_sort(xs.begin(), xs.end(), var_degree_lt(*this, x2v)); + TRACE("isolate_roots", tout << "there are " << (xs.size() - 1) << " variables assigned to nonbasic numbers...\n";); + + // last variables is the one not assigned by x2v, or the unassigned variable vanished + polynomial::var x = xs.back(); + if (x2v.contains(x)) { + // all remaining variables are assigned. + // the unassigned variable vanished when we replaced the rational values. + DEBUG_CODE({ + for (unsigned i = 0; i < xs.size(); i++) { + SASSERT(x2v.contains(xs[i])); + } + }); + return; + } + + // construct univariate polynomial q which contains all roots of p_prime at x2v. + polynomial_ref q(ext_pm); + q = p_prime; + polynomial_ref p_y(ext_pm); + for (unsigned i = 0; i < xs.size() - 1; i++) { + checkpoint(); + polynomial::var y = xs[i]; + SASSERT(x2v.contains(y)); + anum const & v = x2v(y); + SASSERT(!v.is_basic()); + algebraic_cell * c = v.to_algebraic(); + p_y = ext_pm.to_polynomial(c->m_p_sz, c->m_p, y); + ext_pm.resultant(q, p_y, y, q); + TRACE("isolate_roots", tout << "resultant loop i: " << i << ", y: x" << y << "\np_y: " << p_y << "\n"; + tout << "q: " << q << "\n";); + if (ext_pm.is_zero(q)) { + SASSERT(!nested_call); + break; + } + } + + if (ext_pm.is_zero(q)) { + TRACE("isolate_roots", tout << "q vanished\n";); + // q may vanish at some of the other roots of the polynomial defining the values. + // To decide if p_prime vanishes at x2v or not, we start evaluating each coefficient of p_prime at x2v + // until we find one that is not zero at x2v. + // In the process we will copy p_prime to the local polynomial manager, since we will need to create + // an auxiliary variable. + SASSERT(!nested_call); + unsigned n = ext_pm.degree(p_prime, x); + SASSERT(n > 0); + if (n == 1) { + // p_prime is linear on p, so we just evaluate the coefficients... + TRACE("isolate_roots", tout << "p is linear after applying (rational fragment) of x2v\n";); + polynomial_ref c0(ext_pm); + polynomial_ref c1(ext_pm); + c0 = ext_pm.coeff(p_prime, x, 0); + c1 = ext_pm.coeff(p_prime, x, 1); + scoped_anum a0(m_wrapper); + scoped_anum a1(m_wrapper); + ext_pm.eval(c0, x2v, a0); + ext_pm.eval(c1, x2v, a1); + // the root must be - a0/a1 if a1 != 0 + if (is_zero(a1)) { + TRACE("isolate_roots", tout << "coefficient of degree 1 vanished, so p does not have roots at x2v\n";); + // p_prime does not have any root + return; + } + roots.push_back(anum()); + div(a0, a1, roots[0]); + neg(roots[0]); + TRACE("isolate_roots", tout << "after trivial solving p has only one root:\n"; display_root(tout, roots[0]); tout << "\n";); + } + else { + polynomial_ref c(ext_pm); + scoped_anum a(m_wrapper); + int i = n; + for (; i >= 1; i--) { + c = ext_pm.coeff(p_prime, x, i); + ext_pm.eval(c, x2v, a); + if (!is_zero(a)) + break; + } + if (i == 0) { + // all coefficients of x vanished, so + // the polynomial has no roots + TRACE("isolate_roots", tout << "all coefficients vanished... polynomial does not have roots\n";); + return; + } + SASSERT(!is_zero(a)); + polynomial::var z = get_max_var(xs) + 1; + ensure_num_vars(z+1); + // create polynomial q2 in the local manager + // z * x^i + c_{i-1} * x^{i-1} + ... + c_1 * x + c_0 + // where c's are the coefficients of p_prime. + // Then we invoke isolate_roots with q2 and x2v extended with z->a. + // The resultant will not vanish again because + // 0 is not a root of the polynomial defining a. + polynomial_ref q2(pm()); + polynomial_ref z_p(pm()); // z poly + polynomial_ref xi_p(pm()); // x^i poly + polynomial_ref zxi_p(pm()); // z*x^i + SASSERT(i >= 1); + q2 = convert(ext_pm, p_prime, pm(), x, static_cast(i-1)); + xi_p = pm().mk_polynomial(x, i); + z_p = pm().mk_polynomial(z); + q2 = z_p*xi_p + q2; + TRACE("isolate_roots", tout << "invoking isolate_roots with q2:\n" << q2 << "\n"; + tout << "z: x" << z << " -> "; display_root(tout, a); tout << "\n";); + // extend x2p with z->a + ext_var2num ext_x2v(m_wrapper, x2v, z, a); + isolate_roots(q2, ext_x2v, roots, true /* nested call */); + } + } + else if (ext_pm.is_const(q)) { + // q does not have any roots, so p_prime also does not have roots at x2v. + TRACE("isolate_roots", tout << "q is the constant polynomial, so p does not contain any roots at x2v\n";); + } + else { + SASSERT(is_univariate(q)); + isolate_roots(q, roots); + // some roots of q may not be roots of p_prime + filter_roots(p_prime, x2v, x, roots); + } + } + + int eval_at_mpbq(polynomial_ref const & p, polynomial::var2anum const & x2v, polynomial::var x, mpbq const & v) { + scoped_mpq qv(qm()); + to_mpq(qm(), v, qv); + scoped_anum av(m_wrapper); + set(av, qv); + ext_var2num ext_x2v(m_wrapper, x2v, x, av); + return eval_sign_at(p, ext_x2v); + } + + // Make sure that lower and upper of prev and curr don't touch each other + void separate(numeral & prev, numeral & curr) { + SASSERT(lt(prev, curr)); + if (prev.is_basic()) { + if (curr.is_basic()) { + // do nothing + } + else { + while (bqm().le(lower(curr.to_algebraic()), basic_value(prev))) { + refine(curr); + if (curr.is_basic()) + break; // curr became basic + } + } + } + else { + if (curr.is_basic()) { + while (bqm().ge(upper(prev.to_algebraic()), basic_value(curr))) { + refine(prev); + if (prev.is_basic()) + break; + } + } + else { + while (bqm().ge(upper(prev.to_algebraic()), lower(curr.to_algebraic()))) { + refine(prev); + refine(curr); + if (prev.is_basic() || curr.is_basic()) + break; + } + } + } + } + + void int_lt(numeral const & a, numeral & b) { + scoped_mpz v(qm()); + if (a.is_basic()) { + qm().floor(basic_value(a), v); + qm().dec(v); + } + else { + bqm().floor(qm(), lower(a.to_algebraic()), v); + } + m_wrapper.set(b, v); + } + + void int_gt(numeral const & a, numeral & b) { + scoped_mpz v(qm()); + if (a.is_basic()) { + qm().ceil(basic_value(a), v); + qm().inc(v); + } + else { + bqm().ceil(qm(), upper(a.to_algebraic()), v); + } + m_wrapper.set(b, v); + } + + // Select a numeral between prev and curr. + // Pre: prev < curr + void select(numeral & prev, numeral & curr, numeral & result) { + TRACE("algebraic_select", + tout << "prev: "; display_interval(tout, prev); tout << "\n"; + tout << "curr: "; display_interval(tout, curr); tout << "\n";); + SASSERT(lt(prev, curr)); + separate(prev, curr); + scoped_mpbq w(bqm()); + if (prev.is_basic()) { + if (curr.is_basic()) + bqm().select_small_core(qm(), basic_value(prev), basic_value(curr), w); + else + bqm().select_small_core(qm(), basic_value(prev), lower(curr.to_algebraic()), w); + } + else { + if (curr.is_basic()) + bqm().select_small_core(qm(), upper(prev.to_algebraic()), basic_value(curr), w); + else + bqm().select_small_core(upper(prev.to_algebraic()), lower(curr.to_algebraic()), w); + } + scoped_mpq qw(qm()); + to_mpq(qm(), w, qw); + set(result, qw); + } + + // Similar to ext_var2num but all variables that are not mapped by x2v are mapped to the same value. + struct ext2_var2num : public polynomial::var2anum { + manager & m_am; + polynomial::var2anum const & m_x2v; + anum const & m_v; + ext2_var2num(manager & am, polynomial::var2anum const & x2v, anum const & v): + m_am(am), + m_x2v(x2v), + m_v(v) { + } + virtual manager & m() const { return m_am; } + virtual bool contains(polynomial::var x) const { return true; } + virtual anum const & operator()(polynomial::var x) const { + if (m_x2v.contains(x)) + return m_x2v(x); + else + return m_v; + } + }; + +#define DEFAULT_PRECISION 2 + + void isolate_roots(polynomial_ref const & p, polynomial::var2anum const & x2v, numeral_vector & roots, svector & signs) { + isolate_roots(p, x2v, roots); + unsigned num_roots = roots.size(); + if (num_roots == 0) { + anum zero; + ext2_var2num ext_x2v(m_wrapper, x2v, zero); + int s = eval_sign_at(p, ext_x2v); + signs.push_back(s); + } + else { + TRACE("isolate_roots_bug", tout << "p: " << p << "\n"; + polynomial::var_vector xs; + p.m().vars(p, xs); + for (unsigned i = 0; i < xs.size(); i++) { + if (x2v.contains(xs[i])) { + tout << "x" << xs[i] << " -> "; + display_root(tout, x2v(xs[i])); + tout << " "; + display_interval(tout, x2v(xs[i])); + tout << "\n"; + } + } + for (unsigned i = 0; i < roots.size(); i++) { + tout << "root[i]: "; display_root(tout, roots[i]); tout << "\n"; + }); + for (unsigned i = 0; i < num_roots; i++) + refine_until_prec(roots[i], DEFAULT_PRECISION); + + scoped_anum w(m_wrapper); + int_lt(roots[0], w); + TRACE("isolate_roots_bug", tout << "w: "; display_root(tout, w); tout << "\n";); + { + ext2_var2num ext_x2v(m_wrapper, x2v, w); + int s = eval_sign_at(p, ext_x2v); + SASSERT(s != 0); + signs.push_back(s); + } + + for (unsigned i = 1; i < num_roots; i++) { + numeral & prev = roots[i-1]; + numeral & curr = roots[i]; + select(prev, curr, w); + ext2_var2num ext_x2v(m_wrapper, x2v, w); + int s = eval_sign_at(p, ext_x2v); + SASSERT(s != 0); + signs.push_back(s); + } + + int_gt(roots[num_roots - 1], w); + { + ext2_var2num ext_x2v(m_wrapper, x2v, w); + int s = eval_sign_at(p, ext_x2v); + SASSERT(s != 0); + signs.push_back(s); + } + } + } + + void display_root(std::ostream & out, numeral const & a) { + if (is_zero(a)) { + out << "(#, 1)"; // first root of polynomial # + } + else if (a.is_basic()) { + mpq const & v = basic_value(a); + scoped_mpz neg_n(qm()); + qm().set(neg_n, v.numerator()); + qm().neg(neg_n); + mpz const coeffs[2] = { neg_n.get(), v.denominator() }; + out << "("; + upm().display(out, 2, coeffs, "#"); + out << ", 1)"; // first root of the polynomial d*# - n + } + else { + algebraic_cell * c = a.to_algebraic(); + out << "("; + upm().display(out, c->m_p_sz, c->m_p, "#"); + if (c->m_i == 0) { + // undefined + c->m_i = upm().get_root_id(c->m_p_sz, c->m_p, lower(c)) + 1; + } + SASSERT(c->m_i > 0); + out << ", " << c->m_i; + out << ")"; + } + } + + void display_mathematica(std::ostream & out, numeral const & a) { + if (a.is_basic()) { + qm().display(out, basic_value(a)); + } + else { + algebraic_cell * c = a.to_algebraic(); + out << "Root["; + upm().display(out, c->m_p_sz, c->m_p, "#1", true); + if (c->m_i == 0) { + // undefined + c->m_i = upm().get_root_id(c->m_p_sz, c->m_p, lower(c)) + 1; + } + SASSERT(c->m_i > 0); + out << " &, " << c->m_i << "]"; + } + + } + + void display_root_smt2(std::ostream & out, numeral const & a) { + if (is_zero(a)) { + out << "(root-obj x 1)"; + } + else if (a.is_basic()) { + mpq const & v = basic_value(a); + scoped_mpz neg_n(qm()); + qm().set(neg_n, v.numerator()); + qm().neg(neg_n); + mpz const coeffs[2] = { neg_n.get(), v.denominator() }; + out << "(root-obj "; + upm().display_smt2(out, 2, coeffs, "x"); + out << " 1)"; // first root of the polynomial d*# - n + } + else { + algebraic_cell * c = a.to_algebraic(); + out << "(root-obj "; + upm().display_smt2(out, c->m_p_sz, c->m_p, "x"); + if (c->m_i == 0) { + // undefined + c->m_i = upm().get_root_id(c->m_p_sz, c->m_p, lower(c)) + 1; + } + SASSERT(c->m_i > 0); + out << " " << c->m_i; + out << ")"; + } + } + + void display_interval(std::ostream & out, numeral const & a) { + if (a.is_basic()) { + out << "["; + qm().display(out, basic_value(a)); + out << ", "; + qm().display(out, basic_value(a)); + out << "]"; + } + else { + bqim().display(out, a.to_algebraic()->m_interval); + } + } + + bool get_interval(numeral const & a, mpbq & l, mpbq & u, unsigned precision) { + SASSERT(!a.is_basic()); + algebraic_cell * c = a.to_algebraic(); + mpbqi const & i = c->m_interval; + bqm().set(l, i.lower()); + bqm().set(u, i.upper()); + // the precision on refine is base 2 + return upm().refine(c->m_p_sz, c->m_p, bqm(), l, u, precision * 4); + } + + void display_decimal(std::ostream & out, numeral const & a, unsigned precision) { + if (a.is_basic()) { + qm().display_decimal(out, basic_value(a), precision); + } + else { + scoped_mpbq l(bqm()), u(bqm()); + if (get_interval(a, l, u, precision)) { + // this is a hack... fix it + bqm().display_decimal(out, u, precision); + } + else { + // actual root was found + bqm().display_decimal(out, l, precision); + } + } + } + + void get_lower(numeral const & a, mpq & l, unsigned precision) { + if (a.is_basic()) { + qm().set(l, basic_value(a)); + } + else { + scoped_mpbq _l(bqm()), _u(bqm()); + get_interval(a, _l, _u, precision); + to_mpq(qm(), _l, l); + } + } + + void get_upper(numeral const & a, mpq & u, unsigned precision) { + if (a.is_basic()) { + qm().set(u, basic_value(a)); + } + else { + scoped_mpbq _l(bqm()), _u(bqm()); + get_interval(a, _l, _u, precision); + to_mpq(qm(), _u, u); + } + } + + }; + + manager::manager(unsynch_mpq_manager & m, params_ref const & p, small_object_allocator * a) { + m_own_allocator = false; + m_allocator = a; + if (m_allocator == 0) { + m_own_allocator = true; + m_allocator = alloc(small_object_allocator, "algebraic"); + } + m_imp = alloc(imp, *this, m, p, *m_allocator); + } + + manager::~manager() { + dealloc(m_imp); + if (m_own_allocator) + dealloc(m_allocator); + } + + void manager::updt_params(params_ref const & p) { + } + + void manager::set_cancel(bool f) { + m_imp->set_cancel(f); + } + + unsynch_mpq_manager & manager::qm() const { + return m_imp->qm(); + } + + mpbq_manager & manager::bqm() const { + return m_imp->bqm(); + } + + void manager::del(numeral & a) { + set_cancel(false); + return m_imp->del(a); + } + + void manager::reset(numeral & a) { + set_cancel(false); + return m_imp->reset(a); + } + + bool manager::is_zero(numeral const & a) { + set_cancel(false); + return m_imp->is_zero(const_cast(a)); + } + + bool manager::is_pos(numeral const & a) { + set_cancel(false); + return m_imp->is_pos(const_cast(a)); + } + + bool manager::is_neg(numeral const & a) { + set_cancel(false); + return m_imp->is_neg(const_cast(a)); + } + + bool manager::is_rational(numeral const & a) { + set_cancel(false); + return m_imp->is_rational(const_cast(a)); + } + + bool manager::is_int(numeral const & a) { + set_cancel(false); + return m_imp->is_int(const_cast(a)); + } + + unsigned manager::degree(numeral const & a) { + set_cancel(false); + return m_imp->degree(const_cast(a)); + } + + void manager::to_rational(numeral const & a, mpq & r) { + set_cancel(false); + return m_imp->to_rational(const_cast(a), r); + } + + void manager::to_rational(numeral const & a, rational & r) { + set_cancel(false); + return m_imp->to_rational(const_cast(a), r); + } + + void manager::swap(numeral & a, numeral & b) { + set_cancel(false); + return m_imp->swap(a, b); + } + + void manager::int_lt(numeral const & a, numeral & b) { + set_cancel(false); + m_imp->int_lt(const_cast(a), b); + } + + void manager::int_gt(numeral const & a, numeral & b) { + set_cancel(false); + m_imp->int_gt(const_cast(a), b); + } + + void manager::select(numeral const & prev, numeral const & curr, numeral & result) { + set_cancel(false); + m_imp->select(const_cast(prev), const_cast(curr), result); + } + + void manager::set(numeral & a, int n) { + scoped_mpq _n(qm()); + qm().set(_n, n); + set(a, _n); + } + + void manager::set(numeral & a, mpz const & n) { + scoped_mpq _n(qm()); + qm().set(_n, n); + set(a, _n); + } + + void manager::set(numeral & a, mpq const & n) { + set_cancel(false); + m_imp->set(a, n); + } + + void manager::set(numeral & a, numeral const & n) { + set_cancel(false); + m_imp->set(a, n); + } + + void manager::isolate_roots(polynomial_ref const & p, numeral_vector & roots) { + set_cancel(false); + m_imp->isolate_roots(p, roots); + } + + void manager::isolate_roots(polynomial_ref const & p, polynomial::var2anum const & x2v, numeral_vector & roots) { + set_cancel(false); + m_imp->isolate_roots(p, x2v, roots); + } + + void manager::isolate_roots(polynomial_ref const & p, polynomial::var2anum const & x2v, numeral_vector & roots, svector & signs) { + set_cancel(false); + m_imp->isolate_roots(p, x2v, roots, signs); + } + + void manager::mk_root(polynomial_ref const & p, unsigned i, numeral & r) { + set_cancel(false); + m_imp->mk_root(p, i, r); + } + + void manager::mk_root(sexpr const * p, unsigned i, numeral & r) { + set_cancel(false); + m_imp->mk_root(p, i, r); + } + + void manager::root(numeral const & a, unsigned k, numeral & b) { + set_cancel(false); + m_imp->root(const_cast(a), k, b); + } + + void manager::power(numeral const & a, unsigned k, numeral & b) { + TRACE("anum_detail", display_root(tout, a); tout << "^" << k << "\n";); + set_cancel(false); + m_imp->power(const_cast(a), k, b); + TRACE("anum_detail", tout << "^ result: "; display_root(tout, b); tout << "\n";); + } + + void manager::add(numeral const & a, numeral const & b, numeral & c) { + TRACE("anum_detail", display_root(tout, a); tout << " + "; display_root(tout, b); tout << "\n";); + set_cancel(false); + m_imp->add(const_cast(a), const_cast(b), c); + TRACE("anum_detail", tout << "+ result: "; display_root(tout, c); tout << "\n";); + } + + void manager::add(numeral const & a, mpz const & b, numeral & c) { + scoped_anum tmp(*this); + set(tmp, b); + add(a, tmp, c); + } + + void manager::sub(numeral const & a, numeral const & b, numeral & c) { + TRACE("anum_detail", display_root(tout, a); tout << " - "; display_root(tout, b); tout << "\n";); + set_cancel(false); + m_imp->sub(const_cast(a), const_cast(b), c); + TRACE("anum_detail", tout << "- result: "; display_root(tout, c); tout << "\n";); + } + + void manager::mul(numeral const & a, numeral const & b, numeral & c) { + TRACE("anum_detail", display_root(tout, a); tout << " * "; display_root(tout, b); tout << "\n";); + set_cancel(false); + m_imp->mul(const_cast(a), const_cast(b), c); + TRACE("anum_detail", tout << "* result: "; display_root(tout, c); tout << "\n";); + } + + void manager::div(numeral const & a, numeral const & b, numeral & c) { + set_cancel(false); + m_imp->div(const_cast(a), const_cast(b), c); + } + + void manager::neg(numeral & a) { + set_cancel(false); + m_imp->neg(a); + } + + void manager::inv(numeral & a) { + set_cancel(false); + m_imp->inv(a); + } + + int manager::compare(numeral const & a, numeral const & b) { + set_cancel(false); + return m_imp->compare(const_cast(a), const_cast(b)); + } + + bool manager::eq(numeral const & a, numeral const & b) { + set_cancel(false); + return m_imp->eq(const_cast(a), const_cast(b)); + } + + bool manager::eq(numeral const & a, mpq const & b) { + set_cancel(false); + return m_imp->eq(const_cast(a), b); + } + + bool manager::eq(numeral const & a, mpz const & b) { + scoped_mpq _b(qm()); + qm().set(_b, b); + return eq(const_cast(a), _b); + } + + bool manager::lt(numeral const & a, numeral const & b) { + set_cancel(false); + return m_imp->lt(const_cast(a), const_cast(b)); + } + + bool manager::lt(numeral const & a, mpq const & b) { + set_cancel(false); + return m_imp->lt(const_cast(a), b); + } + + bool manager::lt(numeral const & a, mpz const & b) { + scoped_mpq _b(qm()); + qm().set(_b, b); + return lt(const_cast(a), _b); + } + + bool manager::gt(numeral const & a, mpq const & b) { + set_cancel(false); + return m_imp->gt(const_cast(a), b); + } + + bool manager::gt(numeral const & a, mpz const & b) { + scoped_mpq _b(qm()); + qm().set(_b, b); + return gt(const_cast(a), _b); + } + + void manager::get_polynomial(numeral const & a, svector & r) { + m_imp->get_polynomial(a, r); + } + + void manager::get_lower(numeral const & a, mpbq & l) { + SASSERT(!is_rational(a)); + bqm().set(l, a.to_algebraic()->m_interval.lower()); + } + + void manager::get_lower(numeral const & a, mpq & l) { + scoped_mpbq bq(bqm()); + get_lower(a, bq); + to_mpq(qm(), bq, l); + } + + void manager::get_lower(numeral const & a, rational & l) { + scoped_mpq q(m_imp->qm()); + get_lower(a, q); + l = rational(q); + } + + void manager::get_lower(numeral const & a, mpq & l, unsigned precision) { + m_imp->get_lower(a, l, precision); + } + + void manager::get_lower(numeral const & a, rational & l, unsigned precision) { + scoped_mpq _l(qm()); + m_imp->get_lower(a, _l, precision); + l = rational(_l); + } + + void manager::get_upper(numeral const & a, mpbq & u) { + SASSERT(!is_rational(a)); + bqm().set(u, a.to_algebraic()->m_interval.upper()); + } + + void manager::get_upper(numeral const & a, mpq & u) { + scoped_mpbq bq(bqm()); + get_upper(a, bq); + to_mpq(qm(), bq, u); + } + + void manager::get_upper(numeral const & a, rational & u) { + scoped_mpq q(m_imp->qm()); + get_upper(a, q); + u = rational(q); + } + + void manager::get_upper(numeral const & a, mpq & l, unsigned precision) { + m_imp->get_upper(a, l, precision); + } + + void manager::get_upper(numeral const & a, rational & l, unsigned precision) { + scoped_mpq _l(qm()); + m_imp->get_upper(a, _l, precision); + l = rational(_l); + } + + int manager::eval_sign_at(polynomial_ref const & p, polynomial::var2anum const & x2v) { + set_cancel(false); + SASSERT(&(x2v.m()) == this); + return m_imp->eval_sign_at(p, x2v); + } + + void manager::display_interval(std::ostream & out, numeral const & a) const { + const_cast(this)->set_cancel(false); + m_imp->display_interval(out, a); + } + + void manager::display_decimal(std::ostream & out, numeral const & a, unsigned precision) const { + const_cast(this)->set_cancel(false); + m_imp->display_decimal(out, a, precision); + } + + void manager::display_root(std::ostream & out, numeral const & a) const { + const_cast(this)->set_cancel(false); + m_imp->display_root(out, a); + } + + void manager::display_mathematica(std::ostream & out, numeral const & a) const { + const_cast(this)->set_cancel(false); + m_imp->display_mathematica(out, a); + } + + void manager::display_root_smt2(std::ostream & out, numeral const & a) const { + const_cast(this)->set_cancel(false); + m_imp->display_root_smt2(out, a); + } + + void manager::reset_statistics() { + set_cancel(false); + m_imp->reset_statistics(); + } + + void manager::collect_statistics(statistics & st) const { + m_imp->collect_statistics(st); + } +}; diff --git a/lib/algebraic_numbers.h b/lib/algebraic_numbers.h new file mode 100644 index 000000000..4735a875b --- /dev/null +++ b/lib/algebraic_numbers.h @@ -0,0 +1,493 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + algebraic_numbers.h + +Abstract: + + Real Algebraic Numbers + +Author: + + Leonardo (leonardo) 2011-11-22 + +Notes: + +--*/ +#ifndef _ALGEBRAIC_NUMBERS_H_ +#define _ALGEBRAIC_NUMBERS_H_ + +#include"rational.h" +#include"mpq.h" +#include"polynomial.h" +#include"z3_exception.h" +#include"scoped_numeral.h" +#include"scoped_numeral_vector.h" +#include"tptr.h" +#include"statistics.h" +#include"params.h" + +class small_object_allocator; +class mpbq_manager; +class mpbq; + +namespace algebraic_numbers { + class anum; + class manager; + + class algebraic_exception : public default_exception { + public: + algebraic_exception(char const * msg):default_exception(msg) {} + }; + + class manager { + public: + struct imp; + private: + imp * m_imp; + small_object_allocator * m_allocator; + bool m_own_allocator; + public: + static bool precise() { return true; } + static bool field() { return true; } + typedef anum numeral; + typedef svector numeral_vector; + typedef _scoped_numeral scoped_numeral; + typedef _scoped_numeral_vector scoped_numeral_vector; + + manager(unsynch_mpq_manager & m, params_ref const & p = params_ref(), small_object_allocator * a = 0); + ~manager(); + + static void get_param_descrs(param_descrs & r); + static void collect_param_descrs(param_descrs & r) { get_param_descrs(r); } + + void set_cancel(bool f); + + void updt_params(params_ref const & p); + + unsynch_mpq_manager & qm() const; + + mpbq_manager & bqm() const; + + void del(numeral & a); + + /** + \brief a <- 0 + */ + void reset(numeral & a); + + /** + \brief Return true if a is zero. + */ + bool is_zero(numeral const & a); + + /** + \brief Return true if a is positive. + */ + bool is_pos(numeral const & a); + + /** + \brief Return true if a is negative. + */ + bool is_neg(numeral const & a); + + /** + \brief Return true if a is a rational number. + */ + bool is_rational(numeral const & a); + + /** + \brief Return true if a is an integer. + */ + bool is_int(numeral const & a); + + /** + \brief Degree of the algebraic number. + That is, degree of the polynomial that is used to encode \c a. + */ + unsigned degree(numeral const & a); + + /** + \brief Convert a into a rational number. + + \pre is_rational(a) + */ + void to_rational(numeral const & a, mpq & r); + + /** + \brief Convert a into a rational number. + + \pre is_rational(a) + */ + void to_rational(numeral const & a, rational & r); + + /** + \brief a <- n + */ + void set(numeral & a, int n); + void set(numeral & a, mpz const & n); + void set(numeral & a, mpq const & n); + void set(numeral & a, numeral const & n); + + void swap(numeral & a, numeral & b); + + /** + \brief Store in b an integer value smaller than 'a'. + + Remark: this is not the floor, but b <= floor(a) + */ + void int_lt(numeral const & a, numeral & b); + + /** + \brief Store in b an integer value bigger than 'a' + + Remark: this is not the ceil, but b >= ceil(a) + */ + void int_gt(numeral const & a, numeral & b); + + /** + \brief Store in result a value in the interval (prev, next) + + \pre lt(pre,v next) + */ + void select(numeral const & prev, numeral const & curr, numeral & result); + + /** + \brief Isolate the roots of (an univariate polynomial) p, and store them as algebraic numbers in \c root. + That is, p is in Z[x]. + */ + void isolate_roots(polynomial_ref const & p, numeral_vector & roots); + + /** + \brief Isolate the roots of a multivariate polynomial p such that all but one variable of p is fixed by x2v, and + store them as algebraic numbers in \c root. + + That is, we are viewing p as a polynomial in Z[y_1, ..., y_n][x]: + q_n(y_1, ..., y_n)x^n + ... + q_1(y_1, ..., y_n)*x + q_0 + And we are returning the roots of + q_n(x2v(y_1), ..., x2v(y_n))x^n + ... + q_1(x2v(y_1), ..., x2v(y_n))*x + q_0 + */ + void isolate_roots(polynomial_ref const & p, polynomial::var2anum const & x2v, numeral_vector & roots); + + /** + \brief Isolate the roots of the given polynomial, and compute its sign between them. + */ + void isolate_roots(polynomial_ref const & p, polynomial::var2anum const & x2v, numeral_vector & roots, svector & signs); + + /** + \brief Store in r the i-th root of p. + + This method throws an exception if p does not have at least i roots. + + This method is not really used in the nonlinear procedure. + It is mainly used for debugging purposes, and creating regression tests + + \pre i > 0 + */ + void mk_root(polynomial_ref const & p, unsigned i, numeral & r); + + /** + \brief Store in r the i-th root of p. + This method throws an exception if the s-expression p does not represent + an univariate polynomial, of if p does not have at least i roots. + + This method is not really used in the nonlinear procedure. + It is mainly used for debugging purposes, and "reading" root objects in the SMT 2.0 front-end. + + \pre i > 0 + */ + void mk_root(sexpr const * p, unsigned i, numeral & r); + + /** + \brief Return a^{1/k} + + Throws an exception if the result is not a real. + That is, (a is negative and k is even) or (k is zero). + */ + void root(numeral const & a, unsigned k, numeral & b); + + /** + \brief Return a^k + + Throws an exception if 0^0. + */ + void power(numeral const & a, unsigned k, numeral & b); + + /** + \brief c <- a + b + */ + void add(numeral const & a, numeral const & b, numeral & c); + void add(numeral const & a, mpz const & b, numeral & c); + + /** + \brief c <- a - b + */ + void sub(numeral const & a, numeral const & b, numeral & c); + + /** + \brief c <- a * b + */ + void mul(numeral const & a, numeral const & b, numeral & c); + + /** + \brief a <- -a + */ + void neg(numeral & a); + + /** + \brief a <- 1/a if a != 0 + */ + void inv(numeral & a); + + /** + \brief c <- a/b if b != 0 + */ + void div(numeral const & a, numeral const & b, numeral & c); + + /** + Return -1 if a < b + Return 0 if a == b + Return 1 if a > b + */ + int compare(numeral const & a, numeral const & b); + + /** + \brief a == b + */ + bool eq(numeral const & a, numeral const & b); + bool eq(numeral const & a, mpq const & b); + bool eq(numeral const & a, mpz const & b); + + /** + \brief a != b + */ + bool neq(numeral const & a, numeral const & b) { return !eq(a, b); } + bool neq(numeral const & a, mpq const & b) { return !eq(a, b); } + bool neq(numeral const & a, mpz const & b) { return !eq(a, b); } + + /** + \brief a < b + */ + bool lt(numeral const & a, numeral const & b); + bool lt(numeral const & a, mpq const & b); + bool lt(numeral const & a, mpz const & b); + + /** + \brief a > b + */ + bool gt(numeral const & a, numeral const & b) { return lt(b, a); } + bool gt(numeral const & a, mpq const & b); + bool gt(numeral const & a, mpz const & b); + + /** + \brief a <= b + */ + bool le(numeral const & a, numeral const & b) { return !gt(a, b); } + bool le(numeral const & a, mpq const & b) { return !gt(a, b); } + bool le(numeral const & a, mpz const & b) { return !gt(a, b); } + + /** + \brief a >= b + */ + bool ge(numeral const & a, numeral const & b) { return !lt(a, b); } + bool ge(numeral const & a, mpq const & b) { return !lt(a, b); } + bool ge(numeral const & a, mpz const & b) { return !lt(a, b); } + + /** + \brief Evaluate the sign of a multivariate polynomial p(x_1, ..., x_n) + at assignment x2v: [x_1 -> alpha_1, ..., x_n -> alpha_n]. + + \remark forall variable x in p, we have that x2v.contains(x) is true + + Return negative number if p(alpha_1, ..., alpha_n) < 0 + Return 0 if p(alpha_1, ..., alpha_n) == 0 + Return positive number if p(alpha_1, ..., alpha_n) > 0 + */ + int eval_sign_at(polynomial_ref const & p, polynomial::var2anum const & x2v); + + void get_polynomial(numeral const & a, svector & r); + + // Procedures for getting lower and upper bounds for irrational numbers + void get_lower(numeral const & a, mpbq & l); + void get_lower(numeral const & a, mpq & l); + void get_lower(numeral const & a, rational & l); + void get_lower(numeral const & a, mpq & l, unsigned precision); + void get_lower(numeral const & a, rational & l, unsigned precision); + + void get_upper(numeral const & a, mpbq & u); + void get_upper(numeral const & a, mpq & u); + void get_upper(numeral const & a, rational & u); + void get_upper(numeral const & a, mpq & l, unsigned precision); + void get_upper(numeral const & a, rational & l, unsigned precision); + + /** + \brief Display algebraic number as a rational if is_rational(n) + Otherwise, display it as an interval. + */ + void display_interval(std::ostream & out, numeral const & a) const; + + /** + \brief Display algebraic number in decimal notation. + A question mark is added based on the precision requested. + */ + void display_decimal(std::ostream & out, numeral const & a, unsigned precision = 10) const; + + /** + \brief Display algebraic number as a root object: (p, i) + That is, 'a' is the i-th root of p. + */ + void display_root(std::ostream & out, numeral const & a) const; + + /** + \brief Display algebraic number as a root object in SMT 2.0 style: (root-obj p i) + That is, 'a' is the i-th root of p. + */ + void display_root_smt2(std::ostream & out, numeral const & a) const; + + /** + \brief Display algebraic number in Mathematica format. + */ + void display_mathematica(std::ostream & out, numeral const & a) const; + + void display(std::ostream & out, numeral const & a) { return display_decimal(out, a); } + + void reset_statistics(); + + void collect_statistics(statistics & st) const; + }; + + struct basic_cell; + struct algebraic_cell; + + enum anum_kind { BASIC = 0, ROOT }; + + class anum { + friend struct manager::imp; + friend class manager; + void * m_cell; + anum(basic_cell * cell):m_cell(TAG(void*, cell, BASIC)) {} + anum(algebraic_cell * cell):m_cell(TAG(void*, cell, ROOT)) {} + bool is_basic() const { return GET_TAG(m_cell) == BASIC; } + basic_cell * to_basic() const { SASSERT(is_basic()); return UNTAG(basic_cell*, m_cell); } + algebraic_cell * to_algebraic() const { SASSERT(!is_basic()); return UNTAG(algebraic_cell*, m_cell); } + public: + anum():m_cell(0) {} + }; +}; + +typedef algebraic_numbers::manager anum_manager; +typedef algebraic_numbers::manager::numeral anum; +typedef algebraic_numbers::manager::numeral_vector anum_vector; +typedef algebraic_numbers::manager::scoped_numeral scoped_anum; +typedef algebraic_numbers::manager::scoped_numeral_vector scoped_anum_vector; + +#define AN_MK_COMPARISON_CORE(EXTERNAL, INTERNAL, TYPE) \ +inline bool EXTERNAL(scoped_anum const & a, TYPE const & b) { \ + anum_manager & m = a.m(); \ + scoped_anum _b(m); \ + m.set(_b, b); \ + return m.INTERNAL(a, _b); \ +} + +#define AN_MK_COMPARISON(EXTERNAL, INTERNAL) \ +AN_MK_COMPARISON_CORE(EXTERNAL, INTERNAL, int) \ +AN_MK_COMPARISON_CORE(EXTERNAL, INTERNAL, mpz) \ +AN_MK_COMPARISON_CORE(EXTERNAL, INTERNAL, mpq) + +AN_MK_COMPARISON(operator==, eq); +AN_MK_COMPARISON(operator!=, neq); +AN_MK_COMPARISON(operator<, lt); +AN_MK_COMPARISON(operator<=, le); +AN_MK_COMPARISON(operator>, gt); +AN_MK_COMPARISON(operator>=, ge); + +#undef AN_MK_COMPARISON +#undef AN_MK_COMPARISON_CORE + +#define AN_MK_BINARY_CORE(EXTERNAL, INTERNAL, TYPE) \ +inline scoped_anum EXTERNAL(scoped_anum const & a, TYPE const & b) { \ + anum_manager & m = a.m(); \ + scoped_anum _b(m); \ + m.set(_b, b); \ + scoped_anum r(m); \ + m.INTERNAL(a, _b, r); \ + return r; \ +} + +#define AN_MK_BINARY(EXTERNAL, INTERNAL) \ +AN_MK_BINARY_CORE(EXTERNAL, INTERNAL, int) \ +AN_MK_BINARY_CORE(EXTERNAL, INTERNAL, mpz) \ +AN_MK_BINARY_CORE(EXTERNAL, INTERNAL, mpq) + +AN_MK_BINARY(operator+, add) +AN_MK_BINARY(operator-, sub) +AN_MK_BINARY(operator*, mul) +AN_MK_BINARY(operator/, div) + +#undef AN_MK_BINARY +#undef AN_MK_BINARY_CORE + +inline scoped_anum root(scoped_anum const & a, unsigned k) { + scoped_anum r(a.m()); + a.m().root(a, k, r); + return r; +} + +inline scoped_anum power(scoped_anum const & a, unsigned k) { + scoped_anum r(a.m()); + a.m().power(a, k, r); + return r; +} + +inline scoped_anum operator^(scoped_anum const & a, unsigned k) { + return power(a, k); +} + +inline bool is_int(scoped_anum const & a) { + return a.m().is_int(a); +} + +inline bool is_rational(scoped_anum const & a) { + return a.m().is_rational(a); +} + +struct root_obj_pp { + anum_manager & m; + anum const & n; + root_obj_pp(anum_manager & _m, anum const & _n):m(_m), n(_n) {} + root_obj_pp(scoped_anum const & _n):m(_n.m()), n(_n.get()) {} +}; + +inline std::ostream & operator<<(std::ostream & out, root_obj_pp const & n) { + n.m.display_root(out, n.n); + return out; +} + +struct decimal_pp { + anum_manager & m; + anum const & n; + unsigned prec; + decimal_pp(anum_manager & _m, anum const & _n, unsigned p):m(_m), n(_n), prec(p) {} + decimal_pp(scoped_anum const & _n, unsigned p):m(_n.m()), n(_n.get()), prec(p) {} +}; + +inline std::ostream & operator<<(std::ostream & out, decimal_pp const & n) { + n.m.display_decimal(out, n.n, n.prec); + return out; +} + +struct interval_pp { + anum_manager & m; + anum const & n; + interval_pp(anum_manager & _m, anum const & _n):m(_m), n(_n) {} + interval_pp(scoped_anum const & _n):m(_n.m()), n(_n.get()) {} +}; + +inline std::ostream & operator<<(std::ostream & out, interval_pp const & n) { + n.m.display_interval(out, n.n); + return out; +} + +#endif diff --git a/lib/api.py b/lib/api.py new file mode 100644 index 000000000..8571759a4 --- /dev/null +++ b/lib/api.py @@ -0,0 +1,1236 @@ +# Copyright (c) 2011 Microsoft Corporation +# +# Module Name: +# +# api.py +# +# Abstract: +# +# Generate code for logging Z3 APIs. +# It also generate bindings for the z3_replayer interpreter. +# +# Generated files: +# - z3_api_log.h, z3_api_log.cpp: logging support for Z3 api. +# - z3_api_commands.cpp: bindings for z3_replayer. +# +# Author: +# +# Leonardo de Moura (leonardo) 2011-09-23 +import sys +import os +from sets import Set +log_h = open('api_log_macros.h', 'w') +log_c = open('api_log_macros.cpp', 'w') +exe_c = open('api_commands.cpp', 'w') +core_py = open('..\\python\\z3core.py', 'w') +dotnet_fileout = '..\\Microsoft.Z3\\Native.cs' +## +log_h.write('// Automatically generated file, generator: api.py\n') +log_h.write('#include\"z3.h\"\n') +## +log_c.write('// Automatically generated file, generator: api.py\n') +log_c.write('#include\n') +log_c.write('#include\"z3.h\"\n') +log_c.write('#include\"api_log_macros.h\"\n') +log_c.write('#include\"z3_logger.h\"\n') +## +exe_c.write('// Automatically generated file, generator: api.py\n') +exe_c.write('#include\"z3.h\"\n') +exe_c.write('#include\"z3_replayer.h\"\n') +## +log_h.write('extern std::ostream * g_z3_log;\n') +log_h.write('extern bool g_z3_log_enabled;\n') +log_h.write('class z3_log_ctx { bool m_prev; public: z3_log_ctx():m_prev(g_z3_log_enabled) { g_z3_log_enabled = false; } ~z3_log_ctx() { g_z3_log_enabled = m_prev; } bool enabled() const { return m_prev; } };\n') +log_h.write('inline void SetR(void * obj) { *g_z3_log << "= " << obj << "\\n"; }\ninline void SetO(void * obj, unsigned pos) { *g_z3_log << "* " << obj << " " << pos << "\\n"; } \ninline void SetAO(void * obj, unsigned pos, unsigned idx) { *g_z3_log << "@ " << obj << " " << pos << " " << idx << "\\n"; }\n') +log_h.write('#define RETURN_Z3(Z3RES) if (_LOG_CTX.enabled()) { SetR(Z3RES); } return Z3RES\n') +log_h.write('void _Z3_append_log(char const * msg);\n') +## +exe_c.write('void Z3_replacer_error_handler(Z3_context ctx, Z3_error_code c) { printf("[REPLAYER ERROR HANDLER]: %s\\n", Z3_get_error_msg_ex(ctx, c)); }\n') +## +core_py.write('# Automatically generated file, generator: api.py\n') +core_py.write('import sys, os\n') +core_py.write('import ctypes\n') +core_py.write('from z3types import *\n') +core_py.write('from z3consts import *\n') +core_py.write(""" +def _find_lib(): + _dir = os.path.dirname(os.path.abspath(__file__)) + libs = ['z3.dll', 'libz3.so', 'libz3.dylib'] + if sys.maxsize > 2**32: + winlibdir = 'x64' + else: + winlibdir = 'bin' + locs = [_dir, '%s%s..%s%s' % (_dir, os.sep, os.sep, winlibdir), '%s%s..%slib' % (_dir, os.sep, os.sep), '%s%s..%sexternal' % (_dir, os.sep, os.sep)] + for loc in locs: + for lib in libs: + f = '%s%s%s' % (loc, os.sep, lib) + if os.path.exists(f): + return f + return None + +_lib = None +def lib(): + if _lib == None: + l = _find_lib() + if l == None: + raise Z3Exception("init(Z3_LIBRARY_PATH) must be invoked before using Z3-python") + init(l) + assert _lib != None + return _lib + +def init(PATH): + global _lib + _lib = ctypes.CDLL(PATH) +""") + +next_id = 0 +API2Id = {} + +VOID = 0 +VOID_PTR = 1 +INT = 2 +UINT = 3 +INT64 = 4 +UINT64 = 5 +STRING = 6 +STRING_PTR = 7 +BOOL = 8 +SYMBOL = 9 +PRINT_MODE = 10 +ERROR_CODE = 11 +DOUBLE = 12 + +CONFIG = 100 +CONTEXT = 101 +AST = 102 +APP = 103 +SORT = 104 +FUNC_DECL = 105 +PATTERN = 106 +MODEL = 107 +LITERALS = 108 +CONSTRUCTOR = 109 +CONSTRUCTOR_LIST = 110 +THEORY = 111 +THEORY_DATA = 112 +SOLVER = 113 +GOAL = 114 +TACTIC = 115 +PARAMS = 117 +PROBE = 118 +STATS = 119 +AST_VECTOR = 120 +AST_MAP = 121 +APPLY_RESULT = 122 +FUNC_INTERP = 123 +FUNC_ENTRY = 124 +FIXEDPOINT = 125 +PARAM_DESCRS = 126 + +def is_obj(ty): + return ty >= 100 + +IN = 0 +OUT = 1 +INOUT = 2 +IN_ARRAY = 3 +OUT_ARRAY = 4 +INOUT_ARRAY = 5 + +Type2Str = { VOID : 'void', VOID_PTR : 'void*', INT : 'int', UINT : 'unsigned', INT64 : '__int64', UINT64 : '__uint64', DOUBLE : 'double', + STRING : 'Z3_string', STRING_PTR : 'Z3_string_ptr', BOOL : 'Z3_bool', SYMBOL : 'Z3_symbol', + PRINT_MODE : 'Z3_ast_print_mode', ERROR_CODE : 'Z3_error_code', + CONFIG : 'Z3_config', CONTEXT : 'Z3_context', AST : 'Z3_ast', APP : 'Z3_app', SORT : 'Z3_sort', + FUNC_DECL : 'Z3_func_decl', PATTERN : 'Z3_pattern', MODEL : 'Z3_model', LITERALS : 'Z3_literals', + CONSTRUCTOR : 'Z3_constructor', CONSTRUCTOR_LIST : 'Z3_constructor_list', THEORY : 'Z3_theory', + THEORY_DATA : 'Z3_theory_data', SOLVER : 'Z3_solver', GOAL : 'Z3_goal', TACTIC : 'Z3_tactic', + FIXEDPOINT : 'Z3_fixedpoint', + PARAMS : 'Z3_params', PROBE : 'Z3_probe', STATS : 'Z3_stats', AST_VECTOR : 'Z3_ast_vector', + AST_MAP : 'Z3_ast_map', APPLY_RESULT : 'Z3_apply_result', FUNC_INTERP : 'Z3_func_interp', FUNC_ENTRY : 'Z3_func_entry', + PARAM_DESCRS : 'Z3_param_descrs' + } + +Type2PyStr = { VOID_PTR : 'ctypes.c_void_p', INT : 'ctypes.c_int', UINT : 'ctypes.c_uint', INT64 : 'ctypes.c_longlong', + UINT64 : 'ctypes.c_ulonglong', DOUBLE : 'ctypes.c_double', + STRING : 'ctypes.c_char_p', STRING_PTR : 'ctypes.POINTER(ctypes.c_char_p)', BOOL : 'ctypes.c_bool', SYMBOL : 'Symbol', + PRINT_MODE : 'ctypes.c_uint', ERROR_CODE : 'ctypes.c_uint', CONFIG : 'Config', CONTEXT : 'ContextObj', + AST : 'Ast', APP : 'Ast', SORT: 'Sort', FUNC_DECL : 'FuncDecl', PATTERN : 'Pattern', + MODEL : 'Model', LITERALS : 'Literals', CONSTRUCTOR : 'Constructor', CONSTRUCTOR_LIST : 'ConstructorList', + THEORY : 'ctypes.c_void_p', THEORY_DATA : 'ctypes.c_void_p', SOLVER : 'SolverObj', GOAL : 'GoalObj', TACTIC : 'TacticObj', + FIXEDPOINT : 'FixedpointObj', + PARAMS : 'Params', PROBE : 'ProbeObj', STATS : 'StatsObj', AST_VECTOR : 'AstVectorObj', AST_MAP : 'AstMapObj', + APPLY_RESULT : 'ApplyResultObj', FUNC_INTERP : 'FuncInterpObj', FUNC_ENTRY : 'FuncEntryObj', + PARAM_DESCRS : 'ParamDescrs' + } + +# Mapping to .NET types +Type2Dotnet = { VOID : 'void', VOID_PTR : 'IntPtr', INT : 'int', UINT : 'uint', INT64 : 'Int64', UINT64 : 'UInt64', DOUBLE : 'double', + STRING : 'string', STRING_PTR : 'byte**', BOOL : 'int', SYMBOL : 'IntPtr', + PRINT_MODE : 'uint', ERROR_CODE : 'uint' } + +for k, v in Type2Str.iteritems(): + if is_obj(k): + Type2Dotnet[k] = v + +def type2str(ty): + global Type2Str + return Type2Str[ty] + +def type2pystr(ty): + global Type2PyStr + return Type2PyStr[ty] + +def type2dotnet(ty): + global Type2Dotnet + return Type2Dotnet[ty] + +def _in(ty): + return (IN, ty); + +def _in_array(sz, ty): + return (IN_ARRAY, ty, sz); + +def _out(ty): + return (OUT, ty); + +def _out_array(sz, ty): + return (OUT_ARRAY, ty, sz, sz); + +# cap contains the position of the argument that stores the capacity of the array +# sz contains the position of the output argument that stores the (real) size of the array +def _out_array2(cap, sz, ty): + return (OUT_ARRAY, ty, cap, sz) + +def _inout_array(sz, ty): + return (INOUT_ARRAY, ty, sz, sz); + +def param_kind(p): + return p[0] + +def param_type(p): + return p[1] + +def param_array_capacity_pos(p): + return p[2] + +def param_array_size_pos(p): + return p[3] + +def param2str(p): + if param_kind(p) == IN_ARRAY: + return "%s const *" % type2str(param_type(p)) + elif param_kind(p) == OUT_ARRAY or param_kind(p) == IN_ARRAY or param_kind(p) == INOUT_ARRAY: + return "%s*" % type2str(param_type(p)) + elif param_kind(p) == OUT: + return "%s*" % type2str(param_type(p)) + else: + return type2str(param_type(p)) + + +def param2dotnet(p): + k = param_kind(p) + if k == OUT: + if param_type(p) == STRING: + return "out IntPtr" + else: + return "[In, Out] ref %s" % type2dotnet(param_type(p)) + if k == IN_ARRAY: + return "[In] %s[]" % type2dotnet(param_type(p)) + if k == INOUT_ARRAY: + return "[In, Out] %s[]" % type2dotnet(param_type(p)) + if k == OUT_ARRAY: + return "[Out] %s[]" % type2dotnet(param_type(p)) + else: + return type2dotnet(param_type(p)) + +def param2pystr(p): + if param_kind(p) == IN_ARRAY or param_kind(p) == OUT_ARRAY or param_kind(p) == IN_ARRAY or param_kind(p) == INOUT_ARRAY or param_kind(p) == OUT: + return "ctypes.POINTER(%s)" % type2pystr(param_type(p)) + else: + return type2pystr(param_type(p)) + +# Save name, result, params to generate wrapper +_API2PY = [] + +def mk_py_binding(name, result, params): + global core_py + global _API2PY + _API2PY.append((name, result, params)) + if result != VOID: + core_py.write(" _lib.%s.restype = %s\n" % (name, type2pystr(result))) + core_py.write(" _lib.%s.argtypes = [" % name) + first = True + for p in params: + if first: + first = False + else: + core_py.write(", ") + core_py.write(param2pystr(p)) + core_py.write("]\n") + +def extra_API(name, result, params): + mk_py_binding(name, result, params) + reg_dotnet(name, result, params) + +def display_args(num): + for i in range(num): + if i > 0: + core_py.write(", ") + core_py.write("a%s" % i) + +def mk_py_wrappers(): + core_py.write("\n") + for sig in _API2PY: + name = sig[0] + result = sig[1] + params = sig[2] + num = len(params) + core_py.write("def %s(" % name) + display_args(num) + core_py.write("):\n") + if result != VOID: + core_py.write(" r = lib().%s(" % name) + else: + core_py.write(" lib().%s(" % name) + display_args(num) + core_py.write(")\n") + if len(params) > 0 and param_type(params[0]) == CONTEXT: + core_py.write(" err = lib().Z3_get_error_code(a0)\n") + core_py.write(" if err != Z3_OK:\n") + core_py.write(" raise Z3Exception(lib().Z3_get_error_msg_ex(a0, err))\n") + if result != VOID: + core_py.write(" return r\n") + core_py.write("\n") + + +## .NET API native interface +_dotnet_decls = [] +def reg_dotnet(name, result, params): + global _dotnet_decls + _dotnet_decls.append((name, result, params)) + +def mk_dotnet(): + global Type2Str + global dotnet_fileout + dotnet = open(dotnet_fileout, 'w') + dotnet.write('// Automatically generated file, generator: api.py\n') + dotnet.write('using System;\n') + dotnet.write('using System.Collections.Generic;\n') + dotnet.write('using System.Text;\n') + dotnet.write('using System.Runtime.InteropServices;\n\n') + dotnet.write('#pragma warning disable 1591\n\n'); + dotnet.write('namespace Microsoft.Z3\n') + dotnet.write('{\n') + for k, v in Type2Str.iteritems(): + if is_obj(k): + dotnet.write(' using %s = System.IntPtr;\n' % v) + dotnet.write('\n'); + dotnet.write(' public class Native\n') + dotnet.write(' {\n\n') + dotnet.write(' [UnmanagedFunctionPointer(CallingConvention.Cdecl)]\n') + dotnet.write(' public delegate void Z3_error_handler(Z3_context c, Z3_error_code e);\n\n') + dotnet.write(' public unsafe class LIB\n') + dotnet.write(' {\n') + dotnet.write(' #if DEBUG\n' + ' const string Z3_DLL_NAME = \"z3_dbg.dll\";\n' + ' #else\n' + ' const string Z3_DLL_NAME = \"z3.dll\";\n' + ' #endif\n\n'); + dotnet.write(' [DllImport(Z3_DLL_NAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]\n') + dotnet.write(' public extern static void Z3_set_error_handler(Z3_context a0, Z3_error_handler a1);\n\n') + for name, result, params in _dotnet_decls: + dotnet.write(' [DllImport(Z3_DLL_NAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]\n') + dotnet.write(' ') + if result == STRING: + dotnet.write('public extern static IntPtr %s(' % (name)) + else: + dotnet.write('public extern static %s %s(' % (type2dotnet(result), name)) + first = True + i = 0; + for param in params: + if first: + first = False + else: + dotnet.write(', ') + dotnet.write('%s a%d' % (param2dotnet(param), i)) + i = i + 1 + dotnet.write(');\n\n') + dotnet.write(' }\n') + + +DotnetUnwrapped = { 'Z3_del_context' } + +def mk_dotnet_wrappers(): + global Type2Str + global dotnet_fileout + dotnet = open(dotnet_fileout, 'a') + dotnet.write("\n") + dotnet.write(" public static void Z3_set_error_handler(Z3_context a0, Z3_error_handler a1) {\n") + dotnet.write(" LIB.Z3_set_error_handler(a0, a1);\n") + dotnet.write(" Z3_error_code err = (Z3_error_code)LIB.Z3_get_error_code(a0);\n") + dotnet.write(" if (err != Z3_error_code.Z3_OK)\n") + dotnet.write(" throw new Z3Exception(Marshal.PtrToStringAnsi(LIB.Z3_get_error_msg_ex(a0, (uint)err)));\n") + dotnet.write(" }\n\n") + for name, result, params in _dotnet_decls: + if result == STRING: + dotnet.write(' public static string %s(' % (name)) + else: + dotnet.write(' public static %s %s(' % (type2dotnet(result), name)) + first = True + i = 0; + for param in params: + if first: + first = False + else: + dotnet.write(', ') + dotnet.write('%s a%d' % (param2dotnet(param), i)) + i = i + 1 + dotnet.write(') {\n') + dotnet.write(' ') + if result == STRING: + dotnet.write('IntPtr r = '); + elif result != VOID: + dotnet.write('%s r = ' % type2dotnet(result)); + dotnet.write('LIB.%s(' % (name)) + first = True + i = 0 + for param in params: + if first: + first = False + else: + dotnet.write(', ') + if param_kind(param) == OUT: + if param_type(param) == STRING: + dotnet.write('out '); + else: + dotnet.write('ref ') + dotnet.write('a%d' % i) + i = i + 1 + dotnet.write(');\n'); + if name not in DotnetUnwrapped: + if len(params) > 0 and param_type(params[0]) == CONTEXT: + dotnet.write(" Z3_error_code err = (Z3_error_code)LIB.Z3_get_error_code(a0);\n") + dotnet.write(" if (err != Z3_error_code.Z3_OK)\n") + dotnet.write(" throw new Z3Exception(Marshal.PtrToStringAnsi(LIB.Z3_get_error_msg_ex(a0, (uint)err)));\n") + if result == STRING: + dotnet.write(" return Marshal.PtrToStringAnsi(r);\n") + elif result != VOID: + dotnet.write(" return r;\n") + dotnet.write(" }\n\n") + dotnet.write(" }\n\n") + dotnet.write("}\n\n") + +def mk_log_header(file, name, params): + file.write("void log_%s(" % name) + i = 0 + for p in params: + if i > 0: + file.write(", "); + file.write("%s a%s" % (param2str(p), i)) + i = i + 1 + file.write(")"); + +def log_param(p): + kind = param_kind(p) + ty = param_type(p) + return is_obj(ty) and (kind == OUT or kind == INOUT or kind == OUT_ARRAY or kind == INOUT_ARRAY) + +def log_result(result, params): + for p in params: + if log_param(p): + return True + return False + +def mk_log_macro(file, name, params): + file.write("#define LOG_%s(" % name) + i = 0 + for p in params: + if i > 0: + file.write(", ") + file.write("_ARG%s" % i) + i = i + 1 + file.write(") z3_log_ctx _LOG_CTX; ") + auxs = Set([]) + i = 0 + for p in params: + if log_param(p): + kind = param_kind(p) + if kind == OUT_ARRAY or kind == INOUT_ARRAY: + cap = param_array_capacity_pos(p) + if cap not in auxs: + auxs.add(cap) + file.write("unsigned Z3ARG%s; " % cap) + sz = param_array_size_pos(p) + if sz not in auxs: + auxs.add(sz) + file.write("unsigned * Z3ARG%s; " % sz) + file.write("%s Z3ARG%s; " % (param2str(p), i)) + i = i + 1 + file.write("if (_LOG_CTX.enabled()) { log_%s(" % name) + i = 0 + for p in params: + if (i > 0): + file.write(', ') + file.write("_ARG%s" %i) + i = i + 1 + file.write("); ") + auxs = Set([]) + i = 0 + for p in params: + if log_param(p): + kind = param_kind(p) + if kind == OUT_ARRAY or kind == INOUT_ARRAY: + cap = param_array_capacity_pos(p) + if cap not in auxs: + auxs.add(cap) + file.write("Z3ARG%s = _ARG%s; " % (cap, cap)) + sz = param_array_size_pos(p) + if sz not in auxs: + auxs.add(sz) + file.write("Z3ARG%s = _ARG%s; " % (sz, sz)) + file.write("Z3ARG%s = _ARG%s; " % (i, i)) + i = i + 1 + file.write("}\n") + +def mk_log_result_macro(file, name, result, params): + file.write("#define RETURN_%s" % name) + if is_obj(result): + file.write("(Z3RES)") + file.write(" ") + file.write("if (_LOG_CTX.enabled()) { ") + if is_obj(result): + file.write("SetR(Z3RES); ") + i = 0 + for p in params: + if log_param(p): + kind = param_kind(p) + if kind == OUT_ARRAY or kind == INOUT_ARRAY: + cap = param_array_capacity_pos(p) + sz = param_array_size_pos(p) + if cap == sz: + file.write("for (unsigned i = 0; i < Z3ARG%s; i++) { SetAO(Z3ARG%s[i], %s, i); } " % (sz, i, i)) + else: + file.write("for (unsigned i = 0; Z3ARG%s && i < *Z3ARG%s; i++) { SetAO(Z3ARG%s[i], %s, i); } " % (sz, sz, i, i)) + if kind == OUT or kind == INOUT: + file.write("SetO((Z3ARG%s == 0 ? 0 : *Z3ARG%s), %s); " % (i, i, i)) + i = i + 1 + file.write("} ") + if is_obj(result): + file.write("return Z3RES\n") + else: + file.write("return\n") + +def mk_exec_header(file, name): + file.write("void exec_%s(z3_replayer & in)" % name) + +def error(msg): + sys.stderr.write(msg) + exit(-1) + +def API(name, result, params): + global API2Id, next_id + global log_h, log_c + # print "generating ", name + mk_py_binding(name, result, params) + reg_dotnet(name, result, params) + API2Id[next_id] = name + mk_log_header(log_h, name, params) + log_h.write(';\n') + mk_log_header(log_c, name, params) + log_c.write(' {\n R();\n') + mk_exec_header(exe_c, name) + exe_c.write(' {\n') + # Create Log function & Function call + i = 0 + exe_c.write(" ") + if is_obj(result): + exe_c.write("%s result = " % type2str(result)) + exe_c.write("%s(\n " % name) + for p in params: + kind = param_kind(p) + ty = param_type(p) + if (i > 0): + exe_c.write(",\n ") + if kind == IN: + if is_obj(ty): + log_c.write(" P(a%s);\n" % i) + exe_c.write("reinterpret_cast<%s>(in.get_obj(%s))" % (param2str(p), i)) + elif ty == STRING: + log_c.write(" S(a%s);\n" % i) + exe_c.write("in.get_str(%s)" % i) + elif ty == SYMBOL: + log_c.write(" Sy(a%s);\n" % i) + exe_c.write("in.get_symbol(%s)" % i) + elif ty == UINT: + log_c.write(" U(a%s);\n" % i) + exe_c.write("in.get_uint(%s)" % i) + elif ty == UINT64: + log_c.write(" U(a%s);\n" % i) + exe_c.write("in.get_uint64(%s)" % i) + elif ty == INT: + log_c.write(" I(a%s);\n" % i) + exe_c.write("in.get_int(%s)" % i) + elif ty == INT64: + log_c.write(" I(a%s);\n" % i) + exe_c.write("in.get_int64(%s)" % i) + elif ty == DOUBLE: + log_c.write(" D(a%s);\n" % i) + exe_c.write("in.get_double(%s)" % i) + elif ty == BOOL: + log_c.write(" I(a%s);\n" % i) + exe_c.write("in.get_bool(%s)" % i) + elif ty == PRINT_MODE or ty == ERROR_CODE: + log_c.write(" U(static_cast(a%s));\n" % i); + exe_c.write("static_cast<%s>(in.get_uint(%s))" % (type2str(ty), i)) + else: + error("unsupported parameter for %s, %s" % (name, p)) + elif kind == INOUT: + error("unsupported parameter for %s, %s" % (name, p)) + elif kind == OUT: + if is_obj(ty): + log_c.write(" P(0);\n") + exe_c.write("reinterpret_cast<%s>(in.get_obj_addr(%s))" % (param2str(p), i)) + elif ty == STRING: + log_c.write(" S(\"\");\n") + exe_c.write("in.get_str_addr(%s)" % i) + elif ty == UINT: + log_c.write(" U(0);\n") + exe_c.write("in.get_uint_addr(%s)" % i) + elif ty == UINT64: + log_c.write(" U(0);\n") + exe_c.write("in.get_uint64_addr(%s)" % i) + elif ty == INT: + log_c.write(" I(0);\n") + exe_c.write("in.get_int_addr(%s)" % i) + elif ty == INT64: + log_c.write(" I(0);\n") + exe_c.write("in.get_int64_addr(%s)" % i) + else: + error("unsupported parameter for %s, %s" % (name, p)) + elif kind == IN_ARRAY or kind == INOUT_ARRAY: + sz = param_array_capacity_pos(p) + log_c.write(" for (unsigned i = 0; i < a%s; i++) { " % sz) + if is_obj(ty): + log_c.write("P(a%s[i]);" % i) + log_c.write(" }\n") + log_c.write(" Ap(a%s);\n" % sz) + exe_c.write("reinterpret_cast<%s*>(in.get_obj_array(%s))" % (type2str(ty), i)) + elif ty == SYMBOL: + log_c.write("Sy(a%s[i]);" % i) + log_c.write(" }\n") + log_c.write(" Asy(a%s);\n" % sz) + exe_c.write("in.get_symbol_array(%s)" % i) + elif ty == UINT: + log_c.write("U(a%s[i]);" % i) + log_c.write(" }\n") + log_c.write(" Au(a%s);\n" % sz) + exe_c.write("in.get_uint_array(%s)" % i) + else: + error ("unsupported parameter for %s, %s" % (name, p)) + elif kind == OUT_ARRAY: + sz = param_array_capacity_pos(p) + log_c.write(" for (unsigned i = 0; i < a%s; i++) { " % sz) + if is_obj(ty): + log_c.write("P(0);") + log_c.write(" }\n") + log_c.write(" Ap(a%s);\n" % sz) + exe_c.write("reinterpret_cast<%s*>(in.get_obj_array(%s))" % (type2str(ty), i)) + elif ty == UINT: + log_c.write("U(0);") + log_c.write(" }\n") + log_c.write(" Au(a%s);\n" % sz) + exe_c.write("in.get_uint_array(%s)" % i) + else: + error ("unsupported parameter for %s, %s" % (name, p)) + else: + error ("unsupported parameter for %s, %s" % (name, p)) + i = i + 1 + log_c.write(" C(%s);\n" % next_id) + exe_c.write(");\n") + if is_obj(result): + exe_c.write(" in.store_result(result);\n") + if name == 'Z3_mk_context' or name == 'Z3_mk_context_rc': + exe_c.write(" Z3_set_error_handler(result, Z3_replacer_error_handler);") + log_c.write('}\n') + exe_c.write('}\n') + mk_log_macro(log_h, name, params) + if log_result(result, params): + mk_log_result_macro(log_h, name, result, params) + next_id = next_id + 1 + +def mk_bindings(): + exe_c.write("void register_z3_replayer_cmds(z3_replayer & in) {\n") + for key, val in API2Id.items(): + exe_c.write(" in.register_cmd(%s, exec_%s);\n" % (key, val)) + exe_c.write("}\n") + +def mk_defs(): + z3_def = open('..\dll\z3.def', 'w') + z3_dbg_def = open('..\dll\z3_dbg.def', 'w') + z3_def.write('LIBRARY "Z3"\n') + z3_def.write('EXPORTS\n') + z3_dbg_def.write('LIBRARY "Z3_DBG"\n') + z3_dbg_def.write('EXPORTS\n') + i = 1 + for key, val in API2Id.items(): + z3_def.write("\t%s @%s\n" % (val, i)) + z3_dbg_def.write("\t%s @%s\n" % (val, i)) + i = i + 1 + +API('Z3_mk_config', CONFIG, ()) +API('Z3_del_config', VOID, (_in(CONFIG),)) +API('Z3_set_param_value', VOID, (_in(CONFIG), _in(STRING), _in(STRING))) +API('Z3_mk_context', CONTEXT, (_in(CONFIG),)) +API('Z3_mk_context_rc', CONTEXT, (_in(CONFIG),)) +API('Z3_set_logic', VOID, (_in(CONTEXT), _in(STRING))) +API('Z3_del_context', VOID, (_in(CONTEXT),)) +API('Z3_inc_ref', VOID, (_in(CONTEXT), _in(AST))) +API('Z3_dec_ref', VOID, (_in(CONTEXT), _in(AST))) +API('Z3_toggle_warning_messages', VOID, (_in(BOOL),)) +API('Z3_update_param_value', VOID, (_in(CONTEXT), _in(STRING), _in(STRING))) +API('Z3_get_param_value', BOOL, (_in(CONTEXT), _in(STRING), _out(STRING))) +API('Z3_mk_int_symbol', SYMBOL, (_in(CONTEXT), _in(INT))) +API('Z3_mk_string_symbol', SYMBOL, (_in(CONTEXT), _in(STRING))) +API('Z3_is_eq_sort', BOOL, (_in(CONTEXT), _in(SORT), _in(SORT))) +API('Z3_mk_uninterpreted_sort', SORT, (_in(CONTEXT), _in(SYMBOL))) +API('Z3_mk_bool_sort', SORT, (_in(CONTEXT), )) +API('Z3_mk_int_sort', SORT, (_in(CONTEXT), )) +API('Z3_mk_real_sort', SORT, (_in(CONTEXT), )) +API('Z3_mk_bv_sort', SORT, (_in(CONTEXT), _in(UINT))) +API('Z3_mk_array_sort', SORT, (_in(CONTEXT), _in(SORT), _in(SORT))) +API('Z3_mk_tuple_sort', SORT, (_in(CONTEXT), _in(SYMBOL), _in(UINT), + _in_array(2, SYMBOL), + _in_array(2, SORT), + _out(FUNC_DECL), + _out_array(2, FUNC_DECL))) + +API('Z3_mk_enumeration_sort', SORT, (_in(CONTEXT), _in(SYMBOL), _in(UINT), + _in_array(2, SYMBOL), + _out_array(2, FUNC_DECL), + _out_array(2, FUNC_DECL))) + +API('Z3_mk_list_sort', SORT, (_in(CONTEXT), _in(SYMBOL), _in(SORT), _out(FUNC_DECL), _out(FUNC_DECL), _out(FUNC_DECL), _out(FUNC_DECL), _out(FUNC_DECL), _out(FUNC_DECL))) + +API('Z3_mk_constructor', CONSTRUCTOR, (_in(CONTEXT), _in(SYMBOL), _in(SYMBOL), _in(UINT), _in_array(3, SYMBOL), _in_array(3, SORT), _in_array(3, UINT))) +API('Z3_query_constructor', VOID, (_in(CONTEXT), _in(CONSTRUCTOR), _in(UINT), _out(FUNC_DECL), _out(FUNC_DECL), _out_array(2, FUNC_DECL))) +API('Z3_del_constructor', VOID, (_in(CONTEXT), _in(CONSTRUCTOR))) +API('Z3_mk_datatype', SORT, (_in(CONTEXT), _in(SYMBOL), _in(UINT), _inout_array(2, CONSTRUCTOR))) +API('Z3_mk_constructor_list', CONSTRUCTOR_LIST, (_in(CONTEXT), _in(UINT), _in_array(1, CONSTRUCTOR))) +API('Z3_del_constructor_list', VOID, (_in(CONTEXT), _in(CONSTRUCTOR_LIST))) +API('Z3_mk_datatypes', VOID, (_in(CONTEXT), _in(UINT), _in_array(1, SYMBOL), _out_array(1, SORT), _inout_array(1, CONSTRUCTOR_LIST))) + +API('Z3_is_eq_ast', BOOL, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_is_eq_func_decl', BOOL, (_in(CONTEXT), _in(FUNC_DECL), _in(FUNC_DECL))) +API('Z3_mk_func_decl', FUNC_DECL, (_in(CONTEXT), _in(SYMBOL), _in(UINT), _in_array(2, SORT), _in(SORT))) +API('Z3_mk_app', AST, (_in(CONTEXT), _in(FUNC_DECL), _in(UINT), _in_array(2, AST))) +API('Z3_mk_const', AST, (_in(CONTEXT), _in(SYMBOL), _in(SORT))) +API('Z3_mk_label', AST, (_in(CONTEXT), _in(SYMBOL), _in(BOOL), _in(AST))) +API('Z3_mk_fresh_func_decl', FUNC_DECL, (_in(CONTEXT), _in(STRING), _in(UINT), _in_array(2, SORT), _in(SORT))) +API('Z3_mk_fresh_const', AST, (_in(CONTEXT), _in(STRING), _in(SORT))) +API('Z3_mk_true', AST, (_in(CONTEXT), )) +API('Z3_mk_false', AST, (_in(CONTEXT), )) +API('Z3_mk_eq', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_distinct', AST, (_in(CONTEXT), _in(UINT), _in_array(1, AST))) +API('Z3_mk_not', AST, (_in(CONTEXT), _in(AST))) +API('Z3_mk_ite', AST, (_in(CONTEXT), _in(AST), _in(AST), _in(AST))) +API('Z3_mk_iff', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_implies', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_xor', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_and', AST, (_in(CONTEXT), _in(UINT), _in_array(1, AST))) +API('Z3_mk_or', AST, (_in(CONTEXT), _in(UINT), _in_array(1, AST))) +API('Z3_mk_add', AST, (_in(CONTEXT), _in(UINT), _in_array(1, AST))) +API('Z3_mk_mul', AST, (_in(CONTEXT), _in(UINT), _in_array(1, AST))) +API('Z3_mk_sub', AST, (_in(CONTEXT), _in(UINT), _in_array(1, AST))) +API('Z3_mk_unary_minus', AST, (_in(CONTEXT), _in(AST))) +API('Z3_mk_div', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_mod', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_rem', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_power', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_is_algebraic_number', BOOL, (_in(CONTEXT), _in(AST))) +API('Z3_get_algebraic_number_lower', AST, (_in(CONTEXT), _in(AST), _in(UINT))) +API('Z3_get_algebraic_number_upper', AST, (_in(CONTEXT), _in(AST), _in(UINT))) +API('Z3_mk_lt', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_le', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_gt', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_ge', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_int2real', AST, (_in(CONTEXT), _in(AST))) +API('Z3_mk_real2int', AST, (_in(CONTEXT), _in(AST))) +API('Z3_mk_is_int', AST, (_in(CONTEXT), _in(AST))) + +API('Z3_mk_bvnot', AST, (_in(CONTEXT), _in(AST))) +API('Z3_mk_bvredand', AST, (_in(CONTEXT), _in(AST))) +API('Z3_mk_bvredor', AST, (_in(CONTEXT), _in(AST))) +API('Z3_mk_bvand', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvor', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvxor', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvnand', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvnor', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvxnor', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvneg', AST, (_in(CONTEXT), _in(AST))) +API('Z3_mk_bvadd', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvsub', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvmul', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvudiv', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvsdiv', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvurem', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvsrem', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvsmod', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvult', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvslt', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvule', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvsle', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvuge', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvsge', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvugt', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvsgt', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_concat', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_extract', AST, (_in(CONTEXT), _in(UINT), _in(UINT), _in(AST))) +API('Z3_mk_sign_ext', AST, (_in(CONTEXT), _in(UINT), _in(AST))) +API('Z3_mk_zero_ext', AST, (_in(CONTEXT), _in(UINT), _in(AST))) +API('Z3_mk_repeat', AST, (_in(CONTEXT), _in(UINT), _in(AST))) +API('Z3_mk_bvshl', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvlshr', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvashr', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_rotate_left', AST, (_in(CONTEXT), _in(UINT), _in(AST))) +API('Z3_mk_rotate_right', AST, (_in(CONTEXT), _in(UINT), _in(AST))) +API('Z3_mk_ext_rotate_left', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_ext_rotate_right', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_int2bv', AST, (_in(CONTEXT), _in(UINT), _in(AST))) +API('Z3_mk_bv2int', AST, (_in(CONTEXT), _in(AST), _in(BOOL))) +API('Z3_mk_bvadd_no_overflow', AST, (_in(CONTEXT), _in(AST), _in(AST), _in(BOOL))) +API('Z3_mk_bvadd_no_underflow', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvsub_no_overflow', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvsub_no_underflow', AST, (_in(CONTEXT), _in(AST), _in(AST), _in(BOOL))) +API('Z3_mk_bvsdiv_no_overflow', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_bvneg_no_overflow', AST, (_in(CONTEXT), _in(AST))) +API('Z3_mk_bvmul_no_overflow', AST, (_in(CONTEXT), _in(AST), _in(AST), _in(BOOL))) +API('Z3_mk_bvmul_no_underflow', AST, (_in(CONTEXT), _in(AST), _in(AST))) + +API('Z3_mk_select', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_store', AST, (_in(CONTEXT), _in(AST), _in(AST), _in(AST))) +API('Z3_mk_const_array', AST, (_in(CONTEXT), _in(SORT), _in(AST))) +API('Z3_mk_map', AST, (_in(CONTEXT), _in(FUNC_DECL), _in(UINT), _in_array(2, AST))) +API('Z3_mk_array_default', AST, (_in(CONTEXT), _in(AST))) +API('Z3_mk_set_sort', SORT, (_in(CONTEXT), _in(SORT))) +API('Z3_mk_empty_set', AST, (_in(CONTEXT), _in(SORT))) +API('Z3_mk_full_set', AST, (_in(CONTEXT), _in(SORT))) +API('Z3_mk_set_add', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_set_del', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_set_union', AST, (_in(CONTEXT), _in(UINT), _in_array(1, AST))) +API('Z3_mk_set_intersect', AST, (_in(CONTEXT), _in(UINT), _in_array(1, AST))) +API('Z3_mk_set_difference', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_set_complement', AST, (_in(CONTEXT), _in(AST))) +API('Z3_mk_set_member', AST, (_in(CONTEXT), _in(AST), _in(AST))) +API('Z3_mk_set_subset', AST, (_in(CONTEXT), _in(AST), _in(AST))) + +API('Z3_mk_numeral', AST, (_in(CONTEXT), _in(STRING), _in(SORT))) +API('Z3_mk_real', AST, (_in(CONTEXT), _in(INT), _in(INT))) +API('Z3_mk_int', AST, (_in(CONTEXT), _in(INT), _in(SORT))) +API('Z3_mk_unsigned_int', AST, (_in(CONTEXT), _in(UINT), _in(SORT))) +API('Z3_mk_int64', AST, (_in(CONTEXT), _in(INT64), _in(SORT))) +API('Z3_mk_unsigned_int64', AST, (_in(CONTEXT), _in(UINT64), _in(SORT))) + +API('Z3_mk_pattern', PATTERN, (_in(CONTEXT), _in(UINT), _in_array(1, AST))) +API('Z3_mk_bound', AST, (_in(CONTEXT), _in(UINT), _in(SORT))) +API('Z3_mk_forall', AST, (_in(CONTEXT), _in(UINT), + _in(UINT), _in_array(2, PATTERN), + _in(UINT), _in_array(4, SORT), _in_array(4, SYMBOL), + _in(AST))) +API('Z3_mk_exists', AST, (_in(CONTEXT), _in(UINT), + _in(UINT), _in_array(2, PATTERN), + _in(UINT), _in_array(4, SORT), _in_array(4, SYMBOL), + _in(AST))) +API('Z3_mk_quantifier', AST, (_in(CONTEXT), _in(BOOL), _in(UINT), + _in(UINT), _in_array(3, PATTERN), + _in(UINT), _in_array(5, SORT), _in_array(5, SYMBOL), + _in(AST))) +API('Z3_mk_quantifier_ex', AST, (_in(CONTEXT), _in(BOOL), _in(UINT), _in(SYMBOL), _in(SYMBOL), + _in(UINT), _in_array(5, PATTERN), + _in(UINT), _in_array(7, AST), + _in(UINT), _in_array(9, SORT), _in_array(9, SYMBOL), + _in(AST))) +API('Z3_mk_forall_const', AST, (_in(CONTEXT), _in(UINT), _in(UINT), _in_array(2, APP), + _in(UINT), _in_array(4, PATTERN), + _in(AST))) +API('Z3_mk_exists_const', AST, (_in(CONTEXT), _in(UINT), _in(UINT), _in_array(2, APP), + _in(UINT), _in_array(4, PATTERN), + _in(AST))) +API('Z3_mk_quantifier_const', AST, (_in(CONTEXT), _in(BOOL), _in(UINT), _in(UINT), _in_array(3, APP), + _in(UINT), _in_array(5, PATTERN), + _in(AST))) +API('Z3_mk_quantifier_const_ex', AST, (_in(CONTEXT), _in(BOOL), _in(UINT), _in(SYMBOL), _in(SYMBOL), + _in(UINT), _in_array(5, APP), + _in(UINT), _in_array(7, PATTERN), + _in(UINT), _in_array(9, AST), + _in(AST))) + + +API('Z3_get_ast_id', UINT, (_in(CONTEXT), _in(AST))) +API('Z3_get_func_decl_id', UINT, (_in(CONTEXT), _in(FUNC_DECL))) +API('Z3_get_sort_id', UINT, (_in(CONTEXT), _in(SORT))) +API('Z3_is_well_sorted', BOOL, (_in(CONTEXT), _in(AST))) +API('Z3_get_symbol_kind', UINT, (_in(CONTEXT), _in(SYMBOL))) +API('Z3_get_symbol_int', INT, (_in(CONTEXT), _in(SYMBOL))) +API('Z3_get_symbol_string', STRING, (_in(CONTEXT), _in(SYMBOL))) +API('Z3_get_ast_kind', UINT, (_in(CONTEXT), _in(AST))) +API('Z3_get_ast_hash', UINT, (_in(CONTEXT), _in(AST))) +API('Z3_get_numeral_string', STRING, (_in(CONTEXT), _in(AST))) +API('Z3_get_numeral_decimal_string', STRING, (_in(CONTEXT), _in(AST), _in(UINT))) +API('Z3_get_numerator', AST, (_in(CONTEXT), _in(AST))) +API('Z3_get_denominator', AST, (_in(CONTEXT), _in(AST))) +API('Z3_get_numeral_small', BOOL, (_in(CONTEXT), _in(AST), _out(INT64), _out(INT64))) +API('Z3_get_numeral_int', BOOL, (_in(CONTEXT), _in(AST), _out(INT))) +API('Z3_get_numeral_uint', BOOL, (_in(CONTEXT), _in(AST), _out(UINT))) +API('Z3_get_numeral_uint64', BOOL, (_in(CONTEXT), _in(AST), _out(UINT64))) +API('Z3_get_numeral_int64', BOOL, (_in(CONTEXT), _in(AST), _out(INT64))) +API('Z3_get_numeral_rational_int64', BOOL, (_in(CONTEXT), _in(AST), _out(INT64), _out(INT64))) +API('Z3_get_bool_value', UINT, (_in(CONTEXT), _in(AST))) +API('Z3_get_app_decl', FUNC_DECL, (_in(CONTEXT), _in(APP))) +API('Z3_get_app_num_args', UINT, (_in(CONTEXT), _in(APP))) +API('Z3_get_app_arg', AST, (_in(CONTEXT), _in(APP), _in(UINT))) +API('Z3_get_index_value', UINT, (_in(CONTEXT), _in(AST))) +API('Z3_is_quantifier_forall', BOOL, (_in(CONTEXT), _in(AST))) +API('Z3_get_quantifier_weight', UINT, (_in(CONTEXT), _in(AST))) +API('Z3_get_quantifier_num_patterns', UINT, (_in(CONTEXT), _in(AST))) +API('Z3_get_quantifier_pattern_ast', PATTERN, (_in(CONTEXT), _in(AST), _in(UINT))) +API('Z3_get_quantifier_num_no_patterns', UINT, (_in(CONTEXT), _in(AST))) +API('Z3_get_quantifier_no_pattern_ast', AST, (_in(CONTEXT), _in(AST), _in(UINT))) +API('Z3_get_quantifier_bound_name', SYMBOL, (_in(CONTEXT), _in(AST), _in(UINT))) +API('Z3_get_quantifier_bound_sort', SORT, (_in(CONTEXT), _in(AST), _in(UINT))) +API('Z3_get_quantifier_body', AST, (_in(CONTEXT), _in(AST))) +API('Z3_get_quantifier_num_bound', UINT, (_in(CONTEXT), _in(AST))) +API('Z3_get_decl_name', SYMBOL, (_in(CONTEXT), _in(FUNC_DECL))) +API('Z3_get_decl_num_parameters', UINT, (_in(CONTEXT), _in(FUNC_DECL))) +API('Z3_get_decl_parameter_kind', UINT, (_in(CONTEXT), _in(FUNC_DECL), _in(UINT))) +API('Z3_get_decl_int_parameter', INT, (_in(CONTEXT), _in(FUNC_DECL), _in(UINT))) +API('Z3_get_decl_double_parameter', DOUBLE, (_in(CONTEXT), _in(FUNC_DECL), _in(UINT))) +API('Z3_get_decl_symbol_parameter', SYMBOL, (_in(CONTEXT), _in(FUNC_DECL), _in(UINT))) +API('Z3_get_decl_sort_parameter', SORT, (_in(CONTEXT), _in(FUNC_DECL), _in(UINT))) +API('Z3_get_decl_ast_parameter', AST, (_in(CONTEXT), _in(FUNC_DECL), _in(UINT))) +API('Z3_get_decl_func_decl_parameter', FUNC_DECL, (_in(CONTEXT), _in(FUNC_DECL), _in(UINT))) +API('Z3_get_decl_rational_parameter', STRING, (_in(CONTEXT), _in(FUNC_DECL), _in(UINT))) +API('Z3_get_sort_name', SYMBOL, (_in(CONTEXT), _in(SORT))) +API('Z3_get_sort', SORT, (_in(CONTEXT), _in(AST))) +API('Z3_get_domain_size', UINT, (_in(CONTEXT), _in(FUNC_DECL))) +API('Z3_get_domain', SORT, (_in(CONTEXT), _in(FUNC_DECL), _in(UINT))) +API('Z3_get_range', SORT, (_in(CONTEXT), _in(FUNC_DECL))) +API('Z3_get_sort_kind', UINT, (_in(CONTEXT), _in(SORT))) +API('Z3_get_bv_sort_size', UINT, (_in(CONTEXT), _in(SORT))) +API('Z3_get_array_sort_domain', SORT, (_in(CONTEXT), _in(SORT))) +API('Z3_get_array_sort_range', SORT, (_in(CONTEXT), _in(SORT))) +API('Z3_get_tuple_sort_mk_decl', FUNC_DECL, (_in(CONTEXT), _in(SORT))) +API('Z3_get_decl_kind', UINT, (_in(CONTEXT), _in(FUNC_DECL))) +API('Z3_get_tuple_sort_num_fields', UINT, (_in(CONTEXT), _in(SORT))) +API('Z3_get_tuple_sort_field_decl', FUNC_DECL, (_in(CONTEXT), _in(SORT), _in(UINT))) +API('Z3_get_datatype_sort_num_constructors', UINT, (_in(CONTEXT), _in(SORT))) +API('Z3_get_datatype_sort_constructor', FUNC_DECL, (_in(CONTEXT), _in(SORT), _in(UINT))) +API('Z3_get_datatype_sort_recognizer', FUNC_DECL, (_in(CONTEXT), _in(SORT), _in(UINT))) +API('Z3_get_datatype_sort_constructor_accessor', FUNC_DECL, (_in(CONTEXT), _in(SORT), _in(UINT), _in(UINT))) +API('Z3_get_relation_arity', UINT, (_in(CONTEXT), _in(SORT))) +API('Z3_get_relation_column', SORT, (_in(CONTEXT), _in(SORT), _in(UINT))) +API('Z3_get_finite_domain_sort_size', BOOL, (_in(CONTEXT), _in(SORT), _out(UINT64))) +API('Z3_mk_finite_domain_sort', SORT, (_in(CONTEXT), _in(SYMBOL), _in(UINT64))) +API('Z3_get_pattern_num_terms', UINT, (_in(CONTEXT), _in(PATTERN))) +API('Z3_get_pattern', AST, (_in(CONTEXT), _in(PATTERN), _in(UINT))) + +API('Z3_simplify', AST, (_in(CONTEXT), _in(AST))) +API('Z3_simplify_ex', AST, (_in(CONTEXT), _in(AST), _in(PARAMS))) +API('Z3_simplify_get_help', STRING, (_in(CONTEXT),)) +API('Z3_simplify_get_param_descrs', PARAM_DESCRS, (_in(CONTEXT),)) + +API('Z3_update_term', AST, (_in(CONTEXT), _in(AST), _in(UINT), _in_array(2, AST))) +API('Z3_substitute', AST, (_in(CONTEXT), _in(AST), _in(UINT), _in_array(2, AST), _in_array(2, AST))) +API('Z3_substitute_vars', AST, (_in(CONTEXT), _in(AST), _in(UINT), _in_array(2, AST))) + +API('Z3_sort_to_ast', AST, (_in(CONTEXT), _in(SORT))) +API('Z3_func_decl_to_ast', AST, (_in(CONTEXT), _in(FUNC_DECL))) +API('Z3_pattern_to_ast', AST, (_in(CONTEXT), _in(PATTERN))) +API('Z3_app_to_ast', AST, (_in(CONTEXT), _in(APP))) +API('Z3_to_app', APP, (_in(CONTEXT), _in(AST))) +API('Z3_to_func_decl', FUNC_DECL, (_in(CONTEXT), _in(AST))) + +API('Z3_push', VOID, (_in(CONTEXT),)) +API('Z3_pop', VOID, (_in(CONTEXT), _in(UINT))) +API('Z3_get_num_scopes', UINT, (_in(CONTEXT),)) +API('Z3_persist_ast', VOID, (_in(CONTEXT), _in(AST), _in(UINT))) +API('Z3_assert_cnstr', VOID, (_in(CONTEXT), _in(AST))) +API('Z3_check_and_get_model', INT, (_in(CONTEXT), _out(MODEL))) +API('Z3_check', INT, (_in(CONTEXT),)) +API('Z3_check_assumptions', INT, (_in(CONTEXT), _in(UINT), _in_array(1, AST), _out(MODEL), _out(AST), _out(UINT), _out_array2(1, 5, AST))) +API('Z3_get_implied_equalities', UINT, (_in(CONTEXT), _in(UINT), _in_array(1, AST), _out_array(1, UINT))) +API('Z3_del_model', VOID, (_in(CONTEXT), _in(MODEL))) + +API('Z3_soft_check_cancel', VOID, (_in(CONTEXT), )) +API('Z3_get_search_failure', UINT, (_in(CONTEXT), )) + +API('Z3_get_relevant_labels', LITERALS, (_in(CONTEXT), )) +API('Z3_get_relevant_literals', LITERALS, (_in(CONTEXT), )) +API('Z3_get_guessed_literals', LITERALS, (_in(CONTEXT), )) +API('Z3_del_literals', VOID, (_in(CONTEXT), _in(LITERALS))) +API('Z3_get_num_literals', UINT, (_in(CONTEXT), _in(LITERALS))) +API('Z3_get_label_symbol', SYMBOL, (_in(CONTEXT), _in(LITERALS), _in(UINT))) +API('Z3_get_literal', AST, (_in(CONTEXT), _in(LITERALS), _in(UINT))) +API('Z3_disable_literal', VOID, (_in(CONTEXT), _in(LITERALS), _in(UINT))) +API('Z3_block_literals', VOID, (_in(CONTEXT), _in(LITERALS))) + +API('Z3_model_inc_ref', VOID, (_in(CONTEXT), _in(MODEL))) +API('Z3_model_dec_ref', VOID, (_in(CONTEXT), _in(MODEL))) +API('Z3_model_get_const_interp', AST, (_in(CONTEXT), _in(MODEL), _in(FUNC_DECL))) +API('Z3_model_get_func_interp', FUNC_INTERP, (_in(CONTEXT), _in(MODEL), _in(FUNC_DECL))) +API('Z3_model_get_num_consts', UINT, (_in(CONTEXT), _in(MODEL))) +API('Z3_model_get_const_decl', FUNC_DECL, (_in(CONTEXT), _in(MODEL), _in(UINT))) +API('Z3_model_get_num_funcs', UINT, (_in(CONTEXT), _in(MODEL))) +API('Z3_model_get_func_decl', FUNC_DECL, (_in(CONTEXT), _in(MODEL), _in(UINT))) +API('Z3_model_eval', BOOL, (_in(CONTEXT), _in(MODEL), _in(AST), _in(BOOL), _out(AST))) +API('Z3_model_get_num_sorts', UINT, (_in(CONTEXT), _in(MODEL))) +API('Z3_model_get_sort', SORT, (_in(CONTEXT), _in(MODEL), _in(UINT))) +API('Z3_model_get_sort_universe', AST_VECTOR, (_in(CONTEXT), _in(MODEL), _in(SORT))) +API('Z3_is_as_array', BOOL, (_in(CONTEXT), _in(AST))) +API('Z3_get_as_array_func_decl', FUNC_DECL, (_in(CONTEXT), _in(AST))) +API('Z3_func_interp_inc_ref', VOID, (_in(CONTEXT), _in(FUNC_INTERP))) +API('Z3_func_interp_dec_ref', VOID, (_in(CONTEXT), _in(FUNC_INTERP))) +API('Z3_func_interp_get_num_entries', UINT, (_in(CONTEXT), _in(FUNC_INTERP))) +API('Z3_func_interp_get_entry', FUNC_ENTRY, (_in(CONTEXT), _in(FUNC_INTERP), _in(UINT))) +API('Z3_func_interp_get_else', AST, (_in(CONTEXT), _in(FUNC_INTERP))) +API('Z3_func_interp_get_arity', UINT, (_in(CONTEXT), _in(FUNC_INTERP))) +API('Z3_func_entry_inc_ref', VOID, (_in(CONTEXT), _in(FUNC_ENTRY))) +API('Z3_func_entry_dec_ref', VOID, (_in(CONTEXT), _in(FUNC_ENTRY))) +API('Z3_func_entry_get_value', AST, (_in(CONTEXT), _in(FUNC_ENTRY))) +API('Z3_func_entry_get_num_args', UINT, (_in(CONTEXT), _in(FUNC_ENTRY))) +API('Z3_func_entry_get_arg', AST, (_in(CONTEXT), _in(FUNC_ENTRY), _in(UINT))) + +API('Z3_get_model_num_constants', UINT, (_in(CONTEXT), _in(MODEL))) +API('Z3_get_model_constant', FUNC_DECL, (_in(CONTEXT), _in(MODEL), _in(UINT))) +API('Z3_eval_func_decl', BOOL, (_in(CONTEXT), _in(MODEL), _in(FUNC_DECL), _out(AST))) +API('Z3_is_array_value', BOOL, (_in(CONTEXT), _in(MODEL), _in(AST), _out(UINT))) +API('Z3_get_array_value', VOID, (_in(CONTEXT), _in(MODEL), _in(AST), + _in(UINT), _out_array(3, AST), _out_array(3, AST), + _out (AST))) +API('Z3_get_model_num_funcs', UINT, (_in(CONTEXT), _in(MODEL))) +API('Z3_get_model_func_decl', FUNC_DECL, (_in(CONTEXT), _in(MODEL), _in(UINT))) +API('Z3_get_model_func_else', AST, (_in(CONTEXT), _in(MODEL), _in(UINT))) +API('Z3_get_model_func_num_entries', UINT, (_in(CONTEXT), _in(MODEL), _in(UINT))) +API('Z3_get_model_func_entry_num_args', UINT, (_in(CONTEXT), _in(MODEL), _in(UINT), _in(UINT))) +API('Z3_get_model_func_entry_arg', AST, (_in(CONTEXT), _in(MODEL), _in(UINT), _in(UINT), _in(UINT))) +API('Z3_get_model_func_entry_value', AST, (_in(CONTEXT), _in(MODEL), _in(UINT), _in(UINT))) +API('Z3_eval', BOOL, (_in(CONTEXT), _in(MODEL), _in(AST), _out(AST))) +API('Z3_eval_decl', BOOL, (_in(CONTEXT), _in(MODEL), _in(FUNC_DECL), _in(UINT), _in_array(3, AST), _out(AST))) + +API('Z3_set_ast_print_mode', VOID, (_in(CONTEXT), _in(PRINT_MODE))) +API('Z3_ast_to_string', STRING, (_in(CONTEXT), _in(AST))) +API('Z3_pattern_to_string', STRING, (_in(CONTEXT), _in(PATTERN))) +API('Z3_sort_to_string', STRING, (_in(CONTEXT), _in(SORT))) +API('Z3_func_decl_to_string', STRING, (_in(CONTEXT), _in(FUNC_DECL))) +API('Z3_model_to_string', STRING, (_in(CONTEXT), _in(MODEL))) +API('Z3_benchmark_to_smtlib_string', STRING, (_in(CONTEXT), _in(STRING), _in(STRING), _in(STRING), _in(STRING), _in(UINT), _in_array(5, AST), _in(AST))) +API('Z3_context_to_string', STRING, (_in(CONTEXT),)) +API('Z3_statistics_to_string', STRING, (_in(CONTEXT),)) + +API('Z3_get_context_assignment', AST, (_in(CONTEXT),)) + +API('Z3_parse_smtlib_string', VOID, (_in(CONTEXT), _in(STRING), _in(UINT), _in_array(2, SYMBOL), _in_array(2, SORT), + _in(UINT), _in_array(5, SYMBOL), _in_array(5, FUNC_DECL))) +API('Z3_parse_smtlib_file', VOID, (_in(CONTEXT), _in(STRING), _in(UINT), _in_array(2, SYMBOL), _in_array(2, SORT), + _in(UINT), _in_array(5, SYMBOL), _in_array(5, FUNC_DECL))) +API('Z3_get_smtlib_num_formulas', UINT, (_in(CONTEXT), )) +API('Z3_get_smtlib_formula', AST, (_in(CONTEXT), _in(UINT))) +API('Z3_get_smtlib_num_assumptions', UINT, (_in(CONTEXT), )) +API('Z3_get_smtlib_assumption', AST, (_in(CONTEXT), _in(UINT))) +API('Z3_get_smtlib_num_decls', UINT, (_in(CONTEXT), )) +API('Z3_get_smtlib_decl', FUNC_DECL, (_in(CONTEXT), _in(UINT))) +API('Z3_get_smtlib_num_sorts', UINT, (_in(CONTEXT), )) +API('Z3_get_smtlib_sort', SORT, (_in(CONTEXT), _in(UINT))) +API('Z3_get_smtlib_error', STRING, (_in(CONTEXT), )) + +API('Z3_parse_z3_string', AST, (_in(CONTEXT), _in(STRING))) +API('Z3_parse_z3_file', AST, (_in(CONTEXT), _in(STRING))) + +API('Z3_parse_smtlib2_string', AST, (_in(CONTEXT), _in(STRING), _in(UINT), _in_array(2, SYMBOL), _in_array(2, SORT), + _in(UINT), _in_array(5, SYMBOL), _in_array(5, FUNC_DECL))) +API('Z3_parse_smtlib2_file', AST, (_in(CONTEXT), _in(STRING), _in(UINT), _in_array(2, SYMBOL), _in_array(2, SORT), + _in(UINT), _in_array(5, SYMBOL), _in_array(5, FUNC_DECL))) + +API('Z3_get_error_code', UINT, (_in(CONTEXT), )) +API('Z3_set_error', VOID, (_in(CONTEXT), _in(ERROR_CODE))) +API('Z3_get_error_msg', STRING, (_in(ERROR_CODE),)) + +API('Z3_get_version', VOID, (_out(UINT), _out(UINT), _out(UINT), _out(UINT))) +API('Z3_reset_memory', VOID, ()) +API('Z3_is_app', BOOL, (_in(CONTEXT), _in(AST))) +API('Z3_is_numeral_ast', BOOL, (_in(CONTEXT), _in(AST))) +API('Z3_get_arity', UINT, (_in(CONTEXT), _in(FUNC_DECL))) +API('Z3_mk_injective_function', FUNC_DECL, (_in(CONTEXT), _in(SYMBOL), _in(UINT), _in_array(2, SORT), _in(SORT))) + +API('Z3_mk_fixedpoint', FIXEDPOINT, (_in(CONTEXT), )) +API('Z3_fixedpoint_inc_ref', VOID, (_in(CONTEXT), _in(FIXEDPOINT))) +API('Z3_fixedpoint_dec_ref', VOID, (_in(CONTEXT), _in(FIXEDPOINT))) +API('Z3_fixedpoint_push', VOID, (_in(CONTEXT), _in(FIXEDPOINT))) +API('Z3_fixedpoint_pop', VOID, (_in(CONTEXT), _in(FIXEDPOINT))) +API('Z3_fixedpoint_register_relation', VOID, (_in(CONTEXT), _in(FIXEDPOINT), _in(FUNC_DECL))) +API('Z3_fixedpoint_assert', VOID, (_in(CONTEXT), _in(FIXEDPOINT), _in(AST))) +API('Z3_fixedpoint_add_rule', VOID, (_in(CONTEXT), _in(FIXEDPOINT), _in(AST), _in(SYMBOL))) +API('Z3_fixedpoint_add_fact', VOID, (_in(CONTEXT), _in(FIXEDPOINT), _in(FUNC_DECL), _in(UINT), _in_array(3, UINT))) +API('Z3_fixedpoint_query', INT, (_in(CONTEXT), _in(FIXEDPOINT), _in(AST))) +API('Z3_fixedpoint_query_relations', INT, (_in(CONTEXT), _in(FIXEDPOINT), _in(UINT), _in_array(2, FUNC_DECL))) +API('Z3_fixedpoint_get_answer', AST, (_in(CONTEXT), _in(FIXEDPOINT))) +API('Z3_fixedpoint_update_rule', VOID, (_in(CONTEXT), _in(FIXEDPOINT), _in(AST), _in(SYMBOL))) +API('Z3_fixedpoint_get_num_levels', UINT, (_in(CONTEXT), _in(FIXEDPOINT), _in(FUNC_DECL))) +API('Z3_fixedpoint_get_cover_delta', AST, (_in(CONTEXT), _in(FIXEDPOINT), _in(INT), _in(FUNC_DECL))) +API('Z3_fixedpoint_add_cover', VOID, (_in(CONTEXT), _in(FIXEDPOINT), _in(INT), _in(FUNC_DECL), _in(AST))) +API('Z3_fixedpoint_get_statistics', STATS, (_in(CONTEXT), _in(FIXEDPOINT))) +API('Z3_fixedpoint_get_help', STRING, (_in(CONTEXT), _in(FIXEDPOINT))) +API('Z3_fixedpoint_get_param_descrs', PARAM_DESCRS, (_in(CONTEXT), _in(FIXEDPOINT))) +API('Z3_fixedpoint_set_params', VOID, (_in(CONTEXT), _in(FIXEDPOINT), _in(PARAMS))) +API('Z3_fixedpoint_to_string', STRING, (_in(CONTEXT), _in(FIXEDPOINT), _in(UINT), _in_array(2, AST))) +API('Z3_fixedpoint_get_reason_unknown', STRING, (_in(CONTEXT), _in(FIXEDPOINT) )) +API('Z3_fixedpoint_set_predicate_representation', VOID, (_in(CONTEXT), _in(FIXEDPOINT), _in(FUNC_DECL), _in(UINT), _in_array(3, SYMBOL))) +API('Z3_fixedpoint_simplify_rules', AST_VECTOR, (_in(CONTEXT), _in(FIXEDPOINT), _in(UINT), _in_array(2,AST), _in(UINT), _in_array(4,FUNC_DECL))) +# Parameter set support +API('Z3_mk_params', PARAMS, (_in(CONTEXT),)) +API('Z3_params_inc_ref', VOID, (_in(CONTEXT), _in(PARAMS))) +API('Z3_params_dec_ref', VOID, (_in(CONTEXT), _in(PARAMS))) +API('Z3_params_set_bool', VOID, (_in(CONTEXT), _in(PARAMS), _in(SYMBOL), _in(BOOL))) +API('Z3_params_set_uint', VOID, (_in(CONTEXT), _in(PARAMS), _in(SYMBOL), _in(UINT))) +API('Z3_params_set_double', VOID, (_in(CONTEXT), _in(PARAMS), _in(SYMBOL), _in(DOUBLE))) +API('Z3_params_set_symbol', VOID, (_in(CONTEXT), _in(PARAMS), _in(SYMBOL), _in(SYMBOL))) +API('Z3_params_to_string', STRING, (_in(CONTEXT), _in(PARAMS))) +API('Z3_params_validate', VOID, (_in(CONTEXT), _in(PARAMS), _in(PARAM_DESCRS))) +# Parameter description +API('Z3_param_descrs_inc_ref', VOID, (_in(CONTEXT), _in(PARAM_DESCRS))) +API('Z3_param_descrs_dec_ref', VOID, (_in(CONTEXT), _in(PARAM_DESCRS))) +API('Z3_param_descrs_get_kind', UINT, (_in(CONTEXT), _in(PARAM_DESCRS), _in(SYMBOL))) +API('Z3_param_descrs_size', UINT, (_in(CONTEXT), _in(PARAM_DESCRS))) +API('Z3_param_descrs_get_name', SYMBOL, (_in(CONTEXT), _in(PARAM_DESCRS), _in(UINT))) +# New APIs +API('Z3_interrupt', VOID, (_in(CONTEXT),)) +API('Z3_get_error_msg_ex', STRING, (_in(CONTEXT), _in(ERROR_CODE))) +API('Z3_translate', AST, (_in(CONTEXT), _in(AST), _in(CONTEXT))) +# Goal support +API('Z3_mk_goal', GOAL, (_in(CONTEXT), _in(BOOL), _in(BOOL), _in(BOOL))) +API('Z3_goal_inc_ref', VOID, (_in(CONTEXT), _in(GOAL))) +API('Z3_goal_dec_ref', VOID, (_in(CONTEXT), _in(GOAL))) +API('Z3_goal_precision', UINT, (_in(CONTEXT), _in(GOAL))) +API('Z3_goal_assert', VOID, (_in(CONTEXT), _in(GOAL), _in(AST))) +API('Z3_goal_inconsistent', BOOL, (_in(CONTEXT), _in(GOAL))) +API('Z3_goal_depth', UINT, (_in(CONTEXT), _in(GOAL))) +API('Z3_goal_reset', VOID, (_in(CONTEXT), _in(GOAL))) +API('Z3_goal_size', UINT, (_in(CONTEXT), _in(GOAL))) +API('Z3_goal_formula', AST, (_in(CONTEXT), _in(GOAL), _in(UINT))) +API('Z3_goal_num_exprs', UINT, (_in(CONTEXT), _in(GOAL))) +API('Z3_goal_is_decided_sat', BOOL, (_in(CONTEXT), _in(GOAL))) +API('Z3_goal_is_decided_unsat', BOOL, (_in(CONTEXT), _in(GOAL))) +API('Z3_goal_translate', GOAL, (_in(CONTEXT), _in(GOAL), _in(CONTEXT))) +API('Z3_goal_to_string', STRING, (_in(CONTEXT), _in(GOAL))) +# Tactics and Goals +API('Z3_mk_tactic', TACTIC, (_in(CONTEXT), _in(STRING))) +API('Z3_mk_probe', PROBE, (_in(CONTEXT), _in(STRING))) +API('Z3_tactic_inc_ref', VOID, (_in(CONTEXT), _in(TACTIC))) +API('Z3_tactic_dec_ref', VOID, (_in(CONTEXT), _in(TACTIC))) +API('Z3_probe_inc_ref', VOID, (_in(CONTEXT), _in(PROBE))) +API('Z3_probe_dec_ref', VOID, (_in(CONTEXT), _in(PROBE))) +API('Z3_tactic_and_then', TACTIC, (_in(CONTEXT), _in(TACTIC), _in(TACTIC))) +API('Z3_tactic_or_else', TACTIC, (_in(CONTEXT), _in(TACTIC), _in(TACTIC))) +API('Z3_tactic_par_or', TACTIC, (_in(CONTEXT), _in(UINT), _in_array(1, TACTIC))) +API('Z3_tactic_par_and_then', TACTIC, (_in(CONTEXT), _in(TACTIC), _in(TACTIC))) +API('Z3_tactic_try_for', TACTIC, (_in(CONTEXT), _in(TACTIC), _in(UINT))) +API('Z3_tactic_when', TACTIC, (_in(CONTEXT), _in(PROBE), _in(TACTIC))) +API('Z3_tactic_cond', TACTIC, (_in(CONTEXT), _in(PROBE), _in(TACTIC), _in(TACTIC))) +API('Z3_tactic_repeat', TACTIC, (_in(CONTEXT), _in(TACTIC), _in(UINT))) +API('Z3_tactic_skip', TACTIC, (_in(CONTEXT),)) +API('Z3_tactic_fail', TACTIC, (_in(CONTEXT),)) +API('Z3_tactic_fail_if', TACTIC, (_in(CONTEXT), _in(PROBE))) +API('Z3_tactic_fail_if_not_decided', TACTIC, (_in(CONTEXT),)) +API('Z3_tactic_using_params', TACTIC, (_in(CONTEXT), _in(TACTIC), _in(PARAMS))) +API('Z3_probe_const', PROBE, (_in(CONTEXT), _in(DOUBLE))) +API('Z3_probe_lt', PROBE, (_in(CONTEXT), _in(PROBE), _in(PROBE))) +API('Z3_probe_le', PROBE, (_in(CONTEXT), _in(PROBE), _in(PROBE))) +API('Z3_probe_gt', PROBE, (_in(CONTEXT), _in(PROBE), _in(PROBE))) +API('Z3_probe_ge', PROBE, (_in(CONTEXT), _in(PROBE), _in(PROBE))) +API('Z3_probe_eq', PROBE, (_in(CONTEXT), _in(PROBE), _in(PROBE))) +API('Z3_probe_and', PROBE, (_in(CONTEXT), _in(PROBE), _in(PROBE))) +API('Z3_probe_or', PROBE, (_in(CONTEXT), _in(PROBE), _in(PROBE))) +API('Z3_probe_not', PROBE, (_in(CONTEXT), _in(PROBE))) +API('Z3_get_num_tactics', UINT, (_in(CONTEXT),)) +API('Z3_get_tactic_name', STRING, (_in(CONTEXT), _in(UINT))) +API('Z3_get_num_probes', UINT, (_in(CONTEXT),)) +API('Z3_get_probe_name', STRING, (_in(CONTEXT), _in(UINT))) +API('Z3_tactic_get_help', STRING, (_in(CONTEXT), _in(TACTIC))) +API('Z3_tactic_get_param_descrs', PARAM_DESCRS, (_in(CONTEXT), _in(TACTIC))) +API('Z3_tactic_get_descr', STRING, (_in(CONTEXT), _in(STRING))) +API('Z3_probe_get_descr', STRING, (_in(CONTEXT), _in(STRING))) +API('Z3_probe_apply', DOUBLE, (_in(CONTEXT), _in(PROBE), _in(GOAL))) +API('Z3_tactic_apply', APPLY_RESULT, (_in(CONTEXT), _in(TACTIC), _in(GOAL))) +API('Z3_tactic_apply_ex', APPLY_RESULT, (_in(CONTEXT), _in(TACTIC), _in(GOAL), _in(PARAMS))) +API('Z3_apply_result_inc_ref', VOID, (_in(CONTEXT), _in(APPLY_RESULT))) +API('Z3_apply_result_dec_ref', VOID, (_in(CONTEXT), _in(APPLY_RESULT))) +API('Z3_apply_result_to_string', STRING, (_in(CONTEXT), _in(APPLY_RESULT))) +API('Z3_apply_result_get_num_subgoals', UINT, (_in(CONTEXT), _in(APPLY_RESULT))) +API('Z3_apply_result_get_subgoal', GOAL, (_in(CONTEXT), _in(APPLY_RESULT), _in(UINT))) +API('Z3_apply_result_convert_model', MODEL, (_in(CONTEXT), _in(APPLY_RESULT), _in(UINT), _in(MODEL))) +# Solver API +API('Z3_mk_solver', SOLVER, (_in(CONTEXT),)) +API('Z3_mk_simple_solver', SOLVER, (_in(CONTEXT),)) +API('Z3_mk_solver_for_logic', SOLVER, (_in(CONTEXT), _in(SYMBOL))) +API('Z3_mk_solver_from_tactic', SOLVER, (_in(CONTEXT), _in(TACTIC))) +API('Z3_solver_get_help', STRING, (_in(CONTEXT), _in(SOLVER))) +API('Z3_solver_get_param_descrs', PARAM_DESCRS, (_in(CONTEXT), _in(SOLVER))) +API('Z3_solver_set_params', VOID, (_in(CONTEXT), _in(SOLVER), _in(PARAMS))) +API('Z3_solver_inc_ref', VOID, (_in(CONTEXT), _in(SOLVER))) +API('Z3_solver_dec_ref', VOID, (_in(CONTEXT), _in(SOLVER))) +API('Z3_solver_push', VOID, (_in(CONTEXT), _in(SOLVER))) +API('Z3_solver_pop', VOID, (_in(CONTEXT), _in(SOLVER), _in(UINT))) +API('Z3_solver_reset', VOID, (_in(CONTEXT), _in(SOLVER))) +API('Z3_solver_get_num_scopes', UINT, (_in(CONTEXT), _in(SOLVER))) +API('Z3_solver_assert', VOID, (_in(CONTEXT), _in(SOLVER), _in(AST))) +API('Z3_solver_get_assertions', AST_VECTOR, (_in(CONTEXT), _in(SOLVER))) +API('Z3_solver_check', INT, (_in(CONTEXT), _in(SOLVER))) +API('Z3_solver_check_assumptions', INT, (_in(CONTEXT), _in(SOLVER), _in(UINT), _in_array(2, AST))) +API('Z3_solver_get_model', MODEL, (_in(CONTEXT), _in(SOLVER))) +API('Z3_solver_get_proof', AST, (_in(CONTEXT), _in(SOLVER))) +API('Z3_solver_get_unsat_core', AST_VECTOR, (_in(CONTEXT), _in(SOLVER))) +API('Z3_solver_get_reason_unknown', STRING, (_in(CONTEXT), _in(SOLVER))) +API('Z3_solver_get_statistics', STATS, (_in(CONTEXT), _in(SOLVER))) +API('Z3_solver_to_string', STRING, (_in(CONTEXT), _in(SOLVER))) +# Statistics API +API('Z3_stats_to_string', STRING, (_in(CONTEXT), _in(STATS))) +API('Z3_stats_inc_ref', VOID, (_in(CONTEXT), _in(STATS))) +API('Z3_stats_dec_ref', VOID, (_in(CONTEXT), _in(STATS))) +API('Z3_stats_size', UINT, (_in(CONTEXT), _in(STATS))) +API('Z3_stats_get_key', STRING, (_in(CONTEXT), _in(STATS), _in(UINT))) +API('Z3_stats_is_uint', BOOL, (_in(CONTEXT), _in(STATS), _in(UINT))) +API('Z3_stats_is_double', BOOL, (_in(CONTEXT), _in(STATS), _in(UINT))) +API('Z3_stats_get_uint_value', UINT, (_in(CONTEXT), _in(STATS), _in(UINT))) +API('Z3_stats_get_double_value', DOUBLE, (_in(CONTEXT), _in(STATS), _in(UINT))) +# AST Vectors +API('Z3_mk_ast_vector', AST_VECTOR, (_in(CONTEXT),)) +API('Z3_ast_vector_inc_ref', VOID, (_in(CONTEXT), _in(AST_VECTOR))) +API('Z3_ast_vector_dec_ref', VOID, (_in(CONTEXT), _in(AST_VECTOR))) +API('Z3_ast_vector_size', UINT, (_in(CONTEXT), _in(AST_VECTOR))) +API('Z3_ast_vector_get', AST, (_in(CONTEXT), _in(AST_VECTOR), _in(UINT))) +API('Z3_ast_vector_set', VOID, (_in(CONTEXT), _in(AST_VECTOR), _in(UINT), _in(AST))) +API('Z3_ast_vector_resize', VOID, (_in(CONTEXT), _in(AST_VECTOR), _in(UINT))) +API('Z3_ast_vector_push', VOID, (_in(CONTEXT), _in(AST_VECTOR), _in(AST))) +API('Z3_ast_vector_translate', AST_VECTOR, (_in(CONTEXT), _in(AST_VECTOR), _in(CONTEXT))) +API('Z3_ast_vector_to_string', STRING, (_in(CONTEXT), _in(AST_VECTOR))) +# AST Maps +API('Z3_mk_ast_map', AST_MAP, (_in(CONTEXT),)) +API('Z3_ast_map_inc_ref', VOID, (_in(CONTEXT), _in(AST_MAP))) +API('Z3_ast_map_dec_ref', VOID, (_in(CONTEXT), _in(AST_MAP))) +API('Z3_ast_map_contains', BOOL, (_in(CONTEXT), _in(AST_MAP), _in(AST))) +API('Z3_ast_map_find', AST, (_in(CONTEXT), _in(AST_MAP), _in(AST))) +API('Z3_ast_map_insert', VOID, (_in(CONTEXT), _in(AST_MAP), _in(AST), _in(AST))) +API('Z3_ast_map_erase', VOID, (_in(CONTEXT), _in(AST_MAP), _in(AST))) +API('Z3_ast_map_size', UINT, (_in(CONTEXT), _in(AST_MAP))) +API('Z3_ast_map_reset', VOID, (_in(CONTEXT), _in(AST_MAP))) +API('Z3_ast_map_keys', AST_VECTOR, (_in(CONTEXT), _in(AST_MAP))) +API('Z3_ast_map_to_string', STRING, (_in(CONTEXT), _in(AST_MAP))) +# WARNING: we do not support logging for the theory plugin API +extra_API('Z3_open_log', INT, (_in(STRING),)) +extra_API('Z3_append_log', VOID, (_in(STRING),)) +extra_API('Z3_close_log', VOID, ()) + +mk_bindings() +mk_py_wrappers() +mk_dotnet() +mk_dotnet_wrappers() +# mk_defs() diff --git a/lib/api_arith.cpp b/lib/api_arith.cpp new file mode 100644 index 000000000..e4d687d47 --- /dev/null +++ b/lib/api_arith.cpp @@ -0,0 +1,219 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_arith.cpp + +Abstract: + API for arith theory + +Author: + + Leonardo de Moura (leonardo) 2012-02-29. + +Revision History: + +--*/ +#include +#include"z3.h" +#include"api_log_macros.h" +#include"api_context.h" +#include"api_util.h" +#include"arith_decl_plugin.h" +#include"algebraic_numbers.h" + +#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) + +extern "C" { + + Z3_sort Z3_API Z3_mk_int_sort(Z3_context c) { + Z3_TRY; + LOG_Z3_mk_int_sort(c); + RESET_ERROR_CODE(); + Z3_sort r = of_sort(mk_c(c)->m().mk_sort(mk_c(c)->get_arith_fid(), INT_SORT)); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_sort Z3_API Z3_mk_real_sort(Z3_context c) { + Z3_TRY; + LOG_Z3_mk_real_sort(c); + RESET_ERROR_CODE(); + Z3_sort r = of_sort(mk_c(c)->m().mk_sort(mk_c(c)->get_arith_fid(), REAL_SORT)); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_real(__in Z3_context c, int num, int den) { + Z3_TRY; + LOG_Z3_mk_real(c, num, den); + RESET_ERROR_CODE(); + if (den == 0) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + sort* s = mk_c(c)->m().mk_sort(mk_c(c)->get_arith_fid(), REAL_SORT); + ast* a = mk_c(c)->mk_numeral_core(rational(num, den), s); + RETURN_Z3(of_ast(a)); + Z3_CATCH_RETURN(0); + } + + MK_ARITH_OP(Z3_mk_add, OP_ADD); + MK_ARITH_OP(Z3_mk_mul, OP_MUL); + MK_BINARY_ARITH_OP(Z3_mk_power, OP_POWER); + MK_BINARY_ARITH_OP(Z3_mk_mod, OP_MOD); + MK_BINARY_ARITH_OP(Z3_mk_rem, OP_REM); + + Z3_ast Z3_API Z3_mk_div(Z3_context c, Z3_ast n1, Z3_ast n2) { + Z3_TRY; + LOG_Z3_mk_div(c, n1, n2); + RESET_ERROR_CODE(); + decl_kind k = OP_IDIV; + sort* ty = mk_c(c)->m().get_sort(to_expr(n1)); + sort* real_ty = mk_c(c)->m().mk_sort(mk_c(c)->get_arith_fid(), REAL_SORT); + if (ty == real_ty) { + k = OP_DIV; + } + expr * args[2] = { to_expr(n1), to_expr(n2) }; + ast* a = mk_c(c)->m().mk_app(mk_c(c)->get_arith_fid(), k, 0, 0, 2, args); + mk_c(c)->save_ast_trail(a); + check_sorts(c, a); + RETURN_Z3(of_ast(a)); + Z3_CATCH_RETURN(0); + } + + MK_ARITH_PRED(Z3_mk_lt, OP_LT); + 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_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); + + Z3_ast Z3_API Z3_mk_sub(Z3_context c, unsigned num_args, Z3_ast const args[]) { + Z3_TRY; + LOG_Z3_mk_sub(c, num_args, args); + RESET_ERROR_CODE(); + if (num_args == 0) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + if (mk_c(c)->fparams().m_pre_simplify_expr) { + // Do not use logging here... the function is implemented using API primitives + Z3_ast m1 = Z3_mk_int(c, -1, Z3_get_sort(c, args[0])); + Z3_ast args1[2] = { args[0], 0 }; + for (unsigned i = 1; i < num_args; ++i) { + Z3_ast args2[3] = { m1, args[i] }; + args1[1] = Z3_mk_mul(c, 2, args2); + args1[0] = Z3_mk_add(c, 2, args1); + } + RETURN_Z3(args1[0]); + } + else { + expr* r = to_expr(args[0]); + for (unsigned i = 1; i < num_args; ++i) { + expr* args1[2] = { r, to_expr(args[i]) }; + r = mk_c(c)->m().mk_app(mk_c(c)->get_arith_fid(), OP_SUB, 0, 0, 2, args1); + check_sorts(c, r); + } + mk_c(c)->save_ast_trail(r); + RETURN_Z3(of_expr(r)); + } + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_unary_minus(Z3_context c, Z3_ast n) { + Z3_TRY; + LOG_Z3_mk_unary_minus(c, n); + RESET_ERROR_CODE(); + if (mk_c(c)->fparams().m_pre_simplify_expr) { + Z3_ast m1 = Z3_mk_int(c, -1, Z3_get_sort(c, n)); + Z3_ast args[2] = { m1, n }; + Z3_ast r = Z3_mk_mul(c, 2, args); + RETURN_Z3(r); + } + MK_UNARY_BODY(Z3_mk_unary_minus, mk_c(c)->get_arith_fid(), OP_UMINUS, SKIP); + Z3_CATCH_RETURN(0); + } + + Z3_bool Z3_API Z3_is_algebraic_number(Z3_context c, Z3_ast a) { + Z3_TRY; + LOG_Z3_is_algebraic_number(c, a); + RESET_ERROR_CODE(); + expr * e = to_expr(a); + return mk_c(c)->autil().is_irrational_algebraic_numeral(e); + Z3_CATCH_RETURN(Z3_FALSE); + } + + Z3_ast Z3_API Z3_get_algebraic_number_lower(Z3_context c, Z3_ast a, unsigned precision) { + Z3_TRY; + LOG_Z3_get_algebraic_number_lower(c, a, precision); + RESET_ERROR_CODE(); + if (!Z3_is_algebraic_number(c, a)) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + expr * e = to_expr(a); + algebraic_numbers::anum const & val = mk_c(c)->autil().to_irrational_algebraic_numeral(e); + rational l; + mk_c(c)->autil().am().get_lower(val, l, precision); + expr * r = mk_c(c)->autil().mk_numeral(l, false); + mk_c(c)->save_ast_trail(r); + RETURN_Z3(of_expr(r)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_get_algebraic_number_upper(Z3_context c, Z3_ast a, unsigned precision) { + Z3_TRY; + LOG_Z3_get_algebraic_number_upper(c, a, precision); + RESET_ERROR_CODE(); + if (!Z3_is_algebraic_number(c, a)) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + expr * e = to_expr(a); + algebraic_numbers::anum const & val = mk_c(c)->autil().to_irrational_algebraic_numeral(e); + rational l; + mk_c(c)->autil().am().get_upper(val, l, precision); + expr * r = mk_c(c)->autil().mk_numeral(l, false); + mk_c(c)->save_ast_trail(r); + RETURN_Z3(of_expr(r)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_get_numerator(Z3_context c, Z3_ast a) { + Z3_TRY; + LOG_Z3_get_numerator(c, a); + RESET_ERROR_CODE(); + rational val; + ast * _a = to_ast(a); + if (!is_expr(_a) || !mk_c(c)->autil().is_numeral(to_expr(_a), val)) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + expr * r = mk_c(c)->autil().mk_numeral(numerator(val), true); + mk_c(c)->save_ast_trail(r); + RETURN_Z3(of_expr(r)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_get_denominator(Z3_context c, Z3_ast a) { + Z3_TRY; + LOG_Z3_get_denominator(c, a); + RESET_ERROR_CODE(); + rational val; + ast * _a = to_ast(a); + if (!is_expr(_a) || !mk_c(c)->autil().is_numeral(to_expr(_a), val)) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + expr * r = mk_c(c)->autil().mk_numeral(denominator(val), true); + mk_c(c)->save_ast_trail(r); + RETURN_Z3(of_expr(r)); + Z3_CATCH_RETURN(0); + } + +}; diff --git a/lib/api_array.cpp b/lib/api_array.cpp new file mode 100644 index 000000000..415c8696a --- /dev/null +++ b/lib/api_array.cpp @@ -0,0 +1,233 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_array.cpp + +Abstract: + API for array theory + +Author: + + Leonardo de Moura (leonardo) 2012-02-29. + +Revision History: + +--*/ +#include +#include"z3.h" +#include"api_log_macros.h" +#include"api_context.h" +#include"api_util.h" +#include"array_decl_plugin.h" + +extern "C" { + + Z3_sort Z3_API Z3_mk_array_sort(Z3_context c, Z3_sort domain, Z3_sort range) { + Z3_TRY; + LOG_Z3_mk_array_sort(c, domain, range); + RESET_ERROR_CODE(); + parameter params[2] = { parameter(to_sort(domain)), parameter(to_sort(range)) }; + sort * ty = mk_c(c)->m().mk_sort(mk_c(c)->get_array_fid(), ARRAY_SORT, 2, params); + mk_c(c)->save_ast_trail(ty); + RETURN_Z3(of_sort(ty)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_select(Z3_context c, Z3_ast a, Z3_ast i) { + Z3_TRY; + LOG_Z3_mk_select(c, a, i); + RESET_ERROR_CODE(); + ast_manager & m = mk_c(c)->m(); + expr * _a = to_expr(a); + expr * _i = to_expr(i); + sort * a_ty = m.get_sort(_a); + sort * i_ty = m.get_sort(_i); + if (a_ty->get_family_id() != mk_c(c)->get_array_fid()) { + SET_ERROR_CODE(Z3_SORT_ERROR); + RETURN_Z3(0); + } + sort * domain[2] = {a_ty, i_ty}; + func_decl * d = m.mk_func_decl(mk_c(c)->get_array_fid(), OP_SELECT, 2, a_ty->get_parameters(), 2, domain); + expr * args[2] = {_a, _i}; + app * r = m.mk_app(d, 2, args); + mk_c(c)->save_ast_trail(r); + check_sorts(c, r); + RETURN_Z3(of_ast(r)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_store(Z3_context c, Z3_ast a, Z3_ast i, Z3_ast v) { + Z3_TRY; + LOG_Z3_mk_store(c, a, i, v); + RESET_ERROR_CODE(); + ast_manager & m = mk_c(c)->m(); + expr * _a = to_expr(a); + expr * _i = to_expr(i); + expr * _v = to_expr(v); + sort * a_ty = m.get_sort(_a); + sort * i_ty = m.get_sort(_i); + sort * v_ty = m.get_sort(_v); + if (a_ty->get_family_id() != mk_c(c)->get_array_fid()) { + SET_ERROR_CODE(Z3_SORT_ERROR); + RETURN_Z3(0); + } + sort * domain[3] = {a_ty, i_ty, v_ty}; + func_decl * d = m.mk_func_decl(mk_c(c)->get_array_fid(), OP_STORE, 2, a_ty->get_parameters(), 3, domain); + expr * args[3] = {_a, _i, _v}; + app * r = m.mk_app(d, 3, args); + mk_c(c)->save_ast_trail(r); + check_sorts(c, r); + RETURN_Z3(of_ast(r)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_map(__in Z3_context c, __in Z3_func_decl f, unsigned n, __in Z3_ast const* args) { + Z3_TRY; + LOG_Z3_mk_map(c, f, n, args); + RESET_ERROR_CODE(); + if (n == 0) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + ast_manager & m = mk_c(c)->m(); + func_decl* _f = to_func_decl(f); + expr* const* _args = to_exprs(args); + + ptr_vector domain; + for (unsigned i = 0; i < n; ++i) { + domain.push_back(m.get_sort(_args[i])); + } + parameter param(_f); + func_decl * d = m.mk_func_decl(mk_c(c)->get_array_fid(), OP_ARRAY_MAP, 1, ¶m, n, domain.c_ptr()); + app* r = m.mk_app(d, n, _args); + mk_c(c)->save_ast_trail(r); + check_sorts(c, r); + RETURN_Z3(of_ast(r)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_const_array(__in Z3_context c, __in Z3_sort domain, __in Z3_ast v) { + Z3_TRY; + LOG_Z3_mk_const_array(c, domain, v); + RESET_ERROR_CODE(); + ast_manager & m = mk_c(c)->m(); + expr * _v = to_expr(v); + sort * _range = m.get_sort(_v); + sort * _domain = to_sort(domain); + parameter params[2] = { parameter(_domain), parameter(_range) }; + sort * a_ty = mk_c(c)->m().mk_sort(mk_c(c)->get_array_fid(), ARRAY_SORT, 2, params); + parameter param(a_ty); + func_decl* cd = m.mk_func_decl(mk_c(c)->get_array_fid(), OP_CONST_ARRAY, 1, ¶m, 1, &_range); + app * r = m.mk_app(cd, 1, &_v); + mk_c(c)->save_ast_trail(r); + check_sorts(c, r); + RETURN_Z3(of_ast(r)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_array_default(__in Z3_context c, __in Z3_ast array) { + Z3_TRY; + LOG_Z3_mk_array_default(c, array); + RESET_ERROR_CODE(); + ast_manager & m = mk_c(c)->m(); + expr * _a = to_expr(array); + + func_decl * f = m.mk_func_decl(mk_c(c)->get_array_fid(), OP_ARRAY_DEFAULT, 0, 0, 1, &_a); + app * r = m.mk_app(f, 1, &_a); + mk_c(c)->save_ast_trail(r); + check_sorts(c, r); + RETURN_Z3(of_ast(r)); + Z3_CATCH_RETURN(0); + } + + Z3_ast mk_app_array_core(__in Z3_context c, __in Z3_sort domain, __in Z3_ast v) { + RESET_ERROR_CODE(); + ast_manager & m = mk_c(c)->m(); + expr * _v = to_expr(v); + sort * _range = m.get_sort(_v); + sort * _domain = to_sort(domain); + parameter params[2] = { parameter(_domain), parameter(_range) }; + sort * a_ty = mk_c(c)->m().mk_sort(mk_c(c)->get_array_fid(), ARRAY_SORT, 2, params); + parameter param(a_ty); + func_decl * cd = m.mk_func_decl(mk_c(c)->get_array_fid(), OP_CONST_ARRAY, 1, ¶m, 1, &_range); + app * r = m.mk_app(cd, 1, &_v); + mk_c(c)->save_ast_trail(r); + check_sorts(c, r); + return of_ast(r); + } + + Z3_sort Z3_API Z3_mk_set_sort(__in Z3_context c, __in Z3_sort ty) { + Z3_TRY; + return Z3_mk_array_sort(c, ty, Z3_mk_bool_sort(c)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_empty_set(__in Z3_context c, __in Z3_sort domain) { + Z3_TRY; + LOG_Z3_mk_empty_set(c, domain); + RESET_ERROR_CODE(); + Z3_ast r = mk_app_array_core(c, domain, Z3_mk_false(c)); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_full_set(__in Z3_context c, __in Z3_sort domain) { + Z3_TRY; + LOG_Z3_mk_full_set(c, domain); + RESET_ERROR_CODE(); + Z3_ast r = mk_app_array_core(c, domain, Z3_mk_true(c)); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + MK_NARY(Z3_mk_set_union, mk_c(c)->get_array_fid(), OP_SET_UNION, SKIP); + MK_NARY(Z3_mk_set_intersect, mk_c(c)->get_array_fid(), OP_SET_INTERSECT, SKIP); + MK_BINARY(Z3_mk_set_difference, mk_c(c)->get_array_fid(), OP_SET_DIFFERENCE, SKIP); + MK_UNARY(Z3_mk_set_complement, mk_c(c)->get_array_fid(), OP_SET_COMPLEMENT, SKIP); + MK_BINARY(Z3_mk_set_subset, mk_c(c)->get_array_fid(), OP_SET_SUBSET, SKIP); + + Z3_ast Z3_mk_set_member(__in Z3_context c, __in Z3_ast elem, __in Z3_ast set) { + return Z3_mk_select(c, set, elem); + } + + Z3_ast Z3_mk_set_add(__in Z3_context c, __in Z3_ast set, __in Z3_ast elem) { + return Z3_mk_store(c, set, elem, Z3_mk_true(c)); + } + + Z3_ast Z3_mk_set_del(__in Z3_context c, __in Z3_ast set, __in Z3_ast elem) { + return Z3_mk_store(c, set, elem, Z3_mk_false(c)); + } + + 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, 0); + 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()); + RETURN_Z3(r); + } + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + Z3_CATCH_RETURN(0); + } + + Z3_sort Z3_API Z3_get_array_sort_range(Z3_context c, Z3_sort t) { + Z3_TRY; + LOG_Z3_get_array_sort_range(c, t); + RESET_ERROR_CODE(); + CHECK_VALID_AST(t, 0); + 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(1).get_ast()); + RETURN_Z3(r); + } + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + Z3_CATCH_RETURN(0); + } + +}; diff --git a/lib/api_ast.cpp b/lib/api_ast.cpp new file mode 100644 index 000000000..7e3db046f --- /dev/null +++ b/lib/api_ast.cpp @@ -0,0 +1,1156 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_ast.cpp + +Abstract: + Basic API for ASTs + +Author: + + Leonardo de Moura (leonardo) 2012-02-29. + +Revision History: + +--*/ +#include +#include"z3.h" +#include"api_log_macros.h" +#include"api_context.h" +#include"api_util.h" +#include"well_sorted.h" +#include"arith_decl_plugin.h" +#include"bv_decl_plugin.h" +#include"datatype_decl_plugin.h" +#include"array_decl_plugin.h" +#include"ast_translation.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"ast_smt_pp.h" +#include"ast_smt2_pp.h" +#include"th_rewriter.h" +#include"var_subst.h" +#include"expr_substitution.h" +#include"pp.h" +#include"scoped_ctrl_c.h" +#include"cancel_eh.h" +#include"scoped_timer.h" + +extern "C" { + + Z3_symbol Z3_API Z3_mk_int_symbol(Z3_context c, int i) { + Z3_TRY; + LOG_Z3_mk_int_symbol(c, i); + RESET_ERROR_CODE(); + if (i < 0 || (unsigned)i >= (SIZE_MAX >> PTR_ALIGNMENT)) { + SET_ERROR_CODE(Z3_IOB); + return 0; + } + Z3_symbol result = of_symbol(symbol(i)); + return result; + Z3_CATCH_RETURN(0); + } + + Z3_symbol Z3_API Z3_mk_string_symbol(Z3_context c, char const * str) { + Z3_TRY; + LOG_Z3_mk_string_symbol(c, str); + RESET_ERROR_CODE(); + symbol s; + if (str == 0 || *str == 0) + s = symbol::null; + else + s = symbol(str); + Z3_symbol result = of_symbol(s); + return result; + Z3_CATCH_RETURN(0); + } + + Z3_bool Z3_API Z3_is_eq_sort(Z3_context c, Z3_sort s1, Z3_sort s2) { + RESET_ERROR_CODE(); + return s1 == s2; + } + + Z3_sort Z3_API Z3_mk_uninterpreted_sort(Z3_context c, Z3_symbol name) { + Z3_TRY; + LOG_Z3_mk_uninterpreted_sort(c, name); + RESET_ERROR_CODE(); + sort* ty = mk_c(c)->m().mk_sort(to_symbol(name)); + mk_c(c)->save_ast_trail(ty); + RETURN_Z3(of_sort(ty)); + Z3_CATCH_RETURN(0); + } + + Z3_bool Z3_API Z3_is_eq_ast(Z3_context c, Z3_ast s1, Z3_ast s2) { + RESET_ERROR_CODE(); + return s1 == s2; + } + + Z3_bool Z3_API Z3_is_eq_func_decl(Z3_context c, Z3_func_decl s1, Z3_func_decl s2) { + RESET_ERROR_CODE(); + return s1 == s2; + } + + Z3_func_decl Z3_API Z3_mk_func_decl(Z3_context c, Z3_symbol s, unsigned domain_size, Z3_sort const* domain, + Z3_sort range) { + Z3_TRY; + LOG_Z3_mk_func_decl(c, s, domain_size, domain, range); + RESET_ERROR_CODE(); + func_decl* d = mk_c(c)->m().mk_func_decl(to_symbol(s), + domain_size, + to_sorts(domain), + to_sort(range)); + + mk_c(c)->save_ast_trail(d); + RETURN_Z3(of_func_decl(d)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_app(Z3_context c, Z3_func_decl d, unsigned num_args, Z3_ast const * args) { + Z3_TRY; + LOG_Z3_mk_app(c, d, num_args, args); + RESET_ERROR_CODE(); + ptr_buffer arg_list; + for (unsigned i = 0; i < num_args; ++i) { + arg_list.push_back(to_expr(args[i])); + } + func_decl* _d = reinterpret_cast(d); + app* a = mk_c(c)->m().mk_app(_d, num_args, arg_list.c_ptr()); + mk_c(c)->save_ast_trail(a); + check_sorts(c, a); + RETURN_Z3(of_ast(a)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_const(Z3_context c, Z3_symbol s, Z3_sort ty) { + Z3_TRY; + LOG_Z3_mk_const(c, s, ty); + RESET_ERROR_CODE(); + app* a = mk_c(c)->m().mk_const(mk_c(c)->m().mk_const_decl(to_symbol(s), to_sort(ty))); + mk_c(c)->save_ast_trail(a); + RETURN_Z3(of_ast(a)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_label(Z3_context c, Z3_symbol s, Z3_bool is_pos, Z3_ast f) { + Z3_TRY; + LOG_Z3_mk_label(c, s, is_pos, f); + RESET_ERROR_CODE(); + expr* _f = to_expr(f); + if (!mk_c(c)->m().is_bool(_f)) { + SET_ERROR_CODE(Z3_SORT_ERROR); + return f; + } + expr* a = mk_c(c)->m().mk_label(is_pos != 0, to_symbol(s), _f); + mk_c(c)->save_ast_trail(a); + RETURN_Z3(of_ast(a)); + Z3_CATCH_RETURN(0); + } + + Z3_func_decl Z3_API Z3_mk_fresh_func_decl(Z3_context c, const char * prefix, unsigned domain_size, + Z3_sort const domain[], Z3_sort range) { + Z3_TRY; + LOG_Z3_mk_fresh_func_decl(c, prefix, domain_size, domain, range); + RESET_ERROR_CODE(); + if (prefix == 0) { + prefix = ""; + } + + func_decl* d = mk_c(c)->m().mk_fresh_func_decl(prefix, + domain_size, + reinterpret_cast(domain), + to_sort(range)); + + mk_c(c)->save_ast_trail(d); + RETURN_Z3(of_func_decl(d)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_fresh_const(Z3_context c, const char * prefix, Z3_sort ty) { + Z3_TRY; + LOG_Z3_mk_fresh_const(c, prefix, ty); + RESET_ERROR_CODE(); + if (prefix == 0) { + prefix = ""; + } + app* a = mk_c(c)->m().mk_fresh_const(prefix, to_sort(ty)); + mk_c(c)->save_ast_trail(a); + RETURN_Z3(of_ast(a)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_true(Z3_context c) { + Z3_TRY; + LOG_Z3_mk_true(c); + RESET_ERROR_CODE(); + Z3_ast r = of_ast(mk_c(c)->m().mk_true()); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_false(Z3_context c) { + Z3_TRY; + LOG_Z3_mk_false(c); + RESET_ERROR_CODE(); + Z3_ast r = of_ast(mk_c(c)->m().mk_false()); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + MK_UNARY(Z3_mk_not, mk_c(c)->get_basic_fid(), OP_NOT, SKIP); + MK_BINARY(Z3_mk_eq, mk_c(c)->get_basic_fid(), OP_EQ, SKIP); + MK_NARY(Z3_mk_distinct, mk_c(c)->get_basic_fid(), OP_DISTINCT, SKIP); + MK_BINARY(Z3_mk_iff, mk_c(c)->get_basic_fid(), OP_IFF, SKIP); + MK_BINARY(Z3_mk_implies, mk_c(c)->get_basic_fid(), OP_IMPLIES, SKIP); + MK_BINARY(Z3_mk_xor, mk_c(c)->get_basic_fid(), OP_XOR, SKIP); + MK_NARY(Z3_mk_and, mk_c(c)->get_basic_fid(), OP_AND, SKIP); + MK_NARY(Z3_mk_or, mk_c(c)->get_basic_fid(), OP_OR, SKIP); + + Z3_ast mk_ite_core(Z3_context c, Z3_ast t1, Z3_ast t2, Z3_ast t3) { + expr * result = mk_c(c)->m().mk_ite(to_expr(t1), to_expr(t2), to_expr(t3)); + mk_c(c)->save_ast_trail(result); + check_sorts(c, result); + return of_ast(result); + } + + Z3_ast Z3_API Z3_mk_ite(Z3_context c, Z3_ast t1, Z3_ast t2, Z3_ast t3) { + Z3_TRY; + LOG_Z3_mk_ite(c, t1, t2, t3); + RESET_ERROR_CODE(); + Z3_ast r = mk_ite_core(c, t1, t2, t3); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_sort Z3_API Z3_mk_bool_sort(Z3_context c) { + Z3_TRY; + LOG_Z3_mk_bool_sort(c); + RESET_ERROR_CODE(); + Z3_sort r = of_sort(mk_c(c)->m().mk_sort(mk_c(c)->m().get_basic_family_id(), BOOL_SORT)); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_app_to_ast(Z3_context c, Z3_app a) { + RESET_ERROR_CODE(); + return (Z3_ast)(a); + } + + Z3_ast Z3_API Z3_sort_to_ast(Z3_context c, Z3_sort s) { + RESET_ERROR_CODE(); + return (Z3_ast)(s); + } + + Z3_ast Z3_API Z3_func_decl_to_ast(Z3_context c, Z3_func_decl f) { + RESET_ERROR_CODE(); + return (Z3_ast)(f); + } + + // ------------------------ + + unsigned Z3_API Z3_get_ast_id(Z3_context c, Z3_ast t) { + LOG_Z3_get_ast_id(c, t); + RESET_ERROR_CODE(); + return to_expr(t)->get_id(); + } + + unsigned Z3_API Z3_get_func_decl_id(Z3_context c, Z3_func_decl f) { + LOG_Z3_get_func_decl_id(c, f); + RESET_ERROR_CODE(); + return to_func_decl(f)->get_id(); + } + + unsigned Z3_API Z3_get_sort_id(Z3_context c, Z3_sort s) { + LOG_Z3_get_sort_id(c, s); + RESET_ERROR_CODE(); + return to_sort(s)->get_id(); + } + + Z3_bool Z3_API Z3_is_well_sorted(Z3_context c, Z3_ast t) { + Z3_TRY; + LOG_Z3_is_well_sorted(c, t); + RESET_ERROR_CODE(); + return is_well_sorted(mk_c(c)->m(), to_expr(t)); + Z3_CATCH_RETURN(Z3_FALSE); + } + + Z3_symbol_kind Z3_API Z3_get_symbol_kind(Z3_context c, Z3_symbol s) { + Z3_TRY; + LOG_Z3_get_symbol_kind(c, s); + RESET_ERROR_CODE(); + symbol _s = to_symbol(s); + return _s.is_numerical() ? Z3_INT_SYMBOL : Z3_STRING_SYMBOL; + Z3_CATCH_RETURN(Z3_INT_SYMBOL); + } + + int Z3_API Z3_get_symbol_int(Z3_context c, Z3_symbol s) { + Z3_TRY; + LOG_Z3_get_symbol_int(c, s); + RESET_ERROR_CODE(); + symbol _s = to_symbol(s); + if (_s.is_numerical()) { + return _s.get_num(); + } + SET_ERROR_CODE(Z3_INVALID_ARG); + return -1; + Z3_CATCH_RETURN(-1); + } + + char const * Z3_API Z3_get_symbol_string(Z3_context c, Z3_symbol s) { + Z3_TRY; + LOG_Z3_get_symbol_string(c, s); + RESET_ERROR_CODE(); + symbol _s = to_symbol(s); + if (_s.is_numerical()) { + std::ostringstream buffer; + buffer << _s.get_num(); + return mk_c(c)->mk_external_string(buffer.str()); + } + else { + return mk_c(c)->mk_external_string(_s.bare_str()); + } + Z3_CATCH_RETURN(""); + } + + extern bool is_numeral_sort(Z3_context c, Z3_sort ty); + + Z3_ast_kind Z3_API Z3_get_ast_kind(Z3_context c, Z3_ast a) { + Z3_TRY; + LOG_Z3_get_ast_kind(c, a); + RESET_ERROR_CODE(); + CHECK_VALID_AST(a, Z3_UNKNOWN_AST); + ast * _a = to_expr(a); + switch (_a->get_kind()) { + case AST_APP: { + expr * e = to_expr(_a); + if (is_numeral_sort(c, of_sort(mk_c(c)->m().get_sort(e))) && mk_c(c)->m().is_value(e)) + return Z3_NUMERAL_AST; + return Z3_APP_AST; + } + case AST_VAR: return Z3_VAR_AST; + case AST_QUANTIFIER: return Z3_QUANTIFIER_AST; + case AST_SORT: return Z3_SORT_AST; + case AST_FUNC_DECL: return Z3_FUNC_DECL_AST; + default: return Z3_UNKNOWN_AST; + } + Z3_CATCH_RETURN(Z3_UNKNOWN_AST); + } + + unsigned Z3_API Z3_get_ast_hash(Z3_context c, Z3_ast a) { + LOG_Z3_get_ast_hash(c, a); + RESET_ERROR_CODE(); + return to_ast(a)->hash(); + } + + Z3_bool Z3_API Z3_is_app(Z3_context c, Z3_ast a) { + LOG_Z3_is_app(c, a); + RESET_ERROR_CODE(); + return is_app(reinterpret_cast(a)); + } + + Z3_app Z3_API Z3_to_app(Z3_context c, Z3_ast a) { + LOG_Z3_to_app(c, a); + RESET_ERROR_CODE(); + SASSERT(is_app(reinterpret_cast(a))); + RETURN_Z3(of_app(reinterpret_cast(a))); + } + + Z3_func_decl Z3_API Z3_to_func_decl(Z3_context c, Z3_ast a) { + LOG_Z3_to_func_decl(c, a); + RESET_ERROR_CODE(); + SASSERT(is_func_decl(reinterpret_cast(a))); + RETURN_Z3(of_func_decl(reinterpret_cast(a))); + } + + Z3_func_decl Z3_API Z3_get_app_decl(Z3_context c, Z3_app a) { + LOG_Z3_get_app_decl(c, a); + RESET_ERROR_CODE(); + if (!is_app(reinterpret_cast(a))) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + RETURN_Z3(of_func_decl(to_app(a)->get_decl())); + } + + unsigned Z3_API Z3_get_app_num_args(Z3_context c, Z3_app a) { + LOG_Z3_get_app_num_args(c, a); + RESET_ERROR_CODE(); + return to_app(a)->get_num_args(); + } + + Z3_ast Z3_API Z3_get_app_arg(Z3_context c, Z3_app a, unsigned i) { + LOG_Z3_get_app_arg(c, a, i); + RESET_ERROR_CODE(); + if (!is_app(reinterpret_cast(a))) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + if (i >= to_app(a)->get_num_args()) { + SET_ERROR_CODE(Z3_IOB); + RETURN_Z3(0); + } + RETURN_Z3(of_ast(to_app(a)->get_arg(i))); + } + + Z3_symbol Z3_API Z3_get_decl_name(Z3_context c, Z3_func_decl d) { + LOG_Z3_get_decl_name(c, d); + RESET_ERROR_CODE(); + return of_symbol(to_func_decl(d)->get_name()); + } + + unsigned Z3_API Z3_get_decl_num_parameters(Z3_context c, Z3_func_decl d) { + LOG_Z3_get_decl_num_parameters(c, d); + RESET_ERROR_CODE(); + return to_func_decl(d)->get_num_parameters(); + } + + Z3_parameter_kind Z3_API Z3_get_decl_parameter_kind(Z3_context c, Z3_func_decl d, unsigned idx) { + Z3_TRY; + LOG_Z3_get_decl_parameter_kind(c, d, idx); + RESET_ERROR_CODE(); + if (idx >= to_func_decl(d)->get_num_parameters()) { + SET_ERROR_CODE(Z3_IOB); + return Z3_PARAMETER_INT; + } + parameter const& p = to_func_decl(d)->get_parameters()[idx]; + if (p.is_int()) { + return Z3_PARAMETER_INT; + } + if (p.is_double()) { + return Z3_PARAMETER_DOUBLE; + } + if (p.is_symbol()) { + return Z3_PARAMETER_SYMBOL; + } + if (p.is_rational()) { + return Z3_PARAMETER_RATIONAL; + } + if (p.is_ast() && is_sort(p.get_ast())) { + return Z3_PARAMETER_SORT; + } + if (p.is_ast() && is_expr(p.get_ast())) { + return Z3_PARAMETER_AST; + } + SASSERT(p.is_ast() && is_func_decl(p.get_ast())); + return Z3_PARAMETER_FUNC_DECL; + Z3_CATCH_RETURN(Z3_PARAMETER_INT); + } + + int Z3_API Z3_get_decl_int_parameter(Z3_context c, Z3_func_decl d, unsigned idx) { + Z3_TRY; + LOG_Z3_get_decl_int_parameter(c, d, idx); + RESET_ERROR_CODE(); + if (idx >= to_func_decl(d)->get_num_parameters()) { + SET_ERROR_CODE(Z3_IOB); + return 0; + } + parameter const& p = to_func_decl(d)->get_parameters()[idx]; + if (!p.is_int()) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return 0; + } + return p.get_int(); + Z3_CATCH_RETURN(0); + } + + double Z3_API Z3_get_decl_double_parameter(Z3_context c, Z3_func_decl d, unsigned idx) { + Z3_TRY; + LOG_Z3_get_decl_double_parameter(c, d, idx); + RESET_ERROR_CODE(); + if (idx >= to_func_decl(d)->get_num_parameters()) { + SET_ERROR_CODE(Z3_IOB); + return 0; + } + parameter const& p = to_func_decl(d)->get_parameters()[idx]; + if (!p.is_double()) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return 0; + } + return p.get_double(); + Z3_CATCH_RETURN(0.0); + } + + Z3_symbol Z3_API Z3_get_decl_symbol_parameter(Z3_context c, Z3_func_decl d, unsigned idx) { + Z3_TRY; + LOG_Z3_get_decl_symbol_parameter(c, d, idx); + RESET_ERROR_CODE(); + if (idx >= to_func_decl(d)->get_num_parameters()) { + SET_ERROR_CODE(Z3_IOB); + return 0; + } + parameter const& p = to_func_decl(d)->get_parameters()[idx]; + if (!p.is_symbol()) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return 0; + } + return of_symbol(p.get_symbol()); + Z3_CATCH_RETURN(0); + } + + Z3_sort Z3_API Z3_get_decl_sort_parameter(Z3_context c, Z3_func_decl d, unsigned idx) { + Z3_TRY; + LOG_Z3_get_decl_sort_parameter(c, d, idx); + RESET_ERROR_CODE(); + if (idx >= to_func_decl(d)->get_num_parameters()) { + SET_ERROR_CODE(Z3_IOB); + RETURN_Z3(0); + } + parameter const& p = to_func_decl(d)->get_parameters()[idx]; + if (!p.is_ast() || !is_sort(p.get_ast())) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + RETURN_Z3(of_sort(to_sort(p.get_ast()))); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_get_decl_ast_parameter(Z3_context c, Z3_func_decl d, unsigned idx) { + Z3_TRY; + LOG_Z3_get_decl_ast_parameter(c, d, idx); + RESET_ERROR_CODE(); + if (idx >= to_func_decl(d)->get_num_parameters()) { + SET_ERROR_CODE(Z3_IOB); + RETURN_Z3(0); + } + parameter const& p = to_func_decl(d)->get_parameters()[idx]; + if (!p.is_ast()) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + RETURN_Z3(of_ast(p.get_ast())); + Z3_CATCH_RETURN(0); + } + + Z3_func_decl Z3_API Z3_get_decl_func_decl_parameter(Z3_context c, Z3_func_decl d, unsigned idx) { + Z3_TRY; + LOG_Z3_get_decl_func_decl_parameter(c, d, idx); + RESET_ERROR_CODE(); + if (idx >= to_func_decl(d)->get_num_parameters()) { + SET_ERROR_CODE(Z3_IOB); + RETURN_Z3(0); + } + parameter const& p = to_func_decl(d)->get_parameters()[idx]; + if (!p.is_ast() || !is_func_decl(p.get_ast())) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + RETURN_Z3(of_func_decl(to_func_decl(p.get_ast()))); + Z3_CATCH_RETURN(0); + } + + Z3_string Z3_API Z3_get_decl_rational_parameter(Z3_context c, Z3_func_decl d, unsigned idx) { + Z3_TRY; + LOG_Z3_get_decl_rational_parameter(c, d, idx); + RESET_ERROR_CODE(); + if (idx >= to_func_decl(d)->get_num_parameters()) { + SET_ERROR_CODE(Z3_IOB); + return ""; + } + parameter const& p = to_func_decl(d)->get_parameters()[idx]; + if (!p.is_rational()) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return ""; + } + return mk_c(c)->mk_external_string(p.get_rational().to_string()); + Z3_CATCH_RETURN(""); + } + + + Z3_symbol Z3_API Z3_get_sort_name(Z3_context c, Z3_sort t) { + Z3_TRY; + LOG_Z3_get_sort_name(c, t); + RESET_ERROR_CODE(); + return of_symbol(to_sort(t)->get_name()); + Z3_CATCH_RETURN(0); + } + + Z3_sort Z3_API Z3_get_sort(Z3_context c, Z3_ast a) { + Z3_TRY; + LOG_Z3_get_sort(c, a); + RESET_ERROR_CODE(); + Z3_sort r = of_sort(mk_c(c)->m().get_sort(to_expr(a))); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + unsigned Z3_API Z3_get_arity(Z3_context c, Z3_func_decl d) { + Z3_TRY; + LOG_Z3_get_arity(c, d); + RESET_ERROR_CODE(); + return to_func_decl(d)->get_arity(); + Z3_CATCH_RETURN(0); + } + + unsigned Z3_API Z3_get_domain_size(Z3_context c, Z3_func_decl d) { + Z3_TRY; + LOG_Z3_get_domain_size(c, d); + RESET_ERROR_CODE(); + return to_func_decl(d)->get_arity(); + Z3_CATCH_RETURN(0); + } + + Z3_sort Z3_API Z3_get_domain(Z3_context c, Z3_func_decl d, unsigned i) { + Z3_TRY; + LOG_Z3_get_domain(c, d, i); + RESET_ERROR_CODE(); + if (i >= to_func_decl(d)->get_arity()) { + SET_ERROR_CODE(Z3_IOB); + RETURN_Z3(0); + } + Z3_sort r = of_sort(to_func_decl(d)->get_domain(i)); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_sort Z3_API Z3_get_range(Z3_context c, Z3_func_decl d) { + Z3_TRY; + LOG_Z3_get_range(c, d); + RESET_ERROR_CODE(); + CHECK_VALID_AST(d, 0); + Z3_sort r = of_sort(to_func_decl(d)->get_range()); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_sort_kind Z3_get_sort_kind(Z3_context c, Z3_sort t) { + LOG_Z3_get_sort_kind(c, t); + RESET_ERROR_CODE(); + CHECK_VALID_AST(t, Z3_UNKNOWN_SORT); + family_id fid = to_sort(t)->get_family_id(); + decl_kind k = to_sort(t)->get_decl_kind(); + if (fid == null_family_id) { + return Z3_UNINTERPRETED_SORT; + } + else if (fid == mk_c(c)->m().get_basic_family_id() && k == BOOL_SORT) { + return Z3_BOOL_SORT; + } + else if (fid == mk_c(c)->get_arith_fid() && k == INT_SORT) { + return Z3_INT_SORT; + } + else if (fid == mk_c(c)->get_arith_fid() && k == REAL_SORT) { + return Z3_REAL_SORT; + } + else if (fid == mk_c(c)->get_bv_fid() && k == BV_SORT) { + return Z3_BV_SORT; + } + else if (fid == mk_c(c)->get_array_fid() && k == ARRAY_SORT) { + return Z3_ARRAY_SORT; + } + else if (fid == mk_c(c)->get_dt_fid() && k == DATATYPE_SORT) { + return Z3_DATATYPE_SORT; + } + else if (fid == mk_c(c)->get_datalog_fid() && k == datalog::DL_RELATION_SORT) { + return Z3_RELATION_SORT; + } + else if (fid == mk_c(c)->get_datalog_fid() && k == datalog::DL_FINITE_SORT) { + return Z3_FINITE_DOMAIN_SORT; + } + else { + return Z3_UNKNOWN_SORT; + } + } + + Z3_lbool Z3_API Z3_get_bool_value(Z3_context c, Z3_ast a) { + Z3_TRY; + LOG_Z3_get_bool_value(c, a); + RESET_ERROR_CODE(); + ast_manager & m = mk_c(c)->m(); + ast * n = to_ast(a); + if (!is_expr(n)) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return Z3_L_UNDEF; + } + if (m.is_true(to_expr(n))) + return Z3_L_TRUE; + if (m.is_false(to_expr(n))) + return Z3_L_FALSE; + return Z3_L_UNDEF; + Z3_CATCH_RETURN(Z3_L_UNDEF); + } + + static Z3_ast simplify(Z3_context c, Z3_ast _a, Z3_params _p) { + Z3_TRY; + RESET_ERROR_CODE(); + ast_manager & m = mk_c(c)->m(); + expr * a = to_expr(_a); + params_ref p = to_param_ref(_p); + unsigned timeout = p.get_uint(":timeout", UINT_MAX); + bool use_ctrl_c = p.get_bool(":ctrl-c", false); + th_rewriter m_rw(m, p); + expr_ref result(m); + cancel_eh eh(m_rw); + api::context::set_interruptable(*(mk_c(c)), eh); + { + scoped_ctrl_c ctrlc(eh, false, use_ctrl_c); + scoped_timer timer(timeout, &eh); + try { + m_rw(a, result); + } + catch (z3_exception & ex) { + mk_c(c)->handle_exception(ex); + return 0; + } + } + mk_c(c)->save_ast_trail(result); + return of_ast(result.get()); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_simplify(Z3_context c, Z3_ast _a) { + LOG_Z3_simplify(c, _a); + RETURN_Z3(simplify(c, _a, 0)); + } + + Z3_ast Z3_API Z3_simplify_ex(Z3_context c, Z3_ast _a, Z3_params p) { + LOG_Z3_simplify_ex(c, _a, p); + RETURN_Z3(simplify(c, _a, p)); + } + + Z3_string Z3_API Z3_simplify_get_help(Z3_context c) { + Z3_TRY; + LOG_Z3_simplify_get_help(c); + RESET_ERROR_CODE(); + std::ostringstream buffer; + param_descrs descrs; + th_rewriter::get_param_descrs(descrs); + descrs.display(buffer); + return mk_c(c)->mk_external_string(buffer.str()); + Z3_CATCH_RETURN(""); + } + + Z3_param_descrs Z3_API Z3_simplify_get_param_descrs(Z3_context c) { + Z3_TRY; + LOG_Z3_simplify_get_param_descrs(c); + RESET_ERROR_CODE(); + Z3_param_descrs_ref * d = alloc(Z3_param_descrs_ref); + mk_c(c)->save_object(d); + th_rewriter::get_param_descrs(d->m_descrs); + Z3_param_descrs r = of_param_descrs(d); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_update_term(Z3_context c, Z3_ast _a, unsigned num_args, Z3_ast const _args[]) { + Z3_TRY; + LOG_Z3_update_term(c, _a, num_args, _args); + RESET_ERROR_CODE(); + ast_manager& m = mk_c(c)->m(); + expr* a = to_expr(_a); + expr* const* args = to_exprs(_args); + switch(a->get_kind()) { + case AST_APP: { + app* e = to_app(a); + if (e->get_num_args() != num_args) { + SET_ERROR_CODE(Z3_IOB); + } + else { + a = m.mk_app(e->get_decl(), num_args, args); + } + break; + } + case AST_QUANTIFIER: { + if (num_args != 1) { + SET_ERROR_CODE(Z3_IOB); + } + else { + a = m.update_quantifier(to_quantifier(a), args[0]); + } + break; + } + default: + break; + } + mk_c(c)->save_ast_trail(a); + RETURN_Z3(of_expr(a)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_substitute(Z3_context c, + Z3_ast _a, + unsigned num_exprs, + Z3_ast const _from[], + Z3_ast const _to[]) { + Z3_TRY; + LOG_Z3_substitute(c, _a, num_exprs, _from, _to); + RESET_ERROR_CODE(); + ast_manager & m = mk_c(c)->m(); + expr * a = to_expr(_a); + expr * const * from = to_exprs(_from); + expr * const * to = to_exprs(_to); + expr * r = 0; + for (unsigned i = 0; i < num_exprs; i++) { + if (m.get_sort(from[i]) != m.get_sort(to[i])) { + SET_ERROR_CODE(Z3_SORT_ERROR); + RETURN_Z3(of_expr(0)); + } + } + + expr_substitution subst(m); + for (unsigned i = 0; i < num_exprs; i++) { + subst.insert(from[i], to[i]); + } + th_rewriter m_rw(m); + m_rw.set_substitution(&subst); + + expr_ref new_a(m); + proof_ref pr(m); + m_rw(a, new_a, pr); + mk_c(c)->save_ast_trail(new_a); + r = new_a.get(); + RETURN_Z3(of_expr(r)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_substitute_vars(Z3_context c, + Z3_ast _a, + unsigned num_exprs, + Z3_ast const _to[]) { + Z3_TRY; + LOG_Z3_substitute_vars(c, _a, num_exprs, _to); + RESET_ERROR_CODE(); + ast_manager & m = mk_c(c)->m(); + expr * a = to_expr(_a); + expr * const * to = to_exprs(_to); + var_subst subst(m, false); + expr_ref new_a(m); + subst(a, num_exprs, to, new_a); + mk_c(c)->save_ast_trail(new_a); + RETURN_Z3(of_expr(new_a.get())); + Z3_CATCH_RETURN(0); + } + + char const * Z3_API Z3_ast_to_string(Z3_context c, Z3_ast a) { + Z3_TRY; + LOG_Z3_ast_to_string(c, a); + RESET_ERROR_CODE(); + std::ostringstream buffer; + switch (mk_c(c)->get_print_mode()) { + case Z3_PRINT_SMTLIB_FULL: + buffer << mk_pp(to_ast(a), mk_c(c)->m()); + break; + case Z3_PRINT_LOW_LEVEL: + buffer << mk_ll_pp(to_ast(a), mk_c(c)->m()); + break; + case Z3_PRINT_SMTLIB_COMPLIANT: { + ast_smt_pp pp(mk_c(c)->m()); + pp.set_simplify_implies(get_pp_default_params().m_pp_simplify_implies); + ast* a1 = to_ast(a); + pp.set_logic(mk_c(c)->fparams().m_smtlib_logic.c_str()); + if (!is_expr(a1)) { + buffer << mk_pp(a1, mk_c(c)->m()); + break; + } + if (mk_c(c)->get_print_mode() == Z3_PRINT_SMTLIB_COMPLIANT) { + pp.display_expr(buffer, to_expr(a1)); + break; + } + if (mk_c(c)->get_print_mode() == Z3_PRINT_SMTLIB2_COMPLIANT) { + pp.display_expr_smt2(buffer, to_expr(a1)); + break; + } + break; + } + case Z3_PRINT_SMTLIB2_COMPLIANT: { + buffer << mk_ismt2_pp(to_ast(a), mk_c(c)->m()); + break; + } + default: + UNREACHABLE(); + } + return mk_c(c)->mk_external_string(buffer.str()); + Z3_CATCH_RETURN(0); + } + + char const * Z3_API Z3_sort_to_string(Z3_context c, Z3_sort s) { + return Z3_ast_to_string(c, reinterpret_cast(s)); + } + + char const * Z3_API Z3_func_decl_to_string(Z3_context c, Z3_func_decl f) { + return Z3_ast_to_string(c, reinterpret_cast(f)); + } + + Z3_string Z3_API Z3_benchmark_to_smtlib_string(Z3_context c, + Z3_string name, + Z3_string logic, + Z3_string status, + Z3_string attributes, + unsigned num_assumptions, + Z3_ast const assumptions[], + Z3_ast formula) { + Z3_TRY; + LOG_Z3_benchmark_to_smtlib_string(c, name, logic, status, attributes, num_assumptions, assumptions, formula); + RESET_ERROR_CODE(); + std::ostringstream buffer; + ast_smt_pp pp(mk_c(c)->m()); + pp.set_benchmark_name(name); + pp.set_logic(logic); + pp.set_status(status); + pp.add_attributes(attributes); + pp.set_simplify_implies(get_pp_default_params().m_pp_simplify_implies); + for (unsigned i = 0; i < num_assumptions; ++i) { + pp.add_assumption(to_expr(assumptions[i])); + } + if (mk_c(c)->get_print_mode() == Z3_PRINT_SMTLIB2_COMPLIANT) { + pp.display_smt2(buffer, to_expr(formula)); + } + else { + pp.display(buffer, to_expr(formula)); + } + return mk_c(c)->mk_external_string(buffer.str()); + Z3_CATCH_RETURN(""); + } + + Z3_decl_kind Z3_API Z3_get_decl_kind(Z3_context c, Z3_func_decl d) { + Z3_TRY; + LOG_Z3_get_decl_kind(c, d); + RESET_ERROR_CODE(); + func_decl* _d = to_func_decl(d); + + if (null_family_id == _d->get_family_id()) { + return Z3_OP_UNINTERPRETED; + } + if (mk_c(c)->get_basic_fid() == _d->get_family_id()) { + switch(_d->get_decl_kind()) { + case OP_TRUE: return Z3_OP_TRUE; + case OP_FALSE: return Z3_OP_FALSE; + case OP_EQ: return Z3_OP_EQ; + case OP_DISTINCT: return Z3_OP_DISTINCT; + case OP_ITE: return Z3_OP_ITE; + case OP_AND: return Z3_OP_AND; + case OP_OR: return Z3_OP_OR; + case OP_IFF: return Z3_OP_IFF; + case OP_XOR: return Z3_OP_XOR; + case OP_NOT: return Z3_OP_NOT; + case OP_IMPLIES: return Z3_OP_IMPLIES; + case OP_OEQ: return Z3_OP_OEQ; + + case PR_UNDEF: return Z3_OP_PR_UNDEF; + case PR_TRUE: return Z3_OP_PR_TRUE; + case PR_ASSERTED: return Z3_OP_PR_ASSERTED; + case PR_GOAL: return Z3_OP_PR_GOAL; + case PR_MODUS_PONENS: return Z3_OP_PR_MODUS_PONENS; + case PR_REFLEXIVITY: return Z3_OP_PR_REFLEXIVITY; + case PR_SYMMETRY: return Z3_OP_PR_SYMMETRY; + case PR_TRANSITIVITY: return Z3_OP_PR_TRANSITIVITY; + case PR_TRANSITIVITY_STAR: return Z3_OP_PR_TRANSITIVITY_STAR; + case PR_MONOTONICITY: return Z3_OP_PR_MONOTONICITY; + case PR_QUANT_INTRO: return Z3_OP_PR_QUANT_INTRO; + case PR_DISTRIBUTIVITY: return Z3_OP_PR_DISTRIBUTIVITY; + case PR_AND_ELIM: return Z3_OP_PR_AND_ELIM; + case PR_NOT_OR_ELIM: return Z3_OP_PR_NOT_OR_ELIM; + case PR_REWRITE: return Z3_OP_PR_REWRITE; + case PR_REWRITE_STAR: return Z3_OP_PR_REWRITE_STAR; + case PR_PULL_QUANT: return Z3_OP_PR_PULL_QUANT; + case PR_PULL_QUANT_STAR: return Z3_OP_PR_PULL_QUANT_STAR; + case PR_PUSH_QUANT: return Z3_OP_PR_PUSH_QUANT; + case PR_ELIM_UNUSED_VARS: return Z3_OP_PR_ELIM_UNUSED_VARS; + case PR_DER: return Z3_OP_PR_DER; + case PR_QUANT_INST: return Z3_OP_PR_QUANT_INST; + case PR_HYPOTHESIS: return Z3_OP_PR_HYPOTHESIS; + case PR_LEMMA: return Z3_OP_PR_LEMMA; + case PR_UNIT_RESOLUTION: return Z3_OP_PR_UNIT_RESOLUTION; + case PR_IFF_TRUE: return Z3_OP_PR_IFF_TRUE; + case PR_IFF_FALSE: return Z3_OP_PR_IFF_FALSE; + case PR_COMMUTATIVITY: return Z3_OP_PR_COMMUTATIVITY; + case PR_DEF_AXIOM: return Z3_OP_PR_DEF_AXIOM; + case PR_DEF_INTRO: return Z3_OP_PR_DEF_INTRO; + case PR_APPLY_DEF: return Z3_OP_PR_APPLY_DEF; + case PR_IFF_OEQ: return Z3_OP_PR_IFF_OEQ; + case PR_NNF_POS: return Z3_OP_PR_NNF_POS; + case PR_NNF_NEG: return Z3_OP_PR_NNF_NEG; + case PR_NNF_STAR: return Z3_OP_PR_NNF_STAR; + case PR_SKOLEMIZE: return Z3_OP_PR_SKOLEMIZE; + case PR_CNF_STAR: return Z3_OP_PR_CNF_STAR; + case PR_MODUS_PONENS_OEQ: return Z3_OP_PR_MODUS_PONENS_OEQ; + case PR_TH_LEMMA: return Z3_OP_PR_TH_LEMMA; + + default: + UNREACHABLE(); + return Z3_OP_UNINTERPRETED; + } + } + if (mk_c(c)->get_arith_fid() == _d->get_family_id()) { + switch(_d->get_decl_kind()) { + case OP_NUM: return Z3_OP_ANUM; + case OP_IRRATIONAL_ALGEBRAIC_NUM: return Z3_OP_AGNUM; + case OP_LE: return Z3_OP_LE; + case OP_GE: return Z3_OP_GE; + case OP_LT: return Z3_OP_LT; + case OP_GT: return Z3_OP_GT; + case OP_ADD: return Z3_OP_ADD; + case OP_SUB: return Z3_OP_SUB; + case OP_UMINUS: return Z3_OP_UMINUS; + case OP_MUL: return Z3_OP_MUL; + case OP_DIV: return Z3_OP_DIV; + case OP_IDIV: return Z3_OP_IDIV; + case OP_REM: return Z3_OP_REM; + case OP_MOD: return Z3_OP_MOD; + case OP_POWER: return Z3_OP_POWER; + case OP_TO_REAL: return Z3_OP_TO_REAL; + case OP_TO_INT: return Z3_OP_TO_INT; + case OP_IS_INT: return Z3_OP_IS_INT; + default: + UNREACHABLE(); + return Z3_OP_UNINTERPRETED; + } + } + if (mk_c(c)->get_array_fid() == _d->get_family_id()) { + switch(_d->get_decl_kind()) { + case OP_STORE: return Z3_OP_STORE; + case OP_SELECT: return Z3_OP_SELECT; + case OP_CONST_ARRAY: return Z3_OP_CONST_ARRAY; + case OP_ARRAY_DEFAULT: return Z3_OP_ARRAY_DEFAULT; + case OP_ARRAY_MAP: return Z3_OP_ARRAY_MAP; + case OP_SET_UNION: return Z3_OP_SET_UNION; + case OP_SET_INTERSECT: return Z3_OP_SET_INTERSECT; + case OP_SET_DIFFERENCE: return Z3_OP_SET_DIFFERENCE; + case OP_SET_COMPLEMENT: return Z3_OP_SET_COMPLEMENT; + case OP_SET_SUBSET: return Z3_OP_SET_SUBSET; + case OP_AS_ARRAY: return Z3_OP_AS_ARRAY; + default: + UNREACHABLE(); + return Z3_OP_UNINTERPRETED; + } + } + + if (mk_c(c)->get_bv_fid() == _d->get_family_id()) { + switch(_d->get_decl_kind()) { + case OP_BV_NUM: return Z3_OP_BNUM; + case OP_BIT1: return Z3_OP_BIT1; + case OP_BIT0: return Z3_OP_BIT0; + case OP_BNEG: return Z3_OP_BNEG; + case OP_BADD: return Z3_OP_BADD; + case OP_BSUB: return Z3_OP_BSUB; + case OP_BMUL: return Z3_OP_BMUL; + case OP_BSDIV: return Z3_OP_BSDIV; + case OP_BUDIV: return Z3_OP_BUDIV; + case OP_BSREM: return Z3_OP_BSREM; + case OP_BUREM: return Z3_OP_BUREM; + 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_BUREM0: return Z3_OP_BUREM0; + case OP_BSMOD0: return Z3_OP_BSMOD0; + case OP_ULEQ: return Z3_OP_ULEQ; + case OP_SLEQ: return Z3_OP_SLEQ; + case OP_UGEQ: return Z3_OP_UGEQ; + case OP_SGEQ: return Z3_OP_SGEQ; + case OP_ULT: return Z3_OP_ULT; + case OP_SLT: return Z3_OP_SLT; + case OP_UGT: return Z3_OP_UGT; + case OP_SGT: return Z3_OP_SGT; + case OP_BAND: return Z3_OP_BAND; + case OP_BOR: return Z3_OP_BOR; + case OP_BNOT: return Z3_OP_BNOT; + case OP_BXOR: return Z3_OP_BXOR; + case OP_BNAND: return Z3_OP_BNAND; + case OP_BNOR: return Z3_OP_BNOR; + case OP_BXNOR: return Z3_OP_BXNOR; + case OP_CONCAT: return Z3_OP_CONCAT; + case OP_SIGN_EXT: return Z3_OP_SIGN_EXT; + case OP_ZERO_EXT: return Z3_OP_ZERO_EXT; + case OP_EXTRACT: return Z3_OP_EXTRACT; + case OP_REPEAT: return Z3_OP_REPEAT; + case OP_BREDOR: return Z3_OP_BREDOR; + case OP_BREDAND: return Z3_OP_BREDAND; + case OP_BCOMP: return Z3_OP_BCOMP; + case OP_BSHL: return Z3_OP_BSHL; + case OP_BLSHR: return Z3_OP_BLSHR; + case OP_BASHR: return Z3_OP_BASHR; + case OP_ROTATE_LEFT: return Z3_OP_ROTATE_LEFT; + case OP_ROTATE_RIGHT: return Z3_OP_ROTATE_RIGHT; + case OP_EXT_ROTATE_LEFT: return Z3_OP_EXT_ROTATE_LEFT; + case OP_EXT_ROTATE_RIGHT: return Z3_OP_EXT_ROTATE_RIGHT; + case OP_INT2BV: return Z3_OP_INT2BV; + case OP_BV2INT: return Z3_OP_BV2INT; + case OP_CARRY: return Z3_OP_CARRY; + case OP_XOR3: return Z3_OP_XOR3; + default: + UNREACHABLE(); + return Z3_OP_UNINTERPRETED; + } + } + if (mk_c(c)->get_dt_fid() == _d->get_family_id()) { + switch(_d->get_decl_kind()) { + case OP_DT_CONSTRUCTOR: return Z3_OP_DT_CONSTRUCTOR; + case OP_DT_RECOGNISER: return Z3_OP_DT_RECOGNISER; + case OP_DT_ACCESSOR: return Z3_OP_DT_ACCESSOR; + default: + UNREACHABLE(); + return Z3_OP_UNINTERPRETED; + } + } + if (mk_c(c)->get_datalog_fid() == _d->get_family_id()) { + switch(_d->get_decl_kind()) { + case datalog::OP_RA_STORE: return Z3_OP_RA_STORE; + case datalog::OP_RA_EMPTY: return Z3_OP_RA_EMPTY; + case datalog::OP_RA_IS_EMPTY: return Z3_OP_RA_IS_EMPTY; + case datalog::OP_RA_JOIN: return Z3_OP_RA_JOIN; + case datalog::OP_RA_UNION: return Z3_OP_RA_UNION; + case datalog::OP_RA_WIDEN: return Z3_OP_RA_WIDEN; + case datalog::OP_RA_PROJECT: return Z3_OP_RA_PROJECT; + case datalog::OP_RA_FILTER: return Z3_OP_RA_FILTER; + case datalog::OP_RA_NEGATION_FILTER: return Z3_OP_RA_NEGATION_FILTER; + case datalog::OP_RA_RENAME: return Z3_OP_RA_RENAME; + case datalog::OP_RA_COMPLEMENT: return Z3_OP_RA_COMPLEMENT; + case datalog::OP_RA_SELECT: return Z3_OP_RA_SELECT; + case datalog::OP_RA_CLONE: return Z3_OP_RA_CLONE; + case datalog::OP_DL_LT: return Z3_OP_FD_LT; + default: + UNREACHABLE(); + return Z3_OP_UNINTERPRETED; + } + } + + if (mk_c(c)->m().get_label_family_id() == _d->get_family_id()) { + switch(_d->get_decl_kind()) { + case OP_LABEL: return Z3_OP_LABEL; + case OP_LABEL_LIT: return Z3_OP_LABEL_LIT; + default: + UNREACHABLE(); + return Z3_OP_UNINTERPRETED; + } + } + + return Z3_OP_UNINTERPRETED; + Z3_CATCH_RETURN(Z3_OP_UNINTERPRETED); + } + + unsigned Z3_API Z3_get_index_value(Z3_context c, Z3_ast a) { + Z3_TRY; + LOG_Z3_get_index_value(c, a); + RESET_ERROR_CODE(); + ast* _a = reinterpret_cast(a); + if (!_a || _a->get_kind() != AST_VAR) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return 0; + } + var* va = to_var(_a); + if (va) { + return va->get_idx(); + } + SET_ERROR_CODE(Z3_INVALID_ARG); + return 0; + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_translate(Z3_context c, Z3_ast a, Z3_context target) { + Z3_TRY; + LOG_Z3_translate(c, a, target); + RESET_ERROR_CODE(); + CHECK_VALID_AST(a, 0); + if (c == target) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + SASSERT(mk_c(c)->m().contains(to_ast(a))); + ast_translation translator(mk_c(c)->m(), mk_c(target)->m()); + ast * _result = translator(to_ast(a)); + mk_c(target)->save_ast_trail(_result); + RETURN_Z3(of_ast(_result)); + Z3_CATCH_RETURN(0); + } + +}; diff --git a/lib/api_ast_map.cpp b/lib/api_ast_map.cpp new file mode 100644 index 000000000..d9e08ec3e --- /dev/null +++ b/lib/api_ast_map.cpp @@ -0,0 +1,169 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_ast_map.cpp + +Abstract: + API for creating AST maps + +Author: + + Leonardo de Moura (leonardo) 2012-03-09. + +Revision History: + +--*/ +#include +#include"z3.h" +#include"api_log_macros.h" +#include"api_context.h" +#include"api_ast_map.h" +#include"api_ast_vector.h" +#include"ast_smt2_pp.h" +#include"dec_ref_util.h" + +Z3_ast_map_ref::~Z3_ast_map_ref() { + dec_ref_key_values(m, m_map); +} + +extern "C" { + + Z3_ast_map Z3_API Z3_mk_ast_map(Z3_context c) { + Z3_TRY; + LOG_Z3_mk_ast_map(c); + RESET_ERROR_CODE(); + Z3_ast_map_ref * m = alloc(Z3_ast_map_ref, mk_c(c)->m()); + mk_c(c)->save_object(m); + Z3_ast_map r = of_ast_map(m); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_ast_map_inc_ref(Z3_context c, Z3_ast_map m) { + Z3_TRY; + LOG_Z3_ast_map_inc_ref(c, m); + RESET_ERROR_CODE(); + to_ast_map(m)->inc_ref(); + Z3_CATCH; + } + + void Z3_API Z3_ast_map_dec_ref(Z3_context c, Z3_ast_map m) { + Z3_TRY; + LOG_Z3_ast_map_dec_ref(c, m); + RESET_ERROR_CODE(); + to_ast_map(m)->dec_ref(); + Z3_CATCH; + } + + Z3_bool Z3_API Z3_ast_map_contains(Z3_context c, Z3_ast_map m, Z3_ast k) { + Z3_TRY; + LOG_Z3_ast_map_contains(c, m, k); + RESET_ERROR_CODE(); + return to_ast_map_ref(m).contains(to_ast(k)); + Z3_CATCH_RETURN(Z3_FALSE); + } + + Z3_ast Z3_API Z3_ast_map_find(Z3_context c, Z3_ast_map m, Z3_ast k) { + Z3_TRY; + LOG_Z3_ast_map_find(c, m, k); + RESET_ERROR_CODE(); + obj_map::obj_map_entry * entry = to_ast_map_ref(m).find_core(to_ast(k)); + if (entry == 0) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + else { + ast * r = entry->get_data().m_value; + RETURN_Z3(of_ast(r)); + } + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_ast_map_insert(Z3_context c, Z3_ast_map m, Z3_ast k, Z3_ast v) { + Z3_TRY; + LOG_Z3_ast_map_insert(c, m, k, v); + RESET_ERROR_CODE(); + ast_manager & mng = to_ast_map(m)->m; + obj_map::obj_map_entry * entry = to_ast_map_ref(m).insert_if_not_there2(to_ast(k), 0); + if (entry->get_data().m_value == 0) { + // new entry + mng.inc_ref(to_ast(k)); + mng.inc_ref(to_ast(v)); + entry->get_data().m_value = to_ast(v); + } + else { + // replacing entry + mng.inc_ref(to_ast(v)); + mng.dec_ref(entry->get_data().m_value); + entry->get_data().m_value = to_ast(v); + } + Z3_CATCH; + } + + void Z3_API Z3_ast_map_reset(Z3_context c, Z3_ast_map m) { + Z3_TRY; + LOG_Z3_ast_map_reset(c, m); + RESET_ERROR_CODE(); + dec_ref_key_values(to_ast_map(m)->m, to_ast_map_ref(m)); + SASSERT(to_ast_map_ref(m).empty()); + Z3_CATCH; + } + + void Z3_API Z3_ast_map_erase(Z3_context c, Z3_ast_map m, Z3_ast k) { + Z3_TRY; + LOG_Z3_ast_map_erase(c, m, k); + RESET_ERROR_CODE(); + ast * v = 0; + if (to_ast_map_ref(m).find(to_ast(k), v)) { + to_ast_map_ref(m).erase(to_ast(k)); + ast_manager & mng = to_ast_map(m)->m; + mng.dec_ref(to_ast(k)); + mng.dec_ref(v); + } + Z3_CATCH; + } + + unsigned Z3_API Z3_ast_map_size(Z3_context c, Z3_ast_map m) { + Z3_TRY; + LOG_Z3_ast_map_size(c, m); + RESET_ERROR_CODE(); + return to_ast_map_ref(m).size(); + Z3_CATCH_RETURN(0); + } + + Z3_ast_vector Z3_API Z3_ast_map_keys(Z3_context c, Z3_ast_map m) { + Z3_TRY; + LOG_Z3_ast_map_keys(c, m); + RESET_ERROR_CODE(); + Z3_ast_vector_ref * v = alloc(Z3_ast_vector_ref, to_ast_map(m)->m); + mk_c(c)->save_object(v); + obj_map::iterator it = to_ast_map_ref(m).begin(); + obj_map::iterator end = to_ast_map_ref(m).end(); + for (; it != end; ++it) { + v->m_ast_vector.push_back(it->m_key); + } + Z3_ast_vector r = of_ast_vector(v); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_string Z3_API Z3_ast_map_to_string(Z3_context c, Z3_ast_map m) { + Z3_TRY; + LOG_Z3_ast_map_to_string(c, m); + RESET_ERROR_CODE(); + std::ostringstream buffer; + ast_manager & mng = to_ast_map(m)->m; + buffer << "(ast-map"; + obj_map::iterator it = to_ast_map_ref(m).begin(); + obj_map::iterator end = to_ast_map_ref(m).end(); + for (; it != end; ++it) { + buffer << "\n (" << mk_ismt2_pp(it->m_key, mng, 3) << "\n " << mk_ismt2_pp(it->m_value, mng, 3) << ")"; + } + buffer << ")"; + return mk_c(c)->mk_external_string(buffer.str()); + Z3_CATCH_RETURN(0); + } + +}; diff --git a/lib/api_ast_map.h b/lib/api_ast_map.h new file mode 100644 index 000000000..54bf3432c --- /dev/null +++ b/lib/api_ast_map.h @@ -0,0 +1,35 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_ast_map.h + +Abstract: + API for creating AST maps + +Author: + + Leonardo de Moura (leonardo) 2012-03-09. + +Revision History: + +--*/ +#ifndef _API_AST_MAP_H_ +#define _API_AST_MAP_H_ + +#include"api_util.h" +#include"obj_hashtable.h" + +struct Z3_ast_map_ref : public api::object { + ast_manager & m; + obj_map m_map; + Z3_ast_map_ref(ast_manager & _m):m(_m) {} + virtual ~Z3_ast_map_ref(); +}; + +inline Z3_ast_map_ref * to_ast_map(Z3_ast_map v) { return reinterpret_cast(v); } +inline Z3_ast_map of_ast_map(Z3_ast_map_ref * v) { return reinterpret_cast(v); } +inline obj_map & to_ast_map_ref(Z3_ast_map v) { return to_ast_map(v)->m_map; } + +#endif diff --git a/lib/api_ast_vector.cpp b/lib/api_ast_vector.cpp new file mode 100644 index 000000000..e1d4d78ff --- /dev/null +++ b/lib/api_ast_vector.cpp @@ -0,0 +1,140 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_ast_vector.cpp + +Abstract: + API for creating AST vectors + +Author: + + Leonardo de Moura (leonardo) 2012-03-09. + +Revision History: + +--*/ +#include +#include"z3.h" +#include"api_log_macros.h" +#include"api_context.h" +#include"api_ast_vector.h" +#include"ast_translation.h" +#include"ast_smt2_pp.h" + +extern "C" { + + Z3_ast_vector Z3_API Z3_mk_ast_vector(Z3_context c) { + Z3_TRY; + LOG_Z3_mk_ast_vector(c); + RESET_ERROR_CODE(); + Z3_ast_vector_ref * v = alloc(Z3_ast_vector_ref, mk_c(c)->m()); + mk_c(c)->save_object(v); + Z3_ast_vector r = of_ast_vector(v); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_ast_vector_inc_ref(Z3_context c, Z3_ast_vector v) { + Z3_TRY; + LOG_Z3_ast_vector_inc_ref(c, v); + RESET_ERROR_CODE(); + to_ast_vector(v)->inc_ref(); + Z3_CATCH; + } + + void Z3_API Z3_ast_vector_dec_ref(Z3_context c, Z3_ast_vector v) { + Z3_TRY; + LOG_Z3_ast_vector_dec_ref(c, v); + RESET_ERROR_CODE(); + to_ast_vector(v)->dec_ref(); + Z3_CATCH; + } + + unsigned Z3_API Z3_ast_vector_size(Z3_context c, Z3_ast_vector v) { + Z3_TRY; + LOG_Z3_ast_vector_size(c, v); + RESET_ERROR_CODE(); + return to_ast_vector_ref(v).size(); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_ast_vector_get(Z3_context c, Z3_ast_vector v, unsigned i) { + Z3_TRY; + LOG_Z3_ast_vector_get(c, v, i); + RESET_ERROR_CODE(); + if (i >= to_ast_vector_ref(v).size()) { + SET_ERROR_CODE(Z3_IOB); + RETURN_Z3(0); + } + // Remark: Don't need to invoke save_object. + ast * r = to_ast_vector_ref(v).get(i); + RETURN_Z3(of_ast(r)); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_ast_vector_set(Z3_context c, Z3_ast_vector v, unsigned i, Z3_ast a) { + Z3_TRY; + LOG_Z3_ast_vector_set(c, v, i, a); + RESET_ERROR_CODE(); + if (i >= to_ast_vector_ref(v).size()) { + SET_ERROR_CODE(Z3_IOB); + return; + } + to_ast_vector_ref(v).set(i, to_ast(a)); + Z3_CATCH; + } + + void Z3_API Z3_ast_vector_resize(Z3_context c, Z3_ast_vector v, unsigned n) { + Z3_TRY; + LOG_Z3_ast_vector_resize(c, v, n); + RESET_ERROR_CODE(); + to_ast_vector_ref(v).resize(n); + Z3_CATCH; + } + + void Z3_API Z3_ast_vector_push(Z3_context c, Z3_ast_vector v, Z3_ast a) { + Z3_TRY; + LOG_Z3_ast_vector_push(c, v, a); + RESET_ERROR_CODE(); + to_ast_vector_ref(v).push_back(to_ast(a)); + Z3_CATCH; + } + + Z3_ast_vector Z3_API Z3_ast_vector_translate(Z3_context c, Z3_ast_vector v, Z3_context t) { + Z3_TRY; + LOG_Z3_ast_vector_translate(c, v, t); + RESET_ERROR_CODE(); + if (c == t) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + ast_translation translator(mk_c(c)->m(), mk_c(t)->m()); + Z3_ast_vector_ref * new_v = alloc(Z3_ast_vector_ref, mk_c(t)->m()); + mk_c(t)->save_object(new_v); + unsigned sz = to_ast_vector_ref(v).size(); + for (unsigned i = 0; i < sz; i++) { + ast * new_ast = translator(to_ast_vector_ref(v).get(i)); + new_v->m_ast_vector.push_back(new_ast); + } + RETURN_Z3(of_ast_vector(new_v)); + Z3_CATCH_RETURN(0); + } + + Z3_string Z3_API Z3_ast_vector_to_string(Z3_context c, Z3_ast_vector v) { + Z3_TRY; + LOG_Z3_ast_vector_to_string(c, v); + RESET_ERROR_CODE(); + std::ostringstream buffer; + buffer << "(ast-vector"; + unsigned sz = to_ast_vector_ref(v).size(); + for (unsigned i = 0; i < sz; i++) { + buffer << "\n " << mk_ismt2_pp(to_ast_vector_ref(v).get(i), mk_c(c)->m(), 2); + } + buffer << ")"; + return mk_c(c)->mk_external_string(buffer.str()); + Z3_CATCH_RETURN(0); + } + +}; diff --git a/lib/api_ast_vector.h b/lib/api_ast_vector.h new file mode 100644 index 000000000..5071390a3 --- /dev/null +++ b/lib/api_ast_vector.h @@ -0,0 +1,33 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_ast_vector.h + +Abstract: + API for creating AST vectors + +Author: + + Leonardo de Moura (leonardo) 2012-03-09. + +Revision History: + +--*/ +#ifndef _API_AST_VECTOR_H_ +#define _API_AST_VECTOR_H_ + +#include"api_util.h" + +struct Z3_ast_vector_ref : public api::object { + ast_ref_vector m_ast_vector; + Z3_ast_vector_ref(ast_manager & m):m_ast_vector(m) {} + virtual ~Z3_ast_vector_ref() {} +}; + +inline Z3_ast_vector_ref * to_ast_vector(Z3_ast_vector v) { return reinterpret_cast(v); } +inline Z3_ast_vector of_ast_vector(Z3_ast_vector_ref * v) { return reinterpret_cast(v); } +inline ast_ref_vector & to_ast_vector_ref(Z3_ast_vector v) { return to_ast_vector(v)->m_ast_vector; } + +#endif diff --git a/lib/api_bv.cpp b/lib/api_bv.cpp new file mode 100644 index 000000000..32df30a64 --- /dev/null +++ b/lib/api_bv.cpp @@ -0,0 +1,320 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_bv.cpp + +Abstract: + API for bv theory + +Author: + + Leonardo de Moura (leonardo) 2012-02-29. + +Revision History: + +--*/ +#include +#include"z3.h" +#include"api_log_macros.h" +#include"api_context.h" +#include"api_util.h" +#include"bv_decl_plugin.h" + +extern "C" { + + Z3_sort Z3_API Z3_mk_bv_sort(Z3_context c, unsigned sz) { + Z3_TRY; + LOG_Z3_mk_bv_sort(c, sz); + RESET_ERROR_CODE(); + if (sz == 0) { + SET_ERROR_CODE(Z3_INVALID_ARG); + } + parameter p(sz); + Z3_sort r = of_sort(mk_c(c)->m().mk_sort(mk_c(c)->get_bv_fid(), BV_SORT, 1, &p)); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + +#define MK_BV_UNARY(NAME, OP) MK_UNARY(NAME, mk_c(c)->get_bv_fid(), OP, SKIP) +#define MK_BV_BINARY(NAME, OP) MK_BINARY(NAME, mk_c(c)->get_bv_fid(), OP, SKIP) + + MK_BV_UNARY(Z3_mk_bvnot, OP_BNOT); + MK_BV_UNARY(Z3_mk_bvredand, OP_BREDAND); + MK_BV_UNARY(Z3_mk_bvredor, OP_BREDOR); + MK_BV_BINARY(Z3_mk_bvand, OP_BAND); + MK_BV_BINARY(Z3_mk_bvor, OP_BOR); + MK_BV_BINARY(Z3_mk_bvxor, OP_BXOR); + MK_BV_BINARY(Z3_mk_bvnand, OP_BNAND); + MK_BV_BINARY(Z3_mk_bvnor, OP_BNOR); + MK_BV_BINARY(Z3_mk_bvxnor, OP_BXNOR); + MK_BV_BINARY(Z3_mk_bvadd, OP_BADD); + MK_BV_BINARY(Z3_mk_bvmul, OP_BMUL); + MK_BV_BINARY(Z3_mk_bvudiv, OP_BUDIV); + MK_BV_BINARY(Z3_mk_bvsdiv, OP_BSDIV); + MK_BV_BINARY(Z3_mk_bvurem, OP_BUREM); + MK_BV_BINARY(Z3_mk_bvsrem, OP_BSREM); + MK_BV_BINARY(Z3_mk_bvsmod, OP_BSMOD); + MK_BV_BINARY(Z3_mk_bvule, OP_ULEQ); + MK_BV_BINARY(Z3_mk_bvsle, OP_SLEQ); + MK_BV_BINARY(Z3_mk_bvuge, OP_UGEQ); + MK_BV_BINARY(Z3_mk_bvsge, OP_SGEQ); + MK_BV_BINARY(Z3_mk_bvult, OP_ULT); + MK_BV_BINARY(Z3_mk_bvslt, OP_SLT); + MK_BV_BINARY(Z3_mk_bvugt, OP_UGT); + MK_BV_BINARY(Z3_mk_bvsgt, OP_SGT); + MK_BV_BINARY(Z3_mk_concat, OP_CONCAT); + MK_BV_BINARY(Z3_mk_bvshl, OP_BSHL); + MK_BV_BINARY(Z3_mk_bvlshr, OP_BLSHR); + MK_BV_BINARY(Z3_mk_bvashr, OP_BASHR); + MK_BV_BINARY(Z3_mk_ext_rotate_left, OP_EXT_ROTATE_LEFT); + MK_BV_BINARY(Z3_mk_ext_rotate_right, OP_EXT_ROTATE_RIGHT); + + Z3_ast mk_extract_core(Z3_context c, unsigned high, unsigned low, Z3_ast n) { + expr * _n = to_expr(n); + parameter params[2] = { parameter(high), parameter(low) }; + expr * a = mk_c(c)->m().mk_app(mk_c(c)->get_bv_fid(), OP_EXTRACT, 2, params, 1, &_n); + mk_c(c)->save_ast_trail(a); + check_sorts(c, a); + return of_ast(a); + } + + Z3_ast Z3_API Z3_mk_extract(Z3_context c, unsigned high, unsigned low, Z3_ast n) { + Z3_TRY; + LOG_Z3_mk_extract(c, high, low, n); + RESET_ERROR_CODE(); + Z3_ast r = mk_extract_core(c, high, low, n); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + +#define MK_BV_PUNARY(NAME, OP) \ +Z3_ast Z3_API NAME(Z3_context c, unsigned i, Z3_ast n) { \ + Z3_TRY; \ + LOG_ ## NAME(c, i, n); \ + RESET_ERROR_CODE(); \ + expr * _n = to_expr(n); \ + parameter p(i); \ + ast* a = mk_c(c)->m().mk_app(mk_c(c)->get_bv_fid(), OP, 1, &p, 1, &_n); \ + mk_c(c)->save_ast_trail(a); \ + check_sorts(c, a); \ + RETURN_Z3(of_ast(a)); \ + Z3_CATCH_RETURN(0); \ +} + + MK_BV_PUNARY(Z3_mk_sign_ext, OP_SIGN_EXT); + MK_BV_PUNARY(Z3_mk_zero_ext, OP_ZERO_EXT); + MK_BV_PUNARY(Z3_mk_repeat, OP_REPEAT); + MK_BV_PUNARY(Z3_mk_rotate_left, OP_ROTATE_LEFT); + MK_BV_PUNARY(Z3_mk_rotate_right, OP_ROTATE_RIGHT); + MK_BV_PUNARY(Z3_mk_int2bv, OP_INT2BV); + + Z3_ast Z3_API Z3_mk_bv2int(Z3_context c, Z3_ast n, Z3_bool is_signed) { + Z3_TRY; + LOG_Z3_mk_bv2int(c, n, is_signed); + RESET_ERROR_CODE(); + Z3_sort int_s = Z3_mk_int_sort(c); + if (is_signed) { + Z3_ast r = Z3_mk_bv2int(c, n, false); + Z3_sort s = Z3_get_sort(c, n); + unsigned sz = Z3_get_bv_sort_size(c, s); + rational max_bound = power(rational(2), sz); + Z3_ast bound = Z3_mk_numeral(c, max_bound.to_string().c_str(), int_s); + Z3_ast pred = Z3_mk_bvslt(c, n, Z3_mk_int(c, 0, s)); + // if n <_sigend 0 then r - s^sz else r + Z3_ast args[2] = { r, bound }; + Z3_ast res = Z3_mk_ite(c, pred, Z3_mk_sub(c, 2, args), r); + RETURN_Z3(res); + } + else { + expr * _n = to_expr(n); + parameter p(to_sort(int_s)); + ast* a = mk_c(c)->m().mk_app(mk_c(c)->get_bv_fid(), OP_BV2INT, 1, &p, 1, &_n); + mk_c(c)->save_ast_trail(a); + check_sorts(c, a); + RETURN_Z3(of_ast(a)); + } + Z3_CATCH_RETURN(0); + } + + /** + \brief \mlh mk_bvmsb c s \endmlh + Create a bit-vector of sort \s with 1 in the most significant bit position. + + The sort \s must be a bit-vector sort. + + This function is a shorthand for shl(1, N-1) + where N are the number of bits of \c s. + */ + Z3_ast Z3_API Z3_mk_bvmsb(__in Z3_context c, __in Z3_sort s) { + Z3_TRY; + RESET_ERROR_CODE(); + // Not logging this one, since it is just syntax sugar. + unsigned sz = Z3_get_bv_sort_size(c, s); + if (sz == 0) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return 0; + } + return Z3_mk_bvshl(c, Z3_mk_int64(c, 1, s), Z3_mk_int64(c, sz - 1, s)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_mk_bvsmin(__in Z3_context c, __in Z3_sort s) { + return Z3_mk_bvmsb(c, s); + } + + Z3_ast Z3_mk_bvsmax(__in Z3_context c, __in Z3_sort s) { + return Z3_mk_bvnot(c, Z3_mk_bvmsb(c, s)); + } + + Z3_ast Z3_mk_bvumax(__in Z3_context c, __in Z3_sort s) { + return Z3_mk_int(c, -1, s); + } + + Z3_ast Z3_API Z3_mk_bvadd_no_overflow(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2, Z3_bool is_signed) { + Z3_TRY; + RESET_ERROR_CODE(); + if (is_signed) { + Z3_ast zero = Z3_mk_int(c, 0, Z3_get_sort(c, t1)); + Z3_ast r = Z3_mk_bvadd(c, t1, t2); + Z3_ast args[2] = { Z3_mk_bvslt(c, zero, t1), Z3_mk_bvslt(c, zero, t2) }; + Z3_ast args_pos = Z3_mk_and(c, 2, args); + return Z3_mk_implies(c, args_pos, Z3_mk_bvslt(c, zero, r)); + } + else { + unsigned sz = Z3_get_bv_sort_size(c, Z3_get_sort(c, t1)); + t1 = Z3_mk_zero_ext(c, 1, t1); + t2 = Z3_mk_zero_ext(c, 1, t2); + Z3_ast r = Z3_mk_bvadd(c, t1, t2); + return Z3_mk_eq(c, Z3_mk_extract(c, sz, sz, r), Z3_mk_int(c, 0, Z3_mk_bv_sort(c, 1))); + } + Z3_CATCH_RETURN(0); + } + + // only for signed machine integers + Z3_ast Z3_API Z3_mk_bvadd_no_underflow(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2) { + Z3_TRY; + RESET_ERROR_CODE(); + Z3_ast zero = Z3_mk_int(c, 0, Z3_get_sort(c, t1)); + Z3_ast r = Z3_mk_bvadd(c, t1, t2); + Z3_ast args[2] = { Z3_mk_bvslt(c, t1, zero), Z3_mk_bvslt(c, t2, zero) }; + Z3_ast args_neg = Z3_mk_and(c, 2, args); + return Z3_mk_implies(c, args_neg, Z3_mk_bvslt(c, r, zero)); + Z3_CATCH_RETURN(0); + } + + // only for signed machine integers + Z3_ast Z3_API Z3_mk_bvsub_no_overflow(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2) { + Z3_TRY; + RESET_ERROR_CODE(); + Z3_sort s = Z3_get_sort(c, t2); + Z3_ast minus_t2 = Z3_mk_bvneg(c, t2); + Z3_ast min = Z3_mk_bvsmin(c, s); + return Z3_mk_ite(c, Z3_mk_eq(c, t2, min), + Z3_mk_bvslt(c, t1, Z3_mk_int(c, 0, s)), + Z3_mk_bvadd_no_overflow(c, t1, minus_t2, true)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_bvsub_no_underflow(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2, Z3_bool is_signed) { + Z3_TRY; + RESET_ERROR_CODE(); + if (is_signed) { + Z3_ast zero = Z3_mk_int(c, 0, Z3_get_sort(c, t1)); + if (Z3_get_error_code(c) != Z3_OK) return 0; + Z3_ast minus_t2 = Z3_mk_bvneg(c, t2); + if (Z3_get_error_code(c) != Z3_OK) return 0; + return Z3_mk_implies(c, Z3_mk_bvslt(c, zero, t2), Z3_mk_bvadd_no_underflow(c, t1, minus_t2)); + } + else { + return Z3_mk_bvule(c, t2, t1); + } + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_bvmul_no_overflow(__in Z3_context c, __in Z3_ast n1, __in Z3_ast n2, Z3_bool is_signed) { + LOG_Z3_mk_bvmul_no_overflow(c, n1, n2, is_signed); + RESET_ERROR_CODE(); + if (is_signed) { + MK_BINARY_BODY(Z3_mk_bvmul_no_overflow, mk_c(c)->get_bv_fid(), OP_BSMUL_NO_OVFL, SKIP); + } + else { + MK_BINARY_BODY(Z3_mk_bvmul_no_overflow, mk_c(c)->get_bv_fid(), OP_BUMUL_NO_OVFL, SKIP); + } + } + + // only for signed machine integers + Z3_ast Z3_API Z3_mk_bvmul_no_underflow(__in Z3_context c, __in Z3_ast n1, __in Z3_ast n2) { + LOG_Z3_mk_bvmul_no_underflow(c, n1, n2); + MK_BINARY_BODY(Z3_mk_bvmul_no_underflow, mk_c(c)->get_bv_fid(), OP_BSMUL_NO_UDFL, SKIP); + } + + // only for signed machine integers + Z3_ast Z3_API Z3_mk_bvneg_no_overflow(__in Z3_context c, __in Z3_ast t) { + Z3_TRY; + RESET_ERROR_CODE(); + Z3_ast min = Z3_mk_bvsmin(c, Z3_get_sort(c, t)); + if (Z3_get_error_code(c) != Z3_OK) return 0; + Z3_ast eq = Z3_mk_eq(c, t, min); + if (Z3_get_error_code(c) != Z3_OK) return 0; + return Z3_mk_not(c, eq); + Z3_CATCH_RETURN(0); + } + + // only for signed machine integers + Z3_ast Z3_API Z3_mk_bvsdiv_no_overflow(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2) { + Z3_TRY; + RESET_ERROR_CODE(); + Z3_sort s = Z3_get_sort(c, t1); + if (Z3_get_error_code(c) != Z3_OK) return 0; + Z3_ast min = Z3_mk_bvmsb(c, s); + if (Z3_get_error_code(c) != Z3_OK) return 0; + Z3_ast args[2] = { Z3_mk_eq(c, t1, min), + Z3_mk_eq(c, t2, Z3_mk_int(c, -1, s)) }; + return Z3_mk_not(c, Z3_mk_and(c, 2, args)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_bvsub(Z3_context c, Z3_ast n1, Z3_ast n2) { + Z3_TRY; + LOG_Z3_mk_bvsub(c, n1, n2); + RESET_ERROR_CODE(); + // TODO: Do we really need this pre_simplifier hack? + if (mk_c(c)->fparams().m_pre_simplify_expr) { + Z3_ast m1 = Z3_mk_int(c, -1, Z3_get_sort(c, n2)); + Z3_ast r = Z3_mk_bvadd(c, n1, Z3_mk_bvmul(c, m1, n2)); + RETURN_Z3(r); + } + MK_BINARY_BODY(Z3_mk_bvsub, mk_c(c)->get_bv_fid(), OP_BSUB, SKIP); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_bvneg(Z3_context c, Z3_ast n) { + Z3_TRY; + LOG_Z3_mk_bvneg(c, n); + RESET_ERROR_CODE(); + // TODO: Do we really need this pre_simplifier hack? + if (mk_c(c)->fparams().m_pre_simplify_expr) { + Z3_ast m1 = Z3_mk_int(c, -1, Z3_get_sort(c, n)); + Z3_ast r = Z3_mk_bvmul(c, m1, n); + RETURN_Z3(r); + } + MK_UNARY_BODY(Z3_mk_bvneg, mk_c(c)->get_bv_fid(), OP_BNEG, SKIP); + Z3_CATCH_RETURN(0); + } + + unsigned Z3_API Z3_get_bv_sort_size(Z3_context c, Z3_sort t) { + Z3_TRY; + LOG_Z3_get_bv_sort_size(c, t); + RESET_ERROR_CODE(); + CHECK_VALID_AST(t, 0); + if (to_sort(t)->get_family_id() == mk_c(c)->get_bv_fid() && to_sort(t)->get_decl_kind() == BV_SORT) { + return to_sort(t)->get_parameter(0).get_int(); + } + SET_ERROR_CODE(Z3_INVALID_ARG); + return 0; + Z3_CATCH_RETURN(0); + } + +}; diff --git a/lib/api_commands.cpp b/lib/api_commands.cpp new file mode 100644 index 000000000..e77dd1773 --- /dev/null +++ b/lib/api_commands.cpp @@ -0,0 +1,3478 @@ +// Automatically generated file, generator: api.py +#include"z3.h" +#include"z3_replayer.h" +void Z3_replacer_error_handler(Z3_context ctx, Z3_error_code c) { printf("[REPLAYER ERROR HANDLER]: %s\n", Z3_get_error_msg_ex(ctx, c)); } +void exec_Z3_mk_config(z3_replayer & in) { + Z3_config result = Z3_mk_config( + ); + in.store_result(result); +} +void exec_Z3_del_config(z3_replayer & in) { + Z3_del_config( + reinterpret_cast(in.get_obj(0))); +} +void exec_Z3_set_param_value(z3_replayer & in) { + Z3_set_param_value( + reinterpret_cast(in.get_obj(0)), + in.get_str(1), + in.get_str(2)); +} +void exec_Z3_mk_context(z3_replayer & in) { + Z3_context result = Z3_mk_context( + reinterpret_cast(in.get_obj(0))); + in.store_result(result); + Z3_set_error_handler(result, Z3_replacer_error_handler);} +void exec_Z3_mk_context_rc(z3_replayer & in) { + Z3_context result = Z3_mk_context_rc( + reinterpret_cast(in.get_obj(0))); + in.store_result(result); + Z3_set_error_handler(result, Z3_replacer_error_handler);} +void exec_Z3_set_logic(z3_replayer & in) { + Z3_set_logic( + reinterpret_cast(in.get_obj(0)), + in.get_str(1)); +} +void exec_Z3_del_context(z3_replayer & in) { + Z3_del_context( + reinterpret_cast(in.get_obj(0))); +} +void exec_Z3_inc_ref(z3_replayer & in) { + Z3_inc_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_dec_ref(z3_replayer & in) { + Z3_dec_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_toggle_warning_messages(z3_replayer & in) { + Z3_toggle_warning_messages( + in.get_bool(0)); +} +void exec_Z3_update_param_value(z3_replayer & in) { + Z3_update_param_value( + reinterpret_cast(in.get_obj(0)), + in.get_str(1), + in.get_str(2)); +} +void exec_Z3_get_param_value(z3_replayer & in) { + Z3_get_param_value( + reinterpret_cast(in.get_obj(0)), + in.get_str(1), + in.get_str_addr(2)); +} +void exec_Z3_mk_int_symbol(z3_replayer & in) { + Z3_mk_int_symbol( + reinterpret_cast(in.get_obj(0)), + in.get_int(1)); +} +void exec_Z3_mk_string_symbol(z3_replayer & in) { + Z3_mk_string_symbol( + reinterpret_cast(in.get_obj(0)), + in.get_str(1)); +} +void exec_Z3_is_eq_sort(z3_replayer & in) { + Z3_is_eq_sort( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); +} +void exec_Z3_mk_uninterpreted_sort(z3_replayer & in) { + Z3_sort result = Z3_mk_uninterpreted_sort( + reinterpret_cast(in.get_obj(0)), + in.get_symbol(1)); + in.store_result(result); +} +void exec_Z3_mk_bool_sort(z3_replayer & in) { + Z3_sort result = Z3_mk_bool_sort( + reinterpret_cast(in.get_obj(0))); + in.store_result(result); +} +void exec_Z3_mk_int_sort(z3_replayer & in) { + Z3_sort result = Z3_mk_int_sort( + reinterpret_cast(in.get_obj(0))); + in.store_result(result); +} +void exec_Z3_mk_real_sort(z3_replayer & in) { + Z3_sort result = Z3_mk_real_sort( + reinterpret_cast(in.get_obj(0))); + in.store_result(result); +} +void exec_Z3_mk_bv_sort(z3_replayer & in) { + Z3_sort result = Z3_mk_bv_sort( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1)); + in.store_result(result); +} +void exec_Z3_mk_array_sort(z3_replayer & in) { + Z3_sort result = Z3_mk_array_sort( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_tuple_sort(z3_replayer & in) { + Z3_sort result = Z3_mk_tuple_sort( + reinterpret_cast(in.get_obj(0)), + in.get_symbol(1), + in.get_uint(2), + in.get_symbol_array(3), + reinterpret_cast(in.get_obj_array(4)), + reinterpret_cast(in.get_obj_addr(5)), + reinterpret_cast(in.get_obj_array(6))); + in.store_result(result); +} +void exec_Z3_mk_enumeration_sort(z3_replayer & in) { + Z3_sort result = Z3_mk_enumeration_sort( + reinterpret_cast(in.get_obj(0)), + in.get_symbol(1), + in.get_uint(2), + in.get_symbol_array(3), + reinterpret_cast(in.get_obj_array(4)), + reinterpret_cast(in.get_obj_array(5))); + in.store_result(result); +} +void exec_Z3_mk_list_sort(z3_replayer & in) { + Z3_sort result = Z3_mk_list_sort( + reinterpret_cast(in.get_obj(0)), + in.get_symbol(1), + reinterpret_cast(in.get_obj(2)), + reinterpret_cast(in.get_obj_addr(3)), + reinterpret_cast(in.get_obj_addr(4)), + reinterpret_cast(in.get_obj_addr(5)), + reinterpret_cast(in.get_obj_addr(6)), + reinterpret_cast(in.get_obj_addr(7)), + reinterpret_cast(in.get_obj_addr(8))); + in.store_result(result); +} +void exec_Z3_mk_constructor(z3_replayer & in) { + Z3_constructor result = Z3_mk_constructor( + reinterpret_cast(in.get_obj(0)), + in.get_symbol(1), + in.get_symbol(2), + in.get_uint(3), + in.get_symbol_array(4), + reinterpret_cast(in.get_obj_array(5)), + in.get_uint_array(6)); + in.store_result(result); +} +void exec_Z3_query_constructor(z3_replayer & in) { + Z3_query_constructor( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2), + reinterpret_cast(in.get_obj_addr(3)), + reinterpret_cast(in.get_obj_addr(4)), + reinterpret_cast(in.get_obj_array(5))); +} +void exec_Z3_del_constructor(z3_replayer & in) { + Z3_del_constructor( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_mk_datatype(z3_replayer & in) { + Z3_sort result = Z3_mk_datatype( + reinterpret_cast(in.get_obj(0)), + in.get_symbol(1), + in.get_uint(2), + reinterpret_cast(in.get_obj_array(3))); + in.store_result(result); +} +void exec_Z3_mk_constructor_list(z3_replayer & in) { + Z3_constructor_list result = Z3_mk_constructor_list( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + reinterpret_cast(in.get_obj_array(2))); + in.store_result(result); +} +void exec_Z3_del_constructor_list(z3_replayer & in) { + Z3_del_constructor_list( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_mk_datatypes(z3_replayer & in) { + Z3_mk_datatypes( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + in.get_symbol_array(2), + reinterpret_cast(in.get_obj_array(3)), + reinterpret_cast(in.get_obj_array(4))); +} +void exec_Z3_is_eq_ast(z3_replayer & in) { + Z3_is_eq_ast( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); +} +void exec_Z3_is_eq_func_decl(z3_replayer & in) { + Z3_is_eq_func_decl( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); +} +void exec_Z3_mk_func_decl(z3_replayer & in) { + Z3_func_decl result = Z3_mk_func_decl( + reinterpret_cast(in.get_obj(0)), + in.get_symbol(1), + in.get_uint(2), + reinterpret_cast(in.get_obj_array(3)), + reinterpret_cast(in.get_obj(4))); + in.store_result(result); +} +void exec_Z3_mk_app(z3_replayer & in) { + Z3_ast result = Z3_mk_app( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2), + reinterpret_cast(in.get_obj_array(3))); + in.store_result(result); +} +void exec_Z3_mk_const(z3_replayer & in) { + Z3_ast result = Z3_mk_const( + reinterpret_cast(in.get_obj(0)), + in.get_symbol(1), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_label(z3_replayer & in) { + Z3_ast result = Z3_mk_label( + reinterpret_cast(in.get_obj(0)), + in.get_symbol(1), + in.get_bool(2), + reinterpret_cast(in.get_obj(3))); + in.store_result(result); +} +void exec_Z3_mk_fresh_func_decl(z3_replayer & in) { + Z3_func_decl result = Z3_mk_fresh_func_decl( + reinterpret_cast(in.get_obj(0)), + in.get_str(1), + in.get_uint(2), + reinterpret_cast(in.get_obj_array(3)), + reinterpret_cast(in.get_obj(4))); + in.store_result(result); +} +void exec_Z3_mk_fresh_const(z3_replayer & in) { + Z3_ast result = Z3_mk_fresh_const( + reinterpret_cast(in.get_obj(0)), + in.get_str(1), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_true(z3_replayer & in) { + Z3_ast result = Z3_mk_true( + reinterpret_cast(in.get_obj(0))); + in.store_result(result); +} +void exec_Z3_mk_false(z3_replayer & in) { + Z3_ast result = Z3_mk_false( + reinterpret_cast(in.get_obj(0))); + in.store_result(result); +} +void exec_Z3_mk_eq(z3_replayer & in) { + Z3_ast result = Z3_mk_eq( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_distinct(z3_replayer & in) { + Z3_ast result = Z3_mk_distinct( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + reinterpret_cast(in.get_obj_array(2))); + in.store_result(result); +} +void exec_Z3_mk_not(z3_replayer & in) { + Z3_ast result = Z3_mk_not( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_mk_ite(z3_replayer & in) { + Z3_ast result = Z3_mk_ite( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2)), + reinterpret_cast(in.get_obj(3))); + in.store_result(result); +} +void exec_Z3_mk_iff(z3_replayer & in) { + Z3_ast result = Z3_mk_iff( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_implies(z3_replayer & in) { + Z3_ast result = Z3_mk_implies( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_xor(z3_replayer & in) { + Z3_ast result = Z3_mk_xor( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_and(z3_replayer & in) { + Z3_ast result = Z3_mk_and( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + reinterpret_cast(in.get_obj_array(2))); + in.store_result(result); +} +void exec_Z3_mk_or(z3_replayer & in) { + Z3_ast result = Z3_mk_or( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + reinterpret_cast(in.get_obj_array(2))); + in.store_result(result); +} +void exec_Z3_mk_add(z3_replayer & in) { + Z3_ast result = Z3_mk_add( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + reinterpret_cast(in.get_obj_array(2))); + in.store_result(result); +} +void exec_Z3_mk_mul(z3_replayer & in) { + Z3_ast result = Z3_mk_mul( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + reinterpret_cast(in.get_obj_array(2))); + in.store_result(result); +} +void exec_Z3_mk_sub(z3_replayer & in) { + Z3_ast result = Z3_mk_sub( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + reinterpret_cast(in.get_obj_array(2))); + in.store_result(result); +} +void exec_Z3_mk_unary_minus(z3_replayer & in) { + Z3_ast result = Z3_mk_unary_minus( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_mk_div(z3_replayer & in) { + Z3_ast result = Z3_mk_div( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_mod(z3_replayer & in) { + Z3_ast result = Z3_mk_mod( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_rem(z3_replayer & in) { + Z3_ast result = Z3_mk_rem( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_power(z3_replayer & in) { + Z3_ast result = Z3_mk_power( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_is_algebraic_number(z3_replayer & in) { + Z3_is_algebraic_number( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_algebraic_number_lower(z3_replayer & in) { + Z3_ast result = Z3_get_algebraic_number_lower( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_get_algebraic_number_upper(z3_replayer & in) { + Z3_ast result = Z3_get_algebraic_number_upper( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_mk_lt(z3_replayer & in) { + Z3_ast result = Z3_mk_lt( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_le(z3_replayer & in) { + Z3_ast result = Z3_mk_le( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_gt(z3_replayer & in) { + Z3_ast result = Z3_mk_gt( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_ge(z3_replayer & in) { + Z3_ast result = Z3_mk_ge( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_int2real(z3_replayer & in) { + Z3_ast result = Z3_mk_int2real( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_mk_real2int(z3_replayer & in) { + Z3_ast result = Z3_mk_real2int( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_mk_is_int(z3_replayer & in) { + Z3_ast result = Z3_mk_is_int( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_mk_bvnot(z3_replayer & in) { + Z3_ast result = Z3_mk_bvnot( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_mk_bvredand(z3_replayer & in) { + Z3_ast result = Z3_mk_bvredand( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_mk_bvredor(z3_replayer & in) { + Z3_ast result = Z3_mk_bvredor( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_mk_bvand(z3_replayer & in) { + Z3_ast result = Z3_mk_bvand( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvor(z3_replayer & in) { + Z3_ast result = Z3_mk_bvor( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvxor(z3_replayer & in) { + Z3_ast result = Z3_mk_bvxor( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvnand(z3_replayer & in) { + Z3_ast result = Z3_mk_bvnand( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvnor(z3_replayer & in) { + Z3_ast result = Z3_mk_bvnor( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvxnor(z3_replayer & in) { + Z3_ast result = Z3_mk_bvxnor( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvneg(z3_replayer & in) { + Z3_ast result = Z3_mk_bvneg( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_mk_bvadd(z3_replayer & in) { + Z3_ast result = Z3_mk_bvadd( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvsub(z3_replayer & in) { + Z3_ast result = Z3_mk_bvsub( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvmul(z3_replayer & in) { + Z3_ast result = Z3_mk_bvmul( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvudiv(z3_replayer & in) { + Z3_ast result = Z3_mk_bvudiv( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvsdiv(z3_replayer & in) { + Z3_ast result = Z3_mk_bvsdiv( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvurem(z3_replayer & in) { + Z3_ast result = Z3_mk_bvurem( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvsrem(z3_replayer & in) { + Z3_ast result = Z3_mk_bvsrem( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvsmod(z3_replayer & in) { + Z3_ast result = Z3_mk_bvsmod( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvult(z3_replayer & in) { + Z3_ast result = Z3_mk_bvult( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvslt(z3_replayer & in) { + Z3_ast result = Z3_mk_bvslt( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvule(z3_replayer & in) { + Z3_ast result = Z3_mk_bvule( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvsle(z3_replayer & in) { + Z3_ast result = Z3_mk_bvsle( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvuge(z3_replayer & in) { + Z3_ast result = Z3_mk_bvuge( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvsge(z3_replayer & in) { + Z3_ast result = Z3_mk_bvsge( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvugt(z3_replayer & in) { + Z3_ast result = Z3_mk_bvugt( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvsgt(z3_replayer & in) { + Z3_ast result = Z3_mk_bvsgt( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_concat(z3_replayer & in) { + Z3_ast result = Z3_mk_concat( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_extract(z3_replayer & in) { + Z3_ast result = Z3_mk_extract( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + in.get_uint(2), + reinterpret_cast(in.get_obj(3))); + in.store_result(result); +} +void exec_Z3_mk_sign_ext(z3_replayer & in) { + Z3_ast result = Z3_mk_sign_ext( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_zero_ext(z3_replayer & in) { + Z3_ast result = Z3_mk_zero_ext( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_repeat(z3_replayer & in) { + Z3_ast result = Z3_mk_repeat( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvshl(z3_replayer & in) { + Z3_ast result = Z3_mk_bvshl( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvlshr(z3_replayer & in) { + Z3_ast result = Z3_mk_bvlshr( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvashr(z3_replayer & in) { + Z3_ast result = Z3_mk_bvashr( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_rotate_left(z3_replayer & in) { + Z3_ast result = Z3_mk_rotate_left( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_rotate_right(z3_replayer & in) { + Z3_ast result = Z3_mk_rotate_right( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_ext_rotate_left(z3_replayer & in) { + Z3_ast result = Z3_mk_ext_rotate_left( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_ext_rotate_right(z3_replayer & in) { + Z3_ast result = Z3_mk_ext_rotate_right( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_int2bv(z3_replayer & in) { + Z3_ast result = Z3_mk_int2bv( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bv2int(z3_replayer & in) { + Z3_ast result = Z3_mk_bv2int( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_bool(2)); + in.store_result(result); +} +void exec_Z3_mk_bvadd_no_overflow(z3_replayer & in) { + Z3_ast result = Z3_mk_bvadd_no_overflow( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2)), + in.get_bool(3)); + in.store_result(result); +} +void exec_Z3_mk_bvadd_no_underflow(z3_replayer & in) { + Z3_ast result = Z3_mk_bvadd_no_underflow( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvsub_no_overflow(z3_replayer & in) { + Z3_ast result = Z3_mk_bvsub_no_overflow( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvsub_no_underflow(z3_replayer & in) { + Z3_ast result = Z3_mk_bvsub_no_underflow( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2)), + in.get_bool(3)); + in.store_result(result); +} +void exec_Z3_mk_bvsdiv_no_overflow(z3_replayer & in) { + Z3_ast result = Z3_mk_bvsdiv_no_overflow( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_bvneg_no_overflow(z3_replayer & in) { + Z3_ast result = Z3_mk_bvneg_no_overflow( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_mk_bvmul_no_overflow(z3_replayer & in) { + Z3_ast result = Z3_mk_bvmul_no_overflow( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2)), + in.get_bool(3)); + in.store_result(result); +} +void exec_Z3_mk_bvmul_no_underflow(z3_replayer & in) { + Z3_ast result = Z3_mk_bvmul_no_underflow( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_select(z3_replayer & in) { + Z3_ast result = Z3_mk_select( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_store(z3_replayer & in) { + Z3_ast result = Z3_mk_store( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2)), + reinterpret_cast(in.get_obj(3))); + in.store_result(result); +} +void exec_Z3_mk_const_array(z3_replayer & in) { + Z3_ast result = Z3_mk_const_array( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_map(z3_replayer & in) { + Z3_ast result = Z3_mk_map( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2), + reinterpret_cast(in.get_obj_array(3))); + in.store_result(result); +} +void exec_Z3_mk_array_default(z3_replayer & in) { + Z3_ast result = Z3_mk_array_default( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_mk_set_sort(z3_replayer & in) { + Z3_sort result = Z3_mk_set_sort( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_mk_empty_set(z3_replayer & in) { + Z3_ast result = Z3_mk_empty_set( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_mk_full_set(z3_replayer & in) { + Z3_ast result = Z3_mk_full_set( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_mk_set_add(z3_replayer & in) { + Z3_ast result = Z3_mk_set_add( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_set_del(z3_replayer & in) { + Z3_ast result = Z3_mk_set_del( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_set_union(z3_replayer & in) { + Z3_ast result = Z3_mk_set_union( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + reinterpret_cast(in.get_obj_array(2))); + in.store_result(result); +} +void exec_Z3_mk_set_intersect(z3_replayer & in) { + Z3_ast result = Z3_mk_set_intersect( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + reinterpret_cast(in.get_obj_array(2))); + in.store_result(result); +} +void exec_Z3_mk_set_difference(z3_replayer & in) { + Z3_ast result = Z3_mk_set_difference( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_set_complement(z3_replayer & in) { + Z3_ast result = Z3_mk_set_complement( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_mk_set_member(z3_replayer & in) { + Z3_ast result = Z3_mk_set_member( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_set_subset(z3_replayer & in) { + Z3_ast result = Z3_mk_set_subset( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_numeral(z3_replayer & in) { + Z3_ast result = Z3_mk_numeral( + reinterpret_cast(in.get_obj(0)), + in.get_str(1), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_real(z3_replayer & in) { + Z3_ast result = Z3_mk_real( + reinterpret_cast(in.get_obj(0)), + in.get_int(1), + in.get_int(2)); + in.store_result(result); +} +void exec_Z3_mk_int(z3_replayer & in) { + Z3_ast result = Z3_mk_int( + reinterpret_cast(in.get_obj(0)), + in.get_int(1), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_unsigned_int(z3_replayer & in) { + Z3_ast result = Z3_mk_unsigned_int( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_int64(z3_replayer & in) { + Z3_ast result = Z3_mk_int64( + reinterpret_cast(in.get_obj(0)), + in.get_int64(1), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_unsigned_int64(z3_replayer & in) { + Z3_ast result = Z3_mk_unsigned_int64( + reinterpret_cast(in.get_obj(0)), + in.get_uint64(1), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_pattern(z3_replayer & in) { + Z3_pattern result = Z3_mk_pattern( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + reinterpret_cast(in.get_obj_array(2))); + in.store_result(result); +} +void exec_Z3_mk_bound(z3_replayer & in) { + Z3_ast result = Z3_mk_bound( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_forall(z3_replayer & in) { + Z3_ast result = Z3_mk_forall( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + in.get_uint(2), + reinterpret_cast(in.get_obj_array(3)), + in.get_uint(4), + reinterpret_cast(in.get_obj_array(5)), + in.get_symbol_array(6), + reinterpret_cast(in.get_obj(7))); + in.store_result(result); +} +void exec_Z3_mk_exists(z3_replayer & in) { + Z3_ast result = Z3_mk_exists( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + in.get_uint(2), + reinterpret_cast(in.get_obj_array(3)), + in.get_uint(4), + reinterpret_cast(in.get_obj_array(5)), + in.get_symbol_array(6), + reinterpret_cast(in.get_obj(7))); + in.store_result(result); +} +void exec_Z3_mk_quantifier(z3_replayer & in) { + Z3_ast result = Z3_mk_quantifier( + reinterpret_cast(in.get_obj(0)), + in.get_bool(1), + in.get_uint(2), + in.get_uint(3), + reinterpret_cast(in.get_obj_array(4)), + in.get_uint(5), + reinterpret_cast(in.get_obj_array(6)), + in.get_symbol_array(7), + reinterpret_cast(in.get_obj(8))); + in.store_result(result); +} +void exec_Z3_mk_quantifier_ex(z3_replayer & in) { + Z3_ast result = Z3_mk_quantifier_ex( + reinterpret_cast(in.get_obj(0)), + in.get_bool(1), + in.get_uint(2), + in.get_symbol(3), + in.get_symbol(4), + in.get_uint(5), + reinterpret_cast(in.get_obj_array(6)), + in.get_uint(7), + reinterpret_cast(in.get_obj_array(8)), + in.get_uint(9), + reinterpret_cast(in.get_obj_array(10)), + in.get_symbol_array(11), + reinterpret_cast(in.get_obj(12))); + in.store_result(result); +} +void exec_Z3_mk_forall_const(z3_replayer & in) { + Z3_ast result = Z3_mk_forall_const( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + in.get_uint(2), + reinterpret_cast(in.get_obj_array(3)), + in.get_uint(4), + reinterpret_cast(in.get_obj_array(5)), + reinterpret_cast(in.get_obj(6))); + in.store_result(result); +} +void exec_Z3_mk_exists_const(z3_replayer & in) { + Z3_ast result = Z3_mk_exists_const( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + in.get_uint(2), + reinterpret_cast(in.get_obj_array(3)), + in.get_uint(4), + reinterpret_cast(in.get_obj_array(5)), + reinterpret_cast(in.get_obj(6))); + in.store_result(result); +} +void exec_Z3_mk_quantifier_const(z3_replayer & in) { + Z3_ast result = Z3_mk_quantifier_const( + reinterpret_cast(in.get_obj(0)), + in.get_bool(1), + in.get_uint(2), + in.get_uint(3), + reinterpret_cast(in.get_obj_array(4)), + in.get_uint(5), + reinterpret_cast(in.get_obj_array(6)), + reinterpret_cast(in.get_obj(7))); + in.store_result(result); +} +void exec_Z3_mk_quantifier_const_ex(z3_replayer & in) { + Z3_ast result = Z3_mk_quantifier_const_ex( + reinterpret_cast(in.get_obj(0)), + in.get_bool(1), + in.get_uint(2), + in.get_symbol(3), + in.get_symbol(4), + in.get_uint(5), + reinterpret_cast(in.get_obj_array(6)), + in.get_uint(7), + reinterpret_cast(in.get_obj_array(8)), + in.get_uint(9), + reinterpret_cast(in.get_obj_array(10)), + reinterpret_cast(in.get_obj(11))); + in.store_result(result); +} +void exec_Z3_get_ast_id(z3_replayer & in) { + Z3_get_ast_id( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_func_decl_id(z3_replayer & in) { + Z3_get_func_decl_id( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_sort_id(z3_replayer & in) { + Z3_get_sort_id( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_is_well_sorted(z3_replayer & in) { + Z3_is_well_sorted( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_symbol_kind(z3_replayer & in) { + Z3_get_symbol_kind( + reinterpret_cast(in.get_obj(0)), + in.get_symbol(1)); +} +void exec_Z3_get_symbol_int(z3_replayer & in) { + Z3_get_symbol_int( + reinterpret_cast(in.get_obj(0)), + in.get_symbol(1)); +} +void exec_Z3_get_symbol_string(z3_replayer & in) { + Z3_get_symbol_string( + reinterpret_cast(in.get_obj(0)), + in.get_symbol(1)); +} +void exec_Z3_get_ast_kind(z3_replayer & in) { + Z3_get_ast_kind( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_ast_hash(z3_replayer & in) { + Z3_get_ast_hash( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_numeral_string(z3_replayer & in) { + Z3_get_numeral_string( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_numeral_decimal_string(z3_replayer & in) { + Z3_get_numeral_decimal_string( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); +} +void exec_Z3_get_numerator(z3_replayer & in) { + Z3_ast result = Z3_get_numerator( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_get_denominator(z3_replayer & in) { + Z3_ast result = Z3_get_denominator( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_get_numeral_small(z3_replayer & in) { + Z3_get_numeral_small( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_int64_addr(2), + in.get_int64_addr(3)); +} +void exec_Z3_get_numeral_int(z3_replayer & in) { + Z3_get_numeral_int( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_int_addr(2)); +} +void exec_Z3_get_numeral_uint(z3_replayer & in) { + Z3_get_numeral_uint( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint_addr(2)); +} +void exec_Z3_get_numeral_uint64(z3_replayer & in) { + Z3_get_numeral_uint64( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint64_addr(2)); +} +void exec_Z3_get_numeral_int64(z3_replayer & in) { + Z3_get_numeral_int64( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_int64_addr(2)); +} +void exec_Z3_get_numeral_rational_int64(z3_replayer & in) { + Z3_get_numeral_rational_int64( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_int64_addr(2), + in.get_int64_addr(3)); +} +void exec_Z3_get_bool_value(z3_replayer & in) { + Z3_get_bool_value( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_app_decl(z3_replayer & in) { + Z3_func_decl result = Z3_get_app_decl( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_get_app_num_args(z3_replayer & in) { + Z3_get_app_num_args( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_app_arg(z3_replayer & in) { + Z3_ast result = Z3_get_app_arg( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_get_index_value(z3_replayer & in) { + Z3_get_index_value( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_is_quantifier_forall(z3_replayer & in) { + Z3_is_quantifier_forall( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_quantifier_weight(z3_replayer & in) { + Z3_get_quantifier_weight( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_quantifier_num_patterns(z3_replayer & in) { + Z3_get_quantifier_num_patterns( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_quantifier_pattern_ast(z3_replayer & in) { + Z3_pattern result = Z3_get_quantifier_pattern_ast( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_get_quantifier_num_no_patterns(z3_replayer & in) { + Z3_get_quantifier_num_no_patterns( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_quantifier_no_pattern_ast(z3_replayer & in) { + Z3_ast result = Z3_get_quantifier_no_pattern_ast( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_get_quantifier_bound_name(z3_replayer & in) { + Z3_get_quantifier_bound_name( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); +} +void exec_Z3_get_quantifier_bound_sort(z3_replayer & in) { + Z3_sort result = Z3_get_quantifier_bound_sort( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_get_quantifier_body(z3_replayer & in) { + Z3_ast result = Z3_get_quantifier_body( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_get_quantifier_num_bound(z3_replayer & in) { + Z3_get_quantifier_num_bound( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_decl_name(z3_replayer & in) { + Z3_get_decl_name( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_decl_num_parameters(z3_replayer & in) { + Z3_get_decl_num_parameters( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_decl_parameter_kind(z3_replayer & in) { + Z3_get_decl_parameter_kind( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); +} +void exec_Z3_get_decl_int_parameter(z3_replayer & in) { + Z3_get_decl_int_parameter( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); +} +void exec_Z3_get_decl_double_parameter(z3_replayer & in) { + Z3_get_decl_double_parameter( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); +} +void exec_Z3_get_decl_symbol_parameter(z3_replayer & in) { + Z3_get_decl_symbol_parameter( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); +} +void exec_Z3_get_decl_sort_parameter(z3_replayer & in) { + Z3_sort result = Z3_get_decl_sort_parameter( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_get_decl_ast_parameter(z3_replayer & in) { + Z3_ast result = Z3_get_decl_ast_parameter( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_get_decl_func_decl_parameter(z3_replayer & in) { + Z3_func_decl result = Z3_get_decl_func_decl_parameter( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_get_decl_rational_parameter(z3_replayer & in) { + Z3_get_decl_rational_parameter( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); +} +void exec_Z3_get_sort_name(z3_replayer & in) { + Z3_get_sort_name( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_sort(z3_replayer & in) { + Z3_sort result = Z3_get_sort( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_get_domain_size(z3_replayer & in) { + Z3_get_domain_size( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_domain(z3_replayer & in) { + Z3_sort result = Z3_get_domain( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_get_range(z3_replayer & in) { + Z3_sort result = Z3_get_range( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_get_sort_kind(z3_replayer & in) { + Z3_get_sort_kind( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_bv_sort_size(z3_replayer & in) { + Z3_get_bv_sort_size( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_array_sort_domain(z3_replayer & in) { + Z3_sort result = Z3_get_array_sort_domain( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_get_array_sort_range(z3_replayer & in) { + Z3_sort result = Z3_get_array_sort_range( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_get_tuple_sort_mk_decl(z3_replayer & in) { + Z3_func_decl result = Z3_get_tuple_sort_mk_decl( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_get_decl_kind(z3_replayer & in) { + Z3_get_decl_kind( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_tuple_sort_num_fields(z3_replayer & in) { + Z3_get_tuple_sort_num_fields( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_tuple_sort_field_decl(z3_replayer & in) { + Z3_func_decl result = Z3_get_tuple_sort_field_decl( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_get_datatype_sort_num_constructors(z3_replayer & in) { + Z3_get_datatype_sort_num_constructors( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_datatype_sort_constructor(z3_replayer & in) { + Z3_func_decl result = Z3_get_datatype_sort_constructor( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_get_datatype_sort_recognizer(z3_replayer & in) { + Z3_func_decl result = Z3_get_datatype_sort_recognizer( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_get_datatype_sort_constructor_accessor(z3_replayer & in) { + Z3_func_decl result = Z3_get_datatype_sort_constructor_accessor( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2), + in.get_uint(3)); + in.store_result(result); +} +void exec_Z3_get_relation_arity(z3_replayer & in) { + Z3_get_relation_arity( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_relation_column(z3_replayer & in) { + Z3_sort result = Z3_get_relation_column( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_get_finite_domain_sort_size(z3_replayer & in) { + Z3_get_finite_domain_sort_size( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint64_addr(2)); +} +void exec_Z3_mk_finite_domain_sort(z3_replayer & in) { + Z3_sort result = Z3_mk_finite_domain_sort( + reinterpret_cast(in.get_obj(0)), + in.get_symbol(1), + in.get_uint64(2)); + in.store_result(result); +} +void exec_Z3_get_pattern_num_terms(z3_replayer & in) { + Z3_get_pattern_num_terms( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_pattern(z3_replayer & in) { + Z3_ast result = Z3_get_pattern( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_simplify(z3_replayer & in) { + Z3_ast result = Z3_simplify( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_simplify_ex(z3_replayer & in) { + Z3_ast result = Z3_simplify_ex( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_simplify_get_help(z3_replayer & in) { + Z3_simplify_get_help( + reinterpret_cast(in.get_obj(0))); +} +void exec_Z3_simplify_get_param_descrs(z3_replayer & in) { + Z3_param_descrs result = Z3_simplify_get_param_descrs( + reinterpret_cast(in.get_obj(0))); + in.store_result(result); +} +void exec_Z3_update_term(z3_replayer & in) { + Z3_ast result = Z3_update_term( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2), + reinterpret_cast(in.get_obj_array(3))); + in.store_result(result); +} +void exec_Z3_substitute(z3_replayer & in) { + Z3_ast result = Z3_substitute( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2), + reinterpret_cast(in.get_obj_array(3)), + reinterpret_cast(in.get_obj_array(4))); + in.store_result(result); +} +void exec_Z3_substitute_vars(z3_replayer & in) { + Z3_ast result = Z3_substitute_vars( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2), + reinterpret_cast(in.get_obj_array(3))); + in.store_result(result); +} +void exec_Z3_sort_to_ast(z3_replayer & in) { + Z3_ast result = Z3_sort_to_ast( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_func_decl_to_ast(z3_replayer & in) { + Z3_ast result = Z3_func_decl_to_ast( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_pattern_to_ast(z3_replayer & in) { + Z3_ast result = Z3_pattern_to_ast( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_app_to_ast(z3_replayer & in) { + Z3_ast result = Z3_app_to_ast( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_to_app(z3_replayer & in) { + Z3_app result = Z3_to_app( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_to_func_decl(z3_replayer & in) { + Z3_func_decl result = Z3_to_func_decl( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_push(z3_replayer & in) { + Z3_push( + reinterpret_cast(in.get_obj(0))); +} +void exec_Z3_pop(z3_replayer & in) { + Z3_pop( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1)); +} +void exec_Z3_get_num_scopes(z3_replayer & in) { + Z3_get_num_scopes( + reinterpret_cast(in.get_obj(0))); +} +void exec_Z3_persist_ast(z3_replayer & in) { + Z3_persist_ast( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); +} +void exec_Z3_assert_cnstr(z3_replayer & in) { + Z3_assert_cnstr( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_check_and_get_model(z3_replayer & in) { + Z3_check_and_get_model( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj_addr(1))); +} +void exec_Z3_check(z3_replayer & in) { + Z3_check( + reinterpret_cast(in.get_obj(0))); +} +void exec_Z3_check_assumptions(z3_replayer & in) { + Z3_check_assumptions( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + reinterpret_cast(in.get_obj_array(2)), + reinterpret_cast(in.get_obj_addr(3)), + reinterpret_cast(in.get_obj_addr(4)), + in.get_uint_addr(5), + reinterpret_cast(in.get_obj_array(6))); +} +void exec_Z3_get_implied_equalities(z3_replayer & in) { + Z3_get_implied_equalities( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + reinterpret_cast(in.get_obj_array(2)), + in.get_uint_array(3)); +} +void exec_Z3_del_model(z3_replayer & in) { + Z3_del_model( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_soft_check_cancel(z3_replayer & in) { + Z3_soft_check_cancel( + reinterpret_cast(in.get_obj(0))); +} +void exec_Z3_get_search_failure(z3_replayer & in) { + Z3_get_search_failure( + reinterpret_cast(in.get_obj(0))); +} +void exec_Z3_get_relevant_labels(z3_replayer & in) { + Z3_literals result = Z3_get_relevant_labels( + reinterpret_cast(in.get_obj(0))); + in.store_result(result); +} +void exec_Z3_get_relevant_literals(z3_replayer & in) { + Z3_literals result = Z3_get_relevant_literals( + reinterpret_cast(in.get_obj(0))); + in.store_result(result); +} +void exec_Z3_get_guessed_literals(z3_replayer & in) { + Z3_literals result = Z3_get_guessed_literals( + reinterpret_cast(in.get_obj(0))); + in.store_result(result); +} +void exec_Z3_del_literals(z3_replayer & in) { + Z3_del_literals( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_num_literals(z3_replayer & in) { + Z3_get_num_literals( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_label_symbol(z3_replayer & in) { + Z3_get_label_symbol( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); +} +void exec_Z3_get_literal(z3_replayer & in) { + Z3_ast result = Z3_get_literal( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_disable_literal(z3_replayer & in) { + Z3_disable_literal( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); +} +void exec_Z3_block_literals(z3_replayer & in) { + Z3_block_literals( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_model_inc_ref(z3_replayer & in) { + Z3_model_inc_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_model_dec_ref(z3_replayer & in) { + Z3_model_dec_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_model_get_const_interp(z3_replayer & in) { + Z3_ast result = Z3_model_get_const_interp( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_model_get_func_interp(z3_replayer & in) { + Z3_func_interp result = Z3_model_get_func_interp( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_model_get_num_consts(z3_replayer & in) { + Z3_model_get_num_consts( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_model_get_const_decl(z3_replayer & in) { + Z3_func_decl result = Z3_model_get_const_decl( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_model_get_num_funcs(z3_replayer & in) { + Z3_model_get_num_funcs( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_model_get_func_decl(z3_replayer & in) { + Z3_func_decl result = Z3_model_get_func_decl( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_model_eval(z3_replayer & in) { + Z3_model_eval( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2)), + in.get_bool(3), + reinterpret_cast(in.get_obj_addr(4))); +} +void exec_Z3_model_get_num_sorts(z3_replayer & in) { + Z3_model_get_num_sorts( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_model_get_sort(z3_replayer & in) { + Z3_sort result = Z3_model_get_sort( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_model_get_sort_universe(z3_replayer & in) { + Z3_ast_vector result = Z3_model_get_sort_universe( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_is_as_array(z3_replayer & in) { + Z3_is_as_array( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_as_array_func_decl(z3_replayer & in) { + Z3_func_decl result = Z3_get_as_array_func_decl( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_func_interp_inc_ref(z3_replayer & in) { + Z3_func_interp_inc_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_func_interp_dec_ref(z3_replayer & in) { + Z3_func_interp_dec_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_func_interp_get_num_entries(z3_replayer & in) { + Z3_func_interp_get_num_entries( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_func_interp_get_entry(z3_replayer & in) { + Z3_func_entry result = Z3_func_interp_get_entry( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_func_interp_get_else(z3_replayer & in) { + Z3_ast result = Z3_func_interp_get_else( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_func_interp_get_arity(z3_replayer & in) { + Z3_func_interp_get_arity( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_func_entry_inc_ref(z3_replayer & in) { + Z3_func_entry_inc_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_func_entry_dec_ref(z3_replayer & in) { + Z3_func_entry_dec_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_func_entry_get_value(z3_replayer & in) { + Z3_ast result = Z3_func_entry_get_value( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_func_entry_get_num_args(z3_replayer & in) { + Z3_func_entry_get_num_args( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_func_entry_get_arg(z3_replayer & in) { + Z3_ast result = Z3_func_entry_get_arg( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_get_model_num_constants(z3_replayer & in) { + Z3_get_model_num_constants( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_model_constant(z3_replayer & in) { + Z3_func_decl result = Z3_get_model_constant( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_eval_func_decl(z3_replayer & in) { + Z3_eval_func_decl( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2)), + reinterpret_cast(in.get_obj_addr(3))); +} +void exec_Z3_is_array_value(z3_replayer & in) { + Z3_is_array_value( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2)), + in.get_uint_addr(3)); +} +void exec_Z3_get_array_value(z3_replayer & in) { + Z3_get_array_value( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2)), + in.get_uint(3), + reinterpret_cast(in.get_obj_array(4)), + reinterpret_cast(in.get_obj_array(5)), + reinterpret_cast(in.get_obj_addr(6))); +} +void exec_Z3_get_model_num_funcs(z3_replayer & in) { + Z3_get_model_num_funcs( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_model_func_decl(z3_replayer & in) { + Z3_func_decl result = Z3_get_model_func_decl( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_get_model_func_else(z3_replayer & in) { + Z3_ast result = Z3_get_model_func_else( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_get_model_func_num_entries(z3_replayer & in) { + Z3_get_model_func_num_entries( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); +} +void exec_Z3_get_model_func_entry_num_args(z3_replayer & in) { + Z3_get_model_func_entry_num_args( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2), + in.get_uint(3)); +} +void exec_Z3_get_model_func_entry_arg(z3_replayer & in) { + Z3_ast result = Z3_get_model_func_entry_arg( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2), + in.get_uint(3), + in.get_uint(4)); + in.store_result(result); +} +void exec_Z3_get_model_func_entry_value(z3_replayer & in) { + Z3_ast result = Z3_get_model_func_entry_value( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2), + in.get_uint(3)); + in.store_result(result); +} +void exec_Z3_eval(z3_replayer & in) { + Z3_eval( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2)), + reinterpret_cast(in.get_obj_addr(3))); +} +void exec_Z3_eval_decl(z3_replayer & in) { + Z3_eval_decl( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2)), + in.get_uint(3), + reinterpret_cast(in.get_obj_array(4)), + reinterpret_cast(in.get_obj_addr(5))); +} +void exec_Z3_set_ast_print_mode(z3_replayer & in) { + Z3_set_ast_print_mode( + reinterpret_cast(in.get_obj(0)), + static_cast(in.get_uint(1))); +} +void exec_Z3_ast_to_string(z3_replayer & in) { + Z3_ast_to_string( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_pattern_to_string(z3_replayer & in) { + Z3_pattern_to_string( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_sort_to_string(z3_replayer & in) { + Z3_sort_to_string( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_func_decl_to_string(z3_replayer & in) { + Z3_func_decl_to_string( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_model_to_string(z3_replayer & in) { + Z3_model_to_string( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_benchmark_to_smtlib_string(z3_replayer & in) { + Z3_benchmark_to_smtlib_string( + reinterpret_cast(in.get_obj(0)), + in.get_str(1), + in.get_str(2), + in.get_str(3), + in.get_str(4), + in.get_uint(5), + reinterpret_cast(in.get_obj_array(6)), + reinterpret_cast(in.get_obj(7))); +} +void exec_Z3_context_to_string(z3_replayer & in) { + Z3_context_to_string( + reinterpret_cast(in.get_obj(0))); +} +void exec_Z3_statistics_to_string(z3_replayer & in) { + Z3_statistics_to_string( + reinterpret_cast(in.get_obj(0))); +} +void exec_Z3_get_context_assignment(z3_replayer & in) { + Z3_ast result = Z3_get_context_assignment( + reinterpret_cast(in.get_obj(0))); + in.store_result(result); +} +void exec_Z3_parse_smtlib_string(z3_replayer & in) { + Z3_parse_smtlib_string( + reinterpret_cast(in.get_obj(0)), + in.get_str(1), + in.get_uint(2), + in.get_symbol_array(3), + reinterpret_cast(in.get_obj_array(4)), + in.get_uint(5), + in.get_symbol_array(6), + reinterpret_cast(in.get_obj_array(7))); +} +void exec_Z3_parse_smtlib_file(z3_replayer & in) { + Z3_parse_smtlib_file( + reinterpret_cast(in.get_obj(0)), + in.get_str(1), + in.get_uint(2), + in.get_symbol_array(3), + reinterpret_cast(in.get_obj_array(4)), + in.get_uint(5), + in.get_symbol_array(6), + reinterpret_cast(in.get_obj_array(7))); +} +void exec_Z3_get_smtlib_num_formulas(z3_replayer & in) { + Z3_get_smtlib_num_formulas( + reinterpret_cast(in.get_obj(0))); +} +void exec_Z3_get_smtlib_formula(z3_replayer & in) { + Z3_ast result = Z3_get_smtlib_formula( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1)); + in.store_result(result); +} +void exec_Z3_get_smtlib_num_assumptions(z3_replayer & in) { + Z3_get_smtlib_num_assumptions( + reinterpret_cast(in.get_obj(0))); +} +void exec_Z3_get_smtlib_assumption(z3_replayer & in) { + Z3_ast result = Z3_get_smtlib_assumption( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1)); + in.store_result(result); +} +void exec_Z3_get_smtlib_num_decls(z3_replayer & in) { + Z3_get_smtlib_num_decls( + reinterpret_cast(in.get_obj(0))); +} +void exec_Z3_get_smtlib_decl(z3_replayer & in) { + Z3_func_decl result = Z3_get_smtlib_decl( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1)); + in.store_result(result); +} +void exec_Z3_get_smtlib_num_sorts(z3_replayer & in) { + Z3_get_smtlib_num_sorts( + reinterpret_cast(in.get_obj(0))); +} +void exec_Z3_get_smtlib_sort(z3_replayer & in) { + Z3_sort result = Z3_get_smtlib_sort( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1)); + in.store_result(result); +} +void exec_Z3_get_smtlib_error(z3_replayer & in) { + Z3_get_smtlib_error( + reinterpret_cast(in.get_obj(0))); +} +void exec_Z3_parse_z3_string(z3_replayer & in) { + Z3_ast result = Z3_parse_z3_string( + reinterpret_cast(in.get_obj(0)), + in.get_str(1)); + in.store_result(result); +} +void exec_Z3_parse_z3_file(z3_replayer & in) { + Z3_ast result = Z3_parse_z3_file( + reinterpret_cast(in.get_obj(0)), + in.get_str(1)); + in.store_result(result); +} +void exec_Z3_parse_smtlib2_string(z3_replayer & in) { + Z3_ast result = Z3_parse_smtlib2_string( + reinterpret_cast(in.get_obj(0)), + in.get_str(1), + in.get_uint(2), + in.get_symbol_array(3), + reinterpret_cast(in.get_obj_array(4)), + in.get_uint(5), + in.get_symbol_array(6), + reinterpret_cast(in.get_obj_array(7))); + in.store_result(result); +} +void exec_Z3_parse_smtlib2_file(z3_replayer & in) { + Z3_ast result = Z3_parse_smtlib2_file( + reinterpret_cast(in.get_obj(0)), + in.get_str(1), + in.get_uint(2), + in.get_symbol_array(3), + reinterpret_cast(in.get_obj_array(4)), + in.get_uint(5), + in.get_symbol_array(6), + reinterpret_cast(in.get_obj_array(7))); + in.store_result(result); +} +void exec_Z3_get_error_code(z3_replayer & in) { + Z3_get_error_code( + reinterpret_cast(in.get_obj(0))); +} +void exec_Z3_set_error(z3_replayer & in) { + Z3_set_error( + reinterpret_cast(in.get_obj(0)), + static_cast(in.get_uint(1))); +} +void exec_Z3_get_error_msg(z3_replayer & in) { + Z3_get_error_msg( + static_cast(in.get_uint(0))); +} +void exec_Z3_get_version(z3_replayer & in) { + Z3_get_version( + in.get_uint_addr(0), + in.get_uint_addr(1), + in.get_uint_addr(2), + in.get_uint_addr(3)); +} +void exec_Z3_reset_memory(z3_replayer & in) { + Z3_reset_memory( + ); +} +void exec_Z3_is_app(z3_replayer & in) { + Z3_is_app( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_is_numeral_ast(z3_replayer & in) { + Z3_is_numeral_ast( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_get_arity(z3_replayer & in) { + Z3_get_arity( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_mk_injective_function(z3_replayer & in) { + Z3_func_decl result = Z3_mk_injective_function( + reinterpret_cast(in.get_obj(0)), + in.get_symbol(1), + in.get_uint(2), + reinterpret_cast(in.get_obj_array(3)), + reinterpret_cast(in.get_obj(4))); + in.store_result(result); +} +void exec_Z3_mk_fixedpoint(z3_replayer & in) { + Z3_fixedpoint result = Z3_mk_fixedpoint( + reinterpret_cast(in.get_obj(0))); + in.store_result(result); +} +void exec_Z3_fixedpoint_inc_ref(z3_replayer & in) { + Z3_fixedpoint_inc_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_fixedpoint_dec_ref(z3_replayer & in) { + Z3_fixedpoint_dec_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_fixedpoint_push(z3_replayer & in) { + Z3_fixedpoint_push( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_fixedpoint_pop(z3_replayer & in) { + Z3_fixedpoint_pop( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_fixedpoint_register_relation(z3_replayer & in) { + Z3_fixedpoint_register_relation( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); +} +void exec_Z3_fixedpoint_assert(z3_replayer & in) { + Z3_fixedpoint_assert( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); +} +void exec_Z3_fixedpoint_add_rule(z3_replayer & in) { + Z3_fixedpoint_add_rule( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2)), + in.get_symbol(3)); +} +void exec_Z3_fixedpoint_add_fact(z3_replayer & in) { + Z3_fixedpoint_add_fact( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2)), + in.get_uint(3), + in.get_uint_array(4)); +} +void exec_Z3_fixedpoint_query(z3_replayer & in) { + Z3_fixedpoint_query( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); +} +void exec_Z3_fixedpoint_query_relations(z3_replayer & in) { + Z3_fixedpoint_query_relations( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2), + reinterpret_cast(in.get_obj_array(3))); +} +void exec_Z3_fixedpoint_get_answer(z3_replayer & in) { + Z3_ast result = Z3_fixedpoint_get_answer( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_fixedpoint_update_rule(z3_replayer & in) { + Z3_fixedpoint_update_rule( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2)), + in.get_symbol(3)); +} +void exec_Z3_fixedpoint_get_num_levels(z3_replayer & in) { + Z3_fixedpoint_get_num_levels( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); +} +void exec_Z3_fixedpoint_get_cover_delta(z3_replayer & in) { + Z3_ast result = Z3_fixedpoint_get_cover_delta( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_int(2), + reinterpret_cast(in.get_obj(3))); + in.store_result(result); +} +void exec_Z3_fixedpoint_add_cover(z3_replayer & in) { + Z3_fixedpoint_add_cover( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_int(2), + reinterpret_cast(in.get_obj(3)), + reinterpret_cast(in.get_obj(4))); +} +void exec_Z3_fixedpoint_get_statistics(z3_replayer & in) { + Z3_stats result = Z3_fixedpoint_get_statistics( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_fixedpoint_get_help(z3_replayer & in) { + Z3_fixedpoint_get_help( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_fixedpoint_get_param_descrs(z3_replayer & in) { + Z3_param_descrs result = Z3_fixedpoint_get_param_descrs( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_fixedpoint_set_params(z3_replayer & in) { + Z3_fixedpoint_set_params( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); +} +void exec_Z3_fixedpoint_to_string(z3_replayer & in) { + Z3_fixedpoint_to_string( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2), + reinterpret_cast(in.get_obj_array(3))); +} +void exec_Z3_fixedpoint_get_reason_unknown(z3_replayer & in) { + Z3_fixedpoint_get_reason_unknown( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_fixedpoint_set_predicate_representation(z3_replayer & in) { + Z3_fixedpoint_set_predicate_representation( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2)), + in.get_uint(3), + in.get_symbol_array(4)); +} +void exec_Z3_fixedpoint_simplify_rules(z3_replayer & in) { + Z3_ast_vector result = Z3_fixedpoint_simplify_rules( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2), + reinterpret_cast(in.get_obj_array(3)), + in.get_uint(4), + reinterpret_cast(in.get_obj_array(5))); + in.store_result(result); +} +void exec_Z3_mk_params(z3_replayer & in) { + Z3_params result = Z3_mk_params( + reinterpret_cast(in.get_obj(0))); + in.store_result(result); +} +void exec_Z3_params_inc_ref(z3_replayer & in) { + Z3_params_inc_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_params_dec_ref(z3_replayer & in) { + Z3_params_dec_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_params_set_bool(z3_replayer & in) { + Z3_params_set_bool( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_symbol(2), + in.get_bool(3)); +} +void exec_Z3_params_set_uint(z3_replayer & in) { + Z3_params_set_uint( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_symbol(2), + in.get_uint(3)); +} +void exec_Z3_params_set_double(z3_replayer & in) { + Z3_params_set_double( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_symbol(2), + in.get_double(3)); +} +void exec_Z3_params_set_symbol(z3_replayer & in) { + Z3_params_set_symbol( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_symbol(2), + in.get_symbol(3)); +} +void exec_Z3_params_to_string(z3_replayer & in) { + Z3_params_to_string( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_params_validate(z3_replayer & in) { + Z3_params_validate( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); +} +void exec_Z3_param_descrs_inc_ref(z3_replayer & in) { + Z3_param_descrs_inc_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_param_descrs_dec_ref(z3_replayer & in) { + Z3_param_descrs_dec_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_param_descrs_get_kind(z3_replayer & in) { + Z3_param_descrs_get_kind( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_symbol(2)); +} +void exec_Z3_param_descrs_size(z3_replayer & in) { + Z3_param_descrs_size( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_param_descrs_get_name(z3_replayer & in) { + Z3_param_descrs_get_name( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); +} +void exec_Z3_interrupt(z3_replayer & in) { + Z3_interrupt( + reinterpret_cast(in.get_obj(0))); +} +void exec_Z3_get_error_msg_ex(z3_replayer & in) { + Z3_get_error_msg_ex( + reinterpret_cast(in.get_obj(0)), + static_cast(in.get_uint(1))); +} +void exec_Z3_translate(z3_replayer & in) { + Z3_ast result = Z3_translate( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_mk_goal(z3_replayer & in) { + Z3_goal result = Z3_mk_goal( + reinterpret_cast(in.get_obj(0)), + in.get_bool(1), + in.get_bool(2), + in.get_bool(3)); + in.store_result(result); +} +void exec_Z3_goal_inc_ref(z3_replayer & in) { + Z3_goal_inc_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_goal_dec_ref(z3_replayer & in) { + Z3_goal_dec_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_goal_precision(z3_replayer & in) { + Z3_goal_precision( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_goal_assert(z3_replayer & in) { + Z3_goal_assert( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); +} +void exec_Z3_goal_inconsistent(z3_replayer & in) { + Z3_goal_inconsistent( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_goal_depth(z3_replayer & in) { + Z3_goal_depth( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_goal_reset(z3_replayer & in) { + Z3_goal_reset( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_goal_size(z3_replayer & in) { + Z3_goal_size( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_goal_formula(z3_replayer & in) { + Z3_ast result = Z3_goal_formula( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_goal_num_exprs(z3_replayer & in) { + Z3_goal_num_exprs( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_goal_is_decided_sat(z3_replayer & in) { + Z3_goal_is_decided_sat( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_goal_is_decided_unsat(z3_replayer & in) { + Z3_goal_is_decided_unsat( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_goal_translate(z3_replayer & in) { + Z3_goal result = Z3_goal_translate( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_goal_to_string(z3_replayer & in) { + Z3_goal_to_string( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_mk_tactic(z3_replayer & in) { + Z3_tactic result = Z3_mk_tactic( + reinterpret_cast(in.get_obj(0)), + in.get_str(1)); + in.store_result(result); +} +void exec_Z3_mk_probe(z3_replayer & in) { + Z3_probe result = Z3_mk_probe( + reinterpret_cast(in.get_obj(0)), + in.get_str(1)); + in.store_result(result); +} +void exec_Z3_tactic_inc_ref(z3_replayer & in) { + Z3_tactic_inc_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_tactic_dec_ref(z3_replayer & in) { + Z3_tactic_dec_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_probe_inc_ref(z3_replayer & in) { + Z3_probe_inc_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_probe_dec_ref(z3_replayer & in) { + Z3_probe_dec_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_tactic_and_then(z3_replayer & in) { + Z3_tactic result = Z3_tactic_and_then( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_tactic_or_else(z3_replayer & in) { + Z3_tactic result = Z3_tactic_or_else( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_tactic_par_or(z3_replayer & in) { + Z3_tactic result = Z3_tactic_par_or( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1), + reinterpret_cast(in.get_obj_array(2))); + in.store_result(result); +} +void exec_Z3_tactic_par_and_then(z3_replayer & in) { + Z3_tactic result = Z3_tactic_par_and_then( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_tactic_try_for(z3_replayer & in) { + Z3_tactic result = Z3_tactic_try_for( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_tactic_when(z3_replayer & in) { + Z3_tactic result = Z3_tactic_when( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_tactic_cond(z3_replayer & in) { + Z3_tactic result = Z3_tactic_cond( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2)), + reinterpret_cast(in.get_obj(3))); + in.store_result(result); +} +void exec_Z3_tactic_repeat(z3_replayer & in) { + Z3_tactic result = Z3_tactic_repeat( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_tactic_skip(z3_replayer & in) { + Z3_tactic result = Z3_tactic_skip( + reinterpret_cast(in.get_obj(0))); + in.store_result(result); +} +void exec_Z3_tactic_fail(z3_replayer & in) { + Z3_tactic result = Z3_tactic_fail( + reinterpret_cast(in.get_obj(0))); + in.store_result(result); +} +void exec_Z3_tactic_fail_if(z3_replayer & in) { + Z3_tactic result = Z3_tactic_fail_if( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_tactic_fail_if_not_decided(z3_replayer & in) { + Z3_tactic result = Z3_tactic_fail_if_not_decided( + reinterpret_cast(in.get_obj(0))); + in.store_result(result); +} +void exec_Z3_tactic_using_params(z3_replayer & in) { + Z3_tactic result = Z3_tactic_using_params( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_probe_const(z3_replayer & in) { + Z3_probe result = Z3_probe_const( + reinterpret_cast(in.get_obj(0)), + in.get_double(1)); + in.store_result(result); +} +void exec_Z3_probe_lt(z3_replayer & in) { + Z3_probe result = Z3_probe_lt( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_probe_le(z3_replayer & in) { + Z3_probe result = Z3_probe_le( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_probe_gt(z3_replayer & in) { + Z3_probe result = Z3_probe_gt( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_probe_ge(z3_replayer & in) { + Z3_probe result = Z3_probe_ge( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_probe_eq(z3_replayer & in) { + Z3_probe result = Z3_probe_eq( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_probe_and(z3_replayer & in) { + Z3_probe result = Z3_probe_and( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_probe_or(z3_replayer & in) { + Z3_probe result = Z3_probe_or( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_probe_not(z3_replayer & in) { + Z3_probe result = Z3_probe_not( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_get_num_tactics(z3_replayer & in) { + Z3_get_num_tactics( + reinterpret_cast(in.get_obj(0))); +} +void exec_Z3_get_tactic_name(z3_replayer & in) { + Z3_get_tactic_name( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1)); +} +void exec_Z3_get_num_probes(z3_replayer & in) { + Z3_get_num_probes( + reinterpret_cast(in.get_obj(0))); +} +void exec_Z3_get_probe_name(z3_replayer & in) { + Z3_get_probe_name( + reinterpret_cast(in.get_obj(0)), + in.get_uint(1)); +} +void exec_Z3_tactic_get_help(z3_replayer & in) { + Z3_tactic_get_help( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_tactic_get_param_descrs(z3_replayer & in) { + Z3_param_descrs result = Z3_tactic_get_param_descrs( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_tactic_get_descr(z3_replayer & in) { + Z3_tactic_get_descr( + reinterpret_cast(in.get_obj(0)), + in.get_str(1)); +} +void exec_Z3_probe_get_descr(z3_replayer & in) { + Z3_probe_get_descr( + reinterpret_cast(in.get_obj(0)), + in.get_str(1)); +} +void exec_Z3_probe_apply(z3_replayer & in) { + Z3_probe_apply( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); +} +void exec_Z3_tactic_apply(z3_replayer & in) { + Z3_apply_result result = Z3_tactic_apply( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_tactic_apply_ex(z3_replayer & in) { + Z3_apply_result result = Z3_tactic_apply_ex( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2)), + reinterpret_cast(in.get_obj(3))); + in.store_result(result); +} +void exec_Z3_apply_result_inc_ref(z3_replayer & in) { + Z3_apply_result_inc_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_apply_result_dec_ref(z3_replayer & in) { + Z3_apply_result_dec_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_apply_result_to_string(z3_replayer & in) { + Z3_apply_result_to_string( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_apply_result_get_num_subgoals(z3_replayer & in) { + Z3_apply_result_get_num_subgoals( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_apply_result_get_subgoal(z3_replayer & in) { + Z3_goal result = Z3_apply_result_get_subgoal( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_apply_result_convert_model(z3_replayer & in) { + Z3_model result = Z3_apply_result_convert_model( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2), + reinterpret_cast(in.get_obj(3))); + in.store_result(result); +} +void exec_Z3_mk_solver(z3_replayer & in) { + Z3_solver result = Z3_mk_solver( + reinterpret_cast(in.get_obj(0))); + in.store_result(result); +} +void exec_Z3_mk_simple_solver(z3_replayer & in) { + Z3_solver result = Z3_mk_simple_solver( + reinterpret_cast(in.get_obj(0))); + in.store_result(result); +} +void exec_Z3_mk_solver_for_logic(z3_replayer & in) { + Z3_solver result = Z3_mk_solver_for_logic( + reinterpret_cast(in.get_obj(0)), + in.get_symbol(1)); + in.store_result(result); +} +void exec_Z3_mk_solver_from_tactic(z3_replayer & in) { + Z3_solver result = Z3_mk_solver_from_tactic( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_solver_get_help(z3_replayer & in) { + Z3_solver_get_help( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_solver_get_param_descrs(z3_replayer & in) { + Z3_param_descrs result = Z3_solver_get_param_descrs( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_solver_set_params(z3_replayer & in) { + Z3_solver_set_params( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); +} +void exec_Z3_solver_inc_ref(z3_replayer & in) { + Z3_solver_inc_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_solver_dec_ref(z3_replayer & in) { + Z3_solver_dec_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_solver_push(z3_replayer & in) { + Z3_solver_push( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_solver_pop(z3_replayer & in) { + Z3_solver_pop( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); +} +void exec_Z3_solver_reset(z3_replayer & in) { + Z3_solver_reset( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_solver_get_num_scopes(z3_replayer & in) { + Z3_solver_get_num_scopes( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_solver_assert(z3_replayer & in) { + Z3_solver_assert( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); +} +void exec_Z3_solver_get_assertions(z3_replayer & in) { + Z3_ast_vector result = Z3_solver_get_assertions( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_solver_check(z3_replayer & in) { + Z3_solver_check( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_solver_check_assumptions(z3_replayer & in) { + Z3_solver_check_assumptions( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2), + reinterpret_cast(in.get_obj_array(3))); +} +void exec_Z3_solver_get_model(z3_replayer & in) { + Z3_model result = Z3_solver_get_model( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_solver_get_proof(z3_replayer & in) { + Z3_ast result = Z3_solver_get_proof( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_solver_get_unsat_core(z3_replayer & in) { + Z3_ast_vector result = Z3_solver_get_unsat_core( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_solver_get_reason_unknown(z3_replayer & in) { + Z3_solver_get_reason_unknown( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_solver_get_statistics(z3_replayer & in) { + Z3_stats result = Z3_solver_get_statistics( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_solver_to_string(z3_replayer & in) { + Z3_solver_to_string( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_stats_to_string(z3_replayer & in) { + Z3_stats_to_string( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_stats_inc_ref(z3_replayer & in) { + Z3_stats_inc_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_stats_dec_ref(z3_replayer & in) { + Z3_stats_dec_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_stats_size(z3_replayer & in) { + Z3_stats_size( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_stats_get_key(z3_replayer & in) { + Z3_stats_get_key( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); +} +void exec_Z3_stats_is_uint(z3_replayer & in) { + Z3_stats_is_uint( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); +} +void exec_Z3_stats_is_double(z3_replayer & in) { + Z3_stats_is_double( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); +} +void exec_Z3_stats_get_uint_value(z3_replayer & in) { + Z3_stats_get_uint_value( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); +} +void exec_Z3_stats_get_double_value(z3_replayer & in) { + Z3_stats_get_double_value( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); +} +void exec_Z3_mk_ast_vector(z3_replayer & in) { + Z3_ast_vector result = Z3_mk_ast_vector( + reinterpret_cast(in.get_obj(0))); + in.store_result(result); +} +void exec_Z3_ast_vector_inc_ref(z3_replayer & in) { + Z3_ast_vector_inc_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_ast_vector_dec_ref(z3_replayer & in) { + Z3_ast_vector_dec_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_ast_vector_size(z3_replayer & in) { + Z3_ast_vector_size( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_ast_vector_get(z3_replayer & in) { + Z3_ast result = Z3_ast_vector_get( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); + in.store_result(result); +} +void exec_Z3_ast_vector_set(z3_replayer & in) { + Z3_ast_vector_set( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2), + reinterpret_cast(in.get_obj(3))); +} +void exec_Z3_ast_vector_resize(z3_replayer & in) { + Z3_ast_vector_resize( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + in.get_uint(2)); +} +void exec_Z3_ast_vector_push(z3_replayer & in) { + Z3_ast_vector_push( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); +} +void exec_Z3_ast_vector_translate(z3_replayer & in) { + Z3_ast_vector result = Z3_ast_vector_translate( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_ast_vector_to_string(z3_replayer & in) { + Z3_ast_vector_to_string( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_mk_ast_map(z3_replayer & in) { + Z3_ast_map result = Z3_mk_ast_map( + reinterpret_cast(in.get_obj(0))); + in.store_result(result); +} +void exec_Z3_ast_map_inc_ref(z3_replayer & in) { + Z3_ast_map_inc_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_ast_map_dec_ref(z3_replayer & in) { + Z3_ast_map_dec_ref( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_ast_map_contains(z3_replayer & in) { + Z3_ast_map_contains( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); +} +void exec_Z3_ast_map_find(z3_replayer & in) { + Z3_ast result = Z3_ast_map_find( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); + in.store_result(result); +} +void exec_Z3_ast_map_insert(z3_replayer & in) { + Z3_ast_map_insert( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2)), + reinterpret_cast(in.get_obj(3))); +} +void exec_Z3_ast_map_erase(z3_replayer & in) { + Z3_ast_map_erase( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1)), + reinterpret_cast(in.get_obj(2))); +} +void exec_Z3_ast_map_size(z3_replayer & in) { + Z3_ast_map_size( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_ast_map_reset(z3_replayer & in) { + Z3_ast_map_reset( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void exec_Z3_ast_map_keys(z3_replayer & in) { + Z3_ast_vector result = Z3_ast_map_keys( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); + in.store_result(result); +} +void exec_Z3_ast_map_to_string(z3_replayer & in) { + Z3_ast_map_to_string( + reinterpret_cast(in.get_obj(0)), + reinterpret_cast(in.get_obj(1))); +} +void register_z3_replayer_cmds(z3_replayer & in) { + in.register_cmd(0, exec_Z3_mk_config); + in.register_cmd(1, exec_Z3_del_config); + in.register_cmd(2, exec_Z3_set_param_value); + in.register_cmd(3, exec_Z3_mk_context); + in.register_cmd(4, exec_Z3_mk_context_rc); + in.register_cmd(5, exec_Z3_set_logic); + in.register_cmd(6, exec_Z3_del_context); + in.register_cmd(7, exec_Z3_inc_ref); + in.register_cmd(8, exec_Z3_dec_ref); + in.register_cmd(9, exec_Z3_toggle_warning_messages); + in.register_cmd(10, exec_Z3_update_param_value); + in.register_cmd(11, exec_Z3_get_param_value); + in.register_cmd(12, exec_Z3_mk_int_symbol); + in.register_cmd(13, exec_Z3_mk_string_symbol); + in.register_cmd(14, exec_Z3_is_eq_sort); + in.register_cmd(15, exec_Z3_mk_uninterpreted_sort); + in.register_cmd(16, exec_Z3_mk_bool_sort); + in.register_cmd(17, exec_Z3_mk_int_sort); + in.register_cmd(18, exec_Z3_mk_real_sort); + in.register_cmd(19, exec_Z3_mk_bv_sort); + in.register_cmd(20, exec_Z3_mk_array_sort); + in.register_cmd(21, exec_Z3_mk_tuple_sort); + in.register_cmd(22, exec_Z3_mk_enumeration_sort); + in.register_cmd(23, exec_Z3_mk_list_sort); + in.register_cmd(24, exec_Z3_mk_constructor); + in.register_cmd(25, exec_Z3_query_constructor); + in.register_cmd(26, exec_Z3_del_constructor); + in.register_cmd(27, exec_Z3_mk_datatype); + in.register_cmd(28, exec_Z3_mk_constructor_list); + in.register_cmd(29, exec_Z3_del_constructor_list); + in.register_cmd(30, exec_Z3_mk_datatypes); + in.register_cmd(31, exec_Z3_is_eq_ast); + in.register_cmd(32, exec_Z3_is_eq_func_decl); + in.register_cmd(33, exec_Z3_mk_func_decl); + in.register_cmd(34, exec_Z3_mk_app); + in.register_cmd(35, exec_Z3_mk_const); + in.register_cmd(36, exec_Z3_mk_label); + in.register_cmd(37, exec_Z3_mk_fresh_func_decl); + in.register_cmd(38, exec_Z3_mk_fresh_const); + in.register_cmd(39, exec_Z3_mk_true); + in.register_cmd(40, exec_Z3_mk_false); + in.register_cmd(41, exec_Z3_mk_eq); + in.register_cmd(42, exec_Z3_mk_distinct); + in.register_cmd(43, exec_Z3_mk_not); + in.register_cmd(44, exec_Z3_mk_ite); + in.register_cmd(45, exec_Z3_mk_iff); + in.register_cmd(46, exec_Z3_mk_implies); + in.register_cmd(47, exec_Z3_mk_xor); + in.register_cmd(48, exec_Z3_mk_and); + in.register_cmd(49, exec_Z3_mk_or); + in.register_cmd(50, exec_Z3_mk_add); + in.register_cmd(51, exec_Z3_mk_mul); + in.register_cmd(52, exec_Z3_mk_sub); + in.register_cmd(53, exec_Z3_mk_unary_minus); + in.register_cmd(54, exec_Z3_mk_div); + in.register_cmd(55, exec_Z3_mk_mod); + in.register_cmd(56, exec_Z3_mk_rem); + in.register_cmd(57, exec_Z3_mk_power); + in.register_cmd(58, exec_Z3_is_algebraic_number); + in.register_cmd(59, exec_Z3_get_algebraic_number_lower); + in.register_cmd(60, exec_Z3_get_algebraic_number_upper); + in.register_cmd(61, exec_Z3_mk_lt); + in.register_cmd(62, exec_Z3_mk_le); + in.register_cmd(63, exec_Z3_mk_gt); + in.register_cmd(64, exec_Z3_mk_ge); + in.register_cmd(65, exec_Z3_mk_int2real); + in.register_cmd(66, exec_Z3_mk_real2int); + in.register_cmd(67, exec_Z3_mk_is_int); + in.register_cmd(68, exec_Z3_mk_bvnot); + in.register_cmd(69, exec_Z3_mk_bvredand); + in.register_cmd(70, exec_Z3_mk_bvredor); + in.register_cmd(71, exec_Z3_mk_bvand); + in.register_cmd(72, exec_Z3_mk_bvor); + in.register_cmd(73, exec_Z3_mk_bvxor); + in.register_cmd(74, exec_Z3_mk_bvnand); + in.register_cmd(75, exec_Z3_mk_bvnor); + in.register_cmd(76, exec_Z3_mk_bvxnor); + in.register_cmd(77, exec_Z3_mk_bvneg); + in.register_cmd(78, exec_Z3_mk_bvadd); + in.register_cmd(79, exec_Z3_mk_bvsub); + in.register_cmd(80, exec_Z3_mk_bvmul); + in.register_cmd(81, exec_Z3_mk_bvudiv); + in.register_cmd(82, exec_Z3_mk_bvsdiv); + in.register_cmd(83, exec_Z3_mk_bvurem); + in.register_cmd(84, exec_Z3_mk_bvsrem); + in.register_cmd(85, exec_Z3_mk_bvsmod); + in.register_cmd(86, exec_Z3_mk_bvult); + in.register_cmd(87, exec_Z3_mk_bvslt); + in.register_cmd(88, exec_Z3_mk_bvule); + in.register_cmd(89, exec_Z3_mk_bvsle); + in.register_cmd(90, exec_Z3_mk_bvuge); + in.register_cmd(91, exec_Z3_mk_bvsge); + in.register_cmd(92, exec_Z3_mk_bvugt); + in.register_cmd(93, exec_Z3_mk_bvsgt); + in.register_cmd(94, exec_Z3_mk_concat); + in.register_cmd(95, exec_Z3_mk_extract); + in.register_cmd(96, exec_Z3_mk_sign_ext); + in.register_cmd(97, exec_Z3_mk_zero_ext); + in.register_cmd(98, exec_Z3_mk_repeat); + in.register_cmd(99, exec_Z3_mk_bvshl); + in.register_cmd(100, exec_Z3_mk_bvlshr); + in.register_cmd(101, exec_Z3_mk_bvashr); + in.register_cmd(102, exec_Z3_mk_rotate_left); + in.register_cmd(103, exec_Z3_mk_rotate_right); + in.register_cmd(104, exec_Z3_mk_ext_rotate_left); + in.register_cmd(105, exec_Z3_mk_ext_rotate_right); + in.register_cmd(106, exec_Z3_mk_int2bv); + in.register_cmd(107, exec_Z3_mk_bv2int); + in.register_cmd(108, exec_Z3_mk_bvadd_no_overflow); + in.register_cmd(109, exec_Z3_mk_bvadd_no_underflow); + in.register_cmd(110, exec_Z3_mk_bvsub_no_overflow); + in.register_cmd(111, exec_Z3_mk_bvsub_no_underflow); + in.register_cmd(112, exec_Z3_mk_bvsdiv_no_overflow); + in.register_cmd(113, exec_Z3_mk_bvneg_no_overflow); + in.register_cmd(114, exec_Z3_mk_bvmul_no_overflow); + in.register_cmd(115, exec_Z3_mk_bvmul_no_underflow); + in.register_cmd(116, exec_Z3_mk_select); + in.register_cmd(117, exec_Z3_mk_store); + in.register_cmd(118, exec_Z3_mk_const_array); + in.register_cmd(119, exec_Z3_mk_map); + in.register_cmd(120, exec_Z3_mk_array_default); + in.register_cmd(121, exec_Z3_mk_set_sort); + in.register_cmd(122, exec_Z3_mk_empty_set); + in.register_cmd(123, exec_Z3_mk_full_set); + in.register_cmd(124, exec_Z3_mk_set_add); + in.register_cmd(125, exec_Z3_mk_set_del); + in.register_cmd(126, exec_Z3_mk_set_union); + in.register_cmd(127, exec_Z3_mk_set_intersect); + in.register_cmd(128, exec_Z3_mk_set_difference); + in.register_cmd(129, exec_Z3_mk_set_complement); + in.register_cmd(130, exec_Z3_mk_set_member); + in.register_cmd(131, exec_Z3_mk_set_subset); + in.register_cmd(132, exec_Z3_mk_numeral); + in.register_cmd(133, exec_Z3_mk_real); + in.register_cmd(134, exec_Z3_mk_int); + in.register_cmd(135, exec_Z3_mk_unsigned_int); + in.register_cmd(136, exec_Z3_mk_int64); + in.register_cmd(137, exec_Z3_mk_unsigned_int64); + in.register_cmd(138, exec_Z3_mk_pattern); + in.register_cmd(139, exec_Z3_mk_bound); + in.register_cmd(140, exec_Z3_mk_forall); + in.register_cmd(141, exec_Z3_mk_exists); + in.register_cmd(142, exec_Z3_mk_quantifier); + in.register_cmd(143, exec_Z3_mk_quantifier_ex); + in.register_cmd(144, exec_Z3_mk_forall_const); + in.register_cmd(145, exec_Z3_mk_exists_const); + in.register_cmd(146, exec_Z3_mk_quantifier_const); + in.register_cmd(147, exec_Z3_mk_quantifier_const_ex); + in.register_cmd(148, exec_Z3_get_ast_id); + in.register_cmd(149, exec_Z3_get_func_decl_id); + in.register_cmd(150, exec_Z3_get_sort_id); + in.register_cmd(151, exec_Z3_is_well_sorted); + in.register_cmd(152, exec_Z3_get_symbol_kind); + in.register_cmd(153, exec_Z3_get_symbol_int); + in.register_cmd(154, exec_Z3_get_symbol_string); + in.register_cmd(155, exec_Z3_get_ast_kind); + in.register_cmd(156, exec_Z3_get_ast_hash); + in.register_cmd(157, exec_Z3_get_numeral_string); + in.register_cmd(158, exec_Z3_get_numeral_decimal_string); + in.register_cmd(159, exec_Z3_get_numerator); + in.register_cmd(160, exec_Z3_get_denominator); + in.register_cmd(161, exec_Z3_get_numeral_small); + in.register_cmd(162, exec_Z3_get_numeral_int); + in.register_cmd(163, exec_Z3_get_numeral_uint); + in.register_cmd(164, exec_Z3_get_numeral_uint64); + in.register_cmd(165, exec_Z3_get_numeral_int64); + in.register_cmd(166, exec_Z3_get_numeral_rational_int64); + in.register_cmd(167, exec_Z3_get_bool_value); + in.register_cmd(168, exec_Z3_get_app_decl); + in.register_cmd(169, exec_Z3_get_app_num_args); + in.register_cmd(170, exec_Z3_get_app_arg); + in.register_cmd(171, exec_Z3_get_index_value); + in.register_cmd(172, exec_Z3_is_quantifier_forall); + in.register_cmd(173, exec_Z3_get_quantifier_weight); + in.register_cmd(174, exec_Z3_get_quantifier_num_patterns); + in.register_cmd(175, exec_Z3_get_quantifier_pattern_ast); + in.register_cmd(176, exec_Z3_get_quantifier_num_no_patterns); + in.register_cmd(177, exec_Z3_get_quantifier_no_pattern_ast); + in.register_cmd(178, exec_Z3_get_quantifier_bound_name); + in.register_cmd(179, exec_Z3_get_quantifier_bound_sort); + in.register_cmd(180, exec_Z3_get_quantifier_body); + in.register_cmd(181, exec_Z3_get_quantifier_num_bound); + in.register_cmd(182, exec_Z3_get_decl_name); + in.register_cmd(183, exec_Z3_get_decl_num_parameters); + in.register_cmd(184, exec_Z3_get_decl_parameter_kind); + in.register_cmd(185, exec_Z3_get_decl_int_parameter); + in.register_cmd(186, exec_Z3_get_decl_double_parameter); + in.register_cmd(187, exec_Z3_get_decl_symbol_parameter); + in.register_cmd(188, exec_Z3_get_decl_sort_parameter); + in.register_cmd(189, exec_Z3_get_decl_ast_parameter); + in.register_cmd(190, exec_Z3_get_decl_func_decl_parameter); + in.register_cmd(191, exec_Z3_get_decl_rational_parameter); + in.register_cmd(192, exec_Z3_get_sort_name); + in.register_cmd(193, exec_Z3_get_sort); + in.register_cmd(194, exec_Z3_get_domain_size); + in.register_cmd(195, exec_Z3_get_domain); + in.register_cmd(196, exec_Z3_get_range); + in.register_cmd(197, exec_Z3_get_sort_kind); + in.register_cmd(198, exec_Z3_get_bv_sort_size); + in.register_cmd(199, exec_Z3_get_array_sort_domain); + in.register_cmd(200, exec_Z3_get_array_sort_range); + in.register_cmd(201, exec_Z3_get_tuple_sort_mk_decl); + in.register_cmd(202, exec_Z3_get_decl_kind); + in.register_cmd(203, exec_Z3_get_tuple_sort_num_fields); + in.register_cmd(204, exec_Z3_get_tuple_sort_field_decl); + in.register_cmd(205, exec_Z3_get_datatype_sort_num_constructors); + in.register_cmd(206, exec_Z3_get_datatype_sort_constructor); + in.register_cmd(207, exec_Z3_get_datatype_sort_recognizer); + in.register_cmd(208, exec_Z3_get_datatype_sort_constructor_accessor); + in.register_cmd(209, exec_Z3_get_relation_arity); + in.register_cmd(210, exec_Z3_get_relation_column); + in.register_cmd(211, exec_Z3_get_finite_domain_sort_size); + in.register_cmd(212, exec_Z3_mk_finite_domain_sort); + in.register_cmd(213, exec_Z3_get_pattern_num_terms); + in.register_cmd(214, exec_Z3_get_pattern); + in.register_cmd(215, exec_Z3_simplify); + in.register_cmd(216, exec_Z3_simplify_ex); + in.register_cmd(217, exec_Z3_simplify_get_help); + in.register_cmd(218, exec_Z3_simplify_get_param_descrs); + in.register_cmd(219, exec_Z3_update_term); + in.register_cmd(220, exec_Z3_substitute); + in.register_cmd(221, exec_Z3_substitute_vars); + in.register_cmd(222, exec_Z3_sort_to_ast); + in.register_cmd(223, exec_Z3_func_decl_to_ast); + in.register_cmd(224, exec_Z3_pattern_to_ast); + in.register_cmd(225, exec_Z3_app_to_ast); + in.register_cmd(226, exec_Z3_to_app); + in.register_cmd(227, exec_Z3_to_func_decl); + in.register_cmd(228, exec_Z3_push); + in.register_cmd(229, exec_Z3_pop); + in.register_cmd(230, exec_Z3_get_num_scopes); + in.register_cmd(231, exec_Z3_persist_ast); + in.register_cmd(232, exec_Z3_assert_cnstr); + in.register_cmd(233, exec_Z3_check_and_get_model); + in.register_cmd(234, exec_Z3_check); + in.register_cmd(235, exec_Z3_check_assumptions); + in.register_cmd(236, exec_Z3_get_implied_equalities); + in.register_cmd(237, exec_Z3_del_model); + in.register_cmd(238, exec_Z3_soft_check_cancel); + in.register_cmd(239, exec_Z3_get_search_failure); + in.register_cmd(240, exec_Z3_get_relevant_labels); + in.register_cmd(241, exec_Z3_get_relevant_literals); + in.register_cmd(242, exec_Z3_get_guessed_literals); + in.register_cmd(243, exec_Z3_del_literals); + in.register_cmd(244, exec_Z3_get_num_literals); + in.register_cmd(245, exec_Z3_get_label_symbol); + in.register_cmd(246, exec_Z3_get_literal); + in.register_cmd(247, exec_Z3_disable_literal); + in.register_cmd(248, exec_Z3_block_literals); + in.register_cmd(249, exec_Z3_model_inc_ref); + in.register_cmd(250, exec_Z3_model_dec_ref); + in.register_cmd(251, exec_Z3_model_get_const_interp); + in.register_cmd(252, exec_Z3_model_get_func_interp); + in.register_cmd(253, exec_Z3_model_get_num_consts); + in.register_cmd(254, exec_Z3_model_get_const_decl); + in.register_cmd(255, exec_Z3_model_get_num_funcs); + in.register_cmd(256, exec_Z3_model_get_func_decl); + in.register_cmd(257, exec_Z3_model_eval); + in.register_cmd(258, exec_Z3_model_get_num_sorts); + in.register_cmd(259, exec_Z3_model_get_sort); + in.register_cmd(260, exec_Z3_model_get_sort_universe); + in.register_cmd(261, exec_Z3_is_as_array); + in.register_cmd(262, exec_Z3_get_as_array_func_decl); + in.register_cmd(263, exec_Z3_func_interp_inc_ref); + in.register_cmd(264, exec_Z3_func_interp_dec_ref); + in.register_cmd(265, exec_Z3_func_interp_get_num_entries); + in.register_cmd(266, exec_Z3_func_interp_get_entry); + in.register_cmd(267, exec_Z3_func_interp_get_else); + in.register_cmd(268, exec_Z3_func_interp_get_arity); + in.register_cmd(269, exec_Z3_func_entry_inc_ref); + in.register_cmd(270, exec_Z3_func_entry_dec_ref); + in.register_cmd(271, exec_Z3_func_entry_get_value); + in.register_cmd(272, exec_Z3_func_entry_get_num_args); + in.register_cmd(273, exec_Z3_func_entry_get_arg); + in.register_cmd(274, exec_Z3_get_model_num_constants); + in.register_cmd(275, exec_Z3_get_model_constant); + in.register_cmd(276, exec_Z3_eval_func_decl); + in.register_cmd(277, exec_Z3_is_array_value); + in.register_cmd(278, exec_Z3_get_array_value); + in.register_cmd(279, exec_Z3_get_model_num_funcs); + in.register_cmd(280, exec_Z3_get_model_func_decl); + in.register_cmd(281, exec_Z3_get_model_func_else); + in.register_cmd(282, exec_Z3_get_model_func_num_entries); + in.register_cmd(283, exec_Z3_get_model_func_entry_num_args); + in.register_cmd(284, exec_Z3_get_model_func_entry_arg); + in.register_cmd(285, exec_Z3_get_model_func_entry_value); + in.register_cmd(286, exec_Z3_eval); + in.register_cmd(287, exec_Z3_eval_decl); + in.register_cmd(288, exec_Z3_set_ast_print_mode); + in.register_cmd(289, exec_Z3_ast_to_string); + in.register_cmd(290, exec_Z3_pattern_to_string); + in.register_cmd(291, exec_Z3_sort_to_string); + in.register_cmd(292, exec_Z3_func_decl_to_string); + in.register_cmd(293, exec_Z3_model_to_string); + in.register_cmd(294, exec_Z3_benchmark_to_smtlib_string); + in.register_cmd(295, exec_Z3_context_to_string); + in.register_cmd(296, exec_Z3_statistics_to_string); + in.register_cmd(297, exec_Z3_get_context_assignment); + in.register_cmd(298, exec_Z3_parse_smtlib_string); + in.register_cmd(299, exec_Z3_parse_smtlib_file); + in.register_cmd(300, exec_Z3_get_smtlib_num_formulas); + in.register_cmd(301, exec_Z3_get_smtlib_formula); + in.register_cmd(302, exec_Z3_get_smtlib_num_assumptions); + in.register_cmd(303, exec_Z3_get_smtlib_assumption); + in.register_cmd(304, exec_Z3_get_smtlib_num_decls); + in.register_cmd(305, exec_Z3_get_smtlib_decl); + in.register_cmd(306, exec_Z3_get_smtlib_num_sorts); + in.register_cmd(307, exec_Z3_get_smtlib_sort); + in.register_cmd(308, exec_Z3_get_smtlib_error); + in.register_cmd(309, exec_Z3_parse_z3_string); + in.register_cmd(310, exec_Z3_parse_z3_file); + in.register_cmd(311, exec_Z3_parse_smtlib2_string); + in.register_cmd(312, exec_Z3_parse_smtlib2_file); + in.register_cmd(313, exec_Z3_get_error_code); + in.register_cmd(314, exec_Z3_set_error); + in.register_cmd(315, exec_Z3_get_error_msg); + in.register_cmd(316, exec_Z3_get_version); + in.register_cmd(317, exec_Z3_reset_memory); + in.register_cmd(318, exec_Z3_is_app); + in.register_cmd(319, exec_Z3_is_numeral_ast); + in.register_cmd(320, exec_Z3_get_arity); + in.register_cmd(321, exec_Z3_mk_injective_function); + in.register_cmd(322, exec_Z3_mk_fixedpoint); + in.register_cmd(323, exec_Z3_fixedpoint_inc_ref); + in.register_cmd(324, exec_Z3_fixedpoint_dec_ref); + in.register_cmd(325, exec_Z3_fixedpoint_push); + in.register_cmd(326, exec_Z3_fixedpoint_pop); + in.register_cmd(327, exec_Z3_fixedpoint_register_relation); + in.register_cmd(328, exec_Z3_fixedpoint_assert); + in.register_cmd(329, exec_Z3_fixedpoint_add_rule); + in.register_cmd(330, exec_Z3_fixedpoint_add_fact); + in.register_cmd(331, exec_Z3_fixedpoint_query); + in.register_cmd(332, exec_Z3_fixedpoint_query_relations); + in.register_cmd(333, exec_Z3_fixedpoint_get_answer); + in.register_cmd(334, exec_Z3_fixedpoint_update_rule); + in.register_cmd(335, exec_Z3_fixedpoint_get_num_levels); + in.register_cmd(336, exec_Z3_fixedpoint_get_cover_delta); + in.register_cmd(337, exec_Z3_fixedpoint_add_cover); + in.register_cmd(338, exec_Z3_fixedpoint_get_statistics); + in.register_cmd(339, exec_Z3_fixedpoint_get_help); + in.register_cmd(340, exec_Z3_fixedpoint_get_param_descrs); + in.register_cmd(341, exec_Z3_fixedpoint_set_params); + in.register_cmd(342, exec_Z3_fixedpoint_to_string); + in.register_cmd(343, exec_Z3_fixedpoint_get_reason_unknown); + in.register_cmd(344, exec_Z3_fixedpoint_set_predicate_representation); + in.register_cmd(345, exec_Z3_fixedpoint_simplify_rules); + in.register_cmd(346, exec_Z3_mk_params); + in.register_cmd(347, exec_Z3_params_inc_ref); + in.register_cmd(348, exec_Z3_params_dec_ref); + in.register_cmd(349, exec_Z3_params_set_bool); + in.register_cmd(350, exec_Z3_params_set_uint); + in.register_cmd(351, exec_Z3_params_set_double); + in.register_cmd(352, exec_Z3_params_set_symbol); + in.register_cmd(353, exec_Z3_params_to_string); + in.register_cmd(354, exec_Z3_params_validate); + in.register_cmd(355, exec_Z3_param_descrs_inc_ref); + in.register_cmd(356, exec_Z3_param_descrs_dec_ref); + in.register_cmd(357, exec_Z3_param_descrs_get_kind); + in.register_cmd(358, exec_Z3_param_descrs_size); + in.register_cmd(359, exec_Z3_param_descrs_get_name); + in.register_cmd(360, exec_Z3_interrupt); + in.register_cmd(361, exec_Z3_get_error_msg_ex); + in.register_cmd(362, exec_Z3_translate); + in.register_cmd(363, exec_Z3_mk_goal); + in.register_cmd(364, exec_Z3_goal_inc_ref); + in.register_cmd(365, exec_Z3_goal_dec_ref); + in.register_cmd(366, exec_Z3_goal_precision); + in.register_cmd(367, exec_Z3_goal_assert); + in.register_cmd(368, exec_Z3_goal_inconsistent); + in.register_cmd(369, exec_Z3_goal_depth); + in.register_cmd(370, exec_Z3_goal_reset); + in.register_cmd(371, exec_Z3_goal_size); + in.register_cmd(372, exec_Z3_goal_formula); + in.register_cmd(373, exec_Z3_goal_num_exprs); + in.register_cmd(374, exec_Z3_goal_is_decided_sat); + in.register_cmd(375, exec_Z3_goal_is_decided_unsat); + in.register_cmd(376, exec_Z3_goal_translate); + in.register_cmd(377, exec_Z3_goal_to_string); + in.register_cmd(378, exec_Z3_mk_tactic); + in.register_cmd(379, exec_Z3_mk_probe); + in.register_cmd(380, exec_Z3_tactic_inc_ref); + in.register_cmd(381, exec_Z3_tactic_dec_ref); + in.register_cmd(382, exec_Z3_probe_inc_ref); + in.register_cmd(383, exec_Z3_probe_dec_ref); + in.register_cmd(384, exec_Z3_tactic_and_then); + in.register_cmd(385, exec_Z3_tactic_or_else); + in.register_cmd(386, exec_Z3_tactic_par_or); + in.register_cmd(387, exec_Z3_tactic_par_and_then); + in.register_cmd(388, exec_Z3_tactic_try_for); + in.register_cmd(389, exec_Z3_tactic_when); + in.register_cmd(390, exec_Z3_tactic_cond); + in.register_cmd(391, exec_Z3_tactic_repeat); + in.register_cmd(392, exec_Z3_tactic_skip); + in.register_cmd(393, exec_Z3_tactic_fail); + in.register_cmd(394, exec_Z3_tactic_fail_if); + in.register_cmd(395, exec_Z3_tactic_fail_if_not_decided); + in.register_cmd(396, exec_Z3_tactic_using_params); + in.register_cmd(397, exec_Z3_probe_const); + in.register_cmd(398, exec_Z3_probe_lt); + in.register_cmd(399, exec_Z3_probe_le); + in.register_cmd(400, exec_Z3_probe_gt); + in.register_cmd(401, exec_Z3_probe_ge); + in.register_cmd(402, exec_Z3_probe_eq); + in.register_cmd(403, exec_Z3_probe_and); + in.register_cmd(404, exec_Z3_probe_or); + in.register_cmd(405, exec_Z3_probe_not); + in.register_cmd(406, exec_Z3_get_num_tactics); + in.register_cmd(407, exec_Z3_get_tactic_name); + in.register_cmd(408, exec_Z3_get_num_probes); + in.register_cmd(409, exec_Z3_get_probe_name); + in.register_cmd(410, exec_Z3_tactic_get_help); + in.register_cmd(411, exec_Z3_tactic_get_param_descrs); + in.register_cmd(412, exec_Z3_tactic_get_descr); + in.register_cmd(413, exec_Z3_probe_get_descr); + in.register_cmd(414, exec_Z3_probe_apply); + in.register_cmd(415, exec_Z3_tactic_apply); + in.register_cmd(416, exec_Z3_tactic_apply_ex); + in.register_cmd(417, exec_Z3_apply_result_inc_ref); + in.register_cmd(418, exec_Z3_apply_result_dec_ref); + in.register_cmd(419, exec_Z3_apply_result_to_string); + in.register_cmd(420, exec_Z3_apply_result_get_num_subgoals); + in.register_cmd(421, exec_Z3_apply_result_get_subgoal); + in.register_cmd(422, exec_Z3_apply_result_convert_model); + in.register_cmd(423, exec_Z3_mk_solver); + in.register_cmd(424, exec_Z3_mk_simple_solver); + in.register_cmd(425, exec_Z3_mk_solver_for_logic); + in.register_cmd(426, exec_Z3_mk_solver_from_tactic); + in.register_cmd(427, exec_Z3_solver_get_help); + in.register_cmd(428, exec_Z3_solver_get_param_descrs); + in.register_cmd(429, exec_Z3_solver_set_params); + in.register_cmd(430, exec_Z3_solver_inc_ref); + in.register_cmd(431, exec_Z3_solver_dec_ref); + in.register_cmd(432, exec_Z3_solver_push); + in.register_cmd(433, exec_Z3_solver_pop); + in.register_cmd(434, exec_Z3_solver_reset); + in.register_cmd(435, exec_Z3_solver_get_num_scopes); + in.register_cmd(436, exec_Z3_solver_assert); + in.register_cmd(437, exec_Z3_solver_get_assertions); + in.register_cmd(438, exec_Z3_solver_check); + in.register_cmd(439, exec_Z3_solver_check_assumptions); + in.register_cmd(440, exec_Z3_solver_get_model); + in.register_cmd(441, exec_Z3_solver_get_proof); + in.register_cmd(442, exec_Z3_solver_get_unsat_core); + in.register_cmd(443, exec_Z3_solver_get_reason_unknown); + in.register_cmd(444, exec_Z3_solver_get_statistics); + in.register_cmd(445, exec_Z3_solver_to_string); + in.register_cmd(446, exec_Z3_stats_to_string); + in.register_cmd(447, exec_Z3_stats_inc_ref); + in.register_cmd(448, exec_Z3_stats_dec_ref); + in.register_cmd(449, exec_Z3_stats_size); + in.register_cmd(450, exec_Z3_stats_get_key); + in.register_cmd(451, exec_Z3_stats_is_uint); + in.register_cmd(452, exec_Z3_stats_is_double); + in.register_cmd(453, exec_Z3_stats_get_uint_value); + in.register_cmd(454, exec_Z3_stats_get_double_value); + in.register_cmd(455, exec_Z3_mk_ast_vector); + in.register_cmd(456, exec_Z3_ast_vector_inc_ref); + in.register_cmd(457, exec_Z3_ast_vector_dec_ref); + in.register_cmd(458, exec_Z3_ast_vector_size); + in.register_cmd(459, exec_Z3_ast_vector_get); + in.register_cmd(460, exec_Z3_ast_vector_set); + in.register_cmd(461, exec_Z3_ast_vector_resize); + in.register_cmd(462, exec_Z3_ast_vector_push); + in.register_cmd(463, exec_Z3_ast_vector_translate); + in.register_cmd(464, exec_Z3_ast_vector_to_string); + in.register_cmd(465, exec_Z3_mk_ast_map); + in.register_cmd(466, exec_Z3_ast_map_inc_ref); + in.register_cmd(467, exec_Z3_ast_map_dec_ref); + in.register_cmd(468, exec_Z3_ast_map_contains); + in.register_cmd(469, exec_Z3_ast_map_find); + in.register_cmd(470, exec_Z3_ast_map_insert); + in.register_cmd(471, exec_Z3_ast_map_erase); + in.register_cmd(472, exec_Z3_ast_map_size); + in.register_cmd(473, exec_Z3_ast_map_reset); + in.register_cmd(474, exec_Z3_ast_map_keys); + in.register_cmd(475, exec_Z3_ast_map_to_string); +} diff --git a/lib/api_config_params.cpp b/lib/api_config_params.cpp new file mode 100644 index 000000000..07e043726 --- /dev/null +++ b/lib/api_config_params.cpp @@ -0,0 +1,122 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_config_params.cpp + +Abstract: + Configuration parameters + +Author: + + Leonardo de Moura (leonardo) 2012-02-29. + +Revision History: + +--*/ +#include"z3.h" +#include"api_context.h" +#include"api_config_params.h" +#include"pp.h" +#include"api_log_macros.h" +#include"api_util.h" +#include"symbol.h" + +namespace api { + + config_params::config_params(): + m_ini(false /* do not abort on errors */) { + register_verbosity_level(m_ini); + register_warning(m_ini); + m_params.register_params(m_ini); + register_pp_params(m_ini); + } + + config_params::config_params(front_end_params const & p):m_params(p) { + register_verbosity_level(m_ini); + register_warning(m_ini); + register_pp_params(m_ini); + } + +}; + +extern std::string smt_keyword2opt_name(symbol const & opt); + +extern "C" { + Z3_config Z3_API Z3_mk_config() { + LOG_Z3_mk_config(); + memory::initialize(0); + Z3_config r = reinterpret_cast(alloc(api::config_params)); + RETURN_Z3(r); + } + + void Z3_API Z3_del_config(Z3_config c) { + LOG_Z3_del_config(c); + dealloc((reinterpret_cast(c))); + } + + void Z3_API Z3_set_param_value(Z3_config c, char const * param_id, char const * param_value) { + try { + LOG_Z3_set_param_value(c, param_id, param_value); + api::config_params* p = reinterpret_cast(c); + if (param_id != 0 && param_id[0] == ':') { + // Allow SMT2 style paramater names such as :model, :relevancy, etc + std::string new_param_id = smt_keyword2opt_name(symbol(param_id)); + p->m_ini.set_param_value(new_param_id.c_str(), param_value); + } + else { + p->m_ini.set_param_value(param_id, param_value); + } + } + catch (set_get_param_exception & ex) { + // The error handler was not set yet. + // Just throw a warning. + std::ostringstream buffer; + buffer << "Error setting " << param_id << ", " << ex.get_msg(); + warning_msg(buffer.str().c_str()); + } + } + + void Z3_API Z3_update_param_value(Z3_context c, Z3_string param_id, Z3_string param_value) { + LOG_Z3_update_param_value(c, param_id, param_value); + RESET_ERROR_CODE(); + ini_params ini; + mk_c(c)->fparams().register_params(ini); + register_pp_params(ini); + register_verbosity_level(ini); + register_warning(ini); + if (mk_c(c)->has_solver()) { + ini.freeze(); + } + if (param_id != 0 && param_id[0] == ':') { + // Allow SMT2 style paramater names such as :model, :relevancy, etc + std::string new_param_id = smt_keyword2opt_name(symbol(param_id)); + ini.set_param_value(new_param_id.c_str(), param_value); + } + else { + ini.set_param_value(param_id, param_value); + } + memory::set_high_watermark(static_cast(mk_c(c)->fparams().m_memory_high_watermark)*1024*1024); + memory::set_max_size(static_cast(mk_c(c)->fparams().m_memory_max_size)*1024*1024); + } + + Z3_bool Z3_API Z3_get_param_value(Z3_context c, Z3_string param_id, Z3_string* param_value) { + LOG_Z3_get_param_value(c, param_id, param_value); + ini_params ini; + mk_c(c)->fparams().register_params(ini); + register_verbosity_level(ini); + register_pp_params(ini); + register_warning(ini); + std::string param_value_s; + if (!ini.get_param_value(param_id, param_value_s)) { + if (param_value) *param_value = 0; + return false; + } + if (param_value) { + *param_value = mk_c(c)->mk_external_string(param_value_s); + } + return true; + } + +}; diff --git a/lib/api_config_params.h b/lib/api_config_params.h new file mode 100644 index 000000000..22e713c50 --- /dev/null +++ b/lib/api_config_params.h @@ -0,0 +1,36 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_config_params.h + +Abstract: + Configuration parameters + +Author: + + Leonardo de Moura (leonardo) 2012-02-29. + +Revision History: + +--*/ +#ifndef _API_CONFIG_PARAMS_H_ +#define _API_CONFIG_PARAMS_H_ + +#include"ini_file.h" +#include"front_end_params.h" + +namespace api { + + class config_params { + public: + ini_params m_ini; + front_end_params m_params; + config_params(); + config_params(front_end_params const & p); + }; + +}; + +#endif diff --git a/lib/api_context.cpp b/lib/api_context.cpp new file mode 100644 index 000000000..d281af2bd --- /dev/null +++ b/lib/api_context.cpp @@ -0,0 +1,571 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_context.cpp + +Abstract: + Interface of Z3 with "external world". + + It was called _Z3_context + +Author: + + Leonardo de Moura (leonardo) 2012-02-29. + +Revision History: + +--*/ +#include"api_context.h" +#include"api_config_params.h" +#include"smtparser.h" +#include"version.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"api_log_macros.h" +#include"api_util.h" +#include"install_tactics.h" + +namespace api { + + static void default_error_handler(Z3_context, Z3_error_code c) { + printf("Error: %s\n", Z3_get_error_msg(c)); + exit(1); + } + + Z3_search_failure mk_Z3_search_failure(smt::failure f) { + switch(f) { + case smt::OK: return Z3_NO_FAILURE; + case smt::UNKNOWN: return Z3_UNKNOWN; + case smt::TIMEOUT: return Z3_TIMEOUT; + case smt::MEMOUT: return Z3_MEMOUT_WATERMARK; + case smt::CANCELED: return Z3_CANCELED; + case smt::NUM_CONFLICTS: return Z3_NUM_CONFLICTS; + case smt::THEORY: return Z3_THEORY; + case smt::QUANTIFIERS: return Z3_QUANTIFIERS; + default: + UNREACHABLE(); + break; + } + return static_cast(f); + } + + context::param_ini::param_ini(front_end_params & p) : m_params(p) { + p.open_trace_file(); + } + + context::param_ini::~param_ini() { + m_params.close_trace_file(); + } + + context::add_plugins::add_plugins(ast_manager & m) { + m.register_decl_plugins(); + } + + // ------------------------ + // + // Core + // + // ------------------------ + + context::set_interruptable::set_interruptable(context & ctx, event_handler & i): + m_ctx(ctx) { + #pragma omp critical (set_interruptable) + { + SASSERT(m_ctx.m_interruptable == 0); + m_ctx.m_interruptable = &i; + } + } + + context::set_interruptable::~set_interruptable() { + #pragma omp critical (set_interruptable) + { + m_ctx.m_interruptable = 0; + } + } + + context::context(config_params * p, bool user_ref_count): + m_params(p ? p->m_params : front_end_params()), + m_param_ini(m_params), + m_user_ref_count(user_ref_count), + m_manager(m_params.m_proof_mode, m_params.m_trace_stream), + m_plugins(m_manager), + m_arith_util(m_manager), + m_bv_util(m_manager), + m_datalog_util(m_manager), + m_last_result(m_manager), + m_ast_trail(m_manager), + m_replay_stack() { + + m_solver = 0; + m_error_code = Z3_OK; + m_print_mode = Z3_PRINT_SMTLIB_FULL; + m_searching = false; + + m_interruptable = 0; + + m_smtlib_parser = 0; + m_smtlib_parser_has_decls = false; + + z3_bound_num_procs(); + // + // Configuration parameter settings. + // + memory::set_high_watermark(static_cast(m_params.m_memory_high_watermark)*1024*1024); + memory::set_max_size(static_cast(m_params.m_memory_max_size)*1024*1024); + if (m_params.m_debug_ref_count) { + m_manager.debug_ref_count(); + } + + m_error_handler = &default_error_handler; + + m_basic_fid = m_manager.get_basic_family_id(); + m_arith_fid = m_manager.get_family_id("arith"); + m_bv_fid = m_manager.get_family_id("bv"); + m_array_fid = m_manager.get_family_id("array"); + m_dt_fid = m_manager.get_family_id("datatype"); + m_datalog_fid = m_manager.get_family_id("datalog_relation"); + m_dt_plugin = static_cast(m_manager.get_plugin(m_dt_fid)); + + if (!m_user_ref_count) { + m_replay_stack.push_back(0); + } + + install_tactics(*this); + } + + + context::~context() { + m_last_obj = 0; + if (!m_user_ref_count) { + for (unsigned i = 0; i < m_replay_stack.size(); ++i) { + dealloc(m_replay_stack[i]); + } + } + reset_parser(); + dealloc(m_solver); + } + + void context::interrupt() { + #pragma omp critical (set_interruptable) + { + if (m_interruptable) + (*m_interruptable)(); + } + } + + void context::set_error_code(Z3_error_code err) { + m_error_code = err; + if (err != Z3_OK) + invoke_error_handler(err); + } + + void context::check_searching() { + if (m_searching) { + set_error_code(Z3_INVALID_USAGE); // TBD: error code could be fixed. + } + } + + char * context::mk_external_string(char const * str) { + m_string_buffer = str; + return const_cast(m_string_buffer.c_str()); + } + + char * context::mk_external_string(std::string const & str) { + m_string_buffer = str; + return const_cast(m_string_buffer.c_str()); + } + + expr * context::mk_numeral_core(rational const & n, sort * s) { + expr* e = 0; + family_id fid = s->get_family_id(); + if (fid == m_arith_fid) { + e = m_arith_util.mk_numeral(n, s); + } + else if (fid == m_bv_fid) { + e = m_bv_util.mk_numeral(n, s); + } + else if (fid == get_datalog_fid() && n.is_uint64()) { + uint64 sz; + if (m_datalog_util.try_get_size(s, sz) && + sz <= n.get_uint64()) { + invoke_error_handler(Z3_INVALID_ARG); + } + e = m_datalog_util.mk_numeral(n.get_uint64(), s); + } + else { + invoke_error_handler(Z3_INVALID_ARG); + } + save_ast_trail(e); + return e; + } + + expr * context::mk_and(unsigned num_exprs, expr * const * exprs) { + switch(num_exprs) { + case 0: + return m_manager.mk_true(); + case 1: + save_ast_trail(exprs[0]); + return exprs[0]; + default: { + expr * a = m_manager.mk_and(num_exprs, exprs); + save_ast_trail(a); + return a; + } } + } + + void context::persist_ast(ast * n, unsigned num_scopes) { + // persist_ast is irrelevant when m_user_ref_count == true + if (m_user_ref_count) + return; + if (num_scopes > m_ast_lim.size()) { + num_scopes = m_ast_lim.size(); + } + SASSERT(m_replay_stack.size() > num_scopes); + unsigned j = m_replay_stack.size() - num_scopes - 1; + if (!m_replay_stack[j]) { + m_replay_stack[j] = alloc(ast_ref_vector, m_manager); + } + m_replay_stack[j]->push_back(n); + } + + void context::save_ast_trail(ast * n) { + SASSERT(m().contains(n)); + if (m_user_ref_count) { + // Corner case bug: n may be in m_last_result, and this is the only reference to n. + // When, we execute reset() it is deleted + // To avoid this bug, I bump the reference counter before reseting m_last_result + m().inc_ref(n); + m_last_result.reset(); + m_last_result.push_back(n); + m().dec_ref(n); + } + else { + m_ast_trail.push_back(n); + } + } + + void context::save_multiple_ast_trail(ast * n) { + if (m_user_ref_count) + m_last_result.push_back(n); + else + m_ast_trail.push_back(n); + } + + void context::reset_last_result() { + if (m_user_ref_count) + m_last_result.reset(); + m_last_obj = 0; + } + + void context::save_object(object * r) { + m_last_obj = r; + } + + void context::handle_exception(z3_exception & ex) { + if (ex.has_error_code()) { + switch(ex.error_code()) { + case ERR_MEMOUT: + set_error_code(Z3_MEMOUT_FAIL); + break; + case ERR_PARSER: + set_error_code(Z3_PARSER_ERROR); + break; + case ERR_INI_FILE: + set_error_code(Z3_INVALID_ARG); + break; + case ERR_OPEN_FILE: + set_error_code(Z3_FILE_ACCESS_ERROR); + break; + default: + set_error_code(Z3_INTERNAL_FATAL); + break; + } + } + else { + m_exception_msg = ex.msg(); + set_error_code(Z3_EXCEPTION); + } + } + + void context::invoke_error_handler(Z3_error_code c) { + if (m_error_handler) { + if (g_z3_log) { + // error handler can do crazy stuff such as longjmp + g_z3_log_enabled = true; + } + m_error_handler(reinterpret_cast(this), c); + } + } + + void context::check_sorts(ast * n) { + if (!m().check_sorts(n)) { + switch(n->get_kind()) { + case AST_APP: { + std::ostringstream buffer; + app * a = to_app(n); + buffer << mk_pp(a->get_decl(), m()) << " applied to: "; + if (a->get_num_args() > 1) buffer << "\n"; + for (unsigned i = 0; i < a->get_num_args(); ++i) { + buffer << mk_bounded_pp(a->get_arg(i), m(), 3) << " of sort "; + buffer << mk_pp(m().get_sort(a->get_arg(i)), m()) << "\n"; + } + warning_msg("%s",buffer.str().c_str()); + break; + } + case AST_VAR: + case AST_QUANTIFIER: + case AST_SORT: + case AST_FUNC_DECL: + break; + } + set_error_code(Z3_SORT_ERROR); + } + } + + // ------------------------ + // + // Solver interface for backward compatibility + // + // ------------------------ + + smt::solver & context::get_solver() { + if (!m_solver) { + m_solver = alloc(smt::solver, m_manager, m_params); + } + return *m_solver; + } + + void context::assert_cnstr(expr * a) { + get_solver().assert_expr(a); + } + + lbool context::check(model_ref & m) { + flet searching(m_searching, true); + lbool r; + r = get_solver().check(); + if (r != l_false) + get_solver().get_model(m); + return r; + } + + void context::push() { + get_solver().push(); + if (!m_user_ref_count) { + m_ast_lim.push_back(m_ast_trail.size()); + m_replay_stack.push_back(0); + } + } + + void context::pop(unsigned num_scopes) { + for (unsigned i = 0; i < num_scopes; ++i) { + if (!m_user_ref_count) { + unsigned sz = m_ast_lim.back(); + m_ast_lim.pop_back(); + dealloc(m_replay_stack.back()); + m_replay_stack.pop_back(); + while (m_ast_trail.size() > sz) { + m_ast_trail.pop_back(); + } + } + } + get_solver().pop(num_scopes); + } + + // ------------------------ + // + // Parser interface for backward compatibility + // + // ------------------------ + + void context::reset_parser() { + if (m_smtlib_parser) { + dealloc(m_smtlib_parser); + m_smtlib_parser = 0; + m_smtlib_parser_has_decls = false; + m_smtlib_parser_decls.reset(); + m_smtlib_parser_sorts.reset(); + } + SASSERT(!m_smtlib_parser_has_decls); + } + + void context::extract_smtlib_parser_decls() { + if (m_smtlib_parser) { + if (!m_smtlib_parser_has_decls) { + smtlib::symtable * table = m_smtlib_parser->get_benchmark()->get_symtable(); + table->get_func_decls(m_smtlib_parser_decls); + table->get_sorts(m_smtlib_parser_sorts); + m_smtlib_parser_has_decls = true; + } + } + else { + m_smtlib_parser_decls.reset(); + m_smtlib_parser_sorts.reset(); + } + } +}; + + +// ------------------------ +// +// Context creation API +// +// ------------------------ + +extern "C" { + + Z3_context Z3_API Z3_mk_context(Z3_config c) { + LOG_Z3_mk_context(c); + memory::initialize(0); + Z3_context r = reinterpret_cast(alloc(api::context, reinterpret_cast(c), false)); + RETURN_Z3(r); + } + + Z3_context Z3_API Z3_mk_context_rc(Z3_config c) { + LOG_Z3_mk_context_rc(c); + memory::initialize(0); + Z3_context r = reinterpret_cast(alloc(api::context, reinterpret_cast(c), true)); + RETURN_Z3(r); + } + + void Z3_API Z3_del_context(Z3_context c) { + Z3_TRY; + LOG_Z3_del_context(c); + RESET_ERROR_CODE(); + dealloc(mk_c(c)); + Z3_CATCH; + } + + void Z3_API Z3_interrupt(Z3_context c) { + Z3_TRY; + LOG_Z3_interrupt(c); + mk_c(c)->interrupt(); + Z3_CATCH; + } + + void Z3_API Z3_toggle_warning_messages(Z3_bool enabled) { + LOG_Z3_toggle_warning_messages(enabled); + enable_warning_messages(enabled != 0); + } + + void Z3_API Z3_inc_ref(Z3_context c, Z3_ast a) { + Z3_TRY; + LOG_Z3_inc_ref(c, a); + RESET_ERROR_CODE(); + mk_c(c)->m().inc_ref(to_ast(a)); + Z3_CATCH; + } + + void Z3_API Z3_dec_ref(Z3_context c, Z3_ast a) { + Z3_TRY; + LOG_Z3_dec_ref(c, a); + RESET_ERROR_CODE(); + if (to_ast(a)->get_ref_count() == 0) { + SET_ERROR_CODE(Z3_DEC_REF_ERROR); + return; + } + mk_c(c)->m().dec_ref(to_ast(a)); + Z3_CATCH; + } + + Z3_bool Z3_API Z3_set_logic(Z3_context c, Z3_string logic) { + Z3_TRY; + LOG_Z3_set_logic(c, logic); + RESET_ERROR_CODE(); + return mk_c(c)->get_solver().set_logic(symbol(logic)); + Z3_CATCH_RETURN(Z3_FALSE); + } + + void Z3_API Z3_get_version(unsigned * major, + unsigned * minor, + unsigned * build_number, + unsigned * revision_number) { + LOG_Z3_get_version(major, minor, build_number, revision_number); + *major = Z3_MAJOR_VERSION; + *minor = Z3_MINOR_VERSION; + *build_number = Z3_BUILD_NUMBER; + *revision_number = Z3_REVISION_NUMBER; + } + + void Z3_API Z3_reset_memory(void) { + LOG_Z3_reset_memory(); + memory::finalize(); + memory::initialize(0); + } + + Z3_error_code Z3_API Z3_get_error_code(Z3_context c) { + LOG_Z3_get_error_code(c); + return mk_c(c)->get_error_code(); + } + + void Z3_API Z3_set_error_handler(Z3_context c, Z3_error_handler h) { + RESET_ERROR_CODE(); + mk_c(c)->set_error_handler(h); + // [Leo]: using exception handling, we don't need global error handlers anymore + } + + void Z3_API Z3_set_error(__in Z3_context c, __in Z3_error_code e) { + SET_ERROR_CODE(e); + } + + static char const * _get_error_msg_ex(Z3_context c, Z3_error_code err) { + switch(err) { + case Z3_OK: return "ok"; + case Z3_SORT_ERROR: return "type error"; + case Z3_IOB: return "index out of bounds"; + case Z3_INVALID_ARG: return "invalid argument"; + case Z3_PARSER_ERROR: return "parser error"; + case Z3_NO_PARSER: return "parser (data) is not available"; + case Z3_INVALID_PATTERN: return "invalid pattern"; + case Z3_MEMOUT_FAIL: return "out of memory"; + case Z3_FILE_ACCESS_ERROR: return "file access error"; + case Z3_INTERNAL_FATAL: return "internal error"; + case Z3_INVALID_USAGE: return "invalid usage"; + case Z3_DEC_REF_ERROR: return "invalid dec_ref command"; + case Z3_EXCEPTION: return c == 0 ? "Z3 exception" : mk_c(c)->get_exception_msg(); + default: return "unknown"; + } + } + + char const * Z3_API Z3_get_error_msg(Z3_error_code err) { + LOG_Z3_get_error_msg(err); + return _get_error_msg_ex(0, err); + } + + char const * Z3_API Z3_get_error_msg_ex(Z3_context c, Z3_error_code err) { + LOG_Z3_get_error_msg_ex(c, err); + return _get_error_msg_ex(c, err); + } + + void Z3_API Z3_persist_ast(Z3_context c, Z3_ast n, unsigned num_scopes) { + Z3_TRY; + LOG_Z3_persist_ast(c, n, num_scopes); + RESET_ERROR_CODE(); + CHECK_VALID_AST(to_ast(n), ); + mk_c(c)->persist_ast(to_ast(n), num_scopes); + Z3_CATCH; + } + + void Z3_API Z3_set_ast_print_mode(Z3_context c, Z3_ast_print_mode mode) { + Z3_TRY; + LOG_Z3_set_ast_print_mode(c, mode); + RESET_ERROR_CODE(); + mk_c(c)->set_print_mode(mode); + Z3_CATCH; + } + +}; + +ast_manager & Z3_API Z3_get_manager(__in Z3_context c) { + return mk_c(c)->m(); +} + +front_end_params& Z3_API Z3_get_parameters(__in Z3_context c) { + return mk_c(c)->fparams(); +} + +Z3_context Z3_mk_context_from_params(front_end_params const& p) { + api::config_params cp(p); + return reinterpret_cast(alloc(api::context, &cp)); +} diff --git a/lib/api_context.h b/lib/api_context.h new file mode 100644 index 000000000..4bb0b6d18 --- /dev/null +++ b/lib/api_context.h @@ -0,0 +1,216 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_context.h + +Abstract: + Interface of Z3 with "external world". + + It was called _Z3_context + +Author: + + Leonardo de Moura (leonardo) 2012-02-29. + +Revision History: + +--*/ +#ifndef _API_CONTEXT_H_ +#define _API_CONTEXT_H_ + +#include"z3.h" +#include"ast.h" +#include"api_util.h" +#include"arith_decl_plugin.h" +#include"bv_decl_plugin.h" +#include"datatype_decl_plugin.h" +#include"dl_decl_plugin.h" +#include"smt_solver.h" +#include"front_end_params.h" +#include"event_handler.h" +#include"tactic_manager.h" + +namespace smtlib { + class parser; +}; + +namespace api { + class config_params; + + Z3_search_failure mk_Z3_search_failure(smt::failure f); + + + class context : public tactic_manager { + class param_ini { + front_end_params & m_params; + public: + param_ini(front_end_params & p); + ~param_ini(); + }; + + struct add_plugins { add_plugins(ast_manager & m); }; + + front_end_params m_params; + param_ini m_param_ini; + bool m_user_ref_count; //!< if true, the user is responsible for managing referenc counters. + ast_manager m_manager; + add_plugins m_plugins; + + arith_util m_arith_util; + bv_util m_bv_util; + datalog::dl_decl_util m_datalog_util; + + smt::solver * m_solver; // General purpose solver for backward compatibility + + ast_ref_vector m_last_result; //!< used when m_user_ref_count == true + ast_ref_vector m_ast_trail; //!< used when m_user_ref_count == false + unsigned_vector m_ast_lim; + ptr_vector m_replay_stack; + + ref m_last_obj; //!< reference to the last API object returned by the APIs + + family_id m_basic_fid; + family_id m_array_fid; + family_id m_arith_fid; + family_id m_bv_fid; + family_id m_dt_fid; + family_id m_datalog_fid; + datatype_decl_plugin * m_dt_plugin; + + std::string m_string_buffer; // temporary buffer used to cache strings sent to the "external" world. + + Z3_error_code m_error_code; + Z3_error_handler * m_error_handler; + std::string m_exception_msg; // catch the message associated with a Z3 exception + bool m_searching; + Z3_ast_print_mode m_print_mode; + + event_handler * m_interruptable; // Reference to an object that can be interrupted by Z3_interrupt + public: + // Scoped obj for setting m_interruptable + class set_interruptable { + context & m_ctx; + public: + set_interruptable(context & ctx, event_handler & i); + ~set_interruptable(); + }; + + // ------------------------ + // + // Core + // + // ------------------------ + + context(config_params * p, bool user_ref_count = false); + ~context(); + + front_end_params & fparams() { return m_params; } + ast_manager & m() { return m_manager; } + arith_util & autil() { return m_arith_util; } + bv_util & bvutil() { return m_bv_util; } + datalog::dl_decl_util & datalog_util() { return m_datalog_util; } + family_id get_basic_fid() const { return m_basic_fid; } + family_id get_array_fid() const { return m_array_fid; } + family_id get_arith_fid() const { return m_arith_fid; } + family_id get_bv_fid() const { return m_bv_fid; } + family_id get_dt_fid() const { return m_dt_fid; } + family_id get_datalog_fid() const { return m_datalog_fid; } + datatype_decl_plugin * get_dt_plugin() const { return m_dt_plugin; } + + Z3_error_code get_error_code() const { return m_error_code; } + void reset_error_code() { m_error_code = Z3_OK; } + void set_error_code(Z3_error_code err); + void set_error_handler(Z3_error_handler h) { m_error_handler = h; } + // Sign an error if solver is searching + void check_searching(); + + Z3_ast_print_mode get_print_mode() const { return m_print_mode; } + void set_print_mode(Z3_ast_print_mode m) { m_print_mode = m; } + + // Store a copy of str in m_string_buffer, and return a reference to it. + // This method is used to communicate local/internal strings with the "external world" + char * mk_external_string(char const * str); + char * mk_external_string(std::string const & str); + + // Create a numeral of the given sort + expr * mk_numeral_core(rational const & n, sort * s); + + // Return a conjuction that will be exposed to the "external" world. + expr * mk_and(unsigned num_exprs, expr * const * exprs); + + // Hack for preventing an AST for being GC when ref-count is not used + void persist_ast(ast * n, unsigned num_scopes); + + // "Save" an AST that will exposed to the "external" world. + void save_ast_trail(ast * n); + + // Similar to previous method, but it "adds" n to the result. + void save_multiple_ast_trail(ast * n); + + // Reset the cache that stores the ASTs exposed in the previous call. + // This is a NOOP if ref-count is disabled. + void reset_last_result(); + + // "Save" a reference to an object that is exposed by the API + void save_object(object * r); + + // Process exception: save message and set error code. + void handle_exception(z3_exception & ex); + char const * get_exception_msg() const { return m_exception_msg.c_str(); } + + // Interrupt the current interruptable object + void interrupt(); + + void invoke_error_handler(Z3_error_code c); + + static void out_of_memory_handler(void * _ctx); + + void check_sorts(ast * n); + // ------------------------ + // + // Solver interface for backward compatibility + // + // ------------------------ + + bool has_solver() const { return m_solver != 0; } + smt::solver & get_solver(); + void assert_cnstr(expr * a); + lbool check(model_ref & m); + void push(); + void pop(unsigned num_scopes); + unsigned get_num_scopes() const { return m_ast_lim.size(); } + + // ------------------------ + // + // Parser interface for backward compatibility + // + // ------------------------ + + // TODO: move to a "parser" object visible to the external world. + std::string m_smtlib_error_buffer; + smtlib::parser * m_smtlib_parser; + bool m_smtlib_parser_has_decls; + ptr_vector m_smtlib_parser_decls; + ptr_vector m_smtlib_parser_sorts; + + void reset_parser(); + void extract_smtlib_parser_decls(); + + }; + +}; + +inline api::context * mk_c(Z3_context c) { return reinterpret_cast(c); } +#define RESET_ERROR_CODE() { mk_c(c)->reset_error_code(); } +#define SET_ERROR_CODE(ERR) { mk_c(c)->set_error_code(ERR); } +#define CHECK_NON_NULL(_p_,_ret_) { if (_p_ == 0) { SET_ERROR_CODE(Z3_INVALID_ARG); return _ret_; } } +#define CHECK_VALID_AST(_a_, _ret_) { if (_a_ == 0 || !CHECK_REF_COUNT(_a_)) { SET_ERROR_CODE(Z3_INVALID_ARG); return _ret_; } } +#define CHECK_SEARCHING(c) mk_c(c)->check_searching(); +inline bool is_expr(Z3_ast a) { return is_expr(to_ast(a)); } +inline bool is_bool_expr(Z3_context c, Z3_ast a) { return is_expr(a) && mk_c(c)->m().is_bool(to_expr(a)); } +#define CHECK_FORMULA(_a_, _ret_) { if (_a_ == 0 || !CHECK_REF_COUNT(_a_) || !is_bool_expr(c, _a_)) { SET_ERROR_CODE(Z3_INVALID_ARG); return _ret_; } } +inline void check_sorts(Z3_context c, ast * n) { mk_c(c)->check_sorts(n); } + +#endif diff --git a/lib/api_datalog.cpp b/lib/api_datalog.cpp new file mode 100644 index 000000000..b15f34629 --- /dev/null +++ b/lib/api_datalog.cpp @@ -0,0 +1,561 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_datalog.cpp + +Abstract: + Datalog API + +Author: + + Leonardo de Moura (leonardo) 2012-02-29. + +Revision History: + +--*/ +#include"api_datalog.h" +#include"api_context.h" +#include"api_util.h" +#include"dl_context.h" +#include"ast_pp.h" +#include"api_ast_vector.h" +#include"api_log_macros.h" +#include"api_stats.h" +#include"datalog_parser.h" +#include"cancel_eh.h" +#include"scoped_timer.h" + +namespace api { + + fixedpoint_context::fixedpoint_context(ast_manager& m, front_end_params& p) : + m_state(0), + m_reduce_app(0), + m_reduce_assign(0), + m_context(m, p), + m_trail(m) {} + + + void fixedpoint_context::set_state(void* state) { + SASSERT(!m_state); + m_state = state; + symbol name("datalog_relation"); + ast_manager& m = m_context.get_manager(); + if (!m.has_plugin(name)) { + m.register_plugin(name, alloc(datalog::dl_decl_plugin)); + } + datalog::relation_manager& r = m_context.get_rmanager(); + r.register_plugin(alloc(datalog::external_relation_plugin, *this, r)); + } + + void fixedpoint_context::reduce(func_decl* f, unsigned num_args, expr * const* args, expr_ref& result) { + expr* r = 0; + if (m_reduce_app) { + m_reduce_app(m_state, f, num_args, args, &r); + result = r; + m_trail.push_back(f); + for (unsigned i = 0; i < num_args; ++i) { + m_trail.push_back(args[i]); + } + m_trail.push_back(r); + } + // allow fallthrough. + if (r == 0) { + ast_manager& m = m_context.get_manager(); + result = m.mk_app(f, num_args, args); + } + } + + // overwrite terms passed in outs vector with values computed by function. + void fixedpoint_context::reduce_assign(func_decl* f, unsigned num_args, expr * const* args, unsigned num_out, expr* const* outs) { + if (m_reduce_assign) { + m_trail.push_back(f); + for (unsigned i = 0; i < num_args; ++i) { + m_trail.push_back(args[i]); + } + m_reduce_assign(m_state, f, num_args, args, num_out, outs); + } + } + + + void fixedpoint_context::add_rule(expr* rule, symbol const& name) { + m_context.add_rule(rule, name); + } + + void fixedpoint_context::update_rule(expr* rule, symbol const& name) { + m_context.update_rule(rule, name); + } + + void fixedpoint_context::add_table_fact(func_decl* r, unsigned num_args, unsigned args[]) { + m_context.add_table_fact(r, num_args, args); + } + + unsigned fixedpoint_context::get_num_levels(func_decl* pred) { + return m_context.get_num_levels(pred); + } + + expr_ref fixedpoint_context::get_cover_delta(int level, func_decl* pred) { + return m_context.get_cover_delta(level, pred); + } + + void fixedpoint_context::add_cover(int level, func_decl* pred, expr* predicate) { + m_context.add_cover(level, pred, predicate); + } + + std::string fixedpoint_context::get_last_status() { + datalog::execution_result status = m_context.get_status(); + switch(status) { + case datalog::INPUT_ERROR: + return "input error"; + case datalog::OK: + return "ok"; + case datalog::TIMEOUT: + return "timeout"; + default: + UNREACHABLE(); + return "unknown"; + } + } + + std::string fixedpoint_context::to_string(unsigned num_queries, expr*const* queries) { + std::stringstream str; + m_context.display_smt2(num_queries, queries, str); + return str.str(); + } + + void fixedpoint_context::simplify_rules( + unsigned num_rules, expr* const* rules, + unsigned num_outputs, func_decl* const* outputs, expr_ref_vector& result) { + ast_manager& m = m_context.get_manager(); + + datalog::context ctx(m, m_context.get_fparams()); + datalog::rule_manager& rm = ctx.get_rule_manager(); + for (unsigned i = 0; i < num_rules; ++i) { + expr* rule = rules[i], *body, *head; + while (true) { + if (is_quantifier(rule)) { + rule = to_quantifier(rule)->get_expr(); + } + else if (m.is_implies(rule, body, head)) { + rule = head; + } + else { + break; + } + } + if (is_app(rule)) { + func_decl* r = to_app(rule)->get_decl(); + if (!ctx.is_predicate(r)) { + ctx.register_predicate(r); + if (num_outputs == 0) { + ctx.set_output_predicate(r); + } + } + } + } + for (unsigned i = 0; i < num_outputs; ++i) { + ctx.set_output_predicate(outputs[i]); + } + for (unsigned i = 0; i < num_rules; ++i) { + expr* rule = rules[i]; + ctx.add_rule(rule, symbol::null); + } + model_converter_ref mc; // not exposed. + proof_converter_ref pc; // not exposed. + ctx.apply_default_transformation(mc, pc); + datalog::rule_set const& new_rules = ctx.get_rules(); + datalog::rule_set::iterator it = new_rules.begin(), end = new_rules.end(); + for (; it != end; ++it) { + datalog::rule* r = *it; + expr_ref fml(m); + r->to_formula(fml); + result.push_back(fml); + } + } + +}; + +extern "C" { + + //////////////////////////////////// + // Datalog utilities + // + + + unsigned Z3_API Z3_get_relation_arity(Z3_context c, Z3_sort s) { + Z3_TRY; + LOG_Z3_get_relation_arity(c, s); + RESET_ERROR_CODE(); + sort * r = to_sort(s); + if (Z3_get_sort_kind(c, s) != Z3_RELATION_SORT) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return 0; + } + return r->get_num_parameters(); + Z3_CATCH_RETURN(0); + } + + Z3_sort Z3_API Z3_get_relation_column(Z3_context c, Z3_sort s, unsigned col) { + Z3_TRY; + LOG_Z3_get_relation_column(c, s, col); + RESET_ERROR_CODE(); + sort * r = to_sort(s); + if (Z3_get_sort_kind(c, s) != Z3_RELATION_SORT) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + if (col >= r->get_num_parameters()) { + SET_ERROR_CODE(Z3_IOB); + RETURN_Z3(0); + } + parameter const& p = r->get_parameter(col); + if (!p.is_ast() || !is_sort(p.get_ast())) { + UNREACHABLE(); + warning_msg("Sort parameter expected at %d", col); + SET_ERROR_CODE(Z3_INTERNAL_FATAL); + RETURN_Z3(0); + } + Z3_sort res = of_sort(to_sort(p.get_ast())); + RETURN_Z3(res); + Z3_CATCH_RETURN(0); + } + + Z3_sort Z3_API Z3_mk_finite_domain_sort(Z3_context c, Z3_symbol name, unsigned __int64 size) { + Z3_TRY; + LOG_Z3_mk_finite_domain_sort(c, name, size); + RESET_ERROR_CODE(); + sort* s = mk_c(c)->datalog_util().mk_sort(to_symbol(name), size); + mk_c(c)->save_ast_trail(s); + RETURN_Z3(of_sort(s)); + Z3_CATCH_RETURN(0); + } + + Z3_bool Z3_API Z3_get_finite_domain_sort_size(Z3_context c, Z3_sort s, unsigned __int64 * out) { + Z3_TRY; + if (out) { + *out = 0; + } + if (Z3_get_sort_kind(c, s) != Z3_FINITE_DOMAIN_SORT) { + return Z3_FALSE; + } + if (!out) { + return Z3_FALSE; + } + // must start loggging here, since function uses Z3_get_sort_kind above + LOG_Z3_get_finite_domain_sort_size(c, s, out); + RESET_ERROR_CODE(); + VERIFY(mk_c(c)->datalog_util().try_get_size(to_sort(s), *out)); + return Z3_TRUE; + + Z3_CATCH_RETURN(Z3_FALSE); + } + + Z3_fixedpoint Z3_API Z3_mk_fixedpoint(Z3_context c) { + Z3_TRY; + LOG_Z3_mk_fixedpoint(c); + RESET_ERROR_CODE(); + Z3_fixedpoint_ref * d = alloc(Z3_fixedpoint_ref); + d->m_datalog = alloc(api::fixedpoint_context, mk_c(c)->m(), mk_c(c)->fparams()); + mk_c(c)->save_object(d); + Z3_fixedpoint r = of_datalog(d); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_fixedpoint_inc_ref(Z3_context c, Z3_fixedpoint s) { + Z3_TRY; + LOG_Z3_fixedpoint_inc_ref(c, s); + RESET_ERROR_CODE(); + to_fixedpoint(s)->inc_ref(); + Z3_CATCH; + } + + void Z3_API Z3_fixedpoint_dec_ref(Z3_context c, Z3_fixedpoint s) { + Z3_TRY; + LOG_Z3_fixedpoint_dec_ref(c, s); + RESET_ERROR_CODE(); + to_fixedpoint(s)->dec_ref(); + Z3_CATCH; + } + + void Z3_API Z3_fixedpoint_assert(Z3_context c, Z3_fixedpoint d, Z3_ast a) { + Z3_TRY; + LOG_Z3_fixedpoint_assert(c, d, a); + RESET_ERROR_CODE(); + CHECK_FORMULA(a,); + to_fixedpoint_ref(d)->ctx().assert_expr(to_expr(a)); + Z3_CATCH; + } + + void Z3_API Z3_fixedpoint_add_rule(Z3_context c, Z3_fixedpoint d, Z3_ast a, Z3_symbol name) { + Z3_TRY; + LOG_Z3_fixedpoint_add_rule(c, d, a, name); + RESET_ERROR_CODE(); + CHECK_FORMULA(a,); + to_fixedpoint_ref(d)->add_rule(to_expr(a), to_symbol(name)); + Z3_CATCH; + } + + void Z3_API Z3_fixedpoint_add_fact(Z3_context c, Z3_fixedpoint d, + Z3_func_decl r, unsigned num_args, unsigned args[]) { + Z3_TRY; + LOG_Z3_fixedpoint_add_fact(c, d, r, num_args, args); + RESET_ERROR_CODE(); + to_fixedpoint_ref(d)->add_table_fact(to_func_decl(r), num_args, args); + Z3_CATCH; + } + + Z3_lbool Z3_API Z3_fixedpoint_query(Z3_context c,Z3_fixedpoint d, Z3_ast q) { + Z3_TRY; + LOG_Z3_fixedpoint_query(c, d, q); + RESET_ERROR_CODE(); + lbool r = l_undef; + cancel_eh eh(*to_fixedpoint_ref(d)); + unsigned timeout = to_fixedpoint(d)->m_params.get_uint(":timeout", UINT_MAX); + api::context::set_interruptable(*(mk_c(c)), eh); + { + scoped_timer timer(timeout, &eh); + try { + r = to_fixedpoint_ref(d)->ctx().query(to_expr(q)); + } + catch (z3_exception& ex) { + mk_c(c)->handle_exception(ex); + r = l_undef; + } + to_fixedpoint_ref(d)->ctx().cleanup(); + } + return of_lbool(r); + Z3_CATCH_RETURN(Z3_L_UNDEF); + } + + Z3_lbool Z3_API Z3_fixedpoint_query_relations( + __in Z3_context c,__in Z3_fixedpoint d, + __in unsigned num_relations, Z3_func_decl const relations[]) { + Z3_TRY; + LOG_Z3_fixedpoint_query_relations(c, d, num_relations, relations); + RESET_ERROR_CODE(); + lbool r = l_undef; + unsigned timeout = to_fixedpoint(d)->m_params.get_uint(":timeout", UINT_MAX); + cancel_eh eh(*to_fixedpoint_ref(d)); + api::context::set_interruptable(*(mk_c(c)), eh); + { + scoped_timer timer(timeout, &eh); + try { + r = to_fixedpoint_ref(d)->ctx().dl_query(num_relations, to_func_decls(relations)); + } + catch (z3_exception& ex) { + mk_c(c)->handle_exception(ex); + r = l_undef; + } + to_fixedpoint_ref(d)->ctx().cleanup(); + } + return of_lbool(r); + Z3_CATCH_RETURN(Z3_L_UNDEF); + } + + Z3_ast Z3_API Z3_fixedpoint_get_answer(Z3_context c, Z3_fixedpoint d) { + Z3_TRY; + LOG_Z3_fixedpoint_get_answer(c, d); + RESET_ERROR_CODE(); + expr* e = to_fixedpoint_ref(d)->ctx().get_answer_as_formula(); + mk_c(c)->save_ast_trail(e); + RETURN_Z3(of_expr(e)); + Z3_CATCH_RETURN(0); + } + + Z3_string Z3_API Z3_fixedpoint_get_reason_unknown(Z3_context c,Z3_fixedpoint d) { + Z3_TRY; + LOG_Z3_fixedpoint_get_reason_unknown(c, d); + RESET_ERROR_CODE(); + return mk_c(c)->mk_external_string(to_fixedpoint_ref(d)->get_last_status()); + Z3_CATCH_RETURN(""); + } + + Z3_string Z3_API Z3_fixedpoint_to_string( + Z3_context c, + Z3_fixedpoint d, + unsigned num_queries, + Z3_ast _queries[]) { + Z3_TRY; + expr*const* queries = to_exprs(_queries); + LOG_Z3_fixedpoint_to_string(c, d, num_queries, _queries); + RESET_ERROR_CODE(); + return mk_c(c)->mk_external_string(to_fixedpoint_ref(d)->to_string(num_queries, queries)); + Z3_CATCH_RETURN(""); + } + + Z3_stats Z3_API Z3_fixedpoint_get_statistics(Z3_context c,Z3_fixedpoint d) { + Z3_TRY; + LOG_Z3_fixedpoint_get_statistics(c, d); + RESET_ERROR_CODE(); + Z3_stats_ref * st = alloc(Z3_stats_ref); + to_fixedpoint_ref(d)->ctx().collect_statistics(st->m_stats); + mk_c(c)->save_object(st); + Z3_stats r = of_stats(st); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_fixedpoint_register_relation(Z3_context c,Z3_fixedpoint d, Z3_func_decl f) { + Z3_TRY; + LOG_Z3_fixedpoint_register_relation(c, d, f); + to_fixedpoint_ref(d)->ctx().register_predicate(to_func_decl(f)); + Z3_CATCH; + } + + void Z3_API Z3_fixedpoint_set_predicate_representation( + Z3_context c, + Z3_fixedpoint d, + Z3_func_decl f, + unsigned num_relations, + Z3_symbol const relation_kinds[]) { + Z3_TRY; + LOG_Z3_fixedpoint_set_predicate_representation(c, d, f, num_relations, relation_kinds); + svector kinds; + for (unsigned i = 0; i < num_relations; ++i) { + kinds.push_back(to_symbol(relation_kinds[i])); + } + to_fixedpoint_ref(d)->ctx().set_predicate_representation(to_func_decl(f), num_relations, kinds.c_ptr()); + Z3_CATCH; + } + + void Z3_API Z3_fixedpoint_set_reduce_assign_callback( + Z3_context c, Z3_fixedpoint d, Z3_fixedpoint_reduce_assign_callback_fptr f) { + Z3_TRY; + // no logging + to_fixedpoint_ref(d)->set_reduce_assign((reduce_assign_callback_fptr)f); + Z3_CATCH; + } + + void Z3_API Z3_fixedpoint_set_reduce_app_callback( + Z3_context c, Z3_fixedpoint d, Z3_fixedpoint_reduce_app_callback_fptr f) { + Z3_TRY; + // no logging + to_fixedpoint_ref(d)->set_reduce_app((reduce_app_callback_fptr)f); + Z3_CATCH; + } + + Z3_ast_vector Z3_API Z3_fixedpoint_simplify_rules( + Z3_context c, + Z3_fixedpoint d, + unsigned num_rules, + Z3_ast _rules[], + unsigned num_outputs, + Z3_func_decl _outputs[]) { + Z3_TRY; + LOG_Z3_fixedpoint_simplify_rules(c, d, num_rules, _rules, num_outputs, _outputs); + RESET_ERROR_CODE(); + expr** rules = (expr**)_rules; + func_decl** outputs = (func_decl**)_outputs; + ast_manager& m = mk_c(c)->m(); + expr_ref_vector result(m); + to_fixedpoint_ref(d)->simplify_rules(num_rules, rules, num_outputs, outputs, result); + Z3_ast_vector_ref* v = alloc(Z3_ast_vector_ref, mk_c(c)->m()); + mk_c(c)->save_object(v); + for (unsigned i = 0; i < result.size(); ++i) { + v->m_ast_vector.push_back(result[i].get()); + } + RETURN_Z3(of_ast_vector(v)); + Z3_CATCH_RETURN(0) + } + + + void Z3_API Z3_fixedpoint_init(Z3_context c,Z3_fixedpoint d, void* state) { + Z3_TRY; + // not logged + to_fixedpoint_ref(d)->set_state(state); + Z3_CATCH; + } + + + void Z3_API Z3_fixedpoint_update_rule(Z3_context c, Z3_fixedpoint d, Z3_ast a, Z3_symbol name) { + Z3_TRY; + LOG_Z3_fixedpoint_update_rule(c, d, a, name); + RESET_ERROR_CODE(); + CHECK_FORMULA(a,); + to_fixedpoint_ref(d)->update_rule(to_expr(a), to_symbol(name)); + Z3_CATCH; + } + + unsigned Z3_API Z3_fixedpoint_get_num_levels(Z3_context c, Z3_fixedpoint d, Z3_func_decl pred) { + Z3_TRY; + LOG_Z3_fixedpoint_get_num_levels(c, d, pred); + RESET_ERROR_CODE(); + return to_fixedpoint_ref(d)->get_num_levels(to_func_decl(pred)); + Z3_CATCH_RETURN(0) + } + + Z3_ast Z3_API Z3_fixedpoint_get_cover_delta(Z3_context c, Z3_fixedpoint d, int level, Z3_func_decl pred) { + Z3_TRY; + LOG_Z3_fixedpoint_get_cover_delta(c, d, level, pred); + RESET_ERROR_CODE(); + expr_ref r = to_fixedpoint_ref(d)->get_cover_delta(level, to_func_decl(pred)); + mk_c(c)->save_ast_trail(r); + RETURN_Z3(of_expr(r.get())); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_fixedpoint_add_cover(Z3_context c, Z3_fixedpoint d, int level, Z3_func_decl pred, Z3_ast property) { + Z3_TRY; + LOG_Z3_fixedpoint_add_cover(c, d, level, pred, property); + RESET_ERROR_CODE(); + to_fixedpoint_ref(d)->add_cover(level, to_func_decl(pred), to_expr(property)); + Z3_CATCH; + } + + Z3_string Z3_API Z3_fixedpoint_get_help(Z3_context c, Z3_fixedpoint d) { + Z3_TRY; + LOG_Z3_fixedpoint_get_help(c, d); + RESET_ERROR_CODE(); + std::ostringstream buffer; + param_descrs descrs; + to_fixedpoint_ref(d)->collect_param_descrs(descrs); + descrs.display(buffer); + return mk_c(c)->mk_external_string(buffer.str()); + Z3_CATCH_RETURN(""); + } + + Z3_param_descrs Z3_API Z3_fixedpoint_get_param_descrs(Z3_context c, Z3_fixedpoint f) { + Z3_TRY; + LOG_Z3_fixedpoint_get_param_descrs(c, f); + RESET_ERROR_CODE(); + Z3_param_descrs_ref * d = alloc(Z3_param_descrs_ref); + mk_c(c)->save_object(d); + to_fixedpoint_ref(f)->collect_param_descrs(d->m_descrs); + Z3_param_descrs r = of_param_descrs(d); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_fixedpoint_set_params(Z3_context c, Z3_fixedpoint d, Z3_params p) { + Z3_TRY; + LOG_Z3_fixedpoint_set_params(c, d, p); + RESET_ERROR_CODE(); + param_descrs descrs; + to_fixedpoint_ref(d)->collect_param_descrs(descrs); + to_params(p)->m_params.validate(descrs); + to_fixedpoint_ref(d)->updt_params(to_param_ref(p)); + to_fixedpoint(d)->m_params = to_param_ref(p); + Z3_CATCH; + } + + void Z3_API Z3_fixedpoint_push(Z3_context c,Z3_fixedpoint d) { + Z3_TRY; + LOG_Z3_fixedpoint_push(c, d); + RESET_ERROR_CODE(); + to_fixedpoint_ref(d)->ctx().push(); + Z3_CATCH; + } + + void Z3_API Z3_fixedpoint_pop(Z3_context c,Z3_fixedpoint d) { + Z3_TRY; + LOG_Z3_fixedpoint_pop(c, d); + RESET_ERROR_CODE(); + to_fixedpoint_ref(d)->ctx().pop(); + Z3_CATCH; + + } + + +}; diff --git a/lib/api_datalog.h b/lib/api_datalog.h new file mode 100644 index 000000000..fad44de87 --- /dev/null +++ b/lib/api_datalog.h @@ -0,0 +1,84 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_datalog.h + +Abstract: + Datalog API + old external_relation_context_impl + +Author: + + Leonardo de Moura (leonardo) 2012-02-29. + +Revision History: + +--*/ +#ifndef _API_DATALOG_H_ +#define _API_DATALOG_H_ + +#include"z3.h" +#include"ast.h" +#include"front_end_params.h" +#include"dl_external_relation.h" +#include"dl_decl_plugin.h" +#include"smt_solver.h" +#include"api_util.h" +#include"dl_context.h" + +typedef void (*reduce_app_callback_fptr)(void*, func_decl*, unsigned, expr*const*, expr**); +typedef void (*reduce_assign_callback_fptr)(void*, func_decl*, unsigned, expr*const*, unsigned, expr*const*); + +namespace api { + + class fixedpoint_context : public datalog::external_relation_context { + void * m_state; + reduce_app_callback_fptr m_reduce_app; + reduce_assign_callback_fptr m_reduce_assign; + datalog::context m_context; + ast_ref_vector m_trail; + public: + fixedpoint_context(ast_manager& m, front_end_params& p); + virtual ~fixedpoint_context() {} + family_id get_family_id() const { return const_cast(m_context).get_decl_util().get_family_id(); } + void set_state(void* state); + void set_reduce_app(reduce_app_callback_fptr f) { m_reduce_app = f; } + void set_reduce_assign(reduce_assign_callback_fptr f) { m_reduce_assign = f; } + virtual void reduce(func_decl* f, unsigned num_args, expr * const* args, expr_ref& result); + virtual void reduce_assign(func_decl* f, unsigned num_args, expr * const* args, unsigned num_out, expr* const* outs); + datalog::context& ctx() { return m_context; } + void add_rule(expr* rule, symbol const& name); + void update_rule(expr* rule, symbol const& name); + void add_table_fact(func_decl* r, unsigned num_args, unsigned args[]); + std::string get_last_status(); + std::string to_string(unsigned num_queries, expr*const* queries); + void cancel() { m_context.cancel(); } + + unsigned get_num_levels(func_decl* pred); + expr_ref get_cover_delta(int level, func_decl* pred); + void add_cover(int level, func_decl* pred, expr* predicate); + void collect_param_descrs(param_descrs & p) { m_context.collect_params(p); } + void updt_params(params_ref const& p) { m_context.updt_params(p); } + + void simplify_rules( + unsigned num_rules, expr* const* rules, + unsigned num_outputs, func_decl* const* outputs, expr_ref_vector& result); + }; +}; + + +struct Z3_fixedpoint_ref : public api::object { + api::fixedpoint_context * m_datalog; + params_ref m_params; + Z3_fixedpoint_ref():m_datalog(0) {} + virtual ~Z3_fixedpoint_ref() { dealloc(m_datalog); } +}; + +inline Z3_fixedpoint_ref * to_fixedpoint(Z3_fixedpoint s) { return reinterpret_cast(s); } +inline Z3_fixedpoint of_datalog(Z3_fixedpoint_ref * s) { return reinterpret_cast(s); } +inline api::fixedpoint_context * to_fixedpoint_ref(Z3_fixedpoint s) { return to_fixedpoint(s)->m_datalog; } + + +#endif diff --git a/lib/api_datatype.cpp b/lib/api_datatype.cpp new file mode 100644 index 000000000..f3a275508 --- /dev/null +++ b/lib/api_datatype.cpp @@ -0,0 +1,621 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_datatype.cpp + +Abstract: + API for datatype theory + +Author: + + Leonardo de Moura (leonardo) 2012-02-29. + +Revision History: + +--*/ +#include +#include"z3.h" +#include"api_log_macros.h" +#include"api_context.h" +#include"api_util.h" +#include"datatype_decl_plugin.h" + +extern "C" { + + Z3_sort Z3_API Z3_mk_tuple_sort(Z3_context c, + Z3_symbol name, + unsigned num_fields, + Z3_symbol const field_names[], + Z3_sort const field_sorts[], + Z3_func_decl * mk_tuple_decl, + Z3_func_decl proj_decls[]) { + Z3_TRY; + LOG_Z3_mk_tuple_sort(c, name, num_fields, field_names, field_sorts, mk_tuple_decl, proj_decls); + RESET_ERROR_CODE(); + mk_c(c)->reset_last_result(); + ast_manager& m = mk_c(c)->m(); + datatype_util dt_util(m); + + sort_ref_vector tuples(m); + sort* tuple; + std::string recognizer_s("is_"); + recognizer_s += to_symbol(name).str(); + symbol recognizer(recognizer_s.c_str()); + + ptr_vector acc; + for (unsigned i = 0; i < num_fields; ++i) { + acc.push_back(mk_accessor_decl(to_symbol(field_names[i]), type_ref(to_sort(field_sorts[i])))); + } + + constructor_decl* constrs[1] = { mk_constructor_decl(to_symbol(name), recognizer, acc.size(), acc.c_ptr()) }; + + { + datatype_decl * dt = mk_datatype_decl(to_symbol(name), 1, constrs); + bool is_ok = mk_c(c)->get_dt_plugin()->mk_datatypes(1, &dt, tuples); + del_datatype_decl(dt); + + if (!is_ok) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + } + + // create tuple type + SASSERT(tuples.size() == 1); + tuple = tuples[0].get(); + mk_c(c)->save_multiple_ast_trail(tuple); + + // create constructor + SASSERT(dt_util.is_datatype(tuple)); + SASSERT(!dt_util.is_recursive(tuple)); + ptr_vector const * decls = dt_util.get_datatype_constructors(tuple); + func_decl* decl = (*decls)[0]; + mk_c(c)->save_multiple_ast_trail(decl); + *mk_tuple_decl = of_func_decl(decl); + + // Create projections + ptr_vector const * accs = dt_util.get_constructor_accessors(decl); + if (!accs) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + ptr_vector const & _accs = *accs; + SASSERT(_accs.size() == num_fields); + for (unsigned i = 0; i < _accs.size(); i++) { + mk_c(c)->save_multiple_ast_trail(_accs[i]); + proj_decls[i] = of_func_decl(_accs[i]); + } + RETURN_Z3_mk_tuple_sort(of_sort(tuple)); + Z3_CATCH_RETURN(0); + } + + Z3_sort Z3_API Z3_mk_enumeration_sort(Z3_context c, + Z3_symbol name, + unsigned n, + Z3_symbol const enum_names[], + Z3_func_decl enum_consts[], + Z3_func_decl enum_testers[]) { + Z3_TRY; + LOG_Z3_mk_enumeration_sort(c, name, n, enum_names, enum_consts, enum_testers); + RESET_ERROR_CODE(); + mk_c(c)->reset_last_result(); + ast_manager& m = mk_c(c)->m(); + datatype_util dt_util(m); + + sort_ref_vector sorts(m); + sort* e; + + ptr_vector constrs; + for (unsigned i = 0; i < n; ++i) { + symbol e_name(to_symbol(enum_names[i])); + std::string recognizer_s("is_"); + recognizer_s += e_name.str(); + symbol recognizer(recognizer_s.c_str()); + + constrs.push_back(mk_constructor_decl(e_name, recognizer, 0, 0)); + } + + + { + datatype_decl * dt = mk_datatype_decl(to_symbol(name), n, constrs.c_ptr()); + bool is_ok = mk_c(c)->get_dt_plugin()->mk_datatypes(1, &dt, sorts); + del_datatype_decl(dt); + + if (!is_ok) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + } + + // create enum type. + SASSERT(sorts.size() == 1); + e = sorts[0].get(); + mk_c(c)->save_multiple_ast_trail(e); + + // create constructor + SASSERT(dt_util.is_datatype(e)); + SASSERT(!dt_util.is_recursive(e)); + ptr_vector const * decls = dt_util.get_datatype_constructors(e); + SASSERT(decls && decls->size() == n); + for (unsigned i = 0; i < n; ++i) { + func_decl* decl = (*decls)[i]; + mk_c(c)->save_multiple_ast_trail(decl); + enum_consts[i] = of_func_decl(decl); + decl = dt_util.get_constructor_recognizer(decl); + mk_c(c)->save_multiple_ast_trail(decl); + enum_testers[i] = of_func_decl(decl); + } + + RETURN_Z3_mk_enumeration_sort(of_sort(e)); + Z3_CATCH_RETURN(0); + } + + Z3_sort Z3_API Z3_mk_list_sort(Z3_context c, + Z3_symbol name, + Z3_sort elem_sort, + Z3_func_decl* nil_decl, + Z3_func_decl* is_nil_decl, + Z3_func_decl* cons_decl, + Z3_func_decl* is_cons_decl, + Z3_func_decl* head_decl, + Z3_func_decl* tail_decl + ) { + Z3_TRY; + LOG_Z3_mk_list_sort(c, name, elem_sort, nil_decl, is_nil_decl, cons_decl, is_cons_decl, head_decl, tail_decl); + RESET_ERROR_CODE(); + ast_manager& m = mk_c(c)->m(); + mk_c(c)->reset_last_result(); + datatype_util data_util(m); + accessor_decl* head_tail[2] = { + mk_accessor_decl(symbol("head"), type_ref(to_sort(elem_sort))), + mk_accessor_decl(symbol("tail"), type_ref(0)) + }; + constructor_decl* constrs[2] = { + mk_constructor_decl(symbol("nil"), symbol("is_nil"), 0, 0), + // Leo: SMT 2.0 document uses 'insert' instead of cons + mk_constructor_decl(symbol("cons"), symbol("is_cons"), 2, head_tail) + }; + + sort_ref_vector sorts(m); + { + datatype_decl * decl = mk_datatype_decl(to_symbol(name), 2, constrs); + bool is_ok = mk_c(c)->get_dt_plugin()->mk_datatypes(1, &decl, sorts); + del_datatype_decl(decl); + + if (!is_ok) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + } + sort * s = sorts.get(0); + + mk_c(c)->save_multiple_ast_trail(s); + ptr_vector const& cnstrs = *data_util.get_datatype_constructors(s); + SASSERT(cnstrs.size() == 2); + func_decl* f; + if (nil_decl) { + f = cnstrs[0]; + mk_c(c)->save_multiple_ast_trail(f); + *nil_decl = of_func_decl(f); + } + if (is_nil_decl) { + f = data_util.get_constructor_recognizer(cnstrs[0]); + mk_c(c)->save_multiple_ast_trail(f); + *is_nil_decl = of_func_decl(f); + } + if (cons_decl) { + f = cnstrs[1]; + mk_c(c)->save_multiple_ast_trail(f); + *cons_decl = of_func_decl(f); + } + if (is_cons_decl) { + f = data_util.get_constructor_recognizer(cnstrs[1]); + mk_c(c)->save_multiple_ast_trail(f); + *is_cons_decl = of_func_decl(f); + } + if (head_decl) { + ptr_vector const* acc = data_util.get_constructor_accessors(cnstrs[1]); + SASSERT(acc); + SASSERT(acc->size() == 2); + f = (*acc)[0]; + mk_c(c)->save_multiple_ast_trail(f); + *head_decl = of_func_decl(f); + } + if (tail_decl) { + ptr_vector const* acc = data_util.get_constructor_accessors(cnstrs[1]); + SASSERT(acc); + SASSERT(acc->size() == 2); + f = (*acc)[1]; + mk_c(c)->save_multiple_ast_trail(f); + *tail_decl = of_func_decl(f); + } + RETURN_Z3_mk_list_sort(of_sort(s)); + Z3_CATCH_RETURN(0); + } + + struct constructor { + symbol m_name; + symbol m_tester; + svector m_field_names; + sort_ref_vector m_sorts; + unsigned_vector m_sort_refs; + func_decl_ref m_constructor; + constructor(ast_manager& m) : m_sorts(m), m_constructor(m) {} + }; + + Z3_constructor Z3_API Z3_mk_constructor(Z3_context c, + Z3_symbol name, + Z3_symbol tester, + unsigned num_fields, + Z3_symbol const field_names[], + Z3_sort const sorts[], + unsigned sort_refs[] + ) { + Z3_TRY; + LOG_Z3_mk_constructor(c, name, tester, num_fields, field_names, sorts, sort_refs); + RESET_ERROR_CODE(); + ast_manager& m = mk_c(c)->m(); + constructor* cnstr = alloc(constructor, m); + cnstr->m_name = to_symbol(name); + cnstr->m_tester = to_symbol(tester); + for (unsigned i = 0; i < num_fields; ++i) { + cnstr->m_field_names.push_back(to_symbol(field_names[i])); + cnstr->m_sorts.push_back(to_sort(sorts[i])); + cnstr->m_sort_refs.push_back(sort_refs[i]); + } + RETURN_Z3(reinterpret_cast(cnstr)); + Z3_CATCH_RETURN(0); + } + + + void Z3_API Z3_query_constructor(Z3_context c, + Z3_constructor constr, + unsigned num_fields, + Z3_func_decl* constructor_decl, + Z3_func_decl* tester, + Z3_func_decl accessors[]) { + Z3_TRY; + LOG_Z3_query_constructor(c, constr, num_fields, constructor_decl, tester, accessors); + RESET_ERROR_CODE(); + mk_c(c)->reset_last_result(); + if (!constr) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return; + } + ast_manager& m = mk_c(c)->m(); + datatype_util data_util(m); + func_decl* f = reinterpret_cast(constr)->m_constructor.get(); + + if (!f) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return; + } + if (constructor_decl) { + mk_c(c)->save_multiple_ast_trail(f); + *constructor_decl = of_func_decl(f); + } + if (tester) { + func_decl* f2 = data_util.get_constructor_recognizer(f); + mk_c(c)->save_multiple_ast_trail(f2); + *tester = of_func_decl(f2); + } + + ptr_vector const* accs = data_util.get_constructor_accessors(f); + if (!accs && num_fields > 0) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return; + } + for (unsigned i = 0; i < num_fields; ++i) { + func_decl* f2 = (*accs)[i]; + mk_c(c)->save_multiple_ast_trail(f2); + accessors[i] = of_func_decl(f2); + } + RETURN_Z3_query_constructor; + Z3_CATCH; + } + + void Z3_API Z3_del_constructor(Z3_context c, Z3_constructor constr) { + Z3_TRY; + LOG_Z3_del_constructor(c, constr); + RESET_ERROR_CODE(); + dealloc(reinterpret_cast(constr)); + Z3_CATCH; + } + + static datatype_decl* mk_datatype_decl(Z3_context c, + Z3_symbol name, + unsigned num_constructors, + Z3_constructor constructors[]) { + ptr_vector constrs; + for (unsigned i = 0; i < num_constructors; ++i) { + constructor* cn = reinterpret_cast(constructors[i]); + ptr_vector acc; + for (unsigned j = 0; j < cn->m_sorts.size(); ++j) { + if (cn->m_sorts[j].get()) { + acc.push_back(mk_accessor_decl(cn->m_field_names[j], type_ref(cn->m_sorts[j].get()))); + } + else { + acc.push_back(mk_accessor_decl(cn->m_field_names[j], type_ref(cn->m_sort_refs[j]))); + } + } + constrs.push_back(mk_constructor_decl(cn->m_name, cn->m_tester, acc.size(), acc.c_ptr())); + } + return mk_datatype_decl(to_symbol(name), num_constructors, constrs.c_ptr()); + } + + Z3_sort Z3_API Z3_mk_datatype(Z3_context c, + Z3_symbol name, + unsigned num_constructors, + Z3_constructor constructors[]) { + Z3_TRY; + LOG_Z3_mk_datatype(c, name, num_constructors, constructors); + RESET_ERROR_CODE(); + ast_manager& m = mk_c(c)->m(); + datatype_util data_util(m); + + sort_ref_vector sorts(m); + { + datatype_decl * data = mk_datatype_decl(c, name, num_constructors, constructors); + bool is_ok = mk_c(c)->get_dt_plugin()->mk_datatypes(1, &data, sorts); + del_datatype_decl(data); + + if (!is_ok) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + } + sort * s = sorts.get(0); + + mk_c(c)->save_ast_trail(s); + ptr_vector const* cnstrs = data_util.get_datatype_constructors(s); + + for (unsigned i = 0; i < num_constructors; ++i) { + constructor* cn = reinterpret_cast(constructors[i]); + cn->m_constructor = (*cnstrs)[i]; + } + RETURN_Z3_mk_datatype(of_sort(s)); + Z3_CATCH_RETURN(0); + } + + typedef ptr_vector constructor_list; + + Z3_constructor_list Z3_API Z3_mk_constructor_list(Z3_context c, + unsigned num_constructors, + Z3_constructor const constructors[]) { + Z3_TRY; + LOG_Z3_mk_constructor_list(c, num_constructors, constructors); + RESET_ERROR_CODE(); + constructor_list* result = alloc(ptr_vector); + for (unsigned i = 0; i < num_constructors; ++i) { + result->push_back(reinterpret_cast(constructors[i])); + } + RETURN_Z3(reinterpret_cast(result)); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_del_constructor_list(Z3_context c, Z3_constructor_list clist) { + Z3_TRY; + LOG_Z3_del_constructor_list(c, clist); + RESET_ERROR_CODE(); + dealloc(reinterpret_cast(clist)); + Z3_CATCH; + } + + void Z3_API Z3_mk_datatypes(Z3_context c, + unsigned num_sorts, + Z3_symbol const sort_names[], + Z3_sort sorts[], + Z3_constructor_list constructor_lists[]) { + Z3_TRY; + LOG_Z3_mk_datatypes(c, num_sorts, sort_names, sorts, constructor_lists); + RESET_ERROR_CODE(); + ast_manager& m = mk_c(c)->m(); + mk_c(c)->reset_last_result(); + datatype_util data_util(m); + + ptr_vector datas; + for (unsigned i = 0; i < num_sorts; ++i) { + constructor_list* cl = reinterpret_cast(constructor_lists[i]); + datas.push_back(mk_datatype_decl(c,sort_names[i], cl->size(), reinterpret_cast(cl->c_ptr()))); + } + sort_ref_vector _sorts(m); + bool ok = mk_c(c)->get_dt_plugin()->mk_datatypes(datas.size(), datas.c_ptr(), _sorts); + del_datatype_decls(datas.size(), datas.c_ptr()); + + if (!ok) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return; + } + + SASSERT(_sorts.size() == num_sorts); + for (unsigned i = 0; i < _sorts.size(); ++i) { + sort* s = _sorts[i].get(); + mk_c(c)->save_multiple_ast_trail(s); + sorts[i] = of_sort(s); + constructor_list* cl = reinterpret_cast(constructor_lists[i]); + ptr_vector const* cnstrs = data_util.get_datatype_constructors(s); + for (unsigned j = 0; j < cl->size(); ++j) { + constructor* cn = (*cl)[j]; + cn->m_constructor = (*cnstrs)[j]; + } + } + RETURN_Z3_mk_datatypes; + Z3_CATCH; + } + + unsigned Z3_API Z3_get_datatype_sort_num_constructors(Z3_context c, Z3_sort t) { + Z3_TRY; + LOG_Z3_get_datatype_sort_num_constructors(c, t); + RESET_ERROR_CODE(); + CHECK_VALID_AST(t, 0); + sort * _t = to_sort(t); + datatype_util dt_util(mk_c(c)->m()); + + if (!dt_util.is_datatype(_t)) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return 0; + } + ptr_vector const * decls = dt_util.get_datatype_constructors(_t); + if (!decls) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return 0; + } + return decls->size(); + Z3_CATCH_RETURN(0); + } + + Z3_func_decl get_datatype_sort_constructor_core(Z3_context c, Z3_sort t, unsigned idx) { + RESET_ERROR_CODE(); + CHECK_VALID_AST(t, 0); + sort * _t = to_sort(t); + datatype_util dt_util(mk_c(c)->m()); + if (!dt_util.is_datatype(_t)) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return 0; + } + ptr_vector const * decls = dt_util.get_datatype_constructors(_t); + if (!decls || idx >= decls->size()) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return 0; + } + func_decl* decl = (*decls)[idx]; + mk_c(c)->save_ast_trail(decl); + return of_func_decl(decl); + } + + Z3_func_decl Z3_API Z3_get_datatype_sort_constructor(Z3_context c, Z3_sort t, unsigned idx) { + Z3_TRY; + LOG_Z3_get_datatype_sort_constructor(c, t, idx); + RESET_ERROR_CODE(); + Z3_func_decl r = get_datatype_sort_constructor_core(c, t, idx); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_func_decl Z3_API Z3_get_datatype_sort_recognizer(Z3_context c, Z3_sort t, unsigned idx) { + Z3_TRY; + LOG_Z3_get_datatype_sort_recognizer(c, t, idx); + RESET_ERROR_CODE(); + sort * _t = to_sort(t); + datatype_util dt_util(mk_c(c)->m()); + + if (!dt_util.is_datatype(_t)) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + ptr_vector const * decls = dt_util.get_datatype_constructors(_t); + if (!decls || idx >= decls->size()) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + func_decl* decl = (*decls)[idx]; + decl = dt_util.get_constructor_recognizer(decl); + mk_c(c)->save_ast_trail(decl); + RETURN_Z3(of_func_decl(decl)); + Z3_CATCH_RETURN(0); + } + + Z3_func_decl Z3_API Z3_get_datatype_sort_constructor_accessor(Z3_context c, Z3_sort t, unsigned idx_c, unsigned idx_a) { + Z3_TRY; + LOG_Z3_get_datatype_sort_constructor_accessor(c, t, idx_c, idx_a); + RESET_ERROR_CODE(); + sort * _t = to_sort(t); + datatype_util dt_util(mk_c(c)->m()); + + if (!dt_util.is_datatype(_t)) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + ptr_vector const * decls = dt_util.get_datatype_constructors(_t); + if (!decls || idx_c >= decls->size()) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + func_decl* decl = (*decls)[idx_c]; + if (decl->get_arity() <= idx_a) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + ptr_vector const * accs = dt_util.get_constructor_accessors(decl); + SASSERT(accs && accs->size() == decl->get_arity()); + if (!accs || accs->size() <= idx_a) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + decl = (*accs)[idx_a]; + mk_c(c)->save_ast_trail(decl); + RETURN_Z3(of_func_decl(decl)); + Z3_CATCH_RETURN(0); + } + + Z3_func_decl Z3_API Z3_get_tuple_sort_mk_decl(Z3_context c, Z3_sort t) { + Z3_TRY; + LOG_Z3_get_tuple_sort_mk_decl(c, t); + RESET_ERROR_CODE(); + sort * tuple = to_sort(t); + datatype_util dt_util(mk_c(c)->m()); + if (!dt_util.is_datatype(tuple) || dt_util.is_recursive(tuple) || dt_util.get_datatype_num_constructors(tuple) != 1) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + Z3_func_decl r = get_datatype_sort_constructor_core(c, t, 0); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + unsigned Z3_API Z3_get_tuple_sort_num_fields(Z3_context c, Z3_sort t) { + Z3_TRY; + LOG_Z3_get_tuple_sort_num_fields(c, t); + RESET_ERROR_CODE(); + sort * tuple = to_sort(t); + datatype_util dt_util(mk_c(c)->m()); + if (!dt_util.is_datatype(tuple) || dt_util.is_recursive(tuple) || dt_util.get_datatype_num_constructors(tuple) != 1) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return 0; + } + ptr_vector const * decls = dt_util.get_datatype_constructors(tuple); + if (!decls || decls->size() != 1) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return 0; + } + ptr_vector const * accs = dt_util.get_constructor_accessors((*decls)[0]); + if (!accs) { + return 0; + } + return accs->size(); + Z3_CATCH_RETURN(0); + } + + Z3_func_decl Z3_API Z3_get_tuple_sort_field_decl(Z3_context c, Z3_sort t, unsigned i) { + Z3_TRY; + LOG_Z3_get_tuple_sort_field_decl(c, t, i); + RESET_ERROR_CODE(); + sort * tuple = to_sort(t); + datatype_util dt_util(mk_c(c)->m()); + if (!dt_util.is_datatype(tuple) || dt_util.is_recursive(tuple) || dt_util.get_datatype_num_constructors(tuple) != 1) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + ptr_vector const * decls = dt_util.get_datatype_constructors(tuple); + if (!decls || decls->size() != 1) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + ptr_vector const * accs = dt_util.get_constructor_accessors((*decls)[0]); + if (!accs) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + if (accs->size() <= i) { + SET_ERROR_CODE(Z3_IOB); + RETURN_Z3(0); + } + func_decl* acc = (*accs)[i]; + mk_c(c)->save_ast_trail(acc); + RETURN_Z3(of_func_decl(acc)); + Z3_CATCH_RETURN(0); + } + +}; diff --git a/lib/api_goal.cpp b/lib/api_goal.cpp new file mode 100644 index 000000000..10164f5b9 --- /dev/null +++ b/lib/api_goal.cpp @@ -0,0 +1,181 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_goal.cpp + +Abstract: + API for creating goals + +Author: + + Leonardo de Moura (leonardo) 2012-03-06. + +Revision History: + +--*/ +#include +#include"z3.h" +#include"api_log_macros.h" +#include"api_context.h" +#include"api_goal.h" +#include"ast_translation.h" + +extern "C" { + + Z3_goal Z3_API Z3_mk_goal(Z3_context c, Z3_bool models, Z3_bool unsat_cores, Z3_bool proofs) { + Z3_TRY; + LOG_Z3_mk_goal(c, models, unsat_cores, proofs); + RESET_ERROR_CODE(); + if (proofs != 0 && !mk_c(c)->m().proofs_enabled()) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + Z3_goal_ref * g = alloc(Z3_goal_ref); + g->m_goal = alloc(goal, mk_c(c)->m(), proofs != 0, models != 0, unsat_cores != 0); + mk_c(c)->save_object(g); + Z3_goal r = of_goal(g); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_goal_inc_ref(Z3_context c, Z3_goal g) { + Z3_TRY; + LOG_Z3_goal_inc_ref(c, g); + RESET_ERROR_CODE(); + to_goal(g)->inc_ref(); + Z3_CATCH; + } + + void Z3_API Z3_goal_dec_ref(Z3_context c, Z3_goal g) { + Z3_TRY; + LOG_Z3_goal_dec_ref(c, g); + RESET_ERROR_CODE(); + to_goal(g)->dec_ref(); + Z3_CATCH; + } + + Z3_goal_prec Z3_API Z3_goal_precision(Z3_context c, Z3_goal g) { + Z3_TRY; + LOG_Z3_goal_precision(c, g); + RESET_ERROR_CODE(); + switch (to_goal_ref(g)->prec()) { + case goal::PRECISE: return Z3_GOAL_PRECISE; + case goal::UNDER: return Z3_GOAL_UNDER; + case goal::OVER: return Z3_GOAL_OVER; + case goal::UNDER_OVER: return Z3_GOAL_UNDER_OVER; + default: + UNREACHABLE(); + return Z3_GOAL_UNDER_OVER; + } + Z3_CATCH_RETURN(Z3_GOAL_UNDER_OVER); + } + + void Z3_API Z3_goal_assert(Z3_context c, Z3_goal g, Z3_ast a) { + Z3_TRY; + LOG_Z3_goal_assert(c, g, a); + RESET_ERROR_CODE(); + CHECK_FORMULA(a,); + to_goal_ref(g)->assert_expr(to_expr(a)); + Z3_CATCH; + } + + Z3_bool Z3_API Z3_goal_inconsistent(Z3_context c, Z3_goal g) { + Z3_TRY; + LOG_Z3_goal_inconsistent(c, g); + RESET_ERROR_CODE(); + return to_goal_ref(g)->inconsistent(); + Z3_CATCH_RETURN(Z3_FALSE); + } + + unsigned Z3_API Z3_goal_depth(Z3_context c, Z3_goal g) { + Z3_TRY; + LOG_Z3_goal_depth(c, g); + RESET_ERROR_CODE(); + return to_goal_ref(g)->depth(); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_goal_reset(Z3_context c, Z3_goal g) { + Z3_TRY; + LOG_Z3_goal_reset(c, g); + RESET_ERROR_CODE(); + to_goal_ref(g)->reset(); + Z3_CATCH; + } + + unsigned Z3_API Z3_goal_size(Z3_context c, Z3_goal g) { + Z3_TRY; + LOG_Z3_goal_size(c, g); + RESET_ERROR_CODE(); + return to_goal_ref(g)->size(); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_goal_formula(Z3_context c, Z3_goal g, unsigned idx) { + Z3_TRY; + LOG_Z3_goal_formula(c, g, idx); + RESET_ERROR_CODE(); + if (idx >= to_goal_ref(g)->size()) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + expr * result = to_goal_ref(g)->form(idx); + mk_c(c)->save_ast_trail(result); + RETURN_Z3(of_ast(result)); + Z3_CATCH_RETURN(0); + } + + unsigned Z3_API Z3_goal_num_exprs(Z3_context c, Z3_goal g) { + Z3_TRY; + LOG_Z3_goal_num_exprs(c, g); + RESET_ERROR_CODE(); + return to_goal_ref(g)->num_exprs(); + Z3_CATCH_RETURN(0); + } + + Z3_bool Z3_API Z3_goal_is_decided_sat(Z3_context c, Z3_goal g) { + Z3_TRY; + LOG_Z3_goal_is_decided_sat(c, g); + RESET_ERROR_CODE(); + return to_goal_ref(g)->is_decided_sat(); + Z3_CATCH_RETURN(Z3_FALSE); + } + + Z3_bool Z3_API Z3_goal_is_decided_unsat(Z3_context c, Z3_goal g) { + Z3_TRY; + LOG_Z3_goal_is_decided_unsat(c, g); + RESET_ERROR_CODE(); + return to_goal_ref(g)->is_decided_unsat(); + Z3_CATCH_RETURN(Z3_FALSE); + } + + Z3_goal Z3_API Z3_goal_translate(Z3_context c, Z3_goal g, Z3_context target) { + Z3_TRY; + LOG_Z3_goal_translate(c, g, target); + RESET_ERROR_CODE(); + ast_translation translator(mk_c(c)->m(), mk_c(target)->m()); + Z3_goal_ref * _r = alloc(Z3_goal_ref); + _r->m_goal = to_goal_ref(g)->translate(translator); + mk_c(target)->save_object(_r); + Z3_goal r = of_goal(_r); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_string Z3_API Z3_goal_to_string(Z3_context c, Z3_goal g) { + Z3_TRY; + LOG_Z3_goal_to_string(c, g); + RESET_ERROR_CODE(); + std::ostringstream buffer; + to_goal_ref(g)->display(buffer); + // Hack for removing the trailing '\n' + std::string result = buffer.str(); + SASSERT(result.size() > 0); + result.resize(result.size()-1); + return mk_c(c)->mk_external_string(result); + Z3_CATCH_RETURN(""); + } + +}; diff --git a/lib/api_goal.h b/lib/api_goal.h new file mode 100644 index 000000000..50baf451e --- /dev/null +++ b/lib/api_goal.h @@ -0,0 +1,33 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_goal.h + +Abstract: + API for creating goals + +Author: + + Leonardo de Moura (leonardo) 2012-03-06. + +Revision History: + +--*/ +#ifndef _API_GOAL_H_ +#define _API_GOAL_H_ + +#include"api_util.h" +#include"goal.h" + +struct Z3_goal_ref : public api::object { + goal_ref m_goal; + virtual ~Z3_goal_ref() {} +}; + +inline Z3_goal_ref * to_goal(Z3_goal g) { return reinterpret_cast(g); } +inline Z3_goal of_goal(Z3_goal_ref * g) { return reinterpret_cast(g); } +inline goal_ref to_goal_ref(Z3_goal g) { return g == 0 ? goal_ref() : to_goal(g)->m_goal; } + +#endif diff --git a/lib/api_log.cpp b/lib/api_log.cpp new file mode 100644 index 000000000..84c262a96 --- /dev/null +++ b/lib/api_log.cpp @@ -0,0 +1,54 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_log.cpp + +Abstract: + API for creating logs + +Author: + + Leonardo de Moura (leonardo) 2012-02-29. + +Revision History: + +--*/ +#include +#include +#include"z3.h" +#include"api_log_macros.h" +#include"util.h" + +std::ostream * g_z3_log = 0; +bool g_z3_log_enabled = false; + +extern "C" { + Z3_bool Z3_API Z3_open_log(Z3_string filename) { + if (g_z3_log != 0) + Z3_close_log(); + g_z3_log = alloc(std::ofstream, filename); + g_z3_log_enabled = true; + if (g_z3_log->bad() || g_z3_log->fail()) { + dealloc(g_z3_log); + g_z3_log = 0; + return Z3_FALSE; + } + return Z3_TRUE; + } + + void Z3_API Z3_append_log(Z3_string str) { + if (g_z3_log == 0) + return; + _Z3_append_log(static_cast(str)); + } + + void Z3_API Z3_close_log() { + if (g_z3_log != 0) { + dealloc(g_z3_log); + g_z3_log_enabled = false; + g_z3_log = 0; + } + } +} diff --git a/lib/api_log_macros.cpp b/lib/api_log_macros.cpp new file mode 100644 index 000000000..a31bab8ee --- /dev/null +++ b/lib/api_log_macros.cpp @@ -0,0 +1,3311 @@ +// Automatically generated file, generator: api.py +#include +#include"z3.h" +#include"api_log_macros.h" +#include"z3_logger.h" +void log_Z3_mk_config() { + R(); + C(0); +} +void log_Z3_del_config(Z3_config a0) { + R(); + P(a0); + C(1); +} +void log_Z3_set_param_value(Z3_config a0, Z3_string a1, Z3_string a2) { + R(); + P(a0); + S(a1); + S(a2); + C(2); +} +void log_Z3_mk_context(Z3_config a0) { + R(); + P(a0); + C(3); +} +void log_Z3_mk_context_rc(Z3_config a0) { + R(); + P(a0); + C(4); +} +void log_Z3_set_logic(Z3_context a0, Z3_string a1) { + R(); + P(a0); + S(a1); + C(5); +} +void log_Z3_del_context(Z3_context a0) { + R(); + P(a0); + C(6); +} +void log_Z3_inc_ref(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(7); +} +void log_Z3_dec_ref(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(8); +} +void log_Z3_toggle_warning_messages(Z3_bool a0) { + R(); + I(a0); + C(9); +} +void log_Z3_update_param_value(Z3_context a0, Z3_string a1, Z3_string a2) { + R(); + P(a0); + S(a1); + S(a2); + C(10); +} +void log_Z3_get_param_value(Z3_context a0, Z3_string a1, Z3_string* a2) { + R(); + P(a0); + S(a1); + S(""); + C(11); +} +void log_Z3_mk_int_symbol(Z3_context a0, int a1) { + R(); + P(a0); + I(a1); + C(12); +} +void log_Z3_mk_string_symbol(Z3_context a0, Z3_string a1) { + R(); + P(a0); + S(a1); + C(13); +} +void log_Z3_is_eq_sort(Z3_context a0, Z3_sort a1, Z3_sort a2) { + R(); + P(a0); + P(a1); + P(a2); + C(14); +} +void log_Z3_mk_uninterpreted_sort(Z3_context a0, Z3_symbol a1) { + R(); + P(a0); + Sy(a1); + C(15); +} +void log_Z3_mk_bool_sort(Z3_context a0) { + R(); + P(a0); + C(16); +} +void log_Z3_mk_int_sort(Z3_context a0) { + R(); + P(a0); + C(17); +} +void log_Z3_mk_real_sort(Z3_context a0) { + R(); + P(a0); + C(18); +} +void log_Z3_mk_bv_sort(Z3_context a0, unsigned a1) { + R(); + P(a0); + U(a1); + C(19); +} +void log_Z3_mk_array_sort(Z3_context a0, Z3_sort a1, Z3_sort a2) { + R(); + P(a0); + P(a1); + P(a2); + C(20); +} +void log_Z3_mk_tuple_sort(Z3_context a0, Z3_symbol a1, unsigned a2, Z3_symbol const * a3, Z3_sort const * a4, Z3_func_decl* a5, Z3_func_decl* a6) { + R(); + P(a0); + Sy(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { Sy(a3[i]); } + Asy(a2); + for (unsigned i = 0; i < a2; i++) { P(a4[i]); } + Ap(a2); + P(0); + for (unsigned i = 0; i < a2; i++) { P(0); } + Ap(a2); + C(21); +} +void log_Z3_mk_enumeration_sort(Z3_context a0, Z3_symbol a1, unsigned a2, Z3_symbol const * a3, Z3_func_decl* a4, Z3_func_decl* a5) { + R(); + P(a0); + Sy(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { Sy(a3[i]); } + Asy(a2); + for (unsigned i = 0; i < a2; i++) { P(0); } + Ap(a2); + for (unsigned i = 0; i < a2; i++) { P(0); } + Ap(a2); + C(22); +} +void log_Z3_mk_list_sort(Z3_context a0, Z3_symbol a1, Z3_sort a2, Z3_func_decl* a3, Z3_func_decl* a4, Z3_func_decl* a5, Z3_func_decl* a6, Z3_func_decl* a7, Z3_func_decl* a8) { + R(); + P(a0); + Sy(a1); + P(a2); + P(0); + P(0); + P(0); + P(0); + P(0); + P(0); + C(23); +} +void log_Z3_mk_constructor(Z3_context a0, Z3_symbol a1, Z3_symbol a2, unsigned a3, Z3_symbol const * a4, Z3_sort const * a5, unsigned const * a6) { + R(); + P(a0); + Sy(a1); + Sy(a2); + U(a3); + for (unsigned i = 0; i < a3; i++) { Sy(a4[i]); } + Asy(a3); + for (unsigned i = 0; i < a3; i++) { P(a5[i]); } + Ap(a3); + for (unsigned i = 0; i < a3; i++) { U(a6[i]); } + Au(a3); + C(24); +} +void log_Z3_query_constructor(Z3_context a0, Z3_constructor a1, unsigned a2, Z3_func_decl* a3, Z3_func_decl* a4, Z3_func_decl* a5) { + R(); + P(a0); + P(a1); + U(a2); + P(0); + P(0); + for (unsigned i = 0; i < a2; i++) { P(0); } + Ap(a2); + C(25); +} +void log_Z3_del_constructor(Z3_context a0, Z3_constructor a1) { + R(); + P(a0); + P(a1); + C(26); +} +void log_Z3_mk_datatype(Z3_context a0, Z3_symbol a1, unsigned a2, Z3_constructor* a3) { + R(); + P(a0); + Sy(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { P(a3[i]); } + Ap(a2); + C(27); +} +void log_Z3_mk_constructor_list(Z3_context a0, unsigned a1, Z3_constructor const * a2) { + R(); + P(a0); + U(a1); + for (unsigned i = 0; i < a1; i++) { P(a2[i]); } + Ap(a1); + C(28); +} +void log_Z3_del_constructor_list(Z3_context a0, Z3_constructor_list a1) { + R(); + P(a0); + P(a1); + C(29); +} +void log_Z3_mk_datatypes(Z3_context a0, unsigned a1, Z3_symbol const * a2, Z3_sort* a3, Z3_constructor_list* a4) { + R(); + P(a0); + U(a1); + for (unsigned i = 0; i < a1; i++) { Sy(a2[i]); } + Asy(a1); + for (unsigned i = 0; i < a1; i++) { P(0); } + Ap(a1); + for (unsigned i = 0; i < a1; i++) { P(a4[i]); } + Ap(a1); + C(30); +} +void log_Z3_is_eq_ast(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(31); +} +void log_Z3_is_eq_func_decl(Z3_context a0, Z3_func_decl a1, Z3_func_decl a2) { + R(); + P(a0); + P(a1); + P(a2); + C(32); +} +void log_Z3_mk_func_decl(Z3_context a0, Z3_symbol a1, unsigned a2, Z3_sort const * a3, Z3_sort a4) { + R(); + P(a0); + Sy(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { P(a3[i]); } + Ap(a2); + P(a4); + C(33); +} +void log_Z3_mk_app(Z3_context a0, Z3_func_decl a1, unsigned a2, Z3_ast const * a3) { + R(); + P(a0); + P(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { P(a3[i]); } + Ap(a2); + C(34); +} +void log_Z3_mk_const(Z3_context a0, Z3_symbol a1, Z3_sort a2) { + R(); + P(a0); + Sy(a1); + P(a2); + C(35); +} +void log_Z3_mk_label(Z3_context a0, Z3_symbol a1, Z3_bool a2, Z3_ast a3) { + R(); + P(a0); + Sy(a1); + I(a2); + P(a3); + C(36); +} +void log_Z3_mk_fresh_func_decl(Z3_context a0, Z3_string a1, unsigned a2, Z3_sort const * a3, Z3_sort a4) { + R(); + P(a0); + S(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { P(a3[i]); } + Ap(a2); + P(a4); + C(37); +} +void log_Z3_mk_fresh_const(Z3_context a0, Z3_string a1, Z3_sort a2) { + R(); + P(a0); + S(a1); + P(a2); + C(38); +} +void log_Z3_mk_true(Z3_context a0) { + R(); + P(a0); + C(39); +} +void log_Z3_mk_false(Z3_context a0) { + R(); + P(a0); + C(40); +} +void log_Z3_mk_eq(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(41); +} +void log_Z3_mk_distinct(Z3_context a0, unsigned a1, Z3_ast const * a2) { + R(); + P(a0); + U(a1); + for (unsigned i = 0; i < a1; i++) { P(a2[i]); } + Ap(a1); + C(42); +} +void log_Z3_mk_not(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(43); +} +void log_Z3_mk_ite(Z3_context a0, Z3_ast a1, Z3_ast a2, Z3_ast a3) { + R(); + P(a0); + P(a1); + P(a2); + P(a3); + C(44); +} +void log_Z3_mk_iff(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(45); +} +void log_Z3_mk_implies(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(46); +} +void log_Z3_mk_xor(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(47); +} +void log_Z3_mk_and(Z3_context a0, unsigned a1, Z3_ast const * a2) { + R(); + P(a0); + U(a1); + for (unsigned i = 0; i < a1; i++) { P(a2[i]); } + Ap(a1); + C(48); +} +void log_Z3_mk_or(Z3_context a0, unsigned a1, Z3_ast const * a2) { + R(); + P(a0); + U(a1); + for (unsigned i = 0; i < a1; i++) { P(a2[i]); } + Ap(a1); + C(49); +} +void log_Z3_mk_add(Z3_context a0, unsigned a1, Z3_ast const * a2) { + R(); + P(a0); + U(a1); + for (unsigned i = 0; i < a1; i++) { P(a2[i]); } + Ap(a1); + C(50); +} +void log_Z3_mk_mul(Z3_context a0, unsigned a1, Z3_ast const * a2) { + R(); + P(a0); + U(a1); + for (unsigned i = 0; i < a1; i++) { P(a2[i]); } + Ap(a1); + C(51); +} +void log_Z3_mk_sub(Z3_context a0, unsigned a1, Z3_ast const * a2) { + R(); + P(a0); + U(a1); + for (unsigned i = 0; i < a1; i++) { P(a2[i]); } + Ap(a1); + C(52); +} +void log_Z3_mk_unary_minus(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(53); +} +void log_Z3_mk_div(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(54); +} +void log_Z3_mk_mod(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(55); +} +void log_Z3_mk_rem(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(56); +} +void log_Z3_mk_power(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(57); +} +void log_Z3_is_algebraic_number(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(58); +} +void log_Z3_get_algebraic_number_lower(Z3_context a0, Z3_ast a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(59); +} +void log_Z3_get_algebraic_number_upper(Z3_context a0, Z3_ast a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(60); +} +void log_Z3_mk_lt(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(61); +} +void log_Z3_mk_le(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(62); +} +void log_Z3_mk_gt(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(63); +} +void log_Z3_mk_ge(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(64); +} +void log_Z3_mk_int2real(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(65); +} +void log_Z3_mk_real2int(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(66); +} +void log_Z3_mk_is_int(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(67); +} +void log_Z3_mk_bvnot(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(68); +} +void log_Z3_mk_bvredand(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(69); +} +void log_Z3_mk_bvredor(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(70); +} +void log_Z3_mk_bvand(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(71); +} +void log_Z3_mk_bvor(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(72); +} +void log_Z3_mk_bvxor(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(73); +} +void log_Z3_mk_bvnand(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(74); +} +void log_Z3_mk_bvnor(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(75); +} +void log_Z3_mk_bvxnor(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(76); +} +void log_Z3_mk_bvneg(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(77); +} +void log_Z3_mk_bvadd(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(78); +} +void log_Z3_mk_bvsub(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(79); +} +void log_Z3_mk_bvmul(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(80); +} +void log_Z3_mk_bvudiv(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(81); +} +void log_Z3_mk_bvsdiv(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(82); +} +void log_Z3_mk_bvurem(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(83); +} +void log_Z3_mk_bvsrem(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(84); +} +void log_Z3_mk_bvsmod(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(85); +} +void log_Z3_mk_bvult(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(86); +} +void log_Z3_mk_bvslt(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(87); +} +void log_Z3_mk_bvule(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(88); +} +void log_Z3_mk_bvsle(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(89); +} +void log_Z3_mk_bvuge(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(90); +} +void log_Z3_mk_bvsge(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(91); +} +void log_Z3_mk_bvugt(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(92); +} +void log_Z3_mk_bvsgt(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(93); +} +void log_Z3_mk_concat(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(94); +} +void log_Z3_mk_extract(Z3_context a0, unsigned a1, unsigned a2, Z3_ast a3) { + R(); + P(a0); + U(a1); + U(a2); + P(a3); + C(95); +} +void log_Z3_mk_sign_ext(Z3_context a0, unsigned a1, Z3_ast a2) { + R(); + P(a0); + U(a1); + P(a2); + C(96); +} +void log_Z3_mk_zero_ext(Z3_context a0, unsigned a1, Z3_ast a2) { + R(); + P(a0); + U(a1); + P(a2); + C(97); +} +void log_Z3_mk_repeat(Z3_context a0, unsigned a1, Z3_ast a2) { + R(); + P(a0); + U(a1); + P(a2); + C(98); +} +void log_Z3_mk_bvshl(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(99); +} +void log_Z3_mk_bvlshr(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(100); +} +void log_Z3_mk_bvashr(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(101); +} +void log_Z3_mk_rotate_left(Z3_context a0, unsigned a1, Z3_ast a2) { + R(); + P(a0); + U(a1); + P(a2); + C(102); +} +void log_Z3_mk_rotate_right(Z3_context a0, unsigned a1, Z3_ast a2) { + R(); + P(a0); + U(a1); + P(a2); + C(103); +} +void log_Z3_mk_ext_rotate_left(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(104); +} +void log_Z3_mk_ext_rotate_right(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(105); +} +void log_Z3_mk_int2bv(Z3_context a0, unsigned a1, Z3_ast a2) { + R(); + P(a0); + U(a1); + P(a2); + C(106); +} +void log_Z3_mk_bv2int(Z3_context a0, Z3_ast a1, Z3_bool a2) { + R(); + P(a0); + P(a1); + I(a2); + C(107); +} +void log_Z3_mk_bvadd_no_overflow(Z3_context a0, Z3_ast a1, Z3_ast a2, Z3_bool a3) { + R(); + P(a0); + P(a1); + P(a2); + I(a3); + C(108); +} +void log_Z3_mk_bvadd_no_underflow(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(109); +} +void log_Z3_mk_bvsub_no_overflow(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(110); +} +void log_Z3_mk_bvsub_no_underflow(Z3_context a0, Z3_ast a1, Z3_ast a2, Z3_bool a3) { + R(); + P(a0); + P(a1); + P(a2); + I(a3); + C(111); +} +void log_Z3_mk_bvsdiv_no_overflow(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(112); +} +void log_Z3_mk_bvneg_no_overflow(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(113); +} +void log_Z3_mk_bvmul_no_overflow(Z3_context a0, Z3_ast a1, Z3_ast a2, Z3_bool a3) { + R(); + P(a0); + P(a1); + P(a2); + I(a3); + C(114); +} +void log_Z3_mk_bvmul_no_underflow(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(115); +} +void log_Z3_mk_select(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(116); +} +void log_Z3_mk_store(Z3_context a0, Z3_ast a1, Z3_ast a2, Z3_ast a3) { + R(); + P(a0); + P(a1); + P(a2); + P(a3); + C(117); +} +void log_Z3_mk_const_array(Z3_context a0, Z3_sort a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(118); +} +void log_Z3_mk_map(Z3_context a0, Z3_func_decl a1, unsigned a2, Z3_ast const * a3) { + R(); + P(a0); + P(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { P(a3[i]); } + Ap(a2); + C(119); +} +void log_Z3_mk_array_default(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(120); +} +void log_Z3_mk_set_sort(Z3_context a0, Z3_sort a1) { + R(); + P(a0); + P(a1); + C(121); +} +void log_Z3_mk_empty_set(Z3_context a0, Z3_sort a1) { + R(); + P(a0); + P(a1); + C(122); +} +void log_Z3_mk_full_set(Z3_context a0, Z3_sort a1) { + R(); + P(a0); + P(a1); + C(123); +} +void log_Z3_mk_set_add(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(124); +} +void log_Z3_mk_set_del(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(125); +} +void log_Z3_mk_set_union(Z3_context a0, unsigned a1, Z3_ast const * a2) { + R(); + P(a0); + U(a1); + for (unsigned i = 0; i < a1; i++) { P(a2[i]); } + Ap(a1); + C(126); +} +void log_Z3_mk_set_intersect(Z3_context a0, unsigned a1, Z3_ast const * a2) { + R(); + P(a0); + U(a1); + for (unsigned i = 0; i < a1; i++) { P(a2[i]); } + Ap(a1); + C(127); +} +void log_Z3_mk_set_difference(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(128); +} +void log_Z3_mk_set_complement(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(129); +} +void log_Z3_mk_set_member(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(130); +} +void log_Z3_mk_set_subset(Z3_context a0, Z3_ast a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(131); +} +void log_Z3_mk_numeral(Z3_context a0, Z3_string a1, Z3_sort a2) { + R(); + P(a0); + S(a1); + P(a2); + C(132); +} +void log_Z3_mk_real(Z3_context a0, int a1, int a2) { + R(); + P(a0); + I(a1); + I(a2); + C(133); +} +void log_Z3_mk_int(Z3_context a0, int a1, Z3_sort a2) { + R(); + P(a0); + I(a1); + P(a2); + C(134); +} +void log_Z3_mk_unsigned_int(Z3_context a0, unsigned a1, Z3_sort a2) { + R(); + P(a0); + U(a1); + P(a2); + C(135); +} +void log_Z3_mk_int64(Z3_context a0, __int64 a1, Z3_sort a2) { + R(); + P(a0); + I(a1); + P(a2); + C(136); +} +void log_Z3_mk_unsigned_int64(Z3_context a0, __uint64 a1, Z3_sort a2) { + R(); + P(a0); + U(a1); + P(a2); + C(137); +} +void log_Z3_mk_pattern(Z3_context a0, unsigned a1, Z3_ast const * a2) { + R(); + P(a0); + U(a1); + for (unsigned i = 0; i < a1; i++) { P(a2[i]); } + Ap(a1); + C(138); +} +void log_Z3_mk_bound(Z3_context a0, unsigned a1, Z3_sort a2) { + R(); + P(a0); + U(a1); + P(a2); + C(139); +} +void log_Z3_mk_forall(Z3_context a0, unsigned a1, unsigned a2, Z3_pattern const * a3, unsigned a4, Z3_sort const * a5, Z3_symbol const * a6, Z3_ast a7) { + R(); + P(a0); + U(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { P(a3[i]); } + Ap(a2); + U(a4); + for (unsigned i = 0; i < a4; i++) { P(a5[i]); } + Ap(a4); + for (unsigned i = 0; i < a4; i++) { Sy(a6[i]); } + Asy(a4); + P(a7); + C(140); +} +void log_Z3_mk_exists(Z3_context a0, unsigned a1, unsigned a2, Z3_pattern const * a3, unsigned a4, Z3_sort const * a5, Z3_symbol const * a6, Z3_ast a7) { + R(); + P(a0); + U(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { P(a3[i]); } + Ap(a2); + U(a4); + for (unsigned i = 0; i < a4; i++) { P(a5[i]); } + Ap(a4); + for (unsigned i = 0; i < a4; i++) { Sy(a6[i]); } + Asy(a4); + P(a7); + C(141); +} +void log_Z3_mk_quantifier(Z3_context a0, Z3_bool a1, unsigned a2, unsigned a3, Z3_pattern const * a4, unsigned a5, Z3_sort const * a6, Z3_symbol const * a7, Z3_ast a8) { + R(); + P(a0); + I(a1); + U(a2); + U(a3); + for (unsigned i = 0; i < a3; i++) { P(a4[i]); } + Ap(a3); + U(a5); + for (unsigned i = 0; i < a5; i++) { P(a6[i]); } + Ap(a5); + for (unsigned i = 0; i < a5; i++) { Sy(a7[i]); } + Asy(a5); + P(a8); + C(142); +} +void log_Z3_mk_quantifier_ex(Z3_context a0, Z3_bool a1, unsigned a2, Z3_symbol a3, Z3_symbol a4, unsigned a5, Z3_pattern const * a6, unsigned a7, Z3_ast const * a8, unsigned a9, Z3_sort const * a10, Z3_symbol const * a11, Z3_ast a12) { + R(); + P(a0); + I(a1); + U(a2); + Sy(a3); + Sy(a4); + U(a5); + for (unsigned i = 0; i < a5; i++) { P(a6[i]); } + Ap(a5); + U(a7); + for (unsigned i = 0; i < a7; i++) { P(a8[i]); } + Ap(a7); + U(a9); + for (unsigned i = 0; i < a9; i++) { P(a10[i]); } + Ap(a9); + for (unsigned i = 0; i < a9; i++) { Sy(a11[i]); } + Asy(a9); + P(a12); + C(143); +} +void log_Z3_mk_forall_const(Z3_context a0, unsigned a1, unsigned a2, Z3_app const * a3, unsigned a4, Z3_pattern const * a5, Z3_ast a6) { + R(); + P(a0); + U(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { P(a3[i]); } + Ap(a2); + U(a4); + for (unsigned i = 0; i < a4; i++) { P(a5[i]); } + Ap(a4); + P(a6); + C(144); +} +void log_Z3_mk_exists_const(Z3_context a0, unsigned a1, unsigned a2, Z3_app const * a3, unsigned a4, Z3_pattern const * a5, Z3_ast a6) { + R(); + P(a0); + U(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { P(a3[i]); } + Ap(a2); + U(a4); + for (unsigned i = 0; i < a4; i++) { P(a5[i]); } + Ap(a4); + P(a6); + C(145); +} +void log_Z3_mk_quantifier_const(Z3_context a0, Z3_bool a1, unsigned a2, unsigned a3, Z3_app const * a4, unsigned a5, Z3_pattern const * a6, Z3_ast a7) { + R(); + P(a0); + I(a1); + U(a2); + U(a3); + for (unsigned i = 0; i < a3; i++) { P(a4[i]); } + Ap(a3); + U(a5); + for (unsigned i = 0; i < a5; i++) { P(a6[i]); } + Ap(a5); + P(a7); + C(146); +} +void log_Z3_mk_quantifier_const_ex(Z3_context a0, Z3_bool a1, unsigned a2, Z3_symbol a3, Z3_symbol a4, unsigned a5, Z3_app const * a6, unsigned a7, Z3_pattern const * a8, unsigned a9, Z3_ast const * a10, Z3_ast a11) { + R(); + P(a0); + I(a1); + U(a2); + Sy(a3); + Sy(a4); + U(a5); + for (unsigned i = 0; i < a5; i++) { P(a6[i]); } + Ap(a5); + U(a7); + for (unsigned i = 0; i < a7; i++) { P(a8[i]); } + Ap(a7); + U(a9); + for (unsigned i = 0; i < a9; i++) { P(a10[i]); } + Ap(a9); + P(a11); + C(147); +} +void log_Z3_get_ast_id(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(148); +} +void log_Z3_get_func_decl_id(Z3_context a0, Z3_func_decl a1) { + R(); + P(a0); + P(a1); + C(149); +} +void log_Z3_get_sort_id(Z3_context a0, Z3_sort a1) { + R(); + P(a0); + P(a1); + C(150); +} +void log_Z3_is_well_sorted(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(151); +} +void log_Z3_get_symbol_kind(Z3_context a0, Z3_symbol a1) { + R(); + P(a0); + Sy(a1); + C(152); +} +void log_Z3_get_symbol_int(Z3_context a0, Z3_symbol a1) { + R(); + P(a0); + Sy(a1); + C(153); +} +void log_Z3_get_symbol_string(Z3_context a0, Z3_symbol a1) { + R(); + P(a0); + Sy(a1); + C(154); +} +void log_Z3_get_ast_kind(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(155); +} +void log_Z3_get_ast_hash(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(156); +} +void log_Z3_get_numeral_string(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(157); +} +void log_Z3_get_numeral_decimal_string(Z3_context a0, Z3_ast a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(158); +} +void log_Z3_get_numerator(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(159); +} +void log_Z3_get_denominator(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(160); +} +void log_Z3_get_numeral_small(Z3_context a0, Z3_ast a1, __int64* a2, __int64* a3) { + R(); + P(a0); + P(a1); + I(0); + I(0); + C(161); +} +void log_Z3_get_numeral_int(Z3_context a0, Z3_ast a1, int* a2) { + R(); + P(a0); + P(a1); + I(0); + C(162); +} +void log_Z3_get_numeral_uint(Z3_context a0, Z3_ast a1, unsigned* a2) { + R(); + P(a0); + P(a1); + U(0); + C(163); +} +void log_Z3_get_numeral_uint64(Z3_context a0, Z3_ast a1, __uint64* a2) { + R(); + P(a0); + P(a1); + U(0); + C(164); +} +void log_Z3_get_numeral_int64(Z3_context a0, Z3_ast a1, __int64* a2) { + R(); + P(a0); + P(a1); + I(0); + C(165); +} +void log_Z3_get_numeral_rational_int64(Z3_context a0, Z3_ast a1, __int64* a2, __int64* a3) { + R(); + P(a0); + P(a1); + I(0); + I(0); + C(166); +} +void log_Z3_get_bool_value(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(167); +} +void log_Z3_get_app_decl(Z3_context a0, Z3_app a1) { + R(); + P(a0); + P(a1); + C(168); +} +void log_Z3_get_app_num_args(Z3_context a0, Z3_app a1) { + R(); + P(a0); + P(a1); + C(169); +} +void log_Z3_get_app_arg(Z3_context a0, Z3_app a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(170); +} +void log_Z3_get_index_value(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(171); +} +void log_Z3_is_quantifier_forall(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(172); +} +void log_Z3_get_quantifier_weight(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(173); +} +void log_Z3_get_quantifier_num_patterns(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(174); +} +void log_Z3_get_quantifier_pattern_ast(Z3_context a0, Z3_ast a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(175); +} +void log_Z3_get_quantifier_num_no_patterns(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(176); +} +void log_Z3_get_quantifier_no_pattern_ast(Z3_context a0, Z3_ast a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(177); +} +void log_Z3_get_quantifier_bound_name(Z3_context a0, Z3_ast a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(178); +} +void log_Z3_get_quantifier_bound_sort(Z3_context a0, Z3_ast a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(179); +} +void log_Z3_get_quantifier_body(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(180); +} +void log_Z3_get_quantifier_num_bound(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(181); +} +void log_Z3_get_decl_name(Z3_context a0, Z3_func_decl a1) { + R(); + P(a0); + P(a1); + C(182); +} +void log_Z3_get_decl_num_parameters(Z3_context a0, Z3_func_decl a1) { + R(); + P(a0); + P(a1); + C(183); +} +void log_Z3_get_decl_parameter_kind(Z3_context a0, Z3_func_decl a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(184); +} +void log_Z3_get_decl_int_parameter(Z3_context a0, Z3_func_decl a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(185); +} +void log_Z3_get_decl_double_parameter(Z3_context a0, Z3_func_decl a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(186); +} +void log_Z3_get_decl_symbol_parameter(Z3_context a0, Z3_func_decl a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(187); +} +void log_Z3_get_decl_sort_parameter(Z3_context a0, Z3_func_decl a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(188); +} +void log_Z3_get_decl_ast_parameter(Z3_context a0, Z3_func_decl a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(189); +} +void log_Z3_get_decl_func_decl_parameter(Z3_context a0, Z3_func_decl a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(190); +} +void log_Z3_get_decl_rational_parameter(Z3_context a0, Z3_func_decl a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(191); +} +void log_Z3_get_sort_name(Z3_context a0, Z3_sort a1) { + R(); + P(a0); + P(a1); + C(192); +} +void log_Z3_get_sort(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(193); +} +void log_Z3_get_domain_size(Z3_context a0, Z3_func_decl a1) { + R(); + P(a0); + P(a1); + C(194); +} +void log_Z3_get_domain(Z3_context a0, Z3_func_decl a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(195); +} +void log_Z3_get_range(Z3_context a0, Z3_func_decl a1) { + R(); + P(a0); + P(a1); + C(196); +} +void log_Z3_get_sort_kind(Z3_context a0, Z3_sort a1) { + R(); + P(a0); + P(a1); + C(197); +} +void log_Z3_get_bv_sort_size(Z3_context a0, Z3_sort a1) { + R(); + P(a0); + P(a1); + C(198); +} +void log_Z3_get_array_sort_domain(Z3_context a0, Z3_sort a1) { + R(); + P(a0); + P(a1); + C(199); +} +void log_Z3_get_array_sort_range(Z3_context a0, Z3_sort a1) { + R(); + P(a0); + P(a1); + C(200); +} +void log_Z3_get_tuple_sort_mk_decl(Z3_context a0, Z3_sort a1) { + R(); + P(a0); + P(a1); + C(201); +} +void log_Z3_get_decl_kind(Z3_context a0, Z3_func_decl a1) { + R(); + P(a0); + P(a1); + C(202); +} +void log_Z3_get_tuple_sort_num_fields(Z3_context a0, Z3_sort a1) { + R(); + P(a0); + P(a1); + C(203); +} +void log_Z3_get_tuple_sort_field_decl(Z3_context a0, Z3_sort a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(204); +} +void log_Z3_get_datatype_sort_num_constructors(Z3_context a0, Z3_sort a1) { + R(); + P(a0); + P(a1); + C(205); +} +void log_Z3_get_datatype_sort_constructor(Z3_context a0, Z3_sort a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(206); +} +void log_Z3_get_datatype_sort_recognizer(Z3_context a0, Z3_sort a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(207); +} +void log_Z3_get_datatype_sort_constructor_accessor(Z3_context a0, Z3_sort a1, unsigned a2, unsigned a3) { + R(); + P(a0); + P(a1); + U(a2); + U(a3); + C(208); +} +void log_Z3_get_relation_arity(Z3_context a0, Z3_sort a1) { + R(); + P(a0); + P(a1); + C(209); +} +void log_Z3_get_relation_column(Z3_context a0, Z3_sort a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(210); +} +void log_Z3_get_finite_domain_sort_size(Z3_context a0, Z3_sort a1, __uint64* a2) { + R(); + P(a0); + P(a1); + U(0); + C(211); +} +void log_Z3_mk_finite_domain_sort(Z3_context a0, Z3_symbol a1, __uint64 a2) { + R(); + P(a0); + Sy(a1); + U(a2); + C(212); +} +void log_Z3_get_pattern_num_terms(Z3_context a0, Z3_pattern a1) { + R(); + P(a0); + P(a1); + C(213); +} +void log_Z3_get_pattern(Z3_context a0, Z3_pattern a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(214); +} +void log_Z3_simplify(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(215); +} +void log_Z3_simplify_ex(Z3_context a0, Z3_ast a1, Z3_params a2) { + R(); + P(a0); + P(a1); + P(a2); + C(216); +} +void log_Z3_simplify_get_help(Z3_context a0) { + R(); + P(a0); + C(217); +} +void log_Z3_simplify_get_param_descrs(Z3_context a0) { + R(); + P(a0); + C(218); +} +void log_Z3_update_term(Z3_context a0, Z3_ast a1, unsigned a2, Z3_ast const * a3) { + R(); + P(a0); + P(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { P(a3[i]); } + Ap(a2); + C(219); +} +void log_Z3_substitute(Z3_context a0, Z3_ast a1, unsigned a2, Z3_ast const * a3, Z3_ast const * a4) { + R(); + P(a0); + P(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { P(a3[i]); } + Ap(a2); + for (unsigned i = 0; i < a2; i++) { P(a4[i]); } + Ap(a2); + C(220); +} +void log_Z3_substitute_vars(Z3_context a0, Z3_ast a1, unsigned a2, Z3_ast const * a3) { + R(); + P(a0); + P(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { P(a3[i]); } + Ap(a2); + C(221); +} +void log_Z3_sort_to_ast(Z3_context a0, Z3_sort a1) { + R(); + P(a0); + P(a1); + C(222); +} +void log_Z3_func_decl_to_ast(Z3_context a0, Z3_func_decl a1) { + R(); + P(a0); + P(a1); + C(223); +} +void log_Z3_pattern_to_ast(Z3_context a0, Z3_pattern a1) { + R(); + P(a0); + P(a1); + C(224); +} +void log_Z3_app_to_ast(Z3_context a0, Z3_app a1) { + R(); + P(a0); + P(a1); + C(225); +} +void log_Z3_to_app(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(226); +} +void log_Z3_to_func_decl(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(227); +} +void log_Z3_push(Z3_context a0) { + R(); + P(a0); + C(228); +} +void log_Z3_pop(Z3_context a0, unsigned a1) { + R(); + P(a0); + U(a1); + C(229); +} +void log_Z3_get_num_scopes(Z3_context a0) { + R(); + P(a0); + C(230); +} +void log_Z3_persist_ast(Z3_context a0, Z3_ast a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(231); +} +void log_Z3_assert_cnstr(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(232); +} +void log_Z3_check_and_get_model(Z3_context a0, Z3_model* a1) { + R(); + P(a0); + P(0); + C(233); +} +void log_Z3_check(Z3_context a0) { + R(); + P(a0); + C(234); +} +void log_Z3_check_assumptions(Z3_context a0, unsigned a1, Z3_ast const * a2, Z3_model* a3, Z3_ast* a4, unsigned* a5, Z3_ast* a6) { + R(); + P(a0); + U(a1); + for (unsigned i = 0; i < a1; i++) { P(a2[i]); } + Ap(a1); + P(0); + P(0); + U(0); + for (unsigned i = 0; i < a1; i++) { P(0); } + Ap(a1); + C(235); +} +void log_Z3_get_implied_equalities(Z3_context a0, unsigned a1, Z3_ast const * a2, unsigned* a3) { + R(); + P(a0); + U(a1); + for (unsigned i = 0; i < a1; i++) { P(a2[i]); } + Ap(a1); + for (unsigned i = 0; i < a1; i++) { U(0); } + Au(a1); + C(236); +} +void log_Z3_del_model(Z3_context a0, Z3_model a1) { + R(); + P(a0); + P(a1); + C(237); +} +void log_Z3_soft_check_cancel(Z3_context a0) { + R(); + P(a0); + C(238); +} +void log_Z3_get_search_failure(Z3_context a0) { + R(); + P(a0); + C(239); +} +void log_Z3_get_relevant_labels(Z3_context a0) { + R(); + P(a0); + C(240); +} +void log_Z3_get_relevant_literals(Z3_context a0) { + R(); + P(a0); + C(241); +} +void log_Z3_get_guessed_literals(Z3_context a0) { + R(); + P(a0); + C(242); +} +void log_Z3_del_literals(Z3_context a0, Z3_literals a1) { + R(); + P(a0); + P(a1); + C(243); +} +void log_Z3_get_num_literals(Z3_context a0, Z3_literals a1) { + R(); + P(a0); + P(a1); + C(244); +} +void log_Z3_get_label_symbol(Z3_context a0, Z3_literals a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(245); +} +void log_Z3_get_literal(Z3_context a0, Z3_literals a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(246); +} +void log_Z3_disable_literal(Z3_context a0, Z3_literals a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(247); +} +void log_Z3_block_literals(Z3_context a0, Z3_literals a1) { + R(); + P(a0); + P(a1); + C(248); +} +void log_Z3_model_inc_ref(Z3_context a0, Z3_model a1) { + R(); + P(a0); + P(a1); + C(249); +} +void log_Z3_model_dec_ref(Z3_context a0, Z3_model a1) { + R(); + P(a0); + P(a1); + C(250); +} +void log_Z3_model_get_const_interp(Z3_context a0, Z3_model a1, Z3_func_decl a2) { + R(); + P(a0); + P(a1); + P(a2); + C(251); +} +void log_Z3_model_get_func_interp(Z3_context a0, Z3_model a1, Z3_func_decl a2) { + R(); + P(a0); + P(a1); + P(a2); + C(252); +} +void log_Z3_model_get_num_consts(Z3_context a0, Z3_model a1) { + R(); + P(a0); + P(a1); + C(253); +} +void log_Z3_model_get_const_decl(Z3_context a0, Z3_model a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(254); +} +void log_Z3_model_get_num_funcs(Z3_context a0, Z3_model a1) { + R(); + P(a0); + P(a1); + C(255); +} +void log_Z3_model_get_func_decl(Z3_context a0, Z3_model a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(256); +} +void log_Z3_model_eval(Z3_context a0, Z3_model a1, Z3_ast a2, Z3_bool a3, Z3_ast* a4) { + R(); + P(a0); + P(a1); + P(a2); + I(a3); + P(0); + C(257); +} +void log_Z3_model_get_num_sorts(Z3_context a0, Z3_model a1) { + R(); + P(a0); + P(a1); + C(258); +} +void log_Z3_model_get_sort(Z3_context a0, Z3_model a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(259); +} +void log_Z3_model_get_sort_universe(Z3_context a0, Z3_model a1, Z3_sort a2) { + R(); + P(a0); + P(a1); + P(a2); + C(260); +} +void log_Z3_is_as_array(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(261); +} +void log_Z3_get_as_array_func_decl(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(262); +} +void log_Z3_func_interp_inc_ref(Z3_context a0, Z3_func_interp a1) { + R(); + P(a0); + P(a1); + C(263); +} +void log_Z3_func_interp_dec_ref(Z3_context a0, Z3_func_interp a1) { + R(); + P(a0); + P(a1); + C(264); +} +void log_Z3_func_interp_get_num_entries(Z3_context a0, Z3_func_interp a1) { + R(); + P(a0); + P(a1); + C(265); +} +void log_Z3_func_interp_get_entry(Z3_context a0, Z3_func_interp a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(266); +} +void log_Z3_func_interp_get_else(Z3_context a0, Z3_func_interp a1) { + R(); + P(a0); + P(a1); + C(267); +} +void log_Z3_func_interp_get_arity(Z3_context a0, Z3_func_interp a1) { + R(); + P(a0); + P(a1); + C(268); +} +void log_Z3_func_entry_inc_ref(Z3_context a0, Z3_func_entry a1) { + R(); + P(a0); + P(a1); + C(269); +} +void log_Z3_func_entry_dec_ref(Z3_context a0, Z3_func_entry a1) { + R(); + P(a0); + P(a1); + C(270); +} +void log_Z3_func_entry_get_value(Z3_context a0, Z3_func_entry a1) { + R(); + P(a0); + P(a1); + C(271); +} +void log_Z3_func_entry_get_num_args(Z3_context a0, Z3_func_entry a1) { + R(); + P(a0); + P(a1); + C(272); +} +void log_Z3_func_entry_get_arg(Z3_context a0, Z3_func_entry a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(273); +} +void log_Z3_get_model_num_constants(Z3_context a0, Z3_model a1) { + R(); + P(a0); + P(a1); + C(274); +} +void log_Z3_get_model_constant(Z3_context a0, Z3_model a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(275); +} +void log_Z3_eval_func_decl(Z3_context a0, Z3_model a1, Z3_func_decl a2, Z3_ast* a3) { + R(); + P(a0); + P(a1); + P(a2); + P(0); + C(276); +} +void log_Z3_is_array_value(Z3_context a0, Z3_model a1, Z3_ast a2, unsigned* a3) { + R(); + P(a0); + P(a1); + P(a2); + U(0); + C(277); +} +void log_Z3_get_array_value(Z3_context a0, Z3_model a1, Z3_ast a2, unsigned a3, Z3_ast* a4, Z3_ast* a5, Z3_ast* a6) { + R(); + P(a0); + P(a1); + P(a2); + U(a3); + for (unsigned i = 0; i < a3; i++) { P(0); } + Ap(a3); + for (unsigned i = 0; i < a3; i++) { P(0); } + Ap(a3); + P(0); + C(278); +} +void log_Z3_get_model_num_funcs(Z3_context a0, Z3_model a1) { + R(); + P(a0); + P(a1); + C(279); +} +void log_Z3_get_model_func_decl(Z3_context a0, Z3_model a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(280); +} +void log_Z3_get_model_func_else(Z3_context a0, Z3_model a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(281); +} +void log_Z3_get_model_func_num_entries(Z3_context a0, Z3_model a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(282); +} +void log_Z3_get_model_func_entry_num_args(Z3_context a0, Z3_model a1, unsigned a2, unsigned a3) { + R(); + P(a0); + P(a1); + U(a2); + U(a3); + C(283); +} +void log_Z3_get_model_func_entry_arg(Z3_context a0, Z3_model a1, unsigned a2, unsigned a3, unsigned a4) { + R(); + P(a0); + P(a1); + U(a2); + U(a3); + U(a4); + C(284); +} +void log_Z3_get_model_func_entry_value(Z3_context a0, Z3_model a1, unsigned a2, unsigned a3) { + R(); + P(a0); + P(a1); + U(a2); + U(a3); + C(285); +} +void log_Z3_eval(Z3_context a0, Z3_model a1, Z3_ast a2, Z3_ast* a3) { + R(); + P(a0); + P(a1); + P(a2); + P(0); + C(286); +} +void log_Z3_eval_decl(Z3_context a0, Z3_model a1, Z3_func_decl a2, unsigned a3, Z3_ast const * a4, Z3_ast* a5) { + R(); + P(a0); + P(a1); + P(a2); + U(a3); + for (unsigned i = 0; i < a3; i++) { P(a4[i]); } + Ap(a3); + P(0); + C(287); +} +void log_Z3_set_ast_print_mode(Z3_context a0, Z3_ast_print_mode a1) { + R(); + P(a0); + U(static_cast(a1)); + C(288); +} +void log_Z3_ast_to_string(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(289); +} +void log_Z3_pattern_to_string(Z3_context a0, Z3_pattern a1) { + R(); + P(a0); + P(a1); + C(290); +} +void log_Z3_sort_to_string(Z3_context a0, Z3_sort a1) { + R(); + P(a0); + P(a1); + C(291); +} +void log_Z3_func_decl_to_string(Z3_context a0, Z3_func_decl a1) { + R(); + P(a0); + P(a1); + C(292); +} +void log_Z3_model_to_string(Z3_context a0, Z3_model a1) { + R(); + P(a0); + P(a1); + C(293); +} +void log_Z3_benchmark_to_smtlib_string(Z3_context a0, Z3_string a1, Z3_string a2, Z3_string a3, Z3_string a4, unsigned a5, Z3_ast const * a6, Z3_ast a7) { + R(); + P(a0); + S(a1); + S(a2); + S(a3); + S(a4); + U(a5); + for (unsigned i = 0; i < a5; i++) { P(a6[i]); } + Ap(a5); + P(a7); + C(294); +} +void log_Z3_context_to_string(Z3_context a0) { + R(); + P(a0); + C(295); +} +void log_Z3_statistics_to_string(Z3_context a0) { + R(); + P(a0); + C(296); +} +void log_Z3_get_context_assignment(Z3_context a0) { + R(); + P(a0); + C(297); +} +void log_Z3_parse_smtlib_string(Z3_context a0, Z3_string a1, unsigned a2, Z3_symbol const * a3, Z3_sort const * a4, unsigned a5, Z3_symbol const * a6, Z3_func_decl const * a7) { + R(); + P(a0); + S(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { Sy(a3[i]); } + Asy(a2); + for (unsigned i = 0; i < a2; i++) { P(a4[i]); } + Ap(a2); + U(a5); + for (unsigned i = 0; i < a5; i++) { Sy(a6[i]); } + Asy(a5); + for (unsigned i = 0; i < a5; i++) { P(a7[i]); } + Ap(a5); + C(298); +} +void log_Z3_parse_smtlib_file(Z3_context a0, Z3_string a1, unsigned a2, Z3_symbol const * a3, Z3_sort const * a4, unsigned a5, Z3_symbol const * a6, Z3_func_decl const * a7) { + R(); + P(a0); + S(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { Sy(a3[i]); } + Asy(a2); + for (unsigned i = 0; i < a2; i++) { P(a4[i]); } + Ap(a2); + U(a5); + for (unsigned i = 0; i < a5; i++) { Sy(a6[i]); } + Asy(a5); + for (unsigned i = 0; i < a5; i++) { P(a7[i]); } + Ap(a5); + C(299); +} +void log_Z3_get_smtlib_num_formulas(Z3_context a0) { + R(); + P(a0); + C(300); +} +void log_Z3_get_smtlib_formula(Z3_context a0, unsigned a1) { + R(); + P(a0); + U(a1); + C(301); +} +void log_Z3_get_smtlib_num_assumptions(Z3_context a0) { + R(); + P(a0); + C(302); +} +void log_Z3_get_smtlib_assumption(Z3_context a0, unsigned a1) { + R(); + P(a0); + U(a1); + C(303); +} +void log_Z3_get_smtlib_num_decls(Z3_context a0) { + R(); + P(a0); + C(304); +} +void log_Z3_get_smtlib_decl(Z3_context a0, unsigned a1) { + R(); + P(a0); + U(a1); + C(305); +} +void log_Z3_get_smtlib_num_sorts(Z3_context a0) { + R(); + P(a0); + C(306); +} +void log_Z3_get_smtlib_sort(Z3_context a0, unsigned a1) { + R(); + P(a0); + U(a1); + C(307); +} +void log_Z3_get_smtlib_error(Z3_context a0) { + R(); + P(a0); + C(308); +} +void log_Z3_parse_z3_string(Z3_context a0, Z3_string a1) { + R(); + P(a0); + S(a1); + C(309); +} +void log_Z3_parse_z3_file(Z3_context a0, Z3_string a1) { + R(); + P(a0); + S(a1); + C(310); +} +void log_Z3_parse_smtlib2_string(Z3_context a0, Z3_string a1, unsigned a2, Z3_symbol const * a3, Z3_sort const * a4, unsigned a5, Z3_symbol const * a6, Z3_func_decl const * a7) { + R(); + P(a0); + S(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { Sy(a3[i]); } + Asy(a2); + for (unsigned i = 0; i < a2; i++) { P(a4[i]); } + Ap(a2); + U(a5); + for (unsigned i = 0; i < a5; i++) { Sy(a6[i]); } + Asy(a5); + for (unsigned i = 0; i < a5; i++) { P(a7[i]); } + Ap(a5); + C(311); +} +void log_Z3_parse_smtlib2_file(Z3_context a0, Z3_string a1, unsigned a2, Z3_symbol const * a3, Z3_sort const * a4, unsigned a5, Z3_symbol const * a6, Z3_func_decl const * a7) { + R(); + P(a0); + S(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { Sy(a3[i]); } + Asy(a2); + for (unsigned i = 0; i < a2; i++) { P(a4[i]); } + Ap(a2); + U(a5); + for (unsigned i = 0; i < a5; i++) { Sy(a6[i]); } + Asy(a5); + for (unsigned i = 0; i < a5; i++) { P(a7[i]); } + Ap(a5); + C(312); +} +void log_Z3_get_error_code(Z3_context a0) { + R(); + P(a0); + C(313); +} +void log_Z3_set_error(Z3_context a0, Z3_error_code a1) { + R(); + P(a0); + U(static_cast(a1)); + C(314); +} +void log_Z3_get_error_msg(Z3_error_code a0) { + R(); + U(static_cast(a0)); + C(315); +} +void log_Z3_get_version(unsigned* a0, unsigned* a1, unsigned* a2, unsigned* a3) { + R(); + U(0); + U(0); + U(0); + U(0); + C(316); +} +void log_Z3_reset_memory() { + R(); + C(317); +} +void log_Z3_is_app(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(318); +} +void log_Z3_is_numeral_ast(Z3_context a0, Z3_ast a1) { + R(); + P(a0); + P(a1); + C(319); +} +void log_Z3_get_arity(Z3_context a0, Z3_func_decl a1) { + R(); + P(a0); + P(a1); + C(320); +} +void log_Z3_mk_injective_function(Z3_context a0, Z3_symbol a1, unsigned a2, Z3_sort const * a3, Z3_sort a4) { + R(); + P(a0); + Sy(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { P(a3[i]); } + Ap(a2); + P(a4); + C(321); +} +void log_Z3_mk_fixedpoint(Z3_context a0) { + R(); + P(a0); + C(322); +} +void log_Z3_fixedpoint_inc_ref(Z3_context a0, Z3_fixedpoint a1) { + R(); + P(a0); + P(a1); + C(323); +} +void log_Z3_fixedpoint_dec_ref(Z3_context a0, Z3_fixedpoint a1) { + R(); + P(a0); + P(a1); + C(324); +} +void log_Z3_fixedpoint_push(Z3_context a0, Z3_fixedpoint a1) { + R(); + P(a0); + P(a1); + C(325); +} +void log_Z3_fixedpoint_pop(Z3_context a0, Z3_fixedpoint a1) { + R(); + P(a0); + P(a1); + C(326); +} +void log_Z3_fixedpoint_register_relation(Z3_context a0, Z3_fixedpoint a1, Z3_func_decl a2) { + R(); + P(a0); + P(a1); + P(a2); + C(327); +} +void log_Z3_fixedpoint_assert(Z3_context a0, Z3_fixedpoint a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(328); +} +void log_Z3_fixedpoint_add_rule(Z3_context a0, Z3_fixedpoint a1, Z3_ast a2, Z3_symbol a3) { + R(); + P(a0); + P(a1); + P(a2); + Sy(a3); + C(329); +} +void log_Z3_fixedpoint_add_fact(Z3_context a0, Z3_fixedpoint a1, Z3_func_decl a2, unsigned a3, unsigned const * a4) { + R(); + P(a0); + P(a1); + P(a2); + U(a3); + for (unsigned i = 0; i < a3; i++) { U(a4[i]); } + Au(a3); + C(330); +} +void log_Z3_fixedpoint_query(Z3_context a0, Z3_fixedpoint a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(331); +} +void log_Z3_fixedpoint_query_relations(Z3_context a0, Z3_fixedpoint a1, unsigned a2, Z3_func_decl const * a3) { + R(); + P(a0); + P(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { P(a3[i]); } + Ap(a2); + C(332); +} +void log_Z3_fixedpoint_get_answer(Z3_context a0, Z3_fixedpoint a1) { + R(); + P(a0); + P(a1); + C(333); +} +void log_Z3_fixedpoint_update_rule(Z3_context a0, Z3_fixedpoint a1, Z3_ast a2, Z3_symbol a3) { + R(); + P(a0); + P(a1); + P(a2); + Sy(a3); + C(334); +} +void log_Z3_fixedpoint_get_num_levels(Z3_context a0, Z3_fixedpoint a1, Z3_func_decl a2) { + R(); + P(a0); + P(a1); + P(a2); + C(335); +} +void log_Z3_fixedpoint_get_cover_delta(Z3_context a0, Z3_fixedpoint a1, int a2, Z3_func_decl a3) { + R(); + P(a0); + P(a1); + I(a2); + P(a3); + C(336); +} +void log_Z3_fixedpoint_add_cover(Z3_context a0, Z3_fixedpoint a1, int a2, Z3_func_decl a3, Z3_ast a4) { + R(); + P(a0); + P(a1); + I(a2); + P(a3); + P(a4); + C(337); +} +void log_Z3_fixedpoint_get_statistics(Z3_context a0, Z3_fixedpoint a1) { + R(); + P(a0); + P(a1); + C(338); +} +void log_Z3_fixedpoint_get_help(Z3_context a0, Z3_fixedpoint a1) { + R(); + P(a0); + P(a1); + C(339); +} +void log_Z3_fixedpoint_get_param_descrs(Z3_context a0, Z3_fixedpoint a1) { + R(); + P(a0); + P(a1); + C(340); +} +void log_Z3_fixedpoint_set_params(Z3_context a0, Z3_fixedpoint a1, Z3_params a2) { + R(); + P(a0); + P(a1); + P(a2); + C(341); +} +void log_Z3_fixedpoint_to_string(Z3_context a0, Z3_fixedpoint a1, unsigned a2, Z3_ast const * a3) { + R(); + P(a0); + P(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { P(a3[i]); } + Ap(a2); + C(342); +} +void log_Z3_fixedpoint_get_reason_unknown(Z3_context a0, Z3_fixedpoint a1) { + R(); + P(a0); + P(a1); + C(343); +} +void log_Z3_fixedpoint_set_predicate_representation(Z3_context a0, Z3_fixedpoint a1, Z3_func_decl a2, unsigned a3, Z3_symbol const * a4) { + R(); + P(a0); + P(a1); + P(a2); + U(a3); + for (unsigned i = 0; i < a3; i++) { Sy(a4[i]); } + Asy(a3); + C(344); +} +void log_Z3_fixedpoint_simplify_rules(Z3_context a0, Z3_fixedpoint a1, unsigned a2, Z3_ast const * a3, unsigned a4, Z3_func_decl const * a5) { + R(); + P(a0); + P(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { P(a3[i]); } + Ap(a2); + U(a4); + for (unsigned i = 0; i < a4; i++) { P(a5[i]); } + Ap(a4); + C(345); +} +void log_Z3_mk_params(Z3_context a0) { + R(); + P(a0); + C(346); +} +void log_Z3_params_inc_ref(Z3_context a0, Z3_params a1) { + R(); + P(a0); + P(a1); + C(347); +} +void log_Z3_params_dec_ref(Z3_context a0, Z3_params a1) { + R(); + P(a0); + P(a1); + C(348); +} +void log_Z3_params_set_bool(Z3_context a0, Z3_params a1, Z3_symbol a2, Z3_bool a3) { + R(); + P(a0); + P(a1); + Sy(a2); + I(a3); + C(349); +} +void log_Z3_params_set_uint(Z3_context a0, Z3_params a1, Z3_symbol a2, unsigned a3) { + R(); + P(a0); + P(a1); + Sy(a2); + U(a3); + C(350); +} +void log_Z3_params_set_double(Z3_context a0, Z3_params a1, Z3_symbol a2, double a3) { + R(); + P(a0); + P(a1); + Sy(a2); + D(a3); + C(351); +} +void log_Z3_params_set_symbol(Z3_context a0, Z3_params a1, Z3_symbol a2, Z3_symbol a3) { + R(); + P(a0); + P(a1); + Sy(a2); + Sy(a3); + C(352); +} +void log_Z3_params_to_string(Z3_context a0, Z3_params a1) { + R(); + P(a0); + P(a1); + C(353); +} +void log_Z3_params_validate(Z3_context a0, Z3_params a1, Z3_param_descrs a2) { + R(); + P(a0); + P(a1); + P(a2); + C(354); +} +void log_Z3_param_descrs_inc_ref(Z3_context a0, Z3_param_descrs a1) { + R(); + P(a0); + P(a1); + C(355); +} +void log_Z3_param_descrs_dec_ref(Z3_context a0, Z3_param_descrs a1) { + R(); + P(a0); + P(a1); + C(356); +} +void log_Z3_param_descrs_get_kind(Z3_context a0, Z3_param_descrs a1, Z3_symbol a2) { + R(); + P(a0); + P(a1); + Sy(a2); + C(357); +} +void log_Z3_param_descrs_size(Z3_context a0, Z3_param_descrs a1) { + R(); + P(a0); + P(a1); + C(358); +} +void log_Z3_param_descrs_get_name(Z3_context a0, Z3_param_descrs a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(359); +} +void log_Z3_interrupt(Z3_context a0) { + R(); + P(a0); + C(360); +} +void log_Z3_get_error_msg_ex(Z3_context a0, Z3_error_code a1) { + R(); + P(a0); + U(static_cast(a1)); + C(361); +} +void log_Z3_translate(Z3_context a0, Z3_ast a1, Z3_context a2) { + R(); + P(a0); + P(a1); + P(a2); + C(362); +} +void log_Z3_mk_goal(Z3_context a0, Z3_bool a1, Z3_bool a2, Z3_bool a3) { + R(); + P(a0); + I(a1); + I(a2); + I(a3); + C(363); +} +void log_Z3_goal_inc_ref(Z3_context a0, Z3_goal a1) { + R(); + P(a0); + P(a1); + C(364); +} +void log_Z3_goal_dec_ref(Z3_context a0, Z3_goal a1) { + R(); + P(a0); + P(a1); + C(365); +} +void log_Z3_goal_precision(Z3_context a0, Z3_goal a1) { + R(); + P(a0); + P(a1); + C(366); +} +void log_Z3_goal_assert(Z3_context a0, Z3_goal a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(367); +} +void log_Z3_goal_inconsistent(Z3_context a0, Z3_goal a1) { + R(); + P(a0); + P(a1); + C(368); +} +void log_Z3_goal_depth(Z3_context a0, Z3_goal a1) { + R(); + P(a0); + P(a1); + C(369); +} +void log_Z3_goal_reset(Z3_context a0, Z3_goal a1) { + R(); + P(a0); + P(a1); + C(370); +} +void log_Z3_goal_size(Z3_context a0, Z3_goal a1) { + R(); + P(a0); + P(a1); + C(371); +} +void log_Z3_goal_formula(Z3_context a0, Z3_goal a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(372); +} +void log_Z3_goal_num_exprs(Z3_context a0, Z3_goal a1) { + R(); + P(a0); + P(a1); + C(373); +} +void log_Z3_goal_is_decided_sat(Z3_context a0, Z3_goal a1) { + R(); + P(a0); + P(a1); + C(374); +} +void log_Z3_goal_is_decided_unsat(Z3_context a0, Z3_goal a1) { + R(); + P(a0); + P(a1); + C(375); +} +void log_Z3_goal_translate(Z3_context a0, Z3_goal a1, Z3_context a2) { + R(); + P(a0); + P(a1); + P(a2); + C(376); +} +void log_Z3_goal_to_string(Z3_context a0, Z3_goal a1) { + R(); + P(a0); + P(a1); + C(377); +} +void log_Z3_mk_tactic(Z3_context a0, Z3_string a1) { + R(); + P(a0); + S(a1); + C(378); +} +void log_Z3_mk_probe(Z3_context a0, Z3_string a1) { + R(); + P(a0); + S(a1); + C(379); +} +void log_Z3_tactic_inc_ref(Z3_context a0, Z3_tactic a1) { + R(); + P(a0); + P(a1); + C(380); +} +void log_Z3_tactic_dec_ref(Z3_context a0, Z3_tactic a1) { + R(); + P(a0); + P(a1); + C(381); +} +void log_Z3_probe_inc_ref(Z3_context a0, Z3_probe a1) { + R(); + P(a0); + P(a1); + C(382); +} +void log_Z3_probe_dec_ref(Z3_context a0, Z3_probe a1) { + R(); + P(a0); + P(a1); + C(383); +} +void log_Z3_tactic_and_then(Z3_context a0, Z3_tactic a1, Z3_tactic a2) { + R(); + P(a0); + P(a1); + P(a2); + C(384); +} +void log_Z3_tactic_or_else(Z3_context a0, Z3_tactic a1, Z3_tactic a2) { + R(); + P(a0); + P(a1); + P(a2); + C(385); +} +void log_Z3_tactic_par_or(Z3_context a0, unsigned a1, Z3_tactic const * a2) { + R(); + P(a0); + U(a1); + for (unsigned i = 0; i < a1; i++) { P(a2[i]); } + Ap(a1); + C(386); +} +void log_Z3_tactic_par_and_then(Z3_context a0, Z3_tactic a1, Z3_tactic a2) { + R(); + P(a0); + P(a1); + P(a2); + C(387); +} +void log_Z3_tactic_try_for(Z3_context a0, Z3_tactic a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(388); +} +void log_Z3_tactic_when(Z3_context a0, Z3_probe a1, Z3_tactic a2) { + R(); + P(a0); + P(a1); + P(a2); + C(389); +} +void log_Z3_tactic_cond(Z3_context a0, Z3_probe a1, Z3_tactic a2, Z3_tactic a3) { + R(); + P(a0); + P(a1); + P(a2); + P(a3); + C(390); +} +void log_Z3_tactic_repeat(Z3_context a0, Z3_tactic a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(391); +} +void log_Z3_tactic_skip(Z3_context a0) { + R(); + P(a0); + C(392); +} +void log_Z3_tactic_fail(Z3_context a0) { + R(); + P(a0); + C(393); +} +void log_Z3_tactic_fail_if(Z3_context a0, Z3_probe a1) { + R(); + P(a0); + P(a1); + C(394); +} +void log_Z3_tactic_fail_if_not_decided(Z3_context a0) { + R(); + P(a0); + C(395); +} +void log_Z3_tactic_using_params(Z3_context a0, Z3_tactic a1, Z3_params a2) { + R(); + P(a0); + P(a1); + P(a2); + C(396); +} +void log_Z3_probe_const(Z3_context a0, double a1) { + R(); + P(a0); + D(a1); + C(397); +} +void log_Z3_probe_lt(Z3_context a0, Z3_probe a1, Z3_probe a2) { + R(); + P(a0); + P(a1); + P(a2); + C(398); +} +void log_Z3_probe_le(Z3_context a0, Z3_probe a1, Z3_probe a2) { + R(); + P(a0); + P(a1); + P(a2); + C(399); +} +void log_Z3_probe_gt(Z3_context a0, Z3_probe a1, Z3_probe a2) { + R(); + P(a0); + P(a1); + P(a2); + C(400); +} +void log_Z3_probe_ge(Z3_context a0, Z3_probe a1, Z3_probe a2) { + R(); + P(a0); + P(a1); + P(a2); + C(401); +} +void log_Z3_probe_eq(Z3_context a0, Z3_probe a1, Z3_probe a2) { + R(); + P(a0); + P(a1); + P(a2); + C(402); +} +void log_Z3_probe_and(Z3_context a0, Z3_probe a1, Z3_probe a2) { + R(); + P(a0); + P(a1); + P(a2); + C(403); +} +void log_Z3_probe_or(Z3_context a0, Z3_probe a1, Z3_probe a2) { + R(); + P(a0); + P(a1); + P(a2); + C(404); +} +void log_Z3_probe_not(Z3_context a0, Z3_probe a1) { + R(); + P(a0); + P(a1); + C(405); +} +void log_Z3_get_num_tactics(Z3_context a0) { + R(); + P(a0); + C(406); +} +void log_Z3_get_tactic_name(Z3_context a0, unsigned a1) { + R(); + P(a0); + U(a1); + C(407); +} +void log_Z3_get_num_probes(Z3_context a0) { + R(); + P(a0); + C(408); +} +void log_Z3_get_probe_name(Z3_context a0, unsigned a1) { + R(); + P(a0); + U(a1); + C(409); +} +void log_Z3_tactic_get_help(Z3_context a0, Z3_tactic a1) { + R(); + P(a0); + P(a1); + C(410); +} +void log_Z3_tactic_get_param_descrs(Z3_context a0, Z3_tactic a1) { + R(); + P(a0); + P(a1); + C(411); +} +void log_Z3_tactic_get_descr(Z3_context a0, Z3_string a1) { + R(); + P(a0); + S(a1); + C(412); +} +void log_Z3_probe_get_descr(Z3_context a0, Z3_string a1) { + R(); + P(a0); + S(a1); + C(413); +} +void log_Z3_probe_apply(Z3_context a0, Z3_probe a1, Z3_goal a2) { + R(); + P(a0); + P(a1); + P(a2); + C(414); +} +void log_Z3_tactic_apply(Z3_context a0, Z3_tactic a1, Z3_goal a2) { + R(); + P(a0); + P(a1); + P(a2); + C(415); +} +void log_Z3_tactic_apply_ex(Z3_context a0, Z3_tactic a1, Z3_goal a2, Z3_params a3) { + R(); + P(a0); + P(a1); + P(a2); + P(a3); + C(416); +} +void log_Z3_apply_result_inc_ref(Z3_context a0, Z3_apply_result a1) { + R(); + P(a0); + P(a1); + C(417); +} +void log_Z3_apply_result_dec_ref(Z3_context a0, Z3_apply_result a1) { + R(); + P(a0); + P(a1); + C(418); +} +void log_Z3_apply_result_to_string(Z3_context a0, Z3_apply_result a1) { + R(); + P(a0); + P(a1); + C(419); +} +void log_Z3_apply_result_get_num_subgoals(Z3_context a0, Z3_apply_result a1) { + R(); + P(a0); + P(a1); + C(420); +} +void log_Z3_apply_result_get_subgoal(Z3_context a0, Z3_apply_result a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(421); +} +void log_Z3_apply_result_convert_model(Z3_context a0, Z3_apply_result a1, unsigned a2, Z3_model a3) { + R(); + P(a0); + P(a1); + U(a2); + P(a3); + C(422); +} +void log_Z3_mk_solver(Z3_context a0) { + R(); + P(a0); + C(423); +} +void log_Z3_mk_simple_solver(Z3_context a0) { + R(); + P(a0); + C(424); +} +void log_Z3_mk_solver_for_logic(Z3_context a0, Z3_symbol a1) { + R(); + P(a0); + Sy(a1); + C(425); +} +void log_Z3_mk_solver_from_tactic(Z3_context a0, Z3_tactic a1) { + R(); + P(a0); + P(a1); + C(426); +} +void log_Z3_solver_get_help(Z3_context a0, Z3_solver a1) { + R(); + P(a0); + P(a1); + C(427); +} +void log_Z3_solver_get_param_descrs(Z3_context a0, Z3_solver a1) { + R(); + P(a0); + P(a1); + C(428); +} +void log_Z3_solver_set_params(Z3_context a0, Z3_solver a1, Z3_params a2) { + R(); + P(a0); + P(a1); + P(a2); + C(429); +} +void log_Z3_solver_inc_ref(Z3_context a0, Z3_solver a1) { + R(); + P(a0); + P(a1); + C(430); +} +void log_Z3_solver_dec_ref(Z3_context a0, Z3_solver a1) { + R(); + P(a0); + P(a1); + C(431); +} +void log_Z3_solver_push(Z3_context a0, Z3_solver a1) { + R(); + P(a0); + P(a1); + C(432); +} +void log_Z3_solver_pop(Z3_context a0, Z3_solver a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(433); +} +void log_Z3_solver_reset(Z3_context a0, Z3_solver a1) { + R(); + P(a0); + P(a1); + C(434); +} +void log_Z3_solver_get_num_scopes(Z3_context a0, Z3_solver a1) { + R(); + P(a0); + P(a1); + C(435); +} +void log_Z3_solver_assert(Z3_context a0, Z3_solver a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(436); +} +void log_Z3_solver_get_assertions(Z3_context a0, Z3_solver a1) { + R(); + P(a0); + P(a1); + C(437); +} +void log_Z3_solver_check(Z3_context a0, Z3_solver a1) { + R(); + P(a0); + P(a1); + C(438); +} +void log_Z3_solver_check_assumptions(Z3_context a0, Z3_solver a1, unsigned a2, Z3_ast const * a3) { + R(); + P(a0); + P(a1); + U(a2); + for (unsigned i = 0; i < a2; i++) { P(a3[i]); } + Ap(a2); + C(439); +} +void log_Z3_solver_get_model(Z3_context a0, Z3_solver a1) { + R(); + P(a0); + P(a1); + C(440); +} +void log_Z3_solver_get_proof(Z3_context a0, Z3_solver a1) { + R(); + P(a0); + P(a1); + C(441); +} +void log_Z3_solver_get_unsat_core(Z3_context a0, Z3_solver a1) { + R(); + P(a0); + P(a1); + C(442); +} +void log_Z3_solver_get_reason_unknown(Z3_context a0, Z3_solver a1) { + R(); + P(a0); + P(a1); + C(443); +} +void log_Z3_solver_get_statistics(Z3_context a0, Z3_solver a1) { + R(); + P(a0); + P(a1); + C(444); +} +void log_Z3_solver_to_string(Z3_context a0, Z3_solver a1) { + R(); + P(a0); + P(a1); + C(445); +} +void log_Z3_stats_to_string(Z3_context a0, Z3_stats a1) { + R(); + P(a0); + P(a1); + C(446); +} +void log_Z3_stats_inc_ref(Z3_context a0, Z3_stats a1) { + R(); + P(a0); + P(a1); + C(447); +} +void log_Z3_stats_dec_ref(Z3_context a0, Z3_stats a1) { + R(); + P(a0); + P(a1); + C(448); +} +void log_Z3_stats_size(Z3_context a0, Z3_stats a1) { + R(); + P(a0); + P(a1); + C(449); +} +void log_Z3_stats_get_key(Z3_context a0, Z3_stats a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(450); +} +void log_Z3_stats_is_uint(Z3_context a0, Z3_stats a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(451); +} +void log_Z3_stats_is_double(Z3_context a0, Z3_stats a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(452); +} +void log_Z3_stats_get_uint_value(Z3_context a0, Z3_stats a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(453); +} +void log_Z3_stats_get_double_value(Z3_context a0, Z3_stats a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(454); +} +void log_Z3_mk_ast_vector(Z3_context a0) { + R(); + P(a0); + C(455); +} +void log_Z3_ast_vector_inc_ref(Z3_context a0, Z3_ast_vector a1) { + R(); + P(a0); + P(a1); + C(456); +} +void log_Z3_ast_vector_dec_ref(Z3_context a0, Z3_ast_vector a1) { + R(); + P(a0); + P(a1); + C(457); +} +void log_Z3_ast_vector_size(Z3_context a0, Z3_ast_vector a1) { + R(); + P(a0); + P(a1); + C(458); +} +void log_Z3_ast_vector_get(Z3_context a0, Z3_ast_vector a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(459); +} +void log_Z3_ast_vector_set(Z3_context a0, Z3_ast_vector a1, unsigned a2, Z3_ast a3) { + R(); + P(a0); + P(a1); + U(a2); + P(a3); + C(460); +} +void log_Z3_ast_vector_resize(Z3_context a0, Z3_ast_vector a1, unsigned a2) { + R(); + P(a0); + P(a1); + U(a2); + C(461); +} +void log_Z3_ast_vector_push(Z3_context a0, Z3_ast_vector a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(462); +} +void log_Z3_ast_vector_translate(Z3_context a0, Z3_ast_vector a1, Z3_context a2) { + R(); + P(a0); + P(a1); + P(a2); + C(463); +} +void log_Z3_ast_vector_to_string(Z3_context a0, Z3_ast_vector a1) { + R(); + P(a0); + P(a1); + C(464); +} +void log_Z3_mk_ast_map(Z3_context a0) { + R(); + P(a0); + C(465); +} +void log_Z3_ast_map_inc_ref(Z3_context a0, Z3_ast_map a1) { + R(); + P(a0); + P(a1); + C(466); +} +void log_Z3_ast_map_dec_ref(Z3_context a0, Z3_ast_map a1) { + R(); + P(a0); + P(a1); + C(467); +} +void log_Z3_ast_map_contains(Z3_context a0, Z3_ast_map a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(468); +} +void log_Z3_ast_map_find(Z3_context a0, Z3_ast_map a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(469); +} +void log_Z3_ast_map_insert(Z3_context a0, Z3_ast_map a1, Z3_ast a2, Z3_ast a3) { + R(); + P(a0); + P(a1); + P(a2); + P(a3); + C(470); +} +void log_Z3_ast_map_erase(Z3_context a0, Z3_ast_map a1, Z3_ast a2) { + R(); + P(a0); + P(a1); + P(a2); + C(471); +} +void log_Z3_ast_map_size(Z3_context a0, Z3_ast_map a1) { + R(); + P(a0); + P(a1); + C(472); +} +void log_Z3_ast_map_reset(Z3_context a0, Z3_ast_map a1) { + R(); + P(a0); + P(a1); + C(473); +} +void log_Z3_ast_map_keys(Z3_context a0, Z3_ast_map a1) { + R(); + P(a0); + P(a1); + C(474); +} +void log_Z3_ast_map_to_string(Z3_context a0, Z3_ast_map a1) { + R(); + P(a0); + P(a1); + C(475); +} diff --git a/lib/api_log_macros.h b/lib/api_log_macros.h new file mode 100644 index 000000000..d35003100 --- /dev/null +++ b/lib/api_log_macros.h @@ -0,0 +1,975 @@ +// Automatically generated file, generator: api.py +#include"z3.h" +extern std::ostream * g_z3_log; +extern bool g_z3_log_enabled; +class z3_log_ctx { bool m_prev; public: z3_log_ctx():m_prev(g_z3_log_enabled) { g_z3_log_enabled = false; } ~z3_log_ctx() { g_z3_log_enabled = m_prev; } bool enabled() const { return m_prev; } }; +inline void SetR(void * obj) { *g_z3_log << "= " << obj << "\n"; } +inline void SetO(void * obj, unsigned pos) { *g_z3_log << "* " << obj << " " << pos << "\n"; } +inline void SetAO(void * obj, unsigned pos, unsigned idx) { *g_z3_log << "@ " << obj << " " << pos << " " << idx << "\n"; } +#define RETURN_Z3(Z3RES) if (_LOG_CTX.enabled()) { SetR(Z3RES); } return Z3RES +void _Z3_append_log(char const * msg); +void log_Z3_mk_config(); +#define LOG_Z3_mk_config() z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_config(); } +void log_Z3_del_config(Z3_config a0); +#define LOG_Z3_del_config(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_del_config(_ARG0); } +void log_Z3_set_param_value(Z3_config a0, Z3_string a1, Z3_string a2); +#define LOG_Z3_set_param_value(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_set_param_value(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_context(Z3_config a0); +#define LOG_Z3_mk_context(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_context(_ARG0); } +void log_Z3_mk_context_rc(Z3_config a0); +#define LOG_Z3_mk_context_rc(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_context_rc(_ARG0); } +void log_Z3_set_logic(Z3_context a0, Z3_string a1); +#define LOG_Z3_set_logic(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_set_logic(_ARG0, _ARG1); } +void log_Z3_del_context(Z3_context a0); +#define LOG_Z3_del_context(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_del_context(_ARG0); } +void log_Z3_inc_ref(Z3_context a0, Z3_ast a1); +#define LOG_Z3_inc_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_inc_ref(_ARG0, _ARG1); } +void log_Z3_dec_ref(Z3_context a0, Z3_ast a1); +#define LOG_Z3_dec_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_dec_ref(_ARG0, _ARG1); } +void log_Z3_toggle_warning_messages(Z3_bool a0); +#define LOG_Z3_toggle_warning_messages(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_toggle_warning_messages(_ARG0); } +void log_Z3_update_param_value(Z3_context a0, Z3_string a1, Z3_string a2); +#define LOG_Z3_update_param_value(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_update_param_value(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_param_value(Z3_context a0, Z3_string a1, Z3_string* a2); +#define LOG_Z3_get_param_value(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_param_value(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_int_symbol(Z3_context a0, int a1); +#define LOG_Z3_mk_int_symbol(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_int_symbol(_ARG0, _ARG1); } +void log_Z3_mk_string_symbol(Z3_context a0, Z3_string a1); +#define LOG_Z3_mk_string_symbol(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_string_symbol(_ARG0, _ARG1); } +void log_Z3_is_eq_sort(Z3_context a0, Z3_sort a1, Z3_sort a2); +#define LOG_Z3_is_eq_sort(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_is_eq_sort(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_uninterpreted_sort(Z3_context a0, Z3_symbol a1); +#define LOG_Z3_mk_uninterpreted_sort(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_uninterpreted_sort(_ARG0, _ARG1); } +void log_Z3_mk_bool_sort(Z3_context a0); +#define LOG_Z3_mk_bool_sort(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bool_sort(_ARG0); } +void log_Z3_mk_int_sort(Z3_context a0); +#define LOG_Z3_mk_int_sort(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_int_sort(_ARG0); } +void log_Z3_mk_real_sort(Z3_context a0); +#define LOG_Z3_mk_real_sort(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_real_sort(_ARG0); } +void log_Z3_mk_bv_sort(Z3_context a0, unsigned a1); +#define LOG_Z3_mk_bv_sort(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bv_sort(_ARG0, _ARG1); } +void log_Z3_mk_array_sort(Z3_context a0, Z3_sort a1, Z3_sort a2); +#define LOG_Z3_mk_array_sort(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_array_sort(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_tuple_sort(Z3_context a0, Z3_symbol a1, unsigned a2, Z3_symbol const * a3, Z3_sort const * a4, Z3_func_decl* a5, Z3_func_decl* a6); +#define LOG_Z3_mk_tuple_sort(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6) z3_log_ctx _LOG_CTX; Z3_func_decl* Z3ARG5; unsigned Z3ARG2; Z3_func_decl* Z3ARG6; if (_LOG_CTX.enabled()) { log_Z3_mk_tuple_sort(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6); Z3ARG5 = _ARG5; Z3ARG2 = _ARG2; Z3ARG6 = _ARG6; } +#define RETURN_Z3_mk_tuple_sort(Z3RES) if (_LOG_CTX.enabled()) { SetR(Z3RES); SetO((Z3ARG5 == 0 ? 0 : *Z3ARG5), 5); for (unsigned i = 0; i < Z3ARG2; i++) { SetAO(Z3ARG6[i], 6, i); } } return Z3RES +void log_Z3_mk_enumeration_sort(Z3_context a0, Z3_symbol a1, unsigned a2, Z3_symbol const * a3, Z3_func_decl* a4, Z3_func_decl* a5); +#define LOG_Z3_mk_enumeration_sort(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5) z3_log_ctx _LOG_CTX; unsigned Z3ARG2; Z3_func_decl* Z3ARG4; Z3_func_decl* Z3ARG5; if (_LOG_CTX.enabled()) { log_Z3_mk_enumeration_sort(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5); Z3ARG2 = _ARG2; Z3ARG4 = _ARG4; Z3ARG5 = _ARG5; } +#define RETURN_Z3_mk_enumeration_sort(Z3RES) if (_LOG_CTX.enabled()) { SetR(Z3RES); for (unsigned i = 0; i < Z3ARG2; i++) { SetAO(Z3ARG4[i], 4, i); } for (unsigned i = 0; i < Z3ARG2; i++) { SetAO(Z3ARG5[i], 5, i); } } return Z3RES +void log_Z3_mk_list_sort(Z3_context a0, Z3_symbol a1, Z3_sort a2, Z3_func_decl* a3, Z3_func_decl* a4, Z3_func_decl* a5, Z3_func_decl* a6, Z3_func_decl* a7, Z3_func_decl* a8); +#define LOG_Z3_mk_list_sort(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7, _ARG8) z3_log_ctx _LOG_CTX; Z3_func_decl* Z3ARG3; Z3_func_decl* Z3ARG4; Z3_func_decl* Z3ARG5; Z3_func_decl* Z3ARG6; Z3_func_decl* Z3ARG7; Z3_func_decl* Z3ARG8; if (_LOG_CTX.enabled()) { log_Z3_mk_list_sort(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7, _ARG8); Z3ARG3 = _ARG3; Z3ARG4 = _ARG4; Z3ARG5 = _ARG5; Z3ARG6 = _ARG6; Z3ARG7 = _ARG7; Z3ARG8 = _ARG8; } +#define RETURN_Z3_mk_list_sort(Z3RES) if (_LOG_CTX.enabled()) { SetR(Z3RES); SetO((Z3ARG3 == 0 ? 0 : *Z3ARG3), 3); SetO((Z3ARG4 == 0 ? 0 : *Z3ARG4), 4); SetO((Z3ARG5 == 0 ? 0 : *Z3ARG5), 5); SetO((Z3ARG6 == 0 ? 0 : *Z3ARG6), 6); SetO((Z3ARG7 == 0 ? 0 : *Z3ARG7), 7); SetO((Z3ARG8 == 0 ? 0 : *Z3ARG8), 8); } return Z3RES +void log_Z3_mk_constructor(Z3_context a0, Z3_symbol a1, Z3_symbol a2, unsigned a3, Z3_symbol const * a4, Z3_sort const * a5, unsigned const * a6); +#define LOG_Z3_mk_constructor(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_constructor(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6); } +void log_Z3_query_constructor(Z3_context a0, Z3_constructor a1, unsigned a2, Z3_func_decl* a3, Z3_func_decl* a4, Z3_func_decl* a5); +#define LOG_Z3_query_constructor(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5) z3_log_ctx _LOG_CTX; Z3_func_decl* Z3ARG3; Z3_func_decl* Z3ARG4; unsigned Z3ARG2; Z3_func_decl* Z3ARG5; if (_LOG_CTX.enabled()) { log_Z3_query_constructor(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5); Z3ARG3 = _ARG3; Z3ARG4 = _ARG4; Z3ARG2 = _ARG2; Z3ARG5 = _ARG5; } +#define RETURN_Z3_query_constructor if (_LOG_CTX.enabled()) { SetO((Z3ARG3 == 0 ? 0 : *Z3ARG3), 3); SetO((Z3ARG4 == 0 ? 0 : *Z3ARG4), 4); for (unsigned i = 0; i < Z3ARG2; i++) { SetAO(Z3ARG5[i], 5, i); } } return +void log_Z3_del_constructor(Z3_context a0, Z3_constructor a1); +#define LOG_Z3_del_constructor(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_del_constructor(_ARG0, _ARG1); } +void log_Z3_mk_datatype(Z3_context a0, Z3_symbol a1, unsigned a2, Z3_constructor* a3); +#define LOG_Z3_mk_datatype(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; unsigned Z3ARG2; Z3_constructor* Z3ARG3; if (_LOG_CTX.enabled()) { log_Z3_mk_datatype(_ARG0, _ARG1, _ARG2, _ARG3); Z3ARG2 = _ARG2; Z3ARG3 = _ARG3; } +#define RETURN_Z3_mk_datatype(Z3RES) if (_LOG_CTX.enabled()) { SetR(Z3RES); for (unsigned i = 0; i < Z3ARG2; i++) { SetAO(Z3ARG3[i], 3, i); } } return Z3RES +void log_Z3_mk_constructor_list(Z3_context a0, unsigned a1, Z3_constructor const * a2); +#define LOG_Z3_mk_constructor_list(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_constructor_list(_ARG0, _ARG1, _ARG2); } +void log_Z3_del_constructor_list(Z3_context a0, Z3_constructor_list a1); +#define LOG_Z3_del_constructor_list(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_del_constructor_list(_ARG0, _ARG1); } +void log_Z3_mk_datatypes(Z3_context a0, unsigned a1, Z3_symbol const * a2, Z3_sort* a3, Z3_constructor_list* a4); +#define LOG_Z3_mk_datatypes(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4) z3_log_ctx _LOG_CTX; unsigned Z3ARG1; Z3_sort* Z3ARG3; Z3_constructor_list* Z3ARG4; if (_LOG_CTX.enabled()) { log_Z3_mk_datatypes(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4); Z3ARG1 = _ARG1; Z3ARG3 = _ARG3; Z3ARG4 = _ARG4; } +#define RETURN_Z3_mk_datatypes if (_LOG_CTX.enabled()) { for (unsigned i = 0; i < Z3ARG1; i++) { SetAO(Z3ARG3[i], 3, i); } for (unsigned i = 0; i < Z3ARG1; i++) { SetAO(Z3ARG4[i], 4, i); } } return +void log_Z3_is_eq_ast(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_is_eq_ast(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_is_eq_ast(_ARG0, _ARG1, _ARG2); } +void log_Z3_is_eq_func_decl(Z3_context a0, Z3_func_decl a1, Z3_func_decl a2); +#define LOG_Z3_is_eq_func_decl(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_is_eq_func_decl(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_func_decl(Z3_context a0, Z3_symbol a1, unsigned a2, Z3_sort const * a3, Z3_sort a4); +#define LOG_Z3_mk_func_decl(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_func_decl(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4); } +void log_Z3_mk_app(Z3_context a0, Z3_func_decl a1, unsigned a2, Z3_ast const * a3); +#define LOG_Z3_mk_app(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_app(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_mk_const(Z3_context a0, Z3_symbol a1, Z3_sort a2); +#define LOG_Z3_mk_const(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_const(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_label(Z3_context a0, Z3_symbol a1, Z3_bool a2, Z3_ast a3); +#define LOG_Z3_mk_label(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_label(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_mk_fresh_func_decl(Z3_context a0, Z3_string a1, unsigned a2, Z3_sort const * a3, Z3_sort a4); +#define LOG_Z3_mk_fresh_func_decl(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_fresh_func_decl(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4); } +void log_Z3_mk_fresh_const(Z3_context a0, Z3_string a1, Z3_sort a2); +#define LOG_Z3_mk_fresh_const(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_fresh_const(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_true(Z3_context a0); +#define LOG_Z3_mk_true(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_true(_ARG0); } +void log_Z3_mk_false(Z3_context a0); +#define LOG_Z3_mk_false(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_false(_ARG0); } +void log_Z3_mk_eq(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_eq(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_eq(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_distinct(Z3_context a0, unsigned a1, Z3_ast const * a2); +#define LOG_Z3_mk_distinct(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_distinct(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_not(Z3_context a0, Z3_ast a1); +#define LOG_Z3_mk_not(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_not(_ARG0, _ARG1); } +void log_Z3_mk_ite(Z3_context a0, Z3_ast a1, Z3_ast a2, Z3_ast a3); +#define LOG_Z3_mk_ite(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_ite(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_mk_iff(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_iff(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_iff(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_implies(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_implies(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_implies(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_xor(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_xor(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_xor(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_and(Z3_context a0, unsigned a1, Z3_ast const * a2); +#define LOG_Z3_mk_and(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_and(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_or(Z3_context a0, unsigned a1, Z3_ast const * a2); +#define LOG_Z3_mk_or(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_or(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_add(Z3_context a0, unsigned a1, Z3_ast const * a2); +#define LOG_Z3_mk_add(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_add(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_mul(Z3_context a0, unsigned a1, Z3_ast const * a2); +#define LOG_Z3_mk_mul(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_mul(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_sub(Z3_context a0, unsigned a1, Z3_ast const * a2); +#define LOG_Z3_mk_sub(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_sub(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_unary_minus(Z3_context a0, Z3_ast a1); +#define LOG_Z3_mk_unary_minus(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_unary_minus(_ARG0, _ARG1); } +void log_Z3_mk_div(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_div(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_div(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_mod(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_mod(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_mod(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_rem(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_rem(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_rem(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_power(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_power(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_power(_ARG0, _ARG1, _ARG2); } +void log_Z3_is_algebraic_number(Z3_context a0, Z3_ast a1); +#define LOG_Z3_is_algebraic_number(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_is_algebraic_number(_ARG0, _ARG1); } +void log_Z3_get_algebraic_number_lower(Z3_context a0, Z3_ast a1, unsigned a2); +#define LOG_Z3_get_algebraic_number_lower(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_algebraic_number_lower(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_algebraic_number_upper(Z3_context a0, Z3_ast a1, unsigned a2); +#define LOG_Z3_get_algebraic_number_upper(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_algebraic_number_upper(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_lt(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_lt(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_lt(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_le(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_le(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_le(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_gt(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_gt(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_gt(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_ge(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_ge(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_ge(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_int2real(Z3_context a0, Z3_ast a1); +#define LOG_Z3_mk_int2real(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_int2real(_ARG0, _ARG1); } +void log_Z3_mk_real2int(Z3_context a0, Z3_ast a1); +#define LOG_Z3_mk_real2int(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_real2int(_ARG0, _ARG1); } +void log_Z3_mk_is_int(Z3_context a0, Z3_ast a1); +#define LOG_Z3_mk_is_int(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_is_int(_ARG0, _ARG1); } +void log_Z3_mk_bvnot(Z3_context a0, Z3_ast a1); +#define LOG_Z3_mk_bvnot(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvnot(_ARG0, _ARG1); } +void log_Z3_mk_bvredand(Z3_context a0, Z3_ast a1); +#define LOG_Z3_mk_bvredand(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvredand(_ARG0, _ARG1); } +void log_Z3_mk_bvredor(Z3_context a0, Z3_ast a1); +#define LOG_Z3_mk_bvredor(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvredor(_ARG0, _ARG1); } +void log_Z3_mk_bvand(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvand(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvand(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvor(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvor(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvor(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvxor(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvxor(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvxor(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvnand(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvnand(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvnand(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvnor(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvnor(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvnor(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvxnor(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvxnor(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvxnor(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvneg(Z3_context a0, Z3_ast a1); +#define LOG_Z3_mk_bvneg(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvneg(_ARG0, _ARG1); } +void log_Z3_mk_bvadd(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvadd(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvadd(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvsub(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvsub(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvsub(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvmul(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvmul(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvmul(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvudiv(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvudiv(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvudiv(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvsdiv(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvsdiv(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvsdiv(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvurem(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvurem(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvurem(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvsrem(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvsrem(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvsrem(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvsmod(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvsmod(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvsmod(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvult(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvult(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvult(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvslt(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvslt(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvslt(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvule(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvule(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvule(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvsle(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvsle(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvsle(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvuge(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvuge(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvuge(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvsge(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvsge(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvsge(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvugt(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvugt(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvugt(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvsgt(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvsgt(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvsgt(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_concat(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_concat(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_concat(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_extract(Z3_context a0, unsigned a1, unsigned a2, Z3_ast a3); +#define LOG_Z3_mk_extract(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_extract(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_mk_sign_ext(Z3_context a0, unsigned a1, Z3_ast a2); +#define LOG_Z3_mk_sign_ext(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_sign_ext(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_zero_ext(Z3_context a0, unsigned a1, Z3_ast a2); +#define LOG_Z3_mk_zero_ext(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_zero_ext(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_repeat(Z3_context a0, unsigned a1, Z3_ast a2); +#define LOG_Z3_mk_repeat(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_repeat(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvshl(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvshl(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvshl(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvlshr(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvlshr(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvlshr(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvashr(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvashr(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvashr(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_rotate_left(Z3_context a0, unsigned a1, Z3_ast a2); +#define LOG_Z3_mk_rotate_left(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_rotate_left(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_rotate_right(Z3_context a0, unsigned a1, Z3_ast a2); +#define LOG_Z3_mk_rotate_right(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_rotate_right(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_ext_rotate_left(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_ext_rotate_left(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_ext_rotate_left(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_ext_rotate_right(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_ext_rotate_right(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_ext_rotate_right(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_int2bv(Z3_context a0, unsigned a1, Z3_ast a2); +#define LOG_Z3_mk_int2bv(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_int2bv(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bv2int(Z3_context a0, Z3_ast a1, Z3_bool a2); +#define LOG_Z3_mk_bv2int(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bv2int(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvadd_no_overflow(Z3_context a0, Z3_ast a1, Z3_ast a2, Z3_bool a3); +#define LOG_Z3_mk_bvadd_no_overflow(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvadd_no_overflow(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_mk_bvadd_no_underflow(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvadd_no_underflow(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvadd_no_underflow(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvsub_no_overflow(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvsub_no_overflow(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvsub_no_overflow(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvsub_no_underflow(Z3_context a0, Z3_ast a1, Z3_ast a2, Z3_bool a3); +#define LOG_Z3_mk_bvsub_no_underflow(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvsub_no_underflow(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_mk_bvsdiv_no_overflow(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvsdiv_no_overflow(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvsdiv_no_overflow(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bvneg_no_overflow(Z3_context a0, Z3_ast a1); +#define LOG_Z3_mk_bvneg_no_overflow(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvneg_no_overflow(_ARG0, _ARG1); } +void log_Z3_mk_bvmul_no_overflow(Z3_context a0, Z3_ast a1, Z3_ast a2, Z3_bool a3); +#define LOG_Z3_mk_bvmul_no_overflow(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvmul_no_overflow(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_mk_bvmul_no_underflow(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_bvmul_no_underflow(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bvmul_no_underflow(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_select(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_select(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_select(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_store(Z3_context a0, Z3_ast a1, Z3_ast a2, Z3_ast a3); +#define LOG_Z3_mk_store(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_store(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_mk_const_array(Z3_context a0, Z3_sort a1, Z3_ast a2); +#define LOG_Z3_mk_const_array(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_const_array(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_map(Z3_context a0, Z3_func_decl a1, unsigned a2, Z3_ast const * a3); +#define LOG_Z3_mk_map(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_map(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_mk_array_default(Z3_context a0, Z3_ast a1); +#define LOG_Z3_mk_array_default(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_array_default(_ARG0, _ARG1); } +void log_Z3_mk_set_sort(Z3_context a0, Z3_sort a1); +#define LOG_Z3_mk_set_sort(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_set_sort(_ARG0, _ARG1); } +void log_Z3_mk_empty_set(Z3_context a0, Z3_sort a1); +#define LOG_Z3_mk_empty_set(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_empty_set(_ARG0, _ARG1); } +void log_Z3_mk_full_set(Z3_context a0, Z3_sort a1); +#define LOG_Z3_mk_full_set(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_full_set(_ARG0, _ARG1); } +void log_Z3_mk_set_add(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_set_add(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_set_add(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_set_del(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_set_del(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_set_del(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_set_union(Z3_context a0, unsigned a1, Z3_ast const * a2); +#define LOG_Z3_mk_set_union(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_set_union(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_set_intersect(Z3_context a0, unsigned a1, Z3_ast const * a2); +#define LOG_Z3_mk_set_intersect(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_set_intersect(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_set_difference(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_set_difference(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_set_difference(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_set_complement(Z3_context a0, Z3_ast a1); +#define LOG_Z3_mk_set_complement(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_set_complement(_ARG0, _ARG1); } +void log_Z3_mk_set_member(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_set_member(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_set_member(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_set_subset(Z3_context a0, Z3_ast a1, Z3_ast a2); +#define LOG_Z3_mk_set_subset(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_set_subset(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_numeral(Z3_context a0, Z3_string a1, Z3_sort a2); +#define LOG_Z3_mk_numeral(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_numeral(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_real(Z3_context a0, int a1, int a2); +#define LOG_Z3_mk_real(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_real(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_int(Z3_context a0, int a1, Z3_sort a2); +#define LOG_Z3_mk_int(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_int(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_unsigned_int(Z3_context a0, unsigned a1, Z3_sort a2); +#define LOG_Z3_mk_unsigned_int(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_unsigned_int(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_int64(Z3_context a0, __int64 a1, Z3_sort a2); +#define LOG_Z3_mk_int64(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_int64(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_unsigned_int64(Z3_context a0, __uint64 a1, Z3_sort a2); +#define LOG_Z3_mk_unsigned_int64(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_unsigned_int64(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_pattern(Z3_context a0, unsigned a1, Z3_ast const * a2); +#define LOG_Z3_mk_pattern(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_pattern(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_bound(Z3_context a0, unsigned a1, Z3_sort a2); +#define LOG_Z3_mk_bound(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_bound(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_forall(Z3_context a0, unsigned a1, unsigned a2, Z3_pattern const * a3, unsigned a4, Z3_sort const * a5, Z3_symbol const * a6, Z3_ast a7); +#define LOG_Z3_mk_forall(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_forall(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7); } +void log_Z3_mk_exists(Z3_context a0, unsigned a1, unsigned a2, Z3_pattern const * a3, unsigned a4, Z3_sort const * a5, Z3_symbol const * a6, Z3_ast a7); +#define LOG_Z3_mk_exists(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_exists(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7); } +void log_Z3_mk_quantifier(Z3_context a0, Z3_bool a1, unsigned a2, unsigned a3, Z3_pattern const * a4, unsigned a5, Z3_sort const * a6, Z3_symbol const * a7, Z3_ast a8); +#define LOG_Z3_mk_quantifier(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7, _ARG8) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_quantifier(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7, _ARG8); } +void log_Z3_mk_quantifier_ex(Z3_context a0, Z3_bool a1, unsigned a2, Z3_symbol a3, Z3_symbol a4, unsigned a5, Z3_pattern const * a6, unsigned a7, Z3_ast const * a8, unsigned a9, Z3_sort const * a10, Z3_symbol const * a11, Z3_ast a12); +#define LOG_Z3_mk_quantifier_ex(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7, _ARG8, _ARG9, _ARG10, _ARG11, _ARG12) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_quantifier_ex(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7, _ARG8, _ARG9, _ARG10, _ARG11, _ARG12); } +void log_Z3_mk_forall_const(Z3_context a0, unsigned a1, unsigned a2, Z3_app const * a3, unsigned a4, Z3_pattern const * a5, Z3_ast a6); +#define LOG_Z3_mk_forall_const(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_forall_const(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6); } +void log_Z3_mk_exists_const(Z3_context a0, unsigned a1, unsigned a2, Z3_app const * a3, unsigned a4, Z3_pattern const * a5, Z3_ast a6); +#define LOG_Z3_mk_exists_const(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_exists_const(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6); } +void log_Z3_mk_quantifier_const(Z3_context a0, Z3_bool a1, unsigned a2, unsigned a3, Z3_app const * a4, unsigned a5, Z3_pattern const * a6, Z3_ast a7); +#define LOG_Z3_mk_quantifier_const(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_quantifier_const(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7); } +void log_Z3_mk_quantifier_const_ex(Z3_context a0, Z3_bool a1, unsigned a2, Z3_symbol a3, Z3_symbol a4, unsigned a5, Z3_app const * a6, unsigned a7, Z3_pattern const * a8, unsigned a9, Z3_ast const * a10, Z3_ast a11); +#define LOG_Z3_mk_quantifier_const_ex(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7, _ARG8, _ARG9, _ARG10, _ARG11) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_quantifier_const_ex(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7, _ARG8, _ARG9, _ARG10, _ARG11); } +void log_Z3_get_ast_id(Z3_context a0, Z3_ast a1); +#define LOG_Z3_get_ast_id(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_ast_id(_ARG0, _ARG1); } +void log_Z3_get_func_decl_id(Z3_context a0, Z3_func_decl a1); +#define LOG_Z3_get_func_decl_id(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_func_decl_id(_ARG0, _ARG1); } +void log_Z3_get_sort_id(Z3_context a0, Z3_sort a1); +#define LOG_Z3_get_sort_id(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_sort_id(_ARG0, _ARG1); } +void log_Z3_is_well_sorted(Z3_context a0, Z3_ast a1); +#define LOG_Z3_is_well_sorted(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_is_well_sorted(_ARG0, _ARG1); } +void log_Z3_get_symbol_kind(Z3_context a0, Z3_symbol a1); +#define LOG_Z3_get_symbol_kind(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_symbol_kind(_ARG0, _ARG1); } +void log_Z3_get_symbol_int(Z3_context a0, Z3_symbol a1); +#define LOG_Z3_get_symbol_int(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_symbol_int(_ARG0, _ARG1); } +void log_Z3_get_symbol_string(Z3_context a0, Z3_symbol a1); +#define LOG_Z3_get_symbol_string(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_symbol_string(_ARG0, _ARG1); } +void log_Z3_get_ast_kind(Z3_context a0, Z3_ast a1); +#define LOG_Z3_get_ast_kind(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_ast_kind(_ARG0, _ARG1); } +void log_Z3_get_ast_hash(Z3_context a0, Z3_ast a1); +#define LOG_Z3_get_ast_hash(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_ast_hash(_ARG0, _ARG1); } +void log_Z3_get_numeral_string(Z3_context a0, Z3_ast a1); +#define LOG_Z3_get_numeral_string(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_numeral_string(_ARG0, _ARG1); } +void log_Z3_get_numeral_decimal_string(Z3_context a0, Z3_ast a1, unsigned a2); +#define LOG_Z3_get_numeral_decimal_string(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_numeral_decimal_string(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_numerator(Z3_context a0, Z3_ast a1); +#define LOG_Z3_get_numerator(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_numerator(_ARG0, _ARG1); } +void log_Z3_get_denominator(Z3_context a0, Z3_ast a1); +#define LOG_Z3_get_denominator(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_denominator(_ARG0, _ARG1); } +void log_Z3_get_numeral_small(Z3_context a0, Z3_ast a1, __int64* a2, __int64* a3); +#define LOG_Z3_get_numeral_small(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_numeral_small(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_get_numeral_int(Z3_context a0, Z3_ast a1, int* a2); +#define LOG_Z3_get_numeral_int(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_numeral_int(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_numeral_uint(Z3_context a0, Z3_ast a1, unsigned* a2); +#define LOG_Z3_get_numeral_uint(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_numeral_uint(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_numeral_uint64(Z3_context a0, Z3_ast a1, __uint64* a2); +#define LOG_Z3_get_numeral_uint64(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_numeral_uint64(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_numeral_int64(Z3_context a0, Z3_ast a1, __int64* a2); +#define LOG_Z3_get_numeral_int64(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_numeral_int64(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_numeral_rational_int64(Z3_context a0, Z3_ast a1, __int64* a2, __int64* a3); +#define LOG_Z3_get_numeral_rational_int64(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_numeral_rational_int64(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_get_bool_value(Z3_context a0, Z3_ast a1); +#define LOG_Z3_get_bool_value(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_bool_value(_ARG0, _ARG1); } +void log_Z3_get_app_decl(Z3_context a0, Z3_app a1); +#define LOG_Z3_get_app_decl(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_app_decl(_ARG0, _ARG1); } +void log_Z3_get_app_num_args(Z3_context a0, Z3_app a1); +#define LOG_Z3_get_app_num_args(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_app_num_args(_ARG0, _ARG1); } +void log_Z3_get_app_arg(Z3_context a0, Z3_app a1, unsigned a2); +#define LOG_Z3_get_app_arg(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_app_arg(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_index_value(Z3_context a0, Z3_ast a1); +#define LOG_Z3_get_index_value(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_index_value(_ARG0, _ARG1); } +void log_Z3_is_quantifier_forall(Z3_context a0, Z3_ast a1); +#define LOG_Z3_is_quantifier_forall(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_is_quantifier_forall(_ARG0, _ARG1); } +void log_Z3_get_quantifier_weight(Z3_context a0, Z3_ast a1); +#define LOG_Z3_get_quantifier_weight(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_quantifier_weight(_ARG0, _ARG1); } +void log_Z3_get_quantifier_num_patterns(Z3_context a0, Z3_ast a1); +#define LOG_Z3_get_quantifier_num_patterns(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_quantifier_num_patterns(_ARG0, _ARG1); } +void log_Z3_get_quantifier_pattern_ast(Z3_context a0, Z3_ast a1, unsigned a2); +#define LOG_Z3_get_quantifier_pattern_ast(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_quantifier_pattern_ast(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_quantifier_num_no_patterns(Z3_context a0, Z3_ast a1); +#define LOG_Z3_get_quantifier_num_no_patterns(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_quantifier_num_no_patterns(_ARG0, _ARG1); } +void log_Z3_get_quantifier_no_pattern_ast(Z3_context a0, Z3_ast a1, unsigned a2); +#define LOG_Z3_get_quantifier_no_pattern_ast(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_quantifier_no_pattern_ast(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_quantifier_bound_name(Z3_context a0, Z3_ast a1, unsigned a2); +#define LOG_Z3_get_quantifier_bound_name(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_quantifier_bound_name(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_quantifier_bound_sort(Z3_context a0, Z3_ast a1, unsigned a2); +#define LOG_Z3_get_quantifier_bound_sort(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_quantifier_bound_sort(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_quantifier_body(Z3_context a0, Z3_ast a1); +#define LOG_Z3_get_quantifier_body(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_quantifier_body(_ARG0, _ARG1); } +void log_Z3_get_quantifier_num_bound(Z3_context a0, Z3_ast a1); +#define LOG_Z3_get_quantifier_num_bound(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_quantifier_num_bound(_ARG0, _ARG1); } +void log_Z3_get_decl_name(Z3_context a0, Z3_func_decl a1); +#define LOG_Z3_get_decl_name(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_decl_name(_ARG0, _ARG1); } +void log_Z3_get_decl_num_parameters(Z3_context a0, Z3_func_decl a1); +#define LOG_Z3_get_decl_num_parameters(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_decl_num_parameters(_ARG0, _ARG1); } +void log_Z3_get_decl_parameter_kind(Z3_context a0, Z3_func_decl a1, unsigned a2); +#define LOG_Z3_get_decl_parameter_kind(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_decl_parameter_kind(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_decl_int_parameter(Z3_context a0, Z3_func_decl a1, unsigned a2); +#define LOG_Z3_get_decl_int_parameter(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_decl_int_parameter(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_decl_double_parameter(Z3_context a0, Z3_func_decl a1, unsigned a2); +#define LOG_Z3_get_decl_double_parameter(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_decl_double_parameter(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_decl_symbol_parameter(Z3_context a0, Z3_func_decl a1, unsigned a2); +#define LOG_Z3_get_decl_symbol_parameter(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_decl_symbol_parameter(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_decl_sort_parameter(Z3_context a0, Z3_func_decl a1, unsigned a2); +#define LOG_Z3_get_decl_sort_parameter(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_decl_sort_parameter(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_decl_ast_parameter(Z3_context a0, Z3_func_decl a1, unsigned a2); +#define LOG_Z3_get_decl_ast_parameter(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_decl_ast_parameter(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_decl_func_decl_parameter(Z3_context a0, Z3_func_decl a1, unsigned a2); +#define LOG_Z3_get_decl_func_decl_parameter(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_decl_func_decl_parameter(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_decl_rational_parameter(Z3_context a0, Z3_func_decl a1, unsigned a2); +#define LOG_Z3_get_decl_rational_parameter(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_decl_rational_parameter(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_sort_name(Z3_context a0, Z3_sort a1); +#define LOG_Z3_get_sort_name(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_sort_name(_ARG0, _ARG1); } +void log_Z3_get_sort(Z3_context a0, Z3_ast a1); +#define LOG_Z3_get_sort(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_sort(_ARG0, _ARG1); } +void log_Z3_get_domain_size(Z3_context a0, Z3_func_decl a1); +#define LOG_Z3_get_domain_size(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_domain_size(_ARG0, _ARG1); } +void log_Z3_get_domain(Z3_context a0, Z3_func_decl a1, unsigned a2); +#define LOG_Z3_get_domain(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_domain(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_range(Z3_context a0, Z3_func_decl a1); +#define LOG_Z3_get_range(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_range(_ARG0, _ARG1); } +void log_Z3_get_sort_kind(Z3_context a0, Z3_sort a1); +#define LOG_Z3_get_sort_kind(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_sort_kind(_ARG0, _ARG1); } +void log_Z3_get_bv_sort_size(Z3_context a0, Z3_sort a1); +#define LOG_Z3_get_bv_sort_size(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_bv_sort_size(_ARG0, _ARG1); } +void log_Z3_get_array_sort_domain(Z3_context a0, Z3_sort a1); +#define LOG_Z3_get_array_sort_domain(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_array_sort_domain(_ARG0, _ARG1); } +void log_Z3_get_array_sort_range(Z3_context a0, Z3_sort a1); +#define LOG_Z3_get_array_sort_range(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_array_sort_range(_ARG0, _ARG1); } +void log_Z3_get_tuple_sort_mk_decl(Z3_context a0, Z3_sort a1); +#define LOG_Z3_get_tuple_sort_mk_decl(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_tuple_sort_mk_decl(_ARG0, _ARG1); } +void log_Z3_get_decl_kind(Z3_context a0, Z3_func_decl a1); +#define LOG_Z3_get_decl_kind(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_decl_kind(_ARG0, _ARG1); } +void log_Z3_get_tuple_sort_num_fields(Z3_context a0, Z3_sort a1); +#define LOG_Z3_get_tuple_sort_num_fields(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_tuple_sort_num_fields(_ARG0, _ARG1); } +void log_Z3_get_tuple_sort_field_decl(Z3_context a0, Z3_sort a1, unsigned a2); +#define LOG_Z3_get_tuple_sort_field_decl(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_tuple_sort_field_decl(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_datatype_sort_num_constructors(Z3_context a0, Z3_sort a1); +#define LOG_Z3_get_datatype_sort_num_constructors(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_datatype_sort_num_constructors(_ARG0, _ARG1); } +void log_Z3_get_datatype_sort_constructor(Z3_context a0, Z3_sort a1, unsigned a2); +#define LOG_Z3_get_datatype_sort_constructor(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_datatype_sort_constructor(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_datatype_sort_recognizer(Z3_context a0, Z3_sort a1, unsigned a2); +#define LOG_Z3_get_datatype_sort_recognizer(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_datatype_sort_recognizer(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_datatype_sort_constructor_accessor(Z3_context a0, Z3_sort a1, unsigned a2, unsigned a3); +#define LOG_Z3_get_datatype_sort_constructor_accessor(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_datatype_sort_constructor_accessor(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_get_relation_arity(Z3_context a0, Z3_sort a1); +#define LOG_Z3_get_relation_arity(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_relation_arity(_ARG0, _ARG1); } +void log_Z3_get_relation_column(Z3_context a0, Z3_sort a1, unsigned a2); +#define LOG_Z3_get_relation_column(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_relation_column(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_finite_domain_sort_size(Z3_context a0, Z3_sort a1, __uint64* a2); +#define LOG_Z3_get_finite_domain_sort_size(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_finite_domain_sort_size(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_finite_domain_sort(Z3_context a0, Z3_symbol a1, __uint64 a2); +#define LOG_Z3_mk_finite_domain_sort(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_finite_domain_sort(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_pattern_num_terms(Z3_context a0, Z3_pattern a1); +#define LOG_Z3_get_pattern_num_terms(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_pattern_num_terms(_ARG0, _ARG1); } +void log_Z3_get_pattern(Z3_context a0, Z3_pattern a1, unsigned a2); +#define LOG_Z3_get_pattern(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_pattern(_ARG0, _ARG1, _ARG2); } +void log_Z3_simplify(Z3_context a0, Z3_ast a1); +#define LOG_Z3_simplify(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_simplify(_ARG0, _ARG1); } +void log_Z3_simplify_ex(Z3_context a0, Z3_ast a1, Z3_params a2); +#define LOG_Z3_simplify_ex(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_simplify_ex(_ARG0, _ARG1, _ARG2); } +void log_Z3_simplify_get_help(Z3_context a0); +#define LOG_Z3_simplify_get_help(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_simplify_get_help(_ARG0); } +void log_Z3_simplify_get_param_descrs(Z3_context a0); +#define LOG_Z3_simplify_get_param_descrs(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_simplify_get_param_descrs(_ARG0); } +void log_Z3_update_term(Z3_context a0, Z3_ast a1, unsigned a2, Z3_ast const * a3); +#define LOG_Z3_update_term(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_update_term(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_substitute(Z3_context a0, Z3_ast a1, unsigned a2, Z3_ast const * a3, Z3_ast const * a4); +#define LOG_Z3_substitute(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_substitute(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4); } +void log_Z3_substitute_vars(Z3_context a0, Z3_ast a1, unsigned a2, Z3_ast const * a3); +#define LOG_Z3_substitute_vars(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_substitute_vars(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_sort_to_ast(Z3_context a0, Z3_sort a1); +#define LOG_Z3_sort_to_ast(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_sort_to_ast(_ARG0, _ARG1); } +void log_Z3_func_decl_to_ast(Z3_context a0, Z3_func_decl a1); +#define LOG_Z3_func_decl_to_ast(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_func_decl_to_ast(_ARG0, _ARG1); } +void log_Z3_pattern_to_ast(Z3_context a0, Z3_pattern a1); +#define LOG_Z3_pattern_to_ast(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_pattern_to_ast(_ARG0, _ARG1); } +void log_Z3_app_to_ast(Z3_context a0, Z3_app a1); +#define LOG_Z3_app_to_ast(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_app_to_ast(_ARG0, _ARG1); } +void log_Z3_to_app(Z3_context a0, Z3_ast a1); +#define LOG_Z3_to_app(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_to_app(_ARG0, _ARG1); } +void log_Z3_to_func_decl(Z3_context a0, Z3_ast a1); +#define LOG_Z3_to_func_decl(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_to_func_decl(_ARG0, _ARG1); } +void log_Z3_push(Z3_context a0); +#define LOG_Z3_push(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_push(_ARG0); } +void log_Z3_pop(Z3_context a0, unsigned a1); +#define LOG_Z3_pop(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_pop(_ARG0, _ARG1); } +void log_Z3_get_num_scopes(Z3_context a0); +#define LOG_Z3_get_num_scopes(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_num_scopes(_ARG0); } +void log_Z3_persist_ast(Z3_context a0, Z3_ast a1, unsigned a2); +#define LOG_Z3_persist_ast(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_persist_ast(_ARG0, _ARG1, _ARG2); } +void log_Z3_assert_cnstr(Z3_context a0, Z3_ast a1); +#define LOG_Z3_assert_cnstr(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_assert_cnstr(_ARG0, _ARG1); } +void log_Z3_check_and_get_model(Z3_context a0, Z3_model* a1); +#define LOG_Z3_check_and_get_model(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; Z3_model* Z3ARG1; if (_LOG_CTX.enabled()) { log_Z3_check_and_get_model(_ARG0, _ARG1); Z3ARG1 = _ARG1; } +#define RETURN_Z3_check_and_get_model if (_LOG_CTX.enabled()) { SetO((Z3ARG1 == 0 ? 0 : *Z3ARG1), 1); } return +void log_Z3_check(Z3_context a0); +#define LOG_Z3_check(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_check(_ARG0); } +void log_Z3_check_assumptions(Z3_context a0, unsigned a1, Z3_ast const * a2, Z3_model* a3, Z3_ast* a4, unsigned* a5, Z3_ast* a6); +#define LOG_Z3_check_assumptions(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6) z3_log_ctx _LOG_CTX; Z3_model* Z3ARG3; Z3_ast* Z3ARG4; unsigned Z3ARG1; unsigned * Z3ARG5; Z3_ast* Z3ARG6; if (_LOG_CTX.enabled()) { log_Z3_check_assumptions(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6); Z3ARG3 = _ARG3; Z3ARG4 = _ARG4; Z3ARG1 = _ARG1; Z3ARG5 = _ARG5; Z3ARG6 = _ARG6; } +#define RETURN_Z3_check_assumptions if (_LOG_CTX.enabled()) { SetO((Z3ARG3 == 0 ? 0 : *Z3ARG3), 3); SetO((Z3ARG4 == 0 ? 0 : *Z3ARG4), 4); for (unsigned i = 0; Z3ARG5 && i < *Z3ARG5; i++) { SetAO(Z3ARG6[i], 6, i); } } return +void log_Z3_get_implied_equalities(Z3_context a0, unsigned a1, Z3_ast const * a2, unsigned* a3); +#define LOG_Z3_get_implied_equalities(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_implied_equalities(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_del_model(Z3_context a0, Z3_model a1); +#define LOG_Z3_del_model(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_del_model(_ARG0, _ARG1); } +void log_Z3_soft_check_cancel(Z3_context a0); +#define LOG_Z3_soft_check_cancel(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_soft_check_cancel(_ARG0); } +void log_Z3_get_search_failure(Z3_context a0); +#define LOG_Z3_get_search_failure(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_search_failure(_ARG0); } +void log_Z3_get_relevant_labels(Z3_context a0); +#define LOG_Z3_get_relevant_labels(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_relevant_labels(_ARG0); } +void log_Z3_get_relevant_literals(Z3_context a0); +#define LOG_Z3_get_relevant_literals(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_relevant_literals(_ARG0); } +void log_Z3_get_guessed_literals(Z3_context a0); +#define LOG_Z3_get_guessed_literals(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_guessed_literals(_ARG0); } +void log_Z3_del_literals(Z3_context a0, Z3_literals a1); +#define LOG_Z3_del_literals(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_del_literals(_ARG0, _ARG1); } +void log_Z3_get_num_literals(Z3_context a0, Z3_literals a1); +#define LOG_Z3_get_num_literals(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_num_literals(_ARG0, _ARG1); } +void log_Z3_get_label_symbol(Z3_context a0, Z3_literals a1, unsigned a2); +#define LOG_Z3_get_label_symbol(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_label_symbol(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_literal(Z3_context a0, Z3_literals a1, unsigned a2); +#define LOG_Z3_get_literal(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_literal(_ARG0, _ARG1, _ARG2); } +void log_Z3_disable_literal(Z3_context a0, Z3_literals a1, unsigned a2); +#define LOG_Z3_disable_literal(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_disable_literal(_ARG0, _ARG1, _ARG2); } +void log_Z3_block_literals(Z3_context a0, Z3_literals a1); +#define LOG_Z3_block_literals(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_block_literals(_ARG0, _ARG1); } +void log_Z3_model_inc_ref(Z3_context a0, Z3_model a1); +#define LOG_Z3_model_inc_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_model_inc_ref(_ARG0, _ARG1); } +void log_Z3_model_dec_ref(Z3_context a0, Z3_model a1); +#define LOG_Z3_model_dec_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_model_dec_ref(_ARG0, _ARG1); } +void log_Z3_model_get_const_interp(Z3_context a0, Z3_model a1, Z3_func_decl a2); +#define LOG_Z3_model_get_const_interp(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_model_get_const_interp(_ARG0, _ARG1, _ARG2); } +void log_Z3_model_get_func_interp(Z3_context a0, Z3_model a1, Z3_func_decl a2); +#define LOG_Z3_model_get_func_interp(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_model_get_func_interp(_ARG0, _ARG1, _ARG2); } +void log_Z3_model_get_num_consts(Z3_context a0, Z3_model a1); +#define LOG_Z3_model_get_num_consts(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_model_get_num_consts(_ARG0, _ARG1); } +void log_Z3_model_get_const_decl(Z3_context a0, Z3_model a1, unsigned a2); +#define LOG_Z3_model_get_const_decl(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_model_get_const_decl(_ARG0, _ARG1, _ARG2); } +void log_Z3_model_get_num_funcs(Z3_context a0, Z3_model a1); +#define LOG_Z3_model_get_num_funcs(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_model_get_num_funcs(_ARG0, _ARG1); } +void log_Z3_model_get_func_decl(Z3_context a0, Z3_model a1, unsigned a2); +#define LOG_Z3_model_get_func_decl(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_model_get_func_decl(_ARG0, _ARG1, _ARG2); } +void log_Z3_model_eval(Z3_context a0, Z3_model a1, Z3_ast a2, Z3_bool a3, Z3_ast* a4); +#define LOG_Z3_model_eval(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4) z3_log_ctx _LOG_CTX; Z3_ast* Z3ARG4; if (_LOG_CTX.enabled()) { log_Z3_model_eval(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4); Z3ARG4 = _ARG4; } +#define RETURN_Z3_model_eval if (_LOG_CTX.enabled()) { SetO((Z3ARG4 == 0 ? 0 : *Z3ARG4), 4); } return +void log_Z3_model_get_num_sorts(Z3_context a0, Z3_model a1); +#define LOG_Z3_model_get_num_sorts(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_model_get_num_sorts(_ARG0, _ARG1); } +void log_Z3_model_get_sort(Z3_context a0, Z3_model a1, unsigned a2); +#define LOG_Z3_model_get_sort(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_model_get_sort(_ARG0, _ARG1, _ARG2); } +void log_Z3_model_get_sort_universe(Z3_context a0, Z3_model a1, Z3_sort a2); +#define LOG_Z3_model_get_sort_universe(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_model_get_sort_universe(_ARG0, _ARG1, _ARG2); } +void log_Z3_is_as_array(Z3_context a0, Z3_ast a1); +#define LOG_Z3_is_as_array(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_is_as_array(_ARG0, _ARG1); } +void log_Z3_get_as_array_func_decl(Z3_context a0, Z3_ast a1); +#define LOG_Z3_get_as_array_func_decl(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_as_array_func_decl(_ARG0, _ARG1); } +void log_Z3_func_interp_inc_ref(Z3_context a0, Z3_func_interp a1); +#define LOG_Z3_func_interp_inc_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_func_interp_inc_ref(_ARG0, _ARG1); } +void log_Z3_func_interp_dec_ref(Z3_context a0, Z3_func_interp a1); +#define LOG_Z3_func_interp_dec_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_func_interp_dec_ref(_ARG0, _ARG1); } +void log_Z3_func_interp_get_num_entries(Z3_context a0, Z3_func_interp a1); +#define LOG_Z3_func_interp_get_num_entries(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_func_interp_get_num_entries(_ARG0, _ARG1); } +void log_Z3_func_interp_get_entry(Z3_context a0, Z3_func_interp a1, unsigned a2); +#define LOG_Z3_func_interp_get_entry(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_func_interp_get_entry(_ARG0, _ARG1, _ARG2); } +void log_Z3_func_interp_get_else(Z3_context a0, Z3_func_interp a1); +#define LOG_Z3_func_interp_get_else(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_func_interp_get_else(_ARG0, _ARG1); } +void log_Z3_func_interp_get_arity(Z3_context a0, Z3_func_interp a1); +#define LOG_Z3_func_interp_get_arity(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_func_interp_get_arity(_ARG0, _ARG1); } +void log_Z3_func_entry_inc_ref(Z3_context a0, Z3_func_entry a1); +#define LOG_Z3_func_entry_inc_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_func_entry_inc_ref(_ARG0, _ARG1); } +void log_Z3_func_entry_dec_ref(Z3_context a0, Z3_func_entry a1); +#define LOG_Z3_func_entry_dec_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_func_entry_dec_ref(_ARG0, _ARG1); } +void log_Z3_func_entry_get_value(Z3_context a0, Z3_func_entry a1); +#define LOG_Z3_func_entry_get_value(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_func_entry_get_value(_ARG0, _ARG1); } +void log_Z3_func_entry_get_num_args(Z3_context a0, Z3_func_entry a1); +#define LOG_Z3_func_entry_get_num_args(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_func_entry_get_num_args(_ARG0, _ARG1); } +void log_Z3_func_entry_get_arg(Z3_context a0, Z3_func_entry a1, unsigned a2); +#define LOG_Z3_func_entry_get_arg(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_func_entry_get_arg(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_model_num_constants(Z3_context a0, Z3_model a1); +#define LOG_Z3_get_model_num_constants(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_model_num_constants(_ARG0, _ARG1); } +void log_Z3_get_model_constant(Z3_context a0, Z3_model a1, unsigned a2); +#define LOG_Z3_get_model_constant(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_model_constant(_ARG0, _ARG1, _ARG2); } +void log_Z3_eval_func_decl(Z3_context a0, Z3_model a1, Z3_func_decl a2, Z3_ast* a3); +#define LOG_Z3_eval_func_decl(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; Z3_ast* Z3ARG3; if (_LOG_CTX.enabled()) { log_Z3_eval_func_decl(_ARG0, _ARG1, _ARG2, _ARG3); Z3ARG3 = _ARG3; } +#define RETURN_Z3_eval_func_decl if (_LOG_CTX.enabled()) { SetO((Z3ARG3 == 0 ? 0 : *Z3ARG3), 3); } return +void log_Z3_is_array_value(Z3_context a0, Z3_model a1, Z3_ast a2, unsigned* a3); +#define LOG_Z3_is_array_value(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_is_array_value(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_get_array_value(Z3_context a0, Z3_model a1, Z3_ast a2, unsigned a3, Z3_ast* a4, Z3_ast* a5, Z3_ast* a6); +#define LOG_Z3_get_array_value(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6) z3_log_ctx _LOG_CTX; unsigned Z3ARG3; Z3_ast* Z3ARG4; Z3_ast* Z3ARG5; Z3_ast* Z3ARG6; if (_LOG_CTX.enabled()) { log_Z3_get_array_value(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6); Z3ARG3 = _ARG3; Z3ARG4 = _ARG4; Z3ARG5 = _ARG5; Z3ARG6 = _ARG6; } +#define RETURN_Z3_get_array_value if (_LOG_CTX.enabled()) { for (unsigned i = 0; i < Z3ARG3; i++) { SetAO(Z3ARG4[i], 4, i); } for (unsigned i = 0; i < Z3ARG3; i++) { SetAO(Z3ARG5[i], 5, i); } SetO((Z3ARG6 == 0 ? 0 : *Z3ARG6), 6); } return +void log_Z3_get_model_num_funcs(Z3_context a0, Z3_model a1); +#define LOG_Z3_get_model_num_funcs(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_model_num_funcs(_ARG0, _ARG1); } +void log_Z3_get_model_func_decl(Z3_context a0, Z3_model a1, unsigned a2); +#define LOG_Z3_get_model_func_decl(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_model_func_decl(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_model_func_else(Z3_context a0, Z3_model a1, unsigned a2); +#define LOG_Z3_get_model_func_else(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_model_func_else(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_model_func_num_entries(Z3_context a0, Z3_model a1, unsigned a2); +#define LOG_Z3_get_model_func_num_entries(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_model_func_num_entries(_ARG0, _ARG1, _ARG2); } +void log_Z3_get_model_func_entry_num_args(Z3_context a0, Z3_model a1, unsigned a2, unsigned a3); +#define LOG_Z3_get_model_func_entry_num_args(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_model_func_entry_num_args(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_get_model_func_entry_arg(Z3_context a0, Z3_model a1, unsigned a2, unsigned a3, unsigned a4); +#define LOG_Z3_get_model_func_entry_arg(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_model_func_entry_arg(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4); } +void log_Z3_get_model_func_entry_value(Z3_context a0, Z3_model a1, unsigned a2, unsigned a3); +#define LOG_Z3_get_model_func_entry_value(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_model_func_entry_value(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_eval(Z3_context a0, Z3_model a1, Z3_ast a2, Z3_ast* a3); +#define LOG_Z3_eval(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; Z3_ast* Z3ARG3; if (_LOG_CTX.enabled()) { log_Z3_eval(_ARG0, _ARG1, _ARG2, _ARG3); Z3ARG3 = _ARG3; } +#define RETURN_Z3_eval if (_LOG_CTX.enabled()) { SetO((Z3ARG3 == 0 ? 0 : *Z3ARG3), 3); } return +void log_Z3_eval_decl(Z3_context a0, Z3_model a1, Z3_func_decl a2, unsigned a3, Z3_ast const * a4, Z3_ast* a5); +#define LOG_Z3_eval_decl(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5) z3_log_ctx _LOG_CTX; Z3_ast* Z3ARG5; if (_LOG_CTX.enabled()) { log_Z3_eval_decl(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5); Z3ARG5 = _ARG5; } +#define RETURN_Z3_eval_decl if (_LOG_CTX.enabled()) { SetO((Z3ARG5 == 0 ? 0 : *Z3ARG5), 5); } return +void log_Z3_set_ast_print_mode(Z3_context a0, Z3_ast_print_mode a1); +#define LOG_Z3_set_ast_print_mode(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_set_ast_print_mode(_ARG0, _ARG1); } +void log_Z3_ast_to_string(Z3_context a0, Z3_ast a1); +#define LOG_Z3_ast_to_string(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_ast_to_string(_ARG0, _ARG1); } +void log_Z3_pattern_to_string(Z3_context a0, Z3_pattern a1); +#define LOG_Z3_pattern_to_string(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_pattern_to_string(_ARG0, _ARG1); } +void log_Z3_sort_to_string(Z3_context a0, Z3_sort a1); +#define LOG_Z3_sort_to_string(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_sort_to_string(_ARG0, _ARG1); } +void log_Z3_func_decl_to_string(Z3_context a0, Z3_func_decl a1); +#define LOG_Z3_func_decl_to_string(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_func_decl_to_string(_ARG0, _ARG1); } +void log_Z3_model_to_string(Z3_context a0, Z3_model a1); +#define LOG_Z3_model_to_string(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_model_to_string(_ARG0, _ARG1); } +void log_Z3_benchmark_to_smtlib_string(Z3_context a0, Z3_string a1, Z3_string a2, Z3_string a3, Z3_string a4, unsigned a5, Z3_ast const * a6, Z3_ast a7); +#define LOG_Z3_benchmark_to_smtlib_string(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_benchmark_to_smtlib_string(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7); } +void log_Z3_context_to_string(Z3_context a0); +#define LOG_Z3_context_to_string(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_context_to_string(_ARG0); } +void log_Z3_statistics_to_string(Z3_context a0); +#define LOG_Z3_statistics_to_string(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_statistics_to_string(_ARG0); } +void log_Z3_get_context_assignment(Z3_context a0); +#define LOG_Z3_get_context_assignment(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_context_assignment(_ARG0); } +void log_Z3_parse_smtlib_string(Z3_context a0, Z3_string a1, unsigned a2, Z3_symbol const * a3, Z3_sort const * a4, unsigned a5, Z3_symbol const * a6, Z3_func_decl const * a7); +#define LOG_Z3_parse_smtlib_string(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_parse_smtlib_string(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7); } +void log_Z3_parse_smtlib_file(Z3_context a0, Z3_string a1, unsigned a2, Z3_symbol const * a3, Z3_sort const * a4, unsigned a5, Z3_symbol const * a6, Z3_func_decl const * a7); +#define LOG_Z3_parse_smtlib_file(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_parse_smtlib_file(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7); } +void log_Z3_get_smtlib_num_formulas(Z3_context a0); +#define LOG_Z3_get_smtlib_num_formulas(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_smtlib_num_formulas(_ARG0); } +void log_Z3_get_smtlib_formula(Z3_context a0, unsigned a1); +#define LOG_Z3_get_smtlib_formula(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_smtlib_formula(_ARG0, _ARG1); } +void log_Z3_get_smtlib_num_assumptions(Z3_context a0); +#define LOG_Z3_get_smtlib_num_assumptions(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_smtlib_num_assumptions(_ARG0); } +void log_Z3_get_smtlib_assumption(Z3_context a0, unsigned a1); +#define LOG_Z3_get_smtlib_assumption(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_smtlib_assumption(_ARG0, _ARG1); } +void log_Z3_get_smtlib_num_decls(Z3_context a0); +#define LOG_Z3_get_smtlib_num_decls(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_smtlib_num_decls(_ARG0); } +void log_Z3_get_smtlib_decl(Z3_context a0, unsigned a1); +#define LOG_Z3_get_smtlib_decl(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_smtlib_decl(_ARG0, _ARG1); } +void log_Z3_get_smtlib_num_sorts(Z3_context a0); +#define LOG_Z3_get_smtlib_num_sorts(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_smtlib_num_sorts(_ARG0); } +void log_Z3_get_smtlib_sort(Z3_context a0, unsigned a1); +#define LOG_Z3_get_smtlib_sort(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_smtlib_sort(_ARG0, _ARG1); } +void log_Z3_get_smtlib_error(Z3_context a0); +#define LOG_Z3_get_smtlib_error(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_smtlib_error(_ARG0); } +void log_Z3_parse_z3_string(Z3_context a0, Z3_string a1); +#define LOG_Z3_parse_z3_string(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_parse_z3_string(_ARG0, _ARG1); } +void log_Z3_parse_z3_file(Z3_context a0, Z3_string a1); +#define LOG_Z3_parse_z3_file(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_parse_z3_file(_ARG0, _ARG1); } +void log_Z3_parse_smtlib2_string(Z3_context a0, Z3_string a1, unsigned a2, Z3_symbol const * a3, Z3_sort const * a4, unsigned a5, Z3_symbol const * a6, Z3_func_decl const * a7); +#define LOG_Z3_parse_smtlib2_string(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_parse_smtlib2_string(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7); } +void log_Z3_parse_smtlib2_file(Z3_context a0, Z3_string a1, unsigned a2, Z3_symbol const * a3, Z3_sort const * a4, unsigned a5, Z3_symbol const * a6, Z3_func_decl const * a7); +#define LOG_Z3_parse_smtlib2_file(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_parse_smtlib2_file(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5, _ARG6, _ARG7); } +void log_Z3_get_error_code(Z3_context a0); +#define LOG_Z3_get_error_code(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_error_code(_ARG0); } +void log_Z3_set_error(Z3_context a0, Z3_error_code a1); +#define LOG_Z3_set_error(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_set_error(_ARG0, _ARG1); } +void log_Z3_get_error_msg(Z3_error_code a0); +#define LOG_Z3_get_error_msg(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_error_msg(_ARG0); } +void log_Z3_get_version(unsigned* a0, unsigned* a1, unsigned* a2, unsigned* a3); +#define LOG_Z3_get_version(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_version(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_reset_memory(); +#define LOG_Z3_reset_memory() z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_reset_memory(); } +void log_Z3_is_app(Z3_context a0, Z3_ast a1); +#define LOG_Z3_is_app(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_is_app(_ARG0, _ARG1); } +void log_Z3_is_numeral_ast(Z3_context a0, Z3_ast a1); +#define LOG_Z3_is_numeral_ast(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_is_numeral_ast(_ARG0, _ARG1); } +void log_Z3_get_arity(Z3_context a0, Z3_func_decl a1); +#define LOG_Z3_get_arity(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_arity(_ARG0, _ARG1); } +void log_Z3_mk_injective_function(Z3_context a0, Z3_symbol a1, unsigned a2, Z3_sort const * a3, Z3_sort a4); +#define LOG_Z3_mk_injective_function(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_injective_function(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4); } +void log_Z3_mk_fixedpoint(Z3_context a0); +#define LOG_Z3_mk_fixedpoint(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_fixedpoint(_ARG0); } +void log_Z3_fixedpoint_inc_ref(Z3_context a0, Z3_fixedpoint a1); +#define LOG_Z3_fixedpoint_inc_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_inc_ref(_ARG0, _ARG1); } +void log_Z3_fixedpoint_dec_ref(Z3_context a0, Z3_fixedpoint a1); +#define LOG_Z3_fixedpoint_dec_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_dec_ref(_ARG0, _ARG1); } +void log_Z3_fixedpoint_push(Z3_context a0, Z3_fixedpoint a1); +#define LOG_Z3_fixedpoint_push(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_push(_ARG0, _ARG1); } +void log_Z3_fixedpoint_pop(Z3_context a0, Z3_fixedpoint a1); +#define LOG_Z3_fixedpoint_pop(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_pop(_ARG0, _ARG1); } +void log_Z3_fixedpoint_register_relation(Z3_context a0, Z3_fixedpoint a1, Z3_func_decl a2); +#define LOG_Z3_fixedpoint_register_relation(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_register_relation(_ARG0, _ARG1, _ARG2); } +void log_Z3_fixedpoint_assert(Z3_context a0, Z3_fixedpoint a1, Z3_ast a2); +#define LOG_Z3_fixedpoint_assert(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_assert(_ARG0, _ARG1, _ARG2); } +void log_Z3_fixedpoint_add_rule(Z3_context a0, Z3_fixedpoint a1, Z3_ast a2, Z3_symbol a3); +#define LOG_Z3_fixedpoint_add_rule(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_add_rule(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_fixedpoint_add_fact(Z3_context a0, Z3_fixedpoint a1, Z3_func_decl a2, unsigned a3, unsigned const * a4); +#define LOG_Z3_fixedpoint_add_fact(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_add_fact(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4); } +void log_Z3_fixedpoint_query(Z3_context a0, Z3_fixedpoint a1, Z3_ast a2); +#define LOG_Z3_fixedpoint_query(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_query(_ARG0, _ARG1, _ARG2); } +void log_Z3_fixedpoint_query_relations(Z3_context a0, Z3_fixedpoint a1, unsigned a2, Z3_func_decl const * a3); +#define LOG_Z3_fixedpoint_query_relations(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_query_relations(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_fixedpoint_get_answer(Z3_context a0, Z3_fixedpoint a1); +#define LOG_Z3_fixedpoint_get_answer(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_get_answer(_ARG0, _ARG1); } +void log_Z3_fixedpoint_update_rule(Z3_context a0, Z3_fixedpoint a1, Z3_ast a2, Z3_symbol a3); +#define LOG_Z3_fixedpoint_update_rule(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_update_rule(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_fixedpoint_get_num_levels(Z3_context a0, Z3_fixedpoint a1, Z3_func_decl a2); +#define LOG_Z3_fixedpoint_get_num_levels(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_get_num_levels(_ARG0, _ARG1, _ARG2); } +void log_Z3_fixedpoint_get_cover_delta(Z3_context a0, Z3_fixedpoint a1, int a2, Z3_func_decl a3); +#define LOG_Z3_fixedpoint_get_cover_delta(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_get_cover_delta(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_fixedpoint_add_cover(Z3_context a0, Z3_fixedpoint a1, int a2, Z3_func_decl a3, Z3_ast a4); +#define LOG_Z3_fixedpoint_add_cover(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_add_cover(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4); } +void log_Z3_fixedpoint_get_statistics(Z3_context a0, Z3_fixedpoint a1); +#define LOG_Z3_fixedpoint_get_statistics(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_get_statistics(_ARG0, _ARG1); } +void log_Z3_fixedpoint_get_help(Z3_context a0, Z3_fixedpoint a1); +#define LOG_Z3_fixedpoint_get_help(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_get_help(_ARG0, _ARG1); } +void log_Z3_fixedpoint_get_param_descrs(Z3_context a0, Z3_fixedpoint a1); +#define LOG_Z3_fixedpoint_get_param_descrs(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_get_param_descrs(_ARG0, _ARG1); } +void log_Z3_fixedpoint_set_params(Z3_context a0, Z3_fixedpoint a1, Z3_params a2); +#define LOG_Z3_fixedpoint_set_params(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_set_params(_ARG0, _ARG1, _ARG2); } +void log_Z3_fixedpoint_to_string(Z3_context a0, Z3_fixedpoint a1, unsigned a2, Z3_ast const * a3); +#define LOG_Z3_fixedpoint_to_string(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_to_string(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_fixedpoint_get_reason_unknown(Z3_context a0, Z3_fixedpoint a1); +#define LOG_Z3_fixedpoint_get_reason_unknown(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_get_reason_unknown(_ARG0, _ARG1); } +void log_Z3_fixedpoint_set_predicate_representation(Z3_context a0, Z3_fixedpoint a1, Z3_func_decl a2, unsigned a3, Z3_symbol const * a4); +#define LOG_Z3_fixedpoint_set_predicate_representation(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_set_predicate_representation(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4); } +void log_Z3_fixedpoint_simplify_rules(Z3_context a0, Z3_fixedpoint a1, unsigned a2, Z3_ast const * a3, unsigned a4, Z3_func_decl const * a5); +#define LOG_Z3_fixedpoint_simplify_rules(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_fixedpoint_simplify_rules(_ARG0, _ARG1, _ARG2, _ARG3, _ARG4, _ARG5); } +void log_Z3_mk_params(Z3_context a0); +#define LOG_Z3_mk_params(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_params(_ARG0); } +void log_Z3_params_inc_ref(Z3_context a0, Z3_params a1); +#define LOG_Z3_params_inc_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_params_inc_ref(_ARG0, _ARG1); } +void log_Z3_params_dec_ref(Z3_context a0, Z3_params a1); +#define LOG_Z3_params_dec_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_params_dec_ref(_ARG0, _ARG1); } +void log_Z3_params_set_bool(Z3_context a0, Z3_params a1, Z3_symbol a2, Z3_bool a3); +#define LOG_Z3_params_set_bool(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_params_set_bool(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_params_set_uint(Z3_context a0, Z3_params a1, Z3_symbol a2, unsigned a3); +#define LOG_Z3_params_set_uint(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_params_set_uint(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_params_set_double(Z3_context a0, Z3_params a1, Z3_symbol a2, double a3); +#define LOG_Z3_params_set_double(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_params_set_double(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_params_set_symbol(Z3_context a0, Z3_params a1, Z3_symbol a2, Z3_symbol a3); +#define LOG_Z3_params_set_symbol(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_params_set_symbol(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_params_to_string(Z3_context a0, Z3_params a1); +#define LOG_Z3_params_to_string(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_params_to_string(_ARG0, _ARG1); } +void log_Z3_params_validate(Z3_context a0, Z3_params a1, Z3_param_descrs a2); +#define LOG_Z3_params_validate(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_params_validate(_ARG0, _ARG1, _ARG2); } +void log_Z3_param_descrs_inc_ref(Z3_context a0, Z3_param_descrs a1); +#define LOG_Z3_param_descrs_inc_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_param_descrs_inc_ref(_ARG0, _ARG1); } +void log_Z3_param_descrs_dec_ref(Z3_context a0, Z3_param_descrs a1); +#define LOG_Z3_param_descrs_dec_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_param_descrs_dec_ref(_ARG0, _ARG1); } +void log_Z3_param_descrs_get_kind(Z3_context a0, Z3_param_descrs a1, Z3_symbol a2); +#define LOG_Z3_param_descrs_get_kind(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_param_descrs_get_kind(_ARG0, _ARG1, _ARG2); } +void log_Z3_param_descrs_size(Z3_context a0, Z3_param_descrs a1); +#define LOG_Z3_param_descrs_size(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_param_descrs_size(_ARG0, _ARG1); } +void log_Z3_param_descrs_get_name(Z3_context a0, Z3_param_descrs a1, unsigned a2); +#define LOG_Z3_param_descrs_get_name(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_param_descrs_get_name(_ARG0, _ARG1, _ARG2); } +void log_Z3_interrupt(Z3_context a0); +#define LOG_Z3_interrupt(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_interrupt(_ARG0); } +void log_Z3_get_error_msg_ex(Z3_context a0, Z3_error_code a1); +#define LOG_Z3_get_error_msg_ex(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_error_msg_ex(_ARG0, _ARG1); } +void log_Z3_translate(Z3_context a0, Z3_ast a1, Z3_context a2); +#define LOG_Z3_translate(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_translate(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_goal(Z3_context a0, Z3_bool a1, Z3_bool a2, Z3_bool a3); +#define LOG_Z3_mk_goal(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_goal(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_goal_inc_ref(Z3_context a0, Z3_goal a1); +#define LOG_Z3_goal_inc_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_goal_inc_ref(_ARG0, _ARG1); } +void log_Z3_goal_dec_ref(Z3_context a0, Z3_goal a1); +#define LOG_Z3_goal_dec_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_goal_dec_ref(_ARG0, _ARG1); } +void log_Z3_goal_precision(Z3_context a0, Z3_goal a1); +#define LOG_Z3_goal_precision(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_goal_precision(_ARG0, _ARG1); } +void log_Z3_goal_assert(Z3_context a0, Z3_goal a1, Z3_ast a2); +#define LOG_Z3_goal_assert(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_goal_assert(_ARG0, _ARG1, _ARG2); } +void log_Z3_goal_inconsistent(Z3_context a0, Z3_goal a1); +#define LOG_Z3_goal_inconsistent(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_goal_inconsistent(_ARG0, _ARG1); } +void log_Z3_goal_depth(Z3_context a0, Z3_goal a1); +#define LOG_Z3_goal_depth(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_goal_depth(_ARG0, _ARG1); } +void log_Z3_goal_reset(Z3_context a0, Z3_goal a1); +#define LOG_Z3_goal_reset(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_goal_reset(_ARG0, _ARG1); } +void log_Z3_goal_size(Z3_context a0, Z3_goal a1); +#define LOG_Z3_goal_size(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_goal_size(_ARG0, _ARG1); } +void log_Z3_goal_formula(Z3_context a0, Z3_goal a1, unsigned a2); +#define LOG_Z3_goal_formula(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_goal_formula(_ARG0, _ARG1, _ARG2); } +void log_Z3_goal_num_exprs(Z3_context a0, Z3_goal a1); +#define LOG_Z3_goal_num_exprs(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_goal_num_exprs(_ARG0, _ARG1); } +void log_Z3_goal_is_decided_sat(Z3_context a0, Z3_goal a1); +#define LOG_Z3_goal_is_decided_sat(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_goal_is_decided_sat(_ARG0, _ARG1); } +void log_Z3_goal_is_decided_unsat(Z3_context a0, Z3_goal a1); +#define LOG_Z3_goal_is_decided_unsat(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_goal_is_decided_unsat(_ARG0, _ARG1); } +void log_Z3_goal_translate(Z3_context a0, Z3_goal a1, Z3_context a2); +#define LOG_Z3_goal_translate(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_goal_translate(_ARG0, _ARG1, _ARG2); } +void log_Z3_goal_to_string(Z3_context a0, Z3_goal a1); +#define LOG_Z3_goal_to_string(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_goal_to_string(_ARG0, _ARG1); } +void log_Z3_mk_tactic(Z3_context a0, Z3_string a1); +#define LOG_Z3_mk_tactic(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_tactic(_ARG0, _ARG1); } +void log_Z3_mk_probe(Z3_context a0, Z3_string a1); +#define LOG_Z3_mk_probe(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_probe(_ARG0, _ARG1); } +void log_Z3_tactic_inc_ref(Z3_context a0, Z3_tactic a1); +#define LOG_Z3_tactic_inc_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_tactic_inc_ref(_ARG0, _ARG1); } +void log_Z3_tactic_dec_ref(Z3_context a0, Z3_tactic a1); +#define LOG_Z3_tactic_dec_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_tactic_dec_ref(_ARG0, _ARG1); } +void log_Z3_probe_inc_ref(Z3_context a0, Z3_probe a1); +#define LOG_Z3_probe_inc_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_probe_inc_ref(_ARG0, _ARG1); } +void log_Z3_probe_dec_ref(Z3_context a0, Z3_probe a1); +#define LOG_Z3_probe_dec_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_probe_dec_ref(_ARG0, _ARG1); } +void log_Z3_tactic_and_then(Z3_context a0, Z3_tactic a1, Z3_tactic a2); +#define LOG_Z3_tactic_and_then(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_tactic_and_then(_ARG0, _ARG1, _ARG2); } +void log_Z3_tactic_or_else(Z3_context a0, Z3_tactic a1, Z3_tactic a2); +#define LOG_Z3_tactic_or_else(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_tactic_or_else(_ARG0, _ARG1, _ARG2); } +void log_Z3_tactic_par_or(Z3_context a0, unsigned a1, Z3_tactic const * a2); +#define LOG_Z3_tactic_par_or(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_tactic_par_or(_ARG0, _ARG1, _ARG2); } +void log_Z3_tactic_par_and_then(Z3_context a0, Z3_tactic a1, Z3_tactic a2); +#define LOG_Z3_tactic_par_and_then(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_tactic_par_and_then(_ARG0, _ARG1, _ARG2); } +void log_Z3_tactic_try_for(Z3_context a0, Z3_tactic a1, unsigned a2); +#define LOG_Z3_tactic_try_for(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_tactic_try_for(_ARG0, _ARG1, _ARG2); } +void log_Z3_tactic_when(Z3_context a0, Z3_probe a1, Z3_tactic a2); +#define LOG_Z3_tactic_when(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_tactic_when(_ARG0, _ARG1, _ARG2); } +void log_Z3_tactic_cond(Z3_context a0, Z3_probe a1, Z3_tactic a2, Z3_tactic a3); +#define LOG_Z3_tactic_cond(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_tactic_cond(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_tactic_repeat(Z3_context a0, Z3_tactic a1, unsigned a2); +#define LOG_Z3_tactic_repeat(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_tactic_repeat(_ARG0, _ARG1, _ARG2); } +void log_Z3_tactic_skip(Z3_context a0); +#define LOG_Z3_tactic_skip(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_tactic_skip(_ARG0); } +void log_Z3_tactic_fail(Z3_context a0); +#define LOG_Z3_tactic_fail(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_tactic_fail(_ARG0); } +void log_Z3_tactic_fail_if(Z3_context a0, Z3_probe a1); +#define LOG_Z3_tactic_fail_if(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_tactic_fail_if(_ARG0, _ARG1); } +void log_Z3_tactic_fail_if_not_decided(Z3_context a0); +#define LOG_Z3_tactic_fail_if_not_decided(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_tactic_fail_if_not_decided(_ARG0); } +void log_Z3_tactic_using_params(Z3_context a0, Z3_tactic a1, Z3_params a2); +#define LOG_Z3_tactic_using_params(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_tactic_using_params(_ARG0, _ARG1, _ARG2); } +void log_Z3_probe_const(Z3_context a0, double a1); +#define LOG_Z3_probe_const(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_probe_const(_ARG0, _ARG1); } +void log_Z3_probe_lt(Z3_context a0, Z3_probe a1, Z3_probe a2); +#define LOG_Z3_probe_lt(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_probe_lt(_ARG0, _ARG1, _ARG2); } +void log_Z3_probe_le(Z3_context a0, Z3_probe a1, Z3_probe a2); +#define LOG_Z3_probe_le(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_probe_le(_ARG0, _ARG1, _ARG2); } +void log_Z3_probe_gt(Z3_context a0, Z3_probe a1, Z3_probe a2); +#define LOG_Z3_probe_gt(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_probe_gt(_ARG0, _ARG1, _ARG2); } +void log_Z3_probe_ge(Z3_context a0, Z3_probe a1, Z3_probe a2); +#define LOG_Z3_probe_ge(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_probe_ge(_ARG0, _ARG1, _ARG2); } +void log_Z3_probe_eq(Z3_context a0, Z3_probe a1, Z3_probe a2); +#define LOG_Z3_probe_eq(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_probe_eq(_ARG0, _ARG1, _ARG2); } +void log_Z3_probe_and(Z3_context a0, Z3_probe a1, Z3_probe a2); +#define LOG_Z3_probe_and(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_probe_and(_ARG0, _ARG1, _ARG2); } +void log_Z3_probe_or(Z3_context a0, Z3_probe a1, Z3_probe a2); +#define LOG_Z3_probe_or(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_probe_or(_ARG0, _ARG1, _ARG2); } +void log_Z3_probe_not(Z3_context a0, Z3_probe a1); +#define LOG_Z3_probe_not(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_probe_not(_ARG0, _ARG1); } +void log_Z3_get_num_tactics(Z3_context a0); +#define LOG_Z3_get_num_tactics(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_num_tactics(_ARG0); } +void log_Z3_get_tactic_name(Z3_context a0, unsigned a1); +#define LOG_Z3_get_tactic_name(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_tactic_name(_ARG0, _ARG1); } +void log_Z3_get_num_probes(Z3_context a0); +#define LOG_Z3_get_num_probes(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_num_probes(_ARG0); } +void log_Z3_get_probe_name(Z3_context a0, unsigned a1); +#define LOG_Z3_get_probe_name(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_get_probe_name(_ARG0, _ARG1); } +void log_Z3_tactic_get_help(Z3_context a0, Z3_tactic a1); +#define LOG_Z3_tactic_get_help(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_tactic_get_help(_ARG0, _ARG1); } +void log_Z3_tactic_get_param_descrs(Z3_context a0, Z3_tactic a1); +#define LOG_Z3_tactic_get_param_descrs(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_tactic_get_param_descrs(_ARG0, _ARG1); } +void log_Z3_tactic_get_descr(Z3_context a0, Z3_string a1); +#define LOG_Z3_tactic_get_descr(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_tactic_get_descr(_ARG0, _ARG1); } +void log_Z3_probe_get_descr(Z3_context a0, Z3_string a1); +#define LOG_Z3_probe_get_descr(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_probe_get_descr(_ARG0, _ARG1); } +void log_Z3_probe_apply(Z3_context a0, Z3_probe a1, Z3_goal a2); +#define LOG_Z3_probe_apply(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_probe_apply(_ARG0, _ARG1, _ARG2); } +void log_Z3_tactic_apply(Z3_context a0, Z3_tactic a1, Z3_goal a2); +#define LOG_Z3_tactic_apply(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_tactic_apply(_ARG0, _ARG1, _ARG2); } +void log_Z3_tactic_apply_ex(Z3_context a0, Z3_tactic a1, Z3_goal a2, Z3_params a3); +#define LOG_Z3_tactic_apply_ex(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_tactic_apply_ex(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_apply_result_inc_ref(Z3_context a0, Z3_apply_result a1); +#define LOG_Z3_apply_result_inc_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_apply_result_inc_ref(_ARG0, _ARG1); } +void log_Z3_apply_result_dec_ref(Z3_context a0, Z3_apply_result a1); +#define LOG_Z3_apply_result_dec_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_apply_result_dec_ref(_ARG0, _ARG1); } +void log_Z3_apply_result_to_string(Z3_context a0, Z3_apply_result a1); +#define LOG_Z3_apply_result_to_string(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_apply_result_to_string(_ARG0, _ARG1); } +void log_Z3_apply_result_get_num_subgoals(Z3_context a0, Z3_apply_result a1); +#define LOG_Z3_apply_result_get_num_subgoals(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_apply_result_get_num_subgoals(_ARG0, _ARG1); } +void log_Z3_apply_result_get_subgoal(Z3_context a0, Z3_apply_result a1, unsigned a2); +#define LOG_Z3_apply_result_get_subgoal(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_apply_result_get_subgoal(_ARG0, _ARG1, _ARG2); } +void log_Z3_apply_result_convert_model(Z3_context a0, Z3_apply_result a1, unsigned a2, Z3_model a3); +#define LOG_Z3_apply_result_convert_model(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_apply_result_convert_model(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_mk_solver(Z3_context a0); +#define LOG_Z3_mk_solver(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_solver(_ARG0); } +void log_Z3_mk_simple_solver(Z3_context a0); +#define LOG_Z3_mk_simple_solver(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_simple_solver(_ARG0); } +void log_Z3_mk_solver_for_logic(Z3_context a0, Z3_symbol a1); +#define LOG_Z3_mk_solver_for_logic(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_solver_for_logic(_ARG0, _ARG1); } +void log_Z3_mk_solver_from_tactic(Z3_context a0, Z3_tactic a1); +#define LOG_Z3_mk_solver_from_tactic(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_solver_from_tactic(_ARG0, _ARG1); } +void log_Z3_solver_get_help(Z3_context a0, Z3_solver a1); +#define LOG_Z3_solver_get_help(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_solver_get_help(_ARG0, _ARG1); } +void log_Z3_solver_get_param_descrs(Z3_context a0, Z3_solver a1); +#define LOG_Z3_solver_get_param_descrs(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_solver_get_param_descrs(_ARG0, _ARG1); } +void log_Z3_solver_set_params(Z3_context a0, Z3_solver a1, Z3_params a2); +#define LOG_Z3_solver_set_params(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_solver_set_params(_ARG0, _ARG1, _ARG2); } +void log_Z3_solver_inc_ref(Z3_context a0, Z3_solver a1); +#define LOG_Z3_solver_inc_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_solver_inc_ref(_ARG0, _ARG1); } +void log_Z3_solver_dec_ref(Z3_context a0, Z3_solver a1); +#define LOG_Z3_solver_dec_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_solver_dec_ref(_ARG0, _ARG1); } +void log_Z3_solver_push(Z3_context a0, Z3_solver a1); +#define LOG_Z3_solver_push(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_solver_push(_ARG0, _ARG1); } +void log_Z3_solver_pop(Z3_context a0, Z3_solver a1, unsigned a2); +#define LOG_Z3_solver_pop(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_solver_pop(_ARG0, _ARG1, _ARG2); } +void log_Z3_solver_reset(Z3_context a0, Z3_solver a1); +#define LOG_Z3_solver_reset(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_solver_reset(_ARG0, _ARG1); } +void log_Z3_solver_get_num_scopes(Z3_context a0, Z3_solver a1); +#define LOG_Z3_solver_get_num_scopes(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_solver_get_num_scopes(_ARG0, _ARG1); } +void log_Z3_solver_assert(Z3_context a0, Z3_solver a1, Z3_ast a2); +#define LOG_Z3_solver_assert(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_solver_assert(_ARG0, _ARG1, _ARG2); } +void log_Z3_solver_get_assertions(Z3_context a0, Z3_solver a1); +#define LOG_Z3_solver_get_assertions(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_solver_get_assertions(_ARG0, _ARG1); } +void log_Z3_solver_check(Z3_context a0, Z3_solver a1); +#define LOG_Z3_solver_check(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_solver_check(_ARG0, _ARG1); } +void log_Z3_solver_check_assumptions(Z3_context a0, Z3_solver a1, unsigned a2, Z3_ast const * a3); +#define LOG_Z3_solver_check_assumptions(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_solver_check_assumptions(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_solver_get_model(Z3_context a0, Z3_solver a1); +#define LOG_Z3_solver_get_model(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_solver_get_model(_ARG0, _ARG1); } +void log_Z3_solver_get_proof(Z3_context a0, Z3_solver a1); +#define LOG_Z3_solver_get_proof(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_solver_get_proof(_ARG0, _ARG1); } +void log_Z3_solver_get_unsat_core(Z3_context a0, Z3_solver a1); +#define LOG_Z3_solver_get_unsat_core(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_solver_get_unsat_core(_ARG0, _ARG1); } +void log_Z3_solver_get_reason_unknown(Z3_context a0, Z3_solver a1); +#define LOG_Z3_solver_get_reason_unknown(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_solver_get_reason_unknown(_ARG0, _ARG1); } +void log_Z3_solver_get_statistics(Z3_context a0, Z3_solver a1); +#define LOG_Z3_solver_get_statistics(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_solver_get_statistics(_ARG0, _ARG1); } +void log_Z3_solver_to_string(Z3_context a0, Z3_solver a1); +#define LOG_Z3_solver_to_string(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_solver_to_string(_ARG0, _ARG1); } +void log_Z3_stats_to_string(Z3_context a0, Z3_stats a1); +#define LOG_Z3_stats_to_string(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_stats_to_string(_ARG0, _ARG1); } +void log_Z3_stats_inc_ref(Z3_context a0, Z3_stats a1); +#define LOG_Z3_stats_inc_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_stats_inc_ref(_ARG0, _ARG1); } +void log_Z3_stats_dec_ref(Z3_context a0, Z3_stats a1); +#define LOG_Z3_stats_dec_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_stats_dec_ref(_ARG0, _ARG1); } +void log_Z3_stats_size(Z3_context a0, Z3_stats a1); +#define LOG_Z3_stats_size(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_stats_size(_ARG0, _ARG1); } +void log_Z3_stats_get_key(Z3_context a0, Z3_stats a1, unsigned a2); +#define LOG_Z3_stats_get_key(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_stats_get_key(_ARG0, _ARG1, _ARG2); } +void log_Z3_stats_is_uint(Z3_context a0, Z3_stats a1, unsigned a2); +#define LOG_Z3_stats_is_uint(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_stats_is_uint(_ARG0, _ARG1, _ARG2); } +void log_Z3_stats_is_double(Z3_context a0, Z3_stats a1, unsigned a2); +#define LOG_Z3_stats_is_double(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_stats_is_double(_ARG0, _ARG1, _ARG2); } +void log_Z3_stats_get_uint_value(Z3_context a0, Z3_stats a1, unsigned a2); +#define LOG_Z3_stats_get_uint_value(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_stats_get_uint_value(_ARG0, _ARG1, _ARG2); } +void log_Z3_stats_get_double_value(Z3_context a0, Z3_stats a1, unsigned a2); +#define LOG_Z3_stats_get_double_value(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_stats_get_double_value(_ARG0, _ARG1, _ARG2); } +void log_Z3_mk_ast_vector(Z3_context a0); +#define LOG_Z3_mk_ast_vector(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_ast_vector(_ARG0); } +void log_Z3_ast_vector_inc_ref(Z3_context a0, Z3_ast_vector a1); +#define LOG_Z3_ast_vector_inc_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_ast_vector_inc_ref(_ARG0, _ARG1); } +void log_Z3_ast_vector_dec_ref(Z3_context a0, Z3_ast_vector a1); +#define LOG_Z3_ast_vector_dec_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_ast_vector_dec_ref(_ARG0, _ARG1); } +void log_Z3_ast_vector_size(Z3_context a0, Z3_ast_vector a1); +#define LOG_Z3_ast_vector_size(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_ast_vector_size(_ARG0, _ARG1); } +void log_Z3_ast_vector_get(Z3_context a0, Z3_ast_vector a1, unsigned a2); +#define LOG_Z3_ast_vector_get(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_ast_vector_get(_ARG0, _ARG1, _ARG2); } +void log_Z3_ast_vector_set(Z3_context a0, Z3_ast_vector a1, unsigned a2, Z3_ast a3); +#define LOG_Z3_ast_vector_set(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_ast_vector_set(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_ast_vector_resize(Z3_context a0, Z3_ast_vector a1, unsigned a2); +#define LOG_Z3_ast_vector_resize(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_ast_vector_resize(_ARG0, _ARG1, _ARG2); } +void log_Z3_ast_vector_push(Z3_context a0, Z3_ast_vector a1, Z3_ast a2); +#define LOG_Z3_ast_vector_push(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_ast_vector_push(_ARG0, _ARG1, _ARG2); } +void log_Z3_ast_vector_translate(Z3_context a0, Z3_ast_vector a1, Z3_context a2); +#define LOG_Z3_ast_vector_translate(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_ast_vector_translate(_ARG0, _ARG1, _ARG2); } +void log_Z3_ast_vector_to_string(Z3_context a0, Z3_ast_vector a1); +#define LOG_Z3_ast_vector_to_string(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_ast_vector_to_string(_ARG0, _ARG1); } +void log_Z3_mk_ast_map(Z3_context a0); +#define LOG_Z3_mk_ast_map(_ARG0) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_mk_ast_map(_ARG0); } +void log_Z3_ast_map_inc_ref(Z3_context a0, Z3_ast_map a1); +#define LOG_Z3_ast_map_inc_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_ast_map_inc_ref(_ARG0, _ARG1); } +void log_Z3_ast_map_dec_ref(Z3_context a0, Z3_ast_map a1); +#define LOG_Z3_ast_map_dec_ref(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_ast_map_dec_ref(_ARG0, _ARG1); } +void log_Z3_ast_map_contains(Z3_context a0, Z3_ast_map a1, Z3_ast a2); +#define LOG_Z3_ast_map_contains(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_ast_map_contains(_ARG0, _ARG1, _ARG2); } +void log_Z3_ast_map_find(Z3_context a0, Z3_ast_map a1, Z3_ast a2); +#define LOG_Z3_ast_map_find(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_ast_map_find(_ARG0, _ARG1, _ARG2); } +void log_Z3_ast_map_insert(Z3_context a0, Z3_ast_map a1, Z3_ast a2, Z3_ast a3); +#define LOG_Z3_ast_map_insert(_ARG0, _ARG1, _ARG2, _ARG3) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_ast_map_insert(_ARG0, _ARG1, _ARG2, _ARG3); } +void log_Z3_ast_map_erase(Z3_context a0, Z3_ast_map a1, Z3_ast a2); +#define LOG_Z3_ast_map_erase(_ARG0, _ARG1, _ARG2) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_ast_map_erase(_ARG0, _ARG1, _ARG2); } +void log_Z3_ast_map_size(Z3_context a0, Z3_ast_map a1); +#define LOG_Z3_ast_map_size(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_ast_map_size(_ARG0, _ARG1); } +void log_Z3_ast_map_reset(Z3_context a0, Z3_ast_map a1); +#define LOG_Z3_ast_map_reset(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_ast_map_reset(_ARG0, _ARG1); } +void log_Z3_ast_map_keys(Z3_context a0, Z3_ast_map a1); +#define LOG_Z3_ast_map_keys(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_ast_map_keys(_ARG0, _ARG1); } +void log_Z3_ast_map_to_string(Z3_context a0, Z3_ast_map a1); +#define LOG_Z3_ast_map_to_string(_ARG0, _ARG1) z3_log_ctx _LOG_CTX; if (_LOG_CTX.enabled()) { log_Z3_ast_map_to_string(_ARG0, _ARG1); } diff --git a/lib/api_model.cpp b/lib/api_model.cpp new file mode 100644 index 000000000..a6ada4ae6 --- /dev/null +++ b/lib/api_model.cpp @@ -0,0 +1,670 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_quant.cpp + +Abstract: + API for models + +Author: + + Leonardo de Moura (leonardo) 2012-02-29. + +Revision History: + +--*/ +#include +#include"z3.h" +#include"api_log_macros.h" +#include"api_context.h" +#include"api_model.h" +#include"api_ast_vector.h" +#include"array_decl_plugin.h" +#include"model.h" +#include"model_v2_pp.h" +#include"model_smt2_pp.h" + +extern "C" { + + void Z3_API Z3_model_inc_ref(Z3_context c, Z3_model m) { + Z3_TRY; + LOG_Z3_model_inc_ref(c, m); + RESET_ERROR_CODE(); + if (m) { + to_model(m)->inc_ref(); + } + Z3_CATCH; + } + + void Z3_API Z3_model_dec_ref(Z3_context c, Z3_model m) { + Z3_TRY; + LOG_Z3_model_dec_ref(c, m); + RESET_ERROR_CODE(); + if (m) { + to_model(m)->dec_ref(); + } + Z3_CATCH; + } + + Z3_ast Z3_API Z3_model_get_const_interp(Z3_context c, Z3_model m, Z3_func_decl a) { + Z3_TRY; + LOG_Z3_model_get_const_interp(c, m, a); + RESET_ERROR_CODE(); + CHECK_NON_NULL(m, 0); + expr * r = to_model_ref(m)->get_const_interp(to_func_decl(a)); + if (!r) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + RETURN_Z3(of_expr(r)); + Z3_CATCH_RETURN(0); + } + + Z3_func_interp Z3_API Z3_model_get_func_interp(Z3_context c, Z3_model m, Z3_func_decl f) { + Z3_TRY; + LOG_Z3_model_get_func_interp(c, m, f); + RESET_ERROR_CODE(); + CHECK_NON_NULL(m, 0); + func_interp * _fi = to_model_ref(m)->get_func_interp(to_func_decl(f)); + if (!_fi) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + Z3_func_interp_ref * fi = alloc(Z3_func_interp_ref, to_model_ref(m)); + fi->m_func_interp = _fi; + mk_c(c)->save_object(fi); + RETURN_Z3(of_func_interp(fi)); + Z3_CATCH_RETURN(0); + } + + unsigned Z3_API Z3_model_get_num_consts(Z3_context c, Z3_model m) { + Z3_TRY; + LOG_Z3_model_get_num_consts(c, m); + RESET_ERROR_CODE(); + CHECK_NON_NULL(m, 0); + return to_model_ref(m)->get_num_constants(); + Z3_CATCH_RETURN(0); + } + + Z3_func_decl Z3_API Z3_model_get_const_decl(Z3_context c, Z3_model m, unsigned i) { + Z3_TRY; + LOG_Z3_model_get_const_decl(c, m, i); + RESET_ERROR_CODE(); + CHECK_NON_NULL(m, 0); + model * _m = to_model_ref(m); + if (i < _m->get_num_constants()) { + RETURN_Z3(of_func_decl(_m->get_constant(i))); + } + else { + SET_ERROR_CODE(Z3_IOB); + RETURN_Z3(0); + } + Z3_CATCH_RETURN(0); + } + + unsigned Z3_API Z3_model_get_num_funcs(Z3_context c, Z3_model m) { + Z3_TRY; + LOG_Z3_model_get_num_funcs(c, m); + RESET_ERROR_CODE(); + CHECK_NON_NULL(m, 0); + return to_model_ref(m)->get_num_functions(); + Z3_CATCH_RETURN(0); + } + + Z3_func_decl get_model_func_decl_core(Z3_context c, Z3_model m, unsigned i) { + CHECK_NON_NULL(m, 0); + model * _m = to_model_ref(m); + if (i >= _m->get_num_functions()) { + SET_ERROR_CODE(Z3_IOB); + return 0; + } + return of_func_decl(_m->get_function(i)); + } + + Z3_func_decl Z3_API Z3_model_get_func_decl(Z3_context c, Z3_model m, unsigned i) { + Z3_TRY; + LOG_Z3_get_model_func_decl(c, m, i); + RESET_ERROR_CODE(); + Z3_func_decl r = get_model_func_decl_core(c, m, i); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_bool Z3_API Z3_model_eval(Z3_context c, Z3_model m, Z3_ast t, Z3_bool model_completion, Z3_ast * v) { + Z3_TRY; + LOG_Z3_model_eval(c, m, t, model_completion, v); + if (v) *v = 0; + RESET_ERROR_CODE(); + CHECK_NON_NULL(m, Z3_FALSE); + model * _m = to_model_ref(m); + expr_ref result(mk_c(c)->m()); + _m->eval(to_expr(t), result, model_completion == Z3_TRUE); + mk_c(c)->save_ast_trail(result.get()); + *v = of_ast(result.get()); + RETURN_Z3_model_eval Z3_TRUE; + Z3_CATCH_RETURN(0); + } + + unsigned Z3_API Z3_model_get_num_sorts(Z3_context c, Z3_model m) { + Z3_TRY; + LOG_Z3_model_get_num_sorts(c, m); + RESET_ERROR_CODE(); + return to_model_ref(m)->get_num_uninterpreted_sorts(); + Z3_CATCH_RETURN(0); + } + + Z3_sort Z3_API Z3_model_get_sort(Z3_context c, Z3_model m, unsigned i) { + Z3_TRY; + LOG_Z3_model_get_sort(c, m, i); + RESET_ERROR_CODE(); + if (i >= to_model_ref(m)->get_num_uninterpreted_sorts()) { + SET_ERROR_CODE(Z3_IOB); + RETURN_Z3(0); + } + sort * s = to_model_ref(m)->get_uninterpreted_sort(i); + RETURN_Z3(of_sort(s)); + Z3_CATCH_RETURN(0); + } + + Z3_ast_vector Z3_API Z3_model_get_sort_universe(Z3_context c, Z3_model m, Z3_sort s) { + Z3_TRY; + LOG_Z3_model_get_sort_universe(c, m, s); + RESET_ERROR_CODE(); + if (!to_model_ref(m)->has_uninterpreted_sort(to_sort(s))) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + ptr_vector const & universe = to_model_ref(m)->get_universe(to_sort(s)); + Z3_ast_vector_ref * v = alloc(Z3_ast_vector_ref, mk_c(c)->m()); + mk_c(c)->save_object(v); + unsigned sz = universe.size(); + for (unsigned i = 0; i < sz; i++) { + v->m_ast_vector.push_back(universe[i]); + } + RETURN_Z3(of_ast_vector(v)); + Z3_CATCH_RETURN(0); + } + + Z3_bool Z3_API Z3_is_as_array(Z3_context c, Z3_ast a) { + Z3_TRY; + LOG_Z3_is_as_array(c, a); + RESET_ERROR_CODE(); + return is_expr(to_ast(a)) && is_app_of(to_expr(a), mk_c(c)->get_array_fid(), OP_AS_ARRAY); + Z3_CATCH_RETURN(Z3_FALSE); + } + + Z3_func_decl Z3_API Z3_get_as_array_func_decl(Z3_context c, Z3_ast a) { + Z3_TRY; + LOG_Z3_get_as_array_func_decl(c, a); + RESET_ERROR_CODE(); + if (is_expr(to_ast(a)) && is_app_of(to_expr(a), mk_c(c)->get_array_fid(), OP_AS_ARRAY)) { + return of_func_decl(to_func_decl(to_app(a)->get_decl()->get_parameter(0).get_ast())); + } + else { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_func_interp_inc_ref(Z3_context c, Z3_func_interp f) { + Z3_TRY; + LOG_Z3_func_interp_inc_ref(c, f); + RESET_ERROR_CODE(); + if (f) { + to_func_interp(f)->inc_ref(); + } + Z3_CATCH; + } + + void Z3_API Z3_func_interp_dec_ref(Z3_context c, Z3_func_interp f) { + Z3_TRY; + LOG_Z3_func_interp_dec_ref(c, f); + RESET_ERROR_CODE(); + if (f) { + to_func_interp(f)->dec_ref(); + } + Z3_CATCH; + } + + unsigned Z3_API Z3_func_interp_get_num_entries(Z3_context c, Z3_func_interp f) { + Z3_TRY; + LOG_Z3_func_interp_get_num_entries(c, f); + RESET_ERROR_CODE(); + CHECK_NON_NULL(f, 0); + return to_func_interp_ref(f)->num_entries(); + Z3_CATCH_RETURN(0); + } + + Z3_func_entry Z3_API Z3_func_interp_get_entry(Z3_context c, Z3_func_interp f, unsigned i) { + Z3_TRY; + LOG_Z3_func_interp_get_entry(c, f, i); + RESET_ERROR_CODE(); + CHECK_NON_NULL(f, 0); + if (i >= to_func_interp_ref(f)->num_entries()) { + SET_ERROR_CODE(Z3_IOB); + RETURN_Z3(0); + } + Z3_func_entry_ref * e = alloc(Z3_func_entry_ref, to_func_interp(f)->m_model.get()); + e->m_func_interp = to_func_interp_ref(f); + e->m_func_entry = to_func_interp_ref(f)->get_entry(i); + mk_c(c)->save_object(e); + RETURN_Z3(of_func_entry(e)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_func_interp_get_else(Z3_context c, Z3_func_interp f) { + Z3_TRY; + LOG_Z3_func_interp_get_else(c, f); + RESET_ERROR_CODE(); + CHECK_NON_NULL(f, 0); + expr * e = to_func_interp_ref(f)->get_else(); + RETURN_Z3(of_expr(e)); + Z3_CATCH_RETURN(0); + } + + unsigned Z3_API Z3_func_interp_get_arity(Z3_context c, Z3_func_interp f) { + Z3_TRY; + LOG_Z3_func_interp_get_arity(c, f); + RESET_ERROR_CODE(); + CHECK_NON_NULL(f, 0); + return to_func_interp_ref(f)->get_arity(); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_func_entry_inc_ref(Z3_context c, Z3_func_entry e) { + Z3_TRY; + LOG_Z3_func_entry_inc_ref(c, e); + RESET_ERROR_CODE(); + if (e) { + to_func_entry(e)->inc_ref(); + } + Z3_CATCH; + } + + void Z3_API Z3_func_entry_dec_ref(Z3_context c, Z3_func_entry e) { + Z3_TRY; + LOG_Z3_func_entry_dec_ref(c, e); + RESET_ERROR_CODE(); + if (e) { + to_func_entry(e)->dec_ref(); + } + Z3_CATCH; + } + + Z3_ast Z3_API Z3_func_entry_get_value(Z3_context c, Z3_func_entry e) { + Z3_TRY; + LOG_Z3_func_entry_get_value(c, e); + RESET_ERROR_CODE(); + expr * v = to_func_entry_ref(e)->get_result(); + RETURN_Z3(of_expr(v)); + Z3_CATCH_RETURN(0); + } + + unsigned Z3_API Z3_func_entry_get_num_args(Z3_context c, Z3_func_entry e) { + Z3_TRY; + LOG_Z3_func_entry_get_num_args(c, e); + RESET_ERROR_CODE(); + return to_func_entry(e)->m_func_interp->get_arity(); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_func_entry_get_arg(Z3_context c, Z3_func_entry e, unsigned i) { + Z3_TRY; + LOG_Z3_func_entry_get_arg(c, e, i); + RESET_ERROR_CODE(); + if (i >= to_func_entry(e)->m_func_interp->get_arity()) { + SET_ERROR_CODE(Z3_IOB); + RETURN_Z3(0); + } + expr * r = to_func_entry(e)->m_func_entry->get_arg(i); + RETURN_Z3(of_expr(r)); + Z3_CATCH_RETURN(0); + } + + // ---------------------------- + // + // DEPRECATED API + // + // ---------------------------- + + void Z3_API Z3_del_model(Z3_context c, Z3_model m) { + Z3_model_dec_ref(c, m); + } + + unsigned Z3_API Z3_get_model_num_constants(Z3_context c, Z3_model m) { + return Z3_model_get_num_consts(c, m); + } + + Z3_func_decl Z3_API Z3_get_model_constant(Z3_context c, Z3_model m, unsigned i) { + return Z3_model_get_const_decl(c, m, i); + } + + unsigned Z3_API Z3_get_model_num_funcs(Z3_context c, Z3_model m) { + return Z3_model_get_num_funcs(c, m); + } + + Z3_func_decl Z3_API Z3_get_model_func_decl(Z3_context c, Z3_model m, unsigned i) { + return Z3_model_get_func_decl(c, m, i); + } + + Z3_ast Z3_API Z3_get_model_func_else(Z3_context c, Z3_model m, unsigned i) { + Z3_TRY; + LOG_Z3_get_model_func_else(c, m, i); + RESET_ERROR_CODE(); + CHECK_NON_NULL(m, 0); + Z3_func_decl d = get_model_func_decl_core(c, m, i); + if (d) { + model * _m = to_model_ref(m); + func_interp * g = _m->get_func_interp(to_func_decl(d)); + if (g) { + expr * e = g->get_else(); + mk_c(c)->save_ast_trail(e); + RETURN_Z3(of_ast(e)); + } + SET_ERROR_CODE(Z3_IOB); + RETURN_Z3(0); + } + RETURN_Z3(0); + Z3_CATCH_RETURN(0); + } + + unsigned get_model_func_num_entries_core(Z3_context c, Z3_model m, unsigned i) { + RESET_ERROR_CODE(); + CHECK_NON_NULL(m, 0); + Z3_func_decl d = get_model_func_decl_core(c, m, i); + if (d) { + model * _m = to_model_ref(m); + func_interp * g = _m->get_func_interp(to_func_decl(d)); + if (g) { + return g->num_entries(); + } + SET_ERROR_CODE(Z3_IOB); + return 0; + } + return 0; + } + + unsigned Z3_API Z3_get_model_func_num_entries(Z3_context c, Z3_model m, unsigned i) { + Z3_TRY; + LOG_Z3_get_model_func_num_entries(c, m, i); + return get_model_func_num_entries_core(c, m, i); + Z3_CATCH_RETURN(0); + } + + unsigned get_model_func_entry_num_args_core(Z3_context c, + Z3_model m, + unsigned i, + unsigned j) { + RESET_ERROR_CODE(); + CHECK_NON_NULL(m, 0); + if (j >= get_model_func_num_entries_core(c, m, i)) { + SET_ERROR_CODE(Z3_IOB); + return 0; + } + Z3_func_decl d = get_model_func_decl_core(c, m, i); + if (d) { + model * _m = to_model_ref(m); + func_interp * g = _m->get_func_interp(to_func_decl(d)); + return g->get_arity(); + } + return 0; + } + + unsigned Z3_API Z3_get_model_func_entry_num_args(Z3_context c, + Z3_model m, + unsigned i, + unsigned j) { + Z3_TRY; + LOG_Z3_get_model_func_entry_num_args(c, m, i, j); + return get_model_func_entry_num_args_core(c, m, i, j); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_get_model_func_entry_arg(Z3_context c, + Z3_model m, + unsigned i, + unsigned j, + unsigned k) { + Z3_TRY; + LOG_Z3_get_model_func_entry_arg(c, m, i, j, k); + RESET_ERROR_CODE(); + CHECK_NON_NULL(m, 0); + if (j >= get_model_func_num_entries_core(c, m, i) || k >= get_model_func_entry_num_args_core(c, m, i, j)) { + SET_ERROR_CODE(Z3_IOB); + RETURN_Z3(0); + } + Z3_func_decl d = get_model_func_decl_core(c, m, i); + if (d) { + model * _m = to_model_ref(m); + func_interp * g = _m->get_func_interp(to_func_decl(d)); + if (g && j < g->num_entries()) { + func_entry const * e = g->get_entry(j); + if (k < g->get_arity()) { + expr * a = e->get_arg(k); + mk_c(c)->save_ast_trail(a); + RETURN_Z3(of_ast(a)); + } + } + SET_ERROR_CODE(Z3_IOB); + RETURN_Z3(0); + } + RETURN_Z3(0); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_get_model_func_entry_value(Z3_context c, + Z3_model m, + unsigned i, + unsigned j) { + Z3_TRY; + LOG_Z3_get_model_func_entry_value(c, m, i, j); + RESET_ERROR_CODE(); + CHECK_NON_NULL(m, 0); + if (j >= get_model_func_num_entries_core(c, m, i)) { + SET_ERROR_CODE(Z3_IOB); + RETURN_Z3(0); + } + Z3_func_decl d = get_model_func_decl_core(c, m, i); + if (d) { + model * _m = to_model_ref(m); + func_interp * g = _m->get_func_interp(to_func_decl(d)); + if (g && j < g->num_entries()) { + func_entry const* e = g->get_entry(j); + expr* a = e->get_result(); + mk_c(c)->save_ast_trail(a); + RETURN_Z3(of_ast(a)); + } + SET_ERROR_CODE(Z3_IOB); + RETURN_Z3(0); + } + RETURN_Z3(0); + Z3_CATCH_RETURN(0); + } + + Z3_bool Z3_API Z3_eval(Z3_context c, + Z3_model m, + Z3_ast t, + Z3_ast * v) { + return Z3_model_eval(c, m, t, mk_c(c)->fparams().m_model_completion, v); + } + + Z3_bool Z3_API Z3_eval_func_decl(Z3_context c, + Z3_model m, + Z3_func_decl decl, + Z3_ast* v) { + Z3_TRY; + LOG_Z3_eval_func_decl(c, m, decl, v); + RESET_ERROR_CODE(); + CHECK_NON_NULL(m, Z3_FALSE); + ast_manager & mgr = mk_c(c)->m(); + model * _m = to_model_ref(m); + expr_ref result(mgr); + if( _m->eval(to_func_decl(decl), result)) { + mk_c(c)->save_ast_trail(result.get()); + *v = of_ast(result.get()); + RETURN_Z3_eval_func_decl Z3_TRUE; + } + else { + return Z3_FALSE; + } + Z3_CATCH_RETURN(Z3_FALSE); + } + + + Z3_bool Z3_API Z3_is_array_value(Z3_context c, Z3_model _m, Z3_ast _v, unsigned* size) { + Z3_TRY; + LOG_Z3_is_array_value(c, _m, _v, size); + RESET_ERROR_CODE(); + CHECK_NON_NULL(_v, Z3_FALSE); + CHECK_NON_NULL(_m, Z3_FALSE); + model * m = to_model_ref(_m); + expr * v = to_expr(_v); + ast_manager& mgr = mk_c(c)->m(); + family_id afid = mk_c(c)->get_array_fid(); + unsigned sz = 0; + array_util pl(mgr); + if (pl.is_as_array(v)) { + func_decl* f = pl.get_as_array_func_decl(to_app(v)); + func_interp* g = m->get_func_interp(f); + sz = g->num_entries(); + if (sz > 0 && g->get_arity() != 1) { + return Z3_FALSE; + } + } + else { + while (pl.is_store(v)) { + if (to_app(v)->get_num_args() != 3) { + return Z3_FALSE; + } + v = to_app(v)->get_arg(0); + ++sz; + } + if (!is_app_of(v, afid, OP_CONST_ARRAY)) { + return Z3_FALSE; + } + } + if (size) { + *size = sz; + } + return Z3_TRUE; + Z3_CATCH_RETURN(Z3_FALSE); + } + + + void Z3_API Z3_get_array_value(Z3_context c, + Z3_model _m, + Z3_ast _v, + unsigned num_entries, + Z3_ast indices[], + Z3_ast values[], + Z3_ast* else_value) { + Z3_TRY; + LOG_Z3_get_array_value(c, _m, _v, num_entries, indices, values, else_value); + RESET_ERROR_CODE(); + CHECK_NON_NULL(_m, ); + model * m = to_model_ref(_m); + + expr* v = to_expr(_v); + family_id afid = mk_c(c)->get_array_fid(); + ast_manager& mgr = mk_c(c)->m(); + array_util pl(mgr); + + // + // note: _v is already reference counted. + // saving the trail for the returned values + // is redundant. + // + unsigned sz = 0; + if (pl.is_as_array(v)) { + func_decl* f = pl.get_as_array_func_decl(to_app(v)); + func_interp* g = m->get_func_interp(f); + sz = g->num_entries(); + if (g->get_arity() != 1) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return; + } + for (unsigned i = 0; i < sz && i < num_entries; ++i) { + indices[i] = of_ast(g->get_entry(i)->get_arg(0)); + values[i] = of_ast(g->get_entry(i)->get_result()); + } + if (else_value) { + *else_value = of_ast(g->get_else()); + } + } + else { + while (sz <= num_entries && is_app_of(v, afid, OP_STORE)) { + app* a = to_app(v); + if (a->get_num_args() != 3) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return; + } + expr* idx = a->get_arg(1); + expr* val = a->get_arg(2); + indices[sz] = of_ast(idx); + values[sz] = of_ast(val); + v = to_app(v)->get_arg(0); + ++sz; + } + + if (is_app_of(v, afid, OP_CONST_ARRAY)) { + if (else_value) { + *else_value = of_ast(to_app(v)->get_arg(0)); + } + } + else { + SET_ERROR_CODE(Z3_INVALID_ARG); + return; + } + } + RETURN_Z3_get_array_value; + Z3_CATCH; + } + + Z3_bool Z3_API Z3_eval_decl(Z3_context c, + Z3_model m, + Z3_func_decl d, + unsigned num_args, + Z3_ast const args[], + Z3_ast* v) { + Z3_TRY; + LOG_Z3_eval_decl(c, m, d, num_args, args, v); + RESET_ERROR_CODE(); + CHECK_NON_NULL(m, Z3_FALSE); + ast_manager & mgr = mk_c(c)->m(); + model * _m = to_model_ref(m); + app_ref app(mgr); + app = mgr.mk_app(to_func_decl(d), num_args, to_exprs(args)); + expr_ref result(mgr); + _m->eval(app.get(), result); + mk_c(c)->save_ast_trail(result.get()); + *v = of_ast(result.get()); + RETURN_Z3_eval_decl Z3_TRUE; + Z3_CATCH_RETURN(Z3_FALSE); + } + + char const * Z3_API Z3_model_to_string(Z3_context c, Z3_model m) { + Z3_TRY; + LOG_Z3_model_to_string(c, m); + RESET_ERROR_CODE(); + CHECK_NON_NULL(m, 0); + std::ostringstream buffer; + std::string result; + if (mk_c(c)->get_print_mode() == Z3_PRINT_SMTLIB2_COMPLIANT) { + model_smt2_pp(buffer, mk_c(c)->m(), *(to_model_ref(m)), 0); + // Hack for removing the trailing '\n' + result = buffer.str(); + if (result.size() != 0) + result.resize(result.size()-1); + } + else { + model_v2_pp(buffer, *(to_model_ref(m)), mk_c(c)->fparams().m_model_partial); + result = buffer.str(); + } + return mk_c(c)->mk_external_string(result); + Z3_CATCH_RETURN(0); + } + +}; diff --git a/lib/api_model.h b/lib/api_model.h new file mode 100644 index 000000000..454bd48cb --- /dev/null +++ b/lib/api_model.h @@ -0,0 +1,58 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_model.h + +Abstract: + API for models + +Author: + + Leonardo de Moura (leonardo) 2012-03-08. + +Revision History: + +--*/ +#ifndef _API_MODEL_H_ +#define _API_MODEL_H_ + +#include"api_util.h" +#include"model.h" + +struct Z3_model_ref : public api::object { + model_ref m_model; + Z3_model_ref() {} + virtual ~Z3_model_ref() {} +}; + +inline Z3_model_ref * to_model(Z3_model s) { return reinterpret_cast(s); } +inline Z3_model of_model(Z3_model_ref * s) { return reinterpret_cast(s); } +inline model * to_model_ref(Z3_model s) { return to_model(s)->m_model.get(); } + +struct Z3_func_interp_ref : public api::object { + model_ref m_model; // must have it to prevent reference to m_func_interp to be killed. + func_interp * m_func_interp; + Z3_func_interp_ref(model * m):m_model(m), m_func_interp(0) {} + virtual ~Z3_func_interp_ref() {} +}; + +inline Z3_func_interp_ref * to_func_interp(Z3_func_interp s) { return reinterpret_cast(s); } +inline Z3_func_interp of_func_interp(Z3_func_interp_ref * s) { return reinterpret_cast(s); } +inline func_interp * to_func_interp_ref(Z3_func_interp s) { return to_func_interp(s)->m_func_interp; } + +struct Z3_func_entry_ref : public api::object { + model_ref m_model; // must have it to prevent reference to m_func_entry to be killed. + func_interp * m_func_interp; + func_entry const * m_func_entry; + Z3_func_entry_ref(model * m):m_model(m), m_func_interp(0), m_func_entry(0) {} + virtual ~Z3_func_entry_ref() {} +}; + +inline Z3_func_entry_ref * to_func_entry(Z3_func_entry s) { return reinterpret_cast(s); } +inline Z3_func_entry of_func_entry(Z3_func_entry_ref * s) { return reinterpret_cast(s); } +inline func_entry const * to_func_entry_ref(Z3_func_entry s) { return to_func_entry(s)->m_func_entry; } + + +#endif diff --git a/lib/api_numeral.cpp b/lib/api_numeral.cpp new file mode 100644 index 000000000..2660d9a01 --- /dev/null +++ b/lib/api_numeral.cpp @@ -0,0 +1,340 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_numeral.cpp + +Abstract: + API for handling numerals in Z3 + +Author: + + Leonardo de Moura (leonardo) 2012-02-29. + +Revision History: + +--*/ +#include +#include"z3.h" +#include"api_log_macros.h" +#include"api_context.h" +#include"api_util.h" +#include"arith_decl_plugin.h" +#include"bv_decl_plugin.h" +#include"algebraic_numbers.h" + +extern "C" { + + bool is_numeral_sort(Z3_context c, Z3_sort ty) { + sort * _ty = to_sort(ty); + family_id fid = _ty->get_family_id(); + if (fid != mk_c(c)->get_arith_fid() && + fid != mk_c(c)->get_bv_fid() && + fid != mk_c(c)->get_datalog_fid()) { + return false; + } + return true; + } + + bool check_numeral_sort(Z3_context c, Z3_sort ty) { + bool is_num = is_numeral_sort(c, ty); + if (!is_num) { + SET_ERROR_CODE(Z3_INVALID_ARG); + } + return is_num; + } + + Z3_ast Z3_API Z3_mk_numeral(Z3_context c, const char* n, Z3_sort ty) { + Z3_TRY; + LOG_Z3_mk_numeral(c, n, ty); + RESET_ERROR_CODE(); + if (!check_numeral_sort(c, ty)) { + RETURN_Z3(0); + } + if (!n) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + std::string fixed_num; + char const* m = n; + while (*m) { + if (!(('0' <= *m && *m <= '9') || + ('/' == *m) || ('-' == *m) || + (' ' == *m) || ('\n' == *m) || + ('.' == *m) || ('e' == *m) || + ('E' == *m))) { + SET_ERROR_CODE(Z3_PARSER_ERROR); + return 0; + } + ++m; + } + ast * a = mk_c(c)->mk_numeral_core(rational(n), to_sort(ty)); + RETURN_Z3(of_ast(a)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_int(Z3_context c, int value, Z3_sort ty) { + Z3_TRY; + LOG_Z3_mk_int(c, value, ty); + RESET_ERROR_CODE(); + if (!check_numeral_sort(c, ty)) { + RETURN_Z3(0); + } + ast * a = mk_c(c)->mk_numeral_core(rational(value), to_sort(ty)); + RETURN_Z3(of_ast(a)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_unsigned_int(Z3_context c, unsigned value, Z3_sort ty) { + Z3_TRY; + LOG_Z3_mk_unsigned_int(c, value, ty); + RESET_ERROR_CODE(); + if (!check_numeral_sort(c, ty)) { + RETURN_Z3(0); + } + ast * a = mk_c(c)->mk_numeral_core(rational(value), to_sort(ty)); + RETURN_Z3(of_ast(a)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_int64(Z3_context c, long long value, Z3_sort ty) { + Z3_TRY; + LOG_Z3_mk_int64(c, value, ty); + RESET_ERROR_CODE(); + if (!check_numeral_sort(c, ty)) { + RETURN_Z3(0); + } + rational n(value, rational::i64()); + ast* a = mk_c(c)->mk_numeral_core(n, to_sort(ty)); + RETURN_Z3(of_ast(a)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_unsigned_int64(Z3_context c, unsigned long long value, Z3_sort ty) { + Z3_TRY; + LOG_Z3_mk_unsigned_int64(c, value, ty); + RESET_ERROR_CODE(); + if (!check_numeral_sort(c, ty)) { + RETURN_Z3(0); + } + rational n(value, rational::ui64()); + ast * a = mk_c(c)->mk_numeral_core(n, to_sort(ty)); + RETURN_Z3(of_ast(a)); + Z3_CATCH_RETURN(0); + } + + Z3_bool Z3_API Z3_is_numeral_ast(Z3_context c, Z3_ast a) { + Z3_TRY; + LOG_Z3_is_numeral_ast(c, a); + RESET_ERROR_CODE(); + expr* e = to_expr(a); + return + mk_c(c)->autil().is_numeral(e) || + mk_c(c)->bvutil().is_numeral(e); + Z3_CATCH_RETURN(Z3_FALSE); + } + + Z3_bool Z3_API Z3_get_numeral_rational(Z3_context c, Z3_ast a, rational& r) { + Z3_TRY; + // This function is not part of the public API + RESET_ERROR_CODE(); + expr* e = to_expr(a); + if (!e) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return Z3_FALSE; + } + if (mk_c(c)->autil().is_numeral(e, r)) { + return Z3_TRUE; + } + unsigned bv_size; + if (mk_c(c)->bvutil().is_numeral(e, r, bv_size)) { + return Z3_TRUE; + } + uint64 v; + if (mk_c(c)->datalog_util().is_numeral(e, v)) { + r = rational(v, rational::ui64()); + return Z3_TRUE; + } + return Z3_FALSE; + Z3_CATCH_RETURN(Z3_FALSE); + } + + + Z3_string Z3_API Z3_get_numeral_string(Z3_context c, Z3_ast a) { + Z3_TRY; + // This function invokes Z3_get_numeral_rational, but it is still ok to add LOG command here because it does not return a Z3 object. + LOG_Z3_get_numeral_string(c, a); + RESET_ERROR_CODE(); + rational r; + Z3_bool ok = Z3_get_numeral_rational(c, a, r); + if (ok == Z3_TRUE) { + return mk_c(c)->mk_external_string(r.to_string()); + } + else { + SET_ERROR_CODE(Z3_INVALID_ARG); + return ""; + } + Z3_CATCH_RETURN(""); + } + + Z3_string Z3_API Z3_get_numeral_decimal_string(Z3_context c, Z3_ast a, unsigned precision) { + Z3_TRY; + LOG_Z3_get_numeral_decimal_string(c, a, precision); + RESET_ERROR_CODE(); + expr* e = to_expr(a); + if (!e) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return ""; + } + rational r; + arith_util & u = mk_c(c)->autil(); + if (u.is_numeral(e, r) && !r.is_int()) { + std::ostringstream buffer; + r.display_decimal(buffer, precision); + return mk_c(c)->mk_external_string(buffer.str()); + } + if (u.is_irrational_algebraic_numeral(e)) { + algebraic_numbers::anum const & n = u.to_irrational_algebraic_numeral(e); + algebraic_numbers::manager & am = u.am(); + std::ostringstream buffer; + am.display_decimal(buffer, n, precision); + return mk_c(c)->mk_external_string(buffer.str()); + } + Z3_bool ok = Z3_get_numeral_rational(c, a, r); + if (ok == Z3_TRUE) { + return mk_c(c)->mk_external_string(r.to_string()); + } + else { + SET_ERROR_CODE(Z3_INVALID_ARG); + return ""; + } + Z3_CATCH_RETURN(""); + } + + Z3_bool Z3_API Z3_get_numeral_small(Z3_context c, Z3_ast a, long long* num, long long* den) { + Z3_TRY; + // This function invokes Z3_get_numeral_rational, but it is still ok to add LOG command here because it does not return a Z3 object. + LOG_Z3_get_numeral_small(c, a, num, den); + RESET_ERROR_CODE(); + rational r; + Z3_bool ok = Z3_get_numeral_rational(c, a, r); + if (ok == Z3_TRUE) { + rational n = numerator(r); + rational d = denominator(r); + if (n.is_int64() && d.is_int64()) { + *num = n.get_int64(); + *den = d.get_int64(); + return Z3_TRUE; + } + else { + return Z3_FALSE; + } + } + SET_ERROR_CODE(Z3_INVALID_ARG); + return Z3_FALSE; + Z3_CATCH_RETURN(Z3_FALSE); + } + + + Z3_bool Z3_API Z3_get_numeral_int(Z3_context c, Z3_ast v, int* i) { + Z3_TRY; + // This function invokes Z3_get_numeral_int64, but it is still ok to add LOG command here because it does not return a Z3 object. + LOG_Z3_get_numeral_int(c, v, i); + RESET_ERROR_CODE(); + if (!i) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return Z3_FALSE; + } + long long l; + if (Z3_get_numeral_int64(c, v, &l) && l >= INT_MIN && l <= INT_MAX) { + *i = static_cast(l); + return Z3_TRUE; + } + return Z3_FALSE; + Z3_CATCH_RETURN(Z3_FALSE); + } + + Z3_bool Z3_API Z3_get_numeral_uint(Z3_context c, Z3_ast v, unsigned* u) { + Z3_TRY; + // This function invokes Z3_get_numeral_uint64, but it is still ok to add LOG command here because it does not return a Z3 object. + LOG_Z3_get_numeral_uint(c, v, u); + RESET_ERROR_CODE(); + if (!u) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return Z3_FALSE; + } + unsigned long long l; + if (Z3_get_numeral_uint64(c, v, &l) && (l <= 0xFFFFFFFF)) { + *u = static_cast(l); + return Z3_TRUE; + } + return Z3_FALSE; + Z3_CATCH_RETURN(Z3_FALSE); + } + + Z3_bool Z3_API Z3_get_numeral_uint64(Z3_context c, Z3_ast v, unsigned long long* u) { + Z3_TRY; + // This function invokes Z3_get_numeral_rational, but it is still ok to add LOG command here because it does not return a Z3 object. + LOG_Z3_get_numeral_uint64(c, v, u); + RESET_ERROR_CODE(); + if (!u) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return Z3_FALSE; + } + rational r; + Z3_bool ok = Z3_get_numeral_rational(c, v, r); + SASSERT(u); + if (ok == Z3_TRUE && r.is_uint64()) { + *u = r.get_uint64(); + return ok; + } + return Z3_FALSE; + Z3_CATCH_RETURN(Z3_FALSE); + } + + Z3_bool Z3_API Z3_get_numeral_int64(Z3_context c, Z3_ast v, long long* i) { + Z3_TRY; + // This function invokes Z3_get_numeral_rational, but it is still ok to add LOG command here because it does not return a Z3 object. + LOG_Z3_get_numeral_int64(c, v, i); + RESET_ERROR_CODE(); + if (!i) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return Z3_FALSE; + } + rational r; + Z3_bool ok = Z3_get_numeral_rational(c, v, r); + if (ok == Z3_TRUE && r.is_int64()) { + *i = r.get_int64(); + return ok; + } + return Z3_FALSE; + Z3_CATCH_RETURN(Z3_FALSE); + } + + Z3_bool Z3_API Z3_get_numeral_rational_int64(Z3_context c, Z3_ast v, long long* num, long long* den) { + Z3_TRY; + // This function invokes Z3_get_numeral_rational, but it is still ok to add LOG command here because it does not return a Z3 object. + LOG_Z3_get_numeral_rational_int64(c, v, num, den); + RESET_ERROR_CODE(); + if (!num || !den) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return Z3_FALSE; + } + rational r; + Z3_bool ok = Z3_get_numeral_rational(c, v, r); + if (ok != Z3_TRUE) { + return ok; + } + rational n = numerator(r); + rational d = denominator(r); + if (n.is_int64() && d.is_int64()) { + *num = n.get_int64(); + *den = d.get_int64(); + return ok; + } + return Z3_FALSE; + Z3_CATCH_RETURN(Z3_FALSE); + } + +}; diff --git a/lib/api_params.cpp b/lib/api_params.cpp new file mode 100644 index 000000000..2badef8d6 --- /dev/null +++ b/lib/api_params.cpp @@ -0,0 +1,182 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_params.cpp + +Abstract: + API for creating parameter sets. + + This is essentially a wrapper for params_ref. + +Author: + + Leonardo de Moura (leonardo) 2012-03-05. + +Revision History: + +--*/ +#include +#include"z3.h" +#include"api_log_macros.h" +#include"api_context.h" +#include"api_util.h" +#include"params.h" + +extern "C" { + + Z3_params Z3_API Z3_mk_params(Z3_context c) { + Z3_TRY; + LOG_Z3_mk_params(c); + RESET_ERROR_CODE(); + Z3_params_ref * p = alloc(Z3_params_ref); + mk_c(c)->save_object(p); + Z3_params r = of_params(p); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + /** + \brief Increment the reference counter of the given parameter set. + */ + void Z3_API Z3_params_inc_ref(Z3_context c, Z3_params p) { + Z3_TRY; + LOG_Z3_params_inc_ref(c, p); + RESET_ERROR_CODE(); + to_params(p)->inc_ref(); + Z3_CATCH; + } + + /** + \brief Decrement the reference counter of the given parameter set. + */ + void Z3_API Z3_params_dec_ref(Z3_context c, Z3_params p) { + Z3_TRY; + LOG_Z3_params_dec_ref(c, p); + RESET_ERROR_CODE(); + to_params(p)->dec_ref(); + Z3_CATCH; + } + + /** + \brief Add a Boolean parameter \c k with value \c v to the parameter set \c p. + */ + void Z3_API Z3_params_set_bool(Z3_context c, Z3_params p, Z3_symbol k, Z3_bool v) { + Z3_TRY; + LOG_Z3_params_set_bool(c, p, k, v); + RESET_ERROR_CODE(); + to_params(p)->m_params.set_bool(to_symbol(k), v != 0); + Z3_CATCH; + } + + /** + \brief Add a unsigned parameter \c k with value \c v to the parameter set \c p. + */ + void Z3_API Z3_params_set_uint(Z3_context c, Z3_params p, Z3_symbol k, unsigned v) { + Z3_TRY; + LOG_Z3_params_set_uint(c, p, k, v); + RESET_ERROR_CODE(); + to_params(p)->m_params.set_uint(to_symbol(k), v); + Z3_CATCH; + } + + /** + \brief Add a double parameter \c k with value \c v to the parameter set \c p. + */ + void Z3_API Z3_params_set_double(Z3_context c, Z3_params p, Z3_symbol k, double v) { + Z3_TRY; + LOG_Z3_params_set_double(c, p, k, v); + RESET_ERROR_CODE(); + to_params(p)->m_params.set_double(to_symbol(k), v); + Z3_CATCH; + } + + /** + \brief Add a symbol parameter \c k with value \c v to the parameter set \c p. + */ + void Z3_API Z3_params_set_symbol(Z3_context c, Z3_params p, Z3_symbol k, Z3_symbol v) { + Z3_TRY; + LOG_Z3_params_set_symbol(c, p, k, v); + RESET_ERROR_CODE(); + to_params(p)->m_params.set_sym(to_symbol(k), to_symbol(v)); + Z3_CATCH; + } + + /** + \brief Convert a parameter set into a string. This function is mainly used for printing the + contents of a parameter set. + */ + Z3_string Z3_API Z3_params_to_string(Z3_context c, Z3_params p) { + Z3_TRY; + LOG_Z3_params_to_string(c, p); + RESET_ERROR_CODE(); + std::ostringstream buffer; + to_params(p)->m_params.display(buffer); + return mk_c(c)->mk_external_string(buffer.str()); + Z3_CATCH_RETURN(""); + } + + void Z3_API Z3_params_validate(Z3_context c, Z3_params p, Z3_param_descrs d) { + Z3_TRY; + LOG_Z3_params_validate(c, p, d); + RESET_ERROR_CODE(); + to_params(p)->m_params.validate(*to_param_descrs_ptr(d)); + Z3_CATCH; + } + + void Z3_API Z3_param_descrs_inc_ref(Z3_context c, Z3_param_descrs p) { + Z3_TRY; + LOG_Z3_param_descrs_inc_ref(c, p); + RESET_ERROR_CODE(); + to_param_descrs(p)->inc_ref(); + Z3_CATCH; + } + + void Z3_API Z3_param_descrs_dec_ref(Z3_context c, Z3_param_descrs p) { + Z3_TRY; + LOG_Z3_param_descrs_dec_ref(c, p); + RESET_ERROR_CODE(); + to_param_descrs(p)->dec_ref(); + Z3_CATCH; + } + + Z3_param_kind Z3_API Z3_param_descrs_get_kind(Z3_context c, Z3_param_descrs p, Z3_symbol n) { + Z3_TRY; + LOG_Z3_param_descrs_get_kind(c, p, n); + RESET_ERROR_CODE(); + param_kind k = to_param_descrs_ptr(p)->get_kind(to_symbol(n)); + switch (k) { + case CPK_UINT: return Z3_PK_UINT; + case CPK_BOOL: return Z3_PK_BOOL; + case CPK_DOUBLE: return Z3_PK_DOUBLE; + case CPK_STRING: return Z3_PK_STRING; + case CPK_SYMBOL: return Z3_PK_SYMBOL; + case CPK_INVALID: return Z3_PK_INVALID; + default: return Z3_PK_OTHER; + } + Z3_CATCH_RETURN(Z3_PK_INVALID); + } + + unsigned Z3_API Z3_param_descrs_size(Z3_context c, Z3_param_descrs p) { + Z3_TRY; + LOG_Z3_param_descrs_size(c, p); + RESET_ERROR_CODE(); + return to_param_descrs_ptr(p)->size(); + Z3_CATCH_RETURN(0); + } + + Z3_symbol Z3_API Z3_param_descrs_get_name(Z3_context c, Z3_param_descrs p, unsigned i) { + Z3_TRY; + LOG_Z3_param_descrs_get_name(c, p, i); + RESET_ERROR_CODE(); + if (i >= to_param_descrs_ptr(p)->size()) { + SET_ERROR_CODE(Z3_IOB); + RETURN_Z3(0); + } + Z3_symbol result = of_symbol(to_param_descrs_ptr(p)->get_param_name(i)); + return result; + Z3_CATCH_RETURN(0); + } + +}; diff --git a/lib/api_parsers.cpp b/lib/api_parsers.cpp new file mode 100644 index 000000000..d74ade1a7 --- /dev/null +++ b/lib/api_parsers.cpp @@ -0,0 +1,426 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_parsers.cpp + +Abstract: + API for parsing different formats + +Author: + + Leonardo de Moura (leonardo) 2012-02-29. + +Revision History: + +--*/ +#include +#include"z3.h" +#include"api_log_macros.h" +#include"api_context.h" +#include"api_util.h" +#include"cmd_context.h" +#include"smt2parser.h" +#include"smtparser.h" +#include"z3_solver.h" + +extern "C" { + + void init_smtlib_parser(Z3_context c, + unsigned num_sorts, + Z3_symbol const sort_names[], + Z3_sort const types[], + unsigned num_decls, + Z3_symbol const decl_names[], + Z3_func_decl const decls[]) { + mk_c(c)->reset_parser(); + mk_c(c)->m_smtlib_parser = smtlib::parser::create(mk_c(c)->m()); + mk_c(c)->m_smtlib_parser->initialize_smtlib(); + smtlib::symtable * table = mk_c(c)->m_smtlib_parser->get_benchmark()->get_symtable(); + for (unsigned i = 0; i < num_sorts; i++) { + table->insert(to_symbol(sort_names[i]), to_sort(types[i])); + } + for (unsigned i = 0; i < num_decls; i++) { + table->insert(to_symbol(decl_names[i]), to_func_decl(decls[i])); + } + } + + void Z3_API Z3_parse_smtlib_string(Z3_context c, + const char * str, + unsigned num_sorts, + Z3_symbol const sort_names[], + Z3_sort const sorts[], + unsigned num_decls, + Z3_symbol const decl_names[], + Z3_func_decl const decls[]) { + Z3_TRY; + LOG_Z3_parse_smtlib_string(c, str, num_sorts, sort_names, sorts, num_decls, decl_names, decls); + std::ostringstream outs; + bool ok = false; + + RESET_ERROR_CODE(); + init_smtlib_parser(c, num_sorts, sort_names, sorts, num_decls, decl_names, decls); + mk_c(c)->m_smtlib_parser->set_error_stream(outs); + try { + ok = mk_c(c)->m_smtlib_parser->parse_string(str); + } + catch (...) { + ok = false; + } + mk_c(c)->m_smtlib_error_buffer = outs.str(); + if (!ok) { + mk_c(c)->reset_parser(); + SET_ERROR_CODE(Z3_PARSER_ERROR); + } + Z3_CATCH; + } + + void Z3_API Z3_parse_smtlib_file(Z3_context c, + const char * file_name, + unsigned num_sorts, + Z3_symbol const sort_names[], + Z3_sort const types[], + unsigned num_decls, + Z3_symbol const decl_names[], + Z3_func_decl const decls[]) { + Z3_TRY; + LOG_Z3_parse_smtlib_file(c, file_name, num_sorts, sort_names, types, num_decls, decl_names, decls); + bool ok = false; + RESET_ERROR_CODE(); + std::ostringstream outs; + init_smtlib_parser(c, num_sorts, sort_names, types, num_decls, decl_names, decls); + mk_c(c)->m_smtlib_parser->set_error_stream(outs); + try { + ok = mk_c(c)->m_smtlib_parser->parse_file(file_name); + } + catch(...) { + ok = false; + } + mk_c(c)->m_smtlib_error_buffer = outs.str(); + if (!ok) { + mk_c(c)->reset_parser(); + SET_ERROR_CODE(Z3_PARSER_ERROR); + } + Z3_CATCH; + } + + unsigned Z3_API Z3_get_smtlib_num_formulas(Z3_context c) { + Z3_TRY; + LOG_Z3_get_smtlib_num_formulas(c); + RESET_ERROR_CODE(); + if (mk_c(c)->m_smtlib_parser) { + return mk_c(c)->m_smtlib_parser->get_benchmark()->get_num_formulas(); + } + SET_ERROR_CODE(Z3_NO_PARSER); + return 0; + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_get_smtlib_formula(Z3_context c, unsigned i) { + Z3_TRY; + LOG_Z3_get_smtlib_formula(c, i); + RESET_ERROR_CODE(); + if (mk_c(c)->m_smtlib_parser) { + if (i < mk_c(c)->m_smtlib_parser->get_benchmark()->get_num_formulas()) { + ast * f = mk_c(c)->m_smtlib_parser->get_benchmark()->begin_formulas()[i]; + mk_c(c)->save_ast_trail(f); + RETURN_Z3(of_ast(f)); + } + else { + SET_ERROR_CODE(Z3_IOB); + } + } + else { + SET_ERROR_CODE(Z3_NO_PARSER); + } + RETURN_Z3(0); + Z3_CATCH_RETURN(0); + } + + unsigned Z3_API Z3_get_smtlib_num_assumptions(Z3_context c) { + Z3_TRY; + LOG_Z3_get_smtlib_num_assumptions(c); + RESET_ERROR_CODE(); + if (mk_c(c)->m_smtlib_parser) { + return mk_c(c)->m_smtlib_parser->get_benchmark()->get_num_axioms(); + } + SET_ERROR_CODE(Z3_NO_PARSER); + return 0; + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_get_smtlib_assumption(Z3_context c, unsigned i) { + Z3_TRY; + LOG_Z3_get_smtlib_assumption(c, i); + RESET_ERROR_CODE(); + if (mk_c(c)->m_smtlib_parser) { + if (i < mk_c(c)->m_smtlib_parser->get_benchmark()->get_num_axioms()) { + ast * a = mk_c(c)->m_smtlib_parser->get_benchmark()->begin_axioms()[i]; + mk_c(c)->save_ast_trail(a); + RETURN_Z3(of_ast(a)); + } + else { + SET_ERROR_CODE(Z3_IOB); + } + } + else { + SET_ERROR_CODE(Z3_NO_PARSER); + } + RETURN_Z3(0); + Z3_CATCH_RETURN(0); + } + + unsigned Z3_API Z3_get_smtlib_num_decls(Z3_context c) { + Z3_TRY; + LOG_Z3_get_smtlib_num_decls(c); + RESET_ERROR_CODE(); + if (mk_c(c)->m_smtlib_parser) { + mk_c(c)->extract_smtlib_parser_decls(); + return mk_c(c)->m_smtlib_parser_decls.size(); + } + SET_ERROR_CODE(Z3_NO_PARSER); + return 0; + Z3_CATCH_RETURN(0); + } + + Z3_func_decl Z3_API Z3_get_smtlib_decl(Z3_context c, unsigned i) { + Z3_TRY; + LOG_Z3_get_smtlib_decl(c, i); + RESET_ERROR_CODE(); + mk_c(c)->extract_smtlib_parser_decls(); + if (mk_c(c)->m_smtlib_parser) { + if (i < mk_c(c)->m_smtlib_parser_decls.size()) { + func_decl * d = mk_c(c)->m_smtlib_parser_decls[i]; + mk_c(c)->save_ast_trail(d); + RETURN_Z3(of_func_decl(d)); + } + else { + SET_ERROR_CODE(Z3_IOB); + } + } + else { + SET_ERROR_CODE(Z3_NO_PARSER); + } + RETURN_Z3(0); + Z3_CATCH_RETURN(0); + } + + unsigned Z3_API Z3_get_smtlib_num_sorts(Z3_context c) { + Z3_TRY; + LOG_Z3_get_smtlib_num_sorts(c); + RESET_ERROR_CODE(); + if (mk_c(c)->m_smtlib_parser) { + mk_c(c)->extract_smtlib_parser_decls(); + return mk_c(c)->m_smtlib_parser_sorts.size(); + } + SET_ERROR_CODE(Z3_NO_PARSER); + return 0; + Z3_CATCH_RETURN(0); + } + + Z3_sort Z3_API Z3_get_smtlib_sort(Z3_context c, unsigned i) { + Z3_TRY; + LOG_Z3_get_smtlib_sort(c, i); + RESET_ERROR_CODE(); + if (mk_c(c)->m_smtlib_parser) { + mk_c(c)->extract_smtlib_parser_decls(); + if (i < mk_c(c)->m_smtlib_parser_sorts.size()) { + sort* s = mk_c(c)->m_smtlib_parser_sorts[i]; + mk_c(c)->save_ast_trail(s); + RETURN_Z3(of_sort(s)); + } + else { + SET_ERROR_CODE(Z3_IOB); + } + } + else { + SET_ERROR_CODE(Z3_NO_PARSER); + } + RETURN_Z3(0); + Z3_CATCH_RETURN(0); + } + + Z3_string Z3_API Z3_get_smtlib_error(Z3_context c) { + Z3_TRY; + LOG_Z3_get_smtlib_error(c); + RESET_ERROR_CODE(); + return mk_c(c)->m_smtlib_error_buffer.c_str(); + Z3_CATCH_RETURN(""); + } + + Z3_ast parse_z3_stream(Z3_context c, std::istream& is) { + z3_solver parser(c, is, verbose_stream(), mk_c(c)->fparams(), false); + if (!parser.parse()) { + SET_ERROR_CODE(Z3_PARSER_ERROR); + return of_ast(mk_c(c)->m().mk_true()); + } + expr_ref_vector assumptions(mk_c(c)->m()); + parser.get_assumptions(assumptions); + return of_ast(mk_c(c)->mk_and(assumptions.size(), assumptions.c_ptr())); + } + + Z3_ast Z3_API Z3_parse_z3_string(Z3_context c, Z3_string str) { + Z3_TRY; + LOG_Z3_parse_z3_string(c, str); + std::string s(str); + std::istringstream is(s); + Z3_ast r = parse_z3_stream(c, is); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_parse_z3_file(Z3_context c, Z3_string file_name) { + Z3_TRY; + LOG_Z3_parse_z3_file(c, file_name); + std::ifstream is(file_name); + if (!is) { + SET_ERROR_CODE(Z3_PARSER_ERROR); + return 0; + } + Z3_ast r = parse_z3_stream(c, is); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + // --------------- + // Support for SMTLIB2 + + class z3_context_solver : public solver { + api::context & m_ctx; + smt::solver & ctx() const { return m_ctx.get_solver(); } + public: + virtual ~z3_context_solver() {} + z3_context_solver(api::context& c) : m_ctx(c) {} + virtual void init(ast_manager & m, symbol const & logic) {} + virtual void collect_statistics(statistics & st) const {} + virtual void reset() { ctx().reset(); } + virtual void assert_expr(expr * t) { ctx().assert_expr(t); } + virtual void push() { ctx().push(); } + virtual void pop(unsigned n) { ctx().pop(n); } + virtual unsigned get_scope_level() const { return ctx().get_scope_level(); } + virtual lbool check_sat(unsigned num_assumptions, expr * const * assumptions) { + return ctx().check(num_assumptions, assumptions); + } + virtual void get_unsat_core(ptr_vector & r) { + unsigned sz = ctx().get_unsat_core_size(); + for (unsigned i = 0; i < sz; i++) + r.push_back(ctx().get_unsat_core_expr(i)); + } + virtual void get_model(model_ref & m) { ctx().get_model(m); } + virtual proof * get_proof() { return ctx().get_proof(); } + virtual std::string reason_unknown() const { return ctx().last_failure_as_string(); } + virtual void get_labels(svector & r) { + buffer tmp; + ctx().get_relevant_labels(0, tmp); + r.append(tmp.size(), tmp.c_ptr()); + } + + // These are controlled by the main API + virtual void set_cancel(bool f) { } + void cancel() { set_cancel(true); } + void reset_cancel() { set_cancel(false); } + virtual void set_progress_callback(progress_callback * callback) {} + }; + + Z3_ast parse_smtlib2_stream(bool exec, Z3_context c, std::istream& is, + unsigned num_sorts, + Z3_symbol const sort_names[], + Z3_sort const sorts[], + unsigned num_decls, + Z3_symbol const decl_names[], + Z3_func_decl const decls[]) { + Z3_TRY; + cmd_context ctx(mk_c(c)->fparams(), false, &(mk_c(c)->m())); + ctx.set_ignore_check(true); + if (exec) { + ctx.set_solver(alloc(z3_context_solver, *mk_c(c))); + } + for (unsigned i = 0; i < num_decls; ++i) { + ctx.insert(to_symbol(decl_names[i]), to_func_decl(decls[i])); + } + for (unsigned i = 0; i < num_sorts; ++i) { + psort* ps = ctx.pm().mk_psort_cnst(to_sort(sorts[i])); + ctx.insert(ctx.pm().mk_psort_user_decl(0, to_symbol(sort_names[i]), ps)); + } + if (!parse_smt2_commands(ctx, is)) { + SET_ERROR_CODE(Z3_PARSER_ERROR); + return of_ast(mk_c(c)->m().mk_true()); + } + ptr_vector::const_iterator it = ctx.begin_assertions(); + ptr_vector::const_iterator end = ctx.end_assertions(); + unsigned size = static_cast(end - it); + return of_ast(mk_c(c)->mk_and(size, it)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_parse_smtlib2_string(Z3_context c, Z3_string str, + unsigned num_sorts, + Z3_symbol const sort_names[], + Z3_sort const sorts[], + unsigned num_decls, + Z3_symbol const decl_names[], + Z3_func_decl const decls[]) { + Z3_TRY; + LOG_Z3_parse_smtlib2_string(c, str, num_sorts, sort_names, sorts, num_decls, decl_names, decls); + std::string s(str); + std::istringstream is(s); + Z3_ast r = parse_smtlib2_stream(false, c, is, num_sorts, sort_names, sorts, num_decls, decl_names, decls); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_parse_smtlib2_file(Z3_context c, Z3_string file_name, + unsigned num_sorts, + Z3_symbol const sort_names[], + Z3_sort const sorts[], + unsigned num_decls, + Z3_symbol const decl_names[], + Z3_func_decl const decls[]) { + Z3_TRY; + LOG_Z3_parse_smtlib2_string(c, file_name, num_sorts, sort_names, sorts, num_decls, decl_names, decls); + std::ifstream is(file_name); + if (!is) { + SET_ERROR_CODE(Z3_PARSER_ERROR); + return 0; + } + Z3_ast r = parse_smtlib2_stream(false, c, is, num_sorts, sort_names, sorts, num_decls, decl_names, decls); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_exec_smtlib2_string(Z3_context c, Z3_string str, + unsigned num_sorts, + Z3_symbol sort_names[], + Z3_sort sorts[], + unsigned num_decls, + Z3_symbol decl_names[], + Z3_func_decl decls[]) { + Z3_TRY; + cmd_context ctx(mk_c(c)->fparams(), false, &(mk_c(c)->m())); + std::string s(str); + std::istringstream is(s); + // No logging for this one, since it private. + return parse_smtlib2_stream(true, c, is, num_sorts, sort_names, sorts, num_decls, decl_names, decls); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_exec_smtlib2_file(Z3_context c, Z3_string file_name, + unsigned num_sorts, + Z3_symbol sort_names[], + Z3_sort sorts[], + unsigned num_decls, + Z3_symbol decl_names[], + Z3_func_decl decls[]) { + Z3_TRY; + std::ifstream is(file_name); + if (!is) { + SET_ERROR_CODE(Z3_PARSER_ERROR); + return 0; + } + // No logging for this one, since it private. + return parse_smtlib2_stream(true, c, is, num_sorts, sort_names, sorts, num_decls, decl_names, decls); + Z3_CATCH_RETURN(0); + } + +}; diff --git a/lib/api_quant.cpp b/lib/api_quant.cpp new file mode 100644 index 000000000..883e9e752 --- /dev/null +++ b/lib/api_quant.cpp @@ -0,0 +1,511 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_quant.cpp + +Abstract: + API for quantifiers + +Author: + + Leonardo de Moura (leonardo) 2012-02-29. + +Revision History: + +--*/ +#include +#include"z3.h" +#include"api_log_macros.h" +#include"api_context.h" +#include"api_util.h" +#include"pattern_validation.h" +#include"expr_abstract.h" + +extern "C" { + + Z3_ast Z3_API Z3_mk_quantifier( + Z3_context c, + Z3_bool is_forall, + unsigned weight, + unsigned num_patterns, Z3_pattern const patterns[], + unsigned num_decls, Z3_sort const sorts[], + Z3_symbol const decl_names[], + Z3_ast body) + { + return Z3_mk_quantifier_ex( + c, + is_forall, + weight, + 0, + 0, + num_patterns, patterns, + 0, 0, + num_decls, sorts, + decl_names, + body + ); + + } + + Z3_ast mk_quantifier_ex_core( + Z3_context c, + Z3_bool is_forall, + unsigned weight, + Z3_symbol quantifier_id, + Z3_symbol skolem_id, + unsigned num_patterns, Z3_pattern const patterns[], + unsigned num_no_patterns, Z3_ast const no_patterns[], + unsigned num_decls, Z3_sort const sorts[], + Z3_symbol const decl_names[], + Z3_ast body) { + Z3_TRY; + RESET_ERROR_CODE(); + if (!mk_c(c)->m().is_bool(to_expr(body))) { + SET_ERROR_CODE(Z3_SORT_ERROR); + } + if (num_patterns > 0 && num_no_patterns > 0) { + SET_ERROR_CODE(Z3_INVALID_USAGE); + } + expr * const* ps = reinterpret_cast(patterns); + expr * const* no_ps = reinterpret_cast(no_patterns); + pattern_validator v(mk_c(c)->m()); + for (unsigned i = 0; i < num_patterns; i++) { + if (!v(num_decls, ps[i])) { + SET_ERROR_CODE(Z3_INVALID_PATTERN); + return 0; + } + } + + sort* const* ts = reinterpret_cast(sorts); + svector names; + for (unsigned i = 0; i < num_decls; ++i) { + names.push_back(to_symbol(decl_names[i])); + } + expr_ref result(mk_c(c)->m()); + if (num_decls > 0) { + result = mk_c(c)->m().mk_quantifier( + (0 != is_forall), + names.size(), ts, names.c_ptr(), to_expr(body), + weight, + to_symbol(quantifier_id), + to_symbol(skolem_id), + num_patterns, ps, + num_no_patterns, no_ps + ); + } + else { + result = to_expr(body); + } + mk_c(c)->save_ast_trail(result.get()); + return of_ast(result.get()); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_quantifier_ex( + Z3_context c, + Z3_bool is_forall, + unsigned weight, + Z3_symbol quantifier_id, + Z3_symbol skolem_id, + unsigned num_patterns, Z3_pattern const patterns[], + unsigned num_no_patterns, Z3_ast const no_patterns[], + unsigned num_decls, Z3_sort const sorts[], + Z3_symbol const decl_names[], + Z3_ast body) + { + LOG_Z3_mk_quantifier_ex(c, is_forall, weight, quantifier_id, skolem_id, num_patterns, patterns, + num_no_patterns, no_patterns, num_decls, sorts, decl_names, body); + Z3_ast r = mk_quantifier_ex_core(c, is_forall, weight, quantifier_id, skolem_id, num_patterns, patterns, + num_no_patterns, no_patterns, num_decls, sorts, decl_names, body); + RETURN_Z3(r); + } + + Z3_ast Z3_API Z3_mk_forall(Z3_context c, + unsigned weight, + unsigned num_patterns, Z3_pattern const patterns[], + unsigned num_decls, Z3_sort const types[], + Z3_symbol const decl_names[], + Z3_ast body) { + return Z3_mk_quantifier(c, 1, weight, num_patterns, patterns, num_decls, types, decl_names, body); + } + + Z3_ast Z3_API Z3_mk_exists(Z3_context c, + unsigned weight, + unsigned num_patterns, Z3_pattern const patterns[], + unsigned num_decls, Z3_sort const types[], + Z3_symbol const decl_names[], + Z3_ast body) { + return Z3_mk_quantifier(c, 0, weight, num_patterns, patterns, num_decls, types, decl_names, body); + } + + + Z3_ast Z3_API Z3_mk_quantifier_const_ex(Z3_context c, + Z3_bool is_forall, + unsigned weight, + Z3_symbol quantifier_id, + Z3_symbol skolem_id, + unsigned num_bound, + Z3_app const bound[], + unsigned num_patterns, + Z3_pattern const patterns[], + unsigned num_no_patterns, + Z3_ast const no_patterns[], + Z3_ast body) { + Z3_TRY; + LOG_Z3_mk_quantifier_const_ex(c, is_forall, weight, quantifier_id, skolem_id, num_bound, bound, num_patterns, patterns, + num_no_patterns, no_patterns, body); + RESET_ERROR_CODE(); + svector names; + svector types; + ptr_vector bound_asts; + if (num_patterns > 0 && num_no_patterns > 0) { + SET_ERROR_CODE(Z3_INVALID_USAGE); + } + for (unsigned i = 0; i < num_bound; ++i) { + app* a = to_app(bound[i]); + SASSERT(a->get_kind() == AST_APP); + symbol s(to_app(a)->get_decl()->get_name()); + names.push_back(of_symbol(s)); + types.push_back(of_sort(mk_c(c)->m().get_sort(a))); + bound_asts.push_back(a); + if (a->get_family_id() != null_family_id || a->get_num_args() != 0) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + } + // Abstract patterns + svector _patterns; + expr_ref_vector pinned(mk_c(c)->m()); + for (unsigned i = 0; i < num_patterns; ++i) { + expr_ref result(mk_c(c)->m()); + app* pat = to_pattern(patterns[i]); + SASSERT(mk_c(c)->m().is_pattern(pat)); + expr_abstract(mk_c(c)->m(), 0, num_bound, bound_asts.c_ptr(), pat, result); + SASSERT(result.get()->get_kind() == AST_APP); + pinned.push_back(result.get()); + SASSERT(mk_c(c)->m().is_pattern(result.get())); + _patterns.push_back(of_pattern(result.get())); + } + svector _no_patterns; + for (unsigned i = 0; i < num_no_patterns; ++i) { + expr_ref result(mk_c(c)->m()); + if (!is_app(to_expr(no_patterns[i]))) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + app* pat = to_app(to_expr(no_patterns[i])); + expr_abstract(mk_c(c)->m(), 0, num_bound, bound_asts.c_ptr(), pat, result); + SASSERT(result.get()->get_kind() == AST_APP); + pinned.push_back(result.get()); + _no_patterns.push_back(of_ast(result.get())); + } + expr_ref abs_body(mk_c(c)->m()); + expr_abstract(mk_c(c)->m(), 0, num_bound, bound_asts.c_ptr(), to_expr(body), abs_body); + + Z3_ast result = mk_quantifier_ex_core(c, is_forall, weight, + quantifier_id, + skolem_id, + num_patterns, _patterns.c_ptr(), + num_no_patterns, _no_patterns.c_ptr(), + names.size(), types.c_ptr(), names.c_ptr(), + of_ast(abs_body.get())); + RETURN_Z3(result); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_quantifier_const(Z3_context c, + Z3_bool is_forall, + unsigned weight, + unsigned num_bound, + Z3_app const bound[], + unsigned num_patterns, + Z3_pattern const patterns[], + Z3_ast body) { + return Z3_mk_quantifier_const_ex(c, is_forall, weight, 0, 0, + num_bound, bound, + num_patterns, patterns, + 0, 0, + body); + } + + Z3_ast Z3_API Z3_mk_forall_const(Z3_context c, + unsigned weight, + unsigned num_bound, + Z3_app const bound[], + unsigned num_patterns, + Z3_pattern const patterns[], + Z3_ast body) { + return Z3_mk_quantifier_const(c, true, weight, num_bound, bound, num_patterns, patterns, body); + } + + Z3_ast Z3_API Z3_mk_exists_const(Z3_context c, + unsigned weight, + unsigned num_bound, + Z3_app const bound[], + unsigned num_patterns, + Z3_pattern const patterns[], + Z3_ast body) { + return Z3_mk_quantifier_const(c, false, weight, num_bound, bound, num_patterns, patterns, body); + } + + Z3_pattern Z3_API Z3_mk_pattern(Z3_context c, unsigned num_patterns, Z3_ast const terms[]) { + Z3_TRY; + LOG_Z3_mk_pattern(c, num_patterns, terms); + RESET_ERROR_CODE(); + for (unsigned i = 0; i < num_patterns; ++i) { + if (!is_app(to_expr(terms[i]))) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + } + app* a = mk_c(c)->m().mk_pattern(num_patterns, reinterpret_cast(to_exprs(terms))); + mk_c(c)->save_ast_trail(a); + RETURN_Z3(of_pattern(a)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_mk_bound(Z3_context c, unsigned index, Z3_sort ty) { + Z3_TRY; + LOG_Z3_mk_bound(c, index, ty); + RESET_ERROR_CODE(); + ast* a = mk_c(c)->m().mk_var(index, to_sort(ty)); + mk_c(c)->save_ast_trail(a); + RETURN_Z3(of_ast(a)); + Z3_CATCH_RETURN(0); + } + + Z3_bool Z3_API Z3_is_quantifier_forall(Z3_context c, Z3_ast a) { + Z3_TRY; + LOG_Z3_is_quantifier_forall(c, a); + RESET_ERROR_CODE(); + ast * _a = to_ast(a); + if (_a->get_kind() == AST_QUANTIFIER) { + return to_quantifier(_a)->is_forall(); + } + else { + SET_ERROR_CODE(Z3_SORT_ERROR); + return Z3_FALSE; + } + Z3_CATCH_RETURN(Z3_FALSE); + } + + unsigned Z3_API Z3_get_quantifier_weight(Z3_context c, Z3_ast a) { + Z3_TRY; + LOG_Z3_get_quantifier_weight(c, a); + RESET_ERROR_CODE(); + ast * _a = to_ast(a); + if (_a->get_kind() == AST_QUANTIFIER) { + return to_quantifier(_a)->get_weight(); + } + else { + SET_ERROR_CODE(Z3_SORT_ERROR); + return 0; + } + Z3_CATCH_RETURN(0); + } + + unsigned Z3_API Z3_get_quantifier_num_patterns(Z3_context c, Z3_ast a) { + Z3_TRY; + LOG_Z3_get_quantifier_num_patterns(c, a); + RESET_ERROR_CODE(); + ast * _a = to_ast(a); + if (_a->get_kind() == AST_QUANTIFIER) { + return to_quantifier(_a)->get_num_patterns(); + } + else { + SET_ERROR_CODE(Z3_SORT_ERROR); + return 0; + } + Z3_CATCH_RETURN(0); + } + + Z3_pattern Z3_API Z3_get_quantifier_pattern_ast(Z3_context c, Z3_ast a, unsigned i) { + Z3_TRY; + LOG_Z3_get_quantifier_pattern_ast(c, a, i); + RESET_ERROR_CODE(); + ast * _a = to_ast(a); + if (_a->get_kind() == AST_QUANTIFIER) { + Z3_pattern r = of_pattern(to_quantifier(_a)->get_patterns()[i]); + RETURN_Z3(r); + } + else { + SET_ERROR_CODE(Z3_SORT_ERROR); + RETURN_Z3(0); + } + Z3_CATCH_RETURN(0); + } + + + unsigned Z3_API Z3_get_quantifier_num_no_patterns(Z3_context c, Z3_ast a) { + Z3_TRY; + LOG_Z3_get_quantifier_num_no_patterns(c, a); + RESET_ERROR_CODE(); + ast * _a = to_ast(a); + if (_a->get_kind() == AST_QUANTIFIER) { + return to_quantifier(_a)->get_num_no_patterns(); + } + else { + SET_ERROR_CODE(Z3_SORT_ERROR); + return 0; + } + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_get_quantifier_no_pattern_ast(Z3_context c, Z3_ast a, unsigned i) { + Z3_TRY; + LOG_Z3_get_quantifier_no_pattern_ast(c, a, i); + RESET_ERROR_CODE(); + ast * _a = to_ast(a); + if (_a->get_kind() == AST_QUANTIFIER) { + Z3_ast r = of_ast(to_quantifier(_a)->get_no_pattern(i)); + RETURN_Z3(r); + } + else { + SET_ERROR_CODE(Z3_SORT_ERROR); + RETURN_Z3(0); + } + Z3_CATCH_RETURN(0); + } + + Z3_symbol Z3_API Z3_get_quantifier_bound_name(Z3_context c, Z3_ast a, unsigned i) { + Z3_TRY; + LOG_Z3_get_quantifier_bound_name(c, a, i); + RESET_ERROR_CODE(); + ast * _a = to_ast(a); + if (_a->get_kind() == AST_QUANTIFIER) { + return of_symbol(to_quantifier(_a)->get_decl_names()[i]); + } + else { + SET_ERROR_CODE(Z3_SORT_ERROR); + return 0; + } + Z3_CATCH_RETURN(0); + } + + Z3_sort Z3_API Z3_get_quantifier_bound_sort(Z3_context c, Z3_ast a, unsigned i) { + Z3_TRY; + LOG_Z3_get_quantifier_bound_sort(c, a, i); + RESET_ERROR_CODE(); + ast * _a = to_ast(a); + if (_a->get_kind() == AST_QUANTIFIER) { + Z3_sort r = of_sort(to_quantifier(_a)->get_decl_sort(i)); + RETURN_Z3(r); + } + else { + SET_ERROR_CODE(Z3_SORT_ERROR); + RETURN_Z3(0); + } + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_get_quantifier_body(Z3_context c, Z3_ast a) { + Z3_TRY; + LOG_Z3_get_quantifier_body(c, a); + RESET_ERROR_CODE(); + ast * _a = to_ast(a); + if (_a->get_kind() == AST_QUANTIFIER) { + Z3_ast r = of_ast(to_quantifier(_a)->get_expr()); + RETURN_Z3(r); + } + else { + SET_ERROR_CODE(Z3_SORT_ERROR); + RETURN_Z3(0); + } + Z3_CATCH_RETURN(0); + } + + unsigned Z3_API Z3_get_quantifier_num_bound(Z3_context c, Z3_ast a) { + Z3_TRY; + LOG_Z3_get_quantifier_num_bound(c, a); + RESET_ERROR_CODE(); + ast * _a = to_ast(a); + if (_a->get_kind() == AST_QUANTIFIER) { + + return to_quantifier(_a)->get_num_decls(); + } + else { + SET_ERROR_CODE(Z3_SORT_ERROR); + return 0; + } + Z3_CATCH_RETURN(0); + } + + unsigned Z3_API Z3_get_pattern_num_terms(Z3_context c, Z3_pattern p) { + Z3_TRY; + LOG_Z3_get_pattern_num_terms(c, p); + RESET_ERROR_CODE(); + app* _p = to_pattern(p); + if (mk_c(c)->m().is_pattern(_p)) { + return _p->get_num_args(); + } + else { + SET_ERROR_CODE(Z3_SORT_ERROR); + return 0; + } + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_get_pattern(Z3_context c, Z3_pattern p, unsigned idx) { + Z3_TRY; + LOG_Z3_get_pattern(c, p, idx); + RESET_ERROR_CODE(); + app* _p = to_pattern(p); + if (mk_c(c)->m().is_pattern(_p)) { + Z3_ast r = of_ast(_p->get_arg(idx)); + RETURN_Z3(r); + } + else { + SET_ERROR_CODE(Z3_SORT_ERROR); + RETURN_Z3(0); + } + Z3_CATCH_RETURN(0); + } + + Z3_func_decl Z3_API Z3_mk_injective_function(Z3_context c, + Z3_symbol s, + unsigned domain_size, + Z3_sort const domain[], + Z3_sort range) { + Z3_TRY; + LOG_Z3_mk_injective_function(c, s, domain_size, domain, range); + RESET_ERROR_CODE(); + ast_manager & m = mk_c(c)->m(); + mk_c(c)->reset_last_result(); + sort* range_ = to_sort(range); + func_decl* d = m.mk_func_decl(to_symbol(s), domain_size, to_sorts(domain), range_); + expr_ref_vector args(m); + expr_ref fn(m), body(m); + vector names; + for (unsigned i = 0; i < domain_size; ++i) { + unsigned idx = domain_size-i-1; + args.push_back(m.mk_var(idx, to_sort(domain[i]))); + names.push_back(symbol(idx)); + } + fn = m.mk_app(d, args.size(), args.c_ptr()); + + for (unsigned i = 0; i < domain_size; ++i) { + expr* arg = args[i].get(); + sort* dom = m.get_sort(arg); + func_decl* inv = m.mk_fresh_func_decl(symbol("inv"), to_symbol(s), 1, &range_, dom); + body = m.mk_eq(m.mk_app(inv, fn.get()), arg); + body = m.mk_forall(args.size(), to_sorts(domain), names.c_ptr(), body.get()); + mk_c(c)->save_multiple_ast_trail(body.get()); + mk_c(c)->assert_cnstr(body.get()); + } + mk_c(c)->save_multiple_ast_trail(d); + RETURN_Z3(of_func_decl(d)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_pattern_to_ast(Z3_context c, Z3_pattern p) { + RESET_ERROR_CODE(); + return (Z3_ast)(p); + } + + char const * Z3_API Z3_pattern_to_string(Z3_context c, Z3_pattern p) { + return Z3_ast_to_string(c, reinterpret_cast(p)); + } + +}; diff --git a/lib/api_solver.cpp b/lib/api_solver.cpp new file mode 100644 index 000000000..1a4a2ef45 --- /dev/null +++ b/lib/api_solver.cpp @@ -0,0 +1,322 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_solver.cpp + +Abstract: + New solver API + +Author: + + Leonardo de Moura (leonardo) 2012-03-07. + +Revision History: + +--*/ +#include +#include"z3.h" +#include"api_log_macros.h" +#include"api_context.h" +#include"api_tactic.h" +#include"api_solver.h" +#include"api_model.h" +#include"api_stats.h" +#include"api_ast_vector.h" +#include"tactic2solver.h" +#include"scoped_ctrl_c.h" +#include"cancel_eh.h" +#include"scoped_timer.h" +#include"smt_strategic_solver.h" + +extern "C" { + + Z3_solver Z3_API Z3_mk_simple_solver(Z3_context c) { + Z3_TRY; + LOG_Z3_mk_simple_solver(c); + RESET_ERROR_CODE(); + Z3_solver_ref * s = alloc(Z3_solver_ref); + s->m_solver = mk_default_solver(); + s->m_solver->set_front_end_params(mk_c(c)->fparams()); + s->m_solver->init(mk_c(c)->m(), symbol::null); + mk_c(c)->save_object(s); + Z3_solver r = of_solver(s); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_solver Z3_API Z3_mk_solver(Z3_context c) { + Z3_TRY; + LOG_Z3_mk_solver(c); + RESET_ERROR_CODE(); + Z3_solver_ref * s = alloc(Z3_solver_ref); + s->m_solver = mk_smt_strategic_solver(); + s->m_solver->set_front_end_params(mk_c(c)->fparams()); + s->m_solver->init(mk_c(c)->m(), symbol::null); + mk_c(c)->save_object(s); + Z3_solver r = of_solver(s); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_solver Z3_API Z3_mk_solver_for_logic(Z3_context c, Z3_symbol logic) { + Z3_TRY; + LOG_Z3_mk_solver_for_logic(c, logic); + RESET_ERROR_CODE(); + Z3_solver_ref * s = alloc(Z3_solver_ref); + s->m_solver = mk_smt_strategic_solver(true /* force solver to use tactics even when auto_config is disabled */); + s->m_solver->set_front_end_params(mk_c(c)->fparams()); + s->m_solver->init(mk_c(c)->m(), to_symbol(logic)); + mk_c(c)->save_object(s); + Z3_solver r = of_solver(s); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_solver Z3_API Z3_mk_solver_from_tactic(Z3_context c, Z3_tactic t) { + Z3_TRY; + LOG_Z3_mk_solver_from_tactic(c, t); + RESET_ERROR_CODE(); + Z3_solver_ref * s = alloc(Z3_solver_ref); + s->m_solver = alloc(tactic2solver_api, to_tactic_ref(t)); + s->m_solver->set_front_end_params(mk_c(c)->fparams()); + s->m_solver->init(mk_c(c)->m(), symbol::null); + mk_c(c)->save_object(s); + Z3_solver r = of_solver(s); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_string Z3_API Z3_solver_get_help(Z3_context c, Z3_solver s) { + Z3_TRY; + LOG_Z3_solver_get_help(c, s); + RESET_ERROR_CODE(); + std::ostringstream buffer; + param_descrs descrs; + to_solver_ref(s)->collect_param_descrs(descrs); + descrs.display(buffer); + return mk_c(c)->mk_external_string(buffer.str()); + Z3_CATCH_RETURN(""); + } + + Z3_param_descrs Z3_API Z3_solver_get_param_descrs(Z3_context c, Z3_solver s) { + Z3_TRY; + LOG_Z3_solver_get_param_descrs(c, s); + RESET_ERROR_CODE(); + Z3_param_descrs_ref * d = alloc(Z3_param_descrs_ref); + mk_c(c)->save_object(d); + to_solver_ref(s)->collect_param_descrs(d->m_descrs); + Z3_param_descrs r = of_param_descrs(d); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_solver_set_params(Z3_context c, Z3_solver s, Z3_params p) { + Z3_TRY; + LOG_Z3_solver_set_params(c, s, p); + RESET_ERROR_CODE(); + to_solver_ref(s)->updt_params(to_param_ref(p)); + to_solver(s)->m_params = to_param_ref(p); + Z3_CATCH; + } + + void Z3_API Z3_solver_inc_ref(Z3_context c, Z3_solver s) { + Z3_TRY; + LOG_Z3_solver_inc_ref(c, s); + RESET_ERROR_CODE(); + to_solver(s)->inc_ref(); + Z3_CATCH; + } + + void Z3_API Z3_solver_dec_ref(Z3_context c, Z3_solver s) { + Z3_TRY; + LOG_Z3_solver_dec_ref(c, s); + RESET_ERROR_CODE(); + to_solver(s)->dec_ref(); + Z3_CATCH; + } + + void Z3_API Z3_solver_push(Z3_context c, Z3_solver s) { + Z3_TRY; + LOG_Z3_solver_push(c, s); + RESET_ERROR_CODE(); + to_solver_ref(s)->push(); + Z3_CATCH; + } + + void Z3_API Z3_solver_pop(Z3_context c, Z3_solver s, unsigned n) { + Z3_TRY; + LOG_Z3_solver_pop(c, s, n); + RESET_ERROR_CODE(); + if (n > to_solver_ref(s)->get_scope_level()) { + SET_ERROR_CODE(Z3_IOB); + return; + } + if (n > 0) + to_solver_ref(s)->pop(n); + Z3_CATCH; + } + + void Z3_API Z3_solver_reset(Z3_context c, Z3_solver s) { + Z3_TRY; + LOG_Z3_solver_reset(c, s); + RESET_ERROR_CODE(); + to_solver_ref(s)->reset(); + to_solver_ref(s)->init(mk_c(c)->m(), symbol::null); + Z3_CATCH; + } + + unsigned Z3_API Z3_solver_get_num_scopes(Z3_context c, Z3_solver s) { + Z3_TRY; + LOG_Z3_solver_get_num_scopes(c, s); + RESET_ERROR_CODE(); + return to_solver_ref(s)->get_scope_level(); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_solver_assert(Z3_context c, Z3_solver s, Z3_ast a) { + Z3_TRY; + LOG_Z3_solver_assert(c, s, a); + RESET_ERROR_CODE(); + CHECK_FORMULA(a,); + to_solver_ref(s)->assert_expr(to_expr(a)); + Z3_CATCH; + } + + Z3_ast_vector Z3_API Z3_solver_get_assertions(Z3_context c, Z3_solver s) { + Z3_TRY; + LOG_Z3_solver_get_assertions(c, s); + RESET_ERROR_CODE(); + Z3_ast_vector_ref * v = alloc(Z3_ast_vector_ref, mk_c(c)->m()); + mk_c(c)->save_object(v); + unsigned sz = to_solver_ref(s)->get_num_assertions(); + for (unsigned i = 0; i < sz; i++) { + v->m_ast_vector.push_back(to_solver_ref(s)->get_assertion(i)); + } + RETURN_Z3(of_ast_vector(v)); + Z3_CATCH_RETURN(0); + } + + static Z3_lbool _solver_check(Z3_context c, Z3_solver s, unsigned num_assumptions, Z3_ast const assumptions[]) { + for (unsigned i = 0; i < num_assumptions; i++) { + if (!is_expr(to_ast(assumptions[i]))) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return Z3_L_UNDEF; + } + } + expr * const * _assumptions = to_exprs(assumptions); + to_solver_ref(s)->set_produce_models(to_solver(s)->m_params.get_bool(":model", true)); + to_solver_ref(s)->set_produce_proofs(mk_c(c)->m().proofs_enabled()); + to_solver_ref(s)->set_produce_unsat_cores(num_assumptions > 0); + unsigned timeout = to_solver(s)->m_params.get_uint(":timeout", UINT_MAX); + bool use_ctrl_c = to_solver(s)->m_params.get_bool(":ctrl-c", false); + cancel_eh eh(*to_solver_ref(s)); + api::context::set_interruptable(*(mk_c(c)), eh); + lbool result; + { + scoped_ctrl_c ctrlc(eh, false, use_ctrl_c); + scoped_timer timer(timeout, &eh); + try { + result = to_solver_ref(s)->check_sat(num_assumptions, _assumptions); + } + catch (z3_exception & ex) { + mk_c(c)->handle_exception(ex); + return Z3_L_UNDEF; + } + } + return static_cast(result); + } + + Z3_lbool Z3_API Z3_solver_check(Z3_context c, Z3_solver s) { + Z3_TRY; + LOG_Z3_solver_check(c, s); + RESET_ERROR_CODE(); + return _solver_check(c, s, 0, 0); + Z3_CATCH_RETURN(Z3_L_UNDEF); + } + + Z3_lbool Z3_API Z3_solver_check_assumptions(Z3_context c, Z3_solver s, unsigned num_assumptions, Z3_ast const assumptions[]) { + Z3_TRY; + LOG_Z3_solver_check_assumptions(c, s, num_assumptions, assumptions); + RESET_ERROR_CODE(); + return _solver_check(c, s, num_assumptions, assumptions); + Z3_CATCH_RETURN(Z3_L_UNDEF); + } + + Z3_model Z3_API Z3_solver_get_model(Z3_context c, Z3_solver s) { + Z3_TRY; + LOG_Z3_solver_get_model(c, s); + RESET_ERROR_CODE(); + model_ref _m; + to_solver_ref(s)->get_model(_m); + if (!_m) { + SET_ERROR_CODE(Z3_INVALID_USAGE); + RETURN_Z3(0); + } + Z3_model_ref * m_ref = alloc(Z3_model_ref); + m_ref->m_model = _m; + mk_c(c)->save_object(m_ref); + RETURN_Z3(of_model(m_ref)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_solver_get_proof(Z3_context c, Z3_solver s) { + Z3_TRY; + LOG_Z3_solver_get_proof(c, s); + RESET_ERROR_CODE(); + proof * p = to_solver_ref(s)->get_proof(); + if (!p) { + SET_ERROR_CODE(Z3_INVALID_USAGE); + RETURN_Z3(0); + } + mk_c(c)->save_ast_trail(p); + RETURN_Z3(of_ast(p)); + Z3_CATCH_RETURN(0); + } + + Z3_ast_vector Z3_API Z3_solver_get_unsat_core(Z3_context c, Z3_solver s) { + Z3_TRY; + LOG_Z3_solver_get_unsat_core(c, s); + RESET_ERROR_CODE(); + ptr_vector core; + to_solver_ref(s)->get_unsat_core(core); + Z3_ast_vector_ref * v = alloc(Z3_ast_vector_ref, mk_c(c)->m()); + mk_c(c)->save_object(v); + for (unsigned i = 0; i < core.size(); i++) { + v->m_ast_vector.push_back(core[i]); + } + RETURN_Z3(of_ast_vector(v)); + Z3_CATCH_RETURN(0); + } + + Z3_string Z3_API Z3_solver_get_reason_unknown(Z3_context c, Z3_solver s) { + Z3_TRY; + LOG_Z3_solver_get_reason_unknown(c, s); + RESET_ERROR_CODE(); + return mk_c(c)->mk_external_string(to_solver_ref(s)->reason_unknown()); + Z3_CATCH_RETURN(""); + } + + Z3_stats Z3_API Z3_solver_get_statistics(Z3_context c, Z3_solver s) { + Z3_TRY; + LOG_Z3_solver_get_statistics(c, s); + RESET_ERROR_CODE(); + Z3_stats_ref * st = alloc(Z3_stats_ref); + to_solver_ref(s)->collect_statistics(st->m_stats); + mk_c(c)->save_object(st); + Z3_stats r = of_stats(st); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_string Z3_API Z3_solver_to_string(Z3_context c, Z3_solver s) { + Z3_TRY; + LOG_Z3_solver_to_string(c, s); + RESET_ERROR_CODE(); + std::ostringstream buffer; + to_solver_ref(s)->display(buffer); + return mk_c(c)->mk_external_string(buffer.str()); + Z3_CATCH_RETURN(""); + } +}; diff --git a/lib/api_solver.h b/lib/api_solver.h new file mode 100644 index 000000000..a39475bac --- /dev/null +++ b/lib/api_solver.h @@ -0,0 +1,35 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_solver.h + +Abstract: + New solver API + +Author: + + Leonardo de Moura (leonardo) 2012-03-07. + +Revision History: + +--*/ +#ifndef _API_SOLVER_H_ +#define _API_SOLVER_H_ + +#include"api_util.h" +#include"solver.h" + +struct Z3_solver_ref : public api::object { + solver * m_solver; + params_ref m_params; + Z3_solver_ref():m_solver(0) {} + virtual ~Z3_solver_ref() { dealloc(m_solver); } +}; + +inline Z3_solver_ref * to_solver(Z3_solver s) { return reinterpret_cast(s); } +inline Z3_solver of_solver(Z3_solver_ref * s) { return reinterpret_cast(s); } +inline solver * to_solver_ref(Z3_solver s) { return to_solver(s)->m_solver; } + +#endif diff --git a/lib/api_solver_old.cpp b/lib/api_solver_old.cpp new file mode 100644 index 000000000..bb083b7fc --- /dev/null +++ b/lib/api_solver_old.cpp @@ -0,0 +1,365 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_solver_old.cpp + +Abstract: + OLD API for using solvers. + + This has been deprecated + +Author: + + Leonardo de Moura (leonardo) 2012-02-29. + +Revision History: + +--*/ +#include +#include"z3.h" +#include"api_log_macros.h" +#include"api_context.h" +#include"api_model.h" +#include"smt_implied_equalities.h" +#include"cancel_eh.h" + +extern "C" { + + void Z3_API Z3_push(Z3_context c) { + Z3_TRY; + LOG_Z3_push(c); + RESET_ERROR_CODE(); + CHECK_SEARCHING(c); + mk_c(c)->push(); + Z3_CATCH; + } + + void Z3_API Z3_pop(Z3_context c, unsigned num_scopes) { + Z3_TRY; + LOG_Z3_pop(c, num_scopes); + RESET_ERROR_CODE(); + CHECK_SEARCHING(c); + if (num_scopes > mk_c(c)->get_solver().get_scope_level()) { + SET_ERROR_CODE(Z3_IOB); + return; + } + if (num_scopes > 0) { + mk_c(c)->pop(num_scopes); + } + Z3_CATCH; + } + + unsigned Z3_API Z3_get_num_scopes(Z3_context c) { + Z3_TRY; + LOG_Z3_get_num_scopes(c); + RESET_ERROR_CODE(); + return mk_c(c)->get_num_scopes(); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_assert_cnstr(Z3_context c, Z3_ast a) { + Z3_TRY; + LOG_Z3_assert_cnstr(c, a); + RESET_ERROR_CODE(); + CHECK_FORMULA(a,); + mk_c(c)->assert_cnstr(to_expr(a)); + Z3_CATCH; + } + + Z3_lbool Z3_API Z3_check_and_get_model(Z3_context c, Z3_model * m) { + Z3_TRY; + LOG_Z3_check_and_get_model(c, m); + RESET_ERROR_CODE(); + CHECK_SEARCHING(c); + cancel_eh eh(mk_c(c)->get_solver()); + api::context::set_interruptable(*(mk_c(c)), eh); + flet _model(mk_c(c)->fparams().m_model, true); + lbool result; + try { + model_ref _m; + result = mk_c(c)->check(_m); + if (m) { + if (_m) { + Z3_model_ref * m_ref = alloc(Z3_model_ref); + m_ref->m_model = _m; + // Must bump reference counter for backward compatibility reasons. + // Don't need to invoke save_object, since the counter was bumped + m_ref->inc_ref(); + *m = of_model(m_ref); + } + else { + *m = 0; + } + } + } + catch (z3_exception & ex) { + mk_c(c)->handle_exception(ex); + RETURN_Z3_check_and_get_model static_cast(l_undef); + } + RETURN_Z3_check_and_get_model static_cast(result); + Z3_CATCH_RETURN(Z3_L_UNDEF); + } + + Z3_lbool Z3_API Z3_check(Z3_context c) { + Z3_TRY; + // This is just syntax sugar... + RESET_ERROR_CODE(); + CHECK_SEARCHING(c); + Z3_lbool r = Z3_check_and_get_model(c, 0); + return r; + Z3_CATCH_RETURN(Z3_L_UNDEF); + } + + Z3_lbool Z3_API Z3_get_implied_equalities(Z3_context c, + unsigned num_terms, + Z3_ast const terms[], + unsigned class_ids[]) { + Z3_TRY; + LOG_Z3_get_implied_equalities(c, num_terms, terms, class_ids); + RESET_ERROR_CODE(); + CHECK_SEARCHING(c); + lbool result = smt::implied_equalities(mk_c(c)->get_solver(), num_terms, to_exprs(terms), class_ids); + return static_cast(result); + Z3_CATCH_RETURN(Z3_L_UNDEF); + } + + + Z3_lbool Z3_API Z3_check_assumptions(Z3_context c, + unsigned num_assumptions, Z3_ast const assumptions[], + Z3_model * m, Z3_ast* proof, + unsigned* core_size, Z3_ast core[]) { + Z3_TRY; + LOG_Z3_check_assumptions(c, num_assumptions, assumptions, m, proof, core_size, core); + RESET_ERROR_CODE(); + CHECK_SEARCHING(c); + expr * const* _assumptions = to_exprs(assumptions); + flet _model(mk_c(c)->fparams().m_model, true); + cancel_eh eh(mk_c(c)->get_solver()); + api::context::set_interruptable(*(mk_c(c)), eh); + lbool result; + result = mk_c(c)->get_solver().check(num_assumptions, _assumptions); + if (result != l_false && m) { + model_ref _m; + mk_c(c)->get_solver().get_model(_m); + if (_m) { + Z3_model_ref * m_ref = alloc(Z3_model_ref); + m_ref->m_model = _m; + // Must bump reference counter for backward compatibility reasons. + // Don't need to invoke save_object, since the counter was bumped + m_ref->inc_ref(); + *m = of_model(m_ref); + } + else { + *m = 0; + } + } + if (result == l_false && core_size) { + *core_size = mk_c(c)->get_solver().get_unsat_core_size(); + if (*core_size > num_assumptions) { + SET_ERROR_CODE(Z3_INVALID_ARG); + } + for (unsigned i = 0; i < *core_size; ++i) { + core[i] = of_ast(mk_c(c)->get_solver().get_unsat_core_expr(i)); + } + } + else if (core_size) { + *core_size = 0; + } + if (result == l_false && proof) { + *proof = of_ast(mk_c(c)->get_solver().get_proof()); + } + else if (proof) { + *proof = 0; // breaks abstraction. + } + RETURN_Z3_check_assumptions static_cast(result); + Z3_CATCH_RETURN(Z3_L_UNDEF); + } + + Z3_search_failure Z3_API Z3_get_search_failure(Z3_context c) { + Z3_TRY; + LOG_Z3_get_search_failure(c); + RESET_ERROR_CODE(); + CHECK_SEARCHING(c); + smt::failure f = mk_c(c)->get_solver().last_failure(); + return api::mk_Z3_search_failure(f); + Z3_CATCH_RETURN(Z3_UNKNOWN); + } + + class labeled_literal { + expr_ref m_literal; + symbol m_label; + bool m_enabled; + public: + labeled_literal(ast_manager& m, expr* l, symbol const& n) : m_literal(l,m), m_label(n), m_enabled(true) {} + labeled_literal(ast_manager& m, expr* l) : m_literal(l,m), m_label(), m_enabled(true) {} + bool is_enabled() const { return m_enabled; } + void disable() { m_enabled = false; } + symbol const& get_label() const { return m_label; } + expr* get_literal() { return m_literal.get(); } + }; + + typedef vector labels; + + Z3_literals Z3_API Z3_get_relevant_labels(Z3_context c) { + Z3_TRY; + LOG_Z3_get_relevant_labels(c); + RESET_ERROR_CODE(); + buffer labl_syms; + ast_manager& m = mk_c(c)->m(); + expr_ref_vector lits(m); + mk_c(c)->get_solver().get_relevant_labels(0, labl_syms); + mk_c(c)->get_solver().get_relevant_labeled_literals(mk_c(c)->fparams().m_at_labels_cex, lits); + labels* lbls = alloc(labels); + SASSERT(labl_syms.size() == lits.size()); + for (unsigned i = 0; i < lits.size(); ++i) { + lbls->push_back(labeled_literal(m,lits[i].get(), labl_syms[i])); + } + RETURN_Z3(reinterpret_cast(lbls)); + Z3_CATCH_RETURN(0); + } + + Z3_literals Z3_API Z3_get_relevant_literals(Z3_context c) { + Z3_TRY; + LOG_Z3_get_relevant_literals(c); + RESET_ERROR_CODE(); + ast_manager& m = mk_c(c)->m(); + expr_ref_vector lits(m); + mk_c(c)->get_solver().get_relevant_literals(lits); + labels* lbls = alloc(labels); + for (unsigned i = 0; i < lits.size(); ++i) { + lbls->push_back(labeled_literal(m,lits[i].get())); + } + RETURN_Z3(reinterpret_cast(lbls)); + Z3_CATCH_RETURN(0); + } + + Z3_literals Z3_API Z3_get_guessed_literals(Z3_context c) { + Z3_TRY; + LOG_Z3_get_guessed_literals(c); + RESET_ERROR_CODE(); + ast_manager& m = mk_c(c)->m(); + expr_ref_vector lits(m); + mk_c(c)->get_solver().get_guessed_literals(lits); + labels* lbls = alloc(labels); + for (unsigned i = 0; i < lits.size(); ++i) { + lbls->push_back(labeled_literal(m,lits[i].get())); + } + RETURN_Z3(reinterpret_cast(lbls)); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_del_literals(Z3_context c, Z3_literals lbls) { + Z3_TRY; + LOG_Z3_del_literals(c, lbls); + RESET_ERROR_CODE(); + dealloc(reinterpret_cast(lbls)); + Z3_CATCH; + } + + unsigned Z3_API Z3_get_num_literals(Z3_context c,Z3_literals lbls) { + Z3_TRY; + LOG_Z3_get_num_literals(c, lbls); + RESET_ERROR_CODE(); + return reinterpret_cast(lbls)->size(); + Z3_CATCH_RETURN(0); + } + + Z3_symbol Z3_API Z3_get_label_symbol(Z3_context c,Z3_literals lbls, unsigned idx) { + Z3_TRY; + LOG_Z3_get_label_symbol(c, lbls, idx); + RESET_ERROR_CODE(); + return of_symbol((*reinterpret_cast(lbls))[idx].get_label()); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_get_literal(Z3_context c,Z3_literals lbls, unsigned idx) { + Z3_TRY; + LOG_Z3_get_literal(c, lbls, idx); + RESET_ERROR_CODE(); + expr* e = (*reinterpret_cast(lbls))[idx].get_literal(); + mk_c(c)->save_ast_trail(e); + RETURN_Z3(of_ast(e)); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_disable_literal(Z3_context c, Z3_literals lbls, unsigned idx) { + Z3_TRY; + LOG_Z3_disable_literal(c, lbls, idx); + RESET_ERROR_CODE(); + (*reinterpret_cast(lbls))[idx].disable(); + Z3_CATCH; + } + + void Z3_API Z3_block_literals(Z3_context c, Z3_literals lbls) { + Z3_TRY; + LOG_Z3_block_literals(c, lbls); + RESET_ERROR_CODE(); + labels* _lbls = reinterpret_cast(lbls); + ast_manager& m = mk_c(c)->m(); + expr_ref_vector lits(m); + for (unsigned i = 0; i < _lbls->size(); ++i) { + if ((*_lbls)[i].is_enabled()) { + lits.push_back(m.mk_not((*_lbls)[i].get_literal())); + } + } + expr_ref clause(m); + clause = m.mk_or(lits.size(), lits.c_ptr()); + mk_c(c)->save_ast_trail(clause.get()); + mk_c(c)->assert_cnstr(clause.get()); + Z3_CATCH; + } + + char const * Z3_API Z3_context_to_string(Z3_context c) { + Z3_TRY; + LOG_Z3_context_to_string(c); + RESET_ERROR_CODE(); + std::ostringstream buffer; + mk_c(c)->get_solver().display(buffer); + return mk_c(c)->mk_external_string(buffer.str()); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_API Z3_get_context_assignment(Z3_context c) { + Z3_TRY; + LOG_Z3_get_context_assignment(c); + RESET_ERROR_CODE(); + ast_manager& m = mk_c(c)->m(); + expr_ref result(m); + expr_ref_vector assignment(m); + mk_c(c)->get_solver().get_assignments(assignment); + result = mk_c(c)->mk_and(assignment.size(), assignment.c_ptr()); + RETURN_Z3(of_ast(result.get())); + Z3_CATCH_RETURN(0); + } + + Z3_string Z3_API Z3_statistics_to_string(Z3_context c) { + Z3_TRY; + LOG_Z3_statistics_to_string(c); + RESET_ERROR_CODE(); + std::ostringstream buffer; + mk_c(c)->get_solver().display_statistics(buffer); + memory::display_max_usage(buffer); + return mk_c(c)->mk_external_string(buffer.str()); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_soft_check_cancel(Z3_context c) { + Z3_TRY; + LOG_Z3_soft_check_cancel(c); + RESET_ERROR_CODE(); + mk_c(c)->interrupt(); + Z3_CATCH; + } + +}; + +void Z3_display_statistics(Z3_context c, std::ostream& s) { + mk_c(c)->get_solver().display_statistics(s); +} + +void Z3_display_istatistics(Z3_context c, std::ostream& s) { + mk_c(c)->get_solver().display_istatistics(s); +} + diff --git a/lib/api_stats.cpp b/lib/api_stats.cpp new file mode 100644 index 000000000..ee391f14a --- /dev/null +++ b/lib/api_stats.cpp @@ -0,0 +1,133 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_stats.cpp + +Abstract: + API for browsing statistics + +Author: + + Leonardo de Moura (leonardo) 2012-03-07. + +Revision History: + +--*/ +#include +#include"z3.h" +#include"api_log_macros.h" +#include"api_context.h" +#include"api_stats.h" + +extern "C" { + + Z3_string Z3_API Z3_stats_to_string(Z3_context c, Z3_stats s) { + Z3_TRY; + LOG_Z3_stats_to_string(c, s); + RESET_ERROR_CODE(); + std::ostringstream buffer; + to_stats_ref(s).display_smt2(buffer); + std::string result = buffer.str(); + // Hack for removing the trailing '\n' + result = buffer.str(); + SASSERT(result.size() > 0); + result.resize(result.size()-1); + return mk_c(c)->mk_external_string(result); + Z3_CATCH_RETURN(""); + } + + void Z3_API Z3_stats_inc_ref(Z3_context c, Z3_stats s) { + Z3_TRY; + LOG_Z3_stats_inc_ref(c, s); + RESET_ERROR_CODE(); + to_stats(s)->inc_ref(); + Z3_CATCH; + } + + void Z3_API Z3_stats_dec_ref(Z3_context c, Z3_stats s) { + Z3_TRY; + LOG_Z3_stats_dec_ref(c, s); + RESET_ERROR_CODE(); + to_stats(s)->dec_ref(); + Z3_CATCH; + } + + unsigned Z3_API Z3_stats_size(Z3_context c, Z3_stats s) { + Z3_TRY; + LOG_Z3_stats_size(c, s); + RESET_ERROR_CODE(); + return to_stats_ref(s).size(); + Z3_CATCH_RETURN(0); + } + + Z3_string Z3_API Z3_stats_get_key(Z3_context c, Z3_stats s, unsigned idx) { + Z3_TRY; + LOG_Z3_stats_get_key(c, s, idx); + RESET_ERROR_CODE(); + if (idx >= to_stats_ref(s).size()) { + SET_ERROR_CODE(Z3_IOB); + return ""; + } + return to_stats_ref(s).get_key(idx); + Z3_CATCH_RETURN(""); + } + + Z3_bool Z3_API Z3_stats_is_uint(Z3_context c, Z3_stats s, unsigned idx) { + Z3_TRY; + LOG_Z3_stats_is_uint(c, s, idx); + RESET_ERROR_CODE(); + if (idx >= to_stats_ref(s).size()) { + SET_ERROR_CODE(Z3_IOB); + return Z3_FALSE; + } + return to_stats_ref(s).is_uint(idx); + Z3_CATCH_RETURN(0); + } + + Z3_bool Z3_API Z3_stats_is_double(Z3_context c, Z3_stats s, unsigned idx) { + Z3_TRY; + LOG_Z3_stats_is_double(c, s, idx); + RESET_ERROR_CODE(); + if (idx >= to_stats_ref(s).size()) { + SET_ERROR_CODE(Z3_IOB); + return Z3_FALSE; + } + return !to_stats_ref(s).is_uint(idx); + Z3_CATCH_RETURN(Z3_FALSE); + } + + unsigned Z3_API Z3_stats_get_uint_value(Z3_context c, Z3_stats s, unsigned idx) { + Z3_TRY; + LOG_Z3_stats_get_uint_value(c, s, idx); + RESET_ERROR_CODE(); + if (idx >= to_stats_ref(s).size()) { + SET_ERROR_CODE(Z3_IOB); + return 0; + } + if (!to_stats_ref(s).is_uint(idx)) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return 0; + } + return to_stats_ref(s).get_uint_value(idx); + Z3_CATCH_RETURN(0); + } + + double Z3_API Z3_stats_get_double_value(Z3_context c, Z3_stats s, unsigned idx) { + Z3_TRY; + LOG_Z3_stats_get_double_value(c, s, idx); + RESET_ERROR_CODE(); + if (idx >= to_stats_ref(s).size()) { + SET_ERROR_CODE(Z3_IOB); + return 0.0; + } + if (to_stats_ref(s).is_uint(idx)) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return 0.0; + } + return to_stats_ref(s).get_double_value(idx); + Z3_CATCH_RETURN(0.0); + } + +}; diff --git a/lib/api_stats.h b/lib/api_stats.h new file mode 100644 index 000000000..393161309 --- /dev/null +++ b/lib/api_stats.h @@ -0,0 +1,33 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_stats.h + +Abstract: + API for Z3 statistics + +Author: + + Leonardo de Moura (leonardo) 2012-03-07. + +Revision History: + +--*/ +#ifndef _API_STATS_H_ +#define _API_STATS_H_ + +#include"api_util.h" +#include"statistics.h" + +struct Z3_stats_ref : public api::object { + statistics m_stats; + virtual ~Z3_stats_ref() {} +}; + +inline Z3_stats_ref * to_stats(Z3_stats s) { return reinterpret_cast(s); } +inline Z3_stats of_stats(Z3_stats_ref * s) { return reinterpret_cast(s); } +inline statistics & to_stats_ref(Z3_stats s) { return to_stats(s)->m_stats; } + +#endif diff --git a/lib/api_tactic.cpp b/lib/api_tactic.cpp new file mode 100644 index 000000000..ff55923a4 --- /dev/null +++ b/lib/api_tactic.cpp @@ -0,0 +1,528 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_tactic.cpp + +Abstract: + API for creating tactics and probes + +Author: + + Leonardo de Moura (leonardo) 2012-03-06. + +Revision History: + +--*/ +#include +#include"z3.h" +#include"api_log_macros.h" +#include"api_context.h" +#include"api_tactic.h" +#include"api_model.h" +#include"scoped_ctrl_c.h" +#include"cancel_eh.h" +#include"scoped_timer.h" + +Z3_apply_result_ref::Z3_apply_result_ref(ast_manager & m):m_core(m) { +} + +extern "C" { + +#define RETURN_TACTIC(_t_) { \ + Z3_tactic_ref * _ref_ = alloc(Z3_tactic_ref); \ + _ref_->m_tactic = _t_; \ + mk_c(c)->save_object(_ref_); \ + Z3_tactic _result_ = of_tactic(_ref_); \ + RETURN_Z3(_result_); \ +} + +#define RETURN_PROBE(_t_) { \ + Z3_probe_ref * _ref_ = alloc(Z3_probe_ref); \ + _ref_->m_probe = _t_; \ + mk_c(c)->save_object(_ref_); \ + Z3_probe _result_ = of_probe(_ref_); \ + RETURN_Z3(_result_); \ +} + + Z3_tactic Z3_API Z3_mk_tactic(Z3_context c, Z3_string name) { + Z3_TRY; + LOG_Z3_mk_tactic(c, name); + RESET_ERROR_CODE(); + tactic_cmd * t = mk_c(c)->find_tactic_cmd(symbol(name)); + if (t == 0) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + tactic * new_t = t->mk(mk_c(c)->m()); + RETURN_TACTIC(new_t); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_tactic_inc_ref(Z3_context c, Z3_tactic t) { + Z3_TRY; + LOG_Z3_tactic_inc_ref(c, t); + RESET_ERROR_CODE(); + to_tactic(t)->inc_ref(); + Z3_CATCH; + } + + void Z3_API Z3_tactic_dec_ref(Z3_context c, Z3_tactic t) { + Z3_TRY; + LOG_Z3_tactic_dec_ref(c, t); + RESET_ERROR_CODE(); + to_tactic(t)->dec_ref(); + Z3_CATCH; + } + + Z3_probe Z3_API Z3_mk_probe(Z3_context c, Z3_string name) { + Z3_TRY; + LOG_Z3_mk_probe(c, name); + RESET_ERROR_CODE(); + probe_info * p = mk_c(c)->find_probe(symbol(name)); + if (p == 0) { + SET_ERROR_CODE(Z3_INVALID_ARG); + RETURN_Z3(0); + } + probe * new_p = p->get(); + RETURN_PROBE(new_p); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_probe_inc_ref(Z3_context c, Z3_probe p) { + Z3_TRY; + LOG_Z3_probe_inc_ref(c, p); + RESET_ERROR_CODE(); + to_probe(p)->inc_ref(); + Z3_CATCH; + } + + void Z3_API Z3_probe_dec_ref(Z3_context c, Z3_probe p) { + Z3_TRY; + LOG_Z3_probe_dec_ref(c, p); + RESET_ERROR_CODE(); + to_probe(p)->dec_ref(); + Z3_CATCH; + } + + Z3_tactic Z3_API Z3_tactic_and_then(Z3_context c, Z3_tactic t1, Z3_tactic t2) { + Z3_TRY; + LOG_Z3_tactic_and_then(c, t1, t2); + RESET_ERROR_CODE(); + tactic * new_t = and_then(to_tactic_ref(t1), to_tactic_ref(t2)); + RETURN_TACTIC(new_t); + Z3_CATCH_RETURN(0); + } + + Z3_tactic Z3_API Z3_tactic_or_else(Z3_context c, Z3_tactic t1, Z3_tactic t2) { + Z3_TRY; + LOG_Z3_tactic_or_else(c, t1, t2); + RESET_ERROR_CODE(); + tactic * new_t = or_else(to_tactic_ref(t1), to_tactic_ref(t2)); + RETURN_TACTIC(new_t); + Z3_CATCH_RETURN(0); + } + + Z3_tactic Z3_API Z3_tactic_par_or(Z3_context c, unsigned num, Z3_tactic const ts[]) { + Z3_TRY; + LOG_Z3_tactic_par_or(c, num, ts); + RESET_ERROR_CODE(); + ptr_buffer _ts; + for (unsigned i = 0; i < num; i++) { + _ts.push_back(to_tactic_ref(ts[i])); + } + tactic * new_t = par(num, _ts.c_ptr()); + RETURN_TACTIC(new_t); + Z3_CATCH_RETURN(0); + } + + Z3_tactic Z3_API Z3_tactic_par_and_then(Z3_context c, Z3_tactic t1, Z3_tactic t2) { + Z3_TRY; + LOG_Z3_tactic_par_and_then(c, t1, t2); + RESET_ERROR_CODE(); + tactic * new_t = par_and_then(to_tactic_ref(t1), to_tactic_ref(t2)); + RETURN_TACTIC(new_t); + Z3_CATCH_RETURN(0); + } + + Z3_tactic Z3_API Z3_tactic_try_for(Z3_context c, Z3_tactic t, unsigned ms) { + Z3_TRY; + LOG_Z3_tactic_try_for(c, t, ms); + RESET_ERROR_CODE(); + tactic * new_t = try_for(to_tactic_ref(t), ms); + RETURN_TACTIC(new_t); + Z3_CATCH_RETURN(0); + } + + Z3_tactic Z3_API Z3_tactic_when(Z3_context c, Z3_probe p, Z3_tactic t) { + Z3_TRY; + LOG_Z3_tactic_when(c, p, t); + RESET_ERROR_CODE(); + tactic * new_t = when(to_probe_ref(p), to_tactic_ref(t)); + RETURN_TACTIC(new_t); + Z3_CATCH_RETURN(0); + } + + Z3_tactic Z3_API Z3_tactic_cond(Z3_context c, Z3_probe p, Z3_tactic t1, Z3_tactic t2) { + Z3_TRY; + LOG_Z3_tactic_cond(c, p, t1, t2); + RESET_ERROR_CODE(); + tactic * new_t = cond(to_probe_ref(p), to_tactic_ref(t1), to_tactic_ref(t2)); + RETURN_TACTIC(new_t); + Z3_CATCH_RETURN(0); + } + + Z3_tactic Z3_API Z3_tactic_repeat(Z3_context c, Z3_tactic t, unsigned max) { + Z3_TRY; + LOG_Z3_tactic_repeat(c, t, max); + RESET_ERROR_CODE(); + tactic * new_t = repeat(to_tactic_ref(t), max); + RETURN_TACTIC(new_t); + Z3_CATCH_RETURN(0); + } + + Z3_tactic Z3_API Z3_tactic_skip(Z3_context c) { + Z3_TRY; + LOG_Z3_tactic_skip(c); + RESET_ERROR_CODE(); + tactic * new_t = mk_skip_tactic(); + RETURN_TACTIC(new_t); + Z3_CATCH_RETURN(0); + } + + Z3_tactic Z3_API Z3_tactic_fail(Z3_context c) { + Z3_TRY; + LOG_Z3_tactic_fail(c); + RESET_ERROR_CODE(); + tactic * new_t = mk_fail_tactic(); + RETURN_TACTIC(new_t); + Z3_CATCH_RETURN(0); + } + + Z3_tactic Z3_API Z3_tactic_fail_if(Z3_context c, Z3_probe p) { + Z3_TRY; + LOG_Z3_tactic_fail_if(c, p); + RESET_ERROR_CODE(); + tactic * new_t = fail_if(to_probe_ref(p)); + RETURN_TACTIC(new_t); + Z3_CATCH_RETURN(0); + } + + Z3_tactic Z3_API Z3_tactic_fail_if_not_decided(Z3_context c) { + Z3_TRY; + LOG_Z3_tactic_fail_if_not_decided(c); + RESET_ERROR_CODE(); + tactic * new_t = mk_fail_if_undecided_tactic(); + RETURN_TACTIC(new_t); + Z3_CATCH_RETURN(0); + } + + Z3_tactic Z3_API Z3_tactic_using_params(Z3_context c, Z3_tactic t, Z3_params p) { + Z3_TRY; + LOG_Z3_tactic_using_params(c, t, p); + RESET_ERROR_CODE(); + tactic * new_t = using_params(to_tactic_ref(t), to_param_ref(p)); + RETURN_TACTIC(new_t); + Z3_CATCH_RETURN(0); + } + + Z3_probe Z3_API Z3_probe_const(Z3_context c, double val) { + Z3_TRY; + LOG_Z3_probe_const(c, val); + RESET_ERROR_CODE(); + probe * new_p = mk_const_probe(val); + RETURN_PROBE(new_p); + Z3_CATCH_RETURN(0); + } + + Z3_probe Z3_API Z3_probe_lt(Z3_context c, Z3_probe p1, Z3_probe p2) { + Z3_TRY; + LOG_Z3_probe_lt(c, p1, p2); + RESET_ERROR_CODE(); + probe * new_p = mk_lt(to_probe_ref(p1), to_probe_ref(p2)); + RETURN_PROBE(new_p); + Z3_CATCH_RETURN(0); + } + + Z3_probe Z3_API Z3_probe_gt(Z3_context c, Z3_probe p1, Z3_probe p2) { + Z3_TRY; + LOG_Z3_probe_gt(c, p1, p2); + RESET_ERROR_CODE(); + probe * new_p = mk_gt(to_probe_ref(p1), to_probe_ref(p2)); + RETURN_PROBE(new_p); + Z3_CATCH_RETURN(0); + } + + Z3_probe Z3_API Z3_probe_le(Z3_context c, Z3_probe p1, Z3_probe p2) { + Z3_TRY; + LOG_Z3_probe_le(c, p1, p2); + RESET_ERROR_CODE(); + probe * new_p = mk_le(to_probe_ref(p1), to_probe_ref(p2)); + RETURN_PROBE(new_p); + Z3_CATCH_RETURN(0); + } + + Z3_probe Z3_API Z3_probe_ge(Z3_context c, Z3_probe p1, Z3_probe p2) { + Z3_TRY; + LOG_Z3_probe_ge(c, p1, p2); + RESET_ERROR_CODE(); + probe * new_p = mk_ge(to_probe_ref(p1), to_probe_ref(p2)); + RETURN_PROBE(new_p); + Z3_CATCH_RETURN(0); + } + + Z3_probe Z3_API Z3_probe_eq(Z3_context c, Z3_probe p1, Z3_probe p2) { + Z3_TRY; + LOG_Z3_probe_eq(c, p1, p2); + RESET_ERROR_CODE(); + probe * new_p = mk_eq(to_probe_ref(p1), to_probe_ref(p2)); + RETURN_PROBE(new_p); + Z3_CATCH_RETURN(0); + } + + Z3_probe Z3_API Z3_probe_and(Z3_context c, Z3_probe p1, Z3_probe p2) { + Z3_TRY; + LOG_Z3_probe_and(c, p1, p2); + RESET_ERROR_CODE(); + probe * new_p = mk_and(to_probe_ref(p1), to_probe_ref(p2)); + RETURN_PROBE(new_p); + Z3_CATCH_RETURN(0); + } + + Z3_probe Z3_API Z3_probe_or(Z3_context c, Z3_probe p1, Z3_probe p2) { + Z3_TRY; + LOG_Z3_probe_or(c, p1, p2); + RESET_ERROR_CODE(); + probe * new_p = mk_or(to_probe_ref(p1), to_probe_ref(p2)); + RETURN_PROBE(new_p); + Z3_CATCH_RETURN(0); + } + + Z3_probe Z3_API Z3_probe_not(Z3_context c, Z3_probe p) { + Z3_TRY; + LOG_Z3_probe_not(c, p); + RESET_ERROR_CODE(); + probe * new_p = mk_not(to_probe_ref(p)); + RETURN_PROBE(new_p); + Z3_CATCH_RETURN(0); + } + + unsigned Z3_API Z3_get_num_tactics(Z3_context c) { + Z3_TRY; + LOG_Z3_get_num_tactics(c); + RESET_ERROR_CODE(); + return mk_c(c)->num_tactics(); + Z3_CATCH_RETURN(0); + } + + Z3_string Z3_API Z3_get_tactic_name(Z3_context c, unsigned idx) { + Z3_TRY; + LOG_Z3_get_tactic_name(c, idx); + RESET_ERROR_CODE(); + if (idx >= mk_c(c)->num_tactics()) { + SET_ERROR_CODE(Z3_IOB); + return ""; + } + return mk_c(c)->get_tactic(idx)->get_name().bare_str(); + Z3_CATCH_RETURN(""); + } + + unsigned Z3_API Z3_get_num_probes(Z3_context c) { + Z3_TRY; + LOG_Z3_get_num_probes(c); + RESET_ERROR_CODE(); + return mk_c(c)->num_probes(); + Z3_CATCH_RETURN(0); + } + + Z3_string Z3_API Z3_get_probe_name(Z3_context c, unsigned idx) { + Z3_TRY; + LOG_Z3_get_probe_name(c, idx); + RESET_ERROR_CODE(); + if (idx >= mk_c(c)->num_probes()) { + SET_ERROR_CODE(Z3_IOB); + return ""; + } + return mk_c(c)->get_probe(idx)->get_name().bare_str(); + Z3_CATCH_RETURN(""); + } + + Z3_string Z3_API Z3_tactic_get_help(Z3_context c, Z3_tactic t) { + Z3_TRY; + LOG_Z3_tactic_get_help(c, t); + RESET_ERROR_CODE(); + std::ostringstream buffer; + param_descrs descrs; + to_tactic_ref(t)->collect_param_descrs(descrs); + descrs.display(buffer); + return mk_c(c)->mk_external_string(buffer.str()); + Z3_CATCH_RETURN(""); + } + + Z3_param_descrs Z3_API Z3_tactic_get_param_descrs(Z3_context c, Z3_tactic t) { + Z3_TRY; + LOG_Z3_tactic_get_param_descrs(c, t); + RESET_ERROR_CODE(); + Z3_param_descrs_ref * d = alloc(Z3_param_descrs_ref); + mk_c(c)->save_object(d); + to_tactic_ref(t)->collect_param_descrs(d->m_descrs); + Z3_param_descrs r = of_param_descrs(d); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_string Z3_API Z3_tactic_get_descr(Z3_context c, Z3_string name) { + Z3_TRY; + LOG_Z3_tactic_get_descr(c, name); + RESET_ERROR_CODE(); + tactic_cmd * t = mk_c(c)->find_tactic_cmd(symbol(name)); + if (t == 0) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return ""; + } + return t->get_descr(); + Z3_CATCH_RETURN(""); + } + + Z3_string Z3_API Z3_probe_get_descr(Z3_context c, Z3_string name) { + Z3_TRY; + LOG_Z3_probe_get_descr(c, name); + RESET_ERROR_CODE(); + probe_info * p = mk_c(c)->find_probe(symbol(name)); + if (p == 0) { + SET_ERROR_CODE(Z3_INVALID_ARG); + return ""; + } + return p->get_descr(); + Z3_CATCH_RETURN(""); + } + + static Z3_apply_result _tactic_apply(Z3_context c, Z3_tactic t, Z3_goal g, params_ref p) { + goal_ref new_goal; + new_goal = alloc(goal, *to_goal_ref(g)); + Z3_apply_result_ref * ref = alloc(Z3_apply_result_ref, mk_c(c)->m()); + mk_c(c)->save_object(ref); + + unsigned timeout = p.get_uint(":timeout", UINT_MAX); + bool use_ctrl_c = p.get_bool(":ctrl-c", false); + cancel_eh eh(*to_tactic_ref(t)); + + to_tactic_ref(t)->updt_params(p); + + api::context::set_interruptable(*(mk_c(c)), eh); + { + scoped_ctrl_c ctrlc(eh, false, use_ctrl_c); + scoped_timer timer(timeout, &eh); + try { + exec(*to_tactic_ref(t), new_goal, ref->m_subgoals, ref->m_mc, ref->m_pc, ref->m_core); + return of_apply_result(ref); + } + catch (z3_exception & ex) { + mk_c(c)->handle_exception(ex); + return 0; + } + } + } + + double Z3_API Z3_probe_apply(Z3_context c, Z3_probe p, Z3_goal g) { + Z3_TRY; + LOG_Z3_probe_apply(c, p, g); + RESET_ERROR_CODE(); + return to_probe_ref(p)->operator()(*to_goal_ref(g)).get_value(); + Z3_CATCH_RETURN(0); + } + + Z3_apply_result Z3_API Z3_tactic_apply(Z3_context c, Z3_tactic t, Z3_goal g) { + Z3_TRY; + LOG_Z3_tactic_apply(c, t, g); + RESET_ERROR_CODE(); + params_ref p; + Z3_apply_result r = _tactic_apply(c, t, g, p); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + Z3_apply_result Z3_API Z3_tactic_apply_ex(Z3_context c, Z3_tactic t, Z3_goal g, Z3_params p) { + Z3_TRY; + LOG_Z3_tactic_apply_ex(c, t, g, p); + RESET_ERROR_CODE(); + Z3_apply_result r = _tactic_apply(c, t, g, to_param_ref(p)); + RETURN_Z3(r); + Z3_CATCH_RETURN(0); + } + + void Z3_API Z3_apply_result_inc_ref(Z3_context c, Z3_apply_result r) { + Z3_TRY; + LOG_Z3_apply_result_inc_ref(c, r); + RESET_ERROR_CODE(); + to_apply_result(r)->inc_ref(); + Z3_CATCH; + } + + void Z3_API Z3_apply_result_dec_ref(Z3_context c, Z3_apply_result r) { + Z3_TRY; + LOG_Z3_apply_result_dec_ref(c, r); + RESET_ERROR_CODE(); + to_apply_result(r)->dec_ref(); + Z3_CATCH; + } + + Z3_string Z3_API Z3_apply_result_to_string(Z3_context c, Z3_apply_result r) { + Z3_TRY; + LOG_Z3_apply_result_to_string(c, r); + RESET_ERROR_CODE(); + std::ostringstream buffer; + buffer << "(goals\n"; + unsigned sz = to_apply_result(r)->m_subgoals.size(); + for (unsigned i = 0; i < sz; i++) { + to_apply_result(r)->m_subgoals[i]->display(buffer); + } + buffer << ")"; + return mk_c(c)->mk_external_string(buffer.str()); + Z3_CATCH_RETURN(""); + } + + unsigned Z3_API Z3_apply_result_get_num_subgoals(Z3_context c, Z3_apply_result r) { + Z3_TRY; + LOG_Z3_apply_result_get_num_subgoals(c, r); + RESET_ERROR_CODE(); + return to_apply_result(r)->m_subgoals.size(); + Z3_CATCH_RETURN(0); + } + + Z3_goal Z3_API Z3_apply_result_get_subgoal(Z3_context c, Z3_apply_result r, unsigned i) { + Z3_TRY; + LOG_Z3_apply_result_get_subgoal(c, r, i); + RESET_ERROR_CODE(); + if (i > to_apply_result(r)->m_subgoals.size()) { + SET_ERROR_CODE(Z3_IOB); + RETURN_Z3(0); + } + Z3_goal_ref * g = alloc(Z3_goal_ref); + g->m_goal = to_apply_result(r)->m_subgoals[i]; + mk_c(c)->save_object(g); + Z3_goal result = of_goal(g); + RETURN_Z3(result); + Z3_CATCH_RETURN(0); + } + + Z3_model Z3_API Z3_apply_result_convert_model(Z3_context c, Z3_apply_result r, unsigned i, Z3_model m) { + Z3_TRY; + LOG_Z3_apply_result_convert_model(c, r, i, m); + RESET_ERROR_CODE(); + if (i > to_apply_result(r)->m_subgoals.size()) { + SET_ERROR_CODE(Z3_IOB); + RETURN_Z3(0); + } + model_ref new_m = to_model_ref(m)->copy(); + if (to_apply_result(r)->m_mc) + to_apply_result(r)->m_mc->operator()(new_m, i); + Z3_model_ref * m_ref = alloc(Z3_model_ref); + m_ref->m_model = new_m; + mk_c(c)->save_object(m_ref); + RETURN_Z3(of_model(m_ref)); + Z3_CATCH_RETURN(0); + } + +}; diff --git a/lib/api_tactic.h b/lib/api_tactic.h new file mode 100644 index 000000000..80b24ff1a --- /dev/null +++ b/lib/api_tactic.h @@ -0,0 +1,54 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_tactic.h + +Abstract: + API for creating tactics and goals. + +Author: + + Leonardo de Moura (leonardo) 2012-03-06. + +Revision History: + +--*/ +#ifndef _API_TACTIC_H_ +#define _API_TACTIC_H_ + +#include"api_goal.h" +#include"tactical.h" + +struct Z3_tactic_ref : public api::object { + tactic_ref m_tactic; + virtual ~Z3_tactic_ref() {} +}; + +struct Z3_probe_ref : public api::object { + probe_ref m_probe; + virtual ~Z3_probe_ref() {} +}; + +inline Z3_tactic_ref * to_tactic(Z3_tactic g) { return reinterpret_cast(g); } +inline Z3_tactic of_tactic(Z3_tactic_ref * g) { return reinterpret_cast(g); } +inline tactic * to_tactic_ref(Z3_tactic g) { return g == 0 ? 0 : to_tactic(g)->m_tactic.get(); } + +inline Z3_probe_ref * to_probe(Z3_probe g) { return reinterpret_cast(g); } +inline Z3_probe of_probe(Z3_probe_ref * g) { return reinterpret_cast(g); } +inline probe * to_probe_ref(Z3_probe g) { return g == 0 ? 0 : to_probe(g)->m_probe.get(); } + +struct Z3_apply_result_ref : public api::object { + goal_ref_buffer m_subgoals; + model_converter_ref m_mc; + proof_converter_ref m_pc; + expr_dependency_ref m_core; + Z3_apply_result_ref(ast_manager & m); + virtual ~Z3_apply_result_ref() {} +}; + +inline Z3_apply_result_ref * to_apply_result(Z3_apply_result g) { return reinterpret_cast(g); } +inline Z3_apply_result of_apply_result(Z3_apply_result_ref * g) { return reinterpret_cast(g); } + +#endif diff --git a/lib/api_user_theory.cpp b/lib/api_user_theory.cpp new file mode 100644 index 000000000..bb0c582bf --- /dev/null +++ b/lib/api_user_theory.cpp @@ -0,0 +1,333 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_user_theory.cpp + +Abstract: + API for external theories + +Author: + + Leonardo de Moura (leonardo) 2012-02-29. + +Revision History: + +--*/ +#include +#include"z3.h" +#include"api_log_macros.h" +#include"api_context.h" +#include"api_util.h" +#include"user_smt_theory.h" + +smt::user_theory * mk_t(Z3_theory t) { + return reinterpret_cast(t); +} + +extern "C" { + + /////////////////////////////// + // Theory plugin + // No support for logging + + Z3_theory Z3_mk_theory(Z3_context c, Z3_string th_name, void * ext_data) { + Z3_TRY; + RESET_ERROR_CODE(); + if (mk_c(c)->get_solver().get_scope_level() > 0) { + SET_ERROR_CODE(Z3_INVALID_USAGE); + return 0; + } + return reinterpret_cast(mk_user_theory(mk_c(c)->get_solver(), c, ext_data, th_name)); + Z3_CATCH_RETURN(0); + } + + void * Z3_theory_get_ext_data(Z3_theory t) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + void * r = mk_t(t)->get_ext_data(); + return r; + Z3_CATCH_RETURN(0); + } + + Z3_sort Z3_theory_mk_sort(Z3_context c, Z3_theory t, Z3_symbol s) { + Z3_TRY; + RESET_ERROR_CODE(); + sort * r = mk_t(t)->mk_sort(to_symbol(s)); + mk_c(c)->save_ast_trail(r); + return of_sort(r); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_theory_mk_value(Z3_context c, Z3_theory t, Z3_symbol n, Z3_sort s) { + Z3_TRY; + RESET_ERROR_CODE(); + func_decl * d = mk_t(t)->mk_value_decl(to_symbol(n), to_sort(s)); + app * r = mk_c(c)->m().mk_const(d); + mk_c(c)->save_ast_trail(r); + return of_ast(r); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_theory_mk_constant(Z3_context c, Z3_theory t, Z3_symbol n, Z3_sort s) { + Z3_TRY; + RESET_ERROR_CODE(); + Z3_func_decl d = Z3_theory_mk_func_decl(c, t, n, 0, 0, s); + app * r = mk_c(c)->m().mk_const(to_func_decl(d)); + mk_c(c)->save_ast_trail(r); + return of_ast(r); + Z3_CATCH_RETURN(0); + } + + Z3_func_decl Z3_theory_mk_func_decl(Z3_context c, Z3_theory t, Z3_symbol n, + unsigned domain_size, Z3_sort const domain[], + Z3_sort range) { + Z3_TRY; + RESET_ERROR_CODE(); + func_decl * r = mk_t(t)->mk_func_decl(to_symbol(n), domain_size, to_sorts(domain), to_sort(range)); + mk_c(c)->save_ast_trail(r); + return of_func_decl(r); + Z3_CATCH_RETURN(0); + } + + Z3_context Z3_theory_get_context(Z3_theory t) { + Z3_context c = reinterpret_cast(mk_t(t)->get_ext_context()); + RESET_ERROR_CODE(); + return c; + } + + void Z3_set_delete_callback(Z3_theory t, Z3_theory_callback_fptr f) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + mk_t(t)->set_delete_fptr(reinterpret_cast(f)); + Z3_CATCH; + } + + void Z3_set_reduce_app_callback(Z3_theory t, Z3_reduce_app_callback_fptr f) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + mk_t(t)->set_reduce_app_fptr(reinterpret_cast(f)); + Z3_CATCH; + } + + void Z3_set_reduce_eq_callback(Z3_theory t, Z3_reduce_eq_callback_fptr f) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + mk_t(t)->set_reduce_eq_fptr(reinterpret_cast(f)); + Z3_CATCH; + } + + void Z3_set_reduce_distinct_callback(Z3_theory t, Z3_reduce_distinct_callback_fptr f) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + mk_t(t)->set_reduce_distinct_fptr(reinterpret_cast(f)); + Z3_CATCH; + } + + void Z3_set_new_app_callback(Z3_theory t, Z3_theory_ast_callback_fptr f) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + mk_t(t)->set_new_app_fptr(reinterpret_cast(f)); + Z3_CATCH; + } + + void Z3_set_new_elem_callback(Z3_theory t, Z3_theory_ast_callback_fptr f) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + mk_t(t)->set_new_elem_fptr(reinterpret_cast(f)); + Z3_CATCH; + } + + void Z3_set_init_search_callback(Z3_theory t, Z3_theory_callback_fptr f) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + mk_t(t)->set_init_search_fptr(reinterpret_cast(f)); + Z3_CATCH; + } + + void Z3_set_push_callback(Z3_theory t, Z3_theory_callback_fptr f) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + mk_t(t)->set_push_fptr(reinterpret_cast(f)); + Z3_CATCH; + } + + void Z3_set_pop_callback(Z3_theory t, Z3_theory_callback_fptr f) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + mk_t(t)->set_pop_fptr(reinterpret_cast(f)); + Z3_CATCH; + } + + void Z3_set_restart_callback(Z3_theory t, Z3_theory_callback_fptr f) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + mk_t(t)->set_restart_fptr(reinterpret_cast(f)); + Z3_CATCH; + } + + void Z3_set_reset_callback(Z3_theory t, Z3_theory_callback_fptr f) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + mk_t(t)->set_reset_fptr(reinterpret_cast(f)); + Z3_CATCH; + } + + void Z3_set_final_check_callback(Z3_theory t, Z3_theory_final_check_callback_fptr f) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + mk_t(t)->set_final_check_fptr(reinterpret_cast(f)); + Z3_CATCH; + } + + void Z3_set_new_eq_callback(Z3_theory t, Z3_theory_ast_ast_callback_fptr f) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + mk_t(t)->set_new_eq_fptr(reinterpret_cast(f)); + Z3_CATCH; + } + + void Z3_set_new_diseq_callback(Z3_theory t, Z3_theory_ast_ast_callback_fptr f) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + mk_t(t)->set_new_diseq_fptr(reinterpret_cast(f)); + Z3_CATCH; + } + + void Z3_set_new_assignment_callback(Z3_theory t, Z3_theory_ast_bool_callback_fptr f) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + mk_t(t)->set_new_assignment_fptr(reinterpret_cast(f)); + Z3_CATCH; + } + + void Z3_set_new_relevant_callback(Z3_theory t, Z3_theory_ast_callback_fptr f) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + mk_t(t)->set_new_relevant_fptr(reinterpret_cast(f)); + Z3_CATCH; + } + + void Z3_theory_assert_axiom(Z3_theory t, Z3_ast ax) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + mk_t(t)->assert_axiom(to_ast(ax)); + Z3_CATCH; + } + + void Z3_theory_assume_eq(Z3_theory t, Z3_ast lhs, Z3_ast rhs) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + mk_t(t)->assume_eq(to_ast(lhs), to_ast(rhs)); + Z3_CATCH; + } + + void Z3_theory_enable_axiom_simplification(Z3_theory t, Z3_bool flag) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + mk_t(t)->enable_axiom_simplification(flag == Z3_TRUE); + Z3_CATCH; + } + + Z3_ast Z3_theory_get_eqc_root(Z3_theory t, Z3_ast n) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + return of_ast(mk_t(t)->get_root(to_ast(n))); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_theory_get_eqc_next(Z3_theory t, Z3_ast n) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + return of_ast(mk_t(t)->get_next(to_ast(n))); + Z3_CATCH_RETURN(0); + } + + unsigned Z3_theory_get_num_parents(Z3_theory t, Z3_ast n) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + return mk_t(t)->get_num_parents(to_ast(n)); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_theory_get_parent(Z3_theory t, Z3_ast n, unsigned i) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + return of_ast(mk_t(t)->get_parent(to_ast(n), i)); + Z3_CATCH_RETURN(0); + } + + unsigned Z3_theory_get_num_elems(Z3_theory t) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + return mk_t(t)->get_num_asts(); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_theory_get_elem(Z3_theory t, unsigned i) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + return of_ast(mk_t(t)->get_ast(i)); + Z3_CATCH_RETURN(0); + } + + unsigned Z3_theory_get_num_apps(Z3_theory t) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + return mk_t(t)->get_num_parents(); + Z3_CATCH_RETURN(0); + } + + Z3_ast Z3_theory_get_app(Z3_theory t, unsigned i) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + return of_ast(mk_t(t)->get_parent(i)); + Z3_CATCH_RETURN(0); + } + + Z3_bool Z3_theory_is_value(Z3_theory t, Z3_ast n) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + return is_app(to_ast(n)) && mk_t(t)->get_family_id() == to_app(to_ast(n))->get_family_id(); + Z3_CATCH_RETURN(Z3_FALSE); + } + + Z3_bool Z3_theory_is_decl(Z3_theory t, Z3_func_decl d) { + Z3_context c = Z3_theory_get_context(t); + Z3_TRY; + RESET_ERROR_CODE(); + return mk_t(t)->get_family_id() == to_func_decl(d)->get_family_id(); + Z3_CATCH_RETURN(Z3_FALSE); + } + +}; diff --git a/lib/api_util.h b/lib/api_util.h new file mode 100644 index 000000000..d7c50aec7 --- /dev/null +++ b/lib/api_util.h @@ -0,0 +1,149 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + api_util.h + +Abstract: + Goodies used to build the Z3 external API. + +Author: + + Leonardo de Moura (leonardo) 2012-02-29. + +Revision History: + +--*/ +#ifndef _API_UTIL_H_ +#define _API_UTIL_H_ + +#include"params.h" +#include"lbool.h" + +#define Z3_TRY try { +#define Z3_CATCH_CORE(CODE) } catch (z3_exception & ex) { mk_c(c)->handle_exception(ex); CODE } +#define Z3_CATCH Z3_CATCH_CORE(return;) +#define Z3_CATCH_RETURN(VAL) Z3_CATCH_CORE(return VAL;) + +#define CHECK_REF_COUNT(a) (reinterpret_cast(a)->get_ref_count() > 0) +#define VALIDATE(a) SASSERT(!a || CHECK_REF_COUNT(a)) + +namespace api { + // Generic wrapper for ref-count objects exposed by the API + class object { + unsigned m_ref_count; + public: + object():m_ref_count(0) {} + virtual ~object() {} + void inc_ref() { m_ref_count++; } + void dec_ref() { SASSERT(m_ref_count > 0); m_ref_count--; if (m_ref_count == 0) dealloc(this); } + }; +}; + +inline ast * to_ast(Z3_ast a) { VALIDATE(a); return reinterpret_cast(a); } +inline Z3_ast of_ast(ast* a) { return reinterpret_cast(a); } + +inline expr * to_expr(Z3_ast a) { VALIDATE(a); return reinterpret_cast(a); } +inline Z3_ast of_expr(expr* e) { return reinterpret_cast(e); } + +inline expr * const * to_exprs(Z3_ast const* a) { return reinterpret_cast(a); } +inline Z3_ast * const * of_exprs(expr* const* e) { return reinterpret_cast(e); } + +inline app * to_app(Z3_app a) { VALIDATE(a); return reinterpret_cast(a); } +inline app * to_app(Z3_ast a) { VALIDATE(a); return reinterpret_cast(a); } +inline Z3_app of_app(app* a) { return reinterpret_cast(a); } + +inline app * const* to_apps(Z3_ast const* a) { VALIDATE(a); return reinterpret_cast(a); } + +inline ast * const * to_asts(Z3_ast const* a) { return reinterpret_cast(a); } + +inline sort * to_sort(Z3_sort a) { VALIDATE(a); return reinterpret_cast(a); } +inline Z3_sort of_sort(sort* s) { return reinterpret_cast(s); } + +inline sort * const * to_sorts(Z3_sort const* a) { return reinterpret_cast(a); } +inline Z3_sort const * of_sorts(sort* const* s) { return reinterpret_cast(s); } + +inline func_decl * to_func_decl(Z3_func_decl a) { VALIDATE(a); return reinterpret_cast(a); } +inline Z3_func_decl of_func_decl(func_decl* f) { return reinterpret_cast(f); } + +inline func_decl * const * to_func_decls(Z3_func_decl const* f) { return reinterpret_cast(f); } + +inline symbol to_symbol(Z3_symbol s) { return symbol::mk_symbol_from_c_ptr(reinterpret_cast(s)); } +inline Z3_symbol of_symbol(symbol s) { return reinterpret_cast(const_cast(s.c_ptr())); } + +inline Z3_pattern of_pattern(ast* a) { VALIDATE(a); return reinterpret_cast(a); } +inline app* to_pattern(Z3_pattern p) { return reinterpret_cast(p); } + +inline Z3_lbool of_lbool(lbool b) { return static_cast(b); } +inline lbool to_lbool(Z3_lbool b) { return static_cast(b); } + +struct Z3_params_ref : public api::object { + params_ref m_params; + virtual ~Z3_params_ref() {} +}; + +inline Z3_params_ref * to_params(Z3_params p) { return reinterpret_cast(p); } +inline Z3_params of_params(Z3_params_ref * p) { return reinterpret_cast(p); } +inline params_ref to_param_ref(Z3_params p) { return p == 0 ? params_ref() : to_params(p)->m_params; } + +struct Z3_param_descrs_ref : public api::object { + param_descrs m_descrs; + virtual ~Z3_param_descrs_ref() {} +}; + +inline Z3_param_descrs_ref * to_param_descrs(Z3_param_descrs p) { return reinterpret_cast(p); } +inline Z3_param_descrs of_param_descrs(Z3_param_descrs_ref * p) { return reinterpret_cast(p); } +inline param_descrs * to_param_descrs_ptr(Z3_param_descrs p) { return p == 0 ? 0 : &(to_param_descrs(p)->m_descrs); } + + +#define SKIP ((void) 0) + +#define MK_UNARY_BODY(NAME, FID, OP, EXTRA_CODE) \ + Z3_TRY; \ + RESET_ERROR_CODE(); \ + EXTRA_CODE; \ + expr * _n = to_expr(n); \ + ast* a = mk_c(c)->m().mk_app(FID, OP, 0, 0, 1, &_n); \ + mk_c(c)->save_ast_trail(a); \ + check_sorts(c, a); \ + RETURN_Z3(of_ast(a)); \ + Z3_CATCH_RETURN(0); + +#define MK_UNARY(NAME, FID, OP, EXTRA_CODE) \ +Z3_ast Z3_API NAME(Z3_context c, Z3_ast n) { \ + LOG_ ## NAME(c, n); \ + MK_UNARY_BODY(NAME, FID, OP, EXTRA_CODE); \ +} + +#define MK_BINARY_BODY(NAME, FID, OP, EXTRA_CODE) \ + Z3_TRY; \ + RESET_ERROR_CODE(); \ + EXTRA_CODE; \ + expr * args[2] = { to_expr(n1), to_expr(n2) }; \ + ast* a = mk_c(c)->m().mk_app(FID, OP, 0, 0, 2, args); \ + mk_c(c)->save_ast_trail(a); \ + check_sorts(c, a); \ + RETURN_Z3(of_ast(a)); \ + Z3_CATCH_RETURN(0); + +#define MK_BINARY(NAME, FID, OP, EXTRA_CODE) \ +Z3_ast Z3_API NAME(Z3_context c, Z3_ast n1, Z3_ast n2) { \ + LOG_ ## NAME(c, n1, n2); \ + MK_BINARY_BODY(NAME, FID, OP, EXTRA_CODE); \ +} + +#define MK_NARY(NAME, FID, OP, EXTRA_CODE) \ +Z3_ast Z3_API NAME(Z3_context c, unsigned num_args, Z3_ast const* args) { \ + Z3_TRY; \ + LOG_ ## NAME(c, num_args, args); \ + RESET_ERROR_CODE(); \ + EXTRA_CODE; \ + ast* a = mk_c(c)->m().mk_app(FID, OP, 0, 0, num_args, to_exprs(args)); \ + mk_c(c)->save_ast_trail(a); \ + check_sorts(c, a); \ + RETURN_Z3(of_ast(a)); \ + Z3_CATCH_RETURN(0); \ +} + +#endif diff --git a/lib/approx_nat.cpp b/lib/approx_nat.cpp new file mode 100644 index 000000000..0739a65aa --- /dev/null +++ b/lib/approx_nat.cpp @@ -0,0 +1,61 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + approx_nat.cpp + +Abstract: + + Approximated natural numbers. It performs operations on the set [0, ..., 2^{n-2}, huge]. + Where huge represents all numbers greater than 2^{n-2}. + +Author: + + Leonardo (leonardo) 2008-01-11 + +Notes: + +--*/ +#include"approx_nat.h" + +approx_nat::approx_nat(unsigned val) { + m_value = val > m_limit ? UINT_MAX : val; +} + +approx_nat & approx_nat::operator=(unsigned val) { + m_value = val > m_limit ? UINT_MAX : val; + return *this; +} + +approx_nat & approx_nat::operator+=(unsigned w) { + if (is_huge()) + return *this; + if (w > m_limit) { + m_value = UINT_MAX; + return *this; + } + m_value += w; + if (m_value > m_limit) + m_value = UINT_MAX; + return *this; +} + +approx_nat & approx_nat::operator*=(unsigned w) { + if (is_huge()) + return *this; + unsigned long long r = static_cast(m_value) * static_cast(w); + if (r > m_limit) + m_value = UINT_MAX; + else + m_value = static_cast(r); + return *this; +} + +std::ostream & operator<<(std::ostream & target, approx_nat const & w) { + if (w.is_huge()) + target << "[huge]"; + else + target << w.get_value(); + return target; +} diff --git a/lib/approx_nat.h b/lib/approx_nat.h new file mode 100644 index 000000000..6ccc20ce2 --- /dev/null +++ b/lib/approx_nat.h @@ -0,0 +1,45 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + approx_nat.h + +Abstract: + + Approximated natural numbers. It performs operations on the set [0, ..., 2^{n-2}, huge]. + Where huge represents all numbers greater than 2^{n-2}. + +Author: + + Leonardo (leonardo) 2008-01-11 + +Notes: + +--*/ +#ifndef _APPROX_NAT_H_ +#define _APPROX_NAT_H_ + +#include +#include + +class approx_nat { + unsigned m_value; + static const unsigned m_limit = UINT_MAX >> 2; +public: + approx_nat():m_value(0) {} + explicit approx_nat(unsigned val); + bool is_huge() const { return m_value == UINT_MAX; } + unsigned get_value() const { return m_value; } + approx_nat & operator=(unsigned w); + approx_nat & operator+=(unsigned w); + approx_nat & operator+=(approx_nat const & w) { return operator+=(w.m_value); } + approx_nat & operator*=(unsigned w); + approx_nat & operator*=(approx_nat const & w) { return operator*=(w.m_value); } + bool operator<(unsigned w) const { return !is_huge() && m_value < w; } + bool operator<(approx_nat const & w) const { return !is_huge() && !w.is_huge() && m_value < w.m_value; } +}; + +std::ostream & operator<<(std::ostream & target, approx_nat const & w); + +#endif /* _APPROX_NAT_H_ */ diff --git a/lib/approx_set.cpp b/lib/approx_set.cpp new file mode 100644 index 000000000..e6757cf05 --- /dev/null +++ b/lib/approx_set.cpp @@ -0,0 +1,52 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + approx_set.cpp + +Abstract: + + Approximated sets. + +Author: + + Leonardo de Moura (leonardo) 2007-03-02. + +Revision History: + +--*/ + +#include"approx_set.h" + +void approx_set::display(std::ostream & out) const { + out << "{"; + bool first = true; + unsigned long long s = m_set; + for (unsigned i = 0; i < approx_set_traits::capacity; i++) { + if ((s & 1) != 0) { + if (first) { + first = false; + } + else { + out << ", "; + } + out << i; + } + s = s >> 1; + } + out << "}"; +} + +unsigned approx_set::size() const { + unsigned long long tmp = m_set; + unsigned r = 0; + while (tmp > 0) { + if ((tmp & 1) != 0) { + r++; + } + tmp = tmp >> 1; + } + return r; +} + diff --git a/lib/approx_set.h b/lib/approx_set.h new file mode 100644 index 000000000..9c769f299 --- /dev/null +++ b/lib/approx_set.h @@ -0,0 +1,236 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + approx_set.h + +Abstract: + + Approximated sets. + +Author: + + Leonardo de Moura (leonardo) 2007-03-02. + +Revision History: + +--*/ +#ifndef _APPROX_SET_H_ +#define _APPROX_SET_H_ +#include +#include"debug.h" + +template class approx_set_traits; + +template <> class approx_set_traits { +public: + static const unsigned capacity = 64; + static const unsigned long long zero = 0ull; + static const unsigned long long one = 1ull; +}; +COMPILE_TIME_ASSERT(sizeof(unsigned long long) == 8); + +template <> class approx_set_traits { +public: + static const unsigned capacity = 32; + static const unsigned zero = 0; + static const unsigned one = 1; +}; +COMPILE_TIME_ASSERT(sizeof(unsigned) == 4); + +template +class approx_set_tpl : private T2U_Proc { +protected: + R m_set; + + unsigned e2u(T const & e) const { return T2U_Proc::operator()(e); } + + R u2s(unsigned u) const { return (approx_set_traits::one << (u & (approx_set_traits::capacity - 1))); } + + R e2s(T const & e) const { return u2s(e2u(e)); } + + static approx_set_tpl r2s(R const & s) { approx_set_tpl r; r.m_set = s; return r; } + +public: + approx_set_tpl(): + m_set(approx_set_traits::zero) { + } + + explicit approx_set_tpl(T const & e): + m_set(e2s(e)) { + } + + approx_set_tpl(unsigned sz, T const * es): + m_set(approx_set_traits::zero) { + for (unsigned i = 0; i < sz; i++) + insert(es[i]); + } + + approx_set_tpl(approx_set_tpl const & s): + m_set(s.m_set) { + } + + void insert(T const & e) { + m_set |= e2s(e); + } + + bool may_contain(T const & e) const { + return (m_set & e2s(e)) != approx_set_traits::zero; + } + + bool must_not_contain(T const & e) const { + return !may_contain(e); + } + + friend inline approx_set_tpl mk_union(approx_set_tpl const & s1, approx_set_tpl const & s2) { + return r2s(s1.m_set | s2.m_set); + } + + friend inline approx_set_tpl mk_intersection(approx_set_tpl const & s1, approx_set_tpl const & s2) { + return r2s(s1.m_set & s2.m_set); + } + + void operator|=(approx_set_tpl const & other) { + m_set |= other.m_set; + } + + void operator&=(approx_set_tpl const & other) { + m_set &= other.m_set; + } + + void operator-=(approx_set_tpl const & other) { + m_set &= ~(other.m_set); + } + + bool empty() const { + return m_set == approx_set_traits::zero; + } + + friend inline bool empty(approx_set_tpl const & s) { + return s.empty(); + } + + bool must_not_subset(approx_set_tpl const & s2) const { + return (m_set & ~(s2.m_set)) != approx_set_traits::zero; + } + + friend inline bool must_not_subset(approx_set_tpl const & s1, approx_set_tpl const & s2) { + return s1.must_not_subset(s2); + } + + bool must_not_subsume(approx_set_tpl const & s2) const { + return must_not_subset(s2); + } + + friend inline bool must_not_subsume(approx_set_tpl const & s1, approx_set_tpl const & s2) { + return s1.must_not_subset(s2); + } + + friend inline bool must_not_eq(approx_set_tpl const & s1, approx_set_tpl const & s2) { return s1.m_set != s2.m_set; } + + friend inline bool may_eq(approx_set_tpl const & s1, approx_set_tpl const & s2) { return s1.m_set == s2.m_set; } + + /** + \brief Return if s1 and s2 are the same approximated set. + */ + bool equiv(approx_set_tpl const & s2) const { return m_set == s2.m_set; } + friend inline bool equiv(approx_set_tpl const & s1, approx_set_tpl const & s2) { return s1.m_set == s2.m_set; } + + /** + \brief Return true if the approximation of s1 is a subset of the approximation of s2. + */ + friend inline bool approx_subset(approx_set_tpl const & s1, approx_set_tpl const & s2) { + return s2.equiv(mk_union(s1, s2)); + } + + void reset() { + m_set = approx_set_traits::zero; + } + + bool empty_intersection(approx_set_tpl const & other) const { + return mk_intersection(*this, other).empty(); + } +}; + +struct u2u { unsigned operator()(unsigned u) const { return u; } }; + +typedef approx_set_tpl u_approx_set; + +#define APPROX_SET_CAPACITY (approx_set_traits::capacity) + +class approx_set : public u_approx_set { +public: + approx_set():u_approx_set() {} + approx_set(unsigned e):u_approx_set(e) {} + + class iterator { + unsigned long long m_set; + unsigned m_val; + void move_to_next() { + // TODO: this code can be optimized in platforms with special + // instructions to count leading (trailing) zeros in a word. + while (m_set > 0) { + if ((m_set & 1ull) != 0) { + return; + } + m_val ++; + m_set = m_set >> 1; + } + } + public: + iterator(unsigned long long s): + m_set(s), + m_val(0) { + move_to_next(); + } + + unsigned operator*() const { + return m_val; + } + + iterator & operator++() { + m_val++; + m_set = m_set >> 1; + move_to_next(); + return *this; + } + + iterator operator++(int) { + iterator tmp = *this; + ++*this; + return tmp; + } + + bool operator==(iterator const & it) const { + return m_set == it.m_set; + } + + bool operator!=(iterator const & it) const { + return m_set != it.m_set; + } + }; + + iterator begin() const { + return iterator(m_set); + } + + static iterator end() { + return iterator(0); + } + + void display(std::ostream & out) const; + + unsigned size() const; + + // for backward compatibility + friend inline bool operator==(approx_set const & s1, approx_set const & s2) { return may_eq(s1, s2); } +}; + +inline std::ostream & operator<<(std::ostream & out, approx_set const & s) { + s.display(out); + return out; +} + +#endif /* _APPROX_SET_H_ */ + diff --git a/lib/arith_bounds_tactic.cpp b/lib/arith_bounds_tactic.cpp new file mode 100644 index 000000000..afafbb4b5 --- /dev/null +++ b/lib/arith_bounds_tactic.cpp @@ -0,0 +1,161 @@ + + +#include"arith_bounds_tactic.h" +#include"assertion_set_util.h" +#include"arith_decl_plugin.h" + +struct arith_bounds_tactic : public tactic { + + ast_manager& m; + arith_util a; + volatile bool m_cancel; + + arith_bounds_tactic(ast_manager& m): + m(m), + a(m), + m_cancel(false) + { + } + + ast_manager& get_manager() { return m; } + + void set_cancel(bool f) { + m_cancel = f; + } + + virtual void cleanup() { + m_cancel = false; + } + + virtual void operator()(/* in */ goal_ref const & in, + /* out */ goal_ref_buffer & result, + /* out */ model_converter_ref & mc, + /* out */ proof_converter_ref & pc, + /* out */ expr_dependency_ref & core) { + bounds_arith_subsumption(in, result); + } + + virtual tactic* translate(ast_manager& m) { + return alloc(arith_bounds_tactic, m); + } + + void checkpoint() { + if (m_cancel) { + throw tactic_exception(TACTIC_CANCELED_MSG); + } + } + + + struct info { rational r; unsigned idx; bool is_strict;}; + + /** + \brief Basic arithmetic subsumption simplification based on bounds. + */ + + void mk_proof(proof_ref& pr, goal_ref const& s, unsigned i, unsigned j) { + if (s->proofs_enabled()) { + proof* th_lemma = m.mk_th_lemma(a.get_family_id(), m.mk_implies(s->form(i), s->form(j)), 0, 0); + pr = m.mk_modus_ponens(s->pr(i), th_lemma); + } + } + + + bool is_le_or_lt(expr* e, expr*& e1, expr*& e2, bool& is_strict) { + bool is_negated = m.is_not(e, e); + if ((!is_negated && (a.is_le(e, e1, e2) || a.is_ge(e, e2, e1))) || + (is_negated && (a.is_lt(e, e2, e1) || a.is_gt(e, e1, e2)))) { + is_strict = false; + return true; + } + if ((!is_negated && (a.is_lt(e, e1, e2) || a.is_gt(e, e2, e1))) || + (is_negated && (a.is_le(e, e2, e1) || a.is_ge(e, e1, e2)))) { + is_strict = true; + return true; + } + return false; + } + + + + void bounds_arith_subsumption(goal_ref const& g, goal_ref_buffer& result) { + info inf; + rational r; + goal_ref s(g); // initialize result. + obj_map lower, upper; + expr* e1, *e2; + TRACE("arith_subsumption", s->display(tout); ); + for (unsigned i = 0; i < s->size(); ++i) { + checkpoint(); + expr* lemma = s->form(i); + bool is_strict = false; + bool is_lower = false; + if (!is_le_or_lt(lemma, e1, e2, is_strict)) { + continue; + } + // e1 <= e2 or e1 < e2 + if (a.is_numeral(e2, r)) { + is_lower = true; + } + else if (a.is_numeral(e1, r)) { + is_lower = false; + } + else { + continue; + } + proof_ref new_pr(m); + + if (is_lower && upper.find(e1, inf)) { + if (inf.r > r || (inf.r == r && is_strict && !inf.is_strict)) { + mk_proof(new_pr, s, i, inf.idx); + s->update(inf.idx, m.mk_true(), new_pr); + inf.r = r; + inf.is_strict = is_strict; + inf.idx = i; + upper.insert(e1, inf); + } + else { + mk_proof(new_pr, s, inf.idx, i); + s->update(i, m.mk_true(), new_pr); + } + } + else if (is_lower) { + inf.r = r; + inf.is_strict = is_strict; + inf.idx = i; + upper.insert(e1, inf); + } + else if (!is_lower && lower.find(e2, inf)) { + if (inf.r < r || (inf.r == r && is_strict && !inf.is_strict)) { + mk_proof(new_pr, s, i, inf.idx); + s->update(inf.idx, m.mk_true(), new_pr); + inf.r = r; + inf.is_strict = is_strict; + inf.idx = i; + lower.insert(e2, inf); + } + else { + mk_proof(new_pr, s, inf.idx, i); + s->update(i, m.mk_true()); + } + } + else if (!is_lower) { + inf.r = r; + inf.is_strict = is_strict; + inf.idx = i; + lower.insert(e2, inf); + } + } + s->elim_true(); + result.push_back(s.get()); + TRACE("arith_subsumption", s->display(tout); ); + } + + +}; + + +tactic * mk_arith_bounds_tactic(ast_manager & m, params_ref const & p) { + return alloc(arith_bounds_tactic, m); +} + + diff --git a/lib/arith_bounds_tactic.h b/lib/arith_bounds_tactic.h new file mode 100644 index 000000000..c77bf7b48 --- /dev/null +++ b/lib/arith_bounds_tactic.h @@ -0,0 +1,37 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + arith_bounds_tactic.h + +Abstract: + + Fast/rudimentary arithmetic subsumption tactic. + +Author: + + Nikolaj Bjorner (nbjorner) 2012-9-6 + +Notes: + + Background: The Farkas learner in PDR generates tons + of inequalities that contain redundancies. + It therefore needs a fast way to reduce these redundancies before + passing the results to routines that are more expensive. + The arith subsumption_strategy encapsulates a rudimentary + routine for simplifying inequalities. Additional simplification + routines can be added here or composed with this strategy. + + Note: The bound_manager subsumes some of the collection methods used + for assembling bounds, but it does not have a way to check for + subsumption of atoms. + +--*/ +#ifndef _ARITH_BOUNDS_TACTIC_H_ +#define _ARITH_BOUNDS_TACTIC_H_ +#include "tactic.h" + +tactic * mk_arith_bounds_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/arith_decl_plugin.cpp b/lib/arith_decl_plugin.cpp new file mode 100644 index 000000000..926e91c20 --- /dev/null +++ b/lib/arith_decl_plugin.cpp @@ -0,0 +1,558 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + arith_decl_plugin.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-09 + +Revision History: + +--*/ +#include"arith_decl_plugin.h" +#include"warning.h" +#include"algebraic_numbers.h" +#include"id_gen.h" +#include"ast_smt2_pp.h" + +struct arith_decl_plugin::algebraic_numbers_wrapper { + unsynch_mpq_manager m_qmanager; + algebraic_numbers::manager m_amanager; + id_gen m_id_gen; + scoped_anum_vector m_nums; + + algebraic_numbers_wrapper(): + m_amanager(m_qmanager), + m_nums(m_amanager) { + } + + ~algebraic_numbers_wrapper() { + } + + unsigned mk_id(algebraic_numbers::anum const & val) { + SASSERT(!m_amanager.is_rational(val)); + // TODO: avoid linear scan. Use hashtable based on the floor of val + unsigned sz = m_nums.size(); + for (unsigned i = 0; i < sz; i++) { + algebraic_numbers::anum const & other = m_nums.get(i); + if (m_amanager.eq(val, other)) + return i; + } + unsigned new_id = m_id_gen.mk(); + m_nums.reserve(new_id+1); + m_amanager.set(m_nums[new_id], val); + TRACE("algebraic2expr", tout << "mk_id -> " << new_id << "\n"; m_amanager.display(tout, val); tout << "\n";); + return new_id; + } + + void recycle_id(unsigned idx) { + SASSERT(idx < m_nums.size()); + SASSERT(!m_amanager.is_zero(m_nums[idx])); + TRACE("algebraic2expr", tout << "recycling: " << idx << "\n";); + m_id_gen.recycle(idx); + m_amanager.del(m_nums[idx]); + } + + algebraic_numbers::anum const & idx2anum(unsigned idx) { + return m_nums[idx]; + } + + algebraic_numbers::anum const & to_anum(func_decl * f) { + SASSERT(f->get_decl_kind() == OP_IRRATIONAL_ALGEBRAIC_NUM); + return idx2anum(f->get_parameter(0).get_ext_id()); + } + +}; + +arith_decl_plugin::algebraic_numbers_wrapper & arith_decl_plugin::aw() { + if (m_aw == 0) + m_aw = alloc(algebraic_numbers_wrapper); + return *m_aw; +} + +algebraic_numbers::manager & arith_decl_plugin::am() { + return aw().m_amanager; +} + +app * arith_decl_plugin::mk_numeral(algebraic_numbers::anum const & val, bool is_int) { + if (am().is_rational(val)) { + rational rval; + am().to_rational(val, rval); + return mk_numeral(rval, is_int); + } + else { + if (is_int) + m_manager->raise_exception("invalid irrational value passed as an integer"); + unsigned idx = aw().mk_id(val); + parameter p(idx, true); + SASSERT(p.is_external()); + func_decl * decl = m_manager->mk_const_decl(m_rootv_sym, m_real_decl, func_decl_info(m_family_id, OP_IRRATIONAL_ALGEBRAIC_NUM, 1, &p)); + return m_manager->mk_const(decl); + } +} + +app * arith_decl_plugin::mk_numeral(sexpr const * p, unsigned i) { + scoped_anum r(am()); + am().mk_root(p, i, r); + return mk_numeral(r, false); +} + +void arith_decl_plugin::del(parameter const & p) { + SASSERT(p.is_external()); + if (m_aw != 0) { + aw().recycle_id(p.get_ext_id()); + } +} + +parameter arith_decl_plugin::translate(parameter const & p, decl_plugin & target) { + SASSERT(p.is_external()); + arith_decl_plugin & _target = static_cast(target); + return parameter(_target.aw().mk_id(aw().idx2anum(p.get_ext_id())), true); +} + +void arith_decl_plugin::set_cancel(bool f) { + if (m_aw) + m_aw->m_amanager.set_cancel(f); +} + +void arith_decl_plugin::set_manager(ast_manager * m, family_id id) { + decl_plugin::set_manager(m, id); + + m_real_decl = m->mk_sort(symbol("Real"), sort_info(id, REAL_SORT)); + m->inc_ref(m_real_decl); + sort * r = m_real_decl; + + m_int_decl = m->mk_sort(symbol("Int"), sort_info(id, INT_SORT)); + m->inc_ref(m_int_decl); + sort * i = m_int_decl; + + sort * b = m->mk_bool_sort(); + +#define MK_PRED(FIELD, NAME, KIND, SORT) { \ + func_decl_info info(id, KIND); \ + info.set_chainable(true); \ + FIELD = m->mk_func_decl(symbol(NAME), SORT, SORT, b, info); \ + m->inc_ref(FIELD); \ + } + + MK_PRED(m_r_le_decl, "<=", OP_LE, r); + MK_PRED(m_r_ge_decl, ">=", OP_GE, r); + MK_PRED(m_r_lt_decl, "<", OP_LT, r); + MK_PRED(m_r_gt_decl, ">", OP_GT, r); + + MK_PRED(m_i_le_decl, "<=", OP_LE, i); + MK_PRED(m_i_ge_decl, ">=", OP_GE, i); + MK_PRED(m_i_lt_decl, "<", OP_LT, i); + MK_PRED(m_i_gt_decl, ">", OP_GT, i); + +#define MK_AC_OP(FIELD, NAME, KIND, SORT) { \ + func_decl_info info(id, KIND); \ + info.set_associative(); \ + info.set_flat_associative(); \ + info.set_commutative(); \ + FIELD = m->mk_func_decl(symbol(NAME), SORT, SORT, SORT, info); \ + m->inc_ref(FIELD); \ + } + +#define MK_OP(FIELD, NAME, KIND, SORT) \ + FIELD = m->mk_func_decl(symbol(NAME), SORT, SORT, SORT, func_decl_info(id, KIND)); \ + m->inc_ref(FIELD) + +#define MK_UNARY(FIELD, NAME, KIND, SORT) \ + FIELD = m->mk_func_decl(symbol(NAME), SORT, SORT, func_decl_info(id, KIND)); \ + m->inc_ref(FIELD) + + MK_AC_OP(m_r_add_decl, "+", OP_ADD, r); + MK_OP(m_r_sub_decl, "-", OP_SUB, r); + MK_AC_OP(m_r_mul_decl, "*", OP_MUL, r); + MK_OP(m_r_div_decl, "/", OP_DIV, r); + MK_UNARY(m_r_uminus_decl, "-", OP_UMINUS, r); + + MK_AC_OP(m_i_add_decl, "+", OP_ADD, i); + MK_OP(m_i_sub_decl, "-", OP_SUB, i); + MK_AC_OP(m_i_mul_decl, "*", OP_MUL, i); + MK_OP(m_i_div_decl, "div", OP_IDIV, i); + MK_OP(m_i_rem_decl, "rem", OP_REM, i); + MK_OP(m_i_mod_decl, "mod", OP_MOD, i); + MK_UNARY(m_i_uminus_decl, "-", OP_UMINUS, i); + + m_to_real_decl = m->mk_func_decl(symbol("to_real"), i, r, func_decl_info(id, OP_TO_REAL)); + m->inc_ref(m_to_real_decl); + m_to_int_decl = m->mk_func_decl(symbol("to_int"), r, i, func_decl_info(id, OP_TO_INT)); + m->inc_ref(m_to_int_decl); + m_is_int_decl = m->mk_func_decl(symbol("is_int"), r, m->mk_bool_sort(), func_decl_info(id, OP_IS_INT)); + m->inc_ref(m_is_int_decl); + + MK_OP(m_r_power_decl, "^", OP_POWER, r); + MK_OP(m_i_power_decl, "^", OP_POWER, i); + + MK_UNARY(m_sin_decl, "sin", OP_SIN, r); + MK_UNARY(m_cos_decl, "cos", OP_COS, r); + MK_UNARY(m_tan_decl, "tan", OP_TAN, r); + MK_UNARY(m_asin_decl, "asin", OP_ASIN, r); + MK_UNARY(m_acos_decl, "acos", OP_ACOS, r); + MK_UNARY(m_atan_decl, "atan", OP_ATAN, r); + MK_UNARY(m_sinh_decl, "sinh", OP_SINH, r); + MK_UNARY(m_cosh_decl, "cosh", OP_COSH, r); + MK_UNARY(m_tanh_decl, "tanh", OP_TANH, r); + MK_UNARY(m_asinh_decl, "asinh", OP_ASINH, r); + MK_UNARY(m_acosh_decl, "acosh", OP_ACOSH, r); + MK_UNARY(m_atanh_decl, "atanh", OP_ATANH, r); + + func_decl * pi_decl = m->mk_const_decl(symbol("pi"), r, func_decl_info(id, OP_PI)); + m_pi = m->mk_const(pi_decl); + m->inc_ref(m_pi); + + func_decl * e_decl = m->mk_const_decl(symbol("euler"), r, func_decl_info(id, OP_E)); + m_e = m->mk_const(e_decl); + m->inc_ref(m_e); +} + +arith_decl_plugin::arith_decl_plugin(): + m_aw(0), + m_intv_sym("Int"), + m_realv_sym("Real"), + m_rootv_sym("RootObject"), + m_real_decl(0), + m_int_decl(0), + m_r_le_decl(0), + m_r_ge_decl(0), + m_r_lt_decl(0), + m_r_gt_decl(0), + m_r_add_decl(0), + m_r_sub_decl(0), + m_r_uminus_decl(0), + m_r_mul_decl(0), + m_r_div_decl(0), + m_i_le_decl(0), + m_i_ge_decl(0), + m_i_lt_decl(0), + m_i_gt_decl(0), + m_i_add_decl(0), + m_i_sub_decl(0), + m_i_uminus_decl(0), + m_i_mul_decl(0), + m_i_div_decl(0), + m_i_mod_decl(0), + m_i_rem_decl(0), + m_to_real_decl(0), + m_to_int_decl(0), + m_is_int_decl(0), + m_r_power_decl(0), + m_i_power_decl(0), + m_sin_decl(0), + m_cos_decl(0), + m_tan_decl(0), + m_asin_decl(0), + m_acos_decl(0), + m_atan_decl(0), + m_sinh_decl(0), + m_cosh_decl(0), + m_tanh_decl(0), + m_asinh_decl(0), + m_acosh_decl(0), + m_atanh_decl(0), + m_pi(0), + m_e(0) { +} + +arith_decl_plugin::~arith_decl_plugin() { + dealloc(m_aw); +} + +void arith_decl_plugin::finalize() { +#define DEC_REF(decl) if (decl) { m_manager->dec_ref(decl); } ((void) 0) + DEC_REF(m_real_decl); + DEC_REF(m_int_decl); + DEC_REF(m_r_le_decl); + DEC_REF(m_r_ge_decl); + DEC_REF(m_r_lt_decl); + DEC_REF(m_r_gt_decl); + DEC_REF(m_r_add_decl); + DEC_REF(m_r_sub_decl); + DEC_REF(m_r_uminus_decl); + DEC_REF(m_r_mul_decl); + DEC_REF(m_r_div_decl); + DEC_REF(m_i_le_decl); + DEC_REF(m_i_ge_decl); + DEC_REF(m_i_lt_decl); + DEC_REF(m_i_gt_decl); + DEC_REF(m_i_add_decl); + DEC_REF(m_i_sub_decl); + DEC_REF(m_i_uminus_decl); + DEC_REF(m_i_mul_decl); + DEC_REF(m_i_div_decl); + DEC_REF(m_i_mod_decl); + DEC_REF(m_i_rem_decl); + DEC_REF(m_to_real_decl); + DEC_REF(m_to_int_decl); + DEC_REF(m_is_int_decl); + DEC_REF(m_i_power_decl); + DEC_REF(m_r_power_decl); + DEC_REF(m_sin_decl); + DEC_REF(m_cos_decl); + DEC_REF(m_tan_decl); + DEC_REF(m_asin_decl); + DEC_REF(m_acos_decl); + DEC_REF(m_atan_decl); + DEC_REF(m_sinh_decl); + DEC_REF(m_cosh_decl); + DEC_REF(m_tanh_decl); + DEC_REF(m_asinh_decl); + DEC_REF(m_acosh_decl); + DEC_REF(m_atanh_decl); + DEC_REF(m_pi); + DEC_REF(m_e); + m_manager->dec_array_ref(m_small_ints.size(), m_small_ints.c_ptr()); + m_manager->dec_array_ref(m_small_reals.size(), m_small_reals.c_ptr()); +} + +sort * arith_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) { + switch (k) { + case REAL_SORT: return m_real_decl; + case INT_SORT: return m_int_decl; + default: return 0; + } +} + +inline func_decl * arith_decl_plugin::mk_func_decl(decl_kind k, bool is_real) { + switch (k) { + case OP_LE: return is_real ? m_r_le_decl : m_i_le_decl; + case OP_GE: return is_real ? m_r_ge_decl : m_i_ge_decl; + case OP_LT: return is_real ? m_r_lt_decl : m_i_lt_decl; + case OP_GT: return is_real ? m_r_gt_decl : m_i_gt_decl; + case OP_ADD: return is_real ? m_r_add_decl : m_i_add_decl; + case OP_SUB: return is_real ? m_r_sub_decl : m_i_sub_decl; + case OP_UMINUS: return is_real ? m_r_uminus_decl : m_i_uminus_decl; + case OP_MUL: return is_real ? m_r_mul_decl : m_i_mul_decl; + case OP_DIV: SASSERT(is_real); return m_r_div_decl; + case OP_IDIV: SASSERT(!is_real); return m_i_div_decl; + case OP_REM: return m_i_rem_decl; + case OP_MOD: return m_i_mod_decl; + case OP_TO_REAL: return m_to_real_decl; + case OP_TO_INT: return m_to_int_decl; + case OP_IS_INT: return m_is_int_decl; + case OP_POWER: return is_real ? m_r_power_decl : m_i_power_decl; + case OP_SIN: return m_sin_decl; + case OP_COS: return m_cos_decl; + case OP_TAN: return m_tan_decl; + case OP_ASIN: return m_asin_decl; + case OP_ACOS: return m_acos_decl; + case OP_ATAN: return m_atan_decl; + case OP_SINH: return m_sinh_decl; + case OP_COSH: return m_cosh_decl; + case OP_TANH: return m_tanh_decl; + case OP_ASINH: return m_asinh_decl; + case OP_ACOSH: return m_acosh_decl; + case OP_ATANH: return m_atanh_decl; + case OP_PI: return m_pi->get_decl(); + case OP_E: return m_e->get_decl(); + default: return 0; + } +} + +inline decl_kind arith_decl_plugin::fix_kind(decl_kind k, unsigned arity) { + if (k == OP_SUB && arity == 1) { + return OP_UMINUS; + } + return k; +} + +#define MAX_SMALL_NUM_TO_CACHE 16 + +app * arith_decl_plugin::mk_numeral(rational const & val, bool is_int) { + if (is_int && !val.is_int()) { + m_manager->raise_exception("invalid rational value passed as an integer"); + } + if (val.is_unsigned()) { + unsigned u_val = val.get_unsigned(); + if (u_val < MAX_SMALL_NUM_TO_CACHE) { + if (is_int) { + app * r = m_small_ints.get(u_val, 0); + if (r == 0) { + parameter p[2] = { parameter(val), parameter(1) }; + r = m_manager->mk_const(m_manager->mk_const_decl(m_intv_sym, m_int_decl, func_decl_info(m_family_id, OP_NUM, 2, p))); + m_manager->inc_ref(r); + m_small_ints.setx(u_val, r, 0); + } + return r; + } + else { + app * r = m_small_reals.get(u_val, 0); + if (r == 0) { + parameter p[2] = { parameter(val), parameter(0) }; + r = m_manager->mk_const(m_manager->mk_const_decl(m_realv_sym, m_real_decl, func_decl_info(m_family_id, OP_NUM, 2, p))); + m_manager->inc_ref(r); + m_small_reals.setx(u_val, r, 0); + } + return r; + } + } + } + parameter p[2] = { parameter(val), parameter(static_cast(is_int)) }; + func_decl * decl; + if (is_int) + decl = m_manager->mk_const_decl(m_intv_sym, m_int_decl, func_decl_info(m_family_id, OP_NUM, 2, p)); + else + decl = m_manager->mk_const_decl(m_realv_sym, m_real_decl, func_decl_info(m_family_id, OP_NUM, 2, p)); + return m_manager->mk_const(decl); +} + +func_decl * arith_decl_plugin::mk_num_decl(unsigned num_parameters, parameter const * parameters, unsigned arity) { + if (!(num_parameters == 2 && arity == 0 && parameters[0].is_rational() && parameters[1].is_int())) { + m_manager->raise_exception("invalid numeral declaration"); + return 0; + } + if (parameters[1].get_int() != 0) + return m_manager->mk_const_decl(m_intv_sym, m_int_decl, func_decl_info(m_family_id, OP_NUM, num_parameters, parameters)); + else + return m_manager->mk_const_decl(m_realv_sym, m_real_decl, func_decl_info(m_family_id, OP_NUM, num_parameters, parameters)); +} + +func_decl * arith_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + if (k == OP_NUM) + return mk_num_decl(num_parameters, parameters, arity); + if (arity == 0 && k != OP_PI && k != OP_E) { + m_manager->raise_exception("no arguments supplied to arithmetical operator"); + return 0; + } + bool is_real = arity > 0 && domain[0] == m_real_decl; + return mk_func_decl(fix_kind(k, arity), is_real); +} + +func_decl * arith_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned num_args, expr * const * args, sort * range) { + if (k == OP_NUM) + return mk_num_decl(num_parameters, parameters, num_args); + if (num_args == 0 && k != OP_PI && k != OP_E) { + m_manager->raise_exception("no arguments supplied to arithmetical operator"); + return 0; + } + bool is_real = num_args > 0 && m_manager->get_sort(args[0]) == m_real_decl; + return mk_func_decl(fix_kind(k, num_args), is_real); +} + +void arith_decl_plugin::get_sort_names(svector& sort_names, symbol const & logic) { + // TODO: only define Int and Real in the right logics + sort_names.push_back(builtin_name("Int", INT_SORT)); + sort_names.push_back(builtin_name("Real", REAL_SORT)); +} + +void arith_decl_plugin::get_op_names(svector& op_names, symbol const & logic) { + op_names.push_back(builtin_name("<=",OP_LE)); + op_names.push_back(builtin_name(">=",OP_GE)); + op_names.push_back(builtin_name("<",OP_LT)); + op_names.push_back(builtin_name(">",OP_GT)); + op_names.push_back(builtin_name("+",OP_ADD)); + op_names.push_back(builtin_name("-",OP_SUB)); + op_names.push_back(builtin_name("~",OP_UMINUS)); + op_names.push_back(builtin_name("*",OP_MUL)); + op_names.push_back(builtin_name("/",OP_DIV)); + op_names.push_back(builtin_name("div",OP_IDIV)); + op_names.push_back(builtin_name("rem",OP_REM)); + op_names.push_back(builtin_name("mod",OP_MOD)); + op_names.push_back(builtin_name("to_real",OP_TO_REAL)); + op_names.push_back(builtin_name("to_int",OP_TO_INT)); + op_names.push_back(builtin_name("is_int",OP_IS_INT)); + if (logic == symbol::null) { + op_names.push_back(builtin_name("^", OP_POWER)); + op_names.push_back(builtin_name("sin", OP_SIN)); + op_names.push_back(builtin_name("cos", OP_COS)); + op_names.push_back(builtin_name("tan", OP_TAN)); + op_names.push_back(builtin_name("asin", OP_ASIN)); + op_names.push_back(builtin_name("acos", OP_ACOS)); + op_names.push_back(builtin_name("atan", OP_ATAN)); + op_names.push_back(builtin_name("sinh", OP_SINH)); + op_names.push_back(builtin_name("cosh", OP_COSH)); + op_names.push_back(builtin_name("tanh", OP_TANH)); + op_names.push_back(builtin_name("asinh", OP_ASINH)); + op_names.push_back(builtin_name("acosh", OP_ACOSH)); + op_names.push_back(builtin_name("atanh", OP_ATANH)); + op_names.push_back(builtin_name("pi", OP_PI)); + op_names.push_back(builtin_name("euler", OP_E)); + } +} + +bool arith_decl_plugin::is_value(app* e) const { + return is_app_of(e, m_family_id, OP_NUM); +} + +bool arith_decl_plugin::are_distinct(app* a, app* b) const { + TRACE("are_distinct_bug", tout << mk_ismt2_pp(a, *m_manager) << "\n" << mk_ismt2_pp(b, *m_manager) << "\n";); + if (decl_plugin::are_distinct(a,b)) { + return true; + } + +#define is_non_zero(e) is_app_of(e,m_family_id, OP_NUM) && !to_app(e)->get_decl()->get_parameter(0).get_rational().is_zero() + + if (is_app_of(a, m_family_id, OP_ADD) && + a->get_num_args() == 2 && + to_app(a)->get_arg(0) == b && + is_non_zero(to_app(a)->get_arg(1))) { + return true; + } + if (is_app_of(a, m_family_id, OP_ADD) && + a->get_num_args() == 2 && + to_app(a)->get_arg(1) == b && + is_non_zero(to_app(a)->get_arg(0))) { + return true; + } + if (is_app_of(b, m_family_id, OP_ADD) && + b->get_num_args() == 2 && + to_app(b)->get_arg(1) == a && + is_non_zero(to_app(b)->get_arg(0))) { + return true; + } + if (is_app_of(b, m_family_id, OP_ADD) && + b->get_num_args() == 2 && + to_app(b)->get_arg(0) == a && + is_non_zero(to_app(b)->get_arg(1))) { + return true; + } + return false; +} + +expr * arith_decl_plugin::get_some_value(sort * s) { + SASSERT(s == m_int_decl || s == m_real_decl); + return mk_numeral(rational(0), s == m_int_decl); +} + +arith_util::arith_util(ast_manager & m): + m_manager(m), + m_afid(m.get_family_id("arith")), + m_plugin(0) { +} + +void arith_util::init_plugin() { + SASSERT(m_plugin == 0); + m_plugin = static_cast(m_manager.get_plugin(m_afid)); +} + +bool arith_util::is_numeral(expr const * n, rational & val, bool & is_int) const { + if (!is_app_of(n, m_afid, OP_NUM)) + return false; + func_decl * decl = to_app(n)->get_decl(); + val = decl->get_parameter(0).get_rational(); + is_int = decl->get_parameter(1).get_int() != 0; + return true; +} + +bool arith_util::is_irrational_algebraic_numeral(expr const * n, algebraic_numbers::anum & val) { + if (!is_app_of(n, m_afid, OP_IRRATIONAL_ALGEBRAIC_NUM)) + return false; + am().set(val, to_irrational_algebraic_numeral(n)); + return true; +} + +algebraic_numbers::anum const & arith_util::to_irrational_algebraic_numeral(expr const * n) { + SASSERT(is_irrational_algebraic_numeral(n)); + return plugin().aw().to_anum(to_app(n)->get_decl()); +} diff --git a/lib/arith_decl_plugin.h b/lib/arith_decl_plugin.h new file mode 100644 index 000000000..6a377c577 --- /dev/null +++ b/lib/arith_decl_plugin.h @@ -0,0 +1,348 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + arith_decl_plugin.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-09 + +Revision History: + +--*/ +#ifndef _ARITH_DECL_PLUGIN_H_ +#define _ARITH_DECL_PLUGIN_H_ + +#include"ast.h" +class sexpr; + +namespace algebraic_numbers { + class anum; + class manager; +}; + +enum arith_sort_kind { + REAL_SORT, + INT_SORT +}; + +enum arith_op_kind { + OP_NUM, // rational & integers + OP_IRRATIONAL_ALGEBRAIC_NUM, // irrationals that are roots of polynomials with integer coefficients + OP_LE, + OP_GE, + OP_LT, + OP_GT, + OP_ADD, + OP_SUB, + OP_UMINUS, + OP_MUL, + OP_DIV, + OP_IDIV, + OP_REM, + OP_MOD, + OP_TO_REAL, + OP_TO_INT, + OP_IS_INT, + OP_POWER, + // hyperbolic and trigonometric functions + OP_SIN, + OP_COS, + OP_TAN, + OP_ASIN, + OP_ACOS, + OP_ATAN, + OP_SINH, + OP_COSH, + OP_TANH, + OP_ASINH, + OP_ACOSH, + OP_ATANH, + // constants + OP_PI, + OP_E, + LAST_ARITH_OP +}; + +class arith_util; + +class arith_decl_plugin : public decl_plugin { +protected: + struct algebraic_numbers_wrapper; + algebraic_numbers_wrapper * m_aw; + symbol m_intv_sym; + symbol m_realv_sym; + symbol m_rootv_sym; + sort * m_real_decl; + sort * m_int_decl; + func_decl * m_r_le_decl; + func_decl * m_r_ge_decl; + func_decl * m_r_lt_decl; + func_decl * m_r_gt_decl; + + func_decl * m_r_add_decl; + func_decl * m_r_sub_decl; + func_decl * m_r_uminus_decl; + func_decl * m_r_mul_decl; + func_decl * m_r_div_decl; + + func_decl * m_i_le_decl; + func_decl * m_i_ge_decl; + func_decl * m_i_lt_decl; + func_decl * m_i_gt_decl; + + func_decl * m_i_add_decl; + func_decl * m_i_sub_decl; + func_decl * m_i_uminus_decl; + func_decl * m_i_mul_decl; + func_decl * m_i_div_decl; + func_decl * m_i_mod_decl; + func_decl * m_i_rem_decl; + + func_decl * m_to_real_decl; + func_decl * m_to_int_decl; + func_decl * m_is_int_decl; + func_decl * m_r_power_decl; + func_decl * m_i_power_decl; + + func_decl * m_sin_decl; + func_decl * m_cos_decl; + func_decl * m_tan_decl; + func_decl * m_asin_decl; + func_decl * m_acos_decl; + func_decl * m_atan_decl; + func_decl * m_sinh_decl; + func_decl * m_cosh_decl; + func_decl * m_tanh_decl; + func_decl * m_asinh_decl; + func_decl * m_acosh_decl; + func_decl * m_atanh_decl; + + app * m_pi; + app * m_e; + + ptr_vector m_small_ints; + ptr_vector m_small_reals; + + func_decl * mk_func_decl(decl_kind k, bool is_real); + virtual void set_manager(ast_manager * m, family_id id); + decl_kind fix_kind(decl_kind k, unsigned arity); + func_decl * mk_num_decl(unsigned num_parameters, parameter const * parameters, unsigned arity); + +public: + arith_decl_plugin(); + + virtual ~arith_decl_plugin(); + virtual void finalize(); + + algebraic_numbers::manager & am(); + algebraic_numbers_wrapper & aw(); + + virtual void del(parameter const & p); + virtual parameter translate(parameter const & p, decl_plugin & target); + + virtual decl_plugin * mk_fresh() { + return alloc(arith_decl_plugin); + } + + virtual sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters); + + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned num_args, expr * const * args, sort * range); + + virtual bool is_value(app* e) const; + + virtual bool are_distinct(app* a, app* b) const; + + virtual void get_op_names(svector & op_names, symbol const & logic); + + virtual void get_sort_names(svector & sort_names, symbol const & logic); + + app * mk_numeral(rational const & n, bool is_int); + + app * mk_numeral(algebraic_numbers::anum const & val, bool is_int); + + // Create a (real) numeral that is the i-th root of the polynomial encoded using the given sexpr. + app * mk_numeral(sexpr const * p, unsigned i); + + app * mk_pi() const { return m_pi; } + + app * mk_e() const { return m_e; } + + virtual expr * get_some_value(sort * s); + + void set_cancel(bool f); +}; + +class arith_util { + ast_manager & m_manager; + family_id m_afid; + arith_decl_plugin * m_plugin; + + void init_plugin(); + + arith_decl_plugin & plugin() const { + if (!m_plugin) const_cast(this)->init_plugin(); + SASSERT(m_plugin != 0); + return *m_plugin; + } + +public: + arith_util(ast_manager & m); + + ast_manager & get_manager() const { return m_manager; } + family_id get_family_id() const { return m_afid; } + + algebraic_numbers::manager & am() { + return plugin().am(); + } + + bool is_arith_expr(expr const * n) const { return is_app(n) && to_app(n)->get_family_id() == m_afid; } + bool is_numeral(expr const * n, rational & val, bool & is_int) const; + bool is_numeral(expr const * n, rational & val) const { bool is_int; return is_numeral(n, val, is_int); } + bool is_numeral(expr const * n) const { return is_app_of(n, m_afid, OP_NUM); } + bool is_irrational_algebraic_numeral(expr const * n) const { return is_app_of(n, m_afid, OP_IRRATIONAL_ALGEBRAIC_NUM); } + bool is_irrational_algebraic_numeral(expr const * n, algebraic_numbers::anum & val); + algebraic_numbers::anum const & to_irrational_algebraic_numeral(expr const * n); + bool is_zero(expr const * n) const { rational val; return is_numeral(n, val) && val.is_zero(); } + bool is_minus_one(expr * n) const { rational tmp; return is_numeral(n, tmp) && tmp.is_minus_one(); } + // return true if \c n is a term of the form (* -1 r) + bool is_times_minus_one(expr * n, expr * & r) const { + if (is_mul(n) && to_app(n)->get_num_args() == 2 && is_minus_one(to_app(n)->get_arg(0))) { + r = to_app(n)->get_arg(1); + return true; + } + return false; + } + bool is_le(expr const * n) const { return is_app_of(n, m_afid, OP_LE); } + bool is_ge(expr const * n) const { return is_app_of(n, m_afid, OP_GE); } + bool is_lt(expr const * n) const { return is_app_of(n, m_afid, OP_LT); } + bool is_gt(expr const * n) const { return is_app_of(n, m_afid, OP_GT); } + bool is_add(expr const * n) const { return is_app_of(n, m_afid, OP_ADD); } + bool is_sub(expr const * n) const { return is_app_of(n, m_afid, OP_SUB); } + bool is_uminus(expr const * n) const { return is_app_of(n, m_afid, OP_UMINUS); } + bool is_mul(expr const * n) const { return is_app_of(n, m_afid, OP_MUL); } + bool is_div(expr const * n) const { return is_app_of(n, m_afid, OP_DIV); } + bool is_idiv(expr const * n) const { return is_app_of(n, m_afid, OP_IDIV); } + bool is_mod(expr const * n) const { return is_app_of(n, m_afid, OP_MOD); } + bool is_rem(expr const * n) const { return is_app_of(n, m_afid, OP_REM); } + bool is_to_real(expr const * n) const { return is_app_of(n, m_afid, OP_TO_REAL); } + bool is_to_int(expr const * n) const { return is_app_of(n, m_afid, OP_TO_INT); } + bool is_is_int(expr const * n) const { return is_app_of(n, m_afid, OP_IS_INT); } + bool is_power(expr const * n) const { return is_app_of(n, m_afid, OP_POWER); } + + bool is_int(sort const * s) const { return is_sort_of(s, m_afid, INT_SORT); } + bool is_int(expr const * n) const { return is_int(m_manager.get_sort(n)); } + bool is_real(sort const * s) const { return is_sort_of(s, m_afid, REAL_SORT); } + bool is_real(expr const * n) const { return is_real(m_manager.get_sort(n)); } + bool is_int_real(sort const * s) const { return s->get_family_id() == m_afid; } + bool is_int_real(expr const * n) const { return is_int_real(m_manager.get_sort(n)); } + + MATCH_UNARY(is_uminus); + + MATCH_BINARY(is_sub); + MATCH_BINARY(is_add); + MATCH_BINARY(is_mul); + MATCH_BINARY(is_le); + MATCH_BINARY(is_ge); + MATCH_BINARY(is_lt); + MATCH_BINARY(is_gt); + MATCH_BINARY(is_mod); + MATCH_BINARY(is_rem); + MATCH_BINARY(is_div); + MATCH_BINARY(is_idiv); + + + sort * mk_int() { return m_manager.mk_sort(m_afid, INT_SORT); } + sort * mk_real() { return m_manager.mk_sort(m_afid, REAL_SORT); } + + app * mk_numeral(rational const & val, bool is_int) const { + return plugin().mk_numeral(val, is_int); + } + app * mk_numeral(rational const & val, sort const * s) const { + SASSERT(is_int(s) || is_real(s)); + return mk_numeral(val, is_int(s)); + } + app * mk_numeral(algebraic_numbers::anum const & val, bool is_int) { + return plugin().mk_numeral(val, is_int); + } + app * mk_numeral(sexpr const * p, unsigned i) { + return plugin().mk_numeral(p, i); + } + app * mk_le(expr * arg1, expr * arg2) const { return m_manager.mk_app(m_afid, OP_LE, arg1, arg2); } + app * mk_ge(expr * arg1, expr * arg2) const { return m_manager.mk_app(m_afid, OP_GE, arg1, arg2); } + app * mk_lt(expr * arg1, expr * arg2) const { return m_manager.mk_app(m_afid, OP_LT, arg1, arg2); } + app * mk_gt(expr * arg1, expr * arg2) const { return m_manager.mk_app(m_afid, OP_GT, arg1, arg2); } + + app * mk_add(unsigned num_args, expr * const * args) { return m_manager.mk_app(m_afid, OP_ADD, num_args, args); } + app * mk_add(expr * arg1, expr * arg2) { return m_manager.mk_app(m_afid, OP_ADD, arg1, arg2); } + app * mk_add(expr * arg1, expr * arg2, expr* arg3) { return m_manager.mk_app(m_afid, OP_ADD, arg1, arg2, arg3); } + + app * mk_sub(expr * arg1, expr * arg2) { return m_manager.mk_app(m_afid, OP_SUB, arg1, arg2); } + app * mk_sub(unsigned num_args, expr * const * args) { return m_manager.mk_app(m_afid, OP_SUB, num_args, args); } + app * mk_mul(expr * arg1, expr * arg2) { return m_manager.mk_app(m_afid, OP_MUL, arg1, arg2); } + app * mk_mul(expr * arg1, expr * arg2, expr* arg3) { return m_manager.mk_app(m_afid, OP_MUL, arg1, arg2, arg3); } + app * mk_mul(unsigned num_args, expr * const * args) { return m_manager.mk_app(m_afid, OP_MUL, num_args, args); } + app * mk_uminus(expr * arg) { return m_manager.mk_app(m_afid, OP_UMINUS, arg); } + app * mk_div(expr * arg1, expr * arg2) { return m_manager.mk_app(m_afid, OP_DIV, arg1, arg2); } + app * mk_idiv(expr * arg1, expr * arg2) { return m_manager.mk_app(m_afid, OP_IDIV, arg1, arg2); } + app * mk_rem(expr * arg1, expr * arg2) { return m_manager.mk_app(m_afid, OP_REM, arg1, arg2); } + app * mk_mod(expr * arg1, expr * arg2) { return m_manager.mk_app(m_afid, OP_MOD, arg1, arg2); } + app * mk_to_real(expr * arg1) { return m_manager.mk_app(m_afid, OP_TO_REAL, arg1); } + app * mk_to_int(expr * arg1) { return m_manager.mk_app(m_afid, OP_TO_INT, arg1); } + app * mk_is_int(expr * arg1) { return m_manager.mk_app(m_afid, OP_IS_INT, arg1); } + app * mk_power(expr* arg1, expr* arg2) { return m_manager.mk_app(m_afid, OP_POWER, arg1, arg2); } + + app * mk_sin(expr * arg) { return m_manager.mk_app(m_afid, OP_SIN, arg); } + app * mk_cos(expr * arg) { return m_manager.mk_app(m_afid, OP_COS, arg); } + app * mk_tan(expr * arg) { return m_manager.mk_app(m_afid, OP_TAN, arg); } + app * mk_asin(expr * arg) { return m_manager.mk_app(m_afid, OP_ASIN, arg); } + app * mk_acos(expr * arg) { return m_manager.mk_app(m_afid, OP_ACOS, arg); } + app * mk_atan(expr * arg) { return m_manager.mk_app(m_afid, OP_ATAN, arg); } + + app * mk_sinh(expr * arg) { return m_manager.mk_app(m_afid, OP_SINH, arg); } + app * mk_cosh(expr * arg) { return m_manager.mk_app(m_afid, OP_COSH, arg); } + app * mk_tanh(expr * arg) { return m_manager.mk_app(m_afid, OP_TANH, arg); } + app * mk_asinh(expr * arg) { return m_manager.mk_app(m_afid, OP_ASINH, arg); } + app * mk_acosh(expr * arg) { return m_manager.mk_app(m_afid, OP_ACOSH, arg); } + app * mk_atanh(expr * arg) { return m_manager.mk_app(m_afid, OP_ATANH, arg); } + + bool is_pi(expr * arg) { return is_app_of(arg, m_afid, OP_PI); } + bool is_e(expr * arg) { return is_app_of(arg, m_afid, OP_E); } + + app * mk_pi() { return plugin().mk_pi(); } + app * mk_e() { return plugin().mk_e(); } + + /** + \brief Return the equality (= lhs rhs), but it makes sure that + if one of the arguments is a numeral, then it will be in the right-hand-side; + if none of them are numerals, then the left-hand-side has a smaller id than the right hand side. + */ + app * mk_eq(expr * lhs, expr * rhs) { + if (is_numeral(lhs) || (!is_numeral(rhs) && lhs->get_id() > rhs->get_id())) + std::swap(lhs, rhs); + if (lhs == rhs) + return m_manager.mk_true(); + if (is_numeral(lhs) && is_numeral(rhs)) { + SASSERT(lhs != rhs); + return m_manager.mk_false(); + } + return m_manager.mk_eq(lhs, rhs); + } + + void set_cancel(bool f) { + plugin().set_cancel(f); + } +}; + +#endif /* _ARITH_DECL_PLUGIN_H_ */ + diff --git a/lib/arith_eq_adapter.cpp b/lib/arith_eq_adapter.cpp new file mode 100644 index 000000000..53227d0cc --- /dev/null +++ b/lib/arith_eq_adapter.cpp @@ -0,0 +1,316 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + arith_eq_adapter.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-05-25. + +Revision History: + +--*/ + +#include"smt_context.h" +#include"arith_eq_adapter.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"stats.h" +#include"simplifier.h" +#include"ast_smt2_pp.h" + +namespace smt { + + class already_processed_trail : public trail { + // Remark: it is safer to use a trail object, because it guarantees that the enodes + // are still alive when the undo operation is performed. + // + // If a local backtracking stack is used in the class arith_eq_adapter is used, + // then we cannot guarantee that. + arith_eq_adapter::already_processed & m_already_processed; + enode * m_n1; + enode * m_n2; + public: + already_processed_trail(arith_eq_adapter::already_processed & m, enode * n1, enode * n2): + m_already_processed(m), + m_n1(n1), + m_n2(n2) { + } + + virtual void undo(context & ctx) { + m_already_processed.erase(m_n1, m_n2); + TRACE("arith_eq_adapter_profile", tout << "del #" << m_n1->get_owner_id() << " #" << m_n2->get_owner_id() << "\n";); + } + }; + + /** + \brief The atoms m_eq, m_le, and m_ge should be marked as relevant only after + m_n1 and m_n2 are marked as relevant. + */ + class arith_eq_relevancy_eh : public relevancy_eh { + expr * m_n1; + expr * m_n2; + expr * m_eq; + expr * m_le; + expr * m_ge; + public: + arith_eq_relevancy_eh(expr * n1, expr * n2, expr * eq, expr * le, expr * ge): + m_n1(n1), + m_n2(n2), + m_eq(eq), + m_le(le), + m_ge(ge) { + } + + virtual ~arith_eq_relevancy_eh() {} + + virtual void operator()(relevancy_propagator & rp) { + if (!rp.is_relevant(m_n1)) + return; + if (!rp.is_relevant(m_n2)) + return; + rp.mark_as_relevant(m_eq); + rp.mark_as_relevant(m_le); + rp.mark_as_relevant(m_ge); + } + }; + + arith_simplifier_plugin * arith_eq_adapter::get_simplifier() { + if (!m_as) { + simplifier & s = get_context().get_simplifier(); + m_as = static_cast(s.get_plugin(m_owner.get_family_id())); + } + return m_as; + } + + void arith_eq_adapter::mk_axioms(enode * n1, enode * n2) { + SASSERT(n1 != n2); + ast_manager & m = get_manager(); + TRACE("arith_eq_adapter_mk_axioms", tout << "#" << n1->get_owner_id() << " #" << n2->get_owner_id() << "\n"; + tout << mk_ismt2_pp(n1->get_owner(), m) << "\n" << mk_ismt2_pp(n2->get_owner(), m) << "\n";); + if (n1->get_owner_id() > n2->get_owner_id()) + std::swap(n1, n2); + app * t1 = n1->get_owner(); + app * t2 = n2->get_owner(); + if (m.is_value(t1) && m.is_value(t2)) { + // Nothing to be done + // We don't need to create axioms for 2 = 3 + return; + } + + context & ctx = get_context(); + CTRACE("arith_eq_adapter_relevancy", !(ctx.is_relevant(n1) && ctx.is_relevant(n2)), + tout << "is_relevant(n1): #" << n1->get_owner_id() << " " << ctx.is_relevant(n1) << "\n"; + tout << "is_relevant(n2): #" << n2->get_owner_id() << " " << ctx.is_relevant(n2) << "\n"; + tout << mk_pp(n1->get_owner(), get_manager()) << "\n"; + tout << mk_pp(n2->get_owner(), get_manager()) << "\n"; + ctx.display(tout);); + // + // The atoms d.m_t1_eq_t2, d.m_le, and d.m_ge should only be marked as relevant + // after n1 and n2 are marked as relevant. + // + data d; + if (m_already_processed.find(n1, n2, d)) + return; + + TRACE("arith_eq_adapter_profile", tout << "mk #" << n1->get_owner_id() << " #" << n2->get_owner_id() << " " << + m_already_processed.size() << " " << ctx.get_scope_level() << "\n";); + + m_stats.m_num_eq_axioms++; + + TRACE("arith_eq_adapter_profile_detail", + tout << "mk_detail " << mk_bounded_pp(n1->get_owner(), m, 5) << " " << + mk_bounded_pp(n2->get_owner(), m, 5) << "\n";); + + app_ref t1_eq_t2(m); + + t1_eq_t2 = ctx.mk_eq_atom(t1, t2); + SASSERT(!m.is_false(t1_eq_t2)); + + TRACE("arith_eq_adapter_bug", tout << mk_bounded_pp(t1_eq_t2, m) << "\n" + << mk_bounded_pp(t1, m) << "\n" + << mk_bounded_pp(t2, m) << "\n";); + + // UNRESOLVED ISSUE: + // + // arith_eq_adapter is still creating problems. + // The following disabled code fixes the issues, but create performance problems. + // The alternative does not works 100%. It generates problems when the literal + // created by the adapter during the search is included in a learned clause. + // Here is a sequence of events that triggers a crash: + // 1) The terms t1 >= t2 and t1 <= t2 are not in simplified form. + // For example, let us assume t1 := (* -1 x) and t2 := x. + // Since, t1 and t2 were internalized at this point, the following code works. + // That is the arith internalizer accepts the formula (+ (* -1 x) (* -1 x)) + // that is not in simplified form. Let s be the term (+ (* -1 x) (* -1 x)) + // 2) Assume now that a conflict is detected a lemma containing s is created. + // 3) The enodes associated with t1, t2 and s are destroyed during backtracking. + // 4) The term s is reinternalized at smt::context::reinit_clauses. Term t2 is + // also reinitialized, but t1 is not. We only create a "name" for a term (* -1 x) + // if it is embedded in a function application. + // 5) theory_arith fails to internalize (+ (* -1 x) (* -1 x)), and Z3 crashes. + // + +#if 0 + // This block of code uses the simplifier for creating the literals t1 >= t2 and t1 <= t2. + // It has serious performance problems in VCC benchmarks. + // The problem seems to be the following: + // t1 and t2 are slacks (i.e., names for linear polynomials). + // The simplifier will create inequalities that will indirectly imply that t1 >= t2 and t1 <= t2. + // Example if: t1 := 1 + a + // t2 := 2 + b + // the simplifier will create + // a - b >= -1 + // a - b <= -1 + // These inequalities imply that 1+a >= 2+b and 1+a <= 2+b, + // but the tableau is complete different. + + + // BTW, note that we don't really need to handle the is_numeral case when using + // the simplifier. However, doing that, it seems we minimize the performance problem. + expr_ref le(m); + expr_ref ge(m); + if (m_util.is_numeral(t1)) + std::swap(t1, t2); + if (m_util.is_numeral(t2)) { + le = m_util.mk_le(t1, t2); + ge = m_util.mk_ge(t1, t2); + } + else { + arith_simplifier_plugin & s = *(get_simplifier()); + s.mk_le(t1, t2, le); + s.mk_ge(t1, t2, ge); + } + TRACE("arith_eq_adapter_perf", tout << mk_ismt2_pp(t1_eq_t2, m) << "\n" << mk_ismt2_pp(le, m) << "\n" << mk_ismt2_pp(ge, m) << "\n";); +#else + // Old version that used to be buggy. + // I fixed the theory arithmetic internalizer to accept non simplified terms of the form t1 - t2 + // if t1 and t2 already have slacks (theory variables) associated with them. + app * le = 0; + app * ge = 0; + if (m_util.is_numeral(t1)) + std::swap(t1, t2); + if (m_util.is_numeral(t2)) { + le = m_util.mk_le(t1, t2); + ge = m_util.mk_ge(t1, t2); + } + else { + sort * st = m.get_sort(t1); + app * minus_one = m_util.mk_numeral(rational::minus_one(), st); + app * zero = m_util.mk_numeral(rational::zero(), st); + app_ref s(m_util.mk_add(t1, m_util.mk_mul(minus_one, t2)), m); + le = m_util.mk_le(s, zero); + ge = m_util.mk_ge(s, zero); + } + TRACE("arith_eq_adapter_perf", + tout << mk_ismt2_pp(t1_eq_t2, m) << "\n" << mk_ismt2_pp(le, m) << "\n" << mk_ismt2_pp(ge, m) << "\n";); +#endif + + ctx.push_trail(already_processed_trail(m_already_processed, n1, n2)); + m_already_processed.insert(n1, n2, data(t1_eq_t2, le, ge)); + TRACE("arith_eq_adapter_profile", tout << "insert #" << n1->get_owner_id() << " #" << n2->get_owner_id() << "\n";); + ctx.internalize(t1_eq_t2, true); + literal t1_eq_t2_lit(ctx.get_bool_var(t1_eq_t2)); + TRACE("interface_eq", + tout << "core should try true phase first for the equality: " << t1_eq_t2_lit << "\n"; + tout << "#" << n1->get_owner_id() << " == #" << n2->get_owner_id() << "\n"; + tout << "try_true_first: " << ctx.try_true_first(t1_eq_t2_lit.var()) << "\n";); + TRACE("arith_eq_adapter_bug", + tout << "le: " << mk_ismt2_pp(le, m) << "\nge: " << mk_ismt2_pp(ge, m) << "\n";); + ctx.internalize(le, true); + ctx.internalize(ge, true); + SASSERT(ctx.lit_internalized(le)); + SASSERT(ctx.lit_internalized(ge)); + literal le_lit = ctx.get_literal(le); + literal ge_lit = ctx.get_literal(ge); + if (ctx.try_true_first(t1_eq_t2_lit.var())) { + // Remark: I need to propagate the try_true_first flag to the auxiliary atom le_lit and ge_lit. + // Otherwise model based theory combination will be ineffective, because if the core + // case splits in le_lit and ge_lit before t1_eq_t2_lit it will essentially assign an arbitrary phase to t1_eq_t2_lit. + ctx.set_true_first_flag(le_lit.var()); + ctx.set_true_first_flag(ge_lit.var()); + } + theory_id tid = m_owner.get_id(); + if (m.proofs_enabled() && m_proof_hint.empty()) { + m_proof_hint.push_back(parameter(symbol("triangle-eq"))); + } + ctx.mk_th_axiom(tid, ~t1_eq_t2_lit, le_lit, m_proof_hint.size(), m_proof_hint.c_ptr()); + ctx.mk_th_axiom(tid, ~t1_eq_t2_lit, ge_lit, m_proof_hint.size(), m_proof_hint.c_ptr()); + ctx.mk_th_axiom(tid, t1_eq_t2_lit, ~le_lit, ~ge_lit, m_proof_hint.size(), m_proof_hint.c_ptr()); + TRACE("arith_eq_adapter", tout << "internalizing: " + << " " << mk_pp(le, m) << ": " << le_lit + << " " << mk_pp(ge, m) << ": " << ge_lit + << " " << mk_pp(t1_eq_t2, m) << ": " << t1_eq_t2_lit << "\n";); + + if (m_params.m_arith_add_binary_bounds) { + TRACE("arith_eq_adapter", tout << "adding binary bounds...\n";); + ctx.mk_th_axiom(tid, le_lit, ge_lit, 3, m_proof_hint.c_ptr()); + } + if (ctx.relevancy()) { + relevancy_eh * eh = ctx.mk_relevancy_eh(arith_eq_relevancy_eh(n1->get_owner(), n2->get_owner(), t1_eq_t2, le, ge)); + ctx.add_relevancy_eh(n1->get_owner(), eh); + ctx.add_relevancy_eh(n2->get_owner(), eh); + } + if (!m_params.m_arith_lazy_adapter && !ctx.at_base_level() && + n1->get_iscope_lvl() <= ctx.get_base_level() && n2->get_iscope_lvl() <= ctx.get_base_level()) { + m_restart_pairs.push_back(enode_pair(n1, n2)); + } + TRACE("arith_eq_adapter_detail", ctx.display(tout);); + } + + void arith_eq_adapter::new_eq_eh(theory_var v1, theory_var v2) { + TRACE("arith_eq_adapter", tout << "v" << v1 << " = v" << v2 << " #" << get_enode(v1)->get_owner_id() << " = #" << get_enode(v2)->get_owner_id() << "\n";); + TRACE("arith_eq_adapter_bug", tout << mk_bounded_pp(get_enode(v1)->get_owner(), get_manager()) << "\n" << mk_bounded_pp(get_enode(v2)->get_owner(), get_manager()) << "\n";); + mk_axioms(get_enode(v1), get_enode(v2)); + } + + void arith_eq_adapter::new_diseq_eh(theory_var v1, theory_var v2) { + TRACE("arith_eq_adapter", tout << "v" << v1 << " != v" << v2 << " #" << get_enode(v1)->get_owner_id() << " != #" << get_enode(v2)->get_owner_id() << "\n";); + mk_axioms(get_enode(v1), get_enode(v2)); + } + + void arith_eq_adapter::init_search_eh() { + m_restart_pairs.reset(); + } + + void arith_eq_adapter::reset_eh() { + TRACE("arith_eq_adapter", tout << "reset\n";); + m_already_processed .reset(); + m_restart_pairs .reset(); + m_stats .reset(); + } + + void arith_eq_adapter::restart_eh() { + TRACE("arith_eq_adapter", tout << "restart\n";); + svector tmp(m_restart_pairs); + svector::iterator it = tmp.begin(); + svector::iterator end = tmp.end(); + m_restart_pairs.reset(); + for (; it != end; ++it) { + TRACE("arith_eq_adapter", tout << "creating arith_eq_adapter axioms at the base level #" << it->first->get_owner_id() << " #" << + it->second->get_owner_id() << "\n";); + mk_axioms(it->first, it->second); + } + } + + void arith_eq_adapter::collect_statistics(::statistics & st) const { + st.update("eq adapter", m_stats.m_num_eq_axioms); + } + + void arith_eq_adapter::display_already_processed(std::ostream & out) const { + obj_pair_map::iterator it = m_already_processed.begin(); + obj_pair_map::iterator end = m_already_processed.end(); + for (; it != end; ++it) { + enode * n1 = it->get_key1(); + enode * n2 = it->get_key2(); + out << "eq_adapter: #" << n1->get_owner_id() << " #" << n2->get_owner_id() << "\n"; + } + } +}; + diff --git a/lib/arith_eq_adapter.h b/lib/arith_eq_adapter.h new file mode 100644 index 000000000..71fb79136 --- /dev/null +++ b/lib/arith_eq_adapter.h @@ -0,0 +1,97 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + arith_eq_adapter.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-05-25. + +Revision History: + +--*/ +#ifndef _ARITH_EQ_ADAPTER_H_ +#define _ARITH_EQ_ADAPTER_H_ + +#include"smt_theory.h" +#include"obj_pair_hashtable.h" +#include"arith_decl_plugin.h" +#include"statistics.h" +#include"arith_simplifier_plugin.h" + +namespace smt { + + struct arith_eq_adapter_stats { + unsigned m_num_eq_axioms; + void reset() { m_num_eq_axioms = 0; } + arith_eq_adapter_stats() { reset(); } + }; + + /** + \brief Auxiliary class used to convert (dis) equalities + propagated from the core into arith equalities/inequalities + atoms. This class is used by the arithmetic theories to + handle the (dis) equalities propagated from the logical context. + + - config 1: + recreate axioms at restart + + - config 2: + lazy diseq split + */ + class arith_eq_adapter { + public: + arith_eq_adapter_stats m_stats; + + private: + struct data { + expr * m_t1_eq_t2; + expr * m_le; + expr * m_ge; + data():m_t1_eq_t2(0), m_le(0), m_ge(0) {} + data(expr * t1_eq_t2, expr * le, expr * ge):m_t1_eq_t2(t1_eq_t2), m_le(le), m_ge(ge) {} + }; + + public: + typedef obj_pair_map already_processed; + + private: + theory & m_owner; + theory_arith_params & m_params; + arith_util & m_util; + arith_simplifier_plugin * m_as; + + already_processed m_already_processed; + svector m_restart_pairs; + svector m_proof_hint; + + context & get_context() const { return m_owner.get_context(); } + ast_manager & get_manager() const { return m_owner.get_manager(); } + enode * get_enode(theory_var v) const { return m_owner.get_enode(v); } + + arith_simplifier_plugin * get_simplifier(); + + public: + arith_eq_adapter(theory & owner, theory_arith_params & params, arith_util & u):m_owner(owner), m_params(params), m_util(u), m_as(0) {} + void new_eq_eh(theory_var v1, theory_var v2); + void new_diseq_eh(theory_var v1, theory_var v2); + void reset_eh(); + void init_search_eh(); + void restart_eh(); + /** + \brief Add the eq axioms for n1 and n2. + */ + void mk_axioms(enode * n1, enode * n2); + void collect_statistics(::statistics & st) const; + void display_already_processed(std::ostream & out) const; + }; +}; + +#endif /* _ARITH_EQ_ADAPTER_H_ */ + diff --git a/lib/arith_eq_solver.cpp b/lib/arith_eq_solver.cpp new file mode 100644 index 000000000..417f8022b --- /dev/null +++ b/lib/arith_eq_solver.cpp @@ -0,0 +1,632 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + arith_eq_solver.cpp + +Abstract: + + Solver for linear arithmetic equalities. + +Author: + + Nikolaj Bjorner (nbjorner) 2012-02-25 + +--*/ +#include"arith_eq_solver.h" + + +arith_eq_solver::~arith_eq_solver() { +} + +arith_eq_solver::arith_eq_solver(ast_manager & m, params_ref const& p): + m(m), + m_params(p), + m_util(m), + m_arith_rewriter(m) +{ + m_params.set_bool(":gcd-rounding", true); + // m_params.set_bool(":sum", true); + m_arith_rewriter.updt_params(m_params); +} + + +/** + \brief Return true if the first monomial of t is negative. +*/ +bool arith_eq_solver::is_neg_poly(expr * t) const { + if (m_util.is_add(t)) { + t = to_app(t)->get_arg(0); + } + if (m_util.is_mul(t)) { + t = to_app(t)->get_arg(0); + rational r; + bool is_int; + if (m_util.is_numeral(t, r, is_int)) + return r.is_neg(); + } + return false; +} + + +void arith_eq_solver::prop_mod_const(expr * e, unsigned depth, numeral const& k, expr_ref& result) { + SASSERT(m_util.is_int(e)); + SASSERT(k.is_int() && k.is_pos()); + numeral n; + bool is_int; + + if (depth == 0) { + result = e; + } + else if (m_util.is_add(e) || m_util.is_mul(e)) { + expr_ref_vector args(m); + expr_ref tmp(m); + app* a = to_app(e); + for (unsigned i = 0; i < a->get_num_args(); ++i) { + prop_mod_const(a->get_arg(i), depth - 1, k, tmp); + args.push_back(tmp); + } + m_arith_rewriter.mk_app(a->get_decl(), args.size(), args.c_ptr(), result); + } + else if (m_util.is_numeral(e, n, is_int) && is_int) { + result = m_util.mk_numeral(mod(n, k), true); + } + else { + result = e; + } +} + +void arith_eq_solver::gcd_normalize(vector& values) { + numeral g(0); + for (unsigned i = 0; !g.is_one() && i < values.size(); ++i) { + SASSERT(values[i].is_int()); + if (!values[i].is_zero()) { + if (g.is_zero()) { + g = abs(values[i]); + } + else { + g = gcd(abs(values[i]), g); + } + } + } + if (g.is_zero() || g.is_one()) { + return; + } + for (unsigned i = 0; i < values.size(); ++i) { + values[i] = values[i] / g; + SASSERT(values[i].is_int()); + } +} + +unsigned arith_eq_solver::find_abs_min(vector& values) { + SASSERT(values.size() >= 2); + unsigned index = 0; + numeral v(0); + for (unsigned i = 1; i < values.size(); ++i) { + numeral w = abs(values[i]); + if (v.is_zero() || (!w.is_zero() && w < v)) { + index = i; + v = w; + } + } + return index; +} + +static void print_row(std::ostream& out, vector const& row) { + for(unsigned i = 0; i < row.size(); ++i) { + out << row[i] << " "; + } + out << "\n"; +} + +static void print_rows(std::ostream& out, vector > const& rows) { + for (unsigned i = 0; i < rows.size(); ++i) { + print_row(out, rows[i]); + } +} + +// +// The gcd of the coefficients to variables have to divide the +// coefficient to the constant. +// The constant is the last value in the array. +// +bool arith_eq_solver::gcd_test(vector& values) { + SASSERT(values.size() > 0); + numeral g(0); + numeral first_value = values[0]; + for (unsigned i = 1; !g.is_one() && i < values.size(); ++i) { + if (!values[i].is_zero()) { + if (g.is_zero()) { + g = abs(values[i]); + } + else { + g = gcd(abs(values[i]), g); + } + } + } + if (g.is_one()) { + return true; + } + if (g.is_zero()) { + return first_value.is_zero(); + } + numeral r = first_value/g; + return r.is_int(); +} + + +bool arith_eq_solver::solve_integer_equation( + vector& values, + unsigned& index, + bool& is_fresh + ) +{ + TRACE("arith_eq_solver", + tout << "solving: "; + print_row(tout, values); + ); + // + // perform one step of the omega test equality elimination. + // + // Given: + // a1*x1 + a2*x2 + .. + a_n*x_n + a_{n+1} = 0 + // + // Assume gcd(a1,..,a_n,a_{n+1}) = 1 + // Assume gcd(a1,...,a_n) divides a_{n+1} (eg. gcd(a1,..,an) = 1) + // + // post-condition: values[index] = -1. + // + // Let a_index be index of least absolute value. + // + // If |a_index| = 1, then return row and index. + // Otherwise: + // Let m = |a_index| + 1 + // Set + // + // m*x_index' + // = + // ((a1 mod_hat m)*x1 + (a2 mod_hat m)*x2 + .. + (a_n mod_hat m)*x_n + (k mod_hat m)) + // = + // (a1'*x1 + a2'*x2 + .. (-)1*x_index + ...) + // + // <=> Normalize signs so that sign to x_index is -1. + // (-)a1'*x1 + (-)a2'*x2 + .. -1*x_index + ... + m*x_index' = 0 + // + // Return row, where the coefficient to x_index is implicit. + // Instead used the coefficient 'm' at position 'index'. + // + + gcd_normalize(values); + if (!gcd_test(values)) { + TRACE("arith_eq_solver", tout << "not sat\n"; + print_row(tout, values);); + return false; + } + index = find_abs_min(values); + SASSERT(1 <= index && index < values.size()); + numeral a = values[index]; + numeral abs_a = abs(a); + + if (abs_a.is_zero()) { + // The equation is trivial. + return true; + } + if (a.is_one()) { + for (unsigned i = 0; i < values.size(); ++i) { + values[i].neg(); + } + } + is_fresh = !abs_a.is_one(); + + if (is_fresh) { + + numeral m = abs_a + numeral(1); + for (unsigned i = 0; i < values.size(); ++i) { + values[i] = mod_hat(values[i], m); + } + if (values[index].is_one()) { + for (unsigned i = 0; i < values.size(); ++i) { + values[i].neg(); + } + } + SASSERT(values[index].is_minus_one()); + values[index] = m; + } + + TRACE("arith_eq_solver", + tout << "solved at index " << index << ": "; + print_row(tout, values); + ); + + return true; +} + + +void arith_eq_solver::substitute( + row& r, + row const& s, + unsigned index + ) +{ + SASSERT(1 <= index && index < s.size()); + TRACE("arith_eq_solver", + tout << "substitute " << index << ":\n"; + print_row(tout, r); + print_row(tout, s); + ); + + if (index >= r.size()) { + return; + } + + numeral c = r[index]; + if (c.is_zero()) { + // no-op + } + else if (abs(s[index]).is_one()) { + // + // s encodes an equation that contains a variable + // with a unit coefficient. + // + // Let + // c = r[index] + // s = s[index]*x + s'*y = 0 + // r = c*x + r'*y = 0 + // + // => + // + // 0 + // = + // -sign(s[index])*c*s + r + // = + // -s[index]*sign(s[index])*c*x - sign(s[index])*c*s'*y + c*x + r'*y + // = + // -c*x - sign(s[index])*c*s'*y + c*x + r'*y + // = + // -sign(s[index])*c*s'*y + r'*y + // + numeral sign_s = s[index].is_pos()?numeral(1):numeral(-1); + for (unsigned i = 0; i < r.size(); ++i) { + r[i] -= c*sign_s*s[i]; + } + for (unsigned i = r.size(); i < s.size(); ++i) { + r.push_back(-c*sign_s*s[i]); + } + } + else { + // + // s encodes a substitution using an auxiliary variable. + // the auxiliary variable is at position 'index'. + // + // Let + // c = r[index] + // s = s[index]*x + s'*y = 0 + // r = c*x + r'*y = 0 + // + // s encodes : x |-> s[index]*x' + s'*y + // + // Set: + // + // r := c*s + r'*y + // + r[index] = numeral(0); + for (unsigned i = 0; i < r.size(); ++i) { + r[i] += c*s[i]; + } + for (unsigned i = r.size(); i < s.size(); ++i) { + r.push_back(c*s[i]); + } + + } + + TRACE("arith_eq_solver", + tout << "result: "; + print_row(tout, r); + ); +} + +bool arith_eq_solver::solve_integer_equations( + vector& rows, + row& unsat_row + ) +{ + // return solve_integer_equations_units(rows, unsat_row); + return solve_integer_equations_gcd(rows, unsat_row); +} + +// +// Naive integer equation solver where only units are eliminated. +// + +bool arith_eq_solver::solve_integer_equations_units( + vector& rows, + row& unsat_row + ) +{ + + TRACE("arith_eq_solver", print_rows(tout << "solving:\n", rows);); + + unsigned_vector todo, done; + + for (unsigned i = 0; i < rows.size(); ++i) { + todo.push_back(i); + row& r = rows[i]; + gcd_normalize(r); + if (!gcd_test(r)) { + unsat_row = r; + TRACE("arith_eq_solver", print_row(tout << "input is unsat: ", unsat_row); ); + return false; + } + } + for (unsigned i = 0; i < todo.size(); ++i) { + row& r = rows[todo[i]]; + gcd_normalize(r); + if (!gcd_test(r)) { + unsat_row = r; + TRACE("arith_eq_solver", print_row(tout << "unsat: ", unsat_row); ); + return false; + } + unsigned index = find_abs_min(r); + SASSERT(1 <= index && index < r.size()); + numeral a = r[index]; + numeral abs_a = abs(a); + if (abs_a.is_zero()) { + continue; + } + else if (abs_a.is_one()) { + for (unsigned j = i+1; j < todo.size(); ++j) { + substitute(rows[todo[j]], r, index); + } + for (unsigned j = 0; j < done.size(); ++j) { + row& r2 = rows[done[j]]; + if (!r2[index].is_zero()) { + substitute(r2, r, index); + todo.push_back(done[j]); + done.erase(done.begin()+j); + --j; + } + } + } + else { + done.push_back(todo[i]); + } + } + + TRACE("arith_eq_solver", + tout << ((done.size()<=1)?"solved ":"incomplete check ") << done.size() << "\n"; + for (unsigned i = 0; i < done.size(); ++i) { + print_row(tout, rows[done[i]]); + } + ); + + return true; +} + + + + +// +// Partial solver based on the omega test equalities. +// unsatisfiability is not preserved when eliminating +// auxiliary variables. +// + +bool arith_eq_solver::solve_integer_equations_omega( + vector & rows, + row& unsat_row + ) +{ + unsigned index; + bool is_fresh; + vector rows_solved; + unsigned_vector indices; + unsigned_vector aux_indices; + + + for (unsigned i = 0; i < rows.size(); ++i) { + rows_solved.push_back(rows[i]); + row& r = rows_solved.back(); + for (unsigned j = 0; j + 1 < rows_solved.size(); ++j) { + substitute(r, rows_solved[j], indices[j]); + } + if (!solve_integer_equation(r, index, is_fresh)) { + unsat_row = r; + gcd_normalize(unsat_row); + // invert the substitution for every index that is fresh. + + TRACE("arith_eq_solver", + tout << "unsat:\n"; + print_row(tout, unsat_row); + for (unsigned l = 0; l + 1< rows_solved.size(); ++l) { + print_row(tout, rows_solved[l]); + }); + + for (unsigned j = rows_solved.size()-1; j > 0; ) { + --j; + row& solved_row = rows_solved[j]; + unsigned index_j = indices[j]; + unsigned aux_index_j = aux_indices[j]; + SASSERT(index_j <= aux_index_j); + if (unsat_row.size() <= aux_index_j) { + unsat_row.resize(aux_index_j+1); + } + numeral m = solved_row[aux_index_j]; + numeral k = unsat_row[aux_index_j]; + if (aux_index_j != index_j && !k.is_zero()) { + // + // solved_row: -x_index + m*sigma + r1 = 0 + // unsat_row: k*sigma + r2 = 0 + // + // <=> + // + // solved_row: -k*x_index + k*m*sigma + k*r1 = 0 + // unsat_row: m*k*sigma + m*r2 = 0 + // + // => + // + // m*k*sigma + m*r2 + k*x_index - k*m*sigma - k*r1 = 0 + // + for (unsigned l = 0; l < unsat_row.size(); ++l) { + unsat_row[l] *= m; + unsat_row[l] -= k*solved_row[l]; + } + for (unsigned l = unsat_row.size(); l < solved_row.size(); ++l) { + unsat_row.push_back(solved_row[l]); + } + + gcd_normalize(unsat_row); + TRACE("arith_eq_solver", + tout << "gcd: "; + print_row(tout, solved_row); + print_row(tout, unsat_row); + ); + + } + if (gcd_test(unsat_row)) { + TRACE("arith_eq_solver", tout << "missed pure explanation\n";); + return true; + } + SASSERT(!gcd_test(unsat_row)); + } + return false; + } + else if (r[index].is_zero()) { + // Row is trival + rows_solved.pop_back(); + continue; + } + else if (!abs(r[index]).is_one()) { + // + // The solution introduces a fresh auxiliary variable. + // Make space for this variable as a fresh numeral. + // + indices.push_back(index); + aux_indices.push_back(r.size()); + + r.push_back(r[index]); + r[index] = numeral(-1); + + // re-solve the same row. + --i; + } + else { + indices.push_back(index); + aux_indices.push_back(index); + } + } + return true; +} + + + +// +// Eliminate variables by searching for combination of rows where +// the coefficients have gcd = 1. +// + +bool arith_eq_solver::solve_integer_equations_gcd( + vector & rows, + row& unsat_row + ) +{ + unsigned_vector live, useful, gcd_pos; + vector gcds; + rational u, v; + + if (rows.empty()) { + return true; + } + for (unsigned i = 0; i < rows.size(); ++i) { + live.push_back(i); + row& r = rows[i]; + gcd_normalize(r); + if (!gcd_test(r)) { + unsat_row = r; + TRACE("arith_eq_solver", print_row(tout << "input is unsat: ", unsat_row); ); + return false; + } + } + unsigned max_column = rows[0].size(); + bool change = true; + while (change && !live.empty()) { + change = false; + for (unsigned i = 1; i < max_column; ++i) { + rational g(0); + gcds.reset(); + gcd_pos.reset(); + unsigned j = 0; + for (; j < live.size(); ++j) { + rational const& k = rows[live[j]][i]; + if (k.is_zero()) { + continue; + } + if (g.is_zero()) { + g = abs(k); + } + else { + g = gcd(g, abs(k)); + } + if (abs(g).is_one()) { + break; + } + gcds.push_back(g); + gcd_pos.push_back(live[j]); + } + if (j == live.size()) { + continue; + } + + change = true; + // found gcd, now identify reduced set of rows with GCD = 1. + g = abs(rows[live[j]][i]); + useful.push_back(live[j]); + unsigned live_pos = j; + for (j = gcds.size(); !g.is_one() && j > 0; ) { + SASSERT(g.is_pos()); + --j; + if (j == 0 || !gcd(g, gcds[j-1]).is_one()) { + useful.push_back(gcd_pos[j]); + g = gcd(g, gcds[j]); + SASSERT(j == 0 || gcd(g,gcds[j-1]).is_one()); + } + } + // + // we now have a set "useful" of rows whose combined GCD = 1. + // pivot the remaining with the first row. + // + row& r0 = rows[useful[0]]; + for (j = 1; j < useful.size(); ++j) { + row& r1 = rows[useful[j]]; + g = gcd(r0[i], r1[i], u, v); + for (unsigned k = 0; k < max_column; ++k) { + r0[k] = u*r0[k] + v*r1[k]; + } + SASSERT(g == r0[i]); + } + SASSERT(abs(r0[i]).is_one()); + live.erase(live.begin()+live_pos); + for (j = 0; j < live.size(); ++j) { + row& r = rows[live[j]]; + if (!r[i].is_zero()) { + substitute(r, r0, i); + gcd_normalize(r); + if (!gcd_test(r)) { + unsat_row = r; + TRACE("arith_eq_solver", print_row(tout << "unsat: ", unsat_row); ); + return false; + } + } + } + } + } + + TRACE("arith_eq_solver", + tout << ((live.size()<=1)?"solved ":"incomplete check ") << live.size() << "\n"; + for (unsigned i = 0; i < live.size(); ++i) { + print_row(tout, rows[live[i]]); + } + ); + + return true; +} diff --git a/lib/arith_eq_solver.h b/lib/arith_eq_solver.h new file mode 100644 index 000000000..e17ae2d30 --- /dev/null +++ b/lib/arith_eq_solver.h @@ -0,0 +1,108 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + arith_eq_solver.h + +Abstract: + + Solver for linear arithmetic equalities. + +Author: + + Nikolaj Bjorner (nbjorner) 2012-02-25 + +--*/ +#ifndef _ARITH_EQ_SOLVER_H_ +#define _ARITH_EQ_SOLVER_H_ + +#include"arith_decl_plugin.h" +#include"arith_rewriter.h" + +/** + \brief Simplifier for the arith family. +*/ +class arith_eq_solver { + typedef rational numeral; + + ast_manager& m; + params_ref m_params; + arith_util m_util; + arith_rewriter m_arith_rewriter; + + bool is_neg_poly(expr * t) const; + + void prop_mod_const(expr * e, unsigned depth, numeral const& k, expr_ref& result); + + bool gcd_test(vector& value); + unsigned find_abs_min(vector& values); + void gcd_normalize(vector& values); + void substitute(vector& r, vector const& s, unsigned index); + bool solve_integer_equations_units( + vector > & rows, + vector& unsat_row + ); + + bool solve_integer_equations_omega( + vector > & rows, + vector& unsat_row + ); + + void compute_hnf(vector >& A); + + bool solve_integer_equations_hermite( + vector > & rows, + vector& unsat_row + ); + + bool solve_integer_equations_gcd( + vector > & rows, + vector& unsat_row + ); + +public: + arith_eq_solver(ast_manager & m, params_ref const& p = params_ref()); + ~arith_eq_solver(); + + // Integer linear solver for a single equation. + // The array values contains integer coefficients + // + // Determine integer solutions to: + // + // a+k = 0 + // + // where a = sum_i a_i*k_i + // + + typedef vector row; + typedef vector matrix; + + bool solve_integer_equation( + row& values, + unsigned& index, + bool& is_fresh + ); + + // Integer linear solver. + // Determine integer solutions to: + // + // a+k = 0 + // + // where a = sum_i a_i*k_i + // + // Solution, if there is any, is returned as a substitution. + // The return value is "true". + // If there is no solution, then return "false". + // together with equality "eq_unsat", such that + // + // eq_unsat = 0 + // + // is implied and is unsatisfiable over the integers. + // + + bool solve_integer_equations(vector& rows, row& unsat_row); + +}; + +#endif /* _ARITH_EQ_SOLVER_H_ */ diff --git a/lib/arith_rewriter.cpp b/lib/arith_rewriter.cpp new file mode 100644 index 000000000..74562befd --- /dev/null +++ b/lib/arith_rewriter.cpp @@ -0,0 +1,1493 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + arith_rewriter.cpp + +Abstract: + + Basic rewriting rules for arithmetic + +Author: + + Leonardo (leonardo) 2011-04-10 + +Notes: + +--*/ +#include"arith_rewriter.h" +#include"poly_rewriter_def.h" +#include"algebraic_numbers.h" +#include"ast_pp.h" + +void arith_rewriter::updt_local_params(params_ref const & p) { + m_arith_lhs = p.get_bool(":arith-lhs", false); + m_gcd_rounding = p.get_bool(":gcd-rounding", false); + m_eq2ineq = p.get_bool(":eq2ineq", false); + m_elim_to_real = p.get_bool(":elim-to-real", false); + m_push_to_real = p.get_bool(":push-to-real", true); + m_anum_simp = p.get_bool(":algebraic-number-evaluator", true); + m_max_degree = p.get_uint(":max-degree", 64); + m_expand_power = p.get_bool(":expand-power", false); + m_mul2power = p.get_bool(":mul-to-power", false); + m_elim_rem = p.get_bool(":elim-rem", false); + m_expand_tan = p.get_bool(":expand-tan", false); + set_sort_sums(p.get_bool(":sort-sums", false)); // set here to avoid collision with bvadd +} + +void arith_rewriter::updt_params(params_ref const & p) { + poly_rewriter::updt_params(p); + updt_local_params(p); +} + +void arith_rewriter::get_param_descrs(param_descrs & r) { + poly_rewriter::get_param_descrs(r); + r.insert(":algebraic-number-evaluator", CPK_BOOL, "(default: true) simplify/evaluate expressions containing (algebraic) irrational numbers."); + r.insert(":mul-to-power", CPK_BOOL, "(default: false) collpase (* t ... t) into (^ t k), it is ignored if :expand-power is true."); + r.insert(":expand-power", CPK_BOOL, "(default: false) expand (^ t k) into (* t ... t) if 1 < k <= :max-degree."); + r.insert(":expand-tan", CPK_BOOL, "(default: false) replace (tan x) with (/ (sin x) (cos x))."); + r.insert(":max-degree", CPK_UINT, "(default: 64) max degree of algebraic numbers (and power operators) processed by simplifier."); + r.insert(":eq2ineq", CPK_BOOL, "(default: false) split arithmetic equalities into two inequalities."); + r.insert(":sort-sums", CPK_BOOL, "(default: false) sort the arguments of + application."); + r.insert(":gcd-rounding", CPK_BOOL, "(default: false) use gcd rounding on integer arithmetic atoms."); + r.insert(":arith-lhs", CPK_BOOL, "(default: false) all monomials are moved to the left-hand-side, and the right-hand-side is just a constant."); + r.insert(":elim-to-real", CPK_BOOL, "(default: false) eliminate to_real from arithmetic predicates that contain only integers."); + r.insert(":push-to-real", CPK_BOOL, "(default: true) distribute to_real over * and +."); + r.insert(":elim-rem", CPK_BOOL, "(default: false) replace (rem x y) with (ite (>= y 0) (mod x y) (- (mod x y)))."); +} + +br_status arith_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + br_status st = BR_FAILED; + SASSERT(f->get_family_id() == get_fid()); + switch (f->get_decl_kind()) { + case OP_NUM: st = BR_FAILED; break; + case OP_IRRATIONAL_ALGEBRAIC_NUM: st = BR_FAILED; break; + case OP_LE: SASSERT(num_args == 2); st = mk_le_core(args[0], args[1], result); break; + case OP_GE: SASSERT(num_args == 2); st = mk_ge_core(args[0], args[1], result); break; + case OP_LT: SASSERT(num_args == 2); st = mk_lt_core(args[0], args[1], result); break; + case OP_GT: SASSERT(num_args == 2); st = mk_gt_core(args[0], args[1], result); break; + case OP_ADD: st = mk_add_core(num_args, args, result); break; + case OP_MUL: st = mk_mul_core(num_args, args, result); break; + case OP_SUB: st = mk_sub(num_args, args, result); break; + case OP_DIV: SASSERT(num_args == 2); st = mk_div_core(args[0], args[1], result); break; + case OP_IDIV: SASSERT(num_args == 2); st = mk_idiv_core(args[0], args[1], result); break; + case OP_MOD: SASSERT(num_args == 2); st = mk_mod_core(args[0], args[1], result); break; + case OP_REM: SASSERT(num_args == 2); st = mk_rem_core(args[0], args[1], result); break; + case OP_UMINUS: SASSERT(num_args == 1); st = mk_uminus(args[0], result); break; + case OP_TO_REAL: SASSERT(num_args == 1); st = mk_to_real_core(args[0], result); break; + case OP_TO_INT: SASSERT(num_args == 1); st = mk_to_int_core(args[0], result); break; + case OP_IS_INT: SASSERT(num_args == 1); st = mk_is_int(args[0], result); break; + case OP_POWER: SASSERT(num_args == 2); st = mk_power_core(args[0], args[1], result); break; + case OP_SIN: SASSERT(num_args == 1); st = mk_sin_core(args[0], result); break; + case OP_COS: SASSERT(num_args == 1); st = mk_cos_core(args[0], result); break; + case OP_TAN: SASSERT(num_args == 1); st = mk_tan_core(args[0], result); break; + case OP_ASIN: SASSERT(num_args == 1); st = mk_asin_core(args[0], result); break; + case OP_ACOS: SASSERT(num_args == 1); st = mk_acos_core(args[0], result); break; + case OP_ATAN: SASSERT(num_args == 1); st = mk_atan_core(args[0], result); break; + case OP_SINH: SASSERT(num_args == 1); st = mk_sinh_core(args[0], result); break; + case OP_COSH: SASSERT(num_args == 1); st = mk_cosh_core(args[0], result); break; + case OP_TANH: SASSERT(num_args == 1); st = mk_tanh_core(args[0], result); break; + default: st = BR_FAILED; break; + } + CTRACE("arith_rewriter", st != BR_FAILED, tout << mk_pp(f, m()); + for (unsigned i = 0; i < num_args; ++i) tout << mk_pp(args[i], m()) << " "; + tout << "\n==>\n" << mk_pp(result.get(), m()) << "\n";); + return st; +} + +void arith_rewriter::get_coeffs_gcd(expr * t, numeral & g, bool & first, unsigned & num_consts) { + unsigned sz; + expr * const * ms = get_monomials(t, sz); + SASSERT(sz >= 1); + numeral a; + for (unsigned i = 0; i < sz; i++) { + expr * arg = ms[i]; + if (is_numeral(arg, a)) { + if (!a.is_zero()) + num_consts++; + continue; + } + if (first) { + get_power_product(arg, g); + SASSERT(g.is_int()); + first = false; + } + else { + get_power_product(arg, a); + SASSERT(a.is_int()); + g = gcd(abs(a), g); + } + if (g.is_one()) + return; + } +} + +bool arith_rewriter::div_polynomial(expr * t, numeral const & g, const_treatment ct, expr_ref & result) { + SASSERT(m_util.is_int(t)); + SASSERT(!g.is_one()); + unsigned sz; + expr * const * ms = get_monomials(t, sz); + expr_ref_buffer new_args(m()); + numeral a; + for (unsigned i = 0; i < sz; i++) { + expr * arg = ms[i]; + if (is_numeral(arg, a)) { + a /= g; + if (!a.is_int()) { + switch (ct) { + case CT_FLOOR: + a = floor(a); + break; + case CT_CEIL: + a = ceil(a); + break; + case CT_FALSE: + return false; + } + } + if (!a.is_zero()) + new_args.push_back(m_util.mk_numeral(a, true)); + continue; + } + expr * pp = get_power_product(arg, a); + a /= g; + SASSERT(a.is_int()); + if (!a.is_zero()) { + if (a.is_one()) + new_args.push_back(pp); + else + new_args.push_back(m_util.mk_mul(m_util.mk_numeral(a, true), pp)); + } + } + switch (new_args.size()) { + case 0: result = m_util.mk_numeral(numeral(0), true); return true; + case 1: result = new_args[0]; return true; + default: result = m_util.mk_add(new_args.size(), new_args.c_ptr()); return true; + } +} + +bool arith_rewriter::is_bound(expr * arg1, expr * arg2, op_kind kind, expr_ref & result) { + numeral c; + if (!is_add(arg1) && is_numeral(arg2, c)) { + numeral a; + bool r = false; + expr * pp = get_power_product(arg1, a); + if (a.is_neg()) { + a.neg(); + c.neg(); + kind = inv(kind); + r = true; + } + if (!a.is_one()) + r = true; + if (!r) + return false; + c /= a; + bool is_int = m_util.is_int(arg1); + if (is_int && !c.is_int()) { + switch (kind) { + case LE: c = floor(c); break; + case GE: c = ceil(c); break; + case EQ: result = m().mk_false(); return true; + } + } + expr * k = m_util.mk_numeral(c, is_int); + switch (kind) { + case LE: result = m_util.mk_le(pp, k); return true; + case GE: result = m_util.mk_ge(pp, k); return true; + case EQ: result = m_util.mk_eq(pp, k); return true; + } + } + return false; +} + +bool arith_rewriter::elim_to_real_var(expr * var, expr_ref & new_var) { + numeral val; + if (m_util.is_numeral(var, val)) { + if (!val.is_int()) + return false; + new_var = m_util.mk_numeral(val, true); + return true; + } + else if (m_util.is_to_real(var)) { + new_var = to_app(var)->get_arg(0); + return true; + } + return false; +} + +bool arith_rewriter::elim_to_real_mon(expr * monomial, expr_ref & new_monomial) { + if (m_util.is_mul(monomial)) { + expr_ref_buffer new_vars(m()); + expr_ref new_var(m()); + unsigned num = to_app(monomial)->get_num_args(); + for (unsigned i = 0; i < num; i++) { + if (!elim_to_real_var(to_app(monomial)->get_arg(i), new_var)) + return false; + new_vars.push_back(new_var); + } + new_monomial = m_util.mk_mul(new_vars.size(), new_vars.c_ptr()); + return true; + } + else { + return elim_to_real_var(monomial, new_monomial); + } +} + +bool arith_rewriter::elim_to_real_pol(expr * p, expr_ref & new_p) { + if (m_util.is_add(p)) { + expr_ref_buffer new_monomials(m()); + expr_ref new_monomial(m()); + unsigned num = to_app(p)->get_num_args(); + for (unsigned i = 0; i < num; i++) { + if (!elim_to_real_mon(to_app(p)->get_arg(i), new_monomial)) + return false; + new_monomials.push_back(new_monomial); + } + new_p = m_util.mk_add(new_monomials.size(), new_monomials.c_ptr()); + return true; + } + else { + return elim_to_real_mon(p, new_p); + } +} + +bool arith_rewriter::elim_to_real(expr * arg1, expr * arg2, expr_ref & new_arg1, expr_ref & new_arg2) { + if (!m_util.is_real(arg1)) + return false; + return elim_to_real_pol(arg1, new_arg1) && elim_to_real_pol(arg2, new_arg2); +} + +bool arith_rewriter::is_reduce_power_target(expr * arg, bool is_eq) { + unsigned sz; + expr * const * args; + if (m_util.is_mul(arg)) { + sz = to_app(arg)->get_num_args(); + args = to_app(arg)->get_args(); + } + else { + sz = 1; + args = &arg; + } + for (unsigned i = 0; i < sz; i++) { + expr * arg = args[i]; + if (m_util.is_power(arg)) { + rational k; + if (m_util.is_numeral(to_app(arg)->get_arg(1), k) && k.is_int() && ((is_eq && k > rational(1)) || (!is_eq && k > rational(2)))) + return true; + } + } + return false; +} + +expr * arith_rewriter::reduce_power(expr * arg, bool is_eq) { + if (is_zero(arg)) + return arg; + unsigned sz; + expr * const * args; + if (m_util.is_mul(arg)) { + sz = to_app(arg)->get_num_args(); + args = to_app(arg)->get_args(); + } + else { + sz = 1; + args = &arg; + } + ptr_buffer new_args; + rational k; + for (unsigned i = 0; i < sz; i++) { + expr * arg = args[i]; + if (m_util.is_power(arg) && m_util.is_numeral(to_app(arg)->get_arg(1), k) && k.is_int() && ((is_eq && k > rational(1)) || (!is_eq && k > rational(2)))) { + if (is_eq || !k.is_even()) + new_args.push_back(to_app(arg)->get_arg(0)); + else + new_args.push_back(m_util.mk_power(to_app(arg)->get_arg(0), m_util.mk_numeral(rational(2), m_util.is_int(arg)))); + } + else { + new_args.push_back(arg); + } + } + SASSERT(new_args.size() >= 1); + if (new_args.size() == 1) + return new_args[0]; + else + return m_util.mk_mul(new_args.size(), new_args.c_ptr()); +} + +br_status arith_rewriter::reduce_power(expr * arg1, expr * arg2, op_kind kind, expr_ref & result) { + expr * new_arg1 = reduce_power(arg1, kind == EQ); + expr * new_arg2 = reduce_power(arg2, kind == EQ); + switch (kind) { + case LE: result = m_util.mk_le(new_arg1, new_arg2); return BR_REWRITE1; + case GE: result = m_util.mk_ge(new_arg1, new_arg2); return BR_REWRITE1; + default: result = m().mk_eq(new_arg1, new_arg2); return BR_REWRITE1; + } +} + +br_status arith_rewriter::mk_le_ge_eq_core(expr * arg1, expr * arg2, op_kind kind, expr_ref & result) { + expr_ref new_arg1(m()); + expr_ref new_arg2(m()); + if ((is_zero(arg1) && is_reduce_power_target(arg2, kind == EQ)) || + (is_zero(arg2) && is_reduce_power_target(arg1, kind == EQ))) + return reduce_power(arg1, arg2, kind, result); + CTRACE("elim_to_real", m_elim_to_real, tout << "after_elim_to_real\n" << mk_ismt2_pp(arg1, m()) << "\n" << mk_ismt2_pp(arg2, m()) << "\n";); + br_status st = cancel_monomials(arg1, arg2, m_arith_lhs, new_arg1, new_arg2); + TRACE("mk_le_bug", tout << "st: " << st << "\n";); + if (st != BR_FAILED) { + arg1 = new_arg1; + arg2 = new_arg2; + } + expr_ref new_new_arg1(m()); + expr_ref new_new_arg2(m()); + if (m_elim_to_real && elim_to_real(arg1, arg2, new_new_arg1, new_new_arg2)) { + arg1 = new_new_arg1; + arg2 = new_new_arg2; + if (st == BR_FAILED) + st = BR_DONE; + } + numeral a1, a2; + if (is_numeral(arg1, a1) && is_numeral(arg2, a2)) { + switch (kind) { + case LE: result = a1 <= a2 ? m().mk_true() : m().mk_false(); return BR_DONE; + case GE: result = a1 >= a2 ? m().mk_true() : m().mk_false(); return BR_DONE; + default: result = a1 == a2 ? m().mk_true() : m().mk_false(); return BR_DONE; + } + } + +#define ANUM_LE_GE_EQ() { \ + switch (kind) { \ + case LE: result = am.le(v1, v2) ? m().mk_true() : m().mk_false(); return BR_DONE; \ + case GE: result = am.ge(v1, v2) ? m().mk_true() : m().mk_false(); return BR_DONE; \ + default: result = am.eq(v1, v2) ? m().mk_true() : m().mk_false(); return BR_DONE; \ + } \ +} + + if (m_anum_simp) { + if (is_numeral(arg1, a1) && m_util.is_irrational_algebraic_numeral(arg2)) { + anum_manager & am = m_util.am(); + scoped_anum v1(am); + am.set(v1, a1.to_mpq()); + anum const & v2 = m_util.to_irrational_algebraic_numeral(arg2); + ANUM_LE_GE_EQ(); + } + if (m_util.is_irrational_algebraic_numeral(arg1) && is_numeral(arg2, a2)) { + anum_manager & am = m_util.am(); + anum const & v1 = m_util.to_irrational_algebraic_numeral(arg1); + scoped_anum v2(am); + am.set(v2, a2.to_mpq()); + ANUM_LE_GE_EQ(); + } + if (m_util.is_irrational_algebraic_numeral(arg1) && m_util.is_irrational_algebraic_numeral(arg2)) { + anum_manager & am = m_util.am(); + anum const & v1 = m_util.to_irrational_algebraic_numeral(arg1); + anum const & v2 = m_util.to_irrational_algebraic_numeral(arg2); + ANUM_LE_GE_EQ(); + } + } + if (is_bound(arg1, arg2, kind, result)) + return BR_DONE; + if (is_bound(arg2, arg1, inv(kind), result)) + return BR_DONE; + bool is_int = m_util.is_int(arg1); + if (is_int && m_gcd_rounding) { + bool first = true; + numeral g; + unsigned num_consts = 0; + get_coeffs_gcd(arg1, g, first, num_consts); + TRACE("arith_rewriter_gcd", tout << "[step1] g: " << g << ", num_consts: " << num_consts << "\n";); + if ((first || !g.is_one()) && num_consts <= 1) + get_coeffs_gcd(arg2, g, first, num_consts); + TRACE("arith_rewriter_gcd", tout << "[step2] g: " << g << ", num_consts: " << num_consts << "\n";); + if (!first && !g.is_one() && num_consts <= 1) { + bool is_sat = div_polynomial(arg1, g, (kind == LE ? CT_CEIL : (kind == GE ? CT_FLOOR : CT_FALSE)), new_arg1); + if (!is_sat) { + result = m().mk_false(); + return BR_DONE; + } + is_sat = div_polynomial(arg2, g, (kind == LE ? CT_FLOOR : (kind == GE ? CT_CEIL : CT_FALSE)), new_arg2); + if (!is_sat) { + result = m().mk_false(); + return BR_DONE; + } + arg1 = new_arg1.get(); + arg2 = new_arg2.get(); + st = BR_DONE; + } + } + if (st != BR_FAILED) { + switch (kind) { + case LE: result = m_util.mk_le(arg1, arg2); return BR_DONE; + case GE: result = m_util.mk_ge(arg1, arg2); return BR_DONE; + default: result = m().mk_eq(arg1, arg2); return BR_DONE; + } + } + return BR_FAILED; +} + +br_status arith_rewriter::mk_le_core(expr * arg1, expr * arg2, expr_ref & result) { + return mk_le_ge_eq_core(arg1, arg2, LE, result); +} + +br_status arith_rewriter::mk_lt_core(expr * arg1, expr * arg2, expr_ref & result) { + result = m().mk_not(m_util.mk_le(arg2, arg1)); + return BR_REWRITE2; +} + +br_status arith_rewriter::mk_ge_core(expr * arg1, expr * arg2, expr_ref & result) { + return mk_le_ge_eq_core(arg1, arg2, GE, result); +} + +br_status arith_rewriter::mk_gt_core(expr * arg1, expr * arg2, expr_ref & result) { + result = m().mk_not(m_util.mk_le(arg1, arg2)); + return BR_REWRITE2; +} + +br_status arith_rewriter::mk_eq_core(expr * arg1, expr * arg2, expr_ref & result) { + if (m_eq2ineq) { + result = m().mk_and(m_util.mk_le(arg1, arg2), m_util.mk_ge(arg1, arg2)); + return BR_REWRITE2; + } + return mk_le_ge_eq_core(arg1, arg2, EQ, result); +} + +bool arith_rewriter::is_anum_simp_target(unsigned num_args, expr * const * args) { + if (!m_anum_simp) + return false; + unsigned num_irrat = 0; + unsigned num_rat = 0; + for (unsigned i = 0; i < num_args; i++) { + if (m_util.is_numeral(args[i])) { + num_rat++; + if (num_irrat > 0) + return true; + } + if (m_util.is_irrational_algebraic_numeral(args[i]) && + m_util.am().degree(m_util.to_irrational_algebraic_numeral(args[i])) <= m_max_degree) { + num_irrat++; + if (num_irrat > 1 || num_rat > 0) + return true; + } + } + return false; +} + +br_status arith_rewriter::mk_add_core(unsigned num_args, expr * const * args, expr_ref & result) { + if (is_anum_simp_target(num_args, args)) { + expr_ref_buffer new_args(m()); + anum_manager & am = m_util.am(); + scoped_anum r(am); + scoped_anum arg(am); + rational rarg; + am.set(r, 0); + for (unsigned i = 0; i < num_args; i ++) { + unsigned d = am.degree(r); + if (d > 1 && d > m_max_degree) { + new_args.push_back(m_util.mk_numeral(r, false)); + am.set(r, 0); + } + + if (m_util.is_numeral(args[i], rarg)) { + am.set(arg, rarg.to_mpq()); + am.add(r, arg, r); + continue; + } + + if (m_util.is_irrational_algebraic_numeral(args[i])) { + anum const & irarg = m_util.to_irrational_algebraic_numeral(args[i]); + if (am.degree(irarg) <= m_max_degree) { + am.add(r, irarg, r); + continue; + } + } + + new_args.push_back(args[i]); + } + + if (new_args.empty()) { + result = m_util.mk_numeral(r, false); + return BR_DONE; + } + + new_args.push_back(m_util.mk_numeral(r, false)); + br_status st = poly_rewriter::mk_add_core(new_args.size(), new_args.c_ptr(), result); + if (st == BR_FAILED) { + result = m().mk_app(get_fid(), OP_ADD, new_args.size(), new_args.c_ptr()); + return BR_DONE; + } + return st; + } + else { + return poly_rewriter::mk_add_core(num_args, args, result); + } +} + +br_status arith_rewriter::mk_mul_core(unsigned num_args, expr * const * args, expr_ref & result) { + if (is_anum_simp_target(num_args, args)) { + expr_ref_buffer new_args(m()); + anum_manager & am = m_util.am(); + scoped_anum r(am); + scoped_anum arg(am); + rational rarg; + am.set(r, 1); + for (unsigned i = 0; i < num_args; i ++) { + unsigned d = am.degree(r); + if (d > 1 && d > m_max_degree) { + new_args.push_back(m_util.mk_numeral(r, false)); + am.set(r, 1); + } + + if (m_util.is_numeral(args[i], rarg)) { + am.set(arg, rarg.to_mpq()); + am.mul(r, arg, r); + continue; + } + if (m_util.is_irrational_algebraic_numeral(args[i])) { + anum const & irarg = m_util.to_irrational_algebraic_numeral(args[i]); + if (am.degree(irarg) <= m_max_degree) { + am.mul(r, irarg, r); + continue; + } + } + + new_args.push_back(args[i]); + } + + if (new_args.empty()) { + result = m_util.mk_numeral(r, false); + return BR_DONE; + } + new_args.push_back(m_util.mk_numeral(r, false)); + + br_status st = poly_rewriter::mk_mul_core(new_args.size(), new_args.c_ptr(), result); + if (st == BR_FAILED) { + result = m().mk_app(get_fid(), OP_MUL, new_args.size(), new_args.c_ptr()); + return BR_DONE; + } + return st; + } + else { + return poly_rewriter::mk_mul_core(num_args, args, result); + } +} + +br_status arith_rewriter::mk_div_irrat_rat(expr * arg1, expr * arg2, expr_ref & result) { + SASSERT(m_util.is_real(arg1)); + SASSERT(m_util.is_irrational_algebraic_numeral(arg1)); + SASSERT(m_util.is_numeral(arg2)); + anum_manager & am = m_util.am(); + anum const & val1 = m_util.to_irrational_algebraic_numeral(arg1); + rational rval2; + VERIFY(m_util.is_numeral(arg2, rval2)); + if (rval2.is_zero()) + return BR_FAILED; + scoped_anum val2(am); + am.set(val2, rval2.to_mpq()); + scoped_anum r(am); + am.div(val1, val2, r); + result = m_util.mk_numeral(r, false); + return BR_DONE; +} + +br_status arith_rewriter::mk_div_rat_irrat(expr * arg1, expr * arg2, expr_ref & result) { + SASSERT(m_util.is_real(arg1)); + SASSERT(m_util.is_numeral(arg1)); + SASSERT(m_util.is_irrational_algebraic_numeral(arg2)); + anum_manager & am = m_util.am(); + rational rval1; + VERIFY(m_util.is_numeral(arg1, rval1)); + scoped_anum val1(am); + am.set(val1, rval1.to_mpq()); + anum const & val2 = m_util.to_irrational_algebraic_numeral(arg2); + scoped_anum r(am); + am.div(val1, val2, r); + result = m_util.mk_numeral(r, false); + return BR_DONE; +} + +br_status arith_rewriter::mk_div_irrat_irrat(expr * arg1, expr * arg2, expr_ref & result) { + SASSERT(m_util.is_real(arg1)); + SASSERT(m_util.is_irrational_algebraic_numeral(arg1)); + SASSERT(m_util.is_irrational_algebraic_numeral(arg2)); + anum_manager & am = m_util.am(); + anum const & val1 = m_util.to_irrational_algebraic_numeral(arg1); + if (am.degree(val1) > m_max_degree) + return BR_FAILED; + anum const & val2 = m_util.to_irrational_algebraic_numeral(arg2); + if (am.degree(val2) > m_max_degree) + return BR_FAILED; + scoped_anum r(am); + am.div(val1, val2, r); + result = m_util.mk_numeral(r, false); + return BR_DONE; +} + +br_status arith_rewriter::mk_div_core(expr * arg1, expr * arg2, expr_ref & result) { + if (m_anum_simp) { + if (m_util.is_irrational_algebraic_numeral(arg1) && m_util.is_numeral(arg2)) + return mk_div_irrat_rat(arg1, arg2, result); + if (m_util.is_irrational_algebraic_numeral(arg1) && m_util.is_irrational_algebraic_numeral(arg2)) + return mk_div_irrat_irrat(arg1, arg2, result); + if (m_util.is_irrational_algebraic_numeral(arg2) && m_util.is_numeral(arg1)) + return mk_div_rat_irrat(arg1, arg2, result); + } + set_curr_sort(m().get_sort(arg1)); + numeral v1, v2; + bool is_int; + if (m_util.is_numeral(arg2, v2, is_int) && !v2.is_zero()) { + SASSERT(!is_int); + if (m_util.is_numeral(arg1, v1, is_int)) { + result = m_util.mk_numeral(v1/v2, false); + return BR_DONE; + } + else { + numeral k(1); + k /= v2; + result = m().mk_app(get_fid(), OP_MUL, + m_util.mk_numeral(k, false), + arg1); + return BR_REWRITE1; + } + } + + if (!m_util.is_int(arg1)) { + // (/ (* v1 b) (* v2 d)) --> (* v1/v2 (/ b d)) + expr * a, * b, * c, * d; + if (m_util.is_mul(arg1, a, b) && m_util.is_numeral(a, v1)) { + // do nothing arg1 is of the form v1 * b + } + else { + v1 = rational(1); + b = arg1; + } + if (m_util.is_mul(arg2, c, d) && m_util.is_numeral(c, v2)) { + // do nothing arg2 is of the form v2 * d + } + else { + v2 = rational(1); + d = arg2; + } + TRACE("div_bug", tout << "v1: " << v1 << ", v2: " << v2 << "\n";); + if (!v1.is_one() || !v2.is_one()) { + v1 /= v2; + result = m_util.mk_mul(m_util.mk_numeral(v1, false), + m_util.mk_div(b, d)); + return BR_REWRITE2; + } + } + + return BR_FAILED; +} + +br_status arith_rewriter::mk_idiv_core(expr * arg1, expr * arg2, expr_ref & result) { + set_curr_sort(m().get_sort(arg1)); + numeral v1, v2; + bool is_int; + if (m_util.is_numeral(arg1, v1, is_int) && m_util.is_numeral(arg2, v2, is_int) && !v2.is_zero()) { + result = m_util.mk_numeral(div(v1, v2), is_int); + return BR_DONE; + } + return BR_FAILED; +} + +br_status arith_rewriter::mk_mod_core(expr * arg1, expr * arg2, expr_ref & result) { + set_curr_sort(m().get_sort(arg1)); + numeral v1, v2; + bool is_int; + if (m_util.is_numeral(arg1, v1, is_int) && m_util.is_numeral(arg2, v2, is_int) && !v2.is_zero()) { + result = m_util.mk_numeral(mod(v1, v2), is_int); + return BR_DONE; + } + + if (m_util.is_numeral(arg2, v2, is_int) && is_int && v2.is_one()) { + result = m_util.mk_numeral(numeral(0), true); + return BR_DONE; + } + + // mod is idempotent on non-zero modulus. + expr* t1, *t2; + if (m_util.is_mod(arg1, t1, t2) && t2 == arg2 && m_util.is_numeral(arg2, v2, is_int) && is_int && !v2.is_zero()) { + result = arg1; + return BR_DONE; + } + + // propagate mod inside only if not all arguments are not already mod. + if (m_util.is_numeral(arg2, v2, is_int) && is_int && v2.is_pos() && (is_add(arg1) || is_mul(arg1))) { + TRACE("mod_bug", tout << "mk_mod:\n" << mk_ismt2_pp(arg1, m()) << "\n" << mk_ismt2_pp(arg2, m()) << "\n";); + unsigned num_args = to_app(arg1)->get_num_args(); + unsigned i; + rational arg_v; + for (i = 0; i < num_args; i++) { + expr * arg = to_app(arg1)->get_arg(i); + if (m_util.is_mod(arg)) + continue; + if (m_util.is_numeral(arg, arg_v) && mod(arg_v, v2) == arg_v) + continue; + // found target for rewriting + break; + } + TRACE("mod_bug", tout << "mk_mod target: " << i << "\n";); + if (i == num_args) + return BR_FAILED; // did not find any target for applying simplification + ptr_buffer new_args; + for (unsigned i = 0; i < num_args; i++) + new_args.push_back(m_util.mk_mod(to_app(arg1)->get_arg(i), arg2)); + result = m_util.mk_mod(m().mk_app(to_app(arg1)->get_decl(), new_args.size(), new_args.c_ptr()), arg2); + TRACE("mod_bug", tout << "mk_mod result: " << mk_ismt2_pp(result, m()) << "\n";); + return BR_REWRITE3; + } + + return BR_FAILED; +} + +br_status arith_rewriter::mk_rem_core(expr * arg1, expr * arg2, expr_ref & result) { + set_curr_sort(m().get_sort(arg1)); + numeral v1, v2; + bool is_int; + if (m_util.is_numeral(arg1, v1, is_int) && m_util.is_numeral(arg2, v2, is_int) && !v2.is_zero()) { + numeral m = mod(v1, v2); + // + // rem(v1,v2) = if v2 >= 0 then mod(v1,v2) else -mod(v1,v2) + // + if (v2.is_neg()) { + m.neg(); + } + result = m_util.mk_numeral(m, is_int); + return BR_DONE; + } + else if (m_util.is_numeral(arg2, v2, is_int) && is_int && v2.is_one()) { + result = m_util.mk_numeral(numeral(0), true); + return BR_DONE; + } + else if (m_util.is_numeral(arg2, v2, is_int) && is_int && !v2.is_zero()) { + if (is_add(arg1) || is_mul(arg1)) { + ptr_buffer new_args; + unsigned num_args = to_app(arg1)->get_num_args(); + for (unsigned i = 0; i < num_args; i++) + new_args.push_back(m_util.mk_rem(to_app(arg1)->get_arg(i), arg2)); + result = m().mk_app(to_app(arg1)->get_decl(), new_args.size(), new_args.c_ptr()); + return BR_REWRITE2; + } + else { + if (v2.is_neg()) { + result = m_util.mk_uminus(m_util.mk_mod(arg1, arg2)); + return BR_REWRITE2; + } + else { + result = m_util.mk_mod(arg1, arg2); + return BR_REWRITE1; + } + } + } + else if (m_elim_rem) { + expr * mod = m_util.mk_mod(arg1, arg2); + result = m().mk_ite(m_util.mk_ge(arg2, m_util.mk_numeral(rational(0), true)), + mod, + m_util.mk_uminus(mod)); + TRACE("elim_rem", tout << "result: " << mk_ismt2_pp(result, m()) << "\n";); + return BR_REWRITE3; + } + return BR_FAILED; +} + +br_status arith_rewriter::mk_power_core(expr * arg1, expr * arg2, expr_ref & result) { + numeral x, y; + bool is_num_x = m_util.is_numeral(arg1, x); + bool is_num_y = m_util.is_numeral(arg2, y); + bool is_int_sort = m_util.is_int(arg1); + + if ((is_num_x && x.is_one()) || + (is_num_y && y.is_one())) { + result = arg1; + return BR_DONE; + } + + if (is_num_x && is_num_y) { + if (x.is_zero() && y.is_zero()) + return BR_FAILED; + + if (y.is_zero()) { + result = m_util.mk_numeral(rational(1), m().get_sort(arg1)); + return BR_DONE; + } + + if (x.is_zero()) { + result = arg1; + return BR_DONE; + } + + if (y.is_unsigned() && y.get_unsigned() <= m_max_degree) { + x = power(x, y.get_unsigned()); + result = m_util.mk_numeral(x, m().get_sort(arg1)); + return BR_DONE; + } + + if (!is_int_sort && (-y).is_unsigned() && (-y).get_unsigned() <= m_max_degree) { + x = power(rational(1)/x, (-y).get_unsigned()); + result = m_util.mk_numeral(x, m().get_sort(arg1)); + return BR_DONE; + } + } + + if (m_util.is_power(arg1) && is_num_y && y.is_int() && !y.is_zero()) { + // (^ (^ t y2) y) --> (^ t (* y2 y)) If y2 > 0 && y != 0 && y and y2 are integers + rational y2; + if (m_util.is_numeral(to_app(arg1)->get_arg(1), y2) && y2.is_int() && y2.is_pos()) { + result = m_util.mk_power(to_app(arg1)->get_arg(0), m_util.mk_numeral(y*y2, is_int_sort)); + return BR_REWRITE1; + } + } + + if (!is_int_sort && is_num_y && y.is_neg()) { + // (^ t -k) --> (^ (/ 1 t) k) + result = m_util.mk_power(m_util.mk_div(m_util.mk_numeral(rational(1), false), arg1), + m_util.mk_numeral(-y, false)); + return BR_REWRITE2; + } + + if (!is_int_sort && is_num_y && !y.is_int() && !numerator(y).is_one()) { + // (^ t (/ p q)) --> (^ (^ t (/ 1 q)) p) + result = m_util.mk_power(m_util.mk_power(arg1, m_util.mk_numeral(rational(1)/denominator(y), false)), + m_util.mk_numeral(numerator(y), false)); + return BR_REWRITE2; + } + + if ((m_expand_power || (m_som && is_app(arg1) && to_app(arg1)->get_family_id() == get_fid())) && + is_num_y && y.is_unsigned() && 1 < y.get_unsigned() && y.get_unsigned() <= m_max_degree) { + ptr_buffer args; + unsigned k = y.get_unsigned(); + for (unsigned i = 0; i < k; i++) { + args.push_back(arg1); + } + result = m_util.mk_mul(args.size(), args.c_ptr()); + return BR_REWRITE1; + } + + if (!is_num_y) + return BR_FAILED; + + bool is_irrat_x = m_util.is_irrational_algebraic_numeral(arg1); + + if (!is_num_x && !is_irrat_x) + return BR_FAILED; + + rational num_y = numerator(y); + rational den_y = denominator(y); + bool is_neg_y = false; + if (num_y.is_neg()) { + num_y.neg(); + is_neg_y = true; + } + SASSERT(num_y.is_pos()); + SASSERT(den_y.is_pos()); + + if (is_neg_y && is_int_sort) + return BR_FAILED; + + if (!num_y.is_unsigned() || !den_y.is_unsigned()) + return BR_FAILED; + + unsigned u_num_y = num_y.get_unsigned(); + unsigned u_den_y = den_y.get_unsigned(); + + if (u_num_y > m_max_degree || u_den_y > m_max_degree) + return BR_FAILED; + + if (is_num_x) { + rational xk, r; + xk = power(x, u_num_y); + if (xk.root(u_den_y, r)) { + if (is_neg_y) + r = rational(1)/r; + result = m_util.mk_numeral(r, m().get_sort(arg1)); + return BR_DONE; + } + if (m_anum_simp) { + anum_manager & am = m_util.am(); + scoped_anum r(am); + am.set(r, xk.to_mpq()); + am.root(r, u_den_y, r); + if (is_neg_y) + am.inv(r); + result = m_util.mk_numeral(r, false); + return BR_DONE; + } + return BR_FAILED; + } + + SASSERT(is_irrat_x); + if (!m_anum_simp) + return BR_FAILED; + + anum const & val = m_util.to_irrational_algebraic_numeral(arg1); + anum_manager & am = m_util.am(); + if (am.degree(val) > m_max_degree) + return BR_FAILED; + scoped_anum r(am); + am.power(val, u_num_y, r); + am.root(r, u_den_y, r); + if (is_neg_y) + am.inv(r); + result = m_util.mk_numeral(r, false); + return BR_DONE; +} + +br_status arith_rewriter::mk_to_int_core(expr * arg, expr_ref & result) { + numeral a; + if (m_util.is_numeral(arg, a)) { + result = m_util.mk_numeral(floor(a), true); + return BR_DONE; + } + else if (m_util.is_to_real(arg)) { + result = to_app(arg)->get_arg(0); + return BR_DONE; + } + else { + return BR_FAILED; + } +} + +br_status arith_rewriter::mk_to_real_core(expr * arg, expr_ref & result) { + numeral a; + if (m_util.is_numeral(arg, a)) { + result = m_util.mk_numeral(a, false); + return BR_DONE; + } +#if 0 + // The following rewriting rule is not correct. + // It is used only for making experiments. + if (m_util.is_to_int(arg)) { + result = to_app(arg)->get_arg(0); + return BR_DONE; + } +#endif + // push to_real over OP_ADD, OP_MUL + if (m_push_to_real) { + if (m_util.is_add(arg) || m_util.is_mul(arg)) { + ptr_buffer new_args; + unsigned num = to_app(arg)->get_num_args(); + for (unsigned i = 0; i < num; i++) { + new_args.push_back(m_util.mk_to_real(to_app(arg)->get_arg(i))); + } + if (m_util.is_add(arg)) + result = m().mk_app(get_fid(), OP_ADD, new_args.size(), new_args.c_ptr()); + else + result = m().mk_app(get_fid(), OP_MUL, new_args.size(), new_args.c_ptr()); + return BR_REWRITE2; + } + } + return BR_FAILED; +} + +br_status arith_rewriter::mk_is_int(expr * arg, expr_ref & result) { + numeral a; + if (m_util.is_numeral(arg, a)) { + result = a.is_int() ? m().mk_true() : m().mk_false(); + return BR_DONE; + } + else if (m_util.is_to_real(arg)) { + result = m().mk_true(); + return BR_DONE; + } + else { + result = m().mk_eq(m().mk_app(get_fid(), OP_TO_REAL, + m().mk_app(get_fid(), OP_TO_INT, arg)), + arg); + return BR_REWRITE3; + } +} + +void arith_rewriter::set_cancel(bool f) { + m_util.set_cancel(f); +} + +// Return true if t is of the form c*Pi where c is a numeral. +// Store c into k +bool arith_rewriter::is_pi_multiple(expr * t, rational & k) { + if (m_util.is_pi(t)) { + k = rational(1); + return true; + } + expr * a, * b; + return m_util.is_mul(t, a, b) && m_util.is_pi(b) && m_util.is_numeral(a, k); +} + +// Return true if t is of the form (+ s c*Pi) where c is a numeral. +// Store c into k, and c*Pi into m. +bool arith_rewriter::is_pi_offset(expr * t, rational & k, expr * & m) { + if (m_util.is_add(t)) { + unsigned num = to_app(t)->get_num_args(); + for (unsigned i = 0; i < num; i++) { + expr * arg = to_app(t)->get_arg(i); + if (is_pi_multiple(arg, k)) { + m = arg; + return true; + } + } + } + return false; +} + +// Return true if t is of the form 2*pi*to_real(s). +bool arith_rewriter::is_2_pi_integer(expr * t) { + expr * a, * m, * b, * c; + rational k; + return + m_util.is_mul(t, a, m) && + m_util.is_numeral(a, k) && + k.is_int() && + mod(k, rational(2)).is_zero() && + m_util.is_mul(m, b, c) && + ((m_util.is_pi(b) && m_util.is_to_real(c)) || (m_util.is_to_real(b) && m_util.is_pi(c))); +} + +// Return true if t is of the form s + 2*pi*to_real(s). +// Store 2*pi*to_real(s) into m. +bool arith_rewriter::is_2_pi_integer_offset(expr * t, expr * & m) { + if (m_util.is_add(t)) { + unsigned num = to_app(t)->get_num_args(); + for (unsigned i = 0; i < num; i++) { + expr * arg = to_app(t)->get_arg(i); + if (is_2_pi_integer(arg)) { + m = arg; + return true; + } + } + } + return false; +} + +// Return true if t is of the form pi*to_real(s). +bool arith_rewriter::is_pi_integer(expr * t) { + expr * a, * b; + if (m_util.is_mul(t, a, b)) { + rational k; + if (m_util.is_numeral(a, k)) { + if (!k.is_int()) + return false; + expr * c, * d; + if (!m_util.is_mul(b, c, d)) + return false; + a = c; + b = d; + } + TRACE("tan", tout << "is_pi_integer " << mk_ismt2_pp(t, m()) << "\n"; + tout << "a: " << mk_ismt2_pp(a, m()) << "\n"; + tout << "b: " << mk_ismt2_pp(b, m()) << "\n";); + return + (m_util.is_pi(a) && m_util.is_to_real(b)) || + (m_util.is_to_real(a) && m_util.is_pi(b)); + } + return false; +} + +// Return true if t is of the form s + pi*to_real(s). +// Store 2*pi*to_real(s) into m. +bool arith_rewriter::is_pi_integer_offset(expr * t, expr * & m) { + if (m_util.is_add(t)) { + unsigned num = to_app(t)->get_num_args(); + for (unsigned i = 0; i < num; i++) { + expr * arg = to_app(t)->get_arg(i); + if (is_pi_integer(arg)) { + m = arg; + return true; + } + } + } + return false; +} + +app * arith_rewriter::mk_sqrt(rational const & k) { + return m_util.mk_power(m_util.mk_numeral(k, false), m_util.mk_numeral(rational(1, 2), false)); +} + +// Return a constant representing sin(k * pi). +// Return 0 if failed. +expr * arith_rewriter::mk_sin_value(rational const & k) { + rational k_prime = mod(floor(k), rational(2)) + k - floor(k); + TRACE("sine", tout << "k: " << k << ", k_prime: " << k_prime << "\n";); + SASSERT(k_prime >= rational(0) && k_prime < rational(2)); + bool neg = false; + if (k_prime >= rational(1)) { + neg = true; + k_prime = k_prime - rational(1); + } + SASSERT(k_prime >= rational(0) && k_prime < rational(1)); + if (k_prime.is_zero() || k_prime.is_one()) { + // sin(0) == sin(pi) == 0 + return m_util.mk_numeral(rational(0), false); + } + if (k_prime == rational(1, 2)) { + // sin(pi/2) == 1, sin(3/2 pi) == -1 + return m_util.mk_numeral(rational(neg ? -1 : 1), false); + } + if (k_prime == rational(1, 6) || k_prime == rational(5, 6)) { + // sin(pi/6) == sin(5/6 pi) == 1/2 + // sin(7 pi/6) == sin(11/6 pi) == -1/2 + return m_util.mk_numeral(rational(neg ? -1 : 1, 2), false); + } + if (k_prime == rational(1, 4) || k_prime == rational(3, 4)) { + // sin(pi/4) == sin(3/4 pi) == Sqrt(1/2) + // sin(5/4 pi) == sin(7/4 pi) == - Sqrt(1/2) + expr * result = mk_sqrt(rational(1, 2)); + return neg ? m_util.mk_uminus(result) : result; + } + if (k_prime == rational(1, 3) || k_prime == rational(2, 3)) { + // sin(pi/3) == sin(2/3 pi) == Sqrt(3)/2 + // sin(4/3 pi) == sin(5/3 pi) == - Sqrt(3)/2 + expr * result = m_util.mk_div(mk_sqrt(rational(3)), m_util.mk_numeral(rational(2), false)); + return neg ? m_util.mk_uminus(result) : result; + } + if (k_prime == rational(1, 12) || k_prime == rational(11, 12)) { + // sin(1/12 pi) == sin(11/12 pi) == [sqrt(6) - sqrt(2)]/4 + // sin(13/12 pi) == sin(23/12 pi) == -[sqrt(6) - sqrt(2)]/4 + expr * result = m_util.mk_div(m_util.mk_sub(mk_sqrt(rational(6)), mk_sqrt(rational(2))), m_util.mk_numeral(rational(4), false)); + return neg ? m_util.mk_uminus(result) : result; + } + if (k_prime == rational(5, 12) || k_prime == rational(7, 12)) { + // sin(5/12 pi) == sin(7/12 pi) == [sqrt(6) + sqrt(2)]/4 + // sin(17/12 pi) == sin(19/12 pi) == -[sqrt(6) + sqrt(2)]/4 + expr * result = m_util.mk_div(m_util.mk_add(mk_sqrt(rational(6)), mk_sqrt(rational(2))), m_util.mk_numeral(rational(4), false)); + return neg ? m_util.mk_uminus(result) : result; + } + return 0; +} + +br_status arith_rewriter::mk_sin_core(expr * arg, expr_ref & result) { + if (is_app_of(arg, get_fid(), OP_ASIN)) { + // sin(asin(x)) == x + result = to_app(arg)->get_arg(0); + return BR_DONE; + } + rational k; + if (is_numeral(arg, k) && k.is_zero()) { + // sin(0) == 0 + result = arg; + return BR_DONE; + } + + if (is_pi_multiple(arg, k)) { + result = mk_sin_value(k); + if (result.get() != 0) + return BR_REWRITE_FULL; + } + + expr * m; + if (is_pi_offset(arg, k, m)) { + rational k_prime = mod(floor(k), rational(2)) + k - floor(k); + SASSERT(k_prime >= rational(0) && k_prime < rational(2)); + if (k_prime.is_zero()) { + // sin(x + 2*n*pi) == sin(x) + result = m_util.mk_sin(m_util.mk_sub(arg, m)); + return BR_REWRITE2; + } + if (k_prime == rational(1, 2)) { + // sin(x + pi/2 + 2*n*pi) == cos(x) + result = m_util.mk_cos(m_util.mk_sub(arg, m)); + return BR_REWRITE2; + } + if (k_prime.is_one()) { + // sin(x + pi + 2*n*pi) == -sin(x) + result = m_util.mk_uminus(m_util.mk_sin(m_util.mk_sub(arg, m))); + return BR_REWRITE3; + } + if (k_prime == rational(3, 2)) { + // sin(x + 3/2*pi + 2*n*pi) == -cos(x) + result = m_util.mk_uminus(m_util.mk_cos(m_util.mk_sub(arg, m))); + return BR_REWRITE3; + } + } + + if (is_2_pi_integer_offset(arg, m)) { + // sin(x + 2*pi*to_real(a)) == sin(x) + result = m_util.mk_sin(m_util.mk_sub(arg, m)); + return BR_REWRITE2; + } + + return BR_FAILED; +} + +br_status arith_rewriter::mk_cos_core(expr * arg, expr_ref & result) { + if (is_app_of(arg, get_fid(), OP_ACOS)) { + // cos(acos(x)) == x + result = to_app(arg)->get_arg(0); + return BR_DONE; + } + + rational k; + if (is_numeral(arg, k) && k.is_zero()) { + // cos(0) == 1 + result = m_util.mk_numeral(rational(1), false); + return BR_DONE; + } + + if (is_pi_multiple(arg, k)) { + k = k + rational(1, 2); + result = mk_sin_value(k); + if (result.get() != 0) + return BR_REWRITE_FULL; + } + + expr * m; + if (is_pi_offset(arg, k, m)) { + rational k_prime = mod(floor(k), rational(2)) + k - floor(k); + SASSERT(k_prime >= rational(0) && k_prime < rational(2)); + if (k_prime.is_zero()) { + // cos(x + 2*n*pi) == cos(x) + result = m_util.mk_cos(m_util.mk_sub(arg, m)); + return BR_REWRITE2; + } + if (k_prime == rational(1, 2)) { + // cos(x + pi/2 + 2*n*pi) == -sin(x) + result = m_util.mk_uminus(m_util.mk_sin(m_util.mk_sub(arg, m))); + return BR_REWRITE3; + } + if (k_prime.is_one()) { + // cos(x + pi + 2*n*pi) == -cos(x) + result = m_util.mk_uminus(m_util.mk_cos(m_util.mk_sub(arg, m))); + return BR_REWRITE3; + } + if (k_prime == rational(3, 2)) { + // cos(x + 3/2*pi + 2*n*pi) == sin(x) + result = m_util.mk_sin(m_util.mk_sub(arg, m)); + return BR_REWRITE2; + } + } + + if (is_2_pi_integer_offset(arg, m)) { + // cos(x + 2*pi*to_real(a)) == cos(x) + result = m_util.mk_cos(m_util.mk_sub(arg, m)); + return BR_REWRITE2; + } + + return BR_FAILED; +} + +br_status arith_rewriter::mk_tan_core(expr * arg, expr_ref & result) { + if (is_app_of(arg, get_fid(), OP_ATAN)) { + // tan(atan(x)) == x + result = to_app(arg)->get_arg(0); + return BR_DONE; + } + + rational k; + if (is_numeral(arg, k) && k.is_zero()) { + // tan(0) == 0 + result = arg; + return BR_DONE; + } + + if (is_pi_multiple(arg, k)) { + expr_ref n(m()), d(m()); + n = mk_sin_value(k); + if (n.get() == 0) + goto end; + if (is_zero(n)) { + result = n; + return BR_DONE; + } + k = k + rational(1, 2); + d = mk_sin_value(k); + SASSERT(d.get() != 0); + if (is_zero(d)) { + goto end; + } + result = m_util.mk_div(n, d); + return BR_REWRITE_FULL; + } + + expr * m; + if (is_pi_offset(arg, k, m)) { + rational k_prime = k - floor(k); + SASSERT(k_prime >= rational(0) && k_prime < rational(1)); + if (k_prime.is_zero()) { + // tan(x + n*pi) == tan(x) + result = m_util.mk_tan(m_util.mk_sub(arg, m)); + return BR_REWRITE2; + } + } + + if (is_pi_integer_offset(arg, m)) { + // tan(x + pi*to_real(a)) == tan(x) + result = m_util.mk_tan(m_util.mk_sub(arg, m)); + return BR_REWRITE2; + } + + end: + if (m_expand_tan) { + result = m_util.mk_div(m_util.mk_sin(arg), m_util.mk_cos(arg)); + return BR_REWRITE2; + } + return BR_FAILED; +} + +br_status arith_rewriter::mk_asin_core(expr * arg, expr_ref & result) { + // Remark: we assume that ForAll x : asin(-x) == asin(x). + // Mathematica uses this as an axiom. Although asin is an underspecified function for x < -1 or x > 1. + // Actually, in Mathematica, asin(x) is a total function that returns a complex number fo x < -1 or x > 1. + rational k; + if (is_numeral(arg, k)) { + if (k.is_zero()) { + result = arg; + return BR_DONE; + } + if (k < rational(-1)) { + // asin(-2) == -asin(2) + // asin(-3) == -asin(3) + k.neg(); + result = m_util.mk_uminus(m_util.mk_asin(m_util.mk_numeral(k, false))); + return BR_REWRITE2; + } + + if (k > rational(1)) + return BR_FAILED; + + bool neg = false; + if (k.is_neg()) { + neg = true; + k.neg(); + } + + if (k.is_one()) { + // asin(1) == pi/2 + // asin(-1) == -pi/2 + result = m_util.mk_mul(m_util.mk_numeral(rational(neg ? -1 : 1, 2), false), m_util.mk_pi()); + return BR_REWRITE2; + } + + if (k == rational(1, 2)) { + // asin(1/2) == pi/6 + // asin(-1/2) == -pi/6 + result = m_util.mk_mul(m_util.mk_numeral(rational(neg ? -1 : 1, 6), false), m_util.mk_pi()); + return BR_REWRITE2; + } + } + + expr * t; + if (m_util.is_times_minus_one(arg, t)) { + // See comment above + // asin(-x) ==> -asin(x) + result = m_util.mk_uminus(m_util.mk_asin(t)); + return BR_REWRITE2; + } + + return BR_FAILED; +} + +br_status arith_rewriter::mk_acos_core(expr * arg, expr_ref & result) { + rational k; + if (is_numeral(arg, k)) { + if (k.is_zero()) { + // acos(0) = pi/2 + result = m_util.mk_mul(m_util.mk_numeral(rational(1, 2), false), m_util.mk_pi()); + return BR_REWRITE2; + } + if (k.is_one()) { + // acos(1) = 0 + result = m_util.mk_numeral(rational(0), false); + return BR_DONE; + } + if (k.is_minus_one()) { + // acos(-1) = pi + result = m_util.mk_pi(); + return BR_DONE; + } + if (k == rational(1, 2)) { + // acos(1/2) = pi/3 + result = m_util.mk_mul(m_util.mk_numeral(rational(1, 3), false), m_util.mk_pi()); + return BR_REWRITE2; + } + if (k == rational(-1, 2)) { + // acos(-1/2) = 2/3 pi + result = m_util.mk_mul(m_util.mk_numeral(rational(2, 3), false), m_util.mk_pi()); + return BR_REWRITE2; + } + } + return BR_FAILED; +} + +br_status arith_rewriter::mk_atan_core(expr * arg, expr_ref & result) { + rational k; + if (is_numeral(arg, k)) { + if (k.is_zero()) { + result = arg; + return BR_DONE; + } + + if (k.is_one()) { + // atan(1) == pi/4 + result = m_util.mk_mul(m_util.mk_numeral(rational(1, 4), false), m_util.mk_pi()); + return BR_REWRITE2; + } + + if (k.is_minus_one()) { + // atan(-1) == -pi/4 + result = m_util.mk_mul(m_util.mk_numeral(rational(-1, 4), false), m_util.mk_pi()); + return BR_REWRITE2; + } + + if (k < rational(-1)) { + // atan(-2) == -tan(2) + // atan(-3) == -tan(3) + k.neg(); + result = m_util.mk_uminus(m_util.mk_atan(m_util.mk_numeral(k, false))); + return BR_REWRITE2; + } + return BR_FAILED; + } + + expr * t; + if (m_util.is_times_minus_one(arg, t)) { + // atan(-x) ==> -atan(x) + result = m_util.mk_uminus(m_util.mk_atan(t)); + return BR_REWRITE2; + } + return BR_FAILED; +} + +br_status arith_rewriter::mk_sinh_core(expr * arg, expr_ref & result) { + if (is_app_of(arg, get_fid(), OP_ASINH)) { + // sinh(asinh(x)) == x + result = to_app(arg)->get_arg(0); + return BR_DONE; + } + expr * t; + if (m_util.is_times_minus_one(arg, t)) { + // sinh(-t) == -sinh(t) + result = m_util.mk_uminus(m_util.mk_sinh(t)); + return BR_REWRITE2; + } + return BR_FAILED; +} + +br_status arith_rewriter::mk_cosh_core(expr * arg, expr_ref & result) { + if (is_app_of(arg, get_fid(), OP_ACOSH)) { + // cosh(acosh(x)) == x + result = to_app(arg)->get_arg(0); + return BR_DONE; + } + expr * t; + if (m_util.is_times_minus_one(arg, t)) { + // cosh(-t) == cosh + result = m_util.mk_cosh(t); + return BR_DONE; + } + return BR_FAILED; +} + +br_status arith_rewriter::mk_tanh_core(expr * arg, expr_ref & result) { + if (is_app_of(arg, get_fid(), OP_ATANH)) { + // tanh(atanh(x)) == x + result = to_app(arg)->get_arg(0); + return BR_DONE; + } + expr * t; + if (m_util.is_times_minus_one(arg, t)) { + // tanh(-t) == -tanh(t) + result = m_util.mk_uminus(m_util.mk_tanh(t)); + return BR_REWRITE2; + } + return BR_FAILED; +} + +template class poly_rewriter; diff --git a/lib/arith_rewriter.h b/lib/arith_rewriter.h new file mode 100644 index 000000000..833cc2515 --- /dev/null +++ b/lib/arith_rewriter.h @@ -0,0 +1,183 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + arith_rewriter.h + +Abstract: + + Basic rewriting rules for arithmetic + +Author: + + Leonardo (leonardo) 2011-04-10 + +Notes: + +--*/ +#ifndef _ARITH_REWRITER_H_ +#define _ARITH_REWRITER_H_ + +#include"poly_rewriter.h" +#include"arith_decl_plugin.h" + +class arith_rewriter_core { +protected: + typedef rational numeral; + arith_util m_util; + bool m_expand_power; + bool m_mul2power; + bool m_expand_tan; + + ast_manager & m() const { return m_util.get_manager(); } + family_id get_fid() const { return m_util.get_family_id(); } + + bool is_numeral(expr * n) const { return m_util.is_numeral(n); } + bool is_numeral(expr * n, numeral & r) const { return m_util.is_numeral(n, r); } + bool is_zero(expr * n) const { return m_util.is_zero(n); } + bool is_minus_one(expr * n) const { return m_util.is_minus_one(n); } + void normalize(numeral & c, sort * s) {} + app * mk_numeral(numeral const & r, sort * s) { return m_util.mk_numeral(r, s); } + decl_kind add_decl_kind() const { return OP_ADD; } + decl_kind mul_decl_kind() const { return OP_MUL; } + bool use_power() const { return m_mul2power && !m_expand_power; } + decl_kind power_decl_kind() const { return OP_POWER; } +public: + arith_rewriter_core(ast_manager & m):m_util(m) {} +}; + +class arith_rewriter : public poly_rewriter { + bool m_arith_lhs; + bool m_gcd_rounding; + bool m_eq2ineq; + bool m_elim_to_real; + bool m_push_to_real; + bool m_anum_simp; + bool m_elim_rem; + unsigned m_max_degree; + + void get_coeffs_gcd(expr * t, numeral & g, bool & first, unsigned & num_consts); + enum const_treatment { CT_FLOOR, CT_CEIL, CT_FALSE }; + bool div_polynomial(expr * t, numeral const & g, const_treatment ct, expr_ref & result); + enum op_kind { LE, GE, EQ }; + static op_kind inv(op_kind k) { return k == LE ? GE : (k == GE ? LE : EQ); } + bool is_bound(expr * arg1, expr * arg2, op_kind kind, expr_ref & result); + br_status mk_le_ge_eq_core(expr * arg1, expr * arg2, op_kind kind, expr_ref & result); + + bool elim_to_real_var(expr * var, expr_ref & new_var); + bool elim_to_real_mon(expr * monomial, expr_ref & new_monomial); + bool elim_to_real_pol(expr * p, expr_ref & new_p); + bool elim_to_real(expr * arg1, expr * arg2, expr_ref & new_arg1, expr_ref & new_arg2); + + void updt_local_params(params_ref const & p); + + bool is_anum_simp_target(unsigned num_args, expr * const * args); + + br_status mk_div_irrat_rat(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_div_rat_irrat(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_div_irrat_irrat(expr * arg1, expr * arg2, expr_ref & result); + + bool is_reduce_power_target(expr * arg, bool is_eq); + expr * reduce_power(expr * arg, bool is_eq); + br_status reduce_power(expr * arg1, expr * arg2, op_kind kind, expr_ref & result); + + bool is_pi_multiple(expr * t, rational & k); + bool is_pi_offset(expr * t, rational & k, expr * & m); + bool is_2_pi_integer(expr * t); + bool is_2_pi_integer_offset(expr * t, expr * & m); + bool is_pi_integer(expr * t); + bool is_pi_integer_offset(expr * t, expr * & m); + expr * mk_sin_value(rational const & k); + app * mk_sqrt(rational const & k); + +public: + arith_rewriter(ast_manager & m, params_ref const & p = params_ref()): + poly_rewriter(m, p) { + updt_local_params(p); + } + + void updt_params(params_ref const & p); + + static void get_param_descrs(param_descrs & r); + + br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + void mk_app(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + if (mk_app_core(f, num_args, args, result) == BR_FAILED) + result = m().mk_app(f, num_args, args); + } + + br_status mk_eq_core(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_le_core(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_lt_core(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_ge_core(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_gt_core(expr * arg1, expr * arg2, expr_ref & result); + + br_status mk_add_core(unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_mul_core(unsigned num_args, expr * const * args, expr_ref & result); + + void mk_eq(expr * arg1, expr * arg2, expr_ref & result) { + if (mk_eq_core(arg1, arg2, result) == BR_FAILED) + result = m_util.mk_le(arg1, arg2); + } + void mk_le(expr * arg1, expr * arg2, expr_ref & result) { + if (mk_le_core(arg1, arg2, result) == BR_FAILED) + result = m_util.mk_le(arg1, arg2); + } + void mk_lt(expr * arg1, expr * arg2, expr_ref & result) { mk_lt_core(arg1, arg2, result); } + void mk_ge(expr * arg1, expr * arg2, expr_ref & result) { + if (mk_ge_core(arg1, arg2, result) == BR_FAILED) + result = m_util.mk_ge(arg1, arg2); + } + void mk_gt(expr * arg1, expr * arg2, expr_ref & result) { mk_gt_core(arg1, arg2, 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_mod_core(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_rem_core(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_power_core(expr* arg1, expr* arg2, expr_ref & result); + void mk_div(expr * arg1, expr * arg2, expr_ref & result) { + if (mk_div_core(arg1, arg2, result) == BR_FAILED) + result = m().mk_app(get_fid(), OP_DIV, arg1, arg2); + } + void mk_idiv(expr * arg1, expr * arg2, expr_ref & result) { + if (mk_idiv_core(arg1, arg2, result) == BR_FAILED) + result = m().mk_app(get_fid(), OP_IDIV, arg1, arg2); + } + void mk_mod(expr * arg1, expr * arg2, expr_ref & result) { + if (mk_mod_core(arg1, arg2, result) == BR_FAILED) + result = m().mk_app(get_fid(), OP_MOD, arg1, arg2); + } + void mk_rem(expr * arg1, expr * arg2, expr_ref & result) { + if (mk_rem_core(arg1, arg2, result) == BR_FAILED) + result = m().mk_app(get_fid(), OP_REM, arg1, arg2); + } + + br_status mk_to_int_core(expr * arg, expr_ref & result); + br_status mk_to_real_core(expr * arg, expr_ref & result); + void mk_to_int(expr * arg, expr_ref & result) { + if (mk_to_int_core(arg, result) == BR_FAILED) + result = m().mk_app(get_fid(), OP_TO_INT, 1, &arg); + } + void mk_to_real(expr * arg, expr_ref & result) { + if (mk_to_real_core(arg, result) == BR_FAILED) + result = m().mk_app(get_fid(), OP_TO_REAL, 1, &arg); + } + br_status mk_is_int(expr * arg, expr_ref & result); + + void set_cancel(bool f); + + br_status mk_sin_core(expr * arg, expr_ref & result); + br_status mk_cos_core(expr * arg, expr_ref & result); + br_status mk_tan_core(expr * arg, expr_ref & result); + + br_status mk_asin_core(expr * arg, expr_ref & result); + br_status mk_acos_core(expr * arg, expr_ref & result); + br_status mk_atan_core(expr * arg, expr_ref & result); + + br_status mk_sinh_core(expr * arg, expr_ref & result); + br_status mk_cosh_core(expr * arg, expr_ref & result); + br_status mk_tanh_core(expr * arg, expr_ref & result); +}; + +#endif diff --git a/lib/arith_simplifier_params.cpp b/lib/arith_simplifier_params.cpp new file mode 100644 index 000000000..0c2f5c710 --- /dev/null +++ b/lib/arith_simplifier_params.cpp @@ -0,0 +1,26 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + arith_simplifier_params.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-05-09. + +Revision History: + +--*/ + +#include"arith_simplifier_params.h" + +void arith_simplifier_params::register_params(ini_params & p) { + p.register_bool_param("ARITH_EXPAND_EQS", m_arith_expand_eqs); + p.register_bool_param("ARITH_PROCESS_ALL_EQS", m_arith_process_all_eqs); +} + diff --git a/lib/arith_simplifier_params.h b/lib/arith_simplifier_params.h new file mode 100644 index 000000000..3e44b5a6e --- /dev/null +++ b/lib/arith_simplifier_params.h @@ -0,0 +1,37 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + arith_simplifier_params.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-05-09. + +Revision History: + +--*/ +#ifndef _ARITH_SIMPLIFIER_PARAMS_H_ +#define _ARITH_SIMPLIFIER_PARAMS_H_ + +#include"ini_file.h" + +struct arith_simplifier_params { + bool m_arith_expand_eqs; + bool m_arith_process_all_eqs; + + arith_simplifier_params(): + m_arith_expand_eqs(false), + m_arith_process_all_eqs(false) { + } + + void register_params(ini_params & p); +}; + +#endif /* _ARITH_SIMPLIFIER_PARAMS_H_ */ + diff --git a/lib/arith_simplifier_plugin.cpp b/lib/arith_simplifier_plugin.cpp new file mode 100644 index 000000000..b9e93a973 --- /dev/null +++ b/lib/arith_simplifier_plugin.cpp @@ -0,0 +1,442 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + arith_simplifier_plugin.cpp + +Abstract: + + Simplifier for the arithmetic family. + +Author: + + Leonardo (leonardo) 2008-01-08 + +--*/ +#include"arith_simplifier_plugin.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"ast_smt2_pp.h" + +arith_simplifier_plugin::~arith_simplifier_plugin() { +} + +arith_simplifier_plugin::arith_simplifier_plugin(ast_manager & m, basic_simplifier_plugin & b, arith_simplifier_params & p): + poly_simplifier_plugin(symbol("arith"), m, OP_ADD, OP_MUL, OP_UMINUS, OP_SUB, OP_NUM), + m_params(p), + m_util(m), + m_bsimp(b), + m_int_zero(m), + m_real_zero(m) { + m_int_zero = m_util.mk_numeral(rational(0), true); + m_real_zero = m_util.mk_numeral(rational(0), false); +} + +/** + \brief Return true if the first monomial of t is negative. +*/ +bool arith_simplifier_plugin::is_neg_poly(expr * t) const { + if (m_util.is_add(t)) { + t = to_app(t)->get_arg(0); + } + if (m_util.is_mul(t)) { + t = to_app(t)->get_arg(0); + rational r; + bool is_int; + if (m_util.is_numeral(t, r, is_int)) + return r.is_neg(); + } + return false; +} + +void arith_simplifier_plugin::get_monomial_gcd(expr_ref_vector& monomials, numeral& g) { + g = numeral::zero(); + numeral n; + for (unsigned i = 0; !g.is_one() && i < monomials.size(); ++i) { + expr* e = monomials[i].get(); + if (is_numeral(e, n)) { + g = gcd(abs(n), g); + } + else if (is_mul(e) && is_numeral(to_app(e)->get_arg(0), n)) { + g = gcd(abs(n), g); + } + else { + g = numeral::one(); + return; + } + } + if (g.is_zero()) { + g = numeral::one(); + } +} + +void arith_simplifier_plugin::div_monomial(expr_ref_vector& monomials, numeral const& g) { + numeral n; + for (unsigned i = 0; i < monomials.size(); ++i) { + expr* e = monomials[i].get(); + if (is_numeral(e, n)) { + SASSERT((n/g).is_int()); + monomials[i] = mk_numeral(n/g); + } + else if (is_mul(e) && is_numeral(to_app(e)->get_arg(0), n)) { + SASSERT((n/g).is_int()); + monomials[i] = mk_mul(n/g, to_app(e)->get_arg(1)); + } + else { + UNREACHABLE(); + } + } +} + +void arith_simplifier_plugin::gcd_reduce_monomial(expr_ref_vector& monomials, numeral& k) { + numeral g, n; + + get_monomial_gcd(monomials, g); + g = gcd(abs(k), g); + + if (g.is_one()) { + return; + } + SASSERT(g.is_pos()); + + k = k / g; + div_monomial(monomials, g); + +} + +template +void arith_simplifier_plugin::mk_le_ge_eq_core(expr * arg1, expr * arg2, expr_ref & result) { + set_curr_sort(arg1); + bool is_int = m_curr_sort->get_decl_kind() == INT_SORT; + expr_ref_vector monomials(m_manager); + rational k; + TRACE("arith_eq_bug", tout << mk_ismt2_pp(arg1, m_manager) << "\n" << mk_ismt2_pp(arg2, m_manager) << "\n";); + process_sum_of_monomials(false, arg1, monomials, k); + process_sum_of_monomials(true, arg2, monomials, k); + k.neg(); + if (is_int) { + numeral g; + get_monomial_gcd(monomials, g); + if (!g.is_one()) { + div_monomial(monomials, g); + switch(Kind) { + case LE: + // + // g*monmials' <= k + // <=> + // monomials' <= floor(k/g) + // + k = floor(k/g); + break; + case GE: + // + // g*monmials' >= k + // <=> + // monomials' >= ceil(k/g) + // + k = ceil(k/g); + break; + case EQ: + k = k/g; + if (!k.is_int()) { + result = m_manager.mk_false(); + return; + } + break; + } + } + } + expr_ref lhs(m_manager); + mk_sum_of_monomials(monomials, lhs); + if (m_util.is_numeral(lhs)) { + SASSERT(lhs == mk_zero()); + if (( Kind == LE && numeral::zero() <= k) || + ( Kind == GE && numeral::zero() >= k) || + ( Kind == EQ && numeral::zero() == k)) + result = m_manager.mk_true(); + else + result = m_manager.mk_false(); + } + else { + + if (is_neg_poly(lhs)) { + expr_ref neg_lhs(m_manager); + mk_uminus(lhs, neg_lhs); + lhs = neg_lhs; + k.neg(); + expr * rhs = m_util.mk_numeral(k, is_int); + switch (Kind) { + case LE: + result = m_util.mk_ge(lhs, rhs); + break; + case GE: + result = m_util.mk_le(lhs, rhs); + break; + case EQ: + result = m_manager.mk_eq(lhs, rhs); + break; + } + } + else { + expr * rhs = m_util.mk_numeral(k, is_int); + switch (Kind) { + case LE: + result = m_util.mk_le(lhs, rhs); + break; + case GE: + result = m_util.mk_ge(lhs, rhs); + break; + case EQ: + result = m_manager.mk_eq(lhs, rhs); + break; + } + } + } +} + +void arith_simplifier_plugin::mk_arith_eq(expr * arg1, expr * arg2, expr_ref & result) { + mk_le_ge_eq_core(arg1, arg2, result); +} + +void arith_simplifier_plugin::mk_le(expr * arg1, expr * arg2, expr_ref & result) { + mk_le_ge_eq_core(arg1, arg2, result); +} + +void arith_simplifier_plugin::mk_ge(expr * arg1, expr * arg2, expr_ref & result) { + mk_le_ge_eq_core(arg1, arg2, result); +} + +void arith_simplifier_plugin::mk_lt(expr * arg1, expr * arg2, expr_ref & result) { + expr_ref tmp(m_manager); + mk_le(arg2, arg1, tmp); + m_bsimp.mk_not(tmp, result); +} + +void arith_simplifier_plugin::mk_gt(expr * arg1, expr * arg2, expr_ref & result) { + expr_ref tmp(m_manager); + mk_le(arg1, arg2, tmp); + m_bsimp.mk_not(tmp, result); +} + +void arith_simplifier_plugin::gcd_normalize(numeral & coeff, expr_ref& term) { + if (!abs(coeff).is_one()) { + set_curr_sort(term); + SASSERT(m_curr_sort->get_decl_kind() == INT_SORT); + expr_ref_vector monomials(m_manager); + rational k; + monomials.push_back(mk_numeral(numeral(coeff), true)); + process_sum_of_monomials(false, term, monomials, k); + gcd_reduce_monomial(monomials, k); + numeral coeff1; + if (!is_numeral(monomials[0].get(), coeff1)) { + UNREACHABLE(); + } + if (coeff1 == coeff) { + return; + } + monomials[0] = mk_numeral(k, true); + coeff = coeff1; + mk_sum_of_monomials(monomials, term); + } +} + + +void arith_simplifier_plugin::mk_div(expr * arg1, expr * arg2, expr_ref & result) { + set_curr_sort(arg1); + numeral v1, v2; + bool is_int; + if (m_util.is_numeral(arg2, v2, is_int) && !v2.is_zero()) { + SASSERT(!is_int); + if (m_util.is_numeral(arg1, v1, is_int)) + result = m_util.mk_numeral(v1/v2, false); + else { + numeral k(1); + k /= v2; + + expr_ref inv_arg2(m_util.mk_numeral(k, false), m_manager); + mk_mul(inv_arg2, arg1, result); + } + } + else + result = m_util.mk_div(arg1, arg2); +} + +void arith_simplifier_plugin::mk_idiv(expr * arg1, expr * arg2, expr_ref & result) { + set_curr_sort(arg1); + numeral v1, v2; + bool is_int; + if (m_util.is_numeral(arg1, v1, is_int) && m_util.is_numeral(arg2, v2, is_int) && !v2.is_zero()) + result = m_util.mk_numeral(div(v1, v2), is_int); + else + result = m_util.mk_idiv(arg1, arg2); +} + +void arith_simplifier_plugin::prop_mod_const(expr * e, unsigned depth, numeral const& k, expr_ref& result) { + SASSERT(m_util.is_int(e)); + SASSERT(k.is_int() && k.is_pos()); + numeral n; + bool is_int; + + if (depth == 0) { + result = e; + } + else if (is_add(e) || is_mul(e)) { + expr_ref_vector args(m_manager); + expr_ref tmp(m_manager); + app* a = to_app(e); + for (unsigned i = 0; i < a->get_num_args(); ++i) { + prop_mod_const(a->get_arg(i), depth - 1, k, tmp); + args.push_back(tmp); + } + reduce(a->get_decl(), args.size(), args.c_ptr(), result); + } + else if (m_util.is_numeral(e, n, is_int) && is_int) { + result = mk_numeral(mod(n, k), true); + } + else { + result = e; + } +} + +void arith_simplifier_plugin::mk_mod(expr * arg1, expr * arg2, expr_ref & result) { + set_curr_sort(arg1); + numeral v1, v2; + bool is_int; + if (m_util.is_numeral(arg1, v1, is_int) && m_util.is_numeral(arg2, v2, is_int) && !v2.is_zero()) { + result = m_util.mk_numeral(mod(v1, v2), is_int); + } + else if (m_util.is_numeral(arg2, v2, is_int) && is_int && v2.is_one()) { + result = m_util.mk_numeral(numeral(0), true); + } + else if (m_util.is_numeral(arg2, v2, is_int) && is_int && v2.is_pos()) { + expr_ref tmp(m_manager); + prop_mod_const(arg1, 5, v2, tmp); + result = m_util.mk_mod(tmp, arg2); + } + else { + result = m_util.mk_mod(arg1, arg2); + } +} + +void arith_simplifier_plugin::mk_rem(expr * arg1, expr * arg2, expr_ref & result) { + set_curr_sort(arg1); + numeral v1, v2; + bool is_int; + if (m_util.is_numeral(arg1, v1, is_int) && m_util.is_numeral(arg2, v2, is_int) && !v2.is_zero()) { + numeral m = mod(v1, v2); + // + // rem(v1,v2) = if v2 >= 0 then mod(v1,v2) else -mod(v1,v2) + // + if (v2.is_neg()) { + m.neg(); + } + result = m_util.mk_numeral(m, is_int); + } + else if (m_util.is_numeral(arg2, v2, is_int) && is_int && v2.is_one()) { + result = m_util.mk_numeral(numeral(0), true); + } + else if (m_util.is_numeral(arg2, v2, is_int) && is_int && !v2.is_zero()) { + expr_ref tmp(m_manager); + prop_mod_const(arg1, 5, v2, tmp); + result = m_util.mk_mod(tmp, arg2); + if (v2.is_neg()) { + result = m_util.mk_uminus(result); + } + } + else { + result = m_util.mk_rem(arg1, arg2); + } +} + +void arith_simplifier_plugin::mk_to_real(expr * arg, expr_ref & result) { + numeral v; + if (m_util.is_numeral(arg, v)) + result = m_util.mk_numeral(v, false); + else + result = m_util.mk_to_real(arg); +} + +void arith_simplifier_plugin::mk_to_int(expr * arg, expr_ref & result) { + numeral v; + if (m_util.is_numeral(arg, v)) + result = m_util.mk_numeral(floor(v), true); + else if (m_util.is_to_real(arg)) + result = to_app(arg)->get_arg(0); + else + result = m_util.mk_to_int(arg); +} + +void arith_simplifier_plugin::mk_is_int(expr * arg, expr_ref & result) { + numeral v; + if (m_util.is_numeral(arg, v)) + result = v.is_int()?m_manager.mk_true():m_manager.mk_false(); + else if (m_util.is_to_real(arg)) + result = m_manager.mk_true(); + else + result = m_util.mk_is_int(arg); +} + +bool arith_simplifier_plugin::reduce(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + set_reduce_invoked(); + SASSERT(f->get_family_id() == m_fid); + TRACE("arith_simplifier_plugin", tout << mk_pp(f, m_manager) << "\n"; + for (unsigned i = 0; i < num_args; i++) tout << mk_pp(args[i], m_manager) << "\n";); + arith_op_kind k = static_cast(f->get_decl_kind()); + switch (k) { + case OP_NUM: return false; + case OP_LE: if (m_presimp) return false; SASSERT(num_args == 2); mk_le(args[0], args[1], result); break; + case OP_GE: if (m_presimp) return false; SASSERT(num_args == 2); mk_ge(args[0], args[1], result); break; + case OP_LT: if (m_presimp) return false; SASSERT(num_args == 2); mk_lt(args[0], args[1], result); break; + case OP_GT: if (m_presimp) return false; SASSERT(num_args == 2); mk_gt(args[0], args[1], result); break; + case OP_ADD: mk_add(num_args, args, result); break; + case OP_SUB: mk_sub(num_args, args, result); break; + case OP_UMINUS: SASSERT(num_args == 1); mk_uminus(args[0], result); break; + case OP_MUL: + mk_mul(num_args, args, result); + TRACE("arith_simplifier_plugin", tout << mk_pp(result, m_manager) << "\n";); + break; + case OP_DIV: SASSERT(num_args == 2); mk_div(args[0], args[1], result); break; + case OP_IDIV: SASSERT(num_args == 2); mk_idiv(args[0], args[1], result); break; + case OP_REM: SASSERT(num_args == 2); mk_rem(args[0], args[1], result); break; + case OP_MOD: SASSERT(num_args == 2); mk_mod(args[0], args[1], result); break; + case OP_TO_REAL: SASSERT(num_args == 1); mk_to_real(args[0], result); break; + case OP_TO_INT: SASSERT(num_args == 1); mk_to_int(args[0], result); break; + case OP_IS_INT: SASSERT(num_args == 1); mk_is_int(args[0], result); break; + case OP_POWER: return false; + case OP_IRRATIONAL_ALGEBRAIC_NUM: return false; + default: + UNREACHABLE(); + return false; + } + TRACE("arith_simplifier_plugin", tout << mk_pp(result.get(), m_manager) << "\n";); + return true; +} + +bool arith_simplifier_plugin::is_arith_term(expr * n) const { + return n->get_kind() == AST_APP && to_app(n)->get_family_id() == m_fid; +} + +bool arith_simplifier_plugin::reduce_eq(expr * lhs, expr * rhs, expr_ref & result) { + TRACE("reduce_eq_bug", tout << mk_ismt2_pp(lhs, m_manager) << "\n" << mk_ismt2_pp(rhs, m_manager) << "\n";); + set_reduce_invoked(); + if (m_presimp) { + return false; + } + if (m_params.m_arith_expand_eqs) { + expr_ref le(m_manager), ge(m_manager); + mk_le_ge_eq_core(lhs, rhs, le); + mk_le_ge_eq_core(lhs, rhs, ge); + m_bsimp.mk_and(le, ge, result); + return true; + } + + if (m_params.m_arith_process_all_eqs || is_arith_term(lhs) || is_arith_term(rhs)) { + mk_arith_eq(lhs, rhs, result); + return true; + } + return false; +} + + + diff --git a/lib/arith_simplifier_plugin.h b/lib/arith_simplifier_plugin.h new file mode 100644 index 000000000..4402fa8a1 --- /dev/null +++ b/lib/arith_simplifier_plugin.h @@ -0,0 +1,95 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + arith_simplifier_plugin.h + +Abstract: + + Simplifier for the arithmetic family. + +Author: + + Leonardo (leonardo) 2008-01-08 + +--*/ +#ifndef _ARITH_SIMPLIFIER_PLUGIN_H_ +#define _ARITH_SIMPLIFIER_PLUGIN_H_ + +#include"basic_simplifier_plugin.h" +#include"poly_simplifier_plugin.h" +#include"arith_decl_plugin.h" +#include"arith_simplifier_params.h" + +/** + \brief Simplifier for the arith family. +*/ +class arith_simplifier_plugin : public poly_simplifier_plugin { +public: + enum op_kind { + LE, GE, EQ + }; +protected: + arith_simplifier_params & m_params; + arith_util m_util; + basic_simplifier_plugin & m_bsimp; + expr_ref m_int_zero; + expr_ref m_real_zero; + + bool is_neg_poly(expr * t) const; + + template + void mk_le_ge_eq_core(expr * arg1, expr * arg2, expr_ref & result); + + void prop_mod_const(expr * e, unsigned depth, numeral const& k, expr_ref& result); + + void gcd_reduce_monomial(expr_ref_vector& monomials, numeral& k); + + void div_monomial(expr_ref_vector& monomials, numeral const& g); + void get_monomial_gcd(expr_ref_vector& monomials, numeral& g); + +public: + arith_simplifier_plugin(ast_manager & m, basic_simplifier_plugin & b, arith_simplifier_params & p); + ~arith_simplifier_plugin(); + arith_util & get_arith_util() { return m_util; } + virtual numeral norm(const numeral & n) { return n; } + virtual bool is_numeral(expr * n, rational & val) const { bool f; return m_util.is_numeral(n, val, f); } + bool is_numeral(expr * n) const { return m_util.is_numeral(n); } + virtual bool is_minus_one(expr * n) const { numeral tmp; return is_numeral(n, tmp) && tmp.is_minus_one(); } + virtual expr * get_zero(sort * s) const { return m_util.is_int(s) ? m_int_zero.get() : m_real_zero.get(); } + + virtual app * mk_numeral(numeral const & n) { return m_util.mk_numeral(n, m_curr_sort->get_decl_kind() == INT_SORT); } + app * mk_numeral(numeral const & n, bool is_int) { return m_util.mk_numeral(n, is_int); } + bool is_int_sort(sort const * s) const { return m_util.is_int(s); } + bool is_real_sort(sort const * s) const { return m_util.is_real(s); } + bool is_arith_sort(sort const * s) const { return is_int_sort(s) || is_real_sort(s); } + bool is_int(expr const * n) const { return m_util.is_int(n); } + bool is_le(expr const * n) const { return m_util.is_le(n); } + bool is_ge(expr const * n) const { return m_util.is_ge(n); } + + virtual bool is_le_ge(expr * n) const { return is_le(n) || is_ge(n); } + + void mk_le(expr * arg1, expr * arg2, expr_ref & result); + void mk_ge(expr * arg1, expr * arg2, expr_ref & result); + void mk_lt(expr * arg1, expr * arg2, expr_ref & result); + void mk_gt(expr * arg1, expr * arg2, expr_ref & result); + void mk_arith_eq(expr * arg1, expr * arg2, expr_ref & result); + void mk_div(expr * arg1, expr * arg2, expr_ref & result); + void mk_idiv(expr * arg1, expr * arg2, expr_ref & result); + void mk_mod(expr * arg1, expr * arg2, expr_ref & result); + void mk_rem(expr * arg1, expr * arg2, expr_ref & result); + void mk_to_real(expr * arg, expr_ref & result); + void mk_to_int(expr * arg, expr_ref & result); + void mk_is_int(expr * arg, expr_ref & result); + + virtual bool reduce(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + virtual bool reduce_eq(expr * lhs, expr * rhs, expr_ref & result); + + bool is_arith_term(expr * n) const; + + void gcd_normalize(numeral & coeff, expr_ref& term); + +}; + +#endif /* _ARITH_SIMPLIFIER_PLUGIN_H_ */ diff --git a/lib/arith_solver_plugin.cpp b/lib/arith_solver_plugin.cpp new file mode 100644 index 000000000..d39185b4b --- /dev/null +++ b/lib/arith_solver_plugin.cpp @@ -0,0 +1,104 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + arith_solver_plugin.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-30. + +Revision History: + +--*/ +#include"arith_solver_plugin.h" +#include"ast_pp.h" + +arith_solver_plugin::arith_solver_plugin(arith_simplifier_plugin & simp): + solver_plugin(symbol("arith"), simp.get_manager()), + m_simplifier(simp) { +} + +bool arith_solver_plugin::solve(expr * lhs, expr * rhs, expr_mark const & forbidden, app_ref & var, expr_ref & subst) { + rational k; + if (!m_simplifier.is_numeral(rhs, k)) + return false; + bool _is_int = m_simplifier.is_int(lhs); + ptr_buffer monomials; + ptr_buffer todo; + bool already_found = false; + rational c; + todo.push_back(lhs); + while (!todo.empty()) { + expr * curr = todo.back(); + todo.pop_back(); + rational coeff; + if (m_simplifier.is_add(curr)) { + SASSERT(to_app(curr)->get_num_args() == 2); + todo.push_back(to_app(curr)->get_arg(1)); + todo.push_back(to_app(curr)->get_arg(0)); + } + else { + if (!already_found) { + if (m_simplifier.is_mul(curr) && + m_simplifier.is_numeral(to_app(curr)->get_arg(0), coeff) && !coeff.is_zero() && (!_is_int || coeff.is_minus_one()) && + is_uninterp_const(to_app(curr)->get_arg(1)) && + !forbidden.is_marked(to_app(curr)->get_arg(1))) { + c = coeff; + var = to_app(to_app(curr)->get_arg(1)); + already_found = true; + } + else if (is_uninterp_const(curr) && !forbidden.is_marked(curr)) { + c = rational::one(); + var = to_app(curr); + already_found = true; + } + else { + monomials.push_back(curr); + } + } + else { + monomials.push_back(curr); + } + } + } + if (!already_found) + return false; + SASSERT(!c.is_zero()); + k /= c; + expr_ref_vector new_monomials(m_manager); + if (!k.is_zero()) + new_monomials.push_back(m_simplifier.mk_numeral(k, _is_int)); + c.neg(); + expr_ref inv_c(m_manager); + if (!c.is_one()) { + rational inv(1); + inv /= c; + inv_c = m_simplifier.mk_numeral(inv, _is_int); + } + // divide monomials by c + unsigned sz = monomials.size(); + for (unsigned i = 0; i < sz; i++) { + expr * m = monomials[i]; + expr_ref new_m(m_manager); + if (!c.is_one()) + m_simplifier.mk_mul(inv_c, m, new_m); + else + new_m = m; + new_monomials.push_back(new_m); + } + if (new_monomials.empty()) + subst = m_simplifier.mk_numeral(rational(0), _is_int); + else + m_simplifier.mk_add(new_monomials.size(), new_monomials.c_ptr(), subst); + TRACE("arith_solver", tout << "solving:\n" << mk_pp(lhs, m_manager) << "\n" << mk_pp(rhs, m_manager) + << "\nresult:\n" << mk_pp(var, m_manager) << "\n" << mk_pp(subst, m_manager) << "\n";); + return true; +} + + diff --git a/lib/arith_solver_plugin.h b/lib/arith_solver_plugin.h new file mode 100644 index 000000000..bf64380b7 --- /dev/null +++ b/lib/arith_solver_plugin.h @@ -0,0 +1,34 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + arith_solver_plugin.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-30. + +Revision History: + +--*/ +#ifndef _ARITH_SOLVER_PLUGIN_H_ +#define _ARITH_SOLVER_PLUGIN_H_ + +#include"solver_plugin.h" +#include"arith_simplifier_plugin.h" + +class arith_solver_plugin : public solver_plugin { + arith_simplifier_plugin & m_simplifier; +public: + arith_solver_plugin(arith_simplifier_plugin & simp); + virtual ~arith_solver_plugin() {} + virtual bool solve(expr * lhs, expr * rhs, expr_mark const & forbidden, app_ref & var, expr_ref & subst); +}; + +#endif /* _ARITH_SOLVER_PLUGIN_H_ */ + diff --git a/lib/array.h b/lib/array.h new file mode 100644 index 000000000..9dfd2fa94 --- /dev/null +++ b/lib/array.h @@ -0,0 +1,205 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + array.h + +Abstract: + + Fixed size arrays + +Author: + + Leonardo de Moura (leonardo) 2011-01-26. + +Revision History: + +--*/ +#ifndef __ARRAY_H_ +#define __ARRAY_H_ + +template +class array { +public: + // Return the space needed to store an array of size sz. + static size_t space(size_t sz) { return sizeof(T)*sz + sizeof(size_t); } + +private: +#define ARRAY_SIZE_IDX -1 + T * m_data; + void destroy_elements() { + iterator it = begin(); + iterator e = end(); + for (; it != e; ++it) { + it->~T(); + } + } + + char * raw_ptr() const { return reinterpret_cast(reinterpret_cast(m_data) - 1); } + + array & operator=(array const & source); + + void set_data(void * mem, size_t sz) { + size_t * _mem = static_cast(mem); + *_mem = sz; + _mem ++; + m_data = reinterpret_cast(_mem); + } + + template + void allocate(Allocator & a, size_t sz) { + size_t * mem = reinterpret_cast(a.allocate(space(sz))); + set_data(mem, sz); + } + + void init() { + iterator it = begin(); + iterator e = end(); + for (; it != e; ++it) { + new (it) T(); + } + } + + void init(T const * vs) { + iterator it = begin(); + iterator e = end(); + for (; it != e; ++it, ++vs) { + new (it) T(*vs); + } + } + +public: + typedef T data; + typedef T * iterator; + typedef const T * const_iterator; + + array():m_data(0) {} + + /** + \brief Store the array in the given chunk of memory (mem). + This chunck should be big enough to store space(sz) bytes. + */ + array(void * mem, size_t sz, T const * vs) { + DEBUG_CODE(m_data = 0;); + set(mem, sz, vs); + } + + // WARNING: the memory allocated will not be automatically freed. + array(void * mem, size_t sz, bool init_mem) { + DEBUG_CODE(m_data = 0;); + set_data(mem, sz); + if (init_mem) + init(); + } + + // WARNING: the memory allocated will not be automatically freed. + template + array(Allocator & a, size_t sz, T const * vs) { + DEBUG_CODE(m_data = 0;); + set(a, sz, vs); + } + + // WARNING: the memory allocated will not be automatically freed. + template + array(Allocator & a, size_t sz, bool init_mem) { + DEBUG_CODE(m_data = 0;); + allocate(a, sz); + if (init_mem) + init(); + } + + // WARNING: this does not free the memory used to store the array. + // You must free it yourself, or use finalize. + ~array() { + if (m_data && CallDestructors) + destroy_elements(); + } + + // Free the memory used to store the array. + template + void finalize(Allocator & a) { + if (m_data) { + if (CallDestructors) + destroy_elements(); + a.deallocate(size(), raw_ptr); + m_data = 0; + } + } + + void set(void * mem, size_t sz, T const * vs) { + SASSERT(m_data == 0); + set_data(mem, sz); + init(vs); + } + + template + void set(Allocator & a, size_t sz, T const * vs) { + SASSERT(m_data == 0); + allocate(a, sz); + init(sz, vs); + } + + size_t size() const { + if (m_data == 0) { + return 0; + } + return reinterpret_cast(m_data)[SIZE_IDX]; + } + + bool empty() const { return m_data == 0; } + + T & operator[](size_t idx) { + SASSERT(idx < size()); + return m_data[idx]; + } + + T const & operator[](size_t idx) const { + SASSERT(idx < size()); + return m_data[idx]; + } + + iterator begin() { + return m_data; + } + + iterator end() { + return m_data + size(); + } + + const_iterator begin() const { + return m_data; + } + + const_iterator end() const { + return m_data + size(); + } + + T * c_ptr() { return m_data; } +}; + +template +class ptr_array : public array { +public: + ptr_array() {} + ptr_array(void * mem, size_t sz, T * const * vs):array(mem, sz, vs) {} + template + ptr_array(Allocator & a, size_t sz, T * const * vs):array(a, sz, vs) {} + ptr_array(void * mem, size_t sz, bool init_mem):array(mem, sz, init_mem) {} + template + ptr_array(Allocator & a, size_t sz, bool init_mem):array(a, sz, init_mem) {} +}; + +template +class sarray : public array { +public: + sarray() {} + sarray(void * mem, size_t sz, T const * vs):array(mem, sz, vs) {} + template + sarray(Allocator & a, size_t sz, T const * vs):array(a, sz, vs) {} + sarray(void * mem, size_t sz, bool init_mem):array(mem, sz, init_mem) {} + template + sarray(Allocator & a, size_t sz, bool init_mem):array(a, sz, init_mem) {} +}; + +#endif diff --git a/lib/array_decl_plugin.cpp b/lib/array_decl_plugin.cpp new file mode 100644 index 000000000..f2e12d46a --- /dev/null +++ b/lib/array_decl_plugin.cpp @@ -0,0 +1,564 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + array_decl_plugin.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-09 + +Revision History: + +--*/ +#include +#include"array_decl_plugin.h" +#include"warning.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" + +array_decl_plugin::array_decl_plugin(): + m_store_sym("store"), + m_select_sym("select"), + m_const_sym("const"), + m_default_sym("default"), + m_map_sym("map"), + m_set_union_sym("union"), + m_set_intersect_sym("intersect"), + m_set_difference_sym("difference"), + m_set_complement_sym("complement"), + m_set_subset_sym("subset"), + m_array_ext_sym("array-ext"), + m_as_array_sym("as-array") { +} + +#define ARRAY_SORT_STR "Array" + +sort * array_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) { + SASSERT(k == ARRAY_SORT); + if (num_parameters < 2) { + m_manager->raise_exception("invalid array sort definition, invalid number of parameters"); + return 0; + } + + for (unsigned i = 0; i < num_parameters; i++) { + if (!parameters[i].is_ast() || !is_sort(parameters[i].get_ast())) { + m_manager->raise_exception("invalid array sort definition, parameter is not a sort"); + return 0; + } + } + sort * range = to_sort(parameters[num_parameters - 1].get_ast()); + TRACE("array_decl_plugin_bug", tout << mk_pp(range, *m_manager) << "\n";); + if (!range->is_infinite() && !range->is_very_big() && (1 == range->get_num_elements().size())) { + return m_manager->mk_sort(symbol(ARRAY_SORT_STR), sort_info(m_family_id, ARRAY_SORT, 1, + num_parameters, parameters)); + } + bool is_infinite = false; + bool is_very_big = false; + for (unsigned i = 0; i < num_parameters; i++) { + sort * s = to_sort(parameters[i].get_ast()); + if (s->is_infinite()) { + is_infinite = true; + } + if (s->is_very_big()) { + is_very_big = true; + } + } + if (is_infinite) { + return m_manager->mk_sort(symbol(ARRAY_SORT_STR), sort_info(m_family_id, ARRAY_SORT, num_parameters, parameters)); + } + else if (is_very_big) { + return m_manager->mk_sort(symbol(ARRAY_SORT_STR), sort_info(m_family_id, ARRAY_SORT, sort_size::mk_very_big(), + num_parameters, parameters)); + } + else { + rational domain_sz(1); + rational num_elements; + for (unsigned i = 0; i < num_parameters - 1; i++) { + domain_sz *= rational(to_sort(parameters[i].get_ast())->get_num_elements().size(),rational::ui64()); + } + if (domain_sz <= rational(128)) { + num_elements = rational(range->get_num_elements().size(),rational::ui64()); + num_elements = power(num_elements, static_cast(domain_sz.get_int64())); + } + + if (domain_sz > rational(128) || !num_elements.is_uint64()) { + // too many elements... + return m_manager->mk_sort(symbol(ARRAY_SORT_STR), + sort_info(m_family_id, + ARRAY_SORT, + sort_size::mk_very_big(), + num_parameters, + parameters)); + } + else { + return m_manager->mk_sort(symbol(ARRAY_SORT_STR), sort_info(m_family_id, ARRAY_SORT, num_elements.get_uint64(), + num_parameters, parameters)); + } + } +} + +bool array_decl_plugin::is_array_sort(sort* s) const { + return m_family_id == s->get_family_id() && s->get_decl_kind() == ARRAY_SORT; +} + + +func_decl * array_decl_plugin::mk_const(sort * s, unsigned arity, sort * const * domain) { + if (arity != 1) { + m_manager->raise_exception("invalid const array definition, invalid domain size"); + return 0; + } + unsigned num_parameters = s->get_num_parameters(); + + if (num_parameters == 0) { + m_manager->raise_exception("parameter mismatch"); + return 0; + } + + // TBD check that range sort corresponds to last parameter. + + parameter param(s); + func_decl_info info(m_family_id, OP_CONST_ARRAY, 1, ¶m); + info.m_private_parameters = true; + return m_manager->mk_func_decl(m_const_sym, arity, domain, s, info); +} + +func_decl * array_decl_plugin::mk_map(func_decl* f, unsigned arity, sort* const* domain) { + if (arity != f->get_arity()) { + std::ostringstream buffer; + buffer << "map expects to take as many arguments as the function being mapped, " + << "it was given " << arity << " but expects " << f->get_arity(); + m_manager->raise_exception(buffer.str().c_str()); + return 0; + } + if (arity == 0) { + m_manager->raise_exception("don't use map on constants"); + return 0; + } + // + // check that each domain[i] is an array sort + // with the same domains and same ranges. + // and that the ranges coincide with the domain of f. + // + unsigned dom_arity = get_array_arity(domain[0]); + for (unsigned i = 0; i < arity; ++i) { + if (!is_array_sort(domain[i])) { + std::ostringstream buffer; + buffer << "map expects an array sort as argument at position " << i; + m_manager->raise_exception(buffer.str().c_str()); + return 0; + } + if (get_array_arity(domain[i]) != dom_arity) { + std::ostringstream buffer; + buffer << "map expects all arguments to have the same array domain, " + << "this is not the case for argument " << i; + m_manager->raise_exception(buffer.str().c_str()); + return 0; + } + for (unsigned j = 0; j < dom_arity; ++j) { + if (get_array_domain(domain[i],j) != get_array_domain(domain[0],j)) { + std::ostringstream buffer; + buffer << "map expects all arguments to have the same array domain, " + << "this is not the case for argument " << i; + m_manager->raise_exception(buffer.str().c_str()); + return 0; + } + } + if (get_array_range(domain[i]) != f->get_domain(i)) { + std::ostringstream buffer; + buffer << "map expects the argument at position " << i + << " to have the array range the same as the function"; + m_manager->raise_exception(buffer.str().c_str()); + return 0; + } + } + vector parameters; + for (unsigned i = 0; i < dom_arity; ++i) { + parameters.push_back(domain[0]->get_parameter(i)); + } + parameters.push_back(parameter(f->get_range())); + sort* range = mk_sort(ARRAY_SORT, parameters.size(), parameters.c_ptr()); + parameter param(f); + func_decl_info info(m_family_id, OP_ARRAY_MAP, 1, ¶m); + // + // left_associative, right_associative, commutative are inherited. + // m_injective is inherited, since: + // forall x . g(f(x)) = x implies forall X . map(g)(map(f)(X)) = X + // since map(g)(map(f)(X))[i] = g(f(X[i])) = X[i] + // + + info.set_right_associative(f->is_right_associative()); + info.set_left_associative(f->is_left_associative()); + info.set_commutative(f->is_commutative()); + info.set_injective(f->is_injective()); + return m_manager->mk_func_decl(m_map_sym, arity, domain, range, info); +} + + +func_decl * array_decl_plugin::mk_default(unsigned domain_size, sort * const * domain) { + if (domain_size != 1) { + m_manager->raise_exception("invalid default array definition, invalid domain size"); + return 0; + } + // check that domain[0] is an array sort. + unsigned num_parameters = domain[0]->get_num_parameters(); + + if (num_parameters <= 1) { + m_manager->raise_exception("parameter mismatch. There should be more than one parameter to defaults"); + return 0; + } + parameter param(domain[0]->get_parameter(num_parameters-1)); + if (!param.is_ast() || !is_sort(param.get_ast())) { + m_manager->raise_exception("last parameter should be a sort"); + return 0; + } + sort * s = to_sort(param.get_ast()); + + return m_manager->mk_func_decl(m_default_sym, domain_size, domain, s, + func_decl_info(m_family_id, OP_ARRAY_DEFAULT)); +} + + +func_decl* array_decl_plugin::mk_select(unsigned arity, sort * const * domain) { + if (arity <= 1) { + m_manager->raise_exception("select takes at least two arguments"); + return 0; + } + sort * s = domain[0]; + unsigned num_parameters = s->get_num_parameters(); + parameter const* parameters = s->get_parameters(); + + if (num_parameters != arity) { + m_manager->raise_exception("select requires as many arguments as the size of the domain"); + return 0; + } + if (domain[0] != s) { + m_manager->raise_exception("first argument of select needs to be an array"); + return 0; + } + for (unsigned i = 0; i + 1 < num_parameters; ++i) { + if (!parameters[i].is_ast() || domain[i+1] != parameters[i].get_ast()) { + m_manager->raise_exception("domain sort and parameter do not match"); + UNREACHABLE(); + return 0; + } + } + return m_manager->mk_func_decl(m_select_sym, arity, domain, get_array_range(domain[0]), + func_decl_info(m_family_id, OP_SELECT)); +} + +func_decl * array_decl_plugin::mk_store(unsigned arity, sort * const * domain) { + if (arity < 3) { + m_manager->raise_exception("store takes at least 3 arguments"); + return 0; + } + sort * s = domain[0]; + unsigned num_parameters = s->get_num_parameters(); + parameter const * parameters = s->get_parameters(); + if (!is_array_sort(s)) { + m_manager->raise_exception("store expects the first argument sort to be an array"); + UNREACHABLE(); + return 0; + } + if (arity != num_parameters+1) { + std::ostringstream buffer; + buffer << "store expects the first argument to be an array taking " << num_parameters+1 + << ", instead it was passed " << (arity - 1) << "arguments"; + m_manager->raise_exception(buffer.str().c_str()); + UNREACHABLE(); + return 0; + } + for (unsigned i = 0; i < num_parameters; ++i) { + if (!parameters[i].is_ast()) { + m_manager->raise_exception("expecting sort parameter"); + return 0; + } + if (parameters[i].get_ast() != domain[i+1]) { + m_manager->raise_exception("domain sort and parameter do not match"); + UNREACHABLE(); + return 0; + } + } + return m_manager->mk_func_decl(m_store_sym, arity, domain, domain[0], + func_decl_info(m_family_id, OP_STORE)); +} + +func_decl * array_decl_plugin::mk_array_ext_skolem(unsigned arity, sort * const * domain, unsigned i) { + if (arity != 2 || domain[0] != domain[1]) { + UNREACHABLE(); + return 0; + } + sort * s = domain[0]; + unsigned num_parameters = s->get_num_parameters(); + if (num_parameters == 0 || i >= num_parameters - 1) { + UNREACHABLE(); + return 0; + } + sort * r = to_sort(s->get_parameter(i).get_ast()); + parameter param(s); + return m_manager->mk_func_decl(m_array_ext_sym, arity, domain, r, func_decl_info(m_family_id, OP_ARRAY_EXT_SKOLEM, 1, ¶m)); +} + + +bool array_decl_plugin::check_set_arguments(unsigned arity, sort * const * domain) { + for (unsigned i = 0; i < arity; ++i) { + if (domain[i] != domain[0]) { + std::ostringstream buffer; + buffer << "arguments " << 1 << " and " << (i+1) << " have different sorts"; + m_manager->raise_exception(buffer.str().c_str()); + return false; + } + if (domain[i]->get_family_id() != m_family_id) { + std::ostringstream buffer; + buffer << "argument " << (i+1) << " is not of array sort"; + m_manager->raise_exception(buffer.str().c_str()); + return false; + } + } + if (arity > 0) { + unsigned num_params = domain[0]->get_num_parameters(); + parameter const* params = domain[0]->get_parameters(); + if (1 >= num_params) { + m_manager->raise_exception("expecting 2 or more parameters"); + UNREACHABLE(); + return false; + } + if (!params[num_params-1].is_ast()) { + m_manager->raise_exception("expecting term parameters"); + UNREACHABLE(); + return false; + } + if (!is_sort(params[num_params-1].get_ast()) || !m_manager->is_bool(to_sort(params[num_params-1].get_ast()))) { + m_manager->raise_exception("expecting boolean range"); + UNREACHABLE(); + return false; + } + } + return true; +} + +func_decl * array_decl_plugin::mk_set_union(unsigned arity, sort * const * domain) { + + if (arity == 0) { + m_manager->raise_exception("union takes at least one argument"); + return 0; + } + sort * s = domain[0]; + if (!check_set_arguments(arity, domain)) { + return 0; + } + parameter param(s); + func_decl_info info(m_family_id, OP_SET_UNION, 1, ¶m); + info.set_associative(); + info.set_commutative(); + info.set_idempotent(); + sort* domain2[2] = { domain[0], domain[0] }; + return m_manager->mk_func_decl(m_set_union_sym, 2, domain2, domain[0], info); +} + +func_decl * array_decl_plugin::mk_set_intersect(unsigned arity, sort * const * domain) { + + if (arity == 0) { + m_manager->raise_exception("intersection takes at least one argument"); + return 0; + } + if (!check_set_arguments(arity, domain)) { + return 0; + } + func_decl_info info(m_family_id, OP_SET_INTERSECT); + info.set_associative(); + info.set_commutative(); + info.set_idempotent(); + sort* domain2[2] = { domain[0], domain[0] }; + return m_manager->mk_func_decl(m_set_intersect_sym, 2, domain2, domain[0], info); +} + +func_decl * array_decl_plugin::mk_set_difference(unsigned arity, sort * const * domain) { + if (arity != 2) { + m_manager->raise_exception("set difference takes precisely two arguments"); + return 0; + } + if (!check_set_arguments(arity, domain)) { + return 0; + } + return m_manager->mk_func_decl(m_set_difference_sym, arity, domain, domain[0], + func_decl_info(m_family_id, OP_SET_DIFFERENCE)); +} + +func_decl * array_decl_plugin::mk_set_complement(unsigned arity, sort * const * domain) { + if (arity != 1) { + m_manager->raise_exception("set complement takes one argument"); + return 0; + } + if (!check_set_arguments(arity, domain)) { + return 0; + } + return m_manager->mk_func_decl(m_set_complement_sym, arity, domain, domain[0], + func_decl_info(m_family_id, OP_SET_COMPLEMENT)); +} + +func_decl * array_decl_plugin::mk_set_subset(unsigned arity, sort * const * domain) { + if (arity != 2) { + m_manager->raise_exception("subset takes two arguments"); + return 0; + } + if (!check_set_arguments(arity, domain)) { + return 0; + } + sort * bool_sort = m_manager->mk_bool_sort(); + return m_manager->mk_func_decl(m_set_subset_sym, arity, domain, bool_sort, + func_decl_info(m_family_id, OP_SET_SUBSET)); +} + +func_decl * array_decl_plugin::mk_as_array(func_decl * f) { + vector parameters; + for (unsigned i = 0; i < f->get_arity(); i++) { + parameters.push_back(parameter(f->get_domain(i))); + } + parameters.push_back(parameter(f->get_range())); + sort * s = mk_sort(ARRAY_SORT, parameters.size(), parameters.c_ptr()); + parameter param(f); + func_decl_info info(m_family_id, OP_AS_ARRAY, 1, ¶m); + return m_manager->mk_const_decl(m_as_array_sym, s, info); +} + + +func_decl * array_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + switch (k) { + case OP_SELECT: + return mk_select(arity, domain); + case OP_STORE: + return mk_store(arity, domain); + case OP_CONST_ARRAY: { + if (num_parameters == 1 && parameters[0].is_ast() && is_sort(parameters[0].get_ast())) { + sort * s = to_sort(parameters[0].get_ast()); + return mk_const(s, arity, domain); + } + else if (range != 0) { + return mk_const(range, arity, domain); + } + else { + m_manager->raise_exception("array operation requires one sort parameter (the array sort)"); + UNREACHABLE(); + return 0; + } + } + case OP_ARRAY_MAP: { + if (num_parameters != 1 || !parameters[0].is_ast() || !is_func_decl(parameters[0].get_ast())) { + m_manager->raise_exception("array operation requires one function declaration parameter (the function to be mapped)"); + UNREACHABLE(); + return 0; + } + func_decl * f = to_func_decl(parameters[0].get_ast()); + return mk_map(f, arity, domain); + } + case OP_ARRAY_EXT_SKOLEM: + if (num_parameters != 1 || !parameters[0].is_int()) { + UNREACHABLE(); + return 0; + } + return mk_array_ext_skolem(arity, domain, parameters[0].get_int()); + case OP_ARRAY_DEFAULT: + return mk_default(arity, domain); + case OP_SET_UNION: + return mk_set_union(arity, domain); + case OP_SET_INTERSECT: + return mk_set_intersect(arity, domain); + case OP_SET_DIFFERENCE: + return mk_set_difference(arity, domain); + case OP_SET_COMPLEMENT: + return mk_set_complement(arity, domain); + case OP_SET_SUBSET: + return mk_set_subset(arity, domain); + case OP_AS_ARRAY: { + if (num_parameters != 1 || + !parameters[0].is_ast() || + !is_func_decl(parameters[0].get_ast()) || + to_func_decl(parameters[0].get_ast())->get_arity() == 0) { + TRACE("array_bug", + tout << "num_parameters: " << num_parameters << std::endl; + tout << "parameter.kind: " << parameters[0].is_int() << " " << parameters[0].is_ast() << " " << parameters[0].is_symbol() << "\n"; + tout << "as-array-bug: " << to_func_decl(parameters[0].get_ast())->get_name() << " " << to_func_decl(parameters[0].get_ast())->get_arity() << std::endl;); + m_manager->raise_exception("as-array takes one parameter, a function declaration with arity greater than zero"); + UNREACHABLE(); + return 0; + } + func_decl * f = to_func_decl(parameters[0].get_ast()); + return mk_as_array(f); + } + default: return 0; + } +} + +void array_decl_plugin::get_sort_names(svector& sort_names, symbol const & logic) { + sort_names.push_back(builtin_name(ARRAY_SORT_STR, ARRAY_SORT)); +} + +void array_decl_plugin::get_op_names(svector& op_names, symbol const & logic) { + op_names.push_back(builtin_name("store",OP_STORE)); + op_names.push_back(builtin_name("select",OP_SELECT)); + if (logic == symbol::null) { + // none of the SMT2 logics support these extensions + op_names.push_back(builtin_name("const",OP_CONST_ARRAY)); + op_names.push_back(builtin_name("map",OP_ARRAY_MAP)); + op_names.push_back(builtin_name("default",OP_ARRAY_DEFAULT)); + op_names.push_back(builtin_name("union",OP_SET_UNION)); + op_names.push_back(builtin_name("intersect",OP_SET_INTERSECT)); + op_names.push_back(builtin_name("difference",OP_SET_DIFFERENCE)); + op_names.push_back(builtin_name("complement",OP_SET_COMPLEMENT)); + op_names.push_back(builtin_name("subset",OP_SET_SUBSET)); + op_names.push_back(builtin_name("as-array", OP_AS_ARRAY)); + } +} + + +expr * array_decl_plugin::get_some_value(sort * s) { + SASSERT(s->is_sort_of(m_family_id, ARRAY_SORT)); + sort * r = to_sort(s->get_parameter(s->get_num_parameters() - 1).get_ast()); + expr * v = m_manager->get_some_value(r); + parameter p(s); + return m_manager->mk_app(m_family_id, OP_CONST_ARRAY, 1, &p, 1, &v); +} + +bool array_decl_plugin::is_fully_interp(sort const * s) const { + SASSERT(s->is_sort_of(m_family_id, ARRAY_SORT)); + unsigned sz = get_array_arity(s); + for (unsigned i = 0; i < sz; i++) { + if (!m_manager->is_fully_interp(get_array_domain(s, i))) + return false; + } + return m_manager->is_fully_interp(get_array_range(s)); +} + +bool array_util::is_as_array_tree(expr * n) { + ptr_buffer todo; + todo.push_back(n); + while (!todo.empty()) { + expr * curr = todo.back(); + todo.pop_back(); + if (is_as_array(curr)) + continue; + if (m_manager.is_ite(curr)) { + todo.push_back(to_app(curr)->get_arg(1)); + todo.push_back(to_app(curr)->get_arg(2)); + continue; + } + return false; + } + return true; +} + +sort * array_util::mk_array_sort(unsigned arity, sort* const* domain, sort* range) { + vector params; + for (unsigned i = 0; i < arity; ++i) { + params.push_back(parameter(domain[i])); + } + params.push_back(parameter(range)); + return m_manager.mk_sort(m_fid, ARRAY_SORT, params.size(), params.c_ptr()); +} diff --git a/lib/array_decl_plugin.h b/lib/array_decl_plugin.h new file mode 100644 index 000000000..bd1e123e3 --- /dev/null +++ b/lib/array_decl_plugin.h @@ -0,0 +1,170 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + array_decl_plugin.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-09. + +Revision History: + +--*/ +#ifndef _ARRAY_DECL_PLUGIN_H_ +#define _ARRAY_DECL_PLUGIN_H_ + +#include"ast.h" + + +inline sort* get_array_range(sort const * s) { + return to_sort(s->get_parameter(s->get_num_parameters() - 1).get_ast()); +} + +inline unsigned get_array_arity(sort const * s) { + return s->get_num_parameters() -1; +} + +inline sort* get_array_domain(sort const * s, unsigned idx) { + return to_sort(s->get_parameter(idx).get_ast()); +} + +enum array_sort_kind { + ARRAY_SORT +}; + +enum array_op_kind { + OP_STORE, + OP_SELECT, + OP_CONST_ARRAY, + OP_ARRAY_EXT_SKOLEM, + OP_ARRAY_DEFAULT, + OP_ARRAY_MAP, + OP_SET_UNION, + OP_SET_INTERSECT, + OP_SET_DIFFERENCE, + OP_SET_COMPLEMENT, + OP_SET_SUBSET, + OP_AS_ARRAY, // used for model construction + LAST_ARRAY_OP +}; + +class array_decl_plugin : public decl_plugin { + symbol m_store_sym; + symbol m_select_sym; + symbol m_const_sym; + symbol m_default_sym; + symbol m_map_sym; + symbol m_set_union_sym; + symbol m_set_intersect_sym; + symbol m_set_difference_sym; + symbol m_set_complement_sym; + symbol m_set_subset_sym; + symbol m_array_ext_sym; + symbol m_as_array_sym; + + bool check_set_arguments(unsigned arity, sort * const * domain); + + func_decl * mk_const(sort* ty, unsigned arity, sort * const * domain); + + func_decl * mk_map(func_decl* f, unsigned arity, sort* const* domain); + + func_decl * mk_default(unsigned arity, sort* const* domain); + + func_decl * mk_select(unsigned arity, sort * const * domain); + + func_decl * mk_store(unsigned arity, sort * const * domain); + + func_decl * mk_array_ext_skolem(unsigned arity, sort * const * domain, unsigned i); + + func_decl * mk_set_union(unsigned arity, sort * const * domain); + + func_decl * mk_set_intersect(unsigned arity, sort * const * domain); + + func_decl * mk_set_difference(unsigned arity, sort * const * domain); + + func_decl * mk_set_complement(unsigned arity, sort * const * domain); + + func_decl * mk_set_subset(unsigned arity, sort * const * domain); + + func_decl * mk_as_array(func_decl * f); + + bool is_array_sort(sort* s) const; + public: + array_decl_plugin(); + virtual ~array_decl_plugin() {} + + virtual decl_plugin * mk_fresh() { + return alloc(array_decl_plugin); + } + + // + // Contract for sort: + // parameters[0] - 1st dimension + // ... + // parameters[n-1] - nth dimension + // parameters[n] - range + // + virtual sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters); + + // + // Contract for func_decl: + // parameters[0] - array sort + // Contract for others: + // no parameters + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + + virtual void get_op_names(svector & op_names, symbol const & logic); + + virtual void get_sort_names(svector & sort_names, symbol const & logic); + + virtual expr * get_some_value(sort * s); + + virtual bool is_fully_interp(sort const * s) const; +}; + +class array_util { + ast_manager & m_manager; + family_id m_fid; +public: + array_util(ast_manager& m): m_manager(m), m_fid(m.get_family_id("array")) {} + ast_manager & get_manager() const { return m_manager; } + family_id get_family_id() const { return m_fid; } + bool is_array(sort* s) const { return is_sort_of(s, m_fid, ARRAY_SORT);} + bool is_array(expr* n) const { return is_array(m_manager.get_sort(n)); } + bool is_select(expr* n) const { return is_app_of(n, m_fid, OP_SELECT); } + bool is_store(expr* n) const { return is_app_of(n, m_fid, OP_STORE); } + bool is_const(expr* n) const { return is_app_of(n, m_fid, OP_CONST_ARRAY); } + bool is_map(expr* n) const { return is_app_of(n, m_fid, OP_ARRAY_MAP); } + bool is_as_array(expr * n) const { return is_app_of(n, m_fid, OP_AS_ARRAY); } + bool is_as_array_tree(expr * n); + func_decl * get_as_array_func_decl(app * n) const { SASSERT(is_as_array(n)); return to_func_decl(n->get_decl()->get_parameter(0).get_ast()); } + app * mk_map(func_decl * f, unsigned num_args, expr * const * args) { + parameter p(f); + return m_manager.mk_app(m_fid, OP_ARRAY_MAP, 1, &p, num_args, args); + } + app * mk_const_array(sort * s, expr * v) { + parameter param(s); + return m_manager.mk_app(m_fid, OP_CONST_ARRAY, 1, ¶m, 1, &v); + } + app * mk_empty_set(sort * s) { + return mk_const_array(s, m_manager.mk_false()); + } + app * mk_full_set(sort * s) { + return mk_const_array(s, m_manager.mk_true()); + } + + sort * mk_array_sort(sort* dom, sort* range) { return mk_array_sort(1, &dom, range); } + + sort * mk_array_sort(unsigned arity, sort* const* domain, sort* range); +}; + + +#endif /* _ARRAY_DECL_PLUGIN_H_ */ + diff --git a/lib/array_factory.cpp b/lib/array_factory.cpp new file mode 100644 index 000000000..b91857e40 --- /dev/null +++ b/lib/array_factory.cpp @@ -0,0 +1,206 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + array_factory.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-10-28. + +Revision History: + +--*/ + +#include"array_factory.h" +#include"array_decl_plugin.h" +#include"proto_model.h" +#include"func_interp.h" +#include"ast_pp.h" + +func_decl * mk_aux_decl_for_array_sort(ast_manager & m, sort * s) { + ptr_buffer domain; + sort * range = get_array_range(s); + unsigned arity = get_array_arity(s); + for (unsigned i = 0; i < arity; i++) { + domain.push_back(get_array_domain(s, i)); + } + return m.mk_fresh_func_decl(symbol::null, symbol::null, arity, domain.c_ptr(), range); +} + +array_factory::array_factory(ast_manager & m, proto_model & md): + struct_factory(m, m.get_family_id("array"), md) { +} + +/** + \brieft Return as-array[f] where f is a fresh function symbol with the right domain and range for the array sort s. + Store in fi the function interpretation for f. +*/ +expr * array_factory::mk_array_interp(sort * s, func_interp * & fi) { + func_decl * f = mk_aux_decl_for_array_sort(m_manager, s); + fi = alloc(func_interp, m_manager, get_array_arity(s)); + m_model.register_decl(f, fi); + parameter p[1] = { parameter(f) }; + expr * val = m_manager.mk_app(get_family_id(), OP_AS_ARRAY, 1, p); + register_value(val); + return val; +} + +void array_factory::get_some_args_for(sort * s, ptr_buffer & args) { + unsigned arity = get_array_arity(s); + for (unsigned i = 0; i < arity; i++) { + sort * d = get_array_domain(s, i); + expr * a = m_model.get_some_value(d); + args.push_back(a); + } +} + +expr * array_factory::get_some_value(sort * s) { + TRACE("array_factory", tout << mk_pp(s, m_manager) << "\n";); + value_set * set = 0; + if (m_sort2value_set.find(s, set) && !set->empty()) + return *(set->begin()); + func_interp * fi; + expr * val = mk_array_interp(s, fi); + ptr_buffer args; + get_some_args_for(s, args); + fi->insert_entry(args.c_ptr(), m_model.get_some_value(get_array_range(s))); + return val; +} + +bool array_factory::mk_two_diff_values_for(sort * s) { + DEBUG_CODE({ + value_set * set = 0; + SASSERT(!m_sort2value_set.find(s, set) || set->size() == 0); + }); + expr_ref r1(m_manager); + expr_ref r2(m_manager); + sort * range = get_array_range(s); + if (!m_model.get_some_values(range, r1, r2)) + return false; // failed... the range is probably unit. + ptr_buffer args; + get_some_args_for(s, args); + func_interp * fi1; + func_interp * fi2; + mk_array_interp(s, fi1); + mk_array_interp(s, fi2); + fi1->insert_entry(args.c_ptr(), r1); + fi2->insert_entry(args.c_ptr(), r2); + DEBUG_CODE({ + value_set * set = 0; + SASSERT(m_sort2value_set.find(s, set) && set->size() == 2); + }); + return true; +} + +bool array_factory::get_some_values(sort * s, expr_ref & v1, expr_ref & v2) { + value_set * set = 0; + if (!m_sort2value_set.find(s, set) || set->size() == 0) { + if (!mk_two_diff_values_for(s)) + return false; + } + m_sort2value_set.find(s, set); + SASSERT(set != 0); + SASSERT(set->size() > 0); + + if (set->size() == 1) { + v1 = *(set->begin()); + v2 = get_fresh_value(s); + return v2.get() != 0; + } + else { + SASSERT(set->size() >= 2); + value_set::iterator it = set->begin(); + v1 = *it; + ++it; + v2 = *it; + return true; + } +} + +// +// TODO: I have to check if the following procedure is really correct. +// I'm supporting partial arrays where the "else" can be set later by the model_finder or model classes. +// Projection functions may be also used. +// +// If projections are not used, then the following code should work if the "else" of a partial array +// is set with the result of some entry. +// +expr * array_factory::get_fresh_value(sort * s) { + value_set * set = get_value_set(s); + if (set->empty()) { + // easy case + return get_some_value(s); + } + sort * range = get_array_range(s); + expr * range_val = m_model.get_fresh_value(range); + if (range_val != 0) { + // easy case + func_interp * fi; + expr * val = mk_array_interp(s, fi); + ptr_buffer args; + get_some_args_for(s, args); + fi->insert_entry(args.c_ptr(), range_val); + return val; + } + else { + TRACE("array_factory_bug", tout << "array fresh value: using fresh index, range: " << mk_pp(range, m_manager) << "\n";); + expr_ref v1(m_manager); + expr_ref v2(m_manager); + if (m_model.get_some_values(range, v1, v2)) { + // Claim: A is fresh if A[i1] = v1 and A[i2] = v2 where i1 and i2 are fresh values, + // and v1 and v2 are distinct. + // + // Proof: let assume there is an Array A' such that A' = A. + // Then A[i1] == A'[i1] and A[i2] == A'[i2]. Since, i1 and i2 are fresh, + // A' does not have an entry for i1 or i2, So A'[i1] == A'[i2] == A'.m_else. + // Thus, A[i1] == A[i2] which is a contradiction since v1 != v2 and A[i1] = v1 and A[i2] = v2. + TRACE("array_factory_bug", tout << "v1: " << mk_pp(v1, m_manager) << " v2: " << mk_pp(v2, m_manager) << "\n";); + ptr_buffer args1; + ptr_buffer args2; + bool found = false; + unsigned arity = get_array_arity(s); + for (unsigned i = 0; i < arity; i++) { + sort * d = get_array_domain(s, i); + if (!found) { + expr * arg1 = m_model.get_fresh_value(d); + expr * arg2 = m_model.get_fresh_value(d); + if (arg1 != 0 && arg2 != 0) { + found = true; + args1.push_back(arg1); + args2.push_back(arg2); + continue; + } + } + expr * arg = m_model.get_some_value(d); + args1.push_back(arg); + args2.push_back(arg); + } + if (found) { + func_interp * fi; + expr * val = mk_array_interp(s, fi); + fi->insert_entry(args1.c_ptr(), v1); + fi->insert_entry(args2.c_ptr(), v2); + return val; + } + } + } + + // TODO: use more expensive procedures to create a fresh array value. + // Example: track the values used in the domain. + + // Remark: in the current implementation, this function + // will never fail, since if a type is finite, then + // type_pred will be applied and get_fresh_value will not + // need to be used. + + // failed to create a fresh array value + TRACE("array_factory_bug", tout << "failed to build fresh array value\n";); + return 0; +} + diff --git a/lib/array_factory.h b/lib/array_factory.h new file mode 100644 index 000000000..d42e77d15 --- /dev/null +++ b/lib/array_factory.h @@ -0,0 +1,45 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + array_factory.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-10-28. + +Revision History: + +--*/ +#ifndef _ARRAY_FACTORY_H_ +#define _ARRAY_FACTORY_H_ + +#include"struct_factory.h" + +class func_interp; + +func_decl * mk_aux_decl_for_array_sort(ast_manager & m, sort * s); + +class array_factory : public struct_factory { + expr * mk_array_interp(sort * s, func_interp * & fi); + void get_some_args_for(sort * s, ptr_buffer & args); + bool mk_two_diff_values_for(sort * s); +public: + array_factory(ast_manager & m, proto_model & md); + + virtual ~array_factory() {} + + virtual expr * get_some_value(sort * s); + + virtual bool get_some_values(sort * s, expr_ref & v1, expr_ref & v2); + + virtual expr * get_fresh_value(sort * s); +}; + +#endif /* _ARRAY_FACTORY_H_ */ + diff --git a/lib/array_map.h b/lib/array_map.h new file mode 100644 index 000000000..e4fbf3aa5 --- /dev/null +++ b/lib/array_map.h @@ -0,0 +1,162 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + array_map.h + +Abstract: + + A mapping for keys that can be mapped to unsigned integers. + +Author: + + Leonardo de Moura (leonardo) 2008-01-03. + +Revision History: + +--*/ +#ifndef _ARRAY_MAP_H_ +#define _ARRAY_MAP_H_ + +#include"vector.h" +#include"optional.h" + +/** + \brief Implements a mapping from Key to Data. + + Plugin must provide the following functions: + - void ins_eh(Key const & k, Data const & d); + - void del_eh(Key const & k, Data const & d); + - unsigned to_int(Key const & k); +*/ +template +class array_map { + + struct entry { + Key m_key; + Data m_data; + unsigned m_timestamp; + entry(Key const & k, Data const & d, unsigned t): m_key(k), m_data(d), m_timestamp(t) {} + }; + + unsigned m_timestamp; + unsigned m_garbage; + unsigned m_non_garbage; + static const unsigned m_gc_threshold = 10000; + vector, CallDestructors > m_map; + Plugin m_plugin; + + bool is_current(optional const& e) const { + return e->m_timestamp == m_timestamp; + } + + optional const & get_core(Key const & k) const { + unsigned id = m_plugin.to_int(k); + if (id < m_map.size()) { + optional const & e = m_map[id]; + if (e && is_current(e)) { + return e; + } + } + return optional::undef(); + } + + void really_flush() { + typename vector >::iterator it = m_map.begin(); + typename vector >::iterator end = m_map.end(); + for (; it != end; ++it) { + optional & e = *it; + if (e) { + m_plugin.del_eh(e->m_key, e->m_data); + e.set_invalid(); + } + } + m_garbage = 0; + m_non_garbage = 0; + } + + +public: + + array_map(Plugin const & p = Plugin()):m_timestamp(0), m_garbage(0), m_non_garbage(0), m_plugin(p) {} + ~array_map() { really_flush(); } + + bool contains(Key const & k) const { + return get_core(k); + } + + Data const & get(Key const & k) const { + optional const & e = get_core(k); + SASSERT(e); + return e->m_data; + } + + void reset() { + if (m_timestamp < UINT_MAX) { + m_timestamp++; + } + else { + really_flush(); + m_timestamp = 0; + } + } + + void insert(Key const & k, Data const & d) { + unsigned id = m_plugin.to_int(k); + if (id >= m_map.size()) { + m_map.resize(id + 1, optional::undef()); + } + + m_plugin.ins_eh(k, d); + optional & e = m_map[id]; + if (e) { + if (!is_current(e)) { + --m_garbage; + ++m_non_garbage; + } + m_plugin.del_eh(e->m_key, e->m_data); + } + else { + ++m_non_garbage; + } + e = entry(k, d, m_timestamp); + } + + void erase(Key const & k) { + unsigned id = m_plugin.to_int(k); + if (id < m_map.size()) { + optional & e = m_map[id]; + if (e) { + m_plugin.del_eh(e->m_key, e->m_data); + if (is_current(e)) { + SASSERT(m_non_garbage > 0); + --m_non_garbage; + } + else { + SASSERT(m_garbage > 0); + --m_garbage; + } + e.set_invalid(); + } + } + } + + void flush() { + m_garbage += m_non_garbage; + m_non_garbage = 0; + if (m_garbage > m_gc_threshold) { + really_flush(); + } + else { + reset(); + } + } + + void finalize() { + really_flush(); + } + +}; + +#endif /* _ARRAY_MAP_H_ */ diff --git a/lib/array_property_expander.cpp b/lib/array_property_expander.cpp new file mode 100644 index 000000000..4b1d8b002 --- /dev/null +++ b/lib/array_property_expander.cpp @@ -0,0 +1,208 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + array_property_expander.cpp + +Abstract: + + Expand array operations for the array property fragment formulas. + +Author: + + Nikolaj Bjorner (nbjorner) 2010-16-12 + +Revision History: + +--*/ + +#include"array_property_expander.h" +#include"obj_hashtable.h" +#include"var_subst.h" +#include"array_decl_plugin.h" +#include"for_each_expr.h" + +array_property_expander::array_property_expander(ast_manager& m): + m_manager(m) { +} + +namespace array_property_exp { + class proc { + ast_manager& m_manager; + unsigned& m_offset; + expr_ref_vector m_trail; + family_id m_fid; + array_util m_util; + obj_map m_mem; + + + void insert(expr* a, expr* b) { + m_trail.push_back(b); + m_mem.insert(a, b); + } + + + public: + proc(ast_manager& m, unsigned& offset) : + m_manager(m), + m_offset(offset), + m_trail(m), + m_fid(m.get_family_id("array")), + m_util(m) + {} + + expr* find(expr* e) { + expr* result = 0; + VERIFY(m_mem.find(e, result)); + return result; + } + + void operator()(var* n) { insert(n, n); } + + void operator()(quantifier* q) { + expr* e = find(q->get_expr()); + quantifier* q2 = m_manager.update_quantifier(q, e); + insert(q, q2); + } + + void operator()(app* n) { + ast_manager& m = m_manager; + unsigned num_args = n->get_num_args(); + ptr_buffer args; + for (unsigned i = 0; i < num_args; ++i) { + args.push_back(find(n->get_arg(i))); + } + if (m_manager.is_eq(n) && m_util.is_array(args[0])) { + visit_eq(n); + return; + } + if (m_manager.is_distinct(n) && num_args > 0 && m_util.is_array(args[0])) { + ptr_buffer eqs; + for (unsigned i = 0; i < num_args; ++i) { + for (unsigned j = i + 1; j < num_args; ++j) { + eqs.push_back(m.mk_not(m.mk_eq(args[i], args[j]))); + } + } + insert(n, m.mk_and(eqs.size(), eqs.c_ptr())); + return; + } + + if (m_util.is_select(n)) { + SASSERT(num_args > 0); + + // select(store(A,i,v),j) -> ite(i = j, v, select(A,j)) + if (m_util.is_store(args[0])) { + app* a = to_app(args[0]); + expr* b = find(a->get_arg(0)); + expr* v = find(a->get_arg(a->get_num_args()-1)); + ptr_buffer eqs; + SASSERT(num_args + 1 == a->get_num_args()); + for (unsigned i = 1; i < num_args; ++i) { + eqs.push_back(m.mk_eq(args[i], find(a->get_arg(i)))); + } + expr* r = m.mk_ite(m.mk_and(eqs.size(), eqs.c_ptr()), v, mk_select(b, num_args-1, args.c_ptr()+1)); + insert(n, r); + return; + } + + // select(ite(a,b,c),i) -> ite(a, select(b,i), select(c, i)) + if (m.is_ite(args[0])) { + app* k = to_app(args[0]); + expr* a = k->get_arg(0); + expr* b = mk_select(k->get_arg(1), args.size()-1, args.c_ptr()+1); + expr* c = mk_select(k->get_arg(2), args.size()-1, args.c_ptr()+1); + expr* r = m.mk_ite(a, b, c); + insert(n, r); + return; + } + + // select(map_f(A,B),i) -> f(select(A,i), select(B,i)) + if (m_util.is_map(args[0])) { + app* a = to_app(args[0]); + func_decl* f = a->get_decl(); + SASSERT(f->get_num_parameters() == 1); + SASSERT(f->get_parameter(0).is_ast()); + SASSERT(is_func_decl(f->get_parameter(0).get_ast())); + parameter p = f->get_parameter(0); + func_decl* d = to_func_decl(p.get_ast()); + ptr_buffer args2; + for (unsigned i = 0; i < a->get_num_args(); ++i) { + args2.push_back(mk_select(find(a->get_arg(i)), args.size()-1, args.c_ptr()+1)); + } + expr* r = m.mk_app(d, args2.size(), args2.c_ptr()); + insert(n, r); + return; + } + + // select(const v, i) -> v + if (m_util.is_const(args[0])) { + insert(n, to_app(args[0])->get_arg(0)); + return; + } + } + expr* r = m_manager.mk_app(n->get_decl(), args.size(), args.c_ptr()); + insert(n, r); + } + + private: + void visit_eq(app* eq) { + ast_manager& m = m_manager; + SASSERT(m.is_eq(eq)); + sort* s = m.get_sort(eq->get_arg(0)); + SASSERT(is_sort_of(s, m_fid, ARRAY_SORT)); + // sort* rng = get_array_range(s); + unsigned arity = get_array_arity(s); + shift_vars sh(m); + expr_ref e1(m), e2(m); + sh(find(eq->get_arg(0)), arity, e1); + sh(find(eq->get_arg(1)), arity, e2); + expr_ref_vector args(m); + buffer names; + ptr_buffer sorts; + args.push_back(e1); + for (unsigned i = 0; i < arity; ++i) { + args.push_back(m.mk_var(i, get_array_domain(s, i))); + sorts.push_back(get_array_domain(s, arity - i - 1)); + names.push_back(symbol(m_offset++)); + } + e1 = mk_select(args.size(), args.c_ptr()); + args[0] = e2; + e2 = mk_select(args.size(), args.c_ptr()); + e1 = m.mk_eq(e1, e2); + + e1 = m.mk_quantifier(true, arity, sorts.c_ptr(), names.c_ptr(), e1, 1); + insert(eq, e1); + } + + app* mk_select(unsigned n, expr* const* args) { + return m_manager.mk_app(m_fid, OP_SELECT, 0, 0, n, args); + } + + app* mk_select(expr* a, unsigned n, expr* const* args) { + ptr_buffer args2; + args2.push_back(a); + args2.append(n, args); + return mk_select(n+1, args2.c_ptr()); + } + }; +}; + +void array_property_expander::operator()(unsigned num_fmls, expr* const* fmls, expr_ref_vector& result) { + ast_manager& m = m_manager; + unsigned offset = 0; + for (unsigned i = 0; i < num_fmls; ++i) { + bool change = false; + expr_ref e(m); + result.push_back(fmls[i]); + do { + array_property_exp::proc p(m, offset); + e = result[i].get(); + for_each_expr(p, e); + result[i] = p.find(e); + change = e != result[i].get(); + } + while (change); + } +} + diff --git a/lib/array_property_expander.h b/lib/array_property_expander.h new file mode 100644 index 000000000..69b2d2ecd --- /dev/null +++ b/lib/array_property_expander.h @@ -0,0 +1,33 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + array_property_expander.h + +Abstract: + + Expand array operations for the array property fragment formulas. + +Author: + + Nikolaj Bjorner (nbjorner) 2010-16-12 + +Revision History: + +--*/ +#ifndef _ARRAY_PROPERTY_EXPANDER_H_ +#define _ARRAY_PROPERTY_EXPANDER_H_ + +#include"ast.h" + +class array_property_expander { + ast_manager& m_manager; +public: + array_property_expander(ast_manager& m); + void operator()(unsigned num_fmls, expr* const* fmls, expr_ref_vector& result); +}; + + +#endif /* _ARRAY_PROPERTY_EXPANDER_H_ */ + diff --git a/lib/array_property_recognizer.cpp b/lib/array_property_recognizer.cpp new file mode 100644 index 000000000..402e29b07 --- /dev/null +++ b/lib/array_property_recognizer.cpp @@ -0,0 +1,103 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + array_property_recognizer.cpp + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2010-16-12 + +Revision History: + +--*/ + +#include"array_decl_plugin.h" +#include"array_property_recognizer.h" +#include"for_each_expr.h" + + +array_property_recognizer::array_property_recognizer(ast_manager& m): + m_manager(m) {} + +namespace is_array_property_ns { + struct bad {}; + class proc { + ast_manager& m_manager; + family_id m_fid; + bool m_has_quantifier; + + void check_array_sort(expr* n) { + if (is_sort_of(m_manager.get_sort(n), m_fid, ARRAY_SORT)) { + throw bad(); + } + } + + public: + proc(ast_manager& m) : + m_manager(m), + m_fid(m.get_family_id("array")), + m_has_quantifier(false) {} + + bool has_quantifier() const { return m_has_quantifier; } + + void operator()(var* n) { + check_array_sort(n); + } + + void operator()(quantifier * ) { + m_has_quantifier = true; + } + + void operator()(app* n) { + unsigned num_args = n->get_num_args(); + if (m_manager.is_eq(n) || m_manager.is_distinct(n)) { + return; + } + family_id fid = n->get_family_id(); + if (fid == m_fid) { + switch(n->get_decl_kind()) { + case OP_STORE: + for (unsigned i = 1; i + 1 < num_args; ++i) { + check_array_sort(n->get_arg(i)); + } + return; + case OP_SELECT: + for (unsigned i = 1; i < num_args; ++i) { + check_array_sort(n->get_arg(i)); + } + return; + case OP_CONST_ARRAY: + case OP_ARRAY_MAP: + return; + default: + throw bad(); + } + } + // arrays cannot be arguments of other functions. + for (unsigned i = 0; i < num_args; ++i) { + check_array_sort(n->get_arg(i)); + } + } + }; +}; + +bool array_property_recognizer::operator()(unsigned num_fmls, expr* const* fmls) { + is_array_property_ns::proc p(m_manager); + try { + for (unsigned i = 0; i < num_fmls; ++i) { + for_each_expr(p, fmls[i]); + } + } + catch (is_array_property_ns::bad) { + return false; + } + return p.has_quantifier(); +} + + diff --git a/lib/array_property_recognizer.h b/lib/array_property_recognizer.h new file mode 100644 index 000000000..5c20f17da --- /dev/null +++ b/lib/array_property_recognizer.h @@ -0,0 +1,33 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + array_property_recognizer.h + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2010-16-12 + +Revision History: + +--*/ +#ifndef _ARRAY_PROPERTY_RECOGNIZER_H_ +#define _ARRAY_PROPERTY_RECOGNIZER_H_ + +#include"ast.h" + +class array_property_recognizer { + ast_manager& m_manager; +public: + array_property_recognizer(ast_manager& m); + bool operator()(unsigned num_fmls, expr* const* fmls); +}; + + +#endif /* _ARRAY_PROPERTY_RECOGNIZER_H_ */ + diff --git a/lib/array_rewriter.cpp b/lib/array_rewriter.cpp new file mode 100644 index 000000000..268f4dca0 --- /dev/null +++ b/lib/array_rewriter.cpp @@ -0,0 +1,359 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + array_rewriter.cpp + +Abstract: + + Basic rewriting rules for Arrays. + +Author: + + Leonardo (leonardo) 2011-04-06 + +Notes: + +--*/ +#include"array_rewriter.h" +#include"ast_lt.h" +#include"ast_pp.h" + +void array_rewriter::updt_params(params_ref const & p) { + m_sort_store = p.get_bool(":sort-store", false); + m_expand_select_store = p.get_bool(":expand-select-store", false); +} + +void array_rewriter::get_param_descrs(param_descrs & r) { + r.insert(":expand-select-store", CPK_BOOL, "(default: false) replace a (select (store ...) ...) term by an if-then-else term."); + r.insert(":sort-store", CPK_BOOL, "(default: false) sort nested stores when the indices are known to be different."); +} + +br_status array_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(f->get_family_id() == get_fid()); + TRACE("array_rewriter", tout << mk_pp(f, m()) << "\n"; + for (unsigned i = 0; i < num_args; ++i) { + tout << mk_pp(args[i], m()) << "\n"; + }); + switch (f->get_decl_kind()) { + case OP_SELECT: + return mk_select_core(num_args, args, result); + case OP_STORE: + return mk_store_core(num_args, args, result); + case OP_ARRAY_MAP: + SASSERT(f->get_num_parameters() == 1); + SASSERT(f->get_parameter(0).is_ast()); + SASSERT(is_func_decl(f->get_parameter(0).get_ast())); + return mk_map_core(to_func_decl(f->get_parameter(0).get_ast()), num_args, args, result); + case OP_SET_UNION: + return mk_set_union(num_args, args, result); + case OP_SET_INTERSECT: + return mk_set_intersect(num_args, args, result); + case OP_SET_SUBSET: + SASSERT(num_args == 2); + return mk_set_subset(args[0], args[1], result); + case OP_SET_COMPLEMENT: + SASSERT(num_args == 1); + return mk_set_complement(args[0], result); + case OP_SET_DIFFERENCE: + SASSERT(num_args == 2); + return mk_set_difference(args[0], args[1], result); + default: + return BR_FAILED; + } +} + +// 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])) + return l_false; + return l_undef; + } + return l_true; +} + +br_status array_rewriter::mk_store_core(unsigned num_args, expr * const * args, expr_ref & result) { + 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); + switch (r) { + case l_true: { + // + // store(store(a,i,v),i,w) --> store(a,i,w) + // + ptr_buffer new_args; + new_args.push_back(to_app(args[0])->get_arg(0)); + new_args.append(num_args-1, args+1); + SASSERT(new_args.size() == num_args); + result = m().mk_app(get_fid(), OP_STORE, num_args, new_args.c_ptr()); + 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)) { + ptr_buffer new_args; + new_args.push_back(to_app(args[0])->get_arg(0)); + new_args.append(num_args-1, args+1); + expr * nested_store = m().mk_app(get_fid(), OP_STORE, num_args, new_args.c_ptr()); + new_args.reset(); + new_args.push_back(nested_store); + new_args.append(num_args - 1, to_app(args[0])->get_args() + 1); + result = m().mk_app(get_fid(), OP_STORE, num_args, new_args.c_ptr()); + return BR_REWRITE2; + } + break; + case l_undef: + break; + } + } + + // + // store(const(v),i,v) --> const(v) + // + if (m_util.is_const(args[0]) && + to_app(args[0])->get_arg(0) == args[num_args-1]) { + result = args[0]; + return BR_DONE; + } + + expr * v = args[num_args-1]; + + // + // store(a, i, select(a, i)) --> a + // + if (m_util.is_select(v) && + compare_args(num_args-1, args, to_app(v)->get_args())) { + result = args[0]; + return BR_DONE; + } + + return BR_FAILED; +} + +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)) { + 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: { + // select(store(a, I, v), J) --> select(a, J) if I != J + ptr_buffer new_args; + new_args.push_back(to_app(args[0])->get_arg(0)); + new_args.append(num_args-1, args+1); + result = m().mk_app(get_fid(), OP_SELECT, num_args, new_args.c_ptr()); + return BR_REWRITE1; + } + default: + if (m_expand_select_store) { + // select(store(a, I, v), J) --> ite(I=J, v, select(a, J)) + ptr_buffer new_args; + new_args.push_back(to_app(args[0])->get_arg(0)); + new_args.append(num_args-1, args+1); + expr * sel_a_j = m().mk_app(get_fid(), OP_SELECT, num_args, new_args.c_ptr()); + expr * v = to_app(args[0])->get_arg(num_args); + ptr_buffer eqs; + unsigned num_indices = num_args-1; + for (unsigned i = 0; i < num_indices; i++) { + eqs.push_back(m().mk_eq(to_app(args[0])->get_arg(i+1), args[i+1])); + } + if (num_indices == 1) { + result = m().mk_ite(eqs[0], v, sel_a_j); + return BR_REWRITE2; + } + else { + result = m().mk_ite(m().mk_and(eqs.size(), eqs.c_ptr()), v, sel_a_j); + return BR_REWRITE3; + } + } + return BR_FAILED; + } + } + + if (m_util.is_const(args[0])) { + // select(const(v), I) --> v + result = to_app(args[0])->get_arg(0); + return BR_DONE; + } + + if (m_util.is_as_array(args[0])) { + // select(as-array[f], I) --> f(I) + func_decl * f = m_util.get_as_array_func_decl(to_app(args[0])); + result = m().mk_app(f, num_args - 1, args + 1); + return BR_REWRITE1; + } + + return BR_FAILED; +} + +br_status array_rewriter::mk_map_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(num_args >= 0); + bool is_store0 = m_util.is_store(args[0]); + bool is_const0 = m_util.is_const(args[0]); + if (num_args == 1) { + // + // map_f (store a j v) = (store (map_f a) j (f v)) + // + if (is_store0) { + app * store_expr = to_app(args[0]); + unsigned num_args = store_expr->get_num_args(); + SASSERT(num_args >= 3); + expr * a = store_expr->get_arg(0); + expr * v = store_expr->get_arg(num_args-1); + + ptr_buffer new_args; + + new_args.push_back(m_util.mk_map(f, 1, &a)); // (map_f a) + new_args.append(num_args - 2, store_expr->get_args() + 1); // j + new_args.push_back(m().mk_app(f, v)); // (f v) + + result = m().mk_app(get_fid(), OP_STORE, new_args.size(), new_args.c_ptr()); + return BR_REWRITE2; + } + + // + // map_f (const v) = (const (f v)) + // + if (is_const0) { + expr * fv = m().mk_app(f, to_app(args[0])->get_arg(0)); + result = m_util.mk_const_array(m().get_sort(args[0]), fv); + return BR_REWRITE2; + } + return BR_FAILED; + } + + SASSERT(num_args > 1); + + if (is_store0) { + unsigned num_indices = to_app(args[0])->get_num_args() - 2; + unsigned i; + for (i = 1; i < num_args; i++) { + if (!m_util.is_store(args[i])) + break; + unsigned j; + for (j = 1; j < num_indices+1; j++) { + if (to_app(args[0])->get_arg(j) != to_app(args[i])->get_arg(j)) + break; + } + if (j < num_indices+1) + break; + } + // + // map_f (store a_1 j v_1) ... (store a_n j v_n) --> (store (map_f a_1 ... a_n) j (f v_1 ... v_n)) + // + if (i == num_args) { + ptr_buffer arrays; + ptr_buffer values; + for (unsigned i = 0; i < num_args; i++) { + arrays.push_back(to_app(args[i])->get_arg(0)); + values.push_back(to_app(args[i])->get_arg(num_indices+1)); + } + ptr_buffer new_args; + new_args.push_back(m_util.mk_map(f, arrays.size(), arrays.c_ptr())); + new_args.append(num_indices, to_app(args[0])->get_args() + 1); + new_args.push_back(m().mk_app(f, values.size(), values.c_ptr())); + result = m().mk_app(get_fid(), OP_STORE, new_args.size(), new_args.c_ptr()); + return BR_REWRITE2; + } + return BR_FAILED; + } + + if (is_const0) { + unsigned i; + for (i = 1; i < num_args; i++) { + if (!m_util.is_const(args[i])) + break; + } + if (i == num_args) { + // + // map_f (const v_1) ... (const v_n) = (const (f v_1 ... v_n)) + // + ptr_buffer values; + for (unsigned i = 0; i < num_args; i++) { + values.push_back(to_app(args[i])->get_arg(0)); + } + + expr * fv = m().mk_app(f, values.size(), values.c_ptr()); + parameter p(m().get_sort(args[0])); + result = m().mk_app(get_fid(), OP_CONST_ARRAY, 1, &p, 1, &fv); + return BR_REWRITE2; + } + return BR_FAILED; + } + + return BR_FAILED; +} + +void array_rewriter::mk_store(unsigned num_args, expr * const * args, expr_ref & result) { + if (mk_store_core(num_args, args, result) == BR_FAILED) + result = m().mk_app(get_fid(), OP_STORE, num_args, args); +} + +void array_rewriter::mk_select(unsigned num_args, expr * const * args, expr_ref & result) { + if (mk_select_core(num_args, args, result) == BR_FAILED) + result = m().mk_app(get_fid(), OP_SELECT, num_args, args); +} + +void array_rewriter::mk_map(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + if (mk_map_core(f, num_args, args, result) == BR_FAILED) + result = m_util.mk_map(f, num_args, args); +} + +br_status array_rewriter::mk_set_union(unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(num_args > 0); + if (num_args == 1) { + result = args[0]; + return BR_DONE; + } + SASSERT(num_args >= 2); + br_status r = unsigned2br_status(num_args - 2); + result = m_util.mk_map(m().mk_or_decl(), num_args, args); + return r; +} + +br_status array_rewriter::mk_set_intersect(unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(num_args > 0); + if (num_args == 1) { + result = args[0]; + return BR_DONE; + } + SASSERT(num_args >= 2); + br_status r = unsigned2br_status(num_args - 2); + result = m_util.mk_map(m().mk_and_decl(), num_args, args); + return r; +} + + +br_status array_rewriter::mk_set_complement(expr * arg, expr_ref & result) { + return mk_map_core(m().mk_not_decl(), 1, &arg, result); +} + +br_status array_rewriter::mk_set_difference(expr * arg1, expr * arg2, expr_ref & result) { + expr * args[2] = { arg1, m_util.mk_map(m().mk_not_decl(), 1, &arg2) }; + result = m_util.mk_map(m().mk_and_decl(), 2, args); + return BR_REWRITE2; +} + +br_status array_rewriter::mk_set_subset(expr * arg1, expr * arg2, expr_ref & result) { + mk_set_difference(arg1, arg2, result); + result = m().mk_eq(result.get(), m_util.mk_empty_set(m().get_sort(arg1))); + return BR_REWRITE3; +} + diff --git a/lib/array_rewriter.h b/lib/array_rewriter.h new file mode 100644 index 000000000..f1362802d --- /dev/null +++ b/lib/array_rewriter.h @@ -0,0 +1,65 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + array_rewriter.h + +Abstract: + + Basic rewriting rules for Arrays. + +Author: + + Leonardo (leonardo) 2011-04-06 + +Notes: + +--*/ +#ifndef _ARRAY_REWRITER_H_ +#define _ARRAY_REWRITER_H_ + +#include"array_decl_plugin.h" +#include"rewriter_types.h" +#include"lbool.h" +#include"params.h" + +/** + \brief Cheap rewrite rules for Arrays +*/ +class array_rewriter { + array_util m_util; + bool m_sort_store; + bool m_expand_select_store; + template + lbool compare_args(unsigned num_args, expr * const * args1, expr * const * args2); +public: + array_rewriter(ast_manager & m, params_ref const & p = params_ref()): + m_util(m) { + updt_params(p); + } + ast_manager & m() const { return m_util.get_manager(); } + family_id get_fid() const { return m_util.get_family_id(); } + + void updt_params(params_ref const & p); + static void get_param_descrs(param_descrs & r); + + br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + + br_status mk_store_core(unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_select_core(unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_map_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + + void mk_store(unsigned num_args, expr * const * args, expr_ref & result); + void mk_select(unsigned num_args, expr * const * args, expr_ref & result); + void mk_map(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + + // The following methods never return BR_FAILED + br_status mk_set_union(unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_set_intersect(unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_set_complement(expr * arg, expr_ref & result); + br_status mk_set_difference(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_set_subset(expr * arg1, expr * arg2, expr_ref & result); +}; + +#endif diff --git a/lib/array_simplifier_plugin.cpp b/lib/array_simplifier_plugin.cpp new file mode 100644 index 000000000..8654db275 --- /dev/null +++ b/lib/array_simplifier_plugin.cpp @@ -0,0 +1,864 @@ +/*++ +Copyright (c) 2008 Microsoft Corporation + +Module Name: + + array_simplifier_plugin.cpp + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2008-05-05 + +Revision History: + +Notes TODO: + + Examine quadratic cost of simplification vs. model-based procedure. + + Parameterize cache replacement strategy. + Some parameters are hard-wired. + +--*/ + +#include "array_simplifier_plugin.h" +#include "ast_ll_pp.h" +#include "ast_pp.h" + + +array_simplifier_plugin::array_simplifier_plugin( + ast_manager & m, + basic_simplifier_plugin& s, + simplifier& simp, + theory_array_params const& p) : + simplifier_plugin(symbol("array"),m), + m_util(m), + m_simp(s), + m_simplifier(simp), + m_params(p), + m_store_cache_size(0) +{} + + +array_simplifier_plugin::~array_simplifier_plugin() { + + select_cache::iterator it = m_select_cache.begin(); + select_cache::iterator end = m_select_cache.end(); + for ( ; it != end; ++it) { + m_manager.dec_array_ref(it->m_key->size(), it->m_key->c_ptr()); + m_manager.dec_ref(it->m_value); + dealloc(it->m_key); + } + + store_cache::iterator it2 = m_store_cache.begin(); + store_cache::iterator end2 = m_store_cache.end(); + for (; it2 != end2; ++it2) { + m_manager.dec_ref(it->m_value); + dealloc(it->m_key); + } +} + + +bool array_simplifier_plugin::reduce(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + if (!m_params.m_array_simplify) + return false; + set_reduce_invoked(); + if (m_presimp) + return false; +#if _DEBUG + for (unsigned i = 0; i < num_args && i < f->get_arity(); ++i) { + SASSERT(m_manager.get_sort(args[i]) == f->get_domain(i)); + } +#endif + TRACE("array_simplifier", { + tout << mk_pp(f, m_manager) << " "; + for (unsigned i = 0; i < num_args; ++i) { + tout << mk_pp(args[i], m_manager) << " "; + } + tout << "\n"; + } + ); + SASSERT(f->get_family_id() == m_fid); + switch(f->get_decl_kind()) { + case OP_SELECT: + mk_select(num_args, args, result); + break; + case OP_STORE: + mk_store(f, num_args, args, result); + break; + case OP_SET_UNION: { + sort* s = f->get_range(); + expr_ref empty(m_manager); + mk_empty_set(s, empty); + switch(num_args) { + case 0: + result = empty; + break; + case 1: + result = args[0]; + break; + default: { + result = args[0]; + func_decl* f_or = m_manager.mk_or_decl(); + for (unsigned i = 1; i < num_args; ++i) { + mk_map(f_or, result, args[i], result); + } + break; + } + } + break; + } + case OP_SET_INTERSECT: { + expr_ref full(m_manager); + mk_full_set(f->get_range(), full); + switch(num_args) { + case 0: + result = full; + break; + case 1: + result = args[0]; + break; + default: { + result = args[0]; + func_decl* f_and = m_manager.mk_and_decl(); + for (unsigned i = 1; i < num_args; ++i) { + mk_map(f_and, result, args[i], result); + } + break; + } + } + TRACE("array_simplifier", tout << "sort " << mk_pp(result.get(), m_manager) << "\n";); + break; + } + case OP_SET_SUBSET: { + SASSERT(num_args == 2); + expr_ref diff(m_manager), emp(m_manager); + mk_set_difference(num_args, args, diff); + mk_empty_set(m_manager.get_sort(args[0]), emp); + m_simp.mk_eq(diff.get(), emp.get(), result); + break; + } + case OP_SET_COMPLEMENT: { + SASSERT(num_args == 1); + func_decl* f_not = m_manager.mk_not_decl(); + mk_map(f_not, args[0], result); + break; + } + case OP_SET_DIFFERENCE: { + SASSERT(num_args == 2); + expr_ref r1(m_manager); + mk_map(m_manager.mk_not_decl(), args[1], r1); + mk_map(m_manager.mk_and_decl(), args[0], r1, result); + break; + } + case OP_ARRAY_MAP: { + SASSERT(f->get_num_parameters() == 1); + SASSERT(f->get_parameter(0).is_ast()); + SASSERT(is_func_decl(f->get_parameter(0).get_ast())); + // + // map_d (store a j v) = (store (map_f a) v (d v)) + // + if (num_args == 1 && is_store(args[0])) { + app* store_expr = to_app(args[0]); + unsigned num_args = store_expr->get_num_args(); + SASSERT(num_args >= 3); + parameter p = f->get_parameter(0); + func_decl* d = to_func_decl(p.get_ast()); + expr* a = store_expr->get_arg(0); + expr* v = store_expr->get_arg(num_args-1); + // expr*const* args = store_expr->get_args()+1; + expr_ref r1(m_manager), r2(m_manager); + ptr_vector new_args; + + reduce(f, 1, &a, r1); + m_simplifier.mk_app(d, 1, &v, r2); + new_args.push_back(r1); + for (unsigned i = 1; i + 1 < num_args; ++i) { + new_args.push_back(store_expr->get_arg(i)); + } + new_args.push_back(r2); + mk_store(store_expr->get_decl(), num_args, new_args.c_ptr(), result); + break; + } + + // + // map_d (store a j v) (store b j w) = (store (map_f a b) j (d v w)) + // + if (num_args > 1 && same_store(num_args, args)) { + app* store_expr1 = to_app(args[0]); + unsigned num_indices = store_expr1->get_num_args(); + SASSERT(num_indices >= 3); + parameter p = f->get_parameter(0); + func_decl* d = to_func_decl(p.get_ast()); + ptr_vector arrays; + ptr_vector values; + for (unsigned i = 0; i < num_args; ++i) { + arrays.push_back(to_app(args[i])->get_arg(0)); + values.push_back(to_app(args[i])->get_arg(num_indices-1)); + } + + expr_ref r1(m_manager), r2(m_manager); + reduce(f, arrays.size(), arrays.c_ptr(), r1); + m_simplifier.mk_app(d, values.size(), values.c_ptr(), r2); + ptr_vector new_args; + new_args.push_back(r1); + for (unsigned i = 1; i + 1 < num_indices; ++i) { + new_args.push_back(store_expr1->get_arg(i)); + } + new_args.push_back(r2); + mk_store(store_expr1->get_decl(), new_args.size(), new_args.c_ptr(), result); + break; + } + // + // map_d (const v) = (const (d v)) + // + if (num_args == 1 && is_const_array(args[0])) { + app* const_expr = to_app(args[0]); + SASSERT(const_expr->get_num_args() == 1); + parameter p = f->get_parameter(0); + func_decl* d = to_func_decl(p.get_ast()); + expr* v = const_expr->get_arg(0); + expr_ref r1(m_manager); + + m_simplifier.mk_app(d, 1, &v, r1); + expr* arg = r1.get(); + parameter param(f->get_range()); + result = m_manager.mk_app(m_fid, OP_CONST_ARRAY, 1, ¶m, 1, &arg); + break; + } + // + // map_d (const v) (const w) = (const (d v w)) + // + if (num_args > 1 && all_const_array(num_args, args)) { + parameter p = f->get_parameter(0); + func_decl* d = to_func_decl(p.get_ast()); + ptr_vector values; + for (unsigned i = 0; i < num_args; ++i) { + values.push_back(to_app(args[i])->get_arg(0)); + } + expr_ref r1(m_manager); + + m_simplifier.mk_app(d, values.size(), values.c_ptr(), r1); + expr* arg = r1.get(); + parameter param(f->get_range()); + result = m_manager.mk_app(m_fid, OP_CONST_ARRAY, 1, ¶m, 1, &arg); + break; + } + result = m_manager.mk_app(f, num_args, args); + + break; + } + default: + result = m_manager.mk_app(f, num_args, args); + break; + } + TRACE("array_simplifier", + tout << mk_pp(result.get(), m_manager) << "\n";); + + return true; +} + +bool array_simplifier_plugin::same_store(unsigned num_args, expr* const* args) const { + if (num_args == 0) { + return true; + } + if (!is_store(args[0])) { + return false; + } + SASSERT(to_app(args[0])->get_num_args() >= 3); + unsigned num_indices = to_app(args[0])->get_num_args() - 2; + for (unsigned i = 1; i < num_args; ++i) { + if (!is_store(args[i])) { + return false; + } + for (unsigned j = 1; j < num_indices + 1; ++j) { + if (to_app(args[0])->get_arg(j) != to_app(args[i])->get_arg(j)) { + return false; + } + } + } + return true; +} + +bool array_simplifier_plugin::all_const_array(unsigned num_args, expr* const* args) const { + bool is_const = true; + for (unsigned i = 0; is_const && i < num_args; ++i) { + is_const = is_const_array(args[i]); + } + return is_const; +} + +bool array_simplifier_plugin::all_values(unsigned num_args, expr* const* args) const { + for (unsigned i = 0; i < num_args; ++i) { + if (!m_manager.is_value(args[i])) { + return false; + } + } + return true; +} + +bool array_simplifier_plugin::lex_lt(unsigned num_args, expr* const* args1, expr* const* args2) { + for (unsigned i = 0; i < num_args; ++i) { + TRACE("array_simplifier", + tout << mk_pp(args1[i], m_manager) << "\n"; + tout << mk_pp(args2[i], m_manager) << "\n"; + tout << args1[i]->get_id() << " " << args2[i]->get_id() << "\n"; + ); + + if (args1[i]->get_id() < args2[i]->get_id()) return true; + if (args1[i]->get_id() > args2[i]->get_id()) return false; + } + return false; +} + + +void array_simplifier_plugin::get_stores(expr* n, unsigned& arity, expr*& m, ptr_vector& stores) { + while (is_store(n)) { + app* a = to_app(n); + SASSERT(a->get_num_args() > 2); + arity = a->get_num_args()-2; + n = a->get_arg(0); + stores.push_back(a->get_args()+1); + } + m = n; +} + +lbool array_simplifier_plugin::eq_default(expr* def, unsigned arity, unsigned num_st, expr*const* const* st) { + for (unsigned i = 0; i < num_st; ++i) { + if (st[i][arity] == def) { + continue; + } + if (m_manager.is_value(st[i][arity]) && m_manager.is_value(def)) { + return l_false; + } + return l_undef; + } + return l_true; +} + +bool array_simplifier_plugin::insert_table(expr* def, unsigned arity, unsigned num_st, expr*const* const* st, arg_table& table) { + for (unsigned i = 0; i < num_st; ++i ) { + for (unsigned j = 0; j < arity; ++j) { + if (!m_manager.is_value(st[i][j])) { + return false; + } + } + args_entry e(arity, st[i]); + table.insert_if_not_there(e); + } + return true; +} + + +lbool array_simplifier_plugin::eq_stores(expr* def, unsigned arity, unsigned num_st1, expr*const* const* st1, unsigned num_st2, expr*const* const* st2) { + if (num_st1 == 0) { + return eq_default(def, arity, num_st2, st2); + } + if (num_st2 == 0) { + return eq_default(def, arity, num_st1, st1); + } + arg_table table1, table2; + if (!insert_table(def, arity, num_st1, st1, table1)) { + return l_undef; + } + if (!insert_table(def, arity, num_st2, st2, table2)) { + return l_undef; + } + + arg_table::iterator it = table1.begin(); + arg_table::iterator end = table1.end(); + for (; it != end; ++it) { + args_entry const & e1 = *it; + args_entry e2; + expr* v1 = e1.m_args[arity]; + if (table2.find(e1, e2)) { + expr* v2 = e2.m_args[arity]; + if (v1 == v2) { + table2.erase(e1); + continue; + } + if (m_manager.is_value(v1) && m_manager.is_value(v2)) { + return l_false; + } + return l_undef; + } + else if (m_manager.is_value(v1) && m_manager.is_value(def) && v1 != def) { + return l_false; + } + } + it = table2.begin(); + end = table2.end(); + for (; it != end; ++it) { + args_entry const & e = *it; + expr* v = e.m_args[arity]; + if (m_manager.is_value(v) && m_manager.is_value(def) && v != def) { + return l_false; + } + } + if (!table2.empty() || !table1.empty()) { + return l_undef; + } + return l_true; +} + + +bool array_simplifier_plugin::reduce_eq(expr * lhs, expr * rhs, expr_ref & result) { + set_reduce_invoked(); + expr* c1, *c2; + ptr_vector st1, st2; + unsigned arity = 0; + get_stores(lhs, arity, c1, st1); + get_stores(rhs, arity, c2, st2); + if (is_const_array(c1) && is_const_array(c2)) { + c1 = to_app(c1)->get_arg(0); + c2 = to_app(c2)->get_arg(0); + if (c1 == c2) { + lbool eq = eq_stores(c1, arity, st1.size(), st1.c_ptr(), st2.size(), st2.c_ptr()); + TRACE("array_simplifier", + tout << mk_pp(lhs, m_manager) << " = " + << mk_pp(rhs, m_manager) << " := " << eq << "\n";); + switch(eq) { + case l_false: + result = m_manager.mk_false(); + return true; + case l_true: + result = m_manager.mk_true(); + return true; + default: + return false; + } + } + else if (m_manager.is_value(c1) && m_manager.is_value(c2)) { + result = m_manager.mk_false(); + return true; + } + } + return false; +} + +bool array_simplifier_plugin::reduce_distinct(unsigned num_args, expr * const * args, expr_ref & result) { + set_reduce_invoked(); + return false; +} + + +array_simplifier_plugin::const_select_result +array_simplifier_plugin::mk_select_const(expr* m, app* index, expr_ref& result) { + store_info* info = 0; + expr* r = 0, *a = 0; + if (!is_store(m)) { + return NOT_CACHED; + } + if (!m_store_cache.find(m, info)) { + return NOT_CACHED; + } + if (info->m_map.find(index, r)) { + result = r; + return FOUND_VALUE; + } + a = info->m_default.get(); + + // + // Unfold and cache the store while searching for value of index. + // + while (is_store(a) && m_manager.is_value(to_app(a)->get_arg(1))) { + app* b = to_app(a); + app* c = to_app(b->get_arg(1)); + + if (!info->m_map.contains(c)) { + info->m_map.insert(c, b->get_arg(2)); + m_manager.inc_ref(b->get_arg(2)); + ++m_store_cache_size; + } + a = b->get_arg(0); + info->m_default = a; + + if (c == index) { + result = b->get_arg(2); + return FOUND_VALUE; + } + } + result = info->m_default.get(); + return FOUND_DEFAULT; +} + +void array_simplifier_plugin::cache_store(unsigned num_stores, expr* store_term) +{ + if (num_stores <= m_const_store_threshold) { + return; + } + prune_store_cache(); + if (!m_store_cache.contains(store_term)) { + store_info * info = alloc(store_info, m_manager, store_term); + m_manager.inc_ref(store_term); + m_store_cache.insert(store_term, info); + TRACE("cache_store", tout << m_store_cache.size() << "\n";); + ++m_store_cache_size; + } +} + +void array_simplifier_plugin::cache_select(unsigned num_args, expr * const * args, expr * result) { + ptr_vector * entry = alloc(ptr_vector); + entry->append(num_args, const_cast(args)); + const select_cache::key_data & kd = m_select_cache.insert_if_not_there(entry, result); + if (kd.m_key != entry) { + dealloc(entry); + return; + } + m_manager.inc_array_ref(num_args, args); + m_manager.inc_ref(result); + TRACE("cache_select", tout << m_select_cache.size() << "\n";); +} + + + +void array_simplifier_plugin::prune_select_cache() { + if (m_select_cache.size() > m_select_cache_max_size) { + flush_select_cache(); + } +} + +void array_simplifier_plugin::prune_store_cache() { + if (m_store_cache_size > m_store_cache_max_size) { + flush_store_cache(); + } +} + +void array_simplifier_plugin::flush_select_cache() { + select_cache::iterator it = m_select_cache.begin(); + select_cache::iterator end = m_select_cache.end(); + for (; it != end; ++it) { + ptr_vector * e = (*it).m_key; + m_manager.dec_array_ref(e->size(), e->begin()); + m_manager.dec_ref((*it).m_value); + dealloc(e); + } + m_select_cache.reset(); +} + +void array_simplifier_plugin::flush_store_cache() { + store_cache::iterator it = m_store_cache.begin(); + store_cache::iterator end = m_store_cache.end(); + for (; it != end; ++it) { + m_manager.dec_ref((*it).m_key); + const_map::iterator mit = (*it).m_value->m_map.begin(); + const_map::iterator mend = (*it).m_value->m_map.end(); + for (; mit != mend; ++mit) { + m_manager.dec_ref((*mit).m_value); + } + dealloc((*it).m_value); + } + m_store_cache.reset(); + m_store_cache_size = 0; +} + + +void array_simplifier_plugin::flush_caches() { + flush_select_cache(); + flush_store_cache(); +} + +void array_simplifier_plugin::mk_set_difference(unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(num_args == 2); + result = m_manager.mk_app(m_fid, OP_SET_DIFFERENCE, 0, 0, num_args, args); +} + +void array_simplifier_plugin::mk_empty_set(sort* ty, expr_ref & result) { + parameter param(ty); + expr* args[1] = { m_manager.mk_false() }; + result = m_manager.mk_app(m_fid, OP_CONST_ARRAY, 1, ¶m, 1, args); +} + +void array_simplifier_plugin::mk_full_set(sort* ty, expr_ref & result) { + parameter param(ty); + expr* args[1] = { m_manager.mk_true() }; + result = m_manager.mk_app(m_fid, OP_CONST_ARRAY, 1, ¶m, 1, args); +} + + +bool array_simplifier_plugin::same_args(unsigned num_args, expr * const * args1, expr * const * args2) { + for (unsigned i = 0; i < num_args; ++i) { + if (args1[i] != args2[i]) { + return false; + } + } + return true; +} + +void array_simplifier_plugin::mk_store(func_decl* f, unsigned num_args, expr * const * args, expr_ref & result) { + + SASSERT(num_args >= 3); + + expr* arg0 = args[0]; + expr* argn = args[num_args-1]; + + // + // store(store(a,i,v),i,w) = store(a,i,w) + // + if (is_store(arg0) && + same_args(num_args-2, args+1, to_app(arg0)->get_args()+1)) { + expr_ref_buffer new_args(m_manager); + new_args.push_back(to_app(arg0)->get_arg(0)); + for (unsigned i = 1; i < num_args; ++i) { + new_args.push_back(args[i]); + } + reduce(f, num_args, new_args.c_ptr(), result); + TRACE("array_simplifier", tout << mk_pp(result.get(), m_manager) << "\n";); + return; + } + + // + // store(const(v),i,v) = const(v) + // + if (is_const_array(arg0) && + to_app(arg0)->get_arg(0) == args[num_args-1]) { + result = arg0; + TRACE("array_simplifier", tout << mk_pp(result.get(), m_manager) << "\n";); + return; + } + + // + // store(a, i, select(a, i)) = a + // + if (is_select(argn) && + (to_app(argn)->get_num_args() == num_args - 1) && + same_args(num_args-1, args, to_app(argn)->get_args())) { + TRACE("dummy_store", tout << "dummy store simplified mk_store(\n"; + for (unsigned i = 0; i < num_args; i++) ast_ll_pp(tout, m_manager, args[i]); + tout << ") =====>\n"; + ast_ll_pp(tout, m_manager, arg0);); + result = arg0; + TRACE("array_simplifier", tout << mk_pp(result.get(), m_manager) << "\n";); + return; + } + + // + // store(store(a,i,v),j,w) -> store(store(a,j,w),i,v) + // if i, j are values, i->get_id() < j->get_id() + // + if (m_params.m_array_canonize_simplify && + is_store(arg0) && + all_values(num_args-2, args+1) && + all_values(num_args-2, to_app(arg0)->get_args()+1) && + lex_lt(num_args-2, args+1, to_app(arg0)->get_args()+1)) { + expr* const* args2 = to_app(arg0)->get_args(); + expr_ref_buffer new_args(m_manager); + new_args.push_back(args2[0]); + for (unsigned i = 1; i < num_args; ++i) { + new_args.push_back(args[i]); + } + reduce(f, num_args, new_args.c_ptr(), result); + new_args.reset(); + new_args.push_back(result); + for (unsigned i = 1; i < num_args; ++i) { + new_args.push_back(args2[i]); + } + result = m_manager.mk_app(m_fid, OP_STORE, num_args, new_args.c_ptr()); + TRACE("array_simplifier", tout << mk_pp(result.get(), m_manager) << "\n";); + return; + } + + + result = m_manager.mk_app(m_fid, OP_STORE, num_args, args); + TRACE("array_simplifier", tout << "default: " << mk_pp(result.get(), m_manager) << "\n";); + +} + +void array_simplifier_plugin::mk_select_as_array(unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(is_as_array(args[0])); + func_decl * f = get_as_array_func_decl(to_app(args[0])); + result = m_manager.mk_app(f, num_args - 1, args+1); +} + +void array_simplifier_plugin::mk_select_as_array_tree(unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(is_as_array_tree(args[0])); + SASSERT(m_manager.is_ite(args[0])); + ptr_buffer todo; + obj_map cache; + app_ref_buffer trail(m_manager); + todo.push_back(to_app(args[0])); + while (!todo.empty()) { + app * curr = todo.back(); + SASSERT(m_manager.is_ite(curr)); + expr * branches[2] = {0, 0}; + bool visited = true; + for (unsigned i = 0; i < 2; i++) { + expr * arg = curr->get_arg(i+1); + if (is_as_array(arg)) { + branches[i] = m_manager.mk_app(get_as_array_func_decl(to_app(arg)), num_args - 1, args+1); + } + else { + SASSERT(m_manager.is_ite(arg)); + app * new_arg = 0; + if (!cache.find(to_app(arg), new_arg)) { + todo.push_back(to_app(arg)); + visited = false; + } + else { + branches[i] = new_arg; + } + } + } + if (visited) { + todo.pop_back(); + app * new_curr = m_manager.mk_ite(curr->get_arg(0), branches[0], branches[1]); + trail.push_back(new_curr); + cache.insert(curr, new_curr); + } + } + SASSERT(cache.contains(to_app(args[0]))); + app * r = 0; + cache.find(to_app(args[0]), r); + result = r; +} + +void array_simplifier_plugin::mk_select(unsigned num_args, expr * const * args, expr_ref & result) { + expr * r = 0; + + if (is_as_array(args[0])) { + mk_select_as_array(num_args, args, result); + return; + } + + if (is_as_array_tree(args[0])) { + mk_select_as_array_tree(num_args, args, result); + return; + } + + bool is_const_select = num_args == 2 && m_manager.is_value(args[1]); + app* const_index = is_const_select?to_app(args[1]):0; + unsigned num_const_stores = 0; + expr_ref tmp(m_manager); + expr* args2[2]; + if (is_const_select) { + switch(mk_select_const(args[0], const_index, tmp)) { + case NOT_CACHED: + break; + case FOUND_VALUE: + TRACE("mk_select", tout << "found value\n"; ast_ll_pp(tout, m_manager, tmp.get()); ); + result = tmp.get(); + // value of select is stored under result. + return; + case FOUND_DEFAULT: + args2[0] = tmp.get(); + args2[1] = args[1]; + args = args2; + is_const_select = false; + break; + } + } + + SASSERT(num_args > 0); + ptr_vector & entry = m_tmp2; + entry.reset(); + entry.append(num_args, args); + expr * entry0 = entry[0]; + SASSERT(m_todo.empty()); + m_todo.push_back(entry0); + while (!m_todo.empty()) { + expr * m = m_todo.back(); + TRACE("array_simplifier", tout << mk_bounded_pp(m, m_manager) << "\n";); + if (is_store(m)) { + expr * nested_array = to_app(m)->get_arg(0); + expr * else_branch = 0; + entry[0] = nested_array; + if (is_const_select) { + if (m_manager.is_value(to_app(m)->get_arg(1))) { + app* const_index2 = to_app(to_app(m)->get_arg(1)); + // + // we found the value, all other stores are different. + // there is no need to recurse. + // + if (const_index == const_index2) { + result = to_app(m)->get_arg(2); + cache_store(num_const_stores, args[0]); + m_todo.reset(); + return; + } + ++num_const_stores; + } + else { + is_const_select = false; + } + } + if (m_select_cache.find(&entry, else_branch)) { + expr_ref_buffer eqs(m_manager); + for (unsigned i = 1; i < num_args ; ++i) { + expr * a = args[i]; + expr * b = to_app(m)->get_arg(i); + expr_ref eq(m_manager); + m_simp.mk_eq(a, b, eq); + eqs.push_back(eq.get()); + } + expr_ref cond(m_manager); + m_simp.mk_and(eqs.size(), eqs.c_ptr(), cond); + expr * then_branch = to_app(m)->get_arg(num_args); + if (m_manager.is_true(cond.get())) { + result = then_branch; + } + else if (m_manager.is_false(cond.get())) { + result = else_branch; + } + else { + m_simp.mk_ite(cond.get(), then_branch, else_branch, result); + } + entry[0] = m; + cache_select(entry.size(), entry.c_ptr(), result.get()); + m_todo.pop_back(); + } + else { + m_todo.push_back(nested_array); + } + } + else if (is_const_array(m)) { + entry[0] = m; + cache_select(entry.size(), entry.c_ptr(), to_app(m)->get_arg(0)); + m_todo.pop_back(); + } + else { + entry[0] = m; + TRACE("array_simplifier", { + for (unsigned i = 0; i < entry.size(); ++i) { + tout << mk_bounded_pp(entry[i], m_manager) << ": " + << mk_bounded_pp(m_manager.get_sort(entry[i]), m_manager) << "\n"; + }} + ); + r = m_manager.mk_app(m_fid, OP_SELECT, 0, 0, entry.size(), entry.c_ptr()); + cache_select(entry.size(), entry.c_ptr(), r); + m_todo.pop_back(); + } + } + cache_store(num_const_stores, args[0]); + entry[0] = entry0; +#ifdef Z3DEBUG + bool f = +#endif + m_select_cache.find(&entry, r); + SASSERT(f); + result = r; + prune_select_cache(); + prune_store_cache(); + TRACE("mk_select", + for (unsigned i = 0; i < num_args; i++) { + ast_ll_pp(tout, m_manager, args[i]); tout << "\n"; + }; + tout << "is_store: " << is_store(args[0]) << "\n"; + ast_ll_pp(tout, m_manager, r);); +} + + +void array_simplifier_plugin::mk_map(func_decl* f, expr* a, expr* b, expr_ref& result) { + expr* exprs[2] = { a, b }; + parameter param(f); + result = m_manager.mk_app(m_fid, OP_ARRAY_MAP, 1, ¶m, 2, exprs ); +} + +void array_simplifier_plugin::mk_map(func_decl* f, expr* a, expr_ref& result) { + parameter param(f); + result = m_manager.mk_app(m_fid, OP_ARRAY_MAP, 1, ¶m, 1, &a ); +} + + diff --git a/lib/array_simplifier_plugin.h b/lib/array_simplifier_plugin.h new file mode 100644 index 000000000..fffa18c3f --- /dev/null +++ b/lib/array_simplifier_plugin.h @@ -0,0 +1,154 @@ +/*++ +Copyright (c) 2008 Microsoft Corporation + +Module Name: + + array_simplifier_plugin.h + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2008-05-05 + +Revision History: + +--*/ +#ifndef _ARRAY_SIMPLIFIER_PLUGIN_H_ +#define _ARRAY_SIMPLIFIER_PLUGIN_H_ + +#include"ast.h" +#include"map.h" +#include"array_decl_plugin.h" +#include"simplifier_plugin.h" +#include"basic_simplifier_plugin.h" +#include"theory_array_params.h" +#include"simplifier.h" +#include"obj_hashtable.h" +#include"lbool.h" + +class array_simplifier_plugin : public simplifier_plugin { + + typedef ptr_vector entry; + + struct entry_hash_proc { + unsigned operator()(ptr_vector * entry) const { + return get_exprs_hash(entry->size(), entry->begin(), 0xbeef1010); + } + }; + + struct entry_eq_proc { + bool operator()(ptr_vector * entry1, ptr_vector * entry2) const { + if (entry1->size() != entry2->size()) return false; + return compare_arrays(entry1->begin(), entry2->begin(), entry1->size()); + } + }; + + typedef map select_cache; + + struct args_entry { + unsigned m_arity; + expr* const* m_args; + args_entry(unsigned a, expr* const* args) : m_arity(a), m_args(args) {} + args_entry() : m_arity(0), m_args(0) {} + }; + + struct args_entry_hash_proc { + unsigned operator()(args_entry const& e) const { + return get_exprs_hash(e.m_arity, e.m_args, 0xbeef1010); + } + }; + struct args_entry_eq_proc { + bool operator()(args_entry const& e1, args_entry const& e2) const { + if (e1.m_arity != e2.m_arity) return false; + return compare_arrays(e1.m_args, e2.m_args, e1.m_arity); + } + }; + typedef hashtable arg_table; + + array_util m_util; + basic_simplifier_plugin& m_simp; + simplifier& m_simplifier; + theory_array_params const& m_params; + select_cache m_select_cache; + ptr_vector m_tmp; + ptr_vector m_tmp2; + ptr_vector m_todo; + static const unsigned m_select_cache_max_size = 100000; + typedef obj_map const_map; + class store_info { + store_info(); + store_info(store_info const&); + public: + const_map m_map; + expr_ref m_default; + store_info(ast_manager& m, expr* d): m_default(d, m) {} + }; + + typedef obj_map store_cache; + store_cache m_store_cache; + unsigned m_store_cache_size; + static const unsigned m_store_cache_max_size = 10000; + static const unsigned m_const_store_threshold = 5; + enum const_select_result { + NOT_CACHED, + FOUND_DEFAULT, + FOUND_VALUE + }; + + +public: + array_simplifier_plugin(ast_manager & m, basic_simplifier_plugin& s, simplifier& simp, theory_array_params const& p); + virtual ~array_simplifier_plugin(); + + virtual bool reduce(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + + virtual bool reduce_eq(expr * lhs, expr * rhs, expr_ref & result); + + virtual bool reduce_distinct(unsigned num_args, expr * const * args, expr_ref & result); + + virtual void flush_caches(); + +private: + bool is_select(expr* n) const { return m_util.is_select(n); } + bool is_store(expr * n) const { return m_util.is_store(n); } + bool is_const_array(expr * n) const { return m_util.is_const(n); } + bool is_as_array(expr * n) const { return m_util.is_as_array(n); } + bool is_as_array_tree(expr * n) { return m_util.is_as_array_tree(n); } + func_decl * get_as_array_func_decl(app * n) const { return m_util.get_as_array_func_decl(n); } + void mk_select_as_array(unsigned num_args, expr * const * args, expr_ref & result); + void mk_select_as_array_tree(unsigned num_args, expr * const * args, expr_ref & result); + bool is_enumerated(expr* n, expr_ref& c, ptr_vector& keys, ptr_vector& vals); + const_select_result mk_select_const(expr* m, app* index, expr_ref& result); + void cache_store(unsigned num_stores, expr* nested_store); + void cache_select(unsigned num_args, expr * const * args, expr * result); + void prune_select_cache(); + void prune_store_cache(); + void flush_select_cache(); + void flush_store_cache(); + void mk_set_difference(unsigned num_args, expr * const * args, expr_ref & result); + void mk_empty_set(sort* ty, expr_ref & result); + void mk_full_set(sort* ty, expr_ref & result); + void mk_select(unsigned num_args, expr * const * args, expr_ref & result); + void mk_store(func_decl* f, unsigned num_args, expr * const * args, expr_ref & result); + void mk_map(func_decl* f, expr* a, expr* b, expr_ref & result); + void mk_map(func_decl* f, expr* a, expr_ref & result); + bool same_args(unsigned num_args, expr * const * args1, expr * const * args2); + + void get_stores(expr* n, unsigned& arity, expr*& m, ptr_vector& stores); + lbool eq_default(expr* def, unsigned arity, unsigned num_st, expr*const* const* st); + bool insert_table(expr* def, unsigned arity, unsigned num_st, expr*const* const* st, arg_table& table); + lbool eq_stores(expr* def, unsigned arity, unsigned num_st1, expr*const* const* st1, unsigned num_st2, expr*const* const* st2); + + bool same_store(unsigned num_args, expr* const* args) const; + bool all_const_array(unsigned num_args, expr* const* args) const; + bool all_values(unsigned num_args, expr* const* args) const; + bool lex_lt(unsigned num_args, expr* const* args1, expr* const* args2); + +}; + + +#endif /* _ARRAY_SIMPLIFIER_PLUGIN_H_ */ + diff --git a/lib/asserted_formulas.cpp b/lib/asserted_formulas.cpp new file mode 100644 index 000000000..86907adb3 --- /dev/null +++ b/lib/asserted_formulas.cpp @@ -0,0 +1,1494 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + asserted_formulas.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-11. + +Revision History: + +--*/ +#include"asserted_formulas.h" +#include"ast_ll_pp.h" +#include"ast_pp.h" +#include"ast_smt2_pp.h" +#include"arith_simplifier_plugin.h" +#include"array_simplifier_plugin.h" +#include"datatype_simplifier_plugin.h" +#include"bv_simplifier_plugin.h" +#include"arith_solver_plugin.h" +#include"occurs.h" +#include"for_each_expr.h" +#include"well_sorted.h" +#include"pull_ite_tree.h" +#include"push_app_ite.h" +#include"elim_term_ite.h" +#include"pattern_inference.h" +#include"nnf.h" +#include"cnf.h" +#include"expr_context_simplifier.h" +#include"bv_elim.h" +#include"inj_axiom.h" +#include"der.h" +#include"elim_bounds.h" +#include"warning.h" +#include"eager_bit_blaster.h" +#include"bit2int.h" +#include"qe.h" +#include"distribute_forall.h" +#include"demodulator.h" +#include"quasi_macros.h" + +asserted_formulas::asserted_formulas(ast_manager & m, front_end_params & p): + m_manager(m), + m_params(p), + m_pre_simplifier(m), + m_simplifier(m), + m_defined_names(m), + m_static_features(m), + m_asserted_formulas(m), + m_asserted_formula_prs(m), + m_asserted_qhead(0), + m_subst(m), + m_vars_qhead(0), + m_macro_manager(m, m_simplifier), + m_bit2int(m), + m_bv_sharing(m), + m_user_rewriter(m), + m_inconsistent(false), + m_quant_elim(m, p), + m_cancel_flag(false) { + + m_bsimp = 0; + m_bvsimp = 0; + arith_simplifier_plugin * arith_simp = 0; + setup_simplifier_plugins(m_simplifier, m_bsimp, arith_simp, m_bvsimp); + SASSERT(m_bsimp != 0); + SASSERT(arith_simp != 0); + m_simplifier.set_subst_map(&m_subst); + m_macro_finder = alloc(macro_finder, m_manager, m_macro_manager); + + basic_simplifier_plugin * basic_simp = 0; + bv_simplifier_plugin * bv_simp = 0; + setup_simplifier_plugins(m_pre_simplifier, basic_simp, arith_simp, bv_simp); + m_bit2int.set_bv_simplifier(bv_simp); + m_pre_simplifier.enable_presimp(); +} + +void asserted_formulas::setup() { + switch (m_params.m_lift_ite) { + case LI_FULL: + m_params.m_ng_lift_ite = LI_NONE; + break; + case LI_CONSERVATIVE: + if (m_params.m_ng_lift_ite == LI_CONSERVATIVE) + m_params.m_ng_lift_ite = LI_NONE; + break; + default: + break; + } + + if (m_params.m_relevancy_lvl == 0) + m_params.m_relevancy_lemma = false; + + switch (m_params.m_cnf_mode) { + case CNF_QUANT: + if (m_params.m_nnf_mode == NNF_SKOLEM) + m_params.m_nnf_mode = NNF_QUANT; + break; + case CNF_OPPORTUNISTIC: + if (m_params.m_nnf_mode == NNF_SKOLEM) + m_params.m_nnf_mode = NNF_QUANT; + break; + case CNF_FULL: + m_params.m_nnf_mode = NNF_FULL; + break; + default: + break; + } +} + +void asserted_formulas::setup_simplifier_plugins(simplifier & s, basic_simplifier_plugin * & bsimp, arith_simplifier_plugin * & asimp, bv_simplifier_plugin * & bvsimp) { + bsimp = alloc(basic_simplifier_plugin, m_manager); + s.register_plugin(bsimp); + asimp = alloc(arith_simplifier_plugin, m_manager, *bsimp, m_params); + s.register_plugin(asimp); + s.register_plugin(alloc(array_simplifier_plugin, m_manager, *bsimp, s, m_params)); + bvsimp = alloc(bv_simplifier_plugin, m_manager, *bsimp, m_params); + s.register_plugin(bvsimp); + s.register_plugin(alloc(datatype_simplifier_plugin, m_manager, *bsimp)); +} + +void asserted_formulas::init(unsigned num_formulas, expr * const * formulas, proof * const * prs) { + SASSERT(m_asserted_formulas.empty()); + SASSERT(m_asserted_formula_prs.empty()); + SASSERT(!m_inconsistent); + SASSERT(m_scopes.empty()); + m_asserted_formulas.append(num_formulas, formulas); + if (m_manager.proofs_enabled()) + m_asserted_formula_prs.append(num_formulas, prs); +} + +bool asserted_formulas::has_bv() const { + // approaximated answer... assume the formula has bit-vectors if the bv_simplifier_plugin was invoked at least once. + return m_bvsimp->reduce_invoked(); +} + +asserted_formulas::~asserted_formulas() { +} + +void asserted_formulas::push_assertion(expr * e, proof * pr, expr_ref_vector & result, proof_ref_vector & result_prs) { + if (inconsistent()) { + SASSERT(!result.empty()); + return; + } + if (m_manager.is_false(e)) + m_inconsistent = true; + ::push_assertion(m_manager, e, pr, result, result_prs); +} + +void asserted_formulas::set_eliminate_and(bool flag) { + if (m_bsimp->eliminate_and() == flag) + return; + TRACE("eliminate_and", tout << "flushing cache...\n";); + flush_cache(); + m_bsimp->set_eliminate_and(flag); +} + +void asserted_formulas::assert_expr(expr * e, proof * _in_pr) { + if (inconsistent()) + return; + if (!m_params.m_preprocess) { + push_assertion(e, _in_pr, m_asserted_formulas, m_asserted_formula_prs); + return; + } + proof_ref in_pr(_in_pr, m_manager); + expr_ref r1(m_manager); + proof_ref pr1(m_manager); + expr_ref r2(m_manager); + proof_ref pr2(m_manager); + TRACE("assert_expr_before_simp", tout << mk_ll_pp(e, m_manager) << "\n";); + TRACE("assert_expr_bug", tout << mk_ismt2_pp(e, m_manager) << "\n";); + if (m_params.m_pre_simplifier) { + m_pre_simplifier(e, r1, pr1); + } + else { + r1 = e; + pr1 = 0; + } + set_eliminate_and(false); // do not eliminate and before nnf. + m_simplifier(r1, r2, pr2); + TRACE("assert_expr_bug", tout << "after...\n" << mk_ismt2_pp(r1, m_manager) << "\n";); + if (m_manager.proofs_enabled()) { + if (e == r2) + pr2 = in_pr; + else + pr2 = m_manager.mk_modus_ponens(in_pr, m_manager.mk_transitivity(pr1, pr2)); + } + TRACE("assert_expr_after_simp", tout << mk_ll_pp(r1, m_manager) << "\n";); + push_assertion(r2, pr2, m_asserted_formulas, m_asserted_formula_prs); + TRACE("asserted_formulas_bug", tout << "after assert_expr\n"; display(tout);); +} + +void asserted_formulas::assert_expr(expr * e) { + if (inconsistent()) + return; + assert_expr(e, m_manager.mk_asserted(e)); +} + +void asserted_formulas::get_assertions(ptr_vector & result) { + result.append(m_asserted_formulas.size(), m_asserted_formulas.c_ptr()); +} + +void asserted_formulas::push_scope() { + SASSERT(inconsistent() || m_asserted_qhead == m_asserted_formulas.size()); + TRACE("asserted_formulas_scopes", tout << "push:\n"; display(tout);); + m_scopes.push_back(scope()); + m_macro_manager.push_scope(); + scope & s = m_scopes.back(); + s.m_asserted_formulas_lim = m_asserted_formulas.size(); + SASSERT(inconsistent() || s.m_asserted_formulas_lim == m_asserted_qhead); + s.m_vars_lim = m_vars.size(); + s.m_forbidden_vars_lim = m_forbidden_vars.size(); + s.m_inconsistent_old = m_inconsistent; + m_defined_names.push_scope(); + m_bv_sharing.push_scope(); + commit(); +} + +void asserted_formulas::pop_scope(unsigned num_scopes) { + TRACE("asserted_formulas_scopes", tout << "before pop " << num_scopes << "\n"; display(tout);); + m_bv_sharing.pop_scope(num_scopes); + m_macro_manager.pop_scope(num_scopes); + unsigned new_lvl = m_scopes.size() - num_scopes; + scope & s = m_scopes[new_lvl]; + m_inconsistent = s.m_inconsistent_old; + restore_subst(s.m_vars_lim); + restore_forbidden_vars(s.m_forbidden_vars_lim); + m_defined_names.pop_scope(num_scopes); + m_asserted_formulas.shrink(s.m_asserted_formulas_lim); + if (m_manager.proofs_enabled()) + m_asserted_formula_prs.shrink(s.m_asserted_formulas_lim); + m_asserted_qhead = s.m_asserted_formulas_lim; + m_vars_qhead = m_vars.size(); + m_scopes.shrink(new_lvl); + flush_cache(); + TRACE("asserted_formulas_scopes", tout << "after pop " << num_scopes << "\n"; display(tout);); +} + +void asserted_formulas::restore_subst(unsigned old_size) { + unsigned sz = m_vars.size(); + SASSERT(sz >= old_size); + TRACE("asserted_formulas_bug", tout << "restore_subst, old_size: " << old_size << ", curr_size: " << sz << "\n";); + for (unsigned i = old_size; i < sz; i++) { + SASSERT(is_app(m_vars[i])); + TRACE("asserted_formulas_bug", tout << "removing subst: " << mk_pp(m_vars[i], m_manager) << "\n";); + m_subst.erase(m_vars[i]); + SASSERT(!m_subst.contains(m_vars[i])); + } + if (old_size != sz) + flush_cache(); + m_vars.shrink(old_size); +} + +void asserted_formulas::restore_forbidden_vars(unsigned old_size) { + unsigned sz = m_forbidden_vars.size(); + SASSERT(sz >= old_size); + for (unsigned i = old_size; i < sz; i++) { + TRACE("solver_bug", tout << "unmarking: " << m_forbidden_vars[i]->get_decl()->get_name() << "\n";); + m_forbidden.mark(m_forbidden_vars[i], false); + } + m_forbidden_vars.shrink(old_size); +} + +void asserted_formulas::reset() { + m_defined_names.reset(); + m_asserted_qhead = 0; + m_asserted_formulas.reset(); + m_asserted_formula_prs.reset(); + m_subst.reset(); + m_vars.reset(); + m_vars_qhead = 0; + m_forbidden.reset(); + m_forbidden_vars.reset(); + m_macro_manager.reset(); + m_bv_sharing.reset(); + m_inconsistent = false; +} + +void asserted_formulas::set_cancel_flag(bool f) { + m_user_rewriter.set_cancel(f); + m_cancel_flag = f; +} + +#ifdef Z3DEBUG +bool asserted_formulas::check_well_sorted() const { + for (unsigned i = 0; i < m_asserted_formulas.size(); i++) { + if (!is_well_sorted(m_manager, m_asserted_formulas.get(i))) return false; + } + return true; +} +#endif + +void asserted_formulas::reduce() { + if (inconsistent()) + return; + if (canceled()) { + return; + } + if (m_asserted_qhead == m_asserted_formulas.size()) + return; + if (!m_params.m_preprocess) + return; + + if (m_macro_manager.has_macros()) + expand_macros(); + TRACE("before_reduce", display(tout);); + IF_VERBOSE(10000, verbose_stream() << "total size: " << get_total_size() << "\n";); + CASSERT("well_sorted", check_well_sorted()); + +#define INVOKE(COND, FUNC) if (COND) { FUNC; IF_VERBOSE(10000, verbose_stream() << "total size: " << get_total_size() << "\n";); } TRACE("reduce_step_ll", ast_mark visited; display_ll(tout, visited);); TRACE("reduce_step", display(tout << #FUNC << " ");); CASSERT("well_sorted",check_well_sorted()); if (inconsistent() || canceled()) { TRACE("after_reduce", display(tout);); TRACE("after_reduce_ll", ast_mark visited; display_ll(tout, visited);); return; } + + set_eliminate_and(false); // do not eliminate and before nnf. + INVOKE(m_params.m_propagate_booleans, propagate_booleans()); + INVOKE(m_params.m_propagate_values, propagate_values()); + INVOKE(m_params.m_macro_finder && has_quantifiers(), find_macros()); + INVOKE((m_params.m_quant_elim && has_quantifiers()), quant_elim()); + INVOKE(m_params.m_nnf_cnf, nnf_cnf()); + INVOKE(m_params.m_context_simplifier, context_simplifier()); + INVOKE(m_params.m_strong_context_simplifier, strong_context_simplifier()); + INVOKE(m_params.m_eliminate_and, eliminate_and()); + INVOKE(m_params.m_pull_cheap_ite_trees, pull_cheap_ite_trees()); + INVOKE(m_params.m_pull_nested_quantifiers && has_quantifiers(), pull_nested_quantifiers()); + INVOKE(m_params.m_ng_lift_ite != LI_NONE, ng_lift_ite()); + INVOKE(m_params.m_lift_ite != LI_NONE, lift_ite()); + INVOKE(m_params.m_solver, solve()); + INVOKE(m_params.m_eliminate_term_ite && m_params.m_lift_ite != LI_FULL, eliminate_term_ite()); + INVOKE(m_params.m_refine_inj_axiom && has_quantifiers(), refine_inj_axiom()); + TRACE("der_bug", tout << "before DER:\n"; display(tout);); + INVOKE(m_params.m_der && has_quantifiers(), apply_der()); + TRACE("der_bug", tout << "after DER:\n"; display(tout);); + INVOKE(m_params.m_distribute_forall && has_quantifiers(), apply_distribute_forall()); + TRACE("qbv_bug", tout << "after distribute_forall:\n"; display(tout);); + INVOKE(m_params.m_macro_finder && has_quantifiers(), find_macros()); + TRACE("qbv_bug", tout << "before demod:\n"; display(tout);); + INVOKE(m_params.m_pre_demod && has_quantifiers(), apply_demodulators()); + TRACE("qbv_bug", tout << "after demod:\n"; display(tout);); + INVOKE(m_params.m_quasi_macros && has_quantifiers(), apply_quasi_macros()); + INVOKE(m_params.m_simplify_bit2int, apply_bit2int()); + INVOKE(m_user_rewriter.enabled(), apply_user_rewriter()); + INVOKE(m_params.m_eliminate_bounds && has_quantifiers(), cheap_quant_fourier_motzkin()); + INVOKE(!m_params.m_bb_eager && has_quantifiers() && m_params.m_ematching, infer_patterns()); + INVOKE(m_params.m_max_bv_sharing && has_bv(), max_bv_sharing()); + INVOKE(m_params.m_bb_quantifiers, elim_bvs_from_quantifiers()); + INVOKE(m_params.m_bb_eager, apply_eager_bit_blaster()); + INVOKE(m_params.m_bb_eager && m_params.m_nnf_cnf, nnf_cnf()); // bit-blaster destroys CNF + INVOKE(m_params.m_bb_quantifiers && m_params.m_der && has_quantifiers(), apply_der()); // bit-vector elimination + bit-blasting creates new opportunities for der. + INVOKE(m_params.m_bb_eager && has_quantifiers() && m_params.m_ematching, infer_patterns()); + // temporary HACK: make sure that arith & bv are list-assoc + // this may destroy some simplification steps such as max_bv_sharing + reduce_asserted_formulas(); + + CASSERT("well_sorted",check_well_sorted()); + + IF_VERBOSE(10, verbose_stream() << "simplifier done.\n";); + TRACE("after_reduce", display(tout);); + TRACE("after_reduce_ll", ast_mark visited; display_ll(tout, visited);); + TRACE("macros", m_macro_manager.display(tout);); + flush_cache(); +} + +void asserted_formulas::eliminate_and() { + IF_IVERBOSE(10, verbose_stream() << "eliminating and...\n";); + set_eliminate_and(true); + reduce_asserted_formulas(); + TRACE("after_elim_and", display(tout);); +} + +bool asserted_formulas::trivial_solve(expr * lhs, expr * rhs, app_ref & var, expr_ref & subst, proof_ref& pr) { + if (is_uninterp_const(lhs) && !m_forbidden.is_marked(lhs)) { + var = to_app(lhs); + subst = rhs; + if (m_manager.proofs_enabled()) { + app* n = m_manager.mk_eq(lhs,rhs); + pr = m_manager.mk_reflexivity(m_manager.mk_iff(n,n)); + } + TRACE("solve_bug", + tout << "trivial solve " << + mk_pp(var, m_manager) << " |-> " << + mk_pp(subst, m_manager) << "\n";); + return true; + } + else if (is_uninterp_const(rhs) && !m_forbidden.is_marked(rhs)) { + var = to_app(rhs); + subst = lhs; + if (m_manager.proofs_enabled()) { + app* m = m_manager.mk_eq(lhs,rhs); + pr = m_manager.mk_commutativity(m); + } + TRACE("solve_bug", + tout << "trivial solve " << + mk_pp(var, m_manager) << " |-> " << + mk_pp(subst, m_manager) << "\n";); + return true; + } + return false; +} + +bool asserted_formulas::is_pos_literal(expr * n) { + return is_app(n) && to_app(n)->get_num_args() == 0 && to_app(n)->get_family_id() == null_family_id; +} + +bool asserted_formulas::is_neg_literal(expr * n) { + if (m_manager.is_not(n)) + return is_pos_literal(to_app(n)->get_arg(0)); + return false; +} + +unsigned asserted_formulas::get_formulas_last_level() const { + if (m_scopes.empty()) { + return 0; + } + else { + return m_scopes.back().m_asserted_formulas_lim; + } +} + + +/** + \brief (ite x (= c1 y) (= c2 y)) where y is a constant. -> (= y (ite x c1 c2)) +*/ +bool asserted_formulas::solve_ite_definition_core(expr * lhs1, expr * rhs1, expr * lhs2, expr * rhs2, expr * cond, app_ref & var, expr_ref & subst) { + if (rhs1 == rhs2 && is_uninterp_const(rhs1) && !occurs(rhs1, cond) && !occurs(rhs1, lhs1) && !occurs(rhs1, lhs2)) { + var = to_app(rhs1); + m_bsimp->mk_ite(cond, lhs1, lhs2, subst); + return true; + } + return false; +} + +bool asserted_formulas::solve_ite_definition(expr * arg1, expr * arg2, expr * arg3, app_ref & var, expr_ref & subst) { + + if (!m_manager.is_eq(arg2) || !m_manager.is_eq(arg3)) + return false; + + app * app2 = to_app(arg2); + app * app3 = to_app(arg3); + expr * lhs1 = app2->get_arg(0); + expr * rhs1 = app2->get_arg(1); + expr * lhs2 = app3->get_arg(0); + expr * rhs2 = app3->get_arg(1); + + if (solve_ite_definition_core(lhs1, rhs1, lhs2, rhs2, arg1, var, subst)) + return true; + if (solve_ite_definition_core(rhs1, lhs1, lhs2, rhs2, arg1, var, subst)) + return true; + if (solve_ite_definition_core(lhs1, rhs1, rhs2, lhs2, arg1, var, subst)) + return true; + if (solve_ite_definition_core(rhs1, lhs1, rhs2, lhs2, arg1, var, subst)) + return true; + return false; +} + +bool asserted_formulas::solve_core(expr * n, app_ref & var, expr_ref & subst, proof_ref& pr) { + if (m_manager.is_eq(n)) { + // equality case + app * eq = to_app(n); + expr * lhs = eq->get_arg(0); + expr * rhs = eq->get_arg(1); + TRACE("solve_bug", tout << mk_bounded_pp(n, m_manager) << "\n" << mk_bounded_pp(lhs, m_manager) << "\n" << mk_bounded_pp(rhs, m_manager) << "\n";); + if (trivial_solve(lhs, rhs, var, subst, pr)) { + return true; + } + else { + sort * s = m_manager.get_sort(lhs); + family_id fid = s->get_family_id(); + solver_plugin * p = m_solver_plugins.get_plugin(fid); + if (p != 0 && p->solve(lhs, rhs, m_forbidden, var, subst)) { + if (m_manager.proofs_enabled()) { + app* new_eq = m_manager.mk_eq(var,subst); + pr = m_manager.mk_th_lemma(p->get_family_id(), m_manager.mk_iff(n,new_eq),0,0); + } + TRACE("solve_bug", tout << "theory solve\n";); + return true; + } + } + return false; + } + else if (m_manager.is_iff(n)) { + // <=> case + app * iff = to_app(n); + expr * lhs = iff->get_arg(0); + expr * rhs = iff->get_arg(1); + if (trivial_solve(lhs, rhs, var, subst, pr)) { + return true; + } + return false; + } + else { + if (m_manager.is_ite(n)) { + // + // (ite x (= c1 y) (= c2 y)) where y is a constant. -> (= y (ite x c1 c2)) + // + app * ite = to_app(n); + if (solve_ite_definition(ite->get_arg(0), ite->get_arg(1), ite->get_arg(2), var, subst)) { + if (m_manager.proofs_enabled()) { + pr = m_manager.mk_rewrite(n, m_manager.mk_eq(var, subst)); + } + return true; + } + } + + // check if literal + expr * lit = n; + if (is_pos_literal(lit)) { + var = to_app(lit); + subst = m_manager.mk_true(); + if (m_manager.proofs_enabled()) { + // [rewrite]: (iff (iff l true) l) + // [symmetry T1]: (iff l (iff l true)) + pr = m_manager.mk_rewrite(m_manager.mk_eq(var, subst), n); + pr = m_manager.mk_symmetry(pr); + } + + return true; + } + else if (is_neg_literal(lit)) { + var = to_app(to_app(lit)->get_arg(0)); + subst = m_manager.mk_false(); + if (m_manager.proofs_enabled()) { + // [rewrite]: (iff (iff l false) ~l) + // [symmetry T1]: (iff ~l (iff l false)) + pr = m_manager.mk_rewrite(m_manager.mk_eq(var, subst), n); + pr = m_manager.mk_symmetry(pr); + } + + return true; + } + } + return false; +} + +void asserted_formulas::collect_static_features() { + if (m_params.m_display_features) { + unsigned sz = m_asserted_formulas.size(); + unsigned head = m_asserted_qhead; + while (head < sz) { + expr * f = m_asserted_formulas.get(head); + head++; + m_static_features.collect(f); + } + m_static_features.display_primitive(std::cout); + m_static_features.display(std::cout); + } +} + +void asserted_formulas::display(std::ostream & out) const { + out << "asserted formulas:\n"; + for (unsigned i = 0; i < m_asserted_formulas.size(); i++) { + if (i == m_asserted_qhead) + out << "[HEAD] ==>\n"; + out << mk_ismt2_pp(m_asserted_formulas.get(i), m_manager) << "\n"; + } + out << "inconsistent: " << inconsistent() << "\n"; +} + +void asserted_formulas::display_ll(std::ostream & out, ast_mark & pp_visited) const { + if (!m_asserted_formulas.empty()) { + unsigned sz = m_asserted_formulas.size(); + for (unsigned i = 0; i < sz; i++) + ast_def_ll_pp(out, m_manager, m_asserted_formulas.get(i), pp_visited, true, false); + out << "asserted formulas:\n"; + for (unsigned i = 0; i < sz; i++) + out << "#" << m_asserted_formulas[i]->get_id() << " "; + out << "\n"; + } +} + +void asserted_formulas::collect_statistics(statistics & st) const { + m_quant_elim.collect_statistics(st); +} + + +/** + \brief Functor used to order solved equations x = t, in a way they can be solved + efficiently. +*/ +class top_sort { + enum color { White, Grey, Black }; + ast_manager & m_manager; + family_id m_bfid; + + expr_map * m_candidate_map; // Set of candidate substitutions var -> ast + obj_map m_var2idx; // var -> index in vars; + ptr_vector * m_ordered_vars; // Result1: set of variables ordered for applying substitution efficiently. + unsigned_vector * m_failed_idxs; // Result2: indices of substitutions that cannot be applied. + + svector m_colors; + ptr_vector m_todo; + + expr * get_candidate_def(expr * n) const { + if (is_app(n) && to_app(n)->get_num_args() == 0 && m_candidate_map->contains(n)) { + expr * d = 0; + proof * p = 0; + m_candidate_map->get(n, d, p); + SASSERT(d); + return d; + } + return 0; + } + + bool is_candidate(expr * n) const { + return get_candidate_def(n) != 0; + } + + void remove_candidate(app * n) { + TRACE("solve", tout << "removing candidate #" << n->get_id() << " " << mk_bounded_pp(n, m_manager) << "\n";); + unsigned idx = UINT_MAX; + m_var2idx.find(n, idx); + SASSERT(idx != UINT_MAX); + m_candidate_map->erase(n); + m_failed_idxs->push_back(idx); + } + + color get_color(expr * n) const { + return m_colors.get(n->get_id(), White); + } + + void set_color(expr * n, color c) { + unsigned id = n->get_id(); + m_colors.reserve(id+1, White); + m_colors[id] = c; + if (c == Black && is_candidate(n)) + m_ordered_vars->push_back(to_app(n)); + } + + void main_loop(app * n) { + m_todo.push_back(n); + expr * def; + while (!m_todo.empty()) { + expr * n = m_todo.back(); + switch (get_color(n)) { + case Black: + m_todo.pop_back(); + break; + case White: + set_color(n, Grey); + if (visit_children(n)) { + set_color(n, Black); + } + break; + case Grey: + if (all_black_children(n)) { + set_color(n, Black); + } + else { + def = get_candidate_def(n); + if (def) { + // Break loop + remove_candidate(to_app(n)); + set_color(n, Black); + } + // there is another occurrence of n on the stack + SASSERT(std::find(m_todo.begin(), m_todo.end() - 1, n) != m_todo.end()); + } + m_todo.pop_back(); + } + } + } + + void visit(expr * n, bool & visited) { + if (get_color(n) != Black) { + m_todo.push_back(n); + visited = false; + } + } + + bool visit_children(expr * n) { + bool visited = true; + unsigned j; + expr * def; + switch (n->get_kind()) { + case AST_VAR: + break; + case AST_APP: + j = to_app(n)->get_num_args(); + if (j == 0) { + def = get_candidate_def(n); + if (def) + visit(def, visited); + } + else { + while (j > 0) { + --j; + visit(to_app(n)->get_arg(j), visited); + } + } + break; + case AST_QUANTIFIER: + visit(to_quantifier(n)->get_expr(), visited); + break; + default: + UNREACHABLE(); + } + return visited; + } + + bool is_black(expr * n) const { + return get_color(n) == Black; + } + + bool all_black_children(expr * n) const { + expr * def; + unsigned j; + switch (n->get_kind()) { + case AST_VAR: + return true; + case AST_APP: + j = to_app(n)->get_num_args(); + if (j == 0) { + def = get_candidate_def(n); + if (def) + return is_black(def); + return true; + } + else { + while (j > 0) { + --j; + if (!is_black(to_app(n)->get_arg(j))) { + return false; + } + } + } + return true; + case AST_QUANTIFIER: + return is_black(to_quantifier(n)->get_expr()); + default: + UNREACHABLE(); + return true; + } + } + +public: + top_sort(ast_manager & m):m_manager(m), m_bfid(m.get_basic_family_id()) {} + + void operator()(ptr_vector const & vars, + expr_map & candidates, + ptr_vector & ordered_vars, + unsigned_vector & failed_idxs) { + m_var2idx.reset(); + ptr_vector::const_iterator it = vars.begin(); + ptr_vector::const_iterator end = vars.end(); + for (unsigned idx = 0; it != end; ++it, ++idx) + m_var2idx.insert(*it, idx); + m_candidate_map = &candidates; + m_ordered_vars = &ordered_vars; + m_failed_idxs = &failed_idxs; + m_colors.reset(); + it = vars.begin(); + end = vars.end(); + for (; it != end; ++it) { + TRACE("top_sort", tout << "processing: " << (*it)->get_decl()->get_name() << "\n";); + main_loop(*it); + } + } +}; + +void asserted_formulas::get_ordered_subst_vars(ptr_vector & ordered_vars) { + top_sort sort(m_manager); + unsigned_vector failed_idxs; + sort(m_vars, m_subst, ordered_vars, failed_idxs); + SASSERT(failed_idxs.empty()); +} + +bool asserted_formulas::solve_core() { + flush_cache(); + + expr_map tmp_subst(m_manager); + ptr_vector tmp_vars; // domain of m_tmp_subst + expr_ref_vector candidates(m_manager); + proof_ref_vector candidate_prs(m_manager); + + IF_IVERBOSE(10, verbose_stream() << "solving...\n";); + bool has_subst = false; + app_ref var(m_manager); + expr_ref subst(m_manager); + proof_ref pr1(m_manager); + unsigned i = m_asserted_qhead; + unsigned j = i; + unsigned sz = m_asserted_formulas.size(); + for (; i < sz; i++) { + expr * n = m_asserted_formulas.get(i); + proof * pr = m_asserted_formula_prs.get(i, 0); + TRACE("solve", tout << "processing... #" << n->get_id() << "\n";); + TRACE("solve", tout << mk_bounded_pp(n, m_manager, 3) << "\n"; + if (pr) tout << mk_bounded_pp(pr, m_manager, 3) << "\n";); + + if (solve_core(n, var, subst, pr1) && !m_forbidden.is_marked(var)) { + if (m_manager.proofs_enabled()) { + // TODO: refine potentially useless rewrite step + if (m_manager.is_eq(n) && to_app(n)->get_arg(0) == var && + to_app(n)->get_arg(1) == subst) { + // skip useless rewrite step. + } + else { + TRACE("solve", tout << mk_bounded_pp(n, m_manager, 3) << "\n"; + tout << mk_bounded_pp(pr1.get(), m_manager, 5) << "\n";); + pr = m_manager.mk_modus_ponens(pr, pr1.get()); + } + candidate_prs.push_back(pr); + } + + tmp_subst.insert(var, subst, pr); + SASSERT(!m_forbidden.is_marked(var)); + TRACE("solve_subst", tout << mk_pp(var, m_manager) << "\n" << mk_pp(subst, m_manager) << "\n";); + TRACE("solver_bug", tout << mk_pp(var, m_manager) << "\n" << mk_pp(subst, m_manager) << "\n";); + tmp_vars.push_back(var); + m_forbidden.mark(var, true); + candidates.push_back(n); + has_subst = true; + continue; + } + if (j < i) { + m_asserted_formulas.set(j, n); + if (m_manager.proofs_enabled()) + m_asserted_formula_prs.set(j, pr); + } + j++; + } + m_asserted_formulas.shrink(j); + if (m_manager.proofs_enabled()) + m_asserted_formula_prs.shrink(j); + + if (!has_subst) + return false; + + ptr_vector ordered_vars; + unsigned_vector failed_idxs; + top_sort sort(m_manager); + sort(tmp_vars, tmp_subst, ordered_vars, failed_idxs); + // restore substitutions that cannot be applied due to loops. + unsigned_vector::iterator it = failed_idxs.begin(); + unsigned_vector::iterator end = failed_idxs.end(); + for (; it != end; ++it) { + unsigned idx = *it; + m_asserted_formulas.push_back(candidates.get(idx)); + if (m_manager.proofs_enabled()) + m_asserted_formula_prs.push_back(candidate_prs.get(idx)); + app * var = tmp_vars[idx]; + m_forbidden.mark(var, false); + } + IF_IVERBOSE(10, verbose_stream() << "num. eliminated vars: " << ordered_vars.size() << "\n";); + ptr_vector::iterator it2 = ordered_vars.begin(); + ptr_vector::iterator end2 = ordered_vars.end(); + for (; it2 != end2; ++it2) { + app * var = *it2; + TRACE("solve_res", tout << "var: " << mk_pp(var, m_manager) << "\n";); + expr * def = 0; + proof * pr = 0; + tmp_subst.get(var, def, pr); + SASSERT(def != 0); + SASSERT(m_forbidden.is_marked(var)); + m_forbidden.mark(var, false); + expr_ref new_def(m_manager); + proof_ref def_eq_new_def_pr(m_manager); + proof_ref new_pr(m_manager); + TRACE("solve_res", tout << "reducing:\n" << mk_ll_pp(def, m_manager);); + m_simplifier(def, new_def, def_eq_new_def_pr); + TRACE("solve_res", tout << "reducing:\n" << mk_ll_pp(new_def, m_manager);); + new_pr = m_manager.mk_transitivity(pr, def_eq_new_def_pr); + m_subst.insert(var, new_def, new_pr); + m_vars.push_back(var); + TRACE("solve_res", tout << "new substitution:\n" << mk_ll_pp(var, m_manager) << "======>\n" << mk_ll_pp(new_def, m_manager);); + } + return !ordered_vars.empty(); +} + +void asserted_formulas::solve() { + while (solve_core()) { + IF_IVERBOSE(10, verbose_stream() << "reducing...\n";); + flush_cache(); // collect garbage + reduce_asserted_formulas(); + } +} + +void asserted_formulas::reduce_asserted_formulas() { + if (inconsistent()) { + return; + } + expr_ref_vector new_exprs(m_manager); + proof_ref_vector new_prs(m_manager); + unsigned i = m_asserted_qhead; + unsigned sz = m_asserted_formulas.size(); + for (; i < sz && !inconsistent(); i++) { + expr * n = m_asserted_formulas.get(i); + SASSERT(n != 0); + proof * pr = m_asserted_formula_prs.get(i, 0); + expr_ref new_n(m_manager); + proof_ref new_pr(m_manager); + m_simplifier(n, new_n, new_pr); + TRACE("reduce_asserted_formulas", tout << mk_pp(n, m_manager) << " -> " << mk_pp(new_n, m_manager) << "\n";); + if (n == new_n.get()) { + push_assertion(n, pr, new_exprs, new_prs); + } + else { + new_pr = m_manager.mk_modus_ponens(pr, new_pr); + push_assertion(new_n, new_pr, new_exprs, new_prs); + } + if (canceled()) { + return; + } + } + swap_asserted_formulas(new_exprs, new_prs); +} + +void asserted_formulas::swap_asserted_formulas(expr_ref_vector & new_exprs, proof_ref_vector & new_prs) { + SASSERT(!inconsistent() || !new_exprs.empty()); + m_asserted_formulas.shrink(m_asserted_qhead); + m_asserted_formulas.append(new_exprs); + if (m_manager.proofs_enabled()) { + m_asserted_formula_prs.shrink(m_asserted_qhead); + m_asserted_formula_prs.append(new_prs); + } +} + +void asserted_formulas::find_macros_core() { + expr_ref_vector new_exprs(m_manager); + proof_ref_vector new_prs(m_manager); + unsigned sz = m_asserted_formulas.size(); + m_macro_finder->operator()(sz - m_asserted_qhead, m_asserted_formulas.c_ptr() + m_asserted_qhead, + m_asserted_formula_prs.c_ptr() + m_asserted_qhead, new_exprs, new_prs); + swap_asserted_formulas(new_exprs, new_prs); + reduce_and_solve(); +} + +void asserted_formulas::find_macros() { + IF_IVERBOSE(10, verbose_stream() << "trying to find macros...\n";); + TRACE("before_find_macros", display(tout);); + find_macros_core(); + TRACE("after_find_macros", display(tout);); +} + +void asserted_formulas::expand_macros() { + IF_IVERBOSE(10, verbose_stream() << "expanding macros...\n";); + find_macros_core(); +} + +void asserted_formulas::apply_demodulators() { + IF_IVERBOSE(10, verbose_stream() << "applying demodulators...\n";); + TRACE("before_apply_demodulators", display(tout);); + expr_ref_vector new_exprs(m_manager); + proof_ref_vector new_prs(m_manager); + unsigned sz = m_asserted_formulas.size(); + demodulator proc(m_manager, *m_bsimp); + proc(sz - m_asserted_qhead, + m_asserted_formulas.c_ptr() + m_asserted_qhead, + m_asserted_formula_prs.c_ptr() + m_asserted_qhead, + new_exprs, new_prs); + swap_asserted_formulas(new_exprs, new_prs); + TRACE("after_apply_demodulators", display(tout);); + reduce_and_solve(); +} + +void asserted_formulas::apply_quasi_macros() { + IF_IVERBOSE(10, verbose_stream() << "finding quasi macros...\n";); + TRACE("before_quasi_macros", display(tout);); + expr_ref_vector new_exprs(m_manager); + proof_ref_vector new_prs(m_manager); + quasi_macros proc(m_manager, m_macro_manager, *m_bsimp, m_simplifier); + while (proc(m_asserted_formulas.size() - m_asserted_qhead, + m_asserted_formulas.c_ptr() + m_asserted_qhead, + m_asserted_formula_prs.c_ptr() + m_asserted_qhead, + new_exprs, new_prs)) { + swap_asserted_formulas(new_exprs, new_prs); + new_exprs.reset(); + new_prs.reset(); + } + TRACE("after_quasi_macros", display(tout);); + reduce_and_solve(); +} + +void asserted_formulas::nnf_cnf() { + IF_IVERBOSE(10, verbose_stream() << "applying nnf&cnf...\n";); + nnf apply_nnf(m_manager, m_defined_names, m_params); + cnf apply_cnf(m_manager, m_defined_names, m_params); + expr_ref_vector new_exprs(m_manager); + proof_ref_vector new_prs(m_manager); + expr_ref_vector cnf_todo(m_manager); + proof_ref_vector cnf_todo_prs(m_manager); + expr_ref_vector push_todo(m_manager); + proof_ref_vector push_todo_prs(m_manager); + + unsigned i = m_asserted_qhead; + unsigned sz = m_asserted_formulas.size(); + TRACE("nnf_bug", tout << "i: " << i << " sz: " << sz << "\n";); + for (; i < sz; i++) { + expr * n = m_asserted_formulas.get(i); + TRACE("nnf_bug", tout << "processing:\n" << mk_pp(n, m_manager) << "\n";); + proof * pr = m_asserted_formula_prs.get(i, 0); + cnf_todo.reset(); + cnf_todo_prs.reset(); + expr_ref r1(m_manager); + proof_ref pr1(m_manager); + CASSERT("well_sorted",is_well_sorted(m_manager, n)); + apply_nnf(n, cnf_todo, cnf_todo_prs, r1, pr1); + CASSERT("well_sorted",is_well_sorted(m_manager, r1)); + pr = m_manager.mk_modus_ponens(pr, pr1); + cnf_todo.push_back(r1); + cnf_todo_prs.push_back(pr); + + if (canceled()) { + return; + } + + unsigned sz1 = cnf_todo.size(); + for (unsigned j = 0; j < sz1; j++) { + push_todo.reset(); + push_todo_prs.reset(); + + if (canceled()) { + return; + } + + expr * n = cnf_todo.get(j); + proof * pr = m_manager.proofs_enabled() ? cnf_todo_prs.get(j) : 0; + + CASSERT("well_sorted",is_well_sorted(m_manager, n)); + apply_cnf(n, push_todo, push_todo_prs, r1, pr1); + CASSERT("well_sorted",is_well_sorted(m_manager, r1)); + + push_todo.push_back(r1); + + if (m_manager.proofs_enabled()) { + pr = m_manager.mk_modus_ponens(pr, pr1); + push_todo_prs.push_back(pr); + } + + unsigned sz2 = push_todo.size(); + for (unsigned k = 0; k < sz2; k++) { + expr * n = push_todo.get(k); + proof * pr = 0; + m_simplifier(n, r1, pr1); + CASSERT("well_sorted",is_well_sorted(m_manager, r1)); + if (canceled()) { + return; + } + + if (m_manager.proofs_enabled()) + pr = m_manager.mk_modus_ponens(push_todo_prs.get(k), pr1); + else + pr = 0; + push_assertion(r1, pr, new_exprs, new_prs); + } + } + } + swap_asserted_formulas(new_exprs, new_prs); +} + +#define MK_SIMPLE_SIMPLIFIER(NAME, FUNCTOR_DEF, LABEL, MSG) \ +void asserted_formulas::NAME() { \ + IF_IVERBOSE(10, verbose_stream() << MSG << "...\n";); \ + TRACE(LABEL, tout << "before:\n"; display(tout);); \ + FUNCTOR_DEF; \ + expr_ref_vector new_exprs(m_manager); \ + proof_ref_vector new_prs(m_manager); \ + unsigned i = m_asserted_qhead; \ + unsigned sz = m_asserted_formulas.size(); \ + for (; i < sz; i++) { \ + expr * n = m_asserted_formulas.get(i); \ + proof * pr = m_asserted_formula_prs.get(i, 0); \ + expr_ref new_n(m_manager); \ + functor(n, new_n); \ + TRACE("simplifier_simple_step", tout << mk_pp(n, m_manager) << "\n" << mk_pp(new_n, m_manager) << "\n";); \ + if (n == new_n.get()) { \ + push_assertion(n, pr, new_exprs, new_prs); \ + } \ + else if (m_manager.proofs_enabled()) { \ + proof_ref new_pr(m_manager); \ + new_pr = m_manager.mk_rewrite_star(n, new_n, 0, 0); \ + new_pr = m_manager.mk_modus_ponens(pr, new_pr); \ + push_assertion(new_n, new_pr, new_exprs, new_prs); \ + } \ + else { \ + push_assertion(new_n, 0, new_exprs, new_prs); \ + } \ + } \ + swap_asserted_formulas(new_exprs, new_prs); \ + TRACE(LABEL, display(tout);); \ + reduce_and_solve(); \ + TRACE(LABEL, display(tout);); \ +} + +MK_SIMPLE_SIMPLIFIER(apply_distribute_forall, distribute_forall functor(m_manager, *m_bsimp), "distribute_forall", "distribute forall"); + +void asserted_formulas::reduce_and_solve() { + IF_IVERBOSE(10, verbose_stream() << "reducing...\n";); + flush_cache(); // collect garbage + reduce_asserted_formulas(); + if (m_params.m_solver) + solve(); +} + +void asserted_formulas::infer_patterns() { + IF_IVERBOSE(10, verbose_stream() << "pattern inference...\n";); + TRACE("before_pattern_inference", display(tout);); + pattern_inference infer(m_manager, m_params); + expr_ref_vector new_exprs(m_manager); + proof_ref_vector new_prs(m_manager); + unsigned i = m_asserted_qhead; + unsigned sz = m_asserted_formulas.size(); + for (; i < sz; i++) { + expr * n = m_asserted_formulas.get(i); + proof * pr = m_asserted_formula_prs.get(i, 0); + expr_ref new_n(m_manager); + proof_ref new_pr(m_manager); + infer(n, new_n, new_pr); + if (n == new_n.get()) { + push_assertion(n, pr, new_exprs, new_prs); + } + else if (m_manager.proofs_enabled()) { + new_pr = m_manager.mk_modus_ponens(pr, new_pr); + push_assertion(new_n, new_pr, new_exprs, new_prs); + } + else { + push_assertion(new_n, 0, new_exprs, new_prs); + } + } + swap_asserted_formulas(new_exprs, new_prs); + TRACE("after_pattern_inference", display(tout);); +} + +struct mark_forbidden_proc { + expr_mark & m_forbidden; + ptr_vector & m_forbidden_vars; + mark_forbidden_proc(expr_mark & f, ptr_vector & v):m_forbidden(f), m_forbidden_vars(v) {} + void operator()(var * n) {} + void operator()(quantifier * n) {} + void operator()(app * n) { + if (is_uninterp(n) && !m_forbidden.is_marked(n)) { + TRACE("solver_bug", tout << "marking: " << n->get_decl()->get_name() << "\n";); + m_forbidden.mark(n, true); + m_forbidden_vars.push_back(n); + SASSERT(m_forbidden.is_marked(n)); + } + } +}; + +void asserted_formulas::commit() { + expr_fast_mark1 uf_visited; // marks used for update_forbidden + mark_forbidden_proc p(m_forbidden, m_forbidden_vars); + unsigned sz = m_asserted_formulas.size(); + for (unsigned i = m_asserted_qhead; i < sz; i++) + quick_for_each_expr(p, uf_visited, m_asserted_formulas.get(i)); + + m_macro_manager.mark_forbidden(sz - m_asserted_qhead, m_asserted_formulas.c_ptr() + m_asserted_qhead); + + ptr_vector::const_iterator it2 = m_vars.begin() + m_vars_qhead; + ptr_vector::const_iterator end2 = m_vars.end(); + for (; it2 != end2; ++it2) { + app * var = *it2; + expr * def = get_subst(var); + m_forbidden.mark(var, true); + m_forbidden_vars.push_back(var); + quick_for_each_expr(p, uf_visited, def); + } + m_vars_qhead = m_vars.size(); + m_asserted_qhead = m_asserted_formulas.size(); +} + +void asserted_formulas::eliminate_term_ite() { + IF_IVERBOSE(10, verbose_stream() << "eliminating ite term...\n";); + TRACE("before_elim_term_ite", display(tout);); + elim_term_ite elim(m_manager, m_defined_names); + expr_ref_vector new_exprs(m_manager); + proof_ref_vector new_prs(m_manager); + unsigned i = m_asserted_qhead; + unsigned sz = m_asserted_formulas.size(); + for (; i < sz; i++) { + expr * n = m_asserted_formulas.get(i); + proof * pr = m_asserted_formula_prs.get(i, 0); + expr_ref new_n(m_manager); + proof_ref new_pr(m_manager); + elim(n, new_exprs, new_prs, new_n, new_pr); + SASSERT(new_n.get() != 0); + DEBUG_CODE({ + for (unsigned i = 0; i < new_exprs.size(); i++) { + SASSERT(new_exprs.get(i) != 0); + } + }); + if (n == new_n.get()) { + push_assertion(n, pr, new_exprs, new_prs); + } + else if (m_manager.proofs_enabled()) { + new_pr = m_manager.mk_modus_ponens(pr, new_pr); + push_assertion(new_n, new_pr, new_exprs, new_prs); + } + else { + push_assertion(new_n, 0, new_exprs, new_prs); + } + } + swap_asserted_formulas(new_exprs, new_prs); + TRACE("after_elim_term_ite", display(tout);); + reduce_and_solve(); + TRACE("after_elim_term_ite", display(tout);); +} + +void asserted_formulas::propagate_values() { + IF_IVERBOSE(10, verbose_stream() << "constant propagation...\n";); + TRACE("propagate_values", tout << "before:\n"; display(tout);); + flush_cache(); + bool found = false; + // Separate the formulas in two sets: C and R + // C is a set which contains formulas of the form + // { x = n }, where x is a variable and n a numberal. + // R contains the rest. + // + // - new_exprs1 is the set C + // - new_exprs2 is the set R + // + // The loop also updates the m_cache. It adds the entries x -> n to it. + expr_ref_vector new_exprs1(m_manager); + proof_ref_vector new_prs1(m_manager); + expr_ref_vector new_exprs2(m_manager); + proof_ref_vector new_prs2(m_manager); + unsigned i = m_asserted_qhead; + unsigned sz = m_asserted_formulas.size(); + for (; i < sz; i++) { + expr * n = m_asserted_formulas.get(i); + proof * pr = m_asserted_formula_prs.get(i, 0); + if (m_manager.is_eq(n)) { + expr * lhs = to_app(n)->get_arg(0); + expr * rhs = to_app(n)->get_arg(1); + if (m_manager.is_value(lhs) || m_manager.is_value(rhs)) { + if (m_manager.is_value(lhs)) + std::swap(lhs, rhs); + if (!m_manager.is_value(lhs) && !m_simplifier.is_cached(lhs)) { + new_exprs1.push_back(n); + if (m_manager.proofs_enabled()) + new_prs1.push_back(pr); + TRACE("propagate_values", tout << "found:\n" << mk_pp(lhs, m_manager) << "\n->\n" << mk_pp(rhs, m_manager) << "\n";); + m_simplifier.cache_result(lhs, rhs, pr); + found = true; + continue; + } + } + } + new_exprs2.push_back(n); + if (m_manager.proofs_enabled()) + new_prs2.push_back(pr); + } + TRACE("propagate_values", tout << "found: " << found << "\n";); + // If C is not empty, then reduce R using the updated simplifier cache with entries + // x -> n for each constraint 'x = n' in C. + if (found) { + unsigned sz = new_exprs2.size(); + for (unsigned i = 0; i < sz; i++) { + expr * n = new_exprs2.get(i); + proof * pr = new_prs2.get(i, 0); + expr_ref new_n(m_manager); + proof_ref new_pr(m_manager); + m_simplifier(n, new_n, new_pr); + if (n == new_n.get()) { + push_assertion(n, pr, new_exprs1, new_prs1); + } + else { + new_pr = m_manager.mk_modus_ponens(pr, new_pr); + push_assertion(new_n, new_pr, new_exprs1, new_prs1); + } + } + swap_asserted_formulas(new_exprs1, new_prs1); + // IMPORTANT: the cache MUST be flushed. This guarantees that all entries + // x->n will be removed from m_cache. If we don't do that, the next transformation + // may simplify constraints in C using these entries, and the variables x in C + // will be (silently) eliminated, and models produced by Z3 will not contain them. + flush_cache(); + } + TRACE("propagate_values", tout << "afer:\n"; display(tout);); +} + +void asserted_formulas::propagate_booleans() { + bool cont = true; + bool modified = false; + flush_cache(); + while (cont) { + TRACE("propagate_booleans", tout << "before:\n"; display(tout);); + IF_IVERBOSE(10, verbose_stream() << "boolean propagation...\n";); + cont = false; + unsigned i = m_asserted_qhead; + unsigned sz = m_asserted_formulas.size(); +#define PROCESS() { \ + expr * n = m_asserted_formulas.get(i); \ + proof * pr = m_asserted_formula_prs.get(i, 0); \ + expr_ref new_n(m_manager); \ + proof_ref new_pr(m_manager); \ + m_simplifier(n, new_n, new_pr); \ + m_asserted_formulas.set(i, new_n); \ + if (m_manager.proofs_enabled()) { \ + new_pr = m_manager.mk_modus_ponens(pr, new_pr); \ + m_asserted_formula_prs.set(i, new_pr); \ + } \ + if (n != new_n) { \ + cont = true; \ + modified = true; \ + } \ + if (m_manager.is_not(new_n)) \ + m_simplifier.cache_result(to_app(new_n)->get_arg(0), m_manager.mk_false(), m_manager.mk_iff_false(new_pr)); \ + else \ + m_simplifier.cache_result(new_n, m_manager.mk_true(), m_manager.mk_iff_true(new_pr)); \ + } + for (; i < sz; i++) { + PROCESS(); + } + flush_cache(); + TRACE("propagate_booleans", tout << "middle:\n"; display(tout);); + i = sz; + while (i > m_asserted_qhead) { + --i; + PROCESS(); + } + flush_cache(); + TRACE("propagate_booleans", tout << "after:\n"; display(tout);); + } + if (modified) + reduce_asserted_formulas(); +} + +#define MK_SIMPLIFIER(NAME, FUNCTOR, TAG, MSG, REDUCE) \ +bool asserted_formulas::NAME() { \ + IF_IVERBOSE(10, verbose_stream() << MSG << "...\n";); \ + TRACE(TAG, ast_mark visited; display_ll(tout, visited);); \ + FUNCTOR; \ + bool changed = false; \ + expr_ref_vector new_exprs(m_manager); \ + proof_ref_vector new_prs(m_manager); \ + unsigned i = m_asserted_qhead; \ + unsigned sz = m_asserted_formulas.size(); \ + for (; i < sz; i++) { \ + expr * n = m_asserted_formulas.get(i); \ + proof * pr = m_asserted_formula_prs.get(i, 0); \ + expr_ref new_n(m_manager); \ + proof_ref new_pr(m_manager); \ + functor(n, new_n, new_pr); \ + if (n == new_n.get()) { \ + push_assertion(n, pr, new_exprs, new_prs); \ + } \ + else if (m_manager.proofs_enabled()) { \ + changed = true; \ + if (!new_pr) new_pr = m_manager.mk_rewrite(n, new_n); \ + new_pr = m_manager.mk_modus_ponens(pr, new_pr); \ + push_assertion(new_n, new_pr, new_exprs, new_prs); \ + } \ + else { \ + changed = true; \ + push_assertion(new_n, 0, new_exprs, new_prs); \ + } \ + } \ + swap_asserted_formulas(new_exprs, new_prs); \ + TRACE(TAG, ast_mark visited; display_ll(tout, visited);); \ + if (changed && REDUCE) { \ + reduce_and_solve(); \ + TRACE(TAG, ast_mark visited; display_ll(tout, visited);); \ + } \ + return changed; \ +} + +MK_SIMPLIFIER(pull_cheap_ite_trees, pull_cheap_ite_tree_star functor(m_manager, m_simplifier), "pull_cheap_ite_trees", "pull cheap ite trees", false); + +MK_SIMPLIFIER(pull_nested_quantifiers, pull_nested_quant functor(m_manager), "pull_nested_quantifiers", "pull nested quantifiers", false); + +proof * asserted_formulas::get_inconsistency_proof() const { + if (!inconsistent()) + return 0; + if (!m_manager.proofs_enabled()) + return 0; + unsigned sz = m_asserted_formulas.size(); + for (unsigned i = 0; i < sz; i++) { + expr * f = m_asserted_formulas.get(i); + if (m_manager.is_false(f)) + return m_asserted_formula_prs.get(i); + } + UNREACHABLE(); + return 0; +} + +MK_SIMPLE_SIMPLIFIER(context_simplifier, expr_context_simplifier functor(m_manager), "context_simplifier", "context simplifier"); + +MK_SIMPLE_SIMPLIFIER(strong_context_simplifier, expr_strong_context_simplifier functor(m_params, m_manager), "strong_context_simplifier", "strong context simplifier"); + + +void asserted_formulas::refine_inj_axiom() { + IF_IVERBOSE(10, verbose_stream() << "refining injectivity...\n";); + TRACE("inj_axiom", display(tout);); + unsigned i = m_asserted_qhead; + unsigned sz = m_asserted_formulas.size(); + for (; i < sz; i++) { + expr * n = m_asserted_formulas.get(i); + proof * pr = m_asserted_formula_prs.get(i, 0); + expr_ref new_n(m_manager); + if (is_quantifier(n) && simplify_inj_axiom(m_manager, to_quantifier(n), new_n)) { + TRACE("inj_axiom", tout << "simplifying...\n" << mk_pp(n, m_manager) << "\n" << mk_pp(new_n, m_manager) << "\n";); + m_asserted_formulas.set(i, new_n); + if (m_manager.proofs_enabled()) { + proof_ref new_pr(m_manager); + new_pr = m_manager.mk_rewrite(n, new_n); + new_pr = m_manager.mk_modus_ponens(pr, new_pr); + m_asserted_formula_prs.set(i, new_pr); + } + } + } + TRACE("inj_axiom", display(tout);); +} + +MK_SIMPLIFIER(apply_bit2int, bit2int& functor = m_bit2int, "bit2int", "propagate bit-vector over integers", true); + +MK_SIMPLIFIER(apply_user_rewriter, user_rewriter& functor = m_user_rewriter, "user_rewriter", "apply user supplied rewriting", true); + +MK_SIMPLIFIER(apply_der_core, der_star functor(m_manager), "der", "destructive equality resolution", true); + +void asserted_formulas::apply_der() { + // Keep applying DER until it cannot be applied anymore. + // The simplifications applied by REDUCE may create new opportunities for applying DER. + while(!inconsistent() && apply_der_core()) { + } + + TRACE("a_der", for (unsigned i = 0; i + +Author: + + Leonardo de Moura (leonardo) 2008-06-11. + +Revision History: + +--*/ +#ifndef _ASSERTED_FORMULAS_H_ +#define _ASSERTED_FORMULAS_H_ + +#include"front_end_params.h" +#include"simplifier.h" +#include"basic_simplifier_plugin.h" +#include"static_features.h" +#include"macro_manager.h" +#include"macro_finder.h" +#include"defined_names.h" +#include"solver_plugin.h" +#include"maximise_ac_sharing.h" +#include"bit2int.h" +#include"qe.h" +#include"statistics.h" +#include"user_rewriter.h" + +class arith_simplifier_plugin; +class bv_simplifier_plugin; + +class asserted_formulas { + ast_manager & m_manager; + front_end_params & m_params; + simplifier m_pre_simplifier; + subst_simplifier m_simplifier; + basic_simplifier_plugin * m_bsimp; + bv_simplifier_plugin * m_bvsimp; + defined_names m_defined_names; + static_features m_static_features; + expr_ref_vector m_asserted_formulas; // formulas asserted by user + proof_ref_vector m_asserted_formula_prs; // proofs for the asserted formulas. + unsigned m_asserted_qhead; + + expr_map m_subst; + ptr_vector m_vars; // domain of m_subst + unsigned m_vars_qhead; + + expr_mark m_forbidden; + ptr_vector m_forbidden_vars; + + macro_manager m_macro_manager; + scoped_ptr m_macro_finder; + + typedef plugin_manager solver_plugins; + solver_plugins m_solver_plugins; + + bit2int m_bit2int; + + maximise_bv_sharing m_bv_sharing; + + user_rewriter m_user_rewriter; + + bool m_inconsistent; + qe::expr_quant_elim_star1 m_quant_elim; + + struct scope { + unsigned m_asserted_formulas_lim; + unsigned m_vars_lim; + unsigned m_forbidden_vars_lim; + bool m_inconsistent_old; + }; + svector m_scopes; + volatile bool m_cancel_flag; + + void setup_simplifier_plugins(simplifier & s, basic_simplifier_plugin * & bsimp, arith_simplifier_plugin * & asimp, bv_simplifier_plugin * & bvsimp); + bool trivial_solve(expr * lhs, expr * rhs, app_ref & var, expr_ref & subst, proof_ref& pr); + bool is_pos_literal(expr * n); + bool is_neg_literal(expr * n); + bool solve_ite_definition_core(expr * lhs1, expr * rhs1, expr * lhs2, expr * rhs2, expr * cond, app_ref & var, expr_ref & subst); + bool solve_ite_definition(expr * arg1, expr * arg2, expr * arg3, app_ref & var, expr_ref & subst); + bool solve_core(expr * n, app_ref & var, expr_ref & subst, proof_ref& pr); + bool solve_core(); + void solve(); + void reduce_asserted_formulas(); + void swap_asserted_formulas(expr_ref_vector & new_exprs, proof_ref_vector & new_prs); + void find_macros_core(); + void find_macros(); + void expand_macros(); + void apply_demodulators(); + void apply_quasi_macros(); + void nnf_cnf(); + bool apply_eager_bit_blaster(); + bool apply_user_rewriter(); + void infer_patterns(); + void eliminate_term_ite(); + void reduce_and_solve(); + void flush_cache() { m_pre_simplifier.reset(); m_simplifier.reset(); } + void restore_subst(unsigned old_size); + void restore_forbidden_vars(unsigned old_size); + void set_eliminate_and(bool flag); + void propagate_values(); + void propagate_booleans(); + bool pull_cheap_ite_trees(); + bool pull_nested_quantifiers(); + void push_assertion(expr * e, proof * pr, expr_ref_vector & result, proof_ref_vector & result_prs); + void context_simplifier(); + void strong_context_simplifier(); + void eliminate_and(); + void refine_inj_axiom(); + bool cheap_quant_fourier_motzkin(); + bool quant_elim(); + bool apply_der_core(); + void apply_der(); + void apply_distribute_forall(); + bool apply_bit2int(); + void lift_ite(); + bool elim_bvs_from_quantifiers(); + void ng_lift_ite(); +#ifdef Z3DEBUG + bool check_well_sorted() const; +#endif + unsigned get_total_size() const; + bool has_bv() const; + void max_bv_sharing(); + bool canceled() { return m_cancel_flag; } + +public: + asserted_formulas(ast_manager & m, front_end_params & p); + ~asserted_formulas(); + + void setup(); + void assert_expr(expr * e, proof * in_pr); + void assert_expr(expr * e); + void reset(); + void set_cancel_flag(bool f); + void push_scope(); + void pop_scope(unsigned num_scopes); + bool inconsistent() const { return m_inconsistent; } + proof * get_inconsistency_proof() const; + void reduce(); + unsigned get_num_formulas() const { return m_asserted_formulas.size(); } + unsigned get_formulas_last_level() const; + unsigned get_qhead() const { return m_asserted_qhead; } + void commit(); + expr * get_formula(unsigned idx) const { return m_asserted_formulas.get(idx); } + proof * get_formula_proof(unsigned idx) const { return m_manager.proofs_enabled() ? m_asserted_formula_prs.get(idx) : 0; } + expr * const * get_formulas() const { return m_asserted_formulas.c_ptr(); } + proof * const * get_formula_proofs() const { return m_asserted_formula_prs.c_ptr(); } + void init(unsigned num_formulas, expr * const * formulas, proof * const * prs); + void register_simplifier_plugin(simplifier_plugin * p) { m_simplifier.register_plugin(p); } + simplifier & get_simplifier() { return m_simplifier; } + void set_user_rewriter(void* ctx, user_rewriter::fn* rw) { m_user_rewriter.set_rewriter(ctx, rw); } + void get_assertions(ptr_vector & result); + bool empty() const { return m_asserted_formulas.empty(); } + void collect_static_features(); + void display(std::ostream & out) const; + void display_ll(std::ostream & out, ast_mark & pp_visited) const; + void collect_statistics(statistics & st) const; + // TODO: improve precision of the following method. + bool has_quantifiers() const { return m_simplifier.visited_quantifier(); /* approximation */ } + + // ----------------------------------- + // + // Macros + // + // ----------------------------------- + unsigned get_num_macros() const { return m_macro_manager.get_num_macros(); } + unsigned get_first_macro_last_level() const { return m_macro_manager.get_first_macro_last_level(); } + func_decl * get_macro_func_decl(unsigned i) const { return m_macro_manager.get_macro_func_decl(i); } + func_decl * get_macro_interpretation(unsigned i, expr_ref & interp) const { return m_macro_manager.get_macro_interpretation(i, interp); } + quantifier * get_macro_quantifier(func_decl * f) const { return m_macro_manager.get_macro_quantifier(f); } + // auxiliary function used to create a logic context based on a model. + void insert_macro(func_decl * f, quantifier * m, proof * pr) { m_macro_manager.insert(f, m, pr); } + + // ----------------------------------- + // + // Eliminated vars + // + // ----------------------------------- + ptr_vector::const_iterator begin_subst_vars() const { return m_vars.begin(); } + ptr_vector::const_iterator end_subst_vars() const { return m_vars.end(); } + ptr_vector::const_iterator begin_subst_vars_last_level() const { + unsigned sidx = m_scopes.empty() ? 0 : m_scopes.back().m_vars_lim; + return m_vars.begin() + sidx; + } + expr * get_subst(app * var) { expr * def = 0; proof * pr; m_subst.get(var, def, pr); return def; } + bool is_subst(app * var) const { return m_subst.contains(var); } + void get_ordered_subst_vars(ptr_vector & ordered_vars); +}; + +#endif /* _ASSERTED_FORMULAS_H_ */ + diff --git a/lib/assertion_set.cpp b/lib/assertion_set.cpp new file mode 100644 index 000000000..0436f45dc --- /dev/null +++ b/lib/assertion_set.cpp @@ -0,0 +1,430 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + assertion_set.cpp + +Abstract: + + Assertion set. + +Author: + + Leonardo de Moura (leonardo) 2011-04-20 + +Revision History: + +--*/ +#include"assertion_set.h" +#include"cmd_context.h" +#include"ast_ll_pp.h" +#include"ast_smt2_pp.h" +#include"for_each_expr.h" + +void assertion_set::copy(assertion_set & target) const { + if (this == &target) + return; + m().copy(m_forms, target.m_forms); + m().copy(m_proofs, target.m_proofs); + target.m_inconsistent = m_inconsistent; +} + +void assertion_set::push_back(expr * f, proof * pr) { + if (m().is_true(f)) + return; + if (m().is_false(f)) { + m().del(m_forms); + m().del(m_proofs); + m_inconsistent = true; + } + else { + SASSERT(!m_inconsistent); + } + m().push_back(m_forms, f); + if (m().proofs_enabled()) + m().push_back(m_proofs, pr); +} + +void assertion_set::quick_process(bool save_first, expr * & f) { + if (!m().is_and(f) && !(m().is_not(f) && m().is_or(to_app(f)->get_arg(0)))) { + if (!save_first) { + push_back(f, 0); + } + return; + } + typedef std::pair expr_pol; + sbuffer todo; + todo.push_back(expr_pol(f, true)); + while (!todo.empty()) { + if (m_inconsistent) + return; + expr_pol p = todo.back(); + expr * curr = p.first; + bool pol = p.second; + todo.pop_back(); + if (pol && m().is_and(curr)) { + app * t = to_app(curr); + unsigned i = t->get_num_args(); + while (i > 0) { + --i; + todo.push_back(expr_pol(t->get_arg(i), true)); + } + } + else if (!pol && m().is_or(curr)) { + app * t = to_app(curr); + unsigned i = t->get_num_args(); + while (i > 0) { + --i; + todo.push_back(expr_pol(t->get_arg(i), false)); + } + } + else if (m().is_not(curr)) { + todo.push_back(expr_pol(to_app(curr)->get_arg(0), !pol)); + } + else { + if (!pol) + curr = m().mk_not(curr); + if (save_first) { + f = curr; + save_first = false; + } + else { + push_back(curr, 0); + } + } + } +} + +void assertion_set::process_and(bool save_first, app * f, proof * pr, expr_ref & out_f, proof_ref & out_pr) { + unsigned num = f->get_num_args(); + for (unsigned i = 0; i < num; i++) { + if (m_inconsistent) + return; + slow_process(save_first && i == 0, f->get_arg(i), m().mk_and_elim(pr, i), out_f, out_pr); + } +} + +void assertion_set::process_not_or(bool save_first, app * f, proof * pr, expr_ref & out_f, proof_ref & out_pr) { + unsigned num = f->get_num_args(); + for (unsigned i = 0; i < num; i++) { + if (m_inconsistent) + return; + expr * child = f->get_arg(i); + if (m().is_not(child)) { + expr * not_child = to_app(child)->get_arg(0); + slow_process(save_first && i == 0, not_child, m().mk_not_or_elim(pr, i), out_f, out_pr); + } + else { + expr_ref not_child(m()); + not_child = m().mk_not(child); + slow_process(save_first && i == 0, not_child, m().mk_not_or_elim(pr, i), out_f, out_pr); + } + } +} + +void assertion_set::slow_process(bool save_first, expr * f, proof * pr, expr_ref & out_f, proof_ref & out_pr) { + if (m().is_and(f)) + process_and(save_first, to_app(f), pr, out_f, out_pr); + else if (m().is_not(f) && m().is_or(to_app(f)->get_arg(0))) + process_not_or(save_first, to_app(to_app(f)->get_arg(0)), pr, out_f, out_pr); + else if (save_first) { + out_f = f; + out_pr = pr; + } + else { + push_back(f, pr); + } +} + +void assertion_set::slow_process(expr * f, proof * pr) { + expr_ref out_f(m()); + proof_ref out_pr(m()); + slow_process(false, f, pr, out_f, out_pr); +} + +void assertion_set::assert_expr(expr * f, proof * pr) { + SASSERT(m().proofs_enabled() == (pr != 0 && !m().is_undef_proof(pr))); + if (m_inconsistent) + return; + if (m().proofs_enabled()) + slow_process(f, pr); + else + quick_process(false, f); +} + +void assertion_set::update(unsigned i, expr * f, proof * pr) { + SASSERT(m().proofs_enabled() == (pr != 0 && !m().is_undef_proof(pr))); + if (m_inconsistent) + return; + if (m().proofs_enabled()) { + expr_ref out_f(m()); + proof_ref out_pr(m()); + slow_process(true, f, pr, out_f, out_pr); + if (!m_inconsistent) { + if (m().is_false(out_f)) { + push_back(out_f, out_pr); + } + else { + m().set(m_forms, i, out_f); + m().set(m_proofs, i, out_pr); + } + } + } + else { + quick_process(true, f); + if (!m_inconsistent) { + if (m().is_false(f)) + push_back(f, 0); + else + m().set(m_forms, i, f); + } + } +} + +void assertion_set::reset() { + m().del(m_forms); + m().del(m_proofs); + m_inconsistent = false; +} + +void assertion_set::display(cmd_context & ctx, std::ostream & out) const { + out << "(assertion-set"; + unsigned sz = size(); + for (unsigned i = 0; i < sz; i++) { + out << "\n "; + ctx.display(out, form(i), 2); + } + out << ")" << std::endl; +} + +void assertion_set::display(cmd_context & ctx) const { + display(ctx, ctx.regular_stream()); +} + +void assertion_set::display(std::ostream & out) const { + out << "(assertion-set"; + unsigned sz = size(); + for (unsigned i = 0; i < sz; i++) { + out << "\n "; + out << mk_ismt2_pp(form(i), m(), 2); + } + out << ")" << std::endl; +} + +void assertion_set::display_as_and(std::ostream & out) const { + ptr_buffer args; + unsigned sz = size(); + for (unsigned i = 0; i < sz; i++) + args.push_back(form(i)); + expr_ref tmp(m()); + tmp = m().mk_and(args.size(), args.c_ptr()); + out << mk_ismt2_pp(tmp, m()) << "\n"; +} + +void assertion_set::display_ll(std::ostream & out) const { + unsigned sz = size(); + for (unsigned i = 0; i < sz; i++) { + out << mk_ll_pp(form(i), m()) << "\n"; + } +} + +/** + \brief Assumes that the formula is already in CNF. +*/ +void assertion_set::display_dimacs(std::ostream & out) const { + obj_map expr2var; + unsigned num_vars = 0; + unsigned num_cls = size(); + for (unsigned i = 0; i < num_cls; i++) { + expr * f = form(i); + unsigned num_lits; + expr * const * lits; + if (m().is_or(f)) { + num_lits = to_app(f)->get_num_args(); + lits = to_app(f)->get_args(); + } + else { + num_lits = 1; + lits = &f; + } + for (unsigned j = 0; j < num_lits; j++) { + expr * l = lits[j]; + if (m().is_not(l)) + l = to_app(l)->get_arg(0); + if (expr2var.contains(l)) + continue; + num_vars++; + expr2var.insert(l, num_vars); + } + } + out << "p cnf " << num_vars << " " << num_cls << "\n"; + for (unsigned i = 0; i < num_cls; i++) { + expr * f = form(i); + unsigned num_lits; + expr * const * lits; + if (m().is_or(f)) { + num_lits = to_app(f)->get_num_args(); + lits = to_app(f)->get_args(); + } + else { + num_lits = 1; + lits = &f; + } + for (unsigned j = 0; j < num_lits; j++) { + expr * l = lits[j]; + if (m().is_not(l)) { + out << "-"; + l = to_app(l)->get_arg(0); + } + unsigned id = UINT_MAX; + expr2var.find(l, id); + SASSERT(id != UINT_MAX); + out << id << " "; + } + out << "0\n"; + } +} + +unsigned assertion_set::num_exprs() const { + expr_fast_mark1 visited; + unsigned sz = size(); + unsigned r = 0; + for (unsigned i = 0; i < sz; i++) { + r += get_num_exprs(form(i), visited); + } + return r; +} + +/** + \brief Eliminate true formulas. +*/ +void assertion_set::elim_true() { + unsigned sz = size(); + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + expr * f = form(i); + if (m().is_true(f)) + continue; + if (i == j) { + j++; + continue; + } + m().set(m_forms, j, f); + if (m().proofs_enabled()) + m().set(m_proofs, j, m().get(m_proofs, i)); + j++; + } + for (; j < sz; j++) { + m().pop_back(m_forms); + if (m().proofs_enabled()) + m().pop_back(m_proofs); + } +} + +void assertion_set::elim_redundancies() { + if (inconsistent()) + return; + expr_ref_fast_mark1 neg_lits(m()); + expr_ref_fast_mark2 pos_lits(m()); + unsigned sz = size(); + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + expr * f = form(i); + if (m().is_true(f)) + continue; + if (m().is_not(f)) { + expr * atom = to_app(f)->get_arg(0); + if (neg_lits.is_marked(atom)) + continue; + if (pos_lits.is_marked(atom)) { + proof * p = 0; + if (m().proofs_enabled()) { + proof * pr1 = 0; + proof * pr2 = pr(i); + for (unsigned j = 0; j < i; j++) { + if (form(j) == atom) { + pr1 = pr(j); + break; + } + } + SASSERT(pr1); + proof * prs[2] = { pr1, pr2 }; + p = m().mk_unit_resolution(2, prs); + } + push_back(m().mk_false(), p); + return; + } + neg_lits.mark(atom); + } + else { + if (pos_lits.is_marked(f)) + continue; + if (neg_lits.is_marked(f)) { + proof * p = 0; + if (m().proofs_enabled()) { + proof * pr1 = 0; + proof * pr2 = pr(i); + for (unsigned j = 0; j < i; j++) { + expr * curr = form(j); + expr * atom; + if (m().is_not(curr, atom) && atom == f) { + pr1 = pr(j); + break; + } + } + SASSERT(pr1); + proof * prs[2] = { pr1, pr2 }; + p = m().mk_unit_resolution(2, prs); + } + push_back(m().mk_false(), p); + return; + } + pos_lits.mark(f); + } + if (i == j) { + j++; + continue; + } + m().set(m_forms, j, f); + if (m().proofs_enabled()) + m().set(m_proofs, j, pr(i)); + j++; + } + + for (; j < sz; j++) { + m().pop_back(m_forms); + if (m().proofs_enabled()) + m().pop_back(m_proofs); + } +} + +/** + \brief Assert expressions from ctx into t. +*/ +void assert_exprs_from(cmd_context const & ctx, assertion_set & t) { + ptr_vector::const_iterator it = ctx.begin_assertions(); + ptr_vector::const_iterator end = ctx.end_assertions(); + for (; it != end; ++it) { + t.assert_expr(*it); + } +} + +/** + \brief Translate the assertion set to a new one that uses a different ast_manager. +*/ +assertion_set * assertion_set::translate(ast_translation & translator) const { + ast_manager & m_to = translator.to(); + assertion_set * res = alloc(assertion_set, m_to); + + unsigned sz = m().size(m_forms); + for (unsigned i = 0; i < sz; i++) { + res->m().push_back(res->m_forms, translator(m().get(m_forms, i))); + if (m_to.proofs_enabled()) + res->m().push_back(res->m_proofs, translator(m().get(m_proofs, i))); + } + + res->m_inconsistent = m_inconsistent; + + return res; +} diff --git a/lib/assertion_set.h b/lib/assertion_set.h new file mode 100644 index 000000000..3fdbdbbe2 --- /dev/null +++ b/lib/assertion_set.h @@ -0,0 +1,98 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + assertion_set.h + +Abstract: + + Assertion set. + +Author: + + Leonardo de Moura (leonardo) 2011-04-20 + +Revision History: + +--*/ +#ifndef _ASSERTION_SET_H_ +#define _ASSERTION_SET_H_ + +#include"ast.h" +#include"ast_translation.h" +#include"for_each_expr.h" + +class cmd_context; + +class assertion_set { + ast_manager & m_manager; + bool m_inconsistent; + expr_array m_forms; + expr_array m_proofs; + + void push_back(expr * f, proof * pr); + void quick_process(bool save_first, expr * & f); + void process_and(bool save_first, app * f, proof * pr, expr_ref & out_f, proof_ref & out_pr); + void process_not_or(bool save_first, app * f, proof * pr, expr_ref & out_f, proof_ref & out_pr); + void slow_process(bool save_first, expr * f, proof * pr, expr_ref & out_f, proof_ref & out_pr); + void slow_process(expr * f, proof * pr); + +public: + assertion_set(ast_manager & m):m_manager(m), m_inconsistent(false) {} + ~assertion_set() { reset(); } + + ast_manager & m() const { return m_manager; } + + bool inconsistent() const { return m_inconsistent; } + + void reset(); + + void copy(assertion_set & target) const; + + void assert_expr(expr * f, proof * pr); + + void assert_expr(expr * f) { + assert_expr(f, m().mk_asserted(f)); + } + + unsigned size() const { return m().size(m_forms); } + + unsigned num_exprs() const; + + expr * form(unsigned i) const { return m().get(m_forms, i); } + + proof * pr(unsigned i) const { return m().proofs_enabled() ? static_cast(m().get(m_proofs, i)) : 0; } + + void update(unsigned i, expr * f, proof * pr = 0); + + void elim_true(); + + void elim_redundancies(); + + void display(cmd_context & ctx, std::ostream & out) const; + + void display(cmd_context & ctx) const; + + void display(std::ostream & out) const; + + void display_ll(std::ostream & out) const; + + void display_as_and(std::ostream & out) const; + + void display_dimacs(std::ostream & out) const; + + assertion_set * translate(ast_translation & translator) const; +}; + +void assert_exprs_from(cmd_context const & ctx, assertion_set & t); + +template +void for_each_expr_as(ForEachProc& proc, assertion_set const& s) { + expr_mark visited; + for (unsigned i = 0; i < s.size(); ++i) { + for_each_expr(proc, visited, s.form(i)); + } +} + +#endif diff --git a/lib/assertion_set.txt b/lib/assertion_set.txt new file mode 100644 index 000000000..b8d49f7e6 --- /dev/null +++ b/lib/assertion_set.txt @@ -0,0 +1,78 @@ + +The assertion_set class is essentially a set of formulas. +The assertion_set copy() method is constant time. They use persistent +arrays for "sharing" common parts. +The goal is to provide functors (strategies) that apply transformations to these sets. +Examples of transformations are: + - simplification + - gaussian elimination + - CNF, NNF, OCNF transformation + - bit-blasting + - abstraction + - grobner basis computation + - completion + - term rewriting + - bound propagation + - contextal simplification + - if-then-else lifting + - quantifier elimination + - etc + +A functor/strategy is essentially updating an assertion set. It may +also provide a model_converter to convert a model for the resultant +assertion_set into a model for the original assertion_set. See +gaussian_elim.cpp for an example of model_converter. A functor may +return a vector of assertion sets. It may also return a +proof_converter for converting proofs for the resultant assertion +set(s) into a proof for the original assertion_set. The +functor/strategy user is responsible for "plumbing" the +model_converter(s) and proof_converter(s). In the future we may have +tacticals for combining these functors/strategies. This is not needed +now. + +The idea is to provide assertion_set_solvers. They are essentially end-game +strategies. The abstract API for them is defined at assertion_set_solver.h. +Currently, the only assertion_set_solver is based on smt_context.h. +The next one will be based on a pure SAT solver. I also have plans for +one based on linear programming. + +The main goals of the new design are: + - increase modularity. + - expose assertion_set abstraction to external users. + - expose these functor/strategies to external users. + - allow us to combine problem specific solvers in a modular way. + - everything can be interrupted. + - some actions may be resumed. + - clean parallel_z3 implementation. + - support non-satisfiability preserving transformations. + the basic idea is: + * if the transformation is an over-approximation, + then the model_converter must throw an exception if it + can't convert the model into a model for the original assertion_set. + * if the transformation is an under-approximation, + then the proof_converter must throw an exception if it + can't convert the proof(s) into a proof for the original assertion_set. + I don't expect us to provide extensive support for proofs. + So, under-approximations will never really be used to show that + an assertion_set is unsatisfiable. + +Another goal is to a solver object that is essentially invoking an +external solver (process). I expect the external solver (in most cases) to be +Z3 itself. The main advantages are: the main process is safe from crashes, +and we can even invoke solvers in remote machines. + +The new functor/strategy design is not meant for incremental solving. +We may still have solvers that are incremental such as smt_context.h. + +Additional remarks: + +Failures and interruptions are reported using exceptions. +Each functor must provide a cancel() method that can be invoked to +interrupt it. A resume() method is optional. It is a must have if the +functor/strategy may be too time consuming. Each functor may have its +own set of parameters. See rewriter_params.h for an example. The +parameters may have options such as m_max_memory for blocking the +executiong of the functor. Each functor should have a cleanup() +method that must reclaim (almost) all memory consumed by the functor. + + diff --git a/lib/assertion_set2sat.cpp b/lib/assertion_set2sat.cpp new file mode 100644 index 000000000..11fc66228 --- /dev/null +++ b/lib/assertion_set2sat.cpp @@ -0,0 +1,717 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + assertion_set2sat.cpp + +Abstract: + + "Compile" an assertion set into the SAT engine. + Atoms are "abstracted" into boolean variables. + The mapping between boolean variables and atoms + can be used to convert back the state of the + SAT engine into an assertion set. + + The idea is to support scenarios such as: + 1) simplify, blast, convert into SAT, and solve + 2) convert into SAT, apply SAT for a while, learn new units, and translate back into an assertion set. + 3) convert into SAT, apply SAT preprocessor (failed literal propagation, resolution, etc) and translate back into an assertion set. + 4) Convert boolean structure into SAT, convert atoms into another engine, combine engines using lazy combination, solve. + +Author: + + Leonardo (leonardo) 2011-05-22 + +Notes: + +--*/ +#include"assertion_set2sat.h" +#include"ast_smt2_pp.h" +#include"ref_util.h" +#include"cooperate.h" +#include"filter_model_converter.h" +#include"model_evaluator.h" +#include"for_each_expr.h" +#include"model_v2_pp.h" +#include"assertion_set_util.h" + +struct assertion_set2sat::imp { + struct frame { + app * m_t; + unsigned m_root:1; + unsigned m_sign:1; + unsigned m_idx; + frame(app * t, bool r, bool s, unsigned idx): + m_t(t), m_root(r), m_sign(s), m_idx(idx) {} + }; + ast_manager & m; + svector m_frame_stack; + svector m_result_stack; + obj_map m_cache; + obj_hashtable m_interface_vars; + sat::solver & m_solver; + atom2bool_var & m_map; + sat::bool_var m_true; + bool m_ite_extra; + unsigned long long m_max_memory; + volatile bool m_cancel; + + imp(ast_manager & _m, params_ref const & p, sat::solver & s, atom2bool_var & map): + m(_m), + m_solver(s), + m_map(map) { + updt_params(p); + m_cancel = false; + m_true = sat::null_bool_var; + } + + void updt_params(params_ref const & p) { + m_ite_extra = p.get_bool(":ite-extra", true); + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + } + + void throw_op_not_handled() { + throw assertion_set2sat_exception("operator not supported, apply simplifier before invoking translator"); + } + + void mk_clause(sat::literal l) { + TRACE("assertion_set2sat", tout << "mk_clause: " << l << "\n";); + m_solver.mk_clause(1, &l); + } + + void mk_clause(sat::literal l1, sat::literal l2) { + TRACE("assertion_set2sat", tout << "mk_clause: " << l1 << " " << l2 << "\n";); + m_solver.mk_clause(l1, l2); + } + + void mk_clause(sat::literal l1, sat::literal l2, sat::literal l3) { + TRACE("assertion_set2sat", tout << "mk_clause: " << l1 << " " << l2 << " " << l3 << "\n";); + m_solver.mk_clause(l1, l2, l3); + } + + void mk_clause(unsigned num, sat::literal * lits) { + TRACE("assertion_set2sat", tout << "mk_clause: "; for (unsigned i = 0; i < num; i++) tout << lits[i] << " "; tout << "\n";); + m_solver.mk_clause(num, lits); + } + + sat::bool_var mk_true() { + // create fake variable to represent true; + if (m_true == sat::null_bool_var) { + m_true = m_solver.mk_var(); + mk_clause(sat::literal(m_true, false)); // v is true + } + return m_true; + } + + void convert_atom(expr * t, bool root, bool sign) { + SASSERT(m.is_bool(t)); + sat::literal l; + sat::bool_var v = m_map.to_bool_var(t); + if (v == sat::null_bool_var) { + if (m.is_true(t)) { + l = sat::literal(mk_true(), sign); + } + else if (m.is_false(t)) { + l = sat::literal(mk_true(), !sign); + } + else { + bool ext = !is_uninterp_const(t) || m_interface_vars.contains(t); + sat::bool_var v = m_solver.mk_var(ext); + m_map.insert(t, v); + l = sat::literal(v, sign); + TRACE("assertion_set2sat", tout << "new_var: " << v << "\n" << mk_ismt2_pp(t, m) << "\n";); + } + } + else { + SASSERT(v != sat::null_bool_var); + l = sat::literal(v, sign); + } + SASSERT(l != sat::null_literal); + if (root) + mk_clause(l); + else + m_result_stack.push_back(l); + } + + bool process_cached(app * t, bool root, bool sign) { + sat::literal l; + if (m_cache.find(t, l)) { + if (sign) + l.neg(); + if (root) + mk_clause(l); + else + m_result_stack.push_back(l); + return true; + } + return false; + } + + bool visit(expr * t, bool root, bool sign) { + if (!is_app(t)) { + convert_atom(t, root, sign); + return true; + } + if (process_cached(to_app(t), root, sign)) + return true; + if (to_app(t)->get_family_id() != m.get_basic_family_id()) { + convert_atom(t, root, sign); + return true; + } + switch (to_app(t)->get_decl_kind()) { + case OP_NOT: + case OP_OR: + case OP_IFF: + m_frame_stack.push_back(frame(to_app(t), root, sign, 0)); + return false; + case OP_ITE: + case OP_EQ: + if (m.is_bool(to_app(t)->get_arg(1))) { + m_frame_stack.push_back(frame(to_app(t), root, sign, 0)); + return false; + } + convert_atom(t, root, sign); + return true; + case OP_AND: + case OP_XOR: + case OP_IMPLIES: + case OP_DISTINCT: + TRACE("assertion_set2sat_not_handled", tout << mk_ismt2_pp(t, m) << "\n";); + throw_op_not_handled(); + default: + convert_atom(t, root, sign); + return true; + } + } + + void convert_or(app * t, bool root, bool sign) { + TRACE("assertion_set2sat", tout << "convert_or:\n" << mk_ismt2_pp(t, m) << "\n";); + unsigned num = t->get_num_args(); + if (root) { + SASSERT(num == m_result_stack.size()); + if (sign) { + // this case should not really happen. + for (unsigned i = 0; i < num; i++) { + sat::literal l = m_result_stack[i]; + l.neg(); + mk_clause(l); + } + } + else { + mk_clause(m_result_stack.size(), m_result_stack.c_ptr()); + m_result_stack.reset(); + } + } + else { + SASSERT(num <= m_result_stack.size()); + sat::bool_var k = m_solver.mk_var(); + sat::literal l(k, false); + m_cache.insert(t, l); + sat::literal * lits = m_result_stack.end() - num; + for (unsigned i = 0; i < num; i++) { + mk_clause(~lits[i], l); + } + m_result_stack.push_back(~l); + lits = m_result_stack.end() - num - 1; + // remark: mk_clause may perform destructive updated to lits. + // I have to execute it after the binary mk_clause above. + mk_clause(num+1, lits); + unsigned old_sz = m_result_stack.size() - num - 1; + m_result_stack.shrink(old_sz); + if (sign) + l.neg(); + m_result_stack.push_back(l); + } + } + + void convert_ite(app * n, bool root, bool sign) { + unsigned sz = m_result_stack.size(); + SASSERT(sz >= 3); + sat::literal c = m_result_stack[sz-3]; + sat::literal t = m_result_stack[sz-2]; + sat::literal e = m_result_stack[sz-1]; + if (root) { + SASSERT(sz == 3); + if (sign) { + mk_clause(~c, ~t); + mk_clause(c, ~e); + } + else { + mk_clause(~c, t); + mk_clause(c, e); + } + m_result_stack.reset(); + } + else { + sat::bool_var k = m_solver.mk_var(); + sat::literal l(k, false); + m_cache.insert(n, l); + mk_clause(~l, ~c, t); + mk_clause(~l, c, e); + mk_clause(l, ~c, ~t); + mk_clause(l, c, ~e); + if (m_ite_extra) { + mk_clause(~t, ~e, l); + mk_clause(t, e, ~l); + } + m_result_stack.shrink(sz-3); + if (sign) + l.neg(); + m_result_stack.push_back(l); + } + } + + void convert_iff(app * t, bool root, bool sign) { + TRACE("assertion_set2sat", tout << "convert_iff " << root << " " << sign << "\n" << mk_ismt2_pp(t, m) << "\n";); + unsigned sz = m_result_stack.size(); + SASSERT(sz >= 2); + sat::literal l1 = m_result_stack[sz-1]; + sat::literal l2 = m_result_stack[sz-2]; + if (root) { + SASSERT(sz == 2); + if (sign) { + mk_clause(l1, l2); + mk_clause(~l1, ~l2); + } + else { + mk_clause(l1, ~l2); + mk_clause(~l1, l2); + } + m_result_stack.reset(); + } + else { + sat::bool_var k = m_solver.mk_var(); + sat::literal l(k, false); + m_cache.insert(t, l); + mk_clause(~l, l1, ~l2); + mk_clause(~l, ~l1, l2); + mk_clause(l, l1, l2); + mk_clause(l, ~l1, ~l2); + m_result_stack.shrink(sz-2); + if (sign) + l.neg(); + m_result_stack.push_back(l); + } + } + + void convert(app * t, bool root, bool sign) { + SASSERT(t->get_family_id() == m.get_basic_family_id()); + switch (to_app(t)->get_decl_kind()) { + case OP_OR: + convert_or(t, root, sign); + break; + case OP_ITE: + convert_ite(t, root, sign); + break; + case OP_IFF: + case OP_EQ: + convert_iff(t, root, sign); + break; + default: + UNREACHABLE(); + } + } + + void process(expr * n) { + TRACE("assertion_set2sat", tout << "converting: " << mk_ismt2_pp(n, m) << "\n";); + if (visit(n, true, false)) { + SASSERT(m_result_stack.empty()); + return; + } + while (!m_frame_stack.empty()) { + loop: + cooperate("assertion_set2sat"); + if (m_cancel) + throw assertion_set2sat_exception(STE_CANCELED_MSG); + if (memory::get_allocation_size() > m_max_memory) + throw assertion_set2sat_exception(STE_MAX_MEMORY_MSG); + frame & fr = m_frame_stack.back(); + app * t = fr.m_t; + bool root = fr.m_root; + bool sign = fr.m_sign; + TRACE("assertion_set2sat_bug", tout << "result stack\n"; + tout << mk_ismt2_pp(t, m) << " root: " << root << " sign: " << sign << "\n"; + for (unsigned i = 0; i < m_result_stack.size(); i++) tout << m_result_stack[i] << " "; + tout << "\n";); + if (fr.m_idx == 0 && process_cached(t, root, sign)) { + m_frame_stack.pop_back(); + continue; + } + if (m.is_not(t)) { + m_frame_stack.pop_back(); + visit(t->get_arg(0), root, !sign); + continue; + } + unsigned num = t->get_num_args(); + while (fr.m_idx < num) { + expr * arg = t->get_arg(fr.m_idx); + fr.m_idx++; + if (!visit(arg, false, false)) + goto loop; + } + TRACE("assertion_set2sat_bug", tout << "converting\n"; + tout << mk_ismt2_pp(t, m) << " root: " << root << " sign: " << sign << "\n"; + for (unsigned i = 0; i < m_result_stack.size(); i++) tout << m_result_stack[i] << " "; + tout << "\n";); + convert(t, root, sign); + m_frame_stack.pop_back(); + } + SASSERT(m_result_stack.empty()); + } + + + void operator()(assertion_set const & s) { + m_interface_vars.reset(); + collect_boolean_interface(s, m_interface_vars); + + unsigned size = s.size(); + for (unsigned idx = 0; idx < size; idx++) { + expr * f = s.form(idx); + process(f); + } + } + + void operator()(unsigned sz, expr * const * fs) { + m_interface_vars.reset(); + collect_boolean_interface(m, sz, fs, m_interface_vars); + + for (unsigned i = 0; i < sz; i++) + process(fs[i]); + } + + void set_cancel(bool f) { m_cancel = f; } +}; + +struct unsupported_bool_proc { + struct found {}; + ast_manager & m; + unsupported_bool_proc(ast_manager & _m):m(_m) {} + void operator()(var *) {} + void operator()(quantifier *) {} + void operator()(app * n) { + if (n->get_family_id() == m.get_basic_family_id()) { + switch (n->get_decl_kind()) { + case OP_AND: + case OP_XOR: + case OP_IMPLIES: + case OP_DISTINCT: + throw found(); + default: + break; + } + } + } +}; + +/** + \brief Return true if s contains an unsupported Boolean operator. + assertion_set_rewriter (with the following configuration) can be used to + eliminate unsupported operators. + :elim-and true + :blast-distinct true +*/ +bool assertion_set2sat::has_unsupported_bool(assertion_set const & s) { + return test(s); +} + +assertion_set2sat::assertion_set2sat():m_imp(0) { +} + +void assertion_set2sat::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"); +} + +struct assertion_set2sat::scoped_set_imp { + assertion_set2sat * m_owner; + scoped_set_imp(assertion_set2sat * o, assertion_set2sat::imp * i):m_owner(o) { + #pragma omp critical (assertion_set2sat) + { + m_owner->m_imp = i; + } + } + ~scoped_set_imp() { + #pragma omp critical (assertion_set2sat) + { + m_owner->m_imp = 0; + } + } +}; + +void assertion_set2sat::operator()(assertion_set const & s, params_ref const & p, sat::solver & t, atom2bool_var & m) { + imp proc(s.m(), p, t, m); + scoped_set_imp set(this, &proc); + proc(s); +} + +void assertion_set2sat::operator()(ast_manager & mng, unsigned num, expr * const * fs, params_ref const & p, sat::solver & t, atom2bool_var & m) { + imp proc(mng, p, t, m); + scoped_set_imp set(this, &proc); + proc(num, fs); +} + +void assertion_set2sat::set_cancel(bool f) { + #pragma omp critical (assertion_set2sat) + { + if (m_imp) + m_imp->set_cancel(f); + } +} + +class sat_model_converter : public model_converter { + sat::model_converter m_mc; + // TODO: the following mapping is storing a lot of useless information, and may be a performance bottleneck. + // We need to save only the expressions associated with variables that occur in m_mc. + // This information may be stored as a vector of pairs. + // The mapping is only created during the model conversion. + expr_ref_vector m_var2expr; + ref m_fmc; // filter for eliminating fresh variables introduced in the assertion-set --> sat conversion + + sat_model_converter(ast_manager & m): + m_var2expr(m) { + } + +public: + sat_model_converter(ast_manager & m, sat::solver const & s):m_var2expr(m) { + m_mc.copy(s.get_model_converter()); + m_fmc = alloc(filter_model_converter, m); + } + + ast_manager & m() { return m_var2expr.get_manager(); } + + void insert(expr * atom, bool aux) { + m_var2expr.push_back(atom); + if (aux) { + SASSERT(is_uninterp_const(atom)); + SASSERT(m().is_bool(atom)); + m_fmc->insert(to_app(atom)->get_decl()); + } + } + + virtual void operator()(model_ref & md) { + TRACE("sat_mc", tout << "before sat_mc\n"; model_v2_pp(tout, *md); display(tout);); + // REMARK: potential problem + // model_evaluator can't evaluate quantifiers. Then, + // an eliminated variable that depends on a quantified expression can't be recovered. + // A similar problem also affects any model_converter that uses elim_var_model_converter. + // + // Possible solution: + // model_converters reject any variable elimination that depends on a quantified expression. + + model_evaluator ev(*md); + ev.set_model_completion(false); + + // create a SAT model using md + sat::model sat_md; + unsigned sz = m_var2expr.size(); + expr_ref val(m()); + for (sat::bool_var v = 0; v < sz; v++) { + expr * atom = m_var2expr.get(v); + ev(atom, val); + if (m().is_true(val)) + sat_md.push_back(l_true); + else if (m().is_false(val)) + sat_md.push_back(l_false); + else + sat_md.push_back(l_undef); + } + + // apply SAT model converter + m_mc(sat_md); + + // register value of non-auxiliary boolean variables back into md + sz = m_var2expr.size(); + for (sat::bool_var v = 0; v < sz; v++) { + expr * atom = m_var2expr.get(v); + if (is_uninterp_const(atom)) { + func_decl * d = to_app(atom)->get_decl(); + lbool new_val = sat_md[v]; + if (new_val == l_true) + md->register_decl(d, m().mk_true()); + else if (new_val == l_false) + md->register_decl(d, m().mk_false()); + } + } + + // apply filter model converter + (*m_fmc)(md); + TRACE("sat_mc", tout << "after sat_mc\n"; model_v2_pp(tout, *md);); + } + + virtual model_converter * translate(ast_translation & translator) { + sat_model_converter * res = alloc(sat_model_converter, translator.to()); + res->m_fmc = static_cast(m_fmc->translate(translator)); + unsigned sz = m_var2expr.size(); + for (unsigned i = 0; i < sz; i++) + res->m_var2expr.push_back(translator(m_var2expr.get(i))); + return res; + } + + void display(std::ostream & out) { + out << "(sat-model-converter\n"; + m_mc.display(out); + sat::bool_var_set vars; + m_mc.collect_vars(vars); + out << "(atoms"; + unsigned sz = m_var2expr.size(); + for (unsigned i = 0; i < sz; i++) { + if (vars.contains(i)) { + out << "\n (" << i << "\n " << mk_ismt2_pp(m_var2expr.get(i), m(), 2) << ")"; + } + } + out << ")\n"; + m_fmc->display(out); + out << ")\n"; + } +}; + +struct sat2assertion_set::imp { + ast_manager & m; + expr_ref_vector m_lit2expr; + unsigned long long m_max_memory; + bool m_learned; + bool m_produce_models; + volatile bool m_cancel; + + imp(ast_manager & _m, params_ref const & p):m(_m), m_lit2expr(m), m_cancel(false) { + updt_params(p); + } + + void updt_params(params_ref const & p) { + m_produce_models = p.get_bool(":produce-models", false); + m_learned = p.get_bool(":learned", false); + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + } + + void checkpoint() { + if (m_cancel) + throw sat2assertion_set_exception(STE_CANCELED_MSG); + if (memory::get_allocation_size() > m_max_memory) + throw sat2assertion_set_exception(STE_MAX_MEMORY_MSG); + } + + void init_lit2expr(sat::solver const & s, atom2bool_var const & map, model_converter_ref & mc) { + ref _mc; + if (m_produce_models) { + _mc = alloc(sat_model_converter, m, s); + } + unsigned num_vars = s.num_vars(); + m_lit2expr.resize(num_vars * 2); + map.mk_inv(m_lit2expr); + sort * b = m.mk_bool_sort(); + for (sat::bool_var v = 0; v < num_vars; v++) { + checkpoint(); + sat::literal l(v, false); + if (m_lit2expr.get(l.index()) == 0) { + SASSERT(m_lit2expr.get((~l).index()) == 0); + app * aux = m.mk_fresh_const(0, b); + if (_mc) + _mc->insert(aux, true); + m_lit2expr.set(l.index(), aux); + m_lit2expr.set((~l).index(), m.mk_not(aux)); + } + else { + if (_mc) + _mc->insert(m_lit2expr.get(l.index()), false); + SASSERT(m_lit2expr.get((~l).index()) != 0); + } + } + mc = _mc.get(); + } + + expr * lit2expr(sat::literal l) { + return m_lit2expr.get(l.index()); + } + + void assert_clauses(sat::clause * const * begin, sat::clause * const * end, assertion_set & r) { + ptr_buffer lits; + for (sat::clause * const * it = begin; it != end; it++) { + checkpoint(); + lits.reset(); + sat::clause const & c = *(*it); + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) { + lits.push_back(lit2expr(c[i])); + } + r.assert_expr(m.mk_or(lits.size(), lits.c_ptr())); + } + } + + void operator()(sat::solver const & s, atom2bool_var const & map, assertion_set & r, model_converter_ref & mc) { + if (s.inconsistent()) { + r.assert_expr(m.mk_false()); + return; + } + init_lit2expr(s, map, mc); + // collect units + unsigned num_vars = s.num_vars(); + for (sat::bool_var v = 0; v < num_vars; v++) { + checkpoint(); + switch (s.value(v)) { + case l_true: + r.assert_expr(lit2expr(sat::literal(v, false))); + break; + case l_false: + r.assert_expr(lit2expr(sat::literal(v, true))); + break; + case l_undef: + break; + } + } + // collect binary clauses + svector bin_clauses; + s.collect_bin_clauses(bin_clauses, m_learned); + svector::iterator it = bin_clauses.begin(); + svector::iterator end = bin_clauses.end(); + for (; it != end; ++it) { + checkpoint(); + r.assert_expr(m.mk_or(lit2expr(it->first), lit2expr(it->second))); + } + // collect clauses + assert_clauses(s.begin_clauses(), s.end_clauses(), r); + if (m_learned) + assert_clauses(s.begin_learned(), s.end_learned(), r); + } + + void set_cancel(bool f) { m_cancel = f; } +}; + +sat2assertion_set::sat2assertion_set():m_imp(0) { +} + +void sat2assertion_set::collect_param_descrs(param_descrs & r) { + insert_max_memory(r); + r.insert(":learned", CPK_BOOL, "(default: false) collect also learned clauses."); +} + +struct sat2assertion_set::scoped_set_imp { + sat2assertion_set * m_owner; + scoped_set_imp(sat2assertion_set * o, sat2assertion_set::imp * i):m_owner(o) { + #pragma omp critical (sat2assertion_set) + { + m_owner->m_imp = i; + } + } + ~scoped_set_imp() { + #pragma omp critical (sat2assertion_set) + { + m_owner->m_imp = 0; + } + } +}; + +void sat2assertion_set::operator()(sat::solver const & t, atom2bool_var const & m, params_ref const & p, + assertion_set & s, model_converter_ref & mc) { + imp proc(s.m(), p); + scoped_set_imp set(this, &proc); + proc(t, m, s, mc); +} + +void sat2assertion_set::set_cancel(bool f) { + #pragma omp critical (sat2assertion_set) + { + if (m_imp) + m_imp->set_cancel(f); + } +} diff --git a/lib/assertion_set2sat.h b/lib/assertion_set2sat.h new file mode 100644 index 000000000..b58f6d647 --- /dev/null +++ b/lib/assertion_set2sat.h @@ -0,0 +1,95 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + assertion_set2sat.h + +Abstract: + + "Compile" an assertion set into the SAT engine. + Atoms are "abstracted" into boolean variables. + The mapping between boolean variables and atoms + can be used to convert back the state of the + SAT engine into an assertion set. + + The idea is to support scenarios such as: + 1) simplify, blast, convert into SAT, and solve + 2) convert into SAT, apply SAT for a while, learn new units, and translate back into an assertion set. + 3) convert into SAT, apply SAT preprocessor (failed literal propagation, resolution, etc) and translate back into an assertion set. + 4) Convert boolean structure into SAT, convert atoms into another engine, combine engines using lazy combination, solve. + +Author: + + Leonardo (leonardo) 2011-05-22 + +Notes: + +--*/ +#ifndef _ASSERTION_SET2SAT_H_ +#define _ASSERTION_SET2SAT_H_ + +#include"assertion_set.h" +#include"sat_solver.h" +#include"strategy_exception.h" +#include"model_converter.h" +#include"atom2bool_var.h" + +MK_ST_EXCEPTION(assertion_set2sat_exception); + +class assertion_set2sat { + struct imp; + imp * m_imp; + struct scoped_set_imp; +public: + assertion_set2sat(); + + static void collect_param_descrs(param_descrs & r); + + static bool has_unsupported_bool(assertion_set const & s); + + /** + \brief "Compile" the assertion set into the given sat solver. + Store a mapping from atoms to boolean variables into m. + + \remark m doesn't need to be empty. the definitions there are + reused. + */ + void operator()(assertion_set const & s, params_ref const & p, sat::solver & t, atom2bool_var & m); + + /** + \brief "Compile" the formulas fs into the given sat solver. + Store a mapping from atoms to boolean variables into m. + + \remark m doesn't need to be empty. the definitions there are + reused. + */ + void operator()(ast_manager & mng, unsigned num, expr * const * fs, params_ref const & p, sat::solver & t, atom2bool_var & m); + + void operator()(ast_manager & mng, expr * f, params_ref const & p, sat::solver & t, atom2bool_var & m) { operator()(mng, 1, &f, p, t, m); } + + void set_cancel(bool f); +}; + +MK_ST_EXCEPTION(sat2assertion_set_exception); + +class sat2assertion_set { + struct imp; + imp * m_imp; + struct scoped_set_imp; +public: + sat2assertion_set(); + + static void collect_param_descrs(param_descrs & r); + + /** + \brief Translate the state of the SAT engine back into an assertion set. + The SAT solver may use variables that are not in \c m. The translator + creates fresh boolean AST variables for them. They are stored in fvars. + */ + void operator()(sat::solver const & t, atom2bool_var const & m, params_ref const & p, assertion_set & s, model_converter_ref & mc); + + void set_cancel(bool f); +}; + +#endif diff --git a/lib/assertion_set_rewriter.cpp b/lib/assertion_set_rewriter.cpp new file mode 100644 index 000000000..431739024 --- /dev/null +++ b/lib/assertion_set_rewriter.cpp @@ -0,0 +1,122 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + assertion_set_rewriter.cpp + +Abstract: + + Apply rewriting rules in an assertion set. + +Author: + + Leonardo (leonardo) 2011-04-27 + +Notes: + +--*/ +#include"assertion_set_rewriter.h" +#include"th_rewriter.h" +#include"ast_smt2_pp.h" +#include"assertion_set_util.h" + +struct assertion_set_rewriter::imp { + ast_manager & m_manager; + th_rewriter m_r; + unsigned m_num_steps; + + imp(ast_manager & m, params_ref const & p): + m_manager(m), + m_r(m, p), + m_num_steps(0) { + } + + ast_manager & m() const { return m_manager; } + + void set_cancel(bool f) { + m_r.set_cancel(f); + } + + void reset() { + m_r.reset(); + m_num_steps = 0; + } + + void operator()(assertion_set & s) { + SASSERT(is_well_sorted(s)); + as_st_report report("simplifier", s); + TRACE("before_simplifier", s.display(tout);); + m_num_steps = 0; + if (s.inconsistent()) + return; + expr_ref new_curr(m()); + proof_ref new_pr(m()); + unsigned size = s.size(); + for (unsigned idx = 0; idx < size; idx++) { + if (s.inconsistent()) + break; + expr * curr = s.form(idx); + m_r(curr, new_curr, new_pr); + m_num_steps += m_r.get_num_steps(); + if (m().proofs_enabled()) { + proof * pr = s.pr(idx); + new_pr = m().mk_modus_ponens(pr, new_pr); + } + s.update(idx, new_curr, new_pr); + } + TRACE("after_simplifier_bug", s.display(tout);); + s.elim_redundancies(); + TRACE("after_simplifier", s.display(tout);); + SASSERT(is_well_sorted(s)); + } + + unsigned get_num_steps() const { return m_num_steps; } +}; + +assertion_set_rewriter::assertion_set_rewriter(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); +} + +assertion_set_rewriter::~assertion_set_rewriter() { + dealloc(m_imp); +} + +void assertion_set_rewriter::updt_params(params_ref const & p) { + m_params = p; + m_imp->m_r.updt_params(p); +} + +void assertion_set_rewriter::get_param_descrs(param_descrs & r) { + th_rewriter::get_param_descrs(r); +} + +void assertion_set_rewriter::operator()(assertion_set & s) { + m_imp->operator()(s); +} + +void assertion_set_rewriter::set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); +} + +void assertion_set_rewriter::cleanup() { + ast_manager & m = m_imp->m(); + imp * d = m_imp; + #pragma omp critical (as_st_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (as_st_cancel) + { + m_imp = d; + } +} + +unsigned assertion_set_rewriter::get_num_steps() const { + return m_imp->get_num_steps(); +} + diff --git a/lib/assertion_set_rewriter.h b/lib/assertion_set_rewriter.h new file mode 100644 index 000000000..fd93db930 --- /dev/null +++ b/lib/assertion_set_rewriter.h @@ -0,0 +1,56 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + assertion_set_rewriter.h + +Abstract: + + Apply rewriting rules in an assertion set. + +Author: + + Leonardo (leonardo) 2011-04-22 + +Notes: + +--*/ +#ifndef _ASSERTION_SET_REWRITER_H_ +#define _ASSERTION_SET_REWRITER_H_ + +#include"assertion_set_strategy.h" + +class assertion_set_rewriter : public assertion_set_strategy { + struct imp; + imp * m_imp; + params_ref m_params; +public: + assertion_set_rewriter(ast_manager & m, params_ref const & ref = params_ref()); + virtual ~assertion_set_rewriter(); + + virtual void updt_params(params_ref const & p); + static void get_param_descrs(param_descrs & r); + virtual void collect_param_descrs(param_descrs & r) { get_param_descrs(r); } + + /** + \brief Apply rewriting/simplification rules on the assertion set \c s. + */ + void operator()(assertion_set & s); + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + operator()(s); + mc = 0; + } + + virtual void cleanup(); + + unsigned get_num_steps() const; + virtual void set_cancel(bool f); +}; + +inline as_st * mk_simplifier(ast_manager & m, params_ref const & p = params_ref()) { + return clean(alloc(assertion_set_rewriter, m, p)); +} + +#endif diff --git a/lib/assertion_set_strategy.cpp b/lib/assertion_set_strategy.cpp new file mode 100644 index 000000000..3ba1fd9c9 --- /dev/null +++ b/lib/assertion_set_strategy.cpp @@ -0,0 +1,1414 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + assertion_set_strategy.h + +Abstract: + + Abstract strategy for assertion sets, and simple combinators. + +Author: + + Leonardo (leonardo) 2011-05-17 + +Notes: + +--*/ +#include"assertion_set_strategy.h" +#include"assertion_set_util.h" +#include"cooperate.h" +#include"scoped_timer.h" +#include"cancel_eh.h" +#include"smt_solver.h" +#include"front_end_params.h" +#include"progress_callback.h" +#include"params2front_end_params.h" +#include"stopwatch.h" +#include"ast_translation.h" +#include"model_v2_pp.h" +#include +#include"z3_omp.h" + +struct as_st_report::imp { + char const * m_id; + assertion_set & m_set; + stopwatch m_watch; + double m_start_memory; + + imp(char const * id, assertion_set & s): + m_id(id), + m_set(s), + m_start_memory(static_cast(memory::get_allocation_size())/static_cast(1024*1024)) { + m_watch.start(); + } + + ~imp() { + m_watch.stop(); + double end_memory = static_cast(memory::get_allocation_size())/static_cast(1024*1024); + verbose_stream() << "(" << m_id + << " :num-exprs " << m_set.num_exprs() + << " :num-asts " << m_set.m().get_num_asts() + << " :time " << std::fixed << std::setprecision(2) << m_watch.get_seconds() + << " :before-memory " << std::fixed << std::setprecision(2) << m_start_memory + << " :after-memory " << std::fixed << std::setprecision(2) << end_memory + << ")" << std::endl; + } +}; + +as_st_report::as_st_report(char const * id, assertion_set & s) { + if (get_verbosity_level() >= ST_VERBOSITY_LVL) + m_imp = alloc(imp, id, s); + else + m_imp = 0; +} + +void report_st_progress(char const * id, unsigned val) { + if (val > 0) { + IF_VERBOSE(ST_VERBOSITY_LVL, verbose_stream() << "(" << id << " " << val << ")" << std::endl;); + } +} + +as_st_report::~as_st_report() { + if (m_imp) + dealloc(m_imp); +} + +class report_verbose_st : public assertion_set_strategy { + std::string m_msg; + unsigned m_lvl; +public: + report_verbose_st(char const* msg, unsigned lvl) : m_msg(msg), m_lvl(lvl) {} + + virtual void cleanup() {} + virtual void operator()(assertion_set& s, model_converter_ref & mc) { + IF_VERBOSE(m_lvl, verbose_stream() << m_msg << "\n";); + } +}; + +as_st * mk_report_verbose_st(char const* msg, unsigned lvl) { + return alloc(report_verbose_st, msg, lvl); +} + + +void assertion_set_strategy::cancel() { + #pragma omp critical (as_st_cancel) + { + set_cancel(true); + } +} + +void assertion_set_strategy::reset_cancel() { + #pragma omp critical (as_st_cancel) + { + set_cancel(false); + } +} + +bool is_equal(assertion_set const & s1, assertion_set const & s2) { + if (s1.size() != s2.size()) + return false; + unsigned num1 = 0; // num unique ASTs in s1 + unsigned num2 = 0; // num unique ASTs in s2 + expr_fast_mark1 visited1; + expr_fast_mark2 visited2; + unsigned sz = s1.size(); + for (unsigned i = 0; i < sz; i++) { + expr * f1 = s1.form(i); + if (visited1.is_marked(f1)) + continue; + num1++; + visited1.mark(f1); + } + SASSERT(num1 <= sz); + SASSERT(0 <= num1); + for (unsigned i = 0; i < sz; i++) { + expr * f2 = s2.form(i); + if (visited2.is_marked(f2)) + continue; + num2++; + visited2.mark(f2); + if (!visited1.is_marked(f2)) + return false; + } + SASSERT(num2 <= sz); + SASSERT(0 <= num2); + SASSERT(num1 >= num2); + return num1 == num2; +} + +class composite_as_st : public as_st { +protected: + ptr_vector m_sts; + volatile bool m_cancel; + + void checkpoint() { + if (m_cancel) + throw strategy_exception(STE_CANCELED_MSG); + } + +public: + composite_as_st(unsigned num, as_st * const * sts):m_cancel(false) { + for (unsigned i = 0; i < num; i++) { + m_sts.push_back(sts[i]); + sts[i]->inc_ref(); + } + DEBUG_CODE({ + for (unsigned i = 0; i < num; i++) { + SASSERT(sts[i]); + } + }); + } + + virtual ~composite_as_st() { + ptr_buffer old_sts; + unsigned sz = m_sts.size(); + old_sts.append(sz, m_sts.c_ptr()); + #pragma omp critical (as_st_cancel) + { + for (unsigned i = 0; i < sz; i++) { + m_sts[i] = 0; + } + } + for (unsigned i = 0; i < sz; i++) { + old_sts[i]->dec_ref(); + } + } + + virtual void updt_params(params_ref const & p) { + TRACE("composite_updt_params", tout << "updt_params: " << p << "\n";); + ptr_vector::iterator it = m_sts.begin(); + ptr_vector::iterator end = m_sts.end(); + for (; it != end; ++it) + (*it)->updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + ptr_vector::iterator it = m_sts.begin(); + ptr_vector::iterator end = m_sts.end(); + for (; it != end; ++it) + (*it)->collect_param_descrs(r); + } + + virtual void collect_statistics(statistics & st) const { + ptr_vector::const_iterator it = m_sts.begin(); + ptr_vector::const_iterator end = m_sts.end(); + for (; it != end; ++it) + (*it)->collect_statistics(st); + } + + virtual void reset_statistics() { + ptr_vector::const_iterator it = m_sts.begin(); + ptr_vector::const_iterator end = m_sts.end(); + for (; it != end; ++it) + (*it)->reset_statistics(); + } + + virtual void cleanup() { + ptr_vector::iterator it = m_sts.begin(); + ptr_vector::iterator end = m_sts.end(); + for (; it != end; ++it) + (*it)->cleanup(); + } + + virtual void reset() { + ptr_vector::iterator it = m_sts.begin(); + ptr_vector::iterator end = m_sts.end(); + for (; it != end; ++it) + (*it)->reset(); + } + + virtual void set_front_end_params(front_end_params & p) { + ptr_vector::iterator it = m_sts.begin(); + ptr_vector::iterator end = m_sts.end(); + for (; it != end; ++it) + (*it)->set_front_end_params(p); + } + + virtual void set_logic(symbol const & l) { + ptr_vector::iterator it = m_sts.begin(); + ptr_vector::iterator end = m_sts.end(); + for (; it != end; ++it) + (*it)->set_logic(l); + } + + virtual void set_progress_callback(progress_callback * callback) { + ptr_vector::iterator it = m_sts.begin(); + ptr_vector::iterator end = m_sts.end(); + for (; it != end; ++it) + (*it)->set_progress_callback(callback); + } + +protected: + /** + \brief Reset cancel flag of st if this was not canceled. + */ + void parent_reset_cancel(as_st & st) { + #pragma omp critical (as_st_cancel) + { + if (!m_cancel) { + st.set_cancel(false); + } + } + } + + virtual void set_cancel(bool f) { + m_cancel = f; + ptr_vector::iterator it = m_sts.begin(); + ptr_vector::iterator end = m_sts.end(); + for (; it != end; ++it) + if (*it) + (*it)->set_cancel(f); + } +}; + +class and_then_as_st : public composite_as_st { +public: + and_then_as_st(unsigned num, as_st * const * sts):composite_as_st(num, sts) {} + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + mc = 0; + if (s.inconsistent()) + return; + ptr_vector::iterator it = m_sts.begin(); + ptr_vector::iterator end = m_sts.end(); + for (; it != end; ++it) { + checkpoint(); + as_st & st = *(*it); + model_converter_ref mc1; // force mc1 to be 0 at every iteration... otherwise, it may contain value of the previous iteration. + try { + st(s, mc1); + } + catch (strategy_exception & ex) { + mc = 0; + throw ex; + } + mc = concat(mc.get(), mc1.get()); + } + } +}; + +as_st * and_then(unsigned num, as_st * const * sts) { + return alloc(and_then_as_st, num, sts); +} + +as_st * and_then(as_st * st1, as_st * st2) { + as_st * sts[2] = { st1, st2 }; + return and_then(2, sts); +} + +as_st * and_then(as_st * st1, as_st * st2, as_st * st3) { + as_st * sts[3] = { st1, st2, st3 }; + return and_then(3, sts); +} + +as_st * and_then(as_st * st1, as_st * st2, as_st * st3, as_st * st4) { + as_st * sts[4] = { st1, st2, st3, st4 }; + return and_then(4, sts); +} + +as_st * and_then(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5) { + as_st * sts[5] = { st1, st2, st3, st4, st5 }; + return and_then(5, sts); +} + +as_st * and_then(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5, as_st * st6) { + as_st * sts[6] = { st1, st2, st3, st4, st5, st6 }; + return and_then(6, sts); +} + +as_st * and_then(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5, as_st * st6, as_st * st7) { + as_st * sts[7] = { st1, st2, st3, st4, st5, st6, st7 }; + return and_then(7, sts); +} + +as_st * and_then(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5, as_st * st6, as_st * st7, as_st * st8) { + as_st * sts[8] = { st1, st2, st3, st4, st5, st6, st7, st8 }; + return and_then(8, sts); +} + +as_st * and_then(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5, as_st * st6, as_st * st7, as_st * st8, as_st * st9) { + as_st * sts[9] = { st1, st2, st3, st4, st5, st6, st7, st8, st9 }; + return and_then(9, sts); +} + +as_st * and_then(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5, as_st * st6, as_st * st7, as_st * st8, as_st * st9, as_st * st10) { + as_st * sts[10] = { st1, st2, st3, st4, st5, st6, st7, st8, st9, st10 }; + return and_then(10, sts); + +} + +class or_else_as_st : public composite_as_st { +public: + or_else_as_st(unsigned num, as_st * const * sts):composite_as_st(num, sts) {} + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + if (s.inconsistent()) + return; + assertion_set orig_s(s.m()); + s.copy(orig_s); + unsigned sz = m_sts.size(); + unsigned i; + for (i = 0; i < sz - 1; i++) { + checkpoint(); + as_st & st = *(m_sts[i]); + mc = 0; + try { + st(s, mc); + return; + } + catch (strategy_exception ex) { + mc = 0; + s.reset(); + orig_s.copy(s); + } + } + checkpoint(); + SASSERT(i == sz - 1); + as_st & st = *(m_sts[i]); + st(s, mc); + } +}; + + +as_st * or_else(unsigned num, as_st * const * sts) { + return alloc(or_else_as_st, num, sts); +} + +as_st * or_else(as_st * st1, as_st * st2) { + as_st * sts[2] = { st1, st2 }; + return or_else(2, sts); +} + +as_st * or_else(as_st * st1, as_st * st2, as_st * st3) { + as_st * sts[3] = { st1, st2, st3 }; + return or_else(3, sts); +} + +as_st * or_else(as_st * st1, as_st * st2, as_st * st3, as_st * st4) { + as_st * sts[4] = { st1, st2, st3, st4 }; + return or_else(4, sts); +} + +as_st * or_else(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5) { + as_st * sts[5] = { st1, st2, st3, st4, st5 }; + return or_else(5, sts); +} + +as_st * or_else(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5, as_st * st6) { + as_st * sts[6] = { st1, st2, st3, st4, st5, st6 }; + return or_else(6, sts); +} + +as_st * or_else(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5, as_st * st6, as_st * st7) { + as_st * sts[7] = { st1, st2, st3, st4, st5, st6, st7 }; + return or_else(7, sts); +} + +as_st * or_else(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5, as_st * st6, as_st * st7, as_st * st8) { + as_st * sts[8] = { st1, st2, st3, st4, st5, st6, st7, st8 }; + return or_else(8, sts); +} + +as_st * or_else(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5, as_st * st6, as_st * st7, as_st * st8, as_st * st9) { + as_st * sts[9] = { st1, st2, st3, st4, st5, st6, st7, st8, st9 }; + return or_else(9, sts); +} + +as_st * or_else(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5, as_st * st6, as_st * st7, as_st * st8, as_st * st9, as_st * st10) { + as_st * sts[10] = { st1, st2, st3, st4, st5, st6, st7, st8, st9, st10 }; + return or_else(10, sts); +} + +class par_as_st : public or_else_as_st { +public: + par_as_st(unsigned num, as_st * const * sts):or_else_as_st(num, sts) {} + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + if (s.inconsistent()) { + mc = 0; + return; + } + + if (omp_in_parallel()) { + // execute tasks sequentially + or_else_as_st::operator()(s, mc); + return; + } + + ast_manager & m = s.m(); + + sbuffer s_copies; + ptr_vector::iterator it = m_sts.begin(); + ptr_vector::iterator end = m_sts.end(); + for (; it != end; ++it) { + s_copies.push_back(assertion_set(m)); + assertion_set & s_copy = s_copies.back(); + s.copy(s_copy); + } + + unsigned finished_id = UINT_MAX; + char const * ex1_msg = 0; + std::string z3_ex1_msg; + + int sz = m_sts.size(); + + cooperation_section section; + omp_set_num_threads(sz); + #pragma omp parallel for + for (int i = 0; i < sz; i++) { + init_task task("par_as_st"); + as_st & st = *(m_sts[i]); + try { + model_converter_ref tmp_mc; + assertion_set & s_copy = s_copies[i]; + st(s_copy, tmp_mc); + bool first = false; + #pragma omp critical (par_as_st) + { + if (finished_id == UINT_MAX) { + finished_id = i; + first = true; + } + } + if (first) { + mc = tmp_mc; + s_copy.copy(s); + for (int j = 0; j < sz; j++) { + if (i != j) + m_sts[j]->cancel(); + } + } + } + catch (strategy_exception & ex) { + if (i == 0) + ex1_msg = ex.msg(); + } + catch (z3_error & ex) { + throw ex; + } + catch (z3_exception & z3_ex) { + if (i == 0) + z3_ex1_msg = z3_ex.msg(); + } + } + + if (finished_id == UINT_MAX) { + mc = 0; + if (ex1_msg != 0) + throw strategy_exception(ex1_msg); + else + throw default_exception(z3_ex1_msg.c_str()); + } + } +}; + +as_st * par(unsigned num, as_st * const * sts) { + return alloc(par_as_st, num, sts); +} + +as_st * par(as_st * st1, as_st * st2) { + as_st * sts[2] = { st1, st2 }; + return par(2, sts); +} + +as_st * par(as_st * st1, as_st * st2, as_st * st3) { + as_st * sts[3] = { st1, st2, st3 }; + return par(3, sts); +} + +as_st * par(as_st * st1, as_st * st2, as_st * st3, as_st * st4) { + as_st * sts[4] = { st1, st2, st3, st4 }; + return par(4, sts); +} + +class par_or_as_st : public as_st { + ptr_vector m_stfs; + ptr_vector m_sts; + std::string m_exc_msg; + params_ref m_params; + + void dec_ref_sts() { + unsigned sz = m_sts.size(); + for (unsigned i = 0; i < sz; i++) { + as_st * st = m_sts[i]; + #pragma omp critical (as_st_cancel) + { + m_sts[i] = 0; + } + st->dec_ref(); + } + } + + void exec_seq(assertion_set & s, model_converter_ref & mc) { + for (unsigned i = 0; i < m_stfs.size(); i++) { + as_st_f & f = *m_stfs[i]; + as_st * new_st = f(s.m(), m_params); + new_st->inc_ref(); + #pragma omp critical (as_st_cancel) + { + m_sts[i] = new_st; + } + } + or_else_as_st(m_sts.size(), m_sts.c_ptr())(s, mc); + dec_ref_sts(); + } + +public: + par_or_as_st(unsigned num, as_st_f * const * stfs) { + m_stfs.append(num, stfs); + m_sts.resize(num, 0); + } + + virtual ~par_or_as_st() { + DEBUG_CODE({ + for (unsigned i = 0; i < m_sts.size(); i++) { + SASSERT(m_sts[i] == 0); + } + }); + std::for_each(m_stfs.begin(), m_stfs.end(), delete_proc()); + } + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + if (s.inconsistent()) { + mc = 0; + return; + } + + ast_manager & m = s.m(); + + if (omp_in_parallel()) { + exec_seq(s, mc); + return; + } + + ptr_buffer managers; + + unsigned num = m_stfs.size(); + for (unsigned i = 0; i < num; i++) { + SASSERT(!m.is_format_manager()); + managers.push_back(alloc(ast_manager, m.proof_mode())); + managers.back()->copy_families_plugins(m); // has to be the first operation on the new managers. + as_st_f & f = *m_stfs[i]; + ast_manager & m_i = *managers.back(); + as_st * new_st = f(m_i, m_params); + new_st->inc_ref(); + #pragma omp critical (as_st_cancel) + { + m_sts[i] = new_st; + } + } + + int sz = m_sts.size(); + unsigned finished_id = UINT_MAX; + bool got_z3_exc = false; + m_exc_msg = ""; + bool good_abort = false; + + #pragma omp parallel for + for (int i = 0; i < sz; i++) { +#ifdef _WINDOWS + DWORD_PTR am = (0x01 << (omp_get_thread_num() % omp_get_num_procs())); + SetThreadAffinityMask(GetCurrentThread(), am); +#endif + as_st & st = *(m_sts[i]); + ast_manager & s_m = *managers[i]; + ast_translation input_translator(m, s_m); + assertion_set * s_copy = s.translate(input_translator); + + try { + model_converter_ref tmp_mc; + st(*s_copy, tmp_mc); + + #pragma omp critical (par_or_as_st) + { + if (finished_id == UINT_MAX) // ... and we're the first! + finished_id = i; + } + + if (finished_id == static_cast(i)) { + good_abort = true; + for (int j = 0; j < sz; j++) { + if (i != j) + m_sts[j]->cancel(); + } + + ast_translation output_translator(s_m, m); + mc = (tmp_mc) ? tmp_mc->translate(output_translator) : 0; + + assertion_set * temp_set = s_copy->translate(output_translator); + temp_set->copy(s); + dealloc(temp_set); + } + } + catch (strategy_exception & ex) { + if (!good_abort) { + #pragma omp critical (par_or_as_st_ex) + { + m_exc_msg = ex.msg(); + got_z3_exc = false; + } + } + } + catch (z3_error & ex) { + throw ex; + } + catch (z3_exception & z3_ex) { + if (!good_abort) { + #pragma omp critical (par_or_as_st_ex) + { + m_exc_msg = z3_ex.msg(); + got_z3_exc = true; + } + } + } + catch (...) { + if (!good_abort) { + #pragma omp critical (par_or_as_st_ex) + { + m_exc_msg = "unidentified exception in parallel region."; + got_z3_exc = false; + } + } + } + + dealloc(s_copy); + } + + dec_ref_sts(); + + for (unsigned i = 0; i < num; i++) { + dealloc(managers[i]); + } + + if (finished_id == UINT_MAX) { + mc = 0; + if (got_z3_exc) + throw default_exception(m_exc_msg.c_str()); + else + throw strategy_exception(m_exc_msg.c_str()); + } + } + + virtual void cleanup() { + // Cleanup is invoked to free memory allocated by the strategy, + // but it must leave the strategy object in a usable state. + // par_or does not need a cleanup since it only has factories. + } + + virtual void set_cancel(bool f) { + for (unsigned i = 0; i < m_sts.size(); i++) + if (m_sts[i]) + m_sts[i]->set_cancel(f); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + } +}; + +as_st * par_or(ast_manager & m, unsigned num, as_st_f * const * stfs) { +#ifdef _Z3_BUILD_PARALLEL_SMT + return alloc(par_or_as_st, num, stfs); +#else + // If Z3 was not compiled using _Z3_BUILD_PARALLEL_SMT, then + // it is not safe to use par_or (symbol and big_num managers are not protected). + // We use par instead + ptr_buffer sts; + params_ref p; + for (unsigned i = 0; i < num; i++) + sts.push_back(stfs[i]->operator()(m, p)); + return par(num, sts.c_ptr()); +#endif +} + +class fail_as_st : public as_st { +public: + fail_as_st() {} + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + throw strategy_exception("failed"); + } + void cleanup() {} +}; + +as_st * fail() { + return alloc(fail_as_st); +} + +as_st * fail_if_not_decided(as_st * st) { + return and_then(st, cond(check_decided(), noop(), fail())); +} + +class fail_if_unsat_st : public as_st { +public: + fail_if_unsat_st() {} + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + TRACE("fail_if_unsat", s.display(tout);); + if (s.inconsistent()) + throw strategy_exception("failed: not unsat"); + } + void cleanup() {} +}; + +class fail_if_sat_st : public as_st { +public: + fail_if_sat_st() {} + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + TRACE("fail_if_sat", s.display(tout);); + if (s.size() == 0) + throw strategy_exception("failed: not sat"); + } + void cleanup() {} +}; + +as_st * fail_if_unsat() { + return alloc(fail_if_unsat_st); +} + +as_st * fail_if_sat() { + return alloc(fail_if_sat_st); +} + +struct fail_if_not_small_st : public assertion_set_strategy { + unsigned m_size; + fail_if_not_small_st(unsigned sz):m_size(sz) {} + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + TRACE("fail_if_not_small", tout << "fail_if_not_small: executing, s.num_exprs(): " << s.num_exprs() << ", threshold: " << m_size << "\n";); + if (s.num_exprs() >= m_size) { + TRACE("fail_if_not_small", tout << "fail_if_not_small: failed\n";); + throw strategy_exception("failed: not small"); + } + } + + virtual void cleanup() {} +}; + +as_st * fail_if_not_small(unsigned sz) { + return alloc(fail_if_not_small_st, sz); +} + +struct fail_if_not_small_set_st : public assertion_set_strategy { + unsigned m_size; + fail_if_not_small_set_st(unsigned sz):m_size(sz) {} + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + TRACE("fail_if_not_small_set", tout << "fail_if_not_small: executing, s.size(): " << s.size() << ", threshold: " << m_size << "\n";); + if (s.size() >= m_size) { + TRACE("fail_if_not_small_set", tout << "fail_if_not_small: failed\n";); + throw strategy_exception("failed: not small"); + } + } + + virtual void cleanup() {} +}; + +as_st * fail_if_not_small_set(unsigned sz) { + return alloc(fail_if_not_small_set_st, sz); +} + +class round_robin_as_st : public composite_as_st { + unsigned m_start_timeout; + unsigned m_end_timeout; + unsigned m_delta_timeout; +public: + round_robin_as_st(unsigned num, as_st * const * sts, unsigned start, unsigned end, unsigned delta): + composite_as_st(num, sts), + m_start_timeout(start), + m_end_timeout(end), + m_delta_timeout(delta) {} + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + mc = 0; + if (s.inconsistent()) + return; + assertion_set orig_s(s.m()); + s.copy(orig_s); + unsigned timeout = m_start_timeout; + while (true) { + unsigned sz = m_sts.size(); + unsigned i; + for (i = 0; i < sz; i++) { + checkpoint(); + as_st & st = *(m_sts[i]); + cancel_eh eh(st); + { + scoped_timer timer(timeout, &eh); + mc = 0; + try { + st(s, mc); + return; + } + catch (strategy_exception & ex) { + parent_reset_cancel(st); + mc = 0; + if (i == sz - 1 && timeout + m_delta_timeout > m_end_timeout) + throw ex; // last strategy in the last round + s.reset(); + orig_s.copy(s); + } + } + } + timeout += m_delta_timeout; + } + } +}; + +as_st * round_robin(unsigned num, as_st * const * sts, unsigned start, unsigned end, unsigned delta) { + return alloc(round_robin_as_st, num, sts, start, end, delta); +} + +as_st * round_robin(as_st * st1, as_st * st2, unsigned start, unsigned end, unsigned delta) { + as_st * sts[2] = { st1, st2 }; + return round_robin(2, sts, start, end, delta); +} + +as_st * round_robin(as_st * st1, as_st * st2, as_st * st3, unsigned start, unsigned end, unsigned delta) { + as_st * sts[3] = { st1, st2, st3 }; + return round_robin(3, sts, start, end, delta); +} + +as_st * round_robin(as_st * st1, as_st * st2, as_st * st3, as_st * st4, unsigned start, unsigned end, unsigned delta) { + as_st * sts[4] = { st1, st2, st3, st4 }; + return round_robin(4, sts, start, end, delta); +} + +wrapper_as_st::~wrapper_as_st() { + as_st * d = m_st; + #pragma omp critical (as_st_cancel) + { + m_st = 0; + } + d->dec_ref(); +} + +class try_for_as_st : public wrapper_as_st { + unsigned m_timeout; +public: + try_for_as_st(as_st * s, unsigned t):wrapper_as_st(s), m_timeout(t) {} + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + cancel_eh eh(*m_st); + { + // Warning: scoped_timer is not thread safe in Linux. + scoped_timer timer(m_timeout, &eh); + wrapper_as_st::operator()(s, mc); + } + } +}; + +as_st * try_for(as_st * st, unsigned msecs) { + return alloc(try_for_as_st, st, msecs); +} + +class cleanup_as_st : public wrapper_as_st { +public: + cleanup_as_st(as_st * s):wrapper_as_st(s) {} + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + try { + wrapper_as_st::operator()(s, mc); + wrapper_as_st::cleanup(); + } + catch (strategy_exception & ex) { + wrapper_as_st::cleanup(); + throw ex; + } + } +}; + +as_st * clean(as_st * st) { + return alloc(cleanup_as_st, st); +} + +class using_params_as_st : public wrapper_as_st { + params_ref m_params; +public: + using_params_as_st(as_st * s, params_ref const & p):wrapper_as_st(s), m_params(p) { + s->updt_params(p); + } + + virtual void updt_params(params_ref const & p) { + TRACE("using_params", + tout << "before p: " << p << "\n"; + tout << "m_params: " << m_params << "\n"; + ;); + + params_ref new_p = p; + new_p.append(m_params); + wrapper_as_st::updt_params(new_p); + + TRACE("using_params", + tout << "after p: " << p << "\n"; + tout << "m_params: " << m_params << "\n"; + tout << "new_p: " << new_p << "\n";); + } +}; + +as_st * using_params(as_st * st, params_ref const & p) { + return alloc(using_params_as_st, st, p); +} + +/** + \brief For debugging purposes: display model converter in the trace. +*/ +class trace_mc_as_st : public wrapper_as_st { +public: + trace_mc_as_st(as_st * s):wrapper_as_st(s) { + } + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + wrapper_as_st::operator()(s, mc); + TRACE("trace_mc", tout << "trace_mc...\n"; if (mc) mc->display(tout);); + } +}; + +as_st * trace_mc(as_st * st) { + return alloc(trace_mc_as_st, st); +} + +class repeat_as_st : public wrapper_as_st { + unsigned m_max; + volatile bool m_cancel; + + void checkpoint() { + if (m_cancel) + throw strategy_exception(STE_CANCELED_MSG); + } + +public: + repeat_as_st(as_st * s, unsigned max):wrapper_as_st(s), m_max(max), m_cancel(false) { + } + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + mc = 0; + if (s.inconsistent()) + return; + unsigned counter = 0; + while (true) { + checkpoint(); + + assertion_set orig_s(s.m()); + s.copy(orig_s); + + model_converter_ref mc1; + try { + (*m_st)(s, mc1); + } + catch (strategy_exception & ex) { + mc = 0; + throw ex; + } + + mc = concat(mc.get(), mc1.get()); + counter++; + if (counter >= m_max) + return; + if (is_equal(orig_s, s)) + return; + } + } + + virtual void set_cancel(bool f) { + m_cancel = f; + wrapper_as_st::set_cancel(f); + } +}; + +as_st * repeat(as_st * st, unsigned max) { + return alloc(repeat_as_st, st, max); +} + +class noop_as_st : public assertion_set_strategy { +public: + virtual void set_cancel(bool f) {} + virtual void operator()(assertion_set & s, model_converter_ref & mc) {} + virtual void cleanup() {} +}; + +as_st * noop() { + return alloc(noop_as_st); +} + +MK_ST_EXCEPTION(check_is_sat_exception); +MK_ST_EXCEPTION(check_is_unsat_exception); + +class filter_is_sat_st : public wrapper_as_st { + bool const& m_unless_condition; +public: + filter_is_sat_st(as_st* st, bool const& unless_condition): + wrapper_as_st(st), m_unless_condition(unless_condition) {} + virtual ~filter_is_sat_st() {} + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + TRACE("filter_is_sat_bug", tout << "cond: " << m_unless_condition << "\n"; s.display(tout); mc->display(tout);); + wrapper_as_st::operator()(s, mc); + if (!m_unless_condition && (s.size() > 0 || s.inconsistent())) { + throw check_is_sat_exception("Solver did not establish satisfiability"); + } + } +}; + +as_st * filter_is_sat(as_st* st, bool const& unless_condition) { + return alloc(filter_is_sat_st, st, unless_condition); +} + + +class filter_is_unsat_st : public wrapper_as_st { + bool const& m_unless_condition; +public: + filter_is_unsat_st(as_st* st, bool const& unless_condition): + wrapper_as_st(st), m_unless_condition(unless_condition) {} + virtual ~filter_is_unsat_st() {} + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + wrapper_as_st::operator()(s, mc); + if (!m_unless_condition && !s.inconsistent()) { + throw check_is_unsat_exception("Solver did not establish unsatisfiability"); + } + } +}; + +as_st * filter_is_unsat(as_st* st, bool const& unless_condition) { + return alloc(filter_is_unsat_st, st, unless_condition); +} + + + +class cond_as_st : public as_st { +protected: + as_test * m_cond; + as_st * m_then; + as_st * m_else; + volatile bool m_cancel; +public: + cond_as_st(as_test * c, as_st * t, as_st * e):m_cond(c), m_then(t), m_else(e), m_cancel(false) { + SASSERT(c); SASSERT(t); SASSERT(e); + c->inc_ref(); + t->inc_ref(); + e->inc_ref(); + } + + virtual ~cond_as_st() { + as_st * d1 = m_then; + as_st * d2 = m_else; + #pragma omp critical (as_st_cancel) + { + m_then = 0; + m_else = 0; + } + m_cond->dec_ref(); + d1->dec_ref(); + d2->dec_ref(); + } + + virtual void updt_params(params_ref const & p) { + m_then->updt_params(p); + m_else->updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + m_then->collect_param_descrs(r); + m_else->collect_param_descrs(r); + } + + virtual void collect_statistics(statistics & s) const { + m_then->collect_statistics(s); + m_else->collect_statistics(s); + } + + virtual void reset_statistics() { + m_then->reset_statistics(); + m_else->reset_statistics(); + } + + virtual void cleanup() { + m_then->cleanup(); + m_else->cleanup(); + } + + virtual void reset() { + m_then->reset(); + m_else->reset(); + } + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + if (s.inconsistent()) + return; + assertion_set orig_s(s.m()); + s.copy(orig_s); + bool c = (*m_cond)(s); + if (m_cancel) + throw strategy_exception(STE_CANCELED_MSG); + if (c) + (*m_then)(s, mc); + else + (*m_else)(s, mc); + } + + virtual void set_front_end_params(front_end_params & p) { + m_then->set_front_end_params(p); + m_else->set_front_end_params(p); + } + + virtual void set_logic(symbol const & l) { + m_then->set_logic(l); + m_else->set_logic(l); + } + + virtual void set_progress_callback(progress_callback * callback) { + m_then->set_progress_callback(callback); + m_else->set_progress_callback(callback); + } + +protected: + virtual void set_cancel(bool f) { + m_cancel = f; + if (m_then) + m_then->set_cancel(f); + if (m_else) + m_else->set_cancel(f); + } +}; + + +as_st * cond(as_test * c, as_st * t, as_st * e) { + return alloc(cond_as_st, c, t, e); +} + +struct check_mem_test : public as_test { + unsigned m_limit; + check_mem_test(unsigned l):m_limit(l) {} + virtual bool operator()(assertion_set const & s) const { + return memory::get_allocation_size() < m_limit; + } +}; + +as_test * check_mem(unsigned l) { return alloc(check_mem_test, l); } + +struct check_as_size_test : public as_test { + unsigned m_limit; + check_as_size_test(unsigned l):m_limit(l) {} + virtual bool operator()(assertion_set const & s) const { return s.num_exprs() < m_limit; } +}; + +as_test * check_as_size(unsigned l) { return alloc(check_as_size_test, l); } + +struct check_decided_test : public as_test { + check_decided_test() {} + virtual bool operator()(assertion_set const & s) const { return s.size()==0 || s.inconsistent(); } +}; + +as_test * check_decided() { return alloc(check_decided_test); } + +class as_st_solver : public assertion_set_strategy { + scoped_ptr m_params; + params_ref m_params_ref; + statistics m_stats; + std::string m_failure; + smt::solver * m_ctx; + bool m_candidate_models; + symbol m_logic; + progress_callback * m_callback; +public: + as_st_solver(bool candidate_models):m_ctx(0), m_candidate_models(candidate_models), m_callback(0) {} + + front_end_params & fparams() { + if (!m_params) + m_params = alloc(front_end_params); + return *m_params; + } + + struct scoped_init_ctx { + as_st_solver & m_owner; + + scoped_init_ctx(as_st_solver & o, ast_manager & m):m_owner(o) { + smt::solver * new_ctx = alloc(smt::solver, m, o.fparams()); + TRACE("as_solver", tout << "logic: " << o.m_logic << "\n";); + new_ctx->set_logic(o.m_logic); + if (o.m_callback) { + new_ctx->set_progress_callback(o.m_callback); + } + #pragma omp critical (as_st_solver) + { + o.m_ctx = new_ctx; + } + } + + ~scoped_init_ctx() { + smt::solver * d = m_owner.m_ctx; + #pragma omp critical (as_st_cancel) + { + m_owner.m_ctx = 0; + } + if (d) + dealloc(d); + } + }; + + virtual ~as_st_solver() { + SASSERT(m_ctx == 0); + } + + virtual void updt_params(params_ref const & p) { + TRACE("as_solver", tout << "updt_params: " << p << "\n";); + m_params_ref = p; + params2front_end_params(m_params_ref, fparams()); + } + + virtual void collect_param_descrs(param_descrs & r) { + } + + virtual void set_cancel(bool f) { + if (m_ctx) + m_ctx->set_cancel(f); + } + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + SASSERT(is_well_sorted(s)); + IF_VERBOSE(ST_VERBOSITY_LVL, verbose_stream() << "(smt-solver)" << std::endl;); + TRACE("as_solver", tout << "AUTO_CONFIG: " << fparams().m_auto_config << " HIDIV0: " << fparams().m_hi_div0 << " " + << " PREPROCESS: " << fparams().m_preprocess << ", SOLVER:" << fparams().m_solver << "\n";); + TRACE("as_solver_detail", s.display(tout);); + ast_manager & m = s.m(); + TRACE("as_solver_memory", tout << "wasted_size: " << m.get_allocator().get_wasted_size() << "\n";); + // verbose_stream() << "wasted_size: " << m.get_allocator().get_wasted_size() << ", free_objs: " << m.get_allocator().get_num_free_objs() << "\n"; + // m.get_allocator().consolidate(); + scoped_init_ctx init(*this, m); + SASSERT(m_ctx != 0); + unsigned sz = s.size(); + for (unsigned i = 0; i < sz; i++) { + expr * f = s.form(i); + m_ctx->assert_expr(f); + } + lbool r = m_ctx->setup_and_check(); + m_ctx->collect_statistics(m_stats); + switch (r) { + case l_true: { + // the empty assertion set is trivially satifiable. + s.reset(); + // store the model in a do nothin model converter. + model_ref md; + m_ctx->get_model(md); + mc = model2model_converter(md.get()); + return; + } + case l_false: + // formula is unsat, reset the assertion set, and store false there. + s.reset(); + s.assert_expr(m.mk_false(), m_ctx->get_proof()); + return; + case l_undef: + if (m_candidate_models) { + switch (m_ctx->last_failure()) { + case smt::NUM_CONFLICTS: + case smt::THEORY: + case smt::QUANTIFIERS: { + model_ref md; + m_ctx->get_model(md); + mc = model2model_converter(md.get()); + return; + } + default: + break; + } + } + m_failure = m_ctx->last_failure_as_string(); + throw strategy_exception(m_failure.c_str()); + } + } + + virtual void collect_statistics(statistics & st) const { + if (m_ctx) + m_ctx->collect_statistics(st); // ctx is still running... + else + st.copy(m_stats); + } + + virtual void cleanup() { + } + + virtual void reset_statistics() { + m_stats.reset(); + } + + // for backward compatibility + virtual void set_front_end_params(front_end_params & p) { + m_params = alloc(front_end_params, p); + // must propagate the params_ref to fparams + params2front_end_params(m_params_ref, fparams()); + } + + virtual void set_logic(symbol const & l) { + m_logic = l; + } + + virtual void set_progress_callback(progress_callback * callback) { + m_callback = callback; + } +}; + +as_st * mk_smt_solver_core(bool candidate_models) { + return alloc(as_st_solver, candidate_models); +} + +as_st * mk_smt_solver(bool auto_config, bool candidate_models) { + as_st * solver = mk_smt_solver_core(candidate_models); + params_ref solver_p; + solver_p.set_bool(":auto-config", auto_config); + return using_params(solver, solver_p); +}; + +/** + \brief Execute strategy st on the given assertion set. +*/ +void exec(as_st * st, assertion_set & s, model_converter_ref & mc) { + st->reset_statistics(); + st->reset_cancel(); + try { + (*st)(s, mc); + st->cleanup(); + } + catch (strategy_exception & ex) { + IF_VERBOSE(ST_VERBOSITY_LVL, verbose_stream() << "(strategy-exception \"" << escaped(ex.msg()) << "\")" << std::endl;); + st->cleanup(); + throw ex; + } +} + +/** + \brief Use strategy st to check wether the assertion set s is satisfiable or not. + If result == l_true and model generation is enabled, then the model is stored in md. + If result == l_false and proof generation is enabled, then the proof is stored in pr. + If result == l_undef, the reason for failure is stored in reason_unknown. +*/ +lbool check_sat(as_st * st, assertion_set & s, model_ref & md, proof_ref & pr, std::string & reason_unknown) { + ast_manager & m = s.m(); + model_converter_ref mc; + try { + exec(st, s, mc); + } + catch (strategy_exception & ex) { + reason_unknown = ex.msg(); + return l_undef; + } + if (s.size() == 0) { + if (mc) { + TRACE("check_sat_model", tout << "model-converter:\n"; mc->display(tout);); + md = alloc(model, m); + (*mc)(md); + TRACE("check_sat_model", tout << "model:\n"; model_v2_pp(tout, *md);); + } + return l_true; + } + if (s.size() == 1 && m.is_false(s.form(0))) { + pr = s.pr(0); + return l_false; + } + if (mc) { + md = alloc(model, m); + (*mc)(md); + } + reason_unknown = "incomplete"; + return l_undef; +} diff --git a/lib/assertion_set_strategy.h b/lib/assertion_set_strategy.h new file mode 100644 index 000000000..c1209f6ae --- /dev/null +++ b/lib/assertion_set_strategy.h @@ -0,0 +1,228 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + assertion_set_strategy.h + +Abstract: + + Abstract strategy for assertion sets, and simple combinators. + +Author: + + Leonardo (leonardo) 2011-05-17 + +Notes: + +--*/ +#ifndef _AS_STRATEGY_H_ +#define _AS_STRATEGY_H_ + +#include"params.h" +#include"assertion_set.h" +#include"model_converter.h" +#include"statistics.h" +#include"strategy_exception.h" +#include"lbool.h" +#include"assertion_set_util.h" + +struct front_end_params; + +class progress_callback; + +// minimum verbosity level for strategies +#define ST_VERBOSITY_LVL 10 + +class as_st_report { + struct imp; + imp * m_imp; +public: + as_st_report(char const * id, assertion_set & s); + ~as_st_report(); +}; + +void report_st_progress(char const * id, unsigned val); + +/** + \brief Abstract assertion-set strategy. +*/ +class assertion_set_strategy { + unsigned m_ref_count; +public: + assertion_set_strategy():m_ref_count(0) {} + virtual ~assertion_set_strategy() {} + void inc_ref() { m_ref_count++; } + void dec_ref() { SASSERT(m_ref_count > 0); m_ref_count--; if (m_ref_count == 0) dealloc(this); } + virtual void updt_params(params_ref const & p) {} + virtual void collect_param_descrs(param_descrs & r) {} + void cancel(); + void reset_cancel(); + virtual void set_cancel(bool f) {} + virtual void operator()(assertion_set & s, model_converter_ref & mc) = 0; + virtual void collect_statistics(statistics & st) const {} + virtual void reset_statistics() {} + virtual void cleanup() = 0; + virtual void reset() { cleanup(); } + // for backward compatibility + virtual void set_front_end_params(front_end_params & p) {} + virtual void set_logic(symbol const & l) {} + virtual void set_progress_callback(progress_callback * callback) {} +}; + +bool is_equal(assertion_set const & s1, assertion_set const & s2); + +// dummy for using ref_vector +struct assertion_set_strategy_context { + static void inc_ref(assertion_set_strategy * st) { st->inc_ref(); } + static void dec_ref(assertion_set_strategy * st) { st->dec_ref(); } +}; + +typedef assertion_set_strategy as_st; +typedef assertion_set_strategy_context as_st_ctx; + +typedef ref as_st_ref; +typedef ref_vector as_st_ref_vector; +typedef ref_buffer as_st_ref_buffer; + +as_st * and_then(unsigned num, as_st * const * sts); +as_st * and_then(as_st * st1, as_st * st2); +as_st * and_then(as_st * st1, as_st * st2, as_st * st3); +as_st * and_then(as_st * st1, as_st * st2, as_st * st3, as_st * st4); +as_st * and_then(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5); +as_st * and_then(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5, as_st * st6); +as_st * and_then(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5, as_st * st6, as_st * st7); +as_st * and_then(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5, as_st * st6, as_st * st7, as_st * st8); +as_st * and_then(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5, as_st * st6, as_st * st7, as_st * st8, as_st * st9); +as_st * and_then(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5, as_st * st6, as_st * st7, as_st * st8, as_st * st9, as_st * st10); + +as_st * or_else(unsigned num, as_st * const * sts); +as_st * or_else(as_st * st1, as_st * st2); +as_st * or_else(as_st * st1, as_st * st2, as_st * st3); +as_st * or_else(as_st * st1, as_st * st2, as_st * st3, as_st * st4); +as_st * or_else(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5); +as_st * or_else(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5, as_st * st6); +as_st * or_else(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5, as_st * st6, as_st * st7); +as_st * or_else(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5, as_st * st6, as_st * st7, as_st * st8); +as_st * or_else(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5, as_st * st6, as_st * st7, as_st * st8, as_st * st9); +as_st * or_else(as_st * st1, as_st * st2, as_st * st3, as_st * st4, as_st * st5, as_st * st6, as_st * st7, as_st * st8, as_st * st9, as_st * st10); + +as_st * par(unsigned num, as_st * const * sts); +as_st * par(as_st * st1, as_st * st2); +as_st * par(as_st * st1, as_st * st2, as_st * st3); +as_st * par(as_st * st1, as_st * st2, as_st * st3, as_st * st4); + +as_st * round_robin(unsigned num, as_st * const * sts, unsigned start, unsigned end, unsigned delta); +as_st * round_robin(as_st * st1, as_st * st2, unsigned start, unsigned end, unsigned delta); +as_st * round_robin(as_st * st1, as_st * st2, as_st * st3, unsigned start, unsigned end, unsigned delta); +as_st * round_robin(as_st * st1, as_st * st2, as_st * st3, as_st * st4, unsigned start, unsigned end, unsigned delta); + +as_st * try_for(as_st * st, unsigned msecs); +as_st * clean(as_st * st); +as_st * using_params(as_st * st, params_ref const & p); +as_st * noop(); +as_st * trace_mc(as_st * st); + +as_st * filter_is_sat(as_st* st, bool const& unless_condition); +as_st * filter_is_unsat(as_st* st, bool const& unless_condition); + +as_st * mk_report_verbose_st(char const* msg, unsigned lvl); + +as_st * repeat(as_st * st, unsigned max = UINT_MAX); + +class wrapper_as_st : public as_st { +protected: + as_st * m_st; +public: + wrapper_as_st(as_st * s): m_st(s) { SASSERT(s); s->inc_ref(); } + virtual ~wrapper_as_st(); + + virtual void operator()(assertion_set& s, model_converter_ref& mc) { (*m_st)(s, mc); } + virtual void cleanup(void) { m_st->cleanup(); } + virtual void collect_statistics(statistics & st) const { m_st->collect_statistics(st); } + virtual void reset_statistics() { m_st->reset_statistics(); } + virtual void set_front_end_params(front_end_params & p) { m_st->set_front_end_params(p); } + virtual void updt_params(params_ref const & p) { m_st->updt_params(p); } + virtual void collect_param_descrs(param_descrs & r) { m_st->collect_param_descrs(r); } + virtual void reset() { m_st->reset(); } + virtual void set_logic(symbol const& l) { m_st->set_logic(l); } + virtual void set_progress_callback(progress_callback * callback) { m_st->set_progress_callback(callback); } + +protected: + virtual void set_cancel(bool f) { if (m_st) m_st->set_cancel(f); } +}; + + +class as_test { + unsigned m_ref_count; +public: + as_test():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); } + virtual bool operator()(assertion_set const & s) const = 0; +}; + +as_test * check_mem(unsigned l); +as_test * check_as_size(unsigned l); +as_test * check_decided(); +as_st * cond(as_test * c, as_st * t, as_st * e); + +as_st * mk_smt_solver_core(bool candidate_models = false); +as_st * mk_smt_solver(bool auto_config = true, bool candidate_models = false); + +void exec(as_st * st, assertion_set & s, model_converter_ref & mc); +lbool check_sat(as_st * st, assertion_set & s, model_ref & md, proof_ref & pr, std::string & reason_unknown); + +class assertion_set_strategy_factory { +public: + virtual ~assertion_set_strategy_factory() {} + virtual as_st * operator()(ast_manager & m, params_ref const & p) = 0; +}; + +typedef assertion_set_strategy_factory as_st_f; + +as_st * fail(); +as_st * fail_if_not_decided(as_st * st); +as_st * fail_if_sat(); +as_st * fail_if_unsat(); +as_st * fail_if_not_small(unsigned sz); +as_st * fail_if_not_small_set(unsigned sz); + +#define MK_FAIL_IF(NAME, TEST, MSG) \ +class NAME ## _st : public assertion_set_strategy { \ +public: \ + virtual void operator()(assertion_set & s, model_converter_ref & mc) { if (TEST) throw strategy_exception(MSG); } \ + virtual void cleanup() {} \ +}; \ +inline as_st * NAME() { return alloc(NAME ## _st); } + +as_st * par_or(ast_manager & m, unsigned num, as_st_f * const * stfs); + +#define MK_ST_FACTORY(NAME, CODE) \ +class NAME : public assertion_set_strategy_factory { \ +public: \ + virtual ~NAME() {} \ + virtual as_st * operator()(ast_manager & m, params_ref const & p) { CODE } \ +}; + +#define MK_SIMPLE_ST_FACTORY(NAME, ST) MK_ST_FACTORY(NAME, return ST;) + +MK_SIMPLE_ST_FACTORY(smt_solver_stf, mk_smt_solver()); + +struct is_qfbv_test : public as_test { + virtual bool operator()(assertion_set const & s) const { return is_qfbv(s); } +}; + +struct is_qflia_test : public as_test { + virtual bool operator()(assertion_set const & s) const { return is_qflia(s); } +}; + +struct is_qflra_test : public as_test { + virtual bool operator()(assertion_set const & s) const { return is_qflra(s); } +}; + +inline as_test * is_qfbv() { return alloc(is_qfbv_test); } +inline as_test * is_qflia() { return alloc(is_qflia_test); } +inline as_test * is_qflra() { return alloc(is_qflra_test); } + +#endif diff --git a/lib/assertion_set_util.cpp b/lib/assertion_set_util.cpp new file mode 100644 index 000000000..c596b53c4 --- /dev/null +++ b/lib/assertion_set_util.cpp @@ -0,0 +1,220 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + assertion_set_util.cpp + +Abstract: + + Assertion set goodies + +Author: + + Leonardo de Moura (leonardo) 2011-04-28 + +Revision History: + +--*/ +#include"assertion_set_util.h" +#include"well_sorted.h" +#include"for_each_expr.h" +#include"bv_decl_plugin.h" +#include"arith_decl_plugin.h" + +void as_shared_occs::operator()(assertion_set const & s) { + m_occs.reset(); + shared_occs_mark visited; + unsigned sz = s.size(); + for (unsigned i = 0; i < sz; i++) { + expr * t = s.form(i); + m_occs(t, visited); + } +} + +bool is_well_sorted(assertion_set const & s) { + unsigned sz = s.size(); + for (unsigned i = 0; i < sz; i++) { + expr * t = s.form(i); + if (!is_well_sorted(s.m(), t)) + return false; + } + return true; +} + +struct is_non_propositional { + struct found {}; + ast_manager & m; + + is_non_propositional(ast_manager & _m):m(_m) {} + void operator()(var *) { throw found(); } + void operator()(quantifier *) { throw found(); } + void operator()(app * n) { + if (!m.is_bool(n)) + throw found(); + + family_id fid = n->get_family_id(); + if (fid == m.get_basic_family_id()) + return; + + if (is_uninterp_const(n)) + return; + + throw found(); + } +}; + +struct is_non_qfbv { + struct found {}; + ast_manager & m; + bv_util u; + + is_non_qfbv(ast_manager & _m):m(_m), u(m) {} + + void operator()(var *) { throw found(); } + + void operator()(quantifier *) { throw found(); } + + void operator()(app * n) { + if (!m.is_bool(n) && !u.is_bv(n)) + throw found(); + family_id fid = n->get_family_id(); + if (fid == m.get_basic_family_id()) + return; + if (fid == u.get_family_id()) + return; + if (is_uninterp_const(n)) + return; + + throw found(); + } +}; + +bool is_propositional(assertion_set const & s) { + return !test(s); +} + +bool is_qfbv(assertion_set const & s) { + return !test(s); +} + +struct is_non_qflira { + struct found {}; + ast_manager & m; + arith_util u; + bool m_int; + bool m_real; + + is_non_qflira(ast_manager & _m, bool _int, bool _real):m(_m), u(m), m_int(_int), m_real(_real) {} + + void operator()(var *) { throw found(); } + + void operator()(quantifier *) { throw found(); } + + bool compatible_sort(app * n) const { + if (m.is_bool(n)) + return true; + if (m_int && u.is_int(n)) + return true; + if (m_real && u.is_real(n)) + return true; + return false; + } + + void operator()(app * n) { + if (!compatible_sort(n)) + throw found(); + family_id fid = n->get_family_id(); + if (fid == m.get_basic_family_id()) + return; + if (fid == u.get_family_id()) { + switch (n->get_decl_kind()) { + case OP_LE: case OP_GE: case OP_LT: case OP_GT: + case OP_ADD: case OP_NUM: + return; + case OP_MUL: + if (n->get_num_args() != 2) + throw found(); + if (!u.is_numeral(n->get_arg(0))) + throw found(); + return; + case OP_TO_REAL: + if (!m_real) + throw found(); + break; + default: + throw found(); + } + return; + } + if (is_uninterp_const(n)) + return; + throw found(); + } +}; + +bool is_qflia(assertion_set const & s) { + is_non_qflira proc(s.m(), true, false); + return !test(s, proc); +} + +bool is_qflra(assertion_set const & s) { + is_non_qflira proc(s.m(), false, true); + return !test(s, proc); +} + +bool is_qflira(assertion_set const & s) { + is_non_qflira proc(s.m(), true, true); + return !test(s, proc); +} + +bool is_lp(assertion_set const & s) { + ast_manager & m = s.m(); + arith_util u(m); + unsigned sz = s.size(); + for (unsigned i = 0; i < sz; i++) { + expr * f = s.form(i); + bool sign = false; + while (m.is_not(f, f)) + sign = !sign; + if (m.is_eq(f) && !sign) { + if (m.get_sort(to_app(f)->get_arg(0))->get_family_id() != u.get_family_id()) + return false; + continue; + } + if (u.is_le(f) || u.is_ge(f) || u.is_lt(f) || u.is_gt(f)) + continue; + return false; + } + return true; +} + +bool is_ilp(assertion_set const & s) { + if (!is_qflia(s)) + return false; + if (has_term_ite(s)) + return false; + return is_lp(s); +} + +bool is_mip(assertion_set const & s) { + if (!is_qflira(s)) + return false; + if (has_term_ite(s)) + return false; + return is_lp(s); +} + +struct has_term_ite_proc { + struct found {}; + ast_manager & m; + has_term_ite_proc(ast_manager & _m):m(_m) {} + void operator()(var *) {} + void operator()(quantifier *) {} + void operator()(app * n) { if (m.is_term_ite(n)) throw found(); } +}; + +bool has_term_ite(assertion_set const & s) { + return test(s); +} + diff --git a/lib/assertion_set_util.h b/lib/assertion_set_util.h new file mode 100644 index 000000000..425b20707 --- /dev/null +++ b/lib/assertion_set_util.h @@ -0,0 +1,91 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + assertion_set_util.h + +Abstract: + + Assertion set goodies + +Author: + + Leonardo de Moura (leonardo) 2011-04-28 + +Revision History: + +--*/ +#ifndef _ASSERTION_SET_UTIL_H_ +#define _ASSERTION_SET_UTIL_H_ + +#include"assertion_set.h" +#include"shared_occs.h" + +/** + \brief Functor for computing the set of shared occurrences in an assertion set. + + It is essentially a wrapper for shared_occs functor. +*/ +class as_shared_occs { + shared_occs m_occs; +public: + as_shared_occs(ast_manager & m, bool track_atomic = false, bool visit_quantifiers = true, bool visit_patterns = false): + m_occs(m, track_atomic, visit_quantifiers, visit_patterns) { + } + void operator()(assertion_set const & s); + bool is_shared(expr * t) { return m_occs.is_shared(t); } + unsigned num_shared() const { return m_occs.num_shared(); } + void reset() { return m_occs.reset(); } + void cleanup() { return m_occs.cleanup(); } + void display(std::ostream & out, ast_manager & m) const { m_occs.display(out, m); } +}; + +bool is_well_sorted(assertion_set const & s); + +// Return true if the assertion set is propositional logic +bool is_propositional(assertion_set const & s); + +// Return true if the assertion set is in QF_BV +bool is_qfbv(assertion_set const & s); + +// Return true if the assertion set is in QF_LIA +bool is_qflia(assertion_set const & s); + +// Return true if the assertion set is in QF_LRA +bool is_qflra(assertion_set const & s); + +// Return true if the assertion set is in QF_LIRA +bool is_qflira(assertion_set const & s); + +// Return true if the assertion set is in ILP problem (that is QF_LIA without boolean structure) +bool is_ilp(assertion_set const & s); + +// Return true if the assertion set is in MIP problem (that is QF_LIRA without boolean structure) +bool is_mip(assertion_set const & s); + +bool has_term_ite(assertion_set const & s); + +inline bool is_decided(assertion_set const & s) { return s.size() == 0 || s.inconsistent(); } + +template +bool test(assertion_set const & s, Predicate & proc) { + expr_fast_mark1 visited; + try { + unsigned sz = s.size(); + for (unsigned i = 0; i < sz; i++) + quick_for_each_expr(proc, visited, s.form(i)); + } + catch (typename Predicate::found) { + return true; + } + return false; +} + +template +bool test(assertion_set const & s) { + Predicate proc(s.m()); + return test(s, proc); +} + +#endif diff --git a/lib/assertion_stack.cpp b/lib/assertion_stack.cpp new file mode 100644 index 000000000..85bfb057c --- /dev/null +++ b/lib/assertion_stack.cpp @@ -0,0 +1,314 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + assertion_stack.cpp + +Abstract: + + Assertion stacks + +Author: + + Leonardo de Moura (leonardo) 2012-02-17 + +Revision History: + +--*/ +#include"assertion_stack.h" +#include"well_sorted.h" +#include"ast_smt2_pp.h" +#include"ref_util.h" + +assertion_stack::assertion_stack(ast_manager & m, bool models_enabled, bool core_enabled): + m_manager(m), + m_forbidden(m), + m_csubst(m, core_enabled), + m_fsubst(m, core_enabled) { + init(m.proofs_enabled(), models_enabled, core_enabled); +} + +assertion_stack::assertion_stack(ast_manager & m, bool proofs_enabled, bool models_enabled, bool core_enabled): + m_manager(m), + m_forbidden(m), + m_csubst(m, core_enabled, proofs_enabled), + m_fsubst(m, core_enabled, proofs_enabled) { + init(proofs_enabled, models_enabled, core_enabled); +} + +void assertion_stack::init(bool proofs_enabled, bool models_enabled, bool core_enabled) { + m_ref_count = 0; + m_models_enabled = models_enabled; + m_proofs_enabled = proofs_enabled; + m_core_enabled = core_enabled; + m_inconsistent = false; + m_form_qhead = 0; +} + +assertion_stack::~assertion_stack() { + reset(); +} + +void assertion_stack::reset() { + m_inconsistent = false; + m_form_qhead = 0; + m_mc_qhead = 0; + dec_ref_collection_values(m_manager, m_forms); + dec_ref_collection_values(m_manager, m_proofs); + dec_ref_collection_values(m_manager, m_deps); + m_forbidden_set.reset(); + m_forbidden.reset(); + m_csubst.reset(); + m_fsubst.reset(); + m_mc.reset(); + m_scopes.reset(); +} + +void assertion_stack::expand(expr * f, proof * pr, expr_dependency * dep, expr_ref & new_f, proof_ref & new_pr, expr_dependency_ref & new_dep) { + // TODO: expand definitions + new_f = f; + new_pr = pr; + new_dep = dep; +} + +void assertion_stack::push_back(expr * f, proof * pr, expr_dependency * d) { + if (m().is_true(f)) + return; + if (m().is_false(f)) { + m_inconsistent = true; + } + else { + SASSERT(!m_inconsistent); + } + m().inc_ref(f); + m_forms.push_back(f); + if (proofs_enabled()) { + m().inc_ref(pr); + m_proofs.push_back(pr); + } + if (unsat_core_enabled()) { + m().inc_ref(d); + m_deps.push_back(d); + } +} + +void assertion_stack::quick_process(bool save_first, expr * & f, expr_dependency * d) { + if (!m().is_and(f) && !(m().is_not(f) && m().is_or(to_app(f)->get_arg(0)))) { + if (!save_first) { + push_back(f, 0, d); + } + return; + } + typedef std::pair expr_pol; + sbuffer todo; + todo.push_back(expr_pol(f, true)); + while (!todo.empty()) { + if (m_inconsistent) + return; + expr_pol p = todo.back(); + expr * curr = p.first; + bool pol = p.second; + todo.pop_back(); + if (pol && m().is_and(curr)) { + app * t = to_app(curr); + unsigned i = t->get_num_args(); + while (i > 0) { + --i; + todo.push_back(expr_pol(t->get_arg(i), true)); + } + } + else if (!pol && m().is_or(curr)) { + app * t = to_app(curr); + unsigned i = t->get_num_args(); + while (i > 0) { + --i; + todo.push_back(expr_pol(t->get_arg(i), false)); + } + } + else if (m().is_not(curr)) { + todo.push_back(expr_pol(to_app(curr)->get_arg(0), !pol)); + } + else { + if (!pol) + curr = m().mk_not(curr); + if (save_first) { + f = curr; + save_first = false; + } + else { + push_back(curr, 0, d); + } + } + } +} + +void assertion_stack::process_and(bool save_first, app * f, proof * pr, expr_dependency * d, expr_ref & out_f, proof_ref & out_pr) { + unsigned num = f->get_num_args(); + for (unsigned i = 0; i < num; i++) { + if (m_inconsistent) + return; + slow_process(save_first && i == 0, f->get_arg(i), m().mk_and_elim(pr, i), d, out_f, out_pr); + } +} + +void assertion_stack::process_not_or(bool save_first, app * f, proof * pr, expr_dependency * d, expr_ref & out_f, proof_ref & out_pr) { + unsigned num = f->get_num_args(); + for (unsigned i = 0; i < num; i++) { + if (m_inconsistent) + return; + expr * child = f->get_arg(i); + if (m().is_not(child)) { + expr * not_child = to_app(child)->get_arg(0); + slow_process(save_first && i == 0, not_child, m().mk_not_or_elim(pr, i), d, out_f, out_pr); + } + else { + expr_ref not_child(m()); + not_child = m().mk_not(child); + slow_process(save_first && i == 0, not_child, m().mk_not_or_elim(pr, i), d, out_f, out_pr); + } + } +} + +void assertion_stack::slow_process(bool save_first, expr * f, proof * pr, expr_dependency * d, expr_ref & out_f, proof_ref & out_pr) { + if (m().is_and(f)) + process_and(save_first, to_app(f), pr, d, out_f, out_pr); + else if (m().is_not(f) && m().is_or(to_app(f)->get_arg(0))) + process_not_or(save_first, to_app(to_app(f)->get_arg(0)), pr, d, out_f, out_pr); + else if (save_first) { + out_f = f; + out_pr = pr; + } + else { + push_back(f, pr, d); + } +} + +void assertion_stack::slow_process(expr * f, proof * pr, expr_dependency * d) { + expr_ref out_f(m()); + proof_ref out_pr(m()); + slow_process(false, f, pr, d, out_f, out_pr); +} + + +void assertion_stack::assert_expr(expr * f, proof * pr, expr_dependency * d) { + SASSERT(proofs_enabled() == (pr != 0 && !m().is_undef_proof(pr))); + if (m_inconsistent) + return; + expr_ref new_f(m()); proof_ref new_pr(m()); expr_dependency_ref new_d(m()); + expand(f, pr, d, new_f, new_pr, new_d); + if (proofs_enabled()) + slow_process(f, pr, d); + else + quick_process(false, f, d); +} + +#ifdef Z3DEBUG +// bool assertion_stack::is_expanded(expr * f) { +// } +#endif + +void assertion_stack::update(unsigned i, expr * f, proof * pr, expr_dependency * d) { + SASSERT(i >= m_form_qhead); + SASSERT(proofs_enabled() == (pr != 0 && !m().is_undef_proof(pr))); + if (m_inconsistent) + return; + if (proofs_enabled()) { + expr_ref out_f(m()); + proof_ref out_pr(m()); + slow_process(true, f, pr, d, out_f, out_pr); + if (!m_inconsistent) { + if (m().is_false(out_f)) { + push_back(out_f, out_pr, d); + } + else { + m().inc_ref(out_f); + m().dec_ref(m_forms[i]); + m_forms[i] = out_f; + + m().inc_ref(out_pr); + m().dec_ref(m_proofs[i]); + m_proofs[i] = out_pr; + + if (unsat_core_enabled()) { + m().inc_ref(d); + m().dec_ref(m_deps[i]); + m_deps[i] = d; + } + } + } + } + else { + quick_process(true, f, d); + if (!m_inconsistent) { + if (m().is_false(f)) { + push_back(f, 0, d); + } + else { + m().inc_ref(f); + m().dec_ref(m_forms[i]); + m_forms[i] = f; + + if (unsat_core_enabled()) { + m().inc_ref(d); + m().dec_ref(m_deps[i]); + m_deps[i] = d; + } + } + } + } +} + +void assertion_stack::expand_and_update(unsigned i, expr * f, proof * pr, expr_dependency * d) { + SASSERT(i >= m_form_qhead); + SASSERT(proofs_enabled() == (pr != 0 && !m().is_undef_proof(pr))); + if (m_inconsistent) + return; + expr_ref new_f(m()); proof_ref new_pr(m()); expr_dependency_ref new_d(m()); + expand(f, pr, d, new_f, new_pr, new_d); + update(i, new_f, new_pr, new_d); +} + +void assertion_stack::push() { +} + +void assertion_stack::pop(unsigned num_scopes) { +} + +void assertion_stack::commit() { +} + +void assertion_stack::add_filter(func_decl * f) const { +} + +void assertion_stack::add_definition(app * c, expr * def, proof * pr, expr_dependency * dep) { + +} + +void assertion_stack::add_definition(func_decl * f, quantifier * q, proof * pr, expr_dependency * dep) { +} + +void assertion_stack::convert(model_ref & m) { +} + +void assertion_stack::display(std::ostream & out) const { + out << "(assertion-stack"; + unsigned sz = size(); + for (unsigned i = 0; i < sz; i++) { + out << "\n "; + if (i == m_form_qhead) + out << "==>\n"; + out << mk_ismt2_pp(form(i), m(), 2); + } + out << ")" << std::endl; +} + +bool assertion_stack::is_well_sorted() const { + unsigned sz = size(); + for (unsigned i = 0; i < sz; i++) { + expr * t = form(i); + if (!::is_well_sorted(m(), t)) + return false; + } + return true; +} diff --git a/lib/assertion_stack.h b/lib/assertion_stack.h new file mode 100644 index 000000000..ca08ed2f6 --- /dev/null +++ b/lib/assertion_stack.h @@ -0,0 +1,141 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + assertion_stack.h + +Abstract: + + It should be viewed as the "goal" object for incremental solvers. + The main difference is the support of push/pop operations. Like a + goal, an assertion_stack contains expressions, their proofs (if + proof generation is enabled), and dependencies (if unsat core + generation is enabled). + + The assertions on the stack are grouped by scope levels. Scoped + levels are created using push, and removed using pop. + + Assertions may be "committed". Whenever a push is executed, all + "uncommitted" assertions are automatically committed. + Only uncommitted assertions can be simplified/reduced. + + An assertion set has a limited model converter that only supports + definitions (for variable/function elimination) and filters (for fresh + symbols introduced by tactics). + + Some tactics support assertion_stacks and can be applied to them. + However, a tactic can only access the assertions on the top level. + The assertion stack also informs the tactic which declarations + can't be eliminated since they occur in the already committed part. + +Author: + + Leonardo de Moura (leonardo) 2012-02-17 + +Revision History: + +--*/ +#ifndef _ASSERTION_STACK_H_ +#define _ASSERTION_STACK_H_ + +#include"ast.h" +#include"model.h" +#include"expr_substitution.h" +#include"macro_substitution.h" + +class assertion_stack { + ast_manager & m_manager; + unsigned m_ref_count; + bool m_models_enabled; // model generation is enabled. + bool m_proofs_enabled; // proof production is enabled. m_manager.proofs_enabled() must be true if m_proofs_enabled == true + bool m_core_enabled; // unsat core extraction is enabled. + bool m_inconsistent; + ptr_vector m_forms; + ptr_vector m_proofs; + ptr_vector m_deps; + unsigned m_form_qhead; // position of first uncommitted assertion + unsigned m_mc_qhead; + + // Set of declarations that can't be eliminated + obj_hashtable m_forbidden_set; + func_decl_ref_vector m_forbidden; + + // Limited model converter support, it supports only extensions + // and filters. + // It should be viewed as combination of extension_model_converter and + // filter_model_converter for goals. + expr_substitution m_csubst; // substitution for eliminated constants + macro_substitution m_fsubst; // substitution for eliminated functions + + // Model converter is just a sequence of tagged pointers. + // Tag 0 (extension) func_decl was eliminated, and its definition is in m_vsubst or m_fsubst. + // Tag 1 (filter) func_decl was introduced by tactic, and must be removed from model. + ptr_vector m_mc; + + struct scope { + unsigned m_forms_lim; + unsigned m_forbidden_vars_lim; + unsigned m_mc_lim; + bool m_inconsistent_old; + }; + + svector m_scopes; + + void init(bool proofs_enabled, bool models_enabled, bool core_enabled); + void expand(expr * f, proof * pr, expr_dependency * dep, expr_ref & new_f, proof_ref & new_pr, expr_dependency_ref & new_dep); + void push_back(expr * f, proof * pr, expr_dependency * d); + void quick_process(bool save_first, expr * & f, expr_dependency * d); + void process_and(bool save_first, app * f, proof * pr, expr_dependency * d, expr_ref & out_f, proof_ref & out_pr); + void process_not_or(bool save_first, app * f, proof * pr, expr_dependency * d, expr_ref & out_f, proof_ref & out_pr); + void slow_process(bool save_first, expr * f, proof * pr, expr_dependency * d, expr_ref & out_f, proof_ref & out_pr); + void slow_process(expr * f, proof * pr, expr_dependency * d); + +public: + assertion_stack(ast_manager & m, bool models_enabled = true, bool core_enabled = true); + assertion_stack(ast_manager & m, bool proofs_enabled, bool models_enabled, bool core_enabled); + ~assertion_stack(); + + void reset(); + + void inc_ref() { ++m_ref_count; } + void dec_ref() { --m_ref_count; if (m_ref_count == 0) dealloc(this); } + + ast_manager & m() const { return m_manager; } + + bool models_enabled() const { return m_models_enabled; } + bool proofs_enabled() const { return m_proofs_enabled; } + bool unsat_core_enabled() const { return m_core_enabled; } + bool inconsistent() const { return m_inconsistent; } + + unsigned size() const { return m_forms.size(); } + unsigned qhead() const { return m_form_qhead; } + expr * form(unsigned i) const { return m_forms[i]; } + proof * pr(unsigned i) const { return proofs_enabled() ? static_cast(m_proofs[i]) : 0; } + expr_dependency * dep(unsigned i) const { return unsat_core_enabled() ? m_deps[i] : 0; } + + void assert_expr(expr * f, proof * pr, expr_dependency * d); + void assert_expr(expr * f) { + assert_expr(f, proofs_enabled() ? m().mk_asserted(f) : 0, 0); + } + void update(unsigned i, expr * f, proof * pr = 0, expr_dependency * dep = 0); + void expand_and_update(unsigned i, expr * f, proof * pr = 0, expr_dependency * d = 0); + + void commit(); + void push(); + void pop(unsigned num_scopes); + unsigned scope_lvl() const { return m_scopes.size(); } + + bool is_well_sorted() const; + + bool is_forbidden(func_decl * f) const { return m_forbidden_set.contains(f); } + void add_filter(func_decl * f) const; + void add_definition(app * c, expr * def, proof * pr, expr_dependency * dep); + void add_definition(func_decl * f, quantifier * q, proof * pr, expr_dependency * dep); + + void convert(model_ref & m); + + void display(std::ostream & out) const; +}; + +#endif diff --git a/lib/ast.cpp b/lib/ast.cpp new file mode 100644 index 000000000..ac854c1ac --- /dev/null +++ b/lib/ast.cpp @@ -0,0 +1,2903 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + ast.cpp + +Abstract: + + Expression DAG + +Author: + + Leonardo de Moura (leonardo) 2006-09-28. + +Revision History: + +--*/ +#include +#include"ast.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"buffer.h" +#include"warning.h" +#include"string_buffer.h" +#include"ast_util.h" +#include"ast_smt2_pp.h" +#include"arith_decl_plugin.h" +#include"array_decl_plugin.h" +#include"bv_decl_plugin.h" +#include"datatype_decl_plugin.h" +#include"dl_decl_plugin.h" +#include"seq_decl_plugin.h" +#include"float_decl_plugin.h" + +// ----------------------------------- +// +// parameter +// +// ----------------------------------- + +parameter::~parameter() { + if (m_kind == PARAM_RATIONAL) { + reinterpret_cast(m_rational)->~rational(); + } +} + +parameter::parameter(parameter const& other) { + m_kind = PARAM_INT; + m_int = 0; + *this = other; +} + +parameter& parameter::operator=(parameter const& other) { + if (this == &other) { + return *this; + } + if (m_kind == PARAM_RATIONAL) { + reinterpret_cast(m_rational)->~rational(); + } + m_kind = other.m_kind; + switch(other.m_kind) { + case PARAM_INT: m_int = other.get_int(); break; + case PARAM_AST: m_ast = other.get_ast(); break; + case PARAM_SYMBOL: new (m_symbol) symbol(other.get_symbol()); break; + case PARAM_RATIONAL: new (m_rational) rational(other.get_rational()); break; + case PARAM_DOUBLE: m_dval = other.m_dval; break; + case PARAM_EXTERNAL: m_ext_id = other.m_ext_id; break; + default: + UNREACHABLE(); + break; + } + return *this; +} + +void parameter::init_eh(ast_manager & m) { + if (is_ast()) { + m.inc_ref(get_ast()); + } +} + +void parameter::del_eh(ast_manager & m, family_id fid) { + if (is_ast()) { + m.dec_ref(get_ast()); + } + else if (is_external()) { + SASSERT(fid != null_family_id); + m.get_plugin(fid)->del(*this); + } +} + +bool parameter::operator==(parameter const & p) const { + if (m_kind != p.m_kind) return false; + switch(m_kind) { + case PARAM_INT: return m_int == p.m_int; + case PARAM_AST: return m_ast == p.m_ast; + case PARAM_SYMBOL: return get_symbol() == p.get_symbol(); + case PARAM_RATIONAL: return get_rational() == p.get_rational(); + case PARAM_DOUBLE: return m_dval == p.m_dval; + case PARAM_EXTERNAL: return m_ext_id == p.m_ext_id; + default: UNREACHABLE(); return false; + } +} + +unsigned parameter::hash() const { + unsigned b = 0; + switch(m_kind) { + case PARAM_INT: b = m_int; break; + case PARAM_AST: b = m_ast->hash(); break; + case PARAM_SYMBOL: b = get_symbol().hash(); break; + case PARAM_RATIONAL: b = get_rational().hash(); break; + case PARAM_DOUBLE: b = static_cast(m_dval); break; + case PARAM_EXTERNAL: b = m_ext_id; break; + } + return (b << 2) | m_kind; +} + +std::ostream& parameter::display(std::ostream& out) const { + switch(m_kind) { + case PARAM_INT: return out << get_int(); + case PARAM_SYMBOL: return out << get_symbol(); + case PARAM_RATIONAL: return out << get_rational(); + case PARAM_AST: return out << "#" << get_ast()->get_id(); + case PARAM_DOUBLE: return out << m_dval; + case PARAM_EXTERNAL: return out << "@" << m_ext_id; + default: + UNREACHABLE(); + return out << "[invalid parameter]"; + } +} + +void display_parameters(std::ostream & out, unsigned n, parameter const * p) { + if (n > 0) { + out << "["; + for (unsigned i = 0; i < n; i ++) + out << p[i] << (i < n-1 ? ":" : ""); + out << "]"; + } +} + +// ----------------------------------- +// +// family_manager +// +// ----------------------------------- + +family_id family_manager::get_family_id(symbol const & s) { + family_id r; + if (m_families.find(s, r)) { + return r; + } + r = m_next_id; + m_next_id++; + m_families.insert(s, r); + m_names.push_back(s); + return r; +} + +bool family_manager::has_family(symbol const & s) { + return m_families.contains(s); +} + +// ----------------------------------- +// +// decl_info +// +// ----------------------------------- + +decl_info::decl_info(family_id family_id, decl_kind k, unsigned num_parameters, + parameter const * parameters, bool private_params): + m_family_id(family_id), + m_kind(k), + m_parameters(num_parameters, const_cast(parameters)), + m_private_parameters(private_params) { +} + +decl_info::decl_info(decl_info const& other) : + m_family_id(other.m_family_id), + m_kind(other.m_kind), + m_parameters(other.m_parameters.size(), other.m_parameters.c_ptr()), + m_private_parameters(other.m_private_parameters) { +} + + +void decl_info::init_eh(ast_manager & m) { + vector::iterator it = m_parameters.begin(); + vector::iterator end = m_parameters.end(); + for (; it != end; ++it) { + it->init_eh(m); + } +} + +void decl_info::del_eh(ast_manager & m) { + vector::iterator it = m_parameters.begin(); + vector::iterator end = m_parameters.end(); + for (; it != end; ++it) { + it->del_eh(m, m_family_id); + } +} + +struct decl_info_child_hash_proc { + unsigned operator()(decl_info const * info, unsigned idx) const { return info->get_parameter(idx).hash(); } +}; + +unsigned decl_info::hash() const { + unsigned a = m_family_id; + unsigned b = m_kind; + unsigned c = get_num_parameters() == 0 ? 0 : get_composite_hash, decl_info_child_hash_proc>(this, get_num_parameters()); + mix(a, b, c); + return c; +} + +bool decl_info::operator==(decl_info const & info) const { + return m_family_id == info.m_family_id && m_kind == info.m_kind && + compare_arrays(m_parameters.begin(), info.m_parameters.begin(), m_parameters.size()); +} + +std::ostream & operator<<(std::ostream & out, decl_info const & info) { + out << ":fid " << info.get_family_id() << " :decl-kind " << info.get_decl_kind() << " :parameters ("; + for (unsigned i = 0; i < info.get_num_parameters(); i++) { + if (i > 0) out << " "; + out << info.get_parameter(i); + } + out << ")"; + return out; +} + +// ----------------------------------- +// +// sort_size +// +// ----------------------------------- + +std::ostream& operator<<(std::ostream& out, sort_size const & ss) { + if (ss.is_infinite()) { return out << "infinite"; } + else if (ss.is_very_big()) { return out << "very-big"; } + else { SASSERT(ss.is_finite()); return out << ss.size(); } +} + +// ----------------------------------- +// +// sort_info +// +// ----------------------------------- +std::ostream & operator<<(std::ostream & out, sort_info const & info) { + operator<<(out, static_cast(info)); + out << " :size " << info.get_num_elements(); + return out; +} + +// ----------------------------------- +// +// func_decl_info +// +// ----------------------------------- + +func_decl_info::func_decl_info(family_id family_id, decl_kind k, unsigned num_parameters, parameter const * parameters): + decl_info(family_id, k, num_parameters, parameters), + m_left_assoc(false), + m_right_assoc(false), + m_flat_associative(false), + m_commutative(false), + m_chainable(false), + m_pairwise(false), + m_injective(false), + m_idempotent(false), + m_skolem(false) { +} + +bool func_decl_info::operator==(func_decl_info const & info) const { + return decl_info::operator==(info) && + m_left_assoc == info.m_left_assoc && + m_right_assoc == info.m_right_assoc && + m_flat_associative == info.m_flat_associative && + m_commutative == info.m_commutative && + m_chainable == info.m_chainable && + m_pairwise == info.m_pairwise && + m_injective == info.m_injective && + m_skolem == info.m_skolem; +} + +std::ostream & operator<<(std::ostream & out, func_decl_info const & info) { + operator<<(out, static_cast(info)); + out << " :left-assoc " << info.is_left_associative(); + out << " :right-assoc " << info.is_right_associative(); + out << " :flat-associative " << info.is_flat_associative(); + out << " :commutative " << info.is_commutative(); + out << " :chainable " << info.is_chainable(); + out << " :pairwise " << info.is_pairwise(); + out << " :injective " << info.is_injective(); + out << " :idempotent " << info.is_idempotent(); + out << " :skolem " << info.is_skolem(); + return out; +} + +// ----------------------------------- +// +// ast +// +// ----------------------------------- + +char const * g_ast_kind_names[] = {"application", "variable", "quantifier", "sort", "function declaration" }; + +char const * get_ast_kind_name(ast_kind k) { + return g_ast_kind_names[k]; +} + +// ----------------------------------- +// +// func_decl +// +// ----------------------------------- + +func_decl::func_decl(symbol const & name, unsigned arity, sort * const * domain, sort * range, func_decl_info * info): + decl(AST_FUNC_DECL, name, info), + m_arity(arity), + m_range(range) { + memcpy(const_cast(get_domain()), domain, sizeof(sort *) * arity); +} + +// ----------------------------------- +// +// application +// +// ----------------------------------- + +static app_flags mk_const_flags() { + app_flags r; + r.m_depth = 1; + r.m_ground = true; + r.m_has_quantifiers = false; + r.m_has_labels = false; + return r; +} + +static app_flags mk_default_app_flags() { + app_flags r; + r.m_depth = 1; + r.m_ground = true; + r.m_has_quantifiers = false; + r.m_has_labels = false; + return r; +} + +app_flags app::g_constant_flags = mk_const_flags(); + +app::app(func_decl * decl, unsigned num_args, expr * const * args): + expr(AST_APP), + m_decl(decl), + m_num_args(num_args) { + for (unsigned i = 0; i < num_args; i++) + m_args[i] = args[i]; +} + +// ----------------------------------- +// +// quantifier +// +// ----------------------------------- + +quantifier::quantifier(bool forall, unsigned num_decls, sort * const * decl_sorts, symbol const * decl_names, expr * body, + int weight, symbol const & qid, symbol const & skid, unsigned num_patterns, expr * const * patterns, + unsigned num_no_patterns, expr * const * no_patterns): + expr(AST_QUANTIFIER), + m_forall(forall), + m_num_decls(num_decls), + m_expr(body), + m_depth(::get_depth(body) + 1), + m_weight(weight), + m_has_unused_vars(true), + m_has_labels(::has_labels(body)), + m_qid(qid), + m_skid(skid), + m_num_patterns(num_patterns), + m_num_no_patterns(num_no_patterns) { + SASSERT(m_num_patterns == 0 || m_num_no_patterns == 0); + + memcpy(const_cast(get_decl_sorts()), decl_sorts, sizeof(sort *) * num_decls); + memcpy(const_cast(get_decl_names()), decl_names, sizeof(symbol) * num_decls); + memcpy(const_cast(get_patterns()), patterns, sizeof(expr *) * num_patterns); + memcpy(const_cast(get_no_patterns()), no_patterns, sizeof(expr *) * num_no_patterns); +} + +// ----------------------------------- +// +// AST hash-consing +// +// ----------------------------------- + +unsigned get_node_size(ast const * n) { + switch(n->get_kind()) { + case AST_SORT: return to_sort(n)->get_size(); + case AST_FUNC_DECL: return to_func_decl(n)->get_size(); + case AST_APP: return to_app(n)->get_size(); + case AST_VAR: return to_var(n)->get_size(); + case AST_QUANTIFIER: return to_quantifier(n)->get_size(); + default: UNREACHABLE(); + } + return 0; +} + +bool compare_nodes(ast const * n1, ast const * n2) { + if (n1->get_kind() != n2->get_kind()) { + return false; + } + switch (n1->get_kind()) { + case AST_SORT: + if ((to_sort(n1)->get_info() == 0) != (to_sort(n2)->get_info() == 0)) { + return false; + } + if (to_sort(n1)->get_info() != 0 && !(*to_sort(n1)->get_info() == *to_sort(n2)->get_info())) { + return false; + } + return to_sort(n1)->get_name() == to_sort(n2)->get_name(); + case AST_FUNC_DECL: + if ((to_func_decl(n1)->get_info() == 0) != (to_func_decl(n2)->get_info() == 0)) { + return false; + } + if (to_func_decl(n1)->get_info() != 0 && !(*to_func_decl(n1)->get_info() == *to_func_decl(n2)->get_info())) { + return false; + } + return + to_func_decl(n1)->get_name() == to_func_decl(n2)->get_name() && + to_func_decl(n1)->get_arity() == to_func_decl(n2)->get_arity() && + to_func_decl(n1)->get_range() == to_func_decl(n2)->get_range() && + compare_arrays(to_func_decl(n1)->get_domain(), + to_func_decl(n2)->get_domain(), + to_func_decl(n1)->get_arity()); + case AST_APP: + return + to_app(n1)->get_decl() == to_app(n2)->get_decl() && + to_app(n1)->get_num_args() == to_app(n2)->get_num_args() && + compare_arrays(to_app(n1)->get_args(), to_app(n2)->get_args(), to_app(n1)->get_num_args()); + case AST_VAR: + return + to_var(n1)->get_idx() == to_var(n2)->get_idx() && + to_var(n1)->get_sort() == to_var(n2)->get_sort(); + case AST_QUANTIFIER: + return + to_quantifier(n1)->is_forall() == to_quantifier(n2)->is_forall() && + to_quantifier(n1)->get_num_decls() == to_quantifier(n2)->get_num_decls() && + compare_arrays(to_quantifier(n1)->get_decl_sorts(), + to_quantifier(n2)->get_decl_sorts(), + to_quantifier(n1)->get_num_decls()) && + to_quantifier(n1)->get_expr() == to_quantifier(n2)->get_expr() && + to_quantifier(n1)->get_weight() == to_quantifier(n2)->get_weight() && + to_quantifier(n1)->get_num_patterns() == to_quantifier(n2)->get_num_patterns() && + compare_arrays(to_quantifier(n1)->get_patterns(), + to_quantifier(n2)->get_patterns(), + to_quantifier(n1)->get_num_patterns()) && + to_quantifier(n1)->get_num_no_patterns() == to_quantifier(n2)->get_num_no_patterns() && + compare_arrays(to_quantifier(n1)->get_no_patterns(), + to_quantifier(n2)->get_no_patterns(), + to_quantifier(n1)->get_num_no_patterns()); + default: + UNREACHABLE(); + } + return false; +} + +template +inline unsigned ast_array_hash(T * const * array, unsigned size, unsigned init_value) { + if (size == 0) + return init_value; + switch (size) { + case 1: + return combine_hash(array[0]->hash(), init_value); + case 2: + return combine_hash(combine_hash(array[0]->hash(), array[1]->hash()), + init_value); + case 3: + return combine_hash(combine_hash(array[0]->hash(), array[1]->hash()), + combine_hash(array[2]->hash(), init_value)); + default: { + unsigned a, b, c; + a = b = 0x9e3779b9; + c = init_value; + while (size >= 3) { + size--; + a += array[size]->hash(); + size--; + b += array[size]->hash(); + size--; + c += array[size]->hash(); + mix(a, b, c); + } + switch (size) { + case 2: + b += array[1]->hash(); + __fallthrough; + case 1: + c += array[0]->hash(); + } + mix(a, b, c); + return c; + } } +} + +unsigned get_asts_hash(unsigned sz, ast * const* ns, unsigned init) { + return ast_array_hash(ns, sz, init); +} +unsigned get_apps_hash(unsigned sz, app * const* ns, unsigned init) { + return ast_array_hash(ns, sz, init); +} +unsigned get_exprs_hash(unsigned sz, expr * const* ns, unsigned init) { + return ast_array_hash(ns, sz, init); +} +unsigned get_sorts_hash(unsigned sz, sort * const* ns, unsigned init) { + return ast_array_hash(ns, sz, init); +} +unsigned get_decl_hash(unsigned sz, func_decl* const* ns, unsigned init) { + return ast_array_hash(ns, sz, init); +} + +unsigned get_node_hash(ast const * n) { + unsigned a, b, c; + + switch (n->get_kind()) { + case AST_SORT: + if (to_sort(n)->get_info() == 0) + return to_sort(n)->get_name().hash(); + else + return combine_hash(to_sort(n)->get_name().hash(), to_sort(n)->get_info()->hash()); + case AST_FUNC_DECL: + return ast_array_hash(to_func_decl(n)->get_domain(), to_func_decl(n)->get_arity(), + to_func_decl(n)->get_info() == 0 ? + to_func_decl(n)->get_name().hash() : combine_hash(to_func_decl(n)->get_name().hash(), to_func_decl(n)->get_info()->hash())); + case AST_APP: + return ast_array_hash(to_app(n)->get_args(), + to_app(n)->get_num_args(), + to_app(n)->get_decl()->hash()); + case AST_VAR: + return combine_hash(to_var(n)->get_idx(), to_var(n)->get_sort()->hash()); + case AST_QUANTIFIER: + a = ast_array_hash(to_quantifier(n)->get_decl_sorts(), + to_quantifier(n)->get_num_decls(), + to_quantifier(n)->is_forall() ? 31 : 19); + b = to_quantifier(n)->get_num_patterns(); + c = to_quantifier(n)->get_expr()->hash(); + mix(a,b,c); + return c; + default: + UNREACHABLE(); + } + return 0; +} + +void ast_table::erase(ast * n) { + // Customized erase method + // It uses two important properties: + // 1. n is known to be in the table. + // 2. operator== can be used instead of compare_nodes (big savings) + unsigned mask = m_slots - 1; + unsigned h = n->hash(); + unsigned idx = h & mask; + cell * c = m_table + idx; + SASSERT(!c->is_free()); + cell * prev = 0; + while (true) { + if (c->m_data == n) { + m_size--; + if (prev == 0) { + cell * next = c->m_next; + if (next == 0) { + m_used_slots--; + c->mark_free(); + SASSERT(c->is_free()); + } + else { + *c = *next; + recycle_cell(next); + } + } + else { + prev->m_next = c->m_next; + recycle_cell(c); + } + return; + } + CHS_CODE(m_collisions++;); + prev = c; + c = c->m_next; + SASSERT(c); + } +} + +// ----------------------------------- +// +// decl_plugin +// +// ----------------------------------- + +func_decl * decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned num_args, expr * const * args, sort * range) { + ptr_buffer sorts; + for (unsigned i = 0; i < num_args; i++) { + sorts.push_back(m_manager->get_sort(args[i])); + } + return mk_func_decl(k, num_parameters, parameters, num_args, sorts.c_ptr(), range); +} + +// ----------------------------------- +// +// basic_decl_plugin (i.e., builtin plugin) +// +// ----------------------------------- + +basic_decl_plugin::basic_decl_plugin(): + m_bool_sort(0), + m_true_decl(0), + m_false_decl(0), + m_and_decl(0), + m_or_decl(0), + m_iff_decl(0), + m_xor_decl(0), + m_not_decl(0), + m_implies_decl(0), + + m_proof_sort(0), + m_undef_decl(0), + m_true_pr_decl(0), + m_asserted_decl(0), + m_goal_decl(0), + m_modus_ponens_decl(0), + m_reflexivity_decl(0), + m_symmetry_decl(0), + m_transitivity_decl(0), + m_quant_intro_decl(0), + m_and_elim_decl(0), + m_not_or_elim_decl(0), + m_rewrite_decl(0), + m_pull_quant_decl(0), + m_pull_quant_star_decl(0), + m_push_quant_decl(0), + m_elim_unused_vars_decl(0), + m_der_decl(0), + m_quant_inst_decl(0), + + m_hypothesis_decl(0), + m_iff_true_decl(0), + m_iff_false_decl(0), + m_commutativity_decl(0), + m_def_axiom_decl(0), + m_lemma_decl(0), + + m_def_intro_decl(0), + m_iff_oeq_decl(0), + m_skolemize_decl(0), + m_mp_oeq_decl(0) { +} + +bool basic_decl_plugin::check_proof_sorts(basic_op_kind k, unsigned arity, sort * const * domain) const { + if (k == PR_UNDEF) + return arity == 0; + if (arity == 0) + return false; + else { + for (unsigned i = 0; i < arity - 1; i++) + if (domain[i] != m_proof_sort) + return false; + return domain[arity-1] == m_bool_sort || domain[arity-1] == m_proof_sort; + } +} + +bool basic_decl_plugin::check_proof_args(basic_op_kind k, unsigned num_args, expr * const * args) const { + if (k == PR_UNDEF) + return num_args == 0; + if (num_args == 0) + return false; + else { + for (unsigned i = 0; i < num_args - 1; i++) + if (m_manager->get_sort(args[i]) != m_proof_sort) + return false; + return + m_manager->get_sort(args[num_args - 1]) == m_bool_sort || + m_manager->get_sort(args[num_args - 1]) == m_proof_sort; + } +} + +func_decl * basic_decl_plugin::mk_bool_op_decl(char const * name, basic_op_kind k, unsigned num_args, bool assoc, bool comm, bool idempotent, + bool flat_associative, bool chainable) { + ptr_buffer domain; + for (unsigned i = 0; i < num_args; i++) + domain.push_back(m_bool_sort); + func_decl_info info(m_family_id, k); + info.set_associative(assoc); + info.set_flat_associative(flat_associative); + info.set_commutative(comm); + info.set_idempotent(idempotent); + info.set_chainable(chainable); + func_decl * d = m_manager->mk_func_decl(symbol(name), num_args, domain.c_ptr(), m_bool_sort, info); + m_manager->inc_ref(d); + return d; +} + +func_decl * basic_decl_plugin::mk_implies_decl() { + sort * domain[2] = { m_bool_sort, m_bool_sort }; + func_decl_info info(m_family_id, OP_IMPLIES); + info.set_right_associative(); + func_decl * d = m_manager->mk_func_decl(symbol("=>"), 2, domain, m_bool_sort, info); + m_manager->inc_ref(d); + return d; +} + +func_decl * basic_decl_plugin::mk_proof_decl( + char const * name, basic_op_kind k, + unsigned num_parameters, parameter const* params, unsigned num_parents) { + ptr_buffer domain; + for (unsigned i = 0; i < num_parents; i++) + domain.push_back(m_proof_sort); + domain.push_back(m_bool_sort); + func_decl_info info(m_family_id, k, num_parameters, params); + return m_manager->mk_func_decl(symbol(name), num_parents+1, domain.c_ptr(), m_proof_sort, info); +} + +func_decl * basic_decl_plugin::mk_proof_decl(char const * name, basic_op_kind k, unsigned num_parents) { + ptr_buffer domain; + for (unsigned i = 0; i < num_parents; i++) + domain.push_back(m_proof_sort); + domain.push_back(m_bool_sort); + func_decl * d = m_manager->mk_func_decl(symbol(name), num_parents+1, domain.c_ptr(), m_proof_sort, func_decl_info(m_family_id, k)); + m_manager->inc_ref(d); + return d; +} + +func_decl * basic_decl_plugin::mk_compressed_proof_decl(char const * name, basic_op_kind k, unsigned num_parents) { + ptr_buffer domain; + for (unsigned i = 0; i < num_parents; i++) + domain.push_back(m_proof_sort); + func_decl * d = m_manager->mk_func_decl(symbol(name), num_parents, domain.c_ptr(), m_proof_sort, func_decl_info(m_family_id, k)); + m_manager->inc_ref(d); + return d; +} + +func_decl * basic_decl_plugin::mk_proof_decl(char const * name, basic_op_kind k, unsigned num_parents, ptr_vector & cache) { + if (num_parents >= cache.size()) { + cache.resize(num_parents+1, 0); + } + if (cache[num_parents] == 0) { + cache[num_parents] = mk_proof_decl(name, k, num_parents); + } + return cache[num_parents]; +} + +func_decl * basic_decl_plugin::mk_proof_decl(basic_op_kind k, unsigned num_parameters, parameter const* params, unsigned num_parents) { + switch(k) { + case PR_TH_LEMMA: { + return mk_proof_decl("th-lemma", k, num_parameters, params, num_parents); + } + case PR_QUANT_INST: { + SASSERT(num_parents == 0); + return mk_proof_decl("quant-inst", k, num_parameters, params, num_parents); + } + default: + UNREACHABLE(); + return 0; + } +} + +#define MK_DECL(_decl_,_mk_decl_) if (!_decl_) _decl_ = _mk_decl_; return _decl_; + + +func_decl * basic_decl_plugin::mk_proof_decl(char const* name, basic_op_kind k, unsigned num_parents, func_decl*& fn) { + if (!fn) { + fn = mk_proof_decl(name, k, num_parents); + } + return fn; +} + +func_decl * basic_decl_plugin::mk_proof_decl(basic_op_kind k, unsigned num_parents) { + SASSERT(k == PR_UNDEF || m_manager->proofs_enabled()); + switch (static_cast(k)) { + // + // A description of the semantics of the proof + // declarations is provided in z3_api.h + // + case PR_UNDEF: return m_undef_decl; + case PR_TRUE: return mk_proof_decl("true-axiom", k, 0, m_true_pr_decl); + case PR_ASSERTED: return mk_proof_decl("asserted", k, 0, m_asserted_decl); + case PR_GOAL: return mk_proof_decl("goal", k, 2, m_goal_decl); + case PR_MODUS_PONENS: return mk_proof_decl("mp", k, 2, m_modus_ponens_decl); + case PR_REFLEXIVITY: return mk_proof_decl("refl", k, 0, m_reflexivity_decl); + case PR_SYMMETRY: return mk_proof_decl("symm", k, 1, m_symmetry_decl); + case PR_TRANSITIVITY: return mk_proof_decl("trans", k, 2, m_transitivity_decl); + case PR_TRANSITIVITY_STAR: return mk_proof_decl("trans*", k, num_parents, m_transitivity_star_decls); + case PR_MONOTONICITY: return mk_proof_decl("monotonicity", k, num_parents, m_monotonicity_decls); + case PR_QUANT_INTRO: return mk_proof_decl("quant-intro", k, 1, m_quant_intro_decl); + case PR_DISTRIBUTIVITY: return mk_proof_decl("distributivity", k, num_parents, m_distributivity_decls); + case PR_AND_ELIM: return mk_proof_decl("and-elim", k, 1, m_and_elim_decl); + case PR_NOT_OR_ELIM: return mk_proof_decl("not-or-elim", k, 1, m_not_or_elim_decl); + case PR_REWRITE: return mk_proof_decl("rewrite", k, 0, m_rewrite_decl); + case PR_REWRITE_STAR: return mk_proof_decl("rewrite*", k, num_parents, m_rewrite_star_decls); + case PR_PULL_QUANT: return mk_proof_decl("pull-quant", k, 0, m_pull_quant_decl); + case PR_PULL_QUANT_STAR: return mk_proof_decl("pull-quant*", k, 0, m_pull_quant_star_decl); + case PR_PUSH_QUANT: return mk_proof_decl("push-quant", k, 0, m_push_quant_decl); + case PR_ELIM_UNUSED_VARS: return mk_proof_decl("elim-unused", k, 0, m_elim_unused_vars_decl); + case PR_DER: return mk_proof_decl("der", k, 0, m_der_decl); + case PR_QUANT_INST: return mk_proof_decl("quant-inst", k, 0, m_quant_inst_decl); + case PR_HYPOTHESIS: return mk_proof_decl("hypothesis", k, 0, m_hypothesis_decl); + case PR_LEMMA: return mk_proof_decl("lemma", k, 1, m_lemma_decl); + case PR_UNIT_RESOLUTION: return mk_proof_decl("unit-resolution", k, num_parents, m_unit_resolution_decls); + case PR_IFF_TRUE: return mk_proof_decl("iff-true", k, 1, m_iff_true_decl); + case PR_IFF_FALSE: return mk_proof_decl("iff-false", k, 1, m_iff_false_decl); + case PR_COMMUTATIVITY: return mk_proof_decl("commutativity", k, 0, m_commutativity_decl); + case PR_DEF_AXIOM: return mk_proof_decl("def-axiom", k, 0, m_def_axiom_decl); + case PR_DEF_INTRO: return mk_proof_decl("intro-def", k, 0, m_def_intro_decl); + case PR_APPLY_DEF: return mk_proof_decl("apply-def", k, num_parents, m_apply_def_decls); + case PR_IFF_OEQ: return mk_proof_decl("iff~", k, 1, m_iff_oeq_decl); + case PR_NNF_POS: return mk_proof_decl("nnf-pos", k, num_parents, m_nnf_pos_decls); + case PR_NNF_NEG: return mk_proof_decl("nnf-neg", k, num_parents, m_nnf_neg_decls); + case PR_NNF_STAR: return mk_proof_decl("nnf*", k, num_parents, m_nnf_star_decls); + case PR_CNF_STAR: return mk_proof_decl("cnf*", k, num_parents, m_cnf_star_decls); + case PR_SKOLEMIZE: return mk_proof_decl("sk", k, 0, m_skolemize_decl); + case PR_MODUS_PONENS_OEQ: return mk_proof_decl("mp~", k, 2, m_mp_oeq_decl); + case PR_TH_LEMMA: return mk_proof_decl("th-lemma", k, num_parents, m_th_lemma_decls); + default: + UNREACHABLE(); + return 0; + } +} + +void basic_decl_plugin::set_manager(ast_manager * m, family_id id) { + decl_plugin::set_manager(m, id); + + m_bool_sort = m->mk_sort(symbol("Bool"), sort_info(id, BOOL_SORT, sort_size(2))); + m->inc_ref(m_bool_sort); + + m_true_decl = mk_bool_op_decl("true", OP_TRUE); + m_false_decl = mk_bool_op_decl("false", OP_FALSE); + m_and_decl = mk_bool_op_decl("and", OP_AND, 2, true, true, true, true); + m_or_decl = mk_bool_op_decl("or", OP_OR, 2, true, true, true, true); + m_iff_decl = mk_bool_op_decl("iff", OP_IFF, 2, false, true, false, false, true); + m_xor_decl = mk_bool_op_decl("xor", OP_XOR, 2, true, true); + m_not_decl = mk_bool_op_decl("not", OP_NOT, 1); + m_implies_decl = mk_implies_decl(); + + m_proof_sort = m->mk_sort(symbol("Proof"), sort_info(id, PROOF_SORT)); + m->inc_ref(m_proof_sort); + + m_undef_decl = mk_compressed_proof_decl("undef", PR_UNDEF, 0); +} + +void basic_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { + if (logic == symbol::null) + sort_names.push_back(builtin_name("bool", BOOL_SORT)); + sort_names.push_back(builtin_name("Bool", BOOL_SORT)); +} + +void basic_decl_plugin::get_op_names(svector & op_names, symbol const & logic) { + op_names.push_back(builtin_name("true", OP_TRUE)); + op_names.push_back(builtin_name("false", OP_FALSE)); + op_names.push_back(builtin_name("=", OP_EQ)); + op_names.push_back(builtin_name("distinct", OP_DISTINCT)); + op_names.push_back(builtin_name("ite", OP_ITE)); + op_names.push_back(builtin_name("and", OP_AND)); + op_names.push_back(builtin_name("or", OP_OR)); + op_names.push_back(builtin_name("xor", OP_XOR)); + op_names.push_back(builtin_name("not", OP_NOT)); + op_names.push_back(builtin_name("=>", OP_IMPLIES)); + if (logic == symbol::null) { + // user friendly aliases + op_names.push_back(builtin_name("implies", OP_IMPLIES)); + op_names.push_back(builtin_name("iff", OP_IFF)); + op_names.push_back(builtin_name("if_then_else", OP_ITE)); + op_names.push_back(builtin_name("if", OP_ITE)); + op_names.push_back(builtin_name("&&", OP_AND)); + op_names.push_back(builtin_name("||", OP_OR)); + op_names.push_back(builtin_name("equals", OP_EQ)); + op_names.push_back(builtin_name("equiv", OP_IFF)); + } +} + +bool basic_decl_plugin::is_value(app* a) const { + return a->get_decl() == m_true_decl || a->get_decl() == m_false_decl; +} + +void basic_decl_plugin::finalize() { +#define DEC_REF(FIELD) if (FIELD) { m_manager->dec_ref(FIELD); } +#define DEC_ARRAY_REF(FIELD) m_manager->dec_array_ref(FIELD.size(), FIELD.begin()) + DEC_REF(m_bool_sort); + DEC_REF(m_true_decl); + DEC_REF(m_false_decl); + DEC_REF(m_and_decl); + DEC_REF(m_or_decl); + DEC_REF(m_not_decl); + DEC_REF(m_iff_decl); + DEC_REF(m_xor_decl); + DEC_REF(m_implies_decl); + DEC_ARRAY_REF(m_eq_decls); + DEC_ARRAY_REF(m_ite_decls); + + DEC_ARRAY_REF(m_oeq_decls); + DEC_REF(m_proof_sort); + DEC_REF(m_undef_decl); + DEC_REF(m_true_pr_decl); + DEC_REF(m_asserted_decl); + DEC_REF(m_goal_decl); + DEC_REF(m_modus_ponens_decl); + DEC_REF(m_reflexivity_decl); + DEC_REF(m_symmetry_decl); + DEC_REF(m_transitivity_decl); + DEC_REF(m_quant_intro_decl); + DEC_REF(m_and_elim_decl); + DEC_REF(m_not_or_elim_decl); + DEC_REF(m_rewrite_decl); + DEC_REF(m_pull_quant_decl); + DEC_REF(m_pull_quant_star_decl); + DEC_REF(m_push_quant_decl); + DEC_REF(m_elim_unused_vars_decl); + DEC_REF(m_der_decl); + DEC_REF(m_quant_inst_decl); + DEC_ARRAY_REF(m_monotonicity_decls); + DEC_ARRAY_REF(m_transitivity_star_decls); + DEC_ARRAY_REF(m_distributivity_decls); + DEC_ARRAY_REF(m_assoc_flat_decls); + DEC_ARRAY_REF(m_rewrite_star_decls); + + DEC_REF(m_hypothesis_decl); + DEC_REF(m_iff_true_decl); + DEC_REF(m_iff_false_decl); + DEC_REF(m_commutativity_decl); + DEC_REF(m_def_axiom_decl); + DEC_REF(m_lemma_decl); + DEC_ARRAY_REF(m_unit_resolution_decls); + + DEC_REF(m_def_intro_decl); + DEC_REF(m_iff_oeq_decl); + DEC_REF(m_skolemize_decl); + DEC_REF(m_mp_oeq_decl); + DEC_ARRAY_REF(m_apply_def_decls); + DEC_ARRAY_REF(m_nnf_pos_decls); + DEC_ARRAY_REF(m_nnf_neg_decls); + DEC_ARRAY_REF(m_nnf_star_decls); + DEC_ARRAY_REF(m_cnf_star_decls); + + DEC_ARRAY_REF(m_th_lemma_decls); + +} + +sort * basic_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) { + if (k == BOOL_SORT) + return m_bool_sort; + SASSERT(k == PROOF_SORT); + return m_proof_sort; +} + +func_decl * basic_decl_plugin::mk_eq_decl_core(char const * name, decl_kind k, sort * s, ptr_vector & cache) { + unsigned id = s->get_decl_id(); + force_ptr_array_size(cache, id + 1); + if (cache[id] == 0) { + sort * domain[2] = { s, s}; + func_decl_info info(m_family_id, k); + info.set_commutative(); + info.set_chainable(); + func_decl * decl = m_manager->mk_func_decl(symbol(name), 2, domain, m_bool_sort, info); + SASSERT(decl->is_chainable()); + cache[id] = decl; + m_manager->inc_ref(decl); + } + return cache[id]; +} + +func_decl * basic_decl_plugin::mk_ite_decl(sort * s) { + unsigned id = s->get_decl_id(); + force_ptr_array_size(m_ite_decls, id + 1); + if (m_ite_decls[id] == 0) { + sort * domain[3] = { m_bool_sort, s, s}; + func_decl * decl = m_manager->mk_func_decl(symbol("if"), 3, domain, s, func_decl_info(m_family_id, OP_ITE)); + m_ite_decls[id] = decl; + m_manager->inc_ref(decl); + } + return m_ite_decls[id]; +} + +func_decl * basic_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + switch (static_cast(k)) { + case OP_TRUE: return m_true_decl; + case OP_FALSE: return m_false_decl; + case OP_AND: return m_and_decl; + case OP_OR: return m_or_decl; + case OP_NOT: return m_not_decl; + case OP_IFF: return m_iff_decl; + case OP_IMPLIES: return m_implies_decl; + case OP_XOR: return m_xor_decl; + case OP_ITE: return arity == 3 ? mk_ite_decl(domain[1]) : 0; + // eq and oeq must have at least two arguments, they can have more since they are chainable + case OP_EQ: return arity >= 2 ? mk_eq_decl_core("=", OP_EQ, domain[0], m_eq_decls) : 0; + case OP_OEQ: return arity >= 2 ? mk_eq_decl_core("~", OP_OEQ, domain[0], m_oeq_decls) : 0; + case OP_DISTINCT: { + func_decl_info info(m_family_id, OP_DISTINCT); + info.set_pairwise(); + return m_manager->mk_func_decl(symbol("distinct"), arity, domain, m_bool_sort, info); + } + default: + break; + } + + SASSERT(is_proof(k)); + + if (!check_proof_sorts(static_cast(k), arity, domain)) + m_manager->raise_exception("Invalid proof object."); + + if (num_parameters == 0) { + return mk_proof_decl(static_cast(k), arity - 1); + } + return mk_proof_decl(static_cast(k), num_parameters, parameters, arity - 1); +} + +func_decl * basic_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned num_args, expr * const * args, sort * range) { + switch (static_cast(k)) { + case OP_TRUE: return m_true_decl; + case OP_FALSE: return m_false_decl; + case OP_AND: return m_and_decl; + case OP_OR: return m_or_decl; + case OP_NOT: return m_not_decl; + case OP_IFF: return m_iff_decl; + case OP_IMPLIES: return m_implies_decl; + case OP_XOR: return m_xor_decl; + case OP_ITE: return num_args == 3 ? mk_ite_decl(m_manager->get_sort(args[1])): 0; + // eq and oeq must have at least two arguments, they can have more since they are chainable + case OP_EQ: return num_args >= 2 ? mk_eq_decl_core("=", OP_EQ, m_manager->get_sort(args[0]), m_eq_decls) : 0; + case OP_OEQ: return num_args >= 2 ? mk_eq_decl_core("~", OP_OEQ, m_manager->get_sort(args[0]), m_oeq_decls) : 0; + case OP_DISTINCT: + return decl_plugin::mk_func_decl(k, num_parameters, parameters, num_args, args, range); + default: + break; + } + + SASSERT(is_proof(k)); + + if (!check_proof_args(static_cast(k), num_args, args)) + m_manager->raise_exception("Invalid proof object."); + + if (num_parameters == 0) { + return mk_proof_decl(static_cast(k), num_args - 1); + } + return mk_proof_decl(static_cast(k), num_parameters, parameters, num_args - 1); +} + +expr * basic_decl_plugin::get_some_value(sort * s) { + if (s == m_bool_sort) + return m_manager->mk_false(); + return 0; +} + +// ----------------------------------- +// +// label_decl_plugin +// +// ----------------------------------- + +label_decl_plugin::label_decl_plugin(): + m_lblpos("lblpos"), + m_lblneg("lblneg"), + m_lbllit("lbl-lit") { +} + +label_decl_plugin::~label_decl_plugin() { +} + +void label_decl_plugin::set_manager(ast_manager * m, family_id id) { + decl_plugin::set_manager(m, id); +} + +sort * label_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) { + UNREACHABLE(); + return 0; +} + +func_decl * label_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + if (k == OP_LABEL) { + if (arity != 1 || num_parameters < 2 || !parameters[0].is_int() || !parameters[1].is_symbol() || !m_manager->is_bool(domain[0])) { + m_manager->raise_exception("invalid label declaration"); + return 0; + } + for (unsigned i = 2; i < num_parameters; i++) { + if (!parameters[i].is_symbol()) { + m_manager->raise_exception("invalid label declaration"); + return 0; + } + } + return m_manager->mk_func_decl(parameters[0].get_int() ? m_lblpos : m_lblneg, arity, domain, domain[0], + func_decl_info(m_family_id, OP_LABEL, num_parameters, parameters)); + } + else { + SASSERT(k == OP_LABEL_LIT); + if (arity != 0) { + m_manager->raise_exception("invalid label literal declaration"); + return 0; + } + for (unsigned i = 0; i < num_parameters; i++) { + if (!parameters[i].is_symbol()) { + m_manager->raise_exception("invalid label literal declaration"); + return 0; + } + } + return m_manager->mk_func_decl(m_lbllit, 0, static_cast(0), m_manager->mk_bool_sort(), + func_decl_info(m_family_id, OP_LABEL_LIT, num_parameters, parameters)); + } +} + +// ----------------------------------- +// +// pattern_decl_plugin +// +// ----------------------------------- + +sort * pattern_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) { + UNREACHABLE(); + return 0; +} + +func_decl * pattern_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + return m_manager->mk_func_decl(symbol("pattern"), arity, domain, + m_manager->mk_bool_sort(), // the range can be an arbitrary sort. + func_decl_info(m_family_id, OP_PATTERN)); +} + +// ----------------------------------- +// +// model_value_decl_plugin +// +// ----------------------------------- + +sort * model_value_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) { + UNREACHABLE(); + return 0; +} + +func_decl * model_value_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + SASSERT(k == OP_MODEL_VALUE); + if (arity != 0 || num_parameters != 2 || !parameters[0].is_int() || !parameters[1].is_ast() || !is_sort(parameters[1].get_ast())) { + UNREACHABLE(); + m_manager->raise_exception("invalid model value"); + return 0; + } + int idx = parameters[0].get_int(); + sort * s = to_sort(parameters[1].get_ast()); + string_buffer<64> buffer; + buffer << s->get_name().bare_str() << "!val!" << idx; + func_decl_info info(m_family_id, k, num_parameters, parameters); + info.m_private_parameters = true; + return m_manager->mk_func_decl(symbol(buffer.c_str()), 0, static_cast(0), s, info); +} + +bool model_value_decl_plugin::is_value(app* n) const { + return is_app_of(n, m_family_id, OP_MODEL_VALUE); +} + +// ----------------------------------- +// +// user_sort_plugin +// +// ----------------------------------- + +sort * user_sort_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) { + SASSERT(m_family_id != null_family_id); + SASSERT(k < static_cast(m_sort_names.size())); + sort_info si(m_family_id, k, num_parameters, parameters); + return m_manager->mk_sort(m_sort_names[k], si); +} + +func_decl * user_sort_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + UNREACHABLE(); + return 0; +} + +decl_kind user_sort_plugin::register_name(symbol s) { + decl_kind k; + if (m_name2decl_kind.find(s, k)) + return k; + k = m_sort_names.size(); + m_sort_names.push_back(s); + m_name2decl_kind.insert(s, k); + return k; +} + +decl_plugin * user_sort_plugin::mk_fresh() { + user_sort_plugin * p = alloc(user_sort_plugin); + svector::iterator it = m_sort_names.begin(); + svector::iterator end = m_sort_names.end(); + for (; it != end; ++it) + p->register_name(*it); + return p; +} + + +// ----------------------------------- +// +// ast_manager +// +// ----------------------------------- + +ast_manager::ast_manager(proof_gen_mode m, std::ostream *trace_stream, bool is_format_manager): + m_alloc("ast_manager"), + m_expr_array_manager(*this, m_alloc), + m_expr_dependency_manager(*this, m_alloc), + m_expr_dependency_array_manager(*this, m_alloc), + m_proof_mode(m), + m_trace_stream(trace_stream) { + if (!is_format_manager) + m_format_manager = alloc(ast_manager, PGM_DISABLED, trace_stream, true); + else + m_format_manager = 0; + init(); +} + +ast_manager::ast_manager(ast_manager const & src, bool disable_proofs): + m_alloc("ast_manager"), + m_expr_array_manager(*this, m_alloc), + m_expr_dependency_manager(*this, m_alloc), + m_expr_dependency_array_manager(*this, m_alloc), + m_proof_mode(disable_proofs ? PGM_DISABLED : src.m_proof_mode), + m_trace_stream(src.m_trace_stream) { + SASSERT(!src.is_format_manager()); + m_format_manager = 0; + init(); + copy_families_plugins(src); +} + +void ast_manager::init() { + m_debug_ref_count = false; + m_fresh_id = 0; + m_expr_id_gen.reset(0); + m_decl_id_gen.reset(c_first_decl_id); + m_some_value_proc = 0; + m_basic_family_id = get_family_id("basic"); + m_label_family_id = get_family_id("label"); + m_pattern_family_id = get_family_id("pattern"); + m_model_value_family_id = get_family_id("model-value"); + m_user_sort_family_id = get_family_id("user-sort"); + basic_decl_plugin * plugin = alloc(basic_decl_plugin); + register_plugin(m_basic_family_id, plugin); + m_bool_sort = plugin->mk_bool_sort(); + inc_ref(m_bool_sort); + m_proof_sort = plugin->mk_proof_sort(); + inc_ref(m_proof_sort); + m_undef_proof = mk_const(m_basic_family_id, PR_UNDEF); + inc_ref(m_undef_proof); + register_plugin(m_label_family_id, alloc(label_decl_plugin)); + register_plugin(m_pattern_family_id, alloc(pattern_decl_plugin)); + register_plugin(m_model_value_family_id, alloc(model_value_decl_plugin)); + register_plugin(m_user_sort_family_id, alloc(user_sort_plugin)); + m_true = mk_const(m_basic_family_id, OP_TRUE); + inc_ref(m_true); + m_false = mk_const(m_basic_family_id, OP_FALSE); + inc_ref(m_false); +} + +ast_manager::~ast_manager() { + SASSERT(is_format_manager() || !m_family_manager.has_family(symbol("format"))); + dec_ref(m_bool_sort); + dec_ref(m_proof_sort); + dec_ref(m_true); + dec_ref(m_false); + dec_ref(m_undef_proof); + ptr_vector::iterator it = m_plugins.begin(); + ptr_vector::iterator end = m_plugins.end(); + for (; it != end; ++it) { + if (*it) + (*it)->finalize(); + } + it = m_plugins.begin(); + for (; it != end; ++it) { + if (*it) + dealloc(*it); + } + DEBUG_CODE({ + if (!m_ast_table.empty()) + std::cout << "ast_manager LEAKED: " << m_ast_table.size() << std::endl; + }); +#if 1 + DEBUG_CODE({ + ast_table::iterator it_a = m_ast_table.begin(); + ast_table::iterator end_a = m_ast_table.end(); + for (; it_a != end_a; ++it_a) { + ast* a = (*it_a); + std::cout << "Leaked: "; + if (is_sort(a)) { + std::cout << to_sort(a)->get_name() << "\n"; + } + else { + std::cout << mk_ll_pp(a, *this, false); + } + } + }); +#endif + if (m_format_manager != 0) + dealloc(m_format_manager); +} + +void ast_manager::compact_memory() { + m_alloc.consolidate(); + unsigned capacity = m_ast_table.capacity(); + if (capacity > 4*m_ast_table.size()) { + ast_table new_ast_table; + ast_table::iterator it = m_ast_table.begin(); + ast_table::iterator end = m_ast_table.end(); + for (; it != end; ++it) { + new_ast_table.insert(*it); + } + m_ast_table.swap(new_ast_table); + IF_VERBOSE(10, verbose_stream() << "(ast-table :prev-capacity " << capacity + << " :capacity " << m_ast_table.capacity() << " :size " << m_ast_table.size() << ")\n";); + } + else { + IF_VERBOSE(10, verbose_stream() << "(ast-table :capacity " << m_ast_table.capacity() << " :size " << m_ast_table.size() << ")\n";); + } +} + +void ast_manager::compress_ids() { + ptr_vector asts; + m_expr_id_gen.cleanup(); + m_decl_id_gen.cleanup(c_first_decl_id); + ast_table::iterator it = m_ast_table.begin(); + ast_table::iterator end = m_ast_table.end(); + for (; it != end; ++it) { + ast * n = *it; + if (is_decl(n)) + n->m_id = m_decl_id_gen.mk(); + else + n->m_id = m_expr_id_gen.mk(); + asts.push_back(n); + } + m_ast_table.finalize(); + ptr_vector::iterator it2 = asts.begin(); + ptr_vector::iterator end2 = asts.end(); + for (; it2 != end2; ++it2) + m_ast_table.insert(*it2); +} + +void ast_manager::raise_exception(char const * msg) { + throw ast_exception(msg); +} + +void ast_manager::copy_families_plugins(ast_manager const & from) { + TRACE("copy_families_plugins", + tout << "target:\n"; + for (family_id fid = 0; m_family_manager.has_family(fid); fid++) { + tout << "fid: " << fid << " fidname: " << get_family_name(fid) << "\n"; + }); + for (family_id fid = 0; from.m_family_manager.has_family(fid); fid++) { + SASSERT(from.is_builtin_family_id(fid) == is_builtin_family_id(fid)); + SASSERT(!from.is_builtin_family_id(fid) || m_family_manager.has_family(fid)); + symbol fid_name = from.get_family_name(fid); + TRACE("copy_families_plugins", tout << "copying: " << fid_name << ", src fid: " << fid + << ", target has_family: " << m_family_manager.has_family(fid) << "\n"; + if (m_family_manager.has_family(fid)) tout << get_family_id(fid_name) << "\n";); + if (!m_family_manager.has_family(fid)) { + family_id new_fid = get_family_id(fid_name); + TRACE("copy_families_plugins", tout << "new target fid created: " << new_fid << " fid_name: " << fid_name << "\n";); + } + TRACE("copy_families_plugins", tout << "target fid: " << get_family_id(fid_name) << "\n";); + SASSERT(fid == get_family_id(fid_name)); + if (from.has_plugin(fid) && !has_plugin(fid)) { + decl_plugin * new_p = from.get_plugin(fid)->mk_fresh(); + register_plugin(fid, new_p); + SASSERT(new_p->get_family_id() == fid); + } + SASSERT(from.m_family_manager.has_family(fid) == m_family_manager.has_family(fid)); + SASSERT(from.get_family_id(fid_name) == get_family_id(fid_name)); + SASSERT(from.has_plugin(fid) == has_plugin(fid)); + } +} + +void ast_manager::set_next_expr_id(unsigned id) { + while (true) { + id = m_expr_id_gen.set_next_id(id); + ast_table::iterator it = m_ast_table.begin(); + ast_table::iterator end = m_ast_table.end(); + for (; it != end; ++it) { + ast * curr = *it; + if (curr->get_id() == id) + break; + } + if (it == end) + return; + // id is in use, move to the next one. + id++; + } +} + +unsigned ast_manager::get_node_size(ast const * n) { return ::get_node_size(n); } + +void ast_manager::register_plugin(symbol const & s, decl_plugin * plugin) { + family_id id = m_family_manager.get_family_id(s); + SASSERT(is_format_manager() || s != symbol("format")); + register_plugin(id, plugin); +} + +void ast_manager::register_decl_plugins() { + if (!get_plugin(get_family_id(symbol("arith")))) { + register_plugin(symbol("arith"), alloc(arith_decl_plugin)); + } + if (!get_plugin(get_family_id(symbol("bv")))) { + register_plugin(symbol("bv"), alloc(bv_decl_plugin)); + } + if (!get_plugin(get_family_id(symbol("array")))) { + register_plugin(symbol("array"), alloc(array_decl_plugin)); + } + if (!get_plugin(get_family_id(symbol("datatype")))) { + register_plugin(symbol("datatype"), alloc(datatype_decl_plugin)); + } + if (!get_plugin(get_family_id(symbol("datalog_relation")))) { + register_plugin(symbol("datalog_relation"), alloc(datalog::dl_decl_plugin)); + } + if (!get_plugin(get_family_id(symbol("seq")))) { + register_plugin(symbol("seq"), alloc(seq_decl_plugin)); + } + if (!get_plugin(get_family_id(symbol("float")))) { + register_plugin(symbol("float"), alloc(float_decl_plugin)); + } +} + +decl_plugin * ast_manager::get_plugin(family_id fid) const { + return m_plugins.get(fid, 0); +} + + +bool ast_manager::is_value(expr* e) const { + decl_plugin const * p = 0; + if (is_app(e)) { + p = get_plugin(to_app(e)->get_family_id()); + return p && p->is_value(to_app(e)); + } + return false; +} + +bool ast_manager::are_distinct(expr* a, expr* b) const { + if (is_app(a) && is_app(b)) { + app* ap = to_app(a), *bp = to_app(b); + decl_plugin const * p = get_plugin(ap->get_family_id()); + if (!p) { + p = get_plugin(bp->get_family_id()); + } + return p && p->are_distinct(ap, bp); + } + return false; +} + +void ast_manager::register_plugin(family_id id, decl_plugin * plugin) { + SASSERT(m_plugins.get(id, 0) == 0); + m_plugins.setx(id, plugin, 0); + plugin->set_manager(this, id); +} + +sort * ast_manager::get_sort(expr const * n) const { + switch(n->get_kind()) { + case AST_APP: + return to_app(n)->get_decl()->get_range(); + case AST_VAR: + return to_var(n)->get_sort(); + case AST_QUANTIFIER: + return m_bool_sort; + default: + UNREACHABLE(); + return 0; + } +} + +bool ast_manager::is_bool(expr const * n) const { + return get_sort(n) == m_bool_sort; +} + +#ifdef Z3DEBUG +bool ast_manager::slow_not_contains(ast const * n) { + ast_table::iterator it = m_ast_table.begin(); + ast_table::iterator end = m_ast_table.end(); + unsigned num = 0; + for (; it != end; ++it) { + ast * curr = *it; + if (compare_nodes(curr, n)) { + TRACE("nondet_bug", + tout << "id1: " << curr->get_id() << ", id2: " << n->get_id() << "\n"; + tout << "hash1: " << get_node_hash(curr) << ", hash2: " << get_node_hash(n) << "\n";); + return false; + } + SASSERT(!(is_app(n) && is_app(curr) && + to_app(n)->get_decl() == to_app(curr)->get_decl() && + to_app(n)->get_num_args() == 0 && + to_app(curr)->get_num_args() == 0)); + num++; + } + SASSERT(num == m_ast_table.size()); + return true; +} +#endif + +ast * ast_manager::register_node_core(ast * n) { + unsigned h = get_node_hash(n); + n->m_hash = h; +#ifdef Z3DEBUG + bool contains = m_ast_table.contains(n); + CASSERT("nondet_bug", contains || slow_not_contains(n)); +#endif + +#if 0 + static unsigned counter = 0; + counter++; + if (counter % 100000 == 0) + verbose_stream() << "[ast-table] counter: " << counter << " collisions: " << m_ast_table.collisions() << " capacity: " << m_ast_table.capacity() << " size: " << m_ast_table.size() << "\n"; +#endif + + ast * r = m_ast_table.insert_if_not_there(n); + SASSERT(r->m_hash == h); + if (r != n) { +#if 0 + static unsigned reused = 0; + reused++; + if (reused % 100000 == 0) + verbose_stream() << "[ast-table] reused: " << reused << "\n"; +#endif + SASSERT(contains); + SASSERT(m_ast_table.contains(n)); + if (is_func_decl(r) && to_func_decl(r)->get_range() != to_func_decl(n)->get_range()) { + std::ostringstream buffer; + buffer << "Recycling of declaration for the same name '" << to_func_decl(r)->get_name().str().c_str() << "'" + << " and domain, but different range type is not permitted"; + throw ast_exception(buffer.str().c_str()); + } + deallocate_node(n, ::get_node_size(n)); + return r; + } + else { + SASSERT(!contains); + SASSERT(m_ast_table.contains(n)); + } + + n->m_id = is_decl(n) ? m_decl_id_gen.mk() : m_expr_id_gen.mk(); + + TRACE("ast", tout << "Object " << n->m_id << " was created.\n";); + TRACE("mk_var_bug", tout << "mk_ast: " << n->m_id << "\n";); + // increment reference counters + switch (n->get_kind()) { + case AST_SORT: + if (to_sort(n)->m_info != 0) { + to_sort(n)->m_info = alloc(sort_info, *(to_sort(n)->get_info())); + to_sort(n)->m_info->init_eh(*this); + } + break; + case AST_FUNC_DECL: + if (to_func_decl(n)->m_info != 0) { + to_func_decl(n)->m_info = alloc(func_decl_info, *(to_func_decl(n)->get_info())); + to_func_decl(n)->m_info->init_eh(*this); + } + inc_array_ref(to_func_decl(n)->get_arity(), to_func_decl(n)->get_domain()); + inc_ref(to_func_decl(n)->get_range()); + break; + case AST_APP: { + app * t = to_app(n); + inc_ref(t->get_decl()); + unsigned num_args = t->get_num_args(); + if (num_args > 0) { + app_flags * f = t->flags(); + *f = mk_default_app_flags(); + SASSERT(t->is_ground()); + SASSERT(!t->has_quantifiers()); + SASSERT(!t->has_labels()); + if (is_label(t)) + f->m_has_labels = true; + unsigned depth = 0; + for (unsigned i = 0; i < num_args; i++) { + expr * arg = t->get_arg(i); + inc_ref(arg); + unsigned arg_depth; + switch (arg->get_kind()) { + case AST_APP: { + app_flags * arg_flags = to_app(arg)->flags(); + arg_depth = arg_flags->m_depth; + if (arg_flags->m_has_quantifiers) + f->m_has_quantifiers = true; + if (arg_flags->m_has_labels) + f->m_has_labels = true; + if (!arg_flags->m_ground) + f->m_ground = false; + break; + } + case AST_QUANTIFIER: + f->m_has_quantifiers = true; + f->m_ground = false; + arg_depth = to_quantifier(arg)->get_depth(); + break; + case AST_VAR: + f->m_ground = false; + arg_depth = 1; + break; + default: + UNREACHABLE(); + } + if (arg_depth > depth) + depth = arg_depth; + } + depth++; + if (depth > c_max_depth) + depth = c_max_depth; + f->m_depth = depth; + SASSERT(t->get_depth() == depth); + } + break; + } + case AST_VAR: + inc_ref(to_var(n)->get_sort()); + break; + case AST_QUANTIFIER: + inc_array_ref(to_quantifier(n)->get_num_decls(), to_quantifier(n)->get_decl_sorts()); + inc_ref(to_quantifier(n)->get_expr()); + inc_array_ref(to_quantifier(n)->get_num_patterns(), to_quantifier(n)->get_patterns()); + inc_array_ref(to_quantifier(n)->get_num_no_patterns(), to_quantifier(n)->get_no_patterns()); + break; + default: + break; + } + return n; +} + +void ast_manager::delete_node(ast * n) { + TRACE("delete_node_bug", tout << mk_ll_pp(n, *this) << "\n";); + ptr_buffer worklist; + worklist.push_back(n); + + while (!worklist.empty()) { + n = worklist.back(); + worklist.pop_back(); + + TRACE("ast", tout << "Deleting object " << n->m_id << " " << n << "\n";); + CTRACE("del_quantifier", is_quantifier(n), tout << "deleting quantifier " << n->m_id << " " << n << "\n";); + TRACE("mk_var_bug", tout << "del_ast: " << n->m_id << "\n";); + TRACE("ast_delete_node", tout << mk_bounded_pp(n, *this) << "\n";); + + SASSERT(m_ast_table.contains(n)); + m_ast_table.erase(n); + SASSERT(!m_ast_table.contains(n)); + SASSERT(!m_debug_ref_count || !m_debug_free_indices.contains(n->m_id)); + +#ifdef RECYCLE_FREE_AST_INDICES + if (!m_debug_ref_count) { + if (is_decl(n)) + m_decl_id_gen.recycle(n->m_id); + else + m_expr_id_gen.recycle(n->m_id); + } +#endif + switch (n->get_kind()) { + case AST_SORT: + if (to_sort(n)->m_info != 0 && !m_debug_ref_count) { + sort_info * info = to_sort(n)->get_info(); + info->del_eh(*this); + dealloc(info); + } + break; + case AST_FUNC_DECL: + if (to_func_decl(n)->m_info != 0 && !m_debug_ref_count) { + func_decl_info * info = to_func_decl(n)->get_info(); + info->del_eh(*this); + dealloc(info); + } + dec_array_ref(worklist, to_func_decl(n)->get_arity(), to_func_decl(n)->get_domain()); + dec_ref(worklist, to_func_decl(n)->get_range()); + break; + case AST_APP: + dec_ref(worklist, to_app(n)->get_decl()); + dec_array_ref(worklist, to_app(n)->get_num_args(), to_app(n)->get_args()); + break; + case AST_VAR: + dec_ref(worklist, to_var(n)->get_sort()); + break; + case AST_QUANTIFIER: + dec_array_ref(worklist, to_quantifier(n)->get_num_decls(), to_quantifier(n)->get_decl_sorts()); + dec_ref(worklist, to_quantifier(n)->get_expr()); + dec_array_ref(worklist, to_quantifier(n)->get_num_patterns(), to_quantifier(n)->get_patterns()); + dec_array_ref(worklist, to_quantifier(n)->get_num_no_patterns(), to_quantifier(n)->get_no_patterns()); + break; + default: + break; + } + if (m_debug_ref_count) { + m_debug_free_indices.insert(n->m_id,0); + } + deallocate_node(n, ::get_node_size(n)); + } +} + +sort * ast_manager::mk_sort(family_id fid, decl_kind k, unsigned num_parameters, parameter const * parameters) { + decl_plugin * p = get_plugin(fid); + if (p) + return p->mk_sort(k, num_parameters, parameters); + return 0; +} + +func_decl * ast_manager::mk_func_decl(family_id fid, decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + decl_plugin * p = get_plugin(fid); + if (p) + return p->mk_func_decl(k, num_parameters, parameters, arity, domain, range); + return 0; +} + +func_decl * ast_manager::mk_func_decl(family_id fid, decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned num_args, expr * const * args, sort * range) { + decl_plugin * p = get_plugin(fid); + if (p) + return p->mk_func_decl(k, num_parameters, parameters, num_args, args, range); + return 0; +} + +app * ast_manager::mk_app(family_id fid, decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned num_args, expr * const * args, sort * range) { + func_decl * decl = mk_func_decl(fid, k, num_parameters, parameters, num_args, args, range); + if (decl != 0) + return mk_app(decl, num_args, args); + return 0; +} + +app * ast_manager::mk_app(family_id fid, decl_kind k, unsigned num_args, expr * const * args) { + return mk_app(fid, k, 0, 0, num_args, args); +} + +app * ast_manager::mk_app(family_id fid, decl_kind k, expr * arg) { + return mk_app(fid, k, 0, 0, 1, &arg); +} + +app * ast_manager::mk_app(family_id fid, decl_kind k, expr * arg1, expr * arg2) { + expr * args[2] = { arg1, arg2 }; + return mk_app(fid, k, 0, 0, 2, args); +} + +app * ast_manager::mk_app(family_id fid, decl_kind k, expr * arg1, expr * arg2, expr * arg3) { + expr * args[3] = { arg1, arg2, arg3 }; + return mk_app(fid, k, 0, 0, 3, args); +} + +sort * ast_manager::mk_sort(symbol const & name, sort_info * info) { + unsigned sz = sort::get_obj_size(); + void * mem = allocate_node(sz); + sort * new_node = new (mem) sort(name, info); + return register_node(new_node); +} + +func_decl * ast_manager::mk_func_decl(symbol const & name, unsigned arity, sort * const * domain, sort * range, + bool assoc, bool comm, bool inj) { + func_decl_info info(null_family_id, null_decl_kind); + info.set_associative(assoc); + info.set_commutative(comm); + info.set_injective(inj); + return mk_func_decl(name, arity, domain, range, info); +} + +func_decl * ast_manager::mk_func_decl(symbol const & name, unsigned arity, sort * const * domain, sort * range, func_decl_info * info) { + SASSERT(arity == 1 || info == 0 || !info->is_injective()); + SASSERT(arity == 2 || info == 0 || !info->is_associative()); + SASSERT(arity == 2 || info == 0 || !info->is_commutative()); + unsigned sz = func_decl::get_obj_size(arity); + void * mem = allocate_node(sz); + func_decl * new_node = new (mem) func_decl(name, arity, domain, range, info); + return register_node(new_node); +} + +void ast_manager::check_sort(func_decl const * decl, unsigned num_args, expr * const * args) const { + if (decl->is_associative()) { + sort * expected = decl->get_domain(0); + for (unsigned i = 0; i < num_args; i++) { + sort * given = get_sort(args[i]); + if (expected != given) { + string_buffer<> buff; + buff << "invalid function application, sort mismatch on argument at position " << (i+1); + throw ast_exception(buff.c_str()); + } + } + } + else { + if (decl->get_arity() != num_args) { + throw ast_exception("invalid function application, wrong number of arguments"); + } + for (unsigned i = 0; i < num_args; i++) { + sort * expected = decl->get_domain(i); + sort * given = get_sort(args[i]); + if (expected != given) { + string_buffer<> buff; + buff << "invalid function application, sort mismatch on argument at position " << (i+1); + throw ast_exception(buff.c_str()); + } + } + } +} + +/** + \brief Shallow sort checker. + Return true if success. + If n == 0, then fail. + If n is an application, checks whether the arguments of n match the expected types. +*/ +void ast_manager::check_sorts_core(ast const * n) const { + if (!n) { + throw ast_exception("expression is null"); + } + if (n->get_kind() != AST_APP) + return; // nothing else to check... + app const * a = to_app(n); + check_sort(a->get_decl(), a->get_num_args(), a->get_args()); +} + +bool ast_manager::check_sorts(ast const * n) const { + try { + check_sorts_core(n); + return true; + } + catch (ast_exception & ex) { + warning_msg(ex.msg()); + return false; + } +} + +app * ast_manager::mk_app_core(func_decl * decl, unsigned num_args, expr * const * args) { + unsigned sz = app::get_obj_size(num_args); + void * mem = allocate_node(sz); + app * new_node = new (mem) app(decl, num_args, args); + app * r = register_node(new_node); +#ifndef SMTCOMP + if (m_trace_stream != NULL && r == new_node) { + *m_trace_stream << "[mk-app] #" << r->get_id() << " "; + if (r->get_num_args() == 0 && r->get_decl()->get_name() == "int") { + ast_ll_pp(*m_trace_stream, *this, r); + } else if (is_label_lit(r)) { + ast_ll_pp(*m_trace_stream, *this, r); + } else { + *m_trace_stream << r->get_decl()->get_name(); + for (unsigned i = 0; i < r->get_num_args(); i++) + *m_trace_stream << " #" << r->get_arg(i)->get_id(); + *m_trace_stream << "\n"; + } + } +#endif + return r; +} + +inline app * ast_manager::mk_app_core(func_decl * decl, expr * arg1, expr * arg2) { + expr * args[2] = { arg1, arg2 }; + return mk_app_core(decl, 2, args); +} + +app * ast_manager::mk_app(func_decl * decl, unsigned num_args, expr * const * args) { + SASSERT(decl->get_arity() == num_args || decl->is_right_associative() || decl->is_left_associative() || decl->is_chainable()); + app * r = 0; + if (num_args > 2 && !decl->is_flat_associative()) { + if (decl->is_right_associative()) { + unsigned j = num_args - 1; + r = mk_app_core(decl, args[j-1], args[j]); + -- j; + while (j > 0) { + --j; + r = mk_app_core(decl, args[j], r); + } + } + else if (decl->is_left_associative()) { + r = mk_app_core(decl, args[0], args[1]); + for (unsigned i = 2; i < num_args; i++) { + r = mk_app_core(decl, r, args[i]); + } + } + else if (decl->is_chainable()) { + TRACE("chainable", tout << "chainable...\n";); + ptr_buffer new_args; + for (unsigned i = 1; i < num_args; i++) { + new_args.push_back(mk_app_core(decl, args[i-1], args[i])); + } + r = mk_and(new_args.size(), new_args.c_ptr()); + } + } + if (r == 0) { + r = mk_app_core(decl, num_args, args); + } + SASSERT(r != 0); + TRACE("app_ground", tout << "ground: " << r->is_ground() << "\n" << mk_ll_pp(r, *this) << "\n";); + return r; +} + +func_decl * ast_manager::mk_fresh_func_decl(symbol const & prefix, symbol const & suffix, unsigned arity, + sort * const * domain, sort * range) { + func_decl_info info(null_family_id, null_decl_kind); + info.m_skolem = true; + SASSERT(info.is_skolem()); + func_decl * d; + if (prefix == symbol::null && suffix == symbol::null) { + d = mk_func_decl(symbol(m_fresh_id), arity, domain, range, &info); + } + else { + string_buffer<64> buffer; + buffer << prefix; + if (prefix == symbol::null) + buffer << "sk"; + buffer << "!"; + if (suffix != symbol::null) + buffer << suffix << "!"; + buffer << m_fresh_id; + d = mk_func_decl(symbol(buffer.c_str()), arity, domain, range, &info); + } + m_fresh_id++; + SASSERT(d->get_info()); + SASSERT(d->is_skolem()); + return d; +} + +sort * ast_manager::mk_fresh_sort(char const * prefix) { + string_buffer<32> buffer; + buffer << prefix << "!" << m_fresh_id; + m_fresh_id++; + return mk_sort(symbol(buffer.c_str())); +} + +symbol ast_manager::mk_fresh_var_name(char const * prefix) { + string_buffer<32> buffer; + buffer << (prefix ? prefix : "var") << "!" << m_fresh_id; + m_fresh_id++; + return symbol(buffer.c_str()); +} + +var * ast_manager::mk_var(unsigned idx, sort * s) { + unsigned sz = var::get_obj_size(); + void * mem = allocate_node(sz); + var * new_node = new (mem) var(idx, s); + return register_node(new_node); +} + +app * ast_manager::mk_label(bool pos, unsigned num_names, symbol const * names, expr * n) { + SASSERT(num_names > 0); + SASSERT(get_sort(n) == m_bool_sort); + buffer p; + p.push_back(parameter(static_cast(pos))); + for (unsigned i = 0; i < num_names; i++) + p.push_back(parameter(names[i])); + return mk_app(m_label_family_id, OP_LABEL, p.size(), p.c_ptr(), 1, &n); +} + +app * ast_manager::mk_label(bool pos, symbol const & name, expr * n) { + return mk_label(pos, 1, &name, n); +} + +bool ast_manager::is_label(expr const * n, bool & pos, buffer & names) const { + if (!is_app_of(n, m_label_family_id, OP_LABEL)) { + return false; + } + func_decl const * decl = to_app(n)->get_decl(); + pos = decl->get_parameter(0).get_int() != 0; + for (unsigned i = 1; i < decl->get_num_parameters(); i++) + names.push_back(decl->get_parameter(i).get_symbol()); + return true; +} + +app * ast_manager::mk_label_lit(unsigned num_names, symbol const * names) { + SASSERT(num_names > 0); + buffer p; + for (unsigned i = 0; i < num_names; i++) + p.push_back(parameter(names[i])); + return mk_app(m_label_family_id, OP_LABEL_LIT, p.size(), p.c_ptr(), 0, 0); +} + +app * ast_manager::mk_label_lit(symbol const & name) { + return mk_label_lit(1, &name); +} + +bool ast_manager::is_label_lit(expr const * n, buffer & names) const { + if (!is_app_of(n, m_label_family_id, OP_LABEL_LIT)) { + return false; + } + func_decl const * decl = to_app(n)->get_decl(); + for (unsigned i = 0; i < decl->get_num_parameters(); i++) + names.push_back(decl->get_parameter(i).get_symbol()); + return true; +} + +app * ast_manager::mk_pattern(unsigned num_exprs, app * const * exprs) { + DEBUG_CODE({ + for (unsigned i = 0; i < num_exprs; ++i) { + SASSERT(is_app(exprs[i])); + }}); + return mk_app(m_pattern_family_id, OP_PATTERN, 0, 0, num_exprs, (expr*const*)exprs); +} + +bool ast_manager::is_pattern(expr const * n) const { + if (!is_app_of(n, m_pattern_family_id, OP_PATTERN)) { + return false; + } + for (unsigned i = 0; i < to_app(n)->get_num_args(); ++i) { + if (!is_app(to_app(n)->get_arg(i))) { + return false; + } + } + return true; +} + +quantifier * ast_manager::mk_quantifier(bool forall, unsigned num_decls, sort * const * decl_sorts, symbol const * decl_names, + expr * body, int weight , symbol const & qid, symbol const & skid, + unsigned num_patterns, expr * const * patterns, + unsigned num_no_patterns, expr * const * no_patterns) { + SASSERT(body); + SASSERT(num_patterns == 0 || num_no_patterns == 0); + SASSERT(num_decls > 0); + DEBUG_CODE({ + for (unsigned i = 0; i < num_patterns; ++i) { + SASSERT(is_pattern(patterns[i])); + }}); + unsigned sz = quantifier::get_obj_size(num_decls, num_patterns, num_no_patterns); + void * mem = allocate_node(sz); + quantifier * new_node = new (mem) quantifier(forall, num_decls, decl_sorts, decl_names, body, + weight, qid, skid, num_patterns, patterns, + num_no_patterns, no_patterns); + quantifier * r = register_node(new_node); + +#ifndef SMTCOMP + if (m_trace_stream != NULL && r == new_node) { + *m_trace_stream << "[mk-quant] #" << r->get_id() << " " << qid; + for (unsigned i = 0; i < num_patterns; ++i) { + *m_trace_stream << " #" << patterns[i]->get_id(); + } + *m_trace_stream << " #" << body->get_id() << "\n"; + + } +#endif + return r; +} + +// Return true if the patterns of q are the given ones. +static bool same_patterns(quantifier * q, unsigned num_patterns, expr * const * patterns) { + if (num_patterns != q->get_num_patterns()) + return false; + for (unsigned i = 0; i < num_patterns; i++) + if (q->get_pattern(i) != patterns[i]) + return false; + return true; +} + +// Return true if the no patterns of q are the given ones. +static bool same_no_patterns(quantifier * q, unsigned num_no_patterns, expr * const * no_patterns) { + if (num_no_patterns != q->get_num_no_patterns()) + return false; + for (unsigned i = 0; i < num_no_patterns; i++) + if (q->get_no_pattern(i) != no_patterns[i]) + return false; + return true; +} + +quantifier * ast_manager::update_quantifier(quantifier * q, unsigned num_patterns, expr * const * patterns, expr * body) { + if (q->get_expr() == body && same_patterns(q, num_patterns, patterns)) + return q; + return mk_quantifier(q->is_forall(), + q->get_num_decls(), + q->get_decl_sorts(), + q->get_decl_names(), + body, + q->get_weight(), + q->get_qid(), + q->get_skid(), + num_patterns, + patterns, + num_patterns == 0 ? q->get_num_no_patterns() : 0, + num_patterns == 0 ? q->get_no_patterns() : 0); +} + +quantifier * ast_manager::update_quantifier(quantifier * q, unsigned num_patterns, expr * const * patterns, unsigned num_no_patterns, expr * const * no_patterns, expr * body) { + if (q->get_expr() == body && same_patterns(q, num_patterns, patterns) && same_no_patterns(q, num_no_patterns, no_patterns)) + return q; + return mk_quantifier(q->is_forall(), + q->get_num_decls(), + q->get_decl_sorts(), + q->get_decl_names(), + body, + q->get_weight(), + q->get_qid(), + q->get_skid(), + num_patterns, + patterns, + num_no_patterns, + no_patterns); +} + +quantifier * ast_manager::update_quantifier(quantifier * q, expr * body) { + if (q->get_expr() == body) + return q; + return mk_quantifier(q->is_forall(), + q->get_num_decls(), + q->get_decl_sorts(), + q->get_decl_names(), + body, + q->get_weight(), + q->get_qid(), + q->get_skid(), + q->get_num_patterns(), + q->get_patterns(), + q->get_num_no_patterns(), + q->get_no_patterns()); +} + +quantifier * ast_manager::update_quantifier_weight(quantifier * q, int w) { + if (q->get_weight() == w) + return q; + TRACE("update_quantifier_weight", tout << "#" << q->get_id() << " " << q->get_weight() << " -> " << w << "\n";); + return mk_quantifier(q->is_forall(), + q->get_num_decls(), + q->get_decl_sorts(), + q->get_decl_names(), + q->get_expr(), + w, + q->get_qid(), + q->get_skid(), + q->get_num_patterns(), + q->get_patterns(), + q->get_num_no_patterns(), + q->get_no_patterns()); +} + +quantifier * ast_manager::update_quantifier(quantifier * q, bool is_forall, expr * body) { + if (q->get_expr() == body && q->is_forall() == is_forall) + return q; + return mk_quantifier(is_forall, + q->get_num_decls(), + q->get_decl_sorts(), + q->get_decl_names(), + body, + q->get_weight(), + q->get_qid(), + q->get_skid(), + q->get_num_patterns(), + q->get_patterns(), + q->get_num_no_patterns(), + q->get_no_patterns()); +} + +quantifier * ast_manager::update_quantifier(quantifier * q, bool is_forall, unsigned num_patterns, expr * const * patterns, expr * body) { + if (q->get_expr() == body && q->is_forall() == is_forall && same_patterns(q, num_patterns, patterns)) + return q; + return mk_quantifier(is_forall, + q->get_num_decls(), + q->get_decl_sorts(), + q->get_decl_names(), + body, + q->get_weight(), + q->get_qid(), + q->get_skid(), + num_patterns, + patterns, + num_patterns == 0 ? q->get_num_no_patterns() : 0, + num_patterns == 0 ? q->get_no_patterns() : 0); +} + +app * ast_manager::mk_distinct_expanded(unsigned num_args, expr * const * args) { + if (num_args < 2) + return mk_true(); + if (num_args == 2) + return mk_not(mk_eq(args[0], args[1])); + ptr_buffer new_args; + for (unsigned i = 0; i < num_args - 1; i++) { + expr * a1 = args[i]; + for (unsigned j = i + 1; j < num_args; j++) { + expr * a2 = args[j]; + new_args.push_back(mk_not(mk_eq(a1, a2))); + } + } + app * r = mk_and(new_args.size(), new_args.c_ptr()); + TRACE("distinct", tout << "expanded distinct:\n" << mk_pp(r, *this) << "\n";); + return r; +} + +// ----------------------------------- +// +// expr_dependency +// +// ----------------------------------- + +expr_dependency * ast_manager::mk_join(unsigned n, expr * const * ts) { + expr_dependency * d = 0; + for (unsigned i = 0; i < n; i++) + d = mk_join(d, mk_leaf(ts[i])); + return d; +} + +void ast_manager::linearize(expr_dependency * d, ptr_vector & ts) { + m_expr_dependency_manager.linearize(d, ts); + remove_duplicates(ts); +} + +// ----------------------------------- +// +// Values +// +// ----------------------------------- + +app * ast_manager::mk_model_value(unsigned idx, sort * s) { + parameter p[2] = { parameter(idx), parameter(s) }; + return mk_app(m_model_value_family_id, OP_MODEL_VALUE, 2, p, 0, static_cast(0)); +} + +expr * ast_manager::get_some_value(sort * s, some_value_proc * p) { + flet l(m_some_value_proc, p); + return get_some_value(s); +} + +expr * ast_manager::get_some_value(sort * s) { + expr * v = 0; + if (m_some_value_proc) + v = (*m_some_value_proc)(s); + if (v != 0) + return v; + family_id fid = s->get_family_id(); + if (fid != null_family_id) { + decl_plugin * p = get_plugin(fid); + if (p != 0) { + v = p->get_some_value(s); + if (v != 0) + return v; + } + } + return mk_model_value(0, s); +} + +bool ast_manager::is_fully_interp(sort const * s) const { + if (is_uninterp(s)) + return false; + family_id fid = s->get_family_id(); + SASSERT(fid != null_family_id); + decl_plugin * p = get_plugin(fid); + if (p != 0) + return p->is_fully_interp(s); + return false; +} + +// ----------------------------------- +// +// Proof generation +// +// ----------------------------------- + +proof * ast_manager::mk_proof(family_id fid, decl_kind k, unsigned num_args, expr * const * args) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + return mk_app(fid, k, num_args, args); +} + +proof * ast_manager::mk_proof(family_id fid, decl_kind k, expr * arg) { + return mk_proof(fid, k, 1, &arg); +} + +proof * ast_manager::mk_proof(family_id fid, decl_kind k, expr * arg1, expr * arg2) { + expr * args[2] = { arg1, arg2 }; + return mk_proof(fid, k, 2, args); +} + +proof * ast_manager::mk_proof(family_id fid, decl_kind k, expr * arg1, expr * arg2, expr * arg3) { + expr * args[3] = { arg1, arg2, arg3 }; + return mk_proof(fid, k, 2, args); +} + +proof * ast_manager::mk_true_proof() { + expr * f = mk_true(); + return mk_proof(m_basic_family_id, PR_TRUE, f); +} + +proof * ast_manager::mk_asserted(expr * f) { + CTRACE("mk_asserted_bug", !is_bool(f), tout << mk_ismt2_pp(f, *this) << "\nsort: " << mk_ismt2_pp(get_sort(f), *this) << "\n";); + SASSERT(is_bool(f)); + return mk_proof(m_basic_family_id, PR_ASSERTED, f); +} + +proof * ast_manager::mk_goal(expr * f) { + SASSERT(is_bool(f)); + return mk_proof(m_basic_family_id, PR_GOAL, f); +} + +proof * ast_manager::mk_modus_ponens(proof * p1, proof * p2) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + SASSERT(has_fact(p1)); + SASSERT(has_fact(p2)); + CTRACE("mk_modus_ponens", !(is_implies(get_fact(p2)) || is_iff(get_fact(p2)) || is_oeq(get_fact(p2))), + tout << mk_ll_pp(p1, *this) << "\n"; + tout << mk_ll_pp(p2, *this) << "\n";); + SASSERT(is_implies(get_fact(p2)) || is_iff(get_fact(p2)) || is_oeq(get_fact(p2))); + CTRACE("mk_modus_ponens", to_app(get_fact(p2))->get_arg(0) != get_fact(p1), + tout << mk_pp(get_fact(p1), *this) << "\n" << mk_pp(get_fact(p2), *this) << "\n";); + SASSERT(to_app(get_fact(p2))->get_arg(0) == get_fact(p1)); + if (is_reflexivity(p2)) + return p1; + expr * f = to_app(get_fact(p2))->get_arg(1); + if (is_oeq(get_fact(p2))) + return mk_app(m_basic_family_id, PR_MODUS_PONENS_OEQ, p1, p2, f); + return mk_app(m_basic_family_id, PR_MODUS_PONENS, p1, p2, f); +} + +proof * ast_manager::mk_reflexivity(expr * e) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + return mk_app(m_basic_family_id, PR_REFLEXIVITY, mk_eq(e, e)); +} + +proof * ast_manager::mk_oeq_reflexivity(expr * e) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + return mk_app(m_basic_family_id, PR_REFLEXIVITY, mk_oeq(e, e)); +} + +proof * ast_manager::mk_commutativity(app * f) { + SASSERT(f->get_num_args() == 2); + app * f_prime = mk_app(f->get_decl(), f->get_arg(1), f->get_arg(0)); + return mk_app(m_basic_family_id, PR_COMMUTATIVITY, mk_eq(f, f_prime)); +} + +/** + \brief Given a proof of p, return a proof of (p <=> true) +*/ +proof * ast_manager::mk_iff_true(proof * pr) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + SASSERT(has_fact(pr)); + SASSERT(is_bool(get_fact(pr))); + return mk_app(m_basic_family_id, PR_IFF_TRUE, pr, mk_iff(get_fact(pr), mk_true())); +} + +/** + \brief Given a proof of (not p), return a proof of (p <=> false) +*/ +proof * ast_manager::mk_iff_false(proof * pr) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + SASSERT(has_fact(pr)); + SASSERT(is_not(get_fact(pr))); + expr * p = to_app(get_fact(pr))->get_arg(0); + return mk_app(m_basic_family_id, PR_IFF_FALSE, pr, mk_iff(p, mk_false())); +} + +proof * ast_manager::mk_symmetry(proof * p) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + if (!p) + return p; + if (is_reflexivity(p)) + return p; + if (is_symmetry(p)) + return get_parent(p, 0); + SASSERT(has_fact(p)); + SASSERT(is_app(get_fact(p))); + SASSERT(to_app(get_fact(p))->get_num_args() == 2); + return mk_app(m_basic_family_id, PR_SYMMETRY, p, + mk_app(to_app(get_fact(p))->get_decl(), to_app(get_fact(p))->get_arg(1), to_app(get_fact(p))->get_arg(0))); +} + +proof * ast_manager::mk_transitivity(proof * p1, proof * p2) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + if (!p1) + return p2; + if (!p2) + return p1; + SASSERT(has_fact(p1)); + SASSERT(has_fact(p2)); + SASSERT(is_app(get_fact(p1))); + SASSERT(is_app(get_fact(p2))); + SASSERT(to_app(get_fact(p1))->get_num_args() == 2); + SASSERT(to_app(get_fact(p2))->get_num_args() == 2); + CTRACE("mk_transitivity", to_app(get_fact(p1))->get_decl() != to_app(get_fact(p2))->get_decl(), + tout << mk_pp(get_fact(p1), *this) << "\n\n" << mk_pp(get_fact(p2), *this) << "\n"; + tout << mk_pp(to_app(get_fact(p1))->get_decl(), *this) << "\n"; + tout << mk_pp(to_app(get_fact(p2))->get_decl(), *this) << "\n";); + SASSERT(to_app(get_fact(p1))->get_decl() == to_app(get_fact(p2))->get_decl() || + ( (is_eq(get_fact(p1)) || is_oeq(get_fact(p1))) && + (is_eq(get_fact(p2)) || is_oeq(get_fact(p2))))); + CTRACE("mk_transitivity", to_app(get_fact(p1))->get_arg(1) != to_app(get_fact(p2))->get_arg(0), + tout << mk_pp(get_fact(p1), *this) << "\n\n" << mk_pp(get_fact(p2), *this) << "\n"; + tout << mk_bounded_pp(p1, *this, 5) << "\n\n"; + tout << mk_bounded_pp(p2, *this, 5) << "\n\n"; + ); + SASSERT(to_app(get_fact(p1))->get_arg(1) == to_app(get_fact(p2))->get_arg(0)); + if (is_reflexivity(p1)) + return p2; + if (is_reflexivity(p2)) + return p1; + // OEQ is compatible with EQ for transitivity. + func_decl* f = to_app(get_fact(p1))->get_decl(); + if (is_oeq(get_fact(p2))) f = to_app(get_fact(p2))->get_decl(); + return mk_app(m_basic_family_id, PR_TRANSITIVITY, p1, p2, mk_app(f, to_app(get_fact(p1))->get_arg(0), to_app(get_fact(p2))->get_arg(1))); +} + +proof * ast_manager::mk_transitivity(proof * p1, proof * p2, proof * p3) { + return mk_transitivity(mk_transitivity(p1,p2), p3); +} + +proof * ast_manager::mk_transitivity(proof * p1, proof * p2, proof * p3, proof * p4) { + return mk_transitivity(mk_transitivity(mk_transitivity(p1,p2), p3), p4); +} + +proof * ast_manager::mk_transitivity(unsigned num_proofs, proof * const * proofs) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + SASSERT(num_proofs > 0); + proof * r = proofs[0]; + for (unsigned i = 1; i < num_proofs; i++) + r = mk_transitivity(r, proofs[i]); + return r; +} + +proof * ast_manager::mk_transitivity(unsigned num_proofs, proof * const * proofs, expr * n1, expr * n2) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + if (fine_grain_proofs()) + return mk_transitivity(num_proofs, proofs); + SASSERT(num_proofs > 0); + if (num_proofs == 1) + return proofs[0]; + DEBUG_CODE({ + for (unsigned i = 0; i < num_proofs; i++) { + SASSERT(proofs[i]); + SASSERT(!is_reflexivity(proofs[i])); + } + }); + ptr_buffer args; + args.append(num_proofs, (expr**) proofs); + args.push_back(mk_eq(n1,n2)); + return mk_app(m_basic_family_id, PR_TRANSITIVITY_STAR, args.size(), args.c_ptr()); +} + +proof * ast_manager::mk_monotonicity(func_decl * R, app * f1, app * f2, unsigned num_proofs, proof * const * proofs) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + SASSERT(f1->get_num_args() == f2->get_num_args()); + SASSERT(f1->get_decl() == f2->get_decl()); + ptr_buffer args; + args.append(num_proofs, (expr**) proofs); + args.push_back(mk_app(R, f1, f2)); + return mk_app(m_basic_family_id, PR_MONOTONICITY, args.size(), args.c_ptr()); +} + +proof * ast_manager::mk_congruence(app * f1, app * f2, unsigned num_proofs, proof * const * proofs) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + SASSERT(get_sort(f1) == get_sort(f2)); + sort * s = get_sort(f1); + sort * d[2] = { s, s }; + return mk_monotonicity(mk_func_decl(m_basic_family_id, get_eq_op(f1), 0, 0, 2, d), f1, f2, num_proofs, proofs); +} + +proof * ast_manager::mk_oeq_congruence(app * f1, app * f2, unsigned num_proofs, proof * const * proofs) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + SASSERT(get_sort(f1) == get_sort(f2)); + sort * s = get_sort(f1); + sort * d[2] = { s, s }; + return mk_monotonicity(mk_func_decl(m_basic_family_id, OP_OEQ, 0, 0, 2, d), f1, f2, num_proofs, proofs); +} + +proof * ast_manager::mk_quant_intro(quantifier * q1, quantifier * q2, proof * p) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + if (!p) { + return 0; + } + SASSERT(q1->get_num_decls() == q2->get_num_decls()); + SASSERT(has_fact(p)); + SASSERT(is_iff(get_fact(p))); + return mk_app(m_basic_family_id, PR_QUANT_INTRO, p, mk_iff(q1, q2)); +} + +proof * ast_manager::mk_oeq_quant_intro(quantifier * q1, quantifier * q2, proof * p) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + SASSERT(q1->get_num_decls() == q2->get_num_decls()); + SASSERT(has_fact(p)); + SASSERT(is_oeq(get_fact(p))); + return mk_app(m_basic_family_id, PR_QUANT_INTRO, p, mk_oeq(q1, q2)); +} + +proof * ast_manager::mk_distributivity(expr * s, expr * r) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + return mk_app(m_basic_family_id, PR_DISTRIBUTIVITY, mk_eq(s, r)); +} + +proof * ast_manager::mk_rewrite(expr * s, expr * t) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + return mk_app(m_basic_family_id, PR_REWRITE, mk_eq(s, t)); +} + +proof * ast_manager::mk_oeq_rewrite(expr * s, expr * t) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + return mk_app(m_basic_family_id, PR_REWRITE, mk_oeq(s, t)); +} + +proof * ast_manager::mk_rewrite_star(expr * s, expr * t, unsigned num_proofs, proof * const * proofs) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + ptr_buffer args; + args.append(num_proofs, (expr**) proofs); + args.push_back(mk_eq(s, t)); + return mk_app(m_basic_family_id, PR_REWRITE_STAR, args.size(), args.c_ptr()); +} + +proof * ast_manager::mk_pull_quant(expr * e, quantifier * q) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + return mk_app(m_basic_family_id, PR_PULL_QUANT, mk_iff(e, q)); +} + +proof * ast_manager::mk_pull_quant_star(expr * e, quantifier * q) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + return mk_app(m_basic_family_id, PR_PULL_QUANT_STAR, mk_iff(e, q)); +} + +proof * ast_manager::mk_push_quant(quantifier * q, expr * e) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + return mk_app(m_basic_family_id, PR_PUSH_QUANT, mk_iff(q, e)); +} + +proof * ast_manager::mk_elim_unused_vars(quantifier * q, expr * e) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + return mk_app(m_basic_family_id, PR_ELIM_UNUSED_VARS, mk_iff(q, e)); +} + +proof * ast_manager::mk_der(quantifier * q, expr * e) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + return mk_app(m_basic_family_id, PR_DER, mk_iff(q, e)); +} + +proof * ast_manager::mk_quant_inst(expr * not_q_or_i, unsigned num_bind, expr* const* binding) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + vector params; + for (unsigned i = 0; i < num_bind; ++i) { + params.push_back(parameter(binding[i])); + SASSERT(params.back().is_ast()); + } + return mk_app(m_basic_family_id, PR_QUANT_INST, num_bind, params.c_ptr(), 1, & not_q_or_i); +} + +bool ast_manager::is_quant_inst(expr const* e, expr*& not_q_or_i, ptr_vector& binding) const { + if (is_quant_inst(e)) { + not_q_or_i = to_app(e)->get_arg(0); + func_decl* d = to_app(e)->get_decl(); + SASSERT(binding.empty()); + for (unsigned i = 0; i < d->get_num_parameters(); ++i) { + binding.push_back(to_expr(d->get_parameter(i).get_ast())); + } + return true; + } + return false; +} + +bool ast_manager::is_rewrite(expr const* e, expr*& r1, expr*& r2) const { + if (is_rewrite(e)) { + VERIFY (is_eq(to_app(e)->get_arg(0), r1, r2) || + is_iff(to_app(e)->get_arg(0), r1, r2)); + return true; + } + else { + return false; + } +} + +proof * ast_manager::mk_def_axiom(expr * ax) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + return mk_app(m_basic_family_id, PR_DEF_AXIOM, ax); +} + +proof * ast_manager::mk_unit_resolution(unsigned num_proofs, proof * const * proofs) { + SASSERT(num_proofs >= 2); + for (unsigned i = 0; i < num_proofs; i++) { + SASSERT(has_fact(proofs[i])); + } + ptr_buffer args; + args.append(num_proofs, (expr**) proofs); + expr * fact; + expr const * f1 = get_fact(proofs[0]); + expr const * f2 = get_fact(proofs[1]); + if (num_proofs == 2 && is_complement(f1, f2)) { + fact = mk_false(); + } + else { + SASSERT(is_or(f1)); + ptr_buffer new_lits; + app const * cls = to_app(f1); + unsigned num_args = cls->get_num_args(); +#ifdef Z3DEBUG + vector found; +#endif + for (unsigned i = 0; i < num_args; i++) { + expr * lit = cls->get_arg(i); + unsigned j = 1; + for (; j < num_proofs; j++) { + expr const * _fact = get_fact(proofs[j]); + if (is_complement(lit, _fact)) { + DEBUG_CODE(found.setx(j, true, false);); + break; + } + } + if (j == num_proofs) + new_lits.push_back(lit); + } + DEBUG_CODE({ + for (unsigned i = 1; m_proof_mode == PGM_FINE && i < num_proofs; i++) { + CTRACE("mk_unit_resolution_bug", !found.get(i, false), + for (unsigned j = 0; j < num_proofs; j++) { + tout << mk_ll_pp(get_fact(proofs[j]), *this); + }); + SASSERT(found.get(i, false)); + } + }); + switch (new_lits.size()) { + case 0: + fact = mk_false(); + break; + case 1: + fact = new_lits[0]; + break; + default: + fact = mk_or(new_lits.size(), new_lits.c_ptr()); + break; + } + } + args.push_back(fact); + proof * pr = mk_app(m_basic_family_id, PR_UNIT_RESOLUTION, args.size(), args.c_ptr()); + TRACE("unit_resolution", tout << "unit_resolution generating fact\n" << mk_ll_pp(pr, *this);); + return pr; +} + +proof * ast_manager::mk_unit_resolution(unsigned num_proofs, proof * const * proofs, expr * new_fact) { + TRACE("unit_bug", + for (unsigned i = 0; i < num_proofs; i++) tout << mk_pp(get_fact(proofs[i]), *this) << "\n"; + tout << "===>\n"; + tout << mk_pp(new_fact, *this) << "\n";); + + ptr_buffer args; + args.append(num_proofs, (expr**) proofs); + args.push_back(new_fact); +#ifdef Z3DEBUG + expr * f1 = get_fact(proofs[0]); + expr const * f2 = get_fact(proofs[1]); + if (num_proofs == 2 && is_complement(f1, f2)) { + SASSERT(is_false(new_fact)); + } + else { + SASSERT(is_or(f1)); + app * cls = to_app(f1); + unsigned cls_sz = cls->get_num_args(); + CTRACE("cunit_bug", !(num_proofs == cls_sz || (num_proofs == cls_sz + 1 && is_false(new_fact))), + for (unsigned i = 0; i < num_proofs; i++) tout << mk_pp(get_fact(proofs[i]), *this) << "\n"; + tout << "===>\n"; + tout << mk_pp(new_fact, *this) << "\n";); + SASSERT(num_proofs == cls_sz || (num_proofs == cls_sz + 1 && is_false(new_fact))); + unsigned num_matches = 0; + for (unsigned i = 0; i < cls_sz; i++) { + expr * lit = cls->get_arg(i); + unsigned j = 1; + for (; j < num_proofs; j++) { + if (is_complement(lit, get_fact(proofs[j]))) { + num_matches++; + break; + } + } + if (j == num_proofs) { + CTRACE("unit_bug1", new_fact != lit, tout << mk_ll_pp(new_fact, *this) << "\n" << mk_ll_pp(lit, *this) << "\n";); + SASSERT(new_fact == lit); + } + } + SASSERT(num_matches == cls_sz || num_matches == cls_sz - 1); + SASSERT(num_matches != cls_sz || is_false(new_fact)); + } +#endif + proof * pr = mk_app(m_basic_family_id, PR_UNIT_RESOLUTION, args.size(), args.c_ptr()); + TRACE("unit_resolution", tout << "unit_resolution using fact\n" << mk_ll_pp(pr, *this);); + return pr; +} + +proof * ast_manager::mk_hypothesis(expr * h) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + return mk_app(m_basic_family_id, PR_HYPOTHESIS, h); +} + +proof * ast_manager::mk_lemma(proof * p, expr * lemma) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + SASSERT(has_fact(p)); + CTRACE("mk_lemma", !is_false(get_fact(p)), tout << mk_ll_pp(p, *this) << "\n";); + SASSERT(is_false(get_fact(p))); + return mk_app(m_basic_family_id, PR_LEMMA, p, lemma); +} + +proof * ast_manager::mk_def_intro(expr * new_def) { + SASSERT(is_bool(new_def)); + return mk_proof(m_basic_family_id, PR_DEF_INTRO, new_def); +} + +proof * ast_manager::mk_apply_defs(expr * n, expr * def, unsigned num_proofs, proof * const * proofs) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + ptr_buffer args; + args.append(num_proofs, (expr**) proofs); + args.push_back(mk_oeq(n, def)); + return mk_app(m_basic_family_id, PR_APPLY_DEF, args.size(), args.c_ptr()); +} + +proof * ast_manager::mk_iff_oeq(proof * p) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + if (!p) + return p; + + SASSERT(has_fact(p)); + SASSERT(is_iff(get_fact(p)) || is_oeq(get_fact(p))); + if (is_oeq(get_fact(p))) + return p; + + app * iff = to_app(get_fact(p)); + expr * lhs = iff->get_arg(0); + expr * rhs = iff->get_arg(1); + return mk_app(m_basic_family_id, PR_IFF_OEQ, p, mk_oeq(lhs, rhs)); +} + +bool ast_manager::check_nnf_proof_parents(unsigned num_proofs, proof * const * proofs) const { + for (unsigned i = 0; i < num_proofs; i++) { + if (!has_fact(proofs[i])) + return false; + if (!is_oeq(get_fact(proofs[i]))) + return false; + } + return true; +} + +proof * ast_manager::mk_nnf_pos(expr * s, expr * t, unsigned num_proofs, proof * const * proofs) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + check_nnf_proof_parents(num_proofs, proofs); + ptr_buffer args; + args.append(num_proofs, (expr**) proofs); + args.push_back(mk_oeq(s, t)); + return mk_app(m_basic_family_id, PR_NNF_POS, args.size(), args.c_ptr()); +} + +proof * ast_manager::mk_nnf_neg(expr * s, expr * t, unsigned num_proofs, proof * const * proofs) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + check_nnf_proof_parents(num_proofs, proofs); + ptr_buffer args; + args.append(num_proofs, (expr**) proofs); + args.push_back(mk_oeq(mk_not(s), t)); + return mk_app(m_basic_family_id, PR_NNF_NEG, args.size(), args.c_ptr()); +} + +proof * ast_manager::mk_nnf_star(expr * s, expr * t, unsigned num_proofs, proof * const * proofs) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + ptr_buffer args; + args.append(num_proofs, (expr**) proofs); + args.push_back(mk_oeq(s, t)); + return mk_app(m_basic_family_id, PR_NNF_STAR, args.size(), args.c_ptr()); +} + +proof * ast_manager::mk_skolemization(expr * q, expr * e) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + SASSERT(is_bool(q)); + SASSERT(is_bool(e)); + return mk_app(m_basic_family_id, PR_SKOLEMIZE, mk_oeq(q, e)); +} + +proof * ast_manager::mk_cnf_star(expr * s, expr * t, unsigned num_proofs, proof * const * proofs) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + ptr_buffer args; + args.append(num_proofs, (expr**) proofs); + args.push_back(mk_oeq(s, t)); + return mk_app(m_basic_family_id, PR_CNF_STAR, args.size(), args.c_ptr()); +} + +proof * ast_manager::mk_and_elim(proof * p, unsigned i) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + SASSERT(has_fact(p)); + SASSERT(is_and(get_fact(p))); + CTRACE("mk_and_elim", i >= to_app(get_fact(p))->get_num_args(), tout << "i: " << i << "\n" << mk_pp(get_fact(p), *this) << "\n";); + SASSERT(i < to_app(get_fact(p))->get_num_args()); + expr * f = to_app(get_fact(p))->get_arg(i); + return mk_app(m_basic_family_id, PR_AND_ELIM, p, f); +} + +proof * ast_manager::mk_not_or_elim(proof * p, unsigned i) { + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + SASSERT(has_fact(p)); + SASSERT(is_not(get_fact(p))); + SASSERT(is_or(to_app(get_fact(p))->get_arg(0))); + app * or_app = to_app(to_app(get_fact(p))->get_arg(0)); + SASSERT(i < or_app->get_num_args()); + expr * c = or_app->get_arg(i); + expr * f; + if (is_not(c)) + f = to_app(c)->get_arg(0); + else + f = mk_not(c); + return mk_app(m_basic_family_id, PR_NOT_OR_ELIM, p, f); +} + + +proof * ast_manager::mk_th_lemma( + family_id tid, + expr * fact, unsigned num_proofs, proof * const * proofs, + unsigned num_params, parameter const* params + ) +{ + if (m_proof_mode == PGM_DISABLED) + return m_undef_proof; + + ptr_buffer args; + vector parameters; + parameters.push_back(parameter(get_family_name(tid))); + for (unsigned i = 0; i < num_params; ++i) { + parameters.push_back(params[i]); + } + args.append(num_proofs, (expr**) proofs); + args.push_back(fact); + return mk_app(m_basic_family_id, PR_TH_LEMMA, num_params+1, parameters.c_ptr(), args.size(), args.c_ptr()); +} + + +// ----------------------------------- +// +// ast_mark +// +// ----------------------------------- + +bool ast_mark::is_marked(ast * n) const { + if (is_decl(n)) + return m_decl_marks.is_marked(to_decl(n)); + else + return m_expr_marks.is_marked(to_expr(n)); +} + +void ast_mark::mark(ast * n, bool flag) { + if (is_decl(n)) + return m_decl_marks.mark(to_decl(n), flag); + else + return m_expr_marks.mark(to_expr(n), flag); +} + +void ast_mark::reset() { + m_decl_marks.reset(); + m_expr_marks.reset(); +} + +// ----------------------------------- +// +// scoped_mark +// +// ----------------------------------- + +void scoped_mark::mark(ast * n, bool flag) { + SASSERT(flag); + mark(n); +} + +void scoped_mark::mark(ast * n) { + if (!ast_mark::is_marked(n)) { + m_stack.push_back(n); + ast_mark::mark(n, true); + } +} + +void scoped_mark::reset() { + ast_mark::reset(); + m_stack.reset(); + m_lim.reset(); +} + +void scoped_mark::push_scope() { + m_lim.push_back(m_stack.size()); +} + +void scoped_mark::pop_scope() { + unsigned new_size = m_stack.size(); + unsigned old_size = m_lim.back(); + for (unsigned i = old_size; i < new_size; ++i) { + ast_mark::mark(m_stack[i].get(), false); + } + m_lim.pop_back(); + m_stack.resize(old_size); +} + +void scoped_mark::pop_scope(unsigned num_scopes) { + for (unsigned i = 0; i < num_scopes; ++i) { + pop_scope(); + } +} + + diff --git a/lib/ast.h b/lib/ast.h new file mode 100644 index 000000000..86ebcf1fe --- /dev/null +++ b/lib/ast.h @@ -0,0 +1,2315 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + ast.h + +Abstract: + + Expression DAG + +Author: + + Leonardo de Moura (leonardo) 2006-09-18. + +Revision History: + +--*/ +#ifndef _AST_H_ +#define _AST_H_ + +#include"vector.h" +#include"hashtable.h" +#include"buffer.h" +#include"symbol.h" +#include"rational.h" +#include"hash.h" +#include"optional.h" +#include"trace.h" +#include"bit_vector.h" +#include"symbol_table.h" +#include"tptr.h" +#include"memory_manager.h" +#include"small_object_allocator.h" +#include"obj_ref.h" +#include"ref_vector.h" +#include"ref_buffer.h" +#include"obj_mark.h" +#include"obj_hashtable.h" +#include"id_gen.h" +#include"map.h" +#include"parray.h" +#include"dictionary.h" +#include"chashtable.h" +#include"z3_exception.h" +#include"dependency.h" + +#define RECYCLE_FREE_AST_INDICES + +#ifdef _MSC_VER +#pragma warning(disable : 4200) +#pragma warning(disable : 4355) +#endif + +class ast; +class ast_manager; + +/** + \brief Generic exception for AST related errors. + + We used to use fatal_error_msg to report errors inside plugins. +*/ +class ast_exception : public default_exception { +public: + ast_exception(char const * msg):default_exception(msg) {} +}; + +typedef int family_id; +const family_id null_family_id = -1; + +// ----------------------------------- +// +// parameter +// +// ----------------------------------- + +/** + \brief Interpreted function declarations and sorts may have parameters that are used + to encode extra information associated with them. +*/ +class parameter { +public: + enum kind_t { + PARAM_INT, + PARAM_AST, + PARAM_SYMBOL, + PARAM_RATIONAL, + PARAM_DOUBLE, + // PARAM_EXTERNAL is used for handling decl_plugin specific parameters. + // For example, it is used for handling mpf numbers in float_decl_plugin, + // and irrational algebraic numbers in arith_decl_plugin. + // PARAM_EXTERNAL is not supported by z3 low level input format. This format is legacy, so + // this is not a big problem. + // Remark: PARAM_EXTERNAL can only be used to decorate theory decls. + PARAM_EXTERNAL + }; +private: + kind_t m_kind; + + // It is not possible to use tag pointers, since symbols are already tagged. + union { + int m_int; // for PARAM_INT + ast* m_ast; // for PARAM_AST + char m_symbol[sizeof(symbol)]; // for PARAM_SYMBOL + char m_rational[sizeof(rational)]; // for PARAM_RATIONAL + double m_dval; // for PARAM_DOUBLE (remark: this is not used in float_decl_plugin) + unsigned m_ext_id; // for PARAM_EXTERNAL + }; + +public: + + parameter(): m_kind(PARAM_INT), m_int(0) {} + explicit parameter(int val): m_kind(PARAM_INT), m_int(val) {} + explicit parameter(unsigned val): m_kind(PARAM_INT), m_int(val) {} + explicit parameter(ast * p): m_kind(PARAM_AST), m_ast(p) {} + explicit parameter(symbol const & s): m_kind(PARAM_SYMBOL) { new (m_symbol) symbol(s); } + explicit parameter(rational const & r): m_kind(PARAM_RATIONAL) { new (m_rational) rational(r); } + explicit parameter(double d):m_kind(PARAM_DOUBLE), m_dval(d) {} + explicit parameter(unsigned ext_id, bool):m_kind(PARAM_EXTERNAL), m_ext_id(ext_id) {} + parameter(parameter const&); + + ~parameter(); + + parameter& operator=(parameter const& other); + + kind_t get_kind() const { return m_kind; } + bool is_int() const { return m_kind == PARAM_INT; } + bool is_ast() const { return m_kind == PARAM_AST; } + bool is_symbol() const { return m_kind == PARAM_SYMBOL; } + bool is_rational() const { return m_kind == PARAM_RATIONAL; } + bool is_double() const { return m_kind == PARAM_DOUBLE; } + bool is_external() const { return m_kind == PARAM_EXTERNAL; } + + bool is_int(int & i) const { return is_int() && (i = get_int(), true); } + bool is_ast(ast * & a) const { return is_ast() && (a = get_ast(), true); } + bool is_symbol(symbol & s) const { return is_symbol() && (s = get_symbol(), true); } + bool is_rational(rational & r) const { return is_rational() && (r = get_rational(), true); } + bool is_double(double & d) const { return is_double() && (d = get_double(), true); } + bool is_external(unsigned & id) const { return is_external() && (id = get_ext_id(), true); } + + /** + \brief This method is invoked when the parameter is + attached to a function declaration or sort. + */ + void init_eh(ast_manager & m); + /** + \brief This method is invoked before the function + declaration or sort associated with the parameter is + deleted. + */ + void del_eh(ast_manager & m, family_id fid); + + int get_int() const { SASSERT(is_int()); return m_int; } + ast * get_ast() const { SASSERT(is_ast()); return m_ast; } + symbol const & get_symbol() const { SASSERT(is_symbol()); return *(reinterpret_cast(m_symbol)); } + rational const & get_rational() const { SASSERT(is_rational()); return *(reinterpret_cast(m_rational)); } + double get_double() const { SASSERT(is_double()); return m_dval; } + unsigned get_ext_id() const { SASSERT(is_external()); return m_ext_id; } + + bool operator==(parameter const & p) const; + bool operator!=(parameter const & p) const { return !operator==(p); } + + unsigned hash() const; + + std::ostream& display(std::ostream& out) const; +}; + +inline std::ostream& operator<<(std::ostream& out, parameter const & p) { + return p.display(out); +} + +void display_parameters(std::ostream & out, unsigned n, parameter const * p); + +// ----------------------------------- +// +// family_manager +// +// ----------------------------------- + +/** + \brief Interpreted functions and sorts are grouped in families. + Each family has an unique ID. This class models the mapping + between symbols (family names) and the unique IDs. +*/ +class family_manager { + family_id m_next_id; + symbol_table m_families; + svector m_names; +public: + family_manager():m_next_id(0) {} + + family_id get_family_id(symbol const & s); + + bool has_family(symbol const & s); + + void get_dom(svector& dom) const { m_families.get_dom(dom); } + + void get_range(svector & range) const { m_families.get_range(range); } + + symbol const & get_name(family_id fid) const { return fid >= 0 && fid < static_cast(m_names.size()) ? m_names[fid] : symbol::null; } + + bool has_family(family_id fid) const { return fid >= 0 && fid < static_cast(m_names.size()); } +}; + +// ----------------------------------- +// +// decl_info +// +// ----------------------------------- + +/** + \brief Each interpreted function declaration or sort has a kind. + Kinds are used to identify interpreted functions and sorts in a family. +*/ +typedef int decl_kind; +const decl_kind null_decl_kind = -1; + +/** + \brief Interpreted function declarations and sorts are associated with + a family id, kind, and parameters. +*/ +class decl_info { + family_id m_family_id; + decl_kind m_kind; + vector m_parameters; +public: + bool m_private_parameters; + decl_info(family_id family_id = null_family_id, decl_kind k = null_decl_kind, + unsigned num_parameters = 0, parameter const * parameters = 0, bool private_params = false); + + decl_info(decl_info const& other); + ~decl_info() {} + + void init_eh(ast_manager & m); + void del_eh(ast_manager & m); + + family_id get_family_id() const { return m_family_id; } + decl_kind get_decl_kind() const { return m_kind; } + unsigned get_num_parameters() const { return m_parameters.size(); } + parameter const & get_parameter(unsigned idx) const { return m_parameters[idx]; } + parameter const * get_parameters() const { return m_parameters.begin(); } + bool private_parameters() const { return m_private_parameters; } + + unsigned hash() const; + bool operator==(decl_info const & info) const; +}; + +std::ostream & operator<<(std::ostream & out, decl_info const & info); + +// ----------------------------------- +// +// sort_size +// +// ----------------------------------- + +/** + \brief Models the number of elements of a sort. +*/ +class sort_size { + enum kind_t { + SS_FINITE, + // For some sorts it may be too expensive to compute the + // number of elements precisely (e.g., arrays). In this + // cases, we mark the sort as too big. That is, the number + // of elements is at least bigger than 2^64. + SS_FINITE_VERY_BIG, + SS_INFINITE + } m_kind; + uint64 m_size; // It is only meaningful if m_kind == SS_FINITE + sort_size(kind_t k, uint64 r):m_kind(k), m_size(r) {} +public: + sort_size():m_kind(SS_INFINITE) {} + sort_size(uint64 const & sz):m_kind(SS_FINITE), m_size(sz) {} + sort_size(sort_size const& other): m_kind(other.m_kind), m_size(other.m_size) {} + explicit sort_size(rational const& r) { + if (r.is_uint64()) { + m_kind = SS_FINITE; + m_size = r.get_uint64(); + } + else { + m_kind = SS_FINITE_VERY_BIG; + m_size = 0; + } + } + static sort_size mk_infinite() { return sort_size(SS_INFINITE, 0); } + static sort_size mk_very_big() { return sort_size(SS_FINITE_VERY_BIG, 0); } + static sort_size mk_finite(uint64 r) { return sort_size(SS_FINITE, r); } + + bool is_infinite() const { return m_kind == SS_INFINITE; } + bool is_very_big() const { return m_kind == SS_FINITE_VERY_BIG; } + bool is_finite() const { return m_kind == SS_FINITE; } + + static bool is_very_big_base2(unsigned power) { return power >= 64; } + + uint64 size() const { SASSERT(is_finite()); return m_size; } +}; + +std::ostream& operator<<(std::ostream& out, sort_size const & ss); + +// ----------------------------------- +// +// sort_info +// +// ----------------------------------- + +/** + \brief Extra information that may be attached to intepreted sorts. +*/ +class sort_info : public decl_info { + sort_size m_num_elements; +public: + sort_info(family_id family_id = null_family_id, decl_kind k = null_decl_kind, + unsigned num_parameters = 0, parameter const * parameters = 0, bool private_parameters = false): + decl_info(family_id, k, num_parameters, parameters, private_parameters) { + } + + sort_info(family_id family_id, decl_kind k, uint64 num_elements, + unsigned num_parameters = 0, parameter const * parameters = 0, bool private_parameters = false): + decl_info(family_id, k, num_parameters, parameters, private_parameters), m_num_elements(num_elements) { + } + + sort_info(family_id family_id, decl_kind k, sort_size const& num_elements, + unsigned num_parameters = 0, parameter const * parameters = 0, bool private_parameters = false): + decl_info(family_id, k, num_parameters, parameters, private_parameters), m_num_elements(num_elements) { + } + sort_info(sort_info const& other) : decl_info(other), m_num_elements(other.m_num_elements) { + } + ~sort_info() {} + + bool is_infinite() const { return m_num_elements.is_infinite(); } + bool is_very_big() const { return m_num_elements.is_very_big(); } + sort_size const & get_num_elements() const { return m_num_elements; } +}; + +std::ostream & operator<<(std::ostream & out, sort_info const & info); + +// ----------------------------------- +// +// func_decl_info +// +// ----------------------------------- + +/** + \brief Extra information that may be attached to interpreted function decls. +*/ +struct func_decl_info : public decl_info { + bool m_left_assoc:1; + bool m_right_assoc:1; + bool m_flat_associative:1; + bool m_commutative:1; + bool m_chainable:1; + bool m_pairwise:1; + bool m_injective:1; + bool m_idempotent:1; + bool m_skolem:1; + + func_decl_info(family_id family_id = null_family_id, decl_kind k = null_decl_kind, unsigned num_parameters = 0, parameter const * parameters = 0); + ~func_decl_info() {} + + bool is_associative() const { return m_left_assoc && m_right_assoc; } + bool is_left_associative() const { return m_left_assoc; } + bool is_right_associative() const { return m_right_assoc; } + bool is_flat_associative() const { return m_flat_associative; } + bool is_commutative() const { return m_commutative; } + bool is_chainable() const { return m_chainable; } + bool is_pairwise() const { return m_pairwise; } + bool is_injective() const { return m_injective; } + bool is_idempotent() const { return m_idempotent; } + bool is_skolem() const { return m_skolem; } + + void set_associative(bool flag = true) { m_left_assoc = flag; m_right_assoc = flag; } + void set_left_associative(bool flag = true) { m_left_assoc = flag; } + void set_right_associative(bool flag = true) { m_right_assoc = flag; } + void set_flat_associative(bool flag = true) { m_flat_associative = flag; } + void set_commutative(bool flag = true) { m_commutative = flag; } + void set_chainable(bool flag = true) { m_chainable = flag; } + void set_pairwise(bool flag = true) { m_pairwise = flag; } + void set_injective(bool flag = true) { m_injective = flag; } + void set_idempotent(bool flag = true) { m_idempotent = flag; } + void set_skolem(bool flag = true) { m_skolem = flag; } + + bool operator==(func_decl_info const & info) const; + + // Return true if the func_decl_info is equivalent to the null one (i.e., it does not contain any useful info). + bool is_null() const { + return + get_family_id() == null_family_id && !is_left_associative() && !is_right_associative() && !is_commutative() && + !is_chainable() && !is_pairwise() && !is_injective() && !is_idempotent() && !is_skolem(); + } +}; + +std::ostream & operator<<(std::ostream & out, func_decl_info const & info); + +// ----------------------------------- +// +// ast +// +// ----------------------------------- + +typedef enum { AST_APP, AST_VAR, AST_QUANTIFIER, AST_SORT, AST_FUNC_DECL } ast_kind; +char const * get_ast_kind_name(ast_kind k); + +class shared_occs_mark; + +class ast { +protected: + friend class ast_manager; + + unsigned m_id; + unsigned m_kind:16; + // Warning: the marks should be used carefully, since they are shared. + unsigned m_mark1:1; + unsigned m_mark2:1; + // Private mark used by shared_occs functor + // Motivation for this field: + // - A mark cannot be used by more than one owner. + // So, it is only safe to use mark by "self-contained" code. + // They should be viewed as temporary information. + // - The functor shared_occs is used by some AST pretty printers. + // - So, a code that uses marks could not use the pretty printer if + // shared_occs used one of the public marks. + // - This was a constant source of assertion violations. + unsigned m_mark_shared_occs:1; + friend class shared_occs_mark; + void mark_so(bool flag) { m_mark_shared_occs = flag; } + void reset_mark_so() { m_mark_shared_occs = false; } + bool is_marked_so() const { return m_mark_shared_occs; } + unsigned m_ref_count; + unsigned m_hash; +#ifdef Z3DEBUG + // In debug mode, we store who is the owner of the mark. + void * m_mark1_owner; + void * m_mark2_owner; +#endif + + void inc_ref() { + SASSERT(m_ref_count < UINT_MAX); + m_ref_count ++; + } + + void dec_ref() { + SASSERT(m_ref_count > 0); + m_ref_count --; + } + + ast(ast_kind k):m_id(UINT_MAX), m_kind(k), m_mark1(false), m_mark2(false), m_mark_shared_occs(false), m_ref_count(0) { + DEBUG_CODE({ + m_mark1_owner = 0; + m_mark2_owner = 0; + }); + } +public: + unsigned get_id() const { return m_id; } + unsigned get_ref_count() const { return m_ref_count; } + ast_kind get_kind() const { return static_cast(m_kind); } + unsigned hash() const { return m_hash; } + +#ifdef Z3DEBUG + void mark1(bool flag, void * owner) { SASSERT(m_mark1_owner == 0 || m_mark1_owner == owner); m_mark1 = flag; m_mark1_owner = owner; } + void mark2(bool flag, void * owner) { SASSERT(m_mark2_owner == 0 || m_mark2_owner == owner); m_mark2 = flag; m_mark2_owner = owner; } + void reset_mark1(void * owner) { SASSERT(m_mark1_owner == 0 || m_mark1_owner == owner); m_mark1 = false; m_mark1_owner = 0; } + void reset_mark2(void * owner) { SASSERT(m_mark2_owner == 0 || m_mark2_owner == owner); m_mark2 = false; m_mark2_owner = 0; } + bool is_marked1(void * owner) const { SASSERT(m_mark1_owner == 0 || m_mark1_owner == owner); return m_mark1; } + bool is_marked2(void * owner) const { SASSERT(m_mark2_owner == 0 || m_mark2_owner == owner); return m_mark2; } +#define AST_MARK1(A,F,O) A->mark1(F, O) +#define AST_MARK2(A,F,O) A->mark2(F, O) +#define AST_RESET_MARK1(A,O) A->reset_mark1(O) +#define AST_RESET_MARK2(A,O) A->reset_mark2(O) +#define AST_IS_MARKED1(A,O) A->is_marked1(O) +#define AST_IS_MARKED2(A,O) A->is_marked2(O) +#else + void mark1(bool flag) { m_mark1 = flag; } + void mark2(bool flag) { m_mark2 = flag; } + void reset_mark1() { m_mark1 = false; } + void reset_mark2() { m_mark2 = false; } + bool is_marked1() const { return m_mark1; } + bool is_marked2() const { return m_mark2; } +#define AST_MARK1(A,F,O) A->mark1(F) +#define AST_MARK2(A,F,O) A->mark2(F) +#define AST_RESET_MARK1(A,O) A->reset_mark1() +#define AST_RESET_MARK2(A,O) A->reset_mark2() +#define AST_IS_MARKED1(A,O) A->is_marked1() +#define AST_IS_MARKED2(A,O) A->is_marked2() +#endif +}; + +#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) { \ + a1 = to_app(n)->get_arg(0); a2 = to_app(n)->get_arg(1); a3 = to_app(n)->get_arg(2); return true; } \ + return false; \ + } + +#define MATCH_BINARY(_MATCHER_) \ + bool _MATCHER_(expr const* n, expr*& s, expr*& t) const { \ + if (_MATCHER_(n) && to_app(n)->get_num_args() == 2) { s = to_app(n)->get_arg(0); t = to_app(n)->get_arg(1); return true; } \ + return false; \ + } + +#define MATCH_UNARY(_MATCHER_) \ + bool _MATCHER_(expr const* n, expr*& s) const { \ + if (_MATCHER_(n) && to_app(n)->get_num_args() == 1) { s = to_app(n)->get_arg(0); return true; } \ + return false; \ + } + +// ----------------------------------- +// +// decl +// +// ----------------------------------- + +/** + The ids of expressions and declarations are in different ranges. +*/ +const unsigned c_first_decl_id = (1 << 31); + +/** + \brief Superclass for function declarations and sorts. +*/ +class decl : public ast { +protected: + friend class ast_manager; + + symbol m_name; + decl_info * m_info; + + decl(ast_kind k, symbol const & name, decl_info * info):ast(k), m_name(name), m_info(info) {} +public: + unsigned get_decl_id() const { SASSERT(get_id() >= c_first_decl_id); return get_id() - c_first_decl_id; } + symbol const & get_name() const { return m_name; } + decl_info * get_info() const { return m_info; } + family_id get_family_id() const { return m_info == 0 ? null_family_id : m_info->get_family_id(); } + decl_kind get_decl_kind() const { return m_info == 0 ? null_decl_kind : m_info->get_decl_kind(); } + unsigned get_num_parameters() const { return m_info == 0 ? 0 : m_info->get_num_parameters(); } + parameter const & get_parameter(unsigned idx) const { return m_info->get_parameter(idx); } + parameter const * get_parameters() const { return m_info == 0 ? 0 : m_info->get_parameters(); } + bool private_parameters() const { return m_info != 0 && m_info->private_parameters(); } +}; + +// ----------------------------------- +// +// sort +// +// ----------------------------------- + +class sort : public decl { + friend class ast_manager; + + static unsigned get_obj_size() { return sizeof(sort); } + + sort(symbol const & name, sort_info * info):decl(AST_SORT, name, info) {} +public: + sort_info * get_info() const { return static_cast(m_info); } + bool is_infinite() const { return get_info() == 0 || get_info()->is_infinite(); } + bool is_very_big() const { return get_info() == 0 || get_info()->is_very_big(); } + bool is_sort_of(family_id fid, decl_kind k) const { return get_family_id() == fid && get_decl_kind() == k; } + sort_size const & get_num_elements() const { return get_info()->get_num_elements(); } + unsigned get_size() const { return get_obj_size(); } +}; + +// ----------------------------------- +// +// func_decl +// +// ----------------------------------- + +class func_decl : public decl { + friend class ast_manager; + + unsigned m_arity; + sort * m_range; + sort * m_domain[0]; + + static unsigned get_obj_size(unsigned arity) { return sizeof(func_decl) + arity * sizeof(sort *); } + + func_decl(symbol const & name, unsigned arity, sort * const * domain, sort * range, func_decl_info * info); +public: + func_decl_info * get_info() const { return static_cast(m_info); } + bool is_associative() const { return get_info() != 0 && get_info()->is_associative(); } + bool is_left_associative() const { return get_info() != 0 && get_info()->is_left_associative(); } + bool is_right_associative() const { return get_info() != 0 && get_info()->is_right_associative(); } + bool is_flat_associative() const { return get_info() != 0 && get_info()->is_flat_associative(); } + bool is_commutative() const { return get_info() != 0 && get_info()->is_commutative(); } + bool is_chainable() const { return get_info() != 0 && get_info()->is_chainable(); } + bool is_pairwise() const { return get_info() != 0 && get_info()->is_pairwise(); } + bool is_injective() const { return get_info() != 0 && get_info()->is_injective(); } + bool is_skolem() const { return get_info() != 0 && get_info()->is_skolem(); } + bool is_idempotent() const { return get_info() != 0 && get_info()->is_idempotent(); } + unsigned get_arity() const { return m_arity; } + sort * get_domain(unsigned idx) const { SASSERT(idx < get_arity()); return m_domain[idx]; } + sort * const * get_domain() const { return m_domain; } + sort * get_range() const { return m_range; } + unsigned get_size() const { return get_obj_size(m_arity); } +}; + +// ----------------------------------- +// +// expression +// +// ----------------------------------- + +/** + \brief Superclass for applications, variables and quantifiers. +*/ +class expr : public ast { +protected: + friend class ast_manager; + + expr(ast_kind k):ast(k) {} +public: +}; + +// ----------------------------------- +// +// application +// +// ----------------------------------- + +#define APP_DEPTH_NUM_BITS 16 +const unsigned c_max_depth = ((1 << APP_DEPTH_NUM_BITS) - 1); +struct app_flags { + unsigned m_depth:APP_DEPTH_NUM_BITS; // if app is to deep, it doesn't matter. + unsigned m_ground:1; // application does not have free variables or nested quantifiers. + unsigned m_has_quantifiers:1; // application has nested quantifiers. + unsigned m_has_labels:1; // application has nested labels. + static app_flags mk_const_flags(); + static app_flags mk_default_app_flags(); + static app_flags mk_default_quantifier_flags(); +}; + +class app : public expr { + friend class ast_manager; + + func_decl * m_decl; + unsigned m_num_args; + expr * m_args[0]; + + static app_flags g_constant_flags; + + // remark: store term depth in the end of the app. the depth is only stored if the num_args > 0 + static unsigned get_obj_size(unsigned num_args) { + return num_args == 0 ? sizeof(app) : sizeof(app) + num_args * sizeof(expr *) + sizeof(app_flags); + } + + friend class tmp_app; + + app_flags * flags() const { return m_num_args == 0 ? &g_constant_flags : reinterpret_cast(const_cast(m_args + m_num_args)); } + + app(func_decl * decl, unsigned num_args, expr * const * args); +public: + func_decl * get_decl() const { return m_decl; } + family_id get_family_id() const { return get_decl()->get_family_id(); } + decl_kind get_decl_kind() const { return get_decl()->get_decl_kind(); } + bool is_app_of(family_id fid, decl_kind k) const { return get_family_id() == fid && get_decl_kind() == k; } + unsigned get_num_args() const { return m_num_args; } + expr * get_arg(unsigned idx) const { SASSERT(idx < m_num_args); return m_args[idx]; } + expr * const * get_args() const { return m_args; } + unsigned get_size() const { return get_obj_size(get_num_args()); } + + unsigned get_depth() const { return flags()->m_depth; } + bool is_ground() const { return flags()->m_ground; } + bool has_quantifiers() const { return flags()->m_has_quantifiers; } + bool has_labels() const { return flags()->m_has_labels; } +}; + +// ----------------------------------- +// +// temporary application: little hack to avoid +// the creation of temporary expressions to just +// check the presence of the expression in +// some container/index. +// +// ----------------------------------- + +class tmp_app { + unsigned m_num_args; + char * m_data; +public: + tmp_app(unsigned num_args): + m_num_args(num_args) { + unsigned sz = app::get_obj_size(num_args); + m_data = alloc_svect(char, sz); + memset(m_data, 0, sz); + get_app()->m_num_args = m_num_args; + } + + ~tmp_app() { + dealloc_svect(m_data); + } + + app * get_app() { + return reinterpret_cast(m_data); + } + + expr ** get_args() { + return get_app()->m_args; + } + + void set_decl(func_decl * d) { + get_app()->m_decl = d; + } + + void set_num_args(unsigned num_args) { + get_app()->m_num_args = num_args; + } + + void set_arg(unsigned idx, expr * arg) { + get_args()[idx] = arg; + SASSERT(get_app()->get_arg(idx) == arg); + } + + void copy(app * source) { + SASSERT(source->get_num_args() <= m_num_args); + new (m_data) app(source->get_decl(), source->get_num_args(), source->get_args()); + SASSERT(get_app()->get_decl() == source->get_decl()); + SASSERT(get_app()->get_arg(0) == source->get_arg(0)); + SASSERT(get_app()->get_arg(1) == source->get_arg(1)); + } + + void copy_swapping_args(app * source) { + SASSERT(source->get_num_args() == 2 && m_num_args >= 2); + expr * args[2] = { source->get_arg(1), source->get_arg(0) }; + new (m_data) app(source->get_decl(), 2, args); + SASSERT(get_app()->get_decl() == source->get_decl()); + SASSERT(get_app()->get_arg(0) == source->get_arg(1)); + SASSERT(get_app()->get_arg(1) == source->get_arg(0)); + } +}; + +// ----------------------------------- +// +// variables +// +// ----------------------------------- + +class var : public expr { + friend class ast_manager; + + unsigned m_idx; + sort * m_sort; + + static unsigned get_obj_size() { return sizeof(var); } + + var(unsigned idx, sort * s):expr(AST_VAR), m_idx(idx), m_sort(s) {} +public: + unsigned get_idx() const { return m_idx; } + sort * get_sort() const { return m_sort; } + unsigned get_size() const { return get_obj_size(); } +}; + +// ----------------------------------- +// +// quantifier +// +// ----------------------------------- + +class quantifier : public expr { + friend class ast_manager; + bool m_forall; + unsigned m_num_decls; + expr * m_expr; + unsigned m_depth; + // extra fields + int m_weight; + bool m_has_unused_vars; + bool m_has_labels; + symbol m_qid; + symbol m_skid; + unsigned m_num_patterns; + unsigned m_num_no_patterns; + char m_patterns_decls[0]; + + static unsigned get_obj_size(unsigned num_decls, unsigned num_patterns, unsigned num_no_patterns) { + return sizeof(quantifier) + num_decls * (sizeof(sort *) + sizeof(symbol)) + (num_patterns + num_no_patterns) * sizeof(expr*); + } + + quantifier(bool forall, unsigned num_decls, sort * const * decl_sorts, symbol const * decl_names, expr * body, + int weight, symbol const & qid, symbol const & skid, unsigned num_patterns, expr * const * patterns, + unsigned num_no_patterns, expr * const * no_patterns); +public: + bool is_forall() const { return m_forall; } + bool is_exists() const { return !m_forall; } + unsigned get_num_decls() const { return m_num_decls; } + sort * const * get_decl_sorts() const { return reinterpret_cast(m_patterns_decls); } + symbol const * get_decl_names() const { return reinterpret_cast(get_decl_sorts() + m_num_decls); } + sort * get_decl_sort(unsigned idx) const { return get_decl_sorts()[idx]; } + symbol const & get_decl_name(unsigned idx) const { return get_decl_names()[idx]; } + expr * get_expr() const { return m_expr; } + + unsigned get_depth() const { return m_depth; } + + int get_weight() const { return m_weight; } + symbol const & get_qid() const { return m_qid; } + symbol const & get_skid() const { return m_skid; } + unsigned get_num_patterns() const { return m_num_patterns; } + expr * const * get_patterns() const { return reinterpret_cast(get_decl_names() + m_num_decls); } + expr * get_pattern(unsigned idx) const { return get_patterns()[idx]; } + unsigned get_num_no_patterns() const { return m_num_no_patterns; } + expr * const * get_no_patterns() const { return reinterpret_cast(get_decl_names() + m_num_decls); } + expr * get_no_pattern(unsigned idx) const { return get_no_patterns()[idx]; } + bool has_patterns() const { return m_num_patterns > 0 || m_num_no_patterns > 0; } + unsigned get_size() const { return get_obj_size(m_num_decls, m_num_patterns, m_num_no_patterns); } + + bool may_have_unused_vars() const { return m_has_unused_vars; } + void set_no_unused_vars() { m_has_unused_vars = false; } + + bool has_labels() const { return m_has_labels; } + + unsigned get_num_children() const { return 1 + get_num_patterns() + get_num_no_patterns(); } + expr * get_child(unsigned idx) const { + SASSERT(idx < get_num_children()); + if (idx == 0) + return get_expr(); + else if (idx <= get_num_patterns()) + return get_pattern(idx - 1); + else + return get_no_pattern(idx - get_num_patterns() - 1); + } +}; + +// ----------------------------------- +// +// AST recognisers +// +// ----------------------------------- + +inline bool is_decl(ast const * n) { ast_kind k = n->get_kind(); return k == AST_FUNC_DECL || k == AST_SORT; } +inline bool is_sort(ast const * n) { return n->get_kind() == AST_SORT; } +inline bool is_func_decl(ast const * n) { return n->get_kind() == AST_FUNC_DECL; } +inline bool is_expr(ast const * n) { return !is_decl(n); } +inline bool is_app(ast const * n) { return n->get_kind() == AST_APP; } +inline bool is_var(ast const * n) { return n->get_kind() == AST_VAR; } +inline bool is_quantifier(ast const * n) { return n->get_kind() == AST_QUANTIFIER; } +inline bool is_forall(ast const * n) { return is_quantifier(n) && static_cast(n)->is_forall(); } + +// ----------------------------------- +// +// AST coercions +// +// ----------------------------------- + +inline decl * to_decl(ast * n) { SASSERT(is_decl(n)); return static_cast(n); } +inline sort * to_sort(ast * n) { SASSERT(is_sort(n)); return static_cast(n); } +inline func_decl * to_func_decl(ast * n) { SASSERT(is_func_decl(n)); return static_cast(n); } +inline expr * to_expr(ast * n) { SASSERT(is_expr(n)); return static_cast(n); } +inline app * to_app(ast * n) { SASSERT(is_app(n)); return static_cast(n); } +inline var * to_var(ast * n) { SASSERT(is_var(n)); return static_cast(n); } +inline quantifier * to_quantifier(ast * n) { SASSERT(is_quantifier(n)); return static_cast(n); } + +inline decl const * to_decl(ast const * n) { SASSERT(is_decl(n)); return static_cast(n); } +inline sort const * to_sort(ast const * n) { SASSERT(is_sort(n)); return static_cast(n); } +inline func_decl const * to_func_decl(ast const * n) { SASSERT(is_func_decl(n)); return static_cast(n); } +inline expr const * to_expr(ast const * n) { SASSERT(is_expr(n)); return static_cast(n); } +inline app const * to_app(ast const * n) { SASSERT(is_app(n)); return static_cast(n); } +inline var const * to_var(ast const * n) { SASSERT(is_var(n)); return static_cast(n); } +inline quantifier const * to_quantifier(ast const * n) { SASSERT(is_quantifier(n)); return static_cast(n); } + +// ----------------------------------- +// +// AST hash-consing +// +// ----------------------------------- + +unsigned get_node_hash(ast const * n); +bool compare_nodes(ast const * n1, ast const * n2); +unsigned get_node_size(ast const * n); +unsigned get_asts_hash(unsigned sz, ast * const* ns, unsigned init); +unsigned get_apps_hash(unsigned sz, app * const* ns, unsigned init); +unsigned get_exprs_hash(unsigned sz, expr * const* ns, unsigned init); +unsigned get_sorts_hash(unsigned sz, sort * const* ns, unsigned init); +unsigned get_decl_hash(unsigned sz, func_decl* const* ns, unsigned init); + +// This is the internal comparison functor for hash-consing AST nodes. +struct ast_eq_proc { + bool operator()(ast const * n1, ast const * n2) const { + return n1->hash() == n2->hash() && compare_nodes(n1, n2); + } +}; + +class ast_table : public chashtable, ast_eq_proc> { +public: + void erase(ast * n); +}; + +// ----------------------------------- +// +// decl_plugin +// +// ----------------------------------- + +/** + \brief Auxiliary data-structure used to initialize the parser symbol tables. +*/ +struct builtin_name { + decl_kind m_kind; + symbol m_name; + builtin_name(char const * name, decl_kind k) : m_kind(k), m_name(name) {} +}; + +/** + \brief Each family of intepreted function declarations and sorts must provide a plugin + to build sorts and decls of the family. +*/ +class decl_plugin { +protected: + ast_manager * m_manager; + family_id m_family_id; + + virtual void set_manager(ast_manager * m, family_id id) { + SASSERT(m_manager == 0); + m_manager = m; + m_family_id = id; + } + + friend class ast_manager; + +public: + decl_plugin():m_manager(0), m_family_id(null_family_id) {} + + virtual ~decl_plugin() {} + virtual void finalize() {} + + virtual decl_plugin * mk_fresh() = 0; + + family_id get_family_id() const { return m_family_id; } + + virtual sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) = 0; + + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) = 0; + + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const* parameters, + unsigned num_args, expr * const * args, sort * range); + + virtual bool is_value(app*) const { return false; } + + virtual bool are_distinct(app* a, app* b) const { return a != b && is_value(a) && is_value(b); } + + virtual void get_op_names(svector & op_names, symbol const & logic = symbol()) {} + + virtual void get_sort_names(svector & sort_names, symbol const & logic = symbol()) {} + + virtual expr * get_some_value(sort * s) { return 0; } + + // Return true if the interpreted sort s does not depend on uninterpreted sorts. + // This may be the case, for example, for array and datatype sorts. + virtual bool is_fully_interp(sort const * s) const { return true; } + + // Event handlers for deleting/translating PARAM_EXTERNAL + virtual void del(parameter const & p) {} + virtual parameter translate(parameter const & p, decl_plugin & target) { UNREACHABLE(); return p; } +}; + +// ----------------------------------- +// +// basic_decl_plugin (i.e., builtin plugin) +// +// ----------------------------------- + +enum basic_sort_kind { + BOOL_SORT, + PROOF_SORT +}; + +enum basic_op_kind { + OP_TRUE, OP_FALSE, OP_EQ, OP_DISTINCT, OP_ITE, OP_AND, OP_OR, OP_IFF, OP_XOR, OP_NOT, OP_IMPLIES, OP_OEQ, LAST_BASIC_OP, + + PR_UNDEF, PR_TRUE, PR_ASSERTED, PR_GOAL, PR_MODUS_PONENS, PR_REFLEXIVITY, PR_SYMMETRY, PR_TRANSITIVITY, PR_TRANSITIVITY_STAR, PR_MONOTONICITY, PR_QUANT_INTRO, + PR_DISTRIBUTIVITY, PR_AND_ELIM, PR_NOT_OR_ELIM, PR_REWRITE, PR_REWRITE_STAR, PR_PULL_QUANT, + PR_PULL_QUANT_STAR, PR_PUSH_QUANT, PR_ELIM_UNUSED_VARS, PR_DER, PR_QUANT_INST, + + PR_HYPOTHESIS, PR_LEMMA, PR_UNIT_RESOLUTION, PR_IFF_TRUE, PR_IFF_FALSE, PR_COMMUTATIVITY, PR_DEF_AXIOM, + + PR_DEF_INTRO, PR_APPLY_DEF, PR_IFF_OEQ, PR_NNF_POS, PR_NNF_NEG, PR_NNF_STAR, PR_SKOLEMIZE, PR_CNF_STAR, + PR_MODUS_PONENS_OEQ, PR_TH_LEMMA, LAST_BASIC_PR +}; + +class basic_decl_plugin : public decl_plugin { +protected: + sort * m_bool_sort; + func_decl * m_true_decl; + func_decl * m_false_decl; + func_decl * m_and_decl; + func_decl * m_or_decl; + func_decl * m_iff_decl; + func_decl * m_xor_decl; + func_decl * m_not_decl; + func_decl * m_implies_decl; + ptr_vector m_eq_decls; // cached eqs + ptr_vector m_ite_decls; // cached ites + + ptr_vector m_oeq_decls; // cached obsevational eqs + sort * m_proof_sort; + func_decl * m_undef_decl; + func_decl * m_true_pr_decl; + func_decl * m_asserted_decl; + func_decl * m_goal_decl; + func_decl * m_modus_ponens_decl; + func_decl * m_reflexivity_decl; + func_decl * m_symmetry_decl; + func_decl * m_transitivity_decl; + func_decl * m_quant_intro_decl; + func_decl * m_and_elim_decl; + func_decl * m_not_or_elim_decl; + func_decl * m_rewrite_decl; + func_decl * m_pull_quant_decl; + func_decl * m_pull_quant_star_decl; + func_decl * m_push_quant_decl; + func_decl * m_elim_unused_vars_decl; + func_decl * m_der_decl; + func_decl * m_quant_inst_decl; + ptr_vector m_monotonicity_decls; + ptr_vector m_transitivity_star_decls; + ptr_vector m_distributivity_decls; + ptr_vector m_assoc_flat_decls; + ptr_vector m_rewrite_star_decls; + + func_decl * m_hypothesis_decl; + func_decl * m_iff_true_decl; + func_decl * m_iff_false_decl; + func_decl * m_commutativity_decl; + func_decl * m_def_axiom_decl; + func_decl * m_lemma_decl; + ptr_vector m_unit_resolution_decls; + + func_decl * m_def_intro_decl; + func_decl * m_iff_oeq_decl; + func_decl * m_skolemize_decl; + func_decl * m_mp_oeq_decl; + ptr_vector m_apply_def_decls; + ptr_vector m_nnf_pos_decls; + ptr_vector m_nnf_neg_decls; + ptr_vector m_nnf_star_decls; + ptr_vector m_cnf_star_decls; + + ptr_vector m_th_lemma_decls; + + static bool is_proof(decl_kind k) { return k > LAST_BASIC_OP; } + bool check_proof_sorts(basic_op_kind k, unsigned arity, sort * const * domain) const; + bool check_proof_args(basic_op_kind k, unsigned num_args, expr * const * args) const; + func_decl * mk_bool_op_decl(char const * name, basic_op_kind k, unsigned num_args = 0, + bool asooc = false, bool comm = false, bool idempotent = false, bool flat_associative = false, bool chainable = false); + func_decl * mk_implies_decl(); + func_decl * mk_proof_decl(char const * name, basic_op_kind k, unsigned num_parents); + func_decl * mk_proof_decl(char const * name, basic_op_kind k, unsigned num_parents, func_decl*& fn); + func_decl * mk_proof_decl(char const * name, basic_op_kind k, unsigned num_parents, ptr_vector & cache); + func_decl * mk_compressed_proof_decl(char const * name, basic_op_kind k, unsigned num_parents); + func_decl * mk_proof_decl(basic_op_kind k, unsigned num_parents); + func_decl * mk_proof_decl(basic_op_kind k, unsigned num_parameters, parameter const* params, unsigned num_parents); + func_decl * mk_proof_decl( + char const * name, basic_op_kind k, + unsigned num_parameters, parameter const* params, unsigned num_parents); + + + virtual void set_manager(ast_manager * m, family_id id); + func_decl * mk_eq_decl_core(char const * name, decl_kind k, sort * s, ptr_vector & cache); + func_decl * mk_ite_decl(sort * s); +public: + basic_decl_plugin(); + + virtual ~basic_decl_plugin() {} + virtual void finalize(); + + virtual decl_plugin * mk_fresh() { + return alloc(basic_decl_plugin); + } + + virtual sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const* parameters); + + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned num_args, expr * const * args, sort * range); + + virtual void get_op_names(svector & op_names, symbol const & logic); + + virtual void get_sort_names(svector & sort_names, symbol const & logic); + + virtual bool is_value(app* a) const; + + sort * mk_bool_sort() const { return m_bool_sort; } + sort * mk_proof_sort() const { return m_proof_sort; } + + virtual expr * get_some_value(sort * s); +}; + +typedef app proof; /* a proof is just an applicaton */ + +// ----------------------------------- +// +// label_decl_plugin +// +// ----------------------------------- + +enum label_op_kind { + OP_LABEL, + OP_LABEL_LIT, +}; + +/** + \brief Labels are identity functions used to mark sub-expressions. +*/ +class label_decl_plugin : public decl_plugin { + symbol m_lblpos; + symbol m_lblneg; + symbol m_lbllit; + + virtual void set_manager(ast_manager * m, family_id id); + +public: + label_decl_plugin(); + virtual ~label_decl_plugin(); + + virtual decl_plugin * mk_fresh() { return alloc(label_decl_plugin); } + + + virtual sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters); + + /** + contract: when label + parameter[0] (int): 0 - if the label is negative, 1 - if positive. + parameter[1] (symbol): label's tag. + ... + parameter[n-1] (symbol): label's tag. + + contract: when label literal (they are always positive) + parameter[0] (symbol): label's tag + ... + parameter[n-1] (symbol): label's tag. + */ + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); +}; + +// ----------------------------------- +// +// pattern_decl_plugin +// +// ----------------------------------- + +enum pattern_op_kind { + OP_PATTERN +}; + +/** + \brief Patterns are used to group expressions. These expressions are using during E-matching for + heurisitic quantifier instantiation. +*/ +class pattern_decl_plugin : public decl_plugin { + sort * m_list; +public: + virtual decl_plugin * mk_fresh() { return alloc(pattern_decl_plugin); } + + virtual sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters); + + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); +}; + +// ----------------------------------- +// +// model_value_plugin +// +// ----------------------------------- + +enum model_value_op_kind { + OP_MODEL_VALUE +}; + +/** + \brief Values are used during model construction. All values are + assumed to be different. Users should not use them, since they may + introduce unsoundess if the sort of a value is finite. + + Moreover, values should never be internalized in a logical context. + + However, values can be used during evaluation (i.e., simplification). + + \remark Model values can be viewed as the partion ids in Z3 1.x. +*/ +class model_value_decl_plugin : public decl_plugin { +public: + model_value_decl_plugin() {} + + virtual decl_plugin * mk_fresh() { return alloc(model_value_decl_plugin); } + + virtual sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters); + + /** + contract: + parameter[0]: (integer) value idx + parameter[1]: (ast) sort of the value. + */ + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + + virtual bool is_value(app* n) const; +}; + +// ----------------------------------- +// +// user_sort_plugin for supporting user declared sorts in SMT2 +// +// ----------------------------------- + +class user_sort_plugin : public decl_plugin { + svector m_sort_names; + dictionary m_name2decl_kind; +public: + user_sort_plugin() {} + + virtual sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters); + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + decl_kind register_name(symbol s); + virtual decl_plugin * mk_fresh(); +}; + + +// ----------------------------------- +// +// Auxiliary functions +// +// ----------------------------------- + +// Return true if n is an application of d. +inline bool is_app_of(expr const * n, func_decl const * d) { return n->get_kind() == AST_APP && to_app(n)->get_decl() == d; } +inline bool is_app_of(expr const * n, family_id fid, decl_kind k) { return n->get_kind() == AST_APP && to_app(n)->is_app_of(fid, k); } +inline bool is_sort_of(sort const * s, family_id fid, decl_kind k) { return s->is_sort_of(fid, k); } +inline bool is_uninterp_const(expr const * n) { return n->get_kind() == AST_APP && to_app(n)->get_num_args() == 0 && to_app(n)->get_family_id() == null_family_id; } +inline bool is_uninterp(expr const * n) { return n->get_kind() == AST_APP && to_app(n)->get_family_id() == null_family_id; } +inline bool is_decl_of(func_decl const * d, family_id fid, decl_kind k) { return d->get_family_id() == fid && d->get_decl_kind() == k; } +inline bool is_ground(expr const * n) { return is_app(n) && to_app(n)->is_ground(); } +inline bool is_non_ground(expr const * n) { return ( ! is_ground(n)); } + +inline unsigned get_depth(expr const * n) { + if (is_app(n)) return to_app(n)->get_depth(); + else if (is_quantifier(n)) return to_quantifier(n)->get_depth(); + else return 1; +} + +inline bool has_quantifiers(expr const * n) { + return is_app(n) ? to_app(n)->has_quantifiers() : is_quantifier(n); +} + +inline bool has_labels(expr const * n) { + if (is_app(n)) return to_app(n)->has_labels(); + else if (is_quantifier(n)) return to_quantifier(n)->has_labels(); + else return false; +} + +// ----------------------------------- +// +// Get Some Value functor +// +// Functor for returning some value +// of the given sort. +// +// ----------------------------------- +class some_value_proc { +public: + virtual expr * operator()(sort * s) = 0; +}; + +// ----------------------------------- +// +// Proof generation mode +// +// ----------------------------------- + +enum proof_gen_mode { + PGM_DISABLED, + PGM_COARSE, + PGM_FINE +}; + +// ----------------------------------- +// +// ast_manager +// +// ----------------------------------- + +class ast_manager { +protected: +protected: + struct config { + typedef ast_manager value_manager; + typedef small_object_allocator allocator; + static const bool ref_count = true; + }; + + struct array_config : public config { + static const bool preserve_roots = true; + static const unsigned max_trail_sz = 16; + static const unsigned factor = 2; + }; + + struct expr_array_config : public array_config { + typedef expr * value; + }; + + typedef parray_manager expr_array_manager; + + struct expr_dependency_config : public config { + typedef expr * value; + }; + + typedef dependency_manager expr_dependency_manager; + +public: + typedef expr_array_manager::ref expr_array; + typedef expr_dependency_manager::dependency expr_dependency; + +protected: + struct expr_dependency_array_config : public array_config { + typedef expr_dependency * value; + }; + + typedef parray_manager expr_dependency_array_manager; + +public: + typedef expr_dependency_array_manager::ref expr_dependency_array; + +protected: + small_object_allocator m_alloc; + family_manager m_family_manager; + expr_array_manager m_expr_array_manager; + expr_dependency_manager m_expr_dependency_manager; + expr_dependency_array_manager m_expr_dependency_array_manager; + ptr_vector m_plugins; + proof_gen_mode m_proof_mode; + family_id m_basic_family_id; + family_id m_label_family_id; + family_id m_pattern_family_id; + family_id m_model_value_family_id; + family_id m_user_sort_family_id; + ast_table m_ast_table; + id_gen m_expr_id_gen; + id_gen m_decl_id_gen; + sort * m_bool_sort; + sort * m_proof_sort; + app * m_true; + app * m_false; + proof * m_undef_proof; + unsigned m_fresh_id; + bool m_debug_ref_count; + u_map m_debug_free_indices; + std::ostream* m_trace_stream; +#ifdef Z3DEBUG + bool slow_not_contains(ast const * n); +#endif + ast_manager * m_format_manager; // hack for isolating format objects in a different manager. + + void init(); + +public: + ast_manager(proof_gen_mode = PGM_DISABLED, std::ostream * trace_stream = NULL, bool is_format_manager = false); + ast_manager(ast_manager const & src, bool disable_proofs = false); + ~ast_manager(); + + // For debugging purposes + void display_free_ids(std::ostream & out) { m_expr_id_gen.display_free_ids(out); out << "\n"; m_decl_id_gen.display_free_ids(out); } + + void compact_memory(); + + void compress_ids(); + + // Equivalent to throw ast_exception(msg) + void raise_exception(char const * msg); + + bool is_format_manager() const { return m_format_manager == 0; } + + ast_manager & get_format_manager() { return is_format_manager() ? *this : *m_format_manager; } + + void copy_families_plugins(ast_manager const & from); + + small_object_allocator & get_allocator() { return m_alloc; } + + family_id get_family_id(symbol const & s) const { return const_cast(this)->m_family_manager.get_family_id(s); } + + family_id get_family_id(char const * s) const { return get_family_id(symbol(s)); } + + symbol const & get_family_name(family_id fid) const { return m_family_manager.get_name(fid); } + + bool is_builtin_family_id(family_id fid) const { + return + fid == null_family_id || + fid == m_basic_family_id || + fid == m_label_family_id || + fid == m_pattern_family_id || + fid == m_model_value_family_id || + fid == m_user_sort_family_id; + } + + void register_plugin(symbol const & s, decl_plugin * plugin); + + void register_plugin(family_id id, decl_plugin * plugin); + + void register_decl_plugins(); + + decl_plugin * get_plugin(family_id fid) const; + + bool has_plugin(family_id fid) const { return get_plugin(fid) != 0; } + + bool has_plugin(symbol const & s) const { return has_plugin(get_family_id(s)); } + + void get_dom(svector & dom) const { m_family_manager.get_dom(dom); } + + void get_range(svector & range) const { m_family_manager.get_range(range); } + + family_id get_basic_family_id() const { return m_basic_family_id; } + + basic_decl_plugin * get_basic_decl_plugin() const { return static_cast(get_plugin(m_basic_family_id)); } + + family_id get_user_sort_family_id() const { return m_user_sort_family_id; } + + user_sort_plugin * get_user_sort_plugin() const { return static_cast(get_plugin(m_user_sort_family_id)); } + + /** + \brief Debugging support method: set the next expression identifier to be the least value id' s.t. + - id' >= id + - id' is not used by any AST in m_table + - id' is not in the expression m_free_ids + + This method should be only used to create small repros that exposes bugs in Z3. + */ + void set_next_expr_id(unsigned id); + + bool is_value(expr* e) const; + + bool are_distinct(expr* a, expr* b) const; + + bool contains(ast * a) const { return m_ast_table.contains(a); } + + unsigned get_num_asts() const { return m_ast_table.size(); } + + void debug_ref_count() { m_debug_ref_count = true; } + + void inc_ref(ast * n) { + if (n) + n->inc_ref(); + } + + void dec_ref(ast * n) { + if (n) { + n->dec_ref(); + if (n->get_ref_count() == 0) + delete_node(n); + } + } + + template + void inc_array_ref(unsigned sz, T * const * a) { + for(unsigned i = 0; i < sz; i++) { + inc_ref(a[i]); + } + } + + template + void dec_array_ref(unsigned sz, T * const * a) { + for(unsigned i = 0; i < sz; i++) { + dec_ref(a[i]); + } + } + + static unsigned get_node_size(ast const * n); + + size_t get_allocation_size() const { + return m_alloc.get_allocation_size(); + } + +protected: + ast * register_node_core(ast * n); + + template + T * register_node(T * n) { + return static_cast(register_node_core(n)); + } + + void delete_node(ast * n); + + void * allocate_node(unsigned size) { + return m_alloc.allocate(size); + } + + void deallocate_node(ast * n, unsigned sz) { + m_alloc.deallocate(sz, n); + } + +public: + sort * get_sort(expr const * n) const; + void check_sort(func_decl const * decl, unsigned num_args, expr * const * args) const; + void check_sorts_core(ast const * n) const; + bool check_sorts(ast const * n) const; + + bool is_bool(expr const * n) const; + bool is_bool(sort const * s) const { return s == m_bool_sort; } + decl_kind get_eq_op(expr const * n) const { return is_bool(n) ? OP_IFF : OP_EQ; } + +private: + sort * mk_sort(symbol const & name, sort_info * info); + +public: + sort * mk_sort(symbol const & name) { return mk_sort(name, 0); } + + sort * mk_sort(symbol const & name, sort_info const & info) { + if (info.get_family_id() == null_family_id) { + return mk_sort(name, 0); + } + else { + return mk_sort(name, &const_cast(info)); + } + } + + sort * mk_sort(family_id fid, decl_kind k, unsigned num_parameters = 0, parameter const * parameters = 0); + + sort * mk_bool_sort() const { return m_bool_sort; } + + sort * mk_proof_sort() const { return m_proof_sort; } + + sort * mk_fresh_sort(char const * prefix = ""); + + bool is_uninterp(sort const * s) const { return s->get_family_id() == null_family_id || s->get_family_id() == m_user_sort_family_id; } + + /** + \brief A sort is "fully" interpreted if it is interpreted, + and doesn't depend on other uninterpreted sorts. + */ + bool is_fully_interp(sort const * s) const; + + func_decl * mk_func_decl(family_id fid, decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range = 0); + + func_decl * mk_func_decl(family_id fid, decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned num_args, expr * const * args, sort * range = 0); + + app * mk_app(family_id fid, decl_kind k, unsigned num_parameters = 0, parameter const * parameters = 0, + unsigned num_args = 0, expr * const * args = 0, sort * range = 0); + + app * mk_app(family_id fid, decl_kind k, unsigned num_args, expr * const * args); + + app * mk_app(family_id fid, decl_kind k, expr * arg); + + app * mk_app(family_id fid, decl_kind k, expr * arg1, expr * arg2); + + app * mk_app(family_id fid, decl_kind k, expr * arg1, expr * arg2, expr * arg3); + + app * mk_const(family_id fid, decl_kind k) { return mk_app(fid, k, 0, static_cast(0)); } +private: + func_decl * mk_func_decl(symbol const & name, unsigned arity, sort * const * domain, sort * range, + func_decl_info * info); + + app * mk_app_core(func_decl * decl, expr * arg1, expr * arg2); + + app * mk_app_core(func_decl * decl, unsigned num_args, expr * const * args); + +public: + func_decl * mk_func_decl(symbol const & name, unsigned arity, sort * const * domain, sort * range) { + return mk_func_decl(name, arity, domain, range, static_cast(0)); + } + + func_decl * mk_func_decl(symbol const & name, unsigned arity, sort * const * domain, sort * range, + func_decl_info const & info) { + if (info.is_null()) { + return mk_func_decl(name, arity, domain, range, static_cast(0)); + } + else { + return mk_func_decl(name, arity, domain, range, & const_cast(info)); + } + } + + func_decl * mk_func_decl(unsigned arity, sort * const * domain, func_decl_info const & info) { + return mk_func_decl(info.get_family_id(), info.get_decl_kind(), info.get_num_parameters(), info.get_parameters(), + arity, domain); + } + + func_decl * mk_const_decl(symbol const & name, sort * s) { + return mk_func_decl(name, static_cast(0), 0, s); + } + + func_decl * mk_const_decl(symbol const & name, sort * s, func_decl_info const & info) { + return mk_func_decl(name, static_cast(0), 0, s, info); + } + + func_decl * mk_func_decl(symbol const & name, sort * domain, sort * range, func_decl_info const & info) { + return mk_func_decl(name, 1, &domain, range, info); + } + + func_decl * mk_func_decl(symbol const & name, sort * domain, sort * range) { + return mk_func_decl(name, 1, &domain, range); + } + + func_decl * mk_func_decl(symbol const & name, sort * domain1, sort * domain2, sort * range, func_decl_info const & info) { + sort * d[2] = { domain1, domain2 }; + return mk_func_decl(name, 2, d, range, info); + } + + func_decl * mk_func_decl(symbol const & name, sort * domain1, sort * domain2, sort * range) { + sort * d[2] = { domain1, domain2 }; + return mk_func_decl(name, 2, d, range); + } + + func_decl * mk_func_decl(symbol const & name, unsigned arity, sort * const * domain, sort * range, + bool assoc, bool comm = false, bool inj = false); + + func_decl * mk_func_decl(symbol const & name, sort * domain1, sort * domain2, sort * range, bool assoc, bool comm = false) { + sort * d[2] = { domain1, domain2 }; + return mk_func_decl(name, 2, d, range, assoc, comm, false); + } + + app * mk_app(func_decl * decl, unsigned num_args, expr * const * args); + + app * mk_app(func_decl * decl, expr * const * args) { + return mk_app(decl, decl->get_arity(), args); + } + + app * mk_app(func_decl * decl, expr * arg) { + SASSERT(decl->get_arity() == 1); + return mk_app(decl, 1, &arg); + } + + app * mk_app(func_decl * decl, expr * arg1, expr * arg2) { + SASSERT(decl->get_arity() == 2); + expr * args[2] = { arg1, arg2 }; + return mk_app(decl, 2, args); + } + + app * mk_app(func_decl * decl, expr * arg1, expr * arg2, expr * arg3) { + SASSERT(decl->get_arity() == 3); + expr * args[3] = { arg1, arg2, arg3 }; + return mk_app(decl, 3, args); + } + + app * mk_const(func_decl * decl) { + SASSERT(decl->get_arity() == 0); + return mk_app(decl, static_cast(0), static_cast(0)); + } + + app * mk_const(symbol const & name, sort * s) { + return mk_const(mk_const_decl(name, s)); + } + + func_decl * mk_fresh_func_decl(symbol const & prefix, symbol const & suffix, unsigned arity, + sort * const * domain, sort * range); + + func_decl * mk_fresh_func_decl(unsigned arity, sort * const * domain, sort * range) { return mk_fresh_func_decl(symbol::null, symbol::null, arity, domain, range); } + + func_decl * mk_fresh_func_decl(char const * prefix, char const * suffix, unsigned arity, + sort * const * domain, sort * range) { + return mk_fresh_func_decl(symbol(prefix), symbol(suffix), arity, domain, range); + } + + func_decl * mk_fresh_func_decl(char const * prefix, unsigned arity, sort * const * domain, sort * range) { + return mk_fresh_func_decl(symbol(prefix), symbol::null, arity, domain, range); + } + + app * mk_fresh_const(char const * prefix, sort * s) { return mk_const(mk_fresh_func_decl(prefix, 0, 0, s)); } + + symbol mk_fresh_var_name(char const * prefix = 0); + + var * mk_var(unsigned idx, sort * ty); + + app * mk_label(bool pos, unsigned num_names, symbol const * names, expr * n); + + app * mk_label(bool pos, symbol const & name, expr * n); + + bool is_label(expr const * n, bool & pos, buffer & names) const; + + bool is_label(expr const * n, bool & pos, buffer & names, expr*& l) const { + return is_label(n, pos, names)?(l = to_app(n)->get_arg(0), true):false; + } + + bool is_label(expr const * n) const { return is_app_of(n, m_label_family_id, OP_LABEL); } + + bool is_label(expr const * n, expr*& l) const { + return is_label(n)?(l = to_app(n)->get_arg(0), true):false; + } + + bool is_label(expr const * n, bool& pos) const { + if (is_app_of(n, m_label_family_id, OP_LABEL)) { + pos = to_app(n)->get_decl()->get_parameter(0).get_int() != 0; + return true; + } + else { + return false; + } + } + + app * mk_label_lit(unsigned num_names, symbol const * names); + + app * mk_label_lit(symbol const & name); + + bool is_label_lit(expr const * n, buffer & names) const; + + bool is_label_lit(expr const * n) const { return is_app_of(n, m_label_family_id, OP_LABEL_LIT); } + + family_id get_label_family_id() const { return m_label_family_id; } + + app * mk_pattern(unsigned num_exprs, app * const * exprs); + + app * mk_pattern(app * expr) { return mk_pattern(1, &expr); } + + bool is_pattern(expr const * n) const; + +public: + + quantifier * mk_quantifier(bool forall, unsigned num_decls, sort * const * decl_sorts, symbol const * decl_names, expr * body, + int weight = 0, symbol const & qid = symbol::null, symbol const & skid = symbol::null, + unsigned num_patterns = 0, expr * const * patterns = 0, + unsigned num_no_patterns = 0, expr * const * no_patterns = 0); + + quantifier * mk_forall(unsigned num_decls, sort * const * decl_sorts, symbol const * decl_names, expr * body, + int weight = 0, symbol const & qid = symbol::null, symbol const & skid = symbol::null, + unsigned num_patterns = 0, expr * const * patterns = 0, + unsigned num_no_patterns = 0, expr * const * no_patterns = 0) { + return mk_quantifier(true, num_decls, decl_sorts, decl_names, body, weight, qid, skid, num_patterns, patterns, + num_no_patterns, no_patterns); + } + + quantifier * mk_exists(unsigned num_decls, sort * const * decl_sorts, symbol const * decl_names, expr * body, + int weight = 0, symbol const & qid = symbol::null, symbol const & skid = symbol::null, + unsigned num_patterns = 0, expr * const * patterns = 0, + unsigned num_no_patterns = 0, expr * const * no_patterns = 0) { + return mk_quantifier(false, num_decls, decl_sorts, decl_names, body, weight, qid, skid, num_patterns, patterns, + num_no_patterns, no_patterns); + } + + quantifier * update_quantifier(quantifier * q, unsigned new_num_patterns, expr * const * new_patterns, expr * new_body); + + quantifier * update_quantifier(quantifier * q, unsigned new_num_patterns, expr * const * new_patterns, unsigned new_num_no_patterns, expr * const * new_no_patterns, expr * new_body); + + quantifier * update_quantifier(quantifier * q, expr * new_body); + + quantifier * update_quantifier_weight(quantifier * q, int new_weight); + + quantifier * update_quantifier(quantifier * q, bool new_is_forall, expr * new_body); + + quantifier * update_quantifier(quantifier * q, bool new_is_forall, unsigned new_num_patterns, expr * const * new_patterns, expr * new_body); + +// ----------------------------------- +// +// expr_array +// +// ----------------------------------- +public: + void mk(expr_array & r) { m_expr_array_manager.mk(r); } + void del(expr_array & r) { m_expr_array_manager.del(r); } + void copy(expr_array const & s, expr_array & r) { m_expr_array_manager.copy(s, r); } + unsigned size(expr_array const & r) const { return m_expr_array_manager.size(r); } + bool empty(expr_array const & r) const { return m_expr_array_manager.empty(r); } + expr * get(expr_array const & r, unsigned i) const { return m_expr_array_manager.get(r, i); } + void set(expr_array & r, unsigned i, expr * v) { m_expr_array_manager.set(r, i, v); } + void set(expr_array const & s, unsigned i, expr * v, expr_array & r) { m_expr_array_manager.set(s, i, v, r); } + void push_back(expr_array & r, expr * v) { m_expr_array_manager.push_back(r, v); } + void push_back(expr_array const & s, expr * v, expr_array & r) { m_expr_array_manager.push_back(s, v, r); } + void pop_back(expr_array & r) { m_expr_array_manager.pop_back(r); } + void pop_back(expr_array const & s, expr_array & r) { m_expr_array_manager.pop_back(s, r); } + void unshare(expr_array & r) { m_expr_array_manager.unshare(r); } + void unfold(expr_array & r) { m_expr_array_manager.unfold(r); } + void reroot(expr_array & r) { m_expr_array_manager.reroot(r); } + +// ----------------------------------- +// +// expr_dependency +// +// ----------------------------------- +public: + expr_dependency * mk_empty_dependencies() { return m_expr_dependency_manager.mk_empty(); } + expr_dependency * mk_leaf(expr * t) { return m_expr_dependency_manager.mk_leaf(t); } + expr_dependency * mk_join(unsigned n, expr * const * ts); + expr_dependency * mk_join(expr_dependency * d1, expr_dependency * d2) { return m_expr_dependency_manager.mk_join(d1, d2); } + void inc_ref(expr_dependency * d) { if (d) m_expr_dependency_manager.inc_ref(d); } + void dec_ref(expr_dependency * d) { if (d) m_expr_dependency_manager.dec_ref(d); } + void linearize(expr_dependency * d, ptr_vector & ts); + bool contains(expr_dependency * d, expr * t) { return m_expr_dependency_manager.contains(d, t); } + +// ----------------------------------- +// +// expr_dependency_array +// +// ----------------------------------- +public: + void mk(expr_dependency_array & r) { m_expr_dependency_array_manager.mk(r); } + void del(expr_dependency_array & r) { m_expr_dependency_array_manager.del(r); } + void copy(expr_dependency_array const & s, expr_dependency_array & r) { m_expr_dependency_array_manager.copy(s, r); } + unsigned size(expr_dependency_array const & r) const { return m_expr_dependency_array_manager.size(r); } + bool empty(expr_dependency_array const & r) const { return m_expr_dependency_array_manager.empty(r); } + expr_dependency * get(expr_dependency_array const & r, unsigned i) const { return m_expr_dependency_array_manager.get(r, i); } + void set(expr_dependency_array & r, unsigned i, expr_dependency * v) { m_expr_dependency_array_manager.set(r, i, v); } + void set(expr_dependency_array const & s, unsigned i, expr_dependency * v, expr_dependency_array & r) { + m_expr_dependency_array_manager.set(s, i, v, r); + } + void push_back(expr_dependency_array & r, expr_dependency * v) { m_expr_dependency_array_manager.push_back(r, v); } + void push_back(expr_dependency_array const & s, expr_dependency * v, expr_dependency_array & r) { + m_expr_dependency_array_manager.push_back(s, v, r); + } + void pop_back(expr_dependency_array & r) { m_expr_dependency_array_manager.pop_back(r); } + void pop_back(expr_dependency_array const & s, expr_dependency_array & r) { m_expr_dependency_array_manager.pop_back(s, r); } + void unshare(expr_dependency_array & r) { m_expr_dependency_array_manager.unshare(r); } + void unfold(expr_dependency_array & r) { m_expr_dependency_array_manager.unfold(r); } + void reroot(expr_dependency_array & r) { m_expr_dependency_array_manager.reroot(r); } + +// ----------------------------------- +// +// Builtin operators +// +// ----------------------------------- + +public: + + bool is_or(expr const * n) const { return is_app_of(n, m_basic_family_id, OP_OR); } + bool is_implies(expr const * n) const { return is_app_of(n, m_basic_family_id, OP_IMPLIES); } + bool is_and(expr const * n) const { return is_app_of(n, m_basic_family_id, OP_AND); } + bool is_not(expr const * n) const { return is_app_of(n, m_basic_family_id, OP_NOT); } + bool is_eq(expr const * n) const { return is_app_of(n, m_basic_family_id, OP_EQ); } + bool is_oeq(expr const * n) const { return is_app_of(n, m_basic_family_id, OP_OEQ); } + bool is_distinct(expr const * n) const { return is_app_of(n, m_basic_family_id, OP_DISTINCT); } + bool is_iff(expr const * n) const { return is_app_of(n, m_basic_family_id, OP_IFF); } + bool is_xor(expr const * n) const { return is_app_of(n, m_basic_family_id, OP_XOR); } + bool is_ite(expr const * n) const { return is_app_of(n, m_basic_family_id, OP_ITE); } + bool is_term_ite(expr const * n) const { return is_ite(n) && !is_bool(n); } + bool is_true(expr const * n) const { return n == m_true; } + bool is_false(expr const * n) const { return n == m_false; } + bool is_complement_core(expr const * n1, expr const * n2) const { return (is_true(n1) && is_false(n2)) || (is_not(n1) && to_app(n1)->get_arg(0) == n2); } + bool is_complement(expr const * n1, expr const * n2) const { return is_complement_core(n1, n2) || is_complement_core(n2, n1); } + + bool is_or(func_decl const * d) const { return is_decl_of(d, m_basic_family_id, OP_OR); } + bool is_implies(func_decl const * d) const { return is_decl_of(d, m_basic_family_id, OP_IMPLIES); } + bool is_and(func_decl const * d) const { return is_decl_of(d, m_basic_family_id, OP_AND); } + bool is_not(func_decl const * d) const { return is_decl_of(d, m_basic_family_id, OP_NOT); } + bool is_eq(func_decl const * d) const { return is_decl_of(d, m_basic_family_id, OP_EQ); } + bool is_iff(func_decl const * d) const { return is_decl_of(d, m_basic_family_id, OP_IFF); } + bool is_xor(func_decl const * d) const { return is_decl_of(d, m_basic_family_id, OP_XOR); } + bool is_ite(func_decl const * d) const { return is_decl_of(d, m_basic_family_id, OP_ITE); } + bool is_term_ite(func_decl const * d) const { return is_ite(d) && !is_bool(d->get_range()); } + bool is_distinct(func_decl const * d) const { return is_decl_of(d, m_basic_family_id, OP_DISTINCT); } + +public: + + MATCH_UNARY(is_not); + MATCH_BINARY(is_eq); + MATCH_BINARY(is_iff); + MATCH_BINARY(is_implies); + MATCH_BINARY(is_and); + MATCH_BINARY(is_or); + MATCH_BINARY(is_xor); + MATCH_TERNARY(is_and); + MATCH_TERNARY(is_or); + + bool is_ite(expr const* n, expr*& t1, expr*& t2, expr*& t3) const { + if (is_ite(n)) { + t1 = to_app(n)->get_arg(0); + t2 = to_app(n)->get_arg(1); + t3 = to_app(n)->get_arg(2); + return true; + } + return false; + } + +public: + app * mk_eq(expr * lhs, expr * rhs) { return mk_app(m_basic_family_id, get_eq_op(lhs), lhs, rhs); } + app * mk_iff(expr * lhs, expr * rhs) { return mk_app(m_basic_family_id, OP_IFF, lhs, rhs); } + app * mk_oeq(expr * lhs, expr * rhs) { return mk_app(m_basic_family_id, OP_OEQ, lhs, rhs); } + app * mk_xor(expr * lhs, expr * rhs) { return mk_app(m_basic_family_id, OP_XOR, lhs, rhs); } + app * mk_ite(expr * c, expr * t, expr * e) { return mk_app(m_basic_family_id, OP_ITE, c, t, e); } + app * mk_xor(unsigned num_args, expr * const * args) { return mk_app(m_basic_family_id, OP_XOR, num_args, args); } + app * mk_or(unsigned num_args, expr * const * args) { return mk_app(m_basic_family_id, OP_OR, num_args, args); } + app * mk_and(unsigned num_args, expr * const * args) { return mk_app(m_basic_family_id, OP_AND, num_args, args); } + app * mk_or(expr * arg1, expr * arg2) { return mk_app(m_basic_family_id, OP_OR, arg1, arg2); } + app * mk_and(expr * arg1, expr * arg2) { return mk_app(m_basic_family_id, OP_AND, arg1, arg2); } + app * mk_or(expr * arg1, expr * arg2, expr * arg3) { return mk_app(m_basic_family_id, OP_OR, arg1, arg2, arg3); } + app * mk_and(expr * arg1, expr * arg2, expr * arg3) { return mk_app(m_basic_family_id, OP_AND, arg1, arg2, arg3); } + app * mk_implies(expr * arg1, expr * arg2) { return mk_app(m_basic_family_id, OP_IMPLIES, arg1, arg2); } + app * mk_not(expr * n) { return mk_app(m_basic_family_id, OP_NOT, n); } + app * mk_distinct(unsigned num_args, expr * const * args) { return mk_app(m_basic_family_id, OP_DISTINCT, num_args, args); } + app * mk_distinct_expanded(unsigned num_args, expr * const * args); + app * mk_true() { return m_true; } + app * mk_false() { return m_false; } + + func_decl* mk_and_decl() { + sort* domain[2] = { m_bool_sort, m_bool_sort }; + return mk_func_decl(m_basic_family_id, OP_AND, 0, 0, 2, domain); + } + func_decl* mk_not_decl() { return mk_func_decl(m_basic_family_id, OP_NOT, 0, 0, 1, &m_bool_sort); } + func_decl* mk_or_decl() { + sort* domain[2] = { m_bool_sort, m_bool_sort }; + return mk_func_decl(m_basic_family_id, OP_OR, 0, 0, 2, domain); + } + +// ----------------------------------- +// +// Values +// +// ----------------------------------- + +protected: + some_value_proc * m_some_value_proc; +public: + app * mk_model_value(unsigned idx, sort * s); + bool is_model_value(expr const * n) const { return is_app_of(n, m_model_value_family_id, OP_MODEL_VALUE); } + bool is_model_value(func_decl const * d) const { return is_decl_of(d, m_model_value_family_id, OP_MODEL_VALUE); } + + expr * get_some_value(sort * s, some_value_proc * p); + expr * get_some_value(sort * s); + +// ----------------------------------- +// +// Proof generation +// +// ----------------------------------- + +protected: + proof * mk_proof(family_id fid, decl_kind k, unsigned num_args, expr * const * args); + proof * mk_proof(family_id fid, decl_kind k, expr * arg); + proof * mk_proof(family_id fid, decl_kind k, expr * arg1, expr * arg2); + proof * mk_proof(family_id fid, decl_kind k, expr * arg1, expr * arg2, expr * arg3); + +public: + bool proofs_enabled() const { return m_proof_mode != PGM_DISABLED; } + bool proofs_disabled() const { return m_proof_mode == PGM_DISABLED; } + bool coarse_grain_proofs() const { return m_proof_mode == PGM_COARSE; } + bool fine_grain_proofs() const { return m_proof_mode == PGM_FINE; } + proof_gen_mode proof_mode() const { return m_proof_mode; } + void toggle_proof_mode(proof_gen_mode m) { m_proof_mode = m; } // APIs for creating proof objects return [undef] + + proof * mk_undef_proof() const { return m_undef_proof; } + + bool is_proof(expr const * n) const { return is_app(n) && to_app(n)->get_decl()->get_range() == m_proof_sort; } + + bool is_undef_proof(expr const * e) const { return e == m_undef_proof; } + bool is_asserted(expr const * e) const { return is_app_of(e, m_basic_family_id, PR_ASSERTED); } + bool is_goal(expr const * e) const { return is_app_of(e, m_basic_family_id, PR_GOAL); } + bool is_modus_ponens(expr const * e) const { return is_app_of(e, m_basic_family_id, PR_MODUS_PONENS); } + bool is_reflexivity(expr const * e) const { return is_app_of(e, m_basic_family_id, PR_REFLEXIVITY); } + bool is_symmetry(expr const * e) const { return is_app_of(e, m_basic_family_id, PR_SYMMETRY); } + bool is_transitivity(expr const * e) const { return is_app_of(e, m_basic_family_id, PR_TRANSITIVITY); } + bool is_monotonicity(expr const * e) const { return is_app_of(e, m_basic_family_id, PR_MONOTONICITY); } + bool is_quant_intro(expr const * e) const { return is_app_of(e, m_basic_family_id, PR_QUANT_INTRO); } + bool is_quant_inst(expr const * e) const { return is_app_of(e, m_basic_family_id, PR_QUANT_INST); } + bool is_distributivity(expr const * e) const { return is_app_of(e, m_basic_family_id, PR_DISTRIBUTIVITY); } + bool is_and_elim(expr const * e) const { return is_app_of(e, m_basic_family_id, PR_AND_ELIM); } + bool is_not_or_elim(expr const * e) const { return is_app_of(e, m_basic_family_id, PR_NOT_OR_ELIM); } + bool is_rewrite(expr const * e) const { return is_app_of(e, m_basic_family_id, PR_REWRITE); } + bool is_rewrite_star(expr const * e) const { return is_app_of(e, m_basic_family_id, PR_REWRITE_STAR); } + bool is_unit_resolution(expr const * e) const { return is_app_of(e, m_basic_family_id, PR_UNIT_RESOLUTION); } + bool is_lemma(expr const * e) const { return is_app_of(e, m_basic_family_id, PR_LEMMA); } + bool is_quant_inst(expr const* e, expr*& not_q_or_i, ptr_vector& binding) const; + bool is_rewrite(expr const* e, expr*& r1, expr*& r2) const; + + + bool is_def_intro(expr const * e) const { return is_app_of(e, m_basic_family_id, PR_DEF_INTRO); } + bool is_apply_def(expr const * e) const { return is_app_of(e, m_basic_family_id, PR_APPLY_DEF); } + bool is_skolemize(expr const * e) const { return is_app_of(e, m_basic_family_id, PR_SKOLEMIZE); } + + MATCH_UNARY(is_asserted); + MATCH_UNARY(is_lemma); + + bool has_fact(proof const * p) const { + SASSERT(is_proof(p)); + unsigned n = p->get_num_args(); + return n > 0 && get_sort(p->get_arg(n - 1)) != m_proof_sort; + } + expr * get_fact(proof const * p) const { SASSERT(is_proof(p)); SASSERT(has_fact(p)); return p->get_arg(p->get_num_args() - 1); } + unsigned get_num_parents(proof const * p) const { + SASSERT(is_proof(p)); + unsigned n = p->get_num_args(); + return !has_fact(p) ? n : n - 1; + } + proof * get_parent(proof const * p, unsigned idx) const { SASSERT(is_proof(p)); return to_app(p->get_arg(idx)); } + proof * mk_true_proof(); + proof * mk_asserted(expr * f); + proof * mk_goal(expr * f); + proof * mk_modus_ponens(proof * p1, proof * p2); + proof * mk_reflexivity(expr * e); + proof * mk_oeq_reflexivity(expr * e); + proof * mk_symmetry(proof * p); + proof * mk_transitivity(proof * p1, proof * p2); + proof * mk_transitivity(proof * p1, proof * p2, proof * p3); + proof * mk_transitivity(proof * p1, proof * p2, proof * p3, proof * p4); + proof * mk_transitivity(unsigned num_proofs, proof * const * proofs); + proof * mk_transitivity(unsigned num_proofs, proof * const * proofs, expr * n1, expr * n2); + proof * mk_monotonicity(func_decl * R, app * f1, app * f2, unsigned num_proofs, proof * const * proofs); + proof * mk_congruence(app * f1, app * f2, unsigned num_proofs, proof * const * proofs); + proof * mk_oeq_congruence(app * f1, app * f2, unsigned num_proofs, proof * const * proofs); + proof * mk_commutativity(app * f); + proof * mk_iff_true(proof * pr); + proof * mk_iff_false(proof * pr); + proof * mk_quant_intro(quantifier * q1, quantifier * q2, proof * p); + proof * mk_oeq_quant_intro(quantifier * q1, quantifier * q2, proof * p); + proof * mk_distributivity(expr * s, expr * r); + proof * mk_rewrite(expr * s, expr * t); + proof * mk_oeq_rewrite(expr * s, expr * t); + proof * mk_rewrite_star(expr * s, expr * t, unsigned num_proofs, proof * const * proofs); + proof * mk_pull_quant(expr * e, quantifier * q); + proof * mk_pull_quant_star(expr * e, quantifier * q); + proof * mk_push_quant(quantifier * q, expr * e); + proof * mk_elim_unused_vars(quantifier * q, expr * r); + proof * mk_der(quantifier * q, expr * r); + proof * mk_quant_inst(expr * not_q_or_i, unsigned num_bind, expr* const* binding); + + proof * mk_def_axiom(expr * ax); + proof * mk_unit_resolution(unsigned num_proofs, proof * const * proofs); + proof * mk_unit_resolution(unsigned num_proofs, proof * const * proofs, expr * new_fact); + proof * mk_hypothesis(expr * h); + proof * mk_lemma(proof * p, expr * lemma); + + proof * mk_def_intro(expr * new_def); + proof * mk_apply_defs(expr * n, expr * def, unsigned num_proofs, proof * const * proofs); + proof * mk_apply_def(expr * n, expr * def, proof * p) { return mk_apply_defs(n, def, 1, &p); } + proof * mk_iff_oeq(proof * parent); + + proof * mk_nnf_pos(expr * s, expr * t, unsigned num_proofs, proof * const * proofs); + proof * mk_nnf_neg(expr * s, expr * t, unsigned num_proofs, proof * const * proofs); + proof * mk_nnf_star(expr * s, expr * t, unsigned num_proofs, proof * const * proofs); + proof * mk_skolemization(expr * q, expr * e); + proof * mk_cnf_star(expr * s, expr * t, unsigned num_proofs, proof * const * proofs); + + proof * mk_and_elim(proof * p, unsigned i); + proof * mk_not_or_elim(proof * p, unsigned i); + + proof * mk_th_lemma(family_id tid, + expr * fact, unsigned num_proofs, proof * const * proofs, + unsigned num_params = 0, parameter const* params = 0); + +protected: + bool check_nnf_proof_parents(unsigned num_proofs, proof * const * proofs) const; + +private: + void dec_ref(ptr_buffer & worklist, ast * n) { + n->dec_ref(); + if (n->get_ref_count() == 0) { + worklist.push_back(n); + } + } + + template + void dec_array_ref(ptr_buffer & worklist, unsigned sz, T * const * a) { + for(unsigned i = 0; i < sz; i++) { + dec_ref(worklist, a[i]); + } + } +}; + +typedef ast_manager::expr_array expr_array; +typedef ast_manager::expr_dependency expr_dependency; +typedef ast_manager::expr_dependency_array expr_dependency_array; +typedef obj_ref expr_dependency_ref; +typedef ref_vector expr_dependency_ref_vector; +typedef ref_buffer expr_dependency_ref_buffer; + +// ----------------------------------- +// +// More Auxiliary Functions +// +// ----------------------------------- + +inline bool is_predicate(ast_manager const & m, func_decl const * d) { + return m.is_bool(d->get_range()); +} + +struct ast_lt_proc { + bool operator()(ast const * n1, ast const * n2) const { + return n1->get_id() < n2->get_id(); + } +}; + +// ----------------------------------- +// +// ast_ref (smart pointer) +// +// ----------------------------------- + +typedef obj_ref ast_ref; +typedef obj_ref expr_ref; +typedef obj_ref sort_ref; +typedef obj_ref func_decl_ref; +typedef obj_ref quantifier_ref; +typedef obj_ref app_ref; +typedef obj_ref var_ref; +typedef app_ref proof_ref; + +// ----------------------------------- +// +// ast_vector (smart pointer vector) +// +// ----------------------------------- + +typedef ref_vector ast_ref_vector; +typedef ref_vector decl_ref_vector; +typedef ref_vector sort_ref_vector; +typedef ref_vector func_decl_ref_vector; +typedef ref_vector expr_ref_vector; +typedef ref_vector app_ref_vector; +typedef ref_vector var_ref_vector; +typedef ref_vector quantifier_ref_vector; +typedef app_ref_vector proof_ref_vector; + +// ----------------------------------- +// +// ast_buffer +// +// ----------------------------------- + +typedef ref_buffer ast_ref_buffer; +typedef ref_buffer expr_ref_buffer; +typedef ref_buffer sort_ref_buffer; +typedef ref_buffer app_ref_buffer; +typedef app_ref_buffer proof_ref_buffer; + +// ----------------------------------- +// +// expr_mark +// +// ----------------------------------- + +typedef obj_mark expr_mark; + +class expr_sparse_mark { + obj_hashtable m_marked; +public: + expr_sparse_mark() {} + bool is_marked(expr * n) const { return m_marked.contains(n); } + void mark(expr * n) { m_marked.insert(n); } + void mark(expr * n, bool flag) { if (flag) m_marked.insert(n); else m_marked.erase(n); } + void reset() { m_marked.reset(); } +}; + +template +class ast_fast_mark { + ptr_buffer m_to_unmark; +public: + ast_fast_mark() {} + ~ast_fast_mark() { + reset(); + } + bool is_marked(ast * n) { return IDX == 1 ? AST_IS_MARKED1(n, this) : AST_IS_MARKED2(n, this); } + void reset_mark(ast * n) { + if (IDX == 1) { + AST_RESET_MARK1(n, this); + } + else { + AST_RESET_MARK2(n, this); + } + } + void mark(ast * n) { + if (IDX == 1) { + if (AST_IS_MARKED1(n, this)) + return; + AST_MARK1(n, true, this); + } + else { + if (AST_IS_MARKED2(n, this)) + return; + AST_MARK2(n, true, this); + } + m_to_unmark.push_back(n); + } + + void reset() { + ptr_buffer::iterator it = m_to_unmark.begin(); + ptr_buffer::iterator end = m_to_unmark.end(); + for (; it != end; ++it) { + reset_mark(*it); + } + m_to_unmark.reset(); + } + + void mark(ast * n, bool flag) { if (flag) mark(n); else reset_mark(n); } + + unsigned get_level() { + return m_to_unmark.size(); + } + + void set_level(unsigned new_size) { + SASSERT(new_size <= m_to_unmark.size()); + while (new_size < m_to_unmark.size()) { + reset_mark(m_to_unmark.back()); + m_to_unmark.pop_back(); + } + } +}; + +typedef ast_fast_mark<1> ast_fast_mark1; +typedef ast_fast_mark<2> ast_fast_mark2; +typedef ast_fast_mark1 expr_fast_mark1; +typedef ast_fast_mark2 expr_fast_mark2; + +/** + Similar to ast_fast_mark, but increases reference counter. +*/ +template +class ast_ref_fast_mark { + ast_ref_buffer m_to_unmark; +public: + ast_ref_fast_mark(ast_manager & m):m_to_unmark(m) {} + ~ast_ref_fast_mark() { + reset(); + } + bool is_marked(ast * n) { return IDX == 1 ? AST_IS_MARKED1(n, this) : AST_IS_MARKED2(n, this); } + + // It will not decrease the reference counter + void reset_mark(ast * n) { + if (IDX == 1) { + AST_RESET_MARK1(n, this); + } + else { + AST_RESET_MARK2(n, this); + } + } + + void mark(ast * n) { + if (IDX == 1) { + if (AST_IS_MARKED1(n, this)) + return; + AST_MARK1(n, true, this); + } + else { + if (AST_IS_MARKED2(n, this)) + return; + AST_MARK2(n, true, this); + } + m_to_unmark.push_back(n); + } + + void reset() { + ast * const * it = m_to_unmark.c_ptr(); + ast * const * end = it + m_to_unmark.size(); + for (; it != end; ++it) { + reset_mark(*it); + } + m_to_unmark.reset(); + } + + void mark(ast * n, bool flag) { if (flag) mark(n); else reset_mark(n); } +}; + +typedef ast_ref_fast_mark<1> ast_ref_fast_mark1; +typedef ast_ref_fast_mark<2> ast_ref_fast_mark2; +typedef ast_ref_fast_mark1 expr_ref_fast_mark1; +typedef ast_ref_fast_mark2 expr_ref_fast_mark2; + +// ----------------------------------- +// +// ast_mark +// +// ----------------------------------- + +/** + \brief A mapping from AST to Boolean + + \warning This map does not cleanup the entry associated with a node N, + when N is deleted. +*/ +class ast_mark { + struct decl2uint { unsigned operator()(decl const & d) const { return d.get_decl_id(); } }; + obj_mark m_expr_marks; + obj_mark m_decl_marks; +public: + virtual ~ast_mark() {} + bool is_marked(ast * n) const; + virtual void mark(ast * n, bool flag); + virtual void reset(); +}; + +// ----------------------------------- +// +// scoped_mark +// +// ----------------------------------- + +/** + \brief Class for scoped-based marking of asts. + This class is safe with respect to life-times of asts. +*/ +class scoped_mark : public ast_mark { + ast_ref_vector m_stack; + unsigned_vector m_lim; +public: + scoped_mark(ast_manager& m): m_stack(m) {} + virtual ~scoped_mark() {} + virtual void mark(ast * n, bool flag); + virtual void reset(); + void mark(ast * n); + void push_scope(); + void pop_scope(); + void pop_scope(unsigned num_scopes); +}; + +// ------------------------------------- +// +// inc_ref & dec_ref functors +// +// ------------------------------------- + +template +class dec_ref_proc { + ast_manager & m_manager; +public: + dec_ref_proc(ast_manager & m):m_manager(m) {} + void operator()(AST * n) { m_manager.dec_ref(n); } +}; + + +template +class inc_ref_proc { + ast_manager & m_manager; +public: + inc_ref_proc(ast_manager & m):m_manager(m) {} + void operator()(AST * n) { m_manager.inc_ref(n); } +}; + +#endif /* _AST_H_ */ + + diff --git a/lib/ast_dag_pp.cpp b/lib/ast_dag_pp.cpp new file mode 100644 index 000000000..893ef09b6 --- /dev/null +++ b/lib/ast_dag_pp.cpp @@ -0,0 +1,353 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + ast_dag_pp.cpp + +Abstract: + + AST low level pretty printer. + +Author: + + Leonardo de Moura (leonardo) 2006-10-19. + Nikolaj Bjorner (nbjorner) 2007-07-17 + +Revision History: + +--*/ + +#include +#include"for_each_ast.h" +#include"arith_decl_plugin.h" +#include"bv_decl_plugin.h" +#include"ast_pp.h" + +class dag_printer { + ast_manager& m_manager; + std::ostream & m_out; + ast_mark& m_mark; + bool m_initialized; + svector m_names; + family_id m_basic_fid; + family_id m_bv_fid; + family_id m_arith_fid; + family_id m_array_fid; + arith_util m_arith; + bv_util m_bv; + bool m_enable_shortcut; + + void process_ast(ast* a) { + for_each_ast(*this, m_mark, a); + } + + void process_info(decl_info* info) { + if (!info) { + return; + } + unsigned num_params = info->get_num_parameters(); + for (unsigned i = 0; i < num_params; ++i) { + parameter const& p = info->get_parameter(i); + + if (p.is_ast() && !m_mark.is_marked(p.get_ast())) { + process_ast(p.get_ast()); + } + } + } + + template + void display_children(unsigned num_children, T * const * children) { + for (unsigned i = 0; i < num_children; i++) { + display_node_id(children[i]); + } + } + + void display_node_id(ast* n) { + unsigned id = n->get_id(); + switch(n->get_kind()) { + case AST_FUNC_DECL: + case AST_SORT: + m_out << "$d" << (id - (1 << 31)) << " "; + break; + default: + m_out << "$" << id << " "; + break; + } + } + + void display_parameter(parameter const& p) + { + if (p.is_int()) { + m_out << p.get_int() << " "; + } + else if (p.is_ast()) { + SASSERT(p.is_ast()); + display_node_id(p.get_ast()); + } + else if (p.is_rational()) { + m_out << p.get_rational() << " "; + } + else if (p.is_symbol()) { + display_symbol(p.get_symbol()); + } + else { + UNREACHABLE(); + } + } + + // Display: + // App name [ parameters] arguments + // + void display_builtin(app* n) { + func_decl* d = n->get_decl(); + unsigned num_params = d->get_num_parameters(); + + m_out << "App "; + display_node_id(n); + display_symbol(d->get_name()); + if (num_params > 0) { + m_out << "[ "; + for (unsigned i = 0; i < num_params; ++i) { + display_parameter(d->get_parameter(i)); + } + m_out << "] "; + } + display_children(n->get_num_args(), n->get_args()); + m_out << "\n"; + } + + void display_info(func_decl_info* info) { + if (!info) { + return; + } + m_out << "BUILTIN " << get_family_name(info->get_family_id()) << " " << info->get_decl_kind() << " "; + + if (info->is_associative()) { + m_out << ":assoc "; + } + if (info->is_commutative()) { + m_out << ":comm "; + } + if (info->is_injective()) { + m_out << ":inj "; + } + for (unsigned i = 0; i < info->get_num_parameters(); ++i) { + display_parameter(info->get_parameter(i)); + } + } + + void display_info(sort_info* info) { + if (!info) { + return; + } + m_out << "BUILTIN " << get_family_name(info->get_family_id()) << " " << info->get_decl_kind() << " "; + // TODO: remove this if... it doesn't make sense... + if (!info->is_infinite() && !info->is_very_big()) { + m_out << "Size " << info->get_num_elements().size() << " "; + } + for (unsigned i = 0; i < info->get_num_parameters(); ++i) { + display_parameter(info->get_parameter(i)); + } + } + + symbol get_family_name(family_id id) { + if (id == null_family_id) { + return symbol("null"); + } + if (!m_initialized) { + svector names; + svector range; + m_manager.get_dom(names); + m_manager.get_range(range); + m_names.resize(range.size()); + for (unsigned i = 0; i < range.size(); ++i) { + SASSERT(range[i] < static_cast(range.size())); + m_names[range[i]] = names[i]; + } + m_initialized = true; + } + SASSERT(id < static_cast(m_names.size())); + return m_names[id]; + } + + bool has_special_char(char const* s) { + while (s && *s) { + if (*s == ' ') { + return true; + } + ++s; + } + return false; + } + + void display_symbol(symbol const& s) { + if (s.is_numerical()) { + m_out << s << " "; + } + else if (!(s.bare_str()[0])) { + m_out << "\"null\" "; + } + else if (!has_special_char(s.bare_str())) { + m_out << s << " "; + } + else { + char const* r = s.bare_str(); + m_out << "\""; + while (*r) { + if (*r == ' ' || *r == '\n' || + *r == '\t' || *r == '\r') { + m_out << "\\" << ((unsigned)(*r)); + } + else { + m_out << *r; + } + ++r; + } + m_out << "\" "; + } + } + +public: + + dag_printer(ast_manager& mgr, std::ostream & out, ast_mark& mark): + m_manager(mgr), + m_out(out), + m_mark(mark), + m_initialized(false), + m_basic_fid(mgr.get_basic_family_id()), + m_bv_fid(mgr.get_family_id("bv")), + m_arith_fid(mgr.get_family_id("arith")), + m_array_fid(mgr.get_family_id("array")), + m_arith(mgr), + m_bv(mgr), + m_enable_shortcut(true) + { + } + + void operator()(sort * n) { + process_info(n->get_info()); + m_out << "Ty "; + display_node_id(n); + display_symbol(n->get_name()); + display_info(n->get_info()); + m_out << "\n"; + } + + void pp_num(app* n, rational const& r) { + m_out << "Num "; + display_node_id(n); + m_out << r << " "; + display_node_id(m_manager.get_sort(n)); + m_out << "\n"; + } + + void operator()(var * n) { + process_ast(n->get_sort()); + m_out << "Var "; + display_node_id(n); + m_out << n->get_idx() << " "; + display_node_id(n->get_sort()); + m_out << "\n"; + } + + void operator()(func_decl * n) { + + process_info(n->get_info()); + + family_id fid = n->get_family_id(); + if (m_arith_fid == fid && + n->get_info()->get_decl_kind() == OP_NUM) { + return; + } + + if (m_bv_fid == fid && + n->get_info()->get_decl_kind() == OP_BV_NUM) { + return; + } + + m_out << "Dec "; + display_node_id(n); + display_symbol(n->get_name()); + unsigned dom_size = n->get_arity(); + for (unsigned i = 0; i < dom_size; ++i) { + display_node_id(n->get_domain(i)); + } + display_node_id(n->get_range()); + display_info(n->get_info()); + m_out << "\n"; + } + + void operator()(app * n) { + process_ast(n->get_decl()); + family_id fid = n->get_family_id(); + unsigned bv_size; + rational val; + if (m_arith.is_numeral(n, val)) { + pp_num(n, val); + } + else if (m_bv.is_numeral(n, val, bv_size)) { + pp_num(n, val); + } + else if (m_enable_shortcut && + fid != null_family_id && + (fid == m_basic_fid || fid == m_bv_fid || fid == m_array_fid || fid == m_arith_fid)) { + display_builtin(n); + } + else if (n->get_num_args() == 0 && fid == null_family_id) { + func_decl* d = n->get_decl(); + m_out << "Const "; + display_node_id(n); + display_symbol(d->get_name()); + display_node_id(d->get_range()); + m_out << "\n"; + } + else { + m_out << "Fun "; + display_node_id(n); + display_node_id(n->get_decl()); + display_children(n->get_num_args(), n->get_args()); + m_out << "\n"; + } + } + + void operator()(quantifier * n) { + m_out << "Qua "; + display_node_id(n); + m_out << (n->is_forall() ? "FORALL" : "EXISTS") << " "; + m_out << n->get_weight() << " "; + if (symbol::null != n->get_skid()) { + m_out << "\"" << n->get_skid() << "\" "; + } + else { + m_out << "\"null\" "; + } + if (symbol::null != n->get_qid()) { + m_out << "\"" << n->get_qid() << "\" "; + } + else { + m_out << "\"null\" "; + } + unsigned num_decls = n->get_num_decls(); + m_out << num_decls << " "; + for (unsigned i = 0; i < num_decls; i++) { + m_out << n->get_decl_name(i) << " "; + display_node_id(n->get_decl_sort(i)); + } + m_out << n->get_num_patterns() << " "; + display_children(n->get_num_patterns(), n->get_patterns()); + display_node_id(n->get_expr()); + m_out << "\n"; + } +}; + +void ast_dag_pp(std::ostream & out, ast_manager& mgr, ast_mark& mark, ast * n) { + dag_printer p(mgr, out, mark); + for_each_ast(p, mark, n, true); +} + +void ast_dag_pp(std::ostream & out, ast_manager& mgr, ast * n) { + ast_mark mark; + ast_dag_pp(out, mgr, mark, n); +} + diff --git a/lib/ast_dag_pp.h b/lib/ast_dag_pp.h new file mode 100644 index 000000000..fa933e22f --- /dev/null +++ b/lib/ast_dag_pp.h @@ -0,0 +1,31 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + ast_dag_pp.h + +Abstract: + + AST low level pretty printer. + +Author: + + Leonardo de Moura (leonardo) 2006-10-19. + +Revision History: + +--*/ +#ifndef _AST_DAG_PP_H_ +#define _AST_DAG_PP_H_ + +#include + +class ast; + +void ast_dag_pp(std::ostream & out, ast_manager& mgr, ast * n); + +void ast_dag_pp(std::ostream & out, ast_manager& mgr, ast_mark& mark, ast * n); + +#endif /* _AST_DAG_PP_H_ */ + diff --git a/lib/ast_list.h b/lib/ast_list.h new file mode 100644 index 000000000..c02b2d66d --- /dev/null +++ b/lib/ast_list.h @@ -0,0 +1,160 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + ast_list.h + +Abstract: + + Quick hack to have lists of ASTs. + The lists have hash-consing. + This is a substitute for the old expr_lists implemented on top of the ASTs. + The new lists live in a different manager and do not affect the ast_manager. + +Author: + + Leonardo de Moura (leonardo) 2011-01-06. + +Revision History: + +--*/ +#ifndef _AST_LIST_H_ +#define _AST_LIST_H_ + +#include"ast.h" + +template +class ast_list_manager_tpl; + +template +class ast_list_tpl { +public: + typedef ast_list_tpl list; +private: + unsigned m_id; + unsigned m_is_nil:1; + unsigned m_ref_count:31; + AST * m_head; + list * m_tail; + + ast_list_tpl(): + m_is_nil(true), + m_ref_count(0), + m_head(0), + m_tail(0) { + } + + ast_list_tpl(AST * h, list * t): + m_is_nil(false), + m_ref_count(0), + m_head(h), + m_tail(t) { + } + + friend class ast_list_manager_tpl; + + struct hash_proc; + friend struct hash_proc; + + struct hash_proc { + unsigned operator()(ast_list_tpl * l) const { + unsigned h1 = l->m_head ? l->m_head->get_id() : 5; + unsigned h2 = l->m_tail ? l->m_tail->get_id() : 7; + return hash_u_u(h1, h2); + } + }; + + struct eq_proc; + friend struct eq_proc; + + struct eq_proc { + bool operator()(ast_list_tpl * l1, ast_list_tpl * l2) const { + return l1->m_head == l2->m_head && l1->m_tail == l2->m_tail; + } + }; + + typedef ptr_hashtable table; // for hash consing + +public: + unsigned get_id() const { return m_id; } + unsigned get_ref_count() const { return m_ref_count; } + unsigned hash() const { return m_id; } + + friend inline bool is_nil(list const * l) { return l->m_is_nil == true; } + friend inline bool is_cons(list const * l) { return !is_nil(l); } + friend inline AST * head(list const * l) { SASSERT(is_cons(l)); return l->m_head; } + friend inline list * tail(list const * l) { SASSERT(is_cons(l)); return l->m_tail; } +}; + +template +class ast_list_manager_tpl { +public: + typedef ast_list_tpl list; + typedef obj_hashtable list_set; +private: + typedef typename list::table table; + ast_manager & m_manager; + small_object_allocator m_allocator; + table m_table; + id_gen m_id_gen; + list m_nil; + +public: + ast_list_manager_tpl(ast_manager & m): + m_manager(m), + m_nil() { + m_nil.m_id = m_id_gen.mk(); + m_nil.m_ref_count = 1; + } + + void inc_ref(list * l) { + if (l != 0) { + l->m_ref_count++; + } + } + + void dec_ref(list * l) { + while (l != 0) { + SASSERT(l->m_ref_count > 0); + l->m_ref_count--; + if (l->m_ref_count > 0) + return; + SASSERT(l != &m_nil); + m_table.erase(l); + m_manager.dec_ref(l->m_head); + m_id_gen.recycle(l->m_id); + list * old_l = l; + l = l->m_tail; + m_allocator.deallocate(sizeof(list), old_l); + } + } + + list * mk_nil() { return &m_nil; } + + list * mk_cons(AST * h, list * l) { + list tmp(h, l); + list * r = 0; + if (m_table.find(&tmp, r)) + return r; + r = new (m_allocator.allocate(sizeof(list))) list(h, l); + m_manager.inc_ref(h); + inc_ref(l); + r->m_id = m_id_gen.mk(); + m_table.insert(r); + return r; + } +}; + +typedef ast_list_tpl expr_list; +typedef ast_list_manager_tpl expr_list_manager; + +typedef ast_list_tpl quantifier_list; +typedef ast_list_manager_tpl quantifier_list_manager; + +typedef obj_ref expr_list_ref; +typedef obj_ref quantifier_list_ref; +typedef ref_vector expr_list_ref_vector; +typedef ref_vector quantifier_list_ref_vector; + +#endif diff --git a/lib/ast_ll_pp.cpp b/lib/ast_ll_pp.cpp new file mode 100644 index 000000000..23ae673c9 --- /dev/null +++ b/lib/ast_ll_pp.cpp @@ -0,0 +1,315 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + ast_ll_pp.cpp + +Abstract: + + AST low level pretty printer. + +Author: + + Leonardo de Moura (leonardo) 2006-10-19. + +Revision History: + +--*/ + +#include +#include"for_each_ast.h" +#include"arith_decl_plugin.h" + +// #define AST_LL_PP_SHOW_FAMILY_NAME + +class ll_printer { + std::ostream & m_out; + ast_manager & m_manager; + ast * m_root; + bool m_only_exprs; + bool m_compact; + arith_util m_autil; + + void display_def_header(ast * n) { + if (n != m_root) { + m_out << "#" << n->get_id() << " := "; + } + } + + void display_child_ref(ast * n) { + m_out << "#" << n->get_id(); + } + + void display_name(func_decl * decl) { + symbol n = decl->get_name(); + if (decl->is_skolem() && n.is_numerical()) + m_out << "z3.sk." << n.get_num(); + else + m_out << n; + } + + bool process_numeral(expr * n) { + rational val; + bool is_int; + if (m_autil.is_numeral(n, val, is_int)) { + m_out << val << "::" << (is_int ? "Int" : "Real"); + return true; + } + return false; + } + + void display_sort(sort * s) { + m_out << s->get_name(); + display_params(s); + } + + void display_child(ast * n) { + switch (n->get_kind()) { + case AST_SORT: + display_sort(to_sort(n)); + break; + case AST_APP: + if (process_numeral(to_expr(n))) { + // skip + } + else if (to_app(n)->get_num_args() == 0) { + display_name(to_app(n)->get_decl()); + display_params(to_app(n)->get_decl()); + } + else { + display_child_ref(n); + } + break; + default: + display_child_ref(n); + } + } + + template + void display_children(unsigned num_children, T * const * children) { + for (unsigned i = 0; i < num_children; i++) { + if (i > 0) { + m_out << " "; + } + display_child(children[i]); + } + } + + void display_params(decl * d) { + unsigned n = d->get_num_parameters(); + parameter const* p = d->get_parameters(); + + if (n > 0 && !d->private_parameters()) { + m_out << "["; + for (unsigned i = 0; i < n; i ++) { + if (p[i].is_ast()) { + display_child(p[i].get_ast()); + } + else { + m_out << p[i]; + } + m_out << (i < n-1 ? ":" : ""); + } + m_out << "]"; + } + } + +public: + + ll_printer(std::ostream & out, ast_manager & m, ast * n, bool only_exprs, bool compact): + m_out(out), + m_manager(m), + m_root(n), + m_only_exprs(only_exprs), + m_compact(compact), + m_autil(m) { + } + + void operator()(sort * n) { + } + + void operator()(func_decl * n) { + if (m_only_exprs) { + return; + } + if (n->get_family_id() != null_family_id) { + return; + } + m_out << "decl "; + display_name(n); + m_out << " :: "; + if (n->get_arity() == 0) { + display_child(n->get_range()); + } + else { + m_out << "(-> "; + display_children(n->get_arity(), n->get_domain()); + m_out << " "; + display_child(n->get_range()); + m_out << ")"; + display_params(n); + if (n->is_associative()) { + m_out << " :assoc"; + } + if (n->is_commutative()) { + m_out << " :comm"; + } + if (n->is_injective()) { + m_out << " :inj"; + } + } + m_out << "\n"; + } + + void operator()(var * n) { + display_def_header(n); + m_out << "(:var " << to_var(n)->get_idx() << " "; + display_sort(n->get_sort()); + m_out << ")\n"; + } + + void operator()(app * n) { + if (m_autil.is_numeral(n)) { + if (!m_compact) + display_def_header(n); + if (n == m_root || !m_compact) { + process_numeral(n); + m_out << "\n"; + } + } + else if (m_manager.is_proof(n)) { + display_def_header(n); + m_out << "[" << n->get_decl()->get_name(); + unsigned num_params = n->get_decl()->get_num_parameters(); + for (unsigned i = 0; i < num_params; ++i) { + m_out << " "; + m_out << n->get_decl()->get_parameter(i); + } + unsigned num_parents = m_manager.get_num_parents(n); + for (unsigned i = 0; i < num_parents; i++) { + m_out << " "; + display_child(m_manager.get_parent(n, i)); + } + m_out << "]: "; + if (m_manager.has_fact(n)) { + // display(m_manager.get_fact(n), 6); + display_child(m_manager.get_fact(n)); + } + else + m_out << "*"; + m_out << "\n"; + } + else if (m_compact && n->get_num_args() == 0) { + if (n == m_root) { + display_child(n); + m_out << "\n"; + } + } + else { + display_def_header(n); + if (n->get_num_args() > 0) + m_out << "("; + display_name(n->get_decl()); + display_params(n->get_decl()); + if (n->get_num_args() > 0) { + m_out << " "; + display_children(n->get_num_args(), n->get_args()); + m_out << ")"; + } +#ifdef AST_LL_PP_SHOW_FAMILY_NAME + if (to_app(n)->get_family_id() != null_family_id) { + m_out << " family: " << m_manager.get_family_name(to_app(n)->get_family_id()); + } +#endif + m_out << "\n"; + } + } + + void operator()(quantifier * n) { + display_def_header(n); + m_out << "(" << (n->is_forall() ? "forall" : "exists") << " "; + unsigned num_decls = n->get_num_decls(); + m_out << "(vars "; + for (unsigned i = 0; i < num_decls; i++) { + if (i > 0) { + m_out << " "; + } + m_out << "(" << n->get_decl_name(i) << " "; + display_sort(n->get_decl_sort(i)); + m_out << ")"; + } + m_out << ") "; + if (n->get_num_patterns() > 0) { + m_out << "(:pat "; + display_children(n->get_num_patterns(), n->get_patterns()); + m_out << ") "; + } + if (n->get_num_no_patterns() > 0) { + m_out << "(:nopat "; + display_children(n->get_num_no_patterns(), n->get_no_patterns()); + m_out << ") "; + } + display_child(n->get_expr()); + m_out << ")\n"; + } + + void display(expr * n, unsigned depth) { + if (is_var(n)) { + m_out << "(:var " << to_var(n)->get_idx() << ")"; + return; + } + + if (!is_app(n) || depth == 0 || to_app(n)->get_num_args() == 0) { + display_child(n); + return; + } + if (to_app(n)->get_num_args() > depth && to_app(n)->get_num_args() > 16) { + display_child(n); + return; + } + unsigned num_args = to_app(n)->get_num_args(); + if (num_args > 0) + m_out << "("; + display_name(to_app(n)->get_decl()); + display_params(to_app(n)->get_decl()); + for (unsigned i = 0; i < num_args; i++) { + m_out << " "; + display(to_app(n)->get_arg(i), depth-1); + } + if (num_args > 0) + m_out << ")"; + } + + void display_bounded(ast * n, unsigned depth) { + if (is_app(n)) { + display(to_expr(n), depth); + } + else if (is_var(n)) { + m_out << "(:var " << to_var(n)->get_idx() << ")"; + } + else { + m_out << "#" << n->get_id(); + } + } +}; + +void ast_ll_pp(std::ostream & out, ast_manager & m, ast * n, bool only_exprs, bool compact) { + ll_printer p(out, m, n, only_exprs, compact); + for_each_ast(p, n, true); +} + +void ast_ll_pp(std::ostream & out, ast_manager & m, ast * n, ast_mark & visited, bool only_exprs, bool compact) { + ll_printer p(out, m, n, only_exprs, compact); + for_each_ast(p, visited, n, true); +} + +void ast_def_ll_pp(std::ostream & out, ast_manager & m, ast * n, ast_mark & visited, bool only_exprs, bool compact) { + ll_printer p(out, m, 0, only_exprs, compact); + for_each_ast(p, visited, n, true); +} + +void ast_ll_bounded_pp(std::ostream & out, ast_manager & m, ast * n, unsigned depth) { + ll_printer p(out, m, 0, false, true); + p.display_bounded(n, depth); +} diff --git a/lib/ast_ll_pp.h b/lib/ast_ll_pp.h new file mode 100644 index 000000000..2cfe83179 --- /dev/null +++ b/lib/ast_ll_pp.h @@ -0,0 +1,57 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + ast_ll_pp.h + +Abstract: + + AST low level pretty printer. + +Author: + + Leonardo de Moura (leonardo) 2006-10-19. + +Revision History: + +--*/ +#ifndef _AST_LL_PP_H_ +#define _AST_LL_PP_H_ + +#include"ast.h" +#include + +void ast_ll_pp(std::ostream & out, ast_manager & m, ast * n, bool only_exprs=true, bool compact=true); +void ast_ll_pp(std::ostream & out, ast_manager & m, ast * n, ast_mark & visited, bool only_exprs=true, bool compact=true); +void ast_def_ll_pp(std::ostream & out, ast_manager & m, ast * n, ast_mark & visited, bool only_exprs=true, bool compact=true); +void ast_ll_bounded_pp(std::ostream & out, ast_manager & m, ast * n, unsigned depth); + +struct mk_ll_pp { + ast * m_ast; + ast_manager & m_manager; + bool m_only_exprs; + bool m_compact; + mk_ll_pp(ast * a, ast_manager & m, bool only_exprs=true, bool compact=true): m_ast(a), m_manager(m), m_only_exprs(only_exprs), m_compact(compact) {} +}; + +inline std::ostream & operator<<(std::ostream & out, mk_ll_pp const & p) { + ast_ll_pp(out, p.m_manager, p.m_ast, p.m_only_exprs, p.m_compact); + return out; +} + +struct mk_bounded_pp { + ast * m_ast; + ast_manager & m_manager; + unsigned m_depth; + mk_bounded_pp(ast * a, ast_manager & m, unsigned depth=3): m_ast(a), m_manager(m), m_depth(depth) {} +}; + +inline std::ostream & operator<<(std::ostream & out, mk_bounded_pp const & p) { + ast_ll_bounded_pp(out, p.m_manager, p.m_ast, p.m_depth); + return out; +} + + +#endif /* _AST_LL_PP_H_ */ + diff --git a/lib/ast_lt.cpp b/lib/ast_lt.cpp new file mode 100644 index 000000000..1bf153f4f --- /dev/null +++ b/lib/ast_lt.cpp @@ -0,0 +1,144 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + ast_lt.cpp + +Abstract: + + Total order on ASTs that does not depend on the internal ids. + +Author: + + Leonardo de Moura (leonardo) 2011-04-08 + +Revision History: + +--*/ +#include"ast.h" + +#define check_symbol(S1,S2) if (S1 != S2) return lt(S1,S2) +#define check_value(V1,V2) if (V1 != V2) return V1 < V2 +#define check_bool(B1,B2) if (B1 != B2) return !B1 && B2 +#define check_ptr(P1,P2) if (!P1 && P2) return true; if (P1 && !P2) return false +#define check_ast(T1,T2) if (T1 != T2) { n1 = T1; n2 = T2; goto start; } + +#define check_parameter(p1, p2) { \ + check_value(p1.get_kind(), p2.get_kind()); \ + switch (p1.get_kind()) { \ + case parameter::PARAM_INT: \ + check_value(p1.get_int(), p2.get_int()); \ + break; \ + case parameter::PARAM_AST: \ + check_ast(p1.get_ast(), p2.get_ast()); \ + break; \ + case parameter::PARAM_SYMBOL: \ + check_symbol(p1.get_symbol(), p2.get_symbol()); \ + break; \ + case parameter::PARAM_RATIONAL: \ + check_value(p1.get_rational(), p2.get_rational()); \ + break; \ + case parameter::PARAM_DOUBLE: \ + check_value(p1.get_double(), p2.get_double()); \ + break; \ + case parameter::PARAM_EXTERNAL: \ + check_value(p1.get_ext_id(), p2.get_ext_id()); \ + break; \ + default: \ + UNREACHABLE(); \ + break; \ + } \ +} + +bool lt(ast * n1, ast * n2) { + unsigned num; + start: + if (n1 == n2) + return false; + check_value(n1->get_kind(), n2->get_kind()); + switch(n1->get_kind()) { + case AST_SORT: + check_symbol(to_sort(n1)->get_name(), to_sort(n2)->get_name()); + check_value(to_sort(n1)->get_num_parameters(), to_sort(n2)->get_num_parameters()); + num = to_sort(n1)->get_num_parameters(); + SASSERT(num > 0); + for (unsigned i = 0; i < num; i++) { + parameter p1 = to_sort(n1)->get_parameter(i); + parameter p2 = to_sort(n2)->get_parameter(i); + check_parameter(p1, p2); + } + UNREACHABLE(); + return false; + case AST_FUNC_DECL: + check_symbol(to_func_decl(n1)->get_name(), to_func_decl(n2)->get_name()); + check_value(to_func_decl(n1)->get_arity(), to_func_decl(n2)->get_arity()); + check_value(to_func_decl(n1)->get_num_parameters(), to_func_decl(n2)->get_num_parameters()); + num = to_func_decl(n1)->get_num_parameters(); + for (unsigned i = 0; i < num; i++) { + parameter p1 = to_func_decl(n1)->get_parameter(i); + parameter p2 = to_func_decl(n2)->get_parameter(i); + check_parameter(p1, p2); + } + num = to_func_decl(n1)->get_arity(); + for (unsigned i = 0; i < num; i++) { + ast * d1 = to_func_decl(n1)->get_domain(i); + ast * d2 = to_func_decl(n2)->get_domain(i); + check_ast(d1, d2); + } + n1 = to_func_decl(n1)->get_range(); + n2 = to_func_decl(n2)->get_range(); + goto start; + case AST_APP: + check_value(to_app(n1)->get_num_args(), to_app(n2)->get_num_args()); + check_value(to_app(n1)->get_depth(), to_app(n2)->get_depth()); + check_ast(to_app(n1)->get_decl(), to_app(n2)->get_decl()); + num = to_app(n1)->get_num_args(); + for (unsigned i = 0; i < num; i++) { + expr * arg1 = to_app(n1)->get_arg(i); + expr * arg2 = to_app(n2)->get_arg(i); + check_ast(arg1, arg2); + } + UNREACHABLE(); + return false; + case AST_QUANTIFIER: + check_bool(to_quantifier(n1)->is_forall(), to_quantifier(n2)->is_forall()); + check_value(to_quantifier(n1)->get_num_decls(), to_quantifier(n2)->get_num_decls()); + check_value(to_quantifier(n1)->get_num_patterns(), to_quantifier(n2)->get_num_patterns()); + check_value(to_quantifier(n1)->get_num_no_patterns(), to_quantifier(n2)->get_num_no_patterns()); + check_value(to_quantifier(n1)->get_weight(), to_quantifier(n2)->get_weight()); + num = to_quantifier(n1)->get_num_decls(); + for (unsigned i = 0; i < num; i++) { + check_symbol(to_quantifier(n1)->get_decl_name(i), to_quantifier(n2)->get_decl_name(i)); + check_ast(to_quantifier(n1)->get_decl_sort(i), to_quantifier(n2)->get_decl_sort(i)); + } + num = to_quantifier(n1)->get_num_patterns(); + for (unsigned i = 0; i < num; i++) { + check_ast(to_quantifier(n1)->get_pattern(i), to_quantifier(n2)->get_pattern(i)); + } + num = to_quantifier(n1)->get_num_no_patterns(); + for (unsigned i = 0; i < num; i++) { + check_ast(to_quantifier(n1)->get_no_pattern(i), to_quantifier(n2)->get_no_pattern(i)); + } + n1 = to_quantifier(n1)->get_expr(); + n2 = to_quantifier(n2)->get_expr(); + goto start; + case AST_VAR: + check_value(to_var(n1)->get_idx(), to_var(n2)->get_idx()); + n1 = to_var(n1)->get_sort(); + n2 = to_var(n2)->get_sort(); + goto start; + default: + UNREACHABLE(); + return false; + } +} + +bool lex_lt(unsigned num, ast * const * n1, ast * const * n2) { + for (unsigned i = 0; i < num; i ++) { + if (n1[i] == n2[i]) + continue; + return lt(n1[i], n2[i]); + } + return false; +} diff --git a/lib/ast_lt.h b/lib/ast_lt.h new file mode 100644 index 000000000..5eb268997 --- /dev/null +++ b/lib/ast_lt.h @@ -0,0 +1,35 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + ast_lt.h + +Abstract: + + Total order on ASTs that does not depend on the internal ids. + +Author: + + Leonardo de Moura (leonardo) 2011-04-08 + +Revision History: + +--*/ +#ifndef _AST_LT_H_ +#define _AST_LT_H_ + +class ast; + +bool lt(ast * n1, ast * n2); + +struct ast_to_lt { + bool operator()(ast * n1, ast * n2) const { return lt(n1, n2); } +}; + +bool lex_lt(unsigned num, ast * const * n1, ast * const * n2); +inline bool lex_lt(unsigned num, expr * const * n1, expr * const * n2) { + return lex_lt(num, reinterpret_cast(n1), reinterpret_cast(n2)); +} + +#endif diff --git a/lib/ast_pp.cpp b/lib/ast_pp.cpp new file mode 100644 index 000000000..842f05408 --- /dev/null +++ b/lib/ast_pp.cpp @@ -0,0 +1,516 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + ast_pp.cpp + +Abstract: + + Expression DAG pretty printer + +Author: + + Leonardo de Moura 2008-01-20. + +Revision History: + +--*/ + +#include"ast_pp.h" +#include"pp.h" +#include"obj_pair_hashtable.h" +#include"ast_ll_pp.h" +#include"arith_decl_plugin.h" +#include"bv_decl_plugin.h" +#include"datatype_decl_plugin.h" +#include"dl_decl_plugin.h" +#include"ast_list.h" +#include + +using namespace format_ns; + +mk_pp::mk_pp(ast * a, ast_manager & m, pp_params const & p, unsigned indent, unsigned num_var_names, char const * const * var_names): + m_ast(a), + m_manager(m), + m_params(p), + m_indent(indent), + m_num_var_names(num_var_names), + m_var_names(var_names) { +} + +mk_pp::mk_pp(ast * a, ast_manager & m, unsigned indent, unsigned num_var_names, char const * const * var_names): + m_ast(a), + m_manager(m), + m_params(get_pp_default_params()), + m_indent(indent), + m_num_var_names(num_var_names), + m_var_names(var_names) { +} + +std::ostream & ast_pp(std::ostream & strm, ast * n, ast_manager & m) { + return ast_pp(strm, n, m, get_pp_default_params()); +} + +struct pp_cache { + typedef obj_pair_map cache; + + ast_manager & m_manager; + cache m_cache; + + pp_cache(ast_manager & m): + m_manager(m) { + } + + ~pp_cache() { + reset(); + } + + bool contains(ast * k1, quantifier_list * k2) const { return m_cache.contains(k1, k2); } + + void get(ast * k1, quantifier_list * k2, format * & r) const { m_cache.find(k1, k2, r); } + + void insert(ast * k1, quantifier_list * k2, format * f) { + SASSERT(!m_cache.contains(k1, k2)); + fm(m_manager).inc_ref(f); + m_cache.insert(k1, k2, f); + } + + void reset() { + cache::iterator it = m_cache.begin(); + cache::iterator end = m_cache.end(); + for (; it != end; ++it) { + format * f = (*it).get_value(); + fm(m_manager).dec_ref(f); + } + m_cache.reset(); + } +}; + +class formatter { + typedef quantifier_list_manager qlist_manager; + typedef quantifier_list_ref qlist_ref; + typedef quantifier_list_ref_vector qlist_ref_vector; + pp_params const & m_params; + ast_manager & m_manager; + qlist_manager m_qlist_manager; + pp_cache m_cache; + typedef std::pair pp_entry; + svector m_todo; + qlist_ref_vector m_qlists; + app_ref m_nil; + arith_util m_autil; + bv_util m_bvutil; + datatype_util m_datatype_util; + datalog::dl_decl_util m_dl_util; + ptr_vector m_datatypes; + app_ref_vector m_format_trail; + ast_mark m_visited_datatypes; + unsigned m_num_var_names; + char const * const * m_var_names; + + struct symbol2format { + ast_manager & m_manager; + symbol2format(ast_manager & m):m_manager(m) {} + format * operator()(symbol const & s) { + std::string str = s.str(); + return mk_string(m_manager, str.c_str()); + } + }; + + format * get_cached(ast * n, quantifier_list * qlist) { + format * f = 0; + if (is_sort(n)) { + qlist = m_qlist_manager.mk_nil(); + } + m_cache.get(n, qlist, f); + SASSERT(f); + return f; + } + + void visit(ast * n, quantifier_list * qlist, bool & visited) { + if (is_sort(n)) { + qlist = m_qlist_manager.mk_nil(); + } + if (!m_cache.contains(n, qlist)) { + m_todo.push_back(pp_entry(n, qlist)); + visited = false; + } + } + + bool visit_children(ast * n, quantifier_list * qlist) { + unsigned j; + bool visited = true; + switch (n->get_kind()) { + case AST_FUNC_DECL: { + func_decl* f = to_func_decl(n); + j = f->get_arity(); + while (j > 0) { + --j; + visit(f->get_domain(j), qlist, visited); + } + visit(f->get_range(), qlist, visited); + j = f->get_num_parameters(); + while (j > 0) { + --j; + parameter p(f->get_parameter(j)); + if (p.is_ast()) { + visit(p.get_ast(), qlist, visited); + } + } + break; + } + case AST_SORT: { + sort* s = to_sort(n); + j = s->get_num_parameters(); + while (j > 0) { + --j; + parameter p(s->get_parameter(j)); + if (p.is_ast()) { + visit(p.get_ast(), qlist, visited); + } + } + break; + } + case AST_APP: { + app* a = to_app(n); + j = a->get_num_args(); + while (j > 0) { + --j; + visit(a->get_arg(j), qlist, visited); + } + visit(a->get_decl(), qlist, visited); + break; + } + case AST_QUANTIFIER: + j = to_quantifier(n)->get_num_patterns(); + qlist = m_qlist_manager.mk_cons(to_quantifier(n), qlist); + m_qlists.push_back(qlist); + while (j > 0) { + --j; + visit(to_quantifier(n)->get_pattern(j), qlist, visited); + } + j = to_quantifier(n)->get_num_no_patterns(); + while (j > 0) { + --j; + visit(to_quantifier(n)->get_no_pattern(j), qlist, visited); + } + j = to_quantifier(n)->get_num_decls(); + while (j > 0) { + --j; + visit(to_quantifier(n)->get_decl_sort(j), qlist, visited); + visit_sort(to_quantifier(n)->get_decl_sort(j)); + } + visit(to_quantifier(n)->get_expr(), qlist, visited); + break; + default: + break; + } + return visited; + } + + void reduce1(ast * n, quantifier_list * qlist) { + format * r; + switch(n->get_kind()) { + case AST_APP: + r = reduce1_app(to_app(n), qlist); + break; + case AST_VAR: + r = reduce1_var(to_var(n), qlist); + break; + case AST_QUANTIFIER: + r = reduce1_quantifier(to_quantifier(n), qlist); + break; + case AST_SORT: + r = reduce1_sort(to_sort(n), qlist); + break; + case AST_FUNC_DECL: + r = reduce1_func_decl(to_func_decl(n), qlist); + break; + } + m_cache.insert(n, qlist, r); + } + + format * mk_parameter(parameter const & p, quantifier_list * qlist) { + if (p.is_int()) { + return mk_int(m_manager, p.get_int()); + } + else if (p.is_symbol()) { + return mk_string(m_manager, p.get_symbol().str().c_str()); + } + else if (p.is_ast()) { + ast * n = p.get_ast(); + if (is_func_decl(n)) { + return mk_string(m_manager, to_func_decl(n)->get_name().str().c_str()); + } + else { + return get_cached(p.get_ast(), qlist); + } + } + else if (p.is_rational()) { + return mk_string(m_manager, p.get_rational().to_string().c_str()); + } + else { + return 0; + } + } + + void mk_parameters(unsigned num_params, parameter const * p, quantifier_list * qlist, ptr_buffer & result, bool add_separator) { + bool first = true; + for (unsigned i = 0; i < num_params; i++) { + if (!first && add_separator) { + result.push_back(mk_string(m_manager, ":")); + } + format * pp = mk_parameter(p[i], qlist); + if (pp) { + result.push_back(pp); + first = false; + } + } + } + + format * mk_parameters(unsigned num_params, parameter const * p, quantifier_list * qlist) { + if (num_params == 0) + return m_nil; + ptr_buffer buffer; + buffer.push_back(mk_string(m_manager, "[")); + mk_parameters(num_params, p, qlist, buffer, true); + buffer.push_back(mk_string(m_manager, "]")); + return mk_compose(m_manager, buffer.size(), buffer.c_ptr()); + } + + void visit_sort(sort* s) { + if (m_datatype_util.is_datatype(s) && + !m_visited_datatypes.is_marked(s)) { + m_datatypes.push_back(s); + m_visited_datatypes.mark(s, true); + } + } + + format * reduce1_sort(sort * s, quantifier_list * qlist) { + if (m_datatype_util.is_datatype(s)) { + return mk_string(m_manager, s->get_name().str().c_str()); + } + ptr_buffer pps; + mk_parameters(s->get_num_parameters(), s->get_parameters(), qlist, pps, false); + std::string name = s->get_name().str(); + if (pps.empty()) + return mk_string(m_manager, name.c_str()); + return mk_seq1(m_manager, pps.c_ptr(), pps.c_ptr() + pps.size(), + f2f(), name.c_str()); + } + + format * reduce1_func_decl(func_decl * f, quantifier_list * qlist) { + ptr_buffer children; + children.push_back(mk_compose(m_manager, + mk_string(m_manager, f->get_name().str().c_str()), + mk_parameters(f->get_num_parameters(), f->get_parameters(), qlist))); + for (unsigned i = 0; i < f->get_arity(); i++) + children.push_back(get_cached(f->get_domain(i), qlist)); + children.push_back(get_cached(f->get_range(), qlist)); + return mk_seq1(m_manager, children.begin(), children.end(), f2f(), "define"); + } + + void get_children(app * n, quantifier_list * qlist, ptr_buffer & result) { + for (unsigned i = 0; i < n->get_num_args(); i++) + result.push_back(get_cached(n->get_arg(i), qlist)); + } + + format * reduce1_app(app * n, quantifier_list * qlist) { + rational val; + bool is_int; + bool pos; + unsigned bv_size; + uint64 uval; + buffer names; + ptr_buffer children; + if (m_autil.is_numeral(n, val, is_int)) { + std::string str; + if (val.is_neg()) { + str = "(- " + (-val).to_string() + ")"; + } + else { + str = val.to_string(); + } + return mk_string(m_manager, str.c_str()); + } + else if (m_bvutil.is_numeral(n, val, bv_size)) { + std::string str = val.to_string(); + return mk_compose(m_manager, + mk_string(m_manager, "bv"), + mk_string(m_manager, str.c_str()), + mk_compose(m_manager, mk_string(m_manager, "["), mk_unsigned(m_manager, bv_size), mk_string(m_manager, "]"))); + } + else if (m_dl_util.is_finite_sort(n) && + m_dl_util.is_numeral_ext(n, uval)) { + return mk_string(m_manager, rational(uval,rational::ui64()).to_string().c_str()); + } + else if (m_manager.is_label(n, pos, names)) { + get_children(n, qlist, children); + symbol2format f(m_manager); + format * lbl = names.size() > 1 ? mk_seq5(m_manager, names.begin(), names.end(), f) : f(names[0]); + format * args[2] = { lbl, children[0] }; + format ** begin = args; + return mk_seq1(m_manager, begin, begin+2, f2f(), pos ? "lblpos" : "lblneg"); + } + else if (m_manager.is_pattern(n)) { + get_children(n, qlist, children); + return mk_seq5(m_manager, children.begin(), children.end(), f2f(), "{", "}"); + } + else if (m_manager.is_proof(n)) { + get_children(n, qlist, children); + return mk_seq2(m_manager, children.begin(), children.end(), f2f(), n->get_decl()->get_name().str().c_str(), + FORMAT_DEFAULT_INDENT, "[", "]"); + } + else if (m_params.m_pp_fixed_indent || (n->get_decl()->get_num_parameters() > 0 && !n->get_decl()->private_parameters())) { + format * head = mk_compose(m_manager, + mk_string(m_manager, n->get_decl()->get_name().str().c_str()), + mk_parameters(n->get_decl()->get_num_parameters(), n->get_decl()->get_parameters(), qlist)); + if (n->get_num_args() == 0) + return head; + children.push_back(head); + get_children(n, qlist, children); + return mk_seq4(m_manager, children.begin(), children.end(), f2f()); + } + else if (n->get_num_args() == 0) + return mk_string(m_manager, n->get_decl()->get_name().str().c_str()); + else { + get_children(n, qlist, children); + return mk_seq1(m_manager, children.begin(), children.end(), f2f(), n->get_decl()->get_name().str().c_str()); + } + } + + format * reduce1_var(var * v, quantifier_list * qlist) { + unsigned idx = v->get_idx(); + unsigned i = idx; + while (!is_nil(qlist)) { + quantifier * q = head(qlist); + if (i < q->get_num_decls()) + return mk_string(m_manager, q->get_decl_name(q->get_num_decls() - i - 1).str().c_str()); + i -= q->get_num_decls(); + qlist = tail(qlist); + } + if (i < m_num_var_names) { + return mk_string(m_manager, m_var_names[m_num_var_names - i - 1]); + } + else { + return mk_compose(m_manager, mk_string(m_manager, "#"), mk_unsigned(m_manager, idx)); + } + } + + format * reduce1_quantifier(quantifier * q, quantifier_list * qlist) { + qlist = m_qlist_manager.mk_cons(q, qlist); + + ptr_buffer buffer; + unsigned num = q->get_num_decls(); + for (unsigned j = 0; j < num; j++) { + format * d[2]; + d[0] = mk_string(m_manager, q->get_decl_name(j).str().c_str()); + d[1] = get_cached(q->get_decl_sort(j), qlist); + format ** it = d; + buffer.push_back(mk_seq5(m_manager, it, it+2, f2f())); + } + buffer.push_back(get_cached(q->get_expr(), qlist)); + num = q->get_num_patterns(); + char const * pat = ":pat "; + unsigned pat_indent = static_cast(strlen(pat)); + for (unsigned i = 0; i < num; i++) + buffer.push_back(mk_compose(m_manager, mk_string(m_manager, pat), mk_indent(m_manager, pat_indent, get_cached(q->get_pattern(i), qlist)))); + num = q->get_num_no_patterns(); + for (unsigned i = 0; i < num; i++) + buffer.push_back(mk_compose(m_manager, mk_string(m_manager, ":nopat {"), get_cached(q->get_no_pattern(i), qlist), mk_string(m_manager, "}"))); + if (q->get_qid() != symbol::null) + buffer.push_back(mk_compose(m_manager, mk_string(m_manager, ":qid {"), mk_string(m_manager, q->get_qid().str().c_str()), mk_string(m_manager, "}"))); + return mk_seq3(m_manager, buffer.begin(), buffer.end(), f2f(), q->is_forall() ? "forall" : "exists", + q->get_num_decls()); + } + +public: + formatter(ast_manager & m, pp_params const & p, unsigned num_var_names, char const * const * var_names): + m_params(p), + m_manager(m), + m_qlist_manager(m), + m_cache(m), + m_qlists(m_qlist_manager), + m_nil(mk_nil(m), fm(m)), + m_autil(m), + m_bvutil(m), + m_datatype_util(m), + m_dl_util(m), + m_format_trail(fm(m)), + m_num_var_names(num_var_names), + m_var_names(var_names) { + } + + ~formatter() { + } + + format * operator()(ast * n) { + m_todo.push_back(pp_entry(n, m_qlist_manager.mk_nil())); + while (!m_todo.empty()) { + pp_entry k = m_todo.back(); + if (m_cache.contains(k.first, k.second)) + m_todo.pop_back(); + else if (visit_children(k.first, k.second)) { + m_todo.pop_back(); + reduce1(k.first, k.second); + } + } + format* f1 = get_cached(n, m_qlist_manager.mk_nil()); + + if (m_datatypes.empty()) { + return f1; + } + ptr_buffer formats; + formats.push_back(f1); + + for (unsigned i = 0; i < m_datatypes.size(); ++i) { + sort* s = m_datatypes[i]; + std::ostringstream buffer; + m_datatype_util.display_datatype(s, buffer); + format* f2 = mk_string(m_manager, buffer.str().c_str()); + formats.push_back(mk_line_break(m_manager)); + formats.push_back(f2); + } + f1 = mk_compose(m_manager, formats.size(), formats.c_ptr()); + // + // Ensure that reference count is live. + // + m_format_trail.push_back(f1); + return f1; + } +}; + + +std::ostream & ast_pp(std::ostream & out, ast * n, ast_manager & m, pp_params const & p, unsigned indent, + unsigned num_vars, char const * const * names) { + formatter f(m, p, num_vars, names); + app_ref fmt(fm(m)); + fmt = f(n); + if (indent > 0) + fmt = format_ns::mk_indent(m, indent, fmt); + pp(out, fmt, m, p); + return out; +} + +std::string & ast_pp(std::string & out, ast * n, ast_manager & m, pp_params const & p, unsigned indent) { + std::ostringstream buffer; + buffer << mk_pp(n, m, p, indent); + out += buffer.str(); + return out; +} + +std::string ast_pp(ast * n, ast_manager & m, pp_params const & p, unsigned indent) { + std::string out; + return ast_pp(out, n, m, p, indent); +} + + +std::string & ast_pp(std::string & out, ast * n, ast_manager & m) { + return ast_pp(out, n, m, get_pp_default_params()); +} + +std::string ast_pp(ast * n, ast_manager & m) { + return ast_pp(n, m, get_pp_default_params()); +} + diff --git a/lib/ast_pp.h b/lib/ast_pp.h new file mode 100644 index 000000000..eeee29d44 --- /dev/null +++ b/lib/ast_pp.h @@ -0,0 +1,53 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + ast_pp.h + +Abstract: + + Pretty printer + +Author: + + Leonardo de Moura 2008-01-20. + +Revision History: + +--*/ +#ifndef _AST_PP_H_ +#define _AST_PP_H_ + +#include"ast.h" +#include"pp_params.h" + +std::ostream & ast_pp(std::ostream & strm, ast * n, ast_manager & m, pp_params const & p, unsigned indent = 0, + unsigned num_vars = 0, char const * const * names = 0); +std::ostream & ast_pp(std::ostream & strm, ast * n, ast_manager & m); + +std::string & ast_pp(std::string & s, ast * n, ast_manager & m, pp_params const & p, unsigned indent = 0); +std::string ast_pp(ast * n, ast_manager & m, pp_params const & p, unsigned indent = 0); +std::string & ast_pp(std::string & s, ast * n, ast_manager & m); +std::string ast_pp(ast * n, ast_manager & m); + +struct mk_pp { + ast * m_ast; + ast_manager & m_manager; + pp_params const & m_params; + unsigned m_indent; + unsigned m_num_var_names; + char const * const * m_var_names; + mk_pp(ast * a, ast_manager & m, pp_params const & p, unsigned indent = 0, unsigned num_var_names = 0, char const * const * var_names = 0); + mk_pp(ast * a, ast_manager & m, unsigned indent = 0, unsigned num_var_names = 0, char const * const * var_names = 0); +}; + +inline std::ostream& operator<<(std::ostream& out, const mk_pp & p) { + return ast_pp(out, p.m_ast, p.m_manager, p.m_params, p.m_indent, p.m_num_var_names, p.m_var_names); +} + +inline std::string& operator+=(std::string& out, const mk_pp & p) { + return ast_pp(out, p.m_ast, p.m_manager, p.m_params, p.m_indent); +} + +#endif diff --git a/lib/ast_smt2_pp.cpp b/lib/ast_smt2_pp.cpp new file mode 100644 index 000000000..174c36b29 --- /dev/null +++ b/lib/ast_smt2_pp.cpp @@ -0,0 +1,1132 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + ast_smt2_pp.cpp + +Abstract: + + Pretty printer of AST formulas using SMT2 format. + This printer is more expensive than the one in ast_smt_pp.h, + but is supposed to generated a "prettier" and SMT2 compliant output. + +Author: + + Leonardo de Moura (leonardo) + +Revision History: + +--*/ +#include"ast_smt2_pp.h" +#include"shared_occs.h" +#include"pp.h" +#include"ast_ll_pp.h" +#include"ast_pp.h" +#include"algebraic_numbers.h" +using namespace format_ns; + +#define ALIAS_PREFIX "a" +#define MAX_INDENT 16 +#define SMALL_INDENT 2 + +bool is_smt2_simple_symbol_char(char s) { + return + ('0' <= s && s <= '9') || + ('a' <= s && s <= 'z') || + ('A' <= s && s <= 'Z') || + s == '~' || s == '!' || s == '@' || s == '$' || s == '%' || s == '^' || s == '&' || + s == '*' || s == '_' || s == '-' || s == '+' || s == '=' || s == '<' || s == '>' || + s == '.' || s == '?' || s == '/'; +} + +bool is_smt2_quoted_symbol(char const * s) { + if (s == 0) + return false; + if ('0' <= s[0] && s[0] <= '9') + return true; + unsigned len = static_cast(strlen(s)); + for (unsigned i = 0; i < len; i++) + if (!is_smt2_simple_symbol_char(s[i])) + return true; + return false; +} + +bool is_smt2_quoted_symbol(symbol const & s) { + if (s.is_numerical()) + return false; + return is_smt2_quoted_symbol(s.bare_str()); +} + +std::string mk_smt2_quoted_symbol(symbol const & s) { + SASSERT(is_smt2_quoted_symbol(s)); + string_buffer<> buffer; + buffer.append('|'); + char const * str = s.bare_str(); + while (*str) { + if (*str == '|' || *str == '\\') + buffer.append('\\'); + buffer.append(*str); + str++; + } + buffer.append('|'); + return std::string(buffer.c_str()); +} + +format * smt2_pp_environment::pp_fdecl_name(symbol const & s, unsigned & len) const { + ast_manager & m = get_manager(); + if (is_smt2_quoted_symbol(s)) { + std::string str = mk_smt2_quoted_symbol(s); + len = static_cast(str.length()); + return mk_string(m, str.c_str()); + } + else if (s.is_numerical()) { + std::string str = s.str(); + len = static_cast(str.length()); + return mk_string(m, str.c_str()); + } + else { + len = static_cast(strlen(s.bare_str())); + return mk_string(m, s.bare_str()); + } +} + +format * smt2_pp_environment::pp_fdecl_name(func_decl * f, unsigned & len) const { + ast_manager & m = get_manager(); + if (m.is_implies(f)) { + len = 2; + return mk_string(m, "=>"); + } + else if (m.is_ite(f)) { + len = 3; + return mk_string(m, "ite"); + } + else if (m.is_iff(f)) { + len = 1; + return mk_string(m, "="); + } + else { + symbol s = f->get_name(); + return pp_fdecl_name(s, len); + } +} + +bool smt2_pp_environment::is_indexed_fdecl(func_decl * f) const { + if (f->get_family_id() == null_family_id) + return false; + unsigned num = f->get_num_parameters(); + unsigned i; + for (i = 0; i < num; i++) { + if (f->get_parameter(i).is_int()) + continue; + if (f->get_parameter(i).is_ast() && is_func_decl(f->get_parameter(i).get_ast())) + continue; + break; + } + return i == num && num > 0; +} + +bool smt2_pp_environment::is_sort_param(func_decl * f) const { + return + f->get_family_id() != null_family_id && + f->get_num_parameters() == 1 && + f->get_parameter(0).is_ast() && + is_sort(f->get_parameter(0).get_ast()) && + f->get_range() == to_sort(f->get_parameter(0).get_ast()); +} + +format * smt2_pp_environment::pp_as(format * fname, sort * s) { + format * buf[2] = { fname, pp_sort(s) }; + SASSERT(buf[0] != 0 && buf[1] != 0); + return mk_seq1(get_manager(), buf, buf + 2, f2f(), "as"); +} + +format * smt2_pp_environment::pp_fdecl_params(format * fname, func_decl * f) { + SASSERT(is_indexed_fdecl(f)); + unsigned num = f->get_num_parameters(); + ptr_buffer fs; + fs.push_back(fname); + for (unsigned i = 0; i < num; i++) { + SASSERT(f->get_parameter(i).is_int() || (f->get_parameter(i).is_ast() && is_func_decl(f->get_parameter(i).get_ast()))); + if (f->get_parameter(i).is_int()) + fs.push_back(mk_int(get_manager(), f->get_parameter(i).get_int())); + else + fs.push_back(pp_fdecl_ref(to_func_decl(f->get_parameter(i).get_ast()))); + } + return mk_seq1(get_manager(), fs.begin(), fs.end(), f2f(), "_"); +} + +format * smt2_pp_environment::pp_fdecl(func_decl * f, unsigned & len) { + format * fname = pp_fdecl_name(f, len); + if (f->get_family_id() == null_family_id) + return fname; + if (is_sort_param(f)) { + len = UINT_MAX; + return pp_as(fname, f->get_range()); + } + if (is_indexed_fdecl(f)) { + len = UINT_MAX; + return pp_fdecl_params(fname, f); + } + return fname; +} + +format * smt2_pp_environment::pp_signature(format * f_name, func_decl * f) { + if (is_indexed_fdecl(f)) { + f_name = pp_fdecl_params(f_name, f); + } + ptr_buffer f_domain; + for (unsigned i = 0; i < f->get_arity(); i++) + f_domain.push_back(pp_sort(f->get_domain(i))); + ptr_buffer args; + args.push_back(f_name); + args.push_back(mk_seq5(get_manager(), f_domain.begin(), f_domain.end(), f2f())); + args.push_back(pp_sort(f->get_range())); + return mk_seq5(get_manager(), args.begin(), args.end(), f2f()); +} + +format * smt2_pp_environment::pp_fdecl_ref(func_decl * f) { + unsigned len; + format * f_name = pp_fdecl_name(f, len); + if (f->get_family_id() == null_family_id) { + return f_name; + } + return pp_signature(f_name, f); +} + +format * smt2_pp_environment::pp_bv_literal(app * t, bool use_bv_lits, bool bv_neg) { + bv_util & u = get_bvutil(); + SASSERT(u.is_numeral(t)); + rational val; + unsigned bv_size = 1; + u.is_numeral(t, val, bv_size); + SASSERT(val.is_int()); + val = u.norm(val, bv_size, bv_neg); + bool is_neg = false; + if (val.is_neg()) { + val.neg(); + is_neg = true; + } + SASSERT(val.is_nonneg()); + format * vf; + if (!use_bv_lits) { + string_buffer<> buf; + buf << "(_ bv" << val.to_string().c_str() << " " << bv_size << ")"; + vf = mk_string(get_manager(), buf.c_str()); + } + else { + sbuffer buf; + unsigned sz = 0; + buf.push_back('#'); + if (bv_size % 4 == 0) { + buf.push_back('x'); + while (val.is_pos()) { + rational c = val % rational(16); + val = div(val, rational(16)); + SASSERT(rational(0) <= c && c < rational(16)); + if (c <= rational(9)) + buf.push_back('0' + c.get_unsigned()); + else + buf.push_back('a' + (c.get_unsigned() - 10)); + sz+=4; + } + while (sz < bv_size) { + buf.push_back('0'); + sz+=4; + } + } + else { + buf.push_back('b'); + while (val.is_pos()) { + rational c = val % rational(2); + val = div(val, rational(2)); + SASSERT(rational(0) <= c && c < rational(2)); + if (c.is_zero()) + buf.push_back('0'); + else + buf.push_back('1'); + sz += 1; + } + while (sz < bv_size) { + buf.push_back('0'); + sz += 1; + } + } + SASSERT(sz == bv_size); + std::reverse(buf.begin()+2, buf.end()); + buf.push_back(0); + vf = mk_string(get_manager(), buf.begin()); + } + if (is_neg) { + format * buffer[1] = {vf}; + return mk_seq1(get_manager(), buffer, buffer+1, f2f(), "bvneg"); + } + return vf; +} + +format * smt2_pp_environment::pp_float_literal(app * t) { + mpf_manager & fm = get_futil().fm(); + scoped_mpf v(fm); + format * body = 0; + VERIFY(get_futil().is_value(t, v)); + if (fm.is_nan(v)) { + body = mk_string(get_manager(), "NaN"); + } + else if (fm.is_pinf(v)) { + body = mk_string(get_manager(), "plusInfinity"); + } + else if (fm.is_ninf(v)) { + body = mk_string(get_manager(), "minusInfinity"); + } + else if (fm.is_pzero(v)) { + // TODO: make it SMT 2.0 compatible + body = mk_string(get_manager(), "+0.0"); + } + else if (fm.is_nzero(v)) { + // TODO: make it SMT 2.0 compatible + body = mk_string(get_manager(), "-0.0"); + } + else { + // TODO: make it SMT 2.0 compatible + std::string val = fm.to_string(v); + body = mk_string(get_manager(), val.c_str()); + } + return pp_as(body, get_manager().get_sort(t)); +} + +// generate (- f) +format * smt2_pp_environment::mk_neg(format * f) const { + format * buffer[1] = {f}; + return mk_seq1(get_manager(), buffer, buffer+1, f2f(), "-"); +} + +// Return the format string .0 where num is the value of val. +format * smt2_pp_environment::mk_float(rational const & val) const { + SASSERT(val.is_nonneg()); + SASSERT(val.is_int()); + std::string s = val.to_string(); + s += ".0"; + return mk_string(get_manager(), s.c_str()); +} + +format * smt2_pp_environment::pp_arith_literal(app * t, bool decimal, unsigned decimal_prec) { + arith_util & u = get_autil(); + SASSERT(u.is_numeral(t) || u.is_irrational_algebraic_numeral(t)); + rational val; + bool is_int = true; + if (u.is_numeral(t, val, is_int)) { + if (is_int) { + if (val.is_nonneg()) { + return mk_string(get_manager(), val.to_string().c_str()); + } + else { + val.neg(); + return mk_neg(mk_string(get_manager(), val.to_string().c_str())); + } + } + else { + bool is_neg = val.is_neg(); + if (is_neg) + val.neg(); + format * vf; + if (val.is_int()) { + vf = mk_float(val); + } + else if (decimal) { + std::ostringstream buffer; + val.display_decimal(buffer, decimal_prec); + vf = mk_string(get_manager(), buffer.str().c_str()); + } + else { + format * buffer[2] = { mk_float(numerator(val)), mk_float(denominator(val)) }; + vf = mk_seq1(get_manager(), buffer, buffer+2, f2f(), "/"); + } + return is_neg ? mk_neg(vf) : vf; + } + } + else { + SASSERT(u.is_irrational_algebraic_numeral(t)); + anum const & val = u.to_irrational_algebraic_numeral(t); + algebraic_numbers::manager & am = u.am(); + format * vf; + std::ostringstream buffer; + bool is_neg = false; + if (decimal) { + scoped_anum abs_val(am); + am.set(abs_val, val); + if (am.is_neg(val)) { + is_neg = true; + am.neg(abs_val); + } + am.display_decimal(buffer, abs_val, decimal_prec); + } + else { + am.display_root_smt2(buffer, val); + } + vf = mk_string(get_manager(), buffer.str().c_str()); + return is_neg ? mk_neg(vf) : vf; + } +} + +format * smt2_pp_environment::pp_datalog_literal(app * t) { + uint64 v; + VERIFY (get_dlutil().is_numeral(t, v)); + std::ostringstream buffer; + buffer << v; + return mk_string(get_manager(), buffer.str().c_str()); +} + +format_ns::format * smt2_pp_environment::pp_sort(sort * s) { + // Basic sort pretty printing. + // This method is redefined in cmd_context::pp_env: support for parametric sorts. + // Here, we just pretty print builtin sorts: Bool, Int, Real, BitVec and Array. + ast_manager & m = get_manager(); + if (m.is_bool(s)) + return mk_string(m, "Bool"); + if (get_autil().is_int(s)) + return mk_string(m, "Int"); + if (get_autil().is_real(s)) + return mk_string(m, "Real"); + if (get_bvutil().is_bv_sort(s)) { + unsigned sz = get_bvutil().get_bv_size(s); + ptr_buffer fs; + fs.push_back(mk_string(m, "BitVec")); + fs.push_back(mk_unsigned(m, sz)); + return mk_seq1(m, fs.begin(), fs.end(), f2f(), "_"); + } + if (get_arutil().is_array(s)) { + ptr_buffer fs; + unsigned sz = get_array_arity(s); + for (unsigned i = 0; i < sz; i++) { + fs.push_back(pp_sort(get_array_domain(s, i))); + } + fs.push_back(pp_sort(get_array_range(s))); + return mk_seq1(m, fs.begin(), fs.end(), f2f(), "Array"); + } + if (get_futil().is_float(s)) { + unsigned ebits = get_futil().get_ebits(s); + unsigned sbits = get_futil().get_sbits(s); + ptr_buffer fs; + fs.push_back(mk_string(m, "FP")); + fs.push_back(mk_unsigned(m, ebits)); + fs.push_back(mk_unsigned(m, sbits)); + return mk_seq1(m, fs.begin(), fs.end(), f2f(), "_"); + } + return format_ns::mk_string(get_manager(), s->get_name().str().c_str()); +} + +typedef app_ref_vector format_ref_vector; + +class smt2_printer { + ast_manager & m_manager; + pp_params const & m_params; + smt2_pp_environment & m_env; + + shared_occs m_soccs; + expr * m_root; + + typedef obj_map expr2alias; // expr -> position @ m_aliased_exprs, m_aliased_pps, m_aliased_lvls_names. + ptr_vector m_expr2alias_stack; + + expr2alias * m_expr2alias; // expr -> position @ m_aliased_exprs, m_aliased_pps, m_aliased_lvls_names. + ptr_vector m_aliased_exprs; + format_ref_vector m_aliased_pps; + svector > m_aliased_lvls_names; + unsigned m_next_alias_idx; + struct scope { + unsigned m_aliased_exprs_lim; + unsigned m_old_next_alias_idx; + expr * m_old_root; + scope(unsigned lim, unsigned idx, expr * r):m_aliased_exprs_lim(lim), m_old_next_alias_idx(idx), m_old_root(r) {} + }; + svector m_scopes; // size of m_aliased_exprs, m_aliased_pps, m_aliased_lvls_names. + svector m_var_names; + typedef hashtable symbol_set; + symbol_set m_var_names_set; + + struct frame { + expr * m_curr; + unsigned m_idx; + unsigned m_spos; + bool m_use_alias; // if new aliases can be created + frame(expr * c, unsigned i, unsigned s, bool use_alias):m_curr(c), m_idx(i), m_spos(s), m_use_alias(use_alias) {} + }; + + svector m_frame_stack; + format_ref_vector m_format_stack; + struct info { + unsigned m_lvl; + unsigned m_weight; + unsigned m_depth; + info(unsigned l, unsigned w, unsigned d):m_lvl(l), m_weight(w), m_depth(d) {} + }; + svector m_info_stack; + + string_buffer<> m_next_name_buffer; + + symbol next_name(char const * prefix, unsigned & idx) { + while (true) { + m_next_name_buffer.reset(); + m_next_name_buffer.append(prefix); + m_next_name_buffer.append("!"); + m_next_name_buffer.append(idx); + symbol r(m_next_name_buffer.c_str()); + idx++; + if (m_env.uses(r)) + continue; + if (m_var_names_set.contains(r)) + continue; + return r; + } + } + + symbol next_alias() { + return next_name(ALIAS_PREFIX, m_next_alias_idx); + } + + void register_alias(expr * n, format * nf, unsigned lvl, symbol const & name) { + SASSERT(m_aliased_exprs.size() == m_aliased_pps.size()); + SASSERT(m_aliased_exprs.size() == m_aliased_lvls_names.size()); + unsigned idx = m_aliased_exprs.size(); + m_expr2alias->insert(n, idx); + m_aliased_exprs.push_back(n); + m_aliased_pps.push_back(nf); + m_aliased_lvls_names.push_back(std::make_pair(lvl, name)); + } + + void push_frame(expr * n, bool use_alias) { + m_frame_stack.push_back(frame(n, 0, m_format_stack.size(), use_alias)); + } + + void pop_frame() { + m_frame_stack.pop_back(); + } + + ast_manager & m() const { return m_manager; } + ast_manager & fm() const { return format_ns::fm(m()); } + + void pp_var(var * v) { + format * f; + if (v->get_idx() < m_var_names.size()) { + symbol s = m_var_names[m_var_names.size() - v->get_idx() - 1]; + f = mk_string(m(), s.str().c_str()); + } + else { + // fallback... it is not supposed to happen when the printer is correctly used. + string_buffer<> buf; + buf.append("(:var "); + buf.append(v->get_idx()); + buf.append(")"); + f = mk_string(m(), buf.c_str()); + } + m_format_stack.push_back(f); + m_info_stack.push_back(info(0, 1, 1)); + } + + format * pp_attribute(char const * attr, format * f) { + return mk_compose(m(), + mk_string(m(), attr), + mk_indent(m(), static_cast(strlen(attr)), f)); + } + + format * pp_simple_attribute(char const * attr, int v) { + return mk_compose(m(), mk_string(m(), attr), mk_int(m(), v)); + } + + format * pp_simple_attribute(char const * attr, symbol const & s) { + return mk_compose(m(), mk_string(m(), attr), mk_string(m(), s.str().c_str())); + } + + format * pp_labels(bool is_pos, buffer const & names, format * f) { + if (names.empty()) + return f; + ptr_buffer buf; + buf.push_back(f); + for (unsigned i = 0; i < names.size(); i++) { + buf.push_back(pp_simple_attribute(is_pos ? ":lblpos " : ":lblneg ", names[i])); + } + return mk_seq1(m(), buf.begin(), buf.end(), f2f(), "!"); + } + + void pp_const(app * c) { + format * f; + if (m_env.get_autil().is_numeral(c) || m_env.get_autil().is_irrational_algebraic_numeral(c)) { + f = m_env.pp_arith_literal(c, m_params.m_pp_decimal, m_params.m_pp_decimal_precision); + } + else if (m_env.get_bvutil().is_numeral(c)) { + f = m_env.pp_bv_literal(c, m_params.m_pp_bv_lits, m_params.m_pp_bv_neg); + } + else if (m_env.get_futil().is_value(c)) { + f = m_env.pp_float_literal(c); + } + else if (m_env.get_dlutil().is_numeral(c)) { + f = m_env.pp_datalog_literal(c); + } + else { + buffer names; + if (m().is_label_lit(c, names)) { + f = pp_labels(true, names, mk_string(m(), "true")); + } + else { + unsigned len; + f = m_env.pp_fdecl(c->get_decl(), len); + } + } + m_format_stack.push_back(f); + m_info_stack.push_back(info(0, 1, 1)); + } + + bool pp_aliased(expr * t) { + unsigned idx; + if (m_expr2alias->find(t, idx)) { + unsigned lvl = m_aliased_lvls_names[idx].first; + symbol const & s = m_aliased_lvls_names[idx].second; + m_format_stack.push_back(mk_string(m(), s.str().c_str())); + m_info_stack.push_back(info(lvl+1, 1, 1)); + return true; + } + return false; + } + + void process_var(var * v) { + pp_var(v); + pop_frame(); + } + + bool process_args(app * t, frame & fr) { + unsigned num = t->get_num_args(); + while (fr.m_idx < num) { + expr * arg = t->get_arg(fr.m_idx); + fr.m_idx++; + if (pp_aliased(arg)) + continue; + switch (arg->get_kind()) { + case AST_VAR: + pp_var(to_var(arg)); + break; + case AST_APP: + if (to_app(arg)->get_num_args() == 0) { + pp_const(to_app(arg)); + } + else { + push_frame(arg, fr.m_use_alias); + return false; + } + break; + case AST_QUANTIFIER: + push_frame(arg, fr.m_use_alias); + return false; + default: + UNREACHABLE(); + } + } + return true; + } + + void store_result(expr * t, frame & fr, format * f, info & f_info) { + m_format_stack.shrink(fr.m_spos); + m_info_stack.shrink(fr.m_spos); + if (fr.m_use_alias && m_root != t && + ((f_info.m_depth >= m_params.m_pp_max_depth) || + ((f_info.m_weight >= m_params.m_pp_min_alias_size || is_quantifier(t)) && m_soccs.is_shared(t)))) { + symbol a = next_alias(); + TRACE("smt2_pp", tout << "a: " << a << " depth: " << f_info.m_depth << ", weight: " << f_info.m_weight + << ", lvl: " << f_info.m_lvl << " t: #" << t->get_id() << "\n" << mk_ll_pp(t, m()) + << ", is-shared: " << m_soccs.is_shared(t) << "\n";); + register_alias(t, f, f_info.m_lvl, a); + m_format_stack.push_back(mk_string(m(), a.str().c_str())); + m_info_stack.push_back(info(f_info.m_lvl + 1, 1, 1)); + } + else { + m_format_stack.push_back(f); + m_info_stack.push_back(f_info); + } + pop_frame(); + } + + bool flat_assoc(app * t, frame const & fr) { + if (!m_params.m_pp_flat_assoc) + return false; + func_decl * f = t->get_decl(); + if (f->is_associative() && m_frame_stack.size() >= 2 && !m_soccs.is_shared(t)) { + frame const & prev_fr = m_frame_stack[m_frame_stack.size() - 2]; + return is_app(prev_fr.m_curr) && to_app(prev_fr.m_curr)->get_decl() == f; + } + return false; + } + + void process_app(app * t, frame & fr) { + if (fr.m_idx == 0) { + if (pp_aliased(t)) { + pop_frame(); + return; + } + } + if (!process_args(t, fr)) + return; + if (t->get_num_args() == 0) { + pp_const(t); + pop_frame(); + return; + } + if (flat_assoc(t, fr)) { + pop_frame(); + return; + } + buffer labels; + bool is_pos; + format * f = 0; + format ** it = m_format_stack.c_ptr() + fr.m_spos; + format ** end = m_format_stack.c_ptr() + m_format_stack.size(); + if (m().is_label(t, is_pos, labels)) { + SASSERT(it + 1 == end); + f = pp_labels(is_pos, labels, *it); + } + else if (m().is_pattern(t)) { + f = mk_seq5(m(), it, end, f2f()); + } + else { + unsigned len; + SASSERT(it < end); + format * fname = m_env.pp_fdecl(t->get_decl(), len); + if (len > MAX_INDENT) { + f = mk_group(m(), mk_compose(m(), + mk_indent(m(), 1, mk_compose(m(), mk_string(m(), "("), fname)), + mk_indent(m(), SMALL_INDENT, mk_compose(m(), + mk_seq(m(), it, end, f2f()), + mk_string(m(), ")"))))); + } + else { + format * first = *it; + ++it; + f = mk_group(m(), mk_compose(m(), + mk_indent(m(), 1, mk_compose(m(), mk_string(m(), "("), fname)), + mk_indent(m(), len + 2, mk_compose(m(), + mk_string(m(), " "), + first, + mk_seq(m(), it, end, f2f()), + mk_string(m(), ")"))))); + } + } + info f_info(0, 1, 1); + info * it2 = m_info_stack.begin() + fr.m_spos; + info * end2 = m_info_stack.end(); + for (; it2 != end2; ++it2) { + if (it2->m_lvl > f_info.m_lvl) + f_info.m_lvl = it2->m_lvl; + f_info.m_weight += it2->m_weight; + if (it2->m_depth > f_info.m_depth) + f_info.m_depth = it2->m_depth; + } + f_info.m_depth++; + store_result(t, fr, f, f_info); + } + + // Add let decls used to build f. + format * pp_let(format * f, unsigned & num_lets) { + unsigned old_sz = m_scopes.empty() ? 0 : m_scopes.back().m_aliased_exprs_lim; + unsigned sz = m_aliased_exprs.size(); + SASSERT(old_sz <= sz); + num_lets = sz - old_sz; + TRACE("pp_let", tout << "old_sz: " << old_sz << ", sz: " << sz << "\n";); + if (old_sz == sz) + return f; + vector > decls; + for (unsigned i = old_sz; i < sz; i++) { + unsigned lvl = m_aliased_lvls_names[i].first; + symbol f_name = m_aliased_lvls_names[i].second; + format * f_def[1] = { m_aliased_pps.get(i) }; + decls.reserve(lvl+1); + ptr_vector & lvl_decls = decls[lvl]; + lvl_decls.push_back(mk_seq1(m(), f_def, f_def+1, f2f(), f_name.str().c_str())); + } + TRACE("pp_let", tout << "decls.size(): " << decls.size() << "\n";); + ptr_buffer buf; + unsigned num_op = 0; + vector >::iterator it = decls.begin(); + vector >::iterator end = decls.end(); + for (; it != end; ++it) { + ptr_vector & lvl_decls = *it; + if (lvl_decls.empty()) + continue; + if (num_op > 0) + buf.push_back(mk_line_break(m())); + num_op++; + buf.push_back(mk_string(m(), "(let ")); + buf.push_back(mk_indent(m(), 5, mk_seq5(m(), lvl_decls.begin(), lvl_decls.end(), f2f()))); + } + TRACE("pp_let", tout << "num_op: " << num_op << "\n";); + if (num_op == 0) + return f; + buf.push_back(mk_indent(m(), SMALL_INDENT, mk_compose(m(), mk_line_break(m()), f))); + for (unsigned i = 0; i < num_op; i++) + buf.push_back(mk_string(m(), ")")); + return mk_compose(m(), buf.size(), buf.c_ptr()); + } + + format * pp_let(format * f) { + unsigned num_lets; + return pp_let(f, num_lets); + } + + void begin_scope() { + SASSERT(m_aliased_exprs.size() == m_aliased_pps.size()); + SASSERT(m_aliased_exprs.size() == m_aliased_lvls_names.size()); + TRACE("pp_scope", tout << "[begin-scope] sz: " << m_aliased_exprs.size() << ", m_root: " << m_root << "\n";); + m_scopes.push_back(scope(m_aliased_exprs.size(), m_next_alias_idx, m_root)); + unsigned lvl = m_scopes.size(); + while (lvl >= m_expr2alias_stack.size()) + m_expr2alias_stack.push_back(alloc(expr2alias)); + m_expr2alias = m_expr2alias_stack[lvl]; + m_next_alias_idx = 1; + } + + void end_scope() { + TRACE("pp_scope", tout << "[end-scope] before sz: " << m_aliased_exprs.size() << ", m_root: " << m_root << "\n";); + m_expr2alias->reset(); + scope & s = m_scopes.back(); + unsigned old_sz = s.m_aliased_exprs_lim; + m_root = s.m_old_root; + m_next_alias_idx = s.m_old_next_alias_idx; + m_scopes.pop_back(); + unsigned new_lvl = m_scopes.size(); + m_expr2alias = m_expr2alias_stack[new_lvl]; + SASSERT(old_sz <= m_aliased_exprs.size()); + m_aliased_exprs.shrink(old_sz); + m_aliased_pps.shrink(old_sz); + m_aliased_lvls_names.shrink(old_sz); + TRACE("pp_scope", tout << "[end-scope] after sz: " << m_aliased_exprs.size() << ", m_root: " << m_root << "\n";); + } + + void register_var_names(quantifier * q) { + unsigned num_decls = q->get_num_decls(); + for (unsigned i = 0; i < num_decls; i++) { + symbol name = q->get_decl_name(i); + if (name.is_numerical()) { + unsigned idx = 1; + name = next_name("x", idx); + } + else if (m_env.uses(name) || m_var_names_set.contains(name)) { + unsigned idx = 1; + name = next_name(name.bare_str(), idx); + } + SASSERT(!m_var_names_set.contains(name)); + m_var_names.push_back(name); + m_var_names_set.insert(name); + } + } + + void unregister_var_names(quantifier * q) { + unsigned num_decls = q->get_num_decls(); + for (unsigned i = 0; i < num_decls; i++) { + symbol s = m_var_names.back(); + m_var_names.pop_back(); + m_var_names_set.erase(s); + } + } + + format * pp_var_decls(quantifier * q) { + ptr_buffer buf; + unsigned num_decls = q->get_num_decls(); + SASSERT(num_decls <= m_var_names.size()); + symbol * it = m_var_names.end() - num_decls; + for (unsigned i = 0; i < num_decls; i++, it++) { + format * fs[1] = { m_env.pp_sort(q->get_decl_sort(i)) }; + buf.push_back(mk_seq1(m(), fs, fs+1, f2f(), it->str().c_str())); + } + return mk_seq5(m(), buf.begin(), buf.end(), f2f()); + } + + void process_quantifier(quantifier * q, frame & fr) { + if (fr.m_idx == 0) { + begin_scope(); + m_root = q->get_expr(); + register_var_names(q); + } + unsigned num_children = q->get_num_patterns() + q->get_num_no_patterns() + 1; + if (fr.m_idx < num_children) { + unsigned idx = fr.m_idx; + fr.m_idx++; + if (idx < q->get_num_patterns()) { + push_frame(q->get_pattern(idx), false); + } + else if (idx < q->get_num_patterns() + q->get_num_no_patterns()) { + push_frame(q->get_no_pattern(idx - q->get_num_patterns()), false); + } + else { + push_frame(q->get_expr(), fr.m_use_alias); + } + return; + } + unsigned num_lets = 0; + format * f_body = pp_let(m_format_stack.back(), num_lets); + // The current SMT2 frontend uses weight 1 as default. +#define MIN_WEIGHT 1 + if (q->has_patterns() || q->get_weight() > MIN_WEIGHT || + q->get_skid() != symbol::null || (q->get_qid() != symbol::null && !q->get_qid().is_numerical())) { + ptr_buffer buf; + buf.push_back(f_body); + if (q->get_num_patterns() > 0) { + format ** it = m_format_stack.c_ptr() + fr.m_spos; + format ** end = it + q->get_num_patterns(); + for (; it != end; ++it) { + buf.push_back(pp_attribute(":pattern ", *it)); + } + } + if (q->get_num_no_patterns() > 0) { + format ** it = m_format_stack.c_ptr() + fr.m_spos + q->get_num_patterns(); + format ** end = it + q->get_num_no_patterns(); + for (; it != end; ++it) { + buf.push_back(pp_attribute(":no-pattern ", *it)); + } + } + if (q->get_weight() > MIN_WEIGHT) { + buf.push_back(pp_simple_attribute(":weight ", q->get_weight())); + } + if (q->get_skid() != symbol::null) { + buf.push_back(pp_simple_attribute(":skid ", q->get_skid())); + } + if (q->get_qid() != symbol::null) { +#if 0 + buf.push_back(pp_simple_attribute(":qid ", q->get_qid())); +#else + if (!q->get_qid().is_numerical()) + buf.push_back(pp_simple_attribute(":qid ", q->get_qid())); +#endif + } + f_body = mk_seq1(m(), buf.begin(), buf.end(), f2f(), "!"); + } + format * f_decls = pp_var_decls(q); + format * fs[2] = { f_decls, f_body }; + char const * header = q->is_forall() ? "forall" : "exists"; + format * f = mk_seq3(m(), fs, fs+2, f2f(), header, 1, SMALL_INDENT); + + info f_info = m_info_stack.back(); + f_info.m_lvl = 0; // quantifiers don't depend on any let-decls, pp_let added all dependencies for the body. + f_info.m_depth++; + f_info.m_weight += q->get_num_decls()*2 + num_lets*8; + + unregister_var_names(q); + end_scope(); + + store_result(q, fr, f, f_info); + } + + void init_expr2alias_stack() { + SASSERT(m_expr2alias_stack.empty()); + expr2alias * new_map = alloc(expr2alias); + m_expr2alias_stack.push_back(new_map); + m_expr2alias = new_map; + } + + void del_expr2alias_stack() { + std::for_each(m_expr2alias_stack.begin(), m_expr2alias_stack.end(), delete_proc()); + m_expr2alias_stack.reset(); + m_expr2alias = 0; + } + + void reset_expr2alias_stack() { + SASSERT(!m_expr2alias_stack.empty()); + ptr_vector::iterator it = m_expr2alias_stack.begin(); + ptr_vector::iterator end = m_expr2alias_stack.end(); + for (; it != end; ++it) + (*it)->reset(); + m_expr2alias = m_expr2alias_stack[0]; + } + + void reset_stacks() { + m_next_alias_idx = 1; + reset_expr2alias_stack(); + m_aliased_exprs.reset(); + m_aliased_pps.reset(); + m_aliased_lvls_names.reset(); + m_scopes.reset(); + m_frame_stack.reset(); + m_format_stack.reset(); + m_info_stack.reset(); + } + + void process(expr * n, format_ref & r) { + reset_stacks(); + SASSERT(&(r.get_manager()) == &(fm())); + m_soccs(n); + TRACE("smt2_pp_shared", + tout << "shared terms for:\n" << mk_pp(n, m()) << "\n"; + tout << "------>\n"; + shared_occs::iterator it = m_soccs.begin_shared(); + shared_occs::iterator end = m_soccs.end_shared(); + for (; it != end; ++it) { + tout << mk_pp(*it, m()) << "\n"; + }); + m_root = n; + push_frame(n, true); + while (!m_frame_stack.empty()) { + frame & fr = m_frame_stack.back(); + switch (fr.m_curr->get_kind()) { + case AST_QUANTIFIER: + process_quantifier(to_quantifier(fr.m_curr), fr); + break; + case AST_APP: + process_app(to_app(fr.m_curr), fr); + break; + case AST_VAR: + process_var(to_var(fr.m_curr)); + break; + default: + UNREACHABLE(); + } + } + r = pp_let(m_format_stack.back()); + m_format_stack.pop_back(); + } + + void reset_var_names() { + m_var_names.reset(); + m_var_names_set.reset(); + } + +public: + smt2_printer(smt2_pp_environment & env, pp_params const & params): + m_manager(env.get_manager()), + m_params(params), + m_env(env), + m_soccs(m_manager), + m_root(0), + m_aliased_pps(fm()), + m_next_alias_idx(1), + m_format_stack(fm()) { + init_expr2alias_stack(); + } + + ~smt2_printer() { + del_expr2alias_stack(); + } + + void operator()(expr * n, format_ref & r) { + reset_var_names(); + process(n, r); + } + + void operator()(expr * n, unsigned num, char const * var_prefix, format_ref & r, sbuffer & var_names) { + reset_var_names(); + if (var_prefix == 0) + var_prefix = "x"; + if (strcmp(var_prefix, ALIAS_PREFIX) == 0) { + var_prefix = "_a"; + } + unsigned idx = 1; + for (unsigned i = 0; i < num; i++) { + symbol name = next_name(var_prefix, idx); + var_names.push_back(name); + m_var_names_set.insert(name); + m_var_names.push_back(name); + } + std::reverse(m_var_names.begin(), m_var_names.end()); + process(n, r); + } + + void operator()(sort * s, format_ref & r) { + r = m_env.pp_sort(s); + } + + void operator()(func_decl * f, format_ref & r) { + unsigned arity = f->get_arity(); + unsigned len; + format * fname = m_env.pp_fdecl_name(f, len); + format * args[3]; + args[0] = fname; + ptr_buffer buf; + for (unsigned i = 0; i < arity; i++) { + buf.push_back(m_env.pp_sort(f->get_domain(i))); + } + args[1] = mk_seq5(m(), buf.begin(), buf.end(), f2f()); + args[2] = m_env.pp_sort(f->get_range()); + r = mk_seq1(m(), args, args+3, f2f(), "declare-fun"); + } + +}; + +void mk_smt2_format(expr * n, smt2_pp_environment & env, pp_params const & p, + unsigned num_vars, char const * var_prefix, + format_ref & r, sbuffer & var_names) { + smt2_printer pr(env, p); + pr(n, num_vars, var_prefix, r, var_names); +} + +void mk_smt2_format(sort * s, smt2_pp_environment & env, pp_params const & p, format_ref & r) { + smt2_printer pr(env, p); + pr(s, r); +} + +void mk_smt2_format(func_decl * f, smt2_pp_environment & env, pp_params const & p, format_ref & r) { + smt2_printer pr(env, p); + pr(f, r); +} + +std::ostream & ast_smt2_pp(std::ostream & out, expr * n, smt2_pp_environment & env, pp_params const & p, unsigned indent, + unsigned num_vars, char const * var_prefix) { + ast_manager & m = env.get_manager(); + format_ref r(fm(m)); + sbuffer var_names; + mk_smt2_format(n, env, p, num_vars, var_prefix, r, var_names); + if (indent > 0) + r = mk_indent(m, indent, r.get()); + pp(out, r.get(), m, p); + return out; +} + +std::ostream & ast_smt2_pp(std::ostream & out, sort * s, smt2_pp_environment & env, pp_params const & p, unsigned indent) { + ast_manager & m = env.get_manager(); + format_ref r(fm(m)); + sbuffer var_names; + mk_smt2_format(s, env, p, r); + if (indent > 0) + r = mk_indent(m, indent, r.get()); + pp(out, r.get(), m, p); + return out; +} + +std::ostream & ast_smt2_pp(std::ostream & out, func_decl * f, smt2_pp_environment & env, pp_params const & p, unsigned indent) { + ast_manager & m = env.get_manager(); + format_ref r(fm(m)); + sbuffer var_names; + mk_smt2_format(f, env, p, r); + if (indent > 0) + r = mk_indent(m, indent, r.get()); + pp(out, r.get(), m, p); + return out; +} + +mk_ismt2_pp::mk_ismt2_pp(ast * t, ast_manager & m, pp_params const & p, unsigned indent, unsigned num_vars, char const * var_prefix): + m_ast(t), + m_manager(m), + m_params(p), + m_indent(indent), + m_num_vars(num_vars), + m_var_prefix(var_prefix) { +} + +mk_ismt2_pp::mk_ismt2_pp(ast * t, ast_manager & m, unsigned indent, unsigned num_vars, char const * var_prefix): + m_ast(t), + m_manager(m), + m_params(get_pp_default_params()), + m_indent(indent), + m_num_vars(num_vars), + m_var_prefix(var_prefix) { +} + +std::ostream& operator<<(std::ostream& out, mk_ismt2_pp const & p) { + smt2_pp_environment_dbg env(p.m_manager); + if (is_expr(p.m_ast)) { + ast_smt2_pp(out, to_expr(p.m_ast), env, p.m_params, p.m_indent, p.m_num_vars, p.m_var_prefix); + } + else if (is_sort(p.m_ast)) { + ast_smt2_pp(out, to_sort(p.m_ast), env, p.m_params, p.m_indent); + } + else { + SASSERT(is_func_decl(p.m_ast)); + ast_smt2_pp(out, to_func_decl(p.m_ast), env, p.m_params, p.m_indent); + } + return out; +} + diff --git a/lib/ast_smt2_pp.h b/lib/ast_smt2_pp.h new file mode 100644 index 000000000..a3550323a --- /dev/null +++ b/lib/ast_smt2_pp.h @@ -0,0 +1,116 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + ast_smt2_pp.cpp + +Abstract: + + Pretty printer of AST formulas using SMT2 format. + This printer is more expensive than the one in ast_smt_pp.h, + but is supposed to generated a "prettier" and SMT2 compliant output. + +Author: + + Leonardo de Moura (leonardo) + +Revision History: + + +--*/ +#ifndef _AST_SMT2_PP_H_ +#define _AST_SMT2_PP_H_ + +#include"format.h" +#include"pp_params.h" +#include"arith_decl_plugin.h" +#include"bv_decl_plugin.h" +#include"array_decl_plugin.h" +#include"float_decl_plugin.h" +#include"dl_decl_plugin.h" + +bool is_smt2_simple_symbol_char(char c); +bool is_smt2_quoted_symbol(char const * s); +bool is_smt2_quoted_symbol(symbol const & s); +std::string mk_smt2_quoted_symbol(symbol const & s); + +class smt2_pp_environment { +protected: + format_ns::format * mk_neg(format_ns::format * f) const; + format_ns::format * mk_float(rational const & val) const; + bool is_indexed_fdecl(func_decl * f) const; + format_ns::format * pp_fdecl_params(format_ns::format * fname, func_decl * f); + bool is_sort_param(func_decl * f) const; + 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 ast_manager & get_manager() const = 0; + virtual arith_util & get_autil() = 0; + virtual bv_util & get_bvutil() = 0; + virtual array_util & get_arutil() = 0; + virtual float_util & get_futil() = 0; + virtual datalog::dl_decl_util& get_dlutil() = 0; + virtual bool uses(symbol const & s) const = 0; + virtual format_ns::format * pp_fdecl(func_decl * f, unsigned & len); + virtual format_ns::format * pp_bv_literal(app * t, bool use_bv_lits, bool bv_neg); + virtual format_ns::format * pp_arith_literal(app * t, bool decimal, unsigned prec); + virtual format_ns::format * pp_float_literal(app * t); + virtual format_ns::format * pp_datalog_literal(app * t); + virtual format_ns::format * pp_sort(sort * s); + virtual format_ns::format * pp_fdecl_ref(func_decl * f); + format_ns::format * pp_fdecl_name(symbol const & fname, unsigned & len) const; + format_ns::format * pp_fdecl_name(func_decl * f, unsigned & len) const; +}; + +/** + \brief Simple environment that ignores name clashes. + Useful for debugging code. + */ +class smt2_pp_environment_dbg : public smt2_pp_environment { + ast_manager & m_manager; + arith_util m_autil; + bv_util m_bvutil; + array_util m_arutil; + float_util m_futil; + datalog::dl_decl_util m_dlutil; +public: + smt2_pp_environment_dbg(ast_manager & m):m_manager(m), m_autil(m), m_bvutil(m), m_arutil(m), m_futil(m), m_dlutil(m) {} + virtual ast_manager & get_manager() const { return m_manager; } + virtual arith_util & get_autil() { return m_autil; } + virtual bv_util & get_bvutil() { return m_bvutil; } + virtual array_util & get_arutil() { return m_arutil; } + virtual float_util & get_futil() { return m_futil; } + virtual datalog::dl_decl_util& get_dlutil() { return m_dlutil; } + virtual bool uses(symbol const & s) const { return false; } +}; + +void mk_smt2_format(expr * n, smt2_pp_environment & env, pp_params const & p, + unsigned num_vars, char const * var_prefix, + format_ns::format_ref & r, sbuffer & var_names); +void mk_smt2_format(sort * s, smt2_pp_environment & env, pp_params const & p, format_ns::format_ref & r); +void mk_smt2_format(func_decl * f, smt2_pp_environment & env, pp_params const & p, format_ns::format_ref & r); + +std::ostream & ast_smt2_pp(std::ostream & out, expr * n, smt2_pp_environment & env, pp_params const & p, unsigned indent = 0, + unsigned num_vars = 0, char const * var_prefix = 0); +std::ostream & ast_smt2_pp(std::ostream & out, sort * s, smt2_pp_environment & env, pp_params const & p, unsigned indent = 0); +std::ostream & ast_smt2_pp(std::ostream & out, func_decl * f, smt2_pp_environment & env, pp_params const & p, unsigned indent = 0); + +/** + \brief Internal wrapper (for debugging purposes only) +*/ +struct mk_ismt2_pp { + ast * m_ast; + ast_manager & m_manager; + pp_params const & m_params; + unsigned m_indent; + unsigned m_num_vars; + char const * m_var_prefix; + mk_ismt2_pp(ast * t, ast_manager & m, pp_params const & p, unsigned indent = 0, unsigned num_vars = 0, char const * var_prefix = 0); + mk_ismt2_pp(ast * t, ast_manager & m, unsigned indent = 0, unsigned num_vars = 0, char const * var_prefix = 0); +}; + +std::ostream& operator<<(std::ostream& out, mk_ismt2_pp const & p); + +#endif diff --git a/lib/ast_smt_pp.cpp b/lib/ast_smt_pp.cpp new file mode 100644 index 000000000..0dcd78101 --- /dev/null +++ b/lib/ast_smt_pp.cpp @@ -0,0 +1,1214 @@ +/*++ +Copyright (c) 2008 Microsoft Corporation + +Module Name: + + ast_smt_pp.cpp + +Abstract: + + Pretty printer of AST formulas as SMT benchmarks. + +Author: + + Michal Moskal (micmo) 2008-04-09. + Nikolaj Bjorner (nbjorner) + +Revision History: + + +--*/ + +#include +#include +#include"ast_smt_pp.h" +#include"arith_decl_plugin.h" +#include"bv_decl_plugin.h" +#include"array_decl_plugin.h" +#include"datatype_decl_plugin.h" +#include"vector.h" +#include"for_each_ast.h" +#include"decl_collector.h" + +// --------------------------------------- +// smt_renaming + +const static char* m_predef_names[] = { + "=", ">=", "<=", "+", "-", "*", ">", "<", "!=", "or", "and", "implies", "not", "iff", "xor", + "true", "false", "forall", "exists", "let", "flet", NULL +}; + +symbol smt_renaming::fix_symbol(symbol s, int k) { + std::ostringstream buffer; + char const * data = s.is_numerical() ? "" : s.bare_str(); + + if (data[0] && !data[1]) { + switch (data[0]) { + case '/': data = "op_div"; break; + case '%': data = "op_mod"; break; + default: break; + } + } + + if (k == 0 && *data) { + if (s.is_numerical()) { + return s; + } + if (is_special(data)) { + return s; + } + if (all_is_legal(data)) { + return s; + } + } + + if (s.is_numerical()) { + buffer << s << k; + return symbol(buffer.str().c_str()); + } + + buffer << "|"; + if (*data == '|') { + while (*data) { + if (*data == '|') { + if (!data[1]) { + break; + } + buffer << "\\"; + } + buffer << *data; + ++data; + } + } + else { + while (*data) { + if (*data == '|') { + buffer << "\\"; + } + buffer << *data; + ++data; + } + } + if (k > 0) { + buffer << k; + } + buffer << "|"; + + return symbol(buffer.str().c_str()); +} + +bool smt_renaming::is_legal(char c) { + return c == '.' || c == '_' || c == '\'' + || c == '?' || c == '!' || isalnum(c); +} + +bool smt_renaming::is_special(char const* s) { + if (!s) return false; + if (s[0] != '|') return false; + ++s; + while (*s) { + if (s[0] == '|') { + return (0 == s[1]); + } + ++s; + } + return false; +} + +bool smt_renaming::is_numerical(char const* s) { + while (*s) { + if (!isdigit(*s)) { + return false; + } + ++s; + } + return true; +} + +bool smt_renaming::all_is_legal(char const* s) { + if (!s) return false; + if (is_numerical(s)) return false; + while (*s) { + if (!is_legal(*s)) return false; + ++s; + } + return true; +} + +smt_renaming::smt_renaming() { + for (const char **p = m_predef_names; *p; ++p) { + symbol s(*p); + m_translate.insert(s, s); + m_rev_translate.insert(s, s); + } +} + + +symbol smt_renaming::get_symbol(symbol s0) { + symbol s; + if (m_translate.find(s0, s)) { + return s; + } + + int k = 0; + do { + s = fix_symbol(s0, k++); + } + while (m_rev_translate.contains(s)); + m_translate.insert(s0, s); + m_rev_translate.insert(s, s0); + return s; +} + +// --------------------------------------- +// smt_printer + +class smt_printer { + std::ostream& m_out; + ast_manager& m_manager; + ptr_vector& m_qlists; + smt_renaming& m_renaming; + unsigned m_indent; + unsigned m_num_var_names; + char const* const* m_var_names; + ptr_vector m_todo; + ast_mark m_mark; + unsigned m_num_lets; + arith_util m_autil; + bv_util m_bvutil; + family_id m_basic_fid; + family_id m_bv_fid; + family_id m_arith_fid; + family_id m_array_fid; + family_id m_dt_fid; + family_id m_label_fid; + symbol m_logic; + symbol m_AUFLIRA; + bool m_no_lets; + bool m_is_smt2; + bool m_simplify_implies; + expr* m_top; + + bool is_bool(sort* s) { + return + m_basic_fid == s->get_family_id() && + s->get_decl_kind() == BOOL_SORT; + } + + bool is_bool(expr* e) { + return is_bool(m_manager.get_sort(e)); + } + + bool is_proof(sort* s) { + return + m_basic_fid == s->get_family_id() && + s->get_decl_kind() == PROOF_SORT; + } + + bool is_proof(expr* e) { + return is_proof(m_manager.get_sort(e)); + } + + void pp_id(expr* n) { + if (m_is_smt2) { + m_out << (is_bool(n)?"$x":(is_proof(n)?"@x":"?x")) << n->get_id(); + } + else { + m_out << (is_bool(n)?"$x":"?x") << n->get_id(); + } + } + + void pp_decl(func_decl* d) { + symbol sym = m_renaming.get_symbol(d->get_name()); + if (d->get_family_id() == m_dt_fid) { + m_out << sym; + } + else if (m_manager.is_ite(d)) { + if (!m_is_smt2 && is_bool(d->get_range())) { + m_out << "if_then_else"; + } + else { + m_out << "ite"; + } + } + else if (!m_is_smt2 && m_manager.is_implies(d)) { + m_out << "implies"; + } + else if (m_is_smt2 && m_manager.is_iff(d)) { + m_out << "="; + } + else if (m_is_smt2 && m_manager.is_implies(d)) { + m_out << "=>"; + } + else if (m_is_smt2 && is_decl_of(d, m_arith_fid, OP_UMINUS)) { + m_out << "-"; + } + else { + visit_params(false, sym, d->get_num_parameters(), d->get_parameters()); + } + m_out << " "; + } + + bool is_sort_param(unsigned num_params, parameter const* params) { + return + num_params == 1 && + params[0].is_ast() && + is_sort(params[0].get_ast()); + } + + void visit_params(bool is_sort_symbol, symbol const& sym, unsigned num_params, parameter const* params) { + if (0 == num_params) { + m_out << sym; + return; + } + + if (m_is_smt2) { + if (is_sort_symbol && sym != symbol("BitVec")) { + m_out << "(" << sym << " "; + } + else if (!is_sort_symbol && is_sort_param(num_params, params)) { + m_out << "(as " << sym << " "; + } + else { + m_out << "(_ " << sym << " "; + } + } + else { + m_out << sym << "["; + } + for (unsigned i = 0; i < num_params; ++i) { + parameter const& p = params[i]; + if (p.is_ast()) { + if (is_sort(p.get_ast())) { + visit_sort(to_sort(p.get_ast())); + } + else if (is_expr(p.get_ast())) { + pp_expr(to_expr(p.get_ast())); + } + else if (is_func_decl(p.get_ast())) { + pp_decl(to_func_decl(p.get_ast())); + } + else { + m_out << "#" << p.get_ast()->get_id(); + } + } + else { + m_out << p; + } + if (i + 1 < num_params) { + if (m_is_smt2) { + m_out << " "; + } + else { + m_out << ": "; + } + } + } + if (m_is_smt2) { + m_out << ")"; + } + else { + m_out << "]"; + } + } + + bool is_auflira() const { + return m_logic == m_AUFLIRA; + } + + void visit_sort(sort* s, bool bool2int = false) { + symbol sym; + if (bool2int && is_bool(s)) { + sym = symbol("Int"); + } else if (s->is_sort_of(m_bv_fid, BV_SORT)) { + sym = symbol("BitVec"); + } + else if (s->is_sort_of(m_arith_fid, REAL_SORT)) { + sym = s->get_name(); + } + else if (m_manager.is_bool(s)) { + sym = symbol("Bool"); + } + else if (s->is_sort_of(m_arith_fid, INT_SORT)) { + sym = s->get_name(); + } + else if (s->is_sort_of(m_array_fid, ARRAY_SORT) && m_is_smt2) { + sym = "Array"; + } + else if (s->is_sort_of(m_array_fid, ARRAY_SORT) && !m_is_smt2) { + unsigned num_params = s->get_num_parameters(); + SASSERT(num_params >= 2); + if (is_auflira()) { + sort* rng = to_sort(s->get_parameter(1).get_ast()); + if (rng->get_family_id() == m_array_fid) { + m_out << "Array2"; + } + else { + m_out << "Array1"; + } + return; + } + sort* s1 = to_sort(s->get_parameter(0).get_ast()); + sort* s2 = to_sort(s->get_parameter(1).get_ast()); + if (num_params == 2 && + s1->is_sort_of(m_bv_fid, BV_SORT) && + s2->is_sort_of(m_bv_fid, BV_SORT)) { + m_out << "Array"; + m_out << "[" << s1->get_parameter(0).get_int(); + m_out << ":" << s2->get_parameter(0).get_int() << "]"; + return; + } + m_out << "(Array "; + for (unsigned i = 0; i < num_params; ++i) { + visit_sort(to_sort(s->get_parameter(i).get_ast())); + if (i + 1 < num_params) { + m_out << " "; + } + } + m_out << ")"; + return; + } + else if (s->is_sort_of(m_dt_fid, DATATYPE_SORT)) { + m_out << m_renaming.get_symbol(s->get_name()); + return; + } + else { + sym = m_renaming.get_symbol(s->get_name()); + } + visit_params(true, sym, s->get_num_parameters(), s->get_parameters()); + } + + void display_rational(rational const & r, bool is_int) { + bool d = !is_int; + if (r.is_int()) { + m_out << r << (d ? ".0" : ""); + } + else { + m_out << "(/ " << numerator(r) << (d ? ".0" : "") << " " << denominator(r) << (d ? ".0" : "") << ")"; + } + } + + + void pp_arg(expr *arg, app *parent) + { + if (!m_is_smt2 && is_bool(arg) && is_var(arg) && parent->get_family_id() == m_basic_fid) { + m_out << "(not (= "; + pp_marked_expr(arg); + m_out << " 0))"; + } else if (!m_is_smt2 && is_bool(arg) && !is_var(arg) && + parent->get_family_id() != m_basic_fid && + parent->get_family_id() != m_dt_fid) { + + m_out << "(ite "; + pp_marked_expr(arg); + m_out << " 1 0)"; + } else { + pp_marked_expr(arg); + } + } + + void visit_app(app* n) { + rational val; + bool is_int, pos; + buffer names; + unsigned bv_size; + unsigned num_args = n->get_num_args(); + func_decl* decl = n->get_decl(); + if (m_autil.is_numeral(n, val, is_int)) { + if (val.is_neg()) { + val.neg(); + m_out << "(~ "; + display_rational(val, is_int); + m_out << ")"; + } + else { + display_rational(val, is_int); + } + } + else if (m_bvutil.is_numeral(n, val, bv_size)) { + if (m_is_smt2) { + m_out << "(_ bv" << val << " " << bv_size << ")"; + } + else { + m_out << "bv" << val << "[" << bv_size << "]"; + } + } + else if (m_bvutil.is_bit2bool(n)) { + unsigned bit = n->get_decl()->get_parameter(0).get_int(); + if (m_is_smt2) { + m_out << "(= ((_ extract " << bit << " " << bit << ") "; + pp_marked_expr(n->get_arg(0)); + m_out << ") (_ bv1 1))"; + } + else { + m_out << "(= (extract[" << bit << ":" << bit << "] "; + pp_marked_expr(n->get_arg(0)); + m_out << ") bv1[1])"; + } + } + else if (m_manager.is_label(n, pos, names) && names.size() >= 1) { + if (m_is_smt2) { + m_out << "(! "; + pp_marked_expr(n->get_arg(0)); + m_out << (pos?":lblpos":":lblneg") << " " << m_renaming.get_symbol(names[0]) << ")"; + } + else { + m_out << "(" << (pos?"lblpos":"lblneg") << " " << m_renaming.get_symbol(names[0]) << " "; + expr* ch = n->get_arg(0); + pp_marked_expr(ch); + m_out << ")"; + } + } + else if (m_manager.is_label_lit(n, names) && names.size() >= 1) { + if (m_is_smt2) { + m_out << "(! true :lblpos " << m_renaming.get_symbol(names[0]) << ")"; + } + else { + m_out << "(lblpos " << m_renaming.get_symbol(names[0]) << " true )"; + } + } + else if (num_args == 0) { + if (decl->private_parameters()) { + m_out << m_renaming.get_symbol(decl->get_name()); + } + else { + symbol sym = m_renaming.get_symbol(decl->get_name()); + visit_params(false, sym, decl->get_num_parameters(), decl->get_parameters()); + } + } + else if (num_args == 1 && n->get_family_id() == m_label_fid) { + expr* ch = n->get_arg(0); + pp_marked_expr(ch); + } + else if (m_simplify_implies && m_manager.is_implies(decl) && m_manager.is_implies(n->get_arg(1))) { + expr *curr = n; + expr *arg; + m_out << "(implies (and"; + while (m_manager.is_implies(curr)) { + arg = to_app(curr)->get_arg(0); + + m_out << " "; + pp_arg(arg, n); + curr = to_app(curr)->get_arg(1); + } + m_out << ") "; + pp_arg(curr, n); + m_out << ")"; + + } else if (m_manager.is_distinct(decl)) { + ptr_vector args(num_args, n->get_args()); + unsigned idx = 0; + m_out << "(and"; + while (true) { + while (idx < args.size() && !args[idx]) + idx++; + if (idx >= args.size()) break; + sort * s = m_manager.get_sort(args[idx]); + unsigned next = idx + 1; + + // check if there is only a single one + while (next < args.size() && (!args[next] || m_manager.get_sort(args[next]) != s)) + next++; + if (next >= args.size()) { + args[idx] = 0; + // if so, skip it + continue; + } + + // otherwise print all of the relevant sort + m_out << " (distinct"; + for (unsigned i = idx; i < args.size(); ++i) { + if (args[i] && s == m_manager.get_sort(args[i])) { + m_out << " "; + pp_marked_expr(args[i]); + args[i] = 0; + } + } + m_out << ")"; + } + m_out << " true)"; + } + else { + m_out << "("; + pp_decl(decl); + for (unsigned i = 0; i < num_args; ++i) { + pp_arg(n->get_arg(i), n); + if (i + 1 < num_args) { + m_out << " "; + } + } + m_out << ")"; + } + } + + void print_no_lets(expr *e) + { + smt_printer p(m_out, m_manager, m_qlists, m_renaming, m_logic, true, m_simplify_implies, m_is_smt2, m_indent, m_num_var_names, m_var_names); + p(e); + } + + void print_bound(symbol const& name) { + if (name.is_numerical() || '?' != name.bare_str()[0]) { + m_out << "?"; + } + m_out << name; + } + + void visit_quantifier(quantifier* q) { + m_qlists.push_back(q); + + m_out << "("; + if (q->is_forall()) { + m_out << "forall "; + } + else { + m_out << "exists "; + } + if (m_is_smt2) { + m_out << "("; + } + for (unsigned i = 0; i < q->get_num_decls(); ++i) { + sort* s = q->get_decl_sort(i); + m_out << "("; + print_bound(m_renaming.get_symbol(q->get_decl_name(i))); + m_out << " "; + visit_sort(s, true); + m_out << ") "; + } + if (m_is_smt2) { + m_out << ")"; + } + + if (m_is_smt2 && q->get_num_patterns() > 0) { + m_out << "(!"; + } + { + smt_printer p(m_out, m_manager, m_qlists, m_renaming, m_logic, false, m_is_smt2, m_simplify_implies, m_indent, m_num_var_names, m_var_names); + p(q->get_expr()); + } + + for (unsigned i = 0; i < q->get_num_patterns(); ++i) { + app *pat = reinterpret_cast (q->get_pattern(i)); + + if (pat->get_num_args() == 1 && is_app(pat->get_arg(0))) { + app *app = to_app(pat->get_arg(0)); + if (app->get_num_args() == 1 && app->get_decl()->get_name().str() == "sk_hack") { + /* + m_out << " :ex_act { "; + print_no_lets(app->get_arg(0)); + m_out << "}"; + */ + continue; + } + } + + m_out << " :pat { "; + for (unsigned j = 0; j < pat->get_num_args(); ++j) { + print_no_lets(pat->get_arg(j)); + m_out << " "; + } + m_out << "}"; + } + if (m_is_smt2 && q->get_num_patterns() > 0) { + m_out << ")"; + } + m_out << ")"; + newline(); + m_qlists.pop_back(); + } + + void newline() { + unsigned i = m_indent; + m_out << "\n"; + while (i > 0) { m_out << " "; --i; } + } + + void visit_var(var* v) { + unsigned idx = v->get_idx(); + for (unsigned i = m_qlists.size(); ; --i) { + if (i == 0) { + break; + } + quantifier* q = m_qlists[i-1]; + unsigned num_decls = q->get_num_decls(); + if (idx < num_decls) { + unsigned offs = num_decls-idx-1; + symbol name = m_renaming.get_symbol(q->get_decl_name(offs)); + print_bound(name); + return; + } + idx -= num_decls; + } + if (idx < m_num_var_names) { + m_out << m_var_names[m_num_var_names - idx - 1]; + } + else { + m_out << "?" << idx; + } + } + + void pp_marked_expr(expr* n) { + if (m_mark.is_marked(n)) { + pp_id(n); + } + else { + pp_expr(n); + } + } + + void pp_expr(expr* n) { + switch(n->get_kind()) { + case AST_QUANTIFIER: + visit_quantifier(to_quantifier(n)); + break; + case AST_APP: + visit_app(to_app(n)); + break; + case AST_VAR: + visit_var(to_var(n)); + break; + default: + UNREACHABLE(); + } + } + + void visit_expr(expr* n) { + if (m_is_smt2) { + m_out << "(let (("; + } + else if (is_bool(n)) { + m_out << "(flet ("; + } + else { + m_out << "(let ("; + } + pp_id(n); + m_out << " "; + pp_expr(n); + if (m_is_smt2) { + m_out << ")"; + } + m_out << ")"; + newline(); + } + + bool is_unit(expr* n) { + if (n->get_ref_count() <= 2 && is_small(n)) { + return true; + } + if (n == m_top) { + return true; + } + switch(n->get_kind()) { + case AST_VAR: + return true; + case AST_APP: + return to_app(n)->get_num_args() == 0; + default: + return false; + } + } + + static const unsigned m_line_length = 80; + + bool is_small(expr* n) { + unsigned sz = 0; + return is_small(n, sz); + } + + bool is_small(expr* n, unsigned& sz) { + if (sz > m_line_length) { + return false; + } + if (m_mark.is_marked(n)) { + sz += 5; + return sz <= m_line_length; + } + switch(n->get_kind()) { + case AST_QUANTIFIER: + return false; + case AST_VAR: + sz += 5; + return sz <= m_line_length; + case AST_APP: { + app* a = to_app(n); + func_decl* d = a->get_decl(); + symbol const& s = d->get_name(); + if (s.is_numerical()) { + sz += 4; + } + if (s.is_numerical()) { + sz += 7; + } + else { + sz += 3 + static_cast(strlen(s.bare_str())); + } + for (unsigned i = 0; i < a->get_num_args() && sz <= m_line_length; ++i) { + sz += 1; + if (!is_small(a->get_arg(i), sz)) { + return false; + } + } + return sz <= m_line_length; + } + default: + return false; + } + } + + bool visit_children(expr* n) { + unsigned todo_size = m_todo.size(); + switch(n->get_kind()) { + case AST_QUANTIFIER: + case AST_VAR: + break; + case AST_APP: { + app* a = to_app(n); + for (unsigned i = 0; i < a->get_num_args(); ++i) { + expr* ch = a->get_arg(i); + if (!is_unit(ch) && !m_mark.is_marked(ch)) { + m_todo.push_back(ch); + } + } + break; + } + default: + UNREACHABLE(); + break; + } + bool all_visited = todo_size == m_todo.size(); + return all_visited; + } + +public: + smt_printer(std::ostream& out, ast_manager& m, ptr_vector& ql, smt_renaming& rn, + symbol logic, bool no_lets, bool is_smt2, bool simplify_implies, unsigned indent, unsigned num_var_names = 0, char const* const* var_names = 0) : + m_out(out), + m_manager(m), + m_qlists(ql), + m_renaming(rn), + m_indent(indent), + m_num_var_names(num_var_names), + m_var_names(var_names), + m_num_lets(0), + m_autil(m), + m_bvutil(m), + m_logic(logic), + m_AUFLIRA("AUFLIRA"), + // It's much easier to read those testcases with that. + m_no_lets(no_lets), + m_is_smt2(is_smt2), + m_simplify_implies(simplify_implies) + { + m_basic_fid = m.get_basic_family_id(); + m_label_fid = m.get_family_id("label"); + m_bv_fid = m.get_family_id("bv"); + m_arith_fid = m.get_family_id("arith"); + m_array_fid = m.get_family_id("array"); + m_dt_fid = m.get_family_id("datatype"); + } + + void operator()(expr* n) { + m_top = n; + if (!m_no_lets) { + switch(n->get_kind()) { + case AST_APP: + for (unsigned i = 0; i < to_app(n)->get_num_args(); ++i) { + m_todo.push_back(to_app(n)->get_arg(i)); + } + break; + // Don't do this for quantifiers -- they need to have the body be + // visited when the m_qlist contains the relevant quantifier. + default: + break; + } + } + + while (!m_todo.empty()) { + expr* m = m_todo.back(); + if (m_mark.is_marked(m)) { + m_todo.pop_back(); + } + else if (is_unit(m)) { + m_todo.pop_back(); + } + else if (visit_children(m)) { + m_todo.pop_back(); + m_mark.mark(m, true); + visit_expr(m); + ++m_num_lets; + } + } + + pp_marked_expr(n); + for (unsigned i = 0; i < m_num_lets; ++i) { + m_out << ")"; + } + m_mark.reset(); + m_num_lets = 0; + m_top = 0; + } + + void pp_dt(ast_mark& mark, sort* s) { + SASSERT(s->is_sort_of(m_dt_fid, DATATYPE_SORT)); + datatype_util util(m_manager); + ptr_vector const* decls; + ptr_vector rec_sorts; + + rec_sorts.push_back(s); + mark.mark(s, true); + + // collect siblings and sorts that have not already been printed. + for (unsigned h = 0; h < rec_sorts.size(); ++h) { + s = rec_sorts[h]; + decls = util.get_datatype_constructors(s); + + for (unsigned i = 0; i < decls->size(); ++i) { + func_decl* f = (*decls)[i]; + for (unsigned j = 0; j < f->get_arity(); ++j) { + sort* s2 = f->get_domain(j); + if (!mark.is_marked(s2)) { + if (s2->get_family_id() == null_family_id) { + pp_sort_decl(mark, s2); + } + else if (!util.is_datatype(s2)) { + // skip + } + else if (util.are_siblings(s, s2)) { + rec_sorts.push_back(s2); + mark.mark(s2, true); + } + else { + pp_sort_decl(mark, s2); + } + } + } + } + } + + if (m_is_smt2) { + // TBD: datatypes may be declared parametrically. + // get access to parametric generalization, or print + // monomorphic specialization with a tag that gets reused at use-point. + m_out << "(declare-datatypes () ("; + } + else { + m_out << ":datatypes ("; + } + for (unsigned si = 0; si < rec_sorts.size(); ++si) { + s = rec_sorts[si]; + m_out << "("; + m_out << m_renaming.get_symbol(s->get_name()); + m_out << " "; + decls = util.get_datatype_constructors(s); + + for (unsigned i = 0; i < decls->size(); ++i) { + func_decl* f = (*decls)[i]; + ptr_vector const& accs = *util.get_constructor_accessors(f); + if (m_is_smt2 || accs.size() > 0) { + m_out << "("; + } + m_out << m_renaming.get_symbol(f->get_name()); + // if (accs.size() > 0) { + m_out << " "; + // } + for (unsigned j = 0; j < accs.size(); ++j) { + func_decl* a = accs[j]; + m_out << "(" << m_renaming.get_symbol(a->get_name()) << " "; + visit_sort(a->get_range()); + m_out << ")"; + if (j + 1 < accs.size()) m_out << " "; + } + if (m_is_smt2 || accs.size() > 0) { + m_out << ")"; + if (i + 1 < decls->size()) { + m_out << " "; + } + } + } + m_out << ")"; + if (si + 1 < rec_sorts.size()) { + m_out << " "; + } + } + if (m_is_smt2) { + m_out << ")"; + } + m_out << ")"; + newline(); + } + + + void pp_sort_decl(ast_mark& mark, sort* s) { + if (mark.is_marked(s)) { + return; + } + if (s->is_sort_of(m_dt_fid, DATATYPE_SORT)) { + pp_dt(mark, s); + } + else { + if (m_is_smt2) { + m_out << "(declare-sort "; + } + else { + m_out << ":extrasorts ("; + } + visit_sort(s); + m_out << ")"; + newline(); + } + mark.mark(s, true); + } + + + void operator()(func_decl* d) { + if (m_is_smt2) { + m_out << "(declare-fun "; + pp_decl(d); + m_out << "("; + for (unsigned i = 0; i < d->get_arity(); ++i) { + if (i > 0) m_out << " "; + visit_sort(d->get_domain(i), true); + } + m_out << ") "; + visit_sort(d->get_range()); + m_out << ")"; + newline(); + } + else { + m_out << "("; + pp_decl(d); + for (unsigned i = 0; i < d->get_arity(); ++i) { + m_out << " "; + visit_sort(d->get_domain(i), true); + } + m_out << " "; + visit_sort(d->get_range()); + m_out << ")"; + } + } + + void visit_pred(func_decl* d) { + m_out << "("; + pp_decl(d); + for (unsigned i = 0; i < d->get_arity(); ++i) { + m_out << " "; + visit_sort(d->get_domain(i), true); + } + m_out << ")"; + } +}; + + +// --------------------------------------- +// ast_smt_pp: + +ast_smt_pp::ast_smt_pp(ast_manager& m): + m_manager(m), + m_assumptions(m), + m_assumptions_star(m), + m_benchmark_name(), + m_source_info(), + m_status("unknown"), + m_category(), + m_logic(), + m_dt_fid(m.get_family_id("datatype")), + m_is_declared(&m_is_declared_default), + m_simplify_implies(true) +{} + + +void ast_smt_pp::display_expr(std::ostream& strm, expr* n) { + ptr_vector ql; + smt_renaming rn; + smt_printer p(strm, m_manager, ql, rn, m_logic, false, false, m_simplify_implies, 0); + p(n); +} + +void ast_smt_pp::display_expr_smt2(std::ostream& strm, expr* n, unsigned indent, unsigned num_var_names, char const* const* var_names) { + ptr_vector ql; + smt_renaming rn; + smt_printer p(strm, m_manager, ql, rn, m_logic, false, true, m_simplify_implies, indent, num_var_names, var_names); + p(n); +} + + +void ast_smt_pp::display_smt2(std::ostream& strm, expr* n) { + ptr_vector ql; + decl_collector decls(m_manager); + smt_renaming rn; + + for (unsigned i = 0; i < m_assumptions.size(); ++i) { + decls.visit(m_assumptions[i].get()); + } + for (unsigned i = 0; i < m_assumptions_star.size(); ++i) { + decls.visit(m_assumptions_star[i].get()); + } + decls.visit(n); + + if (m_manager.is_proof(n)) { + strm << "("; + } + if (m_benchmark_name != symbol::null) { + strm << "; " << m_benchmark_name << "\n"; + } + if (m_source_info != symbol::null && m_source_info != symbol("")) { + strm << "; :source { " << m_source_info << " }\n"; + } + if (m_manager.is_bool(n)) { + strm << "(set-info :status " << m_status << ")\n"; + } + if (m_category != symbol::null && m_category != symbol("")) { + strm << "; :category { " << m_category << " }\n"; + } + if (m_logic != symbol::null && m_logic != symbol("")) { + strm << "(set-logic " << m_logic << ")\n"; + } + if (m_attributes.size() > 0) { + strm << "; " << m_attributes.c_str(); + } + + ast_mark sort_mark; + for (unsigned i = 0; i < decls.get_num_sorts(); ++i) { + sort* s = decls.get_sorts()[i]; + if (!(*m_is_declared)(s)) { + smt_printer p(strm, m_manager, ql, rn, m_logic, true, true, m_simplify_implies, 0); + p.pp_sort_decl(sort_mark, s); + } + } + + for (unsigned i = 0; i < decls.get_num_decls(); ++i) { + func_decl* d = decls.get_func_decls()[i]; + if (!(*m_is_declared)(d)) { + smt_printer p(strm, m_manager, ql, rn, m_logic, true, true, m_simplify_implies, 0); + p(d); + } + } + + for (unsigned i = 0; i < decls.get_num_preds(); ++i) { + func_decl* d = decls.get_pred_decls()[i]; + if (!(*m_is_declared)(d)) { + smt_printer p(strm, m_manager, ql, rn, m_logic, true, true, m_simplify_implies, 0); + p(d); + } + } + + for (unsigned i = 0; i < m_assumptions.size(); ++i) { + strm << "(assert\n"; + smt_printer p(strm, m_manager, ql, rn, m_logic, false, true, m_simplify_implies, 0); + p(m_assumptions[i].get()); + strm << ")\n"; + } + + for (unsigned i = 0; i < m_assumptions_star.size(); ++i) { + strm << "(assert\n"; + smt_printer p(strm, m_manager, ql, rn, m_logic, false, true, m_simplify_implies, 0); + p(m_assumptions_star[i].get()); + strm << ")\n"; + } + + smt_printer p(strm, m_manager, ql, rn, m_logic, false, true, m_simplify_implies, 0); + if (m_manager.is_bool(n)) { + strm << "(assert\n"; + p(n); + strm << ")\n"; + strm << "(check-sat)\n"; + } + else if (m_manager.is_proof(n)) { + strm << "(proof\n"; + p(n); + strm << "))\n"; + } + else { + p(n); + } +} + +void ast_smt_pp::display(std::ostream& strm, expr* n) { + ptr_vector ql; + decl_collector decls(m_manager); + smt_renaming rn; + + for (unsigned i = 0; i < m_assumptions.size(); ++i) { + decls.visit(m_assumptions[i].get()); + } + for (unsigned i = 0; i < m_assumptions_star.size(); ++i) { + decls.visit(m_assumptions_star[i].get()); + } + decls.visit(n); + + strm << "(benchmark "; + + if (m_benchmark_name != symbol::null) { + strm << m_benchmark_name << "\n"; + } + else { + strm << "unnamed\n"; + } + if (m_source_info != symbol::null && m_source_info != symbol("")) { + strm << ":source { " << m_source_info << " }\n"; + } + strm << ":status " << m_status << "\n"; + if (m_category != symbol::null && m_category != symbol("")) { + strm << ":category { " << m_category << " }\n"; + } + if (m_logic != symbol::null && m_logic != symbol("")) { + strm << ":logic " << m_logic << "\n"; + } + + if (m_attributes.size() > 0) { + strm << m_attributes.c_str(); + } + + ast_mark sort_mark; + for (unsigned i = 0; i < decls.get_num_sorts(); ++i) { + sort* s = decls.get_sorts()[i]; + if (!(*m_is_declared)(s)) { + smt_printer p(strm, m_manager, ql, rn, m_logic, true, false, m_simplify_implies, 0); + p.pp_sort_decl(sort_mark, s); + } + } + + for (unsigned i = 0; i < decls.get_num_decls(); ++i) { + func_decl* d = decls.get_func_decls()[i]; + if (!(*m_is_declared)(d)) { + strm << ":extrafuns ("; + smt_printer p(strm, m_manager, ql, rn, m_logic, true, false, m_simplify_implies, 0); + p(d); + strm << ")\n"; + } + } + + for (unsigned i = 0; i < decls.get_num_preds(); ++i) { + func_decl* d = decls.get_pred_decls()[i]; + if (!(*m_is_declared)(d)) { + strm << ":extrapreds ("; + smt_printer p(strm, m_manager, ql, rn, m_logic, true, false, m_simplify_implies, 0); + p.visit_pred(d); + strm << ")\n"; + } + } + + for (unsigned i = 0; i < m_assumptions.size(); ++i) { + expr * e = m_assumptions[i].get(); + strm << ":assumption\n"; + smt_printer p(strm, m_manager, ql, rn, m_logic, false, false, m_simplify_implies, 0); + p(e); + strm << "\n"; + } + + for (unsigned i = 0; i < m_assumptions_star.size(); ++i) { + strm << ":assumption-core\n"; + smt_printer p(strm, m_manager, ql, rn, m_logic, false, false, m_simplify_implies, 0); + p(m_assumptions_star[i].get()); + strm << "\n"; + } + + { + strm << ":formula\n"; + smt_printer p(strm, m_manager, ql, rn, m_logic, false, false, m_simplify_implies, 0); + p(n); + strm << "\n"; + } + strm << ")\n"; +} diff --git a/lib/ast_smt_pp.h b/lib/ast_smt_pp.h new file mode 100644 index 000000000..36d4ced2e --- /dev/null +++ b/lib/ast_smt_pp.h @@ -0,0 +1,103 @@ +/*++ +Copyright (c) 2008 Microsoft Corporation + +Module Name: + + ast_smt_pp.h + +Abstract: + + Pretty printer of AST formulas as SMT benchmarks. + +Author: + + Nikolaj Bjorner 2008-04-09. + +Revision History: + +--*/ +#ifndef _AST_SMT_PP_H_ +#define _AST_SMT_PP_H_ + +#include"ast.h" +#include +#include"map.h" + +class smt_renaming { + typedef map symbol2symbol; + symbol2symbol m_translate; + symbol2symbol m_rev_translate; + + symbol fix_symbol(symbol s, int k); + bool is_legal(char c); + bool is_special(char const* s); + bool is_numerical(char const* s); + bool all_is_legal(char const* s); +public: + smt_renaming(); + symbol get_symbol(symbol s0); + symbol operator()(symbol const & s) { return get_symbol(s); } +}; + +class ast_smt_pp { +public: + class is_declared { + public: + virtual bool operator()(func_decl* d) const { return false; } + virtual bool operator()(sort* s) const { return false; } + }; +private: + ast_manager& m_manager; + expr_ref_vector m_assumptions; + expr_ref_vector m_assumptions_star; + symbol m_benchmark_name; + symbol m_source_info; + symbol m_status; + symbol m_category; + symbol m_logic; + std::string m_attributes; + family_id m_dt_fid; + is_declared m_is_declared_default; + is_declared* m_is_declared; + bool m_simplify_implies; +public: + ast_smt_pp(ast_manager& m); + + void set_benchmark_name(const char* bn) { if (bn) m_benchmark_name = bn; } + void set_source_info(const char* si) { if (si) m_source_info = si; } + void set_status(const char* s) { if (s) m_status = s; } + void set_category(const char* c) { if (c) m_category = c; } + void set_logic(const char* l) { if (l) m_logic = l; } + void add_attributes(const char* s) { if (s) m_attributes += s; } + void add_assumption(expr* n) { m_assumptions.push_back(n); } + void add_assumption_star(expr* n) { m_assumptions_star.push_back(n); } + void set_simplify_implies(bool f) { m_simplify_implies = f; } + + void set_is_declared(is_declared* id) { m_is_declared = id; } + + void display(std::ostream& strm, expr* n); + void display_smt2(std::ostream& strm, expr* n); + void display_expr(std::ostream& strm, expr* n); + void display_expr_smt2(std::ostream& strm, expr* n, unsigned indent = 0, unsigned num_var_names = 0, char const* const* var_names = 0); + +}; + +struct mk_smt_pp { + expr * m_expr; + ast_manager& m_manager; + unsigned m_indent; + unsigned m_num_var_names; + char const* const* m_var_names; + mk_smt_pp(expr* e, ast_manager & m, unsigned indent = 0, unsigned num_var_names = 0, char const* const* var_names = 0) : + m_expr(e), m_manager(m), m_indent(indent), m_num_var_names(num_var_names), m_var_names(var_names) {} +}; + +inline std::ostream& operator<<(std::ostream& out, const mk_smt_pp & p) { + ast_smt_pp pp(p.m_manager); + pp.display_expr_smt2(out, p.m_expr, p.m_indent, p.m_num_var_names, p.m_var_names); + return out; +} + + + +#endif diff --git a/lib/ast_translation.cpp b/lib/ast_translation.cpp new file mode 100644 index 000000000..8dd6b2f3f --- /dev/null +++ b/lib/ast_translation.cpp @@ -0,0 +1,331 @@ +/*++ +Copyright (c) 2008 Microsoft Corporation + +Module Name: + + ast_translation.cpp + +Abstract: + + AST translation functions + +Author: + + Christoph Wintersteiger (t-cwinte) 2008-11-20 + +Revision History: + +--*/ +#include "arith_decl_plugin.h" +#include "bv_decl_plugin.h" +#include "datatype_decl_plugin.h" +#include "array_decl_plugin.h" +#include "format.h" +#include "ast_translation.h" +#include "ast_ll_pp.h" + +ast_translation::~ast_translation() { + reset_cache(); +} + +void ast_translation::cleanup() { + reset_cache(); + m_cache.finalize(); + m_result_stack.finalize(); + m_frame_stack.finalize(); + m_extra_children_stack.finalize(); +} + +void ast_translation::reset_cache() { + obj_map::iterator it = m_cache.begin(); + obj_map::iterator end = m_cache.end(); + for (; it != end; ++it) { + m_from_manager.dec_ref(it->m_key); + m_to_manager.dec_ref(it->m_value); + } + m_cache.reset(); +} + +void ast_translation::cache(ast * s, ast * t) { + SASSERT(!m_cache.contains(s)); + if (s->get_ref_count() > 1) { + m_cache.insert(s, t); + m_from_manager.inc_ref(s); + m_to_manager.inc_ref(t); + } +} + +void ast_translation::collect_decl_extra_children(decl * d) { + unsigned num_params = d->get_num_parameters(); + for (unsigned i = 0; i < num_params; i++) { + parameter const & p = d->get_parameter(i); + if (p.is_ast()) + m_extra_children_stack.push_back(p.get_ast()); + } +} + +void ast_translation::push_frame(ast * n) { + m_frame_stack.push_back(frame(n, 0, m_extra_children_stack.size(), m_result_stack.size())); + switch (n->get_kind()) { + case AST_SORT: + case AST_FUNC_DECL: + collect_decl_extra_children(to_decl(n)); + break; + default: + break; + } +} + +bool ast_translation::visit(ast * n) { + ast * r; + if (n->get_ref_count() > 1 && m_cache.find(n, r)) { + m_result_stack.push_back(r); + return true; + } + push_frame(n); + return false; +} + +void ast_translation::copy_params(decl * d, unsigned rpos, buffer & ps) { + unsigned num = d->get_num_parameters(); + unsigned j = rpos; + for (unsigned i = 0; i < num; i++) { + parameter const & p = d->get_parameter(i); + if (p.is_ast()) { + ps.push_back(parameter(m_result_stack[j])); + j++; + } + else if (p.is_external()) { + SASSERT(d->get_family_id() != null_family_id); + decl_plugin & from_plugin = *(m_from_manager.get_plugin(d->get_family_id())); + decl_plugin & to_plugin = *(m_to_manager.get_plugin(d->get_family_id())); + ps.push_back(from_plugin.translate(p, to_plugin)); + } + else { + ps.push_back(p); + } + } +} + +void ast_translation::mk_sort(sort * s, frame & fr) { + sort_info * si = s->get_info(); + sort * new_s; + if (si == 0) { + new_s = m_to_manager.mk_sort(s->get_name()); + SASSERT(m_result_stack.size() == fr.m_rpos); + } + else { + buffer ps; + copy_params(s, fr.m_rpos, ps); + new_s = m_to_manager.mk_sort(s->get_name(), sort_info(si->get_family_id(), + si->get_decl_kind(), + si->get_num_elements(), + si->get_num_parameters(), + ps.c_ptr(), + s->private_parameters())); + } + m_result_stack.shrink(fr.m_rpos); + m_result_stack.push_back(new_s); + m_extra_children_stack.shrink(fr.m_cpos); + cache(s, new_s); + m_frame_stack.pop_back(); +} + +void ast_translation::mk_func_decl(func_decl * f, frame & fr) { + func_decl_info * fi = f->get_info(); + SASSERT(fr.m_cpos <= m_extra_children_stack.size()); + unsigned num_extra = m_extra_children_stack.size() - fr.m_cpos; + sort ** new_domain = reinterpret_cast(m_result_stack.c_ptr() + fr.m_rpos + num_extra); + sort * new_range = static_cast(m_result_stack.back()); + func_decl * new_f; + if (fi == 0) { + new_f = m_to_manager.mk_func_decl(f->get_name(), + f->get_arity(), + new_domain, + new_range); + } + else { + buffer ps; + copy_params(f, fr.m_rpos, ps); + func_decl_info new_fi(fi->get_family_id(), + fi->get_decl_kind(), + fi->get_num_parameters(), + ps.c_ptr()); + + new_fi.set_left_associative(fi->is_left_associative()); + new_fi.set_right_associative(fi->is_right_associative()); + new_fi.set_flat_associative(fi->is_flat_associative()); + new_fi.set_commutative(fi->is_commutative()); + new_fi.set_chainable(fi->is_chainable()); + new_fi.set_pairwise(fi->is_pairwise()); + new_fi.set_injective(fi->is_injective()); + new_fi.set_skolem(fi->is_skolem()); + new_fi.set_idempotent(fi->is_idempotent()); + + new_f = m_to_manager.mk_func_decl(f->get_name(), + f->get_arity(), + new_domain, + new_range, + new_fi); + } + TRACE("ast_translation", + tout << f->get_name() << " "; if (fi) tout << *fi; tout << "\n"; + tout << "---->\n"; + tout << new_f->get_name() << " "; if (new_f->get_info()) tout << *(new_f->get_info()); tout << "\n";); + + m_result_stack.shrink(fr.m_rpos); + m_result_stack.push_back(new_f); + m_extra_children_stack.shrink(fr.m_cpos); + cache(f, new_f); + m_frame_stack.pop_back(); +} + +ast * ast_translation::process(ast const * _n) { + SASSERT(m_result_stack.empty()); + SASSERT(m_frame_stack.empty()); + SASSERT(m_extra_children_stack.empty()); + + if (!visit(const_cast(_n))) { + while (!m_frame_stack.empty()) { + loop: + frame & fr = m_frame_stack.back(); + ast * n = fr.m_n; + ast * r; + TRACE("ast_translation", tout << mk_ll_pp(n, m_from_manager, false) << "\n";); + if (fr.m_idx == 0 && n->get_ref_count() > 1 && m_cache.find(n, r)) { + SASSERT(m_result_stack.size() == fr.m_rpos); + m_result_stack.push_back(r); + m_extra_children_stack.shrink(fr.m_cpos); + m_frame_stack.pop_back(); + TRACE("ast_translation", tout << "hit\n";); + continue; + } + switch (n->get_kind()) { + case AST_VAR: { + if (fr.m_idx == 0) { + fr.m_idx = 1; + if (!visit(to_var(n)->get_sort())) + goto loop; + } + sort * new_s = to_sort(m_result_stack.back()); + var * new_var = m_to_manager.mk_var(to_var(n)->get_idx(), new_s); + m_result_stack.pop_back(); + m_result_stack.push_back(new_var); + cache(n, new_var); + m_frame_stack.pop_back(); + break; + } + case AST_APP: { + if (fr.m_idx == 0) { + fr.m_idx = 1; + if (!visit(to_app(n)->get_decl())) + goto loop; + } + unsigned num = to_app(n)->get_num_args(); + while (fr.m_idx <= num) { + expr * arg = to_app(n)->get_arg(fr.m_idx - 1); + fr.m_idx++; + if (!visit(arg)) + goto loop; + } + func_decl * new_f = to_func_decl(m_result_stack[fr.m_rpos]); + expr ** new_args = reinterpret_cast(m_result_stack.c_ptr() + fr.m_rpos + 1); + expr * new_app = m_to_manager.mk_app(new_f, num, new_args); + m_result_stack.shrink(fr.m_rpos); + m_result_stack.push_back(new_app); + cache(n, new_app); + m_frame_stack.pop_back(); + break; + } + case AST_QUANTIFIER: { + unsigned num_decls = to_quantifier(n)->get_num_decls(); + unsigned num = num_decls + to_quantifier(n)->get_num_children(); + while (fr.m_idx < num) { + ast * child; + if (fr.m_idx < num_decls) + child = to_quantifier(n)->get_decl_sort(fr.m_idx); + else + child = to_quantifier(n)->get_child(fr.m_idx - num_decls); + fr.m_idx++; + if (!visit(child)) + goto loop; + } + symbol const * dnames = to_quantifier(n)->get_decl_names(); + sort ** dsorts = reinterpret_cast(m_result_stack.c_ptr() + fr.m_rpos); + expr * body = static_cast(m_result_stack[fr.m_rpos + num_decls]); + unsigned num_pats = to_quantifier(n)->get_num_patterns(); + expr ** pats = reinterpret_cast(m_result_stack.c_ptr() + fr.m_rpos + num_decls + 1); + unsigned num_no_pats = to_quantifier(n)->get_num_no_patterns(); + expr ** no_pats = pats + num_pats; + quantifier * new_q = m_to_manager.mk_quantifier(to_quantifier(n)->is_forall(), + num_decls, + dsorts, + dnames, + body, + to_quantifier(n)->get_weight(), + to_quantifier(n)->get_qid(), + to_quantifier(n)->get_skid(), + num_pats, pats, + num_no_pats, no_pats); + m_result_stack.shrink(fr.m_rpos); + m_result_stack.push_back(new_q); + cache(n, new_q); + m_frame_stack.pop_back(); + break; + } + case AST_SORT: { + SASSERT(fr.m_cpos <= m_extra_children_stack.size()); + unsigned num = m_extra_children_stack.size() - fr.m_cpos; + while (fr.m_idx < num) { + ast * c = m_extra_children_stack[fr.m_cpos + fr.m_idx]; + fr.m_idx++; + if (!visit(c)) + goto loop; + } + mk_sort(to_sort(n), fr); + break; + } + case AST_FUNC_DECL: { + SASSERT(fr.m_cpos <= m_extra_children_stack.size()); + unsigned num_extra = m_extra_children_stack.size() - fr.m_cpos; + unsigned arity = to_func_decl(n)->get_arity(); + unsigned num = num_extra + arity + 1; + while (fr.m_idx < num) { + ast * c; + if (fr.m_idx < num_extra) + c = m_extra_children_stack[fr.m_cpos + fr.m_idx]; + else if (fr.m_idx < num_extra + arity) + c = to_func_decl(n)->get_domain(fr.m_idx - num_extra); + else + c = to_func_decl(n)->get_range(); + fr.m_idx++; + if (!visit(c)) + goto loop; + } + mk_func_decl(to_func_decl(n), fr); + break; + } + default: + UNREACHABLE(); + break; + } + } + } + SASSERT(m_result_stack.size() == 1); + ast * r = m_result_stack.back(); + m_result_stack.reset(); + return r; +} + +expr_dependency * expr_dependency_translation::operator()(expr_dependency * d) { + if (d == 0) + return d; + m_buffer.reset(); + m_translation.from().linearize(d, m_buffer); + unsigned sz = m_buffer.size(); + SASSERT(sz >= 1); + for (unsigned i = 0; i < sz; i++) { + m_buffer[i] = m_translation(m_buffer[i]); + } + return m_translation.to().mk_join(sz, m_buffer.c_ptr()); +} diff --git a/lib/ast_translation.h b/lib/ast_translation.h new file mode 100644 index 000000000..85c6af7ac --- /dev/null +++ b/lib/ast_translation.h @@ -0,0 +1,97 @@ +/*++ +Copyright (c) 2008 Microsoft Corporation + +Module Name: + + ast_translation.h + +Abstract: + + AST translation functions + +Author: + + Christoph Wintersteiger (t-cwinte) 2008-11-20 + +Revision History: + + 2011-05-26: New local translation class. + +--*/ +#ifndef _AST_TRANSLATION_H_ +#define _AST_TRANSLATION_H_ + +#include"ast.h" + +class ast_translation { + struct frame { + ast * m_n; + unsigned m_idx; + unsigned m_cpos; + unsigned m_rpos; + frame(ast * n, unsigned idx, unsigned cpos, unsigned rpos):m_n(n), m_idx(idx), m_cpos(cpos), m_rpos(rpos) {} + }; + ast_manager & m_from_manager; + ast_manager & m_to_manager; + svector m_frame_stack; + ptr_vector m_extra_children_stack; // for sort and func_decl, since they have nested AST in their parameters + ptr_vector m_result_stack; + obj_map m_cache; + + void cache(ast * s, ast * t); + void collect_decl_extra_children(decl * d); + void push_frame(ast * n); + bool visit(ast * n); + void copy_params(decl * d, unsigned rpos, buffer & ps); + void mk_sort(sort * s, frame & fr); + void mk_func_decl(func_decl * f, frame & fr); + + ast * process(ast const * n); + +public: + ast_translation(ast_manager & from, ast_manager & to, bool copy_plugins = true) : m_from_manager(from), m_to_manager(to) { + if (copy_plugins) + m_to_manager.copy_families_plugins(m_from_manager); + } + + ~ast_translation(); + + template + T * operator()(T const * n) { + SASSERT(from().contains(const_cast(n))); + ast * r = process(n); + SASSERT(to().contains(const_cast(r))); + return static_cast(r); + } + + ast_manager & from() const { return m_from_manager; } + ast_manager & to() const { return m_to_manager; } + + void reset_cache(); + void cleanup(); +}; + +// Translation with non-persistent cache. +inline ast * translate(ast const * a, ast_manager & from, ast_manager & to) { + return ast_translation(from, to)(a); +} + +inline expr * translate(expr const * e, ast_manager & from, ast_manager & to) { + return ast_translation(from, to)(e); +} + +class expr_dependency_translation { + ast_translation & m_translation; + ptr_vector m_buffer; +public: + expr_dependency_translation(ast_translation & t):m_translation(t) {} + expr_dependency * operator()(expr_dependency * d); +}; + +inline expr_dependency * translate(expr_dependency * d, ast_manager & from, ast_manager & to) { + ast_translation t(from, to); + expr_dependency_translation td(t); + return td(d); +} + +#endif diff --git a/lib/ast_util.cpp b/lib/ast_util.cpp new file mode 100644 index 000000000..8b0b3fa04 --- /dev/null +++ b/lib/ast_util.cpp @@ -0,0 +1,140 @@ +#include "ast_util.h" + +app * mk_list_assoc_app(ast_manager & m, func_decl * f, unsigned num_args, expr * const * args) { + SASSERT(f->is_associative()); + SASSERT(num_args >= 2); + if (num_args > 2) { + unsigned j = num_args - 1; + app * r = m.mk_app(f, args[j-1], args[j]); + -- j; + while (j > 0) { + --j; + r = m.mk_app(f, args[j], r); + } + return r; + } + else { + SASSERT(num_args == 2); + return m.mk_app(f, args[0], args[1]); + } +} + +app * mk_list_assoc_app(ast_manager & m, family_id fid, decl_kind k, unsigned num_args, expr * const * args) { + func_decl * decl = m.mk_func_decl(fid, k, 0, 0, num_args, args, 0); + SASSERT(decl != 0); + SASSERT(decl->is_associative()); + return mk_list_assoc_app(m, decl, num_args, args); +} + +bool is_well_formed_vars(ptr_vector& bound, expr* e) { + ptr_vector todo; + ast_mark mark; + todo.push_back(e); + while (!todo.empty()) { + expr* e = todo.back(); + todo.pop_back(); + if (mark.is_marked(e)) { + continue; + } + mark.mark(e, true); + if (is_quantifier(e)) { + quantifier* q = to_quantifier(e); + unsigned depth = q->get_num_decls(); + bound.append(depth, q->get_decl_sorts()); + if (!is_well_formed_vars(bound, q->get_expr())) { + return false; + } + bound.resize(bound.size()-depth); + } + else if (is_app(e)) { + app* a = to_app(e); + for (unsigned i = 0; i < a->get_num_args(); ++i) { + todo.push_back(a->get_arg(i)); + } + } + else if (is_var(e)) { + var* v = to_var(e); + unsigned index = v->get_idx(); + sort* s = v->get_sort(); + SASSERT(index < bound.size()); + index = bound.size()-1-index; + if (!bound[index]) { + bound[index] = s; + } + if (bound[index] != s) { + return false; + } + } + else { + UNREACHABLE(); + } + } + return true; +} + + +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; + } + // the other operators of the basic family are not considered atomic: distinct, ite, and, or, iff, xor, not, implies. + return (m.is_eq(n) && !m.is_bool(to_app(n)->get_arg(0))) || m.is_true(n) || m.is_false(n); +} + + +bool is_literal(ast_manager & m, expr * n) { + return + is_atom(m, n) || + (m.is_not(n) && is_atom(m, to_app(n)->get_arg(0))); +} + +void get_literal_atom_sign(ast_manager & m, expr * n, expr * & atom, bool & sign) { + SASSERT(is_literal(m, n)); + if (is_atom(m, n)) { + atom = n; + sign = false; + } + else { + SASSERT(m.is_not(n)); + atom = to_app(n)->get_arg(0); + sign = true; + } +} + +bool is_clause(ast_manager & m, expr * n) { + if (is_literal(m, n)) + return true; + if (m.is_or(n)) { + unsigned num_args = to_app(n)->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + if (!is_literal(m, to_app(n)->get_arg(i))) + return false; + } + return true; + } + return false; +} + +unsigned get_clause_num_literals(ast_manager & m, expr * cls) { + SASSERT(is_clause(m, cls)); + if (is_literal(m, cls)) + return 1; + SASSERT(m.is_or(cls)); + return to_app(cls)->get_num_args(); +} + +expr * get_clause_literal(ast_manager & m, expr * cls, unsigned idx) { + SASSERT(is_clause(m, cls)); + SASSERT(idx < get_clause_num_literals(m, cls)); + if (is_literal(m, cls)) { + SASSERT(idx == 0); + return cls; + } + SASSERT(m.is_or(cls)); + return to_app(cls)->get_arg(idx); +} diff --git a/lib/ast_util.h b/lib/ast_util.h new file mode 100644 index 000000000..c7f54da01 --- /dev/null +++ b/lib/ast_util.h @@ -0,0 +1,99 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + ast_util.h + +Abstract: + + Helper functions + +Author: + + Leonardo de Moura (leonardo) 2007-06-08. + +Revision History: + +--*/ +#ifndef _AST_UTIL_H_ +#define _AST_UTIL_H_ + +#include"ast.h" +#include"obj_hashtable.h" + +template +void remove_duplicates(C & v) { + expr_fast_mark1 visited; + if (!v.empty()) { + unsigned sz = v.size(); + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + typename C::data curr = v.get(i); + if (!visited.is_marked(curr)) { + visited.mark(curr); + if (i != j) + v.set(j, curr); + j++; + } + } + v.shrink(j); + } +} + +app * mk_list_assoc_app(ast_manager & m, func_decl * f, unsigned num_args, expr * const * args); +app * mk_list_assoc_app(ast_manager & m, family_id fid, decl_kind k, unsigned num_args, expr * const * args); + +bool is_well_formed_vars(ptr_vector& bound, expr* n); + +inline bool args_are_vars(app const * n) { + unsigned sz = n->get_num_args(); + for (unsigned i = 0; i < sz; i++) { + if (!is_var(n->get_arg(i))) + return false; + } + return true; +} + +inline bool depth_leq_one(app * n) { + unsigned sz = n->get_num_args(); + for (unsigned i = 0; i < sz; i++) { + expr * arg = n->get_arg(i); + if (is_app(arg) && to_app(arg)->get_num_args() > 0) + return false; + } + return true; +} + +template +void dec_ref(ast_manager & m, obj_hashtable & s) { + typename obj_hashtable::iterator it = s.begin(); + typename obj_hashtable::iterator end = s.end(); + for (;it != end; ++it) { + m.dec_ref(*it); + } +} + +template +void inc_ref(ast_manager & m, obj_hashtable & s) { + typename obj_hashtable::iterator it = s.begin(); + typename obj_hashtable::iterator end = s.end(); + for (;it != end; ++it) { + m.inc_ref(*it); + } +} + +// ----------------------------------- +// +// Clauses (as ASTs) support +// +// ----------------------------------- +bool is_atom(ast_manager & m, expr * n); +bool is_literal(ast_manager & m, expr * n); +void get_literal_atom_sign(ast_manager & m, expr * n, expr * & atom, bool & sign); +bool is_clause(ast_manager & m, expr * n); +unsigned get_clause_num_literals(ast_manager & m, expr * cls); +expr * get_clause_literal(ast_manager & m, expr * cls, unsigned idx); + +#endif /* _AST_UTIL_H_ */ + diff --git a/lib/atom2bool_var.cpp b/lib/atom2bool_var.cpp new file mode 100644 index 000000000..8176fdb53 --- /dev/null +++ b/lib/atom2bool_var.cpp @@ -0,0 +1,121 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + atom2bool_var.cpp + +Abstract: + + The mapping between SAT boolean variables and atoms + +Author: + + Leonardo (leonardo) 2011-10-25 + +Notes: + +--*/ +#include"atom2bool_var.h" +#include"ast_smt2_pp.h" +#include"ref_util.h" +#include"assertion_set.h" // TODO delete +#include"goal.h" + +void atom2bool_var::mk_inv(expr_ref_vector & lit2expr) const { + obj_map::iterator it = m_mapping.begin(); + obj_map::iterator end = m_mapping.end(); + for (; it != end; ++it) { + sat::literal l(static_cast(it->m_value), false); + lit2expr.set(l.index(), it->m_key); + l.neg(); + lit2expr.set(l.index(), m().mk_not(it->m_key)); + } +} + +sat::bool_var atom2bool_var::to_bool_var(expr * n) const { + sat::bool_var v = sat::null_bool_var; + m_mapping.find(n, v); + return v; +} + +struct collect_boolean_interface_proc { + struct visitor { + obj_hashtable & m_r; + visitor(obj_hashtable & r):m_r(r) {} + void operator()(var * n) {} + void operator()(app * n) { if (is_uninterp_const(n)) m_r.insert(n); } + void operator()(quantifier * n) {} + }; + + ast_manager & m; + expr_fast_mark2 fvisited; + expr_fast_mark1 tvisited; + ptr_vector todo; + visitor proc; + + collect_boolean_interface_proc(ast_manager & _m, obj_hashtable & r): + m(_m), + proc(r) { + } + + void process(expr * f) { + if (fvisited.is_marked(f)) + return; + fvisited.mark(f); + todo.push_back(f); + while (!todo.empty()) { + expr * t = todo.back(); + todo.pop_back(); + if (is_uninterp_const(t)) + continue; + if (is_app(t) && to_app(t)->get_family_id() == m.get_basic_family_id() && to_app(t)->get_num_args() > 0) { + decl_kind k = to_app(t)->get_decl_kind(); + if (k == OP_OR || k == OP_NOT || k == OP_IFF || ((k == OP_EQ || k == OP_ITE) && m.is_bool(to_app(t)->get_arg(1)))) { + unsigned num = to_app(t)->get_num_args(); + for (unsigned i = 0; i < num; i++) { + expr * arg = to_app(t)->get_arg(i); + if (fvisited.is_marked(arg)) + continue; + fvisited.mark(arg); + todo.push_back(arg); + } + } + } + else { + quick_for_each_expr(proc, tvisited, t); + } + } + } + + template + void operator()(T const & g) { + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) + process(g.form(i)); + } + + void operator()(unsigned sz, expr * const * fs) { + for (unsigned i = 0; i < sz; i++) + process(fs[i]); + } +}; + +template +void collect_boolean_interface_core(T const & s, obj_hashtable & r) { + collect_boolean_interface_proc proc(s.m(), r); + proc(s); +} + +void collect_boolean_interface(assertion_set const & s, obj_hashtable & r) { + collect_boolean_interface_core(s, r); +} + +void collect_boolean_interface(goal const & g, obj_hashtable & r) { + collect_boolean_interface_core(g, r); +} + +void collect_boolean_interface(ast_manager & m, unsigned num, expr * const * fs, obj_hashtable & r) { + collect_boolean_interface_proc proc(m, r); + proc(num, fs); +} diff --git a/lib/atom2bool_var.h b/lib/atom2bool_var.h new file mode 100644 index 000000000..8dbff5ea4 --- /dev/null +++ b/lib/atom2bool_var.h @@ -0,0 +1,45 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + atom2bool_var.h + +Abstract: + + The mapping between SAT boolean variables and atoms + +Author: + + Leonardo (leonardo) 2011-10-25 + +Notes: + +--*/ +#ifndef _ATOM2BOOL_VAR_H_ +#define _ATOM2BOOL_VAR_H_ + +#include"expr2var.h" +#include"sat_types.h" + +/** + \brief Mapping from atoms into SAT boolean variables. +*/ +class atom2bool_var : public expr2var { +public: + atom2bool_var(ast_manager & m):expr2var(m) {} + void insert(expr * n, sat::bool_var v) { expr2var::insert(n, v); } + sat::bool_var to_bool_var(expr * n) const; + void mk_inv(expr_ref_vector & lit2expr) const; + // return true if the mapping contains uninterpreted atoms. + bool interpreted_atoms() const { return expr2var::interpreted_vars(); } +}; + +class assertion_set; // TODO: delete +class goal; + +void collect_boolean_interface(assertion_set const & s, obj_hashtable & r); // TODO delete +void collect_boolean_interface(goal const & g, obj_hashtable & r); +void collect_boolean_interface(ast_manager & m, unsigned num, expr * const * fs, obj_hashtable & r); + +#endif diff --git a/lib/backtrackable_set.h b/lib/backtrackable_set.h new file mode 100644 index 000000000..617c57769 --- /dev/null +++ b/lib/backtrackable_set.h @@ -0,0 +1,114 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + backtrackable_set.h + +Abstract: + + Quick hack for support backtrackable sets. + +Author: + + Leonardo de Moura (leonardo) 2011-01-08. + +Revision History: + +--*/ +#ifndef _BACKTRACKABLE_SET_H_ +#define _BACKTRACKABLE_SET_H_ + +#include"vector.h" + +template +struct default_eh { + void operator()(T const & e, bool ins) {} +}; + +// quick hack for having backtrackable sets. +// +// EV is a big hack, it should be used with care. +// +template > +class backtrackable_set : private EV { + enum trail_kind { DEL, INS }; + typedef std::pair trail_obj; + Set m_set; + svector m_trail; + svector m_scopes; + +public: + typedef typename Set::iterator iterator; + + backtrackable_set(EV const & ev = EV()): + EV(ev) { + } + + void insert(T const & e) { + if (m_scopes.empty()) { + m_set.insert(e); + } + else if (!m_set.contains(e)) { + m_set.insert(e); + m_trail.push_back(std::make_pair(INS, e)); + } + } + + void erase(T const & e) { + if (m_scopes.empty()) { + m_set.insert(e); + } + else if (m_set.contains(e)) { + m_set.erase(e); + m_trail.push_back(std::make_pair(DEL, e)); + } + } + + bool contains(T const & e) const { + return m_set.contains(e); + } + + bool empty() const { + return m_set.empty(); + } + + void push_scope() { + m_scopes.push_back(m_trail.size()); + } + + void pop_scope() { + unsigned old_sz = m_scopes.back(); + m_scopes.pop_back(); + SASSERT(old_sz <= m_trail.size()); + while (m_trail.size() > old_sz) { + trail_obj & t = m_trail.back(); + if (t.first == INS) { + this->operator()(t.second, true); + m_set.erase(t.second); + } + else { + SASSERT(t.first == DEL); + this->operator()(t.second, false); + m_set.insert(t.second); + } + m_trail.pop_back(); + } + } + + void reset() { + m_scopes.reset(); + m_trail.reset(); + m_set.reset(); + } + + iterator begin() const { + return m_set.begin(); + } + + iterator end() const { + return m_set.end(); + } +}; + +#endif diff --git a/lib/base_simplifier.h b/lib/base_simplifier.h new file mode 100644 index 000000000..4afe5c9d7 --- /dev/null +++ b/lib/base_simplifier.h @@ -0,0 +1,54 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + base_simplifier.h + +Abstract: + + Base class for expression simplifier functors. + +Author: + + Leonardo (leonardo) 2008-01-11 + +Notes: + +--*/ +#ifndef _BASE_SIMPLIFIER_H_ +#define _BASE_SIMPLIFIER_H_ + +#include"expr_map.h" + +/** + \brief Implements basic functionality used by expression simplifiers. +*/ +class base_simplifier { +protected: + ast_manager & m_manager; + expr_map m_cache; + ptr_vector m_todo; + + void cache_result(expr * n, expr * r, proof * p) { m_cache.insert(n, r, p); } + void reset_cache() { m_cache.reset(); } + void flush_cache() { m_cache.flush(); } + void get_cached(expr * n, expr * & r, proof * & p) const { m_cache.get(n, r, p); } + + void visit(expr * n, bool & visited) { + if (!is_cached(n)) { + m_todo.push_back(n); + visited = false; + } + } + +public: + base_simplifier(ast_manager & m): + m_manager(m), + m_cache(m, m.fine_grain_proofs()) { + } + bool is_cached(expr * n) const { return m_cache.contains(n); } + ast_manager & get_manager() { return m_manager; } +}; + +#endif /* _BASE_SIMPLIFIER_H_ */ diff --git a/lib/basic_cmds.cpp b/lib/basic_cmds.cpp new file mode 100644 index 000000000..e7eec649d --- /dev/null +++ b/lib/basic_cmds.cpp @@ -0,0 +1,766 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + basic_cmds.cpp + +Abstract: + Basic commands for SMT2 front end. + +Author: + + Leonardo (leonardo) 2011-03-30 + +Notes: + +--*/ +#include"cmd_context.h" +#include"version.h" +#include"ast_smt_pp.h" +#include"ast_smt2_pp.h" +#include"ast_pp.h" +#include"model_smt2_pp.h" +#include"model_v2_pp.h" +#include"array_decl_plugin.h" +#include"pp.h" +#include"cmd_util.h" +#include"simplify_cmd.h" +#include"eval_cmd.h" +#include"qe_cmd.h" + +class help_cmd : public cmd { + svector m_cmds; + void display_cmd(cmd_context & ctx, symbol const & s, cmd * c) { + char const * usage = c->get_usage(); + char const * descr = c->get_descr(ctx); + ctx.regular_stream() << " (" << s; + if (usage) + ctx.regular_stream() << " " << escaped(usage, true) << ")\n"; + else + ctx.regular_stream() << ")\n"; + if (descr) { + ctx.regular_stream() << " " << escaped(descr, true, 4) << "\n"; + } + } + +public: + help_cmd():cmd("help") {} + virtual char const * get_usage() const { return "*"; } + virtual char const * get_descr(cmd_context & ctx) const { return "print this help."; } + virtual unsigned get_arity() const { return VAR_ARITY; } + virtual void prepare(cmd_context & ctx) { m_cmds.reset(); } + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { return CPK_SYMBOL; } + virtual void set_next_arg(cmd_context & ctx, symbol const & s) { + cmd * c = ctx.find_cmd(s); + if (c == 0) { + std::string err_msg("unknown command '"); + err_msg = err_msg + s.bare_str() + "'"; + throw cmd_exception(err_msg); + } + m_cmds.push_back(s); + } + + typedef std::pair named_cmd; + struct named_cmd_lt { + bool operator()(named_cmd const & c1, named_cmd const & c2) const { return c1.first.str() < c2.first.str(); } + }; + + virtual void execute(cmd_context & ctx) { + ctx.regular_stream() << "\""; + if (m_cmds.empty()) { + vector cmds; + cmd_context::cmd_iterator it = ctx.begin_cmds(); + cmd_context::cmd_iterator end = ctx.end_cmds(); + for (; it != end; ++it) { + cmds.push_back(named_cmd((*it).m_key, (*it).m_value)); + } + // named_cmd_lt is not a total order for commands, but this is irrelevant for Linux x Windows behavior + std::sort(cmds.begin(), cmds.end(), named_cmd_lt()); + vector::const_iterator it2 = cmds.begin(); + vector::const_iterator end2 = cmds.end(); + for (; it2 != end2; ++it2) { + display_cmd(ctx, it2->first, it2->second); + } + } + else { + svector::const_iterator it = m_cmds.begin(); + svector::const_iterator end = m_cmds.end(); + for (; it != end; ++it) { + cmd * c = ctx.find_cmd(*it); + SASSERT(c); + display_cmd(ctx, *it, c); + } + } + ctx.regular_stream() << "\"\n"; + } +}; + +ATOMIC_CMD(exit_cmd, "exit", "exit.", ctx.print_success(); throw stop_parser_exception();); + +ATOMIC_CMD(get_model_cmd, "get-model", "retrieve model for the last check-sat command", { + if (!ctx.is_model_available() || ctx.get_check_sat_result() == 0) + throw cmd_exception("model is not available"); + model_ref m; + ctx.get_check_sat_result()->get_model(m); + if (ctx.params().m_model_v1_pp || ctx.params().m_model_v2_pp) { + std::ostringstream buffer; + model_v2_pp(buffer, *m, ctx.params().m_model_partial); + ctx.regular_stream() << "\"" << escaped(buffer.str().c_str(), true) << "\"" << std::endl; + } else { + ctx.regular_stream() << "(model " << std::endl; + model_smt2_pp(ctx.regular_stream(), ctx, *(m.get()), 2); + // m->display(ctx.regular_stream()); + ctx.regular_stream() << ")" << std::endl; + } +}); + +ATOMIC_CMD(get_assignment_cmd, "get-assignment", "retrieve assignment", { + if (!ctx.is_model_available() || ctx.get_check_sat_result() == 0) + throw cmd_exception("model is not available"); + model_ref m; + ctx.get_check_sat_result()->get_model(m); + ctx.regular_stream() << "("; + dictionary const & macros = ctx.get_macros(); + dictionary::iterator it = macros.begin(); + dictionary::iterator end = macros.end(); + for (bool first = true; it != end; ++it) { + symbol const & name = (*it).m_key; + cmd_context::macro const & _m = (*it).m_value; + if (_m.first == 0 && ctx.m().is_bool(_m.second)) { + expr_ref val(ctx.m()); + m->eval(_m.second, val, true); + if (ctx.m().is_true(val) || ctx.m().is_false(val)) { + if (first) + first = false; + else + ctx.regular_stream() << " "; + ctx.regular_stream() << "(" << name << " " << (ctx.m().is_true(val) ? "true" : "false") << ")"; + } + } + } + ctx.regular_stream() << ")" << std::endl; +}); + +class cmd_is_declared : public ast_smt_pp::is_declared { + cmd_context& m_ctx; +public: + cmd_is_declared(cmd_context& ctx): m_ctx(ctx) {} + + virtual bool operator()(func_decl* d) const { + return m_ctx.is_func_decl(d->get_name()); + } + virtual bool operator()(sort* s) const { + return m_ctx.is_sort_decl(s->get_name()); + } +}; + +ATOMIC_CMD(get_proof_cmd, "get-proof", "retrieve proof", { + if (ctx.params().m_proof_mode == PGM_DISABLED) + throw cmd_exception("proof construction is not enabled, use command (set-option :produce-proofs true)"); + if (!ctx.has_manager() || + ctx.cs_state() != cmd_context::css_unsat) + throw cmd_exception("proof is not available"); + expr_ref pr(ctx.m()); + pr = ctx.get_check_sat_result()->get_proof(); + if (pr == 0) + throw cmd_exception("proof is not available"); + // TODO: reimplement a new SMT2 pretty printer + ast_smt_pp pp(ctx.m()); + cmd_is_declared isd(ctx); + pp.set_is_declared(&isd); + pp.set_logic(ctx.get_logic().str().c_str()); + pp.display_smt2(ctx.regular_stream(), pr); + ctx.regular_stream() << std::endl; +}); + +ATOMIC_CMD(get_unsat_core_cmd, "get-unsat-core", "retrieve unsat core", { + if (!ctx.produce_unsat_cores()) + throw cmd_exception("unsat core construction is not enabled, use command (set-option :produce-unsat-cores true)"); + if (!ctx.has_manager() || + ctx.cs_state() != cmd_context::css_unsat) + throw cmd_exception("unsat core is not available"); + ptr_vector core; + ctx.get_check_sat_result()->get_unsat_core(core); + ctx.regular_stream() << "("; + ptr_vector::const_iterator it = core.begin(); + ptr_vector::const_iterator end = core.end(); + for (bool first = true; it != end; ++it) { + if (first) + first = false; + else + ctx.regular_stream() << " "; + ctx.regular_stream() << mk_ismt2_pp(*it, ctx.m()); + } + ctx.regular_stream() << ")" << std::endl; +}); + +ATOMIC_CMD(labels_cmd, "labels", "retrieve Simplify-like labels", { + if (!ctx.has_manager() || + (ctx.cs_state() != cmd_context::css_sat && ctx.cs_state() != cmd_context::css_unknown)) + throw cmd_exception("labels are not available"); + svector labels; + ctx.get_check_sat_result()->get_labels(labels); + ctx.regular_stream() << "(labels"; + for (unsigned i = 0; i < labels.size(); i++) { + ctx.regular_stream() << " " << labels[i]; + } + ctx.regular_stream() << ")" << std::endl; +}); + +ATOMIC_CMD(get_assertions_cmd, "get-assertions", "retrieve asserted terms when in interactive mode", ctx.display_assertions();); + +UNARY_CMD(set_logic_cmd, "set-logic", "", "set the background logic.", CPK_SYMBOL, symbol const &, ctx.set_logic(arg); ctx.print_success();); + +UNARY_CMD(pp_cmd, "display", "", "display the given term.", CPK_EXPR, expr *, { + ctx.display(ctx.regular_stream(), arg); + ctx.regular_stream() << std::endl; +}); + +UNARY_CMD(echo_cmd, "echo", "", "display the given string", CPK_STRING, char const *, ctx.regular_stream() << arg << std::endl;); + +/** + \brief Convert a keyword into an internal Z3 option name +*/ +std::string smt_keyword2opt_name(symbol const & opt) { + std::string r; + SASSERT(opt.bare_str()[0] == ':'); + r = opt.bare_str() + 1; + unsigned sz = static_cast(r.size()); + for (unsigned i = 0; i < sz; i++) { + char curr = r[i]; + if ('a' <= curr && curr <= 'z') + r[i] = 'A' + (curr - 'a'); + else if (curr == '-') + r[i] = '_'; + } + TRACE("smt2_opt_name", tout << opt << " -> '" << r << "'\n";); + return r; +} + +class set_get_option_cmd : public cmd { +protected: + symbol m_true; + symbol m_false; + + symbol m_print_success; + symbol m_print_warning; + symbol m_expand_definitions; + symbol m_interactive_mode; + symbol m_produce_proofs; + symbol m_produce_unsat_cores; + symbol m_produce_models; + symbol m_produce_assignments; + symbol m_regular_output_channel; + symbol m_diagnostic_output_channel; + symbol m_random_seed; + symbol m_verbosity; + symbol m_global_decls; + symbol m_numeral_as_real; + symbol m_error_behavior; + ini_params m_ini; + + bool is_builtin_option(symbol const & s) const { + return + s == m_print_success || s == m_print_warning || s == m_expand_definitions || + s == m_interactive_mode || s == m_produce_proofs || s == m_produce_unsat_cores || + s == m_produce_models || s == m_produce_assignments || s == m_regular_output_channel || s == m_diagnostic_output_channel || + s == m_random_seed || s == m_verbosity || s == m_global_decls; + } + +public: + set_get_option_cmd(char const * name, front_end_params & params): + cmd(name), + m_true("true"), + m_false("false"), + m_print_success(":print-success"), + m_print_warning(":print-warning"), + m_expand_definitions(":expand-definitions"), + m_interactive_mode(":interactive-mode"), + m_produce_proofs(":produce-proofs"), + m_produce_unsat_cores(":produce-unsat-cores"), + m_produce_models(":produce-models"), + m_produce_assignments(":produce-assignments"), + m_regular_output_channel(":regular-output-channel"), + m_diagnostic_output_channel(":diagnostic-output-channel"), + m_random_seed(":random-seed"), + m_verbosity(":verbosity"), + m_global_decls(":global-decls"), + m_numeral_as_real(":numeral-as-real"), + m_error_behavior(":error-behavior"), + m_ini(false) { + params.register_params(m_ini); + register_pp_params(m_ini); + } + virtual ~set_get_option_cmd() {} + +}; + +class set_option_cmd : public set_get_option_cmd { + bool m_unsupported; + symbol m_option; + + bool to_bool(symbol const & value) const { + if (value != m_true && value != m_false) + throw cmd_exception("invalid option value, true/false expected"); + return value == m_true; + } + + static unsigned to_unsigned(rational const & val) { + if (!val.is_unsigned()) + throw cmd_exception("option value is too big to fit in a machine integer."); + return val.get_unsigned(); + } + + static void check_not_initialized(cmd_context & ctx, symbol const & opt_name) { + if (ctx.has_manager()) { + std::string msg = "error setting '"; + msg += opt_name.str(); + msg += "', option value cannot be modified after initialization"; + throw cmd_exception(msg); + } + } + + void set_param(cmd_context & ctx, char const * value) { + m_ini.freeze(ctx.has_manager()); + std::string internal_opt = smt_keyword2opt_name(m_option); + try { + std::string old_value; + if (!m_ini.get_param_value(internal_opt.c_str(), old_value)) { + m_unsupported = true; + return; + } + m_ini.set_param_value(internal_opt.c_str(), value); + } + catch (set_get_param_exception ex) { + std::string msg = "error setting '"; + msg += m_option.str(); + msg += "', "; + msg += ex.get_msg(); + throw cmd_exception(msg); + } + } + + void set_symbol(cmd_context & ctx, symbol const & value) { + if (m_option == m_print_success) { + ctx.set_print_success(to_bool(value)); + } + else if (m_option == m_print_warning) { + enable_warning_messages(to_bool(value)); + } + else if (m_option == m_expand_definitions) { + m_unsupported = true; + } + else if (m_option == m_interactive_mode) { + check_not_initialized(ctx, m_interactive_mode); + ctx.set_interactive_mode(to_bool(value)); + } + else if (m_option == m_produce_proofs) { + check_not_initialized(ctx, m_produce_proofs); + ctx.params().m_proof_mode = to_bool(value) ? PGM_FINE : PGM_DISABLED; + } + else if (m_option == m_produce_unsat_cores) { + check_not_initialized(ctx, m_produce_unsat_cores); + ctx.set_produce_unsat_cores(to_bool(value)); + } + else if (m_option == m_produce_models) { + ctx.params().m_model = to_bool(value); + } + else if (m_option == m_produce_assignments) { + ctx.set_produce_assignments(to_bool(value)); + } + else if (m_option == m_global_decls) { + check_not_initialized(ctx, m_global_decls); + ctx.set_global_decls(to_bool(value)); + } + else if (m_option == m_numeral_as_real) { + ctx.set_numeral_as_real(to_bool(value)); + } +#ifdef Z3DEBUG + else if (m_option == ":enable-assertions") { + enable_assertions(to_bool(value)); + } +#endif + else if (m_option == m_error_behavior) { + if (value == "immediate-exit") { + ctx.set_exit_on_error(true); + } + else if (value == "continued-execution") { + ctx.set_exit_on_error(false); + } + else { + throw cmd_exception("error setting :error-behavior, 'immediate-execution' or 'continued-execution' expected"); + } + } + else if (is_builtin_option(m_option)) { + throw cmd_exception("option value is not a symbol"); + } + else { + set_param(ctx, value.bare_str()); + } + } + +public: + set_option_cmd(front_end_params & params): + set_get_option_cmd("set-option", params), + m_unsupported(false) { + } + + virtual char const * get_usage() const { return " "; } + + virtual char const * get_descr(cmd_context & ctx) const { return "set configuration option."; } + + virtual unsigned get_arity() const { return 2; } + + virtual void prepare(cmd_context & ctx) { m_unsupported = false; m_option = symbol::null; } + + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { + return m_option == symbol::null ? CPK_KEYWORD : CPK_OPTION_VALUE; + } + + virtual void set_next_arg(cmd_context & ctx, symbol const & opt) { + if (m_option == symbol::null) { + m_option = opt; + } + else { + set_symbol(ctx, opt); + } + } + + virtual void set_next_arg(cmd_context & ctx, rational const & val) { + if (m_option == m_random_seed) { + ctx.set_random_seed(to_unsigned(val)); + } + else if (m_option == m_verbosity) { + set_verbosity_level(to_unsigned(val)); + } + else if (is_builtin_option(m_option)) { + throw cmd_exception("option value is not a numeral"); + } + else { + std::string str = val.to_string(); + set_param(ctx, str.c_str()); + } + } + + virtual void set_next_arg(cmd_context & ctx, char const * value) { + if (m_option == m_regular_output_channel) { + ctx.set_regular_stream(value); + } + else if (m_option == m_diagnostic_output_channel) { + ctx.set_diagnostic_stream(value); + } + else if (is_builtin_option(m_option)) { + throw cmd_exception("option value is not a string"); + } + else { + set_param(ctx, value); + } + } + + virtual void execute(cmd_context & ctx) { + if (m_unsupported) + ctx.print_unsupported(m_option); + else + ctx.print_success(); + } +}; + +class get_option_cmd : public set_get_option_cmd { + static void print_bool(cmd_context & ctx, bool b) { + ctx.regular_stream() << (b ? "true" : "false") << std::endl; + } + + static void print_unsigned(cmd_context & ctx, unsigned v) { + ctx.regular_stream() << v << std::endl; + } + + static void print_string(cmd_context & ctx, char const * str) { + ctx.regular_stream() << str << std::endl; + } + +public: + get_option_cmd(front_end_params & params): + set_get_option_cmd("get-option", params) { + } + virtual char const * get_usage() const { return ""; } + virtual char const * get_descr(cmd_context & ctx) const { return "get configuration option."; } + virtual unsigned get_arity() const { return 1; } + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { return CPK_KEYWORD; } + virtual void set_next_arg(cmd_context & ctx, symbol const & opt) { + if (opt == m_print_success) { + print_bool(ctx, ctx.print_success_enabled()); + } + // TODO: + // else if (opt == m_print_warning) { + // print_bool(ctx, ); + // } + else if (opt == m_expand_definitions) { + ctx.print_unsupported(m_expand_definitions); + } + else if (opt == m_interactive_mode) { + print_bool(ctx, ctx.interactive_mode()); + } + else if (opt == m_produce_proofs) { + print_bool(ctx, ctx.params().m_proof_mode != PGM_DISABLED); + } + else if (opt == m_produce_unsat_cores) { + print_bool(ctx, ctx.produce_unsat_cores()); + } + else if (opt == m_produce_models) { + print_bool(ctx, ctx.params().m_model); + } + else if (opt == m_produce_assignments) { + print_bool(ctx, ctx.produce_assignments()); + } + else if (opt == m_global_decls) { + print_bool(ctx, ctx.global_decls()); + } + else if (opt == m_random_seed) { + print_unsigned(ctx, ctx.random_seed()); + } + else if (opt == m_verbosity) { + print_unsigned(ctx, get_verbosity_level()); + } + else if (opt == m_regular_output_channel) { + print_string(ctx, ctx.get_regular_stream_name()); + } + else if (opt == m_diagnostic_output_channel) { + print_string(ctx, ctx.get_diagnostic_stream_name()); + } + else if (opt == m_error_behavior) { + if (ctx.exit_on_error()) { + print_string(ctx, "immediate-exit"); + } + else { + print_string(ctx, "continued-execution"); + } + } + else { + std::string iopt = smt_keyword2opt_name(opt); + std::string r; + if (m_ini.get_param_value(iopt.c_str(), r)) { + ctx.regular_stream() << r << std::endl; + } + else { + ctx.print_unsupported(opt); + } + } + } +}; + +class get_info_cmd : public cmd { + symbol m_error_behavior; + symbol m_name; + symbol m_authors; + symbol m_version; + symbol m_status; + symbol m_reason_unknown; + symbol m_all_statistics; +public: + get_info_cmd(): + cmd("get-info"), + m_error_behavior(":error-behavior"), + m_name(":name"), + m_authors(":authors"), + m_version(":version"), + m_status(":status"), + m_reason_unknown(":reason-unknown"), + m_all_statistics(":all-statistics") { + } + virtual char const * get_usage() const { return ""; } + virtual char const * get_descr(cmd_context & ctx) const { return "get information."; } + virtual unsigned get_arity() const { return 1; } + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { return CPK_KEYWORD; } + virtual void set_next_arg(cmd_context & ctx, symbol const & opt) { + if (opt == m_error_behavior) { + if (ctx.exit_on_error()) + ctx.regular_stream() << "(:error-behavior immediate-exit)" << std::endl; + else + ctx.regular_stream() << "(:error-behavior continued-execution)" << std::endl; + } + else if (opt == m_name) { + ctx.regular_stream() << "(:name \"Z3\")" << std::endl; + } + else if (opt == m_authors) { + ctx.regular_stream() << "(:authors \"Leonardo de Moura and Nikolaj Bjorner\")" << std::endl; + } + else if (opt == m_version) { + ctx.regular_stream() << "(:version \"" << Z3_MAJOR_VERSION << "." << Z3_MINOR_VERSION << "\")" << std::endl; + } + else if (opt == m_status) { + ctx.regular_stream() << "(:status " << ctx.get_status() << ")" << std::endl; + } + else if (opt == m_reason_unknown) { + ctx.regular_stream() << "(:reason-unknown " << ctx.reason_unknown() << ")" << std::endl; + } + else if (opt == m_all_statistics) { + ctx.display_statistics(); + } + else { + ctx.print_unsupported(opt); + } + } +}; + +class set_info_cmd : public cmd { + symbol m_info; + symbol m_status; + symbol m_unsat; + symbol m_sat; + symbol m_unknown; +public: + set_info_cmd(): + cmd("set-info"), + m_status(":status"), + m_unsat("unsat"), + m_sat("sat"), + m_unknown("unknown") { + } + virtual char const * get_usage() const { return " "; } + virtual char const * get_descr(cmd_context & ctx) const { return "set information."; } + virtual unsigned get_arity() const { return 2; } + virtual void prepare(cmd_context & ctx) { m_info = symbol::null; } + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { + return m_info == symbol::null ? CPK_KEYWORD : CPK_OPTION_VALUE; + } + virtual void set_next_arg(cmd_context & ctx, rational const & val) {} + virtual void set_next_arg(cmd_context & ctx, char const * val) {} + virtual void set_next_arg(cmd_context & ctx, symbol const & s) { + if (m_info == symbol::null) { + m_info = s; + } + else { + if (m_info == m_status) { + if (s == m_unsat) { + ctx.set_status(cmd_context::UNSAT); + } + else if (s == m_sat) { + ctx.set_status(cmd_context::SAT); + } + else if (s == m_unknown) { + ctx.set_status(cmd_context::UNKNOWN); + } + else { + throw cmd_exception("invalid ':status' attribute"); + } + } + } + } + virtual void execute(cmd_context & ctx) { + ctx.print_success(); + } +}; + +class declare_map_cmd : public cmd { + symbol m_array_sort; + symbol m_name; + ptr_vector m_domain; + func_decl * m_f; + family_id m_array_fid; +public: + declare_map_cmd(): + cmd("declare-map"), + m_array_sort("Array"), + m_array_fid(null_family_id) {} + + family_id get_array_fid(cmd_context & ctx) { + if (m_array_fid == null_family_id) { + m_array_fid = ctx.m().get_family_id("array"); + } + return m_array_fid; + } + virtual char const * get_usage() const { return " (+) "; } + virtual char const * get_descr(cmd_context & ctx) const { return "declare a new array map operator with name using the given function declaration.\n ::= \n | ( (*) )\n | ((_ +) (*) )\nThe last two cases are used to disumbiguate between declarations with the same name and/or select (indexed) builtin declarations.\nFor more details about the the array map operator, see 'Generalized and Efficient Array Decision Procedures' (FMCAD 2009).\nExample: (declare-map set-union (Int) (or (Bool Bool) Bool))\nDeclares a new function (declare-fun set-union ((Array Int Bool) (Array Int Bool)) (Array Int Bool)).\nThe instance of the map axiom for this new declaration is:\n(forall ((a1 (Array Int Bool)) (a2 (Array Int Bool)) (i Int)) (= (select (set-union a1 a2) i) (or (select a1 i) (select a2 i))))"; } + virtual unsigned get_arity() const { return 3; } + virtual void prepare(cmd_context & ctx) { m_name = symbol::null; m_domain.reset(); } + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { + if (m_name == symbol::null) return CPK_SYMBOL; + if (m_domain.empty()) return CPK_SORT_LIST; + return CPK_FUNC_DECL; + } + virtual void set_next_arg(cmd_context & ctx, symbol const & s) { m_name = s; } + virtual void set_next_arg(cmd_context & ctx, unsigned num, sort * const * slist) { + if (num == 0) + throw cmd_exception("invalid map declaration, empty sort list"); + m_domain.append(num, slist); + } + virtual void set_next_arg(cmd_context & ctx, func_decl * f) { + m_f = f; + if (m_f->get_arity() == 0) + throw cmd_exception("invalid map declaration, function declaration must have arity > 0"); + } + virtual void reset(cmd_context & ctx) { + m_array_fid = null_family_id; + } + virtual void execute(cmd_context & ctx) { + psort_decl * array_sort = ctx.find_psort_decl(m_array_sort); + if (array_sort == 0) + throw cmd_exception("Array sort is not available"); + ptr_vector & array_sort_args = m_domain; + sort_ref_buffer domain(ctx.m()); + unsigned arity = m_f->get_arity(); + for (unsigned i = 0; i < arity; i++) { + array_sort_args.push_back(m_f->get_domain(i)); + domain.push_back(array_sort->instantiate(ctx.pm(), array_sort_args.size(), array_sort_args.c_ptr())); + array_sort_args.pop_back(); + } + sort_ref range(ctx.m()); + array_sort_args.push_back(m_f->get_range()); + range = array_sort->instantiate(ctx.pm(), array_sort_args.size(), array_sort_args.c_ptr()); + parameter p[1] = { parameter(m_f) }; + func_decl_ref new_map(ctx.m()); + new_map = ctx.m().mk_func_decl(get_array_fid(ctx), OP_ARRAY_MAP, 1, p, domain.size(), domain.c_ptr(), range.get()); + if (new_map == 0) + throw cmd_exception("invalid array map operator"); + ctx.insert(m_name, new_map); + } +}; + +// provides "help" for builtin cmds +class builtin_cmd : public cmd { + char const * m_usage; + char const * m_descr; +public: + builtin_cmd(char const * name, char const * usage, char const * descr): + cmd(name), m_usage(usage), m_descr(descr) {} + virtual char const * get_usage() const { return m_usage; } + virtual char const * get_descr(cmd_context & ctx) const { return m_descr; } +}; + + +void install_basic_cmds(cmd_context & ctx) { + ctx.insert(alloc(set_logic_cmd)); + ctx.insert(alloc(exit_cmd)); + ctx.insert(alloc(get_assignment_cmd)); + ctx.insert(alloc(get_assertions_cmd)); + ctx.insert(alloc(get_proof_cmd)); + ctx.insert(alloc(get_unsat_core_cmd)); + ctx.insert(alloc(set_option_cmd, ctx.params())); + ctx.insert(alloc(get_option_cmd, ctx.params())); + ctx.insert(alloc(get_info_cmd)); + ctx.insert(alloc(set_info_cmd)); + ctx.insert(alloc(builtin_cmd, "assert", "", "assert term.")); + ctx.insert(alloc(builtin_cmd, "check-sat", "*", "check if the current context is satisfiable. If a list of boolean constants B is provided, then check if the current context is consistent with assigning every constant in B to true.")); + ctx.insert(alloc(builtin_cmd, "push", "?", "push 1 (or ) scopes.")); + ctx.insert(alloc(builtin_cmd, "pop", "?", "pop 1 (or ) scopes.")); + ctx.insert(alloc(builtin_cmd, "get-value", "(+)", "evaluate the given terms in the current model.")); + ctx.insert(alloc(builtin_cmd, "declare-sort", " ?", "declare a new uninterpreted sort of arity , if arity is not provided, then it is assumed to be 0.")); + ctx.insert(alloc(builtin_cmd, "define-sort", " (*) ", "define a new sort.")); + ctx.insert(alloc(builtin_cmd, "declare-fun", " (*) ", "declare a new function/constant.")); + ctx.insert(alloc(builtin_cmd, "declare-const", " ", "declare a new constant.")); + ctx.insert(alloc(builtin_cmd, "declare-datatypes", "(*) (+)", "declare mutually recursive datatypes.\n ::= ( +)\n ::= ( *)\n ::= ( )\nexample: (declare-datatypes (T) ((BinTree (leaf (value T)) (node (left BinTree) (right BinTree)))))")); +} + +void install_ext_basic_cmds(cmd_context & ctx) { + ctx.insert(alloc(help_cmd)); + ctx.insert(alloc(pp_cmd)); + ctx.insert(alloc(get_model_cmd)); + ctx.insert(alloc(echo_cmd)); + ctx.insert(alloc(labels_cmd)); + ctx.insert(alloc(declare_map_cmd)); + ctx.insert(alloc(builtin_cmd, "reset", 0, "reset the shell (all declarations and assertions will be erased)")); + install_simplify_cmd(ctx); + install_eval_cmd(ctx); + install_qe_cmd(ctx); +} diff --git a/lib/basic_cmds.h b/lib/basic_cmds.h new file mode 100644 index 000000000..554c1ab31 --- /dev/null +++ b/lib/basic_cmds.h @@ -0,0 +1,26 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + basic_cmds.h + +Abstract: + Basic commands for SMT2 front end. + +Author: + + Leonardo (leonardo) 2011-03-30 + +Notes: + +--*/ +#ifndef _BASIC_CMDS_H_ +#define _BASIC_CMDS_H_ + +class cmd_context; + +void install_basic_cmds(cmd_context & ctx); +void install_ext_basic_cmds(cmd_context & ctx); + +#endif diff --git a/lib/basic_interval.h b/lib/basic_interval.h new file mode 100644 index 000000000..2bf5a8f30 --- /dev/null +++ b/lib/basic_interval.h @@ -0,0 +1,363 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + basic_interval.h + +Abstract: + + Basic interval arithmetic template for precise numerals: mpz, mpq, mpbq. + Only basic support is provided. + There is no support for: + - minus and plus infinity bounds. + - mixed open/closed intervals such as (2, 3] + The main customer of this package is the algebraic_number module. + +Author: + + Leonardo de Moura (leonardo) 2012-12-04. + +Revision History: + +--*/ +#ifndef _BASIC_INTERVAL_H_ +#define _BASIC_INTERVAL_H_ + +template +class basic_interval_manager { +public: + typedef typename numeral_manager::numeral bound; + + class interval { + friend class basic_interval_manager; + bound m_lower; + bound m_upper; + public: + interval() {} + bound const & lower() const { return m_lower; } + bound const & upper() const { return m_upper; } + bound & lower() { return m_lower; } + bound & upper() { return m_upper; } + }; + + class scoped_interval { + basic_interval_manager & m_manager; + interval m_interval; + public: + scoped_interval(basic_interval_manager & m):m_manager(m) {} + ~scoped_interval() { m_manager.del(m_interval); } + + basic_interval_manager & m() const { return m_manager; } + operator interval const &() const { return m_interval; } + operator interval&() { return m_interval; } + interval const & get() const { return m_interval; } + interval & get() { return m_interval; } + void reset() { m().reset(m_interval); } + void swap(scoped_interval & a) { m().swap(m_interval, a.m_interval); } + void swap(interval & a) { m().swap(m_interval, a); } + bound const & lower() const { return m_interval.lower(); } + bound const & upper() const { return m_interval.upper(); } + bound & lower() { return m_interval.lower(); } + bound & upper() { return m_interval.upper(); } + + friend std::ostream & operator<<(std::ostream & out, scoped_interval const & a) { + a.m().display(out, a.get()); + return out; + } + }; + +protected: + numeral_manager & m_manager; + bound m_mul_curr; + bound m_mul_max; + bound m_mul_min; + +public: + typedef interval numeral; // allow intervals to be used by algorithms parameterized by numeral_manager + + basic_interval_manager(numeral_manager & m): + m_manager(m) { + } + + ~basic_interval_manager() { + m().del(m_mul_curr); + m().del(m_mul_max); + m().del(m_mul_min); + } + + numeral_manager & m() const { return m_manager; } + + /** + \brief Delete interval + */ + void del(interval & a) { + m().del(a.m_lower); + m().del(a.m_upper); + } + + /** + \brief Delete and reset lower and upper bounds to 0 + */ + void reset(interval & a) { + m().reset(a.m_lower); + m().reset(a.m_upper); + } + + bound const & lower(interval const & a) { + return a.lower(); + } + + bound const & upper(interval const & a) { + return a.upper(); + } + + /** + \brief a <- (lower, upper) + */ + void set(interval & a, bound const & lower, bound const & upper) { + SASSERT(m().le(lower, upper)); + m().set(a.m_lower, lower); + m().set(a.m_upper, upper); + } + + /** + \brief a <- b + */ + void set(interval & a, interval const & b) { + set(a, b.m_lower, b.m_upper); + } + + /** + \brief a <- (n, n) + + Manager must be configured for closed intervals. + */ + void set(interval & a, bound const & n) { + m().set(a.m_lower, n); + m().set(a.m_upper, n); + } + + void set_lower(interval & a, bound const & n) { + SASSERT(m().le(n, a.m_upper)); + m().set(a.m_lower, n); + } + + void set_upper(interval & a, bound const & n) { + SASSERT(m().le(a.m_lower, n)); + m().set(a.m_upper, n); + } + + void swap(interval & a, interval & b) { + m().swap(a.m_lower, b.m_lower); + m().swap(a.m_upper, b.m_upper); + } + + /** + \brief a <- -a + */ + void neg(interval & a) { + m().neg(a.m_lower); + m().neg(a.m_upper); + m().swap(a.m_lower, a.m_upper); + } + + /** + \brief Return true if a does not contain any value. We can + only have empty intervals if the manager is configured to used + open intervals. + */ + bool is_empty(interval const & a) { + return !closed && m().eq(a.m_lower, a.m_upper); + } + + /** + \brief Return true if all values in the given interval are positive. + */ + bool is_pos(interval const & a) { return (closed && m().is_pos(a.m_lower)) || (!closed && m().is_nonneg(a.m_lower)); } + + /** + \brief Return true if all values in the given interval are negative. + */ + bool is_neg(interval const & a) { return (closed && m().is_neg(a.m_upper)) || (!closed && m().is_nonpos(a.m_upper)); } + + /** + \brief Return true if 0 is in the interval. + */ + bool contains_zero(interval const & a) { + return + (closed && m().is_nonpos(a.m_lower) && m().is_nonneg(a.m_upper)) || + (!closed && m().is_neg(a.m_lower) && m().is_pos(a.m_upper)); + } + + /** + \brief Return true if all values in interval a are in interval b. + */ + bool is_subset(interval const & a, interval const & b) { + return m().le(b.m_lower, a.m_lower) && m().le(a.m_upper, b.m_upper); + } + + /** + \brief Return true if there is no value v s.t. v \in a and v \in b. + */ + bool disjoint(interval const & a, interval const & b) { + return + (closed && (m().lt(a.m_upper, b.m_lower) || m().lt(b.m_upper, a.m_lower))) || + (!closed && (m().le(a.m_upper, b.m_upper) || m().le(b.m_upper, a.m_lower))); + } + + /** + \brief Return true if all elements in a are smaller than all elements in b. + */ + bool precedes(interval const & a, interval const & b) { + return + (closed && (m().lt(a.m_upper, b.m_lower))) || + (!closed && (m().le(a.m_upper, b.m_lower))); + } + + /** + \brief Return true if all elements in a are smaller than b. + */ + bool precedes(interval const & a, bound const & b) { + return + (closed && (m().lt(a.m_upper, b))) || + (!closed && (m().le(a.m_upper, b))); + } + + /** + \brief Return true if a is smaller than all elements in b. + */ + bool precedes(bound const & a, interval const & b) { + return + (closed && (m().lt(a, b.m_lower))) || + (!closed && (m().le(a, b.m_lower))); + } + + /** + \brief a <- 1/a + + \pre a.m_lower and m_upper must not be 0. + \pre bound must be a field. + */ + void inv(interval & a) { + SASSERT(numeral_manager::field()); + SASSERT(!contains_zero(a)); + SASSERT(!m().is_zero(a.m_lower) && !m().is_zero(a.m_upper)); + m().inv(a.m_lower); + m().inv(a.m_upper); + m().swap(a.m_lower, a.m_upper); + } + + /** + \brief c <- a + b + */ + void add(interval const & a, interval const & b, interval & c) { + m().add(a.m_lower, b.m_lower, c.m_lower); + m().add(a.m_upper, b.m_upper, c.m_upper); + } + + /** + \brief c <- a - b + */ + void sub(interval const & a, interval const & b, interval & c) { + m().sub(a.m_lower, b.m_lower, c.m_lower); + m().sub(a.m_upper, b.m_upper, c.m_upper); + } + +private: + /** + \brief Init the value of m_mul_max and m_mul_min using m_mul_curr + */ + void init_mul_max_min() { + m().set(m_mul_min, m_mul_curr); + m().swap(m_mul_max, m_mul_curr); + } + + /** + \brief Update the value of m_mul_max and m_mul_min using m_mul_curr + */ + void update_mul_max_min() { + if (m().lt(m_mul_curr, m_mul_min)) + m().set(m_mul_min, m_mul_curr); + if (m().gt(m_mul_curr, m_mul_max)) + m().swap(m_mul_max, m_mul_curr); + } + +public: + /** + \brief c <- a * b + */ + void mul(interval const & a, interval const & b, interval & c) { + m().mul(a.m_lower, b.m_lower, m_mul_curr); + init_mul_max_min(); + m().mul(a.m_lower, b.m_upper, m_mul_curr); + update_mul_max_min(); + m().mul(a.m_upper, b.m_lower, m_mul_curr); + update_mul_max_min(); + m().mul(a.m_upper, b.m_upper, m_mul_curr); + update_mul_max_min(); + m().swap(c.m_lower, m_mul_min); + m().swap(c.m_upper, m_mul_max); + } + + /** + \brief c <- a/b + + \pre b m_lower and m_upper must not be 0 + \pre bound must be a field. + */ + void div(interval const & a, interval const & b, interval & c) { + SASSERT(numeral_manager::field()); + SASSERT(!contains_zero(b)); + SASSERT(!m().is_zero(b.m_lower) && !m().is_zero(b.m_upper)); + m().div(a.m_lower, b.m_lower, m_mul_curr); + init_mul_max_min(); + m().div(a.m_lower, b.m_upper, m_mul_curr); + update_mul_max_min(); + m().div(a.m_upper, b.m_lower, m_mul_curr); + update_mul_max_min(); + m().div(a.m_upper, b.m_upper, m_mul_curr); + update_mul_max_min(); + m().swap(c.m_lower, m_mul_min); + m().swap(c.m_upper, m_mul_max); + } + + /** + \brief c <- a^n + */ + void power(interval const & a, unsigned n, interval & c) { + // Let a be of the form (l, u) + if (n % 2 == 1) { + // n is odd + // c <- (l^n, u^n) + m().power(a.m_lower, n, c.m_lower); + m().power(a.m_upper, n, c.m_upper); + } + else { + SASSERT(n % 2 == 0); + m().power(a.m_lower, n, c.m_lower); + m().power(a.m_upper, n, c.m_upper); + if (m().is_nonneg(a.m_lower)) { + // n is even and l >= 0 + // c <- (l^n, u^n) + return; + } + if (m().is_neg(a.m_upper)) { + // n is even and u < 0 + // c <- (u^n, l^n) + m().swap(c.m_lower, c.m_upper); + return; + } + // c <- (0, max(l^n, u^n)) + if (m().gt(c.m_lower, c.m_upper)) + m().swap(c.m_lower, c.m_upper); + m().reset(c.m_lower); + } + } + + void display(std::ostream & out, interval const & a) { + out << (closed ? "[" : "(") << m().to_string(a.m_lower) << ", " << m().to_string(a.m_upper) << (closed ? "]" : ")"); + } +}; + +#endif diff --git a/lib/basic_simplifier_plugin.cpp b/lib/basic_simplifier_plugin.cpp new file mode 100644 index 000000000..6c713bb10 --- /dev/null +++ b/lib/basic_simplifier_plugin.cpp @@ -0,0 +1,142 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + basic_simplifier_plugin.cpp + +Abstract: + + Simplifier for the basic family. + +Author: + + Leonardo (leonardo) 2008-01-07 + +--*/ +#include"basic_simplifier_plugin.h" +#include"ast_ll_pp.h" +#include"bool_rewriter.h" + +basic_simplifier_plugin::basic_simplifier_plugin(ast_manager & m): + simplifier_plugin(symbol("basic"), m), + m_rewriter(alloc(bool_rewriter, m)) { +} + +basic_simplifier_plugin::~basic_simplifier_plugin() { + dealloc(m_rewriter); +} + +bool basic_simplifier_plugin::reduce(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + set_reduce_invoked(); + SASSERT(f->get_family_id() == m_manager.get_basic_family_id()); + basic_op_kind k = static_cast(f->get_decl_kind()); + switch (k) { + case OP_FALSE: + case OP_TRUE: + return false; + case OP_EQ: + SASSERT(num_args == 2); + mk_eq(args[0], args[1], result); + return true; + case OP_DISTINCT: + mk_distinct(num_args, args, result); + return true; + case OP_ITE: + SASSERT(num_args == 3); + mk_ite(args[0], args[1], args[2], result); + return true; + case OP_AND: + mk_and(num_args, args, result); + return true; + case OP_OR: + mk_or(num_args, args, result); + return true; + case OP_IMPLIES: + mk_implies(args[0], args[1], result); + return true; + case OP_IFF: + mk_iff(args[0], args[1], result); + return true; + case OP_XOR: + mk_xor(args[0], args[1], result); + return true; + case OP_NOT: + SASSERT(num_args == 1); + mk_not(args[0], result); + return true; + default: + UNREACHABLE(); + return false; + } +} + +/** + \brief Return true if \c rhs is of the form (ite c t1 t2) and are_distinct(lhs, t1) and are_distinct(lhs, t2). +*/ +static bool is_lhs_diseq_rhs_ite_branches(ast_manager & m, expr * lhs, expr * rhs) { + return m.is_ite(rhs) && m.are_distinct(lhs, to_app(rhs)->get_arg(1)) && m.are_distinct(lhs, to_app(rhs)->get_arg(2)); +} + +/** + \brief Return true if \c rhs is of the form (ite c t1 t2) and lhs = t1 && are_distinct(lhs, t2) +*/ +static bool is_lhs_eq_rhs_ite_then(ast_manager & m, expr * lhs, expr * rhs) { + return m.is_ite(rhs) && lhs == to_app(rhs)->get_arg(1) && m.are_distinct(lhs, to_app(rhs)->get_arg(2)); +} + +/** + \brief Return true if \c rhs is of the form (ite c t1 t2) and are_distinct(lhs,t1) && lhs = t2 +*/ +static bool is_lhs_eq_rhs_ite_else(ast_manager & m, expr * lhs, expr * rhs) { + return m.is_ite(rhs) && lhs == to_app(rhs)->get_arg(2) && m.are_distinct(lhs, to_app(rhs)->get_arg(1)); +} + +void basic_simplifier_plugin::mk_eq(expr * lhs, expr * rhs, expr_ref & result) { + // (= t1 (ite C t2 t3)) --> false if are_distinct(t1, t2) && are_distinct(t1, t3) + if (is_lhs_diseq_rhs_ite_branches(m_manager, lhs, rhs) || is_lhs_diseq_rhs_ite_branches(m_manager, rhs, lhs)) { + result = m_manager.mk_false(); + } + // (= t1 (ite C t2 t3)) --> C if t1 = t2 && are_distinct(t1, t3) + else if (is_lhs_eq_rhs_ite_then(m_manager, lhs, rhs)) { + result = to_app(rhs)->get_arg(0); + } + // (= t1 (ite C t2 t3)) --> C if t1 = t2 && are_distinct(t1, t3) + else if (is_lhs_eq_rhs_ite_then(m_manager, rhs, lhs)) { + result = to_app(lhs)->get_arg(0); + } + // (= t1 (ite C t2 t3)) --> (not C) if t1 = t3 && are_distinct(t1, t2) + else if (is_lhs_eq_rhs_ite_else(m_manager, lhs, rhs)) { + mk_not(to_app(rhs)->get_arg(0), result); + } + // (= t1 (ite C t2 t3)) --> (not C) if t1 = t3 && are_distinct(t1, t2) + else if (is_lhs_eq_rhs_ite_else(m_manager, rhs, lhs)) { + mk_not(to_app(lhs)->get_arg(0), result); + } + else { + m_rewriter->mk_eq(lhs, rhs, result); + } +} + +bool basic_simplifier_plugin::eliminate_and() const { return m_rewriter->elim_and(); } +void basic_simplifier_plugin::set_eliminate_and(bool f) { m_rewriter->set_elim_and(f); } +void basic_simplifier_plugin::mk_iff(expr * lhs, expr * rhs, expr_ref & result) { mk_eq(lhs, rhs, result); } +void basic_simplifier_plugin::mk_xor(expr * lhs, expr * rhs, expr_ref & result) { m_rewriter->mk_xor(lhs, rhs, result); } +void basic_simplifier_plugin::mk_implies(expr * lhs, expr * rhs, expr_ref & result) { m_rewriter->mk_implies(lhs, rhs, result); } +void basic_simplifier_plugin::mk_ite(expr * c, expr * t, expr * e, expr_ref & result) { m_rewriter->mk_ite(c, t, e, result); } +void basic_simplifier_plugin::mk_and(unsigned num_args, expr * const * args, expr_ref & result) { m_rewriter->mk_and(num_args, args, result); } +void basic_simplifier_plugin::mk_or(unsigned num_args, expr * const * args, expr_ref & result) { m_rewriter->mk_or(num_args, args, result); } +void basic_simplifier_plugin::mk_and(expr * arg1, expr * arg2, expr_ref & result) { m_rewriter->mk_and(arg1, arg2, result); } +void basic_simplifier_plugin::mk_or(expr * arg1, expr * arg2, expr_ref & result) { m_rewriter->mk_or(arg1, arg2, result); } +void basic_simplifier_plugin::mk_and(expr * arg1, expr * arg2, expr * arg3, expr_ref & result) { m_rewriter->mk_and(arg1, arg2, arg3, result); } +void basic_simplifier_plugin::mk_or(expr * arg1, expr * arg2, expr * arg3, expr_ref & result) { m_rewriter->mk_or(arg1, arg2, arg3, result); } +void basic_simplifier_plugin::mk_nand(unsigned num_args, expr * const * args, expr_ref & result) { m_rewriter->mk_nand(num_args, args, result); } +void basic_simplifier_plugin::mk_nor(unsigned num_args, expr * const * args, expr_ref & result) { m_rewriter->mk_nor(num_args, args, result); } +void basic_simplifier_plugin::mk_nand(expr * arg1, expr * arg2, expr_ref & result) { m_rewriter->mk_nand(arg1, arg2, result); } +void basic_simplifier_plugin::mk_nor(expr * arg1, expr * arg2, expr_ref & result) { m_rewriter->mk_nor(arg1, arg2, result); } +void basic_simplifier_plugin::mk_distinct(unsigned num_args, expr * const * args, expr_ref & result) { m_rewriter->mk_distinct(num_args, args, result); } +void basic_simplifier_plugin::mk_not(expr * n, expr_ref & result) { m_rewriter->mk_not(n, result); } + +void basic_simplifier_plugin::enable_ac_support(bool flag) { + m_rewriter->set_flat(flag); +} diff --git a/lib/basic_simplifier_plugin.h b/lib/basic_simplifier_plugin.h new file mode 100644 index 000000000..d92e09d1d --- /dev/null +++ b/lib/basic_simplifier_plugin.h @@ -0,0 +1,78 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + basic_simplifier_plugin.h + +Abstract: + + Simplifier for the basic family. + +Author: + + Leonardo (leonardo) 2008-01-07 + +--*/ +#ifndef _BASIC_SIMPLIFIER_PLUGIN_H_ +#define _BASIC_SIMPLIFIER_PLUGIN_H_ + +#include"simplifier_plugin.h" + +class bool_rewriter; + +/** + \brief Simplifier for the basic family. +*/ +class basic_simplifier_plugin : public simplifier_plugin { + bool_rewriter * m_rewriter; +public: + basic_simplifier_plugin(ast_manager & m); + virtual ~basic_simplifier_plugin(); + bool_rewriter & get_rewriter() { return *m_rewriter; } + bool eliminate_and() const; + void set_eliminate_and(bool f); + void mk_eq(expr * lhs, expr * rhs, expr_ref & result); + void mk_iff(expr * lhs, expr * rhs, expr_ref & result); + void mk_xor(expr * lhs, expr * rhs, expr_ref & result); + void mk_implies(expr * lhs, expr * rhs, expr_ref & result); + void mk_ite(expr * c, expr * t, expr * e, expr_ref & result); + void mk_and(unsigned num_args, expr * const * args, expr_ref & result); + void mk_or(unsigned num_args, expr * const * args, expr_ref & result); + void mk_and(expr * arg1, expr * arg2, expr_ref & result); + void mk_or(expr * arg1, expr * arg2, expr_ref & result); + void mk_and(expr * arg1, expr * arg2, expr * arg3, expr_ref & result); + void mk_or(expr * arg1, expr * arg2, expr * arg3, expr_ref & result); + void mk_nand(unsigned num_args, expr * const * args, expr_ref & result); + void mk_nor(unsigned num_args, expr * const * args, expr_ref & result); + void mk_nand(expr * arg1, expr * arg2, expr_ref & result); + void mk_nor(expr * arg1, expr * arg2, expr_ref & result); + void mk_distinct(unsigned num_args, expr * const * args, expr_ref & result); + void mk_not(expr * n, expr_ref & result); + virtual bool reduce(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + virtual void enable_ac_support(bool flag); +}; + +/** + \brief Functor that compares expressions, but puts the expressions e and f(e) close to each other, where + f is in family m_fid, and has kind m_dkind; +*/ +struct expr_lt_proc { + family_id m_fid; + decl_kind m_dkind; + + expr_lt_proc(family_id fid = null_family_id, decl_kind k = null_decl_kind):m_fid(fid), m_dkind(k) {} + + unsigned get_id(expr * n) const { + if (m_fid != null_family_id && is_app_of(n, m_fid, m_dkind)) + return (to_app(n)->get_arg(0)->get_id() << 1) + 1; + else + return n->get_id() << 1; + } + + bool operator()(expr * n1, expr * n2) const { + return get_id(n1) < get_id(n2); + } +}; + +#endif /* _BASIC_SIMPLIFIER_PLUGIN_H_ */ diff --git a/lib/big_rational.h b/lib/big_rational.h new file mode 100644 index 000000000..753260ced --- /dev/null +++ b/lib/big_rational.h @@ -0,0 +1,33 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + big_rational.h + +Abstract: + + Big rational numbers + +Author: + + Leonardo de Moura (leonardo) 2006-09-18. + +Revision History: + +--*/ +#ifndef _BIG_RATIONAL_H_ +#define _BIG_RATIONAL_H_ + +#ifdef _WINDOWS +#include"..\msbig_rational\msbig_rational.h" +#else +#ifdef NO_GMP +#include"dummy_big_rational.h" +#else +#include"gmp_big_rational.h" +#endif +#endif + +#endif /* _BIG_RATIONAL_H_ */ + diff --git a/lib/bit2int.cpp b/lib/bit2int.cpp new file mode 100644 index 000000000..ab4193486 --- /dev/null +++ b/lib/bit2int.cpp @@ -0,0 +1,421 @@ +/*++ +Copyright (c) 2009 Microsoft Corporation + +Module Name: + + bit2cpp.cpp + +Abstract: + + Routines for simplifying bit2int expressions. + This propagates bv2int over arithmetical symbols as much as possible, + converting arithmetic operations into bit-vector operations. + +Author: + + Nikolaj Bjorner (nbjorner) 2009-08-28 + +Revision History: + +--*/ + +#include "bit2int.h" +#include "ast_pp.h" +#include "ast_ll_pp.h" +#include "for_each_ast.h" + +#define CHECK(_x_) if (!(_x_)) { UNREACHABLE(); } + +bit2int::bit2int(ast_manager & m) : + m_manager(m), m_bv_util(m), m_arith_util(m), m_cache(m), m_bit0(m) { + m_bit0 = m_bv_util.mk_numeral(0,1); +} + +void bit2int::operator()(expr * m, expr_ref & result, proof_ref& p) { + flush_cache(); + expr_reduce emap(*this); + for_each_ast(emap, m); + result = get_cached(m); + if (m_manager.proofs_enabled() && m != result.get()) { + // TBD: rough + p = m_manager.mk_rewrite(m, result); + } + TRACE("bit2int", + tout << mk_pp(m, m_manager) << "======>\n" + << mk_pp(result, m_manager) << "\n";); + +} + + +unsigned bit2int::get_b2i_size(expr* n) { + SASSERT(m_bv_util.is_bv2int(n)); + return m_bv_util.get_bv_size(to_app(n)->get_arg(0)); +} + +unsigned bit2int::get_numeral_bits(numeral const& k) { + numeral two(2); + numeral n(abs(k)); + unsigned num_bits = 1; + n = div(n, two); + while (n.is_pos()) { + ++num_bits; + n = div(n, two); + } + return num_bits; +} + +void bit2int::align_size(expr* e, unsigned sz, expr_ref& result) { + unsigned sz1 = m_bv_util.get_bv_size(e); + SASSERT(sz1 <= sz); + m_bv_simplifier->mk_zeroext(sz-sz1, e, result); +} + +void bit2int::align_sizes(expr_ref& a, expr_ref& b) { + unsigned sz1 = m_bv_util.get_bv_size(a); + unsigned sz2 = m_bv_util.get_bv_size(b); + expr_ref tmp(m_manager); + if (sz1 > sz2) { + m_bv_simplifier->mk_zeroext(sz1-sz2, b, tmp); + b = tmp; + } + else if (sz2 > sz1) { + m_bv_simplifier->mk_zeroext(sz2-sz1, a, tmp); + a = tmp; + } +} + +bool bit2int::extract_bv(expr* n, unsigned& sz, bool& sign, expr_ref& bv) { + numeral k; + bool is_int; + if (m_bv_util.is_bv2int(n)) { + bv = to_app(n)->get_arg(0); + sz = m_bv_util.get_bv_size(bv); + sign = false; + return true; + } + else if (m_arith_util.is_numeral(n, k, is_int) && is_int) { + sz = get_numeral_bits(k); + bv = m_bv_util.mk_numeral(k, m_bv_util.mk_sort(sz)); + sign = k.is_neg(); + return true; + } + else { + return false; + } +} + + +bool bit2int::mk_add(expr* e1, expr* e2, expr_ref& result) { + unsigned sz1, sz2; + bool sign1, sign2; + expr_ref tmp1(m_manager), tmp2(m_manager), tmp3(m_manager); + + if (extract_bv(e1, sz1, sign1, tmp1) && !sign1 && + extract_bv(e2, sz2, sign2, tmp2) && !sign2) { + unsigned sz; + numeral k; + if (m_bv_util.is_numeral(tmp1, k, sz) && k.is_zero()) { + result = e2; + return true; + } + if (m_bv_util.is_numeral(tmp2, k, sz) && k.is_zero()) { + result = e1; + return true; + } + align_sizes(tmp1, tmp2); + m_bv_simplifier->mk_zeroext(1, tmp1, tmp1); + m_bv_simplifier->mk_zeroext(1, tmp2, tmp2); + SASSERT(m_bv_util.get_bv_size(tmp1) == m_bv_util.get_bv_size(tmp2)); + m_bv_simplifier->mk_add(tmp1, tmp2, tmp3); + m_bv_simplifier->mk_bv2int(tmp3, m_arith_util.mk_int(), result); + return true; + } + return false; +} + +bool bit2int::mk_comp(eq_type ty, expr* e1, expr* e2, expr_ref& result) { + unsigned sz1, sz2; + bool sign1, sign2; + expr_ref tmp1(m_manager), tmp2(m_manager), tmp3(m_manager); + if (extract_bv(e1, sz1, sign1, tmp1) && !sign1 && + extract_bv(e2, sz2, sign2, tmp2) && !sign2) { + align_sizes(tmp1, tmp2); + SASSERT(m_bv_util.get_bv_size(tmp1) == m_bv_util.get_bv_size(tmp2)); + switch(ty) { + case lt: + m_bv_simplifier->mk_leq_core(false, tmp2, tmp1, tmp3); + result = m_manager.mk_not(tmp3); + break; + case le: + m_bv_simplifier->mk_leq_core(false,tmp1, tmp2, result); + break; + case eq: + result = m_manager.mk_eq(tmp1,tmp2); + break; + } + return true; + } + return false; +} + +bool bit2int::mk_mul(expr* e1, expr* e2, expr_ref& result) { + unsigned sz1, sz2; + bool sign1, sign2; + expr_ref tmp1(m_manager), tmp2(m_manager); + expr_ref tmp3(m_manager); + + if (extract_bv(e1, sz1, sign1, tmp1) && + extract_bv(e2, sz2, sign2, tmp2)) { + align_sizes(tmp1, tmp2); + m_bv_simplifier->mk_zeroext(m_bv_util.get_bv_size(tmp1), tmp1, tmp1); + m_bv_simplifier->mk_zeroext(m_bv_util.get_bv_size(tmp2), tmp2, tmp2); + + SASSERT(m_bv_util.get_bv_size(tmp1) == m_bv_util.get_bv_size(tmp2)); + m_bv_simplifier->mk_mul(tmp1, tmp2, tmp3); + m_bv_simplifier->mk_bv2int(tmp3, m_arith_util.mk_int(), result); + if (sign1 != sign2) { + result = m_arith_util.mk_uminus(result); + } + return true; + } + return false; +} + +bool bit2int::is_bv_poly(expr* n, expr_ref& pos, expr_ref& neg) { + ptr_vector todo; + expr_ref tmp(m_manager); + numeral k; + bool is_int; + todo.push_back(n); + m_bv_simplifier->mk_bv2int(m_bit0, m_arith_util.mk_int(), pos); + m_bv_simplifier->mk_bv2int(m_bit0, m_arith_util.mk_int(), neg); + + while (!todo.empty()) { + n = todo.back(); + todo.pop_back(); + if (m_bv_util.is_bv2int(n)) { + CHECK(mk_add(n, pos, pos)); + } + else if (m_arith_util.is_numeral(n, k, is_int) && is_int) { + if (k.is_nonneg()) { + CHECK(mk_add(n, pos, pos)); + } + else { + tmp = m_arith_util.mk_numeral(-k, true); + CHECK(mk_add(tmp, neg, neg)); + } + } + else if (m_arith_util.is_add(n)) { + for (unsigned i = 0; i < to_app(n)->get_num_args(); ++i) { + todo.push_back(to_app(n)->get_arg(i)); + } + } + else if (m_arith_util.is_mul(n) && + to_app(n)->get_num_args() == 2 && + m_arith_util.is_numeral(to_app(n)->get_arg(0), k, is_int) && is_int && k.is_minus_one() && + m_bv_util.is_bv2int(to_app(n)->get_arg(1))) { + CHECK(mk_add(to_app(n)->get_arg(1), neg, neg)); + } + else if (m_arith_util.is_mul(n) && + to_app(n)->get_num_args() == 2 && + m_arith_util.is_numeral(to_app(n)->get_arg(1), k, is_int) && is_int && k.is_minus_one() && + m_bv_util.is_bv2int(to_app(n)->get_arg(0))) { + CHECK(mk_add(to_app(n)->get_arg(0), neg, neg)); + } + else if (m_arith_util.is_uminus(n) && + m_bv_util.is_bv2int(to_app(n)->get_arg(0))) { + CHECK(mk_add(to_app(n)->get_arg(0), neg, neg)); + } + else { + TRACE("bit2int", tout << "Not a poly: " << mk_pp(n, m_manager) << "\n";); + return false; + } + } + return true; +} + +void bit2int::visit(quantifier* q) { + expr_ref result(m_manager); + result = get_cached(q->get_expr()); + result = m_manager.update_quantifier(q, result); + cache_result(q, result); +} + +void bit2int::visit(app* n) { + func_decl* f = n->get_decl(); + unsigned num_args = n->get_num_args(); + + m_args.reset(); + for (unsigned i = 0; i < num_args; ++i) { + m_args.push_back(get_cached(n->get_arg(i))); + } + + expr* const* args = m_args.c_ptr(); + + bool has_b2i = + m_arith_util.is_le(n) || m_arith_util.is_ge(n) || m_arith_util.is_gt(n) || + m_arith_util.is_lt(n) || m_manager.is_eq(n); + expr_ref result(m_manager); + for (unsigned i = 0; !has_b2i && i < num_args; ++i) { + has_b2i = m_bv_util.is_bv2int(args[i]); + } + if (!has_b2i) { + result = m_manager.mk_app(f, num_args, args); + cache_result(n, result); + return; + } + // + // bv2int(x) + bv2int(y) -> bv2int(pad(x) + pad(y)) + // bv2int(x) + k -> bv2int(pad(x) + pad(k)) + // bv2int(x) * bv2int(y) -> bv2int(pad(x) * pad(y)) + // bv2int(x) * k -> sign(k)*bv2int(pad(x) * pad(k)) + // bv2int(x) - bv2int(y) <= z -> bv2int(x) <= bv2int(y) + z + // bv2int(x) <= z - bv2int(y) -> bv2int(x) + bv2int(y) <= z + // + + expr* e1, *e2; + expr_ref tmp1(m_manager), tmp2(m_manager); + expr_ref tmp3(m_manager); + expr_ref pos1(m_manager), neg1(m_manager); + expr_ref pos2(m_manager), neg2(m_manager); + expr_ref e2bv(m_manager); + bool sign2; + numeral k; + unsigned sz2; + + if (num_args >= 2) { + e1 = args[0]; + e2 = args[1]; + } + + if (m_arith_util.is_add(n) && num_args >= 1) { + result = e1; + for (unsigned i = 1; i < num_args; ++i) { + e1 = result; + e2 = args[i]; + if (!mk_add(e1, e2, result)) { + result = m_manager.mk_app(f, num_args, args); + cache_result(n, result); + return; + } + } + cache_result(n, result); + } + else if (m_arith_util.is_mul(n) && num_args >= 1) { + result = e1; + for (unsigned i = 1; i < num_args; ++i) { + e1 = result; + e2 = args[i]; + if (!mk_mul(e1, e2, result)) { + result = m_manager.mk_app(f, num_args, args); + cache_result(n, result); + return; + } + } + cache_result(n, result); + } + else if (m_manager.is_eq(n) && + is_bv_poly(e1, pos1, neg1) && + is_bv_poly(e2, pos2, neg2) && + mk_add(pos1, neg2, tmp1) && + mk_add(neg1, pos2, tmp2) && + mk_comp(eq, tmp1, tmp2, result)) { + cache_result(n, result); + } + else if (m_arith_util.is_le(n) && + is_bv_poly(e1, pos1, neg1) && + is_bv_poly(e2, pos2, neg2) && + mk_add(pos1, neg2, tmp1) && + mk_add(neg1, pos2, tmp2) && + mk_comp(le, tmp1, tmp2, result)) { + cache_result(n, result); + } + else if (m_arith_util.is_lt(n) && + is_bv_poly(e1, pos1, neg1) && + is_bv_poly(e2, pos2, neg2) && + mk_add(pos1, neg2, tmp1) && + mk_add(neg1, pos2, tmp2) && + mk_comp(lt, tmp1, tmp2, result)) { + cache_result(n, result); + } + else if (m_arith_util.is_ge(n) && + is_bv_poly(e1, pos1, neg1) && + is_bv_poly(e2, pos2, neg2) && + mk_add(pos1, neg2, tmp1) && + mk_add(neg1, pos2, tmp2) && + mk_comp(le, tmp2, tmp1, result)) { + cache_result(n, result); + } + else if (m_arith_util.is_gt(n) && + is_bv_poly(e1, pos1, neg1) && + is_bv_poly(e2, pos2, neg2) && + mk_add(pos1, neg2, tmp1) && + mk_add(neg1, pos2, tmp2) && + mk_comp(lt, tmp2, tmp1, result)) { + cache_result(n, result); + } + else if (m_arith_util.is_mod(n) && + is_bv_poly(e1, pos1, neg1) && + extract_bv(e2, sz2, sign2, e2bv) && !sign2) { + // + // (pos1 - neg1) mod e2 = (pos1 + (e2 - (neg1 mod e2))) mod e2 + // + unsigned sz_p, sz_n, sz; + bool sign_p, sign_n; + expr_ref tmp_p(m_manager), tmp_n(m_manager); + CHECK(extract_bv(pos1, sz_p, sign_p, tmp_p)); + CHECK(extract_bv(neg1, sz_n, sign_n, tmp_n)); + SASSERT(!sign_p && !sign_n); + + // pos1 mod e2 + if (m_bv_util.is_numeral(tmp_n, k, sz) && k.is_zero()) { + tmp1 = tmp_p; + tmp2 = e2bv; + align_sizes(tmp1, tmp2); + m_bv_simplifier->mk_bv_urem(tmp1, tmp2, tmp3); + m_bv_simplifier->mk_bv2int(tmp3, m_arith_util.mk_int(), result); + cache_result(n, result); + return; + } + + // neg1 mod e2; + tmp1 = tmp_n; + tmp2 = e2bv; + align_sizes(tmp1, tmp2); + m_bv_simplifier->mk_bv_urem(tmp1, tmp2, tmp3); + // e2 - (neg1 mod e2) + tmp1 = e2bv; + tmp2 = tmp3; + align_sizes(tmp1, tmp2); + m_bv_simplifier->mk_sub(tmp1, tmp2, tmp3); + // pos1 + (e2 - (neg1 mod e2)) + tmp1 = tmp_p; + tmp2 = tmp3; + align_sizes(tmp1, tmp2); + m_bv_simplifier->mk_zeroext(1, tmp1, tmp_p); + m_bv_simplifier->mk_zeroext(1, tmp2, tmp_n); + m_bv_simplifier->mk_add(tmp_p, tmp_n, tmp1); + // (pos1 + (e2 - (neg1 mod e2))) mod e2 + tmp2 = e2bv; + align_sizes(tmp1, tmp2); + m_bv_simplifier->mk_bv_urem(tmp1, tmp2, tmp3); + + m_bv_simplifier->mk_bv2int(tmp3, m_arith_util.mk_int(), result); + + cache_result(n, result); + } + else { + result = m_manager.mk_app(f, num_args, args); + cache_result(n, result); + } +} + +expr * bit2int::get_cached(expr * n) const { + return const_cast(this)->m_cache.find(n); +} + +void bit2int::cache_result(expr * n, expr * r) { + TRACE("bit2int_verbose", tout << "caching:\n" << mk_ll_pp(n, m_manager) << + "======>\n" << mk_ll_pp(r, m_manager) << "\n";); + m_cache.insert(n, r); +} diff --git a/lib/bit2int.h b/lib/bit2int.h new file mode 100644 index 000000000..36351337b --- /dev/null +++ b/lib/bit2int.h @@ -0,0 +1,96 @@ +/*++ +Copyright (c) 2009 Microsoft Corporation + +Module Name: + + bit2int.h + +Abstract: + + Routines for simplifying bit2int expressions. + +Author: + + Nikolaj Bjorner (nbjorner) 2009-08-28 + +Revision History: + +--*/ +#ifndef _BIT2INT_H_ +#define _BIT2INT_H_ + +#include"bv_decl_plugin.h" +#include"arith_decl_plugin.h" +#include"act_cache.h" +#include"basic_simplifier_plugin.h" +#include"bv_simplifier_plugin.h" + + +class bit2int { +protected: + typedef rational numeral; + + enum eq_type { + lt, + le, + eq + }; + + class expr_reduce { + bit2int& m_super; + public: + expr_reduce(bit2int& s) : m_super(s) {} + + void operator()(var* v) { + m_super.cache_result(v, v); + } + + void operator()(quantifier* q) { + m_super.visit(q); + } + + void operator()(app* a) { + m_super.visit(a); + } + + void operator()(ast* a) {} + + }; + + typedef act_cache expr_map; + ast_manager & m_manager; + bv_util m_bv_util; + arith_util m_arith_util; + bv_simplifier_plugin * m_bv_simplifier; + + expr_map m_cache; // map: ast -> ast ref. counters are incremented when inserted here. + expr_ref m_bit0; + ptr_vector m_args; + + + void visit(app* n); + void visit(quantifier* q); + unsigned get_b2i_size(expr * n); + bool extract_bv(expr* n, unsigned& sz, bool& sign, expr_ref& bv); + unsigned get_numeral_bits(numeral const& k); + bool is_bv_poly(expr* n, expr_ref& pos, expr_ref& neg); + bool mk_mul(expr* a, expr* b, expr_ref& result); + bool mk_comp(eq_type ty, expr* e1, expr* e2, expr_ref& result); + bool mk_add(expr* e1, expr* e2, expr_ref& result); + + expr * get_cached(expr * n) const; + bool is_cached(expr * n) const { return get_cached(n) != 0; } + void cache_result(expr * n, expr * r); + void reset_cache() { m_cache.reset(); } + void flush_cache() { m_cache.cleanup(); } + void align_size(expr* e, unsigned sz, expr_ref& result); + void align_sizes(expr_ref& a, expr_ref& b); + +public: + bit2int(ast_manager & m); + void set_bv_simplifier(bv_simplifier_plugin * p) { m_bv_simplifier = p; } + void operator()(expr * m, expr_ref & result, proof_ref& p); +}; + +#endif /* _BIT2INT_H_ */ + diff --git a/lib/bit_blaster.cpp b/lib/bit_blaster.cpp new file mode 100644 index 000000000..153066711 --- /dev/null +++ b/lib/bit_blaster.cpp @@ -0,0 +1,128 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + bit_blaster.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-05. + +Revision History: + +--*/ +#include"bit_blaster.h" +#include"bit_blaster_tpl_def.h" +#include"ast_pp.h" +#include"bv_decl_plugin.h" + +bit_blaster_cfg::bit_blaster_cfg(bv_util & u, bit_blaster_params const & p, basic_simplifier_plugin & _s): + m_util(u), + m_params(p), + s(_s) { +} + +static void sort_args(expr * & l1, expr * & l2, expr * & l3) { + expr * args[3] = {l1, l2, l3}; + // ast_lt_proc is based on the AST ids. So, it is a total order on AST nodes. + // No need for stable_sort + std::sort(args, args+3, ast_lt_proc()); + l1 = args[0]; l2 = args[1]; l3 = args[2]; +} + +void bit_blaster_cfg::mk_xor3(expr * l1, expr * l2, expr * l3, expr_ref & r) { + TRACE("xor3", tout << "#" << l1->get_id() << " #" << l2->get_id() << " #" << l3->get_id();); + sort_args(l1, l2, l3); + TRACE("xor3_sorted", tout << "#" << l1->get_id() << " #" << l2->get_id() << " #" << l3->get_id();); + if (m_params.m_bb_ext_gates) { + if (l1 == l2) + r = l3; + else if (l1 == l3) + r = l2; + else if (l2 == l3) + r = l1; + else if (m().is_complement(l1, l2)) + s.mk_not(l3, r); + else if (m().is_complement(l1, l3)) + s.mk_not(l2, r); + else if (m().is_complement(l2, l3)) + s.mk_not(l1, r); + else if (m().is_true(l1)) + s.mk_iff(l2, l3, r); + else if (m().is_false(l1)) + s.mk_xor(l2, l3, r); + else if (m().is_true(l2)) + s.mk_iff(l1, l3, r); + else if (m().is_false(l2)) + s.mk_xor(l1, l3, r); + else if (m().is_true(l3)) + s.mk_iff(l1, l2, r); + else if (m().is_false(l3)) + s.mk_xor(l1, l2, r); + else + r = m().mk_app(m_util.get_family_id(), OP_XOR3, l1, l2, l3); + } + else { + expr_ref t(m()); + s.mk_xor(l1, l2, t); + s.mk_xor(t, l3, r); + } +} + +void bit_blaster_cfg::mk_carry(expr * l1, expr * l2, expr * l3, expr_ref & r) { + TRACE("carry", tout << "#" << l1->get_id() << " #" << l2->get_id() << " #" << l3->get_id();); + sort_args(l1, l2, l3); + TRACE("carry_sorted", tout << "#" << l1->get_id() << " #" << l2->get_id() << " #" << l3->get_id();); + if (m_params.m_bb_ext_gates) { + if ((m().is_false(l1) && m().is_false(l2)) || + (m().is_false(l1) && m().is_false(l3)) || + (m().is_false(l2) && m().is_false(l3))) + r = m().mk_false(); + else if ((m().is_true(l1) && m().is_true(l2)) || + (m().is_true(l1) && m().is_true(l3)) || + (m().is_true(l2) && m().is_true(l3))) + r = m().mk_true(); + else if (l1 == l2 && l1 == l3) + r = l1; + else if (m().is_false(l1)) + s.mk_and(l2, l3, r); + else if (m().is_false(l2)) + s.mk_and(l1, l3, r); + else if (m().is_false(l3)) + s.mk_and(l1, l2, r); + else if (m().is_true(l1)) + s.mk_or(l2, l3, r); + else if (m().is_true(l2)) + s.mk_or(l1, l3, r); + else if (m().is_true(l3)) + s.mk_or(l1, l2, r); + else if (m().is_complement(l1, l2)) + r = l3; + else if (m().is_complement(l1, l3)) + r = l2; + else if (m().is_complement(l2, l3)) + r = l1; + else + r = m().mk_app(m_util.get_family_id(), OP_CARRY, l1, l2, l3); + } + else { + expr_ref t1(m()), t2(m()), t3(m()); + s.mk_and(l1, l2, t1); + s.mk_and(l1, l3, t2); + s.mk_and(l2, l3, t3); + s.mk_or(t1, t2, t3, r); + } +} + +template class bit_blaster_tpl; + +bit_blaster::bit_blaster(ast_manager & m, bit_blaster_params const & params): + bit_blaster_tpl(bit_blaster_cfg(m_util, params, m_simp)), + m_util(m), + m_simp(m) { +} diff --git a/lib/bit_blaster.h b/lib/bit_blaster.h new file mode 100644 index 000000000..42444aebc --- /dev/null +++ b/lib/bit_blaster.h @@ -0,0 +1,65 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + bit_blaster.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-05. + +Revision History: + +--*/ +#ifndef _BIT_BLASTER_H_ +#define _BIT_BLASTER_H_ + +#include"basic_simplifier_plugin.h" +#include"bit_blaster_params.h" +#include"bit_blaster_tpl.h" +#include"bv_decl_plugin.h" +#include"rational.h" + +class bit_blaster_cfg { +public: + typedef rational numeral; +protected: + bv_util & m_util; + bit_blaster_params const & m_params; + basic_simplifier_plugin & s; +public: + bit_blaster_cfg(bv_util & u, bit_blaster_params const & p, basic_simplifier_plugin & _s); + + ast_manager & m() const { return m_util.get_manager(); } + numeral power(unsigned n) const { return m_util.power_of_two(n); } + void mk_xor(expr * a, expr * b, expr_ref & r) { s.mk_xor(a, b, r); } + void mk_xor3(expr * a, expr * b, expr * c, expr_ref & r); + void mk_carry(expr * a, expr * b, expr * c, expr_ref & r); + void mk_iff(expr * a, expr * b, expr_ref & r) { s.mk_iff(a, b, r); } + void mk_and(expr * a, expr * b, expr_ref & r) { s.mk_and(a, b, r); } + void mk_and(expr * a, expr * b, expr * c, expr_ref & r) { s.mk_and(a, b, c, r); } + void mk_and(unsigned sz, expr * const * args, expr_ref & r) { s.mk_and(sz, args, r); } + void mk_or(expr * a, expr * b, expr_ref & r) { s.mk_or(a, b, r); } + void mk_or(expr * a, expr * b, expr * c, expr_ref & r) { s.mk_or(a, b, c, r); } + void mk_or(unsigned sz, expr * const * args, expr_ref & r) { s.mk_or(sz, args, r); } + void mk_not(expr * a, expr_ref & r) { s.mk_not(a, r); } + void mk_ite(expr * c, expr * t, expr * e, expr_ref & r) { s.mk_ite(c, t, e, r); } + void mk_nand(expr * a, expr * b, expr_ref & r) { s.mk_nand(a, b, r); } + void mk_nor(expr * a, expr * b, expr_ref & r) { s.mk_nor(a, b, r); } +}; + +class bit_blaster : public bit_blaster_tpl { + bv_util m_util; + basic_simplifier_plugin m_simp; +public: + bit_blaster(ast_manager & m, bit_blaster_params const & params); + bit_blaster_params const & get_params() const { return this->m_params; } +}; + +#endif /* _BIT_BLASTER_H_ */ + diff --git a/lib/bit_blaster_model_converter.cpp b/lib/bit_blaster_model_converter.cpp new file mode 100644 index 000000000..6c1c6911c --- /dev/null +++ b/lib/bit_blaster_model_converter.cpp @@ -0,0 +1,187 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + bit_blaster_model_convert.cpp + +Abstract: + + Model converter for bit-blasting tactics. + +Author: + + Leonardo (leonardo) 2011-05-09 + +Notes: + +--*/ +#include"model.h" +#include"model_pp.h" +#include"model_converter.h" +#include"bv_decl_plugin.h" +#include"ast_smt2_pp.h" + +/** + If TO_BOOL == true, then bit-vectors of size n were blasted into n-tuples of Booleans. + If TO_BOOL == false, then bit-vectors of size n were blasted into n-tuples of bit-vectors of size 1. +*/ +template +struct bit_blaster_model_converter : public model_converter { + func_decl_ref_vector m_vars; + expr_ref_vector m_bits; + + ast_manager & m() const { return m_vars.get_manager(); } + + bit_blaster_model_converter(ast_manager & m, obj_map const & const2bits):m_vars(m), m_bits(m) { + obj_map::iterator it = const2bits.begin(); + obj_map::iterator end = const2bits.end(); + for (; it != end; ++it) { + func_decl * v = it->m_key; + expr * bits = it->m_value; + SASSERT(!TO_BOOL || is_app_of(bits, m.get_family_id("bv"), OP_MKBV)); + SASSERT(TO_BOOL || is_app_of(bits, m.get_family_id("bv"), OP_CONCAT)); + m_vars.push_back(v); + m_bits.push_back(bits); + } + } + + virtual ~bit_blaster_model_converter() { + } + + void collect_bits(obj_hashtable & bits) { + unsigned sz = m_bits.size(); + for (unsigned i = 0; i < sz; i++) { + expr * bs = m_bits.get(i); + SASSERT(!TO_BOOL || is_app_of(bs, m().get_family_id("bv"), OP_MKBV)); + SASSERT(TO_BOOL || is_app_of(bs, m().get_family_id("bv"), OP_CONCAT)); + unsigned num_args = to_app(bs)->get_num_args(); + for (unsigned j = 0; j < num_args; j++) { + expr * bit = to_app(bs)->get_arg(j); + SASSERT(!TO_BOOL || m().is_bool(bit)); + SASSERT(TO_BOOL || is_sort_of(m().get_sort(bit), m().get_family_id("bv"), BV_SORT)); + SASSERT(is_uninterp_const(bit)); + bits.insert(to_app(bit)->get_decl()); + } + } + TRACE("blaster_mc", + tout << "bits that should not be included in the model:\n"; + obj_hashtable::iterator it = bits.begin(); + obj_hashtable::iterator end = bits.end(); + for (; it != end; ++it) { + tout << (*it)->get_name() << " "; + } + tout << "\n";); + + } + + void copy_non_bits(obj_hashtable & bits, model * old_model, model * new_model) { + unsigned num = old_model->get_num_constants(); + for (unsigned i = 0; i < num; i++) { + func_decl * f = old_model->get_constant(i); + if (bits.contains(f)) + continue; + TRACE("blaster_mc", tout << "non-bit: " << f->get_name() << "\n";); + expr * fi = old_model->get_const_interp(f); + new_model->register_decl(f, fi); + } + TRACE("blaster_mc", tout << "after copy non bits:\n"; model_pp(tout, *new_model);); + new_model->copy_func_interps(*old_model); + new_model->copy_usort_interps(*old_model); + TRACE("blaster_mc", tout << "after copying functions and sorts:\n"; model_pp(tout, *new_model);); + } + + void mk_bvs(model * old_model, model * new_model) { + bv_util util(m()); + rational val; + rational two(2); + SASSERT(m_vars.size() == m_bits.size()); + unsigned sz = m_vars.size(); + for (unsigned i = 0; i < sz; i++) { + expr * bs = m_bits.get(i); + val.reset(); + unsigned bv_sz = to_app(bs)->get_num_args(); + if (TO_BOOL) { + SASSERT(is_app_of(bs, m().get_family_id("bv"), OP_MKBV)); + unsigned j = bv_sz; + while (j > 0) { + --j; + val *= two; + expr * bit = to_app(bs)->get_arg(j); + SASSERT(m().is_bool(bit)); + SASSERT(is_uninterp_const(bit)); + func_decl * bit_decl = to_app(bit)->get_decl(); + expr * bit_val = old_model->get_const_interp(bit_decl); + // remark: if old_model does not assign bit_val, then assume it is false. + if (bit_val != 0 && m().is_true(bit_val)) + val++; + } + } + else { + SASSERT(is_app_of(bs, m().get_family_id("bv"), OP_CONCAT)); + for (unsigned j = 0; j < bv_sz; j++) { + val *= two; + expr * bit = to_app(bs)->get_arg(j); + SASSERT(util.is_bv(bit)); + SASSERT(util.get_bv_size(bit) == 1); + SASSERT(is_uninterp_const(bit)); + func_decl * bit_decl = to_app(bit)->get_decl(); + expr * bit_val = old_model->get_const_interp(bit_decl); + // remark: if old_model does not assign bit_val, then assume it is false. + if (bit_val != 0 && !util.is_zero(bit_val)) + val++; + } + } + expr * new_val = util.mk_numeral(val, bv_sz); + new_model->register_decl(m_vars.get(i), new_val); + } + } + + virtual void operator()(model_ref & md, unsigned goal_idx) { + SASSERT(goal_idx == 0); + model * new_model = alloc(model, m()); + obj_hashtable bits; + collect_bits(bits); + copy_non_bits(bits, md.get(), new_model); + mk_bvs(md.get(), new_model); + md = new_model; + } + + virtual void operator()(model_ref & md) { + operator()(md, 0); + } + + virtual void display(std::ostream & out) { + out << "(bit-blaster-model-converter"; + unsigned sz = m_vars.size(); + for (unsigned i = 0; i < sz; i++) { + out << "\n (" << m_vars.get(i)->get_name() << " "; + unsigned indent = m_vars.get(i)->get_name().size() + 4; + out << mk_ismt2_pp(m_bits.get(i), m(), indent) << ")"; + } + out << ")" << std::endl; + } + +protected: + bit_blaster_model_converter(ast_manager & m):m_vars(m), m_bits(m) { } +public: + + virtual model_converter * translate(ast_translation & translator) { + bit_blaster_model_converter * res = alloc(bit_blaster_model_converter, translator.to()); + for (unsigned i = 0; i < m_vars.size(); i++) + res->m_vars.push_back(translator(m_vars[i].get())); + for (unsigned i = 0; i < m_bits.size(); i++) + res->m_bits.push_back(translator(m_bits[i].get())); + return res; + } +}; + +model_converter * mk_bit_blaster_model_converter(ast_manager & m, obj_map const & const2bits) { + return alloc(bit_blaster_model_converter, m, const2bits); +} + +model_converter * mk_bv1_blaster_model_converter(ast_manager & m, obj_map const & const2bits) { + return alloc(bit_blaster_model_converter, m, const2bits); +} + + diff --git a/lib/bit_blaster_model_converter.h b/lib/bit_blaster_model_converter.h new file mode 100644 index 000000000..3df50fbae --- /dev/null +++ b/lib/bit_blaster_model_converter.h @@ -0,0 +1,27 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + bit_blaster_model_convert.h + +Abstract: + + Model converter for bit-blasting tactics. + +Author: + + Leonardo (leonardo) 2011-05-09 + +Notes: + +--*/ +#ifndef _BIT_BLASTER_MODEL_CONVERTER_H_ +#define _BIT_BLASTER_MODEL_CONVERTER_H_ + +#include"model_converter.h" + +model_converter * mk_bit_blaster_model_converter(ast_manager & m, obj_map const & const2bits); +model_converter * mk_bv1_blaster_model_converter(ast_manager & m, obj_map const & const2bits); + +#endif diff --git a/lib/bit_blaster_params.h b/lib/bit_blaster_params.h new file mode 100644 index 000000000..b56be35db --- /dev/null +++ b/lib/bit_blaster_params.h @@ -0,0 +1,41 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + bit_blaster_params.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-10-02. + +Revision History: + +--*/ +#ifndef _BIT_BLASTER_PARAMS_H_ +#define _BIT_BLASTER_PARAMS_H_ + +#include"ini_file.h" + +struct bit_blaster_params { + bool m_bb_eager; + bool m_bb_ext_gates; + bool m_bb_quantifiers; + bit_blaster_params(): + m_bb_eager(false), + m_bb_ext_gates(false), + m_bb_quantifiers(false) { + } + void register_params(ini_params & p) { + p.register_bool_param("BB_EAGER", m_bb_eager, "eager bit blasting"); + p.register_bool_param("BB_EXT_GATES", m_bb_ext_gates, "use extended gates during bit-blasting"); + p.register_bool_param("BB_QUANTIFIERS", m_bb_quantifiers, "convert bit-vectors to Booleans in quantifiers"); + } +}; + +#endif /* _BIT_BLASTER_PARAMS_H_ */ + diff --git a/lib/bit_blaster_rewriter.cpp b/lib/bit_blaster_rewriter.cpp new file mode 100644 index 000000000..c00ea6794 --- /dev/null +++ b/lib/bit_blaster_rewriter.cpp @@ -0,0 +1,649 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + bit_blaster_rewriter.cpp + +Abstract: + + Bit-blasting rewriter + +Author: + + Leonardo (leonardo) 2012-10-04 + +Notes: + +--*/ +#include"bit_blaster_rewriter.h" +#include"bv_decl_plugin.h" +#include"bit_blaster_tpl_def.h" +#include"rewriter_def.h" +#include"bool_rewriter.h" +#include"ref_util.h" +#include"ast_smt2_pp.h" + +struct blaster_cfg { + typedef rational numeral; + + bool_rewriter & m_rewriter; + bv_util & m_util; + blaster_cfg(bool_rewriter & r, bv_util & u):m_rewriter(r), m_util(u) {} + + ast_manager & m() const { return m_util.get_manager(); } + numeral power(unsigned n) const { return m_util.power_of_two(n); } + void mk_xor(expr * a, expr * b, expr_ref & r) { m_rewriter.mk_xor(a, b, r); } + void mk_xor3(expr * a, expr * b, expr * c, expr_ref & r) { + expr_ref tmp(m()); + mk_xor(b, c, tmp); + mk_xor(a, tmp, r); + } + void mk_iff(expr * a, expr * b, expr_ref & r) { m_rewriter.mk_iff(a, b, r); } + void mk_and(expr * a, expr * b, expr_ref & r) { m_rewriter.mk_and(a, b, r); } + void mk_and(expr * a, expr * b, expr * c, expr_ref & r) { m_rewriter.mk_and(a, b, c, r); } + void mk_and(unsigned sz, expr * const * args, expr_ref & r) { m_rewriter.mk_and(sz, args, r); } + void mk_or(expr * a, expr * b, expr_ref & r) { m_rewriter.mk_or(a, b, r); } + void mk_or(expr * a, expr * b, expr * c, expr_ref & r) { m_rewriter.mk_or(a, b, c, r); } + void mk_or(unsigned sz, expr * const * args, expr_ref & r) { m_rewriter.mk_or(sz, args, r); } + void mk_not(expr * a, expr_ref & r) { m_rewriter.mk_not(a, r); } + void mk_carry(expr * a, expr * b, expr * c, expr_ref & r) { + expr_ref t1(m()), t2(m()), t3(m()); +#if 1 + mk_and(a, b, t1); + mk_and(a, c, t2); + mk_and(b, c, t3); + mk_or(t1, t2, t3, r); +#else + mk_or(a, b, t1); + mk_or(a, c, t2); + mk_or(b, c, t3); + mk_and(t1, t2, t3, r); +#endif + } + void mk_ite(expr * c, expr * t, expr * e, expr_ref & r) { m_rewriter.mk_ite(c, t, e, r); } + void mk_nand(expr * a, expr * b, expr_ref & r) { m_rewriter.mk_nand(a, b, r); } + void mk_nor(expr * a, expr * b, expr_ref & r) { m_rewriter.mk_nor(a, b, r); } +}; + +// CMW: GCC/LLVM do not like this definition because a symbol of the same name exists in assert_set_bit_blaster.o +// template class bit_blaster_tpl; + +class blaster : public bit_blaster_tpl { + bool_rewriter m_rewriter; + bv_util m_util; +public: + blaster(ast_manager & m): + bit_blaster_tpl(blaster_cfg(m_rewriter, m_util)), + m_rewriter(m), + m_util(m) { + m_rewriter.set_flat(false); + m_rewriter.set_elim_and(true); + } + + bv_util & butil() { return m_util; } +}; + +struct blaster_rewriter_cfg : public default_rewriter_cfg { + ast_manager & m_manager; + blaster & m_blaster; + expr_ref_vector m_in1; + expr_ref_vector m_in2; + expr_ref_vector m_out; + obj_map m_const2bits; + expr_ref_vector m_bindings; + + bool m_blast_mul; + bool m_blast_add; + bool m_blast_quant; + bool m_blast_full; + unsigned long long m_max_memory; + unsigned m_max_steps; + + ast_manager & m() const { return m_manager; } + bv_util & butil() { return m_blaster.butil(); } + + void cleanup_buffers() { + m_in1.finalize(); + m_in2.finalize(); + m_out.finalize(); + m_bindings.finalize(); + } + + blaster_rewriter_cfg(ast_manager & m, blaster & b, params_ref const & p): + m_manager(m), + m_blaster(b), + m_in1(m), + m_in2(m), + m_out(m), + m_bindings(m) { + updt_params(p); + } + + ~blaster_rewriter_cfg() { + dec_ref_map_key_values(m_manager, m_const2bits); + } + + void updt_params(params_ref const & p) { + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + m_max_steps = p.get_uint(":max-steps", UINT_MAX); + m_blast_add = p.get_bool(":blast-add", true); + m_blast_mul = p.get_bool(":blast-mul", true); + m_blast_full = p.get_bool(":blast-full", false); + m_blast_quant = p.get_bool(":blast-quant", false); + m_blaster.set_max_memory(m_max_memory); + } + + bool rewrite_patterns() const { return true; } + + bool max_steps_exceeded(unsigned num_steps) const { + cooperate("bit blaster"); + if (memory::get_allocation_size() > m_max_memory) + throw tactic_exception(TACTIC_MAX_MEMORY_MSG); + return num_steps > m_max_steps; + } + + void get_bits(expr * t, expr_ref_vector & out_bits) { + if (butil().is_mkbv(t)) { + out_bits.append(to_app(t)->get_num_args(), to_app(t)->get_args()); + } + else { + unsigned bv_size = butil().get_bv_size(t); + for (unsigned i = 0; i < bv_size; i++) { + parameter p(i); + out_bits.push_back(m().mk_app(butil().get_family_id(), OP_BIT2BOOL, 1, &p, 1, &t)); + } + SASSERT(bv_size == out_bits.size()); + } + } + + template + app * mk_mkbv(V const & bits) { + return m().mk_app(butil().get_family_id(), OP_MKBV, bits.size(), bits.c_ptr()); + } + + void mk_const(func_decl * f, expr_ref & result) { + SASSERT(f->get_family_id() == null_family_id); + SASSERT(f->get_arity() == 0); + expr * r; + if (m_const2bits.find(f, r)) { + result = r; + return; + } + sort * s = f->get_range(); + SASSERT(butil().is_bv_sort(s)); + unsigned bv_size = butil().get_bv_size(s); + sort * b = m().mk_bool_sort(); + m_out.reset(); + for (unsigned i = 0; i < bv_size; i++) { + m_out.push_back(m().mk_fresh_const(0, b)); + } + r = mk_mkbv(m_out); + m_const2bits.insert(f, r); + m_manager.inc_ref(f); + m_manager.inc_ref(r); + result = r; + } + +#define MK_UNARY_REDUCE(OP, BB_OP) \ +void OP(expr * arg, expr_ref & result) { \ + m_in1.reset(); \ + get_bits(arg, m_in1); \ + m_out.reset(); \ + m_blaster.BB_OP(m_in1.size(), m_in1.c_ptr(), m_out); \ + result = mk_mkbv(m_out); \ +} + + MK_UNARY_REDUCE(reduce_not, mk_not); + MK_UNARY_REDUCE(reduce_redor, mk_redor); + MK_UNARY_REDUCE(reduce_redand, mk_redand); + +#define MK_BIN_REDUCE(OP, BB_OP) \ +void OP(expr * arg1, expr * arg2, expr_ref & result) { \ + m_in1.reset(); m_in2.reset(); \ + get_bits(arg1, m_in1); \ + get_bits(arg2, m_in2); \ + m_out.reset(); \ + m_blaster.BB_OP(m_in1.size(), m_in1.c_ptr(), m_in2.c_ptr(), m_out); \ + result = mk_mkbv(m_out); \ +} + + MK_BIN_REDUCE(reduce_shl, mk_shl); + MK_BIN_REDUCE(reduce_ashr, mk_ashr); + MK_BIN_REDUCE(reduce_lshr, mk_lshr); + MK_BIN_REDUCE(reduce_udiv, mk_udiv); + MK_BIN_REDUCE(reduce_urem, mk_urem); + MK_BIN_REDUCE(reduce_sdiv, mk_sdiv); + MK_BIN_REDUCE(reduce_srem, mk_srem); + MK_BIN_REDUCE(reduce_smod, mk_smod); + MK_BIN_REDUCE(reduce_ext_rotate_left, mk_ext_rotate_left); + MK_BIN_REDUCE(reduce_ext_rotate_right, mk_ext_rotate_right); + +#define MK_BIN_AC_REDUCE(OP, BIN_OP, BB_OP) \ +MK_BIN_REDUCE(BIN_OP, BB_OP); \ +void OP(unsigned num_args, expr * const * args, expr_ref & result) { \ + SASSERT(num_args > 0); \ + result = args[0]; \ + expr_ref new_result(m_manager); \ + for (unsigned i = 1; i < num_args; i++) { \ + BIN_OP(result.get(), args[i], new_result); \ + result = new_result; \ + } \ +} + + MK_BIN_AC_REDUCE(reduce_add, reduce_bin_add, mk_adder); + MK_BIN_AC_REDUCE(reduce_mul, reduce_bin_mul, mk_multiplier); + + MK_BIN_AC_REDUCE(reduce_or, reduce_bin_or, mk_or); + MK_BIN_AC_REDUCE(reduce_xor, reduce_bin_xor, mk_xor); + + +#define MK_BIN_PRED_REDUCE(OP, BB_OP) \ +void OP(expr * arg1, expr * arg2, expr_ref & result) { \ + m_in1.reset(); m_in2.reset(); \ + get_bits(arg1, m_in1); \ + get_bits(arg2, m_in2); \ + m_blaster.BB_OP(m_in1.size(), m_in1.c_ptr(), m_in2.c_ptr(), result); \ +} + + MK_BIN_PRED_REDUCE(reduce_eq, mk_eq); + MK_BIN_PRED_REDUCE(reduce_sle, mk_sle); + MK_BIN_PRED_REDUCE(reduce_ule, mk_ule); + MK_BIN_PRED_REDUCE(reduce_umul_no_overflow, mk_umul_no_overflow); + MK_BIN_PRED_REDUCE(reduce_smul_no_overflow, mk_smul_no_overflow); + MK_BIN_PRED_REDUCE(reduce_smul_no_underflow, mk_smul_no_underflow); + +#define MK_PARAMETRIC_UNARY_REDUCE(OP, BB_OP) \ +void OP(expr * arg, unsigned n, expr_ref & result) { \ + m_in1.reset(); \ + get_bits(arg, m_in1); \ + m_out.reset(); \ + m_blaster.BB_OP(m_in1.size(), m_in1.c_ptr(), n, m_out); \ + result = mk_mkbv(m_out); \ +} + +MK_PARAMETRIC_UNARY_REDUCE(reduce_sign_extend, mk_sign_extend); + + void reduce_ite(expr * arg1, expr * arg2, expr * arg3, expr_ref & result) { + m_in1.reset(); + m_in2.reset(); + get_bits(arg2, m_in1); + get_bits(arg3, m_in2); + m_out.reset(); + m_blaster.mk_multiplexer(arg1, m_in1.size(), m_in1.c_ptr(), m_in2.c_ptr(), m_out); + result = mk_mkbv(m_out); + } + + void reduce_concat(unsigned num_args, expr * const * args, expr_ref & result) { + m_out.reset(); + unsigned i = num_args; + while (i > 0) { + i--; + m_in1.reset(); + get_bits(args[i], m_in1); + m_out.append(m_in1.size(), m_in1.c_ptr()); + } + result = mk_mkbv(m_out); + } + + void reduce_extract(unsigned start, unsigned end, expr * arg, expr_ref & result) { + m_in1.reset(); + get_bits(arg, m_in1); + m_out.reset(); + for (unsigned i = start; i <= end; ++i) + m_out.push_back(m_in1.get(i)); + result = mk_mkbv(m_out); + } + + void reduce_num(func_decl * f, expr_ref & result) { + SASSERT(f->get_num_parameters() == 2); + SASSERT(f->get_parameter(0).is_rational()); + SASSERT(f->get_parameter(1).is_int()); + rational v = f->get_parameter(0).get_rational(); + unsigned bv_sz = f->get_parameter(1).get_int(); + m_out.reset(); + m_blaster.num2bits(v, bv_sz, m_out); + result = mk_mkbv(m_out); + } + + void throw_unsupported() { + throw tactic_exception("operator is not supported, you must simplify the goal before applying bit-blasting"); + } + + void blast_bv_term(expr * t, expr_ref & result, proof_ref & result_pr) { + ptr_buffer bits; + unsigned bv_size = butil().get_bv_size(t); + for (unsigned i = 0; i < bv_size; i++) { + parameter p(i); + bits.push_back(m().mk_app(butil().get_family_id(), OP_BIT2BOOL, 1, &p, 1, &t)); + } + result = mk_mkbv(bits); + result_pr = 0; + } + + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + result_pr = 0; + if (num == 0 && f->get_family_id() == null_family_id && butil().is_bv_sort(f->get_range())) { + mk_const(f, result); + return BR_DONE; + } + + if (m().is_eq(f)) { + SASSERT(num == 2); + if (butil().is_bv(args[0])) { + reduce_eq(args[0], args[1], result); + return BR_DONE; + } + return BR_FAILED; + } + + if (m().is_ite(f)) { + SASSERT(num == 3); + if (butil().is_bv(args[1])) { + reduce_ite(args[0], args[1], args[2], result); + return BR_DONE; + } + return BR_FAILED; + } + + if (f->get_family_id() == butil().get_family_id()) { + switch (f->get_decl_kind()) { + case OP_BV_NUM: + SASSERT(num == 0); + reduce_num(f, result); + return BR_DONE; + case OP_BADD: + if (!m_blast_add) + return BR_FAILED; + reduce_add(num, args, result); + return BR_DONE; + case OP_BMUL: + if (!m_blast_mul) + return BR_FAILED; + reduce_mul(num, args, result); + return BR_DONE; + + case OP_BSDIV: + case OP_BUDIV: + case OP_BSREM: + case OP_BUREM: + case OP_BSMOD: + if (m_blast_mul) + throw_unsupported(); // must simplify to DIV_I AND DIV0 + return BR_FAILED; // keep them + + case OP_BSDIV0: + case OP_BUDIV0: + case OP_BSREM0: + case OP_BUREM0: + case OP_BSMOD0: + return BR_FAILED; + + case OP_BSDIV_I: + if (!m_blast_mul) + return BR_FAILED; + SASSERT(num == 2); + reduce_sdiv(args[0], args[1], result); + return BR_DONE; + case OP_BUDIV_I: + if (!m_blast_mul) + return BR_FAILED; + SASSERT(num == 2); + reduce_udiv(args[0], args[1], result); + return BR_DONE; + case OP_BSREM_I: + if (!m_blast_mul) + return BR_FAILED; + SASSERT(num == 2); + reduce_srem(args[0], args[1], result); + return BR_DONE; + case OP_BUREM_I: + if (!m_blast_mul) + return BR_FAILED; + SASSERT(num == 2); + reduce_urem(args[0], args[1], result); + return BR_DONE; + case OP_BSMOD_I: + if (!m_blast_mul) + return BR_FAILED; + SASSERT(num == 2); + reduce_smod(args[0], args[1], result); + return BR_DONE; + case OP_ULEQ: + SASSERT(num == 2); + reduce_ule(args[0], args[1], result); + return BR_DONE; + case OP_SLEQ: + SASSERT(num == 2); + reduce_sle(args[0], args[1], result); + return BR_DONE; + case OP_BOR: + reduce_or(num, args, result); + return BR_DONE; + case OP_BNOT: + SASSERT(num == 1); + reduce_not(args[0], result); + return BR_DONE; + case OP_BXOR: + reduce_xor(num, args, result); + return BR_DONE; + + case OP_CONCAT: + reduce_concat(num, args, result); + return BR_DONE; + case OP_SIGN_EXT: + SASSERT(num == 1); + reduce_sign_extend(args[0], f->get_parameter(0).get_int(), result); + return BR_DONE; + case OP_EXTRACT: + SASSERT(num == 1); + reduce_extract(f->get_parameter(1).get_int(), f->get_parameter(0).get_int(), args[0], result); + return BR_DONE; + + case OP_BREDOR: + SASSERT(num == 1); + reduce_redor(args[0], result); + return BR_DONE; + case OP_BREDAND: + SASSERT(num == 1); + reduce_redand(args[0], result); + return BR_DONE; + case OP_BSHL: + SASSERT(num == 2); + reduce_shl(args[0], args[1], result); + return BR_DONE; + case OP_BLSHR: + SASSERT(num == 2); + reduce_lshr(args[0], args[1], result); + return BR_DONE; + case OP_BASHR: + SASSERT(num == 2); + reduce_ashr(args[0], args[1], result); + return BR_DONE; + case OP_EXT_ROTATE_LEFT: + SASSERT(num == 2); + reduce_ext_rotate_left(args[0], args[1], result); + return BR_DONE; + case OP_EXT_ROTATE_RIGHT: + SASSERT(num == 2); + reduce_ext_rotate_right(args[0], args[1], result); + return BR_DONE; + + case OP_BUMUL_NO_OVFL: + SASSERT(num == 2); + reduce_umul_no_overflow(args[0], args[1], result); + return BR_DONE; + case OP_BSMUL_NO_OVFL: + SASSERT(num == 2); + reduce_smul_no_overflow(args[0], args[1], result); + return BR_DONE; + case OP_BSMUL_NO_UDFL: + SASSERT(num == 2); + reduce_smul_no_underflow(args[0], args[1], result); + return BR_DONE; + + case OP_BIT2BOOL: + case OP_MKBV: + case OP_INT2BV: + case OP_BV2INT: + return BR_FAILED; + default: + TRACE("bit_blaster", tout << "non-supported operator: " << f->get_name() << "\n"; + for (unsigned i = 0; i < num; i++) tout << mk_ismt2_pp(args[i], m()) << std::endl;); + throw_unsupported(); + } + } + + if (m_blast_full && butil().is_bv_sort(f->get_range())) { + blast_bv_term(m().mk_app(f, num, args), result, result_pr); + return BR_DONE; + } + + return BR_FAILED; + } + + bool pre_visit(expr * t) { + if (m_blast_quant && is_quantifier(t)) { + quantifier * q = to_quantifier(t); + ptr_buffer new_bindings; + ptr_buffer new_args; + unsigned i = q->get_num_decls(); + unsigned j = 0; + while (i > 0) { + --i; + sort * s = q->get_decl_sort(i); + if (butil().is_bv_sort(s)) { + unsigned bv_size = butil().get_bv_size(s); + new_args.reset(); + for (unsigned k = 0; k < bv_size; k++) { + new_args.push_back(m().mk_var(j, m().mk_bool_sort())); + j++; + } + new_bindings.push_back(mk_mkbv(new_args)); + } + else { + new_bindings.push_back(m().mk_var(j, s)); + j++; + } + } + SASSERT(new_bindings.size() == q->get_num_decls()); + i = q->get_num_decls(); + while (i > 0) { + i--; + m_bindings.push_back(new_bindings[i]); + } + } + return true; + } + + bool reduce_var(var * t, expr_ref & result, proof_ref & result_pr) { + if (m_blast_quant) { + if (t->get_idx() >= m_bindings.size()) + return false; + result = m_bindings.get(m_bindings.size() - t->get_idx() - 1); + result_pr = 0; + return true; + } + + if (m_blast_full && butil().is_bv(t)) { + blast_bv_term(t, result, result_pr); + return true; + } + + return false; + } + + bool reduce_quantifier(quantifier * old_q, + expr * new_body, + expr * const * new_patterns, + expr * const * new_no_patterns, + expr_ref & result, + proof_ref & result_pr) { + if (!m_blast_quant) + return false; + unsigned curr_sz = m_bindings.size(); + SASSERT(old_q->get_num_decls() <= curr_sz); + unsigned num_decls = old_q->get_num_decls(); + unsigned old_sz = curr_sz - num_decls; + string_buffer<> name_buffer; + ptr_buffer new_decl_sorts; + sbuffer new_decl_names; + for (unsigned i = 0; i < num_decls; i++) { + symbol const & n = old_q->get_decl_name(i); + sort * s = old_q->get_decl_sort(i); + if (butil().is_bv_sort(s)) { + unsigned bv_size = butil().get_bv_size(s); + for (unsigned j = 0; j < bv_size; j++) { + name_buffer.reset(); + name_buffer << n << "." << j; + new_decl_names.push_back(symbol(name_buffer.c_str())); + new_decl_sorts.push_back(m().mk_bool_sort()); + } + } + else { + new_decl_sorts.push_back(s); + new_decl_names.push_back(n); + } + } + result = m().mk_quantifier(old_q->is_forall(), new_decl_sorts.size(), new_decl_sorts.c_ptr(), new_decl_names.c_ptr(), + new_body, old_q->get_weight(), old_q->get_qid(), old_q->get_skid(), + old_q->get_num_patterns(), new_patterns, old_q->get_num_no_patterns(), new_no_patterns); + result_pr = 0; + m_bindings.shrink(old_sz); + return true; + } +}; + +// CMW: GCC/LLVM do not like this definition because a symbol of the same name exists in assert_set_bit_blaster.o +// template class rewriter_tpl; + +struct bit_blaster_rewriter::imp : public rewriter_tpl { + blaster m_blaster; + blaster_rewriter_cfg m_cfg; + imp(ast_manager & m, params_ref const & p): + rewriter_tpl(m, + m.proofs_enabled(), + m_cfg), + m_blaster(m), + m_cfg(m, m_blaster, p) { + SASSERT(m_blaster.butil().get_family_id() == m.get_family_id("bv")); + } +}; + +bit_blaster_rewriter::bit_blaster_rewriter(ast_manager & m, params_ref const & p): + m_imp(alloc(imp, m, p)) { +} + +bit_blaster_rewriter::~bit_blaster_rewriter() { + dealloc(m_imp); +} + +void bit_blaster_rewriter::updt_params(params_ref const& p) { + m_imp->m_cfg.updt_params(p); +} + +void bit_blaster_rewriter::set_cancel(bool f) { + m_imp->set_cancel(f); + m_imp->m_blaster.set_cancel(f); +} + +ast_manager & bit_blaster_rewriter::m() const { + return m_imp->m(); +} + +unsigned bit_blaster_rewriter::get_num_steps() const { + return m_imp->get_num_steps(); +} + +void bit_blaster_rewriter::cleanup() { + m_imp->cleanup(); +} + +obj_map const & bit_blaster_rewriter::const2bits() const { + return m_imp->m_cfg.m_const2bits; +} + +void bit_blaster_rewriter::operator()(expr * e, expr_ref & result, proof_ref & result_proof) { + m_imp->operator()(e, result, result_proof); +} + diff --git a/lib/bit_blaster_rewriter.h b/lib/bit_blaster_rewriter.h new file mode 100644 index 000000000..4a0f3e2fe --- /dev/null +++ b/lib/bit_blaster_rewriter.h @@ -0,0 +1,42 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + bit_blaster_rewriter.h + +Abstract: + + Bit-blasting rewriter + +Author: + + Leonardo (leonardo) 2012-10-04 + +Notes: + +--*/ +#ifndef _BIT_BLASTER_REWRITER_H_ +#define _BIT_BLASTER_REWRITER_H_ + +#include"ast.h" +#include"obj_hashtable.h" +#include"params.h" + +class bit_blaster_rewriter { + struct imp; + imp * m_imp; +public: + bit_blaster_rewriter(ast_manager & m, params_ref const & p); + ~bit_blaster_rewriter(); + void updt_params(params_ref const & p); + void set_cancel(bool f); + ast_manager & m() const; + unsigned get_num_steps() const; + void cleanup(); + obj_map const& const2bits() const; + void operator()(expr * e, expr_ref & result, proof_ref & result_proof); +}; + +#endif + diff --git a/lib/bit_blaster_tactic.cpp b/lib/bit_blaster_tactic.cpp new file mode 100644 index 000000000..4e2f45a01 --- /dev/null +++ b/lib/bit_blaster_tactic.cpp @@ -0,0 +1,165 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + bit_blaster_tactic.cpp + +Abstract: + + Apply bit-blasting to a given goal + +Author: + + Leonardo (leonardo) 2011-10-25 + +Notes: + +--*/ +#include"tactical.h" +#include"bit_blaster_model_converter.h" +#include"bit_blaster_rewriter.h" +#include"ast_smt2_pp.h" +#include"model_pp.h" + +class bit_blaster_tactic : public tactic { + + struct imp { + bit_blaster_rewriter m_rewriter; + unsigned m_num_steps; + bool m_blast_quant; + + imp(ast_manager & m, params_ref const & p): + m_rewriter(m, p) { + updt_params(p); + } + + void updt_params_core(params_ref const & p) { + m_blast_quant = p.get_bool(":blast-quant", false); + } + + void updt_params(params_ref const & p) { + m_rewriter.updt_params(p); + updt_params_core(p); + } + + ast_manager & m() const { return m_rewriter.m(); } + + void set_cancel(bool f) { + m_rewriter.set_cancel(f); + } + + void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + mc = 0; pc = 0; core = 0; + bool proofs_enabled = g->proofs_enabled(); + + if (proofs_enabled && m_blast_quant) + throw tactic_exception("quantified variable blasting does not support proof generation"); + + tactic_report report("bit-blaster", *g); + + TRACE("before_bit_blaster", g->display(tout);); + m_num_steps = 0; + + expr_ref new_curr(m()); + proof_ref new_pr(m()); + unsigned size = g->size(); + for (unsigned idx = 0; idx < size; idx++) { + if (g->inconsistent()) + break; + expr * curr = g->form(idx); + m_rewriter(curr, new_curr, new_pr); + m_num_steps += m_rewriter.get_num_steps(); + if (proofs_enabled) { + proof * pr = g->pr(idx); + new_pr = m().mk_modus_ponens(pr, new_pr); + } + g->update(idx, new_curr, new_pr, g->dep(idx)); + } + + if (g->models_enabled()) + mc = mk_bit_blaster_model_converter(m(), m_rewriter.const2bits()); + else + mc = 0; + g->inc_depth(); + result.push_back(g.get()); + TRACE("after_bit_blaster", g->display(tout); if (mc) mc->display(tout); tout << "\n";); + m_rewriter.cleanup(); + } + + unsigned get_num_steps() const { return m_num_steps; } + }; + + imp * m_imp; + params_ref m_params; + +public: + bit_blaster_tactic(ast_manager & m, params_ref const & p): + m_params(p){ + m_imp = alloc(imp, m, p); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(bit_blaster_tactic, m, m_params); + } + + virtual ~bit_blaster_tactic() { + dealloc(m_imp); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + insert_max_memory(r); + insert_max_steps(r); + r.insert(":blast-mul", CPK_BOOL, "(default: true) bit-blast multipliers (and dividers, remainders)."); + r.insert(":blast-add", CPK_BOOL, "(default: true) bit-blast adders."); + r.insert(":blast-quant", CPK_BOOL, "(default: false) bit-blast quantified variables."); + r.insert(":blast-full", CPK_BOOL, "(default: false) bit-blast any term with bit-vector sort, this option will make E-matching ineffective in any pattern containing bit-vector terms."); + } + + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + (*m_imp)(g, result, mc, pc, core); + } + + virtual void cleanup() { + ast_manager & m = m_imp->m(); + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + } + + unsigned get_num_steps() const { + return m_imp->get_num_steps(); + } + +protected: + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + + +tactic * mk_bit_blaster_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(bit_blaster_tactic, m, p)); +} diff --git a/lib/bit_blaster_tactic.h b/lib/bit_blaster_tactic.h new file mode 100644 index 000000000..1454be703 --- /dev/null +++ b/lib/bit_blaster_tactic.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + bit_blaster_tactic.h + +Abstract: + + Apply bit-blasting to a given goal. + +Author: + + Leonardo (leonardo) 2011-10-25 + +Notes: + +--*/ +#ifndef _BIT_BLASTER_TACTIC_H_ +#define _BIT_BLASTER_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_bit_blaster_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/bit_blaster_tpl.h b/lib/bit_blaster_tpl.h new file mode 100644 index 000000000..7901a3187 --- /dev/null +++ b/lib/bit_blaster_tpl.h @@ -0,0 +1,135 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + bit_blaster_tpl.h + +Abstract: + + Template for bit-blaster operations + +Author: + + Leonardo de Moura (leonardo) 2011-05-02. + +Revision History: + +--*/ +#ifndef _BIT_BLASTER_TPL_H_ +#define _BIT_BLASTER_TPL_H_ + +#include"rational.h" +#include"strategy_exception.h" + +template +class bit_blaster_tpl : public Cfg { +public: + typedef rational numeral; +protected: + template + void mk_le(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref & out); + + template + void mk_sdiv_srem_smod(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits); + + template + void mk_ext_rotate_left_right(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits); + + unsigned long long m_max_memory; + volatile bool m_cancel; + bool m_use_wtm; /* Wallace Tree Multiplier */ + bool m_use_bcm; /* Booth Multiplier for constants */ + void checkpoint(); + +public: + bit_blaster_tpl(Cfg const & cfg = Cfg(), unsigned long long max_memory = UINT64_MAX, bool use_wtm = false, bool use_bcm=false): + Cfg(cfg), + m_max_memory(max_memory), + m_cancel(false), + m_use_wtm(use_wtm), + m_use_bcm(use_bcm) { + } + + void set_max_memory(unsigned long long max_memory) { + m_max_memory = max_memory; + } + + void set_cancel(bool f) { m_cancel = f; } + void cancel() { set_cancel(true); } + void reset_cancel() { set_cancel(false); } + + // Cfg required API + ast_manager & m() const { return Cfg::m(); } + numeral power(unsigned n) const { return Cfg::power(n); } + void mk_xor(expr * a, expr * b, expr_ref & r) { Cfg::mk_xor(a, b, r); } + void mk_xor3(expr * a, expr * b, expr * c, expr_ref & r) { Cfg::mk_xor3(a, b, c, r); } + void mk_carry(expr * a, expr * b, expr * c, expr_ref & r) { Cfg::mk_carry(a, b, c, r); } + void mk_iff(expr * a, expr * b, expr_ref & r) { Cfg::mk_iff(a, b, r); } + void mk_and(expr * a, expr * b, expr_ref & r) { Cfg::mk_and(a, b, r); } + void mk_and(expr * a, expr * b, expr * c, expr_ref & r) { Cfg::mk_and(a, b, c, r); } + void mk_and(unsigned sz, expr * const * args, expr_ref & r) { Cfg::mk_and(sz, args, r); } + void mk_or(expr * a, expr * b, expr_ref & r) { Cfg::mk_or(a, b, r); } + void mk_or(expr * a, expr * b, expr * c, expr_ref & r) { Cfg::mk_or(a, b, c, r); } + void mk_or(unsigned sz, expr * const * args, expr_ref & r) { Cfg::mk_or(sz, args, r); } + void mk_not(expr * a, expr_ref & r) { Cfg::mk_not(a, r); } + void mk_ite(expr * c, expr * t, expr * e, expr_ref & r) { Cfg::mk_ite(c, t, e, r); } + void mk_nand(expr * a, expr * b, expr_ref & r) { Cfg::mk_nand(a, b, r); } + void mk_nor(expr * a, expr * b, expr_ref & r) { Cfg::mk_nor(a, b, r); } + // + + + bool is_numeral(unsigned sz, expr * const * bits) const; + bool is_numeral(unsigned sz, expr * const * bits, numeral & r) const; + bool is_minus_one(unsigned sz, expr * const * bits) const; + void num2bits(numeral const & v, unsigned sz, expr_ref_vector & out_bits) const; + + void mk_half_adder(expr * a, expr * b, expr_ref & out, expr_ref & cout); + void mk_full_adder(expr * a, expr * b, expr * cin, expr_ref & out, expr_ref & cout); + void mk_neg(unsigned sz, expr * const * a_bits, expr_ref_vector & out_bits); + void mk_adder(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits); + void mk_subtracter(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits, expr_ref & cout); + void mk_multiplier(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits); + void mk_udiv_urem(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & q_bits, expr_ref_vector & r_bits); + void mk_udiv(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & q_bits); + void mk_urem(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & r_bits); + void mk_multiplexer(expr * c, unsigned sz, expr * const * t_bits, expr * const * e_bits, expr_ref_vector & out_bits); + void mk_sdiv(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits); + void mk_srem(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits); + void mk_smod(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits); + void mk_rotate_left(unsigned sz, expr * const * a_bits, unsigned n, expr_ref_vector & out_bits); + void mk_rotate_right(unsigned sz, expr * const * a_bits, unsigned n, expr_ref_vector & out_bits); + void mk_ext_rotate_left(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits); + void mk_ext_rotate_right(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits); + void mk_sign_extend(unsigned sz, expr * const * a_bits, unsigned n, expr_ref_vector & out_bits); + void mk_zero_extend(unsigned sz, expr * const * a_bits, unsigned n, expr_ref_vector & out_bits); + void mk_eq(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref & out); + void mk_is_eq(unsigned sz, expr * const * a_bits, unsigned n, expr_ref & out); + void mk_eqs(unsigned sz, expr * const * a_bits, expr_ref_vector & eqs); + void mk_shl(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits); + void mk_lshr(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits); + void mk_ashr(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits); + void mk_sle(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref & out); + void mk_ule(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref & out); + void mk_not(unsigned sz, expr * const * a_bits, expr_ref_vector & out_bits); + void mk_and(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits); + void mk_or(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits); + void mk_xor(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits); + void mk_xnor(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits); + void mk_nand(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits); + void mk_nor(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits); + void mk_redand(unsigned sz, expr * const * a_bits, expr_ref_vector & out_bits); + void mk_redor(unsigned sz, expr * const * a_bits, expr_ref_vector & out_bits); + void mk_umul_no_overflow(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref & out); + void mk_smul_no_overflow_core(unsigned sz, expr * const * a_bits, expr * const * b_bits, bool is_overflow, expr_ref & result); + void mk_smul_no_overflow(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref & out); + void mk_smul_no_underflow(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref & out); + void mk_comp(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits); + + void mk_carry_save_adder(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr * const * c_bits, expr_ref_vector & sum_bits, expr_ref_vector & carry_bits); + void mk_const_multiplier(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits); + + void mk_abs(unsigned sz, expr * const * a_bits, expr_ref_vector & out_bits); +}; + +#endif diff --git a/lib/bit_blaster_tpl_def.h b/lib/bit_blaster_tpl_def.h new file mode 100644 index 000000000..f00c17322 --- /dev/null +++ b/lib/bit_blaster_tpl_def.h @@ -0,0 +1,1201 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + bit_blaster_tpl_def.h + +Abstract: + + Template for bit-blaster operations + +Author: + + Leonardo de Moura (leonardo) 2011-05-02. + +Revision History: + +--*/ +#include"bit_blaster_tpl.h" +#include"rational.h" +#include"ast_pp.h" +#include"cooperate.h" + +template +void bit_blaster_tpl::checkpoint() { + if (memory::get_allocation_size() > m_max_memory) + throw strategy_exception(STE_MAX_MEMORY_MSG); + if (m_cancel) + throw strategy_exception(STE_CANCELED_MSG); + cooperate("bit-blaster"); +} + +/** + \brief Return true if all bits are true or false. +*/ +template +bool bit_blaster_tpl::is_numeral(unsigned sz, expr * const * bits) const { + for (unsigned i = 0; i < sz; i++) + if (!m().is_true(bits[i]) && !m().is_false(bits[i])) + return false; + return true; +} + +/** + \brief Return true if all bits are true or false, and store the number represent by + these bits in r. +*/ +template +bool bit_blaster_tpl::is_numeral(unsigned sz, expr * const * bits, numeral & r) const { + r.reset(); + for (unsigned i = 0; i < sz; i++) { + if (m().is_true(bits[i])) + r += power(i); + else if (!m().is_false(bits[i])) + return false; + } + return true; +} + +/** + \brief Return true if all bits are true. +*/ +template +bool bit_blaster_tpl::is_minus_one(unsigned sz, expr * const * bits) const { + for (unsigned i = 0; i < sz; i++) + if (!m().is_true(bits[i])) + return false; + return true; +} + +// hack to avoid GCC compilation error. +static void _num2bits(ast_manager & m, rational const & v, unsigned sz, expr_ref_vector & out_bits) { + SASSERT(v.is_nonneg()); + rational aux = v; + rational two(2); + for (unsigned i = 0; i < sz; i++) { + if ((aux % two).is_zero()) + out_bits.push_back(m.mk_false()); + else + out_bits.push_back(m.mk_true()); + aux = div(aux, two); + } +} + + +template +void bit_blaster_tpl::num2bits(numeral const & v, unsigned sz, expr_ref_vector & out_bits) const { + _num2bits(m(), v, sz, out_bits); +} + +template +void bit_blaster_tpl::mk_half_adder(expr * a, expr * b, expr_ref & out, expr_ref & cout) { + mk_xor(a, b, out); + mk_and(a, b, cout); +} + +template +void bit_blaster_tpl::mk_full_adder(expr * a, expr * b, expr * cin, expr_ref & out, expr_ref & cout) { + mk_xor3(a, b, cin, out); + mk_carry(a, b, cin, cout); +} + +template +void bit_blaster_tpl::mk_neg(unsigned sz, expr * const * a_bits, expr_ref_vector & out_bits) { + SASSERT(sz > 0); + expr_ref cin(m()), cout(m()), out(m()); + cin = m().mk_true(); + for (unsigned idx = 0; idx < sz; idx++) { + expr_ref not_a(m()); + mk_not(a_bits[idx], not_a); + if (idx < sz - 1) + mk_half_adder(not_a, cin, out, cout); + else + mk_xor(not_a, cin, out); + out_bits.push_back(out); + cin = cout; + } +} + +template +void bit_blaster_tpl::mk_adder(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits) { + SASSERT(sz > 0); + expr_ref cin(m()), cout(m()), out(m()); + cin = m().mk_false(); + for (unsigned idx = 0; idx < sz; idx++) { + if (idx < sz - 1) + mk_full_adder(a_bits[idx], b_bits[idx], cin, out, cout); + else + mk_xor3(a_bits[idx], b_bits[idx], cin, out); + out_bits.push_back(out); + cin = cout; + } + +#if 0 + static unsigned counter = 0; + counter++; + verbose_stream() << "MK_ADDER: " << counter << std::endl; +#endif +} + +template +void bit_blaster_tpl::mk_subtracter(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits, expr_ref & cout) { + SASSERT(sz > 0); + expr_ref cin(m()), out(m()); + cin = m().mk_true(); + for (unsigned j = 0; j < sz; j++) { + expr_ref not_b(m()); + mk_not(b_bits[j], not_b); + mk_full_adder(a_bits[j], not_b, cin, out, cout); + out_bits.push_back(out); + cin = cout; + } + SASSERT(out_bits.size() == sz); +} + +template +void bit_blaster_tpl::mk_multiplier(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits) { + SASSERT(sz > 0); + + if (!m_use_bcm) { + numeral n_a, n_b; + if (is_numeral(sz, a_bits, n_b)) + std::swap(a_bits, b_bits); + if (is_minus_one(sz, b_bits)) { + mk_neg(sz, a_bits, out_bits); + return; + } + if (is_numeral(sz, a_bits, n_a)) { + n_a *= n_b; + num2bits(n_a, sz, out_bits); + return; + } + } + else { + numeral n_a, n_b; + if (is_numeral(sz, a_bits, n_a)) { + mk_const_multiplier(sz, a_bits, b_bits, out_bits); + return; + } else if (is_numeral(sz, b_bits, n_b)) { + mk_const_multiplier(sz, b_bits, a_bits, out_bits); + return; + } + } + + if (!m_use_wtm) { +#if 0 + static unsigned counter = 0; + counter++; + verbose_stream() << "MK_MULTIPLIER: " << counter << std::endl; +#endif + + expr_ref_vector cins(m()), couts(m()); + expr_ref out(m()), cout(m()); + + mk_and(a_bits[0], b_bits[0], out); + out_bits.push_back(out); + + /* + out = a*b is encoded using the following circuit. + + a[0]&b[0] a[0]&b[1] a[0]&b[2] a[0]&b[3] ... + | | | | + | a[1]&b[0] - HA a[1]&b[1] - HA a[1]&b[2] - HA + | | \ | \ | \ + | | --------------- | -------------- | --- ... + | | \| \ + | | a[2]&b[0] - FA a[2]&b[1] - FA + | | | \ | \ + | | | -------------- | -- ... + | | | \| + | | | a[3]&b[0] - FA + | | | | \ + | | | | -- .... + ... ... ... ... + out[0] out[1] out[2] out[3] + + HA denotes a half-adder. + FA denotes a full-adder. + */ + + for (unsigned i = 1; i < sz; i++) { + checkpoint(); + couts.reset(); + expr_ref i1(m()), i2(m()); + mk_and(a_bits[0], b_bits[i], i1); + mk_and(a_bits[1], b_bits[i-1], i2); + if (i < sz - 1) { + mk_half_adder(i1, i2, out, cout); + couts.push_back(cout); + for (unsigned j = 2; j <= i; j++) { + expr_ref prev_out(m()); + prev_out = out; + expr_ref i3(m()); + mk_and(a_bits[j], b_bits[i-j], i3); + mk_full_adder(i3, prev_out, cins.get(j-2), out, cout); + couts.push_back(cout); + } + out_bits.push_back(out); + cins.swap(couts); + } + else { + // last step --> I don't need to generate/store couts. + mk_xor(i1, i2, out); + for (unsigned j = 2; j <= i; j++) { + expr_ref i3(m()); + mk_and(a_bits[j], b_bits[i-j], i3); + mk_xor3(i3, out, cins.get(j-2), out); + } + out_bits.push_back(out); + } + } + } + else { + // WALLACE TREE MULTIPLIER + + if (sz == 1) { + expr_ref t(m()); + mk_and(a_bits[0], b_bits[0], t); + out_bits.push_back(t); + return; + } + + // There are sz numbers to add and we use a Wallace tree to reduce that to two. + // In this tree, we reduce as early as possible, as opposed to the Dada tree where some + // additions may be delayed if they don't increase the propagation delay [which may be + // a little bit more efficient, but it's tricky to find out which additions create + // additional delays]. + + expr_ref zero(m()); + zero = m().mk_false(); + + vector< expr_ref_vector > pps; + pps.resize(sz, m()); + + for (unsigned i = 0; i < sz; i++) { + checkpoint(); + // The partial product is a_bits AND b_bits[i] + // [or alternatively ITE(b_bits[i], a_bits, bv0[sz])] + + expr_ref_vector & pp = pps[i]; + expr_ref t(m()); + for (unsigned j = 0; j < i; j++) + pp.push_back(zero); // left shift by i bits + for (unsigned j = 0; j < (sz - i); j++) { + mk_and(a_bits[j], b_bits[i], t); + pp.push_back(t); + } + + SASSERT(pps[i].size() == sz); + } + + while (pps.size() != 2) { + unsigned save_inx = 0; + unsigned i = 0; + unsigned end = pps.size() - 3; + for ( ; i <= end; i += 3) { + checkpoint(); + expr_ref_vector pp1(m()), pp2(m()), pp3(m()); + pp1.swap(pps[i]); + pp2.swap(pps[i+1]); + pp3.swap(pps[i+2]); + expr_ref_vector & sum_bits = pps[save_inx]; + expr_ref_vector & carry_bits = pps[save_inx+1]; + SASSERT(sum_bits.empty() && carry_bits.empty()); + carry_bits.push_back(zero); + mk_carry_save_adder(pp1.size(), pp1.c_ptr(), pp2.c_ptr(), pp3.c_ptr(), sum_bits, carry_bits); + carry_bits.pop_back(); + save_inx += 2; + } + + if (i == pps.size()-2) { + pps[save_inx++].swap(pps[i++]); + pps[save_inx++].swap(pps[i++]); + } + else if (i == pps.size()-1) { + pps[save_inx++].swap(pps[i++]); + } + + SASSERT (save_inx < pps.size() && i == pps.size()); + pps.shrink(save_inx); + } + + SASSERT(pps.size() == 2); + + // Now there are only two numbers to add, we can use a ripple carry adder here. + mk_adder(sz, pps[0].c_ptr(), pps[1].c_ptr(), out_bits); + } +} + + +template +void bit_blaster_tpl::mk_umul_no_overflow(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref & result) { + SASSERT(sz > 0); + expr_ref zero(m()); + zero = m().mk_false(); + ptr_buffer ext_a_bits; + ptr_buffer ext_b_bits; + ext_a_bits.append(sz, a_bits); + ext_b_bits.append(sz, b_bits); + ext_a_bits.push_back(zero); + ext_b_bits.push_back(zero); + SASSERT(ext_a_bits.size() == 1 + sz); + SASSERT(ext_b_bits.size() == 1 + sz); + expr_ref_vector mult_cout(m()); + // + // mk_multiplier will simplify output taking into account that + // the most significant bits of ext_a_bits and ext_b_bits are zero. + // + mk_multiplier(1 + sz, ext_a_bits.c_ptr(), ext_b_bits.c_ptr(), mult_cout); + expr_ref overflow1(m()), overflow2(m()), overflow(m()); + // + // ignore bits [0, sz-1] of mult_cout + // + overflow1 = mult_cout[sz].get(); + + expr_ref ovf(m()), v(m()), tmp(m()); + ovf = m().mk_false(); + v = m().mk_false(); + for (unsigned i = 1; i < sz; ++i) { + mk_or(ovf, a_bits[sz-i], ovf); + mk_and(ovf, b_bits[i], tmp); + mk_or(tmp, v, v); + } + + overflow2 = v; + mk_or(overflow1, overflow2, overflow); + mk_not(overflow, result); +} + +template +void bit_blaster_tpl::mk_smul_no_overflow_core(unsigned sz, expr * const * a_bits, expr * const * b_bits, bool is_overflow, expr_ref & result) { + SASSERT(sz > 0); + expr_ref zero(m()); + zero = m().mk_false(); + ptr_buffer ext_a_bits; + ptr_buffer ext_b_bits; + ext_a_bits.append(sz, a_bits); + ext_b_bits.append(sz, b_bits); + ext_a_bits.push_back(a_bits[sz-1]); + ext_b_bits.push_back(b_bits[sz-1]); + SASSERT(ext_a_bits.size() == 1 + sz); + SASSERT(ext_b_bits.size() == 1 + sz); + expr_ref_vector mult_cout(m()); + mk_multiplier(1 + sz, ext_a_bits.c_ptr(), ext_b_bits.c_ptr(), mult_cout); + expr_ref overflow1(m()), overflow2(m()), overflow(m()); + + // + // The two most significant bits are different. + // + mk_xor(mult_cout[sz].get(), mult_cout[sz-1].get(), overflow1); + + // + // let + // a_i = a[sz-1] xor a[i] + // b_i = b[sz-1] xor b[i] + // a_acc_i = a_{sz-2} or ... or a_{sz-1-i} + // b = (a_acc_1 and b_1) or (a_acc_2 and b_2) or ... or (a_acc_{n-2} and b_{n-2}) + // + expr_ref v(m()), tmp(m()), a(m()), b(m()), a_acc(m()), sign(m()); + a_acc = m().mk_false(); + v = m().mk_false(); + for (unsigned i = 1; i + 1 < sz; ++i) { + mk_xor(b_bits[sz-1], b_bits[i], b); + mk_xor(a_bits[sz-1], a_bits[sz-1-i], a); + mk_or(a, a_acc, a_acc); + mk_and(a_acc, b, tmp); + mk_or(tmp, v, v); + } + + overflow2 = v; + mk_or(overflow1, overflow2, overflow); + + if (is_overflow) { + // check for proper overflow + // can only happen when the sign bits are the same. + mk_iff(a_bits[sz-1], b_bits[sz-1], sign); + } + else { + // check for proper underflow + // can only happen when the sign bits are different. + mk_xor(a_bits[sz-1], b_bits[sz-1], sign); + } + mk_and(sign, overflow, overflow); + mk_not(overflow, result); +} + +template +void bit_blaster_tpl::mk_smul_no_overflow(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref & result) { + mk_smul_no_overflow_core(sz, a_bits, b_bits, true, result); +} + +template +void bit_blaster_tpl::mk_smul_no_underflow(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref & result) { + mk_smul_no_overflow_core(sz, a_bits, b_bits, false, result); +} + +template +void bit_blaster_tpl::mk_udiv_urem(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & q_bits, expr_ref_vector & r_bits) { + SASSERT(sz > 0); + + // p is the residual of each stage of the division. + expr_ref_vector & p = r_bits; + + // t is an auxiliary vector used to store the result of a subtraction + expr_ref_vector t(m()); + + // init p + p.push_back(a_bits[sz-1]); + for (unsigned i = 1; i < sz; i++) + p.push_back(m().mk_false()); + + q_bits.resize(sz); + + for (unsigned i = 0; i < sz; i++) { + checkpoint(); + // generate p - b + expr_ref q(m()); + t.reset(); + mk_subtracter(sz, p.c_ptr(), b_bits, t, q); + q_bits.set(sz - i - 1, q); + + // update p + if (i < sz - 1) { + for (unsigned j = sz - 1; j > 0; j--) { + expr_ref i(m()); + mk_ite(q, t.get(j-1), p.get(j-1), i); + p.set(j, i); + } + p.set(0, a_bits[sz - i - 2]); + } + else { + // last step: p contains the remainder + for (unsigned j = 0; j < sz; j++) { + expr_ref i(m()); + mk_ite(q, t.get(j), p.get(j), i); + p.set(j, i); + } + } + } + DEBUG_CODE({ + for (unsigned i = 0; i < sz; i++) { + SASSERT(q_bits.get(i) != 0); + }}); + TRACE("bit_blaster", + tout << "a: "; + for (unsigned i = 0; i < sz; ++i) tout << mk_pp(a_bits[i], m()) << " "; + tout << "\nb: "; + for (unsigned i = 0; i < sz; ++i) tout << mk_pp(b_bits[i], m()) << " "; + tout << "\nq: "; + for (unsigned i = 0; i < sz; ++i) tout << mk_pp(q_bits[i].get(), m()) << " "; + tout << "\nr: "; + for (unsigned i = 0; i < sz; ++i) tout << mk_pp(r_bits[i].get(), m()) << " "; + tout << "\n"; + ); +} + +template +void bit_blaster_tpl::mk_udiv(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & q_bits) { + expr_ref_vector aux(m()); + mk_udiv_urem(sz, a_bits, b_bits, q_bits, aux); +} + +template +void bit_blaster_tpl::mk_urem(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & r_bits) { + expr_ref_vector aux(m()); + mk_udiv_urem(sz, a_bits, b_bits, aux, r_bits); +} + +template +void bit_blaster_tpl::mk_multiplexer(expr * c, unsigned sz, expr * const * t_bits, expr * const * e_bits, expr_ref_vector & out_bits) { + for (unsigned i = 0; i < sz; i++) { + expr_ref t(m()); + mk_ite(c, t_bits[i], e_bits[i], t); + out_bits.push_back(t); + } +} + +template +void bit_blaster_tpl::mk_abs(unsigned sz, expr * const * a_bits, expr_ref_vector & out_bits) { + expr * a_msb = a_bits[sz - 1]; + if (m().is_false(a_msb)) { + out_bits.append(sz, a_bits); + } + else if (m().is_true(a_msb)) { + mk_neg(sz, a_bits, out_bits); + } + else { + expr_ref_vector neg_a_bits(m()); + mk_neg(sz, a_bits, neg_a_bits); + mk_multiplexer(a_msb, sz, neg_a_bits.c_ptr(), a_bits, out_bits); + } +} + +#define SDIV 0 +#define SREM 1 +#define SMOD 2 + +template +template +void bit_blaster_tpl::mk_sdiv_srem_smod(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits) { + // This definition is only good when the most significant bits are set. + // Otherwise, it will create 4 copies of the expensive sdiv/srem/smod + SASSERT(k == SDIV || k == SREM || k == SMOD); + + expr * a_msb = a_bits[sz - 1]; + expr * b_msb = b_bits[sz - 1]; + + expr_ref_vector neg_a_bits(m()); + expr_ref_vector neg_b_bits(m()); + + mk_neg(sz, a_bits, neg_a_bits); + mk_neg(sz, b_bits, neg_b_bits); + + expr_ref_vector pp_q(m()), pp_r(m()), pn_q(m()), pn_r(m()), np_q(m()), np_r(m()), nn_q(m()), nn_r(m()); + + if (!m().is_true(a_msb) && !m().is_true(b_msb)) { + mk_udiv_urem(sz, a_bits, b_bits, pp_q, pp_r); + } + else { + pp_q.resize(sz, m().mk_false()); + pp_r.resize(sz, m().mk_false()); + } + + if (!m().is_false(a_msb) && !m().is_true(b_msb)) { + mk_udiv_urem(sz, neg_a_bits.c_ptr(), b_bits, np_q, np_r); + } + else { + np_q.resize(sz, m().mk_false()); + np_r.resize(sz, m().mk_false()); + } + + if (!m().is_true(a_msb) && !m().is_false(b_msb)) { + mk_udiv_urem(sz, a_bits, neg_b_bits.c_ptr(), pn_q, pn_r); + } + else { + pn_q.resize(sz, m().mk_false()); + pn_r.resize(sz, m().mk_false()); + } + + if (!m().is_false(a_msb) && !m().is_false(b_msb)) { + mk_udiv_urem(sz, neg_a_bits.c_ptr(), neg_b_bits.c_ptr(), nn_q, nn_r); + } + else { + nn_q.resize(sz, m().mk_false()); + nn_r.resize(sz, m().mk_false()); + } + + expr_ref_vector ite1(m()), ite2(m()); + if (k == SDIV) { + expr_ref_vector & pp_out = pp_q; + expr_ref_vector np_out(m()); + expr_ref_vector pn_out(m()); + expr_ref_vector & nn_out = nn_q; + + if (!m().is_false(a_msb) && !m().is_true(b_msb)) + mk_neg(sz, np_q.c_ptr(), np_out); + else + np_out.resize(sz, m().mk_false()); + + if (!m().is_true(a_msb) && !m().is_false(b_msb)) + mk_neg(sz, pn_q.c_ptr(), pn_out); + else + pn_out.resize(sz, m().mk_false()); + +#define MK_MULTIPLEXER() \ + mk_multiplexer(b_msb, sz, nn_out.c_ptr(), np_out.c_ptr(), ite1); \ + mk_multiplexer(b_msb, sz, pn_out.c_ptr(), pp_out.c_ptr(), ite2); \ + mk_multiplexer(a_msb, sz, ite1.c_ptr(), ite2.c_ptr(), out_bits) + + MK_MULTIPLEXER(); + } + else if (k == SREM) { + expr_ref_vector & pp_out = pp_r; + expr_ref_vector np_out(m()); + expr_ref_vector & pn_out = pn_r; + expr_ref_vector nn_out(m()); + + if (!m().is_false(a_msb) && !m().is_true(b_msb)) + mk_neg(sz, np_r.c_ptr(), np_out); + else + np_out.resize(sz, m().mk_false()); + + if (!m().is_false(a_msb) && !m().is_false(b_msb)) + mk_neg(sz, nn_r.c_ptr(), nn_out); + else + nn_out.resize(sz, m().mk_false()); + MK_MULTIPLEXER(); + } + else { + SASSERT(k == SMOD); + expr_ref_vector & pp_out = pp_r; + expr_ref_vector np_out(m()); + expr_ref_vector pn_out(m()); + expr_ref_vector nn_out(m()); + + if (!m().is_false(a_msb) && !m().is_true(b_msb)) { + expr_ref cout(m()); + mk_subtracter(sz, b_bits, np_r.c_ptr(), np_out, cout); + } + else + np_out.resize(sz, m().mk_false()); + + if (!m().is_true(a_msb) && !m().is_false(b_msb)) + mk_adder(sz, b_bits, pn_r.c_ptr(), pn_out); + else + pn_out.resize(sz, m().mk_false()); + + if (!m().is_false(a_msb) && !m().is_false(b_msb)) + mk_neg(sz, nn_r.c_ptr(), nn_out); + else + nn_out.resize(sz, m().mk_false()); + + MK_MULTIPLEXER(); + } +} + +template +void bit_blaster_tpl::mk_sdiv(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits) { + expr * a_msb = a_bits[sz - 1]; + expr * b_msb = b_bits[sz - 1]; + if (m().is_false(a_msb) && m().is_false(b_msb)) { + mk_udiv(sz, a_bits, b_bits, out_bits); + } + else if (m().is_false(a_msb) && m().is_true(b_msb)) { + expr_ref_vector neg_b_bits(m()); + mk_neg(sz, b_bits, neg_b_bits); + expr_ref_vector tmp(m()); + mk_udiv(sz, a_bits, neg_b_bits.c_ptr(), tmp); + mk_neg(sz, tmp.c_ptr(), out_bits); + } + else if (m().is_true(a_msb) && m().is_false(b_msb)) { + expr_ref_vector neg_a_bits(m()); + mk_neg(sz, a_bits, neg_a_bits); + expr_ref_vector tmp(m()); + mk_udiv(sz, neg_a_bits.c_ptr(), b_bits, tmp); + mk_neg(sz, tmp.c_ptr(), out_bits); + } + else if (m().is_true(a_msb) && m().is_true(b_msb)) { + expr_ref_vector neg_a_bits(m()); + mk_neg(sz, a_bits, neg_a_bits); + expr_ref_vector neg_b_bits(m()); + mk_neg(sz, b_bits, neg_b_bits); + mk_udiv(sz, neg_a_bits.c_ptr(), neg_b_bits.c_ptr(), out_bits); + } + else { +#if 0 + // creates 4 dividers + mk_sdiv_srem_smod(sz, a_bits, b_bits, out_bits); +#else + // creates only 1 + expr_ref_vector abs_a_bits(m()); + expr_ref_vector abs_b_bits(m()); + mk_abs(sz, a_bits, abs_a_bits); + mk_abs(sz, b_bits, abs_b_bits); + expr_ref_vector udiv_bits(m()); + mk_udiv(sz, abs_a_bits.c_ptr(), abs_b_bits.c_ptr(), udiv_bits); + expr_ref_vector neg_udiv_bits(m()); + mk_neg(sz, udiv_bits.c_ptr(), neg_udiv_bits); + expr_ref c(m()); + mk_iff(a_msb, b_msb, c); + mk_multiplexer(c, sz, udiv_bits.c_ptr(), neg_udiv_bits.c_ptr(), out_bits); +#endif + } +} + +template +void bit_blaster_tpl::mk_srem(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits) { + expr * a_msb = a_bits[sz - 1]; + expr * b_msb = b_bits[sz - 1]; + if (m().is_false(a_msb) && m().is_false(b_msb)) { + mk_urem(sz, a_bits, b_bits, out_bits); + } + else if (m().is_false(a_msb) && m().is_true(b_msb)) { + expr_ref_vector neg_b_bits(m()); + mk_neg(sz, b_bits, neg_b_bits); + mk_urem(sz, a_bits, neg_b_bits.c_ptr(), out_bits); + } + else if (m().is_true(a_msb) && m().is_false(b_msb)) { + expr_ref_vector neg_a_bits(m()); + mk_neg(sz, a_bits, neg_a_bits); + expr_ref_vector tmp(m()); + mk_urem(sz, neg_a_bits.c_ptr(), b_bits, tmp); + mk_neg(sz, tmp.c_ptr(), out_bits); + } + else if (m().is_true(a_msb) && m().is_true(b_msb)) { + expr_ref_vector neg_a_bits(m()); + mk_neg(sz, a_bits, neg_a_bits); + expr_ref_vector neg_b_bits(m()); + mk_neg(sz, b_bits, neg_b_bits); + expr_ref_vector tmp(m()); + mk_urem(sz, neg_a_bits.c_ptr(), neg_b_bits.c_ptr(), tmp); + mk_neg(sz, tmp.c_ptr(), out_bits); + } + else { +#if 0 + // creates 4 urem + mk_sdiv_srem_smod(sz, a_bits, b_bits, out_bits); +#else + // creates only 1 + expr_ref_vector abs_a_bits(m()); + expr_ref_vector abs_b_bits(m()); + mk_abs(sz, a_bits, abs_a_bits); + mk_abs(sz, b_bits, abs_b_bits); + expr_ref_vector urem_bits(m()); + mk_urem(sz, abs_a_bits.c_ptr(), abs_b_bits.c_ptr(), urem_bits); + expr_ref_vector neg_urem_bits(m()); + mk_neg(sz, urem_bits.c_ptr(), neg_urem_bits); + mk_multiplexer(a_msb, sz, neg_urem_bits.c_ptr(), urem_bits.c_ptr(), out_bits); +#endif + } +} + +/** + \brief Generate circuit for signed mod. + + This function implements the semantics of bvsmod given below for two bits: + + (define-fun bvsmod_def ((s (_ BitVec 2)) (t (_ BitVec 2))) (_ BitVec 2) + (let ((msb_s ((_ extract 1 1) s)) + (msb_t ((_ extract 1 1) t))) + (let ((abs_s (ite (= msb_s #b0) s (bvneg s))) + (abs_t (ite (= msb_t #b0) t (bvneg t)))) + (let ((u (bvurem abs_s abs_t))) + (ite (= u (_ bv0 2)) + u + (ite (and (= msb_s #b0) (= msb_t #b0)) + u + (ite (and (= msb_s #b1) (= msb_t #b0)) + (bvadd (bvneg u) t) + (ite (and (= msb_s #b0) (= msb_t #b1)) + (bvadd u t) + (bvneg u))))))))) + + Note: The semantics is sensitive to the order of these tests. + It is unsound to test first for whether the most significant + bits of s and t are known and use the cases for those. If + u is 0 then the result is 0. +*/ + +template +void bit_blaster_tpl::mk_smod(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits) { + expr * a_msb = a_bits[sz - 1]; + expr * b_msb = b_bits[sz - 1]; + + expr_ref_vector abs_a_bits(m()); + expr_ref_vector abs_b_bits(m()); + mk_abs(sz, a_bits, abs_a_bits); + mk_abs(sz, b_bits, abs_b_bits); + expr_ref_vector u_bits(m()); + mk_urem(sz, abs_a_bits.c_ptr(), abs_b_bits.c_ptr(), u_bits); + expr_ref_vector neg_u_bits(m()); + mk_neg(sz, u_bits.c_ptr(), neg_u_bits); + expr_ref_vector neg_u_add_b(m()); + mk_adder(sz, neg_u_bits.c_ptr(), b_bits, neg_u_add_b); + expr_ref_vector u_add_b(m()); + mk_adder(sz, u_bits.c_ptr(), b_bits, u_add_b); + expr_ref_vector zero(m()); + num2bits(numeral(0), sz, zero); + expr_ref u_eq_0(m()); + mk_eq(sz, u_bits.c_ptr(), zero.c_ptr(), u_eq_0); + + expr_ref_vector & pp_bits = u_bits; // pos & pos case + expr_ref_vector & pn_bits = u_add_b; // pos & neg case + expr_ref_vector & np_bits = neg_u_add_b; // neg & pos case + expr_ref_vector & nn_bits = neg_u_bits; // neg & neg case + + expr_ref_vector ite1(m()); + expr_ref_vector ite2(m()); + expr_ref_vector body(m()); + mk_multiplexer(b_msb, sz, nn_bits.c_ptr(), np_bits.c_ptr(), ite1); + mk_multiplexer(b_msb, sz, pn_bits.c_ptr(), pp_bits.c_ptr(), ite2); + mk_multiplexer(a_msb, sz, ite1.c_ptr(), ite2.c_ptr(), body); + mk_multiplexer(u_eq_0, sz, u_bits.c_ptr(), body.c_ptr(), out_bits); + +} + +template +void bit_blaster_tpl::mk_eq(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref & out) { + expr_ref_vector out_bits(m()); + for (unsigned i = 0; i < sz; i++) { + mk_iff(a_bits[i], b_bits[i], out); + out_bits.push_back(out); + } + mk_and(out_bits.size(), out_bits.c_ptr(), out); +} + +template +void bit_blaster_tpl::mk_rotate_left(unsigned sz, expr * const * a_bits, unsigned n, expr_ref_vector & out_bits) { + n = n % sz; + for (unsigned i = sz - n; i < sz; i++) + out_bits.push_back(a_bits[i]); + for (unsigned i = 0 ; i < sz - n; i++) + out_bits.push_back(a_bits[i]); +} + +template +void bit_blaster_tpl::mk_rotate_right(unsigned sz, expr * const * a_bits, unsigned n, expr_ref_vector & out_bits) { + n = n % sz; + mk_rotate_left(sz, a_bits, sz - n, out_bits); +} + +template +void bit_blaster_tpl::mk_sign_extend(unsigned sz, expr * const * a_bits, unsigned n, expr_ref_vector & out_bits) { + for (unsigned i = 0; i < sz; i++) + out_bits.push_back(a_bits[i]); + expr * high_bit = a_bits[sz - 1]; + for (unsigned i = sz; i < sz + n; i++) + out_bits.push_back(high_bit); +} + +template +void bit_blaster_tpl::mk_zero_extend(unsigned sz, expr * const * a_bits, unsigned n, expr_ref_vector & out_bits) { + for (unsigned i = 0; i < sz; i++) + out_bits.push_back(a_bits[i]); + expr * high_bit = m().mk_false(); + for (unsigned i = sz; i < sz + n; i++) + out_bits.push_back(high_bit); +} + +/** + \brief Return an expression that is true iff a_bits represents the number n. +*/ +template +void bit_blaster_tpl::mk_is_eq(unsigned sz, expr * const * a_bits, unsigned n, expr_ref & out) { + numeral two(2); + expr_ref_vector out_bits(m()); + for (unsigned i = 0; i < sz; i++) { + if (n % 2 == 0) { + expr_ref not_a(m()); + mk_not(a_bits[i], not_a); + out_bits.push_back(not_a); + } + else { + out_bits.push_back(a_bits[i]); + } + n = n / 2; + } + mk_and(out_bits.size(), out_bits.c_ptr(), out); +} + +/** + \brief Store in eqs the equalities a_bits = 0, a_bits = 1, ..., a_bits = sz -1. +*/ +template +void bit_blaster_tpl::mk_eqs(unsigned sz, expr * const * a_bits, expr_ref_vector & eqs) { + for (unsigned i = 0; i < sz; i++) { + expr_ref eq(m()); + mk_is_eq(sz, a_bits, i, eq); + eqs.push_back(eq); + } +} + +template +void bit_blaster_tpl::mk_shl(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits) { + numeral k; + if (is_numeral(sz, b_bits, k)) { + unsigned n = static_cast(k.get_int64()); + if (n >= sz) n = sz; + unsigned pos; + for (pos = 0; pos < n; pos++) + out_bits.push_back(m().mk_false()); + for (unsigned i = 0; pos < sz; pos++, i++) + out_bits.push_back(a_bits[i]); + } + else { + expr_ref_vector eqs(m()); + mk_eqs(sz, b_bits, eqs); + for (unsigned i = 0; i < sz; i++) { + checkpoint(); + expr_ref out(m()); + mk_ite(eqs.get(i), a_bits[0], m().mk_false(), out); + for (unsigned j = 1; j <= i; j++) { + expr_ref new_out(m()); + mk_ite(eqs.get(i - j), a_bits[j], out, new_out); + out = new_out; + } + out_bits.push_back(out); + } + } +} + +template +void bit_blaster_tpl::mk_lshr(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits) { + numeral k; + if (is_numeral(sz, b_bits, k)) { + unsigned n = static_cast(k.get_int64()); + unsigned pos = 0; + for (unsigned i = n; i < sz; pos++, i++) + out_bits.push_back(a_bits[i]); + for (; pos < sz; pos++) + out_bits.push_back(m().mk_false()); + } + else { + expr_ref_vector eqs(m()); + mk_eqs(sz, b_bits, eqs); + out_bits.resize(sz); + for (unsigned i = 0; i < sz; i++) { + checkpoint(); + expr_ref out(m()); + mk_ite(eqs.get(i), a_bits[sz-1], m().mk_false(), out); + for (unsigned j = 1; j <= i; j++) { + expr_ref new_out(m()); + mk_ite(eqs.get(i - j), a_bits[sz - j - 1], out, new_out); + out = new_out; + } + out_bits.set(sz - i - 1, out); + } + } +} + +template +void bit_blaster_tpl::mk_ashr(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits) { + numeral k; + if (is_numeral(sz, b_bits, k)) { + unsigned n = static_cast(k.get_int64()); + unsigned pos = 0; + for (unsigned i = n; i < sz; pos++, i++) + out_bits.push_back(a_bits[i]); + for (; pos < sz; pos++) + out_bits.push_back(a_bits[sz-1]); + } + else { + expr_ref_vector eqs(m()); + mk_eqs(sz, b_bits, eqs); + out_bits.resize(sz); + for (unsigned i = 0; i < sz; i++) { + checkpoint(); + expr_ref out(m()); + out = a_bits[sz-1]; + for (unsigned j = 1; j <= i; j++) { + expr_ref new_out(m()); + mk_ite(eqs.get(i - j), a_bits[sz - j - 1], out, new_out); + out = new_out; + } + TRACE("bit_blaster_tpl", tout << (sz - i - 1) << " :\n" << mk_pp(out, m()) << "\n";); + out_bits.set(sz - i - 1, out); + } + } +} + +template +template +void bit_blaster_tpl::mk_ext_rotate_left_right(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits) { + numeral k; + if (is_numeral(sz, b_bits, k) && k.is_unsigned()) { + if (Left) + mk_rotate_left(sz, a_bits, static_cast(k.get_uint64()), out_bits); + else + mk_rotate_right(sz, a_bits, static_cast(k.get_uint64()), out_bits); + } + else { + expr_ref_vector sz_bits(m()); + expr_ref_vector masked_b_bits(m()); + expr_ref_vector eqs(m()); + numeral sz_numeral(sz); + num2bits(sz_numeral, sz, sz_bits); + mk_urem(sz, b_bits, sz_bits.c_ptr(), masked_b_bits); + mk_eqs(sz, masked_b_bits.c_ptr(), eqs); + for (unsigned i = 0; i < sz; i++) { + checkpoint(); + expr_ref out(m()); + out = a_bits[i]; + for (unsigned j = 1; j < sz; j++) { + expr_ref new_out(m()); + unsigned src = (Left ? (i - j) : (i + j)) % sz; + mk_ite(eqs.get(j), a_bits[src], out, new_out); + out = new_out; + } + out_bits.push_back(out); + } + } +} + +template +void bit_blaster_tpl::mk_ext_rotate_left(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits) { + mk_ext_rotate_left_right(sz, a_bits, b_bits, out_bits); +} + +template +void bit_blaster_tpl::mk_ext_rotate_right(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits) { + mk_ext_rotate_left_right(sz, a_bits, b_bits, out_bits); +} + +template +template +void bit_blaster_tpl::mk_le(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref & out) { + SASSERT(sz > 0); + expr_ref i1(m()), i2(m()), i3(m()), not_a(m()); + mk_not(a_bits[0], not_a); + mk_or(not_a, b_bits[0], out); + for (unsigned idx = 1; idx < (Signed ? sz - 1 : sz); idx++) { + mk_not(a_bits[idx], not_a); + mk_and(not_a, b_bits[idx], i1); + mk_and(not_a, out, i2); + mk_and(b_bits[idx], out, i3); + mk_or(i1, i2, i3, out); + } + if (Signed) { + expr_ref not_b(m()); + mk_not(b_bits[sz-1], not_b); + mk_and(not_b, a_bits[sz-1], i1); + mk_and(not_b, out, i2); + mk_and(a_bits[sz-1], out, i3); + mk_or(i1, i2, i3, out); + } +} + +template +void bit_blaster_tpl::mk_sle(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref & out) { + mk_le(sz, a_bits, b_bits, out); +} + +template +void bit_blaster_tpl::mk_ule(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref & out) { + mk_le(sz, a_bits, b_bits, out); +} + +template +void bit_blaster_tpl::mk_not(unsigned sz, expr * const * a_bits, expr_ref_vector & out_bits) { + for (unsigned i = 0; i < sz; i++) { + expr_ref t(m()); + mk_not(a_bits[i], t); + out_bits.push_back(t); + } +} + +#define MK_BINARY(NAME, OP) \ +template \ +void bit_blaster_tpl::NAME(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits) { \ + for (unsigned i = 0; i < sz; i++) { \ + expr_ref t(m()); \ + OP(a_bits[i], b_bits[i], t); \ + out_bits.push_back(t); \ + } \ +} + +MK_BINARY(mk_and, mk_and); +MK_BINARY(mk_or, mk_or); +MK_BINARY(mk_xor, mk_xor); +MK_BINARY(mk_xnor, mk_iff); +MK_BINARY(mk_nand, mk_nand); +MK_BINARY(mk_nor, mk_nor); + +template +void bit_blaster_tpl::mk_redand(unsigned sz, expr * const * a_bits, expr_ref_vector & out_bits) { + expr_ref tmp(m()); + mk_and(sz, a_bits, tmp); + out_bits.push_back(tmp); +} + +template +void bit_blaster_tpl::mk_redor(unsigned sz, expr * const * a_bits, expr_ref_vector & out_bits) { + expr_ref tmp(m()); + mk_or(sz, a_bits, tmp); + out_bits.push_back(tmp); +} + +template +void bit_blaster_tpl::mk_comp(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits) { + expr_ref tmp(m()); + mk_eq(sz, a_bits, b_bits, tmp); + out_bits.push_back(tmp); +} + +template +void bit_blaster_tpl::mk_carry_save_adder(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr * const * c_bits, expr_ref_vector & sum_bits, expr_ref_vector & carry_bits) { + expr_ref t(m()); + for (unsigned i = 0; i < sz; i++) { + mk_xor3(a_bits[i], b_bits[i], c_bits[i], t); + sum_bits.push_back(t); + mk_carry(a_bits[i], b_bits[i], c_bits[i], t); + carry_bits.push_back(t); + } +} + +template +void bit_blaster_tpl::mk_const_multiplier(unsigned sz, expr * const * a_bits, expr * const * b_bits, expr_ref_vector & out_bits) { + DEBUG_CODE({ + numeral x; + SASSERT(is_numeral(sz, a_bits, x)); + SASSERT(out_bits.empty()); + }); + + expr_ref_vector minus_b_bits(m()), tmp(m()); + mk_neg(sz, b_bits, minus_b_bits); + + out_bits.resize(sz, m().mk_false()); + +#if 1 + bool last=false, now; + for (unsigned i = 0; i < sz; i++) { + now = m().is_true(a_bits[i]); + SASSERT(now || m().is_false(a_bits[i])); + tmp.reset(); + + if (now && !last) { + mk_adder(sz - i, out_bits.c_ptr() + i, minus_b_bits.c_ptr(), tmp); + for (unsigned j = 0; j < (sz - i); j++) + out_bits.set(i+j, tmp.get(j)); // do not use [], it does not work on Linux. + } + else if (!now && last) { + mk_adder(sz - i, out_bits.c_ptr() + i, b_bits, tmp); + for (unsigned j = 0; j < (sz - i); j++) + out_bits.set(i+j, tmp.get(j)); // do not use [], it does not work on Linux. + } + + last = now; + } +#else + // Radix 4 Booth encoder + // B = b_bits, -B = minus_b_bits + // 2B = b2_bits, -2B = minus_b2_bits + + expr_ref_vector b2_bits(m()); + expr_ref_vector minus_b2_bits(m()); + + b2_bits.push_back(m().mk_false()); + minus_b2_bits.push_back(m().mk_false()); + for (unsigned i = 0; i < sz-1; i++) { + b2_bits.push_back(b_bits[i]); + minus_b2_bits.push_back(minus_b_bits.get(i)); + } + + bool last=false, now1, now2; + for (unsigned i = 0; i < sz; i += 2) { + now1 = m().is_true(a_bits[i]); + now2 = m().is_true(a_bits[i+1]); + SASSERT(now1 || m().is_false(a_bits[i])); + SASSERT(now2 || m().is_false(a_bits[i+1])); + tmp.reset(); + + if ((!now2 && !now1 && last) || + (!now2 && now1 && !last)) { // Add B + mk_adder(sz - i, out_bits.c_ptr() + i, b_bits, tmp); + for (unsigned j = 0; j < (sz - i); j++) + out_bits.set(i+j, tmp.get(j)); + } + else if (!now2 && now1 && last) { // Add 2B + mk_adder(sz - i, out_bits.c_ptr() + i, b2_bits.c_ptr(), tmp); + for (unsigned j = 0; j < (sz - i); j++) + out_bits.set(i+j, tmp.get(j)); + } + else if (now2 && !now1 && !last) { // Add -2B + mk_adder(sz - i, out_bits.c_ptr() + i, minus_b2_bits.c_ptr(), tmp); + for (unsigned j = 0; j < (sz - i); j++) + out_bits.set(i+j, tmp.get(j)); + } + else if ((now2 && !now1 && last) || + (now2 && now1 && !last)) { // Add -B + mk_adder(sz - i, out_bits.c_ptr() + i, minus_b_bits.c_ptr(), tmp); + for (unsigned j = 0; j < (sz - i); j++) + out_bits.set(i+j, tmp.get(j)); + } + + last = now2; + } +#endif + + TRACE("bit_blaster_tpl_booth", for (unsigned i=0; i> 1; + r++; + } + return r; +} +#endif + +/** + \brief Return the position of the most significant (set) bit of a + nonzero unsigned integer. +*/ +unsigned msb_pos(unsigned v) { + SASSERT(v != 0); +#ifdef Z3DEBUG + unsigned expected = slow_msb_pos(v); +#endif + unsigned r, shift; + r = (v > 0xFFFF) << 4; + v >>= r; + shift = (v > 0xFF) << 3; + v >>= shift; + r |= shift; + shift = (v > 0xF) << 2; + v >>= shift; + r |= shift; + shift = (v > 0x3) << 1; + v >>= shift; + r |= shift; + r |= (v >> 1); + SASSERT(r == expected); + return r; +} + +/** + \brief Return the number of leading zeros bits in a nonzero unsigned integer. +*/ +unsigned nlz_core(unsigned x) { + SASSERT(x != 0); + return 31 - msb_pos(x); +} + +/** + \brief Return the number of leading zero bits in data (a number of sz words). +*/ +unsigned nlz(unsigned sz, unsigned const * data) { + unsigned r = 0; + unsigned i = sz; + while (i > 0) { + --i; + unsigned d = data[i]; + if (d == 0) + r += 32; + else + return r + nlz_core(d); + } + return r; +} + +/** + \brief Return the number of trailing zeros in a nonzero unsigned number. +*/ +unsigned ntz_core(unsigned x) { + SASSERT(x != 0); + float f = static_cast(x & static_cast(-static_cast(x))); + return (*reinterpret_cast(&f) >> 23) - 0x7f; +} + +/** + \brief Return the number of trailing zero bits in data (a number of sz words). +*/ +unsigned ntz(unsigned sz, unsigned const * data) { + unsigned r = 0; + for (unsigned i = 0; i < sz; i++) { + unsigned d = data[i]; + if (d == 0) + r += 32; + else + return r + ntz_core(d); + } + return r; +} + +/** + \brief dst <- src + + Trucate if src_sz > dst_sz. + Fill range [src_sz, dst_sz) of dst with zeros if dst_sz > src_sz. +*/ +void copy(unsigned src_sz, unsigned const * src, + unsigned dst_sz, unsigned * dst) { + if (dst_sz >= src_sz) { + unsigned i; + for (i = 0; i < src_sz; i++) + dst[i] = src[i]; + for (; i < dst_sz; i++) + dst[i] = 0; + } + else { + SASSERT(dst_sz < src_sz); + for (unsigned i = 0; i < dst_sz; i++) + dst[i] = src[i]; + } +} + +/** + \brief Return true if all words of data are zero. +*/ +bool is_zero(unsigned sz, unsigned const * data) { + for (unsigned i = 0; i < sz; i++) + if (data[i]) + return false; + return true; +} + +/** + \brief Set all words of data to zero. +*/ +void reset(unsigned sz, unsigned * data) { + for (unsigned i = 0; i < sz; i++) + data[i] = 0; +} + +/** + \brief dst <- src << k + Store in dst the result of shifting src k bits to the left. + The result is truncated by dst_sz. + + \pre src_sz != 0 + \pre dst_sz != 0 +*/ +void shl(unsigned src_sz, unsigned const * src, unsigned k, + unsigned dst_sz, unsigned * dst) { + SASSERT(src_sz != 0); + SASSERT(dst_sz != 0); + SASSERT(k != 0); + unsigned word_shift = k / (8 * sizeof(unsigned)); + unsigned bit_shift = k % (8 * sizeof(unsigned)); + if (word_shift > 0) { + unsigned j = src_sz; + unsigned i = src_sz + word_shift; + if (i > dst_sz) { + if (j >= i - dst_sz) + j -= (i - dst_sz); + else + j = 0; + i = dst_sz; + } + else if (i < dst_sz) { + for (unsigned r = i; r < dst_sz; r++) + dst[r] = 0; + } + while (j > 0) { + --j; --i; + dst[i] = src[j]; + } + while (i > 0) { + --i; + dst[i] = 0; + } + if (bit_shift > 0) { + unsigned comp_shift = (8 * sizeof(unsigned)) - bit_shift; + unsigned prev = 0; + for (unsigned i = word_shift; i < dst_sz; i++) { + unsigned new_prev = (dst[i] >> comp_shift); + dst[i] <<= bit_shift; + dst[i] |= prev; + prev = new_prev; + } + } + } + else { + unsigned comp_shift = (8 * sizeof(unsigned)) - bit_shift; + unsigned prev = 0; + if (src_sz > dst_sz) + src_sz = dst_sz; + for (unsigned i = 0; i < src_sz; i++) { + unsigned new_prev = (src[i] >> comp_shift); + dst[i] = src[i]; + dst[i] <<= bit_shift; + dst[i] |= prev; + prev = new_prev; + } + if (dst_sz > src_sz) { + dst[src_sz] = prev; + for (unsigned i = src_sz+1; i < dst_sz; i++) + dst[i] = 0; + } + } +} + +/** + \brief dst <- src >> k + Store in dst the result of shifting src k bits to the right. + + \pre dst must have size sz. + \pre src_sz != 0 + \pre dst_sz != 0 +*/ +void shr(unsigned sz, unsigned const * src, unsigned k, unsigned * dst) { + unsigned digit_shift = k / (8 * sizeof(unsigned)); + if (digit_shift >= sz) { + reset(sz, dst); + return; + } + unsigned bit_shift = k % (8 * sizeof(unsigned)); + unsigned comp_shift = (8 * sizeof(unsigned)) - bit_shift; + unsigned new_sz = sz - digit_shift; + if (new_sz < sz) { + unsigned i = 0; + unsigned j = digit_shift; + if (bit_shift != 0) { + for (; i < new_sz - 1; i++, j++) { + dst[i] = src[j]; + dst[i] >>= bit_shift; + dst[i] |= (src[j+1] << comp_shift); + } + dst[i] = src[j]; + dst[i] >>= bit_shift; + } + else { + for (; i < new_sz; i++, j++) { + dst[i] = src[j]; + } + } + for (unsigned i = new_sz; i < sz; i++) + dst[i] = 0; + } + else { + SASSERT(new_sz == sz); + SASSERT(bit_shift != 0); + unsigned i = 0; + for (; i < new_sz - 1; i++) { + dst[i] = src[i]; + dst[i] >>= bit_shift; + dst[i] |= (src[i+1] << comp_shift); + } + dst[i] = src[i]; + dst[i] >>= bit_shift; + } +} + +void shr(unsigned src_sz, unsigned const * src, unsigned k, unsigned dst_sz, unsigned * dst) { + unsigned digit_shift = k / (8 * sizeof(unsigned)); + if (digit_shift >= src_sz) { + reset(dst_sz, dst); + return; + } + unsigned bit_shift = k % (8 * sizeof(unsigned)); + unsigned comp_shift = (8 * sizeof(unsigned)) - bit_shift; + unsigned new_sz = src_sz - digit_shift; + if (digit_shift > 0) { + unsigned i = 0; + unsigned j = digit_shift; + if (bit_shift != 0) { + unsigned sz = new_sz; + if (new_sz > dst_sz) + sz = dst_sz; + for (; i < sz - 1; i++, j++) { + dst[i] = src[j]; + dst[i] >>= bit_shift; + dst[i] |= (src[j+1] << comp_shift); + } + dst[i] = src[j]; + dst[i] >>= bit_shift; + if (new_sz > dst_sz) + dst[i] |= (src[j+1] << comp_shift); + } + else { + if (new_sz > dst_sz) + new_sz = dst_sz; + for (; i < new_sz; i++, j++) { + dst[i] = src[j]; + } + } + } + else { + SASSERT(new_sz == src_sz); + SASSERT(bit_shift != 0); + unsigned sz = new_sz; + if (new_sz > dst_sz) + sz = dst_sz; + unsigned i = 0; + for (; i < sz - 1; i++) { + dst[i] = src[i]; + dst[i] >>= bit_shift; + dst[i] |= (src[i+1] << comp_shift); + } + dst[i] = src[i]; + dst[i] >>= bit_shift; + if (new_sz > dst_sz) + dst[i] |= (src[i+1] << comp_shift); + } + for (unsigned i = new_sz; i < dst_sz; i++) + dst[i] = 0; +} + +/** + \brief Return true if one of the first k bits of src is not zero. +*/ +bool has_one_at_first_k_bits(unsigned sz, unsigned const * data, unsigned k) { + SASSERT(sz != 0); + unsigned word_sz = k / (8 * sizeof(unsigned)); + if (word_sz > sz) + word_sz = sz; + for (unsigned i = 0; i < word_sz; i++) { + if (data[i] != 0) + return true; + } + if (word_sz < sz) { + unsigned bit_sz = k % (8 * sizeof(unsigned)); + unsigned mask = (1 << bit_sz) - 1; + return (data[word_sz] & mask) != 0; + } + return false; +} + +bool inc(unsigned sz, unsigned * data) { + for (unsigned i = 0; i < sz; i++) { + data[i]++; + if (data[i] != 0) + return true; // no overflow + } + return false; // overflow +} + +bool dec(unsigned sz, unsigned * data) { + for (unsigned i = 0; i < sz; i++) { + data[i]--; + if (data[i] != UINT_MAX) + return true; // no underflow + } + return false; // underflow +} + +bool lt(unsigned sz, unsigned * data1, unsigned * data2) { + unsigned i = sz; + while (i > 0) { + --i; + if (data1[i] < data2[i]) + return true; + if (data1[i] > data2[i]) + return false; + } + return false; +} + +bool add(unsigned sz, unsigned const * a, unsigned const * b, unsigned * c) { + unsigned k = 0; + for (unsigned j = 0; j < sz; j++) { + unsigned r = a[j] + b[j]; + bool c1 = r < a[j]; + c[j] = r + k; + bool c2 = c[j] < r; + k = c1 | c2; + } + return k == 0; +} + diff --git a/lib/bit_util.h b/lib/bit_util.h new file mode 100644 index 000000000..a730534d2 --- /dev/null +++ b/lib/bit_util.h @@ -0,0 +1,132 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + bit_util.h + +Abstract: + + Bit hacking utilities. + +Author: + + Leonardo de Moura (leonardo) 2012-09-11. + +Revision History: + +--*/ +#ifndef _BIT_UTIL_H_ +#define _BIT_UTIL_H_ + +/** + \brief Return the position of the most significant (set) bit of a + nonzero unsigned integer. +*/ +unsigned msb_pos(unsigned v); + +/** + \brief Return the number of leading zeros bits in a nonzero unsigned integer. +*/ +unsigned nlz_core(unsigned x); + +/** + \brief Return the number of leading zero bits in data (a number of sz words). +*/ +unsigned nlz(unsigned sz, unsigned const * data); + +/** + \brief Return the number of trailing zeros in a nonzero unsigned number. +*/ +unsigned ntz_core(unsigned x); + +/** + \brief Return the number of trailing zero bits in data (a number of sz words). +*/ +unsigned ntz(unsigned sz, unsigned const * data); + +/** + \brief dst <- src + + Trucate if src_sz > dst_sz. + Fill range [src_sz, dst_sz) of dst with zeros if dst_sz > src_sz. +*/ +void copy(unsigned src_sz, unsigned const * src, unsigned dst_sz, unsigned * dst); + +/** + \brief Return true if all words of data are zero. +*/ +bool is_zero(unsigned sz, unsigned const * data); + +/** + \brief Set all words of data to zero. +*/ +void reset(unsigned sz, unsigned * data); + +/** + \brief dst <- src << k + Store in dst the result of shifting src k bits to the left. + The result is truncated by dst_sz. + + \pre src_sz != 0 + \pre dst_sz != 0 +*/ +void shl(unsigned src_sz, unsigned const * src, unsigned k, unsigned dst_sz, unsigned * dst); + +/** + \brief dst <- src >> k + Store in dst the result of shifting src k bits to the right. + + \pre dst must have size sz. + \pre src_sz != 0 + \pre dst_sz != 0 +*/ +void shr(unsigned sz, unsigned const * src, unsigned k, unsigned * dst); + +/** + \brief dst <- src >> k + Store in dst the result of shifting src k bits to the right. + + Trucate if src_sz > dst_sz. + Fill range [src_sz, dst_sz) of dst with zeros if dst_sz > src_sz. + + \pre src_sz != 0 + \pre dst_sz != 0 +*/ +void shr(unsigned src_sz, unsigned const * src, unsigned k, unsigned dst_sz, unsigned * dst); + +/** + \brief Return true if one of the first k bits of src is not zero. +*/ +bool has_one_at_first_k_bits(unsigned sz, unsigned const * data, unsigned k); + + +/** + \brief data <- data + 1 + + Return true if no overflow occurred. +*/ +bool inc(unsigned sz, unsigned * data); + +/** + \brief data <- data - 1 + + Return true if no underflow occurred. +*/ +bool dec(unsigned sz, unsigned * data); + +/** + \brief Return true if data1 < data2. + + Both must have the same size. +*/ +bool lt(unsigned sz, unsigned * data1, unsigned * data2); + + +/** + \brief Store in c the a+b. This procedure assumes that a,b,c are vectors of size sz. + Return false if a+b overflows. +*/ +bool add(unsigned sz, unsigned const * a, unsigned const * b, unsigned * c); + +#endif diff --git a/lib/bit_vector.cpp b/lib/bit_vector.cpp new file mode 100644 index 000000000..210d230bc --- /dev/null +++ b/lib/bit_vector.cpp @@ -0,0 +1,220 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + bitvector.cpp + +Abstract: + + Simple bitvector implementation + +Author: + + Leonardo de Moura (leonardo) 2006-10-03. + +Revision History: + +--*/ + +#include"bit_vector.h" +#include"trace.h" + +#define DEFAULT_CAPACITY 2 + +void bit_vector::expand_to(unsigned new_capacity) { + unsigned * new_data = alloc_svect(unsigned, new_capacity); + memset(new_data, 0, new_capacity * sizeof(unsigned)); + if (m_capacity > 0) { + memcpy(new_data, m_data, m_capacity * sizeof(unsigned)); + dealloc_svect(m_data); + } + m_data = new_data; + m_capacity = new_capacity; +} + +void bit_vector::resize(unsigned new_size, bool val) { + if (new_size <= m_num_bits) { + m_num_bits = new_size; + return; + } + + TRACE("bit_vector", tout << "expanding: " << new_size << " capacity: " << m_capacity << " num words: " + << num_words(new_size) << "\n";); + + if (num_words(new_size) > m_capacity) { + expand_to((num_words(new_size) * 3 + 1) >> 1); + } + + + unsigned bwidx = m_num_bits/32; + unsigned ewidx = num_words(new_size); + unsigned * begin = m_data + bwidx; + unsigned pos = m_num_bits % 32; + unsigned mask = (1 << pos) - 1; + int cval; + + if (val) { + *begin |= ~mask; + cval = ~0; + } + else { + *begin &= mask; + cval = 0; + } + + TRACE("bit_vector", + tout << "num_bits: " << m_num_bits << "\n"; + tout << "bwidx: " << bwidx << "\n"; + tout << "ewidx: " << ewidx << "\n"; + tout << "pos: " << pos << "\n"; + tout << "mask: " << std::hex << mask << "\n" << std::dec; + tout << "cval: " << cval << "\n";); + + if (bwidx < ewidx) { + memset(begin + 1, cval, (ewidx - bwidx - 1) * sizeof(unsigned)); + } + + m_num_bits = new_size; +} + +void bit_vector::shift_right(unsigned k) { + if (k == 0) + return; + unsigned new_num_bits = m_num_bits + k; + unsigned old_num_words = num_words(m_num_bits); + unsigned new_num_words = num_words(new_num_bits); + resize(m_num_bits + k, false); + unsigned bit_shift = k % (8 * sizeof(unsigned)); + unsigned word_shift = k / (8 * sizeof(unsigned)); + if (word_shift > 0) { + unsigned j = old_num_words; + unsigned i = old_num_words + word_shift; + while (j > 0) { + --j; --i; + m_data[i] = m_data[j]; + } + while (i > 0) { + --i; + m_data[i] = 0; + } + } + if (bit_shift > 0) { + DEBUG_CODE({ + for (unsigned i = 0; i < word_shift; i++) { + SASSERT(m_data[i] == 0); + } + }); + unsigned comp_shift = (8 * sizeof(unsigned)) - bit_shift; + unsigned prev = 0; + for (unsigned i = word_shift; i < new_num_words; i++) { + unsigned new_prev = (m_data[i] >> comp_shift); + m_data[i] <<= bit_shift; + m_data[i] |= prev; + prev = new_prev; + } + } +} + +bool bit_vector::operator==(bit_vector const & source) { + if (m_num_bits != source.m_num_bits) + return false; + unsigned n = num_words(); + if (n == 0) + return true; + unsigned i; + for (i = 0; i < n - 1; i++) { + if (m_data[i] != source.m_data[i]) + return false; + } + unsigned bit_rest = source.m_num_bits % 32; + unsigned mask = (1 << bit_rest) - 1; + return (m_data[i] & mask) == (source.m_data[i] & mask); +} + +bit_vector & bit_vector::operator|=(bit_vector const & source) { + if (size() < source.size()) + resize(source.size(), false); + unsigned n1 = num_words(); + unsigned n2 = source.num_words(); + SASSERT(n2 <= n1); + unsigned bit_rest = source.m_num_bits % 32; + if (bit_rest == 0) { + unsigned i = 0; + for (i = 0; i < n2; i++) + m_data[i] |= source.m_data[i]; + } + else { + unsigned i = 0; + for (i = 0; i < n2 - 1; i++) + m_data[i] |= source.m_data[i]; + unsigned mask = (1 << bit_rest) - 1; + m_data[i] |= source.m_data[i] & mask; + } + return *this; +} + +bit_vector & bit_vector::operator&=(bit_vector const & source) { + unsigned n1 = num_words(); + unsigned n2 = source.num_words(); + if (n1 == 0) + return *this; + if (n2 > n1) { + for (unsigned i = 0; i < n1; i++) + m_data[i] &= source.m_data[i]; + } + else { + SASSERT(n2 <= n1); + unsigned bit_rest = source.m_num_bits % 32; + unsigned i = 0; + if (bit_rest == 0) { + for (i = 0; i < n2; i++) + m_data[i] &= source.m_data[i]; + } + else { + for (i = 0; i < n2 - 1; i++) + m_data[i] &= source.m_data[i]; + unsigned mask = (1 << bit_rest) - 1; + m_data[i] &= (source.m_data[i] & mask); + + } + for (i = n2; i < n1; i++) + m_data[i] = 0; + } + return *this; +} + +void bit_vector::display(std::ostream & out) const { +#if 1 + unsigned i = m_num_bits; + while (i > 0) { + --i; + if (get(i)) + out << "1"; + else + out << "0"; + } +#else + for (unsigned i = 0; i < m_num_bits; i++) { + if (get(i)) + out << "1"; + else + out << "0"; + if ((i + 1) % 32 == 0) out << "\n"; + } +#endif +} + +void fr_bit_vector::reset() { + unsigned sz = size(); + vector::const_iterator it = m_one_idxs.begin(); + vector::const_iterator end = m_one_idxs.end(); + for (; it != end; ++it) { + unsigned idx = *it; + if (idx < sz) + unset(idx); + } + m_one_idxs.reset(); +} + + diff --git a/lib/bit_vector.h b/lib/bit_vector.h new file mode 100644 index 000000000..f451ae70f --- /dev/null +++ b/lib/bit_vector.h @@ -0,0 +1,234 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + bit_vector.h + +Abstract: + + Simple bitvector implementation. + +Author: + + Leonardo de Moura (leonardo) 2006-10-03. + +Revision History: + +--*/ +#ifndef _BIT_VECTOR_H_ +#define _BIT_VECTOR_H_ + +#include +#include"debug.h" +#include"vector.h" +#include"memory_manager.h" + +COMPILE_TIME_ASSERT(sizeof(unsigned) == 4); +#define BV_DEFAULT_CAPACITY 2 + +class bit_vector { + unsigned m_num_bits; + unsigned m_capacity; //!< in words + unsigned * m_data; + + static unsigned get_pos_mask(unsigned bit_idx) { + return 1 << (bit_idx % 32); + } + + static unsigned num_words(unsigned num_bits) { + return (num_bits % 32) == 0 ? (num_bits / 32) : ((num_bits / 32) + 1); + } + + void expand_to(unsigned new_capacity); + + void expand() { + expand_to(m_capacity == 0 ? BV_DEFAULT_CAPACITY : ((m_capacity * 3 + 1) >> 1)); + } + + unsigned get_bit_word(unsigned bit_idx) const { + SASSERT(bit_idx < size()); + return m_data[bit_idx / 32]; + } + + unsigned & get_bit_word(unsigned bit_idx) { + SASSERT(bit_idx < size()); + return m_data[bit_idx / 32]; + } + +public: + bit_vector(): + m_num_bits(0), + m_capacity(0), + m_data(0) { + } + + bit_vector(bit_vector const & source): + m_num_bits(source.m_num_bits), + m_capacity(source.m_capacity), + m_data(alloc_svect(unsigned, source.m_capacity)) { + memcpy(m_data, source.m_data, source.m_capacity * sizeof(unsigned)); + } + + bit_vector(unsigned const * source, int num_bits): + m_num_bits(num_bits), + m_capacity(num_words(num_bits)), + m_data(alloc_svect(unsigned, m_capacity)) { + memcpy(m_data, source, m_capacity * sizeof(unsigned)); + } + + ~bit_vector() { + if (m_data) { + dealloc_svect(m_data); + } + } + + void reset() { + memset(m_data, 0, m_capacity * sizeof(unsigned)); + m_num_bits = 0; + } + + void swap(bit_vector & other) { + std::swap(m_data, other.m_data); + std::swap(m_num_bits, other.m_num_bits); + std::swap(m_capacity, other.m_capacity); + } + + // Increase the size of the bit_vector by k 0-bits. + void shift_right(unsigned k); + + void fill0() { + memset(m_data, 0, m_capacity * sizeof(unsigned)); + } + + unsigned size() const { + return m_num_bits; + } + + bool empty() const { + return m_num_bits != 0; + } + + unsigned num_words() const { + return num_words(m_num_bits); + } + + unsigned get_word(unsigned word_idx) const { + return m_data[word_idx]; + } + + bool get(unsigned bit_idx) const { + SASSERT(bit_idx < size()); + bool r = (get_bit_word(bit_idx) & get_pos_mask(bit_idx)) != 0; + return r; + } + + void set(unsigned bit_idx) { + SASSERT(bit_idx < size()); + get_bit_word(bit_idx) |= get_pos_mask(bit_idx); + } + + void unset(unsigned bit_idx) { + SASSERT(bit_idx < size()); + get_bit_word(bit_idx) &= ~get_pos_mask(bit_idx); + } + + void set(unsigned bit_idx, bool val) { + SASSERT(bit_idx < size()); + int _val = static_cast(val); + get_bit_word(bit_idx) ^= (-_val ^ get_bit_word(bit_idx)) & get_pos_mask(bit_idx); + } + + void push_back(bool val) { + unsigned idx = m_num_bits; + m_num_bits++; + if (num_words(m_num_bits) > m_capacity) { + expand(); + } + set(idx, val); + } + + void pop_back() { + SASSERT(m_num_bits > 0); + m_num_bits--; + } + + bool back() const { + SASSERT(!empty()); + bool r = get(m_num_bits - 1); + return r; + } + + void shrink(unsigned new_size) { + SASSERT(new_size <= m_num_bits); + m_num_bits = new_size; + } + + void resize(unsigned new_size, bool val = false); + + void reserve(unsigned sz, bool val = false) { + if (sz > size()) + resize(sz, val); + } + + bool operator==(bit_vector const & other); + + bool operator!=(bit_vector const & other) { return !operator==(other); } + + bit_vector & operator=(bit_vector const & source) { + m_num_bits = source.m_num_bits; + if (m_capacity < source.m_capacity) { + dealloc_svect(m_data); + m_data = alloc_svect(unsigned, source.m_capacity); + m_capacity = source.m_capacity; + } + memcpy(m_data, source.m_data, source.m_capacity * sizeof(unsigned)); + return *this; + } + + bit_vector & operator|=(bit_vector const & source); + + bit_vector & operator&=(bit_vector const & source); + + void display(std::ostream & out) const; +}; + +inline std::ostream & operator<<(std::ostream & out, bit_vector const & b) { + b.display(out); + return out; +} + +/** + \brief Bitvector class with fast reset. + This class should be used if the reset is frequently called. +*/ +class fr_bit_vector : private bit_vector { + svector m_one_idxs; +public: + void reset(); + + void fill0() { + bit_vector::fill0(); + m_one_idxs.reset(); + } + + void set(unsigned idx) { + m_one_idxs.push_back(idx); + bit_vector::set(idx); + } + + void set(unsigned idx, bool val) { + if (val) + m_one_idxs.push_back(idx); + bit_vector::set(idx, val); + } + + void push_back(bool val) { + if (val) + m_one_idxs.push_back(size()); + bit_vector::push_back(val); + } +}; + +#endif /* _BIT_VECTOR_H_ */ + diff --git a/lib/bool_rewriter.cpp b/lib/bool_rewriter.cpp new file mode 100644 index 000000000..b7fe296c4 --- /dev/null +++ b/lib/bool_rewriter.cpp @@ -0,0 +1,989 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + bool_rewriter.h + +Abstract: + + Basic rewrites for Boolean operators. + +Author: + + Leonardo (leonardo) 2011-04-04 + +Notes: + +--*/ +#include"bool_rewriter.h" +#include"rewriter_def.h" + +void bool_rewriter::updt_params(params_ref const & p) { + m_flat = p.get_bool(":flat", true); + m_elim_and = p.get_bool(":elim-and", false); + m_local_ctx = p.get_bool(":local-ctx", false); + m_local_ctx_limit = p.get_uint(":local-ctx-limit", UINT_MAX); + m_blast_distinct = p.get_bool(":blast-distinct", false); + m_ite_extra_rules = p.get_bool(":ite-extra-rules", false); +} + +void bool_rewriter::get_param_descrs(param_descrs & r) { + r.insert(":ite-extra-rules", CPK_BOOL, "(default: false) extra ite simplifications, these additional simplifications may reduce size locally but increase globally."); + r.insert(":flat", CPK_BOOL, "(default: true) create nary applications for and,or,+,*,bvadd,bvmul,bvand,bvor,bvxor."); + r.insert(":elim-and", CPK_BOOL, "(default: false) conjunctions are rewritten using negation and disjunctions."); + r.insert(":local-ctx", CPK_BOOL, "(default: false) perform local (i.e., cheap) context simplifications."); + r.insert(":local-ctx-limit", CPK_UINT, "(default: inf) limit for applying local context simplifier."); + r.insert(":blast-distinct", CPK_BOOL, "(default: false) expand a distinct predicate into a quadratic number of disequalities."); +} + +br_status bool_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(f->get_family_id() == m().get_basic_family_id()); + switch (f->get_decl_kind()) { + case OP_EQ: + case OP_IFF: + SASSERT(num_args == 2); + return mk_eq_core(args[0], args[1], result); + case OP_DISTINCT: + return mk_distinct_core(num_args, args, result); + case OP_AND: + return mk_and_core(num_args, args, result); + case OP_OR: + return mk_or_core(num_args, args, result); + case OP_NOT: + SASSERT(num_args == 1); + return mk_not_core(args[0], result); + case OP_ITE: + SASSERT(num_args == 3); + return mk_ite_core(args[0], args[1], args[2], result); + case OP_IMPLIES: + SASSERT(num_args == 2); + mk_implies(args[0], args[1], result); + return BR_DONE; + case OP_XOR: + SASSERT(num_args == 2); + mk_xor(args[0], args[1], result); + return BR_DONE; + default: + return BR_FAILED; + } +} + +void bool_rewriter::mk_and_as_or(unsigned num_args, expr * const * args, expr_ref & result) { + expr_ref_buffer new_args(m()); + for (unsigned i = 0; i < num_args; i++) { + expr_ref tmp(m()); + mk_not(args[i], tmp); + new_args.push_back(tmp); + } + expr_ref tmp(m()); + mk_or(new_args.size(), new_args.c_ptr(), tmp); + mk_not(tmp, result); +} + +br_status bool_rewriter::mk_nflat_and_core(unsigned num_args, expr * const * args, expr_ref & result) { + bool s = false; + ptr_buffer buffer; + expr_fast_mark1 neg_lits; + expr_fast_mark2 pos_lits; + + for (unsigned i = 0; i < num_args; i++) { + expr * arg = args[i]; + if (m().is_true(arg)) { + s = true; + continue; + } + if (m().is_false(arg)) { + result = m().mk_false(); + return BR_DONE; + } + if (m().is_not(arg)) { + expr * atom = to_app(arg)->get_arg(0); + if (neg_lits.is_marked(atom)) { + s = true; + continue; + } + if (pos_lits.is_marked(atom)) { + result = m().mk_false(); + return BR_DONE; + } + neg_lits.mark(atom); + } + else { + if (pos_lits.is_marked(arg)) { + s = true; + continue; + } + if (neg_lits.is_marked(arg)) { + result = m().mk_false(); + return BR_DONE; + } + pos_lits.mark(arg); + } + buffer.push_back(arg); + } + + unsigned sz = buffer.size(); + + switch(sz) { + case 0: + result = m().mk_true(); + return BR_DONE; + case 1: + result = buffer.back(); + return BR_DONE; + default: + if (s) { + result = m().mk_and(sz, buffer.c_ptr()); + return BR_DONE; + } + return BR_FAILED; + } +} + +br_status bool_rewriter::mk_flat_and_core(unsigned num_args, expr * const * args, expr_ref & result) { + unsigned i; + for (i = 0; i < num_args; i++) { + if (m().is_and(args[i])) + break; + } + if (i < num_args) { + // has nested ANDs + ptr_buffer flat_args; + flat_args.append(i, args); + for (; i < num_args; i++) { + expr * arg = args[i]; + // Remark: all rewrites are depth 1. + if (m().is_and(arg)) { + unsigned num = to_app(arg)->get_num_args(); + for (unsigned j = 0; j < num; j++) + flat_args.push_back(to_app(arg)->get_arg(j)); + } + else { + flat_args.push_back(arg); + } + } + if (mk_nflat_and_core(flat_args.size(), flat_args.c_ptr(), result) == BR_FAILED) + result = m().mk_and(flat_args.size(), flat_args.c_ptr()); + return BR_DONE; + } + return mk_nflat_and_core(num_args, args, result); +} + +br_status bool_rewriter::mk_nflat_or_core(unsigned num_args, expr * const * args, expr_ref & result) { + bool s = false; + ptr_buffer buffer; + expr_fast_mark1 neg_lits; + expr_fast_mark2 pos_lits; + for (unsigned i = 0; i < num_args; i++) { + expr * arg = args[i]; + if (m().is_true(arg)) { + result = m().mk_true(); + return BR_DONE; + } + if (m().is_false(arg)) { + s = true; + continue; + } + if (m().is_not(arg)) { + expr * atom = to_app(arg)->get_arg(0); + if (neg_lits.is_marked(atom)) { + s = true; + continue; + } + if (pos_lits.is_marked(atom)) { + result = m().mk_true(); + return BR_DONE; + } + neg_lits.mark(atom); + } + else { + if (pos_lits.is_marked(arg)) { + s = true; + continue; + } + if (neg_lits.is_marked(arg)) { + result = m().mk_true(); + return BR_DONE; + } + pos_lits.mark(arg); + } + buffer.push_back(arg); + } + + unsigned sz = buffer.size(); + + switch(sz) { + case 0: + result = m().mk_false(); + return BR_DONE; + case 1: + result = buffer.back(); + return BR_DONE; + default: + if (m_local_ctx && m_local_ctx_cost <= m_local_ctx_limit) { + neg_lits.reset(); + pos_lits.reset(); + if (local_ctx_simp(sz, buffer.c_ptr(), result)) + return BR_DONE; + } + if (s) { + result = m().mk_or(sz, buffer.c_ptr()); + return BR_DONE; + } + return BR_FAILED; + } +} + + +br_status bool_rewriter::mk_flat_or_core(unsigned num_args, expr * const * args, expr_ref & result) { + unsigned i; + for (i = 0; i < num_args; i++) { + if (m().is_or(args[i])) + break; + } + if (i < num_args) { + // has nested ORs + ptr_buffer flat_args; + flat_args.append(i, args); + for (; i < num_args; i++) { + expr * arg = args[i]; + // Remark: all rewrites are depth 1. + if (m().is_or(arg)) { + unsigned num = to_app(arg)->get_num_args(); + for (unsigned j = 0; j < num; j++) + flat_args.push_back(to_app(arg)->get_arg(j)); + } + else { + flat_args.push_back(arg); + } + } + if (mk_nflat_or_core(flat_args.size(), flat_args.c_ptr(), result) == BR_FAILED) + result = m().mk_or(flat_args.size(), flat_args.c_ptr()); + return BR_DONE; + } + return mk_nflat_or_core(num_args, args, result); +} + +expr * bool_rewriter::mk_or_app(unsigned num_args, expr * const * args) { + switch(num_args) { + case 0: return m().mk_false(); + case 1: return args[0]; + default: return m().mk_or(num_args, args); + } +} + +/** + \brief Auxiliary method for local_ctx_simp. + + Replace args[i] by true if marked in neg_lits. + Replace args[i] by false if marked in pos_lits. +*/ +bool bool_rewriter::simp_nested_not_or(unsigned num_args, expr * const * args, + expr_fast_mark1 & neg_lits, expr_fast_mark2 & pos_lits, expr_ref & result) { + ptr_buffer new_args; + bool simp = false; + m_local_ctx_cost += num_args; + for (unsigned i = 0; i < num_args; i++) { + expr * arg = args[i]; + if (neg_lits.is_marked(arg)) { + result = m().mk_false(); + return true; + } + if (pos_lits.is_marked(arg)) { + simp = true; + continue; + } + if (m().is_not(arg)) { + expr * atom = to_app(arg)->get_arg(0); + if (neg_lits.is_marked(atom)) { + simp = true; + continue; + } + if (pos_lits.is_marked(atom)) { + result = m().mk_false(); + return true; + } + } + new_args.push_back(arg); + } + if (simp) { + switch(new_args.size()) { + case 0: + result = m().mk_true(); + return true; + case 1: + mk_not(new_args[0], result); + return true; + default: + result = m().mk_not(m().mk_or(new_args.size(), new_args.c_ptr())); + return true; + } + } + return false; +} + + +expr * bool_rewriter::simp_arg(expr * arg, expr_fast_mark1 & neg_lits, expr_fast_mark2 & pos_lits, bool & modified) { + if (m().is_not(arg)) { + expr * atom = to_app(arg)->get_arg(0); + if (neg_lits.is_marked(atom)) { + modified = true; + return m().mk_false(); + } + if (pos_lits.is_marked(atom)) { + modified = true; + return m().mk_true(); + } + return arg; + } + else { + if (neg_lits.is_marked(arg)) { + modified = true; + return m().mk_true(); + } + if (pos_lits.is_marked(arg)) { + modified = true; + return m().mk_false(); + } + return arg; + } +} + +/** + \brief Simpler version of mk_ite, that will not invoke mk_or/mk_and. + It is used byt local_ctx_simp to prevent a recursive call to local_ctx_simp. + See comment at simp_nested_eq_ite. +*/ +void bool_rewriter::mk_nested_ite(expr * c, expr * t, expr * e, expr_ref & result) { + if (m().is_true(c)) { + result = t; + return; + } + + if (m().is_false(c)) { + result = e; + return; + } + + if (t == e) { + result = t; + return; + } + + if (m().is_bool(t)) { + if (m().is_true(t)) { + if (m().is_false(e)) { + result = c; + return; + } + result = m().mk_or(c, e); + return; + } + if (m().is_false(t)) { + if (m().is_true(e)) { + mk_not(c, result); + return; + } + expr_ref tmp(m()); + mk_not(e, tmp); + result = m().mk_not(m().mk_or(c, tmp)); + return; + } + if (m().is_true(e)) { + expr_ref tmp(m()); + mk_not(c, tmp); + result = m().mk_or(tmp, t); + return; + } + if (m().is_false(e) || c == e) { + expr_ref tmp1(m()); + expr_ref tmp2(m()); + mk_not(c, tmp1); + mk_not(t, tmp2); + result = m().mk_not(m().mk_or(tmp1, tmp2)); + return; + } + if (c == t) { + result = m().mk_or(c, e); + return; + } + if (m().is_complement_core(t, e)) { // t = not(e) + mk_eq(c, t, result); + return; + } + if (m().is_complement_core(e, t)) { // e = not(t) + mk_eq(c, t, result); + return; + } + } + result = m().mk_ite(c, t, e); +} + +bool bool_rewriter::simp_nested_eq_ite(expr * t, expr_fast_mark1 & neg_lits, expr_fast_mark2 & pos_lits, expr_ref & result) { + bool neg = false; + m_local_ctx_cost += 3; + if (m().is_not(t)) { + neg = true; + t = to_app(t)->get_arg(0); + } + if (m().is_iff(t) || m().is_eq(t)) { + bool modified = false; + expr * new_lhs = simp_arg(to_app(t)->get_arg(0), neg_lits, pos_lits, modified); + expr * new_rhs = simp_arg(to_app(t)->get_arg(1), neg_lits, pos_lits, modified); + if (!modified) + return false; + mk_eq(new_lhs, new_rhs, result); + if (neg) + mk_not(result, result); + return true; + } + if (m().is_ite(t)) { + bool modified = false; + expr * new_c = simp_arg(to_app(t)->get_arg(0), neg_lits, pos_lits, modified); + expr * new_t = simp_arg(to_app(t)->get_arg(1), neg_lits, pos_lits, modified); + expr * new_e = simp_arg(to_app(t)->get_arg(2), neg_lits, pos_lits, modified); + if (!modified) + return false; + // It is not safe to invoke mk_ite here, since it can recursively call + // local_ctx_simp by + // - transforming the ITE into an OR + // - and invoked mk_or, that will invoke local_ctx_simp + // mk_ite(new_c, new_t, new_e, result); + mk_nested_ite(new_c, new_t, new_e, result); + if (neg) + mk_not(result, result); + return true; + } + return false; +} + +/** + \brief Apply local context simplification at (OR args[0] ... args[num_args-1]) + Basic idea: + - Replace args[i] by false in the other arguments + - If args[i] is of the form (not t), then replace t by true in the other arguments. + To make sure the simplification is efficient we bound the depth. +*/ +bool bool_rewriter::local_ctx_simp(unsigned num_args, expr * const * args, expr_ref & result) { + expr_ref_vector old_args(m()); + expr_ref_vector new_args(m()); + expr_ref new_arg(m()); + expr_fast_mark1 neg_lits; + expr_fast_mark2 pos_lits; + bool simp = false; + bool modified = false; + bool forward = true; + unsigned rounds = 0; + + while (true) { + rounds++; +#if 0 + if (rounds > 10) + verbose_stream() << "rounds: " << rounds << "\n"; +#endif + +#define PUSH_NEW_ARG(ARG) { \ + new_args.push_back(ARG); \ + if (m().is_not(ARG)) \ + neg_lits.mark(to_app(ARG)->get_arg(0)); \ + else \ + pos_lits.mark(ARG); \ +} + +#define PROCESS_ARG() \ + { \ + expr * arg = args[i]; \ + if (m().is_not(arg) && m().is_or(to_app(arg)->get_arg(0)) && \ + simp_nested_not_or(to_app(to_app(arg)->get_arg(0))->get_num_args(), \ + to_app(to_app(arg)->get_arg(0))->get_args(), \ + neg_lits, \ + pos_lits, \ + new_arg)) { \ + modified = true; simp = true; \ + arg = new_arg; \ + } \ + if (simp_nested_eq_ite(arg, neg_lits, pos_lits, new_arg)) { \ + modified = true; simp = true; \ + arg = new_arg; \ + } \ + if (m().is_false(arg)) \ + continue; \ + if (m().is_true(arg)) { \ + result = arg; \ + return true; \ + } \ + if (m_flat && m().is_or(arg)) { \ + unsigned sz = to_app(arg)->get_num_args(); \ + for (unsigned j = 0; j < sz; j++) { \ + expr * arg_arg = to_app(arg)->get_arg(j); \ + PUSH_NEW_ARG(arg_arg); \ + } \ + } \ + else { \ + PUSH_NEW_ARG(arg); \ + } \ + } + + m_local_ctx_cost += 2*num_args; +#if 0 + static unsigned counter = 0; + counter++; + if (counter % 10000 == 0) + verbose_stream() << "local-ctx-cost: " << m_local_ctx_cost << "\n"; +#endif + + if (forward) { + for (unsigned i = 0; i < num_args; i++) { + PROCESS_ARG(); + } + forward = false; + } + else { + unsigned i = num_args; + while (i > 0) { + --i; + PROCESS_ARG(); + } + if (!modified) { + if (simp) { + result = mk_or_app(num_args, args); + return true; + } + return false; // didn't simplify + } + // preserve the original order... + std::reverse(new_args.c_ptr(), new_args.c_ptr() + new_args.size()); + modified = false; + forward = true; + } + pos_lits.reset(); + neg_lits.reset(); + old_args.reset(); + old_args.swap(new_args); + SASSERT(new_args.empty()); + args = old_args.c_ptr(); + num_args = old_args.size(); + } +} + +/** + \brief Apply simplification if ite is an if-then-else tree where every leaf is a value. + + This is an efficient way to + +*/ +br_status bool_rewriter::try_ite_value(app * ite, app * val, expr_ref & result) { + SASSERT(m().is_ite(ite)); + SASSERT(m().is_value(val)); + + expr * t = ite->get_arg(1); + expr * e = ite->get_arg(2); + if (!m().is_value(t) || !m().is_value(e)) + return BR_FAILED; + + if (t != val && e != val) { + TRACE("try_ite_value", tout << mk_ismt2_pp(t, m()) << " " << mk_ismt2_pp(e, m()) << " " << mk_ismt2_pp(val, m()) << "\n"; + tout << t << " " << e << " " << val << "\n";); + result = m().mk_false(); + return BR_DONE; + } + + if (t == val && e == val) { + result = m().mk_true(); + return BR_DONE; + } + + if (t == val) { + result = ite->get_arg(0); + return BR_DONE; + } + + SASSERT(e == val); + + mk_not(ite->get_arg(0), result); + return BR_DONE; +} + +#if 0 +// Return true if ite is an if-then-else tree where the leaves are values, +// and they are all different from val +static bool is_ite_value_tree_neq_value(ast_manager & m, app * ite, app * val) { + SASSERT(m.is_ite(ite)); + SASSERT(m.is_value(val)); + + expr_fast_mark1 visited; + ptr_buffer todo; + todo.push_back(ite); + +#define VISIT(ARG) { \ + if (m.is_value(ARG)) { \ + if (ARG == val) \ + return false; \ + } \ + else if (m.is_ite(ARG)) { \ + if (!visited.is_marked(ARG)) { \ + visited.mark(ARG); \ + todo.push_back(to_app(ARG)); \ + } \ + } \ + else { \ + return false; \ + } \ + } + + while (!todo.empty()) { + app * ite = todo.back(); + todo.pop_back(); + SASSERT(m.is_ite(ite)); + expr * t = ite->get_arg(1); + expr * e = ite->get_arg(2); + VISIT(t); + VISIT(e); + } + + return true; +} +#endif + +br_status bool_rewriter::mk_eq_core(expr * lhs, expr * rhs, expr_ref & result) { + if (lhs == rhs) { + result = m().mk_true(); + return BR_DONE; + } + + if (m().are_distinct(lhs, rhs)) { + result = m().mk_false(); + return BR_DONE; + } + + br_status r = BR_FAILED; + if (m().is_ite(lhs) && m().is_value(rhs)) { + // if (is_ite_value_tree_neq_value(m(), to_app(lhs), to_app(rhs))) { + // result = m().mk_false(); + // return BR_DONE; + // } + r = try_ite_value(to_app(lhs), to_app(rhs), result); + CTRACE("try_ite_value", r != BR_FAILED, + tout << mk_ismt2_pp(lhs, m()) << "\n" << mk_ismt2_pp(rhs, m()) << "\n--->\n" << mk_ismt2_pp(result, m()) << "\n";); + } + else if (m().is_ite(rhs) && m().is_value(lhs)) { + // if (is_ite_value_tree_neq_value(m(), to_app(rhs), to_app(lhs))) { + // result = m().mk_false(); + // return BR_DONE; + // } + r = try_ite_value(to_app(rhs), to_app(lhs), result); + CTRACE("try_ite_value", r != BR_FAILED, + tout << mk_ismt2_pp(lhs, m()) << "\n" << mk_ismt2_pp(rhs, m()) << "\n--->\n" << mk_ismt2_pp(result, m()) << "\n";); + } + if (r != BR_FAILED) + return r; + + if (m().is_bool(lhs)) { + bool unfolded = false; + if (m().is_not(lhs) && m().is_not(rhs)) { + lhs = to_app(lhs)->get_arg(0); + rhs = to_app(rhs)->get_arg(0); + unfolded = true; + } + if (m().is_true(lhs)) { + result = rhs; + return BR_DONE; + } + if (m().is_false(lhs)) { + mk_not(rhs, result); + return BR_DONE; + } + if (m().is_true(rhs)) { + result = lhs; + return BR_DONE; + } + if (m().is_false(rhs)) { + mk_not(lhs, result); + return BR_DONE; + } + if (m().is_complement(lhs, rhs)) { + result = m().mk_false(); + return BR_DONE; + } + if (unfolded) { + result = m().mk_eq(lhs, rhs); + return BR_DONE; + } + } + return BR_FAILED; +} + +br_status bool_rewriter::mk_distinct_core(unsigned num_args, expr * const * args, expr_ref & result) { + if (num_args <= 1) { + result = m().mk_true(); + return BR_DONE; + } + + if (num_args == 2) { + expr_ref tmp(m()); + result = m().mk_not(m().mk_eq(args[0], args[1])); + return BR_REWRITE2; // mk_eq may be dispatched to other rewriters. + } + + expr_fast_mark1 visited; + bool all_value = true; + for (unsigned i = 0; i < num_args; i++) { + expr * arg = args[i]; + if (visited.is_marked(arg)) { + result = m().mk_false(); + return BR_DONE; + } + visited.mark(arg); + if (!m().is_value(arg)) + all_value = false; + } + if (all_value) { + result = m().mk_true(); + return BR_DONE; + } + + SASSERT(num_args > 2); + if (m().is_bool(args[0])) { + result = m().mk_false(); + return BR_DONE; + } + + if (m_blast_distinct) { + ptr_buffer new_diseqs; + for (unsigned i = 0; i < num_args; i++) { + for (unsigned j = i + 1; j < num_args; j++) + new_diseqs.push_back(m().mk_not(m().mk_eq(args[i], args[j]))); + } + result = m().mk_and(new_diseqs.size(), new_diseqs.c_ptr()); + return BR_REWRITE3; + } + + return BR_FAILED; +} + +br_status bool_rewriter::mk_ite_core(expr * c, expr * t, expr * e, expr_ref & result) { + bool s = false; + + // (ite (not c) a b) ==> (ite c b a) + if (m().is_not(c)) { + c = to_app(c)->get_arg(0); + std::swap(t, e); + s = true; + } + + // (ite c (ite c t1 t2) t3) ==> (ite c t1 t3) + if (m().is_ite(t) && to_app(t)->get_arg(0) == c) { + // Remark: (ite c (ite (not c) t1 t2) t3) ==> (ite c t2 t3) does not happen if applying rewrites bottom up + t = to_app(t)->get_arg(1); + s = true; + } + + // (ite c t1 (ite c t2 t3)) ==> (ite c t1 t3) + if (m().is_ite(e) && to_app(e)->get_arg(0) == c) { + // Remark: (ite c t1 (ite (not c) t2 t3)) ==> (ite c t1 t2) does not happen if applying rewrites bottom up + e = to_app(e)->get_arg(2); + s = true; + } + + if (m().is_true(c)) { + result = t; + return BR_DONE; + } + + if (m().is_false(c)) { + result = e; + return BR_DONE; + } + + if (t == e) { + result = t; + return BR_DONE; + } + + if (m().is_bool(t)) { + if (m().is_true(t)) { + if (m().is_false(e)) { + result = c; + return BR_DONE; + } + mk_or(c, e, result); + return BR_DONE; + } + if (m().is_false(t)) { + if (m().is_true(e)) { + mk_not(c, result); + return BR_DONE; + } + expr_ref tmp(m()); + mk_not(c, tmp); + mk_and(tmp, e, result); + return BR_DONE; + } + if (m().is_true(e)) { + expr_ref tmp(m()); + mk_not(c, tmp); + mk_or(tmp, t, result); + return BR_DONE; + } + if (m().is_false(e)) { + mk_and(c, t, result); + return BR_DONE; + } + if (c == e) { + mk_and(c, t, result); + return BR_DONE; + } + if (c == t) { + mk_or(c, e, result); + return BR_DONE; + } + if (m().is_complement_core(t, e)) { // t = not(e) + mk_eq(c, t, result); + return BR_DONE; + } + if (m().is_complement_core(e, t)) { // e = not(t) + mk_eq(c, t, result); + return BR_DONE; + } + } + + if (m().is_ite(t) && m_ite_extra_rules) { + // (ite c1 (ite c2 t1 t2) t1) ==> (ite (and c1 (not c2)) t2 t1) + if (e == to_app(t)->get_arg(1)) { + expr_ref not_c2(m()); + mk_not(to_app(t)->get_arg(0), not_c2); + 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; + } + // (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; + } + + if (m().is_ite(e)) { + // (ite c1 (ite c2 t1 t2) (ite c3 t1 t2)) ==> (ite (or (and c1 c2) (and (not c1) c3)) t1 t2) + if (to_app(t)->get_arg(1) == to_app(e)->get_arg(1) && + to_app(t)->get_arg(2) == to_app(e)->get_arg(2)) { + expr_ref and1(m()); + expr_ref and2(m()); + expr_ref notc(m()); + mk_and(c, to_app(t)->get_arg(0), and1); + mk_not(c, notc); + mk_and(notc, to_app(e)->get_arg(0), and2); + 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; + } + + // (ite c1 (ite c2 t1 t2) (ite c3 t2 t1)) ==> (ite (or (and c1 c2) (and (not c1) (not c3))) t1 t2) + if (to_app(t)->get_arg(1) == to_app(e)->get_arg(2) && + to_app(t)->get_arg(2) == to_app(e)->get_arg(1)) { + expr_ref and1(m()); + expr_ref and2(m()); + expr_ref notc(m()); + mk_and(c, to_app(t)->get_arg(0), and1); + mk_not(c, notc); + expr_ref notc3(m()); + mk_not(to_app(e)->get_arg(0), notc3); + mk_and(notc, notc3, and2); + 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; + } + } + } + + if (m().is_ite(e) && m_ite_extra_rules) { + // (ite c1 t1 (ite c2 t1 t2)) ==> (ite (or c1 c2) t1 t2) + if (t == to_app(e)->get_arg(1)) { + expr_ref new_c(m()); + mk_or(c, to_app(e)->get_arg(0), new_c); + result = m().mk_ite(new_c, t, to_app(e)->get_arg(2)); + return BR_REWRITE1; + } + // (ite c1 t1 (ite c2 t2 t1)) ==> (ite (or c1 (not c2)) t1 t2) + if (t == to_app(e)->get_arg(2)) { + expr_ref not_c2(m()); + mk_not(to_app(e)->get_arg(0), not_c2); + expr_ref new_c(m()); + mk_or(c, not_c2, new_c); + result = m().mk_ite(new_c, t, to_app(e)->get_arg(1)); + return BR_REWRITE1; + } + } + + if (s) { + result = m().mk_ite(c, t, e); + return BR_DONE; + } + + return BR_FAILED; +} + +br_status bool_rewriter::mk_not_core(expr * t, expr_ref & result) { + if (m().is_not(t)) { + result = to_app(t)->get_arg(0); + return BR_DONE; + } + if (m().is_true(t)) { + result = m().mk_false(); + return BR_DONE; + } + if (m().is_false(t)) { + result = m().mk_true(); + return BR_DONE; + } + if (is_eq(t) && m().is_bool(to_app(t)->get_arg(0))) { + expr_ref tmp(m()); + mk_not(to_app(t)->get_arg(0), tmp); + mk_eq(tmp, to_app(t)->get_arg(1), result); + return BR_DONE; + } + return BR_FAILED; +} + +void bool_rewriter::mk_xor(expr * lhs, expr * rhs, expr_ref & result) { + expr_ref tmp(m()); + mk_not(lhs, tmp); + mk_eq(tmp, rhs, result); +} + +void bool_rewriter::mk_implies(expr * lhs, expr * rhs, expr_ref & result) { + expr_ref tmp(m()); + mk_not(lhs, tmp); + mk_or(tmp, rhs, result); +} + +void bool_rewriter::mk_nand(unsigned num_args, expr * const * args, expr_ref & result) { + expr_ref tmp(m_manager); + mk_and(num_args, args, tmp); + mk_not(tmp, result); +} + +void bool_rewriter::mk_nor(unsigned num_args, expr * const * args, expr_ref & result) { + expr_ref tmp(m_manager); + mk_or(num_args, args, tmp); + mk_not(tmp, result); +} + +void bool_rewriter::mk_nand(expr * arg1, expr * arg2, expr_ref & result) { + expr_ref tmp(m_manager); + mk_and(arg1, arg2, tmp); + mk_not(tmp, result); +} + +void bool_rewriter::mk_nor(expr * arg1, expr * arg2, expr_ref & result) { + expr_ref tmp(m_manager); + mk_or(arg1, arg2, tmp); + mk_not(tmp, result); +} + +template class rewriter_tpl; diff --git a/lib/bool_rewriter.h b/lib/bool_rewriter.h new file mode 100644 index 000000000..b1a177247 --- /dev/null +++ b/lib/bool_rewriter.h @@ -0,0 +1,199 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + bool_rewriter.h + +Abstract: + + Basic rewriting rules for Boolean operators. + +Author: + + Leonardo (leonardo) 2011-04-04 + +Notes: + +--*/ +#ifndef _BOOL_REWRITER_H_ +#define _BOOL_REWRITER_H_ + +#include"ast.h" +#include"rewriter.h" +#include"params.h" + +/** + \brief Apply basic Boolean rewriting operations. + + Only depth 1 simplifications are performed. + + Note: there are no recursive calls. + + Note: arguments of AC operators are not sorted. + Note: arguments of = and xor are also not sorted. + + Note: By default, (AND A B) is not rewritten as (NOT (OR (NOT A) (NOT B))) + + Note: AND OR operators are flattened only if mk_flat_app, mk_flat_or, mk_flat_and are used. + + The following operators are expanded: + - => (implies) + - xor + - nand + - nor + - iff + + All methods run in time almost linear on the number of arguments. + Actually, this is not true when flattening is enabled. + A better approximation is O(Sum_{t \in args} size1(t)). + Where size1(t) = max{t->get_num_args(), 1}. +*/ +class bool_rewriter { + ast_manager & m_manager; + bool m_flat; + bool m_local_ctx; + bool m_elim_and; + bool m_blast_distinct; + bool m_ite_extra_rules; + unsigned m_local_ctx_limit; + unsigned m_local_ctx_cost; + + br_status mk_flat_and_core(unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_flat_or_core(unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_nflat_and_core(unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_nflat_or_core(unsigned num_args, expr * const * args, expr_ref & result); + + void mk_and_as_or(unsigned num_args, expr * const * args, expr_ref & result); + + expr * mk_or_app(unsigned num_args, expr * const * args); + bool simp_nested_not_or(unsigned num_args, expr * const * args, expr_fast_mark1 & neg_lits, expr_fast_mark2 & pos_lits, expr_ref & result); + expr * simp_arg(expr * arg, expr_fast_mark1 & neg_lits, expr_fast_mark2 & pos_lits, bool & modified); + void mk_nested_ite(expr * new_c, expr * new_t, expr * new_e, expr_ref & result); + bool simp_nested_eq_ite(expr * t, expr_fast_mark1 & neg_lits, expr_fast_mark2 & pos_lits, expr_ref & result); + bool local_ctx_simp(unsigned num_args, expr * const * args, expr_ref & result); + br_status try_ite_value(app * ite, app * val, expr_ref & result); + +public: + bool_rewriter(ast_manager & m, params_ref const & p = params_ref()):m_manager(m), m_local_ctx_cost(0) { updt_params(p); } + ast_manager & m() const { return m_manager; } + family_id get_fid() const { return m().get_basic_family_id(); } + bool is_eq(expr * t) const { return m().is_eq(t) || m().is_iff(t); } + + bool flat() const { return m_flat; } + void set_flat(bool f) { m_flat = f; } + bool elim_and() const { return m_elim_and; } + void set_elim_and(bool f) { m_elim_and = f; } + void reset_local_ctx_cost() { m_local_ctx_cost = 0; } + + void updt_params(params_ref const & p); + + static void get_param_descrs(param_descrs & r); + + // The core methods return true if a rewrite-step/simplification was applied + // to the arguments, and the result is stored in 'result'. Otherwise, they return false + // and result.get == 0. + + br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + void mk_app(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + if (mk_app_core(f, num_args, args, result) == BR_FAILED) + result = m().mk_app(f, num_args, args); + } + + br_status mk_eq_core(expr * lhs, expr * rhs, expr_ref & result); + br_status mk_distinct_core(unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_iff_core(expr * lhs, expr * rhs, expr_ref & result) { return mk_eq_core(lhs, rhs, result); } + br_status mk_and_core(unsigned num_args, expr * const * args, expr_ref & result) { + if (m_elim_and) { + mk_and_as_or(num_args, args, result); + return BR_DONE; + } + else if (m_flat) { + return mk_flat_and_core(num_args, args, result); + } + else { + return mk_nflat_and_core(num_args, args, result); + } + } + br_status mk_or_core(unsigned num_args, expr * const * args, expr_ref & result) { + return m_flat ? + mk_flat_or_core(num_args, args, result) : + mk_nflat_or_core(num_args, args, result); + } + br_status mk_ite_core(expr * c, expr * t, expr * e, expr_ref & result); + br_status mk_not_core(expr * t, expr_ref & result); + + void mk_eq(expr * lhs, expr * rhs, expr_ref & result) { + if (mk_eq_core(lhs, rhs, result) == BR_FAILED) + result = m().mk_eq(lhs, rhs); + } + void mk_iff(expr * lhs, expr * rhs, expr_ref & result) { mk_eq(lhs, rhs, result); } + void mk_xor(expr * lhs, expr * rhs, expr_ref & result); + void mk_and(unsigned num_args, expr * const * args, expr_ref & result) { + if (mk_and_core(num_args, args, result) == BR_FAILED) { + SASSERT(!m_elim_and); + result = m().mk_and(num_args, args); + } + } + void mk_or(unsigned num_args, expr * const * args, expr_ref & result) { + if (mk_or_core(num_args, args, result) == BR_FAILED) + result = m().mk_or(num_args, args); + } + void mk_and(expr * arg1, expr * arg2, expr_ref & result) { + expr * args[2] = {arg1, arg2}; + mk_and(2, args, result); + } + void mk_or(expr * arg1, expr * arg2, expr_ref & result) { + expr * args[2] = {arg1, arg2}; + mk_or(2, args, result); + } + void mk_and(expr * arg1, expr * arg2, expr * arg3, expr_ref & result) { + expr * args[3] = {arg1, arg2, arg3}; + mk_and(3, args, result); + } + void mk_or(expr * arg1, expr * arg2, expr * arg3, expr_ref & result) { + expr * args[3] = {arg1, arg2, arg3}; + mk_or(3, args, result); + } + void mk_implies(expr * lhs, expr * rhs, expr_ref & result); + void mk_ite(expr * c, expr * t, expr * e, expr_ref & result) { + if (mk_ite_core(c, t, e, result) == BR_FAILED) + result = m().mk_ite(c, t, e); + } + void mk_distinct(unsigned num_args, expr * const * args, expr_ref & result) { + if (mk_distinct_core(num_args, args, result) == BR_FAILED) + result = m().mk_distinct(num_args, args); + } + void mk_not(expr * t, expr_ref & result) { + if (mk_not_core(t, result) == BR_FAILED) + result = m().mk_not(t); + } + + void mk_nand(unsigned num_args, expr * const * args, expr_ref & result); + void mk_nor(unsigned num_args, expr * const * args, expr_ref & result); + void mk_nand(expr * arg1, expr * arg2, expr_ref & result); + void mk_nor(expr * arg1, expr * arg2, expr_ref & result); +}; + +struct bool_rewriter_cfg : public default_rewriter_cfg { + bool_rewriter m_r; + bool flat_assoc(func_decl * f) const { return m_r.flat() && (m_r.m().is_and(f) || m_r.m().is_or(f)); } + bool rewrite_patterns() const { return false; } + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + result_pr = 0; + if (f->get_family_id() != m_r.get_fid()) + return BR_FAILED; + return m_r.mk_app_core(f, num, args, result); + } + bool_rewriter_cfg(ast_manager & m, params_ref const & p):m_r(m, p) {} +}; + +class bool_rewriter_star : public rewriter_tpl { + bool_rewriter_cfg m_cfg; +public: + bool_rewriter_star(ast_manager & m, params_ref const & p): + rewriter_tpl(m, false, m_cfg), + m_cfg(m, p) {} +}; + +#endif diff --git a/lib/bound_manager.cpp b/lib/bound_manager.cpp new file mode 100644 index 000000000..2448521a3 --- /dev/null +++ b/lib/bound_manager.cpp @@ -0,0 +1,240 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + bound_manager.cpp + +Abstract: + + Collect bounds. + +Author: + + Leonardo (leonardo) 2011-05-16 + +Notes: + +--*/ +#include"bound_manager.h" +#include"ast_smt2_pp.h" +#include"goal.h" + +bound_manager::bound_manager(ast_manager & m): + m_util(m) { +} + +bound_manager::~bound_manager() { + reset(); +} + +static decl_kind swap_decl(decl_kind k) { + switch (k) { + case OP_LE: return OP_GE; + case OP_LT: return OP_GT; + case OP_GE: return OP_LE; + case OP_GT: return OP_LT; + default: + UNREACHABLE(); + return k; + } +} + +decl_kind bound_manager::neg(decl_kind k) { + switch (k) { + case OP_LE: return OP_GT; + case OP_LT: return OP_GE; + case OP_GE: return OP_LT; + case OP_GT: return OP_LE; + default: + UNREACHABLE(); + return k; + } +} + +void bound_manager::norm(numeral & n, decl_kind & k) { + switch (k) { + case OP_LE: return; + case OP_GE: return; + case OP_LT: + // x < n --> x <= n-1 + n--; + k = OP_LE; + return; + case OP_GT: + // x > n --> x >= n+1 + n++; + k = OP_GE; + return; + default: + return; + } +} + +static bool is_lower(decl_kind k) { + return k == OP_GT || k == OP_GE; +} + +static bool is_strict(decl_kind k) { + return k == OP_LT || k == OP_GT; +} + +void bound_manager::operator()(expr * f, expr_dependency * d) { + TRACE("bound_manager", tout << "processing:\n" << mk_ismt2_pp(f, m()) << "\n";); + expr * v; + numeral n; + if (is_disjunctive_bound(f, d)) + return; + bool pos = true; + while (m().is_not(f, f)) + pos = !pos; + if (!is_app(f)) + return; + app * t = to_app(f); + if (t->get_family_id() != m_util.get_family_id()) + return; + decl_kind k = t->get_decl_kind(); + if (k != OP_LE && k != OP_GE && k != OP_LT && k != OP_GT) + return; + expr * lhs = t->get_arg(0); + expr * rhs = t->get_arg(1); + bool is_int; + if (is_uninterp_const(lhs) && m_util.is_numeral(rhs, n, is_int)) { + v = lhs; + } + else if (is_uninterp_const(rhs) && m_util.is_numeral(lhs, n, is_int)) { + v = rhs; + k = swap_decl(k); + } + else { + return; + } + if (!pos) + k = neg(k); + if (is_int) + norm(n, k); + TRACE("bound_manager", tout << "found bound for:\n" << mk_ismt2_pp(v, m()) << "\n";); + bool strict = is_strict(k); + if (is_lower(k)) { + insert_lower(v, strict, n, d); + } + else { + insert_upper(v, strict, n, d); + } +} + +void bound_manager::insert_upper(expr * v, bool strict, numeral const & n, expr_dependency * d) { + limit old; + if (m_uppers.find(v, old)) { + if (n < old.first || (n == old.first && strict && !old.second)) { + // improved bound + m_uppers.insert(v, limit(n, strict)); + if (d) + m_upper_deps.insert(v, d); + } + } + else { + m_uppers.insert(v, limit(n, strict)); + if (d) + m_upper_deps.insert(v, d); + if (!m_lowers.contains(v)) { + m_bounded_vars.push_back(v); + m().inc_ref(v); + } + } +} + +void bound_manager::insert_lower(expr * v, bool strict, numeral const & n, expr_dependency * d) { + limit old; + if (m_lowers.find(v, old)) { + if (n > old.first || (n == old.first && strict && !old.second)) { + // improved bound + m_lowers.insert(v, limit(n, strict)); + if (d) + m_lower_deps.insert(v, d); + } + } + else { + m_lowers.insert(v, limit(n, strict)); + if (d) + m_lower_deps.insert(v, d); + if (!m_uppers.contains(v)) { + m_bounded_vars.push_back(v); + m().inc_ref(v); + } + } +} + +bool bound_manager::is_disjunctive_bound(expr * f, expr_dependency * d) { + numeral lo, hi, n; + if (!m().is_or(f)) return false; + unsigned sz = to_app(f)->get_num_args(); + if (sz == 0) return false; + expr * x, * y, * v = 0; + bool is_int; + for (unsigned i = 0; i < sz; ++i) { + expr * e = to_app(f)->get_arg(i); + if (!m().is_eq(e, x, y)) return false; + if (is_uninterp_const(x) && + m_util.is_numeral(y, n, is_int) && is_int && + (x == v || v == 0)) { + if (v == 0) { v = x; lo = hi = n; } + if (n < lo) lo = n; + if (n > hi) hi = n; + } + else if (is_uninterp_const(y) && + m_util.is_numeral(x, n, is_int) && is_int && + (y == v || v == 0)) { + if (v == 0) { v = y; lo = hi = n; } + if (n < lo) lo = n; + if (n > hi) hi = n; + } + else { + return false; + } + } + TRACE("bound_manager", tout << "bounds: " << lo << " " << hi << "\n";); + insert_lower(v, false, lo, d); + insert_upper(v, false, hi, d); + return true; +} + +void bound_manager::operator()(assertion_set const & s) { + unsigned sz = s.size(); + for (unsigned i = 0; i < sz; i++) { + operator()(s.form(i), 0); + } +} + +void bound_manager::operator()(goal const & g) { + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + operator()(g.form(i), g.dep(i)); + } +} + +void bound_manager::reset() { + m().dec_array_ref(m_bounded_vars.size(), m_bounded_vars.c_ptr()); + m_bounded_vars.finalize(); + m_lowers.finalize(); + m_uppers.finalize(); + m_lower_deps.finalize(); + m_upper_deps.finalize(); +} + +void bound_manager::display(std::ostream & out) const { + numeral n; bool strict; + for (iterator it = begin(); it != end(); ++it) { + expr * v = *it; + if (has_lower(v, n, strict)) + out << n << " " << (strict ? "<" : "<="); + else + out << "-oo <"; + out << " " << mk_ismt2_pp(v, m()) << " "; + if (has_upper(v, n, strict)) + out << (strict ? "<" : "<=") << " " << n; + else + out << "< oo"; + out << "\n"; + } +} diff --git a/lib/bound_manager.h b/lib/bound_manager.h new file mode 100644 index 000000000..5904165f5 --- /dev/null +++ b/lib/bound_manager.h @@ -0,0 +1,111 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + bound_manager.h + +Abstract: + + Collect bounds. + +Author: + + Leonardo (leonardo) 2011-05-16 + +Notes: + +--*/ +#ifndef _BOUND_MANAGER_H_ +#define _BOUND_MANAGER_H_ + +#include"ast.h" +#include"assertion_set.h" +#include"arith_decl_plugin.h" + +class goal; + +class bound_manager { +public: + typedef rational numeral; +private: + typedef std::pair limit; + arith_util m_util; + obj_map m_lowers; + obj_map m_uppers; + obj_map m_lower_deps; + obj_map m_upper_deps; + ptr_vector m_bounded_vars; + bool is_disjunctive_bound(expr * f, expr_dependency * d); + void insert_lower(expr * v, bool strict, numeral const & n, expr_dependency * d); + void insert_upper(expr * v, bool strict, numeral const & n, expr_dependency * d); +public: + static decl_kind neg(decl_kind k); + static void norm(numeral & n, decl_kind & k); + + bound_manager(ast_manager & m); + ~bound_manager(); + + ast_manager & m() const { return m_util.get_manager(); } + + void operator()(assertion_set const & s); // TODO: delete + void operator()(goal const & g); + void operator()(expr * n, expr_dependency * d = 0); + + bool has_lower(expr * c, numeral & v, bool & strict) const { + limit l; + if (m_lowers.find(c, l)) { + v = l.first; + strict = l.second; + return true; + } + return false; + } + + bool has_upper(expr * c, numeral & v, bool & strict) const { + limit l; + if (m_uppers.find(c, l)) { + v = l.first; + strict = l.second; + return true; + } + return false; + } + + expr_dependency * lower_dep(expr * c) const { + expr_dependency * d; + if (m_lower_deps.find(c, d)) + return d; + return 0; + } + + expr_dependency * upper_dep(expr * c) const { + expr_dependency * d; + if (m_upper_deps.find(c, d)) + return d; + return 0; + } + + bool has_lower(expr * c) const { + return m_lowers.contains(c); + } + + bool has_upper(expr * c) const { + return m_uppers.contains(c); + } + + typedef ptr_vector::const_iterator iterator; + + /** + \brief Iterator for all bounded constants. + */ + iterator begin() const { return m_bounded_vars.begin(); } + iterator end() const { return m_bounded_vars.end(); } + + void reset(); + + // for debugging purposes + void display(std::ostream & out) const; +}; + +#endif diff --git a/lib/bound_propagator.cpp b/lib/bound_propagator.cpp new file mode 100644 index 000000000..40c2ed49b --- /dev/null +++ b/lib/bound_propagator.cpp @@ -0,0 +1,956 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + bound_propagator.cpp + +Abstract: + + Bound propagators for arithmetic. + Support class for implementing strategies and search procedures + +Author: + + Leonardo de Moura (leonardo) 2011-06-18. + +Revision History: + +--*/ +#include"bound_propagator.h" +#include + +// ------------------------------- +// Bound Relaxation configuration +// +// The idea is to minimize errors in floating point computations +// +// If RELAX_BOUNDS is undefined, then bound relaxation is disabled. +// Otherwise, lower bounds l are relaxed using the formula +// PRECISION * floor(l * INV_PRECISION + TOLERANCE) +// and upper bounds u as: +// PRECISION * ceil(u * INV_PRECISION - TOLERANCE) +// In the LP literature, the suggested values are +// l := 10^-5 * floor(l*10^5 + 10^-6) +// u := 10^-5 * ceil(u*10^5 - 10^-6) +// I'm using the following values because of strict bounds +// l := 10^-6 * floor(l*10^6 + 10^-7) +// u := 10^-6 * ceil(u*10^6 - 10^-7) +#define RELAX_BOUNDS +#define TOLERANCE 0.0000001 +#define PRECISION 0.000001 +#define INV_PRECISION 1000000.0 +// ------------------------------- + +bound_propagator::bound::bound(numeral_manager & m, + mpq const & k, + double approx_k, + bool lower, + bool strict, + unsigned lvl, + unsigned ts, + bkind bk, + unsigned c_idx, + assumption a, + bound * prev): + m_approx_k(approx_k), + m_lower(lower), + m_strict(strict), + m_kind(bk), + m_level(lvl), + m_timestamp(ts), + m_prev(prev) { + m.set(m_k, k); + if (bk == DERIVED) + m_constraint_idx = c_idx; + else + m_assumption = a; +} + +bound_propagator::bound_propagator(numeral_manager & _m, allocator & a, params_ref const & p): + m(_m), + m_allocator(a), + m_eq_manager(m, a) { + m_timestamp = 0; + m_qhead = 0; + m_conflict = null_var; + updt_params(p); + reset_statistics(); +} + +bound_propagator::~bound_propagator() { + m.del(m_tmp); + reset(); +} + +void bound_propagator::del_constraints_core() { + constraint_vector::iterator it = m_constraints.begin(); + constraint_vector::iterator end = m_constraints.end(); + for (; it != end; ++it) { + del_constraint(*it); + } + m_constraints.reset(); +} + +void bound_propagator::del_constraints() { + SASSERT(scope_lvl() == 0); + if (m_constraints.empty()) + return; + del_constraints_core(); + m_constraints.finalize(); + vector::iterator it = m_watches.begin(); + vector::iterator end = m_watches.end(); + for (; it != end; ++it) + it->finalize(); +} + +void bound_propagator::del_constraint(constraint & c) { + switch (c.m_kind) { + case LINEAR: + m_eq_manager.del(c.m_eq); + break; + default: + UNREACHABLE(); + break; + } +} + +void bound_propagator::updt_params(params_ref const & p) { + m_max_refinements = p.get_uint(":bound-max-refinements", 16); + m_threshold = p.get_double(":bound-threshold", 0.05); + m_small_interval = p.get_double(":bound-small-interval", 128); + m_strict2double = p.get_double(":strict2double", 0.00001); +} + +void bound_propagator::get_param_descrs(param_descrs & r) { + r.insert(":bound-max-refinements", CPK_UINT, "(default: 16) maximum number of bound refinements (per round) for unbounded variables."); + r.insert(":bound-threshold", CPK_DOUBLE, "(default: 0.05) bound propagation improvement threshold ratio."); +} + +void bound_propagator::collect_statistics(statistics & st) const { + st.update("bound conflicts", m_conflicts); + st.update("bound propagations", m_propagations); + st.update("bound false alarms", m_false_alarms); +} + +void bound_propagator::reset_statistics() { + m_conflicts = 0; + m_propagations = 0; + m_false_alarms = 0; +} + +void bound_propagator::mk_var(var x, bool is_int) { + m_is_int.reserve(x+1, false); + m_dead.reserve(x+1, true); + m_lowers.reserve(x+1, 0); + m_uppers.reserve(x+1, 0); + m_lower_refinements.reserve(x+1, 0); + m_upper_refinements.reserve(x+1, 0); + m_watches.reserve(x+1); + + SASSERT(m_dead[x]); + + m_is_int[x] = is_int; + m_dead[x] = false; + m_lowers[x] = 0; + m_uppers[x] = 0; + m_lower_refinements[x] = 0; + m_upper_refinements[x] = 0; + m_watches[x].reset(); +} + +void bound_propagator::del_var(var x) { + SASSERT(!m_dead[x]); + m_dead[x] = true; + // mark constraints containing x as dead. + wlist & wl = m_watches[x]; + wlist::iterator it = wl.begin(); + wlist::iterator end = wl.end(); + for (; it != end; ++it) { + m_constraints[*it].m_dead = true; + } +} + +void bound_propagator::mk_eq(unsigned sz, mpq * as, var * xs) { + linear_equation * eq = m_eq_manager.mk(sz, as, xs); + init_eq(eq); +} + +void bound_propagator::mk_eq(unsigned sz, mpz * as, var * xs) { + linear_equation * eq = m_eq_manager.mk(sz, as, xs); + init_eq(eq); +} + +void bound_propagator::init_eq(linear_equation * eq) { + if (eq == 0) + return; + unsigned c_idx = m_constraints.size(); + m_constraints.push_back(constraint()); + constraint & new_c = m_constraints.back(); + new_c.m_kind = LINEAR; + new_c.m_dead = false; + new_c.m_timestamp = 0; + new_c.m_act = 0; + new_c.m_counter = 0; + new_c.m_eq = eq; + unsigned sz = eq->size(); + for (unsigned i = 0; i < sz; i++) { + m_watches[eq->x(i)].push_back(c_idx); + } + if (propagate(c_idx) && scope_lvl() > 0) + m_reinit_stack.push_back(c_idx); +} + +void bound_propagator::push() { + m_scopes.push_back(scope()); + scope & s = m_scopes.back(); + s.m_trail_limit = m_trail.size(); + s.m_qhead_old = m_qhead; + s.m_reinit_stack_limit = m_reinit_stack.size(); + s.m_timestamp_old = m_timestamp; + s.m_in_conflict = inconsistent(); +} + +void bound_propagator::undo_trail(unsigned old_sz) { + SASSERT(old_sz <= m_trail.size()); + unsigned i = m_trail.size(); + while (i > old_sz) { + --i; + trail_info & info = m_trail.back(); + var x = info.x(); + bool is_lower = info.is_lower(); + m_trail.pop_back(); + bound * b; + if (is_lower) { + b = m_lowers[x]; + m_lowers[x] = b->m_prev; + } + else { + b = m_uppers[x]; + m_uppers[x] = b->m_prev; + } + m.del(b->m_k); + b->~bound(); + m_allocator.deallocate(sizeof(bound), b); + } + SASSERT(m_trail.size() == old_sz); +} + +void bound_propagator::pop(unsigned num_scopes) { + unsigned lvl = scope_lvl(); + SASSERT(num_scopes <= lvl); + unsigned new_lvl = lvl - num_scopes; + scope & s = m_scopes[new_lvl]; + undo_trail(s.m_trail_limit); + m_timestamp = s.m_timestamp_old; + m_qhead = s.m_qhead_old; + if (!s.m_in_conflict) + m_conflict = null_var; + unsigned reinit_stack_sz = s.m_reinit_stack_limit; + m_scopes.shrink(new_lvl); + + // reinitialize + unsigned i = reinit_stack_sz; + unsigned j = reinit_stack_sz; + unsigned sz = m_reinit_stack.size(); + for (; i < sz; i++) { + unsigned c_idx = m_reinit_stack[i]; + bool p = propagate(c_idx); + if (new_lvl > 0 && p) { + m_reinit_stack[j] = c_idx; + j++; + } + } + m_reinit_stack.shrink(j); +} + +bool bound_propagator::assert_lower_core(var x, mpq & k, bool strict, bkind bk, unsigned c_idx, assumption a) { + if (is_int(x)) { + if (m.is_int(k)) { + if (strict) + m.inc(k); + } + else { + m.ceil(k, k); + } + SASSERT(m.is_int(k)); + strict = false; + } + TRACE("bound_propagator_detail", tout << "new lower x" << x << " " << m.to_string(k) << " strict: " << strict << "\n";); + + bound * old_lower = m_lowers[x]; + if (old_lower) { + bool improves = m.gt(k, old_lower->m_k) || (!old_lower->m_strict && strict && m.eq(k, old_lower->m_k)); + if (!improves) { + if (bk == DERIVED) { + TRACE("bound_propagator_detail", tout << "false alarm\n";); + m_false_alarms++; + } + return false; + } + } + + if (bk == DERIVED) { + TRACE("bound_propagator_derived", tout << "new lower x" << x << " " << m.to_string(k) << " strict: " << strict << "\n";); + m_propagations++; + } + + if (scope_lvl() == 0 && bk == DERIVED) + bk = AXIOM; // don't need justification at level 0 + + double approx_k = m.get_double(k); + TRACE("new_bound", tout << "x" << x << " lower: " << m.to_string(k) << " approx: " << approx_k << "\n";); +#ifdef RELAX_BOUNDS + approx_k = PRECISION*floor(approx_k*INV_PRECISION + TOLERANCE); + TRACE("new_bound", tout << "x" << x << " lower: " << m.to_string(k) << " relaxed approx: " << approx_k << "\n";); +#endif + void * mem = m_allocator.allocate(sizeof(bound)); + bound * new_lower = new (mem) bound(m, k, approx_k, true, strict, scope_lvl(), m_timestamp, bk, c_idx, a, old_lower); + m_timestamp++; + m_lowers[x] = new_lower; + m_trail.push_back(trail_info(x, true)); + m_lower_refinements[x]++; + check_feasibility(x); + + return true; +} + +bool bound_propagator::assert_upper_core(var x, mpq & k, bool strict, bkind bk, unsigned c_idx, assumption a) { + if (is_int(x)) { + if (m.is_int(k)) { + if (strict) + m.dec(k); + } + else { + m.floor(k, k); + } + SASSERT(m.is_int(k)); + strict = false; + } + + TRACE("bound_propagator_detail", tout << "new upper x" << x << " " << m.to_string(k) << " strict: " << strict << "\n";); + + bound * old_upper = m_uppers[x]; + if (old_upper) { + bool improves = m.lt(k, old_upper->m_k) || (!old_upper->m_strict && strict && m.eq(k, old_upper->m_k)); + if (!improves) { + if (bk == DERIVED) { + TRACE("bound_propagator_detail", tout << "false alarm\n";); + m_false_alarms++; + } + return false; + } + } + + if (bk == DERIVED) { + m_propagations++; + TRACE("bound_propagator_derived", tout << "new upper x" << x << " " << m.to_string(k) << " strict: " << strict << "\n";); + } + + if (scope_lvl() == 0 && bk == DERIVED) + bk = AXIOM; // don't need justification at level 0 + + double approx_k = m.get_double(k); + TRACE("new_bound", tout << "x" << x << " upper: " << m.to_string(k) << " approx: " << approx_k << "\n";); +#ifdef RELAX_BOUNDS + approx_k = PRECISION*ceil(approx_k*INV_PRECISION - TOLERANCE); + TRACE("new_bound", tout << "x" << x << " upper: " << m.to_string(k) << " relaxed approx: " << approx_k << "\n";); +#endif + + void * mem = m_allocator.allocate(sizeof(bound)); + bound * new_upper = new (mem) bound(m, k, approx_k, false, strict, scope_lvl(), m_timestamp, bk, c_idx, a, m_uppers[x]); + m_timestamp++; + m_uppers[x] = new_upper; + m_trail.push_back(trail_info(x, false)); + m_upper_refinements[x]++; + check_feasibility(x); + return true; +} + +bool bound_propagator::get_interval_size(var x, double & r) const { + bound * l = m_lowers[x]; + bound * u = m_uppers[x]; + if (l && u) { + r = u->m_approx_k - l->m_approx_k; + return true; + } + return false; +} + +template +bool bound_propagator::relevant_bound(var x, double new_k) const { + TRACE("bound_propagator_detail", tout << "relevant_bound x" << x << " " << new_k << " LOWER: " << LOWER << "\n"; + if (LOWER && has_lower(x)) tout << "old: " << m.to_string(m_lowers[x]->m_k) << " | " << m_lowers[x]->m_approx_k << "\n"; + if (!LOWER && has_upper(x)) tout << "old: " << m.to_string(m_uppers[x]->m_k) << " | " << m_uppers[x]->m_approx_k << "\n";); + bound * b = LOWER ? m_lowers[x] : m_uppers[x]; + if (b == 0) + return true; // variable did not have a bound + + double interval_size; + bool bounded = get_interval_size(x, interval_size); + + if (!is_int(x)) { + // check if the improvement is significant + double improvement; + double abs_k = b->m_approx_k; + if (abs_k < 0.0) + abs_k -= abs_k; + if (bounded) + improvement = m_threshold * std::max(std::min(interval_size, abs_k), 1.0); + else + improvement = m_threshold * std::max(abs_k, 1.0); + + if (LOWER) { + if (new_k <= b->m_approx_k + improvement) { + TRACE("bound_propagator", tout << "LOWER new: " << new_k << " old: " << b->m_approx_k << " improvement is too small\n";); + return false; // improvement is too small + } + } + else { + if (new_k >= b->m_approx_k - improvement) { + TRACE("bound_propagator", tout << "UPPER new: " << new_k << " old: " << b->m_approx_k << " improvement is too small\n";); + return false; // improvement is too small + } + } + } + else { + if (LOWER) { + if (new_k < b->m_approx_k + 1.0) + return false; // no improvement + } + else { + if (new_k > b->m_approx_k - 1.0) + return false; // no improvement + } + } + + if (bounded && interval_size <= m_small_interval) + return true; + + if (LOWER) + return m_lower_refinements[x] < m_max_refinements; + else + return m_upper_refinements[x] < m_max_refinements; +} + +bool bound_propagator::relevant_lower(var x, double approx_k) const { + return relevant_bound(x, approx_k); +} + +bool bound_propagator::relevant_upper(var x, double approx_k) const { + return relevant_bound(x, approx_k); +} + +void bound_propagator::check_feasibility(var x) { + if (inconsistent()) + return; + bound * l = m_lowers[x]; + bound * u = m_uppers[x]; + if (l && u) { + if (m.lt(l->m_k, u->m_k)) + return; + if (!l->m_strict && !u->m_strict && m.eq(l->m_k, u->m_k)) + return; + m_conflict = x; + m_conflicts++; + SASSERT(inconsistent()); + TRACE("bound_propagator", tout << "inconsistency detected: x" << x << "\n"; display(tout);); + } +} + +void bound_propagator::propagate() { + m_to_reset_ts.reset(); + + while (m_qhead < m_trail.size()) { + if (inconsistent()) + break; + trail_info & info = m_trail[m_qhead]; + var x = info.x(); + bool is_lower = info.is_lower(); + bound * b = is_lower ? m_lowers[x] : m_uppers[x]; + SASSERT(b); + unsigned ts = b->m_timestamp; + TRACE("bound_propagator_detail", tout << "propagating x" << x << "\n";); + m_qhead++; + wlist const & wl = m_watches[x]; + wlist::const_iterator it = wl.begin(); + wlist::const_iterator end = wl.end(); + for (; it != end; ++it) { + unsigned c_idx = *it; + constraint & c = m_constraints[c_idx]; + // We don't need to visit c if it was already propagated using b. + // Whenever we visit c we store in c.m_timestamp the current timestamp + // So, we know that c was already propagated any bound using bounds with timestamp lower than c.m_timestamp. + if (ts >= c.m_timestamp) { + if (c.m_timestamp == 0) + m_to_reset_ts.push_back(c_idx); + c.m_timestamp = m_timestamp; + propagate(c_idx); + } + } + } + + unsigned_vector::iterator it = m_to_reset_ts.begin(); + unsigned_vector::iterator end = m_to_reset_ts.end(); + for (; it != end; ++it) + m_constraints[*it].m_timestamp = 0; +} + +bool bound_propagator::propagate(unsigned c_idx) { + constraint const & c = m_constraints[c_idx]; + if (c.m_dead) + return false; + if (c.m_kind == LINEAR) + return propagate_eq(c_idx); + return false; +} + +bool bound_propagator::propagate_eq(unsigned c_idx) { + constraint const & c = m_constraints[c_idx]; + linear_equation * eq = c.m_eq; + +#if 0 + { + static unsigned counter = 0; + static unsigned visited = 0; + counter++; + visited += eq->size(); + if (counter % 1000 == 0) + verbose_stream() << "[bound-propagator] :propagate-eq " << counter << " :visited-vars " << visited << std::endl; + } +#endif + + TRACE("bound_propagator_detail", tout << "propagating using eq: "; m_eq_manager.display(tout, *eq); tout << "\n";); + // ll = (Sum_{a_i < 0} -a_i*lower(x_i)) + (Sum_{a_i > 0} -a_i * upper(x_i)) + // uu = (Sum_{a_i > 0} -a_i*lower(x_i)) + (Sum_{a_i < 0} -a_i * upper(x_i)) + unsigned ll_i = UINT_MAX; // position of the variable that couldn't contribute to ll + unsigned uu_i = UINT_MAX; // position of the variable that coundn't contribute to uu + bool ll_failed = false; + bool uu_failed = false; + double ll = 0.0; + double uu = 0.0; + unsigned sz = eq->size(); + for (unsigned i = 0; i < sz; i++) { + var x_i = eq->x(i); + double a_i = eq->approx_a(i); + bound * l_i = m_lowers[x_i]; + bound * u_i = m_uppers[x_i]; + if (a_i < 0.0) { + if (!ll_failed) { + if (l_i == 0) { + if (ll_i == UINT_MAX) + ll_i = i; + else + ll_failed = true; + } + else { + ll -= a_i * l_i->m_approx_k; + } + } + + if (!uu_failed) { + if (u_i == 0) { + if (uu_i == UINT_MAX) + uu_i = i; + else + uu_failed = true; + } + else { + uu -= a_i * u_i->m_approx_k; + } + } + } + else { + if (!ll_failed) { + if (u_i == 0) { + if (ll_i == UINT_MAX) + ll_i = i; + else + ll_failed = true; + } + else { + ll -= a_i * u_i->m_approx_k; + } + } + + if (!uu_failed) { + if (l_i == 0) { + if (uu_i == UINT_MAX) + uu_i = i; + else + uu_failed = true; + } + else { + uu -= a_i * l_i->m_approx_k; + } + } + } + if (ll_failed && uu_failed) + return false; // nothing to propagate + } + + bool propagated = false; + + SASSERT(!ll_failed || !uu_failed); + if (ll_i == UINT_MAX || uu_i == UINT_MAX) { + for (unsigned i = 0; i < sz; i++) { + var x_i = eq->x(i); + double a_i = eq->approx_a(i); + bound * l_i = m_lowers[x_i]; + bound * u_i = m_uppers[x_i]; + // ll = (Sum_{a_i < 0} -a_i*lower(x_i)) + (Sum_{a_i > 0} -a_i * upper(x_i)) + // uu = (Sum_{a_i > 0} -a_i*lower(x_i)) + (Sum_{a_i < 0} -a_i * upper(x_i)) + if (ll_i == UINT_MAX) { + // can propagate a lower bound for a_i*x_i + if (a_i > 0.0) { + // can propagate a lower bound for x_i + double new_k = (ll + a_i * u_i->m_approx_k)/a_i; + if (relevant_lower(x_i, new_k) && propagate_lower(c_idx, i)) + propagated = true; + } + else { + // a_i < 0.0 + // can propagate a upper bound for x_i + double new_k = (ll + a_i * l_i->m_approx_k)/a_i; + if (relevant_upper(x_i, new_k) && propagate_upper(c_idx, i)) + propagated = true; + } + } + if (uu_i == UINT_MAX) { + // can propagate an upper bound for a_i*x_i + if (a_i > 0.0) { + // can propagate a upper bound for x_i + double new_k = (uu + a_i * l_i->m_approx_k)/a_i; + if (relevant_upper(x_i, new_k) && propagate_upper(c_idx, i)) + propagated = true; + } + else { + // a_i < 0.0 + // can propagate a lower bound for x_i + double new_k = (uu + a_i * u_i->m_approx_k)/a_i; + if (relevant_lower(x_i, new_k) && propagate_lower(c_idx, i)) + propagated = true; + } + } + } + } + + if (!ll_failed && ll_i != UINT_MAX) { + // can propagate a lower bound for the monomial at position ll_i + var x_i = eq->x(ll_i); + double a_i = eq->approx_a(ll_i); + double new_k = ll/a_i; + if (a_i > 0.0) { + if (relevant_lower(x_i, new_k) && propagate_lower(c_idx, ll_i)) + propagated = true; + } + else { + if (relevant_upper(x_i, new_k) && propagate_upper(c_idx, ll_i)) + propagated = true; + } + } + + if (!uu_failed && uu_i != UINT_MAX) { + // can propagate a upper bound for the monomial at position uu_i + var x_i = eq->x(uu_i); + double a_i = eq->approx_a(uu_i); + double new_k = uu/a_i; + if (a_i > 0.0) { + if (relevant_upper(x_i, new_k) && propagate_upper(c_idx, uu_i)) + propagated = true; + } + else { + if (relevant_lower(x_i, new_k) && propagate_lower(c_idx, uu_i)) + propagated = true; + } + } + + return propagated; +} + +/** + \brief Try to propagate a lower bound for the variable stored at position i, using mpq's (rationals). + When this method is invoked, we know that all other variables have the "right" bounds, and + using doubles we improve the current known bound. +*/ +bool bound_propagator::propagate_lower(unsigned c_idx, unsigned i) { + constraint const & c = m_constraints[c_idx]; + linear_equation * eq = c.m_eq; + var x_i = eq->x(i); + mpz const & a_i = eq->a(i); + unsigned sz = eq->size(); + mpq k; + bool strict = false; + bool neg_a_i = m.is_neg(a_i); + for (unsigned j = 0; j < sz; j++) { + if (i == j) + continue; + var x_j = eq->x(j); + mpz const & a_j = eq->a(j); + bound * b_j = (m.is_neg(a_j) == neg_a_i) ? m_uppers[x_j] : m_lowers[x_j]; + TRACE("bound_propagator_step_detail", tout << "k: " << m.to_string(k) << " b_j->m_k: " << m.to_string(b_j->m_k) << + " a_j: " << m.to_string(a_j) << "\n";); + SASSERT(b_j); + if (b_j->m_strict) + strict = true; + m.addmul(k, a_j, b_j->m_k, k); + } + TRACE("bound_propagator_step_detail", tout << "k: " << m.to_string(k) << "\n";); + m.neg(k); + m.div(k, a_i, k); + TRACE("bound_propagator_step", tout << "propagating lower x" << x_i << " " << m.to_string(k) << " strict: " << strict << " using\n"; + m_eq_manager.display(tout, *eq); tout << "\n"; display_bounds_of(tout, *eq);); + bool r = assert_lower_core(x_i, k, strict, DERIVED, c_idx, null_assumption); + m.del(k); + return r; +} + +/** + \brief Try to propagate a upper bound for the variable stored at position i, using mpq's (rationals). + When this method is invoked, we know that all other variables have the "right" bounds, and + using doubles we improve the current known bound. +*/ +bool bound_propagator::propagate_upper(unsigned c_idx, unsigned i) { + constraint const & c = m_constraints[c_idx]; + linear_equation * eq = c.m_eq; + var x_i = eq->x(i); + mpz const & a_i = eq->a(i); + unsigned sz = eq->size(); + mpq k; + bool strict = false; + bool neg_a_i = m.is_neg(a_i); + for (unsigned j = 0; j < sz; j++) { + if (i == j) + continue; + var x_j = eq->x(j); + mpz const & a_j = eq->a(j); + bound * b_j = (m.is_neg(a_j) == neg_a_i) ? m_lowers[x_j] : m_uppers[x_j]; + SASSERT(b_j); + if (b_j->m_strict) + strict = true; + m.addmul(k, a_j, b_j->m_k, k); + } + m.neg(k); + m.div(k, a_i, k); + TRACE("bound_propagator_step", tout << "propagating upper x" << x_i << " " << m.to_string(k) << " strict: " << strict << " using\n"; + m_eq_manager.display(tout, *eq); tout << "\n"; display_bounds_of(tout, *eq);); + bool r = assert_upper_core(x_i, k, strict, DERIVED, c_idx, null_assumption); + m.del(k); + return r; +} + +void bound_propagator::reset() { + undo_trail(0); + del_constraints_core(); + m_constraints.finalize(); + m_is_int.finalize(); + m_dead.finalize(); + m_lowers.finalize(); + m_uppers.finalize(); + m_watches.finalize(); + m_trail.finalize(); + m_qhead = 0; + m_reinit_stack.finalize(); + m_lower_refinements.finalize(); + m_upper_refinements.finalize(); + m_timestamp = 0; + m_conflict = null_var; + m_scopes.finalize(); +} + +bool bound_propagator::lower(var x, mpq & k, bool & strict, unsigned & ts) const { + bound * b = m_lowers[x]; + if (!b) + return false; + m.set(k, b->m_k); + strict = b->m_strict; + ts = b->m_timestamp; + return true; +} + +bool bound_propagator::upper(var x, mpq & k, bool & strict, unsigned & ts) const { + bound * b = m_uppers[x]; + if (!b) + return false; + m.set(k, b->m_k); + strict = b->m_strict; + ts = b->m_timestamp; + return true; +} + +bound_propagator::bound * bound_propagator::bound::at(unsigned timestamp) { + bound * r = this; + while (r != 0 && r->m_timestamp >= timestamp) + r = r->m_prev; + return r; +} + +/** + \brief Return true if the coefficient of x in eq is positive +*/ +bool bound_propagator::is_a_i_pos(linear_equation const & eq, var x) const { + unsigned i = eq.pos(x); + if (i == UINT_MAX) + return false; + return m.is_pos(eq.a(i)); +} + +void bound_propagator::explain(var x, bound * b, unsigned ts, assumption_vector & ex) const { + if (!b) + return; + b = b->at(ts); + if (!b) + return; + if (b->m_kind == AXIOM || b->m_kind == DECISION) + return; + if (b->m_kind == ASSUMPTION) { + ex.push_back(b->m_assumption); + return; + } + svector & todo = const_cast(this)->m_todo; + todo.reset(); + unsigned qhead = 0; + todo.push_back(var_bound(x, b)); + b->m_mark = true; + while (qhead < todo.size()) { + var_bound & vb = todo[qhead]; + qhead ++; + var x = vb.first; + bound * b = vb.second; + SASSERT(b->kind() == ASSUMPTION || b->kind() == DERIVED); + if (b->kind() == ASSUMPTION) { + ex.push_back(b->m_assumption); + continue; + } + SASSERT(b->kind() == DERIVED); + constraint const & c = m_constraints[b->m_constraint_idx]; + switch (c.m_kind) { + case LINEAR: { + linear_equation * eq = c.m_eq; + bool is_lower = b->is_lower(); + if (!is_a_i_pos(*eq, x)) + is_lower = !is_lower; + unsigned sz = eq->size(); + for (unsigned i = 0; i < sz; i++) { + var x_i = eq->x(i); + if (x_i == x) + continue; + bound * b = (m.is_neg(eq->a(i)) == is_lower) ? m_lowers[x_i] : m_uppers[x_i]; + SASSERT(b); + if (b->kind() == DERIVED || b->kind() == ASSUMPTION) { + if (!b->m_mark) { + b->m_mark = true; + todo.push_back(var_bound(x_i, b)); + } + } + } + break; + } + default: + break; + } + } + unsigned sz = todo.size(); + for (unsigned i = 0; i < sz; i++) + todo[i].second->m_mark = false; + todo.reset(); +} + +/** + \brief Compute lower (upper) bound for the linear polynomial as[0]*xs[0] + ... + as[sz-1]*xs[sz-1] + + Return false if the lower (upper) bound is -oo (oo) +*/ +template +bool bound_propagator::get_bound(unsigned sz, Numeral const * as, var const * xs, mpq & r, bool & st) const { + st = false; + m.reset(r); + for (unsigned i = 0; i < sz; i++) { + var x_i = xs[i]; + Numeral const & a_i = as[i]; + if (m.is_zero(a_i)) + continue; + bound * b = (m.is_neg(a_i) == LOWER) ? m_uppers[x_i] : m_lowers[x_i]; + if (!b) { + m.reset(r); + return false; + } + if (b->m_strict) + st = true; + m.addmul(r, a_i, b->m_k, r); + } + return true; +} + +bool bound_propagator::lower(unsigned sz, mpq const * as, var const * xs, mpq & r, bool & st) const { + return get_bound(sz, as, xs, r, st); +} + +bool bound_propagator::upper(unsigned sz, mpq const * as, var const * xs, mpq & r, bool & st) const { + return get_bound(sz, as, xs, r, st); +} + +void bound_propagator::display_bounds_of(std::ostream & out, linear_equation const & eq) const { + for (unsigned i = 0; i < eq.size(); i++) { + display_var_bounds(out, eq.x(i)); + out << "\n"; + } +} + +void bound_propagator::display_var_bounds(std::ostream & out, var x, bool approx, bool precise) const { + if (m_lowers[x]) { + if (precise) + out << m.to_string(m_lowers[x]->m_k); + if (precise && approx) + out << " | "; + if (approx) + out << m_lowers[x]->m_approx_k; + out << " " << (m_lowers[x]->m_strict ? "<" : "<="); + } + else { + out << "-oo <"; + } + out << " x" << x << " "; + if (m_uppers[x]) { + out << (m_uppers[x]->m_strict ? "<" : "<=") << " "; + if (precise) + out << m.to_string(m_uppers[x]->m_k); + if (precise && approx) + out << " | "; + if (approx) + out << m_uppers[x]->m_approx_k; + } + else { + out << "< oo"; + } +} + +void bound_propagator::display_bounds(std::ostream & out, bool approx, bool precise) const { + unsigned num_vars = m_dead.size(); + for (unsigned x = 0; x < num_vars; x++) { + if (!is_dead(x)) { + display_var_bounds(out, x, approx, precise); + out << "\n"; + } + } +} + +void bound_propagator::display_constraints(std::ostream & out) const { + constraint_vector::const_iterator it = m_constraints.begin(); + constraint_vector::const_iterator end = m_constraints.end(); + for (; it != end; ++it) { + constraint const & c = *it; + if (c.m_kind == LINEAR) { + m_eq_manager.display(out, *(c.m_eq)); + out << "\n"; + } + } +} + +void bound_propagator::display(std::ostream & out) const { + display_bounds(out); + display_constraints(out); +} + + + diff --git a/lib/bound_propagator.h b/lib/bound_propagator.h new file mode 100644 index 000000000..04f2e0e17 --- /dev/null +++ b/lib/bound_propagator.h @@ -0,0 +1,265 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + bound_propagator.h + +Abstract: + + Bound propagators for arithmetic. + Support class for implementing strategies and search procedures + +Author: + + Leonardo de Moura (leonardo) 2011-06-18. + +Revision History: + +--*/ +#ifndef _BOUND_PROPAGATOR_H_ +#define _BOUND_PROPAGATOR_H_ + +#include"mpq.h" +#include"vector.h" +#include"params.h" +#include"statistics.h" +#include"numeral_buffer.h" +#include"linear_equation.h" + +class bound_propagator { +public: + typedef unsigned var; + typedef unsigned assumption; + typedef unsynch_mpq_manager numeral_manager; + typedef unsigned_vector assumption_vector; + typedef unsigned constraint_id; + typedef numeral_buffer mpz_buffer; + typedef svector double_vector; + static const assumption null_assumption = UINT_MAX; + static const var null_var = UINT_MAX; + static const unsigned null_constraint_idx = UINT_MAX; + class trail_info { + unsigned m_x_lower; + public: + trail_info(var x, bool is_lower):m_x_lower((x << 1) + static_cast(is_lower)) {} + trail_info():m_x_lower(UINT_MAX) {} + var x() const { return m_x_lower >> 1; } + bool is_lower() const { return (m_x_lower & 1) != 0; } + }; + +protected: + + enum ckind { LINEAR // only linear equalities so far. + }; + + /** + \brief Constraints don't need justification. + */ + class constraint { + friend class bound_propagator; + unsigned m_kind:2; + unsigned m_dead:1; + unsigned m_timestamp; // Constraint tried to propagate new bounds using bounds with timestamp < m_timestamp. + unsigned m_act; // activity + unsigned m_counter; // number of times the constraint propagated + union { + linear_equation * m_eq; + }; + }; + + enum bkind { AXIOM, // doesn't need justification + ASSUMPTION, // aka external case-split, it is used to connect with external search engine. + DERIVED, // implied + DECISION // internal case-split + }; + + struct bound { + mpq m_k; + double m_approx_k; + unsigned m_lower:1; + unsigned m_strict:1; + unsigned m_mark:1; + unsigned m_kind:2; + unsigned m_level:27; + unsigned m_timestamp; + union { + assumption m_assumption; + unsigned m_constraint_idx; + }; + bound * m_prev; + bound(numeral_manager & m, mpq const & k, double approx_k, bool lower, bool strict, unsigned lvl, unsigned ts, bkind bk, + unsigned c_idx, assumption a, bound * prev); + + bound * at(unsigned timestamp); + bkind kind() const { return static_cast(m_kind); } + bool is_lower() const { return m_lower != 0; } + }; + + typedef ptr_vector var2bound; + typedef svector var_vector; + typedef svector constraint_vector; + typedef unsigned_vector c_idx_vector; + typedef c_idx_vector wlist; + typedef small_object_allocator allocator; + typedef linear_equation_manager lin_eq_manager; + + numeral_manager & m; + allocator & m_allocator; + lin_eq_manager m_eq_manager; + constraint_vector m_constraints; + char_vector m_is_int; + char_vector m_dead; + var2bound m_lowers; + var2bound m_uppers; + vector m_watches; + svector m_trail; + unsigned m_qhead; + c_idx_vector m_reinit_stack; + unsigned_vector m_lower_refinements; // number of times a lower bound was propagated for each variable (loop prevention) + unsigned_vector m_upper_refinements; // number of times a upper bound was propagated for each variable (loop prevention) + unsigned m_timestamp; + var m_conflict; + mpq m_tmp; + + struct scope { + unsigned m_trail_limit; + unsigned m_qhead_old; + unsigned m_reinit_stack_limit; + unsigned m_timestamp_old:31; + unsigned m_in_conflict:1; + }; + + svector m_scopes; + + unsigned_vector m_to_reset_ts; // temp field: ids of the constraints we must reset the field m_timestamp + + // config + unsigned m_max_refinements; // maximum number of refinements per round + double m_small_interval; + double m_threshold; // improvement threshold + double m_strict2double; + + // statistics + unsigned m_conflicts; + unsigned m_propagations; + unsigned m_false_alarms; + + void del_constraint(constraint & cnstr); + void del_constraints_core(); + + template + bool relevant_bound(var x, double approx_k) const; + bool relevant_lower(var x, double approx_k) const; + bool relevant_upper(var x, double approx_k) const; + bool get_interval_size(var x, double & r) const; + void check_feasibility(var x); + + bool assert_lower_core(var x, mpq & k, bool strict, bkind bk, unsigned c_idx, assumption a); + bool assert_upper_core(var x, mpq & k, bool strict, bkind bk, unsigned c_idx, assumption a); + + bool propagate(unsigned c_idx); + bool propagate_eq(unsigned c_idx); + bool propagate_lower(unsigned c_idx, unsigned i); + bool propagate_upper(unsigned c_idx, unsigned i); + void undo_trail(unsigned old_sz); + + typedef std::pair var_bound; + svector m_todo; + void explain(var x, bound * b, unsigned ts, assumption_vector & ex) const; + bool is_a_i_pos(linear_equation const & eq, var x) const; + + template + bool get_bound(unsigned sz, Numeral const * as, var const * xs, mpq & r, bool & st) const; + + void init_eq(linear_equation * eq); + +public: + bound_propagator(numeral_manager & m, allocator & a, params_ref const & p); + ~bound_propagator(); + + void updt_params(params_ref const & p); + static void get_param_descrs(param_descrs & r); + + void collect_statistics(statistics & st) const; + void reset_statistics(); + + double strict2double() const { return m_strict2double; } + + bool is_int(var x) const { return m_is_int[x] != 0; } + + unsigned scope_lvl() const { return m_scopes.size(); } + void mk_var(var x, bool is_int); + void del_var(var x); + bool is_dead(var x) const { return m_dead[x] != 0; } + void mk_eq(unsigned sz, mpq * as, var * xs); + void mk_eq(unsigned sz, mpz * as, var * xs); + void del_constraints(); + void assert_lower(var x, mpq const & k, bool strict, assumption a = null_assumption) { + m.set(m_tmp, k); + assert_lower_core(x, m_tmp, strict, a == null_assumption ? AXIOM : ASSUMPTION, 0, a); + } + void assert_upper(var x, mpq const & k, bool strict, assumption a = null_assumption) { + m.set(m_tmp, k); + assert_upper_core(x, m_tmp, strict, a == null_assumption ? AXIOM : ASSUMPTION, 0, a); + } + void assert_decided_lower(var x, mpq const & k) { + m.set(m_tmp, k); + assert_lower_core(x, m_tmp, false, DECISION, 0, null_assumption); + } + void assert_decided_upper(var x, mpq const & k) { + m.set(m_tmp, k); + assert_upper_core(x, m_tmp, false, DECISION, 0, null_assumption); + } + void propagate(); + void push(); + void pop(unsigned num_scopes); + void reset(); + bool has_lower(var x) const { return m_lowers[x] != 0; } + bool has_upper(var x) const { return m_uppers[x] != 0; } + bool lower(var x, mpq & k, bool & strict, unsigned & ts) const; + bool upper(var x, mpq & k, bool & strict, unsigned & ts) const; + bool is_fixed(var x) const { return has_lower(x) && has_upper(x) && m.eq(m_lowers[x]->m_k, m_uppers[x]->m_k) && !inconsistent(); } + mpq const & lower(var x, bool & strict) const { SASSERT(has_lower(x)); bound * b = m_lowers[x]; strict = b->m_strict; return b->m_k; } + mpq const & upper(var x, bool & strict) const { SASSERT(has_upper(x)); bound * b = m_uppers[x]; strict = b->m_strict; return b->m_k; } + mpq const & lower(var x) const { SASSERT(has_lower(x)); return m_lowers[x]->m_k; } + mpq const & upper(var x) const { SASSERT(has_upper(x)); return m_uppers[x]->m_k; } + double approx_lower(var x) const { + SASSERT(has_lower(x)); + return m_lowers[x]->m_strict ? m_lowers[x]->m_approx_k + m_strict2double : m_lowers[x]->m_approx_k; + } + double approx_upper(var x) const { + SASSERT(has_upper(x)); + return m_uppers[x]->m_strict ? m_uppers[x]->m_approx_k - m_strict2double : m_uppers[x]->m_approx_k; + } + bool is_zero(var x) const { return has_lower(x) && has_upper(x) && m.is_zero(lower(x)) && m.is_zero(upper(x)); } + void explain_lower(var x, unsigned ts, assumption_vector & ex) const { explain(x, m_lowers[x], ts, ex); } + void explain_upper(var x, unsigned ts, assumption_vector & ex) const { explain(x, m_uppers[x], ts, ex); } + void explain_lower(var x, assumption_vector & ex) const { explain_lower(x, m_timestamp, ex); } + void explain_upper(var x, assumption_vector & ex) const { explain_upper(x, m_timestamp, ex); } + var conflict_var() const { return m_conflict; } + bool inconsistent() const { return m_conflict != null_var; } + + unsigned trail_size() const { return m_trail.size(); } + unsigned qhead() const { return m_qhead; } + + typedef svector::const_iterator trail_iterator; + + trail_iterator begin_trail() const { return m_trail.begin(); } + trail_iterator end_trail() const { return m_trail.end(); } + + bool lower(unsigned sz, mpq const * as, var const * xs, mpq & r, bool & st) const; + bool upper(unsigned sz, mpq const * as, var const * xs, mpq & r, bool & st) const; + void display(std::ostream & out) const; + void display_var_bounds(std::ostream & out, var x, bool approx = true, bool precise = true) const; + void display_bounds(std::ostream & out, bool approx = true, bool precise = true) const; + void display_precise_bounds(std::ostream & out) const { display_bounds(out, false, true); } + void display_approx_bounds(std::ostream & out) const { display_bounds(out, true, false); } + void display_constraints(std::ostream & out) const; + void display_bounds_of(std::ostream & out, linear_equation const & eq) const; + + unsigned get_num_false_alarms() const { return m_false_alarms; } + unsigned get_num_propagations() const { return m_propagations; } +}; + +#endif diff --git a/lib/buffer.h b/lib/buffer.h new file mode 100644 index 000000000..d3d85db89 --- /dev/null +++ b/lib/buffer.h @@ -0,0 +1,256 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + buffer.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2006-10-16. + +Revision History: + +--*/ +#ifndef _BUFFER_H_ +#define _BUFFER_H_ + +#include +#include"memory_manager.h" + +template +class buffer { +protected: + T * m_buffer; + unsigned m_pos; + unsigned m_capacity; + char m_initial_buffer[INITIAL_SIZE * sizeof(T)]; + + void free_memory() { + if (m_buffer != reinterpret_cast(m_initial_buffer)) { + memory::deallocate(m_buffer); + } + } + + void expand() { + unsigned new_capacity = m_capacity << 1; + T * new_buffer = reinterpret_cast(memory::allocate(sizeof(T) * new_capacity)); + memcpy(new_buffer, m_buffer, m_pos * sizeof(T)); + free_memory(); + m_buffer = new_buffer; + m_capacity = new_capacity; + } + + void destroy_elements() { + iterator it = begin(); + iterator e = end(); + for (; it != e; ++it) { + it->~T(); + } + } + + void destroy() { + if (CallDestructors) { + destroy_elements(); + } + free_memory(); + } + +public: + typedef T data; + typedef T * iterator; + typedef const T * const_iterator; + + buffer(): + m_buffer(reinterpret_cast(m_initial_buffer)), + m_pos(0), + m_capacity(INITIAL_SIZE) { + } + + buffer(const buffer & source): + m_buffer(reinterpret_cast(m_initial_buffer)), + m_pos(0), + m_capacity(INITIAL_SIZE) { + unsigned sz = source.size(); + for(unsigned i = 0; i < sz; i++) { + push_back(source.m_buffer[i]); + } + } + + buffer(unsigned sz, const T & elem): + m_buffer(reinterpret_cast(m_initial_buffer)), + m_pos(0), + m_capacity(INITIAL_SIZE) { + for (unsigned i = 0; i < sz; i++) { + push_back(elem); + } + SASSERT(size() == sz); + } + + ~buffer() { + destroy(); + } + + void reset() { + if (CallDestructors) { + destroy_elements(); + } + m_pos = 0; + } + + void finalize() { + destroy(); + m_buffer = reinterpret_cast(m_initial_buffer); + m_pos = 0; + m_capacity = INITIAL_SIZE; + } + + unsigned size() const { + return m_pos; + } + + bool empty() const { + return m_pos == 0; + } + + iterator begin() { + return m_buffer; + } + + iterator end() { + return m_buffer + size(); + } + + void set_end(iterator it) { + m_pos = static_cast(it - m_buffer); + if (CallDestructors) { + iterator e = end(); + for (; it != e; ++it) { + it->~T(); + } + } + } + + const_iterator begin() const { + return m_buffer; + } + + const_iterator end() const { + return m_buffer + size(); + } + + void push_back(const T & elem) { + if (m_pos >= m_capacity) + expand(); + new (m_buffer + m_pos) T(elem); + m_pos++; + } + + void pop_back() { + if (CallDestructors) { + back().~T(); + } + m_pos--; + } + + const T & back() const { + SASSERT(!empty()); + SASSERT(m_pos > 0); + return m_buffer[m_pos - 1]; + } + + T & back() { + SASSERT(!empty()); + SASSERT(m_pos > 0); + return m_buffer[m_pos - 1]; + } + + T * c_ptr() const { + return m_buffer; + } + + void append(unsigned n, T const * elems) { + for (unsigned i = 0; i < n; i++) { + push_back(elems[i]); + } + } + + void append(const buffer& source) { + append(source.size(), source.c_ptr()); + } + + T & operator[](unsigned idx) { + SASSERT(idx < size()); + return m_buffer[idx]; + } + + const T & operator[](unsigned idx) const { + SASSERT(idx < size()); + return m_buffer[idx]; + } + + T & get(unsigned idx) { + SASSERT(idx < size()); + return m_buffer[idx]; + } + + const T & get(unsigned idx) const { + SASSERT(idx < size()); + return m_buffer[idx]; + } + + void set(unsigned idx, T const & val) { + SASSERT(idx < size()); + m_buffer[idx] = val; + } + + void resize(unsigned nsz, const T & elem=T()) { + unsigned sz = size(); + if (nsz > sz) { + for (unsigned i = sz; i < nsz; i++) { + push_back(elem); + } + } + else if (nsz < sz) { + for (unsigned i = nsz; i < sz; i++) { + pop_back(); + } + } + SASSERT(size() == nsz); + } + + void shrink(unsigned nsz) { + unsigned sz = size(); + SASSERT(nsz <= sz); + for (unsigned i = nsz; i < sz; i++) + pop_back(); + SASSERT(size() == nsz); + } + +private: + buffer& operator=(buffer const&); +}; + +template +class ptr_buffer : public buffer { +public: + void append(unsigned n, T * const * elems) { + for (unsigned i = 0; i < n; i++) { + push_back(elems[i]); + } + } +}; + +template +class sbuffer : public buffer { +public: + sbuffer(): buffer() {} + sbuffer(unsigned sz, const T& elem) : buffer(sz,elem) {} +}; + +#endif /* _BUFFER_H_ */ + diff --git a/lib/bv1_blaster_tactic.cpp b/lib/bv1_blaster_tactic.cpp new file mode 100644 index 000000000..77ab1be31 --- /dev/null +++ b/lib/bv1_blaster_tactic.cpp @@ -0,0 +1,508 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + bv1_blaster_tactic.cpp + +Abstract: + + Rewriter for "blasting" bit-vectors of size n into bit-vectors of size 1. + This rewriter only supports concat and extract operators. + This transformation is useful for handling benchmarks that contain + many BV equalities. + + Remark: other operators can be mapped into concat/extract by using + the simplifiers. + +Author: + + Leonardo (leonardo) 2011-10-25 + +Notes: + +--*/ +#include"tactical.h" +#include"bit_blaster_model_converter.h" +#include"bv_decl_plugin.h" +#include"rewriter_def.h" +#include"for_each_expr.h" +#include"cooperate.h" + +class bv1_blaster_tactic : public tactic { + + struct rw_cfg : public default_rewriter_cfg { + ast_manager & m_manager; + bv_util m_util; + obj_map m_const2bits; + expr_ref_vector m_saved; + expr_ref m_bit1; + expr_ref m_bit0; + + unsigned long long m_max_memory; // in bytes + unsigned m_max_steps; + bool m_produce_models; + + ast_manager & m() const { return m_manager; } + bv_util & butil() { return m_util; } + bv_util const & butil() const { return m_util; } + + void cleanup_buffers() { + m_saved.finalize(); + } + + rw_cfg(ast_manager & m, params_ref const & p): + m_manager(m), + m_util(m), + m_saved(m), + m_bit1(m), + m_bit0(m) { + m_bit1 = butil().mk_numeral(rational(1), 1); + m_bit0 = butil().mk_numeral(rational(0), 1); + updt_params(p); + } + + void updt_params(params_ref const & p) { + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + m_max_steps = p.get_uint(":max-steps", UINT_MAX); + m_produce_models = p.get_bool(":produce-models", false); + } + + bool rewrite_patterns() const { UNREACHABLE(); return false; } + + bool max_steps_exceeded(unsigned num_steps) const { + cooperate("bv1 blaster"); + if (memory::get_allocation_size() > m_max_memory) + throw tactic_exception(TACTIC_MAX_MEMORY_MSG); + return num_steps > m_max_steps; + } + + typedef ptr_buffer bit_buffer; + + void get_bits(expr * arg, bit_buffer & bits) { + SASSERT(butil().is_concat(arg) || butil().get_bv_size(arg) == 1); + if (butil().is_concat(arg)) + bits.append(to_app(arg)->get_num_args(), to_app(arg)->get_args()); + else + bits.push_back(arg); + } + + void mk_const(func_decl * f, expr_ref & result) { + SASSERT(f->get_family_id() == null_family_id); + SASSERT(f->get_arity() == 0); + expr * r; + if (m_const2bits.find(f, r)) { + result = r; + return; + } + sort * s = f->get_range(); + SASSERT(butil().is_bv_sort(s)); + unsigned bv_size = butil().get_bv_size(s); + if (bv_size == 1) { + result = m().mk_const(f); + return; + } + sort * b = butil().mk_sort(1); + ptr_buffer bits; + for (unsigned i = 0; i < bv_size; i++) { + bits.push_back(m().mk_fresh_const(0, b)); + } + r = butil().mk_concat(bits.size(), bits.c_ptr()); + m_saved.push_back(r); + m_const2bits.insert(f, r); + result = r; + } + + void blast_bv_term(expr * t, expr_ref & result) { + bit_buffer bits; + unsigned bv_size = butil().get_bv_size(t); + if (bv_size == 1) { + result = t; + return; + } + unsigned i = bv_size; + while (i > 0) { + --i; + bits.push_back(butil().mk_extract(i, i, t)); + } + result = butil().mk_concat(bits.size(), bits.c_ptr()); + } + + void reduce_eq(expr * arg1, expr * arg2, expr_ref & result) { + bit_buffer bits1; + bit_buffer bits2; + get_bits(arg1, bits1); + get_bits(arg2, bits2); + SASSERT(bits1.size() == bits2.size()); + bit_buffer new_eqs; + unsigned i = bits1.size(); + while (i > 0) { + --i; + new_eqs.push_back(m().mk_eq(bits1[i], bits2[i])); + } + result = m().mk_and(new_eqs.size(), new_eqs.c_ptr()); + } + + void reduce_ite(expr * c, expr * t, expr * e, expr_ref & result) { + bit_buffer t_bits; + bit_buffer e_bits; + get_bits(t, t_bits); + get_bits(e, e_bits); + SASSERT(t_bits.size() == e_bits.size()); + bit_buffer new_ites; + unsigned num = t_bits.size(); + for (unsigned i = 0; i < num; i++) + new_ites.push_back(m().mk_ite(c, t_bits[i], e_bits[i])); + result = butil().mk_concat(new_ites.size(), new_ites.c_ptr()); + } + + void reduce_num(func_decl * f, expr_ref & result) { + SASSERT(f->get_num_parameters() == 2); + SASSERT(f->get_parameter(0).is_rational()); + SASSERT(f->get_parameter(1).is_int()); + bit_buffer bits; + rational v = f->get_parameter(0).get_rational(); + rational two(2); + unsigned sz = f->get_parameter(1).get_int(); + for (unsigned i = 0; i < sz; i++) { + if ((v % two).is_zero()) + bits.push_back(m_bit0); + else + bits.push_back(m_bit1); + v = div(v, two); + } + std::reverse(bits.begin(), bits.end()); + result = butil().mk_concat(bits.size(), bits.c_ptr()); + } + + void reduce_extract(func_decl * f, expr * arg, expr_ref & result) { + bit_buffer arg_bits; + get_bits(arg, arg_bits); + SASSERT(arg_bits.size() == butil().get_bv_size(arg)); + unsigned high = butil().get_extract_high(f); + unsigned low = butil().get_extract_low(f); + unsigned sz = arg_bits.size(); + unsigned start = sz - 1 - high; + unsigned end = sz - 1 - low; + bit_buffer bits; + for (unsigned i = start; i <= end; i++) { + bits.push_back(arg_bits[i]); + } + result = butil().mk_concat(bits.size(), bits.c_ptr()); + } + + void reduce_concat(unsigned num, expr * const * args, expr_ref & result) { + bit_buffer bits; + bit_buffer arg_bits; + for (unsigned i = 0; i < num; i++) { + expr * arg = args[i]; + arg_bits.reset(); + get_bits(arg, arg_bits); + bits.append(arg_bits.size(), arg_bits.c_ptr()); + } + result = butil().mk_concat(bits.size(), bits.c_ptr()); + } + + void reduce_bin_xor(expr * arg1, expr * arg2, expr_ref & result) { + bit_buffer bits1; + bit_buffer bits2; + get_bits(arg1, bits1); + get_bits(arg2, bits2); + SASSERT(bits1.size() == bits2.size()); + bit_buffer new_bits; + unsigned num = bits1.size(); + for (unsigned i = 0; i < num; i++) { + new_bits.push_back(m().mk_ite(m().mk_eq(bits1[i], bits2[i]), m_bit0, m_bit1)); + } + result = butil().mk_concat(new_bits.size(), new_bits.c_ptr()); + } + + void reduce_xor(unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(num_args > 0); +#if 1 + if (num_args == 1) { + result = args[0]; + return; + } + reduce_bin_xor(args[0], args[1], result); + for (unsigned i = 2; i < num_args; i++) { + reduce_bin_xor(result, args[i], result); + } +#else + ptr_buffer args_bits; + for (unsigned i = 0; i < num_args; i++) { + bit_buffer * buff_i = alloc(bit_buffer); + get_bits(args[i], *buff_i); + args_bits.push_back(buff_i); + } + bit_buffer new_bits; + unsigned sz = butil().get_bv_size(args[0]); + for (unsigned i = 0; i < sz; i++) { + ptr_buffer eqs; + for (unsigned j = 0; j < num_args; j++) { + bit_buffer * buff_j = args_bits[j]; + eqs.push_back(m().mk_eq(buff_j->get(i), m_bit1)); + } + expr * cond = m().mk_xor(eqs.size(), eqs.c_ptr()); + new_bits.push_back(m().mk_ite(cond, m_bit1, m_bit0)); + } + result = butil().mk_concat(new_bits.size(), new_bits.c_ptr()); + std::for_each(args_bits.begin(), args_bits.end(), delete_proc()); +#endif + } + + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + result_pr = 0; + if (num == 0 && f->get_family_id() == null_family_id && butil().is_bv_sort(f->get_range())) { + mk_const(f, result); + return BR_DONE; + } + + if (m().is_eq(f)) { + SASSERT(num == 2); + if (butil().is_bv(args[0])) { + reduce_eq(args[0], args[1], result); + return BR_DONE; + } + return BR_FAILED; + } + + if (m().is_ite(f)) { + SASSERT(num == 3); + if (butil().is_bv(args[1])) { + reduce_ite(args[0], args[1], args[2], result); + return BR_DONE; + } + return BR_FAILED; + } + + if (f->get_family_id() == butil().get_family_id()) { + switch (f->get_decl_kind()) { + case OP_BV_NUM: + reduce_num(f, result); + return BR_DONE; + case OP_EXTRACT: + SASSERT(num == 1); + reduce_extract(f, args[0], result); + return BR_DONE; + case OP_CONCAT: + reduce_concat(num, args, result); + return BR_DONE; + case OP_BXOR: + reduce_xor(num, args, result); + return BR_DONE; + default: + UNREACHABLE(); + return BR_FAILED; + } + } + + if (butil().is_bv_sort(f->get_range())) { + blast_bv_term(m().mk_app(f, num, args), result); + return BR_DONE; + } + + return BR_FAILED; + } + + bool reduce_quantifier(quantifier * old_q, + expr * new_body, + expr * const * new_patterns, + expr * const * new_no_patterns, + expr_ref & result, + proof_ref & result_pr) { + UNREACHABLE(); + return false; + } + }; + + struct rw : public rewriter_tpl { + rw_cfg m_cfg; + + rw(ast_manager & m, params_ref const & p): + rewriter_tpl(m, m.proofs_enabled(), m_cfg), + m_cfg(m, p) { + } + }; + + + struct imp { + rw m_rw; + unsigned m_num_steps; + + imp(ast_manager & m, params_ref const & p): + m_rw(m, p) { + } + + struct not_target {}; + + struct visitor { + family_id m_bv_fid; + visitor(family_id bv_fid):m_bv_fid(bv_fid) {} + void operator()(var const * n) { throw not_target(); } + void operator()(app const * n) { + if (n->get_family_id() == m_bv_fid) { + switch (n->get_decl_kind()) { + case OP_BV_NUM: + case OP_EXTRACT: + case OP_CONCAT: + return; + case OP_BXOR: + // it doesn't payoff to do the reduction in this case. + throw not_target(); + default: + throw not_target(); + } + } + } + void operator()(quantifier const * n) { throw not_target(); } + }; + + bool is_target(goal const & g) const { + expr_fast_mark1 visited; + unsigned sz = g.size(); + visitor proc(m_rw.cfg().butil().get_family_id()); + try { + for (unsigned i = 0; i < sz; i++) { + expr * f = g.form(i); + for_each_expr_core(proc, visited, f); + } + } + catch (not_target) { + return false; + } + return true; + } + + ast_manager & m() const { return m_rw.m(); } + + void set_cancel(bool f) { + m_rw.set_cancel(f); + } + + void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + mc = 0; pc = 0; core = 0; + + if (!is_target(*g)) + throw tactic_exception("bv1 blaster cannot be applied to goal"); + + tactic_report report("bv1-blaster", *g); + m_num_steps = 0; + + bool proofs_enabled = g->proofs_enabled(); + expr_ref new_curr(m()); + proof_ref new_pr(m()); + unsigned size = g->size(); + for (unsigned idx = 0; idx < size; idx++) { + if (g->inconsistent()) + break; + expr * curr = g->form(idx); + m_rw(curr, new_curr, new_pr); + m_num_steps += m_rw.get_num_steps(); + if (proofs_enabled) { + proof * pr = g->pr(idx); + new_pr = m().mk_modus_ponens(pr, new_pr); + } + g->update(idx, new_curr, new_pr, g->dep(idx)); + } + + if (g->models_enabled()) + mc = mk_bv1_blaster_model_converter(m(), m_rw.cfg().m_const2bits); + g->inc_depth(); + result.push_back(g.get()); + m_rw.cfg().cleanup(); + } + + unsigned get_num_steps() const { return m_num_steps; } + }; + + imp * m_imp; + params_ref m_params; +public: + bv1_blaster_tactic(ast_manager & m, params_ref const & p = params_ref()): + m_params(p) { + m_imp = alloc(imp, m, p); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(bv1_blaster_tactic, m, m_params); + } + + virtual ~bv1_blaster_tactic() { + dealloc(m_imp); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + m_imp->m_rw.cfg().updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + insert_max_memory(r); + insert_max_steps(r); + } + + bool is_target(goal const & g) const { + return m_imp->is_target(g); + } + + /** + \brief "Blast" bit-vectors of size n in s into bit-vectors of size 1. + If s contains other bit-vectors operators different from concat/extract, then this is method is a NO-OP. + It also does not support quantifiers. + Return a model_converter that converts any model for the updated set into a model for the old set. + */ + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + (*m_imp)(g, result, mc, pc, core); + } + + virtual void cleanup() { + ast_manager & m = m_imp->m(); + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + d = m_imp; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + } + + unsigned get_num_steps() const { + return m_imp->get_num_steps(); + } + +protected: + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + +tactic * mk_bv1_blaster_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(bv1_blaster_tactic, m, p)); +} + +class is_qfbv_eq_probe : public probe { +public: + virtual result operator()(goal const & g) { + bv1_blaster_tactic t(g.m()); + return t.is_target(g); + + } +}; + +probe * mk_is_qfbv_eq_probe() { + return alloc(is_qfbv_eq_probe); +} diff --git a/lib/bv1_blaster_tactic.h b/lib/bv1_blaster_tactic.h new file mode 100644 index 000000000..a38d53d1a --- /dev/null +++ b/lib/bv1_blaster_tactic.h @@ -0,0 +1,35 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + bv1_blaster_tactic.h + +Abstract: + + Rewriter for "blasting" bit-vectors of size n into bit-vectors of size 1. + This rewriter only supports concat and extract operators. + This transformation is useful for handling benchmarks that contain + many BV equalities. + + Remark: other operators can be mapped into concat/extract by using + the simplifiers. + +Author: + + Leonardo (leonardo) 2011-10-25 + +Notes: + +--*/ +#ifndef _BV1_BLASTER_TACTIC_H_ +#define _BV1_BLASTER_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_bv1_blaster_tactic(ast_manager & m, params_ref const & p = params_ref()); +probe * mk_is_qfbv_eq_probe(); + +#endif diff --git a/lib/bv2int_rewriter.cpp b/lib/bv2int_rewriter.cpp new file mode 100644 index 000000000..e8ddf18c9 --- /dev/null +++ b/lib/bv2int_rewriter.cpp @@ -0,0 +1,604 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + bv2int_rewriter.cpp + +Abstract: + + Basic rewriting rules for bv2int propagation. + +Author: + + Nikolaj (nbjorner) 2011-05-05 + +Notes: + +--*/ +#include "bv2int_rewriter.h" +#include "rewriter_def.h" +#include "ast_pp.h" + +void bv2int_rewriter_ctx::update_params(params_ref const& p) { + m_max_size = p.get_uint(":max-bv-size", UINT_MAX); +} + +struct lt_rational { + bool operator()(rational const& a, rational const& b) const { return a < b; } +}; + +void bv2int_rewriter_ctx::collect_power2(goal const& s) { + ast_manager& m = m_trail.get_manager(); + arith_util arith(m); + bv_util bv(m); + + for (unsigned j = 0; j < s.size(); ++j) { + expr* f = s.form(j); + if (!m.is_or(f)) continue; + unsigned sz = to_app(f)->get_num_args(); + expr* x, *y, *v = 0; + rational n; + vector bounds; + bool is_int, ok = true; + + for (unsigned i = 0; ok && i < sz; ++i) { + expr* e = to_app(f)->get_arg(i); + if (!m.is_eq(e, x, y)) { + ok = false; + break; + } + if (arith.is_numeral(y, n, is_int) && is_int && + (x == v || v == 0)) { + v = x; + bounds.push_back(n); + } + else if (arith.is_numeral(x, n, is_int) && is_int && + (y == v || v == 0)) { + v = y; + bounds.push_back(n); + } + else { + ok = false; + break; + } + } + if (!ok || !v) continue; + SASSERT(!bounds.empty()); + lt_rational lt; + // lt is a total order on rationals. + std::sort(bounds.begin(), bounds.end(), lt); + rational p(1); + unsigned num_bits = 0; + for (unsigned i = 0; ok && i < bounds.size(); ++i) { + ok = (p == bounds[i]); + p *= rational(2); + ++num_bits; + } + if (!ok) continue; + unsigned log2 = 0; + for (unsigned i = 1; i <= num_bits; i *= 2) ++log2; + if(log2 == 0) continue; + expr* logx = m.mk_fresh_const("log2_v", bv.mk_sort(log2)); + logx = bv.mk_zero_extend(num_bits - log2, logx); + m_trail.push_back(logx); + TRACE("bv2int_rewriter", tout << mk_pp(v, m) << " |-> " << mk_pp(logx, m) << "\n";); + m_power2.insert(v, logx); + } +} + +bool bv2int_rewriter_ctx::is_power2(expr* x, expr*& log_x) { + return m_power2.find(x, log_x); +} + +bv2int_rewriter::bv2int_rewriter(ast_manager & m, bv2int_rewriter_ctx& ctx) + :m_manager(m), m_ctx(ctx), m_bv(m), m_arith(m) { +} + + +br_status bv2int_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + if(f->get_family_id() == m_arith.get_family_id()) { + switch (f->get_decl_kind()) { + case OP_NUM: return BR_FAILED; + case OP_LE: SASSERT(num_args == 2); return mk_le(args[0], args[1], result); + case OP_GE: SASSERT(num_args == 2); return mk_ge(args[0], args[1], result); + case OP_LT: SASSERT(num_args == 2); return mk_lt(args[0], args[1], result); + case OP_GT: SASSERT(num_args == 2); return mk_gt(args[0], args[1], result); + case OP_ADD: return mk_add(num_args, args, result); + case OP_MUL: return mk_mul(num_args, args, result); + case OP_SUB: return mk_sub(num_args, args, result); + case OP_DIV: return BR_FAILED; + case OP_IDIV: SASSERT(num_args == 2); return mk_idiv(args[0], args[1], result); + case OP_MOD: SASSERT(num_args == 2); return mk_mod(args[0], args[1], result); + case OP_REM: SASSERT(num_args == 2); return mk_rem(args[0], args[1], result); + case OP_UMINUS: SASSERT(num_args == 1); return mk_uminus(args[0], result); + case OP_TO_REAL: return BR_FAILED; + case OP_TO_INT: return BR_FAILED; + case OP_IS_INT: return BR_FAILED; + default: + return BR_FAILED; + } + } + if (f->get_family_id() == m().get_basic_family_id()) { + switch (f->get_decl_kind()) { + case OP_EQ: SASSERT(num_args == 2); return mk_eq(args[0], args[1], result); + case OP_ITE: SASSERT(num_args == 3); return mk_ite(args[0], args[1], args[2], result); + default: return BR_FAILED; + } + } + return BR_FAILED; +} + +br_status bv2int_rewriter::mk_le(expr * s, expr * t, expr_ref & result) { + expr_ref s1(m()), t1(m()), s2(m()), t2(m()); + if (is_bv2int(s, s1) && is_bv2int(t, t1)) { + align_sizes(s1, t1, false); + result = m_bv.mk_ule(s1, t1); + return BR_DONE; + } + if (is_bv2int_diff(s, s1, s2) && is_bv2int_diff(t, t1, t2)) { + // s1 - s2 <= t1 - t2 + // <=> + // s1 + t2 <= t1 + s2 + // + s1 = mk_bv_add(s1, t2, false); + t1 = mk_bv_add(t1, s2, false); + align_sizes(s1, t1, false); + result = m_bv.mk_ule(s1, t1); + return BR_DONE; + } + if (is_sbv2int(s, s1) && is_sbv2int(t, t1)) { + align_sizes(s1, t1, true); + result = m_bv.mk_sle(s1, t1); + return BR_DONE; + } + return BR_FAILED; +} + +br_status bv2int_rewriter::mk_lt(expr * arg1, expr * arg2, expr_ref & result) { + result = m().mk_not(m_arith.mk_le(arg2, arg1)); + return BR_REWRITE2; +} + +br_status bv2int_rewriter::mk_ge(expr * arg1, expr * arg2, expr_ref & result) { + return mk_le(arg2, arg1, result); +} + +br_status bv2int_rewriter::mk_gt(expr * arg1, expr * arg2, expr_ref & result) { + result = m().mk_not(m_arith.mk_le(arg1, arg2)); + return BR_REWRITE2; +} + +br_status bv2int_rewriter::mk_ite(expr* c, expr* s, expr* t, expr_ref& result) { + expr_ref s1(m()), t1(m()); + if (is_bv2int(s, s1) && is_bv2int(t, t1)) { + align_sizes(s1, t1, false); + result = m_bv.mk_bv2int(m().mk_ite(c, s1, t1)); + return BR_DONE; + } + + if (is_sbv2int(s, s1) && is_sbv2int(t, t1)) { + align_sizes(s1, t1, true); + result = mk_sbv2int(m().mk_ite(c, s1, t1)); + return BR_DONE; + } + return BR_FAILED; +} + +br_status bv2int_rewriter::mk_eq(expr * s, expr * t, expr_ref & result) { + expr_ref s1(m()), t1(m()), s2(m()), t2(m()); + if (is_bv2int(s, s1) && is_bv2int(t, t1)) { + align_sizes(s1, t1, false); + result = m().mk_eq(s1, t1); + return BR_DONE; + } + if (is_bv2int_diff(s, s1, s2) && is_bv2int_diff(t, t1, t2)) { + s1 = mk_bv_add(s1, t2, false); + t1 = mk_bv_add(s2, t1, false); + align_sizes(s1, t1, false); + result = m().mk_eq(s1, t1); + return BR_DONE; + } + if (is_sbv2int(s, s1) && is_sbv2int(t, t1)) { + align_sizes(s1, t1, true); + result = m().mk_eq(s1, t1); + return BR_DONE; + } + return BR_FAILED; +} + + +br_status bv2int_rewriter::mk_idiv(expr * arg1, expr * arg2, expr_ref & result) { + // TBD + return BR_FAILED; +} + +br_status bv2int_rewriter::mk_mod(expr * s, expr * t, expr_ref & result) { + expr_ref s1(m()), s2(m()), t1(m()); + if (is_bv2int(s, s1) && is_bv2int(t, t1)) { + align_sizes(s1, t1, false); + result = m_bv.mk_bv2int(m_bv.mk_bv_urem(s1, t1)); + return BR_DONE; + } + + // + // (s1 - s2) mod t1 = (s1 + (t1 - (s2 mod t1))) mod t1 + // + if (is_bv2int_diff(s, s1, s2) && is_bv2int(t, t1)) { + expr_ref u1(m()); + align_sizes(s1, t1, false); + u1 = m_bv.mk_bv_urem(s1, t1); + u1 = m_bv.mk_bv_sub(t1, u1); + u1 = mk_bv_add(s1, u1, false); + align_sizes(u1, t1, false); + result = m_bv.mk_bv2int(m_bv.mk_bv_urem(u1, t1)); + return BR_DONE; + } + +#if 0 + // TBD: check semantics + if (is_sbv2int(s, s1) && is_sbv2int(t, t1)) { + align_sizes(s1, t1, true); + result = mk_sbv2int(m_bv.mk_bv_srem(s1, t1)); + return BR_DONE; + } +#endif + return BR_FAILED; +} + + +br_status bv2int_rewriter::mk_rem(expr * arg1, expr * arg2, expr_ref & result) { + // TBD + return BR_FAILED; +} + +br_status bv2int_rewriter::mk_uminus(expr * s, expr_ref & result) { + expr_ref s1(m()), s2(m()); + if (is_bv2int_diff(s, s1, s2)) { + result = m_arith.mk_sub(m_bv.mk_bv2int(s2), m_bv.mk_bv2int(s1)); + return BR_DONE; + } + if (is_sbv2int(s, s1)) { + result = mk_sbv2int(m_bv.mk_bv_neg(s1)); + return BR_DONE; + } + return BR_FAILED; +} + + +br_status bv2int_rewriter::mk_add(unsigned num_args, expr * const* args, expr_ref& result) { + br_status r = BR_DONE; + SASSERT(num_args > 0); + result = args[0]; + for (unsigned i = 1; r == BR_DONE && i < num_args; ++i) { + r = mk_add(result, args[i], result); + } + return r; +} + +void bv2int_rewriter::align_sizes(expr_ref& s, expr_ref& t, bool is_signed) { + unsigned sz1 = m_bv.get_bv_size(s); + unsigned sz2 = m_bv.get_bv_size(t); + if (sz1 > sz2 && is_signed) { + t = mk_extend(sz1-sz2, t, true); + } + if (sz1 > sz2 && !is_signed) { + t = mk_extend(sz1-sz2, t, false); + } + if (sz1 < sz2 && is_signed) { + s = mk_extend(sz2-sz1, s, true); + } + if (sz1 < sz2 && !is_signed) { + s = mk_extend(sz2-sz1, s, false); + } +} + + +bool bv2int_rewriter::is_zero(expr* n) { + rational r; + unsigned sz; + return m_bv.is_numeral(n, r, sz) && r.is_zero(); +} + +expr* bv2int_rewriter::mk_bv_add(expr* s, expr* t, bool is_signed) { + SASSERT(m_bv.is_bv(s)); + SASSERT(m_bv.is_bv(t)); + + if (is_zero(s)) { + return t; + } + if (is_zero(t)) { + return s; + } + expr_ref s1(s, m()), t1(t, m()); + align_sizes(s1, t1, is_signed); + s1 = mk_extend(1, s1, is_signed); + t1 = mk_extend(1, t1, is_signed); + return m_bv.mk_bv_add(s1, t1); +} + + +br_status bv2int_rewriter::mk_add(expr* s, expr* t, expr_ref& result) { + expr_ref s1(m()), t1(m()), s2(m()), t2(m()); + if (is_bv2int(s, s1) && is_bv2int(t, t1)) { + result = m_bv.mk_bv2int(mk_bv_add(s1, t1, false)); + return BR_DONE; + } + if (is_bv2int_diff(s, s1, s2) && is_bv2int_diff(t, t1, t2)) { + // s1 - s2 + t1 - t2 + // = + // s1 + t1 - (s2 + t2) + // + t1 = m_bv.mk_bv2int(mk_bv_add(s1, t1, false)); + t2 = m_bv.mk_bv2int(mk_bv_add(s2, t2, false)); + result = m_arith.mk_sub(t1, t2); + return BR_DONE; + } + if (is_sbv2int(s, s1) && is_sbv2int(t, t1)) { + result = mk_sbv2int(mk_bv_add(s1, t1, true)); + return BR_DONE; + } + return BR_FAILED; +} + +br_status bv2int_rewriter::mk_mul(unsigned num_args, expr * const* args, expr_ref& result) { + br_status r = BR_DONE; + SASSERT(num_args > 0); + result = args[0]; + for (unsigned i = 1; r == BR_DONE && i < num_args; ++i) { + r = mk_mul(result, args[i], result); + } + return r; +} + +expr* bv2int_rewriter::mk_bv_mul(expr* s, expr* t, bool is_signed) { + SASSERT(m_bv.is_bv(s)); + SASSERT(m_bv.is_bv(t)); + if (is_zero(s)) { + return s; + } + if (is_zero(t)) { + return t; + } + rational r; + unsigned sz; + if (m_bv.is_numeral(s, r, sz) && r.is_one()) { + return t; + } + if (m_bv.is_numeral(t, r, sz) && r.is_one()) { + return s; + } + expr_ref s1(s, m()), t1(t, m()); + align_sizes(s1, t1, is_signed); + unsigned n = m_bv.get_bv_size(t1); + unsigned max_bits = m_ctx.get_max_num_bits(); + bool add_side_conds = 2*n > max_bits; + if (n >= max_bits) { + // + } + else if (2*n > max_bits) { + s1 = mk_extend(max_bits-n, s1, is_signed); + t1 = mk_extend(max_bits-n, t1, is_signed); + } + else { + s1 = mk_extend(n, s1, is_signed); + t1 = mk_extend(n, t1, is_signed); + } + if (add_side_conds) { + if (is_signed) { + m_ctx.add_side_condition(m_bv.mk_bvsmul_no_ovfl(s1, t1)); + m_ctx.add_side_condition(m_bv.mk_bvsmul_no_udfl(s1, t1)); + } + else { + m_ctx.add_side_condition(m_bv.mk_bvumul_no_ovfl(s1, t1)); + } + } + return m_bv.mk_bv_mul(s1, t1); +} + + +br_status bv2int_rewriter::mk_mul(expr* s, expr* t, expr_ref& result) { + expr_ref s1(m()), s2(m()), t1(m()), t2(m()); + if ((is_shl1(s, s1) && is_bv2int(t, t1)) || + (is_shl1(t, s1) && is_bv2int(s, t1))) { + unsigned n = m_bv.get_bv_size(s1); + unsigned m = m_bv.get_bv_size(t1); + s1 = mk_extend(m, s1, false); + t1 = mk_extend(n, t1, false); + result = m_bv.mk_bv2int(m_bv.mk_bv_shl(t1, s1)); + return BR_DONE; + } + if (is_bv2int(s, s1) && is_bv2int(t, t1)) { + result = m_bv.mk_bv2int(mk_bv_mul(s1, t1, false)); + return BR_DONE; + } + if ((is_bv2int(s, s1) && is_bv2int_diff(t, t1, t2)) || + (is_bv2int(t, s1) && is_bv2int_diff(s, t1, t2))) { + t1 = m_bv.mk_bv2int(mk_bv_mul(s1, t1, false)); + t2 = m_bv.mk_bv2int(mk_bv_mul(s1, t2, false)); + result = m_arith.mk_sub(t1, t2); + return BR_DONE; + } + if (is_sbv2int(s, s1) && is_sbv2int(t, t1)) { + result = mk_sbv2int(mk_bv_mul(s1, t1, true)); + return BR_DONE; + } + return BR_FAILED; +} + +br_status bv2int_rewriter::mk_sub(unsigned num_args, expr * const* args, expr_ref& result) { + br_status r = BR_DONE; + SASSERT(num_args > 0); + result = args[0]; + for (unsigned i = 1; r == BR_DONE && i < num_args; ++i) { + r = mk_sub(result, args[i], result); + } + return r; +} + +br_status bv2int_rewriter::mk_sub(expr* s, expr* t, expr_ref& result) { + expr_ref s1(m()), t1(m()), s2(m()), t2(m()); + if (is_bv2int_diff(s, s1, s2) && is_bv2int_diff(t, t1, t2)) { + // s1 - s2 - (t1 - t2) + // = + // s1 + t2 - (t1 + s2) + // + s1 = m_bv.mk_bv2int(mk_bv_add(s1, t2, false)); + s2 = m_bv.mk_bv2int(mk_bv_add(s2, t1, false)); + result = m_arith.mk_sub(s1, s2); + return BR_DONE; + } + if (is_sbv2int(s, s1) && is_sbv2int(t, t1)) { + align_sizes(s1, t1, true); + s1 = m_bv.mk_sign_extend(1, s1); + t1 = m_bv.mk_sign_extend(1, t1); + result = mk_sbv2int(m_bv.mk_bv_sub(s1, t1)); + return BR_DONE; + } + return BR_FAILED; +} + +bool bv2int_rewriter::is_bv2int(expr* n, expr_ref& s) { + rational k; + bool is_int; + if (m_bv.is_bv2int(n)) { + s = to_app(n)->get_arg(0); + return true; + } + if (m_arith.is_numeral(n, k, is_int) && is_int && !k.is_neg()) { + unsigned sz = k.get_num_bits(); + s = m_bv.mk_numeral(k, m_bv.mk_sort(sz)); + return true; + } + return false; +} + +bool bv2int_rewriter::is_shl1(expr* n, expr_ref& s) { + expr* s1, *s2; + rational r; + unsigned bv_size; + if(m_bv.is_bv2int(n, s2) && + m_bv.is_bv_shl(s2, s1, s2) && + m_bv.is_numeral(s1, r, bv_size) && + r.is_one()) { + s = s2; + return true; + } + return false; +} + +bool bv2int_rewriter::is_bv2int_diff(expr* n, expr_ref& s, expr_ref& t) { + if (is_bv2int(n, s)) { + t = m_bv.mk_numeral(0, 1); + return true; + } + rational k; + bool is_int; + if (m_arith.is_numeral(n, k, is_int) && is_int) { + SASSERT(k.is_neg()); + k.neg(); + unsigned sz = k.get_num_bits(); + t = m_bv.mk_numeral(k, m_bv.mk_sort(sz)); + s = m_bv.mk_numeral(0, 1); + return true; + } + // + // bv2int(a) - bv2int(b) + // + expr *e1, *e2; + if (m_arith.is_sub(n, e1, e2) && + is_bv2int(e1, s) && + is_bv2int(e2, t)) { + return true; + } + return false; +} + +bool bv2int_rewriter::is_sbv2int(expr* n, expr_ref& s) { + if (is_bv2int(n, s)) { + s = m_bv.mk_zero_extend(1, s); + return true; + } + expr_ref u1(m()), u2(m()); + if (is_bv2int_diff(n, u1, u2)) { + align_sizes(u1, u2, false); + u1 = mk_extend(1, u1, false); + u2 = mk_extend(1, u2, false); + s = m_bv.mk_bv_sub(u1, u2); + return true; + } + // ite(bv1 == b[n-1:n-1], bv2int(b[0:n-2]) - 2^{n-1}, bv2int(b[0:n-2])) + expr* c, *t, *e1, *c1, *c2, *c3, *t1, *t2, *e2, *e3; + rational k; + bool is_int; + unsigned lo, hi, lo1, hi1, sz; + + if (m().is_ite(n, c, t, e1) && + m().is_eq(c, c1, c2) && + m_bv.is_numeral(c1, k, sz) && k.is_one() && sz == 1 && + m_bv.is_extract(c2, lo, hi, c3) && + lo == hi && lo == m_bv.get_bv_size(c3) - 1 && + m_arith.is_sub(t, t1, t2) && + e1 == t1 && + m_bv.is_bv2int(e1, e2) && + m_bv.is_extract(e2, lo1, hi1, e3) && + lo1 == 0 && hi1 == hi-1 && + m_arith.is_numeral(t2, k, is_int) && is_int && + k == m_bv.power_of_two(hi) + ) { + s = e3; + return true; + } + +#if 0 + // bv2int(b[0:n-2]) - ite(bv1 == b[n-1:n-1], 2^{n-1}, 0) + if (m().is_sub(n, e1, e2) && + m_bv.is_bv2int(e1, e3) && + m_bv.is_extract(e3, lo, hi, e4) && + lo == 0 && hi == m_bv.get_bv_size(e4) - 2 && + m().is_ite(e2, t1, t2, t3) && + m().is_eq(t1, c1, c2) && + m_bv.is_numeral(c1, k, sz) && k.is_one() && sz == 1 && + m_bv.is_extract(c2, lo1, hi1, c3) && lo1 == h1 + 1 && hi1 == lo1 && + c3 == e4 && + m_arith.is_numeral(t2, )) { + + } +#endif + return false; +} + +expr* bv2int_rewriter::mk_sbv2int(expr* b) { + // + // ite(bit1 = b[n-1:n-1], bv2int(b[0:n-2]) - 2^{n-1}, bv2int(b[0:n-2])) + // + expr* bv1 = m_bv.mk_numeral(1, 1); + unsigned n = m_bv.get_bv_size(b); + expr* c = m().mk_eq(bv1, m_bv.mk_extract(n-1, n-1, b)); + expr* e = m_bv.mk_bv2int(m_bv.mk_extract(n-2, 0, b)); + expr* t = m_arith.mk_sub(e, m_arith.mk_numeral(power(rational(2), n-1), true)); + return m().mk_ite(c, t, e); +} + +expr* bv2int_rewriter::mk_extend(unsigned sz, expr* b, bool is_signed) { + if (sz == 0) { + return b; + } + rational r; + unsigned bv_sz; + if (is_signed) { + return m_bv.mk_sign_extend(sz, b); + } + else if (m_bv.is_numeral(b, r, bv_sz)) { + return m_bv.mk_numeral(r, bv_sz + sz); + } + else { + return m_bv.mk_zero_extend(sz, b); + } +} + +template class rewriter_tpl; + + + + diff --git a/lib/bv2int_rewriter.h b/lib/bv2int_rewriter.h new file mode 100644 index 000000000..5783ed856 --- /dev/null +++ b/lib/bv2int_rewriter.h @@ -0,0 +1,120 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + bv2int_rewriter.h + +Abstract: + + Basic rewriting rules for bv2int propagation. + +Author: + + Nikolaj (nbjorner) 2011-05-05 + +Notes: + +--*/ +#ifndef _BV2INT_REWRITER_H_ +#define _BV2INT_REWRITER_H_ + +#include"ast.h" +#include"rewriter.h" +#include"bv_decl_plugin.h" +#include"arith_decl_plugin.h" +#include"params.h" +#include"goal.h" + +class bv2int_rewriter_ctx { + unsigned m_max_size; + expr_ref_vector m_side_conditions; + obj_map m_power2; + expr_ref_vector m_trail; + +public: + bv2int_rewriter_ctx(ast_manager& m, params_ref const& p) : + m_side_conditions(m), m_trail(m) { update_params(p); } + + void reset() { m_side_conditions.reset(); m_trail.reset(); m_power2.reset(); } + void add_side_condition(expr* e) { m_side_conditions.push_back(e); } + unsigned num_side_conditions() const { return m_side_conditions.size(); } + expr* const* side_conditions() const { return m_side_conditions.c_ptr(); } + unsigned get_max_num_bits() const { return m_max_size; } + + void collect_power2(goal const & s); + bool is_power2(expr* x, expr*& log_x); + obj_map const& power2() const { return m_power2; } + +private: + void update_params(params_ref const& p); +}; + +class bv2int_rewriter { + ast_manager & m_manager; + bv2int_rewriter_ctx& m_ctx; + bv_util m_bv; + arith_util m_arith; + +public: + bv2int_rewriter(ast_manager & m, bv2int_rewriter_ctx& ctx); + ast_manager & m() const { return m_manager; } + + br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + void mk_app(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + if (mk_app_core(f, num_args, args, result) == BR_FAILED) + result = m().mk_app(f, num_args, args); + } +private: + br_status mk_eq(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_ite(expr* c, expr* s, expr* t, expr_ref& result); + br_status mk_le(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_lt(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_ge(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_gt(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_idiv(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_mod(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_rem(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_add(unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_mul(unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_sub(unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_add(expr* s, expr* t, expr_ref& result); + br_status mk_mul(expr* s, expr* t, expr_ref& result); + br_status mk_sub(expr* s, expr* t, expr_ref& result); + br_status mk_uminus(expr* e, expr_ref & result); + + bool is_bv2int(expr* e, expr_ref& s); + bool is_sbv2int(expr* e, expr_ref& s); + bool is_bv2int_diff(expr* e, expr_ref& s, expr_ref& t); + bool is_zero(expr* e); + bool is_shl1(expr* e, expr_ref& s); + + expr* mk_bv_add(expr* s, expr* t, bool is_signed); + expr* mk_bv_mul(expr* s, expr* t, bool is_signed); + expr* mk_sbv2int(expr* s); + expr* mk_extend(unsigned sz, expr* b, bool is_signed); + + void align_sizes(expr_ref& s, expr_ref& t, bool is_signed); + +}; + +struct bv2int_rewriter_cfg : public default_rewriter_cfg { + bv2int_rewriter m_r; + bool rewrite_patterns() const { return false; } + bool flat_assoc(func_decl * f) const { return false; } + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + result_pr = 0; + return m_r.mk_app_core(f, num, args, result); + } + bv2int_rewriter_cfg(ast_manager & m, bv2int_rewriter_ctx& ctx):m_r(m, ctx) {} +}; + +class bv2int_rewriter_star : public rewriter_tpl { + bv2int_rewriter_cfg m_cfg; +public: + bv2int_rewriter_star(ast_manager & m, bv2int_rewriter_ctx& ctx): + rewriter_tpl(m, false, m_cfg), + m_cfg(m, ctx) {} +}; + +#endif diff --git a/lib/bv2real_rewriter.cpp b/lib/bv2real_rewriter.cpp new file mode 100644 index 000000000..4f952b697 --- /dev/null +++ b/lib/bv2real_rewriter.cpp @@ -0,0 +1,695 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + bv2real_rewriter.cpp + +Abstract: + + Basic rewriting rules for bv2real propagation. + +Author: + + Nikolaj (nbjorner) 2011-08-05 + +Notes: + +--*/ +#include"bv2real_rewriter.h" +#include"rewriter_def.h" +#include"ast_pp.h" +#include"for_each_expr.h" + + +bv2real_util::bv2real_util(ast_manager& m, rational const& default_root, rational const& default_divisor, unsigned max_num_bits) : + m_manager(m), + m_arith(m), + m_bv(m), + m_decls(m), + m_pos_le(m), + m_pos_lt(m), + m_side_conditions(m), + m_default_root(default_root), + m_default_divisor(default_divisor), + m_max_divisor(rational(2)*default_divisor), + m_max_num_bits(max_num_bits) { + sort* real = m_arith.mk_real(); + sort* domain[2] = { real, real }; + m_pos_lt = m.mk_fresh_func_decl("<","",2,domain,m.mk_bool_sort()); + m_pos_le = m.mk_fresh_func_decl("<=","",2,domain,m.mk_bool_sort()); + m_decls.push_back(m_pos_lt); + m_decls.push_back(m_pos_le); +} + +bool bv2real_util::is_bv2real(func_decl* f) const { + return m_decl2sig.contains(f); +} + +bool bv2real_util::is_bv2real(func_decl* f, unsigned num_args, expr* const* args, + expr*& m, expr*& n, rational& d, rational& r) const { + bvr_sig sig; + if (!m_decl2sig.find(f, sig)) { + return false; + } + SASSERT(num_args == 2); + m = args[0]; + n = args[1]; + d = sig.m_d; + r = sig.m_r; + SASSERT(sig.m_d.is_int() && sig.m_d.is_pos()); + SASSERT(sig.m_r.is_int() && sig.m_r.is_pos()); + SASSERT(m_bv.get_bv_size(m) == sig.m_msz); + SASSERT(m_bv.get_bv_size(n) == sig.m_nsz); + return true; +} + +bool bv2real_util::is_bv2real(expr* e, expr*& m, expr*& n, rational& d, rational& r) const { + if (!is_app(e)) return false; + func_decl* f = to_app(e)->get_decl(); + return is_bv2real(f, to_app(e)->get_num_args(), to_app(e)->get_args(), m, n, d, r); +} + +class bv2real_util::contains_bv2real_proc { + bv2real_util const& m_util; +public: + class found {}; + contains_bv2real_proc(bv2real_util const& u): m_util(u) {} + void operator()(app* a) { + if (m_util.is_bv2real(a->get_decl())) { + throw found(); + } + } + void operator()(var*) {} + void operator()(quantifier*) {} +}; + +bool bv2real_util::contains_bv2real(expr* e) const { + contains_bv2real_proc p(*this); + try { + for_each_expr(p, e); + } + catch (contains_bv2real_proc::found) { + return true; + } + return false; +} + +bool bv2real_util::mk_bv2real(expr* _s, expr* _t, rational& d, rational& r, expr_ref& result) { + expr_ref s(_s,m()), t(_t,m()); + if (align_divisor(s, t, d)) { + result = mk_bv2real_c(s, t, d, r); + return true; + } + else { + return false; + } +} + +expr* bv2real_util::mk_bv2real_c(expr* s, expr* t, rational const& d, rational const& r) { + bvr_sig sig; + sig.m_msz = m_bv.get_bv_size(s); + sig.m_nsz = m_bv.get_bv_size(t); + sig.m_d = d; + sig.m_r = r; + func_decl* f; + if (!m_sig2decl.find(sig, f)) { + sort* domain[2] = { m_manager.get_sort(s), m_manager.get_sort(t) }; + sort* real = m_arith.mk_real(); + f = m_manager.mk_fresh_func_decl("bv2real", "", 2, domain, real); + m_decls.push_back(f); + m_sig2decl.insert(sig, f); + m_decl2sig.insert(f, sig); + } + return m_manager.mk_app(f, s, t); +} + +void bv2real_util::mk_bv2real_reduced(expr* s, expr* t, rational const& d, rational const& r, expr_ref & result) { + expr_ref s1(m()), t1(m()), r1(m()); + rational num; + mk_sbv2real(s, s1); + mk_sbv2real(t, t1); + mk_div(s1, d, s1); + mk_div(t1, d, t1); + r1 = a().mk_power(a().mk_numeral(r, false), a().mk_numeral(rational(1,2),false)); + t1 = a().mk_mul(t1, r1); + result = a().mk_add(s1, t1); +} + +void bv2real_util::mk_div(expr* e, rational const& d, expr_ref& result) { + result = a().mk_div(e, a().mk_numeral(rational(d), false)); +} + +void bv2real_util::mk_sbv2real(expr* e, expr_ref& result) { + rational r; + unsigned bv_size = m_bv.get_bv_size(e); + rational bsize = power(rational(2), bv_size); + expr_ref bvr(a().mk_to_real(m_bv.mk_bv2int(e)), m()); + expr_ref c(m_bv.mk_sle(m_bv.mk_numeral(rational(0), bv_size), e), m()); + result = m().mk_ite(c, bvr, a().mk_sub(bvr, a().mk_numeral(bsize, false))); +} + + +expr* bv2real_util::mk_bv_mul(rational const& n, expr* t) { + if (n.is_one()) return t; + expr* s = mk_sbv(n); + return mk_bv_mul(s, t); +} + +void bv2real_util::align_divisors(expr_ref& s1, expr_ref& s2, expr_ref& t1, expr_ref& t2, rational& d1, rational& d2) { + if (d1 == d2) { + return; + } + // s/d1 ~ t/d2 <=> lcm*s/d1 ~ lcm*t/d2 <=> (lcm/d1)*s ~ (lcm/d2)*t + // s/d1 ~ t/d2 <=> s/gcd*d1' ~ t/gcd*d2' <=> d2'*s/lcm ~ d1'*t/lcm + + rational g = gcd(d1,d2); + rational l = lcm(d1,d2); + rational d1g = d1/g; + rational d2g = d2/g; + s1 = mk_bv_mul(d2g, s1); + s2 = mk_bv_mul(d2g, s2); + t1 = mk_bv_mul(d1g, t1); + t2 = mk_bv_mul(d1g, t2); + d1 = l; + d2 = l; +} + +expr* bv2real_util::mk_bv_mul(expr* s, expr* t) { + SASSERT(m_bv.is_bv(s)); + SASSERT(m_bv.is_bv(t)); + if (is_zero(s)) { + return s; + } + if (is_zero(t)) { + return t; + } + expr_ref s1(s, m()), t1(t, m()); + align_sizes(s1, t1); + unsigned n = m_bv.get_bv_size(t1); + unsigned max_bits = get_max_num_bits(); + bool add_side_conds = 2*n > max_bits; + if (n >= max_bits) { + // nothing + } + else if (2*n > max_bits) { + s1 = mk_extend(max_bits-n, s1); + t1 = mk_extend(max_bits-n, t1); + } + else { + s1 = mk_extend(n, s1); + t1 = mk_extend(n, t1); + } + if (add_side_conds) { + add_side_condition(m_bv.mk_bvsmul_no_ovfl(s1, t1)); + add_side_condition(m_bv.mk_bvsmul_no_udfl(s1, t1)); + } + return m_bv.mk_bv_mul(s1, t1); +} + +bool bv2real_util::is_zero(expr* n) { + rational r; + unsigned sz; + return m_bv.is_numeral(n, r, sz) && r.is_zero(); +} + +expr* bv2real_util::mk_bv_add(expr* s, expr* t) { + SASSERT(m_bv.is_bv(s)); + SASSERT(m_bv.is_bv(t)); + + if (is_zero(s)) { + return t; + } + if (is_zero(t)) { + return s; + } + expr_ref s1(s, m()), t1(t, m()); + align_sizes(s1, t1); + s1 = mk_extend(1, s1); + t1 = mk_extend(1, t1); + return m_bv.mk_bv_add(s1, t1); +} + +void bv2real_util::align_sizes(expr_ref& s, expr_ref& t) { + unsigned sz1 = m_bv.get_bv_size(s); + unsigned sz2 = m_bv.get_bv_size(t); + if (sz1 > sz2) { + t = mk_extend(sz1-sz2, t); + } + else if (sz1 < sz2) { + s = mk_extend(sz2-sz1, s); + } +} + +expr* bv2real_util::mk_sbv(rational const& n) { + SASSERT(n.is_int()); + if (n.is_neg()) { + rational m = abs(n); + unsigned nb = m.get_num_bits(); + return m_bv.mk_bv_neg(m_bv.mk_numeral(m, nb+1)); + } + else { + unsigned nb = n.get_num_bits(); + return m_bv.mk_numeral(n, nb+1); + } +} + +expr* bv2real_util::mk_bv_sub(expr* s, expr* t) { + expr_ref s1(s, m()), t1(t, m()); + align_sizes(s1, t1); + s1 = mk_extend(1, s1); + t1 = mk_extend(1, t1); + return m_bv.mk_bv_sub(s1, t1); +} + +expr* bv2real_util::mk_extend(unsigned sz, expr* b) { + if (sz == 0) { + return b; + } + rational r; + unsigned bv_sz; + if (m_bv.is_numeral(b, r, bv_sz) && + power(rational(2),bv_sz-1) > r) { + return m_bv.mk_numeral(r, bv_sz + sz); + } + return m_bv.mk_sign_extend(sz, b); +} + + +bool bv2real_util::is_bv2real(expr* n, expr_ref& s, expr_ref& t, rational& d, rational& r) { + expr* _s, *_t; + if (is_bv2real(n, _s, _t, d, r)) { + s = _s; + t = _t; + return true; + } + rational k; + bool is_int; + if (m_arith.is_numeral(n, k, is_int) && !is_int) { + d = denominator(k); + r = default_root(); + s = mk_sbv(numerator(k)); + t = mk_sbv(rational(0)); + return true; + } + return false; +} + +bool bv2real_util::align_divisor(expr_ref& s, expr_ref& t, rational& d) { + if (d > max_divisor()) { + // + // if divisor is over threshold, then divide s and t + // add side condition that s, t are divisible. + // + rational overflow = d / max_divisor(); + if (!overflow.is_int()) return false; + if (!mk_is_divisible_by(s, overflow)) return false; + if (!mk_is_divisible_by(t, overflow)) return false; + d = max_divisor(); + } + return true; +} + +bool bv2real_util::mk_is_divisible_by(expr_ref& s, rational const& _overflow) { + rational overflow(_overflow); + SASSERT(overflow.is_int()); + SASSERT(overflow.is_pos()); + SASSERT(!overflow.is_one()); + TRACE("bv2real_rewriter", + tout << mk_pp(s, m()) << " " << overflow << "\n";); + unsigned power2 = 0; + while ((overflow % rational(2)) == rational(0)) { + power2++; + overflow = div(overflow, rational(2)); + } + + if (power2 > 0) { + unsigned sz = m_bv.get_bv_size(s); + if (sz <= power2) { + add_side_condition(m().mk_eq(s, m_bv.mk_numeral(rational(0), sz))); + s = m_bv.mk_numeral(rational(0), 1); + } + else { + expr* s1 = m_bv.mk_extract(power2-1, 0, s); + add_side_condition(m().mk_eq(s1, m_bv.mk_numeral(rational(0), power2))); + s = m_bv.mk_extract(sz-1, power2, s); + } + } + + TRACE("bv2real_rewriter", + tout << mk_pp(s, m()) << " " << overflow << "\n";); + + return overflow.is_one(); +} + + + +// --------------------------------------------------------------------- +// bv2real_rewriter + +bv2real_rewriter::bv2real_rewriter(ast_manager& m, bv2real_util& util): + m_manager(m), + m_util(util), + m_bv(m), + m_arith(m) +{} + + +br_status bv2real_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + TRACE("bv2real_rewriter", + tout << mk_pp(f, m()) << " "; + for (unsigned i = 0; i < num_args; ++i) { + tout << mk_pp(args[i], m()) << " "; + } + tout << "\n";); + if(f->get_family_id() == m_arith.get_family_id()) { + switch (f->get_decl_kind()) { + case OP_NUM: return BR_FAILED; + case OP_LE: SASSERT(num_args == 2); return mk_le(args[0], args[1], result); + case OP_GE: SASSERT(num_args == 2); return mk_ge(args[0], args[1], result); + case OP_LT: SASSERT(num_args == 2); return mk_lt(args[0], args[1], result); + case OP_GT: SASSERT(num_args == 2); return mk_gt(args[0], args[1], result); + case OP_ADD: return mk_add(num_args, args, result); + case OP_MUL: return mk_mul(num_args, args, result); + case OP_SUB: return mk_sub(num_args, args, result); + case OP_DIV: SASSERT(num_args == 2); return mk_div(args[0], args[1], result); + case OP_IDIV: return BR_FAILED; + case OP_MOD: return BR_FAILED; + case OP_REM: return BR_FAILED; + case OP_UMINUS: SASSERT(num_args == 1); return mk_uminus(args[0], result); + case OP_TO_REAL: return BR_FAILED; // TBD + case OP_TO_INT: return BR_FAILED; // TBD + case OP_IS_INT: return BR_FAILED; // TBD + default: return BR_FAILED; + } + } + if (f->get_family_id() == m().get_basic_family_id()) { + switch (f->get_decl_kind()) { + case OP_EQ: SASSERT(num_args == 2); return mk_eq(args[0], args[1], result); + case OP_ITE: SASSERT(num_args == 3); return mk_ite(args[0], args[1], args[2], result); + default: return BR_FAILED; + } + } + if (u().is_pos_ltf(f)) { + SASSERT(num_args == 2); + return mk_lt_pos(args[0], args[1], result); + } + if (u().is_pos_lef(f)) { + SASSERT(num_args == 2); + return mk_le_pos(args[0], args[1], result); + } + + return BR_FAILED; +} + +bool bv2real_rewriter::mk_le(expr* s, expr* t, bool is_pos, bool is_neg, expr_ref& result) { + expr_ref s1(m()), s2(m()), t1(m()), t2(m()); + rational d1, d2, r1, r2; + SASSERT(is_pos || is_neg); + if (u().is_bv2real(s, s1, s2, d1, r1) && u().is_bv2real(t, t1, t2, d2, r2) && + r1 == r2 && r1 == rational(2)) { + // + // (s1 + s2*sqrt(2))/d1 <= (t1 + t2*sqrt(2))/d2 + // <=> + // + // let s1 = s1*d2-t1*d1, t2 = s2*d2-t2*d1 + // + // + // s1 + s2*sqrt(2) <= 0 + // <= + // s1 + s2*approx(sign(s2),sqrt(2)) <= 0 + // or (s1 = 0 & s2 = 0) + // + // If s2 is negative use an under-approximation for sqrt(r). + // If s2 is positive use an over-approximation for sqrt(r). + // e.g., r = 2, then 5/4 and 3/2 are under/over approximations. + // Then s1 + s2*approx(sign(s2), r) <= 0 => s1 + s2*sqrt(r) <= 0 + + + u().align_divisors(s1, s2, t1, t2, d1, d2); + s1 = u().mk_bv_sub(s1, t1); + s2 = u().mk_bv_sub(s2, t2); + unsigned s2_size = m_bv.get_bv_size(s2); + expr_ref le_proxy(m().mk_fresh_const("le_proxy",m().mk_bool_sort()), m()); + u().add_aux_decl(to_app(le_proxy)->get_decl()); + expr_ref gt_proxy(m().mk_not(le_proxy), m()); + expr_ref s2_is_nonpos(m_bv.mk_sle(s2, m_bv.mk_numeral(rational(0), s2_size)), m()); + + expr_ref under(u().mk_bv_add(u().mk_bv_mul(rational(4), s1), u().mk_bv_mul(rational(5), s2)), m()); + expr_ref z1(m_bv.mk_numeral(rational(0), m_bv.get_bv_size(under)), m()); + expr_ref le_under(m_bv.mk_sle(under, z1), m()); + expr_ref over(u().mk_bv_add(u().mk_bv_mul(rational(2), s1), u().mk_bv_mul(rational(3), s2)), m()); + expr_ref z2(m_bv.mk_numeral(rational(0), m_bv.get_bv_size(over)), m()); + expr_ref le_over(m_bv.mk_sle(over, z2), m()); + + // predicate may occur in positive polarity. + if (is_pos) { + // s1 + s2*sqrt(2) <= 0 <== s2 <= 0 & s1 + s2*(5/4) <= 0; 4*s1 + 5*s2 <= 0 + expr* e1 = m().mk_implies(m().mk_and(le_proxy, s2_is_nonpos), le_under); + // s1 + s2*sqrt(2) <= 0 <== s2 > 0 & s1 + s2*(3/2); 0 <=> 2*s1 + 3*s2 <= 0 + expr* e2 = m().mk_implies(m().mk_and(le_proxy, m().mk_not(s2_is_nonpos)), le_over); + u().add_side_condition(e1); + u().add_side_condition(e2); + } + // predicate may occur in negative polarity. + if (is_neg) { + // s1 + s2*sqrt(2) > 0 <== s2 > 0 & s1 + s2*(5/4) > 0; 4*s1 + 5*s2 > 0 + expr* e3 = m().mk_implies(m().mk_and(gt_proxy, m().mk_not(s2_is_nonpos)), m().mk_not(le_under)); + // s1 + s2*sqrt(2) > 0 <== s2 <= 0 & s1 + s2*(3/2) > 0 <=> 2*s1 + 3*s2 > 0 + expr* e4 = m().mk_implies(m().mk_and(gt_proxy, s2_is_nonpos), m().mk_not(le_over)); + u().add_side_condition(e3); + u().add_side_condition(e4); + } + + TRACE("bv2real_rewriter", tout << "mk_le\n";); + + if (is_pos) { + result = le_proxy; + } + else { + result = gt_proxy; + } + return true; + } + return false; +} + +br_status bv2real_rewriter::mk_le_pos(expr * s, expr * t, expr_ref & result) { + if (mk_le(s, t, true, false, result)) { + return BR_DONE; + } + return BR_FAILED; +} + +br_status bv2real_rewriter::mk_lt_pos(expr * s, expr * t, expr_ref & result) { + if (mk_le(t, s, false, true, result)) { + return BR_DONE; + } + return BR_FAILED; +} + + +br_status bv2real_rewriter::mk_le(expr * s, expr * t, expr_ref & result) { + expr_ref s1(m()), s2(m()), t1(m()), t2(m()); + rational d1, d2, r1, r2; + + if (mk_le(s, t, true, true, result)) { + return BR_DONE; + } + + if (u().is_bv2real(s, s1, s2, d1, r1) && u().is_bv2real(t, t1, t2, d2, r2) && r1 == r2) { + // + // somewhat expensive approach without having + // polarity information for sound approximation. + // + + // Convert to: + // t1 + t2*sqrt(r) >= 0 + // then to: + // + // (t1 >= 0 && t2 <= 0 => t1^2 >= t2^2*r) + // (t1 <= 0 && t2 >= 0 => t1^2 <= t2^2*r) + // (t1 >= 0 || t2 >= 0) + // + // A cheaper approach is to approximate > under the assumption + // that > occurs in positive polarity. + // then if t2 is negative use an over-approximation for sqrt(r) + // if t2 is positive use an under-approximation for sqrt(r). + // e.g., r = 2, then 5/4 and 3/2 are under/over approximations. + // Then t1 + t2*approx(sign(t2), r) > 0 => t1 + t2*sqrt(r) > 0 + // + u().align_divisors(s1, s2, t1, t2, d1, d2); + t1 = u().mk_bv_sub(t1, s1); + t2 = u().mk_bv_sub(t2, s2); + expr_ref z1(m()), z2(m()); + z1 = m_bv.mk_numeral(rational(0), m_bv.get_bv_size(t1)); + z2 = m_bv.mk_numeral(rational(0), m_bv.get_bv_size(t2)); + + expr* gz1 = m_bv.mk_sle(z1, t1); + expr* lz1 = m_bv.mk_sle(t1, z1); + expr* gz2 = m_bv.mk_sle(z2, t2); + expr* lz2 = m_bv.mk_sle(t2, z2); + expr_ref t12(u().mk_bv_mul(t1, t1), m()); + expr_ref t22(u().mk_bv_mul(r1, u().mk_bv_mul(t2, t2)), m()); + u().align_sizes(t12, t22); + expr* ge = m_bv.mk_sle(t22, t12); + expr* le = m_bv.mk_sle(t12, t22); + expr* e1 = m().mk_or(gz1, gz2); + expr* e2 = m().mk_or(m().mk_not(gz1), m().mk_not(lz2), ge); + expr* e3 = m().mk_or(m().mk_not(gz2), m().mk_not(lz1), le); + result = m().mk_and(e1, e2, e3); + TRACE("bv2real_rewriter", tout << "\n";); + return BR_DONE; + } + return BR_FAILED; +} + +br_status bv2real_rewriter::mk_lt(expr * arg1, expr * arg2, expr_ref & result) { + result = m().mk_not(m_arith.mk_le(arg2, arg1)); + return BR_REWRITE2; +} + +br_status bv2real_rewriter::mk_ge(expr * arg1, expr * arg2, expr_ref & result) { + return mk_le(arg2, arg1, result); +} + +br_status bv2real_rewriter::mk_gt(expr * arg1, expr * arg2, expr_ref & result) { + result = m().mk_not(m_arith.mk_le(arg1, arg2)); + return BR_REWRITE2; +} + +br_status bv2real_rewriter::mk_ite(expr* c, expr* s, expr* t, expr_ref& result) { + expr_ref s1(m()), s2(m()), t1(m()), t2(m()); + rational d1, d2, r1, r2; + if (u().is_bv2real(s, s1, s2, d1, r1) && u().is_bv2real(t, t1, t2, d2, r2) && r1 == r2) { + u().align_divisors(s1, s2, t1, t2, d1, d2); + u().align_sizes(s1, t1); + u().align_sizes(s2, t2); + if (u().mk_bv2real(m().mk_ite(c, s1, t1), m().mk_ite(c, s2, t2), d1, r1, result)) { + return BR_DONE; + } + } + return BR_FAILED; +} + +br_status bv2real_rewriter::mk_eq(expr * s, expr * t, expr_ref & result) { + expr_ref s1(m()), s2(m()), t1(m()), t2(m()); + rational d1, d2, r1, r2; + if (u().is_bv2real(s, s1, s2, d1, r1) && u().is_bv2real(t, t1, t2, d2, r2) && r1 == r2) { + u().align_divisors(s1, s2, t1, t2, d1, d2); + u().align_sizes(s1, t1); + u().align_sizes(s2, t2); + result = m().mk_and(m().mk_eq(s1, t1), m().mk_eq(s2, t2)); + return BR_DONE; + } + return BR_FAILED; +} + +br_status bv2real_rewriter::mk_uminus(expr * s, expr_ref & result) { + expr_ref s1(m()), s2(m()); + rational d1, r1; + if (u().is_bv2real(s, s1, s2, d1, r1)) { + s1 = u().mk_extend(1, s1); + s2 = u().mk_extend(1, s2); + if (u().mk_bv2real(m_bv.mk_bv_neg(s1), m_bv.mk_bv_neg(s2), d1, r1, result)) { + return BR_DONE; + } + } + return BR_FAILED; +} + +br_status bv2real_rewriter::mk_add(unsigned num_args, expr * const* args, expr_ref& result) { + br_status r = BR_DONE; + SASSERT(num_args > 0); + result = args[0]; + for (unsigned i = 1; r == BR_DONE && i < num_args; ++i) { + r = mk_add(result, args[i], result); + } + return r; +} + +br_status bv2real_rewriter::mk_add(expr* s, expr* t, expr_ref& result) { + expr_ref s1(m()), s2(m()), t1(m()), t2(m()); + rational d1, d2, r1, r2; + if (u().is_bv2real(s, s1, s2, d1, r1) && u().is_bv2real(t, t1, t2, d2, r2) && r1 == r2) { + u().align_divisors(s1, s2, t1, t2, d1, d2); + if (u().mk_bv2real(u().mk_bv_add(s1, t1), u().mk_bv_add(t2, s2), d1, r1, result)) { + return BR_DONE; + } + } + return BR_FAILED; +} + +br_status bv2real_rewriter::mk_mul(unsigned num_args, expr * const* args, expr_ref& result) { + br_status r = BR_DONE; + SASSERT(num_args > 0); + result = args[0]; + for (unsigned i = 1; r == BR_DONE && i < num_args; ++i) { + r = mk_mul(result, args[i], result); + } + return r; +} + +br_status bv2real_rewriter::mk_div(expr* s, expr* t, expr_ref& result) { + return BR_FAILED; +} + +br_status bv2real_rewriter::mk_mul(expr* s, expr* t, expr_ref& result) { + // TBD: optimize + expr_ref s1(m()), t1(m()), s2(m()), t2(m()); + rational d1, d2, r1, r2; + + if (u().is_bv2real(s, s1, s2, d1, r1) && u().is_bv2real(t, t1, t2, d2, r2) && r1 == r2) { + // s1*t1 + r1*(s2*t2) + (s1*t2 + s2*t2)*r1 + expr_ref u1(m()), u2(m()); + u1 = u().mk_bv_add(u().mk_bv_mul(s1, t1), u().mk_bv_mul(r1, u().mk_bv_mul(t2, s2))); + u2 = u().mk_bv_add(u().mk_bv_mul(s1, t2), u().mk_bv_mul(s2, t1)); + rational tmp = d1*d2; + if (u().mk_bv2real(u1, u2, tmp, r1, result)) { + return BR_DONE; + } + } + return BR_FAILED; +} + +br_status bv2real_rewriter::mk_sub(unsigned num_args, expr * const* args, expr_ref& result) { + br_status r = BR_DONE; + SASSERT(num_args > 0); + result = args[0]; + for (unsigned i = 1; r == BR_DONE && i < num_args; ++i) { + r = mk_sub(result, args[i], result); + } + return r; +} + + +br_status bv2real_rewriter::mk_sub(expr* s, expr* t, expr_ref& result) { + expr_ref s1(m()), s2(m()), t1(m()), t2(m()); + rational d1, d2, r1, r2; + if (u().is_bv2real(s, s1, s2, d1, r1) && u().is_bv2real(t, t1, t2, d2, r2) && r1 == r2) { + u().align_divisors(s1, s2, t1, t2, d1, d2); + if (u().mk_bv2real(u().mk_bv_sub(s1, t1), u().mk_bv_sub(s2, t2), d1, r1, result)) { + return BR_DONE; + } + } + return BR_FAILED; +} + + + + +template class rewriter_tpl; + + +br_status bv2real_elim_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + expr* m, *n; + rational d, r; + if (m_util.is_bv2real(f, num_args, args, m, n, d, r)) { + m_util.mk_bv2real_reduced(m, n, d, r, result); + return BR_REWRITE_FULL; + } + return BR_FAILED; +} + +template class rewriter_tpl; + diff --git a/lib/bv2real_rewriter.h b/lib/bv2real_rewriter.h new file mode 100644 index 000000000..8438d71b9 --- /dev/null +++ b/lib/bv2real_rewriter.h @@ -0,0 +1,233 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + bv2real_rewriter.h + +Abstract: + + Basic rewriting rules for bv2real propagation. + +Author: + + Nikolaj (nbjorner) 2011-08-05 + +Notes: + +--*/ +#ifndef _BV2REAL_REWRITER_H_ +#define _BV2REAL_REWRITER_H_ + +#include"ast.h" +#include"rewriter.h" +#include"bv_decl_plugin.h" +#include"arith_decl_plugin.h" + +// +// bv2real[d,r](n,m) has interpretation: +// sbv2int(n)/d + sbv2int(m)/d*sqrt(r) +// where +// sbv2int is signed bit-vector 2 integer. +// +class bv2real_util { + struct bvr_sig { + unsigned m_msz, m_nsz; + rational m_d, m_r; + }; + + struct bvr_eq { + bool operator()(bvr_sig const& x, bvr_sig const& y) const { + return + x.m_msz == y.m_msz && + x.m_nsz == y.m_nsz && + x.m_d == y.m_d && + x.m_r == y.m_r; + } + }; + + struct bvr_hash { + unsigned operator()(bvr_sig const& x) const { + unsigned a[3] = { x.m_msz, x.m_nsz, x.m_d.hash() }; + return string_hash((char const*)a, 12, x.m_r.hash()); + } + }; + + ast_manager& m_manager; + arith_util m_arith; + bv_util m_bv; + func_decl_ref_vector m_decls; + func_decl_ref m_pos_le; + func_decl_ref m_pos_lt; + expr_ref_vector m_side_conditions; + map m_sig2decl; + obj_map m_decl2sig; + rational m_default_root; + rational m_default_divisor; + rational m_max_divisor; + unsigned m_max_num_bits; + + class contains_bv2real_proc; + +public: + bv2real_util(ast_manager& m, rational const& default_root, rational const& default_divisor, unsigned max_num_bits); + + void reset() { m_side_conditions.reset(); } + + bool is_bv2real(func_decl* f) const; + bool is_bv2real(func_decl* f, unsigned num_args, expr* const* args, + expr*& m, expr*& n, rational& d, rational& r) const; + bool is_bv2real(expr* e, expr*& n, expr*& m, rational& d, rational& r) const; + bool is_bv2real(expr* e, expr*& n, expr*& m, rational& d); + + bool contains_bv2real(expr* e) const; + + bool mk_bv2real(expr* s, expr* t, rational& d, rational& r, expr_ref& result); + expr* mk_bv2real_c(expr* s, expr* t, rational const& d, rational const& r); + expr* mk_bv2real(expr* n, expr* m) { return mk_bv2real_c(n, m, default_divisor(), default_root()); } + + void mk_bv2real_reduced(expr* s, expr* t, expr_ref & result) { mk_bv2real_reduced(s, t, default_divisor(), default_root(), result); } + void mk_bv2real_reduced(expr* s, expr* t, rational const& d, rational const& r, expr_ref & result); + + + // + // Positive polarity comparison operators. + // Translation of positive polarity comparison requires fewer clauses. + // + bool is_pos_ltf(func_decl* f) const { return f == m_pos_lt; } + bool is_pos_lef(func_decl* f) const { return f == m_pos_le; } + bool is_pos_lt(expr const* e) const { return is_app(e) && is_pos_ltf(to_app(e)->get_decl()); } + bool is_pos_le(expr const* e) const { return is_app(e) && is_pos_lef(to_app(e)->get_decl()); } + MATCH_BINARY(is_pos_lt); + MATCH_BINARY(is_pos_le); + expr* mk_pos_lt(expr* s, expr* t) { return m().mk_app(m_pos_lt, s, t); } + expr* mk_pos_le(expr* s, expr* t) { return m().mk_app(m_pos_le, s, t); } + + rational const& default_root() const { return m_default_root; } + rational const& default_divisor() const { return m_default_divisor; } + rational const& max_divisor() const { return m_max_divisor; } + + unsigned get_max_num_bits() const { return m_max_num_bits; } + + void add_side_condition(expr* e) { m_side_conditions.push_back(e); } + unsigned num_side_conditions() const { return m_side_conditions.size(); } + expr* const* side_conditions() const { return m_side_conditions.c_ptr(); } + + bool is_zero(expr* e); + + expr* mk_bv_add(expr* s, expr* t); + expr* mk_bv_sub(expr* s, expr* t); + expr* mk_bv_mul(expr* s, expr* t); + expr* mk_bv_mul(rational const& n, expr* t); + expr* mk_extend(unsigned sz, expr* b); + expr* mk_sbv(rational const& n); + + void align_sizes(expr_ref& s, expr_ref& t); + void align_divisors(expr_ref& s1, expr_ref& s2, expr_ref& t1, expr_ref& t2, rational& d1, rational& d2); + + bool is_bv2real(expr* n, expr_ref& s, expr_ref& t, rational& d, rational& r); + bool align_divisor(expr_ref& s, expr_ref& t, rational& d); + + bool mk_is_divisible_by(expr_ref& s, rational const& _overflow); + + void add_aux_decl(func_decl* f) { m_decls.push_back(f); } + unsigned num_aux_decls() const { return m_decls.size(); } + func_decl* get_aux_decl(unsigned i) const { return m_decls[i]; } + +private: + ast_manager & m() const { return m_manager; } + arith_util & a() { return m_arith; } + void mk_div(expr* e, rational const& d, expr_ref& result); + void mk_sbv2real(expr* e, expr_ref& result); +}; + + +class bv2real_rewriter { + ast_manager & m_manager; + bv2real_util& m_util; + bv_util m_bv; + arith_util m_arith; + +public: + bv2real_rewriter(ast_manager & m, bv2real_util& util); + + br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + +private: + ast_manager & m() const { return m_manager; } + arith_util & a() { return m_arith; } + bv2real_util& u() { return m_util; } + br_status mk_eq(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_ite(expr* c, expr* s, expr* t, expr_ref& result); + bool mk_le(expr* s, expr* t, bool is_pos, bool is_neg, expr_ref& result); + br_status mk_le(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_lt(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_le_pos(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_lt_pos(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_ge(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_gt(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_add(unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_mul(unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_sub(unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_div(expr* s, expr* t, expr_ref& result); + br_status mk_add(expr* s, expr* t, expr_ref& result); + br_status mk_mul(expr* s, expr* t, expr_ref& result); + br_status mk_sub(expr* s, expr* t, expr_ref& result); + br_status mk_uminus(expr* e, expr_ref & result); +}; + +struct bv2real_rewriter_cfg : public default_rewriter_cfg { + bv2real_rewriter m_r; + bool rewrite_patterns() const { return false; } + bool flat_assoc(func_decl * f) const { return false; } + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + result_pr = 0; + return m_r.mk_app_core(f, num, args, result); + } + bv2real_rewriter_cfg(ast_manager & m, bv2real_util& u):m_r(m, u) {} +}; + +class bv2real_rewriter_star : public rewriter_tpl { + bv2real_rewriter_cfg m_cfg; +public: + bv2real_rewriter_star(ast_manager & m, bv2real_util& u): + rewriter_tpl(m, false, m_cfg), + m_cfg(m, u) {} +}; + + + + +/** + \brief replace le(bv2real(a),bv2real(b)) by under-approximation. +*/ + +class bv2real_elim_rewriter { + bv2real_util& m_util; +public: + bv2real_elim_rewriter(bv2real_util& util) : m_util(util) {} + + br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); +}; + + +struct bv2real_elim_rewriter_cfg : public default_rewriter_cfg { + bv2real_elim_rewriter m_r; + bool rewrite_patterns() const { return false; } + bool flat_assoc(func_decl * f) const { return false; } + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + result_pr = 0; + return m_r.mk_app_core(f, num, args, result); + } + bv2real_elim_rewriter_cfg(bv2real_util& u):m_r(u) {} +}; + +class bv2real_elim_rewriter_star : public rewriter_tpl { + bv2real_elim_rewriter_cfg m_cfg; +public: + bv2real_elim_rewriter_star(ast_manager & m, bv2real_util& u): + rewriter_tpl(m, false, m_cfg), + m_cfg(u) {} +}; + +#endif diff --git a/lib/bv_decl_plugin.cpp b/lib/bv_decl_plugin.cpp new file mode 100644 index 000000000..fd15ca681 --- /dev/null +++ b/lib/bv_decl_plugin.cpp @@ -0,0 +1,840 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + bv_decl_plugin.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-09. + +Revision History: + +--*/ +#include +#include"bv_decl_plugin.h" +#include"arith_decl_plugin.h" +#include"warning.h" +#include"ast_pp.h" +#include"ast_smt2_pp.h" + +bv_decl_plugin::bv_decl_plugin(): + m_bv_sym("bv"), + m_concat_sym("concat"), + m_sign_extend_sym("sign_extend"), + m_zero_extend_sym("zero_extend"), + m_extract_sym("extract"), + m_rotate_left_sym("rotate_left"), + m_rotate_right_sym("rotate_right"), + m_repeat_sym("repeat"), + m_bit2bool_sym("bit2bool"), + m_mkbv_sym("mkbv"), + m_bit0(0), + m_bit1(0), + m_carry(0), + m_xor3(0), + m_int_sort(0) { +} + +void bv_decl_plugin::mk_table_upto(unsigned n) { + if (m_powers.empty()) { + m_powers.push_back(rational(1)); + } + unsigned sz = m_powers.size(); + rational curr = m_powers[sz - 1]; + rational two(2); + for (unsigned i = sz; i <= n; i++) { + curr *= two; + m_powers.push_back(curr); + } +} + +rational bv_decl_plugin::power_of_two(unsigned n) const { + if (n >= m_powers.size()) { + const_cast(this)->mk_table_upto(n + 1); + } + return m_powers[n]; +} + +void bv_decl_plugin::set_manager(ast_manager * m, family_id id) { + decl_plugin::set_manager(m, id); + + for (unsigned i = 1; i <= 64; i++) { + mk_bv_sort(i); + } + m_bit0 = m->mk_const_decl(symbol("bit0"), get_bv_sort(1), func_decl_info(m_family_id, OP_BIT0)); + m_bit1 = m->mk_const_decl(symbol("bit1"), get_bv_sort(1), func_decl_info(m_family_id, OP_BIT1)); + m->inc_ref(m_bit0); + m->inc_ref(m_bit1); + + sort * b = m->mk_bool_sort(); + sort * d[3] = {b, b, b}; + m_carry = m_manager->mk_func_decl(symbol("carry"), 3, d, b, func_decl_info(m_family_id, OP_CARRY)); + m_manager->inc_ref(m_carry); + m_xor3 = m_manager->mk_func_decl(symbol("xor3"), 3, d, b, func_decl_info(m_family_id, OP_XOR3)); + m_manager->inc_ref(m_xor3); + + m_int_sort = m_manager->mk_sort(m_manager->get_family_id("arith"), INT_SORT); + SASSERT(m_int_sort != 0); // arith_decl_plugin must be installed before bv_decl_plugin. + m_manager->inc_ref(m_int_sort); +} + +void bv_decl_plugin::finalize() { +#define DEC_REF(FIELD) dec_range_ref(FIELD.begin(), FIELD.end(), *m_manager) + if (m_bit0) { m_manager->dec_ref(m_bit0); } + if (m_bit1) { m_manager->dec_ref(m_bit1); } + if (m_carry) { m_manager->dec_ref(m_carry); } + if (m_xor3) { m_manager->dec_ref(m_xor3); } + if (m_int_sort) { m_manager->dec_ref(m_int_sort); } + + DEC_REF(m_bv_sorts); + + DEC_REF(m_bv_neg); + DEC_REF(m_bv_add); + DEC_REF(m_bv_sub); + DEC_REF(m_bv_mul); + + DEC_REF(m_bv_sdiv); + DEC_REF(m_bv_udiv); + DEC_REF(m_bv_srem); + DEC_REF(m_bv_urem); + DEC_REF(m_bv_smod); + + DEC_REF(m_bv_sdiv0); + DEC_REF(m_bv_udiv0); + DEC_REF(m_bv_srem0); + DEC_REF(m_bv_urem0); + DEC_REF(m_bv_smod0); + + DEC_REF(m_bv_sdiv_i); + DEC_REF(m_bv_udiv_i); + DEC_REF(m_bv_srem_i); + DEC_REF(m_bv_urem_i); + DEC_REF(m_bv_smod_i); + + DEC_REF(m_bv_uleq); + DEC_REF(m_bv_sleq); + DEC_REF(m_bv_ugeq); + DEC_REF(m_bv_sgeq); + DEC_REF(m_bv_ult); + DEC_REF(m_bv_slt); + DEC_REF(m_bv_ugt); + DEC_REF(m_bv_sgt); + + DEC_REF(m_bv_and); + DEC_REF(m_bv_or); + DEC_REF(m_bv_not); + DEC_REF(m_bv_xor); + DEC_REF(m_bv_nand); + DEC_REF(m_bv_nor); + DEC_REF(m_bv_xnor); + + DEC_REF(m_bv_redor); + DEC_REF(m_bv_redand); + DEC_REF(m_bv_comp); + + DEC_REF(m_bv_mul_ovfl); + DEC_REF(m_bv_smul_ovfl); + DEC_REF(m_bv_smul_udfl); + + DEC_REF(m_bv_shl); + DEC_REF(m_bv_lshr); + DEC_REF(m_bv_ashr); + + DEC_REF(m_ext_rotate_left); + DEC_REF(m_ext_rotate_right); + + DEC_REF(m_int2bv); + DEC_REF(m_bv2int); + vector >::iterator it = m_bit2bool.begin(); + vector >::iterator end = m_bit2bool.end(); + for (; it != end; ++it) { + ptr_vector & ds = *it; + DEC_REF(ds); + } + DEC_REF(m_mkbv); +} + +void bv_decl_plugin::mk_bv_sort(unsigned bv_size) { + force_ptr_array_size(m_bv_sorts, bv_size + 1); + if (m_bv_sorts[bv_size] == 0) { + parameter p(bv_size); + sort_size sz; + if (sort_size::is_very_big_base2(bv_size)) { + sz = sort_size::mk_very_big(); + } + else { + sz = sort_size(power_of_two(bv_size)); + } + m_bv_sorts[bv_size] = m_manager->mk_sort(symbol("bv"), sort_info(m_family_id, BV_SORT, sz, 1, &p)); + m_manager->inc_ref(m_bv_sorts[bv_size]); + } +} + +inline sort * bv_decl_plugin::get_bv_sort(unsigned bv_size) { + if (bv_size < (1 << 12)) { + mk_bv_sort(bv_size); + return m_bv_sorts[bv_size]; + } + parameter p(bv_size); + sort_size sz(sort_size::mk_very_big()); + return m_manager->mk_sort(symbol("bv"), sort_info(m_family_id, BV_SORT, sz, 1, &p)); +} + +sort * bv_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) { + if (!(num_parameters == 1 && parameters[0].is_int())) { + m_manager->raise_exception("expecting one integer parameter to bit-vector sort"); + } + unsigned bv_size = parameters[0].get_int(); + mk_bv_sort(bv_size); + return m_bv_sorts[bv_size]; +} + +func_decl * bv_decl_plugin::mk_binary(ptr_vector & decls, decl_kind k, + char const * name, unsigned bv_size, bool ac, bool idempotent) { + force_ptr_array_size(decls, bv_size + 1); + + if (decls[bv_size] == 0) { + sort * s = get_bv_sort(bv_size); + func_decl_info info(m_family_id, k); + info.set_associative(ac); + info.set_flat_associative(ac); + info.set_commutative(ac); + info.set_idempotent(idempotent); + decls[bv_size] = m_manager->mk_func_decl(symbol(name), s, s, s, info); + m_manager->inc_ref(decls[bv_size]); + } + + return decls[bv_size]; +} + +func_decl * bv_decl_plugin::mk_unary(ptr_vector & decls, decl_kind k, char const * name, unsigned bv_size) { + force_ptr_array_size(decls, bv_size + 1); + + if (decls[bv_size] == 0) { + sort * s = get_bv_sort(bv_size); + decls[bv_size] = m_manager->mk_func_decl(symbol(name), s, s, func_decl_info(m_family_id, k)); + m_manager->inc_ref(decls[bv_size]); + } + + return decls[bv_size]; +} + +func_decl * bv_decl_plugin::mk_int2bv(unsigned bv_size, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain) { + force_ptr_array_size(m_int2bv, bv_size + 1); + + if (arity != 1) { + m_manager->raise_exception("expecting one argument to int2bv"); + return 0; + } + + if (m_int2bv[bv_size] == 0) { + sort * s = get_bv_sort(bv_size); + m_int2bv[bv_size] = m_manager->mk_func_decl(symbol("int2bv"), domain[0], s, + func_decl_info(m_family_id, OP_INT2BV, num_parameters, parameters)); + m_manager->inc_ref(m_int2bv[bv_size]); + } + + return m_int2bv[bv_size]; +} + +func_decl * bv_decl_plugin::mk_bv2int(unsigned bv_size, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain) { + force_ptr_array_size(m_bv2int, bv_size + 1); + + if (arity != 1) { + m_manager->raise_exception("expecting one argument to bv2int"); + return 0; + } + + if (m_bv2int[bv_size] == 0) { + m_bv2int[bv_size] = m_manager->mk_func_decl(symbol("bv2int"), domain[0], m_int_sort, + func_decl_info(m_family_id, OP_BV2INT)); + m_manager->inc_ref(m_bv2int[bv_size]); + } + + return m_bv2int[bv_size]; +} + +func_decl * bv_decl_plugin::mk_pred(ptr_vector & decls, decl_kind k, char const * name, unsigned bv_size) { + force_ptr_array_size(decls, bv_size + 1); + + if (decls[bv_size] == 0) { + sort * s = get_bv_sort(bv_size); + decls[bv_size] = m_manager->mk_func_decl(symbol(name), s, s, m_manager->mk_bool_sort(), func_decl_info(m_family_id, k)); + m_manager->inc_ref(decls[bv_size]); + } + + return decls[bv_size]; +} + +func_decl * bv_decl_plugin::mk_reduction(ptr_vector & decls, decl_kind k, char const * name, unsigned bv_size) { + force_ptr_array_size(decls, bv_size + 1); + + if (decls[bv_size] == 0) { + sort * d = get_bv_sort(bv_size); + sort * r = get_bv_sort(1); + decls[bv_size] = m_manager->mk_func_decl(symbol(name), d, r, func_decl_info(m_family_id, k)); + m_manager->inc_ref(decls[bv_size]); + } + + return decls[bv_size]; +} + +func_decl * bv_decl_plugin::mk_comp(unsigned bv_size) { + force_ptr_array_size(m_bv_comp, bv_size + 1); + + if (m_bv_comp[bv_size] == 0) { + sort * d = get_bv_sort(bv_size); + sort * r = get_bv_sort(1); + func_decl_info info(m_family_id, OP_BCOMP); + info.set_commutative(); + m_bv_comp[bv_size] = m_manager->mk_func_decl(symbol("bvcomp"), d, d, r, info); + m_manager->inc_ref(m_bv_comp[bv_size]); + } + + return m_bv_comp[bv_size]; +} + + +func_decl * bv_decl_plugin::mk_func_decl(decl_kind k, unsigned bv_size) { + switch (k) { + case OP_BNEG: return mk_unary(m_bv_neg, k, "bvneg", bv_size); + case OP_BADD: return mk_binary(m_bv_add, k, "bvadd", bv_size, true); + case OP_BSUB: return mk_binary(m_bv_sub, k, "bvsub", bv_size, false); + case OP_BMUL: return mk_binary(m_bv_mul, k, "bvmul", bv_size, true); + case OP_BSDIV: return mk_binary(m_bv_sdiv, k, "bvsdiv", bv_size, false); + case OP_BUDIV: return mk_binary(m_bv_udiv, k, "bvudiv", bv_size, false); + case OP_BSREM: return mk_binary(m_bv_srem, k, "bvsrem", bv_size, false); + case OP_BUREM: return mk_binary(m_bv_urem, k, "bvurem", bv_size, false); + case OP_BSMOD: return mk_binary(m_bv_smod, k, "bvsmod", bv_size, false); + case OP_BSDIV0: return mk_unary(m_bv_sdiv0, k, "bvsdiv0", bv_size); + case OP_BUDIV0: return mk_unary(m_bv_udiv0, k, "bvudiv0", bv_size); + case OP_BSREM0: return mk_unary(m_bv_srem0, k, "bvsrem0", bv_size); + case OP_BUREM0: return mk_unary(m_bv_urem0, k, "bvurem0", bv_size); + case OP_BSMOD0: return mk_unary(m_bv_smod0, k, "bvsmod0", bv_size); + case OP_BSDIV_I: return mk_binary(m_bv_sdiv_i, k, "bvsdiv_i", bv_size, false); + case OP_BUDIV_I: return mk_binary(m_bv_udiv_i, k, "bvudiv_i", bv_size, false); + case OP_BSREM_I: return mk_binary(m_bv_srem_i, k, "bvsrem_i", bv_size, false); + case OP_BUREM_I: return mk_binary(m_bv_urem_i, k, "bvurem_i", bv_size, false); + case OP_BSMOD_I: return mk_binary(m_bv_smod_i, k, "bvsmod_i", bv_size, false); + case OP_ULEQ: return mk_pred(m_bv_uleq, k, "bvule", bv_size); + case OP_SLEQ: return mk_pred(m_bv_sleq, k, "bvsle", bv_size); + case OP_UGEQ: return mk_pred(m_bv_ugeq, k, "bvuge", bv_size); + case OP_SGEQ: return mk_pred(m_bv_sgeq, k, "bvsge", bv_size); + case OP_ULT: return mk_pred(m_bv_ult, k, "bvult", bv_size); + case OP_SLT: return mk_pred(m_bv_slt, k, "bvslt", bv_size); + case OP_UGT: return mk_pred(m_bv_ugt, k, "bvugt", bv_size); + case OP_SGT: return mk_pred(m_bv_sgt, k, "bvsgt", bv_size); + + case OP_BAND: return mk_binary(m_bv_and, k, "bvand", bv_size, true, true); + case OP_BOR: return mk_binary(m_bv_or, k, "bvor", bv_size, true, true); + case OP_BNOT: return mk_unary(m_bv_not, k, "bvnot", bv_size); + case OP_BXOR: return mk_binary(m_bv_xor, k, "bvxor", bv_size, true); + case OP_BNAND: return mk_binary(m_bv_nand, k, "bvnand", bv_size, false); + case OP_BNOR: return mk_binary(m_bv_nor, k, "bvnor", bv_size, false); + case OP_BXNOR: return mk_binary(m_bv_xnor, k, "bvxnor", bv_size, false); + + case OP_BREDOR: return mk_reduction(m_bv_redor, k, "bvredor", bv_size); + case OP_BREDAND: return mk_reduction(m_bv_redand, k, "bvredand", bv_size); + case OP_BCOMP: return mk_comp(bv_size); + case OP_BUMUL_NO_OVFL: return mk_pred(m_bv_mul_ovfl, k, "bvumul_noovfl", bv_size); + case OP_BSMUL_NO_OVFL: return mk_pred(m_bv_smul_ovfl, k, "bvsmul_noovfl", bv_size); + case OP_BSMUL_NO_UDFL: return mk_pred(m_bv_smul_udfl, k, "bvsmul_noudfl", bv_size); + + case OP_BSHL: return mk_binary(m_bv_shl, k, "bvshl", bv_size, false); + case OP_BLSHR: return mk_binary(m_bv_lshr, k, "bvlshr", bv_size, false); + case OP_BASHR: return mk_binary(m_bv_ashr, k, "bvashr", bv_size, false); + + case OP_EXT_ROTATE_LEFT: return mk_binary(m_ext_rotate_left, k, "ext_rotate_left", bv_size, false); + case OP_EXT_ROTATE_RIGHT: return mk_binary(m_ext_rotate_right, k, "ext_rotate_right", bv_size, false); + default: return 0; + } +} + +inline bool bv_decl_plugin::get_bv_size(sort * s, int & result) { + if (s->get_family_id() == m_family_id && s->get_decl_kind() == BV_SORT) { + result = s->get_parameter(0).get_int(); + return true; + } + return false; +} + +inline bool bv_decl_plugin::get_bv_size(expr * t, int & result) { + return get_bv_size(m_manager->get_sort(t), result); +} + +bool bv_decl_plugin::get_concat_size(unsigned arity, sort * const * domain, int & result) { + result = 0; + for (unsigned i = 0; i < arity; i++) { + int sz; + if (!get_bv_size(domain[i], sz)) { + return false; + } + result += sz; + } + return true; +} + +bool bv_decl_plugin::get_extend_size(unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, int & result) { + int arg_sz; + if (arity != 1 || num_parameters != 1 || !parameters[0].is_int() || !get_bv_size(domain[0], arg_sz)) { + return false; + } + result = arg_sz + parameters[0].get_int(); + return true; +} + +bool bv_decl_plugin::get_extract_size(unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, int & result) { + int arg_sz; + if (arity != 1 || + !get_bv_size(domain[0], arg_sz) || + num_parameters != 2 || + !parameters[0].is_int() || + !parameters[1].is_int() || + parameters[1].get_int() > parameters[0].get_int() || + parameters[0].get_int() >= arg_sz) { + return false; + } + result = parameters[0].get_int() - parameters[1].get_int() + 1; + return true; +} + +bool bv_decl_plugin::get_int2bv_size(unsigned num_parameters, parameter const * parameters, int & result) { + if (num_parameters != 1) { + m_manager->raise_exception("int2bv expects one parameter"); + return false; + } + parameter p(parameters[0]); + if (p.is_int()) { + result = p.get_int(); + return true; + } + if (!p.is_ast() || !is_expr(p.get_ast())) { + m_manager->raise_exception("int2bv expects one integer parameter"); + return false; + } + return get_bv_size(to_expr(p.get_ast()), result); +} + + +func_decl * bv_decl_plugin::mk_num_decl(unsigned num_parameters, parameter const * parameters, unsigned arity) { + if (!(num_parameters == 2 && arity == 0 && parameters[0].is_rational() && parameters[1].is_int())) { + m_manager->raise_exception("invalid bit-vector numeral declaration"); + return 0; + } + unsigned bv_size = parameters[1].get_int(); + // TODO: sign an error if the parameters[0] is out of range, that is, it is a value not in [0, 2^{bv_size}) + // This cannot be enforced now, since some Z3 modules try to generate these invalid numerals. + // After SMT-COMP, I should find all offending modules. + // For now, I will just simplify the numeral here. + parameter p0(mod(parameters[0].get_rational(), power_of_two(bv_size))); + parameter ps[2] = { p0, parameters[1] }; + sort * bv = get_bv_sort(bv_size); + return m_manager->mk_const_decl(m_bv_sym, bv, func_decl_info(m_family_id, OP_BV_NUM, num_parameters, ps)); +} + +func_decl * bv_decl_plugin::mk_bit2bool(unsigned bv_size, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain) { + if (!(num_parameters == 1 && parameters[0].is_int() && arity == 1 && parameters[0].get_int() < static_cast(bv_size))) { + m_manager->raise_exception("invalid bit2bool declaration"); + return 0; + } + unsigned idx = parameters[0].get_int(); + m_bit2bool.reserve(bv_size+1); + ptr_vector & v = m_bit2bool[bv_size]; + v.reserve(bv_size, 0); + if (v[idx] == 0) { + v[idx] = m_manager->mk_func_decl(m_bit2bool_sym, domain[0], m_manager->mk_bool_sort(), + func_decl_info(m_family_id, OP_BIT2BOOL, num_parameters, parameters)); + m_manager->inc_ref(v[idx]); + } + return v[idx]; +} + +func_decl * bv_decl_plugin::mk_mkbv(unsigned arity, sort * const * domain) { + for (unsigned i = 0; i < arity; i++) { + if (!m_manager->is_bool(domain[i])) { + m_manager->raise_exception("invalid mkbv operator"); + return 0; + } + } + unsigned bv_size = arity; + m_mkbv.reserve(bv_size+1); + if (m_mkbv[bv_size] == 0) { + m_mkbv[bv_size] = m_manager->mk_func_decl(m_mkbv_sym, arity, domain, get_bv_sort(bv_size), func_decl_info(m_family_id, OP_MKBV)); + m_manager->inc_ref(m_mkbv[bv_size]); + } + return m_mkbv[bv_size]; +} + +func_decl * bv_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + int bv_size; + if (k == OP_INT2BV && get_int2bv_size(num_parameters, parameters, bv_size)) { + // bv_size is filled in. + } + else if (k == OP_BV_NUM) { + return mk_num_decl(num_parameters, parameters, arity); + } + else if (k == OP_BIT0) { + return m_bit0; + } + else if (k == OP_BIT1) { + return m_bit1; + } + else if (k == OP_CARRY) { + return m_carry; + } + else if (k == OP_XOR3) { + return m_xor3; + } + else if (k == OP_MKBV) { + return mk_mkbv(arity, domain); + } + else if (arity == 0) { + m_manager->raise_exception("no arguments supplied to bit-vector operator"); + return 0; + } + else if (!get_bv_size(domain[0], bv_size)) { + m_manager->raise_exception("could not extract bit-vector size"); + return 0; + } + func_decl * r = mk_func_decl(k, bv_size); + if (r != 0) { + if (arity != r->get_arity()) { + m_manager->raise_exception("declared arity mismatches supplied arity"); + return 0; + } + for (unsigned i = 0; i < arity; ++i) { + if (domain[i] != r->get_domain(i)) { + m_manager->raise_exception("declared sorts do not match supplied sorts"); + return 0; + } + } + return r; + } + int r_size; + switch (k) { + case OP_BIT2BOOL: + return mk_bit2bool(bv_size, num_parameters, parameters, arity, domain); + case OP_INT2BV: + return mk_int2bv(bv_size, num_parameters, parameters, arity, domain); + case OP_BV2INT: + return mk_bv2int(bv_size, num_parameters, parameters, arity, domain); + case OP_CONCAT: + if (!get_concat_size(arity, domain, r_size)) + m_manager->raise_exception("invalid concat application"); + return m_manager->mk_func_decl(m_concat_sym, arity, domain, get_bv_sort(r_size), + func_decl_info(m_family_id, k)); + case OP_SIGN_EXT: + if (!get_extend_size(num_parameters, parameters, arity, domain, r_size)) + m_manager->raise_exception("invalid sign_extend application"); + return m_manager->mk_func_decl(m_sign_extend_sym, arity, domain, get_bv_sort(r_size), + func_decl_info(m_family_id, k, num_parameters, parameters)); + case OP_ZERO_EXT: + if (!get_extend_size(num_parameters, parameters, arity, domain, r_size)) + m_manager->raise_exception("invalid zero_extend application"); + return m_manager->mk_func_decl(m_zero_extend_sym, arity, domain, get_bv_sort(r_size), + func_decl_info(m_family_id, k, num_parameters, parameters)); + case OP_EXTRACT: + if (!get_extract_size(num_parameters, parameters, arity, domain, r_size)) + m_manager->raise_exception("invalid extract application"); + return m_manager->mk_func_decl(m_extract_sym, arity, domain, get_bv_sort(r_size), + func_decl_info(m_family_id, k, num_parameters, parameters)); + case OP_ROTATE_LEFT: + if (arity != 1) + m_manager->raise_exception("rotate left expects one argument"); + return m_manager->mk_func_decl(m_rotate_left_sym, arity, domain, domain[0], + func_decl_info(m_family_id, k, num_parameters, parameters)); + case OP_ROTATE_RIGHT: + if (arity != 1) + m_manager->raise_exception("rotate right expects one argument"); + return m_manager->mk_func_decl(m_rotate_right_sym, arity, domain, domain[0], + func_decl_info(m_family_id, k, num_parameters, parameters)); + case OP_REPEAT: + if (arity != 1) + m_manager->raise_exception("repeat expects one argument"); + if (num_parameters != 1 || !parameters[0].is_int() || parameters[0].get_int() == 0) + m_manager->raise_exception("repeat expects one nonzero integer parameter"); + if (!get_bv_size(domain[0], bv_size)) + m_manager->raise_exception("repeat expects an argument with bit-vector sort"); + return m_manager->mk_func_decl(m_repeat_sym, arity, domain, get_bv_sort(bv_size * parameters[0].get_int()), + func_decl_info(m_family_id, k, num_parameters, parameters)); + default: + return 0; + } +} + +func_decl * bv_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned num_args, expr * const * args, sort * range) { + int bv_size; + if (k == OP_INT2BV && get_int2bv_size(num_parameters, parameters, bv_size)) { + // bv_size is filled in. + } + else if (k == OP_BV_NUM) { + return mk_num_decl(num_parameters, parameters, num_args); + } + else if (k == OP_BIT0) { + return m_bit0; + } + else if (k == OP_BIT1) { + return m_bit1; + } + else if (k == OP_CARRY) { + return m_carry; + } + else if (k == OP_XOR3) { + return m_xor3; + } + else if (k == OP_MKBV) { + return decl_plugin::mk_func_decl(k, num_parameters, parameters, num_args, args, range); + } + else if (num_args == 0 || !get_bv_size(args[0], bv_size)) { + m_manager->raise_exception("operator is applied to arguments of the wrong sort"); + return 0; + } + func_decl * r = mk_func_decl(k, bv_size); + if (r != 0) { + return r; + } + return decl_plugin::mk_func_decl(k, num_parameters, parameters, num_args, args, range); +} + +bool bv_decl_plugin::is_value(app* e) const { + return is_app_of(e, m_family_id, OP_BV_NUM); +} + +void bv_decl_plugin::get_offset_term(app * a, expr * & t, rational & offset) const { + family_id fid = get_family_id(); + if (a->get_num_args() == 2 && is_app_of(a, fid, OP_BADD) && is_app_of(a->get_arg(0), fid, OP_BV_NUM)) { + unsigned sz; + func_decl * decl = to_app(a->get_arg(0))->get_decl(); + offset = decl->get_parameter(0).get_rational(); + sz = decl->get_parameter(1).get_int(); + t = a->get_arg(1); + offset = mod(offset, power_of_two(sz)); + } + else { + t = a; + offset = rational(0); + } +} + +bool bv_decl_plugin::are_distinct(app * a, app * b) const { +#if 1 + // Check for a + k1 != a + k2 when k1 != k2 + rational a_offset; + expr * a_term; + rational b_offset; + expr * b_term; + get_offset_term(a, a_term, a_offset); + get_offset_term(b, b_term, b_offset); + TRACE("bv_are_distinct", + tout << mk_ismt2_pp(a, *m_manager) << "\n" << mk_ismt2_pp(b, *m_manager) << "\n"; + tout << "---->\n"; + tout << "a: " << a_offset << " + " << mk_ismt2_pp(a_term, *m_manager) << "\n"; + tout << "b: " << b_offset << " + " << mk_ismt2_pp(b_term, *m_manager) << "\n";); + if (a_term == b_term && a_offset != b_offset) + return true; +#endif + return decl_plugin::are_distinct(a, b); +} + +void bv_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { + if (logic == symbol::null) + sort_names.push_back(builtin_name("bv", BV_SORT)); + sort_names.push_back(builtin_name("BitVec", BV_SORT)); +} + +void bv_decl_plugin::get_op_names(svector & op_names, symbol const & logic) { + op_names.push_back(builtin_name("bit1",OP_BIT1)); + op_names.push_back(builtin_name("bit0",OP_BIT0)); + op_names.push_back(builtin_name("bvneg",OP_BNEG)); + op_names.push_back(builtin_name("bvadd",OP_BADD)); + op_names.push_back(builtin_name("bvsub",OP_BSUB)); + op_names.push_back(builtin_name("bvmul",OP_BMUL)); + op_names.push_back(builtin_name("bvsdiv",OP_BSDIV)); + op_names.push_back(builtin_name("bvudiv",OP_BUDIV)); + op_names.push_back(builtin_name("bvsrem",OP_BSREM)); + op_names.push_back(builtin_name("bvurem",OP_BUREM)); + op_names.push_back(builtin_name("bvsmod",OP_BSMOD)); + + op_names.push_back(builtin_name("bvule",OP_ULEQ)); + op_names.push_back(builtin_name("bvsle",OP_SLEQ)); + op_names.push_back(builtin_name("bvuge",OP_UGEQ)); + op_names.push_back(builtin_name("bvsge",OP_SGEQ)); + op_names.push_back(builtin_name("bvult",OP_ULT)); + op_names.push_back(builtin_name("bvslt",OP_SLT)); + op_names.push_back(builtin_name("bvugt",OP_UGT)); + op_names.push_back(builtin_name("bvsgt",OP_SGT)); + op_names.push_back(builtin_name("bvand",OP_BAND)); + op_names.push_back(builtin_name("bvor",OP_BOR)); + op_names.push_back(builtin_name("bvnot",OP_BNOT)); + op_names.push_back(builtin_name("bvxor",OP_BXOR)); + op_names.push_back(builtin_name("bvnand",OP_BNAND)); + op_names.push_back(builtin_name("bvnor",OP_BNOR)); + op_names.push_back(builtin_name("bvxnor",OP_BXNOR)); + op_names.push_back(builtin_name("concat",OP_CONCAT)); + op_names.push_back(builtin_name("sign_extend",OP_SIGN_EXT)); + op_names.push_back(builtin_name("zero_extend",OP_ZERO_EXT)); + op_names.push_back(builtin_name("extract",OP_EXTRACT)); + op_names.push_back(builtin_name("repeat",OP_REPEAT)); + op_names.push_back(builtin_name("bvredor",OP_BREDOR)); + op_names.push_back(builtin_name("bvredand",OP_BREDAND)); + op_names.push_back(builtin_name("bvcomp",OP_BCOMP)); + op_names.push_back(builtin_name("bvshl",OP_BSHL)); + op_names.push_back(builtin_name("bvlshr",OP_BLSHR)); + op_names.push_back(builtin_name("bvashr",OP_BASHR)); + op_names.push_back(builtin_name("rotate_left",OP_ROTATE_LEFT)); + op_names.push_back(builtin_name("rotate_right",OP_ROTATE_RIGHT)); + + if (logic == symbol::null) { + op_names.push_back(builtin_name("bvumul_noovfl",OP_BUMUL_NO_OVFL)); + op_names.push_back(builtin_name("bvsmul_noovfl",OP_BSMUL_NO_OVFL)); + op_names.push_back(builtin_name("bvsmul_noudfl",OP_BSMUL_NO_UDFL)); + + op_names.push_back(builtin_name("bvsdiv0", OP_BSDIV0)); + op_names.push_back(builtin_name("bvudiv0", OP_BUDIV0)); + op_names.push_back(builtin_name("bvsrem0", OP_BSREM0)); + op_names.push_back(builtin_name("bvurem0", OP_BUREM0)); + op_names.push_back(builtin_name("bvsmod0", OP_BSMOD0)); + + op_names.push_back(builtin_name("bvsdiv_i", OP_BSDIV_I)); + op_names.push_back(builtin_name("bvudiv_i", OP_BUDIV_I)); + op_names.push_back(builtin_name("bvsrem_i", OP_BSREM_I)); + op_names.push_back(builtin_name("bvurem_i", OP_BUREM_I)); + op_names.push_back(builtin_name("bvumod_i", OP_BSMOD_I)); + + op_names.push_back(builtin_name("ext_rotate_left",OP_EXT_ROTATE_LEFT)); + op_names.push_back(builtin_name("ext_rotate_right",OP_EXT_ROTATE_RIGHT)); + op_names.push_back(builtin_name("int2bv",OP_INT2BV)); + op_names.push_back(builtin_name("bv2int",OP_BV2INT)); + op_names.push_back(builtin_name("mkbv",OP_MKBV)); + } +} + +expr * bv_decl_plugin::get_some_value(sort * s) { + SASSERT(s->is_sort_of(m_family_id, BV_SORT)); + unsigned bv_size = s->get_parameter(0).get_int(); + parameter p[2] = { parameter(rational(0)), parameter(static_cast(bv_size)) }; + return m_manager->mk_app(m_family_id, OP_BV_NUM, 2, p, 0, 0); +} + +bv_util::bv_util(ast_manager & m): + m_manager(m) { + SASSERT(m.has_plugin(symbol("bv"))); + m_plugin = static_cast(m.get_plugin(m.get_family_id("bv"))); +} + +rational bv_util::norm(rational const & val, unsigned bv_size, bool is_signed) const { + rational r = mod(val, power_of_two(bv_size)); + SASSERT(!r.is_neg()); + if (is_signed) { + if (r >= power_of_two(bv_size - 1)) { + r -= power_of_two(bv_size); + } + if (r < -power_of_two(bv_size - 1)) { + r += power_of_two(bv_size); + } + } + return r; +} + +bool bv_util::has_sign_bit(rational const & n, unsigned bv_size) const { + SASSERT(bv_size > 0); + rational m = norm(n, bv_size, false); + rational p = power_of_two(bv_size - 1); + return m >= p; +} + +bool bv_util::is_bv_sort(sort const * s) const { + return (s->get_family_id() == get_fid() && s->get_decl_kind() == BV_SORT && s->get_num_parameters() == 1); +} + +app * bv_util::mk_numeral(rational const & val, sort* s) { + if (!is_bv_sort(s)) { + return 0; + } + unsigned bv_size = get_bv_size(s); + return mk_numeral(val, bv_size); +} + +app * bv_util::mk_numeral(rational const & val, unsigned bv_size) { + parameter p1(val); + parameter p[2] = { p1, parameter(static_cast(bv_size)) }; + return m_manager.mk_app(get_fid(), OP_BV_NUM, 2, p, 0, 0); +} + +bool bv_util::is_numeral(expr const * n, rational & val, unsigned & bv_size) const { + if (!is_app_of(n, get_fid(), OP_BV_NUM)) { + return false; + } + func_decl * decl = to_app(n)->get_decl(); + val = decl->get_parameter(0).get_rational(); + bv_size = decl->get_parameter(1).get_int(); + return true; +} + + +bool bv_util::is_allone(expr const * e) const { + rational r; + unsigned bv_size; + if (!is_numeral(e, r, bv_size)) { + return false; + } + bool result = (r == power_of_two(bv_size) - rational(1)); + TRACE("is_allone", tout << r << " " << result << "\n";); + return result; +} + +bool bv_util::is_zero(expr const * n) const { + if (!is_app_of(n, get_fid(), OP_BV_NUM)) { + return false; + } + func_decl * decl = to_app(n)->get_decl(); + return decl->get_parameter(0).get_rational().is_zero(); +} + +sort * bv_util::mk_sort(unsigned bv_size) { + parameter p[1] = { parameter(bv_size) }; + return m_manager.mk_sort(get_fid(), BV_SORT, 1, p); +} + + +bool bv_util::mult_inverse(rational const & n, unsigned bv_size, rational & result) { + if (n.is_one()) { + result = n; + return true; + } + + if (!mod(n, rational(2)).is_one()) { + return false; + } + + rational g; + rational x; + rational y; + g = gcd(n, power_of_two(bv_size), x, y); + if (x.is_neg()) { + x = mod(x, power_of_two(bv_size)); + } + SASSERT(x.is_pos()); + SASSERT(mod(x * n, power_of_two(bv_size)).is_one()); + result = x; + return true; +} + +app * bv_util::mk_bv2int(expr* e) { + sort* s = m_manager.mk_sort(m_manager.get_family_id("arith"), INT_SORT); + parameter p(s); + return m_manager.mk_app(get_fid(), OP_BV2INT, 1, &p, 1, &e); +} diff --git a/lib/bv_decl_plugin.h b/lib/bv_decl_plugin.h new file mode 100644 index 000000000..a98f39323 --- /dev/null +++ b/lib/bv_decl_plugin.h @@ -0,0 +1,405 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + bv_decl_plugin.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-09. + +Revision History: + +--*/ +#ifndef _BV_DECL_PLUGIN_H_ +#define _BV_DECL_PLUGIN_H_ + +#include"ast.h" + +enum bv_sort_kind { + BV_SORT +}; + +enum bv_op_kind { + OP_BV_NUM, + OP_BIT1, + OP_BIT0, + OP_BNEG, + OP_BADD, + OP_BSUB, + OP_BMUL, + + OP_BSDIV, + OP_BUDIV, + OP_BSREM, + OP_BUREM, + OP_BSMOD, + + // special functions to record the division by 0 cases + // these are internal functions + OP_BSDIV0, + OP_BUDIV0, + OP_BSREM0, + OP_BUREM0, + OP_BSMOD0, + + // special functions where division by 0 has a fixed interpretation. + OP_BSDIV_I, + OP_BUDIV_I, + OP_BSREM_I, + OP_BUREM_I, + OP_BSMOD_I, + + OP_ULEQ, + OP_SLEQ, + OP_UGEQ, + OP_SGEQ, + OP_ULT, + OP_SLT, + OP_UGT, + OP_SGT, + + OP_BAND, + OP_BOR, + OP_BNOT, + OP_BXOR, + OP_BNAND, + OP_BNOR, + OP_BXNOR, + + OP_CONCAT, + OP_SIGN_EXT, + OP_ZERO_EXT, + OP_EXTRACT, + OP_REPEAT, + + OP_BREDOR, + OP_BREDAND, + OP_BCOMP, + + OP_BSHL, + OP_BLSHR, + OP_BASHR, + OP_ROTATE_LEFT, + OP_ROTATE_RIGHT, + OP_EXT_ROTATE_LEFT, + OP_EXT_ROTATE_RIGHT, + + OP_BUMUL_NO_OVFL, // no unsigned multiplication overflow predicate + OP_BSMUL_NO_OVFL, // no signed multiplication overflow predicate + OP_BSMUL_NO_UDFL, // no signed multiplication underflow predicate + + OP_BIT2BOOL, // predicate + OP_MKBV, // bools to bv + OP_INT2BV, + OP_BV2INT, + + OP_CARRY, + OP_XOR3, + + LAST_BV_OP +}; + +// Assume k is a "div" operator. It returns the div0 uninterpreted function that +// models the value of "div" it is underspecified (i.e., when the denominator is zero). +inline bv_op_kind get_div0_op(bv_op_kind k) { + switch (k) { + case OP_BSDIV: return OP_BSDIV0; + case OP_BUDIV: return OP_BUDIV0; + case OP_BSREM: return OP_BSREM0; + case OP_BUREM: return OP_BUREM0; + case OP_BSMOD: return OP_BSMOD0; + default: UNREACHABLE(); return LAST_BV_OP; + } +} + +// Assume decl is the declaration of a "div" operator. It returns the div0 declaration that +// models the value of "div" it is underspecified (i.e., when the denominator is zero). +inline func_decl * get_div0_decl(ast_manager & m, func_decl * decl) { + return m.mk_func_decl(decl->get_family_id(), get_div0_op(static_cast(decl->get_decl_kind())), + 0, 0, 1, decl->get_domain()); +} + +class bv_decl_plugin : public decl_plugin { +protected: + vector m_powers; + void mk_table_upto(unsigned n); + + symbol m_bv_sym; + symbol m_concat_sym; + symbol m_sign_extend_sym; + symbol m_zero_extend_sym; + symbol m_extract_sym; + symbol m_rotate_left_sym; + symbol m_rotate_right_sym; + symbol m_repeat_sym; + symbol m_bit2bool_sym; + symbol m_mkbv_sym; + + func_decl * m_bit0; + func_decl * m_bit1; + func_decl * m_carry; + func_decl * m_xor3; + + ptr_vector m_bv_sorts; + sort * m_int_sort; + + ptr_vector m_bv_neg; + ptr_vector m_bv_add; + ptr_vector m_bv_sub; + ptr_vector m_bv_mul; + ptr_vector m_bv_sdiv; + ptr_vector m_bv_udiv; + ptr_vector m_bv_srem; + ptr_vector m_bv_urem; + ptr_vector m_bv_smod; + + ptr_vector m_bv_sdiv0; + ptr_vector m_bv_udiv0; + ptr_vector m_bv_srem0; + ptr_vector m_bv_urem0; + ptr_vector m_bv_smod0; + + ptr_vector m_bv_sdiv_i; + ptr_vector m_bv_udiv_i; + ptr_vector m_bv_srem_i; + ptr_vector m_bv_urem_i; + ptr_vector m_bv_smod_i; + + ptr_vector m_bv_uleq; + ptr_vector m_bv_sleq; + ptr_vector m_bv_ugeq; + ptr_vector m_bv_sgeq; + ptr_vector m_bv_ult; + ptr_vector m_bv_slt; + ptr_vector m_bv_ugt; + ptr_vector m_bv_sgt; + + ptr_vector m_bv_and; + ptr_vector m_bv_or; + ptr_vector m_bv_not; + ptr_vector m_bv_xor; + ptr_vector m_bv_nand; + ptr_vector m_bv_nor; + ptr_vector m_bv_xnor; + + ptr_vector m_bv_redor; + ptr_vector m_bv_redand; + ptr_vector m_bv_comp; + + ptr_vector m_bv_mul_ovfl; + ptr_vector m_bv_smul_ovfl; + ptr_vector m_bv_smul_udfl; + + ptr_vector m_bv_shl; + ptr_vector m_bv_lshr; + ptr_vector m_bv_ashr; + ptr_vector m_ext_rotate_left; + ptr_vector m_ext_rotate_right; + + ptr_vector m_bv2int; + ptr_vector m_int2bv; + vector > m_bit2bool; + ptr_vector m_mkbv; + + virtual void set_manager(ast_manager * m, family_id id); + void mk_bv_sort(unsigned bv_size); + sort * get_bv_sort(unsigned bv_size); + func_decl * mk_func_decl(decl_kind k, unsigned bv_size); + func_decl * mk_binary(ptr_vector & decls, decl_kind k, + char const * name, unsigned bv_size, bool ac, bool idempotent = false); + func_decl * mk_unary(ptr_vector & decls, decl_kind k, char const * name, unsigned bv_size); + func_decl * mk_pred(ptr_vector & decls, decl_kind k, + char const * name, unsigned bv_size); + func_decl * mk_reduction(ptr_vector & decls, decl_kind k, char const * name, unsigned bv_size); + func_decl * mk_comp(unsigned bv_size); + bool get_bv_size(sort * t, int & result); + bool get_bv_size(expr * t, int & result); + bool get_concat_size(unsigned arity, sort * const * domain, int & result); + bool get_extend_size(unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, int & result); + bool get_extract_size(unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, int & result); + + func_decl * mk_bv2int(unsigned bv_size, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain); + + func_decl * mk_int2bv(unsigned bv_size, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain); + + func_decl * mk_bit2bool(unsigned bv_size, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain); + + func_decl * mk_mkbv(unsigned arity, sort * const * domain); + + bool get_int2bv_size(unsigned num_parameters, parameter const * parameters, int & result); + + func_decl * mk_num_decl(unsigned num_parameters, parameter const * parameters, unsigned arity); + + void get_offset_term(app * a, expr * & t, rational & offset) const; +public: + bv_decl_plugin(); + + rational power_of_two(unsigned n) const; + + virtual ~bv_decl_plugin() {} + virtual void finalize(); + + virtual decl_plugin * mk_fresh() { return alloc(bv_decl_plugin); } + + virtual sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters); + + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned num_args, expr * const * args, sort * range); + + virtual bool is_value(app* e) const; + + virtual void get_op_names(svector & op_names, symbol const & logic); + + virtual void get_sort_names(svector & sort_names, symbol const & logic); + + virtual bool are_distinct(app* a, app* b) const; + + virtual expr * get_some_value(sort * s); +}; + +class bv_util { + ast_manager & m_manager; + bv_decl_plugin * m_plugin; + +public: + bv_util(ast_manager & m); + + ast_manager & get_manager() const { return m_manager; } + + family_id get_fid() const { return m_plugin->get_family_id(); } + + family_id get_family_id() const { return get_fid(); } + + rational power_of_two(unsigned n) const { return m_plugin->power_of_two(n); } + + rational norm(rational const & val, unsigned bv_size, bool is_signed) const ; + rational norm(rational const & val, unsigned bv_size) const { return norm(val, bv_size, false); } + bool has_sign_bit(rational const & n, unsigned bv_size) const; + app * mk_numeral(rational const & val, sort* s); + app * mk_numeral(rational const & val, unsigned bv_size); + app * mk_numeral(uint64 u, unsigned bv_size) { return mk_numeral(rational(u, rational::ui64()), bv_size); } + sort * mk_sort(unsigned bv_size); + bool is_numeral(expr const * n, rational & val, unsigned & bv_size) const; + bool is_numeral(expr const * n) const { + return is_app_of(n, get_fid(), OP_BV_NUM); + } + bool is_allone(expr const * e) const; + bool is_zero(expr const * e) const; + bool is_bv_sort(sort const * s) const; + bool is_bv(expr const* e) const { + return is_bv_sort(m_manager.get_sort(e)); + } + + unsigned get_bv_size(sort const * s) const { + SASSERT(is_bv_sort(s)); + return static_cast(s->get_parameter(0).get_int()); + } + unsigned get_bv_size(expr const * n) const { return get_bv_size(m_manager.get_sort(n)); } + + app * mk_ule(expr * arg1, expr * arg2) { return m_manager.mk_app(get_fid(), OP_ULEQ, arg1, arg2); } + app * mk_sle(expr * arg1, expr * arg2) { return m_manager.mk_app(get_fid(), OP_SLEQ, arg1, arg2); } + app * mk_extract(unsigned high, unsigned low, expr * n) { + parameter params[2] = { parameter(high), parameter(low) }; + return m_manager.mk_app(get_fid(), OP_EXTRACT, 2, params, 1, &n); + } + app * mk_concat(unsigned num, expr * const * args) { return m_manager.mk_app(get_fid(), OP_CONCAT, num, args); } + app * mk_concat(expr * arg1, expr * arg2) { expr * args[2] = { arg1, arg2 }; return mk_concat(2, args); } + app * mk_bv_or(unsigned num, expr * const * args) { return m_manager.mk_app(get_fid(), OP_BOR, num, args); } + app * mk_bv_not(expr * arg) { return m_manager.mk_app(get_fid(), OP_BNOT, arg); } + app * mk_bv_xor(unsigned num, expr * const * args) { return m_manager.mk_app(get_fid(), OP_BXOR, num, args); } + app * mk_bv_neg(expr * arg) { return m_manager.mk_app(get_fid(), OP_BNEG, arg); } + app * mk_bv_urem(expr * arg1, expr * arg2) { return m_manager.mk_app(get_fid(), OP_BUREM, arg1, arg2); } + app * mk_bv_srem(expr * arg1, expr * arg2) { return m_manager.mk_app(get_fid(), OP_BSREM, arg1, arg2); } + app * mk_bv_add(expr * arg1, expr * arg2) { return m_manager.mk_app(get_fid(), OP_BADD, arg1, arg2); } + app * mk_bv_sub(expr * arg1, expr * arg2) { return m_manager.mk_app(get_fid(), OP_BSUB, arg1, arg2); } + app * mk_bv_mul(expr * arg1, expr * arg2) { return m_manager.mk_app(get_fid(), OP_BMUL, arg1, arg2); } + app * mk_zero_extend(unsigned n, expr* e) { + parameter p(n); + return m_manager.mk_app(get_fid(), OP_ZERO_EXT, 1, &p, 1, &e); + } + app * mk_sign_extend(unsigned n, expr* e) { + parameter p(n); + return m_manager.mk_app(get_fid(), OP_SIGN_EXT, 1, &p, 1, &e); + } + app * mk_bv_shl(expr* arg1, expr* arg2) { return m_manager.mk_app(get_fid(), OP_BSHL, arg1, arg2); } + app * mk_bv_ashr(expr* arg1, expr* arg2) { return m_manager.mk_app(get_fid(), OP_BASHR, arg1, arg2); } + app * mk_bv_lshr(expr* arg1, expr* arg2) { return m_manager.mk_app(get_fid(), OP_BLSHR, arg1, arg2); } + + app * mk_bv2int(expr* e); + + app * mk_bvsmul_no_ovfl(expr* m, expr* n) { return m_manager.mk_app(get_fid(), OP_BSMUL_NO_OVFL, n, m); } + app * mk_bvsmul_no_udfl(expr* m, expr* n) { return m_manager.mk_app(get_fid(), OP_BSMUL_NO_UDFL, n, m); } + app * mk_bvumul_no_ovfl(expr* m, expr* n) { return m_manager.mk_app(get_fid(), OP_BUMUL_NO_OVFL, n, m); } + + app * mk_bv(unsigned n, expr* const* es) { return m_manager.mk_app(get_fid(), OP_MKBV, n, es); } + + bool is_concat(expr const * e) const { return is_app_of(e, get_fid(), OP_CONCAT); } + bool is_extract(func_decl const * f) const { return is_decl_of(f, get_fid(), OP_EXTRACT); } + bool is_extract(expr const * e) const { return is_app_of(e, get_fid(), OP_EXTRACT); } + unsigned get_extract_high(func_decl const * f) const { return f->get_parameter(0).get_int(); } + unsigned get_extract_low(func_decl const * f) const { return f->get_parameter(1).get_int(); } + unsigned get_extract_high(expr const * n) { SASSERT(is_extract(n)); return get_extract_high(to_app(n)->get_decl()); } + unsigned get_extract_low(expr const * n) { SASSERT(is_extract(n)); return get_extract_low(to_app(n)->get_decl()); } + bool is_extract(expr const* e, unsigned& low, unsigned& high, expr*& b) { + if (!is_extract(e)) return false; + low = get_extract_low(e); + high = get_extract_high(e); + b = to_app(e)->get_arg(0); + return true; + } + bool is_bv2int(expr const* e, expr*& r) { + if (!is_bv2int(e)) return false; + r = to_app(e)->get_arg(0); + return true; + } + bool is_bv_add(expr const * e) const { return is_app_of(e, get_fid(), OP_BADD); } + bool is_bv_sub(expr const * e) const { return is_app_of(e, get_fid(), OP_BSUB); } + bool is_bv_mul(expr const * e) const { return is_app_of(e, get_fid(), OP_BMUL); } + bool is_bv_neg(expr const * e) const { return is_app_of(e, get_fid(), OP_BNEG); } + bool is_bv_sdiv(expr const * e) const { return is_app_of(e, get_fid(), OP_BSDIV); } + bool is_bv_udiv(expr const * e) const { return is_app_of(e, get_fid(), OP_BUDIV); } + bool is_bv_srem(expr const * e) const { return is_app_of(e, get_fid(), OP_BSREM); } + bool is_bv_urem(expr const * e) const { return is_app_of(e, get_fid(), OP_BUREM); } + bool is_bv_smod(expr const * e) const { return is_app_of(e, get_fid(), OP_BSMOD); } + bool is_bv_and(expr const * e) const { return is_app_of(e, get_fid(), OP_BAND); } + bool is_bv_or(expr const * e) const { return is_app_of(e, get_fid(), OP_BOR); } + bool is_bv_xor(expr const * e) const { return is_app_of(e, get_fid(), OP_BXOR); } + bool is_bv_nand(expr const * e) const { return is_app_of(e, get_fid(), OP_BNAND); } + bool is_bv_nor(expr const * e) const { return is_app_of(e, get_fid(), OP_BNOR); } + bool is_bv_not(expr const * e) const { return is_app_of(e, get_fid(), OP_BNOT); } + bool is_bv_ule(expr const * e) const { return is_app_of(e, get_fid(), OP_ULEQ); } + bool is_bv_sle(expr const * e) const { return is_app_of(e, get_fid(), OP_SLEQ); } + bool is_bit2bool(expr const * e) const { return is_app_of(e, get_fid(), OP_BIT2BOOL); } + bool is_bv2int(expr const* e) const { return is_app_of(e, get_fid(), OP_BV2INT); } + bool is_int2bv(expr const* e) const { return is_app_of(e, get_fid(), OP_INT2BV); } + bool is_mkbv(expr const * e) const { return is_app_of(e, get_fid(), OP_MKBV); } + bool is_bv_ashr(expr const * e) const { return is_app_of(e, get_fid(), OP_BASHR); } + bool is_bv_lshr(expr const * e) const { return is_app_of(e, get_fid(), OP_BLSHR); } + bool is_bv_shl(expr const * e) const { return is_app_of(e, get_fid(), OP_BSHL); } + bool is_sign_ext(expr const * e) const { return is_app_of(e, get_fid(), OP_SIGN_EXT); } + + MATCH_BINARY(is_bv_add); + MATCH_BINARY(is_bv_mul); + MATCH_BINARY(is_bv_sle); + MATCH_BINARY(is_bv_ule); + MATCH_BINARY(is_bv_shl); + + bool mult_inverse(rational const & n, unsigned bv_size, rational & result); +}; + +#endif /* _BV_DECL_PLUGIN_H_ */ + diff --git a/lib/bv_elim.cpp b/lib/bv_elim.cpp new file mode 100644 index 000000000..3238d13e7 --- /dev/null +++ b/lib/bv_elim.cpp @@ -0,0 +1,113 @@ +#include "bv_elim.h" +#include "bv_decl_plugin.h" +#include "var_subst.h" +#include + +void bv_elim::elim(quantifier* q, quantifier_ref& r) { + + svector names, _names; + sort_ref_buffer sorts(m_manager), _sorts(m_manager); + expr_ref_buffer pats(m_manager); + expr_ref_buffer no_pats(m_manager); + expr_ref_buffer subst_map(m_manager), _subst_map(m_manager); + var_subst subst(m_manager); + bv_util bv(m_manager); + expr_ref new_body(m_manager); + expr* old_body = q->get_expr(); + unsigned num_decls = q->get_num_decls(); + family_id bfid = m_manager.get_family_id("bv"); + + // + // Traverse sequence of bound variables to eliminate + // bit-vecctor variables and replace them by + // Booleans. + // + unsigned var_idx = 0; + for (unsigned i = num_decls; i > 0; ) { + --i; + sort* s = q->get_decl_sort(i); + symbol nm = q->get_decl_name(i); + + if (bv.is_bv_sort(s)) { + // convert n-bit bit-vector variable into sequence of n-Booleans. + unsigned num_bits = bv.get_bv_size(s); + expr_ref_buffer args(m_manager); + expr_ref bv(m_manager); + for (unsigned j = 0; j < num_bits; ++j) { + std::ostringstream new_name; + new_name << nm.str(); + new_name << "_"; + new_name << j; + var* v = m_manager.mk_var(var_idx++, m_manager.mk_bool_sort()); + args.push_back(v); + _sorts.push_back(m_manager.mk_bool_sort()); + _names.push_back(symbol(new_name.str().c_str())); + } + bv = m_manager.mk_app(bfid, OP_MKBV, 0, 0, args.size(), args.c_ptr()); + _subst_map.push_back(bv.get()); + } + else { + _subst_map.push_back(m_manager.mk_var(var_idx++, s)); + _sorts.push_back(s); + _names.push_back(nm); + } + } + // + // reverse the vectors. + // + SASSERT(_names.size() == _sorts.size()); + for (unsigned i = _names.size(); i > 0; ) { + --i; + names.push_back(_names[i]); + sorts.push_back(_sorts[i]); + } + for (unsigned i = _subst_map.size(); i > 0; ) { + --i; + subst_map.push_back(_subst_map[i]); + } + + expr* const* sub = subst_map.c_ptr(); + unsigned sub_size = subst_map.size(); + + subst(old_body, sub_size, sub, new_body); + + for (unsigned j = 0; j < q->get_num_patterns(); j++) { + expr_ref pat(m_manager); + subst(q->get_pattern(j), sub_size, sub, pat); + pats.push_back(pat); + } + for (unsigned j = 0; j < q->get_num_no_patterns(); j++) { + expr_ref nopat(m_manager); + subst(q->get_no_pattern(j), sub_size, sub, nopat); + no_pats.push_back(nopat); + } + + r = m_manager.mk_quantifier(true, + names.size(), + sorts.c_ptr(), + names.c_ptr(), + new_body.get(), + q->get_weight(), + q->get_qid(), + q->get_skid(), + pats.size(), pats.c_ptr(), + no_pats.size(), no_pats.c_ptr()); +} + +bool bv_elim_star::visit_quantifier(quantifier* q) { + // behave like destructive resolution, do not recurse. + return true; +} + +void bv_elim_star::reduce1_quantifier(quantifier* q) { + quantifier_ref r(m_manager); + proof_ref pr(m_manager); + m_bv_elim.elim(q, r); + if (m_manager.fine_grain_proofs()) { + pr = m_manager.mk_rewrite(q, r.get()); + } + else { + pr = 0; + } + cache_result(q, r, pr); +} diff --git a/lib/bv_elim.h b/lib/bv_elim.h new file mode 100644 index 000000000..bca017db7 --- /dev/null +++ b/lib/bv_elim.h @@ -0,0 +1,45 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + bv_elim.h + +Abstract: + + Eliminate bit-vectors variables from clauses, by + replacing them by bound Boolean variables. + +Author: + + Nikolaj Bjorner (nbjorner) 2008-12-16. + +Revision History: + +--*/ +#ifndef _BV_ELIM_H_ +#define _BV_ELIM_H_ + +#include "ast.h" +#include "simplifier.h" + +class bv_elim { + ast_manager& m_manager; +public: + bv_elim(ast_manager& m) : m_manager(m) {}; + + void elim(quantifier* q, quantifier_ref& r); +}; + +class bv_elim_star : public simplifier { +protected: + bv_elim m_bv_elim; + virtual bool visit_quantifier(quantifier* q); + virtual void reduce1_quantifier(quantifier* q); +public: + bv_elim_star(ast_manager& m) : simplifier(m), m_bv_elim(m) { enable_ac_support(false); } + virtual ~bv_elim_star() {} +}; + +#endif /* _BV_ELIM_H_ */ + diff --git a/lib/bv_rewriter.cpp b/lib/bv_rewriter.cpp new file mode 100644 index 000000000..6cd3acbc3 --- /dev/null +++ b/lib/bv_rewriter.cpp @@ -0,0 +1,2076 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + bv_rewriter.cpp + +Abstract: + + Basic rewriting rules for bit-vectors + +Author: + + Leonardo (leonardo) 2011-04-14 + +Notes: + +--*/ +#include"bv_rewriter.h" +#include"poly_rewriter_def.h" +#include"ast_smt2_pp.h" + +mk_extract_proc::mk_extract_proc(bv_util & u): + m_util(u), + m_high(0), + m_low(UINT_MAX), + m_domain(0), + m_f_cached(0) { +} + +mk_extract_proc::~mk_extract_proc() { + if (m_f_cached) { + // m_f_cached has a reference to m_domain, so, I don't need to inc_ref m_domain + ast_manager & m = m_util.get_manager(); + m.dec_ref(m_f_cached); + } +} + +app * mk_extract_proc::operator()(unsigned high, unsigned low, expr * arg) { + ast_manager & m = m_util.get_manager(); + sort * s = m.get_sort(arg); + if (m_low == low && m_high == high && m_domain == s) + return m.mk_app(m_f_cached, arg); + // m_f_cached has a reference to m_domain, so, I don't need to inc_ref m_domain + if (m_f_cached) + m.dec_ref(m_f_cached); + app * r = to_app(m_util.mk_extract(high, low, arg)); + m_high = high; + m_low = low; + m_domain = s; + m_f_cached = r->get_decl(); + m.inc_ref(m_f_cached); + return r; +} + +void bv_rewriter::updt_local_params(params_ref const & p) { + m_hi_div0 = p.get_bool(":hi-div0", true); + m_elim_sign_ext = p.get_bool(":elim-sign-ext", true); + m_mul2concat = p.get_bool(":mul2concat", false); + m_bit2bool = p.get_bool(":bit2bool", true); + m_blast_eq_value = p.get_bool(":blast-eq-value", false); + m_mkbv2num = p.get_bool(":mkbv2num", false); + m_split_concat_eq = p.get_bool(":split-concat-eq", false); + m_udiv2mul = p.get_bool(":udiv2mul", false); +} + +void bv_rewriter::updt_params(params_ref const & p) { + poly_rewriter::updt_params(p); + updt_local_params(p); +} + +void bv_rewriter::get_param_descrs(param_descrs & r) { + poly_rewriter::get_param_descrs(r); + r.insert(":udiv2mul", CPK_BOOL, "(default: false) convert constant udiv to mul."); + r.insert(":split-concat-eq", CPK_BOOL, "(default: false) split equalities of the form (= (concat t1 t2) t3)."); + r.insert(":bit2bool", CPK_BOOL, "(default: true) try to convert bit-vector terms of size 1 into Boolean terms."); + r.insert(":blast-eq-value", CPK_BOOL, "(default: false) blast (some) Bit-vector equalities into bits."); + r.insert(":elim-sign-ext", CPK_BOOL, "(default: true) expand sign-ext operator using concat and extract."); + r.insert(":hi-div0", CPK_BOOL, "(default: true) use the 'hardware interpretation' for division by zero (for bit-vector terms)."); + r.insert(":mul2concat", CPK_BOOL, "(default: false) replace multiplication by a power of two into a concatenation."); +#ifndef _EXTERNAL_RELEASE + r.insert(":mkbv2num", CPK_BOOL, "(default: false) convert (mkbv [true/false]*) into a numeral"); +#endif +} + +br_status bv_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(f->get_family_id() == get_fid()); + + switch(f->get_decl_kind()) { + case OP_BIT0: SASSERT(num_args == 0); result = m_util.mk_numeral(0, 1); return BR_DONE; + case OP_BIT1: SASSERT(num_args == 0); result = m_util.mk_numeral(1, 1); return BR_DONE; + case OP_ULEQ: + SASSERT(num_args == 2); + return mk_ule(args[0], args[1], result); + case OP_UGEQ: + SASSERT(num_args == 2); + return mk_uge(args[0], args[1], result); + case OP_ULT: + SASSERT(num_args == 2); + return mk_ult(args[0], args[1], result); + case OP_UGT: + SASSERT(num_args == 2); + return mk_ult(args[1], args[0], result); + case OP_SLEQ: + SASSERT(num_args == 2); + return mk_sle(args[0], args[1], result); + case OP_SGEQ: + SASSERT(num_args == 2); + return mk_sge(args[0], args[1], result); + case OP_SLT: + SASSERT(num_args == 2); + return mk_slt(args[0], args[1], result); + case OP_SGT: + SASSERT(num_args == 2); + return mk_slt(args[1], args[0], result); + case OP_BADD: + SASSERT(num_args > 0); + return mk_bv_add(num_args, args, result); + case OP_BMUL: + SASSERT(num_args > 0); + return mk_bv_mul(num_args, args, result); + case OP_BSUB: + SASSERT(num_args > 0); + return mk_sub(num_args, args, result); + case OP_BNEG: + SASSERT(num_args == 1); + return mk_uminus(args[0], result); + case OP_BSHL: + SASSERT(num_args == 2); + return mk_bv_shl(args[0], args[1], result); + case OP_BLSHR: + SASSERT(num_args == 2); + return mk_bv_lshr(args[0], args[1], result); + case OP_BASHR: + SASSERT(num_args == 2); + return mk_bv_ashr(args[0], args[1], result); + case OP_BSDIV: + SASSERT(num_args == 2); + return mk_bv_sdiv(args[0], args[1], result); + case OP_BUDIV: + SASSERT(num_args == 2); + return mk_bv_udiv(args[0], args[1], result); + case OP_BSREM: + SASSERT(num_args == 2); + return mk_bv_srem(args[0], args[1], result); + case OP_BUREM: + SASSERT(num_args == 2); + return mk_bv_urem(args[0], args[1], result); + case OP_BSMOD: + SASSERT(num_args == 2); + return mk_bv_smod(args[0], args[1], result); + case OP_BSDIV_I: + SASSERT(num_args == 2); + return mk_bv_sdiv_i(args[0], args[1], result); + case OP_BUDIV_I: + SASSERT(num_args == 2); + return mk_bv_udiv_i(args[0], args[1], result); + case OP_BSREM_I: + SASSERT(num_args == 2); + return mk_bv_srem_i(args[0], args[1], result); + case OP_BUREM_I: + SASSERT(num_args == 2); + return mk_bv_urem_i(args[0], args[1], result); + case OP_BSMOD_I: + SASSERT(num_args == 2); + return mk_bv_smod_i(args[0], args[1], result); + case OP_CONCAT: + return mk_concat(num_args, args, result); + case OP_EXTRACT: + SASSERT(num_args == 1); + return mk_extract(m_util.get_extract_high(f), m_util.get_extract_low(f), args[0], result); + case OP_REPEAT: + SASSERT(num_args == 1); + return mk_repeat(f->get_parameter(0).get_int(), args[0], result); + case OP_ZERO_EXT: + SASSERT(num_args == 1); + return mk_zero_extend(f->get_parameter(0).get_int(), args[0], result); + case OP_SIGN_EXT: + SASSERT(num_args == 1); + return mk_sign_extend(f->get_parameter(0).get_int(), args[0], result); + case OP_BOR: + return mk_bv_or(num_args, args, result); + case OP_BXOR: + return mk_bv_xor(num_args, args, result); + case OP_BNOT: + SASSERT(num_args == 1); + return mk_bv_not(args[0], result); + case OP_BAND: + return mk_bv_and(num_args, args, result); + case OP_BNAND: + return mk_bv_nand(num_args, args, result); + case OP_BNOR: + return mk_bv_nor(num_args, args, result); + case OP_BXNOR: + return mk_bv_xnor(num_args, args, result); + case OP_ROTATE_LEFT: + SASSERT(num_args == 1); + return mk_bv_rotate_left(f->get_parameter(0).get_int(), args[0], result); + case OP_ROTATE_RIGHT: + SASSERT(num_args == 1); + return mk_bv_rotate_right(f->get_parameter(0).get_int(), args[0], result); + case OP_EXT_ROTATE_LEFT: + SASSERT(num_args == 2); + return mk_bv_ext_rotate_left(args[0], args[1], result); + case OP_EXT_ROTATE_RIGHT: + SASSERT(num_args == 2); + return mk_bv_ext_rotate_right(args[0], args[1], result); + case OP_BV2INT: + SASSERT(num_args == 1); + return mk_bv2int(args[0], result); + case OP_INT2BV: + SASSERT(num_args == 1); + return mk_int2bv(m_util.get_bv_size(f->get_range()), args[0], result); + case OP_BREDOR: + SASSERT(num_args == 1); + return mk_bv_redor(args[0], result); + case OP_BREDAND: + SASSERT(num_args == 1); + return mk_bv_redand(args[0], result); + case OP_BCOMP: + SASSERT(num_args == 2); + return mk_bv_comp(args[0], args[1], result); + case OP_MKBV: + return mk_mkbv(num_args, args, result); + default: + return BR_FAILED; + } +} + +br_status bv_rewriter::mk_ule(expr * a, expr * b, expr_ref & result) { + return mk_leq_core(false, a, b, result); +} + +br_status bv_rewriter::mk_uge(expr * a, expr * b, expr_ref & result) { + br_status st = mk_ule(b, a, result); + if (st != BR_FAILED) + return st; + result = m_util.mk_ule(b, a); + return BR_DONE; +} + +br_status bv_rewriter::mk_ult(expr * a, expr * b, expr_ref & result) { + result = m().mk_not(m_util.mk_ule(b, a)); + return BR_REWRITE2; +} + +br_status bv_rewriter::mk_sle(expr * a, expr * b, expr_ref & result) { + return mk_leq_core(true, a, b, result); +} + +br_status bv_rewriter::mk_sge(expr * a, expr * b, expr_ref & result) { + br_status st = mk_sle(b, a, result); + if (st != BR_FAILED) + return st; + result = m_util.mk_sle(b, a); + return BR_DONE; +} + +br_status bv_rewriter::mk_slt(expr * a, expr * b, expr_ref & result) { + result = m().mk_not(m_util.mk_sle(b, a)); + return BR_REWRITE2; +} + +br_status bv_rewriter::mk_leq_core(bool is_signed, expr * a, expr * b, expr_ref & result) { + + numeral r1, r2, r3; + unsigned sz; + bool is_num1 = is_numeral(a, r1, sz); + bool is_num2 = is_numeral(b, r2, sz); + + if (a == b) { + result = m().mk_true(); + return BR_DONE; + } + + if (is_num1) + r1 = m_util.norm(r1, sz, is_signed); + if (is_num2) + r2 = m_util.norm(r2, sz, is_signed); + + if (is_num1 && is_num2) { + result = r1 <= r2 ? m().mk_true() : m().mk_false(); + return BR_DONE; + } + + numeral lower, upper; + + if (is_num1 || is_num2) { + if (is_signed) { + lower = - m_util.power_of_two(sz - 1); + upper = m_util.power_of_two(sz - 1) - numeral(1); + } + else { + lower = numeral(0); + upper = m_util.power_of_two(sz) - numeral(1); + } + } + + if (is_num2) { + if (r2 == lower) { + result = m().mk_eq(a, b); + return BR_REWRITE1; + } + if (r2 == upper) { + result = m().mk_true(); + return BR_DONE; + } + } + + if (is_num1) { + // 0 <= b is true + if (r1 == lower) { + result = m().mk_true(); + return BR_DONE; + } + + // 2^n-1 <= b is a = b + if (r1 == upper) { + result = m().mk_eq(a, b); + return BR_REWRITE1; + } + } + +#if 0 + if (!is_signed && m_util.is_concat(b) && to_app(b)->get_num_args() == 2 && m_util.is_zero(to_app(b)->get_arg(0))) { + // + // a <=_u (concat 0 c) ---> a[h:l] = 0 && a[l-1:0] <=_u c + // + expr * b_1 = to_app(b)->get_arg(0); + expr * b_2 = to_app(b)->get_arg(1); + unsigned sz1 = get_bv_size(b_1); + unsigned sz2 = get_bv_size(b_2); + result = m().mk_and(m().mk_eq(m_mk_extract(sz2+sz1-1, sz2, a), b_1), + m_util.mk_ule(m_mk_extract(sz2-1, 0, a), b_2)); + return BR_REWRITE3; + } +#else + if (!is_signed) { + // Extended version of the rule above using is_zero_bit. + // It also catches examples atoms such as: + // + // a <=_u #x000f + // + unsigned bv_sz = m_util.get_bv_size(b); + unsigned i = bv_sz; + unsigned first_non_zero = UINT_MAX; + while (i > 0) { + --i; + if (!is_zero_bit(b, i)) { + first_non_zero = i; + break; + } + } + + if (first_non_zero == UINT_MAX) { + // all bits are zero + result = m().mk_eq(a, m_util.mk_numeral(numeral(0), bv_sz)); + return BR_REWRITE1; + } + else if (first_non_zero < bv_sz - 1) { + result = m().mk_and(m().mk_eq(m_mk_extract(bv_sz - 1, first_non_zero + 1, a), m_util.mk_numeral(numeral(0), bv_sz - first_non_zero - 1)), + m_util.mk_ule(m_mk_extract(first_non_zero, 0, a), m_mk_extract(first_non_zero, 0, b))); + return BR_REWRITE3; + } + + } +#endif + + + // Investigate if we need: + // + // k <=_s (concat 0 a) <=> (k[u:l] = 0 && k[l-1:0] <=_u a) || k[u:u] = bv1 + // + // (concat 0 a) <=_s k <=> k[u:u] = bv0 && (k[u:l] != 0 || a <=_u k[l-1:0]) + // + // (concat 0 a) <=_u k <=> k[u:l] != 0 || a <=_u k[l-1:0] + // + return BR_FAILED; +} + +br_status bv_rewriter::mk_extract(unsigned high, unsigned low, expr * arg, expr_ref & result) { + unsigned sz = get_bv_size(arg); + SASSERT(sz > 0); + + if (low == 0 && high == sz - 1) { + result = arg; + return BR_DONE; + } + + numeral v; + if (is_numeral(arg, v, sz)) { + sz = high - low + 1; + if (v.is_neg()) + mod(v, m_util.power_of_two(sz), v); + if (v.is_uint64()) { + uint64 u = v.get_uint64(); + uint64 e = shift_right(u, low) & (shift_left(1ull, sz) - 1ull); + result = mk_numeral(numeral(e, numeral::ui64()), sz); + return BR_DONE; + } + div(v, m_util.power_of_two(low), v); + result = mk_numeral(v, sz); + return BR_DONE; + } + + // (extract[high:low] (extract[high2:low2] x)) == (extract[high+low2 : low+low2] x) + if (m_util.is_extract(arg)) { + unsigned low2 = m_util.get_extract_low(arg); + result = m_mk_extract(high + low2, low + low2, to_app(arg)->get_arg(0)); + return BR_DONE; + } + + // (extract (concat ....)) --> (concat (extract ...) ... (extract ...) ) + if (m_util.is_concat(arg)) { + unsigned num = to_app(arg)->get_num_args(); + unsigned idx = sz; + for (unsigned i = 0; i < num; i++) { + expr * curr = to_app(arg)->get_arg(i); + unsigned curr_sz = get_bv_size(curr); + idx -= curr_sz; + if (idx > high) + continue; + // found first argument + if (idx <= low) { + // result is a fragment of this argument + if (low == idx && high - idx == curr_sz - 1) { + result = curr; + return BR_DONE; + } + else { + result = m_mk_extract(high - idx, low - idx, curr); + return BR_REWRITE1; + } + } + else { + // look for remaining arguments + ptr_buffer new_args; + bool used_extract = false; + if (high - idx == curr_sz - 1) { + new_args.push_back(curr); + } + else { + used_extract = true; + new_args.push_back(m_mk_extract(high - idx, 0, curr)); + } + for (unsigned j = i + 1; j < num; j++) { + curr = to_app(arg)->get_arg(j); + unsigned curr_sz = get_bv_size(curr); + idx -= curr_sz; + if (idx > low) { + new_args.push_back(curr); + continue; + } + if (idx == low) { + new_args.push_back(curr); + result = m_util.mk_concat(new_args.size(), new_args.c_ptr()); + return used_extract ? BR_REWRITE2 : BR_DONE; + } + new_args.push_back(m_mk_extract(curr_sz - 1, low - idx, curr)); + result = m_util.mk_concat(new_args.size(), new_args.c_ptr()); + return BR_REWRITE2; + } + UNREACHABLE(); + } + } + UNREACHABLE(); + } + + if (m_util.is_bv_not(arg) || + m_util.is_bv_or(arg) || + m_util.is_bv_xor(arg) || + (low == 0 && (m_util.is_bv_add(arg) || + m_util.is_bv_mul(arg)))) { + ptr_buffer new_args; + unsigned num = to_app(arg)->get_num_args(); + for (unsigned i = 0; i < num; i++) { + expr * curr = to_app(arg)->get_arg(i); + new_args.push_back(m_mk_extract(high, low, curr)); + } + result = m().mk_app(get_fid(), to_app(arg)->get_decl()->get_decl_kind(), new_args.size(), new_args.c_ptr()); + return BR_REWRITE2; + } + + if (m().is_ite(arg)) { + result = m().mk_ite(to_app(arg)->get_arg(0), + m_mk_extract(high, low, to_app(arg)->get_arg(1)), + m_mk_extract(high, low, to_app(arg)->get_arg(2))); + return BR_REWRITE2; + } + + return BR_FAILED; +} + +br_status bv_rewriter::mk_bv_shl(expr * arg1, expr * arg2, expr_ref & result) { + numeral r1, r2; + unsigned bv_size = get_bv_size(arg1); + unsigned sz; + + if (is_numeral(arg2, r2, sz)) { + if (r2.is_zero()) { + // x << 0 == x + result = arg1; + return BR_DONE; + } + + if (r2 >= numeral(bv_size)) { + result = mk_numeral(0, bv_size); + return BR_DONE; + } + + if (is_numeral(arg1, r1, sz)) { + if (bv_size <= 64) { + SASSERT(r1.is_uint64() && r2.is_uint64()); + SASSERT(r2.get_uint64() < bv_size); + + uint64 r = shift_left(r1.get_uint64(), r2.get_uint64()); + numeral rn(r, numeral::ui64()); + rn = m_util.norm(rn, bv_size); + result = mk_numeral(rn, bv_size); + return BR_DONE; + } + + + SASSERT(r2 < numeral(bv_size)); + SASSERT(r2.is_unsigned()); + r1 = m_util.norm(r1 * m_util.power_of_two(r2.get_unsigned()), bv_size); + result = mk_numeral(r1, bv_size); + return BR_DONE; + } + + SASSERT(r2.is_pos()); + SASSERT(r2 < numeral(bv_size)); + // (bvshl x k) -> (concat (extract [n-1-k:0] x) bv0:k) + unsigned k = r2.get_unsigned(); + expr * new_args[2] = { m_mk_extract(bv_size - k - 1, 0, arg1), + mk_numeral(0, k) }; + result = m_util.mk_concat(2, new_args); + return BR_REWRITE2; + } + + return BR_FAILED; +} + +br_status bv_rewriter::mk_bv_lshr(expr * arg1, expr * arg2, expr_ref & result) { + numeral r1, r2; + unsigned bv_size = get_bv_size(arg1); + unsigned sz; + + if (is_numeral(arg2, r2, sz)) { + if (r2.is_zero()) { + // x >> 0 == x + result = arg1; + return BR_DONE; + } + + if (r2 >= numeral(bv_size)) { + result = mk_numeral(0, bv_size); + return BR_DONE; + } + + if (is_numeral(arg1, r1, sz)) { + if (bv_size <= 64) { + SASSERT(r1.is_uint64()); + SASSERT(r2.is_uint64()); + uint64 r = shift_right(r1.get_uint64(), r2.get_uint64()); + numeral rn(r, numeral::ui64()); + rn = m_util.norm(rn, bv_size); + result = mk_numeral(rn, bv_size); + return BR_DONE; + } + + SASSERT(r2.is_unsigned()); + unsigned sh = r2.get_unsigned(); + div(r1, m_util.power_of_two(sh), r1); + result = mk_numeral(r1, bv_size); + return BR_DONE; + } + + SASSERT(r2.is_pos()); + SASSERT(r2 < numeral(bv_size)); + // (bvlshr x k) -> (concat bv0:k (extract [n-1:k] x)) + SASSERT(r2.is_unsigned()); + unsigned k = r2.get_unsigned(); + expr * new_args[2] = { mk_numeral(0, k), + m_mk_extract(bv_size - 1, k, arg1) }; + result = m_util.mk_concat(2, new_args); + return BR_REWRITE2; + } + + return BR_FAILED; +} + +br_status bv_rewriter::mk_bv_ashr(expr * arg1, expr * arg2, expr_ref & result) { + numeral r1, r2; + unsigned bv_size = get_bv_size(arg1); + SASSERT(bv_size > 0); + bool is_num2 = is_numeral(arg2, r2, bv_size); + + if (is_num2 && r2.is_zero()) { + result = arg1; + return BR_DONE; + } + + bool is_num1 = is_numeral(arg1, r1, bv_size); + + if (bv_size <= 64 && is_num1 && is_num2) { + uint64 n1 = r1.get_uint64(); + uint64 n2_orig = r2.get_uint64(); + uint64 n2 = n2_orig % bv_size; + SASSERT(n2 < bv_size); + uint64 r = shift_right(n1, n2); + bool sign = (n1 & shift_left(1ull, bv_size - 1ull)) != 0; + if (n2_orig > n2) { + if (sign) { + r = shift_left(1ull, bv_size) - 1ull; + } + else { + r = 0; + } + } + else if (sign) { + uint64 allone = shift_left(1ull, bv_size) - 1ull; + uint64 mask = ~(shift_left(1ull, bv_size - n2) - 1ull); + mask &= allone; + r |= mask; + } + result = mk_numeral(numeral(r, numeral::ui64()), bv_size); + return BR_DONE; + } + + if (is_num1 && is_num2 && numeral(bv_size) <= r2) { + if (m_util.has_sign_bit(r1, bv_size)) + result = mk_numeral(m_util.power_of_two(bv_size) - numeral(1), bv_size); + else + result = mk_numeral(0, bv_size); + return BR_DONE; + } + + if (is_num1 && is_num2) { + SASSERT(r2 < numeral(bv_size)); + bool sign = m_util.has_sign_bit(r1, bv_size); + div(r1, m_util.power_of_two(r2.get_unsigned()), r1); + if (sign) { + // pad ones. + numeral p(1); + for (unsigned i = 0; i < bv_size; ++i) { + if (r1 < p) { + r1 += p; + } + p *= numeral(2); + } + } + result = mk_numeral(r1, bv_size); + return BR_DONE; + } + + // (bvashr (bvashr x r1) r2) --> (bvashr x r1+r2) + if (is_num2 && m_util.is_bv_ashr(arg1) && is_numeral(to_app(arg1)->get_arg(1), r1, bv_size)) { + r1 += r2; + if (r1 > numeral(bv_size)) + r1 = numeral(bv_size); + result = m().mk_app(get_fid(), OP_BASHR, + to_app(arg1)->get_arg(0), + mk_numeral(r1, bv_size)); + return BR_REWRITE1; // not really needed at this time. + } + +#if 0 + // (bvashr x k) --> (concat extract[sz-1:sz-1](x) ... extract[sz-1:sz-1](x) extract[sz-1:k](x)) + if (is_num2) { + ptr_buffer new_args; + if (r2 > numeral(bv_size)) + r2 = numeral(bv_size); + SASSERT(r2 <= numeral(bv_size)); + unsigned k = r2.get_unsigned(); + expr * sign = m_mk_extract(bv_size-1, bv_size-1, arg1); + for (unsigned i = 0; i < k; i++) + new_args.push_back(sign); + if (k != bv_size) + new_args.push_back(m_mk_extract(bv_size-1, k, arg1)); + result = m_util.mk_concat(new_args.size(), new_args.c_ptr()); + return BR_REWRITE2; + } +#endif + + return BR_FAILED; +} + +br_status bv_rewriter::mk_bv_sdiv_core(expr * arg1, expr * arg2, bool hi_div0, expr_ref & result) { + numeral r1, r2; + unsigned bv_size; + + if (is_numeral(arg2, r2, bv_size)) { + r2 = m_util.norm(r2, bv_size, true); + if (r2.is_zero()) { + if (!hi_div0) { + result = m().mk_app(get_fid(), OP_BSDIV0, arg1); + return BR_DONE; + } + else { + // The "hardware interpretation" for (bvsdiv x 0) is (ite (bvslt x #x0000) #x0001 #xffff) + result = m().mk_ite(m().mk_app(get_fid(), OP_SLT, arg1, mk_numeral(0, bv_size)), + mk_numeral(1, bv_size), + mk_numeral(m_util.power_of_two(bv_size) - numeral(1), bv_size)); + return BR_REWRITE2; + } + } + + if (r2.is_one()) { + result = arg1; + return BR_DONE; + } + + if (!r2.is_zero() && is_numeral(arg1, r1, bv_size)) { + r1 = m_util.norm(r1, bv_size, true); + result = mk_numeral(machine_div(r1, r2), bv_size); + return BR_DONE; + } + + result = m().mk_app(get_fid(), OP_BSDIV_I, arg1, arg2); + return BR_DONE; + } + + if (hi_div0) { + result = m().mk_app(get_fid(), OP_BSDIV_I, arg1, arg2); + return BR_DONE; + } + + bv_size = get_bv_size(arg2); + result = m().mk_ite(m().mk_eq(arg2, mk_numeral(0, bv_size)), + m().mk_app(get_fid(), OP_BSDIV0, arg1), + m().mk_app(get_fid(), OP_BSDIV_I, arg1, arg2)); + return BR_REWRITE2; +} + +br_status bv_rewriter::mk_bv_udiv_core(expr * arg1, expr * arg2, bool hi_div0, expr_ref & result) { + numeral r1, r2; + unsigned bv_size; + + TRACE("bv_udiv", tout << "hi_div0: " << hi_div0 << "\n";); + + TRACE("udiv2mul", tout << mk_ismt2_pp(arg2, m()) << " udiv2mul: " << m_udiv2mul << "\n";); + + if (is_numeral(arg2, r2, bv_size)) { + r2 = m_util.norm(r2, bv_size); + if (r2.is_zero()) { + if (!hi_div0) { + result = m().mk_app(get_fid(), OP_BUDIV0, arg1); + return BR_DONE; + } + else { + // The "hardware interpretation" for (bvudiv x 0) is #xffff + result = mk_numeral(m_util.power_of_two(bv_size) - numeral(1), bv_size); + return BR_DONE; + + } + } + + if (r2.is_one()) { + result = arg1; + return BR_DONE; + } + + if (!r2.is_zero() && is_numeral(arg1, r1, bv_size)) { + r1 = m_util.norm(r1, bv_size); + result = mk_numeral(machine_div(r1, r2), bv_size); + return BR_DONE; + } + + unsigned shift; + if (r2.is_power_of_two(shift)) { + result = m().mk_app(get_fid(), OP_BLSHR, arg1, mk_numeral(shift, bv_size)); + return BR_REWRITE1; + } + + if (m_udiv2mul) { + TRACE("udiv2mul", tout << "using udiv2mul\n";); + numeral inv_r2; + if (m_util.mult_inverse(r2, bv_size, inv_r2)) { + result = m().mk_app(get_fid(), OP_BMUL, mk_numeral(inv_r2, bv_size), arg1); + return BR_REWRITE1; + } + } + + result = m().mk_app(get_fid(), OP_BUDIV_I, arg1, arg2); + return BR_DONE; + } + + if (hi_div0) { + result = m().mk_app(get_fid(), OP_BUDIV_I, arg1, arg2); + return BR_DONE; + } + + bv_size = get_bv_size(arg2); + result = m().mk_ite(m().mk_eq(arg2, mk_numeral(0, bv_size)), + m().mk_app(get_fid(), OP_BUDIV0, arg1), + m().mk_app(get_fid(), OP_BUDIV_I, arg1, arg2)); + + TRACE("bv_udiv", tout << mk_ismt2_pp(arg1, m()) << "\n" << mk_ismt2_pp(arg2, m()) << "\n---->\n" << mk_ismt2_pp(result, m()) << "\n";); + return BR_REWRITE2; +} + +br_status bv_rewriter::mk_bv_srem_core(expr * arg1, expr * arg2, bool hi_div0, expr_ref & result) { + numeral r1, r2; + unsigned bv_size; + + if (is_numeral(arg2, r2, bv_size)) { + r2 = m_util.norm(r2, bv_size, true); + if (r2.is_zero()) { + if (!hi_div0) { + result = m().mk_app(get_fid(), OP_BSREM0, arg1); + return BR_DONE; + } + else { + // The "hardware interpretation" for (bvsrem x 0) is x + result = arg1; + return BR_DONE; + } + } + + if (r2.is_one()) { + result = mk_numeral(0, bv_size); + return BR_DONE; + } + + if (!r2.is_zero() && is_numeral(arg1, r1, bv_size)) { + r1 = m_util.norm(r1, bv_size, true); + result = mk_numeral(r1 % r2, bv_size); + return BR_DONE; + } + + result = m().mk_app(get_fid(), OP_BSREM_I, arg1, arg2); + return BR_DONE; + } + + if (hi_div0) { + result = m().mk_app(get_fid(), OP_BSREM_I, arg1, arg2); + return BR_DONE; + } + + bv_size = get_bv_size(arg2); + result = m().mk_ite(m().mk_eq(arg2, mk_numeral(0, bv_size)), + m().mk_app(get_fid(), OP_BSREM0, arg1), + m().mk_app(get_fid(), OP_BSREM_I, arg1, arg2)); + return BR_REWRITE2; +} + +bool bv_rewriter::is_minus_one_core(expr * arg) const { + numeral r; + unsigned bv_size; + if (is_numeral(arg, r, bv_size)) { + return r == (m_util.power_of_two(bv_size) - numeral(1)); + } + return false; +} + +bool bv_rewriter::is_x_minus_one(expr * arg, expr * & x) { + if (is_add(arg) && to_app(arg)->get_num_args() == 2) { + if (is_minus_one_core(to_app(arg)->get_arg(0))) { + x = to_app(arg)->get_arg(1); + return true; + } + if (is_minus_one_core(to_app(arg)->get_arg(1))) { + x = to_app(arg)->get_arg(0); + return true; + } + } + return false; +} + +br_status bv_rewriter::mk_bv_urem_core(expr * arg1, expr * arg2, bool hi_div0, expr_ref & result) { + numeral r1, r2; + unsigned bv_size; + bool is_num1 = is_numeral(arg1, r1, bv_size); + + if (is_numeral(arg2, r2, bv_size)) { + r2 = m_util.norm(r2, bv_size); + if (r2.is_zero()) { + if (!hi_div0) { + result = m().mk_app(get_fid(), OP_BUREM0, arg1); + return BR_DONE; + } + else { + // The "hardware interpretation" for (bvurem x 0) is x + result = arg1; + return BR_DONE; + } + } + + if (r2.is_one()) { + result = mk_numeral(0, bv_size); + return BR_DONE; + } + + if (!r2.is_zero() && is_num1) { + r1 = m_util.norm(r1, bv_size); + r1 %= r2; + result = mk_numeral(r1, bv_size); + return BR_DONE; + } + + unsigned shift; + if (r2.is_power_of_two(shift)) { + expr * args[2] = { + mk_numeral(0, bv_size - shift), + m_mk_extract(shift-1, 0, arg1) + }; + result = m_util.mk_concat(2, args); + return BR_REWRITE2; + } + + result = m().mk_app(get_fid(), OP_BUREM_I, arg1, arg2); + return BR_DONE; + } + + if (!hi_div0) { + // urem(0, x) ==> ite(x = 0, urem0(x), 0) + if (is_num1 && r1.is_zero()) { + expr * zero = arg1; + result = m().mk_ite(m().mk_eq(arg2, zero), + m().mk_app(get_fid(), OP_BUREM0, zero), + zero); + return BR_REWRITE2; + } + + // urem(x - 1, x) ==> ite(x = 0, urem0(x-1), x - 1) ==> ite(x = 0, urem0(-1), x - 1) + expr * x; + if (is_x_minus_one(arg1, x) && x == arg2) { + bv_size = get_bv_size(arg1); + expr * x_minus_1 = arg1; + expr * minus_one = mk_numeral(m_util.power_of_two(bv_size) - numeral(1), bv_size); + result = m().mk_ite(m().mk_eq(x, mk_numeral(0, bv_size)), + m().mk_app(get_fid(), OP_BUREM0, minus_one), + x_minus_1); + return BR_REWRITE2; + } + } + else { + // Remark: when HI_DIV0=true is used, (bvurem x 0) --> x + if (is_num1 && r1.is_zero()) { + // urem(0, x) --> 0 + expr * zero = arg1; + result = zero; + return BR_DONE; + } + + // urem(x - 1, x) --> x - 1 + expr * x; + if (is_x_minus_one(arg1, x) && x == arg2) { + expr * x_minus_1 = arg1; + result = x_minus_1; + return BR_DONE; + } + } + + if (hi_div0) { + result = m().mk_app(get_fid(), OP_BUREM_I, arg1, arg2); + return BR_DONE; + } + + bv_size = get_bv_size(arg2); + result = m().mk_ite(m().mk_eq(arg2, mk_numeral(0, bv_size)), + m().mk_app(get_fid(), OP_BUREM0, arg1), + m().mk_app(get_fid(), OP_BUREM_I, arg1, arg2)); + return BR_REWRITE2; +} + +br_status bv_rewriter::mk_bv_smod_core(expr * arg1, expr * arg2, bool hi_div0, expr_ref & result) { + numeral r1, r2; + unsigned bv_size; + + bool is_num1 = is_numeral(arg1, r1, bv_size); + if (is_num1) { + r1 = m_util.norm(r1, bv_size, true); + if (r1.is_zero()) { + result = m().mk_app(get_fid(), OP_BUREM, arg1, arg2); + return BR_REWRITE1; + } + } + + if (is_numeral(arg2, r2, bv_size)) { + r2 = m_util.norm(r2, bv_size, true); + if (r2.is_zero()) { + if (!hi_div0) + result = m().mk_app(get_fid(), OP_BSMOD0, arg1); + else + result = arg1; + return BR_DONE; + } + + if (is_num1) { + numeral abs_r1 = m_util.norm(abs(r1), bv_size); + numeral abs_r2 = m_util.norm(abs(r2), bv_size); + numeral u = m_util.norm(abs_r1 % abs_r2, bv_size); + numeral r; + if (u.is_zero()) + r = u; + else if (r1.is_pos() && r2.is_pos()) + r = u; + else if (r1.is_neg() && r2.is_pos()) + r = m_util.norm(-u + r2, bv_size); + else if (r1.is_pos() && r2.is_neg()) + r = m_util.norm(u + r2, bv_size); + else + r = m_util.norm(-u, bv_size); + result = mk_numeral(r, bv_size); + return BR_DONE; + } + + if (r2.is_one()) { + // (bvsmod x 1) --> 0 + result = mk_numeral(0, bv_size); + return BR_REWRITE2; + } + } + + if (hi_div0) { + result = m().mk_app(get_fid(), OP_BSMOD_I, arg1, arg2); + return BR_DONE; + } + + bv_size = get_bv_size(arg2); + result = m().mk_ite(m().mk_eq(arg2, mk_numeral(0, bv_size)), + m().mk_app(get_fid(), OP_BSMOD0, arg1), + m().mk_app(get_fid(), OP_BSMOD_I, arg1, arg2)); + return BR_REWRITE2; +} + +br_status bv_rewriter::mk_int2bv(unsigned bv_size, expr * arg, expr_ref & result) { + numeral val; + bool is_int; + + if (m_autil.is_numeral(arg, val, is_int)) { + val = m_util.norm(val, bv_size); + result = mk_numeral(val, bv_size); + return BR_DONE; + } + + // (int2bv (bv2int x)) --> x + if (m_util.is_bv2int(arg) && bv_size == get_bv_size(to_app(arg)->get_arg(0))) { + result = to_app(arg)->get_arg(0); + return BR_DONE; + } + + return BR_FAILED; +} + +br_status bv_rewriter::mk_bv2int(expr * arg, expr_ref & result) { + numeral v; + unsigned sz; + if (is_numeral(arg, v, sz)) { + result = m_autil.mk_numeral(v, true); + return BR_DONE; + } + + // TODO: add other simplifications + + return BR_FAILED; +} + +br_status bv_rewriter::mk_concat(unsigned num_args, expr * const * args, expr_ref & result) { + expr_ref_buffer new_args(m()); + numeral v1; + numeral v2; + unsigned sz1, sz2; + bool fused_numeral = false; + bool expanded = false; + bool fused_extract = false; + for (unsigned i = 0; i < num_args; i++) { + expr * arg = args[i]; + expr * prev = 0; + if (i > 0) + prev = new_args.back(); + if (is_numeral(arg, v1, sz1) && prev != 0 && is_numeral(prev, v2, sz2)) { + v2 *= m_util.power_of_two(sz1); + v2 += v1; + new_args.pop_back(); + new_args.push_back(mk_numeral(v2, sz1+sz2)); + fused_numeral = true; + } + else if (m_flat && m_util.is_concat(arg)) { + unsigned num2 = to_app(arg)->get_num_args(); + for (unsigned j = 0; j < num2; j++) { + new_args.push_back(to_app(arg)->get_arg(j)); + } + expanded = true; + } + else if (m_util.is_extract(arg) && + prev != 0 && + m_util.is_extract(prev) && + to_app(arg)->get_arg(0) == to_app(prev)->get_arg(0) && + m_util.get_extract_low(prev) == m_util.get_extract_high(arg) + 1) { + // (concat (extract[h1,l1] a) (extract[h2,l2] a)) --> (extract[h1,l2] a) if l1 == h2+1 + expr * new_arg = m_mk_extract(m_util.get_extract_high(prev), + m_util.get_extract_low(arg), + to_app(arg)->get_arg(0)); + new_args.pop_back(); + new_args.push_back(new_arg); + fused_extract = true; + } + else { + new_args.push_back(arg); + } + } + if (!fused_numeral && !expanded && !fused_extract) + return BR_FAILED; + SASSERT(!new_args.empty()); + if (new_args.size() == 1) { + result = new_args.back(); + return fused_extract ? BR_REWRITE1 : BR_DONE; + } + result = m_util.mk_concat(new_args.size(), new_args.c_ptr()); + if (fused_extract) + return BR_REWRITE2; + else if (expanded) + return BR_REWRITE1; + else + return BR_DONE; +} + +br_status bv_rewriter::mk_zero_extend(unsigned n, expr * arg, expr_ref & result) { + if (n == 0) { + result = arg; + return BR_DONE; + } + else { + expr * args[2] = { mk_numeral(0, n), arg }; + result = m_util.mk_concat(2, args); + return BR_REWRITE1; + } +} + +br_status bv_rewriter::mk_sign_extend(unsigned n, expr * arg, expr_ref & result) { + if (n == 0) { + result = arg; + return BR_DONE; + } + + numeral r; + unsigned bv_size; + if (is_numeral(arg, r, bv_size)) { + unsigned result_bv_size = bv_size + n; + r = m_util.norm(r, bv_size, true); + mod(r, m_util.power_of_two(result_bv_size), r); + result = mk_numeral(r, result_bv_size); + return BR_DONE; + } + + if (m_elim_sign_ext) { + unsigned sz = get_bv_size(arg); + expr * sign = m_mk_extract(sz-1, sz-1, arg); + ptr_buffer args; + for (unsigned i = 0; i < n; i++) + args.push_back(sign); + args.push_back(arg); + result = m_util.mk_concat(args.size(), args.c_ptr()); + return BR_REWRITE2; + } + + return BR_FAILED; +} + +br_status bv_rewriter::mk_repeat(unsigned n, expr * arg, expr_ref & result) { + if (n == 1) { + result = arg; + return BR_DONE; + } + ptr_buffer args; + for (unsigned i = 0; i < n; i++) + args.push_back(arg); + result = m_util.mk_concat(args.size(), args.c_ptr()); + return BR_REWRITE1; +} + +br_status bv_rewriter::mk_bv_or(unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num > 0); + if (num == 1) { + result = args[0]; + return BR_DONE; + } + unsigned sz = get_bv_size(args[0]); + bool flattened = false; + ptr_buffer flat_args; + if (m_flat) { + for (unsigned i = 0; i < num; i++) { + expr * arg = args[i]; + if (m_util.is_bv_or(arg)) { + unsigned num2 = to_app(arg)->get_num_args(); + for (unsigned j = 0; j < num2; j++) + flat_args.push_back(to_app(arg)->get_arg(j)); + } + else { + flat_args.push_back(arg); + } + } + if (flat_args.size() != num) { + flattened = true; + num = flat_args.size(); + args = flat_args.c_ptr(); + } + } + + ptr_buffer new_args; + expr_fast_mark1 pos_args; + expr_fast_mark2 neg_args; + bool merged = false; + unsigned num_coeffs = 0; + numeral v1, v2; + for (unsigned i = 0; i < num; i++) { + expr * arg = args[i]; + if (is_numeral(arg, v2, sz)) { + num_coeffs++; + v1 = bitwise_or(v1, v2); + continue; + } + + if (m_util.is_bv_not(arg)) { + expr * atom = to_app(arg)->get_arg(0); + if (pos_args.is_marked(atom)) { + result = mk_numeral(m_util.power_of_two(sz) - numeral(1), sz); + return BR_DONE; + } + else if (neg_args.is_marked(atom)) { + merged = true; + continue; + } + neg_args.mark(atom, true); + new_args.push_back(arg); + } + else { + if (pos_args.is_marked(arg)) { + merged = true; + continue; + } + else if (neg_args.is_marked(arg)) { + result = mk_numeral(m_util.power_of_two(sz) - numeral(1), sz); + return BR_DONE; + } + pos_args.mark(arg, true); + new_args.push_back(arg); + } + } + + if (v1 == m_util.power_of_two(sz) - numeral(1)) { + result = mk_numeral(v1, sz); + return BR_DONE; + } + + // Simplifications of the form: + // (bvor (concat x #x00) (concat #x00 y)) --> (concat x y) + if (new_args.size() == 2 && + num_coeffs == 0 && + m_util.is_concat(new_args[0]) && + m_util.is_concat(new_args[1])) { + app * concat1 = to_app(new_args[0]); + app * concat2 = to_app(new_args[1]); + unsigned i = 0; + for (i = 0; i < sz; i++) + if (!is_zero_bit(concat1, i) && !is_zero_bit(concat2, i)) + break; + if (i == sz) { + // is target + ptr_buffer non_zero_args; + int j = sz; + j--; + while (j >= 0) { + int high = j; + while (j >= 0 && is_zero_bit(concat1, j)) + --j; + if (j != high) + non_zero_args.push_back(m_mk_extract(high, j+1, concat2)); + high = j; + while (j >= 0 && is_zero_bit(concat2, j)) + --j; + if (j != high) + non_zero_args.push_back(m_mk_extract(high, j+1, concat1)); + } + result = m_util.mk_concat(non_zero_args.size(), non_zero_args.c_ptr()); + return BR_REWRITE2; + } + } + + if (!v1.is_zero() && new_args.size() == 1) { + v1 = m_util.norm(v1, sz); +#ifdef _TRACE + numeral old_v1 = v1; +#endif + // OR is a mask + expr * t = new_args[0]; + numeral two(2); + ptr_buffer exs; + unsigned low = 0; + unsigned i = 0; + while (i < sz) { + while (i < sz && mod(v1, two).is_one()) { + i++; + div(v1, two, v1); + } + if (i != low) { + unsigned num_sz = i - low; + exs.push_back(m_util.mk_numeral(m_util.power_of_two(num_sz) - numeral(1), num_sz)); + low = i; + } + while (i < sz && mod(v1, two).is_zero()) { + i++; + div(v1, two, v1); + } + if (i != low) { + exs.push_back(m_mk_extract(i-1, low, t)); + low = i; + } + } + std::reverse(exs.begin(), exs.end()); + result = m_util.mk_concat(exs.size(), exs.c_ptr()); + TRACE("mask_bug", + tout << "(assert (distinct (bvor (_ bv" << old_v1 << " " << sz << ")\n" << mk_ismt2_pp(t, m()) << ")\n"; + tout << mk_ismt2_pp(result, m()) << "))\n";); + return BR_REWRITE2; + } + + if (!flattened && !merged && (num_coeffs == 0 || (num_coeffs == 1 && !v1.is_zero()))) { + return BR_FAILED; + } + + if (!v1.is_zero()) { + new_args.push_back(mk_numeral(v1, sz)); + } + + switch (new_args.size()) { + case 0: + result = mk_numeral(0, sz); + return BR_DONE; + case 1: + result = new_args[0]; + return BR_DONE; + default: + result = m_util.mk_bv_or(new_args.size(), new_args.c_ptr()); + return BR_DONE; + } +} + +br_status bv_rewriter::mk_bv_xor(unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num > 0); + if (num == 1) { + result = args[0]; + return BR_DONE; + } + unsigned sz = get_bv_size(args[0]); + bool flattened = false; + ptr_buffer flat_args; + if (m_flat) { + for (unsigned i = 0; i < num; i++) { + expr * arg = args[i]; + if (m_util.is_bv_xor(arg)) { + unsigned num2 = to_app(arg)->get_num_args(); + for (unsigned j = 0; j < num2; j++) + flat_args.push_back(to_app(arg)->get_arg(j)); + } + else { + flat_args.push_back(arg); + } + } + if (flat_args.size() != num) { + flattened = true; + num = flat_args.size(); + args = flat_args.c_ptr(); + } + } + + expr_fast_mark1 pos_args; + expr_fast_mark2 neg_args; + bool merged = false; + numeral v1, v2; + unsigned num_coeffs = 0; + for (unsigned i = 0; i < num; i++) { + expr * arg = args[i]; + if (is_numeral(arg, v2, sz)) { + v1 = bitwise_xor(v1, v2); + num_coeffs++; + continue; + } + + if (m_util.is_bv_not(arg)) { + expr * atom = to_app(arg)->get_arg(0); + if (neg_args.is_marked(atom)) { + neg_args.mark(atom, false); + merged = true; + } + else if (pos_args.is_marked(atom)) { + pos_args.mark(atom, false); + merged = true; + v1 = bitwise_xor(v1, m_util.power_of_two(sz) - numeral(1)); + } + else { + neg_args.mark(atom, true); + } + } + else { + if (pos_args.is_marked(arg)) { + pos_args.mark(arg, false); + merged = true; + } + else if (neg_args.is_marked(arg)) { + neg_args.mark(arg, false); + merged = true; + v1 = bitwise_xor(v1, m_util.power_of_two(sz) - numeral(1)); + } + else { + pos_args.mark(arg, true); + } + } + } + + if (!merged && !flattened && (num_coeffs == 0 || (num_coeffs == 1 && !v1.is_zero() && v1 != (m_util.power_of_two(sz) - numeral(1))))) + return BR_FAILED; + + ptr_buffer new_args; + expr_ref c(m()); // may not be used + if (!v1.is_zero()) { + c = mk_numeral(v1, sz); + new_args.push_back(c); + } + + for (unsigned i = 0; i < num; i++) { + expr * arg = args[i]; + if (is_numeral(arg)) + continue; + if (m_util.is_bv_not(arg)) { + expr * atom = to_app(arg)->get_arg(0); + if (neg_args.is_marked(atom)) { + new_args.push_back(arg); + neg_args.mark(atom, false); + } + } + else if (pos_args.is_marked(arg)) { + new_args.push_back(arg); + pos_args.mark(arg, false); + } + } + + switch (new_args.size()) { + case 0: + result = mk_numeral(0, sz); + return BR_DONE; + case 1: + result = new_args[0]; + return BR_DONE; + case 2: + if (m_util.is_allone(new_args[0])) { + result = m_util.mk_bv_not(new_args[1]); + return BR_DONE; + } + __fallthrough; + default: + result = m_util.mk_bv_xor(new_args.size(), new_args.c_ptr()); + return BR_DONE; + } +} + +br_status bv_rewriter::mk_bv_not(expr * arg, expr_ref & result) { + if (m_util.is_bv_not(arg)) { + result = to_app(arg)->get_arg(0); + return BR_DONE; + } + + numeral val; + unsigned bv_size; + if (is_numeral(arg, val, bv_size)) { + val = bitwise_not(bv_size, val); + result = mk_numeral(val, bv_size); + return BR_DONE; + } + +#if 1 + if (m_util.is_concat(arg)) { + ptr_buffer new_args; + unsigned num = to_app(arg)->get_num_args(); + for (unsigned i = 0; i < num; i++) { + new_args.push_back(m_util.mk_bv_not(to_app(arg)->get_arg(i))); + } + result = m_util.mk_concat(new_args.size(), new_args.c_ptr()); + return BR_REWRITE2; + } +#endif + + return BR_FAILED; +} + +br_status bv_rewriter::mk_bv_and(unsigned num, expr * const * args, expr_ref & result) { + ptr_buffer new_args; + for (unsigned i = 0; i < num; i++) { + new_args.push_back(m_util.mk_bv_not(args[i])); + } + SASSERT(num == new_args.size()); + result = m_util.mk_bv_not(m_util.mk_bv_or(new_args.size(), new_args.c_ptr())); + return BR_REWRITE3; +} + +br_status bv_rewriter::mk_bv_nand(unsigned num, expr * const * args, expr_ref & result) { + ptr_buffer new_args; + for (unsigned i = 0; i < num; i++) { + new_args.push_back(m_util.mk_bv_not(args[i])); + } + result = m_util.mk_bv_or(new_args.size(), new_args.c_ptr()); + return BR_REWRITE2; +} + +br_status bv_rewriter::mk_bv_nor(unsigned num, expr * const * args, expr_ref & result) { + result = m_util.mk_bv_not(m_util.mk_bv_or(num, args)); + return BR_REWRITE2; +} + +br_status bv_rewriter::mk_bv_xnor(unsigned num_args, expr * const * args, expr_ref & result) { + result = m_util.mk_bv_not(m_util.mk_bv_xor(num_args, args)); + return BR_REWRITE2; +} + +br_status bv_rewriter::mk_bv_rotate_left(unsigned n, expr * arg, expr_ref & result) { + unsigned sz = get_bv_size(arg); + SASSERT(sz > 0); + n = n % sz; + if (n == 0 || sz == 1) { + result = arg; + return BR_DONE; + } + expr * args[2] = { m_mk_extract(sz - n - 1, 0, arg), m_mk_extract(sz - 1, sz - n, arg) }; + result = m_util.mk_concat(2, args); + return BR_REWRITE2; +} + +br_status bv_rewriter::mk_bv_rotate_right(unsigned n, expr * arg, expr_ref & result) { + unsigned sz = get_bv_size(arg); + SASSERT(sz > 0); + n = n % sz; + return mk_bv_rotate_left(sz - n, arg, result); +} + +br_status bv_rewriter::mk_bv_ext_rotate_left(expr * arg1, expr * arg2, expr_ref & result) { + numeral r2; + unsigned bv_size; + if (is_numeral(arg2, r2, bv_size)) { + unsigned shift = static_cast((r2 % numeral(bv_size)).get_uint64() % static_cast(bv_size)); + return mk_bv_rotate_left(shift, arg1, result); + } + return BR_FAILED; +} + +br_status bv_rewriter::mk_bv_ext_rotate_right(expr * arg1, expr * arg2, expr_ref & result) { + numeral r2; + unsigned bv_size; + if (is_numeral(arg2, r2, bv_size)) { + unsigned shift = static_cast((r2 % numeral(bv_size)).get_uint64() % static_cast(bv_size)); + return mk_bv_rotate_right(shift, arg1, result); + } + return BR_FAILED; +} + +br_status bv_rewriter::mk_bv_redor(expr * arg, expr_ref & result) { + if (is_numeral(arg)) { + result = m_util.is_zero(arg) ? mk_numeral(0, 1) : mk_numeral(1, 1); + return BR_DONE; + } + return BR_FAILED; +} + +br_status bv_rewriter::mk_bv_redand(expr * arg, expr_ref & result) { + numeral r; + unsigned bv_size; + if (is_numeral(arg, r, bv_size)) { + result = (r == m_util.power_of_two(bv_size) - numeral(1)) ? mk_numeral(1, 1) : mk_numeral(0, 1); + return BR_DONE; + } + return BR_FAILED; +} + +br_status bv_rewriter::mk_bv_comp(expr * arg1, expr * arg2, expr_ref & result) { + if (arg1 == arg2) { + result = mk_numeral(1,1); + return BR_DONE; + } + + if (is_numeral(arg1) && is_numeral(arg2)) { + SASSERT(arg1 != arg2); + result = mk_numeral(0, 1); + return BR_DONE; + } + + result = m().mk_ite(m().mk_eq(arg1, arg2), + mk_numeral(1, 1), + mk_numeral(0, 1)); + return BR_REWRITE2; +} + +br_status bv_rewriter::mk_bv_add(unsigned num_args, expr * const * args, expr_ref & result) { + br_status st = mk_add_core(num_args, args, result); + if (st != BR_FAILED && st != BR_DONE) + return st; +#if 0 + expr * x; + expr * y; + if (st == BR_FAILED && num_args == 2) { + x = args[0]; y = args[1]; + } + else if (st == BR_DONE && is_add(result) && to_app(result)->get_num_args() == 2) { + x = to_app(result)->get_arg(0); + y = to_app(result)->get_arg(1); + } + else { + return st; + } + + if (!m_util.is_concat(x) && !is_numeral(x)) + return st; + if (!m_util.is_concat(y) && !is_numeral(y)) + return st; + + unsigned sz = get_bv_size(x); + + for (unsigned i = 0; i < sz; i++) { + if (!is_zero_bit(x,i) && !is_zero_bit(y,i)) + return st; + } + + result = m().mk_app(get_fid(), OP_BOR, x, y); + return BR_REWRITE1; +#else + unsigned _num_args; + expr * const * _args; + if (st == BR_FAILED) { + _num_args = num_args; + _args = args; + } + else if (st == BR_DONE && is_add(result)) { + _num_args = to_app(result)->get_num_args(); + _args = to_app(result)->get_args(); + } + else { + return st; + } + if (_num_args < 2) + return st; + unsigned sz = get_bv_size(_args[0]); + for (unsigned i = 0; i < sz; i++) { + bool found_non_zero = false; + for (unsigned j = 0; j < _num_args; j++) { + if (!is_zero_bit(_args[j], i)) { + // at most one of the arguments may have a non-zero bit. + if (found_non_zero) + return st; + found_non_zero = true; + } + } + } + result = m().mk_app(get_fid(), OP_BOR, _num_args, _args); + return BR_REWRITE1; +#endif +} + +bool bv_rewriter::is_zero_bit(expr * x, unsigned idx) { + numeral val; + unsigned bv_size; + loop: + if (is_numeral(x, val, bv_size)) { + if (val.is_zero()) + return true; + div(val, m_util.power_of_two(idx), val); + return (val % numeral(2)).is_zero(); + } + if (m_util.is_concat(x)) { + unsigned i = to_app(x)->get_num_args(); + while (i > 0) { + --i; + expr * y = to_app(x)->get_arg(i); + bv_size = get_bv_size(y); + if (bv_size <= idx) { + idx -= bv_size; + } + else { + x = y; + goto loop; + } + } + UNREACHABLE(); + } + return false; +} + +br_status bv_rewriter::mk_bv_mul(unsigned num_args, expr * const * args, expr_ref & result) { + br_status st = mk_mul_core(num_args, args, result); + if (st != BR_FAILED && st != BR_DONE) + return st; + expr * x; + expr * y; + if (st == BR_FAILED && num_args == 2) { + x = args[0]; y = args[1]; + } + else if (st == BR_DONE && is_mul(result) && to_app(result)->get_num_args() == 2) { + x = to_app(result)->get_arg(0); + y = to_app(result)->get_arg(1); + } + else { + return st; + } + + if (m_mul2concat) { + numeral v; + unsigned bv_size; + unsigned shift; + if (is_numeral(x, v, bv_size) && v.is_power_of_two(shift)) { + SASSERT(shift >= 1); + expr * args[2] = { + m_mk_extract(bv_size-shift-1, 0, y), + mk_numeral(0, shift) + }; + result = m_util.mk_concat(2, args); + return BR_REWRITE2; + } + } + + return st; +} + +br_status bv_rewriter::mk_bit2bool(expr * lhs, expr * rhs, expr_ref & result) { + unsigned sz = get_bv_size(lhs); + if (sz != 1) + return BR_FAILED; + if (is_numeral(lhs)) + std::swap(lhs, rhs); + + numeral v; + + if (!is_numeral(rhs, v, sz)) + return BR_FAILED; + + if (is_numeral(lhs)) { + SASSERT(is_numeral(rhs)); + result = lhs == rhs ? m().mk_true() : m().mk_false(); + return BR_DONE; + } + + if (m().is_ite(lhs)) { + result = m().mk_ite(to_app(lhs)->get_arg(0), + m().mk_eq(to_app(lhs)->get_arg(1), rhs), + m().mk_eq(to_app(lhs)->get_arg(2), rhs)); + return BR_REWRITE2; + } + + if (m_util.is_bv_not(lhs)) { + SASSERT(v.is_one() || v.is_zero()); + result = m().mk_eq(to_app(lhs)->get_arg(0), mk_numeral(numeral(1) - v, 1)); + return BR_REWRITE1; + } + + bool is_one = v.is_one(); + + expr_ref bit1(m()); + bit1 = is_one ? rhs : mk_numeral(numeral(1), 1); + + if (m_util.is_bv_or(lhs)) { + ptr_buffer new_args; + unsigned num = to_app(lhs)->get_num_args(); + for (unsigned i = 0; i < num; i++) { + new_args.push_back(m().mk_eq(to_app(lhs)->get_arg(i), bit1)); + } + result = m().mk_or(new_args.size(), new_args.c_ptr()); + if (is_one) { + return BR_REWRITE2; + } + else { + result = m().mk_not(result); + return BR_REWRITE3; + } + } + + + if (m_util.is_bv_xor(lhs)) { + ptr_buffer new_args; + unsigned num = to_app(lhs)->get_num_args(); + for (unsigned i = 0; i < num; i++) { + new_args.push_back(m().mk_eq(to_app(lhs)->get_arg(i), bit1)); + } + // TODO: bool xor is not flat_assoc... must fix that. + result = m().mk_xor(new_args.size(), new_args.c_ptr()); + if (is_one) { + return BR_REWRITE2; + } + else { + result = m().mk_not(result); + return BR_REWRITE3; + } + } + + + return BR_FAILED; +} + +br_status bv_rewriter::mk_blast_eq_value(expr * lhs, expr * rhs, expr_ref & result) { + unsigned sz = get_bv_size(lhs); + if (sz == 1) + return BR_FAILED; + TRACE("blast_eq_value", tout << "sz: " << sz << "\n" << mk_ismt2_pp(lhs, m()) << "\n";); + if (is_numeral(lhs)) + std::swap(lhs, rhs); + + numeral v; + + if (!is_numeral(rhs, v, sz)) + return BR_FAILED; + if (!m_util.is_bv_or(lhs) && !m_util.is_bv_xor(lhs) && !m_util.is_bv_not(lhs)) + return BR_FAILED; + + numeral two(2); + ptr_buffer new_args; + for (unsigned i = 0; i < sz; i++) { + bool bit0 = (v % two).is_zero(); + new_args.push_back(m().mk_eq(m_mk_extract(i,i, lhs), + mk_numeral(bit0 ? 0 : 1, 1))); + div(v, two, v); + } + result = m().mk_and(new_args.size(), new_args.c_ptr()); + return BR_REWRITE3; +} + +br_status bv_rewriter::mk_eq_concat(expr * lhs, expr * rhs, expr_ref & result) { + SASSERT(m_util.is_concat(lhs) || m_util.is_concat(rhs)); + unsigned num1, num2; + expr * const * args1, * const * args2; + + if (m_util.is_concat(lhs)) { + num1 = to_app(lhs)->get_num_args(); + args1 = to_app(lhs)->get_args(); + } + else { + num1 = 1; + args1 = &lhs; + } + + if (m_util.is_concat(rhs)) { + num2 = to_app(rhs)->get_num_args(); + args2 = to_app(rhs)->get_args(); + } + else { + num2 = 1; + args2 = &rhs; + } + + ptr_buffer new_eqs; + unsigned low1 = 0; + unsigned low2 = 0; + unsigned i1 = num1; + unsigned i2 = num2; + while (i1 > 0 && i2 > 0) { + expr * arg1 = args1[i1-1]; + expr * arg2 = args2[i2-1]; + unsigned sz1 = get_bv_size(arg1); + unsigned sz2 = get_bv_size(arg2); + SASSERT(low1 < sz1 && low2 < sz2); + unsigned rsz1 = sz1 - low1; + unsigned rsz2 = sz2 - low2; + if (rsz1 == rsz2) { + new_eqs.push_back(m().mk_eq(m_mk_extract(sz1 - 1, low1, arg1), + m_mk_extract(sz2 - 1, low2, arg2))); + low1 = 0; + low2 = 0; + --i1; + --i2; + continue; + } + else if (rsz1 < rsz2) { + new_eqs.push_back(m().mk_eq(m_mk_extract(sz1 - 1, low1, arg1), + m_mk_extract(rsz1 + low2 - 1, low2, arg2))); + low1 = 0; + low2 += rsz1; + --i1; + } + else { + new_eqs.push_back(m().mk_eq(m_mk_extract(rsz2 + low1 - 1, low1, arg1), + m_mk_extract(sz2 - 1, low2, arg2))); + low1 += rsz2; + low2 = 0; + --i2; + } + } + SASSERT(i1 == 0 && i2 == 0); + SASSERT(new_eqs.size() >= 1); + result = m().mk_and(new_eqs.size(), new_eqs.c_ptr()); + return BR_REWRITE3; +} + +bool bv_rewriter::is_concat_split_target(expr * t) const { + return + m_split_concat_eq || + m_util.is_concat(t) || + m_util.is_numeral(t) || + m_util.is_bv_or(t); +} + +bool bv_rewriter::is_minus_one_times_t(expr * arg) { + expr * t1, * t2; + return (m_util.is_bv_mul(arg, t1, t2) && is_minus_one(t1)); +} + +void bv_rewriter::mk_t1_add_t2_eq_c(expr * t1, expr * t2, expr * c, expr_ref & result) { + SASSERT(is_numeral(c)); + if (is_minus_one_times_t(t1)) + result = m().mk_eq(t2, m_util.mk_bv_sub(c, t1)); + else + result = m().mk_eq(t1, m_util.mk_bv_sub(c, t2)); +} + +br_status bv_rewriter::mk_mul_eq(expr * lhs, expr * rhs, expr_ref & result) { + expr * c, * x; + numeral c_val, c_inv_val; + unsigned sz; + if (m_util.is_bv_mul(lhs, c, x) && + m_util.is_numeral(c, c_val, sz) && + m_util.mult_inverse(c_val, sz, c_inv_val)) { + + SASSERT(m_util.norm(c_val * c_inv_val, sz).is_one()); + + numeral rhs_val; + // c * x = a + if (m_util.is_numeral(rhs, rhs_val, sz)) { + // x = c_inv * a + result = m().mk_eq(x, m_util.mk_numeral(c_inv_val * rhs_val, sz)); + return BR_REWRITE1; + } + + expr * c2, * x2; + numeral c2_val; + // c * x = c2 * x2 + if (m_util.is_bv_mul(rhs, c2, x2) && m_util.is_numeral(c2, c2_val, sz)) { + // x = c_inv * c2 * x2 + numeral new_c2 = m_util.norm(c_inv_val * c2_val, sz); + if (new_c2.is_one()) + result = m().mk_eq(x, x2); + else + result = m().mk_eq(x, m_util.mk_bv_mul(m_util.mk_numeral(c_inv_val * c2_val, sz), x2)); + return BR_REWRITE1; + } + + // c * x = t_1 + ... + t_n + // and t_i's have non-unary coefficients (this condition is used to make sure we are actually reducing the number of multipliers). + if (m_util.is_bv_add(rhs)) { + // Potential problem: this simplification may increase the number of adders by reducing the amount of sharing. + unsigned num = to_app(rhs)->get_num_args(); + unsigned i; + for (i = 0; i < num; i++) { + expr * arg = to_app(rhs)->get_arg(i); + expr * c2, * x2; + if (m_util.is_numeral(arg)) + continue; + if (m_util.is_bv_mul(arg, c2, x2) && m_util.is_numeral(c2)) + continue; + break; + } + if (i == num) { + result = m().mk_eq(x, m_util.mk_bv_mul(m_util.mk_numeral(c_inv_val, sz), rhs)); + return BR_REWRITE2; + } + } + } + return BR_FAILED; +} + +br_status bv_rewriter::mk_eq_core(expr * lhs, expr * rhs, expr_ref & result) { + if (lhs == rhs) { + result = m().mk_true(); + return BR_DONE; + } + + if (is_numeral(lhs) && is_numeral(rhs)) { + result = m().mk_false(); + return BR_DONE; + } + + bool swapped = false; + if (is_numeral(lhs)) { + swapped = true; + std::swap(lhs, rhs); + } + + br_status st; + if (m_bit2bool) { + st = mk_bit2bool(lhs, rhs, result); + if (st != BR_FAILED) + return st; + } + + st = mk_mul_eq(lhs, rhs, result); + if (st != BR_FAILED) { + TRACE("mk_mul_eq", tout << mk_ismt2_pp(lhs, m()) << "\n=\n" << mk_ismt2_pp(rhs, m()) << "\n----->\n" << mk_ismt2_pp(result,m()) << "\n";); + return st; + } + + st = mk_mul_eq(rhs, lhs, result); + if (st != BR_FAILED) { + TRACE("mk_mul_eq", tout << mk_ismt2_pp(lhs, m()) << "\n=\n" << mk_ismt2_pp(rhs, m()) << "\n----->\n" << mk_ismt2_pp(result,m()) << "\n";); + return st; + } + + if (m_blast_eq_value) { + st = mk_blast_eq_value(lhs, rhs, result); + if (st != BR_FAILED) + return st; + } + + if (m_util.is_bv_add(lhs) || m_util.is_bv_mul(lhs) || m_util.is_bv_add(rhs) || m_util.is_bv_mul(rhs)) { + expr_ref new_lhs(m()); + expr_ref new_rhs(m()); + st = cancel_monomials(lhs, rhs, false, new_lhs, new_rhs); + if (st != BR_FAILED) { + if (is_numeral(new_lhs) && is_numeral(new_rhs)) { + result = new_lhs == new_rhs ? m().mk_true() : m().mk_false(); + return BR_DONE; + } + } + else { + new_lhs = lhs; + new_rhs = rhs; + } + + // Try to rewrite t1 + t2 = c --> t1 = c - t2 + // Reason: it is much cheaper to bit-blast. + expr * t1, * t2; + if (m_util.is_bv_add(new_lhs, t1, t2) && is_numeral(new_rhs)) { + mk_t1_add_t2_eq_c(t1, t2, new_rhs, result); + return BR_REWRITE2; + } + if (m_util.is_bv_add(new_rhs, t1, t2) && is_numeral(new_lhs)) { + mk_t1_add_t2_eq_c(t1, t2, new_lhs, result); + return BR_REWRITE2; + } + + if (st != BR_FAILED) { + result = m().mk_eq(new_lhs, new_rhs); + return BR_DONE; + } + } + + if ((m_util.is_concat(lhs) && is_concat_split_target(rhs)) || + (m_util.is_concat(rhs) && is_concat_split_target(lhs))) + return mk_eq_concat(lhs, rhs, result); + + if (swapped) { + result = m().mk_eq(lhs, rhs); + return BR_DONE; + } + + return BR_FAILED; +} + +br_status bv_rewriter::mk_mkbv(unsigned num, expr * const * args, expr_ref & result) { + if (m_mkbv2num) { + unsigned i; + for (i = 0; i < num; i++) + if (!m().is_true(args[i]) && !m().is_false(args[i])) + return BR_FAILED; + numeral val; + numeral two(2); + i = num; + while (i > 0) { + --i; + val *= two; + if (m().is_true(args[i])) + val++; + } + result = mk_numeral(val, num); + return BR_DONE; + } + return BR_FAILED; +} + + +template class poly_rewriter; + diff --git a/lib/bv_rewriter.h b/lib/bv_rewriter.h new file mode 100644 index 000000000..ba5ac5580 --- /dev/null +++ b/lib/bv_rewriter.h @@ -0,0 +1,171 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + bv_rewriter.h + +Abstract: + + Basic rewriting rules for bit-vectors + +Author: + + Leonardo (leonardo) 2011-04-14 + +Notes: + +--*/ +#ifndef _BV_REWRITER_H_ +#define _BV_REWRITER_H_ + +#include"poly_rewriter.h" +#include"bv_decl_plugin.h" +#include"arith_decl_plugin.h" + +class mk_extract_proc { + bv_util & m_util; + unsigned m_high; + unsigned m_low; + sort * m_domain; + func_decl * m_f_cached; +public: + mk_extract_proc(bv_util & u); + ~mk_extract_proc(); + app * operator()(unsigned high, unsigned low, expr * arg); +}; + +class bv_rewriter_core { +protected: + typedef rational numeral; + bv_util m_util; + ast_manager & m() const { return m_util.get_manager(); } + family_id get_fid() const { return m_util.get_family_id(); } + + bool is_numeral(expr * n) const { return m_util.is_numeral(n); } + bool is_numeral(expr * n, numeral & r) const { unsigned sz; return m_util.is_numeral(n, r, sz); } + bool is_zero(expr * n) const { return m_util.is_zero(n); } + bool is_minus_one(expr * n) const { return m_util.is_allone(n); } + void normalize(numeral & c, sort * s) { unsigned bv_size = m_util.get_bv_size(s); c = m_util.norm(c, bv_size); } + app * mk_numeral(numeral const & r, sort * s) { return m_util.mk_numeral(r, s); } + decl_kind add_decl_kind() const { return OP_BADD; } + decl_kind mul_decl_kind() const { return OP_BMUL; } + bool use_power() const { return false; } + decl_kind power_decl_kind() const { UNREACHABLE(); return static_cast(UINT_MAX); } +public: + bv_rewriter_core(ast_manager & m):m_util(m) {} +}; + +class bv_rewriter : public poly_rewriter { + mk_extract_proc m_mk_extract; + arith_util m_autil; + bool m_hi_div0; + bool m_elim_sign_ext; + bool m_mul2concat; + bool m_bit2bool; + bool m_blast_eq_value; + bool m_mkbv2num; + bool m_split_concat_eq; + bool m_udiv2mul; + + bool is_zero_bit(expr * x, unsigned idx); + + br_status mk_ule(expr * a, expr * b, expr_ref & result); + br_status mk_uge(expr * a, expr * b, expr_ref & result); + br_status mk_ult(expr * a, expr * b, expr_ref & result); + br_status mk_sle(expr * a, expr * b, expr_ref & result); + br_status mk_sge(expr * a, expr * b, expr_ref & result); + br_status mk_slt(expr * a, expr * b, expr_ref & result); + br_status mk_leq_core(bool is_signed, expr * a, expr * b, expr_ref & result); + + br_status mk_concat(unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_extract(unsigned high, unsigned low, expr * arg, expr_ref & result); + br_status mk_repeat(unsigned n, expr * arg, expr_ref & result); + br_status mk_zero_extend(unsigned n, expr * arg, expr_ref & result); + br_status mk_sign_extend(unsigned n, expr * arg, expr_ref & result); + br_status mk_bv_not(expr * arg, expr_ref & result); + br_status mk_bv_or(unsigned num, expr * const * args, expr_ref & result); + br_status mk_bv_xor(unsigned num, expr * const * args, expr_ref & result); + br_status mk_bv_and(unsigned num, expr * const * args, expr_ref & result); + br_status mk_bv_nand(unsigned num, expr * const * args, expr_ref & result); + br_status mk_bv_nor(unsigned num, expr * const * args, expr_ref & result); + br_status mk_bv_xnor(unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_bv_rotate_left(unsigned n, expr * arg, expr_ref & result); + br_status mk_bv_rotate_right(unsigned n, expr * arg, expr_ref & result); + br_status mk_bv_ext_rotate_left(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_bv_ext_rotate_right(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_bv_add(unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_bv_add(expr * arg1, expr * arg2, expr_ref & result) { + expr * args[2] = { arg1, arg2 }; + return mk_bv_add(2, args, result); + } + br_status mk_bv_mul(unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_bv_shl(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_bv_lshr(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_bv_ashr(expr * arg1, expr * arg2, expr_ref & result); + bool is_minus_one_core(expr * arg) const; + bool is_x_minus_one(expr * arg, expr * & x); + br_status mk_bv_sdiv_core(expr * arg1, expr * arg2, bool hi_div0, expr_ref & result); + br_status mk_bv_udiv_core(expr * arg1, expr * arg2, bool hi_div0, expr_ref & result); + br_status mk_bv_srem_core(expr * arg1, expr * arg2, bool hi_div0, expr_ref & result); + br_status mk_bv_urem_core(expr * arg1, expr * arg2, bool hi_div0, expr_ref & result); + br_status mk_bv_smod_core(expr * arg1, expr * arg2, bool hi_div0, expr_ref & result); + br_status mk_bv_sdiv(expr * arg1, expr * arg2, expr_ref & result) { return mk_bv_sdiv_core(arg1, arg2, m_hi_div0, result); } + br_status mk_bv_udiv(expr * arg1, expr * arg2, expr_ref & result) { return mk_bv_udiv_core(arg1, arg2, m_hi_div0, result); } + br_status mk_bv_srem(expr * arg1, expr * arg2, expr_ref & result) { return mk_bv_srem_core(arg1, arg2, m_hi_div0, result); } + br_status mk_bv_urem(expr * arg1, expr * arg2, expr_ref & result) { return mk_bv_urem_core(arg1, arg2, m_hi_div0, result); } + br_status mk_bv_smod(expr * arg1, expr * arg2, expr_ref & result) { return mk_bv_smod_core(arg1, arg2, m_hi_div0, result); } + br_status mk_bv_sdiv_i(expr * arg1, expr * arg2, expr_ref & result) { return mk_bv_sdiv_core(arg1, arg2, true, result); } + br_status mk_bv_udiv_i(expr * arg1, expr * arg2, expr_ref & result) { return mk_bv_udiv_core(arg1, arg2, true, result); } + br_status mk_bv_srem_i(expr * arg1, expr * arg2, expr_ref & result) { return mk_bv_srem_core(arg1, arg2, true, result); } + br_status mk_bv_urem_i(expr * arg1, expr * arg2, expr_ref & result) { return mk_bv_urem_core(arg1, arg2, true, result); } + br_status mk_bv_smod_i(expr * arg1, expr * arg2, expr_ref & result) { return mk_bv_smod_core(arg1, arg2, true, result); } + br_status mk_int2bv(unsigned bv_size, expr * arg, expr_ref & result); + br_status mk_bv2int(expr * arg, expr_ref & result); + br_status mk_bv_redor(expr * arg, expr_ref & result); + br_status mk_bv_redand(expr * arg, expr_ref & result); + br_status mk_bv_comp(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_bit2bool(expr * lhs, expr * rhs, expr_ref & result); + br_status mk_blast_eq_value(expr * lhs, expr * rhs, expr_ref & result); + br_status mk_eq_concat(expr * lhs, expr * rhs, expr_ref & result); + br_status mk_mkbv(unsigned num, expr * const * args, expr_ref & result); + bool is_minus_one_times_t(expr * arg); + void mk_t1_add_t2_eq_c(expr * t1, expr * t2, expr * c, expr_ref & result); + + bool is_concat_split_target(expr * t) const; + + br_status mk_mul_eq(expr * lhs, expr * rhs, expr_ref & result); + + void updt_local_params(params_ref const & p); + +public: + bv_rewriter(ast_manager & m, params_ref const & p = params_ref()): + poly_rewriter(m, p), + m_mk_extract(m_util), + m_autil(m) { + updt_local_params(p); + } + + void updt_params(params_ref const & p); + + static void get_param_descrs(param_descrs & r); + + void set_mkbv2num(bool f) { m_mkbv2num = f; } + + unsigned get_bv_size(expr * t) const {return m_util.get_bv_size(t); } + bool is_numeral(expr * t) const { return m_util.is_numeral(t); } + bool is_numeral(expr * t, numeral & r, unsigned & sz) const { return m_util.is_numeral(t, r, sz); } + bool is_bv(expr * t) const { return m_util.is_bv(t); } + expr * mk_numeral(numeral const & v, unsigned sz) { return m_util.mk_numeral(v, sz); } + expr * mk_numeral(unsigned v, unsigned sz) { return m_util.mk_numeral(numeral(v), sz); } + + br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + void mk_app(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + if (mk_app_core(f, num_args, args, result) == BR_FAILED) + result = m().mk_app(f, num_args, args); + } + + br_status mk_eq_core(expr * lhs, expr * rhs, expr_ref & result); +}; + +#endif diff --git a/lib/bv_simplifier_params.h b/lib/bv_simplifier_params.h new file mode 100644 index 000000000..a6ff749c4 --- /dev/null +++ b/lib/bv_simplifier_params.h @@ -0,0 +1,39 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + bv_simplifier_params.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-10-10. + +Revision History: + +--*/ +#ifndef _BV_SIMPLIFIER_PARAMS_H_ +#define _BV_SIMPLIFIER_PARAMS_H_ + +#include"ini_file.h" + +struct bv_simplifier_params { + bool m_hi_div0; //!< if true, uses the hardware interpretation for div0, mod0, ... if false, div0, mod0, ... are considered uninterpreted. + bool m_bv2int_distribute; //!< if true allows downward propagation of bv2int. + + bv_simplifier_params(): + m_hi_div0(true), + m_bv2int_distribute(true) { + } + void register_params(ini_params & p) { + p.register_bool_param("HI_DIV0", m_hi_div0, "if true, then Z3 uses the usual hardware interpretation for division (rem, mod) by zero. Otherwise, these operations are considered uninterpreted."); + p.register_bool_param("BV2INT_DISTRIBUTE", m_bv2int_distribute, "if true, then int2bv is distributed over arithmetical operators."); + } +}; + +#endif /* _BV_SIMPLIFIER_PARAMS_H_ */ + diff --git a/lib/bv_simplifier_plugin.cpp b/lib/bv_simplifier_plugin.cpp new file mode 100644 index 000000000..2da1f3bab --- /dev/null +++ b/lib/bv_simplifier_plugin.cpp @@ -0,0 +1,2226 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + bv_simplifier_plugin.cpp + +Abstract: + + Simplifier for the bv family. + +Author: + + Leonardo (leonardo) 2008-01-08 + Nikolaj Bjorner (nbjorner) 2008-01-05 + +--*/ +#include"bv_simplifier_plugin.h" +#include"ast_ll_pp.h" +#include"ast_pp.h" +#include"arith_decl_plugin.h" +#include"obj_hashtable.h" +#include"ast_util.h" + +bv_simplifier_plugin::~bv_simplifier_plugin() { + flush_caches(); +} + +bv_simplifier_plugin::bv_simplifier_plugin(ast_manager & m, basic_simplifier_plugin & b, bv_simplifier_params & p): + poly_simplifier_plugin(symbol("bv"), m, OP_BADD, OP_BMUL, OP_BNEG, OP_BSUB, OP_BV_NUM), + m_manager(m), + m_util(m), + m_arith(m), + m_bsimp(b), + m_params(p), + m_zeros(m) { +} + +rational bv_simplifier_plugin::norm(const numeral & n) { + unsigned bv_size = get_bv_size(m_curr_sort); + return norm(n, bv_size, false); +} + + +bool bv_simplifier_plugin::is_numeral(expr * n, rational & val) const { + unsigned bv_size; + return m_util.is_numeral(n, val, bv_size); +} + +expr * bv_simplifier_plugin::get_zero(sort * s) const { + bv_simplifier_plugin * _this = const_cast(this); + unsigned bv_size = _this->get_bv_size(s); + if (bv_size >= m_zeros.size()) + _this->m_zeros.resize(bv_size+1); + if (m_zeros.get(bv_size) == 0) + _this->m_zeros.set(bv_size, _this->m_util.mk_numeral(rational(0), s)); + return m_zeros.get(bv_size); +} + +bool bv_simplifier_plugin::are_numerals(unsigned num_args, expr * const* args, unsigned& bv_size) { + numeral r; + if (num_args == 0) { + return false; + } + for (unsigned i = 0; i < num_args; ++i) { + if (!m_util.is_numeral(args[i], r, bv_size)) { + return false; + } + } + return true; +} + +app * bv_simplifier_plugin::mk_numeral(numeral const & n) { + unsigned bv_size = get_bv_size(m_curr_sort); + return mk_numeral(n, bv_size); +} + +app * bv_simplifier_plugin::mk_numeral(numeral const& n, unsigned bv_size) { + numeral r = mod(n, m_util.power_of_two(bv_size)); + SASSERT(!r.is_neg()); + SASSERT(r < m_util.power_of_two(bv_size)); + return m_util.mk_numeral(r, bv_size); +} + +bool bv_simplifier_plugin::reduce(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + set_reduce_invoked(); + + SASSERT(f->get_family_id() == m_fid); + + bv_op_kind k = static_cast(f->get_decl_kind()); + switch (k) { + case OP_BV_NUM: SASSERT(num_args == 0); result = mk_numeral(f->get_parameter(0).get_rational(), f->get_parameter(1).get_int()); break; + case OP_BIT0: SASSERT(num_args == 0); result = mk_numeral(0, 1); break; + case OP_BIT1: SASSERT(num_args == 0); result = mk_numeral(1, 1); break; + case OP_BADD: SASSERT(num_args > 0); + mk_add(num_args, args, result); + TRACE("bv_add_bug", + for (unsigned i = 0; i < num_args; i++) tout << mk_bounded_pp(args[i], m_manager, 10) << "\n"; + tout << mk_bounded_pp(result, m_manager, 10) << "\n";); + mk_add_concat(result); + break; + case OP_BSUB: SASSERT(num_args > 0); mk_sub(num_args, args, result); break; + case OP_BNEG: SASSERT(num_args == 1); mk_uminus(args[0], result); break; + case OP_BMUL: SASSERT(num_args > 0); mk_mul(num_args, args, result); break; + case OP_ULEQ: if (m_presimp) return false; SASSERT(num_args == 2); mk_ule(args[0], args[1], result); break; + case OP_UGEQ: if (m_presimp) return false; SASSERT(num_args == 2); mk_ule(args[1], args[0], result); break; + case OP_ULT: if (m_presimp) return false; SASSERT(num_args == 2); mk_ult(args[0], args[1], result); break; + case OP_UGT: if (m_presimp) return false; SASSERT(num_args == 2); mk_ult(args[1], args[0], result); break; + case OP_SLEQ: if (m_presimp) return false; SASSERT(num_args == 2); mk_sle(args[0], args[1], result); break; + case OP_SGEQ: if (m_presimp) return false; SASSERT(num_args == 2); mk_sle(args[1], args[0], result); break; + case OP_SLT: if (m_presimp) return false; SASSERT(num_args == 2); mk_slt(args[0], args[1], result); break; + case OP_SGT: if (m_presimp) return false; SASSERT(num_args == 2); mk_slt(args[1], args[0], result); break; + case OP_BAND: SASSERT(num_args > 0); mk_bv_and(num_args, args, result); break; + case OP_BOR: SASSERT(num_args > 0); mk_bv_or(num_args, args, result); break; + case OP_BNOT: SASSERT(num_args == 1); mk_bv_not(args[0], result); break; + case OP_BXOR: SASSERT(num_args > 0); mk_bv_xor(num_args, args, result); break; + case OP_CONCAT: SASSERT(num_args > 0); mk_concat(num_args, args, result); break; + case OP_ZERO_EXT: + SASSERT(num_args == 1); SASSERT(f->get_num_parameters() == 1); + mk_zeroext(f->get_parameter(0).get_int(), args[0], result); + break; + case OP_EXTRACT: + SASSERT(num_args == 1); SASSERT(f->get_num_parameters() == 2); + mk_extract(f->get_parameter(0).get_int(), f->get_parameter(1).get_int(), args[0], result); + break; + case OP_REPEAT: + SASSERT(num_args == 1); SASSERT(f->get_num_parameters() == 1); + mk_repeat(f->get_parameter(0).get_int(), args[0], result); + break; + case OP_BUREM: + SASSERT(num_args == 2); + mk_bv_urem(args[0], args[1], result); + break; + case OP_SIGN_EXT: + SASSERT(num_args == 1); SASSERT(f->get_num_parameters() == 1); + mk_sign_extend(f->get_parameter(0).get_int(), args[0], result); + break; + case OP_BSHL: SASSERT(num_args == 2); mk_bv_shl(args[0], args[1], result); break; + case OP_BLSHR: SASSERT(num_args == 2); mk_bv_lshr(args[0], args[1], result); break; + case OP_INT2BV: SASSERT(num_args == 1); mk_int2bv(args[0], f->get_range(), result); break; + case OP_BV2INT: SASSERT(num_args == 1); mk_bv2int(args[0], f->get_range(), result); break; + case OP_BSDIV: SASSERT(num_args == 2); mk_bv_sdiv(args[0], args[1], result); break; + case OP_BUDIV: SASSERT(num_args == 2); mk_bv_udiv(args[0], args[1], result); break; + case OP_BSREM: SASSERT(num_args == 2); mk_bv_srem(args[0], args[1], result); break; + case OP_BSMOD: SASSERT(num_args == 2); mk_bv_smod(args[0], args[1], result); break; + case OP_BNAND: SASSERT(num_args > 0); mk_bv_nand(num_args, args, result); break; + case OP_BNOR: SASSERT(num_args > 0); mk_bv_nor(num_args, args, result); break; + case OP_BXNOR: SASSERT(num_args > 0); mk_bv_xnor(num_args, args, result); break; + case OP_ROTATE_LEFT: SASSERT(num_args == 1); mk_bv_rotate_left(f, args[0], result); break; + case OP_ROTATE_RIGHT: SASSERT(num_args == 1); mk_bv_rotate_right(f, args[0], result); break; + case OP_EXT_ROTATE_LEFT: SASSERT(num_args == 2); mk_bv_ext_rotate_left(args[0], args[1], result); break; + case OP_EXT_ROTATE_RIGHT: SASSERT(num_args == 2); mk_bv_ext_rotate_right(args[0], args[1], result); break; + case OP_BREDOR: SASSERT(num_args == 1); mk_bv_redor(args[0], result); break; + case OP_BREDAND: SASSERT(num_args == 1); mk_bv_redand(args[0], result); break; + case OP_BCOMP: SASSERT(num_args == 2); mk_bv_comp(args[0], args[1], result); break; + case OP_BASHR: SASSERT(num_args == 2); mk_bv_ashr(args[0], args[1], result); break; + case OP_BSDIV_I: SASSERT(num_args == 2); mk_bv_sdiv_i(args[0], args[1], result); break; + case OP_BUDIV_I: SASSERT(num_args == 2); mk_bv_udiv_i(args[0], args[1], result); break; + case OP_BSREM_I: SASSERT(num_args == 2); mk_bv_srem_i(args[0], args[1], result); break; + case OP_BUREM_I: SASSERT(num_args == 2); mk_bv_urem_i(args[0], args[1], result); break; + case OP_BSMOD_I: SASSERT(num_args == 2); mk_bv_smod_i(args[0], args[1], result); break; + case OP_BSDIV0: + case OP_BUDIV0: + case OP_BSREM0: + case OP_BUREM0: + case OP_BSMOD0: + case OP_BIT2BOOL: + case OP_CARRY: + case OP_XOR3: + case OP_MKBV: + case OP_BUMUL_NO_OVFL: + case OP_BSMUL_NO_OVFL: + case OP_BSMUL_NO_UDFL: + result = m_manager.mk_app(f, num_args, args); + break; + default: + UNREACHABLE(); + break; + } + SASSERT(result.get()); + + TRACE("bv_simplifier", + tout << mk_pp(f, m_manager) << "\n"; + for (unsigned i = 0; i < num_args; ++i) { + tout << mk_pp(args[i], m_manager) << " "; + } + tout << "\n"; + tout << mk_pp(result.get(), m_manager) << "\n"; + ); + + return true; +} + +bool bv_simplifier_plugin::reduce_eq(expr * lhs, expr * rhs, expr_ref & result) { + set_reduce_invoked(); + + if (m_presimp) + return false; + expr_ref tmp(m_manager); + tmp = m_manager.mk_eq(lhs,rhs); + mk_bv_eq(lhs, rhs, result); + return result.get() != tmp.get(); +} + +bool bv_simplifier_plugin::reduce_distinct(unsigned num_args, expr * const * args, expr_ref & result) { + set_reduce_invoked(); + + return false; +} + +void bv_simplifier_plugin::flush_caches() { + TRACE("extract_cache", tout << "flushing extract cache...\n";); + extract_cache::iterator it = m_extract_cache.begin(); + extract_cache::iterator end = m_extract_cache.end(); + for (; it != end; ++it) { + extract_entry & e = (*it).m_key; + m_manager.dec_ref(e.m_arg); + m_manager.dec_ref((*it).m_value); + } + m_extract_cache.reset(); +} + + +inline uint64 bv_simplifier_plugin::to_uint64(const numeral & n, unsigned bv_size) { + SASSERT(bv_size <= 64); + numeral tmp = n; + if (tmp.is_neg()) { + tmp = mod(tmp, m_util.power_of_two(bv_size)); + } + SASSERT(tmp.is_nonneg()); + SASSERT(tmp.is_uint64()); + return tmp.get_uint64(); +} + +#define MK_BV_OP(_oper_,_binop_) \ +rational bv_simplifier_plugin::mk_bv_ ## _oper_(numeral const& a0, numeral const& b0, unsigned sz) { \ + rational r(0), a(a0), b(b0); \ + numeral p64 = m_util.power_of_two(64); \ + numeral mul(1); \ + while (sz > 0) { \ + numeral a1 = a % p64; \ + numeral b1 = b % p64; \ + uint64 u = a1.get_uint64() _binop_ b1.get_uint64(); \ + if (sz < 64) { \ + uint64 mask = shift_left(1ull,(uint64)sz) - 1ull; \ + u = mask & u; \ + } \ + r += mul*rational(u,rational::ui64()); \ + mul *= p64; \ + a = div(a, p64); \ + b = div(b, p64); \ + sz -= (sz<64)?sz:64; \ + } \ + return r; \ +} + +MK_BV_OP(and,&) +MK_BV_OP(or,|) +MK_BV_OP(xor,^) + +rational bv_simplifier_plugin::mk_bv_not(numeral const& a0, unsigned sz) { + rational r(0), a(a0), mul(1); + numeral p64 = m_util.power_of_two(64); + while (sz > 0) { + numeral a1 = a % p64; + uint64 u = ~a1.get_uint64(); + if (sz < 64) { + uint64 mask = shift_left(1ull,(uint64)sz) - 1ull; + u = mask & u; + } + r += mul*rational(u,rational::ui64()); + mul *= p64; + a = div(a, p64); + sz -= (64get_num_args() == 2) { + // + // c <=_u (concat 0 a) <=> c[u:l] = 0 && c[l-1:0] <=_u a + // + app* app = to_app(arg2); + expr * arg2_1 = app->get_arg(0); + expr * arg2_2 = app->get_arg(1); + if (m_util.is_zero(arg2_1)) { + unsigned sz1 = get_bv_size(arg2_1); + unsigned sz2 = get_bv_size(arg2_2); + + expr_ref tmp1(m_manager); + expr_ref tmp2(m_manager); + mk_extract(sz2 + sz1 - 1, sz2, arg1, tmp1); + mk_extract(sz2 - 1, 0, arg1, tmp2); + + expr_ref eq(m_manager); + expr_ref zero(m_manager); + zero = mk_bv0(sz1); + mk_bv_eq(tmp1.get(), zero, eq); + + expr_ref ineq(m_manager); + ineq = m_util.mk_ule(tmp2.get(), arg2_2); + + m_bsimp.mk_and(eq.get(), ineq.get(), result); + return; + } + } + + // + // TODO: + // Others: + // + // k <=_s (concat 0 a) <=> (k[u:l] = 0 && k[l-1:0] <=_u a) || k[u:u] = bv1 + // + // (concat 0 a) <=_s k <=> k[u:u] = bv0 && (k[u:l] != 0 || a <=_u k[l-1:0]) + // + // (concat 0 a) <=_u k <=> k[u:l] != 0 || a <=_u k[l-1:0] + // + + result = m_manager.mk_app(m_fid, k, arg1, arg2); +} + +void bv_simplifier_plugin::mk_extract(unsigned high, unsigned low, expr* arg, expr_ref& result) { + + unsigned arg_sz = get_bv_size(arg); + unsigned sz = high - low + 1; + TRACE("bv_simplifier_plugin", tout << "mk_extract [" << high << ":" << low << "]\n"; + tout << "arg_sz: " << arg_sz << " sz: " << sz << "\n"; + tout << "arg:\n"; + ast_ll_pp(tout, m_manager, arg);); + + if (arg_sz == sz) { + result = arg; + } + else { + mk_extract_core(high, low, arg, result); + } + if (m_extract_cache.size() > (1 << 12)) { + m_extract_cache.reset(); + } + + TRACE("bv_simplifier_plugin", tout << "mk_extract [" << high << ":" << low << "]\n"; + tout << "arg_sz: " << arg_sz << " sz: " << sz << "\n"; + tout << "arg:\n"; + ast_ll_pp(tout, m_manager, arg); + tout << "=====================>\n"; + ast_ll_pp(tout, m_manager, result.get());); +} + + +void bv_simplifier_plugin::cache_extract(unsigned h, unsigned l, expr * arg, expr * result) { + m_manager.inc_ref(arg); + m_manager.inc_ref(result); + m_extract_cache.insert(extract_entry(h, l, arg), result); +} + +expr* bv_simplifier_plugin::get_cached_extract(unsigned h, unsigned l, expr * arg) { + expr * result = 0; + if (m_extract_cache.find(extract_entry(h, l, arg), result)) { + return result; + } + return 0; +} + + +void bv_simplifier_plugin::mk_extract_core(unsigned high, unsigned low, expr * arg, expr_ref& result) { + + if (!lookup_mk_extract(high, low, arg, result)) { + while (!m_extract_args.empty()) { + unsigned low2 = m_lows.back(); + unsigned high2 = m_highs.back(); + expr* arg2 = m_extract_args.back(); + if (try_mk_extract(high2, low2, arg2, result)) { + if (!m_extract_cache.contains(extract_entry(high2, low2, arg2))) { + cache_extract(high2, low2, arg2, result.get()); + } + m_lows.pop_back(); + m_highs.pop_back(); + m_extract_args.pop_back(); + } + } + if (!lookup_mk_extract(high, low, arg, result)) { + UNREACHABLE(); + } + } +} + + +bool bv_simplifier_plugin::lookup_mk_extract(unsigned high, unsigned low, expr * arg, expr_ref& result) { + expr* cached_result = get_cached_extract(high, low, arg); + if (cached_result) { + result = cached_result; + return true; + } + + m_extract_args.push_back(arg); + m_lows.push_back(low); + m_highs.push_back(high); + return false; +} + + +bool bv_simplifier_plugin::try_mk_extract(unsigned high, unsigned low, expr * arg, expr_ref& result) { + + SASSERT(low <= high); + unsigned arg_sz = get_bv_size(arg); + unsigned sz = high - low + 1; + numeral r; + unsigned num_bits; + + if (arg_sz == sz) { + result = arg; + return true; + } + + expr* cached_result = get_cached_extract(high, low, arg); + if (cached_result) { + result = cached_result; + return true; + } + + if (!is_app(arg)) { + result = m_util.mk_extract(high, low, arg); + return true; + } + app* a = to_app(arg); + + if (m_util.is_numeral(a, r, num_bits)) { + if (r.is_neg()) { + r = mod(r, m_util.power_of_two(sz)); + } + SASSERT(r.is_nonneg()); + if (r.is_uint64()) { + uint64 u = r.get_uint64(); + uint64 e = shift_right(u, low) & (shift_left(1ull, sz) - 1ull); + TRACE("mk_extract_bug", tout << u << "[" << high << ":" << low << "] " << e << " (u >> low): " << shift_right(u, low) << " (1ull << sz): " + << shift_left(1ull, sz) << " ((1ull << sz) - 1ull)" << (shift_left(1ull, sz) - 1ull) << "\n";); + result = mk_numeral(numeral(e, numeral::ui64()), sz); + return true; + } + result = mk_numeral(div(r, m_util.power_of_two(low)), sz); + return true; + } + // (extract[high:low] (extract[high2:low2] x)) == (extract[high+low2 : low+low2] x) + else if (is_app_of(a, m_fid, OP_EXTRACT)) { + expr * x = a->get_arg(0); + unsigned low2 = a->get_decl()->get_parameter(1).get_int(); + return lookup_mk_extract(high + low2, low + low2, x, result); + } + // + // (extract[hi:lo] (bvXshr A c:bv[n])) -> (extract[hi+c:lo+c] A) + // if c < n, c <= lo <= hi < n - c + // + else if ((is_app_of(a, m_fid, OP_BASHR) || is_app_of(a, m_fid, OP_BLSHR)) && + is_numeral(a->get_arg(1), r) && r.is_unsigned()) { + unsigned c = r.get_unsigned(); + unsigned bv_size = get_bv_size(a); + if (c < bv_size && c <= low && high < bv_size - c) { + return lookup_mk_extract(high+c, low+c, a->get_arg(0), result); + } + } + // (concat a_0 ... a_{n-1}) + // Remark: the least significant bits are stored in a_{n-1} + else if (is_app_of(a, m_fid, OP_CONCAT)) { + expr_ref_buffer new_args(m_manager); + unsigned i = a->get_num_args(); + // look for first argument + while (i > 0) { + --i; + expr * a_i = a->get_arg(i); + unsigned a_sz = get_bv_size(a_i); + TRACE("extract_bug", tout << "FIRST a_sz: " << a_sz << " high: " << high << " low: " << low << "\n" << + mk_pp(a_i, m_manager) << "\n";); + if (a_sz <= low) { + low -= a_sz; + high -= a_sz; + } + else { + // found first argument + if (a_sz <= high) { + expr_ref new_arg(m_manager); + if (!lookup_mk_extract(a_sz - 1, low, a_i, new_arg)) { + return false; + } + new_args.push_back(new_arg.get()); + unsigned num_consumed_bytes = a_sz - low; + // I have to apply extract[sz - num_consumed_bytes - 1, 0] on the rest of concat + high = (sz - num_consumed_bytes - 1); + break; + } + else { + return lookup_mk_extract(high, low, a_i, result); + } + } + } + TRACE("extract_bug", tout << " high: " << high << " low: " << low << "\n";); + + // look for remaining arguments + while (i > 0) { + --i; + expr * a_i = a->get_arg(i); + unsigned a_sz = get_bv_size(a_i); + TRACE("extract_bug", tout << "SECOND a_sz: " << a_sz << " high: " << high << " " << + mk_pp( a_i, m_manager) << "\n";); + if (a_sz <= high) { + high -= a_sz; + new_args.push_back(a_i); + } + else { + // found last argument + expr_ref new_arg(m_manager); + if (!lookup_mk_extract(high, 0, a_i, new_arg)) { + return false; + } + new_args.push_back(new_arg.get()); + // The arguments in new_args are in reverse order. + ptr_buffer rev_new_args; + unsigned i = new_args.size(); + while (i > 0) { + --i; + rev_new_args.push_back(new_args[i]); + } + mk_concat(rev_new_args.size(), rev_new_args.c_ptr(), result); + return true; + } + } + UNREACHABLE(); + } + else if (is_app_of(a, m_fid, OP_SIGN_EXT)) { + SASSERT(a->get_num_args() == 1); + unsigned bv_size = get_bv_size(a->get_arg(0)); + if (high < bv_size) { + return lookup_mk_extract(high, low, a->get_arg(0), result); + } + } + else if (is_app_of(a, m_fid, OP_BAND) || + is_app_of(a, m_fid, OP_BOR) || + is_app_of(a, m_fid, OP_BXOR) || + is_app_of(a, m_fid, OP_BNOR) || + is_app_of(a, m_fid, OP_BNAND) || + is_app_of(a, m_fid, OP_BNOT) || + (low == 0 && is_app_of(a, m_fid, OP_BADD)) || + (low == 0 && is_app_of(a, m_fid, OP_BMUL)) || + (low == 0 && is_app_of(a, m_fid, OP_BSUB))) { + expr_ref_buffer new_args(m_manager); + bool all_found = true; + for (unsigned i = 0; i < a->get_num_args(); ++i) { + expr_ref new_arg(m_manager); + if (!lookup_mk_extract(high, low, a->get_arg(i), new_arg)) { + all_found = false; + } + new_args.push_back(new_arg.get()); + } + if (!all_found) { + return false; + } + result = m_manager.mk_app(m_fid, a->get_decl_kind(), new_args.size(), new_args.c_ptr()); + return true; + } + else if (m_manager.is_ite(a)) { + expr_ref then_b(m_manager), else_b(m_manager); + bool ok = lookup_mk_extract(high, low, a->get_arg(1), then_b); + ok = lookup_mk_extract(high, low, a->get_arg(2), else_b) && ok; + if (ok) { + m_bsimp.mk_ite(a->get_arg(0), then_b.get(), else_b.get(), result); + } + return ok; + } + result = m_util.mk_extract(high, low, arg); + return true; +} + +/** + \brief Let f be the operator fid:k. Then, this function + store in result the flat args of n. If n is not an f application, then store n in result. + + Example: if n is (f (f a b) (f c (f d e))), then a b c d e are stored in result. +*/ +template +void get_assoc_args(family_id fid, decl_kind k, expr * n, T & result) { + ptr_buffer todo; + todo.push_back(n); + while (!todo.empty()) { + expr * n = todo.back(); + todo.pop_back(); + if (is_app_of(n, fid, k)) { + app * app = to_app(n); + unsigned i = app->get_num_args(); + while (i > 0) { + --i; + todo.push_back(app->get_arg(i)); + } + } + else { + result.push_back(n); + } + } +} + +/** + \brief Similar to get_assoc_args, but the arguments are stored in reverse + other in result. +*/ +template +void get_inv_assoc_args(family_id fid, decl_kind k, expr * n, T & result) { + ptr_buffer todo; + todo.push_back(n); + while (!todo.empty()) { + expr * n = todo.back(); + todo.pop_back(); + if (is_app_of(n, fid, k)) { + app * app = to_app(n); + unsigned num = app->get_num_args(); + for (unsigned i = 0; i < num; i++) + todo.push_back(app->get_arg(i)); + } + else { + result.push_back(n); + } + } +} + +void bv_simplifier_plugin::mk_bv_eq(expr* a1, expr* a2, expr_ref& result) { + + rational val1; + rational val2; + bool is_num1 = is_numeral(a1, val1); + bool is_num2 = is_numeral(a2, val2); + if (is_num1 && is_num2 && val1 != val2) { + result = m_manager.mk_false(); + return; + } + + if (!m_util.is_concat(a1) && !is_num1) { + mk_eq_core(a1, a2, result); + return; + } + if (!m_util.is_concat(a2) && !is_num2) { + mk_eq_core(a1, a2, result); + return; + } + + ptr_buffer args1, args2; + get_inv_assoc_args(m_fid, OP_CONCAT, a1, args1); + get_inv_assoc_args(m_fid, OP_CONCAT, a2, args2); + TRACE("mk_bv_eq_concat", tout << mk_ll_pp(a1, m_manager) << "\n" << mk_ll_pp(a2, m_manager) << "\n"; + tout << "args1:\n"; + for (unsigned i = 0; i < args1.size(); i++) tout << mk_ll_pp(args1[i], m_manager) << "\n"; + tout << "args2:\n"; + for (unsigned i = 0; i < args2.size(); i++) tout << mk_ll_pp(args2[i], m_manager) << "\n";); + + + + expr_ref lhs(m_manager), rhs(m_manager), eq(m_manager); + expr_ref_buffer eqs(m_manager); + unsigned low1 = 0, low2 = 0; + ptr_buffer::iterator it1 = args1.begin(); + ptr_buffer::iterator end1 = args1.end(); + ptr_buffer::iterator it2 = args2.begin(); + ptr_buffer::iterator end2 = args2.end(); + + while (it1 != end1 && it2 != end2) { + SASSERT(it1 != end1); + SASSERT(it2 != end2); + expr * arg1 = *it1; + expr * arg2 = *it2; + TRACE("expr_bv_util", tout << "low1: " << low1 << " low2: " << low2 << "\n"; + ast_ll_pp(tout, m_manager, arg1); + ast_ll_pp(tout, m_manager, arg2);); + unsigned sz1 = get_bv_size(arg1); + unsigned sz2 = get_bv_size(arg2); + SASSERT(low1 < sz1 && low2 < sz2); + unsigned rsz1 = sz1 - low1; + unsigned rsz2 = sz2 - low2; + TRACE("expr_bv_util", tout << "rsz1: " << rsz1 << " rsz2: " << rsz2 << "\n"; + ast_ll_pp(tout, m_manager, arg1); ast_ll_pp(tout, m_manager, arg2);); + + + if (rsz1 == rsz2) { + mk_extract(sz1 - 1, low1, arg1, lhs); + mk_extract(sz2 - 1, low2, arg2, rhs); + low1 = 0; + low2 = 0; + ++it1; + ++it2; + } + else if (rsz1 < rsz2) { + mk_extract(sz1 - 1, low1, arg1, lhs); + mk_extract(rsz1 + low2 - 1, low2, arg2, rhs); + low1 = 0; + low2 += rsz1; + ++it1; + } + else { + mk_extract(rsz2 + low1 - 1, low1, arg1, lhs); + mk_extract(sz2 - 1, low2, arg2, rhs); + low1 += rsz2; + low2 = 0; + ++it2; + } + mk_eq_core(lhs.get(), rhs.get(), eq); + eqs.push_back(eq.get()); + } + m_bsimp.mk_and(eqs.size(), eqs.c_ptr(), result); +} + +void bv_simplifier_plugin::mk_eq_core(expr * arg1, expr * arg2, expr_ref & result) { + TRACE("mk_eq_core", ast_ll_pp(tout, m_manager, arg1 ); ast_ll_pp(tout, m_manager, arg2);); + if (arg1 == arg2) { + result = m_manager.mk_true(); + return; + } + if ((m_util.is_bv_and(arg1) && m_util.is_allone(arg2)) || (m_util.is_bv_or(arg1) && m_util.is_zero(arg2))) { + mk_args_eq_numeral(to_app(arg1), arg2, result); + return; + } + if ((m_util.is_bv_and(arg2) && m_util.is_allone(arg1)) || (m_util.is_bv_or(arg2) && m_util.is_zero(arg1))) { + mk_args_eq_numeral(to_app(arg2), arg1, result); + return; + } + +#if 1 + rational r; + unsigned num_bits = 0; + if (m_util.is_numeral(arg2, r, num_bits)) { + std::swap(arg1, arg2); + } + + if (m_util.is_numeral(arg1, r, num_bits) && + (m_util.is_bv_and(arg2) || m_util.is_bv_or(arg2) || m_util.is_bv_not(arg2))) { + rational two(2); + expr_ref tmp(m_manager); + expr_ref_vector tmps(m_manager); + for (unsigned i = 0; i < num_bits; ++i) { + bool is_neg = (r % two).is_zero(); + bit2bool_simplify(i, arg2, tmp); + if (is_neg) { + expr_ref tmp2(m_manager); + m_bsimp.mk_not(tmp, tmp2); + tmp = tmp2; + } + tmps.push_back(tmp); + r = div(r, two); + } + m_bsimp.mk_and(tmps.size(), tmps.c_ptr(), result); + TRACE("mk_eq_bb", + ast_ll_pp(tout, m_manager, arg1); + ast_ll_pp(tout, m_manager, arg2); + ast_ll_pp(tout, m_manager, result);); + return; + } +#endif + + if (!m_util.is_bv_add(arg1) && !m_util.is_bv_add(arg2) && + !m_util.is_bv_mul(arg1) && !m_util.is_bv_mul(arg2)) { + m_bsimp.mk_eq(arg1, arg2, result); + return; + } + + set_curr_sort(arg1); + expr_ref_vector args1(m_manager); + expr_ref_vector args2(m_manager); + get_assoc_args(m_fid, OP_BADD, arg1, args1); + get_assoc_args(m_fid, OP_BADD, arg2, args2); + TRACE("mk_eq_core", + tout << mk_pp(arg1, m_manager) << "\n" << mk_pp(arg2, m_manager) << "\n"; + tout << args1.size() << " " << args2.size() << "\n";); + + unsigned idx2 = 0; + while (idx2 < args2.size()) { + expr * m2 = args2.get(idx2); + unsigned sz1 = args1.size(); + unsigned idx1 = 0; + for (; idx1 < sz1; ++idx1) { + expr * m1 = args1.get(idx1); + if (eq_monomials_modulo_k(m1, m2)) { + expr_ref tmp(m_manager); + if (merge_monomials(true, m1, m2, tmp)) { + args1.set(idx1, tmp.get()); + } + else { + // the monomial cancelled each other. + args1.erase(idx1); + } + break; + } + } + if (idx1 == sz1) { + ++idx2; + } + else { + args2.erase(idx2); + } + } + + expr_ref lhs(m_manager); + expr_ref rhs(m_manager); + mk_sum_of_monomials(args1, lhs); + mk_sum_of_monomials(args2, rhs); + m_bsimp.mk_eq(lhs.get(), rhs.get(), result); +} + +void bv_simplifier_plugin::mk_args_eq_numeral(app * app, expr * n, expr_ref & result) { + expr_ref_buffer eqs(m_manager); + expr_ref eq(m_manager); + unsigned num = app->get_num_args(); + for (unsigned i = 0; i < num; i++) { + mk_bv_eq(app->get_arg(i), n, eq); + eqs.push_back(eq.get()); + } + m_bsimp.mk_and(eqs.size(), eqs.c_ptr(), result); +} + +void bv_simplifier_plugin::mk_concat(unsigned num_args, expr * const * args, expr_ref & result) { + TRACE("bv_simplifier_plugin", tout << "mk_concat:\n"; + for (unsigned i = 0; i < num_args; i++) ast_ll_pp(tout, m_manager, args[i]);); + unsigned shift = 0; + numeral val(0), arg_val; + for (unsigned i = num_args; i > 0; ) { + --i; + expr * arg = args[i]; + if (is_numeral(arg, arg_val)) { + arg_val *= m_util.power_of_two(shift); + val += arg_val; + shift += get_bv_size(arg); + TRACE("bv_simplifier_plugin", + tout << "val: " << val << " arg_val: " << arg_val << " shift: " << shift << "\n";); + } + else { + // one of the arguments is not a number + result = m_manager.mk_app(m_fid, OP_CONCAT, num_args, args); + return; + } + } + + // all arguments are numerals + result = mk_numeral(val, shift); +} + +void bv_simplifier_plugin::mk_bv_and(unsigned num_args, expr * const* args, expr_ref & result) { + ptr_buffer flat_args; + for (unsigned i = 0; i < num_args; ++i) { + flat_args.push_back(args[i]); + } + // expr_lt_proc is a total order on expressions. + std::sort(flat_args.begin(), flat_args.end(), expr_lt_proc(m_fid, OP_BNOT)); + SASSERT(num_args > 0); + + unsigned bv_size = get_bv_size(args[0]); + + numeral allone = mk_allone(bv_size); + numeral val; + + uint64 unit = bv_size <= 64 ? to_uint64(numeral(-1), bv_size) : 0; + numeral n_unit(allone); + + expr * prev = 0; + ptr_buffer::iterator it = flat_args.begin(); + ptr_buffer::iterator it2 = it; + ptr_buffer::iterator end = flat_args.end(); + for (; it != end; ++it) { + expr* n = *it; + if (prev && + ((is_app_of(n, m_fid, OP_BNOT) && to_app(n)->get_arg(0) == prev) || + (is_app_of(prev, m_fid, OP_BNOT) && to_app(prev)->get_arg(0) == n))) { + result = mk_bv0(bv_size); + return; + } + else if (bv_size <= 64 && is_numeral(n, val)) { + unit &= to_uint64(val, bv_size); + if (unit == 0) { + result = mk_bv0(bv_size); + return; + } + } + else if (bv_size > 64 && is_numeral(n, val)) { + n_unit = mk_bv_and(val, n_unit, bv_size); + if (n_unit.is_zero()) { + result = mk_bv0(bv_size); + return; + } + } + else if (!prev || prev != n) { + *it2 = n; + prev = *it2; + ++it2; + } + } + + if (bv_size <= 64) { + n_unit = numeral(unit, numeral::ui64()); + } + + flat_args.set_end(it2); + if (n_unit != allone) { + flat_args.push_back(mk_numeral(n_unit, bv_size)); + } + + unsigned sz = flat_args.size(); + switch(sz) { + case 0: + result = mk_numeral(n_unit, bv_size); + break; + case 1: + result = flat_args.back(); + break; + default: + result = mk_list_assoc_app(m_manager, m_fid, OP_BAND, sz, flat_args.c_ptr()); + break; + } +} + +void bv_simplifier_plugin::mk_bv_or(unsigned num_args, expr * const* args, expr_ref & result) { +#if 0 + // Transformations for SAGE + // (bvor (concat 0 x) (concat y 0)) ==> (concat y x) + // (bvor (concat x 0) (concat 0 y)) ==> (concat x y) + if (num_args == 2 && + m_util.is_concat(args[0]) && + m_util.is_concat(args[1]) && + to_app(args[0])->get_num_args() == 2 && + to_app(args[1])->get_num_args() == 2) { + expr * x1 = to_app(args[0])->get_arg(0); + expr * x2 = to_app(args[0])->get_arg(1); + expr * y1 = to_app(args[1])->get_arg(0); + expr * y2 = to_app(args[1])->get_arg(1); + if (get_bv_size(x1) == get_bv_size(y1) && + get_bv_size(x2) == get_bv_size(y2)) { + if (m_util.is_zero(x1) && m_util.is_zero(y2)) { + // (bvor (concat 0 x) (concat y 0)) ==> (concat y x) + mk_concat(y1, x2, result); + return; + } + if (m_util.is_zero(x2) && m_util.is_zero(y1)) { + // (bvor (concat x 0) (concat 0 y)) ==> (concat x y) + mk_concat(x1, y2, result); + return; + } + } + } + // Investigate why it did not work. +#endif + + ptr_buffer flat_args; + for (unsigned i = 0; i < num_args; ++i) { + flat_args.push_back(args[i]); + } + std::sort(flat_args.begin(), flat_args.end(), expr_lt_proc(m_fid, OP_BNOT)); + SASSERT(num_args > 0); + + unsigned bv_size = get_bv_size(args[0]), sz; + numeral allone = mk_allone(bv_size); + numeral val; + + uint64 unit = 0; + numeral n_unit(0); + + expr * prev = 0; + ptr_buffer::iterator it = flat_args.begin(); + ptr_buffer::iterator it2 = it; + ptr_buffer::iterator end = flat_args.end(); + for (; it != end; ++it) { + expr* n = *it; + if (prev && + ((is_app_of(n, m_fid, OP_BNOT) && to_app(n)->get_arg(0) == prev) || + (is_app_of(prev, m_fid, OP_BNOT) && to_app(prev)->get_arg(0) == n))) { + result = mk_numeral(allone, bv_size); + return; + } + else if (bv_size <= 64 && is_numeral(n, val)) { + unit |= to_uint64(val, bv_size); + } + else if (bv_size > 64 && is_numeral(n, val)) { + n_unit = mk_bv_or(val, n_unit, bv_size); + } + else if (!prev || prev != n) { + *it2 = n; + prev = *it2; + ++it2; + } + } + + if (bv_size <= 64) { + n_unit = numeral(unit, numeral::ui64()); + } + + if (allone == n_unit) { + result = mk_numeral(allone, bv_size); + return; + } + + flat_args.set_end(it2); + if (!n_unit.is_zero()) { + flat_args.push_back(mk_numeral(n_unit, bv_size)); + } + + sz = flat_args.size(); + switch(sz) { + case 0: + result = mk_numeral(n_unit, bv_size); + break; + case 1: + result = flat_args.back(); + break; + default: + result = mk_list_assoc_app(m_manager, m_fid, OP_BOR, sz, flat_args.c_ptr()); + break; + } +} + +void bv_simplifier_plugin::mk_bv_xor(unsigned num_args, expr * const * args, expr_ref & result) { + ptr_buffer flat_args; + for (unsigned i = 0; i < num_args; ++i) { + flat_args.push_back(args[i]); + } + std::sort(flat_args.begin(), flat_args.end(), expr_lt_proc()); + SASSERT(num_args > 0); + + unsigned bv_size = get_bv_size(args[0]); + numeral val; + + uint64 unit = 0; + numeral n_unit(0); + + expr * prev = 0; + ptr_buffer::iterator it = flat_args.begin(); + ptr_buffer::iterator it2 = it; + ptr_buffer::iterator end = flat_args.end(); + for (; it != end; ++it) { + if (bv_size <= 64 && is_numeral(*it, val)) { + uint64 u = to_uint64(val, bv_size); + unit = u ^ unit; + } + else if (bv_size > 64 && is_numeral(*it, val)) { + n_unit = mk_bv_xor(n_unit, val, bv_size); + } + else if (prev != 0 && prev == *it) { + --it2; // remove prev + prev = 0; + } + else { + *it2 = *it; + prev = *it2; + ++it2; + } + } + flat_args.set_end(it2); + + if (bv_size <= 64) { + n_unit = numeral(numeral(unit,numeral::ui64())); + } + + if (!n_unit.is_zero()) { + flat_args.push_back(mk_numeral(n_unit, bv_size)); + } + + unsigned sz = flat_args.size(); + switch(sz) { + case 0: + result = mk_numeral(n_unit, bv_size); + break; + case 1: + result = flat_args.back(); + break; + default: + result = mk_list_assoc_app(m_manager, m_fid, OP_BXOR, flat_args.size(), flat_args.c_ptr()); + break; + } +} + +void bv_simplifier_plugin::mk_bv_not(expr * arg, expr_ref & result) { + numeral val; + unsigned bv_size; + if (m_util.is_numeral(arg, val, bv_size)) { + if (bv_size <= 64) { + uint64 l = bv_size; + uint64 mask = shift_left(1ull,l) - 1ull; + uint64 u = val.get_uint64(); + u = mask & (~u); + result = mk_numeral(numeral(u, numeral::ui64()), bv_size); + TRACE("bv_not_bug", + tout << l << " " << mask << " " << u << "\n"; + tout << mk_pp(arg, m_manager) << "\n" << mk_pp(result, m_manager) << "\n";); + } + else { + numeral r = mk_bv_not(val, bv_size); + result = mk_numeral(r, bv_size); + TRACE("bv_not_bug", + tout << mk_pp(arg, m_manager) << "\n" << mk_pp(result, m_manager) << "\n";); + } + } + else if (is_app_of(arg, m_fid, OP_BNOT)) { + result = to_app(arg)->get_arg(0); + } + else { + result = m_manager.mk_app(m_fid, OP_BNOT, arg); + } +} + +void bv_simplifier_plugin::mk_zeroext(unsigned n, expr * arg, expr_ref & result) { + if (n == 0) { + result = arg; + } + else { + expr_ref zero(m_manager); + zero = mk_bv0(n); + mk_concat(zero.get(), arg, result); + } +} + +void bv_simplifier_plugin::mk_repeat(unsigned n, expr * arg, expr_ref & result) { + ptr_buffer args; + for (unsigned i = 0; i < n; i++) { + args.push_back(arg); + } + mk_concat(args.size(), args.c_ptr(), result); +} + +bool bv_simplifier_plugin::is_minus_one_core(expr * arg) const { + numeral r; + unsigned bv_size; + if (m_util.is_numeral(arg, r, bv_size)) { + numeral minus_one(-1); + minus_one = mod(minus_one, m_util.power_of_two(bv_size)); + return r == minus_one; + } + return false; +} + +bool bv_simplifier_plugin::is_x_minus_one(expr * arg, expr * & x) { + if (is_add(arg) && to_app(arg)->get_num_args() == 2) { + if (is_minus_one_core(to_app(arg)->get_arg(0))) { + x = to_app(arg)->get_arg(1); + return true; + } + if (is_minus_one_core(to_app(arg)->get_arg(1))) { + x = to_app(arg)->get_arg(0); + return true; + } + } + return false; +} + +void bv_simplifier_plugin::mk_bv_urem(expr * arg1, expr * arg2, expr_ref & result) { + numeral r1, r2; + unsigned bv_size; + bool is_num1 = m_util.is_numeral(arg1, r1, bv_size); + bool is_num2 = m_util.is_numeral(arg2, r2, bv_size); + bv_size = get_bv_size(arg1); + + if (is_num2 && r2.is_zero() && !m_params.m_hi_div0) { + result = m_manager.mk_app(m_fid, OP_BUREM0, arg1); + return; + } + + if (is_num1 && is_num2 && !r2.is_zero()) { + SASSERT(r1.is_nonneg() && r2.is_pos()); + r1 %= r2; + result = mk_numeral(r1, bv_size); + return; + } + + if (!m_params.m_hi_div0) { + // TODO: implement the optimization in this branch for the case the hardware interpretation is used for (x urem 0) + // urem(0, x) ==> ite(x = 0, urem0(x), 0) + if (is_num1 && r1.is_zero()) { + expr * zero = arg1; + expr_ref urem0(m_manager), eq0(m_manager); + urem0 = m_manager.mk_app(m_fid, OP_BUREM0, 1, &zero); + m_bsimp.mk_eq(arg2, zero, eq0); + m_bsimp.mk_ite(eq0.get(), urem0.get(), zero, result); + TRACE("urem", + tout << "urem:\n"; + ast_ll_pp(tout, m_manager, arg1); ast_ll_pp(tout, m_manager, arg2); + tout << "result:\n"; ast_ll_pp(tout, m_manager, result.get());); + return; + } + + // urem(x - 1, x) ==> ite(x = 0, urem0(x-1), x - 1) ==> ite(x = 0, urem0(-1), x - 1) + expr * x; + if (is_x_minus_one(arg1, x) && x == arg2) { + expr * x_minus_1 = arg1; + expr_ref zero(m_manager); + zero = mk_bv0(bv_size); + expr_ref minus_one(m_manager), urem0(m_manager), eq0(m_manager); + minus_one = mk_numeral(numeral::minus_one(), bv_size); + expr * minus_1 = minus_one.get(); + urem0 = m_manager.mk_app(m_fid, OP_BUREM0, 1, &minus_1); + m_bsimp.mk_eq(arg2, zero.get(), eq0); + m_bsimp.mk_ite(eq0.get(), urem0.get(), x_minus_1, result); + TRACE("urem", + tout << "urem:\n"; + ast_ll_pp(tout, m_manager, arg1); ast_ll_pp(tout, m_manager, arg2); + tout << "result:\n"; ast_ll_pp(tout, m_manager, result.get());); + return; + } + } + + if (is_num2 || m_params.m_hi_div0) { + result = m_manager.mk_app(m_fid, OP_BUREM_I, arg1, arg2); + } + else { + bv_size = get_bv_size(arg2); + result = m_manager.mk_ite(m_manager.mk_eq(arg2, mk_numeral(0, bv_size)), + m_manager.mk_app(m_fid, OP_BUREM0, arg1), + m_manager.mk_app(m_fid, OP_BUREM_I, arg1, arg2)); + } +} + +void bv_simplifier_plugin::mk_sign_extend(unsigned n, expr * arg, expr_ref & result) { + numeral r; + unsigned bv_size; + if (m_util.is_numeral(arg, r, bv_size)) { + unsigned result_bv_size = bv_size + n; + r = norm(r, bv_size, true); + r = mod(r, m_util.power_of_two(result_bv_size)); + result = mk_numeral(r, result_bv_size); + TRACE("mk_sign_extend", tout << "n: " << n << "\n"; + ast_ll_pp(tout, m_manager, arg); tout << "====>\n"; + ast_ll_pp(tout, m_manager, result.get());); + return; + } + parameter param(n); + result = m_manager.mk_app(m_fid, OP_SIGN_EXT, 1, ¶m, 1, &arg); +} + +/** + Implement the following reductions + + (bvashr (bvashr a n1) n2) ==> (bvashr a (+ n1 n2)) + (bvlshr (bvlshr a n1) n2) ==> (bvlshr a (+ n1 n2)) + (bvshl (bvshl a n1) n2) ==> (bvshl a (+ n1 n2)) + when n1 and n2 are numerals. + Remark if (+ n1 n2) is greater than bv_size, we set (+ n1 n2) to bv_size + + Return true if the transformation was applied and the result stored in 'result'. + Return false otherwise. +*/ +bool bv_simplifier_plugin::shift_shift(bv_op_kind k, expr* arg1, expr* arg2, expr_ref& result) { + SASSERT(k == OP_BASHR || k == OP_BSHL || k == OP_BLSHR); + if (!is_app_of(arg1, m_fid, k)) + return false; + expr * a = to_app(arg1)->get_arg(0); + expr * n1 = to_app(arg1)->get_arg(1); + expr * n2 = arg2; + numeral r1, r2; + unsigned bv_size = UINT_MAX; + bool is_num1 = m_util.is_numeral(n1, r1, bv_size); + bool is_num2 = m_util.is_numeral(n2, r2, bv_size); + if (!is_num1 || !is_num2) + return false; + SASSERT(bv_size != UINT_MAX); + numeral r = r1 + r2; + if (r > numeral(bv_size)) + r = numeral(bv_size); + switch (k) { + case OP_BASHR: + mk_bv_ashr(a, m_util.mk_numeral(r, bv_size), result); + break; + case OP_BLSHR: + mk_bv_lshr(a, m_util.mk_numeral(r, bv_size), result); + break; + default: + SASSERT(k == OP_BSHL); + mk_bv_shl(a, m_util.mk_numeral(r, bv_size), result); + break; + } + return true; +} + +void bv_simplifier_plugin::mk_bv_shl(expr * arg1, expr * arg2, expr_ref & result) { + // x << 0 == x + numeral r1, r2; + unsigned bv_size = get_bv_size(arg1); + bool is_num1 = is_numeral(arg1, r1); + bool is_num2 = is_numeral(arg2, r2); + + if (is_num2 && r2.is_zero()) { + result = arg1; + } + else if (is_num2 && r2 >= rational(bv_size)) { + result = mk_numeral(0, bv_size); + } + else if (is_num2 && is_num1 && bv_size <= 64) { + SASSERT(r1.is_uint64() && r2.is_uint64()); + SASSERT(r2.get_uint64() < bv_size); + + uint64 r = shift_left(r1.get_uint64(), r2.get_uint64()); + result = mk_numeral(r, bv_size); + } + else if (is_num1 && is_num2) { + SASSERT(r2 < rational(bv_size)); + SASSERT(r2.is_unsigned()); + result = mk_numeral(r1 * m_util.power_of_two(r2.get_unsigned()), bv_size); + } + + // + // (bvshl x k) -> (concat (extract [n-1-k:0] x) bv0:k) + // + else if (is_num2 && r2.is_pos() && r2 < numeral(bv_size)) { + SASSERT(r2.is_unsigned()); + unsigned r = r2.get_unsigned(); + expr_ref tmp1(m_manager); + mk_extract(bv_size - r - 1, 0, arg1, tmp1); + expr_ref zero(m_manager); + zero = mk_bv0(r); + expr* args[2] = { tmp1.get(), zero.get() }; + mk_concat(2, args, result); + } + else if (shift_shift(OP_BSHL, arg1, arg2, result)) { + // done + } + else { + result = m_manager.mk_app(m_fid, OP_BSHL, arg1, arg2); + } + TRACE("mk_bv_shl", + tout << mk_pp(arg1, m_manager) << " << " + << mk_pp(arg2, m_manager) << " = " + << mk_pp(result.get(), m_manager) << "\n";); +} + +void bv_simplifier_plugin::mk_bv_lshr(expr * arg1, expr * arg2, expr_ref & result) { + // x >> 0 == x + numeral r1, r2; + unsigned bv_size = get_bv_size(arg1); + bool is_num1 = is_numeral(arg1, r1); + bool is_num2 = is_numeral(arg2, r2); + + if (is_num2 && r2.is_zero()) { + result = arg1; + } + else if (is_num2 && r2 >= rational(bv_size)) { + result = mk_numeral(rational(0), bv_size); + } + else if (is_num1 && is_num2 && bv_size <= 64) { + SASSERT(r1.is_uint64()); + SASSERT(r2.is_uint64()); + uint64 r = shift_right(r1.get_uint64(), r2.get_uint64()); + result = mk_numeral(r, bv_size); + } + else if (is_num1 && is_num2) { + SASSERT(r2.is_unsigned()); + unsigned sh = r2.get_unsigned(); + r1 = div(r1, m_util.power_of_two(sh)); + result = mk_numeral(r1, bv_size); + } + // + // (bvlshr x k) -> (concat bv0:k (extract [n-1:k] x)) + // + else if (is_num2 && r2.is_pos() && r2 < numeral(bv_size)) { + SASSERT(r2.is_unsigned()); + unsigned r = r2.get_unsigned(); + expr_ref tmp1(m_manager); + mk_extract(bv_size - 1, r, arg1, tmp1); + expr_ref zero(m_manager); + zero = mk_bv0(r); + expr* args[2] = { zero.get(), tmp1.get() }; + mk_concat(2, args, result); + } + else if (shift_shift(OP_BLSHR, arg1, arg2, result)) { + // done + } + else { + result = m_manager.mk_app(m_fid, OP_BLSHR, arg1, arg2); + } + TRACE("mk_bv_lshr", tout << mk_pp(arg1, m_manager) << " >> " << + mk_pp(arg2, m_manager) << " = " << mk_pp(result.get(), m_manager) << "\n";); + +} + + +void bv_simplifier_plugin::mk_int2bv(expr * arg, sort* range, expr_ref & result) { + numeral val; + bool is_int; + unsigned bv_size = get_bv_size(range); + + if (m_arith.is_numeral(arg, val, is_int)) { + result = mk_numeral(val, bv_size); + } + // (int2bv (bv2int x)) == x + else if (is_app_of(arg, m_fid, OP_BV2INT) && bv_size == get_bv_size(to_app(arg)->get_arg(0))) { + result = to_app(arg)->get_arg(0); + } + else { + parameter parameter(bv_size); + result = m_manager.mk_app(m_fid, OP_INT2BV, 1, ¶meter, 1, &arg); + SASSERT(result.get()); + } +} + +void bv_simplifier_plugin::mk_bv2int(expr * arg, sort* range, expr_ref & result) { + if (!m_params.m_bv2int_distribute) { + parameter parameter(range); + result = m_manager.mk_app(m_fid, OP_BV2INT, 1, ¶meter, 1, &arg); + return; + } + numeral v; + if (is_numeral(arg, v)) { + result = m_arith.mk_numeral(v, true); + } + else if (is_mul_no_overflow(arg)) { + expr_ref tmp1(m_manager), tmp2(m_manager); + mk_bv2int(to_app(arg)->get_arg(0), range, tmp1); + mk_bv2int(to_app(arg)->get_arg(1), range, tmp2); + result = m_arith.mk_mul(tmp1, tmp2); + } + else if (is_add_no_overflow(arg)) { + expr_ref tmp1(m_manager), tmp2(m_manager); + mk_bv2int(to_app(arg)->get_arg(0), range, tmp1); + mk_bv2int(to_app(arg)->get_arg(1), range, tmp2); + result = m_arith.mk_add(tmp1, tmp2); + } + // commented out to reproduce bug in reduction of int2bv/bv2int + else if (m_util.is_concat(arg)) { + expr_ref tmp1(m_manager), tmp2(m_manager); + unsigned sz2 = get_bv_size(to_app(arg)->get_arg(1)); + mk_bv2int(to_app(arg)->get_arg(0), range, tmp1); + mk_bv2int(to_app(arg)->get_arg(1), range, tmp2); + tmp1 = m_arith.mk_mul(m_arith.mk_numeral(power(numeral(2), sz2), true), tmp1); + result = m_arith.mk_add(tmp1, tmp2); + } + else { + parameter parameter(range); + result = m_manager.mk_app(m_fid, OP_BV2INT, 1, ¶meter, 1, &arg); + } + SASSERT(m_arith.is_int(m_manager.get_sort(result.get()))); +} + +unsigned bv_simplifier_plugin::num_leading_zero_bits(expr* e) { + numeral v; + unsigned sz = get_bv_size(e); + if (is_numeral(e, v)) { + while (v.is_pos()) { + SASSERT(sz > 0); + --sz; + v = div(v, numeral(2)); + } + return sz; + } + else if (m_util.is_concat(e)) { + app* a = to_app(e); + unsigned sz1 = get_bv_size(a->get_arg(0)); + unsigned nb1 = num_leading_zero_bits(a->get_arg(0)); + if (sz1 == nb1) { + nb1 += num_leading_zero_bits(a->get_arg(1)); + } + return nb1; + } + return 0; +} + +bool bv_simplifier_plugin::is_mul_no_overflow(expr* e) { + if (!is_mul(e)) { + return false; + } + expr* e1 = to_app(e)->get_arg(0); + expr* e2 = to_app(e)->get_arg(1); + unsigned sz = get_bv_size(e1); + unsigned nb1 = num_leading_zero_bits(e1); + unsigned nb2 = num_leading_zero_bits(e2); + return nb1 + nb2 >= sz; +} + +bool bv_simplifier_plugin::is_add_no_overflow(expr* e) { + if (!is_add(e)) { + return false; + } + expr* e1 = to_app(e)->get_arg(0); + expr* e2 = to_app(e)->get_arg(1); + unsigned nb1 = num_leading_zero_bits(e1); + unsigned nb2 = num_leading_zero_bits(e2); + return nb1 > 0 && nb2 > 0; +} + + + +// Macro for generating mk_bv_sdiv_i, mk_bv_udiv_i, mk_bv_srem_i, mk_bv_urem_i and mk_bv_smod_i. +// These are essentially evaluators for the arg1 and arg2 are numerals. +// Q: Why do we need them? +// A: A constant may be eliminated using substitution. Its value is computed using the evaluator. +// Example: Suppose we have the top-level atom (= x (bvsrem_i a b)), and x is eliminated. +#define MK_FIXED_DIV_I(NAME, OP) \ +void bv_simplifier_plugin::NAME##_i(expr * arg1, expr * arg2, expr_ref & result) { \ + numeral r1, r2; \ + unsigned bv_size; \ + bool is_num1 = m_util.is_numeral(arg1, r1, bv_size); \ + bool is_num2 = m_util.is_numeral(arg2, r2, bv_size); \ + if (is_num1 && is_num2 && !r2.is_zero()) { \ + NAME(arg1, arg2, result); \ + } \ + else { \ + result = m_manager.mk_app(m_fid, OP, arg1, arg2); \ + } \ +} + +MK_FIXED_DIV_I(mk_bv_sdiv, OP_BSDIV_I) +MK_FIXED_DIV_I(mk_bv_udiv, OP_BUDIV_I) +MK_FIXED_DIV_I(mk_bv_srem, OP_BSREM_I) +MK_FIXED_DIV_I(mk_bv_urem, OP_BUREM_I) +MK_FIXED_DIV_I(mk_bv_smod, OP_BSMOD_I) + +void bv_simplifier_plugin::mk_bv_sdiv(expr* arg1, expr* arg2, expr_ref& result) { + numeral r1, r2; + unsigned bv_size; + bool is_num1 = m_util.is_numeral(arg1, r1, bv_size); + bool is_num2 = m_util.is_numeral(arg2, r2, bv_size); + + if (is_num2 && r2.is_zero() && !m_params.m_hi_div0) { + result = m_manager.mk_app(m_fid, OP_BSDIV0, arg1); + } + else if (is_num1 && is_num2 && !r2.is_zero()) { + r1 = norm(r1, bv_size, true); + r2 = norm(r2, bv_size, true); + result = mk_numeral(machine_div(r1, r2), bv_size); + } + else if (is_num2 || m_params.m_hi_div0) { + result = m_manager.mk_app(m_fid, OP_BSDIV_I, arg1, arg2); + } + else { + bv_size = get_bv_size(arg2); + result = m_manager.mk_ite(m_manager.mk_eq(arg2, mk_numeral(0, bv_size)), + m_manager.mk_app(m_fid, OP_BSDIV0, arg1), + m_manager.mk_app(m_fid, OP_BSDIV_I, arg1, arg2)); + } +} + +void bv_simplifier_plugin::mk_bv_udiv(expr* arg1, expr* arg2, expr_ref& result) { + numeral r1, r2; + unsigned bv_size; + bool is_num1 = m_util.is_numeral(arg1, r1, bv_size); + bool is_num2 = m_util.is_numeral(arg2, r2, bv_size); + + if (is_num2 && r2.is_zero() && !m_params.m_hi_div0) { + result = m_manager.mk_app(m_fid, OP_BUDIV0, arg1); + } + else if (is_num1 && is_num2 && !r2.is_zero()) { + SASSERT(r1.is_nonneg()); + SASSERT(r2.is_nonneg()); + result = mk_numeral(machine_div(r1, r2), bv_size); + } + else if (is_num2 || m_params.m_hi_div0) { + result = m_manager.mk_app(m_fid, OP_BUDIV_I, arg1, arg2); + } + else { + bv_size = get_bv_size(arg2); + result = m_manager.mk_ite(m_manager.mk_eq(arg2, mk_numeral(0, bv_size)), + m_manager.mk_app(m_fid, OP_BUDIV0, arg1), + m_manager.mk_app(m_fid, OP_BUDIV_I, arg1, arg2)); + } +} + +void bv_simplifier_plugin::mk_bv_srem(expr* arg1, expr* arg2, expr_ref& result) { + numeral r1, r2; + unsigned bv_size; + bool is_num1 = m_util.is_numeral(arg1, r1, bv_size); + bool is_num2 = m_util.is_numeral(arg2, r2, bv_size); + + if (is_num2 && r2.is_zero() && !m_params.m_hi_div0) { + result = m_manager.mk_app(m_fid, OP_BSREM0, arg1); + } + else if (is_num1 && is_num2 && !r2.is_zero()) { + r1 = norm(r1, bv_size, true); + r2 = norm(r2, bv_size, true); + result = mk_numeral(r1 % r2, bv_size); + } + else if (is_num2 || m_params.m_hi_div0) { + result = m_manager.mk_app(m_fid, OP_BSREM_I, arg1, arg2); + } + else { + bv_size = get_bv_size(arg2); + result = m_manager.mk_ite(m_manager.mk_eq(arg2, mk_numeral(0, bv_size)), + m_manager.mk_app(m_fid, OP_BSREM0, arg1), + m_manager.mk_app(m_fid, OP_BSREM_I, arg1, arg2)); + } +} + +void bv_simplifier_plugin::mk_bv_smod(expr* arg1, expr* arg2, expr_ref& result) { + numeral r1, r2; + unsigned bv_size; + bool is_num1 = m_util.is_numeral(arg1, r1, bv_size); + bool is_num2 = m_util.is_numeral(arg2, r2, bv_size); + + if (is_num1) + r1 = m_util.norm(r1, bv_size, true); + if (is_num2) + r2 = m_util.norm(r2, bv_size, true); + + TRACE("bv_simplifier", + tout << mk_pp(arg1, m_manager) << " smod " << mk_pp(arg2, m_manager) << "\n"; + ); + + + if (is_num2 && r2.is_zero()) { + if (!m_params.m_hi_div0) + result = m_manager.mk_app(m_fid, OP_BSMOD0, arg1); + else + result = arg1; + } + else if (is_num1 && is_num2) { + SASSERT(!r2.is_zero()); + numeral abs_r1 = m_util.norm(abs(r1), bv_size); + numeral abs_r2 = m_util.norm(abs(r2), bv_size); + numeral u = m_util.norm(abs_r1 % abs_r2, bv_size); + numeral r; + if (u.is_zero()) + r = u; + else if (r1.is_pos() && r2.is_pos()) + r = u; + else if (r1.is_neg() && r2.is_pos()) + r = m_util.norm(-u + r2, bv_size); + else if (r1.is_pos() && r2.is_neg()) + r = m_util.norm(u + r2, bv_size); + else + r = m_util.norm(-u, bv_size); + result = mk_numeral(r, bv_size); + } + else if (is_num2 || m_params.m_hi_div0) { + result = m_manager.mk_app(m_fid, OP_BSMOD_I, arg1, arg2); + } + else { + bv_size = get_bv_size(arg2); + result = m_manager.mk_ite(m_manager.mk_eq(arg2, mk_numeral(0, bv_size)), + m_manager.mk_app(m_fid, OP_BSMOD0, arg1), + m_manager.mk_app(m_fid, OP_BSMOD_I, arg1, arg2)); + } +} + +uint64 bv_simplifier_plugin::n64(expr* e) { + numeral r; + unsigned bv_size; + if (m_util.is_numeral(e, r, bv_size) && bv_size <= 64) { + return r.get_uint64(); + } + UNREACHABLE(); + return 0; +} + +rational bv_simplifier_plugin::num(expr* e) { + numeral r; + unsigned bv_size; + if (!m_util.is_numeral(e, r, bv_size)) { + UNREACHABLE(); + } + return r; + +} + +void bv_simplifier_plugin::mk_bv_nand(unsigned num_args, expr* const* args, expr_ref& result) { + unsigned bv_size; + if (are_numerals(num_args, args, bv_size)) { + if (bv_size <= 64) { + uint64 r = n64(args[0]); + for (unsigned i = 1; i < num_args; i++) { + r &= n64(args[i]); + } + result = mk_numeral(~r, bv_size); + } + else { + numeral r = num(args[0]); + for (unsigned i = 1; i < num_args; i++) { + r = mk_bv_and(r, num(args[i]), bv_size); + } + result = mk_numeral(mk_bv_not(r, bv_size), bv_size); + } + } + else { + result = m_manager.mk_app(m_fid, OP_BNAND, num_args, args); + } +} + +void bv_simplifier_plugin::mk_bv_nor(unsigned num_args, expr* const* args, expr_ref& result) { + unsigned bv_size; + if (are_numerals(num_args, args, bv_size)) { + if (bv_size <= 64) { + uint64 r = n64(args[0]); + for (unsigned i = 1; i < num_args; i++) { + r |= n64(args[i]); + } + result = mk_numeral(~r, bv_size); + } + else { + numeral r = num(args[0]); + for (unsigned i = 1; i < num_args; i++) { + r = mk_bv_or(r, num(args[i]), bv_size); + } + result = mk_numeral(mk_bv_not(r, bv_size), bv_size); + } + } + else { + result = m_manager.mk_app(m_fid, OP_BNOR, num_args, args); + } +} + +void bv_simplifier_plugin::mk_bv_xnor(unsigned num_args, expr* const* args, expr_ref& result) { + unsigned bv_size; + if (are_numerals(num_args, args, bv_size)) { + if (bv_size <= 64) { + uint64 r = n64(args[0]); + for (unsigned i = 1; i < num_args; i++) { + r ^= n64(args[i]); + } + result = mk_numeral(~r, bv_size); + } + else { + numeral r = num(args[0]); + for (unsigned i = 1; i < num_args; i++) { + r = mk_bv_xor(r, num(args[i]), bv_size); + } + result = mk_numeral(mk_bv_not(r, bv_size), bv_size); + } + } + else { + result = m_manager.mk_app(m_fid, OP_BXNOR, num_args, args); + } +} + +void bv_simplifier_plugin::mk_bv_rotate_left_core(unsigned shift, numeral r, unsigned bv_size, expr_ref& result) { + SASSERT(shift < bv_size); + if (bv_size <= 64) { + uint64 a = r.get_uint64(); + uint64 r = shift_left(a, shift) | shift_right(a, bv_size - shift); + result = mk_numeral(r, bv_size); + } + else { + rational r1 = div(r, m_util.power_of_two(bv_size - shift)); // shift right + rational r2 = (r * m_util.power_of_two(shift)) % m_util.power_of_two(bv_size); // shift left + result = mk_numeral(r1 + r2, bv_size); + } +} + +void bv_simplifier_plugin::mk_bv_rotate_left(func_decl* f, expr* arg, expr_ref& result) { + numeral r; + unsigned bv_size; + SASSERT(f->get_decl_kind() == OP_ROTATE_LEFT); + if (m_util.is_numeral(arg, r, bv_size)) { + unsigned shift = f->get_parameter(0).get_int() % bv_size; + mk_bv_rotate_left_core(shift, r, bv_size, result); + } + else { + result = m_manager.mk_app(f, arg); + } +} + +void bv_simplifier_plugin::mk_bv_rotate_right_core(unsigned shift, numeral r, unsigned bv_size, expr_ref& result) { + SASSERT(shift < bv_size); + if (bv_size <= 64) { + uint64 a = r.get_uint64(); + uint64 r = shift_right(a, shift) | shift_left(a, bv_size - shift); + result = mk_numeral(r, bv_size); + } + else { + rational r1 = div(r, m_util.power_of_two(shift)); // shift right + rational r2 = (r * m_util.power_of_two(bv_size - shift)) % m_util.power_of_two(bv_size); // shift left + result = mk_numeral(r1 + r2, bv_size); + } +} + +void bv_simplifier_plugin::mk_bv_rotate_right(func_decl* f, expr* arg, expr_ref& result) { + numeral r; + unsigned bv_size; + SASSERT(f->get_decl_kind() == OP_ROTATE_RIGHT); + if (m_util.is_numeral(arg, r, bv_size)) { + unsigned shift = f->get_parameter(0).get_int() % bv_size; + mk_bv_rotate_right_core(shift, r, bv_size, result); + } + else { + result = m_manager.mk_app(f, arg); + } +} + +void bv_simplifier_plugin::mk_bv_redor(expr* arg, expr_ref& result) { + if (is_numeral(arg)) { + result = m_util.is_zero(arg)?mk_numeral(0, 1):mk_numeral(1,1); + } + else { + result = m_manager.mk_app(m_fid, OP_BREDOR, arg); + } +} + +void bv_simplifier_plugin::mk_bv_redand(expr* arg, expr_ref& result) { + numeral r; + unsigned bv_size; + if (m_util.is_numeral(arg, r, bv_size)) { + numeral allone = mk_allone(bv_size); + result = mk_numeral((r == allone)?1:0, 1); + } + else { + result = m_manager.mk_app(m_fid, OP_BREDAND, arg); + } +} + +void bv_simplifier_plugin::mk_bv_comp(expr* arg1, expr* arg2, expr_ref& result) { + numeral r1, r2; + if (arg1 == arg2) { + result = mk_numeral(1,1); + } + else if (is_numeral(arg1, r1) && is_numeral(arg2, r2)) { + result = mk_numeral((r1 == r2)?1:0, 1); + } + else { + result = m_manager.mk_app(m_fid, OP_BCOMP, arg1, arg2); + } +} + +void bv_simplifier_plugin::mk_bv_ashr(expr* arg1, expr* arg2, expr_ref& result) { + numeral r1, r2; + unsigned bv_size = get_bv_size(arg1); + bool is_num1 = m_util.is_numeral(arg1, r1, bv_size); + bool is_num2 = m_util.is_numeral(arg2, r2, bv_size); + + if (bv_size == 0) { + result = mk_numeral(rational(0), bv_size); + } + else if (is_num2 && r2.is_zero()) { + result = arg1; + } + else if (bv_size <= 64 && is_num1 && is_num2) { + uint64 n1 = n64(arg1); + uint64 n2_orig = n64(arg2); + uint64 n2 = n2_orig % bv_size; + SASSERT(n2 < bv_size); + uint64 r = shift_right(n1, n2); + bool sign = (n1 & shift_left(1ull, bv_size - 1ull)) != 0; + if (n2_orig > n2) { + if (sign) { + r = shift_left(1ull, bv_size) - 1ull; + } + else { + r = 0; + } + } + else if (sign) { + uint64 allone = shift_left(1ull, bv_size) - 1ull; + uint64 mask = ~(shift_left(1ull, bv_size - n2) - 1ull); + mask &= allone; + r |= mask; + } + result = mk_numeral(r, bv_size); + TRACE("bv", tout << mk_pp(arg1, m_manager) << " >> " + << mk_pp(arg2, m_manager) << " = " + << mk_pp(result.get(), m_manager) << "\n"; + tout << n1 << " >> " << n2 << " = " << r << "\n"; + ); + } + else if (is_num1 && is_num2 && rational(bv_size) <= r2) { + if (has_sign_bit(r1, bv_size)) { + result = mk_numeral(mk_allone(bv_size), bv_size); + } + else { + result = mk_bv0(bv_size); + } + } + else if (is_num1 && is_num2) { + SASSERT(r2 < rational(bv_size)); + bool sign = has_sign_bit(r1, bv_size); + r1 = div(r1, m_util.power_of_two(r2.get_unsigned())); + if (sign) { + // pad ones. + rational p(1); + for (unsigned i = 0; i < bv_size; ++i) { + if (r1 < p) { + r1 += p; + } + p *= rational(2); + } + } + result = mk_numeral(r1, bv_size); + + } + else if (shift_shift(OP_BASHR, arg1, arg2, result)) { + // done + } + else { + result = m_manager.mk_app(m_fid, OP_BASHR, arg1, arg2); + } +} + +void bv_simplifier_plugin::mk_bv_ext_rotate_right(expr* arg1, expr* arg2, expr_ref& result) { + numeral r2; + unsigned bv_size; + if (m_util.is_numeral(arg2, r2, bv_size)) { + unsigned shift = static_cast((r2 % numeral(bv_size)).get_uint64() % static_cast(bv_size)); + numeral r1; + if (is_numeral(arg1, r1)) { + mk_bv_rotate_right_core(shift, r1, bv_size, result); + } + else { + parameter p(shift); + result = m_manager.mk_app(m_fid, OP_ROTATE_RIGHT, 1, &p, 1, &arg1); + } + } + else { + result = m_manager.mk_app(m_fid, OP_EXT_ROTATE_RIGHT, arg1, arg2); + } +} + + +void bv_simplifier_plugin::mk_bv_ext_rotate_left(expr* arg1, expr* arg2, expr_ref& result) { + numeral r2; + unsigned bv_size; + if (m_util.is_numeral(arg2, r2, bv_size)) { + unsigned shift = static_cast((r2 % numeral(bv_size)).get_uint64() % static_cast(bv_size)); + numeral r1; + if (is_numeral(arg1, r1)) { + mk_bv_rotate_left_core(shift, r1, bv_size, result); + } + else { + parameter p(shift); + result = m_manager.mk_app(m_fid, OP_ROTATE_LEFT, 1, &p, 1, &arg1); + } + } + else { + result = m_manager.mk_app(m_fid, OP_EXT_ROTATE_LEFT, arg1, arg2); + } +} + +void bv_simplifier_plugin::bit2bool_simplify(unsigned idx, expr* e, expr_ref& result) { + + parameter p(idx); + + ptr_vector todo; + expr_ref_vector pinned(m_manager); + ptr_vector cache; + todo.push_back(e); + expr* e0 = e; + ptr_vector argv; + expr_ref tmp(m_manager); + while (!todo.empty()) { + e = todo.back(); + unsigned e_id = e->get_id(); + if (e_id >= cache.size()) { + cache.resize(e_id+1,0); + } + if (cache[e_id]) { + todo.pop_back(); + continue; + } + if (!m_util.is_numeral(e) && + !m_util.is_bv_and(e) && + !m_util.is_bv_or(e) && + !(is_app_of(e, m_fid, OP_BXOR) && to_app(e)->get_num_args() == 2) && + !m_manager.is_ite(e) && + !m_util.is_concat(e) && + !m_util.is_bv_not(e)) { + expr_ref extr(m_manager); + extr = m_util.mk_extract(idx, idx, e); + cache[e_id] = m_manager.mk_eq(m_util.mk_numeral(1, 1), extr); + pinned.push_back(cache[e_id]); + todo.pop_back(); + continue; + } + app* a = to_app(e); + unsigned sz = a->get_num_args(); + if (m_util.is_concat(e)) { + // look for first argument + unsigned idx1 = idx; + while (sz > 0) { + --sz; + expr * a_i = a->get_arg(sz); + unsigned a_sz = get_bv_size(a_i); + if (a_sz <= idx1) { + idx1 -= a_sz; + } + else { + // idx < a_sz; + bit2bool_simplify(idx1, a_i, tmp); + pinned.push_back(tmp); + cache[e_id] = to_app(tmp); + break; + } + } + todo.pop_back(); + continue; + } + argv.reset(); + for (unsigned i = 0; i < sz; ++i) { + expr* arg_i = a->get_arg(i); + if (i == 0 && m_manager.is_ite(e)) { + argv.push_back(arg_i); + } + else if (cache.size() > arg_i->get_id() && cache[arg_i->get_id()]) { + argv.push_back(cache[arg_i->get_id()]); + } + else { + todo.push_back(arg_i); + } + } + if (sz != argv.size()) { + continue; + } + todo.pop_back(); + rational val; + unsigned num_bits; + if (m_util.is_numeral(e, val, num_bits)) { + rational two(2); + for (unsigned i = 0; i < idx; ++i) { + val = div(val, two); + } + bool is_pos = !(val % two).is_zero(); + tmp = is_pos?m_manager.mk_true():m_manager.mk_false(); + } + else if (m_util.is_bv_and(e)) { + //tmp = m_manager.mk_and(sz, argv.c_ptr()); + m_bsimp.mk_and(sz, argv.c_ptr(), tmp); + pinned.push_back(tmp); + } + else if (m_util.is_bv_or(e)) { + //tmp = m_manager.mk_or(sz, argv.c_ptr()); + m_bsimp.mk_or(sz, argv.c_ptr(), tmp); + pinned.push_back(tmp); + } + else if (m_util.is_bv_not(e)) { + //tmp = m_manager.mk_not(argv[0]); + m_bsimp.mk_not(argv[0], tmp); + pinned.push_back(tmp); + } + else if (is_app_of(e, m_fid, OP_BXOR)) { + SASSERT(argv.size() == 2); + m_bsimp.mk_xor(argv[0], argv[1], tmp); + pinned.push_back(tmp); + } + else if (m_manager.is_ite(e)) { + //tmp = m_manager.mk_ite(argv[0], argv[1], argv[2]); + m_bsimp.mk_ite(argv[0], argv[1], argv[2], tmp); + pinned.push_back(tmp); + } + else { + UNREACHABLE(); + } + cache[e_id] = to_app(tmp); + } + result = cache[e0->get_id()]; +} + + +// replace addition by concatenation. +void bv_simplifier_plugin::mk_add_concat(expr_ref& result) { + if (!m_util.is_bv_add(result)) { + return; + } + app* a = to_app(result); + if (a->get_num_args() != 2) { + return; + } + expr* x = a->get_arg(0); + expr* y = a->get_arg(1); + if (!m_util.is_concat(x)) { + std::swap(x, y); + } + if (!m_util.is_concat(x)) { + return; + } + unsigned sz = m_util.get_bv_size(x); + +#if 0 + // optimzied version. Seems not worth it.. +#define UPDATE_CURR(_curr1, _idx1,_x,_is_num, _i) \ + if (_idx1 >= m_util.get_bv_size(_curr1)) { \ + _curr1 = _x; \ + _idx1 = _i; \ + _is_num = false; \ + } \ + while (m_util.is_concat(_curr1)) { \ + _is_num = false; \ + unsigned num_args = to_app(_curr1)->get_num_args(); \ + while (true) { \ + --num_args; \ + expr* c1 = to_app(_curr1)->get_arg(num_args); \ + unsigned sz1 = m_util.get_bv_size(c1); \ + if (sz1 < _idx1) { \ + _idx1 -= sz1; \ + } \ + else { \ + _curr1 = c1; \ + break; \ + } \ + } \ + } + + unsigned idx1 = 0, idx2 = 0; + expr* curr1 = x, *curr2 = y; + bool is_num1 = false, is_num2 = false; + rational val1, val2; + rational two(2); + for (unsigned i = 0; i < sz; ++i, ++idx1, ++idx2) { + UPDATE_CURR(curr1, idx1, x, is_num1, i); + UPDATE_CURR(curr2, idx2, y, is_num2, i); + if (idx1 == 0 && m_util.is_numeral(curr1, val1, bv_size)) { + is_num1 = true; + } + if (idx2 == 0 && m_util.is_numeral(curr2, val2, bv_size)) { + is_num2 = true; + } + if ((is_num1 && (val1 % two).is_zero()) || + (is_num2 && (val2 % two).is_zero())) { + val1 = div(val1, two); + val2 = div(val2, two); + continue; + } + return; + } + mk_bv_or(2, a->get_args(), result); +#endif + + for (unsigned i = 0; i < sz; ++i) { + if (!is_zero_bit(x,i) && !is_zero_bit(y,i)) { + return; + } + } + mk_bv_or(2, a->get_args(), result); +} + +bool bv_simplifier_plugin::is_zero_bit(expr* x, unsigned idx) { + rational val; + unsigned bv_size; + if (m_util.is_numeral(x, val, bv_size)) { + if (val.is_zero()) { + return true; + } + rational two(2); + while (idx > 0) { + val = div(val, two); + idx--; + } + return (val % two).is_zero(); + } + if (m_util.is_concat(x)) { + unsigned num_args = to_app(x)->get_num_args(); + while (num_args > 0) { + --num_args; + expr* y = to_app(x)->get_arg(num_args); + bv_size = m_util.get_bv_size(y); + if (bv_size <= idx) { + idx -= bv_size; + } + else { + return is_zero_bit(y, idx); + } + } + UNREACHABLE(); + } + + return false; +} diff --git a/lib/bv_simplifier_plugin.h b/lib/bv_simplifier_plugin.h new file mode 100644 index 000000000..7f66566b6 --- /dev/null +++ b/lib/bv_simplifier_plugin.h @@ -0,0 +1,187 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + bv_simplifier_plugin.h + +Abstract: + + Simplifier for the bv family. + +Author: + + Leonardo (leonardo) 2008-01-08 + +--*/ +#ifndef _BV_SIMPLIFIER_PLUGIN_H_ +#define _BV_SIMPLIFIER_PLUGIN_H_ + +#include"basic_simplifier_plugin.h" +#include"poly_simplifier_plugin.h" +#include"bv_decl_plugin.h" +#include"map.h" +#include"bv_simplifier_params.h" +#include"arith_decl_plugin.h" + +/** + \brief Simplifier for the bv family. +*/ +class bv_simplifier_plugin : public poly_simplifier_plugin { + + typedef rational numeral; + struct extract_entry { + unsigned m_high; + unsigned m_low; + expr * m_arg; + extract_entry():m_high(0), m_low(0), m_arg(0) {} + extract_entry(unsigned h, unsigned l, expr * n):m_high(h), m_low(l), m_arg(n) {} + unsigned hash() const { + unsigned a = m_high; + unsigned b = m_low; + unsigned c = m_arg->get_id(); + mix(a,b,c); + return c; + } + bool operator==(const extract_entry & e) const { + return m_high == e.m_high && m_low == e.m_low && m_arg == e.m_arg; + } + struct hash_proc { + unsigned operator()(extract_entry const& e) const { return e.hash(); } + }; + struct eq_proc { + bool operator()(extract_entry const& a, extract_entry const& b) const { return a == b; } + }; + }; + typedef map extract_cache; + +protected: + ast_manager& m_manager; + bv_util m_util; + arith_util m_arith; + basic_simplifier_plugin & m_bsimp; + bv_simplifier_params & m_params; + expr_ref_vector m_zeros; + extract_cache m_extract_cache; + + unsigned_vector m_lows, m_highs; + ptr_vector m_extract_args; + + rational mk_bv_and(numeral const& a0, numeral const& b0, unsigned sz); + rational mk_bv_or(numeral const& a0, numeral const& b0, unsigned sz); + rational mk_bv_xor(numeral const& a0, numeral const& b0, unsigned sz); + rational mk_bv_not(numeral const& a0, unsigned sz); + rational num(expr* e); + bool has_sign_bit(numeral const& n, unsigned bv_size) { return m_util.has_sign_bit(n, bv_size); } + + bool shift_shift(bv_op_kind k, expr* arg1, expr* arg2, expr_ref& result); + + void bit2bool_simplify(unsigned idx, expr* e, expr_ref& result); + + void mk_add_concat(expr_ref& result); + bool is_zero_bit(expr* x, unsigned idx); + + void mk_bv_rotate_left_core(unsigned shift, numeral r, unsigned bv_size, expr_ref& result); + void mk_bv_rotate_right_core(unsigned shift, numeral r, unsigned bv_size, expr_ref& result); + +public: + bv_simplifier_plugin(ast_manager & m, basic_simplifier_plugin & b, bv_simplifier_params & p); + virtual ~bv_simplifier_plugin(); + + + // simplifier_plugin: + virtual bool reduce(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + virtual bool reduce_eq(expr * lhs, expr * rhs, expr_ref & result); + virtual bool reduce_distinct(unsigned num_args, expr * const * args, expr_ref & result); + virtual void flush_caches(); + + // poly_simplifier_plugin + virtual rational norm(const rational & n); + virtual bool is_numeral(expr * n, rational & val) const; + bool is_numeral(expr * n) const { return m_util.is_numeral(n); } + virtual bool is_minus_one(expr * n) const { return is_minus_one_core(n); } + virtual expr * get_zero(sort * s) const; + virtual app * mk_numeral(rational const & n); + + bool is_bv(expr * n) const { return m_util.is_bv(n); } + bool is_bv_sort(sort * s) const { return m_util.is_bv_sort(s); } + + bool is_le(expr * n) const { return m_util.is_bv_ule(n) || m_util.is_bv_sle(n); } + // REMARK: simplified bv expressions are never of the form a >= b. + virtual bool is_le_ge(expr * n) const { return is_le(n); } + + uint64 to_uint64(const numeral & n, unsigned bv_size); + rational norm(rational const& n, unsigned bv_size, bool is_signed) { return m_util.norm(n, bv_size, is_signed); } + unsigned get_bv_size(expr const * n) { return get_bv_size(m_manager.get_sort(n)); } + unsigned get_bv_size(sort const * s) { return m_util.get_bv_size(s); } + void mk_leq_core(bool is_signed, expr * arg1, expr * arg2, expr_ref & result); + void mk_ule(expr* a, expr* b, expr_ref& result); + void mk_ult(expr* a, expr* b, expr_ref& result); + void mk_sle(expr* a, expr* b, expr_ref& result); + void mk_slt(expr* a, expr* b, expr_ref& result); + void mk_bv_and(unsigned num_args, expr * const* args, expr_ref & result); + void mk_bv_or(unsigned num_args, expr * const* args, expr_ref & result); + void mk_bv_xor(unsigned num_args, expr * const* args, expr_ref & result); + void mk_bv_not(expr * arg, expr_ref & result); + void mk_extract(unsigned hi,unsigned lo, expr* bv, expr_ref& result); + void mk_extract_core(unsigned high, unsigned low, expr * arg, expr_ref& result); + void cache_extract(unsigned h, unsigned l, expr * arg, expr * result); + expr* get_cached_extract(unsigned h, unsigned l, expr * arg); + + bool lookup_mk_extract(unsigned high, unsigned low, expr * arg, expr_ref& result); + bool try_mk_extract(unsigned high, unsigned low, expr * arg, expr_ref& result); + + void mk_bv_eq(expr* a1, expr* a2, expr_ref& result); + void mk_eq_core(expr * arg1, expr * arg2, expr_ref & result); + void mk_args_eq_numeral(app * app, expr * n, expr_ref & result); + + void mk_concat(unsigned num_args, expr * const * args, expr_ref & result); + void mk_concat(expr * arg1, expr * arg2, expr_ref & result) { + expr * args[2] = { arg1, arg2 }; + mk_concat(2, args, result); + } + void mk_zeroext(unsigned n, expr * arg, expr_ref & result); + void mk_repeat(unsigned n, expr * arg, expr_ref & result); + void mk_sign_extend(unsigned n, expr * arg, expr_ref & result); + void mk_bv_lshr(expr * arg1, expr * arg2, expr_ref & result); + void mk_bv_shl(expr * arg1, expr * arg2, expr_ref & result); + void mk_bv_ashr(expr* arg1, expr* arg2, expr_ref& result); + void mk_bv_smod(expr* arg1, expr* arg2, expr_ref& result); + void mk_bv_urem(expr * arg1, expr * arg2, expr_ref & result); + void mk_bv_srem(expr* arg1, expr* arg2, expr_ref& result); + void mk_bv_udiv(expr* arg1, expr* arg2, expr_ref& result); + void mk_bv_sdiv(expr* arg1, expr* arg2, expr_ref& result); + void mk_bv_smod_i(expr* arg1, expr* arg2, expr_ref& result); + void mk_bv_urem_i(expr * arg1, expr * arg2, expr_ref & result); + void mk_bv_srem_i(expr* arg1, expr* arg2, expr_ref& result); + void mk_bv_udiv_i(expr* arg1, expr* arg2, expr_ref& result); + void mk_bv_sdiv_i(expr* arg1, expr* arg2, expr_ref& result); + void mk_bv_nand(unsigned num_args, expr* const* args, expr_ref& result); + void mk_bv_nor(unsigned num_args, expr* const* args, expr_ref& result); + void mk_bv_xnor(unsigned num_args, expr* const* args, expr_ref& result); + void mk_bv_rotate_right(func_decl* f, expr* arg, expr_ref& result); + void mk_bv_rotate_left(func_decl* f, expr* arg, expr_ref& result); + void mk_bv_ext_rotate_right(expr* arg1, expr* arg2, expr_ref& result); + void mk_bv_ext_rotate_left(expr* arg1, expr* arg2, expr_ref& result); + + void mk_bv_redor(expr* arg, expr_ref& result); + void mk_bv_redand(expr* arg, expr_ref& result); + void mk_bv_comp(expr* arg1, expr* arg2, expr_ref& result); + + bool are_numerals(unsigned num_args, expr * const* args, unsigned& bv_size); + app * mk_numeral(rational const & n, unsigned bv_size); + app * mk_numeral(uint64 n, unsigned bv_size) { return mk_numeral(numeral(n, numeral::ui64()), bv_size); } + app* mk_bv0(unsigned bv_size) { return m_util.mk_numeral(numeral(0), bv_size); } + rational mk_allone(unsigned bv_size) { return m_util.power_of_two(bv_size) - numeral(1); } + bool is_minus_one_core(expr * arg) const; + bool is_x_minus_one(expr * arg, expr * & x); + void mk_int2bv(expr * arg, sort* range, expr_ref & result); + void mk_bv2int(expr * arg, sort* range, expr_ref & result); + uint64 n64(expr* e); + bool is_mul_no_overflow(expr* e); + bool is_add_no_overflow(expr* e); + unsigned num_leading_zero_bits(expr* e); + +}; + +#endif /* _BV_SIMPLIFIER_PLUGIN_H_ */ diff --git a/lib/bv_size_reduction_tactic.cpp b/lib/bv_size_reduction_tactic.cpp new file mode 100644 index 000000000..d24f506a1 --- /dev/null +++ b/lib/bv_size_reduction_tactic.cpp @@ -0,0 +1,366 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + bv_size_reduction_tactic.cpp + +Abstract: + + Reduce the number of bits used to encode constants, by using signed bounds. + Example: suppose x is a bit-vector of size 8, and we have + signed bounds for x such that: + -2 <= x <= 2 + Then, x can be replaced by ((sign-extend 5) k) + where k is a fresh bit-vector constant of size 3. + +Author: + + Leonardo (leonardo) 2012-02-19 + +Notes: + +--*/ +#include"tactical.h" +#include"bv_decl_plugin.h" +#include"expr_replacer.h" +#include"extension_model_converter.h" +#include"ast_smt2_pp.h" + +class bv_size_reduction_tactic : public tactic { + struct imp; + imp * m_imp; +public: + bv_size_reduction_tactic(ast_manager & m); + + virtual tactic * translate(ast_manager & m) { + return alloc(bv_size_reduction_tactic, m); + } + + virtual ~bv_size_reduction_tactic(); + + virtual void operator()(goal_ref const & g, goal_ref_buffer & result, model_converter_ref & mc, proof_converter_ref & pc, expr_dependency_ref & core); + + virtual void cleanup(); + virtual void set_cancel(bool f); +}; + +tactic * mk_bv_size_reduction_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(bv_size_reduction_tactic, m)); +} + +struct bv_size_reduction_tactic::imp { + typedef rational numeral; + typedef extension_model_converter bv_size_reduction_mc; + + ast_manager & m; + bv_util m_util; + obj_map m_signed_lowers; + obj_map m_signed_uppers; + obj_map m_unsigned_lowers; + obj_map m_unsigned_uppers; + ref m_mc; + scoped_ptr m_replacer; + bool m_produce_models; + volatile bool m_cancel; + + imp(ast_manager & _m): + m(_m), + m_util(m), + m_replacer(mk_default_expr_replacer(m)), + m_cancel(false) { + } + + void update_signed_lower(app * v, numeral const & k) { + // k <= v + obj_map::obj_map_entry * entry = m_signed_lowers.insert_if_not_there2(v, k); + if (entry->get_data().m_value < k) { + // improve bound + entry->get_data().m_value = k; + } + } + + void update_signed_upper(app * v, numeral const & k) { + // v <= k + obj_map::obj_map_entry * entry = m_signed_uppers.insert_if_not_there2(v, k); + if (k < entry->get_data().m_value) { + // improve bound + entry->get_data().m_value = k; + } + } + + void update_unsigned_lower(app * v, numeral const & k) { + SASSERT(k > numeral(0)); + // k <= v + obj_map::obj_map_entry * entry = m_unsigned_lowers.insert_if_not_there2(v, k); + if (entry->get_data().m_value < k) { + // improve bound + entry->get_data().m_value = k; + } + } + + void update_unsigned_upper(app * v, numeral const & k) { + SASSERT(k > numeral(0)); + // v <= k + obj_map::obj_map_entry * entry = m_unsigned_uppers.insert_if_not_there2(v, k); + if (k < entry->get_data().m_value) { + // improve bound + entry->get_data().m_value = k; + } + } + + void collect_bounds(goal const & g) { + unsigned sz = g.size(); + numeral val; + unsigned bv_sz; + expr * f, * lhs, * rhs; + for (unsigned i = 0; i < sz; i++) { + bool negated = false; + f = g.form(i); + if (m.is_not(f)) { + negated = true; + f = to_app(f)->get_arg(0); + } + + if (m_util.is_bv_sle(f, lhs, rhs)) { + if (is_uninterp_const(lhs) && m_util.is_numeral(rhs, val, bv_sz)) { + TRACE("bv_size_reduction", tout << (negated?"not ":"") << mk_ismt2_pp(f, m) << std::endl; ); + // v <= k + if (negated) update_signed_lower(to_app(lhs), val+numeral(1)); + else update_signed_upper(to_app(lhs), val); + } + else if (is_uninterp_const(rhs) && m_util.is_numeral(lhs, val, bv_sz)) { + TRACE("bv_size_reduction", tout << (negated?"not ":"") << mk_ismt2_pp(f, m) << std::endl; ); + // k <= v + if (negated) update_signed_upper(to_app(rhs), val-numeral(1)); + else update_signed_lower(to_app(rhs), val); + } + } +#if 0 + else if (m_util.is_bv_ule(f, lhs, rhs)) { + if (is_uninterp_const(lhs) && m_util.is_numeral(rhs, val, bv_sz)) { + TRACE("bv_size_reduction", tout << (negated?"not ":"") << mk_ismt2_pp(f, m) << std::endl; ); + // v <= k + if (negated) update_unsigned_lower(to_app(lhs), val+numeral(1)); + else update_unsigned_upper(to_app(lhs), val); + } + else if (is_uninterp_const(rhs) && m_util.is_numeral(lhs, val, bv_sz)) { + TRACE("bv_size_reduction", tout << (negated?"not ":"") << mk_ismt2_pp(f, m) << std::endl; ); + // k <= v + if (negated) update_unsigned_upper(to_app(rhs), val-numeral(1)); + else update_unsigned_lower(to_app(rhs), val); + } + } +#endif + } + } + + void checkpoint() { + if (m_cancel) + throw tactic_exception(TACTIC_CANCELED_MSG); + } + + void operator()(goal & g, model_converter_ref & mc) { + if (g.inconsistent()) + return; + TRACE("before_bv_size_reduction", g.display(tout);); + m_produce_models = g.models_enabled(); + mc = 0; + m_mc = 0; + unsigned num_reduced = 0; + { + tactic_report report("bv-size-reduction", g); + collect_bounds(g); + + // create substitution + expr_substitution subst(m); + + if (!(m_signed_lowers.empty() || m_signed_uppers.empty())) { + TRACE("bv_size_reduction", + tout << "m_signed_lowers: " << std::endl; + for (obj_map::iterator it = m_signed_lowers.begin(); it != m_signed_lowers.end(); it++) + tout << mk_ismt2_pp(it->m_key, m) << " >= " << it->m_value.to_string() << std::endl; + tout << "m_signed_uppers: " << std::endl; + for (obj_map::iterator it = m_signed_uppers.begin(); it != m_signed_uppers.end(); it++) + tout << mk_ismt2_pp(it->m_key, m) << " <= " << it->m_value.to_string() << std::endl; + ); + + obj_map::iterator it = m_signed_lowers.begin(); + obj_map::iterator end = m_signed_lowers.end(); + for (; it != end; ++it) { + app * v = it->m_key; + unsigned bv_sz = m_util.get_bv_size(v); + numeral l = m_util.norm(it->m_value, bv_sz, true); + obj_map::obj_map_entry * entry = m_signed_uppers.find_core(v); + if (entry != 0) { + numeral u = m_util.norm(entry->get_data().m_value, bv_sz, true); + TRACE("bv_size_reduction", tout << l << " <= " << v->get_decl()->get_name() << " <= " << u << "\n";); + expr * new_def = 0; + if (l > u) { + g.assert_expr(m.mk_false()); + return; + } + else if (l == u) { + new_def = m_util.mk_numeral(l, m.get_sort(v)); + } + else { + // l < u + if (l.is_neg()) { + unsigned i_nb = (u - l).get_num_bits(); + unsigned v_nb = m_util.get_bv_size(v); + if (i_nb < v_nb) + new_def = m_util.mk_sign_extend(v_nb - i_nb, m.mk_fresh_const(0, m_util.mk_sort(i_nb))); + } + else { + // 0 <= l <= v <= u + unsigned u_nb = u.get_num_bits(); + unsigned v_nb = m_util.get_bv_size(v); + if (u_nb < v_nb) + new_def = m_util.mk_concat(m_util.mk_numeral(numeral(0), v_nb - u_nb), m.mk_fresh_const(0, m_util.mk_sort(u_nb))); + } + } + + if (new_def) { + subst.insert(v, new_def); + if (m_produce_models) { + if (!m_mc) + m_mc = alloc(bv_size_reduction_mc, m); + m_mc->insert(v->get_decl(), new_def); + } + num_reduced++; + } + } + } + } + +#if 0 + if (!(m_unsigned_lowers.empty() && m_unsigned_uppers.empty())) { + TRACE("bv_size_reduction", + tout << "m_unsigned_lowers: " << std::endl; + for (obj_map::iterator it = m_unsigned_lowers.begin(); it != m_unsigned_lowers.end(); it++) + tout << mk_ismt2_pp(it->m_key, m) << " >= " << it->m_value.to_string() << std::endl; + tout << "m_unsigned_uppers: " << std::endl; + for (obj_map::iterator it = m_unsigned_uppers.begin(); it != m_unsigned_uppers.end(); it++) + tout << mk_ismt2_pp(it->m_key, m) << " <= " << it->m_value.to_string() << std::endl; + ); + + obj_map::iterator it = m_unsigned_uppers.begin(); + obj_map::iterator end = m_unsigned_uppers.end(); + for (; it != end; ++it) { + app * v = it->m_key; + unsigned bv_sz = m_util.get_bv_size(v); + numeral u = m_util.norm(it->m_value, bv_sz, false); + obj_map::obj_map_entry * entry = m_signed_lowers.find_core(v); + numeral l = (entry != 0) ? m_util.norm(entry->get_data().m_value, bv_sz, false) : numeral(0); + + obj_map::obj_map_entry * lse = m_signed_lowers.find_core(v); + obj_map::obj_map_entry * use = m_signed_uppers.find_core(v); + if ((lse != 0 && lse->get_data().m_value > l) && + (use != 0 && use->get_data().m_value < u)) + continue; // Skip, we had better signed bounds. + + if (lse != 0 && lse->get_data().m_value > l) l = lse->get_data().m_value; + if (use != 0 && use->get_data().m_value < u) u = use->get_data().m_value; + + TRACE("bv_size_reduction", tout << l << " <= " << v->get_decl()->get_name() << " <= " << u << "\n";); + expr * new_def = 0; + if (l > u) { + g.assert_expr(m.mk_false()); + return; + } + else if (l == u) { + new_def = m_util.mk_numeral(l, m.get_sort(v)); + } + else { + // 0 <= l <= v <= u + unsigned u_nb = u.get_num_bits(); + unsigned v_nb = m_util.get_bv_size(v); + if (u_nb < v_nb) + new_def = m_util.mk_concat(m_util.mk_numeral(numeral(0), v_nb - u_nb), m.mk_fresh_const(0, m_util.mk_sort(u_nb))); + } + + if (new_def) { + subst.insert(v, new_def); + if (m_produce_models) { + if (!m_mc) + m_mc = alloc(bv_size_reduction_mc, m); + m_mc->insert(v->get_decl(), new_def); + } + num_reduced++; + TRACE("bv_size_reduction", tout << "New definition = " << mk_ismt2_pp(new_def, m) << "\n";); + } + } + } +#endif + + if (subst.empty()) + return; + + m_replacer->set_substitution(&subst); + + unsigned sz = g.size(); + expr * f; + expr_ref new_f(m); + for (unsigned i = 0; i < sz; i++) { + if (g.inconsistent()) + return; + f = g.form(i); + (*m_replacer)(f, new_f); + g.update(i, new_f); + } + mc = m_mc.get(); + m_mc = 0; + } + report_tactic_progress(":bv-reduced", num_reduced); + TRACE("after_bv_size_reduction", g.display(tout); if (m_mc) m_mc->display(tout);); + } + + void set_cancel(bool f) { + m_replacer->set_cancel(f); + m_cancel = f; + } +}; + +bv_size_reduction_tactic::bv_size_reduction_tactic(ast_manager & m) { + m_imp = alloc(imp, m); +} + +bv_size_reduction_tactic::~bv_size_reduction_tactic() { + dealloc(m_imp); +} + +void bv_size_reduction_tactic::operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + fail_if_proof_generation("bv-size-reduction", g); + fail_if_unsat_core_generation("bv-size-reduction", g); + mc = 0; pc = 0; core = 0; result.reset(); + m_imp->operator()(*(g.get()), mc); + g->inc_depth(); + result.push_back(g.get()); + SASSERT(g->is_well_sorted()); +} + +void bv_size_reduction_tactic::set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); +} + +void bv_size_reduction_tactic::cleanup() { + ast_manager & m = m_imp->m; + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } +} + diff --git a/lib/bv_size_reduction_tactic.h b/lib/bv_size_reduction_tactic.h new file mode 100644 index 000000000..696740096 --- /dev/null +++ b/lib/bv_size_reduction_tactic.h @@ -0,0 +1,33 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + bv_size_reduction.h + +Abstract: + + Reduce the number of bits used to encode constants, by using signed bounds. + Example: suppose x is a bit-vector of size 8, and we have + signed bounds for x such that: + -2 <= x <= 2 + Then, x can be replaced by ((sign-extend 5) k) + where k is a fresh bit-vector constant of size 3. + +Author: + + Leonardo (leonardo) 2012-02-19 + +Notes: + +--*/ +#ifndef _BV_SIZE_REDUCTION_TACTIC_H_ +#define _BV_SIZE_REDUCTION_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_bv_size_reduction_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/cached_var_subst.cpp b/lib/cached_var_subst.cpp new file mode 100644 index 000000000..1db3aa0a6 --- /dev/null +++ b/lib/cached_var_subst.cpp @@ -0,0 +1,79 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + cached_var_subst.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2009-01-23. + +Revision History: + +--*/ +#include"cached_var_subst.h" + +bool cached_var_subst::key_eq_proc::operator()(cached_var_subst::key * k1, cached_var_subst::key * k2) const { + if (k1->m_qa != k2->m_qa) + return false; + if (k1->m_num_bindings != k2->m_num_bindings) + return false; + for (unsigned i = 0; i < k1->m_num_bindings; i++) + if (k1->m_bindings[i] != k2->m_bindings[i]) + return false; + return true; +} + +cached_var_subst::cached_var_subst(ast_manager & m): + m_proc(m), + m_refs(m) { +} + +void cached_var_subst::reset() { + m_refs.reset(); + m_instances.reset(); + m_region.reset(); + m_new_keys.reset(); +} + +void cached_var_subst::operator()(quantifier * qa, unsigned num_bindings, smt::enode * const * bindings, expr_ref & result) { + m_new_keys.reserve(num_bindings+1, 0); + key * new_key = m_new_keys[num_bindings]; + if (new_key == 0) + new_key = static_cast(m_region.allocate(sizeof(key) + sizeof(expr*)*num_bindings)); + + new_key->m_qa = qa; + new_key->m_num_bindings = num_bindings; + for (unsigned i = 0; i < num_bindings; i++) + new_key->m_bindings[i] = bindings[i]->get_owner(); + + instances::entry * entry = m_instances.insert_if_not_there2(new_key, 0); + if (entry->get_data().m_key != new_key) { + SASSERT(entry->get_data().m_value != 0); + // entry was already there + m_new_keys[num_bindings] = new_key; // recycle key + result = entry->get_data().m_value; + return; + } + + m_proc(qa->get_expr(), new_key->m_num_bindings, new_key->m_bindings, result); + // cache result + entry->get_data().m_value = result; + + // remove key from cache + m_new_keys[num_bindings] = 0; + + // increment reference counters + m_refs.push_back(qa); + for (unsigned i = 0; i < new_key->m_num_bindings; i++) + m_refs.push_back(new_key->m_bindings[i]); + m_refs.push_back(result); +} + + + diff --git a/lib/cached_var_subst.h b/lib/cached_var_subst.h new file mode 100644 index 000000000..65dbf22d2 --- /dev/null +++ b/lib/cached_var_subst.h @@ -0,0 +1,53 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + cached_var_subst.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2009-01-23. + +Revision History: + +--*/ +#ifndef _CACHED_VAR_SUBST_H_ +#define _CACHED_VAR_SUBST_H_ + +#include"var_subst.h" +#include"map.h" +#include"smt_enode.h" + +class cached_var_subst { + struct key { + quantifier * m_qa; + unsigned m_num_bindings; + expr * m_bindings[0]; + }; + struct key_hash_proc { + unsigned operator()(key * k) const { + return string_hash(reinterpret_cast(k->m_bindings), sizeof(expr *) * k->m_num_bindings, k->m_qa->get_id()); + } + }; + struct key_eq_proc { + bool operator()(key * k1, key * k2) const; + }; + typedef map instances; + var_subst m_proc; + expr_ref_vector m_refs; + instances m_instances; + region m_region; + ptr_vector m_new_keys; // mapping from num_bindings -> next key +public: + cached_var_subst(ast_manager & m); + void operator()(quantifier * qa, unsigned num_bindings, smt::enode * const * bindings, expr_ref & result); + void reset(); +}; + +#endif /* _CACHED_VAR_SUBST_H_ */ + diff --git a/lib/cancel_eh.h b/lib/cancel_eh.h new file mode 100644 index 000000000..74c5040a2 --- /dev/null +++ b/lib/cancel_eh.h @@ -0,0 +1,37 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + cancel_eh.h + +Abstract: + + Template for implementing simple event handler that just invokes cancel method. + +Author: + + Leonardo de Moura (leonardo) 2011-04-27. + +Revision History: + +--*/ +#ifndef _CANCEL_EH_H_ +#define _CANCEL_EH_H_ + +#include"event_handler.h" + +/** + \brief Generic event handler for invoking cancel method. +*/ +template +class cancel_eh : public event_handler { + T & m_obj; +public: + cancel_eh(T & o):m_obj(o) {} + virtual void operator()() { + m_obj.cancel(); + } +}; + +#endif diff --git a/lib/chashtable.h b/lib/chashtable.h new file mode 100644 index 000000000..80484a9f6 --- /dev/null +++ b/lib/chashtable.h @@ -0,0 +1,672 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + chashtable.h + +Abstract: + + Hashtable with chaining. + + The performance of the hashtable in hashtable.h deteriorates if + there is a huge number of deletions. In this case, the hashtable + starts to contain many cells marked as deleted, and insertion/deletion + start to suffer. + + The hashtable defined in this class addresses this problem by using + chaining. Of course, there is the cost of storing the link to the next + cell. + +Author: + + Leonardo de Moura (leonardo) 2011-04-14. + +Revision History: + +--*/ +#ifndef _CHASHTABLE_H_ +#define _CHASHTABLE_H_ + +#include"memory_manager.h" +#include"debug.h" +#include"trace.h" +#ifdef Z3DEBUG +#include"hashtable.h" +#endif + +#define CH_STATISTICS + +#ifdef CH_STATISTICS +#define CHS_CODE(CODE) { CODE } +#else +#define CHS_CODE(CODE) +#endif + +template +class chashtable : private HashProc, private EqProc { +public: + static const unsigned default_init_slots = 8; + static const unsigned default_init_cellar = 2; + +protected: + struct cell { + cell * m_next; + T m_data; + cell():m_next(reinterpret_cast(1)) {} + bool is_free() const { return m_next == reinterpret_cast(1); } + void mark_free() { m_next = reinterpret_cast(1); } + }; + + cell * m_table; // array of cells. + unsigned m_capacity; // size of the array of cells. + unsigned m_init_slots; + unsigned m_init_cellar; + unsigned m_slots; // m_slots < m_capacity, and m_slots is a power of two, the cells [m_slots, m_capacity) are used for chaining. + unsigned m_used_slots; // m_used_slots <= m_slots (number of used slots). + unsigned m_size; // number of occupied cells. +#ifdef CH_STATISTICS + unsigned m_collisions; +#endif + cell * m_next_cell; + cell * m_free_cell; + + unsigned get_hash(T const & d) const { return HashProc::operator()(d); } + bool equals(T const & e1, T const & e2) const { return EqProc::operator()(e1, e2); } + + static cell * alloc_table(unsigned sz) { + return alloc_vect(sz); + } + + void delete_table() { + dealloc_vect(m_table, m_capacity); + } + + // Return the next free cell in the cellar, and the number of used slots + // Return 0 if the cellar is too small (unlikely but it might happen with a bad hash) + cell * copy_table(cell * source, unsigned source_slots, unsigned source_capacity, + cell * target, unsigned target_slots, unsigned target_capacity, + unsigned & used_slots) { + TRACE("chashtable", tout << "copy_table...\n";); + SASSERT(target_slots >= source_slots); + SASSERT(target_capacity >= source_capacity); + unsigned target_mask = target_slots - 1; + used_slots = 0; + cell * source_end = source + source_slots; + cell * target_cellar = target + target_slots; + cell * target_end = target + target_capacity; + for (cell * source_it = source; source_it != source_end; ++source_it) { + if (!source_it->is_free()) { + cell * list_it = source_it; + do { + unsigned h = get_hash(list_it->m_data); + unsigned idx = h & target_mask; + cell * target_it = target + idx; + SASSERT(target_it >= target); + SASSERT(target_it < target + target_slots); + if (target_it->is_free()) { + target_it->m_data = list_it->m_data; + target_it->m_next = 0; + used_slots++; + } + else { + SASSERT((get_hash(target_it->m_data) & target_mask) == idx); + if (target_cellar == target_end) + return 0; // the cellar is too small... + SASSERT(target_cellar >= target + target_slots); + SASSERT(target_cellar < target_end); + *target_cellar = *target_it; + target_it->m_data = list_it->m_data; + target_it->m_next = target_cellar; + target_cellar++; + } + SASSERT(!target_it->is_free()); + list_it = list_it->m_next; + } + while (list_it != 0); + } + } +#if 0 + TRACE("chashtable", + for (unsigned i = 0; i < source_capacity; i++) { + tout << i << ":["; + if (source[i].m_next == 0) + tout << "null"; + else if (source[i].m_next == reinterpret_cast(1)) + tout << "X"; + else + tout << (source[i].m_next - source); + tout << ", " << source[i].m_data << "]\n"; + } + tout << "\n"; + for (unsigned i = 0; i < target_capacity; i++) { + tout << i << ":["; + if (target[i].m_next == 0) + tout << "null"; + else if (target[i].m_next == reinterpret_cast(1)) + tout << "X"; + else + tout << (target[i].m_next - target); + tout << ", " << target[i].m_data << "]\n"; + } + tout << "\n";); +#endif + return target_cellar; + } + + void expand_table() { + unsigned curr_cellar = (m_capacity - m_slots); + unsigned new_slots = m_slots * 2; + unsigned new_cellar = curr_cellar * 2; + while (true) { + unsigned new_capacity = new_slots + new_cellar; + cell * new_table = alloc_table(new_capacity); + cell * next_cell = copy_table(m_table, m_slots, m_capacity, + new_table, new_slots, new_capacity, + m_used_slots); + if (next_cell != 0) { + delete_table(); + m_table = new_table; + m_capacity = new_capacity; + m_slots = new_slots; + m_next_cell = next_cell; + m_free_cell = 0; + CASSERT("chashtable", check_invariant()); + return; + } + dealloc_vect(new_table, new_capacity); + new_cellar *= 2; + } + } + + bool has_free_cells() const { + return m_free_cell != 0 || m_next_cell < m_table + m_capacity; + } + + cell * get_free_cell() { + if (m_free_cell != 0) { + cell * c = m_free_cell; + m_free_cell = c->m_next; + return c; + } + else { + cell * c = m_next_cell; + m_next_cell++; + return c; + } + } + + void recycle_cell(cell * c) { + // c is in the cellar + SASSERT(c >= m_table + m_slots); + SASSERT(c < m_table + m_capacity); + c->m_next = m_free_cell; + m_free_cell = c; + } + + void init(unsigned slots, unsigned cellar) { + m_capacity = slots + cellar; + m_table = alloc_table(m_capacity); + m_slots = slots; + m_used_slots = 0; + m_size = 0; + m_next_cell = m_table + slots; + m_free_cell = 0; + } + +public: + chashtable(HashProc const & h = HashProc(), + EqProc const & e = EqProc(), + unsigned init_slots = default_init_slots, + unsigned init_cellar = default_init_cellar): + HashProc(h), + EqProc(e) { + SASSERT(is_power_of_two(init_slots)); + SASSERT(init_cellar > 0); + m_init_slots = init_slots; + m_init_cellar = init_cellar; + init(m_init_slots, m_init_cellar); + CHS_CODE(m_collisions = 0;); + } + + ~chashtable() { +#if 0 + cell * it = m_table; + cell * end = m_table + m_slots; + verbose_stream() << "[chashtable] free slots: "; + for (; it != end; ++it) { + if (it->is_free()) + verbose_stream() << (it - m_table) << " "; + } + verbose_stream() << "\n"; +#endif + delete_table(); + } + + void reset() { + if (m_size == 0) + return; + finalize(); + } + + void finalize() { + delete_table(); + init(m_init_slots, m_init_cellar); + } + + bool empty() const { + return m_size == 0; + } + + unsigned size() const { + return m_size; + } + + unsigned capacity() const { + return m_capacity; + } + + unsigned used_slots() const { + return m_used_slots; + } + + void insert(T const & d) { + if (!has_free_cells()) + expand_table(); + unsigned mask = m_slots - 1; + unsigned h = get_hash(d); + unsigned idx = h & mask; + cell * c = m_table + idx; + if (c->is_free()) { + m_size++; + m_used_slots++; + c->m_data = d; + c->m_next = 0; + CASSERT("chashtable_bug", check_invariant()); + return; + } + else { + cell * it = c; + do { + if (equals(it->m_data, d)) { + // already there + it->m_data = d; + CASSERT("chashtable_bug", check_invariant()); + return; + } + CHS_CODE(m_collisions++;); + it = it->m_next; + } + while (it != 0); + // d is not in the table. + m_size++; + cell * new_c = get_free_cell(); + *new_c = *c; + c->m_data = d; + c->m_next = new_c; + CASSERT("chashtable_bug", check_invariant()); + return; + } + } + + T & insert_if_not_there(T const & d) { + if (!has_free_cells()) + expand_table(); + unsigned mask = m_slots - 1; + unsigned h = get_hash(d); + unsigned idx = h & mask; + cell * c = m_table + idx; + if (c->is_free()) { + m_size++; + m_used_slots++; + c->m_data = d; + c->m_next = 0; + CASSERT("chashtable_bug", check_invariant()); + return c->m_data; + } + else { + cell * it = c; + do { + if (equals(it->m_data, d)) { + // already there + CASSERT("chashtable_bug", check_invariant()); + return it->m_data; + } + CHS_CODE(m_collisions++;); + it = it->m_next; + } + while (it != 0); + // d is not in the table. + m_size++; + cell * new_c = get_free_cell(); + *new_c = *c; + c->m_data = d; + c->m_next = new_c; + CASSERT("chashtable_bug", check_invariant()); + return c->m_data; + } + } + + bool insert_if_not_there2(T const & d) { + if (!has_free_cells()) + expand_table(); + unsigned mask = m_slots - 1; + unsigned h = get_hash(d); + unsigned idx = h & mask; + cell * c = m_table + idx; + if (c->is_free()) { + m_size++; + m_used_slots++; + c->m_data = d; + c->m_next = 0; + CASSERT("chashtable_bug", check_invariant()); + return true; + } + else { + cell * it = c; + do { + if (equals(it->m_data, d)) { + // already there + CASSERT("chashtable_bug", check_invariant()); + return false; + } + CHS_CODE(m_collisions++;); + it = it->m_next; + } + while (it != 0); + // d is not in the table. + m_size++; + cell * new_c = get_free_cell(); + *new_c = *c; + c->m_data = d; + c->m_next = new_c; + CASSERT("chashtable_bug", check_invariant()); + return true; + } + } + + bool contains(T const & d) const { + unsigned mask = m_slots - 1; + unsigned h = get_hash(d); + unsigned idx = h & mask; + cell * c = m_table + idx; + if (c->is_free()) + return false; + do { + if (equals(c->m_data, d)) { + return true; + } + CHS_CODE(const_cast(this)->m_collisions++;); + c = c->m_next; + } + while (c != 0); + return false; + } + + T * find_core(T const & d) const { + unsigned mask = m_slots - 1; + unsigned h = get_hash(d); + unsigned idx = h & mask; + cell * c = m_table + idx; + if (c->is_free()) + return 0; + do { + if (equals(c->m_data, d)) { + return &(c->m_data); + } + CHS_CODE(const_cast(this)->m_collisions++;); + c = c->m_next; + } + while (c != 0); + return 0; + } + + bool find(T const & d, T & r) { + unsigned mask = m_slots - 1; + unsigned h = get_hash(d); + unsigned idx = h & mask; + cell * c = m_table + idx; + if (c->is_free()) + return false; + do { + if (equals(c->m_data, d)) { + r = c->m_data; + return true; + } + CHS_CODE(const_cast(this)->m_collisions++;); + c = c->m_next; + } + while (c != 0); + return false; + } + + void erase(T const & d) { + unsigned mask = m_slots - 1; + unsigned h = get_hash(d); + unsigned idx = h & mask; + cell * c = m_table + idx; + if (c->is_free()) + return; + cell * prev = 0; + do { + if (equals(c->m_data, d)) { + m_size--; + if (prev == 0) { + cell * next = c->m_next; + if (next == 0) { + m_used_slots--; + c->mark_free(); + SASSERT(c->is_free()); + } + else { + *c = *next; + recycle_cell(next); + } + } + else { + prev->m_next = c->m_next; + recycle_cell(c); + } + CASSERT("chashtable_bug", check_invariant()); + return; + } + CHS_CODE(m_collisions++;); + prev = c; + c = c->m_next; + } + while (c != 0); + } + + class iterator { + cell * m_it; + cell * m_end; + cell * m_list_it; + + void move_to_used() { + while (m_it != m_end) { + if (!m_it->is_free()) { + m_list_it = m_it; + return; + } + m_it++; + } + m_list_it = 0; + } + + public: + iterator(cell * start, cell * end): m_it(start), m_end(end) { move_to_used(); } + iterator():m_it(0), m_end(0), m_list_it(0) {} + T & operator*() { + return m_list_it->m_data; + } + T const & operator*() const { + return m_list_it->m_data; + } + T const * operator->() const { return &(operator*()); } + T * operator->() { return &(operator*()); } + iterator & operator++() { + m_list_it = m_list_it->m_next; + if (m_list_it == 0) { + m_it++; + move_to_used(); + } + return *this; + } + iterator operator++(int) { iterator tmp = *this; ++*this; return tmp; } + bool operator==(iterator const & it) const { return m_list_it == it.m_list_it; } + bool operator!=(iterator const & it) const { return m_list_it != it.m_list_it; } + }; + + iterator begin() const { return iterator(m_table, m_table + m_slots); } + iterator end() const { return iterator(); } + + void swap(chashtable & other) { + std::swap(m_table, other.m_table); + std::swap(m_capacity, other.m_capacity); + std::swap(m_init_slots, other.m_init_slots); + std::swap(m_init_cellar, other.m_init_cellar); + std::swap(m_slots, other.m_slots); + std::swap(m_used_slots, other.m_used_slots); + std::swap(m_size, other.m_size); +#ifdef CH_STATISTICS + std::swap(m_collisions, other.m_collisions); +#endif + std::swap(m_next_cell, other.m_next_cell); + std::swap(m_free_cell, other.m_free_cell); + } + + unsigned collisions() const { +#ifdef CH_STATISTICS + return m_collisions; +#else + return 0; +#endif + } + +#ifdef Z3DEBUG + bool check_invariant() const { + ptr_addr_hashtable visited; + unsigned sz = 0; + cell * _end = m_table + m_slots; + for (cell * it = m_table; it != _end; ++it) { + if (!it->is_free()) { + cell * list_it = it; + while (list_it != 0) { + sz++; + SASSERT(!visited.contains(list_it)); + visited.insert(list_it); + list_it = list_it->m_next; + } + } + } + SASSERT(m_size == sz); + return true; + } +#endif +}; + +template +class cmap { +public: + struct key_value { + Key m_key; + Value m_value; + key_value() {} + key_value(Key const & k):m_key(k) {} + key_value(Key const & k, Value const & v):m_key(k), m_value(v) {} + }; + +protected: + struct key_value_hash_proc : private HashProc { + key_value_hash_proc(HashProc const & p):HashProc(p) {} + unsigned operator()(key_value const & d) const { return HashProc::operator()(d.m_key); } + }; + + struct key_value_eq_proc : private EqProc { + key_value_eq_proc(EqProc const & p):EqProc(p) {} + bool operator()(key_value const & d1, key_value const & d2) const { return EqProc::operator()(d1.m_key, d2.m_key); } + }; + + typedef chashtable table; + + table m_table; + +public: + cmap(HashProc const & h = HashProc(), + EqProc const & e = EqProc(), + unsigned init_slots = table::default_init_slots, + unsigned init_cellar = table::default_init_cellar): + m_table(key_value_hash_proc(h), + key_value_eq_proc(e), + init_slots, + init_cellar) { + } + + typedef typename table::iterator iterator; + + void reset() { + m_table.reset(); + } + + void finalize() { + m_table.finalize(); + } + + bool empty() const { + return m_table.empty(); + } + + unsigned size() const { + return m_table.size(); + } + + unsigned capacity() const { + return m_table.capacity(); + } + + unsigned used_slots() const { + return m_table.used_slots(); + } + + unsigned collisions() const { + return m_table.collisions(); + } + + iterator begin() const { + return m_table.begin(); + } + + iterator end() const { + return m_table.end(); + } + + void insert(Key const & k, Value const & v) { + return m_table.insert(key_value(k, v)); + } + + key_value & insert_if_not_there(Key const & k, Value const & v) { + return m_table.insert_if_not_there(key_value(k, v)); + } + + bool contains(Key const & k) const { + return m_table.contains(key_value(k)); + } + + key_value * find_core(Key const & k) const { + return m_table.find_core(key_value(k)); + } + + bool find(Key const & k, Value & v) const { + key_value * e = m_table.find_core(key_value(k)); + if (e == 0) + return false; + v = e->m_value; + return true; + } + + void erase(Key const & k) { + m_table.erase(key_value(k)); + } +}; + +#endif diff --git a/lib/check_logic.cpp b/lib/check_logic.cpp new file mode 100644 index 000000000..550240a71 --- /dev/null +++ b/lib/check_logic.cpp @@ -0,0 +1,495 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + check_logic.cpp + +Abstract: + + Check whether a given assertion is in the correct logic or not + +Author: + + Leonardo de Moura (leonardo) 2011-08-11. + +Revision History: + +--*/ +#include"check_logic.h" +#include"arith_decl_plugin.h" +#include"array_decl_plugin.h" +#include"bv_decl_plugin.h" +#include"ast_pp.h" + +struct check_logic::imp { + ast_manager & m; + symbol m_logic; + arith_util m_a_util; + bv_util m_bv_util; + array_util m_ar_util; + bool m_uf; // true if the logic supports uninterpreted functions + bool m_arrays; // true if the logic supports arbitrary arrays + bool m_bv_arrays; // true if the logic supports only bv arrays + bool m_reals; // true if the logic supports reals + bool m_ints; // true if the logic supports integers + bool m_diff; // true if the logic supports difference logic only + bool m_nonlinear; // true if the logic supports nonlinear arithmetic + bool m_bvs; // true if the logic supports bit-vectors + bool m_quantifiers; // true if the logic supports quantifiers + bool m_unknown_logic; + + imp(ast_manager & _m):m(_m), m_a_util(m), m_bv_util(m), m_ar_util(m) { + reset(); + } + + void reset() { + m_uf = false; + m_arrays = false; + m_bv_arrays = false; + m_reals = false; + m_ints = false; + m_diff = false; + m_nonlinear = false; + m_bvs = false; + m_quantifiers = false; + m_unknown_logic = true; + } + + void set_logic(symbol const & logic) { + reset(); + m_unknown_logic = false; + if (logic == "AUFLIA") { + m_uf = true; + m_arrays = true; + m_ints = true; + m_quantifiers = true; + } + else if (logic == "AUFLIRA") { + m_uf = true; + m_arrays = true; + m_reals = true; + m_ints = true; + m_quantifiers = true; + } + else if (logic == "AUFNIRA") { + m_uf = true; + m_arrays = true; + m_reals = true; + m_ints = true; + m_nonlinear = true; + m_quantifiers = true; + } + else if (logic == "LRA") { + m_reals = true; + m_quantifiers = true; + } + else if (logic == "QF_ABV") { + m_bv_arrays = true; + m_bvs = true; + } + else if (logic == "QF_AUFBV") { + m_uf = true; + m_bv_arrays = true; + m_bvs = true; + } + else if (logic == "QF_UFBV") { + m_uf = true; + m_bvs = true; + } + else if (logic == "QF_AUFLIA") { + m_uf = true; + m_arrays = true; + m_ints = true; + } + else if (logic == "QF_AX") { + m_arrays = true; + } + else if (logic == "QF_BV") { + m_bvs = true; + } + else if (logic == "QF_IDL") { + m_ints = true; + m_diff = true; + } + else if (logic == "QF_RDL") { + m_reals = true; + m_diff = true; + } + else if (logic == "QF_LIA") { + m_ints = true; + } + else if (logic == "QF_LRA") { + m_reals = true; + } + else if (logic == "QF_NIA") { + m_ints = true; + m_nonlinear = true; + } + else if (logic == "QF_NRA") { + m_reals = true; + m_nonlinear = true; + } + else if (logic == "QF_UF") { + m_uf = true; + } + else if (logic == "QF_UFIDL") { + m_uf = true; + m_ints = true; + m_diff = true; + } + else if (logic == "QF_UFLIA") { + m_uf = true; + m_ints = true; + } + else if (logic == "QF_UFLRA") { + m_uf = true; + m_reals = true; + } + else if (logic == "QF_UFNRA") { + m_uf = true; + m_reals = true; + m_nonlinear = true; + } + else if (logic == "UFLRA") { + m_uf = true; + m_reals = true; + m_quantifiers = true; + } + else if (logic == "UFNIA") { + m_uf = true; + m_ints = true; + m_quantifiers = true; + m_nonlinear = true; + } + else if (logic == "UFBV") { + m_uf = true; + m_bvs = true; + m_quantifiers = true; + } + else { + m_unknown_logic = true; + } + + m_logic = logic; + } + + struct failed {}; + std::string m_last_error; + + void fail(char const * msg) { + m_last_error = msg; + throw failed(); + } + + void check_sort(sort * s) { + if (s->get_family_id() == null_family_id) { + if (!m_uf) + fail("logic does not support uninterpreted sorts"); + } + else if (m.is_bool(s)) { + return; + } + else if (m_a_util.is_int(s)) { + if (!m_ints) + fail("logic does not support integers"); + } + else if (m_a_util.is_real(s)) { + if (!m_reals) + fail("logic does not support reals"); + } + else if (m_bv_util.is_bv_sort(s)) { + if (!m_bvs) + fail("logic does not support bitvectors"); + } + else if (m_ar_util.is_array(s)) { + if (m_arrays) { + return; + } + else if (m_bv_arrays) { + if (get_array_arity(s) != 1) + fail("logic supports only unidimensional arrays"); + if (!m_bv_util.is_bv_sort(get_array_range(s)) || !m_bv_util.is_bv_sort(get_array_domain(s, 0))) + fail("logic supports only arrays from bitvectors to bitvectors"); + } + else { + fail("logic does not support arrays"); + } + } + } + + void operator()(var * n) { + if (!m_quantifiers) + fail("logic does not support quantifiers"); + check_sort(m.get_sort(n)); + } + + bool is_int(expr * t) { + if (m_a_util.is_uminus(t)) + t = to_app(t)->get_arg(0); + return m_a_util.is_numeral(t); + } + + bool is_numeral(expr * t) { + if (m_a_util.is_uminus(t)) + t = to_app(t)->get_arg(0); + // c + if (is_int(t)) + return true; + // c1/c2 + if (m_a_util.is_div(t) && is_int(to_app(t)->get_arg(0)) && is_int(to_app(t)->get_arg(1))) + return true; + return false; + } + + // check if n has at most one argument that is not numeral. + void check_mul(app * n) { + if (m_nonlinear) + return; // nothing to check + unsigned num_args = n->get_num_args(); + bool found_non_numeral = false; + for (unsigned i = 0; i < num_args; i++) { + if (!is_numeral(n->get_arg(i))) { + if (found_non_numeral) + fail("logic does not support nonlinear arithmetic"); + else + found_non_numeral = true; + } + } + } + + // check if the divisor is a numeral + void check_div(app * n) { + SASSERT(n->get_num_args() == 2); + if (!m_nonlinear && !is_numeral(n->get_arg(1))) + fail("logic does not support nonlinear arithmetic"); + } + + bool is_diff_var(expr * t) const { + if (is_app(t) && to_app(t)->get_decl()->get_family_id() == null_family_id) + return true; + if (m.is_ite(t)) + return true; + return false; + } + + void fail_non_diff(expr * t) { + TRACE("check_logic", tout << mk_pp(t, m) << "\n";); + fail("logic only supports difference arithmetic"); + } + + bool same_args(app * t) { + unsigned num_args = t->get_num_args(); + if (num_args == 0) + return false; + expr * arg = t->get_arg(0); + for (unsigned i = 1; i < num_args; i++) { + if (t->get_arg(i) != arg) + return false; + } + return true; + } + + bool is_arith(expr * t) const { + return m.get_sort(t)->get_family_id() == m_a_util.get_family_id(); + } + + bool is_offset(app * t) { + while (true) { + expr * non_numeral = 0; + unsigned num_args = t->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = t->get_arg(i); + if (is_numeral(arg)) + continue; + if (non_numeral != 0) + return false; + non_numeral = arg; + } + if (is_diff_var(non_numeral)) + return true; + if (!m_a_util.is_add(non_numeral) && !m_a_util.is_sub(non_numeral)) + return false; + t = to_app(non_numeral); + } + return true; + } + + bool is_diff_arg(expr * t) { + if (is_diff_var(t)) + return true; + if (is_numeral(t)) + return true; + if (m_a_util.is_add(t) || m_a_util.is_sub(t)) + return is_offset(to_app(t)); + return false; + } + + // Check if n is a diff logic predicate + void check_diff_predicate(app * n) { + expr * lhs = n->get_arg(0); + expr * rhs = n->get_arg(1); + if (!is_arith(lhs)) + return; // formula is not in arithmetic + if (is_diff_arg(lhs) && is_diff_arg(rhs)) + return; + if (is_numeral(lhs)) + std::swap(lhs, rhs); + if (!is_numeral(rhs)) + fail_non_diff(n); + if (!m_a_util.is_sub(lhs) || to_app(lhs)->get_num_args() != 2) + fail_non_diff(n); + expr * t1 = to_app(lhs)->get_arg(0); + expr * t2 = to_app(lhs)->get_arg(1); + if (is_diff_var(t1) && is_diff_var(t2)) + return; + if (m_a_util.is_add(t1) && m_a_util.is_add(t2)) { + // QF_RDL supports (<= (- (+ x ... x) (+ y ... y)) c) + if (to_app(t1)->get_num_args() != to_app(t2)->get_num_args()) + fail_non_diff(n); + if (!same_args(to_app(t1)) || !same_args(to_app(t2))) + fail_non_diff(n); + return; + } + fail_non_diff(n); + } + + void check_diff_arg(expr * t) { + if (!is_diff_arg(t)) + fail_non_diff(t); + } + + // Check if the arith args of n are of the form (t + k) where k is a numeral. + void check_diff_args(app * n) { + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + if (is_arith(n)) + check_diff_arg(n); + } + } + + void operator()(app * n) { + sort * s = m.get_sort(n); + check_sort(s); + func_decl * f = n->get_decl(); + family_id fid = f->get_family_id(); + if (fid == null_family_id) { + if (!m_uf && f->get_arity() > 0) + fail("logic does not support uninterpreted functions"); + if (m_diff) + check_diff_args(n); + } + else if (fid == m_a_util.get_family_id()) { + if (m_a_util.is_mul(n)) + check_mul(n); + else if (m_a_util.is_div(n) || m_a_util.is_idiv(n) || m_a_util.is_rem(n) || m_a_util.is_mod(n)) + check_div(n); + if (m_diff) { + if (m_a_util.is_le(n) || m_a_util.is_lt(n) || m_a_util.is_ge(n) || m_a_util.is_gt(n)) + check_diff_predicate(n); + } + if (!m_ints || !m_reals) { + if (m_a_util.is_to_real(n) || m_a_util.is_to_int(n)) + fail("logic does not support casting operators"); + } + } + else if (fid == m_bv_util.get_family_id()) { + // nothing to check... + } + else if (fid == m_ar_util.get_family_id()) { + // nothing to check... + if (m_diff) + check_diff_args(n); + } + else if (fid == m.get_basic_family_id()) { + // nothing to check... + if (m_diff) { + if (m.is_eq(n)) + check_diff_predicate(n); + else if (m.is_distinct(n) || m.is_ite(n)) + check_diff_args(n); + } + } + else if (m.is_builtin_family_id(fid)) { + // nothing to check + } + else { + fail("logic does not support theory"); + } + } + + void operator()(quantifier * n) { + if (!m_quantifiers) + fail("logic does not support quantifiers"); + } + + bool operator()(expr * n) { + if (m_unknown_logic) + return true; + try { + quick_for_each_expr(*this, n); + return true; + } + catch (failed) { + return false; + } + } + + bool operator()(func_decl * f) { + if (m_unknown_logic) + return true; + try { + unsigned arity = f->get_arity(); + if (arity > 0) { + if (!m_uf) + fail("logic does not support uninterpreted functions"); + for (unsigned i = 0; i < arity; i++) + check_sort(f->get_domain(i)); + } + check_sort(f->get_range()); + return true; + } + catch (failed) { + return false; + } + } +}; + +check_logic::check_logic() { + m_imp = 0; +} + +check_logic::~check_logic() { + if (m_imp) + dealloc(m_imp); +} + +void check_logic::reset() { + if (m_imp) + dealloc(m_imp); + m_imp = 0; +} + +void check_logic::set_logic(ast_manager & m, symbol const & logic) { + reset(); + m_imp = alloc(imp, m); + m_imp->set_logic(logic); +} + +bool check_logic::operator()(expr * n) { + if (m_imp) + return m_imp->operator()(n); + return true; +} + +bool check_logic::operator()(func_decl * f) { + if (m_imp) + return m_imp->operator()(f); + return true; +} + +char const * check_logic::get_last_error() const { + if (m_imp) + return m_imp->m_last_error.c_str(); + return ""; +} diff --git a/lib/check_logic.h b/lib/check_logic.h new file mode 100644 index 000000000..8521ed57b --- /dev/null +++ b/lib/check_logic.h @@ -0,0 +1,37 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + check_logic.h + +Abstract: + + Check whether a given assertion is in the correct logic or not + +Author: + + Leonardo de Moura (leonardo) 2011-08-11. + +Revision History: + +--*/ +#ifndef _CHECK_LOGIC_H_ +#define _CHECK_LOGIC_H_ + +#include"assertion_set.h" + +class check_logic { + struct imp; + imp * m_imp; +public: + check_logic(); + ~check_logic(); + void reset(); + void set_logic(ast_manager & m, symbol const & logic); + bool operator()(expr * n); + bool operator()(func_decl * f); + char const * get_last_error() const; +}; + +#endif diff --git a/lib/check_sat_result.h b/lib/check_sat_result.h new file mode 100644 index 000000000..fcb42acb7 --- /dev/null +++ b/lib/check_sat_result.h @@ -0,0 +1,69 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + check_sat_result.h + +Abstract: + Abstract interface for storing the result produced by + a check_sat like command + +Author: + + Leonardo (leonardo) 2012-01-23 + +Notes: + +--*/ +#ifndef _CHECK_SAT_RESULT_H_ +#define _CHECK_SAT_RESULT_H_ + +#include"model.h" +#include"lbool.h" +#include"statistics.h" + +class check_sat_result { +protected: + unsigned m_ref_count; + lbool m_status; +public: + check_sat_result():m_ref_count(0), m_status(l_undef) {} + virtual ~check_sat_result() {} + 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 set_status(lbool r) { m_status = r; } + lbool status() const { return m_status; } + virtual void collect_statistics(statistics & st) const = 0; + virtual void get_unsat_core(ptr_vector & r) = 0; + virtual void get_model(model_ref & m) = 0; + virtual proof * get_proof() = 0; + virtual std::string reason_unknown() const = 0; + virtual void get_labels(svector & r) = 0; +}; + +struct simple_check_sat_result : public check_sat_result { + statistics m_stats; + model_ref m_model; + expr_ref_vector m_core; + proof_ref m_proof; + std::string m_unknown; + + simple_check_sat_result(ast_manager & m): + m_core(m), + m_proof(m) { + } + virtual ~simple_check_sat_result() {} + virtual void collect_statistics(statistics & st) const { st.copy(m_stats); } + virtual void get_unsat_core(ptr_vector & r) { if (m_status == l_false) r.append(m_core.size(), m_core.c_ptr()); } + virtual void get_model(model_ref & m) { + if (m_status != l_false) m = m_model; else m = 0; + } + virtual proof * get_proof() { return m_status == l_false ? m_proof.get() : 0; } + virtual std::string reason_unknown() const { + return m_unknown; + } + virtual void get_labels(svector & r) {} +}; + +#endif diff --git a/lib/cmd_context.cpp b/lib/cmd_context.cpp new file mode 100644 index 000000000..6127a5351 --- /dev/null +++ b/lib/cmd_context.cpp @@ -0,0 +1,1599 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + cmd_context.cpp + +Abstract: + Command context. + +Author: + + Leonardo (leonardo) 2011-03-01 + +Notes: + +--*/ +#include +#include"tptr.h" +#include"cmd_context.h" +#include"func_decl_dependencies.h" +#include"arith_decl_plugin.h" +#include"bv_decl_plugin.h" +#include"array_decl_plugin.h" +#include"datatype_decl_plugin.h" +#include"seq_decl_plugin.h" +#include"float_decl_plugin.h" +#include"ast_pp.h" +#include"var_subst.h" +#include"pp.h" +#include"ast_smt2_pp.h" +#include"basic_cmds.h" +#include"cancel_eh.h" +#include"scoped_ctrl_c.h" +#include"dec_ref_util.h" +#include"decl_collector.h" +#include"well_sorted.h" +#include"model_evaluator.h" + +func_decls::func_decls(ast_manager & m, func_decl * f): + m_decls(TAG(func_decl*, f, 0)) { + m.inc_ref(f); +} + +void func_decls::finalize(ast_manager & m) { + TRACE("cmd_context_detail", tout << "finalizing func_decls...\n";); + if (GET_TAG(m_decls) == 0) { + m.dec_ref(UNTAG(func_decl *, m_decls)); + } + else { + TRACE("func_decls", tout << "finalize...\n";); + func_decl_set * fs = UNTAG(func_decl_set *, m_decls); + func_decl_set::iterator it = fs->begin(); + func_decl_set::iterator end = fs->end(); + for (; it != end; ++it) { + TRACE("func_decls", tout << "dec_ref of " << (*it)->get_name() << " ref_count: " << (*it)->get_ref_count() << "\n";); + m.dec_ref(*it); + } + dealloc(fs); + } + m_decls = 0; +} + +bool func_decls::contains(func_decl * f) const { + if (GET_TAG(m_decls) == 0) { + return UNTAG(func_decl*, m_decls) == f; + } + else { + func_decl_set * fs = UNTAG(func_decl_set *, m_decls); + return fs->contains(f); + } +} + +bool func_decls::insert(ast_manager & m, func_decl * f) { + if (contains(f)) + return false; + m.inc_ref(f); + if (m_decls == 0) { + m_decls = TAG(func_decl*, f, 0); + } + else if (GET_TAG(m_decls) == 0) { + func_decl_set * new_fs = alloc(func_decl_set); + new_fs->insert(UNTAG(func_decl*, m_decls)); + new_fs->insert(f); + m_decls = TAG(func_decl*, new_fs, 1); + } + else { + func_decl_set * fs = UNTAG(func_decl_set*, m_decls); + fs->insert(f); + } + return true; +} + +void func_decls::erase(ast_manager & m, func_decl * f) { + if (!contains(f)) + return; + if (GET_TAG(m_decls) == 0) { + m.dec_ref(f); + m_decls = 0; + } + else { + func_decl_set * fs = UNTAG(func_decl_set *, m_decls); + fs->erase(f); + m.dec_ref(f); + if (fs->empty()) { + dealloc(fs); + m_decls = 0; + } + } +} + +/** + \brief Return true if func_decls contains a declaration different from f, but with the same domain. +*/ +bool func_decls::clash(func_decl * f) const { + if (m_decls == 0) + return false; + if (GET_TAG(m_decls) == 0) + return false; + func_decl_set * fs = UNTAG(func_decl_set *, m_decls); + func_decl_set::iterator it = fs->begin(); + func_decl_set::iterator end = fs->end(); + for (; it != end; ++it) { + func_decl * g = *it; + if (g == f) + continue; + if (g->get_arity() != f->get_arity()) + continue; + unsigned num = g->get_arity(); + unsigned i; + for (i = 0; i < num; i++) + if (g->get_domain(i) != f->get_domain(i)) + break; + if (i == num) + return true; + } + return false; +} + +bool func_decls::more_than_one() const { + if (m_decls == 0 || GET_TAG(m_decls) == 0) + return false; + func_decl_set * fs = UNTAG(func_decl_set *, m_decls); + return fs->size() > 1; +} + +func_decl * func_decls::first() const { + if (m_decls == 0) + return 0; + if (GET_TAG(m_decls) == 0) + return UNTAG(func_decl*, m_decls); + func_decl_set * fs = UNTAG(func_decl_set *, m_decls); + SASSERT(!fs->empty()); + return *(fs->begin()); +} + +func_decl * func_decls::find(unsigned arity, sort * const * domain, sort * range) const { + if (!more_than_one()) + return first(); + func_decl_set * fs = UNTAG(func_decl_set *, m_decls); + func_decl_set::iterator it = fs->begin(); + func_decl_set::iterator end = fs->end(); + for (; it != end; it++) { + func_decl * f = *it; + if (range != 0 && f->get_range() != range) + continue; + if (f->get_arity() != arity) + continue; + unsigned i = 0; + for (i = 0; i < arity; i++) { + if (f->get_domain(i) != domain[i]) + break; + } + if (i == arity) + return f; + } + return 0; +} + +func_decl * func_decls::find(ast_manager & m, unsigned num_args, expr * const * args, sort * range) const { + if (!more_than_one()) + first(); + ptr_buffer sorts; + for (unsigned i = 0; i < num_args; i++) + sorts.push_back(m.get_sort(args[i])); + return find(num_args, sorts.c_ptr(), range); +} + +ast_object_ref::ast_object_ref(cmd_context & ctx, ast * a):m_ast(a) { + ctx.m().inc_ref(a); +} + +void ast_object_ref::finalize(cmd_context & ctx) { + ctx.m().dec_ref(m_ast); +} + +void stream_ref::set(char const * name) { + if (!name) { + throw cmd_exception("invalid stream name"); + } + reset(); + SASSERT(!m_owner); + if (strcmp(name, "stdout") == 0) { + m_name = "stdout"; + m_stream = &std::cout; + } + else if (strcmp(name, "stderr") == 0) { + m_name = "stderr"; + m_stream = &std::cerr; + } + else { + m_stream = alloc(std::ofstream, name, std::ios_base::app); + m_name = name; + m_owner = true; + if (m_stream->bad() || m_stream->fail()) { + reset(); + std::string msg = "failed to set output stream '"; + msg += name; + msg += "'"; + throw cmd_exception(msg); + } + SASSERT(m_stream); + } +} + +void stream_ref::reset() { + if (m_owner) + dealloc(m_stream); + m_name = m_default_name; + m_stream = &m_default; + m_owner = false; +} + +class cmd_context::pp_env : public smt2_pp_environment { +protected: + cmd_context & m_owner; + arith_util m_autil; + bv_util m_bvutil; + array_util m_arutil; + float_util m_futil; + datalog::dl_decl_util m_dlutil; + + format_ns::format * pp_fdecl_name(symbol const & s, func_decls const & fs, func_decl * f, unsigned & len) { + format_ns::format * f_name = smt2_pp_environment::pp_fdecl_name(s, len); + if (!fs.more_than_one()) + return f_name; + if (!fs.clash(f)) + return f_name; + return pp_as(f_name, f->get_range()); + } + + format_ns::format * pp_fdecl_ref_core(symbol const & s, func_decls const & fs, func_decl * f) { + unsigned len; + format_ns::format * f_name = smt2_pp_environment::pp_fdecl_name(s, len); + if (!fs.more_than_one()) + return f_name; + return pp_signature(f_name, f); + } + +public: + pp_env(cmd_context & o):m_owner(o), m_autil(o.m()), m_bvutil(o.m()), m_arutil(o.m()), m_futil(o.m()), m_dlutil(o.m()) {} + virtual ~pp_env() {} + virtual ast_manager & get_manager() const { return m_owner.m(); } + virtual arith_util & get_autil() { return m_autil; } + virtual bv_util & get_bvutil() { return m_bvutil; } + virtual array_util & get_arutil() { return m_arutil; } + virtual float_util & get_futil() { return m_futil; } + virtual datalog::dl_decl_util& get_dlutil() { return m_dlutil; } + virtual bool uses(symbol const & s) const { + return + m_owner.m_builtin_decls.contains(s) || + m_owner.m_func_decls.contains(s); + } + virtual format_ns::format * pp_sort(sort * s) { + return m_owner.pp(s); + } + virtual format_ns::format * pp_fdecl(func_decl * f, unsigned & len) { + symbol s = f->get_name(); + func_decls fs; + if (m_owner.m_func_decls.find(s, fs) && fs.contains(f)) { + return pp_fdecl_name(s, fs, f, len); + } + if (m_owner.m_func_decl2alias.find(f, s) && m_owner.m_func_decls.find(s, fs) && fs.contains(f)) { + return pp_fdecl_name(s, fs, f, len); + } + return smt2_pp_environment::pp_fdecl(f, len); + } + virtual format_ns::format * pp_fdecl_ref(func_decl * f) { + symbol s = f->get_name(); + func_decls fs; + if (m_owner.m_func_decls.find(s, fs) && fs.contains(f)) { + return pp_fdecl_ref_core(s, fs, f); + } + if (m_owner.m_func_decl2alias.find(f, s) && m_owner.m_func_decls.find(s, fs) && fs.contains(f)) { + return pp_fdecl_ref_core(s, fs, f); + } + return smt2_pp_environment::pp_fdecl_ref(f); + } +}; + +cmd_context::cmd_context(front_end_params & params, bool main_ctx, ast_manager * m, symbol const & l): + m_main_ctx(main_ctx), + m_params(params), + m_logic(l), + m_interactive_mode(false), + m_global_decls(!params.m_smtlib2_compliant), // SMTLIB 2.0 uses scoped decls. + m_print_success(false), // params.m_smtlib2_compliant), + m_random_seed(0), + m_produce_unsat_cores(false), + m_produce_assignments(false), + m_status(UNKNOWN), + m_numeral_as_real(false), + m_ignore_check(false), + m_exit_on_error(false), + m_manager(m), + m_own_manager(m == 0), + m_pmanager(0), + m_sexpr_manager(0), + m_regular("stdout", std::cout), + m_diagnostic("stderr", std::cerr) { + SASSERT(m != 0 || !has_manager()); + install_basic_cmds(*this); + install_ext_basic_cmds(*this); + install_tactic_cmds(*this); + SASSERT(m != 0 || !has_manager()); + if (m) + init_external_manager(); + if (m_main_ctx) { + set_verbose_stream(diagnostic_stream()); + } +} + +cmd_context::~cmd_context() { + if (m_main_ctx) { + set_verbose_stream(std::cerr); + } + reset(true); + finalize_cmds(); + finalize_tactic_cmds(); + finalize_probes(); +} + +cmd_context::check_sat_state cmd_context::cs_state() const { + if (m_check_sat_result.get() == 0) + return css_clear; + switch (m_check_sat_result->status()) { + case l_true: return css_sat; + case l_false: return css_unsat; + default: return css_unknown; + } +} + +void cmd_context::register_builtin_sorts(decl_plugin * p) { + svector names; + p->get_sort_names(names, m_logic); + family_id fid = p->get_family_id(); + svector::const_iterator it = names.begin(); + svector::const_iterator end = names.end(); + for (; it != end; ++it) { + psort_decl * d = pm().mk_psort_builtin_decl((*it).m_name, fid, (*it).m_kind); + insert(d); + } +} + +void cmd_context::register_builtin_ops(decl_plugin * p) { + svector names; + p->get_op_names(names, m_logic); + family_id fid = p->get_family_id(); + svector::const_iterator it = names.begin(); + svector::const_iterator end = names.end(); + for (; it != end; ++it) { + if (m_builtin_decls.contains((*it).m_name)) { + builtin_decl & d = m_builtin_decls.find((*it).m_name); + builtin_decl * new_d = alloc(builtin_decl, fid, (*it).m_kind, d.m_next); + d.m_next = new_d; + m_extra_builtin_decls.push_back(new_d); + } + else { + m_builtin_decls.insert((*it).m_name, builtin_decl(fid, (*it).m_kind)); + } + } +} + +void cmd_context::register_plugin(symbol const & name, decl_plugin * p, bool install_names) { + m_manager->register_plugin(name, p); + if (install_names) { + register_builtin_sorts(p); + register_builtin_ops(p); + } +} + +bool cmd_context::logic_has_arith_core(symbol const & s) const { + return + s == "QF_LRA" || + s == "QF_LIA" || + s == "QF_RDL" || + s == "QF_IDL" || + s == "QF_AUFLIA" || + s == "QF_AUFLIRA" || + s == "QF_AUFNIA" || + s == "QF_AUFNIRA" || + s == "QF_UFLIA" || + s == "QF_UFLRA" || + s == "QF_UFIDL" || + s == "QF_UFRDL" || + s == "QF_NIA" || + s == "QF_NRA" || + s == "QF_NIRA" || + s == "QF_UFNRA" || + s == "QF_UFNIA" || + s == "QF_UFNIRA" || + s == "QF_BVRE" || + s == "AUFLIA" || + s == "AUFLIRA" || + s == "AUFNIA" || + s == "AUFNIRA" || + s == "UFLIA" || + s == "UFLRA" || + s == "UFNRA" || + s == "UFNIRA" || + s == "UFNIA" || + s == "LIA" || + s == "LRA" || + s == "QF_FPA" ; +} + +bool cmd_context::logic_has_arith() const { + return !has_logic() || logic_has_arith_core(m_logic); +} + +bool cmd_context::logic_has_bv_core(symbol const & s) const { + return + s == "UFBV" || + s == "AUFBV" || + s == "ABV" || + s == "BV" || + s == "QF_BV" || + s == "QF_UFBV" || + s == "QF_ABV" || + s == "QF_AUFBV" || + s == "QF_BVRE"; +} + +bool cmd_context::logic_has_bv() const { + return !has_logic() || logic_has_bv_core(m_logic); +} + +bool cmd_context::logic_has_seq_core(symbol const& s) const { + return + s == "QF_BVRE"; + +} + +bool cmd_context::logic_has_seq() const { + return !has_logic() || logic_has_seq_core(m_logic); +} + +bool cmd_context::logic_has_floats() const { + return !has_logic() || m_logic == "QF_FPA"; +} + +bool cmd_context::logic_has_array_core(symbol const & s) const { + return + s == "QF_AX" || + s == "QF_AUFLIA" || + s == "QF_AUFLIRA" || + s == "QF_AUFNIA" || + s == "QF_AUFNIRA" || + s == "AUFLIA" || + s == "AUFLIRA" || + s == "AUFNIA" || + s == "AUFNIRA" || + s == "AUFBV" || + s == "ABV" || + s == "QF_ABV" || + s == "QF_AUFBV"; +} + +bool cmd_context::logic_has_array() const { + return !has_logic() || logic_has_array_core(m_logic); +} + +bool cmd_context::logic_has_datatype() const { + return !has_logic(); +} + +void cmd_context::init_manager_core(bool new_manager) { + SASSERT(m_manager != 0); + SASSERT(m_pmanager != 0); + m_dt_eh = alloc(dt_eh, *this); + m_pmanager->set_new_datatype_eh(m_dt_eh.get()); + if (new_manager) { + decl_plugin * basic = m_manager->get_plugin(m_manager->get_basic_family_id()); + register_builtin_sorts(basic); + register_builtin_ops(basic); + // the manager was created by the command context. + register_plugin(symbol("arith"), alloc(arith_decl_plugin), logic_has_arith()); + register_plugin(symbol("bv"), alloc(bv_decl_plugin), logic_has_bv()); + register_plugin(symbol("array"), alloc(array_decl_plugin), logic_has_array()); + register_plugin(symbol("datatype"), alloc(datatype_decl_plugin), logic_has_datatype()); + register_plugin(symbol("seq"), alloc(seq_decl_plugin), logic_has_seq()); + register_plugin(symbol("float"), alloc(float_decl_plugin), logic_has_floats()); + } + else { + // the manager was created by an external module, we must register all plugins available in the manager. + svector fids; + m_manager->get_range(fids); + svector::iterator it = fids.begin(); + svector::iterator end = fids.end(); + for (; it != end; ++it) { + decl_plugin * p = m_manager->get_plugin(*it); + if (p) { + register_builtin_sorts(p); + register_builtin_ops(p); + } + } + } + if (!has_logic()) { + // add list type only if the logic is not specified. + // it prevents clashes with builtin types. + insert(pm().mk_plist_decl()); + } + if (m_solver) + m_solver->init(m(), m_logic); + m_check_logic.set_logic(m(), m_logic); +} + +void cmd_context::init_manager() { + SASSERT(m_manager == 0); + SASSERT(m_pmanager == 0); + m_check_sat_result = 0; + m_manager = alloc(ast_manager, m_params.m_proof_mode, m_params.m_trace_stream); + m_pmanager = alloc(pdecl_manager, *m_manager); + init_manager_core(true); +} + +void cmd_context::init_external_manager() { + SASSERT(m_manager != 0); + SASSERT(m_pmanager == 0); + m_pmanager = alloc(pdecl_manager, *m_manager); + init_manager_core(false); +} + +bool cmd_context::supported_logic(symbol const & s) const { + return s == "QF_UF" || s == "UF" || + logic_has_arith_core(s) || logic_has_bv_core(s) || + logic_has_array_core(s) || logic_has_seq_core(s) || + s == "QF_FPA"; +} + +void cmd_context::set_logic(symbol const & s) { + if (has_logic()) + throw cmd_exception("the logic has already been set"); + if (has_manager() && m_main_ctx) + throw cmd_exception("logic must be set before initialization"); + if (!supported_logic(s)) { + warning_msg("unknown logic, ignoring set-logic command"); + return; + } + m_logic = s; + if (is_logic("QF_RDL") || + is_logic("QF_LRA") || + is_logic("UFLRA") || + is_logic("LRA") || + is_logic("RDL") || + is_logic("QF_NRA") || + is_logic("QF_UFNRA") || + is_logic("QF_UFLRA")) + m_numeral_as_real = true; +} + +std::string cmd_context::reason_unknown() const { + if (m_check_sat_result.get() == 0) + throw cmd_exception("state of the most recent check-sat command is not unknown"); + return m_check_sat_result->reason_unknown(); +} + +bool cmd_context::is_func_decl(symbol const & s) const { + return m_builtin_decls.contains(s) || m_func_decls.contains(s); +} + +void cmd_context::insert(symbol const & s, func_decl * f) { + m_check_sat_result = 0; + if (!m_check_logic(f)) { + throw cmd_exception(m_check_logic.get_last_error()); + } + if (m_macros.contains(s)) { + throw cmd_exception("invalid declaration, named expression already defined with this name ", s); + } + if (m_builtin_decls.contains(s)) { + throw cmd_exception("invalid declaration, builtin symbol ", s); + } + dictionary::entry * e = m_func_decls.insert_if_not_there2(s, func_decls()); + func_decls & fs = e->get_data().m_value; + if (!fs.insert(m(), f)) { + std::string msg = "invalid declaration, "; + msg += f->get_arity() == 0 ? "constant" : "function"; + msg += " '"; + msg += s.str(); + msg += "' (whith the given signature) already declared"; + throw cmd_exception(msg.c_str()); + } + if (s != f->get_name()) { + TRACE("func_decl_alias", tout << "adding alias for: " << f->get_name() << ", alias: " << s << "\n";); + m_func_decl2alias.insert(f, s); + } + if (!m_global_decls) { + m_func_decls_stack.push_back(sf_pair(s, f)); + } + TRACE("cmd_context", tout << "new sort decl\n" << mk_pp(f, m()) << "\n";); +} + +void cmd_context::insert(symbol const & s, psort_decl * p) { + m_check_sat_result = 0; + if (m_psort_decls.contains(s)) { + throw cmd_exception("sort already defined ", s); + } + pm().inc_ref(p); + m_psort_decls.insert(s, p); + if (!m_global_decls) { + m_psort_decls_stack.push_back(s); + } + TRACE("cmd_context", tout << "new sort decl\n"; p->display(tout); tout << "\n";); +} + +void cmd_context::insert(symbol const & s, unsigned arity, expr * t) { + m_check_sat_result = 0; + if (m_builtin_decls.contains(s)) { + throw cmd_exception("invalid macro/named expression, builtin symbol ", s); + } + if (m_macros.contains(s)) { + throw cmd_exception("named expression already defined"); + } + if (m_func_decls.contains(s)) { + throw cmd_exception("invalid named expression, declaration already defined with this name ", s); + } + m().inc_ref(t); + TRACE("insert_macro", tout << "new macro " << arity << "\n" << mk_pp(t, m()) << "\n";); + m_macros.insert(s, macro(arity, t)); + if (!m_global_decls) { + m_macros_stack.push_back(s); + } +} + +void cmd_context::insert(cmd * c) { + symbol const & s = c->get_name(); + cmd * old_c; + if (m_cmds.find(s, old_c) && c != old_c) { + old_c->finalize(*this); + dealloc(old_c); + } + m_cmds.insert(s, c); +} + +void cmd_context::insert_user_tactic(symbol const & s, sexpr * d) { + sm().inc_ref(d); + sexpr * old_d; + if (m_user_tactic_decls.find(s, old_d)) { + sm().dec_ref(old_d); + } + m_user_tactic_decls.insert(s, d); +} + +void cmd_context::insert(symbol const & s, object_ref * r) { + r->inc_ref(*this); + object_ref * old_r = 0; + if (m_object_refs.find(s, old_r)) { + old_r->dec_ref(*this); + } + m_object_refs.insert(s, r); +} + + +func_decl * cmd_context::find_func_decl(symbol const & s) const { + builtin_decl d; + if (m_builtin_decls.find(s, d)) { + try { + // Remark: ignoring m_next of d. We do not allow two different theories to define the same constant name. + func_decl * f; + f = m().mk_func_decl(d.m_fid, d.m_decl, 0, 0, 0, static_cast(0), 0); + if (f != 0) + return f; + } + catch (ast_exception &) { + } + throw cmd_exception("invalid function declaration reference, must provide signature for builtin symbol ", s); + } + if (m_macros.contains(s)) + throw cmd_exception("invalid function declaration reference, named expressions (aka macros) cannot be referenced ", s); + func_decls fs; + if (m_func_decls.find(s, fs)) { + if (fs.more_than_one()) + throw cmd_exception("ambiguous function declaration reference, provide full signature to disumbiguate ( (*) ) ", s); + return fs.first(); + } + throw cmd_exception("invalid function declaration reference, unknown function ", s); + return 0; +} + +/** + \brief Select a builtin_decl from the list starting at first. + We select the decl d s.t. d->m_fid == target_id + If there is none that satisfies this condition, we return first. + + This is a HACK for supporting arithmetic and floating-point arithmetic. + These are two different theories in Z3, but they share builtin symbol names: +, -, *, /, <, <=, >, >= +*/ +static builtin_decl const & peek_builtin_decl(builtin_decl const & first, family_id target_id) { + builtin_decl const * curr = &first; + while (curr != 0) { + if (curr->m_fid == target_id) + return *curr; + curr = curr->m_next; + } + return first; +} + +func_decl * cmd_context::find_func_decl(symbol const & s, unsigned num_indices, unsigned const * indices, + unsigned arity, sort * const * domain, sort * range) const { + builtin_decl d; + if (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. + if (d.m_decl != 0 && arity > 0) { + builtin_decl const & d2 = peek_builtin_decl(d, domain[0]->get_family_id()); + fid = d2.m_fid; + k = d2.m_decl; + } + func_decl * f; + if (num_indices == 0) { + f = m().mk_func_decl(fid, k, 0, 0, arity, domain, range); + } + else { + buffer ps; + for (unsigned i = 0; i < num_indices; i++) + ps.push_back(parameter(indices[i])); + f = m().mk_func_decl(fid, k, num_indices, ps.c_ptr(), arity, domain, range); + } + if (f == 0) + throw cmd_exception("invalid function declaration reference, invalid builtin reference ", s); + return f; + } + + if (m_macros.contains(s)) + throw cmd_exception("invalid function declaration reference, named expressions (aka macros) cannot be referenced ", s); + + if (num_indices > 0) + throw cmd_exception("invalid indexed function declaration reference, unknown builtin function ", s); + + func_decl * f = 0; + func_decls fs; + if (m_func_decls.find(s, fs)) { + f = fs.find(arity, domain, range); + } + if (f == 0) + throw cmd_exception("invalid function declaration reference, unknown function ", s); + return f; +} + +psort_decl * cmd_context::find_psort_decl(symbol const & s) const { + psort_decl * p = 0; + m_psort_decls.find(s, p); + return p; +} + +cmd_context::macro cmd_context::find_macro(symbol const & s) const { + macro m; + m_macros.find(s, m); + return m; +} + +cmd * cmd_context::find_cmd(symbol const & s) const { + cmd * c = 0; + m_cmds.find(s, c); + return c; +} + +sexpr * cmd_context::find_user_tactic(symbol const & s) const { + sexpr * n = 0; + m_user_tactic_decls.find(s, n); + return n; +} + +object_ref * cmd_context::find_object_ref(symbol const & s) const { + object_ref * r = 0; + m_object_refs.find(s, r); + if (r == 0) throw cmd_exception("unknown global variable ", s); + return r; +} + +#define CHECK_SORT(T) if (params().m_well_sorted_check) m().check_sorts_core(T) + +void cmd_context::mk_const(symbol const & s, expr_ref & result) const { + mk_app(s, 0, 0, 0, 0, 0, result); +} + +void cmd_context::mk_app(symbol const & s, unsigned num_args, expr * const * args, unsigned num_indices, parameter const * indices, sort * range, + expr_ref & result) const { + builtin_decl d; + if (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 the sort of args[0] (if available) to decide which plugin we use. + if (d.m_decl != 0 && num_args > 0) { + builtin_decl const & d2 = peek_builtin_decl(d, m().get_sort(args[0])->get_family_id()); + fid = d2.m_fid; + k = d2.m_decl; + } + if (num_indices == 0) { + result = m().mk_app(fid, k, 0, 0, num_args, args, range); + } + else { + result = m().mk_app(fid, k, num_indices, indices, num_args, args, range); + } + if (result.get() == 0) + throw cmd_exception("invalid builtin application ", s); + CHECK_SORT(result.get()); + return; + } + if (num_indices > 0) + throw cmd_exception("invalid use of indexed indentifier, unknown builtin function ", s); + macro _m; + if (m_macros.find(s, _m)) { + if (num_args != _m.first) + throw cmd_exception("invalid defined function application, incorrect number of arguments ", s); + if (num_args == 0) { + result = _m.second; + return; + } + SASSERT(num_args > 0); + TRACE("macro_bug", tout << "m_well_sorted_check: " << params().m_well_sorted_check << "\n"; + tout << "s: " << s << "\n"; + tout << "body:\n" << mk_ismt2_pp(_m.second, m()) << "\n"; + tout << "args:\n"; for (unsigned i = 0; i < num_args; i++) tout << mk_ismt2_pp(args[i], m()) << "\n" << mk_pp(m().get_sort(args[i]), m()) << "\n";); + var_subst subst(m()); + subst(_m.second, num_args, args, result); + if (params().m_well_sorted_check && !is_well_sorted(m(), result)) + throw cmd_exception("invalid macro application, sort mismatch ", s); + return; + } + + func_decls fs; + if (!m_func_decls.find(s, fs)) { + if (num_args == 0) { + throw cmd_exception("unknown constant ", s); + } + else + throw cmd_exception("unknown function/constant ", s); + } + + if (num_args == 0 && range == 0) { + if (fs.more_than_one()) + throw cmd_exception("ambiguous constant reference, more than one constant with the same sort, use a qualified expression (as ) to disumbiguate ", s); + func_decl * f = fs.first(); + if (f == 0) + throw cmd_exception("unknown constant ", s); + if (f->get_arity() != 0) + throw cmd_exception("invalid function application, missing arguments ", s); + result = m().mk_const(f); + return; + } + else { + func_decl * f = fs.find(m(), num_args, args, range); + if (f == 0) + throw cmd_exception("unknown constant ", s); + if (params().m_well_sorted_check) + m().check_sort(f, num_args, args); + result = m().mk_app(f, num_args, args); + return; + } +} + +void cmd_context::erase_func_decl(symbol const & s) { + if (!global_decls()) { + throw cmd_exception("function declarations can only be erased when global declarations (instead of scoped) are used"); + } + func_decls fs; + m_func_decls.find(s, fs); + while (!fs.empty()) { + func_decl * f = fs.first(); + if (s != f->get_name()) { + SASSERT(m_func_decl2alias.contains(f)); + m_func_decl2alias.erase(f); + } + fs.erase(m(), f); + } + fs.finalize(m()); + m_func_decls.erase(s); +} + +void cmd_context::erase_func_decl_core(symbol const & s, func_decl * f) { + func_decls fs; + m_func_decls.find(s, fs); + if (fs.contains(f)) { + if (s != f->get_name()) { + SASSERT(m_func_decl2alias.contains(f)); + m_func_decl2alias.erase(f); + } + fs.erase(m(), f); + if (fs.empty()) + m_func_decls.erase(s); + } +} + +void cmd_context::erase_func_decl(symbol const & s, func_decl * f) { + if (!global_decls()) { + throw cmd_exception("function declarations can only be erased when global (instead of scoped) declarations are used"); + } + erase_func_decl_core(s, f); +} + +void cmd_context::erase_psort_decl_core(symbol const & s) { + psort_decl * p; + if (m_psort_decls.find(s, p)) { + pm().dec_ref(p); + m_psort_decls.erase(s); + } +} + +void cmd_context::erase_psort_decl(symbol const & s) { + if (!global_decls()) { + throw cmd_exception("sort declarations can only be erased when global (instead of scoped) declarations are used"); + } + erase_psort_decl_core(s); +} + +void cmd_context::erase_macro_core(symbol const & s) { + macro _m; + if (m_macros.find(s, _m)) { + m().dec_ref(_m.second); + m_macros.erase(s); + } +} + +void cmd_context::erase_macro(symbol const & s) { + if (!global_decls()) { + throw cmd_exception("macros (aka named expressions) can only be erased when global (instead of scoped) declarations are used"); + } + erase_macro_core(s); +} + +void cmd_context::erase_cmd(symbol const & s) { + cmd * c; + if (m_cmds.find(s, c)) { + c->finalize(*this); + m_cmds.erase(s); + dealloc(c); + } +} + +void cmd_context::erase_user_tactic(symbol const & s) { + sexpr * d; + if (m_user_tactic_decls.find(s, d)) { + m_user_tactic_decls.erase(s); + sm().dec_ref(d); + } +} + +void cmd_context::erase_object_ref(symbol const & s) { + object_ref * r = 0; + if (m_object_refs.find(s, r)) { + r->dec_ref(*this); + m_object_refs.erase(s); + } +} + +void cmd_context::reset_func_decls() { + dictionary::iterator it = m_func_decls.begin(); + dictionary::iterator end = m_func_decls.end(); + for (; it != end; ++it) { + func_decls fs = (*it).m_value; + fs.finalize(m()); + } + m_func_decls.reset(); + m_func_decls_stack.reset(); + m_func_decl2alias.reset(); +} + +void cmd_context::reset_psort_decls() { + dictionary::iterator it = m_psort_decls.begin(); + dictionary::iterator end = m_psort_decls.end(); + for (; it != end; ++it) { + psort_decl * p = (*it).m_value; + pm().dec_ref(p); + } + m_psort_decls.reset(); + m_psort_decls_stack.reset(); +} + +void cmd_context::reset_macros() { + dictionary::iterator it = m_macros.begin(); + dictionary::iterator end = m_macros.end(); + for (; it != end; ++it) { + expr * t = (*it).m_value.second; + m().dec_ref(t); + } + m_macros.reset(); + m_macros_stack.reset(); +} + +void cmd_context::reset_cmds() { + dictionary::iterator it = m_cmds.begin(); + dictionary::iterator end = m_cmds.end(); + for (; it != end; ++it) { + cmd * c = (*it).m_value; + c->reset(*this); + } +} + +void cmd_context::finalize_cmds() { + dictionary::iterator it = m_cmds.begin(); + dictionary::iterator end = m_cmds.end(); + for (; it != end; ++it) { + cmd * c = (*it).m_value; + c->finalize(*this); + dealloc(c); + } + m_cmds.reset(); +} + +void cmd_context::reset_user_tactics() { + dec_ref_values(sm(), m_user_tactic_decls); + m_user_tactic_decls.reset(); +} + +void cmd_context::reset_object_refs() { + dictionary::iterator it = m_object_refs.begin(); + dictionary::iterator end = m_object_refs.end(); + for (; it != end; ++it) { + object_ref * r = (*it).m_value; + r->dec_ref(*this); + } + m_object_refs.reset(); +} + +void cmd_context::insert_aux_pdecl(pdecl * p) { + pm().inc_ref(p); + m_aux_pdecls.push_back(p); +} + +void cmd_context::reset(bool finalize) { + m_check_sat_result = 0; + m_logic = symbol::null; + m_check_sat_result = 0; + m_numeral_as_real = false; + m_builtin_decls.reset(); + m_extra_builtin_decls.reset(); + m_check_logic.reset(); + reset_object_refs(); + reset_cmds(); + reset_psort_decls(); + restore_aux_pdecls(0); + reset_macros(); + reset_func_decls(); + restore_assertions(0); + restore_assumptions(0); + if (m_solver) + m_solver->reset(); + m_pp_env = 0; + m_dt_eh = 0; + if (m_manager) { + dealloc(m_pmanager); + m_pmanager = 0; + if (m_own_manager) { + dealloc(m_manager); + m_manager = 0; + } + else { + // doesn't own manager... so it cannot be deleted + // reinit cmd_context if this is not a finalization step + if (!finalize) + init_external_manager(); + } + } + if (m_sexpr_manager) { + dealloc(m_sexpr_manager); + m_sexpr_manager = 0; + } + SASSERT(!m_own_manager || !has_manager()); +} + +void cmd_context::assert_expr(expr * t) { + if (!m_check_logic(t)) + throw cmd_exception(m_check_logic.get_last_error()); + m_check_sat_result = 0; + m().inc_ref(t); + m_assertions.push_back(t); + if (m_solver) + m_solver->assert_expr(t); +} + +void cmd_context::assert_expr(symbol const & name, expr * t) { + if (!m_check_logic(t)) + throw cmd_exception(m_check_logic.get_last_error()); + if (!m_produce_unsat_cores || name == symbol::null) { + assert_expr(t); + return; + } + app * proxy = m().mk_const(name, m().mk_bool_sort()); + expr * new_t = m().mk_implies(proxy, t); + m().inc_ref(proxy); + m_assumptions.push_back(proxy); + assert_expr(new_t); +} + +void cmd_context::push() { + m_check_sat_result = 0; + if (!has_manager()) + init_manager(); + m_scopes.push_back(scope()); + scope & s = m_scopes.back(); + s.m_func_decls_stack_lim = m_func_decls_stack.size(); + s.m_psort_decls_stack_lim = m_psort_decls_stack.size(); + s.m_macros_stack_lim = m_macros_stack.size(); + s.m_aux_pdecls_lim = m_aux_pdecls.size(); + s.m_assertions_lim = m_assertions.size(); + s.m_assumptions_lim = m_assumptions.size(); + if (m_solver) + m_solver->push(); +} + +void cmd_context::push(unsigned n) { + for (unsigned i = 0; i < n; i++) + push(); +} + +void cmd_context::restore_func_decls(unsigned old_sz) { + SASSERT(old_sz <= m_func_decls_stack.size()); + svector::iterator it = m_func_decls_stack.begin() + old_sz; + svector::iterator end = m_func_decls_stack.end(); + for (; it != end; ++it) { + sf_pair const & p = *it; + erase_func_decl_core(p.first, p.second); + } + m_func_decls_stack.shrink(old_sz); +} + +void cmd_context::restore_psort_decls(unsigned old_sz) { + SASSERT(old_sz <= m_psort_decls_stack.size()); + svector::iterator it = m_psort_decls_stack.begin() + old_sz; + svector::iterator end = m_psort_decls_stack.end(); + for (; it != end; ++it) { + symbol const & s = *it; + psort_decl * d = 0; + if (!m_psort_decls.find(s, d)) { + UNREACHABLE(); + } + pm().dec_ref(d); + m_psort_decls.erase(s); + } + m_psort_decls_stack.shrink(old_sz); +} + +void cmd_context::restore_macros(unsigned old_sz) { + SASSERT(old_sz <= m_macros_stack.size()); + svector::iterator it = m_macros_stack.begin() + old_sz; + svector::iterator end = m_macros_stack.end(); + for (; it != end; ++it) { + symbol const & s = *it; + macro _m; + if (!m_macros.find(s, _m)) { + UNREACHABLE(); + } + m().dec_ref(_m.second); + m_macros.erase(s); + } + m_macros_stack.shrink(old_sz); +} + +void cmd_context::restore_aux_pdecls(unsigned old_sz) { + SASSERT(old_sz <= m_aux_pdecls.size()); + ptr_vector::iterator it = m_aux_pdecls.begin() + old_sz; + ptr_vector::iterator end = m_aux_pdecls.end(); + for (; it != end; ++it) { + pm().dec_ref(*it); + } + m_aux_pdecls.shrink(old_sz); +} + +void cmd_context::restore_assertions(unsigned old_sz) { + SASSERT(old_sz <= m_assertions.size()); + SASSERT(!m_interactive_mode || m_assertions.size() == m_assertion_strings.size()); + ptr_vector::iterator it = m_assertions.begin() + old_sz; + ptr_vector::iterator end = m_assertions.end(); + for (; it != end; ++it) { + m().dec_ref(*it); + } + m_assertions.shrink(old_sz); + if (m_interactive_mode) + m_assertion_strings.shrink(old_sz); +} + +void cmd_context::restore_assumptions(unsigned old_sz) { + SASSERT(old_sz <= m_assumptions.size()); + ptr_vector::iterator it = m_assumptions.begin() + old_sz; + ptr_vector::iterator end = m_assumptions.end(); + for (; it != end; ++it) { + m().dec_ref(*it); + } + m_assumptions.shrink(old_sz); +} + +void cmd_context::pop(unsigned n) { + m_check_sat_result = 0; + if (n == 0) + return; + unsigned lvl = m_scopes.size(); + if (n > lvl) + throw cmd_exception("invalid pop command, argument is greater than the current stack depth"); + if (m_solver) { + m_solver->pop(n); + } + unsigned new_lvl = lvl - n; + scope & s = m_scopes[new_lvl]; + restore_func_decls(s.m_func_decls_stack_lim); + restore_psort_decls(s.m_psort_decls_stack_lim); + restore_macros(s.m_macros_stack_lim); + restore_aux_pdecls(s.m_aux_pdecls_lim); + restore_assertions(s.m_assertions_lim); + restore_assumptions(s.m_assumptions_lim); + m_scopes.shrink(new_lvl); +} + +void cmd_context::check_sat(unsigned num_assumptions, expr * const * assumptions) { + if (m_ignore_check) + return; + IF_VERBOSE(100, verbose_stream() << "check-sat..." << std::endl;); + TRACE("before_check_sat", dump_assertions(tout);); + if (params().m_ignore_checksat) { + m_check_sat_result = 0; + regular_stream() << "unknown" << std::endl; + return; + } + if (!has_manager()) + init_manager(); + if (m_solver) { + m_check_sat_result = m_solver.get(); // solver itself stores the result. + m_solver->set_front_end_params(m_params); + m_solver->set_progress_callback(this); + m_solver->set_produce_proofs(produce_proofs()); + m_solver->set_produce_models(produce_models()); + m_solver->set_produce_unsat_cores(produce_unsat_cores()); + scoped_watch sw(*this); + cancel_eh eh(*m_solver); + scoped_ctrl_c ctrlc(eh); + unsigned old_sz = m_assumptions.size(); + m_assumptions.append(num_assumptions, assumptions); + lbool r; + try { + r = m_solver->check_sat(m_assumptions.size(), m_assumptions.c_ptr()); + } + catch (z3_error & ex) { + throw ex; + } + catch (z3_exception & ex) { + throw cmd_exception(ex.msg()); + } + m_assumptions.shrink(old_sz); + m_solver->set_status(r); + display_sat_result(r); + validate_check_sat_result(r); + if (r == l_true) + validate_model(); + } + else { + // There is no solver installed in the command context. + regular_stream() << "unknown" << std::endl; + } +} + +void cmd_context::display_sat_result(lbool r) { + switch (r) { + case l_true: + regular_stream() << "sat" << std::endl; + break; + case l_false: + regular_stream() << "unsat" << std::endl; + break; + case l_undef: + regular_stream() << "unknown" << std::endl; + break; + } +} + +void cmd_context::validate_check_sat_result(lbool r) { + switch (r) { + case l_true: + if (m_status == UNSAT) { +#ifdef _EXTERNAL_RELEASE + throw cmd_exception("check annotation that says unsat"); +#else + diagnostic_stream() << "BUG: incompleteness" << std::endl; + exit(ERR_INCOMPLETENESS); +#endif + } + break; + case l_false: + if (m_status == SAT) { +#ifdef _EXTERNAL_RELEASE + throw cmd_exception("check annotation that says sat"); +#else + diagnostic_stream() << "BUG: unsoundness" << std::endl; + exit(ERR_UNSOUNDNESS); +#endif + } + break; + default: + break; + } +} + +void cmd_context::set_diagnostic_stream(char const * name) { + m_diagnostic.set(name); + if (m_main_ctx) { + set_warning_stream(&(*m_diagnostic)); + set_verbose_stream(diagnostic_stream()); + } +} + +struct contains_array_op_proc { + struct found {}; + family_id m_array_fid; + contains_array_op_proc(ast_manager & m):m_array_fid(m.get_family_id("array")) {} + void operator()(var * n) {} + void operator()(app * n) { + if (n->get_family_id() != m_array_fid) + return; + decl_kind k = n->get_decl_kind(); + if (k == OP_AS_ARRAY || + k == OP_STORE || + k == OP_ARRAY_MAP || + k == OP_CONST_ARRAY) + throw found(); + } + void operator()(quantifier * n) {} +}; + +/** + \brief Check if the current model satisfies the quantifier free formulas. +*/ +void cmd_context::validate_model() { + if (!params().m_model_validate) + return; + if (!is_model_available()) + return; + model_ref md; + get_check_sat_result()->get_model(md); + SASSERT(md.get() != 0); + params_ref p; + p.set_uint(":max-degree", UINT_MAX); // evaluate algebraic numbers of any degree. + p.set_uint(":sort-store", true); + p.set_bool(":model-completion", true); + model_evaluator evaluator(*(md.get()), p); + contains_array_op_proc contains_array(m()); + { + cancel_eh eh(evaluator); + expr_ref r(m()); + scoped_ctrl_c ctrlc(eh); + ptr_vector::const_iterator it = begin_assertions(); + ptr_vector::const_iterator end = end_assertions(); + for (; it != end; ++it) { + expr * a = *it; + if (is_ground(a)) { + r = 0; + evaluator(a, r); + TRACE("model_validate", tout << "checking\n" << mk_ismt2_pp(a, m()) << "\nresult:\n" << mk_ismt2_pp(r, m()) << "\n";); + if (m().is_true(r)) + continue; + // The evaluator for array expressions is not complete + // If r contains as_array/store/map/const expressions, then we do not generate the error. + // TODO: improve evaluator for model expressions. + // Note that, if "a" evaluates to false, then the error will be generated. + try { + for_each_expr(contains_array, r); + } + catch (contains_array_op_proc::found) { + continue; + } + throw cmd_exception("an invalid model was generated"); + } + } + } +} + +void cmd_context::set_solver(solver * s) { + m_check_sat_result = 0; + m_solver = s; + m_solver->set_front_end_params(m_params); + if (has_manager() && s != 0) { + m_solver->init(m(), m_logic); + // assert formulas and create scopes in the new solver. + unsigned lim = 0; + svector::iterator it = m_scopes.begin(); + svector::iterator end = m_scopes.end(); + for (; it != end; ++it) { + scope & s = *it; + for (unsigned i = lim; i < s.m_assertions_lim; i++) { + m_solver->assert_expr(m_assertions[i]); + } + lim = s.m_assertions_lim; + m_solver->push(); + } + unsigned sz = m_assertions.size(); + for (unsigned i = lim; i < sz; i++) { + m_solver->assert_expr(m_assertions[i]); + } + } +} + +void cmd_context::display_statistics(bool show_total_time, double total_time) { + statistics st; + unsigned long long mem = memory::get_max_used_memory(); + if (show_total_time) + st.update("total time", total_time); + st.update("time", get_seconds()); + st.update("memory", static_cast(mem)/static_cast(1024*1024)); + if (m_check_sat_result) { + m_check_sat_result->collect_statistics(st); + } + else if (m_solver) { + m_solver->collect_statistics(st); + } + st.display_smt2(regular_stream()); +} + +void cmd_context::display_assertions() { + if (!m_interactive_mode) + throw cmd_exception("command is only available in interactive mode, use command (set-option :interactive-mode true)"); + vector::const_iterator it = m_assertion_strings.begin(); + vector::const_iterator end = m_assertion_strings.end(); + regular_stream() << "("; + for (bool first = true; it != end; ++it) { + std::string const & s = *it; + if (first) + first = false; + else + regular_stream() << "\n "; + regular_stream() << s; + } + regular_stream() << ")" << std::endl; +} + +bool cmd_context::is_model_available() const { + if (params().m_model && + has_manager() && + (cs_state() == css_sat || cs_state() == css_unknown)) { + model_ref md; + get_check_sat_result()->get_model(md); + return md.get() != 0; + } + return false; +} + +format_ns::format * cmd_context::pp(sort * s) const { + TRACE("cmd_context", tout << "pp(sort * s), s: " << mk_pp(s, m()) << "\n";); + return pm().pp(s); +} + +cmd_context::pp_env & cmd_context::get_pp_env() const { + if (m_pp_env.get() == 0) { + const_cast(this)->m_pp_env = alloc(pp_env, *const_cast(this)); + } + return *(m_pp_env.get()); +} + +void cmd_context::pp(expr * n, unsigned num_vars, char const * var_prefix, format_ns::format_ref & r, sbuffer & var_names) const { + mk_smt2_format(n, get_pp_env(), get_pp_default_params(), + num_vars, var_prefix, r, var_names); +} + +void cmd_context::pp(expr * n, format_ns::format_ref & r) const { + sbuffer buf; + pp(n, 0, 0, r, buf); +} + +void cmd_context::pp(func_decl * f, format_ns::format_ref & r) const { + mk_smt2_format(f, get_pp_env(), get_pp_default_params(), r); +} + +void cmd_context::display(std::ostream & out, sort * s, unsigned indent) const { + format_ns::format_ref f(format_ns::fm(m())); + f = pp(s); + if (indent > 0) + f = format_ns::mk_indent(m(), indent, f); + ::pp(out, f.get(), m(), get_pp_default_params()); +} + +void cmd_context::display(std::ostream & out, expr * n, unsigned indent, unsigned num_vars, char const * var_prefix, sbuffer & var_names) const { + format_ns::format_ref f(format_ns::fm(m())); + pp(n, num_vars, var_prefix, f, var_names); + if (indent > 0) + f = format_ns::mk_indent(m(), indent, f); + ::pp(out, f.get(), m(), get_pp_default_params()); +} + +void cmd_context::display(std::ostream & out, expr * n, unsigned indent) const { + sbuffer buf; + display(out, n, indent, 0, 0, buf); +} + +void cmd_context::display(std::ostream & out, func_decl * d, unsigned indent) const { + format_ns::format_ref f(format_ns::fm(m())); + pp(d, f); + if (indent > 0) + f = format_ns::mk_indent(m(), indent, f); + ::pp(out, f.get(), m(), get_pp_default_params()); +} + +void cmd_context::dump_assertions(std::ostream & out) const { + ptr_vector::const_iterator it = m_assertions.begin(); + ptr_vector::const_iterator end = m_assertions.end(); + for (; it != end; ++it) { + display(out, *it); + out << std::endl; + } +} + +void cmd_context::display_smt2_benchmark(std::ostream & out, unsigned num, expr * const * assertions, symbol const & logic) const { + if (logic != symbol::null) + out << "(set-logic " << logic << ")" << std::endl; + // collect uninterpreted function declarations + decl_collector decls(m(), false); + for (unsigned i = 0; i < num; i++) { + decls.visit(assertions[i]); + } + + // TODO: display uninterpreted sort decls, and datatype decls. + + unsigned num_decls = decls.get_num_decls(); + func_decl * const * fs = decls.get_func_decls(); + for (unsigned i = 0; i < num_decls; i++) { + display(out, fs[i]); + out << std::endl; + } + + for (unsigned i = 0; i < num; i++) { + out << "(assert "; + display(out, assertions[i], 8); + out << ")" << std::endl; + } + out << "(check-sat)" << std::endl; +} + +void cmd_context::slow_progress_sample() { + SASSERT(m_solver); + statistics st; + regular_stream() << "(progress\n"; + m_solver->collect_statistics(st); + st.display_smt2(regular_stream()); + svector labels; + m_solver->get_labels(labels); + regular_stream() << "(labels"; + for (unsigned i = 0; i < labels.size(); i++) { + regular_stream() << " " << labels[i]; + } + regular_stream() << "))" << std::endl; +} + +void cmd_context::fast_progress_sample() { +} + +cmd_context::dt_eh::dt_eh(cmd_context & owner): + m_owner(owner), + m_dt_util(owner.m()) { +} + +cmd_context::dt_eh::~dt_eh() { +} + +void cmd_context::dt_eh::operator()(sort * dt) { + TRACE("new_dt_eh", tout << "new datatype: "; m_owner.pm().display(tout, dt); tout << "\n";); + ptr_vector const * constructors = m_dt_util.get_datatype_constructors(dt); + unsigned num_constructors = constructors->size(); + for (unsigned j = 0; j < num_constructors; j++) { + func_decl * c = constructors->get(j); + m_owner.insert(c); + TRACE("new_dt_eh", tout << "new constructor: " << c->get_name() << "\n";); + func_decl * r = m_dt_util.get_constructor_recognizer(c); + m_owner.insert(r); + TRACE("new_dt_eh", tout << "new recognizer: " << r->get_name() << "\n";); + ptr_vector const * accessors = m_dt_util.get_constructor_accessors(c); + unsigned num_accessors = accessors->size(); + for (unsigned k = 0; k < num_accessors; k++) { + func_decl * a = accessors->get(k); + m_owner.insert(a); + TRACE("new_dt_eh", tout << "new accessor: " << a->get_name() << "\n";); + } + } +} + +std::ostream & operator<<(std::ostream & out, cmd_context::status st) { + switch (st) { + case cmd_context::UNSAT: out << "unsat"; break; + case cmd_context::SAT: out << "sat"; break; + default: out << "unknown"; break; + } + return out; +} diff --git a/lib/cmd_context.h b/lib/cmd_context.h new file mode 100644 index 000000000..8bc1e242e --- /dev/null +++ b/lib/cmd_context.h @@ -0,0 +1,404 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + cmd_context.h + +Abstract: + Ultra-light command context. + It provides a generic command pluging infrastructure. + A command context also provides names (aka symbols) to Z3 objects. + These names are used to reference Z3 objects in commands. + +Author: + + Leonardo (leonardo) 2011-03-01 + +Notes: + +--*/ +#ifndef _CMD_CONTEXT_H_ +#define _CMD_CONTEXT_H_ + +#include +#include"ast.h" +#include"pdecl.h" +#include"front_end_params.h" +#include"dictionary.h" +#include"solver.h" +#include"datatype_decl_plugin.h" +#include"stopwatch.h" +#include"cmd_context_types.h" +#include"event_handler.h" +#include"sexpr.h" +#include"tactic_manager.h" +#include"check_logic.h" +#include"progress_callback.h" +#include"scoped_ptr_vector.h" + +class func_decls { + func_decl * m_decls; +public: + func_decls():m_decls(0) {} + func_decls(ast_manager & m, func_decl * f); + void finalize(ast_manager & m); + bool contains(func_decl * f) const; + bool insert(ast_manager & m, func_decl * f); + void erase(ast_manager & m, func_decl * f); + bool more_than_one() const; + bool clash(func_decl * f) const; + bool empty() const { return m_decls == 0; } + func_decl * first() const; + func_decl * find(unsigned arity, sort * const * domain, sort * range) const; + func_decl * find(ast_manager & m, unsigned num_args, expr * const * args, sort * range) const; +}; + +/** + \brief Generic wrapper. +*/ +class object_ref { + unsigned m_ref_count; +public: + object_ref():m_ref_count(0) {} + virtual ~object_ref() {} + virtual void finalize(cmd_context & ctx) = 0; + void inc_ref(cmd_context & ctx) { + m_ref_count++; + } + void dec_ref(cmd_context & ctx) { + SASSERT(m_ref_count > 0); + m_ref_count--; + if (m_ref_count == 0) { + finalize(ctx); + dealloc(this); + } + } + virtual char const * kind() const = 0; +}; + +class ast_object_ref : public object_ref { + ast * m_ast; +public: + ast_object_ref(cmd_context & ctx, ast * a); + virtual void finalize(cmd_context & ctx); + ast * get_ast() const { return m_ast; } + static char const * cls_kind() { return "AST"; } + virtual char const * kind() const { return cls_kind(); } +}; + +class stream_ref { + std::string m_default_name; + std::ostream & m_default; + std::string m_name; + std::ostream * m_stream; + bool m_owner; +public: + stream_ref(std::string n, std::ostream & d):m_default_name(n), m_default(d), m_name(n), m_stream(&d), m_owner(false) {} + ~stream_ref() { reset(); } + void set(char const * name); + void reset(); + std::ostream & operator*() { return *m_stream; } + char const * name() const { return m_name.c_str(); } +}; + +struct builtin_decl { + family_id m_fid; + decl_kind m_decl; + builtin_decl * m_next; + builtin_decl():m_fid(null_family_id), m_decl(0), m_next(0) {} + builtin_decl(family_id fid, decl_kind k, builtin_decl * n = 0):m_fid(fid), m_decl(k), m_next(n) {} +}; + +class cmd_context : public progress_callback, public tactic_manager { +public: + enum status { + UNSAT, SAT, UNKNOWN + }; + + enum check_sat_state { + css_unsat, css_sat, css_unknown, css_clear + }; + + typedef std::pair macro; + + struct scoped_watch { + cmd_context & m_ctx; + public: + scoped_watch(cmd_context & ctx):m_ctx(ctx) { m_ctx.m_watch.reset(); m_ctx.m_watch.start(); } + ~scoped_watch() { m_ctx.m_watch.stop(); } + }; + +protected: + bool m_main_ctx; + front_end_params & m_params; + symbol m_logic; + bool m_interactive_mode; + bool m_global_decls; + bool m_print_success; + unsigned m_random_seed; + bool m_produce_unsat_cores; + bool m_produce_assignments; + status m_status; + bool m_numeral_as_real; + bool m_ignore_check; // used by the API to disable check-sat() commands when parsing SMT 2.0 files. + bool m_exit_on_error; + + static std::ostringstream g_error_stream; + + ast_manager * m_manager; + bool m_own_manager; + pdecl_manager * m_pmanager; + sexpr_manager * m_sexpr_manager; + check_logic m_check_logic; + stream_ref m_regular; + stream_ref m_diagnostic; + dictionary m_cmds; + dictionary m_builtin_decls; + scoped_ptr_vector m_extra_builtin_decls; // make sure that dynamically allocated builtin_decls are deleted + dictionary m_object_refs; // anything that can be named. + dictionary m_user_tactic_decls; + + dictionary m_func_decls; + obj_map m_func_decl2alias; + dictionary m_psort_decls; + dictionary m_macros; + // the following fields m_func_decls_stack, m_psort_decls_stack and m_exprs_stack are used when m_global_decls == false + typedef std::pair sf_pair; + svector m_func_decls_stack; + svector m_psort_decls_stack; + svector m_macros_stack; + // + ptr_vector m_aux_pdecls; + ptr_vector m_assertions; + vector m_assertion_strings; + ptr_vector m_assumptions; // for unsat-core extraction + + struct scope { + unsigned m_func_decls_stack_lim; + unsigned m_psort_decls_stack_lim; + unsigned m_macros_stack_lim; + unsigned m_aux_pdecls_lim; + // only m_assertions_lim and m_assumptions_lim are relevant when m_global_decls = true + unsigned m_assertions_lim; + unsigned m_assumptions_lim; + }; + + svector m_scopes; + ref m_solver; + ref m_check_sat_result; + + stopwatch m_watch; + + class dt_eh : public new_datatype_eh { + cmd_context & m_owner; + datatype_util m_dt_util; + public: + dt_eh(cmd_context & owner); + virtual ~dt_eh(); + virtual void operator()(sort * dt); + }; + + friend class dt_eh; + scoped_ptr m_dt_eh; + + class pp_env; + friend class pp_env; + + scoped_ptr m_pp_env; + pp_env & get_pp_env() const; + + void register_builtin_sorts(decl_plugin * p); + void register_builtin_ops(decl_plugin * p); + void register_plugin(symbol const & name, decl_plugin * p, bool install_names); + void init_manager_core(bool new_manager); + void init_manager(); + void init_external_manager(); + void reset_cmds(); + void finalize_cmds(); + + void restore_func_decls(unsigned old_sz); + void restore_psort_decls(unsigned old_sz); + void restore_macros(unsigned old_sz); + void restore_aux_pdecls(unsigned old_sz); + void restore_assertions(unsigned old_sz); + void restore_assumptions(unsigned old_sz); + + void erase_func_decl_core(symbol const & s, func_decl * f); + void erase_psort_decl_core(symbol const & s); + void erase_macro_core(symbol const & s); + + bool logic_has_arith_core(symbol const & s) const; + bool logic_has_bv_core(symbol const & s) const; + bool logic_has_array_core(symbol const & s) const; + bool logic_has_seq_core(symbol const & s) const; + bool logic_has_arith() const; + bool logic_has_bv() const; + bool logic_has_seq() const; + bool logic_has_array() const; + bool logic_has_datatype() const; + bool logic_has_floats() const; + bool supported_logic(symbol const & s) const; + + void print_unsupported_msg() { regular_stream() << "unsupported" << std::endl; } + void print_unsupported_info(symbol const& s) { if (s != symbol::null) diagnostic_stream() << "; " << s << std::endl;} + +public: + cmd_context(front_end_params & params, bool main_ctx = true, ast_manager * m = 0, symbol const & l = symbol::null); + ~cmd_context(); + void set_logic(symbol const & s); + bool has_logic() const { return m_logic != symbol::null; } + symbol const & get_logic() const { return m_logic; } + bool is_logic(char const * l_name) const { return has_logic() && strcmp(m_logic.bare_str(), l_name) == 0; } + bool numeral_as_real() const { return m_numeral_as_real; } + void set_numeral_as_real(bool f) { m_numeral_as_real = f; } + void set_interactive_mode(bool flag) { m_interactive_mode = flag; } + void set_ignore_check(bool flag) { m_ignore_check = flag; } + void set_exit_on_error(bool flag) { m_exit_on_error = flag; } + bool exit_on_error() const { return m_exit_on_error; } + bool interactive_mode() const { return m_interactive_mode; } + void set_print_success(bool flag) { m_print_success = flag; } + bool print_success_enabled() const { return m_print_success; } + void print_success() { if (print_success_enabled()) regular_stream() << "success" << std::endl; } + void print_unsupported(symbol const & s) { print_unsupported_msg(); print_unsupported_info(s); } + bool global_decls() const { return m_global_decls; } + void set_global_decls(bool flag) { SASSERT(!has_manager()); m_global_decls = flag; } + unsigned random_seed() const { return m_random_seed; } + void set_random_seed(unsigned s) { m_random_seed = s; } + bool produce_models() const { return m_params.m_model; } + bool produce_proofs() const { return m_params.m_proof_mode != PGM_DISABLED; } + bool produce_unsat_cores() const { return m_produce_unsat_cores; } + void set_produce_unsat_cores(bool flag) { m_produce_unsat_cores = flag; } + bool produce_assignments() const { return m_produce_assignments; } + void set_produce_assignments(bool flag) { m_produce_assignments = flag; } + void set_status(status st) { m_status = st; } + status get_status() const { return m_status; } + std::string reason_unknown() const; + + bool has_manager() const { return m_manager != 0; } + ast_manager & m() const { if (!m_manager) const_cast(this)->init_manager(); return *m_manager; } + 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; } + front_end_params & params() const { return m_params; } + + void set_solver(solver * s); + solver * get_solver() const { return m_solver.get(); } + 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(); } + check_sat_state cs_state() const; + void validate_model(); + + bool is_func_decl(symbol const & s) const; + bool is_sort_decl(symbol const& s) const { return m_psort_decls.contains(s); } + void insert(cmd * c); + void insert(symbol const & s, func_decl * f); + void insert(func_decl * f) { insert(f->get_name(), f); } + void insert(symbol const & s, psort_decl * p); + void insert(psort_decl * p) { insert(p->get_name(), p); } + void insert(symbol const & s, unsigned arity, expr * t); + void insert(symbol const & s, object_ref *); + void insert(tactic_cmd * c) { tactic_manager::insert(c); } + void insert(probe_info * p) { tactic_manager::insert(p); } + void insert_user_tactic(symbol const & s, sexpr * d); + void insert_aux_pdecl(pdecl * p); + func_decl * find_func_decl(symbol const & s) const; + func_decl * find_func_decl(symbol const & s, unsigned num_indices, unsigned const * indices, + unsigned arity, sort * const * domain, sort * range) const; + psort_decl * find_psort_decl(symbol const & s) const; + macro find_macro(symbol const & s) const; + cmd * find_cmd(symbol const & s) const; + sexpr * find_user_tactic(symbol const & s) const; + object_ref * find_object_ref(symbol const & s) const; + void mk_const(symbol const & s, expr_ref & result) const; + void mk_app(symbol const & s, unsigned num_args, expr * const * args, unsigned num_indices, parameter const * indices, sort * range, + expr_ref & r) const; + void erase_cmd(symbol const & s); + void erase_func_decl(symbol const & s); + void erase_func_decl(symbol const & s, func_decl * f); + void erase_func_decl(func_decl * f) { erase_func_decl(f->get_name(), f); } + void erase_psort_decl(symbol const & s); + void erase_macro(symbol const & s); + void erase_object_ref(symbol const & s); + void erase_user_tactic(symbol const & s); + void reset_func_decls(); + void reset_psort_decls(); + void reset_macros(); + void reset_object_refs(); + void reset_user_tactics(); + void set_regular_stream(char const * name) { m_regular.set(name); } + void set_diagnostic_stream(char const * name); + std::ostream & regular_stream() { return *m_regular; } + std::ostream & diagnostic_stream() { return *m_diagnostic; } + char const * get_regular_stream_name() const { return m_regular.name(); } + char const * get_diagnostic_stream_name() const { return m_diagnostic.name(); } + typedef dictionary::iterator cmd_iterator; + cmd_iterator begin_cmds() const { return m_cmds.begin(); } + cmd_iterator end_cmds() const { return m_cmds.end(); } + + typedef dictionary::iterator user_tactic_iterator; + user_tactic_iterator begin_user_tactics() const { return m_user_tactic_decls.begin(); } + user_tactic_iterator end_user_tactics() const { return m_user_tactic_decls.end(); } + + void display_assertions(); + void display_statistics(bool show_total_time = false, double total_time = 0.0); + void reset(bool finalize = false); + void assert_expr(expr * t); + void assert_expr(symbol const & name, expr * t); + void push_assert_string(std::string const & s) { SASSERT(m_interactive_mode); m_assertion_strings.push_back(s); } + void push(); + void push(unsigned n); + void pop(unsigned n); + void check_sat(unsigned num_assumptions, expr * const * assumptions); + // display the result produced by a check-sat or check-sat-using commands in the regular stream + void display_sat_result(lbool r); + // check if result produced by check-sat or check-sat-using matches the known status + void validate_check_sat_result(lbool r); + unsigned num_scopes() const { return m_scopes.size(); } + + dictionary const & get_macros() const { return m_macros; } + + bool is_model_available() const; + + double get_seconds() const { return m_watch.get_seconds(); } + + ptr_vector::const_iterator begin_assertions() const { return m_assertions.begin(); } + ptr_vector::const_iterator end_assertions() const { return m_assertions.end(); } + + ptr_vector::const_iterator begin_assumptions() const { return m_assumptions.begin(); } + ptr_vector::const_iterator end_assumptions() const { return m_assumptions.end(); } + + /** + \brief Hack: consume assertions if there are no scopes. + This method is useful for reducing memory consumption in huge benchmarks were incrementality is not an issue. + */ + bool consume_assertions() { + if (num_scopes() > 0) + return false; + restore_assertions(0); + restore_assumptions(0); + return true; + } + + format_ns::format * pp(sort * s) const; + void pp(func_decl * f, format_ns::format_ref & r) const; + void pp(expr * n, unsigned num_vars, char const * var_prefix, format_ns::format_ref & r, sbuffer & var_names) const; + void pp(expr * n, format_ns::format_ref & r) const; + void display(std::ostream & out, sort * s, unsigned indent = 0) const; + void display(std::ostream & out, expr * n, unsigned indent, unsigned num_vars, char const * var_prefix, sbuffer & var_names) const; + void display(std::ostream & out, expr * n, unsigned indent = 0) const; + void display(std::ostream & out, func_decl * f, unsigned indent = 0) const; + + // dump assertions in out using the pretty printer. + void dump_assertions(std::ostream & out) const; + + // display assertions as a SMT2 benchmark. + void display_smt2_benchmark(std::ostream & out, unsigned num, expr * const * assertions, symbol const & logic = symbol::null) const; + + + virtual void slow_progress_sample(); + virtual void fast_progress_sample(); +}; + +std::ostream & operator<<(std::ostream & out, cmd_context::status st); + +#endif diff --git a/lib/cmd_context_types.cpp b/lib/cmd_context_types.cpp new file mode 100644 index 000000000..3d631c28e --- /dev/null +++ b/lib/cmd_context_types.cpp @@ -0,0 +1,44 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + cmd_context_types.h + +Abstract: + +Author: + + Leonardo (leonardo) 2011-04-22 + +Notes: + +--*/ +#include +#include"cmd_context_types.h" + +std::ostream & operator<<(std::ostream & out, cmd_arg_kind k) { + switch (k) { + case CPK_UINT: out << "unsigned int"; break; + case CPK_BOOL: out << "bool"; break; + case CPK_DOUBLE: out << "double"; break; + case CPK_NUMERAL: out << "rational"; break; + case CPK_DECIMAL: out << "rational"; break; + case CPK_STRING: out << "string"; break; + case CPK_OPTION_VALUE: out << "optional-value"; break; + case CPK_KEYWORD: out << "keyword"; break; + case CPK_SYMBOL: out << "symbol"; break; + case CPK_SYMBOL_LIST: out << "symbol-list"; break; + case CPK_SORT: out << "sort"; break; + case CPK_SORT_LIST: out << "sort-list"; break; + case CPK_EXPR: out << "expression"; break; + case CPK_EXPR_LIST: out << "expression-list"; break; + case CPK_FUNC_DECL: out << "declaration"; break; + case CPK_FUNC_DECL_LIST: out << "declaration-list"; break; + case CPK_SORTED_VAR: out << "sorted-variable"; break; + case CPK_SORTED_VAR_LIST: out << "sorted-variable-list"; break; + case CPK_SEXPR: out << "s-expression"; break; + default: out << "unknown"; break; + } + return out; +} diff --git a/lib/cmd_context_types.h b/lib/cmd_context_types.h new file mode 100644 index 000000000..b0a0226e8 --- /dev/null +++ b/lib/cmd_context_types.h @@ -0,0 +1,119 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + cmd_context_types.h + +Abstract: + +Author: + + Leonardo (leonardo) 2011-04-22 + +Notes: + +--*/ +#ifndef _CMD_CONTEXT_TYPES_H_ +#define _CMD_CONTEXT_TYPES_H_ + +#include"symbol.h" +#include"z3_exception.h" +#include +class rational; +class expr; +class sort; +class func_decl; +class sexpr; +class cmd_context; + +enum cmd_arg_kind { + CPK_UINT, CPK_BOOL, CPK_DOUBLE, CPK_NUMERAL, + CPK_DECIMAL, CPK_STRING, CPK_OPTION_VALUE, + CPK_KEYWORD, + CPK_SYMBOL, CPK_SYMBOL_LIST, + CPK_SORT, CPK_SORT_LIST, + CPK_EXPR, CPK_EXPR_LIST, + CPK_FUNC_DECL, CPK_FUNC_DECL_LIST, + CPK_SORTED_VAR, CPK_SORTED_VAR_LIST, + CPK_SEXPR, + CPK_INVALID +}; + +std::ostream & operator<<(std::ostream & out, cmd_arg_kind k); + +typedef cmd_arg_kind param_kind; + +class cmd_exception : public default_exception { + int m_line; + int m_pos; + + std::string compose(char const* msg, symbol const& s) { + std::stringstream stm; + stm << msg << s; + return stm.str(); + } +public: + cmd_exception(char const * msg):default_exception(msg), m_line(-1), m_pos(-1) {} + cmd_exception(std::string const & msg):default_exception(msg.c_str()), m_line(-1), m_pos(-1) {} + cmd_exception(std::string const & msg, int line, int pos):default_exception(msg.c_str()), m_line(line), m_pos(pos) {} + cmd_exception(char const * msg, symbol const & s): + default_exception(compose(msg,s).c_str()),m_line(-1),m_pos(-1) {} + cmd_exception(char const * msg, symbol const & s, int line, int pos): + default_exception(compose(msg,s).c_str()),m_line(line),m_pos(pos) {} + + bool has_pos() const { return m_line >= 0; } + int line() const { SASSERT(has_pos()); return m_line; } + int pos() const { SASSERT(has_pos()); return m_pos; } +}; + +class stop_parser_exception { +}; + +typedef std::pair sorted_var; + +// A command may have a variable number of arguments. +#define VAR_ARITY UINT_MAX + +/** + \brief Command abstract class. + + Commands may have variable number of argumets. +*/ +class cmd { + symbol m_name; +public: + cmd(char const * n):m_name(n) {} + virtual ~cmd() {} + virtual void reset(cmd_context & ctx) {} + virtual void finalize(cmd_context & ctx) {} + virtual symbol get_name() const { return m_name; } + virtual char const * get_usage() const { return 0; } + virtual char const * get_descr(cmd_context & ctx) const { return 0; } + virtual unsigned get_arity() const { return 0; } + + // command invocation + virtual void prepare(cmd_context & ctx) {} + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { UNREACHABLE(); return CPK_UINT; } + virtual void set_next_arg(cmd_context & ctx, unsigned val) { UNREACHABLE(); } + virtual void set_next_arg(cmd_context & ctx, bool val) { UNREACHABLE(); } + virtual void set_next_arg(cmd_context & ctx, rational const & val) { UNREACHABLE(); } + virtual void set_next_arg(cmd_context & ctx, double val) { UNREACHABLE(); } + virtual void set_next_arg(cmd_context & ctx, char const * val) { UNREACHABLE(); } + virtual void set_next_arg(cmd_context & ctx, symbol const & s) { UNREACHABLE(); } + virtual void set_next_arg(cmd_context & ctx, unsigned num, symbol const * slist) { UNREACHABLE(); } + virtual void set_next_arg(cmd_context & ctx, sort * s) { UNREACHABLE(); } + virtual void set_next_arg(cmd_context & ctx, unsigned num, sort * const * slist) { UNREACHABLE(); } + virtual void set_next_arg(cmd_context & ctx, expr * t) { UNREACHABLE(); } + virtual void set_next_arg(cmd_context & ctx, unsigned num, expr * const * tlist) { UNREACHABLE(); } + virtual void set_next_arg(cmd_context & ctx, sorted_var const & sv) { UNREACHABLE(); } + virtual void set_next_arg(cmd_context & ctx, unsigned num, sorted_var const * svlist) { UNREACHABLE(); } + virtual void set_next_arg(cmd_context & ctx, func_decl * f) { UNREACHABLE(); } + virtual void set_next_arg(cmd_context & ctx, unsigned num, func_decl * const * flist) { UNREACHABLE(); } + virtual void set_next_arg(cmd_context & ctx, sexpr * n) { UNREACHABLE(); } + virtual void failure_cleanup(cmd_context & ctx) {} + virtual void execute(cmd_context & ctx) {} +}; + + +#endif diff --git a/lib/cmd_util.cpp b/lib/cmd_util.cpp new file mode 100644 index 000000000..083493751 --- /dev/null +++ b/lib/cmd_util.cpp @@ -0,0 +1,38 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + cmd_util.cpp + +Abstract: + Macros for definining new SMT2 front-end cmds. + +Author: + + Leonardo (leonardo) 2011-04-01 + +Notes: + +--*/ +#include"cmd_context.h" + +ast * get_ast_ref(cmd_context & ctx, symbol const & v) { + object_ref * r = ctx.find_object_ref(v); + SASSERT(r != 0); + if (r->kind() != ast_object_ref::cls_kind()) + throw cmd_exception("global variable does not reference an AST"); + return static_cast(r)->get_ast(); +} + +expr * get_expr_ref(cmd_context & ctx, symbol const & v) { + ast * r = get_ast_ref(ctx, v); + if (!is_expr(r)) + throw cmd_exception("global variable does not reference a term"); + return to_expr(r); +} + +void store_expr_ref(cmd_context & ctx, symbol const & v, expr * t) { + ctx.insert(v, alloc(ast_object_ref, ctx, t)); +} + diff --git a/lib/cmd_util.h b/lib/cmd_util.h new file mode 100644 index 000000000..538964ad9 --- /dev/null +++ b/lib/cmd_util.h @@ -0,0 +1,80 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + cmd_util.h + +Abstract: + Macros for definining new SMT2 front-end cmds. + +Author: + + Leonardo (leonardo) 2011-04-01 + +Notes: + +--*/ +#ifndef _CMD_UTIL_H_ +#define _CMD_UTIL_H_ + +#define ATOMIC_CMD(CLS_NAME, NAME, DESCR, ACTION) \ +class CLS_NAME : public cmd { \ +public: \ + CLS_NAME():cmd(NAME) {} \ + virtual char const * get_usage() const { return 0; } \ + virtual char const * get_descr(cmd_context & ctx) const { return DESCR; } \ + virtual unsigned get_arity() const { return 0; } \ + virtual void execute(cmd_context & ctx) { ACTION } \ +}; + +#define UNARY_CMD(CLS_NAME, NAME, USAGE, DESCR, ARG_KIND, ARG_TYPE, ACTION) \ +class CLS_NAME : public cmd { \ +public: \ + CLS_NAME():cmd(NAME) {} \ + virtual char const * get_usage() const { return USAGE; } \ + virtual char const * get_descr(cmd_context & ctx) const { return DESCR; } \ + virtual unsigned get_arity() const { return 1; } \ + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { return ARG_KIND; } \ + virtual void set_next_arg(cmd_context & ctx, ARG_TYPE arg) { ACTION } \ +} + +// Macro for creating commands where the first argument is a symbol +// The second argument cannot be a symbol +#define BINARY_SYM_CMD(CLS_NAME, NAME, USAGE, DESCR, ARG_KIND, ARG_TYPE, ACTION) \ +class CLS_NAME : public cmd { \ + symbol m_sym; \ +public: \ + CLS_NAME():cmd(NAME) {} \ + virtual char const * get_usage() const { return USAGE; } \ + virtual char const * get_descr(cmd_context & ctx) const { return DESCR; } \ + virtual unsigned get_arity() const { return 2; } \ + virtual void prepare(cmd_context & ctx) { m_sym = symbol::null; } \ + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { \ + return m_sym == symbol::null ? CPK_SYMBOL : ARG_KIND; \ + } \ + virtual void set_next_arg(cmd_context & ctx, symbol const & s) { m_sym = s; } \ + virtual void set_next_arg(cmd_context & ctx, ARG_TYPE arg) { ACTION } \ +}; + + +class ast; +class expr; +class symbol; +class cmd_context; + +/** + \brief Return the AST that is referenced by the global variable v in the given command context. +*/ +ast * get_ast_ref(cmd_context & ctx, symbol const & v); +/** + \brief Return the expression that is referenced by the global variable v in the given command context. +*/ +expr * get_expr_ref(cmd_context & ctx, symbol const & v); + +/** + \brief Store t in the global variable v. +*/ +void store_expr_ref(cmd_context & ctx, symbol const & v, expr * t); + +#endif diff --git a/lib/cnf.cpp b/lib/cnf.cpp new file mode 100644 index 000000000..cda4272a9 --- /dev/null +++ b/lib/cnf.cpp @@ -0,0 +1,526 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + cnf.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-23. + +Revision History: + +--*/ + +#include"cnf.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" + +unsigned cnf_entry::hash() const { + unsigned a = m_node->get_id(); + unsigned b = m_polarity; + unsigned c = m_in_q; + mix(a,b,c); + return c; +} + +bool cnf_entry::operator==(cnf_entry const & k) const { + return m_node == k.m_node && m_polarity == k.m_polarity && m_in_q == k.m_in_q; +} + +cnf_cache::cnf_cache(ast_manager & m): + m_manager(m) { +} + +void cnf_cache::insert(cnf_entry const & k, expr * r, proof * pr) { + SASSERT(!m_cache.contains(k)); + m_manager.inc_ref(r); + m_manager.inc_ref(pr); + m_cache.insert(k, expr_proof_pair(r, pr)); +} + +void cnf_cache::reset() { + cache::iterator it = m_cache.begin(); + cache::iterator end = m_cache.end(); + for (; it != end; ++it) { + expr_proof_pair & pair = (*it).m_value; + m_manager.dec_ref(pair.first); + m_manager.dec_ref(pair.second); + } + m_cache.reset(); +} + +void cnf::cache_result(expr * e, bool in_q, expr * r, proof * pr) { + SASSERT(r); + TRACE("cnf", tout << "caching result for: " << e->get_id() << " " << r->get_id() << "\n";); + m_cache.insert(cnf_entry(e, true, in_q), r, pr); +} + +void cnf::visit(expr * n, bool in_q, bool & visited) { + if (!is_cached(n, in_q)) { + m_todo.push_back(std::make_pair(n, in_q)); + visited = false; + } +} + +bool cnf::visit_children(expr * n, bool in_q) { + bool visited = true; + switch(n->get_kind()) { + case AST_APP: + if (m_manager.is_or(n) || m_manager.is_and(n) || m_manager.is_label(n)) { + unsigned j = to_app(n)->get_num_args(); + while (j > 0) { + --j; + visit(to_app(n)->get_arg(j), in_q, visited); + } + } + break; + case AST_QUANTIFIER: + visit(to_quantifier(n)->get_expr(), true, visited); + break; + default: + break; + } + return visited; +} + +void cnf::reduce1(expr * n, bool in_q) { + switch(n->get_kind()) { + case AST_APP: + if (m_manager.is_or(n)) + reduce1_or(to_app(n), in_q); + else if (m_manager.is_and(n)) + reduce1_and(to_app(n), in_q); + else if (m_manager.is_label(n)) + reduce1_label(to_app(n), in_q); + else + cache_result(n, in_q, n, 0); + break; + case AST_QUANTIFIER: + reduce1_quantifier(to_quantifier(n), in_q); + break; + default: + cache_result(n, in_q, n, 0); + break; + } +} + +void cnf::get_args(app * n, bool in_q, ptr_buffer & new_args, ptr_buffer & new_arg_prs) { + unsigned num = n->get_num_args(); + for (unsigned i = 0; i < num; i++) { + expr * new_arg = 0; + proof * new_arg_pr = 0; + get_cached(n->get_arg(i), in_q, new_arg, new_arg_pr); + SASSERT(new_arg); + new_args.push_back(new_arg); + if (new_arg_pr) + new_arg_prs.push_back(new_arg_pr); + } +} + +void cnf::flat_args(func_decl * d, ptr_buffer const & args, ptr_buffer & flat_args) { + ptr_buffer::const_iterator it = args.begin(); + ptr_buffer::const_iterator end = args.end(); + for (; it != end; ++it) { + expr * arg = *it; + if (is_app_of(arg, d)) + flat_args.append(to_app(arg)->get_num_args(), to_app(arg)->get_args()); + else + flat_args.push_back(arg); + } +} + +/** + \brief Return the approximated size of distributing OR over AND on + (OR args[0] .... args[sz-1]) +*/ +approx_nat cnf::approx_result_size_for_disj(ptr_buffer const & args) { + approx_nat r(1); + ptr_buffer::const_iterator it = args.begin(); + ptr_buffer::const_iterator end = args.end(); + for (; it != end; ++it) { + expr * arg = *it; + if (m_manager.is_and(arg)) + r *= to_app(arg)->get_num_args(); + } + return r; +} + +/** + \brief Return true if it is too expensive to process the disjunction of args +*/ +inline bool cnf::is_too_expensive(approx_nat approx_result_size, ptr_buffer const & args) { + // (OR A (AND B C)) is always considered cheap. + if (args.size() == 2 && (!m_manager.is_and(args[0]) || !m_manager.is_and(args[1]))) + return false; + return !(approx_result_size < m_params.m_cnf_factor); +} + +/** + \brief Create a (positive) name for the expressions of the form (AND ...) in args. + Store the result in new_args. +*/ +void cnf::name_args(ptr_buffer const & args, expr_ref_buffer & new_args, proof_ref_buffer & new_arg_prs) { + ptr_buffer::const_iterator it = args.begin(); + ptr_buffer::const_iterator end = args.end(); + for (; it != end; ++it) { + expr * arg = *it; + if (m_manager.is_and(arg)) { + expr_ref new_def(m_manager); + proof_ref new_def_pr(m_manager); + app_ref new_arg(m_manager); + proof_ref new_arg_pr(m_manager); + + if (m_defined_names.mk_pos_name(to_app(arg), new_def, new_def_pr, new_arg, new_arg_pr)) { + m_todo_defs.push_back(new_def); + if (m_manager.proofs_enabled()) + m_todo_proofs.push_back(new_def_pr); + } + new_args.push_back(new_arg); + + if (m_manager.fine_grain_proofs()) + new_arg_prs.push_back(new_arg_pr); + else + m_coarse_proofs.push_back(new_arg_pr); + } + else + new_args.push_back(arg); + } +} + +void cnf::distribute(app * n, app * & r, proof * & pr) { + SASSERT(m_manager.is_or(n)); + buffer sz; + buffer it; + ptr_buffer new_args; + unsigned num = n->get_num_args(); + for (unsigned i = 0; i < num; i++) { + expr * arg = n->get_arg(i); + it.push_back(0); + if (m_manager.is_and(arg)) + sz.push_back(to_app(arg)->get_num_args()); + else + sz.push_back(1); + } + + do { + ptr_buffer lits; + for (unsigned i = 0; i < num; i++) { + expr * arg = n->get_arg(i); + if (m_manager.is_and(arg)) { + SASSERT(it[i] < to_app(arg)->get_num_args()); + lits.push_back(to_app(arg)->get_arg(it[i])); + } + else { + SASSERT(it[i] == 0); + lits.push_back(arg); + } + } + app * n = m_manager.mk_or(lits.size(), lits.c_ptr()); + new_args.push_back(n); + } + while (product_iterator_next(sz.size(), sz.c_ptr(), it.c_ptr())); + SASSERT(!new_args.empty()); + if (new_args.size() == 1) + r = to_app(new_args[0]); + else + r = m_manager.mk_and(new_args.size(), new_args.c_ptr()); + pr = 0; + if (m_manager.fine_grain_proofs() && r != n) + pr = m_manager.mk_iff_oeq(m_manager.mk_distributivity(n, r)); +} + +void cnf::push_quant(quantifier * q, expr * & r, proof * & pr) { + SASSERT(is_forall(q)); + expr * e = q->get_expr(); + pr = 0; + if (m_manager.is_and(e)) { + expr_ref_buffer new_args(m_manager); + unsigned num = to_app(e)->get_num_args(); + for (unsigned i = 0; i < num; i++) { + quantifier_ref aux(m_manager); + aux = m_manager.update_quantifier(q, 0, 0, 0, 0, to_app(e)->get_arg(i)); + expr_ref new_arg(m_manager); + elim_unused_vars(m_manager, aux, new_arg); + new_args.push_back(new_arg); + } + r = m_manager.mk_and(new_args.size(), new_args.c_ptr()); + if (m_manager.fine_grain_proofs()) + pr = m_manager.mk_iff_oeq(m_manager.mk_push_quant(q, r)); + } + else { + r = q; + } +} + +void cnf::reduce1_or(app * n, bool in_q) { + ptr_buffer new_args; + ptr_buffer new_arg_prs; + get_args(n, in_q, new_args, new_arg_prs); + expr * r; + proof * pr = 0; + if (in_q || m_params.m_cnf_mode == CNF_OPPORTUNISTIC || m_params.m_cnf_mode == CNF_FULL) { + ptr_buffer f_args; + flat_args(n->get_decl(), new_args, f_args); + TRACE("cnf_or", for (unsigned i = 0; i < f_args.size(); i++) tout << mk_pp(f_args[i], m_manager) << "\n";); + approx_nat result_size = approx_result_size_for_disj(f_args); + TRACE("cnf_or", tout << mk_pp(n, m_manager) << "\napprox. result: " << result_size << "\n";); + if (m_params.m_cnf_mode != CNF_OPPORTUNISTIC || result_size < m_params.m_cnf_factor) { + expr_ref_buffer cheap_args(m_manager); + proof_ref_buffer cheap_args_pr(m_manager); + bool named_args; + if (is_too_expensive(result_size, f_args)) { + named_args = true; + name_args(f_args, cheap_args, cheap_args_pr); + } + else { + named_args = false; + cheap_args.append(f_args.size(), f_args.c_ptr()); + } + + app_ref r1(m_manager); + r1 = m_manager.mk_or(cheap_args.size(), cheap_args.c_ptr()); + + // Proof gen support --------------------------- + // r1 is (OR cheap_args) it is only built if proofs are enabled. + // p1 is a proof for (= n r1) + proof * p1 = 0; + if (m_manager.fine_grain_proofs()) { + proof * prs[3]; + app * r[2]; + r[0] = m_manager.mk_or(new_args.size(), new_args.c_ptr()); + prs[0] = n == r[0] ? 0 : m_manager.mk_oeq_congruence(n, r[0], new_arg_prs.size(), new_arg_prs.c_ptr()); + r[1] = m_manager.mk_or(f_args.size(), f_args.c_ptr()); + prs[1] = r[0] == r[1] ? 0 : m_manager.mk_iff_oeq(m_manager.mk_rewrite(r[0], r[1])); + prs[2] = r[1] == r1 ? 0 : m_manager.mk_oeq_congruence(r[1], r1, cheap_args_pr.size(), cheap_args_pr.c_ptr()); + p1 = m_manager.mk_transitivity(3, prs); + } + // -------------------------------------------- + + expr_ref r2(m_manager); + proof_ref p2(m_manager); + m_pull.pull_quant2(r1, r2, p2); + + if (is_quantifier(r2)) { + expr * e = to_quantifier(r2)->get_expr(); + SASSERT(m_manager.is_or(e)); + app * d_r; + proof * d_pr; + distribute(to_app(e), d_r, d_pr); + quantifier_ref r3(m_manager); + r3 = m_manager.update_quantifier(to_quantifier(r2), d_r); + proof * push_pr; + push_quant(r3, r, push_pr); + if (m_manager.fine_grain_proofs()) { + // p1 is a proof of n == r1 + // p2 is a proof of r1 == r2 + p2 = p2 == 0 ? 0 : m_manager.mk_iff_oeq(p2); + proof * p3 = r2 == r3 ? 0 : m_manager.mk_oeq_quant_intro(to_quantifier(r2), r3, d_pr); + CTRACE("cnf_or", p1, tout << "p1:\n" << mk_pp(m_manager.get_fact(p1), m_manager) << "\n";); + CTRACE("cnf_or", p2, tout << "p2:\n" << mk_pp(m_manager.get_fact(p2), m_manager) << "\n";); + CTRACE("cnf_or", p3, tout << "p3:\n" << mk_pp(m_manager.get_fact(p3), m_manager) << "\n";); + TRACE("cnf_or", tout << "r2 == r3: " << (r2 == r3) << "\n" + << mk_pp(r2, m_manager) << "\n" << mk_pp(r3, m_manager) << "\n";); + pr = m_manager.mk_transitivity(p1, p2, p3, push_pr); + } + cache_result(n, in_q, r, pr); + } + else { + SASSERT(p2 == 0); + SASSERT(r1 == r2); + SASSERT(m_manager.is_or(r2)); + app * r3; + distribute(to_app(r2), r3, pr); + r = r3; + pr = m_manager.mk_transitivity(p1, pr); + cache_result(n, in_q, r, pr); + } + return; + } + } + + r = m_manager.mk_or(new_args.size(), new_args.c_ptr()); + if (m_manager.fine_grain_proofs() && n != r) + pr = m_manager.mk_oeq_congruence(n, to_app(r), new_arg_prs.size(), new_arg_prs.c_ptr()); + cache_result(n, in_q, r, pr); +} + +void cnf::reduce1_and(app * n, bool in_q) { + ptr_buffer new_args; + ptr_buffer new_arg_prs; + get_args(n, in_q, new_args, new_arg_prs); + app * r; + proof * pr = 0; + if (in_q || m_params.m_cnf_mode == CNF_OPPORTUNISTIC || m_params.m_cnf_mode == CNF_FULL) { + ptr_buffer f_args; + flat_args(n->get_decl(), new_args, f_args); + r = m_manager.mk_and(f_args.size(), f_args.c_ptr()); + if (m_manager.fine_grain_proofs() && n != r) { + app * r0 = m_manager.mk_and(new_args.size(), new_args.c_ptr()); + proof * p0 = r0 == n ? 0 : m_manager.mk_oeq_congruence(n, r0, new_arg_prs.size(), new_arg_prs.c_ptr()); + proof * p1 = r0 == r ? 0 : m_manager.mk_iff_oeq(m_manager.mk_rewrite(r0, r)); + pr = m_manager.mk_transitivity(p0, p1); + } + } + else { + r = m_manager.mk_and(new_args.size(), new_args.c_ptr()); + if (m_manager.fine_grain_proofs() && n != r) + pr = m_manager.mk_oeq_congruence(n, r, new_arg_prs.size(), new_arg_prs.c_ptr()); + } + cache_result(n, in_q, r, pr); +} + +void cnf::reduce1_label(app * n, bool in_q) { + expr * r; + proof * pr = 0; + expr * new_arg; + proof * new_arg_pr; + get_cached(n->get_arg(0), true, new_arg, new_arg_pr); + if (in_q || m_params.m_cnf_mode == CNF_FULL) { + // TODO: in the current implementation, labels are removed during CNF translation. + // This is satisfactory for Boogie, since it does not use labels inside quantifiers, + // and we only need CNF_QUANT for Superposition Calculus. + r = new_arg; + if (m_manager.fine_grain_proofs()) { + proof * p0 = m_manager.mk_iff_oeq(m_manager.mk_rewrite(n, n->get_arg(0))); + pr = m_manager.mk_transitivity(p0, new_arg_pr); + } + } + else { + r = m_manager.mk_app(n->get_decl(), new_arg); + if (m_manager.fine_grain_proofs() && n != r) + pr = m_manager.mk_oeq_congruence(n, to_app(r), 1, &new_arg_pr); + } + cache_result(n, in_q, r, pr); +} + +void cnf::reduce1_quantifier(quantifier * q, bool in_q) { + expr * new_expr; + proof * new_expr_pr; + get_cached(q->get_expr(), true, new_expr, new_expr_pr); + expr_ref r(m_manager); + proof_ref pr(m_manager); + if (m_manager.is_and(new_expr) && q->is_forall()) { + quantifier_ref q1(m_manager); + q1 = m_manager.update_quantifier(q, new_expr); + expr_ref q2(m_manager); + proof_ref p2(m_manager); + m_pull.pull_quant2(q1, q2, p2); + expr * q3; + proof * p3; + push_quant(to_quantifier(q2), q3, p3); + r = q3; + if (m_manager.fine_grain_proofs()) { + proof * p1 = q == q1 ? 0 : m_manager.mk_oeq_quant_intro(q, q1, new_expr_pr); + p2 = p2 == 0 ? 0 : m_manager.mk_iff_oeq(p2); + pr = m_manager.mk_transitivity(p1, p2, p3); + } + } + else if ((m_manager.is_or(new_expr) || is_forall(new_expr)) && q->is_forall()) { + quantifier_ref q1(m_manager); + q1 = m_manager.update_quantifier(q, new_expr); + m_pull.pull_quant2(q1, r, pr); + if (m_manager.fine_grain_proofs()) { + pr = pr == 0 ? 0 : m_manager.mk_iff_oeq(pr); + proof * p1 = q == q1 ? 0 : m_manager.mk_oeq_quant_intro(q, q1, new_expr_pr); + pr = m_manager.mk_transitivity(p1, pr); + } + } + else { + r = m_manager.update_quantifier(q, new_expr); + if (m_manager.fine_grain_proofs() && r != q) + pr = q == r ? 0 : m_manager.mk_oeq_quant_intro(q, to_quantifier(r), new_expr_pr); + } + + cache_result(q, in_q, r, pr); + TRACE("cnf_quant", tout << mk_pp(q, m_manager) << "\n" << mk_pp(r, m_manager) << "\n";); +} + +cnf::cnf(ast_manager & m, defined_names & n, cnf_params & params): + m_params(params), + m_manager(m), + m_defined_names(n), + m_pull(m), + m_cache(m), + m_todo_defs(m), + m_todo_proofs(m), + m_coarse_proofs(m) { +} + +cnf::~cnf() { +} + +void cnf::reduce(expr * n, expr_ref & r, proof_ref & pr) { + m_coarse_proofs.reset(); + m_todo.reset(); + m_todo.push_back(expr_bool_pair(n, false)); + while (!m_todo.empty()) { + expr_bool_pair pair = m_todo.back(); + expr * n = pair.first; + bool in_q = pair.second; + if (is_cached(n, in_q)) { + m_todo.pop_back(); + } + else if (visit_children(n, in_q)) { + m_todo.pop_back(); + reduce1(n, in_q); + } + } + expr * r2; + proof * pr2; + get_cached(n, false, r2, pr2); + r = r2; + switch (m_manager.proof_mode()) { + case PGM_DISABLED: + pr = m_manager.mk_undef_proof(); + break; + case PGM_COARSE: + remove_duplicates(m_coarse_proofs); + pr = n == r2 ? m_manager.mk_reflexivity(n) : m_manager.mk_cnf_star(n, r2, m_coarse_proofs.size(), m_coarse_proofs.c_ptr()); + break; + case PGM_FINE: + pr = pr2 == 0 ? m_manager.mk_reflexivity(n) : pr2; + break; + } +} + +void cnf::operator()(expr * n, expr_ref_vector & new_defs, proof_ref_vector & new_def_proofs, expr_ref & r, proof_ref & pr) { + if (m_params.m_cnf_mode == CNF_DISABLED) { + r = n; + pr = m_manager.mk_reflexivity(n); + return; + } + + reset(); + reduce(n, r, pr); + for (unsigned i = 0; i < m_todo_defs.size(); i++) { + expr_ref dr(m_manager); + proof_ref dpr(m_manager); + reduce(m_todo_defs.get(i), dr, dpr); + m_result_defs.push_back(dr); + if (m_manager.proofs_enabled()) { + proof * new_pr = m_manager.mk_modus_ponens(m_todo_proofs.get(i), dpr); + m_result_def_proofs.push_back(new_pr); + } + else + m_result_def_proofs.push_back(m_manager.mk_undef_proof()); + } + std::reverse(m_result_defs.begin(), m_result_defs.end()); + new_defs.append(m_result_defs.size(), m_result_defs.c_ptr()); + std::reverse(m_result_def_proofs.begin(), m_result_def_proofs.end()); + new_def_proofs.append(m_result_def_proofs.size(), m_result_def_proofs.c_ptr()); +} + +void cnf::reset() { + m_cache.reset(); + m_todo.reset(); + m_todo_defs.reset(); + m_todo_proofs.reset(); + m_result_defs.reset(); + m_result_def_proofs.reset(); +} diff --git a/lib/cnf.h b/lib/cnf.h new file mode 100644 index 000000000..60c2d2410 --- /dev/null +++ b/lib/cnf.h @@ -0,0 +1,121 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + cnf.h + +Abstract: + + CNF translation + +Author: + + Leonardo de Moura (leonardo) 2008-01-17. + +Revision History: + +--*/ +#ifndef _CNF_H_ +#define _CNF_H_ + +#include"cnf_params.h" +#include"pull_quant.h" +#include"nnf.h" +#include"approx_nat.h" + +/** + \brief Entry into the todo list of the CNF translator. It is also used as the key in the CNF cache. +*/ +struct cnf_entry { + expr * m_node; + bool m_polarity:1; + bool m_in_q:1; + cnf_entry():m_node(0), m_polarity(false), m_in_q(false) {} + cnf_entry(expr * n, bool p, bool in_q):m_node(n), m_polarity(p), m_in_q(in_q) {} + unsigned hash() const; + bool operator==(cnf_entry const & k) const; +}; + +/** + \brief Cache for CNF transformation. It is a mapping from (expr, polarity, in_q) -> (expr, proof) +*/ +class cnf_cache { +public: + typedef std::pair expr_proof_pair; + + typedef map, default_eq > cache; + + ast_manager & m_manager; + cache m_cache; + +public: + cnf_cache(ast_manager & m); + ~cnf_cache() { reset(); } + void insert(cnf_entry const & k, expr * r, proof * pr); + bool contains(cnf_entry const & k) const { return m_cache.contains(k); } + void get(cnf_entry const & k, expr * & r, proof * & pr) const { expr_proof_pair tmp; m_cache.find(k, tmp); r = tmp.first; pr = tmp.second; } + void reset(); +}; + +/** + \brief Functor for converting expressions into CNF. The functor can + optionally process subformulas nested in quantifiers. New names may be + introduced for subformulas that are too expensive to be put into CNF. + + NNF translation must be applied before converting to CNF. + + - To use CNF_QUANT, we must use at least NNF_QUANT + - To use CNF_OPPORTUNISTIC, we must use at least NNF_QUANT + - To use CNF_FULL, we must use NNF_FULL +*/ +class cnf { + typedef std::pair expr_bool_pair; + cnf_params & m_params; + ast_manager & m_manager; + defined_names & m_defined_names; + pull_quant m_pull; + cnf_cache m_cache; + svector m_todo; + expr_ref_vector m_todo_defs; + proof_ref_vector m_todo_proofs; + ptr_vector m_result_defs; + ptr_vector m_result_def_proofs; + proof_ref_vector m_coarse_proofs; + + void cache_result(expr * e, bool in_q, expr * r, proof * pr); + void get_cached(expr * n, bool in_q, expr * & r, proof * & pr) const { m_cache.get(cnf_entry(n, true, in_q), r, pr); } + bool is_cached(expr * n, bool in_q) const { return m_cache.contains(cnf_entry(n, true, in_q)); } + + void visit(expr * n, bool in_q, bool & visited); + bool visit_children(expr * n, bool in_q); + + void get_args(app * n, bool in_q, ptr_buffer & new_args, ptr_buffer & new_arg_prs); + void flat_args(func_decl * d, ptr_buffer const & args, ptr_buffer & flat_args); + approx_nat approx_result_size_for_disj(ptr_buffer const & args); + bool is_too_expensive(approx_nat approx_result_size, ptr_buffer const & args); + void name_args(ptr_buffer const & args, expr_ref_buffer & new_args, proof_ref_buffer & new_arg_prs); + void distribute(app * arg, app * & r, proof * & pr); + void push_quant(quantifier * q, expr * & r, proof * & pr); + void reduce1(expr * n, bool in_q); + void reduce1_or(app * n, bool in_q); + void reduce1_and(app * n, bool in_q); + void reduce1_label(app * n, bool in_q); + void reduce1_quantifier(quantifier * q, bool in_q); + + void reduce(expr * n, expr_ref & r, proof_ref & pr); +public: + cnf(ast_manager & m, defined_names & n, cnf_params & params); + ~cnf(); + void operator()(expr * n, // [IN] expression that should be put into CNF + expr_ref_vector & new_defs, // [OUT] new definitions + proof_ref_vector & new_def_proofs, // [OUT] proofs of the new definitions + expr_ref & r, // [OUT] resultant expression + proof_ref & p // [OUT] proof for (~ n r) + ); + + void reset(); +}; + +#endif /* _CNF_H_ */ + diff --git a/lib/cnf_params.cpp b/lib/cnf_params.cpp new file mode 100644 index 000000000..57d02e8db --- /dev/null +++ b/lib/cnf_params.cpp @@ -0,0 +1,26 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + cnf_params.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-23. + +Revision History: + +--*/ + +#include"cnf_params.h" + +void cnf_params::register_params(ini_params & p) { + p.register_unsigned_param("CNF_FACTOR", m_cnf_factor, "the maximum number of clauses that can be created when converting a subformula"); + p.register_int_param("CNF_MODE", 0, 3, reinterpret_cast(m_cnf_mode), "CNF translation mode: 0 - disabled, 1 - quantifiers in CNF, 2 - 0 + opportunistic, 3 - full"); +} + diff --git a/lib/cnf_params.h b/lib/cnf_params.h new file mode 100644 index 000000000..f694e1716 --- /dev/null +++ b/lib/cnf_params.h @@ -0,0 +1,56 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + cnf_params.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-23. + +Revision History: + +--*/ +#ifndef _CNF_PARAMS_H_ +#define _CNF_PARAMS_H_ + +#include"ini_file.h" + +/** + \brief CNF translation mode. The cheapest mode is CNF_QUANT, and + the most expensive is CNF_FULL. +*/ +enum cnf_mode { + CNF_DISABLED, /* CNF translator is disabled. + This mode is sufficient when using E-matching. + */ + CNF_QUANT, /* A subformula is put into CNF if it is inside of a + quantifier. + + This mode is sufficient when using Superposition + Calculus. + */ + CNF_OPPORTUNISTIC, /* a subformula is also put in CNF if it is cheap. */ + CNF_FULL /* Everything is put into CNF, new names are introduced + if it is too expensive. */ +}; + +struct cnf_params { + cnf_mode m_cnf_mode; + unsigned m_cnf_factor; + cnf_params(): + m_cnf_mode(CNF_DISABLED), + m_cnf_factor(4) { + } + + void register_params(ini_params & p); +}; + + +#endif /* _CNF_PARAMS_H_ */ + diff --git a/lib/cofactor_elim_term_ite.cpp b/lib/cofactor_elim_term_ite.cpp new file mode 100644 index 000000000..b39d82201 --- /dev/null +++ b/lib/cofactor_elim_term_ite.cpp @@ -0,0 +1,712 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + cofactor_elim_term_ite.cpp + +Abstract: + + Eliminate term if-then-else's using cofactors. + +Author: + + Leonardo de Moura (leonardo) 2011-06-05. + +Revision History: + +--*/ +#include"cofactor_elim_term_ite.h" +#include"mk_simplified_app.h" +#include"rewriter_def.h" +#include"cooperate.h" +#include"for_each_expr.h" +#include"ast_smt2_pp.h" +#include"ast_ll_pp.h" +#include"tactic.h" + +struct cofactor_elim_term_ite::imp { + ast_manager & m; + params_ref m_params; + unsigned long long m_max_memory; + volatile bool m_cancel; + + void checkpoint() { + cooperate("cofactor ite"); + if (memory::get_allocation_size() > m_max_memory) + throw tactic_exception(TACTIC_MAX_MEMORY_MSG); + if (m_cancel) + throw tactic_exception(TACTIC_MAX_MEMORY_MSG); + } + + // Collect atoms that contain term if-then-else + struct analyzer { + struct frame { + expr * m_t; + unsigned m_first:1; + unsigned m_form_ctx:1; + frame(expr * t, bool form_ctx):m_t(t), m_first(true), m_form_ctx(form_ctx) {} + }; + + ast_manager & m; + imp & m_owner; + obj_hashtable m_candidates; + expr_fast_mark1 m_processed; + expr_fast_mark2 m_has_term_ite; + svector m_frame_stack; + + analyzer(ast_manager & _m, imp & owner):m(_m), m_owner(owner) {} + + void push_frame(expr * t, bool form_ctx) { + m_frame_stack.push_back(frame(t, form_ctx && m.is_bool(t))); + } + + void visit(expr * t, bool form_ctx, bool & visited) { + if (!m_processed.is_marked(t)) { + visited = false; + push_frame(t, form_ctx); + } + } + + void save_candidate(expr * t, bool form_ctx) { + if (!form_ctx) + return; + if (!m.is_bool(t)) + return; + if (!m_has_term_ite.is_marked(t)) + return; + if (!is_app(t)) + return; + if (to_app(t)->get_family_id() == m.get_basic_family_id()) { + switch (to_app(t)->get_decl_kind()) { + case OP_OR: + case OP_AND: + case OP_NOT: + case OP_XOR: + case OP_IMPLIES: + case OP_TRUE: + case OP_FALSE: + case OP_ITE: + case OP_IFF: + return; + case OP_EQ: + case OP_DISTINCT: + if (m.is_bool(to_app(t)->get_arg(0))) + return; + break; + default: + break; + } + } + // it is an atom in a formula context (i.e., it is not nested inside a term), + // and it contains a term if-then-else. + m_candidates.insert(t); + } + + void operator()(expr * t) { + SASSERT(m.is_bool(t)); + push_frame(t, true); + SASSERT(!m_frame_stack.empty()); + while (!m_frame_stack.empty()) { + frame & fr = m_frame_stack.back(); + expr * t = fr.m_t; + bool form_ctx = fr.m_form_ctx; + TRACE("cofactor_ite_analyzer", tout << "processing, form_ctx: " << form_ctx << "\n" << mk_bounded_pp(t, m) << "\n";); + + m_owner.checkpoint(); + + if (m_processed.is_marked(t)) { + save_candidate(t, form_ctx); + m_frame_stack.pop_back(); + continue; + } + + if (m.is_term_ite(t)) { + m_has_term_ite.mark(t); + m_processed.mark(t); + m_frame_stack.pop_back(); + continue; + } + + if (fr.m_first) { + fr.m_first = false; + bool visited = true; + if (is_app(t)) { + unsigned num_args = to_app(t)->get_num_args(); + for (unsigned i = 0; i < num_args; i++) + visit(to_app(t)->get_arg(i), form_ctx, visited); + } + // ignoring quantifiers + if (!visited) + continue; + } + + if (is_app(t)) { + unsigned num_args = to_app(t)->get_num_args(); + unsigned i; + for (i = 0; i < num_args; i++) { + if (m_has_term_ite.is_marked(to_app(t)->get_arg(i))) + break; + } + if (i < num_args) { + m_has_term_ite.mark(t); + TRACE("cofactor_ite_analyzer", tout << "saving candidate: " << form_ctx << "\n" << mk_bounded_pp(t, m) << "\n";); + save_candidate(t, form_ctx); + } + } + else { + SASSERT(is_quantifier(t) || is_var(t)); + // ignoring quantifiers... they are treated as black boxes. + } + m_processed.mark(t); + m_frame_stack.pop_back(); + } + m_processed.reset(); + m_has_term_ite.reset(); + } + }; + + expr * get_first(expr * t) { + typedef std::pair frame; + expr_fast_mark1 visited; + sbuffer stack; + stack.push_back(frame(t, 0)); + while (!stack.empty()) { + start: + checkpoint(); + frame & fr = stack.back(); + expr * curr = fr.first; + if (m.is_term_ite(curr)) + return to_app(curr)->get_arg(0); + switch (curr->get_kind()) { + case AST_VAR: + case AST_QUANTIFIER: + // ignore quantifiers + stack.pop_back(); + break; + case AST_APP: { + unsigned num_args = to_app(curr)->get_num_args(); + while (fr.second < num_args) { + expr * arg = to_app(curr)->get_arg(fr.second); + fr.second++; + if (arg->get_ref_count() > 1) { + if (visited.is_marked(arg)) + continue; + visited.mark(arg); + } + switch (arg->get_kind()) { + case AST_VAR: + case AST_QUANTIFIER: + // ingore quantifiers + break; + case AST_APP: + if (to_app(arg)->get_num_args() > 0) { + stack.push_back(frame(arg, 0)); + goto start; + } + break; + default: + UNREACHABLE(); + break; + } + } + stack.pop_back(); + break; + } + default: + UNREACHABLE(); + break; + } + } + return 0; + } + + /** + \brief Fuctor for selecting the term if-then-else condition with the most number of occurrences. + */ + expr * get_best(expr * t) { + typedef std::pair frame; + obj_map occs; + expr_fast_mark1 visited; + sbuffer stack; + + stack.push_back(frame(t, 0)); + while (!stack.empty()) { + start: + checkpoint(); + frame & fr = stack.back(); + expr * curr = fr.first; + switch (curr->get_kind()) { + case AST_VAR: + case AST_QUANTIFIER: + // ignore quantifiers + stack.pop_back(); + break; + case AST_APP: { + unsigned num_args = to_app(curr)->get_num_args(); + bool is_term_ite = m.is_term_ite(curr); + while (fr.second < num_args) { + expr * arg = to_app(curr)->get_arg(fr.second); + if (fr.second == 0 && is_term_ite) { + unsigned num = 0; + if (occs.find(arg, num)) + occs.insert(arg, num+1); + else + occs.insert(arg, 1); + } + fr.second++; + if (arg->get_ref_count() > 1) { + if (visited.is_marked(arg)) + continue; + visited.mark(arg); + } + switch (arg->get_kind()) { + case AST_VAR: + case AST_QUANTIFIER: + // ingore quantifiers + break; + case AST_APP: + if (to_app(arg)->get_num_args() > 0) { + stack.push_back(frame(arg, 0)); + goto start; + } + break; + default: + UNREACHABLE(); + break; + } + } + stack.pop_back(); + break; + } + default: + UNREACHABLE(); + break; + } + } + expr * best = 0; + unsigned best_occs = 0; + obj_map::iterator it = occs.begin(); + obj_map::iterator end = occs.end(); + for (; it != end; ++it) { + if ((!best) || + (get_depth(it->m_key) < get_depth(best)) || + (get_depth(it->m_key) == get_depth(best) && it->m_value > best_occs) || + // break ties by giving preference to equalities + (get_depth(it->m_key) == get_depth(best) && it->m_value == best_occs && m.is_eq(it->m_key) && !m.is_eq(best))) { + best = it->m_key; + best_occs = it->m_value; + } + } + visited.reset(); + CTRACE("cofactor_ite_get_best", best != 0, tout << "best num-occs: " << best_occs << "\n" << mk_ismt2_pp(best, m) << "\n";); + return best; + } + + void updt_params(params_ref const & p) { + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + } + + void set_cancel(bool f) { + m_cancel = f; + } + + struct cofactor_rw_cfg : public default_rewriter_cfg { + ast_manager & m; + imp & m_owner; + obj_hashtable * m_has_term_ite; + mk_simplified_app m_mk_app; + expr * m_atom; + bool m_sign; + expr * m_term; + app * m_value; + bool m_strict_lower; + app * m_lower; + bool m_strict_upper; + app * m_upper; + + cofactor_rw_cfg(ast_manager & _m, imp & owner, obj_hashtable * has_term_ite = 0): + m(_m), + m_owner(owner), + m_has_term_ite(has_term_ite), + m_mk_app(m, owner.m_params) { + } + + bool max_steps_exceeded(unsigned num_steps) const { + m_owner.checkpoint(); + return false; + } + + bool pre_visit(expr * t) { + return true; + } + + void set_cofactor_atom(expr * t) { + if (m.is_not(t)) { + m_atom = to_app(t)->get_arg(0); + m_sign = true; + m_term = 0; + // TODO: bounds + } + else { + m_atom = t; + m_sign = false; + m_term = 0; + expr * lhs; + expr * rhs; + if (m.is_eq(t, lhs, rhs)) { + if (m.is_value(lhs)) { + m_term = rhs; + m_value = to_app(lhs); + TRACE("set_cofactor_atom", tout << "term:\n" << mk_ismt2_pp(m_term, m) << "\nvalue: " << mk_ismt2_pp(m_value, m) << "\n";); + } + else if (m.is_value(rhs)) { + m_term = lhs; + m_value = to_app(rhs); + TRACE("set_cofactor_atom", tout << "term:\n" << mk_ismt2_pp(m_term, m) << "\nvalue: " << mk_ismt2_pp(m_value, m) << "\n";); + } + } + // TODO: bounds + } + } + + bool rewrite_patterns() const { return false; } + + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + result_pr = 0; + return m_mk_app.mk_core(f, num, args, result); + } + + bool get_subst(expr * s, expr * & t, proof * & pr) { + pr = 0; + + if (s == m_atom) { + t = m_sign ? m.mk_false() : m.mk_true(); + return true; + } + + if (s == m_term && m_value != 0) { + t = m_value; + return true; + } + + // TODO: handle simple bounds + // Example: s is of the form (<= s 10) and m_term == s, and m_upper is 9 + // then rewrite to true. + + return false; + } + + }; + + struct cofactor_rw : rewriter_tpl { + cofactor_rw_cfg m_cfg; + public: + cofactor_rw(ast_manager & m, imp & owner, obj_hashtable * has_term_ite = 0): + rewriter_tpl(m, false, m_cfg), + m_cfg(m, owner, has_term_ite) { + } + + void set_cofactor_atom(expr * t) { + m_cfg.set_cofactor_atom(t); + reset(); + } + }; + + struct main_rw_cfg : public default_rewriter_cfg { + ast_manager & m; + imp & m_owner; + cofactor_rw m_cofactor; + obj_hashtable const & m_candidates; + obj_map m_cache; + expr_ref_vector m_cache_domain; + + main_rw_cfg(ast_manager & _m, imp & owner, obj_hashtable & candidates): + m(_m), + m_owner(owner), + m_cofactor(m, m_owner), + m_candidates(candidates), + m_cache_domain(_m) { + } + + bool max_steps_exceeded(unsigned num_steps) const { + m_owner.checkpoint(); + return false; + } + + bool pre_visit(expr * t) { + return m.is_bool(t) && !is_quantifier(t); + } + + bool get_subst(expr * s, expr * & t, proof * & t_pr) { + if (m_candidates.contains(s)) { + t_pr = 0; + + if (m_cache.find(s, t)) + return true; + unsigned step = 0; + TRACE("cofactor_ite", tout << "cofactor target:\n" << mk_ismt2_pp(s, m) << "\n";); + expr_ref curr(m); + curr = s; + while (true) { + // expr * c = m_owner.get_best(curr); + expr * c = m_owner.get_first(curr); + if (c == 0) { + m_cache.insert(s, curr); + m_cache_domain.push_back(curr); + t = curr.get(); + return true; + } + step++; + expr_ref pos_cofactor(m); + expr_ref neg_cofactor(m); + m_cofactor.set_cofactor_atom(c); + m_cofactor(curr, pos_cofactor); + expr_ref neg_c(m); + neg_c = m.is_not(c) ? to_app(c)->get_arg(0) : m.mk_not(c); + m_cofactor.set_cofactor_atom(neg_c); + m_cofactor(curr, neg_cofactor); + curr = m.mk_ite(c, pos_cofactor, neg_cofactor); + TRACE("cofactor_ite", tout << "cofactor_ite step: " << step << "\n" << mk_ismt2_pp(curr, m) << "\n";); + } + } + return false; + } + }; + + struct main_rw : rewriter_tpl { + main_rw_cfg m_cfg; + public: + main_rw(ast_manager & m, imp & owner, obj_hashtable & candidates): + rewriter_tpl(m, false, m_cfg), + m_cfg(m, owner, candidates) { + } + }; + + struct bottom_up_elim { + typedef std::pair frame; + ast_manager & m; + imp & m_owner; + obj_map m_cache; + expr_ref_vector m_cache_domain; + obj_hashtable m_has_term_ite; + svector m_frames; + cofactor_rw m_cofactor; + + bottom_up_elim(ast_manager & _m, imp & owner): + m(_m), + m_owner(owner), + m_cache_domain(m), + m_cofactor(m, owner, &m_has_term_ite) { + } + + bool is_atom(expr * t) const { + if (!m.is_bool(t)) + return false; + if (!is_app(t)) + return false; + if (to_app(t)->get_family_id() == m.get_basic_family_id()) { + switch (to_app(t)->get_decl_kind()) { + case OP_EQ: + case OP_DISTINCT: + if (m.is_bool(to_app(t)->get_arg(0))) + return false; + else + return true; + default: + return false; + } + } + return true; + } + + void cofactor(expr * t, expr_ref & r) { + unsigned step = 0; + TRACE("cofactor_ite", tout << "cofactor target:\n" << mk_ismt2_pp(t, m) << "\n";); + expr_ref curr(m); + curr = t; + while (true) { + expr * c = m_owner.get_best(curr); + // expr * c = m_owner.get_first(curr); + if (c == 0) { + r = curr.get(); + return; + } + step++; + expr_ref pos_cofactor(m); + expr_ref neg_cofactor(m); + m_cofactor.set_cofactor_atom(c); + m_cofactor(curr, pos_cofactor); + expr_ref neg_c(m); + neg_c = m.is_not(c) ? to_app(c)->get_arg(0) : m.mk_not(c); + m_cofactor.set_cofactor_atom(neg_c); + m_cofactor(curr, neg_cofactor); + if (pos_cofactor == neg_cofactor) { + curr = pos_cofactor; + TRACE("cofactor_ite", tout << "cofactor_ite step: " << step << "\n" << mk_ismt2_pp(curr, m) << "\n";); + continue; + } + if (m.is_true(pos_cofactor) && m.is_false(neg_cofactor)) { + curr = c; + TRACE("cofactor_ite", tout << "cofactor_ite step: " << step << "\n" << mk_ismt2_pp(curr, m) << "\n";); + continue; + } + if (m.is_false(pos_cofactor) && m.is_true(neg_cofactor)) { + curr = neg_c; + TRACE("cofactor_ite", tout << "cofactor_ite step: " << step << "\n" << mk_ismt2_pp(curr, m) << "\n";); + continue; + } + curr = m.mk_ite(c, pos_cofactor, neg_cofactor); + TRACE("cofactor_ite", tout << "cofactor_ite step: " << step << "\n" << mk_ismt2_pp(curr, m) << "\n";); + } + } + + void visit(expr * t, bool & visited) { + if (!m_cache.contains(t)) { + m_frames.push_back(frame(t, true)); + visited = false; + } + } + + void operator()(expr * t, expr_ref & r) { + ptr_vector new_args; + m_frames.push_back(frame(t, true)); + while (!m_frames.empty()) { + m_owner.checkpoint(); + frame & fr = m_frames.back(); + expr * t = fr.first; + TRACE("cofactor_bug", tout << "processing: " << t->get_id() << " :first " << fr.second << "\n";); + if (!is_app(t)) { + m_cache.insert(t, t); + m_frames.pop_back(); + continue; + } + if (m_cache.contains(t)) { + m_frames.pop_back(); + continue; + } + if (fr.second) { + fr.second = false; + bool visited = true; + unsigned i = to_app(t)->get_num_args(); + while (i > 0) { + --i; + expr * arg = to_app(t)->get_arg(i); + visit(arg, visited); + } + if (!visited) + continue; + } + new_args.reset(); + bool has_new_args = false; + bool has_term_ite = false; + unsigned num = to_app(t)->get_num_args(); + for (unsigned i = 0; i < num; i++) { + expr * arg = to_app(t)->get_arg(i); + expr * new_arg = 0; + TRACE("cofactor_bug", tout << "collecting child: " << arg->get_id() << "\n";); + m_cache.find(arg, new_arg); + SASSERT(new_arg != 0); + if (new_arg != arg) + has_new_args = true; + if (m_has_term_ite.contains(new_arg)) + has_term_ite = true; + new_args.push_back(new_arg); + } + if (m.is_term_ite(t)) + has_term_ite = true; + expr_ref new_t(m); + if (has_new_args) + new_t = m.mk_app(to_app(t)->get_decl(), num, new_args.c_ptr()); + else + new_t = t; + if (has_term_ite && is_atom(new_t)) { + // update new_t + expr_ref new_new_t(m); + m_has_term_ite.insert(new_t); + cofactor(new_t, new_new_t); + m_has_term_ite.erase(new_t); + new_t = new_new_t; + has_term_ite = false; + } + if (has_term_ite) + m_has_term_ite.insert(new_t); + SASSERT(new_t.get() != 0); + TRACE("cofactor_bug", tout << "caching: " << t->get_id() << "\n";); +#if 0 + counter ++; + verbose_stream() << counter << "\n"; +#endif + m_cache.insert(t, new_t); + m_cache_domain.push_back(new_t); + m_frames.pop_back(); + } + expr * result = 0; + m_cache.find(t, result); + r = result; + } + }; + + imp(ast_manager & _m, params_ref const & p): + m(_m), + m_params(p) { + m_cancel = false; + updt_params(p); + } + + void operator()(expr * t, expr_ref & r) { +#if 0 + analyzer proc(m, *this); + proc(t); + main_rw rw(m, *this, proc.m_candidates); + rw(t, r); +#else + bottom_up_elim proc(m, *this); + proc(t, r); +#endif + } +}; + + +cofactor_elim_term_ite::cofactor_elim_term_ite(ast_manager & m, params_ref const & p): + m_imp(alloc(imp, m, p)), + m_params(p) { +} + +cofactor_elim_term_ite::~cofactor_elim_term_ite() { + imp * d = m_imp; + #pragma omp critical (cofactor_elim_term_ite) + { + m_imp = 0; + } + dealloc(d); +} + +void cofactor_elim_term_ite::updt_params(params_ref const & p) { + m_imp->updt_params(p); +} + +void cofactor_elim_term_ite::get_param_descrs(param_descrs & r) { +} + +void cofactor_elim_term_ite::operator()(expr * t, expr_ref & r) { + m_imp->operator()(t, r); +} + +void cofactor_elim_term_ite::set_cancel(bool f) { + #pragma omp critical (cofactor_elim_term_ite) + { + if (m_imp) + m_imp->set_cancel(f); + } +} + +void cofactor_elim_term_ite::cleanup() { + ast_manager & m = m_imp->m; + #pragma omp critical (cofactor_elim_term_ite) + { + dealloc(m_imp); + m_imp = alloc(imp, m, m_params); + } +} + diff --git a/lib/cofactor_elim_term_ite.h b/lib/cofactor_elim_term_ite.h new file mode 100644 index 000000000..9b325b1f0 --- /dev/null +++ b/lib/cofactor_elim_term_ite.h @@ -0,0 +1,44 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + cofactor_elim_term_ite.h + +Abstract: + + Eliminate (ground) term if-then-else's using cofactors. + +Author: + + Leonardo de Moura (leonardo) 2011-06-05. + +Revision History: + +--*/ +#ifndef _COFACTOR_ELIM_TERM_ITE_H_ +#define _COFACTOR_ELIM_TERM_ITE_H_ + +#include"ast.h" +#include"params.h" + +class cofactor_elim_term_ite { + struct imp; + imp * m_imp; + params_ref m_params; +public: + cofactor_elim_term_ite(ast_manager & m, params_ref const & p = params_ref()); + virtual ~cofactor_elim_term_ite(); + + void updt_params(params_ref const & p); + static void get_param_descrs(param_descrs & r); + + void operator()(expr * t, expr_ref & r); + + void cancel() { set_cancel(true); } + void reset_cancel() { set_cancel(false); } + void set_cancel(bool f); + void cleanup(); +}; + +#endif diff --git a/lib/cofactor_term_ite_tactic.cpp b/lib/cofactor_term_ite_tactic.cpp new file mode 100644 index 000000000..16b4d1ad4 --- /dev/null +++ b/lib/cofactor_term_ite_tactic.cpp @@ -0,0 +1,82 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + cofactor_term_ite_tactic.cpp + +Abstract: + + Wrap cofactor_elim_term_ite as a tactic. + Eliminate (ground) term if-then-else's using cofactors. + +Author: + + Leonardo de Moura (leonardo) 2012-02-20. + +Revision History: + +--*/ +#include"tactical.h" +#include"cofactor_elim_term_ite.h" + +/** + \brief Wrapper for applying cofactor_elim_term_ite in an assertion set. + */ +class cofactor_term_ite_tactic : public tactic { + params_ref m_params; + cofactor_elim_term_ite m_elim_ite; + + void process(goal & g) { + ast_manager & m = g.m(); + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + if (g.inconsistent()) + break; + expr * f = g.form(i); + expr_ref new_f(m); + m_elim_ite(f, new_f); + g.update(i, new_f); + } + } + +public: + cofactor_term_ite_tactic(ast_manager & m, params_ref const & p): + m_params(p), + m_elim_ite(m, p) { + } + + virtual tactic * translate(ast_manager & m) { + return alloc(cofactor_term_ite_tactic, m, m_params); + } + + virtual ~cofactor_term_ite_tactic() {} + virtual void updt_params(params_ref const & p) { m_params = p; m_elim_ite.updt_params(p); } + static void get_param_descrs(param_descrs & r) { cofactor_elim_term_ite::get_param_descrs(r); } + virtual void collect_param_descrs(param_descrs & r) { get_param_descrs(r); } + + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + fail_if_proof_generation("cofactor-term-ite", g); + fail_if_unsat_core_generation("cofactor-term-ite", g); + tactic_report report("cofactor-term-ite", *g); + mc = 0; pc = 0; core = 0; + process(*(g.get())); + g->inc_depth(); + result.push_back(g.get()); + TRACE("cofactor-term-ite", g->display(tout);); + SASSERT(g->is_well_sorted()); + } + + virtual void cleanup() { return m_elim_ite.cleanup(); } + + virtual void set_cancel(bool f) { m_elim_ite.set_cancel(f); } +}; + +tactic * mk_cofactor_term_ite_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(cofactor_term_ite_tactic, m, p)); +} diff --git a/lib/cofactor_term_ite_tactic.h b/lib/cofactor_term_ite_tactic.h new file mode 100644 index 000000000..46eba627f --- /dev/null +++ b/lib/cofactor_term_ite_tactic.h @@ -0,0 +1,29 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + cofactor_term_ite_tactic.h + +Abstract: + + Wrap cofactor_elim_term_ite as a tactic. + Eliminate (ground) term if-then-else's using cofactors. + +Author: + + Leonardo de Moura (leonardo) 2012-02-20. + +Revision History: + +--*/ +#ifndef _COFACTOR_TERM_ITE_TACTIC_H_ +#define _COFACTOR_TERM_ITE_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_cofactor_term_ite_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/contains_var.h b/lib/contains_var.h new file mode 100644 index 000000000..ae1716084 --- /dev/null +++ b/lib/contains_var.h @@ -0,0 +1,27 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + contains_var.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-06-12. + +Revision History: + +--*/ +#ifndef _CONTAINS_VAR_H_ +#define _CONTAINS_VAR_H_ + +class ast; + +bool contains_var(ast * n); + +#endif /* _CONTAINS_VAR_H_ */ + diff --git a/lib/converter.h b/lib/converter.h new file mode 100644 index 000000000..e9c0847a4 --- /dev/null +++ b/lib/converter.h @@ -0,0 +1,142 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + converter.h + +Abstract: + + Abstract class and templates for proof and model converters. + +Author: + + Leonardo (leonardo) 2011-11-14 + +Notes: + +--*/ +#ifndef _CONVERTER_H_ +#define _CONVERTER_H_ + +#include"vector.h" +#include"ref.h" +#include"ast_translation.h" + +class converter { + unsigned m_ref_count; +public: + converter():m_ref_count(0) {} + virtual ~converter() {} + + void inc_ref() { ++m_ref_count; } + void dec_ref() { + --m_ref_count; + if (m_ref_count == 0) + dealloc(this); + } + + virtual void cancel() {} + + // for debugging purposes + virtual void display(std::ostream & out) {} +}; + +template +class concat_converter : public T { +protected: + ref m_c1; + ref m_c2; + + template + T * translate_core(ast_translation & translator) { + T * t1 = m_c1->translate(translator); + T * t2 = m_c2->translate(translator); + return alloc(T2, t1, t2); + } + +public: + concat_converter(T * c1, T * c2):m_c1(c1), m_c2(c2) {} + + virtual ~concat_converter() {} + + virtual void cancel() { + m_c2->cancel(); + m_c1->cancel(); + } + + virtual char const * get_name() const = 0; + + virtual void display(std::ostream & out) { + out << "(" << get_name() << "\n"; + m_c1->display(out); + m_c2->display(out); + out << ")\n"; + } +}; + +template +class concat_star_converter : public T { +protected: + ref m_c1; + ptr_vector m_c2s; + unsigned_vector m_szs; + + template + T * translate_core(ast_translation & translator) { + T * t1 = m_c1 ? m_c1->translate(translator) : 0; + ptr_buffer t2s; + unsigned num = m_c2s.size(); + for (unsigned i = 0; i < num; i++) + t2s.push_back(m_c2s[i] ? m_c2s[i]->translate(translator) : 0); + return alloc(T2, t1, num, t2s.c_ptr(), m_szs.c_ptr()); + } + +public: + concat_star_converter(T * c1, unsigned num, T * const * c2s, unsigned * szs): + m_c1(c1) { + for (unsigned i = 0; i < num; i++) { + T * c2 = c2s[i]; + if (c2) + c2->inc_ref(); + m_c2s.push_back(c2); + m_szs.push_back(szs[i]); + } + } + + virtual ~concat_star_converter() { + unsigned sz = m_c2s.size(); + for (unsigned i = 0; i < sz; i++) { + T * c2 = m_c2s[i]; + if (c2) + c2->dec_ref(); + } + } + + virtual void cancel() { + if (m_c1) + m_c1->cancel(); + unsigned num = m_c2s.size(); + for (unsigned i = 0; i < num; i++) { + if (m_c2s[i]) + m_c2s[i]->cancel(); + } + } + + virtual char const * get_name() const = 0; + + virtual void display(std::ostream & out) { + out << "(" << get_name() << "\n"; + if (m_c1) + m_c1->display(out); + out << "(\n"; + unsigned num = m_c2s.size(); + for (unsigned i = 0; i < num; i++) + if (m_c2s[i]) + m_c2s[i]->display(out); + out << "))\n"; + } +}; + +#endif + diff --git a/lib/cooperate.cpp b/lib/cooperate.cpp new file mode 100644 index 000000000..c941b18e5 --- /dev/null +++ b/lib/cooperate.cpp @@ -0,0 +1,87 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + cooperate.cpp + +Abstract: + + Cooperation support + +Author: + + Leonardo (leonardo) 2011-05-17 + +Notes: + +--*/ +#include"z3_omp.h" +#include"cooperate.h" +#include"trace.h" +#include"debug.h" + +struct cooperation_lock { + omp_nest_lock_t m_lock; + char const * m_task; + volatile int m_owner_thread; + cooperation_lock() { + omp_set_nested(1); + omp_init_nest_lock(&m_lock); + m_task = 0; + m_owner_thread = -1; + } + ~cooperation_lock() { + omp_destroy_nest_lock(&m_lock); + } +}; + +cooperation_lock g_lock; + +bool cooperation_ctx::g_cooperate = false; + +void cooperation_ctx::checkpoint(char const * task) { + SASSERT(cooperation_ctx::enabled()); + + int tid = omp_get_thread_num(); + if (g_lock.m_owner_thread == tid) { + g_lock.m_owner_thread = -1; + omp_unset_nest_lock(&(g_lock.m_lock)); + } + // this critical section is used to force the owner thread to give a chance to + // another thread to get the lock + #pragma omp critical (z3_cooperate) + { + omp_set_nest_lock(&(g_lock.m_lock)); + TRACE("cooperate_detail", tout << task << ", tid: " << tid << "\n";); + CTRACE("cooperate", g_lock.m_task != task, tout << "moving to task: " << task << "\n";); + g_lock.m_owner_thread = tid; + } +} + +cooperation_section::cooperation_section() { + SASSERT(!cooperation_ctx::enabled()); + SASSERT(!omp_in_parallel()); + cooperation_ctx::g_cooperate = true; +} + +cooperation_section::~cooperation_section() { + SASSERT(cooperation_ctx::enabled()); + cooperation_ctx::g_cooperate = false; +} + +init_task::init_task(char const * task) { + SASSERT(cooperation_ctx::enabled()); + SASSERT(omp_in_parallel()); + cooperation_ctx::checkpoint(task); +} + +init_task::~init_task() { + int tid = omp_get_thread_num(); + if (g_lock.m_owner_thread == tid) { + g_lock.m_owner_thread = -1; + omp_unset_nest_lock(&(g_lock.m_lock)); + } +} + + diff --git a/lib/cooperate.h b/lib/cooperate.h new file mode 100644 index 000000000..ea7a00c8f --- /dev/null +++ b/lib/cooperate.h @@ -0,0 +1,50 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + cooperate.h + +Abstract: + + Cooperation support + +Author: + + Leonardo (leonardo) 2011-05-17 + +Notes: + +--*/ +#ifndef _COOPERATE_H_ +#define _COOPERATE_H_ + +class cooperation_section; + +class cooperation_ctx { + friend class cooperation_section; + static bool g_cooperate; +public: + static bool enabled() { return g_cooperate; } + static void checkpoint(char const * task); +}; + +inline void cooperate(char const * task) { + if (cooperation_ctx::enabled()) cooperation_ctx::checkpoint(task); +} + +// must be declared before "#pragma parallel" to enable cooperation +class cooperation_section { +public: + cooperation_section(); + ~cooperation_section(); +}; + +// must be first declaration inside "#pragma parallel for" +class init_task { +public: + init_task(char const * task); + ~init_task(); +}; + +#endif diff --git a/lib/cost_evaluator.cpp b/lib/cost_evaluator.cpp new file mode 100644 index 000000000..fe30f9261 --- /dev/null +++ b/lib/cost_evaluator.cpp @@ -0,0 +1,104 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + cost_evaluator.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-14. + +Revision History: + +--*/ +#include"cost_evaluator.h" +#include"warning.h" + +cost_evaluator::cost_evaluator(ast_manager & m): + m_manager(m), + m_util(m) { +} + +float cost_evaluator::eval(expr * f) const { +#define E(IDX) eval(to_app(f)->get_arg(IDX)) + if (is_app(f)) { + unsigned num_args; + family_id fid = to_app(f)->get_family_id(); + if (fid == m_manager.get_basic_family_id()) { + switch (to_app(f)->get_decl_kind()) { + case OP_TRUE: return 1.0f; + case OP_FALSE: return 0.0f; + case OP_NOT: return E(0) == 0.0f ? 1.0f : 0.0f; + case OP_AND: + num_args = to_app(f)->get_num_args(); + for (unsigned i = 0; i < num_args; i++) + if (E(i) == 0.0f) + return 0.0f; + return 1.0f; + case OP_OR: + num_args = to_app(f)->get_num_args(); + for (unsigned i = 0; i < num_args; i++) + if (E(i) != 0.0f) + return 1.0f; + return 0.0f; + case OP_ITE: return E(0) != 0.0f ? E(1) : E(2); + case OP_EQ: + case OP_IFF: return E(0) == E(1) ? 1.0f : 0.0f; + case OP_XOR: return E(0) != E(1) ? 1.0f : 0.0f; + case OP_IMPLIES: + if (E(0) == 0.0f) + return 1.0f; + return E(1) != 0.0f ? 1.0f : 0.0f; + default: + ; + } + } + else if (fid == m_util.get_family_id()) { + switch (to_app(f)->get_decl_kind()) { + case OP_NUM: { + rational r = to_app(f)->get_decl()->get_parameter(0).get_rational(); + return static_cast(numerator(r).get_int64())/static_cast(denominator(r).get_int64()); + } + case OP_LE: return E(0) <= E(1) ? 1.0f : 0.0f; + case OP_GE: return E(0) >= E(1) ? 1.0f : 0.0f; + case OP_LT: return E(0) < E(1) ? 1.0f : 0.0f; + case OP_GT: return E(0) > E(1) ? 1.0f : 0.0f; + case OP_ADD: return E(0) + E(1); + case OP_SUB: return E(0) - E(1); + case OP_UMINUS: return - E(0); + case OP_MUL: return E(0) * E(1); + case OP_DIV: { + float q = E(1); + if (q == 0.0f) { + warning_msg("cost function division by zero"); + return 1.0f; + } + return E(0) / q; + } + default: + ; + } + } + } + else if (is_var(f)) { + unsigned idx = to_var(f)->get_idx(); + if (idx < m_num_args) + return m_args[m_num_args - idx - 1]; + } + warning_msg("cost function evaluation error"); + return 1.0f; +} + +float cost_evaluator::operator()(expr * f, unsigned num_args, float const * args) { + m_num_args = num_args; + m_args = args; + return eval(f); +} + + + diff --git a/lib/cost_evaluator.h b/lib/cost_evaluator.h new file mode 100644 index 000000000..c32d7a924 --- /dev/null +++ b/lib/cost_evaluator.h @@ -0,0 +1,43 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + cost_evaluator.h + +Abstract: + + Simple evaluator for cost function + +Author: + + Leonardo de Moura (leonardo) 2008-06-14. + +Revision History: + +--*/ +#ifndef _COST_EVALUATOR_H_ +#define _COST_EVALUATOR_H_ + +#include"ast.h" +#include"arith_decl_plugin.h" + +class cost_evaluator { + ast_manager & m_manager; + arith_util m_util; + unsigned m_num_args; + float const * m_args; + float eval(expr * f) const; +public: + cost_evaluator(ast_manager & m); + /** + I'm using the same standard used in quantifier instantiation. + (VAR 0) is stored in the last position of the array. + ... + (VAR (num_args - 1)) is stored in the first position of the array. + */ + float operator()(expr * f, unsigned num_args, float const * args); +}; + +#endif /* _COST_EVALUATOR_H_ */ + diff --git a/lib/cost_parser.cpp b/lib/cost_parser.cpp new file mode 100644 index 000000000..a13e9b107 --- /dev/null +++ b/lib/cost_parser.cpp @@ -0,0 +1,70 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + cost_parser.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-14. + +Revision History: + +--*/ +#include"cost_parser.h" + +cost_parser::cost_parser(ast_manager & m): + simple_parser(m), + m_util(m), + m_vars(m) { + family_id fid; + fid = m.get_basic_family_id(); + add_builtin_op("true", fid, OP_TRUE); + add_builtin_op("false", fid, OP_FALSE); + add_builtin_op("not", fid, OP_NOT); + add_builtin_op("and", fid, OP_AND); + add_builtin_op("implies", fid, OP_IMPLIES); + add_builtin_op("or", fid, OP_OR); + add_builtin_op("ite", fid, OP_ITE); + add_builtin_op("=", fid, OP_EQ); + add_builtin_op("iff", fid, OP_IFF); + add_builtin_op("xor", fid, OP_XOR); + + fid = m_util.get_family_id(); + add_builtin_op("+", fid, OP_ADD); + add_builtin_op("*", fid, OP_MUL); + add_builtin_op("-", fid, OP_SUB); + add_builtin_op("/", fid, OP_DIV); + add_builtin_op("<=", fid, OP_LE); + add_builtin_op(">=", fid, OP_GE); + add_builtin_op("<", fid, OP_LT); + add_builtin_op(">", fid, OP_GT); +} + +expr * cost_parser::parse_int(rational const & r) { + return m_util.mk_numeral(r, false); +} + +expr * cost_parser::parse_float(rational const & r) { + return m_util.mk_numeral(r, false); +} + +unsigned cost_parser::add_var(symbol name) { + sort * real = m_util.mk_real(); + unsigned r = m_vars.size(); + var * v = m_manager.mk_var(r, real); + simple_parser::add_var(name, v); + m_vars.push_back(v); + return r; +} + +void cost_parser::reset_vars() { + simple_parser::reset_vars(); + m_vars.reset(); +} + diff --git a/lib/cost_parser.h b/lib/cost_parser.h new file mode 100644 index 000000000..bd0b0b86a --- /dev/null +++ b/lib/cost_parser.h @@ -0,0 +1,39 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + cost_parser.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-14. + +Revision History: + +--*/ +#ifndef _COST_PARSER_H_ +#define _COST_PARSER_H_ + +#include"simple_parser.h" +#include"arith_decl_plugin.h" + +class cost_parser : public simple_parser { + arith_util m_util; + var_ref_vector m_vars; +public: + cost_parser(ast_manager & m); + virtual ~cost_parser() {} + virtual expr * parse_int(rational const & r); + virtual expr * parse_float(rational const & r); + unsigned add_var(symbol name); + unsigned add_var(char const * name) { return add_var(symbol(name)); } + void reset_vars(); +}; + +#endif /* _COST_PARSER_H_ */ + diff --git a/lib/critical_flet.h b/lib/critical_flet.h new file mode 100644 index 000000000..347adf47b --- /dev/null +++ b/lib/critical_flet.h @@ -0,0 +1,45 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + critical flet.cpp + +Abstract: + + Version of flet using "omp critical" directive. + + Warning: it uses omp critical section "critical_flet" + +Author: + + Leonardo de Moura (leonardo) 2011-05-12 + +Revision History: + +--*/ +#ifndef _CRITICAL_FLET_H_ +#define _CRITICAL_FLET_H_ + +template +class critical_flet { + T & m_ref; + T m_old_value; +public: + critical_flet(T & ref, const T & new_value): + m_ref(ref), + m_old_value(ref) { + #pragma omp critical (critical_flet) + { + m_ref = new_value; + } + } + ~critical_flet() { + #pragma omp critical (critical_flet) + { + m_ref = m_old_value; + } + } +}; + +#endif diff --git a/lib/ctx_simplify_tactic.cpp b/lib/ctx_simplify_tactic.cpp new file mode 100644 index 000000000..9b4ca6c54 --- /dev/null +++ b/lib/ctx_simplify_tactic.cpp @@ -0,0 +1,555 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + ctx_simplify_tactic.cpp + +Abstract: + + Simple context simplifier for propagating constants. + +Author: + + Leonardo (leonardo) 2011-10-26 + +Notes: + +--*/ +#include"ctx_simplify_tactic.h" +#include"mk_simplified_app.h" +#include"num_occurs.h" +#include"cooperate.h" +#include"ast_ll_pp.h" +#include"ast_smt2_pp.h" + +struct ctx_simplify_tactic::imp { + struct cached_result { + expr * m_to; + unsigned m_lvl; + cached_result * m_next; + cached_result(expr * t, unsigned lvl, cached_result * next): + m_to(t), + m_lvl(lvl), + m_next(next) { + } + }; + + struct cache_cell { + expr * m_from; + cached_result * m_result; + cache_cell():m_from(0), m_result(0) {} + }; + + ast_manager & m; + small_object_allocator m_allocator; + obj_map m_assertions; + ptr_vector m_trail; + svector m_scopes; + svector m_cache; + vector > m_cache_undo; + unsigned m_scope_lvl; + unsigned m_depth; + unsigned m_num_steps; + num_occurs m_occs; + mk_simplified_app m_mk_app; + unsigned long long m_max_memory; + unsigned m_max_depth; + unsigned m_max_steps; + bool m_bail_on_blowup; + volatile bool m_cancel; + + imp(ast_manager & _m, params_ref const & p): + m(_m), + m_allocator("context-simplifier"), + m_occs(true, true), + m_mk_app(m, p) { + m_cancel = false; + m_scope_lvl = 0; + updt_params(p); + } + + void set_cancel(bool f) { + m_cancel = f; + } + + ~imp() { + pop(m_scope_lvl); + SASSERT(m_scope_lvl == 0); + restore_cache(0); + DEBUG_CODE({ + for (unsigned i = 0; i < m_cache.size(); i++) { + CTRACE("ctx_simplify_tactic_bug", m_cache[i].m_from, + tout << "i: " << i << "\n" << mk_ismt2_pp(m_cache[i].m_from, m) << "\n"; + tout << "m_result: " << m_cache[i].m_result << "\n"; + if (m_cache[i].m_result) tout << "lvl: " << m_cache[i].m_result->m_lvl << "\n";); + SASSERT(m_cache[i].m_from == 0); + SASSERT(m_cache[i].m_result == 0); + } + }); + } + + void updt_params(params_ref const & p) { + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + m_max_steps = p.get_uint(":max-steps", UINT_MAX); + m_max_depth = p.get_uint(":max-depth", 1024); + m_bail_on_blowup = p.get_bool(":bail-on-blowup", false); + } + + void checkpoint() { + cooperate("ctx_simplify_tactic"); + if (memory::get_allocation_size() > m_max_memory) + throw tactic_exception(TACTIC_MAX_MEMORY_MSG); + if (m_cancel) + throw tactic_exception(TACTIC_CANCELED_MSG); + } + + bool shared(expr * t) const { + return t->get_ref_count() > 1 && m_occs.get_num_occs(t) > 1; + } + + bool check_cache() { + for (unsigned i = 0; i < m_cache.size(); i++) { + cache_cell & cell = m_cache[i]; + if (cell.m_from != 0) { + SASSERT(cell.m_result != 0); + cached_result * curr = cell.m_result; + while (curr) { + SASSERT(curr->m_lvl <= scope_level()); + curr = curr->m_next; + } + } + } + return true; + } + + void cache_core(expr * from, expr * to) { + TRACE("ctx_simplify_tactic_cache", tout << "caching\n" << mk_ismt2_pp(from, m) << "\n--->\n" << mk_ismt2_pp(to, m) << "\n";); + unsigned id = from->get_id(); + m_cache.reserve(id+1); + cache_cell & cell = m_cache[id]; + void * mem = m_allocator.allocate(sizeof(cached_result)); + if (cell.m_from == 0) { + // new_entry + cell.m_from = from; + cell.m_result = new (mem) cached_result(to, m_scope_lvl, 0); + m.inc_ref(from); + m.inc_ref(to); + } + else { + // update + cell.m_result = new (mem) cached_result(to, m_scope_lvl, cell.m_result); + m.inc_ref(to); + } + m_cache_undo.reserve(m_scope_lvl+1); + m_cache_undo[m_scope_lvl].push_back(from); + } + + void cache(expr * from, expr * to) { + if (shared(from)) + cache_core(from, to); + } + + unsigned scope_level() const { + return m_scope_lvl; + } + + void push() { + m_scope_lvl++; + m_scopes.push_back(m_trail.size()); + } + + void restore_cache(unsigned lvl) { + if (lvl >= m_cache_undo.size()) + return; + ptr_vector & keys = m_cache_undo[lvl]; + ptr_vector::iterator it = keys.end(); + ptr_vector::iterator begin = keys.begin(); + while (it != begin) { + --it; + expr * key = *it; + unsigned key_id = key->get_id(); + cache_cell & cell = m_cache[key_id]; + SASSERT(cell.m_from == key); + SASSERT(cell.m_result != 0); + m.dec_ref(cell.m_result->m_to); + cached_result * to_delete = cell.m_result; + SASSERT(to_delete->m_lvl == lvl); + TRACE("ctx_simplify_tactic_cache", tout << "uncaching: " << to_delete->m_lvl << "\n" << + mk_ismt2_pp(key, m) << "\n--->\n" << mk_ismt2_pp(to_delete->m_to, m) << "\nrestoring:\n"; + if (to_delete->m_next) tout << mk_ismt2_pp(to_delete->m_next->m_to, m); else tout << ""; + tout << "\n";); + cell.m_result = to_delete->m_next; + if (cell.m_result == 0) { + m.dec_ref(cell.m_from); + cell.m_from = 0; + } + m_allocator.deallocate(sizeof(cached_result), to_delete); + } + keys.reset(); + } + + void pop(unsigned num_scopes) { + if (num_scopes == 0) + return; + SASSERT(num_scopes <= m_scope_lvl); + SASSERT(m_scope_lvl == m_scopes.size()); + + // undo assertions + unsigned old_trail_size = m_scopes[m_scope_lvl - num_scopes]; + unsigned i = m_trail.size(); + while (i > old_trail_size) { + --i; + expr * key = m_trail.back(); + m_assertions.erase(key); + m_trail.pop_back(); + } + SASSERT(m_trail.size() == old_trail_size); + m_scopes.shrink(m_scope_lvl - num_scopes); + + // restore cache + for (unsigned i = 0; i < num_scopes; i++) { + restore_cache(m_scope_lvl); + m_scope_lvl--; + } + CASSERT("ctx_simplify_tactic", check_cache()); + } + + void assert_eq_core(expr * t, app * val) { + if (m_assertions.contains(t)) { + // This branch can only happen when m_max_depth was reached. + // It can happen when m_assertions contains an entry t->val', + // but (= t val) was not simplified to (= val' val) + // because the simplifier stopped at depth m_max_depth + return; + } + + CTRACE("assert_eq_bug", m_assertions.contains(t), tout << "m_depth: " << m_depth << " m_max_depth: " << m_max_depth << "\n" + << "t:\n" << mk_ismt2_pp(t, m) << "\nval:\n" << mk_ismt2_pp(val, m) << "\n"; + expr * old_val = 0; + m_assertions.find(t, old_val); + tout << "old_val:\n" << mk_ismt2_pp(old_val, m) << "\n";); + m_assertions.insert(t, val); + m_trail.push_back(t); + } + + void assert_eq_val(expr * t, app * val, bool mk_scope) { + if (shared(t)) { + if (mk_scope) + push(); + assert_eq_core(t, val); + } + } + + void assert_expr(expr * t, bool sign) { + if (m.is_not(t)) { + t = to_app(t)->get_arg(0); + sign = !sign; + } + bool mk_scope = true; + if (shared(t)) { + push(); + mk_scope = false; + assert_eq_core(t, sign ? m.mk_false() : m.mk_true()); + } + expr * lhs, * rhs; + if (!sign && m.is_eq(t, lhs, rhs)) { + if (m.is_value(rhs)) + assert_eq_val(lhs, to_app(rhs), mk_scope); + else if (m.is_value(lhs)) + assert_eq_val(rhs, to_app(lhs), mk_scope); + } + } + + bool is_cached(expr * t, expr_ref & r) { + unsigned id = t->get_id(); + if (id >= m_cache.size()) + return false; + cache_cell & cell = m_cache[id]; + SASSERT(cell.m_result == 0 || cell.m_result->m_lvl <= scope_level()); + if (cell.m_result != 0 && cell.m_result->m_lvl == scope_level()) { + SASSERT(cell.m_from == t); + SASSERT(cell.m_result->m_to != 0); + r = cell.m_result->m_to; + return true; + } + return false; + } + + void simplify(expr * t, expr_ref & r) { + r = 0; + if (m_depth >= m_max_depth || m_num_steps >= m_max_steps || !is_app(t)) { + r = t; + return; + } + checkpoint(); + TRACE("ctx_simplify_tactic_detail", tout << "processing: " << mk_bounded_pp(t, m) << "\n";); + expr * _r; + if (m_assertions.find(t, _r)) { + r = _r; + SASSERT(r.get() != 0); + return; + } + if (is_cached(t, r)) { + SASSERT(r.get() != 0); + return; + } + m_num_steps++; + m_depth++; + if (m.is_or(t)) + simplify_or_and(to_app(t), r); + else if (m.is_and(t)) + simplify_or_and(to_app(t), r); + else if (m.is_ite(t)) + simplify_ite(to_app(t), r); + else + simplify_app(to_app(t), r); + m_depth--; + SASSERT(r.get() != 0); + TRACE("ctx_simplify_tactic_detail", tout << "result:\n" << mk_bounded_pp(t, m) << "\n---->\n" << mk_bounded_pp(r, m) << "\n";); + } + + template + void simplify_or_and(app * t, expr_ref & r) { + // go forwards + expr_ref_buffer new_args(m); + unsigned old_lvl = scope_level(); + bool modified = false; + unsigned num_args = t->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = t->get_arg(i); + expr_ref new_arg(m); + simplify(arg, new_arg); + if (new_arg != arg) + modified = true; + if ((OR && m.is_false(new_arg)) || + (!OR && m.is_true(new_arg))) { + modified = true; + continue; + } + if ((OR && m.is_true(new_arg)) || + (!OR && m.is_false(new_arg))) { + r = new_arg; + pop(scope_level() - old_lvl); + cache(t, r); + return; + } + new_args.push_back(new_arg); + if (i < num_args - 1) + assert_expr(new_arg, OR); + } + pop(scope_level() - old_lvl); + + // go backwards + expr_ref_buffer new_new_args(m); + unsigned i = new_args.size(); + while (i > 0) { + --i; + expr * arg = new_args[i]; + expr_ref new_arg(m); + simplify(arg, new_arg); + if (new_arg != arg) + modified = true; + if ((OR && m.is_false(new_arg)) || + (!OR && m.is_true(new_arg))) { + modified = true; + continue; + } + if ((OR && m.is_true(new_arg)) || + (!OR && m.is_false(new_arg))) { + r = new_arg; + pop(scope_level() - old_lvl); + cache(t, r); + return; + } + new_new_args.push_back(new_arg); + if (i > 0) + assert_expr(new_arg, OR); + } + pop(scope_level() - old_lvl); + + if (!modified) { + r = t; + } + else { + std::reverse(new_new_args.c_ptr(), new_new_args.c_ptr() + new_new_args.size()); + m_mk_app(t->get_decl(), new_new_args.size(), new_new_args.c_ptr(), r); + } + cache(t, r); + } + + void simplify_ite(app * ite, expr_ref & r) { + expr * c = ite->get_arg(0); + expr * t = ite->get_arg(1); + expr * e = ite->get_arg(2); + expr_ref new_c(m); + unsigned old_lvl = scope_level(); + simplify(c, new_c); + if (m.is_true(new_c)) { + simplify(t, r); + } + else if (m.is_false(new_c)) { + simplify(e, r); + } + else { + expr_ref new_t(m); + expr_ref new_e(m); + assert_expr(new_c, false); + simplify(t, new_t); + pop(scope_level() - old_lvl); + assert_expr(new_c, true); + simplify(e, new_e); + pop(scope_level() - old_lvl); + if (c == new_c && t == new_t && e == new_e) { + r = ite; + } + else { + expr * args[3] = { new_c.get(), new_t.get(), new_e.get() }; + TRACE("ctx_simplify_tactic_ite_bug", + tout << "mk_ite\n" << mk_ismt2_pp(new_c.get(), m) << "\n" << mk_ismt2_pp(new_t.get(), m) + << "\n" << mk_ismt2_pp(new_e.get(), m) << "\n";); + m_mk_app(ite->get_decl(), 3, args, r); + } + } + cache(ite, r); + } + + void simplify_app(app * t, expr_ref & r) { + if (t->get_num_args() == 0) { + r = t; + return; + } + expr_ref_buffer new_args(m); + bool modified = false; + unsigned num_args = t->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = t->get_arg(i); + expr_ref new_arg(m); + simplify(arg, new_arg); + CTRACE("ctx_simplify_tactic_bug", new_arg.get() == 0, tout << mk_ismt2_pp(arg, m) << "\n";); + SASSERT(new_arg); + if (new_arg != arg) + modified = true; + new_args.push_back(new_arg); + } + if (!modified) { + r = t; + } + else { + m_mk_app(t->get_decl(), new_args.size(), new_args.c_ptr(), r); + } + } + + unsigned expr_size(expr* s) { + ast_mark visit; + unsigned sz = 0; + ptr_vector todo; + todo.push_back(s); + while (!todo.empty()) { + s = todo.back(); + todo.pop_back(); + if (visit.is_marked(s)) { + continue; + } + visit.mark(s, true); + ++sz; + for (unsigned i = 0; is_app(s) && i < to_app(s)->get_num_args(); ++i) { + todo.push_back(to_app(s)->get_arg(i)); + } + } + return sz; + } + + void process(expr * s, expr_ref & r) { + TRACE("ctx_simplify_tactic", tout << "simplifying:\n" << mk_ismt2_pp(s, m) << "\n";); + SASSERT(m_scope_lvl == 0); + m_depth = 0; + simplify(s, r); + SASSERT(m_scope_lvl == 0); + SASSERT(m_depth == 0); + SASSERT(r.get() != 0); + TRACE("ctx_simplify_tactic", tout << "result\n" << mk_ismt2_pp(r, m) << " :num-steps " << m_num_steps << "\n"; + tout << "old size: " << expr_size(s) << " new size: " << expr_size(r) << "\n";); + if (m_bail_on_blowup && expr_size(s) < expr_size(r)) { + r = s; + } + } + + void operator()(goal & g) { + SASSERT(g.is_well_sorted()); + bool proofs_enabled = g.proofs_enabled(); + m_occs.reset(); + m_occs(g); + m_num_steps = 0; + expr_ref r(m); + proof * new_pr = 0; + tactic_report report("ctx-simplify", g); + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + if (g.inconsistent()) + return; + expr * t = g.form(i); + process(t, r); + if (proofs_enabled) { + proof * pr = g.pr(i); + new_pr = m.mk_modus_ponens(pr, m.mk_rewrite_star(t, r, 0, 0)); // TODO :-) + } + g.update(i, r, new_pr, g.dep(i)); + } + IF_VERBOSE(TACTIC_VERBOSITY_LVL, verbose_stream() << "(ctx-simplify :num-steps " << m_num_steps << ")\n";); + SASSERT(g.is_well_sorted()); + } + +}; + +ctx_simplify_tactic::ctx_simplify_tactic(ast_manager & m, params_ref const & p): + m_imp(alloc(imp, m, p)), + m_params(p) { +} + +ctx_simplify_tactic::~ctx_simplify_tactic() { + dealloc(m_imp); +} + +void ctx_simplify_tactic::updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); +} + +void ctx_simplify_tactic::get_param_descrs(param_descrs & r) { + insert_max_memory(r); + insert_max_steps(r); + r.insert(":max-depth", CPK_UINT, "(default: 1024) maximum term depth."); +} + +void ctx_simplify_tactic::operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + mc = 0; pc = 0; core = 0; + (*m_imp)(*(in.get())); + in->inc_depth(); + result.push_back(in.get()); +} + +void ctx_simplify_tactic::set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); +} + +void ctx_simplify_tactic::cleanup() { + ast_manager & m = m_imp->m; + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } +} + diff --git a/lib/ctx_simplify_tactic.h b/lib/ctx_simplify_tactic.h new file mode 100644 index 000000000..5d33d1043 --- /dev/null +++ b/lib/ctx_simplify_tactic.h @@ -0,0 +1,56 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + ctx_simplify_tactic.h + +Abstract: + + Simple context simplifier for propagating constants. + +Author: + + Leonardo (leonardo) 2011-10-26 + +Notes: + +--*/ +#ifndef _CTX_SIMPLIFY_TACTIC_H_ +#define _CTX_SIMPLIFY_TACTIC_H_ + +#include"tactical.h" + +class ctx_simplify_tactic : public tactic { + struct imp; + imp * m_imp; + params_ref m_params; +public: + ctx_simplify_tactic(ast_manager & m, params_ref const & p = params_ref()); + + virtual tactic * translate(ast_manager & m) { + return alloc(ctx_simplify_tactic, m, m_params); + } + + virtual ~ctx_simplify_tactic(); + + virtual void updt_params(params_ref const & p); + static void get_param_descrs(param_descrs & r); + virtual void collect_param_descrs(param_descrs & r) { get_param_descrs(r); } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core); + + virtual void cleanup(); +protected: + virtual void set_cancel(bool f); +}; + +inline tactic * mk_ctx_simplify_tactic(ast_manager & m, params_ref const & p = params_ref()) { + return clean(alloc(ctx_simplify_tactic, m, p)); +} + +#endif diff --git a/lib/ctx_solver_simplify_tactic.cpp b/lib/ctx_solver_simplify_tactic.cpp new file mode 100644 index 000000000..1faf73f19 --- /dev/null +++ b/lib/ctx_solver_simplify_tactic.cpp @@ -0,0 +1,284 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + ctx_solver_simplify_tactic.cpp + +Abstract: + + Context simplifier for propagating solver assignments. + +Author: + + Nikolaj (nbjorner) 2012-3-6 + +Notes: + +--*/ + +#include"ctx_solver_simplify_tactic.h" +#include"arith_decl_plugin.h" +#include"front_end_params.h" +#include"smt_solver.h" +#include"ast_pp.h" +#include"mk_simplified_app.h" + + +class ctx_solver_simplify_tactic : public tactic { + ast_manager& m; + params_ref m_params; + front_end_params m_front_p; + smt::solver m_solver; + arith_util m_arith; + mk_simplified_app m_mk_app; + func_decl_ref m_fn; + unsigned m_num_steps; +public: + ctx_solver_simplify_tactic(ast_manager & m, params_ref const & p = params_ref()): + m(m), m_params(p), m_solver(m, m_front_p), m_arith(m), m_mk_app(m), m_fn(m), m_num_steps(0) { + sort* i_sort = m_arith.mk_int(); + m_fn = m.mk_func_decl(symbol(0xbeef101), i_sort, m.mk_bool_sort()); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(ctx_solver_simplify_tactic, m, m_params); + } + + virtual ~ctx_solver_simplify_tactic() {} + + virtual void updt_params(params_ref const & p) { + m_solver.updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + m_solver.collect_param_descrs(r); + } + + virtual void collect_statistics(statistics & st) const { + st.update("solver-simplify-steps", m_num_steps); + } + + virtual void reset_statistics() { m_num_steps = 0; } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + + mc = 0; pc = 0; core = 0; + reduce(*(in.get())); + in->inc_depth(); + result.push_back(in.get()); + } + + virtual void cleanup() { + reset_statistics(); + m_solver.reset(); + } +protected: + virtual void set_cancel(bool f) { + m_solver.set_cancel(f); + } + + void reduce(goal& g) { + SASSERT(g.is_well_sorted()); + bool proofs_enabled = g.proofs_enabled(); + m_num_steps = 0; + expr_ref fml(m); + tactic_report report("ctx-solver-simplify", g); + unsigned sz = g.size(); + if (g.inconsistent()) + return; + ptr_vector fmls; + g.get_formulas(fmls); + fml = m.mk_and(fmls.size(), fmls.c_ptr()); + reduce(fml); + g.reset(); + g.assert_expr(fml, 0, 0); + IF_VERBOSE(TACTIC_VERBOSITY_LVL, verbose_stream() << "(ctx-solver-simplify :num-steps " << m_num_steps << ")\n";); + SASSERT(g.is_well_sorted()); + } + + void reduce(expr_ref& result){ + SASSERT(m.is_bool(result)); + ptr_vector todo; + ptr_vector names; + svector is_checked; + svector parent_ids, self_ids; + expr_ref_vector fresh_vars(m), trail(m); + expr_ref res(m); + obj_map > cache; + unsigned id = 1; + expr* n = m.mk_app(m_fn, m_arith.mk_numeral(rational(id++), true)); + expr* n2, *fml; + unsigned path_id = 0, self_pos = 0; + app * a; + unsigned sz; + std::pair path_r; + ptr_vector found; + + fml = result.get(); + m_solver.assert_expr(m.mk_not(m.mk_iff(fml, n))); + + trail.push_back(n); + todo.push_back(fml); + names.push_back(n); + is_checked.push_back(false); + parent_ids.push_back(0); + self_ids.push_back(0); + m_solver.push(); + + while (!todo.empty()) { + expr_ref res(m); + ptr_buffer args; + expr* e = todo.back(); + unsigned pos = parent_ids.back(); + n = names.back(); + bool checked = is_checked.back(); + + if (cache.contains(e)) { + goto done; + } + if (!m.is_bool(e)) { + res = e; + goto done; + } + if (m.is_bool(e) && !checked && simplify_bool(n, res)) { + goto done; + } + if (!is_app(e)) { + res = e; + goto done; + } + + a = to_app(e); + if (!is_checked.back()) { + self_ids.back() = ++path_id; + is_checked.back() = true; + } + self_pos = self_ids.back(); + sz = a->get_num_args(); + + n2 = 0; + + found.reset(); // arguments already simplified. + for (unsigned i = 0; i < sz; ++i) { + expr* arg = a->get_arg(i); + if (!m.is_bool(arg)) { + args.push_back(arg); + } + else if (cache.find(arg, path_r) && !found.contains(arg)) { + // + // This is a single traversal version of the context + // simplifier. It simplifies only the first occurrence of + // a formula with respect to the context. + // + + found.push_back(arg); + if (path_r.first == self_pos) { + TRACE("ctx_solver_simplify_tactic", tout << "cached " << mk_pp(arg, m) << "\n";); + args.push_back(path_r.second); + } + else { + res = local_simplify(a, n, id, i); + TRACE("ctx_solver_simplify_tactic", + tout << "Already cached: " << path_r.first << " " << mk_pp(res, m) << "\n";); + args.push_back(arg); + } + } + else if (!n2 && !found.contains(arg)) { + n2 = m.mk_app(m_fn, m_arith.mk_numeral(rational(id++), true)); + todo.push_back(arg); + parent_ids.push_back(self_pos); + self_ids.push_back(0); + names.push_back(n2); + trail.push_back(n2); + args.push_back(n2); + is_checked.push_back(false); + } + else { + args.push_back(arg); + } + } + m_mk_app(a->get_decl(), args.size(), args.c_ptr(), res); + trail.push_back(res); + // child needs to be visited. + if (n2) { + m_solver.push(); + m_solver.assert_expr(m.mk_eq(res, n)); + continue; + } + + done: + if (res) { + cache.insert(e, std::make_pair(pos, res)); + } + + TRACE("ctx_solver_simplify_tactic", + tout << mk_pp(e, m) << " checked: " << checked << " cached: " << mk_pp(res?res.get():e, m) << "\n";); + + todo.pop_back(); + parent_ids.pop_back(); + self_ids.pop_back(); + names.pop_back(); + is_checked.pop_back(); + m_solver.pop(1); + } + VERIFY(cache.find(fml, path_r)); + result = path_r.second; + } + + bool simplify_bool(expr* n, expr_ref& res) { + + m_solver.push(); + m_solver.assert_expr(n); + lbool is_sat = m_solver.check(); + m_solver.pop(1); + if (is_sat == l_false) { + res = m.mk_true(); + return true; + } + + m_solver.push(); + m_solver.assert_expr(m.mk_not(n)); + is_sat = m_solver.check(); + m_solver.pop(1); + if (is_sat == l_false) { + res = m.mk_false(); + return true; + } + + return false; + } + + expr_ref local_simplify(app* a, expr* n, unsigned& id, unsigned index) { + SASSERT(index < a->get_num_args()); + SASSERT(m.is_bool(a->get_arg(index))); + expr_ref n2(m), result(m); + n2 = m.mk_app(m_fn, m_arith.mk_numeral(rational(id++), true)); + ptr_buffer args; + for (unsigned i = 0; i < a->get_num_args(); ++i) { + if (i == index) { + args.push_back(n2); + } + else { + args.push_back(a->get_arg(i)); + } + } + m_mk_app(a->get_decl(), args.size(), args.c_ptr(), result); + m_solver.push(); + m_solver.assert_expr(m.mk_eq(result, n)); + if (!simplify_bool(n2, result)) { + result = a; + } + m_solver.pop(1); + return result; + } + +}; + +tactic * mk_ctx_solver_simplify_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(ctx_solver_simplify_tactic, m, p)); +} diff --git a/lib/ctx_solver_simplify_tactic.h b/lib/ctx_solver_simplify_tactic.h new file mode 100644 index 000000000..09467f311 --- /dev/null +++ b/lib/ctx_solver_simplify_tactic.h @@ -0,0 +1,26 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + ctx_solver_simplify_tactic.h + +Abstract: + + Context simplifier for propagating solver assignments. + +Author: + + Nikolaj (nbjorner) 2012-3-6 + +Notes: + +--*/ +#ifndef _CTX_SOLVER_SIMPLIFY_TACTIC_H_ +#define _CTX_SOLVER_SIMPLIFY_TACTIC_H_ + +#include"tactical.h" + +tactic * mk_ctx_solver_simplify_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/database.h b/lib/database.h new file mode 100644 index 000000000..2e71cbae4 --- /dev/null +++ b/lib/database.h @@ -0,0 +1,316 @@ +char const * g_pattern_database = +"(benchmark patterns \n" +" :status unknown \n" +" :logic ALL \n" +" :extrafuns ((?f1 Int Int Int Int) (?f2 Int Int Int) (?f3 Int Int Int) (?f4 Int Int Int)\n" +" (?f5 Int Int Int) (?f6 Int Int) (?f7 Int Int) (?f8 Int Int Int) (?f9 Int Int Int)\n" +" (?f10 Int) (?f11 Int) (?f12 Int Int) (?f13 Int Int) (?f14 Int Int Int) \n" +" (?f15 Int Int) (?f16 Int Int) (?f17 Int Int) (?f18 Int Int) (?f19 Int Int)\n" +" (?f20 Int Int) (?f21 Int) (?f22 Int) (?f23 Int) (?f24 Int Int) (?f25 Int Int)\n" +" )\n" +"\n" +" :formula (forall (a Int) (i Int) (e Int) \n" +" (= (?f2 (?f1 a i e) i) e)\n" +" :pats { (?f1 a i e) }\n" +" :weight { 0 })\n" +"\n" +" :formula (forall (a Int) (i Int) (j Int) (e Int) \n" +" (or (= i j) (= (?f2 (?f1 a i e) j) (?f2 a j)))\n" +" :pats { (?f2 (?f1 a i e) j) }\n" +" :weight { 0 })\n" +"\n" +" :formula (forall (t0 Int) (t1 Int) (t2 Int) \n" +" (or (not (= (?f3 t0 t1) 1))\n" +" (not (= (?f3 t1 t2) 1))\n" +" (= (?f3 t0 t2) 1))\n" +" :pats { (?f3 t0 t1) (?f3 t1 t2) })\n" +"\n" +" :formula (forall (t0 Int) (t1 Int) \n" +" (or (not (= (?f3 t0 t1) 1))\n" +" (not (= (?f3 t1 t0) 1))\n" +" (= t0 t1))\n" +" :pats { (?f3 t0 t1) (?f3 t1 t0) })\n" +"\n" +" :formula (forall (t0 Int) (t1 Int) (t2 Int) \n" +" (or (not (= (?f3 t0 (?f4 t1 t2)) 1))\n" +" (= (?f5 t2 t0) (?f4 t1 t2)))\n" +" :pats { (?f3 t0 (?f4 t1 t2)) })\n" +"\n" +" :formula (forall (t Int) \n" +" (= (?f25 (?f24 t)) t)\n" +" :pats { (?f24 t) })\n" +"\n" +" :formula (forall (t0 Int) (t1 Int) \n" +" (iff (= (?f3 t0 (?f6 t1)) 1)\n" +" (not (or (not (= t0 (?f6 (?f7 t0))))\n" +" (not (= (?f3 (?f7 t0) t1) 1)))))\n" +" :pats { (?f3 t0 (?f6 t1)) })\n" +"\n" +" :formula (forall (x Int) (t Int) \n" +" (or (not (= (?f8 x t) 1))\n" +" (= (?f9 x t) x))\n" +" :pats { (?f9 x t) })\n" +"\n" +" :formula (forall (x Int) (t Int) \n" +" (or (not (= (?f3 t ?f10) 1))\n" +" (iff (= (?f8 x t) 1)\n" +" (or (= x ?f11)\n" +" (= (?f3 (?f12 x) t) 1))))\n" +" :pats { (?f3 t ?f10) (?f8 x t) })\n" +"\n" +" :formula (forall (e Int) (a Int) (i Int) \n" +" (= (?f8 (?f2 (?f2 (?f13 e) a) i)\n" +" (?f7 (?f12 a))) 1)\n" +" :pats { (?f2 (?f2 (?f13 e) a) i) })\n" +"\n" +" :formula (forall (x Int) (f Int) (a0 Int) \n" +" (or (<= (+ a0 (* -1 (?f15 f))) 0)\n" +" (not (= (?f14 x a0) 1))\n" +" (= (?f14 (?f2 f x) a0) 1))\n" +" :pats { (?f14 (?f2 f x) a0) })\n" +"\n" +" :formula (forall (a Int) (e Int) (i Int) (a0 Int) \n" +" (or (<= (+ a0 (* -1 (?f16 e))) 0)\n" +" (not (= (?f14 a a0) 1))\n" +" (= (?f14 (?f2 (?f2 e a) i) a0) 1))\n" +" :pats { (?f14 (?f2 (?f2 e a) i) a0) })\n" +"\n" +" :formula (forall (S Int) \n" +" (= (?f2 (?f18 S) (?f17 (?f18 S))) 1)\n" +" :pats { (?f2 (?f18 S) (?f17 (?f18 S))) })\n" +"\n" +" :formula (forall (s Int) \n" +" (or (not (= 1 (?f19 s)))\n" +" (= (?f3 (?f12 s) ?f23) 1))\n" +" :pats { (?f19 s) })\n" +"\n" +" :formula (forall (t Int) \n" +" (not (or (= (?f20 t) ?f11)\n" +" (not (= (?f8 (?f20 t) ?f21) 1))\n" +" (not (= (?f14 (?f20 t) ?f22) 1))))\n" +" :pats { (?f20 t) })\n" +"\n" +" :extrafuns ((?f26 Int Int Int Int) \n" +" (?f27 Int Int Int Int Int))\n" +" \n" +" :formula (forall (A Int) (o Int) (f Int) (v Int)\n" +" (= (?f26 (?f27 A o f v) o f) v)\n" +" :pats { (?f27 A o f v) }\n" +" :weight { 0 }) \n" +"\n" +" :formula (forall (A Int) (o Int) (f Int) (p Int) (g Int) (v Int)\n" +" (or (= o p) (= (?f26 (?f27 A o f v) p g) (?f26 A p g)))\n" +" :pats { (?f26 (?f27 A o f v) p g) }\n" +" :weight { 0 })\n" +"\n" +" :formula (forall (A Int) (o Int) (f Int) (p Int) (g Int) (v Int)\n" +" (or (= f g) (= (?f26 (?f27 A o f v) p g) (?f26 A p g)))\n" +" :pats { (?f26 (?f27 A o f v) p g) }\n" +" :weight { 0 })\n" +"\n" +" :extrapreds ((?f28 Int Int))\n" +"\n" +" :formula (forall (t Int) (u Int) (v Int)\n" +" (or (not (?f28 t u))\n" +" (not (?f28 u v))\n" +" (?f28 t v))\n" +" :pat {(?f28 t u) (?f28 u v)})\n" +"\n" +" :formula (forall (t Int) (u Int)\n" +" (or (not (?f28 t u))\n" +" (not (?f28 u t))\n" +" (= t u))\n" +" :pat {(?f28 t u) (?f28 u t)})\n" +"\n" +" :extrafuns ((?f29 Int Int) (?f30 Int Int) (?f31 Int Int Int) (?f32 Int) (?f33 Int) (?f34 Int Int Int)\n" +" (?f35 Int Int) (?f36 Int) (?f37 Int) (?f38 Int) (?f39 Int Int) (?f40 Int)\n" +" (?f41 Int) (?f42 Int Int) (?f43 Int Int) (?f44 Int) (?f45 Int Int))\n" +"\n" +" :formula (forall (x Int) (p Int)\n" +" (or (not (?f28 (?f30 (?f31 x p)) ?f32))\n" +" (not (= (?f31 x p) p))\n" +" (= x p))\n" +" :pat { (?f28 (?f30 (?f31 x p)) ?f32)} )\n" +" \n" +" :formula (forall (h Int) (o Int) (f Int) (T Int)\n" +" (or \n" +" (not (= (?f39 h) ?f33))\n" +" (= (?f26 h o (?f34 f T)) ?f36)\n" +" (not (or (not (= (?f26 h (?f26 h o (?f34 f T)) ?f37) o))\n" +" (not (= (?f26 h (?f26 h o (?f34 f T)) ?f38) T)))))\n" +" :pat {(?f26 h o (?f34 f T))})\n" +"\n" +" :formula (forall (h Int) (o Int) (f Int)\n" +" (or\n" +" (not (= (?f39 h) ?f33))\n" +" (= (?f26 h o (?f35 f)) ?f36)\n" +" (not (or (not (= (?f26 h (?f26 h o (?f35 f)) ?f37) (?f26 h o ?f37)))\n" +" (not (= (?f26 h (?f26 h o (?f35 f)) ?f38) (?f26 h o ?f38))))))\n" +" :pat {(?f26 h o (?f35 f))})\n" +" \n" +" :formula (forall (h Int) (o Int)\n" +" (or \n" +" (not (= (?f39 h) ?f33))\n" +" (= (?f26 h o ?f38) ?f44)\n" +" (not (?f28 (?f26 h (?f26 h o ?f37) ?f41) (?f26 h o ?f38)))\n" +" (= (?f26 h (?f26 h o ?f37) ?f40) (?f42 (?f26 h o ?f38)))\n" +" (not (or (not (= (?f26 h o ?f41) (?f43 o)))\n" +" (not (= (?f26 h o ?f40) (?f43 o))))))\n" +" :pat {(?f28 (?f26 h (?f26 h o ?f37) ?f41) (?f26 h o ?f38))})\n" +"\n" +" :formula (forall (T Int) (h Int)\n" +" (or (not (= (?f39 h) ?f33))\n" +" (= (?f26 h (?f45 T) ?f38) ?f44))\n" +" :pat {(?f26 h (?f45 T) ?f38)})\n" +"\n" +" :extrafuns ((?f46 Int Int Int)\n" +" (?f47 Int Int Int)\n" +" (?f48 Int Int Int)\n" +" (?f49 Int)\n" +" (?f50 Int Int Int)\n" +" (?f51 Int Int Int)\n" +" (?f52 Int Int)\n" +" )\n" +"\n" +" :formula (forall (a Int) (T Int) (i Int) (r Int) (heap Int)\n" +" (or (not (= (?f39 heap) ?f33))\n" +" (not (?f28 (?f43 a) (?f46 T r)))\n" +" (= (?f47 (?f48 (?f26 heap a ?f49) i) T) ?f33))\n" +" :pat {(?f28 (?f43 a) (?f46 T r)) (?f48 (?f26 heap a ?f49) i)})\n" +"\n" +" :formula (forall (a Int) (T Int) (r Int)\n" +" (or (= a ?f36) \n" +" (not (?f28 (?f43 a) (?f46 T r)))\n" +" (= (?f52 a) r))\n" +" :pat {(?f28 (?f43 a) (?f46 T r))})\n" +"\n" +" :extrafuns ((?f53 Int Int Int)\n" +" (?f54 Int Int)\n" +" (?f55 Int)\n" +" (?f56 Int Int)\n" +" (?f57 Int)\n" +" (?f58 Int)\n" +" (?f59 Int Int Int)\n" +" (?f60 Int Int Int)\n" +" (?f61 Int Int Int)\n" +" )\n" +"\n" +" :extrapreds ((?f62 Int Int))\n" +" \n" +" :formula (forall (T Int) (ET Int) (r Int)\n" +" (or (not (?f28 T (?f53 ET r)))\n" +" (= (?f54 T) ?f55))\n" +" :pat {(?f28 T (?f53 ET r))})\n" +"\n" +" :formula (forall (A Int) (r Int) (T Int)\n" +" (or\n" +" (not (?f28 T (?f46 A r)))\n" +" (not (or (not (= T (?f46 (?f56 T) r)))\n" +" (not (?f28 (?f56 T) A)))))\n" +" :pat {(?f28 T (?f46 A r))})\n" +"\n" +" :formula (forall (A Int) (r Int) (T Int)\n" +" (or (not (?f28 T (?f53 A r)))\n" +" (= T (?f53 A r)))\n" +" :pat {(?f28 T (?f53 A r))})\n" +"\n" +" :extrafuns ((?f63 Int Int Int)\n" +" (?f64 Int Int Int)\n" +" )\n" +"\n" +" :formula (forall (A Int) (B Int) (C Int)\n" +" (or (not (?f28 C (?f63 B A)))\n" +" (= (?f64 C A) B))\n" +" :pat {(?f28 C (?f63 B A))})\n" +" \n" +" :formula (forall (o Int) (T Int)\n" +" (iff (= (?f47 o T) ?f33)\n" +" (or (= o ?f36)\n" +" (?f28 (?f43 o) T)))\n" +" :pat {(?f47 o T)})\n" +"\n" +" :formula (forall (o Int) (T Int)\n" +" (iff (= (?f51 o T) ?f33)\n" +" (or (= o ?f36)\n" +" (not (= (?f47 o T) ?f33))))\n" +" :pat {(?f51 o T)})\n" +"\n" +" :formula (forall (h Int) (o Int)\n" +" (or (not (= (?f39 h) ?f33))\n" +" (= o ?f36)\n" +" (not (?f28 (?f43 o) ?f57))\n" +" (not (or (not (= (?f26 h o ?f41) (?f43 o)))\n" +" (not (= (?f26 h o ?f40) (?f43 o))))))\n" +" :pat {(?f28 (?f43 o) ?f57) (?f26 h o ?f41)})\n" +"\n" +" :formula (forall (h Int) (o Int) (f Int) (T Int)\n" +" (or (not (= (?f39 h) ?f33))\n" +" (?f62 (?f26 h o (?f60 f T)) T))\n" +" :pat {(?f26 h o (?f60 f T))})\n" +"\n" +" :formula (forall (h Int) (o Int) (f Int)\n" +" (or\n" +" (not (= (?f39 h) ?f33))\n" +" (not (= (?f26 h o ?f58) ?f33))\n" +" (= (?f61 h (?f26 h o f)) ?f33))\n" +" :pat {(?f61 h (?f26 h o f))})\n" +"\n" +" :formula (forall (h Int) (s Int) (f Int)\n" +" (or (not (= (?f61 h s) ?f33))\n" +" (= (?f61 h (?f59 s f)) ?f33))\n" +" :pat {(?f61 h (?f59 s f))})\n" +"\n" +" :extrapreds ((?f65 Int Int))\n" +"\n" +" :formula (forall (x Int) (f Int) (a0 Int)\n" +" (or (<= (+ a0 (* -1 (?f15 f))) 0)\n" +" (not (?f65 x a0))\n" +" (?f65 (?f2 f x) a0))\n" +" :pat {(?f65 (?f2 f x) a0)})\n" +"\n" +" :formula (forall (a Int) (e Int) (i Int) (a0 Int) \n" +" (or (<= (+ a0 (* -1 (?f16 e))) 0)\n" +" (not (?f65 a a0))\n" +" (?f65 (?f2 (?f2 e a) i) a0))\n" +" :pats { (?f65 (?f2 (?f2 e a) i) a0) })\n" +"\n" +" :formula (forall (e Int) (a Int) (i Int) \n" +" (= (?f8 (?f2 (?f2 (?f13 e) a) i)\n" +" (?f7 (?f12 a))) ?f33)\n" +" :pats { (?f2 (?f2 (?f13 e) a) i) })\n" +"\n" +" :formula (forall (t0 Int) (t1 Int)\n" +" (iff (?f28 t0 (?f6 t1))\n" +" (not (or (not (= t0 (?f6 (?f7 t0))))\n" +" (not (?f28 (?f7 t0) t1)))))\n" +" :pat {(?f28 t0 (?f6 t1))})\n" +"\n" +" :formula (forall (t0 Int) (t1 Int) (t2 Int) \n" +" (or (not (?f28 t0 (?f4 t1 t2)))\n" +" (= (?f5 t2 t0) (?f4 t1 t2)))\n" +" :pats { (?f28 t0 (?f4 t1 t2)) })\n" +"\n" +" :formula (forall (t0 Int) (t1 Int) \n" +" (iff (?f28 t0 (?f6 t1))\n" +" (not (or (not (= t0 (?f6 (?f7 t0))))\n" +" (not (?f28 (?f7 t0) t1)))))\n" +" :pats { (?f28 t0 (?f6 t1)) })\n" +"\n" +" :formula (forall (x Int) (t Int) \n" +" (or (not (= (?f8 x t) ?f33))\n" +" (= (?f9 x t) x))\n" +" :pats { (?f9 x t) })\n" +"\n" +" :formula (forall (x Int) (t Int) \n" +" (or (not (?f28 t ?f10))\n" +" (iff (= (?f8 x t) ?f33)\n" +" (or (= x ?f11)\n" +" (?f28 (?f12 x) t))))\n" +" :pats { (?f28 t ?f10) (?f8 x t) })\n" +"\n" +" :formula (forall (e Int) (a Int) (i Int) \n" +" (= (?f8 (?f2 (?f2 (?f13 e) a) i)\n" +" (?f7 (?f12 a))) 1)\n" +" :pats { (?f2 (?f2 (?f13 e) a) i) })\n" +" )\n" +; diff --git a/lib/database.smt b/lib/database.smt new file mode 100644 index 000000000..186dd9b95 --- /dev/null +++ b/lib/database.smt @@ -0,0 +1,314 @@ +(benchmark patterns + :status unknown + :logic ALL + :extrafuns ((?store Int Int Int Int) (?select Int Int Int) (?PO Int Int Int) (?asChild Int Int Int) + (?classDown Int Int Int) (?array Int Int) (?elemtype Int Int) (?is Int Int Int) (?cast Int Int Int) + (?Object Int) (?null Int) (?typeof Int Int) (?asElems Int Int) (?isAllocated Int Int Int) + (?fClosedTime Int Int) (?eClosedTime Int Int) (?max Int Int) (?asLockSet Int Int) (?isNewArray Int Int) + (?classLiteral Int Int) (?Class Int) (?alloc Int) (?arrayType Int) (?f Int Int) (?finv Int Int) + ) + + :formula (forall (a Int) (i Int) (e Int) + (= (?select (?store a i e) i) e) + :pats { (?store a i e) } + :weight { 0 }) + + :formula (forall (a Int) (i Int) (j Int) (e Int) + (or (= i j) (= (?select (?store a i e) j) (?select a j))) + :pats { (?select (?store a i e) j) } + :weight { 0 }) + + :formula (forall (t0 Int) (t1 Int) (t2 Int) + (or (not (= (?PO t0 t1) 1)) + (not (= (?PO t1 t2) 1)) + (= (?PO t0 t2) 1)) + :pats { (?PO t0 t1) (?PO t1 t2) }) + + :formula (forall (t0 Int) (t1 Int) + (or (not (= (?PO t0 t1) 1)) + (not (= (?PO t1 t0) 1)) + (= t0 t1)) + :pats { (?PO t0 t1) (?PO t1 t0) }) + + :formula (forall (t0 Int) (t1 Int) (t2 Int) + (or (not (= (?PO t0 (?asChild t1 t2)) 1)) + (= (?classDown t2 t0) (?asChild t1 t2))) + :pats { (?PO t0 (?asChild t1 t2)) }) + + :formula (forall (t Int) + (= (?finv (?f t)) t) + :pats { (?f t) }) + + :formula (forall (t0 Int) (t1 Int) + (iff (= (?PO t0 (?array t1)) 1) + (not (or (not (= t0 (?array (?elemtype t0)))) + (not (= (?PO (?elemtype t0) t1) 1))))) + :pats { (?PO t0 (?array t1)) }) + + :formula (forall (x Int) (t Int) + (or (not (= (?is x t) 1)) + (= (?cast x t) x)) + :pats { (?cast x t) }) + + :formula (forall (x Int) (t Int) + (or (not (= (?PO t ?Object) 1)) + (iff (= (?is x t) 1) + (or (= x ?null) + (= (?PO (?typeof x) t) 1)))) + :pats { (?PO t ?Object) (?is x t) }) + + :formula (forall (e Int) (a Int) (i Int) + (= (?is (?select (?select (?asElems e) a) i) + (?elemtype (?typeof a))) 1) + :pats { (?select (?select (?asElems e) a) i) }) + + :formula (forall (x Int) (f Int) (a0 Int) + (or (<= (+ a0 (* -1 (?fClosedTime f))) 0) + (not (= (?isAllocated x a0) 1)) + (= (?isAllocated (?select f x) a0) 1)) + :pats { (?isAllocated (?select f x) a0) }) + + :formula (forall (a Int) (e Int) (i Int) (a0 Int) + (or (<= (+ a0 (* -1 (?eClosedTime e))) 0) + (not (= (?isAllocated a a0) 1)) + (= (?isAllocated (?select (?select e a) i) a0) 1)) + :pats { (?isAllocated (?select (?select e a) i) a0) }) + + :formula (forall (S Int) + (= (?select (?asLockSet S) (?max (?asLockSet S))) 1) + :pats { (?select (?asLockSet S) (?max (?asLockSet S))) }) + + :formula (forall (s Int) + (or (not (= 1 (?isNewArray s))) + (= (?PO (?typeof s) ?arrayType) 1)) + :pats { (?isNewArray s) }) + + :formula (forall (t Int) + (not (or (= (?classLiteral t) ?null) + (not (= (?is (?classLiteral t) ?Class) 1)) + (not (= (?isAllocated (?classLiteral t) ?alloc) 1)))) + :pats { (?classLiteral t) }) + + :extrafuns ((?select2 Int Int Int Int) + (?store2 Int Int Int Int Int)) + + :formula (forall (A Int) (o Int) (f Int) (v Int) + (= (?select2 (?store2 A o f v) o f) v) + :pats { (?store2 A o f v) } + :weight { 0 }) + + :formula (forall (A Int) (o Int) (f Int) (p Int) (g Int) (v Int) + (or (= o p) (= (?select2 (?store2 A o f v) p g) (?select2 A p g))) + :pats { (?select2 (?store2 A o f v) p g) } + :weight { 0 }) + + :formula (forall (A Int) (o Int) (f Int) (p Int) (g Int) (v Int) + (or (= f g) (= (?select2 (?store2 A o f v) p g) (?select2 A p g))) + :pats { (?select2 (?store2 A o f v) p g) } + :weight { 0 }) + + :extrapreds ((?subtypes Int Int)) + + :formula (forall (t Int) (u Int) (v Int) + (or (not (?subtypes t u)) + (not (?subtypes u v)) + (?subtypes t v)) + :pat {(?subtypes t u) (?subtypes u v)}) + + :formula (forall (t Int) (u Int) + (or (not (?subtypes t u)) + (not (?subtypes u t)) + (= t u)) + :pat {(?subtypes t u) (?subtypes u t)}) + + :extrafuns ((?Unbox Int Int) (?UnboxedType Int Int) (?Box Int Int Int) (?System.Object Int) (?Smt.true Int) (?AsRepField Int Int Int) + (?AsPeerField Int Int) (?nullObject Int) (?ownerRef_ Int) (?ownerFrame_ Int) (IntsHeap Int Int) (?localinv_ Int) + (?inv_ Int) (?BaseClass_ Int Int) (?typeof_ Int Int) (?PeerGroupPlaceholder_ Int) (?ClassRepr Int Int)) + + :formula (forall (x Int) (p Int) + (or (not (?subtypes (?UnboxedType (?Box x p)) ?System.Object)) + (not (= (?Box x p) p)) + (= x p)) + :pat { (?subtypes (?UnboxedType (?Box x p)) ?System.Object)} ) + + :formula (forall (h Int) (o Int) (f Int) (T Int) + (or + (not (= (IntsHeap h) ?Smt.true)) + (= (?select2 h o (?AsRepField f T)) ?nullObject) + (not (or (not (= (?select2 h (?select2 h o (?AsRepField f T)) ?ownerRef_) o)) + (not (= (?select2 h (?select2 h o (?AsRepField f T)) ?ownerFrame_) T))))) + :pat {(?select2 h o (?AsRepField f T))}) + + :formula (forall (h Int) (o Int) (f Int) + (or + (not (= (IntsHeap h) ?Smt.true)) + (= (?select2 h o (?AsPeerField f)) ?nullObject) + (not (or (not (= (?select2 h (?select2 h o (?AsPeerField f)) ?ownerRef_) (?select2 h o ?ownerRef_))) + (not (= (?select2 h (?select2 h o (?AsPeerField f)) ?ownerFrame_) (?select2 h o ?ownerFrame_)))))) + :pat {(?select2 h o (?AsPeerField f))}) + + :formula (forall (h Int) (o Int) + (or + (not (= (IntsHeap h) ?Smt.true)) + (= (?select2 h o ?ownerFrame_) ?PeerGroupPlaceholder_) + (not (?subtypes (?select2 h (?select2 h o ?ownerRef_) ?inv_) (?select2 h o ?ownerFrame_))) + (= (?select2 h (?select2 h o ?ownerRef_) ?localinv_) (?BaseClass_ (?select2 h o ?ownerFrame_))) + (not (or (not (= (?select2 h o ?inv_) (?typeof_ o))) + (not (= (?select2 h o ?localinv_) (?typeof_ o)))))) + :pat {(?subtypes (?select2 h (?select2 h o ?ownerRef_) ?inv_) (?select2 h o ?ownerFrame_))}) + + :formula (forall (T Int) (h Int) + (or (not (= (IntsHeap h) ?Smt.true)) + (= (?select2 h (?ClassRepr T) ?ownerFrame_) ?PeerGroupPlaceholder_)) + :pat {(?select2 h (?ClassRepr T) ?ownerFrame_)}) + + :extrafuns ((?RefArray Int Int Int) + (Ints_ Int Int Int) + (?RefArrayGet Int Int Int) + (?elements_ Int) + (?NonNullRefArray Int Int Int) + (IntsNotNull_ Int Int Int) + (?Rank_ Int Int) + ) + + :formula (forall (a Int) (T Int) (i Int) (r Int) (heap Int) + (or (not (= (IntsHeap heap) ?Smt.true)) + (not (?subtypes (?typeof_ a) (?RefArray T r))) + (= (Ints_ (?RefArrayGet (?select2 heap a ?elements_) i) T) ?Smt.true)) + :pat {(?subtypes (?typeof_ a) (?RefArray T r)) (?RefArrayGet (?select2 heap a ?elements_) i)}) + + :formula (forall (a Int) (T Int) (r Int) + (or (= a ?nullObject) + (not (?subtypes (?typeof_ a) (?RefArray T r))) + (= (?Rank_ a) r)) + :pat {(?subtypes (?typeof_ a) (?RefArray T r))}) + + :extrafuns ((?ValueArray Int Int Int) + (?ArrayCategory_ Int Int) + (?ArrayCategoryValue_ Int) + (?ElementType_ Int Int) + (?System.Array Int) + (?allocated_ Int) + (?StructGet_ Int Int Int) + (?AsRangeField Int Int Int) + (IntsAllocated Int Int Int) + ) + + :extrapreds ((IntnRange Int Int)) + + :formula (forall (T Int) (ET Int) (r Int) + (or (not (?subtypes T (?ValueArray ET r))) + (= (?ArrayCategory_ T) ?ArrayCategoryValue_)) + :pat {(?subtypes T (?ValueArray ET r))}) + + :formula (forall (A Int) (r Int) (T Int) + (or + (not (?subtypes T (?RefArray A r))) + (not (or (not (= T (?RefArray (?ElementType_ T) r))) + (not (?subtypes (?ElementType_ T) A))))) + :pat {(?subtypes T (?RefArray A r))}) + + :formula (forall (A Int) (r Int) (T Int) + (or (not (?subtypes T (?ValueArray A r))) + (= T (?ValueArray A r))) + :pat {(?subtypes T (?ValueArray A r))}) + + :extrafuns ((?AsDirectSubClass Int Int Int) + (?OneClassDown Int Int Int) + ) + + :formula (forall (A Int) (B Int) (C Int) + (or (not (?subtypes C (?AsDirectSubClass B A))) + (= (?OneClassDown C A) B)) + :pat {(?subtypes C (?AsDirectSubClass B A))}) + + :formula (forall (o Int) (T Int) + (iff (= (Ints_ o T) ?Smt.true) + (or (= o ?nullObject) + (?subtypes (?typeof_ o) T))) + :pat {(Ints_ o T)}) + + :formula (forall (o Int) (T Int) + (iff (= (IntsNotNull_ o T) ?Smt.true) + (or (= o ?nullObject) + (not (= (Ints_ o T) ?Smt.true)))) + :pat {(IntsNotNull_ o T)}) + + :formula (forall (h Int) (o Int) + (or (not (= (IntsHeap h) ?Smt.true)) + (= o ?nullObject) + (not (?subtypes (?typeof_ o) ?System.Array)) + (not (or (not (= (?select2 h o ?inv_) (?typeof_ o))) + (not (= (?select2 h o ?localinv_) (?typeof_ o)))))) + :pat {(?subtypes (?typeof_ o) ?System.Array) (?select2 h o ?inv_)}) + + :formula (forall (h Int) (o Int) (f Int) (T Int) + (or (not (= (IntsHeap h) ?Smt.true)) + (IntnRange (?select2 h o (?AsRangeField f T)) T)) + :pat {(?select2 h o (?AsRangeField f T))}) + + :formula (forall (h Int) (o Int) (f Int) + (or + (not (= (IntsHeap h) ?Smt.true)) + (not (= (?select2 h o ?allocated_) ?Smt.true)) + (= (IntsAllocated h (?select2 h o f)) ?Smt.true)) + :pat {(IntsAllocated h (?select2 h o f))}) + + :formula (forall (h Int) (s Int) (f Int) + (or (not (= (IntsAllocated h s) ?Smt.true)) + (= (IntsAllocated h (?StructGet_ s f)) ?Smt.true)) + :pat {(IntsAllocated h (?StructGet_ s f))}) + + :extrapreds ((?isAllocated_ Int Int)) + + :formula (forall (x Int) (f Int) (a0 Int) + (or (<= (+ a0 (* -1 (?fClosedTime f))) 0) + (not (?isAllocated_ x a0)) + (?isAllocated_ (?select f x) a0)) + :pat {(?isAllocated_ (?select f x) a0)}) + + :formula (forall (a Int) (e Int) (i Int) (a0 Int) + (or (<= (+ a0 (* -1 (?eClosedTime e))) 0) + (not (?isAllocated_ a a0)) + (?isAllocated_ (?select (?select e a) i) a0)) + :pats { (?isAllocated_ (?select (?select e a) i) a0) }) + + :formula (forall (e Int) (a Int) (i Int) + (= (?is (?select (?select (?asElems e) a) i) + (?elemtype (?typeof a))) ?Smt.true) + :pats { (?select (?select (?asElems e) a) i) }) + + :formula (forall (t0 Int) (t1 Int) + (iff (?subtypes t0 (?array t1)) + (not (or (not (= t0 (?array (?elemtype t0)))) + (not (?subtypes (?elemtype t0) t1))))) + :pat {(?subtypes t0 (?array t1))}) + + :formula (forall (t0 Int) (t1 Int) (t2 Int) + (or (not (?subtypes t0 (?asChild t1 t2))) + (= (?classDown t2 t0) (?asChild t1 t2))) + :pats { (?subtypes t0 (?asChild t1 t2)) }) + + :formula (forall (t0 Int) (t1 Int) + (iff (?subtypes t0 (?array t1)) + (not (or (not (= t0 (?array (?elemtype t0)))) + (not (?subtypes (?elemtype t0) t1))))) + :pats { (?subtypes t0 (?array t1)) }) + + :formula (forall (x Int) (t Int) + (or (not (= (?is x t) ?Smt.true)) + (= (?cast x t) x)) + :pats { (?cast x t) }) + + :formula (forall (x Int) (t Int) + (or (not (?subtypes t ?Object)) + (iff (= (?is x t) ?Smt.true) + (or (= x ?null) + (?subtypes (?typeof x) t)))) + :pats { (?subtypes t ?Object) (?is x t) }) + + :formula (forall (e Int) (a Int) (i Int) + (= (?is (?select (?select (?asElems e) a) i) + (?elemtype (?typeof a))) 1) + :pats { (?select (?select (?asElems e) a) i) }) + ) \ No newline at end of file diff --git a/lib/datalog_parser.cpp b/lib/datalog_parser.cpp new file mode 100644 index 000000000..160337a35 --- /dev/null +++ b/lib/datalog_parser.cpp @@ -0,0 +1,1509 @@ + +#include"datalog_parser.h" +#include"string_buffer.h" +#include"str_hashtable.h" +#include"ast_pp.h" +#include"arith_decl_plugin.h" +#include"region.h" +#include"warning.h" +#include +#include +#include + +using namespace datalog; + +enum dtoken { + TK_LP, + TK_RP, + TK_STRING, + TK_ID, + TK_NUM, + TK_PERIOD, + TK_INCLUDE, + TK_COMMA, + TK_COLON, + TK_WILD, + TK_LEFT_ARROW, + TK_EOS, + TK_NEWLINE, + TK_ERROR, + TK_NEQ, + TK_LT, + TK_GT, + TK_EQ, + TK_NEG +}; + +static char const* dtoken_strings[] = { "(", ")", "", "", "", ".", ".include", ",", ":", "_", ":-", "", "\\n", "", "!=", "<", ">", "=", "!" }; + +class line_reader { + + static const char s_delimiter = '\n'; + static const unsigned s_expansion_step = 1024; + +#if 0 + std::istream & m_stm; +#else + FILE * m_file; +#endif + svector m_data; + bool m_eof; + bool m_eof_behind_buffer; + unsigned m_next_index; + + //actually by one larger than the actual size of m_data, + //to fit in the terminating delimiter + unsigned m_data_size; + + void resize_data(unsigned sz) { + m_data_size = sz; + m_data.resize(m_data_size+1); + m_data[m_data_size] = s_delimiter; + } + +#if 0 + void refill_buffer(unsigned start) { + unsigned should_read = m_data_size-start; + m_stm.read(m_data.begin()+start, should_read); + unsigned actually_read = static_cast(m_stm.gcount()); + SASSERT(should_read==actually_read || m_stm.eof()); + if(m_stm.eof()) { + m_eof_behind_buffer = true; + resize_data(start+actually_read); + } + } +#else + void refill_buffer(unsigned start) { + unsigned should_read = m_data_size-start; + size_t actually_read = fread(m_data.begin()+start, 1, should_read, m_file); + if(actually_read==should_read) { + return; + } + SASSERT(actually_read < should_read); + SASSERT(feof(m_file)); + m_eof_behind_buffer = true; + resize_data(start+static_cast(actually_read)); + } +#endif + +public: + +#if 0 + line_reader(std::istream & stm) + : m_stm(stm), + m_eof(false), + m_eof_behind_buffer(false), + m_next_index(0), + m_data_size(0) { + m_data.resize(2*s_expansion_step); + resize_data(0); + } +#else + line_reader(const char * fname) + :m_eof(false), + m_eof_behind_buffer(false), + m_next_index(0), + m_data_size(0) { + m_data.resize(2*s_expansion_step); + resize_data(0); +#if _WINDOWS + errno_t err = fopen_s(&m_file, fname, "rb"); + SASSERT(err==0); +#else + m_file = fopen(fname, "rb"); +#endif + } + ~line_reader() { + fclose(m_file); + } +#endif + + /** + \brief Retrieve next line from the stream. + + This operation invalidates the line previously retrieved. + + This operatio can be called only if we are not at the end of file. + + User is free to modify the content of the returned array until the terminating NULL character. + */ + char * get_line() { + SASSERT(!m_eof); + unsigned start = m_next_index; + unsigned curr = start; + for(;;) { + SASSERT(curr<=m_data_size); + SASSERT(m_data[m_data_size]==s_delimiter); + { + const char * data_ptr = m_data.begin(); + const char * ptr = data_ptr+curr; + while(*ptr!=s_delimiter) { + ptr++; + } + curr = static_cast(ptr-data_ptr); + } + SASSERT(m_data[curr]==s_delimiter); + if(curr str2token; + str2token m_str2token; + +public: + reserved_symbols() { + m_str2token.insert(":-", TK_LEFT_ARROW); + m_str2token.insert("_", TK_WILD); + m_str2token.insert(".", TK_PERIOD); + m_str2token.insert("!=", TK_NEQ); + m_str2token.insert("=", TK_EQ); + m_str2token.insert("<", TK_LT); + m_str2token.insert(">", TK_GT); + m_str2token.insert(":", TK_COLON); + m_str2token.insert(".include", TK_INCLUDE); + m_str2token.insert("!", TK_NEG); + } + + dtoken string2dtoken(char const * str) { + str2token::entry * e = m_str2token.find_core(str); + if (e) + return e->get_data().m_value; + else + return TK_ID; + } +}; + + +class dlexer { + std::istream* m_input; + char m_prev_char; + char m_curr_char; + int m_line; + int m_pos; + int m_tok_pos; + string_buffer<> m_buffer; + reserved_symbols m_reserved_symbols; + +public: + //when parsing domains, we want '.' character to be allowed in IDs, but elsewhere + //we don't (because of the "y." in rules like "P(x,y):-x=y.") + bool m_parsing_domains; + + bool eos() const { + return m_curr_char == EOF; + } + + void next() { + m_prev_char = m_curr_char; + m_curr_char = m_input->get(); + m_pos++; + } + + void save_char(char c) { + m_buffer << c; + } + + void save_and_next() { + m_buffer << m_curr_char; + next(); + } + + dlexer(): + m_input(0), + m_prev_char(0), + m_curr_char(0), + m_line(1), + m_pos(0), + m_tok_pos(0), + m_parsing_domains(false) { + } + + void set_stream(std::istream& s) { + m_input = &s; + next(); + } + + + dtoken read_num() { + while(isdigit(m_curr_char)) { + save_and_next(); + } + return TK_NUM; + } + + dtoken read_id() { + while (!eos() && m_curr_char != '(' && m_curr_char != ')' && + m_curr_char != '#' && m_curr_char != ',' && (m_parsing_domains || m_curr_char != '.') && + m_curr_char != ':' && m_curr_char != '=' && !iswspace(m_curr_char) ) { + save_and_next(); + } + return m_reserved_symbols.string2dtoken(m_buffer.c_str()); + } + + // read an id of the form '|'.*'|' + dtoken read_bid() { + while (!eos() && m_curr_char != '|') { + save_and_next(); + } + if (m_curr_char == '|') { + next(); + } + return m_reserved_symbols.string2dtoken(m_buffer.c_str()); + } + + dtoken read_string() { + m_tok_pos = m_pos; + next(); + while (m_curr_char != '"') { + if (m_input->eof()) { + return TK_ERROR; + } + if (m_curr_char == '\n') { + return TK_ERROR; + } + save_and_next(); + } + next(); + return TK_STRING; + } + + void read_comment() { + bool line_comment = m_prev_char=='\n' || m_prev_char == 0; + while (m_curr_char != '\n' && !eos()) { + next(); + } + if (line_comment && m_curr_char == '\n') { + m_line++; + next(); + } + } + + bool lookahead_newline() { + while (m_curr_char == ' ') { + save_and_next(); + } + if (m_curr_char == '\n') { + next(); + m_line++; + m_buffer.reset(); + return true; + } + if (m_curr_char == '#') { + m_buffer.reset(); + m_prev_char = 0; + read_comment(); + return true; + } + return false; + } + + dtoken next_token() { + for(;;) { + if (eos()) { + return TK_EOS; + } + + m_buffer.reset(); + switch (m_curr_char) { + case '#': // comment + read_comment(); + break; + case '\n': + next(); + m_line++; + return TK_NEWLINE; + case '\\': + // here we ignore a newline if it is preceded by a backslash. + // We need to take care, since anywhere else backshlash is used + // as a regular character + next(); + save_char('\\'); + if (lookahead_newline()) { + break; + } + return read_id(); + case '(': + m_tok_pos = m_pos; + next(); + return TK_LP; + case ')': + m_tok_pos = m_pos; + next(); + return TK_RP; + case ',': + m_tok_pos = m_pos; + next(); + return TK_COMMA; + case '=': + m_tok_pos = m_pos; + next(); + return TK_EQ; + case '!': + m_tok_pos = m_pos; + next(); + if(m_curr_char == '=') { + next(); + return TK_NEQ; + } + return TK_NEG; + case ':': + m_tok_pos = m_pos; + next(); + if (m_curr_char == '-') { + next(); + return TK_LEFT_ARROW; + } + return TK_COLON; + case '\"': + return read_string(); + case '|': + next(); + return read_bid(); + default: + if (iswspace(m_curr_char)) { + next(); + break; + } + else if (iswdigit(m_curr_char)) { + m_tok_pos = m_pos; + save_and_next(); + return read_num(); + } + else { + char old = m_curr_char; + m_tok_pos = m_pos; + save_and_next(); + if (old == '-' && iswdigit(m_curr_char)) { + return read_num(); + } + else { + return read_id(); + } + } + } + } + } + + const char * get_token_data() const { + return m_buffer.c_str(); + } + + unsigned get_token_pos() const { + return m_tok_pos; + } + + unsigned get_line() const { return m_line; } + + + +}; + +class dparser : public parser { +protected: + typedef map > str2var; + typedef map > str2sort; + + context& m_context; + ast_manager& m_manager; + + dlexer* m_lexer; + ast_ref_vector m_pinned; + region m_region; + dl_decl_util & m_decl_util; + arith_util m_arith; + + unsigned m_num_vars; + str2var m_vars; + unsigned m_sym_idx; + std::string m_path; + str2sort m_sort_dict; + + // true if an error occured during the current call to the parse_stream + // function + bool m_error; +public: + dparser(context& ctx, ast_manager& m): + m_context(ctx), + m_manager(m), + m_pinned(m), + m_decl_util(ctx.get_decl_util()), + m_arith(m), + m_num_vars(0), + m_sym_idx(0) + { + } + + virtual bool parse_file(char const * filename) { + reset(); + if (filename != 0) { + set_path(filename); + std::ifstream stream(filename); + if (!stream) { + get_err() << "ERROR: could not open file '" << filename << "'.\n"; + return false; + } + return parse_stream(stream); + } + else { + return parse_stream(std::cin); + } + } + + virtual bool parse_string(char const * string) { + reset(); + std::string s(string); + std::istringstream is(s); + return parse_stream(is); + } + +protected: + + void reset() { + m_num_vars = 0; + m_sym_idx = 0; + m_pinned.reset(); + m_vars.reset(); + m_region.reset(); + m_path.clear(); + m_sort_dict.reset(); + } + + void set_path(char const* filename) { + char const* div = strrchr(filename, '/'); + if (!div) { + div = strrchr(filename,'\\'); + } + if (div) { + m_path.assign(filename, div - filename + 1); + } + } + + std::ostream& get_err() { + return std::cerr; + } + + bool parse_stream(std::istream& is) { + bool result = false; + try { + m_error=false; + dlexer lexer; + m_lexer = &lexer; + m_lexer->set_stream(is); + dtoken tok = m_lexer->next_token(); + tok = parse_domains(tok); + tok = parse_decls(tok); + result = tok == TK_EOS && m_error == false; + } + catch (z3_exception& ex) { + std::cerr << ex.msg() << std::endl; + result = false; + } + return result; + } + + dtoken parse_domains(dtoken tok) { + flet flet_parsing_domains(m_lexer->m_parsing_domains, true); + while (tok != TK_EOS && tok != TK_ERROR) { + switch(tok) { + case TK_ID: + tok = parse_domain(); + break; + case TK_NEWLINE: + return m_lexer->next_token(); + case TK_INCLUDE: + tok = m_lexer->next_token(); + if (tok != TK_STRING) { + tok = unexpected(tok, "a string"); + break; + } + tok = parse_include(m_lexer->get_token_data(), true); + if(tok!=TK_NEWLINE) { + tok = unexpected(tok, "newline expected after include statement"); + } + else { + tok = m_lexer->next_token(); + } + break; + default: + tok = unexpected(tok, "identifier, newline or include"); + break; + } + } + return tok; + } + + bool extract_domain_name(const char* s0, std::string & result) { + std::string str(s0); + size_t last_non_digit = str.find_last_not_of("0123456789"); + if(last_non_digit==std::string::npos) { + //the domain name consists only of digits, which should not happen + result=str; + return false; + } + str.erase(last_non_digit+1); + result=str; + return true; + } + + dtoken parse_domain() { + std::string domain_name; + if(!extract_domain_name(m_lexer->get_token_data(), domain_name)) { + return unexpected(TK_ID, "domain name"); + } + dtoken tok = m_lexer->next_token(); + if (tok == TK_ID && strcmp(m_lexer->get_token_data(), "int")==0) { + register_int_sort(symbol(domain_name.c_str())); + + tok = m_lexer->next_token(); + if(tok != TK_NEWLINE) { + return unexpected(tok, "end of line"); + } + return tok; + } + if (tok != TK_NUM) { + return unexpected(tok, "numeral or 'int'"); + } + + unsigned num = atoi(m_lexer->get_token_data()); + sort * s = register_finite_sort(symbol(domain_name.c_str()), num, context::SK_SYMBOL); + + tok = m_lexer->next_token(); + if (tok == TK_ID) { + tok = parse_mapfile(tok, s, m_lexer->get_token_data()); + } + if (tok == TK_NEWLINE) { + tok = m_lexer->next_token(); + } + return tok; + } + + + dtoken parse_decls(dtoken tok) { + while (tok != TK_EOS && tok != TK_ERROR) { + switch(tok) { + case TK_ID: + tok = parse_rule(tok); + break; + case TK_NEWLINE: + tok = m_lexer->next_token(); + break; + case TK_INCLUDE: + tok = m_lexer->next_token(); + if (tok != TK_STRING) { + tok = unexpected(tok, "a string"); + break; + } + tok = parse_include(m_lexer->get_token_data(), false); + break; + default: + tok = unexpected(tok, "identifier"); + break; + } + } + return tok; + } + + dtoken unexpected(dtoken tok, char const* msg) { +#if 1 + throw default_exception("%s at line %u '%s' found '%s'\n", msg, + m_lexer->get_line(), m_lexer->get_token_data(), dtoken_strings[tok]); + + SASSERT(false); + return tok; +#else + m_error = true; + + get_err() << msg << " expected at line " << m_lexer->get_line() << "\n"; + get_err() << "'" << m_lexer->get_token_data() << "' found\n"; + get_err() << "'" << dtoken_strings[tok] << "'\n"; + if (tok == TK_ERROR || tok == TK_EOS) { + return tok; + } + return m_lexer->next_token(); +#endif + } + + dtoken parse_rule(dtoken tok) { + m_num_vars = 0; + m_vars.reset(); + + switch(tok) { + case TK_EOS: + return tok; + case TK_ID: { + app_ref pred(m_manager); + symbol s(m_lexer->get_token_data()); + tok = m_lexer->next_token(); + bool is_predicate_declaration; + tok = parse_pred(tok, s, pred, is_predicate_declaration); + switch (tok) { + case TK_PERIOD: + if(is_predicate_declaration) { + return unexpected(tok, "predicate declaration should not end with '.'"); + } + add_rule(pred, 0, 0, 0); + return m_lexer->next_token(); + case TK_LEFT_ARROW: + return parse_body(pred); + case TK_NEWLINE: + case TK_EOS: + if(!is_predicate_declaration) { + return unexpected(tok, "'.' expected at the end of rule"); + } + return tok; + default: + return unexpected(tok, "unexpected token"); + } + } + default: + return unexpected(tok, "rule expected"); + } + } + + dtoken parse_body(app* head) { + app_ref_vector body(m_manager); + svector polarity_vect; + dtoken tok = m_lexer->next_token(); + while (tok != TK_ERROR && tok != TK_EOS) { + if (tok == TK_PERIOD) { + SASSERT(body.size()==polarity_vect.size()); + add_rule(head, body.size(), body.c_ptr(), polarity_vect.c_ptr()); + return m_lexer->next_token(); + } + char const* td = m_lexer->get_token_data(); + app_ref pred(m_manager); + bool is_neg = false; + if (tok == TK_NEG) { + tok = m_lexer->next_token(); + is_neg = true; + } + + if (tok == TK_STRING || tok == TK_NUM || (tok == TK_ID && m_vars.contains(td))) { + tok = parse_infix(tok, td, pred); + } + else if (tok == TK_ID) { + symbol s(td); + tok = m_lexer->next_token(); + bool is_declaration; + tok = parse_pred(tok, s, pred, is_declaration); + SASSERT(!is_declaration); + } + else { + tok = unexpected(tok, "expected predicate or relation"); + return tok; + } + body.push_back(pred); + polarity_vect.push_back(is_neg); + + if (tok == TK_COMMA) { + tok = m_lexer->next_token(); + } + else if (tok == TK_PERIOD) { + continue; + } + else { + tok = unexpected(tok, "expected comma or period"); + return tok; + } + } + return tok; + } + + // + // infix: + // Sym REL Sym + // Sym ::= String | NUM | Var + // + dtoken parse_infix(dtoken tok1, char const* td, app_ref& pred) { + symbol td1(td); + expr* v1 = 0, *v2 = 0; + sort* s = 0; + dtoken tok2 = m_lexer->next_token(); + if (tok2 != TK_NEQ && tok2 != TK_GT && tok2 != TK_LT && tok2 != TK_EQ) { + return unexpected(tok2, "built-in infix operator"); + } + dtoken tok3 = m_lexer->next_token(); + td = m_lexer->get_token_data(); + if (tok3 != TK_STRING && tok3 != TK_NUM && !(tok3 == TK_ID && m_vars.contains(td))) { + return unexpected(tok3, "identifier"); + } + symbol td2(td); + + if (tok1 == TK_ID) { + m_vars.find(td1.bare_str(), v1); + } + if (tok3 == TK_ID) { + m_vars.find(td2.bare_str(), v2); + } + if (v1 == 0 && v2 == 0) { + return unexpected(tok3, "at least one argument should be a variable"); + } + if (v1) { + s = m_manager.get_sort(v1); + } + else { + s = m_manager.get_sort(v2); + } + if (!v1) { + v1 = mk_const(td1, s); + } + if (!v2) { + v2 = mk_const(td2, s); + } + + switch(tok2) { + case TK_EQ: + pred = m_manager.mk_eq(v1,v2); + break; + case TK_NEQ: + pred = m_manager.mk_not(m_manager.mk_eq(v1,v2)); + break; + case TK_LT: + pred = m_decl_util.mk_lt(v1, v2); + break; + case TK_GT: + pred = m_decl_util.mk_lt(v2, v1); + break; + default: + UNREACHABLE(); + } + + return m_lexer->next_token(); + } + + + dtoken parse_pred(dtoken tok, symbol const& s, app_ref& pred, bool & is_predicate_declaration) { + + expr_ref_vector args(m_manager); + svector arg_names; + func_decl* f = m_context.try_get_predicate_decl(s); + tok = parse_args(tok, f, args, arg_names); + is_predicate_declaration = f==0; + if (f==0) { + //we're in a declaration + unsigned arity = args.size(); + ptr_vector domain; + for (unsigned i = 0; i < arity; ++i) { + domain.push_back(m_manager.get_sort(args[i].get())); + } + f = m_manager.mk_func_decl(s, domain.size(), domain.c_ptr(), m_manager.mk_bool_sort()); + + m_context.register_predicate(f); + while (tok == TK_ID) { + char const* pred_pragma = m_lexer->get_token_data(); + if(strcmp(pred_pragma, "printtuples")==0 || strcmp(pred_pragma, "outputtuples")==0) { + m_context.set_output_predicate(f); + } + tok = m_lexer->next_token(); + } + m_context.set_argument_names(f, arg_names); + } + if(args.size() < f->get_arity()) { + return unexpected(tok, "too few arguments passed to predicate"); + } + SASSERT(args.size()==f->get_arity()); + //TODO: we do not need to do the mk_app if we're in a declaration + pred = m_manager.mk_app(f, args.size(), args.c_ptr()); + return tok; + } + + /** + \brief Parse predicate arguments. If \c f==0, they are arguments of a predicate declaration. + If parsing a declaration, argumens names are pushed to the \c arg_names vector. + */ + dtoken parse_args(dtoken tok, func_decl* f, expr_ref_vector& args, svector & arg_names) { + if (tok != TK_LP) { + return tok; + } + unsigned arg_idx = 0; + tok = m_lexer->next_token(); + while (tok != TK_EOS && tok != TK_ERROR) { + symbol alias; + sort* s = 0; + + if(!f) { + //we're in a predicate declaration + if(tok != TK_ID) { + tok = unexpected(tok, "Expecting variable in declaration"); + return tok; + } + symbol var_symbol(m_lexer->get_token_data()); + tok = m_lexer->next_token(); + if (tok != TK_COLON) { + tok = unexpected(tok, + "Expecting colon in declaration (first occurence of a predicate must be a declaration)"); + return tok; + } + tok = m_lexer->next_token(); + + if (tok != TK_ID) { + tok = unexpected(tok, "Expecting sort after colon in declaration"); + return tok; + } + std::string sort_name; + if(!extract_domain_name(m_lexer->get_token_data(), sort_name)) { + return unexpected(TK_ID, "sort name"); + } + sort* s = get_sort(sort_name.c_str()); + args.push_back(m_manager.mk_var(m_num_vars, s)); + arg_names.push_back(var_symbol); + tok = m_lexer->next_token(); + } + else { + if(arg_idx>=f->get_arity()) { + return unexpected(tok, "too many arguments passed to predicate"); + } + s = f->get_domain(arg_idx); + + symbol var_symbol; + tok = parse_arg(tok, s, args); + } + + + ++arg_idx; + + if (tok == TK_RP) { + return m_lexer->next_token(); + } + if (tok == TK_COMMA) { + tok = m_lexer->next_token(); + } + } + return tok; + } + + /** + \remark \c var_symbol argument is assigned name of the variable. If the argument is not + a variable, is remains unchanged. + */ + dtoken parse_arg(dtoken tok, sort* s, expr_ref_vector& args) { + switch(tok) { + case TK_WILD: { + args.push_back(m_manager.mk_var(m_num_vars++, s)); + break; + } + case TK_ID: { + symbol data (m_lexer->get_token_data()); + if (is_var(data.bare_str())) { + unsigned idx = 0; + expr* v = 0; + if (!m_vars.find(data.bare_str(), v)) { + idx = m_num_vars++; + v = m_manager.mk_var(idx, s); + m_vars.insert(data.bare_str(), v); + } + else if (s != m_manager.get_sort(v)) { + throw default_exception("sort: %s expected, but got: %s\n", + s->get_name().bare_str(), m_manager.get_sort(v)->get_name().bare_str()); + } + args.push_back(v); + } + else { + args.push_back(mk_const(data, s)); + } + break; + } + case TK_STRING: { + char const* data = m_lexer->get_token_data(); + args.push_back(mk_const(symbol(data), s)); + break; + } + case TK_NUM: { + char const* data = m_lexer->get_token_data(); + rational num(data); + if(!num.is_uint64()) { + return unexpected(tok, "integer expected"); + } + uint64 int_num = num.get_uint64(); + + app * numeral = mk_symbol_const(int_num, s); + args.push_back(numeral); + break; + } + default: + break; + } + return m_lexer->next_token(); + } + + // all IDs are variables. + bool is_var(char const* data) { + return true; + } + + dtoken parse_decl(dtoken tok) { + + return tok; + } + + dtoken parse_include(char const* filename, bool parsing_domain) { + std::string path(m_path); + path += filename; + std::ifstream stream(path.c_str()); + if (!stream) { + get_err() << "ERROR: could not open file '" << path << "'.\n"; + return TK_ERROR; + } + dtoken tok; + dlexer lexer; + { + flet lexer_let(m_lexer, &lexer); + m_lexer->set_stream(stream); + tok = m_lexer->next_token(); + if(parsing_domain) { + tok = parse_domains(tok); + } + tok = parse_decls(tok); + } + if (tok == TK_EOS) { + tok = m_lexer->next_token(); + } + return tok; + } + + dtoken parse_mapfile(dtoken tok, sort * s, char const* filename) { + std::string path(m_path); + path += filename; + std::ifstream stream(path.c_str()); + if (!stream) { + get_err() << "Warning: could not open file '" << path << "'.\n"; + return m_lexer->next_token(); + } + + std::string line; + while(read_line(stream, line)) { + symbol sym=symbol(line.c_str()); + m_context.get_constant_number(s, sym); + } + return m_lexer->next_token(); + } + + bool read_line(std::istream& strm, std::string& line) { + line.clear(); + char ch = strm.get(); + while (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r') { + ch = strm.get(); + } + while (ch != '\n' && ch != '\r' && ch != EOF) { + line.push_back(ch); + ch = strm.get(); + } + return line.size() > 0; + } + + void add_rule(app* head, unsigned sz, app* const* body, const bool * is_neg) { + m_pinned.push_back(head); + for (unsigned i = 0; i < sz; ++i) { + m_pinned.push_back(body[i]); + } + rule_manager& m = m_context.get_rule_manager(); + + if(sz==0 && m.is_fact(head)) { + m_context.add_fact(head); + } + else { + rule * r = m.mk(head, sz, body, is_neg); + rule_ref rule(r, m); + m_context.add_rule(rule); + + } + } + + sort * register_finite_sort(symbol name, uint64 domain_size, context::sort_kind k) { + if(m_sort_dict.contains(name.bare_str())) { + throw default_exception("sort %s already declared", name.bare_str()); + } + sort * s = m_decl_util.mk_sort(name, domain_size); + m_context.register_finite_sort(s, k); + m_sort_dict.insert(name.bare_str(), s); + return s; + } + + sort * register_int_sort(symbol name) { + if(m_sort_dict.contains(name.bare_str())) { + throw default_exception("sort %s already declared", name.bare_str()); + } + sort * s = m_arith.mk_int(); + m_sort_dict.insert(name.bare_str(), s); + return s; + } + + sort * get_sort(const char* str) { + sort * res; + if(!m_sort_dict.find(str, res)) { + throw default_exception("unknown sort \"%s\"", str); + } + return res; + } + + app* mk_const(symbol const& name, sort* s) { + app * res; + if(m_arith.is_int(s)) { + uint64 val; + if(!string_to_uint64(name.bare_str(), val)) { + throw default_exception("Invalid integer: \"&s\"", name.bare_str()); + } + res = m_arith.mk_numeral(rational(val, rational::ui64()), s); + } + else { + unsigned idx = m_context.get_constant_number(s, name); + res = m_decl_util.mk_numeral(idx, s); + } + m_pinned.push_back(res); + return res; + } + /** + \brief Make a constant for DK_SYMBOL sort out of an integer + */ + app* mk_symbol_const(uint64 el, sort* s) { + app * res; + if(m_arith.is_int(s)) { + res = m_arith.mk_numeral(rational(el, rational::ui64()), s); + } + else { + unsigned idx = m_context.get_constant_number(s, symbol(to_string(el).c_str())); + res = m_decl_util.mk_numeral(idx, s); + } + m_pinned.push_back(res); + return res; + } + app* mk_const(uint64 el, sort* s) { + unsigned idx = m_context.get_constant_number(s, el); + app * res = m_decl_util.mk_numeral(idx, s); + m_pinned.push_back(res); + return res; + } + + table_element mk_table_const(symbol const& name, sort* s) { + return m_context.get_constant_number(s, name); + } + table_element mk_table_const(uint64 el, sort* s) { + return m_context.get_constant_number(s, el); + } +}; + +/* + + Program ::== Sort* (Rule | Include | Decl)* + Comment ::== '#...' + Rule ::== Fact | InfRule + Fact ::== Identifier(Element*). + InfRule ::== Identifier(Element*) :- (Identifier(Element*))+. + Element ::== '_' | 'string' | integer | Identifier + + Sort ::== Identifier (Number [map-file]| 'int') + Decl ::== Identifier(SortDecl) [Pragma] \n + SortDecl ::== Identifier ':' Identifier + + Pragma ::== 'input' | 'printtuples' | + + + If sort name ends with a sequence of digits, they are ignored (so V and V1234 stand for the same sort) + This is how BDDBDDB behaves. +*/ + +// ----------------------------------- +// +// wpa_parser +// +// ----------------------------------- + +class wpa_parser_impl : public wpa_parser, dparser { + typedef svector uint64_vector; + typedef hashtable > uint64_set; + typedef map > num2sym; + typedef map sym2nums; + + num2sym m_number_names; + sym2nums m_sort_contents; + + sort_ref m_bool_sort; + sort_ref m_short_sort; + + std::string m_current_file; + unsigned m_current_line; + + bool m_use_map_names; + + uint64_set& ensure_sort_content(symbol sort_name) { + sym2nums::entry * e = m_sort_contents.insert_if_not_there2(sort_name, 0); + if(!e->get_data().m_value) { + e->get_data().m_value = alloc(uint64_set); + } + return *e->get_data().m_value; + } + +public: + wpa_parser_impl(context & ctx) + : dparser(ctx, ctx.get_manager()), + m_bool_sort(ctx.get_manager()), + m_short_sort(ctx.get_manager()), + m_use_map_names(ctx.get_params().get_bool(":use-map-names", true)) { + } + ~wpa_parser_impl() { + reset_dealloc_values(m_sort_contents); + } + void reset() { + } + + virtual bool parse_directory(char const * path) { + bool result = false; + try { + result = parse_directory_core(path); + } + catch (z3_exception& ex) { + std::cerr << ex.msg() << std::endl; + return false; + } + return result; + } + +private: + + bool parse_directory_core(char const* path) { + + IF_VERBOSE(10, verbose_stream() << "Start parsing directory " << path << "\n";); + reset(); + string_vector map_files; + get_file_names(path, "map", true, map_files); + string_vector::iterator mit = map_files.begin(); + string_vector::iterator mend = map_files.end(); + for(; mit!=mend; ++mit) { + std::string map_file_name = *mit; + parse_map_file(map_file_name); + } + + finish_map_files(); + + string_vector rule_files; + get_file_names(path, "rules", true, rule_files); + string_vector::iterator rlit = rule_files.begin(); + string_vector::iterator rlend = rule_files.end(); + for(; rlit!=rlend; ++rlit) { + parse_rules_file(*rlit); + } + + string_vector rel_files; + get_file_names(path, "rel", true, rel_files); + string_vector::iterator rit = rel_files.begin(); + string_vector::iterator rend = rel_files.end(); + for(; rit!=rend; ++rit) { + std::string rel_file_name = *rit; + //skip relations which we do not support yet + if(rel_file_name.find("DirectCall")!=std::string::npos || + rel_file_name.find("FunctionFormals")!=std::string::npos || + rel_file_name.find("IndirectCall")!=std::string::npos) { + continue; + } + parse_rel_file(rel_file_name); + } + IF_VERBOSE(10, verbose_stream() << "Done parsing directory " << path << "\n";); + return true; + } + + bool inp_num_to_element(sort * s, uint64 num, table_element & res) { + if(s==m_bool_sort.get() || s==m_short_sort.get()) { + res = mk_table_const(num, s); + return true; + } + + if(num==0) { + if(!m_use_map_names) { + res = mk_table_const(0, s); + } + else { + res = mk_table_const(symbol(""), s); + } + return true; + } + + sym2nums::entry * e = m_sort_contents.find_core(s->get_name()); + SASSERT(e); + SASSERT(e->get_data().m_value); + uint64_set & sort_content = *e->get_data().m_value; + if(!sort_content.contains(num)) { + warning_msg("symbol number %I64u on line %d in file %s does not belong to sort %s", + num, m_current_line, m_current_file.c_str(), s->get_name().bare_str()); + return false; + } + if(!m_use_map_names) { + res = mk_table_const(num, s); + return true; + } + else { + symbol const_name; + if(num==0) { + const_name = symbol(""); + } else if(!m_number_names.find(num, const_name)) { + throw default_exception("unknown symbol number %I64u on line %d in file %s", + num, m_current_line, m_current_file.c_str()); + } + res = mk_table_const(const_name, s); + return true; + } + } + + void parse_rules_file(std::string fname) { + SASSERT(file_exists(fname)); + flet flet_cur_file(m_current_file, fname); + + std::ifstream stm(fname.c_str()); + SASSERT(!stm.fail()); + + dlexer lexer; + m_lexer = &lexer; + m_lexer->set_stream(stm); + dtoken tok = m_lexer->next_token(); + tok = parse_decls(tok); + m_lexer = 0; + } + + bool parse_rel_line(char * full_line, uint64_vector & args) { + SASSERT(args.empty()); + cut_off_comment(full_line); + if(full_line[0]==0) { + return false; + } + const char * ptr = full_line; + + bool last = false; + do { + while(*ptr==' ') { ptr++; } + if(*ptr==0) { + break; + } + uint64 num; + if(!read_uint64(ptr, num)) { + throw default_exception("number expected on line %d in file %s", + m_current_line, m_current_file.c_str()); + } + if(*ptr!=' ' && *ptr!=0) { + throw default_exception("' ' expected to separate numbers on line %d in file %s, got '%s'", + m_current_line, m_current_file.c_str(), ptr); + } + args.push_back(num); + } while(!last); + return true; + } + + void parse_rel_file(std::string fname) { + SASSERT(file_exists(fname)); + + IF_VERBOSE(10, verbose_stream() << "Parsing relation file " << fname << "\n";); + + flet flet_cur_file(m_current_file, fname); + flet flet_cur_line(m_current_line, 0); + + std::string predicate_name_str = get_file_name_without_extension(fname); + symbol predicate_name(predicate_name_str.c_str()); + + func_decl * pred = m_context.try_get_predicate_decl(predicate_name); + if(!pred) { + throw default_exception("tuple file %s for undeclared predicate %s", + m_current_file.c_str(), predicate_name.bare_str()); + } + if(!m_context.can_add_table_fact(pred)) { + NOT_IMPLEMENTED_YET(); + } + unsigned pred_arity = pred->get_arity(); + sort * const * arg_sorts = pred->get_domain(); + + uint64_vector args; + table_fact fact; + + //std::ifstream stm(fname.c_str(), std::ios_base::binary); + //SASSERT(!stm.fail()); + //line_reader rdr(stm); + line_reader rdr(fname.c_str()); + while(!rdr.eof()) { + m_current_line++; + char * full_line = rdr.get_line(); + + args.reset(); + if(!parse_rel_line(full_line, args)) { + continue; + } + if(args.size()!=pred_arity) { + throw default_exception("invalid number of arguments on line %d in file %s", + m_current_line, m_current_file.c_str()); + } + + bool fact_fail = false; + fact.reset(); + for(unsigned i=0;im_key; + uint64_set & sort_content = *sit->m_value; + //the +1 is for a zero element which happens to appear in the problem files + uint64 domain_size = sort_content.size()+1; + sort * s; + if(!m_use_map_names) { + s = register_finite_sort(sort_name, domain_size, context::SK_UINT64); + } + else { + s = register_finite_sort(sort_name, domain_size, context::SK_SYMBOL); + } + + /* + uint64_set::iterator cit = sort_content.begin(); + uint64_set::iterator cend = sort_content.end(); + for(; cit!=cend; ++cit) { + uint64 const_num = *cit; + inp_num_to_element(s, const_num); + } + */ + } + } + + void cut_off_comment(char * line) { + char * ptr = line; + while(*ptr && *ptr!='#' && *ptr!='\n' && *ptr!='\r') { + ptr++; + } + *ptr=0; + } + + bool parse_map_line(char * full_line, uint64 & num, symbol & name) { + cut_off_comment(full_line); + if(full_line[0]==0) { + return false; + } + + const char * ptr = full_line; + if(!read_uint64(ptr, num)) { + throw default_exception("number expected at line %d in file %s", m_current_line, m_current_file.c_str()); + } + if(*ptr!=' ') { + throw default_exception("' ' expected after the number at line %d in file %s", m_current_line, m_current_file.c_str()); + } + ptr++; + + if(!m_use_map_names) { + static symbol no_name(""); + name=no_name; + } + else { + std::string rest_of_line(ptr); + + const char * cut_off_word = " SC_EXTERN "; + size_t cut_off_pos = rest_of_line.find(cut_off_word); + if(cut_off_pos!=std::string::npos) { + rest_of_line = rest_of_line.substr(0, cut_off_pos); + } + + cut_off_word = " _ZONE_"; + cut_off_pos = rest_of_line.find(cut_off_word); + if(cut_off_pos!=std::string::npos) { + rest_of_line = rest_of_line.substr(0, cut_off_pos); + } + + const char * const ignored_suffix = "Constant "; + const size_t ignored_suffix_len = 9; + + if(rest_of_line.size()>ignored_suffix_len && + rest_of_line.substr(rest_of_line.size()-ignored_suffix_len)==ignored_suffix) { + rest_of_line = rest_of_line.substr(0, rest_of_line.size()-ignored_suffix_len); + } + + if(rest_of_line[rest_of_line.size()-1]==' ') { + rest_of_line = rest_of_line.substr(0, rest_of_line.size()-1); + } + + name = symbol(rest_of_line.c_str()); + } + return true; + } + + void parse_map_file(std::string fname) { + SASSERT(file_exists(fname)); + + IF_VERBOSE(10, verbose_stream() << "Parsing map file " << fname << "\n";); + flet flet_cur_file(m_current_file, fname); + flet flet_cur_line(m_current_line, 0); + + std::string sort_name_str = get_file_name_without_extension(fname); + symbol sort_name(sort_name_str.c_str()); + uint64_set & sort_elements = ensure_sort_content(sort_name); + + //std::ifstream stm(fname.c_str(), std::ios_base::binary); + //SASSERT(!stm.fail()); + //line_reader rdr(stm); + line_reader rdr(fname.c_str()); + while(!rdr.eof()) { + m_current_line++; + char * full_line = rdr.get_line(); + + uint64 num; + symbol el_name; + + if(!parse_map_line(full_line, num, el_name)) { + continue; + } + + sort_elements.insert(num); + + if(m_use_map_names) { + num2sym::entry * e = m_number_names.insert_if_not_there2(num, el_name); + if(e->get_data().m_value!=el_name) { + warning_msg("mismatch of number names on line %d in file %s. old: \"%s\" new: \"%s\"", + m_current_line, fname.c_str(), e->get_data().m_value.bare_str(), el_name.bare_str()); + } + } + } + } +}; + +parser* parser::create(context& ctx, ast_manager& m) { + return alloc(dparser, ctx, m); +} + +wpa_parser * wpa_parser::create(context& ctx, ast_manager & ast_manager) { + return alloc(wpa_parser_impl, ctx); +} + diff --git a/lib/datalog_parser.h b/lib/datalog_parser.h new file mode 100644 index 000000000..88ba3ab72 --- /dev/null +++ b/lib/datalog_parser.h @@ -0,0 +1,48 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + datalog_parser.h + +Abstract: + + Parser for Datalogish files + +Author: + + Nikolaj Bjorner (nbjorner) 2010-5-17 + +Revision History: + +--*/ +#ifndef _DATALOG_PARSER_H_ +#define _DATALOG_PARSER_H_ + +#include "ast.h" +#include "dl_context.h" + +namespace datalog { + + class parser { + public: + static parser * create(context& ctx, ast_manager & ast_manager); + + virtual ~parser() {} + + virtual bool parse_file(char const * path) = 0; + virtual bool parse_string(char const * string) = 0; + }; + + class wpa_parser { + public: + static wpa_parser * create(context& ctx, ast_manager & ast_manager); + + virtual ~wpa_parser() {} + + virtual bool parse_directory(char const * path) = 0; + }; + +}; + +#endif diff --git a/lib/datatype_decl_plugin.cpp b/lib/datatype_decl_plugin.cpp new file mode 100644 index 000000000..5cf841b49 --- /dev/null +++ b/lib/datatype_decl_plugin.cpp @@ -0,0 +1,944 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + datatype_decl_plugin.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-10. + +Revision History: + +--*/ +#include"datatype_decl_plugin.h" +#include"warning.h" +#include"ast_smt2_pp.h" + +/** + \brief Auxiliary class used to declare inductive datatypes. +*/ +class accessor_decl { + symbol m_name; + type_ref m_type; +public: + accessor_decl(const symbol & n, type_ref r):m_name(n), m_type(r) {} + symbol const & get_name() const { return m_name; } + type_ref const & get_type() const { return m_type; } +}; + +accessor_decl * mk_accessor_decl(symbol const & n, type_ref const & t) { + return alloc(accessor_decl, n, t); +} + +void del_accessor_decl(accessor_decl * d) { + dealloc(d); +} + +void del_accessor_decls(unsigned num, accessor_decl * const * as) { + for (unsigned i = 0; i < num; i++) + del_accessor_decl(as[i]); +} + +/** + \brief Auxiliary class used to declare inductive datatypes. +*/ +class constructor_decl { + symbol m_name; + symbol m_recogniser_name; + ptr_vector m_accessors; +public: + constructor_decl(const symbol & n, const symbol & r, unsigned num_accessors, accessor_decl * const * accessors): + m_name(n), m_recogniser_name(r), m_accessors(num_accessors, accessors) {} + ~constructor_decl() { + std::for_each(m_accessors.begin(), m_accessors.end(), delete_proc()); + } + symbol const & get_name() const { return m_name; } + symbol const & get_recognizer_name() const { return m_recogniser_name; } + ptr_vector const & get_accessors() const { return m_accessors; } +}; + +constructor_decl * mk_constructor_decl(symbol const & n, symbol const & r, unsigned num_accessors, accessor_decl * const * accessors) { + return alloc(constructor_decl, n, r, num_accessors, accessors); +} + +void del_constructor_decl(constructor_decl * d) { + dealloc(d); +} + +void del_constructor_decls(unsigned num, constructor_decl * const * cs) { + for (unsigned i = 0; i < num; i++) + del_constructor_decl(cs[i]); +} + +/** + \brief Auxiliary class used to declare inductive datatypes. +*/ +class datatype_decl { + symbol m_name; + ptr_vector m_constructors; +public: + datatype_decl(const symbol & n, unsigned num_constructors, constructor_decl * const * constructors): + m_name(n), m_constructors(num_constructors, constructors) {} + ~datatype_decl() { + std::for_each(m_constructors.begin(), m_constructors.end(), delete_proc()); + } + symbol const & get_name() const { return m_name; } + ptr_vector const & get_constructors() const { return m_constructors; } +}; + +datatype_decl * mk_datatype_decl(symbol const & n, unsigned num_constructors, constructor_decl * const * cs) { + return alloc(datatype_decl, n, num_constructors, cs); +} + +void del_datatype_decl(datatype_decl * d) { + dealloc(d); +} + +void del_datatype_decls(unsigned num, datatype_decl * const * ds) { + for (unsigned i = 0; i < num; i++) + del_datatype_decl(ds[i]); +} + +typedef buffer bool_buffer; + +struct invalid_datatype {}; + +static parameter const & read(unsigned num_parameters, parameter const * parameters, unsigned idx, bool_buffer & read_pos) { + if (idx >= num_parameters) { + throw invalid_datatype(); + } + if (idx >= read_pos.size()) { + read_pos.resize(idx+1, false); + } + read_pos[idx] = true; + return parameters[idx]; +} + +static int read_int(unsigned num_parameters, parameter const * parameters, unsigned idx, bool_buffer & read_pos) { + const parameter & r = read(num_parameters, parameters, idx, read_pos); + if (!r.is_int()) { + throw invalid_datatype(); + } + return r.get_int(); +} + +static symbol read_symbol(unsigned num_parameters, parameter const * parameters, unsigned idx, bool_buffer & read_pos) { + parameter const & r = read(num_parameters, parameters, idx, read_pos); + if (!r.is_symbol()) { + throw invalid_datatype(); + } + return r.get_symbol(); +} + +enum status { + WHITE, + GRAY, + BLACK +}; + +/** + \brief Return true if the inductive datatype is recursive. + Pre-condition: The given argument constains the parameters of an inductive datatype. +*/ +static bool is_recursive_datatype(parameter const * parameters) { + unsigned num_types = parameters[0].get_int(); + unsigned tid = parameters[1].get_int(); + buffer already_found(num_types, WHITE); + buffer todo; + todo.push_back(tid); + while (!todo.empty()) { + unsigned tid = todo.back(); + if (already_found[tid] == BLACK) { + todo.pop_back(); + continue; + } + already_found[tid] = GRAY; + unsigned o = parameters[2 + 2*tid + 1].get_int(); // constructor offset + unsigned num_constructors = parameters[o].get_int(); + bool can_process = true; + for (unsigned s = 1; s <= num_constructors; s++) { + unsigned k_i = parameters[o + s].get_int(); + unsigned num_accessors = parameters[k_i + 2].get_int(); + for (unsigned r = 0; r < num_accessors; r++) { + parameter const & a_type = parameters[k_i + 4 + 2*r]; + if (a_type.is_int()) { + unsigned tid_prime = a_type.get_int(); + switch (already_found[tid_prime]) { + case WHITE: + todo.push_back(tid_prime); + can_process = false; + break; + case GRAY: + // type is recursive + return true; + case BLACK: + break; + } + } + } + } + if (can_process) { + already_found[tid] = BLACK; + todo.pop_back(); + } + } + return false; +} + +/** + \brief Return the size of the inductive datatype. + Pre-condition: The given argument constains the parameters of an inductive datatype. +*/ +static sort_size get_datatype_size(parameter const * parameters) { + unsigned num_types = parameters[0].get_int(); + unsigned tid = parameters[1].get_int(); + buffer szs(num_types, sort_size()); + buffer already_found(num_types, WHITE); + buffer todo; + todo.push_back(tid); + while (!todo.empty()) { + unsigned tid = todo.back(); + if (already_found[tid] == BLACK) { + todo.pop_back(); + continue; + } + already_found[tid] = GRAY; + unsigned o = parameters[2 + 2*tid + 1].get_int(); // constructor offset + unsigned num_constructors = parameters[o].get_int(); + bool is_very_big = false; + bool can_process = true; + for (unsigned s = 1; s <= num_constructors; s++) { + unsigned k_i = parameters[o+s].get_int(); + unsigned num_accessors = parameters[k_i+2].get_int(); + for (unsigned r = 0; r < num_accessors; r++) { + parameter const & a_type = parameters[k_i+4 + 2*r]; + if (a_type.is_int()) { + int tid_prime = a_type.get_int(); + switch (already_found[tid_prime]) { + case WHITE: + todo.push_back(tid_prime); + can_process = false; + break; + case GRAY: + // type is recursive + return sort_size(); + case BLACK: + break; + } + } + else { + SASSERT(a_type.is_ast()); + sort * ty = to_sort(a_type.get_ast()); + if (ty->is_infinite()) { + // type is infinite + return sort_size(); + } + else if (ty->is_very_big()) { + is_very_big = true; + } + } + } + } + if (can_process) { + todo.pop_back(); + already_found[tid] = BLACK; + if (is_very_big) { + szs[tid] = sort_size::mk_very_big(); + } + else { + // the type is not infinite nor the number of elements is infinite... + // computing the number of elements + rational num; + for (unsigned s = 1; s <= num_constructors; s++) { + unsigned k_i = parameters[o+s].get_int(); + unsigned num_accessors = parameters[k_i+2].get_int(); + rational c_num(1); + for (unsigned r = 0; r < num_accessors; r++) { + parameter const & a_type = parameters[k_i+4 + 2*r]; + if (a_type.is_int()) { + int tid_prime = a_type.get_int(); + SASSERT(!szs[tid_prime].is_infinite() && !szs[tid_prime].is_very_big()); + c_num *= rational(szs[tid_prime].size(),rational::ui64()); + } + else { + SASSERT(a_type.is_ast()); + sort * ty = to_sort(a_type.get_ast()); + SASSERT(!ty->is_infinite() && !ty->is_very_big()); + c_num *= rational(ty->get_num_elements().size(), rational::ui64()); + } + } + num += c_num; + } + szs[tid] = sort_size(num); + } + } + } + return szs[tid]; +} + +/** + \brief Return true if the inductive datatype is well-founded. + Pre-condition: The given argument constains the parameters of an inductive datatype. +*/ +static bool is_well_founded(parameter const * parameters) { + unsigned num_types = parameters[0].get_int(); + buffer well_founded(num_types, false); + unsigned num_well_founded = 0; + bool changed; + do { + changed = false; + for (unsigned tid = 0; tid < num_types; tid++) { + if (!well_founded[tid]) { + unsigned o = parameters[2 + 2*tid + 1].get_int(); // constructor offset + unsigned num_constructors = parameters[o].get_int(); + for (unsigned s = 1; s <= num_constructors; s++) { + unsigned k_i = parameters[o + s].get_int(); + unsigned num_accessors = parameters[k_i + 2].get_int(); + unsigned r = 0; + for (; r < num_accessors; r++) { + parameter const & a_type = parameters[k_i + 4 + 2*r]; + if (a_type.is_int() && !well_founded[a_type.get_int()]) { + break; + } + } + if (r == num_accessors) { + changed = true; + well_founded[tid] = true; + num_well_founded++; + break; + } + } + } + } + } while(changed && num_well_founded < num_types); + unsigned tid = parameters[1].get_int(); + return well_founded[tid]; +} + +datatype_decl_plugin::~datatype_decl_plugin() { + SASSERT(m_util.get() == 0); +} + +void datatype_decl_plugin::finalize() { + m_util = 0; // force deletion +} + +datatype_util & datatype_decl_plugin::get_util() const { + SASSERT(m_manager); + if (m_util.get() == 0) { + m_util = alloc(datatype_util, *m_manager); + } + return *(m_util.get()); +} + + +sort * datatype_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) { + try { + if (k != DATATYPE_SORT) { + throw invalid_datatype(); + } + buffer found; + unsigned num_types = read_int(num_parameters, parameters, 0, found); + if (num_types == 0) { + throw invalid_datatype(); + } + unsigned tid = read_int(num_parameters, parameters, 1, found); + for (unsigned j = 0; j < num_types; j++) { + read_symbol(num_parameters, parameters, 2 + 2*j, found); // type name + unsigned o = read_int(num_parameters, parameters, 2 + 2*j + 1, found); + unsigned num_constructors = read_int(num_parameters, parameters, o, found); + if (num_constructors == 0) { + throw invalid_datatype(); + } + for (unsigned s = 1; s <= num_constructors; s++) { + unsigned k_i = read_int(num_parameters, parameters, o + s, found); + read_symbol(num_parameters, parameters, k_i, found); // constructor name + read_symbol(num_parameters, parameters, k_i + 1, found); // recognizer name + unsigned num_accessors = read_int(num_parameters, parameters, k_i + 2, found); + unsigned first_accessor = k_i+3; + for (unsigned r = 0; r < num_accessors; r++) { + read_symbol(num_parameters, parameters, first_accessor + 2*r, found); // accessor name + parameter const & a_type = read(num_parameters, parameters, first_accessor + 2*r + 1, found); // accessort type + if (!a_type.is_int() && !a_type.is_ast()) { + throw invalid_datatype(); + if (a_type.is_ast() && !is_sort(a_type.get_ast())) { + throw invalid_datatype(); + } + } + } + } + } + // check if there is no garbage + if (found.size() != num_parameters || std::find(found.begin(), found.end(), false) != found.end()) { + throw invalid_datatype(); + } + + if (!is_well_founded(parameters)) { + m_manager->raise_exception("datatype is not well-founded"); + return 0; + } + + // compute datatype size + sort_size ts = get_datatype_size(parameters); + symbol const & tname = parameters[2+2*tid].get_symbol(); + return m_manager->mk_sort(tname, + sort_info(m_family_id, k, ts, num_parameters, parameters, true)); + } + catch (invalid_datatype) { + m_manager->raise_exception("invalid datatype"); + return 0; + } +} + +static sort * get_other_datatype(ast_manager & m, family_id datatype_fid, sort * source_datatype, unsigned tid) { + SASSERT(source_datatype->get_family_id() == datatype_fid); + SASSERT(source_datatype->get_decl_kind() == DATATYPE_SORT); + if (tid == static_cast(source_datatype->get_parameter(1).get_int())) { + return source_datatype; + } + buffer p; + unsigned n = source_datatype->get_num_parameters(); + for (unsigned i = 0; i < n; i++) { + p.push_back(source_datatype->get_parameter(i)); + } + p[1] = parameter(tid); + return m.mk_sort(datatype_fid, DATATYPE_SORT, n, p.c_ptr()); +} + +static sort * get_type(ast_manager & m, family_id datatype_fid, sort * source_datatype, parameter const & p) { + SASSERT(p.is_ast() || p.is_int()); + if (p.is_ast()) { + return to_sort(p.get_ast()); + } + else { + return get_other_datatype(m, datatype_fid, source_datatype, p.get_int()); + } +} + +func_decl * datatype_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + if (num_parameters < 2 || !parameters[0].is_ast() || !is_sort(parameters[0].get_ast())) { + m_manager->raise_exception("invalid parameters for datatype operator"); + return 0; + } + sort * datatype = to_sort(parameters[0].get_ast()); + if (datatype->get_family_id() != m_family_id || + datatype->get_decl_kind() != DATATYPE_SORT) { + m_manager->raise_exception("invalid parameters for datatype operator"); + return 0; + } + for (unsigned i = 1; i < num_parameters; i++) { + if (!parameters[i].is_int()) { + m_manager->raise_exception("invalid parameters for datatype operator"); + return 0; + } + } + unsigned c_idx = parameters[1].get_int(); + unsigned tid = datatype->get_parameter(1).get_int(); + unsigned o = datatype->get_parameter(2 + 2 * tid + 1).get_int(); + unsigned num_constructors = datatype->get_parameter(o).get_int(); + if (c_idx >= num_constructors) { + m_manager->raise_exception("invalid parameters for datatype operator"); + return 0; + } + unsigned k_i = datatype->get_parameter(o + 1 + c_idx).get_int(); + + switch (k) { + case OP_DT_CONSTRUCTOR: + if (num_parameters != 2) { + m_manager->raise_exception("invalid parameters for datatype constructor"); + return 0; + } + else { + + + symbol c_name = datatype->get_parameter(k_i).get_symbol(); + unsigned num_accessors = datatype->get_parameter(k_i + 2).get_int(); + if (num_accessors != arity) { + m_manager->raise_exception("invalid domain size for datatype constructor"); + return 0; + } + + // + // the reference count to domain could be 0. + // we need to ensure that creating a temporary + // copy of the same type causes a free. + // + sort_ref_vector domain_check(*m_manager); + + for (unsigned r = 0; r < num_accessors; r++) { + sort_ref ty(*m_manager); + ty = get_type(*m_manager, m_family_id, datatype, datatype->get_parameter(k_i + 4 + 2*r)); + domain_check.push_back(ty); + if (ty != domain[r]) { + m_manager->raise_exception("invalid domain for datatype constructor"); + return 0; + } + + } + func_decl_info info(m_family_id, k, num_parameters, parameters); + info.m_private_parameters = true; + SASSERT(info.private_parameters()); + return m_manager->mk_func_decl(c_name, arity, domain, datatype, info); + } + case OP_DT_RECOGNISER: + if (num_parameters != 2 || arity != 1 || domain[0] != datatype) { + m_manager->raise_exception("invalid parameters for datatype recogniser"); + return 0; + } + else { + symbol r_name = datatype->get_parameter(k_i + 1).get_symbol(); + sort * b = m_manager->mk_bool_sort(); + func_decl_info info(m_family_id, k, num_parameters, parameters); + info.m_private_parameters = true; + SASSERT(info.private_parameters()); + return m_manager->mk_func_decl(r_name, arity, domain, b, info); + } + case OP_DT_ACCESSOR: + if (num_parameters != 3 || arity != 1 || domain[0] != datatype) { + m_manager->raise_exception("invalid parameters for datatype accessor"); + return 0; + } + else { + unsigned a_idx = parameters[2].get_int(); + unsigned num_accessors = datatype->get_parameter(k_i + 2).get_int(); + if (a_idx >= num_accessors) { + m_manager->raise_exception("invalid datatype accessor"); + return 0; + } + symbol a_name = datatype->get_parameter(k_i + 3 + 2*a_idx).get_symbol(); + sort * a_type = get_type(*m_manager, m_family_id, datatype, datatype->get_parameter(k_i + 4 + 2*a_idx)); + func_decl_info info(m_family_id, k, num_parameters, parameters); + info.m_private_parameters = true; + SASSERT(info.private_parameters()); + return m_manager->mk_func_decl(a_name, arity, domain, a_type, info); + } + break; + default: + m_manager->raise_exception("invalid datatype operator kind"); + return 0; + } +} + +bool datatype_decl_plugin::mk_datatypes(unsigned num_datatypes, datatype_decl * const * datatypes, sort_ref_vector & new_types) { + buffer p; + p.push_back(parameter(num_datatypes)); + p.push_back(parameter(-1)); + for (unsigned i = 0; i < num_datatypes; i++) { + p.push_back(parameter(datatypes[i]->get_name())); + p.push_back(parameter(-1)); // offset is unknown at this point + } + for (unsigned i = 0; i < num_datatypes; i++) { + p[3+2*i] = parameter(p.size()); // save offset to constructor table + ptr_vector const & constructors = datatypes[i]->get_constructors(); + unsigned num_constructors = constructors.size(); + p.push_back(parameter(num_constructors)); + for (unsigned j = 0; j < num_constructors; j++) { + p.push_back(parameter(-1)); // offset is unknown at this point + } + } + for (unsigned i = 0; i < num_datatypes; i++) { + unsigned o = p[3+2*i].get_int(); + ptr_vector const & constructors = datatypes[i]->get_constructors(); + unsigned num_constructors = constructors.size(); + for (unsigned j = 0; j < num_constructors; j++) { + p[o+1+j] = parameter(p.size()); // save offset to constructor definition + constructor_decl * c = constructors[j]; + p.push_back(parameter(c->get_name())); + p.push_back(parameter(c->get_recognizer_name())); + ptr_vector const & accessors = c->get_accessors(); + unsigned num_accessors = accessors.size(); + p.push_back(parameter(num_accessors)); + for (unsigned k = 0; k < num_accessors; k++) { + accessor_decl * a = accessors[k]; + p.push_back(parameter(a->get_name())); + type_ref const & ty = a->get_type(); + if (ty.is_idx()) { + if (static_cast(ty.get_idx()) >= num_datatypes) { + TRACE("datatype", tout << "Index out of bounds: " << ty.get_idx() << "\n";); + return false; + } + p.push_back(parameter(ty.get_idx())); + } + else { + p.push_back(parameter(ty.get_sort())); + } + } + } + } + for (unsigned i = 0; i < num_datatypes; i++) { + p[1] = parameter(i); + TRACE("datatype", tout << "new datatype parameters:\n"; + for (unsigned j = 0; j < p.size(); j++) { + tout << "p[" << j << "] -> " << p[j] << "\n"; + }); + sort * ty = mk_sort(DATATYPE_SORT, p.size(), p.c_ptr()); + if (ty == 0) { + TRACE("datatype", tout << "Failed to create datatype sort from parameters\n";); + return false; + } + new_types.push_back(ty); + } + return true; +} + +expr * datatype_decl_plugin::get_some_value(sort * s) { + SASSERT(s->is_sort_of(m_family_id, DATATYPE_SORT)); + datatype_util & util = get_util(); + func_decl * c = util.get_non_rec_constructor(s); + ptr_buffer args; + for (unsigned i = 0; i < c->get_arity(); i++) { + args.push_back(m_manager->get_some_value(c->get_domain(i))); + } + return m_manager->mk_app(c, args.size(), args.c_ptr()); +} + +bool datatype_decl_plugin::is_fully_interp(sort const * s) const { + SASSERT(s->is_sort_of(m_family_id, DATATYPE_SORT)); + parameter const * parameters = s->get_parameters(); + unsigned num_types = parameters[0].get_int(); + for (unsigned tid = 0; tid < num_types; tid++) { + unsigned o = parameters[2 + 2*tid + 1].get_int(); // constructor offset + unsigned num_constructors = parameters[o].get_int(); + for (unsigned s = 1; s <= num_constructors; s++) { + unsigned k_i = parameters[o + s].get_int(); + unsigned num_accessors = parameters[k_i + 2].get_int(); + unsigned r = 0; + for (; r < num_accessors; r++) { + parameter const & a_type = parameters[k_i + 4 + 2*r]; + if (a_type.is_int()) + continue; + SASSERT(a_type.is_ast()); + sort * arg_s = to_sort(a_type.get_ast()); + if (!m_manager->is_fully_interp(arg_s)) + return false; + } + } + } + return true; +} + +bool datatype_decl_plugin::is_value_visit(expr * arg, ptr_buffer & todo) const { + if (!is_app(arg)) + return false; + family_id fid = to_app(arg)->get_family_id(); + if (fid == m_family_id) { + if (!get_util().is_constructor(to_app(arg))) + return false; + if (to_app(arg)->get_num_args() == 0) + return true; + todo.push_back(to_app(arg)); + return true; + } + else { + return m_manager->is_value(arg); + } +} + +bool datatype_decl_plugin::is_value(app * e) const { + TRACE("dt_is_value", tout << "checking\n" << mk_ismt2_pp(e, *m_manager) << "\n";); + if (!get_util().is_constructor(e)) + return false; + if (e->get_num_args() == 0) + return true; + // REMARK: if the following check is too expensive, we should + // cache the values in the datatype_decl_plugin. + ptr_buffer todo; + // potentially expensive check for common sub-expressions. + for (unsigned i = 0; i < e->get_num_args(); i++) { + if (!is_value_visit(e->get_arg(i), todo)) { + TRACE("dt_is_value", tout << "not-value:\n" << mk_ismt2_pp(e->get_arg(i), *m_manager) << "\n";); + return false; + } + } + while (!todo.empty()) { + app * curr = todo.back(); + SASSERT(get_util().is_constructor(curr)); + todo.pop_back(); + for (unsigned i = 0; i < curr->get_num_args(); i++) { + if (!is_value_visit(curr->get_arg(i), todo)) { + TRACE("dt_is_value", tout << "not-value:\n" << mk_ismt2_pp(curr->get_arg(i), *m_manager) << "\n";); + return false; + } + } + } + return true; +} + +datatype_util::datatype_util(ast_manager & m): + m_manager(m), + m_family_id(m.get_family_id("datatype")), + m_asts(m) { +} + +datatype_util::~datatype_util() { + std::for_each(m_vectors.begin(), m_vectors.end(), delete_proc >()); +} + +func_decl * datatype_util::get_constructor(sort * ty, unsigned c_id) { + unsigned tid = ty->get_parameter(1).get_int(); + unsigned o = ty->get_parameter(3 + 2*tid).get_int(); + unsigned k_i = ty->get_parameter(o + c_id + 1).get_int(); + unsigned num_accessors = ty->get_parameter(k_i + 2).get_int(); + parameter p[2] = { parameter(ty), parameter(c_id) }; + ptr_buffer domain; + for (unsigned r = 0; r < num_accessors; r++) { + domain.push_back(get_type(m_manager, m_family_id, ty, ty->get_parameter(k_i + 4 + 2*r))); + } + func_decl * d = m_manager.mk_func_decl(m_family_id, OP_DT_CONSTRUCTOR, 2, p, domain.size(), domain.c_ptr()); + SASSERT(d); + return d; +} + +ptr_vector const * datatype_util::get_datatype_constructors(sort * ty) { + SASSERT(is_datatype(ty)); + ptr_vector * r = 0; + if (m_datatype2constructors.find(ty, r)) + return r; + r = alloc(ptr_vector); + m_asts.push_back(ty); + m_vectors.push_back(r); + m_datatype2constructors.insert(ty, r); + unsigned tid = ty->get_parameter(1).get_int(); + unsigned o = ty->get_parameter(3 + 2*tid).get_int(); + unsigned num_constructors = ty->get_parameter(o).get_int(); + for (unsigned c_id = 0; c_id < num_constructors; c_id++) { + func_decl * c = get_constructor(ty, c_id); + m_asts.push_back(c); + r->push_back(c); + } + return r; +} + +/** + \brief Return a constructor mk(T_1, ... T_n) + where each T_i is not a datatype or it is a datatype that contains + a constructor that will not contain directly or indirectly an element of the given sort. +*/ +func_decl * datatype_util::get_non_rec_constructor(sort * ty) { + SASSERT(is_datatype(ty)); + func_decl * r = 0; + if (m_datatype2nonrec_constructor.find(ty, r)) + return r; + r = 0; + ptr_vector forbidden_set; + forbidden_set.push_back(ty); + r = get_non_rec_constructor_core(ty, forbidden_set); + SASSERT(forbidden_set.back() == ty); + SASSERT(r); + m_asts.push_back(ty); + m_asts.push_back(r); + m_datatype2nonrec_constructor.insert(ty, r); + return r; +} + +/** + \brief Return a constructor mk(T_1, ..., T_n) where + each T_i is not a datatype or it is a datatype t not in forbidden_set, + and get_non_rec_constructor_core(T_i, forbidden_set union { T_i }) +*/ +func_decl * datatype_util::get_non_rec_constructor_core(sort * ty, ptr_vector & forbidden_set) { + // We must select a constructor c(T_1, ..., T_n):T such that + // 1) T_i's are not recursive + // If there is no such constructor, then we select one that + // 2) each type T_i is not recursive or contains a constructor that does not depend on T + ptr_vector const * constructors = get_datatype_constructors(ty); + ptr_vector::const_iterator it = constructors->begin(); + ptr_vector::const_iterator end = constructors->end(); + // step 1) + for (; it != end; ++it) { + func_decl * c = *it; + unsigned num_args = c->get_arity(); + unsigned i = 0; + for (; i < num_args; i++) { + sort * T_i = c->get_domain(i); + if (is_datatype(T_i)) + break; + } + if (i == num_args) + return c; + } + // step 2) + it = constructors->begin(); + for (; it != end; ++it) { + func_decl * c = *it; + TRACE("datatype_util_bug", tout << "non_rec_constructor c: " << c->get_name() << "\n";); + unsigned num_args = c->get_arity(); + unsigned i = 0; + for (; i < num_args; i++) { + sort * T_i = c->get_domain(i); + TRACE("datatype_util_bug", tout << "c: " << c->get_name() << " i: " << i << " T_i: " << T_i->get_name() << "\n";); + if (!is_datatype(T_i)) { + TRACE("datatype_util_bug", tout << "T_i is not a datatype\n";); + continue; + } + if (std::find(forbidden_set.begin(), forbidden_set.end(), T_i) != forbidden_set.end()) { + TRACE("datatype_util_bug", tout << "T_i is in forbidden_set\n";); + break; + } + forbidden_set.push_back(T_i); + func_decl * nested_c = get_non_rec_constructor_core(T_i, forbidden_set); + SASSERT(forbidden_set.back() == T_i); + forbidden_set.pop_back(); + TRACE("datatype_util_bug", tout << "nested_c: " << nested_c->get_name() << "\n";); + if (nested_c == 0) + break; + } + if (i == num_args) + return c; + } + return 0; +} + +func_decl * datatype_util::get_constructor_recognizer(func_decl * constructor) { + SASSERT(is_constructor(constructor)); + func_decl * d = 0; + if (m_constructor2recognizer.find(constructor, d)) + return d; + sort * datatype = constructor->get_range(); + d = m_manager.mk_func_decl(m_family_id, OP_DT_RECOGNISER, 2, constructor->get_parameters(), 1, &datatype); + SASSERT(d); + m_asts.push_back(constructor); + m_asts.push_back(d); + m_constructor2recognizer.insert(constructor, d); + return d; +} + +ptr_vector const * datatype_util::get_constructor_accessors(func_decl * constructor) { + SASSERT(is_constructor(constructor)); + ptr_vector * res = 0; + if (m_constructor2accessors.find(constructor, res)) + return res; + res = alloc(ptr_vector); + m_asts.push_back(constructor); + m_vectors.push_back(res); + m_constructor2accessors.insert(constructor, res); + unsigned c_id = constructor->get_parameter(1).get_int(); + sort * datatype = constructor->get_range(); + unsigned tid = datatype->get_parameter(1).get_int(); + unsigned o = datatype->get_parameter(3 + 2*tid).get_int(); + unsigned k_i = datatype->get_parameter(o + c_id + 1).get_int(); + unsigned num_accessors = datatype->get_parameter(k_i+2).get_int(); + parameter p[3] = { parameter(datatype), parameter(c_id), parameter(-1) }; + for (unsigned r = 0; r < num_accessors; r++) { + p[2] = parameter(r); + func_decl * d = m_manager.mk_func_decl(m_family_id, OP_DT_ACCESSOR, 3, p, 1, &datatype); + SASSERT(d); + m_asts.push_back(d); + res->push_back(d); + } + return res; +} + +func_decl * datatype_util::get_accessor_constructor(func_decl * accessor) { + SASSERT(is_accessor(accessor)); + func_decl * r = 0; + if (m_accessor2constructor.find(accessor, r)) + return r; + sort * datatype = to_sort(accessor->get_parameter(0).get_ast()); + unsigned c_id = accessor->get_parameter(1).get_int(); + r = get_constructor(datatype, c_id); + m_accessor2constructor.insert(accessor, r); + m_asts.push_back(accessor); + m_asts.push_back(r); + return r; +} + +func_decl * datatype_util::get_recognizer_constructor(func_decl * recognizer) { + SASSERT(is_recognizer(recognizer)); + func_decl * r = 0; + if (m_recognizer2constructor.find(recognizer, r)) + return r; + sort * datatype = to_sort(recognizer->get_parameter(0).get_ast()); + unsigned c_id = recognizer->get_parameter(1).get_int(); + r = get_constructor(datatype, c_id); + m_recognizer2constructor.insert(recognizer, r); + m_asts.push_back(recognizer); + m_asts.push_back(r); + return r; +} + +bool datatype_util::is_recursive(sort * ty) { + SASSERT(is_datatype(ty)); + bool r = false; + if (m_is_recursive.find(ty, r)) + return r; + r = is_recursive_datatype(ty->get_parameters()); + m_is_recursive.insert(ty, r); + m_asts.push_back(ty); + return r; +} + +void datatype_util::reset() { + m_datatype2constructors.reset(); + m_datatype2nonrec_constructor.reset(); + m_constructor2accessors.reset(); + m_constructor2recognizer.reset(); + m_recognizer2constructor.reset(); + m_accessor2constructor.reset(); + m_is_recursive.reset(); + std::for_each(m_vectors.begin(), m_vectors.end(), delete_proc >()); + m_vectors.reset(); + m_asts.reset(); +} + +/** + \brief Two datatype sorts s1 and s2 are siblings if they were + defined together in the same mutually recursive definition. +*/ +bool datatype_util::are_siblings(sort * s1, sort * s2) { + SASSERT(is_datatype(s1)); + SASSERT(is_datatype(s2)); + if (s1 == s2) + return true; + if (s1->get_num_parameters() != s2->get_num_parameters()) + return false; + unsigned num_params = s1->get_num_parameters(); + if (s1->get_parameter(0) != s2->get_parameter(0)) + return false; + // position 1 contains the IDX of the datatype in a mutually recursive definition. + for (unsigned i = 2; i < num_params; i++) { + if (s1->get_parameter(i) != s2->get_parameter(i)) + return false; + } + return true; +} + +void datatype_util::display_datatype(sort *s0, std::ostream& strm) { + ast_mark mark; + ptr_buffer todo; + SASSERT(is_datatype(s0)); + strm << s0->get_name() << " where\n"; + todo.push_back(s0); + mark.mark(s0, true); + while (!todo.empty()) { + sort* s = todo.back(); + todo.pop_back(); + strm << s->get_name() << " =\n"; + + ptr_vector const * cnstrs = get_datatype_constructors(s); + for (unsigned i = 0; i < cnstrs->size(); ++i) { + func_decl* cns = (*cnstrs)[i]; + func_decl* rec = get_constructor_recognizer(cns); + strm << " " << cns->get_name() << " :: " << rec->get_name() << " :: "; + ptr_vector const * accs = get_constructor_accessors(cns); + for (unsigned j = 0; j < accs->size(); ++j) { + func_decl* acc = (*accs)[j]; + sort* s1 = acc->get_range(); + strm << "(" << acc->get_name() << ": " << s1->get_name() << ") "; + if (is_datatype(s1) && are_siblings(s1, s0) && !mark.is_marked(s1)) { + mark.mark(s1, true); + todo.push_back(s1); + } + } + strm << "\n"; + } + } + +} diff --git a/lib/datatype_decl_plugin.h b/lib/datatype_decl_plugin.h new file mode 100644 index 000000000..a8bd28d8e --- /dev/null +++ b/lib/datatype_decl_plugin.h @@ -0,0 +1,200 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + datatype_decl_plugin.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-09. + +Revision History: + +--*/ +#ifndef _DATATYPE_DECL_PLUGIN_H_ +#define _DATATYPE_DECL_PLUGIN_H_ + +#include"ast.h" +#include"tptr.h" +#include"buffer.h" +#include"obj_hashtable.h" + +enum datatype_sort_kind { + DATATYPE_SORT +}; + +enum datatype_op_kind { + OP_DT_CONSTRUCTOR, + OP_DT_RECOGNISER, + OP_DT_ACCESSOR, + LAST_DT_OP +}; + +/** + \brief Auxiliary class used to declare inductive datatypes. + It may be a sort or an integer. If it is an integer, + then it represents a reference to a recursive type. + + For example, consider the datatypes + Datatype + Tree = tree(value:Real, children:TreeList) + TreeList = cons_t(first_t:Tree, rest_t:Tree) + | nil_t + End + + The recursive occurrences of Tree and TreeList will have idx 0 and + 1 respectively. + + This is a transient value, it is only used to declare a set of + recursive datatypes. +*/ +class type_ref { + void * m_data; +public: + type_ref():m_data(TAG(void *, static_cast(0), 1)) {} + type_ref(int idx):m_data(BOXINT(void *, idx)) {} + type_ref(sort * s):m_data(TAG(void *, s, 1)) {} + + bool is_idx() const { return GET_TAG(m_data) == 0; } + bool is_sort() const { return GET_TAG(m_data) == 1; } + sort * get_sort() const { return UNTAG(sort *, m_data); } + int get_idx() const { return UNBOXINT(m_data); } +}; + +class accessor_decl; +class constructor_decl; +class datatype_decl; +class datatype_util; + +accessor_decl * mk_accessor_decl(symbol const & n, type_ref const & t); +void del_accessor_decl(accessor_decl * d); +void del_accessor_decls(unsigned num, accessor_decl * const * as); +// Remark: the constructor becomes the owner of the accessor_decls +constructor_decl * mk_constructor_decl(symbol const & n, symbol const & r, unsigned num_accessors, accessor_decl * const * acs); +void del_constructor_decl(constructor_decl * d); +void del_constructor_decls(unsigned num, constructor_decl * const * cs); +// Remark: the datatype becomes the owner of the constructor_decls +datatype_decl * mk_datatype_decl(symbol const & n, unsigned num_constructors, constructor_decl * const * cs); +void del_datatype_decl(datatype_decl * d); +void del_datatype_decls(unsigned num, datatype_decl * const * ds); + +class datatype_decl_plugin : public decl_plugin { + mutable scoped_ptr m_util; + datatype_util & get_util() const; +public: + datatype_decl_plugin() {} + + virtual ~datatype_decl_plugin(); + virtual void finalize(); + + virtual decl_plugin * mk_fresh() { return alloc(datatype_decl_plugin); } + + + /** + Contract for sort: + parameters[0] - (int) n - number of recursive types. + parameters[1] - (int) i - index 0..n-1 of which type is defined. + + for j in 0..n-1 + parameters[2 + 2*j] - (symbol) name of the type + parameters[2 + 2*j + 1] - (int) o - offset where the constructors are defined. + + for each offset o at parameters[2 + 2*j + 1] for some j in 0..n-1 + parameters[o] - (int) m - number of constructors + parameters[o+1] - (int) k_1 - offset for constructor definition + ... + parameters[o+m] - (int) k_m - offset ofr constructor definition + + for each offset k_i at parameters[o+s] for some s in 0..m-1 + parameters[k_i] - (symbol) name of the constructor + parameters[k_i+1] - (symbol) name of the recognizer + parameters[k_i+2] - (int) m' - number of accessors + parameters[k_i+3+2*r] - (symbol) name of the r accessor + parameters[k_i+3+2*r+1] - (int or type_ast) type of the accessor. If integer, then the value must be in [0..n-1], and it + represents an reference to the recursive type. + + The idea with the additional offsets is that + access to relevant constructors and types can be performed using + a few address calculations. + */ + virtual sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters); + + /** + Contract for constructors + parameters[0] - (ast) datatype ast. + parmaeters[1] - (int) constructor idx. + Contract for accessors + parameters[0] - (ast) datatype ast. + parameters[1] - (int) constructor idx. + parameters[2] - (int) accessor idx. + Contract for tester + parameters[0] - (ast) datatype ast. + parameters[1] - (int) constructor idx. + */ + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + + bool mk_datatypes(unsigned num_datatypes, datatype_decl * const * datatypes, sort_ref_vector & new_sorts); + + virtual expr * get_some_value(sort * s); + + virtual bool is_fully_interp(sort const * s) const; + + virtual bool is_value(app* e) const; +private: + bool is_value_visit(expr * arg, ptr_buffer & todo) const; +}; + +class datatype_util { + ast_manager & m_manager; + family_id m_family_id; + + func_decl * get_constructor(sort * ty, unsigned c_id) const; + + obj_map *> m_datatype2constructors; + obj_map m_datatype2nonrec_constructor; + obj_map *> m_constructor2accessors; + obj_map m_constructor2recognizer; + obj_map m_recognizer2constructor; + obj_map m_accessor2constructor; + obj_map m_is_recursive; + ast_ref_vector m_asts; + ptr_vector > m_vectors; + + func_decl * get_constructor(sort * ty, unsigned c_id); + func_decl * get_non_rec_constructor_core(sort * ty, ptr_vector & forbidden_set); + +public: + datatype_util(ast_manager & m); + ~datatype_util(); + ast_manager & get_manager() const { return m_manager; } + bool is_datatype(sort * s) const { return is_sort_of(s, m_family_id, DATATYPE_SORT); } + bool is_recursive(sort * ty); + bool is_constructor(func_decl * f) const { return is_decl_of(f, m_family_id, OP_DT_CONSTRUCTOR); } + bool is_recognizer(func_decl * f) const { return is_decl_of(f, m_family_id, OP_DT_RECOGNISER); } + bool is_accessor(func_decl * f) const { return is_decl_of(f, m_family_id, OP_DT_ACCESSOR); } + bool is_constructor(app * f) const { return is_app_of(f, m_family_id, OP_DT_CONSTRUCTOR); } + bool is_recognizer(app * f) const { return is_app_of(f, m_family_id, OP_DT_RECOGNISER); } + bool is_accessor(app * f) const { return is_app_of(f, m_family_id, OP_DT_ACCESSOR); } + ptr_vector const * get_datatype_constructors(sort * ty); + unsigned get_datatype_num_constructors(sort * ty) { return get_datatype_constructors(ty)->size(); } + unsigned get_constructor_idx(func_decl * f) const { SASSERT(is_constructor(f)); return f->get_parameter(1).get_int(); } + unsigned get_recognizer_constructor_idx(func_decl * f) const { SASSERT(is_recognizer(f)); return f->get_parameter(1).get_int(); } + func_decl * get_non_rec_constructor(sort * ty); + func_decl * get_constructor_recognizer(func_decl * constructor); + ptr_vector const * get_constructor_accessors(func_decl * constructor); + func_decl * get_accessor_constructor(func_decl * accessor); + func_decl * get_recognizer_constructor(func_decl * recognizer); + family_id get_family_id() const { return m_family_id; } + bool are_siblings(sort * s1, sort * s2); + void reset(); + void display_datatype(sort *s, std::ostream& strm); +}; + +#endif /* _DATATYPE_DECL_PLUGIN_H_ */ + diff --git a/lib/datatype_factory.cpp b/lib/datatype_factory.cpp new file mode 100644 index 000000000..3312b046a --- /dev/null +++ b/lib/datatype_factory.cpp @@ -0,0 +1,214 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + datatype_factory.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-11-06. + +Revision History: + +--*/ +#include"datatype_factory.h" +#include"proto_model.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" + +datatype_factory::datatype_factory(ast_manager & m, proto_model & md): + struct_factory(m, m.get_family_id("datatype"), md), + m_util(m) { +} + +expr * datatype_factory::get_some_value(sort * s) { + value_set * set = 0; + if (m_sort2value_set.find(s, set) && !set->empty()) + return *(set->begin()); + func_decl * c = m_util.get_non_rec_constructor(s); + ptr_vector args; + unsigned num = c->get_arity(); + for (unsigned i = 0; i < num; i++) { + args.push_back(m_model.get_some_value(c->get_domain(i))); + } + expr * r = m_manager.mk_app(c, args.size(), args.c_ptr()); + register_value(r); + return r; +} + +/** + \brief Return the last fresh (or almost) fresh value of sort s. +*/ +expr * datatype_factory::get_last_fresh_value(sort * s) { + expr * val = 0; + if (m_last_fresh_value.find(s, val)) + return val; + value_set * set = get_value_set(s); + if (set->empty()) + val = get_some_value(s); + else + val = *(set->begin()); + if (m_util.is_recursive(s)) + m_last_fresh_value.insert(s, val); + return val; +} + +/** + \brief Create an almost fresh value. If s is recursive, then the result is not 0. + It also updates m_last_fresh_value +*/ +expr * datatype_factory::get_almost_fresh_value(sort * s) { + value_set * set = get_value_set(s); + if (set->empty()) { + expr * val = get_some_value(s); + if (m_util.is_recursive(s)) + m_last_fresh_value.insert(s, val); + 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 + // use get_last_fresh_value. + ptr_vector const * constructors = m_util.get_datatype_constructors(s); + ptr_vector::const_iterator it = constructors->begin(); + ptr_vector::const_iterator end = constructors->end(); + for (; it != end; ++it) { + func_decl * constructor = *it; + expr_ref_vector args(m_manager); + bool found_fresh_arg = false; + bool recursive = false; + unsigned num = constructor->get_arity(); + for (unsigned i = 0; i < num; i++) { + sort * s_arg = constructor->get_domain(i); + if (!found_fresh_arg && (!m_util.is_datatype(s_arg) || !m_util.are_siblings(s, s_arg))) { + expr * new_arg = m_model.get_fresh_value(s_arg); + if (new_arg != 0) { + found_fresh_arg = true; + args.push_back(new_arg); + continue; + } + } + if (!found_fresh_arg && m_util.is_datatype(s_arg) && m_util.are_siblings(s, s_arg)) { + recursive = true; + expr * last_fresh = get_last_fresh_value(s_arg); + args.push_back(last_fresh); + } + else { + expr * some_arg = m_model.get_some_value(s_arg); + args.push_back(some_arg); + } + } + if (recursive || found_fresh_arg) { + expr * new_value = m_manager.mk_app(constructor, args.size(), args.c_ptr()); + SASSERT(!found_fresh_arg || !set->contains(new_value)); + register_value(new_value); + if (m_util.is_recursive(s)) + m_last_fresh_value.insert(s, new_value); + return new_value; + } + } + SASSERT(!m_util.is_recursive(s)); + return 0; +} + + +expr * datatype_factory::get_fresh_value(sort * s) { + TRACE("datatype_factory", tout << "generating fresh value for: " << s->get_name() << "\n";); + value_set * set = get_value_set(s); + // Approach 0) + // if no value for s was generated so far, then used get_some_value + if (set->empty()) { + expr * val = get_some_value(s); + if (m_util.is_recursive(s)) + m_last_fresh_value.insert(s, val); + TRACE("datatype_factory", tout << "0. result: " << mk_pp(val, m_manager) << "\n";); + return val; + } + // Approach 1) + // Traverse constructors, and try to invoke get_fresh_value of one of the + // arguments (if the argument is not a sibling datatype of s). + // Two datatypes are siblings if they were defined together in the same mutually recursive definition. + ptr_vector const * constructors = m_util.get_datatype_constructors(s); + ptr_vector::const_iterator it = constructors->begin(); + ptr_vector::const_iterator end = constructors->end(); + for (; it != end; ++it) { + func_decl * constructor = *it; + expr_ref_vector args(m_manager); + bool found_fresh_arg = false; + unsigned num = constructor->get_arity(); + for (unsigned i = 0; i < num; i++) { + sort * s_arg = constructor->get_domain(i); + if (!found_fresh_arg && (!m_util.is_recursive(s) || !m_util.is_datatype(s_arg) || !m_util.are_siblings(s, s_arg))) { + expr * new_arg = m_model.get_fresh_value(s_arg); + if (new_arg != 0) { + found_fresh_arg = true; + args.push_back(new_arg); + continue; + } + } + expr * some_arg = m_model.get_some_value(s_arg); + args.push_back(some_arg); + } + expr_ref new_value(m_manager); + new_value = m_manager.mk_app(constructor, args.size(), args.c_ptr()); + CTRACE("datatype_factory", found_fresh_arg && set->contains(new_value), tout << mk_pp(new_value, m_manager) << "\n";); + SASSERT(!found_fresh_arg || !set->contains(new_value)); + if (!set->contains(new_value)) { + register_value(new_value); + if (m_util.is_recursive(s)) + m_last_fresh_value.insert(s, new_value); + TRACE("datatype_factory", tout << "1. result: " << mk_pp(new_value, m_manager) << "\n";); + return new_value; + } + } + // Approach 2) + // For recursive datatypes. + // search for constructor... + if (m_util.is_recursive(s)) { + while(true) { + TRACE("datatype_factory", tout << mk_pp(get_last_fresh_value(s), m_manager) << "\n";); + ptr_vector const * constructors = m_util.get_datatype_constructors(s); + ptr_vector::const_iterator it = constructors->begin(); + ptr_vector::const_iterator end = constructors->end(); + for (; it != end; ++it) { + func_decl * constructor = *it; + expr_ref_vector args(m_manager); + bool found_sibling = false; + unsigned num = constructor->get_arity(); + for (unsigned i = 0; i < num; i++) { + sort * s_arg = constructor->get_domain(i); + if (!found_sibling && m_util.is_datatype(s_arg) && m_util.are_siblings(s, s_arg)) { + found_sibling = true; + expr * maybe_new_arg = get_almost_fresh_value(s_arg); + args.push_back(maybe_new_arg); + } + else { + expr * some_arg = m_model.get_some_value(s_arg); + args.push_back(some_arg); + } + } + if (found_sibling) { + expr_ref new_value(m_manager); + new_value = m_manager.mk_app(constructor, args.size(), args.c_ptr()); + m_last_fresh_value.insert(s, new_value); + if (!set->contains(new_value)) { + register_value(new_value); + TRACE("datatype_factory", tout << "2. result: " << mk_pp(new_value, m_manager) << "\n";); + return new_value; + } + } + } + } + } + // Approach 3) + // for non-recursive datatypes. + // Search for value that was not created before. + SASSERT(!m_util.is_recursive(s)); + + return 0; +} + diff --git a/lib/datatype_factory.h b/lib/datatype_factory.h new file mode 100644 index 000000000..e8e1f2589 --- /dev/null +++ b/lib/datatype_factory.h @@ -0,0 +1,40 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + datatype_factory.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-11-06. + +Revision History: + +--*/ +#ifndef _DATATYPE_FACTORY_H_ +#define _DATATYPE_FACTORY_H_ + +#include"struct_factory.h" +#include"datatype_decl_plugin.h" + +class datatype_factory : public struct_factory { + datatype_util m_util; + obj_map m_last_fresh_value; + + expr * get_last_fresh_value(sort * s); + expr * get_almost_fresh_value(sort * s); + +public: + datatype_factory(ast_manager & m, proto_model & md); + virtual ~datatype_factory() {} + virtual expr * get_some_value(sort * s); + virtual expr * get_fresh_value(sort * s); +}; + +#endif /* _DATATYPE_FACTORY_H_ */ + diff --git a/lib/datatype_rewriter.cpp b/lib/datatype_rewriter.cpp new file mode 100644 index 000000000..8c55ba498 --- /dev/null +++ b/lib/datatype_rewriter.cpp @@ -0,0 +1,111 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + datatype_rewriter.cpp + +Abstract: + + Basic rewriting rules for Datatypes. + +Author: + + Leonardo (leonardo) 2011-04-06 + +Notes: + +--*/ +#include"datatype_rewriter.h" + +br_status datatype_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(f->get_family_id() == get_fid()); + switch(f->get_decl_kind()) { + case OP_DT_CONSTRUCTOR: return BR_FAILED; + case OP_DT_RECOGNISER: + // + // simplify is_cons(cons(x,y)) -> true + // simplify is_cons(nil) -> false + // + SASSERT(num_args == 1); + 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)) + result = m().mk_true(); + else + result = m().mk_false(); + return BR_DONE; + case OP_DT_ACCESSOR: { + // + // simplify head(cons(x,y)) -> x + // + SASSERT(num_args == 1); + if (!is_app(args[0]) || !m_util.is_constructor(to_app(args[0]))) + return BR_FAILED; + + app * a = to_app(args[0]); + func_decl * c_decl = a->get_decl(); + if (c_decl != m_util.get_accessor_constructor(f)) + return BR_FAILED; + ptr_vector const * acc = m_util.get_constructor_accessors(c_decl); + SASSERT(acc && acc->size() == a->get_num_args()); + unsigned num = acc->size(); + for (unsigned i = 0; i < num; ++i) { + if (f == (*acc)[i]) { + // found it. + result = a->get_arg(i); + return BR_DONE; + } + } + UNREACHABLE(); + break; + } + default: + UNREACHABLE(); + } + + return BR_FAILED; +} + +br_status datatype_rewriter::mk_eq_core(expr * lhs, expr * rhs, expr_ref & result) { + if (!is_app(lhs) || !is_app(rhs) || !m_util.is_constructor(to_app(lhs)) || !m_util.is_constructor(to_app(rhs))) + return BR_FAILED; + if (to_app(lhs)->get_decl() != to_app(rhs)->get_decl()) { + result = m().mk_false(); + return BR_DONE; + } + + // Remark: In datatype_simplifier_plugin, we used + // m_basic_simplifier to create '=' and 'and' applications in the + // following code. This trick not guarantee that the final expression + // will be fully simplified. + // + // Example: + // The assertion + // (assert (= (cons a1 (cons a2 (cons a3 (cons (+ a4 1) (cons (+ a5 c5) (cons a6 nil)))))) + // (cons b1 (cons b2 (cons b3 (cons b4 (cons b5 (cons b6 nil)))))))) + // + // After applying asserted_formulas::reduce(), the following formula was generated. + // + // (= a1 b1) + // (= a2 b2) + // (= a3 b3) + // (= (+ a4 (* (- 1) b4)) (- 1)) + // (= (+ c5 a5) b5) <<< NOT SIMPLIFIED WITH RESPECT TO ARITHMETIC + // (= (cons a6 nil) (cons b6 nil))) <<< NOT SIMPLIFIED WITH RESPECT TO DATATYPE theory + // + // Note that asserted_formulas::reduce() applied the simplier many times. + // After the first simplification step we had: + // (= a1 b1) + // (= (cons a2 (cons a3 (cons (+ a4 1) (cons (+ a5 c5) (cons a6 nil)))))) + // (cons b2 (cons b3 (cons b4 (cons b5 (cons b6 nil)))))) + + ptr_buffer eqs; + unsigned num = to_app(lhs)->get_num_args(); + SASSERT(num == to_app(rhs)->get_num_args()); + for (unsigned i = 0; i < num; ++i) { + eqs.push_back(m().mk_eq(to_app(lhs)->get_arg(i), to_app(rhs)->get_arg(i))); + } + result = m().mk_and(eqs.size(), eqs.c_ptr()); + return BR_REWRITE2; +} diff --git a/lib/datatype_rewriter.h b/lib/datatype_rewriter.h new file mode 100644 index 000000000..46663a6d8 --- /dev/null +++ b/lib/datatype_rewriter.h @@ -0,0 +1,35 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + datatype_rewriter.h + +Abstract: + + Basic rewriting rules for Datatypes. + +Author: + + Leonardo (leonardo) 2011-04-06 + +Notes: + +--*/ +#ifndef _DATATYPE_REWRITER_H_ +#define _DATATYPE_REWRITER_H_ + +#include"datatype_decl_plugin.h" +#include"rewriter_types.h" + +class datatype_rewriter { + datatype_util m_util; +public: + datatype_rewriter(ast_manager & m):m_util(m) {} + ast_manager & m() const { return m_util.get_manager(); } + family_id get_fid() const { return m_util.get_family_id(); } + br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_eq_core(expr * lhs, expr * rhs, expr_ref & result); +}; + +#endif diff --git a/lib/datatype_simplifier_plugin.cpp b/lib/datatype_simplifier_plugin.cpp new file mode 100644 index 000000000..129c0e34b --- /dev/null +++ b/lib/datatype_simplifier_plugin.cpp @@ -0,0 +1,113 @@ +/*++ +Copyright (c) 2008 Microsoft Corporation + +Module Name: + + datatype_simplifier_plugin.cpp + +Abstract: + + Simplifier for algebraic datatypes. + +Author: + + nbjorner 2008-11-6 + +--*/ + +#include"datatype_simplifier_plugin.h" + +datatype_simplifier_plugin::datatype_simplifier_plugin(ast_manager & m, basic_simplifier_plugin & b): + simplifier_plugin(symbol("datatype"), m), + m_util(m), + m_bsimp(b) { +} + +datatype_simplifier_plugin::~datatype_simplifier_plugin() { +} + +bool datatype_simplifier_plugin::reduce(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + set_reduce_invoked(); + + SASSERT(f->get_family_id() == get_family_id()); + switch(f->get_decl_kind()) { + case OP_DT_CONSTRUCTOR: { + return false; + } + case OP_DT_RECOGNISER: { + // + // simplify is_cons(cons(x,y)) -> true + // simplify is_cons(nil) -> false + // + SASSERT(num_args == 1); + + if (!is_app_of(args[0], get_family_id(), OP_DT_CONSTRUCTOR)) { + return false; + } + app* a = to_app(args[0]); + func_decl* f1 = a->get_decl(); + func_decl* f2 = m_util.get_recognizer_constructor(f); + if (f1 == f2) { + result = m_manager.mk_true(); + } + else { + result = m_manager.mk_false(); + } + return true; + } + case OP_DT_ACCESSOR: { + // + // simplify head(cons(x,y)) -> x + // + SASSERT(num_args == 1); + + if (!is_app_of(args[0], get_family_id(), OP_DT_CONSTRUCTOR)) { + return false; + } + app* a = to_app(args[0]); + func_decl* f1 = a->get_decl(); + func_decl* f2 = m_util.get_accessor_constructor(f); + if (f1 != f2) { + return false; + } + ptr_vector const* acc = m_util.get_constructor_accessors(f1); + SASSERT(acc && acc->size() == a->get_num_args()); + for (unsigned i = 0; i < acc->size(); ++i) { + if (f == (*acc)[i]) { + // found it. + result = a->get_arg(i); + return true; + } + } + UNREACHABLE(); + } + default: + UNREACHABLE(); + } + + return false; +} + +bool datatype_simplifier_plugin::reduce_eq(expr * lhs, expr * rhs, expr_ref & result) { + set_reduce_invoked(); + if (is_app_of(lhs, get_family_id(), OP_DT_CONSTRUCTOR) && + is_app_of(rhs, get_family_id(), OP_DT_CONSTRUCTOR)) { + app* a = to_app(lhs); + app* b = to_app(rhs); + if (a->get_decl() != b->get_decl()) { + result = m_manager.mk_false(); + return true; + } + expr_ref_vector eqs(m_manager); + for (unsigned i = 0; i < a->get_num_args(); ++i) { + m_bsimp.mk_eq(a->get_arg(i),b->get_arg(i), result); + eqs.push_back(result); + } + m_bsimp.mk_and(eqs.size(), eqs.c_ptr(), result); + return true; + } + // TBD: occurs check, constructor check. + + return false; +} + diff --git a/lib/datatype_simplifier_plugin.h b/lib/datatype_simplifier_plugin.h new file mode 100644 index 000000000..2a0de8aae --- /dev/null +++ b/lib/datatype_simplifier_plugin.h @@ -0,0 +1,42 @@ +/*++ +Copyright (c) 2008 Microsoft Corporation + +Module Name: + + datatype_simplifier_plugin.h + +Abstract: + + Simplifier for algebraic datatypes. + +Author: + + nbjorner 2008-11-6 + +--*/ +#ifndef _DATATYPE_SIMPLIFIER_PLUGIN_H_ +#define _DATATYPE_SIMPLIFIER_PLUGIN_H_ + +#include"basic_simplifier_plugin.h" +#include"datatype_decl_plugin.h" + +/** + \brief Simplifier for the arith family. +*/ +class datatype_simplifier_plugin : public simplifier_plugin { + datatype_util m_util; + basic_simplifier_plugin & m_bsimp; + + +public: + datatype_simplifier_plugin(ast_manager & m, basic_simplifier_plugin & b); + ~datatype_simplifier_plugin(); + + + virtual bool reduce(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + + virtual bool reduce_eq(expr * lhs, expr * rhs, expr_ref & result); + +}; + +#endif /* _DATATYPE_SIMPLIFIER_PLUGIN_H_ */ diff --git a/lib/dbg_cmds.cpp b/lib/dbg_cmds.cpp new file mode 100644 index 000000000..c473f6448 --- /dev/null +++ b/lib/dbg_cmds.cpp @@ -0,0 +1,383 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + dbg_cmds.cpp + +Abstract: + SMT2 front-end commands for debugging purposes. + +Author: + + Leonardo (leonardo) 2011-04-01 + +Notes: + +--*/ +#include +#include"cmd_context.h" +#include"cmd_util.h" +#include"rewriter.h" +#include"shared_occs.h" +#include"for_each_expr.h" +#include"rewriter.h" +#include"bool_rewriter.h" +#include"ast_lt.h" +#include"simplify_cmd.h" +#include"ast_smt2_pp.h" +#include"bound_manager.h" +#include"used_vars.h" +#include"var_subst.h" + +#ifndef _EXTERNAL_RELEASE + +BINARY_SYM_CMD(get_quantifier_body_cmd, + "dbg-get-qbody", + " ", + "store the body of the quantifier in the global variable ", + CPK_EXPR, + expr *, { + if (!is_quantifier(arg)) + throw cmd_exception("invalid command, term must be a quantifier"); + store_expr_ref(ctx, m_sym, to_quantifier(arg)->get_expr()); +}); + +BINARY_SYM_CMD(set_cmd, + "dbg-set", + " ", + "store in the global variable ", + CPK_EXPR, + expr *, { + store_expr_ref(ctx, m_sym, arg); +}); + + +UNARY_CMD(pp_var_cmd, "dbg-pp-var", "", "pretty print a global variable that references an AST", CPK_SYMBOL, symbol const &, { + expr * t = get_expr_ref(ctx, arg); + SASSERT(t != 0); + ctx.display(ctx.regular_stream(), t); + ctx.regular_stream() << std::endl; +}); + +BINARY_SYM_CMD(shift_vars_cmd, + "dbg-shift-vars", + " ", + "shift the free variables by in the term referenced by the global variable , the result is stored in ", + CPK_UINT, + unsigned, { + expr * t = get_expr_ref(ctx, m_sym); + expr_ref r(ctx.m()); + var_shifter s(ctx.m()); + s(t, arg, r); + store_expr_ref(ctx, m_sym, r.get()); +}); + +UNARY_CMD(pp_shared_cmd, "dbg-pp-shared", "", "display shared subterms of the given term", CPK_EXPR, expr *, { + shared_occs s(ctx.m()); + s(arg); + ctx.regular_stream() << "(shared"; + shared_occs::iterator it = s.begin_shared(); + shared_occs::iterator end = s.end_shared(); + for (; it != end; ++it) { + expr * curr = *it; + ctx.regular_stream() << std::endl << " "; + ctx.display(ctx.regular_stream(), curr, 2); + } + ctx.regular_stream() << ")" << std::endl; +}); + +UNARY_CMD(num_shared_cmd, "dbg-num-shared", "", "return the number of shared subterms", CPK_EXPR, expr *, { + shared_occs s(ctx.m()); + s(arg); + ctx.regular_stream() << s.num_shared() << std::endl; +}); + +UNARY_CMD(size_cmd, "dbg-size", "", "return the size of the given term", CPK_EXPR, expr *, { + ctx.regular_stream() << get_num_exprs(arg) << std::endl; +}); + +class subst_cmd : public cmd { + unsigned m_idx; + expr * m_source; + symbol m_target; + ptr_vector m_subst; +public: + subst_cmd():cmd("dbg-subst") {} + virtual char const * get_usage() const { return " (*) "; } + virtual char const * get_descr() const { return "substitute the free variables in the AST referenced by using the ASTs referenced by *"; } + virtual unsigned get_arity() const { return 3; } + virtual void prepare(cmd_context & ctx) { m_idx = 0; m_source = 0; } + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { + if (m_idx == 1) return CPK_SYMBOL_LIST; + return CPK_SYMBOL; + } + virtual void set_next_arg(cmd_context & ctx, symbol const & s) { + if (m_idx == 0) { + m_source = get_expr_ref(ctx, s); + } + else { + m_target = s; + } + m_idx++; + } + virtual void set_next_arg(cmd_context & ctx, unsigned num, symbol const * s) { + m_subst.reset(); + unsigned i = num; + while (i > 0) { + --i; + m_subst.push_back(get_expr_ref(ctx, s[i])); + } + m_idx++; + } + virtual void execute(cmd_context & ctx) { + expr_ref r(ctx.m()); + beta_reducer p(ctx.m()); + p(m_source, m_subst.size(), m_subst.c_ptr(), r); + store_expr_ref(ctx, m_target, r.get()); + } +}; + +UNARY_CMD(bool_rewriter_cmd, "dbg-bool-rewriter", "", "apply the Boolean rewriter to the given term", CPK_EXPR, expr *, { + expr_ref t(ctx.m()); + params_ref p; + p.set_bool(":flat", false); + SASSERT(p.get_bool(":flat", true) == false); + bool_rewriter_star r(ctx.m(), p); + r(arg, t); + ctx.display(ctx.regular_stream(), t); + ctx.regular_stream() << std::endl; +}); + +UNARY_CMD(bool_frewriter_cmd, "dbg-bool-flat-rewriter", "", "apply the Boolean (flattening) rewriter to the given term", CPK_EXPR, expr *, { + expr_ref t(ctx.m()); + { + params_ref p; + p.set_bool(":flat", true); + bool_rewriter_star r(ctx.m(), p); + r(arg, t); + } + ctx.display(ctx.regular_stream(), t); + ctx.regular_stream() << std::endl; +}); + +UNARY_CMD(elim_and_cmd, "dbg-elim-and", "", "apply the Boolean rewriter (eliminating AND operator and flattening) to the given term", CPK_EXPR, expr *, { + expr_ref t(ctx.m()); + { + params_ref p; + p.set_bool(":flat", true); + p.set_bool(":elim-and", true); + bool_rewriter_star r(ctx.m(), p); + r(arg, t); + } + ctx.display(ctx.regular_stream(), t); + ctx.regular_stream() << std::endl; +}); + +class lt_cmd : public cmd { + expr * m_t1; + expr * m_t2; +public: + lt_cmd():cmd("dbg-lt") {} + virtual char const * get_usage() const { return " "; } + virtual char const * get_descr(cmd_context & ctx) const { return "return true if the first term is smaller than the second one in the internal Z3 total order on terms."; } + virtual unsigned get_arity() const { return 2; } + virtual void prepare(cmd_context & ctx) { m_t1 = 0; } + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { return CPK_EXPR; } + virtual void set_next_arg(cmd_context & ctx, expr * arg) { + if (m_t1 == 0) + m_t1 = arg; + else + m_t2 = arg; + } + virtual void execute(cmd_context & ctx) { + bool r = lt(m_t1, m_t2); + ctx.regular_stream() << (r ? "true" : "false") << std::endl; + } +}; + + +UNARY_CMD(some_value_cmd, "dbg-some-value", "", "retrieve some value of the given sort", CPK_SORT, sort *, { + ast_manager & m = ctx.m(); + expr_ref val(m); + val = m.get_some_value(arg); + ctx.display(ctx.regular_stream(), val); + ctx.regular_stream() << std::endl; +}); + +void tst_params(cmd_context & ctx) { + params_ref p1; + params_ref p2; + p1.set_uint(":val", 100); + p2 = p1; + SASSERT(p2.get_uint(":val", 0) == 100); + p2.set_uint(":val", 200); + SASSERT(p2.get_uint(":val", 0) == 200); + SASSERT(p1.get_uint(":val", 0) == 100); + p2 = p1; + SASSERT(p2.get_uint(":val", 0) == 100); + SASSERT(p1.get_uint(":val", 0) == 100); + ctx.regular_stream() << "worked" << std::endl; +} + +ATOMIC_CMD(params_cmd, "dbg-params", "test parameters", tst_params(ctx);); + + +UNARY_CMD(translator_cmd, "dbg-translator", "", "test AST translator", CPK_EXPR, expr *, { + ast_manager & m = ctx.m(); + scoped_ptr m2 = alloc(ast_manager, m.proof_mode()); + ast_translation proc(m, *m2); + expr_ref s(m); + expr_ref t(*m2); + s = arg; + t = proc(s.get()); + ctx.regular_stream() << mk_ismt2_pp(s, m) << "\n--->\n" << mk_ismt2_pp(t, *m2) << std::endl; +}); + +UNARY_CMD(sexpr_cmd, "dbg-sexpr", "", "display an s-expr", CPK_SEXPR, sexpr *, { + arg->display(ctx.regular_stream()); + ctx.regular_stream() << std::endl; +}); + +static void tst_bound_manager(cmd_context & ctx) { + ast_manager & m = ctx.m(); + assertion_set s(m); + assert_exprs_from(ctx, s); + bound_manager bc(m); + bc(s); + bc.display(ctx.regular_stream()); +} + +#define GUARDED_CODE(CODE) try { CODE } catch (z3_error & ex) { throw ex; } catch (z3_exception & ex) { ctx.regular_stream() << "(error \"" << escaped(ex.msg()) << "\")" << std::endl; } + +ATOMIC_CMD(bounds_cmd, "dbg-bounds", "test bound manager", GUARDED_CODE(tst_bound_manager(ctx);)); + +UNARY_CMD(set_next_id, "dbg-set-next-id", "", "set the next expression id to be at least the given value", CPK_UINT, unsigned, { + ctx.m().set_next_expr_id(arg); +}); + + +UNARY_CMD(used_vars_cmd, "dbg-used-vars", "", "test used_vars functor", CPK_EXPR, expr *, { + used_vars proc; + if (is_quantifier(arg)) + arg = to_quantifier(arg)->get_expr(); + proc(arg); + ctx.regular_stream() << "(vars"; + for (unsigned i = 0; i < proc.get_max_found_var_idx_plus_1(); i++) { + sort * s = proc.get(i); + ctx.regular_stream() << "\n (" << std::left << std::setw(6) << i << " "; + if (s != 0) + ctx.display(ctx.regular_stream(), s, 10); + else + ctx.regular_stream() << ""; + ctx.regular_stream() << ")"; + } + ctx.regular_stream() << ")" << std::endl; +}); + +UNARY_CMD(elim_unused_vars_cmd, "dbg-elim-unused-vars", "", "eliminate unused vars from a quantifier", CPK_EXPR, expr *, { + if (!is_quantifier(arg)) { + ctx.display(ctx.regular_stream(), arg); + return; + } + expr_ref r(ctx.m()); + elim_unused_vars(ctx.m(), to_quantifier(arg), r); + SASSERT(!is_quantifier(r) || !to_quantifier(r)->may_have_unused_vars()); + ctx.display(ctx.regular_stream(), r); + ctx.regular_stream() << std::endl; +}); + +class instantiate_cmd_core : public cmd { +protected: + quantifier * m_q; + ptr_vector m_args; +public: + instantiate_cmd_core(char const * name):cmd(name) {} + virtual char const * get_usage() const { return " (*)"; } + virtual char const * get_descr() const { return "instantiate the quantifier using the given expressions."; } + virtual unsigned get_arity() const { return 2; } + virtual void prepare(cmd_context & ctx) { m_q = 0; m_args.reset(); } + + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { + if (m_q == 0) return CPK_EXPR; + else return CPK_EXPR_LIST; + } + + virtual void set_next_arg(cmd_context & ctx, expr * s) { + if (!is_quantifier(s)) + throw cmd_exception("invalid command, quantifier expected."); + m_q = to_quantifier(s); + } + + virtual void set_next_arg(cmd_context & ctx, unsigned num, expr * const * ts) { + if (num != m_q->get_num_decls()) + throw cmd_exception("invalid command, mismatch between the number of quantified variables and the number of arguments."); + unsigned i = num; + while (i > 0) { + --i; + sort * s = ctx.m().get_sort(ts[i]); + if (s != m_q->get_decl_sort(i)) { + std::ostringstream buffer; + buffer << "invalid command, sort mismatch at position " << i; + throw cmd_exception(buffer.str()); + } + } + m_args.append(num, ts); + } + + virtual void execute(cmd_context & ctx) { + expr_ref r(ctx.m()); + instantiate(ctx.m(), m_q, m_args.c_ptr(), r); + ctx.display(ctx.regular_stream(), r); + ctx.regular_stream() << std::endl; + } +}; + +class instantiate_cmd : public instantiate_cmd_core { +public: + instantiate_cmd():instantiate_cmd_core("dbg-instantiate") {} +}; + +class instantiate_nested_cmd : public instantiate_cmd_core { +public: + instantiate_nested_cmd():instantiate_cmd_core("dbg-instantiate-nested") {} + + virtual char const * get_descr() const { return "instantiate the quantifier nested in the outermost quantifier, this command is used to test the instantiation procedure with quantifiers that contain free variables."; } + + virtual void set_next_arg(cmd_context & ctx, expr * s) { + instantiate_cmd_core::set_next_arg(ctx, s); + if (!is_quantifier(m_q->get_expr())) + throw cmd_exception("invalid command, nested quantifier expected"); + m_q = to_quantifier(m_q->get_expr()); + } +}; + +#endif + +void install_dbg_cmds(cmd_context & ctx) { +#ifndef _EXTERNAL_RELEASE + ctx.insert(alloc(get_quantifier_body_cmd)); + ctx.insert(alloc(set_cmd)); + ctx.insert(alloc(pp_var_cmd)); + ctx.insert(alloc(shift_vars_cmd)); + ctx.insert(alloc(pp_shared_cmd)); + ctx.insert(alloc(num_shared_cmd)); + ctx.insert(alloc(size_cmd)); + ctx.insert(alloc(subst_cmd)); + ctx.insert(alloc(bool_rewriter_cmd)); + ctx.insert(alloc(bool_frewriter_cmd)); + ctx.insert(alloc(elim_and_cmd)); + install_simplify_cmd(ctx, "dbg-th-rewriter"); + ctx.insert(alloc(lt_cmd)); + ctx.insert(alloc(some_value_cmd)); + ctx.insert(alloc(params_cmd)); + ctx.insert(alloc(translator_cmd)); + ctx.insert(alloc(sexpr_cmd)); + ctx.insert(alloc(bounds_cmd)); + ctx.insert(alloc(used_vars_cmd)); + ctx.insert(alloc(elim_unused_vars_cmd)); + ctx.insert(alloc(instantiate_cmd)); + ctx.insert(alloc(instantiate_nested_cmd)); + ctx.insert(alloc(set_next_id)); +#endif +} diff --git a/lib/dbg_cmds.h b/lib/dbg_cmds.h new file mode 100644 index 000000000..8a992a4b8 --- /dev/null +++ b/lib/dbg_cmds.h @@ -0,0 +1,25 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + dbg_cmds.h + +Abstract: + SMT2 front-end commands for debugging purposes. + +Author: + + Leonardo (leonardo) 2011-04-01 + +Notes: + +--*/ +#ifndef _DBG_CMDS_H_ +#define _DBG_CMDS_H_ + +class cmd_context; + +void install_dbg_cmds(cmd_context & ctx); + +#endif diff --git a/lib/debug.cpp b/lib/debug.cpp new file mode 100644 index 000000000..bb4d3970d --- /dev/null +++ b/lib/debug.cpp @@ -0,0 +1,107 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + debug.cpp + +Abstract: + + Basic debugging support. + +Author: + + Leonardo de Moura (leonardo) 2006-09-11. + +Revision History: + +--*/ +#include +#include +#include"str_hashtable.h" + +volatile bool g_enable_assertions = true; + +void enable_assertions(bool f) { + g_enable_assertions = f; +} + +bool assertions_enabled() { + return g_enable_assertions; +} + +void notify_assertion_violation(const char * fileName, int line, const char * condition) { + std::cerr << "ASSERTION VIOLATION\n"; + std::cerr << "File: " << fileName << "\n"; + std::cerr << "Line: " << line << "\n"; + std::cerr << condition << "\n"; +} + +str_hashtable* g_enabled_debug_tags = 0; + +static void init_debug_table() { + if (!g_enabled_debug_tags) { + g_enabled_debug_tags = alloc(str_hashtable); + } +} + +void finalize_debug() { + dealloc(g_enabled_debug_tags); + g_enabled_debug_tags = 0; +} + +void enable_debug(const char * tag) { + init_debug_table(); + g_enabled_debug_tags->insert(const_cast(tag)); +} + +void disable_debug(const char * tag) { + init_debug_table(); + g_enabled_debug_tags->erase(const_cast(tag)); +} + +bool is_debug_enabled(const char * tag) { + init_debug_table(); + return g_enabled_debug_tags->contains(const_cast(tag)); +} + +#ifndef _WINDOWS +void invoke_gdb() { + char buffer[1024]; + int * x = 0; + for (;;) { + std::cerr << "(C)ontinue, (A)bort, (S)top, Invoke (G)DB\n"; + char result; + std::cin >> result; + switch(result) { + case 'C': + case 'c': + return; + case 'A': + case 'a': + exit(1); + case 'S': + case 's': + // force seg fault... + *x = 0; + return; + case 'G': + case 'g': + sprintf(buffer, "gdb -nw /proc/%d/exe %d", getpid(), getpid()); + std::cerr << "invoking GDB...\n"; + if (system(buffer) == 0) { + std::cerr << "continuing the execution...\n"; + } + else { + std::cerr << "error starting GDB...\n"; + // forcing seg fault. + int * x = 0; + *x = 0; + } + return; + default: + std::cerr << "INVALID COMMAND\n"; + } + } +} +#endif diff --git a/lib/debug.h b/lib/debug.h new file mode 100644 index 000000000..ec904aa4e --- /dev/null +++ b/lib/debug.h @@ -0,0 +1,99 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + debug.h + +Abstract: + + Basic debugging support. + +Author: + + Leonardo de Moura (leonardo) 2006-09-11. + +Revision History: + +--*/ +#ifndef _DEBUG_H_ +#define _DEBUG_H_ + +void enable_assertions(bool f); +bool assertions_enabled(); + +#if 0 +#define _CRTDBG_MAP_ALLOC +#include +#include +#include +#endif + +#include"error_codes.h" +#include"warning.h" + +#ifdef Z3DEBUG +#define DEBUG_CODE(CODE) { CODE } ((void) 0) +#else +#define DEBUG_CODE(CODE) ((void) 0) +#endif + +#ifdef _WINDOWS +#define INVOKE_DEBUGGER() __debugbreak() +#else +void invoke_gdb(); +#define INVOKE_DEBUGGER() invoke_gdb() +#endif + +void notify_assertion_violation(const char * file_name, int line, const char * condition); +void enable_debug(const char * tag); +void disable_debug(const char * tag); +bool is_debug_enabled(const char * tag); + +#define SASSERT(COND) DEBUG_CODE(if (assertions_enabled() && !(COND)) { notify_assertion_violation(__FILE__, __LINE__, #COND); INVOKE_DEBUGGER(); }) +#define CASSERT(TAG, COND) DEBUG_CODE(if (assertions_enabled() && is_debug_enabled(TAG) && !(COND)) { notify_assertion_violation(__FILE__, __LINE__, #COND); INVOKE_DEBUGGER(); }) +#define XASSERT(COND, EXTRA_CODE) DEBUG_CODE(if (assertions_enabled() && !(COND)) { notify_assertion_violation(__FILE__, __LINE__, #COND); { EXTRA_CODE } INVOKE_DEBUGGER(); }) +#define UNREACHABLE() DEBUG_CODE(notify_assertion_violation(__FILE__, __LINE__, "UNREACHABLE CODE WAS REACHED."); INVOKE_DEBUGGER();) +#define NOT_IMPLEMENTED_YET() { std::cerr << "NOT IMPLEMENTED YET!\n"; UNREACHABLE(); exit(ERR_NOT_IMPLEMENTED_YET); } ((void) 0) + +#ifdef Z3DEBUG +#define VERIFY(_x_) if (!(_x_)) { \ + std::cerr << "Failed to verify: " << #_x_ << "\n"; \ + UNREACHABLE(); \ + } +#else +#define VERIFY(_x_) (_x_) +#endif + +#define MAKE_NAME2(LINE) zofty_ ## LINE +#define MAKE_NAME(LINE) MAKE_NAME2(LINE) +#define DBG_UNIQUE_NAME MAKE_NAME(__LINE__) +#define COMPILE_TIME_ASSERT(expr) extern char DBG_UNIQUE_NAME[expr] + +template +class class_invariant +{ + T* m_class; + char const* m_module; +public: + class_invariant(T* cls) : m_class(cls), m_module(0) { + SASSERT(cls->invariant()); + } + class_invariant(T* cls, char const* module) : m_class(cls), m_module(module) { + CASSERT(module, cls->invariant()); + } + ~class_invariant() { + if (m_module) { + CASSERT(m_module, m_class->invariant()); + } + else { + SASSERT(m_class->invariant()); + } + } +private: +}; + +void finalize_debug(); + +#endif /* _DEBUG_H_ */ + diff --git a/lib/dec_ref_util.h b/lib/dec_ref_util.h new file mode 100644 index 000000000..a91d9f06d --- /dev/null +++ b/lib/dec_ref_util.h @@ -0,0 +1,68 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + map_util.h + +Abstract: + + Some goodies for managing reference counters + stored in maps. + +Author: + + Leonardo (leonardo) 2011-06-07 + +Notes: + +--*/ +#ifndef _MAP_UTIL_H_ +#define _MAP_UTIL_H_ + +/** + \brief Decrement the reference counter of the keys and values stored in the map, + then reset the map. +*/ +template +void dec_ref_key_values(Mng & m, Map & map) { + typename Map::iterator it = map.begin(); + typename Map::iterator end = map.end(); + for (; it != end; ++it) { + m.dec_ref(it->m_key); + m.dec_ref(it->m_value); + } + map.reset(); +} + +/** + \brief Decrement the reference counter of the keys stored in the map, + then reset the map. +*/ +template +void dec_ref_keys(Mng & m, Map & map) { + typename Map::iterator it = map.begin(); + typename Map::iterator end = map.end(); + for (; it != end; ++it) { + m.dec_ref(it->m_key); + } + map.reset(); +} + + +/** + \brief Decrement the reference counter of the values stored in the map, + then reset the map. +*/ +template +void dec_ref_values(Mng & m, Map & map) { + typename Map::iterator it = map.begin(); + typename Map::iterator end = map.end(); + for (; it != end; ++it) { + m.dec_ref(it->m_value); + } + map.reset(); +} + + +#endif diff --git a/lib/decl_collector.cpp b/lib/decl_collector.cpp new file mode 100644 index 000000000..a83bf6b99 --- /dev/null +++ b/lib/decl_collector.cpp @@ -0,0 +1,102 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + smt_decl_collector.cpp + +Abstract: + + Collect uninterpreted func_delcs and sorts. + This class was originally in ast_smt_pp.h + +Author: + + Leonardo (leonardo) 2011-10-04 + +Revision History: + +--*/ +#include"decl_collector.h" + +void decl_collector::visit_sort(sort * n) { + family_id fid = n->get_family_id(); + if (m().is_uninterp(n)) + m_sorts.push_back(n); + if (fid == m_dt_fid) + m_sorts.push_back(n); +} + +bool decl_collector::is_bool(sort * s) { + return m().is_bool(s); +} + +void decl_collector::visit_func(func_decl * n) { + family_id fid = n->get_family_id(); + if (fid == null_family_id) { + if (m_sep_preds && is_bool(n->get_range())) + m_preds.push_back(n); + else + m_decls.push_back(n); + } +} + +decl_collector::decl_collector(ast_manager & m, bool preds): + m_manager(m), + m_sep_preds(preds) { + m_basic_fid = m_manager.get_basic_family_id(); + m_dt_fid = m_manager.get_family_id("datatype"); +} + +void decl_collector::visit(ast* n) { + ptr_vector todo; + todo.push_back(n); + while (!todo.empty()) { + n = todo.back(); + todo.pop_back(); + if (!m_visited.is_marked(n)) { + m_visited.mark(n, true); + switch(n->get_kind()) { + case AST_APP: { + app * a = to_app(n); + for (unsigned i = 0; i < a->get_num_args(); ++i) { + todo.push_back(a->get_arg(i)); + } + todo.push_back(a->get_decl()); + break; + } + case AST_QUANTIFIER: { + quantifier * q = to_quantifier(n); + unsigned num_decls = q->get_num_decls(); + for (unsigned i = 0; i < num_decls; ++i) { + todo.push_back(q->get_decl_sort(i)); + } + todo.push_back(q->get_expr()); + for (unsigned i = 0; i < q->get_num_patterns(); ++i) { + todo.push_back(q->get_pattern(i)); + } + break; + } + case AST_SORT: + visit_sort(to_sort(n)); + break; + case AST_FUNC_DECL: { + func_decl * d = to_func_decl(n); + for (unsigned i = 0; i < d->get_arity(); ++i) { + todo.push_back(d->get_domain(i)); + } + todo.push_back(d->get_range()); + visit_func(d); + break; + } + case AST_VAR: + break; + default: + UNREACHABLE(); + } + } + } +} + + + diff --git a/lib/decl_collector.h b/lib/decl_collector.h new file mode 100644 index 000000000..4bcec94aa --- /dev/null +++ b/lib/decl_collector.h @@ -0,0 +1,55 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + decl_collector.h + +Abstract: + + Collect uninterpreted func_delcs and sorts. + This class was originally in ast_smt_pp.h + +Author: + + Leonardo (leonardo) 2011-10-04 + +Revision History: + +--*/ +#ifndef _SMT_DECL_COLLECTOR_H_ +#define _SMT_DECL_COLLECTOR_H_ + +#include"ast.h" + +class decl_collector { + ast_manager & m_manager; + bool m_sep_preds; + ptr_vector m_sorts; + ptr_vector m_decls; + ptr_vector m_preds; + ast_mark m_visited; + family_id m_basic_fid; + family_id m_dt_fid; + + void visit_sort(sort* n); + bool is_bool(sort* s); + void visit_func(func_decl* n); + + +public: + // if preds == true, then predicates are stored in a separate collection. + decl_collector(ast_manager & m, bool preds=true); + ast_manager & m() { return m_manager; } + + void visit(ast * n); + + unsigned get_num_sorts() const { return m_sorts.size(); } + unsigned get_num_decls() const { return m_decls.size(); } + unsigned get_num_preds() const { return m_preds.size(); } + sort * const * get_sorts() const { return m_sorts.c_ptr(); } + func_decl * const * get_func_decls() const { return m_decls.c_ptr(); } + func_decl * const * get_pred_decls() const { return m_preds.c_ptr(); } +}; + +#endif diff --git a/lib/default_tactic.cpp b/lib/default_tactic.cpp new file mode 100644 index 000000000..c5687cd1a --- /dev/null +++ b/lib/default_tactic.cpp @@ -0,0 +1,48 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + default_tactic.cpp + +Abstract: + + General purpose tactic for the Z3 logic (when the logic is not specified). + +Author: + + Leonardo (leonardo) 2012-02-22 + +Notes: + +--*/ +#include"default_tactic.h" +#include"simplify_tactic.h" +#include"qfbv_tactic.h" +#include"smt_tactic.h" +#include"qflia_tactic.h" +#include"qflra_tactic.h" +#include"qfnia_tactic.h" +#include"qfnra_tactic.h" +#include"nra_tactic.h" +#include"probe_arith.h" + +tactic * mk_default_tactic(ast_manager & m, params_ref const & p) { + tactic * st = using_params(and_then(mk_simplify_tactic(m), + cond(mk_is_qfbv_probe(), + mk_qfbv_tactic(m), + cond(mk_is_qflia_probe(), + mk_qflia_tactic(m), + cond(mk_is_qflra_probe(), + mk_qflra_tactic(m), + cond(mk_is_qfnra_probe(), + mk_qfnra_tactic(m), + cond(mk_is_qfnia_probe(), + mk_qfnia_tactic(m), + cond(mk_is_nra_probe(), + mk_nra_tactic(m), + mk_smt_tactic()))))))), + p); + return st; +} + diff --git a/lib/default_tactic.h b/lib/default_tactic.h new file mode 100644 index 000000000..4f95ef128 --- /dev/null +++ b/lib/default_tactic.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + default_tactic.h + +Abstract: + + General purpose tactic for the Z3 logic (when the logic is not specified). + +Author: + + Leonardo (leonardo) 2012-02-22 + +Notes: + +--*/ +#ifndef _DEFAULT_TACTIC_ +#define _DEFAULT_TACTIC_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_default_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/defined_names.cpp b/lib/defined_names.cpp new file mode 100644 index 000000000..dab0ca0d8 --- /dev/null +++ b/lib/defined_names.cpp @@ -0,0 +1,226 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + defined_names.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-14. + +Revision History: + +--*/ +#include"defined_names.h" +#include"used_vars.h" +#include"var_subst.h" +#include"ast_smt2_pp.h" +#include"ast_pp.h" + +defined_names::impl::impl(ast_manager & m, char const * prefix): + m_manager(m), + m_exprs(m), + m_names(m), + m_apply_proofs(m) { + if (prefix) + m_z3name = prefix; +} + +defined_names::impl::~impl() { +} + +/** + \brief Given an expression \c e that may contain free variables, return an application (sk x_1 ... x_n), + where sk is a fresh variable name, and x_i's are the free variables of \c e. + + Store in var_sorts and var_names information about the free variables of \c e. This data + is used to create an universal quantifier over the definition of the new name. +*/ +app * defined_names::impl::gen_name(expr * e, sort_ref_buffer & var_sorts, buffer & var_names) { + used_vars uv; + uv(e); + unsigned num_vars = uv.get_max_found_var_idx_plus_1(); + ptr_buffer new_args; + ptr_buffer domain; + for (unsigned i = 0; i < num_vars; i++) { + sort * s = uv.get(i); + if (s) { + domain.push_back(s); + new_args.push_back(m_manager.mk_var(i, s)); + var_sorts.push_back(s); + } + else { + var_sorts.push_back(m_manager.mk_bool_sort()); // could be any sort. + } + var_names.push_back(symbol(i)); + } + + sort * range = m_manager.get_sort(e); + func_decl * new_skolem_decl = m_manager.mk_fresh_func_decl(m_z3name, symbol::null, domain.size(), domain.c_ptr(), range); + app * n = m_manager.mk_app(new_skolem_decl, new_args.size(), new_args.c_ptr()); + TRACE("mk_definition_bug", tout << "gen_name: " << mk_ismt2_pp(n, m_manager) << "\n"; + for (unsigned i = 0; i < var_sorts.size(); i++) tout << mk_pp(var_sorts[i], m_manager) << " "; + tout << "\n";); + return n; +} + +/** + \brief Cache \c n as a name for expression \c e. +*/ +void defined_names::impl::cache_new_name(expr * e, app * n) { + m_expr2name.insert(e, n); + m_exprs.push_back(e); + m_names.push_back(n); +} + +/** + \brief Cache \c pr as a proof that m_expr2name[e] is a name for expression \c e. +*/ +void defined_names::impl::cache_new_name_intro_proof(expr * e, proof * pr) { + SASSERT(m_expr2name.contains(e)); + m_expr2proof.insert(e, pr); + m_apply_proofs.push_back(pr); +} + +/** + \brief Given a definition conjunct \c def of the name \c name, store in \c result this definition. + A quantifier is added around \c def_conjunct, if sorts and names are not empty. + In this case, The application \c name is used as a pattern for the new quantifier. +*/ +void defined_names::impl::bound_vars(sort_ref_buffer const & sorts, buffer const & names, expr * def_conjunct, app * name, expr_ref & result) { + SASSERT(sorts.size() == names.size()); + if (sorts.empty()) + result = def_conjunct; + else { + expr * patterns[1] = { m_manager.mk_pattern(name) }; + quantifier_ref q(m_manager); + q = m_manager.mk_forall(sorts.size(), + sorts.c_ptr(), + names.c_ptr(), + def_conjunct, + 1, symbol::null, symbol::null, + 1, patterns); + TRACE("mk_definition_bug", tout << "before elim_unused_vars:\n" << mk_ismt2_pp(q, m_manager) << "\n";); + elim_unused_vars(m_manager, q, result); + TRACE("mk_definition_bug", tout << "after elim_unused_vars:\n" << mk_ismt2_pp(result, m_manager) << "\n";); + } +} + +/** + \brief Given a definition conjunct \c def of the name \c name, store in \c result this definition. + A quantifier is added around \c def_conjunct, if sorts and names are not empty. + In this case, The application \c name is used as a pattern for the new quantifier. +*/ +void defined_names::impl::bound_vars(sort_ref_buffer const & sorts, buffer const & names, expr * def_conjunct, app * name, expr_ref_buffer & result) { + expr_ref tmp(m_manager); + bound_vars(sorts, names, def_conjunct, name, tmp); + result.push_back(tmp); +} + +#define MK_OR m_manager.mk_or +#define MK_NOT m_manager.mk_not +#define MK_EQ m_manager.mk_eq + +void defined_names::impl::mk_definition(expr * e, app * n, sort_ref_buffer & var_sorts, buffer const & var_names, expr_ref & new_def) { + expr_ref_buffer defs(m_manager); + if (m_manager.is_bool(e)) { + bound_vars(var_sorts, var_names, MK_OR(MK_NOT(n), e), n, defs); + bound_vars(var_sorts, var_names, MK_OR(n, MK_NOT(e)), n, defs); + } + else if (m_manager.is_term_ite(e)) { + bound_vars(var_sorts, var_names, MK_OR(MK_NOT(to_app(e)->get_arg(0)), MK_EQ(n, to_app(e)->get_arg(1))), n, defs); + bound_vars(var_sorts, var_names, MK_OR(to_app(e)->get_arg(0), MK_EQ(n, to_app(e)->get_arg(2))), n, defs); + } + else { + bound_vars(var_sorts, var_names, MK_EQ(e, n), n, defs); + } + new_def = defs.size() == 1 ? defs[0] : m_manager.mk_and(defs.size(), defs.c_ptr()); +} + +void defined_names::pos_impl::mk_definition(expr * e, app * n, sort_ref_buffer & var_sorts, buffer const & var_names, expr_ref & new_def) { + bound_vars(var_sorts, var_names, MK_OR(MK_NOT(n), e), n, new_def); +} + +bool defined_names::impl::mk_name(expr * e, expr_ref & new_def, proof_ref & new_def_pr, app_ref & n, proof_ref & pr) { + TRACE("mk_definition_bug", tout << "making name for:\n" << mk_ismt2_pp(e, m_manager) << "\n";); + + app * n_ptr; + if (m_expr2name.find(e, n_ptr)) { + TRACE("mk_definition_bug", tout << "name for expression is already cached..., returning false...\n";); + n = n_ptr; + if (m_manager.proofs_enabled()) { + proof * pr_ptr; + m_expr2proof.find(e, pr_ptr); + SASSERT(pr_ptr); + pr = pr_ptr; + } + return false; + } + else { + sort_ref_buffer var_sorts(m_manager); + buffer var_names; + + n = gen_name(e, var_sorts, var_names); + cache_new_name(e, n); + + TRACE("mk_definition_bug", tout << "name: " << mk_ismt2_pp(n, m_manager) << "\n";); + // variables are in reverse order in quantifiers + std::reverse(var_sorts.c_ptr(), var_sorts.c_ptr() + var_sorts.size()); + std::reverse(var_names.c_ptr(), var_names.c_ptr() + var_names.size()); + + mk_definition(e, n, var_sorts, var_names, new_def); + + TRACE("mk_definition_bug", tout << "new_def:\n" << mk_ismt2_pp(new_def, m_manager) << "\n";); + + if (m_manager.proofs_enabled()) { + new_def_pr = m_manager.mk_def_intro(new_def); + pr = m_manager.mk_apply_def(e, n, new_def_pr); + cache_new_name_intro_proof(e, pr); + } + return true; + } +} + +void defined_names::impl::push_scope() { + SASSERT(m_exprs.size() == m_names.size()); + m_lims.push_back(m_exprs.size()); +} + +void defined_names::impl::pop_scope(unsigned num_scopes) { + unsigned lvl = m_lims.size(); + SASSERT(num_scopes <= lvl); + unsigned new_lvl = lvl - num_scopes; + unsigned old_sz = m_lims[new_lvl]; + unsigned sz = m_exprs.size(); + SASSERT(old_sz <= sz); + SASSERT(sz == m_names.size()); + while (old_sz != sz) { + --sz; + if (m_manager.proofs_enabled()) { + m_expr2proof.erase(m_exprs.back()); + m_apply_proofs.pop_back(); + } + m_expr2name.erase(m_exprs.back()); + m_exprs.pop_back(); + m_names.pop_back(); + } + SASSERT(m_exprs.size() == old_sz); + m_lims.shrink(new_lvl); +} + +void defined_names::impl::reset() { + m_expr2name.reset(); + m_expr2proof.reset(); + m_exprs.reset(); + m_names.reset(); + m_apply_proofs.reset(); + m_lims.reset(); +} + + + diff --git a/lib/defined_names.h b/lib/defined_names.h new file mode 100644 index 000000000..c5e4a116f --- /dev/null +++ b/lib/defined_names.h @@ -0,0 +1,151 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + defined_names.h + +Abstract: + + In some transformations, we need to name expressions. + These expressions are stored in a table. + +Author: + + Leonardo de Moura (leonardo) 2008-01-14. + +Revision History: + +--*/ +#ifndef _DEFINED_NAMES_H_ +#define _DEFINED_NAMES_H_ + +#include"ast.h" +#include"obj_hashtable.h" + +/** + \brief Mapping from expressions to skolem functions that are used to name them. + + The mapping supports backtracking using the methods #push_scope and #pop_scope. +*/ +class defined_names { + + struct impl { + typedef obj_map expr2name; + typedef obj_map expr2proof; + ast_manager & m_manager; + symbol m_z3name; + + /** + \brief Mapping from expressions to their names. A name is an application. + If the expression does not have free variables, then the name is just a constant. + */ + expr2name m_expr2name; + /** + \brief Mapping from expressions to the apply-def proof. + That is, for each expression e, m_expr2proof[e] is the + proof e and m_expr2name[2] are observ. equivalent. + + This mapping is not used if proof production is disabled. + */ + expr2proof m_expr2proof; + + /** + \brief Domain of m_expr2name. It is used to keep the expressions + alive and for backtracking + */ + expr_ref_vector m_exprs; + expr_ref_vector m_names; //!< Range of m_expr2name. It is used to keep the names alive. + proof_ref_vector m_apply_proofs; //!< Range of m_expr2proof. It is used to keep the def-intro proofs alive. + + + unsigned_vector m_lims; //!< Backtracking support. + + impl(ast_manager & m, char const * prefix); + virtual ~impl(); + + app * gen_name(expr * e, sort_ref_buffer & var_sorts, buffer & var_names); + void cache_new_name(expr * e, app * name); + void cache_new_name_intro_proof(expr * e, proof * pr); + void bound_vars(sort_ref_buffer const & sorts, buffer const & names, expr * def_conjunct, app * name, expr_ref & result); + void bound_vars(sort_ref_buffer const & sorts, buffer const & names, expr * def_conjunct, app * name, expr_ref_buffer & result); + virtual void mk_definition(expr * e, app * n, sort_ref_buffer & var_sorts, buffer const & var_names, expr_ref & new_def); + bool mk_name(expr * e, expr_ref & new_def, proof_ref & new_def_pr, app_ref & n, proof_ref & pr); + void push_scope(); + void pop_scope(unsigned num_scopes); + void reset(); + }; + + struct pos_impl : public impl { + pos_impl(ast_manager & m, char const * fresh_prefix):impl(m, fresh_prefix) {} + virtual void mk_definition(expr * e, app * n, sort_ref_buffer & var_sorts, buffer const & var_names, expr_ref & new_def); + }; + + impl m_impl; + pos_impl m_pos_impl; +public: + defined_names(ast_manager & m, char const * fresh_prefix = "z3name"):m_impl(m, fresh_prefix), m_pos_impl(m, fresh_prefix) {} + + // ----------------------------------- + // + // High-level API + // + // ----------------------------------- + + /** + \brief Create a name for expression \c e if it doesn't already exists. + + Return true if a new name was created, and false if a name already exists for \c e. + + The resultant new name is stored in n, and a [apply-def] proof + that (= e n) is stored into pr. + + If true is returned, then the definition of the new name is + stored into new_def, and a [def-intro] proof into new_def_pr. + + The proofs are not produced when proof generation is disabled. + + The definition of an expression e with name n is: + + - (and (or (not e) n) (or e (not n))) if e is an formula. + - (and (or (not c) (= n t1)) (or c (= n t2))) if e is an if-then-else term of the form (ite c t1 t2) + - (= n e) if e is a term. + + Remark: the definitions are closed with an universal quantifier if e contains free variables. + */ + bool mk_name(expr * e, expr_ref & new_def, proof_ref & new_def_pr, app_ref & n, proof_ref & pr) { + return m_impl.mk_name(e, new_def, new_def_pr, n, pr); + } + + /** + \brief Create a name for a positive occurrence of the expression \c e. + + Return true if a new pos-name was created, and false if a pos-name already exists for \c e. + + If true is returned, then the definition of the new name is stored into new_def. + It has the form: (or (not n) e) + + Remark: the definitions are closed with an universal quantifier if e contains free variables. + */ + bool mk_pos_name(expr * e, expr_ref & new_def, proof_ref & new_def_pr, app_ref & n, proof_ref & pr) { + return m_pos_impl.mk_name(e, new_def, new_def_pr, n, pr); + } + + void push_scope() { + m_impl.push_scope(); + m_pos_impl.push_scope(); + } + + void pop_scope(unsigned num_scopes) { + m_impl.pop_scope(num_scopes); + m_pos_impl.pop_scope(num_scopes); + } + + void reset() { + m_impl.reset(); + m_pos_impl.reset(); + } +}; + +#endif /* _DEFINED_NAMES_H_ */ + diff --git a/lib/degree_shift_tactic.cpp b/lib/degree_shift_tactic.cpp new file mode 100644 index 000000000..8e2c6d7cb --- /dev/null +++ b/lib/degree_shift_tactic.cpp @@ -0,0 +1,349 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + degree_shift_tactic.cpp + +Abstract: + + Simple degree shift procedure. + Basic idea: if goal G contains a real variable x, x occurs with degrees + d_1, ..., d_k in G, and n = gcd(d_1, ..., d_k) > 1. + Then, replace x^n with a new fresh variable y. + +Author: + + Leonardo de Moura (leonardo) 2011-12-30. + +Revision History: + +--*/ +#include"tactical.h" +#include"filter_model_converter.h" +#include"extension_model_converter.h" +#include"cooperate.h" +#include"arith_decl_plugin.h" +#include"simplify_tactic.h" +#include"ast_smt2_pp.h" +#include"rewriter_def.h" + +class degree_shift_tactic : public tactic { + struct imp { + ast_manager & m; + arith_util m_autil; + obj_map m_var2degree; + obj_map m_var2var; + obj_map m_var2pr; + expr_ref_vector m_pinned; + ptr_vector m_todo; + rational m_one; + bool m_produce_models; + bool m_produce_proofs; + volatile bool m_cancel; + + expr * mk_power(expr * t, rational const & k) { + if (k.is_one()) + return t; + else + return m_autil.mk_power(t, m_autil.mk_numeral(k, false)); + } + + struct rw_cfg : public default_rewriter_cfg { + imp & o; + rw_cfg(imp & _o):o(_o) {} + + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + arith_util & u = o.m_autil; + if (!is_decl_of(f, u.get_family_id(), OP_POWER) || !is_app(args[0])) + return BR_FAILED; + ast_manager & m = o.m; + rational g; + app * t = to_app(args[0]); + if (!o.m_var2degree.find(t, g)) + return BR_FAILED; + SASSERT(g > rational(1)); + SASSERT(g.is_int()); + rational k; + VERIFY(u.is_numeral(args[1], k)); + SASSERT(gcd(k, g) == g); + rational new_k = div(k, g); + expr * new_arg = o.m_var2var.find(t); + result = o.mk_power(new_arg, new_k); + if (o.m_produce_proofs) { + proof * pr = o.m_var2pr.find(t); + app * fact = m.mk_eq(m.mk_app(f, num, args), result); + result_pr = m.mk_th_lemma(u.get_family_id(), fact, 1, &pr); + } + return BR_DONE; + } + }; + + class rw : public rewriter_tpl { + rw_cfg m_cfg; + public: + rw(imp & o): + rewriter_tpl(o.m, o.m_produce_proofs, m_cfg), + m_cfg(o) { + } + }; + + scoped_ptr m_rw; + + imp(ast_manager & _m): + m(_m), + m_autil(_m), + m_pinned(_m), + m_one(1), + m_rw(0) { + m_cancel = false; + } + + void set_cancel(bool f) { + m_cancel = f; + } + + void checkpoint() { + if (m_cancel) + throw tactic_exception(TACTIC_CANCELED_MSG); + cooperate("degree_shift"); + } + + void visit(expr * t, expr_fast_mark1 & visited) { + if (!visited.is_marked(t)) { + visited.mark(t); + m_todo.push_back(t); + } + } + + void save_degree(expr * t, rational const & k) { + SASSERT(k.is_int()); + if (is_uninterp_const(t) && m_autil.is_real(t)) { + rational old_k; + if (m_var2degree.find(to_app(t), old_k)) { + old_k = gcd(k, old_k); + m_var2degree.insert(to_app(t), old_k); + } + else { + m_var2degree.insert(to_app(t), k); + } + } + } + + void visit_args(expr * t, expr_fast_mark1 & visited) { + if (is_app(t)) { + unsigned num_args = to_app(t)->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = to_app(t)->get_arg(i); + save_degree(arg, m_one); + visit(arg, visited); + } + } + } + + void collect(expr * t, expr_fast_mark1 & visited) { + rational k; + visit(t, visited); + while (!m_todo.empty()) { + checkpoint(); + expr * t = m_todo.back(); + m_todo.pop_back(); + if (is_var(t)) + continue; + if (is_quantifier(t)) { + unsigned num_children = to_quantifier(t)->get_num_children(); + for (unsigned i = 0; i < num_children; i ++) + visit(to_quantifier(t)->get_child(i), visited); + } + else { + SASSERT(is_app(t)); + if (m_autil.is_power(t) && m_autil.is_numeral(to_app(t)->get_arg(1), k) && k.is_int() && k.is_pos()) { + expr * arg = to_app(t)->get_arg(0); + save_degree(arg, k); + visit_args(arg, visited); + } + else { + visit_args(t, visited); + } + } + } + } + + void display_candidates(std::ostream & out) { + out << "candidates:\n"; + obj_map::iterator it = m_var2degree.begin(); + obj_map::iterator end = m_var2degree.end(); + for (; it != end; ++it) { + if (!it->m_value.is_one()) { + out << "POWER: " << it->m_value << "\n" << mk_ismt2_pp(it->m_key, m) << "\n"; + } + } + } + + void collect(goal const & g) { + m_var2degree.reset(); + expr_fast_mark1 visited; + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + collect(g.form(i), visited); + } + + TRACE("degree_shift", display_candidates(tout);); + } + + void discard_non_candidates() { + m_pinned.reset(); + ptr_vector to_delete; + obj_map::iterator it = m_var2degree.begin(); + obj_map::iterator end = m_var2degree.end(); + for (; it != end; ++it) { + if (it->m_value.is_one()) + to_delete.push_back(it->m_key); + else + m_pinned.push_back(it->m_key); // make sure it is not deleted during simplifications + } + ptr_vector::iterator it2 = to_delete.begin(); + ptr_vector::iterator end2 = to_delete.end(); + for (; it2 != end2; ++it2) + m_var2degree.erase(*it2); + } + + void prepare_substitution(model_converter_ref & mc) { + SASSERT(!m_var2degree.empty()); + filter_model_converter * fmc = 0; + extension_model_converter * xmc = 0; + if (m_produce_models) { + fmc = alloc(filter_model_converter, m); + xmc = alloc(extension_model_converter, m); + mc = concat(fmc, xmc); + } + obj_map::iterator it = m_var2degree.begin(); + obj_map::iterator end = m_var2degree.end(); + for (; it != end; ++it) { + SASSERT(it->m_value.is_int()); + SASSERT(it->m_value >= rational(2)); + app * fresh = m.mk_fresh_const(0, it->m_key->get_decl()->get_range()); + m_pinned.push_back(fresh); + m_var2var.insert(it->m_key, fresh); + if (m_produce_models) { + fmc->insert(fresh->get_decl()); + xmc->insert(it->m_key->get_decl(), mk_power(fresh, rational(1)/it->m_value)); + } + if (m_produce_proofs) { + expr * s = mk_power(it->m_key, it->m_value); + expr * eq = m.mk_eq(fresh, s); + proof * pr1 = m.mk_def_intro(eq); + proof * result_pr = m.mk_apply_def(fresh, s, pr1); + m_pinned.push_back(result_pr); + m_var2pr.insert(it->m_key, result_pr); + } + } + } + + void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + mc = 0; pc = 0; core = 0; + m_produce_proofs = g->proofs_enabled(); + m_produce_models = g->models_enabled(); + tactic_report report("degree_shift", *g); + collect(*g); + discard_non_candidates(); + if (!m_var2degree.empty()) { + prepare_substitution(mc); + m_rw = alloc(rw, *this); + + // substitute + expr_ref new_curr(m); + proof_ref new_pr(m); + unsigned size = g->size(); + for (unsigned idx = 0; idx < size; idx++) { + checkpoint(); + expr * curr = g->form(idx); + (*m_rw)(curr, new_curr, new_pr); + if (m_produce_proofs) { + proof * pr = g->pr(idx); + new_pr = m.mk_modus_ponens(pr, new_pr); + } + g->update(idx, new_curr, new_pr, g->dep(idx)); + } + + // add >= 0 constraints for variables with even degree + obj_map::iterator it = m_var2degree.begin(); + obj_map::iterator end = m_var2degree.end(); + for (; it != end; ++it) { + SASSERT(it->m_value.is_int()); + SASSERT(it->m_value >= rational(2)); + if (it->m_value.is_even()) { + app * new_var = m_var2var.find(it->m_key); + app * new_c = m_autil.mk_ge(new_var, m_autil.mk_numeral(rational(0), false)); + proof * new_pr = 0; + if (m_produce_proofs) { + proof * pr = m_var2pr.find(it->m_key); + new_pr = m.mk_th_lemma(m_autil.get_family_id(), new_c, 1, &pr); + } + g->assert_expr(new_c, new_pr, 0); + } + } + } + g->inc_depth(); + result.push_back(g.get()); + TRACE("degree_shift", g->display(tout); if (mc) mc->display(tout);); + SASSERT(g->is_well_sorted()); + } + }; + + imp * m_imp; +public: + degree_shift_tactic(ast_manager & m) { + m_imp = alloc(imp, m); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(degree_shift_tactic, m); + } + + virtual ~degree_shift_tactic() { + dealloc(m_imp); + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + (*m_imp)(in, result, mc, pc, core); + } + + virtual void cleanup() { + ast_manager & m = m_imp->m; + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + } + +protected: + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + +tactic * mk_degree_shift_tactic(ast_manager & m, params_ref const & p) { + params_ref mul2power_p; + mul2power_p.set_bool(":mul-to-power", true); + return and_then(using_params(mk_simplify_tactic(m), mul2power_p), + clean(alloc(degree_shift_tactic, m))); +} + diff --git a/lib/degree_shift_tactic.h b/lib/degree_shift_tactic.h new file mode 100644 index 000000000..d2406a5a4 --- /dev/null +++ b/lib/degree_shift_tactic.h @@ -0,0 +1,31 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + degree_shift_tactic.h + +Abstract: + + Simple degree shift procedure. + Basic idea: if goal G contains a real variable x, x occurs with degrees + d_1, ..., d_k in G, and n = gcd(d_1, ..., d_k) > 1. + Then, replace x^n with a new fresh variable y. + +Author: + + Leonardo de Moura (leonardo) 2011-12-30. + +Revision History: + +--*/ +#ifndef _DEGREE_SHIFT_TACTIC_H_ +#define _DEGREE_SHIFT_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_degree_shift_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/demodulator.cpp b/lib/demodulator.cpp new file mode 100644 index 000000000..560af99ee --- /dev/null +++ b/lib/demodulator.cpp @@ -0,0 +1,906 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + demodulator.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-04-12. + +Revision History: + + Christoph Wintersteiger 2010-04-21: Implementation + +--*/ + +#include"ast_pp.h" +#include"demodulator.h" +#include"for_each_expr.h" +#include"var_subst.h" +#include"uint_set.h" + +demodulator::demodulator(ast_manager & m, basic_simplifier_plugin & p): + m_manager(m), + m_match_subst(m), + m_bsimp(p), + m_todo(m), + m_rewrite_todo(m), + m_rewrite_cache(m), + m_new_exprs(m) { +} + +demodulator::~demodulator() { + reset_dealloc_values(m_fwd_idx); + reset_dealloc_values(m_back_idx); + for (demodulator2lhs_rhs::iterator it = m_demodulator2lhs_rhs.begin(); it != m_demodulator2lhs_rhs.end(); it++) { + m_manager.dec_ref(it->m_key); + m_manager.dec_ref(it->m_value.first); + m_manager.dec_ref(it->m_value.second); + } +} + +bool demodulator::is_demodulator(expr * e, expr_ref & large, expr_ref & small) const { + if (e->get_kind() == AST_QUANTIFIER) { + quantifier * q = to_quantifier(e); + if (q->is_forall()) { + expr * qe = q->get_expr(); + if ((m_manager.is_eq(qe) || m_manager.is_iff(qe))) { + app * eq = to_app(q->get_expr()); + expr * lhs = eq->get_arg(0); + expr * rhs = eq->get_arg(1); + int subset = is_subset(lhs, rhs); + int smaller = is_smaller(lhs, rhs); + TRACE("demodulator", tout << "testing is_demodulator:\n" + << mk_pp(lhs, m_manager) << "\n" + << mk_pp(rhs, m_manager) << "\n" + << "subset: " << subset << ", smaller: " << smaller << "\n";); + // We only track uninterpreted functions, everything else is likely too expensive. + if ((subset == +1 || subset == +2) && smaller == +1) { + if (is_uninterp(rhs)) { + large = rhs; + small = lhs; + return true; + } +#if 1 + // lhs = (not rhs) --> (not lhs) = rhs + expr * not_rhs; + if (m_manager.is_not(rhs, not_rhs) && is_uninterp(not_rhs)) { + large = not_rhs; + small = m_manager.mk_not(lhs); + return true; + } +#endif + } + + if ((subset == -1 || subset == +2) && smaller == -1) { + if (is_uninterp(lhs)) { + large = lhs; + small = rhs; + return true; + } +#if 1 + // (not lhs) = rhs --> lhs = (not rhs) + expr * not_lhs; + if (m_manager.is_not(lhs, not_lhs) && is_uninterp(not_lhs)) { + large = not_lhs; + small = m_manager.mk_not(rhs); + return true; + } +#endif + } + + } else if (m_manager.is_not(qe) && is_uninterp(to_app(qe)->get_arg(0))) { + // this is like (not (f ... )) --> (= (f ...) false) + large = to_app(qe)->get_arg(0); + small = m_manager.mk_false(); + return true; + } else if (is_uninterp(qe)) { + // this is like (f ... ) --> (= (f ...) true) + large = to_app(qe); + small = m_manager.mk_true(); + return true; + } + } + } + + return false; +} + +class var_set_proc { + uint_set & m_set; +public: + var_set_proc(uint_set &s):m_set(s) {} + void operator()(var * n) { m_set.insert(n->get_idx()); } + void operator()(quantifier * n) {} + void operator()(app * n) {} +}; + +int demodulator::is_subset(expr * e1, expr * e2) const { + uint_set ev1, ev2; + + if (m_manager.is_value(e1)) + return 1; // values are always a subset! + + var_set_proc proc1(ev1); + for_each_expr(proc1, e1); + var_set_proc proc2(ev2); + for_each_expr(proc2, e2); + + return (ev1==ev2 ) ? +2 : // We return +2 if the sets are equal. + (ev1.subset_of(ev2)) ? +1 : + (ev2.subset_of(ev1)) ? -1 : + 0 ; +} + +int demodulator::is_smaller(expr * e1, expr * e2) const { + unsigned sz1 = 0, sz2 = 0; + + // values are always smaller! + if (m_manager.is_value(e1)) + return +1; + else if (m_manager.is_value(e2)) + return -1; + + // interpreted stuff is always better than uninterpreted. + if (!is_uninterp(e1) && is_uninterp(e2)) + return +1; + else if (is_uninterp(e1) && !is_uninterp(e2)) + return -1; + + // two uninterpreted functions are ordered first by the number of + // arguments, then by their id. + if (is_uninterp(e1) && is_uninterp(e2)) { + if (to_app(e1)->get_num_args() < to_app(e2)->get_num_args()) + return +1; + else if (to_app(e1)->get_num_args() > to_app(e2)->get_num_args()) + return -1; + else { + unsigned a = to_app(e1)->get_decl()->get_id(); + unsigned b = to_app(e2)->get_decl()->get_id(); + if (a < b) + return +1; + else if (a > b) + return -1; + } + } + + switch (e1->get_kind()) { + case AST_VAR: sz1 = 1; break; + case AST_QUANTIFIER: sz1 = to_quantifier(e1)->get_depth(); break; + case AST_APP: sz1 = to_app(e1)->get_depth(); break; + default: UNREACHABLE(); + } + + switch (e2->get_kind()) { + case AST_VAR: sz2 = 1; break; + case AST_QUANTIFIER: sz2 = to_quantifier(e2)->get_depth(); break; + case AST_APP: sz2 = to_app(e2)->get_depth(); break; + default: UNREACHABLE(); + } + + return (sz1 == sz2) ? 0 : + (sz1 < sz2) ? +1 : + -1 ; +} + +class max_var_id_proc { + unsigned m_max_var_id; +public: + max_var_id_proc(void):m_max_var_id(0) {} + void operator()(var * n) { + if(n->get_idx() > m_max_var_id) + m_max_var_id = n->get_idx(); + } + void operator()(quantifier * n) {} + void operator()(app * n) {} + unsigned get_max(void) { return m_max_var_id; } +}; + +unsigned demodulator::max_var_id(expr * e) +{ + max_var_id_proc proc; + for_each_expr(proc, e); + return proc.get_max(); +} + +void demodulator::insert_fwd_idx(expr * large, expr * small, quantifier * demodulator) { + SASSERT(large->get_kind() == AST_APP); + SASSERT(demodulator); + SASSERT(large && small); + TRACE("demodulator_fwd", tout << "INSERT: " << mk_pp(demodulator, m_manager) << std::endl; ); + + func_decl * fd = to_app(large)->get_decl(); + + fwd_idx_map::iterator it = m_fwd_idx.find_iterator(fd); + if (it == m_fwd_idx.end()) { + quantifier_set * qs = alloc(quantifier_set, 1); + m_fwd_idx.insert(fd, qs); + it = m_fwd_idx.find_iterator(fd); + } + + SASSERT(it->m_value); + it->m_value->insert(demodulator); + + m_manager.inc_ref(demodulator); + m_manager.inc_ref(large); + m_manager.inc_ref(small); + m_demodulator2lhs_rhs.insert(demodulator, expr_pair(large, small)); +} + +void demodulator::remove_fwd_idx(func_decl * f, quantifier * demodulator) { + TRACE("demodulator_fwd", tout << "REMOVE: " << std::hex << (size_t)demodulator << std::endl; ); + + fwd_idx_map::iterator it = m_fwd_idx.find_iterator(f); + if (it != m_fwd_idx.end()) { + demodulator2lhs_rhs::iterator fit = m_demodulator2lhs_rhs.find_iterator(demodulator); + m_manager.dec_ref(fit->m_value.first); + m_manager.dec_ref(fit->m_value.second); + m_manager.dec_ref(demodulator); + m_demodulator2lhs_rhs.erase(demodulator); + it->m_value->erase(demodulator); + } else { + SASSERT(m_demodulator2lhs_rhs.contains(demodulator)); + } +} + +bool demodulator::check_fwd_idx_consistency(void) { + for (fwd_idx_map::iterator it = m_fwd_idx.begin(); it != m_fwd_idx.end() ; it++ ) { + quantifier_set * set = it->m_value; + SASSERT(set); + + for (quantifier_set::iterator sit = set->begin(); sit != set->end(); sit++) { + if (!m_demodulator2lhs_rhs.contains(*sit)) return false; + } + } + + return true; +} + +void demodulator::show_fwd_idx(std::ostream & out) { + for (fwd_idx_map::iterator it = m_fwd_idx.begin(); it != m_fwd_idx.end() ; it++ ) { + quantifier_set * set = it->m_value; + SASSERT(!set); + + out << it->m_key->get_name() << ": " << std::endl; + + for (quantifier_set::iterator sit = set->begin(); sit != set->end(); sit++) { + out << std::hex << (size_t)*sit << std::endl; + } + } + + out << "D2LR: " << std::endl; + for (demodulator2lhs_rhs::iterator it = m_demodulator2lhs_rhs.begin(); it != m_demodulator2lhs_rhs.end() ; it++) { + out << (size_t) it->m_key << std::endl; + } +} + +bool demodulator::rewrite1(func_decl * f, ptr_vector & m_new_args, expr_ref & np) { + fwd_idx_map::iterator it = m_fwd_idx.find_iterator(f); + if (it != m_fwd_idx.end()) { + TRACE("demodulator_bug", tout << "trying to rewrite: " << f->get_name() << " args:\n"; + for (unsigned i = 0; i < m_new_args.size(); i++) { tout << mk_pp(m_new_args[i], m_manager) << "\n"; }); + quantifier_set::iterator dit = it->m_value->begin(); + quantifier_set::iterator dend = it->m_value->end(); + for ( ; dit != dend ; dit++ ) { + quantifier * d = *dit; + + SASSERT(m_demodulator2lhs_rhs.contains(d)); + expr_pair l_s; + m_demodulator2lhs_rhs.find(d, l_s); + app * large = to_app(l_s.first); + + if (large->get_num_args() != m_new_args.size()) + continue; + + TRACE("demodulator_bug", tout << "Matching with demodulator: " << mk_pp(d, m_manager) << std::endl; ); + + SASSERT(large->get_decl() == f); + + if (m_match_subst(large, l_s.second, m_new_args.c_ptr(), np)) { + TRACE("demodulator_bug", tout << "succeeded...\n" << mk_pp(l_s.second, m_manager) << "\n===>\n" << mk_pp(np, m_manager) << "\n";); + return true; + } + } + } + + return false; +} + +bool demodulator::rewrite_visit_children(app * a) { + bool res=true; + unsigned j = a->get_num_args(); + while (j > 0) { + expr * e = a->get_arg(--j); + if (!m_rewrite_cache.contains(e) || !m_rewrite_cache.get(e).second) { + m_rewrite_todo.push_back(e); + res = false; + } + } + return res; +} + +void demodulator::rewrite_cache(expr * e, expr * new_e, bool done) { + m_rewrite_cache.insert(e, expr_bool_pair(new_e, done)); +} + +expr * demodulator::rewrite(expr * n) { + if (m_fwd_idx.empty()) + return n; + + TRACE("demodulator", tout << "rewrite: " << mk_pp(n, m_manager) << std::endl; ); + app * a; + + SASSERT(m_rewrite_todo.empty()); + m_rewrite_cache.reset(); + + m_rewrite_todo.push_back(n); + while (!m_rewrite_todo.empty()) { + TRACE("demodulator_stack", tout << "STACK: " << std::endl; + for ( unsigned i = 0; iget_kind()) { + case AST_VAR: + rewrite_cache(e, actual, true); + m_rewrite_todo.pop_back(); + break; + case AST_APP: + a = to_app(actual); + if (rewrite_visit_children(a)) { + func_decl * f = a->get_decl(); + m_new_args.reset(); + unsigned num_args = a->get_num_args(); + bool all_untouched=true; + for (unsigned i = 0 ; i < num_args ; i++ ) { + expr * o_child = a->get_arg(i); + expr * n_child; + SASSERT(m_rewrite_cache.contains(o_child) && m_rewrite_cache.get(o_child).second); + expr_bool_pair const & ebp = m_rewrite_cache.get(o_child); + n_child = ebp.first; + if (n_child != o_child) + all_untouched = false; + m_new_args.push_back(n_child); + } + expr_ref np(m_manager); + if (rewrite1(f, m_new_args, np)) { + rewrite_cache(e, np, false); + // No pop. + } else { + if(all_untouched) { + rewrite_cache(e, actual, true); + } + else { + expr_ref na(m_manager); + if (f->get_family_id() != m_manager.get_basic_family_id()) + na = m_manager.mk_app(f, m_new_args.size(), m_new_args.c_ptr()); + else + m_bsimp.reduce(f, m_new_args.size(), m_new_args.c_ptr(), na); + TRACE("demodulator_bug", tout << "e:\n" << mk_pp(e, m_manager) << "\nnew_args: \n"; + for (unsigned i = 0; i < m_new_args.size(); i++) { tout << mk_pp(m_new_args[i], m_manager) << "\n"; } + tout << "=====>\n"; + tout << "na:\n " << mk_pp(na, m_manager) << "\n";); + rewrite_cache(e, na, true); + } + m_rewrite_todo.pop_back(); + } + } + break; + case AST_QUANTIFIER: { + expr * body = to_quantifier(actual)->get_expr(); + if (m_rewrite_cache.contains(body)) { + const expr_bool_pair ebp = m_rewrite_cache.get(body); + SASSERT(ebp.second); + expr * new_body = ebp.first; + quantifier_ref q(m_manager); + q = m_manager.update_quantifier(to_quantifier(actual), new_body); + m_new_exprs.push_back(q); + expr_ref new_q(m_manager); + elim_unused_vars(m_manager, q, new_q); + m_new_exprs.push_back(new_q); + rewrite_cache(e, new_q, true); + m_rewrite_todo.pop_back(); + } else { + m_rewrite_todo.push_back(body); + } + break; + } + default: + UNREACHABLE(); + } + } + + SASSERT(m_rewrite_cache.contains(n)); + const expr_bool_pair & ebp = m_rewrite_cache.get(n); + SASSERT(ebp.second); + expr * r = ebp.first; + + TRACE("demodulator", tout << "rewrite result: " << mk_pp(r, m_manager) << std::endl; ); + + return r; +} + +class demodulator::add_back_idx_proc { + ast_manager & m_manager; + back_idx_map & m_back_idx; + expr * m_expr; +public: + add_back_idx_proc(ast_manager & m, back_idx_map & bi, expr * e):m_manager(m),m_back_idx(bi),m_expr(e) {} + void operator()(var * n) {} + void operator()(quantifier * n) {} + void operator()(app * n) { + // We track only uninterpreted and constant functions. + if (n->get_num_args()==0) return; + SASSERT(m_expr && m_expr != (expr*) 0x00000003); + func_decl * d=n->get_decl(); + if (d->get_family_id() == null_family_id) { + back_idx_map::iterator it = m_back_idx.find_iterator(d); + if (it != m_back_idx.end()) { + SASSERT(it->m_value); + it->m_value->insert(m_expr); + } else { + expr_set * e = alloc(expr_set); + e->insert(m_expr); + m_back_idx.insert(d, e); + } + } + } +}; + +class demodulator::remove_back_idx_proc { + ast_manager & m_manager; + back_idx_map & m_back_idx; + expr * m_expr; +public: + remove_back_idx_proc(ast_manager & m, back_idx_map & bi, expr * e):m_manager(m),m_back_idx(bi),m_expr(e) {} + void operator()(var * n) {} + void operator()(quantifier * n) {} + void operator()(app * n) { + // We track only uninterpreted and constant functions. + if (n->get_num_args()==0) return; + func_decl * d=n->get_decl(); + if (d->get_family_id() == null_family_id) { + back_idx_map::iterator it = m_back_idx.find_iterator(d); + if (it != m_back_idx.end()) { + SASSERT(it->m_value); + it->m_value->remove(m_expr); + } + } + } +}; + +void demodulator::reschedule_processed(func_decl * f) { + //use m_back_idx to find all formulas p in m_processed that contains f { + back_idx_map::iterator it = m_back_idx.find_iterator(f); + if (it != m_back_idx.end()) { + SASSERT(it->m_value); + expr_set temp; + + expr_set::iterator sit = it->m_value->begin(); + expr_set::iterator send = it->m_value->end(); + for ( ; sit != send ; sit++ ) { + expr * p = *sit; + if (m_processed.contains(p)) + temp.insert(p); + } + + sit = temp.begin(); + send = temp.end(); + for ( ; sit != send; sit++) { + expr * p = *sit; + // remove p from m_processed and m_back_idx + m_processed.remove(p); + remove_back_idx_proc proc(m_manager, m_back_idx, p); // this could change it->m_value, thus we need the `temp' set. + for_each_expr(proc, p); + // insert p into m_todo + m_todo.push_back(p); + } + } +} + +bool demodulator::can_rewrite(expr * n, expr * lhs) { + // this is a quick check, we just traverse d and check if there is an expression in d that is an instance of lhs of n'. + // we cannot use the trick used for m_processed, since the main loop would not terminate. + + ptr_vector stack; + expr * curr; + expr_mark visited; + + stack.push_back(n); + + while (!stack.empty()) { + curr = stack.back(); + + if (visited.is_marked(curr)) { + stack.pop_back(); + continue; + } + + switch(curr->get_kind()) { + case AST_VAR: + visited.mark(curr, true); + stack.pop_back(); + break; + + case AST_APP: + if (for_each_expr_args(stack, visited, to_app(curr)->get_num_args(), to_app(curr)->get_args())) { + if (m_match_subst(lhs, curr)) + return true; + visited.mark(curr, true); + stack.pop_back(); + } + break; + + case AST_QUANTIFIER: + if (!for_each_expr_args(stack, visited, to_quantifier(curr)->get_num_patterns(), + to_quantifier(curr)->get_patterns())) { + break; + } + if (!for_each_expr_args(stack, visited, to_quantifier(curr)->get_num_no_patterns(), + to_quantifier(curr)->get_no_patterns())) { + break; + } + if (!visited.is_marked(to_quantifier(curr)->get_expr())) { + stack.push_back(to_quantifier(curr)->get_expr()); + break; + } + + stack.pop_back(); + break; + default: + UNREACHABLE(); + } + } + + return false; +} + +void demodulator::reschedule_demodulators(func_decl * f, expr * lhs) { + // use m_back_idx to find all demodulators d in m_fwd_idx that contains f { + + //ptr_vector to_remove; + + back_idx_map::iterator it = m_back_idx.find_iterator(f); + if (it != m_back_idx.end()) { + SASSERT(it->m_value); + expr_set all_occurrences; + expr_ref l(m_manager); + + expr_set::iterator esit = it->m_value->begin(); + expr_set::iterator esend = it->m_value->end(); + for ( ; esit != esend ; esit++) + all_occurrences.insert(*esit); + + // Run over all f-demodulators + esit = all_occurrences.begin(); + esend = all_occurrences.end(); + for ( ; esit != esend ; esit++ ) { + expr * occ = *esit; + + if (!is_quantifier(occ)) + continue; + + // Use the fwd idx to find out whether this is a demodulator. + demodulator2lhs_rhs::iterator d2lr_it = m_demodulator2lhs_rhs.find_iterator(to_quantifier(occ)); + if (d2lr_it != m_demodulator2lhs_rhs.end()) { + l = d2lr_it->m_value.first; + quantifier_ref d(m_manager); + func_decl_ref df(m_manager); + d = to_quantifier(occ); + df = to_app(l)->get_decl(); + + // Now we know there is an occurrence of f in d + // if n' can rewrite d { + if (can_rewrite(d, lhs)) { + TRACE("demodulator", tout << "Rescheduling: " << std::endl << mk_pp(d, m_manager) << std::endl; ); + // remove d from m_fwd_idx + remove_fwd_idx(df, d); + // remove d from m_back_idx + // just remember it here, because otherwise it and/or esit might become invalid? + // to_remove.insert(d); + remove_back_idx_proc proc(m_manager, m_back_idx, d); + for_each_expr(proc, d); + // insert d into m_todo + m_todo.push_back(d); + } + } + } + } + + //for (ptr_vector::iterator it = to_remove.begin(); it != to_remove.end(); it++) { + // expr * d = *it; + // remove_back_idx_proc proc(m_manager, m_back_idx, d); + // for_each_expr(proc, d); + //} +} + +void demodulator::operator()(unsigned n, expr * const * exprs, proof * const * prs, expr_ref_vector & new_exprs, proof_ref_vector & new_prs) { + if (m_manager.proofs_enabled()) { + // Let us not waste time with proof production + warning_msg("PRE_DEMODULATOR=true is not supported when proofs are enabled."); + new_exprs.append(n, exprs); + new_prs.append(n, prs); + return; + } + + TRACE("demodulator", tout << "before demodulator:\n"; + for ( unsigned i = 0 ; i < n ; i++ ) + tout << mk_pp(exprs[i], m_manager) << std::endl; ); + + // Initially, m_todo contains all formulas. That is, it contains the argument exprs. m_fwd_idx, m_processed, m_back_idx are empty. + unsigned max_vid = 0; + for ( unsigned i = 0 ; i < n ; i++ ) { + m_todo.push_back(exprs[i]); + max_vid = std::max(max_vid, max_var_id(exprs[i])); + } + + m_match_subst.reserve(max_vid); + + while (!m_todo.empty()) { + // let n be the next formula in m_todo. + expr_ref cur(m_manager); + cur = m_todo.back(); + m_todo.pop_back(); + + // rewrite cur using m_fwd_idx, and let n' be the result. + expr * np = rewrite(cur); + // at this point, it should be the case that there is no demodulator in m_fwd_idx that can rewrite n'. + SASSERT(rewrite(np)==np); + + // if (n' is not a demodulator) { + expr_ref large(m_manager), small(m_manager); + if (!is_demodulator(np, large, small)) { + // insert n' into m_processed + m_processed.insert(np); + // update m_back_idx (traverse n' and for each uninterpreted function declaration f in n' add the entry f->n' to m_back_idx) + add_back_idx_proc proc(m_manager, m_back_idx, np); + for_each_expr(proc, np); + } else { + // np is a demodulator that allows us to replace 'large' with 'small'. + TRACE("demodulator", tout << "Found demodulator: " << std::endl; + tout << mk_pp(large.get(), m_manager) << std::endl << " ---> " << + std::endl << mk_pp(small.get(), m_manager) << std::endl; ); + + TRACE("demodulator_s", tout << "Found demodulator: " << std::endl; + tout << to_app(large)->get_decl()->get_name() << + "[" << to_app(large)->get_depth() << "]" << " ---> "; + if (is_app(small)) + tout << to_app(small)->get_decl()->get_name() << + "[" << to_app(small)->get_depth() << "]" << std::endl; + else + tout << mk_pp(small.get(), m_manager) << std::endl; ); + + // let f be the top symbol of n' + SASSERT(is_app(large)); + func_decl * f = to_app(large)->get_decl(); + + reschedule_processed(f); + reschedule_demodulators(f, large); + + // insert n' into m_fwd_idx + insert_fwd_idx(large, small, to_quantifier(np)); + + // update m_back_idx + add_back_idx_proc proc(m_manager, m_back_idx, np); + for_each_expr(proc, np); + } + } + + // the result is the contents of m_processed + all demodulators in m_fwd_idx. + obj_hashtable::iterator pit = m_processed.begin(); + obj_hashtable::iterator pend = m_processed.end(); + for ( ; pit != pend ; pit++ ) { + new_exprs.push_back(*pit); + TRACE("demodulator", tout << mk_pp(*pit, m_manager) << std::endl; ); + } + + fwd_idx_map::iterator fit = m_fwd_idx.begin(); + fwd_idx_map::iterator fend = m_fwd_idx.end(); + for ( ; fit != fend ; fit++ ) { + if (fit->m_value) { + quantifier_set::iterator dit = fit->m_value->begin(); + quantifier_set::iterator dend = fit->m_value->end(); + for ( ; dit != dend ; dit++ ) { + expr * e = *dit; + new_exprs.push_back(e); + TRACE("demodulator", tout << mk_pp(*dit, m_manager) << std::endl; ); + } + } + } + + TRACE("demodulator", tout << "after demodulator:\n"; + for ( unsigned i = 0 ; i < new_exprs.size() ; i++ ) + tout << mk_pp(new_exprs[i].get(), m_manager) << std::endl; ); +} + + +demodulator::match_subst::match_subst(ast_manager & m): + m_manager(m), + m_subst(m) { +} + +/** + \brief Auxiliary functor used to implement optimization in match_args. See comment there. +*/ +struct match_args_aux_proc { + substitution & m_subst; + struct no_match {}; + + match_args_aux_proc(substitution & s):m_subst(s) {} + + void operator()(var * n) { + expr_offset r; + if (m_subst.find(n, 0, r)) { + if (r.get_expr() != n) { + SASSERT(r.get_offset() == 1); + throw no_match(); + } + else { + m_subst.insert(n, 0, expr_offset(n, 1)); + } + } + } + void operator()(quantifier * n) { throw no_match(); } + void operator()(app * n) {} +}; + +bool demodulator::match_subst::match_args(app * lhs, expr * const * args) { + m_cache.reset(); + m_todo.reset(); + + // fill todo-list, and perform quick success/failure tests + m_all_args_eq = true; + unsigned num_args = lhs->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * t_arg = lhs->get_arg(i); + expr * i_arg = args[i]; + if (t_arg != i_arg) + m_all_args_eq = false; + if (is_app(t_arg) && is_app(i_arg) && to_app(t_arg)->get_decl() != to_app(i_arg)->get_decl()) { + // quick failure... + return false; + } + m_todo.push_back(expr_pair(t_arg, i_arg)); + } + + if (m_all_args_eq) { + // quick success worked... + return true; + } + + m_subst.reset(); + + while (!m_todo.empty()) { + expr_pair const & p = m_todo.back(); + + if (is_var(p.first)) { + expr_offset r; + if (m_subst.find(to_var(p.first), 0, r)) { + if (r.get_expr() != p.second) + return false; + } + else { + m_subst.insert(to_var(p.first), 0, expr_offset(p.second, 1)); + } + m_todo.pop_back(); + continue; + } + + if (is_var(p.second)) + return false; + + // we may have nested quantifiers. + if (is_quantifier(p.first) || is_quantifier(p.second)) + return false; + + SASSERT(is_app(p.first) && is_app(p.second)); + + if (to_app(p.first)->is_ground() && !to_app(p.second)->is_ground()) + return false; + + if (p.first == p.second && to_app(p.first)->is_ground()) { + SASSERT(to_app(p.second)->is_ground()); + m_todo.pop_back(); + continue; + } + + if (m_cache.contains(p)) { + m_todo.pop_back(); + continue; + } + + if (p.first == p.second) { + // p.first and p.second is not ground... + + // Traverse p.first and check whether every variable X:0 in p.first + // 1) is unbounded (then we bind X:0 -> X:1) + // 2) or, is already bounded to X:1 + // If that is, the case, we execute: + // m_todo.pop_back(); + // m_cache.insert(p); + // continue; + // Otherwise + // return false; + match_args_aux_proc proc(m_subst); + try { + for_each_expr(proc, p.first); + // succeeded + m_todo.pop_back(); + m_cache.insert(p); + continue; + } + catch (match_args_aux_proc::no_match) { + return false; + } + } + + app * n1 = to_app(p.first); + app * n2 = to_app(p.second); + + if (n1->get_decl() != n2->get_decl()) + return false; + + unsigned num_args1 = n1->get_num_args(); + if (num_args1 != n2->get_num_args()) + return false; + + m_todo.pop_back(); + + if (num_args1 == 0) + continue; + + m_cache.insert(p); + unsigned j = num_args1; + while (j > 0) { + --j; + m_todo.push_back(expr_pair(n1->get_arg(j), n2->get_arg(j))); + } + } + return true; +} + + +bool demodulator::match_subst::operator()(app * lhs, expr * rhs, expr * const * args, expr_ref & new_rhs) { + if (match_args(lhs, args)) { + if (m_all_args_eq) { + // quick success... + new_rhs = rhs; + return true; + } + unsigned deltas[2] = { 0, 0 }; + m_subst.apply(2, deltas, expr_offset(rhs, 0), new_rhs); + return true; + } + return false; +} + +bool demodulator::match_subst::operator()(expr * t, expr * i) { + m_cache.reset(); + m_todo.reset(); + if (is_var(t)) + return true; + if (is_app(t) && is_app(i) && to_app(t)->get_decl() == to_app(i)->get_decl() && to_app(t)->get_num_args() == to_app(i)->get_num_args()) { + return match_args(to_app(t), to_app(i)->get_args()); + } + return false; +} diff --git a/lib/demodulator.h b/lib/demodulator.h new file mode 100644 index 000000000..1925368e5 --- /dev/null +++ b/lib/demodulator.h @@ -0,0 +1,265 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + demodulator.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-04-12. + +Revision History: + +--*/ +#ifndef _DEMODULATOR_H_ +#define _DEMODULATOR_H_ + +#include"ast.h" +#include"substitution.h" +#include"obj_hashtable.h" +#include"obj_pair_hashtable.h" +#include"array_map.h" +#include"basic_simplifier_plugin.h" + +/** + \brief Apply demodulators as a preprocessing technique. + +In first-order theorem proving (FOTP), a demodulator is a universally quantified formula of the form: + +Forall X1, ..., Xn. L[X1, ..., Xn] = R[X1, ..., Xn] +Where L[X1, ..., Xn] contains all variables in R[X1, ..., Xn], and +L[X1, ..., Xn] is "bigger" than R[X1, ...,Xn]. + +The idea is to replace something big L[X1, ..., Xn] with something smaller R[X1, ..., Xn]. +In FOTP, they use term orderings to decide what does it mean to be smaller. +We are using demodulators in a different context (pre-processing). +So, I suggest we have a virtual method is_smaller for comparing expressions. +The default implementation just compares the size of the expressions. + +Similarly, in our context, formulas using iff are also demodulators. +Forall X1, ..., Xn. L[X1, ..., Xn] iff R[X1, ..., Xn] + +After selecting the demodulators, we traverse the rest of the formula looking for instances of L[X1, ..., Xn]. +Whenever we find an instance, we replace it with the associated instance of R[X1, ..., Xn]. + +For example, suppose we have + +Forall x, y. f(x+y, y) = y +and +f(g(b) + h(c), h(c)) <= 0 + +The term f(g(b) + h(c), h(c)) is an instance of f(x+y, y) if we replace x <- g(b) and y <- h(c). +So, we can replace it with "y" which is bound to h(c) in this example. So, the result of the transformation is: + +Forall x, y. f(x+y, y) = y +and +h(c) <= 0 + +In the first implementation, let us ignore theory matching. That is, +for us the term f(a+1) is not an instance of f(1+x), because the +matcher doesn't know + is commutative. Observe the demodulator is +*not* copied to the macro manager in this case. + +Another complication is when we are looking for instances inside other universally quantified formulas. +The problem is that both formulas (demodular) and target are reusing variables names (ids). +To avoid renaming, we use offsets. The idea is to represent renames implicitly. In this case, +each offset is a different "variable bank". A pair (expr, offset) is essentially an expression +where every variable in expr is assumed to be from the "bank" offset. + +The class substitution (in substitution.h) manages offsets for us. +The class matcher (in matcher.h) can be use to test whether an expression is an instance of another one. + +Finally, there is the problem when we have N demodulators (where N is big), and a big formula, and we want +to traverse the formula only once looking for opportunities for applying these N demodulators. +We want to efficiently find the applicable demodulars. +We can start with a simple optimization that given a func_decl it returns the set of demodulators that start with this declaration. +For example, suppose we have the demodulators. + forall x, f(x, g(0)) = 10 + forall x, f(g(h(x)), g(1)) = 20 +Then, in our "index" f would map to these two demodulators. + +As a final optimization, we should adapt the code to use substitution-trees. +The current implementation in Z3 is not efficient, and I think it is buggy. +So, it would be great to replace it with a new one. +The code in spc_rewriter.* does something like that. We cannot reuse this code directly since it is meant +for the superposion engine in Z3, but we can adapt it for our needs in the preprocessor. + +*/ +class demodulator { + class rewrite_proc; + class add_back_idx_proc; + class remove_back_idx_proc; + class can_rewrite_proc; + + typedef std::pair expr_bool_pair; + + class plugin { + ast_manager& m_manager; + public: + plugin(ast_manager& m): m_manager(m) { } + void ins_eh(expr* k, expr_bool_pair v) { m_manager.inc_ref(k); m_manager.inc_ref(v.first); } + void del_eh(expr* k, expr_bool_pair v) { m_manager.dec_ref(k); m_manager.dec_ref(v.first); } + static unsigned to_int(expr const * k) { return k->get_id(); } + }; + typedef array_map expr_map; + + typedef std::pair expr_pair; + typedef obj_hashtable expr_set; + typedef obj_map back_idx_map; + typedef obj_hashtable quantifier_set; + typedef obj_map fwd_idx_map; + typedef obj_map demodulator2lhs_rhs; + typedef expr_map rewrite_cache_map; + + /** + \brief Custom matcher & substitution application + */ + class match_subst { + typedef std::pair expr_pair; + typedef obj_pair_hashtable cache; + + void reset(); + + ast_manager & m_manager; + substitution m_subst; + cache m_cache; + svector m_todo; + bool m_all_args_eq; + + bool match_args(app * t, expr * const * args); + + public: + match_subst(ast_manager & m); + void reserve(unsigned max_vid) { m_subst.reserve(2, max_vid+1); } + /** + \brief Let f be the top symbol of lhs. If (f args) is an + instance of lhs, that is, there is a substitution s + s.t. s[lhs] = (f args), then return true and store s[rhs] + into new_rhs. Where s[t] represents the application of the + substitution s into t. + + Assumptions, the variables in lhs and (f args) are assumed to be distinct. + So, (f x y) matches (f y x). + Moreover, the result should be in terms of the variables in (f args). + */ + bool operator()(app * lhs, expr * rhs, expr * const * args, expr_ref & new_rhs); + + /** + \brief Return true if \c i is an instance of \c t. + */ + bool operator()(expr * t, expr * i); + }; + + ast_manager & m_manager; + match_subst m_match_subst; + basic_simplifier_plugin & m_bsimp; + fwd_idx_map m_fwd_idx; + back_idx_map m_back_idx; + demodulator2lhs_rhs m_demodulator2lhs_rhs; + expr_ref_buffer m_todo; + obj_hashtable m_processed; + ptr_vector m_new_args; + + expr_ref_buffer m_rewrite_todo; + rewrite_cache_map m_rewrite_cache; + expr_ref_buffer m_new_exprs; + + void insert_fwd_idx(expr * large, expr * small, quantifier * demodulator); + void remove_fwd_idx(func_decl * f, quantifier * demodulator); + bool check_fwd_idx_consistency(void); + void show_fwd_idx(std::ostream & out); + bool is_demodulator(expr * e, expr_ref & large, expr_ref & small) const; + bool can_rewrite(expr * n, expr * lhs); + + expr * rewrite(expr * n); + bool rewrite1(func_decl * f, ptr_vector & m_new_args, expr_ref & np); + bool rewrite_visit_children(app * a); + void rewrite_cache(expr * e, expr * new_e, bool done); + void reschedule_processed(func_decl * f); + void reschedule_demodulators(func_decl * f, expr * np); + unsigned max_var_id(expr * e); + +protected: + // is_smaller returns -1 for e1e2. + virtual int is_smaller(expr * e1, expr * e2) const; + + // is_subset returns -1 for e1 subset e2, +1 for e2 subset e1, 0 else. + virtual int is_subset(expr * e1, expr * e2) const; + +public: + demodulator(ast_manager & m, basic_simplifier_plugin & p); + virtual ~demodulator(); + + void operator()(unsigned n, expr * const * exprs, proof * const * prs, expr_ref_vector & new_exprs, proof_ref_vector & new_prs); + + /** + Given a demodulator (aka rewrite rule) of the form + Forall X. L[X] = R[X] + We say the top symbol is the first symbol in L. + For example: f is the top symbol in + Forall x, f(h(x)) = x + 1 + + The rewrite engine main loop is based on the DISCOUNT loop used in first-order theorem provers. + + Main structures: + - m_todo: The todo-stack of formulas to be processed. + - m_fwd_idx: "Forward index" for finding efficiently which demodulators can be used to rewrite an expression. + We organize this set as a mapping from func_decl to a set of demodulators which start with the same top symbol. + - m_processed: The set of already processed formulas. We can represent it using a hashtable. + - m_back_idx: "Backward index" we use it to find efficiently which already processed expressions and demodulators may be rewritten + by a new demodulator. Again, we use a very simple index, for each uninterpreted function symbol (ignore constants) + f, store the expressions in m_processed and the demodulators that contain f. + If you prefer, you may use two m_back_idxs (one for formulas in m_processed and another for demodulators in m_fwd_idx). + + Initially, m_todo contains all formulas. That is, it contains the argument exprs. m_fwd_idx, m_processed, m_back_idx are empty. + + while (m_todo is not empty) { + let n be the next formula in m_todo. + rewrite n using m_fwd_idx, and let n' be the result. + // at this point, it should be the case that there is no demodulator in m_fwd_idx that can rewrite n'. + if (n' is not a demodulator) { + insert n' into m_processed + update m_back_idx (traverse n' and for each uninterpreted function declaration f in n' add the entry f->n' to m_back_idx) + } + else { + let f be the top symbol of n' + use m_back_idx to find all formulas p in m_processed that contains f { + remove p from m_processed + remove p from m_back_idx + insert p into m_todo + } + use m_back_idx to find all demodulators d in m_fwd_idx that contains f { + if n' can rewrite d { + // this is a quick check, we just traverse d and check if there is an expression in d that is an instance of lhs of n'. + // we cannot use the trick used for m_processed, since the main loop would not terminate. + remove d from m_fwd_idx + remode d from m_back_idx + insert p into m_todo + } + } + insert n' into m_fwd_idx + update m_back_idx + } + } + the result is the contents of m_processed + all demodulators in m_fwd_idx. + + Note: to remove p from m_back_idx, we need to traverse p, and for every function declartion f in p, we should remove the entry f->p from m_back_idx. + + Note: we can implement m_back_idx for formulas as: + typedef obj_hashtable expr_set; + obj_map m_back_idx; + we should represent the sets as hashtables because we want to be able to efficiently remove elements from these sets. + ptr_vector m_expr_set_to_delete; // same trick we used in macro_manager. + we can use a similar structure for m_back_idx and m_fwd_idx for demodulators. + + Note: m_processed should be obj_hashtable since we want to remove elements from there efficiently. + */ +}; + +#endif /* _DEMODULATOR_H_ */ + diff --git a/lib/dependency.h b/lib/dependency.h new file mode 100644 index 000000000..6ce9b2025 --- /dev/null +++ b/lib/dependency.h @@ -0,0 +1,326 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dependency.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-12-10. + +Revision History: + +--*/ +#ifndef _DEPENDENCY_H_ +#define _DEPENDENCY_H_ + +#include"vector.h" +#include"region.h" + +template +class dependency_manager { +public: + typedef typename C::value value; + typedef typename C::value_manager value_manager; + typedef typename C::allocator allocator; + + class dependency { + unsigned m_ref_count:30; + unsigned m_mark:1; + unsigned m_leaf:1; + friend class dependency_manager; + dependency(bool leaf): + m_ref_count(0), + m_mark(false), + m_leaf(leaf) { + } + bool is_marked() const { return m_mark == 1; } + void mark() { m_mark = true; } + void unmark() { m_mark = false; } + public: + unsigned get_ref_count() const { return m_ref_count; } + bool is_leaf() const { return m_leaf == 1; } + }; + +private: + struct join : public dependency { + dependency * m_children[2]; + join(dependency * d1, dependency * d2): + dependency(false) { + m_children[0] = d1; + m_children[1] = d2; + } + }; + + struct leaf : public dependency { + value m_value; + leaf(value const & v): + dependency(true), + m_value(v) { + } + }; + + static join * to_join(dependency * d) { SASSERT(!d->is_leaf()); return static_cast(d); } + static leaf * to_leaf(dependency * d) { SASSERT(d->is_leaf()); return static_cast(d); } + + value_manager & m_vmanager; + allocator & m_allocator; + ptr_vector m_todo; + + void inc_ref(value const & v) { + if (C::ref_count) + m_vmanager.inc_ref(v); + } + + void dec_ref(value const & v) { + if (C::ref_count) + m_vmanager.dec_ref(v); + } + + void del(dependency * d) { + SASSERT(d); + m_todo.push_back(d); + while (!m_todo.empty()) { + d = m_todo.back(); + m_todo.pop_back(); + if (d->is_leaf()) { + dec_ref(to_leaf(d)->m_value); + to_leaf(d)->~leaf(); + m_allocator.deallocate(sizeof(leaf), to_leaf(d)); + } + else { + for (unsigned i = 0; i < 2; i++) { + dependency * c = to_join(d)->m_children[i]; + SASSERT(c->m_ref_count > 0); + c->m_ref_count--; + if (c->m_ref_count == 0) + m_todo.push_back(c); + } + to_join(d)->~join(); + m_allocator.deallocate(sizeof(join), to_join(d)); + } + } + } + + void unmark_todo() { + typename ptr_vector::iterator it = m_todo.begin(); + typename ptr_vector::iterator end = m_todo.end(); + for (; it != end; ++it) { + (*it)->unmark(); + } + m_todo.reset(); + } + +public: + + dependency_manager(value_manager & m, allocator & a): + m_vmanager(m), + m_allocator(a) { + } + + void inc_ref(dependency * d) { + if (d) + d->m_ref_count++; + } + + void dec_ref(dependency * d) { + if (d) { + SASSERT(d->m_ref_count > 0); + d->m_ref_count--; + if (d->m_ref_count == 0) + del(d); + } + } + + dependency * mk_empty() { + return 0; + } + + dependency * mk_leaf(value const & v) { + void * mem = m_allocator.allocate(sizeof(leaf)); + inc_ref(v); + return new (mem) leaf(v); + } + + dependency * mk_join(dependency * d1, dependency * d2) { + if (d1 == 0) { + return d2; + } + else if (d2 == 0) { + return d1; + } + else if (d1 == d2) { + return d1; + } + else { + void * mem = m_allocator.allocate(sizeof(join)); + inc_ref(d1); inc_ref(d2); + return new (mem) join(d1, d2); + } + } + + bool contains(dependency * d, value const & v) { + if (d) { + m_todo.reset(); + d->mark(); + m_todo.push_back(d); + unsigned qhead = 0; + while (qhead < m_todo.size()) { + dependency * d = m_todo[qhead]; + qhead++; + if (d->is_leaf()) { + if (to_leaf(d)->m_value == v) { + unmark_todo(); + return true; + } + } + else { + for (unsigned i = 0; i < 2; i++) { + dependency * child = to_join(d)->m_children[i]; + if (!child->is_marked()) { + m_todo.push_back(child); + child->mark(); + } + } + } + } + unmark_todo(); + } + return false; + } + + void linearize(dependency * d, vector & vs) { + if (d) { + m_todo.reset(); + d->mark(); + m_todo.push_back(d); + unsigned qhead = 0; + while (qhead < m_todo.size()) { + dependency * d = m_todo[qhead]; + qhead++; + if (d->is_leaf()) { + vs.push_back(to_leaf(d)->m_value); + } + else { + for (unsigned i = 0; i < 2; i++) { + dependency * child = to_join(d)->m_children[i]; + if (!child->is_marked()) { + m_todo.push_back(child); + child->mark(); + } + } + } + } + unmark_todo(); + } + } +}; + +/** + \brief Version of the dependency_manager where + memory management is scoped (i.e., reference counting is ignored), + and push_scope/pop_scope are used instead. + + Value must be a primitive type such as an integer or pointer. +*/ +template +class scoped_dependency_manager { + + class config { + public: + static const bool ref_count = true; + + typedef Value value; + + class value_manager { + public: + void inc_ref(value const & v) { + } + + void dec_ref(value const & v) { + } + }; + + class allocator { + region m_region; + public: + void * allocate(size_t sz) { + return m_region.allocate(sz); + } + + void deallocate(size_t sz, void * mem) { + } + + void push_scope() { + m_region.push_scope(); + } + + void pop_scope(unsigned num) { + m_region.pop_scope(num); + } + + void reset() { + m_region.reset(); + } + }; + }; + + typedef dependency_manager dep_manager; +public: + typedef typename dep_manager::dependency dependency; + typedef Value value; + +private: + typename config::value_manager m_vmanager; + typename config::allocator m_allocator; + dep_manager m_dep_manager; + +public: + scoped_dependency_manager(): + m_dep_manager(m_vmanager, m_allocator) { + } + + dependency * mk_empty() { + return m_dep_manager.mk_empty(); + } + + dependency * mk_leaf(value const & v) { + return m_dep_manager.mk_leaf(v); + } + + dependency * mk_join(dependency * d1, dependency * d2) { + return m_dep_manager.mk_join(d1, d2); + } + + bool contains(dependency * d, value const & v) { + return m_dep_manager.contains(d, v); + } + + void linearize(dependency * d, vector & vs) { + return m_dep_manager.linearize(d, vs); + } + + void reset() { + m_allocator.reset(); + } + + void push_scope() { + m_allocator.push_scope(); + } + + void pop_scope(unsigned num_scopes) { + m_allocator.pop_scope(num_scopes); + } +}; + +// Implement old dependency manager used by interval and groebner +typedef scoped_dependency_manager v_dependency_manager; +typedef scoped_dependency_manager::dependency v_dependency; + +#endif /* _DEPENDENCY_H_ */ + diff --git a/lib/der.cpp b/lib/der.cpp new file mode 100644 index 000000000..6df1a0105 --- /dev/null +++ b/lib/der.cpp @@ -0,0 +1,639 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + der.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-27. + +Revision History: + + Christoph Wintersteiger, 2010-03-30: Added Destr. Multi-Equality Resolution + +--*/ +#include"der.h" +#include"occurs.h" +#include"for_each_expr.h" +#include"rewriter_def.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"ast_smt2_pp.h" +#include"tactical.h" + +static bool is_var(expr * e, unsigned num_decls) { + return is_var(e) && to_var(e)->get_idx() < num_decls; +} + +static bool is_neg_var(ast_manager & m, expr * e, unsigned num_decls) { + return m.is_not(e) && is_var(to_app(e)->get_arg(0)) && to_var(to_app(e)->get_arg(0))->get_idx() < num_decls; +} + +/** + \brief Return true if \c e is of the form (not (= VAR t)) or (not (iff VAR t)) or (iff VAR t) or (iff (not VAR) t) or (VAR IDX) or (not (VAR IDX)). + The last case can be viewed +*/ +bool der::is_var_diseq(expr * e, unsigned num_decls, var * & v, expr_ref & t) { + // (not (= VAR t)) and (not (iff VAR t)) cases + if (m_manager.is_not(e) && (m_manager.is_eq(to_app(e)->get_arg(0)) || m_manager.is_iff(to_app(e)->get_arg(0)))) { + app * eq = to_app(to_app(e)->get_arg(0)); + SASSERT(m_manager.is_eq(eq) || m_manager.is_iff(eq)); + expr * lhs = eq->get_arg(0); + expr * rhs = eq->get_arg(1); + if (!is_var(lhs, num_decls) && !is_var(rhs, num_decls)) + return false; + if (!is_var(lhs, num_decls)) + std::swap(lhs, rhs); + SASSERT(is_var(lhs, num_decls)); + // Remark: Occurs check is not necessary here... the top-sort procedure will check for cycles... + // if (occurs(lhs, rhs)) { + // return false; + // } + v = to_var(lhs); + t = rhs; + TRACE("der", tout << mk_pp(e, m_manager) << "\n";); + return true; + } + // (iff VAR t) and (iff (not VAR) t) cases + else if (m_manager.is_iff(e)) { + expr * lhs = to_app(e)->get_arg(0); + expr * rhs = to_app(e)->get_arg(1); + // (iff VAR t) case + if (is_var(lhs, num_decls) || is_var(rhs, num_decls)) { + if (!is_var(lhs, num_decls)) + std::swap(lhs, rhs); + SASSERT(is_var(lhs, num_decls)); + // Remark: Occurs check is not necessary here... the top-sort procedure will check for cycles... + // if (occurs(lhs, rhs)) { + // return false; + // } + v = to_var(lhs); + t = m_manager.mk_not(rhs); + m_new_exprs.push_back(t); + TRACE("der", tout << mk_pp(e, m_manager) << "\n";); + return true; + } + // (iff (not VAR) t) case + else if (is_neg_var(m_manager, lhs, num_decls) || is_neg_var(m_manager, rhs, num_decls)) { + if (!is_neg_var(m_manager, lhs, num_decls)) + std::swap(lhs, rhs); + SASSERT(is_neg_var(m_manager, lhs, num_decls)); + expr * lhs_var = to_app(lhs)->get_arg(0); + // Remark: Occurs check is not necessary here... the top-sort procedure will check for cycles... + // if (occurs(lhs_var, rhs)) { + // return false; + // } + v = to_var(lhs_var); + t = rhs; + TRACE("der", tout << mk_pp(e, m_manager) << "\n";); + return true; + } + else { + return false; + } + } + // VAR != false case + else if (is_var(e, num_decls)) { + t = m_manager.mk_false(); + v = to_var(e); + TRACE("der", tout << mk_pp(e, m_manager) << "\n";); + return true; + } + // VAR != true case + else if (is_neg_var(m_manager, e, num_decls)) { + t = m_manager.mk_true(); + v = to_var(to_app(e)->get_arg(0)); + TRACE("der", tout << mk_pp(e, m_manager) << "\n";); + return true; + } + else { + return false; + } +} + +void der::operator()(quantifier * q, expr_ref & r, proof_ref & pr) { + bool reduced = false; + pr = 0; + r = q; + + TRACE("der", tout << mk_pp(q, m_manager) << "\n";); + + // Keep applying it until r doesn't change anymore + do { + proof_ref curr_pr(m_manager); + q = to_quantifier(r); + reduce1(q, r, curr_pr); + if (q != r) + reduced = true; + if (m_manager.proofs_enabled()) { + pr = m_manager.mk_transitivity(pr, curr_pr); + } + } while (q != r && is_quantifier(r)); + + // Eliminate variables that have become unused + if (reduced && is_forall(r)) { + quantifier * q = to_quantifier(r); + elim_unused_vars(m_manager, q, r); + if (m_manager.proofs_enabled()) { + proof * p1 = m_manager.mk_elim_unused_vars(q, r); + pr = m_manager.mk_transitivity(pr, p1); + } + } + m_new_exprs.reset(); +} + +void der::reduce1(quantifier * q, expr_ref & r, proof_ref & pr) { + if (!is_forall(q)) { + pr = 0; + r = q; + return; + } + + expr * e = q->get_expr(); + unsigned num_decls = q->get_num_decls(); + var * v = 0; + expr_ref t(m_manager); + + if (m_manager.is_or(e)) { + unsigned num_args = to_app(e)->get_num_args(); + unsigned i = 0; + unsigned diseq_count = 0; + unsigned largest_vinx = 0; + + m_map.reset(); + m_pos2var.reset(); + m_inx2var.reset(); + + m_pos2var.reserve(num_args, -1); + + // Find all disequalities + for (; i < num_args; i++) { + if (is_var_diseq(to_app(e)->get_arg(i), num_decls, v, t)) { + unsigned idx = v->get_idx(); + if(m_map.get(idx, 0) == 0) { + m_map.reserve(idx + 1, 0); + m_inx2var.reserve(idx + 1, 0); + + m_map[idx] = t; + m_inx2var[idx] = v; + m_pos2var[i] = idx; + diseq_count++; + largest_vinx = (idx>largest_vinx) ? idx : largest_vinx; + } + } + } + + if (diseq_count > 0) { + get_elimination_order(); + SASSERT(m_order.size() <= diseq_count); // some might be missing because of cycles + + if (!m_order.empty()) { + create_substitution(largest_vinx + 1); + apply_substitution(q, r); + } + } + else { + TRACE("der_bug", tout << "Did not find any diseq\n" << mk_pp(q, m_manager) << "\n";); + r = q; + } + } + // Remark: get_elimination_order/top-sort checks for cycles, but it is not invoked for unit clauses. + // So, we must perform a occurs check here. + else if (is_var_diseq(e, num_decls, v, t) && !occurs(v, t)) { + r = m_manager.mk_false(); + } + else + r = q; + + if (m_manager.proofs_enabled()) { + pr = r == q ? 0 : m_manager.mk_der(q, r); + } +} + +void der_sort_vars(ptr_vector & vars, ptr_vector & definitions, unsigned_vector & order) { + order.reset(); + + // eliminate self loops, and definitions containing quantifiers. + bool found = false; + for (unsigned i = 0; i < definitions.size(); i++) { + var * v = vars[i]; + expr * t = definitions[i]; + if (t == 0 || has_quantifiers(t) || occurs(v, t)) + definitions[i] = 0; + else + found = true; // found at least one candidate + } + + if (!found) + return; + + typedef std::pair frame; + svector todo; + + expr_fast_mark1 visiting; + expr_fast_mark2 done; + + unsigned vidx, num; + + for (unsigned i = 0; i < definitions.size(); i++) { + if (definitions[i] == 0) + continue; + var * v = vars[i]; + SASSERT(v->get_idx() == i); + SASSERT(todo.empty()); + todo.push_back(frame(v, 0)); + while (!todo.empty()) { + start: + frame & fr = todo.back(); + expr * t = fr.first; + if (t->get_ref_count() > 1 && done.is_marked(t)) { + todo.pop_back(); + continue; + } + switch (t->get_kind()) { + case AST_VAR: + vidx = to_var(t)->get_idx(); + if (fr.second == 0) { + CTRACE("der_bug", vidx >= definitions.size(), tout << "vidx: " << vidx << "\n";); + // Remark: The size of definitions may be smaller than the number of variables occuring in the quantified formula. + if (definitions.get(vidx, 0) != 0) { + if (visiting.is_marked(t)) { + // cycle detected: remove t + visiting.reset_mark(t); + definitions[vidx] = 0; + } + else { + visiting.mark(t); + fr.second = 1; + todo.push_back(frame(definitions[vidx], 0)); + goto start; + } + } + } + else { + SASSERT(fr.second == 1); + if (definitions.get(vidx, 0) != 0) { + visiting.reset_mark(t); + order.push_back(vidx); + } + else { + // var was removed from the list of candidate vars to elim cycle + // do nothing + } + } + if (t->get_ref_count() > 1) + done.mark(t); + todo.pop_back(); + break; + case AST_QUANTIFIER: + UNREACHABLE(); + todo.pop_back(); + break; + case AST_APP: + num = to_app(t)->get_num_args(); + while (fr.second < num) { + expr * arg = to_app(t)->get_arg(fr.second); + fr.second++; + if (arg->get_ref_count() > 1 && done.is_marked(arg)) + continue; + todo.push_back(frame(arg, 0)); + goto start; + } + if (t->get_ref_count() > 1) + done.mark(t); + todo.pop_back(); + break; + default: + UNREACHABLE(); + todo.pop_back(); + break; + } + } + } +} + +void der::get_elimination_order() { + m_order.reset(); + + TRACE("top_sort", + tout << "DEFINITIONS: " << std::endl; + for(unsigned i = 0; i < m_map.size(); i++) + if(m_map[i]) tout << "VAR " << i << " = " << mk_pp(m_map[i], m_manager) << std::endl; + ); + + // der::top_sort ts(m_manager); + der_sort_vars(m_inx2var, m_map, m_order); + + TRACE("der", + tout << "Elimination m_order:" << std::endl; + for(unsigned i=0; iget_expr(); + unsigned num_args=to_app(e)->get_num_args(); + + // get a new expression + m_new_args.reset(); + for(unsigned i = 0; i < num_args; i++) { + int x = m_pos2var[i]; + if (x != -1 && m_map[x] != 0) + continue; // this is a disequality with definition (vanishes) + + m_new_args.push_back(to_app(e)->get_arg(i)); + } + + unsigned sz = m_new_args.size(); + expr_ref t(m_manager); + t = (sz == 1) ? m_new_args[0] : m_manager.mk_or(sz, m_new_args.c_ptr()); + expr_ref new_e(m_manager); + m_subst(t, m_subst_map.size(), m_subst_map.c_ptr(), new_e); + + // don't forget to update the quantifier patterns + expr_ref_buffer new_patterns(m_manager); + expr_ref_buffer new_no_patterns(m_manager); + for (unsigned j = 0; j < q->get_num_patterns(); j++) { + expr_ref new_pat(m_manager); + m_subst(q->get_pattern(j), m_subst_map.size(), m_subst_map.c_ptr(), new_pat); + new_patterns.push_back(new_pat); + } + + for (unsigned j = 0; j < q->get_num_no_patterns(); j++) { + expr_ref new_nopat(m_manager); + m_subst(q->get_no_pattern(j), m_subst_map.size(), m_subst_map.c_ptr(), new_nopat); + new_no_patterns.push_back(new_nopat); + } + + r = m_manager.update_quantifier(q, new_patterns.size(), new_patterns.c_ptr(), + new_no_patterns.size(), new_no_patterns.c_ptr(), new_e); +} + + +struct der_rewriter_cfg : public default_rewriter_cfg { + der m_der; + + der_rewriter_cfg(ast_manager & m):m_der(m) {} + + ast_manager & m() const { return m_der.m(); } + + bool reduce_quantifier(quantifier * old_q, + expr * new_body, + expr * const * new_patterns, + expr * const * new_no_patterns, + expr_ref & result, + proof_ref & result_pr) { + quantifier_ref q1(m()); + q1 = m().update_quantifier(old_q, old_q->get_num_patterns(), new_patterns, old_q->get_num_no_patterns(), new_no_patterns, new_body); + m_der(q1, result, result_pr); + return true; + } +}; + +template class rewriter_tpl; + +struct der_rewriter::imp : public rewriter_tpl { + der_rewriter_cfg m_cfg; + imp(ast_manager & m): + rewriter_tpl(m, m.proofs_enabled(), m_cfg), + m_cfg(m) { + } +}; + +der_rewriter::der_rewriter(ast_manager & m) { + m_imp = alloc(imp, m); +} + +der_rewriter::~der_rewriter() { + dealloc(m_imp); +} + +ast_manager & der_rewriter::m() const { + return m_imp->m(); +} + + +void der_rewriter::operator()(expr * t, expr_ref & result, proof_ref & result_pr) { + m_imp->operator()(t, result, result_pr); +} + +void der_rewriter::set_cancel(bool f) { + #pragma omp critical (der_rewriter) + { + m_imp->set_cancel(f); + } +} + +void der_rewriter::cleanup() { + ast_manager & m = m_imp->m(); + #pragma omp critical (th_rewriter) + { + dealloc(m_imp); + m_imp = alloc(imp, m); + } +} + +void der_rewriter::reset() { + m_imp->reset(); +} + +struct der_strategy::imp { + ast_manager & m_manager; + der_rewriter m_r; + + imp(ast_manager & m): + m_manager(m), + m_r(m) { + } + + ast_manager & m() const { return m_manager; } + + void set_cancel(bool f) { + m_r.set_cancel(f); + } + + void reset() { + m_r.reset(); + } + + void operator()(assertion_set & s) { + SASSERT(is_well_sorted(s)); + as_st_report report("der", s); + TRACE("before_der", s.display(tout);); + if (s.inconsistent()) + return; + expr_ref new_curr(m()); + proof_ref new_pr(m()); + unsigned size = s.size(); + for (unsigned idx = 0; idx < size; idx++) { + if (s.inconsistent()) + break; + expr * curr = s.form(idx); + m_r(curr, new_curr, new_pr); + if (m().proofs_enabled()) { + proof * pr = s.pr(idx); + new_pr = m().mk_modus_ponens(pr, new_pr); + } + s.update(idx, new_curr, new_pr); + } + s.elim_redundancies(); + TRACE("after_der", s.display(tout);); + SASSERT(is_well_sorted(s)); + } +}; + +der_strategy::der_strategy(ast_manager & m) { + m_imp = alloc(imp, m); +} + +der_strategy::~der_strategy() { + dealloc(m_imp); +} + +void der_strategy::operator()(assertion_set & s) { + m_imp->operator()(s); +} + +void der_strategy::set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); +} + +void der_strategy::cleanup() { + ast_manager & m = m_imp->m(); + imp * d = m_imp; + #pragma omp critical (as_st_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m); + #pragma omp critical (as_st_cancel) + { + m_imp = d; + } +} + +class der_tactic : public tactic { + struct imp { + ast_manager & m_manager; + der_rewriter m_r; + + imp(ast_manager & m): + m_manager(m), + m_r(m) { + } + + ast_manager & m() const { return m_manager; } + + void set_cancel(bool f) { + m_r.set_cancel(f); + } + + void reset() { + m_r.reset(); + } + + void operator()(goal & g) { + SASSERT(g.is_well_sorted()); + bool proofs_enabled = g.proofs_enabled(); + tactic_report report("der", g); + TRACE("before_der", g.display(tout);); + expr_ref new_curr(m()); + proof_ref new_pr(m()); + unsigned size = g.size(); + for (unsigned idx = 0; idx < size; idx++) { + if (g.inconsistent()) + break; + expr * curr = g.form(idx); + m_r(curr, new_curr, new_pr); + if (proofs_enabled) { + proof * pr = g.pr(idx); + new_pr = m().mk_modus_ponens(pr, new_pr); + } + g.update(idx, new_curr, new_pr, g.dep(idx)); + } + g.elim_redundancies(); + TRACE("after_der", g.display(tout);); + SASSERT(g.is_well_sorted()); + } + }; + + imp * m_imp; + +public: + der_tactic(ast_manager & m) { + m_imp = alloc(imp, m); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(der_tactic, m); + } + + virtual ~der_tactic() { + dealloc(m_imp); + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + mc = 0; pc = 0; core = 0; + (*m_imp)(*(in.get())); + in->inc_depth(); + result.push_back(in.get()); + } + + virtual void cleanup() { + ast_manager & m = m_imp->m(); + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + } + + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + +tactic * mk_der_tactic(ast_manager & m) { + return alloc(der_tactic, m); +} diff --git a/lib/der.h b/lib/der.h new file mode 100644 index 000000000..69f294fd4 --- /dev/null +++ b/lib/der.h @@ -0,0 +1,215 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + der.h + +Abstract: + + Destructive equality resolution. + +Author: + + Leonardo de Moura (leonardo) 2008-01-27. + +Revision History: + + Christoph Wintersteiger, 2010-03-30: Added Destr. Multi-Equality Resolution + +--*/ +#ifndef _DER_H_ +#define _DER_H_ + +#include"ast.h" +#include"var_subst.h" +#include"assertion_set_strategy.h" + +/* + New DER: the class DER (above) eliminates variables one by one. + This is inefficient, and we should implement a new version that + can handle efficiently examples with hundreds of variables. + + Suppose the target of the simplification is the quantifier + (FORALL (x1 T1) (x2 T2) ... (xn Tn) (or ....)) + So, the variables x1 ... xn are the candidates for elimination. + First, we build a mapping of candidate substitutions + Since variables x1 ... xn have ids 0 ... n-1, we can + use an array m_map to implement this mapping. + + The idea is to traverse the children of the (or ...) looking + for diseqs. The method is_var_diseq can be used for doing that. + Given a child c, if is_var_diseq(c, num_decls, v, t) returns true, + and m_map[v] is null, then we store t at m_map[v]. + For performance reasons, we also store a mapping from child position (in the OR) to variable ID. + Thus, m_pos2var[pos] contains the variable that is defined by the diseq at position pos. + m_pos2var[pos] = -1, if this position does not contain a diseq. + + After doing that, m_map contains the variables that can + be potentially eliminated using DER. + We say m_map[v] is the definition of variable v. + + The next step is to perform a topological sort on these + variables. The result is an array (m_order) of integers (variable ids) + such that i < j implies that m_map[m_order[i]] does not depend on variable m_order[j]. + For example, consider the case where m_map contains the following values + + m_map[0] = (+ (VAR 2) 0) + m_map[1] = null + m_map[2] = (+ (VAR 3) 0) + m_map[3] = (+ (VAR 1) 1) + m_map[4] = (* (VAR 5) 2) + m_map[5] = null + + In this example, variable 0 depends on the definition of variable 2, which + depends on the definition of variable 3, which depends on the definition of variable 0 (cycle). + On the other hand, no cycle is found when starting at variable 4. + Cycles can be broken by erasing entries from m_map. For example, the cycle above + can be removed by setting m_map[0] = null. + + m_map[0] = null + m_map[1] = null + m_map[2] = (+ (VAR 3) 0) + m_map[3] = (+ (VAR 1) 1) + m_map[4] = (* (VAR 5) 2) + m_map[5] = null + + The file asserted_formulas.cpp has a class top_sort for performing topological sort. + This class cannot be used here, since it is meant for eliminating constants (instead of variables). + We need to implement a new top_sort here, we do not need a separate class for doing that. + Moreover, it is much simpler, since m_map is just an array. + + In the example above (after setting m_map[0] to null), top_sort will produce the following order + m_order = [3, 2, 4] + + The next step is to use var_subst to update the definitions in var_subst. + The idea is to process the variables in the order specified by m_order. + When processing m_map[m_order[i]] we use the definitions of all variables in m_order[0 ... i-1]. + For example: + The first variable is 3, since it is at m_order[0], nothing needs to be done. + Next we have variable 2, we use m_map[3] since 3 is before 2 in m_order. So, the new + definition for 2 is (+ (+ (VAR 1) 1) 0). That is, we update m_map[2] with (+ (+ (VAR 1) 1) 0) + Next we have variable 4, we use m_map[3] and m_map[2] since 3 and 2 are before 4 in m_order. + In this case, var_subst will not do anything since m_map[4] does not contain variables 3 or 2. + So, the new m_map is: + + m_map[0] = null + m_map[1] = null + m_map[2] = (+ (+ (VAR 1) 1) 0) + m_map[3] = (+ (VAR 1) 1) + m_map[4] = (* (VAR 5) 2) + m_map[5] = null + + Now, we update the body of the quantifier using var_subst and the mapping above. + The idea is to create a new set of children for the OR. + For each child at position i, we do + if m_map[m_pos2var[i]] != -1 + skip this child, it is a diseq used during DER + else + apply var_subst using m_map to this child, and store the result in a new children array + Create a new OR (new body of the quantifier) using the new children + Then, we create a new quantifier using this new body, and use the function elim_unused_vars to + eliminate the ununsed variables. + + Remark: let us implement the new version inside the class der. + Use #if 0 ... #endif to comment the old version. + + Remark: after you are done, we can eliminate the call to occurs in is_var_diseq, since + top_sort is already performing cycle detection. +*/ + +/** + \brief Functor for applying Destructive Multi-Equality Resolution. + + (forall (X Y) (or X /= s C[X])) --> (forall (Y) C[Y]) +*/ +class der { + ast_manager & m_manager; + var_subst m_subst; + expr_ref_buffer m_new_exprs; + + ptr_vector m_map; + int_vector m_pos2var; + ptr_vector m_inx2var; + unsigned_vector m_order; + expr_ref_vector m_subst_map; + expr_ref_buffer m_new_args; + + /** + \brief Return true if e can be viewed as a variable disequality. + Store the variable id in v and the definition in t. + For example: + + if e is (not (= (VAR 1) T)), then v assigned to 1, and t to T. + if e is (iff (VAR 2) T), then v is assigned to 2, and t to (not T). + (not T) is used because this formula is equivalent to (not (iff (VAR 2) (not T))), + and can be viewed as a disequality. + */ + bool is_var_diseq(expr * e, unsigned num_decls, var *& v, expr_ref & t); + + void get_elimination_order(); + void create_substitution(unsigned sz); + void apply_substitution(quantifier * q, expr_ref & r); + + void reduce1(quantifier * q, expr_ref & r, proof_ref & pr); + +public: + der(ast_manager & m):m_manager(m),m_subst(m),m_new_exprs(m),m_subst_map(m),m_new_args(m) {} + ast_manager & m() const { return m_manager; } + void operator()(quantifier * q, expr_ref & r, proof_ref & pr); +}; + +/** + \brief Functor for applying Destructive Multi-Equality Resolution in all + universal quantifiers in an expression. +*/ +class der_rewriter { +protected: + struct imp; + imp * m_imp; +public: + der_rewriter(ast_manager & m); + ~der_rewriter(); + + ast_manager & m () const; + + void operator()(expr * t, expr_ref & result, proof_ref & result_pr); + + void cancel() { set_cancel(true); } + void reset_cancel() { set_cancel(false); } + void set_cancel(bool f); + void cleanup(); + void reset(); +}; + +typedef der_rewriter der_star; + +// TODO: delete obsolete class +class der_strategy : public assertion_set_strategy { + struct imp; + imp * m_imp; +public: + der_strategy(ast_manager & m); + virtual ~der_strategy(); + + void operator()(assertion_set & s); + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + operator()(s); + mc = 0; + } + + virtual void cleanup(); + virtual void set_cancel(bool f); +}; + +inline as_st * mk_der(ast_manager & m) { + return alloc(der_strategy, m); +} + +class tactic; + +tactic * mk_der_tactic(ast_manager & m); + +#endif /* _DER_H_ */ + diff --git a/lib/dictionary.h b/lib/dictionary.h new file mode 100644 index 000000000..938d6091a --- /dev/null +++ b/lib/dictionary.h @@ -0,0 +1,29 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + dictionary.h + +Abstract: + +Author: + + Leonardo (leonardo) 2011-03-01 + +Notes: + +--*/ +#ifndef _DICTIONARY_H_ +#define _DICTIONARY_H_ + +#include"map.h" +#include"symbol.h" + +template +class dictionary : public map { +public: + dictionary() {} +}; + +#endif diff --git a/lib/diff_logic.h b/lib/diff_logic.h new file mode 100644 index 000000000..d616edf57 --- /dev/null +++ b/lib/diff_logic.h @@ -0,0 +1,1650 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + diff_logic.h + +Abstract: + + Basic support for difference logic + +Author: + + Leonardo de Moura (leonardo) 2006-11-21. + +Revision History: + +--*/ +#ifndef _DIFF_LOGIC_H_ +#define _DIFF_LOGIC_H_ + +#include"vector.h" +#include"heap.h" +#include"trace.h" +#include"warning.h" + +typedef int dl_var; + +typedef enum { + DL_UNMARKED = 0, // Node/Variable was not yet found in the forward/backward search. + DL_FOUND, // Node/Variable was found but not yet processed. + DL_PROCESSED // Node/Variable was found and processed in the forward/backward search/ +} dl_search_mark; + +typedef enum { + DL_PROP_UNMARKED = 0, + DL_PROP_IRRELEVANT = 1, + DL_PROP_RELEVANT = 2, + DL_PROP_PROCESSED_RELEVANT = 3, + DL_PROP_PROCESSED_IRRELEVANT = 4 +} dl_prop_search_mark ; + +template +class dl_edge { + typedef typename Ext::numeral numeral; + typedef typename Ext::explanation explanation; + dl_var m_source; + dl_var m_target; + numeral m_weight; + unsigned m_timestamp; + explanation m_explanation; + bool m_enabled; +public: + + dl_edge(dl_var s, dl_var t, const numeral & w, unsigned ts, const explanation & ex): + m_source(s), + m_target(t), + m_weight(w), + m_timestamp(ts), + m_explanation(ex), + m_enabled(false) { + } + + dl_var get_source() const { + return m_source; + } + + dl_var get_target() const { + return m_target; + } + + const numeral & get_weight() const { + return m_weight; + } + + const explanation & get_explanation() const { + return m_explanation; + } + + unsigned get_timestamp() const { + return m_timestamp; + } + + bool is_enabled() const { + return m_enabled; + } + + void enable(unsigned timestamp) { + SASSERT(!m_enabled); + m_timestamp = timestamp; + m_enabled = true; + } + + void disable() { + m_enabled = false; + } + +}; + +// Functor for comparing difference logic variables. +// This functor is used to implement Dijkstra algorithm (i.e., Heap). +template +class dl_var_lt { + typedef typename Ext::numeral numeral; + vector & m_values; +public: + dl_var_lt(vector & values): + m_values(values) { + } + + bool operator()(dl_var v1, dl_var v2) const { + return m_values[v1] < m_values[v2]; + } +}; + +typedef int edge_id; +const edge_id null_edge_id = -1; + +template +class dl_graph { + struct statistics { + unsigned m_propagation_cost; + unsigned m_implied_literal_cost; + unsigned m_num_implied_literals; + unsigned m_num_helpful_implied_literals; + unsigned m_num_relax; + void reset() { + m_propagation_cost = 0; + m_implied_literal_cost = 0; + m_num_implied_literals = 0; + m_num_helpful_implied_literals = 0; + m_num_relax = 0; + } + statistics() { reset(); } + void display(std::ostream& out) const { + out << "num. prop. steps. " << m_propagation_cost << "\n"; + out << "num. impl. steps. " << m_implied_literal_cost << "\n"; + out << "num. impl. lits. " << m_num_implied_literals << "\n"; + out << "num. impl. conf lits. " << m_num_helpful_implied_literals << "\n"; + out << "num. bound relax. " << m_num_relax << "\n"; + } + }; + statistics m_stats; + typedef typename Ext::numeral numeral; + typedef typename Ext::explanation explanation; + typedef vector assignment; + typedef dl_edge edge; + typedef vector edges; + + class assignment_trail { + dl_var m_var; + numeral m_old_value; + public: + assignment_trail(dl_var v, const numeral & val): + m_var(v), + m_old_value(val) { + } + + dl_var get_var() const { + return m_var; + } + + const numeral & get_old_value() const { + return m_old_value; + } + }; + + typedef vector assignment_stack; + + assignment m_assignment; // per var + assignment_stack m_assignment_stack; // temporary stack for restoring the assignment + edges m_edges; + + typedef int_vector edge_id_vector; + typedef int_vector dl_var_vector; + + vector m_out_edges; // per var + vector m_in_edges; // per var + + struct scope { + unsigned m_edges_lim; + unsigned m_enabled_edges_lim; + unsigned m_old_timestamp; + scope(unsigned e, unsigned enabled, unsigned t): + m_edges_lim(e), + m_enabled_edges_lim(enabled), + m_old_timestamp(t) { + } + }; + + svector m_trail_stack; + + // forward reachability + vector m_gamma; // per var + svector m_mark; // per var + edge_id_vector m_parent; // per var + dl_var_vector m_visited; + typedef heap > var_heap; + var_heap m_heap; + + unsigned m_timestamp; + unsigned m_last_enabled_edge; + edge_id_vector m_enabled_edges; + + // SCC for cheap equality propagation -- + svector m_unfinished_set; // per var + int_vector m_dfs_time; // per var + dl_var_vector m_roots; + dl_var_vector m_unfinished; + int m_next_dfs_time; + int m_next_scc_id; + // ------------------------------------- + + // activity vector for edges. + svector m_activity; + + + bool check_invariant() const { +#ifdef Z3DEBUG + SASSERT(m_assignment.size() == m_gamma.size()); + SASSERT(m_assignment.size() == m_mark.size()); + SASSERT(m_assignment.size() == m_parent.size()); + SASSERT(m_assignment.size() <= m_heap.get_bounds()); + SASSERT(m_in_edges.size() == m_out_edges.size()); + int n = m_out_edges.size(); + for (dl_var id = 0; id < n; id++) { + const edge_id_vector & e_ids = m_out_edges[id]; + edge_id_vector::const_iterator it = e_ids.begin(); + edge_id_vector::const_iterator end = e_ids.end(); + for (; it != end; ++it) { + edge_id e_id = *it; + SASSERT(static_cast(e_id) <= m_edges.size()); + const edge & e = m_edges[e_id]; + SASSERT(e.get_source() == id); + } + } + for (dl_var id = 0; id < n; id++) { + const edge_id_vector & e_ids = m_in_edges[id]; + edge_id_vector::const_iterator it = e_ids.begin(); + edge_id_vector::const_iterator end = e_ids.end(); + for (; it != end; ++it) { + edge_id e_id = *it; + SASSERT(static_cast(e_id) <= m_edges.size()); + const edge & e = m_edges[e_id]; + SASSERT(e.get_target() == id); + } + } + n = m_edges.size(); + for (int i = 0; i < n; i++) { + const edge & e = m_edges[i]; + SASSERT(std::find(m_out_edges[e.get_source()].begin(), m_out_edges[e.get_source()].end(), i) + != m_out_edges[e.get_source()].end()); + SASSERT(std::find(m_in_edges[e.get_target()].begin(), m_in_edges[e.get_target()].end(), i) + != m_in_edges[e.get_target()].end()); + } +#endif + return true; + } + + + bool is_feasible(const edge & e) const { + return + !e.is_enabled() || + m_assignment[e.get_target()] - m_assignment[e.get_source()] <= e.get_weight(); + } + + +public: + // An assignment is feasible if all edges are feasible. + bool is_feasible() const { +#ifdef Z3DEBUG + for (unsigned i = 0; i < m_edges.size(); ++i) { + if (!is_feasible(m_edges[i])) { + return false; + } + } +#endif + return true; + } + + unsigned get_num_edges() const { return m_edges.size(); } + + dl_var get_source(edge_id id) const { return m_edges[id].get_source(); } + + dl_var get_target(edge_id id) const { return m_edges[id].get_target(); } + + explanation const & get_explanation(edge_id id) const { return m_edges[id].get_explanation(); } + + bool is_enabled(edge_id id) const { return m_edges[id].is_enabled(); } + + bool is_feasible(edge_id id) const { return is_feasible(m_edges[id]); } + + numeral const& get_weight(edge_id id) const { return m_edges[id].get_weight(); } + + +private: + // An assignment is almost feasible if all but edge with idt edge are feasible. + bool is_almost_feasible(edge_id id) const { +#ifdef Z3DEBUG + for (unsigned i = 0; i < m_edges.size(); ++i) { + if (id != static_cast(i) && !is_feasible(m_edges[i])) { + return false; + } + } +#endif + return true; + } + + // Update the assignment of variable v, that is, + // m_assignment[v] += inc + // This method also stores the old value of v in the assignment stack. + void acc_assignment(dl_var v, const numeral & inc) { + TRACE("diff_logic_bug", tout << "update v: " << v << " += " << inc << " m_assignment[v] " << m_assignment[v] << "\n";); + m_assignment_stack.push_back(assignment_trail(v, m_assignment[v])); + m_assignment[v] += inc; + } + + // Restore the assignment using the information in m_assignment_stack. + // This method is called when make_feasible fails. + void undo_assignments() { + typename assignment_stack::iterator it = m_assignment_stack.end(); + typename assignment_stack::iterator begin = m_assignment_stack.begin(); + while (it != begin) { + --it; + TRACE("diff_logic_bug", tout << "undo assignment: " << it->get_var() << " " << it->get_old_value() << "\n";); + m_assignment[it->get_var()] = it->get_old_value(); + } + m_assignment_stack.reset(); + } + + // Store in gamma the normalized weight. The normalized weight is given + // by the formula + // m_assignment[e.get_source()] - m_assignment[e.get_target()] + e.get_weight() + void set_gamma(const edge & e, numeral & gamma) { + gamma = m_assignment[e.get_source()]; + gamma -= m_assignment[e.get_target()]; + gamma += e.get_weight(); + } + + void reset_marks() { + dl_var_vector::iterator it = m_visited.begin(); + dl_var_vector::iterator end = m_visited.end(); + for (; it != end; ++it) { + m_mark[*it] = DL_UNMARKED; + } + m_visited.reset(); + } + + bool marks_are_clear() const { + for (unsigned i = 0; i < m_mark.size(); ++i) { + if (m_mark[i] != DL_UNMARKED) { + return false; + } + } + return true; + } + + // Make the assignment feasible. An assignment is feasible if + // Forall edge e. m_assignment[e.get_target()] - m_assignment[e.get_source()] <= e.get_weight() + // + // This method assumes that if the assignment is not feasible, then the only infeasible edge + // is the last added edge. + bool make_feasible(edge_id id) { + SASSERT(is_almost_feasible(id)); + SASSERT(!m_edges.empty()); + SASSERT(!is_feasible(m_edges[id])); + SASSERT(m_assignment_stack.empty()); + SASSERT(m_heap.empty()); + const edge & last_e = m_edges[id]; + dl_var root = last_e.get_source(); + m_gamma[root].reset(); + dl_var target = last_e.get_target(); + numeral gamma; + set_gamma(last_e, gamma); + m_gamma[target] = gamma; + m_mark[target] = DL_PROCESSED; + m_parent[target] = id; + m_visited.push_back(target); + SASSERT(m_gamma[target].is_neg()); + acc_assignment(target, gamma); + + TRACE("arith", tout << id << "\n";); + + dl_var source = target; + for(;;) { + ++m_stats.m_propagation_cost; + if (m_mark[root] != DL_UNMARKED) { + // negative cycle was found + SASSERT(m_gamma[root].is_neg()); + m_heap.reset(); + reset_marks(); + undo_assignments(); + return false; + } + + typename edge_id_vector::iterator it = m_out_edges[source].begin(); + typename edge_id_vector::iterator end = m_out_edges[source].end(); + for (; it != end; ++it) { + edge_id e_id = *it; + edge & e = m_edges[e_id]; + SASSERT(e.get_source() == source); + if (!e.is_enabled()) { + continue; + } + set_gamma(e, gamma); + + if (gamma.is_neg()) { + target = e.get_target(); + switch (m_mark[target]) { + case DL_UNMARKED: + m_gamma[target] = gamma; + m_mark[target] = DL_FOUND; + m_parent[target] = e_id; + m_visited.push_back(target); + m_heap.insert(target); + break; + case DL_FOUND: + if (gamma < m_gamma[target]) { + m_gamma[target] = gamma; + m_parent[target] = e_id; + m_heap.decreased(target); + } + break; + case DL_PROCESSED: + default: + UNREACHABLE(); + } + } + } + + if (m_heap.empty()) { + SASSERT(is_feasible()); + reset_marks(); + m_assignment_stack.reset(); + return true; + } + + source = m_heap.erase_min(); + m_mark[source] = DL_PROCESSED; + acc_assignment(source, m_gamma[source]); + } + } + + edge const* find_relaxed_edge(edge const* e, numeral & gamma) { + SASSERT(gamma.is_neg()); + dl_var src = e->get_source(); + dl_var dst = e->get_target(); + numeral w = e->get_weight(); + typename edge_id_vector::iterator it = m_out_edges[src].begin(); + typename edge_id_vector::iterator end = m_out_edges[src].end(); + for (; it != end; ++it) { + edge_id e_id = *it; + edge const& e2 = m_edges[e_id]; + if (e2.get_target() == dst && + e2.is_enabled() && // or at least not be inconsistent with current choices + e2.get_weight() > w && (e2.get_weight() - w + gamma).is_neg()) { + e = &e2; + gamma += (e2.get_weight() - w); + w = e2.get_weight(); + ++m_stats.m_num_relax; + } + } + return e; + } + +public: + + dl_graph(): + m_heap(1024, dl_var_lt(m_gamma)), + m_timestamp(0), + m_fw(m_mark), + m_bw(m_mark) { + } + + void display_statistics(std::ostream& out) const { + m_stats.display(out); + } + + // Create/Initialize a variable with the given id. + // The graph does not have control over the ids assigned by the theory. + // That is init_var receives the id as an argument. + void init_var(dl_var v) { + TRACE("diff_logic_bug", tout << "init_var " << v << "\n";); + SASSERT(static_cast(v) >= m_out_edges.size() || + m_out_edges[v].empty()); + SASSERT(check_invariant()); + while (static_cast(v) >= m_out_edges.size()) { + m_assignment .push_back(numeral()); + m_out_edges .push_back(edge_id_vector()); + m_in_edges .push_back(edge_id_vector()); + m_gamma .push_back(numeral()); + m_mark .push_back(DL_UNMARKED); + m_parent .push_back(null_edge_id); + } + if (static_cast(v) >= m_heap.get_bounds()) { + m_heap.set_bounds(v+1); + } + m_assignment[v].reset(); + SASSERT(static_cast(v) < m_heap.get_bounds()); + TRACE("diff_logic_bug", tout << "init_var " << v << ", m_assignment[v]: " << m_assignment[v] << "\n";); + SASSERT(m_assignment[v].is_zero()); + SASSERT(m_out_edges[v].empty()); + SASSERT(m_in_edges[v].empty()); + SASSERT(m_mark[v] == DL_UNMARKED); + SASSERT(check_invariant()); + } + + // Add an new weighted edge "source --weight--> target" with explanation ex. + edge_id add_edge(dl_var source, dl_var target, const numeral & weight, const explanation & ex) { + // SASSERT(is_feasible()); + edge_id new_id = m_edges.size(); + m_edges.push_back(edge(source, target, weight, m_timestamp, ex)); + m_activity.push_back(0); + TRACE("dl_bug", tout << "creating edge:\n"; display_edge(tout, m_edges.back());); + m_out_edges[source].push_back(new_id); + m_in_edges[target].push_back(new_id); + return new_id; + } + + // Return false if the resultant graph has a negative cycle. The negative + // cycle can be extracted using traverse_neg_cycle. + // The method assumes the graph is feasible before the invocation. + bool enable_edge(edge_id id) { + edge& e = m_edges[id]; + bool r = true; + if (!e.is_enabled()) { + e.enable(m_timestamp); + m_last_enabled_edge = id; + m_timestamp++; + if (!is_feasible(e)) { + r = make_feasible(id); + } + SASSERT(check_invariant()); + SASSERT(!r || is_feasible()); + m_enabled_edges.push_back(id); + } + return r; + } + + + // This method should only be invoked when add_edge returns false. + // That is, there is a negative cycle in the graph. + // It will apply the functor f on every explanation attached to the edges + // in the negative cycle. + template + void traverse_neg_cycle(bool try_relax, Functor & f) { + SASSERT(!is_feasible(m_edges[m_last_enabled_edge])); + edge_id last_id = m_last_enabled_edge; + edge const& last_e = m_edges[last_id]; + numeral gamma = m_gamma[last_e.get_source()]; + SASSERT(gamma.is_neg()); + edge_id e_id = last_id; + do { + const edge * e = &m_edges[e_id]; + if (try_relax) { + e = find_relaxed_edge(e, gamma); + } + inc_activity(e_id); + f(e->get_explanation()); + e_id = m_parent[e->get_source()]; + } + while (e_id != last_id); + } + + + // + // Here is a version that tries to + // Find shortcuts on the cycle. + // A shortcut is an edge that that is subsumed + // by the current edges, but provides for a shorter + // path to the conflict. + // Example (<= (- a b) k1) (<= (- b c) k2) (<= (- c d) k3) + // An edge (<= (- a d) k4) where k1 + k2 + k3 <= k4, but gamma + k4 - (k1+k2+k3) < 0 + // is still a conflict. + // + template + void traverse_neg_cycle2(bool try_relax, Functor & f) { + static unsigned num_conflicts = 0; + ++num_conflicts; + SASSERT(!is_feasible(m_edges[m_last_enabled_edge])); + vector potentials; + svector edges; + svector nodes; + edge_id last_id = m_last_enabled_edge; + edge const& last_e = m_edges[last_id]; + numeral potential(0); + edge_id e_id = last_id; + numeral gamma = m_gamma[last_e.get_source()]; + SASSERT(check_gamma(last_id)); + + do { + SASSERT(gamma.is_neg()); + edges.push_back(e_id); + const edge & e = m_edges[e_id]; + dl_var src = e.get_source(); + potential += e.get_weight(); + + // + // search for edges that can reduce size of negative cycle. + // + typename edge_id_vector::iterator it = m_out_edges[src].begin(); + typename edge_id_vector::iterator end = m_out_edges[src].end(); + for (; it != end; ++it) { + edge_id e_id2 = *it; + edge const& e2 = m_edges[e_id2]; + dl_var src2 = e2.get_target(); + if (e_id2 == e_id || !e2.is_enabled()) { + continue; + } + for (unsigned j = 0; j < nodes.size(); ++j) { + if (nodes[j] != src2) { + continue; + } + numeral const& weight = e2.get_weight(); + numeral delta = weight - potential + potentials[j]; + if (delta.is_nonneg() && (gamma + delta).is_neg()) { + TRACE("diff_logic_traverse", tout << "Reducing path by "; + display_edge(tout, e2); + tout << "gamma: " << gamma << " weight: " << weight << "\n"; + tout << "enabled: " << e2.is_enabled() << "\n"; + tout << "delta: " << delta << "\n"; + tout << "literals saved: " << (nodes.size() - j - 1) << "\n"; + ); + gamma += delta; + nodes.shrink(j + 1); + potentials.shrink(j + 1); + edges.shrink(j + 1); + edges.push_back(e_id2); + potential = potentials[j] + weight; + break; + } + else { + TRACE("diff_logic_traverse", display_edge(tout << "skipping: ", e2);); + } + } + } + potentials.push_back(potential); + nodes.push_back(src); + e_id = m_parent[src]; + + SASSERT(check_path(potentials, nodes, edges)); + } + while (e_id != last_id); + + TRACE("diff_logic_traverse", { + tout << "Num conflicts: " << num_conflicts << "\n"; + tout << "Resulting path:\n"; + for (unsigned i = 0; i < edges.size(); ++i) { + display_edge(tout << "potential: " << potentials[i] << " ", m_edges[edges[i]]); + } + } + ); + + if (!check_explanation(edges.size(), edges.c_ptr())) { + throw default_exception("edges are not inconsistent"); + } + +#if 1 + // experimental feature: + prune_edges(edges, f); +#endif + + for (unsigned i = 0; i < edges.size(); ++i) { + edge const& e = m_edges[edges[i]]; + f(e.get_explanation()); + } + } + + // + // Create fresh literals obtained by resolving a pair (or more) + // literals associated with the edges. + // + + template + void prune_edges(svector& edges, Functor & f) { + unsigned max_activity = 0; + edge_id e_id; + for (unsigned i = 0; i < edges.size(); ++i) { + e_id = edges[i]; + inc_activity(e_id); + if (m_activity[e_id] > max_activity) { + max_activity = m_activity[e_id]; + } + } + if (edges.size() > 5 && max_activity > 20) { + prune_edges_min2(edges, f); + } + } + + template + void prune_edges_min1(svector& edges, Functor & f) { + unsigned min_activity = ~0; + unsigned idx = 0; + for (unsigned i = 0; i + 1 < edges.size(); ++i) { + edge_id e_id = edges[i]; + if (m_activity[e_id] < min_activity) { + min_activity = m_activity[e_id]; + idx = i; + } + } + + dl_var dst = get_source(edges[idx+1]); + dl_var src = get_target(edges[idx]); + + f.new_edge(src, dst, 2, edges.begin()+idx); + } + + template + void prune_edges_min2(svector& edges, Functor & f) { + unsigned min1 = ~0, min2 = ~0, max = 0; + unsigned idx1 = 0, idx2 = 0, max_idx = 0; + dl_var src, dst; + for (unsigned i = 0; i < edges.size(); ++i) { + edge_id e_id = edges[i]; + if (m_activity[e_id] <= min1) { + min2 = min1; + min1 = m_activity[e_id]; + idx2 = idx1; + idx1 = i; + } + else if (m_activity[e_id] < min2) { + min2 = m_activity[e_id]; + idx2 = i; + } + // TBD: use also the edge with the maximal + // traversals to create cut-edge. + // + if (m_activity[e_id] > max) { + max = m_activity[e_id]; + max_idx = i; + } + } + + // + // e1 e2 i1 e4 e5 e6 .. e8 i2 e9 e10 + // => + // e1 e2 e_new d9 e10 + // + // alternative: + // e_new e4 ... e8 is the new edge. + // + // or both. + // + if (idx2 < idx1) { + std::swap(idx1,idx2); + } + SASSERT(idx1 < idx2 && idx2 < edges.size()); + SASSERT(max_idx < edges.size()); + dst = get_source(edges[idx2]); + src = get_target(edges[idx1]); + + f.new_edge(src, dst, idx2-idx1+1, edges.begin()+idx1); + } + + + // Create a new scope. + // That is, save the number of edges in the graph. + void push() { + // SASSERT(is_feasible()); <<< I relaxed this condition + m_trail_stack.push_back(scope(m_edges.size(), m_enabled_edges.size(), m_timestamp)); + } + + // Backtrack num_scopes scopes. + // Restore the previous number of edges. + void pop(unsigned num_scopes) { + unsigned lvl = m_trail_stack.size(); + SASSERT(num_scopes <= lvl); + unsigned new_lvl = lvl - num_scopes; + scope & s = m_trail_stack[new_lvl]; + for (unsigned i = m_enabled_edges.size(); i > s.m_enabled_edges_lim; ) { + --i; + m_edges[m_enabled_edges[i]].disable(); + } + m_enabled_edges.shrink(s.m_enabled_edges_lim); + unsigned old_num_edges = s.m_edges_lim; + m_timestamp = s.m_old_timestamp; + unsigned num_edges = m_edges.size(); + SASSERT(old_num_edges <= num_edges); + unsigned to_delete = num_edges - old_num_edges; + for (unsigned i = 0; i < to_delete; i++) { + const edge & e = m_edges.back(); + TRACE("dl_bug", tout << "deleting edge:\n"; display_edge(tout, e);); + dl_var source = e.get_source(); + dl_var target = e.get_target(); + SASSERT(static_cast(m_edges.size()) - 1 == m_out_edges[source].back()); + SASSERT(static_cast(m_edges.size()) - 1 == m_in_edges[target].back()); + m_out_edges[source].pop_back(); + m_in_edges[target].pop_back(); + m_edges.pop_back(); + } + m_trail_stack.shrink(new_lvl); + SASSERT(check_invariant()); + // SASSERT(is_feasible()); <<< I relaxed the condition in push(), so this assertion is not valid anymore. + } + + // Make m_assignment[v] == zero + // The whole assignment is adjusted in a way feasibility is preserved. + // This method should only be invoked if the current assignment if feasible. + void set_to_zero(dl_var v) { + SASSERT(is_feasible()); + if (!m_assignment[v].is_zero()) { + numeral k = m_assignment[v]; + typename assignment::iterator it = m_assignment.begin(); + typename assignment::iterator end = m_assignment.end(); + for (; it != end; ++it) { + *it -= k; + } + SASSERT(is_feasible()); + } + } + + // + // set assignments of v and w both to 0. + // assumption: there are no prior dependencies between v and w. + // so the graph is disconnected. + // assumption: the current assignment is feasible. + // + void set_to_zero(dl_var v, dl_var w) { + if (!m_assignment[v].is_zero()) { + set_to_zero(v); + } + else { + set_to_zero(w); + } + if (!m_assignment[v].is_zero() || !m_assignment[w].is_zero()) { + enable_edge(add_edge(v, w, numeral(0), explanation())); + enable_edge(add_edge(w, v, numeral(0), explanation())); + SASSERT(is_feasible()); + } + } + + struct every_var_proc { + bool operator()(dl_var v) const { + return true; + } + }; + + void display(std::ostream & out) const { + display_core(out, every_var_proc()); + } + + template + void display_core(std::ostream & out, FilterAssignmentProc p) const { + display_edges(out); + display_assignment(out, p); + } + + void display_edges(std::ostream & out) const { + typename edges::const_iterator it = m_edges.begin(); + typename edges::const_iterator end = m_edges.end(); + for (; it != end; ++it) { + edge const& e = *it; + if (e.is_enabled()) { + display_edge(out, e); + } + } + } + + void display_edge(std::ostream & out, edge_id id) const { + display_edge(out, m_edges[id]); + } + + void display_edge(std::ostream & out, const edge & e) const { + out << e.get_explanation() << " (<= (- $" << e.get_target() << " $" << e.get_source() << ") " << e.get_weight() << ") " << e.get_timestamp() << "\n"; + } + + template + void display_assignment(std::ostream & out, FilterAssignmentProc p) const { + unsigned n = m_assignment.size(); + for (unsigned v = 0; v < n; v++) { + if (p(v)) { + out << "$" << v << " := " << m_assignment[v] << "\n"; + } + } + } + + // Return true if there is an edge source --> target. + // If there is such edge, then the weight is stored in w and the explanation in ex. + bool get_edge_weight(dl_var source, dl_var target, numeral & w, explanation & ex) { + edge_id_vector & edges = m_out_edges[source]; + typename edge_id_vector::iterator it = edges.begin(); + typename edge_id_vector::iterator end = edges.end(); + bool found = false; + for (; it != end; ++it) { + edge_id e_id = *it; + edge & e = m_edges[e_id]; + if (e.is_enabled() && e.get_target() == target && (!found || e.get_weight() < w)) { + w = e.get_weight(); + ex = e.get_explanation(); + found = true; + } + } + return found; + } + + template + void enumerate_edges(dl_var source, dl_var target, Functor& f) { + edge_id_vector & edges = m_out_edges[source]; + typename edge_id_vector::iterator it = edges.begin(); + typename edge_id_vector::iterator end = edges.end(); + for (; it != end; ++it) { + edge_id e_id = *it; + edge const& e = m_edges[e_id]; + if (e.get_target() == target) { + f(e.get_weight(), e.get_explanation()); + } + } + } + + + void reset() { + m_assignment .reset(); + m_assignment_stack .reset(); + m_edges .reset(); + m_in_edges .reset(); + m_out_edges .reset(); + m_trail_stack .reset(); + m_gamma .reset(); + m_mark .reset(); + m_parent .reset(); + m_visited .reset(); + m_heap .reset(); + m_enabled_edges .reset(); + m_activity .reset(); + } + + // Compute strongly connected components connected by (normalized) zero edges. + void compute_zero_edge_scc(int_vector & scc_id) { + m_unfinished_set.reset(); + m_dfs_time.reset(); + scc_id.reset(); + m_roots.reset(); + m_unfinished.reset(); + int n = m_assignment.size(); + m_unfinished_set.resize(n, false); + m_dfs_time.resize(n, -1); + scc_id.resize(n, -1); + m_next_dfs_time = 0; + m_next_scc_id = 0; + for (dl_var v = 0; v < n; v++) { + if (m_dfs_time[v] == -1) { + dfs(v, scc_id); + } + } + TRACE("eq_scc", + for (dl_var v = 0; v < n; v++) { + tout << "$" << v << " -> " << scc_id[v] << "\n"; + }); + } + + void dfs(dl_var v, int_vector & scc_id) { + m_dfs_time[v] = m_next_dfs_time; + m_next_dfs_time++; + m_unfinished_set[v] = true; + m_unfinished.push_back(v); + m_roots.push_back(v); + numeral gamma; + edge_id_vector & edges = m_out_edges[v]; + typename edge_id_vector::iterator it = edges.begin(); + typename edge_id_vector::iterator end = edges.end(); + for (; it != end; ++it) { + edge_id e_id = *it; + edge & e = m_edges[e_id]; + if (!e.is_enabled()) { + continue; + } + SASSERT(e.get_source() == v); + set_gamma(e, gamma); + if (gamma.is_zero()) { + dl_var target = e.get_target(); + if (m_dfs_time[target] == -1) { + dfs(target, scc_id); + } + else if (m_unfinished_set[target]) { + SASSERT(!m_roots.empty()); + while (m_dfs_time[m_roots.back()] > m_dfs_time[target]) { + m_roots.pop_back(); + SASSERT(!m_roots.empty()); + } + } + } + } + if (v == m_roots.back()) { + dl_var scc_elem; + unsigned size = 0; + do { + scc_elem = m_unfinished.back(); + m_unfinished.pop_back(); + SASSERT(m_unfinished_set[scc_elem]); + m_unfinished_set[scc_elem] = false; + scc_id[scc_elem] = m_next_scc_id; + size++; + } while (scc_elem != v); + // Ignore SCC with size 1 + if (size == 1) { + scc_id[scc_elem] = -1; + } + else { + m_next_scc_id++; + } + m_roots.pop_back(); + } + } + + numeral get_assignment(dl_var v) const { + return m_assignment[v]; + } + + unsigned get_timestamp() const { + return m_timestamp; + } + +private: + + void inc_activity(edge_id e_id) { + ++m_activity[e_id]; + } + + bool check_explanation(unsigned num_edges, edge_id const* edges) { + numeral w; + for (unsigned i = 0; i < num_edges; ++i) { + edge const& e = m_edges[edges[i]]; + unsigned pred = (i>0)?(i-1):(num_edges-1); + edge const& e1 = m_edges[edges[pred]]; + if (e.get_target() != e1.get_source()) { + TRACE("check_explanation", display_edge(tout, e); display_edge(tout, e1); ); + return false; + } + w += e.get_weight(); + } + if (w.is_nonneg()) { + TRACE("check_explanation", tout << "weight: " << w << "\n";); + return false; + } + return true; + } + + bool check_path(vector& potentials, svector& nodes, svector& edges) { + // Debug: + numeral potential0; + for (unsigned i = 0; i < edges.size(); ++i) { + + potential0 += m_edges[edges[i]].get_weight(); + numeral potential1 = potentials[i]; + if (potential0 != potentials[i] || + nodes[i] != m_edges[edges[i]].get_source()) { + TRACE("diff_logic_traverse", tout << "checking index " << i << " "; + tout << "potential: " << potentials[i] << " "; + display_edge(tout, m_edges[edges[i]]); + ); + return false; + } + } + return true; + } + + bool check_gamma(edge_id last_id) { + edge_id e_id = last_id; + numeral gamma2; + do { + gamma2 += m_edges[e_id].get_weight(); + e_id = m_parent[m_edges[e_id].get_source()]; + } + while (e_id != last_id); + + return gamma2 == m_gamma[m_edges[last_id].get_source()]; + } + + + // Auxliary structure used for breadth-first search. + struct bfs_elem { + dl_var m_var; + int m_parent_idx; + edge_id m_edge_id; + bfs_elem(dl_var v, int parent_idx, edge_id e): + m_var(v), + m_parent_idx(parent_idx), + m_edge_id(e) { + } + }; + +public: + // Find the shortest path from source to target using (normalized) zero edges with timestamp less than the given timestamp. + // The functor f is applied on every explanation attached to the edges in the shortest path. + // Return true if the path exists, false otherwise. + template + bool find_shortest_zero_edge_path(dl_var source, dl_var target, unsigned timestamp, Functor & f) { + svector bfs_todo; + svector bfs_mark; + bfs_mark.resize(m_assignment.size(), false); + + bfs_todo.push_back(bfs_elem(source, -1, null_edge_id)); + bfs_mark[source] = true; + + unsigned m_head = 0; + numeral gamma; + while (m_head < bfs_todo.size()) { + bfs_elem & curr = bfs_todo[m_head]; + int parent_idx = m_head; + m_head++; + dl_var v = curr.m_var; + TRACE("dl_bfs", tout << "processing: " << v << "\n";); + edge_id_vector & edges = m_out_edges[v]; + typename edge_id_vector::iterator it = edges.begin(); + typename edge_id_vector::iterator end = edges.end(); + for (; it != end; ++it) { + edge_id e_id = *it; + edge & e = m_edges[e_id]; + SASSERT(e.get_source() == v); + if (!e.is_enabled()) { + continue; + } + set_gamma(e, gamma); + TRACE("dl_bfs", tout << "processing edge: "; display_edge(tout, e); tout << "gamma: " << gamma << "\n";); + if (gamma.is_zero() && e.get_timestamp() < timestamp) { + dl_var curr_target = e.get_target(); + TRACE("dl_bfs", tout << "curr_target: " << curr_target << + ", mark: " << static_cast(bfs_mark[curr_target]) << "\n";); + if (curr_target == target) { + TRACE("dl_bfs", tout << "found path\n";); + TRACE("dl_eq_bug", tout << "path: " << source << " --> " << target << "\n"; + display_edge(tout, e); + int tmp_parent_idx = parent_idx; + for (;;) { + bfs_elem & curr = bfs_todo[tmp_parent_idx]; + if (curr.m_edge_id == null_edge_id) { + break; + } + else { + edge & e = m_edges[curr.m_edge_id]; + display_edge(tout, e); + tmp_parent_idx = curr.m_parent_idx; + } + tout.flush(); + }); + TRACE("dl_eq_bug", display_edge(tout, e);); + f(e.get_explanation()); + for (;;) { + SASSERT(parent_idx >= 0); + bfs_elem & curr = bfs_todo[parent_idx]; + if (curr.m_edge_id == null_edge_id) { + return true; + } + else { + edge & e = m_edges[curr.m_edge_id]; + TRACE("dl_eq_bug", display_edge(tout, e);); + f(e.get_explanation()); + parent_idx = curr.m_parent_idx; + } + } + } + else { + if (!bfs_mark[curr_target]) { + bfs_todo.push_back(bfs_elem(curr_target, parent_idx, e_id)); + bfs_mark[curr_target] = true; + } + } + } + } + } + return false; + } + + + // + // Theory propagation: + // Given a (newly) added edge id, find the ids of un-asserted edges that + // that are subsumed by the id. + // Separately, reproduce explanations for those ids. + // + // The algorithm works in the following way: + // 1. Let e = source -- weight --> target be the edge at id. + // 2. Compute successors (over the assigned edges) of source, + // those traversing source-target and those leaving source over different edges. + // compute forward potential of visited nodes. + // queue up nodes that are visited, and require the source->target edge. + // 3. Compute pre-decessors (over the assigned edges) of target, + // those traversing source-target, and those entering target + // without visiting source. Maintain only nodes that enter target + // compute backward potential of visited nodes. + // Queue up nodes that are visited, and require the source->target edge. + // 4. traverse the smaller of the two lists. + // check if there is an edge between the two sets such that + // the weight of the edge is >= than the sum of the two potentials - weight + // (since 'weight' is added twice in the traversal. + // +private: + struct dfs_state { + class hp_lt { + assignment& m_delta; + char_vector& m_mark; + public: + hp_lt(assignment& asgn, char_vector& m) : m_delta(asgn),m_mark(m) {} + bool operator()(dl_var v1, dl_var v2) const { + numeral const& delta1 = m_delta[v1]; + numeral const& delta2 = m_delta[v2]; + return delta1 < delta2 || + (delta1 == delta2 && + m_mark[v1] == DL_PROP_IRRELEVANT && m_mark[v2] == DL_PROP_RELEVANT); + } + }; + assignment m_delta; + int_vector m_visited; + int_vector m_parent; + heap m_heap; + unsigned m_num_edges; + dfs_state(char_vector& mark): m_heap(1024, hp_lt(m_delta, mark)), m_num_edges(0) {} + + void re_init(unsigned sz) { + m_delta.resize(sz, numeral(0)); + m_parent.resize(sz, 0); + m_visited.reset(); + m_num_edges = 0; + m_heap.set_bounds(sz); + SASSERT(m_heap.empty()); + } + + void add_size(unsigned n) { m_num_edges += n; } + unsigned get_size() const { return m_num_edges; } + + bool contains(dl_var v) const { + // TBD can be done better using custom marking. + for (unsigned i = 0; i < m_visited.size(); ++i) { + if (v == m_visited[i]) { + return true; + } + } + return false; + } + }; + + dfs_state m_fw; + dfs_state m_bw; + + void fix_sizes() { + m_fw.re_init(m_assignment.size()); + m_bw.re_init(m_assignment.size()); + } + + numeral get_reduced_weight(dfs_state& state, dl_var n, edge const& e) { + numeral gamma; + set_gamma(e, gamma); + return state.m_delta[n] + gamma; + } + + template + void find_relevant(dfs_state& state, edge_id id) { + SASSERT(state.m_visited.empty()); + SASSERT(state.m_heap.empty()); + numeral delta; + edge const& e_init = m_edges[id]; + vector const& edges = is_fw?m_out_edges:m_in_edges; + dl_var target = is_fw?e_init.get_target():e_init.get_source(); + dl_var source = is_fw?e_init.get_source():e_init.get_target(); + + SASSERT(marks_are_clear()); + + dl_prop_search_mark source_mark = DL_PROP_IRRELEVANT; + dl_prop_search_mark target_mark = DL_PROP_RELEVANT; + m_mark[source] = source_mark; + m_mark[target] = target_mark; + state.m_delta[source] = numeral(0); + state.m_delta[target] = get_reduced_weight(state, source, e_init); + SASSERT(state.m_delta[source] <= state.m_delta[target]); + + state.m_heap.insert(source); + state.m_heap.insert(target); + unsigned num_relevant = 1; + TRACE("diff_logic", display(tout); ); + + while (!state.m_heap.empty() && num_relevant > 0) { + + ++m_stats.m_implied_literal_cost; + + source = state.m_heap.erase_min(); + source_mark = static_cast(m_mark[source]); + SASSERT(source_mark == DL_PROP_RELEVANT || source_mark == DL_PROP_IRRELEVANT); + state.m_visited.push_back(source); + if (source_mark == DL_PROP_RELEVANT) { + --num_relevant; + state.add_size(edges[source].size()); + m_mark[source] = DL_PROP_PROCESSED_RELEVANT; + } + else { + m_mark[source] = DL_PROP_PROCESSED_IRRELEVANT; + } + TRACE("diff_logic", tout << "source: " << source << "\n";); + + typename edge_id_vector::const_iterator it = edges[source].begin(); + typename edge_id_vector::const_iterator end = edges[source].end(); + + for (; it != end; ++it) { + edge_id e_id = *it; + edge const& e = m_edges[e_id]; + + if (&e == &e_init) { + continue; + } + SASSERT(!is_fw || e.get_source() == source); + SASSERT(is_fw || e.get_target() == source); + if (!e.is_enabled()) { + continue; + } + TRACE("diff_logic", display_edge(tout, e);); + target = is_fw?e.get_target():e.get_source(); + delta = get_reduced_weight(state, source, e); + SASSERT(delta >= state.m_delta[source]); + + target_mark = static_cast(m_mark[target]); + switch(target_mark) { + case DL_PROP_UNMARKED: { + state.m_delta[target] = delta; + m_mark[target] = source_mark; + state.m_heap.insert(target); + if (source_mark == DL_PROP_RELEVANT) { + ++num_relevant; + } + state.m_parent[target] = e_id; + break; + } + case DL_PROP_RELEVANT: + case DL_PROP_IRRELEVANT: { + numeral const& old_delta = state.m_delta[target]; + if (delta < old_delta || + (delta == old_delta && + source_mark == DL_PROP_IRRELEVANT && target_mark == DL_PROP_RELEVANT)) { + state.m_delta[target] = delta; + m_mark[target] = source_mark; + state.m_heap.decreased(target); + if (target_mark == DL_PROP_IRRELEVANT && source_mark == DL_PROP_RELEVANT) { + ++num_relevant; + } + if (target_mark == DL_PROP_RELEVANT && source_mark == DL_PROP_IRRELEVANT) { + --num_relevant; + } + state.m_parent[target] = e_id; + } + break; + } + case DL_PROP_PROCESSED_RELEVANT: + TRACE("diff_logic", tout << delta << " ?> " << state.m_delta[target] << "\n";); + SASSERT(delta >= state.m_delta[target]); + SASSERT(!(delta == state.m_delta[target] && source_mark == DL_PROP_IRRELEVANT)); + break; + case DL_PROP_PROCESSED_IRRELEVANT: + TRACE("diff_logic", tout << delta << " ?> " << state.m_delta[target] << "\n";); + SASSERT(delta >= state.m_delta[target]); + break; + default: + UNREACHABLE(); + } + } + } + + // + // Clear marks using m_visited and m_heap. + // + unsigned sz = state.m_visited.size(); + for (unsigned i = 0; i < sz; ) { + dl_var v = state.m_visited[i]; + source_mark = static_cast(m_mark[v]); + m_mark[v] = DL_PROP_UNMARKED; + SASSERT(source_mark == DL_PROP_PROCESSED_RELEVANT || source_mark == DL_PROP_PROCESSED_IRRELEVANT); + if (source_mark == DL_PROP_PROCESSED_RELEVANT) { + ++i; + } + else { + state.m_visited[i] = state.m_visited[--sz]; + state.m_visited.resize(sz); + } + } + + TRACE("diff_logic", { + tout << (is_fw?"is_fw":"is_bw") << ": "; + for (unsigned i = 0; i < state.m_visited.size(); ++i) { + tout << state.m_visited[i] << " "; + } + tout << "\n"; + }); + + typename heap::const_iterator it = state.m_heap.begin(); + typename heap::const_iterator end = state.m_heap.end(); + for (; it != end; ++it) { + SASSERT(m_mark[*it] != DL_PROP_UNMARKED); + m_mark[*it] = DL_PROP_UNMARKED;; + } + state.m_heap.reset(); + SASSERT(marks_are_clear()); + } + + void find_subsumed(edge_id bridge_edge, dfs_state& src, dfs_state& tgt, svector& subsumed) { + edge const& e0 = m_edges[bridge_edge]; + dl_var a = e0.get_source(); + dl_var b = e0.get_target(); + numeral n0 = m_assignment[b] - m_assignment[a] - e0.get_weight(); + vector const& edges = m_out_edges; + TRACE("diff_logic", tout << "$" << a << " a:" << m_assignment[a] << " $" << b << " b: " << m_assignment[b] + << " e0: " << e0.get_weight() << " n0: " << n0 << "\n"; + display_edge(tout, e0); + ); + + for (unsigned i = 0; i < src.m_visited.size(); ++i) { + dl_var c = src.m_visited[i]; + typename edge_id_vector::const_iterator it = edges[c].begin(); + typename edge_id_vector::const_iterator end = edges[c].end(); + numeral n1 = n0 + src.m_delta[c] - m_assignment[c]; + for (; it != end; ++it) { + edge_id e_id = *it; + edge const& e1 = m_edges[e_id]; + SASSERT(c == e1.get_source()); + if (e1.is_enabled()) { + continue; + } + dl_var d = e1.get_target(); + numeral n2 = n1 + tgt.m_delta[d] + m_assignment[d]; + + if (tgt.contains(d) && n2 <= e1.get_weight()) { + TRACE("diff_logic", + tout << "$" << c << " delta_c: " << src.m_delta[c] << " c: " << m_assignment[c] << "\n"; + tout << "$" << d << " delta_d: " << src.m_delta[d] << " d: " << m_assignment[d] + << " n2: " << n2 << " e1: " << e1.get_weight() << "\n"; + display_edge(tout << "found: ", e1);); + ++m_stats.m_num_implied_literals; + subsumed.push_back(e_id); + } + } + } + } + +public: + void find_subsumed(edge_id id, svector& subsumed) { + fix_sizes(); + find_relevant(m_fw, id); + find_relevant(m_bw, id); + find_subsumed(id, m_bw, m_fw, subsumed); + m_fw.m_visited.reset(); + m_bw.m_visited.reset(); + if (!subsumed.empty()) { + TRACE("diff_logic", + display(tout); + tout << "subsumed\n"; + for (unsigned i = 0; i < subsumed.size(); ++i) { + display_edge(tout, m_edges[subsumed[i]]); + }); + } + } + + // Find edges that are directly subsumed by id. + void find_subsumed1(edge_id id, svector& subsumed) { + edge const& e1 = m_edges[id]; + dl_var src = e1.get_source(); + dl_var dst = e1.get_target(); + edge_id_vector& out_edges = m_out_edges[src]; + edge_id_vector& in_edges = m_in_edges[dst]; + numeral w = e1.get_weight(); + typename edge_id_vector::const_iterator it, end; + + if (out_edges.size() < in_edges.size()) { + end = out_edges.end(); + for (it = out_edges.begin(); it != end; ++it) { + ++m_stats.m_implied_literal_cost; + edge_id e_id = *it; + edge const& e2 = m_edges[e_id]; + if (e_id != id && !e2.is_enabled() && e2.get_target() == dst && e2.get_weight() >= w) { + subsumed.push_back(e_id); + ++m_stats.m_num_implied_literals; + } + } + } + else { + end = in_edges.end(); + for (it = in_edges.begin(); it != end; ++it) { + ++m_stats.m_implied_literal_cost; + edge_id e_id = *it; + edge const& e2 = m_edges[e_id]; + if (e_id != id && !e2.is_enabled() && e2.get_source() == src && e2.get_weight() >= w) { + subsumed.push_back(e_id); + ++m_stats.m_num_implied_literals; + } + } + } + } + + // + // Find edges that are subsumed by id, or is an edge between + // a predecessor of id's source and id's destination, or + // is an edge between a successor of id's dst, and id's source. + // + // src - id -> dst + // - - + // src' dst' + // + // so searching for: + // . src - id' -> dst + // . src' - id' -> dst + // . src - id' -> dst' + // + void find_subsumed2(edge_id id, svector& subsumed) { + edge const& e1 = m_edges[id]; + dl_var src = e1.get_source(); + dl_var dst = e1.get_target(); + numeral w = e1.get_weight(); + numeral w2; + + find_subsumed1(id, subsumed); + + typename edge_id_vector::const_iterator it, end, it3, end3; + it = m_in_edges[src].begin(); + end = m_in_edges[src].end(); + for (; it != end; ++it) { + edge_id e_id = *it; + edge const& e2 = m_edges[e_id]; + if (!e2.is_enabled() || e2.get_source() == dst) { + continue; + } + w2 = e2.get_weight() + w; + it3 = m_out_edges[e2.get_source()].begin(); + end3 = m_out_edges[e2.get_source()].end(); + for (; it3 != end3; ++it3) { + ++m_stats.m_implied_literal_cost; + edge_id e_id3 = *it3; + edge const& e3 = m_edges[e_id3]; + if (e3.is_enabled() || e3.get_target() != dst) { + continue; + } + if (e3.get_weight() >= w2) { + subsumed.push_back(e_id3); + ++m_stats.m_num_implied_literals; + } + } + } + it = m_out_edges[dst].begin(); + end = m_out_edges[dst].end(); + for (; it != end; ++it) { + edge_id e_id = *it; + edge const& e2 = m_edges[e_id]; + + if (!e2.is_enabled() || e2.get_target() == src) { + continue; + } + w2 = e2.get_weight() + w; + it3 = m_in_edges[e2.get_target()].begin(); + end3 = m_in_edges[e2.get_target()].end(); + for (; it3 != end3; ++it3) { + ++m_stats.m_implied_literal_cost; + edge_id e_id3 = *it3; + edge const& e3 = m_edges[e_id3]; + if (e3.is_enabled() || e3.get_source() != src) { + continue; + } + if (e3.get_weight() >= w2) { + subsumed.push_back(e_id3); + ++m_stats.m_num_implied_literals; + } + } + } + } + + template + void explain_subsumed_lazy(edge_id bridge_id, edge_id subsumed_id, Functor& f) { + edge const& e1 = m_edges[bridge_id]; + edge const& e2 = m_edges[subsumed_id]; + dl_var src2 = e2.get_source(); + dl_var dst2 = e2.get_target(); + unsigned timestamp = e1.get_timestamp(); + + // + // Find path from src2 to dst2 with edges having timestamps no greater than + // timestamp, and of length no longer than weight of e2. + // + // use basic O(m*n) algorithm that traverses each edge once per node. + // + + ++m_stats.m_num_helpful_implied_literals; + + SASSERT(m_heap.empty()); + SASSERT(e1.is_enabled()); + + m_gamma[src2].reset(); + m_gamma[dst2] = e2.get_weight(); + m_heap.insert(src2); + m_visited.push_back(src2); + + TRACE("diff_logic", + display_edge(tout << "bridge: ", e1); + display_edge(tout << "subsumed: ", e2); + display(tout); ); + + while (true) { + SASSERT(!m_heap.empty()); + dl_var v = m_heap.erase_min(); + m_mark[v] = DL_PROCESSED; + TRACE("diff_logic", tout << v << "\n";); + + typename edge_id_vector::iterator it = m_out_edges[v].begin(); + typename edge_id_vector::iterator end = m_out_edges[v].end(); + + for (; it != end; ++it) { + edge_id e_id = *it; + edge const& e = m_edges[e_id]; + if (!e.is_enabled() || e.get_timestamp() > timestamp) { + continue; + } + dl_var w = e.get_target(); + numeral gamma = m_gamma[v] + e.get_weight(); + if ((m_mark[w] != DL_UNMARKED) && m_gamma[w] <= gamma) { + continue; + } + m_gamma[w] = gamma; + m_parent[w] = e_id; + TRACE("diff_logic", tout << w << " : " << gamma << " " << e2.get_weight() << "\n";); + if (w == dst2 && gamma <= e2.get_weight()) { + // found path. + reset_marks(); + m_heap.reset(); + unsigned length = 0; + do { + inc_activity(m_parent[w]); + edge const& ee = m_edges[m_parent[w]]; + f(ee.get_explanation()); + w = ee.get_source(); + ++length; + } + while (w != src2); + return; + } + switch(m_mark[w]) { + case DL_UNMARKED: + m_visited.push_back(w); + // fall through + case DL_PROCESSED: + m_mark[w] = DL_FOUND; + m_heap.insert(w); + break; + case DL_FOUND: + m_heap.decreased(w); + break; + } + } + } + UNREACHABLE(); + } +}; + +#endif /* _DIFF_LOGIC_H_ */ + +#if 0 + + +#endif diff --git a/lib/diff_neq_tactic.cpp b/lib/diff_neq_tactic.cpp new file mode 100644 index 000000000..93ac6912d --- /dev/null +++ b/lib/diff_neq_tactic.cpp @@ -0,0 +1,429 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + diff_neq_tactic.cpp + +Abstract: + + Solver for integer problems that contains literals of the form + k <= x + x <= k + x - y != k + And all variables are bounded. + +Author: + + Leonardo de Moura (leonardo) 2012-02-07. + +Revision History: + +--*/ +#include"tactical.h" +#include"arith_decl_plugin.h" +#include"ast_smt2_pp.h" +#include"model.h" + +class diff_neq_tactic : public tactic { + struct imp { + ast_manager & m; + arith_util u; + typedef unsigned var; + + expr_ref_vector m_var2expr; + obj_map m_expr2var; + + svector m_lower; + svector m_upper; + struct diseq { + var m_y; + int m_k; + diseq(var y, int k):m_y(y), m_k(k) {} + }; + typedef svector diseqs; + vector m_var_diseqs; + typedef svector decision_stack; + decision_stack m_stack; + volatile bool m_cancel; + + bool m_produce_models; + rational m_max_k; + rational m_max_neg_k; + + unsigned m_num_conflicts; + + imp(ast_manager & _m, params_ref const & p): + m(_m), + u(m), + m_var2expr(m) { + updt_params(p); + m_cancel = false; + } + + void updt_params(params_ref const & p) { + m_max_k = rational(p.get_uint(":diff-neq-max-k", 1024)); + m_max_neg_k = -m_max_k; + if (m_max_k >= rational(INT_MAX/2)) + m_max_k = rational(INT_MAX/2); + } + + void set_cancel(bool f) { + m_cancel = f; + } + + void throw_not_supported() { + throw tactic_exception("goal is not diff neq"); + } + + unsigned num_vars() const { + return m_upper.size(); + } + + var mk_var(expr * t) { + SASSERT(is_uninterp_const(t)); + var x; + if (m_expr2var.find(t, x)) + return x; + x = m_upper.size(); + m_expr2var.insert(t, x); + m_var2expr.push_back(t); + m_lower.push_back(INT_MIN); // unknown + m_upper.push_back(INT_MAX); // unknown + m_var_diseqs.push_back(diseqs()); + return x; + } + + void process_le(expr * lhs, expr * rhs) { + if (!u.is_int(lhs)) + throw_not_supported(); + rational k; + if (is_uninterp_const(lhs) && u.is_numeral(rhs, k) && m_max_neg_k <= k && k <= m_max_k) { + var x = mk_var(lhs); + int _k = static_cast(k.get_int64()); + m_upper[x] = _k; + + } + else if (is_uninterp_const(rhs) && u.is_numeral(lhs, k) && m_max_neg_k <= k && k <= m_max_k) { + var x = mk_var(rhs); + int _k = static_cast(k.get_int64()); + m_lower[x] = _k; + } + else { + throw_not_supported(); + } + } + + // process t1 - t2 != k + void process_neq_core(expr * t1, expr * t2, int k) { + var x1 = mk_var(t1); + var x2 = mk_var(t2); + if (x1 == x2) + throw_not_supported(); // must simplify first + if (x1 < x2) { + std::swap(x1, x2); + k = -k; + } + m_var_diseqs[x1].push_back(diseq(x2, k)); + } + + void process_neq(expr * lhs, expr * rhs) { + if (!u.is_int(lhs)) + throw_not_supported(); + if (is_uninterp_const(lhs) && is_uninterp_const(rhs)) { + process_neq_core(lhs, rhs, 0); + return; + } + if (u.is_numeral(lhs)) + std::swap(lhs, rhs); + rational k; + if (!u.is_numeral(rhs, k)) + throw_not_supported(); + if (!(m_max_neg_k <= k && k <= m_max_k)) + throw_not_supported(); + int _k = static_cast(k.get_int64()); + expr * t1, * t2, * mt1, * mt2; + if (u.is_add(lhs, t1, t2)) { + if (is_uninterp_const(t1) && u.is_times_minus_one(t2, mt2) && is_uninterp_const(mt2)) + process_neq_core(t1, mt2, _k); + else if (is_uninterp_const(t2) && u.is_times_minus_one(t1, mt1) && is_uninterp_const(mt1)) + process_neq_core(t2, mt1, _k); + else + throw_not_supported(); + } + else { + throw_not_supported(); + } + } + + // throws exception if contains unbounded variable + void check_unbounded() { + unsigned num = num_vars(); + for (var x = 0; x < num; x++) { + if (m_lower[x] == INT_MIN || m_upper[x] == INT_MAX) + throw_not_supported(); + // possible extension: support bound normalization here + if (m_lower[x] != 0) + throw_not_supported(); // use bound normalizer + } + } + + void compile(goal const & g) { + expr * lhs; + expr * rhs; + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + expr * f = g.form(i); + TRACE("diff_neq_tactic", tout << "processing: " << mk_ismt2_pp(f, m) << "\n";); + if (u.is_le(f, lhs, rhs)) + process_le(lhs, rhs); + else if (u.is_ge(f, lhs, rhs)) + process_le(rhs, lhs); + else if (m.is_not(f, f) && m.is_eq(f, lhs, rhs)) + process_neq(lhs, rhs); + else + throw_not_supported(); + } + check_unbounded(); + } + + void display(std::ostream & out) { + unsigned num = num_vars(); + for (var x = 0; x < num; x++) { + out << m_lower[x] << " <= " << mk_ismt2_pp(m_var2expr.get(x), m) << " <= " << m_upper[x] << "\n"; + } + for (var x = 0; x < num; x++) { + diseqs::iterator it = m_var_diseqs[x].begin(); + diseqs::iterator end = m_var_diseqs[x].end(); + for (; it != end; ++it) { + out << mk_ismt2_pp(m_var2expr.get(x), m) << " != " << mk_ismt2_pp(m_var2expr.get(it->m_y), m) << " + " << it->m_k << "\n"; + } + } + } + + void display_model(std::ostream & out) { + unsigned num = m_stack.size(); + for (var x = 0; x < num; x++) { + out << mk_ismt2_pp(m_var2expr.get(x), m) << " := " << m_stack[x] << "\n"; + } + } + + svector m_forbidden; + + // make sure m_forbidden.size() > max upper bound + void init_forbidden() { + int max = 0; + unsigned num = num_vars(); + for (var x = 0; x < num; x++) { + if (m_upper[x] > max) + max = m_upper[x]; + } + m_forbidden.reset(); + m_forbidden.resize(max+1, false); + } + + // Return a value v s.t. v >= starting_at and v <= m_upper[x] and all diseqs in m_var_diseqs[x] are satisfied. + // Return -1 if such value does not exist. + int choose_value(var x, int starting_at) { + int max = starting_at-1; + int v = starting_at; + int upper = m_upper[x]; + if (starting_at > upper) + return -1; + diseqs const & ds = m_var_diseqs[x]; + diseqs::const_iterator it = ds.begin(); + diseqs::const_iterator end = ds.end(); + for (; it != end; ++it) { + int bad_v = m_stack[it->m_y] + it->m_k; + if (bad_v < v) + continue; + if (bad_v > upper) + continue; + if (bad_v == v) { + while (true) { + v++; + if (v > upper) + return -1; + if (!m_forbidden[v]) + break; + m_forbidden[v] = false; + } + continue; + } + SASSERT(bad_v > v && bad_v <= upper); + m_forbidden[bad_v] = true; + if (bad_v > max) + max = bad_v; + } + // reset forbidden + for (int i = starting_at + 1; i <= max; i++) + m_forbidden[i] = false; + DEBUG_CODE({ + for (unsigned i = 0; i < m_forbidden.size(); i++) { + SASSERT(!m_forbidden[i]); + } + }); + return v; + } + + bool extend_model(var x) { + int v = choose_value(x, 0); + if (v == -1) + return false; + m_stack.push_back(v); + return true; + } + + bool resolve_conflict() { + m_num_conflicts++; + while (!m_stack.empty()) { + int v = m_stack.back(); + m_stack.pop_back(); + var x = m_stack.size(); + v = choose_value(x, v+1); + if (v != -1) { + m_stack.push_back(v); + return true; + } + } + return false; + } + + bool search() { + m_num_conflicts = 0; + init_forbidden(); + unsigned nvars = num_vars(); + while (m_stack.size() < nvars) { + if (m_cancel) + throw tactic_exception(TACTIC_CANCELED_MSG); + TRACE("diff_neq_tactic", display_model(tout);); + var x = m_stack.size(); + if (extend_model(x)) + continue; + if (!resolve_conflict()) + return false; + } + TRACE("diff_neq_tactic", display_model(tout);); + return true; + } + + model * mk_model() { + model * md = alloc(model, m); + unsigned num = num_vars(); + SASSERT(m_stack.size() == num); + for (var x = 0; x < num; x++) { + func_decl * d = to_app(m_var2expr.get(x))->get_decl(); + md->register_decl(d, u.mk_numeral(rational(m_stack[x]), true)); + } + return md; + } + + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + m_produce_models = g->models_enabled(); + mc = 0; pc = 0; core = 0; result.reset(); + tactic_report report("diff-neq", *g); + fail_if_proof_generation("diff-neq", g); + fail_if_unsat_core_generation("diff-neq", g); + if (g->inconsistent()) { + result.push_back(g.get()); + return; + } + compile(*g); + TRACE("diff_neq_tactic", g->display(tout); display(tout);); + bool r = search(); + report_tactic_progress(":conflicts", m_num_conflicts); + if (r) { + if (m_produce_models) + mc = model2model_converter(mk_model()); + g->reset(); + } + else { + g->assert_expr(m.mk_false()); + } + g->inc_depth(); + result.push_back(g.get()); + TRACE("diff_neq", g->display(tout);); + SASSERT(g->is_well_sorted()); + } + }; + + imp * m_imp; + params_ref m_params; +public: + diff_neq_tactic(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(diff_neq_tactic, m, m_params); + } + + virtual ~diff_neq_tactic() { + dealloc(m_imp); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + r.insert(":diff-neq-max-k", CPK_UINT, "(default: 1024) maximum variable upper bound for diff neq solver."); + } + + virtual void collect_statistics(statistics & st) const { + st.update("conflicts", m_imp->m_num_conflicts); + } + + virtual void reset_statistics() { + m_imp->m_num_conflicts = 0; + } + + /** + \brief Fix a DL variable in s to 0. + If s is not really in the difference logic fragment, then this is a NOOP. + */ + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + (*m_imp)(in, result, mc, pc, core); + } + + virtual void cleanup() { + unsigned num_conflicts = m_imp->m_num_conflicts; + ast_manager & m = m_imp->m; + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + d = m_imp; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + m_imp->m_num_conflicts = num_conflicts; + } + +protected: + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + +tactic * mk_diff_neq_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(diff_neq_tactic, m, p)); +} + + + diff --git a/lib/diff_neq_tactic.h b/lib/diff_neq_tactic.h new file mode 100644 index 000000000..a63940fde --- /dev/null +++ b/lib/diff_neq_tactic.h @@ -0,0 +1,32 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + diff_neq_tactic.h + +Abstract: + + Solver for integer problems that contains literals of the form + k <= x + x <= k + x - y != k + And all variables are bounded. + +Author: + + Leonardo de Moura (leonardo) 2012-02-07. + +Revision History: + +--*/ +#ifndef _DIFF_NEQ_TACTIC_H_ +#define _DIFF_NEQ_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_diff_neq_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/dimacs.cpp b/lib/dimacs.cpp new file mode 100644 index 000000000..51b964dd3 --- /dev/null +++ b/lib/dimacs.cpp @@ -0,0 +1,130 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + dimacs.cpp + +Abstract: + + Dimacs CNF parser + +Author: + + Leonardo de Moura (leonardo) 2011-07-26. + +Revision History: + +--*/ +#include"dimacs.h" +#include"sat_solver.h" + +class stream_buffer { + std::istream & m_stream; + int m_val; +public: + + stream_buffer(std::istream & s): + m_stream(s) { + m_val = m_stream.get(); + } + + int operator *() const { + return m_val; + } + + void operator ++() { + m_val = m_stream.get(); + } +}; + +template +void skip_whitespace(Buffer & in) { + while ((*in >= 9 && *in <= 13) || *in == 32) { + ++in; + } +} + +template +void skip_line(Buffer & in) { + while(true) { + if (*in == EOF) { + return; + } + if (*in == '\n') { + ++in; + return; + } + ++in; + } +} + +template +int parse_int(Buffer & in) { + int val = 0; + bool neg = false; + skip_whitespace(in); + + if (*in == '-') { + neg = true; + ++in; + } + else if (*in == '+') { + ++in; + } + + if (*in < '0' || *in > '9') { + std::cerr << "(error, \"unexpected char: " << *in << "\")\n"; + exit(3); + exit(ERR_PARSER); + } + + while (*in >= '0' && *in <= '9') { + val = val*10 + (*in - '0'); + ++in; + } + + return neg ? -val : val; +} + +template +void read_clause(Buffer & in, sat::solver & solver, sat::literal_vector & lits) { + int parsed_lit; + int var; + + lits.reset(); + + while (true) { + parsed_lit = parse_int(in); + if (parsed_lit == 0) + break; + var = abs(parsed_lit); + SASSERT(var > 0); + while (static_cast(var) >= solver.num_vars()) + solver.mk_var(); + lits.push_back(sat::literal(var, parsed_lit < 0)); + } +} + +template +void parse_dimacs_core(Buffer & in, sat::solver & solver) { + sat::literal_vector lits; + while (true) { + skip_whitespace(in); + if (*in == EOF) { + break; + } + else if (*in == 'c' || *in == 'p') { + skip_line(in); + } + else { + read_clause(in, solver, lits); + solver.mk_clause(lits.size(), lits.c_ptr()); + } + } +} + +void parse_dimacs(std::istream & in, sat::solver & solver) { + stream_buffer _in(in); + parse_dimacs_core(_in, solver); +} diff --git a/lib/dimacs.h b/lib/dimacs.h new file mode 100644 index 000000000..63f0a1ee1 --- /dev/null +++ b/lib/dimacs.h @@ -0,0 +1,27 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + dimacs.h + +Abstract: + + Dimacs CNF parser + +Author: + + Leonardo de Moura (leonardo) 2011-07-26. + +Revision History: + +--*/ +#ifndef _DIMACS_H_ +#define _DIMACS_H_ + +#include"sat_types.h" + +void parse_dimacs(std::istream & s, sat::solver & solver); + +#endif /* _DIMACS_PARSER_H_ */ + diff --git a/lib/distribute_forall.cpp b/lib/distribute_forall.cpp new file mode 100644 index 000000000..5e2958579 --- /dev/null +++ b/lib/distribute_forall.cpp @@ -0,0 +1,170 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + distribute_forall.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-04-02. + +Revision History: + + Christoph Wintersteiger 2010-04-06: Added implementation. + +--*/ +#include"var_subst.h" +#include"ast_ll_pp.h" + +#include"distribute_forall.h" + +distribute_forall::distribute_forall(ast_manager & m, basic_simplifier_plugin & p) : + m_manager(m), + m_bsimp(p), + m_cache(m) { +} + +void distribute_forall::visit(expr * n, bool & visited) { + if (!is_cached(n)) { + m_todo.push_back(n); + visited = false; + } +} + +bool distribute_forall::visit_children(expr * n) { + bool visited = true; + unsigned j; + switch(n->get_kind()) { + case AST_VAR: + break; + case AST_APP: + j = to_app(n)->get_num_args(); + while (j > 0) { + --j; + visit(to_app(n)->get_arg(j), visited); + } + break; + case AST_QUANTIFIER: + visit(to_quantifier(n)->get_expr(), visited); + break; + default: + UNREACHABLE(); + } + return visited; +} + +void distribute_forall::reduce1(expr * n) { + switch (n->get_kind()) { + case AST_VAR: + cache_result(n, n); + break; + case AST_APP: + reduce1_app(to_app(n)); + break; + case AST_QUANTIFIER: + reduce1_quantifier(to_quantifier(n)); + break; + default: UNREACHABLE(); + } +} + +void distribute_forall::reduce1_app(app * a) { + SASSERT(a); + unsigned num_args = a->get_num_args(); + unsigned j = num_args; + bool reduced = false; + m_new_args.reserve(num_args); + app * na = a; + + while(j > 0) { + --j; + SASSERT(is_cached(a->get_arg(j))); + expr * c = get_cached(a->get_arg(j)); + SASSERT(c!=0); + if (c != a->get_arg(j)) + reduced = true; + m_new_args[j] = c; + } + + if (reduced) { + na = m_manager.mk_app(a->get_decl(), num_args, m_new_args.c_ptr()); + } + + cache_result(a, na); +} + +void distribute_forall::reduce1_quantifier(quantifier * q) { + // This transformation is applied after skolemization/quantifier elimination. So, all quantifiers are universal. + SASSERT(q->is_forall()); + + // This transformation is applied after basic pre-processing steps. + // So, we can assume that + // 1) All (and f1 ... fn) are already encoded as (not (or (not f1 ... fn))) + // 2) All or-formulas are flat (or f1 (or f2 f3)) is encoded as (or f1 f2 f3) + + expr * e = get_cached(q->get_expr()); + if (m_manager.is_not(e) && m_manager.is_or(to_app(e)->get_arg(0))) { + // found target for simplification + // (forall X (not (or F1 ... Fn))) + // --> + // (and (forall X (not F1)) + // ... + // (forall X (not Fn))) + app * or_e = to_app(to_app(e)->get_arg(0)); + unsigned num_args = or_e->get_num_args(); + expr_ref_buffer new_args(m_manager); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = or_e->get_arg(i); + expr_ref not_arg(m_manager); + // m_bsimp.mk_not applies basic simplifications. For example, if arg is of the form (not a), then it will return a. + m_bsimp.mk_not(arg, not_arg); + quantifier_ref tmp_q(m_manager); + tmp_q = m_manager.update_quantifier(q, not_arg); + expr_ref new_q(m_manager); + elim_unused_vars(m_manager, tmp_q, new_q); + new_args.push_back(new_q); + } + expr_ref result(m_manager); + // m_bsimp.mk_and actually constructs a (not (or ...)) formula, + // it will also apply basic simplifications. + m_bsimp.mk_and(new_args.size(), new_args.c_ptr(), result); + cache_result(q, result); + } + else { + cache_result(q, m_manager.update_quantifier(q, e)); + } +} + +void distribute_forall::operator()(expr * f, expr_ref & result) { + m_todo.reset(); + flush_cache(); + + m_todo.push_back(f); + + while (!m_todo.empty()) { + expr * e = m_todo.back(); + if (visit_children(e)) { + m_todo.pop_back(); + reduce1(e); + } + } + + result = get_cached(f); + SASSERT(result!=0); + TRACE("distribute_forall", tout << mk_ll_pp(f, m_manager) << "======>\n" + << mk_ll_pp(result, m_manager);); +} + +expr * distribute_forall::get_cached(expr * n) const { + return const_cast(this)->m_cache.find(n); +} + +void distribute_forall::cache_result(expr * n, expr * r) { + SASSERT(r != 0); + m_cache.insert(n, r); +} diff --git a/lib/distribute_forall.h b/lib/distribute_forall.h new file mode 100644 index 000000000..8816bf22e --- /dev/null +++ b/lib/distribute_forall.h @@ -0,0 +1,82 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + distribute_forall.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-04-02. + +Revision History: + + Christoph Wintersteiger 2010-04-06: Added implementation + +--*/ +#ifndef _DISTRIBUTE_FORALL_H_ +#define _DISTRIBUTE_FORALL_H_ + +#include"ast.h" +#include"basic_simplifier_plugin.h" +#include"act_cache.h" + +/** + \brief Apply the following transformation + (forall X (and F1 ... Fn)) + --> + (and (forall X F1) ... (forall X Fn)) + + The actual transformation is slightly different since the "and" connective is eliminated and + replaced with a "not or". + So, the actual transformation is: + + (forall X (not (or F1 ... Fn))) + --> + (not (or (not (forall X (not F1))) + ... + (not (forall X (not Fn))))) + + + The implementation uses the visit_children/reduce1 idiom. A cache is used as usual. +*/ +class distribute_forall { + typedef act_cache expr_map; + ast_manager & m_manager; + basic_simplifier_plugin & m_bsimp; // useful for constructing formulas and/or/not in simplified form. + ptr_vector m_todo; + expr_map m_cache; + ptr_vector m_new_args; + // The new expressions are stored in a mapping that increments their reference counter. So, we do not need to store them in + // m_new_exprs + // expr_ref_vector m_new_exprs; + + +public: + distribute_forall(ast_manager & m, basic_simplifier_plugin & p); + + /** + \brief Apply the distribute_forall transformation (when possible) to all universal quantifiers in \c f. + Store the result in \c result. + */ + void operator()(expr * f, expr_ref & result); + +protected: + inline void visit(expr * n, bool & visited); + bool visit_children(expr * n); + void reduce1(expr * n); + void reduce1_quantifier(quantifier * q); + void reduce1_app(app * a); + + expr * get_cached(expr * n) const; + bool is_cached(expr * n) const { return get_cached(n) != 0; } + void cache_result(expr * n, expr * r); + void reset_cache() { m_cache.reset(); } + void flush_cache() { m_cache.cleanup(); } +}; + +#endif /* _DISTRIBUTE_FORALL_H_ */ diff --git a/lib/distribute_forall_tactic.cpp b/lib/distribute_forall_tactic.cpp new file mode 100644 index 000000000..913b55174 --- /dev/null +++ b/lib/distribute_forall_tactic.cpp @@ -0,0 +1,149 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + distribute_forall_tactic.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2012-02-18. + +--*/ +#include"tactical.h" +#include"rewriter_def.h" +#include"var_subst.h" + +class distribute_forall_tactic : public tactic { + + struct rw_cfg : public default_rewriter_cfg { + ast_manager & m; + + rw_cfg(ast_manager & _m):m(_m) {} + bool reduce_quantifier(quantifier * old_q, + expr * new_body, + expr * const * new_patterns, + expr * const * new_no_patterns, + expr_ref & result, + proof_ref & result_pr) { + + if (m.is_not(new_body) && m.is_or(to_app(new_body)->get_arg(0))) { + // (forall X (not (or F1 ... Fn))) + // --> + // (and (forall X (not F1)) + // ... + // (forall X (not Fn))) + app * or_e = to_app(to_app(new_body)->get_arg(0)); + unsigned num_args = or_e->get_num_args(); + expr_ref_buffer new_args(m); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = or_e->get_arg(i); + expr * not_arg = m.mk_not(arg); + quantifier_ref tmp_q(m); + tmp_q = m.update_quantifier(old_q, not_arg); + expr_ref new_q(m); + elim_unused_vars(m, tmp_q, new_q); + new_args.push_back(new_q); + } + result = m.mk_and(new_args.size(), new_args.c_ptr()); + return true; + } + + if (m.is_and(new_body)) { + // (forall X (and F1 ... Fn)) + // --> + // (and (forall X F1) + // ... + // (forall X Fn) + unsigned num_args = to_app(new_body)->get_num_args(); + expr_ref_buffer new_args(m); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = to_app(new_body)->get_arg(i); + quantifier_ref tmp_q(m); + tmp_q = m.update_quantifier(old_q, arg); + expr_ref new_q(m); + elim_unused_vars(m, tmp_q, new_q); + new_args.push_back(new_q); + } + result = m.mk_and(new_args.size(), new_args.c_ptr()); + return true; + } + + return false; + } + }; + + struct rw : public rewriter_tpl { + rw_cfg m_cfg; + + rw(ast_manager & m, bool proofs_enabled): + rewriter_tpl(m, proofs_enabled, m_cfg), + m_cfg(m) { + } + }; + + rw * m_rw; + +public: + distribute_forall_tactic():m_rw(0) {} + + virtual tactic * translate(ast_manager & m) { + return alloc(distribute_forall_tactic); + } + + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + ast_manager & m = g->m(); + bool produce_proofs = g->proofs_enabled(); + rw r(m, produce_proofs); + #pragma omp critical (tactic_cancel) + { + m_rw = &r; + } + mc = 0; pc = 0; core = 0; result.reset(); + tactic_report report("distribute-forall", *g); + + expr_ref new_curr(m); + proof_ref new_pr(m); + unsigned size = g->size(); + for (unsigned idx = 0; idx < size; idx++) { + if (g->inconsistent()) + break; + expr * curr = g->form(idx); + r(curr, new_curr, new_pr); + if (g->proofs_enabled()) { + proof * pr = g->pr(idx); + new_pr = m.mk_modus_ponens(pr, new_pr); + } + g->update(idx, new_curr, new_pr, g->dep(idx)); + } + + g->inc_depth(); + result.push_back(g.get()); + TRACE("distribute-forall", g->display(tout);); + SASSERT(g->is_well_sorted()); + #pragma omp critical (tactic_cancel) + { + m_rw = 0; + } + } + + virtual void set_cancel(bool f) { + if (m_rw) + m_rw->set_cancel(f); + } + + virtual void cleanup() {} +}; + +tactic * mk_distribute_forall_tactic(ast_manager & m, params_ref const & p) { + return alloc(distribute_forall_tactic); +} diff --git a/lib/distribute_forall_tactic.h b/lib/distribute_forall_tactic.h new file mode 100644 index 000000000..ac722e61c --- /dev/null +++ b/lib/distribute_forall_tactic.h @@ -0,0 +1,26 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + distribute_forall_tactic.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2012-02-18. + +--*/ +#ifndef _DISTRIBUTE_FORALL_TACTIC_H_ +#define _DISTRIBUTE_FORALL_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_distribute_forall_tactic(ast_manager & m, params_ref const & p); + +#endif diff --git a/lib/dl_base.cpp b/lib/dl_base.cpp new file mode 100644 index 000000000..5556920f0 --- /dev/null +++ b/lib/dl_base.cpp @@ -0,0 +1,490 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_base.cpp + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-09-14. + +Revision History: + +--*/ + + +#include"ast_pp.h" +#include"union_find.h" +#include"vector.h" +#include"dl_context.h" +#include"dl_base.h" +#include"bool_rewriter.h" +#include + + +namespace datalog { + + context & get_context_from_rel_manager(const relation_manager & rm) { + return rm.get_context(); + } + + ast_manager & get_ast_manager_from_rel_manager(const relation_manager & rm) { + return rm.get_context().get_manager(); + } + +#if DL_LEAK_HUNTING + void leak_guard_check(const symbol & s) { + } +#endif + + void relation_signature::output(ast_manager & m, std::ostream & out) const { + unsigned sz=size(); + out<<"("; + for(unsigned i=0; i reset_fn = + get_manager().mk_filter_interpreted_fn(static_cast(*this), bottom_ref); + if(!reset_fn) { + NOT_IMPLEMENTED_YET(); + } + (*reset_fn)(*this); + } + + + + void table_signature::from_join(const table_signature & s1, const table_signature & s2, unsigned col_cnt, + const unsigned * cols1, const unsigned * cols2, table_signature & result) { + result.reset(); + + unsigned s1sz=s1.size(); + unsigned s2sz=s2.size(); + unsigned s1first_func=s1sz-s1.functional_columns(); + unsigned s2first_func=s2sz-s2.functional_columns(); + for(unsigned i=0; i=col_cnt); + result.set_functional_columns(func_cnt-col_cnt); + } + } + + void table_signature::from_project_with_reduce(const table_signature & src, unsigned col_cnt, + const unsigned * removed_cols, table_signature & result) { + signature_base::from_project(src, col_cnt, removed_cols, result); + + unsigned remaining_fun = src.functional_columns(); + unsigned first_src_fun = src.first_functional(); + for(int i=col_cnt-1; i>=0; i--) { + if(removed_cols[i] remaining_in_equivalence_class; + remaining_in_equivalence_class.resize(join_sig_sz, 0); + bool merging_rows_can_happen = false; + + union_find_default_ctx uf_ctx; + union_find<> uf(uf_ctx); //the numbers in uf correspond to column indexes after the join + for(unsigned i=0; icols1[i]) ? cols1[i] : (first_func_ofs+cols1[i]-s1_first_func); + unsigned idx2 = (s2_first_func>cols2[i]) ? (second_ofs+cols2[i]) : (second_func_ofs+cols2[i]-s2_first_func); + uf.merge(idx1, idx2); + } + for(unsigned i=0; i=first_func_ofs) { + //removing functional columns won't make us merge rows + continue; + } + unsigned eq_class_idx = uf.find(rc); + if(remaining_in_equivalence_class[eq_class_idx]>1) { + remaining_in_equivalence_class[eq_class_idx]--; + } + else { + merging_rows_can_happen = true; + break; + } + } + + if(merging_rows_can_happen) { + //this one marks all columns as non-functional + from_project(aux, removed_col_cnt, removed_cols, result); + SASSERT(result.functional_columns()==0); + } + else { + //this one preserves columns to be functional + from_project_with_reduce(aux, removed_col_cnt, removed_cols, result); + } + } + + + + + // ----------------------------------- + // + // table_base + // + // ----------------------------------- + + //here we give generic implementation of table operations using iterators + + bool table_base::empty() const { + return begin()==end(); + } + + void table_base::remove_facts(unsigned fact_cnt, const table_fact * facts) { + for(unsigned i=0; i to_remove; + table_base::iterator it = begin(); + table_base::iterator iend = end(); + table_fact row; + for(; it!=iend; ++it) { + it->get_fact(row); + to_remove.append(row); + } + remove_facts(to_remove.size(), to_remove.c_ptr()); + } + + bool table_base::contains_fact(const table_fact & f) const { + iterator it = begin(); + iterator iend = end(); + + table_fact row; + + for(; it!=iend; ++it) { + it->get_fact(row); + if(vectors_equal(row, f)) { + return true; + } + } + return false; + } + + bool table_base::fetch_fact(table_fact & f) const { + if(get_signature().functional_columns()==0) { + return contains_fact(f); + } + else { + unsigned sig_sz = get_signature().size(); + unsigned non_func_cnt = sig_sz-get_signature().functional_columns(); + table_base::iterator it = begin(); + table_base::iterator iend = end(); + table_fact row; + for(; it!=iend; ++it) { + it->get_fact(row); + bool differs = false; + for(unsigned i=0; iget_fact(row); + res->add_new_fact(row); + } + return res; + } + + table_base * table_base::complement(func_decl* p, const table_element * func_columns) const { + const table_signature & sig = get_signature(); + SASSERT(sig.functional_columns()==0 || func_columns!=0); + + table_base * res = get_plugin().mk_empty(sig); + + table_fact fact; + fact.resize(sig.first_functional()); + fact.append(sig.functional_columns(), func_columns); + + if(sig.first_functional()==0) { + if(empty()) { + res->add_fact(fact); + } + return res; + } + + if(sig.first_functional()!=1) { //now we support only tables with one non-functional column + NOT_IMPLEMENTED_YET(); + } + + uint64 upper_bound = get_signature()[0]; + bool empty_table = empty(); + + if (upper_bound > (1 << 18)) { + std::ostringstream buffer; + buffer << "creating large table of size " << upper_bound; + if (p) buffer << " for relation " << p->get_name(); + warning_msg(buffer.str().c_str()); + } + + for(table_element i=0; iadd_fact(fact); + } + } + return res; +#if 0 + svector var_arg_indexes(arity); + var_arg_indexes.fill(0); + + svector var_arg_domain_sizes = s; + + unsigned var_cnt=var_arg_indexes.size(); + table_fact fact; + fact.resize(arity); + fact.fill(0); + unsigned depth=arity; + + while(true) { + if(depth==arity) { + SASSERT(!res->contains_fact(fact)); + if(empty_table || !contains_fact(fact)) { + res->add_fact(fact); + } + depth--; + } + else if(fact[depth]==s[depth]-1) { + val_indexes[depth]=0; + if(depth==0) { + break; + } + depth--; + } + else { + SASSERT(val_indexes[depth] + +Author: + + Krystof Hoder (t-khoder) 2010-09-23. + +Revision History: + +--*/ +#ifndef _DL_BASE_H_ +#define _DL_BASE_H_ + +#define DL_LEAK_HUNTING 0 + +#include + +#include"ast.h" +#include"map.h" +#include"vector.h" +#include"ref.h" +#include"dl_util.h" + +namespace datalog { + + class context; + class relation_manager; + + ast_manager & get_ast_manager_from_rel_manager(const relation_manager & rm); + context & get_context_from_rel_manager(const relation_manager & rm); + +#if DL_LEAK_HUNTING + void leak_guard_check(const symbol & s); +#endif + + + /** + Termplate class containing common infrastructure for relations and tables + */ + template + struct tr_infrastructure { + + typedef typename Traits::plugin plugin; + typedef typename Traits::base_object base_object; + typedef typename Traits::element element; + typedef typename Traits::fact fact; + typedef typename Traits::sort sort; + typedef typename Traits::signature_base_base signature_base_base; //this must be a vector-like type + typedef typename Traits::signature signature; //this must be a vector-like type + + /** + The client submits an initial class to be used as a base for signature. Then we excend it by + the common signature methods into a signature_base class which then the client inherits from + to obtain the actual signature class. + */ + class signature_base : public signature_base_base { + public: + bool operator==(const signature & o) const { + unsigned n=signature_base_base::size(); + if(n!=o.size()) { + return false; + } + return memcmp(this->c_ptr(), o.c_ptr(), n*sizeof(sort))==0; + /*for(unsigned i=0; i=2); + result=src; + + permutate_by_cycle(result, cycle_len, permutation_cycle); + } + + /** + \brief Into \c result assign signature \c src with reordered columns. + */ + static void from_permutation_rename(const signature & src, + const unsigned * permutation, signature & result) { + result.reset(); + unsigned n = src.size(); + for(unsigned i=0; i(0)); + } + }; + + /** + \brief Mutator for relations that propagate constraints as a consequence of + combination. + + - supports_attachment + is used to query the mutator if it allows communicating + constraints to relations of the kind of the relation. + + - attach + is used to associate downstream clients. + It assumes that the relation kind is supported (supports_kind returns true) + */ + class mutator_fn : public base_fn { + public: + virtual void operator()(base_object & t) = 0; + + virtual bool supports_attachment(base_object& other) { return false; } + + virtual void attach(base_object& other) { UNREACHABLE(); } + }; + + class intersection_filter_fn : public base_fn { + public: + virtual void operator()(base_object & t, const base_object & intersected_obj) = 0; + }; + + class default_join_project_fn; + + /** + \brief Plugin class providing factory functions for a table and operations on it. + + The functor factory functions (mk_*_fn) may return 0. It means that the plugin + is unable to perform the operation on relations/tables of the particular kind. + */ + class plugin_object { + friend class relation_manager; + friend class check_table_plugin; + + family_id m_kind; + symbol m_name; + relation_manager & m_manager; + protected: + plugin_object(symbol const& name, relation_manager & manager) + : m_kind(null_family_id), m_name(name), m_manager(manager) {} + + /** + \brief Check \c r is of the same kind as the plugin. + */ + bool check_kind(base_object const& r) const { return &r.get_plugin()==this; } + public: + virtual ~plugin_object() {} + + virtual void initialize(family_id fid) { m_kind = fid; } + + family_id get_kind() const { return m_kind; } + + symbol const& get_name() const { return m_name; } + + + relation_manager & get_manager() const { return m_manager; } + ast_manager& get_ast_manager() const { return datalog::get_ast_manager_from_rel_manager(m_manager); } + context& get_context() const { return datalog::get_context_from_rel_manager(m_manager); } + + virtual bool can_handle_signature(const signature & s) = 0; + + virtual bool can_handle_signature(const signature & s, family_id kind) { + return can_handle_signature(s); + } + + /** + \brief Create empty table/relation with given signature and return pointer to it. + + Precondition: can_handle_signature(s)==true + */ + virtual base_object * mk_empty(const signature & s) = 0; + + /** + \brief Create empty table/relation with signature \c s and kind \c kind. + + Precondition: &orig.get_plugin()==this + */ + virtual base_object * mk_empty(const signature & s, family_id kind) { + SASSERT(kind==get_kind()); //if plugin uses multiple kinds, this function needs to be overriden + return mk_empty(s); + } + + /** + \brief Create empty table/relation of the same specification as \c orig and return pointer to it. + + Precondition: &orig.get_plugin()==this + */ + virtual base_object * mk_empty(const base_object & orig) { + return mk_empty(orig.get_signature(), orig.get_kind()); + } + + /** + \brief Create full table/relation with given signature and return pointer to it. + + Precondition: can_handle_signature(s)==true + */ + virtual base_object * mk_full(func_decl* p, const signature & s) { + base_object * aux = mk_empty(s); + base_object * res = aux->complement(p); + aux->deallocate(); + return res; + } + + virtual base_object * mk_full(func_decl* p, const signature & s, family_id kind) { + if (kind == get_kind() || kind == null_family_id) { + return mk_full(p, s); + } + base_object * aux = mk_empty(s, kind); + base_object * res = aux->complement(p); + aux->deallocate(); + return res; + } + + protected: + //see \c relation_manager for documentation of the operations + + virtual join_fn * mk_join_fn(const base_object & t1, const base_object & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2) { return 0; } + + virtual transformer_fn * mk_project_fn(const base_object & t, unsigned col_cnt, + const unsigned * removed_cols) { return 0; } + + virtual join_fn * mk_join_project_fn(const base_object & t1, const base_object & t2, + unsigned joined_col_cnt, const unsigned * cols1, const unsigned * cols2, + unsigned removed_col_cnt, const unsigned * removed_cols) { return 0; } + + virtual transformer_fn * mk_rename_fn(const base_object & t, unsigned permutation_cycle_len, + const unsigned * permutation_cycle) { return 0; } + + virtual transformer_fn * mk_permutation_rename_fn(const base_object & t, + const unsigned * permutation) { return 0; } + + public: + virtual union_fn * mk_union_fn(const base_object & tgt, const base_object & src, + const base_object * delta) { return 0; } + protected: + + virtual union_fn * mk_widen_fn(const base_object & tgt, const base_object & src, + const base_object * delta) { return 0; } + + virtual mutator_fn * mk_filter_identical_fn(const base_object & t, unsigned col_cnt, + const unsigned * identical_cols) { return 0; } + + virtual mutator_fn * mk_filter_equal_fn(const base_object & t, const element & value, + unsigned col) { return 0; } + + virtual mutator_fn * mk_filter_interpreted_fn(const base_object & t, app * condition) + { return 0; } + + virtual transformer_fn * mk_select_equal_and_project_fn(const base_object & t, + const element & value, unsigned col) { return 0; } + + virtual intersection_filter_fn * mk_filter_by_intersection_fn(const base_object & t, + const base_object & src, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * src_cols) + { return 0; } + + virtual intersection_filter_fn * mk_filter_by_negation_fn(const base_object & t, + const base_object & negated_obj, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * negated_cols) + { return 0; } + }; + + class base_ancestor { + plugin & m_plugin; + signature m_signature; + family_id m_kind; +#if DL_LEAK_HUNTING + sort_ref m_leak_guard; +#endif + protected: + base_ancestor(plugin & p, const signature & s) + : m_plugin(p), m_signature(s), m_kind(p.get_kind()) +#if DL_LEAK_HUNTING + , m_leak_guard(p.get_ast_manager().mk_fresh_sort(p.get_name().bare_str()), p.get_ast_manager()) +#endif + { +#if DL_LEAK_HUNTING + leak_guard_check(m_leak_guard->get_name()); +#endif + } + +#if DL_LEAK_HUNTING + base_ancestor(const base_ancestor & o) + : m_plugin(o.m_plugin), + m_signature(o.m_signature), + m_kind(o.m_kind), + m_leak_guard(m_plugin.get_ast_manager().mk_fresh_sort(m_plugin.get_name().bare_str()), + m_plugin.get_ast_manager()) { + leak_guard_check(m_leak_guard->get_name()); + } +#endif + + virtual ~base_ancestor() {} + + void set_kind(family_id kind) { SASSERT(kind>=0); m_kind = kind; } + + /** + Since the destructor is protected, we cannot use the \c dealloc macro. + */ + void destroy() { + SASSERT(this); + this->~base_ancestor(); +#if _DEBUG + memory::deallocate(__FILE__, __LINE__, this); +#else + memory::deallocate(this); +#endif + } + public: + /** + Deallocate the current object. + + Pointers and references to the current object become invalid after a call to this function. + */ + virtual void deallocate() { + destroy(); + } + /** + It must hold that operations created for particular table/relation are able to operate on + tables/relations of the same signature and kind. In most cases it is sufficient to use the kind + of the plugin object. + + However, it there is some parameter that is not reflected in the signature, the plugin may need to + allocate different kind numbers to the tables is creates. + */ + family_id get_kind() const { return m_kind; } + const signature & get_signature() const { return m_signature; } + plugin & get_plugin() const { return m_plugin; } + relation_manager & get_manager() const { return get_plugin().get_manager(); } + + virtual bool empty() const = 0; + virtual void add_fact(const fact & f) = 0; + /** + \brief Like \c add_fact, only here the caller guarantees that the fact is not present in + the table yet. + */ + virtual void add_new_fact(const fact & f) { + add_fact(f); + } + virtual bool contains_fact(const fact & f) const = 0; + + virtual void reset() = 0; + + /** + \brief Return table/relation that contains the same data as the current one. + */ + virtual base_object * clone() const = 0; + + virtual bool can_swap(const base_object & o) const { return false; } + + virtual void swap(base_object & o) { + std::swap(m_kind, o.m_kind); +#if DL_LEAK_HUNTING + m_leak_guard = get_plugin().get_ast_manager().mk_fresh_sort(get_plugin().get_name().bare_str()); + o.m_leak_guard = get_plugin().get_ast_manager().mk_fresh_sort(get_plugin().get_name().bare_str()); + leak_guard_check(m_leak_guard->get_name()); + leak_guard_check(o.m_leak_guard->get_name()); +#endif + } + + virtual unsigned get_size_estimate_rows() const { return UINT_MAX; } + virtual unsigned get_size_estimate_bytes() const { return UINT_MAX; } + virtual bool knows_exact_size() const { return false; } + + virtual void display(std::ostream & out) const = 0; + }; + + + class convenient_join_fn : public join_fn { + signature m_result_sig; + protected: + const unsigned_vector m_cols1; + const unsigned_vector m_cols2; + + convenient_join_fn(const signature & o1_sig, const signature & o2_sig, unsigned col_cnt, + const unsigned * cols1, const unsigned * cols2) + : m_cols1(col_cnt, cols1), + m_cols2(col_cnt, cols2) { + signature::from_join(o1_sig, o2_sig, col_cnt, cols1, cols2, m_result_sig); + } + + const signature & get_result_signature() const { return m_result_sig; } + }; + + class convenient_join_project_fn : public join_fn { + signature m_result_sig; + protected: + const unsigned_vector m_cols1; + const unsigned_vector m_cols2; + //it is non-const because it needs to be modified in sparse_table version of the join_project operator + unsigned_vector m_removed_cols; + + convenient_join_project_fn(const signature & o1_sig, const signature & o2_sig, + unsigned joined_col_cnt, const unsigned * cols1, const unsigned * cols2, + unsigned removed_col_cnt, const unsigned * removed_cols) + : m_cols1(joined_col_cnt, cols1), + m_cols2(joined_col_cnt, cols2), + m_removed_cols(removed_col_cnt, removed_cols) { + + signature::from_join_project(o1_sig, o2_sig, joined_col_cnt, cols1, cols2, + removed_col_cnt, removed_cols, m_result_sig); + } + + const signature & get_result_signature() const { return m_result_sig; } + }; + + class convenient_transformer_fn : public transformer_fn { + signature m_result_sig; + protected: + signature & get_result_signature() { return m_result_sig; } + const signature & get_result_signature() const { return m_result_sig; } + }; + + class convenient_project_fn : public convenient_transformer_fn { + protected: + const unsigned_vector m_removed_cols; + + convenient_project_fn(const signature & orig_sig, unsigned col_cnt, const unsigned * removed_cols) + : m_removed_cols(col_cnt, removed_cols) { + signature::from_project(orig_sig, col_cnt, removed_cols, + convenient_transformer_fn::get_result_signature()); + } + }; + + class convenient_rename_fn : public convenient_transformer_fn { + protected: + const unsigned_vector m_cycle; + + convenient_rename_fn(const signature & orig_sig, unsigned cycle_len, + const unsigned * permutation_cycle) + : m_cycle(cycle_len, permutation_cycle) { + signature::from_rename(orig_sig, cycle_len, permutation_cycle, + convenient_transformer_fn::get_result_signature()); + } + }; + + class convenient_negation_filter_fn : public intersection_filter_fn { + protected: + unsigned m_joined_col_cnt; + const unsigned_vector m_cols1; + const unsigned_vector m_cols2; + bool m_all_neg_bound; //all columns are bound at least once + bool m_overlap; //one column in negated table is bound multiple times + svector m_bound; + + convenient_negation_filter_fn(const base_object & tgt, const base_object & neg_t, + unsigned joined_col_cnt, const unsigned * t_cols, const unsigned * negated_cols) + : m_joined_col_cnt(joined_col_cnt), m_cols1(joined_col_cnt, t_cols), + m_cols2(joined_col_cnt, negated_cols) { + unsigned neg_sig_size = neg_t.get_signature().size(); + m_overlap = false; + m_bound.resize(neg_sig_size, false); + for(unsigned i=0; i + void make_neg_bindings(T & tgt_neg, const U & src) const { + SASSERT(m_all_neg_bound); + SASSERT(!m_overlap); + for(unsigned i=0; i + bool bindings_match(const T & tgt_neg, const U & src) const { + for(unsigned i=0; i renamer_vector; + + unsigned_vector m_permutation; //this is valid only before m_renamers_initialized becomes true + bool m_renamers_initialized; + renamer_vector m_renamers; + public: + default_permutation_rename_fn(const base_object & o, const unsigned * permutation) + : m_permutation(o.get_signature().size(), permutation), + m_renamers_initialized(false) {} + + ~default_permutation_rename_fn() { + dealloc_ptr_vector_content(m_renamers); + } + + base_object * operator()(const base_object & o) { + const base_object * res = &o; + scoped_rel res_scoped; + if(m_renamers_initialized) { + typename renamer_vector::iterator rit = m_renamers.begin(); + typename renamer_vector::iterator rend = m_renamers.end(); + for(; rit!=rend; ++rit) { + res_scoped = (**rit)(*res); + res = res_scoped.get(); + } + } + else { + SASSERT(m_renamers.empty()); + unsigned_vector cycle; + while(try_remove_cycle_from_permutation(m_permutation, cycle)) { + transformer_fn * renamer = o.get_manager().mk_rename_fn(*res, cycle); + SASSERT(renamer); + m_renamers.push_back(renamer); + cycle.reset(); + + res_scoped = (*renamer)(*res); + res = res_scoped.get(); + } + m_renamers_initialized = true; + } + if(res_scoped) { + SASSERT(res==res_scoped.get()); + //we don't want to delete the last one since we'll be returning it + return res_scoped.release(); + } + else { + SASSERT(res==&o); + return res->clone(); + } + } + + }; + + + }; + + + // ----------------------------------- + // + // relation_base + // + // ----------------------------------- + + class relation_signature; + class relation_plugin; + class relation_base; + + typedef sort * relation_sort; + typedef ptr_vector relation_signature_base0; + + typedef app * relation_element; + typedef app_ref relation_element_ref; + + class relation_fact : public app_ref_vector { + public: + class el_proxy { + friend class relation_fact; + + relation_fact & m_parent; + unsigned m_idx; + + el_proxy(relation_fact & parent, unsigned idx) : m_parent(parent), m_idx(idx) {} + public: + operator relation_element() const { + return m_parent.get(m_idx); + } + relation_element operator->() const { + return m_parent.get(m_idx); + } + relation_element operator=(const relation_element & val) const { + m_parent.set(m_idx, val); + return m_parent.get(m_idx); + } + relation_element operator=(const el_proxy & val) { + m_parent.set(m_idx, val); + return m_parent.get(m_idx); + } + }; + + typedef const relation_element * iterator; + + relation_fact(ast_manager & m) : app_ref_vector(m) {} + relation_fact(ast_manager & m, unsigned sz) : app_ref_vector(m) { resize(sz); } + relation_fact(context & ctx); + + iterator begin() const { return c_ptr(); } + iterator end() const { return c_ptr()+size(); } + + relation_element operator[](unsigned i) const { return get(i); } + el_proxy operator[](unsigned i) { return el_proxy(*this, i); } + }; + + struct relation_traits { + typedef relation_plugin plugin; + typedef relation_base base_object; + typedef relation_element element; + typedef relation_fact fact; + typedef relation_sort sort; + typedef relation_signature_base0 signature_base_base; + typedef relation_signature signature; + }; + + typedef tr_infrastructure relation_infrastructure; + + typedef relation_infrastructure::base_fn base_relation_fn; + typedef relation_infrastructure::join_fn relation_join_fn; + typedef relation_infrastructure::transformer_fn relation_transformer_fn; + typedef relation_infrastructure::union_fn relation_union_fn; + typedef relation_infrastructure::mutator_fn relation_mutator_fn; + typedef relation_infrastructure::intersection_filter_fn relation_intersection_filter_fn; + + typedef relation_infrastructure::convenient_join_fn convenient_relation_join_fn; + typedef relation_infrastructure::convenient_join_project_fn convenient_relation_join_project_fn; + typedef relation_infrastructure::convenient_transformer_fn convenient_relation_transformer_fn; + typedef relation_infrastructure::convenient_project_fn convenient_relation_project_fn; + typedef relation_infrastructure::convenient_rename_fn convenient_relation_rename_fn; + typedef relation_infrastructure::convenient_negation_filter_fn convenient_relation_negation_filter_fn; + typedef relation_infrastructure::identity_transformer_fn identity_relation_transformer_fn; + typedef relation_infrastructure::identity_mutator_fn identity_relation_mutator_fn; + typedef relation_infrastructure::identity_intersection_filter_fn identity_relation_intersection_filter_fn; + typedef relation_infrastructure::default_permutation_rename_fn default_relation_permutation_rename_fn; + + class relation_signature : public relation_infrastructure::signature_base { + public: + bool operator!=(const relation_signature & o) const { + return !(*this==o); + } + + void output(ast_manager & m, std::ostream & out) const; + + struct hash { + unsigned operator()(relation_signature const& s) const { + relation_sort const* sorts = s.c_ptr(); + return string_hash(reinterpret_cast(sorts), sizeof(*sorts)*s.size(), 12); } + }; + + struct eq { + bool operator()(relation_signature const& s1, relation_signature const& s2) const { + return s1 == s2; + } + }; + }; + + class relation_plugin : public relation_infrastructure::plugin_object { + protected: + enum special_relation_type { + ST_ORDINARY, + ST_TABLE_RELATION, + ST_FINITE_PRODUCT_RELATION, + ST_PRODUCT_RELATION, + ST_SIEVE_RELATION + }; + private: + special_relation_type m_special_type; + protected: + relation_plugin(symbol const& name, relation_manager & manager, + special_relation_type special_type = ST_ORDINARY) + : plugin_object(name, manager), + m_special_type(special_type) {} + public: + bool from_table() const { return m_special_type==ST_TABLE_RELATION; } + bool is_finite_product_relation() const { return m_special_type==ST_FINITE_PRODUCT_RELATION; } + bool is_product_relation() const { return m_special_type==ST_PRODUCT_RELATION; } + bool is_sieve_relation() const { return m_special_type==ST_SIEVE_RELATION; } + + /** + \brief If true, the relation can contain only one or zero elements. + + Having this zero allows the finite_product_relation to perform some operations in a simpler way. + (KH: I started implementing finite_product_relation::inner_singleton_union_fn that takes advantage of + it, but it's not finished.) + */ + virtual bool is_singleton_relation() const { return false; } + }; + + class relation_base : public relation_infrastructure::base_ancestor { + protected: + relation_base(relation_plugin & plugin, const relation_signature & s) + : base_ancestor(plugin, s) {} + virtual ~relation_base() {} + public: + virtual relation_base * complement(func_decl* p) const = 0; + + virtual void reset(); + + virtual void display_tuples(func_decl & pred, std::ostream & out) const { + out << "Tuples in " << pred.get_name() << ": \n"; + display(out); + } + + virtual void to_formula(expr_ref& fml) const = 0; + + bool from_table() const { return get_plugin().from_table(); } + }; + + typedef ptr_vector relation_vector; + + // ----------------------------------- + // + // table_base + // + // ----------------------------------- + + class table_signature; + class table_plugin; + class table_base; + + typedef uint64 table_sort; + typedef svector table_signature_base0; + + typedef uint64 table_element; + typedef svector table_fact; + + struct table_traits { + typedef table_plugin plugin; + typedef table_base base_object; + typedef table_element element; + typedef table_fact fact; + typedef table_sort sort; + typedef table_signature_base0 signature_base_base; + typedef table_signature signature; + }; + + typedef tr_infrastructure table_infrastructure; + + typedef table_infrastructure::base_fn base_table_fn; + typedef table_infrastructure::join_fn table_join_fn; + typedef table_infrastructure::transformer_fn table_transformer_fn; + typedef table_infrastructure::union_fn table_union_fn; + typedef table_infrastructure::mutator_fn table_mutator_fn; + typedef table_infrastructure::intersection_filter_fn table_intersection_filter_fn; + + typedef table_infrastructure::convenient_join_fn convenient_table_join_fn; + typedef table_infrastructure::convenient_join_project_fn convenient_table_join_project_fn; + typedef table_infrastructure::convenient_transformer_fn convenient_table_transformer_fn; + typedef table_infrastructure::convenient_project_fn convenient_table_project_fn; + typedef table_infrastructure::convenient_rename_fn convenient_table_rename_fn; + typedef table_infrastructure::convenient_negation_filter_fn convenient_table_negation_filter_fn; + typedef table_infrastructure::identity_transformer_fn identity_table_transformer_fn; + typedef table_infrastructure::identity_mutator_fn identity_table_mutator_fn; + typedef table_infrastructure::identity_intersection_filter_fn identity_table_intersection_filter_fn; + typedef table_infrastructure::default_permutation_rename_fn default_table_permutation_rename_fn; + + class table_row_mutator_fn { + public: + /** + \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 + returns true, the modification will appear in the table; otherwise the row will be deleted. + + It is possible that one call to the function stands for multiple table rows that share + the same functional column values. + */ + virtual bool operator()(table_element * func_columns) = 0; + }; + + class table_row_pair_reduce_fn { + public: + /** + \brief The function is called for pair of table rows that became duplicit due to projection. + The values that are in the first array after return from the function will be used for the + resulting row. + + It is assumed that the function is idempotent: when the two functional sub-tuples are equal, + the result is assumed to be equal to them as well, so the function may not be evaluated for them. + */ + virtual void operator()(table_element * func_columns, const table_element * merged_func_columns) = 0; + }; + + + class table_signature : public table_infrastructure::signature_base { + public: + struct hash { + unsigned operator()(table_signature const& s) const { + table_sort const* sorts = s.c_ptr(); + return string_hash(reinterpret_cast(sorts), sizeof(*sorts)*s.size(), 12); } + }; + + struct eq { + bool operator()(table_signature const& s1, table_signature const& s2) const { + return s1 == s2; + } + }; + private: + unsigned m_functional_columns; + public: + table_signature() : m_functional_columns(0) {} + + void swap(table_signature & s) { + signature_base::swap(s); + std::swap(m_functional_columns, s.m_functional_columns); + } + + /** + \brief The returned value is the number of last columns that are functional. + + The uniqueness is enforced on non-functional columns. When projection causes two + facts to have equal non-functional parts, it is not defined which one of them is retained. + */ + unsigned functional_columns() const { return m_functional_columns; } + void set_functional_columns(unsigned val) { SASSERT(size()>=val); m_functional_columns = val; } + + /** + \brief Return index of the first functional column, or the size of the signature if there + are no functional columns. + */ + unsigned first_functional() const { return size()-m_functional_columns; } + + bool operator==(const table_signature & o) const { + return signature_base::operator==(o) && m_functional_columns==o.m_functional_columns; + } + bool operator!=(const table_signature & o) const { + return !(*this==o); + } + + /** + \brief return true iof the two signatures are equal when we ignore which columns are functional. + */ + bool equal_up_to_fn_mark(const table_signature & o) const { + return signature_base::operator==(o); + } + + + /** + \brief Into \c result assign signature of result of join of relations with signatures \c s1 + and \c s2. The result is + + (non-functional of s1)(non-functional of s2)(functional of s1)(functional of s2) + */ + static void from_join(const table_signature & s1, const table_signature & s2, unsigned col_cnt, + const unsigned * cols1, const unsigned * cols2, table_signature & result); + + static void from_join_project(const table_signature & s1, const table_signature & s2, + unsigned joined_col_cnt, const unsigned * cols1, const unsigned * cols2, unsigned removed_col_cnt, + const unsigned * removed_cols, table_signature & result); + + + /** + \brief Into \c result assign signature projected from \c src. + + The array of removed columns must be sorted in ascending order. + + If we remove at least one non-functional column, all the columns in the result are non-functional. + */ + static void from_project(const table_signature & src, unsigned col_cnt, + const unsigned * removed_cols, table_signature & result); + + static void from_project_with_reduce(const table_signature & src, unsigned col_cnt, + const unsigned * removed_cols, table_signature & result); + + /** + \brief Into \c result assign signature \c src with reordered columns. + + Permutations between functional and nonfunctional columns are not allowed. + */ + static void from_rename(const table_signature & src, unsigned cycle_len, + const unsigned * permutation_cycle, table_signature & result) { + signature_base::from_rename(src, cycle_len, permutation_cycle, result); + result.set_functional_columns(src.functional_columns()); +#if Z3DEBUG + unsigned first_src_fun = src.size()-src.functional_columns(); + bool in_func = permutation_cycle[0]>=first_src_fun; + for(unsigned i=1;i=first_src_fun)); + } +#endif + } + + /** + \brief Into \c result assign signature \c src with reordered columns. + + Permutations mixing functional and nonfunctional columns are not allowed. + */ + static void from_permutation_rename(const table_signature & src, + const unsigned * permutation, table_signature & result) { + signature_base::from_permutation_rename(src, permutation, result); + result.set_functional_columns(src.functional_columns()); +#if Z3DEBUG + unsigned sz = src.size(); + unsigned first_src_fun = sz-src.functional_columns(); + for(unsigned i=first_src_fun;i=first_src_fun); + } +#endif + } + + }; + + class table_plugin : public table_infrastructure::plugin_object { + friend class relation_manager; + protected: + table_plugin(symbol const& n, relation_manager & manager) : plugin_object(n, manager) {} + public: + + virtual bool can_handle_signature(const table_signature & s) { return s.functional_columns()==0; } + + protected: + /** + If the returned value is non-zero, the returned object must take ownership of \c mapper. + Otherwise \c mapper must remain unmodified. + */ + virtual table_mutator_fn * mk_map_fn(const table_base & t, table_row_mutator_fn * mapper) { return 0; } + + /** + If the returned value is non-zero, the returned object must take ownership of \c reducer. + Otherwise \c reducer must remain unmodified. + */ + virtual table_transformer_fn * mk_project_with_reduce_fn(const table_base & t, unsigned col_cnt, + const unsigned * removed_cols, table_row_pair_reduce_fn * reducer) { return 0; } + + }; + + class table_base : public table_infrastructure::base_ancestor { + protected: + table_base(table_plugin & plugin, const table_signature & s) + : base_ancestor(plugin, s) {} + virtual ~table_base() {} + public: + virtual table_base * clone() const; + virtual table_base * complement(func_decl* p, const table_element * func_columns = 0) const; + virtual bool empty() const; + + /** + \brief Return true if table contains fact that corresponds to \c f in all non-functional + columns. + */ + virtual bool contains_fact(const table_fact & f) const; + + /** + \brief If \c f (i.e. its non-functional part) is not present in the table, + add it and return true. Otherwise update \c f, so that the values of functional + columns correspond to the ones present in the table. + */ + virtual bool suggest_fact(table_fact & f); + + /** + \brief If \c f (i.e. its non-functional part) is not present in the table, + return false. Otherwise update \c f, so that the values of functional + columns correspond to the ones present in the table and return true. + */ + virtual bool fetch_fact(table_fact & f) const; + + /** + \brief Ensure fact \c f is present in the table (including the values of its functional columns). + */ + virtual void ensure_fact(const table_fact & f); + + virtual void remove_fact(const table_fact & fact) { + SASSERT(fact.size() == get_signature().size()); + remove_fact(fact.c_ptr()); } + + virtual void remove_fact(table_element const* fact) = 0; + virtual void remove_facts(unsigned fact_cnt, const table_fact * facts); + virtual void remove_facts(unsigned fact_cnt, const table_element * facts); + virtual void reset(); + + class row_interface; + + virtual void display(std::ostream & out) const; + + /** + \brief Convert table to a formula that encodes the table. + The columns correspond to bound variables indexed as + 0, .., sig.size()-1 + */ + virtual void to_formula(relation_signature const& sig, expr_ref& fml) const; + + protected: + + + class iterator_core { + unsigned m_ref_cnt; + public: + iterator_core() : m_ref_cnt(0) {} + virtual ~iterator_core() {} + + void inc_ref() { m_ref_cnt++; } + void dec_ref() { + SASSERT(m_ref_cnt>0); + m_ref_cnt--; + if(m_ref_cnt==0) { + dealloc(this); + } + } + + virtual bool is_finished() const = 0; + + virtual row_interface & operator*() = 0; + virtual void operator++() = 0; + virtual bool operator==(const iterator_core & it) { + //we worry about the equality operator only because of checking + //the equality with the end() iterator + if(is_finished() && it.is_finished()) { + return true; + } + return false; + } + private: + //private and undefined copy constructor and assignment operator + iterator_core(const iterator_core &); + iterator_core & operator=(const iterator_core &); + }; + + struct row_iterator_core { + unsigned m_ref_cnt; + public: + row_iterator_core() : m_ref_cnt(0) {} + virtual ~row_iterator_core() {} + + void inc_ref() { m_ref_cnt++; } + void dec_ref() { + SASSERT(m_ref_cnt>0); + m_ref_cnt--; + if(m_ref_cnt==0) { + dealloc(this); + } + } + + virtual bool is_finished() const = 0; + + virtual table_element operator*() = 0; + virtual void operator++() = 0; + virtual bool operator==(const row_iterator_core & it) { + //we worry about the equality operator only because of checking + //the equality with the end() iterator + if(is_finished() && it.is_finished()) { + return true; + } + return false; + } + private: + //private and undefined copy constructor and assignment operator + row_iterator_core(const row_iterator_core &); + row_iterator_core & operator=(const row_iterator_core &); + }; + + public: + class iterator { + friend class table_base; + + ref m_core; + + iterator(iterator_core * core) : m_core(core) {} + public: + /** + \brief Return reference to a row_interface object for the current row. + + The reference is valid only until the \c operator++() is called or + until the iterator is invalidated. + */ + row_interface & operator*() + { return *(*m_core); } + row_interface * operator->() + { return &(*(*m_core)); } + iterator & operator++() + { ++(*m_core); return *this; } + bool operator==(const iterator & it) + { return (*m_core)==(*it.m_core); } + bool operator!=(const iterator & it) + { return !operator==(it); } + }; + + class row_iterator { + friend class table_base; + friend class row_interface; + + ref m_core; + + row_iterator(row_iterator_core * core) : m_core(core) {} + public: + table_element operator*() + { return *(*m_core); } + row_iterator & operator++() + { ++(*m_core); return *this; } + bool operator==(const row_iterator & it) + { return (*m_core)==(*it.m_core); } + bool operator!=(const row_iterator & it) + { return !operator==(it); } + }; + + virtual iterator begin() const = 0; + virtual iterator end() const = 0; + + class row_interface { + class fact_row_iterator; + + const table_base & m_parent_table; + public: + typedef row_iterator iterator; + typedef row_iterator const_iterator; + + row_interface(const table_base & parent_table) : m_parent_table(parent_table) {} + virtual ~row_interface() {} + + virtual table_element operator[](unsigned col) const = 0; + + unsigned size() const { return m_parent_table.get_signature().size(); } + virtual void get_fact(table_fact & result) const; + virtual row_iterator begin() const; + virtual row_iterator end() const; + virtual void display(std::ostream & out) const; + }; + + protected: + + class caching_row_interface : public row_interface { + mutable table_fact m_current; + + bool populated() const { return !m_current.empty(); } + void ensure_populated() const { + if(!populated()) { + get_fact(m_current); + } + } + public: + caching_row_interface(const table_base & parent) : row_interface(parent) {} + + virtual void get_fact(table_fact & result) const = 0; + + virtual table_element operator[](unsigned col) const { + ensure_populated(); + return m_current[col]; + } + /** + \brief Resets the cache of the row object. + + Must be called when the row object begins to represent a different row in the table. + */ + void reset() { m_current.reset(); } + }; + + //This function is here to create iterator instances in classes that derive from table_base. + //We do not want to make the constructor of the iterator class public, and being private, the + //inheritor classes cannot see it directly. + static iterator mk_iterator(iterator_core * core) { + return iterator(core); + } + }; + + +}; + +#endif /* _DL_BASE_H_ */ + diff --git a/lib/dl_bmc_engine.cpp b/lib/dl_bmc_engine.cpp new file mode 100644 index 000000000..f0f91583a --- /dev/null +++ b/lib/dl_bmc_engine.cpp @@ -0,0 +1,691 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + dl_bmc_engine.cpp + +Abstract: + + BMC engine for fixedpoint solver. + +Author: + + Nikolaj Bjorner (nbjorner) 2012-9-20 + +Revision History: + +--*/ + +#include "dl_context.h" +#include "dl_rule_transformer.h" +#include "dl_bmc_engine.h" +#include "dl_mk_slice.h" +#include "smt_solver.h" +#include "datatype_decl_plugin.h" +#include "dl_mk_rule_inliner.h" +#include "dl_decl_plugin.h" +#include "bool_rewriter.h" +#include "model_smt2_pp.h" + +namespace datalog { + bmc::bmc(context& ctx): + m_ctx(ctx), + m(ctx.get_manager()), + m_cancel(false), + m_solver(m, m_fparams), + m_pinned(m), + m_rules(ctx), + m_query_pred(m), + m_answer(m), + m_path_sort(m) { + m_fparams.m_relevancy_lvl = 0; + m_fparams.m_model = true; + m_fparams.m_model_compact = true; + } + + bmc::~bmc() {} + + lbool bmc::query(expr* query) { + + m_solver.reset(); + m_pinned.reset(); + m_pred2sort.reset(); + m_pred2newpred.reset(); + m_pred2args.reset(); + m_answer = 0; + + m_ctx.ensure_opened(); + m_rules.reset(); + m_ctx.get_rmanager().reset_relations(); + datalog::rule_manager& rule_manager = m_ctx.get_rule_manager(); + datalog::rule_set old_rules(m_ctx.get_rules()); + datalog::rule_ref_vector query_rules(rule_manager); + datalog::rule_ref query_rule(rule_manager); + rule_manager.mk_query(query, m_query_pred, query_rules, query_rule); + m_ctx.add_rules(query_rules); + expr_ref bg_assertion = m_ctx.get_background_assertion(); + + + model_converter_ref mc = datalog::mk_skip_model_converter(); + m_pc = datalog::mk_skip_proof_converter(); + m_ctx.set_output_predicate(m_query_pred); + m_ctx.apply_default_transformation(mc, m_pc); + + if (m_ctx.get_params().get_bool(":slice", true)) { + datalog::rule_transformer transformer(m_ctx); + datalog::mk_slice* slice = alloc(datalog::mk_slice, m_ctx); + transformer.register_plugin(slice); + m_ctx.transform_rules(transformer, mc, m_pc); + m_query_pred = slice->get_predicate(m_query_pred.get()); + m_ctx.set_output_predicate(m_query_pred); + } + m_rules.add_rules(m_ctx.get_rules()); + m_rules.close(); + m_ctx.reopen(); + m_ctx.replace_rules(old_rules); + + checkpoint(); + + IF_VERBOSE(2, m_ctx.display_rules(verbose_stream());); + + if (m_rules.get_num_rules() == 0) { + return l_false; + } + + if (false && is_linear()) { + return check_linear(); + } + else { + IF_VERBOSE(1, verbose_stream() << "non-linear BMC is not supported\n";); + // return l_undef; + return check_nonlinear(); + } + } + + bool bmc::is_linear() const { + unsigned sz = m_rules.get_num_rules(); + for (unsigned i = 0; i < sz; ++i) { + if (m_rules.get_rule(i)->get_uninterpreted_tail_size() > 1) { + return false; + } + } + return true; + } + + void bmc::get_model_linear(unsigned level) { + rule_manager& rm = m_ctx.get_rule_manager(); + expr_ref level_query = mk_level_predicate(m_query_pred, level); + model_ref md; + proof_ref pr(m); + rule_unifier unifier(rm, m_ctx, m); + m_solver.get_model(md); + func_decl* pred = m_query_pred; + SASSERT(m.is_true(md->get_const_interp(to_app(level_query)->get_decl()))); + dl_decl_util util(m); + + TRACE("dl", model_smt2_pp(tout, m, *md, 0);); + + rule_ref r0(rm), r1(rm), r2(rm); + while (true) { + TRACE("dl", tout << "Predicate: " << pred->get_name() << "\n";); + expr_ref_vector sub(m); + rule_vector const& rls = m_rules.get_predicate_rules(pred); + rule* r = 0; + unsigned i = 0; + for (; i < rls.size(); ++i) { + expr_ref rule_i = mk_level_rule(pred, i, level); + TRACE("dl", rls[i]->display(m_ctx, tout << "Checking rule " << mk_pp(rule_i, m) << " ");); + if (m.is_true(md->get_const_interp(to_app(rule_i)->get_decl()))) { + r = rls[i]; + break; + } + } + SASSERT(r); + mk_rule_vars(*r, level, i, sub); + // we have rule, we have variable names of rule. + + // extract values for the variables in the rule. + for (unsigned j = 0; j < sub.size(); ++j) { + expr* vl = md->get_const_interp(to_app(sub[j].get())->get_decl()); + if (vl) { + // vl can be 0 if the interpretation does not assign a value to it. + sub[j] = vl; + } + else { + sub[j] = m.mk_var(j, m.get_sort(sub[j].get())); + } + } + svector > positions; + vector substs; + expr_ref fml(m), concl(m); + + r->to_formula(fml); + r2 = r; + rm.substitute(r2, sub.size(), sub.c_ptr()); + if (r0) { + VERIFY(unifier.unify_rules(*r0.get(), 0, *r2.get())); + expr_ref_vector sub1 = unifier.get_rule_subst(*r0.get(), true); + expr_ref_vector sub2 = unifier.get_rule_subst(*r2.get(), false); + apply_subst(sub, sub2); + unifier.apply(*r0.get(), 0, *r2.get(), r1); + r1->to_formula(concl); + scoped_coarse_proof _sp(m); + + proof* p = m.mk_asserted(fml); + proof* premises[2] = { pr, p }; + + positions.push_back(std::make_pair(0, 1)); + + substs.push_back(sub1); + substs.push_back(sub); + pr = util.mk_hyper_resolve(2, premises, concl, positions, substs); + r0 = r1; + } + else { + r2->to_formula(concl); + scoped_coarse_proof _sp(m); + proof* p = m.mk_asserted(fml); + if (sub.empty()) { + pr = p; + } + else { + substs.push_back(sub); + pr = util.mk_hyper_resolve(1, &p, concl, positions, substs); + } + r0 = r2; + } + + if (level == 0) { + SASSERT(r->get_uninterpreted_tail_size() == 0); + break; + } + --level; + SASSERT(r->get_uninterpreted_tail_size() == 1); + pred = r->get_decl(0); + } + scoped_coarse_proof _sp(m); + apply(m, m_pc.get(), pr); + m_answer = pr; + } + + + lbool bmc::check_linear() { + for (unsigned i = 0; ; ++i) { + IF_VERBOSE(1, verbose_stream() << "level: " << i << "\n";); + compile_linear(i); + lbool res = check_linear(i); + if (res == l_undef) { + return res; + } + if (res == l_true) { + get_model_linear(i); + return res; + } + } + } + + lbool bmc::check_linear(unsigned level) { + expr_ref level_query = mk_level_predicate(m_query_pred, level); + expr* q = level_query.get(); + return m_solver.check(1, &q); + } + + void bmc::assert_expr(expr* e) { + TRACE("dl", tout << mk_pp(e, m) << "\n";); + m_solver.assert_expr(e); + } + + expr_ref bmc::mk_level_predicate(func_decl* p, unsigned level) { + return mk_level_predicate(p->get_name(), level); + } + + expr_ref bmc::mk_level_predicate(symbol const& name, unsigned level) { + std::stringstream _name; + _name << name << "#" << level; + symbol nm(_name.str().c_str()); + return expr_ref(m.mk_const(nm, m.mk_bool_sort()), m); + } + + expr_ref bmc::mk_level_arg(func_decl* pred, unsigned idx, unsigned level) { + SASSERT(idx < pred->get_arity()); + std::stringstream _name; + _name << pred->get_name() << "#" << level << "_" << idx; + symbol nm(_name.str().c_str()); + return expr_ref(m.mk_const(nm, pred->get_domain(idx)), m); + } + + expr_ref bmc::mk_level_var(func_decl* pred, sort* s, unsigned rule_id, unsigned idx, unsigned level) { + SASSERT(idx < pred->get_arity()); + std::stringstream _name; + _name << pred->get_name() << "#" << level << "_" << rule_id << "_" << idx; + symbol nm(_name.str().c_str()); + return expr_ref(m.mk_const(nm, s), m); + } + + expr_ref bmc::mk_level_rule(func_decl* p, unsigned rule_idx, unsigned level) { + std::stringstream _name; + _name << "rule:" << p->get_name() << "#" << level << "_" << rule_idx; + symbol nm(_name.str().c_str()); + return expr_ref(m.mk_const(nm, m.mk_bool_sort()), m); + } + + void bmc::mk_rule_vars(rule& r, unsigned level, unsigned rule_id, expr_ref_vector& sub) { + sort_ref_vector sorts(m); + r.get_vars(sorts); + // populate substitution of bound variables. + sub.reset(); + sub.resize(sorts.size()); + + for (unsigned k = 0; k < r.get_decl()->get_arity(); ++k) { + expr* arg = r.get_head()->get_arg(k); + if (is_var(arg)) { + unsigned idx = to_var(arg)->get_idx(); + if (!sub[idx].get()) { + sub[idx] = mk_level_arg(r.get_decl(), k, level); + } + } + } + for (unsigned j = 0; j < r.get_uninterpreted_tail_size(); ++j) { + SASSERT(level > 0); + func_decl* q = r.get_decl(j); + for (unsigned k = 0; k < q->get_arity(); ++k) { + expr* arg = r.get_tail(j)->get_arg(k); + if (is_var(arg)) { + unsigned idx = to_var(arg)->get_idx(); + if (!sub[idx].get()) { + sub[idx] = mk_level_arg(q, k, level-1); + } + } + } + } + for (unsigned j = 0, idx = 0; j < sorts.size(); ++j) { + if (sorts[j].get() && !sub[j].get()) { + sub[j] = mk_level_var(r.get_decl(), sorts[j].get(), rule_id, idx++, level); + } + } + } + + void bmc::compile_linear(unsigned level) { + rule_set::decl2rules::iterator it = m_rules.begin_grouped_rules(); + rule_set::decl2rules::iterator end = m_rules.end_grouped_rules(); + for (; it != end; ++it) { + func_decl* p = it->m_key; + rule_vector const& rls = *it->m_value; + + // Assert: p_level => r1_level \/ r2_level \/ r3_level \/ ... + // Assert: r_i_level => body of rule i for level + equalities for head of rule i + + expr_ref level_pred = mk_level_predicate(p, level); + expr_ref_vector rules(m), sub(m), conjs(m); + expr_ref rule_body(m), tmp(m); + for (unsigned i = 0; i < rls.size(); ++i) { + sub.reset(); + conjs.reset(); + rule& r = *rls[i]; + expr_ref rule_i = mk_level_rule(p, i, level); + rules.push_back(rule_i); + if (level == 0 && r.get_uninterpreted_tail_size() > 0) { + assert_expr(m.mk_not(rule_i)); + continue; + } + + mk_rule_vars(r, level, i, sub); + + // apply substitution to body. + var_subst vs(m, false); + for (unsigned k = 0; k < p->get_arity(); ++k) { + vs(r.get_head()->get_arg(k), sub.size(), sub.c_ptr(), tmp); + conjs.push_back(m.mk_eq(tmp, mk_level_arg(p, k, level))); + } + for (unsigned j = 0; j < r.get_uninterpreted_tail_size(); ++j) { + SASSERT(level > 0); + func_decl* q = r.get_decl(j); + for (unsigned k = 0; k < q->get_arity(); ++k) { + vs(r.get_tail(j)->get_arg(k), sub.size(), sub.c_ptr(), tmp); + conjs.push_back(m.mk_eq(tmp, mk_level_arg(q, k, level-1))); + } + conjs.push_back(mk_level_predicate(q, level-1)); + } + for (unsigned j = r.get_uninterpreted_tail_size(); j < r.get_tail_size(); ++j) { + vs(r.get_tail(j), sub.size(), sub.c_ptr(), tmp); + conjs.push_back(tmp); + } + bool_rewriter(m).mk_and(conjs.size(), conjs.c_ptr(), rule_body); + + assert_expr(m.mk_implies(rule_i, rule_body)); + } + bool_rewriter(m).mk_or(rules.size(), rules.c_ptr(), tmp); + assert_expr(m.mk_implies(level_pred, tmp)); + } + } + + + lbool bmc::check_nonlinear() { + declare_datatypes(); + compile_nonlinear(); + return check_query(); + } + + func_decl_ref bmc::mk_predicate(func_decl* pred) { + std::stringstream _name; + _name << pred->get_name() << "#"; + symbol nm(_name.str().c_str()); + sort* pred_trace_sort = m_pred2sort.find(pred); + return func_decl_ref(m.mk_func_decl(nm, pred_trace_sort, m_path_sort, m.mk_bool_sort()), m); + } + + func_decl_ref bmc::mk_rule(func_decl* p, unsigned rule_idx) { + std::stringstream _name; + _name << "rule:" << p->get_name() << "#" << rule_idx; + symbol nm(_name.str().c_str()); + sort* pred_trace_sort = m_pred2sort.find(p); + return func_decl_ref(m.mk_func_decl(nm, pred_trace_sort, m_path_sort, m.mk_bool_sort()), m); + } + + expr_ref bmc::mk_var_nonlinear(func_decl* pred, sort*s, unsigned idx, expr* path_arg, expr* trace_arg) { + std::stringstream _name; + _name << pred->get_name() << "#V_" << idx; + symbol nm(_name.str().c_str()); + func_decl_ref fn(m); + fn = m.mk_func_decl(nm, m_pred2sort.find(pred), m_path_sort, s); + return expr_ref(m.mk_app(fn, trace_arg, path_arg), m); + } + + expr_ref bmc::mk_arg_nonlinear(func_decl* pred, unsigned idx, expr* path_arg, expr* trace_arg) { + SASSERT(idx < pred->get_arity()); + std::stringstream _name; + _name << pred->get_name() << "#X_" << idx; + symbol nm(_name.str().c_str()); + func_decl_ref fn(m); + fn = m.mk_func_decl(nm, m_pred2sort.find(pred), m_path_sort, pred->get_domain(idx)); + return expr_ref(m.mk_app(fn, trace_arg, path_arg), m); + } + + /** + \brief compile Horn rule into co-Horn implication. + forall args . R(path_var, rule_i(trace_vars)) => Body[X(path_var, rule_i(trace_vars)), Y(S_j(path_var), trace_vars_j)] + */ + void bmc::compile_nonlinear() { + datatype_util dtu(m); + + rule_set::decl2rules::iterator it = m_rules.begin_grouped_rules(); + rule_set::decl2rules::iterator end = m_rules.end_grouped_rules(); + for (; it != end; ++it) { + func_decl* p = it->m_key; + rule_vector const& rls = *it->m_value; + + // Assert: p_level => r1_level \/ r2_level \/ r3_level \/ ... + // Assert: r_i_level => body of rule i for level + equalities for head of rule i + + expr_ref rule_body(m), tmp(m), pred(m), trace_arg(m); + var_ref path_var(m), trace_var(m); + expr_ref_vector rules(m), sub(m), conjs(m), vars(m), patterns(m); + sort* pred_sort = m_pred2sort.find(p); + path_var = m.mk_var(0, m_path_sort); + trace_var = m.mk_var(1, pred_sort); + sort* sorts[2] = { pred_sort, m_path_sort }; + ptr_vector const& cnstrs = *dtu.get_datatype_constructors(pred_sort); + ptr_vector const& succs = *dtu.get_datatype_constructors(m_path_sort); + SASSERT(cnstrs.size() == rls.size()); + pred = m.mk_app(mk_predicate(p), trace_var.get(), path_var.get()); + for (unsigned i = 0; i < rls.size(); ++i) { + sub.reset(); + conjs.reset(); + vars.reset(); + rule& r = *rls[i]; + func_decl_ref rule_pred_i = mk_rule(p, i); + + // Create cnstr_rule_i(Vars) + func_decl* cnstr = cnstrs[i]; + rules.push_back(m.mk_app(rule_pred_i, trace_var.get(), path_var.get())); + unsigned arity = cnstr->get_arity(); + for (unsigned j = 0; j < arity; ++j) { + vars.push_back(m.mk_var(arity-j,cnstr->get_domain(j))); + } + trace_arg = m.mk_app(cnstr, vars.size(), vars.c_ptr()); + + sort_ref_vector sorts(m); + r.get_vars(sorts); + // populate substitution of bound variables. + sub.resize(sorts.size()); + for (unsigned k = 0; k < r.get_decl()->get_arity(); ++k) { + expr* arg = r.get_head()->get_arg(k); + if (is_var(arg)) { + unsigned idx = to_var(arg)->get_idx(); + if (!sub[idx].get()) { + sub[idx] = mk_arg_nonlinear(p, k, path_var, trace_arg); + } + } + } + for (unsigned j = 0; j < r.get_uninterpreted_tail_size(); ++j) { + func_decl* q = r.get_decl(j); + expr_ref path_arg(m); + if (j == 0) { + path_arg = path_var.get(); + } + else { + path_arg = m.mk_app(succs[j-1], path_var.get()); + } + for (unsigned k = 0; k < q->get_arity(); ++k) { + expr* arg = r.get_tail(j)->get_arg(k); + if (is_var(arg)) { + unsigned idx = to_var(arg)->get_idx(); + if (!sub[idx].get()) { + sub[idx] = mk_arg_nonlinear(q, k, path_arg, vars[j].get()); + } + } + } + } + for (unsigned j = 0, idx = 0; j < sorts.size(); ++j) { + if (sorts[j].get() && !sub[j].get()) { + sub[j] = mk_var_nonlinear(r.get_decl(), sorts[j].get(), idx++, path_var, trace_arg); + } + } + + // apply substitution to body. + var_subst vs(m, false); + for (unsigned k = 0; k < p->get_arity(); ++k) { + vs(r.get_head()->get_arg(k), sub.size(), sub.c_ptr(), tmp); + expr_ref arg = mk_arg_nonlinear(p, k, path_var, trace_arg); + conjs.push_back(m.mk_eq(tmp, arg)); + } + for (unsigned j = 0; j < r.get_uninterpreted_tail_size(); ++j) { + expr_ref path_arg(m); + if (j == 0) { + path_arg = path_var.get(); + } + else { + path_arg = m.mk_app(succs[j-1], path_var.get()); + } + func_decl* q = r.get_decl(j); + for (unsigned k = 0; k < q->get_arity(); ++k) { + vs(r.get_tail(j)->get_arg(k), sub.size(), sub.c_ptr(), tmp); + expr_ref arg = mk_arg_nonlinear(q, k, path_arg, vars[j].get()); + conjs.push_back(m.mk_eq(tmp, arg)); + } + func_decl_ref q_pred = mk_predicate(q); + conjs.push_back(m.mk_app(q_pred, vars[j].get(), path_arg)); + } + for (unsigned j = r.get_uninterpreted_tail_size(); j < r.get_tail_size(); ++j) { + vs(r.get_tail(j), sub.size(), sub.c_ptr(), tmp); + conjs.push_back(tmp); + } + bool_rewriter(m).mk_and(conjs.size(), conjs.c_ptr(), rule_body); + expr* rule_pred = m.mk_app(rule_pred_i, trace_arg, path_var.get()); + ptr_vector q_sorts; + vector names; + for (unsigned i = 0; i < vars.size(); ++i) { + q_sorts.push_back(m.get_sort(vars[i].get())); + names.push_back(symbol(i+1)); + } + vars.push_back(path_var); + q_sorts.push_back(m.get_sort(path_var)); + names.push_back(symbol("Path")); + SASSERT(names.size() == q_sorts.size()); + SASSERT(vars.size() == names.size()); + symbol qid = r.name(), skid; + + patterns.reset(); + patterns.push_back(m.mk_pattern(to_app(rule_pred))); + expr_ref fml(m); + fml = m.mk_implies(rule_pred, rule_body); + fml = m.mk_forall(vars.size(), q_sorts.c_ptr(), names.c_ptr(), fml, 1, qid, skid, 1, patterns.c_ptr()); + std::cout << mk_pp(fml, m) << "\n"; + assert_expr(fml); + } + bool_rewriter(m).mk_or(rules.size(), rules.c_ptr(), tmp); + symbol names[2] = { symbol("Trace"), symbol("Path") }; + symbol qid = p->get_name(), skid; + patterns.reset(); + patterns.push_back(m.mk_pattern(to_app(pred))); + expr_ref fml(m); + fml = m.mk_implies(pred, tmp); + fml = m.mk_forall(2, sorts, names, fml, 1, qid, skid, 1, patterns.c_ptr()); + assert_expr(fml); + } + } + + void bmc::declare_datatypes() { + rule_set::decl2rules::iterator it = m_rules.begin_grouped_rules(); + rule_set::decl2rules::iterator end = m_rules.end_grouped_rules(); + datatype_util dtu(m); + ptr_vector dts; + + obj_map pred_idx; + for (unsigned i = 0; it != end; ++it, ++i) { + pred_idx.insert(it->m_key, i); + } + + it = m_rules.begin_grouped_rules(); + for (; it != end; ++it) { + rule_vector const& rls = *it->m_value; + func_decl* pred = it->m_key; + ptr_vector cnstrs; + for (unsigned i = 0; i < rls.size(); ++i) { + rule* r = rls[i]; + ptr_vector accs; + for (unsigned j = 0; j < r->get_uninterpreted_tail_size(); ++j) { + func_decl* q = r->get_decl(j); + unsigned idx = pred_idx.find(q); + std::stringstream _name; + _name << pred->get_name() << "_" << q->get_name() << j; + symbol name(_name.str().c_str()); + type_ref tr(idx); + accs.push_back(mk_accessor_decl(name, tr)); + } + std::stringstream _name; + _name << pred->get_name() << "_" << i; + symbol name(_name.str().c_str()); + _name << "?"; + symbol is_name(_name.str().c_str()); + cnstrs.push_back(mk_constructor_decl(name, is_name, accs.size(), accs.c_ptr())); + } + dts.push_back(mk_datatype_decl(pred->get_name(), cnstrs.size(), cnstrs.c_ptr())); + } + + + sort_ref_vector new_sorts(m); + family_id dfid = m.get_family_id("datatype"); + datatype_decl_plugin* dtp = static_cast(m.get_plugin(dfid)); + VERIFY (dtp->mk_datatypes(dts.size(), dts.c_ptr(), new_sorts)); + + it = m_rules.begin_grouped_rules(); + for (unsigned i = 0; it != end; ++it, ++i) { + std::cout << "insert: " << it->m_key->get_name() << "\n"; + m_pred2sort.insert(it->m_key, new_sorts[i].get()); + m_pinned.push_back(new_sorts[i].get()); + } + if (new_sorts.size() > 0) { + TRACE("dl", dtu.display_datatype(new_sorts[0].get(), tout);); + } + del_datatype_decls(dts.size(), dts.c_ptr()); + + // declare path data-type. + { + new_sorts.reset(); + dts.reset(); + ptr_vector cnstrs; + unsigned max_arity = 0; + rule_set::iterator it = m_rules.begin(); + rule_set::iterator end = m_rules.end(); + for (; it != end; ++it) { + rule* r = *it; + unsigned sz = r->get_uninterpreted_tail_size(); + max_arity = std::max(sz, max_arity); + } + cnstrs.push_back(mk_constructor_decl(symbol("Z#"), symbol("Z#?"), 0, 0)); + + for (unsigned i = 0; i + 1 < max_arity; ++i) { + std::stringstream _name; + _name << "succ#" << i; + symbol name(_name.str().c_str()); + _name << "?"; + symbol is_name(_name.str().c_str()); + std::stringstream _name2; + _name2 << "get_succ#" << i; + symbol acc_name(_name2.str().c_str()); + ptr_vector accs; + type_ref tr(0); + accs.push_back(mk_accessor_decl(name, tr)); + cnstrs.push_back(mk_constructor_decl(name, is_name, accs.size(), accs.c_ptr())); + } + dts.push_back(mk_datatype_decl(symbol("path"), cnstrs.size(), cnstrs.c_ptr())); + VERIFY (dtp->mk_datatypes(dts.size(), dts.c_ptr(), new_sorts)); + m_path_sort = new_sorts[0].get(); + } + } + + // instantiation of algebraic data-types takes care of the rest. + lbool bmc::check_query() { + sort* trace_sort = m_pred2sort.find(m_query_pred); + func_decl_ref q = mk_predicate(m_query_pred); + assert_expr(m.mk_app(q, m.mk_const(symbol("trace"), trace_sort), m.mk_const(symbol("path"),m_path_sort))); + lbool is_sat = m_solver.check(); + if (is_sat == l_undef) { + model_ref md; + proof_ref pr(m); + m_solver.get_model(md); + IF_VERBOSE(2, model_smt2_pp(verbose_stream(), m, *md, 0);); + + } + return is_sat; + } + + void bmc::checkpoint() { + if (m_cancel) { + throw default_exception("bmc canceled"); + } + } + + void bmc::cancel() { + m_cancel = true; + m_solver.cancel(); + } + + void bmc::cleanup() { + m_cancel = false; + m_solver.reset(); + } + + void bmc::display_certificate(std::ostream& out) const { + + } + + void bmc::collect_statistics(statistics& st) const { + + } + + expr_ref bmc::get_answer() { + return m_answer; + } + + void bmc::collect_params(param_descrs& p) { + + } + + void bmc::updt_params() { + + } + +}; diff --git a/lib/dl_bmc_engine.h b/lib/dl_bmc_engine.h new file mode 100644 index 000000000..c5f75ea21 --- /dev/null +++ b/lib/dl_bmc_engine.h @@ -0,0 +1,122 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + dl_bmc_engine.h + +Abstract: + + BMC engine for fixedpoint solver. + +Author: + + Nikolaj Bjorner (nbjorner) 2012-9-20 + +Revision History: + +--*/ + +#ifndef _DL_BMC_ENGINE_H_ +#define _DL_BMC_ENGINE_H_ + +#include "params.h" +#include "statistics.h" +#include "smt_solver.h" + + +namespace datalog { + class context; + + class bmc { + context& m_ctx; + ast_manager& m; + front_end_params m_fparams; + smt::solver m_solver; + obj_map m_pred2sort; + obj_map m_pred2newpred; + obj_map > m_pred2args; + ast_ref_vector m_pinned; + rule_set m_rules; + func_decl_ref m_query_pred; + expr_ref m_answer; + volatile bool m_cancel; + proof_converter_ref m_pc; + sort_ref m_path_sort; + + lbool check_query(); + + void checkpoint(); + + void declare_datatypes(); + + void compile_nonlinear(); + + void mk_rule_vars_nonlinear(rule& r, unsigned rule_id, expr* trace_arg, expr* path_arg, expr_ref_vector& sub); + + expr_ref mk_var_nonlinear(func_decl* pred, sort* s, unsigned idx, expr* path_arg, expr* trace_arg); + + expr_ref mk_arg_nonlinear(func_decl* pred, unsigned idx, expr* path_arg, expr* trace_arg); + + bool is_linear() const; + + lbool check_nonlinear(); + + func_decl_ref mk_predicate(func_decl* p); + + func_decl_ref mk_rule(func_decl* p, unsigned rule_idx); + + // linear check + lbool check_linear(); + + lbool check_linear(unsigned level); + + void compile_linear(); + + void compile_linear(unsigned level); + + void compile_linear(rule& r, unsigned level); + + expr_ref mk_level_predicate(symbol const& name, unsigned level); + + expr_ref mk_level_predicate(func_decl* p, unsigned level); + + expr_ref mk_level_arg(func_decl* pred, unsigned idx, unsigned level); + + expr_ref mk_level_rule(func_decl* p, unsigned rule_idx, unsigned level); + + expr_ref mk_level_var(func_decl* pred, sort* s, unsigned rule_id, unsigned idx, unsigned level); + + void get_model_linear(unsigned level); + + void assert_expr(expr* e); + + void mk_rule_vars(rule& r, unsigned level, unsigned rule_id, expr_ref_vector& sub); + + public: + bmc(context& ctx); + + ~bmc(); + + lbool query(expr* query); + + void cancel(); + + void cleanup(); + + void display_certificate(std::ostream& out) const; + + void collect_statistics(statistics& st) const; + + expr_ref get_answer(); + + static void collect_params(param_descrs& p); + + void updt_params(); + + }; +}; + + + +#endif diff --git a/lib/dl_bound_relation.cpp b/lib/dl_bound_relation.cpp new file mode 100644 index 000000000..ec8551600 --- /dev/null +++ b/lib/dl_bound_relation.cpp @@ -0,0 +1,706 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + dl_bound_relation.cpp + +Abstract: + + Basic (strict upper) bound relation. + +Author: + + Nikolaj Bjorner (nbjorner) 2010-2-11 + +Revision History: + +--*/ + +#include "dl_bound_relation.h" +#include "debug.h" +#include "ast_pp.h" + +namespace datalog { + + bound_relation_plugin::bound_relation_plugin(relation_manager& m): + relation_plugin(bound_relation_plugin::get_name(), m), + m_arith(get_ast_manager()), + m_bsimp(get_ast_manager()) { + } + + bool bound_relation_plugin::can_handle_signature(const relation_signature & sig) { + for (unsigned i = 0; i < sig.size(); ++i) { + if (!m_arith.is_int(sig[i]) && !m_arith.is_real(sig[i])) { + return false; + } + } + return true; + } + + bound_relation& bound_relation_plugin::get(relation_base& r) { + return dynamic_cast(r); + } + + bound_relation const & bound_relation_plugin::get(relation_base const& r) { + return dynamic_cast(r); + } + + bound_relation* bound_relation_plugin::get(relation_base* r) { + return dynamic_cast(r); + } + + bool bound_relation_plugin::is_interval_relation(relation_base const& r) { + return symbol("interval_relation") == r.get_plugin().get_name(); + } + + interval_relation& bound_relation_plugin::get_interval_relation(relation_base& r) { + SASSERT(is_interval_relation(r)); + return dynamic_cast(r); + } + + interval_relation const& bound_relation_plugin::get_interval_relation(relation_base const& r) { + SASSERT(is_interval_relation(r)); + return dynamic_cast(r); + } + + relation_base * bound_relation_plugin::mk_empty(const relation_signature & s) { + return alloc(bound_relation, *this, s, true); + } + + relation_base * bound_relation_plugin::mk_full(func_decl* p, const relation_signature & s) { + return alloc(bound_relation, *this, s, false); + } + + class bound_relation_plugin::join_fn : public convenient_relation_join_fn { + public: + join_fn(const relation_signature & o1_sig, const relation_signature & o2_sig, unsigned col_cnt, + const unsigned * cols1, const unsigned * cols2) + : convenient_relation_join_fn(o1_sig, o2_sig, col_cnt, cols1, cols2) { + } + + virtual relation_base * operator()(const relation_base & _r1, const relation_base & _r2) { + bound_relation const& r1 = get(_r1); + bound_relation const& r2 = get(_r2); + bound_relation_plugin& p = r1.get_plugin(); + bound_relation* result = dynamic_cast(p.mk_full(0, get_result_signature())); + result->mk_join(r1, r2, m_cols1.size(), m_cols1.c_ptr(), m_cols2.c_ptr()); + return result; + } + }; + + relation_join_fn * bound_relation_plugin::mk_join_fn(const relation_base & r1, const relation_base & r2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2) { + if (!check_kind(r1) || !check_kind(r2)) { + return 0; + } + return alloc(join_fn, r1.get_signature(), r2.get_signature(), col_cnt, cols1, cols2); + } + + + class bound_relation_plugin::project_fn : public convenient_relation_project_fn { + public: + project_fn(const relation_signature & orig_sig, unsigned removed_col_cnt, const unsigned * removed_cols) + : convenient_relation_project_fn(orig_sig, removed_col_cnt, removed_cols) { + } + + virtual relation_base * operator()(const relation_base & _r) { + bound_relation const& r = get(_r); + bound_relation_plugin& p = r.get_plugin(); + bound_relation* result = get(p.mk_full(0, get_result_signature())); + result->mk_project(r, m_removed_cols.size(), m_removed_cols.c_ptr()); + return result; + } + }; + + relation_transformer_fn * bound_relation_plugin::mk_project_fn(const relation_base & r, + unsigned col_cnt, const unsigned * removed_cols) { + return alloc(project_fn, r.get_signature(), col_cnt, removed_cols); + } + + class bound_relation_plugin::rename_fn : public convenient_relation_rename_fn { + public: + rename_fn(const relation_signature & orig_sig, unsigned cycle_len, const unsigned * cycle) + : convenient_relation_rename_fn(orig_sig, cycle_len, cycle) { + } + + virtual relation_base * operator()(const relation_base & _r) { + bound_relation const& r = get(_r); + bound_relation_plugin& p = r.get_plugin(); + bound_relation* result = get(p.mk_full(0, get_result_signature())); + result->mk_rename(r, m_cycle.size(), m_cycle.c_ptr()); + return result; + } + }; + + relation_transformer_fn * bound_relation_plugin::mk_rename_fn(const relation_base & r, + unsigned cycle_len, const unsigned * permutation_cycle) { + if(check_kind(r)) { + return alloc(rename_fn, r.get_signature(), cycle_len, permutation_cycle); + } + return 0; + } + + + class bound_relation_plugin::union_fn : public relation_union_fn { + bool m_is_widen; + public: + union_fn(bool is_widen) : + m_is_widen(is_widen) { + } + virtual void operator()(relation_base & _r, const relation_base & _src, relation_base * _delta) { + TRACE("bound_relation", _r.display(tout << "dst:\n"); _src.display(tout << "src:\n");); + get(_r).mk_union(get(_src), get(_delta), m_is_widen); + } + }; + + class bound_relation_plugin::union_fn_i : public relation_union_fn { + bool m_is_widen; + public: + union_fn_i(bool is_widen) : + m_is_widen(is_widen) { + } + virtual void operator()(relation_base & _r, const relation_base & _src, relation_base * _delta) { + TRACE("bound_relation", _r.display(tout << "dst:\n"); _src.display(tout << "src:\n");); + get(_r).mk_union_i(get_interval_relation(_src), get(_delta), m_is_widen); + TRACE("bound_relation", _r.display(tout << "dst':\n");); + } + }; + + + relation_union_fn * bound_relation_plugin::mk_union_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta) { + if (check_kind(tgt) && is_interval_relation(src) && (!delta || check_kind(*delta))) { + return alloc(union_fn_i, false); + } + if (check_kind(tgt) && check_kind(src) && (!delta || check_kind(*delta))) { + return alloc(union_fn, false); + } + return 0; + } + + relation_union_fn * bound_relation_plugin::mk_widen_fn( + const relation_base & tgt, const relation_base & src, + const relation_base * delta) { + if (check_kind(tgt) && is_interval_relation(src) && (!delta || check_kind(*delta))) { + return alloc(union_fn_i, true); + } + if (check_kind(tgt) && check_kind(src) && (!delta || check_kind(*delta))) { + return alloc(union_fn, true); + } + return 0; + } + + class bound_relation_plugin::filter_identical_fn : public relation_mutator_fn { + unsigned_vector m_cols; + public: + filter_identical_fn(unsigned col_cnt, const unsigned * identical_cols) + : m_cols(col_cnt, identical_cols) {} + + virtual void operator()(relation_base & r) { + for (unsigned i = 1; i < m_cols.size(); ++i) { + get(r).equate(m_cols[0], m_cols[i]); + } + } + }; + + relation_mutator_fn * bound_relation_plugin::mk_filter_identical_fn( + const relation_base & t, unsigned col_cnt, const unsigned * identical_cols) { + if(check_kind(t)) { + return alloc(filter_identical_fn, col_cnt, identical_cols); + } + return 0; + } + + class bound_relation_plugin::filter_equal_fn : public relation_mutator_fn { + public: + filter_equal_fn(relation_element const& value, unsigned col) {} + + virtual void operator()(relation_base & r) { } + }; + + relation_mutator_fn * bound_relation_plugin::mk_filter_equal_fn(const relation_base & r, + const relation_element & value, unsigned col) { + if (check_kind(r)) { + return alloc(filter_equal_fn, value, col); + } + return 0; + } + + class bound_relation_plugin::filter_interpreted_fn : public relation_mutator_fn { + enum kind_t { NOT_APPLICABLE, EQ_VAR, EQ_SUB, LT_VAR, LE_VAR, K_FALSE }; + app_ref m_cond; + app_ref m_lt; + arith_util m_arith; + interval_relation* m_interval; + unsigned_vector m_vars; + kind_t m_kind; + + unsigned get_var(expr* a) { + SASSERT(is_var(a)); + return to_var(a)->get_idx(); + } + + // x = z - y + void mk_sub_eq(expr* x, expr* z, expr* y) { + SASSERT(is_var(x)); + SASSERT(is_var(z)); + SASSERT(is_var(y)); + m_vars.push_back(get_var(x)); + m_vars.push_back(get_var(z)); + m_vars.push_back(get_var(y)); + m_kind = EQ_SUB; + } + + void mk_lt(expr* l, expr* r) { + SASSERT(is_var(l)); + SASSERT(is_var(r)); + m_vars.push_back(get_var(l)); + m_vars.push_back(get_var(r)); + m_lt = m_arith.mk_lt(l, r); + m_kind = LT_VAR; + } + + + void mk_le(expr* l, expr* r) { + SASSERT(is_var(l)); + SASSERT(is_var(r)); + m_vars.push_back(get_var(l)); + m_vars.push_back(get_var(r)); + m_kind = LE_VAR; + } + + void mk_eq(expr* l, expr* r) { + m_vars.push_back(get_var(l)); + m_vars.push_back(get_var(r)); + m_kind = EQ_VAR; + } + + public: + + filter_interpreted_fn(ast_manager& m, app* cond) : + m_cond(cond, m), + m_lt(m), m_arith(m), m_interval(0), m_kind(NOT_APPLICABLE) { + expr* l, *r, *r1, *r2, *c2; + rational n1; + if ((m_arith.is_lt(cond, l, r) || m_arith.is_gt(cond, r, l)) && + is_var(l) && is_var(r)) { + mk_lt(l, r); + } + else if (m.is_not(cond, c2) && + (m_arith.is_ge(c2, l, r) || m_arith.is_le(c2, r, l)) && + is_var(l) && is_var(r)) { + mk_lt(l, r); + } + else if ((m_arith.is_le(cond, l, r) || m_arith.is_ge(cond, r, l)) && + is_var(l) && is_var(r)) { + mk_le(l, r); + } + else if (m.is_not(cond, c2) && + (m_arith.is_gt(c2, l, r) || m_arith.is_lt(c2, r, l)) && + is_var(l) && is_var(r)) { + mk_le(l, r); + } + else if (m.is_false(cond)) { + m_kind = K_FALSE; + } + else if (m.is_eq(cond, l, r) && is_var(l) && is_var(r)) { + mk_eq(l, r); + } + else if (m.is_eq(cond, l, r) && + m_arith.is_sub(r, r1, r2) && + is_var(l) && is_var(r1) && is_var(r2)) { + mk_sub_eq(l, r1, r2); + } + else if (m.is_eq(cond, l, r) && + m_arith.is_sub(l, r1, r2) && + is_var(r) && is_var(r1) && is_var(r2)) { + mk_sub_eq(r, r1, r2); + } + else if (m.is_eq(cond, l, r) && + m_arith.is_add(r, r1, r2) && + m_arith.is_numeral(r1, n1) && + n1.is_pos() && is_var(l) && is_var(r2)) { + mk_lt(r2, l); + } + else if (m.is_eq(cond, l, r) && + m_arith.is_add(r, r1, r2) && + m_arith.is_numeral(r2, n1) && + n1.is_pos() && is_var(l) && is_var(r1)) { + mk_lt(r1, l); + } + else { + + } + } + + // + // x = z - y + // x = y + // x < y + // x <= y + // x < y + z + // + + void operator()(relation_base& t) { + TRACE("dl", tout << mk_pp(m_cond, m_cond.get_manager()) << "\n"; t.display(tout);); + bound_relation& r = get(t); + switch(m_kind) { + case K_FALSE: + r.set_empty(); + break; + case NOT_APPLICABLE: + break; + case EQ_VAR: + r.equate(m_vars[0], m_vars[1]); + break; + case EQ_SUB: + // TBD + break; + case LT_VAR: + r.mk_lt(m_vars[0], m_vars[1]); + break; + case LE_VAR: + r.mk_le(m_vars[0], m_vars[1]); + break; + default: + UNREACHABLE(); + break; + } + TRACE("dl", t.display(tout << "result\n");); + } + + bool supports_attachment(relation_base& t) { + return is_interval_relation(t); + } + + void attach(relation_base& t) { + SASSERT(is_interval_relation(t)); + interval_relation& r = get_interval_relation(t); + m_interval = &r; + } + }; + + relation_mutator_fn * bound_relation_plugin::mk_filter_interpreted_fn(const relation_base & t, app * condition) { + return alloc(filter_interpreted_fn, t.get_plugin().get_ast_manager(), condition); + } + + // ----------------------------- + // bound_relation + + void bound_relation_helper::mk_project_t(uint_set2& t, unsigned_vector const& renaming) { + if (t.lt.empty() && t.le.empty()) { + return; + } + uint_set::iterator it = t.lt.begin(), end = t.lt.end(); + unsigned_vector ltv, lev; + for (; it != end; ++it) { + unsigned elem = *it; + ltv.push_back(renaming[*it]); + } + it = t.le.begin(), end = t.le.end(); + for (; it != end; ++it) { + unsigned elem = *it; + lev.push_back(renaming[*it]); + } + TRACE("dl", + tout << "project: "; + for (unsigned i = 0; i < renaming.size(); ++i) + if (renaming[i] == UINT_MAX) tout << i << " "; + tout << ": "; + it = t.lt.begin(); end = t.lt.end(); + for (; it != end; ++it) tout << *it << " "; + tout << " le "; + it = t.le.begin(); end = t.le.end(); + for (; it != end; ++it) tout << *it << " "; + tout << " => "; + for (unsigned i = 0; i < ltv.size(); ++i) tout << ltv[i] << " "; + tout << " le "; + for (unsigned i = 0; i < lev.size(); ++i) tout << lev[i] << " "; + tout << "\n";); + t.lt.reset(); + for (unsigned i = 0; i < ltv.size(); ++i) { + t.lt.insert(ltv[i]); + } + t.le.reset(); + for (unsigned i = 0; i < lev.size(); ++i) { + t.le.insert(lev[i]); + } + } + + bound_relation::bound_relation(bound_relation_plugin& p, relation_signature const& s, bool is_empty): + vector_relation(p, s, is_empty, uint_set2()) + { + } + + + uint_set2 bound_relation::mk_intersect(uint_set2 const& t1, uint_set2 const& t2, bool& is_empty) const { + is_empty = false; + uint_set2 r(t1); + r.lt |= t2.lt; + r.le |= t2.le; + return r; + } + + uint_set2 bound_relation::mk_widen(uint_set2 const& t1, uint_set2 const& t2) const { + return mk_unite(t1, t2); + } + + uint_set2 bound_relation::mk_unite(uint_set2 const& t1, uint_set2 const& t2) const { + uint_set2 s1(t1); + s1.lt &= t2.lt; + s1.le &= t2.le; + return s1; + } + + uint_set2 bound_relation::mk_eq(union_find<> const& old_eqs, union_find<> const& new_eqs, uint_set2 const& t) const { + unsigned sz = old_eqs.get_num_vars(); + SASSERT(sz == new_eqs.get_num_vars()); + uint_set2 result; + for (unsigned i = 0; i < sz; ++i) { + if (t.lt.contains(i)) { + unsigned j = i; + do { + result.lt.insert(new_eqs.find(j)); + j = old_eqs.next(j); + } + while (j != i); + } + if (t.le.contains(i)) { + unsigned j = i; + do { + result.le.insert(new_eqs.find(j)); + j = old_eqs.next(j); + } + while (j != i); + } + } + return result; + } + + bool bound_relation::is_subset_of(uint_set2 const& t1, uint_set2 const& t2) const { + uint_set2 s1, s2; + normalize(t1, s1); + normalize(t2, s2); + return s1.lt.subset_of(s2.lt) && s1.le.subset_of(s2.le); + } + + void bound_relation::mk_rename_elem(uint_set2& t, unsigned col_cnt, unsigned const* cycle) { + // [ 0 -> 2 -> 3 -> 0] + if (col_cnt == 0) return; + unsigned col1, col2; + col1 = find(cycle[0]); + col2 = find(cycle[col_cnt-1]); + bool has_col2_lt = t.lt.contains(col2); + t.lt.remove(col2); + bool has_col2_le = t.le.contains(col2); + t.le.remove(col2); + for (unsigned i = 0; i + 1 < col_cnt; ++i) { + col1 = find(cycle[i]); + col2 = find(cycle[i+1]); + if (t.lt.contains(col1)) { + t.lt.remove(col1); + t.lt.insert(col2); + } + if (t.le.contains(col1)) { + t.le.remove(col1); + t.le.insert(col2); + } + } + if (has_col2_lt) { + col1 = find(cycle[0]); + t.lt.insert(col1); + } + if (has_col2_le) { + col1 = find(cycle[0]); + t.le.insert(col1); + } + } + + + bool bound_relation::is_full(uint_set2 const& t) const { + return t.lt.empty() && t.le.empty(); + } + + bool bound_relation::is_empty(unsigned index, uint_set2 const& t) const { + return t.lt.contains(find(index)) || t.le.contains(find(index)); + } + + void bound_relation::normalize(uint_set const& src, uint_set& dst) const { + uint_set::iterator it = src.begin(), end = src.end(); + for (; it != end; ++it) { + dst.insert(find(*it)); + } + } + void bound_relation::normalize(uint_set2 const& src, uint_set2& dst) const { + normalize(src.lt, dst.lt); + normalize(src.le, dst.le); + } + + + void bound_relation::mk_lt(unsigned i) { + uint_set2& dst = (*this)[i]; + while (!m_todo.empty()) { + unsigned j = m_todo.back().first; + bool strict = m_todo.back().second; + if (i == j && strict) { + m_todo.reset(); + m_empty = true; + return; + } + m_todo.pop_back(); + if (i == j) { + continue; + } + uint_set2& src = (*m_elems)[j]; + uint_set::iterator it = src.lt.begin(), end = src.lt.end(); + for(; it != end; ++it) { + m_todo.push_back(std::make_pair(*it, true)); + } + it = src.le.begin(), end = src.le.end(); + for(; it != end; ++it) { + m_todo.push_back(std::make_pair(*it, strict)); + } + if (strict) { + dst.lt.insert(j); + } + else { + dst.le.insert(j); + } + } + } + + void bound_relation::mk_lt(unsigned i, unsigned j) { + m_todo.reset(); + i = find(i); + m_todo.push_back(std::make_pair(find(j), true)); + mk_lt(i); + } + + void bound_relation::mk_le(unsigned i, unsigned j) { + m_todo.reset(); + i = find(i); + m_todo.push_back(std::make_pair(find(j), false)); + mk_lt(i); + } + + bool bound_relation::is_lt(unsigned i, unsigned j) const { + return (*this)[i].lt.contains(find(j)); + } + + void bound_relation::add_fact(const relation_fact & f) { + bound_relation r(get_plugin(), get_signature(), false); + for (unsigned i = 0; i < f.size(); ++i) { + scoped_ptr fe = get_plugin().mk_filter_equal_fn(r, f[i], i); + (*fe)(r); + } + mk_union(r, 0, false); + } + + bool bound_relation::contains_fact(const relation_fact & f) const { + if (empty()) { + return false; + } + // this is a very rough approximation. + return true; + } + + bound_relation * bound_relation::clone() const { + bound_relation* result = 0; + if (empty()) { + result = bound_relation_plugin::get(get_plugin().mk_empty(get_signature())); + } + else { + result = bound_relation_plugin::get(get_plugin().mk_full(0, get_signature())); + result->copy(*this); + } + return result; + } + + void bound_relation::mk_union_i(interval_relation const& src, bound_relation* delta, bool is_widen) { + unsigned size = get_signature().size(); + for (unsigned i = 0; i < size; ++i) { + if (find(i) != i) { + continue; + } + uint_set2& s = (*this)[i]; + ext_numeral const& lo = src[i].sup(); + if (lo.is_infinite()) { + s.lt.reset(); + s.le.reset(); + continue; + } + uint_set::iterator it = s.lt.begin(), end = s.lt.end(); + for(; it != end; ++it) { + ext_numeral const& hi = src[*it].inf(); + if (hi.is_infinite() || lo.to_rational() >= hi.to_rational()) { + s.lt.remove(*it); + } + } + it = s.le.begin(), end = s.le.end(); + for(; it != end; ++it) { + ext_numeral const& hi = src[*it].inf(); + if (hi.is_infinite() || lo.to_rational() > hi.to_rational()) { + s.le.remove(*it); + } + } + } + } + + bound_relation * bound_relation::complement(func_decl* p) const { + UNREACHABLE(); + return 0; + } + + void bound_relation::to_formula(expr_ref& fml) const { + ast_manager& m = get_plugin().get_ast_manager(); + arith_util& arith = get_plugin().m_arith; + basic_simplifier_plugin& bsimp = get_plugin().m_bsimp; + expr_ref_vector conjs(m); + relation_signature const& sig = get_signature(); + for (unsigned i = 0; i < sig.size(); ++i) { + if (i != find(i)) { + conjs.push_back(m.mk_eq(m.mk_var(i, sig[i]), m.mk_var(find(i), sig[find(i)]))); + continue; + } + uint_set2 const& upper = (*this)[i]; + uint_set::iterator it = upper.lt.begin(), end = upper.lt.end(); + for (; it != end; ++it) { + conjs.push_back(arith.mk_lt(m.mk_var(i, sig[i]), m.mk_var(*it, sig[*it]))); + } + it = upper.le.begin(), end = upper.le.end(); + for (; it != end; ++it) { + conjs.push_back(arith.mk_le(m.mk_var(i, sig[i]), m.mk_var(*it, sig[*it]))); + } + } + bsimp.mk_and(conjs.size(), conjs.c_ptr(), fml); + } + + + void bound_relation::display_index(unsigned i, uint_set2 const& src, std::ostream & out) const { + uint_set::iterator it = src.lt.begin(), end = src.lt.end(); + out << i; + if (!src.lt.empty()) { + out << " < "; + for(; it != end; ++it) { + out << *it << " "; + } + } + if (!src.le.empty()) { + it = src.le.begin(), end = src.le.end(); + out << " <= "; + for(; it != end; ++it) { + out << *it << " "; + } + } + out << "\n"; + } + + bound_relation_plugin& bound_relation::get_plugin() const { + return dynamic_cast(relation_base::get_plugin()); + } + + +}; + + diff --git a/lib/dl_bound_relation.h b/lib/dl_bound_relation.h new file mode 100644 index 000000000..603717bd8 --- /dev/null +++ b/lib/dl_bound_relation.h @@ -0,0 +1,174 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + dl_bound_relation.h + +Abstract: + + Basic (strict upper) bound relation. + +Author: + + Nikolaj Bjorner (nbjorner) 2010-2-11 + +Revision History: + +--*/ +#ifndef _DL_BOUND_RELATION_H_ +#define _DL_BOUND_RELATION_H_ + +#include "dl_context.h" +#include "uint_set.h" +#include "dl_vector_relation.h" +#include "dl_interval_relation.h" +#include "arith_decl_plugin.h" +#include "basic_simplifier_plugin.h" + +namespace datalog { + + class bound_relation; + + class bound_relation_plugin : public relation_plugin { + friend class bound_relation; + class join_fn; + class project_fn; + class rename_fn; + class union_fn; + class union_fn_i; + class filter_equal_fn; + class filter_identical_fn; + class filter_interpreted_fn; + class filter_intersection_fn; + arith_util m_arith; + basic_simplifier_plugin m_bsimp; + public: + bound_relation_plugin(relation_manager& m); + virtual bool can_handle_signature(const relation_signature & s); + static symbol get_name() { return symbol("bound_relation"); } + virtual relation_base * mk_empty(const relation_signature & s); + virtual relation_base * mk_full(func_decl* p, const relation_signature & s); + virtual relation_join_fn * mk_join_fn(const relation_base & t1, const relation_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2); + virtual relation_transformer_fn * mk_project_fn(const relation_base & t, unsigned col_cnt, + const unsigned * removed_cols); + virtual relation_transformer_fn * mk_rename_fn(const relation_base & t, unsigned permutation_cycle_len, + const unsigned * permutation_cycle); + virtual relation_union_fn * mk_union_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta); + virtual relation_union_fn * mk_widen_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta); + virtual relation_mutator_fn * mk_filter_identical_fn(const relation_base & t, unsigned col_cnt, + const unsigned * identical_cols); + virtual relation_mutator_fn * mk_filter_equal_fn(const relation_base & t, const relation_element & value, + unsigned col); + virtual relation_mutator_fn * mk_filter_interpreted_fn(const relation_base & t, app * condition); + + virtual relation_join_fn * mk_join_project_fn(const relation_base & t1, const relation_base & t2, + unsigned joined_col_cnt, const unsigned * cols1, const unsigned * cols2, + unsigned removed_col_cnt, const unsigned * removed_cols) { return 0; } + +#if 0 + virtual intersection_filter_fn * mk_filter_by_intersection_fn( + const relation_base & t, + const relation_base & src, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * src_cols) { + return 0; + } +#endif + + static bound_relation* get(relation_base* r); + private: + static bound_relation& get(relation_base& r); + static bound_relation const & get(relation_base const& r); + + + static bool is_interval_relation(relation_base const& r); + static interval_relation& get_interval_relation(relation_base& r); + static interval_relation const& get_interval_relation(relation_base const& r); + }; + + struct uint_set2 { + uint_set lt; + uint_set le; + uint_set2(uint_set2 const& other):lt(other.lt), le(other.le) {} + uint_set2() {} + bool operator==(const uint_set2& other) const { + return other.lt == lt && other.le == le; + } + bool operator!=(const uint_set2& other) const { + return other.lt != lt || other.le != le; + } + }; + + inline std::ostream & operator<<(std::ostream & target, const uint_set2 & s) { + return target << s.lt << " " << s.le; + } + + + class bound_relation_helper { + public: + static void mk_project_t(uint_set2& t, unsigned_vector const& renaming); + }; + + class bound_relation : public vector_relation { + friend class bound_relation_plugin; + svector > m_todo; + + public: + bound_relation(bound_relation_plugin& p, relation_signature const& s, bool is_empty); + bound_relation& operator=(bound_relation const& other); + + virtual bool empty() const { return m_empty; } + virtual void add_fact(const relation_fact & f); + virtual bool contains_fact(const relation_fact & f) const; + virtual bound_relation * clone() const; + virtual bound_relation * complement(func_decl* p) const; + virtual void to_formula(expr_ref& fml) const; + bound_relation_plugin& get_plugin() const; + + void mk_union_i(interval_relation const& src, bound_relation* delta, bool is_widen); + + void mk_lt(unsigned i, unsigned j); + + void mk_lt(unsigned i); + + void mk_le(unsigned i, unsigned j); + + bool is_lt(unsigned i, unsigned j) const; + + + private: + typedef uint_set2 T; + virtual T mk_intersect(T const& t1, T const& t2, bool& is_empty) const; + + virtual T mk_widen(T const& t1, T const& t2) const; + + virtual T mk_unite(T const& t1, T const& t2) const; + + virtual T mk_eq(union_find<> const& old_eqs, union_find<> const& new_eqs, T const& t) const; + + virtual void mk_rename_elem(T& i, unsigned col_cnt, unsigned const* cycle); + + + virtual bool is_subset_of(T const& t1, T const& t2) const; + + virtual bool is_full(T const& t) const; + + virtual bool is_empty(unsigned idx, T const& t) const; + + virtual void display_index(unsigned idx, T const& t, std::ostream& out) const; + + void normalize(T const& src, T& dst) const; + + void normalize(uint_set const& src, uint_set& dst) const; + + + + }; + +}; + +#endif + diff --git a/lib/dl_check_table.cpp b/lib/dl_check_table.cpp new file mode 100644 index 000000000..de3a71746 --- /dev/null +++ b/lib/dl_check_table.cpp @@ -0,0 +1,332 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + dl_check_table.cpp + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2010-11-15 + + +Revision History: + +--*/ + + +#include "dl_check_table.h" +#include "dl_table.h" + + +namespace datalog { + + bool check_table_plugin::can_handle_signature(table_signature const& s) { + return m_tocheck.can_handle_signature(s) && m_checker.can_handle_signature(s); + } + + + check_table & check_table_plugin::get(table_base& r) { + return static_cast(r); + } + + check_table const & check_table_plugin::get(table_base const& r) { + return static_cast(r); + } + + table_base& check_table_plugin::checker(table_base& r) { return *get(r).m_checker; } + table_base const& check_table_plugin::checker(table_base const& r) { return *get(r).m_checker; } + table_base* check_table_plugin::checker(table_base* r) { return r?(get(*r).m_checker):0; } + table_base const* check_table_plugin::checker(table_base const* r) { return r?(get(*r).m_checker):0; } + table_base& check_table_plugin::tocheck(table_base& r) { return *get(r).m_tocheck; } + table_base const& check_table_plugin::tocheck(table_base const& r) { return *get(r).m_tocheck; } + table_base* check_table_plugin::tocheck(table_base* r) { return r?(get(*r).m_tocheck):0; } + table_base const* check_table_plugin::tocheck(table_base const* r) { return r?(get(*r).m_tocheck):0; } + + table_base * check_table_plugin::mk_empty(const table_signature & s) { + table_base* checker = m_checker.mk_empty(s); + table_base* tocheck = m_tocheck.mk_empty(s); + return alloc(check_table, *this, s, tocheck, checker); + } + + class check_table_plugin::join_fn : public table_join_fn { + scoped_ptr m_tocheck; + scoped_ptr m_checker; + public: + join_fn(check_table_plugin& p, + const table_base & t1, const table_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2) { + m_tocheck = p.get_manager().mk_join_fn(tocheck(t1), tocheck(t2), col_cnt, cols1, cols2); + m_checker = p.get_manager().mk_join_fn(checker(t1), checker(t2), col_cnt, cols1, cols2); + } + + virtual table_base* operator()(const table_base & t1, const table_base & t2) { + table_base* ttocheck = (*m_tocheck)(tocheck(t1), tocheck(t2)); + table_base* tchecker = (*m_checker)(checker(t1), checker(t2)); + return alloc(check_table, get(t1).get_plugin(), ttocheck->get_signature(), ttocheck, tchecker); + } + }; + + table_join_fn * check_table_plugin::mk_join_fn(const table_base & t1, const table_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2) { + if (!check_kind(t1) || !check_kind(t2)) { + return 0; + } + return alloc(join_fn, *this, t1, t2, col_cnt, cols1, cols2); + } + + class check_table_plugin::union_fn : public table_union_fn { + scoped_ptr m_tocheck; + scoped_ptr m_checker; + public: + union_fn(check_table_plugin& p, table_base const& tgt, const table_base& src, table_base const* delta) { + m_tocheck = p.get_manager().mk_union_fn(tocheck(tgt), tocheck(src), tocheck(delta)); + m_checker = p.get_manager().mk_union_fn(checker(tgt), checker(src), checker(delta)); + } + + virtual void operator()(table_base& tgt, const table_base& src, table_base* delta) { + (*m_tocheck)(tocheck(tgt), tocheck(src), tocheck(delta)); + (*m_checker)(checker(tgt), checker(src), checker(delta)); + SASSERT(get(tgt).well_formed()); + } + }; + + table_union_fn * check_table_plugin::mk_union_fn(const table_base & tgt, const table_base & src, const table_base * delta) { + if (!check_kind(tgt) || !check_kind(src) || (delta && !check_kind(*delta))) { + return 0; + } + return alloc(union_fn, *this, tgt, src, delta); + + } + + class check_table_plugin::project_fn : public table_transformer_fn { + scoped_ptr m_checker; + scoped_ptr m_tocheck; + public: + project_fn(check_table_plugin& p, const table_base & t, unsigned col_cnt, const unsigned * removed_cols) { + m_checker = p.get_manager().mk_project_fn(checker(t), col_cnt, removed_cols); + m_tocheck = p.get_manager().mk_project_fn(tocheck(t), col_cnt, removed_cols); + } + + table_base* operator()(table_base const& src) { + table_base* tchecker = (*m_checker)(checker(src)); + table_base* ttocheck = (*m_tocheck)(tocheck(src)); + return alloc(check_table, get(src).get_plugin(), tchecker->get_signature(), ttocheck, tchecker); + } + }; + + table_transformer_fn * check_table_plugin::mk_project_fn(const table_base & t, unsigned col_cnt, const unsigned * removed_cols) { + if (!check_kind(t)) { + return 0; + } + return alloc(project_fn, *this, t, col_cnt, removed_cols); + } + + class check_table_plugin::rename_fn : public table_transformer_fn { + scoped_ptr m_checker; + scoped_ptr m_tocheck; + public: + rename_fn(check_table_plugin& p, const table_base & t, unsigned cycle_len, unsigned const* cycle) { + m_checker = p.get_manager().mk_rename_fn(checker(t), cycle_len, cycle); + m_tocheck = p.get_manager().mk_rename_fn(tocheck(t), cycle_len, cycle); + } + + table_base* operator()(table_base const& src) { + table_base* tchecker = (*m_checker)(checker(src)); + table_base* ttocheck = (*m_tocheck)(tocheck(src)); + return alloc(check_table, get(src).get_plugin(), ttocheck->get_signature(), ttocheck, tchecker); + } + }; + + table_transformer_fn * check_table_plugin::mk_rename_fn(const table_base & t, unsigned len, const unsigned * cycle) { + if (!check_kind(t)) { + return 0; + } + return alloc(rename_fn, *this, t, len, cycle); + } + + class check_table_plugin::filter_identical_fn : public table_mutator_fn { + scoped_ptr m_checker; + scoped_ptr m_tocheck; + public: + filter_identical_fn(check_table_plugin& p, const table_base & t,unsigned cnt, unsigned const* cols) + { + m_checker = p.get_manager().mk_filter_identical_fn(checker(t), cnt, cols); + m_tocheck = p.get_manager().mk_filter_identical_fn(tocheck(t), cnt, cols); + } + + void operator()(table_base & t) { + (*m_checker)(checker(t)); + (*m_tocheck)(tocheck(t)); + SASSERT(get(t).well_formed()); + } + }; + + table_mutator_fn * check_table_plugin::mk_filter_identical_fn(const table_base & t, unsigned col_cnt, + const unsigned * identical_cols) { + if (check_kind(t)) { + return alloc(filter_identical_fn, *this, t, col_cnt, identical_cols); + } + return 0; + } + + class check_table_plugin::filter_equal_fn : public table_mutator_fn { + scoped_ptr m_checker; + scoped_ptr m_tocheck; + public: + filter_equal_fn(check_table_plugin& p, const table_base & t, const table_element & v, unsigned col) + { + m_checker = p.get_manager().mk_filter_equal_fn(checker(t), v, col); + m_tocheck = p.get_manager().mk_filter_equal_fn(tocheck(t), v, col); + } + + virtual void operator()(table_base& src) { + (*m_checker)(checker(src)); + (*m_tocheck)(tocheck(src)); + SASSERT(get(src).well_formed()); + } + }; + + table_mutator_fn * check_table_plugin::mk_filter_equal_fn(const table_base & t, const table_element & value, unsigned col) { + if (check_kind(t)) { + return alloc(filter_equal_fn, *this, t, value, col); + } + return 0; + } + + class check_table_plugin::filter_interpreted_fn : public table_mutator_fn { + scoped_ptr m_checker; + scoped_ptr m_tocheck; + public: + filter_interpreted_fn(check_table_plugin& p, const table_base & t, app * condition) + { + m_checker = p.get_manager().mk_filter_interpreted_fn(checker(t), condition); + m_tocheck = p.get_manager().mk_filter_interpreted_fn(tocheck(t), condition); + } + + virtual void operator()(table_base& src) { + (*m_checker)(checker(src)); + (*m_tocheck)(tocheck(src)); + SASSERT(get(src).well_formed()); + } + }; + + table_mutator_fn * check_table_plugin::mk_filter_interpreted_fn(const table_base & t, app * condition) { + if (check_kind(t)) { + return alloc(filter_interpreted_fn, *this, t, condition); + } + return 0; + } + + class check_table_plugin::filter_by_negation_fn : public table_intersection_filter_fn { + scoped_ptr m_checker; + scoped_ptr m_tocheck; + public: + filter_by_negation_fn( + check_table_plugin& p, + const table_base & t, + const table_base & negated_obj, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * negated_cols) { + m_checker = p.get_manager().mk_filter_by_negation_fn(checker(t), checker(negated_obj), joined_col_cnt, t_cols, negated_cols); + m_tocheck = p.get_manager().mk_filter_by_negation_fn(tocheck(t), tocheck(negated_obj), joined_col_cnt, t_cols, negated_cols); + } + + virtual void operator()(table_base& src, table_base const& negated_obj) { + (*m_checker)(checker(src), checker(negated_obj)); + (*m_tocheck)(tocheck(src), tocheck(negated_obj)); + SASSERT(get(src).well_formed()); + } + + }; + + table_intersection_filter_fn * check_table_plugin::mk_filter_by_negation_fn(const table_base & t, + const table_base & negated_obj, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * negated_cols) { + if (check_kind(t) && check_kind(negated_obj)) { + return alloc(filter_by_negation_fn, *this, t, negated_obj, joined_col_cnt, t_cols, negated_cols); + } + return 0; + } + + // ------------------ + // check_table + + + check_table::check_table(check_table_plugin & p, const table_signature & sig): + table_base(p, sig) { + SASSERT(well_formed()); + } + + check_table::check_table(check_table_plugin & p, const table_signature & sig, table_base* tocheck, table_base* checker): + table_base(p, sig), + m_checker(checker), + m_tocheck(tocheck) { + SASSERT(well_formed()); + } + + check_table::~check_table() { + m_tocheck->deallocate(); + m_checker->deallocate(); + } + + bool check_table::well_formed() const { + iterator it = m_tocheck->begin(), end = m_tocheck->end(); + for (; it != end; ++it) { + table_fact fact; + it->get_fact(fact); + if (!m_checker->contains_fact(fact)) { + m_tocheck->display(verbose_stream()); + m_checker->display(verbose_stream()); + UNREACHABLE(); + return false; + } + } + iterator it2 = m_checker->begin(), end2 = m_checker->end(); + for (; it2 != end2; ++it2) { + table_fact fact; + it2->get_fact(fact); + if (!m_tocheck->contains_fact(fact)) { + m_tocheck->display(verbose_stream()); + m_checker->display(verbose_stream()); + UNREACHABLE(); + return false; + } + } + return true; + } + + bool check_table::empty() const { + return m_tocheck->empty(); + } + + + void check_table::add_fact(const table_fact & f) { + m_tocheck->add_fact(f); + m_checker->add_fact(f); + SASSERT(well_formed()); + } + + void check_table::remove_fact(const table_element* f) { + m_tocheck->remove_fact(f); + m_checker->remove_fact(f); + SASSERT(well_formed()); + } + + bool check_table::contains_fact(const table_fact & f) const { + return m_checker->contains_fact(f); + } + + table_base * check_table::clone() const { + return alloc(check_table, get_plugin(), get_signature(), m_tocheck->clone(), m_checker->clone()); + } + + table_base * check_table::complement(func_decl* p) const { + return alloc(check_table, get_plugin(), get_signature(), m_tocheck->complement(p), m_checker->complement(p)); + } + +}; + diff --git a/lib/dl_check_table.h b/lib/dl_check_table.h new file mode 100644 index 000000000..1c7490cda --- /dev/null +++ b/lib/dl_check_table.h @@ -0,0 +1,133 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + dl_check_table.h + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2010-11-15 + + +Revision History: + +--*/ + +#ifndef _DL_CHECK_TABLE_H_ +#define _DL_CHECK_TABLE_H_ + +#include "dl_base.h" +#include "dl_decl_plugin.h" +#include "dl_relation_manager.h" + +namespace datalog { + class check_table; + + class check_table_plugin : public table_plugin { + friend class check_table; + table_plugin& m_checker; + table_plugin& m_tocheck; + protected: + class join_fn; + class union_fn; + class transformer_fn; + class rename_fn; + class project_fn; + class filter_equal_fn; + class filter_identical_fn; + class filter_interpreted_fn; + class filter_by_negation_fn; + + public: + check_table_plugin(relation_manager & manager, symbol const& checker, symbol const& tocheck) + : table_plugin(symbol("check"), manager), + m_checker(*manager.get_table_plugin(checker)), + m_tocheck(*manager.get_table_plugin(tocheck)) {} + + virtual table_base * mk_empty(const table_signature & s); + + virtual table_join_fn * mk_join_fn(const table_base & t1, const table_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2); + virtual table_union_fn * mk_union_fn(const table_base & tgt, const table_base & src, + const table_base * delta); + virtual table_transformer_fn * mk_project_fn(const table_base & t, unsigned col_cnt, + const unsigned * removed_cols); + virtual table_transformer_fn * mk_rename_fn(const table_base & t, unsigned permutation_cycle_len, + const unsigned * permutation_cycle); + virtual table_mutator_fn * mk_filter_identical_fn(const table_base & t, unsigned col_cnt, + const unsigned * identical_cols); + virtual table_mutator_fn * mk_filter_equal_fn(const table_base & t, const table_element & value, + unsigned col); + virtual table_mutator_fn * mk_filter_interpreted_fn(const table_base & t, app * condition); + virtual table_intersection_filter_fn * mk_filter_by_negation_fn( + const table_base & t, + const table_base & negated_obj, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * negated_cols); + + virtual bool can_handle_signature(table_signature const& s); + + private: + static check_table& get(table_base& r); + + static check_table const & get(table_base const& r); + + static table_base& checker(table_base& r); + static table_base const& checker(table_base const& r); + static table_base* checker(table_base* r); + static table_base const* checker(table_base const* r); + static table_base& tocheck(table_base& r); + static table_base const& tocheck(table_base const& r); + static table_base* tocheck(table_base* r); + static table_base const* tocheck(table_base const* r); + }; + + class check_table : public table_base { + friend class check_table_plugin; + friend class check_table_plugin::join_fn; + friend class check_table_plugin::union_fn; + friend class check_table_plugin::transformer_fn; + friend class check_table_plugin::rename_fn; + friend class check_table_plugin::project_fn; + friend class check_table_plugin::filter_equal_fn; + friend class check_table_plugin::filter_identical_fn; + friend class check_table_plugin::filter_interpreted_fn; + friend class check_table_plugin::filter_by_negation_fn; + + table_base* m_checker; + table_base* m_tocheck; + + check_table(check_table_plugin & p, const table_signature & sig); + check_table(check_table_plugin & p, const table_signature & sig, table_base* tocheck, table_base* checker); + + virtual ~check_table(); + + bool well_formed() const; + + public: + + check_table_plugin & get_plugin() const { + return static_cast(table_base::get_plugin()); + } + + virtual bool empty() const; + virtual void add_fact(const table_fact & f); + virtual void remove_fact(const table_element* fact); + virtual bool contains_fact(const table_fact & f) const; + virtual table_base * complement(func_decl* p) const; + virtual table_base * clone() const; + + virtual iterator begin() const { SASSERT(well_formed()); return m_tocheck->begin(); } + virtual iterator end() const { return m_tocheck->end(); } + + virtual unsigned get_size_estimate_rows() const { return m_tocheck->get_size_estimate_rows(); } + virtual unsigned get_size_estimate_bytes() const { return m_tocheck->get_size_estimate_bytes(); } + }; + + }; + + #endif /* _DL_CHECK_TABLE_H_ */ diff --git a/lib/dl_cmds.cpp b/lib/dl_cmds.cpp new file mode 100644 index 000000000..c8947d78e --- /dev/null +++ b/lib/dl_cmds.cpp @@ -0,0 +1,437 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + dl_cmds.cpp + +Abstract: + Datalog commands for SMT2 front-end. + +Author: + + Leonardo (leonardo) 2011-03-28 + +Notes: + +--*/ +#include"cmd_context.h" +#include"dl_cmds.h" +#include"dl_external_relation.h" +#include"dl_context.h" +#include"dl_decl_plugin.h" +#include"dl_instruction.h" +#include"dl_compiler.h" +#include"dl_rule.h" +#include"ast_pp.h" +#include"parametric_cmd.h" +#include"cancel_eh.h" +#include"scoped_ctrl_c.h" +#include"scoped_timer.h" +#include + + +class dl_context { + cmd_context & m_cmd; + unsigned m_ref_count; + datalog::dl_decl_plugin* m_decl_plugin; + scoped_ptr m_context; + +public: + dl_context(cmd_context & ctx): + m_cmd(ctx), + m_ref_count(0), + m_decl_plugin(0) {} + + void inc_ref() { + ++m_ref_count; + } + + void dec_ref() { + --m_ref_count; + if (0 == m_ref_count) { + dealloc(this); + } + } + + void init() { + ast_manager& m = m_cmd.m(); + if (!m_context) { + m_context = alloc(datalog::context, m, m_cmd.params()); + } + if (!m_decl_plugin) { + symbol name("datalog_relation"); + if (m.has_plugin(name)) { + m_decl_plugin = static_cast(m_cmd.m().get_plugin(m.get_family_id(name))); + } + else { + m_decl_plugin = alloc(datalog::dl_decl_plugin); + m.register_plugin(symbol("datalog_relation"), m_decl_plugin); + } + } + } + + void reset() { + m_context = 0; + } + + void add_rule(expr * rule, symbol const& name) { + init(); + std::string error_msg; + m_context->add_rule(rule, name); + } + + datalog::context & get_dl_context() { + init(); + return *m_context; + } +}; + + +/** + \brief rule command. It is also the owner of dl_context object. +*/ +class dl_rule_cmd : public cmd { + ref m_dl_ctx; + mutable unsigned m_arg_idx; + expr* m_t; + symbol m_name; +public: + dl_rule_cmd(dl_context * dl_ctx): + cmd("rule"), + m_dl_ctx(dl_ctx), + m_arg_idx(0), + m_t(0) {} + virtual char const * get_usage() const { return "(forall (q) (=> (and body) head)) :optional-name"; } + virtual char const * get_descr(cmd_context & ctx) const { return "add a Horn rule."; } + virtual unsigned get_arity() const { return VAR_ARITY; } + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { + switch(m_arg_idx) { + case 0: return CPK_EXPR; + case 1: return CPK_SYMBOL; + default: return CPK_SYMBOL; + } + } + virtual void set_next_arg(cmd_context & ctx, expr * t) { + m_t = t; + m_arg_idx++; + } + virtual void set_next_arg(cmd_context & ctx, symbol const & s) { + m_name = s; + } + virtual void reset(cmd_context & ctx) { m_dl_ctx->reset(); prepare(ctx); } + virtual void prepare(cmd_context& ctx) { m_arg_idx = 0; m_name = symbol::null; } + virtual void finalize(cmd_context & ctx) { + } + virtual void execute(cmd_context & ctx) { + m_dl_ctx->add_rule(m_t, m_name); + } +}; + +class dl_query_cmd : public parametric_cmd { + ref m_dl_ctx; + expr* m_target; +public: + dl_query_cmd(dl_context * dl_ctx): + parametric_cmd("query"), + m_dl_ctx(dl_ctx), + m_target(0) { + } + virtual char const * get_usage() const { return "(exists (q) (and body))"; } + virtual char const * get_main_descr() const { + return "pose a query based on the Horn rules."; + } + + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { + if (m_target == 0) return CPK_EXPR; + return parametric_cmd::next_arg_kind(ctx); + } + + virtual void set_next_arg(cmd_context & ctx, expr * t) { + m_target = t; + } + + virtual void prepare(cmd_context & ctx) { + parametric_cmd::prepare(ctx); + m_target = 0; + } + + virtual void execute(cmd_context& ctx) { + if (m_target == 0) { + throw cmd_exception("invalid query command, argument expected"); + } + datalog::context& dlctx = m_dl_ctx->get_dl_context(); + set_background(ctx); + dlctx.updt_params(m_params); + unsigned timeout = m_params.get_uint(":timeout", UINT_MAX); + cancel_eh eh(dlctx); + lbool status = l_undef; + { + scoped_ctrl_c ctrlc(eh); + scoped_timer timer(timeout, &eh); + cmd_context::scoped_watch sw(ctx); + try { + status = dlctx.query(m_target); + } + catch (z3_error & ex) { + throw ex; + } + catch (z3_exception& ex) { + ctx.regular_stream() << "(error \"query failed: " << ex.msg() << "\")" << std::endl; + } + dlctx.cleanup(); + } + switch (status) { + case l_false: + ctx.regular_stream() << "unsat\n"; + print_certificate(ctx); + break; + case l_true: + ctx.regular_stream() << "sat\n"; + print_answer(ctx); + print_certificate(ctx); + break; + case l_undef: + ctx.regular_stream() << "unknown\n"; + switch(dlctx.get_status()) { + case datalog::INPUT_ERROR: + break; + + case datalog::MEMOUT: + ctx.regular_stream() << "memory bounds exceeded\n"; + break; + + case datalog::TIMEOUT: + ctx.regular_stream() << "timeout\n"; + break; + + case datalog::OK: + break; + default: + UNREACHABLE(); + } + break; + } + print_statistics(ctx); + m_target = 0; + } + + virtual void init_pdescrs(cmd_context & ctx, param_descrs & p) { + m_dl_ctx->get_dl_context().collect_params(p); + insert_timeout(p); + p.insert(":print-answer", CPK_BOOL, "(default: false) print answer instance(s) to query."); + p.insert(":print-certificate", CPK_BOOL, "(default: false) print certificate for reachability or non-reachability."); + p.insert(":print-statistics", CPK_BOOL, "(default: false) print statistics."); + } + + +private: + void set_background(cmd_context& ctx) { + datalog::context& dlctx = m_dl_ctx->get_dl_context(); + ast_manager& m = ctx.m(); + ptr_vector::const_iterator it = ctx.begin_assertions(); + ptr_vector::const_iterator end = ctx.end_assertions(); + for (; it != end; ++it) { + dlctx.assert_expr(*it); + } + } + + void print_answer(cmd_context& ctx) { + if (m_params.get_bool(":print-answer", false)) { + datalog::context& dlctx = m_dl_ctx->get_dl_context(); + ast_manager& m = ctx.m(); + expr_ref query_result(dlctx.get_answer_as_formula(), m); + sbuffer var_names; + unsigned num_decls = 0; + if (is_quantifier(m_target)) { + num_decls = to_quantifier(m_target)->get_num_decls(); + } + ctx.display(ctx.regular_stream(), query_result, 0, num_decls, "X", var_names); + ctx.regular_stream() << std::endl; + } + } + + void print_statistics(cmd_context& ctx) { + if (m_params.get_bool(":print-statistics", false)) { + statistics st; + datalog::context& dlctx = m_dl_ctx->get_dl_context(); + unsigned long long max_mem = memory::get_max_used_memory(); + unsigned long long mem = memory::get_allocation_size(); + dlctx.collect_statistics(st); + st.update("time", ctx.get_seconds()); + st.update("memory", static_cast(mem)/static_cast(1024*1024)); + st.update("max-memory", static_cast(max_mem)/static_cast(1024*1024)); + st.display_smt2(ctx.regular_stream()); + } + } + + void print_certificate(cmd_context& ctx) { + if (m_params.get_bool(":print-certificate", false)) { + datalog::context& dlctx = m_dl_ctx->get_dl_context(); + if (!dlctx.display_certificate(ctx.regular_stream())) { + throw cmd_exception("certificates are not supported for selected DL_ENGINE"); + } + ctx.regular_stream() << "\n"; + } + } +}; + +class dl_declare_rel_cmd : public cmd { + ref m_dl_ctx; + unsigned m_arg_idx; + mutable unsigned m_query_arg_idx; + symbol m_rel_name; + scoped_ptr m_domain; + svector m_kinds; + + void ensure_domain(cmd_context& ctx) { + if (!m_domain) m_domain = alloc(sort_ref_vector, ctx.m()); + } +public: + dl_declare_rel_cmd(dl_context * dl_ctx): + cmd("declare-rel"), + m_dl_ctx(dl_ctx), + m_domain(0) {} + + virtual char const * get_usage() const { return " ( ...) *"; } + virtual char const * get_descr(cmd_context & ctx) const { return "declare new relation"; } + virtual unsigned get_arity() const { return VAR_ARITY; } + + virtual void prepare(cmd_context & ctx) { + m_arg_idx = 0; + m_query_arg_idx = 0; + m_domain = 0; + m_kinds.reset(); + } + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { + switch(m_query_arg_idx++) { + case 0: return CPK_SYMBOL; // relation name + case 1: return CPK_SORT_LIST; // arguments + default: return CPK_SYMBOL; // optional representation specification + } + } + virtual void set_next_arg(cmd_context & ctx, unsigned num, sort * const * slist) { + ensure_domain(ctx); + m_domain->append(num, slist); + m_arg_idx++; + } + virtual void set_next_arg(cmd_context & ctx, symbol const & s) { + if(m_arg_idx==0) { + m_rel_name = s; + } + else { + SASSERT(m_arg_idx>1); + m_kinds.push_back(s); + } + m_arg_idx++; + } + virtual void execute(cmd_context & ctx) { + if(m_arg_idx<2) { + throw cmd_exception("at least 2 arguments expected"); + } + ensure_domain(ctx); + ast_manager& m = ctx.m(); + + func_decl_ref pred( + m.mk_func_decl(m_rel_name, m_domain->size(), m_domain->c_ptr(), m.mk_bool_sort()), m); + ctx.insert(pred); + datalog::context& dctx = m_dl_ctx->get_dl_context(); + dctx.register_predicate(pred, false); + if(!m_kinds.empty()) { + dctx.set_predicate_representation(pred, m_kinds.size(), m_kinds.c_ptr()); + } + m_domain = 0; + } + +}; + +class dl_declare_var_cmd : public cmd { + unsigned m_arg_idx; + symbol m_var_name; + sort* m_var_sort; + ref m_dl_ctx; +public: + dl_declare_var_cmd(dl_context* dl_ctx): + cmd("declare-var"), + m_arg_idx(0), + m_dl_ctx(dl_ctx) + {} + + virtual char const * get_usage() const { return " "; } + virtual char const * get_descr(cmd_context & ctx) const { return "declare constant as variable"; } + virtual unsigned get_arity() const { return 2; } + + virtual void prepare(cmd_context & ctx) { + m_arg_idx = 0; + } + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { + SASSERT(m_arg_idx <= 1); + if (m_arg_idx == 0) { + return CPK_SYMBOL; + } + return CPK_SORT; + } + + virtual void set_next_arg(cmd_context & ctx, sort* s) { + m_var_sort = s; + ++m_arg_idx; + } + + virtual void set_next_arg(cmd_context & ctx, symbol const & s) { + m_var_name = s; + ++m_arg_idx; + } + + virtual void execute(cmd_context & ctx) { + ast_manager& m = ctx.m(); + func_decl_ref var(m.mk_func_decl(m_var_name, 0, static_cast(0), m_var_sort), m); + ctx.insert(var); + m_dl_ctx->get_dl_context().register_variable(var); + } + +}; + +class dl_push_cmd : public cmd { + ref m_ctx; +public: + dl_push_cmd(dl_context* ctx): + cmd("fixedpoint-push"), + m_ctx(ctx) + {} + + virtual char const * get_usage() const { return ""; } + virtual char const * get_descr(cmd_context & ctx) const { return "push context on the fixedpoint engine"; } + + virtual void execute(cmd_context& ctx) { + m_ctx->get_dl_context().push(); + } +}; + +class dl_pop_cmd : public cmd { + ref m_ctx; +public: + dl_pop_cmd(dl_context* ctx): + cmd("fixedpoint-pop"), + m_ctx(ctx) + {} + + virtual char const * get_usage() const { return ""; } + virtual char const * get_descr(cmd_context & ctx) const { return "pop context on the fixedpoint engine"; } + + virtual void execute(cmd_context& ctx) { + m_ctx->get_dl_context().pop(); + } +}; + + + +void install_dl_cmds(cmd_context & ctx) { + dl_context * dl_ctx = alloc(dl_context, ctx); + ctx.insert(alloc(dl_rule_cmd, dl_ctx)); + ctx.insert(alloc(dl_query_cmd, dl_ctx)); + ctx.insert(alloc(dl_declare_rel_cmd, dl_ctx)); + ctx.insert(alloc(dl_declare_var_cmd, dl_ctx)); + PRIVATE_PARAMS(ctx.insert(alloc(dl_push_cmd, dl_ctx));); // not exposed to keep command-extensions simple. + PRIVATE_PARAMS(ctx.insert(alloc(dl_pop_cmd, dl_ctx));); +} + diff --git a/lib/dl_cmds.h b/lib/dl_cmds.h new file mode 100644 index 000000000..72eb2dee2 --- /dev/null +++ b/lib/dl_cmds.h @@ -0,0 +1,47 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + dl_cmds.h + +Abstract: + Datalog commands for SMT2 front-end. + +Author: + + Leonardo (leonardo) 2011-03-28 + +Notes: + +--*/ +#ifndef _DL_CMDS_H_ +#define _DL_CMDS_H_ + +class cmd; +class cmd_context; + +void install_dl_cmds(cmd_context & ctx); + +namespace datalog { + + class context; + + /** + Create a command for declaring relations which is connected to + a particular datalog context. + + Caller must ensure the returned object is deallocated (e.g. by passing it to a cmd_context). + */ + cmd * mk_declare_rel_cmd(context& dctx); + + /** + Declare a constant as a universal/existential variable. + It is implicitly existentially or universally quantified + by the rules. + */ + cmd * mk_declare_var_cmd(context& dctx); + +} + +#endif diff --git a/lib/dl_compiler.cpp b/lib/dl_compiler.cpp new file mode 100644 index 000000000..2d677ed7f --- /dev/null +++ b/lib/dl_compiler.cpp @@ -0,0 +1,1190 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_compiler.cpp + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-09-14. + +Revision History: + +--*/ + + +#include +#include"ref_vector.h" +#include"dl_context.h" +#include"dl_rule.h" +#include"dl_util.h" +#include"dl_compiler.h" +#include"ast_pp.h" +#include"ast_smt2_pp.h" + + +namespace datalog { + + void compiler::reset() { + m_pred_regs.reset(); + m_new_reg = 0; + } + + void compiler::ensure_predicate_loaded(func_decl * pred, instruction_block & acc) { + pred2idx::entry * e = m_pred_regs.insert_if_not_there2(pred, UINT_MAX); + if(e->get_data().m_value!=UINT_MAX) { + //predicate is already loaded + return; + } + relation_signature sig; + m_context.get_rmanager().from_predicate(pred, sig); + reg_idx reg = get_fresh_register(sig); + e->get_data().m_value=reg; + + acc.push_back(instruction::mk_load(m_context.get_manager(), pred, reg)); + } + + void compiler::make_join(reg_idx t1, reg_idx t2, const variable_intersection & vars, reg_idx & result, + instruction_block & acc) { + relation_signature res_sig; + relation_signature::from_join(m_reg_signatures[t1], m_reg_signatures[t2], vars.size(), + vars.get_cols1(), vars.get_cols2(), res_sig); + result = get_fresh_register(res_sig); + acc.push_back(instruction::mk_join(t1, t2, vars.size(), vars.get_cols1(), vars.get_cols2(), result)); + } + + void compiler::make_join_project(reg_idx t1, reg_idx t2, const variable_intersection & vars, + const unsigned_vector & removed_cols, reg_idx & result, instruction_block & acc) { + relation_signature aux_sig; + relation_signature::from_join(m_reg_signatures[t1], m_reg_signatures[t2], vars.size(), + vars.get_cols1(), vars.get_cols2(), aux_sig); + relation_signature res_sig; + relation_signature::from_project(aux_sig, removed_cols.size(), removed_cols.c_ptr(), + res_sig); + + result = get_fresh_register(res_sig); + acc.push_back(instruction::mk_join_project(t1, t2, vars.size(), vars.get_cols1(), + vars.get_cols2(), removed_cols.size(), removed_cols.c_ptr(), result)); + } + + void compiler::make_select_equal_and_project(reg_idx src, const relation_element & val, unsigned col, + reg_idx & result, instruction_block & acc) { + relation_signature res_sig; + relation_signature::from_project(m_reg_signatures[src], 1, &col, res_sig); + result = get_fresh_register(res_sig); + acc.push_back(instruction::mk_select_equal_and_project(m_context.get_manager(), + src, val, col, result)); + } + + void compiler::make_clone(reg_idx src, reg_idx & result, instruction_block & acc) { + relation_signature sig = m_reg_signatures[src]; + result = get_fresh_register(sig); + acc.push_back(instruction::mk_clone(src, result)); + } + + void compiler::make_union(reg_idx src, reg_idx tgt, reg_idx delta, bool widening, + instruction_block & acc) { + SASSERT(m_reg_signatures[src]==m_reg_signatures[tgt]); + SASSERT(delta==execution_context::void_register || m_reg_signatures[src]==m_reg_signatures[delta]); + + if(widening) { + acc.push_back(instruction::mk_widen(src, tgt, delta)); + } + else { + acc.push_back(instruction::mk_union(src, tgt, delta)); + } + } + + void compiler::make_projection(reg_idx src, unsigned col_cnt, const unsigned * removed_cols, + reg_idx & result, instruction_block & acc) { + SASSERT(col_cnt>0); + + relation_signature res_sig; + relation_signature::from_project(m_reg_signatures[src], col_cnt, removed_cols, res_sig); + result = get_fresh_register(res_sig); + acc.push_back(instruction::mk_projection(src, col_cnt, removed_cols, result)); + } + + compiler::reg_idx compiler::get_fresh_register(const relation_signature & sig) { + //since we might be resizing the m_reg_signatures vector, the argument must not point inside it + SASSERT((&sig>=m_reg_signatures.end()) || (&sigto_formula(e); + verbose_stream() << "Compiling unsafe rule column " << col_idx << "\n" + << mk_ismt2_pp(e, m_context.get_manager()) << "\n"; + }); + reg_idx total_table = get_single_column_register(s); + relation_signature sig; + sig.push_back(s); + acc.push_back(instruction::mk_total(sig, pred, total_table)); + if(src == execution_context::void_register) { + result = total_table; + } + else { + variable_intersection empty_vars(m_context.get_manager()); + make_join(src, total_table, empty_vars, result, acc); + make_dealloc_non_void(total_table, acc); + } + } + + void compiler::make_full_relation(func_decl* pred, const relation_signature & sig, reg_idx & result, + instruction_block & acc) { + TRACE("dl", tout << "Adding unbound column " << mk_pp(pred, m_context.get_manager()) << "\n";); + result = get_fresh_register(sig); + acc.push_back(instruction::mk_total(sig, pred, result)); + } + + + void compiler::make_duplicate_column(reg_idx src, unsigned col, reg_idx & result, + instruction_block & acc) { + + relation_signature & src_sig = m_reg_signatures[src]; + reg_idx single_col_reg; + unsigned src_col_cnt = src_sig.size(); + if(src_col_cnt==1) { + single_col_reg = src; + } + else { + unsigned_vector removed_cols; + for(unsigned i=0; i & acis0, + reg_idx & result, + instruction_block & acc) { + + TRACE("dl", tout << mk_pp(head_pred, m_context.get_manager()) << "\n";); + + unsigned col_cnt = acis0.size(); + reg_idx curr = src; + + relation_signature empty_signature; + + relation_signature * curr_sig; + if(curr!=execution_context::void_register) { + curr_sig = & m_reg_signatures[curr]; + } + else { + curr_sig = & empty_signature; + } + unsigned src_col_cnt=curr_sig->size(); + + svector acis(acis0); + int2int handled_unbound; + + //first remove unused source columns + int_set referenced_src_cols; + for(unsigned i=0; isize(); + if(acis[i].kind==ACK_CONSTANT) { + make_add_constant_column(head_pred, curr, acis[i].domain, acis[i].constant, new_curr, acc); + } + else { + SASSERT(acis[i].kind==ACK_UNBOUND_VAR); + make_add_unbound_column(compiled_rule, i, head_pred, curr, acis[i].domain, new_curr, acc); + handled_unbound.insert(acis[i].var_index,bound_column_index); + } + make_dealloc_non_void(curr, acc); + curr=new_curr; + curr_sig = & m_reg_signatures[curr]; + SASSERT(bound_column_index==curr_sig->size()-1); + } + SASSERT((*curr_sig)[bound_column_index]==acis[i].domain); + acis[i].kind=ACK_BOUND_VAR; + acis[i].source_column=bound_column_index; + } + + //duplicate needed source columns + int_set used_cols; + for(unsigned i=0; isize()-1; + SASSERT((*curr_sig)[bound_column_index]==acis[i].domain); + acis[i].source_column=bound_column_index; + } + + //reorder source columns to match target + SASSERT(curr_sig->size()==col_cnt); //now the intermediate table is a permutation + for(unsigned i=0; i=i); //columns below i are already reordered + SASSERT(nextget_num_args(); + for(unsigned i = 0; iget_arg(i); + if(!is_var(e) || globals.get(to_var(e)->get_idx())!=0) { + continue; + } + res.push_back(i+ofs); + } + } + + void compiler::get_local_indexes_for_projection(rule * r, unsigned_vector & res) { + SASSERT(r->get_positive_tail_size()==2); + ast_manager & m = m_context.get_manager(); + var_counter counter; + counter.count_vars(m, r); + app * t1 = r->get_tail(0); + app * t2 = r->get_tail(1); + counter.count_vars(m, t1, -1); + counter.count_vars(m, t2, -1); + get_local_indexes_for_projection(t1, counter, 0, res); + get_local_indexes_for_projection(t2, counter, t1->get_num_args(), res); + } + + void compiler::compile_rule_evaluation_run(rule * r, reg_idx head_reg, const reg_idx * tail_regs, + reg_idx delta_reg, bool use_widening, instruction_block & acc) { + + m_instruction_observer.start_rule(r); + + const app * h = r->get_head(); + unsigned head_len = h->get_num_args(); + func_decl * head_pred = h->get_decl(); + + TRACE("dl", r->display(m_context, tout); ); + + unsigned pt_len = r->get_positive_tail_size(); + SASSERT(pt_len<=2); //we require rules to be processed by the mk_simple_joins rule transformer plugin + + reg_idx single_res; + ptr_vector single_res_expr; + + //used to save on filter_identical instructions where the check is already done + //by the join operation + unsigned second_tail_arg_ofs; + + if(pt_len == 2) { + reg_idx t1_reg=tail_regs[0]; + reg_idx t2_reg=tail_regs[1]; + app * a1 = r->get_tail(0); + app * a2 = r->get_tail(1); + SASSERT(m_reg_signatures[t1_reg].size()==a1->get_num_args()); + SASSERT(m_reg_signatures[t2_reg].size()==a2->get_num_args()); + + variable_intersection a1a2(m_context.get_manager()); + a1a2.populate(a1,a2); + + unsigned_vector removed_cols; + get_local_indexes_for_projection(r, removed_cols); + + if(removed_cols.empty()) { + make_join(t1_reg, t2_reg, a1a2, single_res, acc); + } + else { + make_join_project(t1_reg, t2_reg, a1a2, removed_cols, single_res, acc); + } + + unsigned rem_index = 0; + unsigned rem_sz = removed_cols.size(); + unsigned a1len=a1->get_num_args(); + for(unsigned i=0; i=i); + if(rem_indexget_arg(i)); + } + second_tail_arg_ofs = single_res_expr.size(); + unsigned a2len=a2->get_num_args(); + for(unsigned i=0; i=i+a1len); + if(rem_indexget_arg(i)); + } + SASSERT(rem_index==rem_sz); + } + else if(pt_len==1) { + reg_idx t_reg=tail_regs[0]; + app * a = r->get_tail(0); + SASSERT(m_reg_signatures[t_reg].size()==a->get_num_args()); + + single_res = t_reg; + + unsigned n=a->get_num_args(); + for(unsigned i=0; iget_arg(i); + if(is_app(arg)) { + app * c = to_app(arg); //argument is a constant + SASSERT(c->get_num_args()==0); + SASSERT(m_context.get_decl_util().is_numeral_ext(arg)); + reg_idx new_reg; + make_select_equal_and_project(single_res, c, single_res_expr.size(), new_reg, acc); + if(single_res!=t_reg) { + //since single_res is a local register, we deallocate it + make_dealloc_non_void(single_res, acc); + } + single_res = new_reg; + } + else { + SASSERT(is_var(arg)); + single_res_expr.push_back(arg); + } + } + if(single_res==t_reg) { + //we may be modifying the register later, so we need a local copy + make_clone(t_reg, single_res, acc); + } + + } + else { + SASSERT(pt_len==0); + + //single_res register should never be used in this case + single_res=execution_context::void_register; + } + + add_unbound_columns_for_negation(r, head_pred, single_res, single_res_expr, acc); + + int2ints var_indexes; + + reg_idx filtered_res = single_res; + + { + //enforce equality to constants + unsigned srlen=single_res_expr.size(); + SASSERT((single_res==execution_context::void_register) ? (srlen==0) : (srlen==m_reg_signatures[single_res].size())); + for(unsigned i=0; iget_idx(); + int2ints::entry * e = var_indexes.insert_if_not_there2(var_num, unsigned_vector()); + e->get_data().m_value.push_back(i); + } + } + } + + //enforce equality of columns + int2ints::iterator vit=var_indexes.begin(); + int2ints::iterator vend=var_indexes.end(); + for(; vit!=vend; ++vit) { + int2ints::key_data & k = *vit; + unsigned_vector & indexes = k.m_value; + if(indexes.size()==1) { + continue; + } + SASSERT(indexes.size()>1); + if(pt_len==2 && indexes[0]=second_tail_arg_ofs) { + //If variable appears in multiple tails, the identicity will already be enforced by join. + //(If behavior the join changes so that it is not enforced anymore, remove this + //condition!) + continue; + } + acc.push_back(instruction::mk_filter_identical(filtered_res, indexes.size(), indexes.c_ptr())); + } + + //enforce negative predicates + unsigned ut_len=r->get_uninterpreted_tail_size(); + for(unsigned i=pt_len; iget_tail(i); + func_decl * neg_pred = neg_tail->get_decl(); + variable_intersection neg_intersection(m_context.get_manager()); + neg_intersection.populate(single_res_expr, neg_tail); + unsigned_vector t_cols(neg_intersection.size(), neg_intersection.get_cols1()); + unsigned_vector neg_cols(neg_intersection.size(), neg_intersection.get_cols2()); + + unsigned neg_len = neg_tail->get_num_args(); + for(unsigned i=0; iget_arg(i); + if(is_var(e)) { + continue; + } + SASSERT(is_app(e)); + relation_sort arg_sort; + m_context.get_rmanager().from_predicate(neg_pred, i, arg_sort); + reg_idx new_reg; + make_add_constant_column(head_pred, filtered_res, arg_sort, to_app(e), new_reg, acc); + + make_dealloc_non_void(filtered_res, acc); + filtered_res = new_reg; // here filtered_res value gets changed !! + + t_cols.push_back(single_res_expr.size()); + neg_cols.push_back(i); + single_res_expr.push_back(e); + } + SASSERT(t_cols.size()==neg_cols.size()); + + reg_idx neg_reg; + TRUSTME( m_pred_regs.find(neg_pred, neg_reg) ); + acc.push_back(instruction::mk_filter_by_negation(filtered_res, neg_reg, t_cols.size(), + t_cols.c_ptr(), neg_cols.c_ptr())); + } + + //enforce interpreted tail predicates + unsigned ft_len=r->get_tail_size(); //full tail + for(unsigned tail_index=ut_len; tail_indexget_tail(tail_index); + var_idx_set t_vars; + ast_manager & m = m_context.get_manager(); + collect_vars(m, t, t_vars); + + if(t_vars.empty()) { + expr_ref simplified(m); + m_context.get_rewriter()(t, simplified); + if(m.is_true(simplified)) { + //this tail element is always true + continue; + } + //the tail of this rule is never satisfied + SASSERT(m.is_false(simplified)); + goto finish; + } + + //determine binding size + unsigned max_var=0; + var_idx_set::iterator vit = t_vars.begin(); + var_idx_set::iterator vend = t_vars.end(); + for(; vit!=vend; ++vit) { + unsigned v = *vit; + if(v>max_var) { max_var = v; } + } + + //create binding + expr_ref_vector binding(m); + binding.resize(max_var+1); + vit = t_vars.begin(); + for(; vit!=vend; ++vit) { + unsigned v = *vit; + int2ints::entry * e = var_indexes.find_core(v); + if(!e) { + //we have an unbound variable, so we add an unbound column for it + relation_sort unbound_sort = 0; + + for(unsigned hindex = 0; hindexget_arg(hindex); + if(!is_var(harg) || to_var(harg)->get_idx()!=v) { + continue; + } + unbound_sort = to_var(harg)->get_sort(); + } + if(!unbound_sort) { + // the variable in the interpreted tail is neither bound in the + // uninterpreted tail nor present in the head + std::stringstream sstm; + sstm << "rule with unbound variable #" << v << " in interpreted tail: "; + r->display(m_context, sstm); + throw default_exception(sstm.str()); + } + + reg_idx new_reg; + TRACE("dl", tout << mk_pp(head_pred, m_context.get_manager()) << "\n";); + make_add_unbound_column(r, 0, head_pred, filtered_res, unbound_sort, new_reg, acc); + + make_dealloc_non_void(filtered_res, acc); + filtered_res = new_reg; // here filtered_res value gets changed !! + + unsigned unbound_column_index = single_res_expr.size(); + single_res_expr.push_back(m.mk_var(v, unbound_sort)); + + e = var_indexes.insert_if_not_there2(v, unsigned_vector()); + e->get_data().m_value.push_back(unbound_column_index); + } + unsigned src_col=e->get_data().m_value.back(); + relation_sort var_sort = m_reg_signatures[filtered_res][src_col]; + binding[max_var-v]=m.mk_var(src_col, var_sort); + } + + + expr_ref renamed(m); + m_context.get_var_subst()(t, binding.size(), binding.c_ptr(), renamed); + app_ref app_renamed(to_app(renamed), m); + acc.push_back(instruction::mk_filter_interpreted(filtered_res, app_renamed)); + } + + { + //put together the columns of head relation + relation_signature & head_sig = m_reg_signatures[head_reg]; + svector head_acis; + unsigned_vector head_src_cols; + for(unsigned i=0; iget_arg(i); + if(is_var(exp)) { + unsigned var_num=to_var(exp)->get_idx(); + int2ints::entry * e = var_indexes.find_core(var_num); + if(e) { + unsigned_vector & binding_indexes = e->get_data().m_value; + aci.kind=ACK_BOUND_VAR; + aci.source_column=binding_indexes.back(); + SASSERT(aci.source_column1) { + //if possible, we do not want multiple head columns + //point to a single column in the intermediate table, + //since then we would have to duplicate the column + //(and remove columns we did not point to at all) + binding_indexes.pop_back(); + } + } + else { + aci.kind=ACK_UNBOUND_VAR; + aci.var_index=var_num; + } + } + else { + SASSERT(is_app(exp)); + SASSERT(m_context.get_decl_util().is_numeral_ext(exp)); + aci.kind=ACK_CONSTANT; + aci.constant=to_app(exp); + } + head_acis.push_back(aci); + } + SASSERT(head_acis.size()==head_len); + + reg_idx new_head_reg; + make_assembling_code(r, head_pred, filtered_res, head_acis, new_head_reg, acc); + + //update the head relation + make_union(new_head_reg, head_reg, delta_reg, use_widening, acc); + } + + finish: + m_instruction_observer.finish_rule(); + } + + void compiler::add_unbound_columns_for_negation(rule* r, func_decl* pred, reg_idx& single_res, ptr_vector& single_res_expr, + instruction_block & acc) { + uint_set pos_vars; + u_map neg_vars; + ast_manager& m = m_context.get_manager(); + unsigned pt_len = r->get_positive_tail_size(); + unsigned ut_len = r->get_uninterpreted_tail_size(); + if (pt_len == ut_len || pt_len == 0) { + return; + } + // populate negative variables: + for (unsigned i = pt_len; i < ut_len; ++i) { + app * neg_tail = r->get_tail(i); + unsigned neg_len = neg_tail->get_num_args(); + for (unsigned j = 0; j < neg_len; ++j) { + expr * e = neg_tail->get_arg(j); + if (is_var(e)) { + neg_vars.insert(to_var(e)->get_idx(), e); + } + } + } + // populate positive variables: + for (unsigned i = 0; i < single_res_expr.size(); ++i) { + expr* e = single_res_expr[i]; + if (is_var(e)) { + pos_vars.insert(to_var(e)->get_idx()); + } + } + // add negative variables that are not in positive: + u_map::iterator it = neg_vars.begin(), end = neg_vars.end(); + for (; it != end; ++it) { + unsigned v = it->m_key; + expr* e = it->m_value; + if (!pos_vars.contains(v)) { + single_res_expr.push_back(e); + make_add_unbound_column(r, v, pred, single_res, m.get_sort(e), single_res, acc); + TRACE("dl", tout << "Adding unbound column: " << mk_pp(e, m) << "\n";); + } + } + } + + void compiler::compile_rule_evaluation(rule * r, const pred2idx * input_deltas, + reg_idx output_delta, bool use_widening, instruction_block & acc) { + typedef std::pair tail_delta_info; //(delta register, tail index) + typedef svector tail_delta_infos; + + unsigned rule_len = r->get_uninterpreted_tail_size(); + reg_idx head_reg; + TRUSTME( m_pred_regs.find(r->get_head()->get_decl(), head_reg) ); + + svector tail_regs; + tail_delta_infos tail_deltas; + for(unsigned j=0;jget_tail(j)->get_decl(); + reg_idx tail_reg; + TRUSTME( m_pred_regs.find(tail_pred, tail_reg) ); + tail_regs.push_back(tail_reg); + + if(input_deltas && !all_or_nothing_deltas()) { + reg_idx tail_delta_idx; + if(input_deltas->find(tail_pred, tail_delta_idx)) { + tail_deltas.push_back(tail_delta_info(tail_delta_idx, j)); + } + } + } + + if(!input_deltas || all_or_nothing_deltas()) { + compile_rule_evaluation_run(r, head_reg, tail_regs.c_ptr(), output_delta, use_widening, acc); + } + else { + tail_delta_infos::iterator tdit = tail_deltas.begin(); + tail_delta_infos::iterator tdend = tail_deltas.end(); + for(; tdit!=tdend; ++tdit) { + tail_delta_info tdinfo = *tdit; + flet flet_tail_reg(tail_regs[tdinfo.second], tdinfo.first); + compile_rule_evaluation_run(r, head_reg, tail_regs.c_ptr(), output_delta, use_widening, acc); + } + } + } + + class cycle_breaker + { + typedef func_decl * T; + typedef rule_dependencies::item_set item_set; //set of T + + rule_dependencies & m_deps; + item_set & m_removed; + svector m_stack; + ast_mark m_stack_content; + ast_mark m_visited; + + void traverse(T v) { + SASSERT(!m_stack_content.is_marked(v)); + if(m_visited.is_marked(v) || m_removed.contains(v)) { + return; + } + + m_stack.push_back(v); + m_stack_content.mark(v, true); + m_visited.mark(v, true); + + const item_set & deps = m_deps.get_deps(v); + item_set::iterator it = deps.begin(); + item_set::iterator end = deps.end(); + for(; it!=end; ++it) { + T d = *it; + if(m_stack_content.is_marked(d)) { + //TODO: find the best vertex to remove in the cycle + m_removed.insert(v); + break; + } + traverse(d); + } + SASSERT(m_stack.back()==v); + + m_stack.pop_back(); + m_stack_content.mark(v, false); + } + public: + cycle_breaker(rule_dependencies & deps, item_set & removed) + : m_deps(deps), m_removed(removed) { SASSERT(removed.empty()); } + + void operator()() { + rule_dependencies::iterator it = m_deps.begin(); + rule_dependencies::iterator end = m_deps.end(); + for(; it!=end; ++it) { + T v = it->m_key; + traverse(v); + } + m_deps.remove(m_removed); + } + }; + + void compiler::detect_chains(const func_decl_set & preds, func_decl_vector & ordered_preds, + func_decl_set & global_deltas) { + typedef obj_map pred2pred; + + SASSERT(ordered_preds.empty()); + SASSERT(global_deltas.empty()); + + rule_dependencies deps(m_rule_set.get_dependencies()); + deps.restrict(preds); + cycle_breaker(deps, global_deltas)(); + TRUSTME( deps.sort_deps(ordered_preds) ); + + //the predicates that were removed to get acyclic induced subgraph are put last + //so that all their local input deltas are already populated + func_decl_set::iterator gdit = global_deltas.begin(); + func_decl_set::iterator gend = global_deltas.end(); + for(; gdit!=gend; ++gdit) { + ordered_preds.push_back(*gdit); + } + } + + void compiler::compile_preds(const func_decl_vector & head_preds, const func_decl_set & widened_preds, + const pred2idx * input_deltas, const pred2idx & output_deltas, instruction_block & acc) { + func_decl_vector::const_iterator hpit = head_preds.begin(); + func_decl_vector::const_iterator hpend = head_preds.end(); + for(; hpit!=hpend; ++hpit) { + func_decl * head_pred = *hpit; + + bool widen_predicate_in_loop = widened_preds.contains(head_pred); + + reg_idx d_head_reg; //output delta for the initial rule execution + if(!output_deltas.find(head_pred, d_head_reg)) { + d_head_reg = execution_context::void_register; + } + + const rule_vector & pred_rules = m_rule_set.get_predicate_rules(head_pred); + rule_vector::const_iterator rit = pred_rules.begin(); + rule_vector::const_iterator rend = pred_rules.end(); + for(; rit!=rend; ++rit) { + rule * r = *rit; + SASSERT(head_pred==r->get_head()->get_decl()); + + compile_rule_evaluation(r, input_deltas, d_head_reg, widen_predicate_in_loop, acc); + } + } + } + + void compiler::make_inloop_delta_transition(const pred2idx & global_head_deltas, + const pred2idx & global_tail_deltas, const pred2idx & local_deltas, instruction_block & acc) { + //move global head deltas into tail ones + pred2idx::iterator gdit = global_head_deltas.begin(); + pred2idx::iterator gend = global_head_deltas.end(); + for(; gdit!=gend; ++gdit) { + func_decl * pred = gdit->m_key; + reg_idx head_reg = gdit->m_value; + reg_idx tail_reg; + TRUSTME( global_tail_deltas.find(pred, tail_reg) ); + acc.push_back(instruction::mk_move(head_reg, tail_reg)); + } + //empty local deltas + pred2idx::iterator lit = local_deltas.begin(); + pred2idx::iterator lend = local_deltas.end(); + for(; lit!=lend; ++lit) { + acc.push_back(instruction::mk_dealloc(lit->m_value)); + } + } + + void compiler::compile_loop(const func_decl_vector & head_preds, const func_decl_set & widened_preds, + const pred2idx & global_head_deltas, const pred2idx & global_tail_deltas, + const pred2idx & local_deltas, instruction_block & acc) { + instruction_block * loop_body = alloc(instruction_block); + loop_body->set_observer(&m_instruction_observer); + + pred2idx all_head_deltas(global_head_deltas); + unite_disjoint_maps(all_head_deltas, local_deltas); + pred2idx all_tail_deltas(global_tail_deltas); + unite_disjoint_maps(all_tail_deltas, local_deltas); + + //generate code for the iterative fixpoint search + //The order in which we iterate the preds_vector matters, since rules can depend on + //deltas generated earlier in the same iteration. + compile_preds(head_preds, widened_preds, &all_tail_deltas, all_head_deltas, *loop_body); + + svector loop_control_regs; //loop is controlled by global src regs + collect_map_range(loop_control_regs, global_tail_deltas); + //move target deltas into source deltas at the end of the loop + //and clear local deltas + make_inloop_delta_transition(global_head_deltas, global_tail_deltas, local_deltas, *loop_body); + + loop_body->set_observer(0); + acc.push_back(instruction::mk_while_loop(loop_control_regs.size(), + loop_control_regs.c_ptr(),loop_body)); + } + + void compiler::compile_dependent_rules(const func_decl_set & head_preds, + const pred2idx * input_deltas, const pred2idx & output_deltas, + bool add_saturation_marks, instruction_block & acc) { + + if(!output_deltas.empty()) { + func_decl_set::iterator hpit = head_preds.begin(); + func_decl_set::iterator hpend = head_preds.end(); + for(; hpit!=hpend; ++hpit) { + if(output_deltas.contains(*hpit)) { + //we do not support retrieving deltas for rules that are inside a recursive + //stratum, since we would have to maintain this 'global' delta through the loop + //iterations + NOT_IMPLEMENTED_YET(); + } + } + } + + func_decl_vector preds_vector; + func_decl_set global_deltas; + + detect_chains(head_preds, preds_vector, global_deltas); + + func_decl_set local_deltas(head_preds); + set_difference(local_deltas, global_deltas); + + pred2idx d_global_src; //these deltas serve as sources of tuples for rule evaluation inside the loop + get_fresh_registers(global_deltas, d_global_src); + pred2idx d_global_tgt; //these deltas are targets for new tuples in rule evaluation inside the loop + get_fresh_registers(global_deltas, d_global_tgt); + pred2idx d_local; + get_fresh_registers(local_deltas, d_local); + + pred2idx d_all_src(d_global_src); //src together with local deltas + unite_disjoint_maps(d_all_src, d_local); + pred2idx d_all_tgt(d_global_tgt); //tgt together with local deltas + unite_disjoint_maps(d_all_tgt, d_local); + + + func_decl_set empty_func_decl_set; + + //generate code for the initial run + compile_preds(preds_vector, empty_func_decl_set, input_deltas, d_global_src, acc); + + if(!compile_with_widening()) { + compile_loop(preds_vector, empty_func_decl_set, d_global_tgt, d_global_src, + d_local, acc); + } + else { + //do the part where we zero the global predicates and run the loop saturation loop again + if(global_deltas.size()0); + if(preds.size()>1) { + return false; + } + func_decl * head_pred = *preds.begin(); + const rule_vector & rules = m_rule_set.get_predicate_rules(head_pred); + rule_vector::const_iterator it = rules.begin(); + rule_vector::const_iterator end = rules.end(); + for(; it!=end; ++it) { + //it is sufficient to check just for presence of the first head predicate, + //since if the rules are recursive and their heads are strongly connected by dependence, + //this predicate must appear in some tail + if((*it)->is_in_tail(head_pred)) { + return false; + } + } + return true; + } + + void compiler::compile_nonrecursive_stratum(const func_decl_set & preds, + const pred2idx * input_deltas, const pred2idx & output_deltas, + bool add_saturation_marks, instruction_block & acc) { + //non-recursive stratum always has just one head predicate + SASSERT(preds.size()==1); + SASSERT(is_nonrecursive_stratum(preds)); + func_decl * head_pred = *preds.begin(); + const rule_vector & rules = m_rule_set.get_predicate_rules(head_pred); + + reg_idx output_delta; + if(!output_deltas.find(head_pred, output_delta)) { + output_delta = execution_context::void_register; + } + + rule_vector::const_iterator it = rules.begin(); + rule_vector::const_iterator end = rules.end(); + for(; it!=end; ++it) { + rule * r = *it; + SASSERT(r->get_head()->get_decl()==head_pred); + + compile_rule_evaluation(r, input_deltas, output_delta, false, acc); + } + + if(add_saturation_marks) { + //now the predicate is saturated, so we may mark it as such + acc.push_back(instruction::mk_mark_saturated(m_context.get_manager(), head_pred)); + } + } + + bool compiler::all_saturated(const func_decl_set & preds) const { + func_decl_set::iterator fdit = preds.begin(); + func_decl_set::iterator fdend = preds.end(); + for(; fdit!=fdend; ++fdit) { + if(!m_context.get_rmanager().is_saturated(*fdit)) { + return false; + } + } + return true; + } + + void compiler::compile_strats(const rule_stratifier & stratifier, + const pred2idx * input_deltas, const pred2idx & output_deltas, + bool add_saturation_marks, instruction_block & acc) { + rule_set::pred_set_vector strats = stratifier.get_strats(); + rule_set::pred_set_vector::const_iterator sit = strats.begin(); + rule_set::pred_set_vector::const_iterator send = strats.end(); + for(; sit!=send; ++sit) { + func_decl_set & strat_preds = **sit; + + if(all_saturated(strat_preds)) { + //all predicates in stratum are saturated, so no need to compile rules for them + continue; + } + + TRACE("dl", + tout << "Stratum: "; + func_decl_set::iterator pit = strat_preds.begin(); + func_decl_set::iterator pend = strat_preds.end(); + for(; pit!=pend; ++pit) { + func_decl * pred = *pit; + tout << pred->get_name() << " "; + } + tout << "\n"; + ); + + if(is_nonrecursive_stratum(strat_preds)) { + //this stratum contains just a single non-recursive rule + compile_nonrecursive_stratum(strat_preds, input_deltas, output_deltas, add_saturation_marks, acc); + } + else { + compile_dependent_rules(strat_preds, input_deltas, output_deltas, + add_saturation_marks, acc); + } + } + } + + void compiler::do_compilation(instruction_block & execution_code, + instruction_block & termination_code) { + + unsigned rule_cnt=m_rule_set.get_num_rules(); + if(rule_cnt==0) { + return; + } + + instruction_block & acc = execution_code; + acc.set_observer(&m_instruction_observer); + + + //load predicate data + for(unsigned i=0;iget_head()->get_decl(), acc); + + unsigned rule_len = r->get_uninterpreted_tail_size(); + for(unsigned j=0;jget_tail(j)->get_decl(), acc); + } + } + + pred2idx empty_pred2idx_map; + + compile_strats(m_rule_set.get_stratifier(), static_cast(0), + empty_pred2idx_map, true, execution_code); + + + + //store predicate data + pred2idx::iterator pit = m_pred_regs.begin(); + pred2idx::iterator pend = m_pred_regs.end(); + for(; pit!=pend; ++pit) { + pred2idx::key_data & e = *pit; + func_decl * pred = e.m_key; + reg_idx reg = e.m_value; + termination_code.push_back(instruction::mk_store(m_context.get_manager(), pred, reg)); + } + + acc.set_observer(0); + + TRACE("dl", execution_code.display(m_context, tout);); + } + + +} + diff --git a/lib/dl_compiler.h b/lib/dl_compiler.h new file mode 100644 index 000000000..23e3b309c --- /dev/null +++ b/lib/dl_compiler.h @@ -0,0 +1,272 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_compiler.h + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-09-14. + +Revision History: + +--*/ +#ifndef _DL_COMPILER_H_ +#define _DL_COMPILER_H_ + +#include +#include +#include + +#include "ast.h" +#include "hashtable.h" +#include "map.h" +#include "obj_pair_hashtable.h" +#include "ref_vector.h" +#include "vector.h" + +#include "dl_base.h" +#include "dl_context.h" +#include "dl_instruction.h" + +namespace datalog { + + class compiler { + typedef instruction::reg_idx reg_idx; + typedef hashtable int_set; + typedef u_map int2int; + typedef u_map int2ints; + typedef map,ptr_eq > pred2idx; + typedef unsigned_vector var_vector; + typedef ptr_vector func_decl_vector; + + enum assembling_column_kind { + ACK_BOUND_VAR, + ACK_UNBOUND_VAR, + ACK_CONSTANT + }; + + /** + \brief instruction for assembling head relation from a joint relation built + from rule evaluation. + + ACK_BOUND_VAR(source_column) - encodes that the column contains a variable + bound in the body. + + ACK_UNBOUND_VAR(var_index) - encodes that the column contains a variable that + is unbound (by the corresponding rule body), + var_index is the de-Brujin index (var->get_idx()) + of the variable associated with the column. + + ACK_CONSTANT(constant) - encodes that the column contains the constant. + + Examples: + + P(x) :- Q(x,y), Q(y,z) + The variables in the body relation are [x, y, y, z] indexed as 0, 1, 2, 3. + The variable x gets the instruction ACK_BOUND_VAR(0) + + P(u,x) :- Q(x,y), Q(y,z) + The variable u gets the instruction ACK_UNBOUND_VAR(#0) + + P(1, x) :- Q(x,y), Q(y,z) + The instruction for column 0 is ACK_CONSTANT(1) + + */ + struct assembling_column_info { + + relation_sort domain; // domain of the column + assembling_column_kind kind; // "instruction" tag + unsigned source_column; // for ACK_BOUND_VAR + unsigned var_index; // for ACK_UNBOUND_VAR + relation_element constant; // for ACK_CONSTANT + }; + + class instruction_observer : public instruction_block::instruction_observer { + compiler & m_parent; + rule * m_current; + public: + instruction_observer(compiler & parent) : m_parent(parent), m_current(0) {} + + void start_rule(rule * r) { SASSERT(!m_current); m_current=r; } + void finish_rule() { m_current = 0; } + virtual void notify(instruction * i) { + if(m_current) { + i->set_accounting_parent_object(m_parent.m_context, m_current); + } + } + }; + + + context & m_context; + rule_set const & m_rule_set; + /** + Invariant: the \c m_top_level_code never contains the loop that is being constructed, + so instruction that need to be executed before the loop can be pushed into it. + */ + instruction_block & m_top_level_code; + pred2idx m_pred_regs; + reg_idx m_new_reg; + vector m_reg_signatures; + obj_pair_map m_constant_registers; + instruction_observer m_instruction_observer; + + /** + If true, the union operation on the underlying structure only provides the information + whether the updated relation has changed or not. In this case we do not get anything + from using delta relations at position of input relations in the saturation loop, so we + would not do it. + */ + bool all_or_nothing_deltas() const { return m_context.all_or_nothing_deltas(); } + + /** + If true, we compile the saturation loops in a way that allows us to use widening. + */ + bool compile_with_widening() const { return m_context.compile_with_widening(); } + + reg_idx get_fresh_register(const relation_signature & sig); + reg_idx get_single_column_register(const relation_sort & s); + + /** + \brief Allocate registers for predicates in \c pred and add them into the \c regs map. + + \c regs must not already contain any predicate from \c preds. + */ + void get_fresh_registers(const func_decl_set & preds, pred2idx & regs); + + void make_join(reg_idx t1, reg_idx t2, const variable_intersection & vars, reg_idx & result, + instruction_block & acc); + void make_join_project(reg_idx t1, reg_idx t2, const variable_intersection & vars, + const unsigned_vector & removed_cols, reg_idx & result, instruction_block & acc); + void make_select_equal_and_project(reg_idx src, const relation_element & val, unsigned col, + reg_idx & result, instruction_block & acc); + /** + \brief Create add an union or widen operation and put it into \c acc. + */ + void make_union(reg_idx src, reg_idx tgt, reg_idx delta, bool widening, instruction_block & acc); + void make_projection(reg_idx src, unsigned col_cnt, const unsigned * removed_cols, + reg_idx & result, instruction_block & acc); + void make_rename(reg_idx src, unsigned cycle_len, const unsigned * permutation_cycle, + reg_idx & result, instruction_block & acc); + void make_clone(reg_idx src, reg_idx & result, instruction_block & acc); + + /** + \brief Into \c acc add code that will assemble columns of a relation according to description + in \c acis0. The source for bound variables is the table in register \c src. + + If \c src is \c execution_context::void_register, it is assumed to be a full relation + with empty signature. + */ + void make_assembling_code(rule* compiled_rule, func_decl* head_pred, reg_idx src, const svector & acis0, + reg_idx & result, instruction_block & acc); + + void make_dealloc_non_void(reg_idx r, instruction_block & acc); + + void make_add_constant_column(func_decl* pred, reg_idx src, const relation_sort & s, const relation_element & val, + reg_idx & result, instruction_block & acc); + + void make_add_unbound_column(rule* compiled_rule, unsigned col_idx, func_decl* pred, reg_idx src, const relation_sort & s, reg_idx & result, + instruction_block & acc); + void make_full_relation(func_decl* pred, const relation_signature & sig, reg_idx & result, + instruction_block & acc); + + void add_unbound_columns_for_negation(rule* compiled_rule, func_decl* pred, reg_idx& single_res, ptr_vector& single_res_expr, + instruction_block& acc); + + void make_duplicate_column(reg_idx src, unsigned col, reg_idx & result, instruction_block & acc); + + void ensure_predicate_loaded(func_decl * pred, instruction_block & acc); + + /** + \brief For rule \c r with two positive uninterpreted predicates put into \c res indexes of + local variables in a table that results from join of the two positive predicates. + + Used to get input for the "project" part of join-project. + */ + void get_local_indexes_for_projection(rule * r, unsigned_vector & res); + void get_local_indexes_for_projection(app * t, var_counter & globals, unsigned ofs, + unsigned_vector & res); + + /** + \brief Into \c acc add instructions that will add new facts following from the rule into + \c head_reg, and add the facts that were not in \c head_reg before into \c delta_reg. + */ + void compile_rule_evaluation_run(rule * r, reg_idx head_reg, const reg_idx * tail_regs, + reg_idx delta_reg, bool use_widening, instruction_block & acc); + + void compile_rule_evaluation(rule * r, const pred2idx * input_deltas, reg_idx output_delta, + bool use_widening, instruction_block & acc); + + /** + \brief Generate code to evaluate rules corresponding to predicates in \c head_preds. + The rules are evaluated in the order their heads appear in the \c head_preds vector. + */ + void compile_preds(const func_decl_vector & head_preds, const func_decl_set & widened_preds, + const pred2idx * input_deltas, const pred2idx & output_deltas, instruction_block & acc); + + void make_inloop_delta_transition(const pred2idx & global_head_deltas, + const pred2idx & global_tail_deltas, const pred2idx & local_deltas, instruction_block & acc); + void compile_loop(const func_decl_vector & head_preds, const func_decl_set & widened_preds, + const pred2idx & global_head_deltas, const pred2idx & global_tail_deltas, + const pred2idx & local_deltas, instruction_block & acc); + void compile_dependent_rules(const func_decl_set & head_preds, + const pred2idx * input_deltas, const pred2idx & output_deltas, + bool add_saturation_marks, instruction_block & acc); + + void detect_chains(const func_decl_set & preds, func_decl_vector & ordered_preds, + func_decl_set & global_deltas); + /** + Return true if there is no dependency inside the \c rules stratum. + + The head predicates in stratum must be strongly connected by dependency. + */ + bool is_nonrecursive_stratum(const func_decl_set & preds) const; + /** + input_deltas==0 --> we use the actual content of relations instead of deltas + */ + void compile_nonrecursive_stratum(const func_decl_set & preds, + const pred2idx * input_deltas, const pred2idx & output_deltas, + bool add_saturation_marks, instruction_block & acc); + + void compile_strats(const rule_stratifier & stratifier, + const pred2idx * input_deltas, const pred2idx & output_deltas, + bool add_saturation_marks, instruction_block & acc); + + bool all_saturated(const func_decl_set & preds) const; + + void reset(); + + explicit compiler(context & ctx, rule_set const & rules, instruction_block & top_level_code) + : m_context(ctx), + m_rule_set(rules), + m_top_level_code(top_level_code), + m_instruction_observer(*this) {} + + /** + \brief Compile \c rules in to pseudocode. + + Instructions to load data and perform computations put into \c execution_code + */ + void do_compilation(instruction_block & execution_code, + instruction_block & termination_code); + + public: + + static void compile(context & ctx, rule_set const & rules, instruction_block & execution_code, + instruction_block & termination_code) { + compiler(ctx, rules, execution_code) + .do_compilation(execution_code, termination_code); + } + + }; + + +}; + +#endif /* _DL_COMPILER_H_ */ + diff --git a/lib/dl_context.cpp b/lib/dl_context.cpp new file mode 100644 index 000000000..6c6f2bce5 --- /dev/null +++ b/lib/dl_context.cpp @@ -0,0 +1,1685 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_context.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-05-18. + +Revision History: + +--*/ + +#include +#include +#include"arith_simplifier_plugin.h" +#include"basic_simplifier_plugin.h" +#include"arith_decl_plugin.h" +#include"bv_decl_plugin.h" +#include"dl_table.h" +#include"dl_sparse_table.h" +#include"dl_table_relation.h" +#include"dl_bound_relation.h" +#include"dl_interval_relation.h" +#include"dl_finite_product_relation.h" +#include"dl_product_relation.h" +#include"dl_rule_transformer.h" +#include"dl_mk_coi_filter.h" +#include"dl_mk_explanations.h" +#include"dl_mk_filter_rules.h" +#include"dl_mk_interp_tail_simplifier.h" +#include"dl_mk_magic_sets.h" +#include"dl_mk_rule_inliner.h" +#include"dl_mk_simple_joins.h" +#include"dl_mk_similarity_compressor.h" +#include"dl_mk_unbound_compressor.h" +#include"dl_mk_subsumption_checker.h" +#include"dl_compiler.h" +#include"dl_instruction.h" +#include"dl_context.h" +#include"dl_simplifier_plugin.h" +#include"dl_smt_relation.h" +#ifndef _EXTERNAL_RELEASE +#include"dl_skip_table.h" +#endif +#include"for_each_expr.h" +#include"ast_smt2_pp.h" +#include"expr_functors.h" +#include"dl_mk_partial_equiv.h" +#include"dl_mk_bit_blast.h" + +namespace datalog { + + // ----------------------------------- + // + // context::sort_domain + // + // ----------------------------------- + + class context::sort_domain { + private: + sort_kind m_kind; + protected: + sort_ref m_sort; + bool m_limited_size; + uint64 m_size; + + sort_domain(sort_kind k, context & ctx, sort * s) + : m_kind(k), m_sort(s, ctx.get_manager()) { + m_limited_size = ctx.get_decl_util().try_get_size(s, m_size); + } + public: + virtual ~sort_domain() {} + + sort_kind get_kind() const { return m_kind; } + virtual unsigned get_constant_count() const = 0; + virtual void print_element(finite_element el_num, std::ostream & out) = 0; + }; + + class context::symbol_sort_domain : public sort_domain { + typedef map sym2num; + typedef svector num2sym; + + sym2num m_el_numbers; + num2sym m_el_names; + public: + symbol_sort_domain(context & ctx, sort * s) : sort_domain(SK_SYMBOL, ctx, s) {} + + finite_element get_number(symbol sym) { + //we number symbols starting from zero, so table->size() is equal to the + //index of the symbol to be added next + + unsigned newIdx = m_el_numbers.size(); + + sym2num::entry* sym_e = m_el_numbers.insert_if_not_there2(sym, newIdx); + unsigned idx=sym_e->get_data().m_value; + + if (idx==newIdx) { + m_el_names.push_back(sym); + SASSERT(m_el_names.size()==m_el_numbers.size()); + } + + if (m_limited_size && idx>=m_size) { + std::stringstream sstm; + sstm << "sort " << m_sort->get_name() << " contains more constants than its declared size " << m_size; + throw default_exception(sstm.str()); + } + return idx; + } + + virtual unsigned get_constant_count() const { + return m_el_names.size(); + } + virtual void print_element(finite_element el_num, std::ostream & out) { + if (el_num>=m_el_names.size()) { + out << el_num; + return; + } + out << m_el_names[el_num]; + } + }; + + class context::uint64_sort_domain : public sort_domain { + typedef map > el2num; + typedef svector num2el; + + el2num m_el_numbers; + num2el m_el_names; + public: + uint64_sort_domain(context & ctx, sort * s) : sort_domain(SK_UINT64, ctx, s) {} + + finite_element get_number(uint64 el) { + //we number symbols starting from zero, so table->size() is equal to the + //index of the symbol to be added next + + unsigned newIdx = m_el_numbers.size(); + + el2num::entry* sym_e = m_el_numbers.insert_if_not_there2(el, newIdx); + unsigned idx=sym_e->get_data().m_value; + + if (idx==newIdx) { + m_el_names.push_back(el); + SASSERT(m_el_names.size()==m_el_numbers.size()); + } + + if (m_limited_size && idx>=m_size) { + std::stringstream sstm; + sstm << "sort " << m_sort->get_name() << " contains more constants than its declared size " << m_size; + throw default_exception(sstm.str()); + } + return idx; + } + virtual unsigned get_constant_count() const { + return m_el_names.size(); + } + virtual void print_element(finite_element el_num, std::ostream & out) { + if (el_num >= m_el_names.size()) { + out << "get_name() << ":" << el_num << '>'; + return; + } + out << m_el_names[el_num]; + } + }; + + // ----------------------------------- + // + // trail stack for restoring rules + // + // ----------------------------------- + + class context::restore_rules : public trail { + rule_set* m_old_rules; + void reset() { + dealloc(m_old_rules); + m_old_rules = 0; + } + public: + restore_rules(rule_set& r): m_old_rules(alloc(rule_set, r)) {} + + virtual ~restore_rules() {} + + virtual void undo(context& ctx) { + ctx.reset_tables(); + ctx.replace_rules(*m_old_rules); + reset(); + } + }; + + template + class restore_vec_size_trail : public trail { + Vec& m_vector; + unsigned m_old_size; + public: + restore_vec_size_trail(Vec& v): m_vector(v), m_old_size(v.size()) {} + virtual ~restore_vec_size_trail() {} + virtual void undo(Ctx& ctx) { m_vector.shrink(m_old_size); } + }; + + void context::push() { + m_trail.push_scope(); + m_trail.push(restore_rules(m_rule_set)); + m_trail.push(restore_vec_size_trail(m_background)); + m_trail.push(restore_vec_size_trail(m_table_facts)); + } + + void context::pop() { + if (m_trail.get_num_scopes() == 0) { + throw default_exception("there are no backtracking points to pop to"); + } + m_trail.pop_scope(1); + } + + // ----------------------------------- + // + // context + // + // ----------------------------------- + + context::context(ast_manager & m, front_end_params& fp, params_ref const& pa): + m(m), + m_fparams(fp), + m_params(pa), + m_decl_util(m), + m_rewriter(m), + m_var_subst(m), + m_rmanager(*this), + m_rule_manager(*this), + m_trail(*this), + m_pinned(m), + m_vars(m), + m_rule_set(*this), + m_background(m), + m_closed(false), + m_saturation_was_run(false), + m_last_result_relation(0), + m_last_answer(m), + m_engine(LAST_ENGINE), + m_cancel(false) { + + //register plugins for builtin tables + get_rmanager().register_plugin(alloc(sparse_table_plugin, get_rmanager())); + get_rmanager().register_plugin(alloc(hashtable_table_plugin, get_rmanager())); + get_rmanager().register_plugin(alloc(bitvector_table_plugin, get_rmanager())); + get_rmanager().register_plugin(alloc(equivalence_table_plugin, get_rmanager())); + +#ifndef _EXTERNAL_RELEASE + get_rmanager().register_plugin(alloc(skip_table_plugin, get_rmanager())); +#endif + + //register plugins for builtin relations + get_rmanager().register_plugin(alloc(smt_relation_plugin, get_rmanager())); + + get_rmanager().register_plugin(alloc(bound_relation_plugin, get_rmanager())); + get_rmanager().register_plugin(alloc(interval_relation_plugin, get_rmanager())); + } + + context::~context() { + reset(); + } + + void context::reset() { + m_trail.reset(); + m_rule_set.reset(); + m_argument_var_names.reset(); + m_output_preds.reset(); + m_preds.reset(); + m_preds_by_name.reset(); + reset_dealloc_values(m_sorts); + if (m_last_result_relation) { + m_last_result_relation->deallocate(); + m_last_result_relation = 0; + } + } + + bool context::is_fact(app * head) const { + return m_rule_manager.is_fact(head); + } + + bool context::has_sort_domain(relation_sort s) const { + return m_sorts.contains(s); + } + + context::sort_domain & context::get_sort_domain(relation_sort s) { + sort_domain * dom; + TRUSTME( m_sorts.find(s, dom) ); + return *dom; + } + + const context::sort_domain & context::get_sort_domain(relation_sort s) const { + sort_domain * dom; + TRUSTME( m_sorts.find(s, dom) ); + return *dom; + } + + void context::register_finite_sort(sort * s, sort_kind k) { + m_pinned.push_back(s); + SASSERT(!m_sorts.contains(s)); + sort_domain * dom; + switch (k) { + case SK_SYMBOL: + dom = alloc(symbol_sort_domain, *this, s); + break; + case SK_UINT64: + dom = alloc(uint64_sort_domain, *this, s); + break; + default: + UNREACHABLE(); + } + m_sorts.insert(s, dom); + } + + bool context::is_predicate(func_decl * pred) const { + return m_preds.contains(pred); + } + + func_decl * context::try_get_predicate_decl(symbol pred_name) const { + func_decl * res; + if (!m_preds_by_name.find(pred_name, res)) { + return 0; + } + return res; + } + + void context::register_variable(func_decl* var) { + m_vars.push_back(m.mk_const(var)); + } + + void context::register_predicate(func_decl * decl, bool named) { + SASSERT(!m_preds.contains(decl)); + m_pinned.push_back(decl); + m_preds.insert(decl); + if (named) { + SASSERT(!m_preds_by_name.contains(decl->get_name())); + m_preds_by_name.insert(decl->get_name(), decl); + } + } + + context::finite_element context::get_constant_number(relation_sort srt, symbol sym) { + sort_domain & dom0 = get_sort_domain(srt); + SASSERT(dom0.get_kind() == SK_SYMBOL); + symbol_sort_domain & dom = static_cast(dom0); + return dom.get_number(sym); + } + + context::finite_element context::get_constant_number(relation_sort srt, uint64 el) { + sort_domain & dom0 = get_sort_domain(srt); + SASSERT(dom0.get_kind()==SK_UINT64); + uint64_sort_domain & dom = static_cast(dom0); + return dom.get_number(el); + } + + void context::print_constant_name(relation_sort srt, uint64 num, std::ostream & out) + { + if (has_sort_domain(srt)) { + SASSERT(num<=UINT_MAX); + get_sort_domain(srt).print_element(static_cast(num), out); + } + else { + out << num; + } + } + + bool context::try_get_sort_constant_count(relation_sort srt, uint64 & constant_count) { + if (!has_sort_domain(srt)) { + return false; + } + constant_count = get_sort_domain(srt).get_constant_count(); + return true; + } + + uint64 context::get_sort_size_estimate(relation_sort srt) { + if (get_decl_util().is_rule_sort(srt)) { + return 1; + } + uint64 res; + if (!try_get_sort_constant_count(srt, res)) { + sort_size sz = srt->get_num_elements(); + if (sz.is_finite()) { + res = sz.size(); + } + else { + res = std::numeric_limits::max(); + } + } + return res; + } + + void context::set_argument_names(const func_decl * pred, svector var_names) + { + SASSERT(!m_argument_var_names.contains(pred)); + m_argument_var_names.insert(pred, var_names); + } + + symbol context::get_argument_name(const func_decl * pred, unsigned arg_index) + { + pred2syms::obj_map_entry * e = m_argument_var_names.find_core(pred); + if (!e) { + std::stringstream name_stm; + name_stm << '#' << arg_index; + return symbol(name_stm.str().c_str()); + } + SASSERT(arg_index < e->get_data().m_value.size()); + return e->get_data().m_value[arg_index]; + } + + relation_plugin & context::get_ordinary_relation_plugin(symbol relation_name) { + relation_plugin * plugin = get_rmanager().get_relation_plugin(relation_name); + if (!plugin) { + std::stringstream sstm; + sstm << "relation plugin " << relation_name << " does not exist"; + throw default_exception(sstm.str()); + } + if (plugin->is_product_relation()) { + throw default_exception("cannot request product relation directly"); + } + if (plugin->is_sieve_relation()) { + throw default_exception("cannot request sieve relation directly"); + } + if (plugin->is_finite_product_relation()) { + throw default_exception("cannot request finite product relation directly"); + } + return *plugin; + } + + void context::set_predicate_representation(func_decl * pred, unsigned relation_name_cnt, + symbol * const relation_names) { + relation_manager & rmgr = get_rmanager(); + + family_id target_kind = null_family_id; + if (relation_name_cnt==1) { + target_kind = get_ordinary_relation_plugin(relation_names[0]).get_kind(); + } else { + relation_plugin * tr_plugin = 0; //table plugin, if there is such + ptr_vector rel_plugins; //plugins that are not table plugins + svector rel_kinds; //kinds of plugins that are not table plugins + for (unsigned i=0; iname() == name) { + if (old_rule) { + std::stringstream strm; + strm << "Rule " << name << " occurs twice. It cannot be modified"; + throw default_exception(strm.str()); + } + old_rule = rls[i]; + } + } + if (old_rule) { + if (!check_subsumes(*old_rule, *r)) { + std::stringstream strm; + strm << "Old rule "; + old_rule->display(*this, strm); + strm << "does not subsume new rule "; + r->display(*this, strm); + throw default_exception(strm.str()); + } + m_rule_set.del_rule(old_rule); + } + m_rule_set.add_rule(r); + } + + bool context::check_subsumes(rule const& stronger_rule, rule const& weaker_rule) { + if (stronger_rule.get_head() != weaker_rule.get_head()) { + return false; + } + for (unsigned i = 0; i < stronger_rule.get_tail_size(); ++i) { + app* t = stronger_rule.get_tail(i); + bool found = false; + for (unsigned j = 0; j < weaker_rule.get_tail_size(); ++j) { + app* s = weaker_rule.get_tail(j); + if (s == t) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; + } + + unsigned context::get_num_levels(func_decl* pred) { + switch(get_engine()) { + case DATALOG_ENGINE: + throw default_exception("get_num_levels is unsupported for datalog engine"); + case PDR_ENGINE: + case QPDR_ENGINE: + ensure_pdr(); + return m_pdr->get_num_levels(pred); + case BMC_ENGINE: + throw default_exception("get_num_levels is unsupported for bmc"); + default: + throw default_exception("unknown engine"); + } + } + + expr_ref context::get_cover_delta(int level, func_decl* pred) { + switch(get_engine()) { + case DATALOG_ENGINE: + throw default_exception("operation is unsupported for datalog engine"); + case PDR_ENGINE: + case QPDR_ENGINE: + ensure_pdr(); + return m_pdr->get_cover_delta(level, pred); + case BMC_ENGINE: + throw default_exception("operation is unsupported for BMC engine"); + default: + throw default_exception("unknown engine"); + } + } + + void context::add_cover(int level, func_decl* pred, expr* property) { + switch(get_engine()) { + case DATALOG_ENGINE: + throw default_exception("operation is unsupported for datalog engine"); + case PDR_ENGINE: + case QPDR_ENGINE: + ensure_pdr(); + m_pdr->add_cover(level, pred, property); + break; + case BMC_ENGINE: + throw default_exception("operation is unsupported for BMC engine"); + default: + throw default_exception("unknown engine"); + } + } + + void context::check_uninterpreted_free(rule_ref& r) { + func_decl* f = 0; + if (r->has_uninterpreted_non_predicates(f)) { + std::stringstream stm; + stm << "Uninterpreted '" + << f->get_name() + << "' in "; + r->display(*this, stm); + throw default_exception(stm.str()); + } + } + + void context::check_quantifier_free(rule_ref& r) { + if (r->has_quantifiers()) { + std::stringstream stm; + stm << "cannot process quantifiers in rule "; + r->display(*this, stm); + throw default_exception(stm.str()); + } + } + + class context::contains_pred : public i_expr_pred { + rule_manager const& m; + public: + contains_pred(rule_manager const& m): m(m) {} + virtual ~contains_pred() {} + + virtual bool operator()(expr* e) { + return is_app(e) && m.is_predicate(to_app(e)); + } + }; + + void context::check_existential_tail(rule_ref& r) { + unsigned ut_size = r->get_uninterpreted_tail_size(); + unsigned t_size = r->get_tail_size(); + contains_pred contains_p(get_rule_manager()); + check_pred check_pred(contains_p, get_manager()); + + TRACE("dl", r->display_smt2(get_manager(), tout); tout << "\n";); + for (unsigned i = ut_size; i < t_size; ++i) { + app* t = r->get_tail(i); + TRACE("dl", tout << "checking: " << mk_ismt2_pp(t, get_manager()) << "\n";); + if (check_pred(t)) { + std::ostringstream out; + out << "interpreted body " << mk_ismt2_pp(t, get_manager()) << " contains recursive predicate"; + throw default_exception(out.str()); + } + } + } + + void context::check_positive_predicates(rule_ref& r) { + ast_mark visited; + ptr_vector todo, tocheck; + unsigned ut_size = r->get_uninterpreted_tail_size(); + unsigned t_size = r->get_tail_size(); + for (unsigned i = 0; i < ut_size; ++i) { + if (r->is_neg_tail(i)) { + tocheck.push_back(r->get_tail(i)); + } + } + ast_manager& m = get_manager(); + datalog::rule_manager& rm = get_rule_manager(); + contains_pred contains_p(rm); + check_pred check_pred(contains_p, get_manager()); + + for (unsigned i = ut_size; i < t_size; ++i) { + todo.push_back(r->get_tail(i)); + } + while (!todo.empty()) { + expr* e = todo.back(), *e1, *e2; + todo.pop_back(); + if (visited.is_marked(e)) { + continue; + } + visited.mark(e, true); + if (rm.is_predicate(e)) { + } + else if (m.is_and(e) || m.is_or(e)) { + todo.append(to_app(e)->get_num_args(), to_app(e)->get_args()); + } + else if (m.is_implies(e, e1, e2)) { + tocheck.push_back(e1); + todo.push_back(e2); + } + else if (is_quantifier(e)) { + todo.append(to_quantifier(e)->get_expr()); + } + else if ((m.is_eq(e, e1, e2) || m.is_iff(e, e1, e2)) && + m.is_true(e1)) { + todo.push_back(e2); + } + else if ((m.is_eq(e, e1, e2) || m.is_iff(e, e1, e2)) && + m.is_true(e2)) { + todo.push_back(e1); + } + else { + tocheck.push_back(e); + } + } + for (unsigned i = 0; i < tocheck.size(); ++i) { + expr* e = tocheck[i]; + if (check_pred(e)) { + std::ostringstream out; + out << "recursive predicate " << mk_ismt2_pp(e, get_manager()) << " occurs nested in body"; + throw default_exception(out.str()); + + } + } + } + + void context::check_rule(rule_ref& r) { + switch(get_engine()) { + case DATALOG_ENGINE: + check_quantifier_free(r); + check_uninterpreted_free(r); + check_existential_tail(r); + break; + case PDR_ENGINE: + check_existential_tail(r); + check_positive_predicates(r); + break; + case QPDR_ENGINE: + check_positive_predicates(r); + break; + case BMC_ENGINE: + check_existential_tail(r); + check_positive_predicates(r); + break; + } + } + + void context::add_rule(rule_ref& r) { + get_rmanager().reset_saturated_marks(); + m_rule_set.add_rule(r); + } + + void context::add_rules(rule_ref_vector& rules) { + for (unsigned i = 0; i < rules.size(); ++i) { + rule_ref rule(rules[i].get(), rules.get_manager()); + add_rule(rule); + } + } + + + void context::add_fact(func_decl * pred, const relation_fact & fact) { + if (get_engine() == DATALOG_ENGINE) { + get_rmanager().reset_saturated_marks(); + get_relation(pred).add_fact(fact); + m_table_facts.push_back(std::make_pair(pred, fact)); + } + else { + ast_manager& m = get_manager(); + expr_ref rule(m.mk_app(pred, fact.size(), (expr*const*)fact.c_ptr()), m); + add_rule(rule, symbol::null); + } + } + + + void context::add_fact(app * head) { + SASSERT(is_fact(head)); + + relation_fact fact(get_manager()); + unsigned n=head->get_num_args(); + for (unsigned i=0; iget_arg(i))); + } + add_fact(head->get_decl(), fact); + } + + bool context::can_add_table_fact(func_decl * pred) { + return get_relation(pred).from_table(); + } + + void context::add_table_fact(func_decl * pred, const table_fact & fact) { + relation_base & rel0 = get_relation(pred); + if (get_engine() != DATALOG_ENGINE || + !can_add_table_fact(pred) || + !rel0.from_table()) { + relation_fact rfact(m); + for (unsigned i = 0; i < fact.size(); ++i) { + rfact.push_back(m_decl_util.mk_numeral(fact[i], pred->get_domain()[i])); + } + add_fact(pred, rfact); + } + else { + get_rmanager().reset_saturated_marks(); + table_relation & rel = static_cast(rel0); + rel.add_table_fact(fact); + } + } + + void context::add_table_fact(func_decl * pred, unsigned num_args, unsigned args[]) { + if (pred->get_arity() != num_args) { + std::ostringstream out; + out << "miss-matched number of arguments passed to " << mk_ismt2_pp(pred, m) << " " << num_args << " passed"; + throw default_exception(out.str()); + } + table_fact fact; + for (unsigned i = 0; i < num_args; ++i) { + fact.push_back(args[i]); + } + add_table_fact(pred, fact); + } + + void context::close() { + SASSERT(!m_closed); + if (!m_rule_set.close()) { + throw default_exception("Negation is not stratified!"); + } + m_closed = true; + } + + void context::ensure_closed() { + if (!m_closed) { + close(); + } + } + void context::ensure_opened() { + if (m_closed) { + reopen(); + } + } + + void context::reopen() { + SASSERT(m_closed); + m_rule_set.reopen(); + m_closed = false; + } + + void context::transform_rules(model_converter_ref& mc, proof_converter_ref& pc) { + rule_transformer transf(*this); + transf.register_plugin(alloc(mk_filter_rules,*this)); + transf.register_plugin(alloc(mk_simple_joins,*this)); + + if (unbound_compressor()) { + transf.register_plugin(alloc(mk_unbound_compressor,*this)); + } + + if (similarity_compressor()) { + transf.register_plugin(alloc(mk_similarity_compressor, *this, + similarity_compressor_threshold())); + } + transf.register_plugin(alloc(datalog::mk_partial_equivalence_transformer, *this)); + + transform_rules(transf, mc, pc); + } + + void context::transform_rules(rule_transformer& transf, model_converter_ref& mc, proof_converter_ref& pc) { + SASSERT(m_closed); //we must finish adding rules before we start transforming them + TRACE("dl", display_rules(tout);); + if (transf(m_rule_set, mc, pc)) { + //we have already ensured the negation is stratified and transformations + //should not break the stratification + m_rule_set.ensure_closed(); + TRACE("dl", display_rules(tout);); + TRACE("dl_verbose", display(tout);); + } + } + + void context::replace_rules(rule_set & rs) { + SASSERT(!m_closed); + m_rule_set.reset(); + m_rule_set.add_rules(rs); + } + + void context::apply_default_transformation(model_converter_ref& mc, proof_converter_ref& pc) { + ensure_closed(); + datalog::rule_transformer transf(*this); + transf.register_plugin(alloc(datalog::mk_coi_filter, *this)); + transf.register_plugin(alloc(datalog::mk_interp_tail_simplifier, *this)); + + transf.register_plugin(alloc(datalog::mk_subsumption_checker, *this, 35005)); + transf.register_plugin(alloc(datalog::mk_rule_inliner, *this, 35000)); + transf.register_plugin(alloc(datalog::mk_coi_filter, *this, 34990)); + transf.register_plugin(alloc(datalog::mk_interp_tail_simplifier, *this, 34980)); + + //and another round of inlining + transf.register_plugin(alloc(datalog::mk_subsumption_checker, *this, 34975)); + transf.register_plugin(alloc(datalog::mk_rule_inliner, *this, 34970)); + transf.register_plugin(alloc(datalog::mk_coi_filter, *this, 34960)); + transf.register_plugin(alloc(datalog::mk_interp_tail_simplifier, *this, 34950)); + + transf.register_plugin(alloc(datalog::mk_subsumption_checker, *this, 34940)); + transf.register_plugin(alloc(datalog::mk_rule_inliner, *this, 34930)); + transf.register_plugin(alloc(datalog::mk_subsumption_checker, *this, 34920)); + transf.register_plugin(alloc(datalog::mk_rule_inliner, *this, 34910)); + transf.register_plugin(alloc(datalog::mk_subsumption_checker, *this, 34900)); + transf.register_plugin(alloc(datalog::mk_rule_inliner, *this, 34890)); + transf.register_plugin(alloc(datalog::mk_subsumption_checker, *this, 34880)); + + transf.register_plugin(alloc(datalog::mk_bit_blast, *this, 35000)); + transform_rules(transf, mc, pc); + } + + void context::collect_params(param_descrs& p) { + p.insert(":engine", CPK_SYMBOL, "(default: automatically configured) select 'datalog', PDR 'pdr' engine."); + p.insert(":bit-blast", CPK_BOOL, "(default: false) bit-blast bit-vectors (for PDR engine)."); + p.insert(":default-table", CPK_SYMBOL, "default table implementation: 'sparse' (default), 'hashtable', 'bitvector', 'interval'"); + p.insert(":default-relation", CPK_SYMBOL, "default relation implementation: 'external_relation', 'pentagon'"); + + p.insert(":generate-explanations", CPK_BOOL, "if true, signature of relations will be extended to contain explanations for facts"); + + p.insert(":explanations-on-relation-level", CPK_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)"); + + p.insert(":magic-sets-for-queries", CPK_BOOL, "magic set transformation will be used for queries"); + p.insert(":unbound-compressor", CPK_BOOL, "auxiliary relations will be introduced to avoid unbound variables in rule heads"); + p.insert(":similarity-compressor", CPK_BOOL, "rules that differ only in values of constants will be merged into a single rule"); + p.insert(":similarity-compressor-threshold", CPK_UINT, "if :dl-similiaryt-compressor is on, this value determines how many " + "similar rules there must be in order for them to be merged"); + + p.insert(":all-or-nothing-deltas", CPK_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"); + p.insert(":compile-with-widening", CPK_BOOL, "widening will be used to compile recursive rules"); + p.insert(":eager-emptiness-checking", CPK_BOOL, "emptiness of affected relations will be checked after each instruction, " + "so that we may ommit unnecessary instructions"); + p.insert(":default-table-checked", CPK_BOOL, + "if true, the detault table will be :default-table inside a wrapper that checks that " + "its results are the same as of :default-table-checker table"); + + + p.insert(":initial-restart-timeout", CPK_UINT, "length of saturation run before the first restart (in ms); zero means no restarts"); + p.insert(":restart-timeout-quotient", CPK_UINT, "restart timeout will be multiplied by this number after each restart"); + p.insert(":use-map-names", CPK_BOOL, "use names from map files when displaying tuples"); + + p.insert(":output-profile", CPK_BOOL, "determines whether profile informations should be output when outputting Datalog rules or instructions"); + p.insert(":output-tuples", CPK_BOOL, "determines whether tuples for output predicates should be output"); + p.insert(":profile-timeout-milliseconds", CPK_UINT, "instructions and rules that took less than the threshold will not be printed when printed the instruction/rule list"); + + PRIVATE_PARAMS( + p.insert(":dbg-fpr-nonempty-relation-signature", CPK_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"); + + p.insert(":smt-relation-ground-recursive", CPK_BOOL, "Ensure recursive relation is ground in union"); + ); + + p.insert(":fix-unbound-vars", CPK_BOOL, "fix unbound variables in tail"); + p.insert(":default-table-checker", CPK_SYMBOL, "see :default-table-checked"); + PRIVATE_PARAMS(p.insert(":inline-linear", CPK_BOOL, "try linear inlining method");); + PRIVATE_PARAMS(p.insert(":inline-linear-branch", CPK_BOOL, "try linear inlining method with potential expansion");); + + pdr::dl_interface::collect_params(p); + insert_timeout(p); + } + + void context::updt_params(params_ref const& p) { + m_params.copy(p); + if (m_pdr.get()) m_pdr->updt_params(); + } + + void context::collect_predicates(decl_set & res) { + unsigned rule_cnt = m_rule_set.get_num_rules(); + for (unsigned rindex=0; rindexget_head()->get_decl()); + unsigned tail_len = r->get_uninterpreted_tail_size(); + for (unsigned tindex=0; tindexget_tail(tindex)->get_decl()); + } + } + decl_set::iterator oit = m_output_preds.begin(); + decl_set::iterator oend = m_output_preds.end(); + for (; oit!=oend; ++oit) { + res.insert(*oit); + } + get_rmanager().collect_predicates(res); + } + + void context::restrict_predicates( const decl_set & res ) { + set_intersection(m_output_preds, res); + get_rmanager().restrict_predicates(res); + } + + lbool context::dl_saturate() { + if (!m_closed) { + close(); + } + bool time_limit = soft_timeout()!=0; + unsigned remaining_time_limit = soft_timeout(); + unsigned restart_time = initial_restart_timeout(); + + rule_set original_rules(get_rules()); + decl_set original_predicates; + collect_predicates(original_predicates); + + instruction_block rules_code; + instruction_block termination_code; + execution_context ex_ctx(*this); + + lbool result; + + TRACE("dl", display(tout);); + + while (true) { + model_converter_ref mc; // Ignored in Datalog mode + proof_converter_ref pc; // Ignored in Datalog mode + transform_rules(mc, pc); + compiler::compile(*this, get_rules(), rules_code, termination_code); + + TRACE("dl", rules_code.display(*this, tout); ); + + bool timeout_after_this_round = time_limit && (restart_time==0 || remaining_time_limit<=restart_time); + + if (time_limit || restart_time!=0) { + unsigned timeout = time_limit ? (restart_time!=0) ? + std::min(remaining_time_limit, restart_time) + : remaining_time_limit : restart_time; + ex_ctx.set_timelimit(timeout); + } + + bool early_termination = !rules_code.perform(ex_ctx); + ex_ctx.reset_timelimit(); + VERIFY( termination_code.perform(ex_ctx) ); + + rules_code.process_all_costs(); + + IF_VERBOSE(10, ex_ctx.report_big_relations(1000, verbose_stream());); + + if (!early_termination) { + m_last_status = OK; + result = l_true; + break; + } + + if (memory::above_high_watermark()) { + m_last_status = MEMOUT; + result = l_undef; + break; + } + if (timeout_after_this_round || m_cancel) { + m_last_status = TIMEOUT; + result = l_undef; + break; + } + SASSERT(restart_time!=0); + if (time_limit) { + SASSERT(remaining_time_limit>restart_time); + remaining_time_limit-=restart_time; + } + uint64 new_restart_time = static_cast(restart_time)*initial_restart_timeout(); + if (new_restart_time>UINT_MAX) { + restart_time=UINT_MAX; + } + else { + restart_time=static_cast(new_restart_time); + } + + rules_code.reset(); + termination_code.reset(); + ex_ctx.reset(); + reopen(); + restrict_predicates(original_predicates); + replace_rules(original_rules); + close(); + } + reopen(); + restrict_predicates(original_predicates); + replace_rules(original_rules); + close(); + TRACE("dl", ex_ctx.report_big_relations(100, tout);); + return result; + } + + expr_ref context::get_background_assertion() { + expr_ref result(m); + switch (m_background.size()) { + case 0: result = m.mk_true(); break; + case 1: result = m_background[0].get(); break; + default: result = m.mk_and(m_background.size(), m_background.c_ptr()); break; + } + return result; + } + + void context::assert_expr(expr* e) { + TRACE("dl", tout << mk_ismt2_pp(e, m) << "\n";); + m_background.push_back(e); + } + + + void context::cancel() { + m_cancel = true; + if (m_pdr.get()) m_pdr->cancel(); + } + + void context::cleanup() { + m_cancel = false; + if (m_pdr.get()) m_pdr->cleanup(); + } + + class context::engine_type_proc { + ast_manager& m; + arith_util a; + DL_ENGINE m_engine; + + public: + engine_type_proc(ast_manager& m): m(m), a(m), m_engine(DATALOG_ENGINE) {} + + DL_ENGINE get_engine() const { return m_engine; } + void operator()(expr* e) { + if (is_quantifier(e)) { + m_engine = QPDR_ENGINE; + } + else if (a.is_int_real(e) && m_engine != QPDR_ENGINE) { + m_engine = PDR_ENGINE; + } + else if (is_var(e) && m.is_bool(e)) { + m_engine = PDR_ENGINE; + } + } + }; + + void context::configure_engine() { + symbol e = m_params.get_sym(":engine", symbol()); + + if (e == symbol("datalog")) { + m_engine = DATALOG_ENGINE; + } + else if (e == symbol("pdr")) { + m_engine = PDR_ENGINE; + } + else if (e == symbol("qpdr")) { + m_engine = QPDR_ENGINE; + } + else if (e == symbol("bmc")) { + m_engine = BMC_ENGINE; + } + + if (m_engine == LAST_ENGINE) { + expr_fast_mark1 mark; + engine_type_proc proc(m); + m_engine = DATALOG_ENGINE; + for (unsigned i = 0; m_engine == DATALOG_ENGINE && i < m_rule_set.get_num_rules(); ++i) { + rule * r = m_rule_set.get_rule(i); + quick_for_each_expr(proc, mark, r->get_head()); + for (unsigned j = 0; j < r->get_tail_size(); ++j) { + quick_for_each_expr(proc, mark, r->get_tail(j)); + } + m_engine = proc.get_engine(); + } + } + } + + lbool context::query(expr* query) { + new_query(); + rule_set::iterator it = m_rule_set.begin(), end = m_rule_set.end(); + rule_ref r(m_rule_manager); + for (; it != end; ++it) { + r = *it; + check_rule(r); + } + + switch(get_engine()) { + case DATALOG_ENGINE: + return dl_query(query); + case PDR_ENGINE: + return pdr_query(query); + case QPDR_ENGINE: + return pdr_query(query); + case BMC_ENGINE: + return bmc_query(query); + default: + UNREACHABLE(); + return dl_query(query); + } + } + + void context::new_query() { + if (m_last_result_relation) { + m_last_result_relation->deallocate(); + m_last_result_relation = 0; + } + m_last_status = OK; + m_last_answer = get_manager().mk_true(); + } + + void context::ensure_pdr() { + if (!m_pdr.get()) { + m_pdr = alloc(pdr::dl_interface, *this); + } + } + + lbool context::pdr_query(expr* query) { + ensure_pdr(); + lbool result = m_pdr->query(query); + m_last_answer = m_pdr->get_answer(); + return result; + } + + void context::ensure_bmc() { + if (!m_bmc.get()) { + m_bmc = alloc(bmc, *this); + } + } + + lbool context::bmc_query(expr* query) { + ensure_bmc(); + lbool result = m_bmc->query(query); + m_last_answer = m_bmc->get_answer(); + return result; + } + +#define BEGIN_QUERY() \ + rule_set original_rules(get_rules()); \ + decl_set original_preds; \ + collect_predicates(original_preds); \ + bool was_closed = m_closed; \ + if (m_closed) { \ + reopen(); \ + } \ + +#define END_QUERY() \ + reopen(); \ + replace_rules(original_rules); \ + restrict_predicates(original_preds); \ + \ + if (was_closed) { \ + close(); \ + } \ + + lbool context::dl_query(unsigned num_rels, func_decl * const* rels) { + BEGIN_QUERY(); + for (unsigned i = 0; i < num_rels; ++i) { + set_output_predicate(rels[i]); + } + close(); + reset_negated_tables(); + lbool res = dl_saturate(); + + switch(res) { + case l_true: { + expr_ref_vector ans(m); + expr_ref e(m); + bool some_non_empty = num_rels == 0; + for (unsigned i = 0; i < num_rels; ++i) { + relation_base& rel = get_relation(rels[i]); + if (!rel.empty()) { + some_non_empty = true; + } + rel.to_formula(e); + ans.push_back(e); + } + SASSERT(!m_last_result_relation); + if (some_non_empty) { + m_last_answer = m.mk_and(ans.size(), ans.c_ptr()); + } + else { + m_last_answer = m.mk_false(); + res = l_false; + } + break; + } + case l_false: + m_last_answer = m.mk_false(); + break; + case l_undef: + break; + } + END_QUERY(); + return res; + } + + lbool context::dl_query(expr* query) { + BEGIN_QUERY(); + rule_manager& rm = get_rule_manager(); + rule_ref qrule(rm); + rule_ref_vector qrules(rm); + func_decl_ref query_pred(get_manager()); + try { + rm.mk_query(query, query_pred, qrules, qrule); + } + catch(default_exception& exn) { + close(); + m_last_status = INPUT_ERROR; + throw exn; + } + try { + add_rules(qrules); + } + catch (default_exception& exn) { + close(); + m_last_status = INPUT_ERROR; + throw exn; + } + + set_output_predicate(qrule->get_head()->get_decl()); + close(); + reset_negated_tables(); + + if (generate_explanations()) { + model_converter_ref mc; // ignored in Datalog mode + proof_converter_ref pc; // ignored in Datalog mode + rule_transformer transformer(*this); + //expl_plugin is deallocated when transformer goes out of scope + mk_explanations * expl_plugin = + alloc(mk_explanations, *this, explanations_on_relation_level()); + transformer.register_plugin(expl_plugin); + transform_rules(transformer, mc, pc); + + //we will retrieve the predicate with explanations instead of the original query predicate + query_pred = expl_plugin->get_e_decl(query_pred); + const rule_vector & query_rules = get_rules().get_predicate_rules(query_pred); + SASSERT(query_rules.size()==1); + qrule = query_rules.back(); + } + + if (magic_sets_for_queries()) { + model_converter_ref mc; // Ignored in Datalog mode + proof_converter_ref pc; // Ignored in Datalog mode + rule_transformer transformer(*this); + transformer.register_plugin(alloc(mk_magic_sets, *this, qrule.get())); + transform_rules(transformer, mc, pc); + } + + lbool res = dl_saturate(); + + if (res != l_undef) { + m_last_result_relation = get_relation(query_pred).clone(); + if (m_last_result_relation->empty()) { + res = l_false; + m_last_answer = m.mk_false(); + } + else { + m_last_result_relation->to_formula(m_last_answer); + } + } + + END_QUERY(); + return res; + } + + void context::reset_tables() { + get_rmanager().reset_saturated_marks(); + rule_set::decl2rules::iterator it = m_rule_set.begin_grouped_rules(); + rule_set::decl2rules::iterator end = m_rule_set.end_grouped_rules(); + for (; it != end; ++it) { + func_decl* p = it->m_key; + relation_base & rel = get_relation(p); + rel.reset(); + } + for (unsigned i = 0; i < m_table_facts.size(); ++i) { + func_decl* pred = m_table_facts[i].first; + relation_fact const& fact = m_table_facts[i].second; + get_relation(pred).add_fact(fact); + } + } + + void context::reset_negated_tables() { + rule_set::pred_set_vector const & pred_sets = m_rule_set.get_strats(); + bool non_empty = false; + for (unsigned i = 1; i < pred_sets.size(); ++i) { + func_decl_set::iterator it = pred_sets[i]->begin(), end = pred_sets[i]->end(); + for (; it != end; ++it) { + func_decl* pred = *it; + relation_base & rel = get_relation(pred); + if (!rel.empty()) { + non_empty = true; + break; + } + } + } + if (!non_empty) { + return; + } + // collect predicates that depend on negation. + func_decl_set depends_on_negation; + for (unsigned i = 1; i < pred_sets.size(); ++i) { + bool change = true; + while (change) { + change = false; + func_decl_set::iterator it = pred_sets[i]->begin(), end = pred_sets[i]->end(); + for (; it != end; ++it) { + func_decl* pred = *it; + if (depends_on_negation.contains(pred)) { + continue; + } + rule_vector const& rules = m_rule_set.get_predicate_rules(pred); + bool inserted = false; + for (unsigned j = 0; !inserted && j < rules.size(); ++j) { + rule* r = rules[j]; + unsigned psz = r->get_positive_tail_size(); + unsigned tsz = r->get_uninterpreted_tail_size(); + if (psz < tsz) { + depends_on_negation.insert(pred); + change = true; + inserted = true; + } + for (unsigned k = 0; !inserted && k < tsz; ++k) { + func_decl* tail_decl = r->get_tail(k)->get_decl(); + if (depends_on_negation.contains(tail_decl)) { + depends_on_negation.insert(pred); + change = true; + inserted = true; + } + } + } + } + } + } + func_decl_set::iterator it = depends_on_negation.begin(), end = depends_on_negation.end(); + for (; it != end; ++it) { + func_decl* pred = *it; + relation_base & rel = get_relation(pred); + + if (!rel.empty()) { + TRACE("dl", tout << "Resetting: " << mk_ismt2_pp(pred, m) << "\n";); + rel.reset(); + } + } + } + + expr* context::get_answer_as_formula() { + return m_last_answer.get(); + } + + bool context::display_certificate(std::ostream& out) { + switch(get_engine()) { + case DATALOG_ENGINE: + return false; + case PDR_ENGINE: + m_pdr->display_certificate(out); + return true; + case QPDR_ENGINE: + m_pdr->display_certificate(out); + return true; + case BMC_ENGINE: + m_bmc->display_certificate(out); + return true; + default: + return false; + } + } + + + void context::collect_statistics(statistics& st) { + switch(get_engine()) { + case DATALOG_ENGINE: + break; + case PDR_ENGINE: + m_pdr->collect_statistics(st); + break; + case QPDR_ENGINE: + m_pdr->collect_statistics(st); + break; + case BMC_ENGINE: + m_bmc->collect_statistics(st); + break; + default: + break; + } + } + + + execution_result context::get_status() { return m_last_status; } + + bool context::result_contains_fact(relation_fact const& f) { + SASSERT(m_last_result_relation); + return m_last_result_relation->contains_fact(f); + } + + // TBD: algebraic data-types declarations will not be printed. + class free_func_visitor { + ast_manager& m; + func_decl_set m_funcs; + obj_hashtable m_sorts; + public: + free_func_visitor(ast_manager& m): m(m) {} + void operator()(var * n) { } + void operator()(app * n) { + m_funcs.insert(n->get_decl()); + sort* s = m.get_sort(n); + if (s->get_family_id() == null_family_id) { + m_sorts.insert(s); + } + } + void operator()(quantifier * n) { } + func_decl_set& funcs() { return m_funcs; } + obj_hashtable& sorts() { return m_sorts; } + }; + + static void collect_free_funcs(unsigned sz, expr* const* exprs, + expr_mark& visited, free_func_visitor& v, + mk_fresh_name& fresh_names) { + for (unsigned i = 0; i < sz; ++i) { + expr* e = exprs[i]; + for_each_expr(v, visited, e); + while (is_quantifier(e)) e = to_quantifier(e)->get_expr(); + fresh_names.add(e); + } + } + + static func_decl* get_head_relation(ast_manager& m, expr* fml) { + while (is_quantifier(fml)) { + fml = to_quantifier(fml)->get_expr(); + } + expr* f1; + while (m.is_implies(fml, f1, fml)) {}; + if (is_app(fml)) { + return to_app(fml)->get_decl(); + } + else { + return 0; + } + } + + void context::display_smt2( + unsigned num_queries, + expr* const* queries, + std::ostream& out) { + ast_manager& m = get_manager(); + free_func_visitor visitor(m); + expr_mark visited; + func_decl_set rels; + unsigned num_axioms = m_background.size(); + expr* const* axioms = m_background.c_ptr(); + expr_ref fml(m); + expr_ref_vector rules(m); + { + rule_set::iterator it = m_rule_set.begin(), end = m_rule_set.end(); + for (; it != end; ++it) { + (*it)->to_formula(fml); + rules.push_back(fml); + } + } + + smt2_pp_environment_dbg env(m); + pp_params params; + mk_fresh_name fresh_names; + collect_free_funcs(num_axioms, axioms, visited, visitor, fresh_names); + collect_free_funcs(rules.size(), rules.c_ptr(), visited, visitor, fresh_names); + collect_free_funcs(num_queries, queries, visited, visitor, fresh_names); + func_decl_set funcs; + func_decl_set::iterator it = visitor.funcs().begin(); + func_decl_set::iterator end = visitor.funcs().end(); + for (; it != end; ++it) { + func_decl* f = *it; + if (f->get_family_id() != null_family_id) { + // + } + else if (is_predicate(f)) { + rels.insert(f); + } + else { + funcs.insert(f); + } + } + + it = funcs.begin(), end = funcs.end(); + + obj_hashtable& sorts = visitor.sorts(); + obj_hashtable::iterator sit = sorts.begin(), send = sorts.end(); + for (; sit != send; ++sit) { + ast_smt2_pp(out, *sit, env, params); + } + for (; it != end; ++it) { + func_decl* f = *it; + ast_smt2_pp(out, f, env, params); + out << "\n"; + } + it = rels.begin(); end = rels.end(); + for (; it != end; ++it) { + func_decl* f = *it; + out << "(declare-rel " << f->get_name() << " ("; + for (unsigned i = 0; i < f->get_arity(); ++i) { + ast_smt2_pp(out, f->get_domain(i), env, params); + if (i + 1 < f->get_arity()) { + out << " "; + } + } + out << "))\n"; + } + + declare_vars(rules, fresh_names, out); + + + for (unsigned i = 0; i < num_axioms; ++i) { + out << "(assert "; + ast_smt2_pp(out, axioms[i], env, params); + out << ")\n"; + } + for (unsigned i = 0; i < rules.size(); ++i) { + out << "(rule "; + ast_smt2_pp(out, rules[i].get(), env, params); + out << ")\n"; + } + for (unsigned i = 0; i < num_queries; ++i) { + out << "(query "; + ast_smt2_pp(out, queries[i], env, params); + out << ")\n"; + } + } + + + void context::declare_vars(expr_ref_vector& rules, mk_fresh_name& fresh_names, std::ostream& out) { + // + // replace bound variables in rules by 'var declarations' + // First remove quantifers, then replace bound variables + // by fresh constants. + // + smt2_pp_environment_dbg env(m); + var_subst vsubst(m, false); + pp_params param; + + expr_ref_vector fresh_vars(m), subst(m); + expr_ref res(m); + obj_map var_idxs; + obj_map max_vars; + for (unsigned i = 0; i < rules.size(); ++i) { + expr* r = rules[i].get(); + if (!is_quantifier(r)) { + continue; + } + quantifier* q = to_quantifier(r); + if (!q->is_forall()) { + continue; + } + if (has_quantifiers(q->get_expr())) { + continue; + } + max_vars.reset(); + subst.reset(); + unsigned max_var = 0; + unsigned num_vars = q->get_num_decls(); + for (unsigned j = 0; j < num_vars; ++j) { + sort* s = q->get_decl_sort(num_vars-1-j); + // maximal var for the given sort. + if (!max_vars.find(s, max_var)) { + max_var = 0; + } + else { + ++max_var; + } + max_vars.insert(s, max_var); + + // index into fresh variable array. + unsigned fresh_var_idx = 0; + obj_map::obj_map_entry* e = var_idxs.insert_if_not_there2(s, unsigned_vector()); + unsigned_vector& vars = e->get_data().m_value; + if (max_var >= vars.size()) { + SASSERT(vars.size() == max_var); + vars.push_back(fresh_vars.size()); + symbol name = fresh_names.next(); + fresh_vars.push_back(m.mk_const(name, s)); + out << "(declare-var " << name << " "; + ast_smt2_pp(out, s, env, param); + out << ")\n"; + } + subst.push_back(fresh_vars[vars[max_var]].get()); + } + + vsubst(q->get_expr(), subst.size(), subst.c_ptr(), res); + rules[i] = res.get(); + } + } + + +}; + diff --git a/lib/dl_context.h b/lib/dl_context.h new file mode 100644 index 000000000..5db3c22b7 --- /dev/null +++ b/lib/dl_context.h @@ -0,0 +1,464 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_context.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-05-18. + +Revision History: + +--*/ +#ifndef _DL_CONTEXT_H_ +#define _DL_CONTEXT_H_ + +#include"arith_decl_plugin.h" +#include"front_end_params.h" +#include"map.h" +#include"th_rewriter.h" +#include"str_hashtable.h" +#include"var_subst.h" +#include"dl_base.h" +#include"dl_costs.h" +#include"dl_decl_plugin.h" +#include"dl_relation_manager.h" +#include"dl_rule_set.h" +#include"pdr_dl_interface.h" +#include"dl_bmc_engine.h" +#include"lbool.h" +#include"statistics.h" +#include"params.h" +#include"trail.h" +#include"dl_external_relation.h" +#include"model_converter.h" +#include"proof_converter.h" +#include"model2expr.h" + +namespace datalog { + + class rule_transformer; + + enum execution_result { + OK, + TIMEOUT, + MEMOUT, + INPUT_ERROR + }; + + class context { + public: + typedef unsigned finite_element; + enum sort_kind { + SK_UINT64, + SK_SYMBOL + }; + + private: + class sort_domain; + class symbol_sort_domain; + class uint64_sort_domain; + class restore_rules; + class contains_pred; + + typedef hashtable symbol_set; + typedef map sym2decl; + typedef obj_map > pred2syms; + typedef obj_map sort_domain_map; + typedef vector > fact_vector; + + + ast_manager & m; + front_end_params& m_fparams; + params_ref m_params; + dl_decl_util m_decl_util; + th_rewriter m_rewriter; + var_subst m_var_subst; + relation_manager m_rmanager; + rule_manager m_rule_manager; + + trail_stack m_trail; + ast_ref_vector m_pinned; + app_ref_vector m_vars; + sort_domain_map m_sorts; + func_decl_set m_preds; + sym2decl m_preds_by_name; + pred2syms m_argument_var_names; + decl_set m_output_preds; + rule_set m_rule_set; + expr_ref_vector m_background; + + scoped_ptr m_pdr; + scoped_ptr m_bmc; + + bool m_closed; + bool m_saturation_was_run; + execution_result m_last_status; + relation_base * m_last_result_relation; + expr_ref m_last_answer; + DL_ENGINE m_engine; + volatile bool m_cancel; + fact_vector m_table_facts; + + bool is_fact(app * head) const; + bool has_sort_domain(relation_sort s) const; + sort_domain & get_sort_domain(relation_sort s); + const sort_domain & get_sort_domain(relation_sort s) const; + + relation_plugin & get_ordinary_relation_plugin(symbol relation_name); + + class engine_type_proc; + + + public: + context(ast_manager & m, front_end_params& params, params_ref const& p = params_ref()); + ~context(); + void reset(); + + void push(); + void pop(); + + relation_base & get_relation(func_decl * pred) { return get_rmanager().get_relation(pred); } + relation_base * try_get_relation(func_decl * pred) const { return get_rmanager().try_get_relation(pred); } + + bool saturation_was_run() const { return m_saturation_was_run; } + void notify_saturation_was_run() { m_saturation_was_run = true; } + + /** + \brief Store the relation \c rel under the predicate \c pred. The \c context object + takes over the ownership of the relation object. + */ + void store_relation(func_decl * pred, relation_base * rel) { + get_rmanager().store_relation(pred, rel); + } + + void configure_engine(); + + ast_manager & get_manager() const { return m; } + relation_manager & get_rmanager() { return m_rmanager; } + const relation_manager & get_rmanager() const { return m_rmanager; } + rule_manager & get_rule_manager() { return m_rule_manager; } + front_end_params & get_fparams() const { return m_fparams; } + params_ref const& get_params() const { return m_params; } + DL_ENGINE get_engine() { configure_engine(); return m_engine; } + th_rewriter& get_rewriter() { return m_rewriter; } + var_subst & get_var_subst() { return m_var_subst; } + dl_decl_util & get_decl_util() { return m_decl_util; } + + bool output_profile() const { return m_params.get_bool(":output-profile", false); } + bool fix_unbound_vars() const { return m_params.get_bool(":fix-unbound-vars", false); } + symbol default_table() const { return m_params.get_sym(":default-table", symbol("sparse")); } + symbol default_relation() const { return m_params.get_sym(":default-relation", external_relation_plugin::get_name()); } + symbol default_table_checker() const { return m_params.get_sym(":default-table-checker", symbol("sparse")); } + bool default_table_checked() const { return m_params.get_bool(":default-table-checked", false); } + bool dbg_fpr_nonempty_relation_signature() const { return m_params.get_bool(":dbg-fpr-nonempty-relation-signatures", false); } + unsigned dl_profile_milliseconds_threshold() const { return m_params.get_uint(":profile-milliseconds-threshold", 0); } + bool all_or_nothing_deltas() const { return m_params.get_bool(":all-or-nothing-deltas", false); } + bool compile_with_widening() const { return m_params.get_bool(":compile-with-widening", false); } + bool unbound_compressor() const { return m_params.get_bool(":unbound-compressor", true); } + bool similarity_compressor() const { return m_params.get_bool(":similarity-compressor", true); } + unsigned similarity_compressor_threshold() const { return m_params.get_uint(":similarity-compressor-threshold", 11); } + unsigned soft_timeout() const { return m_fparams.m_soft_timeout; } + unsigned initial_restart_timeout() const { return m_params.get_uint(":initial-restart-timeout", 0); } + bool generate_explanations() const { return m_params.get_bool(":generate-explanations", false); } + bool explanations_on_relation_level() const { return m_params.get_bool(":explanations-on-relation-level", false); } + bool magic_sets_for_queries() const { return m_params.get_bool(":magic-sets-for-queries", false); } + bool eager_emptiness_checking() const { return m_params.get_bool(":eager-emptiness-checking", true); } + + void register_finite_sort(sort * s, sort_kind k); + + /** + Register uninterpreted constant to be used as an implicitly quantified variable. + The variable gets quantified in the formula passed to rule::mk_rule_from_horn. + */ + + void register_variable(func_decl* var); + + app_ref_vector const& get_variables() const { return m_vars; } + + /** + Register datalog relation. + + If names is true, we associate the predicate with its name, so that it can be + retrieved by the try_get_predicate_decl() function. Auxiliary predicates introduced + e.g. by rule transformations do not need to be named. + */ + void register_predicate(func_decl * pred, bool named = true); + + bool is_predicate(func_decl * pred) const; + + /** + \brief If a predicate name has a \c func_decl object assigned, return pointer to it; + otherwise return 0. + + Not all \c func_decl object used as relation identifiers need to be assigned to their + names. Generally, the names coming from the parses are registered here. + */ + func_decl * try_get_predicate_decl(symbol pred_name) const; + + /** + \brief Create a fresh head predicate declaration. + + */ + func_decl * mk_fresh_head_predicate(symbol const & prefix, symbol const & suffix, + unsigned arity, sort * const * domain, func_decl* orig_pred=0); + + + /** + \brief Return number of a symbol in a DK_SYMBOL kind sort (\see register_sort() ) + */ + finite_element get_constant_number(relation_sort sort, symbol s); + /** + \brief Return number of a symbol in a DK_UINT64 kind sort (\see register_sort() ) + */ + finite_element get_constant_number(relation_sort srt, uint64 el); + + /** + \brief Output name of constant with number \c num in sort \c sort. + */ + void print_constant_name(relation_sort sort, uint64 num, std::ostream & out); + + bool try_get_sort_constant_count(relation_sort srt, uint64 & constant_count); + + uint64 get_sort_size_estimate(relation_sort srt); + + /** + \brief Assign names of variables used in the declaration of a predicate. + + These names are used when printing out the relations to make the output conform + to the one of bddbddb. + */ + void set_argument_names(const func_decl * pred, svector var_names); + symbol get_argument_name(const func_decl * pred, unsigned arg_index); + + void set_predicate_representation(func_decl * pred, unsigned relation_name_cnt, + symbol * const relation_names); + + void set_output_predicate(func_decl * pred); + bool is_output_predicate(func_decl * pred) { return m_output_preds.contains(pred); } + const decl_set & get_output_predicates() const { return m_output_preds; } + + rule_set const & get_rules() { return m_rule_set; } + + void add_fact(app * head); + void add_fact(func_decl * pred, const relation_fact & fact); + + + void add_rule(rule_ref& r); + void add_rules(rule_ref_vector& rs); + + void assert_expr(expr* e); + expr_ref get_background_assertion(); + + /** + Method exposed from API for adding rules. + */ + void add_rule(expr* rl, symbol const& name); + + + /** + Update a named rule. + */ + void update_rule(expr* rl, symbol const& name); + + /** + Retrieve the maximal number of relevant unfoldings of 'pred' + with respect to the current state. + */ + unsigned get_num_levels(func_decl* pred); + + /** + Retrieve the current cover of 'pred' up to 'level' unfoldings. + Return just the delta that is known at 'level'. To + obtain the full set of properties of 'pred' one should query + at 'level+1', 'level+2' etc, and include level=-1. + */ + expr_ref get_cover_delta(int level, func_decl* pred); + + /** + Add a property of predicate 'pred' at 'level'. + It gets pushed forward when possible. + */ + void add_cover(int level, func_decl* pred, expr* property); + + /** + \brief Check rule subsumption. + */ + bool check_subsumes(rule const& stronger_rule, rule const& weaker_rule); + + /** + \brief Check if rule is well-formed according to engine. + */ + void check_rule(rule_ref& r); + + /** + \brief Return true if facts to \c pred can be added using the \c add_table_fact() function. + + This function should return true if \c pred is represented by a table_relation + and there is no transformation of relation values before they are put into the + table. + */ + bool can_add_table_fact(func_decl * pred); + void add_table_fact(func_decl * pred, const table_fact & fact); + void add_table_fact(func_decl * pred, unsigned num_args, unsigned args[]); + + /** + \brief To be called after all rules are added. + */ + void close(); + void ensure_closed(); + + /** + \brief Undo the effect of the \c close operation. + */ + void reopen(); + void ensure_opened(); + + void transform_rules(model_converter_ref& mc, proof_converter_ref& pc); + void transform_rules(rule_transformer& trans, model_converter_ref& mc, proof_converter_ref& pc); + void replace_rules(rule_set & rs); + + void apply_default_transformation(model_converter_ref& mc, proof_converter_ref& pc); + + void collect_params(param_descrs& r); + + void updt_params(params_ref const& p); + + void collect_predicates(decl_set & res); + /** + \brief Restrict the set of used predicates to \c res. + + The function deallocates unsused relations, it does not deal with rules. + */ + void restrict_predicates(const decl_set & res); + + void display_rules(std::ostream & out) const { + m_rule_set.display(out); + } + void display_facts(std::ostream & out) const { + m_rmanager.display(out); + } + + void display(std::ostream & out) const { + display_rules(out); + display_facts(out); + } + + void display_smt2(unsigned num_queries, expr* const* queries, std::ostream& out); + + // ----------------------------------- + // + // basic usage methods + // + // ----------------------------------- + + void cancel(); + + void cleanup(); + + /** + \brief check if query 'q' is satisfied under asserted rules and background. + + If successful, return OK and into \c result assign a relation with all + tuples matching the query. Otherwise return reason for failure and do not modify + \c result. + + The numbers of variables in the query body must form a contiguous sequence + starting from zero. + + The caller becomes an owner of the relation object returned in \c result. The + relation object, however, should not outlive the datalog context since it is + linked to a relation plugin in the context. + */ + + lbool query(expr* q); + + /** + Query multiple output relations. + */ + lbool dl_query(unsigned num_rels, func_decl * const* rels); + + /** + Reset tables that are under negation. + */ + void reset_negated_tables(); + + /** + Just reset all tables. + */ + void reset_tables(); + + /** + \brief retrieve last proof status. + */ + execution_result get_status(); + + /** + \brief retrieve formula corresponding to query that returns l_true. + The formula describes one or more instances of the existential variables + in the query that are derivable. + */ + expr* get_answer_as_formula(); + + + void collect_statistics(statistics& st); + + /** + \brief Display a certificate for reachability and/or unreachability. + */ + bool display_certificate(std::ostream& out); + + /** + \brief query result if it contains fact. + */ + bool result_contains_fact(relation_fact const& f); + + /** + \brief display facts generated for query. + */ + void display_output_facts(std::ostream & out) const { + m_rmanager.display_output_tables(out); + } + + /** + \brief expose datalog saturation for test. + */ + lbool dl_saturate(); + + private: + + void ensure_pdr(); + + void ensure_bmc(); + + void new_query(); + + lbool dl_query(expr* query); + + lbool pdr_query(expr* query); + + lbool bmc_query(expr* query); + + void check_quantifier_free(rule_ref& r); + void check_uninterpreted_free(rule_ref& r); + void check_existential_tail(rule_ref& r); + void check_positive_predicates(rule_ref& r); + + // auxilary functions for SMT2 pretty-printer. + void declare_vars(expr_ref_vector& rules, mk_fresh_name& mk_fresh, std::ostream& out); + + //undefined and private copy constructor and operator= + context(const context&); + context& operator=(const context&); + }; + +}; + +#endif /* _DL_CONTEXT_H_ */ + diff --git a/lib/dl_costs.cpp b/lib/dl_costs.cpp new file mode 100644 index 000000000..0b7641093 --- /dev/null +++ b/lib/dl_costs.cpp @@ -0,0 +1,160 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_costs.cpp + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-09-20. + +Revision History: + +--*/ + +#include "debug.h" +#include "stopwatch.h" +#include "dl_context.h" +#include "dl_rule.h" +#include "dl_costs.h" + +namespace datalog { + + // ----------------------------------- + // + // costs + // + // ----------------------------------- + + costs::costs() : milliseconds(0), instructions(0) {} + + bool costs::empty() const { + return !milliseconds && !instructions; + } + + void costs::reset() { + milliseconds = 0; + instructions = 0; + } + + costs costs::operator-(const costs & o) const { + costs res(*this); + SASSERT(milliseconds>o.milliseconds); + res.milliseconds-=o.milliseconds; + SASSERT(instructions>o.instructions); + res.instructions-=o.instructions; + return res; + } + + void costs::operator+=(const costs & o) { + milliseconds+=o.milliseconds; + instructions+=o.instructions; + } + + bool costs::passes_thresholds(context & ctx) const { + return milliseconds >= ctx.dl_profile_milliseconds_threshold(); + } + + void costs::output(std::ostream & out) const { + out << "instr: " << instructions << " time: " << milliseconds << "ms"; + } + + + // ----------------------------------- + // + // accounted_object + // + // ----------------------------------- + + accounted_object::~accounted_object() { + if(m_parent_object) { + SASSERT(m_context); + m_context->get_rule_manager().dec_ref(m_parent_object); + } + } + + void accounted_object::set_accounting_parent_object(context & ctx, rule * parent) { + if(m_parent_object) { + SASSERT(m_context); + SASSERT(m_context==&ctx); + m_context->get_rule_manager().dec_ref(m_parent_object); + } + m_context = &ctx; + m_parent_object = parent; + m_context->get_rule_manager().inc_ref(m_parent_object); + } + + void accounted_object::process_costs() { + costs delta = get_current_costs(); + if(delta.empty()) { + return; + } + get_current_costs().reset(); + accounted_object * obj = this; + do { + obj->m_processed_cost+=delta; + obj=obj->m_parent_object; + } while(obj); + } + + void accounted_object::get_total_cost(costs & result) const { + result.reset(); + result+=m_current_cost; + result+=m_processed_cost; + } + + bool accounted_object::passes_output_thresholds(context & ctx) const { + costs c; + get_total_cost(c); + return c.passes_thresholds(ctx); + } + + + void accounted_object::output_profile(context & ctx, std::ostream & out) const { + costs c; + get_total_cost(c); + c.output(out); + } + + + // ----------------------------------- + // + // cost_recorder + // + // ----------------------------------- + + cost_recorder::cost_recorder() : m_obj(0) { + m_stopwatch = alloc(stopwatch); + m_stopwatch->start(); + } + + cost_recorder::~cost_recorder() { + if(m_obj) { + finish(); + } + dealloc(m_stopwatch); + } + + void cost_recorder::start(accounted_object * obj) { + uint64 curr_time = static_cast(m_stopwatch->get_current_seconds()*1000); + if(m_obj) { + costs::time_type time_delta = static_cast(curr_time-m_last_time); + costs & c = m_obj->get_current_costs(); + c.instructions++; + c.milliseconds+=time_delta; + m_obj->m_being_recorded = false; + } + m_running = obj!=0; + m_obj = obj; + m_last_time = curr_time; + if(obj) { + m_obj->m_being_recorded = true; + } + } + +}; diff --git a/lib/dl_costs.h b/lib/dl_costs.h new file mode 100644 index 000000000..0fc60451d --- /dev/null +++ b/lib/dl_costs.h @@ -0,0 +1,115 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_costs.h + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-09-20. + +Revision History: + +--*/ + +#ifndef _DL_COSTS_H_ +#define _DL_COSTS_H_ + +#include + +#include "ast.h" + +class stopwatch; + +namespace datalog { + + class context; + class rule; + + class cost_recorder; + + struct costs { + typedef unsigned time_type; + + time_type milliseconds; + unsigned instructions; + + costs(); + + bool empty() const; + void reset(); + + costs operator-(const costs & o) const; + void operator+=(const costs & o); + + bool passes_thresholds(context & ctx) const; + + void output(std::ostream & out) const; + }; + + + class accounted_object { + friend class cost_recorder; + + context * m_context; + rule * m_parent_object; + + costs m_current_cost; + costs m_processed_cost; + bool m_being_recorded; + protected: + accounted_object() : m_context(0), m_parent_object(0), m_being_recorded(false) {} + ~accounted_object(); + public: + + void set_accounting_parent_object(context & ctx, rule * parent); + rule * get_parent_object() const { return m_parent_object; } + + costs & get_current_costs() { return m_current_cost; } + const costs & get_current_costs() const { return m_current_cost; } + const costs & get_processed_costs() const { return m_processed_cost; } + void get_total_cost(costs & result) const; + bool being_recorded() const { return m_being_recorded; } + + void process_costs(); + + bool passes_output_thresholds(context & ctx) const; + void output_profile(context & ctx, std::ostream & out) const; + + private: + //private and undefined copy constructor and operator= to avoid the default ones + accounted_object(const accounted_object &); + accounted_object& operator=(const accounted_object &); + }; + + + class cost_recorder { + accounted_object * m_obj; + //it's a pointer to avoid everything depending on the stopwatch.h + //(and transitively then on windows.h) header file + stopwatch * m_stopwatch; + + bool m_running; + uint64 m_last_time; + public: + cost_recorder(); + ~cost_recorder(); + /** + \brief Start recording costs for the next object. + + If the recording of the previous object did not finish, it will be finished here. + Also, it will be done more efficiently than if the \c finish() function was called + before separately. + */ + void start(accounted_object *); + void finish() { start(0); } + }; +}; + +#endif /* _DL_COSTS_H_ */ + diff --git a/lib/dl_decl_plugin.cpp b/lib/dl_decl_plugin.cpp new file mode 100644 index 000000000..e1df89285 --- /dev/null +++ b/lib/dl_decl_plugin.cpp @@ -0,0 +1,831 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + dl_decl_plugin.h + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2010-04-10 + +Revision History: + +--*/ +#include + +#include "ast_pp.h" +#include "array_decl_plugin.h" +#include "datatype_decl_plugin.h" +#include "dl_decl_plugin.h" +#include "warning.h" + +namespace datalog { + + dl_decl_plugin::dl_decl_plugin() : + m_store_sym("store"), + m_empty_sym("empty"), + m_is_empty_sym("is_empty"), + m_join_sym("join"), + m_union_sym("union"), + m_widen_sym("widen"), + m_project_sym("project"), + m_filter_sym("filter"), + m_negation_filter_sym("negation_filter"), + m_rename_sym("rename"), + m_complement_sym("complement"), + m_select_sym("select"), + m_clone_sym("clone"), + m_num_sym("N"), + m_lt_sym("<"), + m_le_sym("<="), + m_rule_sym("R"), + m_hyper_resolve_sym("hyper-res") + { + } + + bool dl_decl_plugin::check_bounds(char const* msg, unsigned low, unsigned up, unsigned val) const { + if (low <= val && val <= up) { + return true; + } + std::ostringstream buffer; + buffer << msg << ", value is not within bound " << low << " <= " << val << " <= " << up; + m_manager->raise_exception(buffer.str().c_str()); + return false; + } + + bool dl_decl_plugin::check_domain(unsigned low, unsigned up, unsigned val) const { + return check_bounds("unexpected number of arguments", low, up, val); + } + + bool dl_decl_plugin::check_params(unsigned low, unsigned up, unsigned val) const { + return check_bounds("unexpected number of parameters", low, up, val); + } + + sort * dl_decl_plugin::mk_relation_sort( unsigned num_parameters, parameter const * parameters) { + bool is_finite = true; + rational r(1); + for (unsigned i = 0; is_finite && i < num_parameters; ++i) { + if (!parameters[i].is_ast() || !is_sort(parameters[i].get_ast())) { + m_manager->raise_exception("expecting sort parameters"); + return 0; + } + sort* s = to_sort(parameters[i].get_ast()); + sort_size sz1 = s->get_num_elements(); + if (sz1.is_finite()) { + r *= rational(sz1.size(),rational::ui64()); + } + else { + is_finite = false; + } + } + sort_size sz; + if (is_finite && r.is_uint64()) { + sz = sort_size::mk_finite(r.get_uint64()); + } + else { + sz = sort_size::mk_very_big(); + } + sort_info info(m_family_id, DL_RELATION_SORT, sz, num_parameters, parameters); + return m_manager->mk_sort(symbol("Table"),info); + } + + sort * dl_decl_plugin::mk_finite_sort(unsigned num_params, parameter const* params) { + if (num_params != 2) { + m_manager->raise_exception("expecting two parameters"); + return 0; + } + if (!params[0].is_symbol()) { + m_manager->raise_exception("expecting symbol"); + return 0; + } + + if (!params[1].is_rational() || !params[1].get_rational().is_uint64()) { + m_manager->raise_exception("expecting rational"); + return 0; + } + sort_size sz = sort_size::mk_finite(params[1].get_rational().get_uint64()); + sort_info info(m_family_id, DL_FINITE_SORT, sz, num_params, params); + return m_manager->mk_sort(params[0].get_symbol(),info); + } + + sort* dl_decl_plugin::mk_rule_sort() { + sort_size sz(sort_size::mk_infinite()); + sort_info info(m_family_id, DL_RULE_SORT, sz, 0, 0); + return m_manager->mk_sort(m_rule_sym, info); + } + + sort * dl_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) { + switch(k) { + case DL_RELATION_SORT: + return mk_relation_sort(num_parameters, parameters); + case DL_FINITE_SORT: + return mk_finite_sort(num_parameters, parameters); + case DL_RULE_SORT: + return mk_rule_sort(); + default: + UNREACHABLE(); + } + return 0; + } + + bool dl_decl_plugin::is_rel_sort(sort* r) { + ptr_vector sorts; + return is_rel_sort(r, sorts); + } + + bool dl_decl_plugin::is_rel_sort(sort* r, ptr_vector& sorts) { + if (!is_sort_of(r, m_family_id, DL_RELATION_SORT)) { + m_manager->raise_exception("expected relation sort"); + return false; + } + unsigned n = r->get_num_parameters(); + for (unsigned i = 0; i < n; ++i) { + parameter const& p = r->get_parameter(i); + if (!p.is_ast() || !is_sort(p.get_ast())) { + m_manager->raise_exception("exptected sort parameter"); + return false; + } + sorts.push_back(to_sort(p.get_ast())); + } + return true; + } + + bool dl_decl_plugin::is_fin_sort(sort* r) { + if (!is_sort_of(r, m_family_id, DL_FINITE_SORT)) { + m_manager->raise_exception("expected finite sort"); + return false; + } + return true; + } + + func_decl* dl_decl_plugin::mk_store_select(decl_kind k, unsigned arity, sort* const* domain) { + bool is_store = (k == OP_RA_STORE); + ast_manager& m = *m_manager; + symbol sym = is_store?m_store_sym:m_select_sym; + sort * r = domain[0]; + if (!is_store) { + r = m.mk_bool_sort(); + } + ptr_vector sorts; + if (!is_rel_sort(r, sorts)) { + return 0; + } + if (sorts.size() + 1 != arity) { + m_manager->raise_exception("wrong arity supplied to relational access"); + return 0; + } + for (unsigned i = 0; i < sorts.size(); ++i) { + if (sorts[i] != domain[i+1]) { + IF_VERBOSE(0, + verbose_stream() << "Domain: " << mk_pp(domain[0], m) << "\n" << + mk_pp(sorts[i], m) << "\n" << + mk_pp(domain[i+1], m) << "\n";); + m_manager->raise_exception("sort miss-match for relational access"); + return 0; + } + } + func_decl_info info(m_family_id, k, 0, 0); + return m.mk_func_decl(sym, arity, domain, r, info); + } + + func_decl * dl_decl_plugin::mk_empty(parameter const& p) { + ast_manager& m = *m_manager; + if (!p.is_ast() || !is_sort(p.get_ast())) { + m_manager->raise_exception("expected sort parameter"); + return 0; + } + sort* r = to_sort(p.get_ast()); + if (!is_rel_sort(r)) { + return 0; + } + func_decl_info info(m_family_id, OP_RA_EMPTY, 1, &p); + return m.mk_func_decl(m_empty_sym, 0, (sort*const*)0, r, info); + } + + func_decl* dl_decl_plugin::mk_project(unsigned num_params, parameter const* params, sort* r) { + ast_manager& m = *m_manager; + ptr_vector sorts; + vector ps; + TRACE("dl_decl_plugin", + tout << mk_pp(r, m) << " "; + for (unsigned i = 0; i < num_params; ++i) { + tout << params[i] << " "; + } + tout << "\n"; + ); + if (!is_rel_sort(r, sorts)) { + return 0; + } + SASSERT(sorts.size() >= num_params); + // populate ps + unsigned j = 0, i = 0; + for (; i < num_params; ++i) { + if (!params[i].is_int()) { + m_manager->raise_exception("expecting integer parameter"); + return 0; + } + unsigned k = params[i].get_int(); + if (j > k) { + m_manager->raise_exception("arguments to projection should be increasing"); + return 0; + } + while (j < k) { + ps.push_back(parameter(sorts[j])); + ++j; + } + ++j; + } + for (; j < sorts.size(); ++j) { + ps.push_back(parameter(sorts[j])); + } + SASSERT(ps.size() + num_params == sorts.size()); + sort* r2 = m.mk_sort(m_family_id, DL_RELATION_SORT, ps.size(), ps.c_ptr()); + func_decl_info info(m_family_id, OP_RA_PROJECT, num_params, params); + return m.mk_func_decl(m_project_sym, 1, &r, r2, info); + } + + func_decl * dl_decl_plugin::mk_unionw(decl_kind k, sort* s1, sort* s2) { + ast_manager& m = *m_manager; + if (s1 != s2) { + m_manager->raise_exception("sort miss-match for arguments to union"); + return 0; + } + if (!is_rel_sort(s1)) { + return 0; + } + sort* domain[2] = { s1, s2 }; + func_decl_info info(m_family_id, k, 0, 0); + return m.mk_func_decl(m_union_sym, 2, domain, s1, info); + } + + func_decl * dl_decl_plugin::mk_filter(parameter const& p, sort* r) { + ast_manager& m = *m_manager; + ptr_vector sorts; + if (!is_rel_sort(r, sorts)) { + return 0; + } + if (!p.is_ast() || !is_expr(p.get_ast())) { + m_manager->raise_exception("ast expression expected to filter"); + } + expr* f = to_expr(p.get_ast()); + // 1. f is of Boolean type. + // 2. the free variables in f correspond to column types of r. + if (!m.is_bool(f)) { + m_manager->raise_exception("filter predicate should be of Boolean type"); + return 0; + } + ptr_vector todo; + todo.push_back(f); + ast_mark mark; + while (!todo.empty()) { + expr* e = todo.back(); + todo.pop_back(); + if (mark.is_marked(e)) { + continue; + } + mark.mark(e, true); + unsigned idx; + switch(e->get_kind()) { + case AST_VAR: + idx = to_var(e)->get_idx(); + if (idx >= sorts.size()) { + m_manager->raise_exception("illegal index"); + return 0; + } + if (sorts[idx] != m.get_sort(e)) { + m_manager->raise_exception("sort miss-match in filter"); + return 0; + } + break; + case AST_APP: + for (unsigned i = 0; i < to_app(e)->get_num_args(); ++i) { + todo.push_back(to_app(e)->get_arg(i)); + } + break; + case AST_QUANTIFIER: + m_manager->raise_exception("quantifiers are not allowed in filter expressions"); + return 0; + default: + m_manager->raise_exception("unexpected filter expression kind"); + return 0; + } + } + func_decl_info info(m_family_id, OP_RA_FILTER, 1, &p); + return m.mk_func_decl(m_filter_sym, 1, &r, r, info); + } + + func_decl * dl_decl_plugin::mk_rename(unsigned num_params, parameter const* params, sort* r) { + ptr_vector sorts; + if (!is_rel_sort(r, sorts)) { + return 0; + } + unsigned index0; + sort* last_sort = 0; + for (unsigned i = 0; i < num_params; ++i) { + parameter const& p = params[i]; + if (!p.is_int()) { + m_manager->raise_exception("expected integer parameter"); + return 0; + } + unsigned j = p.get_int(); + if (j >= sorts.size()) { + // We should not use ast_pp anymore on error messages. + // m_manager->raise_exception("index %d out of bound %s : %d", j, ast_pp(r, *m_manager).c_str(), sorts.size()); + m_manager->raise_exception("index out of bound"); + return 0; + } + if (i == 0) { + index0 = j; + last_sort = sorts[j]; + } + else { + std::swap(last_sort, sorts[j]); + } + } + sorts[index0] = last_sort; + vector params2; + for (unsigned i = 0; i < sorts.size(); ++i) { + params2.push_back(parameter(sorts[i])); + } + sort* rng = m_manager->mk_sort(m_family_id, DL_RELATION_SORT, params2.size(), params2.c_ptr()); + func_decl_info info(m_family_id, OP_RA_RENAME, num_params, params); + return m_manager->mk_func_decl(m_rename_sym, 1, &r, rng, info); + } + + func_decl * dl_decl_plugin::mk_join(unsigned num_params, parameter const* params, sort* r1, sort* r2) { + vector params2; + ptr_vector sorts1, sorts2; + if (!is_rel_sort(r1, sorts1)) { + return 0; + } + if (!is_rel_sort(r2, sorts2)) { + return 0; + } + for (unsigned i = 0; i < sorts1.size(); ++i) { + params2.push_back(parameter(sorts1[i])); + } + for (unsigned i = 0; i < sorts2.size(); ++i) { + params2.push_back(parameter(sorts2[i])); + } + if (0 != num_params % 2) { + m_manager->raise_exception("expecting an even number of parameters to join"); + return 0; + } + for (unsigned i = 0; i + 1 < num_params; i += 2) { + parameter const& p1 = params[i]; + parameter const& p2 = params[i+1]; + if (!p1.is_int() || !p2.is_int()) { + m_manager->raise_exception("encountered non-integer parameter"); + return 0; + } + unsigned i1 = p1.get_int(); + unsigned i2 = p2.get_int(); + if (i1 >= sorts1.size() || i2 >= sorts2.size()) { + m_manager->raise_exception("index out of bounds"); + return 0; + } + if (sorts1[i1] != sorts2[i2]) { + m_manager->raise_exception("sort miss-match in join"); + return 0; + } + } + sort* args[2] = { r1, r2 }; + sort* rng = m_manager->mk_sort(m_family_id, DL_RELATION_SORT, params2.size(), params2.c_ptr()); + func_decl_info info(m_family_id, OP_RA_JOIN, num_params, params); + return m_manager->mk_func_decl(m_join_sym, 2, args, rng, info); + } + + func_decl* dl_decl_plugin::mk_complement(sort* s) { + if (!is_rel_sort(s)) { + return 0; + } + func_decl_info info(m_family_id, OP_RA_COMPLEMENT, 0, 0); + return m_manager->mk_func_decl(m_complement_sym, 1, &s, s, info); + } + + func_decl * dl_decl_plugin::mk_negation_filter(unsigned num_params, parameter const* params, sort* r1, sort* r2) { + ptr_vector sorts1, sorts2; + if (!is_rel_sort(r1, sorts1)) { + return 0; + } + if (!is_rel_sort(r2, sorts2)) { + return 0; + } + if (0 != num_params % 2) { + m_manager->raise_exception("expecting an even number of parameters to negation filter"); + return 0; + } + for (unsigned i = 0; i + 1 < num_params; i += 2) { + parameter const& p1 = params[i]; + parameter const& p2 = params[i+1]; + if (!p1.is_int() || !p2.is_int()) { + m_manager->raise_exception("encountered non-integer parameter"); + return 0; + } + unsigned i1 = p1.get_int(); + unsigned i2 = p2.get_int(); + if (i1 >= sorts1.size() || i2 >= sorts2.size()) { + m_manager->raise_exception("index out of bounds"); + return 0; + } + if (sorts1[i1] != sorts2[i2]) { + m_manager->raise_exception("sort miss-match in join"); + return 0; + } + } + sort* args[2] = { r1, r2 }; + func_decl_info info(m_family_id, OP_RA_NEGATION_FILTER, num_params, params); + return m_manager->mk_func_decl(m_negation_filter_sym, 2, args, r1, info); + } + + func_decl * dl_decl_plugin::mk_is_empty(sort* s) { + if (!is_rel_sort(s)) { + return 0; + } + func_decl_info info(m_family_id, OP_RA_IS_EMPTY, 0, 0); + sort* rng = m_manager->mk_bool_sort(); + return m_manager->mk_func_decl(m_is_empty_sym, 1, &s, rng, info); + } + + func_decl * dl_decl_plugin::mk_constant(parameter const* params) { + parameter const& p = params[0]; + parameter const& ps = params[1]; + if (!p.is_rational() || !p.get_rational().is_uint64()) { + m_manager->raise_exception("first parameter should be a rational"); + return 0; + } + if (!ps.is_ast() || !is_sort(ps.get_ast()) || !is_fin_sort(to_sort(ps.get_ast()))) { + m_manager->raise_exception("second paramter should be a finite domain sort"); + return 0; + } + sort* s = to_sort(ps.get_ast()); + func_decl_info info(m_family_id, OP_DL_CONSTANT, 2, params); + return m_manager->mk_func_decl(m_num_sym, 0, (sort*const*)0, s, info); + } + + func_decl * dl_decl_plugin::mk_compare(decl_kind k, symbol const& sym, sort *const* domain) { + if (!is_sort_of(domain[0], m_family_id, DL_FINITE_SORT)) { + m_manager->raise_exception("expecting finite domain sort"); + return 0; + } + if (domain[0] != domain[1]) { + m_manager->raise_exception("expecting two identical finite domain sorts"); + return 0; + } + func_decl_info info(m_family_id, k, 0, 0); + return m_manager->mk_func_decl(sym, 2, domain, m_manager->mk_bool_sort(), info); + } + + func_decl * dl_decl_plugin::mk_clone(sort* s) { + if (!is_rel_sort(s)) { + return 0; + } + func_decl_info info(m_family_id, OP_RA_CLONE, 0, 0); + return m_manager->mk_func_decl(m_clone_sym, 1, &s, s, info); + } + + func_decl * dl_decl_plugin::mk_hyper_res(unsigned num_params, parameter const* params, unsigned arity, sort *const* domain) { + ast_manager& m = *m_manager; + func_decl_info info(m_family_id, OP_DL_HYPER_RESOLVE, num_params, params); + return m_manager->mk_func_decl(m_hyper_resolve_sym, arity, domain, m_manager->mk_proof_sort(), info); + } + + func_decl * dl_decl_plugin::mk_func_decl( + decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + func_decl* result = 0; + switch(k) { + + case OP_RA_STORE: + case OP_RA_SELECT: + if (!check_params(0, 0, num_parameters) || + !check_domain(1, UINT_MAX, arity)) { + return 0; + } + result = mk_store_select(k, arity, domain); + break; + + case OP_RA_EMPTY: + if (!check_params( 1, 1, num_parameters) || + !check_domain(0, 0, arity)) { + return 0; + } + result = mk_empty(parameters[0]); + break; + + case OP_RA_JOIN: + if (!check_params(0, UINT_MAX, num_parameters) || + !check_domain(2, 2, arity)) { + return 0; + } + result = mk_join(num_parameters, parameters, domain[0], domain[1]); + break; + + case OP_RA_UNION: + case OP_RA_WIDEN: + if (!check_params( 0, 0, num_parameters) || + !check_domain(2, 2, arity)) { + return 0; + } + result = mk_unionw(k, domain[0], domain[1]); + break; + + case OP_RA_PROJECT: + if (!check_params( 1, UINT_MAX, num_parameters) || + !check_domain(1, 1, arity)) { + return 0; + } + result = mk_project(num_parameters, parameters, domain[0]); + break; + + case OP_RA_FILTER: + if (!check_params( 1, 1, num_parameters) || + !check_domain(1, 1, arity)) { + return 0; + } + result = mk_filter(parameters[0], domain[0]); + break; + + case OP_RA_IS_EMPTY: + if (!check_params( 0, 0, num_parameters) || + !check_domain(1, 1, arity)) { + return 0; + } + result = mk_is_empty(domain[0]); + break; + + case OP_RA_RENAME: + if (!check_params( 2, UINT_MAX, num_parameters) || + !check_domain(1, 1, arity)) { + return 0; + } + result = mk_rename(num_parameters, parameters, domain[0]); + break; + + case OP_RA_COMPLEMENT: + if (!check_params( 0, 0, num_parameters) || + !check_domain(1, 1, arity)) { + return 0; + } + result = mk_complement(domain[0]); + break; + + case OP_RA_NEGATION_FILTER: + if (!check_params(1, UINT_MAX, num_parameters) || + !check_domain(2, 2, arity)) { + return 0; + } + result = mk_negation_filter(num_parameters, parameters, domain[0], domain[1]); + break; + + case OP_RA_CLONE: + if (!check_params(0, 0, num_parameters) || !check_domain(1, 1, arity)) { + return 0; + } + result = mk_clone(domain[0]); + break; + + case OP_DL_CONSTANT: + if (!check_params( 2, 2, num_parameters) || + !check_domain(0, 0, arity)) { + return 0; + } + result = mk_constant(parameters); + break; + + case OP_DL_LT: + if (!check_params( 0, 0, num_parameters) || + !check_domain(2, 2, arity)) { + return 0; + } + result = mk_compare(OP_DL_LT, m_lt_sym, domain); + break; + + case OP_DL_HYPER_RESOLVE: + result = mk_hyper_res(num_parameters, parameters, arity, domain); + break; + + default: + m_manager->raise_exception("operator not recognized"); + return 0; + } + + TRACE("dl_decl_plugin", tout << mk_pp(result, *m_manager) << "\n";); + return result; + } + + void dl_decl_plugin::get_op_names(svector & op_names, symbol const & logic) { + + } + + void dl_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { + + } + + + dl_decl_util::ast_plugin_registrator::ast_plugin_registrator(ast_manager& m) + { + // ensure required plugins are installed into the ast_manager + m.register_decl_plugins(); + } + + dl_decl_util::dl_decl_util(ast_manager& m): + m_plugin_registrator(m), + m(m), + m_arith(m), + m_bv(m), + m_fid(m.get_family_id(symbol("datalog_relation"))) + {} + + // create a constant belonging to a given finite domain. + + app* dl_decl_util::mk_numeral(uint64 value, sort* s) { + if (is_finite_sort(s)) { + parameter params[2] = { parameter(rational(value, rational::ui64())), parameter(s) }; + return m.mk_const(m.mk_func_decl(m_fid, OP_DL_CONSTANT, 2, params, 0, (sort*const*)0)); + } + if (m_arith.is_int(s) || m_arith.is_real(s)) { + return m_arith.mk_numeral(rational(value, rational::ui64()), s); + } + if (m_bv.is_bv_sort(s)) { + return m_bv.mk_numeral(rational(value, rational::ui64()), s); + } + if (m.is_bool(s)) { + if (value == 0) { + return m.mk_false(); + } + SASSERT(value == 1); + return m.mk_true(); + } + m.raise_exception("unrecognized sort"); + return 0; + } + + bool dl_decl_util::is_numeral(expr* e, uint64& v) const { + if (is_numeral(e)) { + app* c = to_app(e); + SASSERT(c->get_decl()->get_num_parameters() == 2); + parameter const& p = c->get_decl()->get_parameter(0); + SASSERT(p.is_rational()); + SASSERT(p.get_rational().is_uint64()); + v = p.get_rational().get_uint64(); + return true; + } + return false; + } + + bool dl_decl_util::is_numeral_ext(expr* e, uint64& v) const { + if (is_numeral(e, v)) { + return true; + } + rational val; + unsigned bv_size = 0; + if (m_bv.is_numeral(e, val, bv_size) && bv_size < 64) { + SASSERT(val.is_uint64()); + v = val.get_uint64(); + return true; + } + if (m.is_true(e)) { + v = 1; + return true; + } + if (m.is_false(e)) { + v = 0; + return true; + } + return false; + } + + bool dl_decl_util::is_numeral_ext(expr* c) const { + if (is_numeral(c)) return true; + rational val; + unsigned bv_size = 0; + if (m_arith.is_numeral(c, val) && val.is_uint64()) return true; + if (m_bv.is_numeral(c, val, bv_size) && bv_size < 64) return true; + return m.is_true(c) || m.is_false(c); + } + + sort* dl_decl_util::mk_sort(const symbol& name, uint64 domain_size) { + if (domain_size == 0) { + std::stringstream sstm; + sstm << "Domain size of sort '" << name << "' may not be 0"; + throw default_exception(sstm.str()); + } + parameter params[2] = { parameter(name), parameter(rational(domain_size, rational::ui64())) }; + return m.mk_sort(m_fid, DL_FINITE_SORT, 2, params); + } + + bool dl_decl_util::try_get_size(const sort * s, uint64& size) const { + sort_size sz = s->get_info()->get_num_elements(); + if (sz.is_finite()) { + size = sz.size(); + return true; + } + return false; + } + + app* dl_decl_util::mk_lt(expr* a, expr* b) { + expr* args[2] = { a, b }; + return m.mk_app(m_fid, OP_DL_LT, 0, 0, 2, args); + } + + app* dl_decl_util::mk_le(expr* a, expr* b) { + expr* args[2] = { b, a }; + return m.mk_not(m.mk_app(m_fid, OP_DL_LT, 0, 0, 2, args)); + } + + sort* dl_decl_util::mk_rule_sort() { + return m.mk_sort(m_fid, DL_RULE_SORT); + } + + app* dl_decl_util::mk_rule(symbol const& name, unsigned num_args, expr* const* args) { + ptr_buffer sorts; + for (unsigned i = 0; i < num_args; ++i) { + sorts.push_back(m.get_sort(args[i])); + } + func_decl* f = m.mk_func_decl(name, num_args, sorts.c_ptr(), mk_rule_sort()); + return m.mk_app(f, num_args, args); + } + + proof* dl_decl_util::mk_hyper_resolve(unsigned num_premises, proof* const* premises, expr* concl, + svector > const& positions, + vector const& substs) { + ptr_vector fmls; + SASSERT(positions.size() + 1 == substs.size()); + for (unsigned i = 0; i < num_premises; ++i) { + TRACE("dl", tout << mk_pp(premises[i], m) << "\n";); + fmls.push_back(m.get_fact(premises[i])); + } + SASSERT(m.is_bool(concl)); + vector params; + for (unsigned i = 0; i < substs.size(); ++i) { + expr_ref_vector const& vec = substs[i]; + for (unsigned j = 0; j < vec.size(); ++j) { + params.push_back(parameter(vec[j])); + } + if (i + 1 < substs.size()) { + params.push_back(parameter(positions[i].first)); + params.push_back(parameter(positions[i].second)); + } + } + ptr_vector sorts; + ptr_vector args; + for (unsigned i = 0; i < num_premises; ++i) { + sorts.push_back(m.mk_proof_sort()); + args.push_back(premises[i]); + } + sorts.push_back(m.mk_bool_sort()); + args.push_back(concl); + app* result = m.mk_app(m_fid, OP_DL_HYPER_RESOLVE, params.size(), params.c_ptr(), args.size(), args.c_ptr()); + SASSERT(result->get_family_id() == m_fid); + SASSERT(result->get_decl_kind() == OP_DL_HYPER_RESOLVE); + return result; + } + + bool dl_decl_util::is_hyper_resolve( + proof* p, + proof_ref_vector& premises, + expr_ref& conclusion, + svector > & positions, + vector & substs) const { + if (!is_hyper_resolve(p)) { + return false; + } + unsigned sz = p->get_num_args(); + SASSERT(sz > 0); + for (unsigned i = 0; i + 1 < sz; ++i) { + premises.push_back(to_app(p->get_arg(i))); + } + conclusion = p->get_arg(sz-1); + func_decl* d = p->get_decl(); + unsigned num_p = d->get_num_parameters(); + parameter const* params = d->get_parameters(); + + substs.push_back(expr_ref_vector(m)); + for (unsigned i = 0; i < num_p; ++i) { + if (params[i].is_int()) { + SASSERT(i + 1 < num_p); + SASSERT(params[i+1].is_int()); + unsigned x = static_cast(params[i].get_int()); + unsigned y = static_cast(params[i+1].get_int()); + positions.push_back(std::make_pair(x, y)); + substs.push_back(expr_ref_vector(m)); + ++i; + } + else { + SASSERT(params[i].is_ast()); + ast* a = params[i].get_ast(); + SASSERT(is_expr(a)); + substs.back().push_back(to_expr(a)); + } + } + + return true; + } + +}; diff --git a/lib/dl_decl_plugin.h b/lib/dl_decl_plugin.h new file mode 100644 index 000000000..504e5ac85 --- /dev/null +++ b/lib/dl_decl_plugin.h @@ -0,0 +1,260 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + dl_decl_plugin.h + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2010-04-10 + +Revision History: + +--*/ +#ifndef _DL_DECL_PLUGIN_H_ +#define _DL_DECL_PLUGIN_H_ + +#include"ast.h" +#include "arith_decl_plugin.h" +#include "bv_decl_plugin.h" + +namespace datalog { + + + enum dl_sort_kind { + DL_RELATION_SORT, + DL_FINITE_SORT, + DL_RULE_SORT + }; + + enum dl_op_kind { + OP_RA_STORE, + OP_RA_EMPTY, + OP_RA_IS_EMPTY, + OP_RA_JOIN, + OP_RA_UNION, + OP_RA_WIDEN, + OP_RA_PROJECT, + OP_RA_FILTER, + OP_RA_NEGATION_FILTER, + OP_RA_RENAME, + OP_RA_COMPLEMENT, + OP_RA_SELECT, + OP_RA_CLONE, + OP_DL_CONSTANT, + OP_DL_LT, + OP_DL_HYPER_RESOLVE, + LAST_RA_OP + }; + + class dl_decl_plugin : public decl_plugin { + symbol m_store_sym; + symbol m_empty_sym; + symbol m_is_empty_sym; + symbol m_join_sym; + symbol m_union_sym; + symbol m_widen_sym; + symbol m_project_sym; + symbol m_filter_sym; + symbol m_negation_filter_sym; + symbol m_rename_sym; + symbol m_complement_sym; + symbol m_select_sym; + symbol m_clone_sym; + symbol m_num_sym; + symbol m_lt_sym; + symbol m_le_sym; + symbol m_rule_sym; + symbol m_hyper_resolve_sym; + + bool check_bounds(char const* msg, unsigned low, unsigned up, unsigned val) const; + bool check_domain(unsigned low, unsigned up, unsigned val) const; + bool check_params(unsigned low, unsigned up, unsigned val) const; + + bool is_rel_sort(sort* s); + bool is_rel_sort(sort* s, ptr_vector& sorts); + bool is_fin_sort(sort* r); + + func_decl * mk_store_select(decl_kind k, unsigned arity, sort * const * domain); + func_decl * mk_empty(parameter const& p); + func_decl * mk_is_empty(sort* r); + func_decl * mk_join(unsigned num_params, parameter const* params, sort* r1, sort* r2); + func_decl * mk_unionw(decl_kind k, sort* s1, sort* s2); + func_decl * mk_project(unsigned num_params, parameter const * params, sort* r); + func_decl * mk_filter(parameter const& p, sort* r); + func_decl * mk_rename(unsigned num_params, parameter const * params, sort* r); + func_decl * mk_complement(sort* r); + func_decl * mk_negation_filter(unsigned num_params, parameter const* params, sort* r1, sort* r2); + func_decl * mk_constant(parameter const* params); + func_decl * mk_compare(decl_kind k, symbol const& sym, sort*const* domain); + func_decl * mk_clone(sort* r); + func_decl * mk_rule(unsigned arity); + func_decl * mk_hyper_res(unsigned num_params, parameter const* params, unsigned arity, sort *const* domain); + + sort * mk_finite_sort(unsigned num_params, parameter const* params); + sort * mk_relation_sort(unsigned num_params, parameter const* params); + sort * mk_rule_sort(); + + public: + dl_decl_plugin(); + virtual ~dl_decl_plugin() {} + + virtual decl_plugin * mk_fresh() { return alloc(dl_decl_plugin); } + + // + // Contract for sort DL_RELATION_SORT + // parameters[0] - 1st dimension + // ... + // parameters[n-1] - nth dimension + // + // Contract for sort DL_FINITE_SORT + // parameters[0] - name + // parameters[1] - uint64 + // + virtual sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters); + + // + // Contract for func_decl: + // parameters[0] - array sort + // Contract for OP_DL_CONSTANT: + // parameters[0] - rational containing uint64 with constant value + // parameters[1] - a DL_FINITE_SORT sort of the constant + // Contract for others: + // no parameters + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + + virtual void get_op_names(svector & op_names, symbol const & logic); + + virtual void get_sort_names(svector & sort_names, symbol const & logic); + + virtual bool is_value(app* e) const { return is_app_of(e, m_family_id, OP_DL_CONSTANT); } + + }; + + class dl_decl_util { + /** + Some plugins need to be registered in the ast_manager before we + create some of the member objects (the bv_util requires bv plugin + installed). + + Doing this in the constructor of the dl_decl_plugin object is too late (as + member objects are created before we get into the constructor), so we + have this auxiliary object that is the first object of the context, + so that it gets created first. It's only purpose is that in its + constructor the required plugins are added to the ast manager. + */ + class ast_plugin_registrator { + public: + ast_plugin_registrator(ast_manager& m); + }; + + ast_plugin_registrator m_plugin_registrator; + + ast_manager& m; + arith_util m_arith; + bv_util m_bv; + family_id m_fid; + + public: + dl_decl_util(ast_manager& m); + // create a constant belonging to a given finite domain. + // the options include the DL_FINITE_SORT, BV_SORT, and BOOL_SORT + app* mk_numeral(uint64 value, sort* s); + + app* mk_lt(expr* a, expr* b); + + app* mk_le(expr* a, expr* b); + + bool is_lt(expr* a) { return is_app_of(a, m_fid, OP_DL_LT); } + + bool is_numeral(expr* c) const { return is_app_of(c, m_fid, OP_DL_CONSTANT); } + + bool is_numeral(expr* e, uint64& v) const; + + // + // Utilities for extracting constants + // from bit-vectors and finite domains. + // + bool is_numeral_ext(expr* c, uint64& v) const; + + bool is_numeral_ext(expr* c) const; + + sort* mk_sort(const symbol& name, uint64 domain_size); + + bool try_get_size(const sort *, uint64& size) const; + + bool is_finite_sort(sort* s) const { + return is_sort_of(s, m_fid, DL_FINITE_SORT); + } + + bool is_finite_sort(expr* e) const { + return is_finite_sort(m.get_sort(e)); + } + + bool is_rule_sort(sort* s) const { + return is_sort_of(s, m_fid, DL_RULE_SORT); + } + + sort* mk_rule_sort(); + + app* mk_rule(symbol const& name, unsigned num_args = 0, expr* const* args = 0); + + app* mk_fact(symbol const& name) { return mk_rule(name, 0, 0); } + + ast_manager& get_manager() const { return m; } + + family_id get_family_id() const { return m_fid; } + + /** + \brief Hyper-resolution rule that works for Horn clauses (implication) + + Somewhat related to unit resolution and resolution rule from SPC, but + a general sledgehammer rule. + The clause/implication from the first premise is the main clause. + One of the literals in each of the other premises is resolved with the main clause. + + The facts in the premises are closed formulas. Substitutions required for unification + are passed in. + + positions is a vector of pairs of positions in the main clause and the side clause. + + For clauses that are disjunctions the positions are indexed from 0 starting with the first + literal. + + We use the following (Prolog style) convention for Horn implications: + The head of a Horn implication is position 0, + the first conjunct in the body of an implication is position 1 + the second conjunct in the body of an implication is position 2 + + For general implications where the head is a disjunction, the + first n positions correspond to the n disjuncts in the head. + The next m positions correspond to the m conjuncts in the body. + */ + proof* mk_hyper_resolve(unsigned num_premises, proof* const* premises, expr* concl, + svector > const& positions, + vector const& substs); + + bool is_hyper_resolve(proof* p) const { return is_app_of(p, m_fid, OP_DL_HYPER_RESOLVE); } + + + /** + \brief extract components of a hyper-resolution proof rule. + + */ + bool is_hyper_resolve(proof* p, + proof_ref_vector& premises, + expr_ref& conclusion, + svector > & positions, + vector& substs) const; + + }; + +}; +#endif /* _DL_DECL_PLUGIN_H_ */ + diff --git a/lib/dl_external_relation.cpp b/lib/dl_external_relation.cpp new file mode 100644 index 000000000..f32509473 --- /dev/null +++ b/lib/dl_external_relation.cpp @@ -0,0 +1,456 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + dl_external_relation.cpp + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2010-05-10 + +Revision History: + +--*/ + +#include "debug.h" +#include "ast_pp.h" +#include "dl_context.h" +#include "dl_external_relation.h" +#include "dl_decl_plugin.h" + +namespace datalog { + + external_relation::external_relation(external_relation_plugin & p, const relation_signature & s, expr* r) + : relation_base(p, s), + m_rel(r, p.get_ast_manager()), + m_select_fn(p.get_ast_manager()), + m_store_fn(p.get_ast_manager()), + m_is_empty_fn(p.get_ast_manager()) + { + } + + external_relation::~external_relation() { + } + + void external_relation::mk_accessor(decl_kind k, func_decl_ref& fn, const relation_fact& f, bool destructive, expr_ref& res) const { + ast_manager& m = m_rel.get_manager(); + family_id fid = get_plugin().get_family_id(); + ptr_vector args; + args.push_back(m_rel); + for (unsigned i = 0; i < f.size(); ++i) { + args.push_back(f[i]); + } + if (!fn.get()) { + fn = m.mk_func_decl(fid, k, 0, 0, args.size(), args.c_ptr()); + } + if (destructive) { + get_plugin().reduce_assign(fn, args.size(), args.c_ptr(), 1, args.c_ptr()); + res = m_rel; + } + else { + get_plugin().reduce(fn, args.size(), args.c_ptr(), res); + } + } + + bool external_relation::empty() const { + ast_manager& m = m_rel.get_manager(); + expr* r = m_rel.get(); + expr_ref res(m); + if (!m_is_empty_fn.get()) { + family_id fid = get_plugin().get_family_id(); + const_cast(m_is_empty_fn) = m.mk_func_decl(fid, OP_RA_IS_EMPTY, 0, 0, 1, &r); + } + get_plugin().reduce(m_is_empty_fn, 1, &r, res); + return m.is_true(res); + } + + void external_relation::add_fact(const relation_fact & f) { + mk_accessor(OP_RA_STORE, m_store_fn, f, true, m_rel); + } + + bool external_relation::contains_fact(const relation_fact & f) const { + ast_manager& m = get_plugin().get_ast_manager(); + expr_ref res(m); + mk_accessor(OP_RA_SELECT, const_cast(m_select_fn), f, false, res); + return !m.is_false(res); + } + + external_relation * external_relation::clone() const { + ast_manager& m = m_rel.get_manager(); + family_id fid = get_plugin().get_family_id(); + expr* rel = m_rel.get(); + expr_ref res(m.mk_fresh_const("T", m.get_sort(rel)), m); + expr* rel_out = res.get(); + func_decl_ref fn(m.mk_func_decl(fid, OP_RA_CLONE,0,0, 1, &rel), m); + get_plugin().reduce_assign(fn, 1, &rel, 1, &rel_out); + return alloc(external_relation, get_plugin(), get_signature(), res); + } + + external_relation * external_relation::complement(func_decl* p) const { + ast_manager& m = m_rel.get_manager(); + family_id fid = get_plugin().get_family_id(); + expr_ref res(m); + expr* rel = m_rel; + func_decl_ref fn(m.mk_func_decl(fid, OP_RA_COMPLEMENT,0,0, 1, &rel), m); + get_plugin().reduce(fn, 1, &rel, res); + return alloc(external_relation, get_plugin(), get_signature(), res); + } + + void external_relation::display(std::ostream & out) const { + out << mk_pp(m_rel, m_rel.get_manager()) << "\n"; + } + + void external_relation::display_tuples(func_decl & pred, std::ostream & out) const { + display(out); + } + + + external_relation_plugin & external_relation::get_plugin() const { + return static_cast(relation_base::get_plugin()); + } + + + // ----------------------------------- + // + // external_relation_plugin + // + // ----------------------------------- + + + external_relation_plugin::external_relation_plugin(external_relation_context& ctx, relation_manager & m) + : relation_plugin(external_relation_plugin::get_name(), m), m_ext(ctx) {} + + external_relation const & external_relation_plugin::get(relation_base const& r) { + return dynamic_cast(r); + } + + external_relation & external_relation_plugin::get(relation_base & r) { + return dynamic_cast(r); + } + + relation_base * external_relation_plugin::mk_empty(const relation_signature & s) { + ast_manager& m = get_ast_manager(); + sort* r_sort = get_relation_sort(s); + parameter param(r_sort); + family_id fid = get_family_id(); + expr_ref e(m.mk_fresh_const("T", r_sort), m); + expr* args[1] = { e.get() }; + func_decl_ref empty_decl(m.mk_func_decl(fid, OP_RA_EMPTY, 1, ¶m, 0, (sort*const*)0), m); + reduce_assign(empty_decl, 0, 0, 1, args); + return alloc(external_relation, *this, s, e); + } + + sort* external_relation_plugin::get_relation_sort(relation_signature const& sig) { + vector sorts; + ast_manager& m = get_ast_manager(); + family_id fid = get_family_id(); + for (unsigned i = 0; i < sig.size(); ++i) { + sorts.push_back(parameter(sig[i])); + } + return m.mk_sort(fid, DL_RELATION_SORT, sorts.size(), sorts.c_ptr()); + } + + sort* external_relation_plugin::get_column_sort(unsigned col, sort* s) { + SASSERT(s->get_num_parameters() > col); + SASSERT(s->get_parameter(col).is_ast()); + SASSERT(is_sort(s->get_parameter(col).get_ast())); + return to_sort(s->get_parameter(col).get_ast()); + } + + family_id external_relation_plugin::get_family_id() { + return m_ext.get_family_id(); + } + + + void external_relation_plugin::mk_filter_fn(sort* s, app* condition, func_decl_ref& f) { + ast_manager& m = get_ast_manager(); + family_id fid = get_family_id(); + parameter param(condition); + f = m.mk_func_decl(fid, OP_RA_FILTER, 1, ¶m, 1, &s); + } + + class external_relation_plugin::join_fn : public convenient_relation_join_fn { + external_relation_plugin& m_plugin; + func_decl_ref m_join_fn; + expr* m_args[2]; + public: + join_fn(external_relation_plugin& p, const relation_signature & o1_sig, const relation_signature & o2_sig, unsigned col_cnt, + const unsigned * cols1, const unsigned * cols2) + : convenient_relation_join_fn(o1_sig, o2_sig, col_cnt, cols1, cols2), + m_plugin(p), + m_join_fn(p.get_ast_manager()) { + ast_manager& m = p.get_ast_manager(); + family_id fid = p.get_family_id(); + vector params; + for (unsigned i = 0; i < col_cnt; ++i) { + params.push_back(parameter(cols1[i])); + params.push_back(parameter(cols2[i])); + } + sort* domain[2] = { p.get_relation_sort(o1_sig), p.get_relation_sort(o2_sig) }; + m_join_fn = m.mk_func_decl(fid, OP_RA_JOIN, params.size(), params.c_ptr(), 2, domain); + } + + virtual relation_base * operator()(const relation_base & r1, const relation_base & r2) { + expr_ref res(m_plugin.get_ast_manager()); + m_args[0] = get(r1).get_relation(); + m_args[1] = get(r2).get_relation(); + m_plugin.reduce(m_join_fn, 2, m_args, res); + return alloc(external_relation, m_plugin, get_result_signature(), res); + } + }; + + relation_join_fn * external_relation_plugin::mk_join_fn(const relation_base & r1, const relation_base & r2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2) { + if (!check_kind(r1) || !check_kind(r2)) { + return 0; + } + return alloc(join_fn, *this, r1.get_signature(), r2.get_signature() , col_cnt, cols1, cols2); + } + + + class external_relation_plugin::project_fn : public convenient_relation_project_fn { + external_relation_plugin& m_plugin; + func_decl_ref m_project_fn; + public: + project_fn(external_relation_plugin& p, sort* relation_sort, + const relation_signature & orig_sig, unsigned removed_col_cnt, const unsigned * removed_cols) + : convenient_relation_project_fn(orig_sig, removed_col_cnt, removed_cols), + m_plugin(p), + m_project_fn(p.get_ast_manager()) { + vector params; + ast_manager& m = p.get_ast_manager(); + family_id fid = p.get_family_id(); + for (unsigned i = 0; i < removed_col_cnt; ++i) { + params.push_back(parameter(removed_cols[i])); + } + m_project_fn = m.mk_func_decl(fid, OP_RA_PROJECT, params.size(), params.c_ptr(), 1, &relation_sort); + } + + virtual relation_base * operator()(const relation_base & r) { + expr_ref res(m_plugin.get_ast_manager()); + expr* rel = get(r).get_relation(); + m_plugin.reduce(m_project_fn, 1, &rel, res); + return alloc(external_relation, m_plugin, get_result_signature(), to_app(res)); + } + }; + + relation_transformer_fn * external_relation_plugin::mk_project_fn(const relation_base & r, + unsigned col_cnt, const unsigned * removed_cols) { + return alloc(project_fn, *this, get(r).get_sort(), r.get_signature(), col_cnt, removed_cols); + } + + + class external_relation_plugin::rename_fn : public convenient_relation_rename_fn { + external_relation_plugin& m_plugin; + func_decl_ref m_rename_fn; + expr* m_args[2]; + public: + rename_fn(external_relation_plugin& p, sort* relation_sort, const relation_signature & orig_sig, unsigned cycle_len, const unsigned * cycle) + : convenient_relation_rename_fn(orig_sig, cycle_len, cycle), + m_plugin(p), + m_rename_fn(p.get_ast_manager()) { + + ast_manager& m = p.get_ast_manager(); + family_id fid = p.get_family_id(); + vector params; + for (unsigned i = 0; i < cycle_len; ++i) { + SASSERT(cycle[i] < orig_sig.size()); + params.push_back(parameter(cycle[i])); + } + m_rename_fn = m.mk_func_decl(fid, OP_RA_RENAME, params.size(), params.c_ptr(), 1, &relation_sort); + } + + virtual relation_base * operator()(const relation_base & r) { + expr* rel = get(r).get_relation(); + expr_ref res(m_plugin.get_ast_manager()); + m_args[0] = rel; + m_plugin.reduce(m_rename_fn, 1, &rel, res); + return alloc(external_relation, m_plugin, get_result_signature(), res); + } + }; + + relation_transformer_fn * external_relation_plugin::mk_rename_fn(const relation_base & r, + unsigned cycle_len, const unsigned * permutation_cycle) { + if(!check_kind(r)) { + return 0; + } + return alloc(rename_fn, *this, get(r).get_sort(), r.get_signature(), cycle_len, permutation_cycle); + } + + + class external_relation_plugin::union_fn : public relation_union_fn { + external_relation_plugin& m_plugin; + func_decl_ref m_union_fn; + expr* m_args[2]; + expr* m_outs[2]; + + public: + union_fn(external_relation_plugin& p, decl_kind k, sort* relation_sort): + m_plugin(p), + m_union_fn(p.get_ast_manager()) { + ast_manager& m = p.get_ast_manager(); + sort* domain[2] = { relation_sort, relation_sort }; + m_union_fn = m.mk_func_decl(p.get_family_id(), k, 0, 0, 2, domain); + } + + virtual void operator()(relation_base & r, const relation_base & src, relation_base * delta) { + ast_manager& m = m_plugin.get_ast_manager(); + expr_ref_vector res(m); + m_args[0] = get(r).get_relation(); + m_args[1] = get(src).get_relation(); + m_outs[0] = m_args[0]; + unsigned num_out = 1; + if (delta) { + m_outs[1] = get(*delta).get_relation(); + ++num_out; + } + m_plugin.reduce_assign(m_union_fn, 2, m_args, num_out, m_outs); + } + }; + + relation_union_fn * external_relation_plugin::mk_union_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta) { + if (!check_kind(tgt) || !check_kind(src) || (delta && !check_kind(*delta))) { + return 0; + } + return alloc(union_fn, *this, OP_RA_UNION, get(src).get_sort()); + } + + relation_union_fn * external_relation_plugin::mk_widen_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta) { + if (!check_kind(tgt) || !check_kind(src) || (delta && !check_kind(*delta))) { + return 0; + } + return alloc(union_fn, *this, OP_RA_WIDEN, get(src).get_sort()); + } + + class external_relation_plugin::filter_interpreted_fn : public relation_mutator_fn { + external_relation_plugin& m_plugin; + app_ref m_condition; + func_decl_ref m_filter_fn; + public: + filter_interpreted_fn(external_relation_plugin& p, sort* relation_sort, app * condition) + : m_plugin(p), + m_condition(condition, p.get_ast_manager()), + m_filter_fn(p.get_ast_manager()) { + ast_manager& m = p.get_ast_manager(); + p.mk_filter_fn(relation_sort, condition, m_filter_fn); + SASSERT(m.is_bool(condition)); + } + + virtual void operator()(relation_base & r) { + SASSERT(m_plugin.check_kind(r)); + expr* arg = get(r).get_relation(); + m_plugin.reduce_assign(m_filter_fn, 1, &arg, 1, &arg); + } + }; + + relation_mutator_fn * external_relation_plugin::mk_filter_interpreted_fn(const relation_base & r, app * condition) { + if(!check_kind(r)) { + return 0; + } + return alloc(filter_interpreted_fn, *this, get(r).get_sort(), condition); + } + + relation_mutator_fn * external_relation_plugin::mk_filter_equal_fn(const relation_base & r, + const relation_element & value, unsigned col) { + if(!check_kind(r)) { + return 0; + } + ast_manager& m = get_ast_manager(); + app_ref condition(m); + expr_ref var(m); + sort* relation_sort = get(r).get_sort(); + sort* column_sort = get_column_sort(col, relation_sort); + var = m.mk_var(col, column_sort); + condition = m.mk_eq(var, value); + return mk_filter_interpreted_fn(r, condition); + } + + class external_relation_plugin::filter_identical_fn : public relation_mutator_fn { + external_relation_plugin& m_plugin; + func_decl_ref_vector m_filter_fn; + public: + filter_identical_fn(external_relation_plugin& p, sort* relation_sort, + unsigned col_cnt, const unsigned * identical_cols) + : m_plugin(p), m_filter_fn(p.get_ast_manager()) { + ast_manager& m = p.get_ast_manager(); + func_decl_ref fn(m); + app_ref eq(m); + if (col_cnt <= 1) { + return; + } + unsigned col = identical_cols[0]; + sort* s = p.get_column_sort(col, relation_sort); + var* v0 = m.mk_var(col, s); + for (unsigned i = 1; i < col_cnt; ++i) { + col = identical_cols[i]; + s = p.get_column_sort(col, relation_sort); + eq = m.mk_eq(v0, m.mk_var(col, s)); + p.mk_filter_fn(relation_sort, eq.get(), fn); + m_filter_fn.push_back(fn); + } + } + + virtual void operator()(relation_base & r) { + expr* r0 = get(r).get_relation(); + for (unsigned i = 0; i < m_filter_fn.size(); ++i) { + m_plugin.reduce_assign(m_filter_fn[i].get(), 1, &r0, 1, &r0); + } + } + }; + + relation_mutator_fn * external_relation_plugin::mk_filter_identical_fn(const relation_base & r, + unsigned col_cnt, const unsigned * identical_cols) { + if (!check_kind(r)) { + return 0; + } + return alloc(filter_identical_fn, *this, get(r).get_sort(), col_cnt, identical_cols); + } + + + class external_relation_plugin::negation_filter_fn : public convenient_relation_negation_filter_fn { + external_relation_plugin& m_plugin; + func_decl_ref m_negated_filter_fn; + expr* m_args[2]; + public: + negation_filter_fn(external_relation_plugin& p, + const relation_base & tgt, const relation_base & neg_t, + unsigned joined_col_cnt, const unsigned * t_cols, const unsigned * negated_cols) : + convenient_negation_filter_fn(tgt, neg_t, joined_col_cnt, t_cols, negated_cols), + m_plugin(p), + m_negated_filter_fn(p.get_ast_manager()) + { + ast_manager& m = p.get_ast_manager(); + family_id fid = p.get_family_id(); + vector params; + for (unsigned i = 0; i < joined_col_cnt; ++i) { + params.push_back(parameter(t_cols[i])); + params.push_back(parameter(negated_cols[i])); + } + sort* domain[2] = { get(tgt).get_sort(), get(neg_t).get_sort() }; + m_negated_filter_fn = m.mk_func_decl(fid, OP_RA_NEGATION_FILTER, params.size(), params.c_ptr(), 2, domain); + } + + void operator()(relation_base & t, const relation_base & negated_obj) { + m_args[0] = get(t).get_relation(); + m_args[1] = get(negated_obj).get_relation(); + m_plugin.reduce_assign(m_negated_filter_fn.get(), 2, m_args, 1, m_args); + } + }; + + relation_intersection_filter_fn * external_relation_plugin::mk_filter_by_negation_fn(const relation_base & t, + const relation_base & negated_obj, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * negated_cols) { + if (!check_kind(t) || !check_kind(negated_obj)) { + return 0; + } + return alloc(negation_filter_fn, *this, t, negated_obj, joined_col_cnt, t_cols, negated_cols); + } + +}; diff --git a/lib/dl_external_relation.h b/lib/dl_external_relation.h new file mode 100644 index 000000000..03838ecc4 --- /dev/null +++ b/lib/dl_external_relation.h @@ -0,0 +1,154 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + dl_external_relation.h + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2010-05-10 + +Revision History: + +--*/ +#ifndef _DL_EXTERNAL_RELATION_H_ +#define _DL_EXTERNAL_RELATION_H_ + +#include "dl_base.h" + +namespace datalog { + + class external_relation; + + class external_relation_context { + public: + virtual ~external_relation_context() {} + + virtual family_id get_family_id() const = 0; + + // reduce arguments. + virtual void reduce(func_decl* f, unsigned num_args, expr * const* args, expr_ref& result) = 0; + + // overwrite terms passed in outs vector with values computed by function. + virtual void reduce_assign(func_decl* f, unsigned num_args, expr * const* args, unsigned num_out, expr* const* outs) = 0; + }; + + class external_relation_plugin : public relation_plugin { + + friend class external_relation; + class join_fn; + class project_fn; + class rename_fn; + class union_fn; + class filter_identical_fn; + class filter_interpreted_fn; + class negation_filter_fn; + + external_relation_context& m_ext; + + public: + external_relation_plugin(external_relation_context& ctx, relation_manager & m); + + virtual bool can_handle_signature(const relation_signature & s) { return true; } + + static symbol get_name() { return symbol("external_relation"); } + + virtual relation_base * mk_empty(const relation_signature & s); + + virtual relation_join_fn * mk_join_fn(const relation_base & t1, const relation_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2); + virtual relation_transformer_fn * mk_project_fn(const relation_base & t, unsigned col_cnt, + const unsigned * removed_cols); + virtual relation_transformer_fn * mk_rename_fn(const relation_base & t, unsigned permutation_cycle_len, + const unsigned * permutation_cycle); + virtual relation_union_fn * mk_union_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta); + virtual relation_union_fn * mk_widen_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta); + virtual relation_mutator_fn * mk_filter_identical_fn(const relation_base & t, unsigned col_cnt, + const unsigned * identical_cols); + virtual relation_mutator_fn * mk_filter_equal_fn(const relation_base & t, const relation_element & value, + unsigned col); + virtual relation_mutator_fn * mk_filter_interpreted_fn(const relation_base & t, app * condition); + virtual relation_intersection_filter_fn * mk_filter_by_negation_fn(const relation_base & t, + const relation_base & negated_obj, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * negated_cols); + + private: + + static external_relation& get(relation_base& r); + static external_relation const & get(relation_base const& r); + + void reduce(func_decl* f, unsigned num_args, expr * const* args, expr_ref& result) { + m_ext.reduce(f, num_args, args, result); + } + + void reduce_assign(func_decl* f, unsigned num_args, expr * const* args, unsigned num_out, expr* const* outs) { + m_ext.reduce_assign(f, num_args, args, num_out, outs); + } + + sort* get_relation_sort(relation_signature const& sig); + + sort* get_column_sort(unsigned col, sort* relation_sort); + + void mk_filter_fn(sort* s, app* condition, func_decl_ref& f); + + family_id get_family_id(); + }; + + class external_relation : public relation_base { + friend class external_relation_plugin; + friend class external_relation_plugin::join_fn; + friend class external_relation_plugin::project_fn; + friend class external_relation_plugin::rename_fn; + friend class external_relation_plugin::union_fn; + friend class external_relation_plugin::filter_identical_fn; + friend class external_relation_plugin::filter_interpreted_fn; + friend class external_relation_plugin::negation_filter_fn; + + expr_ref m_rel; + func_decl_ref m_select_fn; + func_decl_ref m_store_fn; + func_decl_ref m_is_empty_fn; + + unsigned size() const { return get_signature().size(); } + + sort* get_sort() const { return m_rel.get_manager().get_sort(m_rel); } + + void mk_accessor(decl_kind k, func_decl_ref& fn, const relation_fact& f, bool destructive, expr_ref& res) const; + + external_relation(external_relation_plugin & p, const relation_signature & s, expr* r); + virtual ~external_relation(); + + public: + external_relation_plugin & get_plugin() const; + + virtual bool empty() const; + + virtual void add_fact(const relation_fact & f); + + virtual bool contains_fact(const relation_fact & f) const; + + virtual external_relation * clone() const; + + virtual external_relation * complement(func_decl*) const; + + virtual void display(std::ostream & out) const; + + virtual void display_tuples(func_decl & pred, std::ostream & out) const; + + expr* get_relation() const { return m_rel.get(); } + + virtual void to_formula(expr_ref& fml) const { fml = get_relation(); } + + }; + + +}; + +#endif diff --git a/lib/dl_finite_product_relation.cpp b/lib/dl_finite_product_relation.cpp new file mode 100644 index 000000000..80dcfc54b --- /dev/null +++ b/lib/dl_finite_product_relation.cpp @@ -0,0 +1,2372 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_finite_product_relation.cpp + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-09-14. + +Revision History: + +--*/ + + +#include +#include"dl_context.h" +#include"dl_relation_manager.h" +#include"dl_table_relation.h" +#include"dl_finite_product_relation.h" +#include"bool_rewriter.h" + +namespace datalog { + + //static variables + + const table_sort finite_product_relation::s_rel_idx_sort = INT_MAX; + + void universal_delete(finite_product_relation* ptr) { + ptr->deallocate(); + } + + + // ----------------------------------- + // + // finite_product_relation_plugin + // + // ----------------------------------- + + finite_product_relation & finite_product_relation_plugin::get(relation_base & r) { + SASSERT(r.get_plugin().is_finite_product_relation()); + return static_cast(r); + } + + const finite_product_relation & finite_product_relation_plugin::get(const relation_base & r) { + SASSERT(r.get_plugin().is_finite_product_relation()); + return static_cast(r); + } + + finite_product_relation * finite_product_relation_plugin::get(relation_base * r) { + SASSERT(!r || r->get_plugin().is_finite_product_relation()); + return static_cast(r); + } + + const finite_product_relation * finite_product_relation_plugin::get(const relation_base * r) { + SASSERT(!r || r->get_plugin().is_finite_product_relation()); + return static_cast(r); + } + + symbol finite_product_relation_plugin::get_name(relation_plugin & inner_plugin) { + std::string str = std::string("fpr_")+inner_plugin.get_name().bare_str(); + return symbol(str.c_str()); + } + + finite_product_relation_plugin & finite_product_relation_plugin::get_plugin(relation_manager & rmgr, + relation_plugin & inner) { + finite_product_relation_plugin * res; + if(!rmgr.try_get_finite_product_relation_plugin(inner, res)) { + res = alloc(finite_product_relation_plugin, inner, rmgr); + rmgr.register_plugin(res); + } + return *res; + } + + finite_product_relation_plugin::finite_product_relation_plugin(relation_plugin & inner_plugin, + relation_manager & manager) + : relation_plugin(get_name(inner_plugin), manager, ST_FINITE_PRODUCT_RELATION), + m_inner_plugin(inner_plugin), m_spec_store(*this) { + } + + void finite_product_relation_plugin::initialize(family_id fid) { + relation_plugin::initialize(fid); + m_spec_store.add_available_kind(get_kind()); + } + + family_id finite_product_relation_plugin::get_relation_kind(finite_product_relation & r, + const bool * table_columns) { + const relation_signature & sig = r.get_signature(); + svector table_cols_vect(sig.size(), table_columns); + return m_spec_store.get_relation_kind(sig, rel_spec(table_cols_vect)); + } + + void finite_product_relation_plugin::get_all_possible_table_columns(relation_manager & rmgr, + const relation_signature & s, svector & table_columns) { + SASSERT(table_columns.empty()); + unsigned s_sz = s.size(); + for(unsigned i=0; i table_columns; + get_all_possible_table_columns(s, table_columns); +#ifndef _EXTERNAL_RELEASE + unsigned s_sz = s.size(); + unsigned rel_col_cnt = 0; + for(unsigned i=0; icomplement_self(p); + return res; + } + + bool finite_product_relation_plugin::can_convert_to_table_relation(const finite_product_relation & r) { + return r.m_other_sig.empty(); + } + + table_relation * finite_product_relation_plugin::to_table_relation(const finite_product_relation & r) { + SASSERT(can_convert_to_table_relation(r)); + r.garbage_collect(true); + //now all rows in the table will correspond to rows in the resulting table_relation + + const table_base & t = r.get_table(); + + unsigned removed_col = t.get_signature().size()-1; + scoped_ptr project_fun = + get_manager().mk_project_fn(r.get_table(), 1, &removed_col); + + table_base * res_table = (*project_fun)(t); + SASSERT(res_table->get_signature().functional_columns()==0); + return static_cast(get_manager().mk_table_relation(r.get_signature(), res_table)); + } + + + bool finite_product_relation_plugin::can_be_converted(const relation_base & r) { + if(&r.get_plugin()==&get_inner_plugin()) { + //can be converted by mk_from_inner_relation + return true; + } + if(r.from_table()) { + //We can convert directly from table plugin only if the inner plugin can handle empty signatures. + + //TODO: If the inner plugin cannot handle empty signatures, we may try to move some of the + //table columns into the inner relation signature. + return get_inner_plugin().can_handle_signature(relation_signature()); + } + return false; + } + + finite_product_relation * finite_product_relation_plugin::mk_from_table_relation(const table_relation & r) { + func_decl* pred = 0; + const relation_signature & sig = r.get_signature(); + const table_base & t = r.get_table(); + table_plugin & tplugin = r.get_table().get_plugin(); + + relation_signature inner_sig; //empty signature for the inner relation + if(!get_inner_plugin().can_handle_signature(inner_sig)) { + return 0; + } + + table_signature idx_singleton_sig; + idx_singleton_sig.push_back(finite_product_relation::s_rel_idx_sort); + idx_singleton_sig.set_functional_columns(1); + + scoped_rel idx_singleton; + if(tplugin.can_handle_signature(idx_singleton_sig)) { + idx_singleton = tplugin.mk_empty(idx_singleton_sig); + } + else { + idx_singleton = get_manager().mk_empty_table(idx_singleton_sig); + } + table_fact idx_singleton_fact; + idx_singleton_fact.push_back(0); + idx_singleton->add_fact(idx_singleton_fact); + + scoped_ptr join_fun = get_manager().mk_join_fn(t, *idx_singleton, 0, 0, 0); + SASSERT(join_fun); + scoped_rel res_table = (*join_fun)(t, *idx_singleton); + + svector table_cols(sig.size(), true); + finite_product_relation * res = mk_empty(sig, table_cols.c_ptr()); + + //this one does not need to be deleted -- it will be taken over by \c res in the \c init function + relation_base * inner_rel = get_inner_plugin().mk_full(pred, inner_sig, get_inner_plugin().get_kind()); + + relation_vector rels; + rels.push_back(inner_rel); + + res->init(*res_table, rels, true); + return res; + } + + finite_product_relation * finite_product_relation_plugin::mk_from_inner_relation(const relation_base & r) { + SASSERT(&r.get_plugin()==&get_inner_plugin()); + const relation_signature & sig = r.get_signature(); + + table_signature idx_singleton_sig; + idx_singleton_sig.push_back(finite_product_relation::s_rel_idx_sort); + idx_singleton_sig.set_functional_columns(1); + + scoped_rel idx_singleton = get_manager().mk_empty_table(idx_singleton_sig); + table_fact idx_singleton_fact; + idx_singleton_fact.push_back(0); + idx_singleton->add_fact(idx_singleton_fact); + + svector table_cols(sig.size(), false); + finite_product_relation * res = mk_empty(sig, table_cols.c_ptr()); + + relation_vector rels; + rels.push_back(r.clone()); + + res->init(*idx_singleton, rels, true); + return res; + } + + class finite_product_relation_plugin::converting_join_fn : public convenient_relation_join_fn { + finite_product_relation_plugin & m_plugin; + scoped_ptr m_native_join; + + finite_product_relation * convert(const relation_base & r) { + SASSERT(&r.get_plugin()!=&m_plugin); + if(&r.get_plugin()==&m_plugin.get_inner_plugin()) { + return m_plugin.mk_from_inner_relation(r); + } + SASSERT(r.from_table()); + const table_relation & tr = static_cast(r); + finite_product_relation * res = m_plugin.mk_from_table_relation(tr); + SASSERT(res); + return res; + } + + public: + converting_join_fn(finite_product_relation_plugin & plugin, const relation_signature & sig1, + const relation_signature & sig2, unsigned col_cnt, const unsigned * cols1, + const unsigned * cols2) + : convenient_relation_join_fn(sig1, sig2, col_cnt, cols1, cols2), + m_plugin(plugin) {} + + virtual relation_base * operator()(const relation_base & r1, const relation_base & r2) { + scoped_rel r1_conv; + if(&r1.get_plugin()!=&m_plugin) { + r1_conv = convert(r1); + } + scoped_rel r2_conv; + if(&r2.get_plugin()!=&m_plugin) { + r2_conv = convert(r2); + } + + const finite_product_relation & fpr1 = r1_conv ? *r1_conv : get(r1); + const finite_product_relation & fpr2 = r2_conv ? *r2_conv : get(r2); + + SASSERT(&fpr1.get_plugin()==&m_plugin); + SASSERT(&fpr2.get_plugin()==&m_plugin); + + if(!m_native_join) { + m_native_join = m_plugin.get_manager().mk_join_fn(fpr1, fpr2, m_cols1, m_cols2, false); + } + return (*m_native_join)(fpr1, fpr2); + } + }; + + + class finite_product_relation_plugin::join_fn : public convenient_relation_join_fn { + scoped_ptr m_tjoin_fn; + scoped_ptr m_rjoin_fn; + + unsigned_vector m_t_joined_cols1; + unsigned_vector m_t_joined_cols2; + unsigned_vector m_r_joined_cols1; + unsigned_vector m_r_joined_cols2; + + //Column equalities betweet table and inner relations. + //The columns numbers correspont to the columns of the table/inner relation + //in the result of the join operation + unsigned_vector m_tr_table_joined_cols; + unsigned_vector m_tr_rel_joined_cols; + + scoped_ptr m_filter_tr_identities; + + scoped_ptr m_tjoined_second_rel_remover; + + //determines which columns of the result are table columns and which are in the inner relation + svector m_res_table_columns; + + public: + class join_maker : public table_row_mutator_fn { + join_fn & m_parent; + const finite_product_relation & m_r1; + const finite_product_relation & m_r2; + relation_vector & m_rjoins; + public: + join_maker(join_fn & parent, const finite_product_relation & r1, const finite_product_relation & r2, + relation_vector & rjoins) + : m_parent(parent), m_r1(r1), m_r2(r2), m_rjoins(rjoins) {} + + virtual bool operator()(table_element * func_columns) { + const relation_base & or1 = m_r1.get_inner_rel(func_columns[0]); + const relation_base & or2 = m_r2.get_inner_rel(func_columns[1]); + SASSERT(&or1); + SASSERT(&or2); + unsigned new_rel_num = m_rjoins.size(); + m_rjoins.push_back(m_parent.do_rjoin(or1, or2)); + func_columns[0]=new_rel_num; + return true; + } + }; + + join_fn(const finite_product_relation & r1, const finite_product_relation & r2, unsigned col_cnt, + const unsigned * cols1, const unsigned * cols2) + : convenient_relation_join_fn(r1.get_signature(), r2.get_signature(), col_cnt, cols1, cols2) { + unsigned second_table_after_join_ofs = r1.m_table2sig.size(); + unsigned second_inner_rel_after_join_ofs = r1.m_other2sig.size(); + for(unsigned i=0;i tjoined = (*m_tjoin_fn)(r1.get_table(), r2.get_table()); + + relation_vector joined_orelations; + + { + join_maker * mutator = alloc(join_maker, *this, r1, r2, joined_orelations); //dealocated in inner_join_mapper + scoped_ptr inner_join_mapper = rmgr.mk_map_fn(*tjoined, mutator); + (*inner_join_mapper)(*tjoined); + } + + + if(!m_tjoined_second_rel_remover) { + unsigned removed_col = tjoined->get_signature().size()-1; + m_tjoined_second_rel_remover = rmgr.mk_project_fn(*tjoined, 1, &removed_col); + } + //remove the second functional column from tjoined to get a table that corresponds + //to the table signature of the resulting relation + scoped_rel res_table = (*m_tjoined_second_rel_remover)(*tjoined); + + relation_plugin & res_oplugin = + joined_orelations.empty() ? r1.m_other_plugin : joined_orelations.back()->get_plugin(); + + //TODO: Maybe we might want to specify a particular relation kind, instead of just null_family_id. + //It would however need to be somehow inferred for the new signature. + + finite_product_relation * res = alloc(finite_product_relation, r1.get_plugin(), get_result_signature(), + m_res_table_columns.c_ptr(), res_table->get_plugin(), res_oplugin, null_family_id); + + res->init(*res_table, joined_orelations, true); + + if(m_tr_table_joined_cols.size()) { + //There were some shared variables between the table and the relation part. + //We enforce those equalities here. + if(!m_filter_tr_identities) { + m_filter_tr_identities = plugin.mk_filter_identical_pairs(*res, m_tr_table_joined_cols.size(), + m_tr_table_joined_cols.c_ptr(), m_tr_rel_joined_cols.c_ptr()); + SASSERT(m_filter_tr_identities); + } + (*m_filter_tr_identities)(*res); + } + return res; + } + }; + + + + + relation_join_fn * finite_product_relation_plugin::mk_join_fn(const relation_base & rb1, const relation_base & rb2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2) { + if(!check_kind(rb1) || !check_kind(rb2)) { + bool r1foreign = &rb1.get_plugin()!=this; + bool r2foreign = &rb2.get_plugin()!=this; + if( (!r1foreign || can_be_converted(rb1)) && (!r2foreign || can_be_converted(rb2))) { + return alloc(converting_join_fn, *this, rb1.get_signature(), rb2.get_signature(), col_cnt, cols1, + cols2); + } + return 0; + } + const finite_product_relation & r1 = get(rb1); + const finite_product_relation & r2 = get(rb2); + + return alloc(join_fn, r1, r2, col_cnt, cols1, cols2); + } + + + class finite_product_relation_plugin::project_fn : public convenient_relation_project_fn { + unsigned_vector m_removed_table_cols; + unsigned_vector m_removed_rel_cols; + + scoped_ptr m_rel_projector; + scoped_ptr m_inner_rel_union; + + //determines which columns of the result are table columns and which are in the inner relation + svector m_res_table_columns; + public: + project_fn(const finite_product_relation & r, unsigned col_cnt, const unsigned * removed_cols) + : convenient_relation_project_fn(r.get_signature(), col_cnt, removed_cols) { + SASSERT(col_cnt>0); + for(unsigned i=0; ii); + m_res_table_columns.push_back(r.is_table_column(i)); + } + } + + class project_reducer : public table_row_pair_reduce_fn { + project_fn & m_parent; + relation_vector & m_relations; + public: + + project_reducer(project_fn & parent, relation_vector & relations) + : m_parent(parent), m_relations(relations) {} + + virtual void operator()(table_element * func_columns, const table_element * merged_func_columns) { + relation_base * tgt = m_relations[static_cast(func_columns[0])]->clone(); + relation_base & src = *m_relations[static_cast(merged_func_columns[0])]; + if(!m_parent.m_inner_rel_union) { + m_parent.m_inner_rel_union = tgt->get_manager().mk_union_fn(*tgt, src); + } + (*m_parent.m_inner_rel_union)(*tgt, src); + + unsigned new_idx = m_relations.size(); + m_relations.push_back(tgt); + func_columns[0] = new_idx; + } + }; + + virtual relation_base * operator()(const relation_base & rb) { + const finite_product_relation & r = get(rb); + finite_product_relation_plugin & plugin = r.get_plugin(); + const table_base & rtable = r.get_table(); + relation_manager & rmgr = plugin.get_manager(); + + r.garbage_collect(false); + relation_vector res_relations; + unsigned orig_rel_cnt = r.m_others.size(); + for(unsigned i=0; iclone() : 0); + } + SASSERT(res_relations.size()==orig_rel_cnt); + + bool shared_res_table = false; + const table_base * res_table; + + if(m_removed_table_cols.empty()) { + shared_res_table = true; + res_table = &rtable; + } + else { + project_reducer * preducer = alloc(project_reducer, *this, res_relations); + scoped_ptr tproject = + rmgr.mk_project_with_reduce_fn(rtable, m_removed_table_cols.size(), m_removed_table_cols.c_ptr(), preducer); + res_table = (*tproject)(rtable); + } + + relation_plugin * res_oplugin = 0; + + if(!m_removed_rel_cols.empty()) { + unsigned res_rel_cnt = res_relations.size(); + for(unsigned i=0; ideallocate(); + if(!res_oplugin) { + res_oplugin = &res_relations[i]->get_plugin(); + } + } + } + + if(!res_oplugin) { + res_oplugin = &r.m_other_plugin; + } + + //TODO: Maybe we might want to specify a particular relation kind, instead of just null_family_id. + //It would however need to be somehow inferred for the new signature. + + finite_product_relation * res = alloc(finite_product_relation, r.get_plugin(), get_result_signature(), + m_res_table_columns.c_ptr(), res_table->get_plugin(), *res_oplugin, null_family_id); + + res->init(*res_table, res_relations, false); + + if(!shared_res_table) { + const_cast(res_table)->deallocate(); + } + + return res; + } + }; + + relation_transformer_fn * finite_product_relation_plugin::mk_project_fn(const relation_base & rb, unsigned col_cnt, + const unsigned * removed_cols) { + if(&rb.get_plugin()!=this) { + return 0; + } + return alloc(project_fn, get(rb), col_cnt, removed_cols); + } + + + + class finite_product_relation_plugin::rename_fn : public convenient_relation_rename_fn { + scoped_ptr m_table_renamer; + scoped_ptr m_rel_renamer; + bool m_rel_identity; + + unsigned_vector m_rel_permutation; + + //determines which columns of the result are table columns and which are in the inner relation + svector m_res_table_columns; + public: + rename_fn(const finite_product_relation & r, unsigned cycle_len, const unsigned * permutation_cycle) + : convenient_relation_rename_fn(r.get_signature(), cycle_len, permutation_cycle) { + SASSERT(cycle_len>1); + + unsigned sig_sz = r.get_signature().size(); + unsigned_vector permutation; + add_sequence(0, sig_sz, permutation); + permutate_by_cycle(permutation, cycle_len, permutation_cycle); + + unsigned_vector table_permutation; + + bool table_identity = true; + m_rel_identity = true; + for(unsigned new_i=0; new_iclone() : 0); + } + + if(!m_rel_identity) { + unsigned res_rel_cnt = res_relations.size(); + for(unsigned i=0; i inner_rel = res_relations[i]; + if(!m_rel_renamer) { + m_rel_renamer = r.get_manager().mk_permutation_rename_fn(*inner_rel, m_rel_permutation); + } + + res_relations[i] = (*m_rel_renamer)(*inner_rel); + } + } + scoped_rel res_table_scoped; + const table_base * res_table = &rtable; + + if(m_table_renamer) { + res_table_scoped = (*m_table_renamer)(*res_table); + res_table = res_table_scoped.get(); + } + + //TODO: Maybe we might want to specify a particular relation kind, instead of just null_family_id. + //It would however need to be somehow inferred for the new signature. + + finite_product_relation * res = alloc(finite_product_relation, r.get_plugin(), get_result_signature(), + m_res_table_columns.c_ptr(), res_table->get_plugin(), r.m_other_plugin, null_family_id); + + res->init(*res_table, res_relations, false); + + return res; + } + }; + + relation_transformer_fn * finite_product_relation_plugin::mk_rename_fn(const relation_base & rb, + unsigned permutation_cycle_len, const unsigned * permutation_cycle) { + + if(&rb.get_plugin()!=this) { + return 0; + } + const finite_product_relation & r = get(rb); + return alloc(rename_fn, r, permutation_cycle_len, permutation_cycle); + } + + + class finite_product_relation_plugin::union_fn : public relation_union_fn { + bool m_use_delta; + unsigned_vector m_data_cols;//non-functional columns in the product-relation table (useful for creating operations) + scoped_ptr m_common_join; //result of the join contains (data columns), tgt_rel_idx, src_rel_idx + scoped_ptr m_rel_union; + scoped_ptr m_table_union; + scoped_ptr m_remove_overlaps; + scoped_ptr m_remove_src_column_from_overlap; + + //this one is populated only if we're doing union with delta + scoped_ptr m_delta_merging_union; + + scoped_ptr m_overlap_delta_table_builder; + public: + union_fn(const finite_product_relation & tgt, bool use_delta) : m_use_delta(use_delta) {} + + relation_union_fn & get_inner_rel_union_op(relation_base & r) { + if(!m_rel_union) { + m_rel_union = r.get_manager().mk_union_fn(r, r, m_use_delta ? &r : 0); + } + return *m_rel_union; + } + + class union_mapper : public table_row_mutator_fn { + union_fn & m_parent; + finite_product_relation & m_tgt; + const finite_product_relation & m_src; + table_base * m_delta_indexes; //table with signature (updated tgt rel index, delta_index in m_delta_rels) + relation_vector * m_delta_rels; + table_fact m_di_fact; //auxiliary fact for inserting into \c m_delta_indexes + public: + /** + If \c delta_indexes is 0, it means we are not collecting indexes. + */ + union_mapper(union_fn & parent, finite_product_relation & tgt, const finite_product_relation & src, + table_base * delta_indexes, relation_vector * delta_rels) + : m_parent(parent), + m_tgt(tgt), + m_src(src), + m_delta_indexes(delta_indexes), + m_delta_rels(delta_rels) {} + + virtual ~union_mapper() {} + + virtual bool operator()(table_element * func_columns) { + relation_base & otgt_orig = m_tgt.get_inner_rel(func_columns[0]); + const relation_base & osrc = m_src.get_inner_rel(func_columns[1]); + + relation_base * otgt = otgt_orig.clone(); + unsigned new_tgt_idx = m_tgt.get_next_rel_idx(); + m_tgt.set_inner_rel(new_tgt_idx, otgt); + if(m_delta_indexes) { + relation_base * odelta = otgt->get_plugin().mk_empty(otgt->get_signature()); + m_parent.get_inner_rel_union_op(*otgt)(*otgt, osrc, odelta); + + unsigned delta_idx = m_delta_rels->size(); + m_delta_rels->push_back(odelta); + m_di_fact.reset(); + m_di_fact.push_back(new_tgt_idx); + m_di_fact.push_back(delta_idx); + m_delta_indexes->add_fact(m_di_fact); + } + else { + m_parent.get_inner_rel_union_op(*otgt)(*otgt, osrc); + } + + func_columns[0]=new_tgt_idx; + return true; + } + }; + + /** + Makes a table whose last column has indexes to relations in \c src into a table + with indexes to relation \c tgt. + */ + class src_copying_mapper : public table_row_mutator_fn { + finite_product_relation & m_tgt; + const finite_product_relation & m_src; + public: + /** + If \c delta_indexes is 0, it means we are not collecting indexes. + */ + src_copying_mapper(finite_product_relation & tgt, const finite_product_relation & src) + : m_tgt(tgt), m_src(src) {} + + virtual bool operator()(table_element * func_columns) { + const relation_base & osrc = m_src.get_inner_rel(func_columns[0]); + unsigned new_tgt_idx = m_tgt.get_next_rel_idx(); + m_tgt.set_inner_rel(new_tgt_idx, osrc.clone()); + func_columns[0]=new_tgt_idx; + return true; + } + }; + + virtual void operator()(relation_base & tgtb, const relation_base & srcb, relation_base * deltab) { + finite_product_relation & tgt = get(tgtb); + const finite_product_relation & src0 = get(srcb); + finite_product_relation * delta = get(deltab); + + relation_manager & rmgr = tgt.get_manager(); + + scoped_rel src_aux_copy; //copy of src in case its specification needs to be modified + + if(!vectors_equal(tgt.m_table2sig, src0.m_table2sig) + || (delta && !vectors_equal(tgt.m_table2sig, delta->m_table2sig)) ) { + src_aux_copy = src0.clone(); + ptr_vector orig_rels; + orig_rels.push_back(src_aux_copy.get()); + orig_rels.push_back(&tgt); + if(delta) { + orig_rels.push_back(delta); + } + if(!finite_product_relation::try_unify_specifications(orig_rels)) { + throw default_exception("finite_product_relation union: cannot convert relations to common specification"); + } + } + + const finite_product_relation & src = src_aux_copy ? *src_aux_copy : src0; + + table_plugin & tplugin = tgt.get_table_plugin(); + + if(!m_common_join) { + unsigned data_cols_cnt = tgt.m_table_sig.size()-1; + for(unsigned i=0; i table_overlap = (*m_common_join)(tgt.get_table(), src.get_table()); + + scoped_rel delta_indexes; + relation_vector delta_rels; + if(m_use_delta) { + table_signature di_sig; + di_sig.push_back(finite_product_relation::s_rel_idx_sort); + di_sig.push_back(finite_product_relation::s_rel_idx_sort); + di_sig.set_functional_columns(1); + delta_indexes = tplugin.mk_empty(di_sig); + } + + { + union_mapper * umapper = alloc(union_mapper, *this, tgt, src, delta_indexes.get(), &delta_rels); + scoped_ptr mapping_fn = rmgr.mk_map_fn(*table_overlap, umapper); + (*mapping_fn)(*table_overlap); + } + + if(!m_remove_src_column_from_overlap) { + unsigned removed_cols[] = { table_overlap->get_signature().size()-1 }; + m_remove_src_column_from_overlap = rmgr.mk_project_fn(*table_overlap, 1, removed_cols); + } + //transform table_overlap into the signature of tgt.get_table(), so that the functional + //column contains indexes of the united relations + scoped_rel regular_overlap = (*m_remove_src_column_from_overlap)(*table_overlap); + + + if(!m_remove_overlaps) { + m_remove_overlaps = rmgr.mk_filter_by_negation_fn(tgt.get_table(), *regular_overlap, m_data_cols, + m_data_cols); + } + + //in tgt keep only the rows that are in tgt only + (*m_remove_overlaps)(tgt.get_table(), *regular_overlap); + + //add rows in which tgt and src overlapped + if(!m_table_union) { + m_table_union = rmgr.mk_union_fn(tgt.get_table(), tgt.get_table()); + } + (*m_table_union)(tgt.get_table(), *regular_overlap); + + scoped_rel src_only = src.get_table().clone(); + (*m_remove_overlaps)(*src_only, *regular_overlap); + + scoped_rel src_only2; //a copy of src_only for use in building the delta + if(m_use_delta) { + src_only2 = src_only->clone(); + } + + { + src_copying_mapper * cpmapper = alloc(src_copying_mapper, tgt, src); + scoped_ptr mapping_fn = rmgr.mk_map_fn(*src_only, cpmapper); + (*mapping_fn)(*src_only); + } + + //add rows that were only in src + (*m_table_union)(tgt.get_table(), *src_only); + + if(m_use_delta) { + bool extending_delta = !delta->empty(); + //current delta, we will add it to the deltab argument later if it was not given to us empty + finite_product_relation * cdelta; + if(extending_delta) { + cdelta = delta->get_plugin().mk_empty(*delta); + } + else { + cdelta = delta; + } + + if(!m_overlap_delta_table_builder) { + unsigned table_fn_col = regular_overlap->get_signature().size()-1; + unsigned first_col = 0; + unsigned removed_cols[] = { table_fn_col, table_fn_col+1 }; + m_overlap_delta_table_builder = rmgr.mk_join_project_fn(*regular_overlap, *delta_indexes, 1, + &table_fn_col, &first_col, 2, removed_cols); + } + + scoped_rel overlap_delta_table = + (*m_overlap_delta_table_builder)(*regular_overlap, *delta_indexes); + + cdelta->init(*overlap_delta_table, delta_rels, true); + + { + src_copying_mapper * cpmapper = alloc(src_copying_mapper, *cdelta, src); + scoped_ptr mapping_fn = rmgr.mk_map_fn(*src_only2, cpmapper); + (*mapping_fn)(*src_only2); + } + + //add rows that were only in src + (*m_table_union)(cdelta->get_table(), *src_only2); + + if(extending_delta) { + if(!m_delta_merging_union) { + m_delta_merging_union = rmgr.mk_union_fn(*delta, *cdelta); + } + (*m_delta_merging_union)(*delta, *cdelta); + cdelta->deallocate(); + } + } + } + }; + + /** + Union operation taking advantage of the fact that the inner relation of all the arguments + is a singleton relation. + */ + class finite_product_relation_plugin::inner_singleton_union_fn : public relation_union_fn { + + class offset_row_mapper : public table_row_mutator_fn { + public: + unsigned m_ofs; + virtual bool operator()(table_element * func_columns) { + func_columns[0] += m_ofs; + return true; + } + }; + + class inner_relation_copier : public table_row_mutator_fn { + finite_product_relation & m_tgt; + const finite_product_relation & m_src; + finite_product_relation * m_delta; + unsigned m_tgt_ofs; + unsigned m_delta_ofs; + public: + virtual bool operator()(table_element * func_columns) { + unsigned src_idx = static_cast(func_columns[0]); + unsigned tgt_idx = src_idx + m_tgt_ofs; + unsigned delta_idx = m_delta ? (src_idx + m_delta_ofs) : 0; + SASSERT(!m_delta || m_tgt.m_others[tgt_idx]==m_delta->m_others[delta_idx]); + SASSERT(m_tgt.m_others[tgt_idx]==0 || m_tgt.m_others[tgt_idx]==m_src.m_others[src_idx]); + if(m_tgt.m_others[tgt_idx]==0) { + m_tgt.m_others[tgt_idx] = m_src.m_others[src_idx]->clone(); + if(m_delta) { + m_delta->m_others[delta_idx] = m_src.m_others[src_idx]->clone(); + } + } + return true; + } + }; + + scoped_ptr m_t_union_fun; + offset_row_mapper * m_offset_mapper_obj; //initialized together with m_offset_mapper_fun, and deallocated by it + scoped_ptr m_offset_mapper_fun; + + + + public: + virtual void operator()(relation_base & tgtb, const relation_base & srcb, relation_base * deltab) { + finite_product_relation & tgt = get(tgtb); + const finite_product_relation & src = get(srcb); + finite_product_relation * delta = get(deltab); + + finite_product_relation_plugin & plugin = tgt.get_plugin(); + relation_manager & rmgr = plugin.get_manager(); + + //we want only non-empty inner relations to remain + tgt.garbage_collect(true); + src.garbage_collect(true); + + table_base & tgt_table = tgt.get_table(); + const table_base & src_table = src.get_table(); + + scoped_rel offsetted_src = src_table.clone(); + + if(!m_offset_mapper_fun) { + m_offset_mapper_obj = alloc(offset_row_mapper); + m_offset_mapper_fun = rmgr.mk_map_fn(*offsetted_src, m_offset_mapper_obj); + } + unsigned src_rel_offset = tgt.m_others.size(); + m_offset_mapper_obj->m_ofs = src_rel_offset; + + (*m_offset_mapper_fun)(*offsetted_src); + + //if we need to generate a delta, we get collect it into an empty relation and then union + //it with the delta passed in as argument. + scoped_rel loc_delta = delta ? get(plugin.mk_empty(*delta)) : 0; + //even if we don't need to generate the delta for the caller, we still want to have a delta + //table to know which relations to copy. + scoped_rel loc_delta_table_scoped; + if(!loc_delta) { + loc_delta_table_scoped = tgt_table.get_plugin().mk_empty(tgt_table); + } + table_base * loc_delta_table = loc_delta ? &loc_delta->get_table() : loc_delta_table_scoped.get(); + + if(!m_t_union_fun) { + m_t_union_fun = rmgr.mk_union_fn(tgt_table, *offsetted_src, loc_delta_table); + } + (*m_t_union_fun)(tgt_table, *offsetted_src, loc_delta_table); + + + //TODO: copy the relations into tgt and (possibly) delta using inner_relation_copier + //TODO: unite the local delta with the delta passed in as an argument + NOT_IMPLEMENTED_YET(); + } + }; + + class finite_product_relation_plugin::converting_union_fn : public relation_union_fn { + scoped_ptr m_tr_union_fun; + public: + virtual void operator()(relation_base & tgtb, const relation_base & srcb, relation_base * deltab) { + SASSERT(srcb.get_plugin().is_finite_product_relation()); + const finite_product_relation & src = get(srcb); + finite_product_relation_plugin & plugin = src.get_plugin(); + scoped_rel tr_src = plugin.to_table_relation(src); + if(!m_tr_union_fun) { + m_tr_union_fun = plugin.get_manager().mk_union_fn(tgtb, *tr_src, deltab); + SASSERT(m_tr_union_fun); + } + (*m_tr_union_fun)(tgtb, *tr_src, deltab); + } + }; + + relation_union_fn * finite_product_relation_plugin::mk_union_fn(const relation_base & tgtb, const relation_base & srcb, + const relation_base * deltab) { + if(&srcb.get_plugin()!=this) { + return 0; + } + const finite_product_relation & src = get(srcb); + if(&tgtb.get_plugin()!=this || (deltab && &deltab->get_plugin()!=this) ) { + if(can_convert_to_table_relation(src)) { + return alloc(converting_union_fn); + } + return 0; + } + + const finite_product_relation * delta = get(deltab); + + return alloc(union_fn, get(tgtb), delta!=0); + } + + + class finite_product_relation_plugin::filter_identical_fn : public relation_mutator_fn { + //the table and relation columns that should be identical + //the column numbering is local to the table or inner relation + unsigned_vector m_table_cols; + unsigned_vector m_rel_cols; + + scoped_ptr m_table_filter; + scoped_ptr m_rel_filter; + scoped_ptr m_tr_filter; + public: + filter_identical_fn(const finite_product_relation & r, unsigned col_cnt, const unsigned * identical_cols) + : m_table_filter(0), m_rel_filter(0), m_tr_filter(0) { + finite_product_relation_plugin & plugin = r.get_plugin(); + for(unsigned i=0; i1) { + m_table_filter = r.get_manager().mk_filter_identical_fn(r.get_table(), m_table_cols.size(), + m_table_cols.c_ptr()); + SASSERT(m_table_filter); + } + if(!m_table_cols.empty() && !m_rel_cols.empty()) { + unsigned tr_filter_table_cols[] = { m_table_cols[0] }; + unsigned tr_filter_rel_cols[] = { m_rel_cols[0] }; + m_tr_filter = plugin.mk_filter_identical_pairs(r, 1, tr_filter_table_cols, tr_filter_rel_cols); + SASSERT(m_tr_filter); + } + } + + void ensure_rel_filter(const relation_base & orel) { + SASSERT(m_rel_cols.size()>1); + if(m_rel_filter) { + return; + } + m_rel_filter = orel.get_manager().mk_filter_identical_fn(orel, m_rel_cols.size(), m_rel_cols.c_ptr()); + SASSERT(m_rel_filter); + } + + virtual void operator()(relation_base & rb) { + finite_product_relation & r = get(rb); + + if(m_table_cols.size()>1) { + (*m_table_filter)(r.get_table()); + } + + if(m_rel_cols.size()>1) { + r.garbage_collect(true); + unsigned rel_cnt = r.m_others.size(); + for(unsigned rel_idx=0; rel_idx m_table_filter; + scoped_ptr m_rel_filter; + unsigned m_col; + app_ref m_value; + public: + filter_equal_fn(const finite_product_relation & r, const relation_element & value, unsigned col) + : m_col(col), m_value(value, r.get_context().get_manager()) { + if(r.is_table_column(col)) { + table_element tval; + r.get_manager().relation_to_table(r.get_signature()[col], value, tval); + m_table_filter = r.get_manager().mk_filter_equal_fn(r.get_table(), tval, r.m_sig2table[col]); + } + } + + virtual void operator()(relation_base & rb) { + finite_product_relation & r = get(rb); + + if(m_table_filter) { + (*m_table_filter)(r.get_table()); + return; + } + r.garbage_collect(false); + relation_vector & inner_rels = r.m_others; + unsigned rel_cnt = inner_rels.size(); + for(unsigned i=0; i m_table_filter; + scoped_ptr m_rel_filter; + app_ref m_cond; + + idx_set m_table_cond_columns; + idx_set m_rel_cond_columns; + + //like m_table_cond_columns and m_rel_cond_columns, only the indexes are local to the + //table/relation, not to the signature of the whole relation + idx_set m_table_local_cond_columns; + idx_set m_rel_local_cond_columns; + + /** + If equal to 0, it means the interpreted condition uses all table columns. Then the original + table is used instead of the result of the projection. + */ + scoped_ptr m_table_cond_columns_project; + /** + \brief Column indexes of the global relations to which correspond the data columns in the table + that is result of applying the \c m_table_cond_columns_project functor. + */ + unsigned_vector m_global_origins_of_projected_columns; + + scoped_ptr m_assembling_join_project; + + + /** + \brief Renaming that transforms the variable numbers pointing to the global relation into + variables that point to the inner relation variables. + + The elements that do not correspond to columns of the inner relation (but rather to the table + columns) is modified in \c operator() when evaluating the condition for all the relevant + combinations of table values. + */ + expr_ref_vector m_renaming_for_inner_rel; + public: + filter_interpreted_fn(const finite_product_relation & r, app * condition) + : m_manager(r.get_context().get_manager()), + m_subst(r.get_context().get_var_subst()), + m_cond(condition, m_manager), + m_renaming_for_inner_rel(m_manager) { + relation_manager & rmgr = r.get_manager(); + + idx_set cond_columns; + collect_vars(m_manager, m_cond, cond_columns); + + unsigned sig_sz = r.get_signature().size(); + for(unsigned i=0; i tproj_scope; + const table_base * tproj; + if(m_table_cond_columns_project) { + tproj_scope = (*m_table_cond_columns_project)(rtable); + tproj = tproj_scope.get(); + } + else { + tproj = &rtable; + } + unsigned projected_data_cols = tproj->get_signature().size()-1; + SASSERT(m_table_cond_columns.num_elems()==projected_data_cols); + + table_signature filtered_sig = tproj->get_signature(); + filtered_sig.push_back(finite_product_relation::s_rel_idx_sort); + filtered_sig.set_functional_columns(1); + + relation_vector new_rels; + + scoped_rel filtered_table = tplugin.mk_empty(filtered_sig); + table_fact f; + unsigned renaming_ofs = m_renaming_for_inner_rel.size()-1; + table_base::iterator pit = tproj->begin(); + table_base::iterator pend = tproj->end(); + for(; pit!=pend; ++pit) { + pit->get_fact(f); + unsigned old_rel_idx = static_cast(f.back()); + const relation_base & old_rel = r.get_inner_rel(old_rel_idx); + + //put the table values into the substitution + for(unsigned i=0; i filter = rmgr.mk_filter_interpreted_fn(*new_rel, to_app(inner_cond)); + (*filter)(*new_rel); + + if(new_rel->empty()) { + new_rel->deallocate(); + continue; + } + + unsigned new_rel_idx = new_rels.size(); + new_rels.push_back(new_rel); + f.push_back(new_rel_idx); + filtered_table->add_fact(f); + } + + if(!m_assembling_join_project) { + unsigned_vector table_cond_columns_vect; + for(unsigned i=0; i new_table = (*m_assembling_join_project)(rtable, *filtered_table); + r.reset(); + r.init(*new_table, new_rels, true); + } + }; + + relation_mutator_fn * finite_product_relation_plugin::mk_filter_interpreted_fn(const relation_base & rb, + app * condition) { + if(&rb.get_plugin()!=this) { + return 0; + } + return alloc(filter_interpreted_fn, get(rb), condition); + } + + class finite_product_relation_plugin::negation_filter_fn : public relation_intersection_filter_fn { + + unsigned_vector m_r_cols; + unsigned_vector m_neg_cols; + + scoped_ptr m_table_neg_filter; + scoped_ptr m_table_neg_complement_selector; + scoped_ptr m_neg_intersection_join; + scoped_ptr m_table_intersection_join; + scoped_ptr m_table_overlap_union; + scoped_ptr m_table_subtract; + scoped_ptr m_inner_subtract; + scoped_ptr m_overlap_table_last_column_remover; + scoped_ptr m_r_table_union; + + bool m_table_overlaps_only; + + unsigned_vector m_r_shared_table_cols; + unsigned_vector m_neg_shared_table_cols; + + unsigned_vector m_r_shared_rel_cols; + unsigned_vector m_neg_shared_rel_cols; + public: + negation_filter_fn(const finite_product_relation & r, const finite_product_relation & neg, + unsigned joined_col_cnt, const unsigned * r_cols, const unsigned * neg_cols) + : m_r_cols(joined_col_cnt, r_cols), + m_neg_cols(joined_col_cnt, neg_cols), + m_table_overlaps_only(true) { + const table_signature & tsig = r.m_table_sig; + const table_base & rtable = r.get_table(); + relation_manager & rmgr = r.get_manager(); + + for(unsigned i=0; iget_signature().size() , all_rel_cols); + m_parent.m_inner_subtract = m_r.get_manager().mk_filter_by_negation_fn(*r_inner, + inters_inner, all_rel_cols, all_rel_cols); + } + (*m_parent.m_inner_subtract)(*r_inner, inters_inner); + + unsigned new_rel_num = m_r.get_next_rel_idx(); + m_r.set_inner_rel(new_rel_num, r_inner); + func_columns[0]=new_rel_num; + return true; + } + }; + + + virtual void operator()(relation_base & rb, const relation_base & negb) { + finite_product_relation & r = get(rb); + const finite_product_relation & neg = get(negb); + + if(m_table_overlaps_only) { + handle_only_tables_overlap_case(r, neg); + return; + } + + finite_product_relation_plugin & plugin = r.get_plugin(); + if(!m_neg_intersection_join) { + } + scoped_rel intersection = get((*m_neg_intersection_join)(r, neg)); + SASSERT(&intersection->get_plugin()==&plugin); //the result of join is also finite product + SASSERT(r.get_signature()==intersection->get_signature()); + + table_base & r_table = r.get_table(); + table_plugin & tplugin = r_table.get_plugin(); + relation_manager & rmgr = r.get_manager(); + + //we need to do this before we perform the \c m_table_subtract + //the sigrature of the \c table_overlap0 relation is + //(data_columns)(original rel idx)(subtracted rel idx) + scoped_rel table_overlap0 = (*m_table_intersection_join)(r_table, + intersection->get_table()); + + //the rows that don't appear in the table of the intersection are safe to stay + (*m_table_subtract)(r_table, intersection->get_table()); + + //now we will examine the rows that do appear in the intersection + + //There are no functional columns in the \c table_overlap0 relation (because of + //the project we did). We need to make both rel idx columns functional. + //We do not lose any rows, since the data columns by themselves are unique. + + table_signature table_overlap_sig(table_overlap0->get_signature()); + table_overlap_sig.set_functional_columns(2); + scoped_rel table_overlap = tplugin.mk_empty(table_overlap_sig); + + if(!m_table_overlap_union) { + m_table_overlap_union = rmgr.mk_union_fn(*table_overlap, *table_overlap0); + SASSERT(m_table_overlap_union); + } + (*m_table_overlap_union)(*table_overlap, *table_overlap0); + + { + rel_subtractor * mutator = alloc(rel_subtractor, *this, r, *intersection); + scoped_ptr mapper = rmgr.mk_map_fn(*table_overlap, mutator); + (*mapper)(*table_overlap); + } + + if(!m_overlap_table_last_column_remover) { + unsigned removed_col = table_overlap->get_signature().size()-1; + m_overlap_table_last_column_remover = rmgr.mk_project_fn(*table_overlap, 1, &removed_col); + } + scoped_rel final_overlapping_rows_table = + (*m_overlap_table_last_column_remover)(*table_overlap); + + if(!m_r_table_union) { + m_r_table_union = rmgr.mk_union_fn(r_table, *final_overlapping_rows_table); + } + + (*m_r_table_union)(r_table, *final_overlapping_rows_table); + } + }; + + relation_intersection_filter_fn * finite_product_relation_plugin::mk_filter_by_negation_fn(const relation_base & rb, + const relation_base & negb, unsigned joined_col_cnt, + const unsigned * r_cols, const unsigned * negated_cols) { + if(&rb.get_plugin()!=this || &negb.get_plugin()!=this) { + return 0; + } + + return alloc(negation_filter_fn, get(rb), get(negb), joined_col_cnt, r_cols, negated_cols); + } + + + class finite_product_relation_plugin::filter_identical_pairs_fn : public relation_mutator_fn { + scoped_ptr m_tproject_fn; //if zero, no columns need to be projected away + unsigned m_col_cnt; + unsigned_vector m_table_cols; + unsigned_vector m_rel_cols; + + scoped_ptr m_assembling_join_project; + scoped_ptr m_updating_union; + public: + filter_identical_pairs_fn(const finite_product_relation & r, unsigned col_cnt, const unsigned * table_cols, + const unsigned * rel_cols) : + m_col_cnt(col_cnt), + m_table_cols(col_cnt, table_cols), + m_rel_cols(col_cnt, rel_cols) { + SASSERT(col_cnt>0); + const table_signature & tsig = r.m_table_sig; + unsigned t_sz = tsig.size(); + + sort_two_arrays(col_cnt, m_table_cols.begin(), m_rel_cols.begin()); + SASSERT(m_table_cols.back() tproj; + if(m_tproject_fn) { + tproj = (*m_tproject_fn)(r.get_table()); + } + else { + tproj = r.get_table().clone(); + } + SASSERT(m_col_cnt+1==tproj->get_signature().size()); + + table_signature filtered_sig = tproj->get_signature(); + filtered_sig.push_back(finite_product_relation::s_rel_idx_sort); + filtered_sig.set_functional_columns(1); + + relation_vector new_rels; + scoped_rel filtered_table = tplugin.mk_empty(filtered_sig); + table_fact f; + table_base::iterator pit = tproj->begin(); + table_base::iterator pend = tproj->end(); + for(; pit!=pend; ++pit) { + pit->get_fact(f); + unsigned old_rel_idx = static_cast(f.back()); + const relation_base & old_rel = r.get_inner_rel(old_rel_idx); + relation_base * new_rel = old_rel.clone(); + for(unsigned i=0; i filter = rmgr.mk_filter_equal_fn(*new_rel, r_el, m_rel_cols[i]); + (*filter)(*new_rel); + } + + if(new_rel->empty()) { + new_rel->deallocate(); + continue; + } + + unsigned new_rel_idx = new_rels.size(); + new_rels.push_back(new_rel); + f.push_back(new_rel_idx); + filtered_table->add_fact(f); + } + + if(!m_assembling_join_project) { + m_assembling_join_project = mk_assembler_of_filter_result(rtable, *filtered_table, m_table_cols); + } + + scoped_rel new_table = (*m_assembling_join_project)(r.get_table(), *filtered_table); + + r.reset(); + r.init(*new_table, new_rels, true); + } + }; + + relation_mutator_fn * finite_product_relation_plugin::mk_filter_identical_pairs(const finite_product_relation & r, + unsigned col_cnt, const unsigned * table_cols, const unsigned * rel_cols) { + return alloc(filter_identical_pairs_fn, r, col_cnt, table_cols, rel_cols); + } + + table_join_fn * finite_product_relation_plugin::mk_assembler_of_filter_result(const table_base & relation_table, + const table_base & filtered_table, const unsigned_vector & selected_columns) { + + table_plugin & tplugin = relation_table.get_plugin(); + const table_signature & rtable_sig = relation_table.get_signature(); + unsigned rtable_sig_sz = rtable_sig.size(); + unsigned selected_col_cnt = selected_columns.size(); + SASSERT(selected_col_cnt+2==filtered_table.get_signature().size()); + SASSERT(rtable_sig.functional_columns()==1); + SASSERT(filtered_table.get_signature().functional_columns()==1); + + unsigned_vector rtable_joined_cols; + rtable_joined_cols.append(selected_col_cnt, selected_columns.c_ptr()); //filtered table cols + rtable_joined_cols.push_back(rtable_sig_sz-1); //unfiltered relation indexes + + unsigned_vector filtered_joined_cols; + add_sequence(0, selected_col_cnt, filtered_joined_cols); //filtered table cols + filtered_joined_cols.push_back(selected_col_cnt); //unfiltered relation indexes + SASSERT(rtable_joined_cols.size()==filtered_joined_cols.size()); + + //the signature after join: + //(all relation table columns)(all filtered relation table columns)(unfiltered rel idx non-func from 'filtered') + // (unfiltered rel idx func from 'rtable')(filtered rel idx) + unsigned_vector removed_cols; + unsigned filtered_nonfunc_ofs = rtable_sig_sz-1; + add_sequence(filtered_nonfunc_ofs, selected_col_cnt, removed_cols); //data columns from 'filtered' + unsigned idx_ofs = filtered_nonfunc_ofs+selected_col_cnt; + removed_cols.push_back(idx_ofs); //unfiltered relation indexes from 'filtered' + removed_cols.push_back(idx_ofs+1); //unfiltered relation indexes from rtable + + table_join_fn * res = tplugin.get_manager().mk_join_project_fn(relation_table, filtered_table, + rtable_joined_cols, filtered_joined_cols, removed_cols); + SASSERT(res); + return res; + } + + // ----------------------------------- + // + // finite_product_relation + // + // ----------------------------------- + + finite_product_relation::finite_product_relation(finite_product_relation_plugin & p, + const relation_signature & s, const bool * table_columns, table_plugin & tplugin, + relation_plugin & oplugin, family_id other_kind) + : relation_base(p, s), + m_other_plugin(oplugin), + m_other_kind(other_kind), + m_full_rel_idx(UINT_MAX) { + const relation_signature & rel_sig = get_signature(); + unsigned sz = rel_sig.size(); + m_sig2table.resize(sz, UINT_MAX); + m_sig2other.resize(sz, UINT_MAX); + for(unsigned i=0; iclone()), + m_others(r.m_others), + m_available_rel_indexes(r.m_available_rel_indexes), + m_full_rel_idx(r.m_full_rel_idx), + m_live_rel_collection_project(), + m_empty_rel_removal_filter() { + //m_others is now just a shallow copy, we need use clone of the relations that in it now + unsigned other_sz = m_others.size(); + for(unsigned i=0; ideallocate(); + relation_vector::iterator it = m_others.begin(); + relation_vector::iterator end = m_others.end(); + for(; it!=end; ++it) { + relation_base * rel= *it; + if(rel) { + rel->deallocate(); + } + } + } + + context & finite_product_relation::get_context() const { + return get_manager().get_context(); + } + + unsigned finite_product_relation::get_next_rel_idx() const { + unsigned res; + if(!m_available_rel_indexes.empty()) { + res = m_available_rel_indexes.back(); + m_available_rel_indexes.pop_back(); + } + else { + res = m_others.size(); + m_others.push_back(0); + } + SASSERT(m_others[res]==0); + return res; + } + + void finite_product_relation::recycle_rel_idx(unsigned idx) const { + SASSERT(m_others[idx]==0); + m_available_rel_indexes.push_back(idx); + } + + unsigned finite_product_relation::get_full_rel_idx() { + if(m_full_rel_idx==UINT_MAX) { + m_full_rel_idx = get_next_rel_idx(); + relation_base * full_other = mk_full_inner(0); + m_others[m_full_rel_idx] = full_other; + } + return m_full_rel_idx; + } + + void finite_product_relation::init(const table_base & table_vals, const relation_vector & others, bool contiguous) { + SASSERT(m_table_sig.equal_up_to_fn_mark(table_vals.get_signature())); + SASSERT(empty()); + if(!m_others.empty()) { + garbage_collect(false); + } + SASSERT(m_others.empty()); + + m_others = others; + scoped_ptr table_union = get_manager().mk_union_fn(get_table(), table_vals); + (*table_union)(get_table(), table_vals); + + if(!contiguous) { + unsigned rel_cnt = m_others.size(); + for(unsigned i=0; isuggest_fact(t_f)) { + SASSERT(t_f.back()==new_rel_idx); + new_rel = mk_empty_inner(); + } else { + new_rel = get_inner_rel(t_f.back()).clone(); + + t_f[t_f.size()-1]=new_rel_idx; + m_table->ensure_fact(t_f); + } + new_rel->add_fact(o_f); + set_inner_rel(new_rel_idx, new_rel); + } + + bool finite_product_relation::contains_fact(const relation_fact & f) const { + table_fact t_f; + extract_table_fact(f, t_f); + + if(!m_table->fetch_fact(t_f)) { + return false; + } + + relation_fact o_f(get_context()); + extract_other_fact(f, o_f); + + const relation_base & other = get_inner_rel(t_f.back()); + + return other.contains_fact(o_f); + } + + bool finite_product_relation::empty() const { + garbage_collect(true); + return m_table->empty(); + } + + finite_product_relation * finite_product_relation::clone() const { + return alloc(finite_product_relation, *this); + } + + void finite_product_relation::complement_self(func_decl* p) { + unsigned other_sz = m_others.size(); + for(unsigned i=0; icomplement(p); + std::swap(m_others[i],r); + r->deallocate(); + } + table_element full_rel_idx = get_full_rel_idx(); + scoped_rel complement_table = m_table->complement(p, &full_rel_idx); + + scoped_ptr u_fn = get_manager().mk_union_fn(*m_table, *complement_table, 0); + SASSERT(u_fn); + (*u_fn)(*m_table, *complement_table, 0); + } + + finite_product_relation * finite_product_relation::complement(func_decl* p) const { + finite_product_relation * res = clone(); + res->complement_self(p); + return res; + } + + class finite_product_relation::live_rel_collection_reducer : public table_row_pair_reduce_fn { + idx_set & m_accumulator; + public: + live_rel_collection_reducer(idx_set & accumulator) : m_accumulator(accumulator) {} + + virtual void operator()(table_element * func_columns, const table_element * merged_func_columns) { + m_accumulator.insert(static_cast(merged_func_columns[0])); + } + }; + + void finite_product_relation::collect_live_relation_indexes(idx_set & res) const { + SASSERT(res.empty()); + unsigned table_data_col_cnt = m_table_sig.size()-1; + + if(table_data_col_cnt==0) { + if(!get_table().empty()) { + table_base::iterator iit = get_table().begin(); + table_base::iterator iend = get_table().end(); + + SASSERT(iit!=iend); + res.insert(static_cast((*iit)[0])); + SASSERT((++iit)==iend); + } + return; + } + + if(!m_live_rel_collection_project) { + buffer removed_cols; + removed_cols.resize(table_data_col_cnt); + for(unsigned i=0; i live_indexes_table = (*m_live_rel_collection_project)(get_table()); + res.swap(m_live_rel_collection_acc); + + SASSERT(live_indexes_table->get_signature().size()==1); + SASSERT(live_indexes_table->get_signature().functional_columns()==1); + if(!live_indexes_table->empty()) { + table_base::iterator iit = live_indexes_table->begin(); + table_base::iterator iend = live_indexes_table->end(); + + SASSERT(iit!=iend); + res.insert(static_cast((*iit)[0])); + SASSERT((++iit)==iend); + } + } + + void finite_product_relation::garbage_collect(bool remove_empty) const { + idx_set live_indexes; + collect_live_relation_indexes(live_indexes); + + scoped_rel empty_rel_indexes; //populated only if \c remove_empty==true + table_fact empty_rel_fact; + + unsigned rel_cnt = m_others.size(); +#if Z3DEBUG + unsigned encountered_live_indexes = 0; +#endif + for(unsigned rel_idx=0; rel_idxempty()) { + continue; + } + if(empty_rel_indexes==0) { + table_signature empty_rel_indexes_sig; + empty_rel_indexes_sig.push_back(s_rel_idx_sort); + empty_rel_indexes = get_table_plugin().mk_empty(empty_rel_indexes_sig); + } + empty_rel_fact.reset(); + empty_rel_fact.push_back(rel_idx); + empty_rel_indexes->add_fact(empty_rel_fact); + } + m_others[rel_idx]->deallocate(); + m_others[rel_idx] = 0; + if(rel_idx==m_full_rel_idx) { + m_full_rel_idx = UINT_MAX; + } + recycle_rel_idx(rel_idx); + } + SASSERT(encountered_live_indexes==live_indexes.num_elems()); + + if(m_available_rel_indexes.size()==m_others.size()) { + m_available_rel_indexes.reset(); + m_others.reset(); + } + + if(empty_rel_indexes) { + SASSERT(remove_empty); + + if(!m_empty_rel_removal_filter) { + unsigned t_joined_cols[] = { m_table_sig.size()-1 }; + unsigned ei_joined_cols[] = { 0 }; + m_empty_rel_removal_filter = get_manager().mk_filter_by_negation_fn(get_table(), *empty_rel_indexes, + 1, t_joined_cols, ei_joined_cols); + } + + (*m_empty_rel_removal_filter)(*m_table, *empty_rel_indexes); + } + } + + bool finite_product_relation::try_unify_specifications(ptr_vector & rels) { + if(rels.empty()) { + return true; + } + unsigned sig_sz = rels.back()->get_signature().size(); + svector table_cols(sig_sz, true); + + ptr_vector::iterator it = rels.begin(); + ptr_vector::iterator end = rels.end(); + for(; it!=end; ++it) { + finite_product_relation & rel = **it; + for(unsigned i=0; i pr_fun = get_manager().mk_project_fn(get_table(), to_project_away); + table_base * moved_cols_table = (*pr_fun)(get_table()); //gets destroyed inside moved_cols_trel + scoped_rel moved_cols_trel = + rmgr.get_table_relation_plugin(moved_cols_table->get_plugin()).mk_from_table(moved_cols_sig, moved_cols_table); + + svector moved_cols_table_flags(moved_cols_sig.size(), false); + + scoped_rel moved_cols_rel = get_plugin().mk_empty(moved_cols_sig, + moved_cols_table_flags.c_ptr()); + + scoped_ptr union_fun = + get_manager().mk_union_fn(*moved_cols_rel, *moved_cols_trel); + SASSERT(union_fun); //the table_relation should be able to be 'unioned into' any relation + + (*union_fun)(*moved_cols_rel, *moved_cols_trel); + + unsigned_vector all_moved_cols_indexes; + add_sequence(0, moved_cols_sig.size(), all_moved_cols_indexes); + + scoped_ptr join_fun = get_manager().mk_join_project_fn(*this, *moved_cols_rel, new_rel_columns, + all_moved_cols_indexes, new_rel_columns, false); + + scoped_rel unordered_rel = (*join_fun)(*this, *moved_cols_rel); + SASSERT(unordered_rel->get_signature().size()==sig_sz); //the signature size should be the same as original + + //now we need to reorder the columns in the \c new_rel to match the original table + unsigned_vector permutation; + unsigned moved_cols_cnt = new_rel_columns.size(); + unsigned next_replaced_idx = 0; + unsigned next_orig_idx = 0; + for(unsigned i=0; i perm_fun = get_manager().mk_rename_fn(*unordered_rel, cycle); + //the scoped_rel wrapper does the destruction of the old object + unordered_rel = (*perm_fun)(*unordered_rel); + cycle.reset(); + } + + finite_product_relation & new_rel = finite_product_relation_plugin::get(*unordered_rel); + + //Swap the content of the current object and new_rel. On exitting the function new_rel will be destroyed + //since it points to the content of scoped_rel unordered_rel. + swap(new_rel); + + return true; + } + + void finite_product_relation::display(std::ostream & out) const { + + garbage_collect(true); + + out << "finite_product_relation:\n"; + + out << " table:\n"; + get_table().display(out); + + unsigned other_sz = m_others.size(); + for(unsigned i=0; iget_fact(tfact); + + const table_relation & orel = static_cast(get_inner_rel(tfact[rel_idx_col])); + const table_base & otable = orel.get_table(); + table_base::iterator oit = otable.begin(); + table_base::iterator oend = otable.end(); + for(; oit!=oend; ++oit) { + oit->get_fact(ofact); + + out << "\t("; + for(unsigned i=0; iget_fact(fact); + conjs.reset(); + SASSERT(fact.size() == fact_sz); + unsigned rel_idx = static_cast(fact[fact_sz-1]); + m_others[rel_idx]->to_formula(tmp); + for (unsigned i = 0; i + 1 < fact_sz; ++i) { + conjs.push_back(m.mk_eq(m.mk_var(i, sig[i]), util.mk_numeral(fact[i], sig[i]))); + } + sh(tmp, fact_sz-1, tmp); + conjs.push_back(tmp); + disjs.push_back(m.mk_and(conjs.size(), conjs.c_ptr())); + } + bool_rewriter(m).mk_or(disjs.size(), disjs.c_ptr(), fml); + } + +}; + diff --git a/lib/dl_finite_product_relation.h b/lib/dl_finite_product_relation.h new file mode 100644 index 000000000..165422661 --- /dev/null +++ b/lib/dl_finite_product_relation.h @@ -0,0 +1,366 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_finite_product_relation.h + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-09-24. + +Revision History: + +--*/ +#ifndef _DL_FINITE_PRODUCT_RELATION_H_ +#define _DL_FINITE_PRODUCT_RELATION_H_ + + +#include "dl_base.h" +#include "dl_relation_manager.h" +#include "dl_table_relation.h" + +namespace datalog { + + class finite_product_relation; + + void universal_delete(finite_product_relation* ptr); + + + class finite_product_relation_plugin : public relation_plugin { + friend class finite_product_relation; + public: + struct rel_spec { + family_id m_inner_kind; //null_family_id means we don't care about the kind + svector m_table_cols; + + rel_spec() : m_inner_kind(null_family_id) {} + rel_spec(const svector& table_cols) + : m_inner_kind(null_family_id), m_table_cols(table_cols) {} + + bool operator==(const rel_spec & o) const { + return m_inner_kind==o.m_inner_kind && vectors_equal(m_table_cols, o.m_table_cols); + } + struct hash { + unsigned operator()(const rel_spec & o) const { + return o.m_inner_kind^int_vector_hash(o.m_table_cols); + } + }; + }; + private: + + class join_fn; + class converting_join_fn; + class project_fn; + class rename_fn; + class union_fn; + class inner_singleton_union_fn; + class converting_union_fn; + class filter_identical_fn; + class filter_equal_fn; + class filter_interpreted_fn; + class negation_filter_fn; + class filter_identical_pairs_fn; + + relation_plugin & m_inner_plugin; + + rel_spec_store > m_spec_store; + + static symbol get_name(relation_plugin & inner_plugin); + family_id get_relation_kind(finite_product_relation & r, const bool * table_columns); + + static void get_all_possible_table_columns(relation_manager & rmgr, const relation_signature & s, + svector & table_columns); + void get_all_possible_table_columns(const relation_signature & s, svector & table_columns) { + get_all_possible_table_columns(get_manager(), s, table_columns); + } + + void split_signatures(const relation_signature & s, table_signature & table_sig, + relation_signature & remaining_sig); + void split_signatures(const relation_signature & s, const bool * table_columns, + table_signature & table_sig, relation_signature & remaining_sig); + public: + static finite_product_relation & get(relation_base & r); + static const finite_product_relation & get(const relation_base & r); + static finite_product_relation * get(relation_base * r); + static const finite_product_relation * get(const relation_base * r); + + static finite_product_relation_plugin & get_plugin(relation_manager & rmgr, relation_plugin & inner); + + finite_product_relation_plugin(relation_plugin & inner_plugin, relation_manager & manager); + + virtual void initialize(family_id fid); + + relation_plugin & get_inner_plugin() const { return m_inner_plugin; } + + virtual bool can_handle_signature(const relation_signature & s); + + virtual relation_base * mk_empty(const relation_signature & s); + /** + \c inner_kind==null_family_id means we don't care about the kind of the inner relation + */ + finite_product_relation * mk_empty(const relation_signature & s, const bool * table_columns, + family_id inner_kind=null_family_id); + finite_product_relation * mk_empty(const finite_product_relation & original); + virtual relation_base * mk_empty(const relation_base & original); + virtual relation_base * mk_empty(const relation_signature & s, family_id kind); + + virtual relation_base * mk_full(func_decl* p, const relation_signature & s); + + /** + \brief Return true if \c r can be converted to \c finite_product_relation_plugin either + by \c mk_from_table_relation or by \c mk_from_inner_relation. + */ + bool can_be_converted(const relation_base & r); + + /** + If the conversion cannot be performed, 0 is returned. + */ + finite_product_relation * mk_from_table_relation(const table_relation & r); + finite_product_relation * mk_from_inner_relation(const relation_base & r); + + bool can_convert_to_table_relation(const finite_product_relation & r); + table_relation * to_table_relation(const finite_product_relation & r); + + protected: + virtual relation_join_fn * mk_join_fn(const relation_base & t1, const relation_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2); + virtual relation_transformer_fn * mk_project_fn(const relation_base & t, unsigned col_cnt, + const unsigned * removed_cols); + virtual relation_transformer_fn * mk_rename_fn(const relation_base & t, unsigned permutation_cycle_len, + const unsigned * permutation_cycle); + virtual relation_union_fn * mk_union_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta); + virtual relation_mutator_fn * mk_filter_identical_fn(const relation_base & t, unsigned col_cnt, + const unsigned * identical_cols); + virtual relation_mutator_fn * mk_filter_equal_fn(const relation_base & t, const relation_element & value, + unsigned col); + virtual relation_mutator_fn * mk_filter_interpreted_fn(const relation_base & t, app * condition); + virtual relation_intersection_filter_fn * mk_filter_by_negation_fn(const relation_base & t, + const relation_base & negated_obj, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * negated_cols); + + private: + /** + \brief Create a filter that enforces equality between pairs of table and relation columns + + The column numbers in arrays \c table_cols and \c rel_cols must be local to the table/inner relation. + */ + relation_mutator_fn * mk_filter_identical_pairs(const finite_product_relation & r, unsigned col_cnt, + const unsigned * table_cols, const unsigned * rel_cols); + + /** + \brief Create a join-project operation that creates a table according to \c relation_table + but with references to relations updated and removed according to the content of \c filtered_table. + \c selected_columns contains sorted indexes of data columns in \c relation_table that are also in + the \c filtered_table (so that the first column in \c filtered_table corresponds to + \c selected_columns[0] -th column in \c relation_table etc...) + + Signature of \c relation_table: + (data columns)(functional column with indexes of relation objects) + + Signature of \c filtered_table: + (selected data columns)(non-functional column with original relation object indexes) + (functional column with indexes of filtered relation objects) + + */ + static table_join_fn * mk_assembler_of_filter_result(const table_base & relation_table, + const table_base & filtered_table, const unsigned_vector & selected_columns); + + }; + + class finite_product_relation : public relation_base { + friend class finite_product_relation_plugin; + friend class finite_product_relation_plugin::join_fn; + friend class finite_product_relation_plugin::project_fn; + friend class finite_product_relation_plugin::union_fn; + friend class finite_product_relation_plugin::rename_fn; + friend class finite_product_relation_plugin::inner_singleton_union_fn; + friend class finite_product_relation_plugin::filter_equal_fn; + friend class finite_product_relation_plugin::filter_identical_pairs_fn; + + class live_rel_collection_reducer; + + + public: + /** + Size of this sort determines how many different relation objects can we refer to. + */ + static const table_sort s_rel_idx_sort; + + + /** + \brief The last column in the signature is a functional column with index of the + associated inner relation. The other columns correspond to the relation signature + according to \c m_table2sig. + + It holds that \c m_table_sig.size()-1==m_table2sig.size() + */ + + table_signature m_table_sig; + unsigned_vector m_table2sig; // (ordered list) + unsigned_vector m_sig2table; //index of corresponding table column or UINT_MAX + private: + relation_signature m_other_sig; + unsigned_vector m_other2sig; // (ordered list) + public: + unsigned_vector m_sig2other; //index of corresponding other relation column or UINT_MAX + private: + relation_plugin & m_other_plugin; + family_id m_other_kind; + + mutable table_base * m_table; + public: + mutable relation_vector m_others; + private: + mutable unsigned_vector m_available_rel_indexes; + + /** + \c UINT_MAX means uninitialized. + If we can get away with it, we want to have a single full relation to refer to. + */ + mutable unsigned m_full_rel_idx; + + mutable idx_set m_live_rel_collection_acc; + mutable scoped_ptr m_live_rel_collection_project; + + mutable scoped_ptr m_empty_rel_removal_filter; + + void recycle_rel_idx(unsigned idx) const; + + // creates a full relation if it does not exist. + unsigned get_full_rel_idx(); + + + + public: + relation_base & get_inner_rel(table_element idx) + { SASSERT(idx(idx)); } + relation_base & get_inner_rel(unsigned idx) { SASSERT(m_others[idx]); return *m_others[idx]; } + const relation_base & get_inner_rel(unsigned idx) const + { return const_cast(*this).get_inner_rel(idx); } + + unsigned get_next_rel_idx() const; + + /** + The relation takes ownership of the \c inner object. + */ + void set_inner_rel(table_element idx, relation_base * inner) + { SASSERT(idx(idx), inner); } + /** + The relation takes ownership of the \c inner object. + */ + void set_inner_rel(unsigned idx, relation_base * inner) { + SASSERT(!m_others[idx]); + SASSERT(inner); + m_others[idx] = inner; + } + table_base & get_table() { return *m_table; } + + table_plugin & get_table_plugin() const { return get_table().get_plugin(); } + + void garbage_collect(bool remove_empty) const; + + /** + \brief Initialize an empty relation with table \c table_vals and relations in \c others. + + The relation object takes ownership of relations inside the \c others vector. + + If \c contiguous is true, it can be assumed that there are no zero elements in the \c others array. + */ + void init(const table_base & table_vals, const relation_vector & others, bool contiguous); + + private: + + + /** + \brief Extract the values of table non-functional columns from the relation fact. + The value of the functional column which determines index of the inner relation is undefined. + */ + void extract_table_fact(const relation_fact rf, table_fact & tf) const; + /** + \brief Extract the values of the inner relation columns from the relation fact. + */ + void extract_other_fact(const relation_fact rf, relation_fact & of) const; + + relation_base * mk_empty_inner(); + relation_base * mk_full_inner(func_decl* pred); + + + void complement_self(func_decl* pred); + + void collect_live_relation_indexes(idx_set & res) const; + + + /** + \brief Try to modify relations in \c rels so that they have the same columns corresponding to the table + and the inner relation (so that the union can be perofrmed on theim in a straightforward way). + + Relations in \c rels must all have equal signature. + + Even if the function fails and false is returned, some relations may already be modified. They are + in a valid state, but with different specification. + */ + static bool try_unify_specifications(ptr_vector & rels); + + bool try_modify_specification(const bool * table_cols); + + virtual bool can_swap(const relation_base & r) const + { return &get_plugin()==&r.get_plugin(); } + + /** + \brief Swap content of the current relation with the content of \c r. + + Both relations must come from the same plugin and be of the same signature. + */ + virtual void swap(relation_base & r); + + /** + \brief Create a \c finite_product_relation object. + */ + finite_product_relation(finite_product_relation_plugin & p, const relation_signature & s, + const bool * table_columns, table_plugin & tplugin, relation_plugin & oplugin, family_id other_kind); + finite_product_relation(const finite_product_relation & r); + virtual ~finite_product_relation(); + public: + context & get_context() const; + finite_product_relation_plugin & get_plugin() const { + return static_cast(relation_base::get_plugin()); + } + + bool is_table_column(unsigned col_idx) const { return m_sig2table[col_idx]!=UINT_MAX; } + + const table_base & get_table() const { return *m_table; } + + const relation_base & get_inner_rel(table_element idx) const + { SASSERT(idx(idx)); } + + /** + The function calls garbage_collect, so the internal state may change when it is called. + */ + virtual bool empty() const; + void reset() { m_table->reset(); garbage_collect(false); } + + virtual void add_fact(const relation_fact & f); + virtual bool contains_fact(const relation_fact & f) const; + + virtual finite_product_relation * clone() const; + virtual finite_product_relation * complement(func_decl* p) const; + + virtual void display(std::ostream & out) const; + virtual void display_tuples(func_decl & pred, std::ostream & out) const; + + virtual unsigned get_size_estimate_rows() const { return m_table->get_size_estimate_rows(); } + virtual unsigned get_size_estimate_bytes() const { return m_table->get_size_estimate_bytes(); } + + virtual void to_formula(expr_ref& fml) const; + }; + +}; + +#endif /* _DL_FINITE_PRODUCT_RELATION_H_ */ + diff --git a/lib/dl_instruction.cpp b/lib/dl_instruction.cpp new file mode 100644 index 000000000..9d6e9036f --- /dev/null +++ b/lib/dl_instruction.cpp @@ -0,0 +1,1056 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_instruction.cpp + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-09-14. + +Revision History: + +--*/ + +#include"ast_pp.h" +#include"stopwatch.h" +#include"dl_context.h" +#include"dl_util.h" +#include"dl_instruction.h" +#include"debug.h" +#include"warning.h" + +namespace datalog { + + // ----------------------------------- + // + // execution_context + // + // ----------------------------------- + + execution_context::execution_context(context & datalog_context) + : m_datalog_context(datalog_context), + m_stopwatch(0), + m_timelimit_ms(0), + m_eager_emptiness_checking(datalog_context.eager_emptiness_checking()) {} + + execution_context::~execution_context() { + reset(); + dealloc(m_stopwatch); + } + + void execution_context::reset() { + reg_vector::iterator it=m_registers.begin(); + reg_vector::iterator end=m_registers.end(); + for(; it != end; ++it) { + relation_base * rel = *it; + if (rel) { + rel->deallocate(); + } + } + m_registers.reset(); + m_reg_annotation.reset(); + reset_timelimit(); + } + + struct compare_size_proc { + typedef std::pair pr; + bool operator()(pr const& a, pr const& b) const { + return a.second > b.second; + } + + }; + void execution_context::report_big_relations(unsigned threshold, std::ostream & out) { + unsigned n = register_count(); + svector > sizes; + size_t total_bytes = 0; + for(unsigned i = 0; i < n; i++) { + unsigned sz = reg(i) ? reg(i)->get_size_estimate_bytes() : 0; + total_bytes += sz; + sizes.push_back(std::make_pair(i, sz)); + } + std::sort(sizes.begin(), sizes.end(), compare_size_proc()); + + out << "bytes " << total_bytes << "\n"; + out << "bytes\trows\tannotation\n"; + for(unsigned i = 0; i < n; i++) { + unsigned sz = sizes[i].second; + unsigned rg = sizes[i].first; + unsigned rows = reg(rg) ? reg(rg)->get_size_estimate_rows() : 0; + if (sz < threshold) { + continue; + } + std::string annotation; + get_register_annotation(i, annotation); + out << sz << "\t" << rows << "\t" << annotation << "\n"; + } + } + + void execution_context::set_timelimit(unsigned time_in_ms) { + SASSERT(time_in_ms > 0); + m_timelimit_ms = time_in_ms; + if (!m_stopwatch) { + m_stopwatch = alloc(stopwatch); + } + m_stopwatch->stop(); + m_stopwatch->reset(); + m_stopwatch->start(); + } + void execution_context::reset_timelimit() { + if (m_stopwatch) { + m_stopwatch->stop(); + } + m_timelimit_ms = 0; + } + + bool execution_context::should_terminate() { + return + memory::above_high_watermark() || + (m_stopwatch && + m_timelimit_ms != 0 && + m_timelimit_ms < static_cast(1000*m_stopwatch->get_current_seconds())); + } + + + // ----------------------------------- + // + // instruction + // + // ----------------------------------- + + instruction::~instruction() { + fn_cache::iterator it = m_fn_cache.begin(); + fn_cache::iterator end = m_fn_cache.end(); + for(; it != end; ++it) { + dealloc(it->m_value); + } + } + + void instruction::process_all_costs() { + process_costs(); + } + + void instruction::display_indented(context & ctx, std::ostream & out, std::string indentation) const { + out << indentation; + display_head_impl(ctx, out); + if (ctx.output_profile()) { + out << " {"; + output_profile(ctx, out); + out << '}'; + } + out << "\n"; + display_body_impl(ctx, out, indentation); + } + + class instr_io : public instruction { + bool m_store; + func_decl_ref m_pred; + reg_idx m_reg; + public: + instr_io(bool store, func_decl_ref pred, reg_idx reg) + : m_store(store), m_pred(pred), m_reg(reg) {} + virtual bool perform(execution_context & ctx) { + if (m_store) { + if (ctx.reg(m_reg)) { + ctx.get_datalog_context().store_relation(m_pred, ctx.release_reg(m_reg)); + } + else { + context & dctx = ctx.get_datalog_context(); + relation_base * empty_rel; + //the object referenced by sig is valid only until we call dctx.store_relation() + const relation_signature & sig = dctx.get_relation(m_pred).get_signature(); + empty_rel = dctx.get_rmanager().mk_empty_relation(sig, m_pred.get()); + dctx.store_relation(m_pred, empty_rel); + } + } + else { + relation_base& rel = ctx.get_datalog_context().get_relation(m_pred); + if ((!ctx.eager_emptiness_checking() || !rel.empty())) { + ctx.set_reg(m_reg, rel.clone()); + } + else { + ctx.make_empty(m_reg); + } + } + return true; + } + virtual void make_annotations(execution_context & ctx) { + ctx.set_register_annotation(m_reg, m_pred->get_name().bare_str()); + } + virtual void display_head_impl(context & ctx, std::ostream & out) const { + const char * rel_name = m_pred->get_name().bare_str(); + if (m_store) { + out << "store " << m_reg << " into " << rel_name; + } + else { + out << "load " << rel_name << " into " << m_reg; + } + } + }; + + instruction * instruction::mk_load(ast_manager & m, func_decl * pred, reg_idx tgt) { + return alloc(instr_io, false, func_decl_ref(pred, m), tgt); + } + + instruction * instruction::mk_store(ast_manager & m, func_decl * pred, reg_idx src) { + return alloc(instr_io, true, func_decl_ref(pred, m), src); + } + + + class instr_dealloc : public instruction { + reg_idx m_reg; + public: + instr_dealloc(reg_idx reg) : m_reg(reg) {} + virtual bool perform(execution_context & ctx) { + ctx.make_empty(m_reg); + return true; + } + virtual void make_annotations(execution_context & ctx) { + ctx.set_register_annotation(m_reg, "alloc"); + } + virtual void display_head_impl(context & ctx, std::ostream & out) const { + out << "dealloc " << m_reg; + } + }; + + instruction * instruction::mk_dealloc(reg_idx reg) { + return alloc(instr_dealloc, reg); + } + + class instr_clone_move : public instruction { + bool m_clone; + reg_idx m_src; + reg_idx m_tgt; + public: + instr_clone_move(bool clone, reg_idx src, reg_idx tgt) + : m_clone(clone), m_src(src), m_tgt(tgt) {} + virtual bool perform(execution_context & ctx) { + ctx.make_empty(m_tgt); + if (m_clone) { + ctx.set_reg(m_tgt, ctx.reg(m_src) ? ctx.reg(m_src)->clone() : 0); + } + else { + ctx.set_reg(m_tgt, ctx.reg(m_src) ? ctx.release_reg(m_src) : 0); + } + return true; + } + virtual void make_annotations(execution_context & ctx) { + std::string str; + if (ctx.get_register_annotation(m_src, str)) { + ctx.set_register_annotation(m_tgt, str); + } + else if (ctx.get_register_annotation(m_tgt, str)) { + ctx.set_register_annotation(m_src, str); + } + } + virtual void display_head_impl(context & ctx, std::ostream & out) const { + out << (m_clone ? "clone " : "move ") << m_src << " into " << m_tgt; + } + }; + + instruction * instruction::mk_clone(reg_idx from, reg_idx to) { + return alloc(instr_clone_move, true, from, to); + } + instruction * instruction::mk_move(reg_idx from, reg_idx to) { + return alloc(instr_clone_move, false, from, to); + } + + + class instr_while_loop : public instruction { + typedef const vector idx_vector; + idx_vector m_controls; + instruction_block * m_body; + + bool control_is_empty(execution_context & ctx) { + idx_vector::const_iterator it=m_controls.begin(); + idx_vector::const_iterator end=m_controls.end(); + for(; it != end; ++it) { + reg_idx r = *it; + if (ctx.reg(r) && !ctx.reg(r)->empty()) { + return false; + } + } + return true; + } + protected: + virtual void process_all_costs() { + instruction::process_all_costs(); + m_body->process_all_costs(); + } + public: + instr_while_loop(unsigned control_reg_cnt, const reg_idx * control_regs, instruction_block * body) + : m_controls(control_reg_cnt, control_regs), m_body(body) {} + virtual ~instr_while_loop() { + dealloc(m_body); + } + virtual bool perform(execution_context & ctx) { + TRACE("dl", tout << "loop entered\n";); + unsigned count = 0; + while (!control_is_empty(ctx)) { + IF_VERBOSE(10, verbose_stream() << "looping ... " << count++ << "\n";); + if (!m_body->perform(ctx)) { + TRACE("dl", tout << "while loop terminated before completion\n";); + return false; + } + } + TRACE("dl", tout << "while loop exited\n";); + return true; + } + virtual void make_annotations(execution_context & ctx) { + m_body->make_annotations(ctx); + } + virtual void display_head_impl(context & ctx, std::ostream & out) const { + out << "while"; + print_container(m_controls, out); + } + virtual void display_body_impl(context & ctx, std::ostream & out, std::string indentation) const { + m_body->display_indented(ctx, out, indentation+" "); + } + }; + + instruction * instruction::mk_while_loop(unsigned control_reg_cnt, const reg_idx * control_regs, + instruction_block * body) { + return alloc(instr_while_loop, control_reg_cnt, control_regs, body); + } + + + class instr_join : public instruction { + typedef unsigned_vector column_vector; + reg_idx m_rel1; + reg_idx m_rel2; + column_vector m_cols1; + column_vector m_cols2; + reg_idx m_res; + public: + instr_join(reg_idx rel1, reg_idx rel2, unsigned col_cnt, const unsigned * cols1, + const unsigned * cols2, reg_idx result) + : m_rel1(rel1), m_rel2(rel2), m_cols1(col_cnt, cols1), + m_cols2(col_cnt, cols2), m_res(result) {} + virtual bool perform(execution_context & ctx) { + ctx.make_empty(m_res); + if (!ctx.reg(m_rel1) || !ctx.reg(m_rel2)) { + return true; + } + relation_join_fn * fn; + const relation_base & r1 = *ctx.reg(m_rel1); + const relation_base & r2 = *ctx.reg(m_rel2); + if (!find_fn(r1, r2, fn)) { + fn = r1.get_manager().mk_join_fn(r1, r2, m_cols1, m_cols2); + if (!fn) { + throw default_exception("trying to perform unsupported join operation on relations of kinds %s and %s", + r1.get_plugin().get_name().bare_str(), r2.get_plugin().get_name().bare_str()); + } + store_fn(r1, r2, fn); + } + + TRACE("dl", + r1.get_signature().output(ctx.get_datalog_context().get_manager(), tout); + tout<<":"<\n";); + + try { + ctx.set_reg(m_res, (*fn)(r1, r2)); + } + catch(...) + { + std::string annotation; + unsigned sz; + ctx.get_register_annotation(m_rel1, annotation); + sz = ctx.reg(m_rel1)?ctx.reg(m_rel1)->get_size_estimate_rows():0; + std::cout << m_rel1 << "\t" << sz << "\t" << annotation << "\n"; + ctx.get_register_annotation(m_rel2, annotation); + sz = ctx.reg(m_rel2)?ctx.reg(m_rel2)->get_size_estimate_rows():0; + std::cout << m_rel2 << "\t" << sz << "\t" << annotation << "\n"; + throw; + } + + TRACE("dl", + ctx.reg(m_res)->get_signature().output(ctx.get_datalog_context().get_manager(), tout); + tout<<":"<get_size_estimate_rows()<<"\n";); + + if (ctx.eager_emptiness_checking() && ctx.reg(m_res)->empty()) { + ctx.make_empty(m_res); + } + return true; + } + virtual void make_annotations(execution_context & ctx) { + std::string a1 = "rel1", a2 = "rel2"; + ctx.get_register_annotation(m_rel1, a1); + ctx.get_register_annotation(m_rel1, a1); + ctx.set_register_annotation(m_res, "join " + a1 + " " + a2); + } + virtual void display_head_impl(context & ctx, std::ostream & out) const { + out << "join " << m_rel1; + print_container(m_cols1, out); + out << " and " << m_rel2; + print_container(m_cols2, out); + out << " into " << m_res; + } + }; + + instruction * instruction::mk_join(reg_idx rel1, reg_idx rel2, unsigned col_cnt, + const unsigned * cols1, const unsigned * cols2, reg_idx result) { + return alloc(instr_join, rel1, rel2, col_cnt, cols1, cols2, result); + } + + class instr_filter_equal : public instruction { + reg_idx m_reg; + app_ref m_value; + unsigned m_col; + public: + instr_filter_equal(ast_manager & m, reg_idx reg, const relation_element & value, unsigned col) + : m_reg(reg), m_value(value, m), m_col(col) {} + virtual bool perform(execution_context & ctx) { + if (!ctx.reg(m_reg)) { + return true; + } + + relation_mutator_fn * fn; + relation_base & r = *ctx.reg(m_reg); + if (!find_fn(r, fn)) { + fn = r.get_manager().mk_filter_equal_fn(r, m_value, m_col); + if (!fn) { + throw default_exception( + "trying to perform unsupported filter_equal operation on a relation of kind %s", + r.get_plugin().get_name().bare_str()); + } + store_fn(r, fn); + } + (*fn)(r); + + if (ctx.eager_emptiness_checking() && r.empty()) { + ctx.make_empty(m_reg); + } + return true; + } + virtual void make_annotations(execution_context & ctx) { + std::stringstream a; + a << "filter_equal " << m_col << " val: " << ctx.get_datalog_context().get_rmanager().to_nice_string(m_value); + ctx.set_register_annotation(m_reg, a.str()); + } + virtual void display_head_impl(context & ctx, std::ostream & out) const { + out << "filter_equal " << m_reg << " col: " << m_col << " val: " + << ctx.get_rmanager().to_nice_string(m_value); + } + }; + + instruction * instruction::mk_filter_equal(ast_manager & m, reg_idx reg, const relation_element & value, + unsigned col) { + return alloc(instr_filter_equal, m, reg, value, col); + } + + + class instr_filter_identical : public instruction { + typedef vector column_vector; + reg_idx m_reg; + column_vector m_cols; + public: + instr_filter_identical(reg_idx reg, unsigned col_cnt, const unsigned * identical_cols) + : m_reg(reg), m_cols(col_cnt, identical_cols) {} + virtual bool perform(execution_context & ctx) { + if (!ctx.reg(m_reg)) { + return true; + } + + relation_mutator_fn * fn; + relation_base & r = *ctx.reg(m_reg); + if (!find_fn(r, fn)) { + fn = r.get_manager().mk_filter_identical_fn(r, m_cols.size(), m_cols.c_ptr()); + if (!fn) { + throw default_exception( + "trying to perform unsupported filter_identical operation on a relation of kind %s", + r.get_plugin().get_name().bare_str()); + } + store_fn(r, fn); + } + (*fn)(r); + + if (ctx.eager_emptiness_checking() && r.empty()) { + ctx.make_empty(m_reg); + } + return true; + } + virtual void display_head_impl(context & ctx, std::ostream & out) const { + out << "filter_identical " << m_reg << " "; + print_container(m_cols, out); + } + virtual void make_annotations(execution_context & ctx) { + ctx.set_register_annotation(m_reg, "filter_identical"); + } + }; + + instruction * instruction::mk_filter_identical(reg_idx reg, unsigned col_cnt, const unsigned * identical_cols) { + return alloc(instr_filter_identical, reg, col_cnt, identical_cols); + } + + + class instr_filter_interpreted : public instruction { + reg_idx m_reg; + app_ref m_cond; + public: + instr_filter_interpreted(reg_idx reg, app_ref & condition) + : m_reg(reg), m_cond(condition) {} + virtual bool perform(execution_context & ctx) { + if (!ctx.reg(m_reg)) { + return true; + } + + relation_mutator_fn * fn; + relation_base & r = *ctx.reg(m_reg); + if (!find_fn(r, fn)) { + fn = r.get_manager().mk_filter_interpreted_fn(r, m_cond); + if (!fn) { + throw default_exception( + "trying to perform unsupported filter_interpreted operation on a relation of kind %s", + r.get_plugin().get_name().bare_str()); + } + store_fn(r, fn); + } + (*fn)(r); + + if (ctx.eager_emptiness_checking() && r.empty()) { + ctx.make_empty(m_reg); + } + return true; + } + virtual void display_head_impl(context & ctx, std::ostream & out) const { + out << "filter_interpreted " << m_reg << " using " + << mk_pp(m_cond, m_cond.get_manager()); + } + virtual void make_annotations(execution_context & ctx) { + std::stringstream a; + a << "filter_interpreted " << mk_pp(m_cond, m_cond.get_manager()); + ctx.set_register_annotation(m_reg, a.str()); + } + + }; + + instruction * instruction::mk_filter_interpreted(reg_idx reg, app_ref & condition) { + return alloc(instr_filter_interpreted, reg, condition); + } + + + class instr_union : public instruction { + reg_idx m_src; + reg_idx m_tgt; + reg_idx m_delta; + bool m_widen; //if true, widening is performed intead of an union + public: + instr_union(reg_idx src, reg_idx tgt, reg_idx delta, bool widen) + : m_src(src), m_tgt(tgt), m_delta(delta), m_widen(widen) {} + virtual bool perform(execution_context & ctx) { + TRACE("dl", tout << "union " << m_src << " into " << m_tgt + << " " << ctx.reg(m_src) << " " << ctx.reg(m_tgt) << "\n";); + if (!ctx.reg(m_src)) { + return true; + } + relation_base & r_src = *ctx.reg(m_src); + if (!ctx.reg(m_tgt)) { + relation_base * new_tgt = r_src.get_plugin().mk_empty(r_src); + ctx.set_reg(m_tgt, new_tgt); + } + relation_base & r_tgt = *ctx.reg(m_tgt); + if (m_delta!=execution_context::void_register && !ctx.reg(m_delta)) { + relation_base * new_delta = r_tgt.get_plugin().mk_empty(r_tgt); + ctx.set_reg(m_delta, new_delta); + } + relation_base * r_delta = (m_delta!=execution_context::void_register) ? ctx.reg(m_delta) : 0; + + relation_union_fn * fn; + + if (r_delta) { + if (!find_fn(r_tgt, r_src, *r_delta, fn)) { + if (m_widen) { + fn = r_src.get_manager().mk_widen_fn(r_tgt, r_src, r_delta); + } + else { + fn = r_src.get_manager().mk_union_fn(r_tgt, r_src, r_delta); + } + if (!fn) { + std::stringstream sstm; + sstm << "trying to perform unsupported union operation on relations of kinds "; + sstm << r_tgt.get_plugin().get_name() << ", " << r_src.get_plugin().get_name() << " and "; + sstm << r_delta->get_plugin().get_name(); + throw default_exception(sstm.str()); + } + store_fn(r_tgt, r_src, *r_delta, fn); + } + } + else { + if (!find_fn(r_tgt, r_src, fn)) { + if (m_widen) { + fn = r_src.get_manager().mk_widen_fn(r_tgt, r_src, 0); + } + else { + fn = r_src.get_manager().mk_union_fn(r_tgt, r_src, 0); + } + if (!fn) { + std::stringstream sstm; + sstm << "trying to perform unsupported union operation on relations of kinds " + << r_tgt.get_plugin().get_name() << " and " + << r_src.get_plugin().get_name(); + throw default_exception(sstm.str()); + } + store_fn(r_tgt, r_src, fn); + } + } + + TRACE("dl_verbose", r_tgt.display(tout <<"pre-union:");); + + (*fn)(r_tgt, r_src, r_delta); + + TRACE("dl_verbose", + r_src.display(tout <<"src:"); + r_tgt.display(tout <<"post-union:"); + if (r_delta) { + r_delta->display(tout <<"delta:"); + }); + + if (ctx.eager_emptiness_checking() && r_delta && r_delta->empty()) { + ctx.make_empty(m_delta); + } + + return true; + } + virtual void make_annotations(execution_context & ctx) { + std::string str; + if (ctx.get_register_annotation(m_tgt, str) && m_delta!=execution_context::void_register) { + ctx.set_register_annotation(m_delta, "delta of "+str); + } + } + virtual void display_head_impl(context & ctx, std::ostream & out) const { + out << (m_widen ? "widen " : "union ") << m_src << " into " << m_tgt; + if (m_delta!=execution_context::void_register) { + out << " with delta " << m_delta; + } + } + }; + + instruction * instruction::mk_union(reg_idx src, reg_idx tgt, reg_idx delta) { + return alloc(instr_union, src, tgt, delta, false); + } + + instruction * instruction::mk_widen(reg_idx src, reg_idx tgt, reg_idx delta) { + return alloc(instr_union, src, tgt, delta, true); + } + + + class instr_project_rename : public instruction { + typedef vector column_vector; + bool m_projection; + reg_idx m_src; + column_vector m_cols; + reg_idx m_tgt; + public: + instr_project_rename(bool projection, reg_idx src, unsigned col_cnt, const unsigned * cols, + reg_idx tgt) : m_projection(projection), m_src(src), + m_cols(col_cnt, cols), m_tgt(tgt) {} + virtual bool perform(execution_context & ctx) { + ctx.make_empty(m_tgt); + if (!ctx.reg(m_src)) { + return true; + } + + relation_transformer_fn * fn; + relation_base & r_src = *ctx.reg(m_src); + if (!find_fn(r_src, fn)) { + if (m_projection) { + fn = r_src.get_manager().mk_project_fn(r_src, m_cols.size(), m_cols.c_ptr()); + } + else { + fn = r_src.get_manager().mk_rename_fn(r_src, m_cols.size(), m_cols.c_ptr()); + } + if (!fn) { + std::stringstream sstm; + sstm << "trying to perform unsupported " << (m_projection ? "project" : "rename"); + sstm << " operation on a relation of kind " << r_src.get_plugin().get_name(); + throw default_exception(sstm.str()); + } + store_fn(r_src, fn); + } + ctx.set_reg(m_tgt, (*fn)(r_src)); + + return true; + } + virtual void display_head_impl(context & ctx, std::ostream & out) const { + out << (m_projection ? "project " : "rename ") << m_src << " into " << m_tgt; + out << (m_projection ? " deleting columns " : " with cycle "); + print_container(m_cols, out); + } + virtual void make_annotations(execution_context & ctx) { + std::stringstream s; + std::string a = "rel_src"; + ctx.get_register_annotation(m_src, a); + s << (m_projection ? "project " : "rename ") << a; + ctx.set_register_annotation(m_tgt, s.str()); + } + }; + + instruction * instruction::mk_projection(reg_idx src, unsigned col_cnt, const unsigned * removed_cols, + reg_idx tgt) { + return alloc(instr_project_rename, true, src, col_cnt, removed_cols, tgt); + } + instruction * instruction::mk_rename(reg_idx src, unsigned cycle_len, const unsigned * permutation_cycle, + reg_idx tgt) { + return alloc(instr_project_rename, false, src, cycle_len, permutation_cycle, tgt); + } + + + class instr_join_project : public instruction { + typedef unsigned_vector column_vector; + reg_idx m_rel1; + reg_idx m_rel2; + column_vector m_cols1; + column_vector m_cols2; + column_vector m_removed_cols; + reg_idx m_res; + public: + instr_join_project(reg_idx rel1, reg_idx rel2, unsigned joined_col_cnt, const unsigned * cols1, + const unsigned * cols2, unsigned removed_col_cnt, const unsigned * removed_cols, reg_idx result) + : m_rel1(rel1), m_rel2(rel2), m_cols1(joined_col_cnt, cols1), + m_cols2(joined_col_cnt, cols2), m_removed_cols(removed_col_cnt, removed_cols), m_res(result) {} + virtual bool perform(execution_context & ctx) { + ctx.make_empty(m_res); + if (!ctx.reg(m_rel1) || !ctx.reg(m_rel2)) { + return true; + } + relation_join_fn * fn; + const relation_base & r1 = *ctx.reg(m_rel1); + const relation_base & r2 = *ctx.reg(m_rel2); + if (!find_fn(r1, r2, fn)) { + fn = r1.get_manager().mk_join_project_fn(r1, r2, m_cols1, m_cols2, m_removed_cols); + if (!fn) { + throw default_exception("trying to perform unsupported join-project operation on relations of kinds %s and %s", + r1.get_plugin().get_name().bare_str(), r2.get_plugin().get_name().bare_str()); + } + store_fn(r1, r2, fn); + } + TRACE("dl", tout<\n";); + ctx.set_reg(m_res, (*fn)(r1, r2)); + TRACE("dl", tout<get_size_estimate_rows()<<"\n";); + if (ctx.eager_emptiness_checking() && ctx.reg(m_res)->empty()) { + ctx.make_empty(m_res); + } + return true; + } + virtual void display_head_impl(context & ctx, std::ostream & out) const { + out << "join_project " << m_rel1; + print_container(m_cols1, out); + out << " and " << m_rel2; + print_container(m_cols2, out); + out << " into " << m_res << " removing columns "; + print_container(m_removed_cols, out); + } + virtual void make_annotations(execution_context & ctx) { + std::string s1 = "rel1", s2 = "rel2"; + ctx.get_register_annotation(m_rel1, s1); + ctx.get_register_annotation(m_rel2, s2); + ctx.set_register_annotation(m_res, "join project " + s1 + " " + s2); + } + }; + + instruction * instruction::mk_join_project(reg_idx rel1, reg_idx rel2, unsigned joined_col_cnt, + const unsigned * cols1, const unsigned * cols2, unsigned removed_col_cnt, + const unsigned * removed_cols, reg_idx result) { + return alloc(instr_join_project, rel1, rel2, joined_col_cnt, cols1, cols2, removed_col_cnt, + removed_cols, result); + } + + + class instr_select_equal_and_project : public instruction { + reg_idx m_src; + reg_idx m_result; + app_ref m_value; + unsigned m_col; + public: + instr_select_equal_and_project(ast_manager & m, reg_idx src, const relation_element & value, + unsigned col, reg_idx result) + : m_src(src), m_result(result), m_value(value, m), m_col(col) { + // [Leo]: does not compile on gcc + // TRACE("dl", tout << "src:" << m_src << " result: " << m_result << " value:" << m_value << " column:" << m_col << "\n";); + } + + virtual bool perform(execution_context & ctx) { + if (!ctx.reg(m_src)) { + ctx.make_empty(m_result); + return true; + } + + relation_transformer_fn * fn; + relation_base & r = *ctx.reg(m_src); + if (!find_fn(r, fn)) { + fn = r.get_manager().mk_select_equal_and_project_fn(r, m_value, m_col); + if (!fn) { + throw default_exception( + "trying to perform unsupported select_equal_and_project operation on a relation of kind %s", + r.get_plugin().get_name().bare_str()); + } + store_fn(r, fn); + } + ctx.set_reg(m_result, (*fn)(r)); + + if (ctx.eager_emptiness_checking() && ctx.reg(m_result)->empty()) { + ctx.make_empty(m_result); + } + return true; + } + virtual void display_head_impl(context & ctx, std::ostream & out) const { + out << "select_equal_and_project " << m_src <<" into " << m_result << " col: " << m_col + << " val: " << ctx.get_rmanager().to_nice_string(m_value); + } + virtual void make_annotations(execution_context & ctx) { + std::stringstream s; + std::string s1 = "src"; + ctx.get_register_annotation(m_src, s1); + s << "select equal project col " << m_col << " val: " + << ctx.get_datalog_context().get_rmanager().to_nice_string(m_value) << " " << s1; + ctx.set_register_annotation(m_result, s.str()); + } + }; + + instruction * instruction::mk_select_equal_and_project(ast_manager & m, reg_idx src, + const relation_element & value, unsigned col, reg_idx result) { + return alloc(instr_select_equal_and_project, m, src, value, col, result); + } + + + class instr_filter_by_negation : public instruction { + typedef vector column_vector; + reg_idx m_tgt; + reg_idx m_neg_rel; + column_vector m_cols1; + column_vector m_cols2; + public: + instr_filter_by_negation(reg_idx tgt, reg_idx neg_rel, unsigned col_cnt, const unsigned * cols1, + const unsigned * cols2) + : m_tgt(tgt), m_neg_rel(neg_rel), m_cols1(col_cnt, cols1), m_cols2(col_cnt, cols2) {} + virtual bool perform(execution_context & ctx) { + if (!ctx.reg(m_tgt) || !ctx.reg(m_neg_rel)) { + return true; + } + relation_intersection_filter_fn * fn; + relation_base & r1 = *ctx.reg(m_tgt); + const relation_base & r2 = *ctx.reg(m_neg_rel); + if (!find_fn(r1, r2, fn)) { + fn = r1.get_manager().mk_filter_by_negation_fn(r1, r2, m_cols1.size(), m_cols1.c_ptr(), m_cols2.c_ptr()); + if (!fn) { + std::stringstream sstm; + sstm << "trying to perform unsupported filter_by_negation on relations of kinds "; + sstm << r1.get_plugin().get_name() << " and " << r2.get_plugin().get_name(); + throw default_exception(sstm.str()); + } + store_fn(r1, r2, fn); + } + (*fn)(r1, r2); + + if (ctx.eager_emptiness_checking() && r1.empty()) { + ctx.make_empty(m_tgt); + } + return true; + } + virtual void display_head_impl(context & ctx, std::ostream & out) const { + out << "filter_by_negation on " << m_tgt; + print_container(m_cols1, out); + out << " with " << m_neg_rel; + print_container(m_cols2, out); + out << " as the negated table"; + } + virtual void make_annotations(execution_context & ctx) { + std::string s = "negated relation"; + ctx.get_register_annotation(m_neg_rel, s); + ctx.set_register_annotation(m_tgt, "filter by negation " + s); + } + }; + + instruction * instruction::mk_filter_by_negation(reg_idx tgt, reg_idx neg_rel, unsigned col_cnt, + const unsigned * cols1, const unsigned * cols2) { + return alloc(instr_filter_by_negation, tgt, neg_rel, col_cnt, cols1, cols2); + } + + + class instr_mk_unary_singleton : public instruction { + relation_signature m_sig; + func_decl* m_pred; + reg_idx m_tgt; + relation_fact m_fact; + public: + instr_mk_unary_singleton(ast_manager & m, func_decl* head_pred, const relation_sort & s, const relation_element & val, + reg_idx tgt) : m_pred(head_pred), m_tgt(tgt), m_fact(m) { + m_sig.push_back(s); + m_fact.push_back(val); + } + virtual bool perform(execution_context & ctx) { + ctx.make_empty(m_tgt); + relation_base * rel = ctx.get_datalog_context().get_rmanager().mk_empty_relation(m_sig, m_pred); + rel->add_fact(m_fact); + ctx.set_reg(m_tgt, rel); + return true; + } + virtual void display_head_impl(context & ctx, std::ostream & out) const { + out << "mk_unary_singleton into " << m_tgt << " sort:" + << ctx.get_rmanager().to_nice_string(m_sig[0]) << " val:" + << ctx.get_rmanager().to_nice_string(m_sig[0], m_fact[0]); + } + virtual void make_annotations(execution_context & ctx) { + std::string s; + if (!ctx.get_register_annotation(m_tgt, s)) { + ctx.set_register_annotation(m_tgt, "mk unary singleton"); + } + } + }; + + instruction * instruction::mk_unary_singleton(ast_manager & m, func_decl* head_pred, const relation_sort & s, + const relation_element & val, reg_idx tgt) { + return alloc(instr_mk_unary_singleton, m, head_pred, s, val, tgt); + } + + + class instr_mk_total : public instruction { + relation_signature m_sig; + func_decl* m_pred; + reg_idx m_tgt; + public: + instr_mk_total(const relation_signature & sig, func_decl* p, reg_idx tgt) : m_sig(sig), m_pred(p), m_tgt(tgt) {} + virtual bool perform(execution_context & ctx) { + ctx.make_empty(m_tgt); + ctx.set_reg(m_tgt, ctx.get_datalog_context().get_rmanager().mk_full_relation(m_sig, m_pred)); + return true; + } + virtual void display_head_impl(context & ctx, std::ostream & out) const { + out << "mk_total into " << m_tgt << " sort:" + << ctx.get_rmanager().to_nice_string(m_sig); + } + virtual void make_annotations(execution_context & ctx) { + std::string s; + if (!ctx.get_register_annotation(m_tgt, s)) { + ctx.set_register_annotation(m_tgt, "mk_total"); + } + } + }; + + instruction * instruction::mk_total(const relation_signature & sig, func_decl* pred, reg_idx tgt) { + return alloc(instr_mk_total, sig, pred, tgt); + } + + class instr_mark_saturated : public instruction { + func_decl_ref m_pred; + public: + instr_mark_saturated(ast_manager & m, func_decl * pred) + : m_pred(pred, m) {} + virtual bool perform(execution_context & ctx) { + ctx.get_datalog_context().get_rmanager().mark_saturated(m_pred); + return true; + } + virtual void display_head_impl(context & ctx, std::ostream & out) const { + out << "mark_saturated " << m_pred->get_name().bare_str(); + } + virtual void make_annotations(execution_context & ctx) { + } + }; + + instruction * instruction::mk_mark_saturated(ast_manager & m, func_decl * pred) { + return alloc(instr_mark_saturated, m, pred); + } + + class instr_assert_signature : public instruction { + relation_signature m_sig; + reg_idx m_tgt; + public: + instr_assert_signature(const relation_signature & s, reg_idx tgt) + : m_sig(s), m_tgt(tgt) {} + virtual bool perform(execution_context & ctx) { + if (ctx.reg(m_tgt)) { + SASSERT(ctx.reg(m_tgt)->get_signature()==m_sig); + } + return true; + } + virtual void display_head_impl(context & ctx, std::ostream & out) const { + out << "instr_assert_signature of " << m_tgt << " signature:"; + print_container(m_sig, out); + } + virtual void make_annotations(execution_context & ctx) { + std::string s; + if (!ctx.get_register_annotation(m_tgt, s)) { + ctx.set_register_annotation(m_tgt, "assert signature"); + } + } + }; + + instruction * instruction::mk_assert_signature(const relation_signature & s, reg_idx tgt) { + return alloc(instr_assert_signature, s, tgt); + } + + + // ----------------------------------- + // + // instruction_block + // + // ----------------------------------- + + instruction_block::~instruction_block() { + reset(); + } + + void instruction_block::reset() { + instr_seq_type::iterator it = m_data.begin(); + instr_seq_type::iterator end = m_data.end(); + for(; it!=end; ++it) { + dealloc(*it); + } + m_data.reset(); + m_observer = 0; + } + + bool instruction_block::perform(execution_context & ctx) const { + cost_recorder crec; + instr_seq_type::const_iterator it = m_data.begin(); + instr_seq_type::const_iterator end = m_data.end(); + bool success = true; + for(; it!=end && success; ++it) { + + instruction * instr=(*it); + crec.start(instr); //finish is performed by the next start() or by the destructor of crec + + TRACE("dl", + tout <<"% "; + instr->display_head_impl(ctx.get_datalog_context(), tout); + tout <<"\n";); + success = !ctx.should_terminate() && instr->perform(ctx); + } + return success; + } + + void instruction_block::process_all_costs() { + instr_seq_type::iterator it = m_data.begin(); + instr_seq_type::iterator end = m_data.end(); + for(; it!=end; ++it) { + (*it)->process_all_costs(); + } + } + + void instruction_block::make_annotations(execution_context & ctx) { + instr_seq_type::iterator it = m_data.begin(); + instr_seq_type::iterator end = m_data.end(); + for(; it!=end; ++it) { + (*it)->make_annotations(ctx); + } + } + + void instruction_block::display_indented(context & ctx, std::ostream & out, std::string indentation) const { + instr_seq_type::const_iterator it = m_data.begin(); + instr_seq_type::const_iterator end = m_data.end(); + for(; it!=end; ++it) { + instruction * i = (*it); + if (i->passes_output_thresholds(ctx) || i->being_recorded()) { + i->display_indented(ctx, out, indentation); + } + } + } +} + diff --git a/lib/dl_instruction.h b/lib/dl_instruction.h new file mode 100644 index 000000000..c44c860a0 --- /dev/null +++ b/lib/dl_instruction.h @@ -0,0 +1,342 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_instruction.h + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-09-14. + +Revision History: + +--*/ +#ifndef _DL_INSTRUCTION_H_ +#define _DL_INSTRUCTION_H_ + +#include +#include +#include +#include "ast.h" +#include "vector.h" +#include "dl_base.h" +#include "dl_costs.h" + +namespace datalog { + + class execution_context; + class instruction_block; + + inline void check_overflow(unsigned i) { + if (i == UINT_MAX) { + throw out_of_memory_error(); + } + } + + // ----------------------------------- + // + // execution_context + // + // ----------------------------------- + + class execution_context { + public: + typedef relation_base * reg_type; + typedef vector reg_vector; + typedef unsigned reg_idx; + + /** + \brief A register number that should never be referenced to. Can stand e.g. for a tail + table in a rule with no tail. + */ + static const reg_idx void_register = UINT_MAX; + private: + typedef u_map reg_annotations; + + context & m_datalog_context; + reg_vector m_registers; + + reg_annotations m_reg_annotation; + stopwatch * m_stopwatch; + unsigned m_timelimit_ms; //zero means no limit + /** + \brief If true, after every operation that may result in an empty relation, a check + for emptiness will be performed, and if a relation is empty, it will be deleted + and replaced by zero. This allows us to avoid performing operations that would have + no effect due to relation emptiness, but if the check for emptiness is expensive, its + cost may overcome the gains. + */ + bool m_eager_emptiness_checking; + public: + execution_context(context & datalog_context); + ~execution_context(); + + void reset(); + + context & get_datalog_context() { return m_datalog_context; }; + + void set_timelimit(unsigned time_in_ms); + void reset_timelimit(); + bool should_terminate(); + + bool eager_emptiness_checking() const { return m_eager_emptiness_checking; } + + /** + \brief Return reference to \c i -th register that contains pointer to a relation. + + If register contains zero, it should be treated as if it contains an empty relation. + */ + reg_type reg(reg_idx i) { + if (i>=m_registers.size()) { + check_overflow(i); + m_registers.resize(i+1,0); + } + return m_registers[i]; + } + /** + \brief Return value of the register and assign zero into it place. + */ + reg_type release_reg(reg_idx i) { + SASSERT(i=m_registers.size()) { + check_overflow(i); + m_registers.resize(i+1,0); + } + if(m_registers[i]) { + m_registers[i]->deallocate(); + } + m_registers[i]=val; + } + void make_empty(reg_idx i) { + if(reg(i)) { + set_reg(i, 0); + } + } + + unsigned register_count() const { + return m_registers.size(); + } + bool get_register_annotation(reg_idx reg, std::string & res) const { + return m_reg_annotation.find(reg, res); + } + void set_register_annotation(reg_idx reg, std::string str) { + m_reg_annotation.insert(reg, str); + } + + void report_big_relations(unsigned threshold, std::ostream & out); + }; + + + + // ----------------------------------- + // + // instruction + // + // ----------------------------------- + + + /** + \brief Base class for instructions used in datalog saturation. + + A relation in a register is owned by that register and is not referenced from anywhere else. + + Instructions that move context of one register to another leave the source register empty + and deallocate the previous content of the target register. + */ + class instruction : public accounted_object { + typedef u_map fn_cache; + + fn_cache m_fn_cache; + + + static const int rk_encode_base = 1024; + + inline static unsigned encode_kind(family_id k) + { SASSERT(k + bool find_fn(const relation_base & r, T* & result) const + { return m_fn_cache.find(encode_kind(r.get_kind()), reinterpret_cast(result)); } + + template + bool find_fn(const relation_base & r1, const relation_base & r2, T*& result) const + { return m_fn_cache.find(encode_kinds(r1.get_kind(), r2.get_kind()), reinterpret_cast(result)); } + + template + bool find_fn(const relation_base & r1, const relation_base & r2, const relation_base & r3, T * & result) const + { return m_fn_cache.find(encode_kinds(r1.get_kind(), r2.get_kind(), r3.get_kind()), reinterpret_cast(result)); } + + void store_fn(const relation_base & r, base_relation_fn * fn) + { m_fn_cache.insert(encode_kind(r.get_kind()), fn); } + void store_fn(const relation_base & r1, const relation_base & r2, base_relation_fn * fn) + { m_fn_cache.insert(encode_kinds(r1.get_kind(), r2.get_kind()), fn); } + void store_fn(const relation_base & r1, const relation_base & r2, const relation_base & r3, + base_relation_fn * fn) + { m_fn_cache.insert(encode_kinds(r1.get_kind(), r2.get_kind(), r3.get_kind()), fn); } + + /** + Process not only costs associated with the current instruction, but in case of + block instructions, process also costs associated with its child instructions. + */ + virtual void process_all_costs(); + + /** + \brief Output one line header of the current instruction. + + The newline character at the end should not be printed. + */ + virtual void display_head_impl(context & ctx, std::ostream & out) const { + out << ""; + } + /** + \brief If relevant, output the body of the current instruction. + + Each line must be prepended by \c indentation and ended by a newline character. + */ + virtual void display_body_impl(context & ctx, std::ostream & out, std::string indentation) const {} + public: + typedef execution_context::reg_type reg_type; + typedef execution_context::reg_idx reg_idx; + + virtual ~instruction(); + + virtual bool perform(execution_context & ctx) = 0; + + virtual void make_annotations(execution_context & ctx) = 0; + + void display(context & ctx, std::ostream & out) const { + display_indented(ctx, out, ""); + } + void display_indented(context & ctx, std::ostream & out, std::string indentation) const; + + static instruction * mk_load(ast_manager & m, func_decl * pred, reg_idx tgt); + /** + \brief The store operation moves the relation from a register into the context. The register + is set to zero after the operation. + */ + static instruction * mk_store(ast_manager & m, func_decl * pred, reg_idx src); + static instruction * mk_dealloc(reg_idx reg); //maybe not necessary + static instruction * mk_clone(reg_idx from, reg_idx to); + static instruction * mk_move(reg_idx from, reg_idx to); + + /** + \brief Return instruction that performs \c body as long as at least one register + in \c control_regs contains non-empty relation. + + The instruction object takes over the ownership of the \c body object. + */ + static instruction * mk_while_loop(unsigned control_reg_cnt, const reg_idx * control_regs, + instruction_block * body); + + static instruction * mk_join(reg_idx rel1, reg_idx rel2, unsigned col_cnt, + const unsigned * cols1, const unsigned * cols2, reg_idx result); + static instruction * mk_filter_equal(ast_manager & m, reg_idx reg, const relation_element & value, unsigned col); + static instruction * mk_filter_identical(reg_idx reg, unsigned col_cnt, const unsigned * identical_cols); + static instruction * mk_filter_interpreted(reg_idx reg, app_ref & condition); + static instruction * mk_union(reg_idx src, reg_idx tgt, reg_idx delta); + static instruction * mk_widen(reg_idx src, reg_idx tgt, reg_idx delta); + static instruction * mk_projection(reg_idx src, unsigned col_cnt, const unsigned * removed_cols, + reg_idx tgt); + static instruction * mk_join_project(reg_idx rel1, reg_idx rel2, unsigned joined_col_cnt, + const unsigned * cols1, const unsigned * cols2, unsigned removed_col_cnt, + const unsigned * removed_cols, reg_idx result); + static instruction * mk_rename(reg_idx src, unsigned cycle_len, const unsigned * permutation_cycle, + reg_idx tgt); + static instruction * mk_filter_by_negation(reg_idx tgt, reg_idx neg_rel, unsigned col_cnt, + const unsigned * cols1, const unsigned * cols2); + static instruction * mk_select_equal_and_project(ast_manager & m, reg_idx src, + const relation_element & value, unsigned col, reg_idx result); + + static instruction * mk_unary_singleton(ast_manager & m, func_decl* pred, const relation_sort & s, const relation_element & val, reg_idx tgt); + static instruction * mk_total(const relation_signature & sig, func_decl* pred, reg_idx tgt); + + /** + \brief The mark_saturated instruction marks a relation as saturated, so that after + next restart it does not have to be part of the saturation again. + */ + static instruction * mk_mark_saturated(ast_manager & m, func_decl * pred); + + static instruction * mk_assert_signature(const relation_signature & s, reg_idx tgt); + + }; + + + // ----------------------------------- + // + // instruction_block + // + // ----------------------------------- + + class instruction_block { + public: + struct instruction_observer { + virtual ~instruction_observer() {} + virtual void notify(instruction * i) {} + }; + private: + typedef ptr_vector instr_seq_type; + instr_seq_type m_data; + instruction_observer* m_observer; + public: + instruction_block() : m_observer(0) {} + ~instruction_block(); + void reset(); + + void push_back(instruction * i) { + m_data.push_back(i); + if(m_observer) { + m_observer->notify(i); + } + } + void set_observer(instruction_observer * o) { + SASSERT(o==0 || m_observer==0); + m_observer = o; + } + + /** + \brief Perform instructions in the block. If the run was interrupted before completion, + return false; otherwise return true. + + The execution can terminate before completion if the function + \c execution_context::should_terminate() returns true. + */ + bool perform(execution_context & ctx) const; + + void process_all_costs(); + + void make_annotations(execution_context & ctx); + + void display(context & ctx, std::ostream & out) const { + display_indented(ctx, out, ""); + } + void display_indented(context & ctx, std::ostream & out, std::string indentation) const; + }; + + +}; + +#endif /* _DL_INSTRUCTION_H_ */ + diff --git a/lib/dl_interval_relation.cpp b/lib/dl_interval_relation.cpp new file mode 100644 index 000000000..150ba1c78 --- /dev/null +++ b/lib/dl_interval_relation.cpp @@ -0,0 +1,655 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + dl_interval_relation.cpp + +Abstract: + + Basic interval reatlion. + +Author: + + Nikolaj Bjorner (nbjorner) 2010-2-11 + +Revision History: + +--*/ + +#include "debug.h" +#include "optional.h" +#include "ast_pp.h" +#include "dl_interval_relation.h" + + +namespace datalog { + // ------------------------- + // interval_relation_plugin + + interval_relation_plugin::interval_relation_plugin(relation_manager& m): + relation_plugin(interval_relation_plugin::get_name(), m), + m_empty(m_dep), + m_arith(get_ast_manager()), + m_bsimp(get_ast_manager()) { + } + + bool interval_relation_plugin::can_handle_signature(const relation_signature & sig) { + for (unsigned i = 0; i < sig.size(); ++i) { + if (!m_arith.is_int(sig[i]) && !m_arith.is_real(sig[i])) { + return false; + } + } + return true; + } + + + relation_base * interval_relation_plugin::mk_empty(const relation_signature & s) { + return alloc(interval_relation, *this, s, true); + } + + relation_base * interval_relation_plugin::mk_full(func_decl* p, const relation_signature & s) { + return alloc(interval_relation, *this, s, false); + } + + class interval_relation_plugin::join_fn : public convenient_relation_join_fn { + public: + join_fn(const relation_signature & o1_sig, const relation_signature & o2_sig, unsigned col_cnt, + const unsigned * cols1, const unsigned * cols2) + : convenient_relation_join_fn(o1_sig, o2_sig, col_cnt, cols1, cols2){ + } + + virtual relation_base * operator()(const relation_base & _r1, const relation_base & _r2) { + interval_relation const& r1 = get(_r1); + interval_relation const& r2 = get(_r2); + interval_relation_plugin& p = r1.get_plugin(); + interval_relation* result = dynamic_cast(p.mk_full(0, get_result_signature())); + result->mk_join(r1, r2, m_cols1.size(), m_cols1.c_ptr(), m_cols2.c_ptr()); + return result; + } + }; + + relation_join_fn * interval_relation_plugin::mk_join_fn(const relation_base & r1, const relation_base & r2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2) { + if (!check_kind(r1) || !check_kind(r2)) { + return 0; + } + return alloc(join_fn, r1.get_signature(), r2.get_signature(), col_cnt, cols1, cols2); + } + + + class interval_relation_plugin::project_fn : public convenient_relation_project_fn { + public: + project_fn(const relation_signature & orig_sig, unsigned removed_col_cnt, const unsigned * removed_cols) + : convenient_relation_project_fn(orig_sig, removed_col_cnt, removed_cols) { + } + + virtual relation_base * operator()(const relation_base & _r) { + interval_relation const& r = get(_r); + interval_relation_plugin& p = r.get_plugin(); + interval_relation* result = dynamic_cast(p.mk_full(0, get_result_signature())); + result->mk_project(r, m_removed_cols.size(), m_removed_cols.c_ptr()); + return result; + } + }; + + relation_transformer_fn * interval_relation_plugin::mk_project_fn(const relation_base & r, + unsigned col_cnt, const unsigned * removed_cols) { + return alloc(project_fn, r.get_signature(), col_cnt, removed_cols); + } + + class interval_relation_plugin::rename_fn : public convenient_relation_rename_fn { + interval_relation_plugin& m_plugin; + public: + rename_fn(interval_relation_plugin& p, const relation_signature & orig_sig, unsigned cycle_len, const unsigned * cycle) + : convenient_relation_rename_fn(orig_sig, cycle_len, cycle), + m_plugin(p){ + } + + virtual relation_base * operator()(const relation_base & _r) { + interval_relation const& r = get(_r); + interval_relation_plugin& p = r.get_plugin(); + interval_relation* result = dynamic_cast(p.mk_full(0, get_result_signature())); + result->mk_rename(r, m_cycle.size(), m_cycle.c_ptr()); + return result; + } + }; + + relation_transformer_fn * interval_relation_plugin::mk_rename_fn(const relation_base & r, + unsigned cycle_len, const unsigned * permutation_cycle) { + if(!check_kind(r)) { + return 0; + } + return alloc(rename_fn, *this, r.get_signature(), cycle_len, permutation_cycle); + } + + interval interval_relation_plugin::unite(interval const& src1, interval const& src2) { + bool l_open = src1.is_lower_open(); + bool r_open = src1.is_upper_open(); + ext_numeral low = src1.inf(); + ext_numeral high = src1.sup(); + if (src2.inf() < low || (src2.inf() == low && l_open)) { + low = src2.inf(); + l_open = src2.is_lower_open(); + } + if (src2.sup() > high || (src2.sup() == high && r_open)) { + high = src2.sup(); + r_open = src2.is_upper_open(); + } + return interval(dep(), low, l_open, 0, high, r_open, 0); + } + + interval interval_relation_plugin::widen(interval const& src1, interval const& src2) { + bool l_open = src1.is_lower_open(); + bool r_open = src1.is_upper_open(); + ext_numeral low = src1.inf(); + ext_numeral high = src1.sup(); + + if (src2.inf() < low || (low == src2.inf() && l_open && !src2.is_lower_open())) { + low = ext_numeral(false); + l_open = true; + } + if (high < src2.sup() || (src2.sup() == high && !r_open && src2.is_upper_open())) { + high = ext_numeral(true); + r_open = true; + } + return interval(dep(), low, l_open, 0, high, r_open, 0); + } + + interval interval_relation_plugin::meet(interval const& src1, interval const& src2, bool& isempty) { + isempty = false; + if (is_empty(0, src1) || is_infinite(src2)) { + return src1; + } + if (is_empty(0, src2) || is_infinite(src1)) { + return src2; + } + bool l_open = src1.is_lower_open(); + bool r_open = src1.is_upper_open(); + ext_numeral low = src1.inf(); + ext_numeral high = src1.sup(); + if (src2.inf() > low || (src2.inf() == low && !l_open)) { + low = src2.inf(); + l_open = src2.is_lower_open(); + } + if (src2.sup() < high || (src2.sup() == high && !r_open)) { + high = src2.sup(); + r_open = src2.is_upper_open(); + } + if (low > high || (low == high && (l_open || r_open))) { + isempty = true; + return interval(dep()); + } + else { + return interval(dep(), low, l_open, 0, high, r_open, 0); + } + } + + bool interval_relation_plugin::is_infinite(interval const& i) { + return i.plus_infinity() && i.minus_infinity(); + } + + bool interval_relation_plugin::is_empty(unsigned, interval const& i) { + return i.sup() < i.inf(); + } + + class interval_relation_plugin::union_fn : public relation_union_fn { + interval_relation_plugin& m_plugin; + bool m_is_widen; + public: + union_fn(interval_relation_plugin& p, bool is_widen) : + m_plugin(p), + m_is_widen(is_widen) { + } + + virtual void operator()(relation_base & _r, const relation_base & _src, relation_base * _delta) { + + TRACE("interval_relation", _r.display(tout << "dst:\n"); _src.display(tout << "src:\n");); + + interval_relation& r = get(_r); + interval_relation const& src = get(_src); + if (_delta) { + interval_relation& d = get(*_delta); + r.mk_union(src, &d, m_is_widen); + } + else { + r.mk_union(src, 0, m_is_widen); + } + } + }; + + relation_union_fn * interval_relation_plugin::mk_union_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta) { + if (!check_kind(tgt) || !check_kind(src) || (delta && !check_kind(*delta))) { + return 0; + } + return alloc(union_fn, *this, false); + } + + relation_union_fn * interval_relation_plugin::mk_widen_fn( + const relation_base & tgt, const relation_base & src, + const relation_base * delta) { + if (!check_kind(tgt) || !check_kind(src) || (delta && !check_kind(*delta))) { + return 0; + } + return alloc(union_fn, *this, true); + } + + class interval_relation_plugin::filter_identical_fn : public relation_mutator_fn { + unsigned_vector m_identical_cols; + public: + filter_identical_fn(unsigned col_cnt, const unsigned * identical_cols) + : m_identical_cols(col_cnt, identical_cols) {} + + virtual void operator()(relation_base & r) { + interval_relation & pr = get(r); + for (unsigned i = 1; i < m_identical_cols.size(); ++i) { + unsigned c1 = m_identical_cols[0]; + unsigned c2 = m_identical_cols[i]; + pr.equate(c1, c2); + } + } + }; + + relation_mutator_fn * interval_relation_plugin::mk_filter_identical_fn( + const relation_base & t, unsigned col_cnt, const unsigned * identical_cols) { + if(!check_kind(t)) { + return 0; + } + return alloc(filter_identical_fn, col_cnt, identical_cols); + } + + + class interval_relation_plugin::filter_equal_fn : public relation_mutator_fn { + unsigned m_col; + rational m_value; + public: + filter_equal_fn(relation_manager & m, const relation_element & value, unsigned col) + : m_col(col) { + arith_util arith(m.get_context().get_manager()); + VERIFY(arith.is_numeral(value, m_value)); + } + + virtual void operator()(relation_base & _r) { + interval_relation & r = get(_r); + interval_relation_plugin & p = r.get_plugin(); + r.mk_intersect(m_col, interval(p.dep(), m_value)); + TRACE("interval_relation", tout << m_value << "\n"; r.display(tout);); + } + }; + + relation_mutator_fn * interval_relation_plugin::mk_filter_equal_fn(const relation_base & r, + const relation_element & value, unsigned col) { + if(check_kind(r)) { + return alloc(filter_equal_fn, get_manager(), value, col); + } + return 0; + } + + + class interval_relation_plugin::filter_interpreted_fn : public relation_mutator_fn { + app_ref m_cond; + public: + filter_interpreted_fn(interval_relation const& t, app* cond): + m_cond(cond, t.get_plugin().get_ast_manager()) { + } + + void operator()(relation_base& t) { + get(t).filter_interpreted(m_cond); + TRACE("interval_relation", tout << mk_pp(m_cond, m_cond.get_manager()) << "\n"; t.display(tout);); + } + }; + + relation_mutator_fn * interval_relation_plugin::mk_filter_interpreted_fn(const relation_base & t, app * condition) { + if (check_kind(t)) { + return alloc(filter_interpreted_fn, get(t), condition); + } + return 0; + } + + interval_relation& interval_relation_plugin::get(relation_base& r) { + return dynamic_cast(r); + } + + interval_relation const & interval_relation_plugin::get(relation_base const& r) { + return dynamic_cast(r); + } + + // ----------------------- + // interval_relation + + interval_relation::interval_relation(interval_relation_plugin& p, relation_signature const& s, bool is_empty): + vector_relation(p, s, is_empty, interval(p.dep())) + { + } + + void interval_relation::add_fact(const relation_fact & f) { + interval_relation r(get_plugin(), get_signature(), false); + ast_manager& m = get_plugin().get_ast_manager(); + for (unsigned i = 0; i < f.size(); ++i) { + app_ref eq(m); + expr* e = f[i]; + eq = m.mk_eq(m.mk_var(i, m.get_sort(e)), e); + r.filter_interpreted(eq.get()); + } + mk_union(r, 0, false); + } + + bool interval_relation::contains_fact(const relation_fact & f) const { + SASSERT(f.size() == get_signature().size()); + interval_relation_plugin& p = get_plugin(); + + for (unsigned i = 0; i < f.size(); ++i) { + if (f[i] != f[find(i)]) { + return false; + } + interval const& iv = (*this)[i]; + if (p.is_infinite(iv)) { + continue; + } + rational v; + if (p.m_arith.is_numeral(f[i], v)) { + if (!iv.contains(v)) { + return false; + } + } + else { + // TBD: may or must? + } + } + return true; + } + + interval_relation * interval_relation::clone() const { + interval_relation* result = alloc(interval_relation, get_plugin(), get_signature(), empty()); + result->copy(*this); + return result; + } + + interval_relation * interval_relation::complement(func_decl*) const { + UNREACHABLE(); + return 0; + } + + void interval_relation::to_formula(expr_ref& fml) const { + ast_manager& m = get_plugin().get_ast_manager(); + arith_util& arith = get_plugin().m_arith; + basic_simplifier_plugin& bsimp = get_plugin().m_bsimp; + expr_ref_vector conjs(m); + relation_signature const& sig = get_signature(); + for (unsigned i = 0; i < sig.size(); ++i) { + if (i != find(i)) { + conjs.push_back(m.mk_eq(m.mk_var(i, sig[i]), + m.mk_var(find(i), sig[find(i)]))); + continue; + } + interval const& iv = (*this)[i]; + sort* ty = sig[i]; + expr_ref var(m.mk_var(i, ty), m); + if (!iv.minus_infinity()) { + expr* lo = arith.mk_numeral(iv.get_lower_value(), ty); + if (iv.is_lower_open()) { + conjs.push_back(arith.mk_lt(lo, var)); + } + else { + conjs.push_back(arith.mk_le(lo, var)); + } + } + if (!iv.plus_infinity()) { + expr* hi = arith.mk_numeral(iv.get_upper_value(), ty); + if (iv.is_upper_open()) { + conjs.push_back(arith.mk_lt(var, hi)); + } + else { + conjs.push_back(arith.mk_le(var, hi)); + } + } + } + bsimp.mk_and(conjs.size(), conjs.c_ptr(), fml); + } + + + void interval_relation::display_index(unsigned i, interval const& j, std::ostream & out) const { + out << i << " in " << j << "\n"; + } + + interval_relation_plugin& interval_relation::get_plugin() const { + return static_cast(relation_base::get_plugin()); + } + + void interval_relation::mk_intersect(unsigned idx, interval const& i) { + bool isempty; + (*this)[idx] = mk_intersect((*this)[idx], i, isempty); + if (isempty || is_empty(idx, (*this)[idx])) { + set_empty(); + } + } + + void interval_relation::mk_rename_elem(interval& i, unsigned, unsigned const* ) { + + } + + void interval_relation::filter_interpreted(app* cond) { + interval_relation_plugin& p = get_plugin(); + rational k; + unsigned x, y; + if (p.is_lt(cond, x, k, y)) { + // 0 < x - y + k + if (x == UINT_MAX) { + // y < k + mk_intersect(y, interval(p.dep(), k, true, false, 0)); + return; + } + if (y == UINT_MAX) { + // -k < x + mk_intersect(x, interval(p.dep(), -k, true, true, 0)); + return; + } + // y < x + k + ext_numeral x_hi = (*this)[x].sup(); + ext_numeral y_lo = (*this)[y].inf(); + if (!x_hi.is_infinite()) { + mk_intersect(y, interval(p.dep(), k + x_hi.to_rational(), true, false, 0)); + } + if (!y_lo.is_infinite()) { + mk_intersect(x, interval(p.dep(), y_lo.to_rational() - k, true, true, 0)); + } + return; + } + bool is_int = false; + if (p.is_le(cond, x, k, y, is_int)) { + // 0 <= x - y + k + if (x == UINT_MAX) { + // y <= k + mk_intersect(y, interval(p.dep(), k, false, false, 0)); + return; + } + if (y == UINT_MAX) { + // -k <= x + mk_intersect(x, interval(p.dep(), -k, false, true, 0)); + return; + } + ext_numeral x_hi = (*this)[x].sup(); + ext_numeral y_lo = (*this)[y].inf(); + if (!x_hi.is_infinite()) { + mk_intersect(y, interval(p.dep(), k + x_hi.to_rational(), false, false, 0)); + } + if (!y_lo.is_infinite()) { + mk_intersect(x, interval(p.dep(), y_lo.to_rational() - k, false, true, 0)); + } + return; + } + if (p.is_eq(cond, x, k, y)) { + // y = x + k + if (x == UINT_MAX) { + SASSERT(y != UINT_MAX); + mk_intersect(y, interval(p.dep(), k)); + return; + } + if (y == UINT_MAX) { + // x = - k + SASSERT(x != UINT_MAX); + mk_intersect(x, interval(p.dep(), -k)); + return; + } + interval x_i = (*this)[x]; + interval y_i = (*this)[y]; + x_i += interval(p.dep(), k); + y_i -= interval(p.dep(), k); + mk_intersect(x, y_i); + mk_intersect(y, x_i); + } + if (get_plugin().get_ast_manager().is_false(cond)) { + set_empty(); + } + } + + bool interval_relation_plugin::is_linear(expr* e, unsigned& neg, unsigned& pos, rational& k, bool is_pos) const { +#define SET_VAR(_idx_) \ + if (is_pos &&pos == UINT_MAX) { \ + pos = _idx_; \ + return true; \ + } \ + if (!is_pos && neg == UINT_MAX) { \ + neg = _idx_; \ + return true; \ + } \ + else { \ + return false; \ + } + + if (is_var(e)) { + SET_VAR(to_var(e)->get_idx()); + } + if (!is_app(e)) { + return false; + } + app* a = to_app(e); + + if (m_arith.is_add(e)) { + for (unsigned i = 0; i < a->get_num_args(); ++i) { + if (!is_linear(a->get_arg(i), neg, pos, k, is_pos)) return false; + } + return true; + } + if (m_arith.is_sub(e)) { + SASSERT(a->get_num_args() == 2); + return + is_linear(a->get_arg(0), neg, pos, k, is_pos) && + is_linear(a->get_arg(1), neg, pos, k, !is_pos); + } + rational k1; + SASSERT(!m_arith.is_mul(e) || a->get_num_args() == 2); + if (m_arith.is_mul(e) && + m_arith.is_numeral(a->get_arg(0), k1) && + k1.is_minus_one() && + is_var(a->get_arg(1))) { + SET_VAR(to_var(a->get_arg(1))->get_idx()); + } + + if (m_arith.is_numeral(e, k1)) { + if (is_pos) { + k += k1; + } + else { + k -= k1; + } + return true; + } + return false; + } + + // 0 <= x - y + k + bool interval_relation_plugin::is_le(app* cond, unsigned& x, rational& k, unsigned& y, bool& is_int) const { + ast_manager& m = get_ast_manager(); + k.reset(); + x = UINT_MAX; + y = UINT_MAX; + + if (m_arith.is_le(cond)) { + is_int = m_arith.is_int(cond->get_arg(0)); + if (!is_linear(cond->get_arg(0), y, x, k, false)) return false; + if (!is_linear(cond->get_arg(1), y, x, k, true)) return false; + return (x != UINT_MAX || y != UINT_MAX); + } + if (m_arith.is_ge(cond)) { + is_int = m_arith.is_int(cond->get_arg(0)); + if (!is_linear(cond->get_arg(0), y, x, k, true)) return false; + if (!is_linear(cond->get_arg(1), y, x, k, false)) return false; + return (x != UINT_MAX || y != UINT_MAX); + } + if (m_arith.is_lt(cond) && m_arith.is_int(cond->get_arg(0))) { + is_int = true; + if (!is_linear(cond->get_arg(0), y, x, k, false)) return false; + if (!is_linear(cond->get_arg(1), y, x, k, true)) return false; + k -= rational::one(); + return (x != UINT_MAX || y != UINT_MAX); + } + if (m_arith.is_gt(cond) && m_arith.is_int(cond->get_arg(0))) { + is_int = true; + if (!is_linear(cond->get_arg(0), y, x, k, true)) return false; + if (!is_linear(cond->get_arg(1), y, x, k, false)) return false; + k += rational::one(); + return (x != UINT_MAX || y != UINT_MAX); + } + if (m.is_not(cond) && is_app(cond->get_arg(0))) { + // not (0 <= x - y + k) + // <=> + // 0 > x - y + k + // <=> + // 0 <= y - x - k - 1 + if (is_le(to_app(cond->get_arg(0)), x, k, y, is_int) && is_int) { + k.neg(); + k -= rational::one(); + std::swap(x, y); + return true; + } + // not (0 < x - y + k) + // <=> + // 0 >= x - y + k + // <=> + // 0 <= y - x - k + if (is_lt(to_app(cond->get_arg(0)), x, k, y)) { + is_int = false; + k.neg(); + std::swap(x, y); + return true; + } + } + return false; + } + + // 0 < x - y + k + bool interval_relation_plugin::is_lt(app* cond, unsigned& x, rational& k, unsigned& y) const { + k.reset(); + x = UINT_MAX; + y = UINT_MAX; + if (m_arith.is_lt(cond) && m_arith.is_real(cond->get_arg(0))) { + if (!is_linear(cond->get_arg(0), y, x, k, false)) return false; + if (!is_linear(cond->get_arg(1), y, x, k, true)) return false; + return (x != UINT_MAX || y != UINT_MAX); + } + if (m_arith.is_gt(cond) && m_arith.is_real(cond->get_arg(0))) { + if (!is_linear(cond->get_arg(0), y, x, k, true)) return false; + if (!is_linear(cond->get_arg(1), y, x, k, false)) return false; + return (x != UINT_MAX || y != UINT_MAX); + } + return false; + } + + // 0 = x - y + k + bool interval_relation_plugin::is_eq(app* cond, unsigned& x, rational& k, unsigned& y) const { + ast_manager& m = get_ast_manager(); + k.reset(); + x = UINT_MAX; + y = UINT_MAX; + if (m.is_eq(cond)) { + if (!is_linear(cond->get_arg(0), y, x, k, false)) return false; + if (!is_linear(cond->get_arg(1), y, x, k, true)) return false; + return (x != UINT_MAX || y != UINT_MAX); + } + return false; + } + +}; + diff --git a/lib/dl_interval_relation.h b/lib/dl_interval_relation.h new file mode 100644 index 000000000..0ff05719e --- /dev/null +++ b/lib/dl_interval_relation.h @@ -0,0 +1,140 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + dl_interval_relation.h + +Abstract: + + Basic interval reatlion. + +Author: + + Nikolaj Bjorner (nbjorner) 2010-2-11 + +Revision History: + +--*/ +#ifndef _DL_INTERVAL_RELATION_H_ +#define _DL_INTERVAL_RELATION_H_ + + +#include "dl_context.h" +#include "old_interval.h" +#include "dl_vector_relation.h" +#include "arith_decl_plugin.h" +#include "basic_simplifier_plugin.h" + +namespace datalog { + + class interval_relation; + + class interval_relation_plugin : public relation_plugin { + v_dependency_manager m_dep; + interval m_empty; + arith_util m_arith; + basic_simplifier_plugin m_bsimp; + + class join_fn; + class project_fn; + class rename_fn; + class union_fn; + class filter_equal_fn; + class filter_identical_fn; + class filter_interpreted_fn; + friend class interval_relation; + + interval unite(interval const& src1, interval const& src2); + interval widen(interval const& src1, interval const& src2); + interval meet(interval const& src1, interval const& src2, bool& is_empty); + + v_dependency_manager & dep() const { return const_cast(m_dep); } + + public: + interval_relation_plugin(relation_manager& m); + virtual bool can_handle_signature(const relation_signature & s); + static symbol get_name() { return symbol("interval_relation"); } + virtual relation_base * mk_empty(const relation_signature & s); + virtual relation_base * mk_full(func_decl* p, const relation_signature & s); + virtual relation_join_fn * mk_join_fn(const relation_base & t1, const relation_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2); + virtual relation_transformer_fn * mk_project_fn(const relation_base & t, unsigned col_cnt, + const unsigned * removed_cols); + virtual relation_transformer_fn * mk_rename_fn(const relation_base & t, unsigned permutation_cycle_len, + const unsigned * permutation_cycle); + virtual relation_union_fn * mk_union_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta); + virtual relation_union_fn * mk_widen_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta); + virtual relation_mutator_fn * mk_filter_identical_fn(const relation_base & t, unsigned col_cnt, + const unsigned * identical_cols); + virtual relation_mutator_fn * mk_filter_equal_fn(const relation_base & t, const relation_element & value, + unsigned col); + virtual relation_mutator_fn * mk_filter_interpreted_fn(const relation_base & t, app * condition); + + static bool is_empty(unsigned idx, interval const& i); + static bool is_infinite(interval const& i); + + private: + static interval_relation& get(relation_base& r); + static interval_relation const & get(relation_base const& r); + + bool is_linear(expr* e, unsigned& pos, unsigned& neg, rational& k, bool is_pos) const; + + // x + k <= y + bool is_le(app* cond, unsigned& x, rational& k, unsigned& y, bool& is_int) const; + // x + k < y + bool is_lt(app* cond, unsigned& x, rational& k, unsigned& y) const; + // x + k = y + bool is_eq(app* cond, unsigned& x, rational& k, unsigned& y) const; + }; + + + class interval_relation : public vector_relation { + friend class interval_relation_plugin; + friend class interval_relation_plugin::filter_equal_fn; + public: + interval_relation(interval_relation_plugin& p, relation_signature const& s, bool is_empty); + + virtual void add_fact(const relation_fact & f); + virtual bool contains_fact(const relation_fact & f) const; + virtual interval_relation * clone() const; + virtual interval_relation * complement(func_decl*) const; + virtual void to_formula(expr_ref& fml) const; + interval_relation_plugin& get_plugin() const; + + void filter_interpreted(app* cond); + + private: + + virtual interval mk_intersect(interval const& t1, interval const& t2, bool& is_empty) const { + return get_plugin().meet(t1, t2, is_empty); + } + + virtual interval mk_unite(interval const& t1, interval const& t2) const { return get_plugin().unite(t1,t2); } + + virtual interval mk_widen(interval const& t1, interval const& t2) const { return get_plugin().widen(t1,t2); } + + virtual bool is_subset_of(interval const& t1, interval const& t2) const { NOT_IMPLEMENTED_YET(); return false; } + + virtual bool is_full(interval const& t) const { + return interval_relation_plugin::is_infinite(t); + } + + virtual bool is_empty(unsigned idx, interval const& t) const { + return interval_relation_plugin::is_empty(idx, t); + } + + virtual void mk_rename_elem(interval& i, unsigned col_cnt, unsigned const* cycle); + + virtual void display_index(unsigned idx, interval const & i, std::ostream& out) const; + + void mk_intersect(unsigned idx, interval const& i); + + }; + +}; + +#endif + diff --git a/lib/dl_mk_bit_blast.cpp b/lib/dl_mk_bit_blast.cpp new file mode 100644 index 000000000..82df6dc40 --- /dev/null +++ b/lib/dl_mk_bit_blast.cpp @@ -0,0 +1,217 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + dl_mk_bit_blast.cpp + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2012-08-30 + +Revision History: + +--*/ + +#include "dl_mk_bit_blast.h" +#include "bit_blaster_rewriter.h" +#include "rewriter_def.h" +#include "ast_pp.h" + + +namespace datalog { + + // + // P(v) :- Q(extract[1:1]v ++ 0), R(1 ++ extract[0:0]v). + // -> + // P(bv(x,y)) :- Q(bv(x,0)), R(bv(1,y)) . + // + // Introduce P_bv: + // P_bv(x,y) :- Q_bv(x,0), R_bv(1,y) + // P(bv(x,y)) :- P_bv(x,y) + // Query + + + class expand_mkbv_cfg : public default_rewriter_cfg { + + context& m_context; + rule_ref_vector& m_rules; + ast_manager& m; + bv_util m_util; + expr_ref_vector m_args, m_f_vars, m_g_vars; + func_decl_ref_vector m_pinned; + obj_map m_pred2blast; + + + public: + + expand_mkbv_cfg(context& ctx, rule_ref_vector& rules): + m_context(ctx), + m_rules(rules), + m(ctx.get_manager()), + m_util(m), + m_args(m), + m_f_vars(m), + m_g_vars(m), + m_pinned(m) + {} + + ~expand_mkbv_cfg() {} + + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + rule_manager& rm = m_context.get_rule_manager(); + bool found = false; + for (unsigned j = 0; !found && j < num; ++j) { + found = m_util.is_mkbv(args[j]); + } + if (!found) { + return BR_FAILED; + } + // + // f(mk_bv(args),...) + // + m_args.reset(); + m_g_vars.reset(); + m_f_vars.reset(); + expr_ref fml(m); + unsigned idx = 0; + for (unsigned j = 0; j < num; ++j) { + expr* arg = args[j]; + if (m_util.is_mkbv(arg)) { + app* a = to_app(arg); + unsigned sz = a->get_num_args(); + for (unsigned i = 0; i < sz; ++i) { + m_args.push_back(a->get_arg(i)); + m_g_vars.push_back(m.mk_var(idx++,m.mk_bool_sort())); + } + m_f_vars.push_back(m_util.mk_bv(sz, m_g_vars.c_ptr()+m_g_vars.size()-sz)); + } + else { + m_args.push_back(arg); + m_f_vars.push_back(m.mk_var(idx++, m.get_sort(arg))); + m_g_vars.push_back(m_f_vars.back()); + } + } + func_decl* g = 0; + + if (!m_pred2blast.find(f, g)) { + + ptr_vector domain; + for (unsigned i = 0; i < m_args.size(); ++i) { + domain.push_back(m.get_sort(m_args[i].get())); + } + g = m_context.mk_fresh_head_predicate(f->get_name(), symbol("bv"), m_args.size(), domain.c_ptr(), f); + m_pinned.push_back(g); + m_pred2blast.insert(f, g); + + // Create rule f(mk_mkbv(args)) :- g(args) + + fml = m.mk_implies(m.mk_app(g, m_g_vars.size(), m_g_vars.c_ptr()), m.mk_app(f, m_f_vars.size(), m_f_vars.c_ptr())); + rm.mk_rule(fml, m_rules, g->get_name()); + } + result = m.mk_app(g, m_args.size(), m_args.c_ptr()); + result_pr = 0; + return BR_DONE; + } + }; + + struct expand_mkbv : public rewriter_tpl { + expand_mkbv_cfg m_cfg; + expand_mkbv(ast_manager& m, context& ctx, rule_ref_vector& rules): + rewriter_tpl(m, m.proofs_enabled(), m_cfg), + m_cfg(ctx, rules) { + } + }; + + + class mk_bit_blast::impl { + + context & m_context; + ast_manager & m; + params_ref m_params; + rule_ref_vector m_rules; + bit_blaster_rewriter m_blaster; + expand_mkbv m_rewriter; + + + bool blast(expr_ref& fml) { + proof_ref pr(m); + expr_ref fml1(m), fml2(m); + m_blaster(fml, fml1, pr); + m_rewriter(fml1, fml2); + TRACE("dl", tout << mk_pp(fml, m) << " -> " << mk_pp(fml1, m) << " -> " << mk_pp(fml2, m) << "\n";); + if (fml2 != fml) { + fml = fml2; + return true; + } + else { + return false; + } + } + + void reset() { + m_rules.reset(); + } + + public: + impl(context& ctx): + m_context(ctx), + m(ctx.get_manager()), + m_rules(ctx.get_rule_manager()), + m_params(ctx.get_params()), + m_blaster(ctx.get_manager(), m_params), + m_rewriter(ctx.get_manager(), ctx, m_rules) { + m_params.set_bool(":blast-full", true); + m_params.set_bool(":blast-quant", true); + m_blaster.updt_params(m_params); + } + + rule_set * operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc) { + // TODO mc, pc + if (!m_context.get_params().get_bool(":bit-blast", false)) { + return 0; + } + if (m_context.get_engine() != PDR_ENGINE) { + return 0; + } + rule_manager& rm = m_context.get_rule_manager(); + unsigned sz = source.get_num_rules(); + expr_ref fml(m); + reset(); + rule_set * result = alloc(rule_set, m_context); + for (unsigned i = 0; i < sz; ++i) { + rule * r = source.get_rule(i); + r->to_formula(fml); + if (blast(fml)) { + rm.mk_rule(fml, m_rules, r->name()); + } + else { + m_rules.push_back(r); + } + } + + for (unsigned i = 0; i < m_rules.size(); ++i) { + result->add_rule(m_rules.get(i)); + } + + return result; + } + }; + + mk_bit_blast::mk_bit_blast(context & ctx, unsigned priority) : plugin(priority) { + m_impl = alloc(impl, ctx); + } + + mk_bit_blast::~mk_bit_blast() { + dealloc(m_impl); + } + + rule_set * mk_bit_blast::operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc) { + return (*m_impl)(source, mc, pc); + } + +}; diff --git a/lib/dl_mk_bit_blast.h b/lib/dl_mk_bit_blast.h new file mode 100644 index 000000000..e16c2058b --- /dev/null +++ b/lib/dl_mk_bit_blast.h @@ -0,0 +1,53 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + dl_mk_bit_blast.h + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2012-08-30 + +Revision History: + +--*/ +#ifndef _DL_MK_BIT_BLAST_H_ +#define _DL_MK_BIT_BLAST_H_ + +#include + +#include"map.h" +#include"obj_pair_hashtable.h" +#include"dl_context.h" +#include"dl_rule_set.h" +#include"dl_rule_transformer.h" + +namespace datalog { + + /** + \brief Functor for bit-blasting a rule set. + */ + + class mk_bit_blast : public rule_transformer::plugin { + class impl; + + impl* m_impl; + void blast(expr_ref& b); + void reset(); + + public: + mk_bit_blast(context & ctx, unsigned priority = 35000); + ~mk_bit_blast(); + + rule_set * operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc); + }; + +}; + +#endif /* _DL_MK_BIT_BLAST_H_ */ + diff --git a/lib/dl_mk_coi_filter.cpp b/lib/dl_mk_coi_filter.cpp new file mode 100644 index 000000000..bd05e5cf7 --- /dev/null +++ b/lib/dl_mk_coi_filter.cpp @@ -0,0 +1,107 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_coi_filter.cpp + +Abstract: + + Rule transformer which removes relations which are out of the cone of + influence of output relations + +Author: + + Krystof Hoder (t-khoder) 2011-10-01. + +Revision History: + +--*/ + + +#include +#include"ast_pp.h" +#include"dl_mk_coi_filter.h" +#include"elim_var_model_converter.h" + +namespace datalog { + + // ----------------------------------- + // + // mk_coi_filter + // + // ----------------------------------- + + + rule_set * mk_coi_filter::operator()( + rule_set const & source, + model_converter_ref& mc, + proof_converter_ref& pc) + { + if (source.get_num_rules()==0) { + return 0; + } + + decl_set interesting_preds; + decl_set pruned_preds; + ptr_vector todo; + { + const decl_set& output_preds = m_context.get_output_predicates(); + decl_set::iterator oend = output_preds.end(); + for (decl_set::iterator it = output_preds.begin(); it!=oend; ++it) { + todo.push_back(*it); + interesting_preds.insert(*it); + } + } + + const rule_dependencies& deps = source.get_dependencies(); + + while (!todo.empty()) { + func_decl * curr = todo.back(); + todo.pop_back(); + interesting_preds.insert(curr); + + const rule_dependencies::item_set& cdeps = deps.get_deps(curr); + rule_dependencies::item_set::iterator dend = cdeps.end(); + for (rule_dependencies::item_set::iterator it = cdeps.begin(); it!=dend; ++it) { + func_decl * dep_pred = *it; + if (!interesting_preds.contains(dep_pred)) { + interesting_preds.insert(dep_pred); + todo.push_back(dep_pred); + } + } + } + + scoped_ptr res = alloc(rule_set, m_context); + + rule_set::iterator rend = source.end(); + for (rule_set::iterator rit = source.begin(); rit!=rend; ++rit) { + rule * r = *rit; + func_decl * pred = r->get_decl(); + if (interesting_preds.contains(pred)) { + res->add_rule(r); + } + else if (mc.get()) { + pruned_preds.insert(pred); + } + } + + if (res->get_num_rules() == source.get_num_rules()) { + res = 0; + } + + if (res && mc) { + decl_set::iterator end = pruned_preds.end(); + decl_set::iterator it = pruned_preds.begin(); + elim_var_model_converter* mc0 = alloc(elim_var_model_converter, m); + for (; it != end; ++it) { + mc0->insert(*it, m.mk_true()); + } + mc = concat(mc.get(), mc0); + } + + return res.detach(); + } + +}; + diff --git a/lib/dl_mk_coi_filter.h b/lib/dl_mk_coi_filter.h new file mode 100644 index 000000000..b8fb37964 --- /dev/null +++ b/lib/dl_mk_coi_filter.h @@ -0,0 +1,50 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_coi_filter.h + +Abstract: + + Rule transformer which removes relations which are out of the cone of + influence of output relations + +Author: + + Krystof Hoder (t-khoder) 2011-10-01. + +Revision History: + +--*/ + +#ifndef _DL_MK_COI_FILTER_H_ +#define _DL_MK_COI_FILTER_H_ + +#include "dl_context.h" +#include "dl_rule_transformer.h" + +namespace datalog { + + class mk_coi_filter : public rule_transformer::plugin { + + typedef obj_map decl_map; + + ast_manager & m; + context & m_context; + public: + mk_coi_filter(context & ctx, unsigned priority=45000) + : plugin(priority), + m(ctx.get_manager()), + m_context(ctx) {} + + + rule_set * operator()(rule_set const & source, + model_converter_ref& mc, + proof_converter_ref& pc); + }; + +}; + +#endif /* _DL_MK_COI_FILTER_H_ */ + diff --git a/lib/dl_mk_explanations.cpp b/lib/dl_mk_explanations.cpp new file mode 100644 index 000000000..425d3fb60 --- /dev/null +++ b/lib/dl_mk_explanations.cpp @@ -0,0 +1,893 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_explanations.cpp + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-11-08. + +Revision History: + +--*/ + + +#include +#include"ast_pp.h" +#include "ast_smt_pp.h" +#include"dl_finite_product_relation.h" +#include"dl_product_relation.h" +#include"dl_sieve_relation.h" + +#include"dl_mk_explanations.h" + +namespace datalog { + + // ----------------------------------- + // + // explanation_relation_plugin declaration + // + // ----------------------------------- + + class explanation_relation; + + class explanation_relation_plugin : public relation_plugin { + friend class explanation_relation; + + class join_fn; + class project_fn; + class rename_fn; + class union_fn; + class foreign_union_fn; + class assignment_filter_fn; + class negation_filter_fn; + class intersection_filter_fn; + + bool m_relation_level_explanations; + + func_decl_ref m_union_decl; + + vector > m_pool; + + + app * mk_union(app * a1, app * a2) { + return get_ast_manager().mk_app(m_union_decl, a1, a2); + } + + public: + static symbol get_name(bool relation_level) { + return symbol(relation_level ? "relation_explanation" : "fact_explanation"); + } + + explanation_relation_plugin(bool relation_level, relation_manager & manager) + : relation_plugin(get_name(relation_level), manager), + m_relation_level_explanations(relation_level), + m_union_decl(mk_explanations::get_union_decl(get_context()), get_ast_manager()) {} + + ~explanation_relation_plugin() { + for (unsigned i = 0; i < m_pool.size(); ++i) { + for (unsigned j = 0; j < m_pool[i].size(); ++j) { + dealloc(m_pool[i][j]); + } + } + } + + virtual bool can_handle_signature(const relation_signature & s) { + unsigned n=s.size(); + for(unsigned i=0; i(relation_base::get_plugin()); + } + + virtual void to_formula(expr_ref& fml) const { + ast_manager& m = fml.get_manager(); + fml = m.mk_eq(m.mk_var(0, m.get_sort(m_data[0])), m_data[0]); + } + + bool is_undefined(unsigned col_idx) const { + return m_data[col_idx]==0; + } + bool no_undefined() const { + if(empty()) { + return true; + } + unsigned n = get_signature().size(); + for(unsigned i=0; i(get_plugin().mk_empty(get_signature())); + res->m_empty = m_empty; + SASSERT(res->m_data.empty()); + res->m_data.append(m_data); + return res; + } + + virtual relation_base * complement(func_decl* pred) const { + explanation_relation * res = static_cast(get_plugin().mk_empty(get_signature())); + if(empty()) { + res->set_undefined(); + } + return res; + } + + void display_explanation(app * expl, std::ostream & out) const { + if(expl) { + //TODO: some nice explanation output + ast_smt_pp pp(get_plugin().get_ast_manager()); + pp.display_expr_smt2(out, expl); + } + else { + out << ""; + } + } + + virtual void display(std::ostream & out) const { + if(empty()) { + out << "\n"; + return; + } + unsigned sz = get_signature().size(); + for(unsigned i=0; i s.size() && !m_pool[s.size()].empty()) { + explanation_relation* r = m_pool[s.size()].back(); + m_pool[s.size()].pop_back(); + r->m_empty = true; + r->m_data.reset(); + return r; + } + return alloc(explanation_relation, *this, s); + } + + void explanation_relation_plugin::recycle(explanation_relation* r) { + relation_signature const& sig = r->get_signature(); + if (m_pool.size() <= sig.size()) { + m_pool.resize(sig.size()+1); + } + m_pool[sig.size()].push_back(r); + } + + + class explanation_relation_plugin::join_fn : public convenient_relation_join_fn { + public: + join_fn(const relation_signature & sig1, const relation_signature & sig2) + : convenient_relation_join_fn(sig1, sig2, 0, 0, 0) {} + + virtual relation_base * operator()(const relation_base & r1_0, const relation_base & r2_0) { + const explanation_relation & r1 = static_cast(r1_0); + const explanation_relation & r2 = static_cast(r2_0); + explanation_relation_plugin & plugin = r1.get_plugin(); + + explanation_relation * res = static_cast(plugin.mk_empty(get_result_signature())); + if(!r1.empty() && !r2.empty()) { + res->m_empty = false; + SASSERT(res->m_data.empty()); + res->m_data.append(r1.m_data); + res->m_data.append(r2.m_data); + } + return res; + } + }; + + relation_join_fn * explanation_relation_plugin::mk_join_fn(const relation_base & r1, const relation_base & r2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2) { + if(&r1.get_plugin()!=this || &r2.get_plugin()!=this) { + return 0; + } + if(col_cnt!=0) { + return 0; + } + return alloc(join_fn, r1.get_signature(), r2.get_signature()); + } + + + class explanation_relation_plugin::project_fn : public convenient_relation_project_fn { + public: + project_fn(const relation_signature & sig, unsigned col_cnt, const unsigned * removed_cols) + : convenient_relation_project_fn(sig, col_cnt, removed_cols) {} + + virtual relation_base * operator()(const relation_base & r0) { + const explanation_relation & r = static_cast(r0); + explanation_relation_plugin & plugin = r.get_plugin(); + + explanation_relation * res = static_cast(plugin.mk_empty(get_result_signature())); + if(!r.empty()) { + relation_fact proj_data = r.m_data; + project_out_vector_columns(proj_data, m_removed_cols); + res->assign_data(proj_data); + } + return res; + } + }; + + relation_transformer_fn * explanation_relation_plugin::mk_project_fn(const relation_base & r, unsigned col_cnt, + const unsigned * removed_cols) { + if(&r.get_plugin()!=this) { + return 0; + } + return alloc(project_fn, r.get_signature(), col_cnt, removed_cols); + } + + + class explanation_relation_plugin::rename_fn : public convenient_relation_rename_fn { + public: + rename_fn(const relation_signature & sig, unsigned permutation_cycle_len, const unsigned * permutation_cycle) + : convenient_relation_rename_fn(sig, permutation_cycle_len, permutation_cycle) {} + + virtual relation_base * operator()(const relation_base & r0) { + const explanation_relation & r = static_cast(r0); + explanation_relation_plugin & plugin = r.get_plugin(); + + explanation_relation * res = static_cast(plugin.mk_empty(get_result_signature())); + if(!r.empty()) { + relation_fact permutated_data = r.m_data; + permutate_by_cycle(permutated_data, m_cycle); + res->assign_data(permutated_data); + } + return res; + } + }; + + relation_transformer_fn * explanation_relation_plugin::mk_rename_fn(const relation_base & r, + unsigned permutation_cycle_len, const unsigned * permutation_cycle) { + return alloc(rename_fn, r.get_signature(), permutation_cycle_len, permutation_cycle); + } + + + class explanation_relation_plugin::union_fn : public relation_union_fn { + scoped_ptr m_delta_union_fun; + public: + virtual void operator()(relation_base & tgt0, const relation_base & src0, relation_base * delta0) { + explanation_relation & tgt = static_cast(tgt0); + const explanation_relation & src = static_cast(src0); + explanation_relation * delta = delta0 ? static_cast(delta0) : 0; + explanation_relation_plugin & plugin = tgt.get_plugin(); + + if(!src.no_undefined() || !tgt.no_undefined() || (delta && !delta->no_undefined())) { + UNREACHABLE(); + } + if(src.empty()) { + return; + } + if(plugin.m_relation_level_explanations) { + tgt.unite_with_data(src.m_data); + if(delta) { + if(!m_delta_union_fun) { + m_delta_union_fun = plugin.get_manager().mk_union_fn(*delta, src); + SASSERT(m_delta_union_fun); + } + (*m_delta_union_fun)(*delta, src); + } + } + else { + if(tgt.empty()) { + tgt.assign_data(src.m_data); + if(delta && delta->empty()) { + delta->assign_data(src.m_data); + } + } + } + } + }; + + class explanation_relation_plugin::foreign_union_fn : public relation_union_fn { + scoped_ptr m_delta_union_fun; + public: + virtual void operator()(relation_base & tgt0, const relation_base & src, relation_base * delta0) { + explanation_relation & tgt = static_cast(tgt0); + explanation_relation * delta = delta0 ? static_cast(delta0) : 0; + + if(src.empty()) { + return; + } + tgt.set_undefined(); + if(delta) { + delta->set_undefined(); + } + } + }; + + relation_union_fn * explanation_relation_plugin::mk_union_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta) { + if(!check_kind(tgt) || (delta && !check_kind(*delta))) { + return 0; + } + if(!check_kind(src)) { + //this is to handle the product relation + return alloc(foreign_union_fn); + } + return alloc(union_fn); + } + + class explanation_relation_plugin::assignment_filter_fn : public relation_mutator_fn { + ast_manager & m_manager; + var_subst & m_subst; + unsigned m_col_idx; + app_ref m_new_rule; + public: + assignment_filter_fn(context & ctx, unsigned col_idx, app_ref new_rule) + : m_manager(ctx.get_manager()), + m_subst(ctx.get_var_subst()), + m_col_idx(col_idx), + m_new_rule(new_rule) {} + + virtual void operator()(relation_base & r0) { + explanation_relation & r = static_cast(r0); + + if(!r.is_undefined(m_col_idx)) { + UNREACHABLE(); + } + + unsigned sz = r.get_signature().size(); + ptr_vector subst_arg; + subst_arg.resize(sz, 0); + unsigned ofs = sz-1; + for(unsigned i=0; iget_arg(0); + expr * arg2 = cond->get_arg(1); + + if(is_var(arg2)) { + std::swap(arg1, arg2); + } + + if(!is_var(arg1) || !is_app(arg2)) { + return 0; + } + var * col_var = to_var(arg1); + app * new_rule = to_app(arg2); + if(!get_context().get_decl_util().is_rule_sort(col_var->get_sort())) { + return 0; + } + unsigned col_idx = col_var->get_idx(); + + return alloc(assignment_filter_fn, get_context(), col_idx, app_ref(new_rule, get_ast_manager())); + } + + + class explanation_relation_plugin::negation_filter_fn : public relation_intersection_filter_fn { + public: + virtual void operator()(relation_base & r, const relation_base & neg) { + if(!neg.empty()) { + r.reset(); + } + } + }; + + relation_intersection_filter_fn * explanation_relation_plugin::mk_filter_by_negation_fn(const relation_base & r, + const relation_base & neg, unsigned joined_col_cnt, const unsigned * t_cols, + const unsigned * negated_cols) { + if(&r.get_plugin()!=this || &neg.get_plugin()!=this) { + return 0; + } + return alloc(negation_filter_fn); + } + + class explanation_relation_plugin::intersection_filter_fn : public relation_intersection_filter_fn { + explanation_relation_plugin & m_plugin; + func_decl_ref m_union_decl; + public: + intersection_filter_fn(explanation_relation_plugin & plugin) + : m_plugin(plugin), m_union_decl(plugin.m_union_decl) {} + + virtual void operator()(relation_base & tgt0, const relation_base & src0) { + explanation_relation & tgt = static_cast(tgt0); + const explanation_relation & src = static_cast(src0); + + if(src.empty()) { + tgt.reset(); + return; + } + if(tgt.empty()) { + return; + } + unsigned sz = tgt.get_signature().size(); + for(unsigned i=0; iget_decl()==m_union_decl.get()) { + if(curr_tgt->get_arg(0)==curr_src || curr_tgt->get_arg(1)==curr_src) { + tgt.m_data.set(i, curr_src); + continue; + } + } + //the intersection is imprecise because we do nothing here, but it is good enough for + //the purpose of explanations + } + } + }; + + relation_intersection_filter_fn * explanation_relation_plugin::mk_filter_by_intersection_fn( + const relation_base & tgt, const relation_base & src, unsigned joined_col_cnt, + const unsigned * tgt_cols, const unsigned * src_cols) { + if(&tgt.get_plugin()!=this || &src.get_plugin()!=this) { + return 0; + } + //this checks the join is one to one on all columns + if(tgt.get_signature()!=src.get_signature() + || joined_col_cnt!=tgt.get_signature().size() + || !containers_equal(tgt_cols, tgt_cols+joined_col_cnt, src_cols, src_cols+joined_col_cnt)) { + return 0; + } + counter ctr; + ctr.count(joined_col_cnt, tgt_cols); + if(ctr.get_max_counter_value()>1 || (joined_col_cnt && ctr.get_max_positive()!=joined_col_cnt-1)) { + return 0; + } + return alloc(intersection_filter_fn, *this); + } + + + // ----------------------------------- + // + // mk_explanations + // + // ----------------------------------- + + + mk_explanations::mk_explanations(context & ctx, bool relation_level) + : plugin(50000), + m_manager(ctx.get_manager()), + m_context(ctx), + m_decl_util(ctx.get_decl_util()), + m_relation_level(relation_level), + m_pinned(m_manager) { + m_e_sort = m_decl_util.mk_rule_sort(); + m_pinned.push_back(m_e_sort); + + relation_manager & rmgr = ctx.get_rmanager(); + symbol er_symbol = explanation_relation_plugin::get_name(relation_level); + m_er_plugin = static_cast(rmgr.get_relation_plugin(er_symbol)); + if(!m_er_plugin) { + m_er_plugin = alloc(explanation_relation_plugin, relation_level, rmgr); + rmgr.register_plugin(m_er_plugin); + if(!m_relation_level) { + DEBUG_CODE( + finite_product_relation_plugin * dummy; + SASSERT(!rmgr.try_get_finite_product_relation_plugin(*m_er_plugin, dummy)); + ); + rmgr.register_plugin(alloc(finite_product_relation_plugin, *m_er_plugin, rmgr)); + } + } + DEBUG_CODE( + if(!m_relation_level) { + finite_product_relation_plugin * dummy; + SASSERT(rmgr.try_get_finite_product_relation_plugin(*m_er_plugin, dummy)); + } + ); + } + + func_decl * mk_explanations::get_union_decl(context & ctx) { + ast_manager & m = ctx.get_manager(); + sort_ref s(ctx.get_decl_util().mk_rule_sort(), m); + //can it happen that the function name would collide with some other symbol? + //if functions can be overloaded by their ranges, it should be fine. + return m.mk_func_decl(symbol("e_union"), s, s, s); + } + + void mk_explanations::assign_rel_level_kind(func_decl * e_decl, func_decl * orig) { + SASSERT(m_relation_level); + + relation_manager & rmgr = m_context.get_rmanager(); + unsigned sz = e_decl->get_arity(); + relation_signature sig; + rmgr.from_predicate(e_decl, sig); + + svector inner_sieve(sz-1, true); + inner_sieve.push_back(false); + + svector expl_sieve(sz-1, false); + expl_sieve.push_back(true); + + sieve_relation_plugin & sieve_plugin = sieve_relation_plugin::get_plugin(rmgr); + + family_id inner_kind = rmgr.get_requested_predicate_kind(orig); //may be null_family_id + family_id inner_sieve_kind = sieve_plugin.get_relation_kind(sig, inner_sieve, inner_kind); + family_id expl_kind = m_er_plugin->get_kind(); + family_id expl_sieve_kind = sieve_plugin.get_relation_kind(sig, expl_sieve, expl_kind); + + product_relation_plugin::rel_spec product_spec; + product_spec.push_back(inner_sieve_kind); + product_spec.push_back(expl_sieve_kind); + + family_id pred_kind = + product_relation_plugin::get_plugin(rmgr).get_relation_kind(sig, product_spec); + + rmgr.set_predicate_kind(e_decl, pred_kind); + } + + func_decl * mk_explanations::get_e_decl(func_decl * orig_decl) { + decl_map::obj_map_entry * e = m_e_decl_map.insert_if_not_there2(orig_decl, 0); + if(e->get_data().m_value==0) { + relation_signature e_domain; + e_domain.append(orig_decl->get_arity(), orig_decl->get_domain()); + e_domain.push_back(m_e_sort); + func_decl * new_decl = m_context.mk_fresh_head_predicate(orig_decl->get_name(), symbol("expl"), + e_domain.size(), e_domain.c_ptr(), orig_decl); + m_pinned.push_back(new_decl); + e->get_data().m_value = new_decl; + + if(m_relation_level) { + assign_rel_level_kind(new_decl, orig_decl); + } + } + return e->get_data().m_value; + } + + app * mk_explanations::get_e_lit(app * lit, unsigned e_var_idx) { + expr_ref_vector args(m_manager); + func_decl * e_decl = get_e_decl(lit->get_decl()); + args.append(lit->get_num_args(), lit->get_args()); + args.push_back(m_manager.mk_var(e_var_idx, m_e_sort)); + return m_manager.mk_app(e_decl, args.c_ptr()); + } + + symbol mk_explanations::get_rule_symbol(rule * r) { + if (r->name() == symbol::null) { + std::stringstream sstm; + r->display(m_context, sstm); + std::string res = sstm.str(); + res = res.substr(0, res.find_last_not_of('\n')+1); + return symbol(res.c_str()); + } + else { + return r->name(); + } + } + + rule * mk_explanations::get_e_rule(rule * r) { + var_counter ctr; + ctr.count_vars(m_manager, r); + unsigned max_var; + unsigned next_var = ctr.get_max_positive(max_var) ? (max_var+1) : 0; + unsigned head_var = next_var++; + app_ref e_head(get_e_lit(r->get_head(), head_var), m_manager); + + app_ref_vector e_tail(m_manager); + svector neg_flags; + unsigned pos_tail_sz = r->get_positive_tail_size(); + for(unsigned i=0; iget_tail(i), e_var)); + neg_flags.push_back(false); + } + unsigned tail_sz = r->get_tail_size(); + for(unsigned i=pos_tail_sz; iget_tail(i)); + neg_flags.push_back(r->is_neg_tail(i)); + } + + symbol rule_repr = get_rule_symbol(r); + + expr_ref_vector rule_expr_args(m_manager); + for(unsigned tail_idx=0; tail_idxget_arg(tail->get_num_args()-1)); + } + else { + //this adds argument values and the explanation term + //(values will be substituted for variables at runtime by the finite_product_relation) + rule_expr_args.append(tail->get_num_args(), tail->get_args()); + } + } + //rule_expr contains rule function with string representation of the rule as symbol and + //for each positive uninterpreted tail it contains its argument values and its explanation term + expr * rule_expr = m_decl_util.mk_rule(rule_repr, rule_expr_args.size(), rule_expr_args.c_ptr()); + + app_ref e_record(m_manager.mk_eq(m_manager.mk_var(head_var, m_e_sort), rule_expr), m_manager); + e_tail.push_back(e_record); + neg_flags.push_back(false); + SASSERT(e_tail.size()==neg_flags.size()); + + return m_context.get_rule_manager().mk(e_head, e_tail.size(), e_tail.c_ptr(), neg_flags.c_ptr()); + } + + void mk_explanations::transform_rules(const rule_set & orig, rule_set & tgt) { + rule_set::iterator rit = orig.begin(); + rule_set::iterator rend = orig.end(); + for(; rit!=rend; ++rit) { + rule * e_rule = get_e_rule(*rit); + tgt.add_rule(e_rule); + } + + //add rules that will (for output predicates) copy facts from explained relations back to + //the original ones + expr_ref_vector lit_args(m_manager); + decl_set::iterator pit = m_original_preds.begin(); + decl_set::iterator pend = m_original_preds.end(); + for(; pit!=pend; ++pit) { + func_decl * orig_decl = *pit; + + if(!m_context.is_output_predicate(orig_decl)) { + continue; + } + + lit_args.reset(); + unsigned arity = orig_decl->get_arity(); + for(unsigned i=0; iget_domain(i))); + } + app_ref orig_lit(m_manager.mk_app(orig_decl, lit_args.c_ptr()), m_manager); + app_ref e_lit(get_e_lit(orig_lit, arity), m_manager); + app * tail[] = { e_lit.get() }; + tgt.add_rule(m_context.get_rule_manager().mk(orig_lit, 1, tail, 0)); + } + + } + + void mk_explanations::translate_rel_level_relation(relation_manager & rmgr, relation_base & orig, + relation_base & e_rel) { + SASSERT(m_e_fact_relation); + SASSERT(e_rel.get_plugin().is_product_relation()); + + product_relation & prod_rel = static_cast(e_rel); + SASSERT(prod_rel.size()==2); + SASSERT(prod_rel[0].get_plugin().is_sieve_relation()); + SASSERT(prod_rel[1].get_plugin().is_sieve_relation()); + sieve_relation * srels[] = { + static_cast(&prod_rel[0]), + static_cast(&prod_rel[1]) }; + if(&srels[0]->get_inner().get_plugin()==m_er_plugin) { + std::swap(srels[0], srels[1]); + } + SASSERT(&srels[0]->get_inner().get_plugin()==&orig.get_plugin()); + SASSERT(&srels[1]->get_inner().get_plugin()==m_er_plugin); + + relation_base & new_orig = srels[0]->get_inner(); + explanation_relation & expl_rel = static_cast(srels[1]->get_inner()); + + { + scoped_ptr orig_union_fun = rmgr.mk_union_fn(new_orig, orig); + SASSERT(orig_union_fun); + (*orig_union_fun)(new_orig, orig); + } + + { + scoped_ptr expl_union_fun = rmgr.mk_union_fn(expl_rel, *m_e_fact_relation); + SASSERT(expl_union_fun); + (*expl_union_fun)(expl_rel, *m_e_fact_relation); + } + } + + void mk_explanations::transform_facts(relation_manager & rmgr) { + + + if(!m_e_fact_relation) { + relation_signature expl_singleton_sig; + expl_singleton_sig.push_back(m_e_sort); + + relation_base * expl_singleton = rmgr.mk_empty_relation(expl_singleton_sig, m_er_plugin->get_kind()); + relation_fact es_fact(m_manager); + es_fact.push_back(m_decl_util.mk_fact(symbol("fact"))); + expl_singleton->add_fact(es_fact); + + SASSERT(&expl_singleton->get_plugin()==m_er_plugin); + m_e_fact_relation = static_cast(expl_singleton); + } + + + + decl_set::iterator it = m_original_preds.begin(); + decl_set::iterator end = m_original_preds.end(); + for(; it!=end; ++it) { + func_decl * orig_decl = *it; + func_decl * e_decl = get_e_decl(orig_decl); + + if(m_context.is_output_predicate(orig_decl)) { + m_context.set_output_predicate(e_decl); + } + + if(!rmgr.try_get_relation(orig_decl)) { + //there are no facts for this predicate + continue; + } + + relation_base & orig_rel = rmgr.get_relation(orig_decl); + relation_base & e_rel = rmgr.get_relation(e_decl); + SASSERT(e_rel.empty()); //the e_rel should be a new relation + + if(m_relation_level) { + translate_rel_level_relation(rmgr, orig_rel, e_rel); + } + else { + scoped_ptr product_fun = rmgr.mk_join_fn(orig_rel, *m_e_fact_relation, 0, 0, 0); + SASSERT(product_fun); + scoped_rel aux_extended_rel = (*product_fun)(orig_rel, *m_e_fact_relation); + scoped_ptr union_fun = rmgr.mk_union_fn(e_rel, *aux_extended_rel); + SASSERT(union_fun); + (*union_fun)(e_rel, *aux_extended_rel); + } + + } + } + + rule_set * mk_explanations::operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc) { + SASSERT(!mc && !pc); + if(source.get_num_rules()==0) { + return 0; + } + + m_context.collect_predicates(m_original_preds); + + rule_set * res = alloc(rule_set, m_context); + transform_facts(m_context.get_rmanager()); + transform_rules(source, *res); + return res; + } + +}; + diff --git a/lib/dl_mk_explanations.h b/lib/dl_mk_explanations.h new file mode 100644 index 000000000..36fb1a3a1 --- /dev/null +++ b/lib/dl_mk_explanations.h @@ -0,0 +1,92 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_explanations.h + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-11-08. + +Revision History: + +--*/ + +#ifndef _DL_MK_EXPLANATIONS_H_ +#define _DL_MK_EXPLANATIONS_H_ + +#include "dl_context.h" +#include "dl_rule_transformer.h" + +namespace datalog { + + class explanation_relation; + class explanation_relation_plugin; + + class mk_explanations : public rule_transformer::plugin { + + typedef obj_map decl_map; + + ast_manager & m_manager; + context & m_context; + dl_decl_util & m_decl_util; + + bool m_relation_level; + + decl_set m_original_preds; + + ast_ref_vector m_pinned; + + explanation_relation_plugin * m_er_plugin; + + sort * m_e_sort; + scoped_rel m_e_fact_relation; + + decl_map m_e_decl_map; + + symbol get_rule_symbol(rule * r); + + app * get_e_lit(app * lit, unsigned e_var_idx); + rule * get_e_rule(rule * r); + + /** + If \c m_relation_level is true, ensure \c e_decl predicate will be represented by + the right relation object. \c orig is the predicate corresponding to \c e_decl without + the explanation column. + */ + void assign_rel_level_kind(func_decl * e_decl, func_decl * orig); + void translate_rel_level_relation(relation_manager & rmgr, relation_base & orig, relation_base & e_rel); + + void transform_rules(const rule_set & orig, rule_set & tgt); + + void transform_facts(relation_manager & rmgr); + public: + /** + If relation_level is true, the explanation will not be stored for each fact, + but we will rather store history of the whole relation. + */ + mk_explanations(context & ctx, bool relation_level); + + /** + \brief Return explanation predicate that corresponds to \c orig_decl. + */ + func_decl * get_e_decl(func_decl * orig_decl); + + static func_decl * get_union_decl(context & ctx); + func_decl * get_union_decl() const { + return get_union_decl(m_context); + } + + rule_set * operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc); + + static expr* get_explanation(relation_base const& r); + }; +}; + +#endif /* _DL_MK_EXPLANATIONS_H_ */ + diff --git a/lib/dl_mk_filter_rules.cpp b/lib/dl_mk_filter_rules.cpp new file mode 100644 index 000000000..4f01a3651 --- /dev/null +++ b/lib/dl_mk_filter_rules.cpp @@ -0,0 +1,168 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_filter_rules.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-05-18. + +Revision History: + +--*/ + +#include"dl_mk_filter_rules.h" +#include"dl_context.h" +#include"for_each_expr.h" +#include"ast_pp.h" + +namespace datalog { + + mk_filter_rules::mk_filter_rules(context & ctx): + plugin(2000), + m_context(ctx), + m_manager(ctx.get_manager()), + m_result(0), + m_pinned(m_manager) { + } + mk_filter_rules::~mk_filter_rules() { + ptr_vector to_dealloc; + filter_cache::iterator it = m_tail2filter.begin(); + filter_cache::iterator end = m_tail2filter.end(); + for(; it!=end; ++it) { + to_dealloc.push_back(it->m_key); + } + m_tail2filter.reset(); + ptr_vector::iterator dit = to_dealloc.begin(); + ptr_vector::iterator dend = to_dealloc.end(); + for(; dit!=dend; ++dit) { + dealloc(*dit); + } + } + + /** + \brief Return true if \c pred is a cadidate for a "filter" rule. + */ + bool mk_filter_rules::is_candidate(app * pred) { + if (!m_context.get_rule_manager().is_predicate(pred)) { + TRACE("mk_filter_rules", tout << mk_pp(pred, m_manager) << "\nis not a candidate because it is interpreted.\n";); + return false; + } + var_idx_set used_vars; + unsigned n = pred->get_num_args(); + for (unsigned i = 0; i < n; i++) { + expr * arg = pred->get_arg(i); + if (m_manager.is_value(arg)) + return true; + SASSERT(is_var(arg)); + unsigned vidx = to_var(arg)->get_idx(); + if (used_vars.contains(vidx)) + return true; + used_vars.insert(vidx); + } + return false; + } + + /** + \brief Create a "filter" (if it doesn't exist already) for the given predicate. + */ + func_decl * mk_filter_rules::mk_filter_decl(app * pred, var_idx_set const & non_local_vars) { + sort_ref_buffer filter_domain(m_manager); + + filter_key * key = alloc(filter_key, m_manager); + mk_new_rule_tail(m_manager, pred, non_local_vars, filter_domain, key->filter_args, key->new_pred); + func_decl * filter_decl = 0; + if (!m_tail2filter.find(key, filter_decl)) { + filter_decl = m_context.mk_fresh_head_predicate(pred->get_decl()->get_name(), symbol("filter"), + filter_domain.size(), filter_domain.c_ptr(), pred->get_decl()); + + m_pinned.push_back(filter_decl); + m_tail2filter.insert(key, filter_decl); + app_ref filter_head(m_manager); + filter_head = m_manager.mk_app(filter_decl, key->filter_args.size(), key->filter_args.c_ptr()); + app * filter_tail = key->new_pred; + rule * filter_rule = m_context.get_rule_manager().mk(filter_head, 1, &filter_tail, (const bool *)0); + filter_rule->set_accounting_parent_object(m_context, m_current); + m_result->add_rule(filter_rule); + } + else { + dealloc(key); + } + SASSERT(filter_decl != 0); + SASSERT(filter_decl->get_arity()==filter_domain.size()); + return filter_decl; + } + + void mk_filter_rules::process(rule * r) { + m_current = r; + app * new_head = r->get_head(); + app_ref_vector new_tail(m_manager); + svector new_is_negated; + unsigned sz = r->get_tail_size(); + bool rule_modified = false; + for (unsigned i = 0; i < sz; i++) { + app * tail = r->get_tail(i); + if (is_candidate(tail)) { + TRACE("mk_filter_rules", tout << "is_candidate: " << mk_pp(tail, m_manager) << "\n";); + var_idx_set non_local_vars; + collect_non_local_vars(m_manager, r, tail, non_local_vars); + func_decl * filter_decl = mk_filter_decl(tail, non_local_vars); + ptr_buffer new_args; + var_idx_set used_vars; + unsigned num_args = tail->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = tail->get_arg(i); + if (is_var(arg)) { + unsigned vidx = to_var(arg)->get_idx(); + if (non_local_vars.contains(vidx) && !used_vars.contains(vidx)) { + new_args.push_back(arg); + used_vars.insert(vidx); + } + } + } + SASSERT(new_args.size() == filter_decl->get_arity()); + new_tail.push_back(m_manager.mk_app(filter_decl, new_args.size(), new_args.c_ptr())); + rule_modified = true; + } + else { + new_tail.push_back(tail); + } + new_is_negated.push_back(r->is_neg_tail(i)); + } + if(rule_modified) { + remove_duplicate_tails(new_tail, new_is_negated); + SASSERT(new_tail.size() == new_is_negated.size()); + rule * new_rule = m_context.get_rule_manager().mk(new_head, new_tail.size(), new_tail.c_ptr(), new_is_negated.c_ptr()); + new_rule->set_accounting_parent_object(m_context, m_current); + m_result->add_rule(new_rule); + m_modified = true; + } + else { + m_result->add_rule(r); + } + } + + rule_set * mk_filter_rules::operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc) { + // TODO mc, pc + m_tail2filter.reset(); + m_result = alloc(rule_set, m_context); + m_modified = false; + unsigned num_rules = source.get_num_rules(); + for (unsigned i = 0; i < num_rules; i++) { + rule * r = source.get_rule(i); + process(r); + } + if(!m_modified) { + dealloc(m_result); + return static_cast(0); + } + return m_result; + } + +}; diff --git a/lib/dl_mk_filter_rules.h b/lib/dl_mk_filter_rules.h new file mode 100644 index 000000000..cd1d10997 --- /dev/null +++ b/lib/dl_mk_filter_rules.h @@ -0,0 +1,82 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_filter_rules.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-05-18. + +Revision History: + +--*/ +#ifndef _DL_MK_FILTER_RULES_H_ +#define _DL_MK_FILTER_RULES_H_ + +#include"map.h" + +#include"dl_rule_set.h" +#include"dl_rule_transformer.h" + +namespace datalog { + + /** + \brief Functor for applying a rule set transformation that creates "filters". + A "filter" is a rule of the form: + + Head(X_1, ..., X_n) :- Tail(...) + + where X_1,...,X_n are distinct, and Tail contain repeated variables and/or values. + + After applying this functor only "filter" rules will contain atoms with repeated variables and/or values. + */ + class mk_filter_rules : public rule_transformer::plugin { + + struct filter_key { + app_ref new_pred; + expr_ref_buffer filter_args; + + filter_key(ast_manager & m) : new_pred(m), filter_args(m) {} + + unsigned hash() const { + return new_pred->hash() ^ int_vector_hash(filter_args); + } + bool operator==(const filter_key & o) const { + return o.new_pred==new_pred && vectors_equal(o.filter_args, filter_args); + } + }; + + typedef map, deref_eq > filter_cache; + + context & m_context; + ast_manager & m_manager; + filter_cache m_tail2filter; + rule_set * m_result; + rule * m_current; + bool m_modified; + ast_ref_vector m_pinned; + + bool is_candidate(app * pred); + func_decl * mk_filter_decl(app * pred, var_idx_set const & non_local_vars); + void process(rule * r); + + public: + mk_filter_rules(context & ctx); + ~mk_filter_rules(); + /** + \brief Return a new rule set where only filter rules contain atoms with repeated variables and/or values. + */ + rule_set * operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc); + }; + +}; + + +#endif /* _DL_MK_FILTER_RULES_H_ */ + diff --git a/lib/dl_mk_interp_tail_simplifier.cpp b/lib/dl_mk_interp_tail_simplifier.cpp new file mode 100644 index 000000000..be730a5c7 --- /dev/null +++ b/lib/dl_mk_interp_tail_simplifier.cpp @@ -0,0 +1,569 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_interp_tail_simplifier.cpp + +Abstract: + + Rule transformer which simplifies interpreted tails + +Author: + + Krystof Hoder (t-khoder) 2011-10-01. + +Revision History: + +--*/ + + +#include +#include"ast_pp.h" +#include"bool_rewriter.h" +#include"rewriter.h" +#include"rewriter_def.h" +#include"dl_mk_rule_inliner.h" +#include"dl_mk_interp_tail_simplifier.h" + +namespace datalog { + + // ----------------------------------- + // + // mk_interp_tail_simplifier::rule_substitution + // + // ----------------------------------- + + void mk_interp_tail_simplifier::rule_substitution::reset(rule * r) { + unsigned var_cnt = m_context.get_rule_manager().get_var_counter().get_max_var(*r)+1; + m_subst.reset(); + m_subst.reserve(1, var_cnt); + m_rule = r; + } + + bool mk_interp_tail_simplifier::rule_substitution::unify(expr * e1, expr * e2) { + SASSERT(m_rule); + + //we need to apply the current substitution in order to ensure the unifier + //works in an incremental way + expr_ref e1_s(m); + expr_ref e2_s(m); + m_subst.apply(e1,e1_s); + m_subst.apply(e2,e2_s); + //and we need to reset the cache as we're going to modify the substitution + m_subst.reset_cache(); + + return m_unif (e1_s, e2_s, m_subst, false); + } + + void mk_interp_tail_simplifier::rule_substitution::apply(app * a, app_ref& res) { + SASSERT(m_rule); + expr_ref res_e(m); + m_subst.apply(a, res_e); + SASSERT(is_app(res_e.get())); + res = to_app(res_e.get()); + } + + void mk_interp_tail_simplifier::rule_substitution::get_result(rule_ref & res) { + SASSERT(m_rule); + + app_ref new_head(m); + apply(m_rule->get_head(), new_head); + + app_ref_vector tail(m); + svector tail_neg; + + unsigned tail_len = m_rule->get_tail_size(); + for (unsigned i=0; iget_tail(i), new_tail_el); + tail.push_back(new_tail_el); + tail_neg.push_back(m_rule->is_neg_tail(i)); + } + + mk_rule_inliner::remove_duplicate_tails(tail, tail_neg); + + SASSERT(tail.size() == tail_neg.size()); + res = m_context.get_rule_manager().mk(new_head, tail.size(), tail.c_ptr(), tail_neg.c_ptr()); + res->set_accounting_parent_object(m_context, m_rule); + res->norm_vars(res.get_manager()); + } + + + // ----------------------------------- + // + // mk_interp_tail_simplifier + // + // ----------------------------------- + + + class mk_interp_tail_simplifier::normalizer_cfg : public default_rewriter_cfg + { + struct expr_cmp + { + ast_manager& m; + + expr_cmp(ast_manager& m) : m(m) {} + + bool operator()(expr * ae, expr * be) { + return cmp_expr(ae, be, 4) == -1; + } + + template + static int cmp(T a, T b) { return (a>b) ? 1 : ((a == b) ? 0 : -1); } + + int cmp_expr(expr * ae, expr * be, int depth) { + if (ae == be) { return 0; } + + //remove negations + bool a_neg = m.is_not(ae, ae); + bool b_neg = m.is_not(be, be); + + if (ae==be) { return cmp(a_neg, b_neg); } + + if (!is_app(ae) && !is_app(be)) { return cmp(ae->get_id(), be->get_id()); } + if (!is_app(ae)) { return -1; } + if (!is_app(be)) { return 1; } + app * a = to_app(ae); + app * b = to_app(be); + if (a->get_decl()!=b->get_decl()) { + return cmp(a->get_decl()->get_id(), b->get_decl()->get_id()); + } + + if (a->get_num_args()!=b->get_num_args()) { + return cmp(a->get_num_args(), b->get_num_args()); + } + + if (depth==0) { + return cmp(a->get_id(),b->get_id()); + } + unsigned arg_cnt = a->get_num_args(); + + unsigned neg_comparison = 0; + + for (unsigned i=0; iget_arg(i); + expr * arg_b = b->get_arg(i); + + //we normalize away negations + bool a_is_neg = m.is_not(arg_a, arg_a); + bool b_is_neg = m.is_not(arg_b, arg_b); + + if (neg_comparison==0 && a_is_neg!=b_is_neg) { + neg_comparison = a_is_neg ? -1 : 1; + } + + int res = cmp_expr(arg_a, arg_b, depth-1); + if (res!=0) { + return res; + } + } + if (neg_comparison!=0) { + return neg_comparison; + } + //by normalizing away negation we may have put non-equal terms to be equal, so here we check + return cmp(a->get_id(),b->get_id()); + } + }; + + ast_manager& m; + bool_rewriter m_brwr; + + //instead of a local variable + expr_ref_vector m_app_args; + + expr_cmp m_expr_cmp; + + public: + normalizer_cfg(ast_manager& m) + : m(m), m_brwr(m), m_app_args(m), m_expr_cmp(m) + { + } + + static void remove_duplicates(expr_ref_vector& v) + { + expr * a = v[0].get(); + unsigned read_idx = 1; + unsigned write_idx = 1; + for (;;) { + while(read_idx arg_pair; + + bool match_arg_pair(expr * e, arg_pair& pair, bool seek_conjunction) + { + if (seek_conjunction) { + return m.is_and(e, pair.first, pair.second); + } + else { + return m.is_or(e, pair.first, pair.second); + } + } + + /** + If inside_disjunction is false, we're inside a conjunction (and arg pairs + represent disjunctions). + */ + app * detect_equivalence(const arg_pair& p1, const arg_pair& p2, bool inside_disjunction) + { + if (m.is_not(p1.first)==m.is_not(p2.first)) { return 0; } + if (m.is_not(p1.second)==m.is_not(p2.second)) { return 0; } + + expr * first_bare = 0; + if (m.is_not(p1.first, first_bare) && p2.first!=first_bare) { return 0; } + if (m.is_not(p2.first, first_bare) && p1.first!=first_bare) { return 0; } + SASSERT(first_bare); + + expr * second_bare = 0; + if (m.is_not(p1.second, second_bare) && p2.second!=second_bare) { return 0; } + if (m.is_not(p2.second, second_bare) && p1.second!=second_bare) { return 0; } + SASSERT(second_bare); + + if (!m.is_bool(first_bare) || !m.is_bool(second_bare)) { return 0; } + + //both negations are in the same pair + bool negs_together = m.is_not(p1.first)==m.is_not(p1.second); + + if (negs_together==inside_disjunction) { + return m.mk_eq(first_bare, second_bare); + } + else { + return m.mk_eq(first_bare, m.mk_not(second_bare)); + } + } + + bool detect_equivalences(expr_ref_vector& v, bool inside_disjunction) + { + bool have_pair = false; + unsigned prev_pair_idx; + arg_pair ap; + + unsigned read_idx = 0; + unsigned write_idx = 0; + while(read_idx rwr(m, false, r_cfg); + expr_ref dl_form_e(m); + rwr(simp1_res.get(), res); + + /*if (simp1_res.get()!=res.get()) { + std::cout<<"pre norm:\n"<get_uninterpreted_tail_size(); + unsigned len = r->get_tail_size(); + if (u_len==len) { + return false; + } + + ptr_vector todo; + for (unsigned i=u_len; iget_tail(i)); + SASSERT(!r->is_neg_tail(i)); + } + + m_rule_subst.reset(r); + + bool found_something = false; + +#define TRY_UNIFY(_x,_y) if (m_rule_subst.unify(_x,_y)) { found_something = true; } +#define IS_FLEX(_x) (is_var(_x) || m.is_value(_x)) + + while (!todo.empty()) { + expr * arg1, *arg2; + expr * t0 = todo.back(); + todo.pop_back(); + expr* t = t0; + bool neg = m.is_not(t, t); + if (is_var(t)) { + TRY_UNIFY(t, neg ? m.mk_false() : m.mk_true()); + } + else if (!neg && m.is_and(t)) { + app* a = to_app(t); + todo.append(a->get_num_args(), a->get_args()); + } + else if (!neg && m.is_eq(t, arg1, arg2) && IS_FLEX(arg1) && IS_FLEX(arg2)) { + TRY_UNIFY(arg1, arg2); + } + else if (m.is_iff(t, arg1, arg2)) { + //determine the polarity of the equivalence and remove the negations + while (m.is_not(arg1, arg1)) neg = !neg; + while (m.is_not(arg2, arg2)) neg = !neg; + if (!is_var(arg1)) { + std::swap(arg1, arg2); + } + if (!IS_FLEX(arg1) || !IS_FLEX(arg2)) { + // no-op + } + else if (is_var(arg1) && !neg) { + TRY_UNIFY(arg1, arg2); + } + else if (is_var(arg1) && neg && m.is_true(arg2)) { + TRY_UNIFY(arg1, m.mk_false()); + } + else if (is_var(arg1) && neg && m.is_false(arg2)) { + TRY_UNIFY(arg1, m.mk_true()); + } + } + } + + if (!found_something) { + return false; + } + TRACE("dl_interp_tail_simplifier_propagation_pre", + tout << "will propagate rule:\n"; + r->display(m_context, tout); + ); + m_rule_subst.get_result(res); + TRACE("dl_interp_tail_simplifier_propagation", + tout << "propagated equivalences of:\n"; + r->display(m_context, tout); + tout << "into:\n"; + res->display(m_context, tout); + ); + return true; + } + + bool mk_interp_tail_simplifier::transform_rule(rule * r0, rule_ref & res) + { + rule_ref r(r0, m_context.get_rule_manager()); + + start: + unsigned u_len = r->get_uninterpreted_tail_size(); + unsigned len = r->get_tail_size(); + if (u_len==len) { + res = r; + return true; + } + app_ref head(r->get_head(), m); + + app_ref_vector tail(m); + svector tail_neg; + + for (unsigned i=0; iget_tail(i)); + tail_neg.push_back(r->is_neg_tail(i)); + } + + bool modified = false; + app_ref itail(m); + + if (u_len+1==len) { + //we have only one interpreted tail + itail = r->get_tail(u_len); + SASSERT(!r->is_neg_tail(u_len)); + } + else { + expr_ref_vector itail_members(m); + for (unsigned i=u_len; iget_tail(i)); + SASSERT(!r->is_neg_tail(i)); + } + itail = m.mk_and(itail_members.size(), itail_members.c_ptr()); + modified = true; + } + + expr_ref simp_res(m); + simplify_expr(itail.get(), simp_res); + + modified |= itail.get()!=simp_res.get(); + + if (is_app(simp_res.get())) { + itail = to_app(simp_res.get()); + } + else if (m.is_bool(simp_res)) { + itail = m.mk_eq(simp_res, m.mk_true()); + } + else { + throw default_exception("simplification resulted in non-boolean non-function"); + } + + if (m.is_false(itail.get())) { + //the tail member is never true, so we may delete the rule + TRACE("dl", r->display(m_context, tout << "rule in infeasible\n");); + return false; + } + if (!m.is_true(itail.get())) { + //if the simplified tail is not a tautology, we add it to the rule + tail.push_back(itail); + tail_neg.push_back(false); + } + else { + modified = true; + } + + SASSERT(tail.size() == tail_neg.size()); + if (modified) { + res = m_context.get_rule_manager().mk(head, tail.size(), tail.c_ptr(), tail_neg.c_ptr()); + res->set_accounting_parent_object(m_context, r); + } + else { + res = r; + } + + rule_ref pro_var_eq_result(m_context.get_rule_manager()); + if (propagate_variable_equivalences(res, pro_var_eq_result)) { + SASSERT(var_counter().get_max_var(*r.get())==0 || + var_counter().get_max_var(*r.get()) > var_counter().get_max_var(*pro_var_eq_result.get())); + r = pro_var_eq_result; + goto start; + } + + CTRACE("dl", (res != r0), r0->display(m_context, tout << "old:\n"); res->display(m_context, tout << "new:\n");); + + return true; + } + + bool mk_interp_tail_simplifier::transform_rules(const rule_set & orig, rule_set & tgt) { + bool modified = false; + rule_set::iterator rit = orig.begin(); + rule_set::iterator rend = orig.end(); + for (; rit!=rend; ++rit) { + rule_ref new_rule(m_context.get_rule_manager()); + if (transform_rule(*rit, new_rule)) { + bool is_modified = *rit != new_rule; + modified |= is_modified; + tgt.add_rule(new_rule); + } + else { + modified = true; + } + } + return modified; + } + + rule_set * mk_interp_tail_simplifier::operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc) { + // TODO mc, pc + if (source.get_num_rules() == 0) { + return 0; + } + + rule_set * res = alloc(rule_set, m_context); + if (!transform_rules(source, *res)) { + dealloc(res); + res = 0; + } + return res; + } + +}; + diff --git a/lib/dl_mk_interp_tail_simplifier.h b/lib/dl_mk_interp_tail_simplifier.h new file mode 100644 index 000000000..99bda325f --- /dev/null +++ b/lib/dl_mk_interp_tail_simplifier.h @@ -0,0 +1,101 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_interp_tail_simplifier.h + +Abstract: + + Rule transformer which simplifies interpreted tails + +Author: + + Krystof Hoder (t-khoder) 2011-10-01. + +Revision History: + +--*/ + +#ifndef _DL_MK_INTERP_TAIL_SIMPLIFIER_H_ +#define _DL_MK_INTERP_TAIL_SIMPLIFIER_H_ + +#include "dl_context.h" +#include "dl_rule_transformer.h" +#include "unifier.h" +#include "substitution.h" + +namespace datalog { + + class mk_interp_tail_simplifier : public rule_transformer::plugin { + + class rule_substitution + { + ast_manager& m; + context& m_context; + substitution m_subst; + unifier m_unif; + + rule * m_rule; + + void apply(app * a, app_ref& res); + public: + rule_substitution(context & ctx) + : m(ctx.get_manager()), m_context(ctx), m_subst(m), m_unif(m), m_rule(0) {} + + /** + Reset substitution and get it ready for working with rule r. + + As long as this object is used without a reset, the rule r must exist. + */ + void reset(rule * r); + + /** Reset subtitution and unify tail tgt_idx of the target rule and the head of the src rule */ + bool unify(expr * e1, expr * e2); + + void get_result(rule_ref & res); + + void display(std::ostream& stm) { + m_subst.display(stm); + } + }; + + + ast_manager & m; + context & m_context; + th_rewriter & m_simp; + + rule_substitution m_rule_subst; + + class normalizer_cfg; + + void simplify_expr(app * a, expr_ref& res); + + /** return true if some propagation was done */ + bool propagate_variable_equivalences(rule * r, rule_ref& res); + + /** Return true if something was modified */ + bool transform_rules(const rule_set & orig, rule_set & tgt); + public: + mk_interp_tail_simplifier(context & ctx, unsigned priority=40000) + : plugin(priority), + m(ctx.get_manager()), + m_context(ctx), + m_simp(ctx.get_rewriter()), + m_rule_subst(ctx) {} + + /**If rule should be retained, assign transformed version to res and return true; + if rule can be deleted, return false. + + This method is kind of useful, so it's public to allow other rules to use it, + e.g. on their intermediate results. + */ + bool transform_rule(rule * r, rule_ref& res); + + rule_set * operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc); + }; + +}; + +#endif /* _DL_MK_INTERP_TAIL_SIMPLIFIER_H_ */ + diff --git a/lib/dl_mk_magic_sets.cpp b/lib/dl_mk_magic_sets.cpp new file mode 100644 index 000000000..668cbf939 --- /dev/null +++ b/lib/dl_mk_magic_sets.cpp @@ -0,0 +1,395 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_magic_sets.cpp + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-10-04. + +Revision History: + +--*/ + +#include +#include +#include"ast_pp.h" +#include"dl_mk_magic_sets.h" + +namespace datalog { + + mk_magic_sets::mk_magic_sets(context & ctx, rule * goal_rule) : + plugin(10000, true), + m_context(ctx), + m_manager(ctx.get_manager()), + m_rules(ctx.get_rule_manager()), + m_pinned(m_manager), + m_goal_rule(goal_rule, ctx.get_rule_manager()) { + } + + void mk_magic_sets::reset() { + m_extentional.reset(); + m_todo.reset(); + m_adorned_preds.reset(); + m_adornments.reset(); + m_magic_preds.reset(); + m_rules.reset(); + m_pinned.reset(); + } + + void mk_magic_sets::adornment::populate(app * lit, const var_idx_set & bound_vars) { + SASSERT(empty()); + unsigned arity = lit->get_num_args(); + for(unsigned i=0; iget_arg(i); + bool bound = !is_var(arg) || bound_vars.contains(to_var(arg)->get_idx()); + push_back(bound ? AD_BOUND : AD_FREE); + } + } + + std::string mk_magic_sets::adornment::to_string() const { + std::string res; + const_iterator eit = begin(); + const_iterator eend = end(); + for(; eit!=eend; ++eit) { + switch(*eit) { + case AD_BOUND: + res+='b'; + break; + case AD_FREE: + res+='f'; + break; + default: + UNREACHABLE(); + } + } + return res; + } + + unsigned get_bound_arg_count(app * lit, const var_idx_set & bound_vars) { + unsigned res = 0; + unsigned n = lit->get_num_args(); + for(unsigned i=0; iget_arg(i); + if(is_var(arg) && !bound_vars.contains(to_var(arg)->get_idx())) { + continue; + } + SASSERT(is_var(arg) || is_app(arg)); + SASSERT(!is_app(arg) || to_app(arg)->get_num_args()==0); + res++; + } + return res; + } + + float mk_magic_sets::get_unbound_cost(app * lit, const var_idx_set & bound_vars) { + func_decl * pred = lit->get_decl(); + float res = 1; + unsigned n = lit->get_num_args(); + for(unsigned i=0; iget_arg(i); + if(is_var(arg) && !bound_vars.contains(to_var(arg)->get_idx())) { + res*=m_context.get_sort_size_estimate(pred->get_domain(i)); + } + //res-=1; + } + return res; + } + + /** + \brief From \c cont which is list of indexes of tail literals of rule \c r, select + the index pointing to a literal with at least one bound variable that will be the next + bound literal in the process of creating an adorned rule. If all literals are unbound, + return -1. + */ + int mk_magic_sets::pop_bound(unsigned_vector & cont, rule * r, const var_idx_set & bound_vars) { + float best_cost; + int candidate_index = -1; + unsigned n = cont.size(); + for(unsigned i=0; iget_tail(cont[i]); + unsigned bound_cnt = get_bound_arg_count(lit, bound_vars); + if(bound_cnt==0) { + continue; + } + float cost = get_unbound_cost(lit, bound_vars); + if(candidate_index==-1 || cost(n-1)) { + std::swap(cont[candidate_index], cont[n-1]); + } + unsigned res = cont.back(); + cont.pop_back(); + return res; + } + + app * mk_magic_sets::adorn_literal(app * lit, const var_idx_set & bound_vars) { + SASSERT(!m_extentional.contains(lit->get_decl())); + func_decl * old_pred = lit->get_decl(); + SASSERT(m_manager.is_bool(old_pred->get_range())); + adornment_desc adn(old_pred); + adn.m_adornment.populate(lit, bound_vars); + adornment_map::entry * e = m_adorned_preds.insert_if_not_there2(adn, 0); + func_decl * new_pred = e->get_data().m_value; + if(new_pred==0) { + std::string suffix = "ad_"+adn.m_adornment.to_string(); + new_pred = m_context.mk_fresh_head_predicate( + old_pred->get_name(), symbol(suffix.c_str()), + old_pred->get_arity(), old_pred->get_domain(), old_pred); + m_pinned.push_back(new_pred); + e->get_data().m_value = new_pred; + m_todo.push_back(adn); + m_adornments.insert(new_pred, adn.m_adornment); + } + app * res = m_manager.mk_app(new_pred, lit->get_args()); + m_pinned.push_back(res); + return res; + } + + app * mk_magic_sets::create_magic_literal(app * l) { + func_decl * l_pred = l->get_decl(); + SASSERT(m_manager.is_bool(l_pred->get_range())); + pred_adornment_map::obj_map_entry * ae = m_adornments.find_core(l_pred); + SASSERT(ae); + const adornment & adn = ae->get_data().m_value; + + unsigned l_arity = l->get_num_args(); + ptr_vector bound_args; + for(unsigned i=0; iget_arg(i)); + } + } + + pred2pred::obj_map_entry * e = m_magic_preds.insert_if_not_there2(l_pred, 0); + func_decl * mag_pred = e->get_data().m_value; + if(mag_pred==0) { + unsigned mag_arity = bound_args.size(); + + ptr_vector mag_domain; + for(unsigned i=0; iget_domain(i)); + } + } + + mag_pred = m_context.mk_fresh_head_predicate(l_pred->get_name(), symbol("ms"), + mag_arity, mag_domain.c_ptr(), l_pred); + m_pinned.push_back(mag_pred); + e->get_data().m_value = mag_pred; + } + + app * res = m_manager.mk_app(mag_pred, bound_args.c_ptr()); + m_pinned.push_back(res); + return res; + } + + void mk_magic_sets::create_magic_rules(app * head, unsigned tail_cnt, app * const * tail, bool const* negated) { + //TODO: maybe include relevant interpreted predicates from the original rule + ptr_vector new_tail; + svector negations; + new_tail.push_back(create_magic_literal(head)); + new_tail.append(tail_cnt, tail); + negations.push_back(false); + negations.append(tail_cnt, negated); + + for(unsigned i=0; iget_decl())) { + continue; + } + app * mag_head = create_magic_literal(tail[i]); + rule * r = m_context.get_rule_manager().mk(mag_head, i+1, new_tail.c_ptr(), negations.c_ptr()); + TRACE("dl", r->display(m_context,tout); ); + m_rules.push_back(r); + } + } + + void mk_magic_sets::transform_rule(const adornment & head_adornment, rule * r) { + app * head = r->get_head(); + unsigned head_len = head->get_num_args(); + SASSERT(head_len==head_adornment.size()); + + var_idx_set bound_vars; + for(unsigned i=0; iget_arg(i); + if(head_adornment[i]==AD_BOUND && is_var(arg)) { + bound_vars.insert(to_var(arg)->get_idx()); + } + } + + unsigned processed_tail_len = r->get_uninterpreted_tail_size(); + unsigned_vector exten_tails; + unsigned_vector inten_tails; + for(unsigned i=0; iget_tail(i); + if(m_extentional.contains(t->get_decl())) { + exten_tails.push_back(i); + } + else { + inten_tails.push_back(i); + } + } + + ptr_vector new_tail; + svector negations; + while(new_tail.size()!=processed_tail_len) { + bool intentional = false; + int curr_index = pop_bound(exten_tails, r, bound_vars); + if(curr_index==-1) { + curr_index = pop_bound(inten_tails, r,bound_vars); + if(curr_index!=-1) { + intentional = true; + } + } + if(curr_index==-1) { + if(!exten_tails.empty()) { + curr_index = exten_tails.back(); + exten_tails.pop_back(); + } + else { + SASSERT(!inten_tails.empty()); + curr_index = inten_tails.back(); + inten_tails.pop_back(); + intentional = true; + } + } + SASSERT(curr_index!=-1); + app * curr = r->get_tail(curr_index); + if(intentional) { + curr = adorn_literal(curr, bound_vars); + } + new_tail.push_back(curr); + negations.push_back(r->is_neg_tail(curr_index)); + collect_vars(m_manager, curr, bound_vars); + } + + + func_decl * new_head_pred; + VERIFY( m_adorned_preds.find(adornment_desc(head->get_decl(), head_adornment), new_head_pred) ); + app * new_head = m_manager.mk_app(new_head_pred, head->get_args()); + + SASSERT(new_tail.size()==r->get_uninterpreted_tail_size()); + create_magic_rules(new_head, new_tail.size(), new_tail.c_ptr(), negations.c_ptr()); + + unsigned tail_len = r->get_tail_size(); + for(unsigned i=processed_tail_len; iget_tail(i)); + negations.push_back(r->is_neg_tail(i)); + } + + new_tail.push_back(create_magic_literal(new_head)); + negations.push_back(false); + + rule * nr = m_context.get_rule_manager().mk(new_head, new_tail.size(), new_tail.c_ptr(), negations.c_ptr()); + m_rules.push_back(nr); + nr->set_accounting_parent_object(m_context, r); + } + + void mk_magic_sets::create_transfer_rule(const adornment_desc & d) { + func_decl * adn_pred; + TRUSTME( m_adorned_preds.find(d, adn_pred) ); + unsigned arity = adn_pred->get_arity(); + SASSERT(arity==d.m_pred->get_arity()); + + ptr_vector args; + for(unsigned i=0; iget_domain(i))); + } + + app * lit = m_manager.mk_app(d.m_pred, args.c_ptr()); + app * adn_lit = m_manager.mk_app(adn_pred, args.c_ptr()); + app * mag_lit = create_magic_literal(adn_lit); + + app * tail[] = {lit, mag_lit}; + + rule * r = m_context.get_rule_manager().mk(adn_lit, 2, tail, 0); + m_rules.push_back(r); + } + + rule_set * mk_magic_sets::operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc) { + SASSERT(!mc && !pc); + unsigned init_rule_cnt = source.get_num_rules(); + { + func_decl_set intentional; + for(unsigned i=0; iget_head()->get_decl()); + } + //now we iterate through all predicates and collect the set of extentional ones + const rule_dependencies * deps; + rule_dependencies computed_deps(m_context); + if(source.is_closed()) { + deps = &source.get_dependencies(); + } + else { + computed_deps.populate(source); + deps = &computed_deps; + } + rule_dependencies::iterator it = deps->begin(); + rule_dependencies::iterator end = deps->end(); + for(; it!=end; ++it) { + func_decl * pred = it->m_key; + if(intentional.contains(pred)) { + continue; + } + SASSERT(it->m_value->empty());//extentional predicates have no dependency + m_extentional.insert(pred); + } + } + + SASSERT(m_rules.empty()); + + app * goal_head = m_goal_rule->get_head(); + //adornment goal_adn; + //goal_adn.populate(goal_head, ); + var_idx_set empty_var_idx_set; + adorn_literal(goal_head, empty_var_idx_set); + + while(!m_todo.empty()) { + adornment_desc task = m_todo.back(); + m_todo.pop_back(); + const rule_vector & pred_rules = source.get_predicate_rules(task.m_pred); + rule_vector::const_iterator it = pred_rules.begin(); + rule_vector::const_iterator end = pred_rules.end(); + for(; it!=end; ++it) { + rule * r = *it; + transform_rule(task.m_adornment, r); + } + if(!m_context.get_relation(task.m_pred).empty()) { + //we need a rule to copy facts that are already in a relation into the adorned + //relation (since out intentional predicates can have facts, not only rules) + create_transfer_rule(task); + } + } + + app * adn_goal_head = adorn_literal(goal_head, empty_var_idx_set); + app * mag_goal_head = create_magic_literal(adn_goal_head); + SASSERT(mag_goal_head->is_ground()); + //SASSERT(is_fact(m_manager, mag_goal_head)); + //m_context.add_fact(mag_goal_head); + rule * mag_goal_rule = m_context.get_rule_manager().mk(mag_goal_head, 0, 0, 0); + m_rules.push_back(mag_goal_rule); + + rule * back_to_goal_rule = m_context.get_rule_manager().mk(goal_head, 1, &adn_goal_head, 0); + m_rules.push_back(back_to_goal_rule); + + rule_set * result = static_cast(0); + result = alloc(rule_set, m_context); + unsigned fin_rule_cnt = m_rules.size(); + for(unsigned i=0; iadd_rule(m_rules.get(i)); + } + return result; + } +}; + diff --git a/lib/dl_mk_magic_sets.h b/lib/dl_mk_magic_sets.h new file mode 100644 index 000000000..f98ad55a3 --- /dev/null +++ b/lib/dl_mk_magic_sets.h @@ -0,0 +1,130 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_magic_sets.h + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-10-4. + +Revision History: + +--*/ +#ifndef _DL_MK_MAGIC_SETS_H_ +#define _DL_MK_MAGIC_SETS_H_ + +#include + +#include"map.h" +#include"obj_pair_hashtable.h" + +#include"dl_context.h" +#include"dl_rule_set.h" +#include"dl_rule_transformer.h" + +namespace datalog { + + /** + \brief Implements magic sets rule transformation. + + According to A. Voronkov. Foundations of Deductive Databases. + + The stratified negation is not in the book addressed wrt. magic sets, but it seems + that, for the purpose of magic sets, the negated literals should be treated just as + if they were non-negated (we are interested only in values of arguments, not in the + actual content of relations, at that point). + */ + class mk_magic_sets : public rule_transformer::plugin { + + enum a_flag { + AD_FREE, + AD_BOUND + }; + + struct adornment : public svector { + + void populate(app * lit, const var_idx_set & bound_vars); + + bool operator==(const adornment & o) const { + return vectors_equal(*this, o); + } + std::string to_string() const; + }; + + struct adornment_desc { + func_decl * m_pred; + adornment m_adornment; + + adornment_desc() {} + adornment_desc(func_decl * pred) : m_pred(pred) {} + adornment_desc(func_decl * pred, const adornment & a) + : m_pred(pred), m_adornment(a) {} + + bool operator==(const adornment_desc & o) const { + //m_tail_adornment value is implied by the rule and the head adornment + return m_pred==o.m_pred && m_adornment==o.m_adornment; + } + unsigned hash() const { + return m_pred->hash()^int_vector_hash(m_adornment); + } + }; + + struct adorned_rule { + app * m_head; + adornment m_head_adornment; + ptr_vector m_tail; + }; + + typedef hashtable, + default_eq > adornment_set; + typedef map, + default_eq > adornment_map; + typedef obj_map pred_adornment_map; + typedef obj_map pred2pred; + + context & m_context; + ast_manager & m_manager; + rule_ref_vector m_rules; + ast_ref_vector m_pinned; + rule_ref m_goal_rule; + /** + \brief Predicates from the original set that appear in a head of a rule + */ + func_decl_set m_extentional; + + //adornment_set m_processed; + vector m_todo; + adornment_map m_adorned_preds; + pred_adornment_map m_adornments; + pred2pred m_magic_preds; + + void reset(); + + float get_unbound_cost(app * lit, const var_idx_set & bound_vars); + + int pop_bound(unsigned_vector & cont, rule * r, const var_idx_set & bound_vars); + app * create_magic_literal(app * l); + void create_magic_rules(app * head, unsigned tail_cnt, app * const * tail, bool const* negated); + app * adorn_literal(app * lit, const var_idx_set & bound_vars); + void transform_rule(const adornment & head_adornment, rule * r); + void create_transfer_rule(const adornment_desc & d); + public: + /** + \brief Create magic sets rule transformer for \c goal_rule. When applying the transformer, + the \c goal_rule must be present in the \c rule_set that is being transformed. + */ + mk_magic_sets(context & ctx, rule * goal_rule); + + rule_set * operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc); + }; + +}; + +#endif /* _DL_MK_MAGIC_SETS_H_ */ + diff --git a/lib/dl_mk_partial_equiv.cpp b/lib/dl_mk_partial_equiv.cpp new file mode 100644 index 000000000..68f333b1b --- /dev/null +++ b/lib/dl_mk_partial_equiv.cpp @@ -0,0 +1,153 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + dl_mk_partial_equiv.cpp + +Abstract: + + Rule transformer which identifies predicates that are partial equivalence relations. + +Author: + + Nikolaj Bjorner (nbjorner) 2012-05-14 + +Revision History: + +--*/ + +#include "dl_mk_partial_equiv.h" +#include "ast_pp.h" + +namespace datalog { + + bool mk_partial_equivalence_transformer::is_symmetry(rule const* r) { + func_decl* p = r->get_head()->get_decl(); + return + p->get_arity() == 2 && + p->get_domain(0) == p->get_domain(1) && + r->get_tail_size() == 1 && + r->get_tail(0)->get_decl() == p && + r->get_head()->get_arg(0) == r->get_tail(0)->get_arg(1) && + r->get_head()->get_arg(1) == r->get_tail(0)->get_arg(0) && + is_var(r->get_head()->get_arg(0)) && + is_var(r->get_head()->get_arg(1)) && + r->get_head()->get_arg(0) != r->get_head()->get_arg(1); + } + + + bool mk_partial_equivalence_transformer::is_transitivity(rule const* r) { + func_decl* p = r->get_head()->get_decl(); + if (p->get_arity() != 2 || + p->get_domain(0) != p->get_domain(1) || + r->get_tail_size() != 2 || + r->get_tail(0)->get_decl() != p || + r->get_tail(1)->get_decl() != p) { + return false; + } + app* h = r->get_head(); + app* a = r->get_tail(0); + app* b = r->get_tail(1); + expr* x1 = h->get_arg(0); + expr* x2 = h->get_arg(1); + expr* a1 = a->get_arg(0); + expr* a2 = a->get_arg(1); + expr* b1 = b->get_arg(0); + expr* b2 = b->get_arg(1); + + if (!(is_var(x1) && is_var(x2) && is_var(a1) && is_var(a2) && is_var(b1) && is_var(b2))) { + return false; + } + if (x1 == x2 || a1 == a2 || b1 == b2) { + return false; + } + if (a2 == b1) { + if (x1 == b2 && x2 == a1) { + return true; + } + if (x1 == a1 && x2 == b2) { + return true; + } + return false; + } + if (a1 == b2) { + if (x1 == b1 && x2 == a2) { + return true; + } + if (x1 == a2 && x2 == b1) { + return true; + } + return false; + } + + return false; +; + } + + + rule_set * mk_partial_equivalence_transformer::operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc) { + // TODO mc, pc + + if (source.get_num_rules() == 0) { + return 0; + } + + if (m_context.get_engine() != DATALOG_ENGINE) { + return 0; + } + + relation_manager & rm = m_context.get_rmanager(); + rule_set::decl2rules::iterator it = source.begin_grouped_rules(); + rule_set::decl2rules::iterator end = source.end_grouped_rules(); + + rule_set* res = alloc(rule_set, m_context); + + for (; it != end; ++it) { + func_decl* p = it->m_key; + rule_vector const& rv = *(it->m_value); + bool has_symmetry = false; + bool has_transitivity = false; + unsigned i_symmetry, i_transitivity; + family_id kind = rm.get_requested_predicate_kind(p); + for (unsigned i = 0; i < rv.size(); ++i) { + + if (kind != null_family_id) { + res->add_rule(rv[i]); + } + else if (is_symmetry(rv[i])) { + i_symmetry = i; + has_symmetry = true; + } + else if (is_transitivity(rv[i])) { + i_transitivity = i; + has_transitivity = true; + } + else { + res->add_rule(rv[i]); + } + } + if (has_symmetry && !has_transitivity) { + res->add_rule(rv[i_symmetry]); + } + else if (!has_symmetry && has_transitivity) { + res->add_rule(rv[i_transitivity]); + } + else if (has_symmetry && has_transitivity) { + TRACE("dl", tout << "updating predicate " << mk_pp(p, m) << " to partial equivalence\n";); + SASSERT(kind == null_family_id); + rm.set_predicate_kind(p, rm.get_table_plugin(symbol("equivalence"))->get_kind()); + } + } + + if (res->get_num_rules() == source.get_num_rules()) { + dealloc(res); + return 0; + } + + return res; + } + +}; + + diff --git a/lib/dl_mk_partial_equiv.h b/lib/dl_mk_partial_equiv.h new file mode 100644 index 000000000..8fef4aea9 --- /dev/null +++ b/lib/dl_mk_partial_equiv.h @@ -0,0 +1,50 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + dl_mk_partial_equiv.h + +Abstract: + + Rule transformer which identifies predicates that are partial equivalence relations. + +Author: + + Nikolaj Bjorner (nbjorner) 2012-05-14 + +Revision History: + +--*/ + + +#ifndef _DL_MK_PARTIAL_EQUIVALENCE_TRANSFORMER_H_ +#define _DL_MK_PARTIAL_EQUIVALENCE_TRANSFORMER_H_ + +#include "dl_context.h" +#include "dl_rule_transformer.h" + +namespace datalog { + + class mk_partial_equivalence_transformer : public rule_transformer::plugin { + ast_manager & m; + context & m_context; + public: + mk_partial_equivalence_transformer(context & ctx, unsigned priority=45000) + : plugin(priority), + m(ctx.get_manager()), + m_context(ctx) {} + + rule_set * operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc); + + private: + + bool is_symmetry(rule const* r); + bool is_transitivity(rule const* r); + }; + +}; + +#endif /* _DL_MK_PARTIAL_EQUIV_TRANSFORMER_H_ */ + + diff --git a/lib/dl_mk_rule_inliner.cpp b/lib/dl_mk_rule_inliner.cpp new file mode 100644 index 000000000..5cccb0051 --- /dev/null +++ b/lib/dl_mk_rule_inliner.cpp @@ -0,0 +1,884 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_rule_inliner.cpp + +Abstract: + + Rule transformer which simplifies interpreted tails + +Author: + + Krystof Hoder (t-khoder) 2011-10-01. + +Revision History: + + Added linear_inline 2012-9-10 (nbjorner) + + +Notes: + +Resolution transformation (resolve): + + P(x) :- Q(y), phi(x,y) Q(y) :- R(z), psi(y,z) + -------------------------------------------------- + P(x) :- R(z), phi(x,y), psi(y,z) + + Proof converter: + + replace assumption (*) by rule and upper assumptions. + + +Subsumption transformation (remove rule): + + P(x) :- Q(y), phi(x,y) Rules + --------------------------------- + Rules + + + Model converter: + + P(x) := P(x) or (exists y . Q(y) & phi(x,y)) + + +--*/ + + +#include +#include "ast_pp.h" +#include "dl_finite_product_relation.h" +#include "dl_product_relation.h" +#include "dl_sieve_relation.h" +#include "rewriter.h" +#include "rewriter_def.h" +#include "dl_mk_rule_inliner.h" + +namespace datalog { + + // ----------------------------------- + // + // mk_rule_inliner::rule_unifier + // + // ----------------------------------- + + bool rule_unifier::unify_rules(const rule& tgt, unsigned tgt_idx, const rule& src) { + var_counter& vc = m_rm.get_var_counter(); + unsigned var_cnt = std::max(vc.get_max_var(tgt), vc.get_max_var(src))+1; + m_subst.reset(); + m_subst.reserve(2, var_cnt); + + m_ready = m_unif(tgt.get_tail(tgt_idx), src.get_head(), m_subst); + + if (m_ready) { + m_deltas[0] = 0; + m_deltas[1] = var_cnt; + TRACE("dl", + output_predicate(m_context, src.get_head(), tout << "unify rules "); + output_predicate(m_context, tgt.get_head(), tout << "\n"); + tout << "\n";); + } + return m_ready; + } + + void rule_unifier::apply(app * a, bool is_tgt, app_ref& res) { + expr_ref res_e(m); + TRACE("dl", output_predicate(m_context, a, tout); tout << "\n";); + m_subst.apply(2, m_deltas, expr_offset(a, is_tgt ? 0 : 1), res_e); + SASSERT(is_app(res_e.get())); + res = to_app(res_e.get()); + } + + void rule_unifier::apply( + rule& r, bool is_tgt, unsigned skipped_index, + app_ref_vector& res, svector& res_neg) { + unsigned rule_len = r.get_tail_size(); + for (unsigned i = 0; i < rule_len; i++) { + if (i != skipped_index) { //i can never be UINT_MAX, so we'll never skip if we're not supposed to + app_ref new_tail_el(m); + apply(r.get_tail(i), is_tgt, new_tail_el); + res.push_back(new_tail_el); + res_neg.push_back(r.is_neg_tail(i)); + } + } + } + + bool rule_unifier::apply(rule& tgt, unsigned tail_index, rule& src, rule_ref& res) { + SASSERT(m_ready); + app_ref new_head(m); + app_ref_vector tail(m); + svector tail_neg; + rule_ref simpl_rule(m_rm); + apply(tgt.get_head(), true, new_head); + apply(tgt, true, tail_index, tail, tail_neg); + apply(src, false, UINT_MAX, tail, tail_neg); + mk_rule_inliner::remove_duplicate_tails(tail, tail_neg); + SASSERT(tail.size()==tail_neg.size()); + res = m_rm.mk(new_head, tail.size(), tail.c_ptr(), tail_neg.c_ptr()); + res->set_accounting_parent_object(m_context, &tgt); + res->norm_vars(m_rm); + if (m_context.fix_unbound_vars()) { + m_rm.fix_unbound_vars(res, true); + } + if (m_interp_simplifier.transform_rule(res.get(), simpl_rule)) { + res = simpl_rule; + return true; + } + else { + return false; + } + } + + expr_ref_vector rule_unifier::get_rule_subst(const rule& r, bool is_tgt) { + SASSERT(m_ready); + expr_ref_vector result(m); + sort_ref_vector sorts(m); + expr_ref v(m), w(m); + r.get_vars(sorts); + for (unsigned i = 0; i < sorts.size(); ++i) { + if (!sorts[i].get()) { + sorts[i] = m.mk_bool_sort(); + } + v = m.mk_var(i, sorts[i].get()); + m_subst.apply(2, m_deltas, expr_offset(v, is_tgt?0:1), w); + result.push_back(w); + } + return result; + } + + + // ----------------------------------- + // + // mk_rule_inliner + // + // ----------------------------------- + + /** + Inline occurrences of rule src at tail_index in tgt and return the result in res. + */ + bool mk_rule_inliner::try_to_inline_rule(rule& tgt, rule& src, unsigned tail_index, rule_ref& res) + { + SASSERT(tail_indexdisplay(m_context, tout << "res\n"); + //m_unifier.display(tout << "subst:\n"); + ); + if (m_pc) { + expr_ref_vector s1 = m_unifier.get_rule_subst(tgt, true); + expr_ref_vector s2 = m_unifier.get_rule_subst(src, false); + datalog::resolve_rule(m_pc, tgt, src, tail_index, s1, s2, *res.get()); + } + return true; + } + else { + TRACE("dl", res->display(m_context, tout << "interpreted tail is unsat\n");); + //the interpreted part is unsatisfiable + return false; + } + } + + bool mk_rule_inliner::has_quantifier(rule const& r) const { + unsigned utsz = r.get_uninterpreted_tail_size(); + for (unsigned i = utsz; i < r.get_tail_size(); ++i) { + if (r.get_tail(i)->has_quantifiers()) return true; + } + return false; + } + + void mk_rule_inliner::count_pred_occurrences(rule_set const & orig) + { + m_context.get_rmanager().collect_non_empty_predicates(m_preds_with_facts); + + rule_set::iterator rend = orig.end(); + for (rule_set::iterator rit = orig.begin(); rit!=rend; ++rit) { + rule * r = *rit; + func_decl * head_pred = r->get_decl(); + m_head_pred_ctr.inc(head_pred); + + if (r->get_tail_size()>0) { + m_head_pred_non_empty_tails_ctr.inc(head_pred); + } + + unsigned ut_len = r->get_uninterpreted_tail_size(); + for (unsigned i=0; iget_decl(i); + m_tail_pred_ctr.inc(pred); + + if (r->is_neg_tail(i)) { + m_preds_with_neg_occurrence.insert(pred); + } + } + } + } + + bool mk_rule_inliner::inlining_allowed(func_decl * pred) + { + if (//these three conditions are important for soundness + m_context.is_output_predicate(pred) || + m_preds_with_facts.contains(pred) || + m_preds_with_neg_occurrence.contains(pred) || + //this condition is used for breaking of cycles among inlined rules + m_forbidden_preds.contains(pred)) { + return false; + } + + //these conditions are optional, they avoid possible exponential increase + //in the size of the problem + + return + //m_head_pred_non_empty_tails_ctr.get(pred)<=1 + m_head_pred_ctr.get(pred) <= 1 + || (m_tail_pred_ctr.get(pred) <= 1 && m_head_pred_ctr.get(pred) <= 4) + ; + } + + /** Caller has to dealloc the returned object */ + rule_set * mk_rule_inliner::create_allowed_rule_set(rule_set const & orig) + { + rule_set * res = alloc(rule_set, m_context); + unsigned rcnt = orig.get_num_rules(); + for (unsigned i=0; iget_decl())) { + res->add_rule(r); + } + } + //the rule set should be stratified, since orig (which is its superset) is as well + VERIFY(res->close()); + return res; + } + + /** + Try to make the set of inlined predicates acyclic by forbidding inlining of one + predicate from each strongly connected component. Return true if we did forbide some + predicate, and false if the set of rules is already acyclic. + */ + bool mk_rule_inliner::forbid_preds_from_cycles(rule_set const & r) + { + SASSERT(r.is_closed()); + + bool something_forbidden = false; + + const rule_stratifier::comp_vector& comps = r.get_stratifier().get_strats(); + + rule_stratifier::comp_vector::const_iterator cend = comps.end(); + for (rule_stratifier::comp_vector::const_iterator it = comps.begin(); it!=cend; ++it) { + rule_stratifier::item_set * stratum = *it; + if (stratum->size()==1) { + continue; + } + SASSERT(stratum->size()>1); + func_decl * first_stratum_pred = *stratum->begin(); + + //we're trying to break cycles by removing one predicate from each of them + m_forbidden_preds.insert(first_stratum_pred); + something_forbidden = true; + } + return something_forbidden; + } + + bool mk_rule_inliner::forbid_multiple_multipliers(const rule_set & orig, + rule_set const & proposed_inlined_rules) { + + bool something_forbidden = false; + + const rule_stratifier::comp_vector& comps = + proposed_inlined_rules.get_stratifier().get_strats(); + + rule_stratifier::comp_vector::const_iterator cend = comps.end(); + for (rule_stratifier::comp_vector::const_iterator it = comps.begin(); it!=cend; ++it) { + rule_stratifier::item_set * stratum = *it; + + SASSERT(stratum->size()==1); + func_decl * head_pred = *stratum->begin(); + + bool is_multi_head_pred = m_head_pred_ctr.get(head_pred)>1; + bool is_multi_occurrence_pred = m_tail_pred_ctr.get(head_pred)>1; + + const rule_vector& pred_rules = proposed_inlined_rules.get_predicate_rules(head_pred); + rule_vector::const_iterator iend = pred_rules.end(); + for (rule_vector::const_iterator iit = pred_rules.begin(); iit!=iend; ++iit) { + rule * r = *iit; + + unsigned pt_len = r->get_positive_tail_size(); + for (unsigned ti = 0; tiget_decl(ti); + if (!inlining_allowed(tail_pred)) { + continue; + } + unsigned tail_pred_head_cnt = m_head_pred_ctr.get(tail_pred); + if (tail_pred_head_cnt<=1) { + continue; + } + if (is_multi_head_pred) { + m_forbidden_preds.insert(head_pred); + something_forbidden = true; + goto process_next_pred; + } + if (is_multi_occurrence_pred) { + m_forbidden_preds.insert(tail_pred); + something_forbidden = true; + } + else { + is_multi_head_pred = true; + m_head_pred_ctr.get(head_pred) = + m_head_pred_ctr.get(head_pred)*tail_pred_head_cnt; + } + } + + } + + process_next_pred:; + } + + + unsigned rule_cnt = orig.get_num_rules(); + for (unsigned ri=0; riget_decl(); + + if (inlining_allowed(head_pred)) { + //we have already processed inlined rules + continue; + } + + bool has_multi_head_pred = false; + unsigned pt_len = r->get_positive_tail_size(); + for (unsigned ti = 0; tiget_decl(ti); + if (!inlining_allowed(pred)) { + continue; + } + if (m_head_pred_ctr.get(pred)<=1) { + continue; + } + if (has_multi_head_pred) { + m_forbidden_preds.insert(pred); + something_forbidden = true; + } + else { + has_multi_head_pred = true; + } + } + } + return something_forbidden; + } + + void mk_rule_inliner::plan_inlining(rule_set const & orig) + { + count_pred_occurrences(orig); + + scoped_ptr candidate_inlined_set = create_allowed_rule_set(orig); + while (forbid_preds_from_cycles(*candidate_inlined_set)) { + candidate_inlined_set = create_allowed_rule_set(orig); + } + + if (forbid_multiple_multipliers(orig, *candidate_inlined_set)) { + candidate_inlined_set = create_allowed_rule_set(orig); + } + + TRACE("dl", tout<<"rules to be inlined:\n" << (*candidate_inlined_set); ); + + // now we start filling in the set of the inlined rules in a topological order, + // so that we inline rules into other rules + + SASSERT(m_inlined_rules.get_num_rules()==0); + + const rule_stratifier::comp_vector& comps = candidate_inlined_set->get_stratifier().get_strats(); + + rule_stratifier::comp_vector::const_iterator cend = comps.end(); + for (rule_stratifier::comp_vector::const_iterator it = comps.begin(); it!=cend; ++it) { + rule_stratifier::item_set * stratum = *it; + SASSERT(stratum->size()==1); + func_decl * pred = *stratum->begin(); + + const rule_vector& pred_rules = candidate_inlined_set->get_predicate_rules(pred); + rule_vector::const_iterator iend = pred_rules.end(); + for (rule_vector::const_iterator iit = pred_rules.begin(); iit!=iend; ++iit) { + transform_rule(*iit, m_inlined_rules); + } + } + + TRACE("dl", tout << "inlined rules after mutual inlining:\n" << m_inlined_rules; ); + } + + bool mk_rule_inliner::transform_rule(rule * r0, rule_set& tgt) { + bool modified = false; + rule_ref_vector todo(m_rm); + + todo.push_back(r0); + + + + while (!todo.empty()) { + rule_ref r(todo.back(), m_rm); + todo.pop_back(); + unsigned pt_len = r->get_positive_tail_size(); + + unsigned i = 0; + + for (; i < pt_len && !inlining_allowed(r->get_decl(i)); ++i) {}; + + if (has_quantifier(*r.get())) { + continue; + } + + if (i == pt_len) { + //there's nothing we can inline in this rule + tgt.add_rule(r); + continue; + } + modified = true; + + func_decl * pred = r->get_decl(i); + const rule_vector& pred_rules = m_inlined_rules.get_predicate_rules(pred); + rule_vector::const_iterator iend = pred_rules.end(); + for (rule_vector::const_iterator iit = pred_rules.begin(); iit!=iend; ++iit) { + rule * inl_rule = *iit; + rule_ref inl_result(m_rm); + if (try_to_inline_rule(*r.get(), *inl_rule, i, inl_result)) { + todo.push_back(inl_result); + } + } + } + return modified; + } + + bool mk_rule_inliner::transform_rules(const rule_set & orig, rule_set & tgt) { + + bool something_done = false; + + rule_set::iterator rend = orig.end(); + for (rule_set::iterator rit = orig.begin(); rit!=rend; ++rit) { + rule_ref r(*rit, m_rm); + func_decl * pred = r->get_decl(); + + // if inlining is allowed, then we are eliminating + // this relation through inlining, + // so we don't add its rules to the result + + something_done |= !inlining_allowed(pred) && transform_rule(r, tgt); + } + + return something_done; + } + + /** + Check whether rule r is oriented in a particular ordering. + This is to avoid infinite cycle of inlining in the eager inliner. + + Out ordering is lexicographic, comparing atoms first on stratum they are in, + then on arity and then on ast ID of their func_decl. + */ + bool mk_rule_inliner::is_oriented_rewriter(rule * r, rule_stratifier const& strat) { + func_decl * head_pred = r->get_decl(); + unsigned head_strat = strat.get_predicate_strat(head_pred); + + unsigned head_arity = head_pred->get_arity(); + + //var_idx_set head_vars; + //var_idx_set same_strat_vars; + //collect_vars(m, r->get_head(), head_vars); + + unsigned pt_len = r->get_positive_tail_size(); + for (unsigned ti=0; tiget_decl(ti); + + unsigned pred_strat = strat.get_predicate_strat(pred); + SASSERT(pred_strat<=head_strat); + + if (pred_strat==head_strat) { + //collect_vars(m, r->get_head(), same_strat_vars); + if (pred->get_arity()>head_arity + || (pred->get_arity()==head_arity && pred->get_id()>=head_pred->get_id()) ) { + return false; + } + } + } + return true; + } + + + bool mk_rule_inliner::do_eager_inlining(rule * r, rule_set const& rules, rule_ref& res) { + + SASSERT(rules.is_closed()); + const rule_stratifier& strat = rules.get_stratifier(); + + func_decl * head_pred = r->get_decl(); + + unsigned pt_len = r->get_positive_tail_size(); + for (unsigned ti = 0; ti < pt_len; ++ti) { + + func_decl * pred = r->get_decl(ti); + if (pred == head_pred || m_preds_with_facts.contains(pred)) { continue; } + + + const rule_vector& pred_rules = rules.get_predicate_rules(pred); + rule * inlining_candidate = 0; + unsigned rule_cnt = pred_rules.size(); + if (rule_cnt == 0) { + inlining_candidate = 0; + } + else if (rule_cnt == 1) { + inlining_candidate = pred_rules[0]; + } + else { + inlining_candidate = 0; + + for (unsigned ri = 0; ri < rule_cnt; ++ri) { + rule * pred_rule = pred_rules[ri]; + if (!m_unifier.unify_rules(*r, ti, *pred_rule)) { + //we skip rules which don't unify with the tail atom + continue; + } + if (inlining_candidate != 0) { + // We have two rules that can be inlined into the current + // tail predicate. In this situation we don't do inlinning + // on this tail atom, as we don't want the overall number + // of rules to increase. + goto process_next_tail; + } + inlining_candidate = pred_rule; + } + } + if (inlining_candidate == 0) { + // nothing unifies with the tail atom, therefore the rule is unsatisfiable + // (we can say this because relation pred doesn't have any ground facts either) + res = 0; + datalog::del_rule(m_mc, *r); + return true; + } + if (!is_oriented_rewriter(inlining_candidate, strat)) { + // The rule which should be used for inlining isn't oriented + // in a simplifying direction. Inlining with such rule might lead to + // infinite loops, so we don't do it. + goto process_next_tail; + } + if (!try_to_inline_rule(*r, *inlining_candidate, ti, res)) { + datalog::del_rule(m_mc, *r); + res = 0; + } + return true; + + process_next_tail:; + } + return false; + } + + bool mk_rule_inliner::do_eager_inlining(scoped_ptr & rules) { + scoped_ptr res = alloc(rule_set, m_context); + bool done_something = false; + + rule_set::iterator rend = rules->end(); + for (rule_set::iterator rit = rules->begin(); rit!=rend; ++rit) { + rule_ref r(*rit, m_rm); + + rule_ref replacement(m_rm); + while (r && do_eager_inlining(r, *rules, replacement)) { + r = replacement; + done_something = true; + } + + if (!r) { + continue; + } + res->add_rule(r); + } + if (done_something) { + rules = res.detach(); + } + return done_something; + } + + /** + Inline predicates that are known to not be join-points. + + P(1,x) :- P(0,y), phi(x,y) + P(0,x) :- P(1,z), psi(x,z) + + -> + + P(1,x) :- P(1,z), phi(x,y), psi(y,z) + + whenever P(0,x) is not unifiable with the + body of the rule where it appears (P(1,z)) + and P(0,x) is unifiable with at most one (?) + other rule (and it does not occur negatively). + */ + bool mk_rule_inliner::visitor::operator()(expr* e) { + m_unifiers.append(m_positions.find(e)); + TRACE("dl", + tout << "unifier: " << (m_unifiers.empty()?0:m_unifiers.back()); + tout << " num unifiers: " << m_unifiers.size(); + tout << " num positions: " << m_positions.find(e).size() << "\n"; + output_predicate(m_context, to_app(e), tout); tout << "\n";); + return true; + } + + void mk_rule_inliner::visitor::reset(unsigned sz) { + m_unifiers.reset(); + m_can_remove.reset(); + m_can_remove.resize(sz, true); + m_can_expand.reset(); + m_can_expand.resize(sz, true); + m_positions.reset(); + } + + unsigned_vector const& mk_rule_inliner::visitor::add_position(expr* e, unsigned j) { + obj_map::obj_map_entry * et = m_positions.insert_if_not_there2(e, unsigned_vector()); + et->get_data().m_value.push_back(j); + return et->get_data().m_value; + } + + unsigned_vector const& mk_rule_inliner::visitor::del_position(expr* e, unsigned j) { + obj_map::obj_map_entry * et = m_positions.find_core(e); + SASSERT(et && et->get_data().m_value.contains(j)); + et->get_data().m_value.erase(j); + return et->get_data().m_value; + } + + void mk_rule_inliner::add_rule(rule* r, unsigned i) { + svector& can_remove = m_head_visitor.can_remove(); + svector& can_expand = m_head_visitor.can_expand(); + app* head = r->get_head(); + func_decl* headd = head->get_decl(); + m_head_visitor.add_position(head, i); + m_head_index.insert(head); + m_pinned.push_back(r); + + if (m_context.is_output_predicate(headd) || + m_preds_with_facts.contains(headd)) { + can_remove.set(i, false); + TRACE("dl", output_predicate(m_context, head, tout << "cannot remove: " << i << " "); tout << "\n";); + } + + unsigned tl_sz = r->get_uninterpreted_tail_size(); + for (unsigned j = 0; j < tl_sz; ++j) { + app* tail = r->get_tail(j); + m_tail_visitor.add_position(tail, i); + m_tail_index.insert(tail); + } + bool can_exp = + tl_sz == 1 + && r->get_positive_tail_size() == 1 + && !m_preds_with_facts.contains(r->get_decl(0)) + && !m_context.is_output_predicate(r->get_decl(0)); + can_expand.set(i, can_exp); + } + + void mk_rule_inliner::del_rule(rule* r, unsigned i) { + app* head = r->get_head(); + m_head_visitor.del_position(head, i); + unsigned tl_sz = r->get_uninterpreted_tail_size(); + for (unsigned j = 0; j < tl_sz; ++j) { + app* tail = r->get_tail(j); + m_tail_visitor.del_position(tail, i); + } + } + + +#define PRT(_x_) ((_x_)?"T":"F") + + bool mk_rule_inliner::inline_linear(scoped_ptr& rules) { + scoped_ptr res = alloc(rule_set, m_context); + bool done_something = false; + unsigned sz = rules->get_num_rules(); + + m_head_visitor.reset(sz); + m_tail_visitor.reset(sz); + m_head_index.reset(); + m_tail_index.reset(); + + TRACE("dl", rules->display(tout);); + + rule_ref_vector acc(m_rm); + for (unsigned i = 0; i < sz; ++i) { + acc.push_back(rules->get_rule(i)); + } + + // set up unification index. + svector& can_remove = m_head_visitor.can_remove(); + svector& can_expand = m_head_visitor.can_expand(); + + for (unsigned i = 0; i < sz; ++i) { + add_rule(acc[i].get(), i); + } + + // initialize substitution. + var_counter& vc = m_rm.get_var_counter(); + unsigned max_var = 0; + for (unsigned i = 0; i < sz; ++i) { + rule* r = acc[i].get(); + max_var = std::max(max_var, vc.get_max_var(r->get_head())); + unsigned tl_sz = r->get_uninterpreted_tail_size(); + for (unsigned j = 0; j < tl_sz; ++j) { + max_var = std::max(max_var, vc.get_max_var(r->get_tail(j))); + } + } + m_subst.reset(); + m_subst.reserve_vars(max_var+1); + m_subst.reserve_offsets(std::max(m_tail_index.get_approx_num_regs(), m_head_index.get_approx_num_regs())); + + svector valid; + valid.reset(); + valid.resize(sz, true); + + params_ref const& params = m_context.get_params(); + bool allow_branching = params.get_bool(":inline-linear-branch", false); + + for (unsigned i = 0; i < sz; ++i) { + + while (true) { + + rule_ref r(acc[i].get(), m_rm); + + TRACE("dl", r->display(m_context, tout << "processing: " << i << "\n");); + + if (!valid.get(i)) { + TRACE("dl", tout << "invalid: " << i << "\n";); + break; + } + if (!can_expand.get(i)) { + TRACE("dl", tout << "cannot expand: " << i << "\n";); + break; + } + + m_head_visitor.reset(); + m_head_index.unify(r->get_tail(0), m_head_visitor); + unsigned num_head_unifiers = m_head_visitor.get_unifiers().size(); + if (num_head_unifiers != 1) { + TRACE("dl", tout << "no unique unifier " << num_head_unifiers << "\n";); + break; + } + unsigned j = m_head_visitor.get_unifiers()[0]; + if (!can_remove.get(j) || !valid.get(j) || i == j) { + TRACE("dl", tout << PRT(can_remove.get(j)) << " " << PRT(valid.get(j)) << " " << PRT(i != j) << "\n";); + break; + } + + rule* r2 = acc[j].get(); + + // check that the head of r2 only unifies with this single body position. + TRACE("dl", output_predicate(m_context, r2->get_head(), tout << "unify head: "); tout << "\n";); + m_tail_visitor.reset(); + m_tail_index.unify(r2->get_head(), m_tail_visitor); + unsigned_vector const& tail_unifiers = m_tail_visitor.get_unifiers(); + unsigned num_tail_unifiers = tail_unifiers.size(); + SASSERT(!tail_unifiers.empty()); + if (!allow_branching && num_tail_unifiers != 1) { + TRACE("dl", tout << "too many tails " << num_tail_unifiers << "\n";); + break; + } + + rule_ref rl_res(m_rm); + if (!try_to_inline_rule(*r.get(), *r2, 0, rl_res)) { + TRACE("dl", r->display(m_context, tout << "inlining failed\n"); r2->display(m_context, tout); ); + break; + } + done_something = true; + TRACE("dl", r->display(m_context, tout); r2->display(m_context, tout); rl_res->display(m_context, tout); ); + + del_rule(r, i); + add_rule(rl_res.get(), i); + + + r = rl_res; + acc[i] = r.get(); + can_expand.set(i, can_expand.get(j)); + + if (num_tail_unifiers == 1) { + TRACE("dl", tout << "setting invalid: " << j << "\n";); + valid.set(j, false); + datalog::del_rule(m_mc, *r2); + del_rule(r2, j); + } + + max_var = std::max(max_var, vc.get_max_var(*r.get())); + m_subst.reserve_vars(max_var+1); + + } + } + if (done_something) { + rules = alloc(rule_set, m_context); + for (unsigned i = 0; i < sz; ++i) { + if (valid.get(i)) { + rules->add_rule(acc[i].get()); + } + } + TRACE("dl", rules->display(tout);); + } + return done_something; + } + + rule_set * mk_rule_inliner::operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc) { + + bool something_done = false; + ref hsmc; + ref hpc; + + if (source.get_num_rules() == 0) { + return 0; + } + + if (mc) { + hsmc = alloc(horn_subsume_model_converter, m); + } + if (pc) { + hpc = alloc(replace_proof_converter, m); + } + m_mc = hsmc.get(); + m_pc = hpc.get(); + + plan_inlining(source); + + scoped_ptr res = alloc(rule_set, m_context); + + something_done = transform_rules(source, *res); + + VERIFY(res->close()); //this transformation doesn't break the negation stratification + + // try eager inlining + if (do_eager_inlining(res)) { + something_done = true; + } + + params_ref const& params = m_context.get_params(); + if (params.get_bool(":inline-linear", true) && inline_linear(res)) { + something_done = true; + } + + if (!something_done) { + res = 0; + } + else { + if (mc) { + mc = concat(mc.get(), hsmc.get()); + } + if (pc) { + pc = concat(pc.get(), hpc.get()); + } + } + + return res.detach(); + } + +}; + diff --git a/lib/dl_mk_rule_inliner.h b/lib/dl_mk_rule_inliner.h new file mode 100644 index 000000000..24cab4352 --- /dev/null +++ b/lib/dl_mk_rule_inliner.h @@ -0,0 +1,198 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_interp_tail_simplifier.h + +Abstract: + + Rule transformer which inlines some of the rules + +Author: + + Krystof Hoder (t-khoder) 2011-10-02. + +Revision History: + +--*/ + +#ifndef _DL_MK_RULE_INLINER_H_ +#define _DL_MK_RULE_INLINER_H_ + +#include "dl_context.h" +#include "dl_rule_transformer.h" +#include "dl_mk_interp_tail_simplifier.h" +#include "unifier.h" +#include "substitution.h" +#include "substitution_tree.h" + +namespace datalog { + + class rule_unifier { + ast_manager& m; + rule_manager& m_rm; + context& m_context; + /** We use this simplifier after inlining to get nicer intermediate rules */ + mk_interp_tail_simplifier m_interp_simplifier; + substitution m_subst; + unifier m_unif; + bool m_ready; + unsigned m_deltas[2]; + public: + rule_unifier(rule_manager& rm, context& ctx, ast_manager& m) + : m(m), m_rm(rm), m_context(ctx), m_interp_simplifier(ctx), m_subst(m), m_unif(m), m_ready(false) {} + + /** Reset subtitution and unify tail tgt_idx of the target rule and the head of the src rule */ + bool unify_rules(const rule& tgt, unsigned tgt_idx, const rule& src); + + /** + \brief Apply unifier to rules. + Return false if the resulting rule is a tautology (the interpreted tail is unsat). + */ + bool apply(rule& tgt, unsigned tgt_idx, rule& src, rule_ref& result); + + void display(std::ostream& stm) { m_subst.display(stm, 2, m_deltas); } + + /** + Retrieve substitutions for src/tgt. (second argument of unify_rules). + */ + expr_ref_vector get_rule_subst(const rule& r, bool is_tgt); + + private: + void apply(app * a, bool is_tgt, app_ref& res); + + /** + Apply substitution to a rule tail. Tail with skipped_index is skipped, + unless skipped_index is equal to UINT_MAX + */ + void apply(rule& r, bool is_tgt, unsigned skipped_index, app_ref_vector& res, + svector& res_neg); + + }; + + class mk_rule_inliner : public rule_transformer::plugin { + + class visitor : public st_visitor { + context& m_context; + unsigned_vector m_unifiers; + svector m_can_remove, m_can_expand; + obj_map m_positions; + public: + visitor(context& c, substitution & s): st_visitor(s), m_context(c) {} + virtual bool operator()(expr* e); + void reset() { m_unifiers.reset(); } + void reset(unsigned sz); + svector& can_remove() { return m_can_remove; } + svector& can_expand() { return m_can_expand; } + unsigned_vector const& add_position(expr* e, unsigned j); + unsigned_vector const& del_position(expr* e, unsigned j); + unsigned_vector const& get_unifiers() { return m_unifiers; } + }; + + typedef obj_map decl_map; + + ast_manager & m; + rule_manager & m_rm; + context & m_context; + th_rewriter& m_simp; + rule_ref_vector m_pinned; + decl_set m_forbidden_preds; + decl_set m_preds_with_facts; + decl_set m_preds_with_neg_occurrence; + ast_counter m_head_pred_ctr; + ast_counter m_head_pred_non_empty_tails_ctr; + ast_counter m_tail_pred_ctr; + rule_set m_inlined_rules; + horn_subsume_model_converter* m_mc; + replace_proof_converter* m_pc; + + + //used in try_to_inline_rule and do_eager_inlining + rule_unifier m_unifier; + + substitution_tree m_head_index; // for straight-line relation inlining. + substitution_tree m_tail_index; + substitution m_subst; + visitor m_head_visitor; + visitor m_tail_visitor; + + bool tail_matches_head(app * tail, app* head); + + bool try_to_inline_rule(rule& tgt, rule& src, unsigned tail_index, rule_ref& res); + + bool inlining_allowed(func_decl * pred); + + void count_pred_occurrences(rule_set const & orig); + + void plan_inlining(rule_set const & orig); + + rule_set * create_allowed_rule_set(rule_set const & orig); + + bool forbid_preds_from_cycles(rule_set const & r); + + /** Ensure we don't inline two multi-head rules that would appear together in some tail */ + bool forbid_multiple_multipliers(const rule_set & orig, rule_set const & proposed_inlined_rules); + + /** Return true if the rule was modified */ + bool transform_rule(rule * r, rule_set& tgt); + + /** Return true if some transformation was performed */ + bool transform_rules(const rule_set & orig, rule_set & tgt); + + bool is_oriented_rewriter(rule * r, rule_stratifier const& strat); + + /** + Return false if nothing was done with the rule. + res may be set to zero if we managed to prove the rule unsatisfiable. + */ + bool do_eager_inlining(rule * r, rule_set const& rules, rule_ref& res); + + + /** + Inline rules even if it doesn't lead to elimination of the whole predicate. + + The inlining is done as long as it doesn't increase the number of rules + (i.e. when only one rule defining a predicate can replace tail atom). + + The original rule-set must be closed before passing t this function + */ + bool do_eager_inlining(scoped_ptr & rules); + + bool has_quantifier(rule const& r) const; + + /** + Inline predicates that are known to not be join-points. + */ + bool inline_linear(scoped_ptr& rules); + + void add_rule(rule* r, unsigned i); + void del_rule(rule* r, unsigned i); + + public: + mk_rule_inliner(context & ctx, unsigned priority=35000) + : plugin(priority), + m(ctx.get_manager()), + m_rm(ctx.get_rule_manager()), + m_context(ctx), + m_simp(m_context.get_rewriter()), + m_pinned(m_rm), + m_inlined_rules(m_context), + m_unifier(ctx.get_rule_manager(), ctx, m), + m_mc(0), + m_pc(0), + m_head_index(m), + m_tail_index(m), + m_subst(m), + m_head_visitor(ctx, m_subst), + m_tail_visitor(ctx, m_subst) + {} + virtual ~mk_rule_inliner() { } + + rule_set * operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc); + }; + +}; + +#endif /* _DL_MK_INTERP_TAIL_SIMPLIFIER_H_ */ + diff --git a/lib/dl_mk_similarity_compressor.cpp b/lib/dl_mk_similarity_compressor.cpp new file mode 100644 index 000000000..8225aa315 --- /dev/null +++ b/lib/dl_mk_similarity_compressor.cpp @@ -0,0 +1,537 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_similarity_compressor.cpp + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-10-22. + +Revision History: + +--*/ + +#include +#include +#include"dl_mk_similarity_compressor.h" + +namespace datalog { + + mk_similarity_compressor::mk_similarity_compressor(context & ctx, unsigned threshold_count) : + plugin(5000), + m_context(ctx), + m_manager(ctx.get_manager()), + m_threshold_count(threshold_count), + m_result_rules(ctx.get_rule_manager()), + m_pinned(m_manager) { + SASSERT(threshold_count>1); + } + + void mk_similarity_compressor::reset() { + m_rules.reset(); + m_result_rules.reset(); + m_pinned.reset(); + } + + /** + Allows to traverse head and positive tails in a single for loop starting from -1 + */ + app * get_by_tail_index(rule * r, int idx) { + if(idx==-1) { + return r->get_head(); + } + SASSERT(idx(r->get_positive_tail_size())); + return r->get_tail(idx); + } + + template + int aux_compare(T a, T b) { + return (a>b) ? 1 : ( (a==b) ? 0 : -1); + } + + int compare_var_args(app* t1, app* t2) { + SASSERT(t1->get_num_args()==t2->get_num_args()); + int res; + unsigned n = t1->get_num_args(); + for(unsigned i=0; iget_arg(i); + expr * a2 = t2->get_arg(i); + + res = aux_compare(is_var(a1), is_var(a2)); + if(res!=0) { return res; } + if(is_var(a1)) { + res = aux_compare(to_var(a1)->get_idx(), to_var(a2)->get_idx()); + if(res!=0) { return res; } + } + } + return 0; + } + + int compare_args(app* t1, app* t2, int & skip_countdown) { + SASSERT(t1->get_num_args()==t2->get_num_args()); + int res; + unsigned n = t1->get_num_args(); + for(unsigned i=0; iget_arg(i))) { + SASSERT(t1->get_arg(i)==t2->get_arg(i)); + continue; + } + if((skip_countdown--)==0) { + continue; + } + res = aux_compare(t1->get_arg(i), t2->get_arg(i)); + if(res!=0) { return res; } + } + return 0; + } + + /** + \brief Return 0 if r1 and r2 could be similar. If the rough similarity + equaivelance class of r1 is greater than the one of r2, return 1; otherwise return -1. + + Two rules are in the same rough similarity class if they differ only in constant arguments + of positive uninterpreted predicates. + */ + int rough_compare(rule * r1, rule * r2) { + int res = aux_compare(r1->get_tail_size(), r2->get_tail_size()); + if(res!=0) { return res; } + res = aux_compare(r1->get_uninterpreted_tail_size(), r2->get_uninterpreted_tail_size()); + if(res!=0) { return res; } + res = aux_compare(r1->get_positive_tail_size(), r2->get_positive_tail_size()); + if(res!=0) { return res; } + + int pos_tail_sz = r1->get_positive_tail_size(); + for(int i=-1; iget_decl(), t2->get_decl()); + if(res!=0) { return res; } + res = compare_var_args(t1, t2); + if(res!=0) { return res; } + } + + unsigned tail_sz = r1->get_tail_size(); + for(unsigned i=pos_tail_sz; iget_tail(i), r2->get_tail(i)); + if(res!=0) { return res; } + } + + return 0; + } + + /** + \c r1 and \c r2 must be equal according to the \c rough_compare function for this function + to be called. + */ + int total_compare(rule * r1, rule * r2, int skipped_arg_index = INT_MAX) { + SASSERT(rough_compare(r1, r2)==0); + int pos_tail_sz = r1->get_positive_tail_size(); + for(int i=-1; i info_vector; + + void collect_const_indexes(app * t, int tail_index, info_vector & res) { + unsigned n = t->get_num_args(); + for(unsigned i=0; iget_arg(i))) { + continue; + } + res.push_back(const_info(tail_index, i)); + } + } + + void collect_const_indexes(rule * r, info_vector & res) { + collect_const_indexes(r->get_head(), -1, res); + unsigned pos_tail_sz = r->get_positive_tail_size(); + for(unsigned i=0; iget_tail(i), i, res); + } + } + + template + void collect_orphan_consts(rule * r, const info_vector & const_infos, T & tgt) { + unsigned const_cnt = const_infos.size(); + tgt.reset(); + for(unsigned i=0; iget_arg(inf.arg_index()))); + SASSERT(tgt.back()->get_num_args()==0); + } + } + template + void collect_orphan_sorts(rule * r, const info_vector & const_infos, T & tgt) { + unsigned const_cnt = const_infos.size(); + tgt.reset(); + for(unsigned i=0; iget_decl()->get_domain(inf.arg_index())); + } + } + + /** + \brief From the \c tail_indexes and \c arg_indexes remove elements corresponding to constants + that are the same in rules \c *first ... \c *(after_last-1). + */ + void remove_stable_constants(rule_vector::iterator first, rule_vector::iterator after_last, + info_vector & const_infos) { + SASSERT(after_last-first>1); + unsigned const_cnt = const_infos.size(); + ptr_vector vals; + rule * r = *(first++); + collect_orphan_consts(r, const_infos, vals); + SASSERT(vals.size()==const_cnt); + rule_vector::iterator it = first; + for(; it!=after_last; ++it) { + for(unsigned i=0; iget_arg(const_infos[i].arg_index())); + if(vals[i]!=val) { + vals[i] = 0; + } + } + } + unsigned removed_cnt = 0; + for(unsigned i=0; i vals; + ptr_vector sorts; + rule * r = *(first++); + collect_orphan_consts(r, const_infos, vals); + collect_orphan_sorts(r, const_infos, sorts); + SASSERT(vals.size()==const_cnt); + vector possible_parents(const_cnt); + for(unsigned i=1; iget_head()->get_num_args() - count_variable_arguments(r->get_head()); + unsigned pos_tail_sz = r->get_positive_tail_size(); + for(unsigned i=0; iget_tail(i)->get_num_args() - count_variable_arguments(r->get_tail(i)); + } + return res; + } + + bool initial_comparator(rule * r1, rule * r2) { + int res = rough_compare(r1, r2); + if(res!=0) { return res>0; } + return total_compare(r1, r2)>0; + } + + class arg_ignoring_comparator { + unsigned m_ignored_index; + public: + arg_ignoring_comparator(unsigned ignored_index) : m_ignored_index(ignored_index) {} + bool operator()(rule * r1, rule * r2) const { + return total_compare(r1, r2, m_ignored_index)>0; + } + bool eq(rule * r1, rule * r2) const { + return total_compare(r1, r2, m_ignored_index)==0; + } + }; + + void mk_similarity_compressor::merge_class(rule_vector::iterator first, + rule_vector::iterator after_last) { + SASSERT(after_last-first>1); + info_vector const_infos; + rule * r = *first; //an arbitrary representative of the class + collect_const_indexes(r, const_infos); + remove_stable_constants(first, after_last, const_infos); + + unsigned const_cnt = const_infos.size(); + SASSERT(const_cnt>0); + + detect_equal_constants(first, after_last, const_infos); + + + //The aux relation contains column for each constant which does not have an earlier constant + //that it is equal to (i.e. only has no parent) + ptr_vector aux_domain; + collect_orphan_sorts(r, const_infos, aux_domain); + + func_decl* head_pred = r->get_head()->get_decl(); + symbol const& name_prefix = head_pred->get_name(); + std::string name_suffix = "sc_" + to_string(const_cnt); + func_decl * aux_pred = m_context.mk_fresh_head_predicate(name_prefix, symbol(name_suffix.c_str()), + aux_domain.size(), aux_domain.c_ptr(), head_pred); + m_pinned.push_back(aux_pred); + + relation_fact val_fact(m_manager, const_cnt); + rule_vector::iterator it = first; + for(; it!=after_last; ++it) { + collect_orphan_consts(*it, const_infos, val_fact); + m_context.add_fact(aux_pred, val_fact); + } + m_context.get_rmanager().mark_saturated(aux_pred); + + app * new_head = r->get_head(); + ptr_vector new_tail; + svector new_negs; + unsigned tail_sz = r->get_tail_size(); + for(unsigned i=0; iget_tail(i)); + new_negs.push_back(r->is_neg_tail(i)); + } + + var_counter var_ctr; + var_ctr.count_vars(m_manager, r); + unsigned max_var_idx, new_var_idx_base; + if(var_ctr.get_max_positive(max_var_idx)) { + new_var_idx_base = max_var_idx+1; + } + else { + new_var_idx_base = 0; + } + + ptr_vector const_vars; //variables at indexes of their corresponding constants + expr_ref_vector aux_vars(m_manager); //variables as arguments for the auxiliary predicate + + unsigned aux_column_index = 0; + + for(unsigned i=0; i mod_args(mod_tail->get_num_args(), mod_tail->get_args()); + + for(; iget_decl(), mod_args.c_ptr()); + m_pinned.push_back(upd_tail); + mod_tail = upd_tail; + } + + app_ref aux_tail(m_manager.mk_app(aux_pred, aux_vars.c_ptr()), m_manager); + new_tail.push_back(aux_tail); + new_negs.push_back(false); + + rule * new_rule = m_context.get_rule_manager().mk(new_head, new_tail.size(), new_tail.c_ptr(), + new_negs.c_ptr()); + m_result_rules.push_back(new_rule); + + //TODO: allow for a rule to have multiple parent objects + new_rule->set_accounting_parent_object(m_context, r); + m_modified = true; + } + + void mk_similarity_compressor::process_class(rule_vector::iterator first, + rule_vector::iterator after_last) { + SASSERT(first!=after_last); + //remove duplicates + { + rule_vector::iterator it = first; + rule_vector::iterator prev = it; + ++it; + while(it!=after_last) { + if(it!=after_last && total_compare(*prev, *it)==0) { + --after_last; + std::swap(*it, *after_last); + m_modified = true; + } + else { + prev = it; + ++it; + } + } + } + SASSERT(first!=after_last); + + unsigned const_cnt = get_constant_count(*first); +#if 0 + for(unsigned ignored_index=0; ignored_indexm_threshold_count) { + merge_class(grp_begin, it); + //group was processed, so we remove it from the class + if(it==after_last) { + after_last=grp_begin; + it=after_last; + } + else { + while(it!=grp_begin) { + std::swap(*--it, *--after_last); + } + } + } + grp_begin = it; + grp_size = 0; + } + } + } +#endif + //TODO: compress also rules with pairs (or tuples) of equal constants + +#if 1 + if(const_cnt>0) { + unsigned rule_cnt = static_cast(after_last-first); + if(rule_cnt>m_threshold_count) { + merge_class(first, after_last); + return; + } + } +#endif + + //put rules which weren't merged into result + rule_vector::iterator it = first; + for(; it!=after_last; ++it) { + m_result_rules.push_back(*it); + } + } + + rule_set * mk_similarity_compressor::operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc) { + // TODO mc, pc + m_modified = false; + unsigned init_rule_cnt = source.get_num_rules(); + SASSERT(m_rules.empty()); + for(unsigned i=0; i(0); + if(m_modified) { + result = alloc(rule_set, m_context); + unsigned fin_rule_cnt = m_result_rules.size(); + for(unsigned i=0; iadd_rule(m_result_rules.get(i)); + } + } + reset(); + return result; + } +}; diff --git a/lib/dl_mk_similarity_compressor.h b/lib/dl_mk_similarity_compressor.h new file mode 100644 index 000000000..ad4a5e246 --- /dev/null +++ b/lib/dl_mk_similarity_compressor.h @@ -0,0 +1,78 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_similarity_compressor.h + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-10-22. + +Revision History: + +--*/ +#ifndef _DL_MK_SIMILARITY_COMPRESSOR_H_ +#define _DL_MK_SIMILARITY_COMPRESSOR_H_ + +#include + +#include"map.h" +#include"obj_pair_hashtable.h" + +#include"dl_context.h" +#include"dl_rule_set.h" +#include"dl_rule_transformer.h" + +namespace datalog { + + /** + \brief Functor for merging groups of similar rules. + + A rule sequence + + P("1",x):-Q(x). + ... + P("N",x):-Q(x). + + will be replaced by + + P(y,x):-Q(x), Aux(y). + + and a set of facts + + Aux("1"). + ... + Aux("N"). + + Similar transformation is performed when the varying constant appears in the positive tail. + */ + class mk_similarity_compressor : public rule_transformer::plugin { + + context & m_context; + ast_manager & m_manager; + /** number of similar rules necessary for a group to be introduced */ + unsigned m_threshold_count; + rule_vector m_rules; + rule_ref_vector m_result_rules; + bool m_modified; + ast_ref_vector m_pinned; + + void merge_class(rule_vector::iterator first, rule_vector::iterator after_last); + void process_class(rule_vector::iterator first, rule_vector::iterator after_last); + + void reset(); + public: + mk_similarity_compressor(context & ctx, unsigned threshold_count); + + rule_set * operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc); + }; + +}; + +#endif /* _DL_MK_SIMILARITY_COMPRESSOR_H_ */ + diff --git a/lib/dl_mk_simple_joins.cpp b/lib/dl_mk_simple_joins.cpp new file mode 100644 index 000000000..da1f80a53 --- /dev/null +++ b/lib/dl_mk_simple_joins.cpp @@ -0,0 +1,734 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_simple_joins.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-05-20. + +Revision History: + +--*/ + +#include +#include +#include +#include"dl_mk_simple_joins.h" +#include"ast_pp.h" +#include"trace.h" + + +namespace datalog { + + mk_simple_joins::mk_simple_joins(context & ctx): + plugin(1000), + m_context(ctx) { + } + + class join_planner { + typedef float cost; + + class pair_info { + cost m_total_cost; + /** + \brief Number of rules longer than two that contain this pair. + + This number is being updated by \c add_rule and \remove rule. Even though between + adding a rule and removing it, the length of a rule can decrease without this pair + being notified about it, it will surely see the decrease from length 3 to 2 which + the threshold for rule being counted in this counter. + */ + unsigned m_consumers; + bool m_stratified; + unsigned m_src_stratum; + public: + var_idx_set m_all_nonlocal_vars; + rule_vector m_rules; + + pair_info() : m_consumers(0), m_stratified(true), m_src_stratum(0) {} + + bool can_be_joined() const { + return m_consumers>0; + } + + cost get_cost() const { + /*if(m_instantiated) { + return std::numeric_limits::min(); + }*/ + SASSERT(m_consumers>0); + cost amortized = m_total_cost/m_consumers; + if(m_stratified) { + return amortized * ( (amortized>0) ? (1/16.0f) : 16.0f); + } + else { + return amortized; + } + } + + /** + \brief Add rule \c r among rules interested in current predicate pair. + + The \c pl.m_rule_content entry of the rule has to be properly filled in + by the time of a call to this function + */ + void add_rule(join_planner & pl, app * t1, app * t2, rule * r, + const var_idx_set & non_local_vars_normalized) { + if(m_rules.empty()) { + m_total_cost = pl.compute_cost(t1, t2); + m_src_stratum = std::max(pl.get_stratum(t1->get_decl()), pl.get_stratum(t2->get_decl())); + } + m_rules.push_back(r); + if(pl.m_rules_content.find_core(r)->get_data().m_value.size()>2) { + m_consumers++; + } + if(m_stratified) { + unsigned head_stratum = pl.get_stratum(r->get_head()->get_decl()); + SASSERT(head_stratum>=m_src_stratum); + if(head_stratum==m_src_stratum) { + m_stratified = false; + } + } + idx_set_union(m_all_nonlocal_vars, non_local_vars_normalized); + } + /** + \brief Remove rule from the pair record. Return true if no rules remain + in the pair, and so it should be removed. + */ + bool remove_rule(rule * r, unsigned original_length) { + TRUSTME( remove_from_vector(m_rules, r) ); + if(original_length>2) { + SASSERT(m_consumers>0); + m_consumers--; + } + SASSERT(!m_rules.empty() || m_consumers==0); + return m_rules.empty(); + } + private: + pair_info & operator=(const pair_info &); //to avoid the implicit one + }; + typedef std::pair app_pair; + typedef map, obj_ptr_hash >, default_eq > cost_map; + typedef map, ptr_hash, ptr_eq > rule_pred_map; + + context & m_context; + ast_manager & m; + var_subst & m_var_subst; + rule_set & m_rs_aux_copy; //reference to a rule_set that will allow to ask for stratum levels + + cost_map m_costs; + ptr_vector m_interpreted; + rule_pred_map m_rules_content; + rule_ref_vector m_introduced_rules; + ptr_hashtable, ptr_eq > m_modified_rules; + + ast_ref_vector m_pinned; + + public: + join_planner(context & ctx, rule_set & rs_aux_copy) + : m_context(ctx), m(ctx.get_manager()), m_var_subst(ctx.get_var_subst()), + m_rs_aux_copy(rs_aux_copy), + m_introduced_rules(ctx.get_rule_manager()), + m_pinned(ctx.get_manager()) + { + } + + ~join_planner() + { + cost_map::iterator it = m_costs.begin(); + cost_map::iterator end = m_costs.end(); + for (; it != end; ++it) { + dealloc(it->m_value); + } + m_costs.reset(); + } + private: + + void get_normalizer(app * t, unsigned & next_var, expr_ref_vector & result) const { + SASSERT(result.size()>0); + unsigned res_ofs = result.size()-1; + unsigned n=t->get_num_args(); + for(unsigned i=0; iget_arg(i))); + var * v = to_var(t->get_arg(i)); + unsigned var_idx = v->get_idx(); + if(result[res_ofs-var_idx]==0) { + result[res_ofs-var_idx]=m.mk_var(next_var, v->get_sort()); + next_var++; + } + } + } + + void get_normalizer(app * t1, app * t2, expr_ref_vector & result) const { + SASSERT(result.empty()); + if(t1->get_num_args()==0 && t2->get_num_args()==0) { + return; //nothing to normalize + } + SASSERT(!t1->is_ground() || !t2->is_ground()); + + unsigned max_var_idx = 0; + { + var_idx_set orig_var_set; + collect_vars(m, t1, orig_var_set); + collect_vars(m, t2, orig_var_set); + var_idx_set::iterator ovit = orig_var_set.begin(); + var_idx_set::iterator ovend = orig_var_set.end(); + for(; ovit!=ovend; ++ovit) { + unsigned var_idx = *ovit; + if(var_idx>max_var_idx) { + max_var_idx = var_idx; + } + } + } + + if(t1->get_decl()!=t2->get_decl()) { + if(t1->get_decl()->get_id()get_decl()->get_id()) { + std::swap(t1, t2); + } + } + else { + int_vector norm1(max_var_idx+1, -1); + int_vector norm2(max_var_idx+1, -1); + unsigned n=t1->get_num_args(); + SASSERT(n==t2->get_num_args()); + for(unsigned i=0; iget_arg(i)); + var * v2 = to_var(t2->get_arg(i)); + if(v1->get_sort()!=v2->get_sort()) { + //different sorts mean we can distinguish the two terms + if(v1->get_sort()->get_id()get_sort()->get_id()) { + std::swap(t1, t2); + } + break; + } + unsigned v1_idx = v1->get_idx(); + unsigned v2_idx = v2->get_idx(); + //since the rules already went through the mk_filter_rules transformer, + //variables must be linear + SASSERT(norm1[v1_idx]==-1); + SASSERT(norm2[v2_idx]==-1); + + if(norm2[v1_idx]!=norm1[v2_idx]) { + //now we can distinguish the two terms + if(norm2[v1_idx](0)); + unsigned next_var = 0; + get_normalizer(t1, next_var, result); + get_normalizer(t2, next_var, result); + } + + app_pair get_key(app * t1, app * t2) { + expr_ref_vector norm_subst(m); + get_normalizer(t1, t2, norm_subst); + expr_ref t1n_ref(m); + expr_ref t2n_ref(m); + m_var_subst(t1, norm_subst.size(), norm_subst.c_ptr(), t1n_ref); + m_var_subst(t2, norm_subst.size(), norm_subst.c_ptr(), t2n_ref); + app * t1n = to_app(t1n_ref); + app * t2n = to_app(t2n_ref); + if(t1n>t2n) { + std::swap(t1n, t2n); + } + m_pinned.push_back(t1n); + m_pinned.push_back(t2n); + + /* + IF_VERBOSE(0, + print_renaming(norm_subst, verbose_stream()); + display_predicate(m_context, t1, verbose_stream()); + display_predicate(m_context, t2, verbose_stream()); + display_predicate(m_context, t1n, verbose_stream()); + display_predicate(m_context, t2n, verbose_stream());); + */ + return app_pair(t1n, t2n); + } + + /** + \brief Add rule \c r among rules interested in predicate pair \c t1, \c t2. + + The \c m_rule_content entry of the rule \c r has to be properly filled in + by the time of a call to this function + */ + void register_pair(app * t1, app * t2, rule * r, const var_idx_set & non_local_vars) { + TRACE("dl", tout << mk_pp(t1, m) << " " << mk_pp(t2, m) << "\n"; + r->display(m_context, tout); tout << "\n";); + SASSERT(t1!=t2); + cost_map::entry * e = m_costs.insert_if_not_there2(get_key(t1, t2), 0); + pair_info * & ptr_inf = e->get_data().m_value; + if(ptr_inf==0) { + ptr_inf = alloc(pair_info); + } + pair_info & inf = *ptr_inf; + + expr_ref_vector normalizer(m); + get_normalizer(t1, t2, normalizer); + unsigned norm_ofs = normalizer.size()-1; + var_idx_set normalized_vars; + var_idx_set::iterator vit = non_local_vars.begin(); + var_idx_set::iterator vend = non_local_vars.end(); + for(; vit!=vend; ++vit) { + unsigned norm_var = to_var(normalizer.get(norm_ofs-*vit))->get_idx(); + normalized_vars.insert(norm_var); + } + + inf.add_rule(*this, t1, t2, r, normalized_vars); + } + + pair_info & get_pair(app_pair key) const { + cost_map::entry * e = m_costs.find_core(key); + SASSERT(e); + return *e->get_data().m_value; + } + + void remove_rule_from_pair(app_pair key, rule * r, unsigned original_len) { + pair_info * ptr = &get_pair(key); + if(ptr->remove_rule(r, original_len)) { + SASSERT(ptr->m_rules.empty()); + m_costs.remove(key); + dealloc(ptr); + } + } + + void register_rule(rule * r) { + var_counter counter; + counter.count_vars(m, r, 1); + + ptr_vector & rule_content = + m_rules_content.insert_if_not_there2(r, ptr_vector())->get_data().m_value; + SASSERT(rule_content.empty()); + + unsigned pos_tail_size=r->get_positive_tail_size(); + for(unsigned i=0; iget_tail(i)); + } + for(unsigned i=0; iget_tail(i); + var_idx_set t1_vars; + collect_vars(m, t1, t1_vars); + counter.count_vars(m, t1, -1); //temporarily remove t1 variables from counter + for(unsigned j=i+1; jget_tail(j); + counter.count_vars(m, t2, -1); //temporarily remove t2 variables from counter + var_idx_set scope_vars(t1_vars); + collect_vars(m, t2, scope_vars); + var_idx_set non_local_vars; + counter.collect_positive(non_local_vars); + counter.count_vars(m, t2, 1); //restore t2 variables in counter + set_intersection(non_local_vars, scope_vars); + register_pair(t1, t2, r, non_local_vars); + } + counter.count_vars(m, t1, 1); //restore t1 variables in counter + } + } + + bool extract_argument_info(unsigned var_idx, app * t, expr_ref_vector & args, + ptr_vector & domain) { + unsigned n=t->get_num_args(); + for(unsigned i=0; iget_arg(i)); + if(v->get_idx()==var_idx) { + args.push_back(v); + domain.push_back(m.get_sort(v)); + return true; + } + } + return false; + } + + void join_pair(app_pair pair_key) { + app * t1 = pair_key.first; + app * t2 = pair_key.second; + pair_info & inf = get_pair(pair_key); + SASSERT(!inf.m_rules.empty()); + var_idx_set & output_vars = inf.m_all_nonlocal_vars; + expr_ref_vector args(m); + ptr_vector domain; + + unsigned arity = output_vars.num_elems(); + idx_set::iterator ovit=output_vars.begin(); + idx_set::iterator ovend=output_vars.end(); + //TODO: improve quadratic complexity + for(;ovit!=ovend;++ovit) { + unsigned var_idx=*ovit; + + bool found=extract_argument_info(var_idx, t1, args, domain); + if(!found) { + found=extract_argument_info(var_idx, t2, args, domain); + } + SASSERT(found); + } + + SASSERT(args.size()==arity); + SASSERT(domain.size()==arity); + + rule * one_parent = inf.m_rules.back(); + + func_decl* parent_head = one_parent->get_head()->get_decl(); + const char * one_parent_name = parent_head->get_name().bare_str(); + std::string parent_name; + if(inf.m_rules.size()>1) { + parent_name = one_parent_name + std::string("_and_") + to_string(inf.m_rules.size()-1); + } + else { + parent_name = one_parent_name; + } + + func_decl * decl = m_context.mk_fresh_head_predicate( + symbol(parent_name.c_str()), symbol("split"), + arity, domain.c_ptr(), parent_head); + + app_ref head(m.mk_app(decl, arity, args.c_ptr()), m); + + app * tail[] = {t1, t2}; + + rule * new_rule = m_context.get_rule_manager().mk(head, 2, tail, 0); + + //TODO: update accounting so that it can handle multiple parents + new_rule->set_accounting_parent_object(m_context, one_parent); + + m_introduced_rules.push_back(new_rule); + + //here we copy the inf.m_rules vector because inf.m_rules will get changed + //in the iteration. Also we use hashtable instead of vector because we do + //not want to process one rule twice. + typedef ptr_hashtable, default_eq > rule_hashtable; + rule_hashtable relevant_rules; + insert_into_set(relevant_rules, inf.m_rules); + rule_hashtable::iterator rit = relevant_rules.begin(); + rule_hashtable::iterator rend = relevant_rules.end(); + for(; rit!=rend; ++rit) { + apply_binary_rule(*rit, pair_key, head); + } + + // SASSERT(!m_costs.contains(pair_key)); + } + + void replace_edges(rule * r, const ptr_vector & removed_tails, + const ptr_vector & added_tails0, const ptr_vector & rule_content) { + SASSERT(removed_tails.size()>=added_tails0.size()); + unsigned len = rule_content.size(); + unsigned original_len = len+removed_tails.size()-added_tails0.size(); + ptr_vector added_tails(added_tails0); //we need a copy since we'll be modifying it + + unsigned rt_sz = removed_tails.size(); + //remove edges between removed tails + for(unsigned i=0; iget_head(); + + var_counter counter; + counter.count_vars(m, head, 1); + + unsigned tail_size=r->get_tail_size(); + unsigned pos_tail_size=r->get_positive_tail_size(); + + for(unsigned i=pos_tail_size; iget_tail(i), 1); + } + for(unsigned i=0; i & rule_content = m_rules_content.find_core(r)->get_data().m_value; + unsigned len = rule_content.size(); + if(len==1) { + return; + } + + func_decl * t1_pred = t1->get_decl(); + func_decl * t2_pred = t2->get_decl(); + ptr_vector removed_tails; + ptr_vector added_tails; + for(unsigned i1=0; i1get_decl()!=t1_pred) { + continue; + } + unsigned i2start = (t1_pred==t2_pred) ? (i1+1) : 0; + for(unsigned i2=i2start; i2get_decl()!=t2_pred) { + continue; + } + if(get_key(rt1, rt2)!=pair_key) { + continue; + } + expr_ref_vector normalizer(m); + get_normalizer(rt1, rt2, normalizer); + expr_ref_vector denormalizer(m); + reverse_renaming(m, normalizer, denormalizer); + expr_ref new_transf(m); + m_var_subst(t_new, denormalizer.size(), denormalizer.c_ptr(), new_transf); + app * new_lit = to_app(new_transf); + + m_pinned.push_back(new_lit); + rule_content[i1]=new_lit; + rule_content[i2]=rule_content.back(); + rule_content.pop_back(); + len--; //here the bound of both loops changes!!! + removed_tails.push_back(rt1); + removed_tails.push_back(rt2); + added_tails.push_back(new_lit); + //this exits the inner loop, the outer one continues in case there will + //be other matches + break; + } + } + SASSERT(!removed_tails.empty()); + SASSERT(!added_tails.empty()); + m_modified_rules.insert(r); + replace_edges(r, removed_tails, added_tails, rule_content); + } + + cost get_domain_size(func_decl * pred, unsigned arg_index) const { + relation_sort sort = pred->get_domain(arg_index); + return static_cast(m_context.get_sort_size_estimate(sort)); + //unsigned sz; + //if(!m_context.get_sort_size(sort, sz)) { + // sz=UINT_MAX; + //} + //return static_cast(sz); + } + + unsigned get_stratum(func_decl * pred) const { + return m_rs_aux_copy.get_predicate_strat(pred); + } + + cost estimate_size(app * t) const { + func_decl * pred = t->get_decl(); + unsigned n=pred->get_arity(); + if( (m_context.saturation_was_run() && m_context.get_rmanager().try_get_relation(pred)) + || m_context.get_rmanager().is_saturated(pred)) { + SASSERT(m_context.get_rmanager().try_get_relation(pred)); //if it is saturated, it should exist + unsigned rel_size_int = m_context.get_relation(pred).get_size_estimate_rows(); + if(rel_size_int!=0) { + cost rel_size = static_cast(rel_size_int); + cost curr_size = rel_size; + for(unsigned i=0; iget_arg(i))) { + curr_size /= get_domain_size(pred, i); + } + } + return curr_size; + } + } + cost res = 1; + for(unsigned i=0; iget_arg(i))) { + res *= get_domain_size(pred, i); + } + } + return res; + } + + cost compute_cost(app * t1, app * t2) const { + func_decl * t1_pred = t1->get_decl(); + func_decl * t2_pred = t2->get_decl(); + cost inters_size = 1; + variable_intersection vi(m_context.get_manager()); + vi.populate(t1, t2); + unsigned n = vi.size(); + for(unsigned i=0; i0) { + res /= 2; + } + else { + res *= 2; + } + } + }*/ + + TRACE("report_costs", + display_predicate(m_context, t1, tout); + display_predicate(m_context, t2, tout); + tout << res << "\n";); + return res; + } + + + bool pick_best_pair(app_pair & p) { + app_pair best; + bool found = false; + cost best_cost; + + cost_map::iterator it = m_costs.begin(); + cost_map::iterator end = m_costs.end(); + for(; it!=end; ++it) { + app_pair key = it->m_key; + pair_info & inf = *it->m_value; + if(!inf.can_be_joined()) { + continue; + } + cost c = inf.get_cost(); + if(!found || cm_key; + ptr_vector content = rcit->m_value; + SASSERT(content.size()<=2); + if(content.size()==orig_r->get_positive_tail_size()) { + //rule did not change + result->add_rule(orig_r); + continue; + } + + ptr_vector tail(content); + svector negs(tail.size(), false); + unsigned or_len = orig_r->get_tail_size(); + for(unsigned i=orig_r->get_positive_tail_size(); iget_tail(i)); + negs.push_back(orig_r->is_neg_tail(i)); + } + + rule * new_rule = m_context.get_rule_manager().mk(orig_r->get_head(), tail.size(), tail.c_ptr(), + negs.c_ptr()); + + new_rule->set_accounting_parent_object(m_context, orig_r); + + result->add_rule(new_rule); + } + while(!m_introduced_rules.empty()) { + result->add_rule(m_introduced_rules.back()); + m_introduced_rules.pop_back(); + } + return result; + } + }; + + rule_set * mk_simple_joins::operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc) { + // TODO mc, pc + rule_set rs_aux_copy(m_context); + rs_aux_copy.add_rules(source); + if(!rs_aux_copy.is_closed()) { + rs_aux_copy.close(); + } + + join_planner planner(m_context, rs_aux_copy); + + return planner.run(source); + } + + +}; + diff --git a/lib/dl_mk_simple_joins.h b/lib/dl_mk_simple_joins.h new file mode 100644 index 000000000..5431c4117 --- /dev/null +++ b/lib/dl_mk_simple_joins.h @@ -0,0 +1,62 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_simple_joins.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-05-20. + +Revision History: + +--*/ +#ifndef _DL_MK_SIMPLE_JOINS_H_ +#define _DL_MK_SIMPLE_JOINS_H_ + +#include"map.h" +#include"obj_pair_hashtable.h" + +#include"dl_context.h" +#include"dl_rule_set.h" +#include"dl_rule_transformer.h" + +namespace datalog { + + /** + \brief Functor for creating rules that contain simple joins. + A simple join is the join of two tables. + + After applying this transformation, every rule has at most one join. + So, the rules will have the form + + HEAD :- TAIL. + HEAD :- TAIL_1, TAIL_2. + + We also assume a rule may contain interpreted expressions that work as filtering conditions. + So, we may also have: + + HEAD :- TAIL, C_1, ..., C_n. + HEAD :- TAIL_1, TAIL_2, C_1, ..., C_n. + + Where the C_i's are interpreted expressions. + + We say that a rule containing C_i's is a rule with a "big tail". + */ + class mk_simple_joins : public rule_transformer::plugin { + context & m_context; + public: + mk_simple_joins(context & ctx); + + rule_set * operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc); + }; + +}; + +#endif /* _DL_MK_SIMPLE_JOINS_H_ */ + diff --git a/lib/dl_mk_slice.cpp b/lib/dl_mk_slice.cpp new file mode 100644 index 000000000..979dd8183 --- /dev/null +++ b/lib/dl_mk_slice.cpp @@ -0,0 +1,839 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_slice.cpp + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2012-9-12. + +Revision History: + + Consider a rule: + + P(x,y) :- R(x,z), phi(x,y,z) + + input: x, z + output: x, y + + Let x_i, y_i, z_i be incides into the vectors x, y, z. + + Suppose that positions in P and R are annotated with what is + slicable. + + Sufficient conditions for sliceability: + + x_i is sliceable if x_i does not appear in phi(x,y,z) + and the positions where x_i is used in P and R are sliceable + + y_i is sliceable if y_i does not occur in phi(x,y,z), or + if it occurs in phi(x,y,z) it is only in one conjunct of the form + y_i = t[x_j,y_j,z_j] + and the positions where y_i is used in P and R are sliceable + + z_i is sliceable if z_i does not occur in phi(x,y,z), or + if it occurs in phi(x,y,z) it is only in one conjunct of the form + y_i = t[x_j,y_j,z_i] where y_i is sliceable + and the positions where z_i is used in P and R are sliceable + + + A more refined approach may be using Gaussean elimination based + on x,z and eliminating variables from x,y (expressing them in terms + of a disjoint subeset of x,z). + + +--*/ + +#include "dl_mk_slice.h" +#include "ast_pp.h" +#include "expr_functors.h" +#include "dl_mk_rule_inliner.h" +#include "model_smt2_pp.h" + +namespace datalog { + + /** + Convert from sliced proofs to original proofs. + Given sliced rule + fml0: forall x y z u. p(x,y) & z = f(x,y) & phi(x,u) => p(u, z) + into + fml1: forall a b . q(a) & phi(a,b) => q(b) + It induces mappings: + theta: a |-> x, b |-> u + vars: x y z u. + predicates: -q(a) |-> p(x,y) + +q(b) |-> z = f(x,y) => p(u,z) + fml1 |-> fml0 + + The mapping theta is an injective function from variable indices + to variable indices. We can apply it as a substitution on expressions, + but we can also apply it as a transformation on substitutions. We + write theta[subst] when applying theta on substitution 'subst' such + that if [x |-> t] is in subst, then [theta(x) |-> theta(t)] is in + the result. + + Given hyper-resolvent: fml1 subst1 fml2 subst2 |- fml3 + where fml1 |-> fml1' with theta1 + fml2 |-> fml2' with theta2 + Perform the following steps: + 1. [Convert fml1' fml2' to datalog rules because we have resolution routines] + 2. Create subst1' := theta1[subst1] + subst2' := theta2[subst2] + 3. Set fml1'' := subst1'(fml1') + fml2'' := subst2'(fml2') + 4. Resolve fml1'' and fml2'' + extract subst1'', subst2'' from resolvents. + extract goal fml3' + 5. Create subst1''' := subst1'' o subst1' + subst2''' := subst2'' o subst2' + 6. Return fml1'' subst1''' fml2'' subst2''' |- fml3' + 7. Attach to fml3' the transformation ...? + + */ + class mk_slice::slice_proof_converter : public proof_converter { + context& m_ctx; + ast_manager& m; + rule_manager& rm; + rule_ref_vector m_pinned_rules; + expr_ref_vector m_pinned_exprs; + obj_map m_rule2slice; // rule to sliced rule + obj_map m_renaming; // rule to renaming + obj_map m_sliceform2rule; // sliced formula to rule. + ptr_vector m_todo; + obj_map m_new_proof; + rule_unifier m_unifier; + + + slice_proof_converter(slice_proof_converter const& other); + + void init_form2rule() { + if (!m_sliceform2rule.empty()) { + return; + } + obj_map::iterator it = m_rule2slice.begin(); + obj_map::iterator end = m_rule2slice.end(); + expr_ref fml(m); + for (; it != end; ++it) { + TRACE("dl", + it->m_key->display(m_ctx, tout << "orig:\n"); + it->m_value->display(m_ctx, tout << "new:\n");); + + it->m_value->to_formula(fml); + m_pinned_exprs.push_back(fml); + m_sliceform2rule.insert(fml, it->m_key); + } + } + + void translate_proof(proof_ref& pr) { + m_todo.reset(); + m_new_proof.reset(); + m_todo.push_back(pr); + while (!m_todo.empty()) { + proof* p = m_todo.back(); + if (m_new_proof.contains(p)) { + m_todo.pop_back(); + } + else if (translate_asserted(p)) { + // done + } + else if (translate_hyper_res(p)) { + // done + } + else { + m_new_proof.insert(p, p); + m_todo.pop_back(); + TRACE("dl", tout << "unhandled proof term\n" << mk_pp(p, m) << "\n";); + } + } + pr = m_new_proof.find(pr); + } + + bool translate_asserted(proof* p) { + expr* fact = 0; + rule* r = 0; + if (!m.is_asserted(p, fact)) { + return false; + } + if (!m_sliceform2rule.find(fact, r)) { + TRACE("dl", tout << "does not have fact\n" << mk_pp(fact, m) << "\n";); + return false; + } + expr_ref fml(m); + proof_ref new_p(m); + r->to_formula(fml); + new_p = m.mk_asserted(fml); + m_pinned_exprs.push_back(new_p); + m_todo.pop_back(); + m_new_proof.insert(p, new_p); + return true; + } + + bool translate_hyper_res(proof* p) { + dl_decl_util util(m); + svector > positions; + expr_ref concl(m), slice_concl(m); + proof_ref_vector premises0(m); + vector substs, substs0; + + if (!util.is_hyper_resolve(p, premises0, slice_concl, positions, substs0)) { + return false; + } + unsigned num_args = p->get_num_args(); + SASSERT(num_args >= 2); + bool all_found = true; + for (unsigned i = 0; i < num_args-1; ++i) { + proof* arg = to_app(p->get_arg(i)); + SASSERT(m.is_proof(arg)); + if (!m_new_proof.contains(arg)) { + m_todo.push_back(arg); + all_found = false; + } + } + if (!all_found) { + return true; + } + ptr_vector premises; + + proof* p0 = to_app(p->get_arg(0)); + proof* p0_new = m_new_proof.find(p0); + expr* fact0 = m.get_fact(p0); + TRACE("dl", tout << "fact0: " << mk_pp(fact0, m) << "\n";); + rule* orig0 = m_sliceform2rule.find(fact0); + rule* slice0 = m_rule2slice.find(orig0); + unsigned_vector const& renaming0 = m_renaming.find(orig0); + premises.push_back(p0_new); + rule_ref r1(rm), r2(rm), r3(rm); + r1 = orig0; + substs.push_back(expr_ref_vector(m)); + for (unsigned i = 1; i < num_args-1; ++i) { + proof* p1 = to_app(p->get_arg(i)); + proof* p1_new = m_new_proof.find(p1); + expr* fact1 = m.get_fact(p1); + TRACE("dl", tout << "fact1: " << mk_pp(fact1, m) << "\n";); + rule* orig1 = m_sliceform2rule.find(fact1); + rule* slice1 = m_rule2slice.find(orig1); + unsigned_vector const& renaming1 = m_renaming.find(orig1); //TBD + premises.push_back(p1_new); + + // TODO: work with substitutions. + r2 = orig1; + unsigned idx = 0; // brittle. TBD get index from positions. + + VERIFY(m_unifier.unify_rules(*r1, idx, *r2)); + m_unifier.apply(*r1.get(), idx, *r2.get(), r3); + expr_ref_vector const sub1 = m_unifier.get_rule_subst(*r1.get(), true); + for (unsigned j = 0; j < substs.size(); ++j) { + apply_subst(substs[j], sub1); + // size of substitutions may have grown...substs[j].resize(num_args[j]); + } + substs.push_back(m_unifier.get_rule_subst(*r2.get(), false)); + TRACE("dl", + r1->display(m_ctx, tout << "rule1:"); + r2->display(m_ctx, tout << "rule2:"); + r3->display(m_ctx, tout << "res:");); + r1 = r3; + } + r1->to_formula(concl); + proof* new_p = util.mk_hyper_resolve(premises.size(), premises.c_ptr(), concl, positions, substs); + m_pinned_exprs.push_back(new_p); + m_pinned_rules.push_back(r1.get()); + m_sliceform2rule.insert(slice_concl, r1.get()); + m_rule2slice.insert(r1.get(), 0); + m_renaming.insert(r1.get(), unsigned_vector()); + m_new_proof.insert(p, new_p); + m_todo.pop_back(); + TRACE("dl", tout << "translated:\n" << mk_pp(p, m) << "\nto\n" << mk_pp(new_p, m) << "\n";); + return true; + } + + public: + slice_proof_converter(context& ctx): + m_ctx(ctx), + m(ctx.get_manager()), + rm(ctx.get_rule_manager()), + m_pinned_rules(rm), + m_pinned_exprs(m), + m_unifier(rm, ctx, m) {} + + void insert(rule* orig_rule, rule* slice_rule, unsigned sz, unsigned const* renaming) { + m_rule2slice.insert(orig_rule, slice_rule); + m_pinned_rules.push_back(orig_rule); + m_pinned_rules.push_back(slice_rule); + m_renaming.insert(orig_rule, unsigned_vector(sz, renaming)); + } + + virtual void operator()(ast_manager& m, unsigned num_source, proof * const * source, proof_ref & result) { + SASSERT(num_source == 1); + result = source[0]; + init_form2rule(); + translate_proof(result); + } + + virtual proof_converter * translate(ast_translation & translator) { + UNREACHABLE(); + // this would require implementing translation for the dl_context. + return 0; + } + }; + + class mk_slice::slice_model_converter : public model_converter { + ast_manager& m; + obj_map m_slice2old; + obj_map m_sliceable; + ast_ref_vector m_pinned; + + public: + slice_model_converter(mk_slice& parent, ast_manager& m): m(m), m_pinned(m) {} + + void add_predicate(func_decl* old_f, func_decl* slice_f) { + m_pinned.push_back(old_f); + m_pinned.push_back(slice_f); + m_slice2old.insert(slice_f, old_f); + } + + void add_sliceable(func_decl* f, bit_vector const& bv) { + m_pinned.push_back(f); + m_sliceable.insert(f, bv); + } + + virtual void operator()(model_ref & md) { + if (m_slice2old.empty()) { + return; + } + TRACE("dl", model_smt2_pp(tout, m, *md, 0); ); + model_ref old_model = alloc(model, m); + obj_map::iterator it = m_slice2old.begin(); + obj_map::iterator end = m_slice2old.end(); + for (; it != end; ++it) { + func_decl* old_p = it->m_value; + func_decl* new_p = it->m_key; + bit_vector const& is_sliced = m_sliceable.find(old_p); + SASSERT(is_sliced.size() == old_p->get_arity()); + SASSERT(is_sliced.size() > new_p->get_arity()); + func_interp* old_fi = alloc(func_interp, m, is_sliced.size()); + + TRACE("dl", tout << mk_pp(old_p, m) << " " << mk_pp(new_p, m) << "\n"; + for (unsigned j = 0; j < is_sliced.size(); ++j) { + tout << (is_sliced.get(j)?"1":"0"); + } + tout << "\n";); + + if (new_p->get_arity() == 0) { + old_fi->set_else(md->get_const_interp(new_p)); + } + else { + expr_ref_vector subst(m); + expr_ref tmp(m); + var_subst vs(m, false); + for (unsigned i = 0; i < is_sliced.size(); ++i) { + if (!is_sliced.get(i)) { + subst.push_back(m.mk_var(i, old_p->get_domain(i))); + } + } + func_interp* new_fi = md->get_func_interp(new_p); + if (!new_fi) { + TRACE("dl", tout << new_p->get_name() << " has no value in the current model\n";); + dealloc(old_fi); + continue; + } + if (!new_fi->is_partial()) { + TRACE("dl", tout << mk_pp(new_fi->get_else(), m) << "\n";); + vs(new_fi->get_else(), subst.size(), subst.c_ptr(), tmp); + old_fi->set_else(tmp); + } + unsigned num_entries = new_fi->num_entries(); + for (unsigned j = 0; j < num_entries; ++j) { + expr_ref res(m); + expr_ref_vector args(m); + func_entry const* e = new_fi->get_entry(j); + for (unsigned k = 0, l = 0; k < old_p->get_arity(); ++k) { + if (!is_sliced.get(k)) { + vs(e->get_arg(l++), subst.size(), subst.c_ptr(), tmp); + args.push_back(tmp); + } + else { + args.push_back(m.mk_var(k, old_p->get_domain(k))); + } + SASSERT(l <= new_p->get_arity()); + } + vs(e->get_result(), subst.size(), subst.c_ptr(), res); + old_fi->insert_entry(args.c_ptr(), res.get()); + } + old_model->register_decl(old_p, old_fi); + } + } + // register values that have not been sliced. + unsigned sz = md->get_num_constants(); + for (unsigned i = 0; i < sz; ++i) { + func_decl* c = md->get_constant(i); + if (!m_slice2old.contains(c)) { + old_model->register_decl(c, md->get_const_interp(c)); + } + } + sz = md->get_num_functions(); + for (unsigned i = 0; i < sz; ++i) { + func_decl* f = md->get_function(i); + if (!m_slice2old.contains(f)) { + func_interp* fi = md->get_func_interp(f); + old_model->register_decl(f, fi->copy()); + } + } + md = old_model; + TRACE("dl", model_smt2_pp(tout, m, *md, 0); ); + } + + virtual model_converter * translate(ast_translation & translator) { + UNREACHABLE(); + return 0; + } + + }; + + mk_slice::mk_slice(context & ctx): + plugin(1), + m_ctx(ctx), + m(ctx.get_manager()), + rm(ctx.get_rule_manager()), + m_solved_vars(m), + m_pinned(m), + m_pc(0), + m_mc(0) + {} + + + bit_vector& mk_slice::get_predicate_slice(func_decl* h) { + if (!m_sliceable.contains(h)) { + bit_vector bv; + bv.resize(h->get_arity(), true); + m_sliceable.insert(h, bv); + } + return m_sliceable.find(h); + } + + /** + \brief Saturate set of rules with respect to slicing criteria. + */ + void mk_slice::saturate(rule_set const& src) { + bool change = true; + while (change) { + change = false; + for (unsigned i = 0; i < src.get_num_rules(); ++i) { + change = prune_rule(*src.get_rule(i)) || change; + } + } + } + + bool mk_slice::prune_rule(rule& r) { + TRACE("dl", r.display(m_ctx, tout << "prune:\n"); ); + bool change = false; + init_vars(r); + // + // if a predicate in the body takes a constant as argument, + // the corresponding position is not sliceable. + // + for (unsigned j = 0; j < r.get_uninterpreted_tail_size(); ++j) { + app* p = r.get_tail(j); + bit_vector& bv = get_predicate_slice(p); + for (unsigned i = 0; i < p->get_num_args(); ++i) { + if (!is_var(p->get_arg(i)) && bv.get(i)) { + bv.unset(i); + change = true; + TRACE("dl", tout << "argument " << i << " is not a variable " << p->get_decl()->get_name() << "\n";); + } + } + } + // + // Collect the set of variables that are solved. + // Collect the occurrence count of the variables per conjunct. + // + expr_ref_vector conjs = get_tail_conjs(r); + uint_set used_vars, parameter_vars; + for (unsigned j = 0; j < conjs.size(); ++j) { + expr* e = conjs[j].get(); + expr_ref r(m); + unsigned v; + if (is_eq(e, v, r) && is_output(v) && m_var_is_sliceable[v]) { + TRACE("dl", tout << "is_eq: " << mk_pp(e, m) << " " << (m_solved_vars[v].get()?"solved":"new") << "\n";); + add_var(v); + if (!m_solved_vars[v].get()) { + add_free_vars(parameter_vars, r); + m_solved_vars[v] = r; + } + else { + // variables can only be solved once. + add_free_vars(used_vars, e); + add_free_vars(used_vars, m_solved_vars[v].get()); + used_vars.insert(v); + } + } + else { + add_free_vars(used_vars, e); + } + } + uint_set::iterator it = used_vars.begin(), end = used_vars.end(); + for (; it != end; ++it) { + if (*it < m_var_is_sliceable.size()) { + m_var_is_sliceable[*it] = false; + } + } + // + // Check if sliceable variables are either solved + // or are used to solve output sliceable variables, or + // don't occur in interpreted tail. + // + for (unsigned i = 0; i < num_vars(); ++i) { + if (!m_var_is_sliceable[i]) { + continue; + } + if (used_vars.contains(i)) { + m_var_is_sliceable[i] = false; + continue; + } + bool is_input = m_input[i]; + bool is_output = m_output[i]; + if (is_input && is_output) { + if (m_solved_vars[i].get()) { + m_var_is_sliceable[i] = false; + } + } + else if (is_output) { + if (parameter_vars.contains(i)) { + m_var_is_sliceable[i] = false; + } + } + else if (is_input) { + // I can be a parameter var, but not in used_vars. + } + else { + // variable does not correspond to + // any position in predicates. + } + } + // + // Update sliceable predicates based on slicing information of variables. + // + change = finalize_vars(r.get_head()) || change; + for (unsigned j = 0; j < r.get_uninterpreted_tail_size(); ++j) { + change = finalize_vars(r.get_tail(j)) || change; + } + return change; + } + + bool mk_slice::is_eq(expr* e, unsigned& v, expr_ref& r) { + expr* c, *th, *el, *e1, *e2; + unsigned v1, v2; + expr_ref r1(m), r2(m); + if (m.is_ite(e, c, th, el)) { + if (is_eq(th, v1, r1) && is_eq(el, v2, r2) && v1 == v2) { + v = v1; + r = m.mk_ite(c, r1, r2); + return true; + } + } + if (is_var(e)) { + v = to_var(e)->get_idx(); + r = m.mk_true(); + return true; + } + if (m.is_not(e,e) && is_var(e)) { + v = to_var(e)->get_idx(); + r = m.mk_false(); + return true; + } + if (m.is_eq(e, e1, e2) && is_var(e1)) { + v = to_var(e1)->get_idx(); + r = e2; + return true; + } + if (m.is_eq(e, e1, e2) && is_var(e2)) { + v = to_var(e2)->get_idx(); + r = e1; + return true; + } + return false; + } + + bool mk_slice::is_output(unsigned idx) { + return idx < m_output.size() && m_output[idx] && !m_input[idx]; + } + + bool mk_slice::is_output(expr* e) { + if (is_var(e)) { + return is_output(to_var(e)->get_idx()); + } + else { + return false; + } + } + + void mk_slice::init_vars(rule& r) { + m_input.reset(); + m_output.reset(); + m_var_is_sliceable.reset(); + m_solved_vars.reset(); + init_vars(r.get_head(), true, false); + for (unsigned j = 0; j < r.get_uninterpreted_tail_size(); ++j) { + init_vars(r.get_tail(j), false, r.is_neg_tail(j)); + } + } + + expr_ref_vector mk_slice::get_tail_conjs(rule const& r) { + expr_ref_vector conjs(m); + for (unsigned j = r.get_uninterpreted_tail_size(); j < r.get_tail_size(); ++j) { + conjs.push_back(r.get_tail(j)); + } + datalog::flatten_and(conjs); + return conjs; + } + + void mk_slice::add_var(unsigned idx) { + if (idx >= m_input.size()) { + m_input.resize(idx+1, false); + m_output.resize(idx+1, false); + m_var_is_sliceable.resize(idx+1, true); + m_solved_vars.resize(idx+1); + } + } + + void mk_slice::init_vars(app* p, bool is_output, bool is_neg_tail) { + bit_vector& bv = get_predicate_slice(p); + for (unsigned i = 0; i < p->get_num_args(); ++i) { + if (is_neg_tail) { + TRACE("dl", tout << "negated " << i << " in " << p->get_decl()->get_name() << "\n";); + bv.unset(i); + } + expr* arg = p->get_arg(i); + if (is_var(arg)) { + unsigned idx = to_var(arg)->get_idx(); + add_var(idx); + if (is_output) { + m_output[idx] = true; + } + else { + m_input[idx] = true; + } + m_var_is_sliceable[idx] &= bv.get(i); + } + else { + SASSERT(m.is_value(arg)); + if (!is_output) { + TRACE("dl", tout << "input " << i << " in " << p->get_decl()->get_name() << "\n";); + bv.unset(i); + } + } + } + } + + bool mk_slice::finalize_vars(app* p) { + bool change = false; + bit_vector& bv = get_predicate_slice(p); + for (unsigned i = 0; i < p->get_num_args(); ++i) { + expr* arg = p->get_arg(i); + if (is_var(arg) && !m_var_is_sliceable[to_var(arg)->get_idx()] && bv.get(i)) { + bv.unset(i); + change = true; + TRACE("dl", tout << "variable is unslicable " << mk_pp(arg, m) << " for index " << i << " in " << p->get_decl()->get_name() << "\n";); + } + } + return change; + } + + void mk_slice::add_free_vars(uint_set& result, expr* e) { + ptr_vector sorts; + get_free_vars(e, sorts); + for (unsigned i = 0; i < sorts.size(); ++i) { + if (sorts[i]) { + result.insert(i); + } + } + } + + void mk_slice::display(std::ostream& out) { + obj_map::iterator it = m_sliceable.begin(); + obj_map::iterator end = m_sliceable.end(); + for (; it != end; ++it) { + out << it->m_key->get_name() << " "; + bit_vector const& bv = it->m_value; + for (unsigned i = 0; i < bv.size(); ++i) { + out << (bv.get(i)?"1":"0"); + } + out << "\n"; + } + } + + void mk_slice::reset() { + m_input.reset(); + m_output.reset(); + m_var_is_sliceable.reset(); + m_solved_vars.reset(); + m_predicates.reset(); + m_pinned.reset(); + } + + void mk_slice::declare_predicates() { + obj_map::iterator it = m_sliceable.begin(), end = m_sliceable.end(); + ptr_vector domain; + func_decl* f; + for (; it != end; ++it) { + domain.reset(); + func_decl* p = it->m_key; + bit_vector const& bv = it->m_value; + for (unsigned i = 0; i < bv.size(); ++i) { + if (!bv.get(i)) { + domain.push_back(p->get_domain(i)); + } + } + if (domain.size() < bv.size()) { + f = m_ctx.mk_fresh_head_predicate(p->get_name(), symbol("slice"), domain.size(), domain.c_ptr(), p); + m_pinned.push_back(f); + m_predicates.insert(p, f); + if (m_mc) { + m_mc->add_predicate(p, f); + } + } + } + } + + bool mk_slice::rule_updated(rule const& r) { + if (m_predicates.contains(r.get_decl())) return true; + for (unsigned i = 0; i < r.get_uninterpreted_tail_size(); ++i) { + if (m_predicates.contains(r.get_decl(i))) return true; + } + return false; + } + + void mk_slice::update_predicate(app* p, app_ref& q) { + func_decl* qd; + if (m_predicates.find(p->get_decl(), qd)) { + bit_vector const& bv = get_predicate_slice(p->get_decl()); + ptr_vector args; + for (unsigned i = 0; i < bv.size(); ++i) { + if (!bv.get(i)) { + args.push_back(p->get_arg(i)); + } + } + q = m.mk_app(qd, args.size(), args.c_ptr()); + } + else { + q = p; + } + } + + void mk_slice::update_rule(rule& r, rule_set& dst) { + rule* new_rule; + if (rule_updated(r)) { + app_ref_vector tail(m); + app_ref head(m); + ptr_vector sorts; + update_predicate(r.get_head(), head); + get_free_vars(head.get(), sorts); + for (unsigned i = 0; i < r.get_uninterpreted_tail_size(); ++i) { + app_ref t(m); + update_predicate(r.get_tail(i), t); + tail.push_back(t); + get_free_vars(t, sorts); + } + expr_ref_vector conjs = get_tail_conjs(r); + + m_solved_vars.reset(); + uint_set used_vars; + unsigned v; + expr_ref b(m); + + for (unsigned i = 0; i < conjs.size(); ++i) { + expr* e = conjs[i].get(); + if (is_eq(e, v, b)) { + if (v >= m_solved_vars.size()) { + m_solved_vars.resize(v+1); + } + if (v < sorts.size() && sorts[v]) { + TRACE("dl", tout << "already bound " << mk_pp(e, m) << "\n";); + add_free_vars(used_vars, e); + } + else if (m_solved_vars[v].get()) { + TRACE("dl", tout << "already solved " << mk_pp(e, m) << "\n";); + add_free_vars(used_vars, e); + add_free_vars(used_vars, m_solved_vars[v].get()); + used_vars.insert(v); + } + else { + TRACE("dl", tout << "new solution " << mk_pp(e, m) << "\n";); + m_solved_vars[v] = b; + } + } + else { + TRACE("dl", tout << "not solved " << mk_pp(e, m) << "\n";); + add_free_vars(used_vars, e); + } + } + for (unsigned i = 0; i < conjs.size(); ++i) { + expr* e = conjs[i].get(); + if (is_eq(e, v, b) && m_solved_vars[v].get() && !used_vars.contains(v)) { + TRACE("dl", tout << "removing conjunct: " << mk_pp(e, m) << "\n";); + // skip conjunct + } + else { + tail.push_back(to_app(e)); + } + } + + new_rule = rm.mk(head.get(), tail.size(), tail.c_ptr(), (const bool*) 0); + TRACE("dl", r.display(m_ctx, tout << "replacing:\n"); new_rule->display(m_ctx, tout << "by:\n");); + } + else { + new_rule = &r; + } + dst.add_rule(new_rule); + + if (m_pc) { + m_pc->insert(&r, new_rule, 0, 0); + } + } + + void mk_slice::update_rules(rule_set const& src, rule_set& dst) { + for (unsigned i = 0; i < src.get_num_rules(); ++i) { + update_rule(*src.get_rule(i), dst); + } + } + + rule_set * mk_slice::operator()(rule_set const & src, model_converter_ref& mc, proof_converter_ref& pc) { + ref spc; + ref smc; + if (pc) { + spc = alloc(slice_proof_converter, m_ctx); + } + if (mc) { + smc = alloc(slice_model_converter, *this, m); + } + m_pc = spc.get(); + m_mc = smc.get(); + reset(); + saturate(src); + declare_predicates(); + if (m_predicates.empty()) { + // nothing could be sliced. + return 0; + } + TRACE("dl", display(tout);); + rule_set* result = alloc(rule_set, m_ctx); + update_rules(src, *result); + TRACE("dl", result->display(tout);); + if (m_mc) { + obj_map::iterator it = m_sliceable.begin(), end = m_sliceable.end(); + for (; it != end; ++it) { + m_mc->add_sliceable(it->m_key, it->m_value); + } + } + pc = concat(pc.get(), spc.get()); + mc = concat(mc.get(), smc.get()); + return result; + } + +}; + diff --git a/lib/dl_mk_slice.h b/lib/dl_mk_slice.h new file mode 100644 index 000000000..ef331534d --- /dev/null +++ b/lib/dl_mk_slice.h @@ -0,0 +1,107 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_slice.h + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-10-4. + +Revision History: + +--*/ +#ifndef _DL_MK_SLICE_H_ +#define _DL_MK_SLICE_H_ + +#include"dl_context.h" +#include"dl_rule_set.h" +#include"uint_set.h" +#include"dl_rule_transformer.h" + +namespace datalog { + + /** + \brief Implements a slicing rule transformation. + */ + class mk_slice : public rule_transformer::plugin { + class slice_proof_converter; + class slice_model_converter; + context& m_ctx; + ast_manager& m; + rule_manager& rm; + svector m_input; + svector m_output; + expr_ref_vector m_solved_vars; + svector m_var_is_sliceable; + obj_map m_predicates; + obj_map m_sliceable; + ast_ref_vector m_pinned; + slice_proof_converter* m_pc; + slice_model_converter* m_mc; + + void reset(); + + void init(rule_set const& source); + + void saturate(rule_set const& source); + + void display(std::ostream& out); + + bool prune_rule(rule& r); + + void init_vars(rule& r); + + void init_vars(app* p, bool is_output, bool is_neg_tail); + + bool finalize_vars(app* p); + + unsigned num_vars() const { return m_input.size(); } + + bit_vector& get_predicate_slice(func_decl* p); + + bit_vector& get_predicate_slice(app* p) { return get_predicate_slice(p->get_decl()); } + + bool is_eq(expr* e, unsigned& v, expr_ref& r); + + void add_free_vars(uint_set& s, expr* e); + + void add_var(unsigned idx); + + bool is_output(expr* e); + + bool is_output(unsigned idx); + + void update_rules(rule_set const& src, rule_set& dst); + + void update_rule(rule& r, rule_set& dst); + + expr_ref_vector get_tail_conjs(rule const& r); + + void declare_predicates(); + + bool rule_updated(rule const& r); + + void update_predicate(app* p, app_ref& q); + + public: + /** + \brief Create slice rule transformer for \c goal predicate. When applying the transformer, + the \c goal must be present in the \c rule_set that is being transformed. + */ + mk_slice(context & ctx); + + rule_set * operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc); + + func_decl* get_predicate(func_decl* p) { func_decl* q = p; m_predicates.find(p, q); return q; } + }; + +}; + +#endif /* _DL_MK_SLICE_H_ */ + diff --git a/lib/dl_mk_subsumption_checker.cpp b/lib/dl_mk_subsumption_checker.cpp new file mode 100644 index 000000000..31d569796 --- /dev/null +++ b/lib/dl_mk_subsumption_checker.cpp @@ -0,0 +1,367 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_subsumption_checker.cpp + +Abstract: + + Rule transformer which checks for subsumption + (currently just for subsumption with total relations) + +Author: + + Krystof Hoder (t-khoder) 2011-10-01. + +Revision History: + +--*/ + + +#include +#include"ast_pp.h" + +#include "rewriter.h" +#include "rewriter_def.h" + + +#include"dl_mk_subsumption_checker.h" +#include"dl_table_relation.h" + +namespace datalog { + + + // ----------------------------------- + // + // mk_subsumption_checker + // + // ----------------------------------- + + + bool mk_subsumption_checker::is_total_rule(const rule * r) { + if(r->get_tail_size()!=0) { return false; } + + unsigned pt_len = r->get_positive_tail_size(); + if(pt_len!=r->get_uninterpreted_tail_size()) { + //we dont' expect rules with negative tails to be total + return false; + } + + for(unsigned i=0; iget_tail(i)->get_decl(); + if(!m_total_relations.contains(tail_pred)) { + //this rule has a non-total predicate in the tail + return false; + } + } + + unsigned t_len = r->get_positive_tail_size(); + for(unsigned i=pt_len; iis_neg_tail(i)); //we assume interpreted tail not to be negated + if(!m.is_true(r->get_tail(i))) { + //this rule has an interpreted tail which is not constant true + return false; + } + } + + var_idx_set head_vars; + app * head = r->get_head(); + unsigned arity = head->get_num_args(); + for(unsigned i=0; iget_arg(i); + if(!is_var(arg)) { return false; } + unsigned idx = to_var(arg)->get_idx(); + if(head_vars.contains(idx)) { return false; } + head_vars.insert(idx); + } + SASSERT(head_vars.num_elems()==arity); + return true; + } + + void mk_subsumption_checker::on_discovered_total_relation(func_decl * pred, rule * r) { + //this should be rule marking a new relation as total + SASSERT(!m_total_relations.contains(pred)); + SASSERT(!r || pred==r->get_head()->get_decl()); + SASSERT(!r || is_total_rule(r)); + + m_total_relations.insert(pred); + m_total_relation_defining_rules.insert(pred, r); + m_have_new_total_rule = true; + if(r) { + m_ref_holder.push_back(r); + } + } + + void mk_subsumption_checker::scan_for_total_rules(const rule_set & rules) { + bool new_discovered; + //we cycle through the rules until we keep discovering new total relations + //(discovering a total relation migh reveal other total relations) + do { + new_discovered = false; + rule_set::iterator rend = rules.end(); + for(rule_set::iterator rit = rules.begin(); rit!=rend; ++rit) { + rule * r = *rit; + func_decl * head_pred = r->get_head()->get_decl(); + if(is_total_rule(r) && !m_total_relations.contains(head_pred)) { + on_discovered_total_relation(head_pred, r); + new_discovered = true; + } + } + } while(new_discovered); + } + + + bool mk_subsumption_checker::transform_rule(rule * r, + rule_subsumption_index& subs_index, rule_ref & res) + { + unsigned u_len = r->get_uninterpreted_tail_size(); + unsigned len = r->get_tail_size(); + if(u_len==0) { + res = r; + return true; + } + app_ref head(r->get_head(), m); + + app_ref_vector tail(m); + svector tail_neg; + + for(unsigned i=0; iget_tail(i); + bool neg = r->is_neg_tail(i); + if(m_total_relations.contains(tail_atom->get_decl()) + || subs_index.is_subsumed(tail_atom)) { + if(neg) { + //rule contains negated total relation, this means that it is unsatisfiable + //and can be removed + return false; + } + else { + //we remove total relations from the tail + continue; + } + } + if(!neg && head.get()==tail_atom) { + //rule contains its head positively in the tail, therefore + //it will never add any new facts to the relation, so it + //can be removed + return false; + } + tail.push_back(tail_atom); + tail_neg.push_back(neg); + } + + if(tail.size()==u_len) { + res = r; + return true; + } + + //we just copy the interpreted part of the tail + for(unsigned i=u_len; iget_tail(i)); + tail_neg.push_back(r->is_neg_tail(i)); + } + + SASSERT(tail.size()==tail_neg.size()); + res = m_context.get_rule_manager().mk(head, tail.size(), tail.c_ptr(), tail_neg.c_ptr()); + res->set_accounting_parent_object(m_context, r); + m_context.get_rule_manager().fix_unbound_vars(res, true); + return true; + } + + bool rule_size_comparator(rule * r1, rule * r2) { + return r1->get_tail_size() < r2->get_tail_size(); + } + + bool mk_subsumption_checker::transform_rules(const rule_set & orig, rule_set & tgt) { + + bool modified = false; + + func_decl_set total_relations_with_included_rules; + + rule_subsumption_index subs_index(m_context); + + rule_ref_vector orig_rules(m_context.get_rule_manager()); + orig_rules.append(orig.get_num_rules(), orig.begin()); + + rule * * rbegin = orig_rules.c_ptr(); + rule * * rend = rbegin + orig_rules.size(); + + //before traversing we sort rules so that the shortest are in the beginning. + //this will help make subsumption checks more efficient + std::sort(rbegin, rend, rule_size_comparator); + + for(rule_set::iterator rit = rbegin; rit!=rend; ++rit) { + + rule * r = *rit; + func_decl * head_pred = r->get_head()->get_decl(); + + if(m_total_relations.contains(head_pred)) { + if(!m_context.is_output_predicate(head_pred) || + total_relations_with_included_rules.contains(head_pred)) { + //We just skip definitions of total non-output relations as + //we'll eliminate them from the problem. + //We also skip rules of total output relations for which we have + //already output the rule which implies their totality. + modified = true; + continue; + } + rule * defining_rule; + TRUSTME(m_total_relation_defining_rules.find(head_pred, defining_rule)); + if(defining_rule) { + rule_ref totality_rule(m_context.get_rule_manager()); + TRUSTME(transform_rule(defining_rule, subs_index, totality_rule)); + if(defining_rule!=totality_rule) { + modified = true; + } + tgt.add_rule(totality_rule); + SASSERT(totality_rule->get_head()->get_decl()==head_pred); + } + else { + modified = true; + } + total_relations_with_included_rules.insert(head_pred); + continue; + } + + rule_ref new_rule(m_context.get_rule_manager()); + if(!transform_rule(r, subs_index, new_rule)) { + modified = true; + continue; + } + if(m_new_total_relation_discovery_during_transformation && is_total_rule(new_rule)) { + on_discovered_total_relation(head_pred, new_rule.get()); + } + if(subs_index.is_subsumed(new_rule)) { + modified = true; + continue; + } + if(new_rule.get()!=r) { + modified = true; + } + tgt.add_rule(new_rule); + subs_index.add(new_rule); + } + TRACE("dl", + tout << "original set size: "<knows_exact_size()) { continue; } + + unsigned arity = pred->get_arity(); + if(arity>30) { continue; } + + //for now we only check booleans domains + for(unsigned i=0; iget_domain(i))) { + goto next_pred; + } + } + + { + unsigned total_size = 1<get_size_estimate_rows(); + + obj_hashtable * head_store; + if(m_ground_unconditional_rule_heads.find(pred, head_store)) { + //Some relations may receive facts by ground unconditioned rules. + //We scanned for those earlier, so now we check whether we cannot get a + //better estimate of relation size from these. + + unsigned gnd_rule_cnt = head_store->size(); + if(gnd_rule_cnt>rel_sz) { + rel_sz = gnd_rule_cnt; + } + } + + SASSERT(total_size>=rel_sz); + if(total_size==rel_sz) { + on_discovered_total_relation(pred, 0); + } + } + next_pred:; + } + } + + void mk_subsumption_checker::collect_ground_unconditional_rule_heads(const rule_set & rules) + { + rule_set::iterator rend = rules.end(); + for(rule_set::iterator rit = rules.begin(); rit!=rend; ++rit) { + rule * r = *rit; + func_decl * pred = r->get_head()->get_decl(); + + if(r->get_tail_size()!=0) { continue; } + + + app * head = r->get_head(); + unsigned arity = pred->get_arity(); + for(unsigned i=0; iget_arg(i); + if(!is_app(arg)) { + goto next_rule; + } + } + + if(!m_ground_unconditional_rule_heads.contains(pred)) { + m_ground_unconditional_rule_heads.insert(pred, alloc(obj_hashtable)); + } + obj_hashtable * head_store; + m_ground_unconditional_rule_heads.find(pred, head_store); + head_store->insert(head); + + next_rule:; + } + } + + rule_set * mk_subsumption_checker::operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc) { + // TODO mc, pc + + m_have_new_total_rule = false; + collect_ground_unconditional_rule_heads(source); + scan_for_relations_total_due_to_facts(); + scan_for_total_rules(source); + + m_have_new_total_rule = false; + rule_set * res = alloc(rule_set, m_context); + bool modified = transform_rules(source, *res); + + if(!m_have_new_total_rule && !modified) { + dealloc(res); + return 0; + } + + + //During the construction of the new set we may discover new total relations + //(by quantifier elimination on the uninterpreted tails). + SASSERT(m_new_total_relation_discovery_during_transformation || !m_have_new_total_rule); + while(m_have_new_total_rule) { + m_have_new_total_rule = false; + + rule_set * old = res; + res = alloc(rule_set, m_context); + transform_rules(*old, *res); + dealloc(old); + } + + return res; + } + +}; + diff --git a/lib/dl_mk_subsumption_checker.h b/lib/dl_mk_subsumption_checker.h new file mode 100644 index 000000000..ce33e7574 --- /dev/null +++ b/lib/dl_mk_subsumption_checker.h @@ -0,0 +1,93 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + mk_subsumption_checker.h + +Abstract: + + Rule transformer which checks for subsumption + (currently just for subsumption with total relations) + +Author: + + Krystof Hoder (t-khoder) 2011-10-01. + +Revision History: + +--*/ + +#ifndef _DL_MK_SUBSUMPTION_CHECKER_H_ +#define _DL_MK_SUBSUMPTION_CHECKER_H_ + +#include "dl_context.h" +#include "dl_rule_transformer.h" +#include "dl_rule_subsumption_index.h" + +namespace datalog { + + class mk_subsumption_checker : public rule_transformer::plugin { + + + ast_manager & m; + context & m_context; + + rule_ref_vector m_ref_holder; + + func_decl_set m_total_relations; + + /** Map that for each relation contains the rule which implies its totality. + If the totality is due to the relation containing all facts, the rule stored + here is zero*/ + obj_map m_total_relation_defining_rules; + + + /** + Contains heads of rules of shape + R(c1,c2,...cN). + grouped by their predicate. + + This information helps to improve the results of the + scan_for_relations_total_due_to_facts() function. + */ + obj_map *> m_ground_unconditional_rule_heads; + + + bool m_have_new_total_rule; + bool m_new_total_relation_discovery_during_transformation; + + bool is_total_rule(const rule * r); + + + + /** Function to be called when a new total relation is discovered */ + void on_discovered_total_relation(func_decl * pred, rule * r); + + void scan_for_total_rules(const rule_set & rules); + void scan_for_relations_total_due_to_facts(); + + void collect_ground_unconditional_rule_heads(const rule_set & rules); + + /** Return false if rule is unsatisfiable */ + bool transform_rule(rule * r, rule_subsumption_index& subs_index, rule_ref & res); + /** Return false if the rule set hasn't changed */ + bool transform_rules(const rule_set & orig, rule_set & tgt); + public: + mk_subsumption_checker(context & ctx, unsigned priority=31000) + : plugin(priority), + m(ctx.get_manager()), + m_context(ctx), + m_ref_holder(ctx.get_rule_manager()), + m_new_total_relation_discovery_during_transformation(true) {} + ~mk_subsumption_checker() { + reset_dealloc_values(m_ground_unconditional_rule_heads); + } + + rule_set * operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc); + }; + +}; + +#endif /* _DL_MK_SUBSUMPTION_CHECKER_H_ */ + diff --git a/lib/dl_mk_unbound_compressor.cpp b/lib/dl_mk_unbound_compressor.cpp new file mode 100644 index 000000000..6f15260ea --- /dev/null +++ b/lib/dl_mk_unbound_compressor.cpp @@ -0,0 +1,383 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_unbound_compressor.cpp + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-10-04. + +Revision History: + +--*/ + +#include +#include +#include"dl_mk_unbound_compressor.h" + +namespace datalog { + + mk_unbound_compressor::mk_unbound_compressor(context & ctx) : + plugin(500), + m_context(ctx), + m_manager(ctx.get_manager()), + m_rules(ctx.get_rule_manager()), + m_pinned(m_manager) { + } + + void mk_unbound_compressor::reset() { + m_rules.reset(); + m_todo.reset(); + m_in_progress.reset(); + m_map.reset(); + m_pinned.reset(); + } + + bool mk_unbound_compressor::is_unbound_argument(rule * r, unsigned head_index) { + app * head = r->get_head(); + expr * head_arg = head->get_arg(head_index); + if (!is_var(head_arg)) { + return false; + } + unsigned var_idx = to_var(head_arg)->get_idx(); + + var_idx_set tail_vars; + collect_tail_vars(m_manager, r, tail_vars); + + return tail_vars.contains(var_idx); + } + + void mk_unbound_compressor::add_task(func_decl * pred, unsigned arg_index) { + c_info ci = c_info(pred, arg_index); + if (m_map.contains(ci)) { + return; //this task was already added + } + + unsigned parent_arity = pred->get_arity(); + sort * const * parent_domain = pred->get_domain(); + symbol const& parent_name = pred->get_name(); + unsigned arity = parent_arity-1; + ptr_vector domain; + for (unsigned i = 0; i < parent_arity; i++) { + if (i != arg_index) { + domain.push_back(parent_domain[i]); + } + } + + std::stringstream name_suffix; + name_suffix << "compr_arg_" << arg_index; + + func_decl * cpred = m_context.mk_fresh_head_predicate(parent_name, symbol(name_suffix.str().c_str()), + arity, domain.c_ptr(), pred); + m_pinned.push_back(cpred); + + m_todo.push_back(ci); + m_map.insert(ci, cpred); + } + + void mk_unbound_compressor::detect_tasks(unsigned rule_index) { + rule * r = m_rules.get(rule_index); + var_idx_set tail_vars; + collect_tail_vars(m_manager, r, tail_vars); + + app * head = r->get_head(); + func_decl * head_pred = head->get_decl(); + if (m_context.is_output_predicate(head_pred)) { + //we don't compress output predicates + return; + } + + unsigned n = head_pred->get_arity(); + + var_counter head_var_counter; + head_var_counter.count_vars(m_manager, head, 1); + + for (unsigned i=0; iget_arg(i); + if (!is_var(arg)) { + continue; + } + unsigned var_idx = to_var(arg)->get_idx(); + if (!tail_vars.contains(var_idx)) { + //unbound + + unsigned occurence_cnt = head_var_counter.get(var_idx); + SASSERT(occurence_cnt>0); + if (occurence_cnt == 1) { + TRACE("dl", r->display(m_context, tout << "Compress: ");); + add_task(head_pred, i); + return; //we compress out the unbound arguments one by one + } + } + } + } + + void mk_unbound_compressor::try_compress(unsigned rule_index) { + start: + rule * r = m_rules.get(rule_index); + var_idx_set tail_vars; + collect_tail_vars(m_manager, r, tail_vars); + + app * head = r->get_head(); + func_decl * head_pred = head->get_decl(); + unsigned head_arity = head_pred->get_arity(); + + var_counter head_var_counter; + head_var_counter.count_vars(m_manager, head); + + unsigned arg_index; + for (arg_index = 0; arg_index < head_arity; arg_index++) { + expr * arg = head->get_arg(arg_index); + if (!is_var(arg)) { + continue; + } + unsigned var_idx = to_var(arg)->get_idx(); + if (!tail_vars.contains(var_idx)) { + //unbound + unsigned occurence_cnt = head_var_counter.get(var_idx); + SASSERT(occurence_cnt>0); + if ( occurence_cnt==1 && m_in_progress.contains(c_info(head_pred, arg_index)) ) { + //we have found what to compress + break; + } + } + } + if (arg_index == head_arity) { + //we didn't find anything to compress + return; + } + SASSERT(arg_index cargs; + for (unsigned i=0; iget_arg(i)); + } + } + + app_ref chead(m_manager.mk_app(cpred, head_arity-1, cargs.c_ptr()), m_manager); + + if (r->get_tail_size()==0 && m_context.get_rule_manager().is_fact(chead)) { + m_non_empty_rels.insert(cpred); + m_context.add_fact(chead); + //remove the rule that became fact by placing the last rule on its place + m_head_occurrence_ctr.dec(m_rules.get(rule_index)->get_head()->get_decl()); + m_rules.set(rule_index, m_rules.get(m_rules.size()-1)); + m_rules.shrink(m_rules.size()-1); + //since we moved the last rule to rule_index, we have to try to compress it as well + if (rule_indexset_accounting_parent_object(m_context, r); + + m_head_occurrence_ctr.dec(m_rules.get(rule_index)->get_head()->get_decl()); + m_rules.set(rule_index, new_rule); + m_head_occurrence_ctr.inc(m_rules.get(rule_index)->get_head()->get_decl()); + detect_tasks(rule_index); + } + + m_modified = true; + } + + void mk_unbound_compressor::mk_decompression_rule(rule * r, unsigned tail_index, unsigned arg_index, + rule_ref& res) + { + app * orig_dtail = r->get_tail(tail_index); //dtail ~ decompressed tail + c_info ci(orig_dtail->get_decl(), arg_index); + func_decl * dtail_pred; + TRUSTME( m_map.find(ci, dtail_pred) ); + ptr_vector dtail_args; + unsigned orig_dtail_arity = orig_dtail->get_num_args(); + for (unsigned i=0;iget_arg(i)); + } + } + SASSERT(dtail_args.size()==dtail_pred->get_arity()); + app_ref dtail(m_manager.mk_app(dtail_pred, dtail_args.size(), dtail_args.c_ptr()), m_manager); + + svector tails_negated; + app_ref_vector tails(m_manager); + unsigned tail_len = r->get_tail_size(); + for (unsigned i=0; iis_neg_tail(i)); + if (i==tail_index && !r->is_neg_tail(i)) { + tails.push_back(dtail); + } + else { + tails.push_back(r->get_tail(i)); + } + } + + // Accumulate negated filtered rule instead + // of replacing the original predicate. + if (r->is_neg_tail(tail_index)) { + tails_negated.push_back(true); + tails.push_back(dtail); + } + + res = m_context.get_rule_manager().mk( r->get_head(), tails.size(), tails.c_ptr(), tails_negated.c_ptr()); + res->set_accounting_parent_object(m_context, r); + m_context.get_rule_manager().fix_unbound_vars(res, true); + } + + void mk_unbound_compressor::add_decompression_rule(rule * r, unsigned tail_index, unsigned arg_index) { + rule_ref new_rule(m_context.get_rule_manager()); + mk_decompression_rule(r, tail_index, arg_index, new_rule); + + unsigned new_rule_index = m_rules.size(); + m_rules.push_back(new_rule); + m_head_occurrence_ctr.inc(new_rule->get_head()->get_decl()); + + + detect_tasks(new_rule_index); + + m_modified = true; + + //TODO: avoid rule duplicity + //If two predicates are compressed in a rule, applying decompression + //to the results can cause a rule being added multiple times: + //P:- R(x,y), S(x,y) + //is decompressed into rules + //P:- R1(x), S(x,y) + //P:- R(x,y), S1(x) + //and each of these rules is again decompressed giving the same rule + //P:- R1(x), S1(x) + //P:- R1(x), S1(x) + } + + void mk_unbound_compressor::replace_by_decompression_rule(unsigned rule_index, unsigned tail_index, unsigned arg_index) + { + rule * r = m_rules.get(rule_index); + + rule_ref new_rule(m_context.get_rule_manager()); + mk_decompression_rule(r, tail_index, arg_index, new_rule); + + m_rules.set(rule_index, new_rule); + + //we don't update the m_head_occurrence_ctr because the head predicate doesn't change + + detect_tasks(rule_index); + + m_modified = true; + } + + void mk_unbound_compressor::add_decompression_rules(unsigned rule_index) { + + unsigned_vector compressed_tail_pred_arg_indexes; + + //this value is updated inside the loop if replace_by_decompression_rule is called + rule_ref r(m_rules.get(rule_index), m_context.get_rule_manager()); + + unsigned utail_len = r->get_uninterpreted_tail_size(); + unsigned tail_index=0; + while (tail_indexget_tail(tail_index); + func_decl * t_pred = t->get_decl(); + unsigned t_arity = t_pred->get_arity(); + bool is_negated_predicate = r->is_neg_tail(tail_index); + compressed_tail_pred_arg_indexes.reset(); + for (unsigned arg_index=0; arg_indexget_uninterpreted_tail_size() >= utail_len); + //here we check that the rule replacement didn't affect other uninterpreted literals + //in the tail (aside of variable renaming) + SASSERT(tail_index==0 || + new_rule->get_tail(tail_index-1)->get_decl()==r->get_tail(tail_index-1)->get_decl()); + + r = new_rule; + + //we have replaced the original rule, with one that has different + //content of the tail_index -th tail. we will therefore not do + //tail_index++, so that we examine the new tail literal as well + } + else { + tail_index++; + } + } + } + + rule_set * mk_unbound_compressor::operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc) { + // TODO mc, pc + m_modified = false; + + m_context.get_rmanager().collect_non_empty_predicates(m_non_empty_rels); + + unsigned init_rule_cnt = source.get_num_rules(); + SASSERT(m_rules.empty()); + for (unsigned i=0; iget_head()->get_decl()); + } + + for (unsigned i=0; i(0); + if (m_modified) { + result = alloc(rule_set, m_context); + unsigned fin_rule_cnt = m_rules.size(); + for (unsigned i=0; iadd_rule(m_rules.get(i)); + } + } + reset(); + return result; + } + + +}; diff --git a/lib/dl_mk_unbound_compressor.h b/lib/dl_mk_unbound_compressor.h new file mode 100644 index 000000000..0e13bad75 --- /dev/null +++ b/lib/dl_mk_unbound_compressor.h @@ -0,0 +1,91 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_unbound_compressor.h + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-10-4. + +Revision History: + +--*/ +#ifndef _DL_MK_UNBOUND_COMPRESSOR_H_ +#define _DL_MK_UNBOUND_COMPRESSOR_H_ + +#include + +#include"map.h" +#include"obj_pair_hashtable.h" + +#include"dl_context.h" +#include"dl_rule_set.h" +#include"dl_rule_transformer.h" + +namespace datalog { + + /** + \brief Functor for introducing auxiliary predicates to avoid unbound variables in + rule heads. + + A rule + P(x,_) :- T(x). + is replaced by + P1(x) :- T(x). + and for each occurrence of P in a tail of a rule, a new rule is added with P1 in + its place. + */ + class mk_unbound_compressor : public rule_transformer::plugin { + /** predicate and index of compressed argument */ + typedef std::pair c_info; + typedef pair_hash,unsigned_hash> c_info_hash; + /** predicates that are results of compression */ + typedef map > c_map; + typedef hashtable > in_progress_table; + typedef svector todo_stack; + + context & m_context; + ast_manager & m_manager; + rule_ref_vector m_rules; + bool m_modified; + todo_stack m_todo; + in_progress_table m_in_progress; + c_map m_map; + + /** + Relations that contain facts + */ + decl_set m_non_empty_rels; + + ast_counter m_head_occurrence_ctr; + + ast_ref_vector m_pinned; + + + bool is_unbound_argument(rule * r, unsigned head_index); + bool has_unbound_head_var(rule * r); + + void detect_tasks(unsigned rule_index); + void add_task(func_decl * pred, unsigned arg_index); + void try_compress(unsigned rule_index); + void add_decompression_rules(unsigned rule_index); + void mk_decompression_rule(rule * r, unsigned tail_index, unsigned arg_index, rule_ref& res); + void add_decompression_rule(rule * r, unsigned tail_index, unsigned arg_index); + void replace_by_decompression_rule(unsigned rule_index, unsigned tail_index, unsigned arg_index); + void reset(); + public: + mk_unbound_compressor(context & ctx); + + rule_set * operator()(rule_set const & source, model_converter_ref& mc, proof_converter_ref& pc); + }; + +}; + +#endif /* _DL_MK_UNBOUND_COMPRESSOR_H_ */ + diff --git a/lib/dl_product_relation.cpp b/lib/dl_product_relation.cpp new file mode 100644 index 000000000..66074d463 --- /dev/null +++ b/lib/dl_product_relation.cpp @@ -0,0 +1,1119 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + dl_product_relation.cpp + +Abstract: + + A Relation combinator. + +Author: + + Nikolaj Bjorner (nbjorner) 2010-4-11 + +Revision History: + +Notes: + + join = + more refined version lets augment the product + relation as a consequence of join. + join Q = + join = + + + u = + more refined version: + < (R u R') n (R u S') n (R' u S), (S u S') n (S u R') n (S' u R)> + + + proj = < proj R, proj S> + + & phi = + attach S to [R & phi] whenever R & phi can propagate to S + + + [rename] = + + + +--*/ + + +#include "dl_sieve_relation.h" +#include "dl_table_relation.h" +#include "dl_product_relation.h" +#include "bool_rewriter.h" +#include "ast_pp.h" + +namespace datalog { + + // ----------------------------------- + // + // product_relation_plugin + // + // ----------------------------------- + + product_relation_plugin & product_relation_plugin::get_plugin(relation_manager & rmgr) { + product_relation_plugin * res = + static_cast(rmgr.get_relation_plugin(get_name())); + if(!res) { + res = alloc(product_relation_plugin, rmgr); + rmgr.register_plugin(res); + } + return *res; + } + + product_relation_plugin::product_relation_plugin(relation_manager& m): + relation_plugin(product_relation_plugin::get_name(), m, ST_PRODUCT_RELATION), + m_spec_store(*this) { + } + + void product_relation_plugin::initialize(family_id fid) { + relation_plugin::initialize(fid); + m_spec_store.add_available_kind(get_kind()); + } + + family_id product_relation_plugin::get_relation_kind(const relation_signature & sig, const rel_spec & spec) { + return m_spec_store.get_relation_kind(sig, spec); + } + + family_id product_relation_plugin::get_relation_kind(const product_relation & r) { + return get_relation_kind(r.get_signature(), r.m_spec); + } + + bool product_relation_plugin::can_handle_signature(const relation_signature & s) { + return m_spec_store.contains_signature(s); + } + + bool product_relation_plugin::can_handle_signature(const relation_signature & s, family_id k) { + return true; + } + + product_relation& product_relation_plugin::get(relation_base& r) { + return dynamic_cast(r); + } + + product_relation const & product_relation_plugin::get(relation_base const& r) { + return dynamic_cast(r); + } + + product_relation* product_relation_plugin::get(relation_base* r) { + return dynamic_cast(r); + } + + product_relation const* product_relation_plugin::get(relation_base const* r) { + return dynamic_cast(r); + } + + bool product_relation_plugin::is_product_relation(relation_base const& r) { + return r.get_plugin().get_name() == product_relation_plugin::get_name(); + } + + bool product_relation_plugin::are_aligned(const product_relation& r1, const product_relation& r2) { + unsigned sz = r1.size(); + if(sz!=r2.size()) { + return false; + } + for(unsigned i=0; i & rels, + rel_spec & res) { + vector specs; + ptr_vector::const_iterator rit = rels.begin(); + ptr_vector::const_iterator rend = rels.end(); + for(; rit!=rend; ++rit) { + specs.push_back((*rit)->m_spec); + } + + vector::iterator sit = specs.begin(); + vector::iterator send = specs.end(); + for(; sit!=send; ++sit) { + rel_spec & s = *sit; + std::sort(s.begin(), s.end()); + } + + res.reset(); + for(;;) { + family_id next = -1; + + sit = specs.begin(); + for(; sit!=send; ++sit) { + rel_spec & s = *sit; + if(!s.empty() && s.back()>next) { + next = s.back(); + } + } + if(next==-1) { + //we're done + break; + } + res.push_back(next); + sit = specs.begin(); + for(; sit!=send; ++sit) { + rel_spec & s = *sit; + if(!s.empty() && s.back()==next) { + s.pop_back(); + } + } + } + } + + + relation_base * product_relation_plugin::mk_empty(const relation_signature & s) { + return alloc(product_relation,*this, s); + } + + relation_base * product_relation_plugin::mk_empty(const relation_signature & s, family_id kind) { + rel_spec spec; + relation_signature sig_empty; + m_spec_store.get_relation_spec(sig_empty, kind, spec); + relation_vector inner_rels; + unsigned rel_cnt = spec.size(); + for(unsigned i=0; i m_joins; + ptr_vector m_full; + unsigned_vector m_offset1; + svector m_kind1; + unsigned_vector m_offset2; + svector m_kind2; + + const relation_base & get_nonsieve_relation(const relation_base & r) { + relation_plugin & rp = r.get_plugin(); + if(rp.is_sieve_relation()) { + return static_cast(r).get_inner(); + } + else { + return r; + } + } + + relation_plugin & get_nonsieve_plugin(const relation_base & r) { + return get_nonsieve_relation(r).get_plugin(); + } + + family_id get_nonsieve_kind(const relation_base & r) { + return get_nonsieve_relation(r).get_kind(); + } + + /** + A tableish relatio is either a table_relation or a sieve_relation with a table_relation inside. + */ + bool is_tableish_relation(const relation_base & r) { + return get_nonsieve_plugin(r).from_table(); + } + + relation_base * get_full_tableish_relation(const relation_signature & sig, func_decl* p, family_id kind) { + relation_manager& rmgr = m_plugin.get_manager(); + table_signature tsig; + if(rmgr.relation_signature_to_table(sig, tsig)) { + return rmgr.mk_table_relation(sig, rmgr.get_appropriate_plugin(tsig).mk_full(p, tsig, kind)); + } + unsigned sz = sig.size(); + tsig.reset(); + for(unsigned i=0; i relations; + unsigned sz = m_joins.size(); + relation_base* result = 0; + for (unsigned i = 0; i < sz; ++i) { + relation_base const& r1 = (m_kind1[i] == T_FULL)?(*m_full[m_offset1[i]]):access(m_offset1[i], _r1); + relation_base const& r2 = (m_kind2[i] == T_FULL)?(*m_full[m_offset2[i]]):access(m_offset2[i], _r2); + relations.push_back((*m_joins[i])(r1, r2)); + } + result = alloc(product_relation, m_plugin, get_result_signature(), sz, relations.c_ptr()); + TRACE("dl",result->display(tout);); + return result; + } + }; + + relation_join_fn * product_relation_plugin::mk_join_fn(const relation_base & r1, const relation_base & r2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2) { + if (is_product_relation(r1) && is_product_relation(r2)) { + return alloc(join_fn, *this, get(r1), get(r2), col_cnt, cols1, cols2); + } + if (is_product_relation(r1)) { + return alloc(join_fn, *this, get(r1), r2, col_cnt, cols1, cols2); + } + if (is_product_relation(r2)) { + return alloc(join_fn, *this, r1, get(r2), col_cnt, cols1, cols2); + } + if (r1.get_kind() != r2.get_kind()) { + return alloc(join_fn, *this, r1, r2, col_cnt, cols1, cols2); + } + return 0; + } + + + class product_relation_plugin::transform_fn : public relation_transformer_fn { + relation_signature m_sig; + ptr_vector m_transforms; + public: + transform_fn(relation_signature s, unsigned num_trans, relation_transformer_fn** trans): + m_sig(s), + m_transforms(num_trans, trans) {} + + ~transform_fn() { dealloc_ptr_vector_content(m_transforms); } + + virtual relation_base * operator()(const relation_base & _r) { + product_relation const& r = get(_r); + product_relation_plugin& p = r.get_plugin(); + SASSERT(m_transforms.size() == r.size()); + ptr_vector relations; + for (unsigned i = 0; i < r.size(); ++i) { + relations.push_back((*m_transforms[i])(r[i])); + } + relation_base* result = alloc(product_relation, p, m_sig, relations.size(), relations.c_ptr()); + TRACE("dl", _r.display(tout); result->display(tout);); + return result; + } + }; + + relation_transformer_fn * product_relation_plugin::mk_project_fn(const relation_base & _r, + unsigned col_cnt, const unsigned * removed_cols) { + if (is_product_relation(_r)) { + product_relation const& r = get(_r); + ptr_vector projs; + for (unsigned i = 0; i < r.size(); ++i) { + projs.push_back(get_manager().mk_project_fn(r[i], col_cnt, removed_cols)); + } + relation_signature s; + relation_signature::from_project(r.get_signature(), col_cnt, removed_cols, s); + return alloc(transform_fn, s, projs.size(), projs.c_ptr()); + } + return 0; + } + + relation_transformer_fn * product_relation_plugin::mk_rename_fn(const relation_base & _r, + unsigned cycle_len, const unsigned * permutation_cycle) { + if(is_product_relation(_r)) { + ptr_vector trans; + product_relation const& r = get(_r); + for (unsigned i = 0; i < r.size(); ++i) { + trans.push_back(get_manager().mk_rename_fn(r[i], cycle_len, permutation_cycle)); + } + relation_signature s; + relation_signature::from_rename(r.get_signature(), cycle_len, permutation_cycle, s); + return alloc(transform_fn, s, trans.size(), trans.c_ptr()); + } + return 0; + } + + class product_relation_plugin::aligned_union_fn : public relation_union_fn { + relation_manager & m_rmgr; + bool m_is_widen; + + //m_union[i][j] is union between i-th and j-th relation. + //It can be zero which means that particular union should be skipped. + vector > m_unions; + + void mk_union_fn(unsigned i, unsigned j, relation_base const& r1, relation_base const& r2, + const relation_base* delta) { + relation_manager& rmgr = r1.get_manager(); + relation_union_fn* u = 0; + if (m_is_widen) { + u = rmgr.mk_widen_fn(r1, r2, delta); + } + else { + u = rmgr.mk_union_fn(r1, r2, delta); + } + m_unions.back().push_back(u); + } + + void init(const relation_vector & tgts, const relation_vector & srcs, const relation_vector * deltas) { + SASSERT(tgts.size()==srcs.size()); + unsigned num = tgts.size(); + for (unsigned i = 0; i < num; ++i) { + relation_base& r1 = *tgts[i]; + relation_base* delta = deltas ? (*deltas)[i] : 0; + m_unions.push_back(ptr_vector()); + for (unsigned j = 0; j < num; ++j) { + relation_base& r2 = *srcs[j]; + mk_union_fn(i, j, r1, r2, delta); + } + } + } + + bool can_do_inner_union(unsigned tgt_idx, unsigned src_idx) { + return m_unions[tgt_idx][src_idx]!=0; + } + + void do_inner_union(unsigned tgt_idx, unsigned src_idx, relation_base& tgt, + relation_base& src, relation_base * delta) { + SASSERT(m_unions[tgt_idx][src_idx]); + (*m_unions[tgt_idx][src_idx])(tgt, src, delta); + } + + /** + If tgt is zero, it is assumed to be a full relation. + */ + void do_destructive_intersection(scoped_rel& tgt, scoped_rel& src) { + if(!src) { + return; + } + if(!tgt) { + tgt=src.release(); + return; + } + do_intersection(*tgt, *src); + src = 0; + } + + void do_intersection(relation_base& tgt, relation_base& src) { + scoped_ptr intersect_fun = + m_rmgr.mk_filter_by_intersection_fn(tgt, src); + if(!intersect_fun) { + warning_msg("intersection does not exist"); + return; + } + (*intersect_fun)(tgt, src); + } + void do_delta_union(unsigned rel_idx, relation_base& tgt, relation_base& src) { + scoped_ptr union_fun = m_rmgr.mk_union_fn(tgt, src); + SASSERT(union_fun); + (*union_fun)(tgt, src); + } + public: + aligned_union_fn(product_relation const& tgt, product_relation const& src, product_relation const* delta, + bool is_widen) : + m_rmgr(tgt.get_manager()), + m_is_widen(is_widen) { + SASSERT(vectors_equal(tgt.m_spec, src.m_spec)); + SASSERT(!delta || vectors_equal(tgt.m_spec, delta->m_spec)); + init(tgt.m_relations, src.m_relations, delta ? &delta->m_relations : 0); + } + + ~aligned_union_fn() { + unsigned sz = m_unions.size(); + for(unsigned i=0; i side_results; + ptr_vector side_deltas; + + for (unsigned i = 0; i < num; ++i) { + relation_base& itgt = tgt[i]; + relation_base* idelta = delta ? &(*delta)[i] : 0; + + scoped_rel fresh_delta = idelta ? idelta->get_plugin().mk_empty(*idelta) : 0; + scoped_rel side_result; + scoped_rel side_delta; + + //compute the side unions with which we will intersect the result of the basic one + for (unsigned j = 0; j < num; ++j) { + if (i == j) { + continue; //this is the basic union which we will perform later + } + if (can_do_inner_union(i, j)) { + TRACE("dl", itgt.display(tout << "tgt:\n"); src[j].display(tout << "src:\n");); + // union[i][j] + scoped_rel one_side_union = itgt.clone(); + scoped_rel one_side_delta = fresh_delta ? fresh_delta->clone() : 0; + TRACE("dl", one_side_union->display(tout << "union 1:\n"); src[j].display(tout);); + do_inner_union(i, j, *one_side_union, src[j], one_side_delta.get()); + TRACE("dl", one_side_union->display(tout << "union:\n");); + do_destructive_intersection(side_result, one_side_union); + TRACE("dl", + side_result->display(tout << "inner-union: " << i << " " << j << "\n"); + itgt.display(tout << "tgt:\n");); + if (one_side_delta) { + do_destructive_intersection(side_delta, one_side_delta); + } + + // union[j][i] + one_side_union = src[i].clone(); + one_side_delta = fresh_delta ? fresh_delta->clone() : 0; + TRACE("dl", one_side_union->display(tout << "union 2:\n"); tgt[j].display(tout);); + do_inner_union(i, j, *one_side_union, tgt[j], one_side_delta.get()); + TRACE("dl", one_side_union->display(tout << "union:\n");); + do_destructive_intersection(side_result, one_side_union); + TRACE("dl", + side_result->display(tout << "inner-union: " << i << " " << j << "\n"); + itgt.display(tout << "tgt:\n");); + if (one_side_delta) { + do_destructive_intersection(side_delta, one_side_delta); + } + } + } + side_results.push_back(side_result.release()); + side_deltas.push_back(side_delta.release()); + } + for (unsigned i = 0; i < num; ++i) { + relation_base& itgt = tgt[i]; + relation_base* idelta = delta ? &(*delta)[i] : 0; + scoped_rel fresh_delta = idelta ? idelta->get_plugin().mk_empty(*idelta) : 0; + scoped_rel side_result(side_results[i]); + scoped_rel side_delta(side_deltas[i]); + + // perform the basic union + // assume a relation can always perform union with the relation of the same type + VERIFY(can_do_inner_union(i,i)); + do_inner_union(i, i, itgt, src[i], fresh_delta.get()); + + if (side_result) { + do_intersection(itgt, *side_result); + TRACE("dl", side_result->display(tout << "inner-union-end: " << i << "\n");); + } + if (fresh_delta) { + do_destructive_intersection(fresh_delta,side_delta); + SASSERT(idelta); + do_delta_union(i, *idelta, *fresh_delta); + } + } + if (num == 0) { + //we need to handle product relation of no relations separately + if (!src.m_default_empty && tgt.m_default_empty) { + tgt.m_default_empty = false; + if (delta) { + delta->m_default_empty = false; + } + } + } + TRACE("dl", _tgt.display(tout << "dst':\n"); + if (_delta) _delta->display(tout << "delta:\n"); ;); + } + }; + + class product_relation_plugin::unaligned_union_fn : public relation_union_fn { + bool m_is_widen; + rel_spec m_common_spec; + scoped_ptr m_aligned_union_fun; + public: + unaligned_union_fn(product_relation const& tgt, product_relation const& src, + product_relation const* delta, bool is_widen) : m_is_widen(is_widen) { + ptr_vector rels; + rels.push_back(&tgt); + rels.push_back(&src); + if(delta) { + rels.push_back(delta); + } + get_common_spec(rels, m_common_spec); + } + + + virtual void operator()(relation_base& _tgt, const relation_base& _src, relation_base* _delta) { + TRACE("dl", _tgt.display(tout << "dst:\n"); _src.display(tout << "src:\n");); + product_relation& tgt = get(_tgt); + product_relation const& src0 = get(_src); + product_relation* delta = _delta ? get(_delta) : 0; + + tgt.convert_spec(m_common_spec); + if(delta) { + delta->convert_spec(m_common_spec); + } + scoped_rel src_scoped; + if(src0.get_kind()!=tgt.get_kind()) { + src_scoped = src0.clone(); + src_scoped->convert_spec(m_common_spec); + } + product_relation const& src = src_scoped ? *src_scoped : src0; + + if(!m_aligned_union_fun) { + m_aligned_union_fun = alloc(aligned_union_fn, tgt, src, delta, m_is_widen); + SASSERT(m_aligned_union_fun); + } + (*m_aligned_union_fun)(tgt, src, delta); + TRACE("dl", _tgt.display(tout << "dst':\n"); + if (_delta) _delta->display(tout << "delta:\n");); + } + }; + + class product_relation_plugin::single_non_transparent_src_union_fn : public relation_union_fn { + unsigned m_single_rel_idx; + scoped_ptr m_inner_union_fun; + public: + single_non_transparent_src_union_fn(unsigned single_rel_idx, relation_union_fn* inner_union_fun) + : m_single_rel_idx(single_rel_idx), + m_inner_union_fun(inner_union_fun) {} + + virtual void operator()(relation_base& tgt, const relation_base& _src, relation_base* delta) { + product_relation const& src = get(_src); + (*m_inner_union_fun)(tgt, src[m_single_rel_idx], delta); + } + }; + + relation_union_fn * product_relation_plugin::mk_union_w_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta, bool is_widen) { + if (check_kind(tgt) && check_kind(src) && (!delta || check_kind(*delta))) { + if(are_aligned(get(tgt), get(src)) && (!delta || are_aligned(get(tgt), *get(delta)))) { + return alloc(aligned_union_fn, get(tgt), get(src), get(delta), is_widen); + } + return alloc(unaligned_union_fn, get(tgt), get(src), get(delta), is_widen); + } + if(check_kind(src)) { + const product_relation & p_src = get(src); + unsigned single_idx; + if(p_src.try_get_single_non_transparent(single_idx)) { + relation_union_fn * inner; + if(is_widen) { + inner = get_manager().mk_widen_fn(tgt, p_src[single_idx], delta); + } + else { + inner = get_manager().mk_union_fn(tgt, p_src[single_idx], delta); + } + if(inner) { + return alloc(single_non_transparent_src_union_fn, single_idx, inner); + } + } + } + return 0; + } + + relation_union_fn * product_relation_plugin::mk_union_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta) { + return mk_union_w_fn(tgt, src, delta, false); + } + + relation_union_fn * product_relation_plugin::mk_widen_fn( + const relation_base & tgt, const relation_base & src, const relation_base * delta) { + return mk_union_w_fn(tgt, src, delta, true); + } + + class product_relation_plugin::mutator_fn : public relation_mutator_fn { + ptr_vector m_mutators; + public: + mutator_fn(unsigned sz, relation_mutator_fn** muts): + m_mutators(sz, muts) {} + + ~mutator_fn() { dealloc_ptr_vector_content(m_mutators); } + + virtual void operator()(relation_base & _r) { + TRACE("dl", _r.display(tout);); + product_relation& r = get(_r); + SASSERT(m_mutators.size() == r.size()); + for (unsigned i = 0; i < r.size(); ++i) { + relation_mutator_fn* m = m_mutators[i]; + if (m) { + (*m)(r[i]); + } + } + TRACE("dl", _r.display(tout);); + } + }; + + + relation_mutator_fn * product_relation_plugin::mk_filter_identical_fn( + const relation_base & _t, unsigned col_cnt, const unsigned * identical_cols) { + + if(is_product_relation(_t)) { + bool found = false; + product_relation const& r = get(_t); + ptr_vector mutators; + for (unsigned i = 0; i < r.size(); ++i) { + relation_mutator_fn* m = get_manager().mk_filter_identical_fn(r[i], col_cnt, identical_cols); + mutators.push_back(m); + if (m) found = true; + } + if (found) { + return alloc(mutator_fn, mutators.size(), mutators.c_ptr()); + } + } + return 0; + } + + relation_mutator_fn * product_relation_plugin::mk_filter_equal_fn(const relation_base & _t, + const relation_element & value, unsigned col) { + if(is_product_relation(_t)) { + product_relation const& r = get(_t); + ptr_vector mutators; + bool found = false; + for (unsigned i = 0; i < r.size(); ++i) { + relation_mutator_fn* m = get_manager().mk_filter_equal_fn(r[i], value, col); + mutators.push_back(m); + if (m) found = true; + } + if (found) { + return alloc(mutator_fn, mutators.size(), mutators.c_ptr()); + } + } + return 0; + } + + class product_relation_plugin::filter_interpreted_fn : public relation_mutator_fn { + ptr_vector m_mutators; + svector > m_attach; + public: + + filter_interpreted_fn(product_relation const& r, app* cond) { + for (unsigned i = 0; i < r.size(); ++i) { + m_mutators.push_back(r.get_manager().mk_filter_interpreted_fn(r[i], cond)); + } + for (unsigned i = 0; i < r.size(); ++i) { + relation_mutator_fn& m1 = *(m_mutators[i]); + for (unsigned j = i + 1; j < r.size(); ++j) { + relation_mutator_fn& m2 = *(m_mutators[j]); + if (m1.supports_attachment(r[j])) { + m_attach.push_back(std::make_pair(i,j)); + } + if (m2.supports_attachment(r[i])) { + m_attach.push_back(std::make_pair(j,i)); + } + } + } + } + + ~filter_interpreted_fn() { dealloc_ptr_vector_content(m_mutators); } + + void operator()(relation_base& _r) { + TRACE("dl", _r.display(tout);); + product_relation const& r = get(_r); + for (unsigned i = 0; i < m_attach.size(); ++i) { + m_mutators[m_attach[i].first]->attach(r[m_attach[i].second]); + } + for (unsigned i = 0; i < m_mutators.size(); ++i) { + (*m_mutators[i])(r[i]); + } + TRACE("dl", _r.display(tout);); + } + }; + + relation_mutator_fn * product_relation_plugin::mk_filter_interpreted_fn(const relation_base & t, app * condition) { + return alloc(filter_interpreted_fn, get(t), condition); + } + + + // ----------------------------------- + // + // product_relation + // + // ----------------------------------- + + product_relation::product_relation(product_relation_plugin& p, relation_signature const& s): + relation_base(p, s), + m_default_empty(true) { + ensure_correct_kind(); + } + + product_relation::product_relation(product_relation_plugin& p, relation_signature const& s, unsigned num_relations, relation_base** relations) : + relation_base(p, s), + m_default_empty(true) { + for (unsigned i = 0; i < num_relations; ++i) { + SASSERT(relations[i]->get_signature()==s); + m_relations.push_back(relations[i]); + } + ensure_correct_kind(); + } + + product_relation::~product_relation() { + unsigned num_relations = m_relations.size(); + for (unsigned i = 0; i < num_relations; ++i) { + m_relations[i]->deallocate(); + } + } + + product_relation_plugin& product_relation::get_plugin() const { + return dynamic_cast(relation_base::get_plugin()); + } + + void product_relation::ensure_correct_kind() { + unsigned rel_cnt = m_relations.size(); + //the rel_cnt==0 part makes us to update the kind also when the relation is newly created + bool spec_changed = rel_cnt!=m_spec.size() || rel_cnt==0; + if(spec_changed) { + m_spec.resize(rel_cnt); + } + for(unsigned i=0;iget_kind(); + if(spec_changed || m_spec[i]!=rkind) { + spec_changed = true; + m_spec[i]=rkind; + } + } + if(spec_changed) { + family_id new_kind = get_plugin().get_relation_kind(*this); + set_kind(new_kind); + } + } + + void product_relation::convert_spec(const rel_spec & spec) { + + func_decl* p = 0; + const relation_signature & sig = get_signature(); + family_id new_kind = get_plugin().get_relation_kind(sig, spec); + if(new_kind==get_kind()) { + return; + } + + unsigned old_sz = size(); + unsigned new_sz = spec.size(); + unsigned old_remain = old_sz; + relation_vector new_rels; + + //the loop is quadratic with the number of relations, maybe we want to fix it + for(unsigned i=0; iget_kind()==ikind) { + irel = m_relations[j]; + m_relations[j] = 0; + old_remain--; + break; + } + } + if(!irel) { + if(old_sz==0 && m_default_empty) { + //The relation didn't contain any inner relations but it was empty, + //so we make the newly added relations empty as well. + irel = get_manager().mk_empty_relation(sig, new_kind); + } + else { + irel = get_manager().mk_full_relation(sig, p, new_kind); + } + } + new_rels.push_back(irel); + } + SASSERT(old_remain==0); //the new specification must be a superset of the old one + m_relations = new_rels; + + set_kind(new_kind); + DEBUG_CODE( + ensure_correct_kind(); + SASSERT(get_kind()==new_kind); + ); + } + + bool product_relation::try_get_single_non_transparent(unsigned & idx) const { + unsigned sz = size(); + bool found = false; + unsigned candidate; + for(unsigned i=0; i relations; + for (unsigned i = 0; i < size(); ++i) { + relations.push_back((*this)[i].clone()); + } + product_relation_plugin& p = get_plugin(); + return alloc(product_relation, p, get_signature(), relations.size(), relations.c_ptr()); + } + + product_relation * product_relation::complement(func_decl*) const { + if(m_relations.empty()) { + product_relation * res = clone(); + res->m_default_empty = !m_default_empty; + return res; + } + UNREACHABLE(); + return 0; + } + + bool product_relation::empty() const { + if(m_relations.empty()) { + return m_default_empty; + } + for (unsigned i = 0; i < m_relations.size(); ++i) { + if (m_relations[i]->empty()) { + return true; + } + } + return false; + } + + void product_relation::to_formula(expr_ref& fml) const { + ast_manager& m = fml.get_manager(); + expr_ref_vector conjs(m); + expr_ref tmp(m); + for (unsigned i = 0; i < m_relations.size(); ++i) { + m_relations[i]->to_formula(tmp); + conjs.push_back(tmp); + } + bool_rewriter(m).mk_and(conjs.size(), conjs.c_ptr(), fml); + } + + void product_relation::display(std::ostream & out) const { + out<<"Product of the following relations:\n"; + for (unsigned i = 0; i < m_relations.size(); ++i) { + m_relations[i]->display(out); + } + } + +}; + + + diff --git a/lib/dl_product_relation.h b/lib/dl_product_relation.h new file mode 100644 index 000000000..c5e755939 --- /dev/null +++ b/lib/dl_product_relation.h @@ -0,0 +1,176 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + dl_product_relation.h + +Abstract: + + A Relation relation combinator. + +Author: + + Nikolaj Bjorner (nbjorner) 2010-4-11 + +Revision History: + +--*/ +#ifndef _DL_PRODUCT_RELATION_H_ +#define _DL_PRODUCT_RELATION_H_ + + +#include "dl_context.h" + +namespace datalog { + + class product_relation; + + class product_relation_plugin : public relation_plugin { + friend class product_relation; + public: + typedef svector rel_spec; + private: + class join_fn; + class transform_fn; + class mutator_fn; + class aligned_union_fn; + class unaligned_union_fn; + class single_non_transparent_src_union_fn; + class filter_equal_fn; + class filter_identical_fn; + class filter_interpreted_fn; + + rel_spec_store m_spec_store; + + family_id get_relation_kind(const product_relation & r); + + bool is_product_relation(relation_base * r) { return r->get_plugin().is_product_relation(); } + + public: + static product_relation_plugin& get_plugin(relation_manager & rmgr); + + product_relation_plugin(relation_manager& m); + + virtual void initialize(family_id fid); + + virtual bool can_handle_signature(const relation_signature & s); + virtual bool can_handle_signature(const relation_signature & s, family_id kind); + + static symbol get_name() { return symbol("product_relation"); } + + family_id get_relation_kind(const relation_signature & sig, const rel_spec & spec); + + virtual relation_base * mk_empty(const relation_signature & s); + virtual relation_base * mk_empty(const relation_signature & s, family_id kind); + + virtual relation_base * mk_full(func_decl* p, const relation_signature & s); + virtual relation_base * mk_full(func_decl* p, const relation_signature & s, family_id kind); + + protected: + virtual relation_join_fn * mk_join_fn(const relation_base & t1, const relation_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2); + virtual relation_transformer_fn * mk_project_fn(const relation_base & t, unsigned col_cnt, + const unsigned * removed_cols); + virtual relation_transformer_fn * mk_rename_fn(const relation_base & t, unsigned permutation_cycle_len, + const unsigned * permutation_cycle); + virtual relation_union_fn * mk_union_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta); + virtual relation_union_fn * mk_widen_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta); + virtual relation_mutator_fn * mk_filter_identical_fn(const relation_base & t, unsigned col_cnt, + const unsigned * identical_cols); + virtual relation_mutator_fn * mk_filter_equal_fn(const relation_base & t, const relation_element & value, + unsigned col); + virtual relation_mutator_fn * mk_filter_interpreted_fn(const relation_base & t, app * condition); + + static bool is_product_relation(relation_base const& r); + + private: + static product_relation& get(relation_base& r); + static product_relation const & get(relation_base const& r); + static product_relation* get(relation_base* r); + static product_relation const* get(relation_base const* r); + + relation_union_fn * mk_union_w_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta, bool is_widen); + + bool are_aligned(const product_relation& r1, const product_relation& r2); + static void get_common_spec(const ptr_vector & rels, rel_spec & res); + }; + + + class product_relation : public relation_base { + friend class product_relation_plugin; + + friend class product_relation_plugin::join_fn; + friend class product_relation_plugin::transform_fn; + friend class product_relation_plugin::mutator_fn; + friend class product_relation_plugin::aligned_union_fn; + friend class product_relation_plugin::unaligned_union_fn; + friend class product_relation_plugin::single_non_transparent_src_union_fn; + friend class product_relation_plugin::filter_equal_fn; + friend class product_relation_plugin::filter_identical_fn; + friend class product_relation_plugin::filter_interpreted_fn; + + + typedef product_relation_plugin::rel_spec rel_spec; + + /** + If m_relations is empty, value of this determines whether the relation is empty or full. + */ + bool m_default_empty; + + /** + There must not be two relations of the same kind + */ + ptr_vector m_relations; + + /** + Array of kinds of inner relations. + + If two product relations have equal signature and specification, their + m_relations arrays contain corresponding relations at the same indexes. + + The value returned by get_kind() depends uniquely on the specification. + */ + rel_spec m_spec; + + /** + \brief Ensure the kind assigned to this relation reflects the types of inner relations. + */ + void ensure_correct_kind(); + /** + The current specification must be a subset of the new one. + */ + void convert_spec(const rel_spec & spec); + public: + product_relation(product_relation_plugin& p, relation_signature const& s); + product_relation(product_relation_plugin& p, relation_signature const& s, unsigned num_relations, relation_base** relations); + + ~product_relation(); + + virtual bool empty() const; + virtual void add_fact(const relation_fact & f); + virtual bool contains_fact(const relation_fact & f) const; + virtual product_relation * clone() const; + virtual product_relation * complement(func_decl* p) const; + virtual void display(std::ostream & out) const; + virtual void to_formula(expr_ref& fml) const; + product_relation_plugin& get_plugin() const; + + unsigned size() const { return m_relations.size(); } + relation_base& operator[](unsigned i) const { return *m_relations[i]; } + + /** + If all relations except one are sieve_relations with no inner columns, + return true and into \c idx assign index of that relation. Otherwise return + false. + */ + bool try_get_single_non_transparent(unsigned & idx) const; + }; + +}; + +#endif + diff --git a/lib/dl_relation_manager.cpp b/lib/dl_relation_manager.cpp new file mode 100644 index 000000000..76d538b08 --- /dev/null +++ b/lib/dl_relation_manager.cpp @@ -0,0 +1,1622 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_relation_manager.cpp + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-09-14. + +Revision History: + +--*/ + + +#include +#include"ast_pp.h" +#include"dl_check_table.h" +#include"dl_context.h" +#include"dl_finite_product_relation.h" +#include"dl_product_relation.h" +#include"dl_sieve_relation.h" +#include"dl_table_relation.h" +#include"dl_relation_manager.h" + +namespace datalog { + + relation_manager::~relation_manager() { + reset(); + } + + + void relation_manager::reset_relations() { + relation_map::iterator it=m_relations.begin(); + relation_map::iterator end=m_relations.end(); + for(;it!=end;++it) { + func_decl * pred = it->m_key; + get_context().get_manager().dec_ref(pred); //inc_ref in get_relation + relation_base * r=(*it).m_value; + r->deallocate(); + } + m_relations.reset(); + } + + void relation_manager::reset() { + reset_relations(); + + m_favourite_table_plugin = static_cast(0); + m_favourite_relation_plugin = static_cast(0); + dealloc_ptr_vector_content(m_table_plugins); + m_table_plugins.reset(); + dealloc_ptr_vector_content(m_relation_plugins); + m_relation_plugins.reset(); + m_next_table_fid = 0; + m_next_relation_fid = 0; + } + + dl_decl_util & relation_manager::get_decl_util() const { + return get_context().get_decl_util(); + } + + family_id relation_manager::get_next_relation_fid(relation_plugin & claimer) { + unsigned res = m_next_relation_fid++; + m_kind2plugin.insert(res, &claimer); + return res; + } + + void relation_manager::set_predicate_kind(func_decl * pred, family_id kind) { + SASSERT(!m_relations.contains(pred)); + m_pred_kinds.insert(pred, kind); + } + + family_id relation_manager::get_requested_predicate_kind(func_decl * pred) { + family_id res; + if(m_pred_kinds.find(pred, res)) { + return res; + } + //This is commented out as the favourite relation might not be suitable for all + //signatures. In the cases where it is suitable, it will be used anyway if we + //now return null_family_id. + //else if (m_favourite_relation_plugin) { + // return m_favourite_relation_plugin->get_kind(); + //} + else { + return null_family_id; + } + } + + relation_base & relation_manager::get_relation(func_decl * pred) { + relation_base * res = try_get_relation(pred); + if(!res) { + relation_signature sig; + from_predicate(pred, sig); + family_id rel_kind = get_requested_predicate_kind(pred); + res = mk_empty_relation(sig, rel_kind); + store_relation(pred, res); + } + return *res; + } + + relation_base * relation_manager::try_get_relation(func_decl * pred) const { + relation_base * res = 0; + if(!m_relations.find(pred, res)) { + return 0; + } + SASSERT(res); + return res; + } + + void relation_manager::store_relation(func_decl * pred, relation_base * rel) { + SASSERT(rel); + relation_map::entry * e = m_relations.insert_if_not_there2(pred, 0); + if(e->get_data().m_value) { + e->get_data().m_value->deallocate(); + } + else { + get_context().get_manager().inc_ref(pred); //dec_ref in reset + } + e->get_data().m_value = rel; + } + + void relation_manager::collect_predicates(decl_set & res) const { + relation_map::iterator it = m_relations.begin(); + relation_map::iterator end = m_relations.end(); + for(; it!=end; ++it) { + res.insert(it->m_key); + } + } + + void relation_manager::collect_non_empty_predicates(decl_set & res) const { + relation_map::iterator it = m_relations.begin(); + relation_map::iterator end = m_relations.end(); + for(; it!=end; ++it) { + if(!it->m_value->empty()) { + res.insert(it->m_key); + } + } + } + + void relation_manager::restrict_predicates(const decl_set & preds) { + typedef ptr_vector fd_vector; + fd_vector to_remove; + + relation_map::iterator rit = m_relations.begin(); + relation_map::iterator rend = m_relations.end(); + for(; rit!=rend; ++rit) { + func_decl * pred = rit->m_key; + if(!preds.contains(pred)) { + to_remove.insert(pred); + } + } + + fd_vector::iterator pit = to_remove.begin(); + fd_vector::iterator pend = to_remove.end(); + for(; pit!=pend; ++pit) { + func_decl * pred = *pit; + relation_base * rel; + TRUSTME( m_relations.find(pred, rel) ); + rel->deallocate(); + m_relations.remove(pred); + get_context().get_manager().dec_ref(pred); + } + + set_intersection(m_saturated_rels, preds); + } + + void relation_manager::register_plugin(table_plugin * plugin) { + plugin->initialize(get_next_table_fid()); + m_table_plugins.push_back(plugin); + + if(plugin->get_name()==get_context().default_table()) { + m_favourite_table_plugin = plugin; + } + + table_relation_plugin * tr_plugin = alloc(table_relation_plugin, *plugin, *this); + register_relation_plugin_impl(tr_plugin); + m_table_relation_plugins.insert(plugin, tr_plugin); + + symbol checker_name = get_context().default_table_checker(); + if(get_context().default_table_checked() && get_table_plugin(checker_name)) { + if( m_favourite_table_plugin && + (plugin==m_favourite_table_plugin || plugin->get_name()==checker_name) ) { + symbol checked_name = get_context().default_table(); + //the plugins we need to create the checking plugin were just added + SASSERT(m_favourite_table_plugin->get_name()==get_context().default_table()); + table_plugin * checking_plugin = alloc(check_table_plugin, *this, checker_name, checked_name); + register_plugin(checking_plugin); + m_favourite_table_plugin = checking_plugin; + } + if(m_favourite_relation_plugin && m_favourite_relation_plugin->from_table()) { + table_relation_plugin * fav_rel_plugin = + static_cast(m_favourite_relation_plugin); + if(&fav_rel_plugin->get_table_plugin()==plugin || plugin->get_name()==checker_name) { + //the plugins we need to create the checking table_relation_plugin were just added + SASSERT(m_favourite_relation_plugin->get_name() == + get_context().default_relation()); + symbol checked_name = fav_rel_plugin->get_table_plugin().get_name(); + table_plugin * checking_plugin = alloc(check_table_plugin, *this, checker_name, checked_name); + register_plugin(checking_plugin); + + table_relation_plugin * checking_tr_plugin = + alloc(table_relation_plugin, *checking_plugin, *this); + register_relation_plugin_impl(checking_tr_plugin); + m_table_relation_plugins.insert(checking_plugin, checking_tr_plugin); + m_favourite_relation_plugin = checking_tr_plugin; + } + } + } + + } + + void relation_manager::register_relation_plugin_impl(relation_plugin * plugin) { + m_relation_plugins.push_back(plugin); + plugin->initialize(get_next_relation_fid(*plugin)); + if (plugin->get_name() == get_context().default_relation()) { + m_favourite_relation_plugin = plugin; + } + if(plugin->is_finite_product_relation()) { + finite_product_relation_plugin * fprp = static_cast(plugin); + relation_plugin * inner = &fprp->get_inner_plugin(); + m_finite_product_relation_plugins.insert(inner, fprp); + } + } + + relation_plugin * relation_manager::try_get_appropriate_plugin(const relation_signature & s) { + if(m_favourite_relation_plugin && m_favourite_relation_plugin->can_handle_signature(s)) { + return m_favourite_relation_plugin; + } + relation_plugin_vector::iterator rpit = m_relation_plugins.begin(); + relation_plugin_vector::iterator rpend = m_relation_plugins.end(); + for(; rpit!=rpend; ++rpit) { + if((*rpit)->can_handle_signature(s)) { + return *rpit; + } + } + return 0; + } + + relation_plugin & relation_manager::get_appropriate_plugin(const relation_signature & s) { + relation_plugin * res = try_get_appropriate_plugin(s); + if(!res) { + throw default_exception("no suitable plugin found for given relation signature"); + throw 0; + } + return *res; + } + + table_plugin * relation_manager::try_get_appropriate_plugin(const table_signature & t) { + if(m_favourite_table_plugin && m_favourite_table_plugin->can_handle_signature(t)) { + return m_favourite_table_plugin; + } + table_plugin_vector::iterator tpit = m_table_plugins.begin(); + table_plugin_vector::iterator tpend = m_table_plugins.end(); + for(; tpit!=tpend; ++tpit) { + if((*tpit)->can_handle_signature(t)) { + return *tpit; + } + } + return 0; + } + + table_plugin & relation_manager::get_appropriate_plugin(const table_signature & t) { + table_plugin * res = try_get_appropriate_plugin(t); + if(!res) { + throw default_exception("no suitable plugin found for given table signature"); + } + return *res; + } + + relation_plugin * relation_manager::get_relation_plugin(symbol const& s) { + relation_plugin_vector::iterator rpit = m_relation_plugins.begin(); + relation_plugin_vector::iterator rpend = m_relation_plugins.end(); + for(; rpit!=rpend; ++rpit) { + if((*rpit)->get_name()==s) { + return *rpit; + } + } + return 0; + } + + relation_plugin & relation_manager::get_relation_plugin(family_id kind) { + SASSERT(kind>=0); + SASSERT(kindget_name()==k) { + return *tpit; + } + } + return 0; + } + + table_relation_plugin & relation_manager::get_table_relation_plugin(table_plugin & tp) { + table_relation_plugin * res; + TRUSTME( m_table_relation_plugins.find(&tp, res) ); + return *res; + } + + bool relation_manager::try_get_finite_product_relation_plugin(const relation_plugin & inner, + finite_product_relation_plugin * & res) { + return m_finite_product_relation_plugins.find(&inner, res); + } + + table_base * relation_manager::mk_empty_table(const table_signature & s) { + return get_appropriate_plugin(s).mk_empty(s); + } + + + bool relation_manager::is_non_explanation(relation_signature const& s) const { + dl_decl_util & decl_util = get_context().get_decl_util(); + unsigned n = s.size(); + for(unsigned i = 0; i < n; i++) { + if(decl_util.is_rule_sort(s[i])) { + return false; + } + } + return true; + } + + relation_base * relation_manager::mk_empty_relation(const relation_signature & s, func_decl* pred) { + return mk_empty_relation(s, get_requested_predicate_kind(pred)); + } + + relation_base * relation_manager::mk_empty_relation(const relation_signature & s, family_id kind) { + if (kind != null_family_id) { + relation_plugin & plugin = get_relation_plugin(kind); + if (plugin.can_handle_signature(s, kind)) + return plugin.mk_empty(s, kind); + } + relation_base * res; + relation_plugin* p = m_favourite_relation_plugin; + + if (p && p->can_handle_signature(s)) { + return p->mk_empty(s); + } + + if(mk_empty_table_relation(s, res)) { + return res; + } + + for (unsigned i = 0; i < m_relation_plugins.size(); ++i) { + p = m_relation_plugins[i]; + if (p->can_handle_signature(s)) { + return p->mk_empty(s); + } + } + + //If there is no plugin to handle the signature, we just create an empty product relation and + //stuff will be added to it by later operations. + return product_relation_plugin::get_plugin(*this).mk_empty(s); + } + + + relation_base * relation_manager::mk_table_relation(const relation_signature & s, table_base * table) { + SASSERT(s.size()==table->get_signature().size()); + return get_table_relation_plugin(table->get_plugin()).mk_from_table(s, table); + } + + bool relation_manager::mk_empty_table_relation(const relation_signature & s, relation_base * & result) { + table_signature tsig; + if(!relation_signature_to_table(s, tsig)) { + return false; + } + table_base * table = mk_empty_table(tsig); + result = mk_table_relation(s, table); + return true; + } + + + relation_base * relation_manager::mk_full_relation(const relation_signature & s, func_decl* p, family_id kind) { + if (kind != null_family_id) { + relation_plugin & plugin = get_relation_plugin(kind); + if (plugin.can_handle_signature(s, kind)) { + return plugin.mk_full(p, s, kind); + } + } + return get_appropriate_plugin(s).mk_full(p, s, null_family_id); + } + + relation_base * relation_manager::mk_full_relation(const relation_signature & s, func_decl* pred) { + family_id kind = get_requested_predicate_kind(pred); + return mk_full_relation(s, pred, kind); + } + + void relation_manager::relation_to_table(const relation_sort & sort, const relation_element & from, + table_element & to) { + SASSERT(from->get_num_args()==0); + TRUSTME(get_context().get_decl_util().is_numeral_ext(from, to)); + } + + void relation_manager::table_to_relation(const relation_sort & sort, const table_element & from, + relation_element & to) { + to = get_decl_util().mk_numeral(from, sort); + } + + void relation_manager::table_to_relation(const relation_sort & sort, const table_element & from, + relation_element_ref & to) { + relation_element rel_el; + table_to_relation(sort, from, rel_el); + to = rel_el; + } + + void relation_manager::table_to_relation(const relation_sort & sort, const table_element & from, + const relation_fact::el_proxy & to) { + relation_element rel_el; + table_to_relation(sort, from, rel_el); + to = rel_el; + } + + bool relation_manager::relation_sort_to_table(const relation_sort & from, table_sort & to) { + return get_context().get_decl_util().try_get_size(from, to); + } + + void relation_manager::from_predicate(func_decl * pred, unsigned arg_index, relation_sort & result) { + result = pred->get_domain(arg_index); + } + + void relation_manager::from_predicate(func_decl * pred, relation_signature & result) { + result.reset(); + unsigned arg_num=pred->get_arity(); + for(unsigned i=0;iget_name().bare_str()); + } + + std::string relation_manager::to_nice_string(const relation_signature & s) const { + std::string res("["); + bool first = true; + relation_signature::const_iterator it = s.begin(); + relation_signature::const_iterator end = s.end(); + for(; it!=end; ++it) { + if(first) { + first = false; + } + else { + res+=','; + } + res+=to_nice_string(*it); + } + res+=']'; + + return res; + } + + void relation_manager::display(std::ostream & out) const { + relation_map::iterator it=m_relations.begin(); + relation_map::iterator end=m_relations.end(); + for(;it!=end;++it) { + out << "Table " << it->m_key->get_name() << "\n"; + it->m_value->display(out); + } + } + + void relation_manager::display_relation_sizes(std::ostream & out) const { + relation_map::iterator it=m_relations.begin(); + relation_map::iterator end=m_relations.end(); + for(;it!=end;++it) { + out << "Relation " << it->m_key->get_name() << " has size " + << it->m_value->get_size_estimate_rows() << "\n"; + } + } + + void relation_manager::display_output_tables(std::ostream & out) const { + const decl_set & output_preds = get_context().get_output_predicates(); + decl_set::iterator it=output_preds.begin(); + decl_set::iterator end=output_preds.end(); + for(; it!=end; ++it) { + func_decl * pred = *it; + relation_base * rel = try_get_relation(pred); + if(!rel) { + out << "Tuples in " << pred->get_name() << ": \n"; + continue; + } + rel->display_tuples(*pred, out); + } + } + + + // ----------------------------------- + // + // relation operations + // + // ----------------------------------- + + class relation_manager::empty_signature_relation_join_fn : public relation_join_fn { + public: + virtual relation_base * operator()(const relation_base & r1, const relation_base & r2) { + TRACE("dl", tout << r1.get_plugin().get_name() << " " << r2.get_plugin().get_name() << "\n";); + if(r1.get_signature().empty()) { + if(r1.empty()) { + return r2.get_manager().mk_empty_relation(r2.get_signature(), r2.get_kind()); + } + else { + return r2.clone(); + } + } + else { + SASSERT(r2.get_signature().empty()); + if(r2.empty()) { + return r1.get_manager().mk_empty_relation(r1.get_signature(), r1.get_kind()); + } + else { + return r1.clone(); + } + } + } + }; + + relation_join_fn * relation_manager::mk_join_fn(const relation_base & t1, const relation_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2, bool allow_product_relation) { + relation_plugin * p1 = &t1.get_plugin(); + relation_plugin * p2 = &t2.get_plugin(); + + relation_join_fn * res = p1->mk_join_fn(t1, t2, col_cnt, cols1, cols2); + if(!res && p1!=p2) { + res = p2->mk_join_fn(t1, t2, col_cnt, cols1, cols2); + } + + if(!res && (t1.get_signature().empty() || t2.get_signature().empty())) { + res = alloc(empty_signature_relation_join_fn); + } + + finite_product_relation_plugin * fprp; + if(!res && p1->from_table() && try_get_finite_product_relation_plugin(*p2, fprp)) { + //we downcast here to relation_plugin so that we don't have to declare + //relation_manager as a friend class of finite_product_relation_plugin + res = static_cast(fprp)->mk_join_fn(t1, t2, col_cnt, cols1, cols2); + } + if(!res && p2->from_table() && try_get_finite_product_relation_plugin(*p1, fprp)) { + res = static_cast(fprp)->mk_join_fn(t1, t2, col_cnt, cols1, cols2); + } + + if(!res && allow_product_relation) { + relation_plugin & product_plugin = product_relation_plugin::get_plugin(*this); + res = product_plugin.mk_join_fn(t1, t2, col_cnt, cols1, cols2); + } + + return res; + } + + relation_transformer_fn * relation_manager::mk_project_fn(const relation_base & t, unsigned col_cnt, + const unsigned * removed_cols) { + return t.get_plugin().mk_project_fn(t, col_cnt, removed_cols); + } + + + class relation_manager::default_relation_join_project_fn : public relation_join_fn { + scoped_ptr m_join; + scoped_ptr m_project; + + unsigned_vector m_removed_cols; + public: + /** + This constructor should be used only if we know that the projection operation + exists for the result of the join. + */ + default_relation_join_project_fn(join_fn * join, unsigned removed_col_cnt, + const unsigned * removed_cols) + : m_join(join), m_project(0), m_removed_cols(removed_col_cnt, removed_cols) {} + + virtual relation_base * operator()(const relation_base & t1, const relation_base & t2) { + scoped_rel aux = (*m_join)(t1, t2); + if(!m_project) { + relation_manager & rmgr = aux->get_plugin().get_manager(); + m_project = rmgr.mk_project_fn(*aux, m_removed_cols.size(), m_removed_cols.c_ptr()); + if(!m_project) { + throw default_exception("projection does not exist"); + } + } + relation_base * res = (*m_project)(*aux); + return res; + } + }; + + + relation_join_fn * relation_manager::mk_join_project_fn(const relation_base & t1, const relation_base & t2, + unsigned joined_col_cnt, const unsigned * cols1, const unsigned * cols2, + unsigned removed_col_cnt, const unsigned * removed_cols, bool allow_product_relation_join) { + relation_join_fn * res = t1.get_plugin().mk_join_project_fn(t1, t2, joined_col_cnt, cols1, cols2, + removed_col_cnt, removed_cols); + if(!res && &t1.get_plugin()!=&t2.get_plugin()) { + res = t2.get_plugin().mk_join_project_fn(t1, t2, joined_col_cnt, cols1, cols2, removed_col_cnt, + removed_cols); + } + if(!res) { + relation_join_fn * join = mk_join_fn(t1, t2, joined_col_cnt, cols1, cols2, allow_product_relation_join); + if(join) { + res = alloc(default_relation_join_project_fn, join, removed_col_cnt, removed_cols); + } + } + return res; + + } + + relation_transformer_fn * relation_manager::mk_rename_fn(const relation_base & t, unsigned permutation_cycle_len, + const unsigned * permutation_cycle) { + return t.get_plugin().mk_rename_fn(t, permutation_cycle_len, permutation_cycle); + } + + relation_transformer_fn * relation_manager::mk_permutation_rename_fn(const relation_base & t, + const unsigned * permutation) { + relation_transformer_fn * res = t.get_plugin().mk_permutation_rename_fn(t, permutation); + if(!res) { + res = alloc(default_relation_permutation_rename_fn, t, permutation); + } + return res; + } + + + relation_union_fn * relation_manager::mk_union_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta) { + relation_union_fn * res = tgt.get_plugin().mk_union_fn(tgt, src, delta); + if(!res && &tgt.get_plugin()!=&src.get_plugin()) { + res = src.get_plugin().mk_union_fn(tgt, src, delta); + } + if(!res && delta && &tgt.get_plugin()!=&delta->get_plugin() && &src.get_plugin()!=&delta->get_plugin()) { + res = delta->get_plugin().mk_union_fn(tgt, src, delta); + } + return res; + } + + relation_union_fn * relation_manager::mk_widen_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta) { + relation_union_fn * res = tgt.get_plugin().mk_widen_fn(tgt, src, delta); + if(!res && &tgt.get_plugin()!=&src.get_plugin()) { + res = src.get_plugin().mk_widen_fn(tgt, src, delta); + } + if(!res && delta && &tgt.get_plugin()!=&delta->get_plugin() && &src.get_plugin()!=&delta->get_plugin()) { + res = delta->get_plugin().mk_widen_fn(tgt, src, delta); + } + if(!res) { + res = mk_union_fn(tgt, src, delta); + } + return res; + } + + relation_mutator_fn * relation_manager::mk_filter_identical_fn(const relation_base & t, unsigned col_cnt, + const unsigned * identical_cols) { + return t.get_plugin().mk_filter_identical_fn(t, col_cnt, identical_cols); + } + + relation_mutator_fn * relation_manager::mk_filter_equal_fn(const relation_base & t, + const relation_element & value, unsigned col) { + + return t.get_plugin().mk_filter_equal_fn(t, value, col); + } + + relation_mutator_fn * relation_manager::mk_filter_interpreted_fn(const relation_base & t, app * condition) { + return t.get_plugin().mk_filter_interpreted_fn(t, condition); + } + + + class relation_manager::default_relation_select_equal_and_project_fn : public relation_transformer_fn { + scoped_ptr m_filter; + scoped_ptr m_project; + public: + default_relation_select_equal_and_project_fn(relation_mutator_fn * filter, relation_transformer_fn * project) + : m_filter(filter), m_project(project) {} + + virtual relation_base * operator()(const relation_base & t1) { + TRACE("dl", tout << t1.get_plugin().get_name() << "\n";); + scoped_rel aux = t1.clone(); + (*m_filter)(*aux); + relation_base * res = (*m_project)(*aux); + return res; + } + }; + + relation_transformer_fn * relation_manager::mk_select_equal_and_project_fn(const relation_base & t, + const relation_element & value, unsigned col) { + relation_transformer_fn * res = t.get_plugin().mk_select_equal_and_project_fn(t, value, col); + TRACE("dl", tout << t.get_plugin().get_name() << " " << value << " " << col << "\n";); + if(!res) { + relation_mutator_fn * selector = mk_filter_equal_fn(t, value, col); + if(selector) { + relation_transformer_fn * projector = mk_project_fn(t, 1, &col); + if(projector) { + res = alloc(default_relation_select_equal_and_project_fn, selector, projector); + } + else { + dealloc(selector); + } + } + } + return res; + } + + + class relation_manager::default_relation_intersection_filter_fn : public relation_intersection_filter_fn { + scoped_ptr m_join_fun; + scoped_ptr m_union_fun; + public: + + default_relation_intersection_filter_fn(relation_join_fn * join_fun, relation_union_fn * union_fun) + : m_join_fun(join_fun), m_union_fun(union_fun) {} + + virtual void operator()(relation_base & tgt, const relation_base & intersected_obj) { + scoped_rel filtered_rel = (*m_join_fun)(tgt, intersected_obj); + TRACE("dl", + tgt.display(tout << "tgt:\n"); + intersected_obj.display(tout << "intersected:\n"); + filtered_rel->display(tout << "filtered:\n"); + ); + if(!m_union_fun) { + SASSERT(tgt.can_swap(*filtered_rel)); + tgt.swap(*filtered_rel); + } + tgt.reset(); + TRACE("dl", tgt.display(tout << "target reset:\n"); ); + (*m_union_fun)(tgt, *filtered_rel); + TRACE("dl", tgt.display(tout << "intersected target:\n"); ); + } + + }; + + relation_intersection_filter_fn * relation_manager::try_mk_default_filter_by_intersection_fn( + const relation_base & tgt, const relation_base & src, unsigned joined_col_cnt, + const unsigned * tgt_cols, const unsigned * src_cols) { + TRACE("dl_verbose", tout << tgt.get_plugin().get_name() << "\n";); + unsigned_vector join_removed_cols; + add_sequence(tgt.get_signature().size(), src.get_signature().size(), join_removed_cols); + scoped_rel join_fun = mk_join_project_fn(tgt, src, joined_col_cnt, tgt_cols, src_cols, + join_removed_cols.size(), join_removed_cols.c_ptr(), false); + if(!join_fun) { + return 0; + } + //we perform the join operation here to see what the result is + scoped_rel join_res = (*join_fun)(tgt, src); + if(tgt.can_swap(*join_res)) { + return alloc(default_relation_intersection_filter_fn, join_fun.release(), 0); + } + if(join_res->get_plugin().is_product_relation()) { + //we cannot have the product relation here, since it uses the intersection operation + //for unions and therefore we would get into an infinite recursion + return 0; + } + scoped_rel union_fun = mk_union_fn(tgt, *join_res); + if(!union_fun) { + return 0; + } + return alloc(default_relation_intersection_filter_fn, join_fun.release(), union_fun.release()); + } + + + relation_intersection_filter_fn * relation_manager::mk_filter_by_intersection_fn(const relation_base & t, + const relation_base & src, unsigned joined_col_cnt, const unsigned * t_cols, const unsigned * src_cols) { + TRACE("dl_verbose", tout << t.get_plugin().get_name() << "\n";); + relation_intersection_filter_fn * res = t.get_plugin().mk_filter_by_intersection_fn(t, src, joined_col_cnt, + t_cols, src_cols); + if(!res && &t.get_plugin()!=&src.get_plugin()) { + res = src.get_plugin().mk_filter_by_intersection_fn(t, src, joined_col_cnt, t_cols, src_cols); + } + if(!res) { + res = try_mk_default_filter_by_intersection_fn(t, src, joined_col_cnt, t_cols, src_cols); + } + return res; + } + + relation_intersection_filter_fn * relation_manager::mk_filter_by_intersection_fn(const relation_base & tgt, + const relation_base & src) { + TRACE("dl_verbose", tout << tgt.get_plugin().get_name() << "\n";); + SASSERT(tgt.get_signature()==src.get_signature()); + unsigned sz = tgt.get_signature().size(); + unsigned_vector cols; + add_sequence(0, sz, cols); + return mk_filter_by_intersection_fn(tgt, src, cols, cols); + } + + + relation_intersection_filter_fn * relation_manager::mk_filter_by_negation_fn(const relation_base & t, + const relation_base & negated_obj, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * negated_cols) { + TRACE("dl", tout << t.get_plugin().get_name() << "\n";); + relation_intersection_filter_fn * res = t.get_plugin().mk_filter_by_negation_fn(t, negated_obj, joined_col_cnt, + t_cols, negated_cols); + if(!res && &t.get_plugin()!=&negated_obj.get_plugin()) { + res = negated_obj.get_plugin().mk_filter_by_negation_fn(t, negated_obj, joined_col_cnt, t_cols, + negated_cols); + } + return res; + } + + + + + + // ----------------------------------- + // + // table operations + // + // ----------------------------------- + + class relation_manager::default_table_join_fn : public convenient_table_join_fn { + unsigned m_col_cnt; + public: + default_table_join_fn(const table_signature & t1_sig, const table_signature & t2_sig, unsigned col_cnt, + const unsigned * cols1, const unsigned * cols2) + : convenient_table_join_fn(t1_sig, t2_sig, col_cnt, cols1, cols2), m_col_cnt(col_cnt) {} + + virtual table_base * operator()(const table_base & t1, const table_base & t2) { + table_plugin * plugin = &t1.get_plugin(); + + const table_signature & res_sign = get_result_signature(); + if (!plugin->can_handle_signature(res_sign)) { + plugin = &t2.get_plugin(); + if (!plugin->can_handle_signature(res_sign)) { + plugin = &t1.get_manager().get_appropriate_plugin(res_sign); + } + } + SASSERT(plugin->can_handle_signature(res_sign)); + table_base * res = plugin->mk_empty(res_sign); + + unsigned t1cols=t1.get_signature().size(); + unsigned t2cols=t2.get_signature().size(); + unsigned t1first_func=t1.get_signature().first_functional(); + unsigned t2first_func=t2.get_signature().first_functional(); + + table_base::iterator els1it = t1.begin(); + table_base::iterator els1end = t1.end(); + table_base::iterator els2end = t2.end(); + + table_fact acc; + + for(; els1it!=els1end; ++els1it) { + const table_base::row_interface & row1 = *els1it; + + table_base::iterator els2it = t2.begin(); + for(; els2it!=els2end; ++els2it) { + const table_base::row_interface & row2 = *els2it; + + bool match=true; + for(unsigned i=0; iadd_fact(acc); + } + } + return res; + } + }; + + table_join_fn * relation_manager::mk_join_fn(const table_base & t1, const table_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2) { + table_join_fn * res = t1.get_plugin().mk_join_fn(t1, t2, col_cnt, cols1, cols2); + if(!res && &t1.get_plugin()!=&t2.get_plugin()) { + res = t2.get_plugin().mk_join_fn(t1, t2, col_cnt, cols1, cols2); + } + if(!res) { + table_signature sig; + table_signature::from_join(t1.get_signature(), t2.get_signature(), + col_cnt, cols1, cols2, sig); + res = alloc(default_table_join_fn, t1.get_signature(), t2.get_signature(), col_cnt, cols1, cols2); + } + return res; + } + + + class relation_manager::auxiliary_table_transformer_fn { + table_fact m_row; + public: + virtual ~auxiliary_table_transformer_fn() {} + virtual const table_signature & get_result_signature() const = 0; + virtual void modify_fact(table_fact & f) const = 0; + + table_base * operator()(const table_base & t) { + table_plugin & plugin = t.get_plugin(); + const table_signature & res_sign = get_result_signature(); + SASSERT(plugin.can_handle_signature(res_sign)); + table_base * res = plugin.mk_empty(res_sign); + + table_base::iterator it = t.begin(); + table_base::iterator end = t.end(); + + for(; it!=end; ++it) { + it->get_fact(m_row); + modify_fact(m_row); + res->add_fact(m_row); + } + return res; + } + }; + + class relation_manager::default_table_project_fn + : public convenient_table_project_fn, auxiliary_table_transformer_fn { + public: + default_table_project_fn(const table_signature & orig_sig, unsigned removed_col_cnt, + const unsigned * removed_cols) + : convenient_table_project_fn(orig_sig, removed_col_cnt, removed_cols) { + SASSERT(removed_col_cnt>0); + } + + virtual const table_signature & get_result_signature() const { + return convenient_table_project_fn::get_result_signature(); + } + + virtual void modify_fact(table_fact & f) const { + project_out_vector_columns(f, m_removed_cols); + } + + virtual table_base * operator()(const table_base & t) { + return auxiliary_table_transformer_fn::operator()(t); + } + }; + + class relation_manager::null_signature_table_project_fn : public table_transformer_fn { + const table_signature m_empty_sig; + public: + null_signature_table_project_fn() : m_empty_sig() {} + virtual table_base * operator()(const table_base & t) { + relation_manager & m = t.get_plugin().get_manager(); + table_base * res = m.mk_empty_table(m_empty_sig); + if(!t.empty()) { + table_fact el; + res->add_fact(el); + } + return res; + } + }; + + + + table_transformer_fn * relation_manager::mk_project_fn(const table_base & t, unsigned col_cnt, + const unsigned * removed_cols) { + table_transformer_fn * res = t.get_plugin().mk_project_fn(t, col_cnt, removed_cols); + if(!res && col_cnt==t.get_signature().size()) { + //all columns are projected out + res = alloc(null_signature_table_project_fn); + } + if(!res) { + res = alloc(default_table_project_fn, t.get_signature(), col_cnt, removed_cols); + } + return res; + } + + + class relation_manager::default_table_join_project_fn : public convenient_table_join_project_fn { + scoped_ptr m_join; + scoped_ptr m_project; + + unsigned_vector m_removed_cols; + public: + default_table_join_project_fn(join_fn * join, const table_base & t1, const table_base & t2, + unsigned joined_col_cnt, const unsigned * cols1, const unsigned * cols2, unsigned removed_col_cnt, + const unsigned * removed_cols) + : convenient_table_join_project_fn(t1.get_signature(), t2.get_signature(), joined_col_cnt, cols1, + cols2, removed_col_cnt, removed_cols), + m_join(join), + m_removed_cols(removed_col_cnt, removed_cols) {} + + class unreachable_reducer : public table_row_pair_reduce_fn { + virtual void operator()(table_element * func_columns, const table_element * merged_func_columns) { + //we do project_with_reduce only if we are sure there will be no reductions + //(see code of the table_signature::from_join_project function) + UNREACHABLE(); + } + }; + + virtual table_base * operator()(const table_base & t1, const table_base & t2) { + table_base * aux = (*m_join)(t1, t2); + if(m_project==0) { + relation_manager & rmgr = aux->get_plugin().get_manager(); + if(get_result_signature().functional_columns()!=0) { + //to preserve functional columns we need to do the project_with_reduction + unreachable_reducer * reducer = alloc(unreachable_reducer); + m_project = rmgr.mk_project_with_reduce_fn(*aux, m_removed_cols.size(), m_removed_cols.c_ptr(), reducer); + } + else { + m_project = rmgr.mk_project_fn(*aux, m_removed_cols); + } + if(!m_project) { + throw default_exception("projection for table does not exist"); + } + } + table_base * res = (*m_project)(*aux); + aux->deallocate(); + return res; + } + }; + + table_join_fn * relation_manager::mk_join_project_fn(const table_base & t1, const table_base & t2, + unsigned joined_col_cnt, const unsigned * cols1, const unsigned * cols2, + unsigned removed_col_cnt, const unsigned * removed_cols) { + table_join_fn * res = t1.get_plugin().mk_join_project_fn(t1, t2, joined_col_cnt, cols1, cols2, + removed_col_cnt, removed_cols); + if(!res && &t1.get_plugin()!=&t2.get_plugin()) { + res = t2.get_plugin().mk_join_project_fn(t1, t2, joined_col_cnt, cols1, cols2, removed_col_cnt, + removed_cols); + } + if(!res) { + table_join_fn * join = mk_join_fn(t1, t2, joined_col_cnt, cols1, cols2); + if(join) { + res = alloc(default_table_join_project_fn, join, t1, t2, joined_col_cnt, cols1, cols2, + removed_col_cnt, removed_cols); + } + } + return res; + + } + + class relation_manager::default_table_rename_fn + : public convenient_table_rename_fn, auxiliary_table_transformer_fn { + const unsigned m_cycle_len; + public: + default_table_rename_fn(const table_signature & orig_sig, unsigned permutation_cycle_len, + const unsigned * permutation_cycle) + : convenient_table_rename_fn(orig_sig, permutation_cycle_len, permutation_cycle), + m_cycle_len(permutation_cycle_len) { + SASSERT(permutation_cycle_len>=2); + } + + virtual const table_signature & get_result_signature() const { + return convenient_table_rename_fn::get_result_signature(); + } + + virtual void modify_fact(table_fact & f) const { + permutate_by_cycle(f, m_cycle); + } + + virtual table_base * operator()(const table_base & t) { + return auxiliary_table_transformer_fn::operator()(t); + } + + }; + + table_transformer_fn * relation_manager::mk_rename_fn(const table_base & t, unsigned permutation_cycle_len, + const unsigned * permutation_cycle) { + table_transformer_fn * res = t.get_plugin().mk_rename_fn(t, permutation_cycle_len, permutation_cycle); + if(!res) { + res = alloc(default_table_rename_fn, t.get_signature(), permutation_cycle_len, permutation_cycle); + } + return res; + } + + table_transformer_fn * relation_manager::mk_permutation_rename_fn(const table_base & t, + const unsigned * permutation) { + table_transformer_fn * res = t.get_plugin().mk_permutation_rename_fn(t, permutation); + if(!res) { + res = alloc(default_table_permutation_rename_fn, t, permutation); + } + return res; + } + + + class relation_manager::default_table_union_fn : public table_union_fn { + table_fact m_row; + public: + virtual void operator()(table_base & tgt, const table_base & src, table_base * delta) { + table_base::iterator it = src.begin(); + table_base::iterator iend = src.end(); + + for(; it!=iend; ++it) { + it->get_fact(m_row); + + if(delta) { + if(!tgt.contains_fact(m_row)) { + tgt.add_new_fact(m_row); + delta->add_fact(m_row); + } + } + else { + //if there's no delta, we don't need to know whether we are actually adding a new fact + tgt.add_fact(m_row); + } + } + } + }; + + table_union_fn * relation_manager::mk_union_fn(const table_base & tgt, const table_base & src, + const table_base * delta) { + table_union_fn * res = tgt.get_plugin().mk_union_fn(tgt, src, delta); + if(!res && &tgt.get_plugin()!=&src.get_plugin()) { + res = src.get_plugin().mk_union_fn(tgt, src, delta); + } + if(!res && delta && &tgt.get_plugin()!=&delta->get_plugin() && &src.get_plugin()!=&delta->get_plugin()) { + res = delta->get_plugin().mk_union_fn(tgt, src, delta); + } + if(!res) { + res = alloc(default_table_union_fn); + } + return res; + } + + table_union_fn * relation_manager::mk_widen_fn(const table_base & tgt, const table_base & src, + const table_base * delta) { + table_union_fn * res = tgt.get_plugin().mk_widen_fn(tgt, src, delta); + if(!res && &tgt.get_plugin()!=&src.get_plugin()) { + res = src.get_plugin().mk_widen_fn(tgt, src, delta); + } + if(!res && delta && &tgt.get_plugin()!=&delta->get_plugin() && &src.get_plugin()!=&delta->get_plugin()) { + res = delta->get_plugin().mk_widen_fn(tgt, src, delta); + } + if(!res) { + res = mk_union_fn(tgt, src, delta); + } + return res; + } + + + /** + An auixiliary 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 + 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 bool should_remove(const table_fact & f) const = 0; + + void operator()(table_base & r) { + m_to_remove.reset(); + unsigned sz = 0; + table_base::iterator it = r.begin(); + table_base::iterator iend = r.end(); + for(; it!=iend; ++it) { + it->get_fact(m_row); + if(should_remove(m_row)) { + m_to_remove.append(m_row.size(), m_row.c_ptr()); + ++sz; + } + } + r.remove_facts(sz, m_to_remove.c_ptr()); + } + }; + + class relation_manager::default_table_filter_identical_fn : public table_mutator_fn, auxiliary_table_filter_fn { + const unsigned m_col_cnt; + const unsigned_vector m_identical_cols; + public: + default_table_filter_identical_fn(unsigned col_cnt, const unsigned * identical_cols) + : m_col_cnt(col_cnt), + m_identical_cols(col_cnt, identical_cols) { + SASSERT(col_cnt>=2); + } + + virtual bool should_remove(const table_fact & f) const { + table_element val=f[m_identical_cols[0]]; + for(unsigned i=1; iget_arg(0); + if (!m.is_eq(condition)) { + return 0; + } + expr* x = to_app(condition)->get_arg(0); + expr* y = to_app(condition)->get_arg(1); + if (!is_var(x)) { + std::swap(x, y); + } + if (!is_var(x)) { + return 0; + } + dl_decl_util decl_util(m); + uint64 value = 0; + if (!decl_util.is_numeral_ext(y, value)) { + return 0; + } + return alloc(default_table_filter_not_equal_fn, ctx, to_var(x)->get_idx(), value); + } + }; + + + + class relation_manager::default_table_filter_interpreted_fn + : public table_mutator_fn, auxiliary_table_filter_fn { + ast_manager & m_ast_manager; + var_subst & m_vs; + dl_decl_util & m_decl_util; + th_rewriter & m_simp; + app_ref m_condition; + ptr_vector m_var_sorts; + expr_ref_vector m_args; + public: + default_table_filter_interpreted_fn(context & ctx, unsigned col_cnt, app* condition) + : m_ast_manager(ctx.get_manager()), + m_vs(ctx.get_var_subst()), + m_decl_util(ctx.get_decl_util()), + m_simp(ctx.get_rewriter()), + m_condition(condition, ctx.get_manager()), + m_args(ctx.get_manager()) { + m_var_sorts.resize(col_cnt); + get_free_vars(m_condition, m_var_sorts); + } + + virtual bool should_remove(const table_fact & f) const { + expr_ref_vector& args = const_cast(m_args); + + args.reset(); + //arguments need to be in reverse order for the substitution + unsigned col_cnt = f.size(); + for(int i=col_cnt-1;i>=0;i--) { + sort * var_sort = m_var_sorts[i]; + if(!var_sort) { + args.push_back(0); + continue; //this variable does not occur in the condition; + } + + table_element el = f[i]; + args.push_back(m_decl_util.mk_numeral(el, var_sort)); + } + + expr_ref ground(m_ast_manager); + m_vs(m_condition.get(), args.size(), args.c_ptr(), ground); + m_simp(ground); + + return m_ast_manager.is_false(ground); + } + + virtual void operator()(table_base & t) { + auxiliary_table_filter_fn::operator()(t); + } + }; + + table_mutator_fn * relation_manager::mk_filter_interpreted_fn(const table_base & t, app * condition) { + context & ctx = get_context(); + table_mutator_fn * res = t.get_plugin().mk_filter_interpreted_fn(t, condition); + if (!res) { + res = default_table_filter_not_equal_fn::mk(ctx, condition); + } + if(!res) { + res = alloc(default_table_filter_interpreted_fn, ctx, t.get_signature().size(), condition); + } + return res; + } + + + table_intersection_filter_fn * relation_manager::mk_filter_by_intersection_fn(const table_base & t, + const table_base & src, unsigned joined_col_cnt, const unsigned * t_cols, const unsigned * src_cols) { + table_intersection_filter_fn * res = t.get_plugin().mk_filter_by_negation_fn(t, src, joined_col_cnt, + t_cols, src_cols); + if(!res && &t.get_plugin()!=&src.get_plugin()) { + res = src.get_plugin().mk_filter_by_negation_fn(t, src, joined_col_cnt, t_cols, src_cols); + } + return res; + } + + + + class relation_manager::default_table_negation_filter_fn : public convenient_table_negation_filter_fn, + auxiliary_table_filter_fn { + const table_base * m_negated_table; + mutable table_fact m_aux_fact; + public: + default_table_negation_filter_fn(const table_base & tgt, const table_base & neg_t, + unsigned joined_col_cnt, const unsigned * t_cols, const unsigned * negated_cols) + : convenient_table_negation_filter_fn(tgt, neg_t, joined_col_cnt, t_cols, negated_cols), + m_negated_table(0) { + m_aux_fact.resize(neg_t.get_signature().size()); + } + + virtual bool should_remove(const table_fact & f) const { + if(!m_all_neg_bound || m_overlap) { + table_base::iterator nit = m_negated_table->begin(); + table_base::iterator nend = m_negated_table->end(); + for(; nit!=nend; ++nit) { + const table_base::row_interface & nrow = *nit; + if(bindings_match(nrow, f)) { + return true; + } + } + return false; + } + else { + make_neg_bindings(m_aux_fact, f); + return m_negated_table->contains_fact(m_aux_fact); + } + } + + virtual void operator()(table_base & tgt, const table_base & negated_table) { + SASSERT(m_negated_table==0); + flet flet_neg_table(m_negated_table, &negated_table); + auxiliary_table_filter_fn::operator()(tgt); + } + + }; + + table_intersection_filter_fn * relation_manager::mk_filter_by_negation_fn(const table_base & t, + const table_base & negated_obj, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * negated_cols) { + table_intersection_filter_fn * res = t.get_plugin().mk_filter_by_negation_fn(t, negated_obj, joined_col_cnt, + t_cols, negated_cols); + if(!res && &t.get_plugin()!=&negated_obj.get_plugin()) { + res = negated_obj.get_plugin().mk_filter_by_negation_fn(t, negated_obj, joined_col_cnt, t_cols, + negated_cols); + } + if(!res) { + res = alloc(default_table_negation_filter_fn, t, negated_obj, joined_col_cnt, t_cols, negated_cols); + } + return res; + } + + + class relation_manager::default_table_select_equal_and_project_fn : public table_transformer_fn { + scoped_ptr m_filter; + scoped_ptr m_project; + public: + default_table_select_equal_and_project_fn(table_mutator_fn * filter, table_transformer_fn * project) + : m_filter(filter), m_project(project) {} + + virtual table_base * operator()(const table_base & t1) { + TRACE("dl", tout << t1.get_plugin().get_name() << "\n";); + scoped_rel aux = t1.clone(); + (*m_filter)(*aux); + table_base * res = (*m_project)(*aux); + return res; + } + }; + + table_transformer_fn * relation_manager::mk_select_equal_and_project_fn(const table_base & t, + const table_element & value, unsigned col) { + table_transformer_fn * res = t.get_plugin().mk_select_equal_and_project_fn(t, value, col); + if(!res) { + table_mutator_fn * selector = mk_filter_equal_fn(t, value, col); + SASSERT(selector); + table_transformer_fn * projector = mk_project_fn(t, 1, &col); + SASSERT(projector); + res = alloc(default_table_select_equal_and_project_fn, selector, projector); + } + return res; + } + + + class relation_manager::default_table_map_fn : public table_mutator_fn { + scoped_ptr m_mapper; + unsigned m_first_functional; + scoped_rel m_aux_table; + scoped_ptr m_union_fn; + table_fact m_curr_fact; + public: + default_table_map_fn(const table_base & t, table_row_mutator_fn * mapper) + : m_mapper(mapper), m_first_functional(t.get_signature().first_functional()) { + SASSERT(t.get_signature().functional_columns()>0); + table_plugin & plugin = t.get_plugin(); + m_aux_table = plugin.mk_empty(t.get_signature()); + m_union_fn = plugin.mk_union_fn(t, *m_aux_table, static_cast(0)); + } + + virtual void operator()(table_base & t) { + SASSERT(t.get_signature()==m_aux_table->get_signature()); + if(!m_aux_table->empty()) { + m_aux_table->reset(); + } + + + table_base::iterator it = t.begin(); + table_base::iterator iend = t.end(); + for(; it!=iend; ++it) { + it->get_fact(m_curr_fact); + if((*m_mapper)(m_curr_fact.c_ptr()+m_first_functional)) { + m_aux_table->add_fact(m_curr_fact); + } + } + + t.reset(); + (*m_union_fn)(t, *m_aux_table, static_cast(0)); + } + }; + + table_mutator_fn * relation_manager::mk_map_fn(const table_base & t, table_row_mutator_fn * mapper) { + SASSERT(t.get_signature().functional_columns()>0); + table_mutator_fn * res = t.get_plugin().mk_map_fn(t, mapper); + if(!res) { + res = alloc(default_table_map_fn, t, mapper); + } + return res; + } + + + class relation_manager::default_table_project_with_reduce_fn : public convenient_table_transformer_fn { + unsigned_vector m_removed_cols; + const unsigned m_inp_col_cnt; + const unsigned m_removed_col_cnt; + const unsigned m_result_col_cnt; + scoped_ptr m_reducer; + unsigned m_res_first_functional; + table_fact m_row; + table_fact m_former_row; + public: + default_table_project_with_reduce_fn(const table_signature & orig_sig, unsigned removed_col_cnt, + const unsigned * removed_cols, table_row_pair_reduce_fn * reducer) + : m_removed_cols(removed_col_cnt, removed_cols), + m_inp_col_cnt(orig_sig.size()), + m_removed_col_cnt(removed_col_cnt), + m_result_col_cnt(orig_sig.size()-removed_col_cnt), + m_reducer(reducer) { + SASSERT(removed_col_cnt>0); + table_signature::from_project_with_reduce(orig_sig, removed_col_cnt, removed_cols, + get_result_signature()); + m_res_first_functional = get_result_signature().first_functional(); + m_row.resize(get_result_signature().size()); + m_former_row.resize(get_result_signature().size()); + } + + virtual void modify_fact(table_fact & f) const { + unsigned ofs=1; + unsigned r_i=1; + for(unsigned i=m_removed_cols[0]+1; isuggest_fact(m_former_row)) { + (*m_reducer)(m_former_row.c_ptr()+m_res_first_functional, m_row.c_ptr()+m_res_first_functional); + res->ensure_fact(m_former_row); + } + } + return res; + } + }; + + table_transformer_fn * relation_manager::mk_project_with_reduce_fn(const table_base & t, unsigned col_cnt, + const unsigned * removed_cols, table_row_pair_reduce_fn * reducer) { + SASSERT(t.get_signature().functional_columns()>0); + table_transformer_fn * res = t.get_plugin().mk_project_with_reduce_fn(t, col_cnt, removed_cols, reducer); + if(!res) { + res = alloc(default_table_project_with_reduce_fn, t.get_signature(), col_cnt, removed_cols, reducer); + } + return res; + } + +}; + diff --git a/lib/dl_relation_manager.h b/lib/dl_relation_manager.h new file mode 100644 index 000000000..270e06f12 --- /dev/null +++ b/lib/dl_relation_manager.h @@ -0,0 +1,683 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_remation_manager.h + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-09-24. + +Revision History: + +--*/ +#ifndef _DL_RELATION_MANAGER_H_ +#define _DL_RELATION_MANAGER_H_ + + +#include"map.h" +#include"vector.h" + +#include"dl_base.h" + +namespace datalog { + + class context; + class dl_decl_util; + class table_relation; + class table_relation_plugin; + class finite_product_relation; + class finite_product_relation_plugin; + class sieve_relation; + class sieve_relation_plugin; + + typedef hashtable, ptr_eq > decl_set; + + class relation_manager { + class empty_signature_relation_join_fn; + class default_relation_join_project_fn; + class default_relation_select_equal_and_project_fn; + class default_relation_intersection_filter_fn; + + class auxiliary_table_transformer_fn; + class auxiliary_table_filter_fn; + + class default_table_join_fn; + class default_table_project_fn; + class null_signature_table_project_fn; + class default_table_join_project_fn; + class default_table_rename_fn; + class default_table_union_fn; + class default_table_filter_equal_fn; + class default_table_filter_identical_fn; + class default_table_filter_interpreted_fn; + class default_table_negation_filter_fn; + class default_table_filter_not_equal_fn; + class default_table_select_equal_and_project_fn; + class default_table_map_fn; + class default_table_project_with_reduce_fn; + + typedef obj_map decl2kind_map; + + typedef u_map kind2plugin_map; + + typedef map, + ptr_eq > tp2trp_map; + typedef map, + ptr_eq > rp2fprp_map; + + typedef map, ptr_eq > relation_map; + typedef ptr_vector table_plugin_vector; + typedef ptr_vector relation_plugin_vector; + + context & m_context; + table_plugin_vector m_table_plugins; + relation_plugin_vector m_relation_plugins; + //table_relation_plugins corresponding to table_plugins + tp2trp_map m_table_relation_plugins; + rp2fprp_map m_finite_product_relation_plugins; + + kind2plugin_map m_kind2plugin; + + table_plugin * m_favourite_table_plugin; + + relation_plugin * m_favourite_relation_plugin; + + relation_map m_relations; + + decl_set m_saturated_rels; + + family_id m_next_table_fid; + family_id m_next_relation_fid; + + /** + Map specifying what kind of relation should be used to represent particular predicate. + */ + decl2kind_map m_pred_kinds; + + void register_relation_plugin_impl(relation_plugin * plugin); + + relation_manager(const relation_manager &); //private and undefined copy constructor + relation_manager & operator=(const relation_manager &); //private and undefined operator= + public: + relation_manager(context & ctx) : + m_context(ctx), + m_favourite_table_plugin(0), + m_favourite_relation_plugin(0), + m_next_table_fid(0), + m_next_relation_fid(0) {} + + virtual ~relation_manager(); + + void reset(); + void reset_relations(); + + context & get_context() const { return m_context; } + dl_decl_util & get_decl_util() const; + + family_id get_next_table_fid() { return m_next_table_fid++; } + family_id get_next_relation_fid(relation_plugin & claimer); + + + /** + Set what kind of relation is going to be used to represent the predicate \c pred. + + This function can be called only before the relation object for \c pred is created + (i.e. before the \c get_relation function is called with \c pred as argument for the + first time). + */ + void set_predicate_kind(func_decl * pred, family_id kind); + /** + Return the relation kind that was requested to represent the predicate \c pred by + \c set_predicate_kind. If there was no such request, return \c null_family_id. + */ + family_id get_requested_predicate_kind(func_decl * pred); + relation_base & get_relation(func_decl * pred); + relation_base * try_get_relation(func_decl * pred) const; + /** + \brief Store the relation \c rel under the predicate \c pred. The \c relation_manager + takes over the relation object. + */ + void store_relation(func_decl * pred, relation_base * rel); + + bool is_saturated(func_decl * pred) const { return m_saturated_rels.contains(pred); } + void mark_saturated(func_decl * pred) { m_saturated_rels.insert(pred); } + void reset_saturated_marks() { + if(!m_saturated_rels.empty()) { + m_saturated_rels.reset(); + } + } + + void collect_predicates(decl_set & res) const; + void collect_non_empty_predicates(decl_set & res) const; + void restrict_predicates(const decl_set & preds); + + void register_plugin(table_plugin * plugin); + /** + table_relation_plugins should not be passed to this function since they are + created automatically when registering a table plugin. + */ + void register_plugin(relation_plugin * plugin) { + SASSERT(!plugin->from_table()); + register_relation_plugin_impl(plugin); + } + + table_plugin & get_appropriate_plugin(const table_signature & t); + relation_plugin & get_appropriate_plugin(const relation_signature & t); + table_plugin * try_get_appropriate_plugin(const table_signature & t); + relation_plugin * try_get_appropriate_plugin(const relation_signature & t); + + table_plugin * get_table_plugin(symbol const& s); + relation_plugin * get_relation_plugin(symbol const& s); + relation_plugin & get_relation_plugin(family_id kind); + table_relation_plugin & get_table_relation_plugin(table_plugin & tp); + bool try_get_finite_product_relation_plugin(const relation_plugin & inner, + finite_product_relation_plugin * & res); + + table_base * mk_empty_table(const table_signature & s); + relation_base * mk_implicit_relation(const relation_signature & s, app * expr); + + relation_base * mk_empty_relation(const relation_signature & s, family_id kind); + relation_base * mk_empty_relation(const relation_signature & s, func_decl* pred); + + relation_base * mk_full_relation(const relation_signature & s, func_decl* pred, family_id kind); + relation_base * mk_full_relation(const relation_signature & s, func_decl* pred); + + relation_base * mk_table_relation(const relation_signature & s, table_base * table); + bool mk_empty_table_relation(const relation_signature & s, relation_base * & result); + + bool is_non_explanation(relation_signature const& s) const; + + + /** + \brief Convert relation value to table one. + + This function can be called only for the relation sorts that have a table counterpart. + */ + void relation_to_table(const relation_sort & sort, const relation_element & from, table_element & to); + + void table_to_relation(const relation_sort & sort, const table_element & from, relation_element & to); + void table_to_relation(const relation_sort & sort, const table_element & from, + const relation_fact::el_proxy & to); + void table_to_relation(const relation_sort & sort, const table_element & from, + relation_element_ref & to); + + bool relation_sort_to_table(const relation_sort & from, table_sort & to); + void from_predicate(func_decl * pred, unsigned arg_index, relation_sort & result); + void from_predicate(func_decl * pred, relation_signature & result); + + /** + \brief Convert relation signature to table signature and return true if successful. If false + is returned, the value of \c to is undefined. + */ + bool relation_signature_to_table(const relation_signature & from, table_signature & to); + + void relation_fact_to_table(const relation_signature & s, const relation_fact & from, + table_fact & to); + void table_fact_to_relation(const relation_signature & s, const table_fact & from, + relation_fact & to); + + + + + // ----------------------------------- + // + // relation operations + // + // ----------------------------------- + + //TODO: If multiple operation implementations are available, we may want to do something to + //select the best one here. + + /** + If \c allow_product_relation is true, we will create a join that builds a product relation, + if there is no other way to do the join. If \c allow_product_relation is false, we will return + zero in that case. + */ + relation_join_fn * mk_join_fn(const relation_base & t1, const relation_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2, bool allow_product_relation=true); + + relation_join_fn * mk_join_fn(const relation_base & t1, const relation_base & t2, + const unsigned_vector & cols1, const unsigned_vector & cols2, bool allow_product_relation=true) { + SASSERT(cols1.size()==cols2.size()); + return mk_join_fn(t1, t2, cols1.size(), cols1.c_ptr(), cols2.c_ptr(), allow_product_relation); + } + + /** + \brief Return functor that transforms a table into one that lacks columns listed in + \c removed_cols array. + + The \c removed_cols cotains columns of table \c t in strictly ascending order. + */ + relation_transformer_fn * mk_project_fn(const relation_base & t, unsigned col_cnt, + const unsigned * removed_cols); + + relation_transformer_fn * mk_project_fn(const relation_base & t, const unsigned_vector & removed_cols) { + return mk_project_fn(t, removed_cols.size(), removed_cols.c_ptr()); + } + + /** + \brief Return an operation that is a composition of a join an a project operation. + */ + relation_join_fn * mk_join_project_fn(const relation_base & t1, const relation_base & t2, + unsigned joined_col_cnt, const unsigned * cols1, const unsigned * cols2, + unsigned removed_col_cnt, const unsigned * removed_cols, bool allow_product_relation_join=true); + + relation_join_fn * mk_join_project_fn(const relation_base & t1, const relation_base & t2, + const unsigned_vector & cols1, const unsigned_vector & cols2, + const unsigned_vector & removed_cols, bool allow_product_relation_join=true) { + return mk_join_project_fn(t1, t2, cols1.size(), cols1.c_ptr(), cols2.c_ptr(), removed_cols.size(), + removed_cols.c_ptr(), allow_product_relation_join); + } + + relation_transformer_fn * mk_rename_fn(const relation_base & t, unsigned permutation_cycle_len, + const unsigned * permutation_cycle); + relation_transformer_fn * mk_rename_fn(const relation_base & t, const unsigned_vector & permutation_cycle) { + return mk_rename_fn(t, permutation_cycle.size(), permutation_cycle.c_ptr()); + } + + /** + Like \c mk_rename_fn, only the permutation is not specified by cycle, but by a permutated array + of column number. + */ + relation_transformer_fn * mk_permutation_rename_fn(const relation_base & t, + const unsigned * permutation); + relation_transformer_fn * mk_permutation_rename_fn(const relation_base & t, + const unsigned_vector permutation) { + SASSERT(t.get_signature().size()==permutation.size()); + return mk_permutation_rename_fn(t, permutation.c_ptr()); + } + + + /** + The post-condition for an ideal union operation is be + + Union(tgt, src, delta): + tgt_1==tgt_0 \union src + delta_1== delta_0 \union ( tgt_1 \setminus tgt_0 ) + + A required post-condition is + + Union(tgt, src, delta): + tgt_1==tgt_0 \union src + tgt_1==tgt_0 => delta_1==delta_0 + delta_0 \subset delta_1 + delta_1 \subset (delta_0 \union tgt_1) + ( tgt_1 \setminus tgt_0 ) \subset delta_1 + + So that a sufficient implementation is + + Union(tgt, src, delta) { + oldTgt:=tgt.clone(); + tgt:=tgt \union src + if(tgt!=oldTgt) { + delta:=delta \union src //also “delta \union tgt” would work + } + } + + If rules are compiled with all_or_nothing_deltas parameter set to true, a sufficient + post-condition is + Union(tgt, src, delta): + tgt_1==tgt_0 \union src + (tgt_1==tgt_0 || delta_0 is non-empty) <=> delta_1 is non-empty + */ + relation_union_fn * mk_union_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta); + + relation_union_fn * mk_union_fn(const relation_base & tgt, const relation_base & src) { + return mk_union_fn(tgt, src, static_cast(0)); + } + + /** + Similar to union, but this one should be used inside loops to allow for abstract + domain convergence. + */ + relation_union_fn * mk_widen_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta); + + relation_mutator_fn * mk_filter_identical_fn(const relation_base & t, unsigned col_cnt, + const unsigned * identical_cols); + relation_mutator_fn * mk_filter_identical_fn(const relation_base & t, const unsigned_vector identical_cols) { + return mk_filter_identical_fn(t, identical_cols.size(), identical_cols.c_ptr()); + } + + relation_mutator_fn * mk_filter_equal_fn(const relation_base & t, const relation_element & value, + unsigned col); + + relation_mutator_fn * mk_filter_interpreted_fn(const relation_base & t, app * condition); + + /** + \brief Operations that returns all rows of \c t for which is column \c col equal to \c value + with the column \c col removed. + + This operation can often be efficiently implemented and is useful for evaluating rules + of the form + + F(x):-P("c",x). + */ + relation_transformer_fn * mk_select_equal_and_project_fn(const relation_base & t, + const relation_element & value, unsigned col); + + + relation_intersection_filter_fn * mk_filter_by_intersection_fn(const relation_base & tgt, + const relation_base & src, unsigned joined_col_cnt, + const unsigned * tgt_cols, const unsigned * src_cols); + relation_intersection_filter_fn * mk_filter_by_intersection_fn(const relation_base & tgt, + const relation_base & src, const unsigned_vector & tgt_cols, const unsigned_vector & src_cols) { + SASSERT(tgt_cols.size()==src_cols.size()); + return mk_filter_by_intersection_fn(tgt, src, tgt_cols.size(), tgt_cols.c_ptr(), src_cols.c_ptr()); + } + relation_intersection_filter_fn * mk_filter_by_intersection_fn(const relation_base & tgt, + const relation_base & src); + + /** + The filter_by_negation postcondition: + filter_by_negation(tgt, neg, columns in tgt: c1,...,cN, + corresponding columns in neg: d1,...,dN): + tgt_1:={x: x\in tgt_0 && ! \exists y: ( y \in neg & pi_c1(x)= pi_d1(y) & ... & pi_cN(x)= pi_dN(y) ) } + */ + relation_intersection_filter_fn * mk_filter_by_negation_fn(const relation_base & t, + const relation_base & negated_obj, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * negated_cols); + relation_intersection_filter_fn * mk_filter_by_negation_fn(const relation_base & t, + const relation_base & negated_obj, const unsigned_vector & t_cols, + const unsigned_vector & negated_cols) { + SASSERT(t_cols.size()==negated_cols.size()); + return mk_filter_by_negation_fn(t, negated_obj, t_cols.size(), t_cols.c_ptr(), negated_cols.c_ptr()); + } + + + // ----------------------------------- + // + // table operations + // + // ----------------------------------- + + + table_join_fn * mk_join_fn(const table_base & t1, const table_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2); + + table_join_fn * mk_join_fn(const table_base & t1, const table_base & t2, + const unsigned_vector & cols1, const unsigned_vector & cols2) { + SASSERT(cols1.size()==cols2.size()); + return mk_join_fn(t1, t2, cols1.size(), cols1.c_ptr(), cols2.c_ptr()); + } + + /** + \brief Return functor that transforms a table into one that lacks columns listed in + \c removed_cols array. + + The \c removed_cols cotains columns of table \c t in strictly ascending order. + + If a project operation removes a non-functional column, all functional columns become + non-functional (so that none of the values in functional columns are lost) + */ + table_transformer_fn * mk_project_fn(const table_base & t, unsigned col_cnt, + const unsigned * removed_cols); + + table_transformer_fn * mk_project_fn(const table_base & t, const unsigned_vector & removed_cols) { + return mk_project_fn(t, removed_cols.size(), removed_cols.c_ptr()); + } + + /** + \brief Return an operation that is a composition of a join an a project operation. + + This operation is equivalent to the two operations performed separately, unless functional + columns are involved. + + The ordinary project would make all of the functional columns into non-functional if any + non-functional column was removed. In function, however, we group columns into equivalence + classes (according to the equalities in \c cols1 and \c cols2) and make everything non-functional + only if some equivalence class of non-functional columns would have no non-functional columns + remain after the removal. + + This behavior is implemented in the \c table_signature::from_join_project function. + */ + table_join_fn * mk_join_project_fn(const table_base & t1, const table_base & t2, + unsigned joined_col_cnt, const unsigned * cols1, const unsigned * cols2, + unsigned removed_col_cnt, const unsigned * removed_cols); + + table_join_fn * mk_join_project_fn(const table_base & t1, const table_base & t2, + const unsigned_vector & cols1, const unsigned_vector & cols2, + const unsigned_vector & removed_cols) { + return mk_join_project_fn(t1, t2, cols1.size(), cols1.c_ptr(), cols2.c_ptr(), removed_cols.size(), + removed_cols.c_ptr()); + } + + table_transformer_fn * mk_rename_fn(const table_base & t, unsigned permutation_cycle_len, + const unsigned * permutation_cycle); + table_transformer_fn * mk_rename_fn(const table_base & t, const unsigned_vector & permutation_cycle) { + return mk_rename_fn(t, permutation_cycle.size(), permutation_cycle.c_ptr()); + } + + /** + Like \c mk_rename_fn, only the permutation is not specified by cycle, but by a permutated array + of column number. + */ + table_transformer_fn * mk_permutation_rename_fn(const table_base & t, const unsigned * permutation); + table_transformer_fn * mk_permutation_rename_fn(const table_base & t, const unsigned_vector permutation) { + SASSERT(t.get_signature().size()==permutation.size()); + return mk_permutation_rename_fn(t, permutation.c_ptr()); + } + + + /** + The post-condition for an ideal union operation is be + + Union(tgt, src, delta): + tgt_1==tgt_0 \union src + delta_1== delta_0 \union ( tgt_1 \setminus tgt_0 ) + + A required post-condition is + + Union(tgt, src, delta): + tgt_1==tgt_0 \union src + tgt_1==tgt_0 => delta_1==delta_0 + delta_0 \subset delta_1 + delta_1 \subset (delta_0 \union tgt_1) + ( tgt_1 \setminus tgt_0 ) \subset delta_1 + + So that a sufficient implementation is + + Union(tgt, src, delta) { + oldTgt:=tgt.clone(); + tgt:=tgt \union src + if(tgt!=oldTgt) { + delta:=delta \union src //also “delta \union tgt” would work + } + } + + If rules are compiled with all_or_nothing_deltas parameter set to true, a sufficient + post-condition is + Union(tgt, src, delta): + tgt_1==tgt_0 \union src + (tgt_1==tgt_0 || delta_0 is non-empty) <=> delta_1 is non-empty + */ + table_union_fn * mk_union_fn(const table_base & tgt, const table_base & src, + const table_base * delta); + + table_union_fn * mk_union_fn(const table_base & tgt, const table_base & src) { + return mk_union_fn(tgt, src, static_cast(0)); + } + + /** + Similar to union, but this one should be used inside loops to allow for abstract + domain convergence. + */ + table_union_fn * mk_widen_fn(const table_base & tgt, const table_base & src, + const table_base * delta); + + table_mutator_fn * mk_filter_identical_fn(const table_base & t, unsigned col_cnt, + const unsigned * identical_cols); + table_mutator_fn * mk_filter_identical_fn(const table_base & t, const unsigned_vector identical_cols) { + return mk_filter_identical_fn(t, identical_cols.size(), identical_cols.c_ptr()); + } + + table_mutator_fn * mk_filter_equal_fn(const table_base & t, const table_element & value, + unsigned col); + + table_mutator_fn * mk_filter_interpreted_fn(const table_base & t, app * condition); + + /** + \brief Operations that returns all rows of \c t for which is column \c col equal to \c value + with the column \c col removed. + + This operation can often be efficiently implemented and is useful for evaluating rules + of the form + + F(x):-P("c",x). + */ + table_transformer_fn * mk_select_equal_and_project_fn(const table_base & t, + const table_element & value, unsigned col); + + table_intersection_filter_fn * mk_filter_by_intersection_fn(const table_base & t, + const table_base & src, unsigned joined_col_cnt, const unsigned * t_cols, const unsigned * src_cols); + table_intersection_filter_fn * mk_filter_by_intersection_fn(const table_base & t, + const table_base & src, const unsigned_vector & t_cols, const unsigned_vector & src_cols) { + SASSERT(t_cols.size()==src_cols.size()); + return mk_filter_by_intersection_fn(t, src, t_cols.size(), t_cols.c_ptr(), src_cols.c_ptr()); + } + + /** + The filter_by_negation postcondition: + filter_by_negation(tgt, neg, columns in tgt: c1,...,cN, + corresponding columns in neg: d1,...,dN): + tgt_1:={x: x\in tgt_0 && ! \exists y: ( y \in neg & pi_c1(x)= pi_d1(y) & ... & pi_cN(x)= pi_dN(y) ) } + */ + table_intersection_filter_fn * mk_filter_by_negation_fn(const table_base & t, const table_base & negated_obj, + unsigned joined_col_cnt, const unsigned * t_cols, const unsigned * negated_cols); + table_intersection_filter_fn * mk_filter_by_negation_fn(const table_base & t, const table_base & negated_obj, + const unsigned_vector & t_cols, const unsigned_vector & negated_cols) { + SASSERT(t_cols.size()==negated_cols.size()); + return mk_filter_by_negation_fn(t, negated_obj, t_cols.size(), t_cols.c_ptr(), negated_cols.c_ptr()); + } + + /** + \c t must contain at least one functional column. + + Created object takes ownership of the \c mapper object. + */ + virtual table_mutator_fn * mk_map_fn(const table_base & t, table_row_mutator_fn * mapper); + + /** + \c t must contain at least one functional column. + + Created object takes ownership of the \c mapper object. + */ + virtual table_transformer_fn * mk_project_with_reduce_fn(const table_base & t, unsigned col_cnt, + const unsigned * removed_cols, table_row_pair_reduce_fn * reducer); + + + + + // ----------------------------------- + // + // output functions + // + // ----------------------------------- + + + std::string to_nice_string(const relation_element & el) const; + /** + This one may give a nicer representation of \c el than the + \c to_nice_string(const relation_element & el) function, by unsing the information about the sort + of the element. + */ + std::string to_nice_string(const relation_sort & s, const relation_element & el) const; + std::string to_nice_string(const relation_sort & s) const; + std::string to_nice_string(const relation_signature & s) const; + + void display(std::ostream & out) const; + void display_relation_sizes(std::ostream & out) const; + void display_output_tables(std::ostream & out) const; + + private: + relation_intersection_filter_fn * try_mk_default_filter_by_intersection_fn(const relation_base & t, + const relation_base & src, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * src_cols); + + }; + + /** + This is a helper class for relation_plugins whose relations can be of various kinds. + */ + template, class Eq=vector_eq_proc > + class rel_spec_store { + typedef relation_signature::hash r_hash; + typedef relation_signature::eq r_eq; + + typedef map family_id_idx_store; + typedef map sig2store; + + typedef u_map family_id2spec; + typedef map sig2spec_store; + + relation_plugin & m_parent; + svector m_allocated_kinds; + sig2store m_kind_assignment; + sig2spec_store m_kind_specs; + + + relation_manager & get_manager() { return m_parent.get_manager(); } + + void add_new_kind() { + add_available_kind(get_manager().get_next_relation_fid(m_parent)); + } + + public: + rel_spec_store(relation_plugin & parent) : m_parent(parent) {} + + ~rel_spec_store() { + reset_dealloc_values(m_kind_assignment); + reset_dealloc_values(m_kind_specs); + } + + void add_available_kind(family_id k) { + m_allocated_kinds.push_back(k); + } + + bool contains_signature(relation_signature const& sig) const { + return m_kind_assignment.contains(sig); + } + + family_id get_relation_kind(const relation_signature & sig, const Spec & spec) { + typename sig2store::entry * e = m_kind_assignment.find_core(sig); + if(!e) { + e = m_kind_assignment.insert_if_not_there2(sig, alloc(family_id_idx_store)); + m_kind_specs.insert(sig, alloc(family_id2spec)); + } + family_id_idx_store & ids = *e->get_data().m_value; + + unsigned res_idx; + if(!ids.find(spec, res_idx)) { + res_idx = ids.size(); + if(res_idx==m_allocated_kinds.size()) { + add_new_kind(); + } + SASSERT(res_idxinsert(m_allocated_kinds[res_idx], spec); + } + return m_allocated_kinds[res_idx]; + } + + void get_relation_spec(const relation_signature & sig, family_id kind, Spec & spec) { + family_id2spec * idspecs; + VERIFY( m_kind_specs.find(sig, idspecs) ); + VERIFY( idspecs->find(kind, spec) ); + } + + }; + +}; + +#endif /* _DL_RELATION_MANAGER_H_ */ + diff --git a/lib/dl_rewriter.cpp b/lib/dl_rewriter.cpp new file mode 100644 index 000000000..9b79775d5 --- /dev/null +++ b/lib/dl_rewriter.cpp @@ -0,0 +1,57 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + dl_rewriter.cpp + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2010-08-10 + +Revision History: + +--*/ + +#include"dl_rewriter.h" + + br_status dl_rewriter::mk_app_core( + func_decl * f, unsigned num_args, expr* const* args, expr_ref& result) { + ast_manager& m = result.get_manager(); + uint64 v1, v2; + switch(f->get_decl_kind()) { + case datalog::OP_DL_LT: + if (m_util.is_numeral_ext(args[0], v1) && + m_util.is_numeral_ext(args[1], v2)) { + result = (v1 < v2)?m.mk_true():m.mk_false(); + return BR_DONE; + } + // x < x <=> false + if (args[0] == args[1]) { + result = m.mk_false(); + return BR_DONE; + } + // x < 0 <=> false + if (m_util.is_numeral_ext(args[1], v2) && v2 == 0) { + result = m.mk_false(); + return BR_DONE; + } + // 0 < x <=> 0 != x + if (m_util.is_numeral_ext(args[1], v1) && v1 == 0) { + result = m.mk_not(m.mk_eq(args[0], args[1])); + return BR_DONE; + } + break; + + default: + break; + } + return BR_FAILED; + } + + + diff --git a/lib/dl_rewriter.h b/lib/dl_rewriter.h new file mode 100644 index 000000000..1ef5c577a --- /dev/null +++ b/lib/dl_rewriter.h @@ -0,0 +1,33 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + dl_rewriter.h + +Abstract: + + Basic rewriting rules for atalog finite domains. + +Author: + + Nikolaj Bjorner (nbjorner) 2012-03-09 + +Notes: + +--*/ +#ifndef _DL_REWRITER_H_ +#define _DL_REWRITER_H_ + +#include"dl_decl_plugin.h" +#include"rewriter_types.h" + +class dl_rewriter { + datalog::dl_decl_util m_util; +public: + dl_rewriter(ast_manager & m):m_util(m) {} + family_id get_fid() const { return m_util.get_family_id(); } + br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); +}; + +#endif diff --git a/lib/dl_rule.cpp b/lib/dl_rule.cpp new file mode 100644 index 000000000..30624c90c --- /dev/null +++ b/lib/dl_rule.cpp @@ -0,0 +1,1084 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_rule.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-05-17. + +Revision History: + +--*/ + +#include +#include + +#include"ast_pp.h" +#include"dl_context.h" +#include"map.h" +#include"recurse_expr_def.h" +#include"dl_rule.h" +#include"qe.h" +#include"for_each_expr.h" +#include"used_vars.h" +#include"var_subst.h" +#include"expr_abstract.h" +#include"rewriter_def.h" +#include"th_rewriter.h" +#include"ast_smt2_pp.h" +#include"used_symbols.h" +#include"quant_hoist.h" +#include"expr_replacer.h" + +namespace datalog { + + rule_manager::rule_manager(context& ctx) + : m(ctx.get_manager()), + m_ctx(ctx), + m_refs(ctx.get_manager()) {} + + bool rule_manager::is_predicate(func_decl * f) const { + return m_ctx.is_predicate(f); + } + + void rule_manager::inc_ref(rule * r) { + if (r) { + SASSERT(r->m_ref_cnt!=UINT_MAX); + r->m_ref_cnt++; + } + } + + void rule_manager::dec_ref(rule * r) { + if (r) { + SASSERT(r->m_ref_cnt>0); + r->m_ref_cnt--; + if(r->m_ref_cnt==0) { + r->deallocate(m); + } + } + } + + class remove_label_cfg : public default_rewriter_cfg { + family_id m_label_fid; + public: + remove_label_cfg(ast_manager& m): m_label_fid(m.get_family_id("label")) {} + virtual ~remove_label_cfg() {} + + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, + proof_ref & result_pr) + { + if (is_decl_of(f, m_label_fid, OP_LABEL)) { + SASSERT(num == 1); + result = args[0]; + return BR_DONE; + } + return BR_FAILED; + } + }; + + void rule_manager::remove_labels(expr_ref& fml) { + expr_ref tmp(m); + remove_label_cfg r_cfg(m); + rewriter_tpl rwr(m, false, r_cfg); + rwr(fml, tmp); + fml = tmp; + } + + + void rule_manager::mk_rule(expr* fml, rule_ref_vector& rules, symbol const& name) { + expr_ref fml1(m); + m_memoize_disj.reset(); + m_refs.reset(); + bind_variables(fml, true, fml1); + remove_labels(fml1); + mk_rule_core(fml1, rules, name); + } + + // + // Hoist quantifier from rule (universal) or query (existential) + // + unsigned rule_manager::hoist_quantifier(bool is_forall, expr_ref& fml, svector* names) { + + unsigned index = var_counter().get_next_var(fml); + while (is_quantifier(fml) && (is_forall == to_quantifier(fml)->is_forall())) { + quantifier* q = to_quantifier(fml); + index += q->get_num_decls(); + if (names) { + names->append(q->get_num_decls(), q->get_decl_names()); + } + fml = q->get_expr(); + } + if (!has_quantifiers(fml)) { + return index; + } + app_ref_vector vars(m); + quantifier_hoister qh(m); + qh.pull_quantifier(is_forall, fml, vars); + if (vars.empty()) { + return index; + } + // replace vars by de-bruijn indices + expr_substitution sub(m); + for (unsigned i = 0; i < vars.size(); ++i) { + app* v = vars[i].get(); + if (names) { + names->push_back(v->get_decl()->get_name()); + } + sub.insert(v, m.mk_var(index++,m.get_sort(v))); + } + scoped_ptr rep = mk_default_expr_replacer(m); + rep->set_substitution(&sub); + (*rep)(fml); + return index; + } + + void rule_manager::mk_rule_core(expr* _fml, rule_ref_vector& rules, symbol const& name) { + app_ref_vector body(m); + app_ref head(m); + expr_ref e(m), fml(_fml, m); + svector is_negated; + TRACE("dl_rule", tout << mk_pp(fml, m) << "\n";); + unsigned index = hoist_quantifier(true, fml, 0); + check_app(fml); + head = to_app(fml); + + while (m.is_implies(head)) { + e = head->get_arg(0); + th_rewriter th_rwr(m); + th_rwr(e); + body.push_back(ensure_app(e)); + e = to_app(head)->get_arg(1); + check_app(e); + head = to_app(e.get()); + } + symbol head_name = (name == symbol::null)?head->get_decl()->get_name():name; + flatten_body(body); + if (body.size() == 1 && m.is_or(body[0].get()) && contains_predicate(body[0].get())) { + app* _or = to_app(body[0].get()); + for (unsigned i = 0; i < _or->get_num_args(); ++i) { + e = m.mk_implies(_or->get_arg(i), head); + mk_rule_core(e, rules, head_name); + } + return; + } + + eliminate_disjunctions(body, rules, head_name); + eliminate_quantifier_body(body, rules, head_name); + hoist_compound(index, head, body); + unsigned sz = body.size(); + for (unsigned i = 0; i < sz; ++i) { + app_ref b(body[i].get(), m); + hoist_compound(index, b, body); + body[i] = b; + } + TRACE("dl_rule", + tout << mk_pp(head, m) << " :- "; + for (unsigned i = 0; i < body.size(); ++i) { + tout << mk_pp(body[i].get(), m) << " "; + } + tout << "\n";); + + for (unsigned i = 0; i < body.size(); ++i) { + expr* e = body[i].get(), *e1; + if (m.is_not(e, e1) && is_predicate(e1)) { + check_app(e1); + body[i] = to_app(e1); + is_negated.push_back(true); + } + else { + is_negated.push_back(false); + } + } + check_valid_rule(head, body.size(), body.c_ptr()); + + rules.push_back(mk(head.get(), body.size(), body.c_ptr(), is_negated.c_ptr(), name)); + + if (m_ctx.fix_unbound_vars()) { + unsigned rule_cnt = rules.size(); + for(unsigned i=0; i vars; + svector names; + app_ref_vector body(m); + expr_ref q(m); + + // Add implicit variables. + // Remove existential prefix. + bind_variables(query, false, q); + hoist_quantifier(false, q, &names); + // retrieve free variables. + get_free_vars(q, vars); + if (vars.contains(static_cast(0))) { + var_subst sub(m, false); + expr_ref_vector args(m); + // [s0, 0, s2, ..] + // [0 -> 0, 1 -> x, 2 -> 1, ..] + for (unsigned i = 0, j = 0; i < vars.size(); ++i) { + if (vars[i]) { + args.push_back(m.mk_var(j, vars[i])); + ++j; + } + else { + args.push_back(m.mk_var(0, m.mk_bool_sort())); + } + } + sub(q, args.size(), args.c_ptr(), q); + vars.reset(); + get_free_vars(q, vars); + } + SASSERT(!vars.contains(static_cast(0)) && "Unused variables have been eliminated"); + + + // flatten body and extract query predicate. + if (!is_app(q)) { + throw default_exception("Query body is not well-formed"); + } + body.push_back(to_app(q)); + flatten_body(body); + func_decl* body_pred = 0; + for(unsigned i = 0; i < body.size(); i++) { + if (is_uninterp(body[i].get())) { + body_pred = body[i]->get_decl(); + break; + } + } + + // we want outermost declared variable first to + // follow order of quantified variables so we reverse vars. + while (vars.size() > names.size()) { + names.push_back(symbol(names.size())); + } + vars.reverse(); + names.reverse(); + qpred = m_ctx.mk_fresh_head_predicate(symbol("query"), symbol(), vars.size(), vars.c_ptr(), body_pred); + + expr_ref_vector qhead_args(m); + for(unsigned i = 0; i < vars.size(); i++) { + qhead_args.push_back(m.mk_var(vars.size()-i-1, vars[i])); + } + app_ref qhead(m.mk_app(qpred, qhead_args.c_ptr()), m); + app_ref impl(m.mk_implies(q, qhead), m); + expr_ref rule_expr(impl.get(), m); + if (!vars.empty()) { + rule_expr = m.mk_forall(vars.size(), vars.c_ptr(), names.c_ptr(), impl); + } + + mk_rule(rule_expr, query_rules); + SASSERT(query_rules.size() >= 1); + query_rule = query_rules.back(); + } + + void rule_manager::bind_variables(expr* fml, bool is_forall, expr_ref& result) { + app_ref_vector const& vars = m_ctx.get_variables(); + if (vars.empty()) { + result = fml; + } + else { + ptr_vector sorts; + expr_abstract(m, 0, vars.size(), reinterpret_cast(vars.c_ptr()), fml, result); + get_free_vars(result, sorts); + if (sorts.empty()) { + result = fml; + } + else { + svector names; + for (unsigned i = 0; i < sorts.size(); ++i) { + if (!sorts[i]) { + sorts[i] = m.mk_bool_sort(); + } + names.push_back(symbol(i)); + } + quantifier_ref q(m); + q = m.mk_quantifier(is_forall, sorts.size(), sorts.c_ptr(), names.c_ptr(), result); + elim_unused_vars(m, q, result); + } + } + } + + void rule_manager::flatten_body(app_ref_vector& body) { + + expr_ref_vector r(m); + for (unsigned i = 0; i < body.size(); ++i) { + r.push_back(body[i].get()); + } + flatten_and(r); + body.reset(); + for (unsigned i = 0; i < r.size(); ++i) { + body.push_back(ensure_app(r[i].get())); + } + } + + void rule_manager::hoist_compound(unsigned& num_bound, app_ref& fml, app_ref_vector& body) { + + expr_ref e(m); + expr* not_fml; + if (m.is_not(fml, not_fml)) { + fml = ensure_app(not_fml); + hoist_compound(num_bound, fml, body); + fml = m.mk_not(fml); + return; + } + expr_ref_vector args(m); + if (!is_predicate(fml)) { + return; + } + for (unsigned i = 0; i < fml->get_num_args(); ++i) { + e = fml->get_arg(i); + if (!is_app(e)) { + args.push_back(e); + continue; + } + app* b = to_app(e); + + if (m.is_value(b)) { + args.push_back(e); + } + else { + var* v = m.mk_var(num_bound++, m.get_sort(b)); + args.push_back(v); + body.push_back(m.mk_eq(v, b)); + } + } + fml = m.mk_app(fml->get_decl(), args.size(), args.c_ptr()); + TRACE("dl_rule", tout << mk_pp(fml.get(), m) << "\n";); + } + + class contains_predicate_proc { + rule_manager const& m; + public: + struct found {}; + contains_predicate_proc(rule_manager const& m): m(m) {} + void operator()(var * n) {} + void operator()(quantifier * n) {} + void operator()(app* n) { + if (m.is_predicate(n)) throw found(); + } + }; + + bool rule_manager::contains_predicate(expr* fml) const { + contains_predicate_proc proc(*this); + try { + quick_for_each_expr(proc, fml); + } + catch (contains_predicate_proc::found) { + return true; + } + return false; + } + + void rule_manager::eliminate_disjunctions(app_ref_vector::element_ref& body, rule_ref_vector& rules, symbol const& name) { + + app* b = body.get(); + expr* e1; + bool negate_args = false; + bool is_disj = false; + unsigned num_disj = 0; + expr* const* disjs = 0; + if (!contains_predicate(b)) { + return; + } + TRACE("dl_rule", tout << mk_ismt2_pp(b, m) << "\n";); + if (m.is_or(b)) { + is_disj = true; + negate_args = false; + num_disj = b->get_num_args(); + disjs = b->get_args(); + } + if (m.is_not(b, e1) && m.is_and(e1)) { + is_disj = true; + negate_args = true; + num_disj = to_app(e1)->get_num_args(); + disjs = to_app(e1)->get_args(); + } + if (is_disj) { + ptr_vector sorts0, sorts1; + get_free_vars(b, sorts0); + expr_ref_vector args(m); + for (unsigned i = 0; i < sorts0.size(); ++i) { + if (sorts0[i]) { + args.push_back(m.mk_var(i,sorts0[i])); + sorts1.push_back(sorts0[i]); + } + } + app* old_head = 0; + if (m_memoize_disj.find(b, old_head)) { + body = old_head; + } + else { + app_ref head(m); + func_decl_ref f(m); + f = m.mk_fresh_func_decl(name.str().c_str(),"", sorts1.size(), sorts1.c_ptr(), m.mk_bool_sort()); + m_ctx.register_predicate(f); + head = m.mk_app(f, args.size(), args.c_ptr()); + + for (unsigned i = 0; i < num_disj; ++i) { + expr_ref fml(m); + expr* e = disjs[i]; + if (negate_args) e = m.mk_not(e); + fml = m.mk_implies(e,head); + mk_rule_core(fml, rules, name); + } + m_memoize_disj.insert(b, head); + m_refs.push_back(b); + m_refs.push_back(head); + // update the body to be the newly introduced head relation + body = head; + } + } + } + + void rule_manager::eliminate_disjunctions(app_ref_vector& body, rule_ref_vector& rules, symbol const& name) { + for (unsigned i = 0; i < body.size(); ++i) { + app_ref_vector::element_ref t = body[i]; + eliminate_disjunctions(t, rules, name); + } + } + + bool rule_manager::is_forall(ast_manager& m, expr* e, quantifier*& q) { + expr* e1, *e2; + if (m.is_iff(e, e1, e2)) { + if (m.is_true(e2)) { + e = e1; + } + else if (m.is_true(e1)) { + e = e2; + } + } + if (is_quantifier(e)) { + q = to_quantifier(e); + return q->is_forall(); + } + return false; + } + + void rule_manager::eliminate_quantifier_body(app_ref_vector::element_ref& body, rule_ref_vector& rules, symbol const& name) { + quantifier* q; + if (is_forall(m, body.get(), q) && contains_predicate(q)) { + expr* e = q->get_expr(); + if (!is_predicate(e)) { + ptr_vector sorts0, sorts1; + get_free_vars(e, sorts0); + expr_ref_vector args(m); + for (unsigned i = 0; i < sorts0.size(); ++i) { + if (sorts0[i]) { + args.push_back(m.mk_var(i,sorts0[i])); + sorts1.push_back(sorts0[i]); + } + } + app_ref head(m), fml(m); + func_decl_ref f(m); + f = m.mk_fresh_func_decl(name.str().c_str(),"", sorts1.size(), sorts1.c_ptr(), m.mk_bool_sort()); + m_ctx.register_predicate(f); + head = m.mk_app(f, args.size(), args.c_ptr()); + fml = m.mk_implies(e, head); + mk_rule_core(fml, rules, name); + // update the body to be the newly introduced head relation + body = m.mk_eq(m.mk_true(), m.update_quantifier(q, head)); + } + } + } + + void rule_manager::eliminate_quantifier_body(app_ref_vector& body, rule_ref_vector& rules, symbol const& name) { + for (unsigned i = 0; i < body.size(); ++i) { + app_ref_vector::element_ref t = body[i]; + eliminate_quantifier_body(t, rules, name); + } + } + + + app_ref rule_manager::ensure_app(expr* e) { + SASSERT(m.is_bool(e)); + if (is_app(e)) { + return app_ref(to_app(e), m); + } + else { + return app_ref(m.mk_eq(e, m.mk_true()), m); + } + } + + void rule_manager::check_app(expr* e) { + if (!is_app(e)) { + std::ostringstream out; + out << "expected application, got " << mk_pp(e, m); + throw default_exception(out.str()); + } + } + + rule * rule_manager::mk(app * head, unsigned n, app * const * tail, bool const * is_negated, symbol const& name) { + DEBUG_CODE(check_valid_rule(head, n, tail);); + unsigned sz = rule::get_obj_size(n); + void * mem = m.get_allocator().allocate(sz); + rule * r = new (mem) rule(); + r->m_head = head; + r->m_name = name; + r->m_tail_size = n; + m.inc_ref(r->m_head); + + app * * uninterp_tail = r->m_tail; //grows upwards + app * * interp_tail = r->m_tail+n; //grows downwards + + bool has_neg = false; + + for (unsigned i = 0; i < n; i++) { + bool is_neg = (is_negated != 0 && is_negated[i]); + app * curr = tail[i]; + + if(is_neg && !is_predicate(curr)) { + curr = m.mk_not(curr); + is_neg = false; + } + if(is_neg) { + has_neg = true; + } + app * tail_entry = TAG(app *, curr, is_neg); + if(is_predicate(curr)) { + *uninterp_tail=tail_entry; + uninterp_tail++; + } + else { + interp_tail--; + *interp_tail=tail_entry; + } + m.inc_ref(curr); + } + SASSERT(uninterp_tail==interp_tail); + + r->m_uninterp_cnt = static_cast(uninterp_tail - r->m_tail); + + if(has_neg) { + //put negative predicates between positive and interpreted + app * * it = r->m_tail; + app * * end = r->m_tail + r->m_uninterp_cnt; + while(it!=end) { + bool is_neg = GET_TAG(*it)!=0; + if(is_neg) { + --end; + std::swap(*it, *end); + } + else { + ++it; + } + } + r->m_positive_cnt = static_cast(it - r->m_tail); + SASSERT(r->m_positive_cnt < r->m_uninterp_cnt); + } + else { + r->m_positive_cnt = r->m_uninterp_cnt; + } + + r->norm_vars(*this); + return r; + } + + rule * rule_manager::mk(rule const * source, symbol const& name) { + return mk(source, source->get_head(), name); + } + + rule * rule_manager::mk(rule const * source, app * new_head, symbol const& name) { + unsigned n = source->get_tail_size(); + unsigned sz = rule::get_obj_size(n); + void * mem = m.get_allocator().allocate(sz); + rule * r = new (mem) rule(); + r->m_head = new_head; + r->m_name = name; + r->m_tail_size = n; + r->m_positive_cnt = source->m_positive_cnt; + r->m_uninterp_cnt = source->m_uninterp_cnt; + m.inc_ref(r->m_head); + for (unsigned i = 0; i < n; i++) { + r->m_tail[i] = source->m_tail[i]; + m.inc_ref(r->get_tail(i)); + } + return r; + } + + void rule_manager::fix_unbound_vars(rule_ref& r, bool try_quantifier_elimination) { + + if(r->get_uninterpreted_tail_size()==r->get_tail_size()) { + //no interpreted tail to fix + return; + } + + ptr_vector free_rule_vars; + var_counter vctr; + app_ref_vector tail(m); + svector tail_neg; + app_ref head(r->get_head(), m); + + get_free_vars(r, free_rule_vars); + vctr.count_vars(m, head); + + unsigned ut_len = r->get_uninterpreted_tail_size(); + for (unsigned i = 0; i < ut_len; i++) { + app * t = r->get_tail(i); + vctr.count_vars(m, t); + tail.push_back(t); + tail_neg.push_back(r->is_neg_tail(i)); + } + + ptr_vector interp_vars; + //var_idx_set interp_vars; + var_idx_set unbound_vars; + expr_ref_vector tails_with_unbound(m); + + unsigned t_len = r->get_tail_size(); + for (unsigned i = ut_len; i < t_len; i++) { + app * t = r->get_tail(i); + + interp_vars.reset(); + + ::get_free_vars(t, interp_vars); + //collect_vars(m, t, interp_vars); + + bool has_unbound = false; + unsigned iv_size = interp_vars.size(); + for(unsigned i=0; i qsorts; + qsorts.resize(q_var_cnt); + + unsigned q_idx = 0; + for(unsigned v = 0; v <= max_var; ++v) { + sort * v_sort = free_rule_vars[v]; + if(!v_sort) { + //this variable index is not used + continue; + } + + unsigned new_idx; + if(unbound_vars.contains(v)) { + new_idx = q_idx++; + qsorts.push_back(v_sort); + } + else { + new_idx = v + q_var_cnt; + } + subst.push_back(m.mk_var(new_idx, v_sort)); + } + SASSERT(q_idx==q_var_cnt); + + svector qnames; + for(unsigned i=0; idisplay(m_ctx, tout); + tout<<"tail with unbound vars: "<display(m_ctx, tout); + tout<<"tail with unbound vars: "<set_accounting_parent_object(m_ctx, old_r); + } + + void rule_manager::substitute(rule_ref& r, unsigned sz, expr*const* es) { + expr_ref tmp(m); + app_ref new_head(m); + app_ref_vector new_tail(m); + svector tail_neg; + var_subst vs(m, false); + vs(r->get_head(), sz, es, tmp); + new_head = to_app(tmp); + for (unsigned i = 0; i < r->get_tail_size(); ++i) { + vs(r->get_tail(i), sz, es, tmp); + new_tail.push_back(to_app(tmp)); + tail_neg.push_back(r->is_neg_tail(i)); + } + r = mk(new_head.get(), new_tail.size(), new_tail.c_ptr(), tail_neg.c_ptr(), r->name()); + + // keep old variable indices around so we can compose with substitutions. + // r->norm_vars(*this); + } + + + void rule_manager::check_valid_rule(app * head, unsigned n, app * const * tail) const { + check_valid_head(head); + } + + void rule_manager::check_valid_head(expr * head) const { + SASSERT(head); + + if (!is_predicate(head)) { + std::ostringstream out; + out << "Illegal head. The head predicate needs to be uninterpreted and registered (as recursive) " << mk_pp(head, m); + throw default_exception(out.str()); + } + unsigned num_args = to_app(head)->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = to_app(head)->get_arg(i); + if (!is_var(arg) && !m.is_value(arg)) { + std::ostringstream out; + out << "Illegal argument to predicate in head " << mk_pp(arg, m); + throw default_exception(out.str()); + } + } + } + + bool rule_manager::is_fact(app * head) const { + unsigned num_args = head->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + if (!m.is_value(head->get_arg(i))) + return false; + } + return true; + } + + void rule::deallocate(ast_manager & m) { + m.dec_ref(m_head); + unsigned n = get_tail_size(); + for (unsigned i = 0; i < n; i++) { + m.dec_ref(get_tail(i)); + } + this->~rule(); + m.get_allocator().deallocate(get_obj_size(n), this); + } + + bool rule::is_in_tail(const func_decl * p, bool only_positive) const { + unsigned len = only_positive ? get_positive_tail_size() : get_uninterpreted_tail_size(); + for (unsigned i = 0; i < len; i++) { + if(get_tail(i)->get_decl()==p) { + return true; + } + } + return false; + } + + struct uninterpreted_function_finder_proc { + bool m_found; + func_decl* m_func; + uninterpreted_function_finder_proc() : m_found(false), m_func(0) {} + void operator()(var * n) { } + void operator()(quantifier * n) { } + void operator()(app * n) { + if(is_uninterp(n)) { + m_found = true; + m_func = n->get_decl(); + } + } + + bool found(func_decl*& f) const { f = m_func; return m_found; } + }; + + // + // non-predicates may appear only in the interpreted tail, it is therefore + // sufficient only to check the tail. + // + bool rule::has_uninterpreted_non_predicates(func_decl*& f) const { + unsigned sz = get_tail_size(); + uninterpreted_function_finder_proc proc; + expr_mark visited; + for(unsigned i = get_uninterpreted_tail_size(); i < sz && !proc.found(f); ++i) { + for_each_expr(proc, visited, get_tail(i)); + } + return proc.found(f); + } + + struct quantifier_finder_proc { + bool m_exist; + bool m_univ; + quantifier_finder_proc() : m_exist(false), m_univ(false) {} + void operator()(var * n) { } + void operator()(quantifier * n) { + if(n->is_forall()) { + m_univ = true; + } + else { + SASSERT(n->is_exists()); + m_exist = true; + } + } + void operator()(app * n) { } + }; + + // + // Quantifiers may appear only in the interpreted tail, it is therefore + // sufficient only to check the interpreted tail. + // + void rule::has_quantifiers(bool& existential, bool& universal) const { + unsigned sz = get_tail_size(); + quantifier_finder_proc proc; + expr_mark visited; + for(unsigned i = get_uninterpreted_tail_size(); i < sz; ++i) { + for_each_expr(proc, visited, get_tail(i)); + } + existential = proc.m_exist; + universal = proc.m_univ; + } + + bool rule::has_quantifiers() const { + bool exist, univ; + has_quantifiers(exist, univ); + return exist || univ; + } + + void rule::get_used_vars(used_vars& used) const { + used.process(get_head()); + unsigned sz = get_tail_size(); + for (unsigned i = 0; i < sz; ++i) { + used.process(get_tail(i)); + } + } + + void rule::get_vars(sort_ref_vector& sorts) const { + sorts.reset(); + used_vars used; + get_used_vars(used); + unsigned sz = used.get_max_found_var_idx_plus_1(); + for (unsigned i = 0; i < sz; ++i) { + sorts.push_back(used.get(i)); + } + } + + void rule::norm_vars(rule_manager & rm) { + used_vars used; + get_used_vars(used); + + unsigned first_unsused = used.get_max_found_var_idx_plus_1(); + if (used.uses_all_vars(first_unsused)) { + return; + } + ast_manager& m = rm.get_manager(); + + unsigned next_fresh_var = 0; + expr_ref_vector subst_vals(m); + for(unsigned i=0; i 0) + out << ","; + out << "\n "; + if (is_neg_tail(i)) + out << "not "; + app * t = get_tail(i); + if(ctx.get_rule_manager().is_predicate(t)) { + output_predicate(ctx, t, out); + } + else { + out << mk_pp(t, m); + } + } + out << '.'; + if(ctx.output_profile()) { + out << " {"; + output_profile(ctx, out); + out << '}'; + } + out << '\n'; + } + + void rule::to_formula(expr_ref& fml) const { + ast_manager & m = fml.get_manager(); + expr_ref_vector body(m); + for (unsigned i = 0; i < m_tail_size; i++) { + body.push_back(get_tail(i)); + if (is_neg_tail(i)) { + body[body.size()-1] = m.mk_not(body.back()); + } + } + switch(body.size()) { + case 0: fml = m_head; break; + case 1: fml = m.mk_implies(body[0].get(), m_head); break; + default: fml = m.mk_implies(m.mk_and(body.size(), body.c_ptr()), m_head); break; + } + + ptr_vector sorts; + get_free_vars(fml, sorts); + if (sorts.empty()) { + return; + } + svector names; + used_symbols<> us; + + us(fml); + sorts.reverse(); + + for (unsigned i = 0; i < sorts.size(); ) { + if (!sorts[i]) { + sorts[i] = m.mk_bool_sort(); + } + for (unsigned j = 0; i < sorts.size(); ++j) { + for (char c = 'A'; i < sorts.size() && c <= 'Z'; ++c) { + func_decl_ref f(m); + std::stringstream _name; + _name << c; + if (j > 0) _name << j; + symbol name(_name.str().c_str()); + if (!us.contains(name)) { + names.push_back(name); + ++i; + } + } + } + } + fml = m.mk_forall(sorts.size(), sorts.c_ptr(), names.c_ptr(), fml); + } + + std::ostream& rule::display_smt2(ast_manager& m, std::ostream & out) const { + expr_ref fml(m); + to_formula(fml); + return out << mk_ismt2_pp(fml, m); + } + + bool rule_eq_proc::operator()(const rule * r1, const rule * r2) const { + if(r1->get_head()!=r2->get_head()) { return false; } + unsigned tail_len = r1->get_tail_size(); + if(r2->get_tail_size()!=tail_len) { + return false; + } + for(unsigned i=0; iget_tail(i)!=r2->get_tail(i)) { + return false; + } + if(r1->is_neg_tail(i)!=r2->is_neg_tail(i)) { + return false; + } + } + return true; + } + + unsigned rule::hash() const { + unsigned res = get_head()->hash(); + unsigned tail_len = get_tail_size(); + for(unsigned i=0; ihash(), is_neg_tail(i))); + } + return res; + } + + unsigned rule_hash_proc::operator()(const rule * r) const { + return r->hash(); + } + + + +}; diff --git a/lib/dl_rule.h b/lib/dl_rule.h new file mode 100644 index 000000000..0117f1e07 --- /dev/null +++ b/lib/dl_rule.h @@ -0,0 +1,265 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_rule.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-05-17. + +Revision History: + +--*/ + +#ifndef _DL_RULE_H_ +#define _DL_RULE_H_ + +#include"ast.h" +#include"dl_costs.h" +#include"dl_util.h" +#include"used_vars.h" + +namespace datalog { + + class rule; + class rule_manager; + class table; + class context; + + typedef obj_ref rule_ref; + typedef ref_vector rule_ref_vector; + typedef ptr_vector rule_vector; + /** + \brief Manager for the \c rule class + + \remark \c rule_manager objects are interchangable as long as they + contain the same \c ast_manager object. + */ + class rule_manager + { + ast_manager& m; + context& m_ctx; + var_counter m_var_counter; + obj_map m_memoize_disj; + expr_ref_vector m_refs; + + // only the context can create a rule_manager + friend class context; + + explicit rule_manager(context& ctx); + + /** + \brief Move functions from predicate tails into the interpreted tail by introducing new variables. + */ + void hoist_compound(unsigned& num_bound, app_ref& fml, app_ref_vector& body); + + void flatten_body(app_ref_vector& body); + + void remove_labels(expr_ref& fml); + + void eliminate_disjunctions(app_ref_vector::element_ref& body, rule_ref_vector& rules, symbol const& name); + + void eliminate_disjunctions(app_ref_vector& body, rule_ref_vector& rules, symbol const& name); + + void eliminate_quantifier_body(app_ref_vector::element_ref& body, rule_ref_vector& rules, symbol const& name); + + void eliminate_quantifier_body(app_ref_vector& body, rule_ref_vector& rules, symbol const& name); + + app_ref ensure_app(expr* e); + + void check_app(expr* e); + + bool contains_predicate(expr* fml) const; + + void bind_variables(expr* fml, bool is_forall, expr_ref& result); + + void mk_rule_core(expr* fml, rule_ref_vector& rules, symbol const& name); + + unsigned hoist_quantifier(bool is_forall, expr_ref& fml, svector* names); + + public: + + ast_manager& get_manager() const { return m; } + + void inc_ref(rule * r); + + void dec_ref(rule * r); + + /** + \brief Create a Datalog rule from a Horn formula. + The formula is of the form (forall (...) (forall (...) (=> (and ...) head))) + + */ + void mk_rule(expr* fml, rule_ref_vector& rules, symbol const& name = symbol::null); + + /** + \brief Create a Datalog query from an expression. + The formula is of the form (exists (...) (exists (...) (and ...)) + */ + void mk_query(expr* query, func_decl_ref& query_pred, rule_ref_vector& query_rules, rule_ref& query_rule); + + /** + \brief Create a Datalog rule head :- tail[0], ..., tail[n-1]. + Return 0 if it is not a valid rule. + + \remark A tail may contain negation. tail[i] is assumed to be negated if is_neg != 0 && is_neg[i] == true + */ + rule * mk(app * head, unsigned n, app * const * tail, bool const * is_neg = 0, + symbol const& name = symbol::null); + + /** + \brief Create a rule with the same tail as \c source and with a specified head. + */ + rule * mk(rule const * source, app * new_head, symbol const& name = symbol::null); + + /** + \brief Create a copy of the given rule. + */ + rule * mk(rule const * source, symbol const& name = symbol::null); + + /** make sure there are not non-quantified variables that occur only in interpreted predicates */ + void fix_unbound_vars(rule_ref& r, bool try_quantifier_elimination); + + + /** + \brief apply substitution to variables of rule. + */ + void substitute(rule_ref& r, unsigned sz, expr*const* es); + + /** + \brief Check that head :- tail[0], ..., tail[n-1] + is a valid Datalog rule. + */ + void check_valid_rule(app * head, unsigned n, app * const * tail) const; + + /** + \brief Check that \c head may occur as a Datalog rule head. + */ + void check_valid_head(expr * head) const; + + /** + \brief Return true if \c head may occur as a fact. + */ + bool is_fact(app * head) const; + + + bool is_predicate(func_decl * f) const; + bool is_predicate(expr * e) const { + return is_app(e) && is_predicate(to_app(e)->get_decl()); + } + + static bool is_forall(ast_manager& m, expr* e, quantifier*& q); + + var_counter& get_var_counter() { return m_var_counter; } + + }; + + class rule : public accounted_object { + friend class rule_manager; + + app * m_head; + unsigned m_tail_size:20; + // unsigned m_reserve:12; + unsigned m_ref_cnt; + unsigned m_positive_cnt; + unsigned m_uninterp_cnt; + symbol m_name; + /** + The following field is an array of tagged pointers. + - Tag 0: the atom is not negated + - Tag 1: the atom is negated. + + The order of tail formulas is the following: + + uninterpreted positive, + uninterpreted negative, + interpreted. + + The negated flag is never set for interpreted tails. + */ + app * m_tail[0]; + + static unsigned get_obj_size(unsigned n) { return sizeof(rule) + n * sizeof(app *); } + + rule() : m_ref_cnt(0) {} + ~rule() {} + + void deallocate(ast_manager & m); + + void get_used_vars(used_vars& uv) const; + + public: + + app * get_head() const { return m_head; } + + func_decl* get_decl() const { return get_head()->get_decl(); } + + unsigned get_tail_size() const { return m_tail_size; } + + /** + \brief Return number of positive uninterpreted predicates in the tail. + + These predicates are the first in the tail. + */ + unsigned get_positive_tail_size() const { return m_positive_cnt; } + unsigned get_uninterpreted_tail_size() const { return m_uninterp_cnt; } + + /** + \brief Return i-th tail atom. The first \c get_uninterpreted_tail_size() + atoms are uninterpreted and the first \c get_positive_tail_size() are + uninterpreted and non-negated. + */ + app * get_tail(unsigned i) const { SASSERT(i < m_tail_size); return UNTAG(app *, m_tail[i]); } + + func_decl* get_decl(unsigned i) const { SASSERT(i < get_uninterpreted_tail_size()); return get_tail(i)->get_decl(); } + + bool is_neg_tail(unsigned i) const { SASSERT(i < m_tail_size); return GET_TAG(m_tail[i]) == 1; } + + /** + Check whether predicate p is in the interpreted tail. + + If only_positive is true, only the positive predicate tail atoms are checked. + */ + bool is_in_tail(const func_decl * p, bool only_positive=false) const; + + bool has_uninterpreted_non_predicates(func_decl*& f) const; + void has_quantifiers(bool& existential, bool& universal) const; + bool has_quantifiers() const; + + /** + \brief Store in d the (direct) dependencies of the given rule. + */ + + void norm_vars(rule_manager & rm); + + void get_vars(sort_ref_vector& sorts) const; + + void to_formula(expr_ref& result) const; + + void display(context & ctx, std::ostream & out) const; + + std::ostream& display_smt2(ast_manager& m, std::ostream & out) const; + + symbol const& name() { return m_name; } + + unsigned hash() const; + + }; + + struct rule_eq_proc { + bool operator()(const rule * r1, const rule * r2) const; + }; + struct rule_hash_proc { + unsigned operator()(const rule * r) const; + }; + +}; + +#endif /* _DL_RULE_H_ */ + diff --git a/lib/dl_rule_set.cpp b/lib/dl_rule_set.cpp new file mode 100644 index 000000000..caecc76ef --- /dev/null +++ b/lib/dl_rule_set.cpp @@ -0,0 +1,691 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_rule_set.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-05-17. + +Revision History: + +--*/ + +#include +#include +#include"dl_context.h" +#include"dl_rule_set.h" +#include"ast_pp.h" + +namespace datalog { + + rule_dependencies::rule_dependencies(context& ctx): m_context(ctx) { + } + + rule_dependencies::rule_dependencies(const rule_dependencies & o, bool reversed): + m_context(o.m_context) { + if(reversed) { + iterator oit = o.begin(); + iterator oend = o.end(); + for(; oit!=oend; ++oit) { + func_decl * pred = oit->m_key; + item_set & orig_items = *oit->get_value(); + + ensure_key(pred); + item_set::iterator dit = orig_items.begin(); + item_set::iterator dend = orig_items.end(); + for(; dit!=dend; ++dit) { + func_decl * master_pred = *dit; + insert(master_pred, pred); + } + } + } + else { + iterator oit = o.begin(); + iterator oend = o.end(); + for(; oit!=oend; ++oit) { + func_decl * pred = oit->m_key; + item_set & orig_items = *oit->get_value(); + m_data.insert(pred, alloc(item_set, orig_items)); + } + } + } + + rule_dependencies::~rule_dependencies() { + reset(); + } + void rule_dependencies::reset() { + reset_dealloc_values(m_data); + } + + void rule_dependencies::remove_m_data_entry(func_decl * key) + { + item_set * itm_set = m_data.find(key); + dealloc(itm_set); + m_data.remove(key); + } + + rule_dependencies::item_set & rule_dependencies::ensure_key(func_decl * pred) { + deps_type::obj_map_entry * e = m_data.insert_if_not_there2(pred, 0); + if(!e->get_data().m_value) { + e->get_data().m_value = alloc(item_set); + } + return *e->get_data().m_value; + } + + void rule_dependencies::insert(func_decl * depending, func_decl * master) { + SASSERT(m_data.contains(master)); //see m_data documentation + item_set & s = ensure_key(depending); + s.insert(master); + } + + void rule_dependencies::populate(const rule_set & rules) { + SASSERT(m_data.empty()); + rule_set::decl2rules::iterator it = rules.m_head2rules.begin(); + rule_set::decl2rules::iterator end = rules.m_head2rules.end(); + for (; it != end; ++it) { + ptr_vector * rules = it->m_value; + ptr_vector::iterator it2 = rules->begin(); + ptr_vector::iterator end2 = rules->end(); + for (; it2 != end2; ++it2) { + populate(*it2); + } + } + } + + void rule_dependencies::populate(unsigned n, rule * const * rules) { + SASSERT(m_data.empty()); + for(unsigned i=0; iget_head()->get_decl(); + func_decl_set & s = ensure_key(d); + + for (unsigned i = 0; i < r->get_tail_size(); ++i) { + m_todo.push_back(r->get_tail(i)); + } + while (!m_todo.empty()) { + expr* e = m_todo.back(); + m_todo.pop_back(); + if (m_visited.is_marked(e)) { + continue; + } + m_visited.mark(e, true); + if (is_app(e)) { + app* a = to_app(e); + d = a->get_decl(); + if (m_context.is_predicate(d)) { + // insert d and ensure the invariant + // that every predicate is present as + // a key in m_data + s.insert(d); + ensure_key(d); + } + m_todo.append(a->get_num_args(), a->get_args()); + } + else if (is_quantifier(e)) { + m_todo.push_back(to_quantifier(e)->get_expr()); + } + } + } + + const rule_dependencies::item_set & rule_dependencies::get_deps(func_decl * f) const { + deps_type::obj_map_entry * e = m_data.find_core(f); + if(!e) { + return m_empty_item_set; + } + SASSERT(e->get_data().get_value()); + return *e->get_data().get_value(); + } + + void rule_dependencies::restrict(const item_set & allowed) { + ptr_vector to_remove; + iterator pit = begin(); + iterator pend = end(); + for(; pit!=pend; ++pit) { + func_decl * pred = pit->m_key; + if(!allowed.contains(pred)) { + to_remove.insert(pred); + continue; + } + item_set& itms = *pit->get_value(); + set_intersection(itms, allowed); + } + ptr_vector::iterator rit = to_remove.begin(); + ptr_vector::iterator rend = to_remove.end(); + for(; rit!=rend; ++rit) { + remove_m_data_entry(*rit); + } + } + + void rule_dependencies::remove(func_decl * itm) { + remove_m_data_entry(itm); + iterator pit = begin(); + iterator pend = end(); + for(; pit!=pend; ++pit) { + item_set & itms = *pit->get_value(); + itms.remove(itm); + } + } + + void rule_dependencies::remove(const item_set & to_remove) { + item_set::iterator rit = to_remove.begin(); + item_set::iterator rend = to_remove.end(); + for(; rit!=rend; ++rit) { + remove_m_data_entry(*rit); + } + iterator pit = begin(); + iterator pend = end(); + for(; pit!=pend; ++pit) { + item_set * itms = pit->get_value(); + set_difference(*itms, to_remove); + } + } + + unsigned rule_dependencies::out_degree(func_decl * f) const { + unsigned res = 0; + iterator pit = begin(); + iterator pend = end(); + for(; pit!=pend; ++pit) { + item_set & itms = *pit->get_value(); + if(itms.contains(f)) { + res++; + } + } + return res; + } + + bool rule_dependencies::sort_deps(ptr_vector & res) { + typedef obj_map deg_map; + unsigned init_len = res.size(); + deg_map degs; + unsigned curr_index = init_len; + rule_dependencies reversed(*this, true); + + iterator pit = begin(); + iterator pend = end(); + for(; pit!=pend; ++pit) { + func_decl * pred = pit->m_key; + unsigned deg = in_degree(pred); + if(deg==0) { + res.push_back(pred); + } + else { + degs.insert(pred, deg); + } + } + + while(curr_indexget_data().m_value; + SASSERT(child_deg>0); + child_deg--; + if(child_deg==0) { + res.push_back(child); + } + } + curr_index++; + } + if(res.size()m_key; + const item_set & deps = *pit->m_value; + item_set::iterator dit=deps.begin(); + item_set::iterator dend=deps.end(); + if(dit==dend) { + out<get_name()<<" - \n"; + } + for(; dit!=dend; ++dit) { + func_decl * dep = *dit; + out<get_name()<<" -> "<get_name()<<"\n"; + } + } + } + + + // ----------------------------------- + // + // rule_set + // + // ----------------------------------- + + rule_set::rule_set(context & ctx) + : m_context(ctx), + m_rule_manager(ctx.get_rule_manager()), + m_rules(m_rule_manager), + m_deps(ctx), + m_stratifier(0) { + } + + rule_set::rule_set(const rule_set & rs) + : m_context(rs.m_context), + m_rule_manager(rs.m_rule_manager), + m_rules(m_rule_manager), + m_deps(rs.m_context), + m_stratifier(0) { + add_rules(rs); + if(rs.m_stratifier) { + TRUSTME(close()); + } + } + + rule_set::~rule_set() { + reset(); + } + + void rule_set::reset() { + if(m_stratifier) { + m_stratifier = 0; + } + reset_dealloc_values(m_head2rules); + m_deps.reset(); + m_rules.reset(); + } + + ast_manager & rule_set::get_manager() const { + return m_context.get_manager(); + } + + void rule_set::add_rule(rule * r) { + TRACE("dl_verbose", r->display(m_context, tout << "add:");); + SASSERT(!is_closed()); + m_rules.push_back(r); + app * head = r->get_head(); + SASSERT(head != 0); + func_decl * d = head->get_decl(); + decl2rules::obj_map_entry* e = m_head2rules.insert_if_not_there2(d, 0); + if (!e->get_data().m_value) e->get_data().m_value = alloc(ptr_vector); + e->get_data().m_value->push_back(r); + } + + void rule_set::del_rule(rule * r) { + TRACE("dl", r->display(m_context, tout << "del:");); + func_decl* d = r->get_head()->get_decl(); + rule_vector* rules = m_head2rules.find(d); +#define DEL_VECTOR(_v) \ + for (unsigned i = (_v).size(); i > 0; ) { \ + --i; \ + if ((_v)[i] == r) { \ + (_v)[i] = (_v).back(); \ + (_v).pop_back(); \ + break; \ + } \ + } \ + + DEL_VECTOR(*rules); + DEL_VECTOR(m_rules); + } + + void rule_set::ensure_closed() + { + if(!is_closed()) { + TRUSTME(close()); + } + } + + bool rule_set::close() { + SASSERT(!is_closed()); //the rule_set is not already closed + + m_deps.populate(*this); + m_stratifier = alloc(rule_stratifier, m_deps); + + if(!stratified_negation()) { + m_stratifier = 0; + m_deps.reset(); + return false; + } + + return true; + } + + void rule_set::reopen() { + SASSERT(is_closed()); + + m_stratifier = 0; + m_deps.reset(); + } + + /** + \brief Return true if the negation is indeed stratified. + */ + bool rule_set::stratified_negation() { + ptr_vector::const_iterator it = m_rules.c_ptr(); + ptr_vector::const_iterator end = m_rules.c_ptr()+m_rules.size(); + for (; it != end; it++) { + rule * r = *it; + app * head = r->get_head(); + func_decl * head_decl = head->get_decl(); + unsigned n = r->get_uninterpreted_tail_size(); + for (unsigned i = r->get_positive_tail_size(); i < n; i++) { + SASSERT(r->is_neg_tail(i)); + func_decl * tail_decl = r->get_tail(i)->get_decl(); + unsigned neg_strat = get_predicate_strat(tail_decl); + unsigned head_strat = get_predicate_strat(head_decl); + + SASSERT(head_strat>=neg_strat); //head strat can never be lower than that of a tail + if(head_strat==neg_strat) { + return false; + } + } + } + return true; + } + + void rule_set::add_rules(const rule_set & src) { + SASSERT(!is_closed()); + unsigned n = src.get_num_rules(); + for (unsigned i=0; iget_data().m_value; + } + + const rule_set::pred_set_vector & rule_set::get_strats() const { + SASSERT(m_stratifier); + return m_stratifier->get_strats(); + } + + unsigned rule_set::get_predicate_strat(func_decl * pred) const { + SASSERT(m_stratifier); + return m_stratifier->get_predicate_strat(pred); + } + + + void rule_set::display(std::ostream & out) const { + out << "; rule count: " << get_num_rules() << "\n"; + out << "; predicate count: " << m_head2rules.size() << "\n"; + decl2rules::iterator it = m_head2rules.begin(); + decl2rules::iterator end = m_head2rules.end(); + for (; it != end; ++it) { + ptr_vector * rules = it->m_value; + ptr_vector::iterator it2 = rules->begin(); + ptr_vector::iterator end2 = rules->end(); + for (; it2 != end2; ++it2) { + rule * r = *it2; + if(!r->passes_output_thresholds(m_context)) { + continue; + } + r->display(m_context, out); + } + } + +#if 0 //print dependencies + out<<"##\n"; + out<get_name()<<" -> "<get_name()<<"\n"; + } + } + if(non_empty && sit!=send) { + out << "\n"; + } + } + } + + // ----------------------------------- + // + // rule_stratifier + // + // ----------------------------------- + + rule_stratifier::~rule_stratifier() { + comp_vector::iterator it = m_strats.begin(); + comp_vector::iterator end = m_strats.end(); + for(; it!=end; ++it) { + SASSERT(*it); + dealloc(*it); + } + } + + unsigned rule_stratifier::get_predicate_strat(func_decl * pred) const { + unsigned num; + if(!m_pred_strat_nums.find(pred, num)) { + //the number of the predicate is not stored, therefore it did not appear + //in the algorithm and therefore it does not depend on anything and nothing + //depends on it. So it is safe to assign zero strate to it, although it is + //not strictly true. + num = 0; + } + return num; + } + + + void rule_stratifier::traverse(T* el) { + unsigned p_num; + if(m_preorder_nums.find(el, p_num)) { + if(p_numinsert(s_el); + m_component_nums.insert(s_el, comp_num); + } while(s_el!=el); + m_stack_P.pop_back(); + } + } + } + + void rule_stratifier::process() { + if(m_deps.empty()) { + return; + } + + //detect strong components + rule_dependencies::iterator it = m_deps.begin(); + rule_dependencies::iterator end = m_deps.end(); + for(; it!=end; ++it) { + T * el = it->m_key; + //we take a note of the preorder number with which this sweep started + m_first_preorder = m_next_preorder; + traverse(el); + } + + //do topological sorting + + //degres of components (number of inter-component edges ending up in the component) + svector in_degrees; + in_degrees.resize(m_components.size()); + + //init in_degrees + it = m_deps.begin(); + end = m_deps.end(); + for(; it!=end; ++it) { + T * el = it->m_key; + item_set * out_edges = it->m_value; + + unsigned el_comp; + TRUSTME( m_component_nums.find(el, el_comp) ); + + item_set::iterator eit=out_edges->begin(); + item_set::iterator eend=out_edges->end(); + for(; eit!=eend; ++eit) { + T * tgt = *eit; + + unsigned tgt_comp = m_component_nums.find(tgt); + + if(el_comp!=tgt_comp) { + in_degrees[tgt_comp]++; + } + } + } + + + //We put components whose indegree is zero to m_strats and assign its + //m_components entry to zero. + unsigned comp_cnt = m_components.size(); + for(unsigned i=0; ibegin(); + item_set::iterator cend=comp->end(); + for(; cit!=cend; ++cit) { + T * el = *cit; + const item_set & deps = m_deps.get_deps(el); + item_set::iterator eit=deps.begin(); + item_set::iterator eend=deps.end(); + for(; eit!=eend; ++eit) { + T * tgt = *eit; + unsigned tgt_comp; + TRUSTME( m_component_nums.find(tgt, tgt_comp) ); + + //m_components[tgt_comp]==0 means the edge is intra-component. + //Otherwise it would go to another component, but it is not possible, since + //as m_components[tgt_comp]==0, its indegree has already reached zero. + if(m_components[tgt_comp]) { + SASSERT(in_degrees[tgt_comp]>0); + in_degrees[tgt_comp]--; + if(in_degrees[tgt_comp]==0) { + m_strats.push_back(m_components[tgt_comp]); + m_components[tgt_comp] = 0; + } + } + + + traverse(*cit); + } + } + strats_index++; + } + //we have managed to topologicaly order all the components + SASSERT(std::find_if(m_components.begin(), m_components.end(), + std::bind1st(std::not_equal_to(), (item_set*)0)) == m_components.end()); + + //reverse the strats array, so that the only the later components would depend on earlier ones + std::reverse(m_strats.begin(), m_strats.end()); + + SASSERT(m_pred_strat_nums.empty()); + unsigned strat_cnt = m_strats.size(); + for(unsigned strat_index=0; strat_indexbegin(); + item_set::iterator cend=comp->end(); + for(; cit!=cend; ++cit) { + T * el = *cit; + + m_pred_strat_nums.insert(el, strat_index); + } + } + + //finalize structures that are not needed anymore + m_preorder_nums.finalize(); + m_stack_S.finalize(); + m_stack_P.finalize(); + m_component_nums.finalize(); + m_components.finalize(); + } + +}; diff --git a/lib/dl_rule_set.h b/lib/dl_rule_set.h new file mode 100644 index 000000000..fdbbf7626 --- /dev/null +++ b/lib/dl_rule_set.h @@ -0,0 +1,260 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_rule_set.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-05-17. + +Revision History: + +--*/ +#ifndef _DL_RULE_SET_H_ +#define _DL_RULE_SET_H_ + +#include"obj_hashtable.h" + +#include"dl_rule.h" + +namespace datalog { + + class rule_set; + + class rule_dependencies { + public: + typedef obj_hashtable item_set; + typedef obj_map deps_type; + + typedef deps_type::iterator iterator; + private: + /** + Map (dependent -> set of master objects) + + Each master object is also present as a key of the map, even if its master set + is empty. + */ + deps_type m_data; + context & m_context; + ptr_vector m_todo; + ast_mark m_visited; + + + //we need to take care with removing to aviod memory leaks + void remove_m_data_entry(func_decl * key); + + //sometimes we need to return reference to an empty set, + //so we return reference to this one. + item_set m_empty_item_set; + + item_set & ensure_key(func_decl * pred); + void insert(func_decl * depending, func_decl * master); + void populate(rule const* r); + public: + rule_dependencies(context& ctx); + rule_dependencies(const rule_dependencies & o, bool reversed = false); + ~rule_dependencies(); + void reset(); + + void populate(const rule_set & rules); + void populate(unsigned n, rule * const * rules); + void restrict(const item_set & allowed); + void remove(func_decl * itm); + void remove(const item_set & to_remove); + + bool empty() const { return m_data.empty(); } + const item_set & get_deps(func_decl * f) const; + /** + \brief Number of predicates \c f depends on. + */ + unsigned in_degree(func_decl * f) const { return get_deps(f).size(); } + /** + \brief Number of predicates that depend on \c f. + */ + unsigned out_degree(func_decl * f) const; + + /** + \brief If the rependency graph is acyclic, put all elements into \c res + ordered so that elements can depend only on elements that are before them. + If the graph is not acyclic, return false. + */ + bool sort_deps(ptr_vector & res); + + iterator begin() const { return m_data.begin(); } + iterator end() const { return m_data.end(); } + + void display( std::ostream & out ) const; + }; + + class rule_stratifier { + public: + typedef func_decl T; + typedef obj_hashtable item_set; + typedef ptr_vector comp_vector; + typedef obj_map deps_type; + private: + + const rule_dependencies & m_deps; + comp_vector m_strats; + + obj_map m_preorder_nums; + ptr_vector m_stack_S; + ptr_vector m_stack_P; + + obj_map m_component_nums; + comp_vector m_components; + obj_map m_pred_strat_nums; + + unsigned m_next_preorder; + unsigned m_first_preorder; + + /** + Finds strongly connected components using the Gabow's algorithm. + */ + void traverse(T* el); + + /** + Calls \c traverse to identify strognly connected components and then + orders them using topological sorting. + */ + void process(); + + public: + + /** + \remark The \c stratifier object keeps a reference to the \c deps object, so + it must exist for the whole lifetime of the \c stratifier object. + */ + rule_stratifier(const rule_dependencies & deps) + : m_deps(deps), m_next_preorder(0) + { + process(); + } + + ~rule_stratifier(); + + /** + Return vector of components ordered so that the only dependencies are from + later components to earlier. + */ + const comp_vector & get_strats() const { return m_strats; } + + unsigned get_predicate_strat(func_decl * pred) const; + }; + + /** + \brief Datalog "Program" (aka rule set). + */ + class rule_set { + friend class rule_dependencies; + public: + typedef ptr_vector pred_set_vector; + typedef obj_map decl2rules; + private: + typedef obj_map decl2deps; + + context & m_context; + rule_manager & m_rule_manager; + rule_ref_vector m_rules; //!< all rules + decl2rules m_head2rules; //!< mapping from head symbol to rules. + rule_dependencies m_deps; //!< dependencies + scoped_ptr m_stratifier; //!< contains stratifier object iff the rule_set is closed + + + //sometimes we need to return reference to an empty rule_vector, + //so we return reference to this one. + rule_vector m_empty_rule_vector; + + void compute_deps(); + void compute_tc_deps(); + bool stratified_negation(); + public: + rule_set(context & ctx); + rule_set(const rule_set & rs); + ~rule_set(); + + ast_manager & get_manager() const; + rule_manager & get_rule_manager() const { return const_cast(m_rule_manager); } + context& get_context() const { return m_context; } + + /** + \brief Add rule \c r to the rule set. + */ + void add_rule(rule * r); + + /** + \brief Remove rule \c r from the rule set. + */ + void del_rule(rule * r); + + /** + \brief Add all rules from a different rule_set. + */ + void add_rules(const rule_set& src); + void add_rules(unsigned sz, rule * const * rules); + + /** + \brief This method should be invoked after all rules are added to the rule set. + It will check if the negation is indeed stratified. + Return true if succeeded. + + \remark If new rules are added, the rule_set will be "reopen". + */ + bool close(); + void ensure_closed(); + /** + \brief Undo the effect of the \c close() operation. + */ + void reopen(); + bool is_closed() const { return m_stratifier != 0; } + + unsigned get_num_rules() const { return m_rules.size(); } + + rule * get_rule(unsigned i) const { return m_rules[i]; } + rule_ref_vector const& get_rules() const { return m_rules; } + + const rule_vector & get_predicate_rules(func_decl * pred) const; + + const rule_stratifier & get_stratifier() const { + SASSERT(m_stratifier); + return *m_stratifier; + } + const pred_set_vector & get_strats() const; + unsigned get_predicate_strat(func_decl * pred) const; + const rule_dependencies & get_dependencies() const { SASSERT(is_closed()); return m_deps; } + + + void reset(); + + void display(std::ostream & out) const; + + /** + \brief Output rule dependencies. + + The rule set must be closed before calling this function. + */ + void display_deps(std::ostream & out) const; + + typedef rule * const * iterator; + iterator begin() const { return m_rules.c_ptr(); } + iterator end() const { return m_rules.c_ptr()+m_rules.size(); } + + decl2rules::iterator begin_grouped_rules() const { return m_head2rules.begin(); } + decl2rules::iterator end_grouped_rules() const { return m_head2rules.end(); } + + }; + + inline std::ostream& operator<<(std::ostream& out, rule_set const& r) { r.display(out); return out; } + + + +}; + +#endif /* _DL_RULE_SET_H_ */ + diff --git a/lib/dl_rule_subsumption_index.cpp b/lib/dl_rule_subsumption_index.cpp new file mode 100644 index 000000000..43f801bbf --- /dev/null +++ b/lib/dl_rule_subsumption_index.cpp @@ -0,0 +1,84 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_rule_subsumption_index.cpp + +Abstract: + + Subsumption index for rules. + Currently an underapproximation (fails to identify some subsumptions). + +Author: + + Krystof Hoder (t-khoder) 2011-10-10. + +Revision History: + +--*/ + + +#include +#include "ast_pp.h" + +#include "dl_rule_subsumption_index.h" + +namespace datalog { + + // ----------------------------------- + // + // rule_subsumption_index + // + // ----------------------------------- + + + void rule_subsumption_index::handle_unconditioned_rule(rule * r) { + SASSERT(r->get_tail_size()==0); + app * head = r->get_head(); + func_decl * pred = head->get_decl(); + + app_set * head_set; + if(!m_unconditioned_heads.find(pred, head_set)) { + head_set = alloc(app_set); + m_unconditioned_heads.insert(pred, head_set); + } + head_set->insert(head); + } + + void rule_subsumption_index::add(rule * r) { + m_ref_holder.push_back(r); + if(r->get_tail_size()==0) { + handle_unconditioned_rule(r); + } + m_rule_set.insert(r); + } + + bool rule_subsumption_index::is_subsumed(app * query) { + func_decl * pred = query->get_decl(); + + app_set * head_set; + if(m_unconditioned_heads.find(pred, head_set)) { + if(head_set->contains(query)) { + return true; + } + } + return false; + } + + bool rule_subsumption_index::is_subsumed(rule * r) { + + app * head = r->get_head(); + if(is_subsumed(head)) { + return true; + } + + if(m_rule_set.contains(r)) { + return true; + } + + return false; + } + +}; + diff --git a/lib/dl_rule_subsumption_index.h b/lib/dl_rule_subsumption_index.h new file mode 100644 index 000000000..44befc9bb --- /dev/null +++ b/lib/dl_rule_subsumption_index.h @@ -0,0 +1,65 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_rule_subsumption_index.h + +Abstract: + + Subsumption index for rules. + Currently an underapproximation (fails to identify some subsumptions). + +Author: + + Krystof Hoder (t-khoder) 2011-10-10. + +Revision History: + +--*/ + +#ifndef _DL_RULE_SUBSUMPTION_INDEX_H_ +#define _DL_RULE_SUBSUMPTION_INDEX_H_ + +#include "dl_context.h" + +namespace datalog { + + class rule_subsumption_index { + //private and undefined copy constroctor + rule_subsumption_index(rule_subsumption_index const&); + //private and undefined operator= + rule_subsumption_index& operator=(rule_subsumption_index const&); + + typedef obj_hashtable app_set; + + ast_manager & m; + context & m_context; + + rule_ref_vector m_ref_holder; + + obj_map m_unconditioned_heads; + + hashtable m_rule_set; + + void handle_unconditioned_rule(rule * r); + + public: + rule_subsumption_index(context & ctx) : + m(ctx.get_manager()), + m_context(ctx), + m_ref_holder(ctx.get_rule_manager()) {} + + ~rule_subsumption_index() { + reset_dealloc_values(m_unconditioned_heads); + } + + void add(rule * r); + bool is_subsumed(rule * r); + bool is_subsumed(app * query); + }; + +}; + +#endif /* _DL_RULE_SUBSUMPTION_INDEX_H_ */ + diff --git a/lib/dl_rule_transformer.cpp b/lib/dl_rule_transformer.cpp new file mode 100644 index 000000000..5f52e0a39 --- /dev/null +++ b/lib/dl_rule_transformer.cpp @@ -0,0 +1,133 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_rule_transformer.cpp + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-09-20. + +Revision History: + +--*/ +#include +#include + +#include"dl_context.h" + +#include"dl_rule_transformer.h" + +namespace datalog { + + rule_transformer::rule_transformer(context & ctx) + : m_context(ctx), m_rule_manager(m_context.get_rule_manager()), m_dirty(false) { + } + + + rule_transformer::~rule_transformer() { + plugin_vector::iterator it=m_plugins.begin(); + plugin_vector::iterator end=m_plugins.end(); + for(; it!=end; ++it) { + dealloc(*it); + } + } + + struct rule_transformer::plugin_comparator { + bool operator()(rule_transformer::plugin * p1, rule_transformer::plugin * p2) { + return p1->get_priority()>p2->get_priority(); + } + }; + + void rule_transformer::ensure_ordered() { + if (!m_dirty) { + return; + } + std::sort(m_plugins.begin(), m_plugins.end(), plugin_comparator()); + m_dirty=false; + } + + void rule_transformer::register_plugin(plugin * p) { + m_plugins.push_back(p); + p->attach(*this); + m_dirty=true; + } + + bool rule_transformer::operator()(rule_set & rules, model_converter_ref& mc, proof_converter_ref& pc) { + ensure_ordered(); + + bool modified = false; + + TRACE("dl_rule_transf", + tout<<"init:\n"; + rules.display(tout); + ); + plugin_vector::iterator it=m_plugins.begin(); + plugin_vector::iterator end=m_plugins.end(); + for(; it!=end; ++it) { + plugin & p = **it; + + rule_set * new_rules = p(rules, mc, pc); + if (!new_rules) { + continue; + } + if (p.can_destratify_negation()) { + if (!new_rules->is_closed()) { + if (!new_rules->close()) { + warning_msg("a rule transformation skipped because it destratified negation"); + dealloc(new_rules); + continue; + } + } + } + modified = true; + rules.reset(); + rules.add_rules(*new_rules); + dealloc(new_rules); + rules.ensure_closed(); + + TRACE("dl_rule_transf", + tout << typeid(p).name()<<":\n"; + rules.display(tout); + ); + + } + return modified; + } + + //------------------------------ + // + //rule_transformer::plugin + // + //------------------------------ + + void rule_transformer::plugin::remove_duplicate_tails(app_ref_vector& tail, svector& tail_neg) + { + //one set for positive and one for negative + obj_hashtable tail_apps[2]; + unsigned i=0; + while(i + +Author: + + Krystof Hoder (t-khoder) 2010-09-20. + +Revision History: + +--*/ +#ifndef _DL_RULE_TRANSFORMER_H_ +#define _DL_RULE_TRANSFORMER_H_ + +#include"map.h" +#include"vector.h" +#include"dl_rule.h" +#include"dl_rule_set.h" +#include"model_converter.h" +#include"proof_converter.h" + +namespace datalog { + + class rule_transformer { + public: + class plugin; + private: + friend class plugin; + + typedef svector plugin_vector; + struct plugin_comparator; + + context & m_context; + rule_manager & m_rule_manager; + bool m_dirty; + svector m_plugins; + + void ensure_ordered(); + public: + + rule_transformer(context & ctx); + ~rule_transformer(); + + /** + \brief Add a plugin for rule transformation. + + The rule_transformer object takes ownership of the plugin object. + */ + void register_plugin(plugin * p); + + /** + \brief Transform the rule set using the registered transformation plugins. If the rule + set has changed, return true; otherwise return false. + */ + bool operator()(rule_set & rules, model_converter_ref& mc, proof_converter_ref& pc); + }; + + class rule_transformer::plugin { + friend class rule_transformer; + + unsigned m_priority; + bool m_can_destratify_negation; + rule_transformer * m_transformer; + + void attach(rule_transformer & transformer) { m_transformer = &transformer; } + + protected: + /** + \brief Create a plugin object for rule_transformer. + + The priority argument determines in what order will rules be applied to the rules + (higher priority plugins will be applied first). + */ + plugin(unsigned priority, bool can_destratify_negation = false) : m_priority(priority), + m_can_destratify_negation(can_destratify_negation), m_transformer(0) {} + public: + virtual ~plugin() {} + + unsigned get_priority() { return m_priority; } + bool can_destratify_negation() const { return m_can_destratify_negation; } + + /** + \brief Return \c rule_set object with containing transformed rules or 0 if no + transformation was done. + + The caller takes ownership of the returned \c rule_set object. + */ + virtual rule_set * operator()(rule_set const & source, + model_converter_ref& mc, + proof_converter_ref& pc) = 0; + + /** + Removes duplicate tails. + */ + static void remove_duplicate_tails(app_ref_vector& tail, svector& tail_neg); + + }; +}; + +#endif /* _DL_RULE_TRANSFORMER_H_ */ + diff --git a/lib/dl_sieve_relation.cpp b/lib/dl_sieve_relation.cpp new file mode 100644 index 000000000..c3ea5a3d0 --- /dev/null +++ b/lib/dl_sieve_relation.cpp @@ -0,0 +1,666 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_mk_explanations.cpp + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-11-08. + +Revision History: + +--*/ + +#include +#include"ast_pp.h" +#include"dl_sieve_relation.h" + +namespace datalog { + + // ----------------------------------- + // + // sieve_relation + // + // ----------------------------------- + + sieve_relation::sieve_relation(sieve_relation_plugin & p, const relation_signature & s, + const bool * inner_columns, relation_base * inner) + : relation_base(p, s), m_inner_cols(s.size(), inner_columns), m_inner(inner) { + unsigned n = s.size(); + for(unsigned i=0; i 0; ) { + --i; + unsigned idx = m_inner2sig[i]; + s.push_back(m.mk_var(idx, sig[i])); + } + get_inner().to_formula(tmp); + get_plugin().get_context().get_var_subst()(tmp, sz, s.c_ptr(), fml); + } + + + void sieve_relation::display(std::ostream & out) const { + out << "Sieve relation "; + print_container(m_inner_cols, out); + out <<"\n"; + get_inner().display(out); + } + + + // ----------------------------------- + // + // sieve_relation_plugin + // + // ----------------------------------- + + sieve_relation_plugin & sieve_relation_plugin::get_plugin(relation_manager & rmgr) { + sieve_relation_plugin * res = static_cast(rmgr.get_relation_plugin(get_name())); + if(!res) { + res = alloc(sieve_relation_plugin, rmgr); + rmgr.register_plugin(res); + } + return *res; + } + + sieve_relation& sieve_relation_plugin::get(relation_base& r) { + return dynamic_cast(r); + } + + sieve_relation const & sieve_relation_plugin::get(relation_base const& r) { + return dynamic_cast(r); + } + + sieve_relation* sieve_relation_plugin::get(relation_base* r) { + return dynamic_cast(r); + } + + sieve_relation const* sieve_relation_plugin::get(relation_base const* r) { + return dynamic_cast(r); + } + + sieve_relation_plugin::sieve_relation_plugin(relation_manager & manager) + : relation_plugin(get_name(), manager, ST_SIEVE_RELATION), + m_spec_store(*this) {} + + void sieve_relation_plugin::initialize(family_id fid) { + relation_plugin::initialize(fid); + m_spec_store.add_available_kind(get_kind()); + } + + family_id sieve_relation_plugin::get_relation_kind(const relation_signature & sig, + const bool * inner_columns, family_id inner_kind) { + rel_spec spec(sig.size(), inner_columns, inner_kind); + return m_spec_store.get_relation_kind(sig, spec); + } + + family_id sieve_relation_plugin::get_relation_kind(sieve_relation & r, const bool * inner_columns) { + const relation_signature & sig = r.get_signature(); + return get_relation_kind(sig, inner_columns, r.get_inner().get_kind()); + } + + void sieve_relation_plugin::extract_inner_columns(const relation_signature & s, relation_plugin & inner, + svector & inner_columns) { + SASSERT(inner_columns.size()==s.size()); + unsigned n = s.size(); + relation_signature inner_sig_singleton; + for(unsigned i=0; i & inner_columns, relation_signature & inner_sig) { + SASSERT(inner_columns.size()==s.size()); + inner_sig.reset(); + unsigned n = s.size(); + for(unsigned i=0; i inner_cols(s.size()); + extract_inner_columns(s, inner_cols.c_ptr()); + collect_inner_signature(s, inner_cols, inner_sig); +#endif + } + + bool sieve_relation_plugin::can_handle_signature(const relation_signature & s) { + //we do not want this plugin to handle anything by default + return false; +#if 0 + relation_signature inner_sig; + extract_inner_signature(s, inner_sig); + SASSERT(inner_sig.size()<=s.size()); + return !inner_sig.empty() && inner_sig.size()!=s.size(); +#endif + } + + sieve_relation * sieve_relation_plugin::mk_from_inner(const relation_signature & s, const bool * inner_columns, + relation_base * inner_rel) { + SASSERT(!inner_rel->get_plugin().is_sieve_relation()); //it does not make sense to make a sieve of a sieve + return alloc(sieve_relation, *this, s, inner_columns, inner_rel); + } + + sieve_relation * sieve_relation_plugin::mk_empty(const sieve_relation & original) { + return static_cast(mk_empty(original.get_signature(), original.get_kind())); + } + + relation_base * sieve_relation_plugin::mk_empty(const relation_base & original) { + return mk_empty(static_cast(original)); + } + + relation_base * sieve_relation_plugin::mk_empty(const relation_signature & s, family_id kind) { + rel_spec spec; + m_spec_store.get_relation_spec(s, kind, spec); + relation_signature inner_sig; + collect_inner_signature(s, spec.m_inner_cols, inner_sig); + relation_base * inner = get_manager().mk_empty_relation(inner_sig, spec.m_inner_kind); + return mk_from_inner(s, spec.m_inner_cols.c_ptr(), inner); + } + + + relation_base * sieve_relation_plugin::mk_empty(const relation_signature & s) { + UNREACHABLE(); + return 0; +#if 0 + svector inner_cols(s.size()); + extract_inner_columns(s, inner_cols.c_ptr()); + return mk_empty(s, inner_cols.c_ptr()); +#endif + } + + sieve_relation * sieve_relation_plugin::mk_empty(const relation_signature & s, relation_plugin & inner_plugin) { + SASSERT(!inner_plugin.is_sieve_relation()); //it does not make sense to make a sieve of a sieve + svector inner_cols(s.size()); + extract_inner_columns(s, inner_plugin, inner_cols); + relation_signature inner_sig; + collect_inner_signature(s, inner_cols, inner_sig); + relation_base * inner_rel = inner_plugin.mk_empty(inner_sig); + return mk_from_inner(s, inner_cols, inner_rel); + } + + relation_base * sieve_relation_plugin::mk_full(func_decl* p, const relation_signature & s) { + relation_signature empty_sig; + relation_base * inner = get_manager().mk_full_relation(empty_sig, p, null_family_id); + svector inner_cols; + inner_cols.resize(s.size(), false); + return mk_from_inner(s, inner_cols, inner); + } + + sieve_relation * sieve_relation_plugin::mk_full(func_decl* p, const relation_signature & s, relation_plugin & inner_plugin) { + SASSERT(!inner_plugin.is_sieve_relation()); //it does not make sense to make a sieve of a sieve + svector inner_cols(s.size()); + extract_inner_columns(s, inner_plugin, inner_cols); + relation_signature inner_sig; + collect_inner_signature(s, inner_cols, inner_sig); + relation_base * inner_rel = inner_plugin.mk_full(p, inner_sig, null_family_id); + return mk_from_inner(s, inner_cols, inner_rel); + } + + class sieve_relation_plugin::join_fn : public convenient_relation_join_fn { + sieve_relation_plugin & m_plugin; + unsigned_vector m_inner_cols_1; + unsigned_vector m_inner_cols_2; + svector m_result_inner_cols; + + scoped_ptr m_inner_join_fun; + public: + join_fn(sieve_relation_plugin & p, const relation_base & r1, const relation_base & r2, unsigned col_cnt, + const unsigned * cols1, const unsigned * cols2, relation_join_fn * inner_join_fun) + : convenient_relation_join_fn(r1.get_signature(), r2.get_signature(), col_cnt, cols1, cols2), + m_plugin(p), + m_inner_join_fun(inner_join_fun) { + bool r1_sieved = r1.get_plugin().is_sieve_relation(); + bool r2_sieved = r2.get_plugin().is_sieve_relation(); + const sieve_relation * sr1 = r1_sieved ? static_cast(&r1) : 0; + const sieve_relation * sr2 = r2_sieved ? static_cast(&r2) : 0; + if(r1_sieved) { + m_result_inner_cols.append(sr1->m_inner_cols); + } + else { + m_result_inner_cols.resize(r1.get_signature().size(), true); + } + if(r2_sieved) { + m_result_inner_cols.append(sr2->m_inner_cols); + } + else { + m_result_inner_cols.resize(m_result_inner_cols.size() + r2.get_signature().size(), true); + } + } + + virtual relation_base * operator()(const relation_base & r1, const relation_base & r2) { + bool r1_sieved = r1.get_plugin().is_sieve_relation(); + bool r2_sieved = r2.get_plugin().is_sieve_relation(); + SASSERT(r1_sieved || r2_sieved); + const sieve_relation * sr1 = r1_sieved ? static_cast(&r1) : 0; + const sieve_relation * sr2 = r2_sieved ? static_cast(&r2) : 0; + const relation_base & inner1 = r1_sieved ? sr1->get_inner() : r1; + const relation_base & inner2 = r2_sieved ? sr2->get_inner() : r2; + + relation_base * inner_res = (*m_inner_join_fun)(inner1, inner2); + + return m_plugin.mk_from_inner(get_result_signature(), m_result_inner_cols.c_ptr(), inner_res); + } + }; + + relation_join_fn * sieve_relation_plugin::mk_join_fn(const relation_base & r1, const relation_base & r2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2) { + if( &r1.get_plugin()!=this && &r2.get_plugin()!=this ) { + //we create just operations that involve the current plugin + return 0; + } + bool r1_sieved = r1.get_plugin().is_sieve_relation(); + bool r2_sieved = r2.get_plugin().is_sieve_relation(); + const sieve_relation * sr1 = r1_sieved ? static_cast(&r1) : 0; + const sieve_relation * sr2 = r2_sieved ? static_cast(&r2) : 0; + const relation_base & inner1 = r1_sieved ? sr1->get_inner() : r1; + const relation_base & inner2 = r2_sieved ? sr2->get_inner() : r2; + + unsigned_vector inner_cols1; + unsigned_vector inner_cols2; + + for(unsigned i=0; iis_inner_col(cols1[i])) { + continue; + } + if(r2_sieved && !sr2->is_inner_col(cols2[i])) { + continue; + } + inner_cols1.push_back( r1_sieved ? sr1->get_inner_col(cols1[i]) : cols1[i] ); + inner_cols2.push_back( r2_sieved ? sr2->get_inner_col(cols2[i]) : cols2[i] ); + } + + relation_join_fn * inner_join_fun = get_manager().mk_join_fn(inner1, inner2, inner_cols1, inner_cols2, false); + if(!inner_join_fun) { + return 0; + } + return alloc(join_fn, *this, r1, r2, col_cnt, cols1, cols2, inner_join_fun); + } + + + class sieve_relation_plugin::transformer_fn : public convenient_relation_transformer_fn { + svector m_result_inner_cols; + + scoped_ptr m_inner_fun; + public: + transformer_fn(relation_transformer_fn * inner_fun, const relation_signature & result_sig, + const bool * result_inner_cols) + : m_result_inner_cols(result_sig.size(), result_inner_cols), m_inner_fun(inner_fun) { + get_result_signature() = result_sig; + } + + virtual relation_base * operator()(const relation_base & r0) { + SASSERT(r0.get_plugin().is_sieve_relation()); + const sieve_relation & r = static_cast(r0); + sieve_relation_plugin & plugin = r.get_plugin(); + + relation_base * inner_res = (*m_inner_fun)(r.get_inner()); + + return plugin.mk_from_inner(get_result_signature(), m_result_inner_cols.c_ptr(), inner_res); + } + }; + + relation_transformer_fn * sieve_relation_plugin::mk_project_fn(const relation_base & r0, unsigned col_cnt, + const unsigned * removed_cols) { + if(&r0.get_plugin()!=this) { + return 0; + } + const sieve_relation & r = static_cast(r0); + unsigned_vector inner_removed_cols; + + for(unsigned i=0; i result_inner_cols = r.m_inner_cols; + project_out_vector_columns(result_inner_cols, col_cnt, removed_cols); + + relation_signature result_sig; + relation_signature::from_project(r.get_signature(), col_cnt, removed_cols, result_sig); + + relation_transformer_fn * inner_fun; + if(inner_removed_cols.empty()) { + inner_fun = alloc(identity_relation_transformer_fn); + } + else { + inner_fun = get_manager().mk_project_fn(r.get_inner(), inner_removed_cols); + } + + if(!inner_fun) { + return 0; + } + return alloc(transformer_fn, inner_fun, result_sig, result_inner_cols.c_ptr()); + } + + relation_transformer_fn * sieve_relation_plugin::mk_rename_fn(const relation_base & r0, + unsigned cycle_len, const unsigned * permutation_cycle) { + if(&r0.get_plugin()!=this) { + return 0; + } + const sieve_relation & r = static_cast(r0); + + unsigned sig_sz = r.get_signature().size(); + unsigned_vector permutation; + add_sequence(0, sig_sz, permutation); + permutate_by_cycle(permutation, cycle_len, permutation_cycle); + + bool inner_identity; + unsigned_vector inner_permutation; + collect_sub_permutation(permutation, r.m_sig2inner, inner_permutation, inner_identity); + + svector result_inner_cols = r.m_inner_cols; + permutate_by_cycle(result_inner_cols, cycle_len, permutation_cycle); + + relation_signature result_sig; + relation_signature::from_rename(r.get_signature(), cycle_len, permutation_cycle, result_sig); + + relation_transformer_fn * inner_fun = + get_manager().mk_permutation_rename_fn(r.get_inner(), inner_permutation); + if(!inner_fun) { + return 0; + } + return alloc(transformer_fn, inner_fun, result_sig, result_inner_cols.c_ptr()); + } + + + class sieve_relation_plugin::union_fn : public relation_union_fn { + scoped_ptr m_union_fun; + public: + union_fn(relation_union_fn * union_fun) : m_union_fun(union_fun) {} + + virtual void operator()(relation_base & tgt, const relation_base & src, relation_base * delta) { + bool tgt_sieved = tgt.get_plugin().is_sieve_relation(); + bool src_sieved = src.get_plugin().is_sieve_relation(); + bool delta_sieved = delta && delta->get_plugin().is_sieve_relation(); + sieve_relation * stgt = tgt_sieved ? static_cast(&tgt) : 0; + const sieve_relation * ssrc = src_sieved ? static_cast(&src) : 0; + sieve_relation * sdelta = delta_sieved ? static_cast(delta) : 0; + relation_base & itgt = tgt_sieved ? stgt->get_inner() : tgt; + const relation_base & isrc = src_sieved ? ssrc->get_inner() : src; + relation_base * idelta = delta_sieved ? &sdelta->get_inner() : delta; + + (*m_union_fun)(itgt, isrc, idelta); + } + }; + + relation_union_fn * sieve_relation_plugin::mk_union_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta) { + if(&tgt.get_plugin()!=this && &src.get_plugin()!=this && (delta && &delta->get_plugin()!=this)) { + //we create the operation only if it involves this plugin + return 0; + } + + bool tgt_sieved = tgt.get_plugin().is_sieve_relation(); + bool src_sieved = src.get_plugin().is_sieve_relation(); + bool delta_sieved = delta && delta->get_plugin().is_sieve_relation(); + const sieve_relation * stgt = tgt_sieved ? static_cast(&tgt) : 0; + const sieve_relation * ssrc = src_sieved ? static_cast(&src) : 0; + const sieve_relation * sdelta = delta_sieved ? static_cast(delta) : 0; + const relation_base & itgt = tgt_sieved ? stgt->get_inner() : tgt; + const relation_base & isrc = src_sieved ? ssrc->get_inner() : src; + const relation_base * idelta = delta_sieved ? &sdelta->get_inner() : delta; + + //Now we require that the sieved and inner columns must match on all relations. + //We may want to allow for some cases of misalignment even though it could introcude imprecision + if( tgt_sieved && src_sieved && (!delta || delta_sieved) ) { + if( !vectors_equal(stgt->m_inner_cols, ssrc->m_inner_cols) + || (delta && !vectors_equal(stgt->m_inner_cols, sdelta->m_inner_cols)) ) { + return 0; + } + } + else { + if( (stgt && !stgt->no_sieved_columns()) + || (ssrc && !ssrc->no_sieved_columns()) + || (sdelta && !sdelta->no_sieved_columns()) ) { + //We have an unsieved relation and then some relation with some sieved columns, + //which means there is an misalignment. + return 0; + } + } + + relation_union_fn * union_fun = get_manager().mk_union_fn(itgt, isrc, idelta); + if(!union_fun) { + return 0; + } + + return alloc(union_fn, union_fun); + } + + + class sieve_relation_plugin::filter_fn : public relation_mutator_fn { + scoped_ptr m_inner_fun; + public: + filter_fn(relation_mutator_fn * inner_fun) + : m_inner_fun(inner_fun) {} + + virtual void operator()(relation_base & r0) { + SASSERT(r0.get_plugin().is_sieve_relation()); + sieve_relation & r = static_cast(r0); + + (*m_inner_fun)(r.get_inner()); + } + }; + + relation_mutator_fn * sieve_relation_plugin::mk_filter_identical_fn(const relation_base & r0, + unsigned col_cnt, const unsigned * identical_cols) { + if(&r0.get_plugin()!=this) { + return 0; + } + const sieve_relation & r = static_cast(r0); + unsigned_vector inner_icols; + + //we ignore the columns which do not belong to the inner relation (which introduces imprecision) + for(unsigned i=0; i(r0); + if(!r.is_inner_col(col)) { + //if the column which do not belong to the inner relation, we do nothing (which introduces imprecision) + return alloc(identity_relation_mutator_fn); + } + unsigned inner_col = r.get_inner_col(col); + + relation_mutator_fn * inner_fun = get_manager().mk_filter_equal_fn(r.get_inner(), value, inner_col); + if(!inner_fun) { + return 0; + } + return alloc(filter_fn, inner_fun); + } + + relation_mutator_fn * sieve_relation_plugin::mk_filter_interpreted_fn(const relation_base & rb, + app * condition) { + if(&rb.get_plugin()!=this) { + return 0; + } + ast_manager & m = get_ast_manager(); + const sieve_relation & r = static_cast(rb); + const relation_signature sig = r.get_signature(); + unsigned sz = sig.size(); + + var_idx_set cond_vars; + collect_vars(m, condition, cond_vars); + expr_ref_vector subst_vect(m); + subst_vect.resize(sz); + unsigned subst_ofs = sz-1; + for(unsigned i=0; i m_inner_fun; + public: + negation_filter_fn(relation_intersection_filter_fn * inner_fun) + : m_inner_fun(inner_fun) {} + + virtual void operator()(relation_base & r, const relation_base & neg) { + bool r_sieved = r.get_plugin().is_sieve_relation(); + bool neg_sieved = neg.get_plugin().is_sieve_relation(); + SASSERT(r_sieved || neg_sieved); + sieve_relation * sr = r_sieved ? static_cast(&r) : 0; + const sieve_relation * sneg = neg_sieved ? static_cast(&neg) : 0; + relation_base & inner_r = r_sieved ? sr->get_inner() : r; + const relation_base & inner_neg = neg_sieved ? sneg->get_inner() : neg; + + (*m_inner_fun)(inner_r, inner_neg); + } + }; + + relation_intersection_filter_fn * sieve_relation_plugin::mk_filter_by_negation_fn(const relation_base & r, + const relation_base & neg, unsigned col_cnt, const unsigned * r_cols, + const unsigned * neg_cols) { + if(&r.get_plugin()!=this && &neg.get_plugin()!=this) { + //we create just operations that involve the current plugin + return 0; + } + bool r_sieved = r.get_plugin().is_sieve_relation(); + bool neg_sieved = neg.get_plugin().is_sieve_relation(); + SASSERT(r_sieved || neg_sieved); + const sieve_relation * sr = r_sieved ? static_cast(&r) : 0; + const sieve_relation * sneg = neg_sieved ? static_cast(&neg) : 0; + const relation_base & inner_r = r_sieved ? sr->get_inner() : r; + const relation_base & inner_neg = neg_sieved ? sneg->get_inner() : neg; + + unsigned_vector ir_cols; + unsigned_vector ineg_cols; + + for(unsigned i=0; iis_inner_col(r_cols[i]); + bool neg_col_inner = neg_sieved && !sneg->is_inner_col(neg_cols[i]); + if(r_col_inner && neg_col_inner) { + ir_cols.push_back( r_sieved ? sr->get_inner_col(i) : i ); + ineg_cols.push_back( neg_sieved ? sneg->get_inner_col(i) : i ); + } + else if(!r_col_inner && neg_col_inner) { + //Sieved (i.e. full) column in r is matched on an inner column in neg. + //If we assume the column in neg is not full, no rows from the inner relation of + //r would be removed. So in this case we perform no operation at cost of a little + //impresicion. + return alloc(identity_relation_intersection_filter_fn); + } + else { + //Inner or sieved column in r must match a sieved column in neg. + //Since sieved columns are full, this is always true so we can skip the equality. + continue; + } + } + + relation_intersection_filter_fn * inner_fun = + get_manager().mk_filter_by_negation_fn(inner_r, inner_neg, ir_cols, ineg_cols); + if(!inner_fun) { + return 0; + } + return alloc(negation_filter_fn, inner_fun); + } + + +}; diff --git a/lib/dl_sieve_relation.h b/lib/dl_sieve_relation.h new file mode 100644 index 000000000..d6df3af55 --- /dev/null +++ b/lib/dl_sieve_relation.h @@ -0,0 +1,197 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_sieve_relation.h + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-11-11. + +Revision History: + +--*/ + +#ifndef _DL_SIEVE_RELATION_H_ +#define _DL_SIEVE_RELATION_H_ + +#include "dl_context.h" + +namespace datalog { + + class sieve_relation; + + class sieve_relation_plugin : public relation_plugin { + friend class sieve_relation; + public: + struct rel_spec { + svector m_inner_cols; + family_id m_inner_kind; + + /** + Create uninitialized rel_spec. + */ + rel_spec() {} + /** + \c inner_kind==null_family_id means we will not specify a relation kind when requesting + the relation object from the relation_manager. + + \c inner_kind==null_family_id cannot hold in a specification of existing relation object. + */ + rel_spec(unsigned sig_sz, const bool * inner_cols, family_id inner_kind=null_family_id) + : m_inner_cols(sig_sz, inner_cols), m_inner_kind(inner_kind) {} + + bool operator==(const rel_spec & o) const { + return m_inner_kind==o.m_inner_kind && vectors_equal(m_inner_cols, o.m_inner_cols); + } + + struct hash { + unsigned operator()(const rel_spec & s) const { + return int_vector_hash(s.m_inner_cols)^s.m_inner_kind; + } + }; + }; + private: + + class join_fn; + class transformer_fn; + class union_fn; + class filter_fn; + class negation_filter_fn; + + rel_spec_store > m_spec_store; + + family_id get_relation_kind(sieve_relation & r, const bool * inner_columns); + + void extract_inner_columns(const relation_signature & s, relation_plugin & inner, + svector & inner_columns); + void extract_inner_signature(const relation_signature & s, relation_signature & inner_sig); + void collect_inner_signature(const relation_signature & s, const svector & inner_columns, + relation_signature & inner_sig); + public: + static symbol get_name() { return symbol("sieve_relation"); } + static sieve_relation_plugin& get_plugin(relation_manager & rmgr); + + static sieve_relation& get(relation_base& r); + static sieve_relation const & get(relation_base const& r); + static sieve_relation* get(relation_base* r); + static sieve_relation const* get(relation_base const* r); + + sieve_relation_plugin(relation_manager & manager); + + virtual void initialize(family_id fid); + + family_id get_relation_kind(const relation_signature & sig, const bool * inner_columns, + family_id inner_kind); + family_id get_relation_kind(const relation_signature & sig, const svector & inner_columns, + family_id inner_kind) { + SASSERT(sig.size()==inner_columns.size()); + return get_relation_kind(sig, inner_columns.c_ptr(), inner_kind); + } + + virtual bool can_handle_signature(const relation_signature & s); + + virtual relation_base * mk_empty(const relation_signature & s); + sieve_relation * mk_empty(const sieve_relation & original); + virtual relation_base * mk_empty(const relation_base & original); + virtual relation_base * mk_empty(const relation_signature & s, family_id kind); + sieve_relation * mk_empty(const relation_signature & s, relation_plugin & inner_plugin); + + virtual relation_base * mk_full(func_decl* p, const relation_signature & s); + sieve_relation * mk_full(func_decl* p, const relation_signature & s, relation_plugin & inner_plugin); + + + sieve_relation * mk_from_inner(const relation_signature & s, const bool * inner_columns, + relation_base * inner_rel); + sieve_relation * mk_from_inner(const relation_signature & s, const svector inner_columns, + relation_base * inner_rel) { + SASSERT(inner_columns.size()==s.size()); + return mk_from_inner(s, inner_columns.c_ptr(), inner_rel); + } + + protected: + + virtual relation_join_fn * mk_join_fn(const relation_base & t1, const relation_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2); + virtual relation_transformer_fn * mk_project_fn(const relation_base & t, unsigned col_cnt, + const unsigned * removed_cols); + virtual relation_transformer_fn * mk_rename_fn(const relation_base & t, unsigned permutation_cycle_len, + const unsigned * permutation_cycle); + virtual relation_union_fn * mk_union_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta); + virtual relation_mutator_fn * mk_filter_identical_fn(const relation_base & t, unsigned col_cnt, + const unsigned * identical_cols); + virtual relation_mutator_fn * mk_filter_equal_fn(const relation_base & t, const relation_element & value, + unsigned col); + virtual relation_mutator_fn * mk_filter_interpreted_fn(const relation_base & t, app * condition); + virtual relation_intersection_filter_fn * mk_filter_by_negation_fn(const relation_base & t, + const relation_base & negated_obj, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * negated_cols); + }; + + + // ----------------------------------- + // + // sieve_relation + // + // ----------------------------------- + + class sieve_relation : public relation_base { + friend class sieve_relation_plugin; + friend class sieve_relation_plugin::join_fn; + friend class sieve_relation_plugin::transformer_fn; + friend class sieve_relation_plugin::union_fn; + friend class sieve_relation_plugin::filter_fn; + + svector m_inner_cols; + + unsigned_vector m_sig2inner; + unsigned_vector m_inner2sig; + unsigned_vector m_ignored_cols; //in ascending order, so that it can be used in project-like functions + + scoped_rel m_inner; + + + sieve_relation(sieve_relation_plugin & p, const relation_signature & s, + const bool * inner_columns, relation_base * inner); + + public: + sieve_relation_plugin & get_plugin() const { + return static_cast(relation_base::get_plugin()); + } + + bool is_inner_col(unsigned idx) const { return m_sig2inner[idx]!=UINT_MAX; } + unsigned get_inner_col(unsigned idx) const { + SASSERT(is_inner_col(idx)); + return m_sig2inner[idx]; + } + bool no_sieved_columns() const { return m_ignored_cols.size()==0; } + bool no_inner_columns() const { return m_ignored_cols.size()==get_signature().size(); } + + relation_base & get_inner() { return *m_inner; } + const relation_base & get_inner() const { return *m_inner; } + + virtual void add_fact(const relation_fact & f); + virtual bool contains_fact(const relation_fact & f) const; + virtual sieve_relation * clone() const; + virtual relation_base * complement(func_decl*p) const; + virtual void to_formula(expr_ref& fml) const; + + virtual bool empty() const { return get_inner().empty(); } + virtual void reset() { get_inner().reset(); } + virtual unsigned get_size_estimate_rows() const { return get_inner().get_size_estimate_rows(); } + virtual unsigned get_size_estimate_bytes() const { return get_inner().get_size_estimate_bytes(); } + + virtual void display(std::ostream & out) const; + }; + + +}; + +#endif /* _DL_SIEVE_RELATION_H_ */ + diff --git a/lib/dl_simplifier_plugin.cpp b/lib/dl_simplifier_plugin.cpp new file mode 100644 index 000000000..b357b39f8 --- /dev/null +++ b/lib/dl_simplifier_plugin.cpp @@ -0,0 +1,64 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + dl_simplifier_plugin.cpp + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2010-08-10 + +Revision History: + +--*/ + +#include"dl_simplifier_plugin.h" + +namespace datalog { + + dl_simplifier_plugin::dl_simplifier_plugin(ast_manager& m) + : simplifier_plugin(symbol("datalog_relation"), m), + m_util(m) + {} + + bool dl_simplifier_plugin::reduce( + func_decl * f, unsigned num_args, expr* const* args, expr_ref& result) { + uint64 v1, v2; + switch(f->get_decl_kind()) { + case OP_DL_LT: + if (m_util.try_get_constant(args[0], v1) && + m_util.try_get_constant(args[1], v2)) { + result = (v1 < v2)?m_manager.mk_true():m_manager.mk_false(); + return true; + } + // x < x <=> false + if (args[0] == args[1]) { + result = m_manager.mk_false(); + return true; + } + // x < 0 <=> false + if (m_util.try_get_constant(args[1], v2) && v2 == 0) { + result = m_manager.mk_false(); + return true; + } + // 0 < x <=> 0 != x + if (m_util.try_get_constant(args[1], v1) && v1 == 0) { + result = m_manager.mk_not(m_manager.mk_eq(args[0], args[1])); + return true; + } + break; + + default: + break; + } + return false; + } + +}; + + diff --git a/lib/dl_simplifier_plugin.h b/lib/dl_simplifier_plugin.h new file mode 100644 index 000000000..8633b7144 --- /dev/null +++ b/lib/dl_simplifier_plugin.h @@ -0,0 +1,38 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + dl_simplifier_plugin.h + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2010-08-10 + +Revision History: + +--*/ +#ifndef _DL_SIMPLIFIER_PLUGIN_H_ +#define _DL_SIMPLIFIER_PLUGIN_H_ + +#include"dl_decl_plugin.h" +#include "simplifier_plugin.h" + +namespace datalog { + + class dl_simplifier_plugin : public simplifier_plugin { + dl_decl_util m_util; + public: + dl_simplifier_plugin(ast_manager& m); + + virtual bool reduce(func_decl * f, unsigned num_args, expr* const* args, expr_ref& result); + + }; + +}; +#endif /* _DL_SIMPLIFIER_PLUGIN_H_ */ + diff --git a/lib/dl_skip_table.cpp b/lib/dl_skip_table.cpp new file mode 100644 index 000000000..50786e22b --- /dev/null +++ b/lib/dl_skip_table.cpp @@ -0,0 +1,620 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + dl_skip_table.h + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) + Leonardo de Moura (leonardo) 2010-10-14 + + +Revision History: + +--*/ + +#ifndef _EXTERNAL_RELEASE + +#include "dl_skip_table.h" +#include "dl_table.h" +#include "dl_context.h" + + +namespace datalog { + + skip_table & skip_table_plugin::get(table_base& r) { + return static_cast(r); + } + + skip_table const & skip_table_plugin::get(table_base const& r) { + return static_cast(r); + } + + table_base * skip_table_plugin::mk_empty(const table_signature & s) { + return alloc(skip_table, *this, s); + } + + skip_table* skip_table_plugin::mk_join( + table_base const& t1, table_base const& t2, table_signature const& result_sig, + unsigned_vector const& cols1, unsigned_vector const& cols2) { + skip_table const& s1 = get(t1); + skip_table const& s2 = get(t2); + imdd_manager& m = s1.get_imdd_manager(); + imdd_ref pr(m); + m.mk_join(s1.get_imdd(), s2.get_imdd(), pr, cols1, cols2); + return alloc(skip_table, s1.get_plugin(), result_sig, pr); + } + + skip_table* skip_table_plugin::mk_join_project( + table_base const& t1, table_base const& t2, table_signature const& result_sig, + unsigned_vector const& cols1, unsigned_vector const& cols2, + unsigned_vector const& proj_cols) { + + skip_table const& s1 = get(t1); + skip_table const& s2 = get(t2); + imdd_manager& m = s1.get_imdd_manager(); + imdd_ref pr(m); + m.mk_join_project(s1.get_imdd(), s2.get_imdd(), pr, cols1, cols2, proj_cols); + return alloc(skip_table, s1.get_plugin(), result_sig, pr); + } + + class skip_table_plugin::join_fn : public convenient_table_join_fn { + public: + join_fn(const table_base & t1, const table_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2): + convenient_table_join_fn(t1.get_signature(), t2.get_signature(), col_cnt, cols1, cols2) { + } + + virtual table_base* operator()(const table_base & t1, const table_base & t2) { + return skip_table_plugin::mk_join(t1, t2, get_result_signature(), m_cols1, m_cols2); + } + }; + + table_join_fn * skip_table_plugin::mk_join_fn(const table_base & t1, const table_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2) { + if (check_kind(t1) && check_kind(t2)) { + return alloc(join_fn, t1, t2, col_cnt, cols1, cols2); + } + TRACE("dl", tout << "could not handle operation\n";); + return 0; + } + + class skip_table_plugin::join_project_fn : public convenient_table_join_project_fn { + public: + join_project_fn(const table_base & t1, const table_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2, + unsigned removed_col_cnt, const unsigned * removed_cols): + convenient_table_join_project_fn(t1.get_signature(), t2.get_signature(), col_cnt, cols1, cols2, + removed_col_cnt, removed_cols) { + } + + virtual table_base* operator()(const table_base & t1, const table_base & t2) { + return skip_table_plugin::mk_join_project(t1, t2, get_result_signature(), m_cols1, m_cols2, m_removed_cols); + } + }; + + + + table_join_fn * skip_table_plugin::mk_join_project_fn( + const table_base & t1, const table_base & t2, + unsigned joined_col_cnt, const unsigned * cols1, const unsigned * cols2, + unsigned removed_col_cnt, const unsigned * removed_cols) { + if (check_kind(t1) && check_kind(t2)) { + return alloc(join_project_fn, t1, t2, joined_col_cnt, cols1, cols2, removed_col_cnt, removed_cols); + } + TRACE("dl", tout << "could not handle operation\n";); + return 0; + } + + class skip_table_plugin::union_fn : public table_union_fn { + public: + virtual void operator()(table_base& tgt, const table_base& src, table_base* delta) { + skip_table& s1 = get(tgt); + skip_table const& s2 = get(src); + imdd_manager& m = s1.get_imdd_manager(); + imdd_ref r(m); + m.mk_union(s1.get_imdd(), s2.get_imdd(), r); + if (delta) { + skip_table& d = get(*delta); + if (m.is_subset(r, s1.get_imdd())) { + d.update(m.mk_empty(s1.get_signature().size())); + } + else { + d.update(r); + } + } + s1.update(r); + } + }; + + table_union_fn * skip_table_plugin::mk_union_fn(const table_base & tgt, const table_base & src, const table_base * delta) { + if (check_kind(tgt) && check_kind(src) && (!delta || check_kind(*delta))) { + return alloc(union_fn); + } + TRACE("dl", tout << "could not handle operation\n";); + return 0; + } + + skip_table* skip_table_plugin::mk_project(table_base const& src, table_signature const& result_sig, unsigned_vector const& cols) { + skip_table const& s = get(src); + imdd_manager& m = s.get_imdd_manager(); + imdd_ref pr(m); + m.mk_project(s.get_imdd(), pr, cols.size(), cols.c_ptr()); + return alloc(skip_table, s.get_plugin(), result_sig, pr); + } + + + class skip_table_plugin::project_fn : public convenient_table_project_fn { + public: + project_fn(table_signature const& orig_sig, unsigned col_cnt, unsigned const* removed_cols): + convenient_table_project_fn(orig_sig, col_cnt, removed_cols) {} + + table_base* operator()(table_base const& src) { + return mk_project(src, get_result_signature(), m_removed_cols); + } + }; + + table_transformer_fn * skip_table_plugin::mk_project_fn(const table_base & t, unsigned col_cnt, const unsigned * removed_cols) { + if (check_kind(t)) { + return alloc(project_fn, t.get_signature(), col_cnt, removed_cols); + } + TRACE("dl", tout << "could not handle operation\n";); + return 0; + } + + class skip_table_plugin::rename_fn : public convenient_table_rename_fn { + + void swap2(imdd_ref& n, unsigned col1, unsigned col2) { + imdd_manager& m = n.get_manager(); + imdd_ref tmp(m); + if (col1 == col2) { + return; + } + if (col1 > col2) { + std::swap(col1, col2); + } + for (unsigned i = col1; i < col2; ++i) { + m.mk_swap(n, tmp, i); + n = tmp; + } + for (unsigned i = col2 - 1; i > col1; ) { + --i; + m.mk_swap(n, tmp, i); + n = tmp; + } + } + public: + rename_fn(table_signature const& sig, unsigned cycle_len, unsigned const* cycle): + convenient_rename_fn(sig, cycle_len, cycle) {} + + table_base* operator()(table_base const& src) { + TRACE("skip", + for (unsigned i = 0; i < m_cycle.size(); ++i) { + tout << m_cycle[i] << " "; + } + tout << "\n"; + src.display(tout);); + skip_table const& s = get(src); + imdd_ref n(s.m_imdd, s.get_imdd_manager()); + unsigned cycle_len = m_cycle.size(); + unsigned col1, col2; + // TBD: review this for proper direction + for (unsigned i = 0; i + 1 < cycle_len; ++i) { + col1 = m_cycle[i]; + col2 = m_cycle[i+1]; + swap2(n, col1, col2); + } + if (cycle_len > 2) { + col1 = m_cycle[cycle_len-1]; + col2 = m_cycle[0]; + swap2(n, col1, col2); + } + return alloc(skip_table, s.get_plugin(), get_result_signature(), n); + } + }; + + table_transformer_fn * skip_table_plugin::mk_rename_fn(const table_base & t, unsigned len, const unsigned * cycle) { + if (check_kind(t)) { + return alloc(rename_fn, t.get_signature(), len, cycle); + } + TRACE("dl", tout << "could not handle operation\n";); + return 0; + } + + class skip_table_plugin::filter_identical_fn : public table_mutator_fn { + unsigned_vector m_cols; + + public: + filter_identical_fn(unsigned cnt, unsigned const* cols): + m_cols(cnt, cols) + {} + + void operator()(table_base & t) { + skip_table& s = get(t); + imdd_manager& m = s.get_imdd_manager(); + m.mk_filter_identical(s.get_imdd(), s.m_imdd, m_cols.size(), m_cols.c_ptr(), true); + } + }; + + table_mutator_fn * skip_table_plugin::mk_filter_identical_fn(const table_base & t, unsigned col_cnt, + const unsigned * identical_cols) { + if (check_kind(t)) { + return alloc(filter_identical_fn, col_cnt, identical_cols); + } + TRACE("dl", tout << "could not handle operation\n";); + return 0; + } + + class skip_table_plugin::filter_equal_fn : public table_mutator_fn { + unsigned m_col; + unsigned m_value; + public: + filter_equal_fn(const table_base & t, const table_element & v, unsigned col): + m_col(col), + m_value(static_cast(v)) + { + SASSERT(v <= UINT_MAX); + } + + virtual void operator()(table_base& src) { + skip_table& s = get(src); + imdd_manager& m = s.get_imdd_manager(); + m.mk_filter_equal(s.get_imdd(), s.m_imdd, m_col, m_value); + } + }; + + table_mutator_fn * skip_table_plugin::mk_filter_equal_fn(const table_base & t, const table_element & value, + unsigned col) { + if (check_kind(t)) { + return alloc(filter_equal_fn, t, value, col); + } + TRACE("dl", tout << "could not handle operation\n";); + return 0; + } + + class skip_table_plugin::filter_not_equal_fn : public table_mutator_fn { + unsigned m_col; + unsigned m_value; + public: + filter_not_equal_fn(const table_base & t, const table_element & v, unsigned col): + m_col(col), + m_value(static_cast(v)) + { + SASSERT(v <= UINT_MAX); + } + + virtual void operator()(table_base& src) { + skip_table& s = get(src); + imdd_manager& m = s.get_imdd_manager(); + m.mk_filter_disequal(s.get_imdd(), s.m_imdd, m_col, m_value); + } + }; + + table_mutator_fn * skip_table_plugin::mk_filter_not_equal_fn(const table_base & t, const table_element & value, + unsigned col) { + if (check_kind(t)) { + return alloc(filter_not_equal_fn, t, value, col); + } + TRACE("dl", tout << "could not handle operation\n";); + return 0; + } + + class skip_table_plugin::filter_distinct_fn : public table_mutator_fn { + unsigned m_col1; + unsigned m_col2; + public: + filter_distinct_fn(const table_base & t, unsigned col1, unsigned col2): + m_col1(col1), + m_col2(col2) { + } + + virtual void operator()(table_base& src) { + skip_table& s = get(src); + imdd_manager& m = s.get_imdd_manager(); + m.mk_filter_distinct(s.get_imdd(), s.m_imdd, m_col1, m_col2); + } + }; + + table_mutator_fn * skip_table_plugin::mk_filter_distinct_fn(const table_base & t, unsigned col1, unsigned col2) { + if (check_kind(t)) { + return alloc(filter_distinct_fn, t, col1, col2); + } + TRACE("dl", tout << "could not handle operation\n";); + return 0; + } + + // + // The default implementation uses an iterator + // if the condition is a comparison <, <=, then interval table native will be an advantage. + // + table_mutator_fn * skip_table_plugin::mk_filter_interpreted_fn(const table_base & t, app * condition) { + ast_manager& m = get_ast_manager(); + dl_decl_util& util = get_context().get_decl_util(); + uint64 value; + + if (m.is_eq(condition)) { + expr* x = condition->get_arg(0); + expr* y = condition->get_arg(1); + if (is_var(y)) { + std::swap(x,y); + } + if (is_var(x) && is_var(y)) { + unsigned cols[2] = { to_var(x)->get_idx(), to_var(y)->get_idx() }; + return mk_filter_identical_fn(t, 2, cols); + } + if (is_var(x) && util.is_numeral_ext(y, value)) { + return mk_filter_equal_fn(t, value, to_var(x)->get_idx()); + } + } + + if (m.is_not(condition) && is_app(condition->get_arg(0))) { + condition = to_app(condition->get_arg(0)); + if (m.is_eq(condition)) { + expr* x = condition->get_arg(0); + expr* y = condition->get_arg(1); + if (is_var(y)) { + std::swap(x,y); + } + if (is_var(x) && is_var(y)) { + return mk_filter_distinct_fn(t, to_var(x)->get_idx(), to_var(y)->get_idx()); + } + if (is_var(x) && util.is_numeral_ext(y, value)) { + return mk_filter_not_equal_fn(t, value, to_var(x)->get_idx()); + } + } + } + + TRACE("dl", tout << "could not handle operation\n";); + return 0; + } + + class skip_table_plugin::filter_by_negation_fn : public convenient_table_negation_filter_fn { + public: + filter_by_negation_fn( + const table_base & tgt, const table_base & neg, + unsigned joined_col_cnt, const unsigned * t_cols, const unsigned * negated_cols) + : convenient_table_negation_filter_fn(tgt, neg, joined_col_cnt, t_cols, negated_cols) { + + } + + // + // Compute + // { (x,y) | t(x,y) & ! exists z . negated_obj(x,z) } + // + // 1. Project z + // 2. Join with result. + // + + virtual void operator()(table_base & tgt0, const table_base & neg0) { + skip_table & tgt = get(tgt0); + const skip_table & neg = get(neg0); + unsigned_vector cols2(m_cols2); + unsigned_vector proj_cols; + table_base* t1 = 0; + if (!m_all_neg_bound) { + unsigned_vector proj_cols, remap; + table_signature sig2; + table_signature const& neg_sig = neg.get_signature(); + for (unsigned i = 0, j = 0; i < m_bound.size(); ++i) { + if (m_bound[i]) { + remap.push_back(j++); + sig2.push_back(neg_sig[i]); + } + else { + proj_cols.push_back(i); + remap.push_back(0); + } + } + for (unsigned i = 0; i < cols2.size(); ++i) { + cols2[i] = remap[cols2[i]]; + } + skip_table* t0 = skip_table_plugin::mk_project(neg, sig2, proj_cols); + t1 = t0->complement(); + t0->deallocate(); + proj_cols.reset(); + } + else { + t1 = neg.complement(); + } + for (unsigned i = 0; i < t1->get_signature().size(); ++i) { + proj_cols.push_back(tgt0.get_signature().size()+i); + } + skip_table* t2 = skip_table_plugin::mk_join_project(tgt0, *t1, tgt0.get_signature(), m_cols1, cols2, proj_cols); + t1->deallocate(); + tgt.update(*t2); + t2->deallocate(); + } + }; + + table_intersection_filter_fn * skip_table_plugin::mk_filter_by_negation_fn( + const table_base & t, + const table_base & negated_obj, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * negated_cols) { + + if (check_kind(t) && check_kind(negated_obj)) { + return alloc(filter_by_negation_fn, t, negated_obj, joined_col_cnt, t_cols, negated_cols); + } + TRACE("dl", tout << "could not handle operation\n";); + return 0; + } + + bool skip_table_plugin::can_handle_signature(table_signature const& sig) { + for (unsigned i = 0; i < sig.size(); ++i) { + if (sig[i] >= UINT_MAX) { + return false; + } + } + return true; + } + + // ------------------ + // skip_table + + + skip_table::skip_table(skip_table_plugin & p, const table_signature & sig): + table_base(p, sig), + m_imdd(p.get_imdd_manager().mk_empty(sig.size()), p.get_imdd_manager()) { + SASSERT(well_formed()); + } + + skip_table::skip_table(skip_table_plugin & p, const table_signature & sig, imdd* m): + table_base(p, sig), + m_imdd(m, p.get_imdd_manager()) { + SASSERT(well_formed()); + } + + skip_table::~skip_table() { + } + + + bool skip_table::well_formed() const { + table_signature const& sig = get_signature(); + return + get_plugin().can_handle_signature(sig) && + (get_imdd()->get_arity() == sig.size()); + } + + bool skip_table::empty() const { + return get_imdd()->empty(); + } + + void skip_table::update(imdd* n) { + m_imdd = n; + SASSERT(well_formed()); + } + + void skip_table::add_fact(const table_fact & f) { + imdd_manager& m = get_plugin().get_imdd_manager(); + unsigned const* fact = get_fact(f.c_ptr()); + m.add_fact(get_imdd(), m_imdd, f.size(), fact); + SASSERT(well_formed()); + } + + void skip_table::remove_fact(const table_element* f) { + imdd_manager& m = get_imdd_manager(); + unsigned const* fact = get_fact(f); + m.remove_facts(get_imdd(), m_imdd, get_signature().size(), fact, fact); + } + + bool skip_table::contains_fact(const table_fact & f) const { + imdd_manager& m = get_imdd_manager(); + unsigned const* fact = get_fact(f.c_ptr()); + return m.contains(get_imdd(), f.size(), fact); + } + + table_base * skip_table::clone() const { + return alloc(skip_table, get_plugin(), get_signature(), get_imdd()); + } + + table_base * skip_table::complement() const { + imdd_manager& m = get_plugin().get_imdd_manager(); + table_signature const& sig = get_signature(); + unsigned_vector mins, maxs; + for (unsigned i = 0; i < sig.size(); ++i) { + SASSERT(sig[i] < UINT_MAX); + mins.push_back(0); + maxs.push_back(static_cast(sig[i])); + } + imdd_ref cmpl(m); + m.mk_complement(get_imdd(), cmpl, sig.size(), mins.c_ptr(), maxs.c_ptr()); + return alloc(skip_table, get_plugin(), get_signature(), cmpl); + } + + unsigned const* skip_table::get_fact(table_element const* f) const { + table_signature const& sig = get_signature(); + const_cast(m_fact).reset(); + for (unsigned i = 0; i < sig.size(); ++i) { + const_cast(m_fact).push_back(static_cast(f[i])); + SASSERT(f[i] < UINT_MAX); + } + return m_fact.c_ptr(); + } + + + + class skip_table::our_iterator_core : public table_base::iterator_core { + skip_table const& m_table; + imdd_manager::iterator m_iterator; + + class our_row : public row_interface { + const our_iterator_core & m_parent; + public: + our_row(const our_iterator_core & parent) : row_interface(parent.m_table), m_parent(parent) {} + + virtual void get_fact(table_fact & result) const { + result.reset(); + unsigned arity = m_parent.m_iterator.get_arity(); + unsigned const* values = *(m_parent.m_iterator); + for (unsigned i = 0; i < arity; ++i) { + result.push_back(values[i]); + } + } + virtual table_element operator[](unsigned col) const { + SASSERT(col < m_parent.m_iterator.get_arity()); + unsigned const* values = *(m_parent.m_iterator); + return values[col]; + } + }; + + our_row m_row_obj; + + public: + struct b {}; + struct e {}; + + our_iterator_core(skip_table const& t, b): + m_table(t), + m_iterator(t.m_imdd.get_manager(), t.get_imdd()), + m_row_obj(*this) {} + + our_iterator_core(skip_table const& t, e): + m_table(t), + m_iterator(), + m_row_obj(*this) {} + + virtual bool is_finished() const { + return m_iterator == imdd_manager::iterator(); + } + + virtual row_interface & operator*() { + SASSERT(!is_finished()); + return m_row_obj; + } + + virtual void operator++() { + SASSERT(!is_finished()); + ++m_iterator; + } + }; + + + table_base::iterator skip_table::begin() const { + return mk_iterator(alloc(our_iterator_core, *this, our_iterator_core::b())); + } + + table_base::iterator skip_table::end() const { + return mk_iterator(alloc(our_iterator_core, *this, our_iterator_core::e())); + } + + unsigned skip_table::get_size_estimate_rows() const { + imdd_manager& m = get_plugin().get_imdd_manager(); + size_t sz = m.get_num_rows(get_imdd()); + unsigned sz0 = static_cast(sz); + SASSERT (sz == sz0 && "we need to use size_t or big-ints for row count"); + return sz0; + } + + unsigned skip_table::get_size_estimate_bytes() const { + imdd_manager& m = get_plugin().get_imdd_manager(); + return m.memory(get_imdd()); + } + +}; + +#endif diff --git a/lib/dl_skip_table.h b/lib/dl_skip_table.h new file mode 100644 index 000000000..6a9af9636 --- /dev/null +++ b/lib/dl_skip_table.h @@ -0,0 +1,161 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + dl_skip_table.h + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) + Leonardo de Moura (leonardo) 2010-10-14 + + +Revision History: + +--*/ + +#ifndef _DL_SKIP_TABLE_H_ +#define _DL_SKIP_TABLE_H_ + +#include "dl_base.h" +#include "imdd.h" + +namespace datalog { + class skip_table; + + class skip_table_plugin : public table_plugin { + friend class skip_table; + imdd_manager m_manager; + protected: + class join_fn; + class join_project_fn; + class union_fn; + class transformer_fn; + class rename_fn; + class project_fn; + class filter_equal_fn; + class filter_not_equal_fn; + class filter_identical_fn; + class filter_distinct_fn; + class filter_by_negation_fn; + + imdd_manager& get_imdd_manager() const { return const_cast(m_manager); } + + public: + typedef skip_table table; + + skip_table_plugin(relation_manager & manager) + : table_plugin(symbol("skip"), manager) {} + + virtual table_base * mk_empty(const table_signature & s); + + virtual table_join_fn * mk_join_fn(const table_base & t1, const table_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2); + virtual table_join_fn * mk_join_project_fn( + const table_base & t1, const table_base & t2, + unsigned joined_col_cnt, const unsigned * cols1, const unsigned * cols2, + unsigned removed_col_cnt, const unsigned * removed_cols); + virtual table_union_fn * mk_union_fn(const table_base & tgt, const table_base & src, + const table_base * delta); + virtual table_transformer_fn * mk_project_fn(const table_base & t, unsigned col_cnt, + const unsigned * removed_cols); + virtual table_transformer_fn * mk_rename_fn(const table_base & t, unsigned permutation_cycle_len, + const unsigned * permutation_cycle); + virtual table_mutator_fn * mk_filter_identical_fn(const table_base & t, unsigned col_cnt, + const unsigned * identical_cols); + virtual table_mutator_fn * mk_filter_equal_fn(const table_base & t, const table_element & value, + unsigned col); + virtual table_mutator_fn * mk_filter_interpreted_fn(const table_base & t, app * condition); + virtual table_intersection_filter_fn * mk_filter_by_negation_fn( + const table_base & t, + const table_base & negated_obj, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * negated_cols); + + virtual bool can_handle_signature(table_signature const& s); + + private: + static skip_table& get(table_base& r); + + static skip_table const & get(table_base const& r); + + static skip_table* mk_join(table_base const& t1, table_base const& t2, table_signature const& s, + unsigned_vector const& cols_t1, unsigned_vector const& cols_t2); + + static skip_table* mk_join_project(table_base const& t1, table_base const& t2, table_signature const& s, + unsigned_vector const& cols_t1, unsigned_vector const& cols_t2, unsigned_vector const& proj_cols); + + static skip_table* mk_project(table_base const& src, table_signature const& result_sig, + unsigned_vector const& cols); + + virtual table_mutator_fn * mk_filter_distinct_fn(const table_base & t, unsigned col1, unsigned col2); + + virtual table_mutator_fn * mk_filter_not_equal_fn(const table_base & t, const table_element & value, + unsigned col); + + }; + + class skip_table : public table_base { + friend class skip_table_plugin; + friend class skip_table_plugin::join_fn; + friend class skip_table_plugin::union_fn; + friend class skip_table_plugin::transformer_fn; + friend class skip_table_plugin::rename_fn; + friend class skip_table_plugin::project_fn; + friend class skip_table_plugin::filter_equal_fn; + friend class skip_table_plugin::filter_not_equal_fn; + friend class skip_table_plugin::filter_identical_fn; + friend class skip_table_plugin::filter_distinct_fn; + + class our_iterator_core; + + imdd_ref m_imdd; + unsigned_vector m_fact; + + imdd* get_imdd() const { return m_imdd.get(); } + + imdd_manager& get_imdd_manager() const { return get_plugin().get_imdd_manager(); } + + unsigned const* get_fact(table_element const* f) const; + + bool well_formed() const; + + void update(imdd* n); + + void update(skip_table& t) { update(t.m_imdd); } + + skip_table(skip_table_plugin & p, const table_signature & sig); + + skip_table(skip_table_plugin & p, const table_signature & sig, imdd*); + + skip_table(const skip_table & t); + + virtual ~skip_table(); + + public: + + skip_table_plugin & get_plugin() const { + return static_cast(table_base::get_plugin()); + } + + virtual bool empty() const; + virtual void add_fact(const table_fact & f); + virtual void remove_fact(const table_element * fact); + virtual bool contains_fact(const table_fact & f) const; + virtual table_base * complement() const; + virtual table_base * clone() const; + + virtual iterator begin() const; + virtual iterator end() const; + + virtual unsigned get_size_estimate_rows() const; + virtual unsigned get_size_estimate_bytes() const; + }; + + }; + + #endif /* _DL_SKIP_TABLE_H_ */ diff --git a/lib/dl_smt_relation.cpp b/lib/dl_smt_relation.cpp new file mode 100644 index 000000000..572894d05 --- /dev/null +++ b/lib/dl_smt_relation.cpp @@ -0,0 +1,760 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + dl_smt_relation.cpp + +Abstract: + + Relation based on SMT signature. + + +Author: + + Nikolaj Bjorner (nbjorner) 2010-10-10 + +Revision History: + +--*/ +#include +#include "debug.h" +#include "ast_pp.h" +#include "dl_context.h" +#include "dl_smt_relation.h" +#include "expr_abstract.h" +#include "smt_solver.h" +#include "th_rewriter.h" +#include "qe.h" +#include "datatype_decl_plugin.h" +#include "bv_decl_plugin.h" +#include "ast_ll_pp.h" +#include "expr_context_simplifier.h" +#include "has_free_vars.h" +#include "ast_smt_pp.h" + +namespace datalog { + + + smt_relation::smt_relation(smt_relation_plugin & p, const relation_signature & s, expr* r) + : relation_base(p, s), + m_rel(r, p.get_ast_manager()), + m_bound_vars(p.get_ast_manager()) + { + ast_manager& m = p.get_ast_manager(); + for (unsigned i = 0; m_bound_vars.size() < s.size(); ++i) { + unsigned j = s.size() - i - 1; + m_bound_vars.push_back(m.mk_const(symbol(j), s[j])); + } + SASSERT(is_well_formed()); + } + + smt_relation::~smt_relation() { + } + + bool smt_relation::is_well_formed() const { + ast_manager& m = get_manager(); + ptr_vector bound_sorts; + for (unsigned i = 0; i < m_bound_vars.size(); ++i) { + bound_sorts.push_back(m.get_sort(m_bound_vars[i])); + } + return is_well_formed_vars(bound_sorts, get_relation()); + } + + void smt_relation::instantiate(expr* r, expr_ref& result) const { + ast_manager& m = get_manager(); + var_subst subst(m); + ptr_vector bound_sorts; + for (unsigned i = 0; i < m_bound_vars.size(); ++i) { + bound_sorts.push_back(m.get_sort(m_bound_vars[i])); + } + TRACE("smt_relation", + tout << mk_ll_pp(r, m) << "\n"; + for (unsigned i = 0; i < bound_sorts.size(); ++i) { + tout << mk_pp(bound_sorts[i], m) << " "; + } + tout << "\n"; + ); + SASSERT(is_well_formed_vars(bound_sorts, r)); + + subst(r, m_bound_vars.size(), m_bound_vars.c_ptr(), result); + } + + void smt_relation::mk_abstract(expr* r, expr_ref& result) const { + ast_manager& m = get_manager(); + TRACE("smt_relation", tout << mk_ll_pp(r, m) << "\n";); + expr_abstract(m, 0, m_bound_vars.size(), m_bound_vars.c_ptr(), r, result); + TRACE("smt_relation", tout << mk_ll_pp(result, m) << "\n";); + ptr_vector bound_sorts; + for (unsigned i = 0; i < m_bound_vars.size(); ++i) { + bound_sorts.push_back(m.get_sort(m_bound_vars[i])); + } + SASSERT(is_well_formed_vars(bound_sorts, r)); + } + + void smt_relation::set_relation(expr* r) { + m_rel = r; + is_well_formed(); + } + + void smt_relation::add_relation(expr* s) { + ast_manager& m = get_manager(); + m_rel = m.mk_or(m_rel, s); + is_well_formed(); + } + + void smt_relation::filter_relation(expr* s) { + ast_manager& m = get_manager(); + m_rel = m.mk_and(m_rel, s); + is_well_formed(); + } + + expr* smt_relation::get_relation() const { + return m_rel.get(); + } + + void smt_relation::simplify(expr_ref& fml) const { + th_rewriter rw(get_manager()); + rw(fml); + } + + bool smt_relation::empty() const { + ast_manager& m = get_manager(); + expr* r = get_relation(); + if (m.is_true(r)) { + return false; + } + if (m.is_false(r)) { + return true; + } + IF_VERBOSE(10, verbose_stream() << "Checking emptiness...\n"; ); + + front_end_params& params = get_plugin().get_fparams(); + flet flet2(params.m_der, true); + smt::solver ctx(m, params); + expr_ref tmp(m); + instantiate(r, tmp); + ctx.assert_expr(tmp); + if (get_plugin().get_fparams().m_dump_goal_as_smt) { + static unsigned n = 0; + std::ostringstream strm; + strm << "File" << n << ".smt2"; + std::ofstream out(strm.str().c_str()); + ast_smt_pp pp(m); + pp.display_smt2(out, tmp); + ++n; + } + return l_false == ctx.check(); + } + + void smt_relation::add_fact(const relation_fact & f) { + SASSERT(f.size() == size()); + ast_manager& m = get_manager(); + expr_ref_vector eqs(m); + for (unsigned i = 0; i < f.size(); ++i) { + eqs.push_back(m.mk_eq(m.mk_var(i,m.get_sort(f[i])), f[i])); + } + expr_ref e1(m.mk_and(eqs.size(), eqs.c_ptr()), m); + add_relation(e1); + } + + + bool smt_relation::contains_fact(const relation_fact & f) const { + ast_manager& m = get_manager(); + expr_ref_vector eqs(m); + expr_ref cond(m); + for (unsigned i = 0; i < f.size(); ++i) { + eqs.push_back(m.mk_eq(m.mk_var(i,m.get_sort(f[i])), f[i])); + } + cond = m.mk_and(eqs.size(), eqs.c_ptr()); + return const_cast(this)->contains(cond); + } + + // + // facts in Rel iff + // facts => Rel iff + // facts & not Rel is unsat + // + bool smt_relation::contains(expr* facts) { + ast_manager& m = get_manager(); + expr_ref fml_free(m), fml_inst(m); + fml_free = m.mk_and(facts, m.mk_not(get_relation())); + instantiate(fml_free, fml_inst); + front_end_params& params = get_plugin().get_fparams(); + flet flet0(params.m_quant_elim, true); + flet flet1(params.m_nnf_cnf, false); + flet flet2(params.m_der, true); + smt::solver ctx(m, params); + ctx.assert_expr(fml_inst); + lbool result = ctx.check(); + TRACE("smt_relation", + display(tout); + tout << mk_pp(facts, m) << "\n"; + tout << ((result == l_false)?"true":"false") << "\n";); + return result == l_false; + } + + smt_relation * smt_relation::clone() const { + return alloc(smt_relation, get_plugin(), get_signature(), get_relation()); + } + + smt_relation * smt_relation::complement(func_decl* p) const { + ast_manager& m = get_manager(); + smt_relation* result = alloc(smt_relation, get_plugin(), get_signature(), m.mk_not(get_relation())); + TRACE("smt_relation", + display(tout<<"src:\n"); + result->display(tout<<"complement:\n");); + return result; + } + + void smt_relation::display(std::ostream & out) const { + if (is_finite_domain()) { + display_finite(out); + } + else { + out << mk_ll_pp(get_relation(), get_manager()) << "\n"; + } + } + + smt_relation_plugin & smt_relation::get_plugin() const { + return static_cast(relation_base::get_plugin()); + } + + bool smt_relation::is_finite_domain() const { + relation_signature const& sig = get_signature(); + for (unsigned i = 0; i < sig.size(); ++i) { + if (!get_plugin().is_finite_domain(sig[i])) { + return false; + } + } + return true; + } + + + void smt_relation::display_finite(std::ostream & out) const { + ast_manager& m = get_manager(); + front_end_params& params = get_plugin().get_fparams(); + expr* r = get_relation(); + expr_ref tmp(m); + expr_ref_vector values(m), eqs(m); + unsigned num_vars = m_bound_vars.size(); + values.resize(num_vars); + eqs.resize(num_vars); + instantiate(r, tmp); + flet flet4(params.m_model, true); + smt::solver ctx(m, params); + ctx.assert_expr(tmp); + + while (true) { + lbool is_sat = ctx.check(); + if (is_sat == l_false) { + break; + } + model_ref mod; + ctx.get_model(mod); + for (unsigned i = 0; i < num_vars; ++i) { + mod->eval(m_bound_vars[i], tmp, true); + values[i] = tmp; + eqs[i] = m.mk_eq(values[i].get(), m_bound_vars[i]); + + } + out << " ("; + for (unsigned i = 0; i < num_vars; ++i) { + unsigned j = num_vars - 1 - i; + out << mk_pp(values[j].get(), m); + if (i + 1 < num_vars) { + out << " "; + } + } + out << ")\n"; + tmp = m.mk_not(m.mk_and(num_vars, eqs.c_ptr())); + ctx.assert_expr(tmp); + } + } + + // ----------------------------------- + // + // smt_relation_plugin + // + // ----------------------------------- + + + smt_relation_plugin::smt_relation_plugin(relation_manager & m) + : relation_plugin(smt_relation_plugin::get_name(), m), m_counter(0) {} + + + relation_base * smt_relation_plugin::mk_empty(const relation_signature & s) { + return alloc(smt_relation, *this, s, get_ast_manager().mk_false()); + } + + + class smt_relation_plugin::join_fn : public convenient_relation_join_fn { + smt_relation_plugin& m_plugin; + expr_ref_vector m_conjs; + public: + join_fn(smt_relation_plugin& p, const relation_signature & o1_sig, const relation_signature & o2_sig, unsigned col_cnt, + const unsigned * cols1, const unsigned * cols2) + : convenient_relation_join_fn(o1_sig, o2_sig, col_cnt, cols1, cols2), + m_plugin(p), + m_conjs(p.get_ast_manager()) { + ast_manager& m = p.get_ast_manager(); + unsigned sz = m_cols1.size(); + for (unsigned i = 0; i < sz; ++i) { + unsigned col1 = m_cols1[i]; + unsigned col2 = m_cols2[i]; + var* v1 = m.mk_var(col1, o1_sig[col1]); + var* v2 = m.mk_var(col2 + o1_sig.size(), o2_sig[col2]); + m_conjs.push_back(m.mk_eq(v1, v2)); + } + } + + virtual relation_base * operator()(const relation_base & r1, const relation_base & r2) { + ast_manager& m = m_plugin.get_ast_manager(); + expr_ref e2(m), res(m); + shift_vars sh(m); + sh(get(r2).get_relation(), r1.get_signature().size(), e2); + m_conjs.push_back(get(r1).get_relation()); + m_conjs.push_back(e2); + res = m.mk_and(m_conjs.size(), m_conjs.c_ptr()); + m_conjs.pop_back(); + m_conjs.pop_back(); + smt_relation* result = alloc(smt_relation, m_plugin, get_result_signature(), res); + TRACE("smt_relation", + get(r1).display(tout << "src1:\n"); + get(r2).display(tout << "src2:\n"); + for (unsigned i = 0; i < m_conjs.size(); ++i) { + tout << m_cols1[i] << " = " << m_cols2[i] << " -- "; + tout << mk_pp(m_conjs[i].get(), m) << "\n"; + } + result->display(tout << "dst:\n"); + ); + return result; + } + }; + + relation_join_fn * smt_relation_plugin::mk_join_fn(const relation_base & r1, const relation_base & r2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2) { + if (!check_kind(r1) || !check_kind(r2)) { + return 0; + } + return alloc(join_fn, *this, r1.get_signature(), r2.get_signature(), col_cnt, cols1, cols2); + } + + class smt_relation_plugin::project_fn : public convenient_relation_project_fn { + smt_relation_plugin& m_plugin; + expr_ref_vector m_subst; + sort_ref_vector m_sorts; + svector m_names; + public: + project_fn(smt_relation_plugin& p, + const relation_signature & orig_sig, unsigned removed_col_cnt, const unsigned * removed_cols) + : convenient_relation_project_fn(orig_sig, removed_col_cnt, removed_cols), + m_plugin(p), + m_subst(p.get_ast_manager()), + m_sorts(p.get_ast_manager()) { + ast_manager& m = p.get_ast_manager(); + unsigned_vector const& cols = m_removed_cols; + unsigned num_cols = cols.size(); + + unsigned lo = 0, hi = num_cols; + for (unsigned i = 0, c = 0; i < orig_sig.size(); ++i) { + SASSERT(c <= num_cols); + if (c == num_cols) { + SASSERT(lo == num_cols); + m_subst.push_back(m.mk_var(hi, orig_sig[i])); + ++hi; + continue; + } + SASSERT(c < num_cols); + unsigned col = cols[c]; + SASSERT(i <= col); + if (i == col) { + m_names.push_back(symbol(p.fresh_name())); + m_sorts.push_back(orig_sig[col]); + m_subst.push_back(m.mk_var(lo, orig_sig[i])); + ++lo; + ++c; + continue; + } + m_subst.push_back(m.mk_var(hi, orig_sig[i])); + ++hi; + } + m_subst.reverse(); + m_sorts.reverse(); + m_names.reverse(); + } + + virtual relation_base * operator()(const relation_base & r) { + ast_manager& m = m_plugin.get_ast_manager(); + expr_ref tmp1(m), tmp2(m); + var_subst subst(m); + smt_relation* result = 0; + tmp1 = get(r).get_relation(); + subst(tmp1, m_subst.size(), m_subst.c_ptr(), tmp2); + tmp2 = m.mk_exists(m_sorts.size(), m_sorts.c_ptr(), m_names.c_ptr(), tmp2); + result = alloc(smt_relation, m_plugin, get_result_signature(), tmp2); + TRACE("smt_relation", + tout << "Signature: "; + for (unsigned i = 0; i < r.get_signature().size(); ++i) { + tout << mk_pp(r.get_signature()[i], m) << " "; + } + tout << "Remove: "; + for (unsigned i = 0; i < m_removed_cols.size(); ++i) { + tout << m_removed_cols[i] << " "; + } + tout << "\n"; + tout << "Subst: "; + for (unsigned i = 0; i < m_subst.size(); ++i) { + tout << mk_pp(m_subst[i].get(), m) << " "; + } + tout << "\n"; + get(r).display(tout); + tout << " --> \n"; + result->display(tout);); + return result; + } + }; + + relation_transformer_fn * smt_relation_plugin::mk_project_fn(const relation_base & r, + unsigned col_cnt, const unsigned * removed_cols) { + return alloc(project_fn, *this, r.get_signature(), col_cnt, removed_cols); + } + + + class smt_relation_plugin::rename_fn : public convenient_relation_rename_fn { + smt_relation_plugin& m_plugin; + expr_ref_vector m_subst; + public: + rename_fn(smt_relation_plugin& p, const relation_signature & orig_sig, unsigned cycle_len, const unsigned * cycle) + : convenient_relation_rename_fn(orig_sig, cycle_len, cycle), + m_plugin(p), + m_subst(p.get_ast_manager()) { + + ast_manager& m = p.get_ast_manager(); + for (unsigned i = 0; i < orig_sig.size(); ++i) { + m_subst.push_back(m.mk_var(i, orig_sig[i])); + } + unsigned col1, col2; + for (unsigned i = 0; i +1 < cycle_len; ++i) { + col1 = cycle[i]; + col2 = cycle[i+1]; + m_subst[col2] = m.mk_var(col1, orig_sig[col2]); + } + col1 = cycle[cycle_len-1]; + col2 = cycle[0]; + m_subst[col2] = m.mk_var(col1, orig_sig[col2]); + m_subst.reverse(); + } + + virtual relation_base * operator()(const relation_base & r) { + ast_manager& m = m_plugin.get_ast_manager(); + expr_ref res(m); + var_subst subst(m); + subst(get(r).get_relation(), m_subst.size(), m_subst.c_ptr(), res); + TRACE("smt_relation3", + tout << "cycle: "; + for (unsigned i = 0; i < m_cycle.size(); ++i) { + tout << m_cycle[i] << " "; + } + tout << "\n"; + tout << "old_sig: "; + for (unsigned i = 0; i < r.get_signature().size(); ++i) { + tout << mk_pp(r.get_signature()[i], m) << " "; + } + tout << "\n"; + tout << "new_sig: "; + for (unsigned i = 0; i < get_result_signature().size(); ++i) { + tout << mk_pp(get_result_signature()[i], m) << " "; + } + tout << "\n"; + tout << "subst: "; + for (unsigned i = 0; i < m_subst.size(); ++i) { + tout << mk_pp(m_subst[i].get(), m) << " "; + } + tout << "\n"; + get(r).display(tout << "src:\n"); + tout << "dst:\n" << mk_ll_pp(res, m) << "\n"; + ); + smt_relation* result = alloc(smt_relation, m_plugin, get_result_signature(), res); + + return result; + } + }; + + relation_transformer_fn * smt_relation_plugin::mk_rename_fn(const relation_base & r, + unsigned cycle_len, const unsigned * permutation_cycle) { + if(!check_kind(r)) { + return 0; + } + return alloc(rename_fn, *this, r.get_signature(), cycle_len, permutation_cycle); + } + + + class smt_relation_plugin::union_fn : public relation_union_fn { + smt_relation_plugin& m_plugin; + + public: + union_fn(smt_relation_plugin& p) : + m_plugin(p) { + } + + virtual void operator()(relation_base & r, const relation_base & src, relation_base * delta) { + ast_manager& m = m_plugin.get_ast_manager(); + expr* srcE = get(src).get_relation(); + TRACE("smt_relation", + tout << "dst:\n"; + get(r).display(tout); + tout << "src:\n"; + get(src).display(tout);); + + SASSERT(get(src).is_well_formed()); + SASSERT(get(r).is_well_formed()); + + if (delta) { + // + // delta(a) <- + // exists x . srcE(a, x) & not rE(a, y) + + + expr_ref rInst(m), srcInst(m), tmp(m), tmp1(m); + expr_ref notR(m), srcGround(m); + front_end_params& fparams = get(r).get_plugin().get_fparams(); + params_ref const& params = get(r).get_plugin().get_params(); + + get(r).instantiate(get(r).get_relation(), rInst); + get(src).instantiate(get(src).get_relation(), srcInst); + qe::expr_quant_elim_star1 qe(m, fparams); + + IF_VERBOSE(10, verbose_stream() << "Computing delta...\n"; ); + + if (params.get_bool(":smt-relation-ground-recursive", false)) { + // ensure R is ground. Simplify S using assumption not R + if (!is_ground(rInst)) { + proof_ref pr(m); + qe(rInst, tmp, pr); + rInst = tmp; + get(r).set_relation(rInst); + } + SASSERT(is_ground(rInst)); + notR = m.mk_not(rInst); + qe.reduce_with_assumption(notR, srcInst, tmp); + SASSERT(is_ground(tmp)); + } + else { + // Simplify not R usng assumption Exists x . S. + expr_ref srcGround(srcInst, m); + app_ref_vector srcVars(m); + qe::hoist_exists(srcGround, srcVars); + SASSERT(is_ground(srcGround)); + notR = m.mk_not(rInst); + qe.reduce_with_assumption(srcGround, notR, tmp1); + tmp = m.mk_and(srcInst, tmp1); + SASSERT(!has_free_vars(tmp)); + TRACE("smt_relation", + tout << "elim_exists result:\n" << mk_ll_pp(tmp, m) << "\n";); + } + + SASSERT(!has_free_vars(tmp)); + get(r).simplify(tmp); + + get(src).mk_abstract(tmp, tmp1); + TRACE("smt_relation", tout << "abstracted:\n"; tout << mk_ll_pp(tmp1, m) << "\n";); + get(*delta).set_relation(tmp1); + get(r).add_relation(tmp1); + } + else { + get(r).add_relation(srcE); + } + TRACE("smt_relation", get(r).display(tout << "dst':\n");); + } + }; + + relation_union_fn * smt_relation_plugin::mk_union_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta) { + if (!check_kind(tgt) || !check_kind(src) || (delta && !check_kind(*delta))) { + return 0; + } + return alloc(union_fn, *this); + } + + relation_union_fn * smt_relation_plugin::mk_widen_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta) { + if (!check_kind(tgt) || !check_kind(src) || (delta && !check_kind(*delta))) { + return 0; + } + return alloc(union_fn, *this); + } + + class smt_relation_plugin::filter_interpreted_fn : public relation_mutator_fn { + smt_relation_plugin& m_plugin; + app_ref m_condition; + public: + filter_interpreted_fn(smt_relation_plugin& p, app * condition) + : m_plugin(p), + m_condition(condition, p.get_ast_manager()) { + } + + virtual void operator()(relation_base & r) { + SASSERT(m_plugin.check_kind(r)); + get(r).filter_relation(m_condition); + TRACE("smt_relation", + tout << mk_pp(m_condition, m_plugin.get_ast_manager()) << "\n"; + get(r).display(tout); + ); + } + }; + + relation_mutator_fn * smt_relation_plugin::mk_filter_interpreted_fn(const relation_base & r, app * condition) { + if(!check_kind(r)) { + return 0; + } + return alloc(filter_interpreted_fn, *this, condition); + } + + relation_mutator_fn * smt_relation_plugin::mk_filter_equal_fn(const relation_base & r, + const relation_element & value, unsigned col) { + if(!check_kind(r)) { + return 0; + } + ast_manager& m = get_ast_manager(); + app_ref condition(m); + expr_ref var(m.mk_var(col, r.get_signature()[col]), m); + condition = m.mk_eq(var, value); + return mk_filter_interpreted_fn(r, condition); + } + + class smt_relation_plugin::filter_identical_fn : public relation_mutator_fn { + smt_relation_plugin& m_plugin; + expr_ref m_condition; + public: + filter_identical_fn(smt_relation_plugin& p, const relation_signature & sig, unsigned col_cnt, const unsigned * identical_cols) + : m_plugin(p), + m_condition(p.get_ast_manager()) { + if (col_cnt <= 1) { + return; + } + ast_manager& m = p.get_ast_manager(); + unsigned col = identical_cols[0]; + expr_ref v0(m.mk_var(col, sig[col]), m); + expr_ref_vector eqs(m); + for (unsigned i = 1; i < col_cnt; ++i) { + col = identical_cols[i]; + eqs.push_back(m.mk_eq(v0, m.mk_var(col, sig[col]))); + } + m_condition = m.mk_and(eqs.size(), eqs.c_ptr()); + } + + virtual void operator()(relation_base & r) { + get(r).filter_relation(m_condition); + TRACE("smt_relation", + tout << mk_pp(m_condition, m_plugin.get_ast_manager()) << "\n"; + get(r).display(tout); + ); + } + }; + + relation_mutator_fn * smt_relation_plugin::mk_filter_identical_fn(const relation_base & r, + unsigned col_cnt, const unsigned * identical_cols) { + if (!check_kind(r)) { + return 0; + } + return alloc(filter_identical_fn, *this, r.get_signature(), col_cnt, identical_cols); + } + + + class smt_relation_plugin::negation_filter_fn : public convenient_relation_negation_filter_fn { + smt_relation_plugin& m_plugin; + expr_ref_vector m_conjs; + public: + negation_filter_fn(smt_relation_plugin& p, + const relation_base & tgt, const relation_base & neg_t, + unsigned joined_col_cnt, const unsigned * t_cols, const unsigned * negated_cols) : + convenient_negation_filter_fn(tgt, neg_t, joined_col_cnt, t_cols, negated_cols), + m_plugin(p), + m_conjs(p.get_ast_manager()) { + ast_manager& m = p.get_ast_manager(); + unsigned sz = m_cols1.size(); + for (unsigned i = 0; i < sz; ++i) { + unsigned col1 = m_cols1[i]; + unsigned col2 = m_cols2[i]; + var* v1 = m.mk_var(col1, tgt.get_signature()[col1]); + var* v2 = m.mk_var(col2, neg_t.get_signature()[col2]); + m_conjs.push_back(m.mk_eq(v1, v2)); + } + } + + void operator()(relation_base & t, const relation_base & negated_obj) { + // TBD: fixme. + NOT_IMPLEMENTED_YET(); + ast_manager& m = m_plugin.get_ast_manager(); + expr_ref res(m), e2(m); + shift_vars sh(m); + sh(get(negated_obj).get_relation(), t.get_signature().size(), e2); + m_conjs.push_back(get(t).get_relation()); + m_conjs.push_back(m.mk_not(e2)); + res = m.mk_and(m_conjs.size(), m_conjs.c_ptr()); + m_conjs.pop_back(); + m_conjs.pop_back(); + // TBD: free variables in negation? + } + }; + + relation_intersection_filter_fn * smt_relation_plugin::mk_filter_by_negation_fn(const relation_base & t, + const relation_base & negated_obj, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * negated_cols) { + if (!check_kind(t) || !check_kind(negated_obj)) { + return 0; + } + return alloc(negation_filter_fn, *this, t, negated_obj, joined_col_cnt, t_cols, negated_cols); + } + + + smt_relation& smt_relation_plugin::get(relation_base& r) { return dynamic_cast(r); } + + smt_relation const & smt_relation_plugin::get(relation_base const& r) { return dynamic_cast(r); } + + bool smt_relation_plugin::can_handle_signature(relation_signature const& sig) { + // TBD: refine according to theories handled by quantifier elimination + return get_manager().is_non_explanation(sig); + } + + // TBD: when relations are finite domain, they also support table iterators. + + symbol smt_relation_plugin::fresh_name() { + return symbol(m_counter++); + } + + front_end_params& smt_relation_plugin::get_fparams() { + return const_cast(get_manager().get_context().get_fparams()); + } + + params_ref const& smt_relation_plugin::get_params() { + return get_manager().get_context().get_params(); + } + + bool smt_relation_plugin::is_finite_domain(sort *s) const { + ast_manager& m = get_ast_manager(); + if (m.is_bool(s)) { + return true; + } + bv_util bv(m); + if (bv.is_bv_sort(s)) { + return true; + } + datatype_util dt(m); + if (dt.is_datatype(s) && !dt.is_recursive(s)) { + ptr_vector const& constrs = *dt.get_datatype_constructors(s); + for (unsigned i = 0; i < constrs.size(); ++i) { + func_decl* f = constrs[i]; + for (unsigned j = 0; j < f->get_arity(); ++j) { + if (!is_finite_domain(f->get_domain(j))) { + return false; + } + } + } + return true; + } + return false; + } +}; + diff --git a/lib/dl_smt_relation.h b/lib/dl_smt_relation.h new file mode 100644 index 000000000..99de2ad83 --- /dev/null +++ b/lib/dl_smt_relation.h @@ -0,0 +1,141 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + dl_smt_relation.h + +Abstract: + + Relation signature represented by signatures that have quantifier elimination. + +Author: + + Nikolaj Bjorner (nbjorner) 2010-10-10 + +Revision History: + +--*/ +#ifndef _DL_SMT_RELATION_H_ +#define _DL_SMT_RELATION_H_ + +#include "dl_base.h" +#include "front_end_params.h" +#include "params.h" + +namespace datalog { + + class smt_relation; + + class smt_relation_plugin : public relation_plugin { + + unsigned m_counter; + + friend class smt_relation; + class join_fn; + class project_fn; + class rename_fn; + class union_fn; + class filter_identical_fn; + class filter_interpreted_fn; + class negation_filter_fn; + + public: + smt_relation_plugin(relation_manager & m); + + virtual bool can_handle_signature(const relation_signature & s); + + static symbol get_name() { return symbol("smt_relation"); } + + virtual relation_base * mk_empty(const relation_signature & s); + + virtual relation_join_fn * mk_join_fn(const relation_base & t1, const relation_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2); + virtual relation_transformer_fn * mk_project_fn(const relation_base & t, unsigned col_cnt, + const unsigned * removed_cols); + virtual relation_transformer_fn * mk_rename_fn(const relation_base & t, unsigned permutation_cycle_len, + const unsigned * permutation_cycle); + virtual relation_union_fn * mk_union_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta); + virtual relation_union_fn * mk_widen_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta); + virtual relation_mutator_fn * mk_filter_identical_fn(const relation_base & t, unsigned col_cnt, + const unsigned * identical_cols); + virtual relation_mutator_fn * mk_filter_equal_fn(const relation_base & t, const relation_element & value, + unsigned col); + virtual relation_mutator_fn * mk_filter_interpreted_fn(const relation_base & t, app * condition); + virtual relation_intersection_filter_fn * mk_filter_by_negation_fn(const relation_base & t, + const relation_base & negated_obj, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * negated_cols); + + symbol fresh_name(); + + front_end_params& get_fparams(); + + params_ref const& get_params(); + + bool is_finite_domain(sort* s) const; + + private: + static smt_relation& get(relation_base& r); + + static smt_relation const & get(relation_base const& r); + }; + + class smt_relation : public relation_base { + friend class smt_relation_plugin; + friend class smt_relation_plugin::join_fn; + friend class smt_relation_plugin::project_fn; + friend class smt_relation_plugin::rename_fn; + friend class smt_relation_plugin::union_fn; + friend class smt_relation_plugin::filter_identical_fn; + friend class smt_relation_plugin::filter_interpreted_fn; + friend class smt_relation_plugin::negation_filter_fn; + + expr_ref m_rel; // relation. + expr_ref_vector m_bound_vars; + + unsigned size() const { return get_signature().size(); } + + ast_manager& get_manager() const { return get_plugin().get_ast_manager(); } + + smt_relation(smt_relation_plugin & p, const relation_signature & s, expr* r); + + virtual ~smt_relation(); + + void instantiate(expr* e, expr_ref& result) const; + + void mk_abstract(expr* e, expr_ref& result) const; + + bool contains(expr* facts); + + bool is_finite_domain() const; + + bool is_well_formed() const; + + void display_finite(std::ostream & out) const; + + void simplify(expr_ref& e) const; + + public: + + virtual bool empty() const; + + virtual void add_fact(const relation_fact & f); + + virtual bool contains_fact(const relation_fact & f) const; + virtual smt_relation * clone() const; + virtual smt_relation * complement(func_decl*) const; + virtual void display(std::ostream & out) const; + virtual void to_formula(expr_ref& fml) const { fml = get_relation(); simplify(fml); } + + expr* get_relation() const; + void set_relation(expr* r); + void add_relation(expr* s); // add s to relation. + void filter_relation(expr* s); // restrict relation with s + smt_relation_plugin& get_plugin() const; + + }; +}; + +#endif diff --git a/lib/dl_sparse_table.cpp b/lib/dl_sparse_table.cpp new file mode 100644 index 000000000..1449b7d3d --- /dev/null +++ b/lib/dl_sparse_table.cpp @@ -0,0 +1,1249 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_sparse_table.cpp + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-09-24. + +Revision History: + +--*/ + +#include +#include"dl_context.h" +#include"dl_util.h" +#include"dl_sparse_table.h" + +namespace datalog { + + // ----------------------------------- + // + // entry_storage + // + // ----------------------------------- + + entry_storage::store_offset entry_storage::insert_or_get_reserve_content() { + SASSERT(has_reserve()); + store_offset entry_ofs = m_data_indexer.insert_if_not_there(m_reserve); + if(m_reserve==entry_ofs) { + //entry inserted, so reserve is no longer a reserve + m_reserve = NO_RESERVE; + } + return entry_ofs; + } + bool entry_storage::insert_reserve_content() { + SASSERT(has_reserve()); + store_offset entry_ofs = m_data_indexer.insert_if_not_there(m_reserve); + if(m_reserve==entry_ofs) { + //entry inserted, so reserve is no longer a reserve + m_reserve = NO_RESERVE; + return true; + } + return false; + } + + bool entry_storage::remove_reserve_content() { + SASSERT(has_reserve()); + store_offset entry_ofs; + if(!find_reserve_content(entry_ofs)) { + //the fact was not in the table + return false; + } + remove_offset(entry_ofs); + return true; + } + + void entry_storage::remove_offset(store_offset ofs) { + m_data_indexer.remove(ofs); + store_offset last_ofs = after_last_offset() - m_entry_size; + if(ofs!=last_ofs) { + SASSERT(ofs+m_entry_size<=last_ofs); + //we don't want any holes, so we put the last element at the place + //of the removed one + m_data_indexer.remove(last_ofs); + char * base = &m_data.get(0); + memcpy(base+ofs, base+last_ofs, m_entry_size); + m_data_indexer.insert(ofs); + } + if(has_reserve()) { + //we already have a reserve, so we need to shrink a little to keep having just one + resize_data(m_data_size-m_entry_size); + } + m_reserve=last_ofs; + } + + unsigned entry_storage::get_size_estimate_bytes() const { + unsigned sz = m_data.capacity(); + sz += m_data_indexer.capacity()*sizeof(storage_indexer::entry); + return sz; + } + + // ----------------------------------- + // + // sparse_table::column_layout + // + // ----------------------------------- + + unsigned get_domain_length(uint64 dom_size) { + SASSERT(dom_size>0); + + unsigned length = 0; + + unsigned dom_size_sm; + if(dom_size>UINT_MAX) { + dom_size_sm = static_cast(dom_size>>32); + length += 32; + if( (dom_size&UINT_MAX)!=0 && dom_size_sm!=UINT_MAX ) { + dom_size_sm++; + } + } + else { + dom_size_sm=static_cast(dom_size); + } + if(dom_size_sm==1) { + length += 1; //unary domains + } + else if(dom_size_sm>0x80000000u) { + length += 32; + } + else { + length += get_num_1bits(next_power_of_two(dom_size_sm)-1); //ceil(log2(dom_size)) + } + return length; + } + + sparse_table::column_layout::column_layout(const table_signature & sig) + : m_functional_col_cnt(sig.functional_columns()) { + SASSERT(sig.size()>0); + unsigned ofs = 0; + unsigned sig_sz = sig.size(); + unsigned first_functional = sig_sz-m_functional_col_cnt; + for(unsigned i=0; i0); + SASSERT(length<=64); + + if(size()>0 && (length>54 || i==first_functional)) { + //large domains must start byte-aligned, as well as functional columns + make_byte_aligned_end(size()-1); + ofs = back().next_ofs(); + } + + push_back(column_info(ofs, length)); + ofs+=length; + } + make_byte_aligned_end(size()-1); + SASSERT(back().next_ofs()%8==0);//the entries must be aligned to whole bytes + m_entry_size = back().next_ofs()/8; + if(m_functional_col_cnt) { + SASSERT((*this)[first_functional].m_offset%8==0); + m_functional_part_size = m_entry_size - (*this)[first_functional].m_offset/8; + } + else { + m_functional_part_size = 0; + } + } + + void sparse_table::column_layout::make_byte_aligned_end(unsigned col_index0) { + unsigned ofs = (*this)[col_index0].next_ofs(); + unsigned ofs_bit_part = ofs%8; + unsigned rounded_ofs = (ofs_bit_part==0) ? ofs : (ofs+8-ofs_bit_part); + + if(rounded_ofs!=ofs) { + SASSERT(rounded_ofs>ofs); + int diff = rounded_ofs-ofs; + unsigned col_idx = col_index0+1; + while(diff!=0) { + //we should always be able to fix the alignment by the time we reach zero + SASSERT(col_idx>0); + col_idx--; + column_info & ci = (*this)[col_idx]; + unsigned new_length = ci.m_length; + if(ci.m_length<64) { + unsigned swallowed = std::min(64-static_cast(ci.m_length), diff); + diff-=swallowed; + new_length+=swallowed; + } + unsigned new_ofs = ci.m_offset+diff; + ci = column_info(new_ofs, new_length); + } + } + + SASSERT(rounded_ofs%8==0); + SASSERT((*this)[col_index0].next_ofs()%8==0); + } + + // ----------------------------------- + // + // sparse_table + // + // ----------------------------------- + + class sparse_table::our_iterator_core : public iterator_core { + + class our_row : public row_interface { + const our_iterator_core & m_parent; + public: + our_row(const sparse_table & t, const our_iterator_core & parent) : + row_interface(t), + m_parent(parent) {} + + virtual table_element operator[](unsigned col) const { + return m_parent.m_layout.get(m_parent.m_ptr, col); + } + + }; + + const char * m_end; + const char * m_ptr; + unsigned m_fact_size; + our_row m_row_obj; + const column_layout & m_layout; + + public: + our_iterator_core(const sparse_table & t, bool finished) : + m_end(t.m_data.after_last()), + m_ptr(finished ? m_end : t.m_data.begin()), + m_fact_size(t.m_fact_size), + m_row_obj(t, *this), + m_layout(t.m_column_layout) {} + + virtual bool is_finished() const { + return m_ptr==m_end; + } + + virtual row_interface & operator*() { + SASSERT(!is_finished()); + return m_row_obj; + } + virtual void operator++() { + SASSERT(!is_finished()); + m_ptr+=m_fact_size; + } + }; + + class sparse_table::key_indexer { + protected: + unsigned_vector m_key_cols; + public: + typedef const store_offset * offset_iterator; + + /** + Iterators returned by \c begin() and \c end() are valid only as long as the \c query_result + object that returned them exists. + */ + struct query_result { + private: + bool m_singleton; + union { + store_offset m_single_result; + struct { + offset_iterator begin; + offset_iterator end; + } m_many; + }; + public: + /** + \brief Empty result. + */ + query_result() : m_singleton(false) { + m_many.begin = 0; + m_many.end = 0; + } + query_result(offset_iterator begin, offset_iterator end) : m_singleton(false) { + m_many.begin = begin; + m_many.end = end; + } + query_result(store_offset single_result) : m_singleton(true), m_single_result(single_result) {} + + offset_iterator begin() const { return m_singleton ? &m_single_result : m_many.begin; } + offset_iterator end() const { return m_singleton ? (&m_single_result+1) : m_many.end; } + bool empty() const { return begin()==end(); } + }; + + key_indexer(unsigned key_len, const unsigned * key_cols) + : m_key_cols(key_len, key_cols) {} + + virtual ~key_indexer() {} + + virtual void update(const sparse_table & t) {} + + virtual query_result get_matching_offsets(const key_value & key) const = 0; + }; + + + class sparse_table::general_key_indexer : public key_indexer { + typedef svector offset_vector; + typedef u_map index_map; + + index_map m_map; + mutable entry_storage m_keys; + store_offset m_first_nonindexed; + + + void key_to_reserve(const key_value & key) const { + m_keys.ensure_reserve(); + m_keys.write_into_reserve(reinterpret_cast(key.c_ptr())); + } + + offset_vector & get_matching_offset_vector(const key_value & key) { + key_to_reserve(key); + store_offset ofs = m_keys.insert_or_get_reserve_content(); + index_map::entry * e = m_map.find_core(ofs); + if(!e) { + TRACE("dl_table_relation", tout << "inserting\n";); + e = m_map.insert_if_not_there2(ofs, offset_vector()); + } + return e->get_data().m_value; + } + public: + general_key_indexer(unsigned key_len, const unsigned * key_cols) + : key_indexer(key_len, key_cols), + m_keys(key_len*sizeof(table_element)), + m_first_nonindexed(0) {} + + virtual void update(const sparse_table & t) { + if(m_first_nonindexed==t.m_data.after_last_offset()) { + return; + } + SASSERT(m_first_nonindexedinsert(ofs); + } + + m_first_nonindexed = t.m_data.after_last_offset(); + } + + virtual query_result get_matching_offsets(const key_value & key) const { + key_to_reserve(key); + store_offset ofs; + if(!m_keys.find_reserve_content(ofs)) { + return query_result(); + } + index_map::entry * e = m_map.find_core(ofs); + if(!e) { + return query_result(); + } + const offset_vector & res = e->get_data().m_value; + return query_result(res.begin(), res.end()); + } + }; + + /** + When doing lookup using this index, the content of the reserve in sparse_table::m_data changes. + */ + class sparse_table::full_signature_key_indexer : public key_indexer { + const sparse_table & m_table; + + /** + Permutation of key columns to make it into table facts. If empty, no permutation is necessary. + */ + unsigned_vector m_permutation; + mutable table_fact m_key_fact; + public: + + static bool can_handle(unsigned key_len, const unsigned * key_cols, const sparse_table & t) { + unsigned non_func_cols = t.get_signature().first_functional(); + if(key_len!=non_func_cols) { + return false; + } + counter ctr; + ctr.count(key_len, key_cols); + if(ctr.get_max_counter_value()!=1 || ctr.get_max_positive()!=non_func_cols-1) { + return false; + } + SASSERT(ctr.get_positive_count()==non_func_cols); + return true; + } + + full_signature_key_indexer(unsigned key_len, const unsigned * key_cols, const sparse_table & t) + : key_indexer(key_len, key_cols), + m_table(t) { + SASSERT(can_handle(key_len, key_cols, t)); + + m_permutation.resize(key_len); + for(unsigned i=0; i(m_table); + t.write_into_reserve(m_key_fact.c_ptr()); + + store_offset res; + if(!t.m_data.find_reserve_content(res)) { + return query_result(); + } + return query_result(res); + } + }; + + sparse_table::sparse_table(sparse_table_plugin & p, const table_signature & sig, unsigned init_capacity) + : table_base(p, sig), + m_column_layout(sig), + m_fact_size(m_column_layout.m_entry_size), + m_data(m_fact_size, m_column_layout.m_functional_part_size, init_capacity) {} + + sparse_table::sparse_table(const sparse_table & t) + : table_base(t.get_plugin(), t.get_signature()), + m_column_layout(t.m_column_layout), + m_fact_size(t.m_fact_size), + m_data(t.m_data) {} + + table_base * sparse_table::clone() const { + return get_plugin().mk_clone(*this); + } + + sparse_table::~sparse_table() { + reset_indexes(); + } + + void sparse_table::reset() { + reset_indexes(); + m_data.reset(); + } + + table_base::iterator sparse_table::begin() const { + return mk_iterator(alloc(our_iterator_core, *this, false)); + } + + table_base::iterator sparse_table::end() const { + return mk_iterator(alloc(our_iterator_core, *this, true)); + } + + sparse_table::key_indexer& sparse_table::get_key_indexer(unsigned key_len, + const unsigned * key_cols) const { +#if Z3DEBUG + //We allow indexes only on non-functional columns because we want to be able to modify them + //without having to worry about updating indexes. + //Maybe we might keep a list of indexes that contain functional columns and on an update reset + //only those. + SASSERT(key_len==0 || + counter().count(key_len, key_cols).get_max_positive()get_data().m_value) { + if(full_signature_key_indexer::can_handle(key_len, key_cols, *this)) { + key_map_entry->get_data().m_value = alloc(full_signature_key_indexer, key_len, key_cols, *this); + } + else { + key_map_entry->get_data().m_value = alloc(general_key_indexer, key_len, key_cols); + } + } + key_indexer & indexer = *key_map_entry->get_data().m_value; + indexer.update(*this); + return indexer; + } + + void sparse_table::reset_indexes() { + key_index_map::iterator kmit = m_key_indexes.begin(); + key_index_map::iterator kmend = m_key_indexes.end(); + for(; kmit!=kmend; ++kmit) { + dealloc((*kmit).m_value); + } + m_key_indexes.reset(); + } + + void sparse_table::write_into_reserve(const table_element* f) { + TRACE("dl_table_relation", tout << "\n";); + m_data.ensure_reserve(); + char * reserve = m_data.get_reserve_ptr(); + unsigned col_cnt = m_column_layout.size(); + for(unsigned i=0; i(*this); + t.write_into_reserve(f.c_ptr()); + unsigned func_col_cnt = get_signature().functional_columns(); + if(func_col_cnt==0) { + return t.m_data.reserve_content_already_present(); + } + else { + store_offset ofs; + if(!t.m_data.find_reserve_content(ofs)) { + return false; + } + unsigned sz = get_signature().size(); + for(unsigned i=func_col_cnt; i(*this); + t.write_into_reserve(f.c_ptr()); + store_offset ofs; + if(!t.m_data.find_reserve_content(ofs)) { + return false; + } + unsigned sz = sig.size(); + for(unsigned i=sig.first_functional(); ipre_projection_idx); + dest_layout.set(dest, dest_idx++, src_layout.get(src, i)); + } + } + + void sparse_table::concatenate_rows(const column_layout & layout1, const column_layout & layout2, + const column_layout & layout_res, const char * ptr1, const char * ptr2, char * res, + const unsigned * removed_cols) { + unsigned t1non_func = layout1.size()-layout1.m_functional_col_cnt; + unsigned t2non_func = layout2.size()-layout2.m_functional_col_cnt; + unsigned t1cols = layout1.size(); + unsigned t2cols = layout2.size(); + unsigned orig_i = 0; + unsigned res_i = 0; + const unsigned * next_removed = removed_cols; + copy_columns(layout1, layout_res, 0, t1non_func, ptr1, res, res_i, orig_i, next_removed); + copy_columns(layout2, layout_res, 0, t2non_func, ptr2, res, res_i, orig_i, next_removed); + copy_columns(layout1, layout_res, t1non_func, t1cols, ptr1, res, res_i, orig_i, next_removed); + copy_columns(layout2, layout_res, t2non_func, t2cols, ptr2, res, res_i, orig_i, next_removed); + } + + void sparse_table::garbage_collect() { + if (memory::above_high_watermark()) { + get_plugin().garbage_collect(); + } + if (memory::above_high_watermark()) { + IF_VERBOSE(1, verbose_stream() << "Ran out of memory while filling table of size: " << get_size_estimate_rows() << " rows " << get_size_estimate_bytes() << " bytes\n";); + throw out_of_memory_error(); + } + } + + void sparse_table::self_agnostic_join_project(const sparse_table & t1, const sparse_table & t2, + unsigned joined_col_cnt, const unsigned * t1_joined_cols, const unsigned * t2_joined_cols, + const unsigned * removed_cols, bool tables_swapped, sparse_table & result) { + + unsigned t1_entry_size = t1.m_fact_size; + unsigned t2_entry_size = t2.m_fact_size; + + unsigned t1idx = 0; + unsigned t1end = t1.m_data.after_last_offset(); + + TRACE("dl_table_relation", + tout << "joined_col_cnt: " << joined_col_cnt << "\n"; + tout << "t1_entry_size: " << t1_entry_size << "\n"; + tout << "t2_entry_size: " << t2_entry_size << "\n"; + t1.display(tout); + t2.display(tout); + tout << (&t1) << " " << (&t2) << " " << (&result) << "\n"; + ); + + if(joined_col_cnt==0) { + unsigned t2idx = 0; + unsigned t2end = t2.m_data.after_last_offset(); + + for(; t1idx!=t1end; t1idx+=t1_entry_size) { + for(t2idx = 0; t2idx != t2end; t2idx += t2_entry_size) { + result.m_data.ensure_reserve(); + result.garbage_collect(); + char * res_reserve = result.m_data.get_reserve_ptr(); + char const* t1ptr = t1.get_at_offset(t1idx); + char const* t2ptr = t2.get_at_offset(t2idx); + if(tables_swapped) { + concatenate_rows(t2.m_column_layout, t1.m_column_layout, result.m_column_layout, + t2ptr, t1ptr, res_reserve, removed_cols); + } else { + concatenate_rows(t1.m_column_layout, t2.m_column_layout, result.m_column_layout, + t1ptr, t2ptr, res_reserve, removed_cols); + } + result.add_reserve_content(); + } + } + return; + } + + key_value t1_key; + t1_key.resize(joined_col_cnt); + key_indexer& t2_indexer = t2.get_key_indexer(joined_col_cnt, t2_joined_cols); + + bool key_modified = true; + key_indexer::query_result t2_offsets; + + for(; t1idx != t1end; t1idx += t1_entry_size) { + for(unsigned i = 0; i < joined_col_cnt; i++) { + table_element val = t1.m_column_layout.get(t1.get_at_offset(t1idx), t1_joined_cols[i]); + TRACE("dl_table_relation", tout << "val: " << val << " " << t1idx << " " << t1_joined_cols[i] << "\n";); + if(t1_key[i] != val) { + t1_key[i] = val; + key_modified = true; + } + } + if(key_modified) { + t2_offsets = t2_indexer.get_matching_offsets(t1_key); + key_modified = false; + } + + if(t2_offsets.empty()) { + continue; + } + + key_indexer::offset_iterator t2ofs_it = t2_offsets.begin(); + key_indexer::offset_iterator t2ofs_end = t2_offsets.end(); + for(; t2ofs_it != t2ofs_end; ++t2ofs_it) { + store_offset t2ofs = *t2ofs_it; + result.m_data.ensure_reserve(); + result.garbage_collect(); + char * res_reserve = result.m_data.get_reserve_ptr(); + char const * t1ptr = t1.get_at_offset(t1idx); + char const * t2ptr = t2.get_at_offset(t2ofs); + if(tables_swapped) { + concatenate_rows(t2.m_column_layout, t1.m_column_layout, result.m_column_layout, + t2ptr, t1ptr, res_reserve, removed_cols); + } else { + concatenate_rows(t1.m_column_layout, t2.m_column_layout, result.m_column_layout, + t1ptr, t2ptr, res_reserve, removed_cols); + } + result.add_reserve_content(); + } + } + } + + + // ----------------------------------- + // + // sparse_table_plugin + // + // ----------------------------------- + + sparse_table_plugin::sparse_table_plugin(relation_manager & manager) + : table_plugin(symbol("sparse"), manager) {} + + sparse_table_plugin::~sparse_table_plugin() { + reset(); + } + + void sparse_table_plugin::reset() { + table_pool::iterator it = m_pool.begin(); + table_pool::iterator end = m_pool.end(); + for(; it!=end; ++it) { + sp_table_vector * vect = it->m_value; + sp_table_vector::iterator it = vect->begin(); + sp_table_vector::iterator end = vect->end(); + for(; it!=end; ++it) { + (*it)->destroy(); //calling deallocate() would only put the table back into the pool + } + dealloc(vect); + } + m_pool.reset(); + } + + void sparse_table_plugin::garbage_collect() { + IF_VERBOSE(2, verbose_stream() << "garbage collecting "<< memory::get_allocation_size() << " bytes down to ";); + reset(); + IF_VERBOSE(2, verbose_stream() << memory::get_allocation_size() << " bytes\n";); + } + + void sparse_table_plugin::recycle(sparse_table * t) { + const table_signature & sig = t->get_signature(); + t->reset(); + + table_pool::entry * e = m_pool.insert_if_not_there2(sig, 0); + sp_table_vector * & vect = e->get_data().m_value; + if(vect==0) { + vect = alloc(sp_table_vector); + } + IF_VERBOSE(12, verbose_stream() << "Recycle: " << t->get_size_estimate_bytes() << "\n";); + + vect->push_back(t); + } + + table_base * sparse_table_plugin::mk_empty(const table_signature & s) { + SASSERT(can_handle_signature(s)); + + sp_table_vector * vect; + if(!m_pool.find(s, vect) || vect->empty()) { + return alloc(sparse_table, *this, s); + } + sparse_table * res = vect->back(); + vect->pop_back(); + return res; + } + + sparse_table * sparse_table_plugin::mk_clone(const sparse_table & t) { + sparse_table * res = static_cast(mk_empty(t.get_signature())); + res->m_data = t.m_data; + return res; + } + + + bool sparse_table_plugin::join_involves_functional(const table_signature & s1, const table_signature & s2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2) { + if(col_cnt==0) { + return false; + } + return counter().count(col_cnt, cols1).get_max_positive()>=s1.first_functional() + || counter().count(col_cnt, cols2).get_max_positive()>=s2.first_functional(); + } + + + class sparse_table_plugin::join_project_fn : public convenient_table_join_project_fn { + public: + join_project_fn(const table_signature & t1_sig, const table_signature & t2_sig, unsigned col_cnt, + const unsigned * cols1, const unsigned * cols2, unsigned removed_col_cnt, + const unsigned * removed_cols) + : convenient_table_join_project_fn(t1_sig, t2_sig, col_cnt, cols1, cols2, + removed_col_cnt, removed_cols) { + m_removed_cols.push_back(UINT_MAX); + } + + virtual table_base * operator()(const table_base & tb1, const table_base & tb2) { + + const sparse_table & t1 = static_cast(tb1); + const sparse_table & t2 = static_cast(tb2); + + sparse_table_plugin & plugin = t1.get_plugin(); + + sparse_table * res = static_cast(plugin.mk_empty(get_result_signature())); + + //If we join with some intersection, want to iterate over the smaller table and + //do indexing into the bigger one. If we simply do a product, we want the bigger + //one to be at the outer iteration (then the small one will hopefully fit into + //the cache) + if( (t1.row_count() > t2.row_count()) == (!m_cols1.empty()) ) { + sparse_table::self_agnostic_join_project(t2, t1, m_cols1.size(), m_cols2.c_ptr(), + m_cols1.c_ptr(), m_removed_cols.c_ptr(), true, *res); + } + else { + sparse_table::self_agnostic_join_project(t1, t2, m_cols1.size(), m_cols1.c_ptr(), + m_cols2.c_ptr(), m_removed_cols.c_ptr(), false, *res); + } + TRACE("dl_table_relation", tb1.display(tout); tb2.display(tout); res->display(tout); ); + return res; + } + }; + + table_join_fn * sparse_table_plugin::mk_join_fn(const table_base & t1, const table_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2) { + const table_signature & sig1 = t1.get_signature(); + const table_signature & sig2 = t2.get_signature(); + if(t1.get_kind()!=get_kind() || t2.get_kind()!=get_kind() + || join_involves_functional(sig1, sig2, col_cnt, cols1, cols2)) { + //We also don't allow indexes on functional columns (and they are needed for joins) + return 0; + } + return mk_join_project_fn(t1, t2, col_cnt, cols1, cols2, 0, static_cast(0)); + } + + table_join_fn * sparse_table_plugin::mk_join_project_fn(const table_base & t1, const table_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2, unsigned removed_col_cnt, + const unsigned * removed_cols) { + const table_signature & sig1 = t1.get_signature(); + const table_signature & sig2 = t2.get_signature(); + if(t1.get_kind()!=get_kind() || t2.get_kind()!=get_kind() + || removed_col_cnt==t1.get_signature().size()+t2.get_signature().size() + || join_involves_functional(sig1, sig2, col_cnt, cols1, cols2)) { + //We don't allow sparse tables with zero signatures (and project on all columns leads to such) + //We also don't allow indexes on functional columns. + return 0; + } + return alloc(join_project_fn, t1.get_signature(), t2.get_signature(), col_cnt, cols1, cols2, + removed_col_cnt, removed_cols); + } + + class sparse_table_plugin::union_fn : public table_union_fn { + public: + virtual void operator()(table_base & tgt0, const table_base & src0, table_base * delta0) { + + sparse_table & tgt = static_cast(tgt0); + const sparse_table & src = static_cast(src0); + sparse_table * delta = static_cast(delta0); + + unsigned fact_size = tgt.m_fact_size; + const char* ptr = src.m_data.begin(); + const char* after_last=src.m_data.after_last(); + for(; ptradd_fact(ptr); + } + } + } + }; + + table_union_fn * sparse_table_plugin::mk_union_fn(const table_base & tgt, const table_base & src, + const table_base * delta) { + if(tgt.get_kind()!=get_kind() || src.get_kind()!=get_kind() + || (delta && delta->get_kind()!=get_kind()) + || tgt.get_signature()!=src.get_signature() + || (delta && delta->get_signature()!=tgt.get_signature())) { + return 0; + } + return alloc(union_fn); + } + + class sparse_table_plugin::project_fn : public convenient_table_project_fn { + const unsigned m_inp_col_cnt; + const unsigned m_removed_col_cnt; + const unsigned m_result_col_cnt; + public: + project_fn(const table_signature & orig_sig, unsigned removed_col_cnt, const unsigned * removed_cols) + : convenient_table_project_fn(orig_sig, removed_col_cnt, removed_cols), + m_inp_col_cnt(orig_sig.size()), + m_removed_col_cnt(removed_col_cnt), + m_result_col_cnt(orig_sig.size()-removed_col_cnt) { + SASSERT(removed_col_cnt>0); + } + + virtual void transform_row(const char * src, char * tgt, + const sparse_table::column_layout & src_layout, + const sparse_table::column_layout & tgt_layout) { + unsigned r_idx=0; + unsigned tgt_i=0; + for(unsigned i=0; i(tb); + + unsigned t_fact_size = t.m_fact_size; + + sparse_table_plugin & plugin = t.get_plugin(); + sparse_table * res = static_cast(plugin.mk_empty(get_result_signature())); + + const sparse_table::column_layout & src_layout = t.m_column_layout; + const sparse_table::column_layout & tgt_layout = res->m_column_layout; + + const char* t_ptr = t.m_data.begin(); + const char* t_end = t.m_data.after_last(); + for(; t_ptr!=t_end; t_ptr+=t_fact_size) { + SASSERT(t_ptrm_data.ensure_reserve(); + char * res_ptr = res->m_data.get_reserve_ptr(); + transform_row(t_ptr, res_ptr, src_layout, tgt_layout); + res->m_data.insert_reserve_content(); + } + return res; + } + }; + + table_transformer_fn * sparse_table_plugin::mk_project_fn(const table_base & t, unsigned col_cnt, + const unsigned * removed_cols) { + if(col_cnt==t.get_signature().size()) { + return 0; + } + return alloc(project_fn, t.get_signature(), col_cnt, removed_cols); + } + + + class sparse_table_plugin::select_equal_and_project_fn : public convenient_table_transformer_fn { + const unsigned m_col; + sparse_table::key_value m_key; + public: + select_equal_and_project_fn(const table_signature & orig_sig, table_element val, unsigned col) + : m_col(col) { + table_signature::from_project(orig_sig, 1, &col, get_result_signature()); + m_key.push_back(val); + } + + virtual table_base * operator()(const table_base & tb) { + const sparse_table & t = static_cast(tb); + + sparse_table_plugin & plugin = t.get_plugin(); + sparse_table * res = static_cast(plugin.mk_empty(get_result_signature())); + + const sparse_table::column_layout & t_layout = t.m_column_layout; + const sparse_table::column_layout & res_layout = res->m_column_layout; + unsigned t_cols = t_layout.size(); + + sparse_table::key_indexer & indexer = t.get_key_indexer(1, &m_col); + sparse_table::key_indexer::query_result t_offsets = indexer.get_matching_offsets(m_key); + if(t_offsets.empty()) { + //no matches + return res; + } + sparse_table::key_indexer::offset_iterator ofs_it=t_offsets.begin(); + sparse_table::key_indexer::offset_iterator ofs_end=t_offsets.end(); + + for(; ofs_it!=ofs_end; ++ofs_it) { + sparse_table::store_offset t_ofs = *ofs_it; + const char * t_ptr = t.get_at_offset(t_ofs); + + res->m_data.ensure_reserve(); + char * res_reserve = res->m_data.get_reserve_ptr(); + + unsigned res_i = 0; + for(unsigned i=0; iadd_reserve_content(); + } + return res; + } + }; + + table_transformer_fn * sparse_table_plugin::mk_select_equal_and_project_fn(const table_base & t, + const table_element & value, unsigned col) { + if(t.get_kind()!=get_kind() || t.get_signature().size()==1 || col>=t.get_signature().first_functional()) { + //We don't allow sparse tables with zero signatures (and project on a single + //column table produces one). + //We also don't allow indexes on functional columns. And our implementation of + //select_equal_and_project uses index on \c col. + return 0; + } + return alloc(select_equal_and_project_fn, t.get_signature(), value, col); + } + + + class sparse_table_plugin::rename_fn : public convenient_table_rename_fn { + const unsigned m_cycle_len; + const unsigned m_col_cnt; + unsigned_vector m_out_of_cycle; + public: + rename_fn(const table_signature & orig_sig, unsigned permutation_cycle_len, const unsigned * permutation_cycle) + : convenient_table_rename_fn(orig_sig, permutation_cycle_len, permutation_cycle), + m_cycle_len(permutation_cycle_len), m_col_cnt(orig_sig.size()) { + SASSERT(permutation_cycle_len>=2); + idx_set cycle_cols; + for(unsigned i=0; i(tb); + + unsigned t_fact_size = t.m_fact_size; + + sparse_table_plugin & plugin = t.get_plugin(); + sparse_table * res = static_cast(plugin.mk_empty(get_result_signature())); + + unsigned res_fact_size = res->m_fact_size; + unsigned res_data_size = res_fact_size*t.row_count(); + + res->m_data.resize_data(res_data_size); + + //here we can separate data creatin and insertion into hashmap, since we know + //that no row will become duplicit + + //create the data + const char* t_ptr = t.m_data.begin(); + char* res_ptr = res->m_data.begin(); + char* res_end = res_ptr+res_data_size; + for(; res_ptr!=res_end; t_ptr+=t_fact_size, res_ptr+=res_fact_size) { + transform_row(t_ptr, res_ptr, t.m_column_layout, res->m_column_layout); + } + + //and insert them into the hash-map + for(unsigned i=0; i!=res_data_size; i+=res_fact_size) { + TRUSTME(res->m_data.insert_offset(i)); + } + + return res; + } + }; + + table_transformer_fn * sparse_table_plugin::mk_rename_fn(const table_base & t, unsigned permutation_cycle_len, + const unsigned * permutation_cycle) { + if(t.get_kind()!=get_kind()) { + return 0; + } + return alloc(rename_fn, t.get_signature(), permutation_cycle_len, permutation_cycle); + } + + class sparse_table_plugin::negation_filter_fn : public convenient_table_negation_filter_fn { + typedef sparse_table::store_offset store_offset; + typedef sparse_table::key_value key_value; + typedef sparse_table::key_indexer key_indexer; + + bool m_joining_neg_non_functional; + + /** + Used by \c collect_intersection_offsets function. + If tgt_is_first is false, contains the same items as \c res. + */ + idx_set m_intersection_content; + + public: + negation_filter_fn(const table_base & tgt, const table_base & neg, + unsigned joined_col_cnt, const unsigned * t_cols, const unsigned * negated_cols) + : convenient_table_negation_filter_fn(tgt, neg, joined_col_cnt, t_cols, negated_cols) { + unsigned neg_fisrt_func = neg.get_signature().first_functional(); + counter ctr; + ctr.count(m_cols2); + m_joining_neg_non_functional = ctr.get_max_counter_value()==1 + && ctr.get_positive_count()==neg_fisrt_func + && (neg_fisrt_func==0 || ctr.get_max_positive()==neg_fisrt_func-1); + } + + /** + Collect offsets of rows in \c t1 or \c t2 (depends on whether \c tgt_is_first is true or false) + that have a match in the other table into \c res. Offsets in \c res are in ascending order. + */ + void collect_intersection_offsets(const sparse_table & t1, const sparse_table & t2, + bool tgt_is_first, svector & res) { + SASSERT(res.empty()); + + if(!tgt_is_first) { + m_intersection_content.reset(); + } + + unsigned joined_col_cnt = m_cols1.size(); + unsigned t1_entry_size = t1.m_data.entry_size(); + + const unsigned * cols1 = tgt_is_first ? m_cols1.c_ptr() : m_cols2.c_ptr(); + const unsigned * cols2 = tgt_is_first ? m_cols2.c_ptr() : m_cols1.c_ptr(); + + key_value t1_key; + t1_key.resize(joined_col_cnt); + key_indexer & t2_indexer = t2.get_key_indexer(joined_col_cnt, cols2); + + bool key_modified=true; + key_indexer::query_result t2_offsets; + store_offset t1_after_last = t1.m_data.after_last_offset(); + for(store_offset t1_ofs=0; t1_ofs(tgt0); + const sparse_table & neg = static_cast(neg0); + + if(m_cols1.size()==0) { + if(!neg.empty()) { + tgt.reset(); + } + return; + } + + svector to_remove; //offsets here are in increasing order + + //We don't do just the simple tgt.row_count()>neg.row_count() because the swapped case is + //more expensive. The constant 4 is, however, just my guess what the ratio might be. + if(tgt.row_count()/4>neg.row_count()) { + collect_intersection_offsets(neg, tgt, false, to_remove); + } + else { + collect_intersection_offsets(tgt, neg, true, to_remove); + } + + if(to_remove.empty()) { + return; + } + + //the largest offsets are at the end, so we can remove them one by one + while(!to_remove.empty()) { + store_offset removed_ofs = to_remove.back(); + to_remove.pop_back(); + tgt.m_data.remove_offset(removed_ofs); + } + tgt.reset_indexes(); + } + + }; + + table_intersection_filter_fn * sparse_table_plugin::mk_filter_by_negation_fn(const table_base & t, + const table_base & negated_obj, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * negated_cols) { + if(!check_kind(t) || !check_kind(negated_obj) + || join_involves_functional(t.get_signature(), negated_obj.get_signature(), joined_col_cnt, + t_cols, negated_cols) ) { + return 0; + } + return alloc(negation_filter_fn, t, negated_obj, joined_col_cnt, t_cols, negated_cols); + } + + unsigned sparse_table::get_size_estimate_bytes() const { + unsigned sz = 0; + sz += m_data.get_size_estimate_bytes(); + sz += m_key_indexes.capacity()*8; // TBD + return sz; + } + + +}; + diff --git a/lib/dl_sparse_table.h b/lib/dl_sparse_table.h new file mode 100644 index 000000000..3920836e6 --- /dev/null +++ b/lib/dl_sparse_table.h @@ -0,0 +1,480 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_table.h + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-09-01. + +Revision History: + +--*/ + +#ifndef _DL_SPARSE_TABLE_H_ +#define _DL_SPARSE_TABLE_H_ + +#include +#include +#include + +#include "ast.h" +#include "bit_vector.h" +#include "buffer.h" +#include "hashtable.h" +#include "map.h" +#include "ref_vector.h" +#include "vector.h" + +#include "dl_base.h" + + +namespace datalog { + class sparse_table; + + class sparse_table_plugin : public table_plugin { + friend class sparse_table; + protected: + class join_project_fn; + class union_fn; + class transformer_fn; + class rename_fn; + class project_fn; + class negation_filter_fn; + class select_equal_and_project_fn; + + typedef ptr_vector sp_table_vector; + typedef map table_pool; + + table_pool m_pool; + + void recycle(sparse_table * t); + + void garbage_collect(); + + void reset(); + + static bool join_involves_functional(const table_signature & s1, const table_signature & s2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2); + + public: + typedef sparse_table table; + + sparse_table_plugin(relation_manager & manager); + ~sparse_table_plugin(); + + virtual bool can_handle_signature(const table_signature & s) + { return s.size()>0; } + + virtual table_base * mk_empty(const table_signature & s); + sparse_table * mk_clone(const sparse_table & t); + + protected: + virtual table_join_fn * mk_join_fn(const table_base & t1, const table_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2); + virtual table_join_fn * mk_join_project_fn(const table_base & t1, const table_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2, unsigned removed_col_cnt, + const unsigned * removed_cols); + virtual table_union_fn * mk_union_fn(const table_base & tgt, const table_base & src, + const table_base * delta); + virtual table_transformer_fn * mk_project_fn(const table_base & t, unsigned col_cnt, + const unsigned * removed_cols); + virtual table_transformer_fn * mk_rename_fn(const table_base & t, unsigned permutation_cycle_len, + const unsigned * permutation_cycle); + virtual table_transformer_fn * mk_select_equal_and_project_fn(const table_base & t, + const table_element & value, unsigned col); + virtual table_intersection_filter_fn * mk_filter_by_negation_fn(const table_base & t, + const table_base & negated_obj, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * negated_cols); + }; + + class entry_storage { + public: + typedef unsigned store_offset; + private: + typedef svector storage; + + class offset_hash_proc { + storage & m_storage; + unsigned m_unique_entry_size; + public: + offset_hash_proc(storage & s, unsigned unique_entry_sz) + : m_storage(s), m_unique_entry_size(unique_entry_sz) {} + unsigned operator()(store_offset ofs) const { + return string_hash(m_storage.c_ptr()+ofs, m_unique_entry_size, 0); + } + }; + + class offset_eq_proc { + storage & m_storage; + unsigned m_unique_entry_size; + public: + offset_eq_proc(storage & s, unsigned unique_entry_sz) + : m_storage(s), m_unique_entry_size(unique_entry_sz) {} + bool operator()(store_offset o1, store_offset o2) const { + const char * base = m_storage.c_ptr(); + return memcmp(base+o1, base+o2, m_unique_entry_size)==0; + } + }; + + typedef hashtable storage_indexer; + + static const store_offset NO_RESERVE = UINT_MAX; + + unsigned m_entry_size; + unsigned m_unique_part_size; + unsigned m_data_size; + /** + Invariant: Every or all but one blocks of length \c m_entry_size in the \c m_data vector + are unique sequences of bytes and have their offset stored in the \c m_data_indexer hashtable. + If the offset of the last block is not stored in the hashtable, it is stored in the \c m_reserve + variable. Otherwise \c m_reserve==NO_RESERVE. + + The size of m_data is actually 8 bytes larger than stated in m_data_size, so that we may + deref an uint64 pointer at the end of the array. + */ + storage m_data; + storage_indexer m_data_indexer; + store_offset m_reserve; + public: + entry_storage(unsigned entry_size, unsigned functional_size = 0, unsigned init_size = 0) + : m_entry_size(entry_size), + m_unique_part_size(entry_size-functional_size), + m_data_indexer(next_power_of_two(std::max(8u,init_size)), + offset_hash_proc(m_data, m_unique_part_size), offset_eq_proc(m_data, m_unique_part_size)), + m_reserve(NO_RESERVE) { + SASSERT(entry_size>0); + SASSERT(functional_size<=entry_size); + resize_data(init_size); + resize_data(0); + } + entry_storage(const entry_storage &s) + : m_entry_size(s.m_entry_size), + m_unique_part_size(s.m_unique_part_size), + m_data_size(s.m_data_size), + m_data(s.m_data), + m_data_indexer(next_power_of_two(std::max(8u,s.entry_count())), + offset_hash_proc(m_data, m_unique_part_size), offset_eq_proc(m_data, m_unique_part_size)), + m_reserve(s.m_reserve) { + store_offset after_last=after_last_offset(); + for(store_offset i=0; i(this)->get(ofs); } + + unsigned entry_count() const { return m_data_indexer.size(); } + + store_offset after_last_offset() const { + return (m_reserve==NO_RESERVE) ? m_data_size : m_reserve; + } + + char * begin() { return get(0); } + const char * begin() const { return get(0); } + const char * after_last() const { return get(after_last_offset()); } + + + bool has_reserve() const { return m_reserve!=NO_RESERVE; } + store_offset reserve() const { SASSERT(has_reserve()); return m_reserve; } + + void ensure_reserve() { + if(has_reserve()) { + SASSERT(m_reserve==m_data_size-m_entry_size); + return; + } + m_reserve=m_data_size; + resize_data(m_data_size+m_entry_size); + } + + /** + \brief Return pointer to the reserve. + + The reserve must exist when the function is called. + */ + char * get_reserve_ptr() { + SASSERT(has_reserve()); + return &m_data.get(reserve()); + } + + bool reserve_content_already_present() const { + SASSERT(has_reserve()); + return m_data_indexer.contains(reserve()); + } + + bool find_reserve_content(store_offset & result) const { + SASSERT(has_reserve()); + storage_indexer::entry * indexer_entry = m_data_indexer.find_core(reserve()); + if(!indexer_entry) { + return false; + } + result = indexer_entry->get_data(); + return true; + } + + /** + \brief Write fact \c f into the reserve at the end of the \c m_data storage. + + If the reserve does not exist, this function creates it. + */ + void write_into_reserve(const char * data) { + ensure_reserve(); + memcpy(get_reserve_ptr(), data, m_entry_size); + } + + /** + \brief If the fact in reserve is not in the table, insert it there and return true; + otherwise return false. + + When a fact is inserted into the table, the reserve becomes part of the table and + is no longer a reserve. + */ + bool insert_reserve_content(); + store_offset insert_or_get_reserve_content(); + bool remove_reserve_content(); + /** + Remove data at the offset \c ofs. + + Data with offset lower than \c ofs are not be modified by this function, data with + higher offset may be moved. + */ + void remove_offset(store_offset ofs); + + + //the following two operations allow breaking of the object invariant! + void resize_data(unsigned sz) { + m_data_size = sz; + m_data.resize(sz + sizeof(uint64)); + } + + bool insert_offset(store_offset ofs) { + return m_data_indexer.insert_if_not_there(ofs)==ofs; + } + }; + + class sparse_table : public table_base { + friend class sparse_table_plugin; + friend class sparse_table_plugin::join_project_fn; + friend class sparse_table_plugin::union_fn; + friend class sparse_table_plugin::transformer_fn; + friend class sparse_table_plugin::rename_fn; + friend class sparse_table_plugin::project_fn; + friend class sparse_table_plugin::negation_filter_fn; + friend class sparse_table_plugin::select_equal_and_project_fn; + + class our_iterator_core; + class key_indexer; + class general_key_indexer; + class full_signature_key_indexer; + typedef entry_storage::store_offset store_offset; + + + class column_info { + unsigned m_big_offset; + unsigned m_small_offset; + uint64 m_mask; + uint64 m_write_mask; + public: + unsigned m_offset; //!< in bits + unsigned m_length; //!< in bits + + column_info(unsigned offset, unsigned length) \ + : m_big_offset(offset/8), + m_small_offset(offset%8), + m_mask( length==64 ? ULLONG_MAX : (static_cast(1)<(rec+m_big_offset); + uint64 res = *ptr; + res>>=m_small_offset; + res&=m_mask; + return res; + } + void set(char * rec, table_element val) const { + SASSERT( (val&~m_mask)==0 ); //the value fits into the column + uint64 * ptr = reinterpret_cast(rec+m_big_offset); + *ptr&=m_write_mask; + *ptr|=val< { + + void make_byte_aligned_end(unsigned col_index); + public: + + unsigned m_entry_size; + /** + Number of last bytes which correspond to functional columns in the signature. + */ + unsigned m_functional_part_size; + unsigned m_functional_col_cnt; + + column_layout(const table_signature & sig); + + table_element get(const char * rec, unsigned col) const { + return (*this)[col].get(rec); + } + void set(char * rec, unsigned col, table_element val) const { + return (*this)[col].set(rec, val); + } + }; + + + typedef svector key_spec; //sequence of columns in a key + typedef svector key_value; //values of key columns + typedef map, + vector_eq_proc > key_index_map; + + static const store_offset NO_RESERVE = UINT_MAX; + + column_layout m_column_layout; + unsigned m_fact_size; + entry_storage m_data; + mutable key_index_map m_key_indexes; + + + const char * get_at_offset(store_offset i) const { + return m_data.get(i); + } + + table_element get_cell(store_offset ofs, unsigned column) const { + return m_column_layout.get(m_data.get(ofs), column); + } + + void set_cell(store_offset ofs, unsigned column, table_element val) { + m_column_layout.set(m_data.get(ofs), column, val); + } + + void write_into_reserve(const table_element* f); + + /** + \brief Return reference to an indexer over columns in \c key_cols. + + An indexer can retrieve a sequence of offsets that with \c key_cols columns equal to + the specified key. Indexers are populated lazily -- they remember the position of the + last fact they contain, and when an indexer is retrieved by the \c get_key_indexer function, + all the new facts are added into the indexer. + + When a fact is removed from the table, all indexers are destroyed. This is not an extra + expense in the current use scenario, because we first perform all fact removals and do the + joins only after that (joins are the only operations that lead to index construction). + */ + key_indexer& get_key_indexer(unsigned key_len, const unsigned * key_cols) const; + + void reset_indexes(); + + static void copy_columns(const column_layout & src_layout, const column_layout & dest_layout, + unsigned start_index, unsigned after_last, const char * src, char * dest, + unsigned & dest_idx, unsigned & pre_projection_idx, const unsigned * & next_removed); + + /** + \c array \c removed_cols contains column indexes to be removed in ascending order and + is terminated by a number greated than the highest column index of a join the the two tables. + This is to simplify the traversal of the array when building facts. + */ + static void concatenate_rows(const column_layout & layout1, const column_layout & layout2, + const column_layout & layout_res, const char * ptr1, const char * ptr2, char * res, + const unsigned * removed_cols); + + /** + \brief Perform join-project between t1 and t2 iterating through t1 and retrieving relevant + columns from t2 using indexing. + + \c array \c removed_cols contains column indexes to be removed in ascending order and + is terminated by a number greated than the highest column index of a join the the two tables. + This is to simplify the traversal of the array when building facts. + + \c tables_swapped value means that the resulting facts should contain facts from t2 first, + instead of the default behavior that would concatenate the two facts as \c (t1,t2). + + \remark The function is called \c self_agnostic_join since, unlike the virtual method + \c join, it is static and therefore allows to easily swap the roles of the two joined + tables (the indexed and iterated one) in a way that is expected to give better performance. + */ + static void self_agnostic_join_project(const sparse_table & t1, const sparse_table & t2, + unsigned joined_col_cnt, const unsigned * t1_joined_cols, const unsigned * t2_joined_cols, + const unsigned * removed_cols, bool tables_swapped, sparse_table & result); + + + /** + If the fact at \c data (in table's native representation) is not in the table, + add it and return true. Otherwise return false. + */ + bool add_fact(const char * data); + + bool add_reserve_content(); + + void garbage_collect(); + + sparse_table(sparse_table_plugin & p, const table_signature & sig, unsigned init_capacity=0); + sparse_table(const sparse_table & t); + virtual ~sparse_table(); + public: + + virtual void deallocate() { + get_plugin().recycle(this); + } + + unsigned row_count() const { return m_data.entry_count(); } + + sparse_table_plugin & get_plugin() const + { return static_cast(table_base::get_plugin()); } + + virtual bool empty() const { return row_count()==0; } + virtual void add_fact(const table_fact & f); + virtual bool contains_fact(const table_fact & f) const; + virtual bool fetch_fact(table_fact & f) const; + virtual void ensure_fact(const table_fact & f); + virtual void remove_fact(const table_element* fact); + virtual void reset(); + + virtual table_base * clone() const; + + virtual table_base::iterator begin() const; + virtual table_base::iterator end() const; + + virtual unsigned get_size_estimate_rows() const { return row_count(); } + virtual unsigned get_size_estimate_bytes() const; + virtual bool knows_exact_size() const { return true; } + }; + + }; + + #endif /* _DL_SPARSE_TABLE_H_ */ diff --git a/lib/dl_table.cpp b/lib/dl_table.cpp new file mode 100644 index 000000000..1f2ba3abc --- /dev/null +++ b/lib/dl_table.cpp @@ -0,0 +1,772 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_table.cpp + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-09-01. + +Revision History: + +--*/ + +#include"dl_context.h" +#include"dl_util.h" +#include"dl_table.h" + +namespace datalog { + + // ----------------------------------- + // + // hashtable_table + // + // ----------------------------------- + + table_base * hashtable_table_plugin::mk_empty(const table_signature & s) { + SASSERT(can_handle_signature(s)); + return alloc(hashtable_table, *this, s); + } + + + class hashtable_table_plugin::join_fn : public convenient_table_join_fn { + unsigned m_joined_col_cnt; + public: + join_fn(const table_signature & t1_sig, const table_signature & t2_sig, unsigned col_cnt, const unsigned * cols1, const unsigned * cols2) + : convenient_table_join_fn(t1_sig, t2_sig, col_cnt, cols1, cols2), + m_joined_col_cnt(col_cnt) {} + + virtual table_base * operator()(const table_base & t1, const table_base & t2) { + + const hashtable_table & ht1 = static_cast(t1); + const hashtable_table & ht2 = static_cast(t2); + + hashtable_table_plugin & plugin = ht1.get_plugin(); + + hashtable_table * res = static_cast(plugin.mk_empty(get_result_signature())); + + hashtable_table::storage::iterator els1it = ht1.m_data.begin(); + hashtable_table::storage::iterator els1end = ht1.m_data.end(); + hashtable_table::storage::iterator els2end = ht2.m_data.end(); + + table_fact acc; + + for(; els1it!=els1end; ++els1it) { + const table_fact & row1 = *els1it; + + hashtable_table::storage::iterator els2it = ht2.m_data.begin(); + for(; els2it!=els2end; ++els2it) { + const table_fact & row2 = *els2it; + + bool match=true; + for(unsigned i=0; im_data.insert(acc); + } + } + return res; + } + }; + + table_join_fn * hashtable_table_plugin::mk_join_fn(const table_base & t1, const table_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2) { + if(t1.get_kind()!=get_kind() || t2.get_kind()!=get_kind()) { + return 0; + } + return alloc(join_fn, t1.get_signature(), t2.get_signature(), col_cnt, cols1, cols2); + } + + + class hashtable_table::our_iterator_core : public iterator_core { + const hashtable_table & m_parent; + storage::iterator m_inner; + storage::iterator m_end; + + class our_row : public row_interface { + const our_iterator_core & m_parent; + public: + our_row(const our_iterator_core & parent) : row_interface(parent.m_parent), m_parent(parent) {} + + virtual void get_fact(table_fact & result) const { + result = *m_parent.m_inner; + } + virtual table_element operator[](unsigned col) const { + return (*m_parent.m_inner)[col]; + } + + }; + + our_row m_row_obj; + + public: + our_iterator_core(const hashtable_table & t, bool finished) : + m_parent(t), m_inner(finished ? t.m_data.end() : t.m_data.begin()), + m_end(t.m_data.end()), m_row_obj(*this) {} + + virtual bool is_finished() const { + return m_inner==m_end; + } + + virtual row_interface & operator*() { + SASSERT(!is_finished()); + return m_row_obj; + } + virtual void operator++() { + SASSERT(!is_finished()); + ++m_inner; + } + }; + + + + table_base::iterator hashtable_table::begin() const { + return mk_iterator(alloc(our_iterator_core, *this, false)); + } + + table_base::iterator hashtable_table::end() const { + return mk_iterator(alloc(our_iterator_core, *this, true)); + } + + // ----------------------------------- + // + // bitvector_table + // + // ----------------------------------- + + bool bitvector_table_plugin::can_handle_signature(const table_signature & sig) { + if(sig.functional_columns()!=0) { + return false; + } + unsigned cols = sig.size(); + unsigned shift = 0; + for (unsigned i = 0; i < cols; ++i) { + unsigned s = static_cast(sig[i]); + if (s != sig[i] || !is_power_of_two(s)) { + return false; + } + unsigned num_bits = 0; + unsigned bit_pos = 1; + for (num_bits = 1; num_bits < 32; ++num_bits) { + if (bit_pos & s) { + break; + } + bit_pos <<= 1; + } + shift += num_bits; + if (shift >= 32) { + return false; + } + } + return true; + } + + table_base * bitvector_table_plugin::mk_empty(const table_signature & s) { + SASSERT(can_handle_signature(s)); + return alloc(bitvector_table, *this, s); + } + + class bitvector_table::bv_iterator : public iterator_core { + + bitvector_table const& m_bv; + unsigned m_offset; + + class our_row : public caching_row_interface { + const bv_iterator& m_parent; + public: + our_row(const bv_iterator & p) : caching_row_interface(p.m_bv), m_parent(p) {} + virtual void get_fact(table_fact& result) const { + if (result.size() < size()) { + result.resize(size(), 0); + } + m_parent.m_bv.offset2fact(m_parent.m_offset, result); + } + }; + our_row m_row_obj; + + public: + bv_iterator(const bitvector_table& bv, bool end): + m_bv(bv), m_offset(end?m_bv.m_bv.size():0), m_row_obj(*this) + { + if (!is_finished() && !m_bv.m_bv.get(m_offset)) { + ++(*this); + } + } + + virtual bool is_finished() const { + return m_offset == m_bv.m_bv.size(); + } + + virtual row_interface & operator*() { + SASSERT(!is_finished()); + return m_row_obj; + } + virtual void operator++() { + SASSERT(!is_finished()); + ++m_offset; + while (!is_finished() && !m_bv.m_bv.get(m_offset)) { + ++m_offset; + } + m_row_obj.reset(); + } + }; + + bitvector_table::bitvector_table(bitvector_table_plugin & plugin, const table_signature & sig) + : table_base(plugin, sig) { + SASSERT(plugin.can_handle_signature(sig)); + + m_num_cols = sig.size(); + unsigned shift = 0; + for (unsigned i = 0; i < m_num_cols; ++i) { + unsigned s = static_cast(sig[i]); + if (s != sig[i] || !is_power_of_two(s)) { + throw default_exception("bit-vector table is specialized to small domains that are powers of two"); + } + m_shift.push_back(shift); + m_mask.push_back(s - 1); + unsigned num_bits = 0; + unsigned bit_pos = 1; + for (num_bits = 1; num_bits < 32; ++num_bits) { + if (bit_pos & s) { + break; + } + bit_pos <<= 1; + } + shift += num_bits; + if (shift >= 32) { + throw default_exception("bit-vector table is specialized to small domains that are powers of two"); + } + m_bv.reserve(1 << shift); + } + } + + unsigned bitvector_table::fact2offset(const table_element* f) const { + unsigned result = 0; + for (unsigned i = 0; i < m_num_cols; ++i) { + SASSERT(f[i]> m_shift[i]); + } + } + + void bitvector_table::add_fact(const table_fact & f) { + m_bv.set(fact2offset(f.c_ptr())); + } + + void bitvector_table::remove_fact(const table_element* fact) { + m_bv.unset(fact2offset(fact)); + } + + bool bitvector_table::contains_fact(const table_fact & f) const { + return m_bv.get(fact2offset(f.c_ptr())); + } + + table_base::iterator bitvector_table::begin() const { + return mk_iterator(alloc(bv_iterator, *this, false)); + } + + table_base::iterator bitvector_table::end() const { + return mk_iterator(alloc(bv_iterator, *this, true)); + } + + + + + // ----------------------------------- + // + // equivalence_table + // + // ----------------------------------- + + bool equivalence_table_plugin::can_handle_signature(const table_signature & sig) { + return sig.functional_columns() == 0 && sig.size() == 2 && sig[0] < UINT_MAX && sig[0] == sig[1]; + } + + bool equivalence_table_plugin::is_equivalence_table(table_base const& tbl) const { + if (tbl.get_kind() != get_kind()) return false; + equivalence_table const& t = static_cast(tbl); + return !t.is_sparse(); + } + + table_base * equivalence_table_plugin::mk_empty(const table_signature & s) { + TRACE("dl", for (unsigned i = 0; i < s.size(); ++i) tout << s[i] << " "; tout << "\n";); + SASSERT(can_handle_signature(s)); + return alloc(equivalence_table, *this, s); + } + + class equivalence_table_plugin::select_equal_and_project_fn : public table_transformer_fn { + unsigned m_val; + table_sort m_sort; + public: + select_equal_and_project_fn(const table_signature & sig, table_element val, unsigned col) + : m_val(static_cast(val)), + m_sort(sig[0]) { + SASSERT(val <= UINT_MAX); + SASSERT(col == 0 || col == 1); + SASSERT(sig.functional_columns() == 0); + SASSERT(sig.size() == 2); + SASSERT(sig[0] < UINT_MAX && sig[0] == sig[1]); + } + + virtual table_base* operator()(const table_base& tb) { + TRACE("dl", tout << "\n";); + table_plugin & plugin = tb.get_plugin(); + table_plugin* rp = plugin.get_manager().get_table_plugin(symbol("sparse")); + SASSERT(rp); + table_signature sig; + sig.push_back(m_sort); + table_base* result = rp->mk_empty(sig); + equivalence_table const& eq_table = static_cast(tb); + if (eq_table.is_valid(m_val)) { + table_fact fact; + fact.resize(1); + unsigned r = m_val; + do { + fact[0] = r; + result->add_fact(fact); + r = eq_table.m_uf.next(r); + } + while (r != m_val); + } + TRACE("dl", tb.display(tout << "src:\n"); result->display(tout << "result\n");); + return result; + } + }; + + table_transformer_fn * equivalence_table_plugin::mk_select_equal_and_project_fn( + const table_base & t, const table_element & value, unsigned col) { + return alloc(select_equal_and_project_fn, t.get_signature(), value, col); + } + + class equivalence_table_plugin::union_fn : public table_union_fn { + + equivalence_table_plugin& m_plugin; + + + void mk_union1(equivalence_table & tgt, const equivalence_table & src, table_base * delta) { + unsigned num_vars = src.m_uf.get_num_vars(); + table_fact fact; + fact.resize(2); + for (unsigned i = 0; i < num_vars; ++i) { + if (src.is_valid(i) && src.m_uf.find(i) == i) { + fact[0] = i; + equivalence_table::class_iterator it = src.class_begin(i); + equivalence_table::class_iterator end = src.class_end(i); + for (; it != end; ++it) { + fact[1] = *it; + if (!tgt.contains_fact(fact)) { + tgt.add_fact(fact); + if (delta) { + delta->add_fact(fact); + } + } + } + } + } + } + + void mk_union2(equivalence_table & tgt, const table_base & src, table_base * delta) { + table_fact fact; + table_base::iterator it = src.begin(), end = src.end(); + for (; it != end; ++it) { + it->get_fact(fact); + if (!tgt.contains_fact(fact)) { + tgt.add_fact(fact); + if (delta) { + delta->add_fact(fact); + TRACE("dl", + tout << "Add: "; + for (unsigned i = 0; i < fact.size(); ++i) tout << fact[i] << " "; + tout << "\n";); + } + } + } + } + + public: + union_fn(equivalence_table_plugin& p) : m_plugin(p) {} + + virtual void operator()(table_base & tgt0, const table_base & src, table_base * delta) { + TRACE("dl", tout << "union\n";); + equivalence_table & tgt = static_cast(tgt0); + if (m_plugin.is_equivalence_table(src)) { + mk_union1(tgt, static_cast(src), delta); + } + else { + mk_union2(tgt, src, delta); + } + TRACE("dl", src.display(tout << "src\n"); tgt.display(tout << "tgt\n"); + if (delta) delta->display(tout << "delta\n");); + } + }; + + table_union_fn * equivalence_table_plugin::mk_union_fn( + const table_base & tgt, const table_base & src, const table_base * delta) { + if (!is_equivalence_table(tgt) || + tgt.get_signature() != src.get_signature() || + (delta && delta->get_signature() != tgt.get_signature())) { + return 0; + } + return alloc(union_fn,*this); + } + + class equivalence_table_plugin::join_project_fn : public convenient_table_join_project_fn { + equivalence_table_plugin& m_plugin; + public: + join_project_fn( + equivalence_table_plugin& plugin, const table_signature & t1_sig, const table_signature & t2_sig, unsigned col_cnt, + const unsigned * cols1, const unsigned * cols2, unsigned removed_col_cnt, + const unsigned * removed_cols) + : convenient_table_join_project_fn(t1_sig, t2_sig, col_cnt, cols1, cols2, removed_col_cnt, removed_cols), + m_plugin(plugin) { + m_removed_cols.push_back(UINT_MAX); + } + + virtual table_base * operator()(const table_base & tb1, const table_base & tb2) { + SASSERT(m_cols1.size() == 1); + const table_signature & res_sign = get_result_signature(); + table_plugin * plugin = &tb1.get_plugin(); + if (!plugin->can_handle_signature(res_sign)) { + plugin = &tb2.get_plugin(); + if (!plugin->can_handle_signature(res_sign)) { + plugin = &tb1.get_manager().get_appropriate_plugin(res_sign); + } + } + SASSERT(plugin->can_handle_signature(res_sign)); + table_base * result = plugin->mk_empty(res_sign); + + if (m_plugin.is_equivalence_table(tb1)) { + mk_join(0, m_cols1[0], static_cast(tb1), + 2, m_cols2[0], tb2, result); + } + else if (m_plugin.is_equivalence_table(tb2)) { + mk_join(tb1.get_signature().size(), m_cols2[0], static_cast(tb2), + 0, m_cols1[0], tb1, result); + } + else { + UNREACHABLE(); + } + TRACE("dl", tb1.display(tout << "tb1\n"); tb2.display(tout << "tb2\n"); result->display(tout << "result\n");); + return result; + } + + private: + table_base * mk_join(unsigned offs1, unsigned col1, equivalence_table const & t1, + unsigned offs2, unsigned col2, table_base const& t2, table_base* res) { + table_base::iterator els2it = t2.begin(); + table_base::iterator els2end = t2.end(); + + table_fact acc, proj; + acc.resize(t1.get_signature().size() + t2.get_signature().size()); + + for(; els2it != els2end; ++els2it) { + const table_base::row_interface & row2 = *els2it; + table_element const& e2 = row2[col2]; + equivalence_table::class_iterator it = t1.class_begin(e2); + equivalence_table::class_iterator end = t1.class_end(e2); + if (it != end) { + for (unsigned i = 0; i < row2.size(); ++i) { + acc[i+offs2] = row2[i]; + } + } + for (; it != end; ++it) { + acc[offs1+col1] = e2; + acc[offs1+1-col1] = *it; + mk_project(acc, proj); + TRACE("dl", for (unsigned i = 0; i < proj.size(); ++i) tout << proj[i] << " "; tout << "\n";); + res->add_fact(proj); + } + } + return res; + } + + virtual void mk_project(table_fact const & f, table_fact & p) const { + unsigned sz = f.size(); + p.reset(); + for (unsigned i = 0, r = 0; i < sz; ++i) { + if (r < m_removed_cols.size() && m_removed_cols[r] == i) { + ++r; + } + else { + p.push_back(f[i]); + } + } + } + + + }; + + table_join_fn * equivalence_table_plugin::mk_join_project_fn( + const table_base & t1, const table_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2, unsigned removed_col_cnt, + const unsigned * removed_cols) { + if (col_cnt != 1) { + TRACE("dl", tout << "WARNING: join_project on multiple columns is not implemented\n";); + return 0; + } + if (is_equivalence_table(t1) || is_equivalence_table(t2)) { + return alloc(join_project_fn, *this, t1.get_signature(), t2.get_signature(), col_cnt, cols1, cols2, + removed_col_cnt, removed_cols); + } + return 0; + } + + class equivalence_table::eq_iterator : public iterator_core { + + equivalence_table const& m_eq; + unsigned m_last; + unsigned m_current; + unsigned m_next; + + class our_row : public caching_row_interface { + const eq_iterator& m_parent; + public: + our_row(const eq_iterator & p) : caching_row_interface(p.m_eq), m_parent(p) {} + + virtual void get_fact(table_fact& result) const { + if (result.size() < size()) { + result.resize(size(), 0); + } + result[0] = m_parent.m_current; + result[1] = m_parent.m_next; + } + + virtual table_element operator[](unsigned col) const { + if (col == 0) return m_parent.m_current; + if (col == 1) return m_parent.m_next; + UNREACHABLE(); + return 0; + } + + }; + our_row m_row_obj; + + public: + eq_iterator(const equivalence_table& eq, bool end): + m_eq(eq), + m_row_obj(*this), + m_last(eq.m_uf.get_num_vars()), + m_current(end?m_last:0), + m_next(0) + { + while (m_current < m_last && !m_eq.is_valid(m_current)) { + m_current++; + m_next = m_current; + } + } + + virtual bool is_finished() const { + return m_current == m_last; + } + + virtual row_interface & operator*() { + SASSERT(!is_finished()); + return m_row_obj; + } + + virtual void operator++() { + SASSERT(!is_finished()); + m_next = m_eq.m_uf.next(m_next); + if (m_next == m_current) { + do { + m_current++; + m_next = m_current; + } + while (m_current < m_last && !m_eq.is_valid(m_current)); + } + } + }; + + equivalence_table::equivalence_table(equivalence_table_plugin & plugin, const table_signature & sig) + : table_base(plugin, sig), m_uf(m_ctx), m_sparse(0) { + SASSERT(plugin.can_handle_signature(sig)); + } + + equivalence_table::~equivalence_table() { + if (is_sparse()) { + m_sparse->deallocate(); + } + } + + + void equivalence_table::add_fact(const table_fact & f) { + if (is_sparse()) { + add_fact_sparse(f); + } + else { + TRACE("dl_verbose", for (unsigned i = 0; i < f.size(); ++i) tout << f[i] << " "; tout << "\n";); + while (first(f) >= m_uf.get_num_vars()) m_uf.mk_var(); + while (second(f) >= m_uf.get_num_vars()) m_uf.mk_var(); + m_uf.merge(first(f), second(f)); + m_valid.reserve(m_uf.get_num_vars()); + m_valid.set(first(f)); + m_valid.set(second(f)); + } + } + + void equivalence_table::remove_fact(const table_element* fact) { + mk_sparse(); + m_sparse->remove_fact(fact); + } + + void equivalence_table::mk_sparse() { + if (m_sparse) return; + + TRACE("dl",tout << "\n";); + table_plugin & plugin = get_plugin(); + table_plugin* rp = plugin.get_manager().get_table_plugin(symbol("sparse")); + SASSERT(rp); + table_base* result = rp->mk_empty(get_signature()); + table_base::iterator it = begin(), e = end(); + table_fact fact; + for (; it != e; ++it) { + it->get_fact(fact); + result->add_fact(fact); + } + m_sparse = result; + } + + void equivalence_table::add_fact_sparse(table_fact const& f) { + table_base::iterator it = m_sparse->begin(), end = m_sparse->end(); + vector to_add; + to_add.push_back(f); + table_fact f1(f); + + f1[0] = f[1]; + f1[1] = f[0]; + to_add.push_back(f1); + + f1[0] = f[1]; + f1[1] = f[1]; + to_add.push_back(f1); + + f1[0] = f[0]; + f1[1] = f[0]; + to_add.push_back(f1); + + for (; it != end; ++it) { + if ((*it)[0] == f[0]) { + f1[0] = f[1]; + f1[1] = (*it)[1]; + to_add.push_back(f1); + std::swap(f1[0],f1[1]); + to_add.push_back(f1); + } + } + for (unsigned i = 0; i < to_add.size(); ++i) { + m_sparse->add_fact(to_add[i]); + } + } + + bool equivalence_table::contains_fact(const table_fact & f) const { + TRACE("dl_verbose", for (unsigned i = 0; i < f.size(); ++i) tout << f[i] << " "; tout << "\n";); + if (is_sparse()) { + return m_sparse->contains_fact(f); + } + return + is_valid(first(f)) && + is_valid(second(f)) && + m_uf.find(first(f)) == m_uf.find(second(f)); + } + + table_base* equivalence_table::clone() const { + if (is_sparse()) { + return m_sparse->clone(); + } + TRACE("dl",tout << "\n";); + table_plugin & plugin = get_plugin(); + table_base* result = plugin.mk_empty(get_signature()); + table_fact fact; + fact.resize(2); + for (unsigned i = 0; i < m_uf.get_num_vars(); ++i) { + if (m_valid.get(i) && m_uf.find(i) == i) { + unsigned n = m_uf.next(i); + fact[0] = i; + while (n != i) { + fact[1] = n; + result->add_fact(fact); + n = m_uf.next(n); + } + } + } + return result; + } + + table_base::iterator equivalence_table::begin() const { + if (is_sparse()) return m_sparse->begin(); + return mk_iterator(alloc(eq_iterator, *this, false)); + } + + table_base::iterator equivalence_table::end() const { + if (is_sparse()) return m_sparse->end(); + return mk_iterator(alloc(eq_iterator, *this, true)); + } + + equivalence_table::class_iterator equivalence_table::class_begin(table_element const& _e) const { + SASSERT(!is_sparse()); + unsigned e = static_cast(_e); + return class_iterator(*this, e, !is_valid(e)); + } + + equivalence_table::class_iterator equivalence_table::class_end(table_element const& _e) const { + SASSERT(!is_sparse()); + unsigned e = static_cast(_e); + return class_iterator(*this, e, true); + } + + void equivalence_table::display(std::ostream& out) const { + if (is_sparse()) { + m_sparse->display(out); + return; + } + for (unsigned i = 0; i < m_uf.get_num_vars(); ++i) { + if (is_valid(i) && m_uf.find(i) == i) { + unsigned j = i, last = i; + do { + out << "<" << i << " " << j << ">\n"; + j = m_uf.next(j); + } + while (last != j); + } + } + } + + unsigned equivalence_table::get_size_estimate_rows() const { + if (is_sparse()) return m_sparse->get_size_estimate_rows(); + return static_cast(get_signature()[0]); + } + + unsigned equivalence_table::get_size_estimate_bytes() const { + if (is_sparse()) return m_sparse->get_size_estimate_bytes(); + return static_cast(get_signature()[0]); + } + + bool equivalence_table::knows_exact_size() const { + return (!is_sparse() || m_sparse->knows_exact_size()); + } + +}; + diff --git a/lib/dl_table.h b/lib/dl_table.h new file mode 100644 index 000000000..8dc0a355b --- /dev/null +++ b/lib/dl_table.h @@ -0,0 +1,265 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_table.h + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-09-01. + +Revision History: + +--*/ +#ifndef _DL_TABLE_H_ +#define _DL_TABLE_H_ + +#include +#include +#include + +#include "ast.h" +#include "bit_vector.h" +#include "buffer.h" +#include "hashtable.h" +#include "map.h" +#include "ref_vector.h" +#include "vector.h" +#include "union_find.h" +#include "dl_base.h" +#include "dl_util.h" +#include "bit_vector.h" + + +namespace datalog { + + class context; + class variable_intersection; + + + + // ----------------------------------- + // + // hashtable_table + // + // ----------------------------------- + + class hashtable_table; + + class hashtable_table_plugin : public table_plugin { + friend class hashtable_table; + protected: + class join_fn; + public: + typedef hashtable_table table; + + hashtable_table_plugin(relation_manager & manager) + : table_plugin(symbol("hashtable"), manager) {} + + virtual table_base * mk_empty(const table_signature & s); + + virtual table_join_fn * mk_join_fn(const table_base & t1, const table_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2); + }; + + class hashtable_table : public table_base { + friend class hashtable_table_plugin; + friend class hashtable_table_plugin::join_fn; + + class our_iterator_core; + + typedef hashtable, + vector_eq_proc > storage; + + storage m_data; + + hashtable_table(hashtable_table_plugin & plugin, const table_signature & sig) + : table_base(plugin, sig) {} + public: + hashtable_table_plugin & get_plugin() const + { return static_cast(table_base::get_plugin()); } + + virtual void add_fact(const table_fact & f) { + m_data.insert(f); + } + virtual void remove_fact(const table_element* fact) { + table_fact f(get_signature().size(), fact); + m_data.remove(f); + } + virtual bool contains_fact(const table_fact & f) const { + return m_data.contains(f); + } + + virtual iterator begin() const; + virtual iterator end() const; + + virtual unsigned get_size_estimate_rows() const { return m_data.size(); } + virtual unsigned get_size_estimate_bytes() const { return m_data.size()*get_signature().size()*8; } + virtual bool knows_exact_size() const { return true; } + }; + + // ----------------------------------- + // + // bitvector_table + // + // ----------------------------------- + + class bitvector_table; + + class bitvector_table_plugin : public table_plugin { + public: + typedef bitvector_table table; + + bitvector_table_plugin(relation_manager & manager) + : table_plugin(symbol("bitvector"), manager) {} + + virtual bool can_handle_signature(const table_signature & s); + + virtual table_base * mk_empty(const table_signature & s); + }; + + class bitvector_table : public table_base { + friend class bitvector_table_plugin; + + class bv_iterator; + bit_vector m_bv; + unsigned m_num_cols; + unsigned_vector m_shift; + unsigned_vector m_mask; + + unsigned fact2offset(const table_element* f) const; + void offset2fact(unsigned offset, table_fact& f) const; + + bitvector_table(bitvector_table_plugin & plugin, const table_signature & sig); + public: + virtual void add_fact(const table_fact & f); + virtual void remove_fact(const table_element* fact); + virtual bool contains_fact(const table_fact & f) const; + virtual iterator begin() const; + virtual iterator end() const; + }; + + // ------------------------------------------- + // Equivalence table. + // Really: partial equivalence relation table. + // ------------------------------------------- + + class equivalence_table; + + class equivalence_table_plugin : public table_plugin { + class union_fn; + class select_equal_and_project_fn; + class join_project_fn; + + bool is_equivalence_table(table_base const& tbl) const; + + public: + typedef equivalence_table table; + + equivalence_table_plugin(relation_manager & manager) + : table_plugin(symbol("equivalence"), manager) {} + + virtual bool can_handle_signature(const table_signature & s); + + virtual table_base * mk_empty(const table_signature & s); + + protected: + virtual table_union_fn * mk_union_fn(const table_base & tgt, const table_base & src, + const table_base * delta); + virtual table_transformer_fn * mk_select_equal_and_project_fn( + const table_base & t, + const table_element & value, unsigned col); + virtual table_join_fn * mk_join_project_fn(const table_base & t1, const table_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2, unsigned removed_col_cnt, + const unsigned * removed_cols); + + +#if 0 + virtual table_join_fn * mk_join_fn(const table_base & t1, const table_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2); + virtual table_transformer_fn * mk_project_fn(const table_base & t, unsigned col_cnt, + const unsigned * removed_cols); + virtual table_transformer_fn * mk_rename_fn(const table_base & t, unsigned permutation_cycle_len, + const unsigned * permutation_cycle); + const table_element & value, unsigned col); + virtual table_intersection_filter_fn * mk_filter_by_negation_fn(const table_base & t, + const table_base & negated_obj, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * negated_cols); +#endif + }; + + class equivalence_table : public table_base { + friend class equivalence_table_plugin; + + class eq_iterator; + union_find_default_ctx m_ctx; + bit_vector m_valid; + union_find<> m_uf; + table_base* m_sparse; + + equivalence_table(equivalence_table_plugin & plugin, const table_signature & sig); + virtual ~equivalence_table(); + + unsigned first(table_fact const& f) const { return static_cast(f[0]); } + unsigned second(table_fact const& f) const { return static_cast(f[1]); } + + bool is_valid(unsigned entry) const { return entry < m_valid.size() && m_valid.get(entry); } + bool is_sparse() const { return m_sparse != 0; } + + // iterator over equivalence class of 'n'. + class class_iterator { + equivalence_table const& m_parent; + unsigned m_current; + unsigned m_last; + bool m_end; + public: + class_iterator(equivalence_table const& s, unsigned n, bool end): + m_parent(s), m_current(n), m_last(n), m_end(end) {} + + unsigned operator*() { return m_current; } + + class_iterator& operator++() { + m_current = m_parent.m_uf.next(m_current); + m_end = (m_current == m_last); + return *this; + } + + bool operator==(const class_iterator & it) const { + return + (m_end && it.m_end) || + (!m_end && !it.m_end && m_current == it.m_current); + } + bool operator!=(const class_iterator & it) const { return !operator==(it); } + + }; + class_iterator class_begin(table_element const& e) const; + class_iterator class_end(table_element const& e) const; + + void add_fact_sparse(table_fact const& f); + void mk_sparse(); + + + public: + virtual void add_fact(const table_fact & f); + virtual void remove_fact(const table_element* fact); + virtual bool contains_fact(const table_fact & f) const; + virtual table_base* clone() const; + virtual iterator begin() const; + virtual iterator end() const; + virtual unsigned get_size_estimate_rows() const; + virtual unsigned get_size_estimate_bytes() const; + virtual bool knows_exact_size() const; + virtual void display(std::ostream & out) const; + + }; + + +}; + +#endif /* _DL_TABLE_H_ */ + diff --git a/lib/dl_table_plugin.h b/lib/dl_table_plugin.h new file mode 100644 index 000000000..134389b61 --- /dev/null +++ b/lib/dl_table_plugin.h @@ -0,0 +1,193 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_table_plugin.h + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-09-23. + +Revision History: + +--*/ +#ifndef _DL_TABLE_PLUGIN_H_ +#define _DL_TABLE_PLUGIN_H_ + +#include"ast.h" +#include"map.h" +#include"vector.h" + +#include"dl_table_ops.h" + +namespace datalog { + + /** + Termplate class containing common infrastructure for relations and tables + */ + template + struct tr_infrastructure { + + typedef typename Traits::base_object base_object; + typedef typename Traits::signature signature; + typedef typename Traits::element element; + typedef typename Traits::fact fact; + typedef typename Traits::kind kind; + + class base_fn { + public: + virtual ~base_fn() {} + }; + + class join_fn : public base_fn { + public: + virtual base_object * operator()(const base_object & t1, const base_object & t2); + }; + + class transformer_fn : public base_fn { + public: + virtual base_object * operator()(const base_object & t); + }; + + class union_fn : public base_fn { + public: + virtual void operator()(base_object & tgt, const base_object & src, base_object * delta); + }; + + class mutator_fn : public base_fn { + public: + virtual void operator()(base_object & t); + }; + + class negation_filter_fn : public base_fn { + public: + virtual void operator()(base_object & t, const base_object & negated_obj); + }; + + class plugin_object { + const kind m_kind; + protected: + plugin_object(kind k) : m_kind(k) {} + public: + kind get_kind(); + + virtual base_object * mk_empty(const signature & s) = 0; + + virtual join_fn * mk_join_fn(const table_base & t1, const table_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2) { + NOT_IMPLEMENTED_YET(); + } + + virtual transformer_fn * mk_project_fn(const base_object & t, unsigned col_cnt, + const unsigned * removed_cols) = 0 + + virtual transformer_fn * mk_rename_fn(const base_object & t, unsigned permutation_cycle_len, + const unsigned * permutation_cycle) = 0; + + virtual union_fn * mk_union_fn(base_object & tgt, const base_object & src, base_object * delta) = 0; + + virtual mutator_fn * mk_filter_identical_fn(base_object & t, unsigned col_cnt, + const unsigned * identical_cols) = 0; + + virtual mutator_fn * mk_filter_equal_fn(base_object & t, const element & value, + unsigned col) = 0; + + virtual mutator_fn * mk_filter_interpreted_fn(base_object & t, app * condition) = 0; + + virtual negation_filter_fn * mk_filter_interpreted_fn(base_object & t, + const base_object & negated_obj, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * negated_cols) = 0; + + }; + + class base_ancestor { + const kind m_kind; + protected: + relation_manager & m_manager; + signature m_signature; + + base_ancestor(kind k, relation_manager & m, const signature & s) + : m_kind(k), m_manager(m), m_signature(s) {} + public: + virtual ~base_ancestor() {} + + kind get_kind() const { return m_kind; } + relation_manager & get_manager() const { return m_manager; } + const signature & get_signature() const { return m_signature; } + + virtual bool empty() const = 0; + virtual void add_fact(const fact & f) = 0; + virtual bool contains_fact(const fact & f) const = 0; + + /** + \brief Return table that contains the same data as the current one. + */ + virtual base_object * clone() const; + + }; + }; + + + // ----------------------------------- + // + // relation_base + // + // ----------------------------------- + + class relation_base1; + + enum relation_kind { + RK_UNKNOWN, + RK_TABLE + }; + + struct relation_traits { + typedef relation_base1 base_object; + typedef relation_signature signature; + typedef app * element; + typedef ptr_vector fact; + typedef relation_kind kind; + }; + + typedef tr_infrastructure relation_infrastructure; + + typedef relation_infrastructure::plugin_object relation_plugin_base; + + class relation_base1 : public relation_infrastructure::base_ancestor { + + }; + + + // ----------------------------------- + // + // table_base + // + // ----------------------------------- + + class table_base1; + + struct table_traits { + typedef table_base1 base_object; + typedef table_signature signature; + typedef unsigned element; + typedef unsigned_vector fact; + typedef table_kind kind; + }; + + typedef tr_infrastructure table_infrastructure; + + typedef table_infrastructure::plugin_object table_plugin_base; + + class table_base1 : public table_infrastructure::base_ancestor { + + }; + +}; + +#endif /* _DL_TABLE_PLUGIN_H_ */ + diff --git a/lib/dl_table_relation.cpp b/lib/dl_table_relation.cpp new file mode 100644 index 000000000..fc661366d --- /dev/null +++ b/lib/dl_table_relation.cpp @@ -0,0 +1,475 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_table_relation.cpp + +Abstract: + + + +Author: + + Krystof Hoder (t-khoder) 2010-09-14. + +Revision History: + +--*/ + + +#include +#include"dl_context.h" +#include"dl_relation_manager.h" +#include"dl_table_relation.h" + + +namespace datalog { + + // ----------------------------------- + // + // table_relation_plugin + // + // ----------------------------------- + + symbol table_relation_plugin::create_plugin_name(const table_plugin &p) { + std::string name = std::string("tr_") + p.get_name().bare_str(); + return symbol(name.c_str()); + } + + bool table_relation_plugin::can_handle_signature(const relation_signature & s) { + table_signature tsig; + if(!get_manager().relation_signature_to_table(s, tsig)) { + return false; + } + return m_table_plugin.can_handle_signature(tsig); + } + + + relation_base * table_relation_plugin::mk_empty(const relation_signature & s) { + table_signature tsig; + if(!get_manager().relation_signature_to_table(s, tsig)) { + return 0; + } + table_base * t = m_table_plugin.mk_empty(tsig); + return alloc(table_relation, *this, s, t); + } + + relation_base * table_relation_plugin::mk_full(const relation_signature & s, func_decl* p, family_id kind) { + table_signature tsig; + if(!get_manager().relation_signature_to_table(s, tsig)) { + return 0; + } + table_base * t = m_table_plugin.mk_full(p, tsig, kind); + return alloc(table_relation, *this, s, t); + } + + relation_base * table_relation_plugin::mk_from_table(const relation_signature & s, table_base * t) { + if (&t->get_plugin() == &m_table_plugin) + return alloc(table_relation, *this, s, t); + table_relation_plugin& other = t->get_manager().get_table_relation_plugin(t->get_plugin()); + return alloc(table_relation, other, s, t); + } + + class table_relation_plugin::tr_join_project_fn : public convenient_relation_join_project_fn { + scoped_ptr m_tfun; + public: + tr_join_project_fn(const relation_signature & s1, const relation_signature & s2, unsigned col_cnt, + const unsigned * cols1, const unsigned * cols2, unsigned removed_col_cnt, + const unsigned * removed_cols, table_join_fn * tfun) + : convenient_relation_join_project_fn(s1, s2, col_cnt, cols1, cols2, removed_col_cnt, + removed_cols), m_tfun(tfun) {} + + virtual relation_base * operator()(const relation_base & t1, const relation_base & t2) { + SASSERT(t1.from_table()); + SASSERT(t2.from_table()); + table_relation_plugin & plugin = static_cast(t1.get_plugin()); + + const table_relation & tr1 = static_cast(t1); + const table_relation & tr2 = static_cast(t2); + + table_base * tres = (*m_tfun)(tr1.get_table(), tr2.get_table()); + + TRACE("dl_table_relation", tout << "# join => "; tres->display(tout);); + if(&tres->get_plugin()!=&plugin.m_table_plugin) { + //Operation returned a table of different type than the one which is associated with + //this plugin. We need to get a correct table_relation_plugin and create the relation + //using it. + return plugin.get_manager().get_table_relation_plugin(tres->get_plugin()) + .mk_from_table(get_result_signature(), tres); + } + return plugin.mk_from_table(get_result_signature(), tres); + } + }; + + relation_join_fn * table_relation_plugin::mk_join_fn(const relation_base & r1, const relation_base & r2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2) { + if(!r1.from_table() || !r2.from_table()) { + return 0; + } + const table_relation & tr1 = static_cast(r1); + const table_relation & tr2 = static_cast(r2); + + table_join_fn * tfun = get_manager().mk_join_fn(tr1.get_table(), tr2.get_table(), col_cnt, cols1, cols2); + if(!tfun) { + return 0; + } + + return alloc(tr_join_project_fn, r1.get_signature(), r2.get_signature(), col_cnt, cols1, + cols2, 0, static_cast(0), tfun); + } + + relation_join_fn * table_relation_plugin::mk_join_project_fn(const relation_base & r1, + const relation_base & r2, unsigned joined_col_cnt, const unsigned * cols1, const unsigned * cols2, + unsigned removed_col_cnt, const unsigned * removed_cols) { + if(!r1.from_table() || !r2.from_table()) { + return 0; + } + const table_relation & tr1 = static_cast(r1); + const table_relation & tr2 = static_cast(r2); + + table_join_fn * tfun = get_manager().mk_join_project_fn(tr1.get_table(), tr2.get_table(), joined_col_cnt, + cols1, cols2, removed_col_cnt, removed_cols); + SASSERT(tfun); + + return alloc(tr_join_project_fn, r1.get_signature(), r2.get_signature(), joined_col_cnt, cols1, + cols2, removed_col_cnt, removed_cols, tfun); + } + + + class table_relation_plugin::tr_transformer_fn : public convenient_relation_transformer_fn { + scoped_ptr m_tfun; + public: + tr_transformer_fn(const relation_signature & rsig, table_transformer_fn * tfun) + : m_tfun(tfun) { get_result_signature() = rsig; } + + virtual relation_base * operator()(const relation_base & t) { + SASSERT(t.from_table()); + table_relation_plugin & plugin = static_cast(t.get_plugin()); + + const table_relation & tr = static_cast(t); + + table_base * tres = (*m_tfun)(tr.get_table()); + + TRACE("dl_table_relation", tout << "# transform => "; tres->display(tout);); + if(&tres->get_plugin()!=&plugin.m_table_plugin) { + //Transformation returned a table of different type than the one which is associated with this plugin. + //We need to get a correct table_relation_plugin and create the relation using it. + return plugin.get_manager().get_table_relation_plugin(tres->get_plugin()) + .mk_from_table(get_result_signature(), tres); + } + return plugin.mk_from_table(get_result_signature(), tres); + } + }; + + relation_transformer_fn * table_relation_plugin::mk_project_fn(const relation_base & t, unsigned col_cnt, + const unsigned * removed_cols) { + if(!t.from_table()) { + return 0; + } + const table_relation & tr = static_cast(t); + + table_transformer_fn * tfun = get_manager().mk_project_fn(tr.get_table(), col_cnt, removed_cols); + SASSERT(tfun); + + relation_signature sig; + relation_signature::from_project(t.get_signature(), col_cnt, removed_cols, sig); + + return alloc(tr_transformer_fn, sig, tfun); + } + + relation_transformer_fn * table_relation_plugin::mk_rename_fn(const relation_base & t, unsigned permutation_cycle_len, + const unsigned * permutation_cycle) { + if(!t.from_table()) { + return 0; + } + const table_relation & tr = static_cast(t); + + table_transformer_fn * tfun = get_manager().mk_rename_fn(tr.get_table(), permutation_cycle_len, permutation_cycle); + SASSERT(tfun); + + relation_signature sig; + relation_signature::from_rename(t.get_signature(), permutation_cycle_len, permutation_cycle, sig); + + return alloc(tr_transformer_fn, sig, tfun); + } + + relation_transformer_fn * table_relation_plugin::mk_permutation_rename_fn(const relation_base & t, + const unsigned * permutation) { + if(!t.from_table()) { + return 0; + } + const table_relation & tr = static_cast(t); + + table_transformer_fn * tfun = get_manager().mk_permutation_rename_fn(tr.get_table(), permutation); + SASSERT(tfun); + + relation_signature sig; + relation_signature::from_permutation_rename(t.get_signature(), permutation, sig); + + return alloc(tr_transformer_fn, sig, tfun); + } + + relation_transformer_fn * table_relation_plugin::mk_select_equal_and_project_fn(const relation_base & t, + const relation_element & value, unsigned col) { + if(!t.from_table()) { + return 0; + } + const table_relation & tr = static_cast(t); + + table_element tvalue; + get_manager().relation_to_table(tr.get_signature()[col], value, tvalue); + + table_transformer_fn * tfun = get_manager().mk_select_equal_and_project_fn(tr.get_table(), tvalue, col); + SASSERT(tfun); + relation_signature res_sig; + relation_signature::from_project(t.get_signature(), 1, &col, res_sig); + return alloc(tr_transformer_fn, res_sig, tfun); + } + + /** + Union functor that can unite table relation into any other relation (using any delta relation) + by iterating through the table and calling \c add_fact of the target relation. + */ + class table_relation_plugin::universal_target_union_fn : public relation_union_fn { + virtual void operator()(relation_base & tgt, const relation_base & src, relation_base * delta) { + SASSERT(src.from_table()); + + const table_relation & tr_src = static_cast(src); + relation_manager & rmgr = tr_src.get_manager(); + relation_signature sig = tr_src.get_signature(); + SASSERT(tgt.get_signature()==sig); + SASSERT(!delta || delta->get_signature()==sig); + + table_base::iterator it = tr_src.get_table().begin(); + table_base::iterator end = tr_src.get_table().end(); + + table_fact tfact; + relation_fact rfact(rmgr.get_context()); + for (; it != end; ++it) { + it->get_fact(tfact); + rmgr.table_fact_to_relation(sig, tfact, rfact); + if(delta) { + if(!tgt.contains_fact(rfact)) { + tgt.add_new_fact(rfact); + delta->add_fact(rfact); + } + } + else { + tgt.add_fact(rfact); + } + } + TRACE("dl_table_relation", tout << "# universal union => "; tgt.display(tout);); + } + }; + + class table_relation_plugin::tr_union_fn : public relation_union_fn { + scoped_ptr m_tfun; + public: + tr_union_fn(table_union_fn * tfun) : m_tfun(tfun) {} + + virtual void operator()(relation_base & tgt, const relation_base & src, relation_base * delta) { + SASSERT(tgt.from_table()); + SASSERT(src.from_table()); + SASSERT(!delta || delta->from_table()); + + table_relation & tr_tgt = static_cast(tgt); + const table_relation & tr_src = static_cast(src); + table_relation * tr_delta = static_cast(delta); + + (*m_tfun)(tr_tgt.get_table(), tr_src.get_table(), tr_delta ? &tr_delta->get_table() : 0); + + TRACE("dl_table_relation", tout << "# union => "; tr_tgt.get_table().display(tout);); + } + }; + + relation_union_fn * table_relation_plugin::mk_union_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta) { + if(!src.from_table()) { + return 0; + } + if(!tgt.from_table() || (delta && !delta->from_table())) { + return alloc(universal_target_union_fn); + } + const table_relation & tr_tgt = static_cast(tgt); + const table_relation & tr_src = static_cast(src); + const table_relation * tr_delta = static_cast(delta); + + table_union_fn * tfun = get_manager().mk_union_fn(tr_tgt.get_table(), tr_src.get_table(), + tr_delta ? &tr_delta->get_table() : 0); + SASSERT(tfun); + + return alloc(tr_union_fn, tfun); + } + + + class table_relation_plugin::tr_mutator_fn : public relation_mutator_fn { + scoped_ptr m_tfun; + public: + tr_mutator_fn(table_mutator_fn * tfun) : m_tfun(tfun) {} + + virtual void operator()(relation_base & r) { + SASSERT(r.from_table()); + table_relation & tr = static_cast(r); + (*m_tfun)(tr.get_table()); + TRACE("dl_table_relation", tout << "# mutator => "; tr.get_table().display(tout);); + } + }; + + relation_mutator_fn * table_relation_plugin::mk_filter_identical_fn(const relation_base & t, unsigned col_cnt, + const unsigned * identical_cols) { + if(!t.from_table()) { + return 0; + } + const table_relation & tr = static_cast(t); + + table_mutator_fn * tfun = get_manager().mk_filter_identical_fn(tr.get_table(), col_cnt, identical_cols); + SASSERT(tfun); + return alloc(tr_mutator_fn, tfun); + } + + relation_mutator_fn * table_relation_plugin::mk_filter_equal_fn(const relation_base & t, const relation_element & value, + unsigned col) { + if(!t.from_table()) { + return 0; + } + const table_relation & tr = static_cast(t); + + table_element tvalue; + get_manager().relation_to_table(tr.get_signature()[col], value, tvalue); + + table_mutator_fn * tfun = get_manager().mk_filter_equal_fn(tr.get_table(), tvalue, col); + SASSERT(tfun); + return alloc(tr_mutator_fn, tfun); + } + + relation_mutator_fn * table_relation_plugin::mk_filter_interpreted_fn(const relation_base & t, app * condition) { + bool condition_needs_transforming = false; + if(!t.from_table() || condition_needs_transforming) { + return 0; + } + const table_relation & tr = static_cast(t); + table_mutator_fn * tfun = get_manager().mk_filter_interpreted_fn(tr.get_table(), condition); + SASSERT(tfun); + return alloc(tr_mutator_fn, tfun); + } + + class table_relation_plugin::tr_intersection_filter_fn : public relation_intersection_filter_fn { + scoped_ptr m_tfun; + public: + tr_intersection_filter_fn(table_intersection_filter_fn * tfun) : m_tfun(tfun) {} + + virtual void operator()(relation_base & r, const relation_base & src) { + SASSERT(r.from_table()); + SASSERT(src.from_table()); + + table_relation & tr = static_cast(r); + const table_relation & tr_src = static_cast(src); + + (*m_tfun)(tr.get_table(), tr_src.get_table()); + TRACE("dl_table_relation", tout << "# negation_filter => "; tr.get_table().display(tout);); + } + }; + + relation_intersection_filter_fn * table_relation_plugin::mk_filter_by_intersection_fn(const relation_base & r, + const relation_base & src, unsigned joined_col_cnt, const unsigned * r_cols, const unsigned * src_cols) { + if(!r.from_table() || !src.from_table()) { + return 0; + } + const table_relation & tr = static_cast(r); + const table_relation & tr_neg = static_cast(src); + table_intersection_filter_fn * tfun = get_manager().mk_filter_by_intersection_fn(tr.get_table(), + tr_neg.get_table(), joined_col_cnt, r_cols, src_cols); + if(!tfun) { + return 0; + } + + return alloc(tr_intersection_filter_fn, tfun); + } + + + relation_intersection_filter_fn * table_relation_plugin::mk_filter_by_negation_fn(const relation_base & r, + const relation_base & negated_rel, unsigned joined_col_cnt, + const unsigned * r_cols, const unsigned * negated_cols) { + if(!r.from_table() || !negated_rel.from_table()) { + return 0; + } + const table_relation & tr = static_cast(r); + const table_relation & tr_neg = static_cast(negated_rel); + table_intersection_filter_fn * tfun = get_manager().mk_filter_by_negation_fn(tr.get_table(), + tr_neg.get_table(), joined_col_cnt, r_cols, negated_cols); + SASSERT(tfun); + + return alloc(tr_intersection_filter_fn, tfun); + } + + + // ----------------------------------- + // + // table_relation + // + // ----------------------------------- + + void table_relation::add_table_fact(const table_fact & f) { + get_table().add_fact(f); + } + + void table_relation::add_fact(const relation_fact & f) { + SASSERT(f.size()==get_signature().size()); + table_fact vals; + get_manager().relation_fact_to_table(get_signature(), f, vals); + get_table().add_fact(vals); + TRACE("dl_table_relation", tout << "# add fact => "; get_table().display(tout);); + } + + bool table_relation::contains_fact(const relation_fact & f) const { + table_fact vals; + get_manager().relation_fact_to_table(get_signature(), f, vals); + return get_table().contains_fact(vals); + } + + relation_base * table_relation::clone() const { + table_base * tres = get_table().clone(); + return get_plugin().mk_from_table(get_signature(), tres); + } + + relation_base * table_relation::complement(func_decl* p) const { + table_base * tres = get_table().complement(p); + return get_plugin().mk_from_table(get_signature(), tres); + } + + void table_relation::display_tuples(func_decl & pred, std::ostream & out) const { + context & ctx = get_manager().get_context(); + unsigned arity = pred.get_arity(); + + out << "Tuples in " << pred.get_name() << ": \n"; + + table_base::iterator it = get_table().begin(); + table_base::iterator end = get_table().end(); + + table_fact fact; + for (; it != end; ++it) { + it->get_fact(fact); + + out << "\t("; + + for(unsigned i=0;i + +Author: + + Krystof Hoder (t-khoder) 2010-09-24. + +Revision History: + +--*/ +#ifndef _DL_TABLE_RELATION_H_ +#define _DL_TABLE_RELATION_H_ + + +#include "dl_base.h" +#include "dl_util.h" + +namespace datalog { + + class table_relation; + + class table_relation_plugin : public relation_plugin { + friend class table_relation; + + class tr_join_project_fn; + class tr_transformer_fn; + class universal_target_union_fn; + class tr_union_fn; + class tr_mutator_fn; + class tr_intersection_filter_fn; + + table_plugin & m_table_plugin; + + static symbol create_plugin_name(const table_plugin & p); + public: + table_relation_plugin(table_plugin & tp, relation_manager & manager) + : relation_plugin(create_plugin_name(tp), manager, ST_TABLE_RELATION), m_table_plugin(tp) {} + + table_plugin & get_table_plugin() { return m_table_plugin; } + + virtual bool can_handle_signature(const relation_signature & s); + + virtual relation_base * mk_empty(const relation_signature & s); + virtual relation_base * mk_full(const relation_signature & s, func_decl* p, family_id kind); + relation_base * mk_from_table(const relation_signature & s, table_base * t); + + protected: + virtual relation_join_fn * mk_join_fn(const relation_base & t1, const relation_base & t2, + unsigned col_cnt, const unsigned * cols1, const unsigned * cols2); + virtual relation_join_fn * mk_join_project_fn(const relation_base & t1, const relation_base & t2, + unsigned joined_col_cnt, const unsigned * cols1, const unsigned * cols2, unsigned removed_col_cnt, + const unsigned * removed_cols); + virtual relation_transformer_fn * mk_project_fn(const relation_base & t, unsigned col_cnt, + const unsigned * removed_cols); + virtual relation_transformer_fn * mk_rename_fn(const relation_base & t, unsigned permutation_cycle_len, + const unsigned * permutation_cycle); + virtual relation_transformer_fn * mk_permutation_rename_fn(const relation_base & t, + const unsigned * permutation); + virtual relation_union_fn * mk_union_fn(const relation_base & tgt, const relation_base & src, + const relation_base * delta); + virtual relation_mutator_fn * mk_filter_identical_fn(const relation_base & t, unsigned col_cnt, + const unsigned * identical_cols); + virtual relation_mutator_fn * mk_filter_equal_fn(const relation_base & t, const relation_element & value, + unsigned col); + virtual relation_mutator_fn * mk_filter_interpreted_fn(const relation_base & t, app * condition); + virtual relation_intersection_filter_fn * mk_filter_by_intersection_fn(const relation_base & t, + const relation_base & src, unsigned joined_col_cnt, const unsigned * t_cols, const unsigned * src_cols); + virtual relation_intersection_filter_fn * mk_filter_by_negation_fn(const relation_base & t, + const relation_base & negated_obj, unsigned joined_col_cnt, + const unsigned * t_cols, const unsigned * negated_cols); + virtual relation_transformer_fn * mk_select_equal_and_project_fn(const relation_base & t, + const relation_element & value, unsigned col); + }; + + class table_relation : public relation_base { + friend class table_relation_plugin; + friend class table_relation_plugin::tr_join_project_fn; + friend class table_relation_plugin::tr_transformer_fn; + + scoped_rel m_table; + + /** + \brief Create a \c table_relation object. + + The newly created object takes ownership of the \c table object. + */ + table_relation(table_relation_plugin & p, const relation_signature & s, table_base * table) + : relation_base(p, s), m_table(table) { + SASSERT(s.size()==table->get_signature().size()); + } + public: + + table_relation_plugin & get_plugin() const { + return static_cast(relation_base::get_plugin()); + } + + table_base & get_table() { return *m_table; } + const table_base & get_table() const { return *m_table; } + + virtual bool empty() const { return m_table->empty(); } + + void add_table_fact(const table_fact & f); + + virtual void add_fact(const relation_fact & f); + virtual bool contains_fact(const relation_fact & f) const; + virtual relation_base * clone() const; + virtual relation_base * complement(func_decl* p) const; + virtual void to_formula(expr_ref& fml) const { get_table().to_formula(get_signature(), fml); } + + virtual void display(std::ostream & out) const { + get_table().display(out); + } + virtual void display_tuples(func_decl & pred, std::ostream & out) const; + + virtual unsigned get_size_estimate_rows() const { return m_table->get_size_estimate_rows(); } + virtual unsigned get_size_estimate_bytes() const { return m_table->get_size_estimate_bytes(); } + virtual bool knows_exact_size() const { return m_table->knows_exact_size(); } + }; + +}; + +#endif /* _DL_TABLE_RELATION_H_ */ + diff --git a/lib/dl_util.cpp b/lib/dl_util.cpp new file mode 100644 index 000000000..304e6de68 --- /dev/null +++ b/lib/dl_util.cpp @@ -0,0 +1,953 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dl_util.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-05-20. + +Revision History: + +--*/ + +#include +#include +#include +#ifdef _WINDOWS +#include +#endif +#include"ast_pp.h" +#include"bool_rewriter.h" +#include"dl_context.h" +#include"dl_rule.h" +#include"for_each_expr.h" +#include"dl_util.h" + +namespace datalog { + + void universal_delete(relation_base* ptr) { + ptr->deallocate(); + } + + void universal_delete(table_base* ptr) { + ptr->deallocate(); + } + + void flatten_and(expr_ref_vector& result) { + ast_manager& m = result.get_manager(); + expr* e1, *e2, *e3; + for (unsigned i = 0; i < result.size(); ++i) { + if (m.is_and(result[i].get())) { + app* a = to_app(result[i].get()); + unsigned num_args = a->get_num_args(); + for (unsigned j = 0; j < num_args; ++j) { + result.push_back(a->get_arg(j)); + } + result[i] = result.back(); + result.pop_back(); + --i; + } + else if (m.is_not(result[i].get(), e1) && m.is_not(e1, e2)) { + result[i] = e2; + --i; + } + else if (m.is_not(result[i].get(), e1) && m.is_or(e1)) { + app* a = to_app(e1); + unsigned num_args = a->get_num_args(); + for (unsigned j = 0; j < num_args; ++j) { + result.push_back(m.mk_not(a->get_arg(j))); + } + result[i] = result.back(); + result.pop_back(); + --i; + } + else if (m.is_not(result[i].get(), e1) && m.is_implies(e1,e2,e3)) { + result.push_back(e2); + result[i] = m.mk_not(e3); + --i; + } + else if (m.is_true(result[i].get()) || + (m.is_not(result[i].get(), e1) && + m.is_false(e1))) { + result[i] = result.back(); + result.pop_back(); + --i; + } + else if (m.is_false(result[i].get()) || + (m.is_not(result[i].get(), e1) && + m.is_true(e1))) { + result.reset(); + result.push_back(m.mk_false()); + return; + } + } + } + + void flatten_and(expr* fml, expr_ref_vector& result) { + SASSERT(result.get_manager().is_bool(fml)); + result.push_back(fml); + flatten_and(result); + } + + void flatten_or(expr_ref_vector& result) { + ast_manager& m = result.get_manager(); + expr* e1, *e2, *e3; + for (unsigned i = 0; i < result.size(); ++i) { + if (m.is_or(result[i].get())) { + app* a = to_app(result[i].get()); + unsigned num_args = a->get_num_args(); + for (unsigned j = 0; j < num_args; ++j) { + result.push_back(a->get_arg(j)); + } + result[i] = result.back(); + result.pop_back(); + --i; + } + else if (m.is_not(result[i].get(), e1) && m.is_not(e1, e2)) { + result[i] = e2; + --i; + } + else if (m.is_not(result[i].get(), e1) && m.is_and(e1)) { + app* a = to_app(e1); + unsigned num_args = a->get_num_args(); + for (unsigned j = 0; j < num_args; ++j) { + result.push_back(m.mk_not(a->get_arg(j))); + } + result[i] = result.back(); + result.pop_back(); + --i; + } + else if (m.is_implies(result[i].get(),e2,e3)) { + result.push_back(e3); + result[i] = m.mk_not(e2); + --i; + } + else if (m.is_false(result[i].get()) || + (m.is_not(result[i].get(), e1) && + m.is_true(e1))) { + result[i] = result.back(); + result.pop_back(); + --i; + } + else if (m.is_true(result[i].get()) || + (m.is_not(result[i].get(), e1) && + m.is_false(e1))) { + result.reset(); + result.push_back(m.mk_true()); + return; + } + } + } + + + void flatten_or(expr* fml, expr_ref_vector& result) { + SASSERT(result.get_manager().is_bool(fml)); + result.push_back(fml); + flatten_or(result); + } + + bool push_toplevel_junction_negation_inside(expr_ref& e) + { + ast_manager& m = e.get_manager(); + bool_rewriter brwr(m); + + expr * arg; + if(!m.is_not(e, arg)) { return false; } + bool is_and = m.is_and(arg); + if(!is_and && !m.is_or(arg)) { return false; } + + //now we know we have formula we need to transform + app * junction = to_app(arg); + expr_ref_vector neg_j_args(m); + unsigned num_args = junction->get_num_args(); + for(unsigned i=0; iget_arg(i), neg_j_arg); + neg_j_args.push_back(neg_j_arg); + } + if(is_and) { + brwr.mk_or(neg_j_args.size(), neg_j_args.c_ptr(), e); + } + else { + brwr.mk_and(neg_j_args.size(), neg_j_args.c_ptr(), e); + } + return true; + } + + + bool contains_var(expr * trm, unsigned var_idx) { + ptr_vector vars; + ::get_free_vars(trm, vars); + return var_idx < vars.size() && vars[var_idx] != 0; + } + + + void collect_vars(ast_manager & m, expr * e, var_idx_set & result) { + ptr_vector vars; + ::get_free_vars(e, vars); + unsigned sz = vars.size(); + for(unsigned i=0; iget_tail_size(); + for(unsigned i=0;iget_tail(i), result); + } + } + + void get_free_tail_vars(rule * r, ptr_vector& sorts) { + unsigned n = r->get_tail_size(); + for(unsigned i=0;iget_tail(i), sorts); + } + } + + void get_free_vars(rule * r, ptr_vector& sorts) { + get_free_vars(r->get_head(), sorts); + get_free_tail_vars(r, sorts); + } + + unsigned count_variable_arguments(app * pred) + { + SASSERT(is_uninterp(pred)); + unsigned res = 0; + unsigned n = pred->get_num_args(); + for (unsigned i = 0; i < n; i++) { + expr * arg = pred->get_arg(i); + if (is_var(arg)) { + res++; + } + } + return res; + } + + void collect_non_local_vars(ast_manager & m, rule const * r, app * t, var_idx_set & result) { + collect_vars(m, r->get_head(), result); + unsigned sz = r->get_tail_size(); + for (unsigned i = 0; i < sz; i++) { + app * curr = r->get_tail(i); + if (curr != t) + collect_vars(m, curr, result); + } + } + + void collect_non_local_vars(ast_manager & m, rule const * r, app * t_1, app * t_2, var_idx_set & result) { + collect_vars(m, r->get_head(), result); + unsigned sz = r->get_tail_size(); + for (unsigned i = 0; i < sz; i++) { + app * curr = r->get_tail(i); + if (curr != t_1 && curr != t_2) + collect_vars(m, curr, result); + } + } + + void mk_new_rule_tail(ast_manager & m, app * pred, var_idx_set const & non_local_vars, unsigned & next_idx, varidx2var_map & varidx2var, + sort_ref_buffer & new_rule_domain, expr_ref_buffer & new_rule_args, app_ref & new_pred) { + expr_ref_buffer new_args(m); + unsigned n = pred->get_num_args(); + for (unsigned i = 0; i < n; i++) { + expr * arg = pred->get_arg(i); + if (m.is_value(arg)) { + new_args.push_back(arg); + } + else { + SASSERT(is_var(arg)); + int vidx = to_var(arg)->get_idx(); + var * new_var = 0; + if (!varidx2var.find(vidx, new_var)) { + new_var = m.mk_var(next_idx, to_var(arg)->get_sort()); + next_idx++; + varidx2var.insert(vidx, new_var); + if (non_local_vars.contains(vidx)) { + // other predicates used this variable... so it should be in the domain of the filter + new_rule_domain.push_back(to_var(arg)->get_sort()); + new_rule_args.push_back(new_var); + } + } + SASSERT(new_var != 0); + new_args.push_back(new_var); + } + } + new_pred = m.mk_app(pred->get_decl(), new_args.size(), new_args.c_ptr()); + } + + void apply_subst(expr_ref_vector& tgt, expr_ref_vector const& sub) { + ast_manager& m = tgt.get_manager(); + var_subst vs(m, false); + expr_ref tmp(m); + for (unsigned i = 0; i < tgt.size(); ++i) { + if (tgt[i].get()) { + vs(tgt[i].get(), sub.size(), sub.c_ptr(), tmp); + tgt[i] = tmp; + } + else { + tgt[i] = sub[i]; + } + } + for (unsigned i = tgt.size(); i < sub.size(); ++i) { + tgt.push_back(sub[i]); + } + } + + + void output_predicate(context & ctx, app * f, std::ostream & out) + { + func_decl * pred_decl = f->get_decl(); + unsigned arity = f->get_num_args(); + + out << pred_decl->get_name() << '('; + + for (unsigned i = 0; i < arity; i++) { + expr * arg = f->get_arg(i); + if (i != 0) { + out << ','; + } + if (is_var(arg)) { + out << "#" << to_var(arg)->get_idx(); + } + else { + out << mk_pp(arg, ctx.get_manager()); + } + } + out << ")"; + } + + void display_predicate(context & ctx, app * f, std::ostream & out) + { + output_predicate(ctx, f, out); + out << "\n"; + } + + void display_fact(context & ctx, app * f, std::ostream & out) + { + func_decl * pred_decl = f->get_decl(); + unsigned arity = f->get_num_args(); + + out << "\t("; + + for(unsigned i = 0; i < arity; i++) { + if (i != 0) { + out << ','; + } + + expr * arg = f->get_arg(i); + uint64 sym_num; + SASSERT(is_app(arg)); + VERIFY( ctx.get_decl_util().is_numeral_ext(to_app(arg), sym_num) ); + relation_sort sort = pred_decl->get_domain(i); + out << ctx.get_argument_name(pred_decl, i) << '='; + ctx.print_constant_name(sort, sym_num, out); + out << '(' << sym_num << ')'; + } + out << ")\n"; + } + + void idx_set_union(idx_set & tgt, const idx_set & src) { + idx_set::iterator vit = src.begin(); + idx_set::iterator vend = src.end(); + for(;vit!=vend;++vit) { + tgt.insert(*vit); + } + } + + + bool variable_intersection::values_match(const expr * v1, const expr * v2) + { + //return !m_manager.are_distinct(v1, v2); + return v1==v2; + } + + bool variable_intersection::args_match(const app * f1, const app * f2) + { + unsigned n=size(); + for (unsigned i = 0; i < n; i++) { + unsigned f1_index, f2_index; + get(i, f1_index, f2_index); + if (!values_match(f1->get_arg(f1_index),f2->get_arg(f2_index))) { + return false; + } + } + return true; + } + + bool variable_intersection::args_self_match(const app * f) + { + if(!args_match(f,f)) { + return false; + } + + unsigned n = m_const_indexes.size(); + for(unsigned i=0; iget_arg(f_index), m_consts[i].get())) { + return false; + } + } + return true; + } + + void variable_intersection::populate_self(const app * a) + { + SASSERT(is_uninterp(a)); + + //TODO: optimize quadratic complexity + //TODO: optimize number of checks when variable occurs multiple times + unsigned arity = a->get_num_args(); + for(unsigned i1=0; i1get_arg(i1); + if(is_var(e1)) { + var* v1=to_var(e1); + for(unsigned i2=i1+1; i2get_arg(i2); + if(!is_var(e2)) { + continue; + } + var* v2=to_var(e2); + if(v1->get_idx()==v2->get_idx()) { + add_pair(i1, i2); + } + } + } + else { + SASSERT(is_app(e1)); + app * c1 = to_app(e1); + SASSERT(c1->get_num_args()==0); //c1 must be a constant + + m_const_indexes.push_back(i1); + m_consts.push_back(c1); + + SASSERT(m_const_indexes.size()==m_consts.size()); + } + } + } + + void counter::update(unsigned el, int delta) { + int & counter = get(el); + SASSERT(!m_stay_non_negative || counter>=0); + SASSERT(!m_stay_non_negative || static_cast(counter)>=-delta); + counter += delta; + } + + int & counter::get(unsigned el) { + return m_data.insert_if_not_there2(el, 0)->get_data().m_value; + } + + counter & counter::count(unsigned sz, const unsigned * els, int delta) { + for(unsigned i=0; im_value>0 ) { + cnt++; + } + } + return cnt; + } + + void counter::collect_positive(idx_set & acc) const { + iterator eit = begin(); + iterator eend = end(); + for(; eit!=eend; ++eit) { + if(eit->m_value>0) { acc.insert(eit->m_key); } + } + } + + bool counter::get_max_positive(unsigned & res) const { + bool found = false; + iterator eit = begin(); + iterator eend = end(); + for(; eit!=eend; ++eit) { + if( eit->m_value>0 && (!found || eit->m_key>res) ) { + found = true; + res = eit->m_key; + } + } + return found; + } + + unsigned counter::get_max_positive() const { + unsigned max_pos; + VERIFY(get_max_positive(max_pos)); + return max_pos; + } + + int counter::get_max_counter_value() const { + int res = 0; + iterator eit = begin(); + iterator eend = end(); + for (; eit!=eend; ++eit) { + if( eit->m_value>res ) { + res = eit->m_value; + } + } + return res; + } + + void var_counter::count_vars(ast_manager & m, const app * pred, int coef) { + unsigned n = pred->get_num_args(); + for (unsigned i = 0; i < n; i++) { + m_sorts.reset(); + ::get_free_vars(pred->get_arg(i), m_sorts); + for (unsigned j = 0; j < m_sorts.size(); ++j) { + if (m_sorts[j]) { + update(j, coef); + } + } + } + } + + void var_counter::count_vars(ast_manager & m, const rule * r, int coef) { + count_vars(m, r->get_head(), 1); + unsigned n = r->get_tail_size(); + for (unsigned i = 0; i < n; i++) { + count_vars(m, r->get_tail(i), coef); + } + } + + unsigned var_counter::get_max_var(bool& has_var) { + has_var = false; + unsigned max_var = 0; + while (!m_todo.empty()) { + expr* e = m_todo.back(); + unsigned scope = m_scopes.back(); + m_todo.pop_back(); + m_scopes.pop_back(); + if (m_visited.is_marked(e)) { + continue; + } + m_visited.mark(e, true); + switch(e->get_kind()) { + case AST_QUANTIFIER: { + quantifier* q = to_quantifier(e); + m_todo.push_back(q->get_expr()); + m_scopes.push_back(scope + q->get_num_decls()); + break; + } + case AST_VAR: { + if (to_var(e)->get_idx() >= scope + max_var) { + has_var = true; + max_var = to_var(e)->get_idx() - scope; + } + break; + } + case AST_APP: { + app* a = to_app(e); + for (unsigned i = 0; i < a->get_num_args(); ++i) { + m_todo.push_back(a->get_arg(i)); + m_scopes.push_back(scope); + } + break; + } + default: + UNREACHABLE(); + break; + } + } + m_visited.reset(); + return max_var; + } + + unsigned var_counter::get_max_var(const rule & r) { + m_todo.push_back(r.get_head()); + m_scopes.push_back(0); + unsigned n = r.get_tail_size(); + bool has_var = false; + for (unsigned i = 0; i < n; i++) { + m_todo.push_back(r.get_tail(i)); + m_scopes.push_back(0); + } + return get_max_var(has_var); + } + + unsigned var_counter::get_max_var(expr* e) { + bool has_var = false; + m_todo.push_back(e); + m_scopes.push_back(0); + return get_max_var(has_var); + } + + unsigned var_counter::get_next_var(expr* e) { + bool has_var = false; + m_todo.push_back(e); + m_scopes.push_back(0); + unsigned mv = get_max_var(has_var); + if (has_var) mv++; + return mv; + } + + void del_rule(horn_subsume_model_converter* mc, rule& r) { + if (mc) { + app* head = r.get_head(); + ast_manager& m = mc->get_manager(); + expr_ref_vector body(m); + for (unsigned i = 0; i < r.get_tail_size(); ++i) { + if (r.is_neg_tail(i)) { + body.push_back(m.mk_not(r.get_tail(i))); + } + else { + body.push_back(r.get_tail(i)); + } + } + mc->insert(r.get_head(), body.size(), body.c_ptr()); + } + } + + void resolve_rule(replace_proof_converter* pc, rule& r1, rule& r2, unsigned idx, + expr_ref_vector const& s1, expr_ref_vector const& s2, rule& res) { + if (!pc) return; + ast_manager& m = s1.get_manager(); + dl_decl_util util(m); + expr_ref fml1(m), fml2(m), fml3(m); + r1.to_formula(fml1); + r2.to_formula(fml2); + res.to_formula(fml3); + vector substs; + svector > positions; + substs.push_back(s1); + substs.push_back(s2); + + scoped_coarse_proof _sc(m); + proof_ref pr(m); + proof_ref_vector premises(m); + premises.push_back(m.mk_asserted(fml1)); + premises.push_back(m.mk_asserted(fml2)); + positions.push_back(std::make_pair(idx+1, 0)); + + TRACE("dl", + tout << premises[0]->get_id() << " " << mk_pp(premises[0].get(), m) << "\n"; + tout << premises[1]->get_id() << " " << mk_pp(premises[1].get(), m) << "\n";); + + pr = util.mk_hyper_resolve(2, premises.c_ptr(), fml3, positions, substs); + pc->insert(pr); + } + + class skip_model_converter : public model_converter { + public: + skip_model_converter() {} + + virtual model_converter * translate(ast_translation & translator) { + return alloc(skip_model_converter); + } + + }; + + model_converter* mk_skip_model_converter() { return alloc(skip_model_converter); } + + class skip_proof_converter : public proof_converter { + virtual void operator()(ast_manager & m, unsigned num_source, proof * const * source, proof_ref & result) { + SASSERT(num_source == 1); + result = source[0]; + } + + virtual proof_converter * translate(ast_translation & translator) { + return alloc(skip_proof_converter); + } + + }; + + proof_converter* mk_skip_proof_converter() { return alloc(skip_proof_converter); } + + + unsigned get_max_var(const rule & r, ast_manager & m) { + var_counter ctr; + return ctr.get_max_var(r); + } + + void reverse_renaming(ast_manager & m, const expr_ref_vector & src, expr_ref_vector & tgt) { + SASSERT(tgt.empty()); + unsigned src_sz = src.size(); + unsigned src_ofs = src_sz-1; + + unsigned max_var_idx = 0; + for(unsigned i=0; iget_idx(); + if(var_idx>max_var_idx) { + max_var_idx=var_idx; + } + } + + unsigned tgt_sz = max_var_idx+1; + unsigned tgt_ofs = tgt_sz-1; + tgt.resize(tgt_sz, 0); + for(unsigned i=0; iget_idx(); + tgt[tgt_ofs-var_idx] = m.mk_var(i, v->get_sort()); + } + } + + void get_renaming_args(const unsigned_vector & map, const relation_signature & orig_sig, + expr_ref_vector & renaming_arg) { + ast_manager & m = renaming_arg.get_manager(); + unsigned sz = map.size(); + unsigned ofs = sz-1; + renaming_arg.resize(sz, static_cast(0)); + for(unsigned i=0; i=0; i--) { + out << (len-1-i) <<"->"; + if(cont.get(i)==0) { + out << "{none}"; + } + else { + out << to_var(cont.get(i))->get_idx(); + } + if(i!=0) { out << ","; } + } + out << ")\n"; + } + + void cycle_from_permutation(unsigned_vector & permutation, unsigned_vector & cycle) { + try_remove_cycle_from_permutation(permutation, cycle); + DEBUG_CODE( + //here we assert that there is at most one cycle in the permutation + unsigned_vector aux; + SASSERT(!try_remove_cycle_from_permutation(permutation, aux)); + ); + } + + bool try_remove_cycle_from_permutation(unsigned_vector & permutation, unsigned_vector & cycle) { + SASSERT(cycle.empty()); + DEBUG_CODE( + counter ctr; + ctr.count(permutation); + SASSERT(permutation.empty() || ctr.get_max_positive()==permutation.size()-1); + SASSERT(permutation.empty() || ctr.get_positive_count()==permutation.size()); + ); + unsigned sz = permutation.size(); + for(unsigned i=0; i & v) { + ptr_vector::iterator it = v.begin(); + ptr_vector::iterator end = v.end(); + for(; it!=end; ++it) { + (*it)->deallocate(); + } + } + + + // ----------------------------------- + // + // misc helper functions (not datalog related) + // + // ----------------------------------- + + void get_file_names(std::string directory, std::string extension, bool traverse_subdirs, + string_vector & res) { + + if(directory[directory.size()-1]!='\\' && directory[directory.size()-1]!='/') { +#ifdef _WINDOWS + directory+='\\'; +#else + directory+='/'; +#endif + } + +#ifdef _WINDOWS + WIN32_FIND_DATAA findFileData; + HANDLE hFind; + std::string filePattern = directory+"*."+extension; + + hFind = FindFirstFileA(filePattern.c_str(), &findFileData); + if (hFind != INVALID_HANDLE_VALUE) { + do { + char const* name = findFileData.cFileName; + size_t len = strlen(name); + if (len > extension.size() && extension == std::string(name+len-extension.size())) { + res.push_back(directory+std::string(name)); + } + } while(FindNextFileA(hFind, &findFileData)); + FindClose(hFind); + } + + + if(traverse_subdirs) { + std::string subdirPattern = directory+"*.*"; + hFind = FindFirstFileA(subdirPattern.c_str(), &findFileData); + if (hFind != INVALID_HANDLE_VALUE) { + do { + if(findFileData.cFileName[0]=='.') { + continue; + } + get_file_names(directory+findFileData.cFileName, extension, traverse_subdirs, res); + } while(FindNextFileA(hFind, &findFileData)); + FindClose(hFind); + } + } + +#else + NOT_IMPLEMENTED_YET(); +#endif + } + + bool file_exists(std::string name) { + struct stat st; + if(stat(name.c_str(),&st) == 0) { + return true; + } + return false; + } + + bool is_directory(std::string name) { + if(!file_exists(name)) { + return false; + } + struct stat status; + stat(name.c_str(), &status); + return (status.st_mode&S_IFDIR)!=0; + } + + std::string get_file_name_without_extension(std::string name) { + size_t slash_index = name.find_last_of("\\/"); + size_t dot_index = name.rfind("."); + size_t ofs = (slash_index==std::string::npos) ? 0 : slash_index+1; + size_t count = (dot_index!=std::string::npos && dot_index>ofs) ? + (dot_index-ofs) : std::string::npos; + return name.substr(ofs, count); + } + + bool string_to_uint64(const char * s, uint64 & res) { +#if _WINDOWS + int converted = sscanf_s(s, "%I64u", &res); +#else + int converted = sscanf(s, "%llu", &res); +#endif + if(converted==0) { + return false; + } + SASSERT(converted==1); + return true; + } + + bool read_uint64(const char * & s, uint64 & res) { + static const uint64 max_but_one_digit = ULLONG_MAX/10; + static const uint64 max_but_one_digit_safe = (ULLONG_MAX-9)/10; + + if(*s<'0' || *s>'9') { + return false; + } + res=*s-'0'; + s++; + while(*s>='0' && *s<='9') { + if(res>max_but_one_digit_safe) { + if(res>max_but_one_digit) { + return false; //overflow + } + res*=10; + char digit = *s-'0'; + if(static_cast(ULLONG_MAX-res)-digit<0) { + return false; //overflow + } + res+=digit; + } + else { + res*=10; + res+=*s-'0'; + s++; + } + } + return true; + } + + std::string to_string(uint64 num) { + std::stringstream stm; + stm<(s.length()), 17); } + }; + + // typedef int_hashtable > idx_set; + typedef uint_set idx_set; + typedef idx_set var_idx_set; + typedef u_map varidx2var_map; + typedef obj_hashtable func_decl_set; //!< Rule dependencies. + typedef vector string_vector; + + + /** + \brief Collect top-level conjunctions and disjunctions. + */ + void flatten_and(expr_ref_vector& result); + + void flatten_and(expr* fml, expr_ref_vector& result); + + void flatten_or(expr_ref_vector& result); + + void flatten_or(expr* fml, expr_ref_vector& result); + + /** + Transform + ~(a1 | ... | aN) + into + ~a1 | ... | ~aN + and + ~(a1 & ... & aN) + into + ~a1 | ... | ~aN + + Return true if something was done. + */ + bool push_toplevel_junction_negation_inside(expr_ref& e); + + + bool contains_var(expr * trm, unsigned var_idx); + + /** + \brief Collect the variables in \c pred. + \pre \c pred must be a valid head or tail. + */ + void collect_vars(ast_manager & m, expr * pred, var_idx_set & result); + void collect_tail_vars(ast_manager & m, rule * r, var_idx_set & result); + + void get_free_vars(rule * r, ptr_vector& sorts); + + /** + \brief Return number of arguments of \c pred that are variables + */ + unsigned count_variable_arguments(app * pred); + + /** + \brief Store in \c result the set of variables used by \c r when ignoring the tail \c t. + */ + void collect_non_local_vars(ast_manager & m, rule const * r, app * t, var_idx_set & result); + + /** + \brief Store in \c result the set of variables used by \c r when ignoring the tail elements \c t_1 and \c t_2. + */ + void collect_non_local_vars(ast_manager & m, rule const * r, app * t_1, app * t_2, var_idx_set & result); + + template + void copy_nonvariables(app * src, T& tgt) + { + unsigned n = src->get_num_args(); + for (unsigned i = 0; i < n; i++) { + expr * arg = src->get_arg(i); + if (!is_var(arg)) { + tgt[i]=arg; + } + } + } + + /** + \brief Auxiliary function used to create a tail based on \c pred for a new rule. + The variables in \c pred are re-assigned using \c next_idx and \c varidx2var. + A variable is considered non-local to the rule if it is in the set \c non_local_vars. + Non-local variables are coppied to new_rule_args, and their sorts to \c new_rule_domain. + The new predicate is stored in \c new_pred. + */ + void mk_new_rule_tail(ast_manager & m, app * pred, + var_idx_set const & non_local_vars, + unsigned & next_idx, varidx2var_map & varidx2var, + sort_ref_buffer & new_rule_domain, expr_ref_buffer & new_rule_args, + app_ref & new_pred); + + /** + \brief Simpler version of the previous function. Initializes next_idx with 0, and + an empty varid2var + */ + inline void mk_new_rule_tail(ast_manager & m, app * pred, + var_idx_set const & non_local_vars, + sort_ref_buffer & new_rule_domain, expr_ref_buffer & new_rule_args, + app_ref & new_pred) { + unsigned next_idx = 0; + varidx2var_map varidx2var; + mk_new_rule_tail(m, pred, non_local_vars, next_idx, varidx2var, new_rule_domain, new_rule_args, new_pred); + } + + /** + \brief Print a predicate \c f to the stream \c out. + */ + void display_predicate(context & ctx, app * f, std::ostream & out); + + /** + \brief Like \c display_predicate, just without the final '\n' character. + */ + void output_predicate(context & ctx, app * f, std::ostream & out); + + /** + \brief Print a fact \c f to the stream \c out in a format conforming to Bddbddb. + */ + void display_fact(context & ctx, app * f, std::ostream & out); + + class scoped_coarse_proof { + ast_manager& m; + proof_gen_mode m_mode; + public: + scoped_coarse_proof(ast_manager& m): m(m) { + m_mode = m.proof_mode(); + m.toggle_proof_mode(PGM_COARSE); + } + ~scoped_coarse_proof() { + m.toggle_proof_mode(m_mode); + } + }; + + class variable_intersection + { + bool values_match(const expr * v1, const expr * v2); + + ast_manager & m_manager; + + unsigned_vector m_args1; + unsigned_vector m_args2; + + unsigned_vector m_const_indexes; + app_ref_vector m_consts; + + static unsigned expr_cont_get_size(app * a) { return a->get_num_args(); } + static expr * expr_cont_get(app * a, unsigned i) { return a->get_arg(i); } + static unsigned expr_cont_get_size(const ptr_vector & v) { return v.size(); } + static expr * expr_cont_get(const ptr_vector & v, unsigned i) { return v[i]; } + public: + variable_intersection(ast_manager & m) : m_manager(m), m_consts(m) {} + + unsigned size() const { + return m_args1.size(); + } + + const unsigned * get_cols1() const { + return m_args1.c_ptr(); + } + + const unsigned * get_cols2() const { + return m_args2.c_ptr(); + } + + bool empty() const { + return size()==0; + } + + void get(unsigned i, unsigned & index1, unsigned & index2) const { + index1=m_args1[i]; + index2=m_args2[i]; + } + + void reset() { + m_args1.reset(); + m_args2.reset(); + m_const_indexes.reset(); + m_consts.reset(); + } + + bool args_match(const app * f1, const app * f2); + bool args_self_match(const app * f); + + /** + \brief Fill arguments of \c f1 into corresponding positions in + \c tgt using its \c operator[]. + */ + template + void fill_into_second(const app * f1, T & tgt) const { + unsigned n=size(); + for(unsigned i=0; iget_arg(f1_index); + } + } + + void add_pair(unsigned idx1, unsigned idx2) { + m_args1.push_back(idx1); + m_args2.push_back(idx2); + } + + /** + Find pairs of indexes of arguments of \c a1 and \c a2 that correspond to the same + variable. Here we do not detect the constant arguments in \c a1 and \c a2. + */ + template + void populate(const T1 & a1, const T2 & a2) + { + //TODO: optimize quadratic complexity + //TODO: optimize number of checks when variable occurs multiple times + unsigned a1num = expr_cont_get_size(a1); + unsigned a2num = expr_cont_get_size(a2); + for(unsigned i1=0; i1get_idx()==v2->get_idx()) { + add_pair(i1, i2); + } + } + } + } + + /** + Find pairs of indexes of arguments of \c a that correspond to the same variable + and indexes that correspond to a constant. + */ + void populate_self(const app * a); + }; + + template + void project_out_vector_columns(T & container, unsigned removed_col_cnt, const unsigned * removed_cols) { + if(removed_col_cnt==0) { + return; + } + unsigned n = container.size(); + unsigned ofs = 1; + unsigned r_i = 1; + for(unsigned i=removed_cols[0]+1; i + void project_out_vector_columns(ref_vector & container, unsigned removed_col_cnt, + const unsigned * removed_cols) { + if(removed_col_cnt==0) { + return; + } + unsigned n = container.size(); + unsigned ofs = 1; + int r_i = 1; + for(unsigned i=removed_cols[0]+1; i + void project_out_vector_columns(T & container, const unsigned_vector removed_cols) { + project_out_vector_columns(container, removed_cols.size(), removed_cols.c_ptr()); + } + + + + /** + \brief Take a single cycle permutation and store it in the form of a cycle. + + The function modifies the \c permutation vector + */ + void cycle_from_permutation(unsigned_vector & permutation, unsigned_vector & cycle); + + + /** + \brief If \c permutation is an identity, return false. Otherwise remove one cycle from the + permutation, store it in the form of a cycle in \c cycle and return true. + + Using this function one can retrieve all cycles in a permutation. + + \c cycle must be empty before calling the function. + */ + bool try_remove_cycle_from_permutation(unsigned_vector & permutation, unsigned_vector & cycle); + + void collect_sub_permutation(const unsigned_vector & permutation, const unsigned_vector & translation, + unsigned_vector & res, bool & identity); + + template + void permutate_by_cycle(T & container, unsigned cycle_len, const unsigned * permutation_cycle) { + if(cycle_len<2) { + return; + } + typename T::data aux = container[permutation_cycle[0]]; + for(unsigned i=1; i + void permutate_by_cycle(ref_vector & container, unsigned cycle_len, const unsigned * permutation_cycle) { + if(cycle_len<2) { + return; + } + T * aux = container.get(permutation_cycle[0]); + for(unsigned i=1; i + void permutate_by_cycle(T & container, const unsigned_vector permutation_cycle) { + permutate_by_cycle(container, permutation_cycle.size(), permutation_cycle.c_ptr()); + } + + + class counter { + protected: + typedef u_map map_impl; + map_impl m_data; + const bool m_stay_non_negative; + public: + typedef map_impl::iterator iterator; + + counter(bool stay_non_negative = true) : m_stay_non_negative(stay_non_negative) {} + + iterator begin() const { return m_data.begin(); } + iterator end() const { return m_data.end(); } + + void update(unsigned el, int delta); + int & get(unsigned el); + /** + \brief Increase values of elements in \c els by \c delta. + + The function returns a reference to \c *this to allow for expressions like + counter().count(sz, arr).get_positive_count() + */ + counter & count(unsigned sz, const unsigned * els, int delta = 1); + counter & count(const unsigned_vector & els, int delta = 1) { + return count(els.size(), els.c_ptr(), delta); + } + + void collect_positive(idx_set & acc) const; + unsigned get_positive_count() const; + bool get_max_positive(unsigned & res) const; + unsigned get_max_positive() const; + /** + Since the default counter value of a counter is zero, the result is never negative. + */ + int get_max_counter_value() const; + }; + + class var_counter : public counter { + ptr_vector m_sorts; + expr_fast_mark1 m_visited; + ptr_vector m_todo; + unsigned_vector m_scopes; + unsigned get_max_var(bool & has_var); + + public: + var_counter(bool stay_non_negative = true) : counter(stay_non_negative) {} + void count_vars(ast_manager & m, const app * t, int coef = 1); + void count_vars(ast_manager & m, const rule * r, int coef = 1); + unsigned get_max_var(const rule& r); + unsigned get_max_var(expr* e); + unsigned get_next_var(expr* e); + }; + + class ast_counter { + typedef obj_map map_impl; + map_impl m_data; + bool m_stay_non_negative; + public: + typedef map_impl::iterator iterator; + + ast_counter(bool stay_non_negative = true) : m_stay_non_negative(stay_non_negative) {} + + iterator begin() const { return m_data.begin(); } + iterator end() const { return m_data.end(); } + + int & get(ast * el) { + return m_data.insert_if_not_there2(el, 0)->get_data().m_value; + } + void update(ast * el, int delta){ + get(el)+=delta; + SASSERT(!m_stay_non_negative || get(el)>=0); + } + + void inc(ast * el) { update(el, 1); } + void dec(ast * el) { update(el, -1); } + }; + + void del_rule(horn_subsume_model_converter* mc, rule& r); + + void resolve_rule(replace_proof_converter* pc, rule& r1, rule& r2, unsigned idx, + expr_ref_vector const& s1, expr_ref_vector const& s2, rule& res); + + model_converter* mk_skip_model_converter(); + + proof_converter* mk_skip_proof_converter(); + + + /** + Return maximal variable number, or zero is there isn't any + */ + // unsigned get_max_var(const rule & r, ast_manager & m); + + void reverse_renaming(ast_manager & m, const expr_ref_vector & src, expr_ref_vector & tgt); + + /** + \brief Populate vector \c renaming_args so that it can be used as an argument to \c var_subst. + The renaming we want is one that transforms variables with numbers of indexes of \c map into the + values of at those indexes. If a value if \c UINT_MAX, it means we do not transform the index + corresponding to it. + */ + void get_renaming_args(const unsigned_vector & map, const relation_signature & orig_sig, + expr_ref_vector & renaming_arg); + + void print_renaming(const expr_ref_vector & cont, std::ostream & out); + + /** + \brief Update tgt with effect of applying substitution from 'sub' to it. + tgt is extended by variables that are substituted by 'sub'. + We use the convention that the entry at index 'i' corresponds to variable + with de-Bruijn index 'i'. + */ + void apply_subst(expr_ref_vector& tgt, expr_ref_vector const& sub); + + // ----------------------------------- + // + // container functions + // + // ----------------------------------- + + template + void set_intersection(Set & tgt, const Set & src) { + svector to_remove; + typename Set::iterator vit = tgt.begin(); + typename Set::iterator vend = tgt.end(); + for(;vit!=vend;++vit) { + typename Set::data itm=*vit; + if(!src.contains(itm)) { + to_remove.push_back(itm); + } + } + while(!to_remove.empty()) { + tgt.remove(to_remove.back()); + to_remove.pop_back(); + } + } + + template + void set_difference(Set & tgt, const Set & to_remove) { + typename Set::iterator vit = to_remove.begin(); + typename Set::iterator vend = to_remove.end(); + for(;vit!=vend;++vit) { + typename Set::data itm=*vit; + tgt.remove(itm); + } + } + + template + void set_union(Set & tgt, const Set & to_add) { + typename Set::iterator vit = to_add.begin(); + typename Set::iterator vend = to_add.end(); + for(;vit!=vend;++vit) { + typename Set::data itm=*vit; + tgt.insert(itm); + } + } + + void idx_set_union(idx_set & tgt, const idx_set & src); + + template + void unite_disjoint_maps(T & tgt, const T & src) { + typename T::iterator it = src.begin(); + typename T::iterator end = src.end(); + for(; it!=end; ++it) { + SASSERT(!tgt.contains(it->m_key)); + tgt.insert(it->m_key, it->m_value); + } + } + + template + void collect_map_range(T & acc, const U & map) { + typename U::iterator it = map.begin(); + typename U::iterator end = map.end(); + for(; it!=end; ++it) { + acc.push_back(it->m_value); + } + } + + + template + void print_container(const T & begin, const T & end, std::ostream & out) { + T it = begin; + out << "("; + bool first = true; + for(; it!=end; ++it) { + if(first) { first = false; } else { out << ","; } + out << (*it); + } + out << ")"; + } + + template + void print_container(const T & cont, std::ostream & out) { + print_container(cont.begin(), cont.end(), out); + } + + template + void print_container(const ref_vector & cont, std::ostream & out) { + print_container(cont.c_ptr(), cont.c_ptr() + cont.size(), out); + } + + template + void print_map(const T & cont, std::ostream & out) { + typename T::iterator it = cont.begin(); + typename T::iterator end = cont.end(); + out << "("; + bool first = true; + for(; it!=end; ++it) { + if(first) { first = false; } else { out << ","; } + out << it->m_key << "->" << it->m_value; + } + out << ")"; + } + + template + unsigned find_index(const It & begin, const It & end, const V & val) { + unsigned idx = 0; + It it = begin; + for(; it!=end; it++, idx++) { + if(*it==val) { + return idx; + } + } + return UINT_MAX; + } + + template + bool containers_equal(const T & begin1, const T & end1, const U & begin2, const U & end2) { + T it1 = begin1; + U it2 = begin2; + for(; it1!=end1 && it2!=end2; ++it1, ++it2) { + if(*it1!=*it2) { + return false; + } + } + return it1==end1 && it2==end2; + } + + template + bool vectors_equal(const T & c1, const U & c2) { + if(c1.size()!=c2.size()) { + return false; + } + typename T::data * it1 = c1.c_ptr(); + typename T::data * end1 = c1.c_ptr()+c1.size(); + typename U::data * it2 = c2.c_ptr(); + for(; it1!=end1; ++it1, ++it2) { + if(*it1!=*it2) { + return false; + } + } + return true; + } + + template + unsigned int_vector_hash(const T & cont) { + return string_hash(reinterpret_cast(cont.c_ptr()), + cont.size()*sizeof(typename T::data), 0); + } + + template + struct int_vector_hash_proc { + unsigned operator()(const T & cont) const { + return int_vector_hash(cont); + } + }; + template + struct vector_eq_proc { + bool operator()(const T & c1, const T & c2) const { return vectors_equal(c1, c2); } + }; + + template + void dealloc_ptr_vector_content(ptr_vector & v) { + typename ptr_vector::iterator it = v.begin(); + typename ptr_vector::iterator end = v.end(); + for(; it!=end; ++it) { + dealloc(*it); + } + } + + void dealloc_ptr_vector_content(ptr_vector & v); + + /** + \brief Add elements from an iterable object \c src into the vector \c vector. + */ + template + void push_into_vector(VectType & vector, const U & src) { + typename U::iterator it = src.begin(); + typename U::iterator end = src.end(); + for(; it!=end; ++it) { + vector.push_back(*it); + } + } + + template + void push_into_vector(VectType & vector, const ref_vector & src) { + U * const * it = src.begin(); + U * const * end = src.end(); + for(; it!=end; ++it) { + vector.push_back(*it); + } + } + + template + void insert_into_set(SetType & tgt, const U & src) { + typename U::const_iterator it = src.begin(); + typename U::const_iterator end = src.end(); + for(; it!=end; ++it) { + tgt.insert(*it); + } + } + + + /** + \brief Remove the first occurence of \c el from \c v and return \c true. If + \c el is not present in \c v, return \c false. The order of elements in \c v + is not preserved. + */ + template + bool remove_from_vector(T & v, const typename T::data & el) { + unsigned sz = v.size(); + for(unsigned i=0; i + */ + template + void reset_dealloc_values(map & m) { + typename map::iterator it = m.begin(); + typename map::iterator end = m.end(); + for (; it != end; ++it) { + dealloc(it->m_value); + } + m.reset(); + } + + template + struct aux__index_comparator { + T* m_keys; + aux__index_comparator(T* keys) : m_keys(keys) {} + bool operator()(unsigned a, unsigned b) { + return m_keys[a] + void sort_two_arrays(unsigned len, T* keys, U* vals) { + if(len<2) { + return; + } + if(len==2) { + if(keys[0]>keys[1]) { + std::swap(keys[0], keys[1]); + std::swap(vals[0], vals[1]); + } + return; + } + unsigned_vector numbers; + for(unsigned i=0; i cmp(keys); + std::sort(numbers.begin(), numbers.end(), cmp); + for(unsigned i=0; i + void add_sequence_without_set(unsigned start, unsigned count, const Container & complement, unsigned_vector & v) { + unsigned after_last = start+count; + for(unsigned i=start; i + void universal_delete(T* ptr) { + dealloc(ptr); + } + + void universal_delete(relation_base* ptr); + void universal_delete(table_base* ptr); + + template + class scoped_rel { + T* m_t; + public: + scoped_rel(T* t) : m_t(t) {} + ~scoped_rel() { if (m_t) { universal_delete(m_t); } } + scoped_rel() : m_t(0) {} + scoped_rel& operator=(T* t) { if (m_t) { universal_delete(m_t); } m_t = t; return *this; } + T* operator->() { return m_t; } + const T* operator->() const { return m_t; } + T& operator*() { return *m_t; } + const T& operator*() const { return *m_t; } + operator bool() const { return m_t!=0; } + T* get() { return m_t; } + /** + \brief Remove object from \c scoped_rel without deleting it. + */ + T* release() { + T* res = m_t; + m_t = 0; + return res; + } + }; + + /** + \brief If it is possible to convert the beginning of \c s to uint64, + store the result of conversion and return true; otherwise return false. + */ + bool string_to_uint64(const char * s, uint64 & res); + std::string to_string(uint64 num); + /** + \brief Read the sequence of decimal digits starting at \c s and interpret it as + uint64. If successful, \c res will contain the read number and \c s will point + to the first non-digit character, and true is returned. If the first character + is not a digit, no parameter is modified and false is returned. If the uint64 + overflows, \c points to the character which caused the overflow and false is + returned. + */ + bool read_uint64(const char * & s, uint64 & res); +}; + +#endif /* _DL_UTIL_H_ */ + diff --git a/lib/dl_vector_relation.h b/lib/dl_vector_relation.h new file mode 100644 index 000000000..a3795aa22 --- /dev/null +++ b/lib/dl_vector_relation.h @@ -0,0 +1,408 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + dl_vector_relation.h + +Abstract: + + Basic relation with equivalences. + +Author: + + Nikolaj Bjorner (nbjorner) 2010-2-11 + +Revision History: + +--*/ +#ifndef _DL_VECTOR_RELATION_H_ +#define _DL_VECTOR_RELATION_H_ + +#include "ast_pp.h" +#include "dl_context.h" +#include "union_find.h" + +namespace datalog { + + typedef std::pair u_pair; + + template + class vector_relation_helper { + public: + static void mk_project_t(T& t, unsigned_vector const& renaming) {} + }; + + template > + class vector_relation : public relation_base { + protected: + T m_default; + vector* m_elems; + bool m_empty; + union_find_default_ctx m_ctx; + union_find<>* m_eqs; + + friend class vector_relation_plugin; + + public: + vector_relation(relation_plugin& p, relation_signature const& s, bool is_empty, T const& t = T()): + relation_base(p, s), + m_default(t), + m_elems(alloc(vector)), + m_empty(is_empty), + m_eqs(alloc(union_find<>, m_ctx)) { + m_elems->resize(s.size(), t); + for (unsigned i = 0; i < s.size(); ++i) { + m_eqs->mk_var(); + } + } + + virtual ~vector_relation() { + dealloc(m_eqs); + dealloc(m_elems); + } + + virtual bool can_swap() const { return true; } + + virtual void swap(relation_base& other) { + vector_relation& o = dynamic_cast(other); + if (&o == this) return; + std::swap(o.m_eqs, m_eqs); + std::swap(o.m_empty, m_empty); + std::swap(o.m_elems, m_elems); + } + + void copy(vector_relation const& other) { + SASSERT(get_signature() == other.get_signature()); + if (other.empty()) { + set_empty(); + return; + } + m_empty = false; + for (unsigned i = 0; i < m_elems->size(); ++i) { + (*this)[i] = other[i]; + SASSERT(find(i) == i); + } + for (unsigned i = 0; i < m_elems->size(); ++i) { + merge(i, find(i)); + } + } + + + virtual bool empty() const { return m_empty; } + + T& operator[](unsigned i) { return (*m_elems)[find(i)]; } + + T const& operator[](unsigned i) const { return (*m_elems)[find(i)]; } + + virtual void display_index(unsigned i, T const& t, std::ostream& out) const = 0; + + virtual void display(std::ostream & out) const { + if (empty()) { + out << "empty\n"; + return; + } + for (unsigned i = 0; i < m_elems->size(); ++i) { + if (i == find(i)) { + display_index(i, (*m_elems)[i], out); + } + else { + out << i << " = " << find(i) << "\n"; + } + } + } + + + bool is_subset_of(vector_relation const& other) const { + if (empty()) return true; + if (other.empty()) return false; + for (unsigned i = 0; i < get_signature().size(); ++i) { + if (!is_subset_of((*this)[i], other[i])) { + return false; + } + } + return true; + } + + void set_empty() { + unsigned sz = m_elems->size(); + m_empty = true; + m_elems->reset(); + m_elems->resize(sz, m_default); + dealloc(m_eqs); + m_eqs = alloc(union_find<>,m_ctx); + for (unsigned i = 0; i < sz; ++i) { + m_eqs->mk_var(); + } + } + + + virtual T mk_intersect(T const& t1, T const& t2, bool& is_empty) const = 0; + + virtual T mk_widen(T const& t1, T const& t2) const = 0; + + virtual T mk_unite(T const& t1, T const& t2) const = 0; + + virtual bool is_subset_of(T const& t1, T const& t2) const = 0; + + virtual bool is_full(T const& t) const = 0; + + virtual bool is_empty(unsigned i, T const& t) const = 0; + + virtual void mk_rename_elem(T& t, unsigned col_cnt, unsigned const* cycle) = 0; + + virtual T mk_eq(union_find<> const& old_eqs, union_find<> const& neq_eqs, T const& t) const { return t; } + + void equate(unsigned i, unsigned j) { + SASSERT(i < get_signature().size()); + SASSERT(j < get_signature().size()); + if (!empty() && find(i) != find(j)) { + bool isempty; + T r = mk_intersect((*this)[i], (*this)[j], isempty); + if (isempty || is_empty(find(i),r)) { + m_empty = true; + } + else { + merge(i, j); + (*this)[i] = r; + } + } + } + + bool is_full() const { + for (unsigned i = 0; i < m_elems->size(); ++i) { + if (!is_full((*this)[i])) { + return false; + } + } + return true; + } + + void mk_join(vector_relation const& r1, vector_relation const& r2, + unsigned num_cols, unsigned const* cols1, unsigned const* cols2) { + SASSERT(is_full()); + bool is_empty = r1.empty() || r2.empty(); + if (is_empty) { + m_empty = true; + return; + } + unsigned sz1 = r1.get_signature().size(); + unsigned sz2 = r2.get_signature().size(); + for (unsigned i = 0; i < sz1; ++i) { + (*this)[i] = r1[i]; + } + for (unsigned i = 0; i < sz2; ++i) { + (*this)[sz1+i] = r2[i]; + } + for (unsigned i = 0; i < num_cols; ++i) { + unsigned col1 = cols1[i]; + unsigned col2 = cols2[i]; + equate(col1, sz1 + col2); + } + + TRACE("dl_relation", + r1.display(tout << "r1:\n"); + r2.display(tout << "r2:\n"); + display(tout << "dst:\n"); + ); + } + + void mk_project(vector_relation const& r, unsigned col_cnt, unsigned const* removed_cols) { + SASSERT(is_full()); + unsigned j = 0, k = 0; + unsigned_vector classRep, repNode; + unsigned result_size = get_signature().size(); + unsigned input_size = r.get_signature().size(); + repNode.resize(input_size, UINT_MAX); + + // initialize vector entries and set class representatives. + for (unsigned i = 0, j = 0, c = 0; i < input_size; ++i) { + if (c < col_cnt && removed_cols[c] == i) { + ++c; + } + else { + (*this)[j] = r[i]; + classRep.push_back(r.find(i)); + ++j; + } + } + + // merge remaining equivalence classes. + for (unsigned i = 0; i < result_size; ++i) { + unsigned rep = classRep[i]; + if (repNode[rep] == UINT_MAX) { + repNode[rep] = i; + } + else { + merge(repNode[rep], i); + } + } + + // rename columns in image of vector relation. + unsigned_vector renaming; + for (unsigned i = 0, j = 0, c = 0; i < input_size; ++i) { + if (c < col_cnt && removed_cols[c] == i) { + renaming.push_back(UINT_MAX); + ++c; + } + else { + renaming.push_back(find(j)); + ++j; + } + } + for (unsigned k = 0; k < result_size; ++k) { + Helper::mk_project_t((*this)[k], renaming); + } + + + TRACE("dl_relation", + ast_manager& m = r.get_plugin().get_ast_manager(); + tout << "Signature: "; + for (unsigned i = 0; i < r.get_signature().size(); ++i) { + tout << mk_pp(r.get_signature()[i], m) << " "; + } + tout << "Remove: "; + for (unsigned i = 0; i < col_cnt; ++i) { + tout << removed_cols[i] << " "; + } + tout << "\n"; + r.display(tout); + tout << " --> \n"; + display(tout);); + } + + void mk_rename(vector_relation const& r, unsigned col_cnt, unsigned const* cycle) { + unsigned col1, col2; + SASSERT(is_full()); + + // roundabout way of creating permuted relation. + unsigned_vector classRep, repNode; + for (unsigned i = 0; i < r.m_elems->size(); ++i) { + classRep.push_back(r.find(i)); + repNode.push_back(UINT_MAX); + (*this)[i] = r[i]; + } + for (unsigned i = 0; i + 1 < col_cnt; ++i) { + col1 = cycle[i]; + col2 = cycle[i+1]; + (*this)[col2] = (*r.m_elems)[col1]; + classRep[col2] = r.find(col1); + } + col1 = cycle[col_cnt-1]; + col2 = cycle[0]; + (*this)[col2] = (*r.m_elems)[col1]; + classRep[col2] = r.find(col1); + + for (unsigned i = 0; i < r.m_elems->size(); ++i) { + unsigned rep = classRep[i]; + if (repNode[rep] == UINT_MAX) { + repNode[rep] = i; + } + else { + merge(repNode[rep], i); + } + } + + for (unsigned i = 0; i < r.m_elems->size(); ++i) { + mk_rename_elem((*m_elems)[i], col_cnt, cycle); + } + + TRACE("dl_relation", + ast_manager& m = r.get_plugin().get_ast_manager(); + tout << "cycle: "; + for (unsigned i = 0; i < col_cnt; ++i) { + tout << cycle[i] << " "; + } + tout << "\nold_sig: "; + for (unsigned i = 0; i < r.get_signature().size(); ++i) { + tout << mk_pp(r.get_signature()[i], m) << " "; + } + tout << "\nnew_sig: "; + for (unsigned i = 0; i < get_signature().size(); ++i) { + tout << mk_pp(get_signature()[i], m) << " "; + } + tout << "\n"; + r.display(tout << "src:\n"); + ); + } + + void mk_union(vector_relation const& src, vector_relation* delta, bool is_widen) { + TRACE("dl_relation", display(tout << "dst:\n"); src.display(tout << "src:\n");); + + if (src.empty()) { + if (delta) { + delta->copy(src); + } + return; + } + + if (empty()) { + copy(src); + if (delta) { + delta->copy(src); + } + return; + } + + // find coarsest equivalence class containing joint equalities + union_find<>* uf = alloc(union_find<>, m_ctx); + unsigned size = get_signature().size(); + map, default_eq > mp; + bool change = false; + bit_vector finds; + finds.resize(size, false); + for (unsigned i = 0; i < size; ++i) { + uf->mk_var(); + unsigned w; + u_pair p(std::make_pair(find(i), src.find(i))); + if (mp.find(p, w)) { + uf->merge(i, w); + } + else { + mp.insert(p, i); + // detect change + if (finds.get(find(i))) { + change = true; + } + else { + finds.set(find(i), true); + } + } + } + vector* elems = alloc(vector); + for (unsigned i = 0; i < size; ++i) { + T t1 = mk_eq(*m_eqs, *uf, (*this)[i]); + T t2 = mk_eq(*src.m_eqs, *uf, src[i]); + if (is_widen) { + elems->push_back(mk_widen(t1, t2)); + } + else { + elems->push_back(mk_unite(t1, t2)); + } + TRACE("dl_relation", tout << t1 << " u " << t2 << " = " << elems->back() << "\n";); + change = delta && (change || !((*elems)[i] == (*this)[i])); + } + dealloc(m_eqs); + dealloc(m_elems); + m_eqs = uf; + m_elems = elems; + if (delta && change) { + delta->copy(*this); + } + TRACE("dl_relation", display(tout << "dst':\n");); + } + + unsigned find(unsigned i) const { + return m_eqs->find(i); + } + + void merge(unsigned i, unsigned j) { + m_eqs->merge(i, j); + } + + }; + +}; + +#endif + diff --git a/lib/dlist.h b/lib/dlist.h new file mode 100644 index 000000000..d07b9264d --- /dev/null +++ b/lib/dlist.h @@ -0,0 +1,137 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + dlist.h + +Abstract: + + Templates for manipulating doubly linked lists. + +Author: + + Leonardo de Moura (leonardo) 2011-01-25. + +Revision History: + +--*/ +#ifndef __DLIST_H_ +#define __DLIST_H_ + +/** + Add element \c elem to the list headed by \c head. + NextProc and PrevProc must have the methods: + T * & operator()(T *); + T * & operator()(T *); + They should return the next and prev fields of the given object. +*/ +template +void dlist_add(T * & head, T * elem, NextProc const & next = NextProc(), PrevProc const & prev = PrevProc()) { + SASSERT(prev(elem) == 0); + SASSERT(next(elem) == 0); + if (head == 0) { + head = elem; + } + else { + next(elem) = head; + prev(head) = elem; + head = elem; + } +} + +template +void dlist_del(T * & head, T * elem, NextProc const & next = NextProc(), PrevProc const & prev = PrevProc()) { + T * & prev_elem = prev(elem); + T * & next_elem = next(elem); + if (head == elem) { + SASSERT(prev_elem == 0); + if (next_elem != 0) + prev(next_elem) = 0; + head = next_elem; + } + else { + SASSERT(prev_elem != 0); + next(prev_elem) = next_elem; + if (next_elem != 0) + prev(next_elem) = prev_elem; + } + prev_elem = 0; + next_elem = 0; +} + +/** + \brief Remove the head of the list. Return the old head. +*/ +template +T * dlist_pop(T * & head, NextProc const & next = NextProc(), PrevProc const & prev = PrevProc()) { + if (head == 0) { + return 0; + } + else { + SASSERT(prev(head) == 0); + T * r = head; + head = next(head); + if (head) + prev(head) = 0; + next(r) = 0; + return r; + } +} + +/** + \brief Insert new element after elem. +*/ +template +void dlist_insert_after(T * elem, T * new_elem, NextProc const & next = NextProc(), PrevProc const & prev = PrevProc()) { + SASSERT(elem); + SASSERT(new_elem); + T * & old_next_elem = next(elem); + prev(new_elem) = elem; + next(new_elem) = old_next_elem; + if (old_next_elem) + prev(old_next_elem) = new_elem; + // next(elem) = new_elem; + old_next_elem = new_elem; +} + +template +bool dlist_contains(T * head, T const * elem, NextProc const & next = NextProc()) { + T * curr = head; + while (curr != 0) { + if (curr == elem) + return true; + curr = next(curr); + } + return false; +} + +template +unsigned dlist_length(T * head, NextProc const & next = NextProc()) { + unsigned r = 0; + T * curr = head; + while (curr != 0) { + r++; + curr = next(curr); + } + return r; +} + +template +bool dlist_check_invariant(T * head, NextProc const & next = NextProc(), PrevProc const & prev = PrevProc()) { + if (head == 0) + return true; + SASSERT(prev(head) == 0); + T * old = head; + T * curr = next(head); + while (curr != 0) { + SASSERT(prev(curr) == old); + old = curr; + curr = next(curr); + } + return true; +} + +#endif + + diff --git a/lib/double_manager.h b/lib/double_manager.h new file mode 100644 index 000000000..a6a72cc6f --- /dev/null +++ b/lib/double_manager.h @@ -0,0 +1,103 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + double_manager.h + +Abstract: + + Simulates mpq interface using doubles + +Author: + + Leonardo de Moura (leonardo) 2011-06-09. + +Revision History: + +--*/ +#ifndef _DOUBLE_MANAGER_H_ +#define _DOUBLE_MANAGER_H_ + +#include +#include +#include +#include +#include"util.h" +#include"debug.h" +#include"hash.h" +#include"params.h" + +/** + \brief Create an interface for manipulating double numbers compatible with the one for mpq. +*/ +class double_manager { + double m_zero_tolerance; +public: + typedef double numeral; + static bool precise() { return false; } + + double_manager(params_ref const & p = params_ref()) { updt_params(p); } + + void updt_params(params_ref const & p) { + m_zero_tolerance = p.get_double(":zero-tolerance", 0.00000001); + } + + static void reset(double & a) { a = 0.0; } + + static void del(double & a) { /* do nothing */ } + static void add(double a, double b, double & c) { c = a + b; } + // d <- a + b*c + static void addmul(double a, double b, double c, double & d) { d = a + b*c; } + // d <- a - b*c + static void submul(double a, double b, double c, double & d) { d = a - b*c; } + static void sub(double a, double b, double & c) { c = a - b; } + static void mul(double a, double b, double & c) { c = a * b; } + static void div(double a, double b, double & c) { c = a / b; } + static void inv(double a, double & b) { b = 1 / a; } + static void inv(double & a) { a = 1 / a; } + static void neg(double & a) { a = -a; } + static void abs(double & a) { if (a < 0.0) neg(a); } + static void power(double a, unsigned p, double & b) { + SASSERT(p <= INT_MAX); + b = ::pow(a, static_cast(p)); + } + static void floor(double a, double & b) { b = ::floor(a); } + static void ceil(double a, double & b) { b = ::ceil(a); } + bool eq(double a, double b) const { return is_zero(a - b); } + bool neq(double a, double b) const { return !eq(a, b); } + static bool lt(double a, double b) { return a < b; } + static bool le(double a, double b) { return a <= b; } + static bool gt(double a, double b) { return a > b; } + static bool ge(double a, double b) { return a >= b; } + static void set(double & a, int n, int d) { a = static_cast(n)/static_cast(d); } + static void set(double & a, double val) { a = val; } + static void set(double & a, char const * val) { a = atof(val); } + static void set(double & a, int val) { a = static_cast(val); } + static void set(double & a, unsigned val) { a = static_cast(val); } + static void set(double & a, int64 val) { a = static_cast(val); } + static void set(double & a, uint64 val) { a = static_cast(val); } + static void swap(double & a, double & b) { std::swap(a, b); } + bool is_pos(double a) const { return a > m_zero_tolerance; } + bool is_neg(double a) const { return a < m_zero_tolerance; } + bool is_zero(double a) const { return -m_zero_tolerance <= a && a <= m_zero_tolerance; } + bool is_nonpos(double a) const { return !is_pos(a); } + bool is_nonneg(double a) const { return !is_neg(a); } + static bool is_one(double a) { return a == 1.0; } + static bool is_minus_one(double a) { return a == -1.0; } + static bool is_int(double a) { return a == ::floor(a); } + static std::string to_string(double a) { + std::ostringstream sstream; + sstream << std::setprecision(12) << a; + return sstream.str(); + } + + static unsigned hash(double a) { + return hash_ull(static_cast(a)); + } +}; + +COMPILE_TIME_ASSERT(sizeof(uint64) == sizeof(double)); + +#endif /* _DOUBLE_MANAGER_H_ */ + diff --git a/lib/dummy_big_rational.h b/lib/dummy_big_rational.h new file mode 100644 index 000000000..350e06140 --- /dev/null +++ b/lib/dummy_big_rational.h @@ -0,0 +1,54 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dummy_big_rational.h + +Abstract: + + Dummy big rational + +Author: + + Leonardo de Moura (leonardo) 2006-09-26. + +Revision History: + +--*/ + +#ifndef _DUMMY_BIG_RATIONAL_H_ +#define _DUMMY_BIG_RATIONAL_H_ + +#include +#include"debug.h" + +class big_rational { +public: + big_rational() { } + big_rational(int n) {} + ~big_rational() {} + void reset() {} + unsigned hash() const { return 0; } + void set(int num, int den) { UNREACHABLE(); } + void set(const char * str) { UNREACHABLE(); } + bool is_int() const { UNREACHABLE(); return false; } + long get_int() const { UNREACHABLE(); return 0; } + void neg() { UNREACHABLE(); } + big_rational & operator=(const big_rational & r) { UNREACHABLE(); return *this; } + bool operator==(const big_rational & r) const { UNREACHABLE(); return false; } + bool operator<(const big_rational & r) const { UNREACHABLE(); return false; } + big_rational & operator+=(const big_rational & r) { UNREACHABLE(); return *this; } + big_rational & operator-=(const big_rational & r) { UNREACHABLE(); return *this; } + big_rational & operator*=(const big_rational & r) { UNREACHABLE(); return *this; } + big_rational & operator/=(const big_rational & r) { UNREACHABLE(); return *this; } + big_rational & operator%=(const big_rational & r) { UNREACHABLE(); return *this; } + friend void div(const big_rational & r1, const big_rational & r2, big_rational & result) { UNREACHABLE(); } + void get_numerator(big_rational & result) { UNREACHABLE(); } + void get_denominator(big_rational & result) { UNREACHABLE(); } + void get_floor(big_rational & result) { UNREACHABLE(); } + std::string to_string() const { UNREACHABLE(); return std::string(""); } +}; + +#endif /* _DUMMY_BIG_RATIONAL_H_ */ + diff --git a/lib/dyn_ack.cpp b/lib/dyn_ack.cpp new file mode 100644 index 000000000..fc9b8939f --- /dev/null +++ b/lib/dyn_ack.cpp @@ -0,0 +1,524 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dyn_ack.cpp + +Abstract: + + Dynamic Ackermann's reduction + +Author: + + Leonardo de Moura (leonardo) 2007-04-24. + +Revision History: + +--*/ +#include"smt_context.h" +#include"dyn_ack.h" +#include"ast_pp.h" + +namespace smt { + + /** + \brief Justification for dynamic ackermann clause + */ + class dyn_ack_justification : public justification { + app * m_app1; + app * m_app2; + public: + dyn_ack_justification(app * n1, app * n2): + justification(false), // dyn_ack_justifications are not stored in regions. + m_app1(n1), + m_app2(n2) { + SASSERT(m_app1->get_num_args() == m_app2->get_num_args()); + SASSERT(m_app1->get_decl() == m_app2->get_decl()); + SASSERT(m_app1->get_num_args() > 0); + SASSERT(m_app1->get_id() < m_app2->get_id()); + } + + virtual char const * get_name() const { return "dyn-ack"; } + + virtual void get_antecedents(conflict_resolution & cr) { + } + + virtual void display_debug_info(conflict_resolution & cr, std::ostream & out) { + ast_manager & m = cr.get_manager(); + out << "m_app1:\n" << mk_pp(m_app1, m) << "\n"; + out << "m_app2:\n" << mk_pp(m_app2, m) << "\n"; + } + + /** + \brief Make a hypothesis (= lhs rhs) for the given equality. + The arguments of the given equality eq may have been swapped. That is, \c eq is of the form (= rhs lhs). + In this case, we also apply a symmetry rule. + + \remark if negate == true, then the hypothesis is actually (not (= lhs rhs)) + */ + proof * mk_hypothesis(ast_manager & m, app * eq, bool negate, expr * lhs, expr * rhs) { + SASSERT(m.is_eq(eq)); + SASSERT((eq->get_arg(0) == lhs && eq->get_arg(1) == rhs) || + (eq->get_arg(0) == rhs && eq->get_arg(1) == lhs)); + app * h = negate ? m.mk_not(eq) : eq; + if (eq->get_arg(0) == lhs && eq->get_arg(1) == rhs) { + return m.mk_hypothesis(h); + } + else { + return m.mk_symmetry(m.mk_hypothesis(h)); + } + } + + virtual proof * mk_proof(conflict_resolution & cr) { + ast_manager & m = cr.get_manager(); + context & ctx = cr.get_context(); + unsigned num_args = m_app1->get_num_args(); + ptr_buffer prs; + ptr_buffer lits; + for (unsigned i = 0; i < num_args; i++) { + expr * arg1 = m_app1->get_arg(i); + expr * arg2 = m_app2->get_arg(i); + if (arg1 != arg2) { + app * eq = ctx.mk_eq_atom(arg1, arg2); + app * neq = m.mk_not(eq); + if (std::find(lits.begin(), lits.end(), neq) == lits.end()) { + lits.push_back(neq); + prs.push_back(mk_hypothesis(m, eq, false, arg1, arg2)); + } + } + } + proof * antecedents[2]; + antecedents[0] = m.mk_congruence(m_app1, m_app2, prs.size(), prs.c_ptr()); + app * eq = ctx.mk_eq_atom(m_app1, m_app2); + antecedents[1] = mk_hypothesis(m, eq, true, m_app1, m_app2); + proof * false_pr = m.mk_unit_resolution(2, antecedents); + lits.push_back(eq); + SASSERT(lits.size() >= 2); + app * lemma = m.mk_or(lits.size(), lits.c_ptr()); + TRACE("dyn_ack", tout << mk_pp(lemma, m) << "\n";); + TRACE("dyn_ack", tout << mk_pp(false_pr, m) << "\n";); + return m.mk_lemma(false_pr, lemma); + } + + }; + + dyn_ack_manager::dyn_ack_manager(context & ctx, dyn_ack_params & p): + m_context(ctx), + m_manager(ctx.get_manager()), + m_params(p) { + } + + dyn_ack_manager::~dyn_ack_manager() { + reset_app_pairs(); + reset_app_triples(); + } + + void dyn_ack_manager::reset_app_pairs() { + svector::iterator it = m_app_pairs.begin(); + svector::iterator end = m_app_pairs.end(); + for (; it != end; ++it) { + app_pair & p = *it; + m_manager.dec_ref(p.first); + m_manager.dec_ref(p.second); + } + m_app_pairs.reset(); + } + + + void dyn_ack_manager::init_search_eh() { + m_app_pair2num_occs.reset(); + reset_app_pairs(); + m_to_instantiate.reset(); + m_qhead = 0; + m_num_instances = 0; + m_num_propagations_since_last_gc = 0; + + m_triple.m_app2num_occs.reset(); + reset_app_triples(); + m_triple.m_to_instantiate.reset(); + m_triple.m_qhead = 0; + } + + void dyn_ack_manager::cg_eh(app * n1, app * n2) { + SASSERT(n1->get_decl() == n2->get_decl()); + SASSERT(n1->get_num_args() == n2->get_num_args()); + SASSERT(n1 != n2); + if (m_manager.is_eq(n1)) + return; + if (n1->get_id() > n2->get_id()) + std::swap(n1,n2); + app_pair p(n1, n2); + if (m_instantiated.contains(p)) + return; + unsigned num_occs = 0; + if (m_app_pair2num_occs.find(n1, n2, num_occs)) { + TRACE("dyn_ack", tout << "used_cg_eh:\n" << mk_pp(n1, m_manager) << "\n" << mk_pp(n2, m_manager) << "\nnum_occs: " << num_occs << "\n";); + num_occs++; + } + else { + num_occs = 1; + m_manager.inc_ref(n1); + m_manager.inc_ref(n2); + m_app_pairs.push_back(p); + } + SASSERT(num_occs > 0); + m_app_pair2num_occs.insert(n1, n2, num_occs); +#ifdef Z3DEBUG + unsigned num_occs2 = 0; + SASSERT(m_app_pair2num_occs.find(n1, n2, num_occs2) && num_occs == num_occs2); +#endif + if (num_occs == m_params.m_dack_threshold) { + TRACE("dyn_ack", tout << "found candidate:\n" << mk_pp(n1, m_manager) << "\n" << mk_pp(n2, m_manager) << "\nnum_occs: " << num_occs << "\n";); + m_to_instantiate.push_back(p); + } + } + + void dyn_ack_manager::eq_eh(app * n1, app * n2, app* r) { + if (n1 == n2 || r == n1 || r == n2 || m_manager.is_bool(n1)) { + return; + } + if (n1->get_id() > n2->get_id()) + std::swap(n1,n2); + TRACE("dyn_ack", + tout << mk_pp(n1, m_manager) << " = " << mk_pp(n2, m_manager) + << " = " << mk_pp(r, m_manager) << "\n";); + app_triple tr(n1, n2, r); + if (m_triple.m_instantiated.contains(tr)) + return; + unsigned num_occs = 0; + if (m_triple.m_app2num_occs.find(n1, n2, r, num_occs)) { + TRACE("dyn_ack", tout << mk_pp(n1, m_manager) << "\n" << mk_pp(n2, m_manager) + << mk_pp(r, m_manager) << "\n" << "\nnum_occs: " << num_occs << "\n";); + num_occs++; + } + else { + num_occs = 1; + m_manager.inc_ref(n1); + m_manager.inc_ref(n2); + m_manager.inc_ref(r); + m_triple.m_apps.push_back(tr); + } + SASSERT(num_occs > 0); + m_triple.m_app2num_occs.insert(n1, n2, r, num_occs); +#ifdef Z3DEBUG + unsigned num_occs2 = 0; + SASSERT(m_triple.m_app2num_occs.find(n1, n2, r, num_occs2) && num_occs == num_occs2); +#endif + if (num_occs == m_params.m_dack_threshold) { + TRACE("dyn_ack", tout << "found candidate:\n" << mk_pp(n1, m_manager) << "\n" << mk_pp(n2, m_manager) + << "\n" << mk_pp(r, m_manager) + << "\nnum_occs: " << num_occs << "\n";); + m_triple.m_to_instantiate.push_back(tr); + } + + } + + struct app_pair_lt { + typedef std::pair app_pair; + typedef obj_pair_map app_pair2num_occs; + app_pair2num_occs & m_app_pair2num_occs; + + app_pair_lt(app_pair2num_occs & m): + m_app_pair2num_occs(m) { + } + + bool operator()(app_pair const & p1, app_pair const & p2) const { + unsigned n1 = 0; + unsigned n2 = 0; + m_app_pair2num_occs.find(p1.first, p1.second, n1); + m_app_pair2num_occs.find(p2.first, p2.second, n2); + SASSERT(n1 > 0); + SASSERT(n2 > 0); + return n1 > n2; + } + }; + + void dyn_ack_manager::gc() { + TRACE("dyn_ack", tout << "dyn_ack GC\n";); + unsigned num_deleted = 0; + m_to_instantiate.reset(); + m_qhead = 0; + svector::iterator it = m_app_pairs.begin(); + svector::iterator end = m_app_pairs.end(); + svector::iterator it2 = it; + for (; it != end; ++it) { + app_pair & p = *it; + if (m_instantiated.contains(p)) { + TRACE("dyn_ack", tout << "1) erasing:\n" << mk_pp(p.first, m_manager) << "\n" << mk_pp(p.second, m_manager) << "\n";); + m_manager.dec_ref(p.first); + m_manager.dec_ref(p.second); + SASSERT(!m_app_pair2num_occs.contains(p.first, p.second)); + continue; + } + unsigned num_occs = 0; + m_app_pair2num_occs.find(p.first, p.second, num_occs); + // The following invariant is not true. p.first and + // p.second may have been instantiated, and removed from + // m_app_pair2num_occs, but not from m_app_pairs. + // + // SASSERT(num_occs > 0); + num_occs = static_cast(num_occs * m_params.m_dack_gc_inv_decay); + if (num_occs <= 1) { + num_deleted++; + TRACE("dyn_ack", tout << "2) erasing:\n" << mk_pp(p.first, m_manager) << "\n" << mk_pp(p.second, m_manager) << "\n";); + m_app_pair2num_occs.erase(p.first, p.second); + m_manager.dec_ref(p.first); + m_manager.dec_ref(p.second); + continue; + } + *it2 = p; + ++it2; + SASSERT(num_occs > 0); + m_app_pair2num_occs.insert(p.first, p.second, num_occs); + if (num_occs >= m_params.m_dack_threshold) + m_to_instantiate.push_back(p); + } + m_app_pairs.set_end(it2); + app_pair_lt f(m_app_pair2num_occs); + // app_pair_lt is not a total order on pairs of expressions. + // So, we should use stable_sort to avoid different behavior in different platforms. + std::stable_sort(m_to_instantiate.begin(), m_to_instantiate.end(), f); + // IF_VERBOSE(10, if (num_deleted > 0) verbose_stream() << "dynamic ackermann GC: " << num_deleted << "\n";); + } + + class dyn_ack_clause_del_eh : public clause_del_eh { + dyn_ack_manager & m_manager; + public: + dyn_ack_clause_del_eh(dyn_ack_manager & m): + m_manager(m) { + } + virtual ~dyn_ack_clause_del_eh() {} + virtual void operator()(ast_manager & m, clause * cls) { + m_manager.del_clause_eh(cls); + dealloc(this); + } + }; + + void dyn_ack_manager::del_clause_eh(clause * cls) { + TRACE("dyn_ack", tout << "del_clause_eh: "; m_context.display_clause(tout, cls); tout << "\n";); + m_context.m_stats.m_num_del_dyn_ack++; + + app_pair p((app*)0,(app*)0); + if (m_clause2app_pair.find(cls, p)) { + SASSERT(p.first && p.second); + m_instantiated.erase(p); + SASSERT(!m_app_pair2num_occs.contains(p.first, p.second)); + return; + } + app_triple tr(0,0,0); + if (m_triple.m_clause2apps.find(cls, tr)) { + SASSERT(tr.first && tr.second && tr.third); + m_triple.m_instantiated.erase(tr); + SASSERT(!m_triple.m_app2num_occs.contains(tr.first, tr.second, tr.third)); + return; + } + } + + void dyn_ack_manager::propagate_eh() { + if (m_params.m_dack == DACK_DISABLED) + return; + m_num_propagations_since_last_gc++; + if (m_num_propagations_since_last_gc > m_params.m_dack_gc) { + gc(); + m_num_propagations_since_last_gc = 0; + } + unsigned max_instances = static_cast(m_context.get_num_conflicts() * m_params.m_dack_factor); + while (m_num_instances < max_instances && m_qhead < m_to_instantiate.size()) { + app_pair & p = m_to_instantiate[m_qhead]; + m_qhead++; + m_num_instances++; + instantiate(p.first, p.second); + } + while (m_num_instances < max_instances && m_triple.m_qhead < m_triple.m_to_instantiate.size()) { + app_triple & p = m_triple.m_to_instantiate[m_triple.m_qhead]; + m_triple.m_qhead++; + m_num_instances++; + instantiate(p.first, p.second, p.third); + } + } + + literal dyn_ack_manager::mk_eq(expr * n1, expr * n2) { + app * eq = m_context.mk_eq_atom(n1, n2); + m_context.internalize(eq, true); + literal l = m_context.get_literal(eq); + TRACE("dyn_ack", tout << "eq:\n" << mk_pp(eq, m_manager) << "\nliteral: "; + m_context.display_literal(tout, l); tout << "\n";); + return l; + } + + void dyn_ack_manager::instantiate(app * n1, app * n2) { + SASSERT(m_params.m_dack != DACK_DISABLED); + SASSERT(n1->get_decl() == n2->get_decl()); + SASSERT(n1->get_num_args() == n2->get_num_args()); + SASSERT(n1 != n2); + m_context.m_stats.m_num_dyn_ack++; + TRACE("dyn_ack_inst", tout << "dyn_ack: " << n1->get_id() << " " << n2->get_id() << "\n";); + TRACE("dyn_ack", tout << "expanding Ackermann's rule for:\n" << mk_pp(n1, m_manager) << "\n" << mk_pp(n2, m_manager) << "\n";); + unsigned num_args = n1->get_num_args(); + literal_buffer lits; + for (unsigned i = 0; i < num_args; i++) { + expr * arg1 = n1->get_arg(i); + expr * arg2 = n2->get_arg(i); + if (arg1 != arg2) + lits.push_back(~mk_eq(arg1, arg2)); + } + app_pair p(n1, n2); + SASSERT(m_app_pair2num_occs.contains(n1, n2)); + m_app_pair2num_occs.erase(n1, n2); + // pair n1,n2 is still in m_app_pairs + m_instantiated.insert(p); + lits.push_back(mk_eq(n1, n2)); + clause_del_eh * del_eh = alloc(dyn_ack_clause_del_eh, *this); + + justification * js = 0; + if (m_manager.proofs_enabled()) + js = alloc(dyn_ack_justification, n1, n2); + clause * cls = m_context.mk_clause(lits.size(), lits.c_ptr(), js, CLS_AUX_LEMMA, del_eh); + if (!cls) { + dealloc(del_eh); + return; + } + TRACE("dyn_ack_clause", tout << "new clause:\n"; m_context.display_clause_detail(tout, cls); tout << "\n";); + m_clause2app_pair.insert(cls, p); + } + + void dyn_ack_manager::reset() { + init_search_eh(); + m_instantiated.reset(); + m_clause2app_pair.reset(); + m_triple.m_instantiated.reset(); + m_triple.m_clause2apps.reset(); + } + + void dyn_ack_manager::reset_app_triples() { + svector::iterator it = m_triple.m_apps.begin(); + svector::iterator end = m_triple.m_apps.end(); + for (; it != end; ++it) { + app_triple & p = *it; + m_manager.dec_ref(p.first); + m_manager.dec_ref(p.second); + m_manager.dec_ref(p.third); + } + m_triple.m_apps.reset(); + } + + void dyn_ack_manager::instantiate(app * n1, app * n2, app* r) { + SASSERT(m_params.m_dack != DACK_DISABLED); + SASSERT(n1 != n2 && n1 != r && n2 != r); + m_context.m_stats.m_num_dyn_ack++; + TRACE("dyn_ack_inst", tout << "dyn_ack: " << n1->get_id() << " " << n2->get_id() << " " << r->get_id() << "\n";); + TRACE("dyn_ack", tout << "expanding Ackermann's rule for:\n" << mk_pp(n1, m_manager) << "\n" + << mk_pp(n2, m_manager) << "\n" + << mk_pp(r, m_manager) << "\n"; + ); + app_triple tr(n1, n2, r); + SASSERT(m_triple.m_app2num_occs.contains(n1, n2, r)); + m_triple.m_app2num_occs.erase(n1, n2, r); + // pair n1,n2 is still in m_triple.m_apps + m_triple.m_instantiated.insert(tr); + literal_buffer lits; + lits.push_back(~mk_eq(n1, r)); + lits.push_back(~mk_eq(n2, r)); + lits.push_back(mk_eq(n1, n2)); + clause_del_eh * del_eh = alloc(dyn_ack_clause_del_eh, *this); + + justification * js = 0; + if (m_manager.proofs_enabled()) + js = alloc(dyn_ack_justification, n1, n2); + clause * cls = m_context.mk_clause(lits.size(), lits.c_ptr(), js, CLS_AUX_LEMMA, del_eh); + if (!cls) { + dealloc(del_eh); + return; + } + TRACE("dyn_ack_clause", tout << "new clause:\n"; m_context.display_clause_detail(tout, cls); tout << "\n";); + m_triple.m_clause2apps.insert(cls, tr); + } + + + struct app_triple_lt { + typedef triple app_triple; + typedef obj_triple_map app_triple2num_occs; + app_triple2num_occs & m_app_triple2num_occs; + + app_triple_lt(app_triple2num_occs & m): + m_app_triple2num_occs(m) { + } + + bool operator()(app_triple const & p1, app_triple const & p2) const { + unsigned n1 = 0; + unsigned n2 = 0; + m_app_triple2num_occs.find(p1.first, p1.second, p1.third, n1); + m_app_triple2num_occs.find(p2.first, p2.second, p2.third, n2); + SASSERT(n1 > 0); + SASSERT(n2 > 0); + return n1 > n2; + } + }; + + void dyn_ack_manager::gc_triples() { + TRACE("dyn_ack", tout << "dyn_ack GC\n";); + unsigned num_deleted = 0; + m_triple.m_to_instantiate.reset(); + m_triple.m_qhead = 0; + svector::iterator it = m_triple.m_apps.begin(); + svector::iterator end = m_triple.m_apps.end(); + svector::iterator it2 = it; + for (; it != end; ++it) { + app_triple & p = *it; + if (m_triple.m_instantiated.contains(p)) { + TRACE("dyn_ack", tout << "1) erasing:\n" << mk_pp(p.first, m_manager) << "\n" << mk_pp(p.second, m_manager) << "\n";); + m_manager.dec_ref(p.first); + m_manager.dec_ref(p.second); + m_manager.dec_ref(p.third); + SASSERT(!m_triple.m_app2num_occs.contains(p.first, p.second, p.third)); + continue; + } + unsigned num_occs = 0; + m_triple.m_app2num_occs.find(p.first, p.second, p.third, num_occs); + // The following invariant is not true. p.first and + // p.second may have been instantiated, and removed from + // m_app_triple2num_occs, but not from m_app_triples. + // + // SASSERT(num_occs > 0); + num_occs = static_cast(num_occs * m_params.m_dack_gc_inv_decay); + if (num_occs <= 1) { + num_deleted++; + TRACE("dyn_ack", tout << "2) erasing:\n" << mk_pp(p.first, m_manager) << "\n" << mk_pp(p.second, m_manager) << "\n";); + m_triple.m_app2num_occs.erase(p.first, p.second, p.third); + m_manager.dec_ref(p.first); + m_manager.dec_ref(p.second); + m_manager.dec_ref(p.third); + continue; + } + *it2 = p; + ++it2; + SASSERT(num_occs > 0); + m_triple.m_app2num_occs.insert(p.first, p.second, p.third, num_occs); + if (num_occs >= m_params.m_dack_threshold) + m_triple.m_to_instantiate.push_back(p); + } + m_triple.m_apps.set_end(it2); + app_triple_lt f(m_triple.m_app2num_occs); + // app_triple_lt is not a total order + std::stable_sort(m_triple.m_to_instantiate.begin(), m_triple.m_to_instantiate.end(), f); + // IF_VERBOSE(10, if (num_deleted > 0) verbose_stream() << "dynamic ackermann GC: " << num_deleted << "\n";); + } + + + +#ifdef Z3DEBUG + bool dyn_ack_manager::check_invariant() const { + clause2app_pair::iterator it = m_clause2app_pair.begin(); + clause2app_pair::iterator end = m_clause2app_pair.end(); + for (; it != end; ++it) { + app_pair const & p = it->get_value(); + SASSERT(m_instantiated.contains(p)); + SASSERT(!m_app_pair2num_occs.contains(p.first, p.second)); + } + + return true; + } +#endif + +}; diff --git a/lib/dyn_ack.h b/lib/dyn_ack.h new file mode 100644 index 000000000..b0fe7a6b3 --- /dev/null +++ b/lib/dyn_ack.h @@ -0,0 +1,137 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dyn_ack.h + +Abstract: + + Support code for implementing Dynamic Ackermann's reduction + +Author: + + Leonardo de Moura (leonardo) 2007-04-12. + +Revision History: + +--*/ +#ifndef _DYN_ACK_H_ +#define _DYN_ACK_H_ + +#include"ast.h" +#include"dyn_ack_params.h" +#include"obj_hashtable.h" +#include"obj_pair_hashtable.h" +#include"obj_triple_hashtable.h" +#include"smt_clause.h" + +namespace smt { + + class context; + + class dyn_ack_manager { + typedef std::pair app_pair; + typedef obj_pair_map app_pair2num_occs; + typedef svector app_pair_vector; + typedef obj_pair_hashtable app_pair_set; + typedef obj_map clause2app_pair; + + typedef triple app_triple; + typedef obj_triple_map app_triple2num_occs; + typedef svector app_triple_vector; + typedef obj_triple_hashtable app_triple_set; + typedef obj_map clause2app_triple; + + context & m_context; + ast_manager & m_manager; + dyn_ack_params & m_params; + app_pair2num_occs m_app_pair2num_occs; + app_pair_vector m_app_pairs; + app_pair_vector m_to_instantiate; + unsigned m_qhead; + unsigned m_num_instances; + unsigned m_num_propagations_since_last_gc; + app_pair_set m_instantiated; + clause2app_pair m_clause2app_pair; + + struct _triple { + app_triple2num_occs m_app2num_occs; + app_triple_vector m_apps; + app_triple_vector m_to_instantiate; + unsigned m_qhead; + unsigned m_num_instances; + unsigned m_num_propagations_since_last_gc; + app_triple_set m_instantiated; + clause2app_triple m_clause2apps; + }; + _triple m_triple; + + + + void gc(); + void reset_app_pairs(); + friend class dyn_ack_clause_del_eh; + void del_clause_eh(clause * cls); + void instantiate(app * n1, app * n2); + literal mk_eq(expr * n1, expr * n2); + void cg_eh(app * n1, app * n2); + + void eq_eh(app * n1, app * n2, app* r); + void instantiate(app * n1, app * n2, app* r); + void reset_app_triples(); + void gc_triples(); + + public: + dyn_ack_manager(context & ctx, dyn_ack_params & p); + ~dyn_ack_manager(); + + void setup() { + } + + /** + \brief This method is invoked before the beginning of the search. + */ + void init_search_eh(); + + /** + \brief This method is invoked when the congruence rule was used during conflict resolution. + */ + void used_cg_eh(app * n1, app * n2) { + if (m_params.m_dack == DACK_CR) + cg_eh(n1, n2); + } + + /** + \brief This method is invoked when the congruence rule is the root of a conflict. + */ + void cg_conflict_eh(app * n1, app * n2) { + if (m_params.m_dack == DACK_ROOT) + cg_eh(n1, n2); + } + + /** + \brief This method is invoked when equalities are used during conflict resolution. + */ + void used_eq_eh(app * n1, app * n2, app* r) { + if (m_params.m_dack_eq) + eq_eh(n1, n2, r); + } + + + /** + \brief This method is invoked when it is safe to expand the new ackermann rule entries. + */ + void propagate_eh(); + + void reset(); + +#ifdef Z3DEBUG + bool check_invariant() const; +#endif + }; + +}; + +#endif /* _DYN_ACK_H_ */ + diff --git a/lib/dyn_ack_params.cpp b/lib/dyn_ack_params.cpp new file mode 100644 index 000000000..b4d0546a9 --- /dev/null +++ b/lib/dyn_ack_params.cpp @@ -0,0 +1,31 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dyn_ack_params.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-05-18. + +Revision History: + +--*/ +#include"dyn_ack_params.h" + +void dyn_ack_params::register_params(ini_params & p) { + p.register_int_param("DACK", 0, 2, reinterpret_cast(m_dack), + "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."); + p.register_bool_param("DACK_EQ", m_dack_eq, "enable dynamic ackermannization for transtivity of equalities"); + p.register_unsigned_param("DACK_THRESHOLD", m_dack_threshold, "number of times the congruence rule must be used before Leibniz's axiom is expanded"); + p.register_double_param("DACK_FACTOR", m_dack_factor, "number of instance per conflict"); + p.register_unsigned_param("DACK_GC", m_dack_gc, "Dynamic ackermannization garbage collection frequency (per conflict)."); + p.register_double_param("DACK_GC_INV_DECAY", m_dack_gc_inv_decay); +} + + diff --git a/lib/dyn_ack_params.h b/lib/dyn_ack_params.h new file mode 100644 index 000000000..c5325195e --- /dev/null +++ b/lib/dyn_ack_params.h @@ -0,0 +1,53 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + dyn_ack_params.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-05-18. + +Revision History: + +--*/ +#ifndef _DYN_ACK_PARAMS_H_ +#define _DYN_ACK_PARAMS_H_ + +#include"ini_file.h" + +enum dyn_ack_strategy { + DACK_DISABLED, + DACK_ROOT, // congruence is the root of the conflict + DACK_CR // congruence used during conflict resolution +}; + +struct dyn_ack_params { + dyn_ack_strategy m_dack; + bool m_dack_eq; + double m_dack_factor; + unsigned m_dack_threshold; + unsigned m_dack_gc; + double m_dack_gc_inv_decay; + +public: + dyn_ack_params(): + m_dack(DACK_ROOT), + m_dack_eq(false), + m_dack_factor(0.1), + m_dack_threshold(10), + m_dack_gc(2000), + m_dack_gc_inv_decay(0.8) { + } + + void register_params(ini_params & p); +}; + + +#endif /* _DYN_ACK_PARAMS_H_ */ + diff --git a/lib/eager_bit_blaster.cpp b/lib/eager_bit_blaster.cpp new file mode 100644 index 000000000..2c7cab462 --- /dev/null +++ b/lib/eager_bit_blaster.cpp @@ -0,0 +1,436 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + eager_bit_blaster.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-10-02. + +Revision History: + +--*/ + +#include"ast_ll_pp.h" +#include"eager_bit_blaster.h" + +eager_bit_blaster::basic_plugin::basic_plugin(ast_manager & m, eager_bit_blaster::bv_plugin & p, basic_simplifier_plugin & s): + simplifier_plugin(symbol("basic"), m), + m_main(p), + m_s(s) { +} + +eager_bit_blaster::basic_plugin::~basic_plugin() { +} + +bool eager_bit_blaster::basic_plugin::reduce(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + if (f->get_decl_kind() == OP_ITE) { + SASSERT(num_args == 3); + return m_main.reduce_ite(args[0], args[1], args[2], result); + } + else if (f->get_decl_kind() == OP_NOT) { + // the internalizer assumes there is not double negation (not (not x)) + SASSERT(num_args == 1); + m_s.mk_not(args[0], result); + return true; + } + return false; +} + +eager_bit_blaster::bv_plugin::bv_plugin(ast_manager & m, bit_blaster_params const & p): + simplifier_plugin(symbol("bv"), m), + m_util(m), + m_bb(m, p), + m_s(m) { +} + +eager_bit_blaster::bv_plugin::~bv_plugin() { +} + +void eager_bit_blaster::bv_plugin::get_bits(expr * n, expr_ref_vector & out_bits) { + rational val; + unsigned bv_size; + if (m_util.is_numeral(n, val, bv_size)) { + TRACE("eager_bb_bug", tout << "bv_size: " << bv_size << "\n";); + m_bb.num2bits(val, bv_size, out_bits); + SASSERT(out_bits.size() == bv_size); + } + else if (m_util.is_mkbv(n)) { + out_bits.append(to_app(n)->get_num_args(), to_app(n)->get_args()); + } + else { + unsigned bv_size = m_util.get_bv_size(n); + for (unsigned i = 0; i < bv_size; i++) { + parameter p(i); + out_bits.push_back(m_manager.mk_app(get_family_id(), OP_BIT2BOOL, 1, &p, 1, &n)); + } + SASSERT(bv_size == out_bits.size()); + } +} + +inline app * eager_bit_blaster::bv_plugin::mk_mkbv(expr_ref_vector const & bits) { +#ifdef Z3DEBUG + for (unsigned i = 0; i < bits.size(); i++) { + expr * b = bits.get(i); + SASSERT(!m_manager.is_not(b) || !m_manager.is_not(to_app(b)->get_arg(0))); + } +#endif + return m_manager.mk_app(get_family_id(), OP_MKBV, bits.size(), bits.c_ptr()); +} + +#define MK_UNARY_REDUCE(OP, BB_OP) \ +void eager_bit_blaster::bv_plugin::OP(expr * arg, expr_ref & result) { \ + expr_ref_vector bits(m_manager); \ + get_bits(arg, bits); \ + expr_ref_vector out_bits(m_manager); \ + m_bb.BB_OP(bits.size(), bits.c_ptr(), out_bits); \ + result = mk_mkbv(out_bits); \ +} + +#define MK_BIN_REDUCE(OP, BB_OP) \ +void eager_bit_blaster::bv_plugin::OP(expr * arg1, expr * arg2, expr_ref & result) { \ + expr_ref_vector bits1(m_manager); \ + expr_ref_vector bits2(m_manager); \ + get_bits(arg1, bits1); \ + get_bits(arg2, bits2); \ + expr_ref_vector out_bits(m_manager); \ + m_bb.BB_OP(bits1.size(), bits1.c_ptr(), bits2.c_ptr(), out_bits); \ + result = mk_mkbv(out_bits); \ +} + +#define MK_BIN_AC_FLAT_REDUCE(OP, BIN_OP, S_OP, BB_OP) \ +MK_BIN_REDUCE(BIN_OP, BB_OP); \ +void eager_bit_blaster::bv_plugin::OP(unsigned num_args, expr * const * args, expr_ref & result) { \ + SASSERT(num_args > 0); \ + if (num_args == 2) { \ + BIN_OP(args[0], args[1], result); \ + return; \ + } \ + \ + ptr_buffer args_bits; \ + for (unsigned i = 0; i < num_args; i++) { \ + expr_ref_vector * bits = alloc(expr_ref_vector, m_manager); \ + get_bits(args[i], *bits); \ + args_bits.push_back(bits); \ + } \ + \ + unsigned bv_size = m_util.get_bv_size(args[0]); \ + expr_ref_vector new_bits(m_manager); \ + for (unsigned i = 0; i < bv_size; i++) { \ + expr_ref_vector arg_bits(m_manager); \ + for (unsigned j = 0; j < num_args; j++) \ + arg_bits.push_back(args_bits[j]->get(i)); \ + expr_ref new_bit(m_manager); \ + m_s.S_OP(arg_bits.size(), arg_bits.c_ptr(), new_bit); \ + new_bits.push_back(new_bit); \ + } \ + result = mk_mkbv(new_bits); \ + std::for_each(args_bits.begin(), args_bits.end(), delete_proc()); \ +} + +#define MK_BIN_AC_REDUCE(OP, BIN_OP, BB_OP) \ +MK_BIN_REDUCE(BIN_OP, BB_OP); \ +void eager_bit_blaster::bv_plugin::OP(unsigned num_args, expr * const * args, expr_ref & result) { \ + SASSERT(num_args > 0); \ + result = args[0]; \ + for (unsigned i = 1; i < num_args; i++) { \ + expr_ref new_result(m_manager); \ + BIN_OP(result.get(), args[i], new_result); \ + result = new_result; \ + } \ +} + +#define MK_BIN_PRED_REDUCE(OP, BB_OP) \ +void eager_bit_blaster::bv_plugin::OP(expr * arg1, expr * arg2, expr_ref & result) { \ + expr_ref_vector bits1(m_manager); \ + expr_ref_vector bits2(m_manager); \ + get_bits(arg1, bits1); \ + get_bits(arg2, bits2); \ + m_bb.BB_OP(bits1.size(), bits1.c_ptr(), bits2.c_ptr(), result); \ +} + +#define MK_PARAMETRIC_UNARY_REDUCE(OP, BB_OP) \ +void eager_bit_blaster::bv_plugin::OP(expr * arg, unsigned n, expr_ref & result) { \ + expr_ref_vector bits(m_manager); \ + get_bits(arg, bits); \ + expr_ref_vector out_bits(m_manager); \ + m_bb.BB_OP(bits.size(), bits.c_ptr(), n, out_bits); \ + result = mk_mkbv(out_bits); \ +} + +MK_UNARY_REDUCE(reduce_not, mk_not); +MK_BIN_AC_FLAT_REDUCE(reduce_or, reduce_bin_or, mk_or, mk_or); +MK_BIN_AC_FLAT_REDUCE(reduce_and, reduce_bin_and, mk_and, mk_and); +MK_BIN_AC_FLAT_REDUCE(reduce_nor, reduce_bin_nor, mk_nor, mk_nor); +MK_BIN_AC_FLAT_REDUCE(reduce_nand, reduce_bin_nand, mk_nand, mk_nand); +MK_BIN_REDUCE(reduce_xor, mk_xor); +MK_BIN_REDUCE(reduce_xnor, mk_xnor); +MK_UNARY_REDUCE(reduce_neg, mk_neg); +MK_BIN_AC_REDUCE(reduce_add, reduce_bin_add, mk_adder); +MK_BIN_AC_REDUCE(reduce_mul, reduce_bin_mul, mk_multiplier); +MK_BIN_PRED_REDUCE(reduce_sle, mk_sle); +MK_BIN_PRED_REDUCE(reduce_ule, mk_ule); +MK_PARAMETRIC_UNARY_REDUCE(reduce_rotate_left, mk_rotate_left); +MK_PARAMETRIC_UNARY_REDUCE(reduce_rotate_right, mk_rotate_right); +MK_PARAMETRIC_UNARY_REDUCE(reduce_sign_extend, mk_sign_extend); +MK_PARAMETRIC_UNARY_REDUCE(reduce_zero_extend, mk_zero_extend); +MK_UNARY_REDUCE(reduce_redor, mk_redor); +MK_UNARY_REDUCE(reduce_redand, mk_redand); +MK_BIN_REDUCE(reduce_shl, mk_shl); +MK_BIN_REDUCE(reduce_ashr, mk_ashr); +MK_BIN_REDUCE(reduce_lshr, mk_lshr); +MK_BIN_REDUCE(reduce_comp, mk_comp); +MK_BIN_REDUCE(reduce_udiv, mk_udiv); +MK_BIN_REDUCE(reduce_urem, mk_urem); +MK_BIN_REDUCE(reduce_sdiv, mk_sdiv); +MK_BIN_REDUCE(reduce_srem, mk_srem); +MK_BIN_REDUCE(reduce_smod, mk_smod); + +void eager_bit_blaster::bv_plugin::reduce_extract(unsigned start, unsigned end, expr * arg, expr_ref & result) { + expr_ref_vector bits(m_manager); + get_bits(arg, bits); + expr_ref_vector out_bits(m_manager); + for (unsigned i = start; i <= end; ++i) + out_bits.push_back(bits.get(i)); + result = mk_mkbv(out_bits); +} + +void eager_bit_blaster::bv_plugin::reduce_concat(unsigned num_args, expr * const * args, expr_ref & result) { + expr_ref_vector out_bits(m_manager); + unsigned i = num_args; + while (i > 0) { + i--; + expr_ref_vector bits(m_manager); + get_bits(args[i], bits); + out_bits.append(bits.size(), bits.c_ptr()); + } + result = mk_mkbv(out_bits); +} + +bool eager_bit_blaster::bv_plugin::reduce_ite(expr * arg1, expr * arg2, expr * arg3, expr_ref & result) { + sort * s = m_manager.get_sort(arg2); + if (!m_util.is_bv_sort(s)) + return false; + expr_ref_vector bits1(m_manager); + expr_ref_vector bits2(m_manager); + get_bits(arg2, bits1); + get_bits(arg3, bits2); + SASSERT(bits1.size() == bits2.size()); + expr_ref_vector out_bits(m_manager); + unsigned bv_size = bits1.size(); + for (unsigned i = 0; i < bv_size; i++) { + expr_ref new_bit(m_manager); + m_s.mk_ite(arg1, bits1.get(i), bits2.get(i), new_bit); + out_bits.push_back(new_bit); + } + result = mk_mkbv(out_bits); + return true; +} + +bool eager_bit_blaster::bv_plugin::reduce(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + bv_op_kind k = static_cast(f->get_decl_kind()); + switch (k) { + case OP_BNOT: + SASSERT(num_args == 1); + reduce_not(args[0], result); + return true; + case OP_BOR: + reduce_or(num_args, args, result); + return true; + case OP_BAND: + reduce_and(num_args, args, result); + return true; + case OP_BNOR: + reduce_nor(num_args, args, result); + return true; + case OP_BNAND: + reduce_nand(num_args, args, result); + return true; + case OP_BXOR: + SASSERT(num_args == 2); + reduce_xor(args[0], args[1], result); + return true; + case OP_BXNOR: + SASSERT(num_args == 2); + reduce_xnor(args[0], args[1], result); + return true; + case OP_BNEG: + SASSERT(num_args == 1); + reduce_neg(args[0], result); + return true; + case OP_BADD: + reduce_add(num_args, args, result); + return true; + case OP_BMUL: + reduce_mul(num_args, args, result); + return true; + case OP_BIT1: + case OP_BIT0: + case OP_BSUB: + // I'm assuming the expressions were simplified before invoking this method. + UNREACHABLE(); + return false; + case OP_BSDIV: + case OP_BUDIV: + case OP_BSREM: + case OP_BUREM: + case OP_BSMOD: + // I'm assuming the expressions were simplified before invoking this method. + UNREACHABLE(); + return false; + case OP_BSDIV0: + case OP_BUDIV0: + case OP_BSREM0: + case OP_BUREM0: + case OP_BSMOD0: + // do nothing... these are uninterpreted + return true; + case OP_BSDIV_I: + SASSERT(num_args == 2); + reduce_sdiv(args[0], args[1], result); + return true; + case OP_BUDIV_I: + SASSERT(num_args == 2); + reduce_udiv(args[0], args[1], result); + return true; + case OP_BSREM_I: + SASSERT(num_args == 2); + reduce_srem(args[0], args[1], result); + return true; + case OP_BUREM_I: + SASSERT(num_args == 2); + reduce_urem(args[0], args[1], result); + return true; + case OP_BSMOD_I: + SASSERT(num_args == 2); + reduce_smod(args[0], args[1], result); + return true; + case OP_ULEQ: + SASSERT(num_args == 2); + reduce_ule(args[0], args[1], result); + return true; + case OP_SLEQ: + SASSERT(num_args == 2); + reduce_sle(args[0], args[1], result); + return true; + case OP_UGEQ: + case OP_SGEQ: + case OP_ULT: + case OP_SLT: + case OP_UGT: + case OP_SGT: + // I'm assuming the expressions were simplified before invoking this method. + UNREACHABLE(); + return false; + case OP_EXTRACT: + SASSERT(num_args == 1); + reduce_extract(f->get_parameter(1).get_int(), f->get_parameter(0).get_int(), args[0], result); + return true; + case OP_CONCAT: + reduce_concat(num_args, args, result); + return true; + case OP_SIGN_EXT: + SASSERT(num_args == 1); + reduce_sign_extend(args[0], f->get_parameter(0).get_int(), result); + return true; + case OP_ZERO_EXT: + SASSERT(num_args == 1); + reduce_zero_extend(args[0], f->get_parameter(0).get_int(), result); + return true; + case OP_REPEAT: + UNREACHABLE(); + return false; + case OP_BREDOR: + SASSERT(num_args == 1); + reduce_redor(args[0], result); + return true; + case OP_BREDAND: + SASSERT(num_args == 1); + reduce_redand(args[0], result); + return true; + case OP_BCOMP: + SASSERT(num_args == 2); + reduce_comp(args[0], args[1], result); + return true; + case OP_BSHL: + SASSERT(num_args == 2); + reduce_shl(args[0], args[1], result); + return true; + case OP_BLSHR: + SASSERT(num_args == 2); + reduce_lshr(args[0], args[1], result); + return true; + case OP_BASHR: + SASSERT(num_args == 2); + reduce_ashr(args[0], args[1], result); + return true; + case OP_ROTATE_LEFT: + SASSERT(num_args == 1); + reduce_rotate_left(args[0], f->get_parameter(0).get_int(), result); + return true; + case OP_ROTATE_RIGHT: + SASSERT(num_args == 1); + reduce_rotate_right(args[0], f->get_parameter(0).get_int(), result); + return true; + default: + return false; + } +} + +bool eager_bit_blaster::bv_plugin::reduce_eq(expr * lhs, expr * rhs, expr_ref & result) { + TRACE("eager_bb_eq", tout << mk_ll_pp(lhs, m_manager) << "\n" << mk_ll_pp(rhs, m_manager) << "\n";); + SASSERT(m_util.get_bv_size(lhs) == m_util.get_bv_size(rhs)); + expr_ref_vector bits1(m_manager); + expr_ref_vector bits2(m_manager); + get_bits(lhs, bits1); + get_bits(rhs, bits2); + SASSERT(bits1.size() == bits2.size()); + m_bb.mk_eq(bits1.size(), bits1.c_ptr(), bits2.c_ptr(), result); + return true; +} + +bool eager_bit_blaster::bv_plugin::reduce_distinct(unsigned num_args, expr * const * args, expr_ref & result) { + if (num_args <= 1) { + result = m_manager.mk_true(); + } + if (num_args == 2) { + expr_ref tmp(m_manager); + reduce_eq(args[0], args[1], tmp); + m_s.mk_not(tmp, result); + } + else { + expr_ref_vector new_args(m_manager); + for (unsigned i = 0; i < num_args - 1; i++) { + expr * a1 = args[i]; + for (unsigned j = i + 1; j < num_args; j++) { + expr * a2 = args[j]; + expr_ref tmp1(m_manager); + reduce_eq(a1, a2, tmp1); + expr_ref tmp2(m_manager); + m_s.mk_not(tmp1, tmp2); + new_args.push_back(tmp2); + } + } + m_s.mk_and(new_args.size(), new_args.c_ptr(), result); + } + return true; +} + +eager_bit_blaster::eager_bit_blaster(ast_manager & m, bit_blaster_params const & p): + m_simplifier(m) { + m_simplifier.enable_ac_support(false); + bv_plugin * bv_p = alloc(bv_plugin, m, p); + m_simplifier.register_plugin(bv_p); + m_simplifier.register_plugin(alloc(basic_plugin, m, *bv_p, bv_p->get_basic_simplifier())); +} + +void eager_bit_blaster::operator()(expr * s, expr_ref & r, proof_ref & p) { + m_simplifier.operator()(s, r, p); +} + diff --git a/lib/eager_bit_blaster.h b/lib/eager_bit_blaster.h new file mode 100644 index 000000000..d3d0284d1 --- /dev/null +++ b/lib/eager_bit_blaster.h @@ -0,0 +1,107 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + eager_bit_blaster.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-10-02. + +Revision History: + +--*/ +#ifndef _EAGER_BIT_BLASTER_H_ +#define _EAGER_BIT_BLASTER_H_ + +#include"bv_decl_plugin.h" +#include"bit_blaster.h" +#include"simplifier.h" +#include"basic_simplifier_plugin.h" + +class eager_bit_blaster { + + class bv_plugin : public simplifier_plugin { + bv_util m_util; + bit_blaster m_bb; + basic_simplifier_plugin m_s; + + void get_bits(expr * n, expr_ref_vector & out_bits); + app * mk_mkbv(expr_ref_vector const & bits); + + void reduce_not(expr * arg, expr_ref & result); + void reduce_bin_or(expr * arg1, expr * arg2, expr_ref & result); + void reduce_or(unsigned num_args, expr * const * args, expr_ref & result); + void reduce_bin_and(expr * arg1, expr * arg2, expr_ref & result); + void reduce_and(unsigned num_args, expr * const * args, expr_ref & result); + void reduce_bin_nor(expr * arg1, expr * arg2, expr_ref & result); + void reduce_nor(unsigned num_args, expr * const * args, expr_ref & result); + void reduce_bin_nand(expr * arg1, expr * arg2, expr_ref & result); + void reduce_nand(unsigned num_args, expr * const * args, expr_ref & result); + void reduce_xor(expr * arg1, expr * arg2, expr_ref & result); + void reduce_xnor(expr * arg1, expr * arg2, expr_ref & result); + + void reduce_neg(expr * arg, expr_ref & result); + void reduce_bin_add(expr * arg1, expr * arg2, expr_ref & result); + void reduce_add(unsigned num_args, expr * const * args, expr_ref & result); + void reduce_bin_mul(expr * arg1, expr * arg2, expr_ref & result); + void reduce_mul(unsigned num_args, expr * const * args, expr_ref & result); + void reduce_sdiv(expr * arg1, expr * arg2, expr_ref & result); + void reduce_udiv(expr * arg1, expr * arg2, expr_ref & result); + void reduce_srem(expr * arg1, expr * arg2, expr_ref & result); + void reduce_urem(expr * arg1, expr * arg2, expr_ref & result); + void reduce_smod(expr * arg1, expr * arg2, expr_ref & result); + void reduce_sle(expr * arg1, expr * arg2, expr_ref & result); + void reduce_ule(expr * arg1, expr * arg2, expr_ref & result); + + void reduce_concat(unsigned num_args, expr * const * args, expr_ref & result); + void reduce_extract(unsigned start, unsigned end, expr * arg, expr_ref & result); + + void reduce_redor(expr * arg, expr_ref & result); + void reduce_redand(expr * arg, expr_ref & result); + + void reduce_comp(expr * arg1, expr * arg2, expr_ref & result); + void reduce_shl(expr * arg1, expr * arg2, expr_ref & result); + void reduce_ashr(expr * arg1, expr * arg2, expr_ref & result); + void reduce_lshr(expr * arg1, expr * arg2, expr_ref & result); + + void reduce_rotate_left(expr * arg, unsigned n, expr_ref & result); + void reduce_rotate_right(expr * arg, unsigned n, expr_ref & result); + void reduce_sign_extend(expr * arg, unsigned n, expr_ref & result); + void reduce_zero_extend(expr * arg, unsigned n, expr_ref & result); + + public: + bv_plugin(ast_manager & m, bit_blaster_params const & p); + virtual ~bv_plugin(); + virtual bool reduce(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + virtual bool reduce_eq(expr * lhs, expr * rhs, expr_ref & result); + virtual bool reduce_distinct(unsigned num_args, expr * const * args, expr_ref & result); + basic_simplifier_plugin & get_basic_simplifier() { return m_s; } + bool reduce_ite(expr * arg1, expr * arg2, expr * arg3, expr_ref & result); + }; + + /** + \brief Plugin for handling the term-ite. + */ + class basic_plugin : public simplifier_plugin { + bv_plugin & m_main; + basic_simplifier_plugin & m_s; + public: + basic_plugin(ast_manager & m, bv_plugin & p, basic_simplifier_plugin & s); + virtual ~basic_plugin(); + virtual bool reduce(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + }; + + simplifier m_simplifier; +public: + eager_bit_blaster(ast_manager & m, bit_blaster_params const & p); + void operator()(expr * s, expr_ref & r, proof_ref & p); +}; + +#endif /* _EAGER_BIT_BLASTER_H_ */ + diff --git a/lib/elim_bounds.cpp b/lib/elim_bounds.cpp new file mode 100644 index 000000000..ce15d9eb1 --- /dev/null +++ b/lib/elim_bounds.cpp @@ -0,0 +1,222 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + elim_bounds.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-28. + +Revision History: + +--*/ +#include"elim_bounds.h" +#include"used_vars.h" +#include"obj_hashtable.h" +#include"var_subst.h" +#include"ast_pp.h" + +elim_bounds::elim_bounds(ast_manager & m): + m_manager(m), + m_util(m) { +} + +/** + \brief Find bounds of the form + + (<= x k) + (<= (+ x (* -1 y)) k) + (<= (+ x (* -1 t)) k) + (<= (+ t (* -1 x)) k) + + x and y are a bound variables, t is a ground term and k is a numeral + + It also detects >=, and the atom can be negated. +*/ +bool elim_bounds::is_bound(expr * n, var * & lower, var * & upper) { + upper = 0; + lower = 0; + bool neg = false; + if (m_manager.is_not(n)) { + n = to_app(n)->get_arg(0); + neg = true; + } + + bool le = false; + if (m_util.is_le(n)) { + SASSERT(m_util.is_numeral(to_app(n)->get_arg(1))); + n = to_app(n)->get_arg(0); + le = true; + } + else if (m_util.is_ge(n)) { + SASSERT(m_util.is_numeral(to_app(n)->get_arg(1))); + n = to_app(n)->get_arg(0); + le = false; + } + else { + return false; + } + + if (neg) + le = !le; + + if (is_var(n)) { + upper = to_var(n); + } + else if (m_util.is_add(n) && to_app(n)->get_num_args() == 2) { + expr * arg1 = to_app(n)->get_arg(0); + expr * arg2 = to_app(n)->get_arg(1); + if (is_var(arg1)) + upper = to_var(arg1); + else if (!is_ground(arg1)) + return false; + rational k; + bool is_int; + if (m_util.is_mul(arg2) && m_util.is_numeral(to_app(arg2)->get_arg(0), k, is_int) && k.is_minus_one()) { + arg2 = to_app(arg2)->get_arg(1); + if (is_var(arg2)) + lower = to_var(arg2); + else if (!is_ground(arg2)) + return false; // not supported + } + else { + return false; // not supported + } + } + else { + return false; + } + + if (!le) + std::swap(upper, lower); + + return true; +} + +bool elim_bounds::is_bound(expr * n) { + var * lower, * upper; + return is_bound(n, lower, upper); +} + +void elim_bounds::operator()(quantifier * q, expr_ref & r) { + if (!q->is_forall()) { + r = q; + return; + } + expr * n = q->get_expr(); + ptr_buffer atoms; + if (m_manager.is_or(n)) + atoms.append(to_app(n)->get_num_args(), to_app(n)->get_args()); + else + atoms.push_back(n); + used_vars m_used_vars; + // collect non-candidates + unsigned sz = atoms.size(); + for (unsigned i = 0; i < sz; i++) { + expr * a = atoms[i]; + if (!is_bound(a)) + m_used_vars.process(a); + } + if (m_used_vars.uses_all_vars(q->get_num_decls())) { + r = q; + return; + } + // collect candidates + obj_hashtable m_lowers; + obj_hashtable m_uppers; + obj_hashtable m_candidate_set; + ptr_buffer m_candidates; +#define ADD_CANDIDATE(V) if (!m_lowers.contains(V) && !m_uppers.contains(V)) { m_candidate_set.insert(V); m_candidates.push_back(V); } + for (unsigned i = 0; i < sz; i++) { + expr * a = atoms[i]; + var * lower = 0; + var * upper = 0; + if (is_bound(a, lower, upper)) { + if (lower != 0 && !m_used_vars.contains(lower->get_idx())) { + ADD_CANDIDATE(lower); + m_lowers.insert(lower); + } + if (upper != 0 && !m_used_vars.contains(upper->get_idx())) { + ADD_CANDIDATE(upper); + m_uppers.insert(upper); + } + } + } + TRACE("elim_bounds", tout << "candidates:\n"; for (unsigned i = 0; i < m_candidates.size(); i++) tout << mk_pp(m_candidates[i], m_manager) << "\n";); + // remove candidates that have lower and upper bounds + for (unsigned i = 0; i < m_candidates.size(); i++) { + var * v = m_candidates[i]; + if (m_lowers.contains(v) && m_uppers.contains(v)) + m_candidate_set.erase(v); + } + TRACE("elim_bounds", tout << "candidates after filter:\n"; for (unsigned i = 0; i < m_candidates.size(); i++) tout << mk_pp(m_candidates[i], m_manager) << "\n";); + if (m_candidate_set.empty()) { + r = q; + return; + } + // remove bounds that contain variables in m_candidate_set + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + expr * a = atoms[i]; + var * lower = 0; + var * upper = 0; + if (is_bound(a, lower, upper) && ((lower != 0 && m_candidate_set.contains(lower)) || (upper != 0 && m_candidate_set.contains(upper)))) + continue; + atoms[j] = a; + j++; + } + atoms.resize(j); + expr * new_body = 0; + switch (atoms.size()) { + case 0: + r = m_manager.mk_false(); + return; + case 1: + new_body = atoms[0]; + break; + default: + new_body = m_manager.mk_or(atoms.size(), atoms.c_ptr()); + break; + } + quantifier_ref new_q(m_manager); + new_q = m_manager.update_quantifier(q, new_body); + elim_unused_vars(m_manager, new_q, r); + TRACE("elim_bounds", tout << mk_pp(q, m_manager) << "\n" << mk_pp(r, m_manager) << "\n";); +} + +bool elim_bounds_star::visit_quantifier(quantifier * q) { + if (!q->is_forall() || q->get_num_patterns() != 0) + return true; + bool visited = true; + visit(q->get_expr(), visited); + return visited; +} + +void elim_bounds_star::reduce1_quantifier(quantifier * q) { + if (!q->is_forall() || q->get_num_patterns() != 0) { + cache_result(q, q, 0); + return; + } + quantifier_ref new_q(m_manager); + expr * new_body = 0; + proof * new_pr; + get_cached(q->get_expr(), new_body, new_pr); + new_q = m_manager.update_quantifier(q, new_body); + expr_ref r(m_manager); + m_elim(new_q, r); + if (q == r.get()) { + cache_result(q, q, 0); + return; + } + proof_ref pr(m_manager); + if (m_manager.fine_grain_proofs()) + pr = m_manager.mk_rewrite(q, r); // TODO: improve justification + cache_result(q, r, pr); +} + diff --git a/lib/elim_bounds.h b/lib/elim_bounds.h new file mode 100644 index 000000000..66b6ed524 --- /dev/null +++ b/lib/elim_bounds.h @@ -0,0 +1,69 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + elim_bounds.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-28. + +Revision History: + +--*/ +#ifndef _ELIM_BOUNDS_H_ +#define _ELIM_BOUNDS_H_ + +#include"ast.h" +#include"arith_decl_plugin.h" +#include"simplifier.h" + +/** + \brief Functor for eliminating irrelevant bounds in quantified formulas. + + Example: + (forall (x Int) (y Int) (or (not (>= y x) (not (>= x 0)) (= (select a x) 1)))) + + The bound (>= y x) is irrelevant and can be eliminated. + + This can be easily proved by using Fourier-Motzkin elimination. + + Limitations & Assumptions: + - It assumes the input formula was already simplified. + - It can only handle bounds in the diff-logic fragment. + + \remark This operation is subsumed by Fourier-Motzkin elimination. +*/ +class elim_bounds { + ast_manager & m_manager; + arith_util m_util; + bool is_bound(expr * n, var * & lower, var * & upper); + bool is_bound(expr * n); +public: + elim_bounds(ast_manager & m); + void operator()(quantifier * q, expr_ref & r); +}; + +/** + \brief Functor for applying elim_bounds in all + universal quantifiers in an expression. + + Assumption: the formula was already skolemized. +*/ +class elim_bounds_star : public simplifier { +protected: + elim_bounds m_elim; + virtual bool visit_quantifier(quantifier * q); + virtual void reduce1_quantifier(quantifier * q); +public: + elim_bounds_star(ast_manager & m):simplifier(m), m_elim(m) { enable_ac_support(false); } + virtual ~elim_bounds_star() {} +}; + +#endif /* _ELIM_BOUNDS_H_ */ + diff --git a/lib/elim_distinct.cpp b/lib/elim_distinct.cpp new file mode 100644 index 000000000..bf4502822 --- /dev/null +++ b/lib/elim_distinct.cpp @@ -0,0 +1,232 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + elim_distinct.cpp + +Abstract: + + Replace one distinct(t0, ..., tn) with (t0 = 0 and ... and tn = n) + when the sort of t0...tn is uninterpreted. + +Author: + + Leonardo de Moura (leonardo) 2011-04-28 + +Revision History: + +--*/ +#include"elim_distinct.h" +#include"assertion_set.h" +#include"model_converter.h" +#include"arith_decl_plugin.h" +#include"rewriter_def.h" +#include"critical_flet.h" + +struct elim_distinct::imp { + struct mc : public model_converter { + ast_ref_vector m_asts; + sort * m_usort; + obj_map m_inv_map; + public: + mc(ast_manager & m):m_asts(m) { + } + }; + + struct u2i_cfg : public default_rewriter_cfg { + arith_util m_autil; + ast_ref_vector m_asts; + sort * m_usort; + sort * m_int_sort; + obj_map m_f2f; + + ast_manager & m() const { return m_asts.get_manager(); } + + bool must_remap(func_decl * f) const { + if (f->get_range() == m_usort) + return true; + for (unsigned i = 0; i < f->get_arity(); i++) { + if (f->get_domain(i) == m_usort) + return true; + } + return false; + } + + sort * remap(sort * s) { + return (s == m_usort) ? m_int_sort : s; + } + + func_decl * remap(func_decl * f) { + ptr_buffer new_domain; + sort * new_range = remap(f->get_range()); + for (unsigned i = 0; i < f->get_arity(); i++) + new_domain.push_back(remap(f->get_domain(i))); + func_decl * new_f = m().mk_func_decl(f->get_name(), new_domain.size(), new_domain.c_ptr(), new_range); + m_asts.push_back(new_f); + m_asts.push_back(f); + m_f2f.insert(f, new_f); + return new_f; + } + + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + result_pr = 0; + func_decl * new_f; + if (m_f2f.find(f, new_f)) { + result = m().mk_app(new_f, num, args); + return BR_DONE; + } + + if (!must_remap(f)) + return BR_FAILED; + + if (m().is_eq(f)) { + result = m().mk_eq(args[0], args[1]); + return BR_DONE; + } + + if (m().is_ite(f)) { + result = m().mk_ite(args[0], args[1], args[2]); + return BR_DONE; + } + + if (f->get_family_id() != null_family_id || f->get_info() != 0) { + throw elim_distinct_exception("uninterpreted sort is used in interpreted function symbol"); + } + + new_f = remap(f); + result = m().mk_app(new_f, num, args); + return BR_DONE; + } + + bool reduce_quantifier(quantifier * old_q, + expr * new_body, + expr * const * new_patterns, + expr * const * new_no_patterns, + expr_ref & result, + proof_ref & result_pr) { + throw elim_distinct_exception("elim-distinct tactic does not support quantifiers"); + } + + u2i_cfg(ast_manager & m, sort * u): + m_autil(m), + m_asts(m), + m_usort(u) { + m_asts.push_back(u); + m_int_sort = m_autil.mk_int(); + m_asts.push_back(m_int_sort); + } + }; + + class u2i : public rewriter_tpl { + u2i_cfg m_cfg; + public: + u2i(ast_manager & m, sort * u): + rewriter_tpl(m, false, m_cfg), + m_cfg(m, u) { + if (m.proofs_enabled()) + throw elim_distinct_exception("elim-distinct tactic does not support proof generation"); + } + arith_util & autil() { return cfg().m_autil; } + }; + + ast_manager & m_manager; + u2i * m_u2i; + + imp(ast_manager & m):m_manager(m), m_u2i(0) {} + ast_manager & m() const { return m_manager; } + + bool is_distinct(expr * t) { + if (!m().is_distinct(t)) + return false; + if (to_app(t)->get_num_args() == 0) + return false; + return m().is_uninterp(m().get_sort(to_app(t)->get_arg(0))); + } + + model_converter * operator()(assertion_set & s, app * d) { + if (d && !is_distinct(d)) + d = 0; + app * r = 0; + unsigned sz = s.size(); + for (unsigned i = 0; i < sz; i++) { + expr * curr = s.form(i); + if (curr == d) + break; + if (is_distinct(curr)) { + if (!r || to_app(curr)->get_num_args() > r->get_num_args()) + r = to_app(curr); + } + } + if (d != 0) + r = d; + if (r == 0) + return 0; + sort * u = m().get_sort(to_app(r)->get_arg(0)); + u2i conv(m(), u); + { + critical_flet l1(m_u2i, &conv); + expr_ref new_curr(m()); + for (unsigned i = 0; i < sz; i++) { + expr * curr = s.form(i); + if (curr == r) { + unsigned num = r->get_num_args(); + for (unsigned j = 0; j < num; j++) { + expr * arg = r->get_arg(j); + conv(arg, new_curr); + expr * eq = m().mk_eq(new_curr, conv.autil().mk_numeral(rational(j), true)); + s.assert_expr(eq); + } + new_curr = m().mk_true(); + } + else { + conv(curr, new_curr); + } + + s.update(i, new_curr); + } + } + + // TODO: create model converter + return 0; + } + + void cancel() { + // Remark: m_u2i is protected by the omp global critical section. + // If this is a performance problem, then replace critical_flet by a custom flet that uses a different + // section name + #pragma omp critical (critical_flet) + { + if (m_u2i) + m_u2i->cancel(); + } + } +}; + +template class rewriter_tpl; + +elim_distinct::elim_distinct(ast_manager & m) { + m_imp = alloc(imp, m); +} + +elim_distinct::~elim_distinct() { + dealloc(m_imp); +} + +model_converter * elim_distinct::operator()(assertion_set & s, app * d) { + return m_imp->operator()(s, d); +} + +void elim_distinct::cancel() { + m_imp->cancel(); +} + +void elim_distinct::reset() { + cleanup(); +} + +void elim_distinct::cleanup() { + ast_manager & m = m_imp->m(); + dealloc(m_imp); + m_imp = alloc(imp, m); +} diff --git a/lib/elim_distinct.h b/lib/elim_distinct.h new file mode 100644 index 000000000..2e04997bd --- /dev/null +++ b/lib/elim_distinct.h @@ -0,0 +1,54 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + elim_distinct.h + +Abstract: + + Replace one distinct(t0, ..., tn) with (t0 = 0 and ... and tn = n) + when the sort of t0...tn is uninterpreted. + +Author: + + Leonardo de Moura (leonardo) 2011-04-28 + +Revision History: + +--*/ +#ifndef _ELIM_DISTINCT_H_ +#define _ELIM_DISTINCT_H_ + +#include"assertion_set.h" + +class model_converter; +class ast_manager; +class assertion_set; + +class elim_distinct_exception : public default_exception { +public: + elim_distinct_exception(char const * msg):default_exception(msg) {} +}; + +class elim_distinct { + struct imp; + imp * m_imp; +public: + elim_distinct(ast_manager & m); + ~elim_distinct(); + + /** + \brief It throws an elim_distinct_exception if the strategy failed. + If d == 0, then search for the biggest distinct(t0, ..., tn) in the assertion set. + if d != 0, then succeed only if d is in the assertion set. + */ + model_converter * operator()(assertion_set & s, app * d); + + void cancel(); + + void reset(); + void cleanup(); +}; + +#endif diff --git a/lib/elim_term_ite.cpp b/lib/elim_term_ite.cpp new file mode 100644 index 000000000..2e1671f43 --- /dev/null +++ b/lib/elim_term_ite.cpp @@ -0,0 +1,160 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + elim_term_ite.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-12. + +Revision History: + +--*/ +#include"elim_term_ite.h" +#include"ast_smt2_pp.h" + +void elim_term_ite::operator()(expr * n, + expr_ref_vector & new_defs, + proof_ref_vector & new_def_proofs, + expr_ref & r, + proof_ref & pr) { + + m_coarse_proofs.reset(); + m_new_defs = &new_defs; + m_new_def_proofs = &new_def_proofs; + reduce_core(n); + expr * r2; + proof * pr2; + get_cached(n, r2, pr2); + r = r2; + switch (m_manager.proof_mode()) { + case PGM_DISABLED: + pr = m_manager.mk_undef_proof(); + break; + case PGM_COARSE: + remove_duplicates(m_coarse_proofs); + pr = n == r2 ? m_manager.mk_oeq_reflexivity(n) : m_manager.mk_apply_defs(n, r, m_coarse_proofs.size(), m_coarse_proofs.c_ptr()); + break; + case PGM_FINE: + pr = pr2 == 0 ? m_manager.mk_oeq_reflexivity(n) : pr2; + break; + } + m_coarse_proofs.reset(); +} + +void elim_term_ite::reduce_core(expr * n) { + m_todo.reset(); + if (!is_cached(n)) { + m_todo.push_back(n); + while (!m_todo.empty()) { + expr * n = m_todo.back(); + if (is_cached(n)) { + m_todo.pop_back(); + } + else if (visit_children(n)) { + m_todo.pop_back(); + reduce1(n); + } + } + } +} + +bool elim_term_ite::visit_children(expr * n) { + bool visited = true; + unsigned j; + switch(n->get_kind()) { + case AST_VAR: + return true; + case AST_APP: + j = to_app(n)->get_num_args(); + while (j > 0) { + --j; + visit(to_app(n)->get_arg(j), visited); + } + return visited; + case AST_QUANTIFIER: + visit(to_quantifier(n)->get_expr(), visited); + return visited; + default: + UNREACHABLE(); + return true; + } +} + +void elim_term_ite::reduce1(expr * n) { + switch (n->get_kind()) { + case AST_VAR: + cache_result(n, n, 0); + break; + case AST_APP: + reduce1_app(to_app(n)); + break; + case AST_QUANTIFIER: + reduce1_quantifier(to_quantifier(n)); + break; + default: + UNREACHABLE(); + } +} + +void elim_term_ite::reduce1_app(app * n) { + m_args.reset(); + + func_decl * decl = n->get_decl(); + proof_ref p1(m_manager); + get_args(n, m_args, p1); + if (!m_manager.fine_grain_proofs()) + p1 = 0; + + expr_ref r(m_manager); + r = m_manager.mk_app(decl, m_args.size(), m_args.c_ptr()); + if (m_manager.is_term_ite(r)) { + expr_ref new_def(m_manager); + proof_ref new_def_pr(m_manager); + app_ref new_r(m_manager); + proof_ref new_pr(m_manager); + if (m_defined_names.mk_name(r, new_def, new_def_pr, new_r, new_pr)) { + CTRACE("elim_term_ite_bug", new_def.get() == 0, tout << mk_ismt2_pp(r, m_manager) << "\n";); + SASSERT(new_def.get() != 0); + m_new_defs->push_back(new_def); + if (m_manager.fine_grain_proofs()) { + m_new_def_proofs->push_back(new_def_pr); + new_pr = m_manager.mk_transitivity(p1, new_pr); + } + else { + // [Leo] This looks fishy... why do we add 0 into m_coarse_proofs when fine_grain_proofs are disabled? + new_pr = 0; + if (m_manager.proofs_enabled()) + m_coarse_proofs.push_back(new_pr); + } + } + else { + SASSERT(new_def.get() == 0); + if (!m_manager.fine_grain_proofs()) + new_pr = 0; + } + cache_result(n, new_r, new_pr); + } + else { + cache_result(n, r, p1); + } +} + +void elim_term_ite::reduce1_quantifier(quantifier * q) { + expr * new_body; + proof * new_body_pr; + get_cached(q->get_expr(), new_body, new_body_pr); + + quantifier * new_q = m_manager.update_quantifier(q, new_body); + proof * p = q == new_q ? 0 : m_manager.mk_oeq_quant_intro(q, new_q, new_body_pr); + cache_result(q, new_q, p); +} + + + diff --git a/lib/elim_term_ite.h b/lib/elim_term_ite.h new file mode 100644 index 000000000..5c15af9e5 --- /dev/null +++ b/lib/elim_term_ite.h @@ -0,0 +1,50 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + elim_term_ite.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-12. + +Revision History: + +--*/ +#ifndef _ELIM_TERM_ITE_H_ +#define _ELIM_TERM_ITE_H_ + +#include"simplifier.h" +#include"defined_names.h" + +class elim_term_ite : public simplifier { + defined_names & m_defined_names; + proof_ref_vector m_coarse_proofs; + expr_ref_vector * m_new_defs; + proof_ref_vector * m_new_def_proofs; + void reduce_core(expr * n); + bool visit_children(expr * n); + void reduce1(expr * n); + void reduce1_app(app * n); + void reduce1_quantifier(quantifier * q); +public: + elim_term_ite(ast_manager & m, defined_names & d):simplifier(m), m_defined_names(d), m_coarse_proofs(m) { + m_use_oeq = true; + enable_ac_support(false); + } + virtual ~elim_term_ite() {} + void operator()(expr * n, // [IN] + expr_ref_vector & new_defs, // [OUT] new definitions + proof_ref_vector & new_def_proofs, // [OUT] proofs of the new definitions + expr_ref & r, // [OUT] resultant expression + proof_ref & pr // [OUT] proof for (~ n r) + ); +}; + +#endif /* _ELIM_TERM_ITE_H_ */ + diff --git a/lib/elim_term_ite_strategy.cpp b/lib/elim_term_ite_strategy.cpp new file mode 100644 index 000000000..85d79b37a --- /dev/null +++ b/lib/elim_term_ite_strategy.cpp @@ -0,0 +1,174 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + elim_term_ite_strategy.cpp + +Abstract: + + Eliminate term if-then-else by adding + new fresh auxiliary variables. + +Author: + + Leonardo (leonardo) 2011-06-15 + +Notes: + +--*/ +#include"elim_term_ite_strategy.h" +#include"defined_names.h" +#include"rewriter_def.h" +#include"filter_model_converter.h" +#include"cooperate.h" + +struct elim_term_ite_strategy::imp { + + struct rw_cfg : public default_rewriter_cfg { + ast_manager & m; + defined_names m_defined_names; + ref m_mc; + assertion_set * m_set; + unsigned long long m_max_memory; // in bytes + bool m_produce_models; + unsigned m_num_fresh; + + bool max_steps_exceeded(unsigned num_steps) const { + cooperate("elim term ite"); + if (memory::get_allocation_size() > m_max_memory) + throw elim_term_ite_exception(STE_MAX_MEMORY_MSG); + return false; + } + + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + if (!m.is_term_ite(f)) + return BR_FAILED; + expr_ref new_ite(m); + new_ite = m.mk_app(f, num, args); + + expr_ref new_def(m); + proof_ref new_def_pr(m); + app_ref _result(m); + if (m_defined_names.mk_name(new_ite, new_def, new_def_pr, _result, result_pr)) { + m_set->assert_expr(new_def, new_def_pr); + m_num_fresh++; + if (m_produce_models) { + if (!m_mc) + m_mc = alloc(filter_model_converter, m); + m_mc->insert(_result->get_decl()); + } + } + result = _result.get(); + return BR_DONE; + } + + rw_cfg(ast_manager & _m, params_ref const & p): + m(_m), + m_defined_names(m, 0 /* don't use prefix */) { + updt_params(p); + m_set = 0; + m_num_fresh = 0; + } + + void updt_params(params_ref const & p) { + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + m_produce_models = p.get_bool(":produce-models", false); + } + }; + + struct rw : public rewriter_tpl { + rw_cfg m_cfg; + + rw(ast_manager & m, params_ref const & p): + rewriter_tpl(m, m.proofs_enabled(), m_cfg), + m_cfg(m, p) { + } + }; + + ast_manager & m; + rw m_rw; + + imp(ast_manager & _m, params_ref const & p): + m(_m), + m_rw(m, p) { + } + + void set_cancel(bool f) { + m_rw.set_cancel(f); + } + + void updt_params(params_ref const & p) { + m_rw.cfg().updt_params(p); + } + + void operator()(assertion_set & s, model_converter_ref & mc) { + mc = 0; + if (s.inconsistent()) + return; + { + as_st_report report("elim-term-ite", s); + m_rw.m_cfg.m_num_fresh = 0; + m_rw.m_cfg.m_set = &s; + expr_ref new_curr(m); + proof_ref new_pr(m); + unsigned size = s.size(); + for (unsigned idx = 0; idx < size; idx++) { + expr * curr = s.form(idx); + m_rw(curr, new_curr, new_pr); + if (m.proofs_enabled()) { + proof * pr = s.pr(idx); + new_pr = m.mk_modus_ponens(pr, new_pr); + } + s.update(idx, new_curr, new_pr); + } + mc = m_rw.m_cfg.m_mc.get(); + } + report_st_progress(":elim-term-ite-consts", m_rw.m_cfg.m_num_fresh); + } +}; + + +elim_term_ite_strategy::elim_term_ite_strategy(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); +} + +elim_term_ite_strategy::~elim_term_ite_strategy() { + dealloc(m_imp); +} + +void elim_term_ite_strategy::updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); +} + +void elim_term_ite_strategy::get_param_descrs(param_descrs & r) { + insert_max_memory(r); + insert_produce_models(r); +} + +void elim_term_ite_strategy::operator()(assertion_set & s, model_converter_ref & mc) { + m_imp->operator()(s, mc); +} + +void elim_term_ite_strategy::set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); +} + +void elim_term_ite_strategy::cleanup() { + ast_manager & m = m_imp->m; + imp * d = m_imp; + #pragma omp critical (as_st_cancel) + { + d = m_imp; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (as_st_cancel) + { + m_imp = d; + } +} + diff --git a/lib/elim_term_ite_strategy.h b/lib/elim_term_ite_strategy.h new file mode 100644 index 000000000..67eae2b29 --- /dev/null +++ b/lib/elim_term_ite_strategy.h @@ -0,0 +1,53 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + elim_term_ite_strategy.h + +Abstract: + + Eliminate term if-then-else by adding + new fresh auxiliary variables. + +Author: + + Leonardo (leonardo) 2011-06-15 + +Notes: + +--*/ +#ifndef _ELIM_TERM_ITE_STRATEGY_H_ +#define _ELIM_TERM_ITE_STRATEGY_H_ + +#include"strategy_exception.h" +#include"model_converter.h" +#include"params.h" +#include"assertion_set_strategy.h" + +class assertion_set; +MK_ST_EXCEPTION(elim_term_ite_exception); + +class elim_term_ite_strategy : public assertion_set_strategy { + struct imp; + imp * m_imp; + params_ref m_params; +public: + elim_term_ite_strategy(ast_manager & m, params_ref const & p = params_ref()); + virtual ~elim_term_ite_strategy(); + + virtual void updt_params(params_ref const & p); + static void get_param_descrs(param_descrs & r); + virtual void collect_param_descrs(param_descrs & r) { get_param_descrs(r); } + + virtual void operator()(assertion_set & s, model_converter_ref & mc); + + virtual void cleanup(); + virtual void set_cancel(bool f); +}; + +inline as_st * mk_elim_term_ite(ast_manager & m, params_ref const & p = params_ref()) { + return clean(alloc(elim_term_ite_strategy, m, p)); +} + +#endif diff --git a/lib/elim_term_ite_tactic.cpp b/lib/elim_term_ite_tactic.cpp new file mode 100644 index 000000000..4f04859ac --- /dev/null +++ b/lib/elim_term_ite_tactic.cpp @@ -0,0 +1,198 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + elim_term_ite_tactic.cpp + +Abstract: + + Eliminate term if-then-else by adding + new fresh auxiliary variables. + +Author: + + Leonardo (leonardo) 2011-12-29 + +Notes: + +--*/ +#include"tactical.h" +#include"defined_names.h" +#include"rewriter_def.h" +#include"filter_model_converter.h" +#include"cooperate.h" + +class elim_term_ite_tactic : public tactic { + + struct rw_cfg : public default_rewriter_cfg { + ast_manager & m; + defined_names m_defined_names; + ref m_mc; + goal * m_goal; + unsigned long long m_max_memory; // in bytes + bool m_produce_models; + unsigned m_num_fresh; + + bool max_steps_exceeded(unsigned num_steps) const { + cooperate("elim term ite"); + if (memory::get_allocation_size() > m_max_memory) + throw tactic_exception(TACTIC_MAX_MEMORY_MSG); + return false; + } + + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + if (!m.is_term_ite(f)) + return BR_FAILED; + expr_ref new_ite(m); + new_ite = m.mk_app(f, num, args); + + expr_ref new_def(m); + proof_ref new_def_pr(m); + app_ref _result(m); + if (m_defined_names.mk_name(new_ite, new_def, new_def_pr, _result, result_pr)) { + m_goal->assert_expr(new_def, new_def_pr, 0); + m_num_fresh++; + if (m_produce_models) { + if (!m_mc) + m_mc = alloc(filter_model_converter, m); + m_mc->insert(_result->get_decl()); + } + } + result = _result.get(); + return BR_DONE; + } + + rw_cfg(ast_manager & _m, params_ref const & p): + m(_m), + m_defined_names(m, 0 /* don't use prefix */) { + updt_params(p); + m_goal = 0; + m_num_fresh = 0; + } + + void updt_params(params_ref const & p) { + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + } + }; + + struct rw : public rewriter_tpl { + rw_cfg m_cfg; + + rw(ast_manager & m, params_ref const & p): + rewriter_tpl(m, m.proofs_enabled(), m_cfg), + m_cfg(m, p) { + } + }; + + struct imp { + ast_manager & m; + rw m_rw; + + imp(ast_manager & _m, params_ref const & p): + m(_m), + m_rw(m, p) { + } + + void set_cancel(bool f) { + m_rw.set_cancel(f); + } + + void updt_params(params_ref const & p) { + m_rw.cfg().updt_params(p); + } + + void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + mc = 0; pc = 0; core = 0; + tactic_report report("elim-term-ite", *g); + bool produce_proofs = g->proofs_enabled(); + m_rw.cfg().m_produce_models = g->models_enabled(); + + m_rw.m_cfg.m_num_fresh = 0; + m_rw.m_cfg.m_goal = g.get(); + expr_ref new_curr(m); + proof_ref new_pr(m); + unsigned size = g->size(); + for (unsigned idx = 0; idx < size; idx++) { + expr * curr = g->form(idx); + m_rw(curr, new_curr, new_pr); + if (produce_proofs) { + proof * pr = g->pr(idx); + new_pr = m.mk_modus_ponens(pr, new_pr); + } + g->update(idx, new_curr, new_pr, g->dep(idx)); + } + mc = m_rw.m_cfg.m_mc.get(); + report_tactic_progress(":elim-term-ite-consts", m_rw.m_cfg.m_num_fresh); + g->inc_depth(); + result.push_back(g.get()); + TRACE("elim_term_ite", g->display(tout);); + SASSERT(g->is_well_sorted()); + } + }; + + imp * m_imp; + params_ref m_params; +public: + elim_term_ite_tactic(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(elim_term_ite_tactic, m, m_params); + } + + virtual ~elim_term_ite_tactic() { + dealloc(m_imp); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + m_imp->m_rw.cfg().updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + insert_max_memory(r); + insert_max_steps(r); + r.insert(":max-args", CPK_UINT, + "(default: 128) maximum number of arguments (per application) that will be considered by the greedy (quadratic) heuristic."); + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + (*m_imp)(in, result, mc, pc, core); + } + + virtual void cleanup() { + ast_manager & m = m_imp->m; + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + } + + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + +tactic * mk_elim_term_ite_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(elim_term_ite_tactic, m, p)); +} diff --git a/lib/elim_term_ite_tactic.h b/lib/elim_term_ite_tactic.h new file mode 100644 index 000000000..9bc0a9294 --- /dev/null +++ b/lib/elim_term_ite_tactic.h @@ -0,0 +1,29 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + elim_term_ite_tactic.h + +Abstract: + + Eliminate term if-then-else by adding + new fresh auxiliary variables. + +Author: + + Leonardo (leonardo) 2011-12-29 + +Notes: + +--*/ +#ifndef _ELIM_TERM_ITE_TACTIC_H_ +#define _ELIM_TERM_ITE_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_elim_term_ite_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/elim_uncnstr_tactic.cpp b/lib/elim_uncnstr_tactic.cpp new file mode 100644 index 000000000..7d15bc7c0 --- /dev/null +++ b/lib/elim_uncnstr_tactic.cpp @@ -0,0 +1,1076 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + elim_uncnstr_vars.cpp + +Abstract: + + Eliminated unconstrained variables. + +Author: + + Leonardo (leonardo) 2011-10-22 + +Notes: + +--*/ +#include"tactical.h" +#include"extension_model_converter.h" +#include"filter_model_converter.h" +#include"rewriter_def.h" +#include"arith_decl_plugin.h" +#include"bv_decl_plugin.h" +#include"array_decl_plugin.h" +#include"datatype_decl_plugin.h" +#include"cooperate.h" +#include"ast_smt2_pp.h" +#include"ast_ll_pp.h" + +class elim_uncnstr_tactic : public tactic { + + struct imp { + // unconstrained vars collector + struct collect { + expr_fast_mark1 m_visited; + expr_fast_mark2 m_more_than_once; + typedef std::pair frame; + svector m_stack; + ptr_vector m_vars; + expr_sparse_mark m_uncnstr_vars; + + bool visit(expr * t) { + if (m_visited.is_marked(t)) { + if (is_uninterp_const(t)) + m_more_than_once.mark(t); + return true; + } + m_visited.mark(t); + if (is_uninterp_const(t)) { + m_vars.push_back(to_app(t)); + return true; + } + if (is_var(t)) + return true; + if (is_app(t) && to_app(t)->get_num_args() == 0) + return true; + m_stack.push_back(frame(t, 0)); + return false; + } + + void process(expr * t) { + SASSERT(m_stack.empty()); + if (visit(t)) + return; + SASSERT(!m_stack.empty()); + unsigned num; + expr * child; + while (!m_stack.empty()) { + start: + frame & fr = m_stack.back(); + expr * t = fr.first; + switch (t->get_kind()) { + case AST_APP: + num = to_app(t)->get_num_args(); + while (fr.second < num) { + child = to_app(t)->get_arg(fr.second); + fr.second++; + if (!visit(child)) + goto start; + } + m_stack.pop_back(); + break; + case AST_QUANTIFIER: + // don't need to visit patterns + child = to_quantifier(t)->get_expr(); + fr.second++; + if (!visit(child)) + goto start; + m_stack.pop_back(); + break; + default: + UNREACHABLE(); + } + } + } + + void operator()(goal const & g, obj_hashtable & r) { + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + expr * t = g.form(i); + process(t); + } + + ptr_vector::const_iterator it = m_vars.begin(); + ptr_vector::const_iterator end = m_vars.end(); + for (; it != end; ++it) { + if (m_more_than_once.is_marked(*it)) + continue; + r.insert(*it); + } + m_visited.reset(); + m_more_than_once.reset(); + } + }; + + typedef extension_model_converter mc; + + struct rw_cfg : public default_rewriter_cfg { + bool m_produce_proofs; + obj_hashtable & m_vars; + ref m_mc; + arith_util m_a_util; + bv_util m_bv_util; + array_util m_ar_util; + datatype_util m_dt_util; + app_ref_vector m_fresh_vars; + obj_map m_cache; + app_ref_vector m_cache_domain; + unsigned long long m_max_memory; + unsigned m_max_steps; + + rw_cfg(ast_manager & m, bool produce_proofs, obj_hashtable & vars, mc * _m, + unsigned long long max_memory, unsigned max_steps): + m_produce_proofs(produce_proofs), + m_vars(vars), + m_mc(_m), + m_a_util(m), + m_bv_util(m), + m_ar_util(m), + m_dt_util(m), + m_fresh_vars(m), + m_cache_domain(m), + m_max_memory(max_memory), + m_max_steps(max_steps) { + } + + ast_manager & m() const { return m_a_util.get_manager(); } + + bool max_steps_exceeded(unsigned num_steps) const { + cooperate("elim-uncnstr-vars"); + if (memory::get_allocation_size() > m_max_memory) + throw tactic_exception(TACTIC_MAX_MEMORY_MSG); + return num_steps > m_max_steps; + } + + bool uncnstr(expr * arg) const { + return m_vars.contains(arg); + } + + bool uncnstr(unsigned num, expr * const * args) const { + for (unsigned i = 0; i < num; i++) + if (!uncnstr(args[i])) + return false; + return true; + } + + /** + \brief Create a fresh variable for abstracting (f args[0] ... args[num-1]) + Return true if it a new variable was created, and false if the variable already existed for this + application. Store the variable in v + */ + bool mk_fresh_uncnstr_var_for(app * t, app * & v) { + if (m_cache.find(t, v)) { + return false; // variable already existed for this application + } + + v = m().mk_fresh_const(0, m().get_sort(t)); + TRACE("elim_uncnstr_bug", tout << "eliminating:\n" << mk_ismt2_pp(t, m()) << "\n";); + TRACE("elim_uncnstr_bug_ll", tout << "eliminating:\n" << mk_bounded_pp(t, m()) << "\n";); + m_fresh_vars.push_back(v); + m_cache_domain.push_back(t); + m_cache.insert(t, v); + return true; + } + + bool mk_fresh_uncnstr_var_for(func_decl * f, unsigned num, expr * const * args, app * & v) { + return mk_fresh_uncnstr_var_for(m().mk_app(f, num, args), v); + } + + bool mk_fresh_uncnstr_var_for(func_decl * f, expr * arg1, expr * arg2, app * & v) { + return mk_fresh_uncnstr_var_for(m().mk_app(f, arg1, arg2), v); + } + + bool mk_fresh_uncnstr_var_for(func_decl * f, expr * arg, app * & v) { + return mk_fresh_uncnstr_var_for(m().mk_app(f, arg), v); + } + + void add_def(expr * v, expr * def) { + SASSERT(uncnstr(v)); + SASSERT(to_app(v)->get_num_args() == 0); + if (m_mc) + m_mc->insert(to_app(v)->get_decl(), def); + } + + void add_defs(unsigned num, expr * const * args, expr * u, expr * identity) { + if (m_mc) { + add_def(args[0], u); + for (unsigned i = 1; i < num; i++) + add_def(args[i], identity); + } + } + + // return a term that is different from t. + bool mk_diff(expr * t, expr_ref & r) { + sort * s = m().get_sort(t); + if (m().is_bool(s)) { + r = m().mk_not(t); + return true; + } + family_id fid = s->get_family_id(); + if (fid == m_a_util.get_family_id()) { + r = m_a_util.mk_add(t, m_a_util.mk_numeral(rational(1), s)); + return true; + } + if (fid == m_bv_util.get_family_id()) { + r = m().mk_app(m_bv_util.get_family_id(), OP_BNOT, t); + return true; + } + if (fid == m_ar_util.get_family_id()) { + if (m().is_uninterp(get_array_range(s))) + return false; + unsigned arity = get_array_arity(s); + for (unsigned i = 0; i < arity; i++) + if (m().is_uninterp(get_array_domain(s, i))) + return false; + // building + // r = (store t i1 ... in d) + // where i1 ... in are arbitrary values + // and d is a term different from (select t i1 ... in) + ptr_buffer new_args; + new_args.push_back(t); + for (unsigned i = 0; i < arity; i++) + new_args.push_back(m().get_some_value(get_array_domain(s, i))); + expr_ref sel(m()); + sel = m().mk_app(fid, OP_SELECT, new_args.size(), new_args.c_ptr()); + expr_ref diff_sel(m()); + if (!mk_diff(sel, diff_sel)) + return false; + new_args.push_back(diff_sel); + r = m().mk_app(fid, OP_STORE, new_args.size(), new_args.c_ptr()); + return true; + } + if (fid == m_dt_util.get_family_id()) { + // In the current implementation, I only handle the case where + // the datatype has a recursive constructor. + ptr_vector const * constructors = m_dt_util.get_datatype_constructors(s); + ptr_vector::const_iterator it = constructors->begin(); + ptr_vector::const_iterator end = constructors->end(); + for (; it != end; ++it) { + func_decl * constructor = *it; + unsigned num = constructor->get_arity(); + unsigned target = UINT_MAX; + for (unsigned i = 0; i < num; i++) { + sort * s_arg = constructor->get_domain(i); + if (s == s_arg) { + target = i; + continue; + } + if (m().is_uninterp(s_arg)) + break; + } + if (target == UINT_MAX) + continue; + // use the constructor the distinct term constructor(...,t,...) + ptr_buffer new_args; + for (unsigned i = 0; i < num; i++) { + if (i == target) { + new_args.push_back(t); + } + else { + new_args.push_back(m().get_some_value(constructor->get_domain(i))); + } + } + r = m().mk_app(constructor, new_args.size(), new_args.c_ptr()); + return true; + } + // TODO: handle more cases. + return false; + } + return false; + } + + app * process_eq(func_decl * f, expr * arg1, expr * arg2) { + expr * v; + expr * t; + if (uncnstr(arg1)) { + v = arg1; + t = arg2; + } + else if (uncnstr(arg2)) { + v = arg2; + t = arg1; + } + else { + return 0; + } + + sort * s = m().get_sort(arg1); + + // Remark: + // I currently do not support unconstrained vars that have + // uninterpreted sorts, for the following reasons: + // - Soundness + // (forall ((x S) (y S)) (= x y)) + // (not (= c1 c2)) + // + // The constants c1 and c2 have only one occurrence in + // the formula above, but they are not really unconstrained. + // The quantifier forces S to have interpretations of size 1. + // If we replace (= c1 c2) with fresh k. The formula will + // become satisfiable. + // + // - Even if the formula is quantifier free, I would still + // have to build an interpretation for the eliminated + // variables. + // + if (!m().is_fully_interp(s)) + return 0; + + // If the interpreted sort has only one element, + // then it is unsound to eliminate the unconstrained variable in the equality + sort_size sz = s->get_num_elements(); + + if (sz.is_finite() && sz.size() <= 1) + return 0; + + if (!m_mc) { + // easy case, model generation is disabled. + app * u; + mk_fresh_uncnstr_var_for(f, arg1, arg2, u); + return u; + } + + expr_ref d(m()); + if (mk_diff(t, d)) { + app * u; + if (!mk_fresh_uncnstr_var_for(f, arg1, arg2, u)) + return u; + add_def(v, m().mk_ite(u, t, d)); + return u; + } + return 0; + } + + app * process_basic_app(func_decl * f, unsigned num, expr * const * args) { + SASSERT(f->get_family_id() == m().get_basic_family_id()); + switch (f->get_decl_kind()) { + case OP_ITE: + SASSERT(num == 3); + if (uncnstr(args[1]) && uncnstr(args[2])) { + app * r; + if (!mk_fresh_uncnstr_var_for(f, num, args, r)) + return r; + add_def(args[1], r); + add_def(args[2], r); + return r; + } + if (uncnstr(args[0]) && uncnstr(args[1])) { + app * r; + if (!mk_fresh_uncnstr_var_for(f, num, args, r)) + return r; + add_def(args[0], m().mk_true()); + add_def(args[1], r); + return r; + } + if (uncnstr(args[0]) && uncnstr(args[2])) { + app * r; + if (!mk_fresh_uncnstr_var_for(f, num, args, r)) + return r; + add_def(args[0], m().mk_false()); + add_def(args[2], r); + return r; + } + return 0; + case OP_NOT: + SASSERT(num == 1); + if (uncnstr(args[0])) { + app * r; + if (!mk_fresh_uncnstr_var_for(f, num, args, r)) + return r; + if (m_mc) + add_def(args[0], m().mk_not(r)); + return r; + } + return 0; + case OP_AND: + if (num > 0 && uncnstr(num, args)) { + app * r; + if (!mk_fresh_uncnstr_var_for(f, num, args, r)) + return r; + if (m_mc) + add_defs(num, args, r, m().mk_true()); + return r; + } + return 0; + case OP_OR: + if (num > 0 && uncnstr(num, args)) { + app * r; + if (!mk_fresh_uncnstr_var_for(f, num, args, r)) + return r; + if (m_mc) + add_defs(num, args, r, m().mk_false()); + return r; + } + return 0; + case OP_IFF: + case OP_EQ: + SASSERT(num == 2); + return process_eq(f, args[0], args[1]); + default: + return 0; + } + } + + app * process_le_ge(func_decl * f, expr * arg1, expr * arg2, bool le) { + expr * v; + expr * t; + if (uncnstr(arg1)) { + v = arg1; + t = arg2; + } + else if (uncnstr(arg2)) { + v = arg2; + t = arg1; + le = !le; + } + else { + return 0; + } + app * u; + if (!mk_fresh_uncnstr_var_for(f, arg1, arg2, u)) + return u; + if (!m_mc) + return u; + // v = ite(u, t, t + 1) if le + // v = ite(u, t, t - 1) if !le + add_def(v, m().mk_ite(u, t, m_a_util.mk_add(t, m_a_util.mk_numeral(rational(le ? 1 : -1), m().get_sort(arg1))))); + return u; + } + + app * process_add(family_id fid, decl_kind add_k, decl_kind sub_k, unsigned num, expr * const * args) { + if (num == 0) + return 0; + unsigned i; + expr * v = 0; + for (i = 0; i < num; i++) { + expr * arg = args[i]; + if (uncnstr(arg)) { + v = arg; + break; + } + } + if (v == 0) + return 0; + app * u; + if (!mk_fresh_uncnstr_var_for(m().mk_app(fid, add_k, num, args), u)) + return u; + if (!m_mc) + return u; + ptr_buffer new_args; + for (unsigned j = 0; j < num; j++) { + if (j == i) + continue; + new_args.push_back(args[j]); + } + if (new_args.empty()) { + add_def(v, u); + } + else { + expr * rest; + if (new_args.size() == 1) + rest = new_args[0]; + else + rest = m().mk_app(fid, add_k, new_args.size(), new_args.c_ptr()); + add_def(v, m().mk_app(fid, sub_k, u, rest)); + } + return u; + } + + app * process_arith_mul(func_decl * f, unsigned num, expr * const * args) { + if (num == 0) + return 0; + sort * s = m().get_sort(args[0]); + if (uncnstr(num, args)) { + app * r; + if (!mk_fresh_uncnstr_var_for(f, num, args, r)) + return r; + if (m_mc) + add_defs(num, args, r, m_a_util.mk_numeral(rational(1), s)); + return r; + } + // c * v case for reals + bool is_int; + rational val; + if (num == 2 && uncnstr(args[1]) && m_a_util.is_numeral(args[0], val, is_int) && !is_int) { + if (val.is_zero()) + return 0; + app * r; + if (!mk_fresh_uncnstr_var_for(f, num, args, r)) + return r; + if (m_mc) { + val = rational(1) / val; + add_def(args[1], m_a_util.mk_mul(m_a_util.mk_numeral(val, false), r)); + } + return r; + } + return 0; + } + + app * process_arith_app(func_decl * f, unsigned num, expr * const * args) { + + SASSERT(f->get_family_id() == m_a_util.get_family_id()); + switch (f->get_decl_kind()) { + case OP_ADD: + return process_add(f->get_family_id(), OP_ADD, OP_SUB, num, args); + case OP_MUL: + return process_arith_mul(f, num, args); + case OP_LE: + SASSERT(num == 2); + return process_le_ge(f, args[0], args[1], true); + case OP_GE: + SASSERT(num == 2); + return process_le_ge(f, args[0], args[1], false); + default: + return 0; + } + } + + app * process_bv_mul(func_decl * f, unsigned num, expr * const * args) { + if (num == 0) + return 0; + if (uncnstr(num, args)) { + sort * s = m().get_sort(args[0]); + app * r; + if (!mk_fresh_uncnstr_var_for(f, num, args, r)) + return r; + if (m_mc) + add_defs(num, args, r, m_bv_util.mk_numeral(rational(1), s)); + return r; + } + // c * v (c is even) case + unsigned bv_size; + rational val; + rational inv; + if (num == 2 && + uncnstr(args[1]) && + m_bv_util.is_numeral(args[0], val, bv_size) && + m_bv_util.mult_inverse(val, bv_size, inv)) { + app * r; + if (!mk_fresh_uncnstr_var_for(f, num, args, r)) + return r; + sort * s = m().get_sort(args[1]); + if (m_mc) + add_def(args[1], m_bv_util.mk_bv_mul(m_bv_util.mk_numeral(inv, s), r)); + return r; + } + return 0; + } + + app * process_extract(func_decl * f, expr * arg) { + if (!uncnstr(arg)) + return 0; + app * r; + if (!mk_fresh_uncnstr_var_for(f, arg, r)) + return r; + if (!m_mc) + return r; + unsigned high = m_bv_util.get_extract_high(f); + unsigned low = m_bv_util.get_extract_low(f); + unsigned bv_size = m_bv_util.get_bv_size(m().get_sort(arg)); + if (bv_size == high - low + 1) { + add_def(arg, r); + } + else { + ptr_buffer args; + if (high < bv_size - 1) + args.push_back(m_bv_util.mk_numeral(rational(0), bv_size - high - 1)); + args.push_back(r); + if (low > 0) + args.push_back(m_bv_util.mk_numeral(rational(0), low)); + add_def(arg, m_bv_util.mk_concat(args.size(), args.c_ptr())); + } + return r; + } + + app * process_bv_div(func_decl * f, expr * arg1, expr * arg2) { + if (uncnstr(arg1) && uncnstr(arg2)) { + sort * s = m().get_sort(arg1); + app * r; + if (!mk_fresh_uncnstr_var_for(f, arg1, arg2, r)) + return r; + if (!m_mc) + return r; + add_def(arg1, r); + add_def(arg2, m_bv_util.mk_numeral(rational(1), s)); + return r; + } + return 0; + } + + app * process_concat(func_decl * f, unsigned num, expr * const * args) { + if (num == 0) + return 0; + if (!uncnstr(num, args)) + return 0; + app * r; + if (!mk_fresh_uncnstr_var_for(f, num, args, r)) + return r; + if (m_mc) { + unsigned i = num; + unsigned low = 0; + while (i > 0) { + --i; + expr * arg = args[i]; + unsigned sz = m_bv_util.get_bv_size(arg); + add_def(arg, m_bv_util.mk_extract(low + sz - 1, low, r)); + low += sz; + } + } + return r; + } + + app * process_bv_le(func_decl * f, expr * arg1, expr * arg2, bool is_signed) { + if (m_produce_proofs) { + // The result of bv_le is not just introducing a new fresh name, + // we need a side condition. + // TODO: the correct proof step + return 0; + } + if (uncnstr(arg1)) { + // v <= t + expr * v = arg1; + expr * t = arg2; + // v <= t ---> (u or t == MAX) u is fresh + // add definition v = ite(u or t == MAX, t, t+1) + unsigned bv_sz = m_bv_util.get_bv_size(arg1); + rational MAX; + if (is_signed) + MAX = m_bv_util.power_of_two(bv_sz - 1) - rational(1); + else + MAX = m_bv_util.power_of_two(bv_sz) - rational(1); + app * u; + bool is_new = mk_fresh_uncnstr_var_for(f, arg1, arg2, u); + app * r = m().mk_or(u, m().mk_eq(t, m_bv_util.mk_numeral(MAX, bv_sz))); + if (m_mc && is_new) + add_def(v, m().mk_ite(r, t, m_bv_util.mk_bv_add(t, m_bv_util.mk_numeral(rational(1), bv_sz)))); + return r; + } + if (uncnstr(arg2)) { + // v >= t + expr * v = arg2; + expr * t = arg1; + // v >= t ---> (u ot t == MIN) u is fresh + // add definition v = ite(u or t == MIN, t, t-1) + unsigned bv_sz = m_bv_util.get_bv_size(arg1); + rational MIN; + if (is_signed) + MIN = -m_bv_util.power_of_two(bv_sz - 1); + else + MIN = rational(0); + app * u; + bool is_new = mk_fresh_uncnstr_var_for(f, arg1, arg2, u); + app * r = m().mk_or(u, m().mk_eq(t, m_bv_util.mk_numeral(MIN, bv_sz))); + if (m_mc && is_new) + add_def(v, m().mk_ite(r, t, m_bv_util.mk_bv_sub(t, m_bv_util.mk_numeral(rational(1), bv_sz)))); + return r; + } + return 0; + } + + app * process_bv_app(func_decl * f, unsigned num, expr * const * args) { + SASSERT(f->get_family_id() == m_bv_util.get_family_id()); + switch (f->get_decl_kind()) { + case OP_BADD: + return process_add(f->get_family_id(), OP_BADD, OP_BSUB, num, args); + case OP_BMUL: + return process_bv_mul(f, num, args); + case OP_BSDIV: + case OP_BUDIV: + case OP_BSDIV_I: + case OP_BUDIV_I: + SASSERT(num == 2); + return process_bv_div(f, args[0], args[1]); + case OP_SLEQ: + SASSERT(num == 2); + return process_bv_le(f, args[0], args[1], true); + case OP_ULEQ: + SASSERT(num == 2); + return process_bv_le(f, args[0], args[1], false); + case OP_CONCAT: + return process_concat(f, num, args); + case OP_EXTRACT: + SASSERT(num == 1); + return process_extract(f, args[0]); + case OP_BNOT: + SASSERT(num == 1); + if (uncnstr(args[0])) { + app * r; + if (!mk_fresh_uncnstr_var_for(f, num, args, r)) + return r; + if (m_mc) + add_def(args[0], m().mk_app(f, r)); + return r; + } + return 0; + case OP_BOR: + if (num > 0 && uncnstr(num, args)) { + sort * s = m().get_sort(args[0]); + app * r; + if (!mk_fresh_uncnstr_var_for(f, num, args, r)) + return r; + if (m_mc) + add_defs(num, args, r, m_bv_util.mk_numeral(rational(0), s)); + return r; + } + return 0; + default: + return 0; + } + } + + app * process_array_app(func_decl * f, unsigned num, expr * const * args) { + SASSERT(f->get_family_id() == m_ar_util.get_family_id()); + switch (f->get_decl_kind()) { + case OP_SELECT: + if (uncnstr(args[0])) { + app * r; + if (!mk_fresh_uncnstr_var_for(f, num, args, r)) + return r; + sort * s = m().get_sort(args[0]); + if (m_mc) + add_def(args[0], m_ar_util.mk_const_array(s, r)); + return r; + } + return 0; + case OP_STORE: + if (uncnstr(args[0]) && uncnstr(args[num-1])) { + app * r; + if (!mk_fresh_uncnstr_var_for(f, num, args, r)) + return r; + if (m_mc) { + add_def(args[num-1], m().mk_app(m_ar_util.get_family_id(), OP_SELECT, num-1, args)); + add_def(args[0], r); + } + return r; + } + default: + return 0; + } + } + + app * process_datatype_app(func_decl * f, unsigned num, expr * const * args) { + if (m_dt_util.is_recognizer(f)) { + SASSERT(num == 1); + if (uncnstr(args[0])) { + if (!m_mc) { + app * r; + mk_fresh_uncnstr_var_for(f, num, args, r); + return r; + } + // TODO: handle model generation + } + } + else if (m_dt_util.is_accessor(f)) { + SASSERT(num == 1); + if (uncnstr(args[0])) { + if (!m_mc) { + app * r; + mk_fresh_uncnstr_var_for(f, num, args, r); + return r; + } + func_decl * c = m_dt_util.get_accessor_constructor(f); + for (unsigned i = 0; i < c->get_arity(); i++) + if (!m().is_fully_interp(c->get_domain(i))) + return 0; + app * u; + if (!mk_fresh_uncnstr_var_for(f, num, args, u)) + return u; + ptr_vector const * accs = m_dt_util.get_constructor_accessors(c); + ptr_buffer new_args; + for (unsigned i = 0; i < accs->size(); i++) { + if (accs->get(i) == f) + new_args.push_back(u); + else + new_args.push_back(m().get_some_value(c->get_domain(i))); + } + add_def(args[0], m().mk_app(c, new_args.size(), new_args.c_ptr())); + return u; + } + } + else if (m_dt_util.is_constructor(f)) { + if (uncnstr(num, args)) { + app * u; + if (!mk_fresh_uncnstr_var_for(f, num, args, u)) + return u; + if (!m_mc) + return u; + ptr_vector const * accs = m_dt_util.get_constructor_accessors(f); + for (unsigned i = 0; i < num; i++) { + add_def(args[i], m().mk_app(accs->get(i), u)); + } + return u; + } + } + return 0; + } + + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + if (num == 0) + return BR_FAILED; + family_id fid = f->get_family_id(); + if (fid == null_family_id) + return BR_FAILED; + + for (unsigned i = 0; i < num; i++) { + if (!is_ground(args[i])) + return BR_FAILED; // non-ground terms are not handled. + } + + app * u = 0; + + if (fid == m().get_basic_family_id()) + u = process_basic_app(f, num, args); + else if (fid == m_a_util.get_family_id()) + u = process_arith_app(f, num, args); + else if (fid == m_bv_util.get_family_id()) + u = process_bv_app(f, num, args); + else if (fid == m_ar_util.get_family_id()) + u = process_array_app(f, num, args); + else if (fid == m_dt_util.get_family_id()) + u = process_datatype_app(f, num, args); + + if (u == 0) + return BR_FAILED; + + result = u; + if (m_produce_proofs) { + expr * s = m().mk_app(f, num, args); + expr * eq = m().mk_eq(s, u); + proof * pr1 = m().mk_def_intro(eq); + result_pr = m().mk_apply_def(s, u, pr1); + } + + return BR_DONE; + } + }; + + class rw : public rewriter_tpl { + rw_cfg m_cfg; + public: + rw(ast_manager & m, bool produce_proofs, obj_hashtable & vars, mc * _m, + unsigned long long max_memory, unsigned max_steps): + rewriter_tpl(m, produce_proofs, m_cfg), + m_cfg(m, produce_proofs, vars, _m, max_memory, max_steps) { + } + }; + + ast_manager & m_manager; + ref m_mc; + obj_hashtable m_vars; + scoped_ptr m_rw; + unsigned m_num_elim_apps; + unsigned long long m_max_memory; + unsigned m_max_steps; + + imp(ast_manager & m, params_ref const & p): + m_manager(m), + m_num_elim_apps(0) { + updt_params(p); + } + + void updt_params(params_ref const & p) { + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + m_max_steps = p.get_uint(":max-steps", UINT_MAX); + } + + ast_manager & m() { return m_manager; } + + void init_mc(bool produce_models) { + if (!produce_models) { + m_mc = 0; + return; + } + m_mc = alloc(mc, m()); + } + + void init_rw(bool produce_proofs) { + #pragma omp critical (tactic_cancel) + { + m_rw = alloc(rw, m(), produce_proofs, m_vars, m_mc.get(), m_max_memory, m_max_steps); + } + } + + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + mc = 0; pc = 0; core = 0; + bool produce_models = g->models_enabled(); + bool produce_proofs = g->proofs_enabled(); + + TRACE("elim_uncnstr_bug", g->display(tout);); + tactic_report report("elim-uncnstr-vars", *g); + m_vars.reset(); + collect p; + p(*g, m_vars); + if (m_vars.empty()) { + result.push_back(g.get()); + // did not increase depth since it didn't do anything. + return; + } + bool modified = true; + TRACE("elim_uncnstr", tout << "unconstrained variables...\n"; + obj_hashtable::iterator it = m_vars.begin(); + obj_hashtable::iterator end = m_vars.end(); + for (; it != end; ++it) { + expr * v = *it; + tout << mk_ismt2_pp(v, m()) << " "; + } + tout << "\n";); + init_mc(produce_models); + init_rw(produce_proofs); + + expr_ref new_f(m()); + proof_ref new_pr(m()); + unsigned round = 0; + unsigned size = g->size(); + unsigned idx = 0; + while (true) { + for (; idx < size; idx++) { + expr * f = g->form(idx); + m_rw->operator()(f, new_f, new_pr); + if (f == new_f) + continue; + modified = true; + if (produce_proofs) { + proof * pr = g->pr(idx); + new_pr = m().mk_modus_ponens(pr, new_pr); + } + g->update(idx, new_f, new_pr, g->dep(idx)); + } + if (!modified) { + if (round == 0) { + mc = 0; + } + else { + app_ref_vector & fresh_vars = m_rw->cfg().m_fresh_vars; + m_num_elim_apps = fresh_vars.size(); + if (produce_models && !fresh_vars.empty()) { + filter_model_converter * fmc = alloc(filter_model_converter, m()); + for (unsigned i = 0; i < fresh_vars.size(); i++) + fmc->insert(fresh_vars.get(i)->get_decl()); + mc = concat(fmc, m_mc.get()); + } + else { + mc = 0; + } + } + m_mc = 0; + #pragma omp critical (tactic_cancel) + { + m_rw = 0; + } + TRACE("elim_uncnstr", if (mc) mc->display(tout);); + result.push_back(g.get()); + g->inc_depth(); + return; + } + modified = false; + round ++; + size = g->size(); + m_rw->reset(); // reset cache + m_vars.reset(); + { + collect p; + p(*g, m_vars); + } + if (m_vars.empty()) + idx = size; // force to finish + else + idx = 0; + } + } + + void set_cancel(bool f) { + if (m_rw) + m_rw->set_cancel(f); + } + }; + + imp * m_imp; + params_ref m_params; +public: + elim_uncnstr_tactic(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(elim_uncnstr_tactic, m, m_params); + } + + virtual ~elim_uncnstr_tactic() { + dealloc(m_imp); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + insert_max_memory(r); + insert_max_steps(r); + } + + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + (*m_imp)(g, result, mc, pc, core); + report_tactic_progress(":num-elim-apps", get_num_elim_apps()); + } + + virtual void cleanup() { + unsigned num_elim_apps = get_num_elim_apps(); + ast_manager & m = m_imp->m_manager; + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + m_imp->m_num_elim_apps = num_elim_apps; + } + + unsigned get_num_elim_apps() const { + return m_imp->m_num_elim_apps; + } + + virtual void collect_statistics(statistics & st) const { + st.update("eliminated applications", get_num_elim_apps()); + } + + virtual void reset_statistics() { + m_imp->m_num_elim_apps = 0; + } + +protected: + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + +tactic * mk_elim_uncnstr_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(elim_uncnstr_tactic, m, p)); +} diff --git a/lib/elim_uncnstr_tactic.h b/lib/elim_uncnstr_tactic.h new file mode 100644 index 000000000..d83c49262 --- /dev/null +++ b/lib/elim_uncnstr_tactic.h @@ -0,0 +1,30 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + elim_uncnstr_tactic.h + +Abstract: + + Eliminated applications containing unconstrained variables. + +Author: + + Leonardo (leonardo) 2011-10-22 + +Notes: + +--*/ +#ifndef _ELIM_UNCNSTR_TACTIC_H_ +#define _ELIM_UNCNSTR_TACTIC_H_ + +#include"params.h" + +class tactic; +class ast_manager; + +tactic * mk_elim_uncnstr_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif + diff --git a/lib/elim_var_model_converter.cpp b/lib/elim_var_model_converter.cpp new file mode 100644 index 000000000..3acc5918a --- /dev/null +++ b/lib/elim_var_model_converter.cpp @@ -0,0 +1,120 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + elim_var_model_converter.cpp + +Abstract: + + Model converter that introduces eliminated variables in a model. + +Author: + + Leonardo (leonardo) 2011-05-05 + +Notes: + +--*/ +#include"elim_var_model_converter.h" +#include"model_evaluator.h" +#include"ast_smt2_pp.h" +#include"model_v2_pp.h" +#include"ast_pp.h" + +elim_var_model_converter::~elim_var_model_converter() { +} + +struct elim_var_model_converter::set_eval { + elim_var_model_converter * m_owner; + model_evaluator * m_old; + set_eval(elim_var_model_converter * owner, model_evaluator * ev) { + m_owner = owner; + m_old = owner->m_eval; + #pragma omp critical (elim_var_model_converter) + { + owner->m_eval = ev; + } + } + ~set_eval() { + #pragma omp critical (elim_var_model_converter) + { + m_owner->m_eval = m_old; + } + } + +}; + +static void display_decls_info(std::ostream & out, model_ref & md) { + ast_manager & m = md->get_manager(); + unsigned sz = md->get_num_decls(); + for (unsigned i = 0; i < sz; i++) { + func_decl * d = md->get_decl(i); + out << d->get_name(); + out << " ("; + for (unsigned j = 0; j < d->get_arity(); j++) + out << mk_pp(d->get_domain(j), m); + out << mk_pp(d->get_range(), m); + out << ") "; + if (d->get_info()) + out << *(d->get_info()); + out << " :id " << d->get_id() << "\n"; + } +} + +void elim_var_model_converter::operator()(model_ref & md) { + TRACE("elim_var_mc", model_v2_pp(tout, *md); display_decls_info(tout, md);); + model_evaluator ev(*(md.get())); + ev.set_model_completion(true); + expr_ref val(m()); + { + set_eval setter(this, &ev); + unsigned i = m_vars.size(); + while (i > 0) { + --i; + expr * def = m_defs.get(i); + ev(def, val); + TRACE("elim_var_mc", tout << m_vars.get(i)->get_name() << " ->\n" << mk_ismt2_pp(def, m()) << "\n==>\n" << mk_ismt2_pp(val, m()) << "\n";); + func_decl * f = m_vars.get(i); + unsigned arity = f->get_arity(); + if (arity == 0) { + md->register_decl(f, val); + } + else { + func_interp * new_fi = alloc(func_interp, m(), arity); + new_fi->set_else(val); + md->register_decl(f, new_fi); + } + } + } + TRACE("elim_var_mc", model_v2_pp(tout, *md); display_decls_info(tout, md);); +} + +void elim_var_model_converter::cancel() { + #pragma omp critical (elim_var_model_converter) + { + if (m_eval) + m_eval->cancel(); + } +} + +void elim_var_model_converter::display(std::ostream & out) { + ast_manager & m = m_vars.get_manager(); + out << "(elim-var-model-converter"; + for (unsigned i = 0; i < m_vars.size(); i++) { + out << "\n (" << m_vars.get(i)->get_name() << " "; + unsigned indent = m_vars.get(i)->get_name().size() + 4; + out << mk_ismt2_pp(m_defs.get(i), m, indent) << ")"; + } + out << ")" << std::endl; +} + +model_converter * elim_var_model_converter::translate(ast_translation & translator) { + elim_var_model_converter * res = alloc(elim_var_model_converter, translator.to()); + for (unsigned i = 0; i < m_vars.size(); i++) + res->m_vars.push_back(translator(m_vars[i].get())); + for (unsigned i = 0; i < m_defs.size(); i++) + res->m_defs.push_back(translator(m_defs[i].get())); + // m_eval is a transient object. So, it doesn't need to be translated. + return res; +} diff --git a/lib/elim_var_model_converter.h b/lib/elim_var_model_converter.h new file mode 100644 index 000000000..7efdd9224 --- /dev/null +++ b/lib/elim_var_model_converter.h @@ -0,0 +1,56 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + elim_var_model_converter.h + +Abstract: + + Model converter that introduces eliminated variables in a model. + +Author: + + Leonardo (leonardo) 2011-05-05 + +Notes: + +--*/ +#ifndef _ELIM_VAR_MODEL_CONVERTER_H_ +#define _ELIM_VAR_MODEL_CONVERTER_H_ + +#include"ast.h" +#include"model_converter.h" + +class model_evaluator; + +class elim_var_model_converter : public model_converter { + func_decl_ref_vector m_vars; + expr_ref_vector m_defs; + model_evaluator * m_eval; + struct set_eval; +public: + elim_var_model_converter(ast_manager & m):m_vars(m), m_defs(m), m_eval(0) { + } + + virtual ~elim_var_model_converter(); + + ast_manager & m() const { return m_vars.get_manager(); } + + virtual void operator()(model_ref & md); + + virtual void cancel(); + + virtual void display(std::ostream & out); + + // register a variable that was eliminated + void insert(func_decl * v, expr * def) { + m_vars.push_back(v); + m_defs.push_back(def); + } + + virtual model_converter * translate(ast_translation & translator); +}; + + +#endif diff --git a/lib/error_codes.h b/lib/error_codes.h new file mode 100644 index 000000000..4afc9a38d --- /dev/null +++ b/lib/error_codes.h @@ -0,0 +1,37 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + error_codes.h + +Abstract: + + Error codes produced by Z3. + +Author: + + Leonardo de Moura (leonardo) 2007-09-04. + +Revision History: + +--*/ +#ifndef _ERROR_CODES_H_ +#define _ERROR_CODES_H_ + +#define ERR_OK 0 +#define ERR_MEMOUT 101 +#define ERR_TIMEOUT 102 +#define ERR_PARSER 103 +#define ERR_UNSOUNDNESS 104 +#define ERR_INCOMPLETENESS 105 +#define ERR_INI_FILE 106 +#define ERR_NOT_IMPLEMENTED_YET 107 +#define ERR_OPEN_FILE 108 +#define ERR_CMD_LINE 109 +#define ERR_INTERNAL_FATAL 110 +#define ERR_TYPE_CHECK 111 +#define ERR_UNKNOWN_RESULT 112 + +#endif /* _ERROR_CODES_H_ */ + diff --git a/lib/euclidean_solver.cpp b/lib/euclidean_solver.cpp new file mode 100644 index 000000000..718dbb052 --- /dev/null +++ b/lib/euclidean_solver.cpp @@ -0,0 +1,855 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + euclidean_solver.cpp + +Abstract: + + Euclidean Solver with support for explanations. + +Author: + + Leonardo de Moura (leonardo) 2011-07-08. + +Revision History: + +--*/ +#include"euclidean_solver.h" +#include"numeral_buffer.h" +#include"heap.h" + +struct euclidean_solver::imp { + typedef unsigned var; + typedef unsigned justification; + typedef unsynch_mpq_manager numeral_manager; + typedef numeral_buffer mpz_buffer; + typedef numeral_buffer mpq_buffer; + typedef svector justification_vector; + static const justification null_justification = UINT_MAX; +#define null_var UINT_MAX +#define null_eq_idx UINT_MAX + typedef svector var_vector; + typedef svector mpz_vector; + typedef svector mpq_vector; + + struct elim_order_lt { + unsigned_vector & m_solved; + elim_order_lt(unsigned_vector & s):m_solved(s) {} + bool operator()(var x1, var x2) const { return m_solved[x1] < m_solved[x2]; } + }; + + typedef heap var_queue; // queue used for scheduling variables for applying substitution. + + static unsigned pos(unsigned_vector const & xs, unsigned x_i) { + if (xs.empty()) + return UINT_MAX; + int low = 0; + int high = xs.size() - 1; + while (true) { + int mid = low + ((high - low) / 2); + var x_mid = xs[mid]; + if (x_i > x_mid) { + low = mid + 1; + if (low > high) + return UINT_MAX; + } + else if (x_i < x_mid) { + high = mid - 1; + if (low > high) + return UINT_MAX; + } + else { + return mid; + } + } + } + + /** + Equation as[0]*xs[0] + ... + as[n-1]*xs[n-1] + c = 0 with justification bs[0]*js[0] + ... + bs[m-1]*js[m-1] + */ + struct equation { + mpz_vector m_as; + var_vector m_xs; + mpz m_c; + // justification + mpq_vector m_bs; + justification_vector m_js; + + unsigned size() const { return m_xs.size(); } + unsigned js_size() const { return m_js.size(); } + var x(unsigned i) const { return m_xs[i]; } + var & x(unsigned i) { return m_xs[i]; } + mpz const & a(unsigned i) const { return m_as[i]; } + mpz & a(unsigned i) { return m_as[i]; } + mpz const & c() const { return m_c; } + mpz & c() { return m_c; } + var j(unsigned i) const { return m_js[i]; } + var & j(unsigned i) { return m_js[i]; } + mpq const & b(unsigned i) const { return m_bs[i]; } + mpq & b(unsigned i) { return m_bs[i]; } + + unsigned pos_x(unsigned x_i) const { return pos(m_xs, x_i); } + }; + + typedef ptr_vector equations; + typedef svector occs; + + numeral_manager * m_manager; + bool m_owns_m; + volatile bool m_cancel; + + equations m_equations; + equations m_solution; + + svector m_parameter; + unsigned_vector m_solved; // null_eq_idx if var is not solved, otherwise the position in m_solution + vector m_occs; // occurrences of the variable in m_equations. + + unsigned m_inconsistent; // null_eq_idx if not inconsistent, otherwise it is the index of an unsatisfiable equality in m_equations. + unsigned m_next_justification; + mpz_buffer m_decompose_buffer; + mpz_buffer m_as_buffer; + mpq_buffer m_bs_buffer; + + var_vector m_tmp_xs; + mpz_buffer m_tmp_as; + mpq_buffer m_tmp_bs; + + var_vector m_norm_xs_vector; + mpz_vector m_norm_as_vector; + mpq_vector m_norm_bs_vector; + + var_queue m_var_queue; + + // next candidate + unsigned m_next_eq; + var m_next_x; + mpz m_next_a; + bool m_next_pos_a; + + numeral_manager & m() const { return *m_manager; } + + bool solved(var x) const { return m_solved[x] != null_eq_idx; } + + template + void sort_core(svector & as, unsigned_vector & xs, numeral_buffer & buffer) { + std::sort(xs.begin(), xs.end()); + unsigned num = as.size(); + for (unsigned i = 0; i < num; i++) { + m().swap(as[i], buffer[xs[i]]); + } + } + + template + void sort(svector & as, unsigned_vector & xs, numeral_buffer & buffer) { + unsigned num = as.size(); + for (unsigned i = 0; i < num; i++) { + m().set(buffer[xs[i]], as[i]); + } + sort_core(as, xs, buffer); + } + + equation * mk_eq(unsigned num, mpz const * as, var const * xs, mpz const & c, unsigned num_js, mpq const * bs, justification const * js, + bool sort = true) { + equation * new_eq = alloc(equation); + for (unsigned i = 0; i < num; i++) { + m().set(m_as_buffer[xs[i]], as[i]); + new_eq->m_as.push_back(mpz()); + new_eq->m_xs.push_back(xs[i]); + } + sort_core(new_eq->m_as, new_eq->m_xs, m_as_buffer); + m().set(new_eq->m_c, c); + for (unsigned i = 0; i < num_js; i++) { + m().set(m_bs_buffer[js[i]], bs[i]); + new_eq->m_bs.push_back(mpq()); + new_eq->m_js.push_back(js[i]); + } + if (sort) + sort_core(new_eq->m_bs, new_eq->m_js, m_bs_buffer); + return new_eq; + } + + template + void div(svector & as, mpz const & g) { + unsigned n = as.size(); + for (unsigned i = 0; i < n; i++) + m().div(as[i], g, as[i]); + } + + void normalize_eq(unsigned eq_idx) { + if (inconsistent()) + return; + equation & eq = *(m_equations[eq_idx]); + TRACE("euclidean_solver", tout << "normalizing:\n"; display(tout, eq); tout << "\n";); + unsigned num = eq.size(); + if (num == 0) { + // c == 0 inconsistency + if (!m().is_zero(eq.c())) { + TRACE("euclidean_solver", tout << "c = 0 inconsistency detected\n";); + m_inconsistent = eq_idx; + } + else { + del_eq(&eq); + m_equations[eq_idx] = 0; + } + return; + } + + mpz g; + mpz a; + m().set(g, eq.a(0)); + m().abs(g); + for (unsigned i = 1; i < num; i++) { + if (m().is_one(g)) + break; + m().set(a, eq.a(i)); + m().abs(a); + m().gcd(g, a, g); + } + if (m().is_one(g)) + return; + if (!m().divides(g, eq.c())) { + // g does not divide c + TRACE("euclidean_solver", tout << "gcd inconsistency detected\n";); + m_inconsistent = eq_idx; + return; + } + div(eq.m_as, g); + div(eq.m_bs, g); + m().del(g); + m().del(a); + TRACE("euclidean_solver", tout << "after normalization:\n"; display(tout, eq); tout << "\n";); + } + + bool is_better(mpz const & a, var x, unsigned eq_sz) { + SASSERT(m().is_pos(a)); + if (m_next_x == null_var) + return true; + if (m().lt(a, m_next_a)) + return true; + if (m().lt(m_next_a, a)) + return false; + if (m_occs[x].size() < m_occs[m_next_x].size()) + return true; + if (m_occs[x].size() > m_occs[m_next_x].size()) + return false; + return eq_sz < m_equations[m_next_eq]->size(); + } + + void updt_next_candidate(unsigned eq_idx) { + if (!m_equations[eq_idx]) + return; + mpz abs_a; + equation const & eq = *(m_equations[eq_idx]); + unsigned num = eq.size(); + for (unsigned i = 0; i < num; i++) { + mpz const & a = eq.a(i); + m().set(abs_a, a); + m().abs(abs_a); + if (is_better(abs_a, eq.x(i), num)) { + m().set(m_next_a, abs_a); + m_next_x = eq.x(i); + m_next_eq = eq_idx; + m_next_pos_a = m().is_pos(a); + } + } + m().del(abs_a); + } + + /** + \brief Select next variable to be eliminated. + Return false if there is not variable to eliminate. + + The result is in + m_next_x variable to be eliminated + m_next_eq id of the equation containing x + m_next_a absolute value of the coefficient of x in eq. + m_next_pos_a true if the coefficient of x is positive in eq. + */ + bool select_next_var() { + while (!m_equations.empty() && m_equations.back() == 0) + m_equations.pop_back(); + if (m_equations.empty()) + return false; + SASSERT(!m_equations.empty() && m_equations.back() != 0); + m_next_x = null_var; + unsigned eq_idx = m_equations.size(); + while (eq_idx > 0) { + --eq_idx; + updt_next_candidate(eq_idx); + // stop as soon as possible + // TODO: use heuristics + if (m_next_x != null_var && m().is_one(m_next_a)) + return true; + } + CTRACE("euclidean_solver_bug", m_next_x == null_var, display(tout);); + SASSERT(m_next_x != null_var); + return true; + } + + template + void del_nums(svector & as) { + unsigned sz = as.size(); + for (unsigned i = 0; i < sz; i++) + m().del(as[i]); + as.reset(); + } + + void del_eq(equation * eq) { + m().del(eq->c()); + del_nums(eq->m_as); + del_nums(eq->m_bs); + dealloc(eq); + } + + void del_equations(equations & eqs) { + unsigned sz = eqs.size(); + for (unsigned i = 0; i < sz; i++) { + if (eqs[i]) + del_eq(eqs[i]); + } + } + + /** + \brief Store the "solved" variables in xs into m_var_queue. + */ + void schedule_var_subst(unsigned num, var const * xs) { + for (unsigned i = 0; i < num; i++) { + if (solved(xs[i])) + m_var_queue.insert(xs[i]); + } + } + + void schedule_var_subst(var_vector const & xs) { + schedule_var_subst(xs.size(), xs.c_ptr()); + } + + /** + \brief Store as1*xs1 + k*as2*xs2 into new_as*new_xs + + If UpdateOcc == true, + Then, + 1) for each variable x occurring in xs2 but not in xs1: + - eq_idx is added to m_occs[x] + 2) for each variable that occurs in xs1 and xs2 and the resultant coefficient is zero, + - eq_idx is removed from m_occs[x] IF x != except_var + + If UpdateQueue == true + Then, + 1) for each variable x occurring in xs2 but not in xs1: + - if x is solved, then x is inserted into m_var_queue + 2) for each variable that occurs in xs1 and xs2 and the resultant coefficient is zero, + - if x is solved, then x is removed from m_var_queue + + */ + template + void addmul(svector const & as1, var_vector const & xs1, + mpz const & k, svector const & as2, var_vector const & xs2, + numeral_buffer & new_as, var_vector & new_xs, + unsigned eq_idx = null_eq_idx, var except_var = null_var) { + Numeral new_a; + SASSERT(as1.size() == xs1.size()); + SASSERT(as2.size() == xs2.size()); + new_as.reset(); + new_xs.reset(); + unsigned sz1 = xs1.size(); + unsigned sz2 = xs2.size(); + unsigned i1 = 0; + unsigned i2 = 0; + while (true) { + if (i1 == sz1) { + // copy remaining entries from as2*xs2 + while (i2 < sz2) { + var x2 = xs2[i2]; + if (UpdateOcc) + m_occs[x2].push_back(eq_idx); + if (UpdateQueue && solved(x2)) + m_var_queue.insert(x2); + new_as.push_back(Numeral()); + m().mul(k, as2[i2], new_as.back()); + new_xs.push_back(x2); + i2++; + } + break; + } + if (i2 == sz2) { + // copy remaining entries from as1*xs1 + while (i1 < sz1) { + new_as.push_back(as1[i1]); + new_xs.push_back(xs1[i1]); + i1++; + } + break; + } + var x1 = xs1[i1]; + var x2 = xs2[i2]; + if (x1 < x2) { + new_as.push_back(as1[i1]); + new_xs.push_back(xs1[i1]); + i1++; + } + else if (x1 > x2) { + if (UpdateOcc) + m_occs[x2].push_back(eq_idx); + if (UpdateQueue && solved(x2)) + m_var_queue.insert(x2); + new_as.push_back(Numeral()); + m().mul(k, as2[i2], new_as.back()); + new_xs.push_back(x2); + i2++; + } + else { + m().addmul(as1[i1], k, as2[i2], new_a); + TRACE("euclidean_solver_add_mul", tout << "i1: " << i1 << ", i2: " << i2 << " new_a: " << m().to_string(new_a) << "\n"; + tout << "as1: " << m().to_string(as1[i1]) << ", k: " << m().to_string(k) << ", as2: " << m().to_string(as2[i2]) << "\n";); + if (m().is_zero(new_a)) { + // variable was canceled + if (UpdateOcc && x1 != except_var) + m_occs[x1].erase(eq_idx); + if (UpdateQueue && solved(x1) && m_var_queue.contains(x1)) + m_var_queue.erase(x1); + } + else { + new_as.push_back(new_a); + new_xs.push_back(x1); + } + i1++; + i2++; + } + } + m().del(new_a); + } + + template + void apply_solution(var x, mpz_vector & as, var_vector & xs, mpz & c, mpq_vector & bs, justification_vector & js, + unsigned eq_idx = null_eq_idx, var except_var = null_var) { + SASSERT(solved(x)); + unsigned idx = pos(xs, x); + if (idx == UINT_MAX) + return; + mpz const & a1 = as[idx]; + SASSERT(!m().is_zero(a1)); + equation const & eq2 = *(m_solution[m_solved[x]]); + SASSERT(eq2.pos_x(x) != UINT_MAX); + SASSERT(m().is_minus_one(eq2.a(eq2.pos_x(x)))); + TRACE("euclidean_solver_apply", + tout << "applying: " << m().to_string(a1) << " * "; display(tout, eq2); tout << "\n"; + for (unsigned i = 0; i < xs.size(); i++) tout << m().to_string(as[i]) << "*x" << xs[i] << " "; tout << "\n";); + addmul(as, xs, a1, eq2.m_as, eq2.m_xs, m_tmp_as, m_tmp_xs, eq_idx, except_var); + m().addmul(c, a1, eq2.m_c, c); + m_tmp_as.swap(as); + m_tmp_xs.swap(xs); + SASSERT(as.size() == xs.size()); + TRACE("euclidean_solver_apply", for (unsigned i = 0; i < xs.size(); i++) tout << m().to_string(as[i]) << "*x" << xs[i] << " "; tout << "\n";); + addmul(bs, js, a1, eq2.m_bs, eq2.m_js, m_tmp_bs, m_tmp_xs); + m_tmp_bs.swap(bs); + m_tmp_xs.swap(js); + SASSERT(pos(xs, x) == UINT_MAX); + } + + void apply_solution(mpz_vector & as, var_vector & xs, mpz & c, mpq_vector & bs, justification_vector & js) { + m_var_queue.reset(); + schedule_var_subst(xs); + while (!m_var_queue.empty()) { + var x = m_var_queue.erase_min(); + apply_solution(x, as, xs, c, bs, js); + } + } + + void apply_solution(equation & eq) { + apply_solution(eq.m_as, eq.m_xs, eq.m_c, eq.m_bs, eq.m_js); + } + + void display(std::ostream & out, equation const & eq) const { + unsigned num = eq.js_size(); + for (unsigned i = 0; i < num; i ++) { + if (i > 0) out << " "; + out << m().to_string(eq.b(i)) << "*j" << eq.j(i); + } + if (num > 0) out << " "; + out << "|= "; + num = eq.size(); + for (unsigned i = 0; i < num; i++) { + out << m().to_string(eq.a(i)) << "*x" << eq.x(i) << " + "; + } + out << m().to_string(eq.c()) << " = 0"; + } + + void display(std::ostream & out, equations const & eqs) const { + unsigned num = eqs.size(); + for (unsigned i = 0; i < num; i++) { + if (eqs[i]) { + display(out, *(eqs[i])); + out << "\n"; + } + } + } + + void display(std::ostream & out) const { + if (inconsistent()) { + out << "inconsistent: "; + display(out, *(m_equations[m_inconsistent])); + out << "\n"; + } + out << "solution set:\n"; + display(out, m_solution); + out << "todo:\n"; + display(out, m_equations); + } + + void add_occs(unsigned eq_idx) { + equation const & eq = *(m_equations[eq_idx]); + unsigned sz = eq.size(); + for (unsigned i = 0; i < sz; i++) + m_occs[eq.x(i)].push_back(eq_idx); + } + + imp(numeral_manager * m): + m_manager(m == 0 ? alloc(numeral_manager) : m), + m_owns_m(m == 0), + m_decompose_buffer(*m_manager), + m_as_buffer(*m_manager), + m_bs_buffer(*m_manager), + m_tmp_as(*m_manager), + m_tmp_bs(*m_manager), + m_var_queue(16, elim_order_lt(m_solved)) { + m_inconsistent = null_eq_idx; + m_next_justification = 0; + m_cancel = false; + m_next_x = null_var; + m_next_eq = null_eq_idx; + } + + ~imp() { + m().del(m_next_a); + del_equations(m_equations); + del_equations(m_solution); + if (m_owns_m) + dealloc(m_manager); + } + + var mk_var(bool parameter) { + var x = m_solved.size(); + m_parameter.push_back(parameter); + m_solved.push_back(null_eq_idx); + m_occs.push_back(occs()); + m_as_buffer.push_back(mpz()); + m_var_queue.reserve(x+1); + return x; + } + + justification mk_justification() { + justification r = m_next_justification; + m_bs_buffer.push_back(mpq()); + m_next_justification++; + return r; + } + + void assert_eq(unsigned num, mpz const * as, var const * xs, mpz const & c, justification j) { + if (inconsistent()) + return; + equation * eq; + if (j == null_justification) { + eq = mk_eq(num, as, xs, c, 0, 0, 0); + } + else { + mpq one(1); + eq = mk_eq(num, as, xs, c, 1, &one, &j); + } + TRACE("euclidean_solver", tout << "new-eq:\n"; display(tout, *eq); tout << "\n";); + unsigned eq_idx = m_equations.size(); + m_equations.push_back(eq); + apply_solution(*eq); + normalize_eq(eq_idx); + add_occs(eq_idx); + TRACE("euclidean_solver", tout << "asserted:\n"; display(tout, *eq); tout << "\n";); + } + + justification_vector const & get_justification() const { + SASSERT(inconsistent()); + return m_equations[m_inconsistent]->m_js; + } + + template + void neg_coeffs(svector & as) { + unsigned sz = as.size(); + for (unsigned i = 0; i < sz; i++) { + m().neg(as[i]); + } + } + + void substitute_most_recent_solution(var x) { + SASSERT(!m_solution.empty()); + equation & eq = *(m_solution.back()); + TRACE("euclidean_solver", tout << "applying solution for x" << x << "\n"; display(tout, eq); tout << "\n";); + occs & use_list = m_occs[x]; + occs::iterator it = use_list.begin(); + occs::iterator end = use_list.end(); + for (; it != end; ++it) { + unsigned eq_idx = *it; + // remark we don't want to update the use_list of x while we are traversing it. + equation & eq2 = *(m_equations[eq_idx]); + TRACE("euclidean_solver", tout << "eq before substituting x" << x << "\n"; display(tout, eq2); tout << "\n";); + apply_solution(x, eq2.m_as, eq2.m_xs, eq2.m_c, eq2.m_bs, eq2.m_js, eq_idx, x); + TRACE("euclidean_solver", tout << "eq after substituting x" << x << "\n"; display(tout, eq2); tout << "\n";); + normalize_eq(eq_idx); + if (inconsistent()) + break; + } + use_list.reset(); + } + + void elim_unit() { + SASSERT(m().is_one(m_next_a)); + equation & eq = *(m_equations[m_next_eq]); + TRACE("euclidean_solver", tout << "eliminating equation with unit coefficient:\n"; display(tout, eq); tout << "\n";); + if (m_next_pos_a) { + // neg coeffs... to make sure that m_next_x is -1 + neg_coeffs(eq.m_as); + neg_coeffs(eq.m_bs); + } + unsigned sz = eq.size(); + for (unsigned i = 0; i < sz; i++) { + m_occs[eq.x(i)].erase(m_next_eq); + } + m_solved[m_next_x] = m_solution.size(); + m_solution.push_back(&eq); + m_equations[m_next_eq] = 0; + substitute_most_recent_solution(m_next_x); + } + + void decompose(bool pos_a, mpz const & abs_a, mpz const & a_i, mpz & new_a_i, mpz & r_i) { + mpz abs_a_i; + bool pos_a_i = m().is_pos(a_i); + m().set(abs_a_i, a_i); + if (!pos_a_i) + m().neg(abs_a_i); + bool new_pos_a_i = pos_a_i; + if (pos_a) + new_pos_a_i = !new_pos_a_i; + m().div(abs_a_i, abs_a, new_a_i); + if (m().divides(abs_a, a_i)) { + m().reset(r_i); + } + else { + if (pos_a_i) + m().submul(a_i, abs_a, new_a_i, r_i); + else + m().addmul(a_i, abs_a, new_a_i, r_i); + } + if (!new_pos_a_i) + m().neg(new_a_i); + m().del(abs_a_i); + } + + void decompose_and_elim() { + m_tmp_xs.reset(); + mpz_buffer & buffer = m_decompose_buffer; + buffer.reset(); + var p = mk_var(true); + mpz new_a_i; + equation & eq = *(m_equations[m_next_eq]); + TRACE("euclidean_solver", tout << "decompositing equation for x" << m_next_x << "\n"; display(tout, eq); tout << "\n";); + unsigned sz = eq.size(); + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + var x_i = eq.x(i); + if (x_i == m_next_x) { + m().set(new_a_i, -1); + buffer.push_back(new_a_i); + m_tmp_xs.push_back(m_next_x); + m_occs[x_i].erase(m_next_eq); + } + else { + decompose(m_next_pos_a, m_next_a, eq.a(i), new_a_i, eq.m_as[j]); + buffer.push_back(new_a_i); + m_tmp_xs.push_back(x_i); + if (m().is_zero(eq.m_as[j])) { + m_occs[x_i].erase(m_next_eq); + } + else { + eq.m_xs[j] = x_i; + j++; + } + } + } + SASSERT(j < sz); + // add parameter: p to new equality, and m_next_pos_a * m_next_a * p to current eq + m().set(new_a_i, 1); + buffer.push_back(new_a_i); + m_tmp_xs.push_back(p); + m().set(eq.m_as[j], m_next_a); + if (!m_next_pos_a) + m().neg(eq.m_as[j]); + eq.m_xs[j] = p; + j++; + unsigned new_sz = j; + // shrink current eq + for (; j < sz; j++) + m().del(eq.m_as[j]); + eq.m_as.shrink(new_sz); + eq.m_xs.shrink(new_sz); + // ajust c + mpz new_c; + decompose(m_next_pos_a, m_next_a, eq.m_c, new_c, eq.m_c); + // create auxiliary equation + equation * new_eq = mk_eq(m_tmp_xs.size(), buffer.c_ptr(), m_tmp_xs.c_ptr(), new_c, 0, 0, 0, false); + // new_eq doesn't need to normalized, since it has unit coefficients + TRACE("euclidean_solver", tout << "decomposition: new parameter x" << p << " aux eq:\n"; + display(tout, *new_eq); tout << "\n"; + display(tout, eq); tout << "\n";); + m_solved[m_next_x] = m_solution.size(); + m_solution.push_back(new_eq); + substitute_most_recent_solution(m_next_x); + m().del(new_a_i); + m().del(new_c); + } + + bool solve() { + if (inconsistent()) return false; + TRACE("euclidean_solver", tout << "solving...\n"; display(tout);); + while (select_next_var()) { + CTRACE("euclidean_solver_bug", m_next_x == null_var, display(tout);); + TRACE("euclidean_solver", tout << "eliminating x" << m_next_x << "\n";); + if (m().is_one(m_next_a) || m().is_minus_one(m_next_a)) + elim_unit(); + else + decompose_and_elim(); + TRACE("euclidean_solver_step", display(tout);); + if (inconsistent()) return false; + } + return true; + } + + bool inconsistent() const { + return m_inconsistent != null_eq_idx; + } + + bool is_parameter(var x) const { + return m_parameter[x]; + } + + void normalize(unsigned num, mpz const * as, var const * xs, mpz const & c, mpz & a_prime, mpz & c_prime, justification_vector & js) { + TRACE("euclidean_solver", tout << "before applying solution set\n"; + for (unsigned i = 0; i < num; i++) { + tout << m().to_string(as[i]) << "*x" << xs[i] << " "; + } + tout << "\n";); + m_norm_xs_vector.reset(); + m_norm_as_vector.reset(); + for (unsigned i = 0; i < num; i++) { + m_norm_xs_vector.push_back(xs[i]); + m_norm_as_vector.push_back(mpz()); + m().set(m_norm_as_vector.back(), as[i]); + } + sort(m_norm_as_vector, m_norm_xs_vector, m_as_buffer); + m_norm_bs_vector.reset(); + js.reset(); + m().set(c_prime, c); + apply_solution(m_norm_as_vector, m_norm_xs_vector, c_prime, m_norm_bs_vector, js); + TRACE("euclidean_solver", tout << "after applying solution set\n"; + for (unsigned i = 0; i < m_norm_as_vector.size(); i++) { + tout << m().to_string(m_norm_as_vector[i]) << "*x" << m_norm_xs_vector[i] << " "; + } + tout << "\n";); + // compute gcd of the result m_norm_as_vector + if (m_norm_as_vector.empty()) { + m().set(a_prime, 0); + } + else { + mpz a; + m().set(a_prime, m_norm_as_vector[0]); + m().abs(a_prime); + unsigned sz = m_norm_as_vector.size(); + for (unsigned i = 1; i < sz; i++) { + if (m().is_one(a_prime)) + break; + m().set(a, m_norm_as_vector[i]); + m().abs(a); + m().gcd(a_prime, a, a_prime); + } + m().del(a); + } + // REMARK: m_norm_bs_vector contains the linear combination of the justifications. It may be useful if we + // decided (one day) to generate detailed proofs for this step. + del_nums(m_norm_as_vector); + del_nums(m_norm_bs_vector); + } + + void set_cancel(bool f) { + m_cancel = f; + } + +}; + +euclidean_solver::euclidean_solver(numeral_manager * m): + m_imp(alloc(imp, m)) { +} + +euclidean_solver::~euclidean_solver() { + dealloc(m_imp); +} + +euclidean_solver::numeral_manager & euclidean_solver::m() const { + return m_imp->m(); +} + +void euclidean_solver::reset() { + numeral_manager * m = m_imp->m_manager; + bool owns_m = m_imp->m_owns_m; + m_imp->m_owns_m = false; + #pragma omp critical (euclidean_solver) + { + dealloc(m_imp); + m_imp = alloc(imp, m); + m_imp->m_owns_m = owns_m; + } +} + +euclidean_solver::var euclidean_solver::mk_var() { + return m_imp->mk_var(false); +} + +euclidean_solver::justification euclidean_solver::mk_justification() { + return m_imp->mk_justification(); +} + +void euclidean_solver::assert_eq(unsigned num, mpz const * as, var const * xs, mpz const & c, justification j) { + m_imp->assert_eq(num, as, xs, c, j); +} + +bool euclidean_solver::solve() { + return m_imp->solve(); +} + +euclidean_solver::justification_vector const & euclidean_solver::get_justification() const { + return m_imp->get_justification(); +} + +bool euclidean_solver::inconsistent() const { + return m_imp->inconsistent(); +} + +bool euclidean_solver::is_parameter(var x) const { + return m_imp->is_parameter(x); +} + +void euclidean_solver::normalize(unsigned num, mpz const * as, var const * xs, mpz const & c, mpz & a_prime, mpz & c_prime, + justification_vector & js) { + return m_imp->normalize(num, as, xs, c, a_prime, c_prime, js); +} + +void euclidean_solver::set_cancel(bool f) { + #pragma omp critical (euclidean_solver) + { + m_imp->set_cancel(f); + } +} + +void euclidean_solver::display(std::ostream & out) const { + m_imp->display(out); +} + + diff --git a/lib/euclidean_solver.h b/lib/euclidean_solver.h new file mode 100644 index 000000000..f2345b9ed --- /dev/null +++ b/lib/euclidean_solver.h @@ -0,0 +1,106 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + euclidean_solver.h + +Abstract: + + Euclidean Solver with support for explanations. + +Author: + + Leonardo de Moura (leonardo) 2011-07-08. + +Revision History: + +--*/ +#ifndef _EUCLIDEAN_SOLVER_H_ +#define _EUCLIDEAN_SOLVER_H_ + +#include"mpq.h" +#include"vector.h" + +class euclidean_solver { + struct imp; + imp * m_imp; +public: + typedef unsigned var; + typedef unsigned justification; + typedef unsynch_mpq_manager numeral_manager; + typedef svector justification_vector; + static const justification null_justification = UINT_MAX; + + /** + \brief If m == 0, then the solver will create its own numeral manager. + */ + euclidean_solver(numeral_manager * m); + + ~euclidean_solver(); + + numeral_manager & m() const; + + /** + \brief Reset the state of the euclidean solver. + */ + void reset(); + + /** + \brief Creates a integer variable. + */ + var mk_var(); + + /** + \brief Creates a fresh justification id. + */ + justification mk_justification(); + + /** + \brief Asserts an equation of the form as[0]*xs[0] + ... + as[num-1]*xs[num-1] + c = 0 with justification j. + + The numerals must be created using the numeral_manager m(). + */ + void assert_eq(unsigned num, mpz const * as, var const * xs, mpz const & c, justification j = null_justification); + + /** + \brief Solve the current set of equations. Return false if it is inconsistent. + */ + bool solve(); + + /** + \brief Return a set of justifications (proof) for the inconsitency. + + \pre inconsistent() + */ + justification_vector const & get_justification() const; + + bool inconsistent() const; + + /** + \brief Return true if the variable is a "parameter" created by the Euclidean solver. + */ + bool is_parameter(var x) const; + + /** + Given a linear polynomial as[0]*xs[0] + ... + as[num-1]*xs[num-1] + c and the current solution set, + It applies the solution set to produce a polynomial of the for a_prime * p + c_prime, where + a_prime * p represents a linear polynomial where the coefficient of every monomial is a multiple of + a_prime. + + The justification is stored in js. + Note that, this function does not return the actual p. + + The numerals must be created using the numeral_manager m(). + */ + void normalize(unsigned num, mpz const * as, var const * xs, mpz const & c, mpz & a_prime, mpz & c_prime, justification_vector & js); + + /** + \brief Set/Reset the cancel flag. + */ + void set_cancel(bool f); + + void display(std::ostream & out) const; +}; + +#endif diff --git a/lib/eval_cmd.cpp b/lib/eval_cmd.cpp new file mode 100644 index 000000000..fe9c738a2 --- /dev/null +++ b/lib/eval_cmd.cpp @@ -0,0 +1,87 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + eval_cmd.cpp + +Abstract: + + eval_cmd + +Author: + + Leonardo (leonardo) 2011-04-30 + +Notes: + +--*/ +#include"cmd_context.h" +#include"model_evaluator.h" +#include"parametric_cmd.h" +#include"scoped_timer.h" +#include"scoped_ctrl_c.h" +#include"cancel_eh.h" + +class eval_cmd : public parametric_cmd { + expr * m_target; + symbol m_last; +public: + eval_cmd():parametric_cmd("eval") {} + + virtual char const * get_usage() const { return " ( )*"; } + + virtual char const * get_main_descr() const { + return "evaluate the given term in the current model."; + } + + virtual void init_pdescrs(cmd_context & ctx, param_descrs & p) { + model_evaluator::get_param_descrs(p); + insert_timeout(p); + } + + virtual void prepare(cmd_context & ctx) { + parametric_cmd::prepare(ctx); + m_target = 0; + } + + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { + if (m_target == 0) return CPK_EXPR; + return parametric_cmd::next_arg_kind(ctx); + } + + virtual void set_next_arg(cmd_context & ctx, expr * arg) { + m_target = arg; + } + + virtual void execute(cmd_context & ctx) { + if (!ctx.is_model_available()) + throw cmd_exception("model is not available"); + model_ref md; + check_sat_result * last_result = ctx.get_check_sat_result(); + SASSERT(last_result); + last_result->get_model(md); + expr_ref r(ctx.m()); + unsigned timeout = m_params.get_uint(":timeout", UINT_MAX); + model_evaluator ev(*(md.get()), m_params); + cancel_eh eh(ev); + { + scoped_ctrl_c ctrlc(eh); + scoped_timer timer(timeout, &eh); + cmd_context::scoped_watch sw(ctx); + try { + ev(m_target, r); + } + catch (model_evaluator_exception & ex) { + ctx.regular_stream() << "(error \"evaluator failed: " << ex.msg() << "\")" << std::endl; + return; + } + } + ctx.display(ctx.regular_stream(), r.get()); + ctx.regular_stream() << std::endl; + } +}; + +void install_eval_cmd(cmd_context & ctx) { + ctx.insert(alloc(eval_cmd)); +} diff --git a/lib/eval_cmd.h b/lib/eval_cmd.h new file mode 100644 index 000000000..e6e220b1d --- /dev/null +++ b/lib/eval_cmd.h @@ -0,0 +1,26 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + eval_cmd.h + +Abstract: + + eval_cmd + +Author: + + Leonardo (leonardo) 2011-04-30 + +Notes: + +--*/ +#ifndef _EVAL_CMD_H_ +#define _EVAL_CMD_H_ + +class cmd_context; + +void install_eval_cmd(cmd_context & ctx); + +#endif diff --git a/lib/event_handler.h b/lib/event_handler.h new file mode 100644 index 000000000..4249a7e4b --- /dev/null +++ b/lib/event_handler.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + event_handler.h + +Abstract: + + Abstract event handler. + +Author: + + Leonardo de Moura (leonardo) 2011-04-26. + +Revision History: + +--*/ +#ifndef _EVENT_HANDLER_H_ +#define _EVENT_HANDLER_H_ + +class event_handler { +public: + virtual ~event_handler() {} + virtual void operator()() = 0; +}; + +#endif diff --git a/lib/expr2dot.cpp b/lib/expr2dot.cpp new file mode 100644 index 000000000..b0fef27d0 --- /dev/null +++ b/lib/expr2dot.cpp @@ -0,0 +1,84 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + expr2dot.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-03-07. + +Revision History: + +--*/ +#include"expr2dot.h" +#include"for_each_expr.h" + +class dot_printer { + std::ostream & m_out; + ast_manager & m_manager; + bool m_proofs_only; +public: + dot_printer(std::ostream & out, ast_manager & m, bool proofs): + m_out(out), + m_manager(m), + m_proofs_only(proofs) { + } + + char const * get_color(app * n) { + if (m_manager.is_unit_resolution(n)) + return "blue"; + else if (m_manager.is_lemma(n)) + return "gold"; + else if (m_manager.is_transitivity(n)) + return "red"; + else if (m_manager.is_monotonicity(n)) + return "green"; + else + return "black"; + } + + void operator()(var * n) { + if (!m_proofs_only) { + m_out << n->get_id() << "[label=\"\",shape=circle];\n"; + } + } + + void operator()(quantifier * n) { + if (!m_proofs_only) { + m_out << n->get_id() << "[label=\"\",shape=circle,color=gray];\n"; + m_out << n->get_expr()->get_id() << " -> " << n->get_id() << ";\n"; + } + } + + void operator()(app * n) { + if (!m_proofs_only || m_manager.is_proof(n)) { + char const * c = get_color(n); + m_out << n->get_id() << "[label=\"\",shape=circle,color=" << c << "];\n"; + if (m_proofs_only) { + unsigned num = m_manager.get_num_parents(n); + for (unsigned i = 0; i < num; i++) + m_out << m_manager.get_parent(n, i)->get_id() << " -> " << n->get_id() << " [color=" << c << "];\n"; + } + else { + unsigned num = n->get_num_args(); + for (unsigned i = 0; i < num; i++) + m_out << n->get_arg(i)->get_id() << " -> " << n->get_id() << " [color=" << c << "];\n"; + } + } + } +}; + +void expr2dot(std::ostream & out, expr * n, ast_manager & m, bool proofs) { + out << "digraph \"ast\" {\n"; + dot_printer p(out, m, proofs); + for_each_expr(p, n); + out << "}\n"; +} + + diff --git a/lib/expr2dot.h b/lib/expr2dot.h new file mode 100644 index 000000000..54c65b034 --- /dev/null +++ b/lib/expr2dot.h @@ -0,0 +1,27 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + expr2dot.h + +Abstract: + + Convert expressions into a .DOT file + +Author: + + Leonardo de Moura (leonardo) 2008-03-07. + +Revision History: + +--*/ +#ifndef _EXPR2DOT_H_ +#define _EXPR2DOT_H_ + +#include"ast.h" + +void expr2dot(std::ostream & out, expr * a, ast_manager & m, bool proofs = false); + +#endif /* _AST2DOT_H_ */ + diff --git a/lib/expr2polynomial.cpp b/lib/expr2polynomial.cpp new file mode 100644 index 000000000..72bd72aa7 --- /dev/null +++ b/lib/expr2polynomial.cpp @@ -0,0 +1,481 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + expr2polynomial.cpp + +Abstract: + + Translator from Z3 expressions into multivariate polynomials (and back). + + +Author: + + Leonardo (leonardo) 2011-12-23 + +Notes: + +--*/ +#include"expr2polynomial.h" +#include"expr2var.h" +#include"arith_decl_plugin.h" +#include"ast_smt2_pp.h" +#include"z3_exception.h" +#include"cooperate.h" + +struct expr2polynomial::imp { + struct frame { + app * m_curr; + unsigned m_idx; + frame():m_curr(0), m_idx(0) {} + frame(app * t):m_curr(t), m_idx(0) {} + }; + + expr2polynomial & m_wrapper; + ast_manager & m_am; + arith_util m_autil; + polynomial::manager & m_pm; + expr2var * m_expr2var; + bool m_expr2var_owner; + expr_ref_vector m_var2expr; + + obj_map m_cache; + expr_ref_vector m_cached_domain; + polynomial::polynomial_ref_vector m_cached_polynomials; + polynomial::scoped_numeral_vector m_cached_denominators; + + svector m_frame_stack; + polynomial::polynomial_ref_vector m_presult_stack; + polynomial::scoped_numeral_vector m_dresult_stack; + + volatile bool m_cancel; + + imp(expr2polynomial & w, ast_manager & am, polynomial::manager & pm, expr2var * e2v): + m_wrapper(w), + m_am(am), + m_autil(am), + m_pm(pm), + m_expr2var(e2v == 0 ? alloc(expr2var, am) : e2v), + m_expr2var_owner(e2v == 0), + m_var2expr(am), + m_cached_domain(am), + m_cached_polynomials(pm), + m_cached_denominators(pm.m()), + m_presult_stack(pm), + m_dresult_stack(pm.m()), + m_cancel(false) { + } + + ~imp() { + if (m_expr2var_owner) + dealloc(m_expr2var); + } + + ast_manager & m() { return m_am; } + polynomial::manager & pm() { return m_pm; } + polynomial::numeral_manager & nm() { return pm().m(); } + + void reset() { + m_frame_stack.reset(); + m_presult_stack.reset(); + m_dresult_stack.reset(); + } + + void reset_cache() { + m_cache.reset(); + m_cached_domain.reset(); + m_cached_polynomials.reset(); + m_cached_denominators.reset(); + } + + void checkpoint() { + if (m_cancel) + throw default_exception("canceled"); + cooperate("expr2polynomial"); + } + + void push_frame(app * t) { + m_frame_stack.push_back(frame(t)); + } + + void cache_result(expr * t) { + SASSERT(!m_cache.contains(t)); + SASSERT(m_cached_denominators.size() == m_cached_polynomials.size()); + SASSERT(m_cached_denominators.size() == m_cached_domain.size()); + if (t->get_ref_count() <= 1) + return; + unsigned idx = m_cached_polynomials.size(); + m_cache.insert(t, idx); + m_cached_domain.push_back(t); + m_cached_polynomials.push_back(m_presult_stack.back()); + m_cached_denominators.push_back(m_dresult_stack.back()); + } + + bool is_cached(expr * t) { + return t->get_ref_count() > 1 && m_cache.contains(t); + } + + bool is_int_real(expr * t) { + return m_autil.is_int_real(t); + } + + void store_result(expr * t, polynomial::polynomial * p, polynomial::numeral & d) { + m_presult_stack.push_back(p); + m_dresult_stack.push_back(d); + cache_result(t); + } + + void store_var_poly(expr * t) { + polynomial::var x = m_expr2var->to_var(t); + if (x == UINT_MAX) { + bool is_int = m_autil.is_int(t); + x = m_wrapper.mk_var(is_int); + m_expr2var->insert(t, x); + if (x >= m_var2expr.size()) + m_var2expr.resize(x+1, 0); + m_var2expr.set(x, t); + } + polynomial::numeral one(1); + store_result(t, pm().mk_polynomial(x), one); + } + + void store_const_poly(app * n) { + rational val; + VERIFY(m_autil.is_numeral(n, val)); + polynomial::scoped_numeral d(nm()); + d = val.to_mpq().denominator(); + store_result(n, pm().mk_const(numerator(val)), d); + } + + bool visit_arith_app(app * t) { + switch (t->get_decl_kind()) { + case OP_NUM: + store_const_poly(t); + return true; + case OP_ADD: case OP_SUB: case OP_MUL: case OP_UMINUS: case OP_TO_REAL: + push_frame(t); + return false; + case OP_POWER: { + rational k; + SASSERT(t->get_num_args() == 2); + if (!m_autil.is_numeral(t->get_arg(1), k) || !k.is_int() || !k.is_unsigned()) { + store_var_poly(t); + return true; + } + push_frame(t); + return false; + } + default: + // can't handle operator + store_var_poly(t); + return true; + } + } + + bool visit(expr * t) { + SASSERT(is_int_real(t)); + if (is_cached(t)) { + unsigned idx = m_cache.find(t); + m_presult_stack.push_back(m_cached_polynomials.get(idx)); + m_dresult_stack.push_back(m_cached_denominators.get(idx)); + return true; + } + + SASSERT(!is_quantifier(t)); + if (::is_var(t)) { + store_var_poly(t); + return true; + } + + SASSERT(is_app(t)); + if (!m_autil.is_arith_expr(t)) { + store_var_poly(t); + return true; + } + + return visit_arith_app(to_app(t)); + } + + void pop(unsigned num_args) { + SASSERT(m_presult_stack.size() == m_dresult_stack.size()); + SASSERT(m_presult_stack.size() >= num_args); + m_presult_stack.shrink(m_presult_stack.size() - num_args); + m_dresult_stack.shrink(m_dresult_stack.size() - num_args); + } + + polynomial::polynomial * const * polynomial_args(unsigned num_args) { + SASSERT(m_presult_stack.size() >= num_args); + return m_presult_stack.c_ptr() + m_presult_stack.size() - num_args; + } + + polynomial::numeral const * denominator_args(unsigned num_args) { + SASSERT(m_dresult_stack.size() >= num_args); + return m_dresult_stack.c_ptr() + m_dresult_stack.size() - num_args; + } + + template + void process_add_sub(app * t) { + SASSERT(t->get_num_args() <= m_presult_stack.size()); + unsigned num_args = t->get_num_args(); + polynomial::polynomial * const * p_args = polynomial_args(num_args); + polynomial::numeral const * d_args = denominator_args(num_args); + polynomial::polynomial_ref p(pm()); + polynomial::polynomial_ref p_aux(pm()); + polynomial::scoped_numeral d(nm()); + polynomial::scoped_numeral d_aux(nm()); + d = 1; + for (unsigned i = 0; i < num_args; i++) { + nm().lcm(d, d_args[i], d); + } + p = pm().mk_zero(); + for (unsigned i = 0; i < num_args; i++) { + checkpoint(); + nm().div(d, d_args[i], d_aux); + p_aux = pm().mul(d_aux, p_args[i]); + if (i == 0) + p = p_aux; + else if (is_add) + p = pm().add(p, p_aux); + else + p = pm().sub(p, p_aux); + } + pop(num_args); + store_result(t, p.get(), d.get()); + } + + void process_add(app * t) { + process_add_sub(t); + } + + void process_sub(app * t) { + process_add_sub(t); + } + + void process_mul(app * t) { + SASSERT(t->get_num_args() <= m_presult_stack.size()); + unsigned num_args = t->get_num_args(); + polynomial::polynomial * const * p_args = polynomial_args(num_args); + polynomial::numeral const * d_args = denominator_args(num_args); + polynomial::polynomial_ref p(pm()); + polynomial::scoped_numeral d(nm()); + p = pm().mk_const(rational(1)); + d = 1; + for (unsigned i = 0; i < num_args; i++) { + checkpoint(); + p = pm().mul(p, p_args[i]); + d = d * d_args[i]; + } + pop(num_args); + store_result(t, p.get(), d.get()); + } + + void process_uminus(app * t) { + SASSERT(t->get_num_args() <= m_presult_stack.size()); + polynomial::polynomial_ref neg_p(pm()); + neg_p = pm().neg(m_presult_stack.back()); + m_presult_stack.pop_back(); + m_presult_stack.push_back(neg_p); + cache_result(t); + } + + void process_power(app * t) { + SASSERT(t->get_num_args() <= m_presult_stack.size()); + rational _k; + VERIFY(m_autil.is_numeral(t->get_arg(1), _k)); + SASSERT(_k.is_int() && _k.is_unsigned()); + unsigned k = _k.get_unsigned(); + polynomial::polynomial_ref p(pm()); + polynomial::scoped_numeral d(nm()); + unsigned num_args = t->get_num_args(); + polynomial::polynomial * const * p_args = polynomial_args(num_args); + polynomial::numeral const * d_args = denominator_args(num_args); + pm().pw(p_args[0], k, p); + nm().power(d_args[0], k, d); + pop(num_args); + store_result(t, p.get(), d.get()); + } + + void process_to_real(app * t) { + // do nothing + cache_result(t); + } + + void process_app(app * t) { + SASSERT(m_presult_stack.size() == m_dresult_stack.size()); + + switch (t->get_decl_kind()) { + case OP_ADD: + process_add(t); + return; + case OP_SUB: + process_sub(t); + return; + case OP_MUL: + process_mul(t); + return; + case OP_POWER: + process_power(t); + return; + case OP_UMINUS: + process_uminus(t); + return; + case OP_TO_REAL: + process_to_real(t); + return; + default: + UNREACHABLE(); + } + } + + bool to_polynomial(expr * t, polynomial::polynomial_ref & p, polynomial::scoped_numeral & d) { + if (!is_int_real(t)) + return false; + reset(); + if (!visit(t)) { + while (!m_frame_stack.empty()) { + begin_loop: + checkpoint(); + frame & fr = m_frame_stack.back(); + app * t = fr.m_curr; + TRACE("expr2polynomial", tout << "processing: " << fr.m_idx << "\n" << mk_ismt2_pp(t, m()) << "\n";); + unsigned num_args = t->get_num_args(); + while (fr.m_idx < num_args) { + expr * arg = t->get_arg(fr.m_idx); + fr.m_idx++; + if (!visit(arg)) + goto begin_loop; + } + process_app(t); + m_frame_stack.pop_back(); + } + } + p = m_presult_stack.back(); + d = m_dresult_stack.back(); + reset(); + return true; + } + + bool is_int_poly(polynomial::polynomial_ref const & p) { + unsigned sz = size(p); + for (unsigned i = 0; i < sz; i++) { + polynomial::monomial * m = pm().get_monomial(p, i); + unsigned msz = pm().size(m); + for (unsigned j = 0; j < msz; j++) { + polynomial::var x = pm().get_var(m, j); + if (!m_wrapper.is_int(x)) + return false; + } + } + return true; + } + + void to_expr(polynomial::polynomial_ref const & p, bool use_power, expr_ref & r) { + expr_ref_buffer args(m()); + expr_ref_buffer margs(m()); + unsigned sz = size(p); + bool is_int = is_int_poly(p); + + for (unsigned i = 0; i < sz; i++) { + margs.reset(); + polynomial::monomial * m = pm().get_monomial(p, i); + polynomial::numeral const & a = pm().coeff(p, i); + if (!nm().is_one(a)) { + margs.push_back(m_autil.mk_numeral(rational(a), is_int)); + } + unsigned msz = pm().size(m); + for (unsigned j = 0; j < msz; j++) { + polynomial::var x = pm().get_var(m, j); + expr * t = m_var2expr.get(x); + if (m_wrapper.is_int(x) && !is_int) { + t = m_autil.mk_to_real(t); + } + unsigned d = pm().degree(m, j); + if (use_power && d > 1) { + margs.push_back(m_autil.mk_power(t, m_autil.mk_numeral(rational(d), is_int))); + } + else { + for (unsigned k = 0; k < d; k++) + margs.push_back(t); + } + } + if (margs.size() == 0) { + args.push_back(m_autil.mk_numeral(rational(1), is_int)); + } + else if (margs.size() == 1) { + args.push_back(margs[0]); + } + else { + args.push_back(m_autil.mk_mul(margs.size(), margs.c_ptr())); + } + } + + if (args.size() == 0) { + r = m_autil.mk_numeral(rational(0), is_int); + } + else if (args.size() == 1) { + r = args[0]; + } + else { + r = m_autil.mk_add(args.size(), args.c_ptr()); + } + } + + void set_cancel(bool f) { + m_cancel = f; + } +}; + +expr2polynomial::expr2polynomial(ast_manager & am, polynomial::manager & pm, expr2var * e2v) { + m_imp = alloc(imp, *this, am, pm, e2v); +} + +expr2polynomial::~expr2polynomial() { + dealloc(m_imp); +} + +ast_manager & expr2polynomial::m() const { + return m_imp->m_am; +} + +polynomial::manager & expr2polynomial::pm() const { + return m_imp->m_pm; +} + +bool expr2polynomial::to_polynomial(expr * t, polynomial::polynomial_ref & p, polynomial::scoped_numeral & d) { + return m_imp->to_polynomial(t, p, d); +} + +void expr2polynomial::to_expr(polynomial::polynomial_ref const & p, bool use_power, expr_ref & r) { + m_imp->to_expr(p, use_power, r); +} + +bool expr2polynomial::is_var(expr * t) const { + return m_imp->m_expr2var->is_var(t); +} + +expr2var const & expr2polynomial::get_mapping() const { + return *(m_imp->m_expr2var); +} + +void expr2polynomial::set_cancel(bool f) { + m_imp->set_cancel(f); +} + +default_expr2polynomial::default_expr2polynomial(ast_manager & am, polynomial::manager & pm): + expr2polynomial(am, pm, 0) { +} + +default_expr2polynomial::~default_expr2polynomial() { +} + +bool default_expr2polynomial::is_int(polynomial::var x) const { + return m_is_int[x]; +} + +polynomial::var default_expr2polynomial::mk_var(bool is_int) { + polynomial::var x = pm().mk_var(); + m_is_int.reserve(x+1, false); + m_is_int[x] = is_int; + return x; +} diff --git a/lib/expr2polynomial.h b/lib/expr2polynomial.h new file mode 100644 index 000000000..fbeda019d --- /dev/null +++ b/lib/expr2polynomial.h @@ -0,0 +1,93 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + expr2polynomial.h + +Abstract: + + Translator from Z3 expressions into multivariate polynomials (and back). + + +Author: + + Leonardo (leonardo) 2011-12-23 + +Notes: + +--*/ +#ifndef _EXPR2POLYNOMIAL_H_ +#define _EXPR2POLYNOMIAL_H_ + +#include"ast.h" +#include"polynomial.h" + +class expr2var; + +class expr2polynomial { + struct imp; + imp * m_imp; +public: + expr2polynomial(ast_manager & am, polynomial::manager & pm, expr2var * e2v); + virtual ~expr2polynomial(); + + ast_manager & m() const; + polynomial::manager & pm() const; + + /** + \brief Convert a Z3 expression into a polynomial in Z[x0, ..., x_n]. + Since Z3 expressions may be representing polynomials in Q[x0, ..., x_n], + the method also returns a "denominator" d. + Thus, we have that n is equal to p/d + + \remark Return false if t is not an integer or real expression. + + \pre The only supported operators are MUL, ADD, SUB, UMINUS, TO_REAL, TO_INT, POWER (with constants) + */ + bool to_polynomial(expr * t, polynomial::polynomial_ref & p, polynomial::scoped_numeral & d); + + /** + \brief Convert a polynomial into a Z3 expression. + + \remark If the polynomial has one real variable, then the resultant + expression is an real expression. Otherwise, it is an integer + */ + void to_expr(polynomial::polynomial_ref const & p, bool use_power, expr_ref & r); + + /** + \brief Return true if t was encoded as a variable by the translator. + */ + bool is_var(expr * t) const; + + + /** + \brief Return the mapping from expressions to variables + */ + expr2var const & get_mapping() const; + + /** + \brief Cancel/Interrupt execution. + */ + void set_cancel(bool f); + + /** + \brief Return true if the variable is associated with an expression of integer sort. + */ + virtual bool is_int(polynomial::var x) const = 0; + +protected: + virtual polynomial::var mk_var(bool is_int) = 0; +}; + +class default_expr2polynomial : public expr2polynomial { + svector m_is_int; +public: + default_expr2polynomial(ast_manager & am, polynomial::manager & pm); + virtual ~default_expr2polynomial(); + virtual bool is_int(polynomial::var x) const; +protected: + virtual polynomial::var mk_var(bool is_int); +}; + +#endif diff --git a/lib/expr2subpaving.cpp b/lib/expr2subpaving.cpp new file mode 100644 index 000000000..ba8dac208 --- /dev/null +++ b/lib/expr2subpaving.cpp @@ -0,0 +1,395 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + expr2subpaving.cpp + +Abstract: + + Translator from Z3 expressions into generic subpaving data-structure. + + +Author: + + Leonardo (leonardo) 2012-08-08 + +Notes: + +--*/ +#include"expr2subpaving.h" +#include"expr2var.h" +#include"ref_util.h" +#include"z3_exception.h" +#include"cooperate.h" +#include"arith_decl_plugin.h" +#include"scoped_numeral_buffer.h" + +struct expr2subpaving::imp { + struct frame { + app * m_curr; + unsigned m_idx; + frame():m_curr(0), m_idx(0) {} + frame(app * t):m_curr(t), m_idx(0) {} + }; + + ast_manager & m_manager; + subpaving::context & m_subpaving; + unsynch_mpq_manager & m_qm; + arith_util m_autil; + expr2var * m_expr2var; + bool m_expr2var_owner; + + expr_ref_vector m_var2expr; + + typedef svector var_vector; + + obj_map m_cache; + var_vector m_cached_vars; + scoped_mpz_vector m_cached_numerators; + scoped_mpz_vector m_cached_denominators; + + obj_map m_lit_cache; + + volatile bool m_cancel; + + imp(ast_manager & m, subpaving::context & s, expr2var * e2v): + m_manager(m), + m_subpaving(s), + m_qm(s.qm()), + m_autil(m), + m_var2expr(m), + m_cached_numerators(m_qm), + m_cached_denominators(m_qm) { + + if (e2v == 0) { + m_expr2var = alloc(expr2var, m); + m_expr2var_owner = true; + } + else { + m_expr2var = e2v; + m_expr2var_owner = false; + } + + m_cancel = false; + } + + ~imp() { + reset_cache(); + if (m_expr2var_owner) + dealloc(m_expr2var); + } + + ast_manager & m() { return m_manager; } + + subpaving::context & s() { return m_subpaving; } + + unsynch_mpq_manager & qm() const { return m_qm; } + + void reset_cache() { + dec_ref_map_keys(m(), m_cache); + m_cached_vars.reset(); + m_cached_numerators.reset(); + m_cached_denominators.reset(); + dec_ref_map_key_values(m(), s(), m_lit_cache); + } + + void checkpoint() { + if (m_cancel) + throw default_exception("canceled"); + cooperate("expr2subpaving"); + } + + subpaving::var mk_var_for(expr * t) { + SASSERT(!m_autil.is_numeral(t)); + subpaving::var x = m_expr2var->to_var(t); + if (x == subpaving::null_var) { + bool is_int = m_autil.is_int(t); + x = s().mk_var(is_int); + m_expr2var->insert(t, x); + if (x >= m_var2expr.size()) + m_var2expr.resize(x+1, 0); + m_var2expr.set(x, t); + } + return x; + } + + void found_non_simplified() { + throw default_exception("you must apply simplifier before internalizing expressions into the subpaving module."); + } + + bool is_cached(expr * t) { + return t->get_ref_count() > 1 && m_cache.contains(t); + } + + bool is_int_real(expr * t) { + return m_autil.is_int_real(t); + } + + void cache_result(expr * t, subpaving::var x, mpz const & n, mpz const & d) { + SASSERT(!m_cache.contains(t)); + SASSERT(m_cached_numerators.size() == m_cached_vars.size()); + SASSERT(m_cached_denominators.size() == m_cached_vars.size()); + if (t->get_ref_count() <= 1) + return; + unsigned idx = m_cached_vars.size(); + m_cache.insert(t, idx); + m().inc_ref(t); + m_cached_vars.push_back(x); + m_cached_numerators.push_back(n); + m_cached_denominators.push_back(d); + } + + subpaving::var process_num(app * t, unsigned depth, mpz & n, mpz & d) { + rational k; + VERIFY(m_autil.is_numeral(t, k)); + qm().set(n, k.to_mpq().numerator()); + qm().set(d, k.to_mpq().denominator()); + return subpaving::null_var; + } + + // Put t as a^k. + void as_power(expr * t, expr * & a, unsigned & k) { + if (!m_autil.is_power(t)) { + a = t; + k = 1; + return; + } + rational _k; + if (!m_autil.is_numeral(to_app(t)->get_arg(1), _k) || !_k.is_int() || !_k.is_unsigned()) { + a = t; + k = 1; + return; + } + a = to_app(t)->get_arg(0); + k = _k.get_unsigned(); + } + + subpaving::var process_mul(app * t, unsigned depth, mpz & n, mpz & d) { + unsigned num_args = t->get_num_args(); + if (num_args <= 1) + found_non_simplified(); + rational k; + expr * m; + if (m_autil.is_numeral(t->get_arg(0), k)) { + if (num_args != 2) + found_non_simplified(); + qm().set(n, k.to_mpq().numerator()); + qm().set(d, k.to_mpq().denominator()); + m = t->get_arg(1); + } + else { + qm().set(n, 1); + qm().set(d, 1); + m = t; + } + expr * const * margs; + unsigned sz; + if (m_autil.is_mul(m)) { + margs = to_app(m)->get_args(); + sz = to_app(m)->get_num_args(); + } + else { + margs = &m; + sz = 1; + } + scoped_mpz n_arg(qm()); + scoped_mpz d_arg(qm()); + sbuffer pws; + for (unsigned i = 0; i < sz; i++) { + expr * arg = margs[i]; + unsigned k; + as_power(arg, arg, k); + subpaving::var x_arg = process(arg, depth+1, n_arg, d_arg); + qm().power(n_arg, k, n_arg); + qm().power(d_arg, k, d_arg); + qm().mul(n, n_arg, n); + qm().mul(d, d_arg, d); + if (x_arg != subpaving::null_var) + pws.push_back(subpaving::power(x_arg, k)); + } + subpaving::var x; + if (pws.empty()) + x = subpaving::null_var; + else if (pws.size() == 1 && pws[0].degree() == 1) + x = pws[0].get_var(); + else + x = s().mk_monomial(pws.size(), pws.c_ptr()); + cache_result(t, x, n, d); + return x; + } + + typedef _scoped_numeral_buffer mpz_buffer; + typedef sbuffer var_buffer; + + subpaving::var process_add(app * t, unsigned depth, mpz & n, mpz & d) { + unsigned num_args = t->get_num_args(); + mpz_buffer ns(qm()), ds(qm()); + var_buffer xs; + scoped_mpq c(qm()), c_arg(qm()); + scoped_mpz n_arg(qm()), d_arg(qm()); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = t->get_arg(i); + subpaving::var x_arg = process(arg, depth+1, n_arg, d_arg); + if (x_arg == subpaving::null_var) { + qm().set(c_arg, n_arg, d_arg); + qm().add(c, c_arg, c); + } + else { + xs.push_back(x_arg); + ns.push_back(n_arg); + ds.push_back(d_arg); + } + } + qm().set(d, c.get().denominator()); + unsigned sz = xs.size(); + for (unsigned i = 0; i < sz; i++) { + qm().lcm(d, ds[i], d); + } + scoped_mpz & k = d_arg; + qm().div(d, c.get().denominator(), k); + scoped_mpz sum_c(qm()); + qm().mul(c.get().numerator(), k, sum_c); + for (unsigned i = 0; i < sz; i++) { + qm().div(d, ds[i], k); + qm().mul(ns[i], k, ns[i]); + } + subpaving::var x; + if (sz == 0) { + qm().set(n, sum_c); + x = subpaving::null_var; + } + else { + x = s().mk_sum(sum_c, sz, ns.c_ptr(), xs.c_ptr()); + qm().set(n, 1); + } + cache_result(t, x, n, d); + return x; + } + + subpaving::var process_power(app * t, unsigned depth, mpz & n, mpz & d) { + rational k; + SASSERT(t->get_num_args() == 2); + if (!m_autil.is_numeral(t->get_arg(1), k) || !k.is_int() || !k.is_unsigned()) { + qm().set(n, 1); + qm().set(d, 1); + return mk_var_for(t); + } + unsigned _k = k.get_unsigned(); + subpaving::var x = process(t->get_arg(0), depth+1, n, d); + if (x != subpaving::null_var) { + subpaving::power p(x, _k); + x = s().mk_monomial(1, &p); + } + qm().power(n, _k, n); + qm().power(d, _k, d); + cache_result(t, x, n, d); + return x; + } + + subpaving::var process_arith_app(app * t, unsigned depth, mpz & n, mpz & d) { + SASSERT(m_autil.is_arith_expr(t)); + + switch (t->get_decl_kind()) { + case OP_NUM: + return process_num(t, depth, n, d); + case OP_ADD: + return process_add(t, depth, n, d); + case OP_MUL: + return process_mul(t, depth, n, d); + case OP_POWER: + return process_power(t, depth, n, d); + case OP_TO_REAL: + return process(t->get_arg(0), depth+1, n, d); + case OP_SUB: + case OP_UMINUS: + found_non_simplified(); + break; + case OP_TO_INT: + case OP_DIV: + case OP_IDIV: + case OP_MOD: + case OP_REM: + case OP_IRRATIONAL_ALGEBRAIC_NUM: + throw default_exception("you must apply arithmetic purifier before internalizing expressions into the subpaving module."); + case OP_SIN: + case OP_COS: + case OP_TAN: + case OP_ASIN: + case OP_ACOS: + case OP_ATAN: + case OP_SINH: + case OP_COSH: + case OP_TANH: + case OP_ASINH: + case OP_ACOSH: + case OP_ATANH: + // TODO + throw default_exception("transcendental and hyperbolic functions are not supported yet."); + default: + UNREACHABLE(); + } + return subpaving::null_var; + } + + subpaving::var process(expr * t, unsigned depth, mpz & n, mpz & d) { + SASSERT(is_int_real(t)); + checkpoint(); + + if (is_cached(t)) { + unsigned idx = m_cache.find(t); + qm().set(n, m_cached_numerators[idx]); + qm().set(d, m_cached_denominators[idx]); + return m_cached_vars[idx]; + } + + SASSERT(!is_quantifier(t)); + if (::is_var(t) || !m_autil.is_arith_expr(t)) { + qm().set(n, 1); + qm().set(d, 1); + return mk_var_for(t); + } + + return process_arith_app(to_app(t), depth, n, d); + } + + bool is_var(expr * t) const { + return m_expr2var->is_var(t); + } + + void set_cancel(bool f) { + m_cancel = f; + } + + subpaving::var internalize_term(expr * t, mpz & n, mpz & d) { + return process(t, 0, n, d); + } +}; + +expr2subpaving::expr2subpaving(ast_manager & m, subpaving::context & s, expr2var * e2v) { + m_imp = alloc(imp, m, s, e2v); +} + +expr2subpaving::~expr2subpaving() { + dealloc(m_imp); +} + +ast_manager & expr2subpaving::m() const { + return m_imp->m(); +} + +subpaving::context & expr2subpaving::s() const { + return m_imp->s(); +} + +bool expr2subpaving::is_var(expr * t) const { + return m_imp->is_var(t); +} + +void expr2subpaving::set_cancel(bool f) { + m_imp->set_cancel(f); +} + +subpaving::var expr2subpaving::internalize_term(expr * t, mpz & n, mpz & d) { + return m_imp->internalize_term(t, n, d); +} diff --git a/lib/expr2subpaving.h b/lib/expr2subpaving.h new file mode 100644 index 000000000..1deb023f7 --- /dev/null +++ b/lib/expr2subpaving.h @@ -0,0 +1,58 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + expr2subpaving.h + +Abstract: + + Translator from Z3 expressions into generic subpaving data-structure. + + +Author: + + Leonardo (leonardo) 2012-08-08 + +Notes: + +--*/ +#ifndef _EXPR2SUBPAVING_H_ +#define _EXPR2SUBPAVING_H_ + +#include"ast.h" +#include"subpaving.h" + +class expr2var; + +class expr2subpaving { + struct imp; + imp * m_imp; +public: + expr2subpaving(ast_manager & m, subpaving::context & s, expr2var * e2v = 0); + ~expr2subpaving(); + + ast_manager & m() const; + + subpaving::context & s() const; + + /** + \brief Return true if t was encoded as a variable by the translator. + */ + bool is_var(expr * t) const; + + /** + \brief Cancel/Interrupt execution. + */ + void set_cancel(bool f); + + /** + \brief Internalize a Z3 arithmetical expression into the subpaving data-structure. + + \remark throws subpaving::exception there is a translation error (when using imprecise representations, i.e. floats, in the subpaving module) + */ + subpaving::var internalize_term(expr * t, /* out */ mpz & n, /* out */ mpz & d); +}; + + +#endif diff --git a/lib/expr2var.cpp b/lib/expr2var.cpp new file mode 100644 index 000000000..1f5a8fe5c --- /dev/null +++ b/lib/expr2var.cpp @@ -0,0 +1,77 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + expr2var.h + +Abstract: + + The mapping between Z3 expressions and (low level) variables. + Example of low level variables: + - SAT solver + - Polynomial + - etc. + +Author: + + Leonardo (leonardo) 2011-12-23 + +Notes: + +--*/ +#include"expr2var.h" +#include"ast_smt2_pp.h" +#include"ref_util.h" + +void expr2var::insert(expr * n, var v) { + if (!is_uninterp_const(n)) { + TRACE("expr2var", tout << "interpreted:\n" << mk_ismt2_pp(n, m()) << "\n";); + m_interpreted_vars = true; + } + m().inc_ref(n); + m_mapping.insert(n, v); + m_recent_exprs.push_back(n); +} + +expr2var::expr2var(ast_manager & m): + m_manager(m), + m_interpreted_vars(false) { +} + +expr2var::~expr2var() { + dec_ref_map_keys(m(), m_mapping); +} + +expr2var::var expr2var::to_var(expr * n) const { + var v = UINT_MAX; + m_mapping.find(n, v); + return v; +} + +void expr2var::display(std::ostream & out) const { + obj_map::iterator it = m_mapping.begin(); + obj_map::iterator end = m_mapping.end(); + for (; it != end; ++it) { + out << mk_ismt2_pp(it->m_key, m()) << " -> " << it->m_value << "\n"; + } +} + +void expr2var::mk_inv(expr_ref_vector & var2expr) const { + obj_map::iterator it = m_mapping.begin(); + obj_map::iterator end = m_mapping.end(); + for (; it != end; ++it) { + expr * t = it->m_key; + var x = it->m_value; + if (x >= var2expr.size()) + var2expr.resize(x+1, 0); + var2expr.set(x, t); + } +} + +void expr2var::reset() { + dec_ref_map_keys(m(), m_mapping); + SASSERT(m_mapping.empty()); + m_recent_exprs.reset(); + m_interpreted_vars = false; +} diff --git a/lib/expr2var.h b/lib/expr2var.h new file mode 100644 index 000000000..35b5da632 --- /dev/null +++ b/lib/expr2var.h @@ -0,0 +1,75 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + expr2var.h + +Abstract: + + The mapping between Z3 expressions and (low level) variables. + Example of low level variables: + - SAT solver + - Polynomial + - etc. + +Author: + + Leonardo (leonardo) 2011-12-23 + +Notes: + +--*/ +#ifndef _EXPR2VAR_H_ +#define _EXPR2VAR_H_ + +#include"ast.h" +#include"obj_hashtable.h" + +/** + \brief The mapping between Z3 expressions and (low level) variables. +*/ +class expr2var { +public: + typedef unsigned var; + typedef obj_map expr2var_mapping; + typedef expr2var_mapping::iterator iterator; + typedef ptr_vector::const_iterator recent_iterator; +protected: + ast_manager & m_manager; + expr2var_mapping m_mapping; + ptr_vector m_recent_exprs; + bool m_interpreted_vars; +public: + expr2var(ast_manager & m); + ~expr2var(); + + ast_manager & m() const { return m_manager; } + + void insert(expr * n, var v); + + var to_var(expr * n) const; + + bool is_var(expr * n) const { return m_mapping.contains(n); } + + void display(std::ostream & out) const; + + void mk_inv(expr_ref_vector & var2expr) const; + + // return true if the mapping contains interpreted vars. + bool interpreted_vars() const { return m_interpreted_vars; } + + iterator begin() const { return m_mapping.begin(); } + iterator end() const { return m_mapping.end(); } + + void reset_recent() { m_recent_exprs.reset(); } + + // Iterators for traversing the recently registered expressions. + // The set of recent registered expressions is reset by using reset_recent(). + recent_iterator begin_recent() const { return m_recent_exprs.begin(); } + recent_iterator end_recent() const { return m_recent_exprs.end(); } + + void reset(); +}; + +#endif diff --git a/lib/expr_abstract.cpp b/lib/expr_abstract.cpp new file mode 100644 index 000000000..6deb4bf45 --- /dev/null +++ b/lib/expr_abstract.cpp @@ -0,0 +1,97 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + expr_abstract.h + +Abstract: + + Abstract occurrences of constants to bound variables. + +Author: + + Nikolaj Bjorner (nbjorner) 2008-03-08 + +Notes: + +--*/ + +#include "expr_abstract.h" +#include "map.h" + +void expr_abstract(ast_manager& m, unsigned base, unsigned num_bound, expr* const* bound, expr* n, expr_ref& result) { + ast_ref_vector pinned(m); + ptr_vector stack; + obj_map map; + expr * curr = 0, *b = 0; + SASSERT(n->get_ref_count() > 0); + + stack.push_back(n); + + for (unsigned i = 0; i < num_bound; ++i) { + b = bound[i]; + expr* v = m.mk_var(base + num_bound - i - 1, m.get_sort(b)); + pinned.push_back(v); + map.insert(b, v); + } + + while(!stack.empty()) { + curr = stack.back(); + if (map.contains(curr)) { + stack.pop_back(); + continue; + } + switch(curr->get_kind()) { + case AST_VAR: { + map.insert(curr, curr); + stack.pop_back(); + break; + } + case AST_APP: { + app* a = to_app(curr); + bool all_visited = true; + ptr_vector args; + for (unsigned i = 0; i < a->get_num_args(); ++i) { + if (!map.find(a->get_arg(i), b)) { + stack.push_back(a->get_arg(i)); + all_visited = false; + } + else { + args.push_back(b); + } + } + if (all_visited) { + b = m.mk_app(a->get_decl(), args.size(), args.c_ptr()); + pinned.push_back(b); + map.insert(curr, b); + stack.pop_back(); + } + break; + } + case AST_QUANTIFIER: { + quantifier* q = to_quantifier(curr); + expr_ref_buffer patterns(m); + expr_ref result1(m); + unsigned new_base = base + q->get_num_decls(); + + for (unsigned i = 0; i < q->get_num_patterns(); ++i) { + expr_abstract(m, new_base, num_bound, bound, q->get_pattern(i), result1); + patterns.push_back(result1.get()); + } + expr_abstract(m, new_base, num_bound, bound, q->get_expr(), result1); + b = m.update_quantifier(q, patterns.size(), patterns.c_ptr(), result1.get()); + pinned.push_back(b); + map.insert(curr, b); + stack.pop_back(); + break; + } + default: + UNREACHABLE(); + } + } + if (!map.find(n, b)) { + UNREACHABLE(); + } + result = b; +} diff --git a/lib/expr_abstract.h b/lib/expr_abstract.h new file mode 100644 index 000000000..c6ec7973b --- /dev/null +++ b/lib/expr_abstract.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + expr_abstract.h + +Abstract: + + Abstract occurrences of constants to bound variables. + +Author: + + Nikolaj Bjorner (nbjorner) 2008-03-08 + +Notes: + +--*/ +#ifndef _EXPR_ABSTRACT_H_ +#define _EXPR_ABSTRACT_H_ + +#include"ast.h" + +void expr_abstract(ast_manager& m, unsigned base, unsigned num_bound, expr* const* bound, expr* n, expr_ref& result); + +#endif + + diff --git a/lib/expr_context_simplifier.cpp b/lib/expr_context_simplifier.cpp new file mode 100644 index 000000000..a2a097ff5 --- /dev/null +++ b/lib/expr_context_simplifier.cpp @@ -0,0 +1,753 @@ +/*++ +Copyright (c) 2008 Microsoft Corporation + +Module Name: + + expr_context_simplifier.cpp + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2008-06-03 + +Revision History: + +--*/ + +#include "expr_context_simplifier.h" +#include "ast_pp.h" +#include "obj_hashtable.h" +#include "smt_solver.h" +#include "for_each_expr.h" + +// table lookup before/after simplification. + +expr_context_simplifier::expr_context_simplifier(ast_manager& m): + m_manager(m), m_arith(m), m_trail(m), m_simp(m), m_forward(true) {} + +void expr_context_simplifier::reduce(expr * m, expr_ref & result) { + expr_ref tmp(m_manager); + m_mark.reset(); + unsigned trail_size = m_trail.size(); + m_forward = true; + reduce_rec(m, tmp); + m_mark.reset(); + m_forward = false; + reduce_rec(tmp.get(), result); + clean_trail(trail_size); +} + +void expr_context_simplifier::reduce_fix(expr * m, expr_ref & result) { + expr_ref tmp(m_manager); + result = m; + do { + tmp = result.get(); + reduce(tmp.get(), result); + } + while (tmp.get() != result.get()); +} + +void expr_context_simplifier::reduce_rec(expr * m, expr_ref & result) { + // + // reduce expr in context evaluation. + // + bool polarity; + if (m_context.find(m, polarity)) { + result = polarity ? m_manager.mk_true() : m_manager.mk_false(); + } + else if (m_mark.is_marked(m) && !m_manager.is_not(m)) { + result = m; + } + else if (is_quantifier(m)) { + reduce_rec(to_quantifier(m), result); + m_mark.mark(m, true); + } + else if (is_app(m)) { + reduce_rec(to_app(m), result); + m_mark.mark(m, true); + } + else if (is_var(m)) { + result = m; + m_mark.mark(m, true); + } + else { + UNREACHABLE(); + result = m; + } +} + +void expr_context_simplifier::reduce_rec(quantifier* q, expr_ref & result) { + result = q; + +#if 0 + // + // The context assumes that asserted expressions are in NNF with + // respect to the quantifier occurrences. + // This can be disabled if the strong context simplifier + // is called from the API over the Z3_simplify method. + // + expr_context_simplifier nested(m_manager); + expr_ref body_r(m_manager); + nested.reduce(q->get_expr(), body_r); + if (body_r.get() != q->get_expr()) { + result = m_manager.update_quantifier(q, body_r.get()); + } + else { + result = q; + } +#endif +} + +void expr_context_simplifier::reduce_rec(app * a, expr_ref & result) { + if (m_manager.get_basic_family_id() == a->get_family_id()) { + switch(a->get_decl_kind()) { + case OP_AND: + reduce_and(a->get_num_args(), a->get_args(), result); + return; + case OP_OR: + reduce_or(a->get_num_args(), a->get_args(), result); + return; + case OP_IFF: { + expr_ref tmp1(m_manager), tmp2(m_manager); + reduce_rec(a->get_arg(0), tmp1); + reduce_rec(a->get_arg(1), tmp2); + m_simp.mk_iff(tmp1.get(), tmp2.get(), result); + return; + } + case OP_XOR: { + expr_ref tmp1(m_manager), tmp2(m_manager); + reduce_rec(a->get_arg(0), tmp1); + reduce_rec(a->get_arg(1), tmp2); + m_simp.mk_xor(tmp1.get(), tmp2.get(), result); + return; + } + case OP_NOT: { + expr_ref tmp(m_manager); + reduce_rec(a->get_arg(0), tmp); + m_simp.mk_not(tmp.get(), result); + return; + } + case OP_IMPLIES: { + app_ref tmp(m_manager); + tmp = m_manager.mk_not(a->get_arg(0)); + expr* args[2] = { tmp.get(), a->get_arg(1) }; + reduce_or(2, args, result); + return; + } + case OP_ITE: { + expr_ref tmp(m_manager), tmp1(m_manager), tmp2(m_manager); + reduce_rec(a->get_arg(0), tmp); + if (is_true(tmp.get())) { + reduce_rec(a->get_arg(1), result); + } + else if (is_false(tmp.get())) { + reduce_rec(a->get_arg(2), result); + } + else { + unsigned trail_size = m_trail.size(); + insert_context(tmp.get(), true); + reduce_rec(a->get_arg(1), tmp1); + clean_trail(trail_size); + + insert_context(tmp.get(), false); + reduce_rec(a->get_arg(2), tmp2); + clean_trail(trail_size); + + m_simp.mk_ite(tmp.get(), tmp1.get(), tmp2.get(), result); + } + return; + } + default: + break; + } + } + + expr_ref_vector args(m_manager); + for (unsigned i = 0; i < a->get_num_args(); ++i) { + expr_ref tmp(m_manager); + reduce_rec(a->get_arg(i), tmp); + args.push_back(tmp.get()); + } + result = m_manager.mk_app(a->get_decl(), args.size(), args.c_ptr()); +} + +void expr_context_simplifier::clean_trail(unsigned old_lim) { + for (unsigned i = m_trail.size(); i > old_lim; ) { + --i; + m_context.erase(m_trail[i].get()); + } + m_trail.resize(old_lim); +} + +void expr_context_simplifier::insert_context(expr* e, bool polarity) { + TRACE("expr_context_simplifier", tout << mk_pp(e, m_manager) << "\n";); + if (m_manager.is_not(e)) { + e = to_app(e)->get_arg(0); + polarity = !polarity; + } + if (!m_context.contains(e)) { + m_context.insert(e, polarity); + m_trail.push_back(e); + } +} + +bool expr_context_simplifier::insert_arg(bool is_and, expr* arg, expr_ref_vector& args) { + expr_ref tmp(m_manager); + reduce_rec(arg, tmp); + TRACE("expr_context_simplifier", tout << mk_pp(arg, m_manager) << " -> " << mk_pp(tmp.get(), m_manager) << "\n";); + if (is_true(tmp.get()) && is_and) { + // skip. + } + else if (is_false(tmp.get()) && !is_and) { + // skip. + } + else if (is_false(tmp.get()) && is_and) { + return true; + } + else if (is_true(tmp.get()) && !is_and) { + return true; + } + else { + insert_context(tmp.get(), is_and); + if (arg != tmp.get()) { + insert_context(arg, is_and); // allow to also use un-simplified version + } + args.push_back(tmp.get()); + } + return false; +} + +void expr_context_simplifier::reduce_and_or(bool is_and, unsigned num_args, expr * const* args, expr_ref & result) { + expr_ref tmp(m_manager); + expr_ref_vector args1(m_manager); + unsigned trail_size = m_trail.size(); + + if (m_forward) { + for (unsigned i = 0; i < num_args; ++i) { + if (insert_arg(is_and, args[i], args1)) { + clean_trail(trail_size); + result = is_and?m_manager.mk_false():m_manager.mk_true(); + return; + } + } + } + else { + for (unsigned i = num_args; i > 0; ) { + --i; + if (insert_arg(is_and, args[i], args1)) { + clean_trail(trail_size); + result = is_and?m_manager.mk_false():m_manager.mk_true(); + return; + } + } + } + clean_trail(trail_size); + + if (is_and) { + m_simp.mk_and(args1.size(), args1.c_ptr(), result); + } + else { + m_simp.mk_or(args1.size(), args1.c_ptr(), result); + } +} + +void expr_context_simplifier::reduce_and(unsigned num_args, expr * const* args, expr_ref & result) { + reduce_and_or(true, num_args, args, result); +} + +void expr_context_simplifier::reduce_or(unsigned num_args, expr * const* args, expr_ref & result) { + reduce_and_or(false, num_args, args, result); +} + +bool expr_context_simplifier::is_true(expr* e) const { + return + m_manager.is_true(e) || + (m_manager.is_not(e) && m_manager.is_false(to_app(e)->get_arg(0))); +} + +bool expr_context_simplifier::is_false(expr* e) const { + return + m_manager.is_false(e) || + (m_manager.is_not(e) && m_manager.is_true(to_app(e)->get_arg(0))); +} + + +// +// This routine performs strong context simplification. +// It replaces sub-formulas by a fresh name +// and checks if the original formula is equivalent +// to the resulting formula if the fresh name is set to +// true or false. +// otherwise it recursively expands the definition of the +// fresh name to match the original formula. +// +// assert ! (fml <=> n) +// +//for (fml', n') +// check visited +// check n' +// check !n' +// if each a is visited, +// fml' by fml'[a/a'] +// pop_scope +// ow, +// let a be a non-visited argument. +// push_scope +// push (a, n'') +// assert (n' <=> f(visited_args, n'', visited_or_non_visited_args)) +// +// The implementation avoid the stack. It uses the following vectors: +// todo - DFS stack +// names - collection of fresh names. +// is_checked - Boolean to control if contextual equivalence with T or F was checked. +// parent_ids - stack of IDs used to identify path down to expression on first visit. +// self_ids - stack of IDs used to identify path down to children on first visit. +// The parent_ids, self_ids stacks are used to ensure that caching results can be done +// in a context dependent way. A cached result is only valid for simplification if +// it occurs in the context (on the path) where it was inserted. +// + +expr_strong_context_simplifier::expr_strong_context_simplifier(front_end_params& p, ast_manager& m): + m_manager(m), m_params(p), m_arith(m), m_id(0), m_fn(0,m), m_solver(m, p) { + sort* i_sort = m_arith.mk_int(); + m_fn = m.mk_func_decl(symbol(0xbeef101), i_sort, m.mk_bool_sort()); +} + + +void expr_strong_context_simplifier::simplify_basic(expr* fml, expr_ref& result) { + ast_manager& m = m_manager; + // + // The context assumes that asserted expressions are in NNF with + // respect to the quantifier occurrences. + // This can be disabled if the strong context simplifier + // is called from the API over the Z3_simplify method. + // + if (!m.is_bool(fml) || has_quantifiers(fml)) { + result = fml; + return; + } + flet fl1(m_params.m_strong_context_simplifier, false); + + ptr_vector todo; + ptr_vector names; + svector is_checked; + svector parent_ids, self_ids; + expr_ref_vector fresh_vars(m); + expr_ref_vector trail(m); + obj_map > cache; + m_solver.push(); + unsigned id = 1; + expr* n = m.mk_app(m_fn, m_arith.mk_numeral(rational(id++), true)); + expr* r, *n2; + lbool is_sat; + unsigned path_id = 0, self_pos = 0; + app * a; + unsigned sz; + trail.push_back(n); + + m_solver.assert_expr(m.mk_not(m.mk_iff(fml, n))); + todo.push_back(fml); + names.push_back(n); + is_checked.push_back(false); + parent_ids.push_back(0); + self_ids.push_back(0); + std::pair path_r; + + m_solver.push(); + while (!todo.empty()) { + + r = 0; + ptr_buffer args; + expr* e = todo.back(); + unsigned pos = parent_ids.back(); + n = names.back(); + bool checked = is_checked.back(); + + if (cache.contains(e)) { + goto done; + } + if (!m.is_bool(e)) { + r = e; + goto done; + } + if (m.is_bool(e) && !checked) { + m_solver.push(); + m_solver.assert_expr(n); + is_sat = m_solver.check(); + m_solver.pop(1); + if (is_sat == l_false) { + r = m.mk_true(); + goto done; + } + } + if (m.is_bool(e) && !checked) { + m_solver.push(); + m_solver.assert_expr(m.mk_not(n)); + is_sat = m_solver.check(); + m_solver.pop(1); + if (is_sat == l_false) { + r = m.mk_false(); + goto done; + } + } + if (!is_app(e)) { + r = e; + goto done; + } + + a = to_app(e); + if (!is_checked.back()) { + self_ids.back() = ++path_id; + is_checked.back() = true; + } + self_pos = self_ids.back(); + sz = a->get_num_args(); + + n2 = 0; + for (unsigned i = 0; i < sz; ++i) { + expr* arg = a->get_arg(i); + + if (cache.find(arg, path_r)) { + if (path_r.first == self_pos) { + args.push_back(path_r.second); + } + else { + args.push_back(arg); + } + } + else if (!m.is_bool(arg)) { + args.push_back(arg); + } + else if (!n2) { + n2 = m.mk_app(m_fn, m_arith.mk_numeral(rational(id++), true)); + todo.push_back(arg); + parent_ids.push_back(self_pos); + self_ids.push_back(0); + names.push_back(n2); + trail.push_back(n2); + args.push_back(n2); + is_checked.push_back(false); + } + else { + args.push_back(arg); + } + } + r = m.mk_app(a->get_decl(), args.size(), args.c_ptr()); + trail.push_back(r); + if (n2) { + m_solver.push(); + m_solver.assert_expr(m.mk_eq(r, n)); + continue; + } + + done: + if (r) { + cache.insert(e, std::make_pair(pos, r)); + } + + TRACE("expr_context_simplifier", + tout << mk_pp(e, m_manager) + << " checked: " << checked + << " cached: " + << mk_pp(r?r:e, m_manager) << "\n";); + + todo.pop_back(); + parent_ids.pop_back(); + self_ids.pop_back(); + names.pop_back(); + is_checked.pop_back(); + m_solver.pop(1); + } + + VERIFY(cache.find(fml, path_r)); + m_solver.pop(1); + result = path_r.second; +} + + +void expr_strong_context_simplifier::simplify_model_based(expr* fml, expr_ref& result) { + ast_manager& m = m_manager; + // + // The context assumes that asserted expressions are in NNF with + // respect to the quantifier occurrences. + // This can be disabled if the strong context simplifier + // is called from the API over the Z3_simplify method. + // + if (!m.is_bool(fml) || has_quantifiers(fml)) { + result = fml; + return; + } + flet fl1(m_params.m_strong_context_simplifier, false); + + ptr_vector todo; + ptr_vector names; + svector is_checked; + svector parent_ids, self_ids; + expr_ref_vector fresh_vars(m); + expr_ref_vector trail(m); + obj_map > cache; + lbool is_sat; + expr_ref_vector assignments(m); + + m_solver.push(); + m_solver.assert_expr(fml); + is_sat = m_solver.check(); + if (is_sat != l_false) { + m_solver.get_assignments(assignments); + } + m_solver.pop(1); + if (is_sat == l_false) { + result = m.mk_false(); + return; + } + // Collect assignments to sub-formulas from satisfying assignment. + obj_map assignment_map; + { + expr* n1, *n2; + for (unsigned i = 0; i < assignments.size(); ++i) { + if (m.is_not(assignments[i].get(), n1)) { + assignment_map.insert(n1, l_false); + } + else { + assignment_map.insert(assignments[i].get(), l_true); + } + } + + todo.push_back(fml); + while (!todo.empty()) { + expr* n = todo.back(); + if (!is_app(n)) { + assignment_map.insert(n, l_undef); + todo.pop_back(); + continue; + } + app* a = to_app(n); + unsigned sz = a->get_num_args(); + bool all_visit = true; + for (unsigned i = 0; i < sz; ++i) { + if (!assignment_map.contains(a->get_arg(i))) { + todo.push_back(a->get_arg(i)); + all_visit = false; + } + } + if (!all_visit) { + continue; + } + todo.pop_back(); + lbool value = l_undef; + if (m.is_and(a)) { + value = l_true; + for (unsigned i = 0; value != l_false && i < sz; ++i) { + switch(assignment_map.find(a->get_arg(i))) { + case l_false: value = l_false; break; + case l_undef: value = l_undef; break; + default: break; + } + } + assignment_map.insert(a, value); + } + else if (m.is_or(a)) { + value = l_false; + for (unsigned i = 0; value != l_true && i < sz; ++i) { + switch(assignment_map.find(a->get_arg(i))) { + case l_true: value = l_true; break; + case l_undef: value = l_undef; break; + default: break; + } + } + assignment_map.insert(a, value); + } + else if (m.is_not(a)) { + switch(assignment_map.find(a->get_arg(0))) { + case l_true: value = l_false; break; + case l_false: value = l_true; break; + default: value = l_undef; break; + } + assignment_map.insert(a, value); + } + else if (m.is_implies(a, n1, n2)) { + lbool v1 = assignment_map.find(n1); + lbool v2 = assignment_map.find(n2); + if (v1 == l_false || v2 == l_true) { + value = l_true; + } + else if (v1 == l_true && v2 == l_false) { + value = l_false; + } + else { + value = l_undef; + } + assignment_map.insert(a, value); + } + else if (m.is_iff(a, n1, n2) || m.is_eq(a, n1, n2)) { + lbool v1 = assignment_map.find(n1); + lbool v2 = assignment_map.find(n2); + if (v1 == l_undef || v2 == l_undef) { + value = l_undef; + } + else if (v1 == v2) { + value = l_true; + } + else { + value = l_false; + } + assignment_map.insert(a, value); + } + else { + assignment_map.insert(a, l_undef); + } + } + } + + m_solver.push(); + unsigned id = 1; + expr* n = m.mk_app(m_fn, m_arith.mk_numeral(rational(id++), true)); + expr* r, *n2; + unsigned path_id = 0, self_pos = 0; + app * a; + unsigned sz; + trail.push_back(n); + + m_solver.assert_expr(m.mk_not(m.mk_iff(fml, n))); + todo.push_back(fml); + names.push_back(n); + is_checked.push_back(false); + parent_ids.push_back(0); + self_ids.push_back(0); + std::pair path_r; + + m_solver.push(); + while (!todo.empty()) { + + r = 0; + ptr_buffer args; + expr* e = todo.back(); + unsigned pos = parent_ids.back(); + n = names.back(); + bool checked = is_checked.back(); + + if (cache.contains(e)) { + goto done; + } + if (!m.is_bool(e)) { + r = e; + goto done; + } + if (m.is_bool(e) && !checked) { + lbool value = l_undef; + assignment_map.find(e, value); + + switch(value) { + case l_true: + if (is_forced(n, m.mk_true())) { + r = m.mk_true(); + goto done; + } + break; + case l_false: + if (is_forced(n, m.mk_false())) { + r = m.mk_false(); + goto done; + } + break; + default: + // NB. assignments contain just internalized literals, + // the literals in the input may not be internalized. + // we therefore fall back to the default behavior, which + // is to check both cases. + if (is_forced(n, m.mk_true())) { + r = m.mk_true(); + goto done; + } + if (is_forced(n, m.mk_false())) { + r = m.mk_false(); + goto done; + } + break; + } + + } + if (!is_app(e)) { + r = e; + goto done; + } + + a = to_app(e); + if (!is_checked.back()) { + self_ids.back() = ++path_id; + is_checked.back() = true; + } + self_pos = self_ids.back(); + sz = a->get_num_args(); + + n2 = 0; + for (unsigned i = 0; i < sz; ++i) { + expr* arg = a->get_arg(i); + + if (cache.find(arg, path_r)) { + if (path_r.first == self_pos) { + args.push_back(path_r.second); + } + else { + args.push_back(arg); + } + } + else if (!m.is_bool(arg)) { + args.push_back(arg); + } + else if (!n2) { + n2 = m.mk_app(m_fn, m_arith.mk_numeral(rational(id++), true)); + todo.push_back(arg); + parent_ids.push_back(self_pos); + self_ids.push_back(0); + names.push_back(n2); + trail.push_back(n2); + args.push_back(n2); + is_checked.push_back(false); + } + else { + args.push_back(arg); + } + } + r = m.mk_app(a->get_decl(), args.size(), args.c_ptr()); + trail.push_back(r); + if (n2) { + m_solver.push(); + m_solver.assert_expr(m.mk_eq(r, n)); + continue; + } + + done: + if (r) { + cache.insert(e, std::make_pair(pos, r)); + } + + TRACE("expr_context_simplifier", + tout << mk_pp(e, m_manager) + << " checked: " << checked + << " cached: " + << mk_pp(r?r:e, m_manager) << "\n";); + + todo.pop_back(); + parent_ids.pop_back(); + self_ids.pop_back(); + names.pop_back(); + is_checked.pop_back(); + m_solver.pop(1); + } + + VERIFY(cache.find(fml, path_r)); + m_solver.pop(1); + result = path_r.second; +} + +bool expr_strong_context_simplifier::is_forced(expr* e, expr* v) { + m_solver.push(); + m_solver.assert_expr(m_manager.mk_eq(e, v)); + lbool is_sat = m_solver.check(); + m_solver.pop(1); + return (is_sat == l_false); +} diff --git a/lib/expr_context_simplifier.h b/lib/expr_context_simplifier.h new file mode 100644 index 000000000..758c3af8b --- /dev/null +++ b/lib/expr_context_simplifier.h @@ -0,0 +1,86 @@ +/*++ +Copyright (c) 2008 Microsoft Corporation + +Module Name: + + expr_context_simplifier.h + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2008-06-03 + +Revision History: + +--*/ +#ifndef _EXPR_CONTEXT_SIMPLIFIER_H_ +#define _EXPR_CONTEXT_SIMPLIFIER_H_ + +#include "ast.h" +#include "obj_hashtable.h" +#include "basic_simplifier_plugin.h" +#include "front_end_params.h" +#include "smt_solver.h" +#include "arith_decl_plugin.h" + +class expr_context_simplifier { + typedef obj_map context_map; + + ast_manager& m_manager; + arith_util m_arith; + context_map m_context; + expr_ref_vector m_trail; + basic_simplifier_plugin m_simp; + expr_mark m_mark; + bool m_forward; +public: + expr_context_simplifier(ast_manager & m); + void reduce_fix(expr * m, expr_ref & result); + void operator()(expr * m, expr_ref & result) { reduce(m, result); } + void insert_context(expr* e, bool polarity); +private: + void reduce(expr * m, expr_ref & result); + void reduce_rec(expr * m, expr_ref & result); + void reduce_rec(quantifier* q, expr_ref & result); + void reduce_rec(app * a, expr_ref & result); + void clean_trail(unsigned old_lim); + bool insert_arg(bool is_and, expr* arg, expr_ref_vector& args); + void reduce_and_or(bool is_and, unsigned num_args, expr * const* args, expr_ref & result); + void reduce_and(unsigned num_args, expr * const* args, expr_ref & result); + void reduce_or(unsigned num_args, expr * const* args, expr_ref & result); + bool is_true(expr* e) const; + bool is_false(expr* e) const; +}; + +class expr_strong_context_simplifier { + ast_manager& m_manager; + front_end_params & m_params; + arith_util m_arith; + unsigned m_id; + func_decl_ref m_fn; + smt::solver m_solver; + + void simplify(expr* e, expr_ref& result) { simplify_model_based(e, result); } + void simplify_basic(expr* fml, expr_ref& result); + void simplify_model_based(expr* fml, expr_ref& result); + + bool is_forced(expr* e, expr* v); + +public: + expr_strong_context_simplifier(front_end_params& p, ast_manager& m); + void operator()(expr* e, expr_ref& result) { simplify(e, result); } + void operator()(expr_ref& result) { simplify(result.get(), result); } + void push() { m_solver.push(); } + void pop() { m_solver.pop(1); } + void assert(expr* e) { m_solver.assert_expr(e); } + + void collect_statistics(statistics & st) const { m_solver.collect_statistics(st); } + void reset_statistics() { m_solver.reset_statistics(); } + void set_cancel(bool f) { m_solver.set_cancel(f); } +}; + +#endif /* _EXPR_CONTEXT_SIMPLIFIER_H__ */ + diff --git a/lib/expr_delta.cpp b/lib/expr_delta.cpp new file mode 100644 index 000000000..344e555c9 --- /dev/null +++ b/lib/expr_delta.cpp @@ -0,0 +1,77 @@ +#include "expr_delta.h" +#include "ast_pp.h" + +expr_delta::expr_delta(ast_manager& m) : m_manager(m), m_exprs(m) {} + +void expr_delta::assert_cnstr(expr* n) { + m_exprs.push_back(n); +} + +bool expr_delta::delta_dfs(unsigned n, expr_ref_vector& result) { + return delta_dfs(n, m_exprs.size(), m_exprs.c_ptr(), result); +} + +bool expr_delta::delta_dfs(unsigned& n, unsigned sz, expr* const* exprs, expr_ref_vector& result) { + expr_ref r(m_manager); + for (unsigned i = 0; i < sz; ++i) { + expr* e = exprs[i]; + if (delta_dfs(n, e, r)) { + result.push_back(r.get()); + for (unsigned j = i+1; j < sz; ++j) { + result.push_back(exprs[j]); + } + return true; + } + else { + result.push_back(e); + } + } + return false; +} + +bool expr_delta::delta_dfs(unsigned& n, app* a, expr_ref& result) { + expr_ref_vector args(m_manager); + if (delta_dfs(n, a->get_num_args(), a->get_args(), args)) { + result = m_manager.mk_app(a->get_decl(), args.size(), args.c_ptr()); + return true; + } + else { + return false; + } +} + +bool expr_delta::delta_dfs(unsigned& n, expr* e, expr_ref& result) { + ast_manager& m = m_manager; + if (m.is_true(e) || m.is_false(e)) { + return false; + } + if (n == 0 && m.is_bool(e)) { + result = m.mk_true(); + return true; + } + else if (n == 1 && m.is_bool(e)) { + result = m.mk_false(); + return true; + } + else if (is_app(e)) { + if (m.is_bool(e)) { + SASSERT(n >= 2); + n -= 2; + } + return delta_dfs(n, to_app(e), result); + } + else if (is_quantifier(e)) { + SASSERT(n >= 2); + n -= 2; + quantifier* q = to_quantifier(e); + if (delta_dfs(n, q->get_expr(), result)) { + result = m.update_quantifier(q, result.get()); + return true; + } + else { + return false; + } + } + return false; +} + diff --git a/lib/expr_delta.h b/lib/expr_delta.h new file mode 100644 index 000000000..1ac03d579 --- /dev/null +++ b/lib/expr_delta.h @@ -0,0 +1,52 @@ +/*++ +Copyright (c) 2008 Microsoft Corporation + +Module Name: + + expr_delta.h + +Abstract: + + Delta debugging support for specifications. + A specification is a list of assumptions. + +Author: + + Nikolaj Bjorner (nbjorner) 2008-21-06 + +Revision History: + +--*/ +#ifndef _EXPR_DELTA_H_ +#define _EXPR_DELTA_H_ + +#include "ast.h" + +class expr_delta { + ast_manager& m_manager; + expr_ref_vector m_exprs; +public: + expr_delta(ast_manager& m); + + // Assert a constraint. + void assert_cnstr(expr* e); + + // + // Create the n'th delta in dfs mode. + // resturn 'true' if a delta was obtained. + // + bool delta_dfs(unsigned n, expr_ref_vector& result); + +private: + + // perform delta + bool delta_dfs(unsigned& n, expr* e, expr_ref& result); + + bool delta_dfs(unsigned& n, app* a, expr_ref& result); + + bool delta_dfs(unsigned& n, unsigned sz, expr* const* exprs, expr_ref_vector& result); + +}; + + +#endif diff --git a/lib/expr_delta_pair.h b/lib/expr_delta_pair.h new file mode 100644 index 000000000..f1994db6a --- /dev/null +++ b/lib/expr_delta_pair.h @@ -0,0 +1,36 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + expr_delta_pair.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-14. + +Revision History: + +--*/ +#ifndef _EXPR_DELTA_PAIR_H_ +#define _EXPR_DELTA_PAIR_H_ + +/** + \brief Auxiliary structure used to cache the intermediate results of the variable substitution procedure. +*/ +struct expr_delta_pair { + expr * m_node; + unsigned m_delta; + + expr_delta_pair():m_node(0), m_delta(0) {} + expr_delta_pair(expr * n, unsigned d):m_node(n), m_delta(d) {} + unsigned hash() const { return hash_u_u(m_node->hash(), m_delta); } + bool operator==(const expr_delta_pair & e) const { return m_node == e.m_node && m_delta == e.m_delta; } +}; + +#endif /* _EXPR_DELTA_PAIR_H_ */ + diff --git a/lib/expr_functors.cpp b/lib/expr_functors.cpp new file mode 100644 index 000000000..592d96a68 --- /dev/null +++ b/lib/expr_functors.cpp @@ -0,0 +1,142 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + expr_functors.cpp + +Abstract: + + Functors on expressions. + +Author: + + Nikolaj Bjorner (nbjorner) 2010-02-19 + +Revision History: + + Hoisted from quant_elim. + +--*/ + +#include "expr_functors.h" + +// ---------- +// check_pred + +bool check_pred::operator()(expr* e) { + if (!m_visited.is_marked(e)) { + m_refs.push_back(e); + visit(e); + } + SASSERT(m_visited.is_marked(e)); + return m_pred_holds.is_marked(e); +} + +void check_pred::visit(expr* e) { + ptr_vector todo; + todo.push_back(e); + while (!todo.empty()) { + e = todo.back(); + if (m_pred(e)) { + m_pred_holds.mark(e, true); + } + if (m_visited.is_marked(e)) { + todo.pop_back(); + continue; + } + switch(e->get_kind()) { + case AST_APP: { + app* a = to_app(e); + bool all_visited = true; + unsigned num_args = a->get_num_args(); + for (unsigned i = 0; i < num_args; ++i) { + expr* arg = a->get_arg(i); + if (!m_visited.is_marked(arg)) { + todo.push_back(arg); + all_visited = false; + } + else if (m_pred_holds.is_marked(arg)) { + m_pred_holds.mark(e, true); + } + } + if (all_visited) { + m_visited.mark(e, true); + todo.pop_back(); + } + break; + } + case AST_QUANTIFIER: { + quantifier* q = to_quantifier(e); + expr* arg = q->get_expr(); + if (m_visited.is_marked(arg)) { + todo.pop_back(); + if (m_pred_holds.is_marked(arg)) { + m_pred_holds.mark(e, true); + } + m_visited.mark(e, true); + } + else { + todo.push_back(arg); + } + break; + } + case AST_VAR: + todo.pop_back(); + m_visited.mark(e, true); + break; + default: + UNREACHABLE(); + break; + } + } +} + +// ------------ +// contains_app + + +bool contains_app::operator()(unsigned size, expr* const* es) { + for (unsigned i = 0; i < size; ++i) { + if ((*this)(es[i])) { + return true; + } + } + return false; +} + +// ----------- +// map_proc + +void map_proc::reconstruct(app* a) { + m_args.reset(); + bool is_new = false; + for (unsigned i = 0; i < a->get_num_args(); ++i) { + expr* e1 = a->get_arg(i); + expr* e2 = get_expr(e1); + m_args.push_back(e2); + if (e1 != e2) { + is_new = true; + } + } + if (is_new) { + expr* b = m.mk_app(a->get_decl(), m_args.size(), m_args.c_ptr()); + m_map.insert(a, b, 0); + } + else { + m_map.insert(a, a, 0); + } +} + +void map_proc::visit(quantifier* e) { + expr_ref q(m); + q = m.update_quantifier(e, get_expr(e->get_expr())); + m_map.insert(e, q, 0); +} + +expr* map_proc::get_expr(expr* e) { + expr* result = 0; + proof* p = 0; + m_map.get(e, result, p); + return result; +} diff --git a/lib/expr_functors.h b/lib/expr_functors.h new file mode 100644 index 000000000..f2016c8ed --- /dev/null +++ b/lib/expr_functors.h @@ -0,0 +1,121 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + expr_functors.h + +Abstract: + + Functors on expressions. + +Author: + + Nikolaj Bjorner (nbjorner) 2010-02-19 + +Revision History: + + Hoisted from quant_elim. + +--*/ + +#ifndef __EXPR_FUNCTORS_H__ +#define __EXPR_FUNCTORS_H__ + +#include "ast.h" +#include "expr_map.h" + +class i_expr_pred { +public: + virtual bool operator()(expr* e) = 0; + virtual ~i_expr_pred() {} +}; + +/** + \brief Memoizing predicate functor on sub-expressions. + + The class is initialized with a predicate 'p' on expressions. + + The class is memoizing. + +*/ + +class check_pred { + i_expr_pred& m_pred; + ast_mark m_pred_holds; + ast_mark m_visited; + expr_ref_vector m_refs; +public: + check_pred(i_expr_pred& p, ast_manager& m) : m_pred(p), m_refs(m) {} + + bool operator()(expr* e); + + void reset() { m_pred_holds.reset(); m_visited.reset(); m_refs.reset(); } + +private: + void visit(expr* e); +}; + +/** + \brief Determine if expression 'e' or vector of expressions 'v' contains the app x +*/ + +class contains_app { + class pred : public i_expr_pred { + app* m_x; + public: + pred(app* x) : m_x(x) {} + virtual bool operator()(expr* e) { + return m_x == e; + } + }; + app_ref m_x; + pred m_pred; + check_pred m_check; + +public: + contains_app(ast_manager& m, app* x) : + m_x(x, m), m_pred(x), m_check(m_pred, m) {} + + bool operator()(expr* e) { + return m_check(e); + } + + bool operator()(expr_ref_vector const& v) { + return (*this)(v.size(), v.c_ptr()); + } + + bool operator()(unsigned size, expr* const* es); + + app* x() const { return m_x; } + +}; + +/** + \brief Base class of functor that applies map to expressions. +*/ +class map_proc { +protected: + ast_manager& m; + expr_map m_map; + ptr_vector m_args; +public: + map_proc(ast_manager& m): + m(m), + m_map(m) + {} + + void reset() { m_map.reset(); } + + void visit(var* e) { m_map.insert(e, e, 0); } + + void visit(quantifier* e); + + void reconstruct(app* a); + + expr* get_expr(expr* e); + +}; + + +#endif diff --git a/lib/expr_map.cpp b/lib/expr_map.cpp new file mode 100644 index 000000000..87db2186b --- /dev/null +++ b/lib/expr_map.cpp @@ -0,0 +1,94 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + expr_map.cpp + +Abstract: + + Mapping from expressions to expressions + proofs. This mapping + is used to cache simplification results. + For every entry [e1->(e2, p)] we have that p is a proof that (= e1 e2). + +Author: + + Leonardo (leonardo) 2008-01-03 + +Notes: + +--*/ +#include"expr_map.h" +#include"dec_ref_util.h" + +expr_map::expr_map(ast_manager & m): + m_manager(m), + m_store_proofs(m.proofs_enabled()) { +} + +expr_map::expr_map(ast_manager & m, bool store_proofs): + m_manager(m), + m_store_proofs(store_proofs) { +} + +expr_map::~expr_map() { + reset(); +} + +void expr_map::insert(expr * k, expr * d, proof * p) { + m_manager.inc_ref(d); + obj_map::obj_map_entry * entry = m_expr2expr.find_core(k); + if (entry != 0) { + m_manager.dec_ref(entry->get_data().m_value); + entry->get_data().m_value = d; + if (m_store_proofs) { + m_manager.inc_ref(p); + obj_map::obj_map_entry * entry_pr = m_expr2pr.find_core(k); + SASSERT(entry_pr != 0); + m_manager.dec_ref(entry_pr->get_data().m_value); + entry_pr->get_data().m_value = p; + } + } + else { + m_manager.inc_ref(k); + m_expr2expr.insert(k, d); + if (m_store_proofs) { + m_manager.inc_ref(p); + m_expr2pr.insert(k, p); + } + } +} + +void expr_map::get(expr * k, expr * & d, proof * & p) const { + if (m_expr2expr.find(k, d)) { + p = 0; + if (m_store_proofs) + m_expr2pr.find(k, p); + } +} + +void expr_map::erase(expr * k) { + expr * v; + if (m_expr2expr.find(k, v)) { + m_expr2expr.erase(k); + m_manager.dec_ref(v); + if (m_store_proofs) { + proof * pr = 0; + m_expr2pr.find(k, pr); + m_expr2pr.erase(k); + m_manager.dec_ref(pr); + } + m_manager.dec_ref(k); + } +} + +void expr_map::reset() { + dec_ref_values(m_manager, m_expr2pr); + dec_ref_key_values(m_manager, m_expr2expr); +} + +void expr_map::flush() { + reset(); + m_expr2expr.finalize(); + m_expr2pr.finalize(); +} diff --git a/lib/expr_map.h b/lib/expr_map.h new file mode 100644 index 000000000..5ab6545f8 --- /dev/null +++ b/lib/expr_map.h @@ -0,0 +1,49 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + expr_map.h + +Abstract: + + Mapping from expressions to expressions + proofs. This mapping + is used to cache simplification results. + For every entry [e1->(e2, p)] we have that p is a proof that (= e1 e2). + +Author: + + Leonardo (leonardo) 2008-01-03 + +Notes: + +--*/ +#ifndef _EXPR_MAP_H_ +#define _EXPR_MAP_H_ + +#include"ast.h" +#include"obj_hashtable.h" + +/** + \brief Map from expressions to expressions+proofs. + + When proof production is disabled, no extra space is used. +*/ +class expr_map { + ast_manager & m_manager; + bool m_store_proofs; + obj_map m_expr2expr; + obj_map m_expr2pr; +public: + expr_map(ast_manager & m); + expr_map(ast_manager & m, bool store_proofs); + ~expr_map(); + void insert(expr * k, expr * d, proof * p); + bool contains(expr * k) const { return m_expr2expr.contains(k); } + void get(expr * k, expr * & d, proof * & p) const; + void erase(expr * k); + void reset(); + void flush(); +}; + +#endif diff --git a/lib/expr_offset.h b/lib/expr_offset.h new file mode 100644 index 000000000..854720c9e --- /dev/null +++ b/lib/expr_offset.h @@ -0,0 +1,53 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + expr_offset.h + +Abstract: + + Expressions + Offsets. + In order to avoid creating variants of terms, we use a pair (expression, offset), + where offset is just a small integer. + Non ground terms with different offsets are always considered + disequal. For example, (f x):1 is different from (f x):2, and + (f x):1 is unifiable with x:2. + +Author: + + Leonardo de Moura (leonardo) 2008-01-28. + +Revision History: + +--*/ +#ifndef _EXPR_OFFSET_H_ +#define _EXPR_OFFSET_H_ + +#include"ast.h" + +class expr_offset { + expr * m_expr; + unsigned m_offset; +public: + expr_offset():m_expr(0), m_offset(0) {} + expr_offset(expr * e, unsigned o):m_expr(e), m_offset(o) {} + + expr * get_expr() const { return m_expr; } + unsigned get_offset() const { return m_offset; } + bool operator==(expr_offset const & other) const { return m_expr == other.m_expr && m_offset == other.m_offset; } + bool operator!=(expr_offset const & other) const { return !operator==(other); } + + unsigned hash() const { + unsigned a = m_expr->get_id(); + unsigned b = m_offset; + unsigned c = 17; + mix(a, b, c); + return c; + } +}; + +typedef std::pair expr_offset_pair; +typedef pair_hash, obj_hash > expr_offset_pair_hash; + +#endif /* _EXPR_OFFSET_H_ */ diff --git a/lib/expr_offset_map.h b/lib/expr_offset_map.h new file mode 100644 index 000000000..dac5ceac6 --- /dev/null +++ b/lib/expr_offset_map.h @@ -0,0 +1,94 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + expr_offset_map.h + +Abstract: + + A generic mapping from (expression, offset) to a value T. + +Author: + + Leonardo de Moura (leonardo) 2008-02-01. + +Revision History: + +--*/ +#ifndef _EXPR_OFFSET_MAP_H_ +#define _EXPR_OFFSET_MAP_H_ + +#include"expr_offset.h" +#include"vector.h" + +/** + \brief A mapping from expr_offset to some value of type T. +*/ +template +class expr_offset_map { + struct data { + T m_data; + unsigned m_timestamp; + data():m_timestamp(0) {} + }; + vector > m_map; + unsigned m_timestamp; +public: + expr_offset_map(): + m_timestamp(1) {} + + bool contains(expr_offset const & n) const { + unsigned off = n.get_offset(); + if (off < m_map.size()) { + svector const & v = m_map[off]; + unsigned id = n.get_expr()->get_id(); + if (id < v.size()) + return v[id].m_timestamp == m_timestamp; + } + return false; + } + + bool find(expr_offset const & n, T & r) const { + unsigned off = n.get_offset(); + if (off < m_map.size()) { + svector const & v = m_map[off]; + unsigned id = n.get_expr()->get_id(); + if (id < v.size() && v[id].m_timestamp == m_timestamp) { + r = v[id].m_data; + return true; + } + } + return false; + } + + void insert(expr_offset const & n, T const & r) { + unsigned off = n.get_offset(); + if (off >= m_map.size()) + m_map.resize(off+1, svector()); + svector & v = m_map[off]; + unsigned id = n.get_expr()->get_id(); + if (id >= v.size()) + v.resize(id+1); + v[id].m_data = r; + v[id].m_timestamp = m_timestamp; + } + + void reset() { + m_timestamp++; + if (m_timestamp == UINT_MAX) { + typename vector >::iterator it = m_map.begin(); + typename vector >::iterator end = m_map.end(); + for (; it != end; ++it) { + svector & v = *it; + typename svector::iterator it2 = v.begin(); + typename svector::iterator end2 = v.end(); + for (; it2 != end2; ++it2) + it2->m_timestamp = 0; + } + m_timestamp = 1; + } + } +}; + +#endif /* _EXPR_OFFSET_MAP_H_ */ diff --git a/lib/expr_pattern_match.cpp b/lib/expr_pattern_match.cpp new file mode 100644 index 000000000..e1698e043 --- /dev/null +++ b/lib/expr_pattern_match.cpp @@ -0,0 +1,494 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + ast_pattern_match.cpp + +Abstract: + + Search for opportune pattern matching utilities. + +Author: + + Nikolaj Bjorner (nbjorner) 2007-04-10 + Leonardo (leonardo) + +Notes: + + instead of the brute force enumeration of permutations + we can add an instruction 'gate' which copies the ast + into a register and creates another register with the same + term. Matching against a 'gate' is a noop, apart from clearing + the ast in the register. Then on backtracking we know how many + terms were matched from the permutation. It does not make sense + to enumerate all combinations of terms that were not considered, so + skip these. + + Also, compilation should re-order terms to fail fast. + +--*/ + +#include"ast.h" +#include"expr_pattern_match.h" +#include"smtparser.h" +#include"for_each_ast.h" +#include"ast_ll_pp.h" +#include"ast_pp.h" + +expr_pattern_match::expr_pattern_match(ast_manager & manager): + m_manager(manager), m_precompiled(manager) { +} + +expr_pattern_match::~expr_pattern_match() { +} + +bool +expr_pattern_match::match_quantifier(quantifier* qf, app_ref_vector& patterns, unsigned& weight) { + if (m_regs.empty()) { + // HACK: the code crashes if database is empty. + return false; + } + m_regs[0] = qf->get_expr(); + for (unsigned i = 0; i < m_precompiled.size(); ++i) { + quantifier* qf2 = m_precompiled[i].get(); + if (qf2->is_forall() != qf->is_forall()) { + continue; + } + if (qf2->get_num_decls() != qf->get_num_decls()) { + continue; + } + subst s; + if (match(qf->get_expr(), m_first_instrs[i], s)) { + for (unsigned j = 0; j < qf2->get_num_patterns(); ++j) { + app* p = static_cast(qf2->get_pattern(j)); + expr_ref p_result(m_manager); + instantiate(p, qf->get_num_decls(), s, p_result); + patterns.push_back(to_app(p_result.get())); + } + weight = qf2->get_weight(); + return true; + } + } + return false; +} + +void +expr_pattern_match::instantiate(expr* a, unsigned num_bound, subst& s, expr_ref& result) { + bound b; + for (unsigned i = 0; i < num_bound; ++i) { + b.insert(m_bound_dom[i], m_bound_rng[i]); + } + + inst_proc proc(m_manager, s, b, m_regs); + for_each_ast(proc, a); + expr* v = 0; + proc.m_memoize.find(a, v); + SASSERT(v); + result = v; +} + + + +void +expr_pattern_match::compile(expr* q) +{ + SASSERT(q->get_kind() == AST_QUANTIFIER); + quantifier* qf = to_quantifier(q); + unsigned ip = m_instrs.size(); + m_first_instrs.push_back(ip); + m_precompiled.push_back(qf); + + instr instr(BACKTRACK); + unsigned_vector regs; + ptr_vector pats; + unsigned max_reg = 1; + subst s; + pats.push_back(qf->get_expr()); + regs.push_back(0); + unsigned num_bound = 0; + obj_map bound; + + while (!pats.empty()) { + + unsigned reg = regs.back(); + expr* pat = pats.back(); + regs.pop_back(); + pats.pop_back(); + + instr.m_pat = pat; + instr.m_next = m_instrs.size()+1; + instr.m_reg = reg; + instr.m_offset = max_reg; + + switch(pat->get_kind()) { + case AST_VAR: { + var* b = to_var(pat); + if (bound.find(b, instr.m_num_bound)) { + instr.m_kind = CHECK_BOUND; + } + else { + instr.m_kind = SET_BOUND; + instr.m_num_bound = num_bound; + bound.insert(b, num_bound); + ++num_bound; + } + break; + } + case AST_APP: { + unsigned r = 0; + app* app = to_app(pat); + func_decl* d = app->get_decl(); + + for (unsigned i = 0; i < app->get_num_args(); ++i) { + regs.push_back(max_reg); + pats.push_back(app->get_arg(i)); + ++max_reg; + } + + if (is_var(d)) { + if (s.find(d, r)) { + instr.m_kind = CHECK_VAR; + instr.m_other_reg = r; + } + else { + instr.m_kind = SET_VAR; + s.insert(d, reg); + } + } + else { + if (d->is_associative() && d->is_commutative()) { + instr.m_kind = BIND_AC; + } + else if (d->is_commutative()) { + SASSERT(app->get_num_args() == 2); + instr.m_kind = BIND_C; + } + else { + instr.m_kind = BIND; + } + } + break; + } + default: + instr.m_kind = CHECK_TERM; + break; + } + m_instrs.push_back(instr); + } + + if (m_regs.size() <= max_reg) { + m_regs.resize(max_reg+1, 0); + } + if (m_bound_dom.size() <= num_bound) { + m_bound_dom.resize(num_bound+1, 0); + m_bound_rng.resize(num_bound+1, 0); + } + + instr.m_kind = YIELD; + m_instrs.push_back(instr); +} + + +bool +expr_pattern_match::match(expr* a, unsigned init, subst& s) +{ + svector bstack; + instr pc = m_instrs[init]; + + while (true) { + bool ok = false; + switch(pc.m_kind) { + case YIELD: + // substitution s contains registers with matching declarations. + return true; + case CHECK_TERM: + TRACE("expr_pattern_match", display(tout, pc); + ast_pp(tout, m_regs[pc.m_reg], m_manager) << "\n";); + ok = (pc.m_pat == m_regs[pc.m_reg]); + break; + case SET_VAR: + case CHECK_VAR: { + TRACE("expr_pattern_match", display(tout, pc); + ast_pp(tout, m_regs[pc.m_reg], m_manager) << "\n";); + app* app1 = to_app(pc.m_pat); + a = m_regs[pc.m_reg]; + if (a->get_kind() != AST_APP) { + break; + } + app* app2 = to_app(a); + if (app1->get_num_args() != app2->get_num_args()) { + break; + } + if (pc.m_kind == CHECK_VAR && + to_app(m_regs[pc.m_reg])->get_decl() != + to_app(m_regs[pc.m_other_reg])->get_decl()) { + break; + } + for (unsigned i = 0; i < app2->get_num_args(); ++i) { + m_regs[pc.m_offset + i] = app2->get_arg(i); + } + if (pc.m_kind == SET_VAR) { + s.insert(app1->get_decl(), pc.m_reg); + } + ok = true; + break; + } + case SET_BOUND: { + TRACE("expr_pattern_match", display(tout, pc); + ast_pp(tout, m_regs[pc.m_reg], m_manager) << "\n";); + a = m_regs[pc.m_reg]; + if (a->get_kind() != AST_VAR) { + break; + } + ok = true; + var* var_a = to_var(a); + var* var_p = to_var(pc.m_pat); + // check that the mapping of bound variables remains a bijection. + for (unsigned i = 0; ok && i < pc.m_num_bound; ++i) { + ok = (a != m_bound_rng[i]); + } + if (!ok) { + break; + } + m_bound_dom[pc.m_num_bound] = var_p; + m_bound_rng[pc.m_num_bound] = var_a; + break; + } + case CHECK_BOUND: + TRACE("expr_pattern_match", + tout + << "check bound " + << pc.m_num_bound << " " << pc.m_reg; + ); + ok = m_bound_rng[pc.m_num_bound] == m_regs[pc.m_reg]; + break; + case BIND: + case BIND_AC: + case BIND_C: { + TRACE("expr_pattern_match", display(tout, pc); + tout << mk_pp(m_regs[pc.m_reg],m_manager) << "\n";); + app* app1 = to_app(pc.m_pat); + a = m_regs[pc.m_reg]; + if (a->get_kind() != AST_APP) { + break; + } + app* app2 = to_app(a); + if (app1->get_num_args() != app2->get_num_args()) { + break; + } + if (!match_decl(app1->get_decl(), app2->get_decl())) { + break; + } + switch(pc.m_kind) { + case BIND: + for (unsigned i = 0; i < app2->get_num_args(); ++i) { + m_regs[pc.m_offset + i] = app2->get_arg(i); + } + ok = true; + break; // process the next instruction. + case BIND_AC: + // push CHOOSE_AC on the backtracking stack. + bstack.push_back(instr(CHOOSE_AC, pc.m_offset, pc.m_next, app2, 1)); + break; + case BIND_C: + // push CHOOSE_C on the backtracking stack. + ok = true; + m_regs[pc.m_offset] = app2->get_arg(0); + m_regs[pc.m_offset+1] = app2->get_arg(1); + bstack.push_back(instr(CHOOSE_C, pc.m_offset, pc.m_next, app2, 2)); + break; + default: + break; + } + break; + } + case CHOOSE_C: + ok = true; + SASSERT (pc.m_count == 2); + m_regs[pc.m_offset+1] = pc.m_app->get_arg(0); + m_regs[pc.m_offset] = pc.m_app->get_arg(1); + break; + case CHOOSE_AC: { + ok = true; + app* app2 = pc.m_app; + for (unsigned i = 0; i < app2->get_num_args(); ++i) { + m_regs[pc.m_offset + i] = app2->get_arg(i); + } + // generate the k'th permutation. + unsigned k = pc.m_count; + unsigned fac = 1; + unsigned num_args = pc.m_app->get_num_args(); + for (unsigned j = 2; j <= num_args; ++j) { + fac *= (j-1); + SASSERT(((k /fac) % j) + 1 <= j); + std::swap(m_regs[pc.m_offset + j - 1], m_regs[pc.m_offset + j - ((k / fac) % j) - 1]); + } + if (k < fac*num_args) { + bstack.push_back(instr(CHOOSE_AC, pc.m_offset, pc.m_next, app2, k+1)); + } + TRACE("expr_pattern_match", + { + tout << "fac: " << fac << " num_args:" << num_args << " k:" << k << "\n"; + for (unsigned i = 0; i < num_args; ++i) { + ast_pp(tout, m_regs[pc.m_offset + i], m_manager); + tout << " "; + } + tout << "\n"; + }); + break; + } + case BACKTRACK: + if (bstack.empty()) { + return false; + } + pc = bstack.back(); + bstack.pop_back(); + continue; // with the loop. + } + + if (ok) { + pc = m_instrs[pc.m_next]; + } + else { + TRACE("expr_pattern_match", tout << "backtrack\n";); + pc = m_instrs[0]; + } + } +} + + +bool +expr_pattern_match::match_decl(func_decl const * pat, func_decl const * d) const { + if (pat == d) { + return true; + } + if (pat->get_arity() != d->get_arity()) { + return false; + } + // match families + if (pat->get_family_id() == null_family_id) { + return false; + } + if (d->get_family_id() != pat->get_family_id()) { + return false; + } + if (d->get_decl_kind() != pat->get_decl_kind()) { + return false; + } + if (d->get_num_parameters() != pat->get_num_parameters()) { + return false; + } + for (unsigned i = 0; i < d->get_num_parameters(); ++i) { + if (!(d->get_parameter(i) == pat->get_parameter(i))) { + return false; + } + } + return true; +} + +bool +expr_pattern_match::is_var(func_decl* d) { + const char* s = d->get_name().bare_str(); + return s && *s == '?'; +} + +void +expr_pattern_match::initialize(char const * spec_string) { + if (!m_instrs.empty()) { + return; + } + + m_instrs.push_back(instr(BACKTRACK)); + + smtlib::parser* parser = smtlib::parser::create(m_manager); + parser->initialize_smtlib(); + if (!parser->parse_string(spec_string)) { + UNREACHABLE(); + } + smtlib::benchmark* bench = parser->get_benchmark(); + smtlib::theory::expr_iterator it = bench->begin_formulas(); + smtlib::theory::expr_iterator end = bench->end_formulas(); + for (; it != end; ++it) { + compile(*it); + } + dealloc(parser); + TRACE("expr_pattern_match", display(tout); ); +} + +void +expr_pattern_match::display(std::ostream& out) const { + for (unsigned i = 0; i < m_instrs.size(); ++i) { + display(out, m_instrs[i]); + } +} + +void +expr_pattern_match::display(std::ostream& out, instr const& pc) const { + switch(pc.m_kind) { + case BACKTRACK: + out << "backtrack\n"; + break; + case BIND: + out << "bind "; + ast_pp(out, to_app(pc.m_pat)->get_decl(), m_manager) << " "; + ast_pp(out, pc.m_pat, m_manager) << "\n"; + out << "next: " << pc.m_next << "\n"; + out << "offset: " << pc.m_offset << "\n"; + out << "reg: " << pc.m_reg << "\n"; + break; + case BIND_AC: + out << "bind_ac "; + ast_pp(out, to_app(pc.m_pat)->get_decl(), m_manager) << " "; + ast_pp(out, pc.m_pat, m_manager) << "\n"; + out << "next: " << pc.m_next << "\n"; + out << "offset: " << pc.m_offset << "\n"; + out << "reg: " << pc.m_reg << "\n"; + break; + case BIND_C: + out << "bind_c "; + ast_pp(out, to_app(pc.m_pat)->get_decl(), m_manager) << " "; + ast_pp(out, pc.m_pat, m_manager) << "\n"; + out << "next: " << pc.m_next << "\n"; + out << "offset: " << pc.m_offset << "\n"; + out << "reg: " << pc.m_reg << "\n"; + break; + case CHOOSE_AC: + out << "choose_ac\n"; + out << "next: " << pc.m_next << "\n"; + out << "count: " << pc.m_count << "\n"; + break; + case CHOOSE_C: + out << "choose_c\n"; + out << "next: " << pc.m_next << "\n"; + //out << "reg: " << pc.m_reg << "\n"; + break; + case CHECK_VAR: + out << "check_var "; + ast_pp(out, pc.m_pat, m_manager) << "\n"; + out << "next: " << pc.m_next << "\n"; + out << "reg: " << pc.m_reg << "\n"; + out << "other_reg: " << pc.m_other_reg << "\n"; + break; + case CHECK_TERM: + out << "check "; + ast_pp(out, pc.m_pat, m_manager) << "\n"; + out << "next: " << pc.m_next << "\n"; + out << "reg: " << pc.m_reg << "\n"; + break; + case YIELD: + out << "yield\n"; + break; + case SET_VAR: + out << "set_var "; + ast_pp(out, pc.m_pat, m_manager) << "\n"; + out << "next: " << pc.m_next << "\n"; + break; + default: + break; + } } + + +// TBD: fix type overloading. +// TBD: bound number of permutations. +// TBD: forward pruning checks. diff --git a/lib/expr_pattern_match.h b/lib/expr_pattern_match.h new file mode 100644 index 000000000..eb3c05a94 --- /dev/null +++ b/lib/expr_pattern_match.h @@ -0,0 +1,148 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + expr_pattern_match.h + +Abstract: + + Search for opportune pattern matching utilities. + +Author: + + Nikolaj Bjorner (nbjorner) 2007-04-10 + Leonardo (leonardo) + +Notes: + +--*/ +#ifndef _EXPR_PATTERN_MATCH_H_ +#define _EXPR_PATTERN_MATCH_H_ + +#include"ast.h" +#include"map.h" +#include"front_end_params.h" + +class expr_pattern_match { + + enum instr_kind { + BACKTRACK, + BIND, + BIND_AC, + BIND_C, + CHOOSE_AC, + CHOOSE_C, + SET_VAR, + CHECK_VAR, + CHECK_TERM, + SET_BOUND, + CHECK_BOUND, + YIELD, + }; + + struct instr { + instr(instr_kind k) : m_kind(k) {} + instr(instr_kind k, unsigned o, unsigned next, app* app, unsigned count): + m_kind(k), m_offset(o), m_next(next), m_app(app), m_count(count) {} + + instr_kind m_kind; + unsigned m_offset; + unsigned m_next; + app* m_app; + expr* m_pat; + unsigned m_reg; + unsigned m_other_reg; + unsigned m_count; + unsigned m_num_bound; + }; + + typedef obj_map subst; + typedef obj_map bound; + + struct inst_proc { + ast_manager& m_manager; + expr_ref_vector m_pinned; + subst& m_subst; + bound& m_bound; + obj_map m_memoize; + ptr_vector& m_regs; + + + inst_proc(ast_manager& m, subst& s, bound& b, ptr_vector& regs) : + m_manager(m), m_pinned(m), m_subst(s), m_bound(b), m_regs(regs) {} + + + void operator()(ast* a) { + } + + void operator()(expr* a) { + m_memoize.insert(a, a); + } + + void operator()(var* v) { + var* b = 0; + if (m_bound.find(v, b)) { + m_memoize.insert(v, b); + } + else { + UNREACHABLE(); + } + } + + void operator()(app * n) { + unsigned r; + ptr_vector args; + unsigned num_args = n->get_num_args(); + func_decl * decl = n->get_decl(); + expr* result; + if (m_subst.find(decl, r)) { + decl = to_app(m_regs[r])->get_decl(); + } + for (unsigned i = 0; i < num_args; ++i) { + expr* arg = 0; + if (m_memoize.find(n->get_arg(i), arg)) { + SASSERT(arg); + args.push_back(arg); + } + else { + UNREACHABLE(); + } + } + if (m_manager.is_pattern(n)) { + result = m_manager.mk_pattern(num_args, reinterpret_cast(args.c_ptr())); + } + else { + result = m_manager.mk_app(decl, num_args, args.c_ptr()); + } + m_pinned.push_back(result); + m_memoize.insert(n, result); + return; + } + }; + + ast_manager & m_manager; + quantifier_ref_vector m_precompiled; + unsigned_vector m_first_instrs; + svector m_instrs; + ptr_vector m_regs; + ptr_vector m_bound_dom; + ptr_vector m_bound_rng; + + public: + expr_pattern_match(ast_manager & manager); + ~expr_pattern_match(); + bool match_quantifier(quantifier* qf, app_ref_vector& patterns, unsigned& weight); + void initialize(char const * database); + void display(std::ostream& out) const; + + private: + void instantiate(expr* a, unsigned num_bound, subst& s, expr_ref& result); + void compile(expr* q); + bool match(expr* a, unsigned init, subst& s); + bool match_decl(func_decl const * pat, func_decl const * d) const; + bool is_var(func_decl* d); + void display(std::ostream& out, instr const& pc) const; +}; + +#endif diff --git a/lib/expr_rand.cpp b/lib/expr_rand.cpp new file mode 100644 index 000000000..e1a7631c4 --- /dev/null +++ b/lib/expr_rand.cpp @@ -0,0 +1,268 @@ +#include "expr_rand.h" +#include "bv_decl_plugin.h" +#include "array_decl_plugin.h" +#include "arith_decl_plugin.h" +#include "ast_pp.h" + + +expr_rand::expr_rand(ast_manager& m): + m_manager(m), + m_num_vars(0), + m_num_apps(0), + m_num_nodes(0), + m_max_steps(10), + m_funcs(m) +{} + +expr_rand::~expr_rand() { + map_t::iterator it = m_nodes.begin(); + map_t::iterator end = m_nodes.end(); + for (; it != end; ++it) { + dealloc(it->m_value); + } +} + +void expr_rand::add_var(sort* s) { + add_expr(m_manager.mk_fresh_const("x", s)); +} + +void expr_rand::add_func_decl(func_decl* f) { + m_funcs.push_back(f); +} + +void expr_rand::add_expr(expr* t) { + sort* s = m_manager.get_sort(t); + expr_ref_vector* vals = 0; + if (!m_nodes.find(s, vals)) { + vals = alloc(expr_ref_vector, m_manager); + m_nodes.insert(s, vals); + } + vals->push_back(t); +} + +void expr_rand::get_next(sort* s, expr_ref& e) { + walk(m_max_steps); + e = choose_expr(s); +} + +void expr_rand::walk() { + func_decl* f = choose_func_decl(); + unsigned arity = f->get_arity(); + expr_ref_vector args(m_manager); + for (unsigned i = 0; i < arity; ++i) { + args.push_back(choose_expr(f->get_domain(i))); + } + expr* r = m_manager.mk_app(f, args.size(), args.c_ptr()); + add_expr(r); +} + +void expr_rand::walk(unsigned n) { + for (unsigned i = 0; i < n; ++i) { + walk(); + } +} + +func_decl* expr_rand::choose_func_decl() { + unsigned idx = m_random(m_funcs.size()); + return m_funcs[idx].get(); +} + +expr* expr_rand::choose_expr(sort* s) { + expr_ref_vector* vals = 0; + if (!m_nodes.find(s, vals)) { + add_var(s); + if (!m_nodes.find(s, vals)) { + UNREACHABLE(); + } + SASSERT(vals); + } + unsigned idx = m_random(vals->size()); + return (*vals)[idx].get(); +} + +void expr_rand::initialize_arith(unsigned num_vars) { + arith_util u(m_manager); + family_id afid = m_manager.get_family_id("arith"); + sort* i_ty = m_manager.mk_sort(afid, INT_SORT, 0, 0); + for(unsigned i = 0; i < num_vars; ++i) { + add_var(i_ty); + } + sort* is[2] = { i_ty, i_ty }; + decl_kind kinds[7] = {OP_ADD, OP_MUL, OP_SUB, OP_LE, OP_LT, OP_GE, OP_GT }; + for (unsigned i = 0; i < 7; ++i) { + add_func_decl(m_manager.mk_func_decl(afid, kinds[i], 0, 0, 2, is)); + } + + add_expr(u.mk_numeral(rational(0), true)); + add_expr(u.mk_numeral(rational(1), true)); + add_expr(u.mk_numeral(rational(2), true)); + add_expr(u.mk_numeral(rational(3), true)); + add_expr(u.mk_numeral(rational(6), true)); + add_expr(u.mk_numeral(rational(7), true)); + add_expr(u.mk_numeral(rational(-1), true)); + add_expr(u.mk_numeral(rational(-2), true)); +} + +void expr_rand::initialize_bv(unsigned num_vars) { + bv_util u(m_manager); + family_id bfid = m_manager.get_basic_family_id(); + family_id bvfid = m_manager.get_family_id("bv"); + + + const unsigned num_sizes = 6; + unsigned sizes[num_sizes] = { 1, 2, 8, 16, 24, 32 }; + parameter p1(1), p2(2), p3(3), p4(4), p8(8), p16(16), p24(24), p32(32); + + for (unsigned i = 0; i < num_sizes; ++i) { + add_expr(u.mk_numeral(rational(0), sizes[i])); + add_expr(u.mk_numeral(rational(1), sizes[i])); + } + add_expr(u.mk_numeral(rational(2), 2)); + add_expr(u.mk_numeral(rational(3), 2)); + add_expr(u.mk_numeral(rational(6), 8)); + add_expr(u.mk_numeral(rational(7), 8)); + add_expr(u.mk_numeral(rational(static_cast(-2)), 32)); + add_expr(u.mk_numeral(rational(static_cast(-1)), 32)); + + for (unsigned i = 0; num_vars > 0; ++i, --num_vars) { + i = i % num_sizes; + parameter param(sizes[i]); + add_var(m_manager.mk_sort(bvfid, BV_SORT, 1, ¶m)); + } + for (unsigned i = 0; i < num_sizes; ++i) { + parameter param(sizes[i]); + sort* s = m_manager.mk_sort(bvfid, BV_SORT, 1, ¶m); + + sort* ss[3] = { s, s, s }; + add_func_decl(m_manager.mk_func_decl(bvfid, OP_BNEG, 0, 0, 1, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_BADD, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_BSUB, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_BMUL, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_BSDIV, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_BUDIV, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_BSREM, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_BUREM, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_BSMOD, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_ULEQ, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_SLEQ, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_UGEQ, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_SGEQ, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_ULT, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_SLT, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_UGT, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_SGT, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_BAND, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_BOR, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_BNOT, 0, 0, 1, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_BXOR, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_BXNOR, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_BNAND, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_BCOMP, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_BREDAND, 0, 0, 1, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_BREDOR, 0, 0, 1, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_BSHL, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_BLSHR, 0, 0, 2, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_BASHR, 0, 0, 2, ss)); + + add_func_decl(m_manager.mk_func_decl(bfid, OP_EQ, 0, 0, 2, ss)); + + add_func_decl(m_manager.mk_func_decl(bvfid, OP_ROTATE_LEFT, 1, &p1, 1, ss)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_ROTATE_RIGHT, 1, &p1, 1, ss)); + + } + + sort* b8 = m_manager.mk_sort(bvfid, BV_SORT, 1, &p8); + sort* b16 = m_manager.mk_sort(bvfid, BV_SORT, 1, &p16); + sort* b24 = m_manager.mk_sort(bvfid, BV_SORT, 1, &p24); + sort* b32 = m_manager.mk_sort(bvfid, BV_SORT, 1, &p32); + + // OP_CONCAT: + { + sort* ss[2] = { b8, b8 }; + add_func_decl(m_manager.mk_func_decl(bvfid, OP_CONCAT, 0, 0, 2, ss)); + ss[0] = b16; + ss[1] = b8; + add_func_decl(m_manager.mk_func_decl(bvfid, OP_CONCAT, 0, 0, 2, ss)); + ss[0] = b8; + ss[1] = b16; + add_func_decl(m_manager.mk_func_decl(bvfid, OP_CONCAT, 0, 0, 2, ss)); + ss[0] = b16; + ss[1] = b16; + add_func_decl(m_manager.mk_func_decl(bvfid, OP_CONCAT, 0, 0, 2, ss)); + ss[0] = b24; + ss[1] = b8; + add_func_decl(m_manager.mk_func_decl(bvfid, OP_CONCAT, 0, 0, 2, ss)); + ss[0] = b8; + ss[1] = b24; + add_func_decl(m_manager.mk_func_decl(bvfid, OP_CONCAT, 0, 0, 2, ss)); + } + + + add_func_decl(m_manager.mk_func_decl(bvfid, OP_SIGN_EXT, 1, &p8, 1, &b8)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_SIGN_EXT, 1, &p8, 1, &b16)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_SIGN_EXT, 1, &p16, 1, &b8)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_SIGN_EXT, 1, &p16, 1, &b16)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_SIGN_EXT, 1, &p8, 1, &b16)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_SIGN_EXT, 1, &p16, 1, &b8)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_SIGN_EXT, 1, &p8, 1, &b24)); + + add_func_decl(m_manager.mk_func_decl(bvfid, OP_ZERO_EXT, 1, &p8, 1, &b8)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_ZERO_EXT, 1, &p8, 1, &b16)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_ZERO_EXT, 1, &p16, 1, &b8)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_ZERO_EXT, 1, &p16, 1, &b16)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_ZERO_EXT, 1, &p8, 1, &b16)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_ZERO_EXT, 1, &p16, 1, &b8)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_ZERO_EXT, 1, &p8, 1, &b24)); + + parameter bounds[2] = { parameter(7), parameter(0) }; + add_func_decl(m_manager.mk_func_decl(bvfid, OP_EXTRACT, 2, bounds, 1, &b32)); + bounds[0] = parameter(15); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_EXTRACT, 2, bounds, 1, &b32)); + bounds[0] = parameter(23); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_EXTRACT, 2, bounds, 1, &b32)); + bounds[1] = parameter(8); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_EXTRACT, 2, bounds, 1, &b32)); + bounds[1] = parameter(16); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_EXTRACT, 2, bounds, 1, &b32)); + + add_func_decl(m_manager.mk_func_decl(bvfid, OP_REPEAT, 1, &p4, 1, &b8)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_REPEAT, 1, &p3, 1, &b8)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_REPEAT, 1, &p2, 1, &b8)); + add_func_decl(m_manager.mk_func_decl(bvfid, OP_REPEAT, 1, &p1, 1, &b8)); + + /* + OP_ROTATE_LEFT, + OP_ROTATE_RIGHT, + */ +} + +void expr_rand::initialize_array(unsigned num_vars, sort* dom, sort* rng) { + family_id afid = m_manager.get_family_id("array"); + parameter p1(dom), p2(rng); + parameter ps[2] = { p1, p2 }; + sort* a = m_manager.mk_sort(afid, ARRAY_SORT, 2, ps); + sort* ss[3] = { a, dom, rng }; + + add_func_decl(m_manager.mk_func_decl(afid, OP_STORE, 0, 0, 3, ss)); + add_func_decl(m_manager.mk_func_decl(afid, OP_SELECT, 0, 0, 2, ss)); + + for (unsigned i = 0; i < num_vars; ++i) { + add_var(a); + } +} + +void expr_rand::initialize_basic(unsigned amplification) { + family_id bfid = m_manager.get_basic_family_id(); + sort* bools[2] = { m_manager.mk_bool_sort(), m_manager.mk_bool_sort() }; + for (unsigned i = 0; i < amplification; ++i) { + add_func_decl(m_manager.mk_func_decl(bfid, OP_OR, 0, 0, 2, bools)); + add_func_decl(m_manager.mk_func_decl(bfid, OP_NOT, 0, 0, 1, bools)); + } + map_t::iterator it = m_nodes.begin(); + map_t::iterator end = m_nodes.end(); + for (; it != end; ++it) { + sort* s = it->m_key; + sort* ites[3] = { bools[0], s, s }; + add_func_decl(m_manager.mk_func_decl(bfid, OP_ITE, 0, 0, 3, ites)); + } +} diff --git a/lib/expr_rand.h b/lib/expr_rand.h new file mode 100644 index 000000000..f85cd92c0 --- /dev/null +++ b/lib/expr_rand.h @@ -0,0 +1,61 @@ +/*++ +Copyright (c) 2008 Microsoft Corporation + +Module Name: + + expr_rand.h + +Abstract: + + Generator of random ASTs. + +Author: + + Nikolaj Bjorner 2008-04-10. + +Revision History: + +--*/ +#ifndef _EXPR_RAND_H_ +#define _EXPR_RAND_H_ + +#include"ast.h" +#include"obj_hashtable.h" + +class expr_rand { + ast_manager& m_manager; + unsigned m_num_vars; + unsigned m_num_apps; + unsigned m_num_nodes; + unsigned m_max_steps; + random_gen m_random; + + typedef obj_map map_t; + + func_decl_ref_vector m_funcs; + map_t m_nodes; + +public: + expr_rand(ast_manager& m); + ~expr_rand(); + void add_var(sort*); + void add_func_decl(func_decl*); + void add_expr(expr* t); + void get_next(sort* s, expr_ref& e); + void initialize_bv(unsigned num_vars); + void initialize_arith(unsigned num_vars); + void initialize_array(unsigned num_vars, sort* dom, sort* rng); + void initialize_basic(unsigned amplification); + void seed(unsigned n) { m_random = random_gen(n); } + +private: + void walk(); + void walk(unsigned n); + + func_decl* choose_func_decl(); + expr* choose_expr(sort*); + +}; + + +#endif diff --git a/lib/expr_replacer.cpp b/lib/expr_replacer.cpp new file mode 100644 index 000000000..829e82092 --- /dev/null +++ b/lib/expr_replacer.cpp @@ -0,0 +1,156 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + expr_replacer.cpp + +Abstract: + + Abstract (functor) for replacing constants with expressions. + +Author: + + Leonardo (leonardo) 2011-04-29 + +Notes: + +--*/ +#include"expr_replacer.h" +#include"rewriter_def.h" +#include"th_rewriter.h" +#include"cooperate.h" + +void expr_replacer::operator()(expr * t, expr_ref & result, proof_ref & result_pr) { + expr_dependency_ref result_dep(m()); + operator()(t, result, result_pr, result_dep); +} + +void expr_replacer::operator()(expr * t, expr_ref & result) { + proof_ref pr(m()); + operator()(t, result, pr); +} + +struct expr_replacer::scoped_set_subst { + expr_replacer & m_r; + scoped_set_subst(expr_replacer & r, expr_substitution & s):m_r(r) { m_r.set_substitution(&s); } + ~scoped_set_subst() { m_r.set_substitution(0); } +}; + +void expr_replacer::apply_substitution(expr * s, expr * def, proof * def_pr, expr_ref & t) { + expr_substitution sub(m()); + sub.insert(s, def, def_pr); + scoped_set_subst set(*this, sub); + (*this)(t); +} + +void expr_replacer::apply_substitution(expr * s, expr * def, expr_ref & t) { + expr_substitution sub(m()); + sub.insert(s, def); + scoped_set_subst set(*this, sub); + (*this)(t); +} + +struct default_expr_replacer_cfg : public default_rewriter_cfg { + ast_manager & m; + expr_substitution * m_subst; + expr_dependency_ref m_used_dependencies; + + default_expr_replacer_cfg(ast_manager & _m): + m(_m), + m_subst(0), + m_used_dependencies(_m) { + } + + bool get_subst(expr * s, expr * & t, proof * & pr) { + if (m_subst == 0) + return false; + expr_dependency * d = 0; + if (m_subst->find(s, t, pr, d)) { + m_used_dependencies = m.mk_join(m_used_dependencies, d); + return true; + } + return false; + } + + bool max_steps_exceeded(unsigned num_steps) const { + cooperate("simplifier"); + return false; + } +}; + +template class rewriter_tpl; + +class default_expr_replacer : public expr_replacer { + default_expr_replacer_cfg m_cfg; + rewriter_tpl m_replacer; +public: + default_expr_replacer(ast_manager & m): + m_cfg(m), + m_replacer(m, m.proofs_enabled(), m_cfg) { + } + + virtual ast_manager & m() const { return m_replacer.m(); } + + virtual void set_substitution(expr_substitution * s) { + m_replacer.cleanup(); + m_replacer.cfg().m_subst = s; + } + + virtual void operator()(expr * t, expr_ref & result, proof_ref & result_pr, expr_dependency_ref & result_dep) { + result_dep = 0; + m_replacer.operator()(t, result, result_pr); + if (m_cfg.m_used_dependencies != 0) { + result_dep = m_cfg.m_used_dependencies; + m_replacer.reset(); // reset cache + m_cfg.m_used_dependencies = 0; + } + } + + virtual void set_cancel(bool f) { + m_replacer.set_cancel(f); + } + + virtual unsigned get_num_steps() const { + return m_replacer.get_num_steps(); + } +}; + +expr_replacer * mk_default_expr_replacer(ast_manager & m) { + return alloc(default_expr_replacer, m); +} + +/** + \brief Adapter for using th_rewriter as an expr_replacer. + */ +class th_rewriter2expr_replacer : public expr_replacer { + th_rewriter m_r; +public: + th_rewriter2expr_replacer(ast_manager & m, params_ref const & p): + m_r(m, p) { + } + + virtual ~th_rewriter2expr_replacer() {} + + virtual ast_manager & m() const { return m_r.m(); } + + virtual void set_substitution(expr_substitution * s) { m_r.set_substitution(s); } + + virtual void operator()(expr * t, expr_ref & result, proof_ref & result_pr, expr_dependency_ref & result_dep) { + m_r(t, result, result_pr); + result_dep = m_r.get_used_dependencies(); + m_r.reset_used_dependencies(); + } + + virtual void set_cancel(bool f) { + m_r.set_cancel(f); + } + + virtual unsigned get_num_steps() const { + return m_r.get_num_steps(); + } +}; + +expr_replacer * mk_expr_simp_replacer(ast_manager & m, params_ref const & p) { + return alloc(th_rewriter2expr_replacer, m, p); +} diff --git a/lib/expr_replacer.h b/lib/expr_replacer.h new file mode 100644 index 000000000..722c0ff12 --- /dev/null +++ b/lib/expr_replacer.h @@ -0,0 +1,62 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + expr_replacer.h + +Abstract: + + Abstract (functor) for replacing expressions. + +Author: + + Leonardo (leonardo) 2011-04-29 + +Notes: + +--*/ +#ifndef _EXPR_REPLACER_H_ +#define _EXPR_REPLACER_H_ + +#include"ast.h" +#include"expr_substitution.h" +#include"params.h" + +/** + \brief Abstract interface for functors that replace constants with expressions. +*/ +class expr_replacer { + struct scoped_set_subst; +public: + virtual ~expr_replacer() {} + + virtual ast_manager & m() const = 0; + virtual void set_substitution(expr_substitution * s) = 0; + + virtual void operator()(expr * t, expr_ref & result, proof_ref & result_pr, expr_dependency_ref & deps) = 0; + virtual void operator()(expr * t, expr_ref & result, proof_ref & result_pr); + virtual void operator()(expr * t, expr_ref & result); + virtual void operator()(expr_ref & t) { expr_ref s(t, m()); (*this)(s, t); } + + void cancel() { set_cancel(true); } + void reset_cancel() { set_cancel(false); } + virtual void set_cancel(bool f) = 0; + virtual unsigned get_num_steps() const { return 0; } + + + void apply_substitution(expr * s, expr * def, proof * def_pr, expr_ref & t); + void apply_substitution(expr * s, expr * def, expr_ref & t); +}; + +/** + \brief Create a vanilla replacer. It just applies the substitution. +*/ +expr_replacer * mk_default_expr_replacer(ast_manager & m); + +/** + \brief Apply substitution and simplify. +*/ +expr_replacer * mk_expr_simp_replacer(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/expr_stat.cpp b/lib/expr_stat.cpp new file mode 100644 index 000000000..8ac259afa --- /dev/null +++ b/lib/expr_stat.cpp @@ -0,0 +1,88 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + expr_stat.cpp + +Abstract: + + Expression statistics (symbol count, var count, depth, ...) + + All functions in these module assume expressions do not contain + nested quantifiers. + +Author: + + Leonardo de Moura (leonardo) 2008-02-05. + +Revision History: + +--*/ +#include"for_each_expr.h" +#include"expr_stat.h" + +void get_expr_stat(expr * n, expr_stat & r) { + typedef std::pair pair; + buffer todo; + todo.push_back(pair(n, 0)); + while (!todo.empty()) { + pair & p = todo.back(); + n = p.first; + unsigned depth = p.second; + unsigned j; + todo.pop_back(); + r.m_sym_count++; + if (depth > r.m_depth) + r.m_depth = depth; + switch (n->get_kind()) { + case AST_APP: + j = to_app(n)->get_num_args(); + if (j == 0) + r.m_const_count++; + while (j > 0) { + --j; + todo.push_back(pair(to_app(n)->get_arg(j), depth + 1)); + } + break; + case AST_VAR: + if (to_var(n)->get_idx() > r.m_max_var_idx) + r.m_max_var_idx = to_var(n)->get_idx(); + r.m_ground = false; + break; + case AST_QUANTIFIER: + todo.push_back(pair(to_quantifier(n)->get_expr(), depth+1)); + break; + default: + UNREACHABLE(); + } + } +} + +unsigned get_symbol_count(expr * n) { + unsigned r = 0; + ptr_buffer todo; + todo.push_back(n); + while (!todo.empty()) { + n = todo.back(); + unsigned j; + todo.pop_back(); + r++; + switch (n->get_kind()) { + case AST_APP: + j = to_app(n)->get_num_args(); + while (j > 0) { + --j; + todo.push_back(to_app(n)->get_arg(j)); + } + break; + case AST_QUANTIFIER: + todo.push_back(to_quantifier(n)->get_expr()); + break; + default: + break; + } + } + return r; +} + diff --git a/lib/expr_stat.h b/lib/expr_stat.h new file mode 100644 index 000000000..7e94e5212 --- /dev/null +++ b/lib/expr_stat.h @@ -0,0 +1,50 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + expr_stat.h + +Abstract: + + Expression statistics (symbol count, var count, depth, ...) + + All functions in these module assume expressions do not contain + nested quantifiers. + +Author: + + Leonardo de Moura (leonardo) 2008-02-05. + +Revision History: + +--*/ +#ifndef _EXPR_STAT_H_ +#define _EXPR_STAT_H_ + +class expr; + +struct expr_stat { + unsigned m_sym_count; // symbol count + unsigned m_depth; // depth + unsigned m_const_count; // constant count + unsigned m_max_var_idx; + bool m_ground; + expr_stat():m_sym_count(0), m_depth(0), m_const_count(0), m_max_var_idx(0), m_ground(true) {} +}; + +/** + \brief Collect statistics regarding the given expression. + + \warning This function traverses the dag as a tree. +*/ +void get_expr_stat(expr * n, expr_stat & r); + +/** + \brief Return the number of symbols in \c n. + + \warning This function traverses the dag as a tree. +*/ +unsigned get_symbol_count(expr * n); + +#endif /* _EXPR_STAT_H_ */ diff --git a/lib/expr_substitution.cpp b/lib/expr_substitution.cpp new file mode 100644 index 000000000..00d741fda --- /dev/null +++ b/lib/expr_substitution.cpp @@ -0,0 +1,157 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + expr_substitution.cpp + +Abstract: + + expr -> expr substitution + +Author: + + Leonardo (leonardo) 2011-04-29 + +Notes: + +--*/ +#include"expr_substitution.h" +#include"ref_util.h" + +typedef obj_map expr2proof; +typedef obj_map expr2expr_dependency; + +void expr_substitution::init() { + + if (proofs_enabled()) + m_subst_pr = alloc(expr2proof); + if (unsat_core_enabled()) + m_subst_dep = alloc(expr2expr_dependency); +} + +expr_substitution::expr_substitution(ast_manager & m): + m_manager(m), + m_cores_enabled(false), + m_proofs_enabled(m.proofs_enabled()) { + init(); +} + +expr_substitution::expr_substitution(ast_manager & m, bool core_enabled): + m_manager(m), + m_cores_enabled(core_enabled), + m_proofs_enabled(m.proofs_enabled()) { + init(); +} + +expr_substitution::expr_substitution(ast_manager & m, bool core_enabled, bool proofs_enabled): + m_manager(m), + m_cores_enabled(core_enabled), + m_proofs_enabled(proofs_enabled) { + SASSERT(!proofs_enabled || m.proofs_enabled()); + init(); +} + +expr_substitution::~expr_substitution() { + reset(); +} + +void expr_substitution::insert(expr * c, expr * def, proof * def_pr, expr_dependency * def_dep) { + obj_map::obj_map_entry * entry = m_subst.insert_if_not_there2(c, 0); + if (entry->get_data().m_value == 0) { + // new entry + m_manager.inc_ref(c); + m_manager.inc_ref(def); + entry->get_data().m_value = def; + if (proofs_enabled()) { + SASSERT(!m_subst_pr->contains(c)); + m_subst_pr->insert(c, def_pr); + m_manager.inc_ref(def_pr); + } + if (unsat_core_enabled()) { + SASSERT(!m_subst_dep->contains(c)); + m_subst_dep->insert(c, def_dep); + m_manager.inc_ref(def_dep); + } + } + else { + // replacing entry + m_manager.inc_ref(def); + m_manager.dec_ref(entry->get_data().m_value); + entry->get_data().m_value = def; + if (proofs_enabled()) { + obj_map::obj_map_entry * entry_pr = m_subst_pr->find_core(c); + SASSERT(entry_pr != 0); + m_manager.inc_ref(def_pr); + m_manager.dec_ref(entry_pr->get_data().m_value); + entry_pr->get_data().m_value = def_pr; + } + if (unsat_core_enabled()) { + obj_map::obj_map_entry * entry_dep = m_subst_dep->find_core(c); + SASSERT(entry_dep != 0); + m_manager.inc_ref(def_dep); + m_manager.dec_ref(entry_dep->get_data().m_value); + entry_dep->get_data().m_value = def_dep; + } + } +} + +void expr_substitution::erase(expr * c) { + if (proofs_enabled()) { + proof * pr = 0; + if (m_subst_pr->find(c, pr)) { + m_manager.dec_ref(pr); + m_subst_pr->erase(c); + } + } + if (unsat_core_enabled()) { + expr_dependency * dep = 0; + if (m_subst_dep->find(c, dep)) { + m_manager.dec_ref(dep); + m_subst_dep->erase(c); + } + } + expr * def = 0; + if (m_subst.find(c, def)) { + m_manager.dec_ref(c); + m_manager.dec_ref(def); + m_subst.erase(c); + } +} + +bool expr_substitution::find(expr * c, expr * & def, proof * & def_pr) { + if (m_subst.find(c, def)) { + if (proofs_enabled()) + m_subst_pr->find(c, def_pr); + return true; + } + return false; +} + +bool expr_substitution::find(expr * c, expr * & def, proof * & def_pr, expr_dependency * & def_dep) { + if (m_subst.find(c, def)) { + if (proofs_enabled()) + m_subst_pr->find(c, def_pr); + if (unsat_core_enabled()) + m_subst_dep->find(c, def_dep); + return true; + } + return false; +} + +void expr_substitution::reset() { + dec_ref_map_key_values(m_manager, m_subst); + if (proofs_enabled()) + dec_ref_map_values(m_manager, *m_subst_pr); + if (unsat_core_enabled()) + dec_ref_map_values(m_manager, *m_subst_dep); +} + +void expr_substitution::cleanup() { + reset(); + m_subst.finalize(); + if (proofs_enabled()) + m_subst_pr->finalize(); + if (unsat_core_enabled()) + m_subst_dep->finalize(); +} diff --git a/lib/expr_substitution.h b/lib/expr_substitution.h new file mode 100644 index 000000000..204a28cc1 --- /dev/null +++ b/lib/expr_substitution.h @@ -0,0 +1,54 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + expr_substitution.h + +Abstract: + + expr -> expr substitution + +Author: + + Leonardo (leonardo) 2011-04-29 + +Notes: + +--*/ +#ifndef _EXPR_SUBSTITUTION_H_ +#define _EXPR_SUBSTITUTION_H_ + +#include"ast.h" + +class expr_substitution { + ast_manager & m_manager; + obj_map m_subst; + scoped_ptr > m_subst_pr; + scoped_ptr > m_subst_dep; + unsigned m_cores_enabled:1; + unsigned m_proofs_enabled:1; + + void init(); + +public: + expr_substitution(ast_manager & m); + expr_substitution(ast_manager & m, bool cores_enabled); + expr_substitution(ast_manager & m, bool cores_enabled, bool proofs_enabled); + ~expr_substitution(); + + ast_manager & m() const { return m_manager; } + + bool proofs_enabled() const { return m_proofs_enabled; } + bool unsat_core_enabled() const { return m_cores_enabled; } + + bool empty() const { return m_subst.empty(); } + void insert(expr * s, expr * def, proof * def_pr = 0, expr_dependency * def_dep = 0); + void erase(expr * s); + bool find(expr * s, expr * & def, proof * & def_pr); + bool find(expr * s, expr * & def, proof * & def_pr, expr_dependency * & def_dep); + void reset(); + void cleanup(); +}; + +#endif diff --git a/lib/expr_weight.cpp b/lib/expr_weight.cpp new file mode 100644 index 000000000..60d64b70b --- /dev/null +++ b/lib/expr_weight.cpp @@ -0,0 +1,32 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + expr_weight.cpp + +Abstract: + + Functor for computing the weight of an expression. + +Author: + + Leonardo (leonardo) 2008-01-11 + +Notes: + +--*/ +#include"expr_weight.h" +#include"recurse_expr_def.h" + +template class recurse_expr; + +weight expr_weight_visitor::visit(app const * n, weight const * args) { + weight r(1); + unsigned j = n->get_num_args(); + while (j > 0) { + --j; + r += args[j]; + } + return r; +} diff --git a/lib/expr_weight.h b/lib/expr_weight.h new file mode 100644 index 000000000..9c371484f --- /dev/null +++ b/lib/expr_weight.h @@ -0,0 +1,35 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + expr_weight.h + +Abstract: + + Functor for computing the weight of an expression. + +Author: + + Leonardo (leonardo) 2008-01-11 + +Notes: + +--*/ +#ifndef _EXPR_WEIGHT_H_ +#define _EXPR_WEIGHT_H_ + +#include"recurse_expr.h" +#include"approx_nat.h" + +typedef approx_nat weight; + +struct expr_weight_visitor { + weight visit(var * n) { return weight(1); } + weight visit(app const * n, weight const * args); + weight visit(quantifier * n, weight body, weight *, weight *) { body += 1; return body; } +}; + +typedef recurse_expr expr_weight; + +#endif /* _EXPR_WEIGHT_H_ */ diff --git a/lib/ext_gcd.h b/lib/ext_gcd.h new file mode 100644 index 000000000..5be90c5ea --- /dev/null +++ b/lib/ext_gcd.h @@ -0,0 +1,52 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + ext_gcd.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-06-09. + +Revision History: + +--*/ +#ifndef _EXT_GCD_H_ +#define _EXT_GCD_H_ + +template +void extended_gcd(const numeral & in_a, const numeral & in_b, numeral & gcd, numeral & x, numeral & y) { + numeral a = in_a; + numeral b = in_b; + x = numeral(0); + y = numeral(1); + numeral lastx(1); + numeral lasty(0); + numeral tmp; + numeral quotient; + while (!b.is_zero()) { + tmp = b; + quotient = div(a, b); + b = mod(a, b); + a = tmp; + + tmp = x; + x = lastx - (quotient * x); + lastx = tmp; + + tmp = y; + y = lasty - (quotient * y); + lasty = tmp; + } + gcd = a; + x = lastx; + y = lasty; +} + +#endif /* _EXT_GCD_H_ */ + diff --git a/lib/ext_numeral.h b/lib/ext_numeral.h new file mode 100644 index 000000000..af4b7ac10 --- /dev/null +++ b/lib/ext_numeral.h @@ -0,0 +1,335 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + ext_numeral.h + +Abstract: + + Goodies for handling extended numerals such as R union { -oo, +oo }. + We can have extended sets of mpq, mpz, mpbq, mpf, etc. + +Author: + + Leonardo de Moura (leonardo) 2011-12-04. + +Revision History: + +--*/ +#ifndef _EXT_NUMERAL_H_ +#define _EXT_NUMERAL_H_ + +#include +#include"debug.h" + +enum ext_numeral_kind { EN_MINUS_INFINITY, EN_NUMERAL, EN_PLUS_INFINITY }; + +template +bool is_zero(numeral_manager & m, + typename numeral_manager::numeral const & a, + ext_numeral_kind ak) { + return ak == EN_NUMERAL && m.is_zero(a); +} + +template +bool is_pos(numeral_manager & m, + typename numeral_manager::numeral const & a, + ext_numeral_kind ak) { + return ak == EN_PLUS_INFINITY || (ak == EN_NUMERAL && m.is_pos(a)); +} + +template +bool is_neg(numeral_manager & m, + typename numeral_manager::numeral const & a, + ext_numeral_kind ak) { + return ak == EN_MINUS_INFINITY || (ak == EN_NUMERAL && m.is_neg(a)); +} + +inline bool is_infinite(ext_numeral_kind ak) { return ak != EN_NUMERAL; } + +template +void set(numeral_manager & m, + typename numeral_manager::numeral & a, + ext_numeral_kind & ak, + typename numeral_manager::numeral const & b, + ext_numeral_kind bk) { + m.set(a, b); + ak = bk; +} + +template +void reset(numeral_manager & m, + typename numeral_manager::numeral & a, + ext_numeral_kind & ak) { + m.reset(a); + ak = EN_NUMERAL; +} + +template +void neg(numeral_manager & m, + typename numeral_manager::numeral & a, + ext_numeral_kind & ak) { + switch (ak) { + case EN_MINUS_INFINITY: + ak = EN_PLUS_INFINITY; + break; + case EN_NUMERAL: + m.neg(a); + break; + case EN_PLUS_INFINITY: + ak = EN_MINUS_INFINITY; + break; + } +} + +template +void inv(numeral_manager & m, + typename numeral_manager::numeral & a, + ext_numeral_kind & ak) { + SASSERT(numeral_manager::field()); + switch (ak) { + case EN_MINUS_INFINITY: + ak = EN_NUMERAL; + m.reset(a); + break; + case EN_NUMERAL: + SASSERT(!m.is_zero(a)); + m.inv(a); + break; + case EN_PLUS_INFINITY: + ak = EN_NUMERAL; + m.reset(a); + break; + } +} + +template +void add(numeral_manager & m, + typename numeral_manager::numeral const & a, + ext_numeral_kind ak, + typename numeral_manager::numeral const & b, + ext_numeral_kind bk, + typename numeral_manager::numeral & c, + ext_numeral_kind & ck) { + SASSERT(!(ak == EN_MINUS_INFINITY && bk == EN_PLUS_INFINITY)); // result is undefined + SASSERT(!(ak == EN_PLUS_INFINITY && bk == EN_MINUS_INFINITY)); // result is undefined + if (ak != EN_NUMERAL) { + m.reset(c); + ck = ak; + } + else if (bk != EN_NUMERAL) { + m.reset(c); + ck = bk; + } + else { + m.add(a, b, c); + ck = EN_NUMERAL; + } +} + +template +void sub(numeral_manager & m, + typename numeral_manager::numeral const & a, + ext_numeral_kind ak, + typename numeral_manager::numeral const & b, + ext_numeral_kind bk, + typename numeral_manager::numeral & c, + ext_numeral_kind & ck) { + SASSERT(!(ak == EN_MINUS_INFINITY && bk == EN_MINUS_INFINITY)); // result is undefined + SASSERT(!(ak == EN_PLUS_INFINITY && bk == EN_PLUS_INFINITY)); // result is undefined + if (ak != EN_NUMERAL) { + SASSERT(bk != ak); + m.reset(c); + ck = ak; + } + else { + switch (bk) { + case EN_MINUS_INFINITY: + m.reset(c); + ck = EN_PLUS_INFINITY; + break; + case EN_NUMERAL: + m.sub(a, b, c); + ck = EN_NUMERAL; + break; + case EN_PLUS_INFINITY: + m.reset(c); + ck = EN_MINUS_INFINITY; + break; + } + } +} + +template +void mul(numeral_manager & m, + typename numeral_manager::numeral const & a, + ext_numeral_kind ak, + typename numeral_manager::numeral const & b, + ext_numeral_kind bk, + typename numeral_manager::numeral & c, + ext_numeral_kind & ck) { + if (is_zero(m, a, ak) || is_zero(m, b, bk)) { + m.reset(c); + ck = EN_NUMERAL; + } + else if (is_infinite(ak) || is_infinite(bk)) { + if (is_pos(m, a, ak) == is_pos(m, b, bk)) + ck = EN_PLUS_INFINITY; + else + ck = EN_MINUS_INFINITY; + m.reset(c); + } + else { + ck = EN_NUMERAL; + m.mul(a, b, c); + } +} + +template +void div(numeral_manager & m, + typename numeral_manager::numeral const & a, + ext_numeral_kind ak, + typename numeral_manager::numeral const & b, + ext_numeral_kind bk, + typename numeral_manager::numeral & c, + ext_numeral_kind & ck) { + SASSERT(!is_zero(m, b, bk)); + if (is_zero(m, a, ak)) { + SASSERT(!is_zero(m, b, bk)); + m.reset(c); + ck = EN_NUMERAL; + } + else if (is_infinite(ak)) { + SASSERT(!is_infinite(bk)); + if (is_pos(m, a, ak) == is_pos(m, b, bk)) + ck = EN_PLUS_INFINITY; + else + ck = EN_MINUS_INFINITY; + m.reset(c); + } + else if (is_infinite(bk)) { + SASSERT(!is_infinite(ak)); + m.reset(c); + ck = EN_NUMERAL; + } + else { + ck = EN_NUMERAL; + m.div(a, b, c); + } +} + + +template +void power(numeral_manager & m, + typename numeral_manager::numeral & a, + ext_numeral_kind & ak, + unsigned n) { + switch (ak) { + case EN_MINUS_INFINITY: + if (n % 2 == 0) + ak = EN_PLUS_INFINITY; + break; + case EN_NUMERAL: + m.power(a, n, a); + break; + case EN_PLUS_INFINITY: + break; // do nothing + } +} + +/** + \brief Return true if (a,ak) == (b,bk). +*/ +template +bool eq(numeral_manager & m, + typename numeral_manager::numeral const & a, + ext_numeral_kind ak, + typename numeral_manager::numeral const & b, + ext_numeral_kind bk) { + if (ak == EN_NUMERAL) { + return bk == EN_NUMERAL && m.eq(a, b); + } + else { + return ak == bk; + } +} + +template +bool neq(numeral_manager & m, + typename numeral_manager::numeral const & a, + ext_numeral_kind ak, + typename numeral_manager::numeral const & b, + ext_numeral_kind bk) { + return !eq(m, a, ak, b, bk); +} + +template +bool lt(numeral_manager & m, + typename numeral_manager::numeral const & a, + ext_numeral_kind ak, + typename numeral_manager::numeral const & b, + ext_numeral_kind bk) { + switch (ak) { + case EN_MINUS_INFINITY: + return bk != EN_MINUS_INFINITY; + case EN_NUMERAL: + switch (bk) { + case EN_MINUS_INFINITY: + return false; + case EN_NUMERAL: + return m.lt(a, b); + case EN_PLUS_INFINITY: + return true; + default: + UNREACHABLE(); + return false; + } + case EN_PLUS_INFINITY: + return false; + default: + UNREACHABLE(); + return false; + } +} + +template +bool gt(numeral_manager & m, + typename numeral_manager::numeral const & a, + ext_numeral_kind ak, + typename numeral_manager::numeral const & b, + ext_numeral_kind bk) { + return lt(m, b, bk, a, ak); +} + +template +bool le(numeral_manager & m, + typename numeral_manager::numeral const & a, + ext_numeral_kind ak, + typename numeral_manager::numeral const & b, + ext_numeral_kind bk) { + return !gt(m, a, ak, b, bk); +} + +template +bool ge(numeral_manager & m, + typename numeral_manager::numeral const & a, + ext_numeral_kind ak, + typename numeral_manager::numeral const & b, + ext_numeral_kind bk) { + return !lt(m, a, ak, b, bk); +} + +template +void display(std::ostream & out, + numeral_manager & m, + typename numeral_manager::numeral const & a, + ext_numeral_kind ak) { + switch (ak) { + case EN_MINUS_INFINITY: out << "-oo"; break; + case EN_NUMERAL: m.display(out, a); break; + case EN_PLUS_INFINITY: out << "+oo"; break; + } +} + +#endif diff --git a/lib/extension_model_converter.cpp b/lib/extension_model_converter.cpp new file mode 100644 index 000000000..e006068da --- /dev/null +++ b/lib/extension_model_converter.cpp @@ -0,0 +1,121 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + extension_model_converter.cpp + +Abstract: + + Model converter that introduces eliminated variables in a model. + +Author: + + Leonardo (leonardo) 2011-10-21 + +Notes: + +--*/ +#include"extension_model_converter.h" +#include"model_evaluator.h" +#include"ast_smt2_pp.h" +#include"model_v2_pp.h" +#include"ast_pp.h" + +extension_model_converter::~extension_model_converter() { +} + +struct extension_model_converter::set_eval { + extension_model_converter * m_owner; + model_evaluator * m_old; + set_eval(extension_model_converter * owner, model_evaluator * ev) { + m_owner = owner; + m_old = owner->m_eval; + #pragma omp critical (extension_model_converter) + { + owner->m_eval = ev; + } + } + ~set_eval() { + #pragma omp critical (extension_model_converter) + { + m_owner->m_eval = m_old; + } + } + +}; + +static void display_decls_info(std::ostream & out, model_ref & md) { + ast_manager & m = md->get_manager(); + unsigned sz = md->get_num_decls(); + for (unsigned i = 0; i < sz; i++) { + func_decl * d = md->get_decl(i); + out << d->get_name(); + out << " ("; + for (unsigned j = 0; j < d->get_arity(); j++) + out << mk_pp(d->get_domain(j), m); + out << mk_pp(d->get_range(), m); + out << ") "; + if (d->get_info()) + out << *(d->get_info()); + out << " :id " << d->get_id() << "\n"; + } +} + +void extension_model_converter::operator()(model_ref & md, unsigned goal_idx) { + SASSERT(goal_idx == 0); + TRACE("extension_mc", model_v2_pp(tout, *md); display_decls_info(tout, md);); + model_evaluator ev(*(md.get())); + ev.set_model_completion(true); + expr_ref val(m()); + { + set_eval setter(this, &ev); + unsigned i = m_vars.size(); + while (i > 0) { + --i; + expr * def = m_defs.get(i); + ev(def, val); + TRACE("extension_mc", tout << m_vars.get(i)->get_name() << " ->\n" << mk_ismt2_pp(def, m()) << "\n==>\n" << mk_ismt2_pp(val, m()) << "\n";); + func_decl * f = m_vars.get(i); + unsigned arity = f->get_arity(); + if (arity == 0) { + md->register_decl(f, val); + } + else { + func_interp * new_fi = alloc(func_interp, m(), arity); + new_fi->set_else(val); + md->register_decl(f, new_fi); + } + } + } + TRACE("extension_mc", model_v2_pp(tout, *md); display_decls_info(tout, md);); +} + +void extension_model_converter::cancel() { + #pragma omp critical (extension_model_converter) + { + if (m_eval) + m_eval->cancel(); + } +} + +void extension_model_converter::display(std::ostream & out) { + ast_manager & m = m_vars.get_manager(); + out << "(extension-model-converter"; + for (unsigned i = 0; i < m_vars.size(); i++) { + out << "\n (" << m_vars.get(i)->get_name() << " "; + unsigned indent = m_vars.get(i)->get_name().size() + 4; + out << mk_ismt2_pp(m_defs.get(i), m, indent) << ")"; + } + out << ")" << std::endl; +} + +model_converter * extension_model_converter::translate(ast_translation & translator) { + extension_model_converter * res = alloc(extension_model_converter, translator.to()); + for (unsigned i = 0; i < m_vars.size(); i++) + res->m_vars.push_back(translator(m_vars[i].get())); + for (unsigned i = 0; i < m_defs.size(); i++) + res->m_defs.push_back(translator(m_defs[i].get())); + // m_eval is a transient object. So, it doesn't need to be translated. + return res; +} diff --git a/lib/extension_model_converter.h b/lib/extension_model_converter.h new file mode 100644 index 000000000..84d29edd0 --- /dev/null +++ b/lib/extension_model_converter.h @@ -0,0 +1,57 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + extension_model_converter.h + +Abstract: + + Model converter that introduces new interpretations into a model. + It used to be called elim_var_model_converter + +Author: + + Leonardo (leonardo) 2011-10-21 + +Notes: + +--*/ +#ifndef _EXTENSION_MODEL_CONVERTER_H_ +#define _EXTENSION_MODEL_CONVERTER_H_ + +#include"ast.h" +#include"model_converter.h" + +class model_evaluator; + +class extension_model_converter : public model_converter { + func_decl_ref_vector m_vars; + expr_ref_vector m_defs; + model_evaluator * m_eval; + struct set_eval; +public: + extension_model_converter(ast_manager & m):m_vars(m), m_defs(m), m_eval(0) { + } + + virtual ~extension_model_converter(); + + ast_manager & m() const { return m_vars.get_manager(); } + + virtual void operator()(model_ref & md, unsigned goal_idx); + + virtual void cancel(); + + virtual void display(std::ostream & out); + + // register a variable that was eliminated + void insert(func_decl * v, expr * def) { + m_vars.push_back(v); + m_defs.push_back(def); + } + + virtual model_converter * translate(ast_translation & translator); +}; + + +#endif diff --git a/lib/f2n.h b/lib/f2n.h new file mode 100644 index 000000000..6576b136a --- /dev/null +++ b/lib/f2n.h @@ -0,0 +1,167 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + f2n.h + +Abstract: + + Template for wrapping a float-like API as a numeral-like API. + The basic idea is to have the rounding mode as an implicit argument. + +Author: + + Leonardo de Moura (leonardo) 2012-07-30. + +Revision History: + +--*/ +#ifndef __F2N_H_ +#define __F2N_H_ + +#include"mpf.h" + +template +class f2n { +public: + typedef typename fmanager::numeral numeral; + struct exception {}; + +private: + fmanager & m_manager; + mpf_rounding_mode m_mode; + unsigned m_ebits; + unsigned m_sbits; + numeral m_tmp1; + numeral m_one; + + void check(numeral const & n) { if (!m().is_regular(n)) throw exception(); } + +public: + static bool field() { return true; } + static bool precise() { return false; } + + f2n(fmanager & m, unsigned ebits = 11, unsigned sbits = 53):m_manager(m), m_mode(MPF_ROUND_TOWARD_POSITIVE), m_ebits(ebits), m_sbits(sbits) { + m_manager.set(m_one, ebits, sbits, 1); + } + + ~f2n() { + m().del(m_tmp1); + m().del(m_one); + } + + void set_rounding_mode(mpf_rounding_mode m) { m_mode = m; } + mpf_rounding_mode rounding_mode() const { return m_mode; } + void round_to_plus_inf() { m_mode = MPF_ROUND_TOWARD_POSITIVE; } + void round_to_minus_inf() { m_mode = MPF_ROUND_TOWARD_NEGATIVE; } + void set_rounding(bool to_plus_inf) { if (to_plus_inf) round_to_plus_inf(); else round_to_minus_inf(); } + unsigned ebits() const { return m_ebits; } + unsigned sbits() const { return m_sbits; } + + fmanager & m() const { return m_manager; } + + double to_double(numeral & x) const { return m().to_double(x); } + + void del(numeral & x) { m().del(x); } + + void abs(numeral & o) { m().abs(o); } + void abs(numeral const & x, numeral & o) { m().abs(x, o); } + + void neg(numeral & o) { m().neg(o); } + void neg(numeral const & x, numeral & o) { m().neg(x, o); } + + bool is_zero(numeral const & x) { return m().is_zero(x); } + bool is_neg(numeral const & x) { return m().is_neg(x) && !m().is_zero(x); /* it is not clear whether actual hardware returns true for is_neg(0-) */ } + bool is_pos(numeral const & x) { return m().is_pos(x) && !m().is_zero(x); } + bool is_nonneg(numeral const & x) { return !is_neg(x); } + bool is_nonpos(numeral const & x) { return !is_pos(x); } + + void set(numeral & o, int value) { m().set(o, m_ebits, m_sbits, value); check(o); } + void set(numeral & o, int n, int d) { m().set(o, m_ebits, m_sbits, m_mode, n, d); check(o); } + void set(numeral & o, double x) { m().set(o, m_ebits, m_sbits, x); check(o); } + void set(numeral & o, unsigned value) { m().set(o, m_ebits, m_sbits, (double)value); check(o); } + void set(numeral & o, numeral const & x) { m().set(o, x); check(o); } + void set(numeral & o, mpq const & x) { m().set(o, m_ebits, m_sbits, m_mode, x); check(o); } + void reset(numeral & o) { m().reset(o, m_ebits, m_sbits); } + static void swap(numeral & x, numeral & y) { x.swap(y); } + + void add(numeral const & x, numeral const & y, numeral & o) { m().add(m_mode, x, y, o); check(o); } + void sub(numeral const & x, numeral const & y, numeral & o) { m().sub(m_mode, x, y, o); check(o); } + void mul(numeral const & x, numeral const & y, numeral & o) { m().mul(m_mode, x, y, o); check(o); } + void div(numeral const & x, numeral const & y, numeral & o) { m().div(m_mode, x, y, o); check(o); } + void inv(numeral & o) { numeral a; set(a, 1); div(a, o, o); del(a); check(o); } + void inv(numeral const & x, numeral & o) { set(o, x); inv(o); } + void inc(numeral & x) { add(x, m_one, x); } + void dec(numeral & x) { sub(x, m_one, x); } + + void power(numeral const & a, unsigned p, numeral & b) { + unsigned mask = 1; + numeral power; + set(power, a); + set(b, 1); + while (mask <= p) { + if (mask & p) + mul(b, power, b); + mul(power, power, power); + mask = mask << 1; + } + del(power); + check(b); + } + + // Store the floor of a into b. Return true if a is an integer. + // Throws an exception if the result cannot be computed precisely. + void floor(numeral const & a, numeral & b) { + SASSERT(m().is_regular(a)); + // Claim: If a is a regular float, then floor(a) is an integer that can be precisely represented. + // Justification: (for the case a is nonnegative) + // If 0 <= a > 2^sbits(), then a is an integer, and floor(a) == a + // If 0 <= a <= 2^sbits(), then floor(a) is representable since every integer less than 2^sbit + m().round_to_integral(MPF_ROUND_TOWARD_NEGATIVE, a, m_tmp1); + SASSERT(m().is_regular(m_tmp1)); + if (m().le(m_tmp1, a)) { + m().set(b, m_tmp1); + } + else { + // the rounding mode doesn't matter for the following operation. + m().sub(MPF_ROUND_TOWARD_NEGATIVE, m_tmp1, m_one, b); + } + SASSERT(m().is_regular(b)); + } + + void ceil(numeral const & a, numeral & b) { + SASSERT(m().is_regular(a)); + // See comment in floor + m().round_to_integral(MPF_ROUND_TOWARD_POSITIVE, a, m_tmp1); + SASSERT(m().is_regular(m_tmp1)); + if (m().ge(m_tmp1, a)) { + m().set(b, m_tmp1); + } + else { + // the rounding mode doesn't matter for the following operation. + m().add(MPF_ROUND_TOWARD_NEGATIVE, m_tmp1, m_one, b); + } + SASSERT(m().is_regular(b)); + } + + unsigned prev_power_of_two(numeral const & a) { return m().prev_power_of_two(a); } + + bool eq(numeral const & x, numeral const & y) { return m().eq(x, y); } + bool lt(numeral const & x, numeral const & y) { return m().lt(x, y); } + bool le(numeral const & x, numeral const & y) { return m().le(x, y); } + bool gt(numeral const & x, numeral const & y) { return m().gt(x, y); } + bool ge(numeral const & x, numeral const & y) { return m().ge(x, y); } + + bool is_int(numeral const & x) { return m().is_int(x); } + bool is_one(numeral const & x) { return m().is_one(x); } + bool is_minus_one(numeral const & x) { numeral & _x = const_cast(x); m().neg(_x); bool r = m().is_one(_x); m().neg(_x); return r; } + + std::string to_string(numeral const & a) { return m().to_string(a); } + std::string to_rational_string(numeral const & a) { return m().to_rational_string(a); } + void display(std::ostream & out, numeral const & a) { out << to_string(a); } + void display_decimal(std::ostream & out, numeral const & a, unsigned k) { m().display_decimal(out, a, k); } + void display_smt2(std::ostream & out, numeral const & a, bool decimal) { m().display_smt2(out, a, decimal); } +}; + +#endif diff --git a/lib/factor_rewriter.cpp b/lib/factor_rewriter.cpp new file mode 100644 index 000000000..9e988caf7 --- /dev/null +++ b/lib/factor_rewriter.cpp @@ -0,0 +1,352 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + factor_rewriter.cpp + +Abstract: + + Rewriting utilities for factoring polynomials in equations, + and inequalities. + +Author: + + Nikolaj (nbjorner) 2011-19-05 + +Notes: + +--*/ + +#include"factor_rewriter.h" +#include"ast_pp.h" +#include"rewriter_def.h" + +factor_rewriter::factor_rewriter(ast_manager & m): m_manager(m), m_arith(m), m_factors(m) { +} + +br_status factor_rewriter::mk_app_core( + func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + + if (m().is_eq(f)) { SASSERT(num_args == 2); return mk_eq(args[0], args[1], result); } + + if(f->get_family_id() == a().get_family_id()) { + switch (f->get_decl_kind()) { + case OP_LE: SASSERT(num_args == 2); return mk_le(args[0], args[1], result); + case OP_GE: SASSERT(num_args == 2); return mk_ge(args[0], args[1], result); + case OP_LT: SASSERT(num_args == 2); return mk_lt(args[0], args[1], result); + case OP_GT: SASSERT(num_args == 2); return mk_gt(args[0], args[1], result); + default: return BR_FAILED; + } + } + return BR_FAILED; +} + +br_status factor_rewriter::mk_eq(expr * arg1, expr * arg2, expr_ref & result) { + if (!a().is_real(arg1) && !m_arith.is_int(arg1)) { + return BR_FAILED; + } + mk_adds(arg1, arg2); + mk_muls(); + if (m_muls.empty()) { + result = m().mk_true(); + return BR_DONE; + } + if (!extract_factors()) { + TRACE("factor_rewriter", tout << mk_pp(arg1, m()) << " = " << mk_pp(arg2, m()) << "\n";); + return BR_FAILED; + } + powers_t::iterator it = m_powers.begin(), end = m_powers.end(); + expr_ref_vector eqs(m()); + for(; it != end; ++it) { + expr* e = it->m_key; + eqs.push_back(m().mk_eq(e, a().mk_numeral(rational(0), m().get_sort(e)))); + } + result = m().mk_or(eqs.size(), eqs.c_ptr()); + return BR_DONE; +} + +br_status factor_rewriter::mk_le(expr * arg1, expr * arg2, expr_ref & result) { + mk_adds(arg1, arg2); + mk_muls(); + if (m_muls.empty()) { + result = m().mk_true(); + return BR_DONE; + } + if (!extract_factors()) { + TRACE("factor_rewriter", tout << mk_pp(arg1, m()) << " <= " << mk_pp(arg2, m()) << "\n";); + return BR_FAILED; + } + + // a^2 * b^3 * c <= 0 -> + // a = 0 \/ (b = 0 \/ b > 0 & c <= 0 \/ b < 0 & c >= 0) + // + + expr_ref neg(m()); + expr_ref_vector eqs(m()); + mk_is_negative(neg, eqs); + eqs.push_back(neg); + result = m().mk_or(eqs.size(), eqs.c_ptr()); + TRACE("factor_rewriter", + tout << mk_pp(arg1, m()) << " <= " << mk_pp(arg2, m()) << "\n"; + tout << mk_pp(result.get(), m()) << "\n";); + return BR_DONE; +} + +br_status factor_rewriter::mk_lt(expr * arg1, expr * arg2, expr_ref & result) { + mk_adds(arg1, arg2); + mk_muls(); + if (m_muls.empty()) { + result = m().mk_false(); + return BR_DONE; + } + if (!extract_factors()) { + TRACE("factor_rewriter", tout << mk_pp(arg1, m()) << " < " << mk_pp(arg2, m()) << "\n";); + return BR_FAILED; + } + // a^2 * b^3 * c < 0 -> + // a != 0 /\ (b > 0 & c < 0 \/ b < 0 & c > 0) + // + + expr_ref neg(m()); + expr_ref_vector eqs(m()); + mk_is_negative(neg, eqs); + for (unsigned i = 0; i < eqs.size(); ++i) { + eqs[i] = m().mk_not(eqs[i].get()); + } + eqs.push_back(neg); + result = m().mk_and(eqs.size(), eqs.c_ptr()); + TRACE("factor_rewriter", tout << mk_pp(result.get(), m()) << "\n";); + return BR_DONE; +} + +void factor_rewriter::mk_is_negative(expr_ref& result, expr_ref_vector& eqs) { + powers_t::iterator it = m_powers.begin(), end = m_powers.end(); + SASSERT(m_powers.size() >= 1); + SASSERT(it != end); + expr_ref neg0(m()), neg(m()), pos0(m()), pos(m()), tmp(m()); + expr* e = it->m_key; + expr_ref zero(a().mk_numeral(rational(0), m().get_sort(e)), m()); + expr_ref_vector conjs(m()); + pos0 = m().mk_true(); + neg0 = m().mk_false(); + for(; it != end; ++it) { + e = it->m_key; + eqs.push_back(m().mk_eq(zero, e)); + if (!even(it->m_value)) { + pos = a().mk_lt(zero, e); + neg = a().mk_lt(e, zero); + if (m().is_false(neg0)) { + neg0 = neg; + pos0 = pos; + } + else { + tmp = m().mk_or(m().mk_and(pos, pos0), m().mk_and(neg, neg0)); + neg0 = m().mk_or(m().mk_and(neg, pos0), m().mk_and(pos, neg0)); + pos0 = tmp; + } + } + } + result = neg0; +} + +// convert arg1 - arg2 into +// sum of monomials +// m_adds: sum of products. +// m_muls: list of products +void factor_rewriter::mk_adds(expr* arg1, expr* arg2) { + m_adds.reset(); + m_adds.push_back(std::make_pair(arg1, true)); + m_adds.push_back(std::make_pair(arg2, false)); + rational k; + for (unsigned i = 0; i < m_adds.size();) { + bool sign = m_adds[i].second; + expr* _e = m_adds[i].first; + + TRACE("factor_rewriter", tout << i << " " << mk_pp(_e, m_manager) << "\n";); + + if (!is_app(_e)) { + ++i; + continue; + } + app* e = to_app(_e); + if (a().is_add(e) && e->get_num_args() > 0) { + m_adds[i].first = e->get_arg(0); + for (unsigned j = 1; j < e->get_num_args(); ++j) { + m_adds.push_back(std::make_pair(e->get_arg(j),sign)); + } + } + else if (a().is_sub(e) && e->get_num_args() > 0) { + m_adds[i].first = e->get_arg(0); + for (unsigned j = 1; j < e->get_num_args(); ++j) { + m_adds.push_back(std::make_pair(e->get_arg(j),!sign)); + } + } + else if (a().is_uminus(e)) { + m_adds[i].first = e->get_arg(0); + m_adds[i].second = !sign; + } + else if (a().is_numeral(e, k) && k.is_zero()) { + unsigned sz = m_adds.size(); + m_adds[i] = m_adds[sz-1]; + m_adds.resize(sz-1); + } + else { + ++i; + } + } + TRACE("factor_rewriter", + for (unsigned i = 0; i < m_adds.size(); ++i) { + if (!m_adds[i].second) tout << "-"; else tout << "+"; + tout << mk_pp(m_adds[i].first, m()) << " "; + } + tout << "\n"; + ); +} + +void factor_rewriter::mk_muls() { + m_muls.reset(); + for (unsigned i = 0; i < m_adds.size(); ++i) { + m_muls.push_back(ptr_vector()); + m_muls.back().push_back(m_adds[i].first); + mk_expand_muls(m_muls.back()); + if (m_muls.back().empty()) { + m_muls.pop_back(); + m_adds.erase(m_adds.begin() + i); + --i; + } + } + TRACE("factor_rewriter", + for (unsigned i = 0; i < m_muls.size(); ++i) { + for (unsigned j = 0; j < m_muls[i].size(); ++j) { + tout << mk_pp(m_muls[i][j], m()) << " "; + } + tout << "\n"; + } + tout << "\n"; + ); +} + +void factor_rewriter::mk_expand_muls(ptr_vector& muls) { + for (unsigned i = 0; i < muls.size(); ) { + expr* _e = muls[i]; + if (!is_app(_e)) { + ++i; + continue; + } + app* e = to_app(_e); + if (a().is_mul(e) && e->get_num_args() > 0) { + muls[i] = e->get_arg(0); + for (unsigned j = 1; j < e->get_num_args(); ++j) { + muls.push_back(e->get_arg(j)); + } + } + else { + ++i; + } + } +} + +bool factor_rewriter::extract_factors() { + m_factors.reset(); + unsigned_vector pos; + expr* e; + SASSERT(!m_muls.empty()); + if (m_muls.size() == 1) { + if (m_muls[0].size() > 1) { + m_factors.append(m_muls[0].size(), m_muls[0].c_ptr()); + if (!m_adds[0].second) { + bool found_numeral = false; + sort* s = m().get_sort(m_muls[0][0]); + rational v; + for (unsigned i = 0; !found_numeral && i < m_factors.size(); ++i) { + if (a().is_numeral(m_factors[i].get(), v)) { + m_factors[i] = a().mk_numeral(-v, s); + found_numeral = true; + } + } + if (!found_numeral) { + m_factors.push_back(a().mk_numeral(rational(-1),s)); + } + } + collect_powers(); + return true; + } + return false; + } + for (unsigned i = 0; i < m_muls[0].size(); ++i) { + pos.reset(); + pos.push_back(i); + e = m_muls[0][i]; + bool ok = true; + for (unsigned j = 1; ok && j < m_muls.size(); ++j) { + ok = false; + unsigned k = 0; + for (k = 0; !ok && k < m_muls[j].size(); ++k) { + ok = m_muls[j][k] == e; + } + pos.push_back(k-1); + } + if (ok) { + SASSERT(pos.size() == m_muls.size()); + m_factors.push_back(e); + for (unsigned j = 0; j < pos.size(); ++j) { + m_muls[j].erase(m_muls[j].begin() + pos[j]); + } + --i; + } + } + if (m_factors.empty()) { + return false; + } + SASSERT(m_muls.size() == m_adds.size()); + expr_ref_vector trail(m()); + sort* s = m().get_sort(m_factors[0].get()); + for (unsigned i = 0; i < m_adds.size(); ++i) { + switch(m_muls[i].size()) { + case 0: + e = a().mk_numeral(rational(1), s); + break; + case 1: + e = m_muls[i][0]; + break; + default: + e = a().mk_mul(m_muls[i].size(), m_muls[i].c_ptr()); + break; + } + if (!m_adds[i].second) { + e = a().mk_uminus(e); + } + trail.push_back(e); + } + switch(trail.size()) { + case 0: + break; + case 1: + m_factors.push_back(trail[0].get()); + break; + default: + m_factors.push_back(a().mk_add(trail.size(), trail.c_ptr())); + break; + } + TRACE("factor_rewriter", + for (unsigned i = 0; i < m_factors.size(); ++i) { + tout << mk_pp(m_factors[i].get(), m()) << " "; + } + tout << "\n"; + ); + collect_powers(); + return true; +} + +void factor_rewriter::collect_powers() { + m_powers.reset(); + for (unsigned i = 0; i < m_factors.size(); ++i) { + obj_map::obj_map_entry* entry = m_powers.insert_if_not_there2(m_factors[i].get(), 0); + if (entry) { + ++(entry->get_data().m_value); + } + } +} + +template class rewriter_tpl; diff --git a/lib/factor_rewriter.h b/lib/factor_rewriter.h new file mode 100644 index 000000000..ee6a1d03a --- /dev/null +++ b/lib/factor_rewriter.h @@ -0,0 +1,78 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + factor_rewriter.h + +Abstract: + + Rewriting utilities for factoring polynomials in equations, + and inequalities. + +Author: + + Nikolaj (nbjorner) 2011-19-05 + +Notes: + +--*/ +#ifndef _FACTOR_REWRITER_H_ +#define _FACTOR_REWRITER_H_ + +#include"ast.h" +#include"rewriter.h" +#include"arith_decl_plugin.h" + +class factor_rewriter { + typedef obj_map powers_t; + ast_manager & m_manager; + arith_util m_arith; + powers_t m_powers; + vector > m_adds; + vector > m_muls; + expr_ref_vector m_factors; + +public: + factor_rewriter(ast_manager & m); + ast_manager & m() const { return m_manager; } + arith_util & a() { return m_arith; } + + br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + +private: + br_status mk_eq(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_le(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_lt(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_ge(expr * a1, expr * a2, expr_ref & r) { return mk_le(a2,a1,r); } + br_status mk_gt(expr * a1, expr * a2, expr_ref & r) { return mk_lt(a2,a1,r); } + + void mk_adds(expr* arg1, expr* arg2); + void mk_muls(); + void mk_expand_muls(ptr_vector& muls); + void collect_powers(); + bool extract_factors(); + bool even(unsigned n) const { return 0 == (n & 0x1); } + void mk_is_negative(expr_ref& result, expr_ref_vector& eqs); +}; + +struct factor_rewriter_cfg : public default_rewriter_cfg { + factor_rewriter m_r; + bool rewrite_patterns() const { return false; } + bool flat_assoc(func_decl * f) const { return false; } + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + result_pr = 0; + return m_r.mk_app_core(f, num, args, result); + } + factor_rewriter_cfg(ast_manager & m):m_r(m) {} +}; + +class factor_rewriter_star : public rewriter_tpl { + factor_rewriter_cfg m_cfg; +public: + factor_rewriter_star(ast_manager & m): + rewriter_tpl(m, false, m_cfg), + m_cfg(m) {} +}; + +#endif diff --git a/lib/factor_tactic.cpp b/lib/factor_tactic.cpp new file mode 100644 index 000000000..2cfbc9ef8 --- /dev/null +++ b/lib/factor_tactic.cpp @@ -0,0 +1,361 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + factor_tactic.cpp + +Abstract: + + Polynomial factorization tactic. + +Author: + + Leonardo de Moura (leonardo) 2012-02-03 + +Revision History: + +--*/ +#include"tactical.h" +#include"expr2polynomial.h" +#include"rewriter_def.h" + +class factor_tactic : public tactic { + + struct rw_cfg : public default_rewriter_cfg { + ast_manager & m; + arith_util m_util; + unsynch_mpq_manager m_qm; + polynomial::manager m_pm; + default_expr2polynomial m_expr2poly; + polynomial::factor_params m_fparams; + bool m_split_factors; + + rw_cfg(ast_manager & _m, params_ref const & p): + m(_m), + m_util(_m), + m_pm(m_qm), + m_expr2poly(m, m_pm) { + updt_params(p); + } + + void updt_params(params_ref const & p) { + m_split_factors = p.get_bool(":split-factors", true); + m_fparams.updt_params(p); + } + + expr * mk_mul(unsigned sz, expr * const * args) { + SASSERT(sz > 0); + if (sz == 1) + return args[0]; + return m_util.mk_mul(sz, args); + } + + expr * mk_zero_for(expr * arg) { + return m_util.mk_numeral(rational(0), m_util.is_int(arg)); + } + + // p1^k1 * p2^k2 = 0 --> p1*p2 = 0 + void mk_eq(polynomial::factors const & fs, expr_ref & result) { + expr_ref_buffer args(m); + expr_ref arg(m); + for (unsigned i = 0; i < fs.distinct_factors(); i++) { + m_expr2poly.to_expr(fs[i], true, arg); + args.push_back(arg); + } + result = m.mk_eq(mk_mul(args.size(), args.c_ptr()), mk_zero_for(arg)); + } + + // p1^k1 * p2^k2 = 0 --> p1 = 0 or p2 = 0 + void mk_split_eq(polynomial::factors const & fs, expr_ref & result) { + expr_ref_buffer args(m); + expr_ref arg(m); + for (unsigned i = 0; i < fs.distinct_factors(); i++) { + m_expr2poly.to_expr(fs[i], true, arg); + args.push_back(m.mk_eq(arg, mk_zero_for(arg))); + } + if (args.size() == 1) + result = args[0]; + else + result = m.mk_or(args.size(), args.c_ptr()); + } + + decl_kind flip(decl_kind k) { + SASSERT(k == OP_LT || k == OP_GT || k == OP_LE || k == OP_GE); + switch (k) { + case OP_LT: return OP_GT; + case OP_LE: return OP_GE; + case OP_GT: return OP_LT; + case OP_GE: return OP_LE; + default: + UNREACHABLE(); + return k; + } + } + + // p1^{2*k1} * p2^{2*k2 + 1} >=< 0 + // --> + // (p1^2)*p2 >=<0 + void mk_comp(decl_kind k, polynomial::factors const & fs, expr_ref & result) { + SASSERT(k == OP_LT || k == OP_GT || k == OP_LE || k == OP_GE); + expr_ref_buffer args(m); + expr_ref arg(m); + for (unsigned i = 0; i < fs.distinct_factors(); i++) { + m_expr2poly.to_expr(fs[i], true, arg); + if (fs.get_degree(i) % 2 == 0) + arg = m_util.mk_power(arg, m_util.mk_numeral(rational(2), m_util.is_int(arg))); + args.push_back(arg); + } + expr * lhs = mk_mul(args.size(), args.c_ptr()); + result = m.mk_app(m_util.get_family_id(), k, lhs, mk_zero_for(lhs)); + } + + // See mk_split_strict_comp and mk_split_nonstrict_comp + void split_even_odd(bool strict, polynomial::factors const & fs, expr_ref_buffer & even_eqs, expr_ref_buffer & odd_factors) { + expr_ref arg(m); + for (unsigned i = 0; i < fs.distinct_factors(); i++) { + m_expr2poly.to_expr(fs[i], true, arg); + if (fs.get_degree(i) % 2 == 0) { + expr * eq = m.mk_eq(arg, mk_zero_for(arg)); + if (strict) + even_eqs.push_back(m.mk_not(eq)); + else + even_eqs.push_back(eq); + } + else { + odd_factors.push_back(arg); + } + } + } + + // Strict case + // p1^{2*k1} * p2^{2*k2 + 1} >< 0 + // --> + // p1 != 0 and p2 >< 0 + // + // Nonstrict + // p1^{2*k1} * p2^{2*k2 + 1} >=< 0 + // --> + // p1 = 0 or p2 >=< 0 + // + void mk_split_comp(decl_kind k, polynomial::factors const & fs, expr_ref & result) { + SASSERT(k == OP_LT || k == OP_GT || k == OP_LE || k == OP_GE); + bool strict = (k == OP_LT) || (k == OP_GT); + expr_ref_buffer args(m); + expr_ref_buffer odd_factors(m); + split_even_odd(strict, fs, args, odd_factors); + if (odd_factors.empty()) { + if (k == OP_LT) { + result = m.mk_false(); + return; + } + if (k == OP_GE) { + result = m.mk_true(); + return; + } + } + else { + args.push_back(m.mk_app(m_util.get_family_id(), k, mk_mul(odd_factors.size(), odd_factors.c_ptr()), mk_zero_for(odd_factors[0]))); + } + SASSERT(!args.empty()); + if (args.size() == 1) + result = args[0]; + else if (strict) + result = m.mk_and(args.size(), args.c_ptr()); + else + result = m.mk_or(args.size(), args.c_ptr()); + } + + br_status factor(func_decl * f, expr * lhs, expr * rhs, expr_ref & result) { + polynomial_ref p1(m_pm); + polynomial_ref p2(m_pm); + scoped_mpz d1(m_qm); + scoped_mpz d2(m_qm); + m_expr2poly.to_polynomial(lhs, p1, d1); + m_expr2poly.to_polynomial(rhs, p2, d2); + TRACE("factor_tactic_bug", + tout << "lhs: " << mk_ismt2_pp(lhs, m) << "\n"; + tout << "p1: " << p1 << "\n"; + tout << "d1: " << d1 << "\n"; + tout << "rhs: " << mk_ismt2_pp(rhs, m) << "\n"; + tout << "p2: " << p2 << "\n"; + tout << "d2: " << d2 << "\n";); + scoped_mpz lcm(m_qm); + m_qm.lcm(d1, d2, lcm); + m_qm.div(lcm, d1, d1); + m_qm.div(lcm, d2, d2); + m_qm.neg(d2); + polynomial_ref p(m_pm); + p = m_pm.addmul(d1, m_pm.mk_unit(), p1, d2, m_pm.mk_unit(), p2); + if (is_const(p)) + return BR_FAILED; + polynomial::factors fs(m_pm); + TRACE("factor_tactic_bug", tout << "p: " << p << "\n";); + m_pm.factor(p, fs, m_fparams); + SASSERT(fs.distinct_factors() > 0); + TRACE("factor_tactic_bug", tout << "factors:\n"; fs.display(tout); tout << "\n";); + if (fs.distinct_factors() == 1 && fs.get_degree(0) == 1) + return BR_FAILED; + if (m.is_eq(f)) { + if (m_split_factors) + mk_split_eq(fs, result); + else + mk_eq(fs, result); + } + else { + decl_kind k = f->get_decl_kind(); + if (m_qm.is_neg(fs.get_constant())) + k = flip(k); + + if (m_split_factors) + mk_split_comp(k, fs, result); + else + mk_comp(k, fs, result); + } + return BR_DONE; + } + + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + if (num != 2) + return BR_FAILED; + if (m.is_eq(f) && (m_util.is_arith_expr(args[0]) || m_util.is_arith_expr(args[1]))) + return factor(f, args[0], args[1], result); + if (f->get_family_id() != m_util.get_family_id()) + return BR_FAILED; + switch (f->get_decl_kind()) { + case OP_LT: + case OP_GT: + case OP_LE: + case OP_GE: + return factor(f, args[0], args[1], result); + } + return BR_FAILED; + } + }; + + struct rw : public rewriter_tpl { + rw_cfg m_cfg; + + rw(ast_manager & m, params_ref const & p): + rewriter_tpl(m, m.proofs_enabled(), m_cfg), + m_cfg(m, p) { + } + }; + + struct imp { + ast_manager & m; + rw m_rw; + + imp(ast_manager & _m, params_ref const & p): + m(_m), + m_rw(m, p) { + } + + void set_cancel(bool f) { + m_rw.set_cancel(f); + m_rw.cfg().m_pm.set_cancel(f); + } + + void updt_params(params_ref const & p) { + m_rw.cfg().updt_params(p); + } + + void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + mc = 0; pc = 0; core = 0; + tactic_report report("factor", *g); + bool produce_proofs = g->proofs_enabled(); + + expr_ref new_curr(m); + proof_ref new_pr(m); + unsigned size = g->size(); + for (unsigned idx = 0; idx < size; idx++) { + expr * curr = g->form(idx); + m_rw(curr, new_curr, new_pr); + if (produce_proofs) { + proof * pr = g->pr(idx); + new_pr = m.mk_modus_ponens(pr, new_pr); + } + g->update(idx, new_curr, new_pr, g->dep(idx)); + } + g->inc_depth(); + result.push_back(g.get()); + TRACE("factor", g->display(tout);); + SASSERT(g->is_well_sorted()); + } + }; + + imp * m_imp; + params_ref m_params; +public: + factor_tactic(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(factor_tactic, m, m_params); + } + + virtual ~factor_tactic() { + dealloc(m_imp); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + m_imp->m_rw.cfg().updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + r.insert(":split-factors", CPK_BOOL, + "(default: true) apply simplifications such as (= (* p1 p2) 0) --> (or (= p1 0) (= p2 0))."); + polynomial::factor_params::get_param_descrs(r); + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + try { + (*m_imp)(in, result, mc, pc, core); + } + catch (z3_error & ex) { + throw ex; + } + catch (z3_exception & ex) { + throw tactic_exception(ex.msg()); + } + } + + virtual void cleanup() { + ast_manager & m = m_imp->m; + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + } + + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + +tactic * mk_factor_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(factor_tactic, m, p)); +} + + + diff --git a/lib/factor_tactic.h b/lib/factor_tactic.h new file mode 100644 index 000000000..d19b57bed --- /dev/null +++ b/lib/factor_tactic.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + factor_tactic.h + +Abstract: + + Polynomial factorization tactic. + +Author: + + Leonardo de Moura (leonardo) 2012-02-03 + +Revision History: + +--*/ +#ifndef _FACTOR_TACTIC_H_ +#define _FACTOR_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_factor_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/filter_model_converter.cpp b/lib/filter_model_converter.cpp new file mode 100644 index 000000000..da4c71e54 --- /dev/null +++ b/lib/filter_model_converter.cpp @@ -0,0 +1,66 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + filter_model_converter.cpp + +Abstract: + + Filter decls from a model + +Author: + + Leonardo (leonardo) 2011-05-06 + +Notes: + +--*/ +#include"filter_model_converter.h" +#include"model_v2_pp.h" + +filter_model_converter::~filter_model_converter() { +} + +void filter_model_converter::operator()(model_ref & old_model, unsigned goal_idx) { + TRACE("filter_mc", tout << "before filter_model_converter\n"; model_v2_pp(tout, *old_model); display(tout);); + ast_fast_mark1 fs; + unsigned num = m_decls.size(); + for (unsigned i = 0; i < num; i++) + fs.mark(m_decls.get(i)); + model * new_model = alloc(model, m()); + num = old_model->get_num_constants(); + for (unsigned i = 0; i < num; i++) { + func_decl * f = old_model->get_constant(i); + if (fs.is_marked(f)) + continue; + expr * fi = old_model->get_const_interp(f); + new_model->register_decl(f, fi); + } + num = old_model->get_num_functions(); + for (unsigned i = 0; i < num; i++) { + func_decl * f = old_model->get_function(i); + if (fs.is_marked(f)) + continue; + func_interp * fi = old_model->get_func_interp(f); + new_model->register_decl(f, fi->copy()); + } + new_model->copy_usort_interps(*old_model); + old_model = new_model; + TRACE("filter_mc", tout << "after filter_model_converter\n"; model_v2_pp(tout, *old_model);); +} + +void filter_model_converter::display(std::ostream & out) { + out << "(filter-model-converter"; + for (unsigned i = 0; i < m_decls.size(); i++) { + out << " " << m_decls.get(i)->get_name(); + } + out << ")" << std::endl; +} + +model_converter * filter_model_converter::translate(ast_translation & translator) { + filter_model_converter * res = alloc(filter_model_converter, translator.to()); + for (unsigned i = 0; i < m_decls.size(); i++) + res->m_decls.push_back(translator(m_decls[i].get())); + return res; +} diff --git a/lib/filter_model_converter.h b/lib/filter_model_converter.h new file mode 100644 index 000000000..afc77d345 --- /dev/null +++ b/lib/filter_model_converter.h @@ -0,0 +1,48 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + filter_model_converter.h + +Abstract: + + Filter decls from a model + +Author: + + Leonardo (leonardo) 2011-05-06 + +Notes: + +--*/ +#ifndef _FILTER_MODEL_CONVERTER_H_ +#define _FILTER_MODEL_CONVERTER_H_ + +#include"model_converter.h" + +class filter_model_converter : public model_converter { + func_decl_ref_vector m_decls; +public: + filter_model_converter(ast_manager & m):m_decls(m) {} + + virtual ~filter_model_converter(); + + ast_manager & m() const { return m_decls.get_manager(); } + + virtual void operator()(model_ref & md, unsigned goal_idx); + + virtual void operator()(model_ref & md) { operator()(md, 0); } // TODO: delete + + virtual void cancel() {} + + virtual void display(std::ostream & out); + + void insert(func_decl * d) { + m_decls.push_back(d); + } + + virtual model_converter * translate(ast_translation & translator); +}; + +#endif diff --git a/lib/fingerprints.cpp b/lib/fingerprints.cpp new file mode 100644 index 000000000..6cb0bfd2a --- /dev/null +++ b/lib/fingerprints.cpp @@ -0,0 +1,156 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + fingerprints.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-02-24. + +Revision History: + +--*/ +#include"fingerprints.h" + +namespace smt { + + fingerprint::fingerprint(region & r, void * d, unsigned d_h, unsigned n, enode * const * args): + m_data(d), + m_data_hash(d_h), + m_num_args(n), + m_args(0) { + m_args = new (r) enode*[n]; + memcpy(m_args, args, sizeof(enode*) * n); + } + + bool fingerprint_set::fingerprint_eq_proc::operator()(fingerprint const * f1, fingerprint const * f2) const { + if (f1->get_data() != f2->get_data()) + return false; + if (f1->get_num_args() != f2->get_num_args()) + return false; + unsigned n = f1->get_num_args(); + for(unsigned i = 0; i < n; i++) + if (f1->get_arg(i) != f2->get_arg(i)) + return false; + return true; + } + + fingerprint * fingerprint_set::mk_dummy(void * data, unsigned data_hash, unsigned num_args, enode * const * args) { + m_tmp.reset(); + m_tmp.append(num_args, args); + m_dummy.m_data = data; + m_dummy.m_data_hash = data_hash; + m_dummy.m_num_args = num_args; + m_dummy.m_args = m_tmp.c_ptr(); + return &m_dummy; + } + + fingerprint * fingerprint_set::insert(void * data, unsigned data_hash, unsigned num_args, enode * const * args) { + fingerprint * d = mk_dummy(data, data_hash, num_args, args); + if (m_set.contains(d)) + return 0; + TRACE("fingerprint_bug", tout << "1) inserting: " << data_hash << " num_args: " << num_args; + for (unsigned i = 0; i < num_args; i++) tout << " " << args[i]->get_owner_id(); + tout << "\n";); + for (unsigned i = 0; i < num_args; i++) + d->m_args[i] = d->m_args[i]->get_root(); + if (m_set.contains(d)) { + TRACE("fingerprint_bug", tout << "failed: " << data_hash << " num_args: " << num_args; + for (unsigned i = 0; i < num_args; i++) tout << " " << d->m_args[i]->get_owner_id(); + tout << "\n";); + return 0; + } + TRACE("fingerprint_bug", tout << "2) inserting: " << data_hash << " num_args: " << num_args; + for (unsigned i = 0; i < num_args; i++) tout << " " << args[i]->get_owner_id(); + tout << "\n";); + fingerprint * f = new (m_region) fingerprint(m_region, data, data_hash, num_args, d->m_args); + m_fingerprints.push_back(f); + m_set.insert(f); + return f; + } + + bool fingerprint_set::contains(void * data, unsigned data_hash, unsigned num_args, enode * const * args) { + fingerprint * d = mk_dummy(data, data_hash, num_args, args); + if (m_set.contains(d)) + return true; + for (unsigned i = 0; i < num_args; i++) + d->m_args[i] = d->m_args[i]->get_root(); + if (m_set.contains(d)) + return true; + return false; + } + + void fingerprint_set::reset() { + m_set.reset(); + m_fingerprints.reset(); + } + + void fingerprint_set::push_scope() { + m_scopes.push_back(m_fingerprints.size()); + } + + void fingerprint_set::pop_scope(unsigned num_scopes) { + unsigned lvl = m_scopes.size(); + SASSERT(num_scopes <= lvl); + unsigned new_lvl = lvl - num_scopes; + unsigned old_size = m_scopes[new_lvl]; + unsigned size = m_fingerprints.size(); + for (unsigned i = old_size; i < size; i++) + m_set.erase(m_fingerprints[i]); + m_fingerprints.shrink(old_size); + m_scopes.shrink(new_lvl); + } + + void fingerprint_set::display(std::ostream & out) const { + out << "fingerprints:\n"; + SASSERT(m_set.size() == m_fingerprints.size()); + ptr_vector::const_iterator it = m_fingerprints.begin(); + ptr_vector::const_iterator end = m_fingerprints.end(); + for (; it != end; ++it) { + fingerprint const * f = *it; + out << f->get_data() << " #" << f->get_data_hash(); + for (unsigned i = 0; i < f->get_num_args(); i++) + out << " #" << f->get_arg(i)->get_owner_id(); + out << "\n"; + } + } + +#ifdef Z3DEBUG + /** + \brief Slow function for checking if there is a fingerprint congruent to (data args[0] ... args[num_args-1]) + */ + bool fingerprint_set::slow_contains(void const * data, unsigned data_hash, unsigned num_args, enode * const * args) const { + ptr_vector::const_iterator it = m_fingerprints.begin(); + ptr_vector::const_iterator end = m_fingerprints.end(); + for (; it != end; ++it) { + fingerprint const * f = *it; + if (f->get_data() != data) + continue; + if (f->get_num_args() != num_args) + continue; + unsigned i = 0; + for (i = 0; i < num_args; i++) + if (f->get_arg(i)->get_root() != args[i]->get_root()) + break; + if (i == num_args) { + TRACE("missing_instance_detail", tout << "found instance data: " << data << "=" << f->get_data() << " hash: " << f->get_data_hash(); + for (unsigned i = 0; i < num_args; i++) { + tout << " " << f->get_arg(i)->get_owner_id() << ":" << f->get_arg(i)->get_root()->get_owner_id() << "=" + << args[i]->get_owner_id() << ":" << args[i]->get_root()->get_owner_id(); + } + tout << "\n";); + return true; + } + } + return false; + } +#endif + + +}; diff --git a/lib/fingerprints.h b/lib/fingerprints.h new file mode 100644 index 000000000..e9045990d --- /dev/null +++ b/lib/fingerprints.h @@ -0,0 +1,85 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + fingerprints.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-02-24. + +Revision History: + +--*/ +#ifndef _FINGERPRINTS_H_ +#define _FINGERPRINTS_H_ + +#include"smt_enode.h" + +namespace smt { + + class fingerprint { + protected: + void * m_data; + unsigned m_data_hash; + unsigned m_num_args; + enode * * m_args; + + friend class fingerprint_set; + fingerprint() {} + public: + fingerprint(region & r, void * d, unsigned d_hash, unsigned n, enode * const * args); + void * get_data() const { return m_data; } + unsigned get_data_hash() const { return m_data_hash; } + unsigned get_num_args() const { return m_num_args; } + enode * const * get_args() const { return m_args; } + enode * get_arg(unsigned idx) const { SASSERT(idx < m_num_args); return m_args[idx]; } + }; + + class fingerprint_set { + + struct fingerprint_khasher { + unsigned operator()(fingerprint const * f) const { return f->get_data_hash(); } + }; + struct fingerprint_chasher { + unsigned operator()(fingerprint const * f, unsigned idx) const { return f->get_arg(idx)->hash(); } + }; + struct fingerprint_hash_proc { + unsigned operator()(fingerprint const * f) const { + return get_composite_hash(const_cast(f), f->get_num_args()); + } + }; + struct fingerprint_eq_proc { bool operator()(fingerprint const * f1, fingerprint const * f2) const; }; + typedef ptr_hashtable set; + + region & m_region; + set m_set; + ptr_vector m_fingerprints; + unsigned_vector m_scopes; + ptr_vector m_tmp; + fingerprint m_dummy; + + fingerprint * mk_dummy(void * data, unsigned data_hash, unsigned num_args, enode * const * args); + + public: + fingerprint_set(region & r):m_region(r) {} + fingerprint * insert(void * data, unsigned data_hash, unsigned num_args, enode * const * args); + unsigned size() const { return m_fingerprints.size(); } + bool contains(void * data, unsigned data_hash, unsigned num_args, enode * const * args); + void reset(); + void push_scope(); + void pop_scope(unsigned num_scopes); + void display(std::ostream & out) const; +#ifdef Z3DEBUG + bool slow_contains(void const * data, unsigned data_hash, unsigned num_args, enode * const * args) const; +#endif + }; +}; + +#endif /* _FINGERPRINTS_H_ */ + diff --git a/lib/fix_dl_var_tactic.cpp b/lib/fix_dl_var_tactic.cpp new file mode 100644 index 000000000..11feff3a0 --- /dev/null +++ b/lib/fix_dl_var_tactic.cpp @@ -0,0 +1,358 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + fix_dl_var_tactic.cpp + +Abstract: + + Fix a difference logic variable to 0. + If the problem is in the difference logic fragment, that is, all arithmetic terms + are of the form (x + k), and the arithmetic atoms are of the + form x - y <= k or x - y = k. Then, we can set one variable to 0. + + This is useful because, many bounds can be exposed after this operation is performed. + +Author: + + Leonardo de Moura (leonardo) 2011-12-19 + +Revision History: + +--*/ +#include"tactical.h" +#include"th_rewriter.h" +#include"extension_model_converter.h" +#include"arith_decl_plugin.h" +#include"expr_substitution.h" +#include"ast_smt2_pp.h" + +class fix_dl_var_tactic : public tactic { + + struct is_target { + struct failed {}; + ast_manager & m; + arith_util & m_util; + expr_fast_mark1 * m_visited; + ptr_vector m_todo; + obj_map m_occs; + obj_map m_non_nested_occs; + + is_target(arith_util & u): + m(u.get_manager()), + m_util(u) { + } + + void throw_failed(expr * ctx1, expr * ctx2 = 0) { + TRACE("fix_dl_var", tout << mk_ismt2_pp(ctx1, m) << "\n"; if (ctx2) tout << mk_ismt2_pp(ctx2, m) << "\n";); + throw failed(); + } + + bool is_arith(expr * n) { + sort * s = m.get_sort(n); + return s->get_family_id() == m_util.get_family_id(); + } + // Return true if n is uninterpreted with respect to arithmetic. + bool is_uninterp(expr * n) { + return is_app(n) && to_app(n)->get_family_id() != m_util.get_family_id(); + } + + // Remark: we say an expression is nested, if it occurs inside the boolean structure of the formula. + // That is, the expression is not part of an unit clause comprising of a single inequality/equality. + + void inc_occ(expr * n, bool nested) { + if (is_uninterp_const(n) && is_arith(n)) { + obj_map::obj_map_entry * entry = m_occs.insert_if_not_there2(to_app(n), 0); + entry->get_data().m_value++; + + if (!nested) { + entry = m_non_nested_occs.insert_if_not_there2(to_app(n), 0); + entry->get_data().m_value++; + } + } + } + + void visit(expr * n, bool nested) { + inc_occ(n, nested); + if (!m_visited->is_marked(n)) { + m_visited->mark(n); + m_todo.push_back(n); + } + } + + void process_app(app * t) { + unsigned num = t->get_num_args(); + for (unsigned i = 0; i < num; i++) + visit(t->get_arg(i), false); + } + + void process_arith_atom(expr * lhs, expr * rhs, bool nested) { + if (is_uninterp(lhs) && is_uninterp(rhs)) { + visit(lhs, nested); + visit(rhs, nested); + return; + } + + if (m_util.is_numeral(lhs)) + std::swap(lhs, rhs); + + if (!m_util.is_numeral(rhs)) + throw_failed(lhs, rhs); + + expr * t, * ms, * s; + // check if lhs is of the form: (+ t (* (- 1) s)) + if (m_util.is_add(lhs, t, ms) && m_util.is_times_minus_one(ms, s) && is_uninterp(t) && is_uninterp(s)) { + visit(t, nested); + visit(s, nested); + } + else { + CTRACE("fix_dl_var", m_util.is_add(lhs, t, ms), + s = 0; + tout << "is_times_minus_one: " << m_util.is_times_minus_one(ms, s) << "\n"; + tout << "is_uninterp(t): " << is_uninterp(t) << "\n"; + tout << "t.family_id(): " << (is_app(t) ? to_app(t)->get_family_id() : -1) << "\n"; + tout << "util.family_id: " << m_util.get_family_id() << "\n"; + if (s) { + tout << "is_uninterp(s): " << is_uninterp(s) << "\n"; + tout << "s.family_id(): " << (is_app(s) ? to_app(s)->get_family_id() : -1) << "\n"; + }); + throw_failed(lhs, rhs); + } + } + + void process_eq(app * t, bool nested) { + if (!is_arith(t->get_arg(0))) { + process_app(t); + return; + } + process_arith_atom(t->get_arg(0), t->get_arg(1), nested); + } + + void process_arith(app * t, bool nested) { + if (m.is_bool(t)) { + process_arith_atom(t->get_arg(0), t->get_arg(1), nested); + return; + } + // check if t is of the form c + k + expr * c, * k; + if (m_util.is_add(t, k, c) && is_uninterp(c) && m_util.is_numeral(k)) { + visit(c, nested); + } + else { + throw_failed(t); + } + } + + void process(expr * n) { + if (m_visited->is_marked(n)) + return; + + while (m.is_not(n, n)) + ; + + if (is_app(n) && to_app(n)->get_family_id() == m_util.get_family_id()) { + process_arith(to_app(n), false); + return; + } + + m_todo.push_back(n); + m_visited->mark(n); + + while (!m_todo.empty()) { + expr * n = m_todo.back(); + m_todo.pop_back(); + + if (!is_app(n)) + throw_failed(n); + + app * t = to_app(n); + + if (m.is_eq(t)) + process_eq(t, true); + else if (t->get_family_id() == m_util.get_family_id()) + process_arith(t, true); + else + process_app(t); + } + } + + app * most_occs(obj_map & occs, unsigned & best) { + app * r = 0; + best = 0; + obj_map::iterator it = occs.begin(); + obj_map::iterator end = occs.end(); + for (; it != end; ++it) { + if (it->m_value > best) { + best = it->m_value; + r = it->m_key; + } + } + return r; + } + + // TODO make it a parameter +#define NESTED_PENALTY 10 + + app * most_occs() { + // We try to choose a variable that when set to 0 will generate many bounded variables. + // That is why we give preference to variables occuring in non-nested inequalities. + unsigned best1, best2; + app * r1, * r2; + r1 = most_occs(m_non_nested_occs, best1); + r2 = most_occs(m_occs, best2); + TRACE("fix_dl_var_choice", + if (r1) { + tout << "r1 occs: " << best1 << "\n"; + tout << mk_ismt2_pp(r1, m) << "\n"; + } + if (r2) { + tout << "r2 occs: " << best2 << "\n"; + tout << mk_ismt2_pp(r2, m) << "\n"; + }); + if (best2 > NESTED_PENALTY * best1) + return r2; + else + return r1; + } + + app * operator()(goal const & g) { + try { + expr_fast_mark1 visited; + m_visited = &visited; + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + process(g.form(i)); + } + return most_occs(); + } + catch (failed) { + return 0; + } + } + }; + + struct imp { + ast_manager & m; + arith_util u; + th_rewriter m_rw; + bool m_produce_models; + + imp(ast_manager & _m, params_ref const & p): + m(_m), + u(m), + m_rw(m, p) { + } + + void updt_params(params_ref const & p) { + m_rw.updt_params(p); + } + + void set_cancel(bool f) { + m_rw.set_cancel(f); + } + + void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + mc = 0; pc = 0; core = 0; + tactic_report report("fix-dl-var", *g); + bool produce_proofs = g->proofs_enabled(); + m_produce_models = g->models_enabled(); + + app * var = is_target(u)(*g); + if (var != 0) { + IF_VERBOSE(TACTIC_VERBOSITY_LVL, verbose_stream() << "(fixing-at-zero " << var->get_decl()->get_name() << ")\n";); + tactic_report report("fix-dl-var", *g); + + expr_substitution subst(m); + app * zero = u.mk_numeral(rational(0), u.is_int(var)); + subst.insert(var, zero); + m_rw.set_substitution(&subst); + + if (m_produce_models) { + extension_model_converter * _mc = alloc(extension_model_converter, m); + _mc->insert(var->get_decl(), zero); + mc = _mc; + } + + expr_ref new_curr(m); + proof_ref new_pr(m); + unsigned size = g->size(); + for (unsigned idx = 0; idx < size; idx++) { + expr * curr = g->form(idx); + m_rw(curr, new_curr, new_pr); + if (produce_proofs) { + proof * pr = g->pr(idx); + new_pr = m.mk_modus_ponens(pr, new_pr); + } + g->update(idx, new_curr, new_pr, g->dep(idx)); + } + g->inc_depth(); + } + result.push_back(g.get()); + TRACE("fix_dl_var", g->display(tout);); + SASSERT(g->is_well_sorted()); + } + }; + + imp * m_imp; + params_ref m_params; +public: + fix_dl_var_tactic(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(fix_dl_var_tactic, m, m_params); + } + + virtual ~fix_dl_var_tactic() { + dealloc(m_imp); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + th_rewriter::get_param_descrs(r); + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + (*m_imp)(in, result, mc, pc, core); + } + + virtual void cleanup() { + ast_manager & m = m_imp->m; + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + } + + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + +tactic * mk_fix_dl_var_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(fix_dl_var_tactic, m, p)); +} diff --git a/lib/fix_dl_var_tactic.h b/lib/fix_dl_var_tactic.h new file mode 100644 index 000000000..c0d6fc4e4 --- /dev/null +++ b/lib/fix_dl_var_tactic.h @@ -0,0 +1,33 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + fix_dl_var_tactic.h + +Abstract: + + Fix a difference logic variable to 0. + If the problem is in the difference logic fragment, that is, all arithmetic terms + are of the form (x + k), and the arithmetic atoms are of the + form x - y <= k or x - y = k. Then, we can set one variable to 0. + + This is useful because, many bounds can be exposed after this operation is performed. + +Author: + + Leonardo (leonardo) 2011-12-29 + +Notes: + +--*/ +#ifndef _FIX_DL_VAR_TACTIC_H_ +#define _FIX_DL_VAR_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_fix_dl_var_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/float_decl_plugin.cpp b/lib/float_decl_plugin.cpp new file mode 100644 index 000000000..7cf8f32bd --- /dev/null +++ b/lib/float_decl_plugin.cpp @@ -0,0 +1,545 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + float_decl_plugin.cpp + +Abstract: + + Floating point decl plugin + +Author: + + Leonardo de Moura (leonardo) 2012-01-15. + +Revision History: + +--*/ +#include"float_decl_plugin.h" +#include"arith_decl_plugin.h" +#include"bv_decl_plugin.h" + +float_decl_plugin::float_decl_plugin(): + m_values(m_fm), + m_value_table(mpf_hash_proc(m_values), mpf_eq_proc(m_values)) { + m_real_sort = 0; + m_int_sort = 0; + m_bv_plugin = 0; +} + +void float_decl_plugin::set_manager(ast_manager * m, family_id id) { + decl_plugin::set_manager(m, id); + + family_id aid = m_manager->get_family_id("arith"); + m_real_sort = m_manager->mk_sort(aid, REAL_SORT); + SASSERT(m_real_sort != 0); // arith_decl_plugin must be installed before float_decl_plugin. + m_manager->inc_ref(m_real_sort); + + m_int_sort = m_manager->mk_sort(aid, INT_SORT); + SASSERT(m_int_sort != 0); // arith_decl_plugin must be installed before float_decl_plugin. + m_manager->inc_ref(m_int_sort); + + if (m_manager->has_plugin(symbol("bv"))) { + // bv plugin is optional, so m_bv_plugin may be 0 + m_bv_fid = m_manager->get_family_id("bv"); + m_bv_plugin = static_cast(m_manager->get_plugin(m_bv_fid)); + } +} + +float_decl_plugin::~float_decl_plugin() { +} + +unsigned float_decl_plugin::mk_id(mpf const & v) { + unsigned new_id = m_id_gen.mk(); + m_values.reserve(new_id+1); + m_fm.set(m_values[new_id], v); + unsigned old_id = m_value_table.insert_if_not_there(new_id); + if (old_id != new_id) { + m_id_gen.recycle(new_id); + m_fm.del(m_values[new_id]); + } + return old_id; +} + +void float_decl_plugin::recycled_id(unsigned id) { + SASSERT(m_value_table.contains(id)); + m_value_table.erase(id); + m_id_gen.recycle(id); + m_fm.del(m_values[id]); +} + +func_decl * float_decl_plugin::mk_value_decl(mpf const & v) { + parameter p(mk_id(v), true); + SASSERT(p.is_external()); + sort * s = mk_float_sort(v.get_ebits(), v.get_sbits()); + return m_manager->mk_const_decl(symbol("float"), s, func_decl_info(m_family_id, OP_FLOAT_VALUE, 1, &p)); +} + +app * float_decl_plugin::mk_value(mpf const & v) { + return m_manager->mk_const(mk_value_decl(v)); +} + +bool float_decl_plugin::is_value(expr * n, mpf & val) { + if (is_app_of(n, m_family_id, OP_FLOAT_VALUE)) { + m_fm.set(val, m_values[to_app(n)->get_decl()->get_parameter(0).get_ext_id()]); + return true; + } + return false; +} + +bool float_decl_plugin::is_rm(expr * n, mpf_rounding_mode & val) { + if (is_app_of(n, m_family_id, OP_RM_NEAREST_TIES_TO_AWAY)) { + val = MPF_ROUND_NEAREST_TAWAY; + return true; + } + else if (is_app_of(n, m_family_id, OP_RM_NEAREST_TIES_TO_EVEN)) { + val = MPF_ROUND_NEAREST_TEVEN; + return true; + } + else if (is_app_of(n, m_family_id, OP_RM_TOWARD_NEGATIVE)) { + val = MPF_ROUND_TOWARD_NEGATIVE; + return true; + } + else if (is_app_of(n, m_family_id, OP_RM_TOWARD_POSITIVE)) { + val = MPF_ROUND_TOWARD_POSITIVE; + return true; + } + else if (is_app_of(n, m_family_id, OP_RM_TOWARD_ZERO)) { + val = MPF_ROUND_TOWARD_ZERO; + return true; + } + + return 0; +} + +void float_decl_plugin::del(parameter const & p) { + SASSERT(p.is_external()); + recycled_id(p.get_ext_id()); +} + +parameter float_decl_plugin::translate(parameter const & p, decl_plugin & target) { + SASSERT(p.is_external()); + float_decl_plugin & _target = static_cast(target); + return parameter(_target.mk_id(m_values[p.get_ext_id()]), true); +} + +void float_decl_plugin::finalize() { + if (m_real_sort) { m_manager->dec_ref(m_real_sort); } + if (m_int_sort) { m_manager->dec_ref(m_int_sort); } +} + +decl_plugin * float_decl_plugin::mk_fresh() { + return alloc(float_decl_plugin); +} + +sort * float_decl_plugin::mk_float_sort(unsigned ebits, unsigned sbits) { + parameter p1(ebits), p2(sbits); + parameter ps[2] = { p1, p2 }; + sort_size sz; + sz = sort_size::mk_very_big(); // TODO: refine + return m_manager->mk_sort(symbol("FP"), sort_info(m_family_id, FLOAT_SORT, sz, 2, ps)); +} + +sort * float_decl_plugin::mk_rm_sort() { + return m_manager->mk_sort(symbol("RoundingMode"), sort_info(m_family_id, ROUNDING_MODE_SORT)); +} + +sort * float_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) { + switch (k) { + case FLOAT_SORT: + if (!(num_parameters == 2 && parameters[0].is_int() && parameters[1].is_int())) { + m_manager->raise_exception("expecting two integer parameters to floating point sort"); + } + return mk_float_sort(parameters[0].get_int(), parameters[1].get_int()); + case ROUNDING_MODE_SORT: + return mk_rm_sort(); + default: + m_manager->raise_exception("unknown floating point theory sort"); + return 0; + } +} + +func_decl * float_decl_plugin::mk_rm_const_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + if (num_parameters != 0) + m_manager->raise_exception("rounding mode constant does not have parameters"); + if (arity != 0) + m_manager->raise_exception("rounding mode is a constant"); + sort * s = mk_rm_sort(); + func_decl_info finfo(m_family_id, k); + switch (k) { + case OP_RM_NEAREST_TIES_TO_EVEN: + return m_manager->mk_const_decl(symbol("roundNearestTiesToEven"), s, finfo); + case OP_RM_NEAREST_TIES_TO_AWAY: + return m_manager->mk_const_decl(symbol("roundNearestTiesToAway"), s, finfo); + case OP_RM_TOWARD_POSITIVE: + return m_manager->mk_const_decl(symbol("roundTowardPositive"), s, finfo); + case OP_RM_TOWARD_NEGATIVE: + return m_manager->mk_const_decl(symbol("roundTowardNegative"), s, finfo); + case OP_RM_TOWARD_ZERO: + return m_manager->mk_const_decl(symbol("roundTowardZero"), s, finfo); + default: + UNREACHABLE(); + return 0; + } +} + +func_decl * float_decl_plugin::mk_float_const_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + sort * s; + if (num_parameters == 1 && parameters[0].is_ast() && is_sort(parameters[0].get_ast()) && is_float_sort(to_sort(parameters[0].get_ast()))) { + s = to_sort(parameters[0].get_ast()); + } + else if (range != 0 && is_float_sort(range)) { + s = range; + } + else { + m_manager->raise_exception("sort of floating point constant was not specified"); + } + + SASSERT(is_sort_of(s, m_family_id, FLOAT_SORT)); + + unsigned ebits = s->get_parameter(0).get_int(); + unsigned sbits = s->get_parameter(1).get_int(); + scoped_mpf val(m_fm); + if (k == OP_FLOAT_NAN) { + m_fm.mk_nan(ebits, sbits, val); + SASSERT(m_fm.is_nan(val)); + } + else if (k == OP_FLOAT_MINUS_INF) { + m_fm.mk_ninf(ebits, sbits, val); + } + else { + SASSERT(k == OP_FLOAT_PLUS_INF); + m_fm.mk_pinf(ebits, sbits, val); + } + return mk_value_decl(val); +} + +func_decl * float_decl_plugin::mk_bin_rel_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + if (arity != 2) + m_manager->raise_exception("invalid number of arguments to floating point relation"); + if (domain[0] != domain[1] || !is_float_sort(domain[0])) + m_manager->raise_exception("sort mismatch"); + symbol name; + switch (k) { + case OP_FLOAT_EQ: name = "=="; break; + case OP_FLOAT_LT: name = "<"; break; + case OP_FLOAT_GT: name = ">"; break; + case OP_FLOAT_LE: name = "<="; break; + case OP_FLOAT_GE: name = ">="; break; + default: + UNREACHABLE(); + break; + } + func_decl_info finfo(m_family_id, k); + finfo.set_chainable(true); + return m_manager->mk_func_decl(name, arity, domain, m_manager->mk_bool_sort(), finfo); +} + +func_decl * float_decl_plugin::mk_unary_rel_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + if (arity != 1) + m_manager->raise_exception("invalid number of arguments to floating point relation"); + if (!is_float_sort(domain[0])) + m_manager->raise_exception("sort mismatch"); + symbol name; + switch (k) { + case OP_FLOAT_IS_ZERO: name = "isZero"; break; + case OP_FLOAT_IS_NZERO: name = "isNZero"; break; + case OP_FLOAT_IS_PZERO: name = "isPZero"; break; + case OP_FLOAT_IS_SIGN_MINUS: name = "isSignMinus"; break; + default: + UNREACHABLE(); + break; + } + return m_manager->mk_func_decl(name, arity, domain, m_manager->mk_bool_sort(), func_decl_info(m_family_id, k)); +} + +func_decl * float_decl_plugin::mk_unary_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + if (arity != 1) + m_manager->raise_exception("invalid number of arguments to floating point operator"); + if (!is_float_sort(domain[0])) + m_manager->raise_exception("sort mismatch"); + symbol name; + switch (k) { + case OP_FLOAT_ABS: name = "abs"; break; + case OP_FLOAT_UMINUS: name = "-"; break; + default: + UNREACHABLE(); + break; + } + return m_manager->mk_func_decl(name, arity, domain, domain[0], func_decl_info(m_family_id, k)); +} + +func_decl * float_decl_plugin::mk_binary_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + if (arity != 2) + m_manager->raise_exception("invalid number of arguments to floating point operator"); + if (domain[0] != domain[1] || !is_float_sort(domain[0])) + m_manager->raise_exception("sort mismatch"); + symbol name; + switch (k) { + case OP_FLOAT_REM: name = "remainder"; break; + case OP_FLOAT_MIN: name = "min"; break; + case OP_FLOAT_MAX: name = "max"; break; + default: + UNREACHABLE(); + break; + } + return m_manager->mk_func_decl(name, arity, domain, domain[0], func_decl_info(m_family_id, k)); +} + +func_decl * float_decl_plugin::mk_rm_binary_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + if (arity != 3) + m_manager->raise_exception("invalid number of arguments to floating point operator"); + if (!is_rm_sort(domain[0]) || domain[1] != domain[2] || !is_float_sort(domain[1])) + m_manager->raise_exception("sort mismatch"); + symbol name; + switch (k) { + case OP_FLOAT_ADD: name = "+"; break; + case OP_FLOAT_SUB: name = "-"; break; + case OP_FLOAT_MUL: name = "*"; break; + case OP_FLOAT_DIV: name = "/"; break; + default: + UNREACHABLE(); + break; + } + return m_manager->mk_func_decl(name, arity, domain, domain[1], func_decl_info(m_family_id, k)); +} + +func_decl * float_decl_plugin::mk_rm_unary_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + if (arity != 2) + m_manager->raise_exception("invalid number of arguments to floating point operator"); + if (!is_rm_sort(domain[0]) || !is_float_sort(domain[1])) + m_manager->raise_exception("sort mismatch"); + symbol name; + switch (k) { + case OP_FLOAT_SQRT: name = "squareRoot"; break; + case OP_FLOAT_ROUND_TO_INTEGRAL: name = "roundToIntegral"; break; + default: + UNREACHABLE(); + break; + } + return m_manager->mk_func_decl(name, arity, domain, domain[1], func_decl_info(m_family_id, k)); +} + +func_decl * float_decl_plugin::mk_fused_ma(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + if (arity != 4) + m_manager->raise_exception("invalid number of arguments to fused_ma operator"); + if (!is_rm_sort(domain[0]) || domain[1] != domain[2] || domain[1] != domain[3] || !is_float_sort(domain[1])) + m_manager->raise_exception("sort mismatch"); + symbol name("fusedMA"); + return m_manager->mk_func_decl(name, arity, domain, domain[1], func_decl_info(m_family_id, k)); +} + +func_decl * float_decl_plugin::mk_to_float(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + if (m_bv_plugin && arity == 3 && + is_sort_of(domain[0], m_bv_fid, BV_SORT) && + is_sort_of(domain[1], m_bv_fid, BV_SORT) && + is_sort_of(domain[2], m_bv_fid, BV_SORT)) { + // When the bv_decl_plugin is installed, then we know how to convert 3 bit-vectors into a float! + sort * fp = mk_float_sort(domain[2]->get_parameter(0).get_int(), domain[1]->get_parameter(0).get_int()+1); + symbol name("asFloat"); + return m_manager->mk_func_decl(name, arity, domain, fp, func_decl_info(m_family_id, k, num_parameters, parameters)); + } + else { + // .. Otherwise we only know how to convert rationals/reals. + if (!(num_parameters == 2 && parameters[0].is_int() && parameters[1].is_int())) + m_manager->raise_exception("expecting two integer parameters to asFloat"); + if (arity != 2 && arity != 3) + m_manager->raise_exception("invalid number of arguments to asFloat operator"); + if (!is_rm_sort(domain[0]) || domain[1] != m_real_sort) + m_manager->raise_exception("sort mismatch"); + if (arity == 2) { + sort * fp = mk_float_sort(parameters[0].get_int(), parameters[1].get_int()); + symbol name("asFloat"); + return m_manager->mk_func_decl(name, arity, domain, fp, func_decl_info(m_family_id, k, num_parameters, parameters)); + } + else { + if (domain[2] != m_int_sort) + m_manager->raise_exception("sort mismatch"); + sort * fp = mk_float_sort(parameters[0].get_int(), parameters[1].get_int()); + symbol name("asFloat"); + return m_manager->mk_func_decl(name, arity, domain, fp, func_decl_info(m_family_id, k, num_parameters, parameters)); + } + } +} + +func_decl * float_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + switch (k) { + case OP_TO_FLOAT: + return mk_to_float(k, num_parameters, parameters, arity, domain, range); + case OP_FLOAT_MINUS_INF: + case OP_FLOAT_PLUS_INF: + case OP_FLOAT_NAN: + return mk_float_const_decl(k, num_parameters, parameters, arity, domain, range); + case OP_RM_NEAREST_TIES_TO_EVEN: + case OP_RM_NEAREST_TIES_TO_AWAY: + case OP_RM_TOWARD_POSITIVE: + case OP_RM_TOWARD_NEGATIVE: + case OP_RM_TOWARD_ZERO: + return mk_rm_const_decl(k, num_parameters, parameters, arity, domain, range); + case OP_FLOAT_EQ: + case OP_FLOAT_LT: + case OP_FLOAT_GT: + case OP_FLOAT_LE: + case OP_FLOAT_GE: + return mk_bin_rel_decl(k, num_parameters, parameters, arity, domain, range); + case OP_FLOAT_IS_ZERO: + case OP_FLOAT_IS_NZERO: + case OP_FLOAT_IS_PZERO: + case OP_FLOAT_IS_SIGN_MINUS: + return mk_unary_rel_decl(k, num_parameters, parameters, arity, domain, range); + case OP_FLOAT_ABS: + case OP_FLOAT_UMINUS: + return mk_unary_decl(k, num_parameters, parameters, arity, domain, range); + case OP_FLOAT_REM: + case OP_FLOAT_MIN: + case OP_FLOAT_MAX: + return mk_binary_decl(k, num_parameters, parameters, arity, domain, range); + case OP_FLOAT_ADD: + case OP_FLOAT_MUL: + case OP_FLOAT_DIV: + return mk_rm_binary_decl(k, num_parameters, parameters, arity, domain, range); + case OP_FLOAT_SUB: + if (arity == 1) + return mk_unary_decl(OP_FLOAT_UMINUS, num_parameters, parameters, arity, domain, range); + else + return mk_rm_binary_decl(k, num_parameters, parameters, arity, domain, range); + case OP_FLOAT_SQRT: + case OP_FLOAT_ROUND_TO_INTEGRAL: + return mk_rm_unary_decl(k, num_parameters, parameters, arity, domain, range); + case OP_FLOAT_FUSED_MA: + return mk_fused_ma(k, num_parameters, parameters, arity, domain, range); + default: + m_manager->raise_exception("unsupported floating point operator"); + return 0; + } +} + +void float_decl_plugin::get_op_names(svector & op_names, symbol const & logic) { + op_names.push_back(builtin_name("plusInfinity", OP_FLOAT_PLUS_INF)); + op_names.push_back(builtin_name("minusInfinity", OP_FLOAT_MINUS_INF)); + op_names.push_back(builtin_name("NaN", OP_FLOAT_NAN)); + op_names.push_back(builtin_name("roundNearestTiesToEven", OP_RM_NEAREST_TIES_TO_EVEN)); + op_names.push_back(builtin_name("roundNearestTiesToAway", OP_RM_NEAREST_TIES_TO_AWAY)); + op_names.push_back(builtin_name("roundTowardPositive", OP_RM_TOWARD_POSITIVE)); + op_names.push_back(builtin_name("roundTowardNegative", OP_RM_TOWARD_NEGATIVE)); + op_names.push_back(builtin_name("roundTowardZero", OP_RM_TOWARD_ZERO)); + + op_names.push_back(builtin_name("+", OP_FLOAT_ADD)); + op_names.push_back(builtin_name("-", OP_FLOAT_SUB)); + op_names.push_back(builtin_name("/", OP_FLOAT_DIV)); + op_names.push_back(builtin_name("*", OP_FLOAT_MUL)); + + op_names.push_back(builtin_name("abs", OP_FLOAT_ABS)); + op_names.push_back(builtin_name("remainder", OP_FLOAT_REM)); + op_names.push_back(builtin_name("fusedMA", OP_FLOAT_FUSED_MA)); + op_names.push_back(builtin_name("squareRoot", OP_FLOAT_SQRT)); + op_names.push_back(builtin_name("roundToIntegral", OP_FLOAT_ROUND_TO_INTEGRAL)); + + op_names.push_back(builtin_name("==", OP_FLOAT_EQ)); + + op_names.push_back(builtin_name("<", OP_FLOAT_LT)); + op_names.push_back(builtin_name(">", OP_FLOAT_GT)); + op_names.push_back(builtin_name("<=", OP_FLOAT_LE)); + op_names.push_back(builtin_name(">=", OP_FLOAT_GE)); + + op_names.push_back(builtin_name("isZero", OP_FLOAT_IS_ZERO)); + op_names.push_back(builtin_name("isNZero", OP_FLOAT_IS_NZERO)); + op_names.push_back(builtin_name("isPZero", OP_FLOAT_IS_PZERO)); + op_names.push_back(builtin_name("isSignMinus", OP_FLOAT_IS_SIGN_MINUS)); + + op_names.push_back(builtin_name("min", OP_FLOAT_MIN)); + op_names.push_back(builtin_name("max", OP_FLOAT_MAX)); + + op_names.push_back(builtin_name("asFloat", OP_TO_FLOAT)); +} + +void float_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { + sort_names.push_back(builtin_name("FP", FLOAT_SORT)); + sort_names.push_back(builtin_name("RoundingMode", ROUNDING_MODE_SORT)); +} + +bool float_decl_plugin::is_value(app * e) const { + if (e->get_family_id() != m_family_id) + return false; + switch (e->get_decl_kind()) { + case OP_RM_NEAREST_TIES_TO_EVEN: + case OP_RM_NEAREST_TIES_TO_AWAY: + case OP_RM_TOWARD_POSITIVE: + case OP_RM_TOWARD_NEGATIVE: + case OP_RM_TOWARD_ZERO: + case OP_FLOAT_VALUE: + return true; + case OP_TO_FLOAT: + return m_manager->is_value(e->get_arg(0)); + default: + return false; + } +} + +float_util::float_util(ast_manager & m): + m_manager(m), + m_fid(m.get_family_id("float")), + m_a_util(m) { + m_plugin = static_cast(m.get_plugin(m_fid)); +} + +float_util::~float_util() { +} + +sort * float_util::mk_float_sort(unsigned ebits, unsigned sbits) { + parameter ps[2] = { parameter(ebits), parameter(sbits) }; + return m().mk_sort(m_fid, FLOAT_SORT, 2, ps); +} + +unsigned float_util::get_ebits(sort * s) { + SASSERT(is_float(s)); + return static_cast(s->get_parameter(0).get_int()); +} + +unsigned float_util::get_sbits(sort * s) { + SASSERT(is_float(s)); + return static_cast(s->get_parameter(1).get_int()); +} + +app * float_util::mk_nan(unsigned ebits, unsigned sbits) { + scoped_mpf v(fm()); + fm().mk_nan(ebits, sbits, v); + return mk_value(v); +} + +app * float_util::mk_plus_inf(unsigned ebits, unsigned sbits) { + scoped_mpf v(fm()); + fm().mk_pinf(ebits, sbits, v); + return mk_value(v); +} + +app * float_util::mk_minus_inf(unsigned ebits, unsigned sbits) { + scoped_mpf v(fm()); + fm().mk_ninf(ebits, sbits, v); + return mk_value(v); +} + +app * float_util::mk_pzero(unsigned ebits, unsigned sbits) { + scoped_mpf v(fm()); + fm().mk_pzero(ebits, sbits, v); + return mk_value(v); +} + +app * float_util::mk_nzero(unsigned ebits, unsigned sbits) { + scoped_mpf v(fm()); + fm().mk_nzero(ebits, sbits, v); + return mk_value(v); +} + diff --git a/lib/float_decl_plugin.h b/lib/float_decl_plugin.h new file mode 100644 index 000000000..416275306 --- /dev/null +++ b/lib/float_decl_plugin.h @@ -0,0 +1,243 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + float_decl_plugin.h + +Abstract: + + Floating point decl plugin + +Author: + + Leonardo de Moura (leonardo) 2012-01-15. + +Revision History: + +--*/ +#ifndef _FLOAT_DECL_PLUGIN_H_ +#define _FLOAT_DECL_PLUGIN_H_ + +#include"ast.h" +#include"id_gen.h" +#include"arith_decl_plugin.h" +#include"bv_decl_plugin.h" +#include"mpf.h" + +enum float_sort_kind { + FLOAT_SORT, + ROUNDING_MODE_SORT +}; + +enum float_op_kind { + OP_RM_NEAREST_TIES_TO_EVEN, + OP_RM_NEAREST_TIES_TO_AWAY, + OP_RM_TOWARD_POSITIVE, + OP_RM_TOWARD_NEGATIVE, + OP_RM_TOWARD_ZERO, + + + OP_FLOAT_VALUE, + OP_FLOAT_PLUS_INF, + OP_FLOAT_MINUS_INF, + OP_FLOAT_NAN, + + OP_FLOAT_ADD, + OP_FLOAT_SUB, + OP_FLOAT_UMINUS, + OP_FLOAT_MUL, + OP_FLOAT_DIV, + OP_FLOAT_REM, + OP_FLOAT_ABS, + OP_FLOAT_MIN, + OP_FLOAT_MAX, + OP_FLOAT_FUSED_MA, // x*y + z + OP_FLOAT_SQRT, + OP_FLOAT_ROUND_TO_INTEGRAL, + + OP_FLOAT_EQ, + OP_FLOAT_LT, + OP_FLOAT_GT, + OP_FLOAT_LE, + OP_FLOAT_GE, + OP_FLOAT_IS_ZERO, + OP_FLOAT_IS_NZERO, + OP_FLOAT_IS_PZERO, + OP_FLOAT_IS_SIGN_MINUS, + + OP_TO_FLOAT, + + LAST_FLOAT_OP +}; + +class float_decl_plugin : public decl_plugin { + struct mpf_hash_proc { + scoped_mpf_vector const & m_values; + mpf_hash_proc(scoped_mpf_vector const & values):m_values(values) {} + unsigned operator()(unsigned id) const { return m_values.m().hash(m_values[id]); } + }; + + struct mpf_eq_proc { + scoped_mpf_vector const & m_values; + mpf_eq_proc(scoped_mpf_vector const & values):m_values(values) {} + bool operator()(unsigned id1, unsigned id2) const { return m_values.m().eq_core(m_values[id1], m_values[id2]); } + }; + + typedef chashtable value_table; + + + mpf_manager m_fm; + id_gen m_id_gen; + scoped_mpf_vector m_values; + value_table m_value_table; + sort * m_real_sort; + sort * m_int_sort; + family_id m_bv_fid; + bv_decl_plugin * m_bv_plugin; + + sort * mk_float_sort(unsigned ebits, unsigned sbits); + sort * mk_rm_sort(); + func_decl * mk_rm_const_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + func_decl * mk_float_const_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + func_decl * mk_bin_rel_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + func_decl * mk_unary_rel_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + func_decl * mk_unary_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + func_decl * mk_binary_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + func_decl * mk_rm_binary_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + func_decl * mk_rm_unary_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + func_decl * mk_fused_ma(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + func_decl * mk_to_float(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + + virtual void set_manager(ast_manager * m, family_id id); + unsigned mk_id(mpf const & v); + void recycled_id(unsigned id); +public: + float_decl_plugin(); + + bool is_float_sort(sort * s) const { return is_sort_of(s, m_family_id, FLOAT_SORT); } + bool is_rm_sort(sort * s) const { return is_sort_of(s, m_family_id, ROUNDING_MODE_SORT); } + + virtual ~float_decl_plugin(); + virtual void finalize(); + + virtual decl_plugin * mk_fresh(); + virtual sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters); + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + virtual void get_op_names(svector & op_names, symbol const & logic); + virtual void get_sort_names(svector & sort_names, symbol const & logic); + virtual bool is_value(app* e) const; + + mpf_manager & fm() { return m_fm; } + func_decl * mk_value_decl(mpf const & v); + app * mk_value(mpf const & v); + bool is_value(expr * n) { return is_app_of(n, m_family_id, OP_FLOAT_VALUE); } + bool is_value(expr * n, mpf & val); + bool is_rm(expr * n, mpf_rounding_mode & val); + + mpf const & get_value(unsigned id) const { + SASSERT(m_value_table.contains(id)); + return m_values[id]; + } + + virtual void del(parameter const & p); + virtual parameter translate(parameter const & p, decl_plugin & target); +}; + +class float_util { + ast_manager & m_manager; + float_decl_plugin * m_plugin; + family_id m_fid; + arith_util m_a_util; +public: + float_util(ast_manager & m); + ~float_util(); + + ast_manager & m() const { return m_manager; } + mpf_manager & fm() const { return m_plugin->fm(); } + family_id get_fid() const { return m_fid; } + family_id get_family_id() const { return m_fid; } + arith_util & au() { return m_a_util; } + + sort * mk_float_sort(unsigned ebits, unsigned sbits); + sort * mk_rm_sort() { return m().mk_sort(m_fid, ROUNDING_MODE_SORT); } + bool is_float(sort * s) { return is_sort_of(s, m_fid, FLOAT_SORT); } + bool is_rm(sort * s) { return is_sort_of(s, m_fid, ROUNDING_MODE_SORT); } + unsigned get_ebits(sort * s); + unsigned get_sbits(sort * s); + + app * mk_round_nearest_ties_to_even() { return m().mk_const(m_fid, OP_RM_NEAREST_TIES_TO_EVEN); } + app * mk_round_nearest_ties_to_away() { return m().mk_const(m_fid, OP_RM_NEAREST_TIES_TO_AWAY); } + app * mk_round_toward_positive() { return m().mk_const(m_fid, OP_RM_TOWARD_POSITIVE); } + app * mk_round_toward_negative() { return m().mk_const(m_fid, OP_RM_TOWARD_NEGATIVE); } + app * mk_round_toward_zero() { return m().mk_const(m_fid, OP_RM_TOWARD_ZERO); } + + app * mk_nan(unsigned ebits, unsigned sbits); + app * mk_plus_inf(unsigned ebits, unsigned sbits); + app * mk_minus_inf(unsigned ebits, unsigned sbits); + app * mk_nan(sort * s) { return mk_nan(get_ebits(s), get_sbits(s)); } + app * mk_plus_inf(sort * s) { return mk_plus_inf(get_ebits(s), get_sbits(s)); } + app * mk_minus_inf(sort * s) { return mk_minus_inf(get_ebits(s), get_sbits(s)); } + + app * mk_value(mpf const & v) { return m_plugin->mk_value(v); } + bool is_value(expr * n) { return m_plugin->is_value(n); } + bool is_value(expr * n, mpf & v) { return m_plugin->is_value(n, v); } + bool is_rm(expr * n, mpf_rounding_mode & v) { return m_plugin->is_rm(n, v); } + + app * mk_pzero(unsigned ebits, unsigned sbits); + app * mk_nzero(unsigned ebits, unsigned sbits); + app * mk_pzero(sort * s) { return mk_pzero(get_ebits(s), get_sbits(s)); } + app * mk_nzero(sort * s) { return mk_nzero(get_ebits(s), get_sbits(s)); } + + bool is_nan(expr * n) { scoped_mpf v(fm()); return is_value(n, v) && fm().is_nan(v); } + bool is_plus_inf(expr * n) { scoped_mpf v(fm()); return is_value(n, v) && fm().is_pinf(v); } + bool is_minus_inf(expr * n) { scoped_mpf v(fm()); return is_value(n, v) && fm().is_ninf(v); } + bool is_zero(expr * n) { scoped_mpf v(fm()); return is_value(n, v) && fm().is_zero(v); } + bool is_pzero(expr * n) { scoped_mpf v(fm()); return is_value(n, v) && fm().is_pzero(v); } + bool is_nzero(expr * n) { scoped_mpf v(fm()); return is_value(n, v) && fm().is_nzero(v); } + + bool is_to_float(expr * n) { return is_app_of(n, m_fid, OP_TO_FLOAT); } + + app * mk_to_float(expr * arg1, expr * arg2) { return m().mk_app(m_fid, OP_TO_FLOAT, arg1, arg2); } + app * mk_add(expr * arg1, expr * arg2, expr * arg3) { return m().mk_app(m_fid, OP_FLOAT_ADD, arg1, arg2, arg3); } + app * mk_mul(expr * arg1, expr * arg2, expr * arg3) { return m().mk_app(m_fid, OP_FLOAT_MUL, arg1, arg2, arg3); } + app * mk_sub(expr * arg1, expr * arg2, expr * arg3) { return m().mk_app(m_fid, OP_FLOAT_SUB, arg1, arg2, arg3); } + app * mk_div(expr * arg1, expr * arg2, expr * arg3) { return m().mk_app(m_fid, OP_FLOAT_DIV, arg1, arg2, arg3); } + app * mk_uminus(expr * arg1) { return m().mk_app(m_fid, OP_FLOAT_UMINUS, arg1); } + app * mk_rem(expr * arg1, expr * arg2) { return m().mk_app(m_fid, OP_FLOAT_REM, arg1, arg2); } + app * mk_max(expr * arg1, expr * arg2) { return m().mk_app(m_fid, OP_FLOAT_MAX, arg1, arg2); } + app * mk_min(expr * arg1, expr * arg2) { return m().mk_app(m_fid, OP_FLOAT_MIN, arg1, arg2); } + app * mk_abs(expr * arg1, expr * arg2) { return m().mk_app(m_fid, OP_FLOAT_ABS, arg1, arg2); } + app * mk_sqrt(expr * arg1, expr * arg2) { return m().mk_app(m_fid, OP_FLOAT_SQRT, arg1, arg2); } + app * mk_round(expr * arg1, expr * arg2) { return m().mk_app(m_fid, OP_FLOAT_ROUND_TO_INTEGRAL, arg1, arg2); } + app * mk_fused_ma(expr * arg1, expr * arg2, expr * arg3, expr * arg4) { + expr * args[4] = { arg1, arg2, arg3, arg4 }; + return m().mk_app(m_fid, OP_FLOAT_FUSED_MA, 4, args); + } + + app * mk_float_eq(expr * arg1, expr * arg2) { return m().mk_app(m_fid, OP_FLOAT_EQ, arg1, arg2); } + app * mk_lt(expr * arg1, expr * arg2) { return m().mk_app(m_fid, OP_FLOAT_LT, arg1, arg2); } + app * mk_gt(expr * arg1, expr * arg2) { return m().mk_app(m_fid, OP_FLOAT_GT, arg1, arg2); } + app * mk_le(expr * arg1, expr * arg2) { return m().mk_app(m_fid, OP_FLOAT_LE, arg1, arg2); } + app * mk_ge(expr * arg1, expr * arg2) { return m().mk_app(m_fid, OP_FLOAT_GE, arg1, arg2); } + + app * mk_is_zero(expr * arg1) { return m().mk_app(m_fid, OP_FLOAT_IS_ZERO, arg1); } + app * mk_is_nzero(expr * arg1) { return m().mk_app(m_fid, OP_FLOAT_IS_NZERO, arg1); } + app * mk_is_pzero(expr * arg1) { return m().mk_app(m_fid, OP_FLOAT_IS_PZERO, arg1); } + app * mk_is_sign_minus(expr * arg1) { return m().mk_app(m_fid, OP_FLOAT_IS_SIGN_MINUS, arg1); } + + bool is_uminus(expr * a) { return is_app_of(a, m_fid, OP_FLOAT_UMINUS); } +}; + +#endif diff --git a/lib/float_rewriter.cpp b/lib/float_rewriter.cpp new file mode 100644 index 000000000..ad0709423 --- /dev/null +++ b/lib/float_rewriter.cpp @@ -0,0 +1,441 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + float_rewriter.cpp + +Abstract: + + Basic rewriting rules for floating point numbers. + +Author: + + Leonardo (leonardo) 2012-02-02 + +Notes: + +--*/ +#include"float_rewriter.h" + +float_rewriter::float_rewriter(ast_manager & m, params_ref const & p): + m_util(m) { + updt_params(p); +} + +float_rewriter::~float_rewriter() { +} + +void float_rewriter::updt_params(params_ref const & p) { +} + +void float_rewriter::get_param_descrs(param_descrs & r) { +} + +br_status float_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + br_status st = BR_FAILED; + SASSERT(f->get_family_id() == get_fid()); + switch (f->get_decl_kind()) { + case OP_TO_FLOAT: st = mk_to_float(f, num_args, args, result); break; + case OP_FLOAT_ADD: SASSERT(num_args == 3); st = mk_add(args[0], args[1], args[2], result); break; + case OP_FLOAT_SUB: SASSERT(num_args == 3); st = mk_sub(args[0], args[1], args[2], result); break; + case OP_FLOAT_UMINUS: SASSERT(num_args == 1); st = mk_uminus(args[0], result); break; + case OP_FLOAT_MUL: SASSERT(num_args == 3); st = mk_mul(args[0], args[1], args[2], result); break; + case OP_FLOAT_DIV: SASSERT(num_args == 3); st = mk_div(args[0], args[1], args[2], result); break; + case OP_FLOAT_REM: SASSERT(num_args == 2); st = mk_rem(args[0], args[1], result); break; + case OP_FLOAT_ABS: SASSERT(num_args == 1); st = mk_abs(args[0], result); break; + case OP_FLOAT_MIN: SASSERT(num_args == 2); st = mk_min(args[0], args[1], result); break; + case OP_FLOAT_MAX: SASSERT(num_args == 2); st = mk_max(args[0], args[1], result); break; + case OP_FLOAT_FUSED_MA: SASSERT(num_args == 4); st = mk_fused_ma(args[0], args[1], args[2], args[3], result); break; + case OP_FLOAT_SQRT: SASSERT(num_args == 2); st = mk_sqrt(args[0], args[1], result); break; + case OP_FLOAT_ROUND_TO_INTEGRAL: SASSERT(num_args == 2); st = mk_round(args[0], args[1], result); break; + + case OP_FLOAT_EQ: SASSERT(num_args == 2); st = mk_float_eq(args[0], args[1], result); break; + case OP_FLOAT_LT: SASSERT(num_args == 2); st = mk_lt(args[0], args[1], result); break; + case OP_FLOAT_GT: SASSERT(num_args == 2); st = mk_gt(args[0], args[1], result); break; + case OP_FLOAT_LE: SASSERT(num_args == 2); st = mk_le(args[0], args[1], result); break; + case OP_FLOAT_GE: SASSERT(num_args == 2); st = mk_ge(args[0], args[1], result); break; + case OP_FLOAT_IS_ZERO: SASSERT(num_args == 1); st = mk_is_zero(args[0], result); break; + case OP_FLOAT_IS_NZERO: SASSERT(num_args == 1); st = mk_is_nzero(args[0], result); break; + case OP_FLOAT_IS_PZERO: SASSERT(num_args == 1); st = mk_is_pzero(args[0], result); break; + case OP_FLOAT_IS_SIGN_MINUS: SASSERT(num_args == 1); st = mk_is_sign_minus(args[0], result); break; + } + return st; +} + +br_status float_rewriter::mk_to_float(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(f->get_num_parameters() == 2); + SASSERT(f->get_parameter(0).is_int()); + SASSERT(f->get_parameter(1).is_int()); + unsigned ebits = f->get_parameter(0).get_int(); + unsigned sbits = f->get_parameter(1).get_int(); + + if (num_args == 2) { + mpf_rounding_mode rm; + if (!m_util.is_rm(args[0], rm)) + return BR_FAILED; + + rational q; + if (!m_util.au().is_numeral(args[1], q)) + return BR_FAILED; + + mpf v; + m_util.fm().set(v, ebits, sbits, rm, q.to_mpq()); + result = m_util.mk_value(v); + m_util.fm().del(v); + return BR_DONE; + } + else if (num_args == 3 && + m_util.is_rm(m().get_sort(args[0])) && + m_util.au().is_real(args[1]) && + m_util.au().is_int(args[2])) { + + mpf_rounding_mode rm; + if (!m_util.is_rm(args[0], rm)) + return BR_FAILED; + + rational q; + if (!m_util.au().is_numeral(args[1], q)) + return BR_FAILED; + + rational e; + if (!m_util.au().is_numeral(args[2], e)) + return BR_FAILED; + + TRACE("fp_rewriter", tout << "q: " << q << ", e: " << e << "\n";); + + mpf v; + m_util.fm().set(v, ebits, sbits, rm, q.to_mpq(), e.to_mpq().numerator()); + result = m_util.mk_value(v); + m_util.fm().del(v); + return BR_DONE; + } + else { + return BR_FAILED; + } +} + +br_status float_rewriter::mk_add(expr * arg1, expr * arg2, expr * arg3, expr_ref & result) { + mpf_rounding_mode rm; + if (m_util.is_rm(arg1, rm)) { + scoped_mpf v2(m_util.fm()), v3(m_util.fm()); + if (m_util.is_value(arg2, v2) && m_util.is_value(arg3, v3)) { + scoped_mpf t(m_util.fm()); + m_util.fm().add(rm, v2, v3, t); + result = m_util.mk_value(t); + return BR_DONE; + } + } + + return BR_FAILED; +} + +br_status float_rewriter::mk_sub(expr * arg1, expr * arg2, expr * arg3, expr_ref & result) { + // a - b = a + (-b) + result = m_util.mk_add(arg1, arg2, m_util.mk_uminus(arg3)); + return BR_REWRITE2; +} + +br_status float_rewriter::mk_mul(expr * arg1, expr * arg2, expr * arg3, expr_ref & result) { + mpf_rounding_mode rm; + if (m_util.is_rm(arg1, rm)) { + scoped_mpf v2(m_util.fm()), v3(m_util.fm()); + if (m_util.is_value(arg2, v2) && m_util.is_value(arg3, v3)) { + scoped_mpf t(m_util.fm()); + m_util.fm().mul(rm, v2, v3, t); + result = m_util.mk_value(t); + return BR_DONE; + } + } + + return BR_FAILED; +} + +br_status float_rewriter::mk_div(expr * arg1, expr * arg2, expr * arg3, expr_ref & result) { + mpf_rounding_mode rm; + if (m_util.is_rm(arg1, rm)) { + scoped_mpf v2(m_util.fm()), v3(m_util.fm()); + if (m_util.is_value(arg2, v2) && m_util.is_value(arg3, v3)) { + scoped_mpf t(m_util.fm()); + m_util.fm().div(rm, v2, v3, t); + result = m_util.mk_value(t); + return BR_DONE; + } + } + + return BR_FAILED; +} + +br_status float_rewriter::mk_uminus(expr * arg1, expr_ref & result) { + if (m_util.is_nan(arg1)) { + // -nan --> nan + result = arg1; + return BR_DONE; + } + if (m_util.is_plus_inf(arg1)) { + // - +oo --> -oo + result = m_util.mk_minus_inf(m().get_sort(arg1)); + return BR_DONE; + } + if (m_util.is_minus_inf(arg1)) { + // - -oo -> +oo + result = m_util.mk_plus_inf(m().get_sort(arg1)); + return BR_DONE; + } + if (m_util.is_uminus(arg1)) { + // - - a --> a + result = to_app(arg1)->get_arg(0); + return BR_DONE; + } + + scoped_mpf v1(m_util.fm()); + if (m_util.is_value(arg1, v1)) { + m_util.fm().neg(v1); + result = m_util.mk_value(v1); + return BR_DONE; + } + + // TODO: more simplifications + return BR_FAILED; +} + +br_status float_rewriter::mk_rem(expr * arg1, expr * arg2, expr_ref & result) { + scoped_mpf v1(m_util.fm()), v2(m_util.fm()); + if (m_util.is_value(arg1, v1) && m_util.is_value(arg2, v2)) { + scoped_mpf t(m_util.fm()); + m_util.fm().rem(v1, v2, t); + result = m_util.mk_value(t); + return BR_DONE; + } + + return BR_FAILED; +} + +br_status float_rewriter::mk_abs(expr * arg1, expr_ref & result) { + if (m_util.is_nan(arg1)) { + result = arg1; + return BR_DONE; + } + sort * s = m().get_sort(arg1); + result = m().mk_ite(m_util.mk_lt(arg1, m_util.mk_pzero(s)), + m_util.mk_uminus(arg1), + arg1); + return BR_REWRITE2; +} + +br_status float_rewriter::mk_min(expr * arg1, expr * arg2, expr_ref & result) { + if (m_util.is_nan(arg1)) { + result = arg2; + return BR_DONE; + } + if (m_util.is_nan(arg2)) { + result = arg1; + return BR_DONE; + } + // expand as using ite's + result = m().mk_ite(mk_eq_nan(arg1), + arg2, + m().mk_ite(mk_eq_nan(arg2), + arg1, + m().mk_ite(m_util.mk_lt(arg1, arg2), + arg1, + arg2))); + return BR_REWRITE_FULL; +} + +br_status float_rewriter::mk_max(expr * arg1, expr * arg2, expr_ref & result) { + if (m_util.is_nan(arg1)) { + result = arg2; + return BR_DONE; + } + if (m_util.is_nan(arg2)) { + result = arg1; + return BR_DONE; + } + // expand as using ite's + result = m().mk_ite(mk_eq_nan(arg1), + arg2, + m().mk_ite(mk_eq_nan(arg2), + arg1, + m().mk_ite(m_util.mk_gt(arg1, arg2), + arg1, + arg2))); + return BR_REWRITE_FULL; +} + +br_status float_rewriter::mk_fused_ma(expr * arg1, expr * arg2, expr * arg3, expr * arg4, expr_ref & result) { + mpf_rounding_mode rm; + if (m_util.is_rm(arg1, rm)) { + scoped_mpf v2(m_util.fm()), v3(m_util.fm()), v4(m_util.fm()); + if (m_util.is_value(arg2, v2) && m_util.is_value(arg3, v3) && m_util.is_value(arg4, v4)) { + scoped_mpf t(m_util.fm()); + m_util.fm().fused_mul_add(rm, v2, v3, v4, t); + result = m_util.mk_value(t); + return BR_DONE; + } + } + + return BR_FAILED; +} + +br_status float_rewriter::mk_sqrt(expr * arg1, expr * arg2, expr_ref & result) { + mpf_rounding_mode rm; + if (m_util.is_rm(arg1, rm)) { + scoped_mpf v2(m_util.fm()); + if (m_util.is_value(arg2, v2)) { + scoped_mpf t(m_util.fm()); + m_util.fm().sqrt(rm, v2, t); + result = m_util.mk_value(t); + return BR_DONE; + } + } + + return BR_FAILED; +} + +br_status float_rewriter::mk_round(expr * arg1, expr * arg2, expr_ref & result) { + mpf_rounding_mode rm; + if (m_util.is_rm(arg1, rm)) { + scoped_mpf v2(m_util.fm()); + if (m_util.is_value(arg2, v2)) { + scoped_mpf t(m_util.fm()); + m_util.fm().round_to_integral(rm, v2, t); + result = m_util.mk_value(t); + return BR_DONE; + } + } + + return BR_FAILED; +} + +// This the floating point theory == +br_status float_rewriter::mk_float_eq(expr * arg1, expr * arg2, expr_ref & result) { + scoped_mpf v1(m_util.fm()), v2(m_util.fm()); + if (m_util.is_value(arg1, v1) && m_util.is_value(arg2, v2)) { + result = (m_util.fm().eq(v1, v2)) ? m().mk_true() : m().mk_false(); + return BR_DONE; + } + + return BR_FAILED; +} + +// Return (= arg NaN) +app * float_rewriter::mk_eq_nan(expr * arg) { + return m().mk_eq(arg, m_util.mk_nan(m().get_sort(arg))); +} + +// Return (not (= arg NaN)) +app * float_rewriter::mk_neq_nan(expr * arg) { + return m().mk_not(mk_eq_nan(arg)); +} + +br_status float_rewriter::mk_lt(expr * arg1, expr * arg2, expr_ref & result) { + if (m_util.is_nan(arg1) || m_util.is_nan(arg2)) { + result = m().mk_false(); + return BR_DONE; + } + if (m_util.is_minus_inf(arg1)) { + // -oo < arg2 --> not(arg2 = -oo) and not(arg2 = NaN) + result = m().mk_and(m().mk_not(m().mk_eq(arg2, arg1)), mk_neq_nan(arg2)); + return BR_REWRITE2; + } + if (m_util.is_minus_inf(arg2)) { + // arg1 < -oo --> false + result = m().mk_false(); + return BR_DONE; + } + if (m_util.is_plus_inf(arg1)) { + // +oo < arg2 --> false + result = m().mk_false(); + return BR_DONE; + } + if (m_util.is_plus_inf(arg2)) { + // arg1 < +oo --> not(arg1 = +oo) and not(arg1 = NaN) + result = m().mk_and(m().mk_not(m().mk_eq(arg1, arg2)), mk_neq_nan(arg1)); + return BR_REWRITE2; + } + + scoped_mpf v1(m_util.fm()), v2(m_util.fm()); + if (m_util.is_value(arg1, v1) && m_util.is_value(arg2, v2)) { + result = (m_util.fm().lt(v1, v2)) ? m().mk_true() : m().mk_false(); + return BR_DONE; + } + + // TODO: more simplifications + return BR_FAILED; +} + +br_status float_rewriter::mk_gt(expr * arg1, expr * arg2, expr_ref & result) { + result = m_util.mk_lt(arg2, arg1); + return BR_REWRITE1; +} + +br_status float_rewriter::mk_le(expr * arg1, expr * arg2, expr_ref & result) { + if (m_util.is_nan(arg1) || m_util.is_nan(arg2)) { + result = m().mk_false(); + return BR_DONE; + } + scoped_mpf v1(m_util.fm()), v2(m_util.fm()); + if (m_util.is_value(arg1, v1) && m_util.is_value(arg2, v2)) { + result = (m_util.fm().le(v1, v2)) ? m().mk_true() : m().mk_false(); + return BR_DONE; + } + + return BR_FAILED; +} + +br_status float_rewriter::mk_ge(expr * arg1, expr * arg2, expr_ref & result) { + result = m_util.mk_le(arg2, arg1); + return BR_REWRITE1; +} + +br_status float_rewriter::mk_is_zero(expr * arg1, expr_ref & result) { + scoped_mpf v(m_util.fm()); + if (m_util.is_value(arg1, v)) { + result = (m_util.fm().is_zero(v)) ? m().mk_true() : m().mk_false(); + return BR_DONE; + } + + return BR_FAILED; +} + +br_status float_rewriter::mk_is_nzero(expr * arg1, expr_ref & result) { + scoped_mpf v(m_util.fm()); + if (m_util.is_value(arg1, v)) { + result = (m_util.fm().is_nzero(v)) ? m().mk_true() : m().mk_false(); + return BR_DONE; + } + + return BR_FAILED; +} + +br_status float_rewriter::mk_is_pzero(expr * arg1, expr_ref & result) { + scoped_mpf v(m_util.fm()); + if (m_util.is_value(arg1, v)) { + result = (m_util.fm().is_pzero(v)) ? m().mk_true() : m().mk_false(); + return BR_DONE; + } + + return BR_FAILED; +} + +br_status float_rewriter::mk_is_sign_minus(expr * arg1, expr_ref & result) { + scoped_mpf v(m_util.fm()); + if (m_util.is_value(arg1, v)) { + result = (m_util.fm().is_neg(v)) ? m().mk_true() : m().mk_false(); + return BR_DONE; + } + + return BR_FAILED; +} + +// This the SMT = +br_status float_rewriter::mk_eq_core(expr * arg1, expr * arg2, expr_ref & result) { + scoped_mpf v1(m_util.fm()), v2(m_util.fm()); + if (m_util.is_value(arg1, v1) && m_util.is_value(arg2, v2)) { + result = (v1 == v2) ? m().mk_true() : m().mk_false(); + return BR_DONE; + } + + return BR_FAILED; +} diff --git a/lib/float_rewriter.h b/lib/float_rewriter.h new file mode 100644 index 000000000..e4258895d --- /dev/null +++ b/lib/float_rewriter.h @@ -0,0 +1,72 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + float_rewriter.h + +Abstract: + + Basic rewriting rules for floating point numbers. + +Author: + + Leonardo (leonardo) 2012-02-02 + +Notes: + +--*/ +#ifndef _FLOAT_REWRITER_H_ +#define _FLOAT_REWRITER_H_ + +#include"ast.h" +#include"rewriter.h" +#include"params.h" +#include"float_decl_plugin.h" +#include"mpf.h" + +class float_rewriter { + float_util m_util; + mpf_manager m_fm; + + app * mk_eq_nan(expr * arg); + app * mk_neq_nan(expr * arg); + +public: + float_rewriter(ast_manager & m, params_ref const & p = params_ref()); + ~float_rewriter(); + + ast_manager & m() const { return m_util.m(); } + family_id get_fid() const { return m_util.get_fid(); } + + void updt_params(params_ref const & p); + static void get_param_descrs(param_descrs & r); + + br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_eq_core(expr * arg1, expr * arg2, expr_ref & result); + + br_status mk_to_float(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_add(expr * arg1, expr * arg2, expr * arg3, expr_ref & result); + br_status mk_sub(expr * arg1, expr * arg2, expr * arg3, expr_ref & result); + br_status mk_mul(expr * arg1, expr * arg2, expr * arg3, expr_ref & result); + br_status mk_div(expr * arg1, expr * arg2, expr * arg3, expr_ref & result); + br_status mk_uminus(expr * arg1, expr_ref & result); + br_status mk_rem(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_abs(expr * arg1, expr_ref & result); + br_status mk_min(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_max(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_fused_ma(expr * arg1, expr * arg2, expr * arg3, expr * arg4, expr_ref & result); + br_status mk_sqrt(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_round(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_float_eq(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_lt(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_gt(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_le(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_ge(expr * arg1, expr * arg2, expr_ref & result); + br_status mk_is_zero(expr * arg1, expr_ref & result); + br_status mk_is_nzero(expr * arg1, expr_ref & result); + br_status mk_is_pzero(expr * arg1, expr_ref & result); + br_status mk_is_sign_minus(expr * arg1, expr_ref & result); +}; + +#endif diff --git a/lib/fm_tactic.cpp b/lib/fm_tactic.cpp new file mode 100644 index 000000000..4796e7f39 --- /dev/null +++ b/lib/fm_tactic.cpp @@ -0,0 +1,1715 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + fm_tactic.cpp + +Abstract: + + Use Fourier-Motzkin to eliminate variables. + This strategy can handle conditional bounds + (i.e., clauses with at most one constraint). + + The strategy mk_occf can be used to put the + formula in OCC form. + +Author: + + Leonardo de Moura (leonardo) 2012-02-04. + +Revision History: + +--*/ +#include"fm_tactic.h" +#include"tactical.h" +#include"arith_decl_plugin.h" +#include"for_each_expr.h" +#include"cooperate.h" +#include"ast_smt2_pp.h" +#include"ast_pp.h" +#include"id_gen.h" +#include"model_evaluator.h" +#include"model_v2_pp.h" +#include"simplify_tactic.h" + +class fm_tactic : public tactic { + typedef ptr_vector clauses; + typedef unsigned var; + typedef int bvar; + typedef int literal; + typedef svector var_vector; + + struct fm_model_converter : public model_converter { + ast_manager & m; + ptr_vector m_xs; + vector m_clauses; + volatile bool m_cancel; + + enum r_kind { + NONE, + LOWER, + UPPER + }; + + bool is_false(model_ref & md, app * p) { + SASSERT(is_uninterp_const(p)); + expr * val = md->get_const_interp(p->get_decl()); + if (val == 0) { + // if it is don't care, then set to false + md->register_decl(p->get_decl(), m.mk_false()); + return true; + } + return m.is_false(val); + } + + r_kind process(func_decl * x, expr * cls, arith_util & u, model_evaluator & ev, rational & r) { + unsigned num_lits; + expr * const * lits; + if (m.is_or(cls)) { + num_lits = to_app(cls)->get_num_args(); + lits = to_app(cls)->get_args(); + } + else { + num_lits = 1; + lits = &cls; + } + + bool is_lower = false; + bool found = false; + for (unsigned i = 0; i < num_lits; i++) { + expr * l = lits[i]; + expr * atom; + if (is_uninterp_const(l) || (m.is_not(l, atom) && is_uninterp_const(atom))) { + expr_ref val(m); + ev(l, val); + if (m.is_true(val)) + return NONE; // clause was satisfied + } + else { + found = true; + bool neg = m.is_not(l, l); + SASSERT(u.is_le(l) || u.is_ge(l)); + bool strict = neg; + rational a_val; + if (u.is_ge(l)) + neg = !neg; + expr * lhs = to_app(l)->get_arg(0); + expr * rhs = to_app(l)->get_arg(1); + rational c; + u.is_numeral(rhs, c); + if (neg) + c.neg(); + unsigned num_mons; + expr * const * mons; + if (u.is_add(lhs)) { + num_mons = to_app(lhs)->get_num_args(); + mons = to_app(lhs)->get_args(); + } + else { + num_mons = 1; + mons = &lhs; + } + for (unsigned j = 0; j < num_mons; j++) { + expr * monomial = mons[j]; + expr * ai; + expr * xi; + rational ai_val; + if (u.is_mul(monomial, ai, xi)) { + u.is_numeral(ai, ai_val); + } + else { + xi = monomial; + ai_val = rational(1); + } + if (u.is_to_real(xi)) + xi = to_app(xi)->get_arg(0); + SASSERT(is_uninterp_const(xi)); + if (x == to_app(xi)->get_decl()) { + a_val = ai_val; + if (neg) + a_val.neg(); + } + else { + expr_ref val(m); + ev(monomial, val); + SASSERT(u.is_numeral(val)); + rational tmp; + u.is_numeral(val, tmp); + if (neg) + tmp.neg(); + c -= tmp; + } + } + if (u.is_int(x->get_range()) && strict) { + // a*x < c --> a*x <= c-1 + SASSERT(c.is_int()); + c--; + } + is_lower = a_val.is_neg(); + c /= a_val; + if (u.is_int(x->get_range())) { + if (is_lower) + c = ceil(c); + else + c = floor(c); + } + r = c; + } + } + SASSERT(found); + return is_lower ? LOWER : UPPER; + } + + public: + fm_model_converter(ast_manager & _m):m(_m) {} + + virtual ~fm_model_converter() { + m.dec_array_ref(m_xs.size(), m_xs.c_ptr()); + vector::iterator it = m_clauses.begin(); + vector::iterator end = m_clauses.end(); + for (; it != end; ++it) + m.dec_array_ref(it->size(), it->c_ptr()); + } + + void insert(func_decl * x, clauses & c) { + m.inc_ref(x); + m.inc_array_ref(c.size(), c.c_ptr()); + m_xs.push_back(x); + m_clauses.push_back(clauses()); + m_clauses.back().swap(c); + } + + virtual void operator()(model_ref & md, unsigned goal_idx) { + TRACE("fm_mc", model_v2_pp(tout, *md); display(tout);); + m_cancel = false; + model_evaluator ev(*(md.get())); + ev.set_model_completion(true); + arith_util u(m); + unsigned i = m_xs.size(); + while (i > 0) { + --i; + func_decl * x = m_xs[i]; + rational lower; + rational upper; + rational val; + bool has_lower = false; + bool has_upper = false; + TRACE("fm_mc", tout << "processing " << x->get_name() << "\n";); + clauses::iterator it = m_clauses[i].begin(); + clauses::iterator end = m_clauses[i].end(); + for (; it != end; ++it) { + if (m_cancel) throw tactic_exception(TACTIC_CANCELED_MSG); + switch (process(x, *it, u, ev, val)) { + case NONE: + TRACE("fm_mc", tout << "no bound for:\n" << mk_ismt2_pp(*it, m) << "\n";); + break; + case LOWER: + TRACE("fm_mc", tout << "lower bound: " << val << " for:\n" << mk_ismt2_pp(*it, m) << "\n";); + if (!has_lower || val > lower) + lower = val; + has_lower = true; + break; + case UPPER: + TRACE("fm_mc", tout << "upper bound: " << val << " for:\n" << mk_ismt2_pp(*it, m) << "\n";); + if (!has_upper || val < upper) + upper = val; + has_upper = true; + break; + } + } + + expr * x_val; + if (u.is_int(x->get_range())) { + if (has_lower) + x_val = u.mk_numeral(lower, true); + else if (has_upper) + x_val = u.mk_numeral(upper, true); + else + x_val = u.mk_numeral(rational(0), true); + } + else { + if (has_lower && has_upper) + x_val = u.mk_numeral((upper + lower)/rational(2), false); + else if (has_lower) + x_val = u.mk_numeral(lower + rational(1), false); + else if (has_upper) + x_val = u.mk_numeral(upper - rational(1), false); + else + x_val = u.mk_numeral(rational(0), false); + } + TRACE("fm_mc", tout << x->get_name() << " --> " << mk_ismt2_pp(x_val, m) << "\n";); + md->register_decl(x, x_val); + } + TRACE("fm_mc", model_v2_pp(tout, *md);); + } + + virtual void cancel() { + m_cancel = true; + } + + virtual void display(std::ostream & out) { + out << "(fm-model-converter"; + SASSERT(m_xs.size() == m_clauses.size()); + unsigned sz = m_xs.size(); + for (unsigned i = 0; i < sz; i++) { + out << "\n(" << m_xs[i]->get_name(); + clauses const & cs = m_clauses[i]; + clauses::const_iterator it = cs.begin(); + clauses::const_iterator end = cs.end(); + for (; it != end; ++it) { + out << "\n " << mk_ismt2_pp(*it, m, 2); + } + out << ")"; + } + out << ")\n"; + } + + virtual model_converter * translate(ast_translation & translator) { + ast_manager & to_m = translator.to(); + fm_model_converter * res = alloc(fm_model_converter, to_m); + unsigned sz = m_xs.size(); + for (unsigned i = 0; i < sz; i++) { + func_decl * new_x = translator(m_xs[i]); + to_m.inc_ref(new_x); + res->m_xs.push_back(new_x); + + clauses const & cs = m_clauses[i]; + res->m_clauses.push_back(clauses()); + clauses & new_cs = res->m_clauses.back(); + clauses::const_iterator it = cs.begin(); + clauses::const_iterator end = cs.end(); + for (; it != end; ++it) { + app * new_c = translator(*it); + to_m.inc_ref(new_c); + new_cs.push_back(new_c); + } + } + return res; + } + }; + + // Encode the constraint + // lits \/ ( as[0]*xs[0] + ... + as[num_vars-1]*xs[num_vars-1] <= c + // if strict is true, then <= is <. + struct constraint { + static unsigned get_obj_size(unsigned num_lits, unsigned num_vars) { + return sizeof(constraint) + num_lits*sizeof(literal) + num_vars*(sizeof(var) + sizeof(rational)); + } + unsigned m_id; + unsigned m_num_lits:29; + unsigned m_strict:1; + unsigned m_dead:1; + unsigned m_mark:1; + unsigned m_num_vars; + literal * m_lits; + var * m_xs; + rational * m_as; + rational m_c; + expr_dependency * m_dep; + ~constraint() { + rational * it = m_as; + rational * end = it + m_num_vars; + for (; it != end; ++it) + it->~rational(); + } + + unsigned hash() const { return hash_u(m_id); } + }; + + typedef ptr_vector constraints; + + class constraint_set { + unsigned_vector m_id2pos; + constraints m_set; + public: + typedef constraints::const_iterator iterator; + + bool contains(constraint const & c) const { + if (c.m_id >= m_id2pos.size()) + return false; + return m_id2pos[c.m_id] != UINT_MAX; + } + + bool empty() const { return m_set.empty(); } + unsigned size() const { return m_set.size(); } + + void insert(constraint & c) { + unsigned id = c.m_id; + m_id2pos.reserve(id+1, UINT_MAX); + if (m_id2pos[id] != UINT_MAX) + return; // already in the set + unsigned pos = m_set.size(); + m_id2pos[id] = pos; + m_set.push_back(&c); + } + + void erase(constraint & c) { + unsigned id = c.m_id; + if (id >= m_id2pos.size()) + return; + unsigned pos = m_id2pos[id]; + if (pos == UINT_MAX) + return; + m_id2pos[id] = UINT_MAX; + unsigned last_pos = m_set.size() - 1; + if (pos != last_pos) { + constraint * last_c = m_set[last_pos]; + m_set[pos] = last_c; + m_id2pos[last_c->m_id] = pos; + } + m_set.pop_back(); + } + + constraint & erase() { + SASSERT(!empty()); + constraint & c = *m_set.back(); + m_id2pos[c.m_id] = UINT_MAX; + m_set.pop_back(); + return c; + } + + void reset() { m_id2pos.reset(); m_set.reset(); } + void finalize() { m_id2pos.finalize(); m_set.finalize(); } + + iterator begin() const { return m_set.begin(); } + iterator end() const { return m_set.end(); } + }; + + struct imp { + ast_manager & m; + small_object_allocator m_allocator; + arith_util m_util; + constraints m_constraints; + expr_ref_vector m_bvar2expr; + char_vector m_bvar2sign; + obj_map m_expr2bvar; + char_vector m_is_int; + char_vector m_forbidden; + expr_ref_vector m_var2expr; + obj_map m_expr2var; + unsigned_vector m_var2pos; + vector m_lowers; + vector m_uppers; + obj_hashtable m_forbidden_set; // variables that cannot be eliminated because occur in non OCC ineq part + goal_ref m_new_goal; + ref m_mc; + volatile bool m_cancel; + id_gen m_id_gen; + bool m_produce_models; + bool m_fm_real_only; + unsigned m_fm_limit; + unsigned m_fm_cutoff1; + unsigned m_fm_cutoff2; + unsigned m_fm_extra; + bool m_fm_occ; + unsigned long long m_max_memory; + unsigned m_counter; + bool m_inconsistent; + expr_dependency_ref m_inconsistent_core; + constraint_set m_sub_todo; + + // --------------------------- + // + // OCC clause recognizer + // + // --------------------------- + + bool is_literal(expr * t) const { + expr * atom; + return is_uninterp_const(t) || (m.is_not(t, atom) && is_uninterp_const(atom)); + } + + bool is_constraint(expr * t) const { + return !is_literal(t); + } + + bool is_var(expr * t, expr * & x) const { + if (is_uninterp_const(t)) { + x = t; + return true; + } + else if (m_util.is_to_real(t) && is_uninterp_const(to_app(t)->get_arg(0))) { + x = to_app(t)->get_arg(0); + return true; + } + return false; + } + + bool is_var(expr * t) const { + expr * x; + return is_var(t, x); + } + + bool is_linear_mon_core(expr * t, expr * & x) const { + expr * c; + if (m_util.is_mul(t, c, x) && m_util.is_numeral(c) && is_var(x, x)) + return true; + return is_var(t, x); + } + + bool is_linear_mon(expr * t) const { + expr * x; + return is_linear_mon_core(t, x); + } + + bool is_linear_pol(expr * t) const { + unsigned num_mons; + expr * const * mons; + if (m_util.is_add(t)) { + num_mons = to_app(t)->get_num_args(); + mons = to_app(t)->get_args(); + } + else { + num_mons = 1; + mons = &t; + } + + expr_fast_mark2 visited; + bool all_forbidden = true; + for (unsigned i = 0; i < num_mons; i++) { + expr * x; + if (!is_linear_mon_core(mons[i], x)) + return false; + if (visited.is_marked(x)) + return false; // duplicates are not supported... must simplify first + visited.mark(x); + if (!m_forbidden_set.contains(to_app(x)->get_decl()) && (!m_fm_real_only || !m_util.is_int(x))) + all_forbidden = false; + } + return !all_forbidden; + } + + bool is_linear_ineq(expr * t) const { + m.is_not(t, t); + expr * lhs, * rhs; + TRACE("is_occ_bug", tout << mk_pp(t, m) << "\n";); + if (m_util.is_le(t, lhs, rhs) || m_util.is_ge(t, lhs, rhs)) { + if (!m_util.is_numeral(rhs)) + return false; + return is_linear_pol(lhs); + } + return false; + } + + bool is_occ(expr * t) { + if (m_fm_occ && m.is_or(t)) { + unsigned num = to_app(t)->get_num_args(); + bool found = false; + for (unsigned i = 0; i < num; i++) { + expr * l = to_app(t)->get_arg(i); + if (is_literal(l)) { + continue; + } + else if (is_linear_ineq(l)) { + if (found) + return false; + found = true; + } + else { + return false; + } + } + return found; + } + return is_linear_ineq(t); + } + + // --------------------------- + // + // Memory mng + // + // --------------------------- + void del_constraint(constraint * c) { + m.dec_ref(c->m_dep); + m_sub_todo.erase(*c); + m_id_gen.recycle(c->m_id); + c->~constraint(); + unsigned sz = constraint::get_obj_size(c->m_num_lits, c->m_num_vars); + m_allocator.deallocate(sz, c); + } + + void del_constraints(unsigned sz, constraint * const * cs) { + for (unsigned i = 0; i < sz; i++) + del_constraint(cs[i]); + } + + void reset_constraints() { + del_constraints(m_constraints.size(), m_constraints.c_ptr()); + m_constraints.reset(); + } + + constraint * mk_constraint(unsigned num_lits, literal * lits, unsigned num_vars, var * xs, rational * as, rational & c, bool strict, + expr_dependency * dep) { + unsigned sz = constraint::get_obj_size(num_lits, num_vars); + char * mem = static_cast(m_allocator.allocate(sz)); + char * mem_as = mem + sizeof(constraint); + char * mem_lits = mem_as + sizeof(rational)*num_vars; + char * mem_xs = mem_lits + sizeof(literal)*num_lits; + constraint * cnstr = new (mem) constraint(); + cnstr->m_id = m_id_gen.mk(); + cnstr->m_num_lits = num_lits; + cnstr->m_dead = false; + cnstr->m_mark = false; + cnstr->m_strict = strict; + cnstr->m_num_vars = num_vars; + cnstr->m_lits = reinterpret_cast(mem_lits); + for (unsigned i = 0; i < num_lits; i++) + cnstr->m_lits[i] = lits[i]; + cnstr->m_xs = reinterpret_cast(mem_xs); + cnstr->m_as = reinterpret_cast(mem_as); + for (unsigned i = 0; i < num_vars; i++) { + TRACE("mk_constraint_bug", tout << "xs[" << i << "]: " << xs[i] << "\n";); + cnstr->m_xs[i] = xs[i]; + new (cnstr->m_as + i) rational(as[i]); + } + cnstr->m_c = c; + DEBUG_CODE({ + for (unsigned i = 0; i < num_vars; i++) { + SASSERT(cnstr->m_xs[i] == xs[i]); + SASSERT(cnstr->m_as[i] == as[i]); + } + }); + cnstr->m_dep = dep; + m.inc_ref(dep); + return cnstr; + } + + // --------------------------- + // + // Util + // + // --------------------------- + + unsigned num_vars() const { return m_is_int.size(); } + + // multiply as and c, by the lcm of their denominators + void mk_int(unsigned num, rational * as, rational & c) { + rational l = denominator(c); + for (unsigned i = 0; i < num; i++) + l = lcm(l, denominator(as[i])); + if (l.is_one()) + return; + c *= l; + SASSERT(c.is_int()); + for (unsigned i = 0; i < num; i++) { + as[i] *= l; + SASSERT(as[i].is_int()); + } + } + + void normalize_coeffs(constraint & c) { + if (c.m_num_vars == 0) + return; + // compute gcd of all coefficients + rational g = c.m_c; + if (g.is_neg()) + g.neg(); + for (unsigned i = 0; i < c.m_num_vars; i++) { + if (g.is_one()) + break; + if (c.m_as[i].is_pos()) + g = gcd(c.m_as[i], g); + else + g = gcd(-c.m_as[i], g); + } + if (g.is_one()) + return; + c.m_c /= g; + for (unsigned i = 0; i < c.m_num_vars; i++) + c.m_as[i] /= g; + } + + void display(std::ostream & out, constraint const & c) const { + for (unsigned i = 0; i < c.m_num_lits; i++) { + literal l = c.m_lits[i]; + if (sign(l)) + out << "~"; + bvar p = lit2bvar(l); + out << mk_ismt2_pp(m_bvar2expr[p], m); + out << " "; + } + out << "("; + if (c.m_num_vars == 0) + out << "0"; + for (unsigned i = 0; i < c.m_num_vars; i++) { + if (i > 0) + out << " + "; + if (!c.m_as[i].is_one()) + out << c.m_as[i] << "*"; + out << mk_ismt2_pp(m_var2expr.get(c.m_xs[i]), m); + } + if (c.m_strict) + out << " < "; + else + out << " <= "; + out << c.m_c; + out << ")"; + } + + /** + \brief Return true if c1 subsumes c2 + + c1 subsumes c2 If + 1) All literals of c1 are literals of c2 + 2) polynomial of c1 == polynomial of c2 + 3) c1.m_c <= c2.m_c + */ + bool subsumes(constraint const & c1, constraint const & c2) { + if (&c1 == &c2) + return false; + // quick checks first + if (c1.m_num_lits > c2.m_num_lits) + return false; + if (c1.m_num_vars != c2.m_num_vars) + return false; + if (c1.m_c > c2.m_c) + return false; + if (!c1.m_strict && c2.m_strict && c1.m_c == c2.m_c) + return false; + + m_counter += c1.m_num_lits + c2.m_num_lits; + + for (unsigned i = 0; i < c1.m_num_vars; i++) { + m_var2pos[c1.m_xs[i]] = i; + } + + bool failed = false; + for (unsigned i = 0; i < c2.m_num_vars; i++) { + unsigned pos1 = m_var2pos[c2.m_xs[i]]; + if (pos1 == UINT_MAX || c1.m_as[pos1] != c2.m_as[i]) { + failed = true; + break; + } + } + + for (unsigned i = 0; i < c1.m_num_vars; i++) { + m_var2pos[c1.m_xs[i]] = UINT_MAX; + } + + if (failed) + return false; + + for (unsigned i = 0; i < c2.m_num_lits; i++) { + literal l = c2.m_lits[i]; + bvar b = lit2bvar(l); + SASSERT(m_bvar2sign[b] == 0); + m_bvar2sign[b] = sign(l) ? -1 : 1; + } + + for (unsigned i = 0; i < c1.m_num_lits; i++) { + literal l = c1.m_lits[i]; + bvar b = lit2bvar(l); + char s = sign(l) ? -1 : 1; + if (m_bvar2sign[b] != s) { + failed = true; + break; + } + } + + for (unsigned i = 0; i < c2.m_num_lits; i++) { + literal l = c2.m_lits[i]; + bvar b = lit2bvar(l); + m_bvar2sign[b] = 0; + } + + if (failed) + return false; + + return true; + } + + void backward_subsumption(constraint const & c) { + if (c.m_num_vars == 0) + return; + var best = UINT_MAX; + unsigned best_sz = UINT_MAX; + bool best_lower = false; + for (unsigned i = 0; i < c.m_num_vars; i++) { + var xi = c.m_xs[i]; + if (is_forbidden(xi)) + continue; // variable is not in the index + bool neg_a = c.m_as[i].is_neg(); + constraints & cs = neg_a ? m_lowers[xi] : m_uppers[xi]; + if (cs.size() < best_sz) { + best = xi; + best_sz = cs.size(); + best_lower = neg_a; + } + } + if (best_sz == 0) + return; + if (best == UINT_MAX) + return; // none of the c variables are in the index. + constraints & cs = best_lower ? m_lowers[best] : m_uppers[best]; + m_counter += cs.size(); + constraints::iterator it = cs.begin(); + constraints::iterator it2 = it; + constraints::iterator end = cs.end(); + for (; it != end; ++it) { + constraint * c2 = *it; + if (c2->m_dead) + continue; + if (subsumes(c, *c2)) { + TRACE("fm_subsumption", display(tout, c); tout << "\nsubsumed:\n"; display(tout, *c2); tout << "\n";); + c2->m_dead = true; + continue; + } + *it2 = *it; + ++it2; + } + cs.set_end(it2); + } + + void subsume() { + while (!m_sub_todo.empty()) { + constraint & c = m_sub_todo.erase(); + if (c.m_dead) + continue; + backward_subsumption(c); + } + } + + // --------------------------- + // + // Initialization + // + // --------------------------- + + imp(ast_manager & _m, params_ref const & p): + m(_m), + m_allocator("fm-tactic"), + m_util(m), + m_bvar2expr(m), + m_var2expr(m), + m_inconsistent_core(m) { + updt_params(p); + m_cancel = false; + } + + ~imp() { + reset_constraints(); + } + + void updt_params(params_ref const & p) { + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + m_fm_real_only = p.get_bool(":fm-real-only", true); + m_fm_limit = p.get_uint(":fm-limit", 5000000); + m_fm_cutoff1 = p.get_uint(":fm-cutoff1", 8); + m_fm_cutoff2 = p.get_uint(":fm-cutoff2", 256); + m_fm_extra = p.get_uint(":fm-extra", 0); + m_fm_occ = p.get_bool(":fm-occ", false); + } + + void set_cancel(bool f) { + m_cancel = f; + } + + struct forbidden_proc { + imp & m_owner; + forbidden_proc(imp & o):m_owner(o) {} + void operator()(::var * n) {} + void operator()(app * n) { + if (is_uninterp_const(n) && m_owner.m.get_sort(n)->get_family_id() == m_owner.m_util.get_family_id()) { + m_owner.m_forbidden_set.insert(n->get_decl()); + } + } + void operator()(quantifier * n) {} + }; + + void init_forbidden_set(goal const & g) { + m_forbidden_set.reset(); + expr_fast_mark1 visited; + forbidden_proc proc(*this); + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + expr * f = g.form(i); + if (is_occ(f)) + continue; + TRACE("is_occ_bug", tout << "not OCC:\n" << mk_ismt2_pp(f, m) << "\n";); + quick_for_each_expr(proc, visited, f); + } + } + + void init(goal const & g) { + m_sub_todo.reset(); + m_id_gen.reset(); + reset_constraints(); + m_bvar2expr.reset(); + m_bvar2sign.reset(); + m_bvar2expr.push_back(0); // bvar 0 is not used + m_bvar2sign.push_back(0); + m_expr2var.reset(); + m_is_int.reset(); + m_var2pos.reset(); + m_forbidden.reset(); + m_var2expr.reset(); + m_expr2var.reset(); + m_lowers.reset(); + m_uppers.reset(); + m_new_goal = 0; + m_mc = 0; + m_counter = 0; + m_inconsistent = false; + m_inconsistent_core = 0; + init_forbidden_set(g); + } + + // --------------------------- + // + // Internal data-structures + // + // --------------------------- + + static bool sign(literal l) { return l < 0; } + static bvar lit2bvar(literal l) { return l < 0 ? -l : l; } + + bool is_int(var x) const { + return m_is_int[x] != 0; + } + + bool is_forbidden(var x) const { + return m_forbidden[x] != 0; + } + + bool all_int(constraint const & c) const { + for (unsigned i = 0; i < c.m_num_vars; i++) { + if (!is_int(c.m_xs[i])) + return false; + } + return true; + } + + app * to_expr(constraint const & c) { + expr * ineq; + if (c.m_num_vars == 0) { + // 0 < k (for k > 0) --> true + // 0 <= 0 -- > true + if (c.m_c.is_pos() || (!c.m_strict && c.m_c.is_zero())) + return m.mk_true(); + ineq = 0; + } + else { + bool int_cnstr = all_int(c); + ptr_buffer ms; + for (unsigned i = 0; i < c.m_num_vars; i++) { + expr * x = m_var2expr.get(c.m_xs[i]); + if (!int_cnstr && is_int(c.m_xs[i])) + x = m_util.mk_to_real(x); + if (c.m_as[i].is_one()) + ms.push_back(x); + else + ms.push_back(m_util.mk_mul(m_util.mk_numeral(c.m_as[i], int_cnstr), x)); + } + expr * lhs; + if (c.m_num_vars == 1) + lhs = ms[0]; + else + lhs = m_util.mk_add(ms.size(), ms.c_ptr()); + expr * rhs = m_util.mk_numeral(c.m_c, int_cnstr); + if (c.m_strict) { + ineq = m.mk_not(m_util.mk_ge(lhs, rhs)); + } + else { + ineq = m_util.mk_le(lhs, rhs); + } + } + + if (c.m_num_lits == 0) { + if (ineq) + return to_app(ineq); + else + return m.mk_false(); + } + + ptr_buffer lits; + for (unsigned i = 0; i < c.m_num_lits; i++) { + literal l = c.m_lits[i]; + if (sign(l)) + lits.push_back(m.mk_not(m_bvar2expr.get(lit2bvar(l)))); + else + lits.push_back(m_bvar2expr.get(lit2bvar(l))); + } + if (ineq) + lits.push_back(ineq); + if (lits.size() == 1) + return to_app(lits[0]); + else + return m.mk_or(lits.size(), lits.c_ptr()); + } + + var mk_var(expr * t) { + SASSERT(is_uninterp_const(t)); + SASSERT(m_util.is_int(t) || m_util.is_real(t)); + var x = m_var2expr.size(); + m_var2expr.push_back(t); + bool is_int = m_util.is_int(t); + m_is_int.push_back(is_int); + m_var2pos.push_back(UINT_MAX); + m_expr2var.insert(t, x); + m_lowers.push_back(constraints()); + m_uppers.push_back(constraints()); + bool forbidden = m_forbidden_set.contains(to_app(t)->get_decl()) || (m_fm_real_only && is_int); + m_forbidden.push_back(forbidden); + SASSERT(m_var2expr.size() == m_is_int.size()); + SASSERT(m_lowers.size() == m_is_int.size()); + SASSERT(m_uppers.size() == m_is_int.size()); + SASSERT(m_forbidden.size() == m_is_int.size()); + SASSERT(m_var2pos.size() == m_is_int.size()); + return x; + } + + bvar mk_bvar(expr * t) { + SASSERT(is_uninterp_const(t)); + SASSERT(m.is_bool(t)); + bvar p = m_bvar2expr.size(); + m_bvar2expr.push_back(t); + m_bvar2sign.push_back(0); + SASSERT(m_bvar2expr.size() == m_bvar2sign.size()); + m_expr2bvar.insert(t, p); + SASSERT(p > 0); + return p; + } + + var to_var(expr * t) { + var x; + if (!m_expr2var.find(t, x)) + x = mk_var(t); + SASSERT(m_expr2var.contains(t)); + SASSERT(m_var2expr.get(x) == t); + TRACE("to_var_bug", tout << mk_ismt2_pp(t, m) << " --> " << x << "\n";); + return x; + } + + bvar to_bvar(expr * t) { + bvar p; + if (m_expr2bvar.find(t, p)) + return p; + return mk_bvar(t); + } + + literal to_literal(expr * t) { + if (m.is_not(t, t)) + return -to_bvar(t); + else + return to_bvar(t); + } + + + void add_constraint(expr * f, expr_dependency * dep) { + SASSERT(!m.is_or(f) || m_fm_occ); + sbuffer lits; + sbuffer xs; + buffer as; + rational c; + bool strict; + unsigned num; + expr * const * args; + if (m.is_or(f)) { + num = to_app(f)->get_num_args(); + args = to_app(f)->get_args(); + } + else { + num = 1; + args = &f; + } + +#if Z3DEBUG + bool found_ineq = false; +#endif + for (unsigned i = 0; i < num; i++) { + expr * l = args[i]; + if (is_literal(l)) { + lits.push_back(to_literal(l)); + } + else { + // found inequality + SASSERT(!found_ineq); + DEBUG_CODE(found_ineq = true;); + bool neg = m.is_not(l, l); + SASSERT(m_util.is_le(l) || m_util.is_ge(l)); + strict = neg; + if (m_util.is_ge(l)) + neg = !neg; + expr * lhs = to_app(l)->get_arg(0); + expr * rhs = to_app(l)->get_arg(1); + m_util.is_numeral(rhs, c); + if (neg) + c.neg(); + unsigned num_mons; + expr * const * mons; + if (m_util.is_add(lhs)) { + num_mons = to_app(lhs)->get_num_args(); + mons = to_app(lhs)->get_args(); + } + else { + num_mons = 1; + mons = &lhs; + } + + bool all_int = true; + for (unsigned j = 0; j < num_mons; j++) { + expr * monomial = mons[j]; + expr * a; + rational a_val; + expr * x; + if (m_util.is_mul(monomial, a, x)) { + VERIFY(m_util.is_numeral(a, a_val)); + } + else { + x = monomial; + a_val = rational(1); + } + if (neg) + a_val.neg(); + VERIFY(is_var(x, x)); + xs.push_back(to_var(x)); + as.push_back(a_val); + if (!is_int(xs.back())) + all_int = false; + } + mk_int(as.size(), as.c_ptr(), c); + if (all_int && strict) { + strict = false; + c--; + } + } + } + + TRACE("to_var_bug", tout << "before mk_constraint: "; for (unsigned i = 0; i < xs.size(); i++) tout << " " << xs[i]; tout << "\n";); + + constraint * new_c = mk_constraint(lits.size(), + lits.c_ptr(), + xs.size(), + xs.c_ptr(), + as.c_ptr(), + c, + strict, + dep); + + TRACE("to_var_bug", tout << "add_constraint: "; display(tout, *new_c); tout << "\n";); + VERIFY(register_constraint(new_c)); + } + + bool is_false(constraint const & c) const { + return c.m_num_lits == 0 && c.m_num_vars == 0 && (c.m_c.is_neg() || (c.m_strict && c.m_c.is_zero())); + } + + bool register_constraint(constraint * c) { + normalize_coeffs(*c); + if (is_false(*c)) { + del_constraint(c); + m_inconsistent = true; + TRACE("add_constraint_bug", tout << "is false "; display(tout, *c); tout << "\n";); + return false; + } + + bool r = false; + + for (unsigned i = 0; i < c->m_num_vars; i++) { + var x = c->m_xs[i]; + if (!is_forbidden(x)) { + r = true; + if (c->m_as[i].is_neg()) + m_lowers[x].push_back(c); + else + m_uppers[x].push_back(c); + } + } + + if (r) { + m_sub_todo.insert(*c); + m_constraints.push_back(c); + return true; + } + else { + TRACE("add_constraint_bug", tout << "all variables are forbidden "; display(tout, *c); tout << "\n";); + m_new_goal->assert_expr(to_expr(*c), 0, c->m_dep); + del_constraint(c); + return false; + } + } + + void init_use_list(goal const & g) { + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + if (m_inconsistent) + return; + expr * f = g.form(i); + if (is_occ(f)) + add_constraint(f, g.dep(i)); + else + m_new_goal->assert_expr(f, 0, g.dep(i)); + } + } + + unsigned get_cost(var x) const { + unsigned long long r = static_cast(m_lowers[x].size()) * static_cast(m_uppers[x].size()); + if (r > UINT_MAX) + return UINT_MAX; + return static_cast(r); + } + + typedef std::pair x_cost; + + struct x_cost_lt { + char_vector const m_is_int; + x_cost_lt(char_vector & is_int):m_is_int(is_int) {} + bool operator()(x_cost const & p1, x_cost const & p2) const { + // Integer variables with cost 0 can be eliminated even if they depend on real variables. + // Cost 0 == no lower or no upper bound. + if (p1.second == 0) { + if (p2.second > 0) return true; + return p1.first < p2.first; + } + if (p2.second == 0) return false; + bool int1 = m_is_int[p1.first] != 0; + bool int2 = m_is_int[p2.first] != 0; + return (!int1 && int2) || (int1 == int2 && p1.second < p2.second); + } + }; + + void sort_candidates(var_vector & xs) { + svector x_cost_vector; + unsigned num = num_vars(); + for (var x = 0; x < num; x++) { + if (!is_forbidden(x)) { + x_cost_vector.push_back(x_cost(x, get_cost(x))); + } + } + // x_cost_lt is not a total order on variables + std::stable_sort(x_cost_vector.begin(), x_cost_vector.end(), x_cost_lt(m_is_int)); + TRACE("fm", + svector::iterator it2 = x_cost_vector.begin(); + svector::iterator end2 = x_cost_vector.end(); + for (; it2 != end2; ++it2) { + tout << "(" << mk_ismt2_pp(m_var2expr.get(it2->first), m) << " " << it2->second << ") "; + } + tout << "\n";); + svector::iterator it2 = x_cost_vector.begin(); + svector::iterator end2 = x_cost_vector.end(); + for (; it2 != end2; ++it2) { + xs.push_back(it2->first); + } + } + + void cleanup_constraints(constraints & cs) { + unsigned j = 0; + unsigned sz = cs.size(); + for (unsigned i = 0; i < sz; i++) { + constraint * c = cs[i]; + if (c->m_dead) + continue; + cs[j] = c; + j++; + } + cs.shrink(j); + } + + // Set all_int = true if all variables in c are int. + // Set unit_coeff = true if the coefficient of x in c is 1 or -1. + // If all_int = false, then unit_coeff may not be set. + void analyze(constraint const & c, var x, bool & all_int, bool & unit_coeff) const { + all_int = true; + unit_coeff = true; + for (unsigned i = 0; i < c.m_num_vars; i++) { + if (!is_int(c.m_xs[i])) { + all_int = false; + return; + } + if (c.m_xs[i] == x) { + unit_coeff = (c.m_as[i].is_one() || c.m_as[i].is_minus_one()); + } + } + } + + void analyze(constraints const & cs, var x, bool & all_int, bool & unit_coeff) const { + all_int = true; + unit_coeff = true; + constraints::const_iterator it = cs.begin(); + constraints::const_iterator end = cs.end(); + for (; it != end; ++it) { + bool curr_unit_coeff; + analyze(*(*it), x, all_int, curr_unit_coeff); + if (!all_int) + return; + if (!curr_unit_coeff) + unit_coeff = false; + } + } + + // An integer variable x may be eliminated, if + // 1- All variables in the contraints it occur are integer. + // 2- The coefficient of x in all lower bounds (or all upper bounds) is unit. + bool can_eliminate(var x) const { + if (!is_int(x)) + return true; + bool all_int; + bool l_unit, u_unit; + analyze(m_lowers[x], x, all_int, l_unit); + if (!all_int) + return false; + analyze(m_uppers[x], x, all_int, u_unit); + return all_int && (l_unit || u_unit); + } + + void copy_constraints(constraints const & s, clauses & t) { + constraints::const_iterator it = s.begin(); + constraints::const_iterator end = s.end(); + for (; it != end; ++it) { + app * c = to_expr(*(*it)); + t.push_back(c); + } + } + + clauses tmp_clauses; + void save_constraints(var x) { + if (m_produce_models) { + tmp_clauses.reset(); + copy_constraints(m_lowers[x], tmp_clauses); + copy_constraints(m_uppers[x], tmp_clauses); + m_mc->insert(to_app(m_var2expr.get(x))->get_decl(), tmp_clauses); + } + } + + void mark_constraints_dead(constraints const & cs) { + constraints::const_iterator it = cs.begin(); + constraints::const_iterator end = cs.end(); + for (; it != end; ++it) + (*it)->m_dead = true; + } + + void mark_constraints_dead(var x) { + save_constraints(x); + mark_constraints_dead(m_lowers[x]); + mark_constraints_dead(m_uppers[x]); + } + + void get_coeff(constraint const & c, var x, rational & a) { + for (unsigned i = 0; i < c.m_num_vars; i++) { + if (c.m_xs[i] == x) { + a = c.m_as[i]; + return; + } + } + UNREACHABLE(); + } + + var_vector new_xs; + vector new_as; + svector new_lits; + + constraint * resolve(constraint const & l, constraint const & u, var x) { + m_counter += l.m_num_vars + u.m_num_vars + l.m_num_lits + u.m_num_lits; + rational a, b; + get_coeff(l, x, a); + get_coeff(u, x, b); + SASSERT(a.is_neg()); + SASSERT(b.is_pos()); + a.neg(); + + SASSERT(!is_int(x) || a.is_one() || b.is_one()); + + new_xs.reset(); + new_as.reset(); + rational new_c = l.m_c*b + u.m_c*a; + bool new_strict = l.m_strict || u.m_strict; + + for (unsigned i = 0; i < l.m_num_vars; i++) { + var xi = l.m_xs[i]; + if (xi == x) + continue; + unsigned pos = new_xs.size(); + new_xs.push_back(xi); + SASSERT(m_var2pos[xi] == UINT_MAX); + m_var2pos[xi] = pos; + new_as.push_back(l.m_as[i] * b); + SASSERT(new_xs[m_var2pos[xi]] == xi); + SASSERT(new_xs.size() == new_as.size()); + } + + for (unsigned i = 0; i < u.m_num_vars; i++) { + var xi = u.m_xs[i]; + if (xi == x) + continue; + unsigned pos = m_var2pos[xi]; + if (pos == UINT_MAX) { + new_xs.push_back(xi); + new_as.push_back(u.m_as[i] * a); + } + else { + new_as[pos] += u.m_as[i] * a; + } + } + + // remove zeros and check whether all variables are int + bool all_int = true; + unsigned sz = new_xs.size(); + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + if (new_as[i].is_zero()) + continue; + if (!is_int(new_xs[i])) + all_int = false; + if (i != j) { + new_xs[j] = new_xs[i]; + new_as[j] = new_as[i]; + } + j++; + } + new_xs.shrink(j); + new_as.shrink(j); + + if (all_int && new_strict) { + new_strict = false; + new_c --; + } + + // reset m_var2pos + for (unsigned i = 0; i < l.m_num_vars; i++) { + m_var2pos[l.m_xs[i]] = UINT_MAX; + } + + if (new_xs.empty() && (new_c.is_pos() || (!new_strict && new_c.is_zero()))) { + // literal is true + TRACE("fm", tout << "resolution " << x << " consequent literal is always true: \n"; + display(tout, l); + tout << "\n"; + display(tout, u); tout << "\n";); + return 0; // no constraint needs to be created. + } + + new_lits.reset(); + for (unsigned i = 0; i < l.m_num_lits; i++) { + literal lit = l.m_lits[i]; + bvar p = lit2bvar(lit); + m_bvar2sign[p] = sign(lit) ? -1 : 1; + new_lits.push_back(lit); + } + + bool tautology = false; + for (unsigned i = 0; i < u.m_num_lits && !tautology; i++) { + literal lit = u.m_lits[i]; + bvar p = lit2bvar(lit); + switch (m_bvar2sign[p]) { + case 0: + new_lits.push_back(lit); + break; + case -1: + if (!sign(lit)) + tautology = true; + break; + case 1: + if (sign(lit)) + tautology = true; + break; + default: + UNREACHABLE(); + } + } + + // reset m_bvar2sign + for (unsigned i = 0; i < l.m_num_lits; i++) { + literal lit = l.m_lits[i]; + bvar p = lit2bvar(lit); + m_bvar2sign[p] = 0; + } + + if (tautology) { + TRACE("fm", tout << "resolution " << x << " tautology: \n"; + display(tout, l); + tout << "\n"; + display(tout, u); tout << "\n";); + return 0; + } + + expr_dependency * new_dep = m.mk_join(l.m_dep, u.m_dep); + + if (new_lits.empty() && new_xs.empty() && (new_c.is_neg() || (new_strict && new_c.is_zero()))) { + TRACE("fm", tout << "resolution " << x << " inconsistent: \n"; + display(tout, l); + tout << "\n"; + display(tout, u); tout << "\n";); + m_inconsistent = true; + m_inconsistent_core = new_dep; + return 0; + } + + constraint * new_cnstr = mk_constraint(new_lits.size(), + new_lits.c_ptr(), + new_xs.size(), + new_xs.c_ptr(), + new_as.c_ptr(), + new_c, + new_strict, + new_dep); + + TRACE("fm", tout << "resolution " << x << "\n"; + display(tout, l); + tout << "\n"; + display(tout, u); + tout << "\n---->\n"; + display(tout, *new_cnstr); + tout << "\n"; + tout << "new_dep: " << new_dep << "\n";); + + return new_cnstr; + } + + ptr_vector new_constraints; + + bool try_eliminate(var x) { + constraints & l = m_lowers[x]; + constraints & u = m_uppers[x]; + cleanup_constraints(l); + cleanup_constraints(u); + + if (l.empty() || u.empty()) { + // easy case + mark_constraints_dead(x); + TRACE("fm", tout << "variables was eliminated (trivial case)\n";); + return true; + } + + unsigned num_lowers = l.size(); + unsigned num_uppers = u.size(); + + if (num_lowers > m_fm_cutoff1 && num_uppers > m_fm_cutoff1) + return false; + + if (num_lowers * num_uppers > m_fm_cutoff2) + return false; + + if (!can_eliminate(x)) + return false; + + m_counter += num_lowers * num_uppers; + + TRACE("fm_bug", tout << "eliminating " << mk_ismt2_pp(m_var2expr.get(x), m) << "\nlowers:\n"; + display_constraints(tout, l); tout << "uppers:\n"; display_constraints(tout, u);); + + unsigned num_old_cnstrs = num_uppers + num_lowers; + unsigned limit = num_old_cnstrs + m_fm_extra; + unsigned num_new_cnstrs = 0; + new_constraints.reset(); + for (unsigned i = 0; i < num_lowers; i++) { + for (unsigned j = 0; j < num_uppers; j++) { + if (m_inconsistent || num_new_cnstrs > limit) { + TRACE("fm", tout << "too many new constraints: " << num_new_cnstrs << "\n";); + del_constraints(new_constraints.size(), new_constraints.c_ptr()); + return false; + } + constraint const & l_c = *(l[i]); + constraint const & u_c = *(u[j]); + constraint * new_c = resolve(l_c, u_c, x); + if (new_c != 0) { + num_new_cnstrs++; + new_constraints.push_back(new_c); + } + } + } + + mark_constraints_dead(x); + + unsigned sz = new_constraints.size(); + + m_counter += sz; + + for (unsigned i = 0; i < sz; i++) { + constraint * c = new_constraints[i]; + backward_subsumption(*c); + register_constraint(c); + } + TRACE("fm", tout << "variables was eliminated old: " << num_old_cnstrs << " new_constraints: " << sz << "\n";); + return true; + } + + void copy_remaining(vector & v2cs) { + vector::iterator it = v2cs.begin(); + vector::iterator end = v2cs.end(); + for (; it != end; ++it) { + constraints & cs = *it; + constraints::iterator it2 = cs.begin(); + constraints::iterator end2 = cs.end(); + for (; it2 != end2; ++it2) { + constraint * c = *it2; + if (!c->m_dead) { + c->m_dead = true; + expr * new_f = to_expr(*c); + TRACE("fm_bug", tout << "asserting...\n" << mk_ismt2_pp(new_f, m) << "\nnew_dep: " << c->m_dep << "\n";); + m_new_goal->assert_expr(new_f, 0, c->m_dep); + } + } + } + v2cs.finalize(); + } + + // Copy remaining clauses to m_new_goal + void copy_remaining() { + copy_remaining(m_uppers); + copy_remaining(m_lowers); + } + + void checkpoint() { + cooperate("fm"); + if (m_cancel) + throw tactic_exception(TACTIC_CANCELED_MSG); + if (memory::get_allocation_size() > m_max_memory) + throw tactic_exception(TACTIC_MAX_MEMORY_MSG); + } + + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + mc = 0; pc = 0; core = 0; + tactic_report report("fm", *g); + fail_if_proof_generation("fm", g); + m_produce_models = g->models_enabled(); + + init(*g); + + m_new_goal = alloc(goal, *g, true); + SASSERT(m_new_goal->depth() == g->depth()); + SASSERT(m_new_goal->prec() == g->prec()); + m_new_goal->inc_depth(); + + init_use_list(*g); + + if (m_inconsistent) { + m_new_goal->reset(); + m_new_goal->assert_expr(m.mk_false(), 0, m_inconsistent_core); + } + else { + TRACE("fm", display(tout);); + + subsume(); + var_vector candidates; + sort_candidates(candidates); + + unsigned eliminated = 0; + + if (m_produce_models) + m_mc = alloc(fm_model_converter, m); + + unsigned num = candidates.size(); + for (unsigned i = 0; i < num; i++) { + checkpoint(); + if (m_counter > m_fm_limit) + break; + m_counter++; + if (try_eliminate(candidates[i])) + eliminated++; + if (m_inconsistent) { + m_new_goal->reset(); + m_new_goal->assert_expr(m.mk_false(), 0, m_inconsistent_core); + break; + } + } + report_tactic_progress(":fm-eliminated", eliminated); + report_tactic_progress(":fm-cost", m_counter); + if (!m_inconsistent) { + copy_remaining(); + mc = m_mc.get(); + } + } + reset_constraints(); + result.push_back(m_new_goal.get()); + TRACE("fm", m_new_goal->display(tout);); + SASSERT(m_new_goal->is_well_sorted()); + } + + void display_constraints(std::ostream & out, constraints const & cs) const { + constraints::const_iterator it = cs.begin(); + constraints::const_iterator end = cs.end(); + for (; it != end; ++it) { + out << " "; + display(out, *(*it)); + out << "\n"; + } + } + + void display(std::ostream & out) const { + unsigned num = num_vars(); + for (var x = 0; x < num; x++) { + if (is_forbidden(x)) + continue; + out << mk_ismt2_pp(m_var2expr.get(x), m) << "\n"; + display_constraints(out, m_lowers[x]); + display_constraints(out, m_uppers[x]); + } + } + }; + + imp * m_imp; + params_ref m_params; +public: + + fm_tactic(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(fm_tactic, m, m_params); + } + + virtual ~fm_tactic() { + dealloc(m_imp); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + insert_produce_models(r); + insert_max_memory(r); + r.insert(":fm-real-only", CPK_BOOL, "(default: true) consider only real variables for fourier-motzkin elimination."); + r.insert(":fm-occ", CPK_BOOL, "(default: false) consider inequalities occurring in clauses for FM."); + r.insert(":fm-limit", CPK_UINT, "(default: 5000000) maximum number of constraints, monomials, clauses visited during FM."); + r.insert(":fm-cutoff1", CPK_UINT, "(default: 8) first cutoff for FM based on maximum number of lower/upper occurrences."); + r.insert(":fm-cutoff2", CPK_UINT, "(default: 256) second cutoff for FM based on num_lower * num_upper occurrences."); + r.insert(":fm-extra", CPK_UINT, "(default: 0) max. increase on the number of inequalities for each FM variable elimination step."); + } + + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } + + virtual void cleanup() { + ast_manager & m = m_imp->m; + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + (*m_imp)(in, result, mc, pc, core); + } +}; + +tactic * mk_fm_tactic(ast_manager & m, params_ref const & p) { + params_ref s_p = p; + s_p.set_bool(":arith-lhs", true); + s_p.set_bool(":elim-and", true); + s_p.set_bool(":som", true); + return and_then(using_params(mk_simplify_tactic(m, s_p), s_p), + clean(alloc(fm_tactic, m, p))); +} diff --git a/lib/fm_tactic.h b/lib/fm_tactic.h new file mode 100644 index 000000000..f13a1e6ba --- /dev/null +++ b/lib/fm_tactic.h @@ -0,0 +1,33 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + fm_tactic.h + +Abstract: + + Use Fourier-Motzkin to eliminate variables. + This strategy can handle conditional bounds + (i.e., clauses with at most one constraint). + + The strategy mk_occf can be used to put the + formula in OCC form. + +Author: + + Leonardo de Moura (leonardo) 2012-02-04. + +Revision History: + +--*/ +#ifndef _FM_TACTIC_H_ +#define _FM_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_fm_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/for_each_ast.cpp b/lib/for_each_ast.cpp new file mode 100644 index 000000000..f5b8218ae --- /dev/null +++ b/lib/for_each_ast.cpp @@ -0,0 +1,45 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + for_each_ast.cpp + +Abstract: + + For each ast visitor + +Author: + + Leonardo de Moura (leonardo) 2006-10-18. + +Revision History: + +--*/ + +#include"for_each_ast.h" + +struct ast_counter_proc { + unsigned m_num; + ast_counter_proc():m_num(0) {} + void operator()(ast *) { m_num++; } +}; + +unsigned get_num_nodes(ast * n) { + for_each_ast_proc counter; + for_each_ast(counter, n); + return counter.m_num; +} + + +bool for_each_parameter(ptr_vector & stack, ast_mark & visited, unsigned num_args, parameter const * params) { + bool result = true; + for (unsigned i = 0; i < num_args; i++) { + parameter const& p = params[i]; + if (p.is_ast() && !visited.is_marked(p.get_ast())) { + stack.push_back(p.get_ast()); + result = false; + } + } + return result; +} diff --git a/lib/for_each_ast.h b/lib/for_each_ast.h new file mode 100644 index 000000000..0fca62ca9 --- /dev/null +++ b/lib/for_each_ast.h @@ -0,0 +1,274 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + for_each_ast.h + +Abstract: + + Visitor for AST nodes + +Author: + + Leonardo de Moura (leonardo) 2006-10-18. + +Revision History: + +--*/ +#ifndef _FOR_EACH_AST_H_ +#define _FOR_EACH_AST_H_ + +#include"ast.h" +#include"trace.h" +#include"map.h" + +template +bool for_each_ast_args(ptr_vector & stack, ast_mark & visited, unsigned num_args, T * const * args) { + bool result = true; + for (unsigned i = 0; i < num_args; i++) { + T * arg = args[i]; + if (!visited.is_marked(arg)) { + stack.push_back(arg); + result = false; + } + } + return result; +} + +bool for_each_parameter(ptr_vector & stack, ast_mark & visited, unsigned num_args, parameter const * params); + +template +void for_each_ast(ForEachProc & proc, ast_mark & visited, ast * n, bool visit_parameters = false) { + ptr_vector stack; + ast * curr; + + stack.push_back(n); + + while (!stack.empty()) { + curr = stack.back(); + TRACE("for_each_ast", tout << "visiting node: " << curr->get_id() << ", kind: " << get_ast_kind_name(curr->get_kind()) + << ", stack size: " << stack.size() << "\n";); + + if (visited.is_marked(curr)) { + stack.pop_back(); + continue; + } + + switch(curr->get_kind()) { + case AST_SORT: + if (visit_parameters && + !for_each_parameter(stack, visited, to_sort(curr)->get_num_parameters(), to_sort(curr)->get_parameters())) { + break; + } + proc(to_sort(curr)); + visited.mark(curr, true); + stack.pop_back(); + break; + + case AST_VAR: { + var* v = to_var(curr); + proc(v); + visited.mark(curr, true); + stack.pop_back(); + break; + } + + case AST_FUNC_DECL: + if (visit_parameters && + !for_each_parameter(stack, visited, to_func_decl(curr)->get_num_parameters(), to_func_decl(curr)->get_parameters())) { + break; + } + if (!for_each_ast_args(stack, + visited, + to_func_decl(curr)->get_arity(), + to_func_decl(curr)->get_domain())) { + break; + } + if (!visited.is_marked(to_func_decl(curr)->get_range())) { + stack.push_back(to_func_decl(curr)->get_range()); + break; + } + proc(to_func_decl(curr)); + visited.mark(curr, true); + stack.pop_back(); + break; + + case AST_APP: + if (!visited.is_marked(to_app(curr)->get_decl())) { + stack.push_back(to_app(curr)->get_decl()); + break; + } + if (for_each_ast_args(stack, visited, to_app(curr)->get_num_args(), to_app(curr)->get_args())) { + proc(to_app(curr)); + visited.mark(curr, true); + stack.pop_back(); + } + break; + + case AST_QUANTIFIER: + if (!for_each_ast_args(stack, visited, to_quantifier(curr)->get_num_patterns(), + to_quantifier(curr)->get_patterns())) { + break; + } + if (!for_each_ast_args(stack, visited, to_quantifier(curr)->get_num_no_patterns(), + to_quantifier(curr)->get_no_patterns())) { + break; + } + if (!visited.is_marked(to_quantifier(curr)->get_expr())) { + stack.push_back(to_quantifier(curr)->get_expr()); + break; + } + proc(to_quantifier(curr)); + visited.mark(curr, true); + stack.pop_back(); + break; + } + } +} + +template +void for_each_ast(ForEachProc & proc, ast * n, bool visit_parameters = false) { + ast_mark visited; + for_each_ast(proc, visited, n, visit_parameters); +} + +template +struct for_each_ast_proc : public EscapeProc { + void operator()(ast * n) { EscapeProc::operator()(n); } + void operator()(sort * n) { operator()(static_cast(n)); } + void operator()(func_decl * n) { operator()(static_cast(n)); } + void operator()(var * n) { operator()(static_cast(n)); } + void operator()(app * n) { operator()(static_cast(n)); } + void operator()(quantifier * n) { operator()(static_cast(n)); } +}; + +unsigned get_num_nodes(ast * n); + +template +class recurse_ast { + template + class mem_map : public map, ptr_eq > {}; + +public: + static T* recurse(Visitor & visit, ast * aArg) { + unsigned arity; + ast* a; + ast * const * args; + T* result; + ptr_vector stack; + mem_map memoize; + ptr_vector results; + + stack.push_back(aArg); + + while (!stack.empty()) { + a = stack.back(); + + results.reset(); + + if (memoize.find(a, result)) { + stack.pop_back(); + continue; + } + + switch(a->get_kind()) { + + case AST_SORT: + memoize.insert(a, visit.mk_sort(to_sort(a))); + stack.pop_back(); + break; + + case AST_FUNC_DECL: { + arity = to_func_decl(a)->get_arity(); + func_decl * func_decl_ast = to_func_decl(a); + args = (ast * const *)(func_decl_ast->get_domain()); + recurse_list(stack, arity, args, &memoize, results); + if (!memoize.find(func_decl_ast->get_range(), result)) { + stack.push_back(func_decl_ast->get_range()); + } + else if (results.size() == arity) { + result = visit.mk_func_decl(func_decl_ast, result, results); + memoize.insert(a, result); + stack.pop_back(); + } + break; + } + + case AST_APP: { + app * app = to_app(a); + arity = app->get_num_args(); + args = (ast * const *)(app->get_args()); + recurse_list(stack, arity, args, &memoize, results); + if (arity == results.size()) { + result = visit.mk_app(app, results); + memoize.insert(a, result); + stack.pop_back(); + } + break; + } + + case AST_VAR: + memoize.insert(a, visit.mk_var(to_var(a))); + stack.pop_back(); + break; + + case AST_QUANTIFIER: { + quantifier * quantifier_ast = to_quantifier(a); + ptr_vector decl_types; + + if (recurse_quantifier) { + args = (ast * const *) quantifier_ast->get_decl_sorts(); + arity = quantifier_ast->get_num_decls(); + ast* body = quantifier_ast->get_expr(); + + recurse_list(stack, arity, args, &memoize, decl_types); + + if (!memoize.find(body, result)) { + stack.push_back(body); + } + else if (decl_types.size() == arity) { + result = visit.mk_quantifier(quantifier_ast, decl_types, result); + memoize.insert(a, result); + stack.pop_back(); + } + } + else { + result = visit.mk_quantifier(quantifier_ast, decl_types, result); + memoize.insert(a, result); + stack.pop_back(); + } + break; + } + + default: + UNREACHABLE(); + break; + } + } + + if (!memoize.find(aArg, result)) { + UNREACHABLE(); + } + return result; + } + +private: + + template + static void recurse_list(ptr_vector & stack, unsigned arity, AST * const * ast_list, mem_map * memoize, + ptr_vector & results) { + T * result; + for (unsigned i = 0; i < arity; ++i) { + if (memoize->find(ast_list[i], result)) { + results.push_back(result); + } + else { + stack.push_back(ast_list[i]); + } + } + } +}; + +#endif /* _FOR_EACH_AST_H_ */ + diff --git a/lib/for_each_expr.cpp b/lib/for_each_expr.cpp new file mode 100644 index 000000000..cca42b01c --- /dev/null +++ b/lib/for_each_expr.cpp @@ -0,0 +1,65 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + for_each_expr.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-12-28. + +Revision History: + +--*/ + +#include"for_each_expr.h" + +struct expr_counter_proc { + unsigned m_num; + expr_counter_proc():m_num(0) {} + void operator()(var * n) { m_num++; } + void operator()(app * n) { m_num++; if (n->get_decl()->is_associative()) m_num += n->get_num_args() - 2; } + void operator()(quantifier * n) { m_num++; } +}; + +unsigned get_num_exprs(expr * n, expr_mark & visited) { + expr_counter_proc counter; + for_each_expr(counter, visited, n); + return counter.m_num; +} + +unsigned get_num_exprs(expr * n, expr_fast_mark1 & visited) { + expr_counter_proc counter; + for_each_expr_core(counter, visited, n); + return counter.m_num; +} + +unsigned get_num_exprs(expr * n) { + expr_fast_mark1 visited; + return get_num_exprs(n, visited); +} + +namespace has_skolem_functions_ns { + struct found {}; + struct proc { + void operator()(var * n) const {} + void operator()(app const * n) const { if (n->get_decl()->is_skolem() && n->get_num_args() > 0) throw found(); } + void operator()(quantifier * n) const {} + }; +}; + +bool has_skolem_functions(expr * n) { + has_skolem_functions_ns::proc p; + try { + for_each_expr(p, n); + } + catch (has_skolem_functions_ns::found) { + return true; + } + return false; +} diff --git a/lib/for_each_expr.h b/lib/for_each_expr.h new file mode 100644 index 000000000..d0e110ec8 --- /dev/null +++ b/lib/for_each_expr.h @@ -0,0 +1,157 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + for_each_expr.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-12-28. + +Revision History: + +--*/ +#ifndef _FOR_EACH_EXPR_H_ +#define _FOR_EACH_EXPR_H_ + +#include"ast.h" +#include"trace.h" + +template +void for_each_expr_core(ForEachProc & proc, ExprMark & visited, expr * n) { + typedef std::pair frame; + + if (MarkAll || n->get_ref_count() > 1) { + if (visited.is_marked(n)) + return; + visited.mark(n); + } + + sbuffer stack; + + stack.push_back(frame(n, 0)); + while (!stack.empty()) { + start: + frame & fr = stack.back(); + expr * curr = fr.first; + switch (curr->get_kind()) { + case AST_VAR: + proc(to_var(curr)); + stack.pop_back(); + break; + case AST_APP: { + unsigned num_args = to_app(curr)->get_num_args(); + while (fr.second < num_args) { + expr * arg = to_app(curr)->get_arg(fr.second); + fr.second++; + if (MarkAll || arg->get_ref_count() > 1) { + if (visited.is_marked(arg)) + continue; + visited.mark(arg); + } + switch (arg->get_kind()) { + case AST_VAR: + proc(to_var(arg)); + break; + case AST_QUANTIFIER: + stack.push_back(frame(arg, 0)); + goto start; + case AST_APP: + if (to_app(arg)->get_num_args() == 0) { + proc(to_app(arg)); + } + else { + stack.push_back(frame(arg, 0)); + goto start; + } + break; + default: + UNREACHABLE(); + break; + } + } + stack.pop_back(); + proc(to_app(curr)); + break; + } + case AST_QUANTIFIER: { + quantifier * q = to_quantifier(curr); + unsigned num_children = IgnorePatterns ? 1 : q->get_num_children(); + while (fr.second < num_children) { + expr * child = q->get_child(fr.second); + fr.second++; + if (MarkAll || child->get_ref_count() > 1) { + if (visited.is_marked(child)) + continue; + visited.mark(child); + } + stack.push_back(frame(child, 0)); + goto start; + } + stack.pop_back(); + proc(to_quantifier(curr)); + break; + } + default: + UNREACHABLE(); + break; + } + } +} + +template +bool for_each_expr_args(ptr_vector & stack, expr_mark & visited, unsigned num_args, T * const * args) { + bool result = true; + for (unsigned i = 0; i < num_args; i++) { + T * arg = args[i]; + if (!visited.is_marked(arg)) { + stack.push_back(arg); + result = false; + } + } + return result; +} + +template +void for_each_expr(ForEachProc & proc, expr_mark & visited, expr * n) { + for_each_expr_core(proc, visited, n); +} + +template +void for_each_expr(ForEachProc & proc, expr * n) { + expr_mark visited; + for_each_expr_core(proc, visited, n); +} + +template +void quick_for_each_expr(ForEachProc & proc, expr_fast_mark1 & visited, expr * n) { + for_each_expr_core(proc, visited, n); +} + +template +void quick_for_each_expr(ForEachProc & proc, expr * n) { + expr_fast_mark1 visited; + for_each_expr_core(proc, visited, n); +} + +template +struct for_each_expr_proc : public EscapeProc { + void operator()(expr * n) { EscapeProc::operator()(n); } + void operator()(var * n) { operator()(static_cast(n)); } + void operator()(app * n) { operator()(static_cast(n)); } + void operator()(quantifier * n) { operator()(static_cast(n)); } +}; + +unsigned get_num_exprs(expr * n); +unsigned get_num_exprs(expr * n, expr_mark & visited); +unsigned get_num_exprs(expr * n, expr_fast_mark1 & visited); + +bool has_skolem_functions(expr * n); + +#endif /* _FOR_EACH_EXPR_H_ */ + diff --git a/lib/format.cpp b/lib/format.cpp new file mode 100644 index 000000000..68ba5ea9a --- /dev/null +++ b/lib/format.cpp @@ -0,0 +1,212 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + format.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-20. + +Revision History: + +--*/ +#include"format.h" +#include"recurse_expr_def.h" + +namespace format_ns { + + class format_decl_plugin : public decl_plugin { + protected: + sort * m_format_sort; + symbol m_nil; + symbol m_string; + symbol m_indent; + symbol m_compose; + symbol m_choice; + symbol m_line_break; + symbol m_line_break_ext; + + virtual void set_manager(ast_manager * m, family_id id) { + SASSERT(m->is_format_manager()); + decl_plugin::set_manager(m, id); + + m_format_sort = m->mk_sort(symbol("format"), sort_info(id, FORMAT_SORT)); + m->inc_ref(m_format_sort); + } + + public: + format_decl_plugin(): + m_format_sort(0), + m_nil("nil"), + m_string("string"), + m_indent("indent"), + m_compose("compose"), + m_choice("choice"), + m_line_break("cr"), + m_line_break_ext("cr++") { + } + + virtual ~format_decl_plugin() {} + + virtual void finalize() { + if (m_format_sort) + m_manager->dec_ref(m_format_sort); + } + + virtual decl_plugin * mk_fresh() { + return alloc(format_decl_plugin); + } + + virtual sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const* parameters) { + SASSERT(k == FORMAT_SORT); + return m_format_sort; + } + + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + switch (k) { + case OP_NIL: + return m_manager->mk_func_decl(m_nil, arity, domain, m_format_sort, + func_decl_info(m_family_id, OP_NIL)); + case OP_STRING: + return m_manager->mk_func_decl(m_string, arity, domain, m_format_sort, + func_decl_info(m_family_id, OP_STRING, num_parameters, parameters)); + case OP_INDENT: + return m_manager->mk_func_decl(m_indent, arity, domain, m_format_sort, + func_decl_info(m_family_id, OP_INDENT, num_parameters, parameters)); + case OP_COMPOSE: + return m_manager->mk_func_decl(m_compose, arity, domain, m_format_sort, + func_decl_info(m_family_id, OP_COMPOSE)); + case OP_CHOICE: + return m_manager->mk_func_decl(m_choice, arity, domain, m_format_sort, + func_decl_info(m_family_id, OP_CHOICE)); + case OP_LINE_BREAK: + return m_manager->mk_func_decl(m_line_break, arity, domain, m_format_sort, + func_decl_info(m_family_id, OP_LINE_BREAK)); + + case OP_LINE_BREAK_EXT: + return m_manager->mk_func_decl(m_line_break_ext, arity, domain, m_format_sort, + func_decl_info(m_family_id, OP_LINE_BREAK_EXT, num_parameters, parameters)); + default: + return 0; + } + } + }; + + family_id get_format_family_id(ast_manager & m) { + symbol f("format"); + if (!fm(m).has_plugin(f)) + fm(m).register_plugin(f, alloc(format_decl_plugin)); + return fm(m).get_family_id(f); + } + + static family_id fid(ast_manager & m) { + return get_format_family_id(m); + } + + sort * fsort(ast_manager & m) { + return fm(m).mk_sort(fid(m), FORMAT_SORT); + } + + struct flat_visitor { + ast_manager & m_manager; + family_id m_fid; + + flat_visitor(ast_manager & m): + m_manager(fm(m)), + m_fid(fid(m)) { + SASSERT(m_manager.is_format_manager()); + } + + format * visit(var *) { UNREACHABLE(); return 0; } + format * visit(quantifier * q, format *, format * const *, format * const *) { UNREACHABLE(); return 0; } + format * visit(format * n, format * const * children) { + if (is_app_of(n, m_fid, OP_LINE_BREAK)) + return mk_string(m_manager, " "); + else if (is_app_of(n, m_fid, OP_LINE_BREAK_EXT)) + return mk_string(m_manager, n->get_decl()->get_parameter(0).get_symbol().bare_str()); + else if (is_app_of(n, m_fid, OP_CHOICE)) + return to_app(n->get_arg(0)); + else + return m_manager.mk_app(n->get_decl(), n->get_num_args(), (expr *const*) children); + } + }; + + format * flat(ast_manager & m, format * f) { + flat_visitor v(m); + recurse_expr r(v); + return r(f); + } + + format * mk_string(ast_manager & m, char const * str) { + symbol s(str); + parameter p(s); + return fm(m).mk_app(fid(m), OP_STRING, 1, &p, 0, 0); + } + + format * mk_int(ast_manager & m, int i) { + static char buffer[128]; +#ifdef _WINDOWS + sprintf_s(buffer, ARRAYSIZE(buffer), "%d", i); +#else + sprintf(buffer, "%d", i); +#endif + return mk_string(m, buffer); + } + + format * mk_unsigned(ast_manager & m, unsigned u) { + static char buffer[128]; +#ifdef _WINDOWS + sprintf_s(buffer, ARRAYSIZE(buffer), "%u", u); +#else + sprintf(buffer, "%u", u); +#endif + return mk_string(m, buffer); + } + + format * mk_indent(ast_manager & m, unsigned i, format * f) { + parameter p(i); + expr * e = static_cast(f); + return fm(m).mk_app(fid(m), OP_INDENT, 1, &p, 1, &e); + } + + format * mk_line_break(ast_manager & m) { + return fm(m).mk_app(fid(m), OP_LINE_BREAK); + } + + format * mk_choice(ast_manager & m, format * f1, format * f2) { + return fm(m).mk_app(fid(m), OP_CHOICE, f1, f2); + } + + format * mk_group(ast_manager & m, format * f) { + return mk_choice(m, flat(m, f), f); + } + + format * mk_compose(ast_manager & m, unsigned num_children, format * const * children) { + return fm(m).mk_app(fid(m), OP_COMPOSE, num_children, (expr * const *) children); + } + + format * mk_compose(ast_manager & m, format * f1, format * f2) { + return fm(m).mk_app(fid(m), OP_COMPOSE, f1, f2); + } + + format * mk_compose(ast_manager & m, format * f1, format * f2, format * f3) { + return fm(m).mk_app(fid(m), OP_COMPOSE, f1, f2, f3); + } + + format * mk_compose(ast_manager & m, format * f1, format * f2, format * f3, format * f4) { + expr * f[4] = { f1, f2, f3, f4 }; + return fm(m).mk_app(fid(m), OP_COMPOSE, 4, f); + } + + format * mk_nil(ast_manager & m) { + return fm(m).mk_app(fid(m), OP_NIL); + } + +}; diff --git a/lib/format.h b/lib/format.h new file mode 100644 index 000000000..780e76251 --- /dev/null +++ b/lib/format.h @@ -0,0 +1,197 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + format.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-20. + +Revision History: + +--*/ +#ifndef _FORMAT_H_ +#define _FORMAT_H_ + +#include"ast.h" + +namespace format_ns { + typedef app format; + + typedef app_ref format_ref; + + enum format_sort_kind { + FORMAT_SORT + }; + + enum format_op_kind { + OP_NIL, + OP_STRING, + OP_INDENT, + OP_COMPOSE, + OP_CHOICE, + OP_LINE_BREAK, + OP_LINE_BREAK_EXT + }; + + struct f2f { + format * operator()(format * f) { return f; } + }; + + /** + \brief Return the "format manager" associated with the given ast_manager. + */ + inline ast_manager & fm(ast_manager & m) { + return m.get_format_manager(); + } + + family_id get_format_family_id(ast_manager & m); + + format * mk_string(ast_manager & m, char const * str); + format * mk_int(ast_manager & m, int i); + format * mk_unsigned(ast_manager & m, unsigned u); + format * mk_indent(ast_manager & m, unsigned i, format * f); + format * mk_line_break(ast_manager & m); + format * mk_group(ast_manager & m, format * f); + format * mk_compose(ast_manager & m, unsigned num_children, format * const * children); + format * mk_compose(ast_manager & m, format * f1, format * f2); + format * mk_compose(ast_manager & m, format * f1, format * f2, format * f3); + format * mk_compose(ast_manager & m, format * f1, format * f2, format * f3, format * f4); + format * mk_nil(ast_manager & m); + format * mk_choice(ast_manager & m, format * f1, format * f2); + + template + format * mk_seq(ast_manager & m, It const & begin, It const & end, ToDoc proc) { + app_ref_buffer children(fm(m)); + for (It it = begin; it != end; ++it) { + format * curr = proc(*it); + if (curr->get_decl_kind() != OP_NIL) { + children.push_back(mk_line_break(m)); + children.push_back(curr); + } + } + return mk_compose(m, children.size(), children.c_ptr()); + } + + /** + (header elem_1 + elem_2 + ... + elem_n) + */ + template + format * mk_seq1(ast_manager & m, It const & begin, It const & end, ToDoc proc, char const * header, + char const * lp = "(", char const * rp = ")") { + if (begin == end) + return mk_compose(m, mk_string(m, lp), mk_string(m, header), mk_string(m, rp)); + unsigned indent = static_cast(strlen(lp) + strlen(header) + 1); + It it = begin; + format * first = proc(*it); + ++it; + return mk_group(m, mk_compose(m, + mk_string(m, lp), + mk_string(m, header), + mk_indent(m, indent, + mk_compose(m, + mk_string(m, " "), + first, + mk_seq(m, it, end, proc), + mk_string(m, rp))))); + } + +#define FORMAT_DEFAULT_INDENT 2 + + /** + (header + elem_1 + ... + elem_n) + */ + template + format * mk_seq2(ast_manager & m, It const & begin, It const & end, ToDoc proc, char const * header, + unsigned indent = FORMAT_DEFAULT_INDENT, char const * lp = "(", char const * rp = ")") { + + if (begin == end) + return mk_compose(m, mk_string(m, lp), mk_string(m, header), mk_string(m, rp)); + return mk_group(m, mk_compose(m, + mk_indent(m, static_cast(strlen(lp)), + mk_compose(m, mk_string(m, lp), mk_string(m, header))), + mk_indent(m, indent, + mk_compose(m, mk_seq(m, begin, end, proc), mk_string(m, rp))))); + } + + /** + (header elem_1 + ... + elem_i + elem_{i+1} + ... + elem_n) + */ + template + format * mk_seq3(ast_manager & m, It const & begin, It const & end, ToDoc proc, char const * header, unsigned i = 1, + unsigned indent = FORMAT_DEFAULT_INDENT, char const * lp = "(", char const * rp = ")") { + SASSERT(i >= 1); + if (begin == end) + return mk_compose(m, mk_string(m, lp), mk_string(m, header), mk_string(m, rp)); + unsigned idx = 0; + It end1 = begin; + for (;end1 != end && idx < i; ++end1, ++idx) + ; + It it = begin; + format * first = proc(*it); + ++it; + return mk_group(m, + mk_compose(m, + mk_compose(m, mk_string(m, lp), mk_string(m, header)), + mk_group(m, mk_indent(m, static_cast(strlen(header) + strlen(lp) + 1), + mk_compose(m, mk_string(m, " "), first, + mk_seq(m, it, end1, proc)))), + mk_indent(m, indent, mk_seq(m, end1, end, proc)), + mk_string(m, rp))); + } + + /** + (elem_1 + elem_2 + ... + elem_n) + */ + template + format * mk_seq4(ast_manager & m, It const & begin, It const & end, ToDoc proc, unsigned indent = FORMAT_DEFAULT_INDENT, + char const * lp = "(", char const * rp = ")") { + if (begin == end) + return mk_compose(m, mk_string(m, lp), mk_string(m, rp)); + unsigned indent1 = static_cast(strlen(lp)); + It it = begin; + format * first = proc(*it); + ++it; + return mk_group(m, mk_compose(m, + mk_indent(m, indent1, mk_compose(m, mk_string(m, lp), first)), + mk_indent(m, indent, mk_compose(m, + mk_seq(m, it, end, proc), + mk_string(m, rp))))); + } + + /** + (elem_1 + elem_2 + ... + elem_n) + */ + template + format * mk_seq5(ast_manager & m, It const & begin, It const & end, ToDoc proc, + char const * lp = "(", char const * rp = ")") { + return mk_seq4(m, begin, end, proc, static_cast(strlen(lp)), lp, rp); + } + +}; + +#endif /* _FORMAT_H_ */ + diff --git a/lib/fpa2bv_converter.cpp b/lib/fpa2bv_converter.cpp new file mode 100644 index 000000000..bfb23fd12 --- /dev/null +++ b/lib/fpa2bv_converter.cpp @@ -0,0 +1,2112 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + fpa2bv_converter.cpp + +Abstract: + + Conversion routines for Floating Point -> Bit-Vector + +Author: + + Christoph (cwinter) 2012-02-09 + +Notes: + +--*/ +#include"ast_smt2_pp.h" +#include"well_sorted.h" + +#include"fpa2bv_converter.h" + +#define BVULT(X,Y,R) { expr_ref bvult_eq(m), bvult_not(m); m_simp.mk_eq(X, Y, bvult_eq); m_simp.mk_not(bvult_eq, bvult_not); m_simp.mk_and(m_bv_util.mk_ule(X,Y), bvult_not, R); } +#define BVSLT(X,Y,R) { expr_ref bvslt_eq(m), bvslt_not(m); m_simp.mk_eq(X, Y, bvslt_eq); m_simp.mk_not(bvslt_eq, bvslt_not); m_simp.mk_and(m_bv_util.mk_sle(X,Y), bvslt_not, R); } + +fpa2bv_converter::fpa2bv_converter(ast_manager & m) : + m(m), + m_simp(m), + m_util(m), + m_mpf_manager(m_util.fm()), + m_mpz_manager(m_mpf_manager.mpz_manager()), + m_bv_util(m), + extra_assertions(m) { + m_plugin = static_cast(m.get_plugin(m.get_family_id("float"))); +} + +fpa2bv_converter::~fpa2bv_converter() { + dec_ref_map_key_values(m, m_const2bv); + dec_ref_map_key_values(m, m_rm_const2bv); +} + +void fpa2bv_converter::mk_eq(expr * a, expr * b, expr_ref & result) { + SASSERT(is_app_of(a, m_plugin->get_family_id(), OP_TO_FLOAT)); + SASSERT(is_app_of(b, m_plugin->get_family_id(), OP_TO_FLOAT)); + + expr_ref sgn(m), s(m), e(m); + m_simp.mk_eq(to_app(a)->get_arg(0), to_app(b)->get_arg(0), sgn); + m_simp.mk_eq(to_app(a)->get_arg(1), to_app(b)->get_arg(1), s); + m_simp.mk_eq(to_app(a)->get_arg(2), to_app(b)->get_arg(2), e); + + // The SMT FPA theory asks for _one_ NaN value, but the bit-blasting + // has many, like IEEE754. This encoding of equality makes it look like + // a single NaN again. + expr_ref both_the_same(m), a_is_nan(m), b_is_nan(m), both_are_nan(m); + m_simp.mk_and(sgn, s, e, both_the_same); + mk_is_nan(a, a_is_nan); + mk_is_nan(b, b_is_nan); + m_simp.mk_and(a_is_nan, b_is_nan, both_are_nan); + m_simp.mk_or(both_are_nan, both_the_same, result); +} + +void fpa2bv_converter::mk_ite(expr * c, expr * t, expr * f, expr_ref & result) { + SASSERT(is_app_of(t, m_plugin->get_family_id(), OP_TO_FLOAT)); + SASSERT(is_app_of(f, m_plugin->get_family_id(), OP_TO_FLOAT)); + + expr_ref sgn(m), s(m), e(m); + m_simp.mk_ite(c, to_app(t)->get_arg(0), to_app(f)->get_arg(0), sgn); + m_simp.mk_ite(c, to_app(t)->get_arg(1), to_app(f)->get_arg(1), s); + m_simp.mk_ite(c, to_app(t)->get_arg(2), to_app(f)->get_arg(2), e); + + mk_triple(sgn, s, e, result); +} + +void fpa2bv_converter::mk_value(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); + + unsigned sbits = v.get_sbits(); + unsigned ebits = v.get_ebits(); + + bool sign = m_util.fm().sgn(v); + mpz const & sig = m_util.fm().sig(v); + mpf_exp_t const & exp = m_util.fm().exp(v); + + if (m_util.fm().is_nan(v)) + mk_nan(f, result); + else if (m_util.fm().is_inf(v)) { + if (m_util.fm().sgn(v)) + mk_minus_inf(f, result); + else + mk_plus_inf(f, result); + } + else { + expr_ref bv_sgn(m), bv_sig(m), e(m), biased_exp(m); + bv_sgn = m_bv_util.mk_numeral( (sign) ? 1 : 0, 1); + bv_sig = m_bv_util.mk_numeral(rational(sig), sbits-1); + e = m_bv_util.mk_numeral(exp, ebits); + + mk_bias(e, biased_exp); + + mk_triple(bv_sgn, bv_sig, biased_exp, result); + TRACE("fpa2bv_dbg", tout << "value of [" << sign << " " << m_mpz_manager.to_string(sig) << " " << exp << "] is " + << mk_ismt2_pp(result, m) << std::endl;); + + } +} + +void fpa2bv_converter::mk_const(func_decl * f, expr_ref & result) { + SASSERT(f->get_family_id() == null_family_id); + SASSERT(f->get_arity() == 0); + expr * r; + if (m_const2bv.find(f, r)) { + result = r; + } + else { + sort * srt = f->get_range(); + SASSERT(is_float(srt)); + unsigned ebits = m_util.get_ebits(srt); + unsigned sbits = m_util.get_sbits(srt); + +#ifdef _DEBUG + std::string p("fpa2bv"); + std::string name = f->get_name().str(); + + expr * sgn = m.mk_fresh_const((p + "_sgn_" + name).c_str(), m_bv_util.mk_sort(1)); + expr * s = m.mk_fresh_const((p + "_sig_" + name).c_str(), m_bv_util.mk_sort(sbits-1)); + expr * e = m.mk_fresh_const((p + "_exp_" + name).c_str(), m_bv_util.mk_sort(ebits)); +#else + expr * sgn = m.mk_fresh_const(0, m_bv_util.mk_sort(1)); + expr * s = m.mk_fresh_const(0, m_bv_util.mk_sort(sbits-1)); + expr * e = m.mk_fresh_const(0, m_bv_util.mk_sort(ebits)); +#endif + + mk_triple(sgn, s, e, result); + + m_const2bv.insert(f, result); + m.inc_ref(f); + m.inc_ref(result); + } +} + + +void fpa2bv_converter::mk_rm_const(func_decl * f, expr_ref & result) { + SASSERT(f->get_family_id() == null_family_id); + SASSERT(f->get_arity() == 0); + expr * r; + if (m_rm_const2bv.find(f, r)) { + result = r; + } + else { + SASSERT(is_rm_sort(f->get_range())); + + result = m.mk_fresh_const( + #ifdef _DEBUG + "fpa2bv_rm" + #else + 0 + #endif + , m_bv_util.mk_sort(3)); + + m_rm_const2bv.insert(f, result); + m.inc_ref(f); + m.inc_ref(result); + } +} + +void fpa2bv_converter::mk_plus_inf(func_decl * f, expr_ref & result) { + sort * srt = f->get_range(); + SASSERT(is_float(srt)); + unsigned sbits = m_util.get_sbits(srt); + unsigned ebits = m_util.get_ebits(srt); + expr_ref top_exp(m); + mk_top_exp(ebits, top_exp); + mk_triple(m_bv_util.mk_numeral(0, 1), + m_bv_util.mk_numeral(0, sbits-1), + top_exp, + result); +} + +void fpa2bv_converter::mk_minus_inf(func_decl * f, expr_ref & result) { + sort * srt = f->get_range(); + SASSERT(is_float(srt)); + unsigned sbits = m_util.get_sbits(srt); + unsigned ebits = m_util.get_ebits(srt); + expr_ref top_exp(m); + mk_top_exp(ebits, top_exp); + mk_triple(m_bv_util.mk_numeral(1, 1), + m_bv_util.mk_numeral(0, sbits-1), + top_exp, + result); +} + +void fpa2bv_converter::mk_nan(func_decl * f, expr_ref & result) { + sort * srt = f->get_range(); + SASSERT(is_float(srt)); + unsigned sbits = m_util.get_sbits(srt); + unsigned ebits = m_util.get_ebits(srt); + expr_ref top_exp(m); + mk_top_exp(ebits, top_exp); + mk_triple(m_bv_util.mk_numeral(0, 1), + m_bv_util.mk_numeral(1, sbits-1), + top_exp, + result); +} + +void fpa2bv_converter::mk_nzero(func_decl *f, expr_ref & result) { + sort * srt = f->get_range(); + SASSERT(is_float(srt)); + unsigned sbits = m_util.get_sbits(srt); + unsigned ebits = m_util.get_ebits(srt); + expr_ref bot_exp(m); + mk_bot_exp(ebits, bot_exp); + mk_triple(m_bv_util.mk_numeral(1, 1), + m_bv_util.mk_numeral(0, sbits-1), + bot_exp, + result); +} + +void fpa2bv_converter::mk_pzero(func_decl *f, expr_ref & result) { + sort * srt = f->get_range(); + SASSERT(is_float(srt)); + unsigned sbits = m_util.get_sbits(srt); + unsigned ebits = m_util.get_ebits(srt); + expr_ref bot_exp(m); + mk_bot_exp(ebits, bot_exp); + mk_triple(m_bv_util.mk_numeral(0, 1), + m_bv_util.mk_numeral(0, sbits-1), + bot_exp, + result); +} + +void fpa2bv_converter::add_core(unsigned sbits, unsigned ebits, expr_ref & rm, + expr_ref & c_sgn, expr_ref & c_sig, expr_ref & c_exp, expr_ref & d_sgn, expr_ref & d_sig, expr_ref & d_exp, + expr_ref & res_sgn, expr_ref & res_sig, expr_ref & res_exp) +{ + // c/d are now such that c_exp >= d_exp. + + expr_ref exp_delta(m); + exp_delta = m_bv_util.mk_bv_sub(c_exp, d_exp); + + dbg_decouple("fpa2bv_add_exp_delta", exp_delta); + + // cap the delta + expr_ref cap(m); + cap = m_bv_util.mk_numeral(sbits+2, ebits); + m_simp.mk_ite(m_bv_util.mk_ule(cap, exp_delta), cap, exp_delta, exp_delta); + + dbg_decouple("fpa2bv_add_exp_delta_capped", exp_delta); + + // Three extra bits for c/d + c_sig = m_bv_util.mk_concat(c_sig, m_bv_util.mk_numeral(0, 3)); + d_sig = m_bv_util.mk_concat(d_sig, m_bv_util.mk_numeral(0, 3)); + + SASSERT(is_well_sorted(m, c_sig)); + SASSERT(is_well_sorted(m, d_sig)); + + // Alignment shift with sticky bit computation. + expr_ref big_d_sig(m); + big_d_sig = m_bv_util.mk_concat(d_sig, m_bv_util.mk_numeral(0, sbits+3)); + + SASSERT(is_well_sorted(m, big_d_sig)); + + expr_ref shifted_big(m), shifted_d_sig(m), sticky_raw(m), sticky(m); + shifted_big = m_bv_util.mk_bv_lshr(big_d_sig, m_bv_util.mk_concat(m_bv_util.mk_numeral(0, (2*(sbits+3))-ebits), exp_delta)); + shifted_d_sig = m_bv_util.mk_extract((2*(sbits+3)-1), (sbits+3), shifted_big); + SASSERT(is_well_sorted(m, shifted_d_sig)); + + sticky_raw = m_bv_util.mk_extract(sbits+2, 0, shifted_big); + expr_ref sticky_eq(m); + m_simp.mk_eq(sticky_raw, m_bv_util.mk_numeral(0, sbits+3), sticky_eq); + m_simp.mk_ite(sticky_eq, + m_bv_util.mk_numeral(0, sbits+3), + m_bv_util.mk_numeral(1, sbits+3), + sticky); + SASSERT(is_well_sorted(m, sticky)); + + expr * or_args[2] = { shifted_d_sig.get(), sticky.get() }; + shifted_d_sig = m_bv_util.mk_bv_or(2, or_args); + SASSERT(is_well_sorted(m, shifted_d_sig)); + + expr_ref eq_sgn(m), neq_sgn(m); + m_simp.mk_eq(c_sgn, d_sgn, eq_sgn); + + // two extra bits for catching the overflow. + c_sig = m_bv_util.mk_zero_extend(2, c_sig); + shifted_d_sig = m_bv_util.mk_zero_extend(2, shifted_d_sig); + + SASSERT(m_bv_util.get_bv_size(c_sig) == sbits+5); + SASSERT(m_bv_util.get_bv_size(shifted_d_sig) == sbits+5); + + dbg_decouple("fpa2bv_add_c_sig", c_sig); + dbg_decouple("fpa2bv_add_shifted_d_sig", shifted_d_sig); + + expr_ref sum(m); + m_simp.mk_ite(eq_sgn, + m_bv_util.mk_bv_add(c_sig, shifted_d_sig), + m_bv_util.mk_bv_sub(c_sig, shifted_d_sig), + sum); + + SASSERT(is_well_sorted(m, sum)); + + dbg_decouple("fpa2bv_add_sum", sum); + + expr_ref sign_bv(m), n_sum(m); + sign_bv = m_bv_util.mk_extract(sbits+4, sbits+4, sum); + n_sum = m_bv_util.mk_bv_neg(sum); + + dbg_decouple("fpa2bv_add_sign_bv", sign_bv); + dbg_decouple("fpa2bv_add_n_sum", n_sum); + + family_id bvfid = m_bv_util.get_fid(); + + expr_ref res_sgn_c1(m), res_sgn_c2(m), res_sgn_c3(m); + res_sgn_c1 = m.mk_app(bvfid, OP_BAND, m_bv_util.mk_bv_not(c_sgn), d_sgn, sign_bv); + res_sgn_c2 = m.mk_app(bvfid, OP_BAND, c_sgn, m_bv_util.mk_bv_not(d_sgn), m_bv_util.mk_bv_not(sign_bv)); + res_sgn_c3 = m.mk_app(bvfid, OP_BAND, c_sgn, d_sgn); + expr * res_sgn_or_args[3] = { res_sgn_c1.get(), res_sgn_c2.get(), res_sgn_c3.get() }; + res_sgn = m_bv_util.mk_bv_or(3, res_sgn_or_args); + + expr_ref res_sig_eq(m), sig_abs(m); + m_simp.mk_eq(sign_bv, m_bv_util.mk_numeral(1, 1), res_sig_eq); + m_simp.mk_ite(res_sig_eq, n_sum, sum, sig_abs); + + dbg_decouple("fpa2bv_add_sig_abs", sig_abs); + + res_sig = m_bv_util.mk_extract(sbits+3, 0, sig_abs); + res_exp = m_bv_util.mk_sign_extend(2, c_exp); // rounder requires 2 extra bits! +} + +void fpa2bv_converter::mk_add(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num == 3); + + expr_ref rm(m), x(m), y(m); + rm = args[0]; + x = args[1]; + y = args[2]; + + expr_ref nan(m), nzero(m), pzero(m); + mk_nan(f, nan); + mk_nzero(f, nzero); + mk_pzero(f, pzero); + + expr_ref x_is_nan(m), x_is_zero(m), x_is_pos(m), x_is_neg(m), x_is_inf(m); + expr_ref y_is_nan(m), y_is_zero(m), y_is_pos(m), y_is_neg(m), y_is_inf(m); + mk_is_nan(x, x_is_nan); + mk_is_zero(x, x_is_zero); + mk_is_pos(x, x_is_pos); + mk_is_neg(x, x_is_neg); + mk_is_inf(x, x_is_inf); + mk_is_nan(y, y_is_nan); + mk_is_zero(y, y_is_zero); + mk_is_pos(y, y_is_pos); + mk_is_neg(y, y_is_neg); + mk_is_inf(y, y_is_inf); + + dbg_decouple("fpa2bv_add_x_is_nan", x_is_nan); + dbg_decouple("fpa2bv_add_x_is_zero", x_is_zero); + dbg_decouple("fpa2bv_add_x_is_pos", x_is_pos); + dbg_decouple("fpa2bv_add_x_is_neg", x_is_neg); + dbg_decouple("fpa2bv_add_x_is_inf", x_is_inf); + dbg_decouple("fpa2bv_add_y_is_nan", y_is_nan); + dbg_decouple("fpa2bv_add_y_is_zero", y_is_zero); + dbg_decouple("fpa2bv_add_y_is_pos", y_is_pos); + dbg_decouple("fpa2bv_add_y_is_neg", y_is_neg); + dbg_decouple("fpa2bv_add_y_is_inf", y_is_inf); + + expr_ref c1(m), c2(m), c3(m), c4(m), c5(m), c6(m); + expr_ref v1(m), v2(m), v3(m), v4(m), v5(m), v6(m), v7(m); + + m_simp.mk_or(x_is_nan, y_is_nan, c1); + v1 = nan; + + mk_is_inf(x, c2); + expr_ref nx(m), ny(m), nx_xor_ny(m), inf_xor(m); + mk_is_neg(x, nx); + mk_is_neg(y, ny); + m_simp.mk_xor(nx, ny, nx_xor_ny); + m_simp.mk_and(y_is_inf, nx_xor_ny, inf_xor); + mk_ite(inf_xor, nan, x, v2); + + mk_is_inf(y, c3); + expr_ref xy_is_neg(m), v3_and(m); + m_simp.mk_xor(x_is_neg, y_is_neg, xy_is_neg); + m_simp.mk_and(x_is_inf, xy_is_neg, v3_and); + mk_ite(v3_and, nan, y, v3); + + expr_ref rm_is_to_neg(m), v4_and(m); + m_simp.mk_and(x_is_zero, y_is_zero, c4); + mk_is_rm(rm, BV_RM_TO_NEGATIVE, rm_is_to_neg); + mk_ite(rm_is_to_neg, nzero, pzero, v4); + m_simp.mk_and(x_is_neg, y_is_neg, v4_and); + mk_ite(v4_and, x, v4, v4); + + c5 = x_is_zero; + v5 = y; + + c6 = y_is_zero; + v6 = x; + + // Actual addition. + unsigned ebits = m_util.get_ebits(f->get_range()); + unsigned sbits = m_util.get_sbits(f->get_range()); + SASSERT(ebits <= sbits); + + expr_ref a_sgn(m), a_sig(m), a_exp(m), b_sgn(m), b_sig(m), b_exp(m); + unpack(x, a_sgn, a_sig, a_exp, true); + unpack(y, b_sgn, b_sig, b_exp, false); + + dbg_decouple("fpa2bv_add_unpack_a_sgn", a_sgn); + dbg_decouple("fpa2bv_add_unpack_a_sig", a_sig); + dbg_decouple("fpa2bv_add_unpack_a_exp", a_exp); + dbg_decouple("fpa2bv_add_unpack_b_sgn", b_sgn); + dbg_decouple("fpa2bv_add_unpack_b_sig", b_sig); + dbg_decouple("fpa2bv_add_unpack_b_exp", b_exp); + + expr_ref swap_cond(m); + swap_cond = m_bv_util.mk_sle(a_exp, b_exp); + + expr_ref c_sgn(m), c_sig(m), c_exp(m), d_sgn(m), d_sig(m), d_exp(m); + m_simp.mk_ite(swap_cond, b_sgn, a_sgn, c_sgn); + m_simp.mk_ite(swap_cond, b_sig, a_sig, c_sig); // has sbits + m_simp.mk_ite(swap_cond, b_exp, a_exp, c_exp); // has ebits + m_simp.mk_ite(swap_cond, a_sgn, b_sgn, d_sgn); + m_simp.mk_ite(swap_cond, a_sig, b_sig, d_sig); // has sbits + m_simp.mk_ite(swap_cond, a_exp, b_exp, d_exp); // has ebits + + expr_ref res_sgn(m), res_sig(m), res_exp(m); + add_core(sbits, ebits, rm, + c_sgn, c_sig, c_exp, d_sgn, d_sig, d_exp, + res_sgn, res_sig, res_exp); + + expr_ref is_zero_sig(m); + m_simp.mk_eq(res_sig, m_bv_util.mk_numeral(0, sbits+4), is_zero_sig); + + SASSERT(is_well_sorted(m, is_zero_sig)); + + dbg_decouple("fpa2bv_add_is_zero_sig", is_zero_sig); + + expr_ref zero_case(m); + mk_ite(rm_is_to_neg, nzero, pzero, zero_case); + + expr_ref rounded(m); + round(f->get_range(), rm, res_sgn, res_sig, res_exp, rounded); + + mk_ite(is_zero_sig, zero_case, rounded, v7); + + mk_ite(c6, v6, v7, result); + mk_ite(c5, v5, result, result); + mk_ite(c4, v4, result, result); + mk_ite(c3, v3, result, result); + mk_ite(c2, v2, result, result); + mk_ite(c1, v1, result, result); + + SASSERT(is_well_sorted(m, result)); + + TRACE("fpa2bv_add", tout << "ADD = " << mk_ismt2_pp(result, m) << std::endl; ); +} + +void fpa2bv_converter::mk_sub(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num == 3); + expr_ref t(m); + mk_uminus(f, 1, &args[2], t); + expr * nargs[3] = { args[0], args[1], t.get() }; + mk_add(f, 3, nargs, result); +} + +void fpa2bv_converter::mk_uminus(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num == 1); + expr * sgn, * s, * e; + split(args[0], sgn, s, e); + expr_ref c(m), nsgn(m); + mk_is_nan(args[0], c); + nsgn = m_bv_util.mk_bv_not(sgn); + expr_ref r_sgn(m); + m_simp.mk_ite(c, sgn, nsgn, r_sgn); + mk_triple(r_sgn, s, e, result); +} + +void fpa2bv_converter::mk_mul(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num == 3); + + expr_ref rm(m), x(m), y(m); + rm = args[0]; + x = args[1]; + y = args[2]; + + expr_ref nan(m), nzero(m), pzero(m), ninf(m), pinf(m); + mk_nan(f, nan); + mk_nzero(f, nzero); + mk_pzero(f, pzero); + mk_minus_inf(f, ninf); + mk_plus_inf(f, pinf); + + expr_ref x_is_nan(m), x_is_zero(m), x_is_pos(m), x_is_inf(m); + expr_ref y_is_nan(m), y_is_zero(m), y_is_pos(m), y_is_inf(m); + mk_is_nan(x, x_is_nan); + mk_is_zero(x, x_is_zero); + mk_is_pos(x, x_is_pos); + mk_is_inf(x, x_is_inf); + mk_is_nan(y, y_is_nan); + mk_is_zero(y, y_is_zero); + mk_is_pos(y, y_is_pos); + mk_is_inf(y, y_is_inf); + + dbg_decouple("fpa2bv_mul_x_is_nan", x_is_nan); + dbg_decouple("fpa2bv_mul_x_is_zero", x_is_zero); + dbg_decouple("fpa2bv_mul_x_is_pos", x_is_pos); + dbg_decouple("fpa2bv_mul_x_is_inf", x_is_inf); + dbg_decouple("fpa2bv_mul_y_is_nan", y_is_nan); + dbg_decouple("fpa2bv_mul_y_is_zero", y_is_zero); + dbg_decouple("fpa2bv_mul_y_is_pos", y_is_pos); + dbg_decouple("fpa2bv_mul_y_is_inf", y_is_inf); + + expr_ref c1(m), c2(m), c3(m), c4(m), c5(m), c6(m); + expr_ref v1(m), v2(m), v3(m), v4(m), v5(m), v6(m), v7(m); + + // (x is NaN) || (y is NaN) -> NaN + m_simp.mk_or(x_is_nan, y_is_nan, c1); + v1 = nan; + + // (x is +oo) -> if (y is 0) then NaN else inf with y's sign. + mk_is_pinf(x, c2); + expr_ref y_sgn_inf(m); + mk_ite(y_is_pos, pinf, ninf, y_sgn_inf); + mk_ite(y_is_zero, nan, y_sgn_inf, v2); + + // (y is +oo) -> if (x is 0) then NaN else inf with x's sign. + mk_is_pinf(y, c3); + expr_ref x_sgn_inf(m); + mk_ite(x_is_pos, pinf, ninf, x_sgn_inf); + mk_ite(x_is_zero, nan, x_sgn_inf, v3); + + // (x is -oo) -> if (y is 0) then NaN else inf with -y's sign. + mk_is_ninf(x, c4); + expr_ref neg_y_sgn_inf(m); + mk_ite(y_is_pos, ninf, pinf, neg_y_sgn_inf); + mk_ite(y_is_zero, nan, neg_y_sgn_inf, v4); + + // (y is -oo) -> if (x is 0) then NaN else inf with -x's sign. + mk_is_ninf(y, c5); + expr_ref neg_x_sgn_inf(m); + mk_ite(x_is_pos, ninf, pinf, neg_x_sgn_inf); + mk_ite(x_is_zero, nan, neg_x_sgn_inf, v5); + + // (x is 0) || (y is 0) -> x but with sign = x.sign ^ y.sign + m_simp.mk_or(x_is_zero, y_is_zero, c6); + expr_ref sign_xor(m); + m_simp.mk_xor(x_is_pos, y_is_pos, sign_xor); + mk_ite(sign_xor, nzero, pzero, v6); + + // else comes the actual multiplication. + unsigned ebits = m_util.get_ebits(f->get_range()); + unsigned sbits = m_util.get_sbits(f->get_range()); + SASSERT(ebits <= sbits); + + expr_ref a_sgn(m), a_sig(m), a_exp(m), b_sgn(m), b_sig(m), b_exp(m); + unpack(x, a_sgn, a_sig, a_exp, true); + unpack(y, b_sgn, b_sig, b_exp, true); + + expr_ref lz_a(m), lz_b(m); + mk_leading_zeros(a_sig, ebits+2, lz_a); + mk_leading_zeros(b_sig, ebits+2, lz_b); + + expr_ref a_sig_ext(m), b_sig_ext(m); + a_sig_ext = m_bv_util.mk_zero_extend(sbits, a_sig); + b_sig_ext = m_bv_util.mk_zero_extend(sbits, b_sig); + + expr_ref a_exp_ext(m), b_exp_ext(m); + a_exp_ext = m_bv_util.mk_sign_extend(2, a_exp); + b_exp_ext = m_bv_util.mk_sign_extend(2, b_exp); + + expr_ref res_sgn(m), res_sig(m), res_exp(m); + expr * signs[2] = { a_sgn, b_sgn }; + res_sgn = m_bv_util.mk_bv_xor(2, signs); + + dbg_decouple("fpa2bv_mul_res_sgn", res_sgn); + + res_exp = m_bv_util.mk_bv_sub( + m_bv_util.mk_bv_add(a_exp_ext, b_exp_ext), + m_bv_util.mk_bv_add(lz_a, lz_b)); + + expr_ref product(m); + product = m_bv_util.mk_bv_mul(a_sig_ext, b_sig_ext); + + dbg_decouple("fpa2bv_mul_product", product); + + SASSERT(m_bv_util.get_bv_size(product) == 2*sbits); + + expr_ref sticky(m); + sticky = m.mk_app(m_bv_util.get_fid(), OP_BREDOR, m_bv_util.mk_extract(sbits-4, 0, product)); + res_sig = m_bv_util.mk_concat(m_bv_util.mk_extract(2*sbits-1, sbits-3, product), sticky); + + round(f->get_range(), rm, res_sgn, res_sig, res_exp, v7); + + // And finally, we tie them together. + mk_ite(c6, v6, v7, result); + mk_ite(c5, v5, result, result); + mk_ite(c4, v4, result, result); + mk_ite(c3, v3, result, result); + mk_ite(c2, v2, result, result); + mk_ite(c1, v1, result, result); + + SASSERT(is_well_sorted(m, result)); + + TRACE("fpa2bv_mul", tout << "MUL = " << mk_ismt2_pp(result, m) << std::endl; ); +} + +void fpa2bv_converter::mk_div(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num == 3); + + expr_ref rm(m), x(m), y(m); + rm = args[0]; + x = args[1]; + y = args[2]; + + expr_ref nan(m), nzero(m), pzero(m), ninf(m), pinf(m); + mk_nan(f, nan); + mk_nzero(f, nzero); + mk_pzero(f, pzero); + mk_minus_inf(f, ninf); + mk_plus_inf(f, pinf); + + expr_ref x_is_nan(m), x_is_zero(m), x_is_pos(m), x_is_inf(m); + expr_ref y_is_nan(m), y_is_zero(m), y_is_pos(m), y_is_inf(m); + mk_is_nan(x, x_is_nan); + mk_is_zero(x, x_is_zero); + mk_is_pos(x, x_is_pos); + mk_is_inf(x, x_is_inf); + mk_is_nan(y, y_is_nan); + mk_is_zero(y, y_is_zero); + mk_is_pos(y, y_is_pos); + mk_is_inf(y, y_is_inf); + + dbg_decouple("fpa2bv_div_x_is_nan", x_is_nan); + dbg_decouple("fpa2bv_div_x_is_zero", x_is_zero); + dbg_decouple("fpa2bv_div_x_is_pos", x_is_pos); + dbg_decouple("fpa2bv_div_x_is_inf", x_is_inf); + dbg_decouple("fpa2bv_div_y_is_nan", y_is_nan); + dbg_decouple("fpa2bv_div_y_is_zero", y_is_zero); + dbg_decouple("fpa2bv_div_y_is_pos", y_is_pos); + dbg_decouple("fpa2bv_div_y_is_inf", y_is_inf); + + expr_ref c1(m), c2(m), c3(m), c4(m), c5(m), c6(m); + expr_ref v1(m), v2(m), v3(m), v4(m), v5(m), v6(m), v7(m); + + // (x is NaN) || (y is NaN) -> NaN + m_simp.mk_or(x_is_nan, y_is_nan, c1); + v1 = nan; + + // (x is +oo) -> if (y is oo) then NaN else inf with y's sign. + mk_is_pinf(x, c2); + expr_ref y_sgn_inf(m); + mk_ite(y_is_pos, pinf, ninf, y_sgn_inf); + mk_ite(y_is_inf, nan, y_sgn_inf, v2); + + // (y is +oo) -> if (x is oo) then NaN else 0 with sign x.sgn ^ y.sgn + mk_is_pinf(y, c3); + expr_ref xy_zero(m), signs_xor(m); + m_simp.mk_xor(x_is_pos, y_is_pos, signs_xor); + mk_ite(signs_xor, nzero, pzero, xy_zero); + mk_ite(x_is_inf, nan, xy_zero, v3); + + // (x is -oo) -> if (y is oo) then NaN else inf with -y's sign. + mk_is_ninf(x, c4); + expr_ref neg_y_sgn_inf(m); + mk_ite(y_is_pos, ninf, pinf, neg_y_sgn_inf); + mk_ite(y_is_inf, nan, neg_y_sgn_inf, v4); + + // (y is -oo) -> if (x is oo) then NaN else 0 with sign x.sgn ^ y.sgn + mk_is_ninf(y, c5); + mk_ite(x_is_inf, nan, xy_zero, v5); + + // (y is 0) -> if (x is 0) then NaN else inf with x's sign. + c6 = y_is_zero; + expr_ref x_sgn_inf(m); + mk_ite(x_is_pos, pinf, ninf, x_sgn_inf); + mk_ite(x_is_zero, nan, x_sgn_inf, v6); + + // else comes the actual division. + unsigned ebits = m_util.get_ebits(f->get_range()); + unsigned sbits = m_util.get_sbits(f->get_range()); + SASSERT(ebits <= sbits); + + expr_ref a_sgn(m), a_sig(m), a_exp(m), b_sgn(m), b_sig(m), b_exp(m); + unpack(x, a_sgn, a_sig, a_exp, true); + unpack(y, b_sgn, b_sig, b_exp, true); + + unsigned extra_bits = sbits+2; + expr_ref a_sig_ext(m), b_sig_ext(m); + a_sig_ext = m_bv_util.mk_concat(a_sig, m_bv_util.mk_numeral(0, sbits + extra_bits)); + b_sig_ext = m_bv_util.mk_zero_extend(sbits + extra_bits, b_sig); + + expr_ref a_exp_ext(m), b_exp_ext(m); + a_exp_ext = m_bv_util.mk_sign_extend(2, a_exp); + b_exp_ext = m_bv_util.mk_sign_extend(2, b_exp); + + expr_ref res_sgn(m), res_sig(m), res_exp(m); + expr * signs[2] = { a_sgn, b_sgn }; + res_sgn = m_bv_util.mk_bv_xor(2, signs); + + res_exp = m_bv_util.mk_bv_sub(a_exp_ext, b_exp_ext); + + expr_ref quotient(m); + quotient = m.mk_app(m_bv_util.get_fid(), OP_BUDIV, a_sig_ext, b_sig_ext); + + dbg_decouple("fpa2bv_div_quotient", quotient); + + SASSERT(m_bv_util.get_bv_size(quotient) == (sbits + sbits + extra_bits)); + + expr_ref sticky(m); + sticky = m.mk_app(m_bv_util.get_fid(), OP_BREDOR, m_bv_util.mk_extract(extra_bits-2, 0, quotient)); + res_sig = m_bv_util.mk_concat(m_bv_util.mk_extract(extra_bits+sbits+1, extra_bits-1, quotient), sticky); + + SASSERT(m_bv_util.get_bv_size(res_sig) == (sbits + 4)); + + round(f->get_range(), rm, res_sgn, res_sig, res_exp, v7); + + // And finally, we tie them together. + mk_ite(c6, v6, v7, result); + mk_ite(c5, v5, result, result); + mk_ite(c4, v4, result, result); + mk_ite(c3, v3, result, result); + mk_ite(c2, v2, result, result); + mk_ite(c1, v1, result, result); + + SASSERT(is_well_sorted(m, result)); + + TRACE("fpa2bv_div", tout << "DIV = " << mk_ismt2_pp(result, m) << std::endl; ); +} + +void fpa2bv_converter::mk_remainder(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num == 2); + + // Remainder is always exact, so there is no rounding mode. + expr_ref x(m), y(m); + x = args[0]; + y = args[1]; + + expr_ref nan(m), nzero(m), pzero(m), ninf(m), pinf(m); + mk_nan(f, nan); + mk_nzero(f, nzero); + mk_pzero(f, pzero); + mk_minus_inf(f, ninf); + mk_plus_inf(f, pinf); + + expr_ref x_is_nan(m), x_is_zero(m), x_is_pos(m), x_is_inf(m); + expr_ref y_is_nan(m), y_is_zero(m), y_is_pos(m), y_is_inf(m); + mk_is_nan(x, x_is_nan); + mk_is_zero(x, x_is_zero); + mk_is_pos(x, x_is_pos); + mk_is_inf(x, x_is_inf); + mk_is_nan(y, y_is_nan); + mk_is_zero(y, y_is_zero); + mk_is_pos(y, y_is_pos); + mk_is_inf(y, y_is_inf); + + dbg_decouple("fpa2bv_rem_x_is_nan", x_is_nan); + dbg_decouple("fpa2bv_rem_x_is_zero", x_is_zero); + dbg_decouple("fpa2bv_rem_x_is_pos", x_is_pos); + dbg_decouple("fpa2bv_rem_x_is_inf", x_is_inf); + dbg_decouple("fpa2bv_rem_y_is_nan", y_is_nan); + dbg_decouple("fpa2bv_rem_y_is_zero", y_is_zero); + dbg_decouple("fpa2bv_rem_y_is_pos", y_is_pos); + dbg_decouple("fpa2bv_rem_y_is_inf", y_is_inf); + + expr_ref c1(m), c2(m), c3(m), c4(m), c5(m), c6(m); + expr_ref v1(m), v2(m), v3(m), v4(m), v5(m), v6(m), v7(m); + + // (x is NaN) || (y is NaN) -> NaN + m_simp.mk_or(x_is_nan, y_is_nan, c1); + v1 = nan; + + // (x is +-oo) -> NaN + c2 = x_is_inf; + v2 = nan; + + // (y is +-oo) -> x + c3 = y_is_inf; + v3 = x; + + // (x is 0) -> x + c4 = x_is_zero; + v4 = x; + + // (y is 0) -> NaN. + c5 = y_is_zero; + v5 = nan; + + // else the actual remainder. + unsigned ebits = m_util.get_ebits(f->get_range()); + unsigned sbits = m_util.get_sbits(f->get_range()); + + expr_ref a_sgn(m), a_sig(m), a_exp(m); + expr_ref b_sgn(m), b_sig(m), b_exp(m); + unpack(x, a_sgn, a_sig, a_exp, true); + unpack(y, b_sgn, b_sig, b_exp, true); + + BVSLT(a_exp, b_exp, c6); + v6 = x; + + // max. exponent difference is (2^ebits) - 3 + const mpz & two_to_ebits = fu().fm().m_powers2(ebits); + mpz max_exp_diff; + m_mpz_manager.sub(two_to_ebits, 3, max_exp_diff); + SASSERT(m_mpz_manager.is_int64(max_exp_diff)); + SASSERT(m_mpz_manager.get_uint64(max_exp_diff) <= UINT_MAX); + + unsigned int max_exp_diff_ui = (unsigned int)m_mpz_manager.get_uint64(max_exp_diff); + m_mpz_manager.del(max_exp_diff); + + expr_ref exp_diff(m); + exp_diff = m_bv_util.mk_bv_sub(a_exp, b_exp); + dbg_decouple("fpa2bv_rem_exp_diff", exp_diff); + + // CMW: This creates _huge_ bit-vectors, which is potentially sub-optimal, + // but calculating this via rem = x - y * nearest(x/y) creates huge circuits. + expr_ref huge_sig(m), shifted_sig(m), huge_rem(m); + huge_sig = m_bv_util.mk_zero_extend(max_exp_diff_ui, a_sig); + shifted_sig = m_bv_util.mk_bv_shl(huge_sig, m_bv_util.mk_zero_extend(max_exp_diff_ui + sbits - ebits, exp_diff)); + huge_rem = m_bv_util.mk_bv_urem(shifted_sig, m_bv_util.mk_zero_extend(max_exp_diff_ui, b_sig)); + dbg_decouple("fpa2bv_rem_huge_rem", huge_rem); + + expr_ref res_sgn(m), res_sig(m), res_exp(m); + res_sgn = a_sgn; + res_sig = m_bv_util.mk_concat(m_bv_util.mk_extract(sbits, 0, huge_rem), + m_bv_util.mk_numeral(0, 3)); + res_exp = m_bv_util.mk_sign_extend(2, b_exp); + + expr_ref rm(m); + rm = m_bv_util.mk_numeral(BV_RM_TIES_TO_EVEN, 3); + round(f->get_range(), rm, res_sgn, res_sig, res_exp, v7); + + // And finally, we tie them together. + mk_ite(c6, v6, v7, result); + mk_ite(c5, v5, result, result); + mk_ite(c4, v4, result, result); + mk_ite(c3, v3, result, result); + mk_ite(c2, v2, result, result); + mk_ite(c1, v1, result, result); + + SASSERT(is_well_sorted(m, result)); + + TRACE("fpa2bv_rem", tout << "REM = " << mk_ismt2_pp(result, m) << std::endl; ); +} + +void fpa2bv_converter::mk_abs(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num == 1); + expr * sgn, * s, * e; + split(args[0], sgn, s, e); + mk_triple(m_bv_util.mk_numeral(0, 1), s, e, result); +} + +void fpa2bv_converter::mk_min(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num == 2); + + expr * x = args[0], * y = args[1]; + + expr * x_sgn, * x_sig, * x_exp; + expr * y_sgn, * y_sig, * y_exp; + split(x, x_sgn, x_sig, x_exp); + split(y, y_sgn, y_sig, y_exp); + + expr_ref c1(m), c2(m), y_is_nan(m), x_is_nzero(m), y_is_zero(m), c2_and(m); + mk_is_nan(x, c1); + mk_is_nan(y, y_is_nan); + mk_is_nzero(x, x_is_nzero); + mk_is_zero(y, y_is_zero); + m_simp.mk_and(x_is_nzero, y_is_zero, c2_and); + m_simp.mk_or(y_is_nan, c2_and, c2); + + expr_ref c3(m); + mk_float_lt(f, num, args, c3); + + expr_ref r_sgn(m), r_sig(m), r_exp(m); + + expr_ref c3xy(m), c2c3(m); + m_simp.mk_ite(c3, x_sgn, y_sgn, c3xy); + m_simp.mk_ite(c2, x_sgn, c3xy, c2c3); + m_simp.mk_ite(c1, y_sgn, c2c3, r_sgn); + + expr_ref c3xy_sig(m), c2c3_sig(m); + m_simp.mk_ite(c3, x_sig, y_sig, c3xy_sig); + m_simp.mk_ite(c2, x_sig, c3xy_sig, c2c3_sig); + m_simp.mk_ite(c1, y_sig, c2c3_sig, r_sig); + + expr_ref c3xy_exp(m), c2c3_exp(m); + m_simp.mk_ite(c3, x_exp, y_exp, c3xy_exp); + m_simp.mk_ite(c2, x_exp, c3xy_exp, c2c3_exp); + m_simp.mk_ite(c1, y_exp, c2c3_exp, r_exp); + + mk_triple(r_sgn, r_sig, r_exp, result); +} + +void fpa2bv_converter::mk_max(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num == 2); + + expr * x = args[0], * y = args[1]; + + expr * x_sgn, * x_sig, * x_exp; + expr * y_sgn, * y_sig, * y_exp; + split(x, x_sgn, x_sig, x_exp); + split(y, y_sgn, y_sig, y_exp); + + expr_ref c1(m), c2(m), y_is_nan(m), y_is_nzero(m), x_is_zero(m), xy_is_zero(m); + mk_is_nan(x, c1); + mk_is_nan(y, y_is_nan); + mk_is_nzero(y, y_is_nzero); + mk_is_zero(x, x_is_zero); + m_simp.mk_and(y_is_nzero, x_is_zero, xy_is_zero); + m_simp.mk_or(y_is_nan, xy_is_zero, c2); + + expr_ref c3(m); + mk_float_gt(f, num, args, c3); + + expr_ref r_sgn(m), r_sig(m), r_exp(m); + + expr_ref c3xy_sgn(m), c2c3_sgn(m); + m_simp.mk_ite(c3, x_sgn, y_sgn, c3xy_sgn); + m_simp.mk_ite(c2, x_sgn, c3xy_sgn, c2c3_sgn); + m_simp.mk_ite(c1, y_sgn, c2c3_sgn, r_sgn); + + expr_ref c3xy_sig(m), c2c3_sig(m); + m_simp.mk_ite(c3, x_sig, y_sig, c3xy_sig); + m_simp.mk_ite(c2, x_sig, c3xy_sig, c2c3_sig); + m_simp.mk_ite(c1, y_sig, c2c3_sig, r_sig); + + expr_ref c3xy_exp(m), c2c3_exp(m); + m_simp.mk_ite(c3, x_exp, y_exp, c3xy_exp); + m_simp.mk_ite(c2, x_exp, c3xy_exp, c2c3_exp); + m_simp.mk_ite(c1, y_exp, c2c3_exp, r_exp); + + mk_triple(r_sgn, r_sig, r_exp, result); +} + +void fpa2bv_converter::mk_fusedma(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num == 4); + + // fusedma means (x * y) + z + expr_ref rm(m), x(m), y(m), z(m); + rm = args[0]; + x = args[1]; + y = args[2]; + z = args[3]; + + expr_ref nan(m), nzero(m), pzero(m), ninf(m), pinf(m); + mk_nan(f, nan); + mk_nzero(f, nzero); + mk_pzero(f, pzero); + mk_minus_inf(f, ninf); + mk_plus_inf(f, pinf); + + expr_ref x_is_nan(m), x_is_zero(m), x_is_pos(m), x_is_inf(m); + expr_ref y_is_nan(m), y_is_zero(m), y_is_pos(m), y_is_inf(m); + expr_ref z_is_nan(m), z_is_zero(m), z_is_pos(m), z_is_inf(m); + mk_is_nan(x, x_is_nan); + mk_is_zero(x, x_is_zero); + mk_is_pos(x, x_is_pos); + mk_is_inf(x, x_is_inf); + mk_is_nan(y, y_is_nan); + mk_is_zero(y, y_is_zero); + mk_is_pos(y, y_is_pos); + mk_is_inf(y, y_is_inf); + mk_is_nan(z, z_is_nan); + mk_is_zero(z, z_is_zero); + mk_is_pos(z, z_is_pos); + mk_is_inf(z, z_is_inf); + + dbg_decouple("fpa2bv_fma_x_is_nan", x_is_nan); + dbg_decouple("fpa2bv_fma_x_is_zero", x_is_zero); + dbg_decouple("fpa2bv_fma_x_is_pos", x_is_pos); + dbg_decouple("fpa2bv_fma_x_is_inf", x_is_inf); + dbg_decouple("fpa2bv_fma_y_is_nan", y_is_nan); + dbg_decouple("fpa2bv_fma_y_is_zero", y_is_zero); + dbg_decouple("fpa2bv_fma_y_is_pos", y_is_pos); + dbg_decouple("fpa2bv_fma_y_is_inf", y_is_inf); + dbg_decouple("fpa2bv_fma_z_is_nan", z_is_nan); + dbg_decouple("fpa2bv_fma_z_is_zero", z_is_zero); + dbg_decouple("fpa2bv_fma_z_is_pos", z_is_pos); + dbg_decouple("fpa2bv_fma_z_is_inf", z_is_inf); + + expr_ref c1(m), c2(m), c3(m), c4(m), c5(m), c6(m); + expr_ref v1(m), v2(m), v3(m), v4(m), v5(m), v6(m), v7(m); + + // (x is NaN) || (y is NaN) -> NaN + m_simp.mk_or(x_is_nan, y_is_nan, c1); + v1 = nan; + + // (x is +oo) -> if (y is 0) then NaN else inf with y's sign. + mk_is_pinf(x, c2); + expr_ref y_sgn_inf(m); + mk_ite(y_is_pos, pinf, ninf, y_sgn_inf); + mk_ite(y_is_zero, nan, y_sgn_inf, v2); + + // (y is +oo) -> if (x is 0) then NaN else inf with x's sign. + mk_is_pinf(y, c3); + expr_ref x_sgn_inf(m); + mk_ite(x_is_pos, pinf, ninf, x_sgn_inf); + mk_ite(x_is_zero, nan, x_sgn_inf, v3); + + // (x is -oo) -> if (y is 0) then NaN else inf with -y's sign. + mk_is_ninf(x, c4); + expr_ref neg_y_sgn_inf(m); + mk_ite(y_is_pos, ninf, pinf, neg_y_sgn_inf); + mk_ite(y_is_zero, nan, neg_y_sgn_inf, v4); + + // (y is -oo) -> if (x is 0) then NaN else inf with -x's sign. + mk_is_ninf(y, c5); + expr_ref neg_x_sgn_inf(m); + mk_ite(x_is_pos, ninf, pinf, neg_x_sgn_inf); + mk_ite(x_is_zero, nan, neg_x_sgn_inf, v5); + + // (x is 0) || (y is 0) -> x but with sign = x.sign ^ y.sign + m_simp.mk_or(x_is_zero, y_is_zero, c6); + expr_ref sign_xor(m); + m_simp.mk_xor(x_is_pos, y_is_pos, sign_xor); + mk_ite(sign_xor, nzero, pzero, v6); + + // else comes the fused multiplication. + unsigned ebits = m_util.get_ebits(f->get_range()); + unsigned sbits = m_util.get_sbits(f->get_range()); + SASSERT(ebits <= sbits); + + expr_ref rm_is_to_neg(m); + mk_is_rm(rm, BV_RM_TO_NEGATIVE, rm_is_to_neg); + + expr_ref a_sgn(m), a_sig(m), a_exp(m); + expr_ref b_sgn(m), b_sig(m), b_exp(m); + expr_ref c_sgn(m), c_sig(m), c_exp(m); + unpack(x, a_sgn, a_sig, a_exp, true); + unpack(y, b_sgn, b_sig, b_exp, true); + unpack(z, c_sgn, c_sig, c_exp, false); + + expr_ref lz_a(m), lz_b(m); + mk_leading_zeros(a_sig, ebits+2, lz_a); + mk_leading_zeros(b_sig, ebits+2, lz_b); + + expr_ref a_sig_ext(m), b_sig_ext(m); + a_sig_ext = m_bv_util.mk_zero_extend(sbits, a_sig); + b_sig_ext = m_bv_util.mk_zero_extend(sbits, b_sig); + + expr_ref a_exp_ext(m), b_exp_ext(m); + a_exp_ext = m_bv_util.mk_sign_extend(2, a_exp); + b_exp_ext = m_bv_util.mk_sign_extend(2, b_exp); + + expr_ref mul_sgn(m), mul_sig(m), mul_exp(m); + expr * signs[2] = { a_sgn, b_sgn }; + mul_sgn = m_bv_util.mk_bv_xor(2, signs); + + dbg_decouple("fpa2bv_fma_mul_sgn", mul_sgn); + + mul_exp = m_bv_util.mk_bv_sub( + m_bv_util.mk_bv_add(a_exp_ext, b_exp_ext), + m_bv_util.mk_bv_add(lz_a, lz_b)); + + mul_sig = m_bv_util.mk_bv_mul(a_sig_ext, b_sig_ext); + + dbg_decouple("fpa2bv_fma_mul_sig", mul_sig); + + SASSERT(m_bv_util.get_bv_size(mul_sig) == 2*sbits); + + // The result in `product' represents a number of the form 1.*** (unpacked) + // (product = mul_sgn/mul_sig/mul_exp and c_sgn/c_sig/c_exp is unpacked w/o normalization). + + // extend c. + c_sig = m_bv_util.mk_concat(c_sig, m_bv_util.mk_numeral(0, sbits)); + c_exp = m_bv_util.mk_sign_extend(2, c_exp); + + expr_ref swap_cond(m); + swap_cond = m_bv_util.mk_sle(mul_exp, c_exp); + SASSERT(is_well_sorted(m, swap_cond)); + + expr_ref e_sgn(m), e_sig(m), e_exp(m), f_sgn(m), f_sig(m), f_exp(m); + m_simp.mk_ite(swap_cond, c_sgn, mul_sgn, e_sgn); + m_simp.mk_ite(swap_cond, c_sig, mul_sig, e_sig); // has 2 * sbits + m_simp.mk_ite(swap_cond, c_exp, mul_exp, e_exp); // has ebits + 2 + m_simp.mk_ite(swap_cond, mul_sgn, c_sgn, f_sgn); + m_simp.mk_ite(swap_cond, mul_sig, c_sig, f_sig); // has 2 * sbits + m_simp.mk_ite(swap_cond, mul_exp, c_exp, f_exp); // has ebits + 2 + + SASSERT(is_well_sorted(m, e_sgn)); + SASSERT(is_well_sorted(m, e_sig)); + SASSERT(is_well_sorted(m, e_exp)); + SASSERT(is_well_sorted(m, f_sgn)); + SASSERT(is_well_sorted(m, f_sig)); + SASSERT(is_well_sorted(m, f_exp)); + + expr_ref res_sgn(m), res_sig(m), res_exp(m); + add_core(2 * sbits, ebits + 2, rm, + e_sgn, e_sig, e_exp, f_sgn, f_sig, f_exp, + res_sgn, res_sig, res_exp); + + // Note: res_sig is now 2 * sbits + 4, i.e., `sbits' too much, which should go into a sticky bit. + unsigned sig_size = m_bv_util.get_bv_size(res_sig); + SASSERT(sig_size == (2*sbits+4)); + + // Note: res_exp is 2 bits too wide. + unsigned exp_size = m_bv_util.get_bv_size(res_exp); + SASSERT(exp_size == ebits+4); + res_exp = m_bv_util.mk_extract(ebits+1, 0, res_exp); + + expr_ref sticky(m); + sticky = m.mk_app(m_bv_util.get_fid(), OP_BREDOR, m_bv_util.mk_extract(sbits, 0, res_sig)); + res_sig = m_bv_util.mk_concat(m_bv_util.mk_extract(2*sbits+3, sbits+1, res_sig), sticky); + + sig_size = m_bv_util.get_bv_size(res_sig); + SASSERT(sig_size == sbits+4); + + expr_ref is_zero_sig(m); + m_simp.mk_eq(res_sig, m_bv_util.mk_numeral(0, sbits+4), is_zero_sig); + + SASSERT(is_well_sorted(m, is_zero_sig)); + + dbg_decouple("fpa2bv_fma_is_zero_sig", is_zero_sig); + + expr_ref zero_case(m); + mk_ite(rm_is_to_neg, nzero, pzero, zero_case); + + expr_ref rounded(m); + round(f->get_range(), rm, res_sgn, res_sig, res_exp, rounded); + + mk_ite(is_zero_sig, zero_case, rounded, v7); + + // And finally, we tie them together. + mk_ite(c6, v6, v7, result); + mk_ite(c5, v5, result, result); + mk_ite(c4, v4, result, result); + mk_ite(c3, v3, result, result); + mk_ite(c2, v2, result, result); + mk_ite(c1, v1, result, result); + + SASSERT(is_well_sorted(m, result)); + + TRACE("fpa2bv_mul", tout << "MUL = " << mk_ismt2_pp(result, m) << std::endl; ); +} + +void fpa2bv_converter::mk_sqrt(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + NOT_IMPLEMENTED_YET(); +} + +void fpa2bv_converter::mk_round_to_integral(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num == 2); + + expr_ref rm(m), x(m); + rm = args[0]; + x = args[1]; + + expr_ref nan(m), nzero(m), pzero(m), ninf(m), pinf(m); + mk_nan(f, nan); + mk_nzero(f, nzero); + mk_pzero(f, pzero); + + expr_ref x_is_zero(m), x_is_pos(m); + mk_is_zero(x, x_is_zero); + mk_is_pos(x, x_is_pos); + + dbg_decouple("fpa2bv_r2i_x_is_zero", x_is_zero); + dbg_decouple("fpa2bv_r2i_x_is_pos", x_is_pos); + + expr_ref c1(m), c2(m), c3(m), c4(m); + expr_ref v1(m), v2(m), v3(m), v4(m), v5(m); + + mk_is_nan(x, c1); + v1 = nan; + + mk_is_inf(x, c2); + v2 = x; + + unsigned ebits = m_util.get_ebits(f->get_range()); + unsigned sbits = m_util.get_sbits(f->get_range()); + SASSERT(ebits < sbits); + + expr_ref a_sgn(m), a_sig(m), a_exp(m); + unpack(x, a_sgn, a_sig, a_exp, true); + + expr_ref exp_is_small(m); + m_simp.mk_eq(m_bv_util.mk_extract(ebits-1, ebits-1, a_exp), + m_bv_util.mk_numeral(1, 1), + exp_is_small); + dbg_decouple("fpa2bv_r2i_exp_is_small", exp_is_small); + c3 = exp_is_small; + mk_ite(x_is_pos, pzero, nzero, v3); + + expr_ref exp_is_large(m); + exp_is_large = m_bv_util.mk_sle(m_bv_util.mk_numeral(sbits-1, ebits), a_exp); + dbg_decouple("fpa2bv_r2i_exp_is_large", exp_is_large); + c4 = exp_is_large; + v4 = x; + + // The actual rounding. + expr_ref res_sgn(m), res_sig(m), res_exp(m); + res_sgn = a_sgn; + res_exp = m_bv_util.mk_concat(m_bv_util.mk_numeral(0, 2), a_exp); + + expr_ref shift(m), r_shifted(m), l_shifted(m); + shift = m_bv_util.mk_bv_sub(m_bv_util.mk_numeral(sbits-1, ebits+1), + m_bv_util.mk_sign_extend(1, a_exp)); + r_shifted = m_bv_util.mk_bv_lshr(a_sig, m_bv_util.mk_zero_extend(sbits-ebits-1, shift)); + SASSERT(m_bv_util.get_bv_size(r_shifted) == sbits); + l_shifted = m_bv_util.mk_bv_shl(r_shifted, m_bv_util.mk_zero_extend(sbits-ebits-1, shift)); + SASSERT(m_bv_util.get_bv_size(l_shifted) == sbits); + + res_sig = m_bv_util.mk_concat(m_bv_util.mk_numeral(0, 1), + m_bv_util.mk_concat(l_shifted, + m_bv_util.mk_numeral(0, 3))); + + SASSERT(m_bv_util.get_bv_size(res_sig) == (sbits + 4)); + + round(f->get_range(), rm, res_sgn, res_sig, res_exp, v5); + + // And finally, we tie them together. + mk_ite(c4, v4, v5, result); + mk_ite(c3, v3, result, result); + mk_ite(c2, v2, result, result); + mk_ite(c1, v1, result, result); + + SASSERT(is_well_sorted(m, result)); + + TRACE("fpa2bv_round_to_integral", tout << "ROUND2INTEGRAL = " << mk_ismt2_pp(result, m) << std::endl; ); +} + +void fpa2bv_converter::mk_float_eq(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num == 2); + + expr * x = args[0], * y = args[1]; + + expr_ref c1(m), c2(m), x_is_nan(m), y_is_nan(m), x_is_zero(m), y_is_zero(m); + mk_is_nan(x, x_is_nan); + mk_is_nan(y, y_is_nan); + m_simp.mk_or(x_is_nan, y_is_nan, c1); + mk_is_zero(x, x_is_zero); + mk_is_zero(y, y_is_zero); + m_simp.mk_and(x_is_zero, y_is_zero, c2); + + expr * x_sgn, * x_sig, * x_exp; + expr * y_sgn, * y_sig, * y_exp; + split(x, x_sgn, x_sig, x_exp); + split(y, y_sgn, y_sig, y_exp); + + expr_ref x_eq_y_sgn(m), x_eq_y_exp(m), x_eq_y_sig(m); + m_simp.mk_eq(x_sgn, y_sgn, x_eq_y_sgn); + m_simp.mk_eq(x_exp, y_exp, x_eq_y_exp); + m_simp.mk_eq(x_sig, y_sig, x_eq_y_sig); + + expr_ref c3(m), t4(m); + m_simp.mk_not(x_eq_y_sgn, c3); + m_simp.mk_and(x_eq_y_exp, x_eq_y_sig, t4); + + expr_ref c3t4(m), c2else(m); + m_simp.mk_ite(c3, m.mk_false(), t4, c3t4); + m_simp.mk_ite(c2, m.mk_true(), c3t4, c2else); + + m_simp.mk_ite(c1, m.mk_false(), c2else, result); +} + +void fpa2bv_converter::mk_float_lt(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num == 2); + + expr * x = args[0], * y = args[1]; + + expr_ref c1(m), c2(m), x_is_nan(m), y_is_nan(m), x_is_zero(m), y_is_zero(m); + mk_is_nan(x, x_is_nan); + mk_is_nan(y, y_is_nan); + m_simp.mk_or(x_is_nan, y_is_nan, c1); + mk_is_zero(x, x_is_zero); + mk_is_zero(y, y_is_zero); + m_simp.mk_and(x_is_zero, y_is_zero, c2); + + expr * x_sgn, * x_sig, * x_exp; + expr * y_sgn, * y_sig, * y_exp; + split(x, x_sgn, x_sig, x_exp); + split(y, y_sgn, y_sig, y_exp); + + expr_ref c3(m), t3(m), t4(m); + + m_simp.mk_eq(x_sgn, m_bv_util.mk_numeral(1, 1), c3); + + expr_ref y_sgn_eq_0(m), y_lt_x_exp(m), y_lt_x_sig(m), y_eq_x_exp(m), y_le_x_sig_exp(m), t3_or(m); + m_simp.mk_eq(y_sgn, m_bv_util.mk_numeral(0, 1), y_sgn_eq_0); + BVULT(y_exp, x_exp, y_lt_x_exp); + BVULT(y_sig, x_sig, y_lt_x_sig); + m_simp.mk_eq(y_exp, x_exp, y_eq_x_exp); + m_simp.mk_and(y_eq_x_exp, y_lt_x_sig, y_le_x_sig_exp); + m_simp.mk_or(y_lt_x_exp, y_le_x_sig_exp, t3_or); + m_simp.mk_ite(y_sgn_eq_0, m.mk_true(), t3_or, t3); + + expr_ref y_sgn_eq_1(m), x_lt_y_exp(m), x_eq_y_exp(m), x_lt_y_sig(m), x_le_y_sig_exp(m), t4_or(m); + m_simp.mk_eq(y_sgn, m_bv_util.mk_numeral(1, 1), y_sgn_eq_1); + BVULT(x_exp, y_exp, x_lt_y_exp); + m_simp.mk_eq(x_exp, y_exp, x_eq_y_exp); + BVULT(x_sig, y_sig, x_lt_y_sig); + m_simp.mk_and(x_eq_y_exp, x_lt_y_sig, x_le_y_sig_exp); + m_simp.mk_or(x_lt_y_exp, x_le_y_sig_exp, t4_or); + m_simp.mk_ite(y_sgn_eq_1, m.mk_false(), t4_or, t4); + + expr_ref c3t3t4(m), c2else(m); + m_simp.mk_ite(c3, t3, t4, c3t3t4); + m_simp.mk_ite(c2, m.mk_false(), c3t3t4, c2else); + m_simp.mk_ite(c1, m.mk_false(), c2else, result); +} + +void fpa2bv_converter::mk_float_gt(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num == 2); + + expr * x = args[0], * y = args[1]; + + expr_ref t3(m); + mk_float_le(f, num, args, t3); + + expr_ref nan_or(m), xy_zero(m), not_t3(m), r_else(m); + expr_ref x_is_nan(m), y_is_nan(m), x_is_zero(m), y_is_zero(m); + mk_is_nan(x, x_is_nan); + mk_is_nan(y, y_is_nan); + m_simp.mk_or(x_is_nan, y_is_nan, nan_or); + mk_is_zero(x, x_is_zero); + mk_is_zero(y, y_is_zero); + m_simp.mk_and(x_is_zero, y_is_zero, xy_zero); + m_simp.mk_not(t3, not_t3); + m_simp.mk_ite(xy_zero, m.mk_false(), not_t3, r_else); + m_simp.mk_ite(nan_or, m.mk_false(), r_else, result); +} + +void fpa2bv_converter::mk_float_le(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num == 2); + expr_ref a(m), b(m); + mk_float_lt(f, num, args, a); + mk_float_eq(f, num, args, b); + m_simp.mk_or(a, b, result); +} + +void fpa2bv_converter::mk_float_ge(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num == 2); + expr_ref a(m), b(m); + mk_float_gt(f, num, args, a); + mk_float_eq(f, num, args, b); + m_simp.mk_or(a, b, result); +} + +void fpa2bv_converter::mk_is_zero(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num == 1); + mk_is_zero(args[0], result); +} + +void fpa2bv_converter::mk_is_nzero(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num == 1); + expr_ref a0_is_neg(m), a0_is_zero(m); + mk_is_neg(args[0], a0_is_neg); + mk_is_zero(args[0], a0_is_zero); + m_simp.mk_and(a0_is_neg, a0_is_zero, result); +} + +void fpa2bv_converter::mk_is_pzero(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num == 1); + expr_ref a0_is_pos(m), a0_is_zero(m); + mk_is_pos(args[0], a0_is_pos); + mk_is_zero(args[0], a0_is_zero); + m_simp.mk_and(a0_is_pos, a0_is_zero, result); +} + +void fpa2bv_converter::mk_is_sign_minus(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + SASSERT(num == 1); + mk_is_neg(args[0], result); +} + +void fpa2bv_converter::mk_to_float(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + if (num == 3 && m_bv_util.is_bv(args[0]) && + m_bv_util.is_bv(args[1]) && m_bv_util.is_bv(args[2])) { + // Theoretically, the user could have thrown in it's own triple of bit-vectors. + // Just keep it here, as there will be something else that uses it. + mk_triple(args[0], args[1], args[2], result); + } + else { + SASSERT(num == 2); + SASSERT(m_util.is_float(f->get_range())); + unsigned ebits = m_util.get_ebits(f->get_range()); + unsigned sbits = m_util.get_sbits(f->get_range()); + + SASSERT(m_util.is_rm(to_app(args[0])->get_decl()->get_range())); + mpf_rounding_mode rm = static_cast(to_app(args[1])->get_decl_kind()); + + rational q; + SASSERT(m_util.au().is_numeral(args[1])); + m_util.au().is_numeral(args[1], q); + + mpf v; + m_util.fm().set(v, ebits, sbits, rm, q.to_mpq()); + + expr * sgn = m_bv_util.mk_numeral((m_util.fm().sgn(v)) ? 1 : 0, 1); + expr * s = m_bv_util.mk_numeral(m_util.fm().sig(v), sbits-1); + expr * e = m_bv_util.mk_numeral(m_util.fm().exp(v), ebits); + + mk_triple(sgn, s, e, result); + + m_util.fm().del(v); + } +} + +void fpa2bv_converter::split(expr * e, expr * & sgn, expr * & sig, expr * & exp) const { + SASSERT(is_app_of(e, m_plugin->get_family_id(), OP_TO_FLOAT)); + SASSERT(to_app(e)->get_num_args() == 3); + + sgn = to_app(e)->get_arg(0); + sig = to_app(e)->get_arg(1); + exp = to_app(e)->get_arg(2); +} + +void fpa2bv_converter::mk_is_nan(expr * e, expr_ref & result) { + expr * sgn, * sig, * exp; + split(e, sgn, sig, exp); + + // exp == 1^n , sig != 0 + expr_ref sig_is_zero(m), sig_is_not_zero(m), exp_is_top(m), top_exp(m); + mk_top_exp(m_bv_util.get_bv_size(exp), top_exp); + + m_simp.mk_eq(sig, m_bv_util.mk_numeral(0, m_bv_util.get_bv_size(sig)), sig_is_zero); + m_simp.mk_not(sig_is_zero, sig_is_not_zero); + m_simp.mk_eq(exp, top_exp, exp_is_top); + m_simp.mk_and(exp_is_top, sig_is_not_zero, result); +} + +void fpa2bv_converter::mk_is_inf(expr * e, expr_ref & result) { + expr * sgn, * sig, * exp; + split(e, sgn, sig, exp); + expr_ref eq1(m), eq2(m), top_exp(m); + mk_top_exp(m_bv_util.get_bv_size(exp), top_exp); + m_simp.mk_eq(sig, m_bv_util.mk_numeral(0, m_bv_util.get_bv_size(sig)), eq1); + m_simp.mk_eq(exp, top_exp, eq2); + m_simp.mk_and(eq1, eq2, result); +} + +void fpa2bv_converter::mk_is_pinf(expr * e, expr_ref & result) { + expr_ref e_is_pos(m), e_is_inf(m); + mk_is_pos(e, e_is_pos); + mk_is_inf(e, e_is_inf); + m_simp.mk_and(e_is_pos, e_is_inf, result); +} + +void fpa2bv_converter::mk_is_ninf(expr * e, expr_ref & result) { + expr_ref e_is_neg(m), e_is_inf(m); + mk_is_neg(e, e_is_neg); + mk_is_inf(e, e_is_inf); + m_simp.mk_and(e_is_neg, e_is_inf, result); +} + +void fpa2bv_converter::mk_is_pos(expr * e, expr_ref & result) { + SASSERT(is_app_of(e, m_plugin->get_family_id(), OP_TO_FLOAT)); + SASSERT(to_app(e)->get_num_args() == 3); + expr * a0 = to_app(e)->get_arg(0); + m_simp.mk_eq(a0, m_bv_util.mk_numeral(0, m_bv_util.get_bv_size(a0)), result); +} + +void fpa2bv_converter::mk_is_neg(expr * e, expr_ref & result) { + SASSERT(is_app_of(e, m_plugin->get_family_id(), OP_TO_FLOAT)); + SASSERT(to_app(e)->get_num_args() == 3); + expr * a0 = to_app(e)->get_arg(0); + m_simp.mk_eq(a0, m_bv_util.mk_numeral(1, m_bv_util.get_bv_size(a0)), result); +} + +void fpa2bv_converter::mk_is_zero(expr * e, expr_ref & result) { + expr * sgn, * sig, * exp; + split(e, sgn, sig, exp); + expr_ref eq1(m), eq2(m), bot_exp(m); + mk_bot_exp(m_bv_util.get_bv_size(exp), bot_exp); + m_simp.mk_eq(sig, m_bv_util.mk_numeral(0, m_bv_util.get_bv_size(sig)), eq1); + m_simp.mk_eq(exp, bot_exp, eq2); + m_simp.mk_and(eq1, eq2, result); +} + +void fpa2bv_converter::mk_is_nzero(expr * e, expr_ref & result) { + expr * sgn, * sig, * exp; + split(e, sgn, sig, exp); + expr_ref e_is_zero(m), eq(m); + mk_is_zero(e, e_is_zero); + m_simp.mk_eq(sgn, m_bv_util.mk_numeral(1, 1), eq); + m_simp.mk_and(eq, e_is_zero, result); +} + +void fpa2bv_converter::mk_is_pzero(expr * e, expr_ref & result) { + expr * sgn, * sig, * exp; + split(e, sgn, sig, exp); + expr_ref e_is_zero(m), eq(m); + mk_is_zero(e, e_is_zero); + m_simp.mk_eq(sgn, m_bv_util.mk_numeral(0, 1), eq); + m_simp.mk_and(eq, e_is_zero, result); +} + +void fpa2bv_converter::mk_is_denormal(expr * e, expr_ref & result) { + expr * sgn, * sig, * exp; + split(e, sgn, sig, exp); + m_simp.mk_eq(exp, m_bv_util.mk_numeral(0, m_bv_util.get_bv_size(exp)), result); +} + +void fpa2bv_converter::mk_is_normal(expr * e, expr_ref & result) { + expr * sgn, * sig, * exp; + split(e, sgn, sig, exp); + + expr_ref is_special(m), is_denormal(m); + mk_is_denormal(e, is_denormal); + unsigned ebits = m_bv_util.get_bv_size(exp); + m_simp.mk_eq(exp, m_bv_util.mk_numeral(fu().fm().m_powers2.m1(ebits), ebits), is_special); + + expr_ref or_ex(m); + m_simp.mk_or(is_special, is_denormal, or_ex); + m_simp.mk_not(or_ex, result); +} + +void fpa2bv_converter::mk_is_rm(expr * e, BV_RM_VAL rm, expr_ref & result) { + SASSERT(m_bv_util.is_bv(e) && m_bv_util.get_bv_size(e) == 3); + expr_ref rm_num(m); + rm_num = m_bv_util.mk_numeral(rm, 3); + switch(rm) + { + case BV_RM_TIES_TO_AWAY: + case BV_RM_TIES_TO_EVEN: + case BV_RM_TO_NEGATIVE: + case BV_RM_TO_POSITIVE: return m_simp.mk_eq(e, rm_num, result); + case BV_RM_TO_ZERO: + default: + rm_num = m_bv_util.mk_numeral(BV_RM_TO_POSITIVE, 3); + expr_ref r(m); r = m_bv_util.mk_ule(e, rm_num); + return m_simp.mk_not(r, result); + } +} + +void fpa2bv_converter::mk_top_exp(unsigned sz, expr_ref & result) { + result = m_bv_util.mk_numeral(fu().fm().m_powers2.m1(sz), sz); +} + +void fpa2bv_converter::mk_bot_exp(unsigned sz, expr_ref & result) { + result = m_bv_util.mk_numeral(0, sz); +} + +void fpa2bv_converter::mk_min_exp(unsigned ebits, expr_ref & result) { + SASSERT(ebits > 0); + const mpz & z = m_mpf_manager.m_powers2.m1(ebits-1, true); + result = m_bv_util.mk_numeral(z + mpz(1), ebits); +} + +void fpa2bv_converter::mk_max_exp(unsigned ebits, expr_ref & result) { + SASSERT(ebits > 0); + result = m_bv_util.mk_numeral(m_mpf_manager.m_powers2.m1(ebits-1, false), ebits); +} + +void fpa2bv_converter::mk_leading_zeros(expr * e, unsigned max_bits, expr_ref & result) { + SASSERT(m_bv_util.is_bv(e)); + unsigned bv_sz = m_bv_util.get_bv_size(e); + + if (bv_sz == 0) + result = m_bv_util.mk_numeral(0, max_bits); + else if (bv_sz == 1) { + expr_ref eq(m); + m_simp.mk_eq(e, m_bv_util.mk_numeral(0, 1), eq); + m_simp.mk_ite(eq, m_bv_util.mk_numeral(1, max_bits), m_bv_util.mk_numeral(0, max_bits), result); + } + else { + expr_ref H(m), L(m); + H = m_bv_util.mk_extract(bv_sz-1, bv_sz/2, e); + L = m_bv_util.mk_extract(bv_sz/2-1, 0, e); + + unsigned H_size = m_bv_util.get_bv_size(H); + unsigned L_size = m_bv_util.get_bv_size(L); + + expr_ref lzH(m), lzL(m); + mk_leading_zeros(H, max_bits, lzH); /* recursive! */ + mk_leading_zeros(L, max_bits, lzL); + + expr_ref H_is_zero(m); + m_simp.mk_eq(H, m_bv_util.mk_numeral(0, H_size), H_is_zero); + + expr_ref sum(m); + sum = m_bv_util.mk_bv_add(m_bv_util.mk_numeral(H_size, max_bits), lzL); + m_simp.mk_ite(H_is_zero, sum, lzH, result); + } + + SASSERT(is_well_sorted(m, result)); +} + +void fpa2bv_converter::mk_bias(expr * e, expr_ref & result) { + unsigned ebits = m_bv_util.get_bv_size(e); + SASSERT(ebits >= 2); + + expr_ref mask(m); + mask = m_bv_util.mk_numeral(fu().fm().m_powers2.m1(ebits-1), ebits); + result = m_bv_util.mk_bv_add(e, mask); +} + +void fpa2bv_converter::mk_unbias(expr * e, expr_ref & result) { + unsigned ebits = m_bv_util.get_bv_size(e); + SASSERT(ebits >= 2); + + expr_ref e_plus_one(m); + e_plus_one = m_bv_util.mk_bv_add(e, m_bv_util.mk_numeral(1, ebits)); + + expr_ref leading(m), n_leading(m), rest(m); + leading = m_bv_util.mk_extract(ebits-1, ebits-1, e_plus_one); + n_leading = m_bv_util.mk_bv_not(leading); + rest = m_bv_util.mk_extract(ebits-2, 0, e_plus_one); + + result = m_bv_util.mk_concat(n_leading, rest); +} + +void fpa2bv_converter::unpack(expr * e, expr_ref & sgn, expr_ref & sig, expr_ref & exp, bool normalize) { + SASSERT(is_app_of(e, m_plugin->get_family_id(), OP_TO_FLOAT)); + SASSERT(to_app(e)->get_num_args() == 3); + + sort * srt = to_app(e)->get_decl()->get_range(); + SASSERT(is_float(srt)); + unsigned sbits = m_util.get_sbits(srt); + unsigned ebits = m_util.get_ebits(srt); + + sgn = to_app(e)->get_arg(0); + sig = to_app(e)->get_arg(1); + exp = to_app(e)->get_arg(2); + + expr_ref is_normal(m); + mk_is_normal(e, is_normal); + + expr_ref normal_sig(m), normal_exp(m); + normal_sig = m_bv_util.mk_concat(m_bv_util.mk_numeral(1, 1), sig); + mk_unbias(exp, normal_exp); + + expr_ref denormal_sig(m), denormal_exp(m); + denormal_sig = m_bv_util.mk_concat(m_bv_util.mk_numeral(0, 1), sig); + denormal_exp = m_bv_util.mk_numeral(1, ebits); + mk_unbias(denormal_exp, denormal_exp); + + if (normalize) { + SASSERT(ebits <= sbits); + expr_ref is_sig_zero(m), shift(m), lz(m); + m_simp.mk_eq(m_bv_util.mk_numeral(0, sbits-1), sig, is_sig_zero); + mk_leading_zeros(sig, ebits, lz); + m_simp.mk_ite(is_sig_zero, m_bv_util.mk_numeral(0, ebits), lz, shift); + SASSERT(is_well_sorted(m, is_sig_zero)); + SASSERT(is_well_sorted(m, lz)); + SASSERT(is_well_sorted(m, shift)); + denormal_sig = m_bv_util.mk_bv_shl(denormal_sig, m_bv_util.mk_zero_extend(sbits-ebits, shift)); + // CMW: The book says we don't need this, but it feels wrong not to do that. + //denormal_exp = m_bv_util.mk_bv_sub(denormal_exp, shift); + } + + SASSERT(is_well_sorted(m, normal_sig)); + SASSERT(is_well_sorted(m, denormal_sig)); + + m_simp.mk_ite(is_normal, normal_sig, denormal_sig, sig); + m_simp.mk_ite(is_normal, normal_exp, denormal_exp, exp); + + SASSERT(is_well_sorted(m, sgn)); + SASSERT(is_well_sorted(m, sig)); + SASSERT(is_well_sorted(m, exp)); + + SASSERT(m_bv_util.get_bv_size(sgn) == 1); + SASSERT(m_bv_util.get_bv_size(sig) == sbits); + SASSERT(m_bv_util.get_bv_size(exp) == ebits); + + TRACE("fpa2bv_unpack", tout << "UNPACK SGN = " << mk_ismt2_pp(sgn, m) << std::endl; ); + TRACE("fpa2bv_unpack", tout << "UNPACK SIG = " << mk_ismt2_pp(sig, m) << std::endl; ); + TRACE("fpa2bv_unpack", tout << "UNPACK EXP = " << mk_ismt2_pp(exp, m) << std::endl; ); +} + +void fpa2bv_converter::mk_rounding_mode(func_decl * f, expr_ref & result) +{ + switch(f->get_decl_kind()) + { + case OP_RM_NEAREST_TIES_TO_AWAY: result = m_bv_util.mk_numeral(BV_RM_TIES_TO_AWAY, 3); break; + case OP_RM_NEAREST_TIES_TO_EVEN: result = m_bv_util.mk_numeral(BV_RM_TIES_TO_EVEN, 3); break; + case OP_RM_TOWARD_NEGATIVE: result = m_bv_util.mk_numeral(BV_RM_TO_NEGATIVE, 3); break; + case OP_RM_TOWARD_POSITIVE: result = m_bv_util.mk_numeral(BV_RM_TO_POSITIVE, 3); break; + case OP_RM_TOWARD_ZERO: result = m_bv_util.mk_numeral(BV_RM_TO_ZERO, 3); break; + default: UNREACHABLE(); + } +} + +void fpa2bv_converter::dbg_decouple(const char * prefix, expr_ref & e) { + #ifdef _DEBUG + expr_ref new_e(m); + new_e = m.mk_fresh_const(prefix, m.get_sort(e)); + extra_assertions.push_back(m.mk_eq(new_e, e)); + e = new_e; + #endif +} + +void fpa2bv_converter::round(sort * s, expr_ref & rm, expr_ref & sgn, expr_ref & sig, expr_ref & exp, expr_ref & result) { + unsigned ebits = m_util.get_ebits(s); + unsigned sbits = m_util.get_sbits(s); + + dbg_decouple("fpa2bv_rnd_rm", rm); + dbg_decouple("fpa2bv_rnd_sgn", sgn); + dbg_decouple("fpa2bv_rnd_sig", sig); + dbg_decouple("fpa2bv_rnd_exp", exp); + + SASSERT(is_well_sorted(m, rm)); + SASSERT(is_well_sorted(m, sgn)); + SASSERT(is_well_sorted(m, sig)); + SASSERT(is_well_sorted(m, exp)); + + TRACE("fpa2bv_dbg", tout << "RND: " << std::endl << + "ebits = " << ebits << std::endl << + "sbits = " << sbits << std::endl << + "sgn = " << mk_ismt2_pp(sgn, m) << std::endl << + "sig = " << mk_ismt2_pp(sig, m) << std::endl << + "exp = " << mk_ismt2_pp(exp, m) << std::endl; ); + + // Assumptions: sig is of the form f[-1:0] . f[1:sbits-1] [guard,round,sticky], + // i.e., it has 2 + (sbits-1) + 3 = sbits + 4 bits, where the first one is in sgn. + // Furthermore, note that sig is an unsigned bit-vector, while exp is signed. + + SASSERT(ebits <= sbits); + SASSERT(m_bv_util.is_bv(rm) && m_bv_util.get_bv_size(rm) == 3); + SASSERT(m_bv_util.is_bv(sgn) && m_bv_util.get_bv_size(sgn) == 1); + SASSERT(m_bv_util.is_bv(sig) && m_bv_util.get_bv_size(sig) >= 5); + SASSERT(m_bv_util.is_bv(exp) && m_bv_util.get_bv_size(exp) >= 4); + + SASSERT(m_bv_util.get_bv_size(sig) == sbits+4); + SASSERT(m_bv_util.get_bv_size(exp) == ebits+2); + + // bool UNFen = false; + // bool OVFen = false; + + expr_ref e_min(m), e_max(m); + mk_min_exp(ebits, e_min); + mk_max_exp(ebits, e_max); + + TRACE("fpa2bv_dbg", tout << "e_min = " << mk_ismt2_pp(e_min, m) << std::endl << + "e_max = " << mk_ismt2_pp(e_max, m) << std::endl;); + + expr_ref OVF1(m), e_top_three(m), sigm1(m), e_eq_emax_and_sigm1(m), e_eq_emax(m); + expr_ref e3(m), ne3(m), e2(m), e1(m), e21(m); + m_simp.mk_eq(m_bv_util.mk_extract(ebits+1, ebits+1, exp), m_bv_util.mk_numeral(1, 1), e3); + m_simp.mk_eq(m_bv_util.mk_extract(ebits, ebits, exp), m_bv_util.mk_numeral(1, 1), e2); + m_simp.mk_eq(m_bv_util.mk_extract(ebits-1, ebits-1, exp), m_bv_util.mk_numeral(1, 1), e1); + m_simp.mk_or(e2, e1, e21); + m_simp.mk_not(e3, ne3); + m_simp.mk_and(ne3, e21, e_top_three); + m_simp.mk_eq(m_bv_util.mk_zero_extend(2, e_max), exp, e_eq_emax); + m_simp.mk_eq(m_bv_util.mk_extract(sbits+3, sbits+3, sig), m_bv_util.mk_numeral(1, 1), sigm1); + m_simp.mk_and(e_eq_emax, sigm1, e_eq_emax_and_sigm1); + m_simp.mk_or(e_top_three, e_eq_emax_and_sigm1, OVF1); + + dbg_decouple("fpa2bv_rnd_OVF1", OVF1); + + TRACE("fpa2bv_dbg", tout << "OVF1 = " << mk_ismt2_pp(OVF1, m) << std::endl;); + SASSERT(is_well_sorted(m, OVF1)); + + expr_ref lz(m); + mk_leading_zeros(sig, ebits+2, lz); // CMW: is this always large enough? + + dbg_decouple("fpa2bv_rnd_lz", lz); + + TRACE("fpa2bv_dbg", tout << "LZ = " << mk_ismt2_pp(lz, m) << std::endl;); + + expr_ref t(m); + t = m_bv_util.mk_bv_add(exp, m_bv_util.mk_numeral(1, ebits+2)); + t = m_bv_util.mk_bv_sub(t, lz); + t = m_bv_util.mk_bv_sub(t, m_bv_util.mk_sign_extend(2, e_min)); + expr_ref TINY(m); + TINY = m_bv_util.mk_sle(t, m_bv_util.mk_numeral(-1, ebits+2)); + + TRACE("fpa2bv_dbg", tout << "TINY = " << mk_ismt2_pp(TINY, m) << std::endl;); + SASSERT(is_well_sorted(m, TINY)); + + dbg_decouple("fpa2bv_rnd_TINY", TINY); + + expr_ref beta(m); + beta = m_bv_util.mk_bv_add(m_bv_util.mk_bv_sub(exp, lz), m_bv_util.mk_numeral(1, ebits+2)); + + TRACE("fpa2bv_dbg", tout << "beta = " << mk_ismt2_pp(beta, m)<< std::endl; ); + SASSERT(is_well_sorted(m, beta)); + + dbg_decouple("fpa2bv_rnd_beta", beta); + + expr_ref sigma(m), sigma_add(m); + sigma_add = m_bv_util.mk_bv_add(exp, m_bv_util.mk_numeral(fu().fm().m_powers2.m1(ebits-1), ebits+2)); + m_simp.mk_ite(TINY, sigma_add, lz, sigma); + + dbg_decouple("fpa2bv_rnd_sigma", sigma); + + TRACE("fpa2bv_dbg", tout << "Shift distance: " << mk_ismt2_pp(sigma, m) << std::endl;); + SASSERT(is_well_sorted(m, sigma)); + + // Normalization shift + dbg_decouple("fpa2bv_rnd_sig_before_shift", sig); + + unsigned sig_size = m_bv_util.get_bv_size(sig); + SASSERT(sig_size == sbits+4); + unsigned sigma_size = m_bv_util.get_bv_size(sigma); + + expr_ref sigma_neg(m), sigma_cap(m), sigma_neg_capped(m), sigma_lt_zero(m), sig_ext(m), rs_sig(m), ls_sig(m), big_sh_sig(m); + sigma_neg = m_bv_util.mk_bv_neg(sigma); + sigma_cap = m_bv_util.mk_numeral(sbits+2, sigma_size); + m_simp.mk_ite(m_bv_util.mk_sle(sigma_neg, sigma_cap), sigma_neg, sigma_cap, sigma_neg_capped); + dbg_decouple("fpa2bv_rnd_sigma_neg", sigma_neg); + dbg_decouple("fpa2bv_rnd_sigma_neg_capped", sigma_neg_capped); + sigma_lt_zero = m_bv_util.mk_sle(sigma, m_bv_util.mk_numeral(-1, sigma_size)); + dbg_decouple("fpa2bv_rnd_sigma_lt_zero", sigma_lt_zero); + + sig_ext = m_bv_util.mk_concat(sig, m_bv_util.mk_numeral(0, sig_size)); + rs_sig = m_bv_util.mk_bv_lshr(sig_ext, m_bv_util.mk_zero_extend(2*sig_size - sigma_size, sigma_neg_capped)); + ls_sig = m_bv_util.mk_bv_shl(sig_ext, m_bv_util.mk_zero_extend(2*sig_size - sigma_size, sigma)); + m_simp.mk_ite(sigma_lt_zero, rs_sig, ls_sig, big_sh_sig); + SASSERT(m_bv_util.get_bv_size(big_sh_sig) == 2*sig_size); + + dbg_decouple("fpa2bv_rnd_big_sh_sig", big_sh_sig); + + unsigned sig_extract_low_bit = (2*sig_size-1)-(sbits+2)+1; + sig = m_bv_util.mk_extract(2*sig_size-1, sig_extract_low_bit, big_sh_sig); + SASSERT(m_bv_util.get_bv_size(sig) == sbits+2); + + dbg_decouple("fpa2bv_rnd_shifted_sig", sig); + + expr_ref sticky(m); + sticky = m.mk_app(m_bv_util.get_fid(), OP_BREDOR, m_bv_util.mk_extract(sig_extract_low_bit-1, 0, big_sh_sig)); + SASSERT(is_well_sorted(m, sticky)); + SASSERT(is_well_sorted(m, sig)); + + // put the sticky bit into the significand. + expr * tmp[] = { sig, m_bv_util.mk_zero_extend(sbits+1, sticky) }; + sig = m_bv_util.mk_bv_or(2, tmp); + SASSERT(is_well_sorted(m, sig)); + SASSERT(m_bv_util.get_bv_size(sig) == sbits+2); + + // CMW: The (OVF1 && OVFen) and (TINY && UNFen) cases are never taken. + m_simp.mk_ite(TINY, m_bv_util.mk_zero_extend(2, e_min), beta, exp); + SASSERT(is_well_sorted(m, exp)); + + // Significand rounding + expr_ref round(m), last(m); + sticky = m_bv_util.mk_extract(0, 0, sig); // new sticky bit! + round = m_bv_util.mk_extract(1, 1, sig); + last = m_bv_util.mk_extract(2, 2, sig); + + TRACE("fpa2bv_dbg", tout << "sticky = " << mk_ismt2_pp(sticky, m) << std::endl;); + + dbg_decouple("fpa2bv_rnd_sticky", sticky); + dbg_decouple("fpa2bv_rnd_round", round); + dbg_decouple("fpa2bv_rnd_last", last); + + sig = m_bv_util.mk_extract(sbits+1, 2, sig); + + expr * last_sticky[2] = { last, sticky }; + expr * round_sticky[2] = { round, sticky }; + expr * last_or_sticky = m_bv_util.mk_bv_or(2, last_sticky); + expr * round_or_sticky = m_bv_util.mk_bv_or(2, round_sticky); + expr * round_lors[2] = { m_bv_util.mk_bv_not(round), m_bv_util.mk_bv_not(last_or_sticky) }; + expr * pos_args[2] = { sgn, m_bv_util.mk_bv_not(round_or_sticky) }; + expr * neg_args[2] = { m_bv_util.mk_bv_not(sgn), m_bv_util.mk_bv_not(round_or_sticky) }; + + expr_ref inc_teven(m), inc_taway(m), inc_pos(m), inc_neg(m); + inc_teven = m_bv_util.mk_bv_not(m_bv_util.mk_bv_or(2, round_lors)); + inc_taway = round; + inc_pos = m_bv_util.mk_bv_not(m_bv_util.mk_bv_or(2, pos_args)); + inc_neg = m_bv_util.mk_bv_not(m_bv_util.mk_bv_or(2, neg_args)); + + expr_ref inc(m), inc_c2(m), inc_c3(m), inc_c4(m); + expr_ref rm_is_to_neg(m), rm_is_to_pos(m), rm_is_away(m), rm_is_even(m); + mk_is_rm(rm, BV_RM_TO_NEGATIVE, rm_is_to_neg); + mk_is_rm(rm, BV_RM_TO_POSITIVE, rm_is_to_pos); + mk_is_rm(rm, BV_RM_TIES_TO_AWAY, rm_is_away); + mk_is_rm(rm, BV_RM_TIES_TO_EVEN, rm_is_even); + m_simp.mk_ite(rm_is_to_neg, inc_neg, m_bv_util.mk_numeral(0, 1), inc_c4); + m_simp.mk_ite(rm_is_to_pos, inc_pos, inc_c4, inc_c3); + m_simp.mk_ite(rm_is_away, inc_taway, inc_c3, inc_c2); + m_simp.mk_ite(rm_is_even, inc_teven, inc_c2, inc); + + SASSERT(m_bv_util.get_bv_size(inc) == 1 && is_well_sorted(m, inc)); + dbg_decouple("fpa2bv_rnd_inc", inc); + + sig = m_bv_util.mk_bv_add(m_bv_util.mk_zero_extend(1, sig), + m_bv_util.mk_zero_extend(sbits, inc)); + SASSERT(is_well_sorted(m, sig)); + dbg_decouple("fpa2bv_rnd_sig_plus_inc", sig); + + // Post normalization + SASSERT(m_bv_util.get_bv_size(sig) == sbits + 1); + expr_ref SIGovf(m); + m_simp.mk_eq(m_bv_util.mk_extract(sbits, sbits, sig), m_bv_util.mk_numeral(1, 1), SIGovf); + SASSERT(is_well_sorted(m, SIGovf)); + dbg_decouple("fpa2bv_rnd_SIGovf", SIGovf); + + m_simp.mk_ite(SIGovf, + m_bv_util.mk_extract(sbits, 1, sig), + m_bv_util.mk_extract(sbits-1, 0, sig), + sig); + + SASSERT(m_bv_util.get_bv_size(exp) == ebits + 2); + + m_simp.mk_ite(SIGovf, + m_bv_util.mk_bv_add(exp, m_bv_util.mk_numeral(1, ebits+2)), + exp, + exp); + + SASSERT(is_well_sorted(m, sig)); + SASSERT(is_well_sorted(m, exp)); + dbg_decouple("fpa2bv_rnd_sig_postnormalized", sig); + dbg_decouple("fpa2bv_rnd_exp_postnormalized", exp); + + SASSERT(m_bv_util.get_bv_size(sig) == sbits); + SASSERT(m_bv_util.get_bv_size(exp) == ebits + 2); + SASSERT(m_bv_util.get_bv_size(e_max) == ebits); + + // Exponent adjustment and rounding + expr_ref biased_exp(m); + mk_bias(m_bv_util.mk_extract(ebits-1, 0, exp), biased_exp); + dbg_decouple("fpa2bv_rnd_unbiased_exp", exp); + dbg_decouple("fpa2bv_rnd_biased_exp", biased_exp); + + // AdjustExp + SASSERT(is_well_sorted(m, OVF1)); + SASSERT(m.is_bool(OVF1)); + + expr_ref preOVF2(m), OVF2(m), OVF(m); + m_simp.mk_eq(m.mk_app(m_bv_util.get_fid(), OP_BREDAND, biased_exp.get()), m_bv_util.mk_numeral(1, 1), preOVF2); + m_simp.mk_and(SIGovf, preOVF2, OVF2); + m_simp.mk_ite(OVF2, m_bv_util.mk_numeral(fu().fm().m_powers2.m1(ebits-2), ebits), + biased_exp, + biased_exp); + m_simp.mk_or(OVF1, OVF2, OVF); + + SASSERT(is_well_sorted(m, OVF2)); + SASSERT(is_well_sorted(m, OVF)); + + SASSERT(m.is_bool(OVF2)); + SASSERT(m.is_bool(OVF)); + dbg_decouple("fpa2bv_rnd_OVF2", OVF2); + dbg_decouple("fpa2bv_rnd_OVF", OVF); + + // ExpRnd + expr_ref top_exp(m), bot_exp(m); + mk_top_exp(ebits, top_exp); + mk_bot_exp(ebits, bot_exp); + + expr_ref rm_is_to_zero(m), rm_zero_or_neg(m), rm_zero_or_pos(m); + mk_is_rm(rm, BV_RM_TO_ZERO, rm_is_to_zero); + m_simp.mk_or(rm_is_to_zero, rm_is_to_neg, rm_zero_or_neg); + m_simp.mk_or(rm_is_to_zero, rm_is_to_pos, rm_zero_or_pos); + + expr_ref sgn_is_zero(m); + m_simp.mk_eq(sgn, m_bv_util.mk_numeral(0, 1), sgn_is_zero); + + expr_ref max_sig(m), max_exp(m), inf_sig(m), inf_exp(m); + max_sig = m_bv_util.mk_numeral(fu().fm().m_powers2.m1(sbits-1, false), sbits-1); + max_exp = m_bv_util.mk_concat(m_bv_util.mk_numeral(fu().fm().m_powers2.m1(ebits-1, false), ebits-1), + m_bv_util.mk_numeral(0, 1)); + inf_sig = m_bv_util.mk_numeral(0, sbits-1); + inf_exp = top_exp; + + dbg_decouple("fpa2bv_rnd_max_exp", max_exp); + + expr_ref ovfl_exp(m), max_inf_exp_neg(m), max_inf_exp_pos(m), n_d_check(m), n_d_exp(m); + m_simp.mk_ite(rm_zero_or_neg, max_exp, inf_exp, max_inf_exp_neg); + m_simp.mk_ite(rm_zero_or_pos, max_exp, inf_exp, max_inf_exp_pos); + m_simp.mk_ite(sgn_is_zero, max_inf_exp_neg, max_inf_exp_pos, ovfl_exp); + m_simp.mk_eq(m_bv_util.mk_extract(sbits-1, sbits-1, sig), m_bv_util.mk_numeral(0, 1), n_d_check); + m_simp.mk_ite(n_d_check, bot_exp /* denormal */, biased_exp, n_d_exp); + m_simp.mk_ite(OVF, ovfl_exp, n_d_exp, exp); + + expr_ref max_inf_sig_neg(m), max_inf_sig_pos(m), ovfl_sig(m); + m_simp.mk_ite(rm_zero_or_neg, max_sig, inf_sig, max_inf_sig_neg); + m_simp.mk_ite(rm_zero_or_pos, max_sig, inf_sig, max_inf_sig_pos); + m_simp.mk_ite(sgn_is_zero, max_inf_sig_neg, max_inf_sig_pos, ovfl_sig); + m_simp.mk_ite(OVF, ovfl_sig, m_bv_util.mk_extract(sbits-2, 0, sig), sig); + + dbg_decouple("fpa2bv_rnd_sgn_final", sgn); + dbg_decouple("fpa2bv_rnd_sig_final", sig); + dbg_decouple("fpa2bv_rnd_exp_final", exp); + + expr_ref res_sgn(m), res_sig(m), res_exp(m); + res_sgn = sgn; + res_sig = sig; + res_exp = exp; + + SASSERT(m_bv_util.get_bv_size(res_sgn) == 1); + SASSERT(is_well_sorted(m, res_sgn)); + SASSERT(m_bv_util.get_bv_size(res_sig) == sbits-1); + SASSERT(is_well_sorted(m, res_sig)); + SASSERT(m_bv_util.get_bv_size(res_exp) == ebits); + SASSERT(is_well_sorted(m, res_exp)); + + mk_triple(res_sgn, res_sig, res_exp, result); + + TRACE("fpa2bv_round", tout << "ROUND = " << mk_ismt2_pp(result, m) << std::endl; ); +} + + +fpa2bv_model_converter * fpa2bv_converter::mk_model_converter() { + return alloc(fpa2bv_model_converter, m, m_const2bv, m_rm_const2bv); +} + +void fpa2bv_model_converter::display(std::ostream & out) { + out << "(fpa2bv-model-converter"; + for (obj_map::iterator it = m_const2bv.begin(); + it != m_const2bv.end(); + it++) { + const symbol & n = it->m_key->get_name(); + out << "\n (" << n << " "; + unsigned indent = n.size() + 4; + out << mk_ismt2_pp(it->m_value, m, indent) << ")"; + } + for (obj_map::iterator it = m_rm_const2bv.begin(); + it != m_rm_const2bv.end(); + it++) { + const symbol & n = it->m_key->get_name(); + out << "\n (" << n << " "; + unsigned indent = n.size() + 4; + out << mk_ismt2_pp(it->m_value, m, indent) << ")"; + } + out << ")" << std::endl; +} + +model_converter * fpa2bv_model_converter::translate(ast_translation & translator) { + fpa2bv_model_converter * res = alloc(fpa2bv_model_converter, translator.to()); + for (obj_map::iterator it = m_const2bv.begin(); + it != m_const2bv.end(); + it++) + res->m_const2bv.insert(translator(it->m_key), translator(it->m_value)); + for (obj_map::iterator it = m_rm_const2bv.begin(); + it != m_rm_const2bv.end(); + it++) + res->m_rm_const2bv.insert(translator(it->m_key), translator(it->m_value)); + return res; +} + +void fpa2bv_model_converter::convert(model * bv_mdl, model * float_mdl) { + float_util fu(m); + bv_util bu(m); + mpf fp_val; + unsynch_mpz_manager & mpzm = fu.fm().mpz_manager(); + unsynch_mpq_manager & mpqm = fu.fm().mpq_manager(); + + TRACE("fpa2bv_mc", tout << "BV Model: " << std::endl; + for (unsigned i = 0 ; i < bv_mdl->get_num_constants(); i++) + tout << bv_mdl->get_constant(i)->get_name() << " --> " << + mk_ismt2_pp(bv_mdl->get_const_interp(bv_mdl->get_constant(i)), m) << std::endl; + ); + + + for (obj_map::iterator it = m_const2bv.begin(); + it != m_const2bv.end(); + it++) + { + func_decl * var = it->m_key; + app * a = to_app(it->m_value); + SASSERT(fu.is_float(var->get_range())); + SASSERT(var->get_range()->get_num_parameters() == 2); + + unsigned ebits = fu.get_ebits(var->get_range()); + unsigned sbits = fu.get_sbits(var->get_range()); + + expr * sgn = bv_mdl->get_const_interp(to_app(a->get_arg(0))->get_decl()); + expr * sig = bv_mdl->get_const_interp(to_app(a->get_arg(1))->get_decl()); + expr * exp = bv_mdl->get_const_interp(to_app(a->get_arg(2))->get_decl()); + + if (!sgn && !sig && !exp) + continue; + + unsigned sgn_sz = bu.get_bv_size(m.get_sort(a->get_arg(0))); + unsigned sig_sz = bu.get_bv_size(m.get_sort(a->get_arg(1))) - 1; + unsigned exp_sz = bu.get_bv_size(m.get_sort(a->get_arg(2))); + + rational sgn_q(0), sig_q(0), exp_q(0); + + if (sgn) bu.is_numeral(sgn, sgn_q, sgn_sz); + if (sig) bu.is_numeral(sig, sig_q, sig_sz); + if (exp) bu.is_numeral(exp, exp_q, exp_sz); + + // un-bias exponent + rational exp_unbiased_q; + exp_unbiased_q = exp_q - fu.fm().m_powers2.m1(ebits-1); + + mpz sig_z; mpf_exp_t exp_z; + mpzm.set(sig_z, sig_q.to_mpq().numerator()); + exp_z = mpzm.get_int64(exp_unbiased_q.to_mpq().numerator()); + + TRACE("fpa2bv_mc", tout << var->get_name() << " == [" << sgn_q.to_string() << " " << + mpzm.to_string(sig_z) << " " << exp_z << "(" << exp_q.to_string() << ")]" << std::endl; ); + + fu.fm().set(fp_val, ebits, sbits, !mpqm.is_zero(sgn_q.to_mpq()), sig_z, exp_z); + + float_mdl->register_decl(var, fu.mk_value(fp_val)); + + mpzm.del(sig_z); + } + + for (obj_map::iterator it = m_rm_const2bv.begin(); + it != m_rm_const2bv.end(); + it++) + { + func_decl * var = it->m_key; + app * a = to_app(it->m_value); + SASSERT(fu.is_rm(var->get_range())); + rational val(0); + unsigned sz = 0; + if (a && bu.is_numeral(a, val, sz)) { + TRACE("fpa2bv_mc", tout << var->get_name() << " == " << val.to_string() << std::endl; ); + SASSERT(val.is_uint64()); + switch (val.get_uint64()) + { + case BV_RM_TIES_TO_AWAY: float_mdl->register_decl(var, fu.mk_round_nearest_ties_to_away()); break; + case BV_RM_TIES_TO_EVEN: float_mdl->register_decl(var, fu.mk_round_nearest_ties_to_even()); break; + case BV_RM_TO_NEGATIVE: float_mdl->register_decl(var, fu.mk_round_toward_negative()); break; + case BV_RM_TO_POSITIVE: float_mdl->register_decl(var, fu.mk_round_toward_positive()); break; + case BV_RM_TO_ZERO: + default: float_mdl->register_decl(var, fu.mk_round_toward_zero()); + } + } + } + + fu.fm().del(fp_val); +} \ No newline at end of file diff --git a/lib/fpa2bv_converter.h b/lib/fpa2bv_converter.h new file mode 100644 index 000000000..09a1a4de2 --- /dev/null +++ b/lib/fpa2bv_converter.h @@ -0,0 +1,200 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + fpa2bv_converter.h + +Abstract: + + Conversion routines for Floating Point -> Bit-Vector + +Author: + + Christoph (cwinter) 2012-02-09 + +Notes: + +--*/ +#ifndef _FPA2BV_CONVERTER_ +#define _FPA2BV_CONVERTER_ + +#include"ast.h" +#include"obj_hashtable.h" +#include"ref_util.h" +#include"float_decl_plugin.h" +#include"bv_decl_plugin.h" +#include"model_converter.h" +#include"basic_simplifier_plugin.h" + +typedef enum { BV_RM_TIES_TO_AWAY=0, BV_RM_TIES_TO_EVEN=1, BV_RM_TO_NEGATIVE=2, BV_RM_TO_POSITIVE=3, BV_RM_TO_ZERO=4 } BV_RM_VAL; + +class fpa2bv_model_converter; + +class fpa2bv_converter { + ast_manager & m; + basic_simplifier_plugin m_simp; + float_util m_util; + mpf_manager & m_mpf_manager; + unsynch_mpz_manager & m_mpz_manager; + bv_util m_bv_util; + float_decl_plugin * m_plugin; + + obj_map m_const2bv; + obj_map m_rm_const2bv; + +public: + fpa2bv_converter(ast_manager & m); + ~fpa2bv_converter(); + + float_util & fu() { return m_util; } + + bool is_float(sort * s) { return m_util.is_float(s); } + bool is_float(expr * e) { return is_app(e) && m_util.is_float(to_app(e)->get_decl()->get_range()); } + bool is_float_family(func_decl * f) { return f->get_family_id() == m_util.get_family_id(); } + bool is_rm_sort(sort * s) { return m_util.is_rm(s); } + + void mk_triple(expr * sign, expr * significand, expr * exponent, expr_ref & result) { + SASSERT(m_bv_util.is_bv(sign) && m_bv_util.get_bv_size(sign) == 1); + SASSERT(m_bv_util.is_bv(significand)); + SASSERT(m_bv_util.is_bv(exponent)); + result = m.mk_app(m_util.get_family_id(), OP_TO_FLOAT, sign, significand, exponent); + } + + void mk_eq(expr * a, expr * b, expr_ref & result); + void mk_ite(expr * c, expr * t, expr * f, expr_ref & result); + + void mk_rounding_mode(func_decl * f, expr_ref & result); + void mk_value(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + void mk_const(func_decl * f, expr_ref & result); + void mk_rm_const(func_decl * f, expr_ref & result); + + void mk_plus_inf(func_decl * f, expr_ref & result); + void mk_minus_inf(func_decl * f, expr_ref & result); + void mk_nan(func_decl * f, expr_ref & result); + void mk_nzero(func_decl *f, expr_ref & result); + void mk_pzero(func_decl *f, expr_ref & result); + + void mk_add(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + void mk_sub(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + void mk_uminus(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + void mk_mul(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + void mk_div(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + void mk_remainder(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + void mk_abs(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + void mk_min(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + void mk_max(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + void mk_fusedma(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + void mk_sqrt(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + void mk_round_to_integral(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + + void mk_float_eq(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + void mk_float_lt(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + void mk_float_gt(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + void mk_float_le(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + void mk_float_ge(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + + void mk_is_zero(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + void mk_is_nzero(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + void mk_is_pzero(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + void mk_is_sign_minus(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + + void mk_to_float(func_decl * f, unsigned num, expr * const * args, expr_ref & result); + + fpa2bv_model_converter * mk_model_converter(); + + void dbg_decouple(const char * prefix, expr_ref & e); + expr_ref_vector extra_assertions; + +protected: + void split(expr * e, expr * & sgn, expr * & sig, expr * & exp) const; + + void mk_is_nan(expr * e, expr_ref & result); + void mk_is_inf(expr * e, expr_ref & result); + void mk_is_pinf(expr * e, expr_ref & result); + void mk_is_ninf(expr * e, expr_ref & result); + void mk_is_pos(expr * e, expr_ref & result); + void mk_is_neg(expr * e, expr_ref & result); + void mk_is_zero(expr * e, expr_ref & result); + void mk_is_nzero(expr * e, expr_ref & result); + void mk_is_pzero(expr * e, expr_ref & result); + void mk_is_denormal(expr * e, expr_ref & result); + void mk_is_normal(expr * e, expr_ref & result); + + void mk_is_rm(expr * e, BV_RM_VAL rm, expr_ref & result); + + void mk_top_exp(unsigned sz, expr_ref & result); + void mk_bot_exp(unsigned sz, expr_ref & result); + void mk_min_exp(unsigned ebits, expr_ref & result); + void mk_max_exp(unsigned ebits, expr_ref & result); + + void mk_leading_zeros(expr * e, unsigned max_bits, expr_ref & result); + + void mk_bias(expr * e, expr_ref & result); + void mk_unbias(expr * e, expr_ref & result); + + void unpack(expr * e, expr_ref & sgn, expr_ref & sig, expr_ref & exp, bool normalize); + void round(sort * s, expr_ref & rm, expr_ref & sgn, expr_ref & sig, expr_ref & exp, expr_ref & result); + + void add_core(unsigned sbits, unsigned ebits, expr_ref & rm, + expr_ref & c_sgn, expr_ref & c_sig, expr_ref & c_exp, expr_ref & d_sgn, expr_ref & d_sig, expr_ref & d_exp, + expr_ref & res_sgn, expr_ref & res_sig, expr_ref & res_exp); +}; + + +class fpa2bv_model_converter : public model_converter { + ast_manager & m; + obj_map m_const2bv; + obj_map m_rm_const2bv; + +public: + fpa2bv_model_converter(ast_manager & m, obj_map & const2bv, + obj_map & rm_const2bv) : + m(m) { + // Just create a copy? + for (obj_map::iterator it = const2bv.begin(); + it != const2bv.end(); + it++) + { + m_const2bv.insert(it->m_key, it->m_value); + m.inc_ref(it->m_key); + m.inc_ref(it->m_value); + } + for (obj_map::iterator it = rm_const2bv.begin(); + it != rm_const2bv.end(); + it++) + { + m_rm_const2bv.insert(it->m_key, it->m_value); + m.inc_ref(it->m_key); + m.inc_ref(it->m_value); + } + } + + virtual ~fpa2bv_model_converter() { + dec_ref_map_key_values(m, m_const2bv); + dec_ref_map_key_values(m, m_rm_const2bv); + } + + virtual void operator()(model_ref & md, unsigned goal_idx) { + SASSERT(goal_idx == 0); + model * new_model = alloc(model, m); + obj_hashtable bits; + convert(md.get(), new_model); + md = new_model; + } + + virtual void operator()(model_ref & md) { + operator()(md, 0); + } + + void display(std::ostream & out); + + virtual model_converter * translate(ast_translation & translator); + +protected: + fpa2bv_model_converter(ast_manager & m) : m(m) { } + + void convert(model * bv_mdl, model * float_mdl); +}; + +#endif \ No newline at end of file diff --git a/lib/fpa2bv_tactic.cpp b/lib/fpa2bv_tactic.cpp new file mode 100644 index 000000000..a4e46472a --- /dev/null +++ b/lib/fpa2bv_tactic.cpp @@ -0,0 +1,310 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + fpa2bv_tactic.cpp + +Abstract: + + Tactic that converts floating points to bit-vectors + +Author: + + Christoph (cwinter) 2012-02-09 + +Notes: + +--*/ +#include"tactical.h" +#include"rewriter_def.h" +#include"cooperate.h" +#include"ref_util.h" +#include"bv_decl_plugin.h" +#include"float_decl_plugin.h" +#include"fpa2bv_converter.h" + +#include"tactical.h" +#include"simplify_tactic.h" + +#include"fpa2bv_tactic.h" + +struct fpa2bv_rewriter_cfg : public default_rewriter_cfg { + ast_manager & m_manager; + expr_ref_vector m_out; + fpa2bv_converter & m_conv; + + unsigned long long m_max_memory; + unsigned m_max_steps; + + ast_manager & m() const { return m_manager; } + + fpa2bv_rewriter_cfg(ast_manager & m, fpa2bv_converter & c, params_ref const & p): + m_manager(m), + m_out(m), + m_conv(c) { + updt_params(p); + // We need to make sure that the mananger has the BV plugin loaded. + symbol s_bv("bv"); + if (!m_manager.has_plugin(s_bv)) + m_manager.register_plugin(s_bv, alloc(bv_decl_plugin)); + } + + ~fpa2bv_rewriter_cfg() { + } + + void cleanup_buffers() { + m_out.finalize(); + } + + void updt_params(params_ref const & p) { + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + m_max_steps = p.get_uint(":max-steps", UINT_MAX); + } + + bool max_steps_exceeded(unsigned num_steps) const { + cooperate("fpa2bv"); + if (memory::get_allocation_size() > m_max_memory) + throw tactic_exception(TACTIC_MAX_MEMORY_MSG); + return num_steps > m_max_steps; + } + + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + TRACE("fpa2bv_rw", tout << "APP: " << f->get_name() << std::endl; ); + + if (num == 0 && f->get_family_id() == null_family_id && m_conv.is_float(f->get_range())) { + m_conv.mk_const(f, result); + return BR_DONE; + } + + if (num == 0 && f->get_family_id() == null_family_id && m_conv.is_rm_sort(f->get_range())) { + m_conv.mk_rm_const(f, result); + return BR_DONE; + } + + if (m().is_eq(f)) { + SASSERT(num == 2); + if (m_conv.is_float(args[0])) { + m_conv.mk_eq(args[0], args[1], result); + return BR_DONE; + } + return BR_FAILED; + } + + if (m().is_ite(f)) { + SASSERT(num == 3); + if (m_conv.is_float(args[1])) { + m_conv.mk_ite(args[0], args[1], args[2], result); + return BR_DONE; + } + return BR_FAILED; + } + + if (m_conv.is_float_family(f)) { + switch (f->get_decl_kind()) { + case OP_RM_NEAREST_TIES_TO_AWAY: + case OP_RM_NEAREST_TIES_TO_EVEN: + case OP_RM_TOWARD_NEGATIVE: + case OP_RM_TOWARD_POSITIVE: + case OP_RM_TOWARD_ZERO: m_conv.mk_rounding_mode(f, result); return BR_DONE; + case OP_FLOAT_VALUE: m_conv.mk_value(f, num, args, result); return BR_DONE; + case OP_FLOAT_PLUS_INF: m_conv.mk_plus_inf(f, result); return BR_DONE; + case OP_FLOAT_MINUS_INF: m_conv.mk_minus_inf(f, result); return BR_DONE; + case OP_FLOAT_NAN: m_conv.mk_nan(f, result); return BR_DONE; + case OP_FLOAT_ADD: m_conv.mk_add(f, num, args, result); return BR_DONE; + case OP_FLOAT_SUB: m_conv.mk_sub(f, num, args, result); return BR_DONE; + case OP_FLOAT_UMINUS: m_conv.mk_uminus(f, num, args, result); return BR_DONE; + case OP_FLOAT_MUL: m_conv.mk_mul(f, num, args, result); return BR_DONE; + case OP_FLOAT_DIV: m_conv.mk_div(f, num, args, result); return BR_DONE; + case OP_FLOAT_REM: m_conv.mk_remainder(f, num, args, result); return BR_DONE; + case OP_FLOAT_ABS: m_conv.mk_abs(f, num, args, result); return BR_DONE; + case OP_FLOAT_MIN: m_conv.mk_min(f, num, args, result); return BR_DONE; + case OP_FLOAT_MAX: m_conv.mk_max(f, num, args, result); return BR_DONE; + case OP_FLOAT_FUSED_MA: m_conv.mk_fusedma(f, num, args, result); return BR_DONE; + case OP_FLOAT_SQRT: m_conv.mk_sqrt(f, num, args, result); return BR_DONE; + case OP_FLOAT_ROUND_TO_INTEGRAL: m_conv.mk_round_to_integral(f, num, args, result); return BR_DONE; + case OP_FLOAT_EQ: m_conv.mk_float_eq(f, num, args, result); return BR_DONE; + case OP_FLOAT_LT: m_conv.mk_float_lt(f, num, args, result); return BR_DONE; + case OP_FLOAT_GT: m_conv.mk_float_gt(f, num, args, result); return BR_DONE; + case OP_FLOAT_LE: m_conv.mk_float_le(f, num, args, result); return BR_DONE; + case OP_FLOAT_GE: m_conv.mk_float_ge(f, num, args, result); return BR_DONE; + case OP_FLOAT_IS_ZERO: m_conv.mk_is_zero(f, num, args, result); return BR_DONE; + case OP_FLOAT_IS_NZERO: m_conv.mk_is_nzero(f, num, args, result); return BR_DONE; + case OP_FLOAT_IS_PZERO: m_conv.mk_is_pzero(f, num, args, result); return BR_DONE; + case OP_FLOAT_IS_SIGN_MINUS: m_conv.mk_is_sign_minus(f, num, args, result); return BR_DONE; + case OP_TO_FLOAT: m_conv.mk_to_float(f, num, args, result); return BR_DONE; + default: + TRACE("fpa2bv", tout << "unsupported operator: " << f->get_name() << "\n"; + for (unsigned i = 0; i < num; i++) tout << mk_ismt2_pp(args[i], m()) << std::endl;); + throw tactic_exception("NYI"); + } + } + + return BR_FAILED; + } + + bool reduce_quantifier(quantifier * old_q, + expr * new_body, + expr * const * new_patterns, + expr * const * new_no_patterns, + expr_ref & result, + proof_ref & result_pr) { + return false; + } + + bool reduce_var(var * t, expr_ref & result, proof_ref & result_pr) { + return false; + } +}; + +template class rewriter_tpl; + +struct fpa2bv_rewriter : public rewriter_tpl { + fpa2bv_rewriter_cfg m_cfg; + fpa2bv_rewriter(ast_manager & m, fpa2bv_converter & c, params_ref const & p): + rewriter_tpl(m, m.proofs_enabled(), m_cfg), + m_cfg(m, c, p) { + } +}; + +class fpa2bv_tactic : public tactic { + struct imp { + ast_manager & m; + fpa2bv_converter m_conv; + fpa2bv_rewriter m_rw; + unsigned m_num_steps; + + bool m_proofs_enabled; + bool m_produce_models; + bool m_produce_unsat_cores; + + imp(ast_manager & _m, params_ref const & p): + m(_m), + m_conv(m), + m_rw(m, m_conv, p), + m_proofs_enabled(false), + m_produce_models(false), + m_produce_unsat_cores(false) { + } + + void updt_params(params_ref const & p) { + m_rw.cfg().updt_params(p); + } + + void set_cancel(bool f) { + m_rw.set_cancel(f); + } + + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + fail_if_proof_generation("fpa2bv", g); + fail_if_unsat_core_generation("fpa2bv", g); + m_proofs_enabled = g->proofs_enabled(); + m_produce_models = g->models_enabled(); + m_produce_unsat_cores = g->unsat_core_enabled(); + + mc = 0; pc = 0; core = 0; result.reset(); + tactic_report report("fpa2bv", *g); + m_rw.reset(); + + TRACE("fpa2bv", tout << "BEFORE: " << std::endl; g->display(tout);); + + if (g->inconsistent()) { + result.push_back(g.get()); + return; + } + + m_num_steps = 0; + expr_ref new_curr(m); + proof_ref new_pr(m); + unsigned size = g->size(); + for (unsigned idx = 0; idx < size; idx++) { + if (g->inconsistent()) + break; + expr * curr = g->form(idx); + m_rw(curr, new_curr, new_pr); + m_num_steps += m_rw.get_num_steps(); + if (m_proofs_enabled) { + proof * pr = g->pr(idx); + new_pr = m.mk_modus_ponens(pr, new_pr); + } + g->update(idx, new_curr, new_pr, g->dep(idx)); + } + + if (g->models_enabled()) + mc = m_conv.mk_model_converter(); + + g->inc_depth(); + result.push_back(g.get()); + + for (unsigned i = 0; i < m_conv.extra_assertions.size(); i++) + result.back()->assert_expr(m_conv.extra_assertions[i].get()); + + SASSERT(g->is_well_sorted()); + TRACE("fpa2bv", tout << "AFTER: " << std::endl; g->display(tout); + if (mc) mc->display(tout); tout << std::endl; ); + } + }; + + imp * m_imp; + params_ref m_params; + +public: + fpa2bv_tactic(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(fpa2bv_tactic, m, m_params); + } + + virtual ~fpa2bv_tactic() { + dealloc(m_imp); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + (*m_imp)(in, result, mc, pc, core); + } + + virtual void cleanup() { + ast_manager & m = m_imp->m; + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + d = m_imp; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + } + +protected: + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + +tactic * mk_fpa2bv_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(fpa2bv_tactic, m, p)); +} diff --git a/lib/fpa2bv_tactic.h b/lib/fpa2bv_tactic.h new file mode 100644 index 000000000..91647318f --- /dev/null +++ b/lib/fpa2bv_tactic.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + fpa2bv_tactic.h + +Abstract: + + Tactic that converts floating points to bit-vectors + +Author: + + Christoph (cwinter) 2012-02-09 + +Notes: + +--*/ +#ifndef _FPA2BV_TACTIC_ +#define _FPA2BV_TACTIC_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_fpa2bv_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif \ No newline at end of file diff --git a/lib/front_end_params.cpp b/lib/front_end_params.cpp new file mode 100644 index 000000000..2dc037e1b --- /dev/null +++ b/lib/front_end_params.cpp @@ -0,0 +1,108 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + front_end_params.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-05-10. + +Revision History: + +--*/ +#include"front_end_params.h" + +void front_end_params::register_params(ini_params & p) { + p.register_param_vector(m_param_vector.get()); + preprocessor_params::register_params(p); + spc_params::register_params(p); + smt_params::register_params(p); + parser_params::register_params(p); + arith_simplifier_params::register_params(p); + p.register_int_param("ENGINE", 0, 2, reinterpret_cast(m_engine), "0: SMT solver, 1: Superposition prover, 2: EPR solver, true"); + z3_solver_params::register_params(p); + model_params::register_params(p); + p.register_unsigned_param("MAX_COUNTEREXAMPLES", m_max_num_cex, + "set the maximum number of counterexamples when using Simplify front end"); + p.register_bool_param("AT_LABELS_CEX", m_at_labels_cex, + "only use labels that contain '@' when building multiple counterexamples"); + p.register_bool_param("CHECK_AT_LABELS", m_check_at_labels, + "check that labels containing '@' are used correctly to only produce unique counter examples"); + p.register_bool_param("DEFAULT_QID", m_default_qid, "create a default quantifier id based on its position, the id is used to report profiling information (see QI_PROFILE)"); + + p.register_bool_param("TYPE_CHECK", m_well_sorted_check, "enable/disable type checker"); + p.register_bool_param("WELL_SORTED_CHECK", m_well_sorted_check, "enable/disable type checker"); + p.register_bool_param("INTERACTIVE", m_interactive, "enable interactive mode using Simplify input format"); + p.register_unsigned_param("SOFT_TIMEOUT", m_soft_timeout, "set approximate timeout for each solver query (milliseconds), the value 0 represents no timeout", true); + p.register_double_param("INSTRUCTION_MAX", m_instr_out, "set the (approximate) maximal number of instructions per invocation of check", true); + p.register_bool_param("AUTO_CONFIG", m_auto_config, "use heuristics to set Z3 configuration parameters, it is only available for the SMT-LIB input format"); + p.register_int_param("PROOF_MODE", 0, 2, reinterpret_cast(m_proof_mode), "select proof generation mode: 0 - disabled, 1 - coarse grain, 2 - fine grain"); + p.register_bool_param("TRACE", m_trace, "enable tracing for the Axiom Profiler tool"); + p.register_string_param("TRACE_FILE_NAME", m_trace_file_name, "tracing file name"); + p.register_bool_param("IGNORE_SETPARAMETER", m_ignore_setparameter, "ignore (SETPARAMETER ...) commands in Simplify format input"); + p.register_bool_param("ASYNC_COMMANDS", m_async_commands, "enable/disable support for asynchronous commands in the Simplify front-end."); + p.register_bool_param("DISPLAY_CONFIG", m_display_config, "display configuration used by Z3"); + +#ifdef _WINDOWS + // The non-windows memory manager does not have access to memory sizes. + p.register_unsigned_param("MEMORY_HIGH_WATERMARK", m_memory_high_watermark, + "set high watermark for memory consumption (in megabytes)"); + p.register_unsigned_param("MEMORY_MAX_SIZE", m_memory_max_size, + "set hard upper limit for memory consumption (in megabytes)"); +#endif + +#ifndef _EXTERNAL_RELEASE + // external users should not have access to it. + p.register_bool_param("PREPROCESS", m_preprocess); +#endif + + p.register_bool_param("USER_THEORY_PREPROCESS_AXIOMS", + m_user_theory_preprocess_axioms, + "Apply full pre-processing to user theory axioms", + true); + + p.register_bool_param("USER_THEORY_PERSIST_AXIOMS", + m_user_theory_persist_axioms, + "Persist user axioms to the base level", + true); + + p.register_bool_param("SMTLIB2_COMPLIANT", m_smtlib2_compliant); + + p.register_bool_param("IGNORE_BAD_PATTERNS", m_ignore_bad_patterns); + + PRIVATE_PARAMS({ + p.register_bool_param("IGNORE_CHECKSAT", m_ignore_checksat); + p.register_bool_param("DEBUG_REF_COUNT", m_debug_ref_count); + p.register_bool_param("IGNORE_USER_PATTERNS", m_ignore_user_patterns); + p.register_bool_param("INCREMENTAL_CORE_ASSERT", m_incremental_core_assert); + DEBUG_CODE(p.register_int_param("COPY_PARAMS", m_copy_params);); + }); + + // temporary hack until strategic_solver is ported to new tactic framework + PRIVATE_PARAMS({ + p.register_bool_param("NLSAT", m_nlsat); + }); +} + +void front_end_params::open_trace_file() { + if (m_trace) { + m_trace_stream = alloc(std::fstream, m_trace_file_name.c_str(), std::ios_base::out); + } +} + +void front_end_params::close_trace_file() { + if (m_trace_stream != NULL) { + std::fstream &tmp = *m_trace_stream; + m_trace_stream = NULL; + tmp << "[eof]\n"; + tmp.close(); + // do not delete it, this might be called from a Ctrl-C signal handler + // and there might be someone writing to it + } +} diff --git a/lib/front_end_params.h b/lib/front_end_params.h new file mode 100644 index 000000000..9c6c1af8c --- /dev/null +++ b/lib/front_end_params.h @@ -0,0 +1,139 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + front_end_params.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-05-10. + +Revision History: + +--*/ +#ifndef _FRONT_END_PARAMS_H_ +#define _FRONT_END_PARAMS_H_ + +#include"ini_file.h" +#include"ast.h" +#include"preprocessor_params.h" +#include"spc_params.h" +#include"smt_params.h" +#include"pp_params.h" +#include"parser_params.h" +#include"arith_simplifier_params.h" +#include"z3_solver_params.h" +#include"model_params.h" + +enum engine { + ENG_SMT, + ENG_SPC, + ENG_EPR +}; + +struct front_end_params : public preprocessor_params, public spc_params, public smt_params, public parser_params, + public arith_simplifier_params, public z3_solver_params, public model_params + { + ref m_param_vector; + engine m_engine; + unsigned m_max_num_cex; // maximum number of counterexamples + 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_default_qid; + bool m_interactive; + bool m_well_sorted_check; + bool m_ignore_bad_patterns; + bool m_ignore_user_patterns; + bool m_incremental_core_assert; // assert conditions to the core incrementally + unsigned m_soft_timeout; + double m_instr_out; + unsigned m_memory_high_watermark; + unsigned m_memory_max_size; + proof_gen_mode m_proof_mode; + bool m_auto_config; + bool m_smtlib2_compliant; +#ifdef Z3DEBUG + int m_copy_params; // used for testing copy params... Invoke method copy_params(m_copy_params) in main.cpp when diff -1. +#endif + bool m_preprocess; // temporary hack for disabling all preprocessing.. + bool m_ignore_checksat; // abort before checksat... for internal debugging + bool m_debug_ref_count; + bool m_trace; + std::string m_trace_file_name; + std::fstream* m_trace_stream; + bool m_ignore_setparameter; + bool m_async_commands; + bool m_display_config; + bool m_user_theory_preprocess_axioms; + bool m_user_theory_persist_axioms; + bool m_nlsat; // temporary hack until strategic_solver is ported to new tactic framework + + front_end_params(): + m_param_vector(alloc(param_vector, this)), + m_engine(ENG_SMT), + m_max_num_cex(1), + m_at_labels_cex(false), + m_check_at_labels(false), + m_default_qid(false), + m_interactive(false), + m_well_sorted_check(true), + m_ignore_bad_patterns(true), + m_ignore_user_patterns(false), + m_incremental_core_assert(true), + m_soft_timeout(0), + m_instr_out(0.0), + m_memory_high_watermark(0), + m_memory_max_size(0), + m_proof_mode(PGM_DISABLED), +#if defined(SMTCOMP) || defined(_EXTERNAL_RELEASE) + m_auto_config(true), +#else + m_auto_config(false), +#endif +#if 1 + // #if defined(SMTCOMP) TODO: put it back after SMTCOMP + m_smtlib2_compliant(true), +#else + m_smtlib2_compliant(false), +#endif +#ifdef Z3DEBUG + m_copy_params(-1), +#endif + m_preprocess(true), // temporary hack for disabling all preprocessing.. + m_ignore_checksat(false), + m_debug_ref_count(false), + m_trace(false), + m_trace_file_name("z3.log"), + m_trace_stream(NULL), + m_ignore_setparameter(false), + m_async_commands(true), + m_display_config(false), + m_user_theory_preprocess_axioms(false), + m_user_theory_persist_axioms(false), + m_nlsat(false) { + } + + void register_params(ini_params & p); + + void open_trace_file(); + + void close_trace_file(); + + void copy_params(unsigned idx) { + m_param_vector->copy_params(this, idx); + } + + bool has_auto_config(unsigned idx) { return m_auto_config; } + +private: + + front_end_params& operator=(front_end_params const& other); +}; + +#endif /* _FRONT_END_PARAMS_H_ */ + diff --git a/lib/func_decl_dependencies.cpp b/lib/func_decl_dependencies.cpp new file mode 100644 index 000000000..5e054d5d9 --- /dev/null +++ b/lib/func_decl_dependencies.cpp @@ -0,0 +1,225 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + func_decl_dependencies.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-12-15. + +Revision History: + +--*/ +#include"func_decl_dependencies.h" +#include"for_each_expr.h" +#include"ast_util.h" + +struct collect_dependencies_proc { + ast_manager & m_manager; + func_decl_set & m_set; + bool m_ng_only; // collect only declarations in non ground expressions + + collect_dependencies_proc(ast_manager & m, func_decl_set & s, bool ng_only): + m_manager(m), + m_set(s), + m_ng_only(ng_only) {} + + void operator()(var * n) {} + + void operator()(quantifier * n) {} + + void operator()(app * n) { + // We do not need to track dependencies on constants ... + if (n->get_num_args()==0) + return; + if (m_ng_only && is_ground(n)) + return; + // ... and interpreted function symbols + func_decl * d = n->get_decl(); + if (d->get_family_id() == null_family_id) { + m_set.insert(d); + } + } +}; + +void collect_func_decls(ast_manager & m, expr * n, func_decl_set & r, bool ng_only) { + collect_dependencies_proc proc(m, r, ng_only); + for_each_expr(proc, n); +} + +void func_decl_dependencies::reset() { + dependency_graph::iterator it = m_deps.begin(); + dependency_graph::iterator end = m_deps.end(); + for (; it != end; ++it) { + func_decl * f = (*it).m_key; + func_decl_set * s = (*it).m_value; + m_manager.dec_ref(f); + dec_ref(m_manager, *s); + dealloc(s); + } + m_deps.reset(); +} + +void func_decl_dependencies::collect_func_decls(expr * n, func_decl_set * s) { + ::collect_func_decls(m_manager, n, *s, false); +} + +void func_decl_dependencies::collect_ng_func_decls(expr * n, func_decl_set * s) { + ::collect_func_decls(m_manager, n, *s, true); +} + +/** + \brief Functor for finding cycles in macro definitions +*/ +class func_decl_dependencies::top_sort { + enum color { OPEN, IN_PROGRESS, CLOSED }; + ast_manager & m_manager; + dependency_graph & m_deps; + + typedef obj_map color_map; + color_map m_colors; + ptr_vector m_todo; + + func_decl_set * definition(func_decl * f) const { + func_decl_set * r = 0; + m_deps.find(f, r); + return r; + } + + color get_color(func_decl * f) const { + if (!f) + return CLOSED; + color_map::iterator it = m_colors.find_iterator(f); + if (it != m_colors.end()) + return it->m_value; + return OPEN; + } + + void set_color(func_decl * f, color c) { + m_colors.insert(f, c); + } + + void visit(func_decl * f, bool & visited) { + if (get_color(f) != CLOSED) { + m_todo.push_back(f); + visited = false; + } + } + + bool visit_children(func_decl * f) { + func_decl_set * def = definition(f); + if (!def) + return true; + bool visited = true; + func_decl_set::iterator it = def->begin(); + func_decl_set::iterator end = def->end(); + for (; it != end; ++it) { + visit(*it, visited); + } + return visited; + } + + bool all_children_closed(func_decl * f) const { + func_decl_set * def = definition(f); + if (!def) + return true; + func_decl_set::iterator it = def->begin(); + func_decl_set::iterator end = def->end(); + for (; it != end; ++it) { + if (get_color(*it) != CLOSED) + return false; + } + return true; + } + + /** + \brief Return \c true if a cycle is detected. + */ + bool main_loop(func_decl * f) { + if (get_color(f) == CLOSED) + return false; + m_todo.push_back(f); + while (!m_todo.empty()) { + func_decl * f = m_todo.back(); + + switch (get_color(f)) { + case CLOSED: + m_todo.pop_back(); + break; + case OPEN: + set_color(f, IN_PROGRESS); + if (visit_children(f)) { + SASSERT(m_todo.back() == f); + m_todo.pop_back(); + set_color(f, CLOSED); + } + break; + case IN_PROGRESS: + if (all_children_closed(f)) { + SASSERT(m_todo.back() == f); + set_color(f, CLOSED); + } else { + m_todo.reset(); + return true; + } + break; + default: + UNREACHABLE(); + } + } + return false; + } + +public: + top_sort(ast_manager & m, dependency_graph & deps):m_manager(m), m_deps(deps) {} + + bool operator()(func_decl * new_decl) { + + // [Leo]: It is not trivial to reuse m_colors between different calls since we are update the graph. + // To implement this optimization, we need an incremental topological sort algorithm. + // The trick of saving the dependencies will save a lot of time. So, I don't think we really + // need a incremental top-sort algo. + m_colors.reset(); + return main_loop(new_decl); + } +}; + +bool func_decl_dependencies::insert(func_decl * f, func_decl_set * s) { + if (m_deps.contains(f)) { + dealloc(s); + return false; + } + + m_deps.insert(f, s); + + top_sort cycle_detector(m_manager, m_deps); + if (cycle_detector(f)) { + m_deps.erase(f); + dealloc(s); + return false; + } + + m_manager.inc_ref(f); + inc_ref(m_manager, *s); + return true; +} + +void func_decl_dependencies::erase(func_decl * f) { + func_decl_set * s = 0; + if (m_deps.find(f, s)) { + m_manager.dec_ref(f); + dec_ref(m_manager, *s); + m_deps.erase(f); + dealloc(s); + } +} + +void func_decl_dependencies::display(std::ostream & out) { + // TODO +} diff --git a/lib/func_decl_dependencies.h b/lib/func_decl_dependencies.h new file mode 100644 index 000000000..f88062063 --- /dev/null +++ b/lib/func_decl_dependencies.h @@ -0,0 +1,110 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + func_decl_dependencies.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-12-15. + +Revision History: + +--*/ +#ifndef _FUNC_DECL_DEPENDENCIES_H_ +#define _FUNC_DECL_DEPENDENCIES_H_ + +#include"ast.h" +#include"obj_hashtable.h" + +// Set of dependencies +typedef obj_hashtable func_decl_set; + +/** + \brief Collect uninterpreted function declarations (with arity > 0) occurring in \c n. +*/ +void collect_func_decls(ast_manager & m, expr * n, func_decl_set & r, bool ng_only = false); + +/** + \brief Auxiliary data-structure used for tracking dependencies between function declarations. + + The following pattern of use is expected: + + func_decl_dependencies & dm; + func_decl_set * S = dm.mk_func_decl_set(); + dm.collect_func_decls(t_1, S); + ... + dm.collect_func_decls(t_n, S); + dm.insert(f, S); +*/ +class func_decl_dependencies { + typedef obj_map dependency_graph; + ast_manager & m_manager; + dependency_graph m_deps; + + class top_sort; + +public: + func_decl_dependencies(ast_manager & m):m_manager(m) {} + ~func_decl_dependencies() { + reset(); + } + + void reset(); + + /** + \brief Create a dependecy set. + This set should be populated using #collect_func_decls. + After populating the set, it must be used as an argument for the #insert method. + + \remark The manager owns the set. + + \warning Failure to call #insert will produce a memory leak. + */ + func_decl_set * mk_func_decl_set() { return alloc(func_decl_set); } + + /** + \brief Store the uninterpreted function declarations used in \c n into \c s. + */ + void collect_func_decls(expr * n, func_decl_set * s); + + /** + \brief Store the uninterpreted function declarations (in non ground terms) used in \c n into \c s. + */ + void collect_ng_func_decls(expr * n, func_decl_set * s); + + /** + \brief Insert \c f in the manager with the given set of dependencies. + The insertion succeeds iff + 1- no cycle is created between the new entry and + the already existing dependencies. + 2- \c f was not already inserted into the manager. + + Return false in case of failure. + + \remark The manager is the owner of the dependency sets. + */ + bool insert(func_decl * f, func_decl_set * s); + + /** + \brief Return true if \c f is registered in this manager. + */ + bool contains(func_decl * f) const { return m_deps.contains(f); } + + func_decl_set * get_dependencies(func_decl * f) const { func_decl_set * r = 0; m_deps.find(f, r); return r; } + + /** + \brief Erase \c f (and its dependencies) from the manager. + */ + void erase(func_decl * f); + + void display(std::ostream & out); +}; + + +#endif diff --git a/lib/func_interp.cpp b/lib/func_interp.cpp new file mode 100644 index 000000000..e612734eb --- /dev/null +++ b/lib/func_interp.cpp @@ -0,0 +1,360 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + func_interp.cpp + +Abstract: + See func_interp.h + +Author: + + Leonardo de Moura (leonardo) 2010-12-30. + +Revision History: + +--*/ +#include"func_interp.h" +#include"simplifier.h" +#include"basic_simplifier_plugin.h" +#include"var_subst.h" +#include"obj_hashtable.h" +#include"ast_pp.h" +#include"ast_smt2_pp.h" + +func_entry::func_entry(ast_manager & m, unsigned arity, expr * const * args, expr * result): + m_args_are_values(true), + m_result(result) { + SASSERT(is_ground(result)); + m.inc_ref(result); + for (unsigned i = 0; i < arity; i++) { + expr * arg = args[i]; + SASSERT(is_ground(arg)); + if (!m.is_value(arg)) + m_args_are_values = false; + m.inc_ref(arg); + m_args[i] = arg; + } +} + +func_entry * func_entry::mk(ast_manager & m, unsigned arity, expr * const * args, expr * result) { + small_object_allocator & allocator = m.get_allocator(); + unsigned sz = get_obj_size(arity); + void * mem = allocator.allocate(sz); + return new (mem) func_entry(m, arity, args, result); +} + +void func_entry::set_result(ast_manager & m, expr * r) { + m.inc_ref(r); + m.dec_ref(m_result); + m_result = r; +} + +bool func_entry::eq_args(unsigned arity, expr * const * args) const { + unsigned i = 0; + for (; i < arity; i++) { + if (m_args[i] != args[i]) + return false; + } + return true; +} + +void func_entry::deallocate(ast_manager & m, unsigned arity) { + for (unsigned i = 0; i < arity; i++) { + m.dec_ref(m_args[i]); + } + m.dec_ref(m_result); + small_object_allocator & allocator = m.get_allocator(); + unsigned sz = get_obj_size(arity); + allocator.deallocate(sz, this); +} + +func_interp::func_interp(ast_manager & m, unsigned arity): + m_manager(m), + m_arity(arity), + m_else(0), + m_args_are_values(true), + m_interp(0) { +} + +func_interp::~func_interp() { + ptr_vector::iterator it = m_entries.begin(); + ptr_vector::iterator end = m_entries.end(); + for (; it != end; ++it) { + func_entry * curr = *it; + curr->deallocate(m_manager, m_arity); + } + m_manager.dec_ref(m_else); + m_manager.dec_ref(m_interp); +} + +func_interp * func_interp::copy() const { + func_interp * new_fi = alloc(func_interp, m_manager, m_arity); + + ptr_vector::const_iterator it = m_entries.begin(); + ptr_vector::const_iterator end = m_entries.end(); + for (; it != end; ++it) { + func_entry * curr = *it; + new_fi->insert_new_entry(curr->get_args(), curr->get_result()); + } + new_fi->set_else(m_else); + return new_fi; +} + +void func_interp::reset_interp_cache() { + m_manager.dec_ref(m_interp); + m_interp = 0; +} + +void func_interp::set_else(expr * e) { + reset_interp_cache(); + m_manager.inc_ref(e); + m_manager.dec_ref(m_else); + m_else = e; +} + +/** + \brief Return true if the interpretation represents the constant function. +*/ +bool func_interp::is_constant() const { + if (is_partial()) + return false; + if (!is_ground(m_else)) + return false; + ptr_vector::const_iterator it = m_entries.begin(); + ptr_vector::const_iterator end = m_entries.end(); + for (; it != end; ++it) { + func_entry * curr = *it; + if (curr->get_result() != m_else) + return false; + } + return true; +} + +/** + \brief Return a func_entry e such that e.m_args[i] == args[i] for all i in [0, m_arity). + If such entry does not exist then return 0, and store set + args_are_values to true if for all entries e e.args_are_values() is true. +*/ +func_entry * func_interp::get_entry(expr * const * args) const { + ptr_vector::const_iterator it = m_entries.begin(); + ptr_vector::const_iterator end = m_entries.end(); + for (; it != end; ++it) { + func_entry * curr = *it; + if (curr->eq_args(m_arity, args)) + return curr; + } + return 0; +} + +void func_interp::insert_entry(expr * const * args, expr * r) { + reset_interp_cache(); + func_entry * entry = get_entry(args); + if (entry != 0) { + entry->set_result(m_manager, r); + return; + } + insert_new_entry(args, r); +} + +void func_interp::insert_new_entry(expr * const * args, expr * r) { + reset_interp_cache(); + CTRACE("func_interp_bug", get_entry(args) != 0, + for (unsigned i = 0; i < m_arity; i++) { + tout << mk_ismt2_pp(args[i], m_manager) << "\n"; + } + tout << "Old: " << mk_ismt2_pp(get_entry(args)->m_result, m_manager) << "\n"; + tout << "New: " << mk_ismt2_pp(r, m_manager) << "\n";); + SASSERT(get_entry(args) == 0); + func_entry * new_entry = func_entry::mk(m_manager, m_arity, args, r); + if (!new_entry->args_are_values()) + m_args_are_values = false; + m_entries.push_back(new_entry); +} + +bool func_interp::eval_else(expr * const * args, expr_ref & result) const { + if (m_else == 0) + return false; + var_subst s(m_manager, false); + SASSERT(!s.std_order()); // (VAR 0) <- args[0], (VAR 1) <- args[1], ... + s(m_else, m_arity, args, result); + return true; +} + +/** + \brief Store in r the result of applying args to this function. + Return true in case of success. + The function may fail if m_else == 0. +*/ +bool func_interp::eval(simplifier & s, expr * const * args, expr_ref & result) { + bool actuals_are_values = true; + + if (!m_entries.empty()) { + for (unsigned i = 0; actuals_are_values && i < m_arity; i++) { + actuals_are_values = m_manager.is_value(args[i]); + } + } + + func_entry * entry = get_entry(args); + if (entry != 0) { + result = entry->get_result(); + TRACE("func_interp", tout << "found entry for: "; + for(unsigned i = 0; i < m_arity; i++) + tout << mk_pp(args[i], m_manager) << " "; + tout << "\nresult: " << mk_pp(result, m_manager) << "\n";); + return true; + } + + TRACE("func_interp", tout << "failed to find entry for: "; + for(unsigned i = 0; i < m_arity; i++) + tout << mk_pp(args[i], m_manager) << " "; + tout << "\nis partial: " << is_partial() << "\n";); + + if (!eval_else(args, result)) { + TRACE("func_interp", tout << "function is partial, failed to evaluate\n";); + return false; + } + + if (actuals_are_values && m_args_are_values) { + // cheap case... we are done + return true; + } + + + // build symbolic result... the actuals may be equal to the args of one of the entries. + basic_simplifier_plugin * bs = static_cast(s.get_plugin(m_manager.get_basic_family_id())); + ptr_vector::iterator it = m_entries.begin(); + ptr_vector::iterator end = m_entries.end(); + for (; it != end; ++it) { + func_entry * curr = *it; + SASSERT(!curr->eq_args(m_arity, args)); + if (!actuals_are_values || !curr->args_are_values()) { + expr_ref_buffer eqs(m_manager); + unsigned i = m_arity; + while (i > 0) { + --i; + expr_ref new_eq(m_manager); + bs->mk_eq(curr->get_arg(i), args[i], new_eq); + eqs.push_back(new_eq); + } + SASSERT(eqs.size() == m_arity); + expr_ref new_cond(m_manager); + bs->mk_and(eqs.size(), eqs.c_ptr(), new_cond); + bs->mk_ite(new_cond, curr->get_result(), result, result); + } + } + return true; +} + +/** + \brief Return the result with the maximal number of occurrencies in m_entries. +*/ +expr * func_interp::get_max_occ_result() const { + if (m_entries.empty()) + return 0; + obj_map num_occs; + expr * r_max = 0; + unsigned max = 0; + ptr_vector::const_iterator it = m_entries.begin(); + ptr_vector::const_iterator end = m_entries.end(); + for (; it != end; ++it) { + func_entry * curr = *it; + expr * r = curr->get_result(); + unsigned occs = 0; + num_occs.find(r, occs); + occs++; + num_occs.insert(r, occs); + if (occs > max) { + max = occs; + r_max = r; + } + } + return r_max; +} + +/** + \brief Remove entries e such that e.get_result() == m_else. +*/ +void func_interp::compress() { + if (m_else == 0 || m_entries.empty()) + return; // nothing to be done + if (!is_ground(m_else)) + return; // forall entries e in m_entries e.get_result() is ground + unsigned i = 0; + unsigned j = 0; + unsigned sz = m_entries.size(); + m_args_are_values = true; + for (; i < sz; i++) { + func_entry * curr = m_entries[i]; + if (curr->get_result() != m_else) { + m_entries[j] = curr; + j++; + if (!curr->args_are_values()) + m_args_are_values = false; + } + else { + curr->deallocate(m_manager, m_arity); + } + } + if (j < sz) { + reset_interp_cache(); + m_entries.shrink(j); + } +} + +expr * func_interp::get_interp_core() const { + if (m_else == 0) + return 0; + expr * r = m_else; + ptr_buffer vars; + ptr_vector::const_iterator it = m_entries.begin(); + ptr_vector::const_iterator end = m_entries.end(); + for (; it != end; ++it) { + func_entry * curr = *it; + if (vars.empty()) { + for (unsigned i = 0; i < m_arity; i++) { + vars.push_back(m_manager.mk_var(i, m_manager.get_sort(curr->get_arg(i)))); + } + } + ptr_buffer eqs; + for (unsigned i = 0; i < m_arity; i++) { + eqs.push_back(m_manager.mk_eq(vars[i], curr->get_arg(i))); + } + SASSERT(eqs.size() == m_arity); + expr * cond; + if (m_arity == 1) + cond = eqs.get(0); + else + cond = m_manager.mk_and(eqs.size(), eqs.c_ptr()); + r = m_manager.mk_ite(cond, curr->get_result(), r); + } + return r; +} + +expr * func_interp::get_interp() const { + if (m_interp != 0) + return m_interp; + expr * r = get_interp_core(); + if (r != 0) { + const_cast(this)->m_interp = r; + m_manager.inc_ref(m_interp); + } + return r; +} + +func_interp * func_interp::translate(ast_translation & translator) const { + func_interp * new_fi = alloc(func_interp, m_manager, m_arity); + + ptr_vector::const_iterator it = m_entries.begin(); + ptr_vector::const_iterator end = m_entries.end(); + for (; it != end; ++it) { + func_entry * curr = *it; + ptr_buffer new_args; + for (unsigned i=0; iget_arg(i))); + new_fi->insert_new_entry(new_args.c_ptr(), translator(curr->get_result())); + } + new_fi->set_else(translator(m_else)); + return new_fi; +} diff --git a/lib/func_interp.h b/lib/func_interp.h new file mode 100644 index 000000000..73244efc1 --- /dev/null +++ b/lib/func_interp.h @@ -0,0 +1,114 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + func_interp.h + +Abstract: + + Support for function graphs (aka interpretations for functions). + They are used during model construction, and for evaluating expressions + modulo a model. + + Main goal: Remove some code duplication and make the evaluator more efficient. + + Example of code duplication that existed in Z3: + - smt_model_generator was creating func_values that were essentially partial func_interps + - smt_model_generator was creating if-then-else (lambda) exprs representing func_values + - the model object was converting these lambdas back into func_graphs (a limited version of func_interps). + - the smt_model_finder needs to manipulate func_interps, but the func_values in smt_model_generator + were private and too restrictive. + +Author: + + Leonardo de Moura (leonardo) 2010-12-30. + +Revision History: + +--*/ +#ifndef _FUNC_INTERP_H +#define _FUNC_INTERP_H + +#include"ast.h" +#include"ast_translation.h" + +class func_interp; +class simplifier; + +class func_entry { + bool m_args_are_values; //!< true if is_value(m_args[i]) is true for all i in [0, arity) + + // m_result and m_args[i] must be ground terms. + + expr * m_result; + expr * m_args[]; + + static unsigned get_obj_size(unsigned arity) { return sizeof(func_entry) + arity * sizeof(expr*); } + func_entry(ast_manager & m, unsigned arity, expr * const * args, expr * result); + + friend class func_interp; + + void set_result(ast_manager & m, expr * r); + bool args_are_values() const { return m_args_are_values; } + +public: + static func_entry * mk(ast_manager & m, unsigned arity, expr * const * args, expr * result); + void deallocate(ast_manager & m, unsigned arity); + expr * get_result() const { return m_result; } + expr * get_arg(unsigned idx) const { return m_args[idx]; } + expr * const * get_args() const { return m_args; } + /** + \brief Return true if m_args[i] == args[i] for all i in [0, arity) + */ + bool eq_args(unsigned arity, expr * const * args) const; +}; + +class func_interp { + ast_manager & m_manager; + unsigned m_arity; + ptr_vector m_entries; + expr * m_else; + bool m_args_are_values; //!< true if forall e in m_entries e.args_are_values() == true + + expr * m_interp; //!< cache for representing the whole interpretation as a single expression (it uses ite terms). + + void reset_interp_cache(); + + expr * get_interp_core() const; + +public: + func_interp(ast_manager & m, unsigned arity); + ~func_interp(); + + func_interp * copy() const; + + unsigned get_arity() const { return m_arity; } + + bool is_partial() const { return m_else == 0; } + // A function interpretation is said to be simple if m_else is ground. + bool is_simple() const { return is_partial() || is_ground(m_else); } + + bool is_constant() const; + + expr * get_else() const { return m_else; } + void set_else(expr * e); + + void insert_entry(expr * const * args, expr * r); + void insert_new_entry(expr * const * args, expr * r); + func_entry * get_entry(expr * const * args) const; + bool eval_else(expr * const * args, expr_ref & result) const; + bool eval(simplifier & s, expr * const * args, expr_ref & result); + unsigned num_entries() const { return m_entries.size(); } + func_entry const * const * get_entries() const { return m_entries.c_ptr(); } + func_entry const * get_entry(unsigned idx) const { return m_entries[idx]; } + + expr * get_max_occ_result() const; + void compress(); + + expr * get_interp() const; + + func_interp * translate(ast_translation & translator) const; +}; + +#endif diff --git a/lib/fvi.h b/lib/fvi.h new file mode 100644 index 000000000..fffec2ce3 --- /dev/null +++ b/lib/fvi.h @@ -0,0 +1,214 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + fvi.h + +Abstract: + + Feature Vector Indexing. + +Author: + + Leonardo de Moura (leonardo) 2008-02-01. + +Revision History: + +--*/ +#ifndef _FVI_H_ +#define _FVI_H_ + +#include"splay_tree_map.h" +#include"hashtable.h" +#include"vector.h" + +/** + \brief A feature vector indexing for objects of type T *. + + ToVector is a functor for converting T into a vector of natural numbers. + It should provide a method: + - void operator(T * d, unsigned * f); + This method should fill the vector f with the features of d. + + Hash: functor for computing the hashcode of T *. + + Eq : functor for comparing T pointers. +*/ +template > +class fvi : private ToVector { +public: + struct statistics { + unsigned m_size; + unsigned m_num_nodes; + unsigned m_num_leaves; + unsigned m_min_leaf_size; + unsigned m_avg_leaf_size; + unsigned m_max_leaf_size; + statistics() { reset(); } + + void reset() { + m_size = m_num_nodes = m_num_leaves = m_avg_leaf_size = m_max_leaf_size = 0; + m_min_leaf_size = UINT_MAX; + } + }; + +private: + struct ucompare { + int operator()(unsigned i1, unsigned i2) const { + if (i1 < i2) return -1; + if (i1 > i2) return 1; + return 0; + } + }; + + struct node { + node() {} + virtual ~node() {} + virtual bool is_leaf() const = 0; + }; + + typedef splay_tree_map children; + + struct non_leaf : public node { + children m_children; + non_leaf() {} + + struct delete_children { + void operator()(unsigned k, node * n) const { + dealloc(n); + } + }; + + virtual ~non_leaf() { + delete_children visitor; + m_children.visit(visitor); + m_children.reset(); + } + + virtual bool is_leaf() const { return false; } + }; + + typedef ptr_hashtable set; + + struct leaf : public node { + set m_set; + leaf() {} + virtual ~leaf() {} + virtual bool is_leaf() const { return true; } + }; + + unsigned m_num_features; + svector m_tmp_buffer; + non_leaf * m_root; + + struct stop {}; + + template + void visit_leaf(leaf * n, Visitor & v, bool le) const { + typename set::iterator it = n->m_set.begin(); + typename set::iterator end = n->m_set.end(); + for (; it != end; ++it) + if (!v(*it)) + throw stop(); + } + + template + struct non_leaf_visitor { + fvi const & m_owner; + unsigned m_fidx; + Visitor & m_visitor; + bool m_le; + + non_leaf_visitor(fvi const & o, unsigned fidx, Visitor & v, bool le): + m_owner(o), m_fidx(fidx), m_visitor(v), m_le(le) {} + + void operator()(unsigned k, node * n) { + if (n->is_leaf()) + m_owner.visit_leaf(static_cast(n), m_visitor, m_le); + else + m_owner.visit_non_leaf(static_cast(n), m_fidx + 1, m_visitor, m_le); + } + }; + + template + void visit_non_leaf(non_leaf * n, unsigned fidx, Visitor & v, bool le) const { + // Remark: this function is recursive, but there is no risk + // of stack overflow since the number of features is small. + non_leaf_visitor v2(*this, fidx, v, le); + if (le) + n->m_children.visit_le(v2, m_tmp_buffer[fidx]); + else + n->m_children.visit_ge(v2, m_tmp_buffer[fidx]); + } + +#ifdef Z3DEBUG + bool m_visiting; +#endif + + void to_fvector(T * d) const { + fvi * _this = const_cast(this); + _this->ToVector::operator()(d, _this->m_tmp_buffer.c_ptr()); + } + + struct non_leaf_stat_visitor { + fvi const & m_owner; + statistics & m_stats; + non_leaf_stat_visitor(fvi const & o, statistics & st):m_owner(o), m_stats(st) {} + void operator()(unsigned k, node * n); + }; + + void stats(leaf * n, statistics & result) const; + void stats(non_leaf * n, statistics & result) const; + + struct non_leaf_collect_visitor { + fvi const & m_owner; + ptr_vector & m_elems; + non_leaf_collect_visitor(fvi const & o, ptr_vector & elems):m_owner(o), m_elems(elems) {} + void operator()(unsigned k, node * n); + }; + + void collect(leaf * n, ptr_vector & result) const; + void collect(non_leaf * n, ptr_vector & result) const; + +public: + fvi(unsigned num_features, ToVector const & t = ToVector()); + ~fvi() { reset(); dealloc(m_root); } + + void insert(T * d); + bool contains(T * d) const; + void erase(T * d); + void reset(); + + /** + \brief Traverse the elements that have features smaller (greater) or equal than the one of the given element. + + For each visited element the following method of v is executed: + + - bool operator()(T * d) + + If false is returned, the traversal is aborted. + + \warning The object cannot be updated during the traversal. + */ + template + void visit(T * d, Visitor & v, bool le = true) const { + DEBUG_CODE(const_cast(this)->m_visiting = true;); + to_fvector(d); + try { + visit_non_leaf(m_root, 0, v, le); + } + catch (stop) { + } + DEBUG_CODE(const_cast(this)->m_visiting = false;); + } + + void stats(statistics & result) const; + + /** + \brief Copy to result the set of elements stored in the index. + */ + void collect(ptr_vector & result) const; +}; + +#endif /* _FVI_H_ */ diff --git a/lib/fvi_def.h b/lib/fvi_def.h new file mode 100644 index 000000000..8fcbda89b --- /dev/null +++ b/lib/fvi_def.h @@ -0,0 +1,199 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + fvi_def.h + +Abstract: + + Feature Vector Indexing. + +Author: + + Leonardo de Moura (leonardo) 2008-02-01. + +Revision History: + +--*/ +#ifndef _FVI_DEF_H_ +#define _FVI_DEF_H_ + +#include"fvi.h" +#include"splay_tree_def.h" +#include"buffer.h" + +template +fvi::fvi(unsigned num_features, ToVector const & t): + ToVector(t), + m_num_features(num_features), + m_root(0) { + m_tmp_buffer.resize(num_features, 0); + m_root = alloc(non_leaf); + SASSERT(num_features >= 2); + DEBUG_CODE(m_visiting = false;); +} + +template +void fvi::reset() { + SASSERT(!m_visiting); + dealloc(m_root); + m_root = alloc(non_leaf); +} + +template +void fvi::insert(T * d) { + SASSERT(!m_visiting); + to_fvector(d); + non_leaf * n = m_root; + unsigned i = 0; + for (; i < m_num_features - 1; i++) { + node * child = 0; + if (!n->m_children.find(m_tmp_buffer[i], child)) { + child = alloc(non_leaf); + n->m_children.insert(m_tmp_buffer[i], child); + } + SASSERT(child); + SASSERT(!child->is_leaf()); + n = static_cast(child); + } + node * l = 0; + SASSERT(i == m_num_features - 1); + if (!n->m_children.find(m_tmp_buffer[i], l)) { + l = alloc(leaf); + n->m_children.insert(m_tmp_buffer[i], l); + } + SASSERT(l); + SASSERT(l->is_leaf()); + static_cast(l)->m_set.insert(d); +} + +template +bool fvi::contains(T * d) const { + to_fvector(d); + non_leaf * n = m_root; + node * child; + unsigned i = 0; + for (; i < m_num_features - 1; i++) { + if (!n->m_children.find(m_tmp_buffer[i], child)) + return false; + SASSERT(child); + SASSERT(!child->is_leaf()); + n = static_cast(child); + } + SASSERT(i == m_num_features - 1); + return + n->m_children.find(m_tmp_buffer[i], child) && + static_cast(child)->m_set.contains(d); +} + +template +void fvi::erase(T * d) { + SASSERT(!m_visiting); + SASSERT(contains(d)); + ptr_buffer path; + to_fvector(d); + non_leaf * n = m_root; + node * child; + unsigned i = 0; + for (; i < m_num_features - 1; i++) { + path.push_back(n); + if (!n->m_children.find(m_tmp_buffer[i], child)) { + UNREACHABLE(); + } + SASSERT(child); + SASSERT(!child->is_leaf()); + n = static_cast(child); + } + path.push_back(n); + SASSERT(i == m_num_features - 1); + if (!n->m_children.find(m_tmp_buffer[i], child)) { + UNREACHABLE(); + } + SASSERT(child); + SASSERT(child->is_leaf()); + leaf * l = static_cast(child); + l->m_set.erase(d); + if (l->m_set.empty()) { + dealloc(l); + while (true) { + non_leaf * n = path.back(); + n->m_children.erase(m_tmp_buffer[i]); + path.pop_back(); + i--; + if (!n->m_children.empty() || n == m_root) + break; + dealloc(n); + } + } +} + +template +void fvi::non_leaf_stat_visitor::operator()(unsigned k, node * n) { + if (n->is_leaf()) + m_owner.stats(static_cast(n), m_stats); + else + m_owner.stats(static_cast(n), m_stats); +} + +template +void fvi::stats(leaf * n, statistics & result) const { + unsigned sz = n->m_set.size(); + result.m_size += sz; + if (sz > result.m_max_leaf_size) + result.m_max_leaf_size = sz; + if (sz < result.m_min_leaf_size) + result.m_min_leaf_size = sz; + result.m_num_leaves ++; + result.m_num_nodes ++; +} + +template +void fvi::stats(non_leaf * n, statistics & result) const { + result.m_num_nodes++; + // Remark: this function is recursive, but there is no risk + // of stack overflow since the number of features is small. + non_leaf_stat_visitor v(*this, result); + n->m_children.visit(v); +} + +template +void fvi::stats(statistics & result) const { + result.reset(); + stats(m_root, result); + if (m_root->m_children.empty()) + result.m_min_leaf_size = 0; + if (result.m_num_leaves > 0) + result.m_avg_leaf_size = result.m_size / result.m_num_leaves; +} + +template +void fvi::non_leaf_collect_visitor::operator()(unsigned k, node * n) { + if (n->is_leaf()) + m_owner.collect(static_cast(n), m_elems); + else + m_owner.collect(static_cast(n), m_elems); +} + +template +void fvi::collect(leaf * n, ptr_vector & result) const { + typename set::iterator it = n->m_set.begin(); + typename set::iterator end = n->m_set.end(); + for (; it != end; ++it) + result.push_back(*it); +} + +template +void fvi::collect(non_leaf * n, ptr_vector & result) const { + // Remark: this function is recursive, but there is no risk + // of stack overflow since the number of features is small. + non_leaf_collect_visitor v(*this, result); + n->m_children.visit(v); +} + +template +void fvi::collect(ptr_vector & result) const { + collect(m_root, result); +} + +#endif /* _FVI_DEF_H_ */ diff --git a/lib/gaussian_elim.cpp b/lib/gaussian_elim.cpp new file mode 100644 index 000000000..a3aa667f3 --- /dev/null +++ b/lib/gaussian_elim.cpp @@ -0,0 +1,775 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + gaussian_elim.cpp + +Abstract: + + (extended) Gaussian elimination for assertion sets. + It also supports other theories besides arithmetic. + +Author: + + Leonardo (leonardo) 2011-04-29 + +Notes: + +--*/ +#include"gaussian_elim.h" +#include"ast.h" +#include"expr_replacer.h" +#include"model_converter.h" +#include"assertion_set.h" +#include"ast_smt2_pp.h" +#include"elim_var_model_converter.h" +#include"occurs.h" +#include"cooperate.h" +#include"assertion_set_util.h" + +struct gaussian_elim::imp { + ast_manager & m_manager; + expr_replacer * m_r; + bool m_r_owner; + arith_util m_a_util; + obj_map m_num_occs; + unsigned m_num_steps; + unsigned m_num_eliminated_vars; + bool m_produce_models; + bool m_theory_solver; + bool m_ite_solver; + unsigned m_max_occs; + + void updt_params(params_ref const & p) { + m_produce_models = p.get_bool(":produce-models", false); + m_ite_solver = p.get_bool(":ite-solver", true); + m_theory_solver = p.get_bool(":theory-solver", true); + m_max_occs = p.get_uint(":gaussian-max-occs", UINT_MAX); + } + + typedef elim_var_model_converter gmc; + + expr_substitution m_subst; + expr_substitution m_norm_subst; + expr_sparse_mark m_candidate_vars; + expr_sparse_mark m_candidate_set; + ptr_vector m_candidates; + ptr_vector m_vars; + ptr_vector m_ordered_vars; + volatile bool m_cancel; + + imp(ast_manager & m, params_ref const & p, expr_replacer * r, bool owner): + m_manager(m), + m_r(r), + m_r_owner(r == 0 || owner), + m_a_util(m), + m_num_steps(0), + m_num_eliminated_vars(0), + m_subst(m), + m_norm_subst(m), + m_cancel(false) { + updt_params(p); + if (m_r == 0) + m_r = mk_default_expr_replacer(m); + } + + ~imp() { + if (m_r_owner) + dealloc(m_r); + } + + ast_manager & m() const { return m_manager; } + + bool check_occs(expr * t) const { + if (m_max_occs == UINT_MAX) + return true; + unsigned num = 0; + m_num_occs.find(t, num); + TRACE("gaussian_check_occs", tout << mk_ismt2_pp(t, m_manager) << " num_occs: " << num << " max: " << m_max_occs << "\n";); + return num <= m_max_occs; + } + + // Use: (= x def) and (= def x) + bool trivial_solve(expr * lhs, expr * rhs, app_ref & var, expr_ref & def, proof_ref & pr) { + if (is_uninterp_const(lhs) && !m_candidate_vars.is_marked(lhs) && !occurs(lhs, rhs) && check_occs(lhs)) { + var = to_app(lhs); + def = rhs; + pr = 0; + return true; + } + else if (is_uninterp_const(rhs) && !m_candidate_vars.is_marked(rhs) && !occurs(rhs, lhs) && check_occs(rhs)) { + var = to_app(rhs); + def = lhs; + if (m_manager.proofs_enabled()) + pr = m().mk_commutativity(m().mk_eq(lhs, rhs)); + return true; + } + return false; + } + + // (ite c (= x t1) (= x t2)) --> (= x (ite c t1 t2)) + bool solve_ite_core(app * ite, expr * lhs1, expr * rhs1, expr * lhs2, expr * rhs2, app_ref & var, expr_ref & def, proof_ref & pr) { + if (lhs1 != lhs2) + return false; + if (!is_uninterp_const(lhs1) || m_candidate_vars.is_marked(lhs1)) + return false; + if (occurs(lhs1, ite->get_arg(0)) || occurs(lhs1, rhs1) || occurs(lhs1, rhs2)) + return false; + if (!check_occs(lhs1)) + return false; + var = to_app(lhs1); + def = m().mk_ite(ite->get_arg(0), rhs1, rhs2); + + if (m().proofs_enabled()) + pr = m().mk_rewrite(ite, m().mk_eq(var, def)); + return true; + } + + // (ite c (= x t1) (= x t2)) --> (= x (ite c t1 t2)) + bool solve_ite(app * ite, app_ref & var, expr_ref & def, proof_ref & pr) { + expr * t = ite->get_arg(1); + expr * e = ite->get_arg(2); + + if (!m().is_eq(t) || !m().is_eq(e)) + return false; + + expr * lhs1 = to_app(t)->get_arg(0); + expr * rhs1 = to_app(t)->get_arg(1); + expr * lhs2 = to_app(e)->get_arg(0); + expr * rhs2 = to_app(e)->get_arg(1); + + return + solve_ite_core(ite, lhs1, rhs1, lhs2, rhs2, var, def, pr) || + solve_ite_core(ite, rhs1, lhs1, lhs2, rhs2, var, def, pr) || + solve_ite_core(ite, lhs1, rhs1, rhs2, lhs2, var, def, pr) || + solve_ite_core(ite, rhs1, lhs1, rhs2, lhs2, var, def, pr); + } + + bool is_pos_literal(expr * n) { + return is_app(n) && to_app(n)->get_num_args() == 0 && to_app(n)->get_family_id() == null_family_id; + } + + bool is_neg_literal(expr * n) { + if (m_manager.is_not(n)) + return is_pos_literal(to_app(n)->get_arg(0)); + return false; + } + +#if 0 + bool not_bool_eq(expr * f, app_ref & var, expr_ref & def, proof_ref & pr) { + if (!m().is_not(f)) + return false; + expr * eq = to_app(f)->get_arg(0); + if (!m().is_eq(f)) + return false; + + } +#endif + + /** + \brief Given t of the form (f s_0 ... s_n), + return true if x occurs in some s_j for j != i + */ + bool occurs_except(expr * x, app * t, unsigned i) { + unsigned num = t->get_num_args(); + for (unsigned j = 0; j < num; j++) { + if (i != j && occurs(x, t->get_arg(j))) + return true; + } + return false; + } + + bool solve_arith_core(app * lhs, expr * rhs, expr * eq, app_ref & var, expr_ref & def, proof_ref & pr) { + SASSERT(m_a_util.is_add(lhs)); + bool is_int = m_a_util.is_int(lhs); + expr * a; + expr * v; + rational a_val; + unsigned num = lhs->get_num_args(); + unsigned i; + for (i = 0; i < num; i++) { + expr * arg = lhs->get_arg(i); + if (is_uninterp_const(arg) && !m_candidate_vars.is_marked(arg) && check_occs(arg) && !occurs(arg, rhs) && !occurs_except(arg, lhs, i)) { + a_val = rational(1); + v = arg; + break; + } + else if (m_a_util.is_mul(arg, a, v) && + is_uninterp_const(v) && !m_candidate_vars.is_marked(v) && + m_a_util.is_numeral(a, a_val) && + !a_val.is_zero() && + (!is_int || a_val.is_minus_one()) && + check_occs(v) && + !occurs(v, rhs) && + !occurs_except(v, lhs, i)) { + break; + } + } + if (i == num) + return false; + var = to_app(v); + expr_ref inv_a(m()); + if (!a_val.is_one()) { + inv_a = m_a_util.mk_numeral(rational(1)/a_val, is_int); + rhs = m_a_util.mk_mul(inv_a, rhs); + } + + ptr_buffer other_args; + for (unsigned j = 0; j < num; j++) { + if (i != j) { + if (inv_a) + other_args.push_back(m_a_util.mk_mul(inv_a, lhs->get_arg(j))); + else + other_args.push_back(lhs->get_arg(j)); + } + } + switch (other_args.size()) { + case 0: + def = rhs; + break; + case 1: + def = m_a_util.mk_sub(rhs, other_args[0]); + break; + default: + def = m_a_util.mk_sub(rhs, m_a_util.mk_add(other_args.size(), other_args.c_ptr())); + break; + } + if (m().proofs_enabled()) { + pr = m().mk_rewrite(eq, m().mk_eq(var, def)); + } + return true; + } + + bool solve_arith(expr * lhs, expr * rhs, expr * eq, app_ref & var, expr_ref & def, proof_ref & pr) { + return + (m_a_util.is_add(lhs) && solve_arith_core(to_app(lhs), rhs, eq, var, def, pr)) || + (m_a_util.is_add(rhs) && solve_arith_core(to_app(rhs), lhs, eq, var, def, pr)); + } + + bool solve(expr * f, app_ref & var, expr_ref & def, proof_ref & pr) { + if (m().is_eq(f)) { + if (trivial_solve(to_app(f)->get_arg(0), to_app(f)->get_arg(1), var, def, pr)) + return true; + if (m_theory_solver) { + expr * lhs = to_app(f)->get_arg(0); + expr * rhs = to_app(f)->get_arg(1); + if (solve_arith(lhs, rhs, f, var, def, pr)) + return true; + } + return false; + } + + if (m().is_iff(f)) + return trivial_solve(to_app(f)->get_arg(0), to_app(f)->get_arg(1), var, def, pr); + +#if 0 + if (not_bool_eq(f, var, def, pr)) + return true; +#endif + + if (m_ite_solver && m().is_ite(f)) + return solve_ite(to_app(f), var, def, pr); + + if (is_pos_literal(f)) { + if (m_candidate_vars.is_marked(f)) + return false; + var = to_app(f); + def = m().mk_true(); + if (m().proofs_enabled()) { + // [rewrite]: (iff (iff l true) l) + // [symmetry T1]: (iff l (iff l true)) + pr = m().mk_rewrite(m().mk_eq(var, def), var); + pr = m().mk_symmetry(pr); + } + TRACE("gaussian_elim_bug2", tout << "eliminating: " << mk_ismt2_pp(f, m()) << "\n";); + return true; + } + + if (is_neg_literal(f)) { + var = to_app(to_app(f)->get_arg(0)); + if (m_candidate_vars.is_marked(var)) + return false; + def = m().mk_false(); + if (m().proofs_enabled()) { + // [rewrite]: (iff (iff l false) ~l) + // [symmetry T1]: (iff ~l (iff l false)) + pr = m().mk_rewrite(m().mk_eq(var, def), f); + pr = m().mk_symmetry(pr); + } + return true; + } + + return false; + } + + void checkpoint() { + if (m_cancel) + throw gaussian_elim_exception(STE_CANCELED_MSG); + cooperate("gaussian elimination"); + } + + /** + \brief Start collecting candidates + */ + void collect(assertion_set & set) { + m_subst.reset(); + m_norm_subst.reset(); + m_r->set_substitution(0); + m_candidate_vars.reset(); + m_candidate_set.reset(); + m_candidates.reset(); + m_vars.reset(); + + app_ref var(m()); + expr_ref def(m()); + proof_ref pr(m()); + unsigned size = set.size(); + for (unsigned idx = 0; idx < size; idx++) { + checkpoint(); + expr * f = set.form(idx); + if (solve(f, var, def, pr)) { + m_vars.push_back(var); + m_candidates.push_back(f); + m_candidate_set.mark(f); + m_candidate_vars.mark(var); + if (m().proofs_enabled()) { + if (pr == 0) + pr = set.pr(idx); + else + pr = m().mk_modus_ponens(set.pr(idx), pr); + } + m_subst.insert(var, def, pr); + } + m_num_steps++; + } + + TRACE("gaussian_elim", + tout << "candidate vars:\n"; + ptr_vector::iterator it = m_vars.begin(); + ptr_vector::iterator end = m_vars.end(); + for (; it != end; ++it) { + tout << mk_ismt2_pp(*it, m()) << " "; + } + tout << "\n";); + } + + + void sort_vars() { + SASSERT(m_candidates.size() == m_vars.size()); + TRACE("gaussian_elim_bug", tout << "sorting vars...\n";); + m_ordered_vars.reset(); + + + // The variables (and its definitions) in m_subst must remain alive until the end of this procedure. + // Reason: they are scheduled for unmarking in visiting/done. + // They should remain alive while they are on the stack. + // To make sure this is the case, whenever a variable (and its definition) is removed from m_subst, + // I add them to the saved vector. + + expr_ref_vector saved(m()); + + expr_fast_mark1 visiting; + expr_fast_mark2 done; + + typedef std::pair frame; + svector todo; + ptr_vector::const_iterator it = m_vars.begin(); + ptr_vector::const_iterator end = m_vars.end(); + unsigned num; + for (; it != end; ++it) { + checkpoint(); + app * v = *it; + if (!m_candidate_vars.is_marked(v)) + continue; + todo.push_back(frame(v, 0)); + while (!todo.empty()) { + start: + frame & fr = todo.back(); + expr * t = fr.first; + m_num_steps++; + TRACE("gaussian_elim_bug", tout << "processing:\n" << mk_ismt2_pp(t, m()) << "\n";); + if (t->get_ref_count() > 1 && done.is_marked(t)) { + todo.pop_back(); + continue; + } + switch (t->get_kind()) { + case AST_VAR: + todo.pop_back(); + break; + case AST_QUANTIFIER: + num = to_quantifier(t)->get_num_children(); + while (fr.second < num) { + expr * c = to_quantifier(t)->get_child(fr.second); + fr.second++; + if (c->get_ref_count() > 1 && done.is_marked(c)) + continue; + todo.push_back(frame(c, 0)); + goto start; + } + if (t->get_ref_count() > 1) + done.mark(t); + todo.pop_back(); + break; + case AST_APP: + num = to_app(t)->get_num_args(); + if (num == 0) { + if (fr.second == 0) { + if (m_candidate_vars.is_marked(t)) { + if (visiting.is_marked(t)) { + // cycle detected: remove t + visiting.reset_mark(t); + m_candidate_vars.mark(t, false); + SASSERT(!m_candidate_vars.is_marked(t)); + + // Must save t and its definition. + // See comment in the beginning of the function + expr * def = 0; + proof * pr; + m_subst.find(to_app(t), def, pr); + SASSERT(def != 0); + saved.push_back(t); + saved.push_back(def); + // + + m_subst.erase(t); + } + else { + visiting.mark(t); + fr.second = 1; + expr * def = 0; + proof * pr; + m_subst.find(to_app(t), def, pr); + SASSERT(def != 0); + todo.push_back(frame(def, 0)); + goto start; + } + } + } + else { + SASSERT(fr.second == 1); + if (m_candidate_vars.is_marked(t)) { + visiting.reset_mark(t); + m_ordered_vars.push_back(to_app(t)); + } + else { + // var was removed from the list of candidate vars to elim cycle + // do nothing + } + } + } + else { + while (fr.second < num) { + expr * arg = to_app(t)->get_arg(fr.second); + fr.second++; + if (arg->get_ref_count() > 1 && done.is_marked(arg)) + continue; + todo.push_back(frame(arg, 0)); + goto start; + } + } + if (t->get_ref_count() > 1) + done.mark(t); + todo.pop_back(); + break; + default: + UNREACHABLE(); + todo.pop_back(); + break; + } + } + } + + // cleanup + it = m_vars.begin(); + for (unsigned idx = 0; it != end; ++it, ++idx) { + if (!m_candidate_vars.is_marked(*it)) { + m_candidate_set.mark(m_candidates[idx], false); + } + } + + TRACE("gaussian_elim", + tout << "ordered vars:\n"; + ptr_vector::iterator it = m_ordered_vars.begin(); + ptr_vector::iterator end = m_ordered_vars.end(); + for (; it != end; ++it) { + SASSERT(m_candidate_vars.is_marked(*it)); + tout << mk_ismt2_pp(*it, m()) << " "; + } + tout << "\n";); + m_candidate_vars.reset(); + } + + void normalize() { + m_norm_subst.reset(); + m_r->set_substitution(&m_norm_subst); + + expr_ref new_def(m()); + proof_ref new_pr(m()); + unsigned size = m_ordered_vars.size(); + for (unsigned idx = 0; idx < size; idx++) { + checkpoint(); + expr * v = m_ordered_vars[idx]; + expr * def = 0; + proof * pr = 0; + m_subst.find(v, def, pr); + SASSERT(def != 0); + m_r->operator()(def, new_def, new_pr); + m_num_steps += m_r->get_num_steps() + 1; + if (m().proofs_enabled()) + new_pr = m().mk_transitivity(pr, new_pr); + m_norm_subst.insert(v, new_def, new_pr); + // we updated the substituting, but we don't need to reset m_r + // because all cached values there do not depend on v. + } + m_subst.reset(); + TRACE("gaussian_elim", + tout << "after normalizing variables\n"; + for (unsigned i = 0; i < m_ordered_vars.size(); i++) { + expr * v = m_ordered_vars[i]; + expr * def = 0; + proof * pr = 0; + m_norm_subst.find(v, def, pr); + tout << mk_ismt2_pp(v, m()) << "\n----->\n" << mk_ismt2_pp(def, m()) << "\n\n"; + }); +#if 0 + DEBUG_CODE({ + for (unsigned i = 0; i < m_ordered_vars.size(); i++) { + expr * v = m_ordered_vars[i]; + expr * def = 0; + proof * pr = 0; + m_norm_subst.find(v, def, pr); + SASSERT(def != 0); + CASSERT("gaussian_elim_bug", !occurs(v, def)); + } + }); +#endif + } + + void substitute(assertion_set & set) { + // force the cache of m_r to be reset. + m_r->set_substitution(&m_norm_subst); + + expr_ref new_f(m()); + proof_ref new_pr(m()); + unsigned size = set.size(); + for (unsigned idx = 0; idx < size; idx++) { + checkpoint(); + expr * f = set.form(idx); + TRACE("gaussian_leak", tout << "processing:\n" << mk_ismt2_pp(f, m()) << "\n";); + if (m_candidate_set.is_marked(f)) { + // f may be deleted after the following update. + // so, we must remove remove the mark before doing the update + m_candidate_set.mark(f, false); + SASSERT(!m_candidate_set.is_marked(f)); + set.update(idx, m().mk_true(), m().mk_true_proof()); + m_num_steps ++; + continue; + } + else { + m_r->operator()(f, new_f, new_pr); + } + TRACE("gaussian_elim_subst", tout << mk_ismt2_pp(f, m()) << "\n--->\n" << mk_ismt2_pp(new_f, m()) << "\n";); + m_num_steps += m_r->get_num_steps() + 1; + if (m().proofs_enabled()) { + new_pr = m().mk_modus_ponens(set.pr(idx), new_pr); + } + set.update(idx, new_f, new_pr); + if (set.inconsistent()) + return; + } + set.elim_true(); + TRACE("gaussian_elim", + tout << "after applying substitution\n"; + set.display(tout);); +#if 0 + DEBUG_CODE({ + for (unsigned i = 0; i < m_ordered_vars.size(); i++) { + expr * v = m_ordered_vars[i]; + for (unsigned j = 0; j < set.size(); j++) { + CASSERT("gaussian_elim_bug", !occurs(v, set.form(j))); + } + }}); +#endif + } + + void save_elim_vars(model_converter_ref & mc) { + IF_VERBOSE(100, if (!m_ordered_vars.empty()) verbose_stream() << "num. eliminated vars: " << m_ordered_vars.size() << "\n";); + m_num_eliminated_vars += m_ordered_vars.size(); + if (m_produce_models) { + if (mc.get() == 0) + mc = alloc(gmc, m()); + ptr_vector::iterator it = m_ordered_vars.begin(); + ptr_vector::iterator end = m_ordered_vars.end(); + for (; it != end; ++it) { + app * v = *it; + expr * def = 0; + proof * pr; + m_norm_subst.find(v, def, pr); + SASSERT(def != 0); + static_cast(mc.get())->insert(v->get_decl(), def); + } + } + } + + + void collect_num_occs(expr * t, expr_fast_mark1 & visited) { + ptr_buffer stack; + +#define VISIT(ARG) { \ + if (is_uninterp_const(ARG)) { \ + obj_map::obj_map_entry * entry = m_num_occs.insert_if_not_there2(ARG, 0); \ + entry->get_data().m_value++; \ + } \ + if (!visited.is_marked(ARG)) { \ + visited.mark(ARG, true); \ + stack.push_back(ARG); \ + } \ + } + + VISIT(t); + + while (!stack.empty()) { + expr * t = stack.back(); + stack.pop_back(); + if (!is_app(t)) + continue; + unsigned j = to_app(t)->get_num_args(); + while (j > 0) { + --j; + expr * arg = to_app(t)->get_arg(j); + VISIT(arg); + } + } + } + + void collect_num_occs(assertion_set & s) { + if (m_max_occs == UINT_MAX) + return; // no need to compute num occs + m_num_occs.reset(); + expr_fast_mark1 visited; + unsigned sz = s.size(); + for (unsigned i = 0; i < sz; i++) + collect_num_occs(s.form(i), visited); + } + + void operator()(assertion_set & s, model_converter_ref & mc) { + SASSERT(is_well_sorted(s)); + as_st_report report("gaussian-elimination", s); + TRACE("gaussian_elim", tout << "starting guassian elimination\n"; s.display(tout); tout << "\n";); + m_num_steps = 0; + mc = 0; + if (s.inconsistent()) + return; + + while (true) { + collect_num_occs(s); + collect(s); + if (m_subst.empty()) + break; + sort_vars(); + if (m_ordered_vars.empty()) + break; + normalize(); + substitute(s); + if (s.inconsistent()) { + mc = 0; + break; + } + save_elim_vars(mc); + TRACE("gaussian_elim_round", s.display(tout); if (mc) mc->display(tout);); + } + TRACE("gaussian_elim", s.display(tout);); + SASSERT(is_well_sorted(s)); + } + + void set_cancel(bool f) { + m_cancel = f; + m_r->set_cancel(f); + } + + unsigned get_num_steps() const { + return m_num_steps; + } + + unsigned get_num_eliminated_vars() const { + return m_num_eliminated_vars; + } +}; + +gaussian_elim::gaussian_elim(ast_manager & m, params_ref const & p, expr_replacer * r, bool owner): + m_params(p) { + m_imp = alloc(imp, m, p, r, owner); +} + +gaussian_elim::~gaussian_elim() { + dealloc(m_imp); +} + +ast_manager & gaussian_elim::m() const { + return m_imp->m(); +} + +void gaussian_elim::updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); +} + +void gaussian_elim::get_param_descrs(param_descrs & r) { + insert_produce_models(r); + r.insert(":gaussian-max-occs", CPK_UINT, "(default: infty) maximum number of occurrences for considering a variable for gaussian eliminations."); + r.insert(":theory-solver", CPK_BOOL, "(default: true) use theory solvers."); + r.insert(":ite-solver", CPK_BOOL, "(default: true) use if-then-else solver."); +} + +void gaussian_elim::operator()(assertion_set & s, model_converter_ref & mc) { + m_imp->operator()(s, mc); + report_st_progress(":num-elim-vars", get_num_eliminated_vars()); +} + +void gaussian_elim::set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); +} + +void gaussian_elim::cleanup() { + unsigned num_elim_vars = m_imp->m_num_eliminated_vars; + ast_manager & m = m_imp->m(); + imp * d = m_imp; + expr_replacer * r = m_imp->m_r_owner ? m_imp->m_r : 0; + if (r) + r->set_substitution(0); + bool owner = m_imp->m_r_owner; + m_imp->m_r_owner = false; // stole replacer + #pragma omp critical (as_st_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m, m_params, r, owner); + #pragma omp critical (as_st_cancel) + { + m_imp = d; + } + m_imp->m_num_eliminated_vars = num_elim_vars; +} + +unsigned gaussian_elim::get_num_steps() const { + return m_imp->get_num_steps(); +} + +unsigned gaussian_elim::get_num_eliminated_vars() const { + return m_imp->get_num_eliminated_vars(); +} + +void gaussian_elim::collect_statistics(statistics & st) const { + st.update("eliminated vars", get_num_eliminated_vars()); +} + +void gaussian_elim::reset_statistics() { + m_imp->m_num_eliminated_vars = 0; +} + +as_st * mk_gaussian(ast_manager & m, params_ref const & p) { + return clean(alloc(gaussian_elim, m, p, mk_expr_simp_replacer(m, p), true)); +} diff --git a/lib/gaussian_elim.h b/lib/gaussian_elim.h new file mode 100644 index 000000000..994e9623a --- /dev/null +++ b/lib/gaussian_elim.h @@ -0,0 +1,66 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + gaussian_elim.h + +Abstract: + + (extended) Gaussian elimination for assertion sets. + It also supports other theories besides arithmetic. + +Author: + + Leonardo (leonardo) 2011-04-21 + +Notes: + +--*/ +#ifndef _GAUSSIAN_ELIM_H_ +#define _GAUSSIAN_ELIM_H_ + +#include"assertion_set_strategy.h" + +class expr_replacer; + +MK_ST_EXCEPTION(gaussian_elim_exception); + +class gaussian_elim : public assertion_set_strategy { + struct imp; + imp * m_imp; + params_ref m_params; +public: + gaussian_elim(ast_manager & m, params_ref const & p = params_ref(), expr_replacer * r = 0, bool owner = false); + virtual ~gaussian_elim(); + + ast_manager & m () const; + + virtual void updt_params(params_ref const & p); + static void get_param_descrs(param_descrs & r); + virtual void collect_param_descrs(param_descrs & r) { get_param_descrs(r); } + + /** + \brief Apply gaussian elimination on the assertion set \c s. + Return a model_converter that converts any model for the updated set into a model for the old set. + */ + virtual void operator()(assertion_set & s, model_converter_ref & mc); + + virtual void cleanup(); + + unsigned get_num_steps() const; + unsigned get_num_eliminated_vars() const; + + virtual void collect_statistics(statistics & st) const; + virtual void reset_statistics(); +protected: + virtual void set_cancel(bool f); +}; + +as_st * mk_gaussian(ast_manager & m, params_ref const & p = params_ref()); + +inline as_st * mk_eq_solver(ast_manager & m, params_ref const & p = params_ref()) { + return mk_gaussian(m, p); +} + +#endif diff --git a/lib/gl_tactic.cpp b/lib/gl_tactic.cpp new file mode 100644 index 000000000..e5d219b06 --- /dev/null +++ b/lib/gl_tactic.cpp @@ -0,0 +1,1047 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + gl_tactic.cpp + +Abstract: + + A T-function + and perhaps Goldreich Levin-based heuristic. + +Author: + + Nikolaj (nbjorner) 2011-12-18 + +Notes: + + This module experiments with T-function bit-vector operations. + + TBD: + 1. convert to tactic. + 2. implement using fewer y bits based on satisfying assignment. + 3. try out code-generation. + 4. better lemma learning? + 5. Incremental refinement into SAT. + + + +--*/ + +#include"gl_tactic.h" +#include"assertion_set_strategy.h" +#include"bv_decl_plugin.h" +#include"shallow_context_simplifier.h" +#include"elim_uncnstr_vars.h" +#include"max_bv_sharing.h" +#include"max_bool_sharing.h" +#include"bv_size_reduction.h" +#include"context_simplifier.h" +#include"nnf.h" +#include"cooperate.h" +#include"ast_smt2_pp.h" +#include"elim_var_model_converter.h" +#include"smt_context.h" +#include"ast_pp.h" + +using namespace gl; + + +typedef svector assignment; + +static void gl_unsupported(ast_manager& m, expr* e) { + TRACE("gl_st", if (e) tout << "unsupported: " << mk_ismt2_pp(e, m) << "\n";); + throw gl_exception(""); +} + +class expr_evaluator { + typedef unsigned char uint8; + typedef unsigned short uint16; + typedef unsigned uint32; + typedef signed char int8; + typedef signed short int16; + typedef signed int int32; + + enum code { + ADD, + SUB, + MUL, + ITE, + BAND, + BOR, + BXOR, + BNOT, + ULT, + SLT, + SHL, + ASHR, + LSHR, + LOADC, + LOADV, + EQ, + UREM, + SREM, + SMOD, + UDIV, + SDIV + }; + + struct oper { + code m_code; + unsigned dst; + unsigned src1; + unsigned src2; + unsigned src3; + uint64 data; + unsigned bw; + + static oper mk_binary(code c, unsigned bw, unsigned dst, unsigned src1, unsigned src2) { + return oper(c, bw, dst, src1, src2, 0, 0); + } + static oper mk_ite(unsigned bw, unsigned dst, unsigned src1, unsigned src2, unsigned src3) { + return oper(ITE, bw, dst, src1, src2, src3, 0); + } + static oper mk_loadc(unsigned bw, unsigned dst, uint64 data) { + return oper(LOADC, bw, dst, 0, 0, 0, data); + } + + private: + oper(code c, unsigned bw, unsigned dst, unsigned src1, unsigned src2, unsigned src3, uint64 data): + m_code(c), bw(bw), dst(dst), src1(src1), src2(src2), src3(src3), data(data) { } + }; + + ast_manager& m; + bv_util m_bv; + svector m_regs; + svector m_ops; + unsigned m_dst; + obj_map m_cache; + assignment const* m_vals_tmp; + obj_map* m_vars_tmp; + + unsigned mk_oper(code c, expr* e, unsigned src1, unsigned src2) { + return mk_oper(c, get_bitwidth(e), src1, src2); + } + + unsigned get_bitwidth(expr* e) { + unsigned bw = 0; + if (m.is_bool(e)) { + bw = 1; + } + else if (m_bv.is_bv(e)) { + bw= m_bv.get_bv_size(e); + if (bw > 64) { + unsupported(e); + } + } + else { + unsupported(e); + } + return bw; + } + + unsigned mk_oper(code c, unsigned bw, unsigned src1, unsigned src2) { + unsigned dst = m_regs.size(); + m_regs.push_back(0); + m_ops.push_back(oper::mk_binary(c, bw, dst, src1, src2)); + return dst; + } + + unsigned mk_const(expr* e, uint64 c) { + return mk_const(get_bitwidth(e), c); + } + + unsigned mk_const(unsigned bw, uint64 c) { + unsigned dst = m_regs.size(); + m_regs.push_back(0); + m_ops.push_back(oper::mk_loadc(bw, dst, c)); + return dst; + } + + + unsigned mk_ite(expr* e, unsigned src1, unsigned src2, unsigned src3) { + unsigned dst = m_regs.size(); + unsigned bw = get_bitwidth(e); + m_regs.push_back(0); + m_ops.push_back(oper::mk_ite(bw, dst, src1, src2, src3)); + return dst; + } + + unsigned mk_oper(code c, unsigned num_args, expr* const* args, uint64 def) { + if (num_args == 0) { + unsupported(0); + return mk_const(static_cast(0), def); + } + unsigned dst = compile(args[0]); + for (unsigned i = 1; i < num_args; ++i) { + dst = mk_oper(c, args[i], dst, compile(args[i])); + } + return dst; + } + + unsigned compile(app* a) { + unsigned src1 = 0, src2 = 0, src3 = 0, dst = 0; + uint64 src0 = 0; + unsigned num_args = a->get_num_args(); + expr* const* args = a->get_args(); + unsigned var_id = 0; + + if (num_args >= 1) { + src1 = compile(a->get_arg(0)); + } + if (num_args >= 2) { + src2 = compile(a->get_arg(1)); + } + if (num_args >= 3) { + src3 = compile(a->get_arg(2)); + } + if (m_bv.is_bv(a) && m_bv.get_bv_size(a) > 64) { + unsupported(a); + } + if (m_vars_tmp->find(a, var_id)) { + return mk_oper(LOADV, a, var_id, 0); + } + if (a->get_family_id() == m.get_basic_family_id()) { + switch(a->get_decl_kind()) { + case OP_AND: + return mk_oper(BAND, num_args, args, 1); + case OP_OR: + return mk_oper(BOR, num_args, args, 0); + case OP_NOT: + return mk_oper(BNOT, a, src1, 0); + case OP_EQ: + return mk_oper(EQ, a, src1, src2); + case OP_ITE: + return mk_ite(a, src1, src2, src3); + case OP_DISTINCT: + unsupported(a); + case OP_TRUE: + return mk_const(a, 1); + case OP_FALSE: + return mk_const(a, 0); + case OP_XOR: + return mk_oper(BNOT, a, mk_oper(EQ, a, src1, src2), 0); + case OP_IFF: + return mk_oper(EQ, a, src1, src2); // mask? + default: + UNREACHABLE(); + } + } + if (a->get_family_id() == m_bv.get_family_id()) { + unsigned bv_size; + rational val; + switch(a->get_decl_kind()) { + case OP_BV_NUM: + VERIFY(m_bv.is_numeral(a, val, bv_size)); + if (bv_size > 64) { + unsupported(a); + } + src0 = val.get_uint64(); + return mk_const(a, src0); + case OP_BIT1: + return mk_const(a, 1); + case OP_BIT0: + return mk_const(a, 0); + case OP_BNEG: + SASSERT(num_args == 1); + return mk_oper(SUB, a, 0, src1); + case OP_BADD: + return mk_oper(ADD, num_args, args, 0); + case OP_BSUB: + SASSERT(num_args == 2); + return mk_oper(SUB, a, src1, src2); + case OP_BMUL: + return mk_oper(MUL, num_args, args, 1); + case OP_BSDIV: + case OP_BSDIV0: + case OP_BSDIV_I: + return mk_oper(SDIV, a, src1, src2); + case OP_BUDIV: + case OP_BUDIV0: + case OP_BUDIV_I: + return mk_oper(UDIV, a, src1, src2); + case OP_BSREM: + case OP_BSREM0: + case OP_BSREM_I: + return mk_oper(SREM, a, src1, src2); + case OP_BUREM: + case OP_BUREM0: + case OP_BUREM_I: + return mk_oper(UREM, a, src1, src2); + case OP_BSMOD: + case OP_BSMOD0: + case OP_BSMOD_I: + return mk_oper(SMOD, a, src1, src2); + case OP_ULEQ: + return mk_oper(BOR, a, mk_oper(ULT, a, src1, src2), mk_oper(EQ, a, src1, src2)); + case OP_SLEQ: + return mk_oper(BOR, a, mk_oper(SLT, a, src1, src2), mk_oper(EQ, a, src1, src2)); + case OP_UGEQ: + return mk_oper(BOR, a, mk_oper(ULT, a, src2, src1), mk_oper(EQ, a, src1, src2)); + case OP_SGEQ: + return mk_oper(BOR, a, mk_oper(SLT, a, src2, src1), mk_oper(EQ, a, src1, src2)); + case OP_ULT: + return mk_oper(ULT, a, src1, src2); + case OP_SLT: + return mk_oper(SLT, a, src1, src2); + case OP_UGT: + return mk_oper(ULT, a, src2, src1); + case OP_SGT: + return mk_oper(SLT, a, src2, src1); + case OP_BAND: + return mk_oper(BAND, num_args, args, ~((uint64)0)); + case OP_BOR: + return mk_oper(BOR, num_args, args, 0); + case OP_BNOT: + return mk_oper(BNOT, a, src1, 0); + case OP_BXOR: + SASSERT(num_args == 2); + return mk_oper(BXOR, a, src1, src2); + case OP_BNAND: + return mk_oper(BNOT, a, mk_oper(BAND, num_args, args, ~((uint64)0)), 0); + case OP_BNOR: + return mk_oper(BNOT, a, mk_oper(BOR, num_args, args, 0), 0); + case OP_BXNOR: + SASSERT(num_args == 2); + return mk_oper(BNOT, a, mk_oper(BXOR, a, src1, src2), 0); + case OP_CONCAT: { + SASSERT(num_args > 0); + expr* last = args[num_args-1]; + src1 = compile(last); + unsigned offset = m_bv.get_bv_size(last); + for (unsigned i = num_args-1; i > 0; ) { + --i; + unsigned bv_size = m_bv.get_bv_size(args[i]); + uint64 mask = (1ull << offset)-1; + offset += bv_size; + src2 = compile(args[i]); + src2 = mk_oper(SHL, args[i], src2, offset); + src1 = mk_oper(BAND, offset, src1, mk_const(offset, mask)); + src1 = mk_oper(BOR, offset, src1, src2); + + } + return src1; + } + case OP_SIGN_EXT: + unsupported(a); + case OP_ZERO_EXT: + unsupported(a); + case OP_EXTRACT: { + unsigned lo = m_bv.get_extract_low(a); + unsigned hi = m_bv.get_extract_high(a); + if (lo > 0) { + dst = mk_oper(SHL, a, src1, lo); + } + else { + dst = src1; + } + return dst; + } + case OP_REPEAT: + case OP_BREDOR: + case OP_BREDAND: + case OP_BCOMP: + unsupported(a); + case OP_BSHL: + SASSERT(num_args == 2); + return mk_oper(SHL, a, src1, src2); + case OP_BLSHR: + SASSERT(num_args == 2); + return mk_oper(LSHR, a, src1, src2); + case OP_BASHR: + SASSERT(num_args == 2); + return mk_oper(ASHR, a, src1, src2); + case OP_ROTATE_LEFT: + case OP_ROTATE_RIGHT: + case OP_EXT_ROTATE_LEFT: + case OP_EXT_ROTATE_RIGHT: + case OP_BUMUL_NO_OVFL: // no unsigned multiplication overflow predicate + case OP_BSMUL_NO_OVFL: // no signed multiplication overflow predicate + case OP_BSMUL_NO_UDFL: // no signed multiplication underflow predicate + case OP_BIT2BOOL: // predicate + unsupported(a); + case OP_MKBV: // bools to bv + return src1; + case OP_INT2BV: + case OP_BV2INT: + case OP_CARRY: + case OP_XOR3: + unsupported(a); + } + } + unsupported(a); + UNREACHABLE(); + return 0; + } + + uint64 to_bool(bool b) { return b?0x1:0x0; } + + void interpret(oper oper) { + unsigned mask = 0; + switch(oper.m_code) { + case LOADC: + m_regs[oper.dst] = oper.data; + break; + case LOADV: + m_regs[oper.dst] = (*m_vals_tmp)[oper.src1]; + break; + case ADD: + m_regs[oper.dst] = m_regs[oper.src1] + m_regs[oper.src2]; + break; + case SUB: + switch(oper.bw) + { + case 8: m_regs[oper.dst] = (uint8)m_regs[oper.src1] - (uint8)m_regs[oper.src2]; break; + case 16: m_regs[oper.dst] = (uint16)m_regs[oper.src1] - (uint16)m_regs[oper.src2]; break; + case 32: m_regs[oper.dst] = (uint32)m_regs[oper.src1] - (uint32)m_regs[oper.src2]; break; + case 64: m_regs[oper.dst] = m_regs[oper.src1] - m_regs[oper.src2]; break; + default: unsupported(0); + } + break; + case MUL: + m_regs[oper.dst] = m_regs[oper.src1] * m_regs[oper.src2]; + break; + case SDIV: + switch(oper.bw) + { + case 8: m_regs[oper.dst] = (int8)m_regs[oper.src1] / (int8)m_regs[oper.src2]; break; + case 16: m_regs[oper.dst] = (int16)m_regs[oper.src1] / (int16)m_regs[oper.src2]; break; + case 32: m_regs[oper.dst] = (int32)m_regs[oper.src1] / (int32)m_regs[oper.src2]; break; + case 64: m_regs[oper.dst] = (int64)m_regs[oper.src1] / (int64)m_regs[oper.src2]; break; + default: unsupported(0); + } + break; + case UDIV: + switch(oper.bw) + { + case 8: m_regs[oper.dst] = (uint8)m_regs[oper.src1] / (uint8)m_regs[oper.src2]; break; + case 16: m_regs[oper.dst] = (uint16)m_regs[oper.src1] / (uint16)m_regs[oper.src2]; break; + case 32: m_regs[oper.dst] = (uint32)m_regs[oper.src1] / (uint32)m_regs[oper.src2]; break; + case 64: m_regs[oper.dst] = m_regs[oper.src1] / m_regs[oper.src2]; break; + default: unsupported(0); + } + break; + case SMOD: + case SREM: + case UREM: + unsupported(0); + case BAND: + m_regs[oper.dst] = m_regs[oper.src1] & m_regs[oper.src2]; + break; + case BOR: + m_regs[oper.dst] = m_regs[oper.src1] | m_regs[oper.src2]; + break; + case BXOR: + m_regs[oper.dst] = m_regs[oper.src1] ^ m_regs[oper.src2]; + break; + case BNOT: + m_regs[oper.dst] = ~m_regs[oper.src1]; + break; + case SHL: + mask = (1ull << oper.bw) - 1; + m_regs[oper.dst] = m_regs[oper.src1] << (mask & m_regs[oper.src2]); + break; + case LSHR: + m_regs[oper.dst] = m_regs[oper.src1] >> (mask & m_regs[oper.src2]); + break; + case ASHR: + switch(oper.bw) + { + case 8: m_regs[oper.dst] = (int8)m_regs[oper.src1] >> (int8)m_regs[oper.src2]; break; + case 16: m_regs[oper.dst] = (int16)m_regs[oper.src1] >> (int16)m_regs[oper.src2]; break; + case 32: m_regs[oper.dst] = (int32)m_regs[oper.src1] >> (int32)m_regs[oper.src2]; break; + case 64: m_regs[oper.dst] = (int64)m_regs[oper.src1] >> (int64)m_regs[oper.src2]; break; + default: unsupported(0); + } + break; + case ULT: + switch(oper.bw) + { + case 8: m_regs[oper.dst] = to_bool((uint8)m_regs[oper.src1] < (uint8)m_regs[oper.src2]); break; + case 16: m_regs[oper.dst] = to_bool((uint16)m_regs[oper.src1] < (uint16)m_regs[oper.src2]); break; + case 32: m_regs[oper.dst] = to_bool((uint32)m_regs[oper.src1] < (uint32)m_regs[oper.src2]); break; + case 64: m_regs[oper.dst] = to_bool(m_regs[oper.src1] < m_regs[oper.src2]); break; + default: unsupported(0); + } + break; + case SLT: + switch(oper.bw) + { + case 8: m_regs[oper.dst] = to_bool((int8)m_regs[oper.src1] < (int8)m_regs[oper.src2]); break; + case 16: m_regs[oper.dst] = to_bool((int16)m_regs[oper.src1] < (int16)m_regs[oper.src2]); break; + case 32: m_regs[oper.dst] = to_bool((int32)m_regs[oper.src1] < (int32)m_regs[oper.src2]); break; + case 64: m_regs[oper.dst] = to_bool((int64)m_regs[oper.src1] < (int64)m_regs[oper.src2]); break; + default: unsupported(0); + } + break; + case ITE: + m_regs[oper.dst] = (0x1&m_regs[oper.src1])?m_regs[oper.src2]:m_regs[oper.src3]; + break; + case EQ: + mask = (1ull << oper.bw) - 1; + m_regs[oper.dst] = to_bool((mask & m_regs[oper.src1]) == (mask & m_regs[oper.src2])); + break; + default: + UNREACHABLE(); + break; + } + } + + unsigned compile(expr* e) { + unsigned val; + TRACE("gl_st", tout << mk_ismt2_pp(e, m) << "\n";); + if (!is_app(e)) { + unsupported(e); + } + app* a = to_app(e); + if (!m_cache.find(a, val)) { + val = compile(a); + m_cache.insert(a, val); + } + return val; + } + + void interpret() { + for (unsigned i = 0; i < m_ops.size(); ++i) { + interpret(m_ops[i]); + } + } + + void unsupported(expr* e) { + gl_unsupported(m, e); + } + +public: + + expr_evaluator(ast_manager& m): m(m), m_bv(m), m_vals_tmp(0) + {} + + void compile(obj_map& vars, expr* e) { + m_vars_tmp = &vars; + m_dst = compile(e); + } + + uint64 evaluate(assignment const& assign) { + m_vals_tmp = &assign; + interpret(); + return m_regs[m_dst]; + } + +}; + +// compile a formula by abstracting T-functions (of certain size) +// to propositional abstrations. +class t_function_abstractor { + ast_manager& m; + bv_util m_bv; + family_id m_bv_fid; + app_ref_vector* m_t_funs, *m_t_proxies; + obj_map* m_t_vars; + obj_map m_cache; + + void unsupported(expr* e) { gl_unsupported(m, e); } + + void unsupported() { gl_unsupported(m, 0); } + + bool is_bv(app* t) const { + return m_bv_fid == t->get_family_id(); + } + + // t is a T function. Extract constraints on inputs. + void absT(expr* _t, app_ref& result) { + app* c; + if (!is_app(_t)) { + unsupported(_t); + } + app* t = to_app(_t); + if (m_cache.find(t, c)) { + result = c; + } + else { + absT(t, result); + m_cache.insert(t, result); + } + } + + void absT(app* t, app_ref& result) { + if (is_bv(t)) { + absTBv(t, result); + } + else { + absTVar(t, result); + } + } + + void absT(unsigned num_args, expr* const* args, expr_ref_vector& rs) { + app_ref result(m); + for (unsigned i = 0; i < num_args; ++i) { + absT(args[i], result); + rs.push_back(result); + } + } + + void absTVar(app* t, app_ref& result) { + absF(t, result); + m_t_vars->insert(result, m_t_vars->size()); + } + + void absTBv(app* t, app_ref& result) { + SASSERT(is_bv(t)); + expr_ref_vector results(m); + switch(t->get_decl_kind()) { + case OP_BV_NUM: + case OP_BIT1: + case OP_BIT0: + case OP_BADD: + case OP_BSUB: + case OP_BNEG: + case OP_BMUL: + case OP_BAND: + case OP_BOR: + case OP_BNOT: + case OP_BXOR: + case OP_BNAND: + case OP_BNOR: + case OP_BXNOR: + case OP_CONCAT: + case OP_SIGN_EXT: + case OP_ZERO_EXT: + case OP_EXTRACT: + case OP_BSHL: + absT(t->get_num_args(), t->get_args(), results); + result = m.mk_app(t->get_decl(), results.size(), results.c_ptr()); + break; + default: + absTVar(t, result); + break; + } + } + + // f is a formula/non-T function. Extract T functions. + void absF(expr* _f, app_ref& result) { + app* c; + if (!is_app(_f)) { + unsupported(_f); + } + app* f = to_app(_f); + if (m_cache.find(f, c)) { + result = c; + } + else { + absF(f, result); + m_cache.insert(f, result); + } + } + + void absF(app* a, app_ref& result) { + if (a->get_family_id() == m.get_basic_family_id()) { + absFBasic(a, result); + } + else if (is_bv(a)) { + absFBv(a, result); + } + else { + expr_ref_vector rs(m); + absF(a->get_num_args(), a->get_args(), rs); + result = m.mk_app(a->get_decl(), rs.size(), rs.c_ptr()); + } + } + + void absF(unsigned num_args, expr*const* args, expr_ref_vector& rs) { + app_ref result(m); + for (unsigned i = 0; i < num_args; ++i) { + absF(args[i], result); + rs.push_back(result); + } + } + + void absFBasic(app* f, app_ref& result) { + expr_ref_vector results(m); + expr* e1, *e2; + if (m.is_eq(f, e1, e2) && m_bv.is_bv(e1)) { + app_ref r1(m), r2(m); + r1 = to_app(m_bv.mk_bv_xor(2, f->get_args())); + absFBv(r1, r2); + e1 = m_bv.mk_numeral(rational(0), m_bv.get_bv_size(e1)); + result = m.mk_eq(e1, r2); + } + else { + absF(f->get_num_args(), f->get_args(), results); + result = m.mk_app(f->get_decl(), results.size(), results.c_ptr()); + } + } + + void absFBv(app* a, app_ref& result) { + expr_ref_vector results(m); + switch(a->get_decl_kind()) { + case OP_BADD: + case OP_BSUB: + case OP_BNEG: + case OP_BMUL: + case OP_BAND: + case OP_BOR: + case OP_BNOT: + case OP_BXOR: + case OP_BNAND: + case OP_BNOR: + case OP_BXNOR: + case OP_CONCAT: + case OP_SIGN_EXT: + case OP_ZERO_EXT: + case OP_EXTRACT: + case OP_BSHL: { + app_ref tmp(m); + absT(a, tmp); + result = m.mk_fresh_const("T",m.get_sort(a)); + m_t_funs->push_back(tmp); + m_t_proxies->push_back(result); + return; + } + default: + absF(a->get_num_args(), a->get_args(), results); + result = m.mk_app(a->get_decl(), results.size(), results.c_ptr()); + return; + } + } + +public: + t_function_abstractor(ast_manager& m): m(m), m_bv(m), m_bv_fid(m.get_family_id("bv")) {} + + void operator()(expr* e, app_ref& result, app_ref_vector& t_funs, app_ref_vector& t_proxies, obj_map& t_vars) { + m_cache.reset(); + m_t_funs = &t_funs; + m_t_proxies = &t_proxies; + m_t_vars = &t_vars; + absF(e, result); + } + +}; + +class gl_eval { + ast_manager& m; + expr_evaluator m_eval; + app_ref m_proxy; + expr_ref m_proxy_val; + uint64 m_expected; + bool m_expected_valid; +public: + gl_eval(ast_manager& m): m(m), m_eval(m), m_proxy(m), m_proxy_val(m), m_expected(0) {} + + void set_model(model_ref& mdl) { + m_proxy_val = 0; + if (mdl->eval(m_proxy, m_proxy_val, false) && m_proxy_val.get()) { + rational r; + bv_util bv(m); + unsigned bv_size; + m_expected_valid = true; + VERIFY(bv.is_numeral(m_proxy_val, r, bv_size)); + m_expected = r.get_uint64(); + TRACE("gl_st", tout << mk_pp(m_proxy, m) << " |-> " << r << "\n";); + } + else { + m_expected_valid = false; + } + } + + void set_proxy(app* e) { m_proxy = e; } + + expr* get_proxy() { return m_proxy; } + + expr* get_proxy_val() { return m_proxy_val; } + + bool operator()(assignment const& assign, unsigned num_bits) { + if (!m_expected_valid) { + TRACE("gl_st", tout << mk_pp(m_proxy, m) << " not valid\n";); + return true; + } + else { + uint64 val = m_eval.evaluate(assign); + uint64 mask = (1ULL << num_bits) - 1ULL; + TRACE("gl_st", tout << mk_pp(m_proxy, m) << " " << val << " expected: " << m_expected << " num_bits: " << num_bits << "\n";); + return (val & mask) == (m_expected & mask); + } + } + + void compile(obj_map& var2index, expr* e) { + m_eval.compile(var2index, e); + } +}; + + +class gl_st : public assertion_set_strategy { + + ast_manager& m; + front_end_params m_params; + + // variables and their assignments. obj_map m_var2index; + ptr_vector m_index2var; + svector m_vals; + + // t-functions + vector m_evals; + + // external search state. + smt::context m_ctx; + + // internal search state. + unsigned_vector m_search_stack; + unsigned m_max_bitwidth; + unsigned m_max_search_depth; + + + bv_util m_bv; + volatile bool m_cancel; + +public: + + gl_st(ast_manager& m, params_ref const& p): + m(m), m_ctx(m, m_params), + m_bv(m), m_cancel(false), + m_max_search_depth(0) { + m_params.m_model = true; + } + + virtual ~gl_st() { + for (unsigned i = 0; i < m_evals.size(); ++i) dealloc(m_evals[i]); + } + + struct nonsat_by_gl { }; + + struct unsupported_by_gl { }; + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + ptr_vector conjs; + for (unsigned i = 0; i < s.size(); ++i) { + conjs.push_back(s.form(i)); + } + app_ref fml1(m.mk_and(conjs.size(), conjs.c_ptr()), m); + app_ref fml2(m); + t_function_abstractor TAbs(m); + app_ref_vector t_funs(m), t_proxies(m); + TAbs(fml1, fml2, t_funs, t_proxies, m_var2index); + TRACE("gl_st", + tout << mk_pp(fml1, m) << "\n"; + tout << mk_pp(fml2, m) << "\n";); + + m_index2var.resize(m_var2index.size()); + m_vals.resize(m_var2index.size()); + obj_map::iterator it = m_var2index.begin(), end = m_var2index.end(); + for (; it != end; ++it) { + m_index2var[it->m_value] = it->m_key; + } + + m_max_bitwidth = 0; + + for (unsigned i = 0; i < t_funs.size(); ++i) { + gl_eval* eval = alloc(gl_eval, m); + eval->compile(m_var2index, t_funs[i].get()); + eval->set_proxy(t_proxies[i].get()); + m_evals.push_back(eval); + m_max_bitwidth = std::max(m_max_bitwidth, m_bv.get_bv_size(t_proxies[i].get())); + } + + m_ctx.assert_expr(fml2); + + if (search()) { + elim_var_model_converter* emc = alloc(elim_var_model_converter, m); + mc = emc; + for (unsigned i = 0; i < m_index2var.size(); ++i) { + expr_ref vl(m); + if (m.is_bool(m_index2var[i])) { + vl = m_vals[i]? m.mk_true():m.mk_false(); + } + else if (m_bv.is_bv(m_index2var[i])) { + vl = m_bv.mk_numeral(m_vals[i], m_bv.get_bv_size(m_index2var[i])); + } + emc->insert(m_index2var[i]->get_decl(), vl); + } + // TBD: other variables. + s.reset(); + } + else { + throw nonsat_by_gl(); + } + } + + virtual void cleanup() { + + } + + virtual void collect_statistics(statistics & st) const { + + } + + virtual void reset_statistics() { + + } +protected: + virtual void set_cancel(bool f) { + } +private: + + void checkpoint() { + if (m_cancel) + throw gl_exception(STE_CANCELED_MSG); + cooperate("gl"); + } + + bool search() { + while (true) { + lbool res = m_ctx.check(); + if (res == l_false) { + return false; + } + model_ref mdl; + m_ctx.get_model(mdl); + for (unsigned i = 0; i < m_evals.size(); ++i) { + m_evals[i]->set_model(mdl); + } + m_max_search_depth = 0; + if (t_search()) { + return true; + } + expr_ref_vector eqs(m); + for (unsigned i = 0; i < m_evals.size(); ++i) { + expr* v = m_evals[i]->get_proxy_val(); + expr* p = m_evals[i]->get_proxy(); + if (v && p) { + if (m_bv.is_bv(v) && m_bv.get_bv_size(v) > m_max_search_depth) { + v = m_bv.mk_extract(m_max_search_depth-1,0,v); + p = m_bv.mk_extract(m_max_search_depth-1,0,p); + } + eqs.push_back(m.mk_eq(v, p)); + } + } + expr_ref fml(m); + fml = m.mk_and(eqs.size(), eqs.c_ptr()); + TRACE("gl_st", tout << "block " << mk_pp(fml, m) << "\n";); + m_ctx.assert_expr(m.mk_not(fml)); + } + return false; + } + + + bool t_search() { + if (!decide()) { + return false; + } + while (true) { + while (!propagate()) { + if (!backtrack()) { + return false; + } + } + if (!decide()) { + return true; + } + } + } + + bool propagate() { + // update the assignment. + for (unsigned i = 0; i < m_index2var.size(); ++i) { + m_vals[i] = 0; + for (unsigned j = 0; j < m_search_stack.size(); ++j) { + if (m_search_stack[j] & (1 << i)) { + m_vals[i] |= (1ULL << j); + } + } + } + // evaluate under assignment + for (unsigned i = 0; i < m_evals.size(); ++i) { + if (!(*m_evals[i])(m_vals, m_search_stack.size())) { + return false; + } + } + return true; + } + + // backtrack to previous level. + bool backtrack() { + while (!next()) { + TRACE("gl_st", tout << "level: " << m_search_stack.size() << "\n";); + m_search_stack.pop_back(); + if (m_search_stack.empty()) { + return false; + } + } + return true; + } + + bool next() { + SASSERT(!m_search_stack.empty()); + m_search_stack.back()++; + TRACE("gl_st", tout << "next " << m_search_stack.back() << "\n";); + return !(m_search_stack.back() & (1 << m_index2var.size())); + } + + // choose the next assignment. + bool decide() { + if (m_search_stack.size() == m_max_bitwidth) { + return false; + } + else { + m_search_stack.push_back(0); + m_max_search_depth = std::max(m_max_search_depth, m_search_stack.size()); + return true; + } + } + + std::ostream& display(std::ostream& out) { + out << "gl"; + return out; + } + +}; + +MK_FAIL_IF(fail_if_not_bv, !is_qfbv(s), "failed: GL supports only pure QF_BV for now."); + +static as_st * mk_gl_preamble(ast_manager & m, params_ref const & p) { + params_ref main_p; + main_p.set_bool(":elim-and", true); + main_p.set_bool(":push-ite-bv", true); + main_p.set_bool(":blast-distinct", true); + main_p.set_bool(":hi-div0", true); + + params_ref simp2_p = p; + simp2_p.set_bool(":som", true); + simp2_p.set_bool(":pull-cheap-ite", true); + simp2_p.set_bool(":push-ite-bv", false); + simp2_p.set_bool(":local-ctx", true); + simp2_p.set_uint(":local-ctx-limit", 10000000); + + params_ref hoist_p; + hoist_p.set_bool(":hoist-mul", true); + hoist_p.set_bool(":som", false); + + params_ref gaussian_p; + // conservative guassian elimination. + gaussian_p.set_uint(":gaussian-max-occs", 2); + + return and_then(and_then(mk_simplifier(m), + mk_shallow_simplifier(m), + using_params(mk_gaussian(m), gaussian_p), + mk_elim_uncnstr_vars(m), + mk_bv_size_reduction(m), + using_params(mk_simplifier(m), simp2_p)), + using_params(mk_simplifier(m), hoist_p), + mk_max_bv_sharing(m), + mk_nnf(p)); +} + +as_st * mk_gl(ast_manager & m, params_ref const& p) { + return alloc(gl_st, m, p); +} + +as_st * mk_qfbv_gl_strategy(ast_manager & m, params_ref const & p) { + params_ref gl_p(p); + + as_st * st = and_then(mk_gl_preamble(m, p), + fail_if_not_bv(), + mk_gl(m, gl_p)); + + st->updt_params(p); + return st; +} + diff --git a/lib/gl_tactic.h b/lib/gl_tactic.h new file mode 100644 index 000000000..63a88c98d --- /dev/null +++ b/lib/gl_tactic.h @@ -0,0 +1,29 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + gl_tactic.h + +Abstract: + + A T-function/Goldreich Levin-based heuristic. + +Author: + + Nikolaj (nbjorner) 2011-12-18 + +Notes: + +--*/ + +#ifndef _GL_TACTIC_H_ +#define _GL_TACTIC_H_ + +#include"tactic.h" + +namespace gl { + tactic * mk_tactic(ast_manager& m, params_ref const& p); +}; + +#endif diff --git a/lib/gmp_big_rational.cpp b/lib/gmp_big_rational.cpp new file mode 100644 index 000000000..3b2948607 --- /dev/null +++ b/lib/gmp_big_rational.cpp @@ -0,0 +1,117 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + gmp_big_rational.cpp + +Abstract: + + Big rationals using GMP + +Author: + + Leonardo de Moura (leonardo) 2006-09-26. + +Revision History: + +--*/ +#include + +#include"gmp_big_rational.h" +#include"trace.h" +#include"buffer.h" + +#ifndef NO_GMP +mpz_t big_rational::m_tmp; +bool big_rational::m_tmp_initialized = false; +mpz_t big_rational::m_int_min; +mpz_t big_rational::m_int_max; +mpz_t big_rational::m_uint_max; +mpz_t big_rational::m_two32; +mpz_t big_rational::m_int64_min; +mpz_t big_rational::m_int64_max; +mpz_t big_rational::m_uint64_max; +bool big_rational::m_has_limits = false; + +void big_rational::init_limits() { + mpz_init(m_int_min); + mpz_init(m_int_max); + mpz_init(m_uint_max); + mpz_init(m_two32); + mpz_init(m_int64_min); + mpz_init(m_int64_max); + mpz_init(m_uint64_max); + mpz_set_si(m_int_min, INT_MIN); + mpz_set_si(m_int_max, INT_MAX); + mpz_set_ui(m_uint_max, UINT_MAX); + mpz_set_ui(m_two32, UINT_MAX); + mpz_t & tmp = get_tmp(); + mpz_set_si(tmp, 1); + mpz_add(m_two32, m_two32, tmp); + unsigned max_l = static_cast(INT64_MAX % static_cast(UINT_MAX)); + unsigned max_h = static_cast(INT64_MAX / static_cast(UINT_MAX)); + mpz_set_ui(m_int64_max, max_h); + mpz_mul(m_int64_max, m_uint_max, m_int64_max); + mpz_set_ui(tmp, max_l); + mpz_add(m_int64_max, tmp, m_int64_max); + mpz_neg(m_int64_min, m_int64_max); + mpz_set_si(tmp, -1); + mpz_add(m_int64_min, m_int64_min, tmp); + mpz_set(m_uint64_max, m_int64_max); + mpz_set_si(tmp, 2); + mpz_mul(m_uint64_max, m_uint64_max, tmp); + mpz_set_si(tmp, 1); + mpz_add(m_uint64_max, m_uint64_max, tmp); + m_has_limits = true; +} + +std::string big_rational::to_string() const { + size_t sz = mpz_sizeinbase(mpq_numref(m_data), 10) + mpz_sizeinbase(mpq_numref(m_data), 10) + 3; + buffer b(sz, 0); + mpq_get_str(b.c_ptr(), 10, m_data); + std::string result(b.c_ptr()); + return result; +} + +int64 big_rational::get_int64() const { + if (!m_has_limits) { + init_limits(); + } + SASSERT(is_int64()); + mpz_t & tmp = get_tmp(); + mpq_get_num(tmp, m_data); + if (sizeof(int64) == sizeof(long) || mpz_fits_slong_p(tmp)) { + return mpz_get_si(tmp); + } + else { + mpz_mod(tmp, tmp, two32()); + int64 r = static_cast(mpz_get_ui(tmp)); + mpq_get_num(tmp, m_data); + mpz_div(tmp, tmp, two32()); + r += static_cast(mpz_get_si(tmp)) << static_cast(32); + return r; + } +} + +uint64 big_rational::get_uint64() const { + if (!m_has_limits) { + init_limits(); + } + SASSERT(is_uint64()); + mpz_t & tmp = get_tmp(); + mpq_get_num(tmp, m_data); + if (sizeof(uint64) == sizeof(unsigned long) || mpz_fits_ulong_p(tmp)) { + return mpz_get_ui(tmp); + } + else { + mpz_mod(tmp, tmp, two32()); + uint64 r = static_cast(mpz_get_ui(tmp)); + mpq_get_num(tmp, m_data); + mpz_div(tmp, tmp, two32()); + r += static_cast(mpz_get_ui(tmp)) << static_cast(32); + return r; + } +} + +#endif diff --git a/lib/gmp_big_rational.h b/lib/gmp_big_rational.h new file mode 100644 index 000000000..bffb069b7 --- /dev/null +++ b/lib/gmp_big_rational.h @@ -0,0 +1,193 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + gmp_big_rational.h + +Abstract: + + Big rationals using gmp + +Author: + + Leonardo de Moura (leonardo) 2006-09-26. + +Revision History: + +--*/ +#ifndef _GMP_BIG_RATIONAL_H_ +#define _GMP_BIG_RATIONAL_H_ + +#ifndef NO_GMP + +#include +#include +#include"util.h" +#include"debug.h" +#include"trace.h" + +class big_rational { + mpq_t m_data; + static mpz_t m_tmp; + static bool m_tmp_initialized; + static mpz_t & get_tmp() { + if (!m_tmp_initialized) { + mpz_init(m_tmp); + m_tmp_initialized = true; + } + return m_tmp; + } + static mpz_t m_int_min; + static mpz_t m_int_max; + static mpz_t m_uint_max; + static mpz_t m_two32; + static mpz_t m_int64_min; + static mpz_t m_int64_max; + static mpz_t m_uint64_max; + static bool m_has_limits; + static void init_limits(); + + static mpz_t & int64_min() { + if (!m_has_limits) { + init_limits(); + } + return m_int64_min; + } + + static mpz_t & int64_max() { + if (!m_has_limits) { + init_limits(); + } + return m_int64_max; + } + + static mpz_t & uint64_max() { + if (!m_has_limits) { + init_limits(); + } + return m_uint64_max; + } + + static mpz_t & uint_max() { + if (!m_has_limits) { + init_limits(); + } + return m_uint_max; + } + + static mpz_t & two32() { + if (!m_has_limits) { + init_limits(); + } + return m_two32; + } + +public: + big_rational() { mpq_init(m_data); reset(); } + big_rational(int n) { mpq_init(m_data); mpq_set_si(m_data, n, 1); } + ~big_rational() { mpq_clear(m_data); } + void reset() { mpq_set_si(m_data, 0, 1); } + unsigned hash() const { return mpz_get_si(mpq_numref(m_data)); } + void set(int num, int den) { + mpq_set_si(m_data, num, den); + mpq_canonicalize(m_data); + } + void setu(unsigned num) { + mpq_set_ui(m_data, num, 1); + mpq_canonicalize(m_data); + } + void set(const char * str) { + mpq_set_str(m_data, str, 10); + } + bool is_int() const { + return mpz_cmp_ui(mpq_denref(m_data), 1) == 0; + } + bool is_even() const { + if (!is_int()) { + return false; + } + mpz_t & tmp = get_tmp(); + mpq_get_num(tmp, m_data); + return mpz_even_p(tmp) != 0; + } + bool is_int64() const { + if (!is_int()) { + return false; + } + mpz_t & tmp = get_tmp(); + mpq_get_num(tmp, m_data); + return mpz_fits_slong_p(tmp) || + (mpz_cmp(tmp, int64_min()) >= 0 && mpz_cmp(tmp, int64_max()) <= 0); + } + bool is_uint64() const { + if (!is_int()) { + return false; + } + mpz_t & tmp = get_tmp(); + mpq_get_num(tmp, m_data); + return mpz_sgn(tmp) >= 0 && (mpz_fits_ulong_p(tmp) || mpz_cmp(tmp, uint64_max()) <= 0); + } + int64 get_int64() const; + uint64 get_uint64() const; + void neg() { mpq_neg(m_data, m_data); } + big_rational & operator=(const big_rational & r) { + mpq_set(m_data, r.m_data); + return *this; + } + bool operator==(const big_rational & r) const { return mpq_equal(m_data, r.m_data) != 0; } + bool operator<(const big_rational & r) const { return mpq_cmp(m_data, r.m_data) < 0; } + big_rational & operator+=(const big_rational & r) { + mpq_add(m_data, m_data, r.m_data); + return *this; + } + big_rational & operator-=(const big_rational & r) { + mpq_sub(m_data, m_data, r.m_data); + return *this; + } + big_rational & operator*=(const big_rational & r) { + mpq_mul(m_data, m_data, r.m_data); + return *this; + } + big_rational & operator/=(const big_rational & r) { + mpq_div(m_data, m_data, r.m_data); + return *this; + } + big_rational & operator%=(const big_rational & r) { + mpz_t & tmp = get_tmp(); + mpz_tdiv_r(tmp, mpq_numref(m_data), mpq_numref(r.m_data)); + mpq_set_z(m_data, tmp); + return *this; + } + friend void div(const big_rational & r1, const big_rational & r2, big_rational & result) { + mpz_t & tmp = get_tmp(); + mpz_tdiv_q(tmp, mpq_numref(r1.m_data), mpq_numref(r2.m_data)); + mpq_set_z(result.m_data, tmp); + } + void get_numerator(big_rational & result) { + mpz_t & tmp = get_tmp(); + mpq_get_num(tmp, m_data); + mpq_set_z(result.m_data, tmp); + } + void get_denominator(big_rational & result) { + mpz_t & tmp = get_tmp(); + mpq_get_den(tmp, m_data); + mpq_set_z(result.m_data, tmp); + } + void get_floor(big_rational & result) { + mpz_t & tmp = get_tmp(); + mpz_fdiv_q(tmp, mpq_numref(m_data), mpq_denref(m_data)); + mpq_set_z(result.m_data, tmp); + } + std::string to_string() const; +#ifdef Z3DEBUG + static void test() { + init_limits(); + } +#endif +}; + +#endif + +#endif /* _GMP_BIG_RATIONAL_H_ */ + diff --git a/lib/goal.cpp b/lib/goal.cpp new file mode 100644 index 000000000..0146ac252 --- /dev/null +++ b/lib/goal.cpp @@ -0,0 +1,606 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + goal.cpp + +Abstract: + + Proof / Model finding Goals + +Author: + + Leonardo de Moura (leonardo) 2011-10-12 + +Revision History: + +--*/ +#include"goal.h" +#include"cmd_context.h" +#include"ast_ll_pp.h" +#include"ast_smt2_pp.h" +#include"for_each_expr.h" +#include"well_sorted.h" + +std::ostream & operator<<(std::ostream & out, goal::precision p) { + switch (p) { + case goal::PRECISE: out << "precise"; break; + case goal::UNDER: out << "under"; break; + case goal::OVER: out << "over"; break; + case goal::UNDER_OVER: out << "under-over"; break; + } + return out; +} + +void goal::copy_to(goal & target) const { + SASSERT(&m_manager == &(target.m_manager)); + if (this == &target) + return; + + m().copy(m_forms, target.m_forms); + m().copy(m_proofs, target.m_proofs); + m().copy(m_dependencies, target.m_dependencies); + + target.m_depth = std::max(m_depth, target.m_depth); + SASSERT(target.m_proofs_enabled == m_proofs_enabled); + SASSERT(target.m_core_enabled == m_core_enabled); + target.m_inconsistent = m_inconsistent; + target.m_precision = mk_union(prec(), target.prec()); +} + +void goal::push_back(expr * f, proof * pr, expr_dependency * d) { + if (m().is_true(f)) + return; + if (m().is_false(f)) { + m().del(m_forms); + m().del(m_proofs); + m().del(m_dependencies); + m_inconsistent = true; + } + else { + SASSERT(!m_inconsistent); + } + m().push_back(m_forms, f); + if (proofs_enabled()) + m().push_back(m_proofs, pr); + if (unsat_core_enabled()) + m().push_back(m_dependencies, d); +} + +void goal::quick_process(bool save_first, expr * & f, expr_dependency * d) { + if (!m().is_and(f) && !(m().is_not(f) && m().is_or(to_app(f)->get_arg(0)))) { + if (!save_first) { + push_back(f, 0, d); + } + return; + } + typedef std::pair expr_pol; + sbuffer todo; + todo.push_back(expr_pol(f, true)); + while (!todo.empty()) { + if (m_inconsistent) + return; + expr_pol p = todo.back(); + expr * curr = p.first; + bool pol = p.second; + todo.pop_back(); + if (pol && m().is_and(curr)) { + app * t = to_app(curr); + unsigned i = t->get_num_args(); + while (i > 0) { + --i; + todo.push_back(expr_pol(t->get_arg(i), true)); + } + } + else if (!pol && m().is_or(curr)) { + app * t = to_app(curr); + unsigned i = t->get_num_args(); + while (i > 0) { + --i; + todo.push_back(expr_pol(t->get_arg(i), false)); + } + } + else if (m().is_not(curr)) { + todo.push_back(expr_pol(to_app(curr)->get_arg(0), !pol)); + } + else { + if (!pol) + curr = m().mk_not(curr); + if (save_first) { + f = curr; + save_first = false; + } + else { + push_back(curr, 0, d); + } + } + } +} + +void goal::process_and(bool save_first, app * f, proof * pr, expr_dependency * d, expr_ref & out_f, proof_ref & out_pr) { + unsigned num = f->get_num_args(); + for (unsigned i = 0; i < num; i++) { + if (m_inconsistent) + return; + slow_process(save_first && i == 0, f->get_arg(i), m().mk_and_elim(pr, i), d, out_f, out_pr); + } +} + +void goal::process_not_or(bool save_first, app * f, proof * pr, expr_dependency * d, expr_ref & out_f, proof_ref & out_pr) { + unsigned num = f->get_num_args(); + for (unsigned i = 0; i < num; i++) { + if (m_inconsistent) + return; + expr * child = f->get_arg(i); + if (m().is_not(child)) { + expr * not_child = to_app(child)->get_arg(0); + slow_process(save_first && i == 0, not_child, m().mk_not_or_elim(pr, i), d, out_f, out_pr); + } + else { + expr_ref not_child(m()); + not_child = m().mk_not(child); + slow_process(save_first && i == 0, not_child, m().mk_not_or_elim(pr, i), d, out_f, out_pr); + } + } +} + +void goal::slow_process(bool save_first, expr * f, proof * pr, expr_dependency * d, expr_ref & out_f, proof_ref & out_pr) { + if (m().is_and(f)) + process_and(save_first, to_app(f), pr, d, out_f, out_pr); + else if (m().is_not(f) && m().is_or(to_app(f)->get_arg(0))) + process_not_or(save_first, to_app(to_app(f)->get_arg(0)), pr, d, out_f, out_pr); + else if (save_first) { + out_f = f; + out_pr = pr; + } + else { + push_back(f, pr, d); + } +} + +void goal::slow_process(expr * f, proof * pr, expr_dependency * d) { + expr_ref out_f(m()); + proof_ref out_pr(m()); + slow_process(false, f, pr, d, out_f, out_pr); +} + +void goal::assert_expr(expr * f, proof * pr, expr_dependency * d) { + SASSERT(proofs_enabled() == (pr != 0 && !m().is_undef_proof(pr))); + if (m_inconsistent) + return; + if (proofs_enabled()) + slow_process(f, pr, d); + else + quick_process(false, f, d); +} + +void goal::get_formulas(ptr_vector & result) { + unsigned sz = size(); + for (unsigned i = 0; i < sz; i++) { + result.push_back(form(i)); + } +} + +void goal::update(unsigned i, expr * f, proof * pr, expr_dependency * d) { + SASSERT(proofs_enabled() == (pr != 0 && !m().is_undef_proof(pr))); + if (m_inconsistent) + return; + if (proofs_enabled()) { + expr_ref out_f(m()); + proof_ref out_pr(m()); + slow_process(true, f, pr, d, out_f, out_pr); + if (!m_inconsistent) { + if (m().is_false(out_f)) { + push_back(out_f, out_pr, d); + } + else { + m().set(m_forms, i, out_f); + m().set(m_proofs, i, out_pr); + if (unsat_core_enabled()) + m().set(m_dependencies, i, d); + } + } + } + else { + quick_process(true, f, d); + if (!m_inconsistent) { + if (m().is_false(f)) { + push_back(f, 0, d); + } + else { + m().set(m_forms, i, f); + if (unsat_core_enabled()) + m().set(m_dependencies, i, d); + } + } + } +} + +void goal::reset_core() { + m().del(m_forms); + m().del(m_proofs); + m().del(m_dependencies); +} + +void goal::reset_all() { + reset_core(); + m_depth = 0; + m_inconsistent = false; + m_precision = PRECISE; +} + +void goal::reset() { + reset_core(); + m_inconsistent = false; +} + +void goal::display(cmd_context & ctx, std::ostream & out) const { + out << "(goal"; + unsigned sz = size(); + for (unsigned i = 0; i < sz; i++) { + out << "\n "; + ctx.display(out, form(i), 2); + } + out << "\n :precision " << prec() << " :depth " << depth() << ")" << std::endl; +} + +void goal::display_with_dependencies(cmd_context & ctx, std::ostream & out) const { + ptr_vector deps; + obj_hashtable to_pp; + out << "(goal"; + unsigned sz = size(); + for (unsigned i = 0; i < sz; i++) { + out << "\n |-"; + deps.reset(); + m().linearize(dep(i), deps); + ptr_vector::iterator it = deps.begin(); + ptr_vector::iterator end = deps.end(); + for (; it != end; ++it) { + expr * d = *it; + if (is_uninterp_const(d)) { + out << " " << mk_ismt2_pp(d, m()); + } + else { + out << " #" << d->get_id(); + to_pp.insert(d); + } + } + out << "\n "; + ctx.display(out, form(i), 2); + } + if (!to_pp.empty()) { + out << "\n :dependencies-definitions ("; + obj_hashtable::iterator it = to_pp.begin(); + obj_hashtable::iterator end = to_pp.end(); + for (; it != end; ++it) { + expr * d = *it; + out << "\n (#" << d->get_id() << "\n "; + ctx.display(out, d, 2); + out << ")"; + } + out << ")"; + } + out << "\n :precision " << prec() << " :depth " << depth() << ")" << std::endl; +} + +void goal::display(cmd_context & ctx) const { + display(ctx, ctx.regular_stream()); +} + +void goal::display_with_dependencies(cmd_context & ctx) const { + display_with_dependencies(ctx, ctx.regular_stream()); +} + +void goal::display(std::ostream & out) const { + out << "(goal"; + unsigned sz = size(); + for (unsigned i = 0; i < sz; i++) { + out << "\n "; + out << mk_ismt2_pp(form(i), m(), 2); + } + out << ")" << std::endl; +} + +void goal::display_as_and(std::ostream & out) const { + ptr_buffer args; + unsigned sz = size(); + for (unsigned i = 0; i < sz; i++) + args.push_back(form(i)); + expr_ref tmp(m()); + tmp = m().mk_and(args.size(), args.c_ptr()); + out << mk_ismt2_pp(tmp, m()) << "\n"; +} + +void goal::display_ll(std::ostream & out) const { + unsigned sz = size(); + for (unsigned i = 0; i < sz; i++) { + out << mk_ll_pp(form(i), m()) << "\n"; + } +} + +/** + \brief Assumes that the formula is already in CNF. +*/ +void goal::display_dimacs(std::ostream & out) const { + obj_map expr2var; + unsigned num_vars = 0; + unsigned num_cls = size(); + for (unsigned i = 0; i < num_cls; i++) { + expr * f = form(i); + unsigned num_lits; + expr * const * lits; + if (m().is_or(f)) { + num_lits = to_app(f)->get_num_args(); + lits = to_app(f)->get_args(); + } + else { + num_lits = 1; + lits = &f; + } + for (unsigned j = 0; j < num_lits; j++) { + expr * l = lits[j]; + if (m().is_not(l)) + l = to_app(l)->get_arg(0); + if (expr2var.contains(l)) + continue; + num_vars++; + expr2var.insert(l, num_vars); + } + } + out << "p cnf " << num_vars << " " << num_cls << "\n"; + for (unsigned i = 0; i < num_cls; i++) { + expr * f = form(i); + unsigned num_lits; + expr * const * lits; + if (m().is_or(f)) { + num_lits = to_app(f)->get_num_args(); + lits = to_app(f)->get_args(); + } + else { + num_lits = 1; + lits = &f; + } + for (unsigned j = 0; j < num_lits; j++) { + expr * l = lits[j]; + if (m().is_not(l)) { + out << "-"; + l = to_app(l)->get_arg(0); + } + unsigned id = UINT_MAX; + expr2var.find(l, id); + SASSERT(id != UINT_MAX); + out << id << " "; + } + out << "0\n"; + } +} + +unsigned goal::num_exprs() const { + expr_fast_mark1 visited; + unsigned sz = size(); + unsigned r = 0; + for (unsigned i = 0; i < sz; i++) { + r += get_num_exprs(form(i), visited); + } + return r; +} + +void goal::shrink(unsigned j) { + SASSERT(j <= size()); + unsigned sz = size(); + for (unsigned i = j; i < sz; i++) + m().pop_back(m_forms); + if (proofs_enabled()) { + for (unsigned i = j; i < sz; i++) + m().pop_back(m_proofs); + } + if (unsat_core_enabled()) { + for (unsigned i = j; i < sz; i++) + m().pop_back(m_dependencies); + } +} + +/** + \brief Eliminate true formulas. +*/ +void goal::elim_true() { + unsigned sz = size(); + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + expr * f = form(i); + if (m().is_true(f)) + continue; + if (i == j) { + j++; + continue; + } + m().set(m_forms, j, f); + if (proofs_enabled()) + m().set(m_proofs, j, m().get(m_proofs, i)); + if (unsat_core_enabled()) + m().set(m_dependencies, j, m().get(m_dependencies, i)); + j++; + } + shrink(j); +} + +/** + \brief Return the position of formula f in the goal. + Return UINT_MAX if f is not in the goal +*/ +unsigned goal::get_idx(expr * f) const { + unsigned sz = size(); + for (unsigned j = 0; j < sz; j++) { + if (form(j) == f) + return j; + } + return UINT_MAX; +} + +/** + \brief Return the position of formula (not f) in the goal. + Return UINT_MAX if (not f) is not in the goal +*/ +unsigned goal::get_not_idx(expr * f) const { + expr * atom; + unsigned sz = size(); + for (unsigned j = 0; j < sz; j++) { + if (m().is_not(form(j), atom) && atom == f) + return j; + } + return UINT_MAX; +} + +void goal::elim_redundancies() { + if (inconsistent()) + return; + expr_ref_fast_mark1 neg_lits(m()); + expr_ref_fast_mark2 pos_lits(m()); + unsigned sz = size(); + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + expr * f = form(i); + if (m().is_true(f)) + continue; + if (m().is_not(f)) { + expr * atom = to_app(f)->get_arg(0); + if (neg_lits.is_marked(atom)) + continue; + if (pos_lits.is_marked(atom)) { + proof * p = 0; + if (proofs_enabled()) { + proof * prs[2] = { pr(get_idx(atom)), pr(i) }; + p = m().mk_unit_resolution(2, prs); + } + expr_dependency * d = 0; + if (unsat_core_enabled()) + d = m().mk_join(dep(get_idx(atom)), dep(i)); + push_back(m().mk_false(), p, d); + return; + } + neg_lits.mark(atom); + } + else { + if (pos_lits.is_marked(f)) + continue; + if (neg_lits.is_marked(f)) { + proof * p = 0; + if (proofs_enabled()) { + proof * prs[2] = { pr(get_not_idx(f)), pr(i) }; + p = m().mk_unit_resolution(2, prs); + } + expr_dependency * d = 0; + if (unsat_core_enabled()) + d = m().mk_join(dep(get_not_idx(f)), dep(i)); + push_back(m().mk_false(), p, d); + return; + } + pos_lits.mark(f); + } + if (i == j) { + j++; + continue; + } + m().set(m_forms, j, f); + if (proofs_enabled()) + m().set(m_proofs, j, pr(i)); + if (unsat_core_enabled()) + m().set(m_dependencies, j, dep(i)); + j++; + } + shrink(j); +} + +bool goal::is_well_sorted() const { + unsigned sz = size(); + for (unsigned i = 0; i < sz; i++) { + expr * t = form(i); + if (!::is_well_sorted(m(), t)) + return false; + } + return true; +} + +/** + \brief Assert expressions from ctx into t. +*/ +void assert_exprs_from(cmd_context const & ctx, goal & t) { + if (ctx.produce_proofs() && ctx.produce_unsat_cores()) + throw cmd_exception("Frontend does not support simultaneous generation of proofs and unsat cores"); + ast_manager & m = t.m(); + bool proofs_enabled = t.proofs_enabled(); + ptr_vector::const_iterator it = ctx.begin_assertions(); + ptr_vector::const_iterator end = ctx.end_assertions(); + for (; it != end; ++it) { + t.assert_expr(*it, proofs_enabled ? m.mk_asserted(*it) : 0, 0); + } + if (ctx.produce_unsat_cores()) { + SASSERT(!ctx.produce_proofs()); + it = ctx.begin_assumptions(); + end = ctx.end_assumptions(); + for (; it != end; ++it) { + t.assert_expr(*it, 0, m.mk_leaf(*it)); + } + } + else { + SASSERT(ctx.begin_assumptions() == ctx.end_assumptions()); + } +} + +/** + \brief Translate the assertion set to a new one that uses a different ast_manager. +*/ +goal * goal::translate(ast_translation & translator) const { + expr_dependency_translation dep_translator(translator); + + ast_manager & m_to = translator.to(); + goal * res = alloc(goal, m_to, m_to.proofs_enabled() && proofs_enabled(), models_enabled(), unsat_core_enabled()); + + unsigned sz = m().size(m_forms); + for (unsigned i = 0; i < sz; i++) { + res->m().push_back(res->m_forms, translator(m().get(m_forms, i))); + if (res->proofs_enabled()) + res->m().push_back(res->m_proofs, translator(m().get(m_proofs, i))); + if (res->unsat_core_enabled()) + res->m().push_back(res->m_dependencies, dep_translator(m().get(m_dependencies, i))); + } + + res->m_inconsistent = m_inconsistent; + res->m_depth = m_depth; + res->m_precision = m_precision; + + return res; +} + +bool is_equal(goal const & s1, goal const & s2) { + if (s1.size() != s2.size()) + return false; + unsigned num1 = 0; // num unique ASTs in s1 + unsigned num2 = 0; // num unique ASTs in s2 + expr_fast_mark1 visited1; + expr_fast_mark2 visited2; + unsigned sz = s1.size(); + for (unsigned i = 0; i < sz; i++) { + expr * f1 = s1.form(i); + if (visited1.is_marked(f1)) + continue; + num1++; + visited1.mark(f1); + } + SASSERT(num1 <= sz); + SASSERT(0 <= num1); + for (unsigned i = 0; i < sz; i++) { + expr * f2 = s2.form(i); + if (visited2.is_marked(f2)) + continue; + num2++; + visited2.mark(f2); + if (!visited1.is_marked(f2)) + return false; + } + SASSERT(num2 <= sz); + SASSERT(0 <= num2); + SASSERT(num1 >= num2); + return num1 == num2; +} diff --git a/lib/goal.h b/lib/goal.h new file mode 100644 index 000000000..9beb9421e --- /dev/null +++ b/lib/goal.h @@ -0,0 +1,254 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + goal.h + +Abstract: + + A goal is essentially a set of formulas. Tactics are used to build + proof and model finding procedures for these sets. + + Remark: In a previous version of Z3, goals were called assertion_sets. + Here is a summary of the main changes: + - Goals track whether they are the result of applying over/under approximation steps. + This prevent users from creating unsound strategies (e.g., user uses nia2sat, but does not check the sat_preserving flag). + - Goals track dependencies (aka light proofs) for unsat core extraction, and building multi-tier solvers. + This kind of dependency tracking is more powerful than the one used in the current Z3, since + it does not prevent the use of preprocessing steps such as "Gaussian Elimination". + +Author: + + Leonardo de Moura (leonardo) 2011-10-12 + +Revision History: + +--*/ +#ifndef _GOAL_H_ +#define _GOAL_H_ + +#include"ast.h" +#include"ast_translation.h" +#include"for_each_expr.h" +#include"ref.h" +#include"ref_vector.h" +#include"ref_buffer.h" + +class cmd_context; + +class goal { +public: + enum precision { + PRECISE, + UNDER, // goal is the product of an under-approximation + OVER, // goal is the product of an over-approximation + UNDER_OVER // goal is garbage: the produce of combined under and over approximation steps. + }; + + static precision mk_union(precision p1, precision p2) { + if (p1 == PRECISE) return p2; + if (p2 == PRECISE) return p1; + if (p1 != p2) return UNDER_OVER; + return p1; + } + +protected: + ast_manager & m_manager; + unsigned m_ref_count; + expr_array m_forms; + expr_array m_proofs; + expr_dependency_array m_dependencies; + // attributes + unsigned m_depth:26; // depth of the goal in the goal tree. + unsigned m_models_enabled:1; // model generation is enabled. + unsigned m_proofs_enabled:1; // proof production is enabled. m_manager.proofs_enabled() must be true if m_proofs_enabled == true + unsigned m_core_enabled:1; // unsat core extraction is enabled. + unsigned m_inconsistent:1; // true if the goal is known to be inconsistent. + unsigned m_precision:2; // PRECISE, UNDER, OVER. + + void push_back(expr * f, proof * pr, expr_dependency * d); + void quick_process(bool save_first, expr * & f, expr_dependency * d); + void process_and(bool save_first, app * f, proof * pr, expr_dependency * d, expr_ref & out_f, proof_ref & out_pr); + void process_not_or(bool save_first, app * f, proof * pr, expr_dependency * d, expr_ref & out_f, proof_ref & out_pr); + void slow_process(bool save_first, expr * f, proof * pr, expr_dependency * d, expr_ref & out_f, proof_ref & out_pr); + void slow_process(expr * f, proof * pr, expr_dependency * d); + unsigned get_idx(expr * f) const; + unsigned get_not_idx(expr * f) const; + void shrink(unsigned j); + void reset_core(); + +public: + goal(ast_manager & m, bool models_enabled = true, bool core_enabled = false): + m_manager(m), + m_ref_count(0), + m_depth(0), + m_models_enabled(models_enabled), + m_proofs_enabled(m.proofs_enabled()), + m_core_enabled(core_enabled), + m_inconsistent(false), + m_precision(PRECISE) { + } + + goal(ast_manager & m, bool proofs_enabled, bool models_enabled, bool core_enabled): + m_manager(m), + m_ref_count(0), + m_depth(0), + m_models_enabled(models_enabled), + m_proofs_enabled(proofs_enabled), + m_core_enabled(core_enabled), + m_inconsistent(false), + m_precision(PRECISE) { + SASSERT(!proofs_enabled || m.proofs_enabled()); + } + + goal(goal const & src): + m_manager(src.m()), + m_ref_count(0), + m_depth(0), + m_models_enabled(src.models_enabled()), + m_proofs_enabled(src.proofs_enabled()), + m_core_enabled(src.unsat_core_enabled()), + m_inconsistent(false), + m_precision(PRECISE) { + copy_from(src); + } + + // Copy configuration: depth, models/proofs/cores flags, and precision from src. + // The assertions are not copied + goal(goal const & src, bool): + m_manager(src.m()), + m_ref_count(0), + m_depth(src.m_depth), + m_models_enabled(src.models_enabled()), + m_proofs_enabled(src.proofs_enabled()), + m_core_enabled(src.unsat_core_enabled()), + m_inconsistent(false), + m_precision(src.m_precision) { + } + + ~goal() { reset_core(); } + + void inc_ref() { ++m_ref_count; } + void dec_ref() { --m_ref_count; if (m_ref_count == 0) dealloc(this); } + + ast_manager & m() const { return m_manager; } + + unsigned depth() const { return m_depth; } + bool models_enabled() const { return m_models_enabled; } + bool proofs_enabled() const { return m_proofs_enabled; } + bool unsat_core_enabled() const { return m_core_enabled; } + bool inconsistent() const { return m_inconsistent; } + precision prec() const { return static_cast(m_precision); } + + void set_depth(unsigned d) { m_depth = d; } + void inc_depth() { m_depth++; } + void set_prec(precision d) { m_precision = d; } + void updt_prec(precision d) { m_precision = mk_union(prec(), d); } + + void reset_all(); // reset goal and precision and depth attributes. + void reset(); // reset goal but preserve precision and depth attributes. + + void copy_to(goal & target) const; + void copy_from(goal const & src) { src.copy_to(*this); } + + void assert_expr(expr * f, proof * pr, expr_dependency * d); + void assert_expr(expr * f) { + assert_expr(f, proofs_enabled() ? m().mk_asserted(f) : 0, 0); + } + + unsigned size() const { return m().size(m_forms); } + + unsigned num_exprs() const; + + expr * form(unsigned i) const { return m().get(m_forms, i); } + proof * pr(unsigned i) const { return proofs_enabled() ? static_cast(m().get(m_proofs, i)) : 0; } + expr_dependency * dep(unsigned i) const { return unsat_core_enabled() ? m().get(m_dependencies, i) : 0; } + + void update(unsigned i, expr * f, proof * pr = 0, expr_dependency * dep = 0); + + void get_formulas(ptr_vector & result); + + void elim_true(); + void elim_redundancies(); + + void display(cmd_context & ctx, std::ostream & out) const; + void display(cmd_context & ctx) const; + void display(std::ostream & out) const; + void display_ll(std::ostream & out) const; + void display_as_and(std::ostream & out) const; + void display_dimacs(std::ostream & out) const; + void display_with_dependencies(cmd_context & ctx, std::ostream & out) const; + void display_with_dependencies(cmd_context & ctx) const; + + bool sat_preserved() const { + return prec() == PRECISE || prec() == UNDER; + } + + bool unsat_preserved() const { + return prec() == PRECISE || prec() == OVER; + } + + bool is_decided_sat() const { + return size() == 0 && sat_preserved(); + } + + bool is_decided_unsat() const { + return inconsistent() && unsat_preserved(); + } + + bool is_decided() const { + return is_decided_sat() || is_decided_unsat(); + } + + bool is_well_sorted() const; + + goal * translate(ast_translation & translator) const; +}; + +std::ostream & operator<<(std::ostream & out, goal::precision p); + +typedef ref goal_ref; +typedef sref_vector goal_ref_vector; +typedef sref_buffer goal_ref_buffer; + +template +inline bool is_decided(GoalCollection const & c) { return c.size() == 1 && c[0]->is_decided(); } +template +inline bool is_decided_sat(GoalCollection const & c) { return c.size() == 1 && c[0]->is_decided_sat(); } +template +inline bool is_decided_unsat(GoalCollection const & c) { return c.size() == 1 && c[0]->is_decided_unsat(); } + +void assert_exprs_from(cmd_context const & ctx, goal & t); + +template +void for_each_expr_at(ForEachProc& proc, goal const & s) { + expr_mark visited; + for (unsigned i = 0; i < s.size(); ++i) { + for_each_expr(proc, visited, s.form(i)); + } +} + +bool is_equal(goal const & g1, goal const & g2); + +template +bool test(goal const & g, Predicate & proc) { + expr_fast_mark1 visited; + try { + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) + quick_for_each_expr(proc, visited, g.form(i)); + } + catch (typename Predicate::found) { + return true; + } + return false; +} + +template +bool test(goal const & g) { + Predicate proc(g.m()); + return test(g, proc); +} + +#endif diff --git a/lib/goal2nlsat.cpp b/lib/goal2nlsat.cpp new file mode 100644 index 000000000..75e3c4a40 --- /dev/null +++ b/lib/goal2nlsat.cpp @@ -0,0 +1,309 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + goal2nlsat.cpp + +Abstract: + + "Compile" a goal into the nonlinear arithmetic engine. + Non-arithmetic atoms are "abstracted" into boolean variables. + Non-supported terms are "abstracted" into variables. + + The mappings can be used to convert back the state of the + engine into a goal. + +Author: + + Leonardo (leonardo) 2012-01-02 + +Notes: + +--*/ +#include"goal2nlsat.h" +#include"goal.h" +#include"goal_util.h" +#include"nlsat_solver.h" +#include"expr2polynomial.h" +#include"expr2var.h" +#include"arith_decl_plugin.h" +#include"tactic.h" +#include"ast_smt2_pp.h" + +struct goal2nlsat::imp { + struct nlsat_expr2polynomial : public expr2polynomial { + nlsat::solver & m_solver; + nlsat_expr2polynomial(nlsat::solver & s, ast_manager & m, polynomial::manager & pm, expr2var * e2v): + expr2polynomial(m, pm, e2v), + m_solver(s) { + } + + virtual bool is_int(polynomial::var x) const { + return m_solver.is_int(x); + } + + virtual polynomial::var mk_var(bool is_int) { + return m_solver.mk_var(is_int); + } + }; + + ast_manager & m; + nlsat::solver & m_solver; + polynomial::manager & m_pm; + unsynch_mpq_manager & m_qm; + arith_util m_util; + expr2var & m_a2b; + expr2var & m_t2x; + nlsat_expr2polynomial m_expr2poly; + polynomial::factor_params m_fparams; + + unsigned long long m_max_memory; + bool m_factor; + + volatile bool m_cancel; + + imp(ast_manager & _m, params_ref const & p, nlsat::solver & s, expr2var & a2b, expr2var & t2x): + m(_m), + m_solver(s), + m_pm(s.pm()), + m_qm(s.qm()), + m_util(m), + m_a2b(a2b), + m_t2x(t2x), + m_expr2poly(m_solver, m, m_solver.pm(), &m_t2x) { + updt_params(p); + m_cancel = false; + } + + void updt_params(params_ref const & p) { + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + m_factor = p.get_bool(":factor", true); + m_fparams.updt_params(p); + } + + void set_cancel(bool f) { + m_cancel = f; + m_pm.set_cancel(f); + } + + nlsat::atom::kind flip(nlsat::atom::kind k) { + switch (k) { + case nlsat::atom::EQ: return k; + case nlsat::atom::LT: return nlsat::atom::GT; + case nlsat::atom::GT: return nlsat::atom::LT; + default: + UNREACHABLE(); + return k; + } + } + + nlsat::bool_var factor_atom(polynomial::polynomial * p, nlsat::atom::kind k) { + sbuffer is_even; + ptr_buffer ps; + polynomial::factors fs(m_pm); + m_pm.factor(p, fs, m_fparams); + TRACE("goal2nlsat_bug", tout << "factors:\n" << fs << "\n";); + SASSERT(fs.distinct_factors() > 0); + for (unsigned i = 0; i < fs.distinct_factors(); i++) { + ps.push_back(fs[i]); + is_even.push_back(fs.get_degree(i) % 2 == 0); + } + if (m_qm.is_neg(fs.get_constant())) + k = flip(k); + return m_solver.mk_ineq_atom(k, ps.size(), ps.c_ptr(), is_even.c_ptr()); + } + + nlsat::literal process_atom(app * f, nlsat::atom::kind k) { + SASSERT(f->get_num_args() == 2); + expr * lhs = f->get_arg(0); + expr * rhs = f->get_arg(1); + polynomial_ref p1(m_pm); + polynomial_ref p2(m_pm); + scoped_mpz d1(m_qm); + scoped_mpz d2(m_qm); + m_expr2poly.to_polynomial(lhs, p1, d1); + m_expr2poly.to_polynomial(rhs, p2, d2); + scoped_mpz lcm(m_qm); + m_qm.lcm(d1, d2, lcm); + m_qm.div(lcm, d1, d1); + m_qm.div(lcm, d2, d2); + m_qm.neg(d2); + polynomial_ref p(m_pm); + p = m_pm.addmul(d1, m_pm.mk_unit(), p1, d2, m_pm.mk_unit(), p2); + TRACE("goal2nlsat_bug", tout << "p: " << p << "\nk: " << k << "\n";); + if (is_const(p)) { + int sign; + if (is_zero(p)) + sign = 0; + else + sign = m_qm.is_pos(m_pm.coeff(p, 0)) ? 1 : -1; + switch (k) { + case nlsat::atom::EQ: return sign == 0 ? nlsat::true_literal : nlsat::false_literal; + case nlsat::atom::LT: return sign < 0 ? nlsat::true_literal : nlsat::false_literal; + case nlsat::atom::GT: return sign > 0 ? nlsat::true_literal : nlsat::false_literal; + default: + UNREACHABLE(); + return nlsat::true_literal; + } + } + if (m_factor) { + return nlsat::literal(factor_atom(p, k), false); + } + else { + bool is_even = false; + polynomial::polynomial * _p = p.get(); + return nlsat::literal(m_solver.mk_ineq_atom(k, 1, &_p, &is_even), false); + } + } + + nlsat::literal process_eq(app * f) { + return process_atom(f, nlsat::atom::EQ); + } + + nlsat::literal process_le(app * f) { + return ~process_atom(f, nlsat::atom::GT); + } + + nlsat::literal process_ge(app * f) { + return ~process_atom(f, nlsat::atom::LT); + } + + // everything else is compiled as a boolean variable + nlsat::bool_var process_bvar(expr * f) { + if (m_a2b.is_var(f)) { + return static_cast(m_a2b.to_var(f)); + } + else { + nlsat::bool_var b = m_solver.mk_bool_var(); + m_a2b.insert(f, b); + return b; + } + } + + nlsat::literal process_atom(expr * f) { + if (m.is_eq(f)) { + if (m_util.is_int_real(to_app(f)->get_arg(0))) + return process_eq(to_app(f)); + else + return nlsat::literal(process_bvar(f), false); + } + else if (m_util.is_le(f)) { + return process_le(to_app(f)); + } + else if (m_util.is_ge(f)) { + return process_ge(to_app(f)); + } + else if (is_app(f)) { + if (to_app(f)->get_family_id() == m.get_basic_family_id()) { + switch (to_app(f)->get_decl_kind()) { + case OP_TRUE: + case OP_FALSE: + TRACE("goal2nlsat", tout << "f: " << mk_ismt2_pp(f, m) << "\n";); + throw tactic_exception("apply simplify before applying nlsat"); + case OP_AND: + case OP_OR: + case OP_IFF: + case OP_XOR: + case OP_NOT: + case OP_IMPLIES: + throw tactic_exception("convert goal into cnf before applying nlsat"); + case OP_DISTINCT: + throw tactic_exception("eliminate distinct operator (use tactic '(using-params simplify :blast-distinct true)') before applying nlsat"); + default: + UNREACHABLE(); + return nlsat::literal(nlsat::null_bool_var, false); + } + } + else if (to_app(f)->get_family_id() == m_util.get_family_id()) { + throw tactic_exception("apply purify-arith before applying nlsat"); + } + else { + return nlsat::literal(process_bvar(f), false); + } + } + else { + SASSERT(is_quantifier(f)); + return nlsat::literal(process_bvar(f), false); + } + } + + nlsat::literal process_literal(expr * f) { + bool neg = false; + while (m.is_not(f, f)) + neg = !neg; + nlsat::literal l = process_atom(f); + if (neg) + l.neg(); + return l; + } + + void process(expr * f, expr_dependency * dep) { + unsigned num_lits; + expr * const * lits; + if (m.is_or(f)) { + num_lits = to_app(f)->get_num_args(); + lits = to_app(f)->get_args(); + } + else { + num_lits = 1; + lits = &f; + } + sbuffer ls; + for (unsigned i = 0; i < num_lits; i++) { + ls.push_back(process_literal(lits[i])); + } + m_solver.mk_clause(ls.size(), ls.c_ptr(), dep); + } + + void operator()(goal const & g) { + if (has_term_ite(g)) + throw tactic_exception("eliminate term-ite before applying nlsat"); + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + process(g.form(i), g.dep(i)); + } + } +}; + +struct goal2nlsat::scoped_set_imp { + goal2nlsat & m_owner; + scoped_set_imp(goal2nlsat & o, imp & i):m_owner(o) { + #pragma omp critical (tactic_cancel) + { + m_owner.m_imp = &i; + } + } + + ~scoped_set_imp() { + #pragma omp critical (tactic_cancel) + { + m_owner.m_imp = 0; + } + } +}; + +goal2nlsat::goal2nlsat() { + m_imp = 0; +} + +goal2nlsat::~goal2nlsat() { + SASSERT(m_imp == 0); +} + +void goal2nlsat::collect_param_descrs(param_descrs & r) { + insert_max_memory(r); + r.insert(":factor", CPK_BOOL, "(default: true) factor polynomials."); + polynomial::factor_params::get_param_descrs(r); +} + +void goal2nlsat::operator()(goal const & g, params_ref const & p, nlsat::solver & s, expr2var & a2b, expr2var & t2x) { + imp local_imp(g.m(), p, s, a2b, t2x); + scoped_set_imp setter(*this, local_imp); + local_imp(g); +} + +void goal2nlsat::set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); +} diff --git a/lib/goal2nlsat.h b/lib/goal2nlsat.h new file mode 100644 index 000000000..ad84ef9b3 --- /dev/null +++ b/lib/goal2nlsat.h @@ -0,0 +1,75 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + goal2nlsat.h + +Abstract: + + "Compile" a goal into the nonlinear arithmetic engine. + Non-arithmetic atoms are "abstracted" into boolean variables. + Non-supported terms are "abstracted" into variables. + + The mappings can be used to convert back the state of the + engine into a goal. + +Author: + + Leonardo (leonardo) 2012-01-02 + +Notes: + +--*/ +#ifndef _GOAL2NLSAT_H_ +#define _GOAL2NLSAT_H_ + +#include"nlsat_types.h" +#include"model_converter.h" + +class goal; +class expr2var; + +class goal2nlsat { + struct imp; + imp * m_imp; + struct scoped_set_imp; +public: + goal2nlsat(); + ~goal2nlsat(); + + static void collect_param_descrs(param_descrs & r); + + /** + \brief "Compile" the goal into the given nlsat engine. + Store a mapping from atoms to boolean variables into a2b. + Store a mapping from terms into arithmetic variables into t2x. + + \remark a2b and t2x m don't need to be empty. The definitions there are reused. + + The input is expected to be in CNF + */ + void operator()(goal const & g, params_ref const & p, nlsat::solver & s, expr2var & a2b, expr2var & t2x); + + void set_cancel(bool f); +}; + +class nlsat2goal { + struct imp; + imp * m_imp; +public: + nlsat2goal(); + ~nlsat2goal(); + + static void collect_param_descrs(param_descrs & r); + + /** + \brief Translate the state of the nlsat engine back into a goal. + */ + void operator()(nlsat::solver const & s, expr2var const & a2b, expr2var const & t2x, + params_ref const & p, goal & g, model_converter_ref & mc); + + void set_cancel(bool f); +}; + +#endif diff --git a/lib/goal2sat.cpp b/lib/goal2sat.cpp new file mode 100644 index 000000000..b6c2e39cf --- /dev/null +++ b/lib/goal2sat.cpp @@ -0,0 +1,711 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + goal2sat.cpp + +Abstract: + + "Compile" a goal into the SAT engine. + Atoms are "abstracted" into boolean variables. + The mapping between boolean variables and atoms + can be used to convert back the state of the + SAT engine into a goal. + + The idea is to support scenarios such as: + 1) simplify, blast, convert into SAT, and solve + 2) convert into SAT, apply SAT for a while, learn new units, and translate back into a goal. + 3) convert into SAT, apply SAT preprocessor (failed literal propagation, resolution, etc) and translate back into a goal. + 4) Convert boolean structure into SAT, convert atoms into another engine, combine engines using lazy combination, solve. + +Author: + + Leonardo (leonardo) 2011-10-26 + +Notes: + +--*/ +#include"goal2sat.h" +#include"ast_smt2_pp.h" +#include"ref_util.h" +#include"cooperate.h" +#include"filter_model_converter.h" +#include"model_evaluator.h" +#include"for_each_expr.h" +#include"model_v2_pp.h" +#include"tactic.h" + +struct goal2sat::imp { + struct frame { + app * m_t; + unsigned m_root:1; + unsigned m_sign:1; + unsigned m_idx; + frame(app * t, bool r, bool s, unsigned idx): + m_t(t), m_root(r), m_sign(s), m_idx(idx) {} + }; + ast_manager & m; + svector m_frame_stack; + svector m_result_stack; + obj_map m_cache; + obj_hashtable m_interface_vars; + sat::solver & m_solver; + atom2bool_var & m_map; + sat::bool_var m_true; + bool m_ite_extra; + unsigned long long m_max_memory; + volatile bool m_cancel; + + imp(ast_manager & _m, params_ref const & p, sat::solver & s, atom2bool_var & map): + m(_m), + m_solver(s), + m_map(map) { + updt_params(p); + m_cancel = false; + m_true = sat::null_bool_var; + } + + void updt_params(params_ref const & p) { + m_ite_extra = p.get_bool(":ite-extra", true); + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + } + + void throw_op_not_handled() { + throw tactic_exception("operator not supported, apply simplifier before invoking translator"); + } + + void mk_clause(sat::literal l) { + TRACE("goal2sat", tout << "mk_clause: " << l << "\n";); + m_solver.mk_clause(1, &l); + } + + void mk_clause(sat::literal l1, sat::literal l2) { + TRACE("goal2sat", tout << "mk_clause: " << l1 << " " << l2 << "\n";); + m_solver.mk_clause(l1, l2); + } + + void mk_clause(sat::literal l1, sat::literal l2, sat::literal l3) { + TRACE("goal2sat", tout << "mk_clause: " << l1 << " " << l2 << " " << l3 << "\n";); + m_solver.mk_clause(l1, l2, l3); + } + + void mk_clause(unsigned num, sat::literal * lits) { + TRACE("goal2sat", tout << "mk_clause: "; for (unsigned i = 0; i < num; i++) tout << lits[i] << " "; tout << "\n";); + m_solver.mk_clause(num, lits); + } + + sat::bool_var mk_true() { + // create fake variable to represent true; + if (m_true == sat::null_bool_var) { + m_true = m_solver.mk_var(); + mk_clause(sat::literal(m_true, false)); // v is true + } + return m_true; + } + + void convert_atom(expr * t, bool root, bool sign) { + SASSERT(m.is_bool(t)); + sat::literal l; + sat::bool_var v = m_map.to_bool_var(t); + if (v == sat::null_bool_var) { + if (m.is_true(t)) { + l = sat::literal(mk_true(), sign); + } + else if (m.is_false(t)) { + l = sat::literal(mk_true(), !sign); + } + else { + bool ext = !is_uninterp_const(t) || m_interface_vars.contains(t); + sat::bool_var v = m_solver.mk_var(ext); + m_map.insert(t, v); + l = sat::literal(v, sign); + TRACE("goal2sat", tout << "new_var: " << v << "\n" << mk_ismt2_pp(t, m) << "\n";); + } + } + else { + SASSERT(v != sat::null_bool_var); + l = sat::literal(v, sign); + } + SASSERT(l != sat::null_literal); + if (root) + mk_clause(l); + else + m_result_stack.push_back(l); + } + + bool process_cached(app * t, bool root, bool sign) { + sat::literal l; + if (m_cache.find(t, l)) { + if (sign) + l.neg(); + if (root) + mk_clause(l); + else + m_result_stack.push_back(l); + return true; + } + return false; + } + + bool visit(expr * t, bool root, bool sign) { + if (!is_app(t)) { + convert_atom(t, root, sign); + return true; + } + if (process_cached(to_app(t), root, sign)) + return true; + if (to_app(t)->get_family_id() != m.get_basic_family_id()) { + convert_atom(t, root, sign); + return true; + } + switch (to_app(t)->get_decl_kind()) { + case OP_NOT: + case OP_OR: + case OP_IFF: + m_frame_stack.push_back(frame(to_app(t), root, sign, 0)); + return false; + case OP_ITE: + case OP_EQ: + if (m.is_bool(to_app(t)->get_arg(1))) { + m_frame_stack.push_back(frame(to_app(t), root, sign, 0)); + return false; + } + convert_atom(t, root, sign); + return true; + case OP_AND: + case OP_XOR: + case OP_IMPLIES: + case OP_DISTINCT: + TRACE("goal2sat_not_handled", tout << mk_ismt2_pp(t, m) << "\n";); + throw_op_not_handled(); + default: + convert_atom(t, root, sign); + return true; + } + } + + void convert_or(app * t, bool root, bool sign) { + TRACE("goal2sat", tout << "convert_or:\n" << mk_ismt2_pp(t, m) << "\n";); + unsigned num = t->get_num_args(); + if (root) { + SASSERT(num == m_result_stack.size()); + if (sign) { + // this case should not really happen. + for (unsigned i = 0; i < num; i++) { + sat::literal l = m_result_stack[i]; + l.neg(); + mk_clause(l); + } + } + else { + mk_clause(m_result_stack.size(), m_result_stack.c_ptr()); + m_result_stack.reset(); + } + } + else { + SASSERT(num <= m_result_stack.size()); + sat::bool_var k = m_solver.mk_var(); + sat::literal l(k, false); + m_cache.insert(t, l); + sat::literal * lits = m_result_stack.end() - num; + for (unsigned i = 0; i < num; i++) { + mk_clause(~lits[i], l); + } + m_result_stack.push_back(~l); + lits = m_result_stack.end() - num - 1; + // remark: mk_clause may perform destructive updated to lits. + // I have to execute it after the binary mk_clause above. + mk_clause(num+1, lits); + unsigned old_sz = m_result_stack.size() - num - 1; + m_result_stack.shrink(old_sz); + if (sign) + l.neg(); + m_result_stack.push_back(l); + } + } + + void convert_ite(app * n, bool root, bool sign) { + unsigned sz = m_result_stack.size(); + SASSERT(sz >= 3); + sat::literal c = m_result_stack[sz-3]; + sat::literal t = m_result_stack[sz-2]; + sat::literal e = m_result_stack[sz-1]; + if (root) { + SASSERT(sz == 3); + if (sign) { + mk_clause(~c, ~t); + mk_clause(c, ~e); + } + else { + mk_clause(~c, t); + mk_clause(c, e); + } + m_result_stack.reset(); + } + else { + sat::bool_var k = m_solver.mk_var(); + sat::literal l(k, false); + m_cache.insert(n, l); + mk_clause(~l, ~c, t); + mk_clause(~l, c, e); + mk_clause(l, ~c, ~t); + mk_clause(l, c, ~e); + if (m_ite_extra) { + mk_clause(~t, ~e, l); + mk_clause(t, e, ~l); + } + m_result_stack.shrink(sz-3); + if (sign) + l.neg(); + m_result_stack.push_back(l); + } + } + + void convert_iff(app * t, bool root, bool sign) { + TRACE("goal2sat", tout << "convert_iff " << root << " " << sign << "\n" << mk_ismt2_pp(t, m) << "\n";); + unsigned sz = m_result_stack.size(); + SASSERT(sz >= 2); + sat::literal l1 = m_result_stack[sz-1]; + sat::literal l2 = m_result_stack[sz-2]; + if (root) { + SASSERT(sz == 2); + if (sign) { + mk_clause(l1, l2); + mk_clause(~l1, ~l2); + } + else { + mk_clause(l1, ~l2); + mk_clause(~l1, l2); + } + m_result_stack.reset(); + } + else { + sat::bool_var k = m_solver.mk_var(); + sat::literal l(k, false); + m_cache.insert(t, l); + mk_clause(~l, l1, ~l2); + mk_clause(~l, ~l1, l2); + mk_clause(l, l1, l2); + mk_clause(l, ~l1, ~l2); + m_result_stack.shrink(sz-2); + if (sign) + l.neg(); + m_result_stack.push_back(l); + } + } + + void convert(app * t, bool root, bool sign) { + SASSERT(t->get_family_id() == m.get_basic_family_id()); + switch (to_app(t)->get_decl_kind()) { + case OP_OR: + convert_or(t, root, sign); + break; + case OP_ITE: + convert_ite(t, root, sign); + break; + case OP_IFF: + case OP_EQ: + convert_iff(t, root, sign); + break; + default: + UNREACHABLE(); + } + } + + void process(expr * n) { + TRACE("goal2sat", tout << "converting: " << mk_ismt2_pp(n, m) << "\n";); + if (visit(n, true, false)) { + SASSERT(m_result_stack.empty()); + return; + } + while (!m_frame_stack.empty()) { + loop: + cooperate("goal2sat"); + if (m_cancel) + throw tactic_exception(TACTIC_CANCELED_MSG); + if (memory::get_allocation_size() > m_max_memory) + throw tactic_exception(TACTIC_MAX_MEMORY_MSG); + frame & fr = m_frame_stack.back(); + app * t = fr.m_t; + bool root = fr.m_root; + bool sign = fr.m_sign; + TRACE("goal2sat_bug", tout << "result stack\n"; + tout << mk_ismt2_pp(t, m) << " root: " << root << " sign: " << sign << "\n"; + for (unsigned i = 0; i < m_result_stack.size(); i++) tout << m_result_stack[i] << " "; + tout << "\n";); + if (fr.m_idx == 0 && process_cached(t, root, sign)) { + m_frame_stack.pop_back(); + continue; + } + if (m.is_not(t)) { + m_frame_stack.pop_back(); + visit(t->get_arg(0), root, !sign); + continue; + } + unsigned num = t->get_num_args(); + while (fr.m_idx < num) { + expr * arg = t->get_arg(fr.m_idx); + fr.m_idx++; + if (!visit(arg, false, false)) + goto loop; + } + TRACE("goal2sat_bug", tout << "converting\n"; + tout << mk_ismt2_pp(t, m) << " root: " << root << " sign: " << sign << "\n"; + for (unsigned i = 0; i < m_result_stack.size(); i++) tout << m_result_stack[i] << " "; + tout << "\n";); + convert(t, root, sign); + m_frame_stack.pop_back(); + } + SASSERT(m_result_stack.empty()); + } + + + void operator()(goal const & g) { + m_interface_vars.reset(); + collect_boolean_interface(g, m_interface_vars); + + unsigned size = g.size(); + for (unsigned idx = 0; idx < size; idx++) { + expr * f = g.form(idx); + process(f); + } + } + + void operator()(unsigned sz, expr * const * fs) { + m_interface_vars.reset(); + collect_boolean_interface(m, sz, fs, m_interface_vars); + + for (unsigned i = 0; i < sz; i++) + process(fs[i]); + } + + void set_cancel(bool f) { m_cancel = f; } +}; + +struct unsupported_bool_proc { + struct found {}; + ast_manager & m; + unsupported_bool_proc(ast_manager & _m):m(_m) {} + void operator()(var *) {} + void operator()(quantifier *) {} + void operator()(app * n) { + if (n->get_family_id() == m.get_basic_family_id()) { + switch (n->get_decl_kind()) { + case OP_AND: + case OP_XOR: + case OP_IMPLIES: + case OP_DISTINCT: + throw found(); + default: + break; + } + } + } +}; + +/** + \brief Return true if s contains an unsupported Boolean operator. + goal_rewriter (with the following configuration) can be used to + eliminate unsupported operators. + :elim-and true + :blast-distinct true +*/ +bool goal2sat::has_unsupported_bool(goal const & g) { + return test(g); +} + +goal2sat::goal2sat():m_imp(0) { +} + +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"); +} + +struct goal2sat::scoped_set_imp { + goal2sat * m_owner; + scoped_set_imp(goal2sat * o, goal2sat::imp * i):m_owner(o) { + #pragma omp critical (goal2sat) + { + m_owner->m_imp = i; + } + } + ~scoped_set_imp() { + #pragma omp critical (goal2sat) + { + m_owner->m_imp = 0; + } + } +}; + +void goal2sat::operator()(goal const & g, params_ref const & p, sat::solver & t, atom2bool_var & m) { + imp proc(g.m(), p, t, m); + scoped_set_imp set(this, &proc); + proc(g); +} + +void goal2sat::set_cancel(bool f) { + #pragma omp critical (goal2sat) + { + if (m_imp) + m_imp->set_cancel(f); + } +} + +struct sat2goal::imp { + + // Wrapper for sat::model_converter: converts it into an "AST level" model_converter. + class sat_model_converter : public model_converter { + sat::model_converter m_mc; + // TODO: the following mapping is storing a lot of useless information, and may be a performance bottleneck. + // We need to save only the expressions associated with variables that occur in m_mc. + // This information may be stored as a vector of pairs. + // The mapping is only created during the model conversion. + expr_ref_vector m_var2expr; + ref m_fmc; // filter for eliminating fresh variables introduced in the assertion-set --> sat conversion + + sat_model_converter(ast_manager & m): + m_var2expr(m) { + } + + public: + sat_model_converter(ast_manager & m, sat::solver const & s):m_var2expr(m) { + m_mc.copy(s.get_model_converter()); + m_fmc = alloc(filter_model_converter, m); + } + + ast_manager & m() { return m_var2expr.get_manager(); } + + void insert(expr * atom, bool aux) { + m_var2expr.push_back(atom); + if (aux) { + SASSERT(is_uninterp_const(atom)); + SASSERT(m().is_bool(atom)); + m_fmc->insert(to_app(atom)->get_decl()); + } + } + + virtual void operator()(model_ref & md, unsigned goal_idx) { + SASSERT(goal_idx == 0); + TRACE("sat_mc", tout << "before sat_mc\n"; model_v2_pp(tout, *md); display(tout);); + // REMARK: potential problem + // model_evaluator can't evaluate quantifiers. Then, + // an eliminated variable that depends on a quantified expression can't be recovered. + // A similar problem also affects any model_converter that uses elim_var_model_converter. + // + // Possible solution: + // model_converters reject any variable elimination that depends on a quantified expression. + + model_evaluator ev(*md); + ev.set_model_completion(false); + + // create a SAT model using md + sat::model sat_md; + unsigned sz = m_var2expr.size(); + expr_ref val(m()); + for (sat::bool_var v = 0; v < sz; v++) { + expr * atom = m_var2expr.get(v); + ev(atom, val); + if (m().is_true(val)) + sat_md.push_back(l_true); + else if (m().is_false(val)) + sat_md.push_back(l_false); + else + sat_md.push_back(l_undef); + } + + // apply SAT model converter + m_mc(sat_md); + + // register value of non-auxiliary boolean variables back into md + sz = m_var2expr.size(); + for (sat::bool_var v = 0; v < sz; v++) { + expr * atom = m_var2expr.get(v); + if (is_uninterp_const(atom)) { + func_decl * d = to_app(atom)->get_decl(); + lbool new_val = sat_md[v]; + if (new_val == l_true) + md->register_decl(d, m().mk_true()); + else if (new_val == l_false) + md->register_decl(d, m().mk_false()); + } + } + + // apply filter model converter + (*m_fmc)(md); + TRACE("sat_mc", tout << "after sat_mc\n"; model_v2_pp(tout, *md);); + } + + virtual model_converter * translate(ast_translation & translator) { + sat_model_converter * res = alloc(sat_model_converter, translator.to()); + res->m_fmc = static_cast(m_fmc->translate(translator)); + unsigned sz = m_var2expr.size(); + for (unsigned i = 0; i < sz; i++) + res->m_var2expr.push_back(translator(m_var2expr.get(i))); + return res; + } + + void display(std::ostream & out) { + out << "(sat-model-converter\n"; + m_mc.display(out); + sat::bool_var_set vars; + m_mc.collect_vars(vars); + out << "(atoms"; + unsigned sz = m_var2expr.size(); + for (unsigned i = 0; i < sz; i++) { + if (vars.contains(i)) { + out << "\n (" << i << "\n " << mk_ismt2_pp(m_var2expr.get(i), m(), 2) << ")"; + } + } + out << ")\n"; + m_fmc->display(out); + out << ")\n"; + } + }; + + ast_manager & m; + expr_ref_vector m_lit2expr; + unsigned long long m_max_memory; + bool m_learned; + volatile bool m_cancel; + + imp(ast_manager & _m, params_ref const & p):m(_m), m_lit2expr(m), m_cancel(false) { + updt_params(p); + } + + void updt_params(params_ref const & p) { + m_learned = p.get_bool(":learned", false); + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + } + + void checkpoint() { + if (m_cancel) + throw tactic_exception(TACTIC_CANCELED_MSG); + if (memory::get_allocation_size() > m_max_memory) + throw tactic_exception(TACTIC_MAX_MEMORY_MSG); + } + + void init_lit2expr(sat::solver const & s, atom2bool_var const & map, model_converter_ref & mc, bool produce_models) { + ref _mc; + if (produce_models) + _mc = alloc(sat_model_converter, m, s); + unsigned num_vars = s.num_vars(); + m_lit2expr.resize(num_vars * 2); + map.mk_inv(m_lit2expr); + sort * b = m.mk_bool_sort(); + for (sat::bool_var v = 0; v < num_vars; v++) { + checkpoint(); + sat::literal l(v, false); + if (m_lit2expr.get(l.index()) == 0) { + SASSERT(m_lit2expr.get((~l).index()) == 0); + app * aux = m.mk_fresh_const(0, b); + if (_mc) + _mc->insert(aux, true); + m_lit2expr.set(l.index(), aux); + m_lit2expr.set((~l).index(), m.mk_not(aux)); + } + else { + if (_mc) + _mc->insert(m_lit2expr.get(l.index()), false); + SASSERT(m_lit2expr.get((~l).index()) != 0); + } + } + mc = _mc.get(); + } + + expr * lit2expr(sat::literal l) { + return m_lit2expr.get(l.index()); + } + + void assert_clauses(sat::clause * const * begin, sat::clause * const * end, goal & r) { + ptr_buffer lits; + for (sat::clause * const * it = begin; it != end; it++) { + checkpoint(); + lits.reset(); + sat::clause const & c = *(*it); + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) { + lits.push_back(lit2expr(c[i])); + } + r.assert_expr(m.mk_or(lits.size(), lits.c_ptr())); + } + } + + void operator()(sat::solver const & s, atom2bool_var const & map, goal & r, model_converter_ref & mc) { + if (s.inconsistent()) { + r.assert_expr(m.mk_false()); + return; + } + init_lit2expr(s, map, mc, r.models_enabled()); + // collect units + unsigned num_vars = s.num_vars(); + for (sat::bool_var v = 0; v < num_vars; v++) { + checkpoint(); + switch (s.value(v)) { + case l_true: + r.assert_expr(lit2expr(sat::literal(v, false))); + break; + case l_false: + r.assert_expr(lit2expr(sat::literal(v, true))); + break; + case l_undef: + break; + } + } + // collect binary clauses + svector bin_clauses; + s.collect_bin_clauses(bin_clauses, m_learned); + svector::iterator it = bin_clauses.begin(); + svector::iterator end = bin_clauses.end(); + for (; it != end; ++it) { + checkpoint(); + r.assert_expr(m.mk_or(lit2expr(it->first), lit2expr(it->second))); + } + // collect clauses + assert_clauses(s.begin_clauses(), s.end_clauses(), r); + if (m_learned) + assert_clauses(s.begin_learned(), s.end_learned(), r); + } + + void set_cancel(bool f) { m_cancel = f; } +}; + +sat2goal::sat2goal():m_imp(0) { +} + +void sat2goal::collect_param_descrs(param_descrs & r) { + insert_max_memory(r); + r.insert(":learned", CPK_BOOL, "(default: false) collect also learned clauses."); +} + +struct sat2goal::scoped_set_imp { + sat2goal * m_owner; + scoped_set_imp(sat2goal * o, sat2goal::imp * i):m_owner(o) { + #pragma omp critical (sat2goal) + { + m_owner->m_imp = i; + } + } + ~scoped_set_imp() { + #pragma omp critical (sat2goal) + { + m_owner->m_imp = 0; + } + } +}; + +void sat2goal::operator()(sat::solver const & t, atom2bool_var const & m, params_ref const & p, + goal & g, model_converter_ref & mc) { + imp proc(g.m(), p); + scoped_set_imp set(this, &proc); + proc(t, m, g, mc); +} + +void sat2goal::set_cancel(bool f) { + #pragma omp critical (sat2goal) + { + if (m_imp) + m_imp->set_cancel(f); + } +} diff --git a/lib/goal2sat.h b/lib/goal2sat.h new file mode 100644 index 000000000..7e38a1ca5 --- /dev/null +++ b/lib/goal2sat.h @@ -0,0 +1,86 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + goal2sat.h + +Abstract: + + "Compile" a goal into the SAT engine. + Atoms are "abstracted" into boolean variables. + The mapping between boolean variables and atoms + can be used to convert back the state of the + SAT engine into a goal. + + The idea is to support scenarios such as: + 1) simplify, blast, convert into SAT, and solve + 2) convert into SAT, apply SAT for a while, learn new units, and translate back into a goal. + 3) convert into SAT, apply SAT preprocessor (failed literal propagation, resolution, etc) and translate back into a goal. + 4) Convert boolean structure into SAT, convert atoms into another engine, combine engines using lazy combination, solve. + +Author: + + Leonardo (leonardo) 2011-10-26 + +Notes: + +--*/ +#ifndef _GOAL2SAT_H_ +#define _GOAL2SAT_H_ + +#include"goal.h" +#include"sat_solver.h" +#include"model_converter.h" +#include"atom2bool_var.h" + +class goal2sat { + struct imp; + imp * m_imp; + struct scoped_set_imp; +public: + goal2sat(); + + static void collect_param_descrs(param_descrs & r); + + static bool has_unsupported_bool(goal const & s); + + /** + \brief "Compile" the goal into the given sat solver. + Store a mapping from atoms to boolean variables into m. + + \remark m doesn't need to be empty. the definitions there are + reused. + + \warning conversion throws a tactic_exception, if it is interrupted (by set_cancel), + an unsupported operator is found, or memory consumption limit is reached (set with param :max-memory). + */ + void operator()(goal const & g, params_ref const & p, sat::solver & t, atom2bool_var & m); + + void set_cancel(bool f); +}; + + +class sat2goal { + struct imp; + imp * m_imp; + struct scoped_set_imp; +public: + sat2goal(); + + static void collect_param_descrs(param_descrs & r); + + /** + \brief Translate the state of the SAT engine back into a goal. + The SAT solver may use variables that are not in \c m. The translator + creates fresh boolean AST variables for them. They are stored in fvars. + + \warning conversion throws a tactic_exception, if it is interrupted (by set_cancel), + or memory consumption limit is reached (set with param :max-memory). + */ + void operator()(sat::solver const & t, atom2bool_var const & m, params_ref const & p, goal & s, model_converter_ref & mc); + + void set_cancel(bool f); +}; + +#endif diff --git a/lib/goal_shared_occs.cpp b/lib/goal_shared_occs.cpp new file mode 100644 index 000000000..48c79f739 --- /dev/null +++ b/lib/goal_shared_occs.cpp @@ -0,0 +1,29 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + goal_shared_occs.cpp + +Abstract: + + Functor for computing the set of shared occurrences in a goal. + +Author: + + Leonardo de Moura (leonardo) 2011-12-28 + +Revision History: + +--*/ +#include"goal_shared_occs.h" + +void goal_shared_occs::operator()(goal const & g) { + m_occs.reset(); + shared_occs_mark visited; + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + expr * t = g.form(i); + m_occs(t, visited); + } +} diff --git a/lib/goal_shared_occs.h b/lib/goal_shared_occs.h new file mode 100644 index 000000000..0d914cd8c --- /dev/null +++ b/lib/goal_shared_occs.h @@ -0,0 +1,45 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + goal_shared_occs.h + +Abstract: + + Functor for computing the set of shared occurrences in a goal. + +Author: + + Leonardo de Moura (leonardo) 2011-12-28 + +Revision History: + +--*/ +#ifndef _GOAL_SHARED_OCCS_H_ +#define _GOAL_SHARED_OCCS_H_ + +#include"goal.h" +#include"shared_occs.h" + +/** + \brief Functor for computing the set of shared occurrences in a goal. + + It is essentially a wrapper for shared_occs functor. +*/ +class goal_shared_occs { + shared_occs m_occs; +public: + goal_shared_occs(ast_manager & m, bool track_atomic = false, bool visit_quantifiers = true, bool visit_patterns = false): + m_occs(m, track_atomic, visit_quantifiers, visit_patterns) { + } + void operator()(goal const & s); + bool is_shared(expr * t) { return m_occs.is_shared(t); } + unsigned num_shared() const { return m_occs.num_shared(); } + void reset() { return m_occs.reset(); } + void cleanup() { return m_occs.cleanup(); } + void display(std::ostream & out, ast_manager & m) const { m_occs.display(out, m); } +}; + + +#endif diff --git a/lib/goal_util.cpp b/lib/goal_util.cpp new file mode 100644 index 000000000..d0dfc11f6 --- /dev/null +++ b/lib/goal_util.cpp @@ -0,0 +1,33 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + goal_util.cpp + +Abstract: + + goal goodies. + +Author: + + Leonardo de Moura (leonardo) 2012-01-03. + +Revision History: + +--*/ +#include"goal_util.h" +#include"goal.h" + +struct has_term_ite_functor { + struct found {}; + ast_manager & m; + has_term_ite_functor(ast_manager & _m):m(_m) {} + void operator()(var *) {} + void operator()(quantifier *) {} + void operator()(app * n) { if (m.is_term_ite(n)) throw found(); } +}; + +bool has_term_ite(goal const & g) { + return test(g); +} diff --git a/lib/goal_util.h b/lib/goal_util.h new file mode 100644 index 000000000..cda7e44e0 --- /dev/null +++ b/lib/goal_util.h @@ -0,0 +1,25 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + goal_util.h + +Abstract: + + goal goodies. + +Author: + + Leonardo de Moura (leonardo) 2012-01-03. + +Revision History: + +--*/ +#ifndef _GOAL_UTIL_H_ +#define _GOAL_UTIL_H_ + +class goal; +bool has_term_ite(goal const & g); + +#endif diff --git a/lib/grobner.cpp b/lib/grobner.cpp new file mode 100644 index 000000000..11bebf6c5 --- /dev/null +++ b/lib/grobner.cpp @@ -0,0 +1,961 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + grobner.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-12-04. + +Revision History: + +--*/ +#include"grobner.h" +#include"ast_pp.h" +#include"ref_util.h" + +// #define PROFILE_GB + +grobner::grobner(ast_manager & m, v_dependency_manager & d): + m_manager(m), + m_dep_manager(d), + m_util(m), + m_var_lt(m_var2weight), + m_monomial_lt(m_var_lt), + m_changed_leading_term(false), + m_unsat(0) { +} + +grobner::~grobner() { + flush(); +} + +void grobner::flush() { + dec_ref_map_keys(m_manager, m_var2weight); + del_equations(0); +} + +void grobner::del_equations(unsigned old_size) { + SASSERT(m_equations_to_delete.size() >= old_size); + equation_vector::iterator it = m_equations_to_delete.begin(); + equation_vector::iterator end = m_equations_to_delete.end(); + it += old_size; + for (; it != end; ++it) { + equation * eq = *it; + if (eq) + del_equation(eq); + } + m_equations_to_delete.shrink(old_size); +} + +void grobner::del_equation(equation * eq) { + m_processed.erase(eq); + m_to_process.erase(eq); + SASSERT(m_equations_to_delete[eq->m_bidx] == eq); + m_equations_to_delete[eq->m_bidx] = 0; + ptr_vector::iterator it1 = eq->m_monomials.begin(); + ptr_vector::iterator end1 = eq->m_monomials.end(); + for (; it1 != end1; ++it1) { + monomial * m = *it1; + del_monomial(m); + } + dealloc(eq); +} + +void grobner::del_monomial(monomial * m) { + ptr_vector::iterator it2 = m->m_vars.begin(); + ptr_vector::iterator end2 = m->m_vars.end(); + for (; it2 != end2; ++it2) { + expr * v = *it2; + m_manager.dec_ref(v); + } + dealloc(m); +} + +void grobner::unfreeze_equations(unsigned old_size) { + SASSERT(m_equations_to_unfreeze.size() >= old_size); + equation_vector::iterator it = m_equations_to_unfreeze.begin(); + equation_vector::iterator end = m_equations_to_unfreeze.end(); + it += old_size; + for (; it != end; ++it) { + equation * eq = *it; + m_to_process.insert(eq); + } + m_equations_to_unfreeze.shrink(old_size); +} + +void grobner::push_scope() { + m_scopes.push_back(scope()); + scope & s = m_scopes.back(); + s.m_equations_to_unfreeze_lim = m_equations_to_unfreeze.size(); + s.m_equations_to_delete_lim = m_equations_to_delete.size(); +} + +void grobner::pop_scope(unsigned num_scopes) { + SASSERT(num_scopes >= get_scope_level()); + unsigned new_lvl = get_scope_level() - num_scopes; + scope & s = m_scopes[new_lvl]; + unfreeze_equations(s.m_equations_to_unfreeze_lim); + del_equations(s.m_equations_to_delete_lim); + m_scopes.shrink(new_lvl); +} + +void grobner::reset() { + flush(); + m_processed.reset(); + m_to_process.reset(); + m_equations_to_unfreeze.reset(); + m_equations_to_delete.reset(); + m_unsat = 0; +} + +void grobner::display_var(std::ostream & out, expr * var) const { + if (is_app(var) && to_app(var)->get_num_args() > 0) + out << "#" << var->get_id(); + else + out << mk_pp(var, m_manager); +} + +void grobner::display_vars(std::ostream & out, unsigned num_vars, expr * const * vars) const { + for (unsigned i = 0; i < num_vars; i++) { + display_var(out, vars[i]); + out << " "; + } +} + +void grobner::display_monomial(std::ostream & out, monomial const & m) const { + if (!m.m_coeff.is_one() || m.m_vars.empty()) { + out << m.m_coeff; + if (!m.m_vars.empty()) + out << "*"; + } + + if (!m.m_vars.empty()) { + ptr_vector::const_iterator it = m.m_vars.begin(); + ptr_vector::const_iterator end = m.m_vars.end(); + unsigned power = 1; + expr * prev = *it; + it++; + for (; it != end; ++it) { + expr * curr = *it; + if (curr == prev) { + power++; + } + else { + display_var(out, prev); + if (power > 1) + out << "^" << power; + power = 1; + prev = curr; + out << "*"; + } + } + display_var(out, prev); + if (power > 1) + out << "^" << power; + } +} + +void grobner::display_monomials(std::ostream & out, unsigned num_monomials, monomial * const * monomials) const { + bool first = true; + for (unsigned i = 0; i < num_monomials; i++) { + monomial const * m = monomials[i]; + if (first) + first = false; + else + out << " + "; + display_monomial(out, *m); + } +} + +void grobner::display_equation(std::ostream & out, equation const & eq) const { + display_monomials(out, eq.m_monomials.size(), eq.m_monomials.c_ptr()); + out << " = 0\n"; +} + +void grobner::display_equations(std::ostream & out, equation_set const & v, char const * header) const { + if (!v.empty()) { + out << header << "\n"; + equation_set::iterator it = v.begin(); + equation_set::iterator end = v.end(); + for (; it != end; ++it) + display_equation(out, *(*it)); + } +} + +void grobner::display(std::ostream & out) const { + display_equations(out, m_processed, "processed:"); + display_equations(out, m_to_process, "to process:"); +} + +void grobner::set_weight(expr * n, int weight) { + if (weight == 0) + return; + if (!m_var2weight.contains(n)) + m_manager.inc_ref(n); + m_var2weight.insert(n, weight); +} + +/** + \brief Update equation using the new variable order. + Return true if the leading term was modified. +*/ +bool grobner::update_order(equation * eq) { + if (eq->get_num_monomials() == 0) + return false; + monomial * first = eq->m_monomials[0]; + ptr_vector::iterator it = eq->m_monomials.begin(); + ptr_vector::iterator end = eq->m_monomials.end(); + for (; it != end; ++it) { + monomial * m = *it; + std::stable_sort(m->m_vars.begin(), m->m_vars.end(), m_var_lt); + } + std::stable_sort(eq->m_monomials.begin(), eq->m_monomials.end(), m_monomial_lt); + return eq->m_monomials[0] != first; +} + +void grobner::update_order(equation_set & s, bool processed) { + ptr_buffer to_remove; + equation_set::iterator it = s.begin(); + equation_set::iterator end = s.end(); + for (;it != end; ++it) { + equation * eq = *it; + if (update_order(eq)) { + if (processed) { + to_remove.push_back(eq); + m_to_process.insert(eq); + } + } + } + ptr_buffer::iterator it2 = to_remove.begin(); + ptr_buffer::iterator end2 = to_remove.end(); + for (; it2 != end2; ++it2) + s.erase(*it2); +} + +void grobner::update_order() { + update_order(m_to_process, false); + update_order(m_processed, true); +} + +bool grobner::var_lt::operator()(expr * v1, expr * v2) const { + int w1 = 0; + int w2 = 0; + m_var2weight.find(v1, w1); + m_var2weight.find(v2, w2); + return (w1 > w2) || (w1 == w2 && v1->get_id() < v2->get_id()); +} + +bool grobner::monomial_lt::operator()(monomial * m1, monomial * m2) const { + // Using graded lex order. + if (m1->get_degree() > m2->get_degree()) + return true; + if (m1->get_degree() < m2->get_degree()) + return false; + ptr_vector::iterator it1 = m1->m_vars.begin(); + ptr_vector::iterator it2 = m2->m_vars.begin(); + ptr_vector::iterator end1 = m1->m_vars.end(); + for (; it1 != end1; ++it1, ++it2) { + expr * v1 = *it1; + expr * v2 = *it2; + if (m_var_lt(v1, v2)) + return true; + if (v1 != v2) + return false; + } + return false; +} + +inline void grobner::add_var(monomial * m, expr * v) { + SASSERT(!m_util.is_numeral(v)); + m_manager.inc_ref(v); + m->m_vars.push_back(v); +} + +grobner::monomial * grobner::mk_monomial(rational const & coeff, unsigned num_vars, expr * const * vars) { + monomial * r = alloc(monomial); + r->m_coeff = coeff; + for (unsigned i = 0; i < num_vars; i++) + add_var(r, vars[i]); + std::stable_sort(r->m_vars.begin(), r->m_vars.end(), m_var_lt); + return r; +} + +grobner::monomial * grobner::mk_monomial(rational const & coeff, expr * m) { + monomial * r = alloc(monomial); + if (m_util.is_numeral(m, r->m_coeff)) { + r->m_coeff *= coeff; + return r; + } + if (m_util.is_mul(m)) { + expr * body = m; + SASSERT(!m_util.is_numeral(to_app(m)->get_arg(1))); // monomial is in normal form + if (m_util.is_numeral(to_app(m)->get_arg(0), r->m_coeff)) { + r->m_coeff *= coeff; + body = to_app(m)->get_arg(1); + } + else { + r->m_coeff = coeff; + } + while (m_util.is_mul(body)) { + add_var(r, to_app(body)->get_arg(0)); + body = to_app(body)->get_arg(1); + } + add_var(r, body); + std::stable_sort(r->m_vars.begin(), r->m_vars.end(), m_var_lt); + } + else { + r->m_coeff = coeff; + r->m_vars.push_back(m); + m_manager.inc_ref(m); + } + return r; +} + +void grobner::init_equation(equation * eq, v_dependency * d) { + eq->m_scope_lvl = get_scope_level(); + unsigned bidx = m_equations_to_delete.size(); + eq->m_bidx = bidx; + eq->m_dep = d; + eq->m_lc = true; + m_equations_to_delete.push_back(eq); + SASSERT(m_equations_to_delete[eq->m_bidx] == eq); +} + +void grobner::assert_eq_0(unsigned num_monomials, monomial * const * monomials, v_dependency * ex) { + ptr_vector ms; + ms.append(num_monomials, monomials); + std::stable_sort(ms.begin(), ms.end(), m_monomial_lt); + merge_monomials(ms); + if (!ms.empty()) { + normalize_coeff(ms); + equation * eq = alloc(equation); + eq->m_monomials.swap(ms); + init_equation(eq, ex); + m_to_process.insert(eq); + } +} + +void grobner::assert_eq_0(unsigned num_monomials, rational const * coeffs, expr * const * monomials, v_dependency * ex) { +#define MK_EQ(COEFF) \ + ptr_vector ms; \ + for (unsigned i = 0; i < num_monomials; i++) \ + ms.push_back(mk_monomial(COEFF, monomials[i])); \ + std::stable_sort(ms.begin(), ms.end(), m_monomial_lt); \ + merge_monomials(ms); \ + if (!ms.empty()) { \ + equation * eq = alloc(equation); \ + normalize_coeff(ms); \ + eq->m_monomials.swap(ms); \ + init_equation(eq, ex); \ + m_to_process.insert(eq); \ + } + + MK_EQ(coeffs[i]); +} + +void grobner::assert_eq_0(unsigned num_monomials, expr * const * monomials, v_dependency * ex) { + rational one(1); + MK_EQ(one); +} + +void grobner::extract_monomials(expr * lhs, ptr_buffer & monomials) { + while (m_util.is_add(lhs)) { + SASSERT(!m_util.is_add(to_app(lhs)->get_arg(0))); + monomials.push_back(to_app(lhs)->get_arg(0)); + lhs = to_app(lhs)->get_arg(1); + } + monomials.push_back(lhs); +} + +void grobner::assert_eq(expr * eq, v_dependency * ex) { + SASSERT(m_manager.is_eq(eq)); + expr * lhs = to_app(eq)->get_arg(0); + expr * rhs = to_app(eq)->get_arg(1); + SASSERT(m_util.is_numeral(rhs)); + ptr_buffer monomials; + extract_monomials(lhs, monomials); + rational c; + bool is_int = false; + m_util.is_numeral(rhs, c, is_int); + expr_ref new_c(m_manager); + if (!c.is_zero()) { + c.neg(); + new_c = m_util.mk_numeral(c, is_int); + monomials.push_back(new_c); + } + assert_eq_0(monomials.size(), monomials.c_ptr(), ex); +} + +void grobner::assert_monomial_tautology(expr * m) { + equation * eq = alloc(equation); + eq->m_monomials.push_back(mk_monomial(rational(1), m)); + // create (quote m) + monomial * m1 = alloc(monomial); + m1->m_coeff = rational(-1); + m_manager.inc_ref(m); + m1->m_vars.push_back(m); + eq->m_monomials.push_back(m1); + normalize_coeff(eq->m_monomials); + init_equation(eq, static_cast(0)); \ + m_to_process.insert(eq); +} + +/** + \brief Return true if the body of m1 and m2 are equal +*/ +bool grobner::is_eq_monomial_body(monomial const * m1, monomial const * m2) { + if (m1->get_degree() != m2->get_degree()) + return false; + ptr_vector::const_iterator it1 = m1->m_vars.begin(); + ptr_vector::const_iterator it2 = m2->m_vars.begin(); + ptr_vector::const_iterator end1 = m1->m_vars.end(); + for (; it1 != end1; ++it1, ++it2) { + expr * v1 = *it1; + expr * v2 = *it2; + if (v1 != v2) + return false; + } + return true; +} + +/** + \brief Merge monomials (* c1 m) (* c2 m). + + \remark This method assumes the monomials are sorted. +*/ +void grobner::merge_monomials(ptr_vector & monomials) { + TRACE("grobner", tout << "before merging monomials:\n"; display_monomials(tout, monomials.size(), monomials.c_ptr()); tout << "\n";); + unsigned j = 0; + unsigned sz = monomials.size(); + if (sz == 0) + return; + for (unsigned i = 1; i < sz; ++i) { + monomial * m1 = monomials[j]; + monomial * m2 = monomials[i]; + if (is_eq_monomial_body(m1, m2)) { + m1->m_coeff += m2->m_coeff; + del_monomial(m2); + } + else { + if (m1->m_coeff.is_zero()) + del_monomial(m1); // cancelled + else + j++; + monomials[j] = m2; + } + } + SASSERT(j < sz); + monomial * m1 = monomials[j]; + if (m1->m_coeff.is_zero()) + del_monomial(m1); // cancelled + else + j++; + monomials.shrink(j); + TRACE("grobner", tout << "after merging monomials:\n"; display_monomials(tout, monomials.size(), monomials.c_ptr()); tout << "\n";); +} + +/** + \brief Divide the coefficients by the coefficient of the leading term. +*/ +void grobner::normalize_coeff(ptr_vector & monomials) { + if (monomials.empty()) + return; + rational c = monomials[0]->m_coeff; + if (c.is_one()) + return; + unsigned sz = monomials.size(); + for (unsigned i = 0; i < sz; i++) + monomials[i]->m_coeff /= c; +} + +/** + \brief Simplify the given monomials +*/ +void grobner::simplify(ptr_vector & monomials) { + std::stable_sort(monomials.begin(), monomials.end(), m_monomial_lt); + merge_monomials(monomials); + normalize_coeff(monomials); +} + +/** + \brief Return true if the equation is of the form k = 0, where k is a numeral different from zero. + + \remark This method assumes the equation is simplified. +*/ +inline bool grobner::is_inconsistent(equation * eq) const { + SASSERT(!(eq->m_monomials.size() == 1 && eq->m_monomials[0]->get_degree() == 0) || !eq->m_monomials[0]->m_coeff.is_zero()); + return eq->m_monomials.size() == 1 && eq->m_monomials[0]->get_degree() == 0; +} + +/** + \brief Return true if the equation is of the form 0 = 0. +*/ +inline bool grobner::is_trivial(equation * eq) const { + return eq->m_monomials.empty(); +} + +/** + \brief Sort monomials, and merge monomials with the same body. +*/ +void grobner::simplify(equation * eq) { + simplify(eq->m_monomials); + if (is_inconsistent(eq) && !m_unsat) + m_unsat = eq; +} + +/** + \brief Return true if monomial m1 is (* c1 M) and m2 is (* c2 M M'). + Store M' in rest. + + \remark This method assumes the variables of m1 and m2 are sorted. +*/ +bool grobner::is_subset(monomial const * m1, monomial const * m2, ptr_vector & rest) const { + unsigned i1 = 0; + unsigned i2 = 0; + unsigned sz1 = m1->m_vars.size(); + unsigned sz2 = m2->m_vars.size(); + if (sz1 <= sz2) { + while (true) { + if (i1 >= sz1) { + for (; i2 < sz2; i2++) + rest.push_back(m2->m_vars[i2]); + TRACE("grobner", + tout << "monomail: "; display_monomial(tout, *m1); tout << " is a subset of "; + display_monomial(tout, *m2); tout << "\n"; + tout << "rest: "; display_vars(tout, rest.size(), rest.c_ptr()); tout << "\n";); + return true; + } + if (i2 >= sz2) + break; + expr * var1 = m1->m_vars[i1]; + expr * var2 = m2->m_vars[i2]; + if (var1 == var2) { + i1++; + i2++; + continue; + } + if (m_var_lt(var2, var1)) { + i2++; + rest.push_back(var2); + continue; + } + SASSERT(m_var_lt(var1, var2)); + break; + } + } + // is not subset + TRACE("grobner", tout << "monomail: "; display_monomial(tout, *m1); tout << " is not a subset of "; + display_monomial(tout, *m2); tout << "\n";); + return false; +} + +/** + \brief Multiply the monomials of source starting at position start_idx by (coeff * vars), and store the resultant monomials + at result. +*/ +void grobner::mul_append(unsigned start_idx, equation const * source, rational const & coeff, ptr_vector const & vars, ptr_vector & result) { + unsigned sz = source->get_num_monomials(); + for (unsigned i = start_idx; i < sz; i++) { + monomial const * m = source->get_monomial(i); + monomial * new_m = alloc(monomial); + new_m->m_coeff = m->m_coeff; + new_m->m_coeff *= coeff; + new_m->m_vars.append(m->m_vars.size(), m->m_vars.c_ptr()); + new_m->m_vars.append(vars.size(), vars.c_ptr()); + ptr_vector::iterator it = new_m->m_vars.begin(); + ptr_vector::iterator end = new_m->m_vars.end(); + for (; it != end; ++it) + m_manager.inc_ref(*it); + std::stable_sort(new_m->m_vars.begin(), new_m->m_vars.end(), m_var_lt); + result.push_back(new_m); + } +} + +/** + \brief Copy the given monomial. +*/ +grobner::monomial * grobner::copy_monomial(monomial const * m) { + monomial * r = alloc(monomial); + r->m_coeff = m->m_coeff; + ptr_vector::const_iterator it = m->m_vars.begin(); + ptr_vector::const_iterator end = m->m_vars.end(); + for (; it != end; ++it) + add_var(r, *it); + return r; +} + +/** + \brief Copy the given equation. +*/ +grobner::equation * grobner::copy_equation(equation const * eq) { + equation * r = alloc(equation); + unsigned sz = eq->get_num_monomials(); + for (unsigned i = 0; i < sz; i++) + r->m_monomials.push_back(copy_monomial(eq->get_monomial(i))); + init_equation(r, eq->m_dep); + r->m_lc = eq->m_lc; + return r; +} + +/** + \brief Simplify the target equation using the source as a rewrite rule. + Return 0 if target was not simplified. + Return target if target was simplified but source->m_scope_lvl <= target->m_scope_lvl. + Return new_equation if source->m_scope_lvl > target->m_scope_lvl, moreover target is freezed, and new_equation contains the result. +*/ +grobner::equation * grobner::simplify(equation const * source, equation * target) { + TRACE("grobner", tout << "simplifying: "; display_equation(tout, *target); tout << "using: "; display_equation(tout, *source);); + if (source->get_num_monomials() == 0) + return 0; + m_stats.m_simplify++; + bool result = false; + bool simplified; + do { + simplified = false; + unsigned i = 0; + unsigned j = 0; + unsigned sz = target->m_monomials.size(); + monomial const * LT = source->get_monomial(0); + ptr_vector & new_monomials = m_tmp_monomials; + new_monomials.reset(); + ptr_vector & rest = m_tmp_vars1; + for (; i < sz; i++) { + monomial * curr = target->m_monomials[i]; + rest.reset(); + if (is_subset(LT, curr, rest)) { + if (i == 0) + m_changed_leading_term = true; + if (source->m_scope_lvl > target->m_scope_lvl) { + target = copy_equation(target); + SASSERT(target->m_scope_lvl >= source->m_scope_lvl); + } + if (!result) { + // first time that source is being applied. + target->m_dep = m_dep_manager.mk_join(target->m_dep, source->m_dep); + } + simplified = true; + result = true; + rational coeff = curr->m_coeff; + coeff /= LT->m_coeff; + coeff.neg(); + if (!rest.empty()) + target->m_lc = false; + mul_append(1, source, coeff, rest, new_monomials); + del_monomial(curr); + } + else { + target->m_monomials[j] = curr; + j++; + } + } + if (simplified) { + target->m_monomials.shrink(j); + target->m_monomials.append(new_monomials.size(), new_monomials.c_ptr()); + simplify(target); + } + } + while (simplified); + TRACE("grobner", tout << "result: "; display_equation(tout, *target);); + return result ? target : 0; +} + +/** + \brief Simplify given equation using processed equalities. + Return 0, if the equation was not simplified. + Return eq, if the equation was simplified using destructive updates. + Return new_eq otherwise. +*/ +grobner::equation * grobner::simplify_using_processed(equation * eq) { + bool result = false; + bool simplified; + TRACE("grobner", tout << "simplifying: "; display_equation(tout, *eq); tout << "using already processed equalities\n";); + do { + simplified = false; + equation_set::iterator it = m_processed.begin(); + equation_set::iterator end = m_processed.end(); + for (; it != end; ++it) { + equation const * p = *it; + equation * new_eq = simplify(p, eq); + if (new_eq) { + result = true; + simplified = true; + eq = new_eq; + } + } + } + while (simplified); + TRACE("grobner", tout << "simplification result: "; display_equation(tout, *eq);); + return result ? eq : 0; +} + +/** + \brief Return true if eq1 is a better than e2, for being the next equation to be processed. +*/ +bool grobner::is_better_choice(equation * eq1, equation * eq2) { + if (!eq2) + return true; + if (eq1->m_monomials.empty()) + return true; + if (eq2->m_monomials.empty()) + return false; + if (eq1->m_monomials[0]->get_degree() < eq2->m_monomials[0]->get_degree()) + return true; + if (eq1->m_monomials[0]->get_degree() > eq2->m_monomials[0]->get_degree()) + return false; + return eq1->m_monomials.size() < eq2->m_monomials.size(); +} + +/** + \brief Pick next unprocessed equation +*/ +grobner::equation * grobner::pick_next() { + equation * r = 0; + ptr_buffer to_delete; + equation_set::iterator it = m_to_process.begin(); + equation_set::iterator end = m_to_process.end(); + for (; it != end; ++it) { + equation * curr = *it; + if (is_trivial(curr)) + to_delete.push_back(curr); + else if (is_better_choice(curr, r)) + r = curr; + } + ptr_buffer::const_iterator it1 = to_delete.begin(); + ptr_buffer::const_iterator end1 = to_delete.end(); + for (; it1 != end1; ++it1) + del_equation(*it1); + if (r) + m_to_process.erase(r); + TRACE("grobner", tout << "selected equation: "; if (!r) tout << "\n"; else display_equation(tout, *r);); + return r; +} + +/** + \brief Use the given equation to simplify processed terms. +*/ +void grobner::simplify_processed(equation * eq) { + ptr_buffer to_insert; + ptr_buffer to_remove; + ptr_buffer to_delete; + equation_set::iterator it = m_processed.begin(); + equation_set::iterator end = m_processed.end(); + for (; it != end; ++it) { + equation * curr = *it; + m_changed_leading_term = false; + // if the leading term is simplified, then the equation has to be moved to m_to_process + equation * new_curr = simplify(eq, curr); + if (new_curr != 0) { + if (new_curr != curr) { + m_equations_to_unfreeze.push_back(curr); + to_remove.push_back(curr); + if (m_changed_leading_term) { + m_to_process.insert(new_curr); + to_remove.push_back(curr); + } + else { + to_insert.push_back(new_curr); + } + curr = new_curr; + } + else { + if (m_changed_leading_term) { + m_to_process.insert(curr); + to_remove.push_back(curr); + } + } + } + if (is_trivial(curr)) + to_delete.push_back(curr); + } + ptr_buffer::const_iterator it1 = to_insert.begin(); + ptr_buffer::const_iterator end1 = to_insert.end(); + for (; it1 != end1; ++it1) + m_processed.insert(*it1); + it1 = to_remove.begin(); + end1 = to_remove.end(); + for (; it1 != end1; ++it1) + m_processed.erase(*it1); + it1 = to_delete.begin(); + end1 = to_delete.end(); + for (; it1 != end1; ++it1) + del_equation(*it1); +} + +/** + \brief Use the given equation to simplify to-process terms. +*/ +void grobner::simplify_to_process(equation * eq) { + equation_set::iterator it = m_to_process.begin(); + equation_set::iterator end = m_to_process.end(); + ptr_buffer to_insert; + ptr_buffer to_remove; + ptr_buffer to_delete; + for (; it != end; ++it) { + equation * curr = *it; + equation * new_curr = simplify(eq, curr); + if (new_curr != 0 && new_curr != curr) { + m_equations_to_unfreeze.push_back(curr); + to_insert.push_back(new_curr); + to_remove.push_back(curr); + curr = new_curr; + } + if (is_trivial(curr)) + to_delete.push_back(curr); + } + ptr_buffer::const_iterator it1 = to_insert.begin(); + ptr_buffer::const_iterator end1 = to_insert.end(); + for (; it1 != end1; ++it1) + m_to_process.insert(*it1); + it1 = to_remove.begin(); + end1 = to_remove.end(); + for (; it1 != end1; ++it1) + m_to_process.erase(*it1); + it1 = to_delete.begin(); + end1 = to_delete.end(); + for (; it1 != end1; ++it1) + del_equation(*it1); +} + +/** + \brief If m1 = (* c M M1) and m2 = (* d M M2) and M is non empty, then return true and store M1 in rest1 and M2 in rest2. +*/ +bool grobner::unify(monomial const * m1, monomial const * m2, ptr_vector & rest1, ptr_vector & rest2) { + TRACE("grobner", tout << "unifying: "; display_monomial(tout, *m1); tout << " "; display_monomial(tout, *m2); tout << "\n";); + bool found_M = false; + unsigned i1 = 0; + unsigned i2 = 0; + unsigned sz1 = m1->m_vars.size(); + unsigned sz2 = m2->m_vars.size(); + while (true) { + if (i1 >= sz1) { + if (found_M) { + for (; i2 < sz2; i2++) + rest2.push_back(m2->m_vars[i2]); + return true; + } + return false; + } + if (i2 >= sz2) { + if (found_M) { + for (; i1 < sz1; i1++) + rest1.push_back(m1->m_vars[i1]); + return true; + } + return false; + } + expr * var1 = m1->m_vars[i1]; + expr * var2 = m2->m_vars[i2]; + if (var1 == var2) { + found_M = true; + i1++; + i2++; + } + else if (m_var_lt(var2, var1)) { + i2++; + rest2.push_back(var2); + } + else { + i1++; + rest1.push_back(var1); + } + } +} + +/** + \brief Superpose the given two equations. +*/ +void grobner::superpose(equation * eq1, equation * eq2) { + if (eq1->m_monomials.empty() || eq2->m_monomials.empty()) + return; + m_stats.m_superpose++; + ptr_vector & rest1 = m_tmp_vars1; + rest1.reset(); + ptr_vector & rest2 = m_tmp_vars2; + rest2.reset(); + if (unify(eq1->m_monomials[0], eq2->m_monomials[0], rest1, rest2)) { + TRACE("grobner", tout << "superposing:\n"; display_equation(tout, *eq1); display_equation(tout, *eq2); + tout << "rest1: "; display_vars(tout, rest1.size(), rest1.c_ptr()); tout << "\n"; + tout << "rest2: "; display_vars(tout, rest2.size(), rest2.c_ptr()); tout << "\n";); + ptr_vector & new_monomials = m_tmp_monomials; + new_monomials.reset(); + mul_append(1, eq1, eq2->m_monomials[0]->m_coeff, rest2, new_monomials); + rational c = eq1->m_monomials[0]->m_coeff; + c.neg(); + mul_append(1, eq2, c, rest1, new_monomials); + simplify(new_monomials); + TRACE("grobner", tout << "resulting monomials: "; display_monomials(tout, new_monomials.size(), new_monomials.c_ptr()); tout << "\n";); + if (new_monomials.empty()) + return; + m_num_new_equations++; + equation * new_eq = alloc(equation); + new_eq->m_monomials.swap(new_monomials); + init_equation(new_eq, m_dep_manager.mk_join(eq1->m_dep, eq2->m_dep)); + new_eq->m_lc = false; + m_to_process.insert(new_eq); + } +} + +/** + \brief Superpose the given equations with the equations in m_processed. +*/ +void grobner::superpose(equation * eq) { + equation_set::iterator it = m_processed.begin(); + equation_set::iterator end = m_processed.end(); + for (; it != end; ++it) { + equation * curr = *it; + superpose(eq, curr); + } +} + +bool grobner::compute_basis(unsigned threshold) { + m_stats.m_compute_basis++; + m_num_new_equations = 0; + while (m_num_new_equations < threshold) { + equation * eq = pick_next(); + if (!eq) + return true; + m_stats.m_num_processed++; +#ifdef PROFILE_GB + if (m_stats.m_num_processed % 100 == 0) { + verbose_stream() << "[grobner] " << m_processed.size() << " " << m_to_process.size() << "\n"; + } +#endif + equation * new_eq = simplify_using_processed(eq); + if (new_eq != 0 && eq != new_eq) { + // equation was updated using non destructive updates + m_equations_to_unfreeze.push_back(eq); + eq = new_eq; + } + simplify_processed(eq); + superpose(eq); + m_processed.insert(eq); + simplify_to_process(eq); + TRACE("grobner", tout << "end of iteration:\n"; display(tout);); + } + return false; +} + +void grobner::copy_to(equation_set const & s, ptr_vector & result) const { + equation_set::iterator it = s.begin(); + equation_set::iterator end = s.end(); + for (; it != end; ++it) + result.push_back(*it); +} + +/** + \brief Copy the equations in m_processed and m_to_process to result. + + \warning This equations can be deleted when compute_basis is invoked. +*/ +void grobner::get_equations(ptr_vector & result) const { + copy_to(m_processed, result); + copy_to(m_to_process, result); +} + diff --git a/lib/grobner.h b/lib/grobner.h new file mode 100644 index 000000000..d69a3ac0a --- /dev/null +++ b/lib/grobner.h @@ -0,0 +1,281 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + grobner.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-12-04. + +Revision History: + +--*/ +#ifndef _GROBNER_H_ +#define _GROBNER_H_ + +#include"ast.h" +#include"arith_decl_plugin.h" +#include"heap.h" +#include"obj_hashtable.h" +#include"region.h" +#include"dependency.h" + + +struct grobner_stats { + long m_simplify; long m_superpose; long m_compute_basis; long m_num_processed; + void reset() { memset(this, 0, sizeof(grobner_stats)); } + grobner_stats() { reset(); } +}; + + +/** + \brief Simple Grobner basis implementation with no indexing. +*/ +class grobner { +protected: + struct monomial_lt; +public: + grobner_stats m_stats; + class monomial { + rational m_coeff; + ptr_vector m_vars; //!< sorted variables + + friend class grobner; + friend struct monomial_lt; + + monomial() {} + public: + rational const & get_coeff() const { return m_coeff; } + unsigned get_degree() const { return m_vars.size(); } + unsigned get_size() const { return get_degree(); } + expr * get_var(unsigned idx) const { return m_vars[idx]; } + }; + + class equation { + unsigned m_scope_lvl; //!< scope level when this equation was created. + unsigned m_bidx:31; //!< position at m_equations_to_delete + unsigned m_lc:1; //!< true if equation if a linear combination of the input equations. + ptr_vector m_monomials; //!< sorted monomials + v_dependency * m_dep; //!< justification for the equality + friend class grobner; + equation() {} + public: + unsigned get_num_monomials() const { return m_monomials.size(); } + monomial const * get_monomial(unsigned idx) const { return m_monomials[idx]; } + monomial * const * get_monomials() const { return m_monomials.c_ptr(); } + v_dependency * get_dependency() const { return m_dep; } + unsigned hash() const { return m_bidx; } + bool is_linear_combination() const { return m_lc; } + }; + +protected: + static bool is_eq_monomial_body(monomial const * m1, monomial const * m2); + + struct var_lt { + obj_map & m_var2weight; + var_lt(obj_map & m):m_var2weight(m) {} + bool operator()(expr * v1, expr * v2) const; + }; + + struct monomial_lt { + var_lt & m_var_lt; + monomial_lt(var_lt & lt):m_var_lt(lt) {} + bool operator()(monomial * m1, monomial * m2) const; + }; + + typedef obj_hashtable equation_set; + typedef ptr_vector equation_vector; + + ast_manager & m_manager; + v_dependency_manager & m_dep_manager; + arith_util m_util; + obj_map m_var2weight; + var_lt m_var_lt; + monomial_lt m_monomial_lt; + equation_set m_processed; + equation_set m_to_process; + equation_vector m_equations_to_unfreeze; + equation_vector m_equations_to_delete; + bool m_changed_leading_term; // set to true, if the leading term was simplified. + equation * m_unsat; + struct scope { + unsigned m_equations_to_unfreeze_lim; + unsigned m_equations_to_delete_lim; + }; + svector m_scopes; + ptr_vector m_tmp_monomials; + ptr_vector m_tmp_vars1; + ptr_vector m_tmp_vars2; + unsigned m_num_new_equations; // temporary variable + + bool is_monomial_lt(monomial const & m1, monomial const & m2) const; + + void display_vars(std::ostream & out, unsigned num_vars, expr * const * vars) const; + + void display_var(std::ostream & out, expr * var) const; + + void display_monomials(std::ostream & out, unsigned num_monomials, monomial * const * monomials) const; + + void display_equations(std::ostream & out, equation_set const & v, char const * header) const; + + void del_equations(unsigned old_size); + + void unfreeze_equations(unsigned old_size); + + void del_equation(equation * eq); + + void flush(); + + bool update_order(equation * eq); + + void update_order(equation_set & s, bool processed); + + void add_var(monomial * m, expr * v); + + monomial * mk_monomial(rational const & coeff, expr * m); + + void init_equation(equation * eq, v_dependency * d); + + void extract_monomials(expr * lhs, ptr_buffer & monomials); + + void merge_monomials(ptr_vector & monomials); + + bool is_inconsistent(equation * eq) const; + + bool is_trivial(equation * eq) const; + + void normalize_coeff(ptr_vector & monomials); + + void simplify(ptr_vector & monomials); + + void simplify(equation * eq); + + bool is_subset(monomial const * m1, monomial const * m2, ptr_vector & rest) const; + + void mul_append(unsigned start_idx, equation const * source, rational const & coeff, ptr_vector const & vars, ptr_vector & result); + + monomial * copy_monomial(monomial const * m); + + equation * copy_equation(equation const * eq); + + equation * simplify(equation const * source, equation * target); + + equation * simplify_using_processed(equation * eq); + + bool is_better_choice(equation * eq1, equation * eq2); + + equation * pick_next(); + + void simplify_processed(equation * eq); + + void simplify_to_process(equation * eq); + + bool unify(monomial const * m1, monomial const * m2, ptr_vector & rest1, ptr_vector & rest2); + + void superpose(equation * eq1, equation * eq2); + + void superpose(equation * eq); + + void copy_to(equation_set const & s, ptr_vector & result) const; + +public: + grobner(ast_manager & m, v_dependency_manager & dep_m); + + ~grobner(); + + unsigned get_scope_level() const { return m_scopes.size(); } + + /** + \brief Set the weight of a term that is viewed as a variable by this module. + The weight is used to order monomials. If the weight is not set for a term t, then the + weight of t is assumed to be 0. + */ + void set_weight(expr * n, int weight); + + int get_weight(expr * n) const { int w = 0; m_var2weight.find(n, w); return w; } + + /** + \brief Update equations after set_weight was invoked once or more. + */ + void update_order(); + + /** + \brief Create a new monomial. The caller owns the monomial until it invokes assert_eq_0. + A monomial cannot be use to create several equations. + */ + monomial * mk_monomial(rational const & coeff, unsigned num_vars, expr * const * vars); + + void del_monomial(monomial * m); + + /** + \brief Assert the given equality. + This method assumes eq is simplified. + */ + void assert_eq(expr * eq, v_dependency * ex = 0); + + /** + \brief Assert the equality monomials[0] + ... + monomials[num_monomials - 1] = 0. + This method assumes the monomials were simplified. + */ + void assert_eq_0(unsigned num_monomials, expr * const * monomials, v_dependency * ex = 0); + + /** + \brief Assert the equality monomials[0] + ... + monomials[num_monomials - 1] = 0. + This method assumes the monomials were simplified. + */ + void assert_eq_0(unsigned num_monomials, monomial * const * monomials, v_dependency * ex = 0); + + /** + \brief Assert the equality coeffs[0] * monomials[0] + ... + coeffs[num_monomials-1] * monomials[num_monomials - 1] = 0. + This method assumes the monomials were simplified. + */ + void assert_eq_0(unsigned num_monomials, rational const * coeffs, expr * const * monomials, v_dependency * ex = 0); + + /** + \brief Assert the monomial tautology (quote (x_1 * ... * x_n)) - x_1 * ... * x_n = 0 + */ + void assert_monomial_tautology(expr * m); + + /** + \brief Compute Grobner basis. + Return true if the threshold was not reached. + */ + bool compute_basis(unsigned threshold); + + /** + \brief Return true if an inconsistency was detected. + */ + bool inconsistent() const { return m_unsat != 0; } + + /** + \brief Simplify the given expression using the equalities asserted + using assert_eq. Store the result in 'result'. + */ + void simplify(expr * n, expr_ref & result); + + /** + \brief Reset state. Remove all equalities asserted with assert_eq. + */ + void reset(); + + void get_equations(ptr_vector & result) const; + + void push_scope(); + + void pop_scope(unsigned num_scopes); + + void display_equation(std::ostream & out, equation const & eq) const; + + void display_monomial(std::ostream & out, monomial const & m) const; + + void display(std::ostream & out) const; +}; + +#endif /* _GROBNER_H_ */ + diff --git a/lib/has_free_vars.cpp b/lib/has_free_vars.cpp new file mode 100644 index 000000000..ba2c9eeb8 --- /dev/null +++ b/lib/has_free_vars.cpp @@ -0,0 +1,94 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + has_free_vars.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-23. + +Revision History: + +--*/ +#include"ast.h" +#include"expr_delta_pair.h" +#include"hashtable.h" + +class contains_vars { + typedef hashtable, default_eq > cache; + cache m_cache; + svector m_todo; + bool m_contains; + unsigned m_window; + + void visit(expr * n, unsigned delta, bool & visited) { + expr_delta_pair e(n, delta); + if (!m_cache.contains(e)) { + m_todo.push_back(e); + visited = false; + } + } + + bool visit_children(expr * n, unsigned delta) { + bool visited = true; + unsigned dw; + unsigned j; + switch (n->get_kind()) { + case AST_VAR: + dw = m_window <= UINT_MAX - delta ? m_window + delta : UINT_MAX; + if (to_var(n)->get_idx() >= delta && to_var(n)->get_idx() <= dw) + m_contains = true; + break; + case AST_APP: + j = to_app(n)->get_num_args(); + while (j > 0) { + --j; + visit(to_app(n)->get_arg(j), delta, visited); + } + break; + case AST_QUANTIFIER: + if (delta <= UINT_MAX - to_quantifier(n)->get_num_decls()) { + visit(to_quantifier(n)->get_expr(), delta + to_quantifier(n)->get_num_decls(), visited); + } + break; + default: + break; + } + return visited; + } + +public: + // return true if n contains a variable in the range [begin, end] + bool operator()(expr * n, unsigned begin = 0, unsigned end = UINT_MAX) { + m_contains = false; + m_window = end - begin; + m_todo.reset(); + m_cache.reset(); + m_todo.push_back(expr_delta_pair(n, begin)); + while (!m_todo.empty()) { + expr_delta_pair e = m_todo.back(); + if (visit_children(e.m_node, e.m_delta)) { + m_cache.insert(e); + m_todo.pop_back(); + } + if (m_contains) { + return true; + } + } + SASSERT(!m_contains); + return false; + } +}; + +bool has_free_vars(expr * n) { + contains_vars p; + return p(n); +} + + diff --git a/lib/has_free_vars.h b/lib/has_free_vars.h new file mode 100644 index 000000000..d3d856ca1 --- /dev/null +++ b/lib/has_free_vars.h @@ -0,0 +1,27 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + has_free_vars.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-23. + +Revision History: + +--*/ +#ifndef _HAS_FREE_VARS_H_ +#define _HAS_FREE_VARS_H_ + +class expr; + +bool has_free_vars(expr * n); + +#endif /* _HAS_FREE_VARS_H_ */ + diff --git a/lib/hash.cpp b/lib/hash.cpp new file mode 100644 index 000000000..8b7530376 --- /dev/null +++ b/lib/hash.cpp @@ -0,0 +1,85 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + hash.cpp + +Abstract: + + Basic hash computation support. + +Author: + + Leonardo de Moura (leonardo) 2006-09-11. + +Revision History: + +--*/ + +#include"debug.h" +#include"hash.h" + +// I'm using Bob Jenkin's hash function. +// http://burtleburtle.net/bob/hash/doobs.html +unsigned string_hash(const char * str, unsigned length, unsigned init_value) { + register unsigned a, b, c, len; + + /* Set up the internal state */ + len = length; + a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */ + c = init_value; /* the previous hash value */ + + /*---------------------------------------- handle most of the key */ + while (len >= 12) { + a += reinterpret_cast(str)[0]; + b += reinterpret_cast(str)[1]; + c += reinterpret_cast(str)[2]; + mix(a,b,c); + str += 12; len -= 12; + } + + /*------------------------------------- handle the last 11 bytes */ + c += length; + switch(len) { /* all the case statements fall through */ + case 11: + c+=((unsigned)str[10]<<24); + __fallthrough; + case 10: + c+=((unsigned)str[9]<<16); + __fallthrough; + case 9 : + c+=((unsigned)str[8]<<8); + __fallthrough; + /* the first byte of c is reserved for the length */ + case 8 : + b+=((unsigned)str[7]<<24); + __fallthrough; + case 7 : + b+=((unsigned)str[6]<<16); + __fallthrough; + case 6 : + b+=((unsigned)str[5]<<8); + __fallthrough; + case 5 : + b+=str[4]; + __fallthrough; + case 4 : + a+=((unsigned)str[3]<<24); + __fallthrough; + case 3 : + a+=((unsigned)str[2]<<16); + __fallthrough; + case 2 : + a+=((unsigned)str[1]<<8); + __fallthrough; + case 1 : + a+=str[0]; + __fallthrough; + /* case 0: nothing left to add */ + } + mix(a,b,c); + /*-------------------------------------------- report the result */ + return c; +} + diff --git a/lib/hash.h b/lib/hash.h new file mode 100644 index 000000000..581f8e4d2 --- /dev/null +++ b/lib/hash.h @@ -0,0 +1,249 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + hash.h + +Abstract: + + Basic hash computation support. + +Author: + + Leonardo de Moura (leonardo) 2006-09-11. + +Revision History: + +--*/ +#ifndef _HASH_H_ +#define _HASH_H_ + +#include + +#ifndef _WINDOWS +#ifndef __fallthrough +#define __fallthrough +#endif +#endif + +#define mix(a,b,c) \ +{ \ + a -= b; a -= c; a ^= (c>>13); \ + b -= c; b -= a; b ^= (a<<8); \ + c -= a; c -= b; c ^= (b>>13); \ + a -= b; a -= c; a ^= (c>>12); \ + b -= c; b -= a; b ^= (a<<16); \ + c -= a; c -= b; c ^= (b>>5); \ + a -= b; a -= c; a ^= (c>>3); \ + b -= c; b -= a; b ^= (a<<10); \ + c -= a; c -= b; c ^= (b>>15); \ +} + +inline unsigned hash_u(unsigned a) { + a = (a+0x7ed55d16) + (a<<12); + a = (a^0xc761c23c) ^ (a>>19); + a = (a+0x165667b1) + (a<<5); + a = (a+0xd3a2646c) ^ (a<<9); + a = (a+0xfd7046c5) + (a<<3); + a = (a^0xb55a4f09) ^ (a>>16); + return a; +} + +inline unsigned hash_ull(unsigned long long a) { + a = (~a) + (a << 18); + a ^= (a >> 31); + a += (a << 2) + (a << 4); + a ^= (a >> 11); + a += (a << 6); + a ^= (a >> 22); + return static_cast(a); +} + +inline unsigned combine_hash(unsigned h1, unsigned h2) { + h2 -= h1; h2 ^= (h1 << 8); + h1 -= h2; h2 ^= (h1 << 16); + h2 -= h1; h2 ^= (h1 << 10); + return h2; +} + +inline unsigned hash_u_u(unsigned a, unsigned b) { + return combine_hash(hash_u(a), hash_u(b)); +} + +unsigned string_hash(const char * str, unsigned len, unsigned init_value); + +template +unsigned get_composite_hash(Composite app, unsigned n, GetKindHashProc const & khasher = GetKindHashProc(), GetChildHashProc const & chasher = GetChildHashProc()) { + unsigned a, b, c; + SASSERT(n > 0); + unsigned kind_hash = khasher(app); + + a = b = 0x9e3779b9; + c = 11; + + switch (n) { + case 1: + a += kind_hash; + b = chasher(app, 0); + mix(a, b, c); + return c; + case 2: + a += kind_hash; + b += chasher(app, 0); + c += chasher(app, 1); + mix(a, b, c); + return c; + case 3: + a += chasher(app, 0); + b += chasher(app, 1); + c += chasher(app, 2); + mix(a, b, c); + a += kind_hash; + mix(a, b, c); + return c; + default: + while (n >= 3) { + n--; + a += chasher(app, n); + n--; + b += chasher(app, n); + n--; + c += chasher(app, n); + mix(a, b, c); + } + + a += kind_hash; + switch (n) { + case 2: + b += chasher(app, 1); + __fallthrough; + case 1: + c += chasher(app, 0); + } + mix(a, b, c); + return c; + } +} + +template +struct default_kind_hash_proc { unsigned operator()(Composite const & c) const { return 17; } }; + +struct int_hash { + typedef int data; + unsigned operator()(int x) const { return static_cast(x); } +}; + +struct unsigned_hash { + typedef unsigned data; + unsigned operator()(unsigned x) const { return x; } +}; + +struct size_t_hash { + typedef size_t data; + unsigned operator()(size_t x) const { return static_cast(x); } +}; + +struct bool_hash { + typedef bool data; + unsigned operator()(bool x) const { return static_cast(x); } +}; + +template +struct obj_hash { + typedef T data; + unsigned operator()(const T & e) const { + return e.hash(); + } +}; + +template +struct obj_ptr_hash { + typedef T * data; + unsigned operator()(T * a) const { + return a->hash(); + } +}; + +template +struct obj_ptr_pair_hash { + typedef std::pair data; + unsigned operator()(data const & d) const { + return combine_hash(d.first->hash(), d.second->hash()); + } +}; + +template +struct triple { + T1 first; + T2 second; + T3 third; + triple(): first(T1()), second(T2()), third(T3()) {} + triple(T1 f, T2 s, T3 t): first(f), second(s), third(t) {} + + bool operator==(triple const& other) const { + return + first == other.first && + second == other.second && + third == other.third; + } +}; + +template +struct triple_hash : private Hash1 { + Hash2 m_hash2; + Hash3 m_hash3; + typedef triple data; + triple_hash(Hash1 const & h1 = Hash1(), Hash2 const & h2 = Hash2(), Hash3 const & h3 = Hash3()): + Hash1(h1), + m_hash2(h2), + m_hash3(h3) { + } + + unsigned operator()(std::pair const & p) const { + return combine_hash(combine_hash(Hash1::operator()(p.first), m_hash2.operator()(p.second)), m_hash3.operator()(p.third)); + } +}; + +template +struct obj_ptr_triple_hash { + typedef triple data; + unsigned operator()(data const & d) const { + return combine_hash(combine_hash(d.first->hash(), d.second->hash()), d.third->hash()); + } +}; + +template +struct pair_hash : private Hash1 { + Hash2 m_hash2; + typedef std::pair data; + pair_hash(Hash1 const & h1 = Hash1(), Hash2 const & h2 = Hash2()): + Hash1(h1), + m_hash2(h2) { + } + + unsigned operator()(std::pair const & p) const { + return combine_hash(Hash1::operator()(p.first), m_hash2.operator()(p.second)); + } +}; + +template +inline unsigned get_ptr_hash(T * ptr) { + return static_cast(reinterpret_cast(ptr)); +} + +template +struct ptr_hash { + typedef T * data; + unsigned operator()(T * ptr) const { + return get_ptr_hash(ptr); + } +}; + +inline unsigned mk_mix(unsigned a, unsigned b, unsigned c) { + mix(a, b, c); + return c; +} + +#endif /* _HASH_H_ */ + diff --git a/lib/hashtable.h b/lib/hashtable.h new file mode 100644 index 000000000..1eb168120 --- /dev/null +++ b/lib/hashtable.h @@ -0,0 +1,643 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + hashtable.h + +Abstract: + + Hashtable without buckets. + +Author: + + Leonardo de Moura (leonardo) 2006-09-11. + +Revision History: + +--*/ +#ifndef _HASHTABLE_H_ +#define _HASHTABLE_H_ +#include"debug.h" +#include +#include"util.h" +#include"limits.h" +#include"memory_manager.h" +#include"hash.h" + +#define DEFAULT_HASHTABLE_INITIAL_CAPACITY 8 +#define SMALL_TABLE_CAPACITY 64 + +// #define HASHTABLE_STATISTICS + +#ifdef HASHTABLE_STATISTICS +#define HS_CODE(CODE) { CODE } +#else +#define HS_CODE(CODE) +#endif + +typedef enum { HT_FREE, + HT_DELETED, + HT_USED } hash_entry_state; + +template +class default_hash_entry { + unsigned m_hash; //!< cached hash code + hash_entry_state m_state; + T m_data; +public: + typedef T data; + default_hash_entry():m_state(HT_FREE) {} + unsigned get_hash() const { return m_hash; } + bool is_free() const { return m_state == HT_FREE; } + bool is_deleted() const { return m_state == HT_DELETED; } + bool is_used() const { return m_state == HT_USED; } + T & get_data() { return m_data; } + const T & get_data() const { return m_data; } + void set_data(const T & d) { m_data = d; m_state = HT_USED; } + void set_hash(unsigned h) { m_hash = h; } + void mark_as_deleted() { m_state = HT_DELETED; } + void mark_as_free() { m_state = HT_FREE; } +}; + +/** + \brief Special entry for a hashtable of integers. This entry "steals" two values for representing HT_FREE and HT_DELETED. +*/ +template +class int_hash_entry { + unsigned m_hash; //!< cached hash code + int m_data; +public: + typedef int data; + int_hash_entry():m_data(Free) {} + unsigned get_hash() const { return m_hash; } + bool is_free() const { return m_data == Free; } + bool is_deleted() const { return m_data == Deleted; } + bool is_used() const { return m_data != Free && m_data != Deleted; } + int get_data() const { return m_data; } + int & get_data() { return m_data; } + void set_data(int d) { m_data = d; } + void set_hash(unsigned h) { m_hash = h; } + void mark_as_deleted() { m_data = Deleted; } + void mark_as_free() { m_data = Free; } +}; + +/** + \brief Special entry for a hashtable of pointers. This entry uses 0x0 and 0x1 to represent HT_FREE and HT_DELETED. +*/ +template +class ptr_hash_entry { + unsigned m_hash; //!< cached hash code + T * m_ptr; +public: + typedef T * data; + ptr_hash_entry():m_ptr(0) {} + unsigned get_hash() const { return m_hash; } + bool is_free() const { return m_ptr == 0; } + bool is_deleted() const { return m_ptr == reinterpret_cast(1); } + bool is_used() const { return m_ptr != reinterpret_cast(0) && m_ptr != reinterpret_cast(1); } + T * get_data() const { return m_ptr; } + T * & get_data() { return m_ptr; } + void set_data(T * d) { m_ptr = d; } + void set_hash(unsigned h) { m_hash = h; } + void mark_as_deleted() { m_ptr = reinterpret_cast(1); } + void mark_as_free() { m_ptr = 0; } +}; + + +/** + \brief Special entry for a hashtable of pointers which uses the pointer itself as the hashcode. + This entry uses 0x0 and 0x1 to represent HT_FREE and HT_DELETED. +*/ +template +class ptr_addr_hash_entry : public ptr_hash_entry { + T * m_ptr; +public: + typedef T * data; + ptr_addr_hash_entry():m_ptr(0) {} + unsigned get_hash() const { return get_ptr_hash(m_ptr); } + bool is_free() const { return m_ptr == 0; } + bool is_deleted() const { return m_ptr == reinterpret_cast(1); } + bool is_used() const { return m_ptr != reinterpret_cast(0) && m_ptr != reinterpret_cast(1); } + T * get_data() const { return m_ptr; } + T * & get_data() { return m_ptr; } + void set_data(T * d) { m_ptr = d; } + void set_hash(unsigned h) { SASSERT(h == get_ptr_hash(m_ptr)); /* do nothing */ } + void mark_as_deleted() { m_ptr = reinterpret_cast(1); } + void mark_as_free() { m_ptr = 0; } +}; + +template +class core_hashtable : private HashProc, private EqProc { +protected: + Entry * m_table; + unsigned m_capacity; + unsigned m_size; + unsigned m_num_deleted; +#ifdef HASHTABLE_STATISTICS + unsigned long long m_st_collision; +#endif + + Entry* alloc_table(unsigned size) { + Entry* entries = alloc_vect(size); + return entries; + } + + void delete_table() { + dealloc_vect(m_table, m_capacity); + m_table = 0; + } + +public: + typedef typename Entry::data data; + typedef Entry entry; +protected: + unsigned get_hash(data const & e) const { return HashProc::operator()(e); } + bool equals(data const & e1, data const & e2) const { return EqProc::operator()(e1, e2); } + + static void copy_table(entry * source, unsigned source_capacity, entry * target, unsigned target_capacity) { + SASSERT(target_capacity >= source_capacity); + unsigned target_mask = target_capacity - 1; + entry * source_end = source + source_capacity; + entry * target_end = target + target_capacity; + for (entry * source_curr = source; source_curr != source_end; ++source_curr) { + if (source_curr->is_used()) { + unsigned hash = source_curr->get_hash(); + unsigned idx = hash & target_mask; + entry * target_begin = target + idx; + entry * target_curr = target_begin; + for (; target_curr != target_end; ++target_curr) { + SASSERT(!target_curr->is_deleted()); + if (target_curr->is_free()) { + *target_curr = *source_curr; + goto end; + } + } + for (target_curr = target; target_curr != target_begin; ++target_curr) { + SASSERT(!target_curr->is_deleted()); + if (target_curr->is_free()) { + *target_curr = *source_curr; + goto end; + } + } + UNREACHABLE(); + end: + ; + } + } + } + + void expand_table() { + unsigned new_capacity = m_capacity << 1; + entry * new_table = alloc_table(new_capacity); + copy_table(m_table, m_capacity, new_table, new_capacity); + delete_table(); + m_table = new_table; + m_capacity = new_capacity; + m_num_deleted = 0; + } + + + void remove_deleted_entries() { + if (memory::is_out_of_memory()) + return; + entry * new_table = alloc_table(m_capacity); + copy_table(m_table, m_capacity, new_table, m_capacity); + delete_table(); + m_table = new_table; + m_num_deleted = 0; + } + +public: + core_hashtable(unsigned initial_capacity = DEFAULT_HASHTABLE_INITIAL_CAPACITY, + HashProc const & h = HashProc(), + EqProc const & e = EqProc()): + HashProc(h), + EqProc(e) { + SASSERT(is_power_of_two(initial_capacity)); + m_table = alloc_table(initial_capacity); + m_capacity = initial_capacity; + m_size = 0; + m_num_deleted = 0; + HS_CODE({ + m_st_collision = 0; + }); + } + + core_hashtable(const core_hashtable & source): + HashProc(source), + EqProc(source) { + m_capacity = source.m_capacity; + m_table = alloc_table(m_capacity); + copy_table(source.m_table, m_capacity, m_table, m_capacity); + m_size = source.m_size; + m_num_deleted = 0; + HS_CODE({ + m_st_collision = 0; + }); + } + + ~core_hashtable() { + delete_table(); + } + + void swap(core_hashtable & source) { + std::swap(m_table, source.m_table); + std::swap(m_capacity, source.m_capacity); + std::swap(m_size, source.m_size); + std::swap(m_num_deleted, source.m_num_deleted); + HS_CODE({ + std::swap(m_st_collision, source.m_st_collision); + }); + } + + void reset() { + if (m_size == 0 && m_num_deleted == 0) + return; + unsigned overhead = 0; + entry * curr = m_table; + entry * end = m_table + m_capacity; + for (; curr != end; ++curr) { + if (!curr->is_free()) + curr->mark_as_free(); + else + overhead++; + } + if (m_capacity > 16 && overhead << 2 > (m_capacity * 3)) { + delete_table(); + SASSERT(m_capacity > 16); + SASSERT(is_power_of_two(m_capacity)); + m_capacity = (m_capacity >> 1); + SASSERT(is_power_of_two(m_capacity)); + m_table = alloc_table(m_capacity); + } + m_size = 0; + m_num_deleted = 0; + } + + void finalize() { + if (m_capacity > SMALL_TABLE_CAPACITY) { + delete_table(); + m_table = alloc_table(SMALL_TABLE_CAPACITY); + m_capacity = SMALL_TABLE_CAPACITY; + m_size = 0; + m_num_deleted = 0; + } + else { + reset(); + } + } + + class iterator { + entry * m_curr; + entry * m_end; + void move_to_used() { + while (m_curr != m_end && !m_curr->is_used()) { + m_curr++; + } + } + public: + iterator(entry * start, entry * end): m_curr(start), m_end(end) { move_to_used(); } + data & operator*() { return m_curr->get_data(); } + data const & operator*() const { return m_curr->get_data(); } + data const * operator->() const { return &(operator*()); } + data * operator->() { return &(operator*()); } + iterator & operator++() { ++m_curr; move_to_used(); return *this; } + iterator operator++(int) { iterator tmp = *this; ++*this; return tmp; } + bool operator==(iterator const & it) const { return m_curr == it.m_curr; } + bool operator!=(iterator const & it) const { return m_curr != it.m_curr; } + }; + + bool empty() const { return m_size == 0; } + + unsigned size() const { return m_size; } + + unsigned capacity() const { return m_capacity; } + + iterator begin() const { return iterator(m_table, m_table + m_capacity); } + + iterator end() const { return iterator(m_table + m_capacity, m_table + m_capacity); } + +#define INSERT_LOOP_BODY() { \ + if (curr->is_used()) { \ + if (curr->get_hash() == hash && equals(curr->get_data(), e)) { \ + curr->set_data(e); \ + return; \ + } \ + HS_CODE(m_st_collision++;); \ + } \ + else if (curr->is_free()) { \ + entry * new_entry; \ + if (del_entry) { new_entry = del_entry; m_num_deleted--; } \ + else { new_entry = curr; } \ + new_entry->set_data(e); \ + new_entry->set_hash(hash); \ + m_size++; \ + return; \ + } \ + else { \ + SASSERT(curr->is_deleted()); \ + del_entry = curr; \ + HS_CODE(m_st_collision++;); \ + } \ + } ((void) 0) + + void insert(data const & e) { + if ((m_size + m_num_deleted) << 2 > (m_capacity * 3)) { + // if ((m_size + m_num_deleted) * 2 > (m_capacity)) { + expand_table(); + } + unsigned hash = get_hash(e); + unsigned mask = m_capacity - 1; + unsigned idx = hash & mask; + entry * begin = m_table + idx; + entry * end = m_table + m_capacity; + entry * curr = begin; + entry * del_entry = 0; + for (; curr != end; ++curr) { + INSERT_LOOP_BODY(); + } + for (curr = m_table; curr != begin; ++curr) { + INSERT_LOOP_BODY(); + } + UNREACHABLE(); + } + +#define INSERT_LOOP_CORE_BODY() { \ + if (curr->is_used()) { \ + if (curr->get_hash() == hash && equals(curr->get_data(), e)) { \ + et = curr; \ + return false; \ + } \ + HS_CODE(m_st_collision++;); \ + } \ + else if (curr->is_free()) { \ + entry * new_entry; \ + if (del_entry) { new_entry = del_entry; m_num_deleted--; } \ + else { new_entry = curr; } \ + new_entry->set_data(e); \ + new_entry->set_hash(hash); \ + m_size++; \ + et = new_entry; \ + return true; \ + } \ + else { \ + SASSERT(curr->is_deleted()); \ + del_entry = curr; \ + HS_CODE(m_st_collision++;); \ + } \ + } ((void) 0) + + /** + \brief Insert the element e if it is not in the table. + Return true if it is a new element, and false otherwise. + Store the entry/slot of the table in et. + */ + bool insert_if_not_there_core(data const & e, entry * & et) { + if ((m_size + m_num_deleted) << 2 > (m_capacity * 3)) { + // if ((m_size + m_num_deleted) * 2 > (m_capacity)) { + expand_table(); + } + unsigned hash = get_hash(e); + unsigned mask = m_capacity - 1; + unsigned idx = hash & mask; + entry * begin = m_table + idx; + entry * end = m_table + m_capacity; + entry * curr = begin; + entry * del_entry = 0; + for (; curr != end; ++curr) { + INSERT_LOOP_CORE_BODY(); + } + for (curr = m_table; curr != begin; ++curr) { + INSERT_LOOP_CORE_BODY(); + } + UNREACHABLE(); + return 0; + } + + /** + \brief Insert the element e if it is not in the table. + Return a reference to e or to an object identical to e + that was already in the table. + */ + data const & insert_if_not_there(data const & e) { + entry * et; + insert_if_not_there_core(e, et); + return et->get_data(); + } + + /** + \brief Insert the element e if it is not in the table. + Return the entry that contains e. + */ + entry * insert_if_not_there2(data const & e) { + entry * et; + insert_if_not_there_core(e, et); + return et; + } + +#define FIND_LOOP_BODY() { \ + if (curr->is_used()) { \ + if (curr->get_hash() == hash && equals(curr->get_data(), e)) { \ + return curr; \ + } \ + HS_CODE(const_cast(this)->m_st_collision++;); \ + } \ + else if (curr->is_free()) { \ + return 0; \ + } \ + HS_CODE(const_cast(this)->m_st_collision++;); \ + } ((void) 0) + + entry * find_core(data const & e) const { + unsigned hash = get_hash(e); + unsigned mask = m_capacity - 1; + unsigned idx = hash & mask; + entry * begin = m_table + idx; + entry * end = m_table + m_capacity; + entry * curr = begin; + for (; curr != end; ++curr) { + FIND_LOOP_BODY(); + } + for (curr = m_table; curr != begin; ++curr) { + FIND_LOOP_BODY(); + } + return 0; + } + + bool find(data const & k, data & r) const { + entry * e = find_core(k); + if (e != 0) { + r = e->get_data(); + return true; + } + return false; + } + + bool contains(data const & e) const { + return find_core(e) != 0; + } + + iterator find(data const & e) const { + entry * r = find_core(e); + if (r) { + return iterator(r, m_table + m_capacity); + } + else { + return end(); + } + } + +#define REMOVE_LOOP_BODY() { \ + if (curr->is_used()) { \ + if (curr->get_hash() == hash && equals(curr->get_data(), e)) { \ + goto end_remove; \ + } \ + HS_CODE(m_st_collision++;); \ + } \ + else if (curr->is_free()) { \ + return; \ + } \ + HS_CODE(m_st_collision++;); \ + } ((void) 0) + + void remove(data const & e) { + unsigned hash = get_hash(e); + unsigned mask = m_capacity - 1; + unsigned idx = hash & mask; + entry * begin = m_table + idx; + entry * end = m_table + m_capacity; + entry * curr = begin; + for (; curr != end; ++curr) { + REMOVE_LOOP_BODY(); + } + for (curr = m_table; curr != begin; ++curr) { + REMOVE_LOOP_BODY(); + } + SASSERT(!contains(e)); + return; // node is not in the table + end_remove: + entry * next = curr + 1; + if (next == end) { + next = m_table; + } + if (next->is_free()) { + curr->mark_as_free(); + m_size--; + } + else { + curr->mark_as_deleted(); + m_num_deleted++; + m_size--; + if (m_num_deleted > m_size && m_num_deleted > SMALL_TABLE_CAPACITY) { + remove_deleted_entries(); + } + } + } + + void erase(data const & e) { remove(e); } + + void dump(std::ostream & out) { + entry * curr = m_table; + entry * end = m_table + m_capacity; + out << "["; + bool first = true; + for (; curr != end; ++curr) { + if (curr->is_used()) { + if (first) { + first = false; + } + else { + out << " "; + } + out << curr->get_data(); + } + } + out << "]"; + } + +#ifdef Z3DEBUG + bool check_invariant() { + entry * curr = m_table; + entry * end = m_table + m_capacity; + unsigned num_deleted = 0; + unsigned num_used = 0; + for (; curr != end; ++curr) { + if (curr->is_deleted()) { + num_deleted ++; + } + if (curr->is_used()) { + num_used++; + } + } + SASSERT(num_deleted == m_num_deleted); + SASSERT(num_used == m_size); + return true; + } +#endif + +#ifdef HASHTABLE_STATISTICS + unsigned long long get_num_collision() const { return m_st_collision; } +#else + unsigned long long get_num_collision() const { return 0; } +#endif + + private: + + core_hashtable& operator=(core_hashtable const&); + +}; + +template +class hashtable : public core_hashtable, HashProc, EqProc> { +public: + hashtable(unsigned initial_capacity = DEFAULT_HASHTABLE_INITIAL_CAPACITY, + HashProc const & h = HashProc(), + EqProc const & e = EqProc()): + core_hashtable, HashProc, EqProc>(initial_capacity, h, e) {} +}; + +template +class ptr_hashtable : public core_hashtable, HashProc, EqProc> { +public: + ptr_hashtable(unsigned initial_capacity = DEFAULT_HASHTABLE_INITIAL_CAPACITY, + HashProc const & h = HashProc(), + EqProc const & e = EqProc()): + core_hashtable, HashProc, EqProc>(initial_capacity, h, e) {} +}; + +/** + \brief Hashtable of pointers which use the pointer as the hash-code. +*/ +template +class ptr_addr_hashtable : public core_hashtable, ptr_hash, ptr_eq > { +public: + typedef typename core_hashtable, ptr_hash, ptr_eq >::iterator iterator; + ptr_addr_hashtable(unsigned initial_capacity = DEFAULT_HASHTABLE_INITIAL_CAPACITY): + core_hashtable, ptr_hash, ptr_eq >(initial_capacity) {} + + // Using iterators to traverse the elements of this kind of hashtable will produce non-determinism. + iterator begin() const { + UNREACHABLE(); + } + + iterator end() const { + UNREACHABLE(); + } +}; + +/** + \brief Simple int_hashtable. The values INT_MIN and INT_MIN + 1 are used to mark + deleted and free slots. So, these values cannot be stored in the table. Use core_hashtable + template to avoid this limitation. + */ +template +class int_hashtable : public core_hashtable, HashProc, EqProc> { +public: + int_hashtable(unsigned initial_capacity = DEFAULT_HASHTABLE_INITIAL_CAPACITY, + HashProc const & h = HashProc(), + EqProc const & e = EqProc()): + core_hashtable, HashProc, EqProc>(initial_capacity, h, e) {} +}; + +#endif /* _HASHTABLE_H_ */ diff --git a/lib/heap.h b/lib/heap.h new file mode 100644 index 000000000..dfb741e13 --- /dev/null +++ b/lib/heap.h @@ -0,0 +1,245 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + heap.h + +Abstract: + + A heap of integers. + +Author: + + Leonardo de Moura (leonardo) 2006-09-14. + +Revision History: + +--*/ +#ifndef _HEAP_H_ +#define _HEAP_H_ + +#include"vector.h" +#include"debug.h" + +template +class heap : private LT { + int_vector m_values; + int_vector m_value2indices; + + bool less_than(int v1, int v2) const { + return LT::operator()(v1, v2); + } + + static int left(int i) { + return i << 1; + } + + static int right(int i) { + return (i << 1) + 1; + } + + static int parent(int i) { + return i >> 1; + } + +#ifdef Z3DEBUG + // Return true if the value can be inserted in the heap. That is, the vector m_value2indices is big enough to store this value. + bool is_valid_value(int v) const { + SASSERT(v >= 0 && v < static_cast(m_value2indices.size())); + return true; + } + + bool check_invariant_core(int idx) const { + if (idx < static_cast(m_values.size())) { + SASSERT(m_value2indices[m_values[idx]] == idx); + SASSERT(parent(idx) == 0 || !less_than(m_values[idx], m_values[parent(idx)])); + SASSERT(check_invariant_core(left(idx))); + SASSERT(check_invariant_core(right(idx))); + } + return true; + } +public: + bool check_invariant() const { + return check_invariant_core(1); + } +#endif +private: + + void move_up(int idx) { + int val = m_values[idx]; + while (true) { + int parent_idx = parent(idx); + if (parent_idx == 0 || !less_than(val, m_values[parent_idx])) { + break; + } + m_values[idx] = m_values[parent_idx]; + m_value2indices[m_values[idx]] = idx; + idx = parent_idx; + } + m_values[idx] = val; + m_value2indices[val] = idx; + CASSERT("heap", check_invariant()); + } + + void move_down(int idx) { + int val = m_values[idx]; + int sz = static_cast(m_values.size()); + while (true) { + int left_idx = left(idx); + if (left_idx >= sz) { + break; + } + int right_idx = right(idx); + int min_idx = right_idx < sz && less_than(m_values[right_idx], m_values[left_idx]) ? right_idx : left_idx; + SASSERT(parent(min_idx) == idx); + int min_value = m_values[min_idx]; + if (!less_than(min_value, val)) { + break; + } + m_values[idx] = min_value; + m_value2indices[min_value] = idx; + idx = min_idx; + } + m_values[idx] = val; + m_value2indices[val] = idx; + CASSERT("heap", check_invariant()); + } + +public: + typedef int * iterator; + typedef const int * const_iterator; + + heap(int s, const LT & lt = LT()):LT(lt) { + m_values.push_back(-1); + set_bounds(s); + } + + bool empty() const { + return m_values.size() == 1; + } + + bool contains(int val) const { + return val < static_cast(m_value2indices.size()) && m_value2indices[val] != 0; + } + + void reset() { + if (empty()) { + return; + } + memset(m_value2indices.begin(), 0, sizeof(int) * m_value2indices.size()); + m_values.reset(); + m_values.push_back(-1); + } + + void clear() { + reset(); + } + + void set_bounds(int s) { + m_value2indices.resize(s, 0); + } + + unsigned get_bounds() const { + return m_value2indices.size(); + } + + void reserve(int s) { + if (s > static_cast(m_value2indices.size())) + set_bounds(s); + } + + int min_value() const { + SASSERT(!empty()); + return m_values[1]; + } + + int erase_min() { + SASSERT(!empty()); + SASSERT(m_values.size() >= 2); + int result = m_values[1]; + if (m_values.size() == 2) { + m_value2indices[result] = 0; + m_values.pop_back(); + SASSERT(empty()); + } + else { + int last_val = m_values.back(); + m_values[1] = last_val; + m_value2indices[last_val] = 1; + m_value2indices[result] = 0; + m_values.pop_back(); + move_down(1); + } + CASSERT("heap", check_invariant()); + return result; + } + + void erase(int val) { + SASSERT(contains(val)); + int idx = m_value2indices[val]; + if (idx == static_cast(m_values.size()) - 1) { + m_value2indices[val] = 0; + m_values.pop_back(); + } + else { + int last_val = m_values.back(); + m_values[idx] = last_val; + m_value2indices[last_val] = idx; + m_value2indices[val] = 0; + m_values.pop_back(); + int parent_idx = parent(idx); + if (parent_idx != 0 && less_than(last_val, m_values[parent(idx)])) { + move_up(idx); + } + else { + move_down(idx); + } + } + CASSERT("heap", check_invariant()); + } + + void decreased(int val) { + SASSERT(contains(val)); + move_up(m_value2indices[val]); + } + + void increased(int val) { + SASSERT(contains(val)); + move_down(m_value2indices[val]); + } + + void insert(int val) { + SASSERT(is_valid_value(val)); + int idx = static_cast(m_values.size()); + m_value2indices[val] = idx; + m_values.push_back(val); + SASSERT(idx == static_cast(m_values.size()) - 1); + move_up(idx); + } + + iterator begin() { + return m_values.begin() + 1; + } + + iterator end() { + return m_values.end(); + } + + const_iterator begin() const { + return m_values.begin() + 1; + } + + const_iterator end() const { + return m_values.end(); + } + + void swap(heap & other) { + m_values.swap(other.m_values); + m_value2indices.swap(other.m_value2indices); + } + +}; + +#endif /* _HEAP_H_ */ + diff --git a/lib/horn_subsume_model_converter.cpp b/lib/horn_subsume_model_converter.cpp new file mode 100644 index 000000000..b2b76e39c --- /dev/null +++ b/lib/horn_subsume_model_converter.cpp @@ -0,0 +1,221 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + horn_subsume_model_converter.cpp + +Abstract: + + Model converter for redundant Horn clauses. + +Author: + + Nikolaj Bjorner (nbjorner) 2012-9-16 + +Revision History: + +--*/ + + +#include "horn_subsume_model_converter.h" +#include "var_subst.h" +#include "ast_pp.h" +#include "model_smt2_pp.h" +#include "bool_rewriter.h" + +void horn_subsume_model_converter::insert(app* head, expr* body) { + func_decl_ref pred(m); + expr_ref body_res(m); + VERIFY(mk_horn(head, body, pred, body_res)); + insert(pred.get(), body_res.get()); +} + +void horn_subsume_model_converter::insert(app* head, unsigned sz, expr* const* body) { + expr_ref b(m); + bool_rewriter(m).mk_and(sz, body, b); + insert(head, b.get()); +} + + +bool horn_subsume_model_converter::mk_horn( + app* head, expr* body, func_decl_ref& pred, expr_ref& body_res) const { + + expr_ref_vector conjs(m), subst(m); + ptr_vector sorts, sorts2; + var_subst vs(m, false); + + if (!is_uninterp(head)) { + return false; + } + + pred = head->get_decl(); + unsigned arity = head->get_num_args(); + + get_free_vars(head, sorts); + get_free_vars(body, sorts); + + if (arity == 0 && sorts.empty()) { + body_res = body; + return true; + } + + svector names; + for (unsigned i = 0; i < sorts.size(); ++i) { + if (!sorts[i]) { + sorts[i] = m.mk_bool_sort(); + } + names.push_back(symbol(i)); + } + names.reverse(); + sorts.reverse(); + conjs.push_back(body); + for (unsigned i = 0; i < arity; ++i) { + expr* arg = head->get_arg(i); + var_ref v(m); + v = m.mk_var(sorts.size()+i,m.get_sort(arg)); + + if (is_var(arg)) { + unsigned w = to_var(arg)->get_idx(); + if (w >= subst.size()) { + subst.resize(w+1); + } + if (subst[w].get()) { + conjs.push_back(m.mk_eq(v, subst[w].get())); + } + else { + subst[w] = v; + } + } + else { + conjs.push_back(m.mk_eq(v, arg)); + } + } + expr_ref body_expr(m); + body_expr = m.mk_and(conjs.size(), conjs.c_ptr()); + + // substitute variables directly. + if (!subst.empty()) { + expr_ref tmp(body_expr); + vs(tmp, subst.size(), subst.c_ptr(), body_expr); + } + + + // remove bound variables that are redundant. + get_free_vars(body_expr, sorts2); + subst.reset(); + + unsigned num_bound_vars = sorts.size(); + unsigned new_num_bound_vars = 0; + + for (unsigned i = 0, j = 0; i < sorts2.size(); ++i) { + if (sorts2[i]) { + subst.push_back(m.mk_var(j++, sorts2[i])); + if (i < num_bound_vars) { + ++new_num_bound_vars; + } + } + else { + subst.push_back(0); + } + } + if (new_num_bound_vars < num_bound_vars) { + expr_ref tmp(body_expr); + vs(tmp, subst.size(), subst.c_ptr(), body_expr); + sorts.reset(); + names.reset(); + for (unsigned i = 0; i < num_bound_vars; ++i) { + if (sorts2[i]) { + sorts.push_back(sorts2[i]); + names.push_back(symbol(sorts.size())); + } + } + sorts.reverse(); + names.reverse(); + } + if (sorts.empty()) { + body_res = body_expr; + } + else { + body_res = m.mk_exists(sorts.size(), sorts.c_ptr(), names.c_ptr(), body_expr.get()); + } + TRACE("dl", + tout << mk_pp(head, m) << " :- " << mk_pp(body, m) << "\n"; + tout << pred->get_name() << " :- " << mk_pp(body_res.get(), m) << "\n";); + + return true; +} + + +bool horn_subsume_model_converter::mk_horn( + expr* clause, func_decl_ref& pred, expr_ref& body) const { + ptr_vector sorts; + + // formula is closed. + DEBUG_CODE(get_free_vars(clause, sorts); SASSERT(sorts.empty());); + + while (is_quantifier(clause) && to_quantifier(clause)->is_forall()) { + quantifier* q = to_quantifier(clause); + clause = q->get_expr(); + } + expr* e1, *e2; + if (m.is_implies(clause, e1, e2)) { + if (!is_uninterp(e2)) { + return false; + } + return mk_horn(to_app(e2), e1, pred, body); + } + else if (m.is_or(clause)) { + // todo? + return false; + } + else { + return false; + } +} + + +void horn_subsume_model_converter::operator()(model_ref& mr) { + for (unsigned i = m_funcs.size(); i > 0; ) { + --i; + func_decl* h = m_funcs[i].get(); + expr_ref body(m_bodies[i].get(), m); + unsigned arity = h->get_arity(); + + expr_ref tmp(body); + mr->eval(tmp, body); + TRACE("dl", tout << "eval: " << mk_pp(tmp, m) << "\nto:\n" << mk_pp(body, m) << "\n";); + TRACE("dl", model_smt2_pp(tout, m, *mr, 0);); + + if (arity == 0) { + expr* e = mr->get_const_interp(h); + if (e) { + mr->register_decl(h, m.mk_or(e, body)); + } + else { + mr->register_decl(h, body); + } + } + else { + func_interp* f = mr->get_func_interp(h); + if (f) { + f->set_else(m.mk_or(f->get_else(), body.get())); + } + else { + f = alloc(func_interp, m, arity); + f->set_else(body); + mr->register_decl(h, f); + } + } + } +} + + +model_converter* horn_subsume_model_converter::translate(ast_translation & translator) { + horn_subsume_model_converter* mc = alloc(horn_subsume_model_converter, translator.to()); + for (unsigned i = 0; i < m_funcs.size(); ++i) { + mc->insert(translator(m_funcs[i].get()), translator(m_bodies[i].get())); + } + return mc; +} + diff --git a/lib/horn_subsume_model_converter.h b/lib/horn_subsume_model_converter.h new file mode 100644 index 000000000..91172a72c --- /dev/null +++ b/lib/horn_subsume_model_converter.h @@ -0,0 +1,67 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + horn_subsume_model_converter.h + +Abstract: + + Model converter for redundant Horn clauses. + +Author: + + Nikolaj Bjorner (nbjorner) 2012-9-16 + +Revision History: + + +Notes: + + +Subsumption transformation (remove Horn clause): + + P(x) :- Body(x,y) Rules + ---------------------------- + Rules + + + Model converter: + + P(x) := P(x) or (exists y. Body(x,y)) + +--*/ + +#ifndef _HORN_SUBSUME_MODEL_CONVERTER_H_ +#define _HORN_SUBSUME_MODEL_CONVERTER_H_ + +#include "model_converter.h" + +class horn_subsume_model_converter : public model_converter { + ast_manager& m; + func_decl_ref_vector m_funcs; + expr_ref_vector m_bodies; + +public: + + horn_subsume_model_converter(ast_manager& m): m(m), m_funcs(m), m_bodies(m) {} + + bool mk_horn(expr* clause, func_decl_ref& pred, expr_ref& body) const; + + bool mk_horn(app* head, expr* body, func_decl_ref& pred, expr_ref& body_res) const; + + void insert(app* head, expr* body); + + void insert(app* head, unsigned sz, expr* const* body); + + void insert(func_decl* p, expr* body) { m_funcs.push_back(p); m_bodies.push_back(body); } + + virtual void operator()(model_ref& m); + + virtual model_converter * translate(ast_translation & translator); + + ast_manager& get_manager() { return m; } + +}; + +#endif diff --git a/lib/hwf.cpp b/lib/hwf.cpp new file mode 100644 index 000000000..c54f51040 --- /dev/null +++ b/lib/hwf.cpp @@ -0,0 +1,646 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + hwf.cpp + +Abstract: + + Hardware Floating Point Numbers + +Author: + + Christoph Wintersteiger (cwinter) 2012-07-30. + +Revision History: + +--*/ +#include + +#ifdef _WINDOWS +#pragma float_control( except, on ) // exception semantics; this does _not_ mean that exceptions are enabled (we want them off!) +#pragma float_control( precise, on ) // precise semantics (no guessing!) +#pragma fp_contract(off) // contractions off (`contraction' means x*y+z is turned into a fused-mul-add). +#pragma fenv_access(on) // fpu environment sensitivity (needed to be allowed to make FPU mode changes). +#else +#pragma STDC FENV_ACCESS ON +#include +#include +#endif + +#ifndef _M_IA64 +#define USE_INTRINSICS +#endif + +#include + +#include"hwf.h" + +// Note: +// Which FPU will be used is determined by compiler settings. On x64 it's always SSE2, +// on x86 we have to chose SSE2 by enabling /arch:SSE2 (otherwise the x87 FPU will be used). +// Christoph has decided that we don't want to use the x87; this makes everything a lot easier. + + +// For SSE2, it is best to use compiler intrinsics because this makes it completely +// clear to the compiler what instructions should be used. E.g., for sqrt(), the Windows compiler selects +// the x87 FPU, even when /arch:SSE2 is on. +// Luckily, these are kind of standardized, at least for Windows/Linux/OSX. +#include + + +hwf_manager::hwf_manager() : + m_mpz_manager(m_mpq_manager) +{ +#ifdef _WINDOWS +#if defined(_AMD64_) || defined(_M_IA64) + // Precision control is not supported on x64. + // See: http://msdn.microsoft.com/en-us/library/e9b52ceh(VS.110).aspx + // CMW: I think this is okay though, the compiler will chose the right instructions + // (the x64/SSE2 FPU has separate instructions for different precisions). +#else + // Setting the precision should only be required on the x87, but it won't hurt to do it anyways. + // _PC_53 means double precision (53 significand bits). For extended precision use _PC_64. + +#ifndef USE_INTRINSICS + __control87_2(_PC_53, _MCW_PC, &x86_state, &sse2_state); +#endif +#endif +#else + // OSX/Linux: Nothing. +#endif + + // We only set the precision of the FPU here in the constructor. At the moment, there are no + // other parts of the code that could overwrite this, and Windows takes care of context switches. + + // CMW: I'm not sure what happens on CPUs with hyper-threading (since the FPU is shared). + // I have yet to discover whether Linux and OSX save the FPU state when switching context. + // As long as we stick to using the SSE2 FPU though, there shouldn't be any problems with respect + // to the precision (not sure about the rounding modes though). +} + +hwf_manager::~hwf_manager() +{ +} + +#define RAW(X) (*reinterpret_cast(&(X))) +#define DBL(X) (*reinterpret_cast(&(X))) + +void hwf_manager::set(hwf & o, int value) { + o.value = (double) value; +} + +void hwf_manager::set(hwf & o, mpf_rounding_mode rm, int n, int d) { + set_rounding_mode(rm); + o.value = ((double) n)/((double) d); +} + +void hwf_manager::set(hwf & o, double value) { + o.value = value; +} + +void hwf_manager::set(hwf & o, float value) { + o.value = (double)value; +} + +void hwf_manager::set(hwf & o, mpf_rounding_mode rm, mpq const & value) { + set_rounding_mode(rm); + o.value = m_mpq_manager.get_double(value); +} + +void hwf_manager::set(hwf & o, mpf_rounding_mode rm, char const * value) { + // We expect [i].[f]P[e], where P means that the exponent is interpreted as 2^e instead of 10^e. + + std::string v(value); + size_t e_pos = v.find('p'); + if (e_pos == std::string::npos) e_pos = v.find('P'); + + std::string f, e; + + f = (e_pos != std::string::npos) ? v.substr(0, e_pos) : v; + e = (e_pos != std::string::npos) ? v.substr(e_pos+1) : "0"; + + TRACE("mpf_dbg", tout << " f = " << f << " e = " << e << std::endl;); + + mpq q; + m_mpq_manager.set(q, f.c_str()); + + mpz ex; + m_mpz_manager.set(ex, e.c_str()); + + set(o, rm, q, ex); + + TRACE("mpf_dbg", tout << "set: res = " << to_string(o) << std::endl;); +} + +void hwf_manager::set(hwf & o, mpf_rounding_mode rm, mpq const & significand, mpz const & exponent) { + // Assumption: this represents significand * 2^exponent. + set_rounding_mode(rm); + + mpq sig; + m_mpq_manager.set(sig, significand); + int64 exp = m_mpz_manager.get_int64(exponent); + + if (m_mpq_manager.is_zero(significand)) + o.value = 0.0; + else + { + while (m_mpq_manager.lt(sig, 1)) + { + m_mpq_manager.mul(sig, 2, sig); + exp--; + } + + hwf s; s.value = m_mpq_manager.get_double(sig); + uint64 r = (RAW(s.value) & 0x800FFFFFFFFFFFFFull) | ((exp + 1023) << 52); + o.value = DBL(r); + } +} + +void hwf_manager::set(hwf & o, bool sign, uint64 significand, int exponent) { + // Assumption: this represents (sign * -1) * (significand/2^sbits) * 2^exponent. + SASSERT(significand <= 0x000FFFFFFFFFFFFFull); + SASSERT(-1022 <= exponent && exponent <= 1023); + uint64 raw = (sign?0x8000000000000000ull:0); + raw |= (((uint64)exponent) + 1023) << 52; + raw |= significand; + o.value = *reinterpret_cast(&raw); +} + +void hwf_manager::set(hwf & o, hwf const & x) { + o.value = x.value; +} + +void hwf_manager::abs(hwf & o) { + o.value = fabs(o.value); +} + +void hwf_manager::abs(hwf const & x, hwf & o) { + o.value = fabs(x.value); +} + +void hwf_manager::neg(hwf & o) { + o.value = -o.value; +} + +void hwf_manager::neg(hwf const & x, hwf & o) { + o.value = -x.value; +} + +bool hwf_manager::eq(hwf const & x, hwf const & y) { + return (x.value == y.value); +} + +bool hwf_manager::lt(hwf const & x, hwf const & y) { + return (x.value < y.value); +} + +bool hwf_manager::lte(hwf const & x, hwf const & y) { + return (x.value <= y.value); +} + +bool hwf_manager::gt(hwf const & x, hwf const & y) { + return (x.value > y.value); +} + +bool hwf_manager::gte(hwf const & x, hwf const & y) { + return (x.value >= y.value); +} + +void hwf_manager::add(mpf_rounding_mode rm, hwf const & x, hwf const & y, hwf & o) { + set_rounding_mode(rm); +#ifdef USE_INTRINSICS + _mm_store_sd(&o.value, _mm_add_sd(_mm_set_sd(x.value), _mm_set_sd(y.value))); +#else + o.value = x.value + y.value; +#endif +} + +void hwf_manager::sub(mpf_rounding_mode rm, hwf const & x, hwf const & y, hwf & o) { + set_rounding_mode(rm); +#ifdef USE_INTRINSICS + _mm_store_sd(&o.value, _mm_sub_sd(_mm_set_sd(x.value), _mm_set_sd(y.value))); +#else + o.value = x.value - y.value; +#endif +} + +#define DBL_SCALE 15360 + +void hwf_manager::mul(mpf_rounding_mode rm, hwf const & x, hwf const & y, hwf & o) { + set_rounding_mode(rm); +#ifdef USE_INTRINSICS + _mm_store_sd(&o.value, _mm_mul_sd(_mm_set_sd(x.value), _mm_set_sd(y.value))); +#else + o.value = x.value * y.value; +#endif + +#if 0 + // On the x86 FPU (x87), we use custom assembly routines because + // the code generated for x*y and x/y suffers from the double + // rounding on underflow problem. The scaling trick is described + // in Roger Golliver: `Efficiently producing default orthogonal IEEE + // double results using extended IEEE hardware', see + // http://www.open-std.org/JTC1/SC22/JSG/docs/m3/docs/jsgn326.pdf + // CMW: Tthis is not really needed if we use only the SSE2 FPU, + // it shouldn't hurt the performance too much though. + + static const int const1 = -DBL_SCALE; + static const int const2 = +DBL_SCALE; + double xv = x.value; + double yv = y.value; + double & ov = o.value; + + __asm { + fild const1; + fld xv; + fscale; + fstp st(1); + fmul yv; + fild const2; + fxch st(1); + fscale; + fstp ov; + } +#endif +} + +void hwf_manager::div(mpf_rounding_mode rm, hwf const & x, hwf const & y, hwf & o) { + set_rounding_mode(rm); +#ifdef USE_INTRINSICS + _mm_store_sd(&o.value, _mm_div_sd(_mm_set_sd(x.value), _mm_set_sd(y.value))); +#else + o.value = x.value / y.value; +#endif + +#if 0 + // see mul(...) + + static const int const1 = -DBL_SCALE; + static const int const2 = +DBL_SCALE; + double xv = x.value; + double yv = y.value; + double & ov = o.value; + + __asm { + fild const1; + fld xv; + fscale; + fstp st(1); + fdiv yv; + fild const2; + fxch st(1); + fscale; + fstp ov; + } +#endif +} + +#ifdef _M_IA64 +#pragma fp_contract(on) +#endif + +void hwf_manager::fused_mul_add(mpf_rounding_mode rm, hwf const & x, hwf const & y, hwf const &z, hwf & o) { + // CMW: fused_mul_add is not available on most CPUs. As of 2012, only Itanium, + // Intel Sandybridge and AMD Bulldozers support that (via AVX). + +#ifdef _M_IA64 + // IA64 (Itanium) will do it, if contractions are on. + set_rounding_mode(rm); + o.value = x.value * y.value + z.value; +#else + // NOT_IMPLEMENTED_YET(); + // Just a dummy for now: + hwf t; + mul(rm, x, y, t); + add(rm, t, z, o); +#endif +} + +#ifdef _M_IA64 +#pragma fp_contract(off) +#endif + +void hwf_manager::sqrt(mpf_rounding_mode rm, hwf const & x, hwf & o) { + set_rounding_mode(rm); +#ifdef USE_INTRINSICS + _mm_store_sd(&o.value, _mm_sqrt_pd(_mm_set_sd(x.value))); +#else + o.value = ::sqrt(x.value); +#endif +} + +void hwf_manager::round_to_integral(mpf_rounding_mode rm, hwf const & x, hwf & o) { + set_rounding_mode(rm); + modf(x.value, &o.value); + // Note: on x64, this sometimes produces an SNAN instead of a QNAN? +} + +void hwf_manager::rem(hwf const & x, hwf const & y, hwf & o) { + // The built-in fmod() works, except for the special numbers. + + if (is_inf(x) && is_inf(y)) + o.value = x.value/y.value; // NaN + else if (is_inf(y)) + o.value = x.value; + else + o.value = fmod(x.value, y.value); + +// Here is an x87 alternative if the above makes problems; this may also be faster. +#if 0 + double xv = x.value; + double yv = y.value; + double & ov = o.value; + + // This is from: http://webster.cs.ucr.edu/AoA/DOS/ch14/CH14-4.html#HEADING4-173 + __asm { + fld yv + fld xv +L: fprem1 + fstsw ax // Get condition bits in AX. + test ah, 100b // See if C2 is set. + jnz L // Repeat if not done yet. + fstp ov // Store remainder away. + fstp st(0) // Pop old y value. + } +#endif +} + +void hwf_manager::maximum(hwf const & x, hwf const & y, hwf & o) { +#ifdef USE_INTRINSICS + _mm_store_sd(&o.value, _mm_max_sd(_mm_set_sd(x.value), _mm_set_sd(y.value))); +#else + // use __max ? + if (is_nan(x)) + o.value = y.value; + else if (is_nan(y)) + o.value = x.value; + else if (lt(x, y)) + o.value = y.value; + else + o.value = x.value; +#endif +} + +void hwf_manager::minimum(hwf const & x, hwf const & y, hwf & o) { +#ifdef USE_INTRINSICS + _mm_store_sd(&o.value, _mm_min_sd(_mm_set_sd(x.value), _mm_set_sd(y.value))); +#else + // use __min ? + if (is_nan(x) || is_nan(x)) + o.value = y.value; + else if (is_nan(y)) + o.value = x.value; + else if (lt(x, y)) + o.value = x.value; + else + o.value = y.value; +#endif +} + +std::string hwf_manager::to_string(hwf const & x) { + std::stringstream ss(""); + ss << std::scientific << x.value; + return ss.str(); +} + +std::string hwf_manager::to_rational_string(hwf const & a) { + // temporary hack + unsynch_mpq_manager qm; + scoped_mpq q(qm); + to_rational(a, q); + return qm.to_string(q); +} + +void hwf_manager::display_decimal(std::ostream & out, hwf const & a, unsigned k) { + // temporary hack + unsynch_mpq_manager qm; + scoped_mpq q(qm); + to_rational(a, q); + qm.display_decimal(out, q, k); +} + +void hwf_manager::display_smt2(std::ostream & out, hwf const & a, bool decimal) { + // temporary hack + unsynch_mpq_manager qm; + scoped_mpq q(qm); + to_rational(a, q); + qm.display_smt2(out, q, decimal); +} + +void hwf_manager::to_rational(hwf const & x, unsynch_mpq_manager & qm, mpq & o) { + SASSERT(is_normal(x) || is_denormal(x) || is_zero(x)); + scoped_mpz n(qm), d(qm); + + if (is_normal(x)) + qm.set(n, sig(x) | 0x0010000000000000ull); + else + qm.set(n, sig(x)); + if (sgn(x)) + qm.neg(n); + qm.set(d, 0x0010000000000000ull); + int e = exp(x); + if (e >= 0) + qm.mul2k(n, (unsigned)e); + else + qm.mul2k(d, (unsigned)-e); + qm.set(o, n, d); +} + +bool hwf_manager::is_zero(hwf const & x) { + uint64 t = RAW(x.value) & 0x7FFFFFFFFFFFFFFFull; + return (t == 0x0ull); + // CMW: I tried, and these are slower: + // return (t != 0x0ull) ? false : true; + // return (x.value == 0.0 || x.value == -0.0); // [uses SSE2]. +} + +bool hwf_manager::is_neg(hwf const & x) { + // [Leo]: I added !is_nan(x) + return sgn(x) && !is_nan(x); +} + +bool hwf_manager::is_pos(hwf const & x) { + return !sgn(x) && !is_nan(x); +} + +bool hwf_manager::is_nzero(hwf const & x) { + return RAW(x.value) == 0x8000000000000000ull; +} + +bool hwf_manager::is_pzero(hwf const & x) { + return RAW(x.value) == 0x0000000000000000ull; +} + +bool hwf_manager::is_one(hwf const & x) { + return RAW(x.value) == 0x3FF0000000000000ull; +} + +bool hwf_manager::is_nan(hwf const & x) { + bool r = ((RAW(x.value) & 0x7FF0000000000000ull) == 0x7FF0000000000000ull) && + ((RAW(x.value) & 0x000FFFFFFFFFFFFFull) != 0x0); +#ifdef _WINDOWS + SASSERT( !r || (_fpclass(x.value) == _FPCLASS_SNAN || _fpclass(x.value) == _FPCLASS_QNAN)); +#endif + return r; +} + +bool hwf_manager::is_inf(hwf const & x) { + bool r = ((RAW(x.value) & 0x7FF0000000000000ull) == 0x7FF0000000000000ull) && + ((RAW(x.value) & 0x000FFFFFFFFFFFFFull) == 0x0); +#ifdef _WINDOWS + SASSERT( !r || (_fpclass(x.value) == _FPCLASS_NINF || _fpclass(x.value) == _FPCLASS_PINF)); +#endif + return r; +} + +bool hwf_manager::is_pinf(hwf const & x) { + return !sgn(x) && is_inf(x); +} + +bool hwf_manager::is_ninf(hwf const & x) { + return sgn(x) && is_inf(x); +} + +bool hwf_manager::is_normal(hwf const & x) { + uint64 t = RAW(x.value) & 0x7FF0000000000000ull; + return (t != 0x0ull && t != 0x7FF0000000000000ull); +} + +bool hwf_manager::is_denormal(hwf const & x) { + uint64 t = RAW(x.value); + return ((t & 0x7FF0000000000000ull) == 0x0 && + (t & 0x000FFFFFFFFFFFFFull) != 0x0); +} + +bool hwf_manager::is_regular(hwf const & x) { + // Everything that doesn't have the top-exponent is considered regular. + // Note that +-0.0 and denormal numbers have exponent==0; these are regular. + // All normal numbers are also regular. What remains is +-Inf and NaN, they are + // not regular and they are the only numbers that have exponent 7FF. + uint64 e = RAW(x.value) & 0x7FF0000000000000ull; // the exponent + return (e != 0x7FF0000000000000ull); +} + +bool hwf_manager::is_int(hwf const & x) { + if (!is_normal(x)) + return false; + + const int e = exp(x); + if (e >= 52) + return true; + else if (e < 0) + return false; + else + { + uint64 t = sig(x); + unsigned shift = 52 - ((unsigned)e); + uint64 mask = (0x1ull << shift) - 1; + return (t & mask) == 0; + } +} + +void hwf_manager::mk_nzero(hwf & o) { + uint64 raw = 0x8000000000000000ull; + o.value = DBL(raw); +} + +void hwf_manager::mk_pzero(hwf & o) { + o.value = 0; +} + +void hwf_manager::mk_zero(bool sign, hwf & o) { + if (sign) + mk_nzero(o); + else + mk_pzero(o); +} + +void hwf_manager::mk_nan(hwf & o) { + uint64 raw = 0x7FF0000000000001ull; + o.value = DBL(raw); +} + +void hwf_manager::mk_inf(bool sign, hwf & o) { + uint64 raw = (sign) ? 0xFFF0000000000000ull : 0x7FF0000000000000ull; + o.value = DBL(raw); +} + +void hwf_manager::mk_pinf(hwf & o) { + uint64 raw = 0x7FF0000000000000ull; + o.value = DBL(raw); +} + +void hwf_manager::mk_ninf(hwf & o) { + uint64 raw = 0xFFF0000000000000ull; + o.value = DBL(raw); +} + +#ifdef _WINDOWS +#if defined(_AMD64_) || defined(_M_IA64) + #ifdef USE_INTRINSICS + #define SETRM(RM) _MM_SET_ROUNDING_MODE(RM) + #else + #define SETRM(RM) _controlfp_s(&sse2_state, RM, _MCW_RC); + #endif +#else + #ifdef USE_INTRINSICS + #define SETRM(RM) _MM_SET_ROUNDING_MODE(RM) + #else + #define SETRM(RM) __control87_2(RM, _MCW_RC, &x86_state, &sse2_state) + #endif +#endif +#else + #define SETRM(RM) fesetround(RM) +#endif + +unsigned hwf_manager::prev_power_of_two(hwf const & a) { + SASSERT(!is_nan(a) && !is_pinf(a) && !is_ninf(a)); + if (!is_pos(a)) + return 0; + if (exp(a) <= -52) + return 0; + return 51 + exp(a); +} + +void hwf_manager::set_rounding_mode(mpf_rounding_mode rm) +{ +#ifdef _WINDOWS + switch (rm) { + case MPF_ROUND_NEAREST_TEVEN: + SETRM(_RC_NEAR); + break; + case MPF_ROUND_TOWARD_POSITIVE: + SETRM(_RC_UP); + break; + case MPF_ROUND_TOWARD_NEGATIVE: + SETRM(_RC_DOWN); + break; + case MPF_ROUND_TOWARD_ZERO: + SETRM(_RC_CHOP); + break; + case MPF_ROUND_NEAREST_TAWAY: + default: + UNREACHABLE(); // Note: MPF_ROUND_NEAREST_TAWAY is not supported by the hardware! + } +#else // OSX/Linux + switch (rm) { + case MPF_ROUND_NEAREST_TEVEN: + SETRM(FE_TONEAREST); + break; + case MPF_ROUND_TOWARD_POSITIVE: + SETRM(FE_UPWARD); + break; + case MPF_ROUND_TOWARD_NEGATIVE: + SETRM(FE_DOWNWARD); + break; + case MPF_ROUND_TOWARD_ZERO: + SETRM(FE_TOWARDZERO); + break; + case MPF_ROUND_NEAREST_TAWAY: + default: + UNREACHABLE(); // Note: MPF_ROUND_NEAREST_TAWAY is not supported by the hardware! + } +#endif +} diff --git a/lib/hwf.h b/lib/hwf.h new file mode 100644 index 000000000..3b7a0e94b --- /dev/null +++ b/lib/hwf.h @@ -0,0 +1,174 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + hwf.h + +Abstract: + + Hardware Floating Point Numbers + +Author: + + Christoph Wintersteiger (cwinter) 2012-07-30. + +Revision History: + +--*/ +#ifndef _HWF_H_ +#define _HWF_H_ + +#include +#include"mpz.h" +#include"mpq.h" +#include"mpf.h" // we use the same rounding modes as mpf's + +class hwf { + friend class hwf_manager; + double value; + hwf & operator=(hwf const & other) { UNREACHABLE(); return *this; } + +public: + hwf() {} + hwf(hwf const & other) { this->value = other.value; } + ~hwf() {} + void swap(hwf & other) { double t = value; value = other.value; other.value = t; } +}; + + +class hwf_manager { + unsynch_mpq_manager m_mpq_manager; + unsynch_mpz_manager & m_mpz_manager; // A mpq_manager is a mpz_manager, reusing it. + +public: + typedef hwf numeral; + hwf_manager(); + ~hwf_manager(); + + void reset(hwf & o) { set(o, 0); } + void set(hwf & o, int value); + void set(hwf & o, mpf_rounding_mode rm, int n, int d); + void set(hwf & o, float value); + void set(hwf & o, double value); + void set(hwf & o, mpf_rounding_mode rm, mpq const & value); + void set(hwf & o, mpf_rounding_mode rm, char const * value); + void set(hwf & o, mpf_rounding_mode rm, mpq const & significand, mpz const & exponent); + void set(hwf & o, bool sign, uint64 significand, int exponent); + void set(hwf & o, hwf const & x); + + // auxiliary methods to make the interface compatible with mpf + void reset(hwf & o, unsigned ebits, unsigned sbits) { set(o, 0); } + void set(hwf & o, unsigned ebits, unsigned sbits, mpf_rounding_mode rm, mpq const & value) { set(o, rm, value); } + void set(hwf & o, unsigned ebits, unsigned sbits, int value) { set(o, value); } + void set(hwf & o, unsigned ebits, unsigned sbits, mpf_rounding_mode rm, int n, int d) { set(o, rm, n, d); } + void set(hwf & o, unsigned ebits, unsigned sbits, float value) { set(o, value); } + void set(hwf & o, unsigned ebits, unsigned sbits, double value) { set(o, value); } + + + void del(hwf & x) {} + + void abs(hwf & o); + void abs(hwf const & x, hwf & o); + + void neg(hwf & o); + void neg(hwf const & x, hwf & o); + + bool is_zero(hwf const & x); + bool is_neg(hwf const & x); + bool is_pos(hwf const & x); + + bool is_nzero(hwf const & x); + bool is_pzero(hwf const & x); + + bool is_one(hwf const & x); + + // structural eq + bool eq_core(hwf const & x, hwf const & y); + + bool eq(hwf const & x, hwf const & y); + bool lt(hwf const & x, hwf const & y); + bool lte(hwf const & x, hwf const & y); + bool le(hwf const & x, hwf const & y) { return lte(x, y); } + bool gt(hwf const & x, hwf const & y); + bool gte(hwf const & x, hwf const & y); + bool ge(hwf const & x, hwf const & y) { return gte(x, y); } + + void add(mpf_rounding_mode rm, hwf const & x, hwf const & y, hwf & o); + void sub(mpf_rounding_mode rm, hwf const & x, hwf const & y, hwf & o); + void mul(mpf_rounding_mode rm, hwf const & x, hwf const & y, hwf & o); + void div(mpf_rounding_mode rm, hwf const & x, hwf const & y, hwf & o); + + void fused_mul_add(mpf_rounding_mode rm, hwf const & x, hwf const & y, hwf const &z, hwf & o); + + void sqrt(mpf_rounding_mode rm, hwf const & x, hwf & o); + + void round_to_integral(mpf_rounding_mode rm, hwf const & x, hwf & o); + + void rem(hwf const & x, hwf const & y, hwf & o); + + void maximum(hwf const & x, hwf const & y, hwf & o); + void minimum(hwf const & x, hwf const & y, hwf & o); + + std::string to_string(hwf const & a); + std::string to_rational_string(hwf const & a); + void display_decimal(std::ostream & out, hwf const & a, unsigned k); + void display_smt2(std::ostream & out, hwf const & a, bool decimal); + + double to_double(hwf const & x) { return x.value; } + float to_float(hwf const & x) { return (float) x.value; } + void to_rational(hwf const & x, unsynch_mpq_manager & qm, mpq & o); + void to_rational(hwf const & x, scoped_mpq & o) { to_rational(x, o.m(), o); } + + + bool sgn(hwf const & x) const { + uint64 raw = *reinterpret_cast(&x.value); + return (raw & 0x8000000000000000ull) != 0; + } + + const uint64 sig(hwf const & x) const { + return *reinterpret_cast(&x.value) & 0x000FFFFFFFFFFFFFull; + } + + const int exp(hwf const & x) const { + return ((*reinterpret_cast(&x.value) & 0x7FF0000000000000ull) >> 52) - 1023; + } + + bool is_nan(hwf const & x); + bool is_inf(hwf const & x); + bool is_pinf(hwf const & x); + bool is_ninf(hwf const & x); + bool is_normal(hwf const & x); + bool is_denormal(hwf const & x); + bool is_regular(hwf const & x); + bool is_int(hwf const & x); + + void mk_zero(bool sign, hwf & o); + void mk_nzero(hwf & o); + void mk_pzero(hwf & o); + void mk_nan(hwf & o); + void mk_inf(bool sign, hwf & o); + void mk_pinf(hwf & o); + void mk_ninf(hwf & o); + + unsigned hash(hwf const & a) { return hash_ull(*reinterpret_cast(&a.value)); } + + inline void set_rounding_mode(mpf_rounding_mode rm); + + /** + \brief Return the biggest k s.t. 2^k <= a. + + \remark Return 0 if a is not positive. + */ + unsigned prev_power_of_two(hwf const & a); + +protected: +#ifdef _WINDOWS + unsigned x86_state, sse2_state; +#endif +}; + +typedef _scoped_numeral scoped_hwf; +typedef _scoped_numeral_vector scoped_hwf_vector; + +#endif diff --git a/lib/id_gen.h b/lib/id_gen.h new file mode 100644 index 000000000..b1713b524 --- /dev/null +++ b/lib/id_gen.h @@ -0,0 +1,83 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + id_gen.h + +Abstract: + + Basic support for generating & recycling ids. + +Author: + + Leonardo de Moura (leonardo) 2008-02-02. + +Revision History: + +--*/ +#ifndef _ID_GEN_H_ +#define _ID_GEN_H_ + +#include"vector.h" +#include"util.h" + +class id_gen { + unsigned m_next_id; + unsigned_vector m_free_ids; +public: + id_gen(unsigned start = 0):m_next_id(start) {} + + unsigned mk() { + unsigned r; + if (m_free_ids.empty()) { + r = m_next_id; + m_next_id++; + } + else { + r = m_free_ids.back(); + m_free_ids.pop_back(); + } + return r; + } + + void recycle(unsigned id) { + if (memory::is_out_of_memory()) + return; + m_free_ids.push_back(id); + } + + void reset(unsigned start = 0) { + m_next_id = start; + m_free_ids.reset(); + } + + void cleanup(unsigned start = 0) { + m_next_id = start; + m_free_ids.finalize(); + } + + /** + \brief Return N if the range of ids generated by this module is in the set [0..N) + */ + unsigned get_id_range() const { return m_next_id; } + + + /** + \brief Debugging support method: set m_next_id to the least value id' s.t. id' >= id and id' is not in m_free_ids. + This method is only used to create small repros that exposes bugs in Z3. + */ + unsigned set_next_id(unsigned id) { + m_next_id = id; + while (std::find(m_free_ids.begin(), m_free_ids.end(), m_next_id) != m_free_ids.end()) + m_next_id++; + return m_next_id; + } + + void display_free_ids(std::ostream & out) { + ::display(out, m_free_ids.begin(), m_free_ids.end()); + } + +}; + +#endif /* _ID_GEN_H_ */ diff --git a/lib/imdd.cpp b/lib/imdd.cpp new file mode 100644 index 000000000..072ab567d --- /dev/null +++ b/lib/imdd.cpp @@ -0,0 +1,3137 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + imdd.cpp + +Abstract: + + Interval based Multiple-valued Decision Diagrams. + +Author: + + Leonardo de Moura (leonardo) 2010-10-13. + +Revision History: + +--*/ +#ifndef _EXTERNAL_RELEASE + +#include"imdd.h" +#include"map.h" + +#define DEFAULT_SIMPLE_MAX_ENTRIES 32 + +inline bool is_cached(imdd2imdd_cache & cache, imdd * d, imdd * & r) { + if (d->is_shared()) { + if (cache.find(d, r) && (r == 0 || !r->is_dead())) + return true; + } + return false; +} + +inline void cache_result(imdd2imdd_cache & cache, imdd * d, imdd * r) { + if (d->is_shared()) + cache.insert(d, r); +} + +inline bool is_cached(imdd_pair2imdd_cache & cache, imdd * d1, imdd * d2, imdd * & r) { + if (d1->is_shared() && d2->is_shared()) { + if (cache.find(d1, d2, r) && (r == 0 || !r->is_dead())) + return true; + } + return false; +} + +inline void cache_result(imdd_pair2imdd_cache & cache, imdd * d1, imdd * d2, imdd * r) { + if (d1->is_shared() && d2->is_shared()) + cache.insert(d1, d2, r); +} + +inline bool destructive_update_at(bool destructive, imdd * d) { + return destructive && !d->is_memoized() && !d->is_shared(); +} + +void sl_imdd_manager::inc_ref_eh(imdd * v) { + m_manager->inc_ref(v); +} + +void sl_imdd_manager::dec_ref_eh(imdd * v) { + m_manager->dec_ref(v); +} + +unsigned imdd::hc_hash() const { + unsigned r = 0; + r = get_arity(); + imdd_children::iterator it = begin_children(); + imdd_children::iterator end = end_children(); + for (; it != end; ++it) { + unsigned b = it->begin_key(); + unsigned e = it->end_key(); + imdd const * child = it->val(); + mix(b, e, r); + if (child) { + SASSERT(child->is_memoized()); + unsigned v = child->get_id(); + mix(v, v, r); + } + } + return r; +} + +bool imdd::hc_equal(imdd const * other) const { + if (m_arity != other->m_arity) + return false; + return m_children.is_equal(other->m_children); +} + +imdd_manager::delay_dealloc::~delay_dealloc() { + SASSERT(m_manager.m_to_delete.size() >= m_to_delete_size); + ptr_vector::iterator it = m_manager.m_to_delete.begin() + m_to_delete_size; + ptr_vector::iterator end = m_manager.m_to_delete.end(); + for (; it != end; ++it) { + SASSERT((*it)->is_dead()); + m_manager.deallocate_imdd(*it); + } + m_manager.m_to_delete.shrink(m_to_delete_size); + m_manager.m_delay_dealloc = m_delay_dealloc_value; +} + +imdd_manager::imdd_manager(): + m_sl_manager(m_alloc), + m_simple_max_entries(DEFAULT_SIMPLE_MAX_ENTRIES), + m_delay_dealloc(false) { + m_sl_manager.m_manager = this; + m_swap_new_child = 0; +} + +imdd * imdd_manager::_mk_empty(unsigned arity) { + SASSERT(arity > 0); + void * mem = m_alloc.allocate(sizeof(imdd)); + return new (mem) imdd(m_sl_manager, m_id_gen.mk(), arity); +} + +imdd * imdd_manager::defrag_core(imdd * d) { + if (d->is_memoized()) + return d; + unsigned h = d->get_arity(); + if (h == 1 && !is_simple_node(d)) + return d; + imdd * new_d = 0; + if (is_cached(m_defrag_cache, d, new_d)) + return new_d; + + if (h == 1) { + SASSERT(is_simple_node(d)); + imdd * new_can_d = memoize(d); + cache_result(m_defrag_cache, d, new_can_d); + return new_can_d; + } + + SASSERT(h > 1); + new_d = _mk_empty(h); + imdd_children::push_back_proc push_back(m_sl_manager, new_d->m_children); + bool has_new = false; + bool children_memoized = true; + + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + for (; it != end; ++it) { + imdd * child = it->val(); + imdd * new_child = defrag_core(child); + if (child != new_child) + has_new = true; + if (!new_child->is_memoized()) + children_memoized = false; + push_back(it->begin_key(), it->end_key(), new_child); + } + + if (has_new) { + if (children_memoized && is_simple_node(new_d)) { + imdd * new_can_d = memoize(new_d); + if (new_can_d != new_d) { + SASSERT(new_d->get_ref_count() == 0); + delete_imdd(new_d); + } + new_d = new_can_d; + } + } + else { + SASSERT(!has_new); + delete_imdd(new_d); + new_d = d; + if (children_memoized && is_simple_node(new_d)) { + new_d = memoize(new_d); + } + } + + cache_result(m_defrag_cache, d, new_d); + return new_d; +} + +/** + \brief Compress the given IMDD by using hash-consing. +*/ +void imdd_manager::defrag(imdd_ref & d) { + delay_dealloc delay(*this); + m_defrag_cache.reset(); + d = defrag_core(d); +} + +/** + \brief Memoize the given IMDD. + Return an IMDD structurally equivalent to d. + This method assumes the children of d are memoized. + If that is not the case, the user should invoke defrag instead. +*/ +imdd * imdd_manager::memoize(imdd * d) { + if (d->is_memoized()) + return d; + unsigned h = d->get_arity(); + m_tables.reserve(h); + DEBUG_CODE({ + if (h > 1) { + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + for (; it != end; ++it) { + SASSERT(it->val()->is_memoized()); + } + } + }); + imdd * r = m_tables[h-1].insert_if_not_there(d); + if (r == d) { + r->mark_as_memoized(); + } + SASSERT(r->is_memoized()); + return r; +} + +/** + \brief Remove the given IMDD from the hash-consing table +*/ +void imdd_manager::unmemoize(imdd * d) { + SASSERT(d->is_memoized()); + m_tables[d->get_arity()-1].erase(d); + d->mark_as_memoized(false); +} + + +/** + \brief Remove the given IMDD (and its children) from the hash-consing tables. +*/ +void imdd_manager::unmemoize_rec(imdd * d) { + SASSERT(m_worklist.empty()); + m_worklist.push_back(d); + while (!m_worklist.empty()) { + d = m_worklist.back(); + if (d->is_memoized()) { + unmemoize(d); + SASSERT(!d->is_memoized()); + if (d->get_arity() > 1) { + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + for (; it != end; ++it) + m_worklist.push_back(it->val()); + } + } + } +} + +bool imdd_manager::is_simple_node(imdd * d) const { + return !d->m_children.has_more_than_k_entries(m_simple_max_entries); +} + +void imdd_manager::mark_as_dead(imdd * d) { + // The references to the children were decremented by delete_imdd. + SASSERT(!d->is_dead()); + d->m_children.deallocate_no_decref(m_sl_manager); + d->mark_as_dead(); + if (m_delay_dealloc) + m_to_delete.push_back(d); + else + deallocate_imdd(d); +} + +void imdd_manager::deallocate_imdd(imdd * d) { + SASSERT(d->is_dead()); + m_alloc.deallocate(sizeof(imdd), d); +} + +void imdd_manager::delete_imdd(imdd * d) { + SASSERT(m_worklist.empty()); + m_worklist.push_back(d); + + while (!m_worklist.empty()) { + d = m_worklist.back(); + m_worklist.pop_back(); + + m_id_gen.recycle(d->get_id()); + + SASSERT(d->get_ref_count() == 0); + if (d->is_memoized()) { + unsigned arity = d->get_arity(); + SASSERT(m_tables[arity-1].contains(d)); + m_tables[arity-1].erase(d); + } + + if (d->get_arity() > 1) { + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + for (; it != end; ++it) { + imdd * child = it->val(); + SASSERT(child); + child->dec_ref(); + if (child->get_ref_count() == 0) + m_worklist.push_back(child); + } + } + + mark_as_dead(d); + } +} + +/** + \brief Return a (non-memoized) shallow copy of d. +*/ +imdd * imdd_manager::copy_main(imdd * d) { + imdd * d_copy = _mk_empty(d->get_arity()); + d_copy->m_children.copy(m_sl_manager, d->m_children); + SASSERT(!d_copy->is_memoized()); + SASSERT(d_copy->get_ref_count() == 0); + return d_copy; +} + +/** + \brief Insert the values [b, e] into a IMDD of arity 1. + + If destructive == true, d is not memoized and is not shared, then a + destructive update is performed, and d is returned. + + Otherwise, a fresh IMDD is returned. +*/ +imdd * imdd_manager::insert_main(imdd * d, unsigned b, unsigned e, bool destructive, bool memoize_res) { + SASSERT(d->get_arity() == 1); + if (destructive_update_at(destructive, d)) { + add_child(d, b, e, 0); + return d; + } + else { + imdd * new_d = copy_main(d); + add_child(new_d, b, e, 0); + return memoize_new_imdd_if(memoize_res, new_d); + } +} + + +/** + \brief Remove the values [b, e] from an IMDD of arity 1. + + If destructive == true, d is not memoized and is not shared, then a + destructive update is performed, and d is returned. + + Otherwise, a fresh IMDD is returned. +*/ +imdd * imdd_manager::remove_main(imdd * d, unsigned b, unsigned e, bool destructive, bool memoize_res) { + SASSERT(d->get_arity() == 1); + if (destructive_update_at(destructive, d)) { + remove_child(d, b, e); + return d; + } + else { + imdd * new_d = copy_main(d); + remove_child(new_d, b, e); + return memoize_new_imdd_if(memoize_res, new_d); + } +} + +/** + \brief Auxiliary functor used to implement destructive version of mk_product. +*/ +struct imdd_manager::null2imdd_proc { + imdd * m_d2; + null2imdd_proc(imdd * d2):m_d2(d2) {} + imdd * operator()(imdd * d) { SASSERT(d == 0); return m_d2; } +}; + +/** + \brief Auxiliary functor used to implement destructive version of mk_product. +*/ +struct imdd_manager::mk_product_proc { + imdd_manager & m_manager; + imdd * m_d2; + bool m_memoize; + mk_product_proc(imdd_manager & m, imdd * d2, bool memoize): + m_manager(m), + m_d2(d2), + m_memoize(memoize) { + } + imdd * operator()(imdd * d1) { return m_manager.mk_product_core(d1, m_d2, true, m_memoize); } +}; + +imdd * imdd_manager::mk_product_core(imdd * d1, imdd * d2, bool destructive, bool memoize_res) { + SASSERT(!d1->empty()); + SASSERT(!d2->empty()); + if (destructive && !d1->is_shared()) { + if (d1->is_memoized()) + unmemoize(d1); + + if (d1->get_arity() == 1) { + null2imdd_proc f(d2); + d1->m_children.update_values(m_sl_manager, f); + d1->m_arity += d2->m_arity; + } + else { + mk_product_proc f(*this, d2, memoize_res); + d1->m_children.update_values(m_sl_manager, f); + d1->m_arity += d2->m_arity; + } + return d1; + } + else { + imdd * new_d1 = 0; + if (is_cached(m_mk_product_cache, d1, new_d1)) + return new_d1; + unsigned arity1 = d1->get_arity(); + unsigned arity2 = d2->get_arity(); + new_d1 = _mk_empty(arity1 + arity2); + imdd_children::push_back_proc push_back(m_sl_manager, new_d1->m_children); + imdd_children::iterator it = d1->begin_children(); + imdd_children::iterator end = d1->end_children(); + bool children_memoized = true; + for (; it != end; ++it) { + imdd * new_child; + if (arity1 == 1) + new_child = d2; + else + new_child = mk_product_core(it->val(), d2, false, memoize_res); + if (!new_child->is_memoized()) + children_memoized = false; + push_back(it->begin_key(), it->end_key(), new_child); + } + new_d1 = memoize_new_imdd_if(memoize_res && children_memoized, new_d1); + cache_result(m_mk_product_cache, d1, new_d1); + return new_d1; + } +} + +imdd * imdd_manager::mk_product_main(imdd * d1, imdd * d2, bool destructive, bool memoize_res) { + if (d1->empty() || d2->empty()) { + unsigned a1 = d1->get_arity(); + unsigned a2 = d2->get_arity(); + return _mk_empty(a1 + a2); + } + delay_dealloc delay(*this); + if (d1 == d2) + destructive = false; + m_mk_product_cache.reset(); + return mk_product_core(d1, d2, destructive, memoize_res); +} + +void imdd_manager::init_add_facts_new_children(unsigned num, unsigned const * lowers, unsigned const * uppers, bool memoize_res) { + if (m_add_facts_new_children[num] != 0) { + DEBUG_CODE({ + for (unsigned i = 1; i <= num; i++) { + SASSERT(m_add_facts_new_children[i] != 0); + }}); + return; + } + for (unsigned i = 1; i <= num; i++) { + if (m_add_facts_new_children[i] == 0) { + imdd * new_child = _mk_empty(i); + unsigned b = lowers[num - i]; + unsigned e = uppers[num - i]; + bool prev_memoized = true; + if (i == 1) { + add_child(new_child, b, e, 0); + } + else { + SASSERT(m_add_facts_new_children[i-1] != 0); + prev_memoized = m_add_facts_new_children[i-1]->is_memoized(); + add_child(new_child, b, e, m_add_facts_new_children[i-1]); + } + new_child = memoize_new_imdd_if(memoize_res && prev_memoized, new_child); + m_add_facts_new_children[i] = new_child; + } + } + DEBUG_CODE({ + for (unsigned i = 1; i <= num; i++) { + SASSERT(m_add_facts_new_children[i] != 0); + }}); +} + +imdd * imdd_manager::add_facts_core(imdd * d, unsigned num, unsigned const * lowers, unsigned const * uppers, bool destructive, bool memoize_res) { + SASSERT(d->get_arity() == num); + imdd_ref new_child(*this); + bool new_children_memoized = true; +#define INIT_NEW_CHILD() { \ + if (new_child == 0) { \ + init_add_facts_new_children(num - 1, lowers + 1, uppers + 1, memoize_res); \ + new_child = m_add_facts_new_children[num-1]; \ + if (!new_child->is_memoized()) new_children_memoized = false; \ + }} + + if (destructive_update_at(destructive, d)) { + if (num == 1) + return insert_main(d, *lowers, *uppers, destructive, memoize_res); + SASSERT(num > 1); + sbuffer to_insert; + new_child = m_add_facts_new_children[num-1]; + + unsigned b = *lowers; + unsigned e = *uppers; + imdd_children::iterator it = d->m_children.find_geq(b); + imdd_children::iterator end = d->end_children(); + for (; it != end && b <= e; ++it) { + imdd_children::entry const & curr_entry = *it; + if (e < curr_entry.begin_key()) + break; + if (b < curr_entry.begin_key()) { + INIT_NEW_CHILD(); + SASSERT(b <= curr_entry.begin_key() - 1); + to_insert.push_back(entry(b, curr_entry.begin_key() - 1, new_child)); + b = curr_entry.begin_key(); + } + imdd * curr_child = curr_entry.val(); + SASSERT(b >= curr_entry.begin_key()); + bool cover = b == curr_entry.begin_key() && e >= curr_entry.end_key(); + // If cover == true, then the curr_child is completely covered by the new facts, and it is not needed anymore. + // So, we can perform a destructive update. + imdd * new_curr_child = add_facts_core(curr_child, num - 1, lowers + 1, uppers + 1, cover, memoize_res); + + if (e >= curr_entry.end_key()) { + SASSERT(b <= curr_entry.end_key()); + to_insert.push_back(entry(b, curr_entry.end_key(), new_curr_child)); + } + else { + SASSERT(e < curr_entry.end_key()); + SASSERT(b <= e); + to_insert.push_back(entry(b, e, new_curr_child)); + } + b = curr_entry.end_key() + 1; + } + // Re-insert entries in m_add_facts_to_insert into d->m_children, + // and if b <= e also insert [b, e] -> new_child + if (b <= e) { + INIT_NEW_CHILD(); + add_child(d, b, e, new_child); + } + + svector::iterator it2 = to_insert.begin(); + svector::iterator end2 = to_insert.end(); + for (; it2 != end2; ++it2) { + imdd_children::entry const & curr_entry = *it2; + add_child(d, curr_entry.begin_key(), curr_entry.end_key(), curr_entry.val()); + } + + return d; + } + else { + imdd * new_d = 0; + if (is_cached(m_add_facts_cache, d, new_d)) + return new_d; + + if (num == 1) { + new_d = insert_main(d, *lowers, *uppers, destructive, memoize_res); + } + else { + new_d = copy_main(d); + new_child = m_add_facts_new_children[num-1]; + TRACE("add_facts_bug", tout << "after copying: " << new_d << "\n" << mk_ll_pp(new_d, *this) << "\n";); + + unsigned b = *lowers; + unsigned e = *uppers; + imdd_children::iterator it = d->m_children.find_geq(b); + imdd_children::iterator end = d->end_children(); + for (; it != end && b <= e; ++it) { + imdd_children::entry const & curr_entry = *it; + if (e < curr_entry.begin_key()) + break; + if (b < curr_entry.begin_key()) { + INIT_NEW_CHILD(); + SASSERT(b <= curr_entry.begin_key() - 1); + add_child(new_d, b, curr_entry.begin_key() - 1, new_child); + TRACE("add_facts_bug", tout << "after inserting new child: " << new_d << "\n" << mk_ll_pp(new_d, *this) << "\n";); + b = curr_entry.begin_key(); + } + imdd * curr_child = curr_entry.val(); + imdd * new_curr_child = add_facts_core(curr_child, num - 1, lowers + 1, uppers + 1, false, memoize_res); + if (!new_curr_child->is_memoized()) + new_children_memoized = false; + if (e >= curr_entry.end_key()) { + SASSERT(b <= curr_entry.end_key()); + add_child(new_d, b, curr_entry.end_key(), new_curr_child); + TRACE("add_facts_bug", tout << "1) after inserting new curr child: " << new_d << "\n" << mk_ll_pp(new_d, *this) << "\n"; + tout << "new_curr_child: " << mk_ll_pp(new_curr_child, *this) << "\n";); + } + else { + SASSERT(e < curr_entry.end_key()); + SASSERT(b <= e); + add_child(new_d, b, e, new_curr_child); + TRACE("add_facts_bug", tout << "2) after inserting new curr child: " << new_d << "\n" << mk_ll_pp(new_d, *this) << "\n"; + tout << "new_curr_child: " << mk_ll_pp(new_curr_child, *this) << "\n";); + } + b = curr_entry.end_key() + 1; + } + if (b <= e) { + INIT_NEW_CHILD(); + add_child(new_d, b, e, new_child); + } + + new_d = memoize_new_imdd_if(memoize_res && d->is_memoized() && new_children_memoized, new_d); + } + + cache_result(m_add_facts_cache, d, new_d); + return new_d; + } +} + +imdd * imdd_manager::add_facts_main(imdd * d, unsigned num, unsigned const * lowers, unsigned const * uppers, bool destructive, bool memoize_res) { + SASSERT(d->get_arity() == num); + delay_dealloc delay(*this); + m_add_facts_cache.reset(); + m_add_facts_new_children.reset(); + m_add_facts_new_children.resize(num, 0); + return add_facts_core(d, num, lowers, uppers, destructive, memoize_res); +} + +inline void update_memoized_flag(imdd * d, bool & memoized_flag) { + if (d && !d->is_memoized()) + memoized_flag = false; +} + +void imdd_manager::push_back_entries(unsigned head, imdd_children::iterator & it, imdd_children::iterator & end, + imdd_children::push_back_proc & push_back, bool & children_memoized) { + if (it != end) { + push_back(head, it->end_key(), it->val()); + ++it; + for (; it != end; ++it) { + update_memoized_flag(it->val(), children_memoized); + push_back(it->begin_key(), it->end_key(), it->val()); + } + } +} + +/** + \brief Push the entries starting at head upto the given limit (no included). + That is, we are copying the entries in the interval [head, limit). +*/ +void imdd_manager::push_back_upto(unsigned & head, imdd_children::iterator & it, imdd_children::iterator & end, + unsigned limit, imdd_children::push_back_proc & push_back, bool & children_memoized) { + SASSERT(it != end); + SASSERT(head <= it->end_key()); + SASSERT(head >= it->begin_key()); + SASSERT(head < limit); + while (head < limit && it != end) { + if (it->end_key() < limit) { + update_memoized_flag(it->val(), children_memoized); + push_back(head, it->end_key(), it->val()); + ++it; + if (it != end) + head = it->begin_key(); + } + else { + SASSERT(it->end_key() >= limit); + update_memoized_flag(it->val(), children_memoized); + push_back(head, limit-1, it->val()); + head = limit; + } + } + SASSERT(head == limit || it == end || head == it->begin_key()); +} + +void imdd_manager::move_head(unsigned & head, imdd_children::iterator & it, imdd_children::iterator & end, unsigned new_head) { + SASSERT(new_head >= head); + SASSERT(head >= it->begin_key()); + SASSERT(head <= it->end_key()); + SASSERT(new_head <= it->end_key()); + if (new_head < it->end_key()) { + head = new_head+1; + SASSERT(head <= it->end_key()); + } + else { + SASSERT(new_head == it->end_key()); + ++it; + if (it != end) + head = it->begin_key(); + } +} + +/** + \brief Copy the entries starting at head upto the given limit (no included). + That is, we are copying the entries in the interval [head, limit). +*/ +void imdd_manager::copy_upto(unsigned & head, imdd_children::iterator & it, imdd_children::iterator & end, + unsigned limit, sbuffer & result) { + SASSERT(it != end); + SASSERT(head <= it->end_key()); + SASSERT(head >= it->begin_key()); + SASSERT(head < limit); + while (head < limit && it != end) { + if (it->end_key() < limit) { + result.push_back(entry(head, it->end_key(), it->val())); + ++it; + if (it != end) + head = it->begin_key(); + } + else { + SASSERT(it->end_key() >= limit); + result.push_back(entry(head, limit-1, it->val())); + head = limit; + } + } + SASSERT(head == limit || it == end || head == it->begin_key()); +} + +imdd * imdd_manager::mk_union_core(imdd * d1, imdd * d2, bool destructive, bool memoize_res) { + if (d1 == d2) + return d1; + if (destructive_update_at(destructive, d1)) { + if (d1->get_arity() == 1) { + imdd_children::iterator it = d2->begin_children(); + imdd_children::iterator end = d2->end_children(); + for (; it != end; ++it) + add_child(d1, it->begin_key(), it->end_key(), 0); + return d1; + } + else { + imdd_children::iterator it2 = d2->begin_children(); + imdd_children::iterator end2 = d2->end_children(); + imdd_children::iterator it1 = d1->m_children.find_geq(it2->begin_key()); + imdd_children::iterator end1 = d1->end_children(); + sbuffer to_insert; + unsigned head1 = it1 != end1 ? it1->begin_key() : UINT_MAX; + SASSERT(it2 != end2); + unsigned head2 = it2->begin_key(); + while (true) { + if (it1 == end1) { + // copy it2 to d1 + // Remark: we don't need to copy to to_insert, since we will not be using it1 anymore. + // That is, we can directly insert into d1->m_children. + if (it2 != end2) { + add_child(d1, head2, it2->end_key(), it2->val()); + ++it2; + for (; it2 != end2; ++it2) + add_child(d1, it2->begin_key(), it2->end_key(), it2->val()); + } + break; + } + + if (it2 == end2) { + break; + } + + if (head1 < head2) { + head1 = head2; + it1.move_to(head2); + } + else if (head1 > head2) { + copy_upto(head2, it2, end2, head1, to_insert); + } + else { + SASSERT(head1 == head2); + unsigned tail = std::min(it1->end_key(), it2->end_key()); + imdd * new_child = 0; + SASSERT(d1->get_arity() > 1); + bool cover = head1 == it1->begin_key() && tail == it1->end_key(); + // If cover == true, then the it1->val() (curr_child) is completely covered by + // the new_child, and it is not needed anymore. + // So, we can perform a destructive update. + new_child = mk_union_core(it1->val(), it2->val(), cover, memoize_res); + if (new_child != it1->val()) + to_insert.push_back(entry(head1, tail, new_child)); + move_head(head1, it1, end1, tail); + move_head(head2, it2, end2, tail); + } + } + sbuffer::const_iterator it3 = to_insert.begin(); + sbuffer::const_iterator end3 = to_insert.end(); + for (; it3 != end3; ++it3) + add_child(d1, it3->begin_key(), it3->end_key(), it3->val()); + return d1; + } + } + else { + imdd * r = 0; + if (is_cached(m_union_cache, d1, d2, r)) + return r; + + unsigned arity = d1->get_arity(); + r = _mk_empty(arity); + imdd_children::push_back_proc push_back(m_sl_manager, r->m_children); + imdd_children::iterator it1 = d1->begin_children(); + imdd_children::iterator end1 = d1->end_children(); + imdd_children::iterator it2 = d2->begin_children(); + imdd_children::iterator end2 = d2->end_children(); + SASSERT(it1 != end1); + SASSERT(it2 != end2); + unsigned head1 = it1->begin_key(); + unsigned head2 = it2->begin_key(); + bool children_memoized = true; + while (true) { + if (it1 == end1) { + // copy it2 to result + push_back_entries(head2, it2, end2, push_back, children_memoized); + break; + } + if (it2 == end2) { + // copy it1 to result + push_back_entries(head1, it1, end1, push_back, children_memoized); + break; + } + + if (head1 < head2) { + push_back_upto(head1, it1, end1, head2, push_back, children_memoized); + } + else if (head1 > head2) { + push_back_upto(head2, it2, end2, head1, push_back, children_memoized); + } + else { + SASSERT(head1 == head2); + unsigned tail = std::min(it1->end_key(), it2->end_key()); + imdd * new_child = 0; + if (arity > 1) { + new_child = mk_union_core(it1->val(), it2->val(), false, memoize_res); + update_memoized_flag(new_child, children_memoized); + } + push_back(head1, tail, new_child); + move_head(head1, it1, end1, tail); + move_head(head2, it2, end2, tail); + } + } + r = memoize_new_imdd_if(memoize_res && children_memoized, r); + cache_result(m_union_cache, d1, d2, r); + return r; + } +} + +void imdd_manager::reset_union_cache() { + m_union_cache.reset(); +} + +imdd * imdd_manager::mk_union_main(imdd * d1, imdd * d2, bool destructive, bool memoize_res) { + SASSERT(d1->get_arity() == d2->get_arity()); + if (d1 == d2) + return d1; + if (d1->empty()) + return d2; + if (d2->empty()) + return d1; + delay_dealloc delay(*this); + reset_union_cache(); + return mk_union_core(d1, d2, destructive, memoize_res); +} + +void imdd_manager::mk_union_core_dupdt(imdd_ref & d1, imdd * d2, bool memoize_res) { + SASSERT(d1->get_arity() == d2->get_arity()); + if (d1 == d2 || d2->empty()) + return; + if (d1->empty()) { + d1 = d2; + return; + } + d1 = mk_union_core(d1, d2, true, memoize_res); +} + +void imdd_manager::mk_union_core(imdd * d1, imdd * d2, imdd_ref & r, bool memoize_res) { + SASSERT(d1->get_arity() == d2->get_arity()); + if (d1 == d2 || d2->empty()) { + r = d1; + return; + } + if (d1->empty()) { + r = d2; + return; + } + TRACE("mk_union_core", + tout << "d1:\n"; + display_ll(tout, d1); + tout << "\nd2:\n"; + display_ll(tout, d2); + tout << "\n";); + r = mk_union_core(d1, d2, false, memoize_res); +} + +imdd * imdd_manager::mk_complement_core(imdd * d, unsigned num, unsigned const * mins, unsigned const * maxs, bool destructive, bool memoize_res) { + SASSERT(d->get_arity() == num); + SASSERT(!d->empty()); + SASSERT(*mins <= *maxs); + unsigned arity = d->get_arity(); + imdd_ref new_child(*this); + bool new_children_memoized = true; +#undef INIT_NEW_CHILD +#define INIT_NEW_CHILD() { \ + if (arity > 1 && new_child == 0) { \ + init_add_facts_new_children(num - 1, mins + 1, maxs + 1, memoize_res); \ + new_child = m_add_facts_new_children[num-1]; \ + SASSERT(new_child != 0); \ + if (!new_child->is_memoized()) new_children_memoized = false; \ + }} + + if (false && destructive_update_at(destructive, d)) { + // TODO + NOT_IMPLEMENTED_YET(); + return 0; + } + else { + destructive = false; + imdd * r = 0; + if (is_cached(m_complement_cache, d, r)) + return r; + + r = _mk_empty(arity); + imdd_children::push_back_proc push_back(m_sl_manager, r->m_children); + + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + unsigned prev_key = *mins; + for (; it != end; ++it) { + SASSERT(it->begin_key() >= *mins); + SASSERT(it->end_key() <= *maxs); + if (prev_key < it->begin_key()) { + INIT_NEW_CHILD(); + push_back(prev_key, it->begin_key() - 1, new_child); + } + if (arity > 1) { + imdd * new_curr = mk_complement_core(it->val(), num - 1, mins + 1, maxs + 1, false, memoize_res); + if (new_curr != 0) { + push_back(it->begin_key(), it->end_key(), new_curr); + if (!new_curr->is_memoized()) + new_children_memoized = false; + } + } + prev_key = it->end_key() + 1; + } + + if (prev_key <= *maxs) { + INIT_NEW_CHILD(); + push_back(prev_key, *maxs, new_child); + } + + if (r->empty()) { + delete_imdd(r); + r = 0; + } + + r = memoize_new_imdd_if(r && memoize_res && new_children_memoized, r); + cache_result(m_complement_cache, d, r); + return r; + } +} + +imdd * imdd_manager::mk_complement_main(imdd * d, unsigned num, unsigned const * mins, unsigned const * maxs, bool destructive, bool memoize_res) { + unsigned arity = d->get_arity(); + SASSERT(arity == num); + // reuse m_add_facts_new_children for creating the universe-set IMDDs + m_add_facts_new_children.reset(); + m_add_facts_new_children.resize(num+1, 0); + + if (d->empty()) { + // return the universe-set + init_add_facts_new_children(num, mins, maxs, memoize_res); + return m_add_facts_new_children[num]; + } + + delay_dealloc delay(*this); + m_complement_cache.reset(); + imdd * r = mk_complement_core(d, num, mins, maxs, destructive, memoize_res); + if (r == 0) + return _mk_empty(arity); + else + return r; +} + +/** + \brief Replace the IMDD children with new_children. + The function will also decrement the ref-counter of every IMDD in new_children, since + it assumes the counter was incremented when the IMDD was inserted into the buffer. +*/ +void imdd::replace_children(sl_imdd_manager & m, sbuffer & new_children) { + m_children.reset(m); + imdd_children::push_back_proc push_back(m, m_children); + svector::iterator it = new_children.begin(); + svector::iterator end = new_children.end(); + for (; it != end; ++it) { + SASSERT(it->val() == 0 || it->val()->get_ref_count() > 0); + push_back(it->begin_key(), it->end_key(), it->val()); + SASSERT(it->val() == 0 || it->val()->get_ref_count() > 1); + if (it->val() != 0) + it->val()->dec_ref(); + } +} + +imdd * imdd_manager::mk_filter_equal_core(imdd * d, unsigned vidx, unsigned value, bool destructive, bool memoize_res) { + SASSERT(!d->empty()); + SASSERT(vidx >= 0 && vidx < d->get_arity()); + unsigned arity = d->get_arity(); + if (destructive_update_at(destructive, d)) { + if (vidx == 0) { + imdd * child = 0; + if (d->m_children.find(value, child)) { + imdd_ref ref(*this); + ref = child; // protect child, we don't want the following reset to delete it. + d->m_children.reset(m_sl_manager); + add_child(d, value, child); + return d; + } + else { + return 0; + } + } + SASSERT(arity > 1); + sbuffer to_insert; + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + for (; it != end; ++it) { + imdd * curr_child = it->val(); + imdd * new_child = mk_filter_equal_core(curr_child, vidx-1, value, true, memoize_res); + if (new_child != 0) { + new_child->inc_ref(); // protect new child, we will be resetting d->m_children later. + to_insert.push_back(entry(it->begin_key(), it->end_key(), new_child)); + } + } + if (to_insert.empty()) { + return 0; + } + d->replace_children(m_sl_manager, to_insert); + return d; + } + + imdd * r = 0; + if (is_cached(m_filter_equal_cache, d, r)) + return r; + bool new_children_memoized = true; + + if (vidx == 0) { + // found filter variable + imdd * child = 0; + if (d->m_children.find(value, child)) { + r = _mk_empty(arity); + add_child(r, value, child); + if (child && !child->is_memoized()) + new_children_memoized = false; + } + else { + r = 0; + } + } + else { + SASSERT(arity > 1); + r = _mk_empty(arity); + imdd_children::push_back_proc push_back(m_sl_manager, r->m_children); + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + for (; it != end; ++it) { + imdd * curr_child = it->val(); + imdd * new_child = mk_filter_equal_core(curr_child, vidx-1, value, false, memoize_res); + if (new_child != 0) { + push_back(it->begin_key(), it->end_key(), new_child); + if (!new_child->is_memoized()) + new_children_memoized = false; + } + } + if (r->empty()) { + delete_imdd(r); + r = 0; + } + } + + r = memoize_new_imdd_if(r && memoize_res && new_children_memoized, r); + cache_result(m_filter_equal_cache, d, r); + return r; +} + +imdd * imdd_manager::mk_filter_equal_main(imdd * d, unsigned vidx, unsigned value, bool destructive, bool memoize_res) { + unsigned arity = d->get_arity(); + SASSERT(vidx >= 0 && vidx < arity); + if (d->empty()) + return d; + delay_dealloc delay(*this); + m_filter_equal_cache.reset(); + imdd * r = mk_filter_equal_core(d, vidx, value, destructive, memoize_res); + if (r == 0) + return _mk_empty(arity); + else + return r; +} + + +/** + \brief create map from imdd nodes to the interval of variables that are covered by variable. + +*/ + +static void mk_interval_set_intersect(sl_manager_base &m, sl_interval_set const& src, unsigned b, unsigned e, sl_interval_set& dst) { + sl_interval_set::iterator it = src.find_geq(b); + sl_interval_set::iterator end = src.end(); + for (; it != end; ++it) { + unsigned b1 = it->begin_key(); + unsigned e1 = it->end_key(); + if (e < b1) { + break; + } + if (b1 < b) { + b1 = b; + } + if (e < e1) { + e1 = e; + } + SASSERT(b <= b1 && b1 <= e1 && e1 <= e); + dst.insert(m, b1, e1); + } +} + +static void mk_interval_set_union(sl_manager_base &m, sl_interval_set& dst, sl_interval_set const& src) { + sl_interval_set::iterator it = src.begin(), end = src.end(); + for (; it != end; ++it) { + dst.insert(m, it->begin_key(), it->end_key()); + } +} + +void imdd_manager::reset_fi_intervals(sl_imanager& m) { + for (unsigned i = 0; i < m_alloc_is.size(); ++i) { + m_alloc_is[i]->deallocate(m); + dealloc(m_alloc_is[i]); + } + m_alloc_is.reset(); + m_imdd2interval_set.reset(); +} + +sl_interval_set const* imdd_manager::init_fi_intervals(sl_imanager& m, imdd* d, unsigned var, unsigned num_found) { + sl_interval_set* result = 0; +#define ALLOC_RESULT() if (!result) { result = alloc(sl_interval_set, m); m_alloc_is.push_back(result); } + if (m_imdd2interval_set.find(d, result)) { + return result; + } + bool _is_fi_var = is_fi_var(var); + unsigned new_num_found = _is_fi_var?num_found+1:num_found; + bool last_fi_var = (new_num_found == m_fi_num_vars); + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + for (; it != end; ++it) { + imdd_children::entry const & curr_entry = *it; + unsigned b = curr_entry.begin_key(); + unsigned e = curr_entry.end_key(); + + if (last_fi_var) { + ALLOC_RESULT(); + result->insert(m, b, e, 1); + } + else { + SASSERT(d->get_arity() > 1); + imdd* curr_child = curr_entry.val(); + sl_interval_set const* is2 = init_fi_intervals(m, curr_child, var+1, new_num_found); + if (!is2) { + continue; + } + if (_is_fi_var) { + sl_interval_set is3(m); + mk_interval_set_intersect(m, *is2, b, e, is3); + if (!is3.empty()) { + ALLOC_RESULT(); + mk_interval_set_union(m, *result, is3); + } + is3.deallocate(m); + } + else { + if (is2 && result != is2) { + ALLOC_RESULT(); + mk_interval_set_union(m, *result, *is2); + } + } + } + } + m_imdd2interval_set.insert(d, result); + return result; +} + +inline mk_fi_result mk(imdd * d) { + mk_fi_result r; + r.m_d = d; + return r; +} + +inline mk_fi_result mk(fi_cache_entry * e) { + mk_fi_result r; + r.m_entry = e; + return r; +} + +fi_cache_entry * imdd_manager::mk_fi_cache_entry(imdd * d, unsigned lower, unsigned upper, unsigned num_pairs, imdd_value_pair pairs[]) { + void * mem = m_fi_entries.allocate(sizeof(fi_cache_entry) + sizeof(imdd_value_pair) * num_pairs); + DEBUG_CODE({ + for (unsigned i = 0; i < num_pairs; i++) { + SASSERT(pairs[i].first != 0); + } + }); + return new (mem) fi_cache_entry(d, lower, upper, num_pairs, pairs); +} + +struct imdd_value_pair_lt_value { + bool operator()(imdd_value_pair const & p1, imdd_value_pair const & p2) const { + return p1.second < p2.second; + } +}; + +// Review note: identical nodes should be unit intervals? +// 1 -> 1 -> x + +mk_fi_result imdd_manager::mk_filter_identical_core(imdd * d, unsigned var, unsigned num_found, unsigned lower, unsigned upper, + bool destructive, bool memoize_res) { + TRACE("imdd", tout << "#" << d->get_id() << " var: " << var << " num_found: " << num_found << + " lower: " << lower << " upper: " << upper << "\n";); + + unsigned arity = d->get_arity(); +#define MK_EMPTY_ENTRY (num_found == 0 && destructive_update_at(destructive, d))?mk(static_cast(0)):mk(static_cast(0)) + sl_interval_set* node_ranges = m_imdd2interval_set.find(d); + if (!node_ranges) { + return MK_EMPTY_ENTRY; + } + sl_interval_set::iterator sl_it = node_ranges->find_geq(lower); + if (sl_it == node_ranges->end() || upper < sl_it->begin_key()) { + return MK_EMPTY_ENTRY; + } + + bool new_children_memoized = true; + bool _is_fi_var = is_fi_var(var); + if (num_found == 0) { + SASSERT(arity > 1); + if (destructive_update_at(destructive, d)) { + sbuffer to_insert; + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + for (; it != end; ++it) { + imdd * curr_child = it->val(); + if (_is_fi_var) { + fi_cache_entry * child_entry = (mk_filter_identical_core(curr_child, + var+1, + 1, + it->begin_key(), + it->end_key(), + false, + memoize_res)).m_entry; + for (unsigned i = 0; child_entry && i < child_entry->m_num_result; i++) { + imdd * new_child = child_entry->m_result[i].first; + unsigned value = child_entry->m_result[i].second; + to_insert.push_back(entry(value, value, new_child)); + } + } + else { + imdd * new_child = (mk_filter_identical_core(curr_child, var+1, 0, 0, UINT_MAX, false, memoize_res)).m_d; + if (new_child != 0) { + new_child->inc_ref(); + to_insert.push_back(entry(it->begin_key(), it->end_key(), new_child)); + } + } + } + if (to_insert.empty()) { + return mk(static_cast(0)); + } + d->replace_children(m_sl_manager, to_insert); + return mk(d); + } + else { + // num_found == 0 && variable is not part of the filter && no destructive update. + imdd * r = 0; + if (is_cached(m_fi_top_cache, d, r)) + return mk(r); + r = _mk_empty(arity); + imdd_children::push_back_proc push_back(m_sl_manager, r->m_children); + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + for (; it != end; ++it) { + imdd * curr_child = it->val(); + if (_is_fi_var) { + fi_cache_entry * entry = (mk_filter_identical_core(curr_child, + var+1, + 1, + it->begin_key(), + it->end_key(), + false, + memoize_res)).m_entry; + for (unsigned i = 0; entry && i < entry->m_num_result; i++) { + imdd * new_child = entry->m_result[i].first; + unsigned value = entry->m_result[i].second; + push_back(value, value, new_child); + if (!new_child->is_memoized()) + new_children_memoized = false; + } + } + else { + imdd * new_child = (mk_filter_identical_core(curr_child, var+1, 0, 0, UINT_MAX, false, memoize_res)).m_d; + if (new_child != 0) { + push_back(it->begin_key(), it->end_key(), new_child); + if (!new_child->is_memoized()) + new_children_memoized = false; + } + } + } + if (r->empty()) { + delete_imdd(r); + r = 0; + } + r = memoize_new_imdd_if(r && memoize_res && new_children_memoized, r); + cache_result(m_fi_top_cache, d, r); + return mk(r); + } + } + else { + SASSERT(num_found > 0); + fi_cache_entry d_entry(d, lower, upper); + fi_cache_entry * r_entry; + if (d->is_shared() && m_fi_bottom_cache.find(&d_entry, r_entry)) + return mk(r_entry); + sbuffer result; + if (_is_fi_var) { + unsigned new_num_found = num_found + 1; + bool last_fi_var = (new_num_found == m_fi_num_vars); + imdd_children::iterator it = d->m_children.find_geq(lower); + imdd_children::iterator end = d->end_children(); + for (; it != end; ++it) { + imdd * curr_child = it->val(); + unsigned curr_lower = it->begin_key(); + unsigned curr_upper = it->end_key(); + if (curr_lower < lower) + curr_lower = lower; + if (curr_upper > upper) + curr_upper = upper; + if (curr_lower <= curr_upper) { + if (last_fi_var) { + for (unsigned i = curr_lower; i <= curr_upper; i++) { + imdd * new_d = _mk_empty(arity); + add_child(new_d, i, curr_child); + new_d = memoize_new_imdd_if(memoize_res && (curr_child == 0 || curr_child->is_memoized()), new_d); + result.push_back(imdd_value_pair(new_d, i)); + } + } + else { + fi_cache_entry * new_entry = (mk_filter_identical_core(curr_child, + var+1, + new_num_found, + curr_lower, + curr_upper, + false, + memoize_res)).m_entry; + for (unsigned i = 0; new_entry && i < new_entry->m_num_result; i++) { + SASSERT(new_entry->m_result[i].first != 0); + imdd * curr_child = new_entry->m_result[i].first; + unsigned value = new_entry->m_result[i].second; + imdd * new_d = _mk_empty(arity); + add_child(new_d, value, curr_child); + SASSERT(curr_child != 0); + new_d = memoize_new_imdd_if(memoize_res && curr_child->is_memoized(), new_d); + result.push_back(imdd_value_pair(new_d, value)); + } + } + } + if (curr_upper == upper) + break; + } + } + else { + SASSERT(arity > 1); + u_map value2imdd; + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + for (; it != end; ++it) { + imdd * curr_child = it->val(); + SASSERT(curr_child != 0); + fi_cache_entry * new_entry = (mk_filter_identical_core(curr_child, + var+1, + num_found, + lower, + upper, + false, + memoize_res)).m_entry; + for (unsigned i = 0; new_entry && i < new_entry->m_num_result; i++) { + unsigned value = new_entry->m_result[i].second; + imdd * new_child = new_entry->m_result[i].first; + imdd * new_d = 0; + if (!value2imdd.find(value, new_d)) { + new_d = _mk_empty(arity); + value2imdd.insert(value, new_d); + result.push_back(imdd_value_pair(new_d, value)); + } + SASSERT(new_d != 0); + add_child(new_d, it->begin_key(), it->end_key(), new_child); + } + } + std::sort(result.begin(), result.end(), imdd_value_pair_lt_value()); + } + r_entry = mk_fi_cache_entry(d, lower, upper, result.size(), result.c_ptr()); + if (d->is_shared()) + m_fi_bottom_cache.insert(r_entry); + return mk(r_entry); + } +} + +imdd * imdd_manager::mk_filter_identical_main(imdd * d, unsigned num_vars, unsigned * vars, bool destructive, bool memoize_res) { + if (d->empty() || num_vars < 2) + return d; + TRACE("imdd", + tout << "vars: "; + for (unsigned i = 0; i < num_vars; ++i) { + tout << vars[i] << " "; + } + tout << "\n"; + tout << "rows: " << get_num_rows(d) << "\n"; + display_ll(tout, d); ); + unsigned arity = d->get_arity(); + DEBUG_CODE(for (unsigned i = 0; i < num_vars; ++i) SASSERT(vars[i] < arity);); + m_fi_num_vars = num_vars; + m_fi_begin_vars = vars; + m_fi_end_vars = vars + num_vars; + delay_dealloc delay(*this); + m_fi_bottom_cache.reset(); + m_fi_top_cache.reset(); + m_fi_entries.reset(); + + sl_imanager imgr; + init_fi_intervals(imgr, d, 0, 0); + + TRACE("imdd_verbose", + ptr_addr_hashtable ht; + imdd2intervals::iterator it = m_imdd2interval_set.begin(); + imdd2intervals::iterator end = m_imdd2interval_set.end(); + for (; it != end; ++it) { + tout << it->m_key->get_id() << " "; + if (it->m_value) { + if (ht.contains(it->m_value)) { + tout << "dup\n"; + continue; + } + ht.insert(it->m_value); + sl_interval_set::iterator sit = it->m_value->begin(); + sl_interval_set::iterator send = it->m_value->end(); + for (; sit != send; ++sit) { + tout << "[" << sit->begin_key() << ":" << sit->end_key() << "] "; + } + } + else { + tout << "{}"; + } + tout << "\n"; + }); + mk_fi_result r = mk_filter_identical_core(d, 0, 0, 0, UINT_MAX, destructive, memoize_res); + reset_fi_intervals(imgr); + + TRACE("imdd", if (r.m_d) display_ll(tout << "result\n", r.m_d);); + if (r.m_d == 0) + return _mk_empty(arity); + else + return r.m_d; +} + +void imdd_manager::swap_in(imdd * d, imdd_ref& r, unsigned num_vars, unsigned * vars) { + // variables are sorted. + DEBUG_CODE(for (unsigned i = 0; i + 1 < num_vars; ++i) SASSERT(vars[i] < vars[i+1]);); + imdd_ref tmp1(*this), tmp2(*this); + tmp1 = d; + SASSERT(num_vars > 1); + unsigned v1 = vars[0]+1; // next position to swap to. + for (unsigned i = 1; i < num_vars; ++i) { + unsigned v2 = vars[i]; + SASSERT(v1 <= v2); + for (unsigned j = v2-1; j >= v1; --j) { + mk_swap(tmp1, tmp2, j); + tmp1 = tmp2; + } + ++v1; + } + TRACE("imdd", + for (unsigned i = 0; i < num_vars; ++i) tout << vars[i] << " "; + tout << "\n"; + display_ll(tout << "in\n", d); + display_ll(tout << "out\n", tmp1); + tout << "\n";); + r = tmp1; +} + +void imdd_manager::swap_out(imdd * d, imdd_ref& r, unsigned num_vars, unsigned * vars) { + // variables are sorted. + DEBUG_CODE(for (unsigned i = 0; i + 1 < num_vars; ++i) SASSERT(vars[i] < vars[i+1]);); + imdd_ref tmp1(*this), tmp2(*this); + tmp1 = d; + SASSERT(num_vars > 1); + unsigned v1 = vars[0]+num_vars-1; // position of next variable to be swapped. + for (unsigned i = num_vars; i > 1; ) { + --i; + unsigned v2 = vars[i]; + for (unsigned j = v1; j < v2; ++j) { + mk_swap(tmp1, tmp2, j); + tmp1 = tmp2; + } + --v1; + } + TRACE("imdd", + for (unsigned i = 0; i < num_vars; ++i) tout << vars[i] << " "; + tout << "\n"; + display_ll(tout << "out\n", d); + display_ll(tout << "in\n", tmp1); + tout << "\n";); + r = tmp1; +} + +void imdd_manager::filter_identical_core2(imdd* d, unsigned num_vars, unsigned b, unsigned e, ptr_vector& ch) { + SASSERT(num_vars > 0); + imdd* r = 0; + + imdd_children::iterator it = d->m_children.find_geq(b); + imdd_children::iterator end = d->m_children.end(); + + for (; it != end; ++it) { + unsigned b1 = it->begin_key(); + unsigned e1 = it->end_key(); + if (e < b1) { + break; + } + if (b1 < b) { + b1 = b; + } + if (e <= e1) { + e1 = e; + } + SASSERT(b <= b1 && b1 <= e1 && e1 <= e); + imdd* curr_child = it->val(); + if (num_vars == 1) { + for (unsigned i = b1; i <= e1; ++i) { + r = _mk_empty(d->get_arity()); + add_child(r, i, i, it->val()); + r = memoize_new_imdd_if(!it->val() || it->val()->is_memoized(), r); + ch.push_back(r); + } + continue; + } + ptr_vector ch2; + filter_identical_core2(curr_child, num_vars-1, b1, e1, ch2); + for (unsigned i = 0; i < ch2.size(); ++i) { + r = _mk_empty(d->get_arity()); + unsigned key = ch2[i]->begin_children()->begin_key(); + SASSERT(ch2[i]->begin_children()->end_key() == key); + add_child(r, key, key, ch2[i]); + r = memoize_new_imdd_if(ch2[i]->is_memoized(), r); + ch.push_back(r); + } + } +} + +imdd* imdd_manager::filter_identical_core2(imdd* d, unsigned var, unsigned num_vars, bool memoize_res) { + imdd* r = 0; + if (m_filter_identical_cache.find(d, r)) { + return r; + } + + bool children_memoized = true; + + r = _mk_empty(d->get_arity()); + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + imdd_children::push_back_proc push_back(m_sl_manager, r->m_children); + + if (var > 0) { + for (; it != end; ++it) { + imdd* curr_child = it->val(); + imdd* new_child = filter_identical_core2(curr_child, var-1, num_vars, memoize_res); + if (new_child) { + push_back(it->begin_key(), it->end_key(), new_child); + if (!new_child->is_memoized()) { + children_memoized = false; + } + } + } + } + else { + ptr_vector ch; + + for (; it != end; ++it) { + imdd* curr_child = it->val(); + filter_identical_core2(curr_child, num_vars-1, it->begin_key(), it->end_key(), ch); + } + for (unsigned i = 0; i < ch.size(); ++i) { + unsigned key = ch[i]->begin_children()->begin_key(); + SASSERT(ch[i]->begin_children()->end_key() == key); + push_back(key, key, ch[i]); + if (!ch[i]->is_memoized()) { + children_memoized = false; + } + } + } + if (r->empty()) { + delete_imdd(r); + r = 0; + } + m_filter_identical_cache.insert(d, r); + r = memoize_new_imdd_if(r && memoize_res && children_memoized, r); + return r; +} + +void imdd_manager::filter_identical_main2(imdd * d, imdd_ref& r, unsigned num_vars, unsigned * vars, bool destructive, bool memoize_res) { + m_filter_identical_cache.reset(); + imdd_ref tmp1(*this), tmp2(*this); + swap_in(d, tmp1, num_vars, vars); + tmp2 = filter_identical_core2(tmp1, vars[0], num_vars, memoize_res); + if (!tmp2) { + r = _mk_empty(d->get_arity()); + } + else { + swap_out(tmp2, r, num_vars, vars); + } + m_filter_identical_cache.reset(); +} + + +void imdd_manager::mk_project_core(imdd * d, imdd_ref & r, unsigned var, unsigned num_found, bool memoize_res) { + unsigned arity = d->get_arity(); + bool _is_proj_var = is_proj_var(var); + + if (_is_proj_var && arity == (m_proj_num_vars - num_found)) { + r = 0; // 0 here means the unit element (aka the 0-tuple). + return; + } + + imdd * _r = 0; + if (is_cached(m_proj_cache, d, _r)) { + r = _r; + return; + } + + bool new_children_memoized = true; + if (_is_proj_var) { + SASSERT(d != 0); + SASSERT(d->get_arity() > 1); + unsigned new_var = var + 1; + unsigned new_num_found = num_found + 1; + bool found_all = new_num_found == m_proj_num_vars; + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + imdd_ref tmp_r(*this); + CTRACE("imdd", it == end, display_ll(tout, d);); + SASSERT(it != end); + for (; it != end; ++it) { + imdd_ref new_child(*this); + if (found_all) + new_child = it->val(); + else + mk_project_core(it->val(), new_child, new_var, new_num_found, memoize_res); + + if (tmp_r == 0) + tmp_r = new_child; + else + mk_union_core(tmp_r, new_child, tmp_r, memoize_res); + SASSERT(tmp_r != 0); + } + SASSERT(tmp_r != 0); + SASSERT(tmp_r->get_arity() == d->get_arity() - (m_proj_num_vars - num_found)); + r = tmp_r; + } + else { + SASSERT(num_found < m_proj_num_vars); + unsigned new_var = var+1; + _r = _mk_empty(arity - (m_proj_num_vars - num_found)); + imdd_children::push_back_proc push_back(m_sl_manager, _r->m_children); + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + SASSERT(it != end); + for (; it != end; ++it) { + imdd * curr_child = it->val(); + imdd_ref new_child(*this); + mk_project_core(curr_child, new_child, new_var, num_found, memoize_res); + push_back(it->begin_key(), it->end_key(), new_child); + if (new_child != 0 && !new_child->is_memoized()) + new_children_memoized = false; + } + SASSERT(!_r->empty()); + _r = memoize_new_imdd_if(memoize_res && new_children_memoized, _r); + r = _r; + } + cache_result(m_proj_cache, d, r); +} + +void imdd_manager::mk_project_dupdt_core(imdd_ref & d, unsigned var, unsigned num_found, bool memoize_res) { + unsigned arity = d->get_arity(); + bool _is_proj_var = is_proj_var(var); + + if (_is_proj_var && arity == (m_proj_num_vars - num_found)) { + d = 0; // 0 here means the unit element (aka the 0-tuple). + return; + } + + if (!destructive_update_at(true, d)) { + mk_project_core(d, d, var, num_found, memoize_res); + return; + } + + if (_is_proj_var) { + SASSERT(d != 0); + SASSERT(d->get_arity() > 1); + unsigned new_var = var + 1; + unsigned new_num_found = num_found + 1; + bool found_all = new_num_found == m_proj_num_vars; + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + imdd_ref r(*this); + for (; it != end; ++it) { + imdd_ref new_child(*this); + it.take_curr_ownership(m_sl_manager, new_child); + if (!found_all) { + mk_project_dupdt_core(new_child, new_var, new_num_found, memoize_res); + } + if (r == 0) + r = new_child; + else + mk_union_core_dupdt(r, new_child, memoize_res); + } + // we traverse the children of d, and decrement the reference counter of each one of them. + d->m_children.reset_no_decref(m_sl_manager); + d = r; + SASSERT(r != 0); + SASSERT(r->get_arity() == d->get_arity() - (m_proj_num_vars - num_found)); + } + else { + SASSERT(!_is_proj_var); + sbuffer to_insert; + unsigned new_var = var+1; + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + for (; it != end; ++it) { + imdd_ref new_child(*this); + it.take_curr_ownership(m_sl_manager, new_child); + mk_project_dupdt_core(new_child, new_var, num_found, memoize_res); + if (new_child) + new_child->inc_ref(); // protect new child, since we will insert it into to_insert + to_insert.push_back(entry(it->begin_key(), it->end_key(), new_child)); + } + SASSERT(!to_insert.empty()); + d->replace_children(m_sl_manager, to_insert); + } +} + +void imdd_manager::mk_project_init(unsigned num_vars, unsigned * vars) { + m_proj_num_vars = num_vars; + m_proj_begin_vars = vars; + m_proj_end_vars = vars + num_vars; + m_proj_cache.reset(); + reset_union_cache(); +} + +void imdd_manager::mk_project_dupdt(imdd_ref & d, unsigned num_vars, unsigned * vars, bool memoize_res) { + if (num_vars == 0) { + return; + } + unsigned arity = d->get_arity(); + SASSERT(num_vars < arity); + if (d->empty()) { + d = _mk_empty(arity - num_vars); + return; + } + delay_dealloc delay(*this); + mk_project_init(num_vars, vars); + mk_project_dupdt_core(d, 0, 0, memoize_res); +} + +void imdd_manager::mk_project(imdd * d, imdd_ref & r, unsigned num_vars, unsigned * vars, bool memoize_res) { + if (num_vars == 0) { + r = d; + return; + } + unsigned arity = d->get_arity(); + SASSERT(num_vars < arity); + if (d->empty()) { + r = _mk_empty(arity - num_vars); + return; + } + delay_dealloc delay(*this); + mk_project_init(num_vars, vars); + mk_project_core(d, r, 0, 0, memoize_res); + + STRACE("imdd_trace", tout << "mk_project(0x" << d << ", 0x" << r.get() << ", "; + for (unsigned i = 0; i < num_vars; i++) tout << vars[i] << ", "; + tout << memoize_res << ");\n";); +} + +void imdd_manager::mk_join( + imdd * d1, imdd * d2, imdd_ref & r, + unsigned_vector const& vars1, unsigned_vector const& vars2, + bool memoize_res) { + SASSERT(vars1.size() == vars2.size()); + imdd_ref tmp(*this); + unsigned d1_arity = d1->get_arity(); + mk_product(d1, d2, tmp); + for (unsigned i = 0; i < vars1.size(); ++i) { + unsigned vars[2] = { vars1[i], vars2[i] + d1_arity }; + mk_filter_identical_dupdt(tmp, 2, vars); + } + r = tmp; // memoize_new_imdd_if(memoize_res, tmp); +} + +void imdd_manager::mk_join_project( + imdd * d1, imdd * d2, imdd_ref & r, + unsigned_vector const& vars1, unsigned_vector const& vars2, + unsigned_vector const& proj_vars, bool memoize_res) { + SASSERT(vars1.size() == vars2.size()); + imdd_ref tmp(*this); + unsigned d1_arity = d1->get_arity(); + mk_product(d1, d2, tmp); + for (unsigned i = 0; i < vars1.size(); ++i) { + unsigned vars[2] = { vars1[i], vars2[i] + d1_arity }; + mk_filter_identical(tmp, tmp, 2, vars); + } + mk_project(tmp, tmp, proj_vars.size(), proj_vars.c_ptr()); + TRACE("dl", + tout << "vars: "; + for (unsigned i = 0; i < vars1.size(); ++i) tout << vars1[i] << ":" << vars2[i] << " "; + tout << "\n"; + tout << "proj: "; + for (unsigned i = 0; i < proj_vars.size(); ++i) tout << proj_vars[i] << " "; + tout << "\n"; + tout << "arity: " << d1->get_arity() << " + " << d2->get_arity() << "\n"; + display_ll(tout << "d1\n", d1); + display_ll(tout << "\nd2\n", d2); + display_ll(tout << "\nresult\n", tmp); + tout << "\n"; + ); + + r = tmp; // memoize_new_imdd_if(memoize_res, tmp); +} + + + +imdd * imdd_manager::mk_swap_new_child(unsigned lower, unsigned upper, imdd * grandchild) { + if (m_swap_new_child == 0) { + m_swap_new_child = _mk_empty(grandchild == 0 ? 1 : grandchild->get_arity() + 1); + add_child(m_swap_new_child, lower, upper, grandchild); + if (grandchild && !grandchild->is_memoized()) + m_swap_granchildren_memoized = false; + inc_ref(m_swap_new_child); + } + SASSERT(m_swap_new_child != 0); + return m_swap_new_child; +} + +void imdd_manager::mk_swap_acc1_dupdt(imdd_ref & d, unsigned lower, unsigned upper, imdd * grandchild, bool memoize_res) { + SASSERT(d->get_ref_count() == 1); + sbuffer to_insert; + imdd_children::ext_iterator it; + imdd_children::ext_iterator end; + d->m_children.move_geq(it, lower); + while (it != end && lower <= upper) { + imdd_children::entry const & curr_entry = *it; + unsigned curr_entry_begin_key = curr_entry.begin_key(); + unsigned curr_entry_end_key = curr_entry.end_key(); + imdd * curr_entry_val = curr_entry.val(); + bool move_head = true; + if (upper < curr_entry_begin_key) + break; + if (lower < curr_entry_begin_key) { + to_insert.push_back(entry(lower, curr_entry_begin_key - 1, grandchild)); + lower = curr_entry_begin_key; + } + SASSERT(lower >= curr_entry_begin_key); + imdd * curr_grandchild = curr_entry_val; + imdd_ref new_grandchild(*this); + SASSERT((curr_grandchild == 0) == (grandchild == 0)); + if (curr_grandchild != 0) { + bool cover = lower == curr_entry_begin_key && upper >= curr_entry_end_key; + // If cover == true, then the curr_child is completely covered, and it is not needed anymore. + // So, we can perform a destructive update. + if (cover) { + new_grandchild = curr_grandchild; // take over the ownership of curr_grandchild + it.erase(m_sl_manager); + move_head = false; // it.erase is effectively moving the head. + mk_union_core_dupdt(new_grandchild, grandchild, memoize_res); + } + else { + mk_union_core(curr_grandchild, grandchild, new_grandchild, memoize_res); + } + if (!new_grandchild->is_memoized()) + m_swap_granchildren_memoized = false; + // protect new_grandchild, since it will be stored in to_insert. + new_grandchild->inc_ref(); + } + if (upper >= curr_entry_end_key) { + to_insert.push_back(entry(lower, curr_entry_end_key, new_grandchild)); + } + else { + to_insert.push_back(entry(lower, upper, new_grandchild)); + } + lower = curr_entry_end_key + 1; + if (move_head) + ++it; + } + svector::iterator it2 = to_insert.begin(); + svector::iterator end2 = to_insert.end(); + for (; it2 != end2; ++it2) { + imdd_children::entry const & curr_entry = *it2; + add_child(d, curr_entry.begin_key(), curr_entry.end_key(), curr_entry.val()); + if (curr_entry.val()) + curr_entry.val()->dec_ref(); // to_insert will be destroyed, so decrement ref. + } + if (lower <= upper) { + add_child(d, lower, upper, grandchild); + } +} + +void imdd_manager::mk_swap_acc1(imdd * d, imdd_ref & r, unsigned lower, unsigned upper, imdd * grandchild, bool memoize_res) { + copy(d, r); + imdd_children::iterator it = d->m_children.find_geq(lower); + imdd_children::iterator end = d->end_children(); + for (; it != end && lower <= upper; ++it) { + imdd_children::entry const & curr_entry = *it; + if (upper < curr_entry.begin_key()) + break; + if (lower < curr_entry.begin_key()) { + SASSERT(lower <= curr_entry.begin_key() - 1); + add_child(r, lower, curr_entry.begin_key() - 1, grandchild); + lower = curr_entry.begin_key(); + } + SASSERT(lower >= curr_entry.begin_key()); + imdd * curr_grandchild = curr_entry.val(); + SASSERT((curr_grandchild == 0) == (grandchild == 0)); + imdd_ref new_curr_grandchild(*this); + mk_union_core(curr_grandchild, grandchild, new_curr_grandchild, memoize_res); + if (new_curr_grandchild && !new_curr_grandchild->is_memoized()) + m_swap_granchildren_memoized = false; + if (upper >= curr_entry.end_key()) { + add_child(r, lower, curr_entry.end_key(), new_curr_grandchild); + } + else { + SASSERT(upper < curr_entry.end_key()); + SASSERT(lower <= upper); + add_child(r, lower, upper, new_curr_grandchild); + } + lower = curr_entry.end_key() + 1; + } + if (lower <= upper) { + add_child(r, lower, upper, grandchild); + } +} + +/** + \brief Auxiliary function for mk_swap_core +*/ +void imdd_manager::mk_swap_acc2(imdd_ref & r, unsigned lower1, unsigned upper1, unsigned lower2, unsigned upper2, imdd * grandchild, bool memoize_res) { + SASSERT((r->get_arity() == 2 && grandchild == 0) || + (r->get_arity() == grandchild->get_arity() + 2)); + SASSERT(r->get_ref_count() <= 1); // we perform destructive updates on r. + SASSERT(!r->is_memoized()); + TRACE("mk_swap_bug", + tout << mk_ll_pp(r, *this) << "\n"; + tout << "adding\n[" << lower1 << ", " << upper1 << "] -> [" << lower2 << ", " << upper2 << "] ->\n"; + tout << mk_ll_pp(grandchild, *this) << "\n";); + + sbuffer to_insert; + imdd_children::ext_iterator it; + imdd_children::ext_iterator end; + r->m_children.move_geq(it, lower1); + + while(it != end && lower1 <= upper1) { + imdd_children::entry const & curr_entry = *it; + unsigned curr_entry_begin_key = curr_entry.begin_key(); + unsigned curr_entry_end_key = curr_entry.end_key(); + imdd * curr_entry_val = curr_entry.val(); + bool move_head = true; + if (upper1 < curr_entry_begin_key) + break; + if (lower1 < curr_entry_begin_key) { + imdd * new_child = mk_swap_new_child(lower2, upper2, grandchild); + SASSERT(lower1 <= curr_entry_begin_key - 1); + add_child(r, lower1, curr_entry_begin_key - 1, new_child); + lower1 = curr_entry_begin_key; + } + SASSERT(lower1 >= curr_entry_begin_key); + imdd * curr_child = curr_entry_val; + SASSERT(curr_child != 0); + SASSERT(!curr_child->is_memoized()); + imdd_ref new_curr_child(*this); + bool destructive = curr_child->get_ref_count() == 1 && lower1 == curr_entry_begin_key && upper1 >= curr_entry_end_key; + if (destructive) { + new_curr_child = curr_child; // take over curr_child. + it.erase(m_sl_manager); + move_head = false; // it.erase is effectively moving the head. + mk_swap_acc1_dupdt(new_curr_child, lower2, upper2, grandchild, memoize_res); + } + else { + mk_swap_acc1(curr_child, new_curr_child, lower2, upper2, grandchild, memoize_res); + } + new_curr_child->inc_ref(); // it will be stored in to_insert + if (upper1 >= curr_entry_end_key) { + to_insert.push_back(entry(lower1, curr_entry_end_key, new_curr_child)); + } + else { + to_insert.push_back(entry(lower1, upper1, new_curr_child)); + } + lower1 = curr_entry_end_key + 1; + if (move_head) + ++it; + } + svector::iterator it2 = to_insert.begin(); + svector::iterator end2 = to_insert.end(); + for (; it2 != end2; ++it2) { + imdd_children::entry const & curr_entry = *it2; + add_child(r, curr_entry.begin_key(), curr_entry.end_key(), curr_entry.val()); + SASSERT(curr_entry.val()); + curr_entry.val()->dec_ref(); // to_insert will be destroyed, so decrement ref. + } + if (lower1 <= upper1) { + imdd * new_child = mk_swap_new_child(lower2, upper2, grandchild); + add_child(r, lower1, upper1, new_child); + } + TRACE("mk_swap_bug", tout << "after mk_swap_acc2\n" << mk_ll_pp(r, *this) << "\n";); +} + +/** + \brief Memoize the given IMDD assuming all grandchildren of d are memoized. +*/ +imdd * imdd_manager::mk_swap_memoize(imdd * d) { + if (d->is_memoized()) + return d; + bool children_memoized = true; + imdd * new_d = _mk_empty(d->get_arity()); + imdd_children::push_back_proc push_back(m_sl_manager, new_d->m_children); + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + for (; it != end; ++it) { + imdd * child = it->val(); + imdd * new_child = memoize(child); + if (!new_child->is_memoized()) + children_memoized = false; + push_back(it->begin_key(), it->end_key(), new_child); + } + + if (children_memoized && is_simple_node(new_d)) { + imdd * new_can_d = memoize(new_d); + if (new_can_d != new_d) { + SASSERT(new_d->get_ref_count() == 0); + delete_imdd(new_d); + } + new_d = new_can_d; + } + return new_d; +} + +/** + \brief Swap the two top vars. +*/ +void imdd_manager::mk_swap_top_vars(imdd * d, imdd_ref & r, bool memoize_res) { + SASSERT(d->get_arity() >= 2); + r = _mk_empty(d->get_arity()); + m_swap_granchildren_memoized = true; + m_swap_new_child = 0; + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + for (; it != end; ++it) { + imdd * curr_child = it->val(); + SASSERT(curr_child != 0); + SASSERT(m_swap_new_child == 0); + imdd_children::iterator it2 = curr_child->begin_children(); + imdd_children::iterator end2 = curr_child->end_children(); + for (; it2 != end2; ++it2) { + imdd * grandchild = it2->val(); + mk_swap_acc2(r, it2->begin_key(), it2->end_key(), it->begin_key(), it->end_key(), grandchild, memoize_res); + } + if (m_swap_new_child != 0) { + dec_ref(m_swap_new_child); + m_swap_new_child = 0; + } + } + + if (memoize_res && m_swap_granchildren_memoized) { + r = mk_swap_memoize(r); + } + TRACE("mk_swap_bug", tout << "result:\n" << mk_ll_pp(r, *this) << "\n";); +} + +void imdd_manager::mk_swap_core(imdd * d, imdd_ref & r, unsigned vidx, bool memoize_res) { + SASSERT(d); + unsigned arity = d->get_arity(); + SASSERT(arity > 1); + + imdd * _r = 0; + if (is_cached(m_swap_cache, d, _r)) { + r = _r; + return; + } + + if (vidx == 0) { + mk_swap_top_vars(d, r, memoize_res); + } + else { + SASSERT(vidx > 0); + bool new_children_memoized = true; + unsigned new_vidx = vidx - 1; + _r = _mk_empty(arity); + imdd_children::push_back_proc push_back(m_sl_manager, _r->m_children); + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + SASSERT(it != end); + for (; it != end; ++it) { + imdd * curr_child = it->val(); + imdd_ref new_child(*this); + mk_swap_core(curr_child, new_child, new_vidx, memoize_res); + push_back(it->begin_key(), it->end_key(), new_child); + if (new_child != 0 && !new_child->is_memoized()) + new_children_memoized = false; + } + SASSERT(!_r->empty()); + _r = memoize_new_imdd_if(memoize_res && new_children_memoized, _r); + r = _r; + } + cache_result(m_swap_cache, d, r); +} + +void imdd_manager::mk_swap_dupdt_core(imdd_ref & d, unsigned vidx, bool memoize_res) { + SASSERT(d); + unsigned arity = d->get_arity(); + SASSERT(arity > 1); + + if (!destructive_update_at(true, d)) { + mk_swap_core(d, d, vidx, memoize_res); + return; + } + + if (vidx == 0) { + mk_swap_top_vars(d, d, memoize_res); + return; + } + + SASSERT(vidx > 0); + sbuffer to_insert; + unsigned new_vidx = vidx-1; + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + for (; it != end; ++it) { + imdd_ref new_child(*this); + it.take_curr_ownership(m_sl_manager, new_child); + mk_swap_dupdt_core(new_child, new_vidx, memoize_res); + new_child->inc_ref(); // protect new child, since we insert into to_insert + to_insert.push_back(entry(it->begin_key(), it->end_key(), new_child)); + } + SASSERT(!to_insert.empty()); + d->replace_children(m_sl_manager, to_insert); +} + +void imdd_manager::mk_swap_dupdt(imdd_ref & d, unsigned vidx, bool memoize_res) { + SASSERT(d); + unsigned arity = d->get_arity(); + SASSERT(arity > 1); + SASSERT(vidx < arity - 1); + if (d->empty()) { + return; + } + m_swap_cache.reset(); + reset_union_cache(); + delay_dealloc delay(*this); + mk_swap_dupdt_core(d, vidx, memoize_res); +} + +void imdd_manager::mk_swap(imdd * d, imdd_ref & r, unsigned vidx, bool memoize_res) { + SASSERT(d); + unsigned arity = d->get_arity(); + SASSERT(arity > 1); + SASSERT(vidx < arity - 1); + if (d->empty()) { + r = d; + return; + } + TRACE("mk_swap_bug", tout << "mk_swap: " << vidx << "\n"; display_ll(tout, d); tout << "\n";); + TRACE("mk_swap_bug2", tout << "mk_swap: " << vidx << "\n"; display_ll(tout, d); tout << "\n";); + m_swap_cache.reset(); + reset_union_cache(); + delay_dealloc delay(*this); + mk_swap_core(d, r, vidx, memoize_res); + TRACE("mk_swap_bug2", tout << "mk_swap result\n"; display_ll(tout, r); tout << "\n";); + STRACE("imdd_trace", tout << "mk_swap(0x" << d << ", 0x" << r.get() << ", " << vidx << ", " << memoize_res << ");\n";); +} + +imdd_manager::iterator::iterator(imdd_manager const & m, imdd const * d) { + if (d->empty()) { + m_done = true; + return; + } + m_done = false; + unsigned arity = d->get_arity(); + m_iterators.resize(arity); + m_element.resize(arity); + begin_iterators(d, 0); +} + +void imdd_manager::iterator::begin_iterators(imdd const * curr, unsigned start_idx) { + for (unsigned i = start_idx; i < get_arity(); i++) { + m_iterators[i] = curr->begin_children(); + imdd_children::iterator & it = m_iterators[i]; + SASSERT(!it.at_end()); + m_element[i] = it->begin_key(); + curr = it->val(); + } +} + +imdd_manager::iterator & imdd_manager::iterator::operator++() { + unsigned i = get_arity(); + while (i > 0) { + --i; + imdd_children::iterator & it = m_iterators[i]; + if (m_element[i] < it->end_key()) { + m_element[i]++; + begin_iterators(it->val(), i+1); + return *this; + } + else { + ++it; + if (!it.at_end()) { + m_element[i] = it->begin_key(); + begin_iterators(it->val(), i+1); + return *this; + } + } + } + m_done = true; // at end... + return *this; +} + +bool imdd_manager::iterator::operator==(iterator const & it) const { + if (m_done && it.m_done) + return true; + if (m_done || it.m_done) + return false; + if (m_element.size() != it.m_element.size()) + return false; + unsigned sz = m_element.size(); + for (unsigned i = 0; i < sz; i++) + if (m_element[i] != it.m_element[i]) + return false; + return true; +} + +bool imdd_manager::is_equal_core(imdd * d1, imdd * d2) { + if (d1 == d2) + return true; + if (d1->is_memoized() && d2->is_memoized()) + return false; + SASSERT(d1->get_arity() == d2->get_arity()); + SASSERT(!d1->empty()); + SASSERT(!d2->empty()); + bool shared = d1->is_shared() && d2->is_shared(); + bool r; + if (shared && m_is_equal_cache.find(d1, d2, r)) + return r; + + if (d1->get_arity() == 1) { + r = d1->m_children.is_equal(d2->m_children); + } + else { + imdd_children::iterator it1 = d1->begin_children(); + imdd_children::iterator end1 = d1->end_children(); + imdd_children::iterator it2 = d2->begin_children(); + imdd_children::iterator end2 = d2->end_children(); + SASSERT(it1 != end1); + SASSERT(it2 != end2); + if (it1->begin_key() != it2->begin_key()) { + r = false; + } + else { + while (true) { + if (it1 == end1) { + r = (it2 == end2); + break; + } + if (it2 == end2) { + r = (it1 == end1); + break; + } + SASSERT(it1->val() != 0 && it2->val() != 0); + if (!is_equal_core(it1->val(), it2->val())) { + r = false; + break; + } + if (it1->end_key() < it2->end_key()) { + unsigned prev_end_key = it1->end_key(); + ++it1; + if (it1 == end1 || it1->begin_key() != prev_end_key + 1) { + r = false; + break; + } + } + else if (it1->end_key() > it2->end_key()) { + unsigned prev_end_key = it2->end_key(); + ++it2; + if (it2 == end2 || it2->begin_key() != prev_end_key + 1) { + r = false; + break; + } + } + else { + SASSERT(it1->end_key() == it2->end_key()); + ++it1; + ++it2; + } + } + } + } + if (shared) + m_is_equal_cache.insert(d1, d2, r); + return r; +} + +bool imdd_manager::is_equal(imdd * d1, imdd * d2) { + if (d1 == d2) + return true; + if (d1->is_memoized() && d2->is_memoized()) + return false; + if (d1->get_arity() != d2->get_arity()) + return false; + if (d1->empty() && d2->empty()) + return true; + if (d1->empty() || d2->empty()) + return false; + SASSERT(!d1->empty()); + SASSERT(!d2->empty()); + m_is_equal_cache.reset(); + return is_equal_core(d1, d2); +} + +/** + \brief Return true if the given imdd contains the tuple (values[0], ..., values[num-1]) +*/ +bool imdd_manager::contains(imdd * d, unsigned num, unsigned const * values) const { + SASSERT(d->get_arity() == num); + imdd * curr = d; + for (unsigned i = 0; i < num; i++) { + imdd * child; + if (!curr->m_children.find(values[i], child)) + return false; + curr = child; + } + return true; +} + +inline bool overlap(unsigned b1, unsigned e1, unsigned b2, unsigned e2) { + // [b1, e1] [b2, e2] + if (e1 < b2) + return false; + // [b2, e2] [b1, e1] + if (e2 < b1) + return false; + return true; +} + +bool imdd_manager::subsumes_core(imdd * d1, imdd * d2) { + if (d1 == d2) + return true; + SASSERT(d1->get_arity() == d2->get_arity()); + SASSERT(!d1->empty()); + SASSERT(!d2->empty()); + bool shared = d1->is_shared() && d2->is_shared(); + bool r; + if (shared && m_subsumes_cache.find(d1, d2, r)) + return r; + + imdd_children::iterator it1 = d1->begin_children(); + imdd_children::iterator end1 = d1->end_children(); + imdd_children::iterator it2 = d2->begin_children(); + imdd_children::iterator end2 = d2->end_children(); + SASSERT(it1 != end1); + SASSERT(it2 != end2); + if (it1->begin_key() > it2->begin_key()) { + r = false; + goto subsumes_end; + } + + if (d1->get_arity() == 1) { + // leaf case + while(true) { + SASSERT(it1 != end1); + SASSERT(it2 != end2); + it1.move_to(it2->begin_key()); + if (it1 == end1 || + it1->begin_key() > it2->begin_key() || // missed beginning of it2 curr entry + it1->end_key() < it2->end_key() // missed end of it2 curr entry + ) { + r = false; + goto subsumes_end; + } + SASSERT(it1->end_key() >= it2->end_key()); + ++it2; + if (it2 == end2) { + r = true; + goto subsumes_end; + } + } + } + else { + // non-leaf case + while (true) { + SASSERT(it1 != end1); + SASSERT(it2 != end2); + SASSERT(it1->val() != 0); + SASSERT(it2->val() != 0); + it1.move_to(it2->begin_key()); + if (it1 == end1 || + it1->begin_key() > it2->begin_key() // missed beginning of it2 curr entry + ) { + r = false; + goto subsumes_end; + } + // the beginning of it2 is inside it1 + SASSERT(it2->begin_key() >= it1->begin_key() && it2->begin_key() <= it1->end_key()); + // it1: [ ][ ][ ] + // it2 [ ] + while (true) { + SASSERT(it1 != end1); + SASSERT(it2 != end2); + // there is a overlap between the current entry in it1 and the current entry in it2 + SASSERT(overlap(it1->begin_key(), it1->end_key(), + it2->begin_key(), it2->end_key())); + if (!subsumes_core(it1->val(), it2->val())) { + r = false; + goto subsumes_end; + } + if (it1->end_key() >= it2->end_key()) { + ++it2; // processed the whole entry in it2 + break; + } + SASSERT(it1->end_key() < it2->end_key()); + unsigned prev_end_key = it1->end_key(); + ++it1; + if (it1 == end1 || it1->begin_key() != prev_end_key + 1) { + r = false; + goto subsumes_end; + } + } + if (it2 == end2) { + r = true; + goto subsumes_end; + } + } + } + subsumes_end: + if (shared) + m_subsumes_cache.insert(d1, d2, r); + return r; +} + +/** + \brief Return true is d1 is a superset of d2, or equal to d2. +*/ +bool imdd_manager::subsumes(imdd * d1, imdd * d2) { + SASSERT(d1->get_arity() == d2->get_arity()); + if (d1 == d2) + return true; + if (d2->empty()) + return true; + if (d1->empty()) { + SASSERT(!d2->empty()); + return false; + } + SASSERT(!d1->empty()); + SASSERT(!d2->empty()); + m_subsumes_cache.reset(); + return subsumes_core(d1, d2); +} + +void imdd_manager::remove_facts_dupdt(imdd_ref & d, unsigned num, unsigned const * lowers, unsigned const * uppers) { + SASSERT(d->get_arity() == num); + d = remove_facts_main(d, num, lowers, uppers, true, true); +} + +void imdd_manager::remove_facts(imdd * d, imdd_ref & r, unsigned num, unsigned const * lowers, unsigned const * uppers) { + SASSERT(d->get_arity() == num); + r = remove_facts_main(d, num, lowers, uppers, false, true); +} + +imdd * imdd_manager::remove_facts_main(imdd * d, unsigned num, unsigned const * lowers, unsigned const * uppers, bool destructive, bool memoize_res) { + SASSERT(d->get_arity() == num); + delay_dealloc delay(*this); + m_remove_facts_cache.reset(); + return remove_facts_core(d, num, lowers, uppers, destructive, memoize_res); +} + +imdd * imdd_manager::remove_facts_core(imdd * d, unsigned num, unsigned const * lowers, unsigned const * uppers, bool destructive, bool memoize_res) { + SASSERT(d->get_arity() == num && num > 0); + + imdd * new_d = 0; + unsigned b = *lowers; + unsigned e = *uppers; + sbuffer to_insert, to_remove; + imdd_children::iterator it = d->m_children.find_geq(b); + imdd_children::iterator end = d->end_children(); + bool is_destructive = destructive_update_at(destructive, d); + + if (!is_destructive && is_cached(m_remove_facts_cache, d, new_d)) { + return new_d; + } + + if (num == 1) { + new_d = remove_main(d, *lowers, *uppers, destructive, memoize_res); + if (!is_destructive) { + cache_result(m_remove_facts_cache, d, new_d); + } + return new_d; + } + + if (it == end || e < it->begin_key()) { + if (!is_destructive) { + cache_result(m_remove_facts_cache, d, d); + } + return d; + } + + if (!is_destructive) { + new_d = copy_main(d); + } + else { + new_d = d; + } + for (; it != end && b <= e; ++it) { + // + // remove ([b:e]::rest), [b_key:e_key] * ch) = + // { [b_key:e_key] * ch } if e < b_key + // { [b_key:e_key] * ch' } if b <= b_key <= e_key <= e + // { [b_key:e]*ch' [e+1:e_key]*ch } if b <= b_key <= e < e_key + // { [b_key:b-1]*ch [b:e]*ch', [e+1:e_key]*ch } if b_key < b <= e < e_key + // { [b_key:b-1]*ch [b:e_key]*ch' } if b_key < b <= e_key <= e + // where + // ch' = remove(rest, ch) + // assumption: remove_child retains the cases where ch is in the tree. + // + + imdd_children::entry const & curr_entry = *it; + unsigned b_key = curr_entry.begin_key(); + unsigned e_key = curr_entry.end_key(); + imdd* curr_child = curr_entry.val(); + imdd* new_curr_child = 0; + + if (e < b_key) { + break; + } + + new_curr_child = remove_facts_core(curr_child, num-1, lowers+1, uppers+1, destructive, memoize_res); + + if (new_curr_child == curr_child) { + continue; + } + + if (new_curr_child != 0) { + if (b <= b_key && e_key <= e) { + to_insert.push_back(entry(b_key, e_key, new_curr_child)); + } + if (b <= b_key && e < e_key) { + to_insert.push_back(entry(b_key, e, new_curr_child)); + } + if (b_key < b && e < e_key) { + to_insert.push_back(entry(b, e, new_curr_child)); + } + if (b_key < b && e_key <= e) { + to_insert.push_back(entry(b, e_key, new_curr_child)); + } + } + if (is_destructive) { + to_remove.push_back(entry(b, e, 0)); + } + else { + remove_child(new_d, b, e); + } + b = e_key + 1; + } + for (unsigned i = 0; i < to_remove.size(); ++i) { + remove_child(new_d, to_remove[i].begin_key(), to_remove[i].end_key()); + } + for (unsigned i = 0; i < to_insert.size(); ++i) { + add_child(new_d, to_insert[i].begin_key(), to_insert[i].end_key(), to_insert[i].val()); + } + if (!is_destructive) { + cache_result(m_remove_facts_cache, d, new_d); + } + return new_d; +} + +void imdd_manager::display(std::ostream & out, imdd const * d, unsigned_vector & intervals, bool & first) const { + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + if (d->get_arity() == 1) { + for (; it != end; ++it) { + SASSERT(it->val() == 0); + if (first) { + first = false; + } + else { + out << ",\n "; + } + out << "("; + unsigned sz = intervals.size(); + for (unsigned i = 0; i < sz; i+=2) { + out << "[" << intervals[i] << ", " << intervals[i+1] << "]"; + out << ", "; + } + out << "[" << it->begin_key() << ", " << it->end_key() << "])"; + } + } + else { + for (; it != end; ++it) { + intervals.push_back(it->begin_key()); + intervals.push_back(it->end_key()); + display(out, it->val(), intervals, first); + intervals.pop_back(); + intervals.pop_back(); + } + } +} + +void imdd_manager::display(std::ostream & out, imdd const * d) const { + unsigned_vector intervals; + bool first = true; + out << "{"; + display(out, d, intervals, first); + out << "}"; +} + +struct display_ll_context { + typedef map, default_eq > imdd2uint; + std::ostream & m_out; + unsigned m_next_id; + imdd2uint m_map; + display_ll_context(std::ostream & out):m_out(out), m_next_id(1) {} + + void display_tabs(unsigned num_tabs) { + for (unsigned i = 0; i < num_tabs; i++) + m_out << " "; + } + + void display_node(unsigned num_tabs, imdd const * d) { + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + if (it == end) { + m_out << "{}"; + return; + } + if (d->get_arity() == 1) { + // leaf + m_out << "{"; + for (bool first = true; it != end; ++it) { + if (first) + first = false; + else + m_out << ", "; + m_out << "[" << it->begin_key() << ", " << it->end_key() << "]"; + } + m_out << "}"; + } + else { + m_out << "{\n"; + display_tabs(num_tabs); + while (it != end) { + m_out << " [" << it->begin_key() << ", " << it->end_key() << "] -> "; + display(num_tabs+1, it->val()); + ++it; + if (it != end) { + m_out << "\n"; + display_tabs(num_tabs); + } + else { + m_out << "}"; + } + } + } + m_out << (d->is_memoized() ? "*" : "") << "$" << d->memory(); + } + + void display(unsigned num_tabs, imdd const * d) { + if (d == 0) { + m_out << ""; + return; + } + unsigned id; + if (m_map.find(const_cast(d), id)) { + m_out << "#" << id; + } + else if (d->is_shared()) { + id = m_next_id; + m_next_id++; + m_map.insert(const_cast(d), id); + m_out << "#" << id << ":"; + display_node(num_tabs, d); + } + else { + display_node(num_tabs, d); + } + } +}; + +void imdd_manager::display_ll(std::ostream & out, imdd const * d) const { + display_ll_context ctx(out); + ctx.display(0, d); +} + +struct addone_proc { + unsigned m_r; + addone_proc():m_r(0) {} + void operator()(imdd * d) { m_r++; } +}; + +/** + \brief Return the number of nodes in an IMDD. + This is *not* a constant time operation. + It is linear on the size of the IMDD +*/ +unsigned imdd_manager::get_num_nodes(imdd const * d) const { + addone_proc p; + for_each(const_cast(d), p); + return p.m_r; +} + +struct addmem_proc { + unsigned m_r; + addmem_proc():m_r(0) {} + void operator()(imdd * d) { m_r += d->memory(); } +}; + +/** + \brief Return the amount of memory consumed by the given IMDD. +*/ +unsigned imdd_manager::memory(imdd const * d) const { + addmem_proc p; + for_each(const_cast(d), p); + return p.m_r; +} + +/** + \brief Return number of rows the given IMDD represents. +*/ +size_t imdd_manager::get_num_rows(imdd const* root) const { + obj_map counts; + ptr_vector todo; + todo.push_back(root); + while (!todo.empty()) { + imdd const* d = todo.back(); + if (counts.contains(d)) { + todo.pop_back(); + continue; + } + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + bool all_valid = true; + size_t count = 0, c; + for (; it != end; ++it) { + imdd const* ch = it->val(); + if (!ch) { + count += (it->end_key()-it->begin_key()+1); + } + else if (counts.find(ch, c)) { + count += c*(it->end_key()-it->begin_key()+1); + } + else { + all_valid = false; + todo.push_back(ch); + } + } + if (all_valid) { + todo.pop_back(); + counts.insert(d, count); + } + } + return counts.find(root); +} + +imdd * imdd_manager::add_bounded_var_core(imdd * d, unsigned before_vidx, unsigned lower, unsigned upper, bool destructive, bool memoize_res) { + if (d == 0) { + SASSERT(before_vidx == 0); + imdd * r = _mk_empty(1); + add_child(r, lower, upper, 0); + r = memoize_new_imdd_if(memoize_res, r); + return r; + } + imdd * r = 0; + if (is_cached(m_add_bounded_var_cache, d, r)) + return r; + if (before_vidx == 0) { + imdd * r = _mk_empty(d->get_arity() + 1); + add_child(r, lower, upper, d); + r = memoize_new_imdd_if(d->is_memoized() && memoize_res, r); + return r; + } + + if (destructive_update_at(destructive, d)) { + sbuffer to_insert; + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + for (; it != end; ++it) { + imdd * curr_child = it->val(); + imdd * new_child = add_bounded_var_core(curr_child, before_vidx-1, lower, upper, true, memoize_res); + new_child->inc_ref(); // we will be resetting d->m_children later. + to_insert.push_back(entry(it->begin_key(), it->end_key(), new_child)); + } + SASSERT(!to_insert.empty()); + d->replace_children(m_sl_manager, to_insert); + return d; + } + + bool new_children_memoized = true; + r = _mk_empty(d->get_arity() + 1); + imdd_children::push_back_proc push_back(m_sl_manager, r->m_children); + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + SASSERT(it != end); + for (; it != end; ++it) { + imdd * curr_child = it->val(); + imdd * new_child = add_bounded_var_core(curr_child, before_vidx-1, lower, upper, false, memoize_res); + push_back(it->begin_key(), it->end_key(), new_child); + if (!new_child->is_memoized()) + new_children_memoized = false; + } + r = memoize_new_imdd_if(r && memoize_res && new_children_memoized, r); + cache_result(m_add_bounded_var_cache, d, r); + return r; +} + +/** + \brief Add a variable (bounded by lower and upper) before the variable before_var. + + That is, for each tuple (v_1, ..., v_n) in the IMDD \c d, the resultant IMDD contains the + tuples + + (v_1, ..., v_{after_vidx}, lower, ..., v_n) + (v_1, ..., v_{after_vidx}, lower+1, ..., v_n) + ... + (v_1, ..., v_{after_vidx}, upper, ..., v_n) + + This function is mainly used to implement mk_filter. + + \pre after_vidx < d->get_arity() +*/ +imdd * imdd_manager::add_bounded_var_main(imdd * d, unsigned before_vidx, unsigned lower, unsigned upper, bool destructive, bool memoize_res) { + SASSERT(before_vidx <= d->get_arity()); + if (d->empty()) + return d; + m_add_bounded_var_cache.reset(); + delay_dealloc delay(*this); + imdd * r = add_bounded_var_core(d, before_vidx, lower, upper, destructive, memoize_res); + return r; +} + +filter_cache_entry * imdd_manager::mk_filter_cache_entry(imdd * d, unsigned ctx_sz, unsigned * ctx, imdd * r) { + void * mem = m_filter_entries.allocate(filter_cache_entry::get_obj_size(ctx_sz)); + return new (mem) filter_cache_entry(d, r, ctx_sz, ctx); +} + +imdd * imdd_manager::is_mk_filter_cached(imdd * d, unsigned ctx_sz, unsigned * ctx) { + if (!d->is_shared()) + return 0; + m_filter_entries.push_scope(); + filter_cache_entry * tmp_entry = mk_filter_cache_entry(d, ctx_sz, ctx, 0); + filter_cache_entry * e = 0; + bool r = m_filter_cache.find(tmp_entry, e); + m_filter_entries.pop_scope(); + if (!r || e->m_r->is_dead()) + return 0; + return e->m_r; +} + +void imdd_manager::cache_mk_filter(imdd * d, unsigned ctx_sz, unsigned * ctx, imdd * r) { + if (d->is_shared()) { + filter_cache_entry * new_entry = mk_filter_cache_entry(d, ctx_sz, ctx, r); + m_filter_cache.insert(new_entry); + } +} + +void imdd_manager::init_mk_filter(unsigned arity, unsigned num_vars, unsigned * vars) { + m_filter_cache.reset(); + m_filter_entries.reset(); + m_filter_context.reset(); + m_filter_num_vars = num_vars; + m_filter_begin_vars = vars; + m_filter_end_vars = vars + num_vars; + DEBUG_CODE({ + for (unsigned i = 0; i < num_vars; i++) { + SASSERT(vars[i] < arity); + } + }); +} + +template +void imdd_manager::mk_filter_dupdt_core(imdd_ref & d, unsigned vidx, unsigned num_found, FilterProc & proc, bool memoize_res) { + SASSERT(!d->empty()); + + if (!destructive_update_at(true, d)) { + mk_filter_core(d, d, vidx, num_found, proc, memoize_res); + return; + } + + bool _is_filter_var = is_filter_var(vidx); + if (_is_filter_var) + num_found++; + unsigned new_vidx = vidx+1; + imdd_ref new_r(*this); + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + SASSERT(it != end); + for (; it != end; ++it) { + imdd_ref new_child(*this); + it.take_curr_ownership(m_sl_manager, new_child); // take ownership of the current child. + if (!_is_filter_var) { + mk_filter_dupdt_core(new_child, new_vidx, num_found, proc, memoize_res); + add_bounded_var_dupdt(new_child, num_found, it->begin_key(), it->end_key(), memoize_res); + if (new_r == 0) + new_r = new_child; + else + mk_union_core_dupdt(new_r, new_child, memoize_res); + } + else { + m_filter_context.push_back(it->begin_key()); + m_filter_context.push_back(it->end_key()); + if (num_found < m_filter_num_vars) { + mk_filter_dupdt_core(new_child, new_vidx, num_found, proc, memoize_res); + } + else { + proc(m_filter_context.c_ptr(), new_child, new_child, memoize_res); + } + m_filter_context.pop_back(); + m_filter_context.pop_back(); + if (new_r == 0) + new_r = new_child; + else + mk_union_core_dupdt(new_r, new_child, memoize_res); + } + } + d->m_children.reset(m_sl_manager); + d = new_r; +} + +template +void imdd_manager::mk_filter_core(imdd * d, imdd_ref & r, unsigned vidx, unsigned num_found, FilterProc & proc, bool memoize_res) { + SASSERT(!d->empty()); + + imdd * _r = is_mk_filter_cached(d, m_filter_context.size(), m_filter_context.c_ptr()); + if (_r) { + r = _r; + return; + } + + bool _is_filter_var = is_filter_var(vidx); + if (_is_filter_var) + num_found++; + unsigned new_vidx = vidx+1; + imdd_ref new_r(*this); + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + SASSERT(it != end); + for (; it != end; ++it) { + imdd * curr_child = it->val(); + imdd_ref new_child(*this); + if (!_is_filter_var) { + mk_filter_core(curr_child, new_child, new_vidx, num_found, proc, memoize_res); + imdd_ref tmp(*this); + add_bounded_var(new_child, tmp, num_found, it->begin_key(), it->end_key(), memoize_res); + if (new_r == 0) + new_r = tmp; + else + mk_union_core(new_r, tmp, new_r, memoize_res); + } + else { + m_filter_context.push_back(it->begin_key()); + m_filter_context.push_back(it->end_key()); + if (num_found < m_filter_num_vars) { + mk_filter_core(curr_child, new_child, new_vidx, num_found, proc, memoize_res); + } + else { + proc(m_filter_context.c_ptr(), curr_child, new_child, memoize_res); + } + m_filter_context.pop_back(); + m_filter_context.pop_back(); + if (new_r == 0) + new_r = new_child; + else + mk_union_core(new_r, new_child, new_r, memoize_res); + } + } + r = new_r; + cache_mk_filter(d, m_filter_context.size(), m_filter_context.c_ptr(), r); +} + +template +void imdd_manager::mk_filter_dupdt(imdd_ref & d, unsigned num_vars, unsigned * vars, FilterProc & proc, bool memoize_res) { + if (d->empty()) + return; + unsigned arity = d->get_arity(); + init_mk_filter(arity, num_vars, vars); + mk_filter_dupdt_core(d, 0, 0, proc, memoize_res); + if (d == 0) + d = _mk_empty(arity); +} + +template +void imdd_manager::mk_filter(imdd * d, imdd_ref & r, unsigned num_vars, unsigned * vars, FilterProc & proc, bool memoize_res) { + if (d->empty()) { + r = d; + return; + } + unsigned arity = d->get_arity(); + init_mk_filter(arity, num_vars, vars); + mk_filter_core(d, r, 0, 0, proc, memoize_res); + if (r == 0) + r = _mk_empty(arity); +} + +imdd * imdd_manager::mk_distinct_imdd(unsigned l1, unsigned u1, unsigned l2, unsigned u2, imdd * d, bool memoize_res) { + unsigned d_arity; + if (d == 0) { + d_arity = 0; + } + else { + d_arity = d->get_arity(); + memoize_res = memoize_res && d->is_memoized(); + } + imdd * r = _mk_empty(d_arity + 2); + imdd_children::push_back_proc push_back(m_sl_manager, r->m_children); + + TRACE("mk_distinct_imdd", tout << "STARTING: " << l1 << " " << u1 << " " << l2 << " " << u2 << "\n";); + +#define ADD_ENTRY(L1, U1, L2, U2) { \ + TRACE("mk_distinct_imdd", tout << "[" << L1 << " " << U1 << " " << L2 << " " << U2 << "]\n";); \ + imdd * new_child = _mk_empty(d_arity + 1); \ + add_child(new_child, L2, U2, d); \ + new_child = memoize_new_imdd_if(memoize_res, new_child); \ + push_back(L1, U1, new_child);} + + if (u1 < l2 || u2 < l1) { + ADD_ENTRY(l1, u1, l2, u2); // the intervals are disjoint + } + else { + if (l1 < l2) { + SASSERT(u1 >= l2); + // [l1 ... + // [l2 ... + // --> + // [l1, l2-1] X [l2, u2] + ADD_ENTRY(l1, l2-1, l2, u2); + } + + unsigned l = std::max(l1, l2); + unsigned u = std::min(u1, u2); + // [l, l] X [l2, l-1] // if l-1 >= l2 (i.e., l > l2) + // [l, l] X [l+1, u2] + // [l+1, l+1] X [l2, l] + // [l+1, l+1] X [l+2, u2] + // [l+2, l+2] X [l2, l+1] + // [l+2, l+2] X [l+3, u2] + // ... + // [u, u] X [l2, u-1] + // [u, u] X [u+1, u2] // if u+1 <= u2 (i.e., u < u2) + for (unsigned i = l; i <= u; i++) { + if (i > l2 && i < u2) { + // ADD_ENTRY(i, i, l2, i-1); + // ADD_ENTRY(i, i, i+1, u2); + imdd * new_child = _mk_empty(d_arity + 1); + add_child(new_child, l2, i-1, d); + add_child(new_child, i+1, u2, d); + new_child = memoize_new_imdd_if(memoize_res, new_child); + push_back(i, i, new_child); + } + else if (i > l2) { + SASSERT(!(i < u2)); + ADD_ENTRY(i, i, l2, i-1); + } + else if (i < u2) { + SASSERT(!(i > l2)); + ADD_ENTRY(i, i, i+1, u2); + } + } + + if (u1 > u2) { + // ... u1] + // ... u2] + // --> + // [u2+1, u1] X [l2, u2] + SASSERT(u2 >= l1); + ADD_ENTRY(u2+1, u1, l2, u2); + } + } +#undef ADD_ENTRY + + r = memoize_new_imdd_if(memoize_res, r); + return r; +} + +/** + \brief Auxiliary functor used to implement mk_filter_distinct +*/ +struct distinct_proc { + imdd_manager & m_manager; + + distinct_proc(imdd_manager & m): + m_manager(m) { + } + + void operator()(unsigned * lowers_uppers, imdd * d, imdd_ref & r, bool memoize_res) { + r = m_manager.mk_distinct_imdd(lowers_uppers[0], lowers_uppers[1], lowers_uppers[2], lowers_uppers[3], d, memoize_res); + } +}; + +void imdd_manager::mk_filter_distinct_dupdt(imdd_ref & d, unsigned v1, unsigned v2, bool memoize_res) { + unsigned vars[2] = { v1, v2 }; + distinct_proc proc(*this); + mk_filter_dupdt(d, 2, vars, proc, memoize_res); +} + +void imdd_manager::mk_filter_distinct(imdd * d, imdd_ref & r, unsigned v1, unsigned v2, bool memoize_res) { + unsigned vars[2] = { v1, v2 }; + distinct_proc proc(*this); + mk_filter(d, r, 2, vars, proc, memoize_res); + + STRACE("imdd_trace", tout << "mk_filter_distinct(0x" << d << ", 0x" << r.get() << ", " << v1 << ", " << v2 << ", " << memoize_res << ");\n";); +} + +imdd * imdd_manager::mk_disequal_imdd(unsigned l1, unsigned u1, unsigned value, imdd * d, bool memoize_res) { + unsigned d_arity; + if (d == 0) { + d_arity = 0; + } + else { + d_arity = d->get_arity(); + memoize_res = memoize_res && d->is_memoized(); + } + imdd * r = _mk_empty(d_arity + 1); + if (value < l1 || value > u1) { + add_child(r, l1, u1, d); + } + else { + SASSERT(l1 <= value && value <= u1); + if (l1 < value) { + add_child(r, l1, value-1, d); + } + if (value < u1) { + add_child(r, value+1, u1, d); + } + } + r = memoize_new_imdd_if(memoize_res, r); + return r; +} + +struct disequal_proc { + imdd_manager & m_manager; + unsigned m_value; + + disequal_proc(imdd_manager & m, unsigned v): + m_manager(m), + m_value(v) { + } + + void operator()(unsigned * lowers_uppers, imdd * d, imdd_ref & r, bool memoize_res) { + r = m_manager.mk_disequal_imdd(lowers_uppers[0], lowers_uppers[1], m_value, d, memoize_res); + } +}; + +void imdd_manager::mk_filter_disequal_dupdt(imdd_ref & d, unsigned var, unsigned value, bool memoize_res) { + unsigned vars[1] = { var }; + disequal_proc proc(*this, value); + mk_filter_dupdt(d, 1, vars, proc, memoize_res); +} + +void imdd_manager::mk_filter_disequal(imdd * d, imdd_ref & r, unsigned var, unsigned value, bool memoize_res) { + unsigned vars[1] = { var }; + disequal_proc proc(*this, value); + mk_filter(d, r, 1, vars, proc, memoize_res); + + STRACE("imdd_trace", tout << "mk_filter_disequal(0x" << d << ", 0x" << r.get() << ", " << var << ", " << value << ", " << memoize_res << ");\n";); +} + +#endif + diff --git a/lib/imdd.h b/lib/imdd.h new file mode 100644 index 000000000..4fcda2f1c --- /dev/null +++ b/lib/imdd.h @@ -0,0 +1,745 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + imdd.h + +Abstract: + + Interval based Multiple-valued Decision Diagrams. + +Author: + + Leonardo de Moura (leonardo) 2010-10-13. + +Revision History: + +--*/ +#ifndef _IMDD_H_ +#define _IMDD_H_ + +#include"id_gen.h" +#include"hashtable.h" +#include"map.h" +#include"obj_hashtable.h" +#include"obj_pair_hashtable.h" +#include"buffer.h" +#include"interval_skip_list.h" +#include"region.h" +#include"obj_ref.h" + +class imdd; +class imdd_manager; + +/** + \brief Manager for skip-lists used to implement IMDD nodes. +*/ +class sl_imdd_manager : public random_level_manager { + imdd_manager * m_manager; // real manager + small_object_allocator & m_alloc; + friend class imdd_manager; +public: + sl_imdd_manager(small_object_allocator & alloc):m_alloc(alloc) {} + void * allocate(size_t size) { return m_alloc.allocate(size); } + void deallocate(size_t size, void* p) { m_alloc.deallocate(size, p); } + void inc_ref_eh(imdd * v); + void dec_ref_eh(imdd * v); +}; + +#define IMDD_BUCKET_CAPACITY 128 +#define IMDD_MAX_LEVEL 32 + +typedef interval_skip_list, + IMDD_BUCKET_CAPACITY, + IMDD_MAX_LEVEL, + true, /* support ref-counting */ + sl_imdd_manager> > imdd_children; + +typedef interval_skip_list, + IMDD_BUCKET_CAPACITY, + IMDD_MAX_LEVEL, + false, + sl_manager_base > > sl_interval_set; + +/* + Notes: + + - We use reference counting for garbage collecting IMDDs nodes. + + - Each IMDD node has a "memoized" flag. If the flag is true, the we use hash-consing for this node. + + - The children of a memoized node must be memoized. + + - The children of a non-memoized node may be memoized. + + - The "memoized" flag cannot be reset after it was set. + + - The result of some operations may be cached. We only use caching for + operations processing memoized nodes. + + - For non-memoized nodes, if m_ref_count <= 1, destructive updates may be performed by some operations. + + - IMPORTANT: "memoized" flag == false doesn't imply m_ref_count <= 1. +*/ + + +/** + \brief IMDDs +*/ +class imdd { + +protected: + friend class imdd_manager; + + unsigned m_id; //!< Unique ID + unsigned m_ref_count; + unsigned m_arity:30; + unsigned m_memoized:1; + unsigned m_dead:1; + imdd_children m_children; + + void inc_ref() { + m_ref_count ++; + } + + void dec_ref() { + SASSERT(m_ref_count > 0); + m_ref_count --; + } + + void mark_as_memoized(bool flag = true) { + SASSERT(is_memoized() != flag); + m_memoized = flag; + } + + void mark_as_dead() { SASSERT(!m_dead); m_dead = true; } + + void replace_children(sl_imdd_manager & m, sbuffer & new_children); + +public: + imdd(sl_imdd_manager & m, unsigned id, unsigned arity):m_id(id), m_ref_count(0), m_arity(arity), m_memoized(false), m_dead(false), m_children(m) {} + unsigned get_id() const { return m_id; } + unsigned get_ref_count() const { return m_ref_count; } + bool is_memoized() const { return m_memoized; } + bool is_shared() const { return m_ref_count > 1; } + bool is_dead() const { return m_dead; } + unsigned get_arity() const { return m_arity; } + imdd_children::iterator begin_children() const { return m_children.begin(); } + imdd_children::iterator end_children() const { return m_children.end(); } + unsigned hc_hash() const; // hash code for hash-consing. + bool hc_equal(imdd const * other) const; // eq function for hash-consing + bool empty() const { return m_children.empty(); } + unsigned hash() const { return m_id; } + unsigned memory() const { return sizeof(imdd) + m_children.memory() - sizeof(imdd_children); } +}; + +// ----------------------------------- +// +// IMDD hash-consing +// +// ----------------------------------- + +// this is the internal hashing functor for hash-consing IMDDs. +struct imdd_hash_proc { + unsigned operator()(imdd const * d) const { return d->hc_hash(); } +}; + +// This is the internal comparison functor for hash-consing IMDDs. +struct imdd_eq_proc { + bool operator()(imdd const * d1, imdd const * d2) const { return d1->hc_equal(d2); } +}; + +typedef ptr_hashtable imdd_table; +typedef obj_hashtable imdd_cache; +typedef obj_map imdd2imdd_cache; +typedef obj_pair_map imdd_pair2imdd_cache; +typedef obj_pair_map imdd_pair2bool_cache; +typedef obj_map imdd2intervals; + +typedef std::pair imdd_value_pair; + +struct fi_cache_entry { + imdd * m_d; + unsigned m_lower; + unsigned m_upper; + unsigned m_hash; + unsigned m_num_result; + imdd_value_pair m_result[0]; + + void mk_hash() { + m_hash = hash_u_u(m_d->get_id(), hash_u_u(m_lower, m_upper)); + } + + fi_cache_entry(imdd * d, unsigned l, unsigned u): + m_d(d), + m_lower(l), + m_upper(u) { + mk_hash(); + } + + fi_cache_entry(imdd * d, unsigned l, unsigned u, unsigned num, imdd_value_pair result[]): + m_d(d), + m_lower(l), + m_upper(u), + m_num_result(num) { + mk_hash(); + memcpy(m_result, result, sizeof(imdd_value_pair)*num); + } + + unsigned hash() const { + return m_hash; + } + + bool operator==(fi_cache_entry const & other) const { + return + m_d == other.m_d && + m_lower == other.m_lower && + m_upper == other.m_upper; + } +}; + +typedef obj_hashtable imdd_fi_cache; +typedef union { + imdd * m_d; + fi_cache_entry * m_entry; +} mk_fi_result; + +struct filter_cache_entry { + imdd * m_d; + imdd * m_r; + unsigned m_hash; + unsigned m_ctx_size; + unsigned m_ctx[0]; // lower and upper bounds that are part of the context. + + static unsigned get_obj_size(unsigned ctx_size) { + return sizeof(filter_cache_entry) + ctx_size * sizeof(unsigned); + } + + void mk_hash() { + if (m_ctx_size > 0) + m_hash = string_hash(reinterpret_cast(m_ctx), m_ctx_size * sizeof(unsigned), m_d->get_id()); + else + m_hash = m_d->get_id(); + } + + filter_cache_entry(imdd * d, imdd * r, unsigned ctx_size, unsigned * ctx): + m_d(d), + m_r(r), + m_ctx_size(ctx_size) { + memcpy(m_ctx, ctx, sizeof(unsigned)*m_ctx_size); + mk_hash(); + } + + unsigned hash() const { + return m_hash; + } + + bool operator==(filter_cache_entry const & other) const { + if (m_d != other.m_d) + return false; + if (m_ctx_size != other.m_ctx_size) + return false; + for (unsigned i = 0; i < m_ctx_size; i++) + if (m_ctx[i] != other.m_ctx[i]) + return false; + return true; + } +}; + +typedef obj_hashtable imdd_mk_filter_cache; + +typedef obj_ref imdd_ref; + +class imdd_manager { + typedef imdd_children::entry entry; + small_object_allocator m_alloc; + id_gen m_id_gen; + vector m_tables; // we keep a table for each height. + sl_imdd_manager m_sl_manager; + unsigned m_simple_max_entries; //!< maximum number of entries in a "simple" node. + bool m_delay_dealloc; + ptr_vector m_to_delete; //!< list of IMDDs marked as dead. These IMDDs may still be in cache tables. + + // generic cache and todo-lists + ptr_vector m_worklist; + imdd_cache m_visited; + + void mark_as_dead(imdd * d); + void deallocate_imdd(imdd * d); + void delete_imdd(imdd * d); + + class delay_dealloc; + friend class delay_dealloc; + + class delay_dealloc { + imdd_manager & m_manager; + bool m_delay_dealloc_value; + unsigned m_to_delete_size; + public: + delay_dealloc(imdd_manager & m): + m_manager(m), + m_delay_dealloc_value(m_manager.m_delay_dealloc), + m_to_delete_size(m_manager.m_to_delete.size()) { + m_manager.m_delay_dealloc = true; + } + ~delay_dealloc(); + }; + + bool is_simple_node(imdd * d) const; + + void add_child(imdd * d, unsigned lower, unsigned upper, imdd * child) { + d->m_children.insert(m_sl_manager, lower, upper, child); + } + + void add_child(imdd * d, unsigned value, imdd * child) { + add_child(d, value, value, child); + } + + void remove_child(imdd * d, unsigned lower, unsigned upper) { + d->m_children.remove(m_sl_manager, lower, upper); + } + + imdd * copy_main(imdd * d); + + imdd * insert_main(imdd * d, unsigned b, unsigned e, bool destructive, bool memoize_res); + + imdd * remove_main(imdd * d, unsigned b, unsigned e, bool destructive, bool memoize_res); + + imdd2imdd_cache m_mk_product_cache; + struct null2imdd_proc; + struct mk_product_proc; + friend struct mk_product_proc; + imdd * mk_product_core(imdd * d1, imdd * d2, bool destructive, bool memoize); + imdd * mk_product_main(imdd * d1, imdd * d2, bool destructive, bool memoize_res); + + imdd2imdd_cache m_add_facts_cache; + ptr_vector m_add_facts_new_children; + void init_add_facts_new_children(unsigned num, unsigned const * lowers, unsigned const * uppers, bool memoize_res); + imdd * add_facts_core(imdd * d, unsigned num, unsigned const * lowers, unsigned const * uppers, bool destructive, bool memoize_res); + imdd * add_facts_main(imdd * d, unsigned num, unsigned const * lowers, unsigned const * uppers, bool destructive, bool memoize_res); + + imdd2imdd_cache m_remove_facts_cache; + imdd * remove_facts_core(imdd * d, unsigned num, unsigned const * lowers, unsigned const * uppers, bool destructive, bool memoize_res); + imdd * remove_facts_main(imdd * d, unsigned num, unsigned const * lowers, unsigned const * uppers, bool destructive, bool memoize_res); + + + imdd2imdd_cache m_defrag_cache; + imdd * defrag_core(imdd * d); + + imdd_pair2imdd_cache m_union_cache; + void push_back_entries(unsigned head, imdd_children::iterator & it, imdd_children::iterator & end, + imdd_children::push_back_proc & push_back, bool & children_memoized); + void push_back_upto(unsigned & head, imdd_children::iterator & it, imdd_children::iterator & end, unsigned limit, + imdd_children::push_back_proc & push_back, bool & children_memoized); + void move_head(unsigned & head, imdd_children::iterator & it, imdd_children::iterator & end, unsigned new_head); + void copy_upto(unsigned & head, imdd_children::iterator & it, imdd_children::iterator & end, unsigned limit, sbuffer & result); + void reset_union_cache(); + imdd * mk_union_core(imdd * d1, imdd * d2, bool destructive, bool memoize_res); + imdd * mk_union_main(imdd * d1, imdd * d2, bool destructive, bool memoize_res); + void mk_union_core_dupdt(imdd_ref & d1, imdd * d2, bool memoize_res); + void mk_union_core(imdd * d1, imdd * d2, imdd_ref & r, bool memoize_res); + + imdd_pair2bool_cache m_is_equal_cache; + bool is_equal_core(imdd * d1, imdd * d2); + + imdd_pair2bool_cache m_subsumes_cache; + bool subsumes_core(imdd * d1, imdd * d2); + + imdd2imdd_cache m_complement_cache; + imdd * mk_complement_core(imdd * d, unsigned num, unsigned const * mins, unsigned const * maxs, bool destructive, bool memoize_res); + imdd * mk_complement_main(imdd * d, unsigned num, unsigned const * mins, unsigned const * maxs, bool destructive, bool memoize_res); + + imdd2imdd_cache m_filter_equal_cache; + imdd * mk_filter_equal_core(imdd * d, unsigned vidx, unsigned value, bool destructive, bool memoize_res); + imdd * mk_filter_equal_main(imdd * d, unsigned vidx, unsigned value, bool destructive, bool memoize_res); + + + imdd2intervals m_imdd2interval_set; + ptr_vector m_alloc_is; + typedef sl_manager_base sl_imanager; + void reset_fi_intervals(sl_imanager& m); + sl_interval_set const* init_fi_intervals(sl_imanager& m, imdd* d, unsigned var, unsigned num_found); + + imdd2imdd_cache m_fi_top_cache; + imdd_fi_cache m_fi_bottom_cache; + unsigned m_fi_num_vars; + unsigned * m_fi_begin_vars; + unsigned * m_fi_end_vars; + region m_fi_entries; + bool is_fi_var(unsigned v) const { return std::find(m_fi_begin_vars, m_fi_end_vars, v) != m_fi_end_vars; } + fi_cache_entry * mk_fi_cache_entry(imdd * d, unsigned lower, unsigned upper, unsigned num_pairs, imdd_value_pair pairs[]); + mk_fi_result mk_filter_identical_core(imdd * d, unsigned offset, unsigned num_found, unsigned lower, unsigned upper, + bool destructive, bool memoize_res); + imdd * mk_filter_identical_main(imdd * d, unsigned num_vars, unsigned * vars, bool destructive, bool memoize_res); + + obj_map m_filter_identical_cache; + void filter_identical_core2(imdd* d, unsigned num_vars, unsigned b, unsigned e, ptr_vector& ch); + imdd* filter_identical_core2(imdd* d, unsigned var, unsigned num_vars, bool memoize_res); + void filter_identical_main2(imdd * d, imdd_ref& r, unsigned num_vars, unsigned * vars, bool destructive, bool memoize_res); + void swap_in(imdd * d, imdd_ref& r, unsigned num_vars, unsigned * vars); + void swap_out(imdd * d, imdd_ref& r, unsigned num_vars, unsigned * vars); + + unsigned m_proj_num_vars; + unsigned * m_proj_begin_vars; + unsigned * m_proj_end_vars; + imdd2imdd_cache m_proj_cache; + bool is_proj_var(unsigned v) const { return std::find(m_proj_begin_vars, m_proj_end_vars, v) != m_proj_end_vars; } + void mk_project_init(unsigned num_vars, unsigned * vars); + void mk_project_core(imdd * d, imdd_ref & r, unsigned var, unsigned num_found, bool memoize_res); + void mk_project_dupdt_core(imdd_ref & d, unsigned var, unsigned num_found, bool memoize_res); + + imdd2imdd_cache m_swap_cache; + imdd * m_swap_new_child; + bool m_swap_granchildren_memoized; + imdd * mk_swap_new_child(unsigned lower, unsigned upper, imdd * child); + void mk_swap_acc1_dupdt(imdd_ref & d, unsigned lower, unsigned upper, imdd * grandchild, bool memoize_res); + void mk_swap_acc1(imdd * d, imdd_ref & r, unsigned lower, unsigned upper, imdd * grandchild, bool memoize_res); + void mk_swap_acc2(imdd_ref & r, unsigned lower1, unsigned upper1, unsigned lower2, unsigned upper2, imdd * grandchild, bool memoize_res); + void mk_swap_top_vars(imdd * d, imdd_ref & r, bool memoize_res); + imdd * mk_swap_memoize(imdd * d); + void mk_swap_core(imdd * d, imdd_ref & r, unsigned vidx, bool memoize_res); + void mk_swap_dupdt_core(imdd_ref & d, unsigned vidx, bool memoize_res); + + imdd2imdd_cache m_add_bounded_var_cache; + imdd * add_bounded_var_core(imdd * d, unsigned before_vidx, unsigned lower, unsigned upper, bool destructive, bool memoize_res); + imdd * add_bounded_var_main(imdd * d, unsigned before_vidx, unsigned lower, unsigned upper, bool destructive, bool memoize_res); + + friend struct distinct_proc; + imdd * mk_distinct_imdd(unsigned l1, unsigned u1, unsigned l2, unsigned u2, imdd * d, bool memoize_res = true); + + imdd_mk_filter_cache m_filter_cache; + region m_filter_entries; + unsigned m_filter_num_vars; + unsigned * m_filter_begin_vars; + unsigned * m_filter_end_vars; + unsigned_vector m_filter_context; + bool is_filter_var(unsigned v) const { return std::find(m_filter_begin_vars, m_filter_end_vars, v) != m_filter_end_vars; } + filter_cache_entry * mk_filter_cache_entry(imdd * d, unsigned ctx_sz, unsigned * ctx, imdd * r); + imdd * is_mk_filter_cached(imdd * d, unsigned ctx_sz, unsigned * ctx); + void cache_mk_filter(imdd * d, unsigned ctx_sz, unsigned * ctx, imdd * r); + void init_mk_filter(unsigned arity, unsigned num_vars, unsigned * vars); + template + void mk_filter_dupdt_core(imdd_ref & d, unsigned vidx, unsigned num_found, FilterProc & proc, bool memoize_res); + template + void mk_filter_core(imdd * d, imdd_ref & r, unsigned vidx, unsigned num_found, FilterProc & proc, bool memoize_res); + /** + \brief Filter the elements of the given IMDD using the given filter. + + The FilterProc template parameter is a filter for computing subsets of sets of the form: + + [L_1, U_1] X [L_2, U_2] X ... X [L_n, U_n] X d (where d is an IMDD) + + where n == num_vars + + The subset of elements is returned as an IMDD. + + FilterProc must have a method of the form: + + void operator()(unsigned * lowers_uppers, imdd * d, imdd_ref & r, bool memoize_res); + + The size of the array lowers_uppers is 2*num_vars + + The arity of the resultant IMDD must be num_vars + d->get_arity(). + */ + template + void mk_filter_dupdt(imdd_ref & d, unsigned num_vars, unsigned * vars, FilterProc & proc, bool memoize_res = true); + template + void mk_filter(imdd * d, imdd_ref & r, unsigned num_vars, unsigned * vars, FilterProc & proc, bool memoize_res = true); + + imdd * mk_disequal_imdd(unsigned l1, unsigned u1, unsigned value, imdd * d, bool memoize_res); + friend struct disequal_proc; + +public: + imdd_manager(); + + void inc_ref(imdd * d) { + if (d) + d->inc_ref(); + } + + void dec_ref(imdd * d) { + if (d) { + d->dec_ref(); + if (d->get_ref_count() == 0) + delete_imdd(d); + } + } + + unsigned get_num_nodes(imdd const * d) const; + + // count number of keys (rows) in table as if table is uncompressed. + size_t get_num_rows(imdd const* d) const; + + unsigned memory(imdd const * d) const; + +private: + imdd * _mk_empty(unsigned arity); + +public: + imdd * mk_empty(unsigned arity) { + imdd * r = _mk_empty(arity); + STRACE("imdd_trace", tout << "mk_empty(" << arity << ", 0x" << r << ");\n";); + return r; + } + +private: + imdd * memoize(imdd * d); + +public: + void memoize(imdd_ref const & d, imdd_ref & r) { r = memoize(d.get()); } + + void memoize(imdd_ref & d) { d = memoize(d.get()); } + + imdd * memoize_new_imdd_if(bool cond, imdd * r) { + if (cond && is_simple_node(r)) { + SASSERT(!r->is_shared()); + imdd * can_r = memoize(r); + if (can_r != r) { + SASSERT(r->get_ref_count() == 0); + delete_imdd(r); + } + return can_r; + } + return r; + } + +public: + void defrag(imdd_ref & d); + + void unmemoize(imdd * d); + + void unmemoize_rec(imdd * d); + + void copy(imdd * d, imdd_ref & r) { r = copy_main(d); } + + void insert_dupdt(imdd_ref & d, unsigned b, unsigned e, bool memoize_res = true) { + d = insert_main(d, b, e, true, memoize_res); + } + + void insert(imdd * d, imdd_ref & r, unsigned b, unsigned e, bool memoize_res = true) { + r = insert_main(d, b, e, false, memoize_res); + } + + void mk_product_dupdt(imdd_ref & d1, imdd * d2, bool memoize_res = true) { + d1 = mk_product_main(d1.get(), d2, true, memoize_res); + } + + void mk_product(imdd * d1, imdd * d2, imdd_ref & r, bool memoize_res = true) { + r = mk_product_main(d1, d2, false, memoize_res); + STRACE("imdd_trace", tout << "mk_product(0x" << d1 << ", 0x" << d2 << ", 0x" << r.get() << ", " << memoize_res << ");\n";); + } + + void mk_union_dupdt(imdd_ref & d1, imdd * d2, bool memoize_res = true) { + d1 = mk_union_main(d1.get(), d2, true, memoize_res); + } + + void mk_union(imdd * d1, imdd * d2, imdd_ref & r, bool memoize_res = true) { + r = mk_union_main(d1, d2, false, memoize_res); + STRACE("imdd_trace", tout << "mk_union(0x" << d1 << ", 0x" << d2 << ", 0x" << r.get() << ", " << memoize_res << ");\n";); + } + + void mk_complement_dupdt(imdd_ref & d, unsigned num, unsigned const * mins, unsigned const * maxs, bool memoize_res = true) { + d = mk_complement_main(d, num, mins, maxs, true, memoize_res); + } + + void mk_complement(imdd * d, imdd_ref & r, unsigned num, unsigned const * mins, unsigned const * maxs, bool memoize_res = true) { + r = mk_complement_main(d, num, mins, maxs, false, memoize_res); + + STRACE("imdd_trace", tout << "mk_complement(0x" << d << ", 0x" << r.get() << ", "; + for (unsigned i = 0; i < num; i++) tout << mins[i] << ", " << maxs[i] << ", "; + tout << memoize_res << ");\n";); + } + + void mk_filter_equal_dupdt(imdd_ref & d, unsigned vidx, unsigned value, bool memoize_res = true) { + d = mk_filter_equal_main(d, vidx, value, true, memoize_res); + } + + void mk_filter_equal(imdd * d, imdd_ref & r, unsigned vidx, unsigned value, bool memoize_res = true) { + r = mk_filter_equal_main(d, vidx, value, false, memoize_res); + + STRACE("imdd_trace", tout << "mk_filter_equal(0x" << d << ", 0x" << r.get() << ", " << vidx << ", " << value << ", " << memoize_res << ");\n";); + } + + void mk_filter_identical_dupdt(imdd_ref & d, unsigned num_vars, unsigned * vars, bool memoize_res = true) { + // d = mk_filter_identical_main(d, num_vars, vars, true, memoize_res); + filter_identical_main2(d, d, num_vars, vars, true, memoize_res); + } + + void mk_filter_identical(imdd * d, imdd_ref & r, unsigned num_vars, unsigned * vars, bool memoize_res = true) { + filter_identical_main2(d, r, num_vars, vars, false, memoize_res); + + STRACE("imdd_trace", tout << "mk_filter_identical(0x" << d << ", 0x" << r.get() << ", "; + for (unsigned i = 0; i < num_vars; i++) tout << vars[i] << ", "; + tout << memoize_res << ");\n";); + } + + void mk_project_dupdt(imdd_ref & d, unsigned num_vars, unsigned * vars, bool memoize_res = true); + + void mk_project(imdd * d, imdd_ref & r, unsigned num_vars, unsigned * vars, bool memoize_res = true); + + // swap vidx and vidx+1 + void mk_swap_dupdt(imdd_ref & d, unsigned vidx, bool memoize_res = true); + + // swap vidx and vidx+1 + void mk_swap(imdd * d, imdd_ref & r, unsigned vidx, bool memoize_res = true); + + void add_facts_dupdt(imdd_ref & d, unsigned num, unsigned const * lowers, unsigned const * uppers, bool memoize_res = true) { + d = add_facts_main(d, num, lowers, uppers, true, memoize_res); + } + + void add_facts(imdd * d, imdd_ref & r, unsigned num, unsigned const * lowers, unsigned const * uppers, bool memoize_res = true) { + r = add_facts_main(d, num, lowers, uppers, false, memoize_res); + + STRACE("imdd_trace", tout << "add_facts(0x" << d << ", 0x" << r.get() << ", "; + for (unsigned i = 0; i < num; i++) tout << lowers[i] << ", " << uppers[i] << ", "; + tout << memoize_res << ");\n";); + } + + void add_fact_dupdt(imdd_ref & d, unsigned num, unsigned const * values, bool memoize_res = true) { + add_facts_dupdt(d, num, values, values, memoize_res); + } + + void add_fact(imdd * d, imdd_ref & r, unsigned num, unsigned const * values, bool memoize_res = true) { + add_facts(d, r, num, values, values, memoize_res); + } + + void add_bounded_var_dupdt(imdd_ref & d, unsigned before_vidx, unsigned lower, unsigned upper, bool memoize_res = true) { + d = add_bounded_var_main(d, before_vidx, lower, upper, true, memoize_res); + } + + void add_bounded_var(imdd * d, imdd_ref & r, unsigned before_vidx, unsigned lower, unsigned upper, bool memoize_res = true) { + r = add_bounded_var_main(d, before_vidx, lower, upper, false, memoize_res); + } + + void mk_filter_distinct_dupdt(imdd_ref & d, unsigned v1, unsigned v2, bool memoize_res = true); + + void mk_filter_distinct(imdd * d, imdd_ref & r, unsigned v1, unsigned v2, bool memoize_res = true); + + void mk_filter_disequal_dupdt(imdd_ref & d, unsigned var, unsigned value, bool memoize_res = true); + + void mk_filter_disequal(imdd * d, imdd_ref & r, unsigned var, unsigned value, bool memoize_res = true); + + void mk_join(imdd * d1, imdd * d2, imdd_ref & r, unsigned_vector const& vars1, unsigned_vector const& vars2, bool memoize_res = true); + + void mk_join_project(imdd * d1, imdd * d2, imdd_ref & r, + unsigned_vector const& vars1, unsigned_vector const& vars2, + unsigned_vector const& proj_vars, bool memoize_res = true); + + void mk_join_dupdt(imdd_ref & d1, imdd * d2, unsigned num_vars, unsigned const * vars1, unsigned const * vars2, bool memoize_res = true); + + void remove_facts_dupdt(imdd_ref & d, unsigned num, unsigned const * lowers, unsigned const * uppers); + + void remove_facts(imdd * d, imdd_ref & r, unsigned num, unsigned const * lowers, unsigned const * uppers); + + bool is_equal(imdd * d1, imdd * d2); + + bool contains(imdd * d, unsigned num, unsigned const * values) const; + + bool contains(imdd * d, unsigned v) const { return contains(d, 1, &v); } + + bool contains(imdd * d, unsigned v1, unsigned v2) const { unsigned vs[2] = {v1, v2}; return contains(d, 2, vs); } + + bool contains(imdd * d, unsigned v1, unsigned v2, unsigned v3) const { unsigned vs[3] = {v1,v2,v3}; return contains(d, 3, vs); } + + bool subsumes(imdd * d1, imdd * d2); + + bool is_subset(imdd * d1, imdd * d2) { return subsumes(d2, d1); } + +private: + void display(std::ostream & out, imdd const * d, unsigned_vector & intervals, bool & first) const; + +public: + void display(std::ostream & out, imdd const * d) const; + + void display_ll(std::ostream & out, imdd const * d) const; + + /** + \brief Execute proc (once) in each node in the IMDD rooted by d. + */ + template + void for_each(imdd * d, Proc & proc) const { + // for_each is a generic procedure, we don't know what proc will actually do. + // So, it is not safe to reuse m_worklist and m_visited. + ptr_buffer worklist; + imdd_cache visited; + worklist.push_back(d); + while (!worklist.empty()) { + d = worklist.back(); + worklist.pop_back(); + if (d->is_shared() && visited.contains(d)) + continue; + if (d->is_shared()) + visited.insert(d); + proc(d); + if (d->get_arity() > 1) { + imdd_children::iterator it = d->begin_children(); + imdd_children::iterator end = d->end_children(); + for (; it != end; ++it) + worklist.push_back(it->val()); + } + } + } + + class iterator { + bool m_done; + svector m_iterators; + svector m_element; + + void begin_iterators(imdd const * curr, unsigned start_idx); + + public: + iterator():m_done(true) {} + iterator(imdd_manager const & m, imdd const * d); + + unsigned get_arity() const { return m_element.size(); } + unsigned * operator*() const { return m_element.c_ptr(); } + iterator & operator++(); + + bool operator==(iterator const & it) const; + bool operator!=(iterator const & it) const { return !operator==(it); } + }; + + friend class iterator; + + iterator begin(imdd const * d) const { return iterator(*this, d); } + iterator end(imdd const * d) const { return iterator(); } +}; + +inline std::ostream & operator<<(std::ostream & out, imdd_ref const & r) { + r.get_manager().display(out, r.get()); + return out; +} + +struct mk_imdd_pp { + imdd * m_d; + imdd_manager & m_manager; + mk_imdd_pp(imdd * d, imdd_manager & m):m_d(d), m_manager(m) {} +}; + +inline mk_imdd_pp mk_pp(imdd * d, imdd_manager & m) { + return mk_imdd_pp(d, m); +} + +inline std::ostream & operator<<(std::ostream & out, mk_imdd_pp const & pp) { + pp.m_manager.display(out, pp.m_d); + return out; +} + +struct mk_imdd_ll_pp : public mk_imdd_pp { + mk_imdd_ll_pp(imdd * d, imdd_manager & m):mk_imdd_pp(d, m) {} +}; + +inline mk_imdd_ll_pp mk_ll_pp(imdd * d, imdd_manager & m) { + return mk_imdd_ll_pp(d, m); +} + +inline std::ostream & operator<<(std::ostream & out, mk_imdd_ll_pp const & pp) { + pp.m_manager.display_ll(out, pp.m_d); + return out; +} + +#endif /* _IMDD_H_ */ + diff --git a/lib/inf_int_rational.cpp b/lib/inf_int_rational.cpp new file mode 100644 index 000000000..54a838a75 --- /dev/null +++ b/lib/inf_int_rational.cpp @@ -0,0 +1,41 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + inf_int_rational.cpp + +Abstract: + + Rational numbers with infenitesimals + +Author: + + Nikolaj Bjorner (nbjorner) 2006-12-05. + +Revision History: + +--*/ +#include +#include"inf_int_rational.h" + +inf_int_rational inf_int_rational::m_zero(0); +inf_int_rational inf_int_rational::m_one(1); +inf_int_rational inf_int_rational::m_minus_one(-1); + +std::string inf_int_rational::to_string() const { + if (m_second == 0) { + return m_first.to_string(); + } + std::ostringstream s; + + s << "(" << m_first.to_string(); + if (m_second < 0) { + s << " -e*" << (-m_second) << ")"; + } + else { + s << " +e*" << m_second << ")"; + } + return s.str(); +} + diff --git a/lib/inf_int_rational.h b/lib/inf_int_rational.h new file mode 100644 index 000000000..c9879f0ee --- /dev/null +++ b/lib/inf_int_rational.h @@ -0,0 +1,372 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + inf_int_rational.h + +Abstract: + + Rational numbers with infenitesimals + +Author: + + Leonardo de Moura (leonardo) 2006-09-18. + Nikolaj Bjorner (nbjorner) 2006-10-24. + +Revision History: + +--*/ +#ifndef _INF_INT_RATIONAL_H_ +#define _INF_INT_RATIONAL_H_ +#include +#include +#include"debug.h" +#include"vector.h" +#include"rational.h" + + +class inf_int_rational { + static inf_int_rational m_zero; + static inf_int_rational m_one; + static inf_int_rational m_minus_one; + rational m_first; + int m_second; + public: + + unsigned hash() const { + return m_first.hash() ^ (static_cast(m_second) + 1); + } + + struct hash_proc { unsigned operator()(inf_int_rational const& r) const { return r.hash(); } }; + + struct eq_proc { bool operator()(inf_int_rational const& r1, inf_int_rational const& r2) const { return r1 == r2; } }; + + void swap(inf_int_rational & n) { + m_first.swap(n.m_first); + std::swap(m_second, n.m_second); + } + + std::string to_string() const; + + inf_int_rational(): + m_first(rational()), + m_second(0) + {} + + inf_int_rational(const inf_int_rational & r): + m_first(r.m_first), + m_second(r.m_second) + {} + + explicit inf_int_rational(int n): + m_first(rational(n)), + m_second(0) + {} + + explicit inf_int_rational(int n, int d): + m_first(rational(n,d)), + m_second(0) + {} + + explicit inf_int_rational(rational const& r, bool pos_inf): + m_first(r), + m_second(pos_inf?1:-1) + {} + + explicit inf_int_rational(rational const& r): + m_first(r), + m_second(0) {} + + inf_int_rational(rational const& r, int i): + m_first(r), + m_second(i) { + } + + ~inf_int_rational() {} + + /** + \brief Set inf_int_rational to 0. + */ + void reset() { + m_first.reset(); + m_second = 0; + } + + bool is_int() const { + return m_first.is_int() && m_second == 0; + } + + bool is_int64() const { + return m_first.is_int64() && m_second == 0; + } + + bool is_uint64() const { + return m_first.is_uint64() && m_second == 0; + } + + bool is_rational() const { return m_second == 0; } + + int64 get_int64() const { + SASSERT(is_int64()); + return m_first.get_int64(); + } + + uint64 get_uint64() const { + SASSERT(is_uint64()); + return m_first.get_uint64(); + } + + rational const& get_rational() const { + return m_first; + } + + rational get_infinitesimal() const { + return rational(m_second); + } + + rational const & get_first() const { return m_first; } + + inf_int_rational & operator=(const inf_int_rational & r) { + m_first = r.m_first; + m_second = r.m_second; + return *this; + } + + inf_int_rational & operator=(const rational & r) { + m_first = r; + m_second = 0; + return *this; + } + + friend inline inf_int_rational numerator(const inf_int_rational & r) { + SASSERT(r.m_second == 0); + return inf_int_rational(numerator(r.m_first)); + } + + friend inline inf_int_rational denominator(const inf_int_rational & r) { + SASSERT(r.m_second == 0); + return inf_int_rational(denominator(r.m_first)); + } + + inf_int_rational & operator+=(const inf_int_rational & r) { + m_first += r.m_first; + m_second += r.m_second; + return *this; + } + + inf_int_rational & operator-=(const inf_int_rational & r) { + m_first -= r.m_first; + m_second -= r.m_second; + return *this; + } + + inf_int_rational & operator+=(const rational & r) { + m_first += r; + return *this; + } + + inf_int_rational & operator-=(const rational & r) { + m_first -= r; + return *this; + } + + inf_int_rational & operator++() { + ++m_first; + return *this; + } + + const inf_int_rational operator++(int) { inf_int_rational tmp(*this); ++(*this); return tmp; } + + inf_int_rational & operator--() { + --m_first; + return *this; + } + + const inf_int_rational operator--(int) { inf_int_rational tmp(*this); --(*this); return tmp; } + + friend inline bool operator==(const inf_int_rational & r1, const inf_int_rational & r2) { + return r1.m_first == r2.m_first && r1.m_second == r2.m_second; + } + + friend inline bool operator==(const rational & r1, const inf_int_rational & r2) { + return r1 == r2.m_first && r2.m_second == 0; + } + + friend inline bool operator==(const inf_int_rational & r1, const rational & r2) { + return r1.m_first == r2 && r1.m_second == 0; + } + + friend inline bool operator<(const inf_int_rational & r1, const inf_int_rational & r2) { + return + (r1.m_first < r2.m_first) || + (r1.m_first == r2.m_first && r1.m_second < r2.m_second); + } + + friend inline bool operator<(const rational & r1, const inf_int_rational & r2) { + return + (r1 < r2.m_first) || + (r1 == r2.m_first && r2.m_second > 0); + } + + friend inline bool operator<(const inf_int_rational & r1, const rational & r2) { + return + (r1.m_first < r2) || + (r1.m_first == r2 && r1.m_second < 0); + } + + void neg() { + m_first.neg(); + m_second = -m_second; + } + + bool is_zero() const { + return m_first.is_zero() && m_second == 0; + } + + bool is_one() const { + return m_first.is_one() && m_second == 0; + } + + bool is_minus_one() const { + return m_first.is_minus_one() && m_second == 0; + } + + bool is_neg() const { + return + m_first.is_neg() || + (m_first.is_zero() && m_second < 0); + } + + bool is_pos() const { + return + m_first.is_pos() || + (m_first.is_zero() && m_second > 0); + } + + bool is_nonneg() const { + return + m_first.is_pos() || + (m_first.is_zero() && m_second >= 0); + } + + bool is_nonpos() const { + return + m_first.is_neg() || + (m_first.is_zero() && m_second <= 0); + } + + friend inline rational floor(const inf_int_rational & r) { + if (r.m_first.is_int()) { + if (r.m_second >= 0) { + return r.m_first; + } + return r.m_first - rational(1); + } + + return floor(r.m_first); + } + + friend inline rational ceil(const inf_int_rational & r) { + if (r.m_first.is_int()) { + if (r.m_second <= 0) { + return r.m_first; + } + return r.m_first + rational(1); + } + + return ceil(r.m_first); + } + + static const inf_int_rational & zero() { + return m_zero; + } + + static const inf_int_rational & one() { + return m_one; + } + + static const inf_int_rational & minus_one() { + return m_minus_one; + } + +}; + +inline bool operator!=(const inf_int_rational & r1, const inf_int_rational & r2) { + return !operator==(r1, r2); +} + +inline bool operator!=(const rational & r1, const inf_int_rational & r2) { + return !operator==(r1, r2); +} + +inline bool operator!=(const inf_int_rational & r1, const rational & r2) { + return !operator==(r1, r2); +} + +inline bool operator>(const inf_int_rational & r1, const inf_int_rational & r2) { + return operator<(r2, r1); +} + +inline bool operator>(const inf_int_rational & r1, const rational & r2) { + return operator<(r2, r1); +} + +inline bool operator>(const rational & r1, const inf_int_rational & r2) { + return operator<(r2, r1); +} + +inline bool operator<=(const inf_int_rational & r1, const inf_int_rational & r2) { + return !operator>(r1, r2); +} + +inline bool operator<=(const rational & r1, const inf_int_rational & r2) { + return !operator>(r1, r2); +} + +inline bool operator<=(const inf_int_rational & r1, const rational & r2) { + return !operator>(r1, r2); +} + +inline bool operator>=(const inf_int_rational & r1, const inf_int_rational & r2) { + return !operator<(r1, r2); +} + +inline bool operator>=(const rational & r1, const inf_int_rational & r2) { + return !operator<(r1, r2); +} + +inline bool operator>=(const inf_int_rational & r1, const rational & r2) { + return !operator<(r1, r2); +} + +inline inf_int_rational operator+(const inf_int_rational & r1, const inf_int_rational & r2) { + return inf_int_rational(r1) += r2; +} + +inline inf_int_rational operator-(const inf_int_rational & r1, const inf_int_rational & r2) { + return inf_int_rational(r1) -= r2; +} + +inline inf_int_rational operator-(const inf_int_rational & r) { + inf_int_rational result(r); + result.neg(); + return result; +} + +inline std::ostream & operator<<(std::ostream & target, const inf_int_rational & r) +{ + target << r.to_string(); + return target; +} + + +inline inf_int_rational abs(const inf_int_rational & r) { + inf_int_rational result(r); + if (result.is_neg()) { + result.neg(); + } + return result; +} + +#endif /* _INF_INT_RATIONAL_H_ */ diff --git a/lib/inf_rational.cpp b/lib/inf_rational.cpp new file mode 100644 index 000000000..3837d2c15 --- /dev/null +++ b/lib/inf_rational.cpp @@ -0,0 +1,179 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + inf_rational.cpp + +Abstract: + + Rational numbers with infenitesimals + +Author: + + Nikolaj Bjorner (nbjorner) 2006-12-05. + +Revision History: + +--*/ +#include"inf_rational.h" + +inf_rational inf_rational::m_zero(0); +inf_rational inf_rational::m_one(1); +inf_rational inf_rational::m_minus_one(-1); + +inf_rational inf_mult(inf_rational const& r1, inf_rational const& r2) +{ + inf_rational result; + result.m_first = r1.m_first * r2.m_first; + result.m_second = (r1.m_first * r2.m_second) + (r1.m_second * r2.m_first); + + if (r1.m_second.is_pos() && r2.m_second.is_neg()) { + --result.m_second; + } + else if (r1.m_second.is_neg() && r2.m_second.is_pos()) { + --result.m_second; + } + return result; +} + +inf_rational sup_mult(inf_rational const& r1, inf_rational const& r2) +{ + inf_rational result; + result.m_first = r1.m_first * r2.m_first; + result.m_second = (r1.m_first * r2.m_second) + (r1.m_second * r2.m_first); + + if (r1.m_second.is_pos() && r2.m_second.is_pos()) { + ++result.m_second; + } + else if (r1.m_second.is_neg() && r2.m_second.is_neg()) { + ++result.m_second; + } + return result; +} + +// +// Find rationals c, x, such that c + epsilon*x <= r1/r2 +// +// let r1 = a + d_1 +// let r2 = b + d_2 +// +// suppose b != 0: +// +// r1/b <= r1/r2 +// <=> { if b > 0, then r2 > 0, and cross multiplication does not change the sign } +// { if b < 0, then r2 < 0, and cross multiplication changes sign twice } +// r1 * r2 <= b * r1 +// <=> +// r1 * (b + d_2) <= r1 * b +// <=> +// r1 * d_2 <= 0 +// +// if r1 * d_2 > 0, then r1/(b + sign_of(r1)*1/2*|b|) <= r1/r2 +// +// Not handled here: +// if b = 0, then d_2 != 0 +// if r1 * d_2 = 0 then it's 0. +// if r1 * d_2 > 0, then result is +oo +// if r1 * d_2 < 0, then result is -oo +// +inf_rational inf_div(inf_rational const& r1, inf_rational const& r2) +{ + SASSERT(!r2.m_first.is_zero()); + inf_rational result; + + if (r2.m_second.is_neg() && r1.is_neg()) { + result = r1 / (r2.m_first - (abs(r2.m_first)/rational(2))); + } + else if (r2.m_second.is_pos() && r1.is_pos()) { + result = r1 / (r2.m_first + (abs(r2.m_first)/rational(2))); + } + else { + result = r1 / r2.m_first; + } + return result; +} + +inf_rational sup_div(inf_rational const& r1, inf_rational const& r2) +{ + SASSERT(!r2.m_first.is_zero()); + inf_rational result; + + if (r2.m_second.is_pos() && r1.is_neg()) { + result = r1 / (r2.m_first + (abs(r2.m_first)/rational(2))); + } + else if (r2.m_second.is_neg() && r1.is_pos()) { + result = r1 / (r2.m_first - (abs(r2.m_first)/rational(2))); + } + else { + result = r1 / r2.m_first; + } + return result; + +} + +inf_rational inf_power(inf_rational const& r, unsigned n) +{ + bool is_even = (0 == (n & 0x1)); + inf_rational result; + if (n == 1) { + result = r; + } + else if ((r.m_second.is_zero()) || + (r.m_first.is_pos() && r.m_second.is_pos()) || + (r.m_first.is_neg() && r.m_second.is_neg() && is_even)) { + result.m_first = r.m_first.expt(n); + } + else if (is_even) { + // 0 will work. + } + else if (r.m_first.is_zero()) { + result.m_first = rational(-1); + } + else if (r.m_first.is_pos()) { + result.m_first = rational(r.m_first - r.m_first/rational(2)).expt(n); + } + else { + result.m_first = rational(r.m_first + r.m_first/rational(2)).expt(n); + } + return result; +} + +inf_rational sup_power(inf_rational const& r, unsigned n) +{ + bool is_even = (0 == (n & 0x1)); + inf_rational result; + if (n == 1) { + result = r; + } + else if (r.m_second.is_zero() || + (r.m_first.is_pos() && r.m_second.is_neg()) || + (r.m_first.is_neg() && r.m_second.is_pos() && is_even)) { + result.m_first = r.m_first.expt(n); + } + else if (r.m_first.is_zero() || (n == 0)) { + result.m_first = rational(1); + } + else if (r.m_first.is_pos() || is_even) { + result.m_first = rational(r.m_first + r.m_first/rational(2)).expt(n); + } + else { + // r (r.m_first) is negative, n is odd. + result.m_first = rational(r.m_first - r.m_first/rational(2)).expt(n); + } + return result; +} + +inf_rational inf_root(inf_rational const& r, unsigned n) +{ + SASSERT(!r.is_neg()); + // use 0 + return inf_rational(); +} + +inf_rational sup_root(inf_rational const& r, unsigned n) +{ + SASSERT(!r.is_neg()); + // use r. + return r; +} diff --git a/lib/inf_rational.h b/lib/inf_rational.h new file mode 100644 index 000000000..9e1753484 --- /dev/null +++ b/lib/inf_rational.h @@ -0,0 +1,470 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + inf_rational.h + +Abstract: + + Rational numbers with infenitesimals + +Author: + + Leonardo de Moura (leonardo) 2006-09-18. + Nikolaj Bjorner (nbjorner) 2006-10-24. + +Revision History: + +--*/ +#ifndef _INF_RATIONAL_H_ +#define _INF_RATIONAL_H_ +#include +#include +#include"debug.h" +#include"vector.h" +#include"rational.h" + + +class inf_rational { + static inf_rational m_zero; + static inf_rational m_one; + static inf_rational m_minus_one; + rational m_first; + rational m_second; + public: + + unsigned hash() const { + return m_first.hash() ^ (m_second.hash()+1); + } + + struct hash_proc { unsigned operator()(inf_rational const& r) const { return r.hash(); } }; + + struct eq_proc { bool operator()(inf_rational const& r1, inf_rational const& r2) const { return r1 == r2; } }; + + void swap(inf_rational & n) { + m_first.swap(n.m_first); + m_second.swap(n.m_second); + } + std::string to_string() const { + if (m_second.is_zero()) { + return m_first.to_string(); + } + std::string s = "("; + s += m_first.to_string(); + if (m_second.is_neg()) { + s += " -e*"; + } + else { + s += " +e*"; + } + s += abs(m_second).to_string(); + s += ")"; + return s; + } + + inf_rational(): + m_first(rational()), + m_second(rational()) + {} + + inf_rational(const inf_rational & r): + m_first(r.m_first), + m_second(r.m_second) + {} + + explicit inf_rational(int n): + m_first(rational(n)), + m_second(rational()) + {} + + explicit inf_rational(int n, int d): + m_first(rational(n,d)), + m_second(rational()) + {} + + explicit inf_rational(rational const& r, bool pos_inf): + m_first(r), + m_second(pos_inf?rational(1):rational(-1)) + {} + + explicit inf_rational(rational const& r): + m_first(r) + { + m_second.reset(); + } + + inf_rational(rational const& r, rational const& i): + m_first(r), + m_second(i) { + } + + ~inf_rational() {} + + /** + \brief Set inf_rational to 0. + */ + void reset() { + m_first.reset(); + m_second.reset(); + } + + bool is_int() const { + return m_first.is_int() && m_second.is_zero(); + } + + bool is_int64() const { + return m_first.is_int64() && m_second.is_zero(); + } + + bool is_uint64() const { + return m_first.is_uint64() && m_second.is_zero(); + } + + bool is_rational() const { return m_second.is_zero(); } + + int64 get_int64() const { + SASSERT(is_int64()); + return m_first.get_int64(); + } + + uint64 get_uint64() const { + SASSERT(is_uint64()); + return m_first.get_uint64(); + } + + rational const& get_rational() const { + return m_first; + } + + rational const& get_infinitesimal() const { + return m_second; + } + + rational const & get_first() const { return m_first; } + + inf_rational & operator=(const inf_rational & r) { + m_first = r.m_first; + m_second = r.m_second; + return *this; + } + + inf_rational & operator=(const rational & r) { + m_first = r; + m_second.reset(); + return *this; + } + + friend inline inf_rational numerator(const inf_rational & r) { + SASSERT(r.m_second.is_zero()); + return inf_rational(numerator(r.m_first)); + } + + friend inline inf_rational denominator(const inf_rational & r) { + SASSERT(r.m_second.is_zero()); + return inf_rational(denominator(r.m_first)); + } + + inf_rational & operator+=(const inf_rational & r) { + m_first += r.m_first; + m_second += r.m_second; + return *this; + } + + inf_rational & operator-=(const inf_rational & r) { + m_first -= r.m_first; + m_second -= r.m_second; + return *this; + } + + inf_rational & operator+=(const rational & r) { + m_first += r; + return *this; + } + + inf_rational & operator-=(const rational & r) { + m_first -= r; + return *this; + } + + inf_rational & operator*=(const rational & r1) { + m_first *= r1; + m_second *= r1; + return *this; + } + + // + // These operations get us out of the realm of inf_rational: + // (r1 + e*k1)*(r2 + e*k2) = (r1*r2 + (r1*k2 + r2*k1)*e) + // + // inf_rational & operator*=(const inf_rational & r) + // inf_rational & operator/=(const inf_rational & r) + // inf_rational & operator%=(const inf_rational & r) + // friend inline inf_rational div(const inf_rational & r1, const inf_rational & r2) + // inf_rational expt(int n) + // instead, we define operators that approximate some of these operations from above and below. + + friend inf_rational inf_mult(inf_rational const& r1, inf_rational const& r2); + friend inf_rational sup_mult(inf_rational const& r1, inf_rational const& r2); + + friend inf_rational inf_div(inf_rational const& r1, inf_rational const& r2); + friend inf_rational sup_div(inf_rational const& r1, inf_rational const& r2); + + friend inf_rational inf_power(inf_rational const& r1, unsigned n); + friend inf_rational sup_power(inf_rational const& r1, unsigned n); + + friend inf_rational inf_root(inf_rational const& r1, unsigned n); + friend inf_rational sup_root(inf_rational const& r1, unsigned n); + + inf_rational & operator/=(const rational & r) { + m_first /= r; + m_second /= r; + return *this; + } + + friend inline inf_rational operator*(const rational & r1, const inf_rational & r2); + friend inline inf_rational operator/(const inf_rational & r1, const rational & r2); + + inf_rational & operator++() { + ++m_first; + return *this; + } + + const inf_rational operator++(int) { inf_rational tmp(*this); ++(*this); return tmp; } + + inf_rational & operator--() { + --m_first; + return *this; + } + + const inf_rational operator--(int) { inf_rational tmp(*this); --(*this); return tmp; } + + friend inline bool operator==(const inf_rational & r1, const inf_rational & r2) { + return r1.m_first == r2.m_first && r1.m_second == r2.m_second; + } + + friend inline bool operator==(const rational & r1, const inf_rational & r2) { + return r1 == r2.m_first && r2.m_second.is_zero(); + } + + friend inline bool operator==(const inf_rational & r1, const rational & r2) { + return r1.m_first == r2 && r1.m_second.is_zero(); + } + + friend inline bool operator<(const inf_rational & r1, const inf_rational & r2) { + return + (r1.m_first < r2.m_first) || + (r1.m_first == r2.m_first && r1.m_second < r2.m_second); + } + + friend inline bool operator<(const rational & r1, const inf_rational & r2) { + return + (r1 < r2.m_first) || + (r1 == r2.m_first && r2.m_second.is_pos()); + } + + friend inline bool operator<(const inf_rational & r1, const rational & r2) { + return + (r1.m_first < r2) || + (r1.m_first == r2 && r1.m_second.is_neg()); + } + + void neg() { + m_first.neg(); + m_second.neg(); + } + + bool is_zero() const { + return m_first.is_zero() && m_second.is_zero(); + } + + bool is_one() const { + return m_first.is_one() && m_second.is_zero(); + } + + bool is_minus_one() const { + return m_first.is_minus_one() && m_second.is_zero(); + } + + bool is_neg() const { + return + m_first.is_neg() || + (m_first.is_zero() && m_second.is_neg()); + } + + bool is_pos() const { + return + m_first.is_pos() || + (m_first.is_zero() && m_second.is_pos()); + } + + bool is_nonneg() const { + return + m_first.is_pos() || + (m_first.is_zero() && m_second.is_nonneg()); + } + + bool is_nonpos() const { + return + m_first.is_neg() || + (m_first.is_zero() && m_second.is_nonpos()); + } + + friend inline rational floor(const inf_rational & r) { + if (r.m_first.is_int()) { + if (r.m_second.is_nonneg()) { + return r.m_first; + } + return r.m_first - rational(1); + } + + return floor(r.m_first); + } + + friend inline rational ceil(const inf_rational & r) { + if (r.m_first.is_int()) { + if (r.m_second.is_nonpos()) { + return r.m_first; + } + return r.m_first + rational(1); + } + + return ceil(r.m_first); + } + + static const inf_rational & zero() { + return m_zero; + } + + static const inf_rational & one() { + return m_one; + } + + static const inf_rational & minus_one() { + return m_minus_one; + } + + // Perform: this += c * k + void addmul(const rational & c, const inf_rational & k) { + m_first.addmul(c, k.m_first); + m_second.addmul(c, k.m_second); + } + + // Perform: this += c * k + void submul(const rational & c, const inf_rational & k) { + m_first.submul(c, k.m_first); + m_second.submul(c, k.m_second); + } +}; + +inline bool operator!=(const inf_rational & r1, const inf_rational & r2) { + return !operator==(r1, r2); +} + +inline bool operator!=(const rational & r1, const inf_rational & r2) { + return !operator==(r1, r2); +} + +inline bool operator!=(const inf_rational & r1, const rational & r2) { + return !operator==(r1, r2); +} + +inline bool operator>(const inf_rational & r1, const inf_rational & r2) { + return operator<(r2, r1); +} + +inline bool operator>(const inf_rational & r1, const rational & r2) { + return operator<(r2, r1); +} + +inline bool operator>(const rational & r1, const inf_rational & r2) { + return operator<(r2, r1); +} + +inline bool operator<=(const inf_rational & r1, const inf_rational & r2) { + return !operator>(r1, r2); +} + +inline bool operator<=(const rational & r1, const inf_rational & r2) { + return !operator>(r1, r2); +} + +inline bool operator<=(const inf_rational & r1, const rational & r2) { + return !operator>(r1, r2); +} + +inline bool operator>=(const inf_rational & r1, const inf_rational & r2) { + return !operator<(r1, r2); +} + +inline bool operator>=(const rational & r1, const inf_rational & r2) { + return !operator<(r1, r2); +} + +inline bool operator>=(const inf_rational & r1, const rational & r2) { + return !operator<(r1, r2); +} + +inline inf_rational operator+(const inf_rational & r1, const inf_rational & r2) { + return inf_rational(r1) += r2; +} + +inline inf_rational operator-(const inf_rational & r1, const inf_rational & r2) { + return inf_rational(r1) -= r2; +} + +inline inf_rational operator-(const inf_rational & r) { + inf_rational result(r); + result.neg(); + return result; +} + +inline inf_rational operator*(const rational & r1, const inf_rational & r2) { + inf_rational result(r2); + result.m_first *= r1; + result.m_second *= r1; + return result; +} + +inline inf_rational operator/(const inf_rational & r1, const rational & r2) { + inf_rational result(r1); + result.m_first /= r2; + result.m_second /= r2; + return result; +} + +#if 0 +inf_rational inf_mult(inf_rational const& r1, inf_rational const& r2); +inf_rational sup_mult(inf_rational const& r1, inf_rational const& r2); + +inf_rational inf_div(inf_rational const& r1, inf_rational const& r2); +inf_rational sup_div(inf_rational const& r1, inf_rational const& r2); + +inf_rational inf_power(inf_rational const& r1, unsigned n); +inf_rational sup_power(inf_rational const& r1, unsigned n); + +inf_rational inf_root(inf_rational const& r1, unsigned n); +inf_rational sup_root(inf_rational const& r1, unsigned n); +#endif +// +// inline inf_rational operator/(const inf_rational & r1, const inf_rational & r2) +// inline inf_rational operator%(const inf_rational & r1, const inf_rational & r2) +// inf_rational gcd(const inf_rational & r1, const inf_rational & r2); +// inf_rational lcm(const inf_rational & r1, const inf_rational & r2); + +inline std::ostream & operator<<(std::ostream & target, const inf_rational & r) +{ + target << r.to_string(); + return target; +} + + +inline inf_rational abs(const inf_rational & r) { + inf_rational result(r); + if (result.is_neg()) { + result.neg(); + } + return result; +} + +#endif /* _INF_RATIONAL_H_ */ diff --git a/lib/inf_s_integer.cpp b/lib/inf_s_integer.cpp new file mode 100644 index 000000000..602dc14ac --- /dev/null +++ b/lib/inf_s_integer.cpp @@ -0,0 +1,25 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + inf_s_integer.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-06-16. + +Revision History: + +--*/ + +#include"inf_s_integer.h" + +inf_s_integer inf_s_integer::m_zero(0); +inf_s_integer inf_s_integer::m_one(1); +inf_s_integer inf_s_integer::m_minus_one(-1); + diff --git a/lib/inf_s_integer.h b/lib/inf_s_integer.h new file mode 100644 index 000000000..b039333af --- /dev/null +++ b/lib/inf_s_integer.h @@ -0,0 +1,351 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + inf_s_integer.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-06-11. + +Revision History: + +--*/ +#ifndef _INF_S_INTEGER_H_ +#define _INF_S_INTEGER_H_ + +#include"s_integer.h" +#include"rational.h" + +class inf_s_integer { + static inf_s_integer m_zero; + static inf_s_integer m_one; + static inf_s_integer m_minus_one; + int m_first; + int m_second; + public: + + unsigned hash() const { + return m_first ^ (m_second + 1); + } + + struct hash_proc { unsigned operator()(inf_s_integer const& r) const { return r.hash(); } }; + + struct eq_proc { bool operator()(inf_s_integer const& r1, inf_s_integer const& r2) const { return r1 == r2; } }; + + void swap(inf_s_integer & n) { + std::swap(m_first, n.m_first); + std::swap(m_second, n.m_second); + } + + std::string to_string() const; + + inf_s_integer():m_first(0), m_second(0) {} + + inf_s_integer(const inf_s_integer & r):m_first(r.m_first), m_second(r.m_second) {} + + explicit inf_s_integer(int n):m_first(n), m_second(0) {} + explicit inf_s_integer(int n, int d): m_first(n), m_second(0) { SASSERT(d == 1); } + explicit inf_s_integer(s_integer const& r, bool pos_inf):m_first(r.get_int()), m_second(pos_inf ? 1 : -1) {} + explicit inf_s_integer(s_integer const& r):m_first(r.get_int()), m_second(0) {} + explicit inf_s_integer(rational const& r):m_first(static_cast(r.get_int64())), m_second(0) {} + inf_s_integer(s_integer const& r, s_integer const& i):m_first(r.get_int()), m_second(i.get_int()) {} + void reset() { m_first = 0; m_second = 0; } + bool is_int() const { return m_second == 0; } + bool is_int64() const { return m_second == 0; } + bool is_uint64() const { return m_second == 0; } + bool is_rational() const { return m_second == 0; } + int64 get_int64() const { return m_first; } + uint64 get_uint64() const { return m_first; } + s_integer get_rational() const { return s_integer(m_first); } + s_integer get_infinitesimal() const { return s_integer(m_second); } + inf_s_integer & operator=(const inf_s_integer & r) { + m_first = r.m_first; + m_second = r.m_second; + return *this; + } + inf_s_integer & operator=(const rational & r) { + m_first = static_cast(r.get_int64()); + m_second = 0; + return *this; + } + inf_s_integer & operator=(const s_integer & r) { + m_first = r.get_int(); + m_second = 0; + return *this; + } + friend inline inf_s_integer numerator(const inf_s_integer & r) { + SASSERT(r.m_second == 0); + return inf_s_integer(r.m_first); + } + friend inline inf_s_integer denominator(const inf_s_integer & r) { + SASSERT(r.m_second == 0); + return inf_s_integer(1); + } + inf_s_integer & operator+=(const inf_s_integer & r) { + m_first += r.m_first; + m_second += r.m_second; + return *this; + } + inf_s_integer & operator-=(const inf_s_integer & r) { + m_first -= r.m_first; + m_second -= r.m_second; + return *this; + } + inf_s_integer & operator+=(const s_integer & r) { + m_first += r.get_int(); + return *this; + } + inf_s_integer & operator-=(const s_integer & r) { + m_first -= r.get_int(); + return *this; + } + inf_s_integer & operator*=(const s_integer & r1) { + m_first *= r1.get_int(); + m_second *= r1.get_int(); + return *this; + } + +// friend inf_s_integer inf_mult(inf_s_integer const& r1, inf_s_integer const& r2); +// friend inf_s_integer sup_mult(inf_s_integer const& r1, inf_s_integer const& r2); + +// friend inf_s_integer inf_div(inf_s_integer const& r1, inf_s_integer const& r2); +// friend inf_s_integer sup_div(inf_s_integer const& r1, inf_s_integer const& r2); + +// friend inf_s_integer inf_power(inf_s_integer const& r1, unsigned n); +// friend inf_s_integer sup_power(inf_s_integer const& r1, unsigned n); + +// friend inf_s_integer inf_root(inf_s_integer const& r1, unsigned n); +// friend inf_s_integer sup_root(inf_s_integer const& r1, unsigned n); + + inf_s_integer & operator/=(const s_integer & r) { + m_first /= r.get_int(); + m_second /= r.get_int(); + return *this; + } + + friend inline inf_s_integer operator*(const s_integer & r1, const inf_s_integer & r2); + friend inline inf_s_integer operator/(const inf_s_integer & r1, const s_integer & r2); + + inf_s_integer & operator++() { + ++m_first; + return *this; + } + + const inf_s_integer operator++(int) { inf_s_integer tmp(*this); ++(*this); return tmp; } + + inf_s_integer & operator--() { + --m_first; + return *this; + } + + const inf_s_integer operator--(int) { inf_s_integer tmp(*this); --(*this); return tmp; } + + friend inline bool operator==(const inf_s_integer & r1, const inf_s_integer & r2) { + return r1.m_first == r2.m_first && r1.m_second == r2.m_second; + } + + friend inline bool operator==(const s_integer & r1, const inf_s_integer & r2) { + return r1.get_int() == r2.m_first && r2.m_second == 0; + } + + friend inline bool operator==(const inf_s_integer & r1, const s_integer & r2) { + return r1.m_first == r2.get_int() && r1.m_second == 0; + } + + friend inline bool operator<(const inf_s_integer & r1, const inf_s_integer & r2) { + return + (r1.m_first < r2.m_first) || + (r1.m_first == r2.m_first && r1.m_second < r2.m_second); + } + + friend inline bool operator<(const s_integer & r1, const inf_s_integer & r2) { + return + (r1.get_int() < r2.m_first) || + (r1.get_int() == r2.m_first && r2.m_second > 0); + } + + friend inline bool operator<(const inf_s_integer & r1, const s_integer & r2) { + return + (r1.m_first < r2.get_int()) || + (r1.m_first == r2.get_int() && r1.m_second < 0); + } + + void neg() { + m_first = -m_first; + m_second = -m_second; + } + + bool is_zero() const { + return m_first == 0 && m_second == 0; + } + + bool is_one() const { + return m_first == 1 && m_second == 0; + } + + bool is_minus_one() const { + return m_first == -1 && m_second == 0; + } + + bool is_neg() const { + return m_first < 0 || (m_first == 0 && m_second < 0); + } + + bool is_pos() const { + return m_first > 0 || (m_first == 0 && m_second > 0); + } + + bool is_nonneg() const { + return m_first > 0 || (m_first == 0 && m_second >= 0); + } + + bool is_nonpos() const { + return m_first < 0 || (m_first == 0 && m_second <= 0); + } + + friend inline s_integer floor(const inf_s_integer & r) { + if (r.m_second >= 0) { + return s_integer(r.m_first); + } + return s_integer(r.m_first - 1); + } + + friend inline s_integer ceil(const inf_s_integer & r) { + if (r.m_second <= 0) { + return s_integer(r.m_first); + } + return s_integer(r.m_first + 1); + } + + static const inf_s_integer & zero() { + return m_zero; + } + + static const inf_s_integer & one() { + return m_one; + } + + static const inf_s_integer & minus_one() { + return m_minus_one; + } + + // Perform: this += c * k + void addmul(const s_integer & c, const inf_s_integer & k) { + m_first += c.get_int() * k.m_first; + m_second += c.get_int() * k.m_second; + } + + // Perform: this += c * k + void submul(const s_integer & c, const inf_s_integer & k) { + m_first -= c.get_int() * k.m_first; + m_second -= c.get_int() * k.m_second; + } + + friend inline std::ostream & operator<<(std::ostream & target, const inf_s_integer & r) { + if (r.m_second == 0) { + target << r.m_first; + } + else if (r.m_second < 0) { + target << "(" << r.m_first << " -e*" << r.m_second << ")"; + } + else { + target << "(" << r.m_first << " +e*" << r.m_second << ")"; + } + return target; + } + +}; + +inline bool operator!=(const inf_s_integer & r1, const inf_s_integer & r2) { + return !operator==(r1, r2); +} + +inline bool operator!=(const s_integer & r1, const inf_s_integer & r2) { + return !operator==(r1, r2); +} + +inline bool operator!=(const inf_s_integer & r1, const s_integer & r2) { + return !operator==(r1, r2); +} + +inline bool operator>(const inf_s_integer & r1, const inf_s_integer & r2) { + return operator<(r2, r1); +} + +inline bool operator>(const inf_s_integer & r1, const s_integer & r2) { + return operator<(r2, r1); +} + +inline bool operator>(const s_integer & r1, const inf_s_integer & r2) { + return operator<(r2, r1); +} + +inline bool operator<=(const inf_s_integer & r1, const inf_s_integer & r2) { + return !operator>(r1, r2); +} + +inline bool operator<=(const s_integer & r1, const inf_s_integer & r2) { + return !operator>(r1, r2); +} + +inline bool operator<=(const inf_s_integer & r1, const s_integer & r2) { + return !operator>(r1, r2); +} + +inline bool operator>=(const inf_s_integer & r1, const inf_s_integer & r2) { + return !operator<(r1, r2); +} + +inline bool operator>=(const s_integer & r1, const inf_s_integer & r2) { + return !operator<(r1, r2); +} + +inline bool operator>=(const inf_s_integer & r1, const s_integer & r2) { + return !operator<(r1, r2); +} + +inline inf_s_integer operator+(const inf_s_integer & r1, const inf_s_integer & r2) { + return inf_s_integer(r1) += r2; +} + +inline inf_s_integer operator-(const inf_s_integer & r1, const inf_s_integer & r2) { + return inf_s_integer(r1) -= r2; +} + +inline inf_s_integer operator-(const inf_s_integer & r) { + inf_s_integer result(r); + result.neg(); + return result; +} + +inline inf_s_integer operator*(const s_integer & r1, const inf_s_integer & r2) { + inf_s_integer result(r2); + result.m_first *= r1.get_int(); + result.m_second *= r1.get_int(); + return result; +} + +inline inf_s_integer operator/(const inf_s_integer & r1, const s_integer & r2) { + inf_s_integer result(r1); + result.m_first /= r2.get_int(); + result.m_second /= r2.get_int(); + return result; +} + +inline inf_s_integer abs(const inf_s_integer & r) { + inf_s_integer result(r); + if (result.is_neg()) { + result.neg(); + } + return result; +} + + +#endif /* _INF_S_INTEGER_H_ */ + diff --git a/lib/ini_file.cpp b/lib/ini_file.cpp new file mode 100644 index 000000000..48279a2ea --- /dev/null +++ b/lib/ini_file.cpp @@ -0,0 +1,1564 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + ini_file.cpp + +Abstract: + + Configuration file support. + +Author: + + Leonardo de Moura (leonardo) 2007-05-10. + +Revision History: + +--*/ +#include"ini_file.h" +#include"util.h" +#include"trace.h" +#include"str_hashtable.h" +#include"map.h" +#include"string_buffer.h" +#include"symbol_table.h" +#include"error_codes.h" +#include + +template +class value_vector_map { + void * m_owner; + map *, ptr_hash, ptr_eq > m_mapping; + ptr_vector m_domain; + ptr_vector > m_range; + unsigned m_max_size; +public: + value_vector_map(void * owner):m_owner(owner), m_max_size(0) {} + + ~value_vector_map() { + std::for_each(m_range.begin(), m_range.end(), delete_proc >()); + } + + void insert(T * ptr, T const & value) { + SASSERT(reinterpret_cast(ptr) >= reinterpret_cast(m_owner)); + vector * vect; + if (m_mapping.find(ptr, vect)) { + vect->push_back(value); + if (vect->size() > m_max_size) + m_max_size = vect->size(); + return; + } + vect = alloc(vector); + m_range.push_back(vect); + m_domain.push_back(ptr); + vect->push_back(value); + if (m_max_size == 0) + m_max_size = 1; + m_mapping.insert(ptr, vect); + } + + void copy_params(void * curr_owner, unsigned idx) { + typename ptr_vector::iterator it = m_domain.begin(); + typename ptr_vector::iterator end = m_domain.end(); + for (; it != end; ++it) { + T * ptr = *it; + vector * vect = 0; + m_mapping.find(ptr, vect); + SASSERT(vect != 0); + if (idx < vect->size()) { + // BIG HACK + SASSERT(reinterpret_cast(ptr) >= reinterpret_cast(m_owner)); + size_t offset = reinterpret_cast(ptr) - reinterpret_cast(m_owner); + T * curr_ptr = reinterpret_cast(reinterpret_cast(curr_owner) + offset); + *curr_ptr = vect->operator[](idx); + } + } + } + + unsigned size(void) const { return m_max_size; } +}; + +struct param_vector_imp { + value_vector_map m_bool_params; + value_vector_map m_unsigned_params; + value_vector_map m_int_params; + value_vector_map m_double_params; + value_vector_map m_string_params; + value_vector_map m_symbol_params; + value_vector_map > m_symbol_list_params; + value_vector_map > m_symbol_nat_list_params; + + param_vector_imp(void * owner): + m_bool_params(owner), + m_unsigned_params(owner), + m_int_params(owner), + m_double_params(owner), + m_string_params(owner), + m_symbol_params(owner), + m_symbol_list_params(owner), + m_symbol_nat_list_params(owner) { + } + + void insert_bool_param(bool * value_ptr, bool value) { + TRACE("param_vector", tout << "insert: " << value_ptr << " -> " << value << "\n";); + m_bool_params.insert(value_ptr, value); + } + + void insert_unsigned_param(unsigned * value_ptr, unsigned value) { + TRACE("param_vector", tout << "insert: " << value_ptr << " -> " << value << "\n";); + m_unsigned_params.insert(value_ptr, value); + } + + void insert_int_param(int * value_ptr, int value) { + TRACE("param_vector", tout << "insert: " << value_ptr << " -> " << value << "\n";); + m_int_params.insert(value_ptr, value); + } + + void insert_double_param(double * value_ptr, double value) { + TRACE("param_vector", tout << "insert: " << value_ptr << " -> " << value << "\n";); + m_double_params.insert(value_ptr, value); + } + + void insert_string_param(std::string * value_ptr, std::string const & value) { + TRACE("param_vector", tout << "insert: " << value_ptr << " -> " << value << "\n";); + m_string_params.insert(value_ptr, value); + } + + void insert_symbol_param(symbol * value_ptr, symbol const & value) { + TRACE("param_vector", tout << "insert: " << value_ptr << " -> " << value << "\n";); + m_symbol_params.insert(value_ptr, value); + } + + void insert_symbol_list_param(svector * value_ptr, svector const & value) { + TRACE("param_vector", tout << "insert: " << value_ptr << " -> "; display(tout, value.begin(), value.end()); tout << "\n";); + m_symbol_list_params.insert(value_ptr, value); + } + + void insert_symbol_nat_list_param(svector * value_ptr, svector const & value) { + TRACE("param_vector", tout << "insert: " << value_ptr << " -> "; display(tout, value.begin(), value.end()); tout << "\n";); + m_symbol_nat_list_params.insert(value_ptr, value); + } + + void copy_params(void * curr_owner, unsigned idx) { + m_bool_params.copy_params(curr_owner, idx); + m_unsigned_params.copy_params(curr_owner, idx); + m_int_params.copy_params(curr_owner, idx); + m_double_params.copy_params(curr_owner, idx); + m_string_params.copy_params(curr_owner, idx); + m_symbol_params.copy_params(curr_owner, idx); + m_symbol_list_params.copy_params(curr_owner, idx); + m_symbol_nat_list_params.copy_params(curr_owner, idx); + } + + unsigned size(void) const { + unsigned ret = 0; + ret = std::max(ret, m_bool_params.size()); + ret = std::max(ret, m_unsigned_params.size()); + ret = std::max(ret, m_int_params.size()); + ret = std::max(ret, m_double_params.size()); + ret = std::max(ret, m_string_params.size()); + ret = std::max(ret, m_symbol_params.size()); + ret = std::max(ret, m_symbol_list_params.size()); + ret = std::max(ret, m_symbol_nat_list_params.size()); + return ret; + } +}; + + +param_vector::param_vector(void * owner): + m_ref_count(0) { + m_imp = alloc(param_vector_imp, owner); +} + +param_vector::~param_vector() { + dealloc(m_imp); +} + +void param_vector::inc_ref() { + m_ref_count++; +} + +void param_vector::dec_ref() { + SASSERT(m_ref_count > 0); + m_ref_count--; + if (m_ref_count == 0) + dealloc(this); +} + +void param_vector::copy_params(void * curr_owner, unsigned idx) { + m_imp->copy_params(curr_owner, idx); +} + +unsigned param_vector::size(void) const +{ + return m_imp->size(); +} + +enum itoken { + ITK_NULL, ITK_ID, ITK_NUM, ITK_DOUBLE, ITK_STRING, ITK_BAD_STRING, ITK_TRUE, ITK_FALSE, ITK_COMMA, ITK_LP, ITK_RP, ITK_LCB, ITK_RCB, ITK_CLN, ITK_EQ, ITK_BAD_ID, ITK_EOS, ITK_LAST +}; + +class ini_reserved_symbols { + typedef map str2token; + str2token m_str2token; + +public: + ini_reserved_symbols() { + m_str2token.insert("true", ITK_TRUE); + m_str2token.insert("false", ITK_FALSE); + } + + itoken string2itoken(char const * str) { + str2token::entry * e = m_str2token.find_core(const_cast(str)); + if (e) + return e->get_data().m_value; + else + return ITK_ID; + } +}; + +static char const * g_itoken2string[] = { + "", + "", + "", + "", + "", + "", + "true", + "false", + ",", + "(", + ")", + "{", + "}", + ":", + "=", + "", + "" +}; + +COMPILE_TIME_ASSERT(sizeof(g_itoken2string)/sizeof(char const*) == ITK_LAST); + +inline static const char * itoken2string(itoken t) { + return g_itoken2string[t]; +} + +inline itoken string2itoken(ini_reserved_symbols& reserved, char const * str) { + itoken r = reserved.string2itoken(str); + TRACE("token", tout << str << " -> " << itoken2string(r) << "\n";); + return r; +} + +class ini_lexer { + std::istream & m_input; + char m_curr_char; + int m_line; + int m_pos; + int m_tok_pos; + string_buffer<> m_buffer; + ini_reserved_symbols m_ini_reserved; +public: + bool eos() const { + return m_curr_char == EOF; + } + + void next() { + m_curr_char = m_input.get(); + m_pos++; + } + + void save_and_next() { + m_buffer << m_curr_char; + next(); + } + + ini_lexer(std::istream & input): + m_input(input), + m_line(1), + m_pos(0), + m_tok_pos(0), + m_ini_reserved() { + next(); + } + + itoken read_num() { + while (isdigit(m_curr_char)) { + save_and_next(); + } + if (m_curr_char == '.') { + save_and_next(); + while (isdigit(m_curr_char)) { + save_and_next(); + } + if (m_curr_char == 'e' || m_curr_char == 'E' || m_curr_char == 'd' || m_curr_char == 'D') { + save_and_next(); + if (m_curr_char == '-') { + save_and_next(); + } + while (isdigit(m_curr_char)) { + save_and_next(); + } + } + return ITK_DOUBLE; + } + return ITK_NUM; + } + + itoken read_id() { + while (!eos() && (isalpha(m_curr_char) || isdigit(m_curr_char) || m_curr_char == '_')) { + save_and_next(); + } + return string2itoken(m_ini_reserved, m_buffer.c_str()); + } + + itoken read_string() { + m_tok_pos = m_pos; + next(); + while (m_curr_char != '"' && m_curr_char != '\'') { + if (m_input.eof()) { + return ITK_BAD_STRING; + } + if (m_curr_char == '\n') { + return ITK_BAD_STRING; + } + if (m_curr_char == '\\') { + next(); // do not save the '\' + switch (m_curr_char) { + case 't': + m_buffer << '\t'; + next(); + break; + case 'n': + m_buffer << '\n'; + next(); + break; + case '\n': + m_buffer << '\n'; + next(); + break; + default: + if (!isdigit(m_curr_char)) { + save_and_next(); /* handles \\, \", \', and \? */ + } + else { /* \xxx */ + int c = 0; + int i = 0; + do { + c = 10*c + (m_curr_char-'0'); + next(); + } + while (++i<3 && isdigit(m_curr_char)); + if (c > UCHAR_MAX) { + return ITK_BAD_STRING; + } + m_buffer << static_cast(c); + } + } + } + else { + save_and_next(); + } + } + next(); + return ITK_STRING; + } + + itoken next_token() { + for(;;) { + if (eos()) { + return ITK_EOS; + } + + m_buffer.reset(); + switch (m_curr_char) { + case ';': // comment + while (m_curr_char != '\n' && !eos()) { + next(); + } + break; + case '\n': + next(); + m_line++; + break; + case '=': + m_tok_pos = m_pos; + next(); + return ITK_EQ; + case '\'': + case '\"': + return read_string(); + case '{': + m_tok_pos = m_pos; + next(); + return ITK_LCB; + case '}': + m_tok_pos = m_pos; + next(); + return ITK_RCB; + case '(': + m_tok_pos = m_pos; + next(); + return ITK_LP; + case ')': + m_tok_pos = m_pos; + next(); + return ITK_RP; + case ',': + m_tok_pos = m_pos; + next(); + return ITK_COMMA; + case ':': + m_tok_pos = m_pos; + next(); + return ITK_CLN; + default: + if (isspace(m_curr_char)) { + next(); + break; + } + else if (isdigit(m_curr_char)) { + m_tok_pos = m_pos; + save_and_next(); + return read_num(); + } + else { + char old = m_curr_char; + m_tok_pos = m_pos; + save_and_next(); + TRACE("ini_lexer", tout << "old: " << static_cast(old) << " " << old << "\n";); + if (old == '-' && isdigit(m_curr_char)) { + return read_num(); + } + else if (old == '_' || isalpha(old)) { + return read_id(); + } + else { + return ITK_BAD_ID; + } + } + } + } + } + + char const * get_token_data() const { + return m_buffer.c_str(); + } + + unsigned get_token_pos() const { + return m_tok_pos; + } +}; + + +enum ini_param_kind { + IPK_BOOL, + IPK_INT, + IPK_UNSIGNED, + IPK_DOUBLE, + IPK_PERCENTAGE, + IPK_STRING, + IPK_SYMBOL, + IPK_SYMBOL_LIST, + IPK_SYMBOL_NAT_LIST +}; + + +struct ini_param_info { + ini_param_kind m_kind; + bool m_is_mutable; + + char const * m_description; + union { + struct { + int m_int_min; + int m_int_max; + int * m_int_val; + }; + struct { + unsigned m_uint_min; + unsigned m_uint_max; + unsigned * m_uint_val; + }; + bool * m_bool_val; + double * m_double_val; + double * m_perc_val; + symbol * m_sym_val; + std::string * m_str_val; + svector * m_sym_list_val; + svector * m_sym_nat_list_val; + }; + + ini_param_info(char const * descr = 0): + m_kind(IPK_BOOL), + m_is_mutable(false), + m_description(descr), + m_bool_val(0) { + } + + ini_param_info(int min, int max, int * val, char const * descr, bool is_mutable): + m_kind(IPK_INT), + m_is_mutable(is_mutable), + m_description(descr), + m_int_min(min), + m_int_max(max), + m_int_val(val) { + } + + ini_param_info(unsigned min, unsigned max, unsigned * val, char const * descr, bool is_mutable): + m_kind(IPK_UNSIGNED), + m_is_mutable(is_mutable), + m_description(descr), + m_uint_min(min), + m_uint_max(max), + m_uint_val(val) { + } + + ini_param_info(bool * val, char const * descr, bool is_mutable): + m_kind(IPK_BOOL), + m_is_mutable(is_mutable), + m_description(descr), + m_bool_val(val) { + } + + ini_param_info(bool perc, double * val, char const * descr, bool is_mutable): + m_kind(perc ? IPK_PERCENTAGE : IPK_DOUBLE), + m_is_mutable(is_mutable), + m_description(descr), + m_perc_val(val) { + } + + ini_param_info(svector * val, char const * descr, bool is_mutable): + m_kind(IPK_SYMBOL_LIST), + m_is_mutable(is_mutable), + m_description(descr), + m_sym_list_val(val) { + } + + ini_param_info(svector * val, char const * descr, bool is_mutable): + m_kind(IPK_SYMBOL_NAT_LIST), + m_is_mutable(is_mutable), + m_description(descr), + m_sym_nat_list_val(val) { + } + + ini_param_info(symbol * s, char const * descr, bool is_mutable): + m_kind(IPK_SYMBOL), + m_is_mutable(is_mutable), + m_description(descr), + m_sym_val(s) { + } + + ini_param_info(std::string * str, char const * descr, bool is_mutable): + m_kind(IPK_STRING), + m_is_mutable(is_mutable), + m_description(descr), + m_str_val(str) { + } + +}; + +struct ini_params_imp { + bool m_abort_on_error; + ini_reserved_symbols m_ini_reserved; + ref m_param_vector; + symbol_table m_param_info; + bool m_is_frozen; + + ini_params_imp(bool abort_on_error): m_abort_on_error(abort_on_error), m_is_frozen(false) {} + + void freeze(bool f) { m_is_frozen = f; } + + void register_param_vector(param_vector * pv) { + m_param_vector = pv; + } + + void register_bool_param(symbol param_name, bool & value, char const * descr, bool is_mutable) { + SASSERT(!m_param_info.contains(param_name)); + m_param_info.insert(param_name, ini_param_info(&value, descr, is_mutable)); + } + + void register_unsigned_param(symbol param_name, unsigned min, unsigned max, unsigned & value, char const * descr, bool is_mutable) { + SASSERT(!m_param_info.contains(param_name)); + m_param_info.insert(param_name, ini_param_info(min, max, &value, descr, is_mutable)); + } + + void register_int_param(symbol param_name, int min, int max, int & value, char const * descr, bool is_mutable) { + SASSERT(!m_param_info.contains(param_name)); + m_param_info.insert(param_name, ini_param_info(min, max, &value, descr, is_mutable)); + } + + void register_percentage_param(symbol param_name, double & value, char const * descr, bool is_mutable) { + SASSERT(!m_param_info.contains(param_name)); + m_param_info.insert(param_name, ini_param_info(true, &value, descr, is_mutable)); + } + + void register_double_param(symbol param_name, double & value, char const * descr, bool is_mutable) { + SASSERT(!m_param_info.contains(param_name)); + m_param_info.insert(param_name, ini_param_info(false, &value, descr, is_mutable)); + } + + void register_string_param(symbol param_name, std::string & value, char const * descr, bool is_mutable) { + SASSERT(!m_param_info.contains(param_name)); + m_param_info.insert(param_name, ini_param_info(&value, descr, is_mutable)); + } + + void register_symbol_param(symbol param_name, symbol & value, char const * descr, bool is_mutable) { + SASSERT(!m_param_info.contains(param_name)); + m_param_info.insert(param_name, ini_param_info(&value, descr, is_mutable)); + } + + void register_symbol_list_param(symbol param_name, svector & value, char const * descr, bool is_mutable) { + SASSERT(!m_param_info.contains(param_name)); + m_param_info.insert(param_name, ini_param_info(&value, descr, is_mutable)); + } + + void register_symbol_nat_list_param(symbol param_name, svector & value, char const * descr, bool is_mutable) { + SASSERT(!m_param_info.contains(param_name)); + m_param_info.insert(param_name, ini_param_info(&value, descr, is_mutable)); + } + + struct symbol_lt_proc { + bool operator()(const symbol & s1, const symbol & s2) const { + return strcmp(s1.bare_str(), s2.bare_str()) < 0; + } + }; + + void display_param_help(char const* param_id, std::ostream& out) const { + ini_param_info info; + if (m_param_info.find(symbol(param_id), info)) { + display_param_info(out, info); + } + else { + out << "option " << param_id << " does not exist"; + } + } + + void display_param_info(std::ostream& out, ini_param_info& info) const { + switch (info.m_kind) { + case IPK_BOOL: + out << " boolean"; + out << ", default: " << (*info.m_bool_val ? "true" : "false"); + break; + case IPK_INT: + out << " integer"; + if (info.m_int_min > INT_MIN) { + out << ", min: " << info.m_int_min; + } + if (info.m_int_max < INT_MAX) { + out << ", max: " << info.m_int_max; + } + out << ", default: " << *info.m_int_val; + break; + case IPK_UNSIGNED: + out << " unsigned integer"; + if (info.m_uint_min > 0) { + out << ", min: " << info.m_uint_min; + } + if (info.m_uint_max < INT_MAX) { + out << ", max: " << info.m_uint_max; + } + out << ", default: " << *info.m_uint_val; + break; + case IPK_DOUBLE: + out << " double"; + out << ", default: " << *info.m_double_val; + break; + case IPK_PERCENTAGE: + out << " percentage"; + out << ", default: " << *info.m_perc_val; + break; + case IPK_SYMBOL: + out << " symbol"; + out << ", default: " << *info.m_sym_val; + break; + case IPK_STRING: + out << " string"; + out << ", default: " << *info.m_str_val; + break; + case IPK_SYMBOL_LIST: + out << " list of symbols (strings)"; + break; + case IPK_SYMBOL_NAT_LIST: + out << " list of pairs: symbols(strings) x unsigned"; + break; + } + if (info.m_description) { + out << ", " << info.m_description << "."; + } + out << "\n"; + } + + void display_params(std::ostream & out) const { + svector params; + m_param_info.get_dom(params); + std::sort(params.begin(), params.end(), symbol_lt_proc()); + svector::iterator it = params.begin(); + svector::iterator end = params.end(); + for (; it != end; ++it) { + out << *it << ":"; + ini_param_info info; + m_param_info.find(*it, info); + display_param_info(out, info); + } + } + + void display_params_documentation(std::ostream & out) const { + out << "// AUTOMATICALLY GENERATED FILE - DO NOT EDIT\n\n" + << "/**\n" + << " \\page config INI parameters\n\n"; + svector params; + m_param_info.get_dom(params); + std::sort(params.begin(), params.end(), symbol_lt_proc()); + svector::iterator it = params.begin(); + svector::iterator end = params.end(); + for (; it != end; ++it) { + out << "- \\c " << *it << ":"; + ini_param_info info; + m_param_info.find(*it, info); + switch (info.m_kind) { + case IPK_BOOL: + out << " \\em boolean"; + out << ", default: " << (*info.m_bool_val ? "\\c true" : "\\c false"); + break; + case IPK_INT: + out << " \\em integer"; + if (info.m_int_min > INT_MIN) { + out << ", min: \\c " << info.m_int_min; + } + if (info.m_int_max < INT_MAX) { + out << ", max: \\c " << info.m_int_max; + } + out << ", default: \\c " << *info.m_int_val; + break; + case IPK_UNSIGNED: + out << " \\em unsigned \\em integer"; + if (info.m_uint_min > 0) { + out << ", min: \\c " << info.m_uint_min; + } + if (info.m_uint_max < INT_MAX) { + out << ", max: \\c " << info.m_uint_max; + } + out << ", default: \\c " << *info.m_uint_val; + break; + case IPK_DOUBLE: + out << " \\em double"; + out << ", default: \\c " << *info.m_double_val; + break; + case IPK_PERCENTAGE: + out << " \\em percentage"; + out << ", default: \\c " << *info.m_perc_val; + break; + case IPK_STRING: + out << " \\em string"; + out << ", default: " << *info.m_str_val; + break; + case IPK_SYMBOL: + out << " \\em symbol"; + out << ", default: " << *info.m_sym_val; + break; + case IPK_SYMBOL_LIST: + out << " \\em list \\em of \\em symbols \\em (strings)"; + break; + case IPK_SYMBOL_NAT_LIST: + out << " \\em list \\em of \\em pairs: \\em symbols(strings) \\em x \\em unsigned"; + break; + } + if (info.m_description) { + out << ", " << info.m_description << "."; + } + out << "\n\n"; + } + out << "*/\n"; + } + + void error(char const * param_id, char const * msg) { + if (m_abort_on_error) { + verbose_stream() << "Error setting '" << param_id << "', reason: " << msg << "\n"; + throw z3_error(ERR_INI_FILE); + } + else { + throw set_get_param_exception(param_id, msg); + } + } + + symbol trim(char const * param_id) { + string_buffer<> m_buffer; + while (*param_id != 0 && !isspace(*param_id)) { + m_buffer.append(*param_id); + param_id++; + } + return symbol(m_buffer.c_str()); + } + + void set_param_value(char const * param_id, char const * param_value) { + TRACE("param_value", tout << param_id << " " << param_value << "\n";); + if (param_value == 0) { + error(param_id, "invalid (null) option"); + } + symbol s(param_id); + ini_param_info info; + if (!m_param_info.find(s, info)) { + s = trim(param_id); + if (!m_param_info.find(s, info)) + error(param_id, "unknown option."); + } + if (!info.m_is_mutable && m_is_frozen) { + error(param_id, "option value cannot be modified after initialization"); + } + switch (info.m_kind) { + case IPK_BOOL: + parse_bool_param(param_id, info, param_value); + break; + case IPK_INT: + parse_int_param(param_id, info, param_value); + break; + case IPK_UNSIGNED: + parse_uint_param(param_id, info, param_value); + break; + case IPK_DOUBLE: + parse_double_param(param_id, info, param_value); + break; + case IPK_PERCENTAGE: + parse_perc_param(param_id, info, param_value); + break; + case IPK_STRING: + parse_string_param(param_id, info, param_value); + break; + case IPK_SYMBOL: + // TODO: not used so far + *info.m_sym_val = param_value; + break; + case IPK_SYMBOL_LIST: + error(param_id, "this option can only be set in an INI file"); + break; + case IPK_SYMBOL_NAT_LIST: + error(param_id, "this option can only be set in an INI file"); + break; + default: + UNREACHABLE(); + } + } + + + bool get_param_value(char const * param_id, std::string& param_value) { + TRACE("param_value", tout << param_id << "\n";); + std::ostringstream buffer; + symbol s(param_id); + ini_param_info info; + if (!m_param_info.find(s, info)) { + s = trim(param_id); + if (!m_param_info.find(s, info)) { + return false; + } + } + switch (info.m_kind) { + case IPK_BOOL: + buffer << ((*info.m_bool_val)?"true":"false"); + break; + case IPK_INT: + buffer << (*info.m_int_val); + break; + case IPK_UNSIGNED: + buffer << (*info.m_uint_val); + break; + case IPK_DOUBLE: + buffer << (*info.m_double_val); + break; + case IPK_PERCENTAGE: + buffer << (*info.m_perc_val); + break; + case IPK_STRING: + buffer << (*info.m_str_val); + break; + case IPK_SYMBOL: + buffer << (info.m_sym_val->str()); + break; + case IPK_SYMBOL_LIST: + error(param_id, "this option cannot be retrieved"); + break; + case IPK_SYMBOL_NAT_LIST: + error(param_id, "this option cannot be retrieved"); + break; + default: + UNREACHABLE(); + } + param_value = buffer.str(); + return true; + } + + string_buffer<> m_buffer; + + char const * get_token_data() const { + return m_buffer.c_str(); + } + + void save_and_next(char const * & in) { + m_buffer.append(*in); + ++in; + } + + itoken read_num(char const * & in) { + while (isdigit(*in)) { + save_and_next(in); + } + TRACE("read_num", tout << "1. read_num: " << m_buffer.c_str() << ", *in: " << *in << "\n";); + if (*in == '.') { + save_and_next(in); + while (isdigit(*in)) { + save_and_next(in); + } + TRACE("read_num", tout << "2. read_num: " << m_buffer.c_str() << ", *in: " << *in << "\n";); + if (*in == 'e' || *in == 'E' || *in == 'd' || *in == 'D') { + save_and_next(in); + if (*in == '-') { + save_and_next(in); + } + while (isdigit(*in)) { + save_and_next(in); + } + } + return ITK_DOUBLE; + } + return ITK_NUM; + } + + itoken read_id(char const * & in) { + while (!*in == 0 && (isalpha(*in) || isdigit(*in) || *in == '_')) { + save_and_next(in); + } + return string2itoken(m_ini_reserved, m_buffer.c_str()); + } + + itoken read_string(char const * & in) { + ++in; + while (*in != '"' && *in != '\'') { + TRACE("read_string", tout << *in << "\n";); + if (*in == 0) + return ITK_BAD_STRING; + if (*in == '\n') + return ITK_BAD_STRING; + if (*in == '\\') { + ++in; + switch (*in) { + case 't': + m_buffer << '\t'; + ++in; + break; + case 'n': + m_buffer << '\n'; + ++in; + break; + case '\n': + m_buffer << '\n'; + ++in; + break; + default: + if (!isdigit(*in)) { + save_and_next(in); /* handles \\, \", \', and \? */ + } + else { /* \xxx */ + int c = 0; + int i = 0; + do { + c = 10*c + (*in-'0'); + ++in; + } + while (++i<3 && isdigit(*in)); + if (c > UCHAR_MAX) + return ITK_BAD_STRING; + m_buffer << static_cast(c); + } + } + } + else { + save_and_next(in); + } + } + ++in; + return ITK_STRING; + } + + itoken next_token(char const * & in) { + for(;;) { + if (*in == 0) + return ITK_EOS; + m_buffer.reset(); + switch (*in) { + case '{': + in++; + return ITK_LCB; + case '}': + in++; + return ITK_RCB; + case ',': + in++; + return ITK_COMMA; + case '"': + case '\'': + TRACE("read_string", tout << "found \"\n";); + return read_string(in); + default: + if (isspace(*in)) { + in++; + break; + } + else if (isdigit(*in)) { + TRACE("read_num", tout << "found is_digit\n";); + return read_num(in); + } + else { + char old = *in; + save_and_next(in); + if (old == '-' && isdigit(*in)) { + return read_num(in); + } + else if (old == '_' || isalpha(old)) { + return read_id(in); + } + else { + return ITK_BAD_ID; + } + } + } + } + } + + bool end_of_list(char const * param_id, char const * & param_value) { + switch (next_token(param_value)) { + case ITK_COMMA: + return false; + case ITK_RCB: + return true; + default: + error(param_id, "boolean value (true/false) expected"); + } + return false; + } + + void parse_bool_param(char const * param_id, ini_param_info & info, char const * param_value) { + switch (next_token(param_value)) { + case ITK_TRUE: + *info.m_bool_val = true; + break; + case ITK_FALSE: + *info.m_bool_val = false; + break; + default: + error(param_id, "boolean value (true/false) expected"); + } + if (m_param_vector.get() && next_token(param_value) == ITK_LCB) { + for (;;) { + switch (next_token(param_value)) { + case ITK_TRUE: + m_param_vector->m_imp->insert_bool_param(info.m_bool_val, true); + break; + case ITK_FALSE: + m_param_vector->m_imp->insert_bool_param(info.m_bool_val, false); + break; + default: + error(param_id, "boolean value (true/false) expected"); + } + if (end_of_list(param_id, param_value)) + return; + } + } + } + + void parse_int_param(char const * param_id, ini_param_info & info, char const * param_value) { + if (next_token(param_value) != ITK_NUM) + error(param_id, "integer expected"); + int val = strtol(get_token_data(), 0, 10); + if (val < info.m_int_min || val > info.m_int_max) + error(param_id, "integer out of bounds"); + *info.m_int_val = val; + if (m_param_vector.get() && next_token(param_value) == ITK_LCB) { + for (;;) { + if (next_token(param_value) != ITK_NUM) + error(param_id, "integer expected"); + int val = strtol(get_token_data(), 0, 10); + if (val < info.m_int_min || val > info.m_int_max) + error(param_id, "integer out of bounds"); + m_param_vector->m_imp->insert_int_param(info.m_int_val, val); + if (end_of_list(param_id, param_value)) + return; + } + } + } + + void parse_uint_param(char const * param_id, ini_param_info & info, char const * param_value) { + if (next_token(param_value) != ITK_NUM) + error(param_id, "integer expected"); + unsigned val = static_cast(strtol(get_token_data(), 0, 10)); + + if (val < info.m_uint_min || val > info.m_uint_max) + error(param_id, "unsigned out of bounds"); + *info.m_uint_val = val; + if (m_param_vector.get() && next_token(param_value) == ITK_LCB) { + for (;;) { + if (next_token(param_value) != ITK_NUM) + error(param_id, "integer expected"); + unsigned val = static_cast(strtol(get_token_data(), 0, 10)); + if (val < info.m_uint_min || val > info.m_uint_max) + error(param_id, "unsigned out of bounds"); + m_param_vector->m_imp->insert_unsigned_param(info.m_uint_val, val); + if (end_of_list(param_id, param_value)) + return; + } + } + } + + void parse_double_param(char const * param_id, ini_param_info & info, char const * param_value) { + itoken k = next_token(param_value); + if (k != ITK_NUM && k != ITK_DOUBLE) + error(param_id, "float expected"); + char * aux; + *info.m_double_val = strtod(get_token_data(), &aux); + if (m_param_vector.get() && next_token(param_value) == ITK_LCB) { + for (;;) { + k = next_token(param_value); + if (k != ITK_NUM && k != ITK_DOUBLE) + error(param_id, "float expected"); + m_param_vector->m_imp->insert_double_param(info.m_double_val, strtod(get_token_data(), &aux)); + if (end_of_list(param_id, param_value)) + return; + } + } + } + + void parse_perc_param(char const * param_id, ini_param_info & info, char const * param_value) { + if (next_token(param_value) != ITK_NUM) + error(param_id, "integer expected"); + int val = strtol(get_token_data(), 0, 10); + if (val < 0 || val > 100) + error(param_id, "integer between 0 and 100 expected"); + *info.m_perc_val = static_cast(val)/100.0; + if (m_param_vector.get() && next_token(param_value) == ITK_LCB) { + for (;;) { + if (next_token(param_value) != ITK_NUM) + error(param_id, "integer expected"); + int val = strtol(get_token_data(), 0, 10); + if (val < 0 || val > 100) + error(param_id, "integer between 0 and 100 expected"); + m_param_vector->m_imp->insert_double_param(info.m_perc_val, static_cast(val)/100.0); + if (end_of_list(param_id, param_value)) + return; + } + } + } + + void parse_string_param(char const * param_id, ini_param_info & info, char const * param_value) { + if (next_token(param_value) != ITK_STRING) + error(param_id, "string expected"); + *info.m_str_val = get_token_data(); + if (m_param_vector.get() && next_token(param_value) == ITK_LCB) { + for (;;) { + if (next_token(param_value) != ITK_STRING) + error(param_id, "string expected"); + m_param_vector->m_imp->insert_string_param(info.m_str_val, get_token_data()); + if (end_of_list(param_id, param_value)) + return; + } + } + } +}; + +class ini_parser { + ini_lexer m_lexer; + ini_params_imp * m_params; + itoken m_curr_token; + + void error(unsigned pos, char const * msg) { + if (m_params->m_abort_on_error) { + verbose_stream() << "Error INI file [position: " << pos << "]: " << msg << "\n"; + throw z3_error(ERR_INI_FILE); + } + else { + throw ini_parser_exception(pos, msg); + } + } + + void error(char const * msg) { + error(m_lexer.get_token_pos(), msg); + } + + itoken get_curr_token() { + if (m_curr_token == ITK_NULL) { + m_curr_token = m_lexer.next_token(); + } + SASSERT(m_curr_token != ITK_NULL); + return m_curr_token; + } + + void next() { + if (m_curr_token == ITK_NULL) { + m_lexer.next_token(); + } + else { + m_curr_token = ITK_NULL; + } + } + + char const * get_token_data() { + if (m_curr_token == ITK_NULL) { + get_curr_token(); + } + SASSERT(m_curr_token != ITK_NULL); + return m_lexer.get_token_data(); + } + + bool test_next(itoken expected) { + if (get_curr_token() == expected) { + next(); + return true; + } + else { + return false; + } + } + + void check(itoken expected) { + if (!test_next(expected)) { + string_buffer<> msg; + msg << "unexpected token '" << itoken2string(get_curr_token()) + << "', '" << itoken2string(expected) << "' expected."; + error(msg.c_str()); + } + } + +public: + ini_parser(std::istream & in, ini_params_imp * p): + m_lexer(in), + m_params(p), + m_curr_token(ITK_NULL) { + } + + void parse_param() { + symbol s(get_token_data()); + check(ITK_ID); + ini_param_info info; + if (!m_params->m_param_info.find(s, info)) { + error("unknown option."); + } + check(ITK_EQ); + switch (info.m_kind) { + case IPK_BOOL: + parse_bool_param(info); + break; + case IPK_INT: + parse_int_param(info); + break; + case IPK_UNSIGNED: + parse_uint_param(info); + break; + case IPK_DOUBLE: + parse_double_param(info); + break; + case IPK_PERCENTAGE: + parse_perc_param(info); + break; + case IPK_STRING: + parse_string_param(info); + break; + case IPK_SYMBOL: + // TODO: add support for VAL{VAL,...,VAL} + *info.m_sym_val = get_token_data(); + check(ITK_STRING); + break; + case IPK_SYMBOL_NAT_LIST: + parse_symbol_nat_list(info); + break; + case IPK_SYMBOL_LIST: + parse_symbol_list(info); + break; + default: + UNREACHABLE(); + } + } + + bool end_of_list() { + switch (get_curr_token()) { + case ITK_COMMA: + next(); + return false; + case ITK_RCB: + next(); + return true; + default: + error("boolean value expected"); + } + return false; + } + + void parse_bool_param(ini_param_info & info) { + if (get_curr_token() != ITK_TRUE && get_curr_token() != ITK_FALSE) + error("boolean value expected"); + *info.m_bool_val = get_curr_token() == ITK_TRUE; + next(); + if (m_params->m_param_vector.get() && get_curr_token() == ITK_LCB) { + next(); + for (;;) { + if (get_curr_token() != ITK_TRUE && get_curr_token() != ITK_FALSE) + error("boolean value expected"); + m_params->m_param_vector->m_imp->insert_bool_param(info.m_bool_val, get_curr_token() == ITK_TRUE); + next(); + if (end_of_list()) + return; + } + } + } + + void parse_int_param(ini_param_info & info) { + if (get_curr_token() != ITK_NUM) + error("integer expected"); + int val = strtol(get_token_data(), 0, 10); + if (val < info.m_int_min || val > info.m_int_max) + error("integer out of bounds"); + *info.m_int_val = val; + next(); + TRACE("int_param", tout << "val: " << val << "\n";); + if (m_params->m_param_vector.get() && get_curr_token() == ITK_LCB) { + next(); + for (;;) { + if (get_curr_token() != ITK_NUM) + error("integer expected"); + int val = strtol(get_token_data(), 0, 10); + if (val < info.m_int_min || val > info.m_int_max) + error("integer out of bounds"); + m_params->m_param_vector->m_imp->insert_int_param(info.m_int_val, val); + next(); + if (end_of_list()) + return; + } + } + } + + void parse_uint_param(ini_param_info & info) { + if (get_curr_token() != ITK_NUM) + error("integer expected"); + long val = strtol(get_token_data(), 0, 10); + if (val < static_cast(info.m_uint_min) || val > static_cast(info.m_uint_max)) + error("integer out of bounds"); + *info.m_uint_val = val; + next(); + if (m_params->m_param_vector.get() && get_curr_token() == ITK_LCB) { + next(); + for (;;) { + if (get_curr_token() != ITK_NUM) + error("integer expected"); + long val = strtol(get_token_data(), 0, 10); + if (val < static_cast(info.m_uint_min) || val > static_cast(info.m_uint_max)) + error("integer out of bounds"); + m_params->m_param_vector->m_imp->insert_unsigned_param(info.m_uint_val, val); + next(); + if (end_of_list()) + return; + } + } + } + + void parse_double_param(ini_param_info & info) { + if (get_curr_token() != ITK_NUM && get_curr_token() != ITK_DOUBLE) + error("float expected"); + char * aux; + *info.m_double_val = strtod(get_token_data(), &aux); + next(); + if (m_params->m_param_vector.get() && get_curr_token() == ITK_LCB) { + next(); + for (;;) { + if (get_curr_token() != ITK_NUM && get_curr_token() != ITK_DOUBLE) + error("float expected"); + m_params->m_param_vector->m_imp->insert_double_param(info.m_double_val, strtod(get_token_data(), &aux)); + next(); + if (end_of_list()) + return; + } + } + } + + void parse_perc_param(ini_param_info & info) { + if (get_curr_token() != ITK_NUM) + error("integer expected"); + int val = strtol(get_token_data(), 0, 10); + if (val < 0 || val > 100) + error("integer between 0 and 100 expected"); + *info.m_perc_val = static_cast(val)/100.0; + next(); + if (m_params->m_param_vector.get() && get_curr_token() == ITK_LCB) { + next(); + for (;;) { + if (get_curr_token() != ITK_NUM) + error("integer expected"); + int val = strtol(get_token_data(), 0, 10); + if (val < 0 || val > 100) + error("integer between 0 and 100 expected"); + m_params->m_param_vector->m_imp->insert_double_param(info.m_perc_val, static_cast(val)/100.0); + next(); + if (end_of_list()) + return; + } + } + } + + void parse_string_param(ini_param_info & info) { + if (get_curr_token() != ITK_STRING) + error("string expected"); + *info.m_str_val = get_token_data(); + next(); + if (m_params->m_param_vector.get() && get_curr_token() == ITK_LCB) { + next(); + for (;;) { + if (get_curr_token() != ITK_STRING) + error("string expected"); + m_params->m_param_vector->m_imp->insert_string_param(info.m_str_val, get_token_data()); + next(); + if (end_of_list()) + return; + } + } + } + + void parse_s_list(svector & result) { + check(ITK_LP); + for (;;) { + symbol s(get_token_data()); + result.push_back(s); + check(ITK_ID); + if (!test_next(ITK_COMMA)) { + check(ITK_RP); + return; + } + } + } + + void parse_symbol_list(ini_param_info & info) { + parse_s_list(*(info.m_sym_list_val)); + if (m_params->m_param_vector.get() && test_next(ITK_LCB)) { + for (;;) { + svector lst; + parse_s_list(lst); + m_params->m_param_vector->m_imp->insert_symbol_list_param(info.m_sym_list_val, lst); + if (!test_next(ITK_COMMA)) { + check(ITK_RCB); + return; + } + } + } + } + + void parse_sn_list(svector & result) { + check(ITK_LP); + for (;;) { + symbol s(get_token_data()); + check(ITK_ID); + check(ITK_CLN); + unsigned val = strtol(get_token_data(), 0, 10); + check(ITK_NUM); + result.push_back(std::make_pair(s, val)); + if (!test_next(ITK_COMMA)) { + check(ITK_RP); + return; + } + } + } + + void parse_symbol_nat_list(ini_param_info & info) { + parse_sn_list(*(info.m_sym_nat_list_val)); + if (m_params->m_param_vector.get() && test_next(ITK_LCB)) { + for (;;) { + svector lst; + parse_sn_list(lst); + m_params->m_param_vector->m_imp->insert_symbol_nat_list_param(info.m_sym_nat_list_val, lst); + if (!test_next(ITK_COMMA)) { + check(ITK_RCB); + return; + } + } + } + } + + void parse() { + while (get_curr_token() != ITK_EOS) { + parse_param(); + } + } +}; + +ini_params::ini_params(bool abort_on_error) { + m_imp = alloc(ini_params_imp, abort_on_error); +} + +ini_params::~ini_params() { + dealloc(m_imp); +} + +void ini_params::register_bool_param(char const * param_name, bool & value, char const * descr, bool is_mutable) { + m_imp->register_bool_param(symbol(param_name), value, descr, is_mutable); +} + +void ini_params::register_unsigned_param(char const * param_name, unsigned min, unsigned max, unsigned & value, char const * descr, bool is_mutable) { + m_imp->register_unsigned_param(symbol(param_name), min, max, value, descr, is_mutable); +} + +void ini_params::register_int_param(char const * param_name, int min, int max, int & value, char const * descr, bool is_mutable) { + m_imp->register_int_param(symbol(param_name), min, max, value, descr, is_mutable); +} + +void ini_params::register_double_param(char const * param_name, double & value, char const * descr, bool is_mutable) { + m_imp->register_double_param(symbol(param_name), value, descr, is_mutable); +} + +void ini_params::register_percentage_param(char const * param_name, double & value, char const * descr, bool is_mutable) { + m_imp->register_percentage_param(symbol(param_name), value, descr, is_mutable); +} + +void ini_params::register_string_param(char const * param_name, std::string & value, char const * descr, bool is_mutable) { + m_imp->register_string_param(symbol(param_name), value, descr, is_mutable); +} + +void ini_params::register_symbol_param(char const * param_name, symbol & value, char const * descr, bool is_mutable) { + m_imp->register_symbol_param(symbol(param_name), value, descr, is_mutable); +} + +void ini_params::register_symbol_list_param(char const * param_name, svector & value, char const * descr, bool is_mutable) { + m_imp->register_symbol_list_param(symbol(param_name), value, descr, is_mutable); +} + +void ini_params::register_symbol_nat_list_param(char const * param_name, svector & value, char const * descr, bool is_mutable) { + m_imp->register_symbol_nat_list_param(symbol(param_name), value, descr, is_mutable); +} + +void ini_params::register_param_vector(param_vector * pv) { + m_imp->register_param_vector(pv); +} + +void ini_params::read_ini_file(std::istream & in) { + ini_parser p(in, m_imp); + p.parse(); +} + +void ini_params::display_params(std::ostream & out) const { + m_imp->display_params(out); +} + +void ini_params::display_parameter_help(char const* param_id, std::ostream & out) const { + m_imp->display_param_help(param_id, out); +} + +void ini_params::display_params_documentation(std::ostream & out) const { + m_imp->display_params_documentation(out); +} + +void ini_params::set_param_value(char const * param_id, char const * param_value) { + m_imp->set_param_value(param_id, param_value); +} +bool ini_params::get_param_value(char const * param_id, std::string& param_value) { + return m_imp->get_param_value(param_id, param_value); +} + +void ini_params::freeze(bool f) { + m_imp->freeze(f); +} + + + diff --git a/lib/ini_file.h b/lib/ini_file.h new file mode 100644 index 000000000..cb884b916 --- /dev/null +++ b/lib/ini_file.h @@ -0,0 +1,117 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + ini_file.h + +Abstract: + + Configuration file support. + +Author: + + Leonardo de Moura (leonardo) 2007-05-10. + +Revision History: + +--*/ +#ifndef _INI_FILE_H_ +#define _INI_FILE_H_ + +#include +#include +#include + +#include"symbol.h" +#include"vector.h" +#include"ref.h" + +#ifdef _EXTERNAL_RELEASE +#define PRIVATE_PARAMS(CODE) ((void) 0) +#else +#define PRIVATE_PARAMS(CODE) { CODE } +#endif + +struct param_vector_imp; +struct ini_params_imp; +class ini_parser; + +typedef std::pair symbol_nat_pair; + +inline std::ostream & operator<<(std::ostream & out, symbol_nat_pair const & p) { + out << p.first << ":" << p.second; + return out; +} + +/** + \brief Support for multiple values for a parameter. The values are used to configure + different cores. +*/ +class param_vector { + unsigned m_ref_count; + param_vector_imp * m_imp; + friend struct ini_params_imp; + friend class ini_parser; +public: + // TODO: onwer is a big hack + param_vector(void * owner); + ~param_vector(); + void inc_ref(); + void dec_ref(); + void copy_params(void * curr_owner, unsigned idx); + unsigned size(void) const; +}; + +class set_get_param_exception { + std::string m_id; + std::string m_msg; +public: + set_get_param_exception(char const * id, char const * msg):m_id(id), m_msg(msg) {} + char const * get_param_id() const { return m_id.c_str(); } + char const * get_msg() const { return m_msg.c_str(); } +}; + +class ini_parser_exception { + unsigned m_pos; + std::string m_msg; +public: + ini_parser_exception(unsigned pos, char const * msg):m_pos(pos), m_msg(msg) {} + unsigned get_pos() const { return m_pos; } + char const * get_msg() const { return m_msg.c_str(); } +}; + +class ini_params { + ini_params_imp * m_imp; +public: + ini_params(bool abort_on_error = true); + ~ini_params(); + void freeze(bool f = true); + void register_bool_param(char const * param_name, bool & value, char const * descr = 0, bool is_mutable = false); + void register_unsigned_param(char const * param_name, unsigned min, unsigned max, unsigned & value, char const * descr = 0, bool is_mutable = false); + void register_unsigned_param(char const * param_name, unsigned & value, char const * descr = 0, bool is_mutable = false) { + register_unsigned_param(param_name, 0, INT_MAX, value, descr, is_mutable); + } + void register_int_param(char const * param_name, int min, int max, int & value, char const * descr = 0, bool is_mutable = false); + void register_int_param(char const * param_name, int & value, char const * descr = 0, bool is_mutable = false) { + register_int_param(param_name, INT_MIN, INT_MAX, value, descr, is_mutable); + } + void register_double_param(char const * param_name, double & value, char const * descr = 0,bool is_mutable = false); + void register_percentage_param(char const * param_name, double & value, char const * descr = 0, bool is_mutable = false); + void register_string_param(char const * param_name, std::string & value, char const * descr = 0, bool is_mutable = false); + void register_symbol_param(char const * param_name, symbol & value, char const * descr = 0, bool is_mutable = false); + void register_symbol_list_param(char const * param_name, svector & value, char const * descr = 0, bool is_mutable = false); + void register_symbol_nat_list_param(char const * param_name, svector & value, char const * descr = 0, bool is_mutable = false); + void register_param_vector(param_vector * pv); + + void read_ini_file(std::istream & in); + void display_params(std::ostream & out) const; + void display_parameter_help(char const* param_id, std::ostream & out) const; + void set_param_value(char const * param_id, char const * param_value); + void set_param_mutable(char const * param_id); + bool get_param_value(char const * param_id, std::string& param_value); + void display_params_documentation(std::ostream & out) const; +}; + +#endif /* _INI_FILE_H_ */ + diff --git a/lib/inj_axiom.cpp b/lib/inj_axiom.cpp new file mode 100644 index 000000000..50d051d2e --- /dev/null +++ b/lib/inj_axiom.cpp @@ -0,0 +1,142 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + inj_axiom.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-23. + +Revision History: + +--*/ +#include"inj_axiom.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"has_free_vars.h" +#include"well_sorted.h" + +/** + \brief Little HACK for simplifying injectivity axioms + + \remark It is not covering all possible cases. +*/ +bool simplify_inj_axiom(ast_manager & m, quantifier * q, expr_ref & result) { + expr * n = q->get_expr(); + if (q->is_forall() && m.is_or(n) && to_app(n)->get_num_args() == 2) { + expr * arg1 = to_app(n)->get_arg(0); + expr * arg2 = to_app(n)->get_arg(1); + if (m.is_not(arg2)) + std::swap(arg1, arg2); + if (m.is_not(arg1) && + m.is_eq(to_app(arg1)->get_arg(0)) && + m.is_eq(arg2)) { + expr * app1 = to_app(to_app(arg1)->get_arg(0))->get_arg(0); + expr * app2 = to_app(to_app(arg1)->get_arg(0))->get_arg(1); + expr * var1 = to_app(arg2)->get_arg(0); + expr * var2 = to_app(arg2)->get_arg(1); + if (is_app(app1) && + is_app(app2) && + to_app(app1)->get_decl() == to_app(app2)->get_decl() && + to_app(app1)->get_num_args() == to_app(app2)->get_num_args() && + to_app(app1)->get_family_id() == null_family_id && + to_app(app1)->get_num_args() > 0 && + is_var(var1) && + is_var(var2) && + var1 != var2) { + app * f1 = to_app(app1); + app * f2 = to_app(app2); + bool found_vars = false; + unsigned num = f1->get_num_args(); + unsigned idx = UINT_MAX; + unsigned num_vars = 1; + for (unsigned i = 0; i < num; i++) { + expr * c1 = f1->get_arg(i); + expr * c2 = f2->get_arg(i); + if (!is_var(c1) && !is_uninterp_const(c1)) + return false; + if ((c1 == var1 && c2 == var2) || (c1 == var2 && c2 == var1)) { + if (found_vars) + return false; + found_vars = true; + idx = i; + } + else if (c1 == c2 && c1 != var1 && c1 != var2) { + if (is_var(c1)) { + ++num_vars; + } + } + else { + return false; + } + } + if (found_vars && !has_free_vars(q)) { + TRACE("inj_axiom", + tout << "Cadidate for simplification:\n" << mk_ll_pp(q, m) << mk_pp(app1, m) << "\n" << mk_pp(app2, m) << "\n" << + mk_pp(var1, m) << "\n" << mk_pp(var2, m) << "\nnum_vars: " << num_vars << "\n";); + // Building new (optimized) axiom + func_decl * decl = f1->get_decl(); + unsigned var_idx = 0; + ptr_buffer f_args, inv_vars; + ptr_buffer decls; + buffer names; + + expr * var = 0; + for (unsigned i = 0; i < num; i++) { + expr * c = f1->get_arg(i); + if (is_var(c)) { + names.push_back(symbol(i)); + sort * s = decl->get_domain(i); + decls.push_back(s); + expr * new_c = m.mk_var(var_idx, s); + var_idx++; + f_args.push_back(new_c); + if (i == idx) { + var = new_c; + } + else { + inv_vars.push_back(new_c); + } + } + else { + SASSERT(is_uninterp_const(c)); + f_args.push_back(c); + } + } + SASSERT(var != 0); + app * f = m.mk_app(decl, f_args.size(), f_args.c_ptr()); + + ptr_vector domain; + inv_vars.push_back(f); + for (unsigned i = 0; i < inv_vars.size(); ++i) { + domain.push_back(m.get_sort(inv_vars[i])); + } + sort * d = decl->get_domain(idx); + func_decl * inv_decl = m.mk_fresh_func_decl("inj", domain.size(), domain.c_ptr(), d); + + expr * proj = m.mk_app(inv_decl, inv_vars.size(), inv_vars.c_ptr()); + expr * eq = m.mk_eq(proj, var); + expr * p = m.mk_pattern(f); + + // decls are in the wrong order... + // Remark: the sort of the var 0 must be in the last position. + std::reverse(decls.begin(), decls.end()); + + result = m.mk_forall(decls.size(), decls.c_ptr(), names.c_ptr(), eq, + 0, symbol(), symbol(), 1, &p); + TRACE("inj_axiom", tout << "new axiom:\n" << mk_pp(result, m) << "\n";); + SASSERT(is_well_sorted(m, result)); + return true; + } + } + } + } + return false; +} + diff --git a/lib/inj_axiom.h b/lib/inj_axiom.h new file mode 100644 index 000000000..f473d5eae --- /dev/null +++ b/lib/inj_axiom.h @@ -0,0 +1,27 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + inj_axiom.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-23. + +Revision History: + +--*/ +#ifndef _INJ_AXIOM_H_ +#define _INJ_AXIOM_H_ + +#include"ast.h" + +bool simplify_inj_axiom(ast_manager & m, quantifier * q, expr_ref & result); + +#endif /* _INJ_AXIOM_H_ */ + diff --git a/lib/install_tactics.cpp b/lib/install_tactics.cpp new file mode 100644 index 000000000..629e25389 --- /dev/null +++ b/lib/install_tactics.cpp @@ -0,0 +1,234 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + install_tactics.cpp + +Abstract: + Install tactics in the SMT 2.0 frontend. + +Author: + + Leonardo (leonardo) 2012-02-19 + +Notes: + +--*/ +#include"tactic_cmds.h" +#include"cmd_context.h" +#include"simplify_tactic.h" +#include"split_clause_tactic.h" +#include"normalize_bounds_tactic.h" +#include"elim_uncnstr_tactic.h" +#include"add_bounds_tactic.h" +#include"ctx_simplify_tactic.h" +#include"ctx_solver_simplify_tactic.h" + +#include"bit_blaster_tactic.h" +#include"bv1_blaster_tactic.h" +#include"der.h" +#include"aig_tactic.h" +#include"smt_tactic.h" +#include"sat_tactic.h" +#include"occf_tactic.h" +#include"qe_tactic.h" +#include"qe_sat_tactic.h" +#include"propagate_values_tactic.h" +#include"nnf_tactic.h" +#include"solve_eqs_tactic.h" +#include"max_bv_sharing_tactic.h" +#include"elim_term_ite_tactic.h" +#include"fix_dl_var_tactic.h" +#include"tseitin_cnf_tactic.h" +#include"degree_shift_tactic.h" +#include"purify_arith_tactic.h" +#include"nla2bv_tactic.h" +#include"nlsat_tactic.h" +#include"factor_tactic.h" +#include"fm_tactic.h" +#include"diff_neq_tactic.h" +#include"lia2pb_tactic.h" +#include"fpa2bv_tactic.h" +#include"qffpa_tactic.h" +#include"pb2bv_tactic.h" +#include"recover_01_tactic.h" +#include"symmetry_reduce_tactic.h" +#include"distribute_forall_tactic.h" +#include"reduce_args_tactic.h" +#include"bv_size_reduction_tactic.h" +#include"propagate_ineqs_tactic.h" +#include"cofactor_term_ite_tactic.h" +#include"mip_tactic.h" +#include"vsubst_tactic.h" +#include"sls_tactic.h" +#include"probe_arith.h" +#include"qflia_tactic.h" +#include"qflra_tactic.h" +#include"qfnia_tactic.h" +#include"qfnra_tactic.h" +#include"qfbv_tactic.h" +#include"subpaving_tactic.h" +#include"unit_subsumption_tactic.h" + +MK_SIMPLE_TACTIC_FACTORY(simplifier_fct, mk_simplify_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(split_clause_fct, mk_split_clause_tactic(p)); +MK_SIMPLE_TACTIC_FACTORY(normalize_bounds_fct, mk_normalize_bounds_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(elim_uncnstr_fct, mk_elim_uncnstr_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(add_bounds_fct, mk_add_bounds_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(bitblaster_fct, mk_bit_blaster_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(bv1blaster_fct, mk_bv1_blaster_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(aig_fct, mk_aig_tactic(p)); +MK_SIMPLE_TACTIC_FACTORY(skip_fct, mk_skip_tactic()); +MK_SIMPLE_TACTIC_FACTORY(fail_fct, mk_fail_tactic()); +MK_SIMPLE_TACTIC_FACTORY(smt_fct, mk_smt_tactic(p)); +MK_SIMPLE_TACTIC_FACTORY(sat_fct, mk_sat_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(sat_preprocess_fct, mk_sat_preprocessor_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(ctx_simplify_fct, mk_ctx_simplify_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(ctx_solver_simplify_fct, mk_ctx_solver_simplify_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(unit_subsume_fct, mk_unit_subsumption_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(der_fct, mk_der_tactic(m)); +MK_SIMPLE_TACTIC_FACTORY(occf_fct, mk_occf_tactic(m)); +MK_SIMPLE_TACTIC_FACTORY(qe_fct, mk_qe_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(qe_sat_fct, qe::mk_sat_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(propagate_values_fct, mk_propagate_values_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(snf_fct, mk_snf_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(nnf_fct, mk_nnf_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(solve_eqs_fct, mk_solve_eqs_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(max_bv_sharing_fct, mk_max_bv_sharing_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(elim_term_ite_fct, mk_elim_term_ite_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(fix_dl_var_fct, mk_fix_dl_var_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(tseitin_cnf_fct, mk_tseitin_cnf_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(tseitin_cnf_core_fct, mk_tseitin_cnf_core_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(degree_shift_fct, mk_degree_shift_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(purify_arith_fct, mk_purify_arith_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(nlsat_fct, mk_nlsat_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(factor_fct, mk_factor_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(fm_fct, mk_fm_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(fail_if_undecided_fct, mk_fail_if_undecided_tactic()); +MK_SIMPLE_TACTIC_FACTORY(diff_neq_fct, mk_diff_neq_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(lia2pb_fct, mk_lia2pb_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(fpa2bv_fct, mk_fpa2bv_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(qffpa_fct, mk_qffpa_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(pb2bv_fct, mk_pb2bv_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(recover_01_fct, mk_recover_01_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(elim_and_fct, mk_elim_and_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(symmetry_reduce_fct, mk_symmetry_reduce_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(distribute_forall_fct, mk_distribute_forall_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(reduce_args_fct, mk_reduce_args_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(bv_size_reduction_fct, mk_bv_size_reduction_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(propagate_ineqs_fct, mk_propagate_ineqs_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(cofactor_term_ite_fct, mk_cofactor_term_ite_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(mip_fct, mk_mip_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(nla2bv_fct, mk_nla2bv_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(vsubst_fct, mk_vsubst_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(qfbv_sls_fct, mk_qfbv_sls_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(subpaving_fct, mk_subpaving_tactic(m, p)); + +MK_SIMPLE_TACTIC_FACTORY(qflia_fct, mk_qflia_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(qflra_fct, mk_qflra_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(qfbv_fct, mk_qfbv_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(qfnia_fct, mk_qfnia_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(qfnra_fct, mk_qfnra_tactic(m, p)); + +#define ADD_TACTIC_CMD(NAME, DESCR, FACTORY) ctx.insert(alloc(tactic_cmd, symbol(NAME), DESCR, alloc(FACTORY))) +#define ADD_PROBE(NAME, DESCR, PROBE) ctx.insert(alloc(probe_info, symbol(NAME), DESCR, PROBE)) + +void install_tactics(tactic_manager & ctx) { + ADD_TACTIC_CMD("simplify", "apply simplification rules.", simplifier_fct); + ADD_TACTIC_CMD("split-clause", "split a clause in many subgoals.", split_clause_fct); + ADD_TACTIC_CMD("normalize-bounds", "replace a variable x with lower bound k <= x with x' = x - k.", normalize_bounds_fct); + ADD_TACTIC_CMD("elim-uncnstr", "eliminate application containing unconstrained variables.", elim_uncnstr_fct); + ADD_TACTIC_CMD("elim-and", "convert (and a b) into (not (or (not a) (not b))).", elim_and_fct); + ADD_TACTIC_CMD("add-bounds", "add bounds to unbounded variables (under approximation).", add_bounds_fct); + ADD_TACTIC_CMD("aig", "simplify Boolean structure using AIGs.", aig_fct); + ADD_TACTIC_CMD("skip", "do nothing tactic.", skip_fct); + ADD_TACTIC_CMD("fail", "always fail tactic.", fail_fct); + ADD_TACTIC_CMD("smt", "apply a SAT based SMT solver.", smt_fct); + ADD_TACTIC_CMD("bit-blast", "reduce bit-vector expressions into SAT.", bitblaster_fct); + ADD_TACTIC_CMD("bv1-blast", "reduce bit-vector expressions into bit-vectors of size 1 (notes: only equality, extract and concat are supported).", bv1blaster_fct); + ADD_TACTIC_CMD("sat", "(try to) solve goal using a SAT solver.", sat_fct); + ADD_TACTIC_CMD("sat-preprocess", "Apply SAT solver preprocessing procedures (bounded resolution, Boolean constant propagation, 2-SAT, subsumption, subsumption resolution).", sat_preprocess_fct); + ADD_TACTIC_CMD("ctx-simplify", "apply contextual simplification rules.", ctx_simplify_fct); + ADD_TACTIC_CMD("ctx-solver-simplify", "apply solver-based contextual simplification rules.", ctx_solver_simplify_fct); + ADD_TACTIC_CMD("der", "destructive equality resolution.", der_fct); + ADD_TACTIC_CMD("unit-subsume-simplify", "unit subsumption simplification.", unit_subsume_fct); + ADD_TACTIC_CMD("occf", "put goal in one constraint per clause normal form (notes: fails if proof generation is enabled; only clauses are considered).", occf_fct); + ADD_TACTIC_CMD("qe", "apply quantifier elimination.", qe_fct); + ADD_TACTIC_CMD("qe-sat", "check satisfiability of quantified formulas using quantifier elimination.", qe_sat_fct); + ADD_TACTIC_CMD("propagate-values", "propagate constants.", propagate_values_fct); + ADD_TACTIC_CMD("snf", "put goal in skolem normal form.", snf_fct); + ADD_TACTIC_CMD("nnf", "put goal in negation normal form.", nnf_fct); + ADD_TACTIC_CMD("solve-eqs", "eliminate variables by solving equations.", solve_eqs_fct); + ADD_TACTIC_CMD("max-bv-sharing", "use heuristics to maximize the sharing of bit-vector expressions such as adders and multipliers.", max_bv_sharing_fct); + ADD_TACTIC_CMD("elim-term-ite", "eliminate term if-then-else by adding fresh auxiliary declarations.", elim_term_ite_fct); + ADD_TACTIC_CMD("fix-dl-var", "if goal is in the difference logic fragment, then fix the variable with the most number of occurrences at 0.", + fix_dl_var_fct); + ADD_TACTIC_CMD("tseitin-cnf", "convert goal into CNF using tseitin-like encoding (note: quantifiers are ignored).", tseitin_cnf_fct); + ADD_TACTIC_CMD("tseitin-cnf-core", "convert goal into CNF using tseitin-like encoding (note: quantifiers are ignored). This tactic does not apply required simplifications to the input goal like the tseitin-cnf tactic.", tseitin_cnf_core_fct); + ADD_TACTIC_CMD("degree-shift", "try to reduce degree of polynomials (remark: :mul2power simplification is automatically applied).", degree_shift_fct); + ADD_TACTIC_CMD("purify-arith", "eliminate unnecessary operators: -, /, div, mod, rem, is-int, to-int, ^, root-objects.", purify_arith_fct); + ADD_TACTIC_CMD("nlsat", "(try to) solve goal using a nonlinear arithmetic solver.", nlsat_fct); + ADD_TACTIC_CMD("factor", "polynomial factorization.", factor_fct); + ADD_TACTIC_CMD("fm", "eliminate variables using fourier-motzkin elimination.", fm_fct); + ADD_TACTIC_CMD("fail-if-undecided", "fail if goal is undecided.", fail_if_undecided_fct); + ADD_TACTIC_CMD("diff-neq", "specialized solver for integer arithmetic problems that contain only atoms of the form (<= k x) (<= x k) and (not (= (- x y) k)), where x and y are constants and k is a numberal, and all constants are bounded.", diff_neq_fct); + ADD_TACTIC_CMD("lia2pb", "convert bounded integer variables into a sequence of 0-1 variables.", lia2pb_fct); + ADD_TACTIC_CMD("fpa2bv", "convert floating point numbers to bit-vectors.", fpa2bv_fct); + ADD_TACTIC_CMD("qffpa", "(try to) solve goal using the tactic for QF_FPA.", qffpa_fct); + ADD_TACTIC_CMD("pb2bv", "convert pseudo-boolean constraints to bit-vectors.", pb2bv_fct); + ADD_TACTIC_CMD("recover-01", "recover 0-1 variables hidden as Boolean variables.", recover_01_fct); + ADD_TACTIC_CMD("symmetry-reduce", "apply symmetry reduction.", symmetry_reduce_fct); + ADD_TACTIC_CMD("distribute-forall", "distribute forall over conjunctions.", distribute_forall_fct); + ADD_TACTIC_CMD("reduce-args", "reduce the number of arguments of function applications, when for all occurrences of a function f the i-th is a value.", + reduce_args_fct); + ADD_TACTIC_CMD("reduce-bv-size", "try to reduce bit-vector sizes using inequalities.", bv_size_reduction_fct); + ADD_TACTIC_CMD("propagate-ineqs", "propagate ineqs/bounds, remove subsumed inequalities.", propagate_ineqs_fct); + ADD_TACTIC_CMD("cofactor-term-ite", "eliminate term if-the-else using cofactors.", cofactor_term_ite_fct); + ADD_TACTIC_CMD("mip", "solver for mixed integer programming problems.", mip_fct); + ADD_TACTIC_CMD("nla2bv", "convert a nonlinear arithmetic problem into a bit-vector problem, in most cases the resultant goal is an under approximation and is useul for finding models.", nla2bv_fct); + ADD_TACTIC_CMD("vsubst", "checks satsifiability of quantifier-free non-linear constraints using virtual substitution.", vsubst_fct); + ADD_TACTIC_CMD("qfbv-sls", "(try to) solve using stochastic local search for QF_BV.", qfbv_sls_fct); + ADD_TACTIC_CMD("qflia", "builtin strategy for solving QF_LIA problems.", qflia_fct); + ADD_TACTIC_CMD("qflra", "builtin strategy for solving QF_LRA problems.", qflra_fct); + ADD_TACTIC_CMD("qfnia", "builtin strategy for solving QF_NIA problems.", qfnia_fct); + ADD_TACTIC_CMD("qfnra", "builtin strategy for solving QF_NRA problems.", qfnra_fct); + ADD_TACTIC_CMD("qfbv", "builtin strategy for solving QF_BV problems.", qfbv_fct); +#ifndef _EXTERNAL_RELEASE + ADD_TACTIC_CMD("subpaving", "tactic for testing subpaving module.", subpaving_fct); +#endif + + ADD_PROBE("memory", "ammount of used memory in megabytes.", mk_memory_probe()); + ADD_PROBE("depth", "depth of the input goal.", mk_depth_probe()); + ADD_PROBE("size", "number of assertions in the given goal.", mk_size_probe()); + ADD_PROBE("num-exprs", "number of expressions/terms in the given goal.", mk_num_exprs_probe()); + ADD_PROBE("num-consts", "number of non Boolean constants in the given goal.", mk_num_consts_probe()); + ADD_PROBE("num-bool-consts", "number of Boolean constants in the given goal.", mk_num_bool_consts_probe()); + ADD_PROBE("num-arith-consts", "number of arithmetic constants in the given goal.", mk_num_arith_consts_probe()); + ADD_PROBE("num-bv-consts", "number of bit-vector constants in the given goal.", mk_num_bv_consts_probe()); + ADD_PROBE("produce-proofs", "true if proof generation is enabled for the given goal.", mk_produce_proofs_probe()); + ADD_PROBE("produce-model", "true if model generation is enabled for the given goal.", mk_produce_models_probe()); + ADD_PROBE("produce-unsat-cores", "true if unsat-core generation is enabled for the given goal.", mk_produce_unsat_cores_probe()); + ADD_PROBE("has-patterns", "true if the goal contains quantifiers with patterns.", mk_has_pattern_probe()); + ADD_PROBE("is-propositional", "true if the goal is in propositional logic.", mk_is_propositional_probe()); + ADD_PROBE("is-qfbv", "true if the goal is in QF_BV.", mk_is_qfbv_probe()); + ADD_PROBE("is-qfbv-eq", "true if the goal is in a fragment of QF_BV which uses only =, extract, concat.", mk_is_qfbv_eq_probe()); + ADD_PROBE("is-qflia", "true if the goal is in QF_LIA.", mk_is_qflia_probe()); + ADD_PROBE("is-qflra", "true if the goal is in QF_LRA.", mk_is_qflra_probe()); + ADD_PROBE("is-qflira", "true if the goal is in QF_LIRA.", mk_is_qflira_probe()); + ADD_PROBE("is-ilp", "true if the goal is ILP.", mk_is_ilp_probe()); + ADD_PROBE("is-mip", "true if the goal is MIP.", mk_is_mip_probe()); + ADD_PROBE("is-unbounded", "true if the goal contains integer/real constants that do not have lower/upper bounds.", mk_is_unbounded_probe()); + ADD_PROBE("is-pb", "true if the goal is a pseudo-boolean problem.", mk_is_pb_probe()); + ADD_PROBE("arith-max-deg", "max polynomial total degree of an arithmetic atom.", mk_arith_max_degree_probe()); + ADD_PROBE("arith-avg-deg", "avg polynomial total degree of an arithmetic atom.", mk_arith_avg_degree_probe()); + ADD_PROBE("arith-max-bw", "max coefficient bit width.", mk_arith_max_bw_probe()); + ADD_PROBE("arith-avg-bw", "avg coefficient bit width.", mk_arith_avg_bw_probe()); + + ADD_PROBE("is-qfnia", "true if the goal is in QF_NIA (quantifier-free nonlinear integer arithmetic).", mk_is_qfnia_probe()); + ADD_PROBE("is-qfnra", "true if the goal is in QF_NRA (quantifier-free nonlinear real arithmetic).", mk_is_qfnra_probe()); + ADD_PROBE("is-nia", "true if the goal is in NIA (nonlinear integer arithmetic, formula may have quantifiers).", mk_is_nia_probe()); + ADD_PROBE("is-nra", "true if the goal is in NRA (nonlinear real arithmetic, formula may have quantifiers).", mk_is_nra_probe()); +} + + diff --git a/lib/install_tactics.h b/lib/install_tactics.h new file mode 100644 index 000000000..da0c829f1 --- /dev/null +++ b/lib/install_tactics.h @@ -0,0 +1,24 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + install_tactics.h + +Abstract: + Install tactics in the SMT 2.0 frontend. + +Author: + + Leonardo (leonardo) 2012-02-19 + +Notes: + +--*/ +#ifndef _INSTALL_TACTICS_H_ +#define _INSTALL_TACTICS_H_ + +class tactic_manager; +void install_tactics(tactic_manager & ctx); + +#endif diff --git a/lib/instruction_count.cpp b/lib/instruction_count.cpp new file mode 100644 index 000000000..ff9387f79 --- /dev/null +++ b/lib/instruction_count.cpp @@ -0,0 +1,41 @@ +#ifdef _WINDOWS +#include "windows.h" +#endif +#include "instruction_count.h" + +#ifdef _WINDOWS +typedef BOOL (WINAPI *QTCP)(HANDLE, PULONG64); +static QTCP QTCP_proc; +BOOL WINAPI dummy_qtcp(HANDLE h, PULONG64 u) +{ + *u = 0; + return 0; +} + +inline void check_handler() +{ + if (!QTCP_proc) { + QTCP_proc = (QTCP) GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "QueryThreadCycleTime"); + if (!QTCP_proc) + QTCP_proc = &dummy_qtcp; + } +} +#endif + +void instruction_count::start() { + m_count = 0; +#ifdef _WINDOWS + check_handler(); + QTCP_proc(GetCurrentThread(), &m_count); +#endif +} + +double instruction_count::get_num_instructions() { + unsigned long long current = 0; +#ifdef _WINDOWS + check_handler(); + QTCP_proc(GetCurrentThread(), ¤t); +#endif + return static_cast(current - m_count); +} + diff --git a/lib/instruction_count.h b/lib/instruction_count.h new file mode 100644 index 000000000..29fa0c45b --- /dev/null +++ b/lib/instruction_count.h @@ -0,0 +1,43 @@ +/*++ +Copyright (c) 2009 Microsoft Corporation + +Module Name: + + instruction_count.h + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2009-03-04. + +Revision History: + +--*/ +#ifndef _INSTRUCTION_COUNT_H_ +#define _INSTRUCTION_COUNT_H_ + + +/** + \brief Wrapper for an instruction counter. +*/ +class instruction_count { + unsigned long long m_count; +public: + instruction_count() : m_count(0) {} + + ~instruction_count() {} + + void start(); + + double get_num_instructions(); + + bool is_instruction_maxed(double max_instr) { + return max_instr > 0.0 && get_num_instructions() > max_instr; + } +}; + +#endif /* _INSTRUcTION_COUNT_H_ */ + diff --git a/lib/interval.h b/lib/interval.h new file mode 100644 index 000000000..33f202e06 --- /dev/null +++ b/lib/interval.h @@ -0,0 +1,353 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + interval.h + +Abstract: + + Goodies/Templates for interval arithmetic + +Author: + + Leonardo de Moura (leonardo) 2012-07-19. + +Revision History: + +--*/ +#ifndef _INTERVAL_H_ +#define _INTERVAL_H_ + +#include"mpq.h" +#include"ext_numeral.h" + +/** + \brief Default configuration for interval manager. + It is used for documenting the required interface. +*/ +class im_default_config { + unsynch_mpq_manager & m_manager; +public: + typedef unsynch_mpq_manager numeral_manager; + typedef mpq numeral; + + // Every configuration object must provide an interval type. + // The actual fields are irrelevant, the interval manager + // accesses interval data using the following API. + struct interval { + numeral m_lower; + numeral m_upper; + unsigned m_lower_open:1; + unsigned m_upper_open:1; + unsigned m_lower_inf:1; + unsigned m_upper_inf:1; + }; + + // Should be NOOPs for precise numeral types. + // For imprecise types (e.g., floats) it should set the rounding mode. + void round_to_minus_inf() {} + void round_to_plus_inf() {} + void set_rounding(bool to_plus_inf) {} + + // Getters + numeral const & lower(interval const & a) const { return a.m_lower; } + numeral const & upper(interval const & a) const { return a.m_upper; } + numeral & lower(interval & a) { return a.m_lower; } + numeral & upper(interval & a) { return a.m_upper; } + bool lower_is_open(interval const & a) const { return a.m_lower_open; } + bool upper_is_open(interval const & a) const { return a.m_upper_open; } + bool lower_is_inf(interval const & a) const { return a.m_lower_inf; } + bool upper_is_inf(interval const & a) const { return a.m_upper_inf; } + + // Setters + void set_lower(interval & a, numeral const & n) { m_manager.set(a.m_lower, n); } + void set_upper(interval & a, numeral const & n) { m_manager.set(a.m_upper, n); } + void set_lower_is_open(interval & a, bool v) { a.m_lower_open = v; } + void set_upper_is_open(interval & a, bool v) { a.m_upper_open = v; } + void set_lower_is_inf(interval & a, bool v) { a.m_lower_inf = v; } + void set_upper_is_inf(interval & a, bool v) { a.m_upper_inf = v; } + + // Reference to numeral manager + numeral_manager & m() const { return m_manager; } + + im_default_config(numeral_manager & m):m_manager(m) {} +}; + +#define DEP_IN_LOWER1 1 +#define DEP_IN_UPPER1 2 +#define DEP_IN_LOWER2 4 +#define DEP_IN_UPPER2 8 + +typedef short bound_deps; +inline bool dep_in_lower1(bound_deps d) { return (d & DEP_IN_LOWER1) != 0; } +inline bool dep_in_lower2(bound_deps d) { return (d & DEP_IN_LOWER2) != 0; } +inline bool dep_in_upper1(bound_deps d) { return (d & DEP_IN_UPPER1) != 0; } +inline bool dep_in_upper2(bound_deps d) { return (d & DEP_IN_UPPER2) != 0; } +inline bound_deps dep1_to_dep2(bound_deps d) { + SASSERT(!dep_in_lower2(d) && !dep_in_upper2(d)); + bound_deps r = d << 2; + SASSERT(dep_in_lower1(d) == dep_in_lower2(r)); + SASSERT(dep_in_upper1(d) == dep_in_upper2(r)); + SASSERT(!dep_in_lower1(r) && !dep_in_upper1(r)); + return r; +} + +/** + \brief Interval dependencies for unary and binary operations on intervals. + It contains the dependencies for the output lower and upper bounds + for the resultant interval. +*/ +struct interval_deps { + bound_deps m_lower_deps; + bound_deps m_upper_deps; +}; + +template +class interval_manager { + typedef typename C::numeral_manager numeral_manager; + typedef typename numeral_manager::numeral numeral; + typedef typename C::interval interval; + + C m_c; + numeral m_result_lower; + numeral m_result_upper; + numeral m_mul_ad; + numeral m_mul_bc; + numeral m_mul_ac; + numeral m_mul_bd; + numeral m_one; + numeral m_minus_one; + numeral m_inv_k; + + unsigned m_pi_n; + interval m_pi_div_2; + interval m_pi; + interval m_3_pi_div_2; + interval m_2_pi; + + volatile bool m_cancel; + + void round_to_minus_inf() { m_c.round_to_minus_inf(); } + void round_to_plus_inf() { m_c.round_to_plus_inf(); } + void set_rounding(bool to_plus_inf) { m_c.set_rounding(to_plus_inf); } + + ext_numeral_kind lower_kind(interval const & a) const { return m_c.lower_is_inf(a) ? EN_MINUS_INFINITY : EN_NUMERAL; } + ext_numeral_kind upper_kind(interval const & a) const { return m_c.upper_is_inf(a) ? EN_PLUS_INFINITY : EN_NUMERAL; } + + void set_lower(interval & a, numeral const & n) { m_c.set_lower(a, n); } + void set_upper(interval & a, numeral const & n) { m_c.set_upper(a, n); } + void set_lower_is_open(interval & a, bool v) { m_c.set_lower_is_open(a, v); } + void set_upper_is_open(interval & a, bool v) { m_c.set_upper_is_open(a, v); } + void set_lower_is_inf(interval & a, bool v) { m_c.set_lower_is_inf(a, v); } + void set_upper_is_inf(interval & a, bool v) { m_c.set_upper_is_inf(a, v); } + + void nth_root_slow(numeral const & a, unsigned n, numeral const & p, numeral & lo, numeral & hi); + void A_div_x_n(numeral const & A, numeral const & x, unsigned n, bool to_plus_inf, numeral & r); + void rough_approx_nth_root(numeral const & a, unsigned n, numeral & o); + void approx_nth_root(numeral const & a, unsigned n, numeral const & p, numeral & o); + void nth_root_pos(numeral const & A, unsigned n, numeral const & p, numeral & lo, numeral & hi); + void nth_root(numeral const & a, unsigned n, numeral const & p, numeral & lo, numeral & hi); + + void pi_series(int x, numeral & r, bool to_plus_inf); + void fact(unsigned n, numeral & o); + void sine_series(numeral const & a, unsigned k, bool upper, numeral & o); + void cosine_series(numeral const & a, unsigned k, bool upper, numeral & o); + void e_series(unsigned k, bool upper, numeral & o); + + void div_mul(numeral const & k, interval const & a, interval & b, bool inv_k); + + void checkpoint(); + +public: + interval_manager(C const & c); + ~interval_manager(); + + void set_cancel(bool f) { m_cancel = f; } + + numeral_manager & m() const { return m_c.m(); } + + void del(interval & a); + + numeral const & lower(interval const & a) const { return m_c.lower(a); } + numeral const & upper(interval const & a) const { return m_c.upper(a); } + numeral & lower(interval & a) { return m_c.lower(a); } + numeral & upper(interval & a) { return m_c.upper(a); } + bool lower_is_open(interval const & a) const { return m_c.lower_is_open(a); } + bool upper_is_open(interval const & a) const { return m_c.upper_is_open(a); } + bool lower_is_inf(interval const & a) const { return m_c.lower_is_inf(a); } + bool upper_is_inf(interval const & a) const { return m_c.upper_is_inf(a); } + + bool lower_is_neg(interval const & a) const { return ::is_neg(m(), lower(a), lower_kind(a)); } + bool lower_is_pos(interval const & a) const { return ::is_pos(m(), lower(a), lower_kind(a)); } + bool lower_is_zero(interval const & a) const { return ::is_zero(m(), lower(a), lower_kind(a)); } + + bool upper_is_neg(interval const & a) const { return ::is_neg(m(), upper(a), upper_kind(a)); } + bool upper_is_pos(interval const & a) const { return ::is_pos(m(), upper(a), upper_kind(a)); } + bool upper_is_zero(interval const & a) const { return ::is_zero(m(), upper(a), upper_kind(a)); } + + bool is_P(interval const & n) const { return lower_is_pos(n) || lower_is_zero(n); } + bool is_P0(interval const & n) const { return lower_is_zero(n) && !lower_is_open(n); } + bool is_P1(interval const & n) const { return lower_is_pos(n) || (lower_is_zero(n) && lower_is_open(n)); } + bool is_N(interval const & n) const { return upper_is_neg(n) || upper_is_zero(n); } + bool is_N0(interval const & n) const { return upper_is_zero(n) && !upper_is_open(n); } + bool is_N1(interval const & n) const { return upper_is_neg(n) || (upper_is_zero(n) && upper_is_open(n)); } + bool is_M(interval const & n) const { return lower_is_neg(n) && upper_is_pos(n); } + bool is_zero(interval const & n) const { return lower_is_zero(n) && upper_is_zero(n); } + + void set(interval & t, interval const & s); + + bool eq(interval const & a, interval const & b) const; + + /** + \brief Set lower bound to -oo. + */ + void reset_lower(interval & a); + + /** + \brief Set upper bound to +oo. + */ + void reset_upper(interval & a); + + /** + \brief Set interval to (-oo, oo) + */ + void reset(interval & a); + + /** + \brief Return true if the given interval contains 0. + */ + bool contains_zero(interval const & n) const; + + /** + \brief Return true if n contains v. + */ + bool contains(interval const & n, numeral const & v) const; + + void display(std::ostream & out, interval const & n) const; + + bool check_invariant(interval const & n) const; + + /** + \brief b <- -a + */ + void neg(interval const & a, interval & b, interval_deps & b_deps); + void neg(interval const & a, interval & b); + void neg_jst(interval const & a, interval_deps & b_deps); + + /** + \brief c <- a + b + */ + void add(interval const & a, interval const & b, interval & c, interval_deps & c_deps); + void add(interval const & a, interval const & b, interval & c); + void add_jst(interval const & a, interval const & b, interval_deps & c_deps); + + /** + \brief c <- a - b + */ + void sub(interval const & a, interval const & b, interval & c, interval_deps & c_deps); + void sub(interval const & a, interval const & b, interval & c); + void sub_jst(interval const & a, interval const & b, interval_deps & c_deps); + + /** + \brief b <- k * a + */ + void mul(numeral const & k, interval const & a, interval & b, interval_deps & b_deps); + void mul(numeral const & k, interval const & a, interval & b) { div_mul(k, a, b, false); } + void mul_jst(numeral const & k, interval const & a, interval_deps & b_deps); + /** + \brief b <- (n/d) * a + */ + void mul(int n, int d, interval const & a, interval & b); + + /** + \brief b <- a/k + + \remark For imprecise numerals, this is not equivalent to + m().inv(k) + mul(k, a, b) + + That is, we must invert k rounding towards +oo or -oo depending whether we + are computing a lower or upper bound. + */ + void div(interval const & a, numeral const & k, interval & b, interval_deps & b_deps); + void div(interval const & a, numeral const & k, interval & b) { div_mul(k, a, b, true); } + void div_jst(interval const & a, numeral const & k, interval_deps & b_deps) { mul_jst(k, a, b_deps); } + + /** + \brief c <- a * b + */ + void mul(interval const & a, interval const & b, interval & c, interval_deps & c_deps); + void mul(interval const & a, interval const & b, interval & c); + void mul_jst(interval const & a, interval const & b, interval_deps & c_deps); + + /** + \brief b <- a^n + */ + void power(interval const & a, unsigned n, interval & b, interval_deps & b_deps); + void power(interval const & a, unsigned n, interval & b); + void power_jst(interval const & a, unsigned n, interval_deps & b_deps); + + /** + \brief b <- a^(1/n) with precision p. + + \pre if n is even, then a must not contain negative numbers. + */ + void nth_root(interval const & a, unsigned n, numeral const & p, interval & b, interval_deps & b_deps); + void nth_root(interval const & a, unsigned n, numeral const & p, interval & b); + void nth_root_jst(interval const & a, unsigned n, numeral const & p, interval_deps & b_deps); + + /** + \brief Given an equation x^n = y and an interval for y, compute the solution set for x with precision p. + + \pre if n is even, then !lower_is_neg(y) + */ + void xn_eq_y(interval const & y, unsigned n, numeral const & p, interval & x, interval_deps & x_deps); + void xn_eq_y(interval const & y, unsigned n, numeral const & p, interval & x); + void xn_eq_y_jst(interval const & y, unsigned n, numeral const & p, interval_deps & x_deps); + + /** + \brief b <- 1/a + \pre !contains_zero(a) + */ + void inv(interval const & a, interval & b, interval_deps & b_deps); + void inv(interval const & a, interval & b); + void inv_jst(interval const & a, interval_deps & b_deps); + + /** + \brief c <- a/b + \pre !contains_zero(b) + \pre &a == &c (that is, c should not be an alias for a) + */ + void div(interval const & a, interval const & b, interval & c, interval_deps & c_deps); + void div(interval const & a, interval const & b, interval & c); + void div_jst(interval const & a, interval const & b, interval_deps & c_deps); + + /** + \brief Store in r an interval that contains the number pi. + The size of the interval is (1/15)*(1/16^n) + */ + void pi(unsigned n, interval & r); + + /** + \brief Set the precision of the internal interval representing pi. + */ + void set_pi_prec(unsigned n); + + /** + \brief Set the precision of the internal interval representing pi to a precision of at least n. + */ + void set_pi_at_least_prec(unsigned n); + + void sine(numeral const & a, unsigned k, numeral & lo, numeral & hi); + + void cosine(numeral const & a, unsigned k, numeral & lo, numeral & hi); + + /** + \brief Store in r the Euler's constant e. + The size of the interval is 4/(k+1)! + */ + void e(unsigned k, interval & r); +}; + +#endif diff --git a/lib/interval_def.h b/lib/interval_def.h new file mode 100644 index 000000000..c4259fad7 --- /dev/null +++ b/lib/interval_def.h @@ -0,0 +1,1985 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + interval_def.h + +Abstract: + + Goodies/Templates for interval arithmetic + +Author: + + Leonardo de Moura (leonardo) 2012-07-19. + +Revision History: + +--*/ +#ifndef _INTERVAL_DEF_H_ +#define _INTERVAL_DEF_H_ + +#include"interval.h" +#include"debug.h" +#include"trace.h" +#include"scoped_numeral.h" +#include"cooperate.h" + +#define DEFAULT_PI_PRECISION 2 + +// #define TRACE_NTH_ROOT + +template +interval_manager::interval_manager(C const & c):m_c(c) { + m().set(m_minus_one, -1); + m().set(m_one, 1); + m_pi_n = 0; + m_cancel = false; +} + +template +interval_manager::~interval_manager() { + del(m_pi_div_2); + del(m_pi); + del(m_3_pi_div_2); + del(m_2_pi); + m().del(m_result_lower); + m().del(m_result_upper); + m().del(m_mul_ad); + m().del(m_mul_bc); + m().del(m_mul_ac); + m().del(m_mul_bd); + m().del(m_minus_one); + m().del(m_one); + m().del(m_inv_k); +} + +template +void interval_manager::del(interval & a) { + m().del(lower(a)); + m().del(upper(a)); +} + + +template +void interval_manager::checkpoint() { + if (m_cancel) + throw default_exception("canceled"); + cooperate("interval"); +} + +/* + Compute the n-th root of a with precision p. The result hi - lo <= p + lo and hi are lower/upper bounds for the value of the n-th root of a. + That is, the n-th root is in the interval [lo, hi] + + If n is even, then a is assumed to be nonnegative. + + If numeral_manager is not precise, the procedure does not guarantee the precision p. +*/ +template +void interval_manager::nth_root_slow(numeral const & a, unsigned n, numeral const & p, numeral & lo, numeral & hi) { +#ifdef TRACE_NTH_ROOT + static unsigned counter = 0; + static unsigned loop_counter = 0; + counter++; + if (counter % 1000 == 0) + std::cerr << "[nth-root] " << counter << " " << loop_counter << " " << ((double)loop_counter)/((double)counter) << std::endl; +#endif + + bool n_is_even = (n % 2 == 0); + SASSERT(!n_is_even || m().is_nonneg(a)); + if (m().is_zero(a) || m().is_one(a) || (!n_is_even && m().eq(a, m_minus_one))) { + m().set(lo, a); + m().set(hi, a); + return; + } + if (m().lt(a, m_minus_one)) { + m().set(lo, a); + m().set(hi, -1); + } + else if (m().is_neg(a)) { + m().set(lo, -1); + m().set(hi, 0); + } + else if (m().lt(a, m_one)) { + m().set(lo, 0); + m().set(hi, 1); + } + else { + m().set(lo, 1); + m().set(hi, a); + } + SASSERT(m().le(lo, hi)); + _scoped_numeral c(m()), cn(m()); + _scoped_numeral two(m()); + m().set(two, 2); + while (true) { + checkpoint(); +#ifdef TRACE_NTH_ROOT + loop_counter++; +#endif + m().add(hi, lo, c); + m().div(c, two, c); + if (m().precise()) { + m().power(c, n, cn); + if (m().gt(cn, a)) { + m().set(hi, c); + } + else if (m().eq(cn, a)) { + // root is precise + m().set(lo, c); + m().set(hi, c); + return; + } + else { + m().set(lo, c); + } + } + else { + round_to_minus_inf(); + m().power(c, n, cn); + if (m().gt(cn, a)) { + m().set(hi, c); + } + else { + round_to_plus_inf(); + m().power(c, n, cn); + if (m().lt(cn, a)) { + m().set(lo, c); + } + else { + // can't improve, numeral_manager is not precise enough, + // a is between round-to-minus-inf(c^n) and round-to-plus-inf(c^n) + return; + } + } + } + round_to_plus_inf(); + m().sub(hi, lo, c); + if (m().le(c, p)) + return; // result is precise enough + } +} + +/** + \brief Store in o a rough approximation of a^1/n. + + It uses 2^Floor[Floor(Log2(a))/n] + + \pre is_pos(a) +*/ +template +void interval_manager::rough_approx_nth_root(numeral const & a, unsigned n, numeral & o) { + SASSERT(m().is_pos(a)); + SASSERT(n > 0); + round_to_minus_inf(); + unsigned k = m().prev_power_of_two(a); + m().set(o, 2); + m().power(o, k/n, o); +} + +/* + Compute the n-th root of \c a with (suggested) precision p. + The only guarantee provided by this method is that a^(1/n) is in [lo, hi]. + + If n is even, then a is assumed to be nonnegative. +*/ +template +void interval_manager::nth_root(numeral const & a, unsigned n, numeral const & p, numeral & lo, numeral & hi) { + // nth_root_slow(a, n, p, lo, hi); + // return; + SASSERT(n > 0); + SASSERT(n % 2 != 0 || m().is_nonneg(a)); + if (n == 1 || m().is_zero(a) || m().is_one(a) || m().is_minus_one(a)) { + // easy cases: 1, -1, 0 + m().set(lo, a); + m().set(hi, a); + return; + } + bool is_neg = m().is_neg(a); + _scoped_numeral A(m()); + m().set(A, a); + m().abs(A); + + nth_root_pos(A, n, p, lo, hi); + STRACE("nth_root_trace", + tout << "[nth-root] ("; m().display(tout, A); tout << ")^(1/" << n << ") >= "; m().display(tout, lo); tout << "\n"; + tout << "[nth-root] ("; m().display(tout, A); tout << ")^(1/" << n << ") <= "; m().display(tout, hi); tout << "\n";); + if (is_neg) { + m().swap(lo, hi); + m().neg(lo); + m().neg(hi); + } +} + +/** + r <- A/(x^n) + + If to_plus_inf, then r >= A/(x^n) + If not to_plus_inf, then r <= A/(x^n) + +*/ +template +void interval_manager::A_div_x_n(numeral const & A, numeral const & x, unsigned n, bool to_plus_inf, numeral & r) { + if (n == 1) { + if (m().precise()) { + m().div(A, x, r); + } + else { + set_rounding(to_plus_inf); + m().div(A, x, r); + } + } + else { + if (m().precise()) { + m().power(x, n, r); + m().div(A, r, r); + } + else { + set_rounding(!to_plus_inf); + m().power(x, n, r); + set_rounding(to_plus_inf); + m().div(A, r, r); + } + } +} + +/** + \brief Compute an approximation of A^(1/n) using the sequence + + x' = 1/n((n-1)*x + A/(x^(n-1))) + + The computation stops when the difference between current and new x is less than p. + The procedure may not terminate if m() is not precise and p is very small. + +*/ +template +void interval_manager::approx_nth_root(numeral const & A, unsigned n, numeral const & p, numeral & x) { + SASSERT(m().is_pos(A)); + SASSERT(n > 1); +#ifdef TRACE_NTH_ROOT + static unsigned counter = 0; + static unsigned loop_counter = 0; + counter++; + if (counter % 1000 == 0) + std::cerr << "[nth-root] " << counter << " " << loop_counter << " " << ((double)loop_counter)/((double)counter) << std::endl; +#endif + + _scoped_numeral x_prime(m()), d(m()); + + m().set(d, 1); + if (m().lt(A, d)) + m().set(x, A); + else + rough_approx_nth_root(A, n, x); + + round_to_minus_inf(); + + if (n == 2) { + _scoped_numeral two(m()); + m().set(two, 2); + while (true) { + checkpoint(); +#ifdef TRACE_NTH_ROOT + loop_counter++; +#endif + m().div(A, x, x_prime); + m().add(x, x_prime, x_prime); + m().div(x_prime, two, x_prime); + m().sub(x_prime, x, d); + m().abs(d); + m().swap(x, x_prime); + if (m().lt(d, p)) + return; + } + } + else { + _scoped_numeral _n(m()), _n_1(m()); + m().set(_n, n); // _n contains n + m().set(_n_1, n); + m().dec(_n_1); // _n_1 contains n-1 + + while (true) { + checkpoint(); +#ifdef TRACE_NTH_ROOT + loop_counter++; +#endif + m().power(x, n-1, x_prime); + m().div(A, x_prime, x_prime); + m().mul(_n_1, x, d); + m().add(d, x_prime, x_prime); + m().div(x_prime, _n, x_prime); + m().sub(x_prime, x, d); + m().abs(d); + TRACE("nth_root", + tout << "A: "; m().display(tout, A); tout << "\n"; + tout << "x: "; m().display(tout, x); tout << "\n"; + tout << "x_prime: "; m().display(tout, x_prime); tout << "\n"; + tout << "d: "; m().display(tout, d); tout << "\n"; + ); + m().swap(x, x_prime); + if (m().lt(d, p)) + return; + } + } +} + +template +void interval_manager::nth_root_pos(numeral const & A, unsigned n, numeral const & p, numeral & lo, numeral & hi) { + approx_nth_root(A, n, p, hi); + if (m().precise()) { + // Assuming hi has a upper bound for A^(n-1) + // Then, A/(x^(n-1)) must be lower bound + A_div_x_n(A, hi, n-1, false, lo); + // Check if we were wrong + if (m().lt(hi, lo)) { + // swap if wrong + m().swap(lo, hi); + } + } + else { + // Check if hi is really a upper bound for A^(n-1) + A_div_x_n(A, hi, n-1, true /* lo will be greater than the actual lower bound */, lo); + TRACE("nth_root_bug", + tout << "Assuming upper\n"; + tout << "A: "; m().display(tout, A); tout << "\n"; + tout << "hi: "; m().display(tout, hi); tout << "\n"; + tout << "lo: "; m().display(tout, hi); tout << "\n";); + if (m().le(lo, hi)) { + // hi is really the upper bound + // Must compute lo again but approximating to -oo + A_div_x_n(A, hi, n-1, false, lo); + } + else { + // hi should be lower bound + m().swap(lo, hi); + // check if lo is lower bound + A_div_x_n(A, lo, n-1, false /* hi will less than the actual upper bound */, hi); + if (m().le(lo, hi)) { + // lo is really the lower bound + // Must compute hi again but approximating to +oo + A_div_x_n(A, lo, n-1, true, hi); + } + else { + // we don't have anything due to rounding errors + // Be supper conservative + // This should not really happen very often. + _scoped_numeral one(m()); + if (m().lt(A, one)) { + m().set(lo, 0); + m().set(hi, 1); + } + else { + m().set(lo, 1); + m().set(hi, A); + } + } + } + } +} + + +/** + \brief o <- n! +*/ +template +void interval_manager::fact(unsigned n, numeral & o) { + _scoped_numeral aux(m()); + m().set(o, 1); + for (unsigned i = 2; i <= n; i++) { + m().set(aux, static_cast(i)); + m().mul(aux, o, o); + TRACE("fact_bug", tout << "i: " << i << ", o: " << m().to_rational_string(o) << "\n";); + } +} + +template +void interval_manager::sine_series(numeral const & a, unsigned k, bool upper, numeral & o) { + SASSERT(k % 2 == 1); + // Compute sine using taylor series up to k + // x - x^3/3! + x^5/5! - x^7/7! + ... + // The result should be greater than or equal to the actual value if upper == true + // Otherwise it must be less than or equal to the actual value. + // The argument upper only matter if the numeral_manager is not precise. + + // Taylor series up to k with rounding to + _scoped_numeral f(m()); + _scoped_numeral aux(m()); + m().set(o, a); + bool sign = true; + bool upper_factor = !upper; // since the first sign is negative, we must minimize factor to maximize result + for (unsigned i = 3; i <= k; i+=2) { + TRACE("sine_bug", tout << "[begin-loop] o: " << m().to_rational_string(o) << "\ni: " << i << "\n"; + tout << "upper: " << upper << ", upper_factor: " << upper_factor << "\n"; + tout << "o (default): " << m().to_string(o) << "\n";); + set_rounding(upper_factor); + m().power(a, i, f); + TRACE("sine_bug", tout << "a^i " << m().to_rational_string(f) << "\n";); + set_rounding(!upper_factor); + fact(i, aux); + TRACE("sine_bug", tout << "i! " << m().to_rational_string(aux) << "\n";); + set_rounding(upper_factor); + m().div(f, aux, f); + TRACE("sine_bug", tout << "a^i/i! " << m().to_rational_string(f) << "\n";); + set_rounding(upper); + if (sign) + m().sub(o, f, o); + else + m().add(o, f, o); + TRACE("sine_bug", tout << "o: " << m().to_rational_string(o) << "\n";); + sign = !sign; + upper_factor = !upper_factor; + } +} + +template +void interval_manager::sine(numeral const & a, unsigned k, numeral & lo, numeral & hi) { + TRACE("sine", tout << "sine(a), a: " << m().to_rational_string(a) << "\na: " << m().to_string(a) << "\n";); + SASSERT(&lo != &hi); + if (m().is_zero(a)) { + m().reset(lo); + m().reset(hi); + return; + } + + // Compute sine using taylor series + // x - x^3/3! + x^5/5! - x^7/7! + ... + // + // Note that, the coefficient of even terms is 0. + // So, we force k to be odd to make sure the error is minimized. + if (k % 2 == 0) + k++; + + // Taylor series error = |x|^(k+1)/(k+1)! + _scoped_numeral error(m()); + _scoped_numeral aux(m()); + round_to_plus_inf(); + m().set(error, a); + if (m().is_neg(error)) + m().neg(error); + m().power(error, k+1, error); + TRACE("sine", tout << "a^(k+1): " << m().to_rational_string(error) << "\nk : " << k << "\n";); + round_to_minus_inf(); + fact(k+1, aux); + TRACE("sine", tout << "(k+1)!: " << m().to_rational_string(aux) << "\n";); + round_to_plus_inf(); + m().div(error, aux, error); + TRACE("sine", tout << "error: " << m().to_rational_string(error) << "\n";); + + // Taylor series up to k with rounding to -oo + sine_series(a, k, false, lo); + + if (m().precise()) { + m().set(hi, lo); + m().sub(lo, error, lo); + if (m().lt(lo, m_minus_one)) { + m().set(lo, -1); + m().set(hi, 1); + } + else { + m().add(hi, error, hi); + } + } + else { + // We must recompute the series with rounding to +oo + TRACE("sine", tout << "lo before -error: " << m().to_rational_string(lo) << "\n";); + round_to_minus_inf(); + m().sub(lo, error, lo); + TRACE("sine", tout << "lo: " << m().to_rational_string(lo) << "\n";); + if (m().lt(lo, m_minus_one)) { + m().set(lo, -1); + m().set(hi, 1); + return; + } + sine_series(a, k, true, hi); + round_to_plus_inf(); + m().add(hi, error, hi); + TRACE("sine", tout << "hi: " << m().to_rational_string(hi) << "\n";); + } +} + +template +void interval_manager::cosine_series(numeral const & a, unsigned k, bool upper, numeral & o) { + SASSERT(k % 2 == 0); + // Compute cosine using taylor series up to k + // 1 - x^2/2! + x^4/4! - x^6/6! + ... + // The result should be greater than or equal to the actual value if upper == true + // Otherwise it must be less than or equal to the actual value. + // The argument upper only matter if the numeral_manager is not precise. + + + // Taylor series up to k with rounding to -oo + _scoped_numeral f(m()); + _scoped_numeral aux(m()); + m().set(o, 1); + bool sign = true; + bool upper_factor = !upper; // since the first sign is negative, we must minimize factor to maximize result + for (unsigned i = 2; i <= k; i+=2) { + set_rounding(upper_factor); + m().power(a, i, f); + set_rounding(!upper_factor); + fact(i, aux); + set_rounding(upper_factor); + m().div(f, aux, f); + set_rounding(upper); + if (sign) + m().sub(o, f, o); + else + m().add(o, f, o); + sign = !sign; + upper_factor = !upper_factor; + } +} + +template +void interval_manager::cosine(numeral const & a, unsigned k, numeral & lo, numeral & hi) { + TRACE("cosine", tout << "cosine(a): "; m().display_decimal(tout, a, 32); tout << "\n";); + SASSERT(&lo != &hi); + if (m().is_zero(a)) { + m().set(lo, 1); + m().set(hi, 1); + return; + } + + // Compute cosine using taylor series + // 1 - x^2/2! + x^4/4! - x^6/6! + ... + // + // Note that, the coefficient of odd terms is 0. + // So, we force k to be even to make sure the error is minimized. + if (k % 2 == 1) + k++; + + // Taylor series error = |x|^(k+1)/(k+1)! + _scoped_numeral error(m()); + _scoped_numeral aux(m()); + round_to_plus_inf(); + m().set(error, a); + if (m().is_neg(error)) + m().neg(error); + m().power(error, k+1, error); + round_to_minus_inf(); + fact(k+1, aux); + round_to_plus_inf(); + m().div(error, aux, error); + TRACE("sine", tout << "error: "; m().display_decimal(tout, error, 32); tout << "\n";); + + // Taylor series up to k with rounding to -oo + cosine_series(a, k, false, lo); + + if (m().precise()) { + m().set(hi, lo); + m().sub(lo, error, lo); + if (m().lt(lo, m_minus_one)) { + m().set(lo, -1); + m().set(hi, 1); + } + else { + m().add(hi, error, hi); + } + } + else { + // We must recompute the series with rounding to +oo + round_to_minus_inf(); + m().sub(lo, error, lo); + if (m().lt(lo, m_minus_one)) { + m().set(lo, -1); + m().set(hi, 1); + return; + } + cosine_series(a, k, true, hi); + round_to_plus_inf(); + m().add(hi, error, hi); + } +} + +template +void interval_manager::reset_lower(interval & a) { + m().reset(lower(a)); + set_lower_is_open(a, true); + set_lower_is_inf(a, true); +} + +template +void interval_manager::reset_upper(interval & a) { + m().reset(upper(a)); + set_upper_is_open(a, true); + set_upper_is_inf(a, true); +} + +template +void interval_manager::reset(interval & a) { + reset_lower(a); + reset_upper(a); +} + +template +bool interval_manager::contains_zero(interval const & n) const { + return + (lower_is_neg(n) || (lower_is_zero(n) && !lower_is_open(n))) && + (upper_is_pos(n) || (upper_is_zero(n) && !upper_is_open(n))); +} + + +template +bool interval_manager::contains(interval const & n, numeral const & v) const { + if (!lower_is_inf(n)) { + if (m().lt(v, lower(n))) return false; + if (m().eq(v, lower(n)) && lower_is_open(n)) return false; + } + if (!upper_is_inf(n)) { + if (m().gt(v, upper(n))) return false; + if (m().eq(v, upper(n)) && upper_is_open(n)) return false; + } + return true; +} + +template +void interval_manager::display(std::ostream & out, interval const & n) const { + out << (lower_is_open(n) ? "(" : "["); + ::display(out, m(), lower(n), lower_kind(n)); + out << ", "; + ::display(out, m(), upper(n), upper_kind(n)); + out << (upper_is_open(n) ? ")" : "]"); +} + +template +bool interval_manager::check_invariant(interval const & n) const { + if (::eq(m(), lower(n), lower_kind(n), upper(n), upper_kind(n))) { + SASSERT(!lower_is_open(n)); + SASSERT(!upper_is_open(n)); + } + else { + SASSERT(lt(m(), lower(n), lower_kind(n), upper(n), upper_kind(n))); + } + return true; +} + +template +void interval_manager::set(interval & t, interval const & s) { + if (&t == &const_cast(s)) + return; + if (lower_is_inf(s)) { + set_lower_is_inf(t, true); + } + else { + m().set(lower(t), lower(s)); + set_lower_is_inf(t, false); + } + if (upper_is_inf(s)) { + set_upper_is_inf(t, true); + } + else { + m().set(upper(t), upper(s)); + set_upper_is_inf(t, false); + } + set_lower_is_open(t, lower_is_open(s)); + set_upper_is_open(t, upper_is_open(s)); + SASSERT(check_invariant(t)); +} + +template +bool interval_manager::eq(interval const & a, interval const & b) const { + return + ::eq(m(), lower(a), lower_kind(a), lower(b), lower_kind(b)) && + ::eq(m(), upper(a), upper_kind(a), upper(b), upper_kind(b)) && + lower_is_open(a) == lower_is_open(b) && + upper_is_open(a) == upper_is_open(b); +} + +template +void interval_manager::neg_jst(interval const & a, interval_deps & b_deps) { + if (lower_is_inf(a)) { + if (upper_is_inf(a)) { + b_deps.m_lower_deps = 0; + b_deps.m_upper_deps = 0; + } + else { + b_deps.m_lower_deps = DEP_IN_UPPER1; + b_deps.m_upper_deps = 0; + } + } + else { + if (upper_is_inf(a)) { + b_deps.m_lower_deps = 0; + b_deps.m_upper_deps = DEP_IN_LOWER1; + } + else { + b_deps.m_lower_deps = DEP_IN_UPPER1; + b_deps.m_upper_deps = DEP_IN_LOWER1; + } + } +} + +template +void interval_manager::neg(interval const & a, interval & b, interval_deps & b_deps) { + neg_jst(a, b_deps); + neg(a, b); +} + +template +void interval_manager::neg(interval const & a, interval & b) { + if (lower_is_inf(a)) { + if (upper_is_inf(a)) { + reset(b); + } + else { + m().set(lower(b), upper(a)); + m().neg(lower(b)); + set_lower_is_inf(b, false); + set_lower_is_open(b, upper_is_open(a)); + + m().reset(upper(b)); + set_upper_is_inf(b, true); + set_upper_is_open(b, true); + } + } + else { + if (upper_is_inf(a)) { + m().set(upper(b), lower(a)); + m().neg(upper(b)); + set_upper_is_inf(b, false); + set_upper_is_open(b, lower_is_open(a)); + + m().reset(lower(b)); + set_lower_is_inf(b, true); + set_lower_is_open(b, true); + } + else { + if (&a == &b) { + m().swap(lower(b), upper(b)); + } + else { + m().set(lower(b), upper(a)); + m().set(upper(b), lower(a)); + } + m().neg(lower(b)); + m().neg(upper(b)); + set_lower_is_inf(b, false); + set_upper_is_inf(b, false); + bool l_o = lower_is_open(a); + bool u_o = upper_is_open(a); + set_lower_is_open(b, u_o); + set_upper_is_open(b, l_o); + } + } + SASSERT(check_invariant(b)); +} + +template +void interval_manager::add_jst(interval const & a, interval const & b, interval_deps & c_deps) { + c_deps.m_lower_deps = DEP_IN_LOWER1 | DEP_IN_LOWER2; + c_deps.m_upper_deps = DEP_IN_UPPER1 | DEP_IN_UPPER2; +} + +template +void interval_manager::add(interval const & a, interval const & b, interval & c, interval_deps & c_deps) { + add_jst(a, b, c_deps); + add(a, b, c); +} + +template +void interval_manager::add(interval const & a, interval const & b, interval & c) { + ext_numeral_kind new_l_kind, new_u_kind; + round_to_minus_inf(); + ::add(m(), lower(a), lower_kind(a), lower(b), lower_kind(b), lower(c), new_l_kind); + round_to_plus_inf(); + ::add(m(), upper(a), upper_kind(a), upper(b), upper_kind(b), upper(c), new_u_kind); + set_lower_is_inf(c, new_l_kind == EN_MINUS_INFINITY); + set_upper_is_inf(c, new_u_kind == EN_PLUS_INFINITY); + set_lower_is_open(c, lower_is_open(a) || lower_is_open(b)); + set_upper_is_open(c, upper_is_open(a) || upper_is_open(b)); + SASSERT(check_invariant(c)); +} + +template +void interval_manager::sub_jst(interval const & a, interval const & b, interval_deps & c_deps) { + c_deps.m_lower_deps = DEP_IN_LOWER1 | DEP_IN_UPPER2; + c_deps.m_upper_deps = DEP_IN_UPPER1 | DEP_IN_LOWER2; +} + +template +void interval_manager::sub(interval const & a, interval const & b, interval & c, interval_deps & c_deps) { + sub_jst(a, b, c_deps); + sub(a, b, c); +} + +template +void interval_manager::sub(interval const & a, interval const & b, interval & c) { + ext_numeral_kind new_l_kind, new_u_kind; + round_to_minus_inf(); + ::sub(m(), lower(a), lower_kind(a), upper(b), upper_kind(b), lower(c), new_l_kind); + round_to_plus_inf(); + ::sub(m(), upper(a), upper_kind(a), lower(b), lower_kind(b), upper(c), new_u_kind); + set_lower_is_inf(c, new_l_kind == EN_MINUS_INFINITY); + set_upper_is_inf(c, new_u_kind == EN_PLUS_INFINITY); + set_lower_is_open(c, lower_is_open(a) || upper_is_open(b)); + set_upper_is_open(c, upper_is_open(a) || lower_is_open(b)); + SASSERT(check_invariant(c)); +} + +template +void interval_manager::mul_jst(numeral const & k, interval const & a, interval_deps & b_deps) { + if (m().is_zero(k)) { + b_deps.m_lower_deps = 0; + b_deps.m_upper_deps = 0; + } + else if (m().is_neg(k)) { + b_deps.m_lower_deps = DEP_IN_UPPER1; + b_deps.m_upper_deps = DEP_IN_LOWER1; + } + else { + b_deps.m_lower_deps = DEP_IN_LOWER1; + b_deps.m_upper_deps = DEP_IN_UPPER1; + } +} + +template +void interval_manager::div_mul(numeral const & k, interval const & a, interval & b, bool inv_k) { + if (m().is_zero(k)) { + reset(b); + } + else { + numeral const & l = lower(a); ext_numeral_kind l_k = lower_kind(a); + numeral const & u = upper(a); ext_numeral_kind u_k = upper_kind(a); + numeral & new_l_val = m_result_lower; + numeral & new_u_val = m_result_upper; + ext_numeral_kind new_l_kind, new_u_kind; + bool l_o = lower_is_open(a); + bool u_o = upper_is_open(a); + if (m().is_pos(k)) { + set_lower_is_open(b, l_o); + set_upper_is_open(b, u_o); + if (inv_k) { + round_to_minus_inf(); + m().inv(k, m_inv_k); + ::mul(m(), l, l_k, m_inv_k, EN_NUMERAL, new_l_val, new_l_kind); + + round_to_plus_inf(); + m().inv(k, m_inv_k); + ::mul(m(), u, u_k, m_inv_k, EN_NUMERAL, new_u_val, new_u_kind); + } + else { + round_to_minus_inf(); + ::mul(m(), l, l_k, k, EN_NUMERAL, new_l_val, new_l_kind); + round_to_plus_inf(); + ::mul(m(), u, u_k, k, EN_NUMERAL, new_u_val, new_u_kind); + } + } + else { + set_lower_is_open(b, u_o); + set_upper_is_open(b, l_o); + if (inv_k) { + round_to_minus_inf(); + m().inv(k, m_inv_k); + ::mul(m(), u, u_k, m_inv_k, EN_NUMERAL, new_l_val, new_l_kind); + + round_to_plus_inf(); + m().inv(k, m_inv_k); + ::mul(m(), l, l_k, m_inv_k, EN_NUMERAL, new_u_val, new_u_kind); + } + else { + round_to_minus_inf(); + ::mul(m(), u, u_k, k, EN_NUMERAL, new_l_val, new_l_kind); + round_to_plus_inf(); + ::mul(m(), l, l_k, k, EN_NUMERAL, new_u_val, new_u_kind); + } + } + m().swap(lower(b), new_l_val); + m().swap(upper(b), new_u_val); + set_lower_is_inf(b, new_l_kind == EN_MINUS_INFINITY); + set_upper_is_inf(b, new_u_kind == EN_PLUS_INFINITY); + } +} + +template +void interval_manager::mul(numeral const & k, interval const & a, interval & b, interval_deps & b_deps) { + mul_jst(k, a, b_deps); + mul(k, a, b); +} + +template +void interval_manager::mul(int n, int d, interval const & a, interval & b) { + _scoped_numeral aux(m()); + m().set(aux, n, d); + mul(aux, a, b); +} + +template +void interval_manager::div(interval const & a, numeral const & k, interval & b, interval_deps & b_deps) { + div_jst(a, k, b_deps); + div(a, k, b); +} + +template +void interval_manager::mul_jst(interval const & i1, interval const & i2, interval_deps & r_deps) { + if (is_zero(i1)) { + r_deps.m_lower_deps = DEP_IN_LOWER1 | DEP_IN_UPPER1; + r_deps.m_upper_deps = DEP_IN_LOWER1 | DEP_IN_UPPER1; + } + else if (is_zero(i2)) { + r_deps.m_lower_deps = DEP_IN_LOWER2 | DEP_IN_UPPER2; + r_deps.m_upper_deps = DEP_IN_LOWER2 | DEP_IN_UPPER2; + } + else if (is_N(i1)) { + if (is_N(i2)) { + // x <= b <= 0, y <= d <= 0 --> b*d <= x*y + // a <= x <= b <= 0, c <= y <= d <= 0 --> x*y <= a*c (we can use the fact that x or y is always negative (i.e., b is neg or d is neg)) + r_deps.m_lower_deps = DEP_IN_UPPER1 | DEP_IN_UPPER2; + r_deps.m_upper_deps = DEP_IN_LOWER1 | DEP_IN_LOWER2 | DEP_IN_UPPER1; // we can replace DEP_IN_UPPER1 with DEP_IN_UPPER2 + } + else if (is_M(i2)) { + // a <= x <= b <= 0, y <= d, d > 0 --> a*d <= x*y (uses the fact that b is not positive) + // a <= x <= b <= 0, c <= y, c < 0 --> x*y <= a*c (uses the fact that b is not positive) + r_deps.m_lower_deps = DEP_IN_LOWER1 | DEP_IN_UPPER2 | DEP_IN_UPPER1; + r_deps.m_upper_deps = DEP_IN_LOWER1 | DEP_IN_LOWER2 | DEP_IN_UPPER1; + } + else { + // a <= x <= b <= 0, 0 <= c <= y <= d --> a*d <= x*y (uses the fact that x is neg (b is not positive) or y is pos (c is not negative)) + // x <= b <= 0, 0 <= c <= y --> x*y <= b*c + r_deps.m_lower_deps = DEP_IN_LOWER1 | DEP_IN_UPPER2 | DEP_IN_UPPER1; // we can replace DEP_IN_UPPER1 with DEP_IN_UPPER2 + r_deps.m_upper_deps = DEP_IN_UPPER1 | DEP_IN_LOWER2; + } + } + else if (is_M(i1)) { + if (is_N(i2)) { + // b > 0, x <= b, c <= y <= d <= 0 --> b*c <= x*y (uses the fact that d is not positive) + // a < 0, a <= x, c <= y <= d <= 0 --> x*y <= a*c (uses the fact that d is not positive) + r_deps.m_lower_deps = DEP_IN_UPPER1 | DEP_IN_LOWER2 | DEP_IN_UPPER2; + r_deps.m_upper_deps = DEP_IN_LOWER1 | DEP_IN_LOWER2 | DEP_IN_UPPER2; + } + else if (is_M(i2)) { + r_deps.m_lower_deps = DEP_IN_LOWER1 | DEP_IN_UPPER1 | DEP_IN_LOWER2 | DEP_IN_UPPER2; + r_deps.m_upper_deps = DEP_IN_LOWER1 | DEP_IN_UPPER1 | DEP_IN_LOWER2 | DEP_IN_UPPER2; + } + else { + // a < 0, a <= x, 0 <= c <= y <= d --> a*d <= x*y (uses the fact that c is not negative) + // b > 0, x <= b, 0 <= c <= y <= d --> x*y <= b*d (uses the fact that c is not negative) + r_deps.m_lower_deps = DEP_IN_LOWER1 | DEP_IN_UPPER2 | DEP_IN_LOWER2; + r_deps.m_upper_deps = DEP_IN_UPPER1 | DEP_IN_UPPER2 | DEP_IN_LOWER2; + } + } + else { + SASSERT(is_P(i1)); + if (is_N(i2)) { + // 0 <= a <= x <= b, c <= y <= d <= 0 --> x*y <= b*c (uses the fact that x is pos (a is not neg) or y is neg (d is not pos)) + // 0 <= a <= x, y <= d <= 0 --> a*d <= x*y + r_deps.m_lower_deps = DEP_IN_UPPER1 | DEP_IN_LOWER2 | DEP_IN_LOWER1; // we can replace DEP_IN_LOWER1 with DEP_IN_UPPER2 + r_deps.m_upper_deps = DEP_IN_LOWER1 | DEP_IN_UPPER2; + } + else if (is_M(i2)) { + // 0 <= a <= x <= b, c <= y --> b*c <= x*y (uses the fact that a is not negative) + // 0 <= a <= x <= b, y <= d --> x*y <= b*d (uses the fact that a is not negative) + r_deps.m_lower_deps = DEP_IN_UPPER1 | DEP_IN_LOWER2 | DEP_IN_LOWER1; + r_deps.m_upper_deps = DEP_IN_UPPER1 | DEP_IN_UPPER2 | DEP_IN_LOWER1; + } + else { + SASSERT(is_P(i2)); + // 0 <= a <= x, 0 <= c <= y --> a*c <= x*y + // x <= b, y <= d --> x*y <= b*d (uses the fact that x is pos (a is not negative) or y is pos (c is not negative)) + r_deps.m_lower_deps = DEP_IN_LOWER1 | DEP_IN_LOWER2; + r_deps.m_upper_deps = DEP_IN_UPPER1 | DEP_IN_UPPER2 | DEP_IN_LOWER1; // we can replace DEP_IN_LOWER1 with DEP_IN_LOWER2 + } + } +} + +template +void interval_manager::mul(interval const & i1, interval const & i2, interval & r, interval_deps & r_deps) { + mul_jst(i1, i2, r_deps); + mul(i1, i2, r); +} + +template +void interval_manager::mul(interval const & i1, interval const & i2, interval & r) { +#ifdef _TRACE + static unsigned call_id = 0; +#endif +#if Z3DEBUG || _TRACE + bool i1_contains_zero = contains_zero(i1); + bool i2_contains_zero = contains_zero(i2); +#endif + if (is_zero(i1)) { + set(r, i1); + return; + } + if (is_zero(i2)) { + set(r, i2); + return; + } + + numeral const & a = lower(i1); ext_numeral_kind a_k = lower_kind(i1); + numeral const & b = upper(i1); ext_numeral_kind b_k = upper_kind(i1); + numeral const & c = lower(i2); ext_numeral_kind c_k = lower_kind(i2); + numeral const & d = upper(i2); ext_numeral_kind d_k = upper_kind(i2); + + bool a_o = lower_is_open(i1); + bool b_o = upper_is_open(i1); + bool c_o = lower_is_open(i2); + bool d_o = upper_is_open(i2); + + numeral & new_l_val = m_result_lower; + numeral & new_u_val = m_result_upper; + ext_numeral_kind new_l_kind, new_u_kind; + + if (is_N(i1)) { + if (is_N(i2)) { + // x <= b <= 0, y <= d <= 0 --> b*d <= x*y + // a <= x <= b <= 0, c <= y <= d <= 0 --> x*y <= a*c (we can use the fact that x or y is always negative (i.e., b is neg or d is neg)) + TRACE("interval_bug", tout << "(N, N) #" << call_id << "\n"; display(tout, i1); tout << "\n"; display(tout, i2); tout << "\n"; + tout << "a: "; m().display(tout, a); tout << "\n"; + tout << "b: "; m().display(tout, b); tout << "\n"; + tout << "c: "; m().display(tout, c); tout << "\n"; + tout << "d: "; m().display(tout, d); tout << "\n"; + tout << "is_N0(i1): " << is_N0(i1) << "\n"; + tout << "is_N0(i2): " << is_N0(i2) << "\n"; + ); + set_lower_is_open(r, (is_N0(i1) || is_N0(i2)) ? false : (b_o || d_o)); + set_upper_is_open(r, a_o || c_o); + // if b = 0 (and the interval is closed), then the lower bound is closed + + round_to_minus_inf(); + ::mul(m(), b, b_k, d, d_k, new_l_val, new_l_kind); + round_to_plus_inf(); + ::mul(m(), a, a_k, c, c_k, new_u_val, new_u_kind); + } + else if (is_M(i2)) { + // a <= x <= b <= 0, y <= d, d > 0 --> a*d <= x*y (uses the fact that b is not positive) + // a <= x <= b <= 0, c <= y, c < 0 --> x*y <= a*c (uses the fact that b is not positive) + TRACE("interval_bug", tout << "(N, M) #" << call_id << "\n";); + + set_lower_is_open(r, a_o || d_o); + set_upper_is_open(r, a_o || c_o); + + round_to_minus_inf(); + ::mul(m(), a, a_k, d, d_k, new_l_val, new_l_kind); + round_to_plus_inf(); + ::mul(m(), a, a_k, c, c_k, new_u_val, new_u_kind); + } + else { + // a <= x <= b <= 0, 0 <= c <= y <= d --> a*d <= x*y (uses the fact that x is neg (b is not positive) or y is pos (c is not negative)) + // x <= b <= 0, 0 <= c <= y --> x*y <= b*c + TRACE("interval_bug", tout << "(N, P) #" << call_id << "\n";); + SASSERT(is_P(i2)); + + // must update upper_is_open first, since value of is_N0(i1) and is_P0(i2) may be affected by update + set_upper_is_open(r, (is_N0(i1) || is_P0(i2)) ? false : (b_o || c_o)); + set_lower_is_open(r, a_o || d_o); + + round_to_minus_inf(); + ::mul(m(), a, a_k, d, d_k, new_l_val, new_l_kind); + round_to_plus_inf(); + ::mul(m(), b, b_k, c, c_k, new_u_val, new_u_kind); + } + } + else if (is_M(i1)) { + if (is_N(i2)) { + // b > 0, x <= b, c <= y <= d <= 0 --> b*c <= x*y (uses the fact that d is not positive) + // a < 0, a <= x, c <= y <= d <= 0 --> x*y <= a*c (uses the fact that d is not positive) + TRACE("interval_bug", tout << "(M, N) #" << call_id << "\n";); + + set_lower_is_open(r, b_o || c_o); + set_upper_is_open(r, a_o || c_o); + + round_to_minus_inf(); + ::mul(m(), b, b_k, c, c_k, new_l_val, new_l_kind); + round_to_plus_inf(); + ::mul(m(), a, a_k, c, c_k, new_u_val, new_u_kind); + } + else if (is_M(i2)) { + numeral & ad = m_mul_ad; ext_numeral_kind ad_k; + numeral & bc = m_mul_bc; ext_numeral_kind bc_k; + numeral & ac = m_mul_ac; ext_numeral_kind ac_k; + numeral & bd = m_mul_bd; ext_numeral_kind bd_k; + + bool ad_o = a_o || d_o; + bool bc_o = b_o || c_o; + bool ac_o = a_o || c_o; + bool bd_o = b_o || d_o; + + round_to_minus_inf(); + ::mul(m(), a, a_k, d, d_k, ad, ad_k); + ::mul(m(), b, b_k, c, c_k, bc, bc_k); + round_to_plus_inf(); + ::mul(m(), a, a_k, c, c_k, ac, ac_k); + ::mul(m(), b, b_k, d, d_k, bd, bd_k); + + if (::lt(m(), ad, ad_k, bc, bc_k) || (::eq(m(), ad, ad_k, bc, bc_k) && !ad_o && bc_o)) { + m().swap(new_l_val, ad); + new_l_kind = ad_k; + set_lower_is_open(r, ad_o); + } + else { + m().swap(new_l_val, bc); + new_l_kind = bc_k; + set_lower_is_open(r, bc_o); + } + + + if (::gt(m(), ac, ac_k, bd, bd_k) || (::eq(m(), ac, ac_k, bd, bd_k) && !ac_o && bd_o)) { + m().swap(new_u_val, ac); + new_u_kind = ac_k; + set_upper_is_open(r, ac_o); + } + else { + m().swap(new_u_val, bd); + new_u_kind = bd_k; + set_upper_is_open(r, bd_o); + } + } + else { + // a < 0, a <= x, 0 <= c <= y <= d --> a*d <= x*y (uses the fact that c is not negative) + // b > 0, x <= b, 0 <= c <= y <= d --> x*y <= b*d (uses the fact that c is not negative) + TRACE("interval_bug", tout << "(M, P) #" << call_id << "\n";); + SASSERT(is_P(i2)); + + set_lower_is_open(r, a_o || d_o); + set_upper_is_open(r, b_o || d_o); + + round_to_minus_inf(); + ::mul(m(), a, a_k, d, d_k, new_l_val, new_l_kind); + round_to_plus_inf(); + ::mul(m(), b, b_k, d, d_k, new_u_val, new_u_kind); + } + } + else { + SASSERT(is_P(i1)); + if (is_N(i2)) { + // 0 <= a <= x <= b, c <= y <= d <= 0 --> x*y <= b*c (uses the fact that x is pos (a is not neg) or y is neg (d is not pos)) + // 0 <= a <= x, y <= d <= 0 --> a*d <= x*y + TRACE("interval_bug", tout << "(P, N) #" << call_id << "\n";); + + // must update upper_is_open first, since value of is_P0(i1) and is_N0(i2) may be affected by update + set_upper_is_open(r, (is_P0(i1) || is_N0(i2)) ? false : a_o || d_o); + set_lower_is_open(r, b_o || c_o); + + round_to_minus_inf(); + ::mul(m(), b, b_k, c, c_k, new_l_val, new_l_kind); + round_to_plus_inf(); + ::mul(m(), a, a_k, d, d_k, new_u_val, new_u_kind); + } + else if (is_M(i2)) { + // 0 <= a <= x <= b, c <= y --> b*c <= x*y (uses the fact that a is not negative) + // 0 <= a <= x <= b, y <= d --> x*y <= b*d (uses the fact that a is not negative) + TRACE("interval_bug", tout << "(P, M) #" << call_id << "\n";); + + set_lower_is_open(r, b_o || c_o); + set_upper_is_open(r, b_o || d_o); + + round_to_minus_inf(); + ::mul(m(), b, b_k, c, c_k, new_l_val, new_l_kind); + round_to_plus_inf(); + ::mul(m(), b, b_k, d, d_k, new_u_val, new_u_kind); + } + else { + SASSERT(is_P(i2)); + // 0 <= a <= x, 0 <= c <= y --> a*c <= x*y + // x <= b, y <= d --> x*y <= b*d (uses the fact that x is pos (a is not negative) or y is pos (c is not negative)) + TRACE("interval_bug", tout << "(P, P) #" << call_id << "\n";); + + set_lower_is_open(r, (is_P0(i1) || is_P0(i2)) ? false : a_o || c_o); + set_upper_is_open(r, b_o || d_o); + + round_to_minus_inf(); + ::mul(m(), a, a_k, c, c_k, new_l_val, new_l_kind); + round_to_plus_inf(); + ::mul(m(), b, b_k, d, d_k, new_u_val, new_u_kind); + } + } + + m().swap(lower(r), new_l_val); + m().swap(upper(r), new_u_val); + set_lower_is_inf(r, new_l_kind == EN_MINUS_INFINITY); + set_upper_is_inf(r, new_u_kind == EN_PLUS_INFINITY); + SASSERT(!(i1_contains_zero || i2_contains_zero) || contains_zero(r)); + TRACE("interval_bug", tout << "result: "; display(tout, r); tout << "\n";); +#ifdef _TRACE + call_id++; +#endif +} + +template +void interval_manager::power_jst(interval const & a, unsigned n, interval_deps & b_deps) { + if (n == 1) { + b_deps.m_lower_deps = DEP_IN_LOWER1; + b_deps.m_upper_deps = DEP_IN_UPPER1; + } + else if (n % 2 == 0) { + if (lower_is_pos(a)) { + // [l, u]^n = [l^n, u^n] if l > 0 + // 0 < l <= x --> l^n <= x^n (lower bound guarantees that is positive) + // 0 < l <= x <= u --> x^n <= u^n (use lower and upper bound -- need the fact that x is positive) + b_deps.m_lower_deps = DEP_IN_LOWER1; + if (upper_is_inf(a)) + b_deps.m_upper_deps = 0; + else + b_deps.m_upper_deps = DEP_IN_LOWER1 | DEP_IN_UPPER1; + } + else if (upper_is_neg(a)) { + // [l, u]^n = [u^n, l^n] if u < 0 + // l <= x <= u < 0 --> x^n <= l^n (use lower and upper bound -- need the fact that x is negative) + // x <= u < 0 --> u^n <= x^n + b_deps.m_lower_deps = DEP_IN_UPPER1; + if (lower_is_inf(a)) + b_deps.m_upper_deps = 0; + else + b_deps.m_upper_deps = DEP_IN_LOWER1 | DEP_IN_UPPER1; + } + else { + // [l, u]^n = [0, max{l^n, u^n}] otherwise + // we need both bounds to justify upper bound + b_deps.m_upper_deps = DEP_IN_LOWER1 | DEP_IN_UPPER1; + b_deps.m_lower_deps = 0; + } + } + else { + // Remark: when n is odd x^n is monotonic. + if (lower_is_inf(a)) + b_deps.m_lower_deps = 0; + else + b_deps.m_lower_deps = DEP_IN_LOWER1; + + if (upper_is_inf(a)) + b_deps.m_upper_deps = 0; + else + b_deps.m_upper_deps = DEP_IN_UPPER1; + } +} + +template +void interval_manager::power(interval const & a, unsigned n, interval & b, interval_deps & b_deps) { + power_jst(a, n, b_deps); + power(a, n, b); +} + +template +void interval_manager::power(interval const & a, unsigned n, interval & b) { +#ifdef _TRACE + static unsigned call_id = 0; +#endif + if (n == 1) { + set(b, a); + } + else if (n % 2 == 0) { + if (lower_is_pos(a)) { + // [l, u]^n = [l^n, u^n] if l > 0 + // 0 < l <= x --> l^n <= x^n (lower bound guarantees that is positive) + // 0 < l <= x <= u --> x^n <= u^n (use lower and upper bound -- need the fact that x is positive) + SASSERT(!lower_is_inf(a)); + round_to_minus_inf(); + m().power(lower(a), n, lower(b)); + set_lower_is_inf(b, false); + set_lower_is_open(b, lower_is_open(a)); + + if (upper_is_inf(a)) { + reset_upper(b); + } + else { + round_to_plus_inf(); + m().power(upper(a), n, upper(b)); + set_upper_is_inf(b, false); + set_upper_is_open(b, upper_is_open(a)); + } + } + else if (upper_is_neg(a)) { + // [l, u]^n = [u^n, l^n] if u < 0 + // l <= x <= u < 0 --> x^n <= l^n (use lower and upper bound -- need the fact that x is negative) + // x <= u < 0 --> u^n <= x^n + SASSERT(!upper_is_inf(a)); + bool lower_a_open = lower_is_open(a), upper_a_open = upper_is_open(a); + bool lower_a_inf = lower_is_inf(a); + + m().set(lower(b), lower(a)); + m().set(upper(b), upper(a)); + m().swap(lower(b), upper(b)); // we use a swap because a and b can be aliased + + + round_to_minus_inf(); + m().power(lower(b), n, lower(b)); + + set_lower_is_open(b, upper_a_open); + set_lower_is_inf(b, false); + + if (lower_a_inf) { + reset_upper(b); + } + else { + round_to_plus_inf(); + m().power(upper(b), n, upper(b)); + set_upper_is_inf(b, false); + set_upper_is_open(b, lower_a_open); + } + } + else { + // [l, u]^n = [0, max{l^n, u^n}] otherwise + // we need both bounds to justify upper bound + TRACE("interval_bug", tout << "(M) #" << call_id << "\n"; display(tout, a); tout << "\nn:" << n << "\n";); + + ext_numeral_kind un1_kind = lower_kind(a), un2_kind = upper_kind(a); + numeral & un1 = m_result_lower; + numeral & un2 = m_result_upper; + m().set(un1, lower(a)); + m().set(un2, upper(a)); + round_to_plus_inf(); + ::power(m(), un1, un1_kind, n); + ::power(m(), un2, un2_kind, n); + + if (::gt(m(), un1, un1_kind, un2, un2_kind) || (::eq(m(), un1, un1_kind, un2, un2_kind) && !lower_is_open(a) && upper_is_open(a))) { + m().swap(upper(b), un1); + set_upper_is_inf(b, un1_kind == EN_PLUS_INFINITY); + set_upper_is_open(b, lower_is_open(a)); + } + else { + m().swap(upper(b), un2); + set_upper_is_inf(b, un2_kind == EN_PLUS_INFINITY); + set_upper_is_open(b, upper_is_open(a)); + } + + m().reset(lower(b)); + set_lower_is_inf(b, false); + set_lower_is_open(b, false); + } + } + else { + // Remark: when n is odd x^n is monotonic. + if (lower_is_inf(a)) { + reset_lower(b); + } + else { + m().power(lower(a), n, lower(b)); + set_lower_is_inf(b, false); + set_lower_is_open(b, lower_is_open(a)); + } + + if (upper_is_inf(a)) { + reset_upper(b); + } + else { + m().power(upper(a), n, upper(b)); + set_upper_is_inf(b, false); + set_upper_is_open(b, upper_is_open(a)); + } + } + TRACE("interval_bug", tout << "result: "; display(tout, b); tout << "\n";); +#ifdef _TRACE + call_id++; +#endif +} + + +template +void interval_manager::nth_root(interval const & a, unsigned n, numeral const & p, interval & b, interval_deps & b_deps) { + nth_root_jst(a, n, p, b_deps); + nth_root(a, n, p, b); +} + +template +void interval_manager::nth_root(interval const & a, unsigned n, numeral const & p, interval & b) { + SASSERT(n % 2 != 0 || !lower_is_neg(a)); + if (n == 1) { + set(b, a); + return; + } + + if (lower_is_inf(a)) { + SASSERT(n % 2 != 0); // n must not be even. + m().reset(lower(b)); + set_lower_is_inf(b, true); + set_lower_is_open(b, true); + } + else { + numeral & lo = m_result_lower; + numeral & hi = m_result_upper; + nth_root(lower(a), n, p, lo, hi); + set_lower_is_inf(b, false); + set_lower_is_open(b, lower_is_open(a) && m().eq(lo, hi)); + m().set(lower(b), lo); + } + + if (upper_is_inf(a)) { + m().reset(upper(b)); + set_upper_is_inf(b, true); + set_upper_is_open(b, true); + } + else { + numeral & lo = m_result_lower; + numeral & hi = m_result_upper; + nth_root(upper(a), n, p, lo, hi); + set_upper_is_inf(b, false); + set_upper_is_open(b, upper_is_open(a) && m().eq(lo, hi)); + m().set(upper(b), hi); + } + TRACE("interval_nth_root", display(tout, a); tout << " --> "; display(tout, b); tout << "\n";); +} + +template +void interval_manager::nth_root_jst(interval const & a, unsigned n, numeral const & p, interval_deps & b_deps) { + b_deps.m_lower_deps = DEP_IN_LOWER1; + if (n % 2 == 0) + b_deps.m_upper_deps = DEP_IN_LOWER1 | DEP_IN_UPPER1; + else + b_deps.m_upper_deps = DEP_IN_UPPER1; +} + +template +void interval_manager::xn_eq_y(interval const & y, unsigned n, numeral const & p, interval & x, interval_deps & x_deps) { + xn_eq_y_jst(y, n, p, x_deps); + xn_eq_y(y, n, p, x); +} + +template +void interval_manager::xn_eq_y(interval const & y, unsigned n, numeral const & p, interval & x) { + SASSERT(n % 2 != 0 || !lower_is_neg(y)); + if (n % 2 == 0) { + SASSERT(!lower_is_inf(y)); + if (upper_is_inf(y)) { + reset(x); + } + else { + numeral & lo = m_result_lower; + numeral & hi = m_result_upper; + nth_root(upper(y), n, p, lo, hi); + // result is [-hi, hi] + // result is open if upper(y) is open and lo == hi + TRACE("interval_xn_eq_y", tout << "x^n = "; display(tout, y); tout << "\n"; + tout << "sqrt(y) in "; m().display(tout, lo); tout << " "; m().display(tout, hi); tout << "\n";); + bool open = upper_is_open(y) && m().eq(lo, hi); + set_lower_is_inf(x, false); + set_upper_is_inf(x, false); + set_lower_is_open(x, open); + set_upper_is_open(x, open); + m().set(upper(x), hi); + round_to_minus_inf(); + m().set(lower(x), hi); + m().neg(lower(x)); + TRACE("interval_xn_eq_y", tout << "interval for x: "; display(tout, x); tout << "\n";); + } + } + else { + SASSERT(n % 2 == 1); // n is odd + nth_root(y, n, p, x); + } +} + +template +void interval_manager::xn_eq_y_jst(interval const & y, unsigned n, numeral const & p, interval_deps & x_deps) { + if (n % 2 == 0) { + x_deps.m_lower_deps = DEP_IN_LOWER1 | DEP_IN_UPPER1; + x_deps.m_upper_deps = DEP_IN_LOWER1 | DEP_IN_UPPER1; + } + else { + x_deps.m_lower_deps = DEP_IN_LOWER1; + x_deps.m_upper_deps = DEP_IN_UPPER1; + } +} + +template +void interval_manager::inv_jst(interval const & a, interval_deps & b_deps) { + SASSERT(!contains_zero(a)); + if (is_P1(a)) { + b_deps.m_lower_deps = DEP_IN_LOWER1 | DEP_IN_UPPER1; + b_deps.m_upper_deps = DEP_IN_LOWER1; + } + else if (is_N1(a)) { + // x <= u < 0 --> 1/u <= 1/x + // l <= x <= u < 0 --> 1/l <= 1/x (use lower and upper bounds) + b_deps.m_lower_deps = DEP_IN_UPPER1; + b_deps.m_upper_deps = DEP_IN_LOWER1 | DEP_IN_UPPER1; + } + else { + UNREACHABLE(); + } +} + +template +void interval_manager::inv(interval const & a, interval & b, interval_deps & b_deps) { + inv_jst(a, b_deps); + inv(a, b); +} + +template +void interval_manager::inv(interval const & a, interval & b) { +#ifdef _TRACE + static unsigned call_id = 0; +#endif + // If the interval [l,u] does not contain 0, then 1/[l,u] = [1/u, 1/l] + SASSERT(!contains_zero(a)); + TRACE("interval_bug", tout << "(inv) #" << call_id << "\n"; display(tout, a); tout << "\n";); + + numeral & new_l_val = m_result_lower; + numeral & new_u_val = m_result_upper; + ext_numeral_kind new_l_kind, new_u_kind; + + if (is_P1(a)) { + // 0 < l <= x --> 1/x <= 1/l + // 0 < l <= x <= u --> 1/u <= 1/x (use lower and upper bounds) + + round_to_minus_inf(); + m().set(new_l_val, upper(a)); new_l_kind = upper_kind(a); + ::inv(m(), new_l_val, new_l_kind); + SASSERT(new_l_kind == EN_NUMERAL); + bool new_l_open = upper_is_open(a); + + if (lower_is_zero(a)) { + SASSERT(lower_is_open(a)); + m().reset(upper(b)); + set_upper_is_inf(b, true); + set_upper_is_open(b, true); + } + else { + round_to_plus_inf(); + m().set(new_u_val, lower(a)); + m().inv(new_u_val); + m().swap(upper(b), new_u_val); + set_upper_is_inf(b, false); + set_upper_is_open(b, lower_is_open(a)); + } + + m().swap(lower(b), new_l_val); + set_lower_is_inf(b, false); + set_lower_is_open(b, new_l_open); + } + else if (is_N1(a)) { + // x <= u < 0 --> 1/u <= 1/x + // l <= x <= u < 0 --> 1/l <= 1/x (use lower and upper bounds) + + round_to_plus_inf(); + m().set(new_u_val, lower(a)); new_u_kind = lower_kind(a); + ::inv(m(), new_u_val, new_u_kind); + SASSERT(new_u_kind == EN_NUMERAL); + bool new_u_open = lower_is_open(a); + + if (upper_is_zero(a)) { + SASSERT(upper_is_open(a)); + m().reset(lower(b)); + set_lower_is_open(b, true); + set_lower_is_inf(b, true); + } + else { + round_to_minus_inf(); + m().set(new_l_val, upper(a)); + m().inv(new_l_val); + m().swap(lower(b), new_l_val); + set_lower_is_inf(b, false); + set_lower_is_open(b, upper_is_open(a)); + } + + m().swap(upper(b), new_u_val); + set_upper_is_inf(b, false); + set_upper_is_open(b, new_u_open); + } + else { + UNREACHABLE(); + } + TRACE("interval_bug", tout << "result: "; display(tout, b); tout << "\n";); +#ifdef _TRACE + call_id++; +#endif +} + +template +void interval_manager::div_jst(interval const & i1, interval const & i2, interval_deps & r_deps) { + SASSERT(!contains_zero(i2)); + if (is_zero(i1)) { + if (is_P1(i2)) { + r_deps.m_lower_deps = DEP_IN_LOWER1 | DEP_IN_LOWER2; + r_deps.m_upper_deps = DEP_IN_UPPER1 | DEP_IN_LOWER2; + } + else { + r_deps.m_lower_deps = DEP_IN_UPPER1 | DEP_IN_UPPER2; + r_deps.m_upper_deps = DEP_IN_LOWER1 | DEP_IN_UPPER2; + } + } + else { + if (is_N(i1)) { + if (is_N1(i2)) { + // x <= b <= 0, c <= y <= d < 0 --> b/c <= x/y + // a <= x <= b <= 0, y <= d < 0 --> x/y <= a/d + r_deps.m_lower_deps = DEP_IN_UPPER1 | DEP_IN_LOWER2 | DEP_IN_UPPER2; + r_deps.m_upper_deps = DEP_IN_LOWER1 | DEP_IN_UPPER2; + } + else { + // a <= x, a < 0, 0 < c <= y --> a/c <= x/y + // x <= b <= 0, 0 < c <= y <= d --> x/y <= b/d + r_deps.m_lower_deps = DEP_IN_LOWER1 | DEP_IN_LOWER2; + r_deps.m_upper_deps = DEP_IN_UPPER1 | DEP_IN_LOWER2 | DEP_IN_UPPER2; + } + } + else if (is_M(i1)) { + if (is_N1(i2)) { + // 0 < a <= x <= b < 0, y <= d < 0 --> b/d <= x/y + // 0 < a <= x <= b < 0, y <= d < 0 --> x/y <= a/d + r_deps.m_lower_deps = DEP_IN_UPPER1 | DEP_IN_UPPER2; + r_deps.m_upper_deps = DEP_IN_LOWER1 | DEP_IN_UPPER2; + } + else { + // 0 < a <= x <= b < 0, 0 < c <= y --> a/c <= x/y + // 0 < a <= x <= b < 0, 0 < c <= y --> x/y <= b/c + r_deps.m_lower_deps = DEP_IN_LOWER1 | DEP_IN_LOWER2; + r_deps.m_upper_deps = DEP_IN_UPPER1 | DEP_IN_LOWER2; + } + } + else { + SASSERT(is_P(i1)); + if (is_N1(i2)) { + // b > 0, x <= b, c <= y <= d < 0 --> b/d <= x/y + // 0 <= a <= x, c <= y <= d < 0 --> x/y <= a/c + r_deps.m_lower_deps = DEP_IN_UPPER1 | DEP_IN_UPPER2; + r_deps.m_upper_deps = DEP_IN_LOWER1 | DEP_IN_LOWER2 | DEP_IN_UPPER2; + } + else { + SASSERT(is_P1(i2)); + // 0 <= a <= x, 0 < c <= y <= d --> a/d <= x/y + // b > 0 x <= b, 0 < c <= y --> x/y <= b/c + r_deps.m_lower_deps = DEP_IN_LOWER1 | DEP_IN_LOWER2 | DEP_IN_UPPER2; + r_deps.m_upper_deps = DEP_IN_UPPER1 | DEP_IN_LOWER2; + } + } + } +} + +template +void interval_manager::div(interval const & i1, interval const & i2, interval & r, interval_deps & r_deps) { + div_jst(i1, i2, r_deps); + div(i1, i2, r); +} + +template +void interval_manager::div(interval const & i1, interval const & i2, interval & r) { +#ifdef _TRACE + static unsigned call_id = 0; +#endif + SASSERT(!contains_zero(i2)); + SASSERT(&i1 != &r); + + if (is_zero(i1)) { + TRACE("interval_bug", tout << "div #" << call_id << "\n"; display(tout, i1); tout << "\n"; display(tout, i2); tout << "\n";); + + // 0/other = 0 if other != 0 + m().reset(lower(r)); + m().reset(upper(r)); + set_lower_is_inf(r, false); + set_upper_is_inf(r, false); + set_lower_is_open(r, false); + set_upper_is_open(r, false); + } + else { + numeral const & a = lower(i1); ext_numeral_kind a_k = lower_kind(i1); + numeral const & b = upper(i1); ext_numeral_kind b_k = upper_kind(i1); + numeral const & c = lower(i2); ext_numeral_kind c_k = lower_kind(i2); + numeral const & d = upper(i2); ext_numeral_kind d_k = upper_kind(i2); + + bool a_o = lower_is_open(i1); + bool b_o = upper_is_open(i1); + bool c_o = lower_is_open(i2); + bool d_o = upper_is_open(i2); + + numeral & new_l_val = m_result_lower; + numeral & new_u_val = m_result_upper; + ext_numeral_kind new_l_kind, new_u_kind; + + TRACE("interval_bug", tout << "div #" << call_id << "\n"; display(tout, i1); tout << "\n"; display(tout, i2); tout << "\n"; + tout << "a: "; m().display(tout, a); tout << "\n"; + tout << "b: "; m().display(tout, b); tout << "\n"; + tout << "c: "; m().display(tout, c); tout << "\n"; + tout << "d: "; m().display(tout, d); tout << "\n"; + ); + + if (is_N(i1)) { + if (is_N1(i2)) { + // x <= b <= 0, c <= y <= d < 0 --> b/c <= x/y + // a <= x <= b <= 0, y <= d < 0 --> x/y <= a/d + TRACE("interval_bug", tout << "(N, N) #" << call_id << "\n";); + + set_lower_is_open(r, is_N0(i1) ? false : b_o || c_o); + set_upper_is_open(r, a_o || d_o); + + round_to_minus_inf(); + ::div(m(), b, b_k, c, c_k, new_l_val, new_l_kind); + if (m().is_zero(d)) { + SASSERT(d_o); + m().reset(new_u_val); + new_u_kind = EN_PLUS_INFINITY; + } + else { + round_to_plus_inf(); + ::div(m(), a, a_k, d, d_k, new_u_val, new_u_kind); + } + } + else { + // a <= x, a < 0, 0 < c <= y --> a/c <= x/y + // x <= b <= 0, 0 < c <= y <= d --> x/y <= b/d + TRACE("interval_bug", tout << "(N, P) #" << call_id << "\n";); + SASSERT(is_P1(i2)); + + set_upper_is_open(r, is_N0(i1) ? false : (b_o || d_o)); + set_lower_is_open(r, a_o || c_o); + + if (m().is_zero(c)) { + SASSERT(c_o); + m().reset(new_l_val); + new_l_kind = EN_MINUS_INFINITY; + } + else { + round_to_minus_inf(); + ::div(m(), a, a_k, c, c_k, new_l_val, new_l_kind); + } + round_to_plus_inf(); + ::div(m(), b, b_k, d, d_k, new_u_val, new_u_kind); + } + } + else if (is_M(i1)) { + if (is_N1(i2)) { + // 0 < a <= x <= b < 0, y <= d < 0 --> b/d <= x/y + // 0 < a <= x <= b < 0, y <= d < 0 --> x/y <= a/d + TRACE("interval_bug", tout << "(M, N) #" << call_id << "\n";); + + set_lower_is_open(r, b_o || d_o); + set_upper_is_open(r, a_o || d_o); + + if (m().is_zero(d)) { + SASSERT(d_o); + m().reset(new_l_val); m().reset(new_u_val); + new_l_kind = EN_MINUS_INFINITY; + new_u_kind = EN_PLUS_INFINITY; + } + else { + round_to_minus_inf(); + ::div(m(), b, b_k, d, d_k, new_l_val, new_l_kind); + round_to_plus_inf(); + ::div(m(), a, a_k, d, d_k, new_u_val, new_u_kind); + TRACE("interval_bug", tout << "new_l_kind: " << new_l_kind << ", new_u_kind: " << new_u_kind << "\n";); + } + } + else { + // 0 < a <= x <= b < 0, 0 < c <= y --> a/c <= x/y + // 0 < a <= x <= b < 0, 0 < c <= y --> x/y <= b/c + + TRACE("interval_bug", tout << "(M, P) #" << call_id << "\n";); + SASSERT(is_P1(i2)); + + set_lower_is_open(r, a_o || c_o); + set_upper_is_open(r, b_o || c_o); + + if (m().is_zero(c)) { + SASSERT(c_o); + m().reset(new_l_val); m().reset(new_u_val); + new_l_kind = EN_MINUS_INFINITY; + new_u_kind = EN_PLUS_INFINITY; + } + else { + round_to_minus_inf(); + ::div(m(), a, a_k, c, c_k, new_l_val, new_l_kind); + round_to_plus_inf(); + ::div(m(), b, b_k, c, c_k, new_u_val, new_u_kind); + } + } + } + else { + SASSERT(is_P(i1)); + if (is_N1(i2)) { + // b > 0, x <= b, c <= y <= d < 0 --> b/d <= x/y + // 0 <= a <= x, c <= y <= d < 0 --> x/y <= a/c + TRACE("interval_bug", tout << "(P, N) #" << call_id << "\n";); + + set_upper_is_open(r, is_P0(i1) ? false : a_o || c_o); + set_lower_is_open(r, b_o || d_o); + + if (m().is_zero(d)) { + SASSERT(d_o); + m().reset(new_l_val); + new_l_kind = EN_MINUS_INFINITY; + } + else { + round_to_minus_inf(); + ::div(m(), b, b_k, d, d_k, new_l_val, new_l_kind); + } + round_to_plus_inf(); + ::div(m(), a, a_k, c, c_k, new_u_val, new_u_kind); + } + else { + SASSERT(is_P1(i2)); + // 0 <= a <= x, 0 < c <= y <= d --> a/d <= x/y + // b > 0 x <= b, 0 < c <= y --> x/y <= b/c + TRACE("interval_bug", tout << "(P, P) #" << call_id << "\n";); + + set_lower_is_open(r, is_P0(i1) ? false : a_o || d_o); + set_upper_is_open(r, b_o || c_o); + + round_to_minus_inf(); + ::div(m(), a, a_k, d, d_k, new_l_val, new_l_kind); + if (m().is_zero(c)) { + SASSERT(c_o); + m().reset(new_u_val); + new_u_kind = EN_PLUS_INFINITY; + } + else { + round_to_plus_inf(); + ::div(m(), b, b_k, c, c_k, new_u_val, new_u_kind); + } + } + } + + m().swap(lower(r), new_l_val); + m().swap(upper(r), new_u_val); + set_lower_is_inf(r, new_l_kind == EN_MINUS_INFINITY); + set_upper_is_inf(r, new_u_kind == EN_PLUS_INFINITY); + } + TRACE("interval_bug", tout << "result: "; display(tout, r); tout << "\n";); +#ifdef _TRACE + call_id++; +#endif +} + +template +void interval_manager::pi_series(int x, numeral & r, bool up) { + // Store in r the value: 1/16^x (4/(8x + 1) - 2/(8x + 4) - 1/(8x + 5) - 1/(8x + 6)) + _scoped_numeral f(m()); + set_rounding(up); + m().set(r, 4, 8*x + 1); + set_rounding(!up); + m().set(f, 2, 8*x + 4); + set_rounding(up); + m().sub(r, f, r); + set_rounding(!up); + m().set(f, 1, 8*x + 5); + set_rounding(up); + m().sub(r, f, r); + set_rounding(!up); + m().set(f, 1, 8*x + 6); + set_rounding(up); + m().sub(r, f, r); + m().set(f, 1, 16); + m().power(f, x, f); + m().mul(r, f, r); +} + +template +void interval_manager::pi(unsigned n, interval & r) { + // Compute an interval that contains pi using the series + // P[0] + P[1] + ... + P[n] + // where + // P[n] := 1/16^x (4/(8x + 1) - 2/(8x + 4) - 1/(8x + 5) - 1/(8x + 6)) + // + // The size of the interval is 1/15 * 1/(16^n) + // + // Lower is P[0] + P[1] + ... + P[n] + // Upper is Lower + 1/15 * 1/(16^n) + + // compute size of the resulting interval + round_to_plus_inf(); // overestimate size of the interval + _scoped_numeral len(m()); + _scoped_numeral p(m()); + m().set(len, 1, 16); + m().power(len, n, len); + m().set(p, 1, 15); + m().mul(p, len, len); + + // compute lower bound + numeral & l_val = m_result_lower; + m().reset(l_val); + for (unsigned i = 0; i <= n; i++) { + pi_series(i, p, false); + round_to_minus_inf(); + m().add(l_val, p, l_val); + } + + // computer upper bound + numeral & u_val = m_result_upper; + if (m().precise()) { + // the numeral manager is precise, so we do not need to recompute the series + m().add(l_val, len, u_val); + } + else { + // recompute the sum rounding to plus infinite + m().reset(u_val); + for (unsigned i = 0; i <= n; i++) { + pi_series(i, p, true); + round_to_plus_inf(); + m().add(u_val, p, u_val); + } + round_to_plus_inf(); + m().add(u_val, len, u_val); + } + + set_lower_is_open(r, false); + set_upper_is_open(r, false); + set_lower_is_inf(r, false); + set_upper_is_inf(r, false); + m().set(lower(r), l_val); + m().set(upper(r), u_val); +} + +template +void interval_manager::set_pi_prec(unsigned n) { + SASSERT(n > 0); + m_pi_n = n; + pi(n, m_pi); + mul(1, 2, m_pi, m_pi_div_2); + mul(3, 2, m_pi, m_3_pi_div_2); + mul(2, 1, m_pi, m_2_pi); +} + +template +void interval_manager::set_pi_at_least_prec(unsigned n) { + if (n > m_pi_n) + set_pi_prec(n); +} + +template +void interval_manager::e_series(unsigned k, bool upper, numeral & o) { + _scoped_numeral d(m()), a(m()); + m().set(o, 2); + m().set(d, 1); + for (unsigned i = 2; i <= k; i++) { + set_rounding(!upper); + m().set(a, static_cast(i)); + m().mul(d, a, d); // d == i! + m().set(a, d); + set_rounding(upper); + m().inv(a); // a == 1/i! + m().add(o, a, o); + } +} + +template +void interval_manager::e(unsigned k, interval & r) { + // Store in r lower and upper bounds for Euler's constant. + // + // The procedure uses the series + // + // V = 1 + 1/1 + 1/2! + 1/3! + ... + 1/k! + // + // The error in the approximation above is <= E = 4/(k+1)! + // Thus, e must be in the interval [V, V+E] + numeral & lo = m_result_lower; + numeral & hi = m_result_upper; + + e_series(k, false, lo); + + _scoped_numeral error(m()), aux(m()); + round_to_minus_inf(); + fact(k+1, error); + round_to_plus_inf(); + m().inv(error); // error == 1/(k+1)! + m().set(aux, 4); + m().mul(aux, error, error); // error == 4/(k+1)! + + if (m().precise()) { + m().set(hi, lo); + m().add(hi, error, hi); + } + else { + e_series(k, true, hi); + round_to_plus_inf(); + m().add(hi, error, hi); + } + + set_lower_is_open(r, false); + set_upper_is_open(r, false); + set_lower_is_inf(r, false); + set_upper_is_inf(r, false); + m().set(lower(r), lo); + m().set(upper(r), hi); +} + +#endif diff --git a/lib/interval_skip_list.h b/lib/interval_skip_list.h new file mode 100644 index 000000000..369d90bd3 --- /dev/null +++ b/lib/interval_skip_list.h @@ -0,0 +1,1876 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + interval_skip_list.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-10-01. + +Revision History: + +--*/ +#ifndef _INTERVAL_SKIP_LIST_H_ +#define _INTERVAL_SKIP_LIST_H_ + +#include"skip_list_base.h" + +/* + Interval skip lists implement a mapping from keys to values. + The key must be a total order. + + It compress consecutive entries k->v and (k+1)->v by + using intervals. Internally, we have [k1, k2]->v to represent + the set of entries k1->v, (k1+1)->v, ..., k2->v. + + For improving cache behavior, the entries are packed in + buckets. As regular skip-lists, a node/bucket may have + several next/forward pointers. + + Interval skip lists can also encode sets. In this case, + there is no value type. We achieve that by allowing the template + to be instantiated with an arbitrary "entry" class for encoding + [k1, k2]->v. This class must provide the methods: + + key const & begin_key() const + key const & end_key() const + value const & val() const + void set_begin_key(key const & k) + void set_end_key(key const & k) + void set_val(value const & v) + void display(ostream & out) const + bool operator==(entry const & other) const; + + And a definition for the key and value types. +*/ + +/** + \brief Default interval_skip_list entry. + It is useful for implementing mappings. +*/ +template +class default_islist_entry { +public: + typedef Key key; + typedef Value value; +private: + key m_begin_key; + key m_end_key; + value m_value; +public: + default_islist_entry() {} + default_islist_entry(key const & b, key const & e, value const & v): + m_begin_key(b), + m_end_key(e), + m_value(v) { + } + key const & begin_key() const { return m_begin_key; } + key const & end_key() const { return m_end_key; } + value const & val() const { return m_value; } + void set_begin_key(key const & k) { m_begin_key = k; } + void set_end_key(key const & k) { m_end_key = k; } + void set_val(value const & v) { m_value = v; } + void display(std::ostream & out) const { + out << "[" << begin_key() << ", " << end_key() << "] -> " << val(); + } +}; + +/** + \brief Default interval_skip_list entry for encoding sets. +*/ +template +struct default_set_islist_entry { +public: + typedef Key key; + typedef unsigned value; // don't care type +private: + key m_begin_key; + key m_end_key; +public: + default_set_islist_entry() {} + default_set_islist_entry(key const & b, key const & e): + m_begin_key(b), + m_end_key(e) { + } + key const & begin_key() const { return m_begin_key; } + key const & end_key() const { return m_end_key; } + unsigned const & val() const { static unsigned v = 0; return v; } + void set_begin_key(key const & k) { m_begin_key = k; } + void set_end_key(key const & k) { m_end_key = k; } + void set_val(value const & v) { /* do nothing */ } + void display(std::ostream & out) const { + out << "[" << begin_key() << ", " << end_key() << "]"; + } +}; + + +/** + \brief An interval skip list. + + See comments at skip_list_base.h +*/ +template +class interval_skip_list : public skip_list_base { +protected: + typedef typename skip_list_base::bucket bucket; +public: + typedef typename Traits::manager manager; + typedef typename Traits::entry entry; + typedef typename entry::key key; + typedef typename entry::value value; + +protected: + bool lt(key const & k1, key const & k2) const { return skip_list_base::lt(k1, k2); } + + static key const & first_key(bucket const * bt) { return bt->first_entry().begin_key(); } + + static key const & last_key(bucket const * bt) { return bt->last_entry().end_key(); } + + static void set_entry(bucket * bt, unsigned idx, key const & b, key const & e, value const & v) { + entry & en = bt->get(idx); + en.set_begin_key(b); + en.set_end_key(e); + en.set_val(v); + } + + /** + \brief Add first entry to the list. + + \remark This method will invoke inc_ref_eh for v. + */ + void insert_first_entry(manager & m, key const & b, key const & e, value const & v) { + entry en; + en.set_begin_key(b); + en.set_end_key(e); + en.set_val(v); + skip_list_base::insert_first_entry(m, en); + } + + /** + \brief Return true if the given key \c k is in the entry \c e. That is, + k \in [e.begin_key(), e.end_key()]. + */ + bool contains(entry const & e, key const & k) const { return leq(e.begin_key(), k) && leq(k, e.end_key()); } + + /** + \brief Return true if the given key \c k is in the entry \c e. That is, + k \in [e.begin_key(), e.end_key()]. If that is the case, then store e.value() in v. + Otherwise, return false. + */ + bool contains(entry const & e, key const & k, value & v) const { + if (contains(e, k)) { + v = e.val(); + return true; + } + else { + return false; + } + } + + /** + \brief Search for a key in a bucket starting at position s_idx using binary search. + + Return true if k was found in the bucket and store the index of + the entry containing \c k in \c idx. + + Otherwise, return false and \c idx will contain the index + s.t. + (idx == s_idx || entry[idx-1].end_key() < k) && (idx == bt->size() || k < entry[idx].begin_key()) + */ + bool find_core(bucket const * bt, unsigned s_idx, key const & k, unsigned & idx) const { + if (s_idx >= bt->size()) { + idx = s_idx; + return false; + } + int low = s_idx; + int high = bt->size() - 1; + for (;;) { + int mid = low + ((high - low) / 2); + entry const & mid_entry = bt->get(mid); + if (gt(k, mid_entry.end_key())) { + low = mid + 1; + if (low > high) { + idx = static_cast(mid) + 1; + SASSERT(idx >= s_idx); + SASSERT(idx == s_idx || lt(bt->get(idx - 1).end_key(), k)); + SASSERT(idx == bt->size() || lt(k, bt->get(idx).begin_key())); + return false; + } + + } + else if (lt(k, mid_entry.begin_key())) { + high = mid - 1; + if (low > high) { + idx = static_cast(mid); + SASSERT(idx >= s_idx); + SASSERT(idx == s_idx || lt(bt->get(idx - 1).end_key(), k)); + SASSERT(idx == bt->size() || lt(k, bt->get(idx).begin_key())); + return false; + } + } + else { + SASSERT(contains(mid_entry, k)); + SASSERT(mid >= 0); + idx = static_cast(mid); + SASSERT(idx >= s_idx); + return true; + } + } + } + + /** + \brief Search for a key in a bucket using binary search. + + Return true if k was found in the bucket and store the index of + the entry containing \c k in \c idx. + + Otherwise, return false and \c idx will contain the index + s.t. + (idx == 0 || entry[idx-1].end_key() < k) && (idx == bt->size() || k < entry[idx].begin_key() + */ + bool find_core(bucket const * bt, key const & k, unsigned & idx) const { + bool r = find_core(bt, 0, k, idx); + SASSERT(!r || contains(bt->get(idx), k)); + SASSERT( r || idx == 0 || lt(bt->get(idx - 1).end_key(), k)); + SASSERT( r || idx == bt->size() || lt(k, bt->get(idx).begin_key())); + return r; + } + + /** + \brief Search for the given key in the interval skip list. + Return true if the key is stored in the list, and store the location of the entry that contains the k in the + pair (bt, idx). The location should be read as the key is in the entry idx of the bucket bt. + Otherwise, returns false and (bt, idx) contains: + Case 1: bt != 0 && 0 < idx < bt->size() && bt->get(idx-1).end_key() < k && k < bt->get(idx).begin_key() + Case 2: bt != 0 && idx == 0 && (pred_bucket(bt) == m_header || last_key(pred_bucket(bt)) < k) && k < first_key(bt) + Case 3: bt == 0 && idx == 0 && k is greater than all keys in the list. + bt != m_header + + Even when find_core returns false, the pair (bt, idx) can be used to create an iterator object + to traverse keys >= k. + */ + template + bool find_core(key const & k, bucket * & bt, unsigned & idx, bucket * pred_vect[]) const { + bucket * curr = this->m_header; + unsigned i = this->m_header->level(); + bucket * next; + while (i > 0) { + i--; + for (;;) { + next = curr->get_next(i); + if (next != 0 && lt(first_key(next), k)) + curr = next; + else + break; + } + if (UpdatePredVect) + pred_vect[i] = curr; + } + + SASSERT(next == curr->get_next(0)); + + // the search_key must be in the current bucket, or in the first entry of the next bucket (if the next bucket is not 0). + SASSERT(curr->empty() || lt(first_key(curr), k)); + SASSERT(next == 0 || geq(first_key(next), k)); + DEBUG_CODE({ + if (UpdatePredVect && next != 0) + for (unsigned i = 0; i < next->level(); i++) + SASSERT(pred_vect[i]->get_next(i) == next); + }); + + if (next != 0 && contains(next->first_entry(), k)) { + bt = next; + idx = 0; + return true; + } + + bool r = find_core(curr, k, idx); + if (idx == curr->size()) { + SASSERT(!r); + bt = next; + idx = 0; + } + else { + SASSERT(idx < curr->size()); + bt = curr; + } + SASSERT(bt != this->m_header); + SASSERT((bt == 0 && idx == 0) || (bt != 0 && idx < bt->size())); + SASSERT(!r || contains(bt->get(idx), k)); + SASSERT(r || + // Case 1 + (bt != 0 && 0 < idx && idx < bt->size() && lt(bt->get(idx-1).end_key(), k) && lt(k, bt->get(idx).begin_key())) || + // Case 2 + (bt != 0 && idx == 0 && (this->pred_bucket(bt) == this->m_header || lt(last_key(this->pred_bucket(bt)), k)) && lt(k, first_key(bt))) || + // Case 3 + (bt == 0 && idx == 0) // k is greater than all keys in the list. + ); + return r; + } + + /** + \brief Return true if the two entries (that satisfy lt(e1, e2)) can be merged. + */ + bool can_be_merged(entry const & e1, entry const & e2) const { + return val_eq(e1.val(), e2.val()) && eq(succ(e1.end_key()), e2.begin_key()); + } + + /** + \brief Try to merge the last entry with bt with the first entry of its successor. + + \remark pred_vect contains the predecessors of the successor of bt. + */ + void merge_first_of_succ_if_possible(manager & m, bucket * bt, bucket * pred_vect[]) { + SASSERT(check_pred_vect(bt->get_next(0), pred_vect)); + bucket * next_bucket = bt->get_next(0); + if (next_bucket != 0) { + entry & curr_entry = bt->last_entry(); + entry & next_entry = next_bucket->get(0); + if (can_be_merged(curr_entry, next_entry)) { + curr_entry.set_end_key(next_entry.end_key()); + del_entry(m, next_bucket, 0); // del_entry invokes dec_ref_eh + if (next_bucket->empty()) + del_bucket(m, next_bucket, pred_vect); + } + } + } + + /** + \brief Try to merge the entry at position idx with the next entry if possible. + */ + void merge_next_if_possible(manager & m, bucket * bt, unsigned idx, bucket * pred_vect[]) { + SASSERT(!bt->empty()); + if (idx + 1 == bt->size()) { + // it is the last element + merge_first_of_succ_if_possible(m, bt, pred_vect); + } + else { + entry & curr_entry = bt->get(idx); + entry & next_entry = bt->get(idx+1); + if (can_be_merged(curr_entry, next_entry)) { + curr_entry.set_end_key(next_entry.end_key()); + del_entry(m, bt, idx+1); // del_entry invokes dec_ref_eh + } + } + } + + /** + \brief Try to merge the entry at position idx with the previous entry if possible. + + \remark This method assumes that idx > 0. + */ + void merge_prev_if_possible(manager & m, bucket * bt, unsigned idx) { + SASSERT(idx > 0); + entry & curr_entry = bt->get(idx); + entry & prev_entry = bt->get(idx-1); + if (can_be_merged(prev_entry, curr_entry)) { + prev_entry.set_end_key(curr_entry.end_key()); + del_entry(m, bt, idx); // del_entry invokes dec_ref_eh + } + } + + /** + \brief Delete entries starting at indices [s_idx, e_idx), assuming e_idx < bt->size() + + \remark The pre-condition guarantees that the bucket will not be empty after the entries + are deleted. + + \remark If RECYCLE_ENTRY is true, then method will try to recycle position s_idx if it is deleted, and will return true. + Position s_idx will be an empty slot in this case. + */ + template + bool del_entries(manager & m, bucket * bt, unsigned s_idx, unsigned e_idx) { + bool result = false; + if (RECYCLE_ENTRY && s_idx + 1 < e_idx) { + // The position s_idx will be recycled, but the reference to the value stored there + // will be lost. + this->dec_ref(m, bt->get(s_idx).val()); + s_idx++; + result = true; + } + TRACE("del_entries_upto_bug", this->display(tout, bt); tout << "[" << s_idx << ", " << e_idx << ")\n";); + SASSERT(e_idx >= s_idx); + SASSERT(e_idx < bt->size()); + // move entries + unsigned num_removed = e_idx - s_idx; + entry * dest_it = bt->get_entries() + s_idx; + entry * source_it = bt->get_entries() + e_idx; + entry * source_end = bt->get_entries() + bt->size(); + if (Traits::ref_count) { + // invoke dec_ref_eh for entries between dest_it and source_it, since they are being removed + entry * it = dest_it; + for (; it < source_it; ++it) { + this->dec_ref(m, it->val()); + } + } + for (; source_it < source_end; ++dest_it, ++source_it) { + *dest_it = *source_it; + } + // update size + bt->shrink(num_removed); + SASSERT(!bt->empty()); + TRACE("del_entries_upto_bug", this->display(tout, bt);); + return result; + } + + /** + \brief Delete all keys (starting at position s_idx) in the given bucket that are <= k. + The method assumes that k < bt->last_key(). + This condition guarantees that the bucket will not be empty after removing the keys. + + + \remark If RECYCLE_ENTRY is true, then method will try to recycle position s_idx if it is deleted, and will return true. + Position s_idx will be an empty slot in this case. + */ + template + bool del_last_entries_upto(manager & m, bucket * bt, unsigned s_idx, key const & k) { + SASSERT(s_idx < bt->size()); + SASSERT(lt(k, last_key(bt))); + int low = s_idx; + int high = bt->size() - 1; + SASSERT(low <= high); + for (;;) { + int mid = low + ((high - low) / 2); + SASSERT(mid < static_cast(bt->size())); + entry & mid_entry = bt->get(mid); + if (gt(k, mid_entry.end_key())) { + low = mid + 1; + if (low > high) { + // mid entry must be deleted since k > mid_entry.end_key(). + TRACE("del_entries_upto_bug", tout << "exit 1) mid: " << mid << "\n"; this->display(tout, mid_entry); tout << "\n";); + SASSERT(mid + 1 < static_cast(bt->size())); // Reason: method pre-condition: lt(k, last_key(bt)) + return del_entries(m, bt, s_idx, mid + 1); + } + } + else if (lt(k, mid_entry.begin_key())) { + high = mid - 1; + if (low > high) { + // mid entry must not be deleted since k < mid_entry.begin_key(). + TRACE("del_entries_upto_bug", tout << "exit 2) mid: " << mid << "\n"; this->display(tout, mid_entry); tout << "\n";); + SASSERT(mid < static_cast(bt->size())); // Reason: loop invariant + return del_entries(m, bt, s_idx, mid); + } + } + else { + SASSERT(contains(mid_entry, k)); + if (lt(k, mid_entry.end_key())) { + TRACE("del_entries_upto_bug", tout << "exit 3) mid: " << mid << "\n"; this->display(tout, mid_entry); tout << "\n";); + mid_entry.set_begin_key(succ(k)); + SASSERT(mid < static_cast(bt->size())); // Reason: loop invariant + return del_entries(m, bt, s_idx, mid); + } + else { + // mid_entry should also be removed + TRACE("del_entries_upto_bug", tout << "exit 4) mid: " << mid << "\n"; this->display(tout, mid_entry); tout << "\n";); + SASSERT(mid + 1 < static_cast(bt->size())); // Reason: method pre-condition: lt(k, last_key(bt)) + return del_entries(m, bt, s_idx, mid + 1); + } + } + } + } + + /** + \brief Keep deleting keys <= k in bt and its successors. + */ + void del_entries_upto_loop(manager & m, bucket * bt, key const & k, bucket * pred_vect []) { + SASSERT(check_pred_vect(bt, pred_vect)); + while (bt != 0) { + key const & bt_last_key = last_key(bt); + if (lt(k, bt_last_key)) { + del_last_entries_upto(m, bt, 0, k); + return; + } + else if (eq(k, bt_last_key)) { + del_bucket(m, bt, pred_vect); + return; + } + else { + SASSERT(gt(k, bt_last_key)); + bucket * next = bt->get_next(0); + del_bucket(m, bt, pred_vect); + bt = next; + // continue deleting... + } + } + } + + /** + \brief Delete entries starting at position 0 such that keys are <= k. + + If INSERT == true, then try to save/recycle an entry. Return true, if + managed to recycle the entry. + + The bucket bt may be deleted when INSERT==false and k >= last_key(bt). + + - pred_vect must contain the predecessors of bt. + + - next_pred_vect is an uninitialized predecessor vector. It may be initialized + when INSERT == true. If needed it is initialized using + update_predecessor_vector(pred_vect, bt, next_pred_vect); + */ + template + bool del_entries_upto(manager & m, bucket * bt, key const & k, bucket * pred_vect[], bucket * next_pred_vect[]) { + SASSERT(check_pred_vect(bt, pred_vect)); // pred_vect contains the predecessors of bt. + if (lt(k, first_key(bt))) { + // nothing to be done... + return false; // didn't manage to recycle entry. + } + + key const & bt_last_key = last_key(bt); + TRACE("del_entries_upto_bug", tout << "bt_last_key: " << bt_last_key << "\n";); + if (lt(k, bt_last_key)) { + return del_last_entries_upto(m, bt, 0, k); + } + else { + if (INSERT) { + // Invoke DEC-REF for all entries in bt + this->dec_ref(m, bt); + // REMARK: the slot 0 will be reused, but the element there is gone. + bt->set_size(1); + if (gt(k, bt_last_key)) { + bucket * next = bt->get_next(0); + if (next != 0) { + update_predecessor_vector(pred_vect, bt, next_pred_vect); + del_entries_upto_loop(m, next, k, next_pred_vect); + } + } + return true; // recycled entry. + } + else { + bucket * next = bt->get_next(0); + del_bucket(m, bt, pred_vect); // it will invoke dec_ref_eh for all values in bt. + // pred_vect does not need to be updated since it contains the predecessors of + // bt, since bt was deleted they are now the predecessors of its successor. + if (next != 0) { + del_entries_upto_loop(m, next, k, pred_vect); + } + return false; // don't care in this case, since it is not an insertion. + } + } + } + + /** + \brief Delete entries starting at position s_idx (> 0) such that keys are <= k. + The bucket bt cannot be deleted since s_idx > 0. + + If INSERT == true, then try to save/recycle an entry. Return true, if + managed to recycle the entry. + + - pred_vect must contain the predecessors of bt->get_next(0). + */ + template + bool del_entries_upto(manager & m, bucket * bt, unsigned s_idx, key const & k, bucket * pred_vect[]) { + SASSERT(check_pred_vect(bt->get_next(0), pred_vect)); // pred_vect contains the predecessors of the successor of bt. + SASSERT(s_idx > 0); + TRACE("del_entries_upto_bug", + tout << "INSERT: " << INSERT << "\n"; + tout << "del_entries_upto, s_idx: " << s_idx << ", k: " << k << "\n"; + this->display(tout, bt); + tout << "\n"; + this->display_predecessor_vector(tout, pred_vect);); + + if (s_idx >= bt->size()) { + // nothing to do in bt, moving to successors... + del_entries_upto_loop(m, bt->get_next(0), k, pred_vect); + return false; // didn't manage to recycle an entry + } + + if (lt(k, bt->get(s_idx).begin_key())) { + // nothing to be done... + return false; // didn't manage to recycle an entry + } + + key const & bt_last_key = last_key(bt); + TRACE("del_entries_upto_bug", tout << "bt_last_key: " << bt_last_key << "\n";); + if (lt(k, bt_last_key)) { + return del_last_entries_upto(m, bt, s_idx, k); + } + else { + if (gt(k, bt_last_key)) { + del_entries_upto_loop(m, bt->get_next(0), k, pred_vect); + } + if (Traits::ref_count) { + // Invoke dec_ref_eh for all values in [s_idx, bt->size()) + unsigned sz = bt->size(); + for (unsigned i = s_idx; i < sz; i++) + this->dec_ref(m, bt->get(i).val()); + } + if (INSERT) { + SASSERT(s_idx < bt->size()); + bt->set_size(s_idx + 1); + return true; // recycled an entry + + } + else { + bt->set_size(s_idx); + return false; // don't care. it is not an insertion. + } + } + } + + /** + \brief Insert entry [b,e]->v in the beginning of the bucket bt. + */ + void insert_begin(manager & m, bucket * bt, key const & b, key const & e, value const & v, bucket * pred_vect[]) { + TRACE("interval_skip_list_bug", tout << "insert_begin: [" << b << ", " << e << "] -> " << v << "\n"; this->display(tout, bt);); + SASSERT(check_pred_vect(bt, pred_vect)); + SASSERT(!bt->empty()); + SASSERT(bt->size() <= bt->capacity()); + SASSERT(leq(b, first_key(bt))); + bucket * next_pred_vect[Traits::max_level]; + next_pred_vect[0] = 0; + + this->inc_ref(m, v); + + // Delete entries that will be overlapped by new entry. + // Try to reuse a slot that was deleted... + bool recycled = del_entries_upto(m, bt, e, pred_vect, next_pred_vect); + if (recycled) { + set_entry(bt, 0, b, e, v); // Reference to v was stored, and inc_ref_eh was invoked above. + TRACE("interval_skip_list_bug", this->display_physical(tout);); + if (next_pred_vect[0] != 0) { + // the vector next_pred_vect was initialized by del_entries_upto. + merge_next_if_possible(m, bt, 0, next_pred_vect); + } + else { + update_predecessor_vector(pred_vect, bt); + merge_next_if_possible(m, bt, 0, pred_vect); + } + return; + } + // check if can merge with first entry in the bucket. + entry & fe = bt->first_entry(); + if (val_eq(fe.val(), v) && eq(fe.begin_key(), succ(e))) { + // can merge + fe.set_begin_key(b); + // A new reference to v was not created. So, we must invoke dec_ref_eh since we increased the counter above. + this->dec_ref(m, v); + return; + } + // Is there space for the new entry? + if (bt->size() == bt->capacity()) { + if (bt->capacity() < Traits::max_capacity) { + SASSERT(this->first_bucket() == bt && this->first_bucket()->get_next(0) == 0); + expand_first_bucket(m); + bt = this->first_bucket(); + } + else { + // there is no space + splice(m, bt, pred_vect); + } + } + open_space(bt, 0); + set_entry(bt, 0, b, e, v); // Reference to v was stored, and inc_ref_eh was invoked above. + SASSERT(!can_be_merged(bt->get(0), bt->get(1))); + } + + /** + \brief Insert the entry [b, e]->v at position idx. + */ + void insert_at(manager & m, bucket * bt, unsigned idx, key const & b, key const & e, value const & v, bucket * pred_vect[]) { + SASSERT(idx > 0); + SASSERT(check_pred_vect(bt->get_next(0), pred_vect)); + + this->inc_ref(m, v); + TRACE("insert_at_bug", tout << "before del_entries_upto:\n"; this->display_physical(tout);); + + bool recycled = del_entries_upto(m, bt, idx, e, pred_vect); + + TRACE("insert_at_bug", tout << "after del_entries_upto:\n"; this->display_physical(tout);); + + if (recycled) { + set_entry(bt, idx, b, e, v); // Reference to v was stored, and inc_ref_eh was invoked above. + merge_next_if_possible(m, bt, idx, pred_vect); + merge_prev_if_possible(m, bt, idx); + TRACE("insert_at_bug", tout << "using recycled:\n"; this->display_physical(tout);); + return; + } + + // Is there space for the new entry? + if (bt->size() == bt->capacity()) { + // there is no space + if (bt->capacity() < Traits::max_capacity) { + SASSERT(this->first_bucket() == bt && this->first_bucket()->get_next(0) == 0); + expand_first_bucket(m); + bt = this->first_bucket(); + // there is no need to update pred_vect, since the list contains only one bucket. + } + else { + splice(m, bt, pred_vect); + bucket * new_next = bt->get_next(0); + SASSERT(bt->size() == bt->capacity()/2); + if (idx == bt->capacity()/2) { + entry & bt_last_entry = bt->last_entry(); + if (val_eq(bt_last_entry.val(), v) && eq(bt_last_entry.end_key(), pred(b))) { + // merged with the last key of bt + bt_last_entry.set_end_key(e); + // A new reference to v was not created. So, we must invoke dec_ref_eh since we increased the counter above. + this->dec_ref(m, v); + return; + } + entry & new_next_first_entry = new_next->first_entry(); + if (val_eq(new_next_first_entry.val(), v) && eq(new_next_first_entry.begin_key(), succ(e))) { + // merged with the first key of new_next + new_next_first_entry.set_begin_key(b); + // A new reference to v was not created. So, we must invoke dec_ref_eh since we increased the counter above. + this->dec_ref(m, v); + return; + } + // insert in the end of bt. + bt->set_size(bt->capacity()/2 + 1); + set_entry(bt, bt->capacity()/2, b, e, v); // Reference to v was stored, and inc_ref_eh was invoked above. + return; + } + else if (idx > bt->capacity()/2) { + idx -= bt->capacity()/2; + SASSERT(idx > 0); + bt = new_next; + update_predecessor_vector(pred_vect, bt); + } + } + } + SASSERT(idx > 0); + open_space(bt, idx); + set_entry(bt, idx, b, e, v); // Reference to v was stored, and inc_ref_eh was invoked above. + merge_next_if_possible(m, bt, idx, pred_vect); + merge_prev_if_possible(m, bt, idx); + TRACE("insert_at_bug", tout << "using open-space:\n"; this->display_physical(tout);); + } + + /** + \brief Insert the entry [b,e]->v into the bucket bt. + + pred_vect contains the predecessors of the successor of bt (i.e., bt->get_next(0)) + */ + void insert_inside(manager & m, bucket * bt, key const & b, key const & e, value const & v, bucket * pred_vect[]) { + TRACE("interval_skip_list_bug", tout << "insert_inside: [" << b << ", " << e << "] -> " << v << "\n";); + SASSERT(check_pred_vect(bt->get_next(0), pred_vect)); + SASSERT(!bt->empty()); + SASSERT(bt->size() <= bt->capacity()); + // perform binary search to find position to insert [b, e]->v + int low = 0; + int high = bt->size() - 1; + for (;;) { + int mid = low + ((high - low) / 2); + entry & mid_entry = bt->get(mid); + if (gt(b, mid_entry.end_key())) { + low = mid + 1; + if (low > high) { + // insert after mid_entry since b > mid_entry.end_key(). + insert_at(m, bt, mid+1, b, e, v, pred_vect); + return; + } + } + else if (lt(b, mid_entry.begin_key())) { + high = mid - 1; + if (low > high) { + // insert before mid_entry since b < mid_entry.begin_key(). + SASSERT(mid > 0); // Reason: insert_begin would have been called instead. + insert_at(m, bt, mid, b, e, v, pred_vect); + return; + } + } + else { + SASSERT(contains(mid_entry, b)); + TRACE("insert_inside_bug", tout << "insert_inside:\n"; this->display(tout, bt);); + if (val_eq(mid_entry.val(), v)) { + if (gt(e, mid_entry.end_key())) { + // No need to create space. + // We did not create a new reference to v. + mid_entry.set_end_key(e); + del_entries_upto(m, bt, mid+1, e, pred_vect); + merge_next_if_possible(m, bt, mid, pred_vect); + return; + } + } + else { + if (gt(b, mid_entry.begin_key())) { + if (lt(e, mid_entry.end_key())) { + // New interval is the middle of existing interval + + // We must INVOKE add_ref_eh for mid_entry.val() and v. + this->inc_ref(m, v); + this->inc_ref(m, mid_entry.val()); // mid_entry was split in two. + + // we need two new entries. + if (bt->size() >= bt->capacity() - 1) { + if (bt->capacity() < Traits::max_capacity) { + SASSERT(this->first_bucket() == bt && this->first_bucket()->get_next(0) == 0); + expand_first_bucket(m); + bt = this->first_bucket(); + } + else { + splice(m, bt, pred_vect); + int new_sz = bt->size(); + bucket * new_next = bt->get_next(0); + if (mid >= new_sz) { + mid -= new_sz; + SASSERT(mid >= 0); + bt = new_next; + } + } + } + open_2spaces(bt, mid); + entry & mid1_entry = bt->get(mid); + entry & new_entry = bt->get(mid+1); + entry & mid2_entry = bt->get(mid+2); + mid2_entry = mid1_entry; + mid1_entry.set_end_key(pred(b)); + new_entry.set_begin_key(b); + new_entry.set_end_key(e); + new_entry.set_val(v); + mid2_entry.set_begin_key(succ(e)); + } + else { + mid_entry.set_end_key(pred(b)); + insert_at(m, bt, mid+1, b, e, v, pred_vect); + } + } + else { + SASSERT(eq(b, mid_entry.begin_key())); + SASSERT(mid > 0); // Reason: insert_begin would have been called instead. + insert_at(m, bt, mid, b, e, v, pred_vect); + } + } + return; + } + } + } + + /** + \brief Remove [b,e]->v from the beginning of the bucket bt. + */ + void remove_begin(manager & m, bucket * bt, key const & b, key const & e, bucket * pred_vect[]) { + TRACE("interval_skip_list_bug", tout << "remove_begin: [" << b << ", " << e << "]\n";); + SASSERT(!bt->empty()); + SASSERT(pred_vect[0]->get_next(0) == bt); + del_entries_upto(m, bt, e, pred_vect, 0); + } + + /** + \brief Remove [b,e]->v from the bucket bt. + */ + void remove_inside(manager & m, bucket * bt, key const & b, key const & e, bucket * pred_vect[]) { + TRACE("interval_skip_list_bug", tout << "remove_inside: [" << b << ", " << e << "]\n";); + // perform binary search to find position to insert [b, e]->v + int low = 0; + int high = bt->size() - 1; + for (;;) { + int mid = low + ((high - low) / 2); + entry & mid_entry = bt->get(mid); + if (gt(b, mid_entry.end_key())) { + low = mid + 1; + if (low > high) { + // insert after mid_entry since b > mid_entry.end_key(). + del_entries_upto(m, bt, mid+1, e, pred_vect); + return; + } + } + else if (lt(b, mid_entry.begin_key())) { + high = mid - 1; + if (low > high) { + // insert before mid_entry since b < mid_entry.begin_key(). + SASSERT(mid > 0); // Reason: remove_begin would have been called instead. + del_entries_upto(m, bt, mid, e, pred_vect); + return; + } + } + else { + SASSERT(contains(mid_entry, b)); + if (gt(b, mid_entry.begin_key())) { + if (lt(e, mid_entry.end_key())) { + // The removed interval is inside of an existing interval. + + // mid_entry will be split in two. So, we must invoke add_ref_eh for mid_entry.val() + this->inc_ref(m, mid_entry.val()); + + // We need to break mid_entry in two parts. + if (bt->size() == bt->capacity()) { + if (bt->capacity() < Traits::max_capacity) { + SASSERT(this->first_bucket() == bt && this->first_bucket()->get_next(0) == 0); + expand_first_bucket(m); + bt = this->first_bucket(); + SASSERT(bt->size() < bt->capacity()); + } + else { + splice(m, bt, pred_vect); + if (mid >= static_cast(bt->size())) { + // mid_entry moved to new (successor) bucket + mid -= bt->size(); + bt = bt->get_next(0); + } + } + } + open_space(bt, mid); + entry & mid1_entry = bt->get(mid); + entry & mid2_entry = bt->get(mid+1); + mid1_entry.set_end_key(pred(b)); + mid2_entry.set_begin_key(succ(e)); + } + else { + mid_entry.set_end_key(pred(b)); + del_entries_upto(m, bt, mid+1, e, pred_vect); + } + } + else { + SASSERT(eq(b, mid_entry.begin_key())); + SASSERT(mid > 0); // Reason: remove_begin would have been called instead. + del_entries_upto(m, bt, mid, e, pred_vect); + } + return; + } + } + } + +public: + interval_skip_list() { + } + + interval_skip_list(manager & m):skip_list_base(m) { + } + + ~interval_skip_list() { + } + + /** + \brief Copy the elements of other. + This method assumes that the *this* skip-list is empty. + */ + void copy(manager & m, interval_skip_list const & other) { + SASSERT(this->empty()); + other.clone_core(m, this); + } + + /** + \brief Return the smallest key stored in the interval skip list. + */ + key const & smallest() const { + SASSERT(!this->empty()); + return this->first_bucket()->get(0).begin_key(); + } + + /** + \brief Search for the given key in the interval skip list. + Return true if the key is stored in the list, and store the associated value in \c v. + */ + bool contains(key const & k, value & v) const { + bucket * bt; + unsigned idx; + if (find_core(k, bt, idx, 0)) { + v = bt->get(idx).val(); + return true; + } + return false; + } + + /** + \brief Alias for #contains. + */ + bool find(key const & k, value & v) const { + return contains(k, v); + } + +private: + /** + \brief Search for a bucket based on the key \c k. + + curr, next and pred_vect are output arguments. + + pred_vect must be an array of size level(). + + Post-conditions: + + pred_vect contains the predecessors of next. + That is, pred_vect[i] is the predecessor of level i. + + next is the successor of curr. + + pred_vect[0] == curr. + + curr == m_header || first_key(curr) < k + + next == 0 || k <= first_key(next) + */ + void find_bucket(key const & k, bucket * & curr, bucket * & next, bucket * pred_vect[]) { + SASSERT(this->level() > 0); + curr = this->m_header; + unsigned i = curr->level(); + SASSERT(i > 0); + while (i > 0) { + i--; + for (;;) { + next = curr->get_next(i); + if (next != 0 && lt(first_key(next), k)) + curr = next; + else + break; + } + pred_vect[i] = curr; + } + + SASSERT(next == curr->get_next(0)); + SASSERT(pred_vect[0] == curr); + DEBUG_CODE({ + if (next != 0) + for (unsigned i = 0; i < next->level(); i++) + SASSERT(pred_vect[i]->get_next(i) == next); + }); + SASSERT(curr == this->m_header || lt(first_key(curr), k)); + SASSERT(next == 0 || leq(k, first_key(next))); + } + +public: + + /** + \brief Insert the entries [i -> v] for every i \in [b, e]. + */ + void insert(manager & m, key const & b, key const & e, value const & v) { + SASSERT(leq(b, e)); + if (this->empty()) { + insert_first_entry(m, b, e, v); + return; + } + + // find the bucket where the new entries should be stored. + + // pred_vect[i] contains a pointer to the rightmost bucket of + // level i or higher that is to the left of the location of + // the insertion. + bucket * pred_vect[Traits::max_level]; + bucket * curr, * next; + find_bucket(b, curr, next, pred_vect); + + if (curr == this->m_header) { + SASSERT(next != 0); + // entry must be inserted in the first bucket. + SASSERT(this->first_bucket() == next); + insert_begin(m, next, b, e, v, pred_vect); + } + else if (next == 0 || gt(first_key(next), b)) { + insert_inside(m, curr, b, e, v, pred_vect); + } + else { + SASSERT(!curr->empty()); + SASSERT(!next->empty()); + SASSERT(next != 0); + SASSERT(eq(first_key(next), b)); + // Bucket curr is the predecessor of next. + SASSERT(curr->get_next(0) == next); + + // check if we can merge with last entry of curr + entry & curr_last_entry = curr->last_entry(); + if (val_eq(curr_last_entry.val(), v) && eq(curr_last_entry.end_key(), pred(b))) { + // No new reference to v was create, we don't need to invok inc_ref_eh + curr_last_entry.set_end_key(e); + del_entries_upto(m, next, e, pred_vect, 0); + merge_first_of_succ_if_possible(m, curr, pred_vect); + return; + } + insert_begin(m, next, b, e, v, pred_vect); + } + } + + /** + \brief Insert key [k->v]. + */ + void insert(manager & m, key const & k, value const & v) { + insert(m, k, k, v); + } + + class push_back_proc; + friend class push_back_proc; + + /** + \brief Functor for efficiently inserting elements in the end of the skip list. + + \remark The context becomes invalid if the skip-list is updated by other methods. + */ + class push_back_proc { + friend class interval_skip_list; + manager & m_manager; + interval_skip_list & m_list; + bucket * m_pred_vect[Traits::max_level]; + + bucket * last_bucket() const { return m_pred_vect[0]; } + + public: + push_back_proc(manager & m, interval_skip_list & l): + m_manager(m), + m_list(l) { + // initialize m_pred_vect + unsigned lvl = m_list.level(); + bucket * curr = m_list.m_header; + bucket * next; + unsigned i = lvl; + while (i > 0) { + i--; + for (;;) { + next = curr->get_next(i); + if (next != 0) + curr = next; + else + break; + } + m_pred_vect[i] = curr; + } + SASSERT(next == 0); + } + + interval_skip_list & list() { + return m_list; + } + + bool empty() const { + return m_list.empty(); + } + + key const & last_key() const { + return last_bucket()->last_entry().end_key(); + } + + void operator()(key const & b, key const & e, value const & v) { + SASSERT(m_list.leq(b, e)); + if (m_list.empty()) { + m_list.insert_first_entry(m_manager, b, e, v); + bucket * new_bucket = m_list.first_bucket(); + skip_list_base::update_predecessor_vector(m_pred_vect, new_bucket); + } + else { + bucket * bt = last_bucket(); + entry & et = bt->last_entry(); + SASSERT(m_list.lt(et.end_key(), b)); + // first check if new entry can be merged with the last entry in the list + if (m_list.val_eq(et.val(), v) && m_list.eq(et.end_key(), m_list.pred(b))) { + // can merge + et.set_end_key(e); + return; + } + // insert in the last bucket + unsigned sz = bt->size(); + if (sz >= bt->capacity()) { + if (bt->capacity() < Traits::max_capacity) { + SASSERT(m_list.first_bucket() == bt && m_list.first_bucket()->get_next(0) == 0); + m_list.expand_first_bucket(m_manager); + bt = m_list.first_bucket(); + SASSERT(bt->size() < bt->capacity()); + skip_list_base::update_predecessor_vector(m_pred_vect, bt); + sz = bt->size(); + } + else { + // last bucket is full... creating new bucket... + unsigned new_bucket_lvl = m_manager.random_level(Traits::max_level); + bucket * new_bucket = interval_skip_list::mk_bucket(m_manager, new_bucket_lvl); + m_list.update_list_level(m_manager, new_bucket_lvl, m_pred_vect); + for (unsigned i = 0; i < new_bucket_lvl; i++) { + SASSERT(m_pred_vect[i]->get_next(i) == 0); + m_pred_vect[i]->set_next(i, new_bucket); + m_pred_vect[i] = new_bucket; + SASSERT(m_pred_vect[i]->get_next(i) == 0); + } + SASSERT(last_bucket() == new_bucket); + bt = new_bucket; + sz = 0; + } + } + SASSERT(sz < bt->capacity()); + m_list.inc_ref(m_manager, v); + bt->expand(1); + interval_skip_list::set_entry(bt, sz, b, e, v); + } + } + }; + + /** + \brief For each i \in [b, e] remove any entry [i->v] if it is in the list. + */ + void remove(manager & m, key const & b, key const & e) { + SASSERT(leq(b, e)); + if (this->empty()) + return; + bucket * pred_vect[Traits::max_level]; + bucket * curr, * next; + + find_bucket(b, curr, next, pred_vect); + + if (curr == this->m_header) { + SASSERT(next != 0); + remove_begin(m, next, b, e, pred_vect); + } + else if (next == 0 || gt(first_key(next), b)) { + remove_inside(m, curr, b, e, pred_vect); + } + else { + SASSERT(next != 0); + SASSERT(eq(first_key(next), b)); + remove_begin(m, next, b, e, pred_vect); + } + } + + /** + \brief Remove entry [k->v] for some v, if it is in the list. + */ + void remove(manager & m, key const & k) { + remove(m, k, k); + } + + /** + \brief Alias for #remove. + */ + void erase(manager & m, key const & b, key const & e) { + remove(m, b, e); + } + + /** + \brief Alias for #remove. + */ + void erase(manager & m, key const & k) { + remove(m, k, k); + } + + /** + \begin Traverse the list applying the functor f. + The functor must have a method + + bool operator()(key const & b, key const & e, value const & v) + + The method will invoke f(b, e, v) whenever the entries [i -> v] for i \in [b, e] are + in the list. + + If the functor returns false, then the traversal is interrupted. + */ + template + void for_each(Functor & f) const { + SASSERT(this->m_header->empty()); + bucket * curr = this->first_bucket(); + while (curr != 0) { + unsigned sz = curr->size(); + for (unsigned i = 0; i < sz; i++) { + entry const & e = curr->get(i); + if (!f(e.begin_key(), e.end_key(), e.val())) + return; + } + curr = curr->get_next(0); + } + } + + /** + \brief Return the next/successor buffer, but skipping buffers that do not contains keys greater than or equal to k. + */ + bucket * next_bucket(bucket const * bt, key const & k) const { + bucket * curr = bt->get_next(0); // move to successor + if (curr == 0) + return 0; + unsigned i = curr->level(); + unsigned max = i; + bucket * next = 0; + while (i > 0) { + --i; + for (;;) { + next = curr->get_next(i); + if (next != 0 && leq(first_key(next), k)) { + TRACE("interval_skip_list", tout << "next_bucket(" << k << "), i: " << i << " skipping #" << get_bucket_idx(curr); + tout << ", moving to: #" << get_bucket_idx(next) << "\n"; this->display(tout, next);); + curr = next; + if (curr->level() > max) { + max = curr->level(); + i = curr->level(); + TRACE("interval_skip_list", tout << "max: " << max << ", curr->level(): " << curr->level() << ", i: " << i << "\n";); + break; + } + } + else { + break; + } + } + } + SASSERT(i == 0); + SASSERT(curr->get_next(0) == next); + SASSERT(next == 0 || lt(k, first_key(next))); + return curr; + } + + class iterator; + friend class iterator; + + class iterator { + interval_skip_list const * m_list; + bucket const * m_curr; + unsigned m_idx; + public: + iterator():m_list(0), m_curr(0), m_idx(0) {} + iterator(interval_skip_list const * l, bucket const * b = 0, unsigned idx = 0):m_list(l), m_curr(b), m_idx(idx) {} + entry const & operator*() const { return m_curr->get(m_idx); } + entry const * operator->() const { return &(operator*()); } + iterator & operator++() { + SASSERT(m_curr); + m_idx++; + if (m_idx >= m_curr->size()) { + m_idx = 0; + m_curr = m_curr->get_next(0); + } + return *this; + } + iterator operator++(int) { iterator tmp = *this; ++*this; return tmp; } + + bool at_end() const { + return m_curr == 0; + } + + /** + \brief Move the iterator to the next entry of the form ([b, e] -> v) s.t. + + 1) k in [b, e], or + 2) b > k and for every entry ([b',e']->v') between the old current entry and ([b,e]->v), we + have k > e' + + If such entry does not exist, then the iterator is moved to the end. + That is, at_end() returns true. + */ + void move_to(key const & k) { + SASSERT(m_curr); + SASSERT(m_idx < m_curr->size()); + entry const & curr_entry = m_curr->get(m_idx); + if (m_list->gt(k, curr_entry.end_key())) { + m_list->find_core(m_curr, m_idx+1, k, m_idx); + if (m_idx < m_curr->size()) + return; // found new position + SASSERT(m_idx == m_curr->size()); + m_curr = m_list->next_bucket(m_curr, k); + if (m_curr != 0) { + // search for k in the current buffer. + m_list->find_core(m_curr, k, m_idx); + if (m_idx == m_curr->size()) { + // k is greater than all keys in the list. + m_curr = 0; + m_idx = 0; + } + } + else { + SASSERT(m_curr == 0); + m_idx = 0; + } + } + } + + bool operator==(iterator const & it) const { + SASSERT(m_list == it.m_list); + return m_curr == it.m_curr && m_idx == it.m_idx; + } + + bool operator!=(iterator const & it) const { + SASSERT(m_list == it.m_list); + return m_curr != it.m_curr; + } + + /** + \brief Take the ownership of the current value and reset it to 0. + + \warning This method should be used with extreme care, since it puts the interval skip list + in an inconsistent state that must be restored. This method should be only used by developers + familiar with the interval skip-lists internal invariants. + For users not familiar with internal invariants, they should only use the reset method in the skip list class + after this method is invoked. + + Store in r the current value. + */ + template + void take_curr_ownership(manager & m, ObjRef & r) { + SASSERT(!at_end()); + entry & curr = const_cast(operator*()); // <<< HACK + r = curr.val(); + if (Traits::ref_count) + m.dec_ref_eh(curr.val()); + curr.set_val(0); + } + }; + + iterator begin() const { return iterator(this, this->first_bucket()); } + + iterator end() const { return iterator(this); } + + /** + \brief Return an iterator starting at the first entry that contains k. + If the skip-list does not contain k, then return the "end()" iterator. + */ + iterator find(key const & k) const { + bucket * bt; + unsigned idx; + if (find_core(k, bt, idx, 0)) { + return iterator(this, bt, idx); + } + else { + return end(); + } + } + + iterator find_geq(key const & k) const { + bucket * bt; + unsigned idx; + find_core(k, bt, idx, 0); + return iterator(this, bt, idx); + } + + /** + \brief Return true if the skip lists are equal. + */ + bool is_equal(interval_skip_list const & other) const { + iterator it1 = begin(); + iterator end1 = end(); + iterator it2 = other.begin(); + iterator end2 = other.end(); + for (; it1 != end1 && it2 != end2; it1++, it2++) { + entry const & e1 = *it1; + entry const & e2 = *it2; + if (!eq(e1.begin_key(), e2.begin_key())) + return false; + if (!eq(e1.end_key(), e2.end_key())) + return false; + if (!val_eq(e1.val(), e2.val())) + return false; + } + return true; + } + + /** + \brief Update the values stored in the skip-list by appling the given + functor to them. The functor must provide the operation: + + value operator()(value const & v); + + The functor must be injective. That is + + x != y implies f(x) != f(y) + + If a non-injective functor is used, then the resultant skip-list may + not be in a consistent state. + */ + template + void update_values(manager & m, InjectiveFunction & f) { + if (!this->empty()) { + iterator it = begin(); + iterator it_end = end(); + for (; it != it_end; ++it) { + entry & curr = const_cast(*it); + value const & old_val = curr.val(); + value new_val = f(old_val); + inc_ref(m, new_val); + dec_ref(m, old_val); + curr.set_val(new_val); + } + SASSERT(check_invariant()); + } + } + + class ext_iterator; + friend class ext_iterator; + + class ext_iterator { + friend class interval_skip_list; + + interval_skip_list * m_list; + bucket * m_curr; + unsigned m_idx; + bucket * m_pred_vect[Traits::max_level]; + + void move_next_bucket() { + m_list->update_predecessor_vector(m_pred_vect, m_curr); + m_idx = 0; + m_curr = m_curr->get_next(0); + } + + public: + ext_iterator():m_list(0), m_curr(0), m_idx(0) {} + + entry const & operator*() const { return m_curr->get(m_idx); } + + entry const * operator->() const { return &(operator*()); } + + ext_iterator & operator++() { + SASSERT(m_curr); + m_idx++; + if (m_idx >= m_curr->size()) + move_next_bucket(); + return *this; + } + + ext_iterator operator++(int) { ext_iterator tmp = *this; ++*this; return tmp; } + + bool at_end() const { + return m_curr == 0; + } + + bool operator==(ext_iterator const & it) const { + return m_curr == it.m_curr && m_idx == it.m_idx; + } + + bool operator!=(ext_iterator const & it) const { + return m_curr != it.m_curr; + } + + void erase(manager & m) { + SASSERT(!at_end()); + SASSERT(m_curr->size() > 0); + if (m_curr->size() > 1) { + m_list->del_entry(m, m_curr, m_idx); + if (m_idx >= m_curr->size()) + move_next_bucket(); + } + else { + SASSERT(m_curr->size() == 1); + bucket * old_curr = m_curr; + m_curr = m_curr->get_next(0); + m_list->del_bucket(m, old_curr, m_pred_vect); + } + } + }; + + void move_begin(ext_iterator & it) { + if (!this->empty()) { + it.m_list = this; + it.m_curr = this->first_bucket(); + it.m_idx = 0; + unsigned lvl = this->level(); + for (unsigned i = 0; i < lvl; i++) + it.m_pred_vect[i] = this->m_header; + } + else { + it.m_curr = 0; + it.m_idx = 0; + } + } + + void move_geq(ext_iterator & it, key const & k) { + it.m_list = this; + find_core(k, it.m_curr, it.m_idx, it.m_pred_vect); + } + +private: + /** + \brief Auxiliary data-structure used to implement the join of two interval_skip_lists. + To implement an efficient join, we want to be able to skip as many entries as possible. + */ + struct join_state { + bucket * m_bucket; + unsigned m_entry_idx; + key m_head; // it it a key in [m_bucket->m_entries[m_entry_idx].begin_key(), m_bucket->m_entries[m_entry_idx].end_key()] + public: + join_state(bucket * bt): + m_bucket(bt), + m_entry_idx(0), + m_head(bt->first_entry().begin_key()) { + } + + bool done() const { + return m_bucket == 0; + } + + key const & head() const { + SASSERT(!done()); + return m_head; + } + + key const & tail() const { + SASSERT(!done()); + SASSERT(m_entry_idx < m_bucket->size()); + return m_bucket->get(m_entry_idx).end_key(); + } + + value const & val() const { + SASSERT(!done()); + return m_bucket->get(m_entry_idx).val(); + } + }; + + /** + \brief Create a join_state auxiliary data-structure for performing a join starting at key k. + */ + join_state mk_join_state(key const & k) const { + return join_state(next_bucket(this->m_header, k)); + } + + /** + \brief Move the join_state towards k. + */ + void move_js(join_state & js, key const & k) const { + SASSERT(!js.done()); + if (leq(k, js.tail())) { + // We can't skip the current entry, because k in inside it. + // So, we just update the head. + js.m_head = k; + } + else { + // Moving to the next entry. + js.m_entry_idx++; + if (js.m_entry_idx < js.m_bucket->size()) { + // Update js.m_head with the beginning of the next entry. + js.m_head = js.m_bucket->get(js.m_entry_idx).begin_key(); + } + else { + // We processed all entries in the current bucket. So, set state to js.m_move_next. + js.m_bucket = next_bucket(js.m_bucket, k); + js.m_entry_idx = 0; + if (js.m_bucket != 0) + js.m_head = first_key(js.m_bucket); + } + } + } + +public: + + /** + \brief Join two interval_skip_lists and apply the given functor in the process. + + The functor must have a method + + bool operator()(key const & b, key const & e, value const & v1, value const & v2) + + The method will invoke f(b, e, v1, v2) whenever forall i \in [b, e] entries [i -> v1] are in the *this* list, and [i->v2] in the *other* list. + */ + template + void join(interval_skip_list const & other, Functor & f) { + if (this->empty() || other.empty()) + return; + key const & f1 = smallest(); + key const & f2 = other.smallest(); + key const & smallest_key = leq(f1, f2) ? f1 : f2; + join_state s1 = mk_join_state(smallest_key); + join_state s2 = other.mk_join_state(smallest_key); + while (!s1.done() && !s2.done()) { + key const & h1 = s1.head(); + key const & h2 = s2.head(); + if (eq(h1, h2)) { + key const & t1 = s1.tail(); + key const & t2 = s2.tail(); + key const & t = leq(t1, t2) ? t1 : t2; + f(h1, t, s1.val(), s2.val()); + key next_key = succ(t); + move_js(s1, next_key); + move_js(s2, next_key); + } + else if (lt(h1, h2)) { + move_js(s1, h2); + } + else { + SASSERT(lt(h2, h1)); + move_js(s2, h1); + } + } + } + +#ifdef Z3DEBUG +private: + bool check_invariant(entry const & e) const { + SASSERT(leq(e.begin_key(), e.end_key())); + return true; + } + + /** + \brief Return true if the last key of \c e1 is less than the first key of \c e2. + */ + bool lt(entry const & e1, entry const & e2) const { + return lt(e1.end_key(), e2.begin_key()); + } + + bool check_invariant(bucket const * bt) const { + SASSERT(bt->size() <= bt->capacity()); + SASSERT(bt == this->m_header || !bt->empty()); + for (unsigned i = 0; i < bt->size(); i++) { + entry const & curr = bt->get(i); + check_invariant(curr); + if (i > 0) { + entry const & prev = bt->get(i-1); + CTRACE("interval_skip_list", !lt(prev, curr), this->display_physical(tout);); + SASSERT(lt(prev, curr)); + CTRACE("interval_skip_list", can_be_merged(prev, curr), this->display_physical(tout);); + SASSERT(!can_be_merged(prev, curr)); + } + } + return true; + } + +public: + bool check_invariant() const { + SASSERT(this->m_header->empty()); + for (unsigned i = 0; i < this->m_header->level(); i++) { + // traverse buckets using get_next(i) pointers + bucket const * curr = this->m_header->get_next(i); + while (curr != 0) { + SASSERT(!curr->empty()); // only the header is empty. + bucket const * next = curr->get_next(i); + if (next != 0) { + SASSERT(next->level() >= i); + SASSERT(i == 0 || is_reachable_at_i(curr, next, i-1)); + SASSERT(!next->empty()); + entry const & last_of_curr = curr->last_entry(); + entry const & first_of_next = next->first_entry(); + CTRACE("interval_skip_list", !lt(last_of_curr, first_of_next), + this->display_physical(tout); + tout << "\ncurr:\n"; + this->display(tout, curr); + tout << "\nnext:\n"; + this->display(tout, next);); + SASSERT(lt(last_of_curr, first_of_next)); + CTRACE("interval_skip_list", can_be_merged(last_of_curr, first_of_next), + this->display_physical(tout); + tout << "\ncurr:\n"; + this->display(tout, curr); + tout << "\nnext:\n"; + this->display(tout, next);); + SASSERT(!can_be_merged(last_of_curr, first_of_next)); + } + curr = next; + } + } + bucket const * curr = this->m_header; + while (curr != 0) { + check_invariant(curr); + curr = curr->get_next(0); + } + return true; + } +#endif + + static void display_size_info(std::ostream & out) { + skip_list_base::display_size_info_core(out, sizeof(interval_skip_list)); + } + + /** + \brief Return the amount of memory consumed by the list. + */ + unsigned memory() const { + return this->memory_core(sizeof(interval_skip_list)); + } + +}; + +/** + \brief Traits for instantiating a mapping from unsigned to Value using the interval_skip_list template. +*/ +template, + unsigned MaxCapacity=32, + unsigned MaxLevel=32, + bool RefCount=false, + typename Manager=sl_manager_base > +struct unsigned_interval_skip_list_traits : private EqProc { + typedef default_islist_entry entry; + typedef Manager manager; + typedef typename entry::key key; + typedef typename entry::value value; + static const unsigned max_capacity = MaxCapacity; + static const unsigned initial_capacity = 2; + static const unsigned max_level = MaxLevel; + static const bool ref_count = RefCount; + + bool lt(key const & k1, key const & k2) const { return k1 < k2; } + bool eq(key const & k1, key const & k2) const { return k1 == k2; } + key succ(key const & k) const { return k + 1; } + key pred(key const & k) const { SASSERT(k > 0); return k - 1; } + bool val_eq(value const & v1, value const & v2) const { return EqProc::operator()(v1, v2); } +}; + +/** + \brief Traits for instantiating a set of unsigned values using the interval_skip_list template. +*/ +template > +struct unsigned_set_interval_skip_list_traits { + typedef default_set_islist_entry entry; + typedef Manager manager; + typedef typename entry::key key; + typedef typename entry::value value; + static const unsigned max_capacity = MaxCapacity; + static const unsigned initial_capacity = 2; + static const unsigned max_level = MaxLevel; + static const bool ref_count = false; + + bool lt(key const & k1, key const & k2) const { return k1 < k2; } + bool eq(key const & k1, key const & k2) const { return k1 == k2; } + key succ(key const & k) const { return k + 1; } + key pred(key const & k) const { SASSERT(k > 0); return k - 1; } + bool val_eq(value const & v1, value const & v2) const { return true; } +}; + +/** + \brief Generic adapater for generating a set-like API on top of the map API +*/ +template +class map2set_adapter : public Map { + typedef typename Map::manager manager; + typedef typename Map::key key; + typedef typename Map::value value; + + template + struct for_each_functor_adapter : public Functor { + for_each_functor_adapter(Functor const & f):Functor(f) { + } + bool operator()(key const & b, key const & e, value const & v) { + return Functor::operator()(b, e); + } + }; + +public: + map2set_adapter(manager & m): + Map(m) { + SASSERT(this->m_header != 0); + } + + void insert(manager & m, key const & b, key const & e) { + value _dummy; + Map::insert(m, b, e, _dummy); + } + + void insert(manager & m, key const & k) { + value _dummy; + Map::insert(m, k, k, _dummy); + } + + bool contains(key const & k) const { + value _dummy; + return Map::contains(k); + } + + bool find(key const & k) const { + return contains(k); + } + + /** + \begin Traverse the set applying the functor f. + The functor must have a method + + bool operator()(key const & b, key const & e) + + The method will invoke f(b, e) whenever the interval [b, e] are + in the set. + + If the functor returns false, then the traversal is interrupted. + */ + template + void for_each(Functor & f) const { + for_each_functor_adapter F(f); + Map::for_each(F); + } +}; + + +/** + \brief A set of unsigned values using interval_skip_list. +*/ +template > +class unsigned_isp_set : public map2set_adapter > > { +public: + unsigned_isp_set(Manager & m): + map2set_adapter > >(m) { + } +}; + +#endif /* _INTERVAL_SKIP_LIST_H_ */ + + diff --git a/lib/kbo.cpp b/lib/kbo.cpp new file mode 100644 index 000000000..e35bdbda8 --- /dev/null +++ b/lib/kbo.cpp @@ -0,0 +1,276 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + kbo.cpp + +Abstract: + + Knuth-Bendix ordering. + +Author: + + Leonardo de Moura (leonardo) 2008-01-28. + +Revision History: + +--*/ +#include"kbo.h" +#include"ast_pp.h" + +inline unsigned kbo::f_weight(func_decl * f) const { + // TODO + return 1; +} + +inline unsigned kbo::var_weight() const { + return m_var_weight; +} + +inline void kbo::reset() { + m_weight_balance = 0; + m_deltas.reset(); + m_num_pos = 0; + m_num_neg = 0; +} + +/** + \brief Increase the balance of the given variable. +*/ +inline void kbo::inc(expr_offset v) { + SASSERT(is_var(v.get_expr())); + int val; + unsigned v_idx = to_var(v.get_expr())->get_idx(); + unsigned offset = v.get_offset(); + if (m_deltas.find(v_idx, offset, val)) { + if (val == -1) + m_num_neg--; + else if (val == 0) + m_num_pos++; + m_deltas.insert(v_idx, offset, val + 1); + } + else { + m_deltas.insert(v_idx, offset, 1); + m_num_pos ++; + } +} + +/** + \brief Decreate the balance of the given variable. +*/ +inline void kbo::dec(expr_offset v) { + int val; + unsigned v_idx = to_var(v.get_expr())->get_idx(); + unsigned offset = v.get_offset(); + if (m_deltas.find(v_idx, offset, val)) { + if (val == 0) + m_num_neg++; + else if (val == 1) + m_num_pos--; + m_deltas.insert(v_idx, offset, val - 1); + } + else { + m_deltas.insert(v_idx, offset, -1); + m_num_neg ++; + } +} + +/** + \brief Accumulate the variables and weight balance of t. Return + true if t contains target_var. +*/ +template +bool kbo::VWBc(expr_offset t, expr_offset target_var) { + SASSERT(target_var.get_expr() == 0 || is_var(target_var.get_expr())); + svector & todo = m_vwbc_todo; + expr_offset s; + bool found = false; + unsigned j; + SASSERT(todo.empty()); + todo.push_back(t); + while (!todo.empty()) { + t = todo.back(); + if (t == target_var) + found = true; + expr * n = t.get_expr(); + unsigned offset = t.get_offset(); + todo.pop_back(); + switch (n->get_kind()) { + case AST_VAR: + if (m_subst && m_subst->find(to_var(n), offset, s)) + todo.push_back(s); + else if (pos) { + inc(t); + m_weight_balance += var_weight(); + } + else { + dec(t); + m_weight_balance -= var_weight(); + } + break; + case AST_APP: + if (pos) + m_weight_balance += f_weight(to_app(n)->get_decl()); + else + m_weight_balance -= f_weight(to_app(n)->get_decl()); + j = to_app(n)->get_num_args(); + while (j > 0) { + --j; + todo.push_back(expr_offset(to_app(n)->get_arg(j), offset)); + } + break; + default: + UNREACHABLE(); + break; + } + } + return found; +} + +template +inline void kbo::VWB(expr_offset t, unsigned idx) { + expr_offset null(0, 0); + app * n = to_app(t.get_expr()); + unsigned num = n->get_num_args(); + for (; idx < num; idx++) + VWBc(expr_offset(n->get_arg(idx), t.get_offset()), null); +} + +inline bool is_unary_app(expr * n) { + return is_app(n) && to_app(n)->get_num_args() == 1; +} + +inline kbo::result kbo::no_neg() const { + return m_num_neg == 0 ? GREATER : UNCOMPARABLE; +} + +inline kbo::result kbo::no_pos() const { + return m_num_pos == 0 ? LESSER : UNCOMPARABLE; +} + +order::result kbo::compare(expr_offset const & t1, expr_offset const & t2, substitution * s) { + reset(); + m_subst = s; + + if (t1 == t2) + return EQUAL; + + expr * n1 = t1.get_expr(); + expr * n2 = t2.get_expr(); + + // f(s) >_{kbo} f(t) iff s >_{kbo} t + while (is_unary_app(n1) && is_unary_app(n2) && to_app(n1)->get_decl() == to_app(n2)->get_decl()) { + n1 = to_app(n1)->get_arg(0); + n2 = to_app(n2)->get_arg(0); + } + + svector & todo = m_compare_todo; + SASSERT(todo.empty()); + todo.push_back(entry(find(expr_offset(n1, t1.get_offset())), + find(expr_offset(n2, t2.get_offset())), + 0)); + + result res = UNKNOWN; + + while (!todo.empty()) { + entry & e = todo.back(); + expr_offset t1 = e.m_t1; + expr_offset t2 = e.m_t2; + expr * n1 = t1.get_expr(); + expr * n2 = t2.get_expr(); + TRACE("kbo", tout << "processing with idx: " << e.m_idx << "\n" << + mk_pp(n1, m_manager) << "\n" << mk_pp(n2, m_manager) << "\n"; + tout << "wb : " << m_weight_balance << "\n";); + SASSERT(!is_quantifier(n1) && !is_quantifier(n2)); + bool v1 = is_var(n1); + bool v2 = is_var(n2); + if (v1 && v2) { + todo.pop_back(); + inc(t1); + dec(t2); + res = t1 == t2 ? EQUAL : UNCOMPARABLE; + } + else if (v1) { + todo.pop_back(); + res = VWBc(t2, t1) ? LESSER : UNCOMPARABLE; + inc(t1); + m_weight_balance += var_weight(); + } + else if (v2) { + todo.pop_back(); + res = VWBc(t1, t2) ? GREATER : UNCOMPARABLE; + dec(t2); + m_weight_balance -= var_weight(); + } + else { + func_decl * f = to_app(n1)->get_decl(); + func_decl * g = to_app(n2)->get_decl(); + result lex; + if (f != g || to_app(n1)->get_num_args() != to_app(n2)->get_num_args()) { + VWB(t1, 0); + VWB(t2, 0); + lex = UNCOMPARABLE; + } + else { + unsigned & idx = e.m_idx; + // when idx > 0, res contains the result for child (idx - 1) + if (idx > 0 && res != EQUAL) { + VWB(t1, idx); + VWB(t2, idx); + lex = res; + } + else if (idx == to_app(n1)->get_num_args()) { + // all children were visited + lex = EQUAL; + } + else if (idx < to_app(n1)->get_num_args()) { + expr_offset c1 = find(expr_offset(to_app(n1)->get_arg(idx), t1.get_offset())); + expr_offset c2 = find(expr_offset(to_app(n2)->get_arg(idx), t2.get_offset())); + idx++; // move curr entry child idx + entry new_entry(c1, c2, 0); + todo.push_back(new_entry); + continue; // process child before continuing + } + } + + todo.pop_back(); + m_weight_balance += f_weight(f); + m_weight_balance -= f_weight(g); + + if (m_weight_balance > 0) + res = no_neg(); + else if (m_weight_balance < 0) + res = no_pos(); + else if (f_greater(f, g)) + res = no_neg(); + else if (f_greater(g, f)) + res = no_pos(); + else if (f != g) + res = UNCOMPARABLE; + else if (lex == EQUAL) + res = EQUAL; + else if (lex == GREATER) + res = no_neg(); + else if (lex == LESSER) + res = no_pos(); + else + res = UNCOMPARABLE; + } + TRACE("kbo", tout << "result: " << res << "\n";); + } + + return res; +} + +bool kbo::greater(expr_offset const & t1, expr_offset const & t2, substitution * s) { + return compare(t1, t2, s) == GREATER; +} + +int kbo::compare_ge(expr_offset const & t1, expr_offset const & t2, substitution * s) { + switch (compare(t1, t2, s)) { + case GREATER: return 1; + case EQUAL: return 0; + default: return -1; + } +} diff --git a/lib/kbo.h b/lib/kbo.h new file mode 100644 index 000000000..eed4709bd --- /dev/null +++ b/lib/kbo.h @@ -0,0 +1,70 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + kbo.h + +Abstract: + + Knuth-Bendix ordering. + +Author: + + Leonardo de Moura (leonardo) 2008-01-28. + +Revision History: + +--*/ +#ifndef _KBO_H_ +#define _KBO_H_ + +#include"order.h" + +class kbo : public order { + struct entry { + expr_offset m_t1; + expr_offset m_t2; + unsigned m_idx; + entry():m_idx(UINT_MAX) {} + entry(expr_offset const & t1, expr_offset const & t2, unsigned idx): + m_t1(t1), m_t2(t2), m_idx(idx) {} + }; + + unsigned m_var_weight; + int m_weight_balance; + var_offset_map m_deltas; + unsigned m_num_pos; + unsigned m_num_neg; + svector m_vwbc_todo; + svector m_compare_todo; + + unsigned f_weight(func_decl * f) const; + unsigned var_weight() const; + + void reset(); + void inc(expr_offset v); + void dec(expr_offset v); + + template + bool VWBc(expr_offset t, expr_offset target_var); + + template + void VWB(expr_offset t, unsigned idx); + + result no_neg() const; + result no_pos() const; + +public: + kbo(ast_manager & m, precedence * p, unsigned var_weight = 1):order(m, p), m_var_weight(var_weight) {} + virtual ~kbo() {} + virtual void reserve(unsigned num_offsets, unsigned num_vars) { m_deltas.reserve(num_offsets, num_vars); } + virtual void reserve_offsets(unsigned num_offsets) { m_deltas.reserve_offsets(num_offsets); } + virtual void reserve_vars(unsigned num_vars) { m_deltas.reserve_vars(num_vars); } + virtual result compare(expr_offset const & t1, expr_offset const & t2, substitution * s); + result compare(expr * t1, expr * t2) { return compare(expr_offset(t1, 0), expr_offset(t2, 0), 0); } + virtual bool greater(expr_offset const & t1, expr_offset const & t2, substitution * s); + virtual int compare_ge(expr_offset const & t1, expr_offset const & t2, substitution * s); +}; + +#endif /* _KBO_H_ */ diff --git a/lib/lbool.cpp b/lib/lbool.cpp new file mode 100644 index 000000000..b77a9b892 --- /dev/null +++ b/lib/lbool.cpp @@ -0,0 +1,36 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + lbool.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-08. + +Revision History: + +--*/ +#include"lbool.h" + +std::ostream & operator<<(std::ostream & out, lbool b) { + switch(b) { + case l_false: return out << "l_false"; + case l_true: return out << "l_true"; + default: return out << "l_undef"; + } +} + +char const * to_sat_str(lbool l) { + switch (l) { + case l_false: return "unsatisfiable"; + case l_true: return "satisfiable"; + default: return "unknown"; + } +} + diff --git a/lib/lbool.h b/lib/lbool.h new file mode 100644 index 000000000..efcc5b0f0 --- /dev/null +++ b/lib/lbool.h @@ -0,0 +1,42 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + lbool.h + +Abstract: + + Lifted boolean + +Author: + + Leonardo de Moura (leonardo) 2008-02-08. + +Revision History: + +--*/ +#ifndef _LBOOL_H_ +#define _LBOOL_H_ + +#include"util.h" + +typedef enum { l_false = -1, l_undef, l_true } lbool; + +inline lbool operator~(lbool lb) { + return static_cast(-static_cast(lb)); +} + +inline lbool to_lbool(bool b) { + return static_cast(static_cast(b)*2-1); +} + +std::ostream & operator<<(std::ostream & out, lbool b); + +/** + \brief Convert l_true -> satisfiable, l_false -> unsatisfiable, and l_undef -> unknown. +*/ +char const * to_sat_str(lbool l); + +#endif /* _LBOOL_H_ */ + diff --git a/lib/lia2pb_tactic.cpp b/lib/lia2pb_tactic.cpp new file mode 100644 index 000000000..f9f22007a --- /dev/null +++ b/lib/lia2pb_tactic.cpp @@ -0,0 +1,366 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + lia2pb_tactic.cpp + +Abstract: + + Reduce bounded LIA benchmark into 0-1 LIA benchmark. + +Author: + + Leonardo de Moura (leonardo) 2012-02-07. + +Revision History: + +--*/ +#include"tactical.h" +#include"bound_manager.h" +#include"th_rewriter.h" +#include"for_each_expr.h" +#include"extension_model_converter.h" +#include"filter_model_converter.h" +#include"arith_decl_plugin.h" +#include"expr_substitution.h" +#include"ast_smt2_pp.h" + +class lia2pb_tactic : public tactic { + struct imp { + ast_manager & m; + bound_manager m_bm; + arith_util m_util; + expr_dependency_ref_vector m_new_deps; + th_rewriter m_rw; + bool m_produce_models; + bool m_produce_unsat_cores; + bool m_partial_lia2pb; + unsigned m_max_bits; + unsigned m_total_bits; + + + imp(ast_manager & _m, params_ref const & p): + m(_m), + m_bm(m), + m_util(m), + m_new_deps(m), + m_rw(m, p) { + updt_params(p); + } + + void updt_params_core(params_ref const & p) { + m_partial_lia2pb = p.get_bool(":lia2pb-partial", false); + m_max_bits = p.get_uint(":lia2pb-max-bits", 32); + m_total_bits = p.get_uint(":lia2pb-total-bits", 2048); + } + + void updt_params(params_ref const & p) { + m_rw.updt_params(p); + updt_params_core(p); + } + + void set_cancel(bool f) { + m_rw.set_cancel(f); + } + + bool is_target_core(expr * n, rational & u) { + if (!is_uninterp_const(n)) + return false; + rational l; bool s; + if (m_bm.has_lower(n, l, s) && + m_bm.has_upper(n, u, s) && + l.is_zero() && + u.get_num_bits() <= m_max_bits) { + + return true; + } + return false; + } + + bool is_bounded(expr * n) { + rational u; + return is_target_core(n, u); + } + + bool is_target(expr * n) { + rational u; + return is_target_core(n, u) && u > rational(1); + } + + struct failed {}; + + struct visitor { + imp & m_owner; + + visitor(imp & o):m_owner(o) {} + + void throw_failed(expr * n) { + TRACE("lia2pb", tout << "Failed at:\n" << mk_ismt2_pp(n, m_owner.m) << "\n";); + throw failed(); + } + + void operator()(var * n) { + throw_failed(n); + } + + void operator()(app * n) { + family_id fid = n->get_family_id(); + if (fid == m_owner.m.get_basic_family_id()) { + // all basic family ops are OK + } + else if (fid == m_owner.m_util.get_family_id()) { + // check if linear + switch (n->get_decl_kind()) { + case OP_LE: case OP_GE: case OP_LT: case OP_GT: + case OP_ADD: case OP_NUM: + return; + case OP_MUL: + if (n->get_num_args() != 2) + throw_failed(n); + if (!m_owner.m_util.is_numeral(n->get_arg(0))) + throw_failed(n); + return; + default: + throw_failed(n); + } + } + else if (is_uninterp_const(n)) { + if (m_owner.m_util.is_real(n)) { + if (!m_owner.m_partial_lia2pb) + throw_failed(n); + } + else if (m_owner.m_util.is_int(n)) { + if (!m_owner.m_partial_lia2pb && !m_owner.is_bounded(n)) + throw_failed(n); + } + } + else { + sort * s = m_owner.m.get_sort(n); + if (s->get_family_id() == m_owner.m_util.get_family_id()) + throw_failed(n); + } + } + + void operator()(quantifier * n) { + throw_failed(n); + } + }; + + + bool check(goal const & g) { + try { + expr_fast_mark1 visited; + visitor proc(*this); + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + expr * f = g.form(i); + for_each_expr_core(proc, visited, f); + } + return true; + } + catch (failed) { + return false; + } + } + + bool has_target() { + bound_manager::iterator it = m_bm.begin(); + bound_manager::iterator end = m_bm.end(); + for (; it != end; ++it) { + if (is_target(*it)) + return true; + } + return false; + } + + bool check_num_bits() { + unsigned num_bits = 0; + rational u; + bound_manager::iterator it = m_bm.begin(); + bound_manager::iterator end = m_bm.end(); + for (; it != end; ++it) { + expr * x = *it; + if (is_target_core(x, u) && u > rational(1)) { + num_bits += u.get_num_bits(); + if (num_bits > m_total_bits) + return false; + } + } + return true; + } + + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + fail_if_proof_generation("lia2pb", g); + m_produce_models = g->models_enabled(); + m_produce_unsat_cores = g->unsat_core_enabled(); + mc = 0; pc = 0; core = 0; result.reset(); + tactic_report report("lia2pb", *g); + m_bm.reset(); m_rw.reset(); m_new_deps.reset(); + + if (g->inconsistent()) { + result.push_back(g.get()); + return; + } + + m_bm(*g); + + TRACE("lia2pb", m_bm.display(tout);); + + // check if there is some variable to be converted + if (!has_target()) { + // nothing to be done + g->inc_depth(); + result.push_back(g.get()); + return; + } + + if (!check(*g)) + throw tactic_exception("goal is in a fragment unsupported by lia2pb"); + + if (!check_num_bits()) + throw tactic_exception("lia2pb failed, number of necessary bits exceeds specified threshold (use option :lia2pb-total-bits to increase threshold)"); + + extension_model_converter * mc1 = 0; + filter_model_converter * mc2 = 0; + if (m_produce_models) { + mc1 = alloc(extension_model_converter, m); + mc2 = alloc(filter_model_converter, m); + mc = concat(mc2, mc1); + } + + expr_ref zero(m); + expr_ref one(m); + zero = m_util.mk_numeral(rational(0), true); + one = m_util.mk_numeral(rational(1), true); + + unsigned num_converted = 0; + expr_substitution subst(m, m_produce_unsat_cores, false); + rational u; + ptr_buffer def_args; + bound_manager::iterator it = m_bm.begin(); + bound_manager::iterator end = m_bm.end(); + for (; it != end; ++it) { + expr * x = *it; + if (is_target_core(x, u) && u > rational(1)) { + num_converted++; + def_args.reset(); + rational a(1); + unsigned num_bits = u.get_num_bits(); + for (unsigned i = 0; i < num_bits; i++) { + app * x_prime = m.mk_fresh_const(0, m_util.mk_int()); + g->assert_expr(m_util.mk_le(zero, x_prime)); + g->assert_expr(m_util.mk_le(x_prime, one)); + if (a.is_one()) + def_args.push_back(x_prime); + else + def_args.push_back(m_util.mk_mul(m_util.mk_numeral(a, true), x_prime)); + if (m_produce_models) + mc2->insert(x_prime->get_decl()); + a *= rational(2); + } + SASSERT(def_args.size() > 1); + expr * def = m_util.mk_add(def_args.size(), def_args.c_ptr()); + expr_dependency * dep = 0; + if (m_produce_unsat_cores) { + dep = m.mk_join(m_bm.lower_dep(x), m_bm.upper_dep(x)); + if (dep != 0) + m_new_deps.push_back(dep); + } + TRACE("lia2pb", tout << mk_ismt2_pp(x, m) << " -> " << dep << "\n";); + subst.insert(x, def, 0, dep); + if (m_produce_models) + mc1->insert(to_app(x)->get_decl(), def); + } + } + + report_tactic_progress(":converted-lia2pb", num_converted); + + m_rw.set_substitution(&subst); + + expr_ref new_curr(m); + proof_ref new_pr(m); + unsigned size = g->size(); + for (unsigned idx = 0; idx < size; idx++) { + expr * curr = g->form(idx); + expr_dependency * dep = 0; + m_rw(curr, new_curr, new_pr); + if (m_produce_unsat_cores) { + dep = m.mk_join(m_rw.get_used_dependencies(), g->dep(idx)); + m_rw.reset_used_dependencies(); + } + g->update(idx, new_curr, 0, dep); + } + g->inc_depth(); + result.push_back(g.get()); + TRACE("lia2pb", g->display(tout);); + SASSERT(g->is_well_sorted()); + } + }; + + imp * m_imp; + params_ref m_params; +public: + lia2pb_tactic(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(lia2pb_tactic, m, m_params); + } + + virtual ~lia2pb_tactic() { + dealloc(m_imp); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + r.insert(":lia2pb-partial", CPK_BOOL, "(default: false) partial lia2pb conversion."); + r.insert(":lia2pb-max-bits", CPK_UINT, "(default: 32) maximum number of bits to be used (per variable) in lia2pb."); + r.insert(":lia2pb-total-bits", CPK_UINT, "(default: 2048) total number of bits to be used (per problem) in lia2pb."); + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + (*m_imp)(in, result, mc, pc, core); + } + + virtual void cleanup() { + ast_manager & m = m_imp->m; + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + d = m_imp; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + } + +protected: + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + +tactic * mk_lia2pb_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(lia2pb_tactic, m, p)); +} + diff --git a/lib/lia2pb_tactic.h b/lib/lia2pb_tactic.h new file mode 100644 index 000000000..a9c2d1c52 --- /dev/null +++ b/lib/lia2pb_tactic.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + lia2pb_tactic.h + +Abstract: + + Reduce bounded LIA benchmark into 0-1 LIA benchmark. + +Author: + + Leonardo de Moura (leonardo) 2012-02-07. + +Revision History: + +--*/ +#ifndef _LIA2PB_TACTIC_H_ +#define _LIA2PB_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_lia2pb_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/lib.vcxproj b/lib/lib.vcxproj new file mode 100644 index 000000000..75e3faa24 --- /dev/null +++ b/lib/lib.vcxproj @@ -0,0 +1,2161 @@ + + + + + bench + Win32 + + + bench + x64 + + + commercial + Win32 + + + commercial + x64 + + + debug_opt + Win32 + + + debug_opt + x64 + + + Debug + Win32 + + + Debug + x64 + + + external_64 + Win32 + + + external_64 + x64 + + + external_dbg + Win32 + + + external_dbg + x64 + + + external_parallel_x64 + Win32 + + + external_parallel_x64 + x64 + + + external_parallel + Win32 + + + external_parallel + x64 + + + external + Win32 + + + external + x64 + + + mpi_debug + Win32 + + + mpi_debug + x64 + + + mpi_release + Win32 + + + mpi_release + x64 + + + parallel_debug + Win32 + + + parallel_debug + x64 + + + parallel_release + Win32 + + + parallel_release + x64 + + + ReleaseD + Win32 + + + ReleaseD + x64 + + + release_mt + Win32 + + + release_mt + x64 + + + release_static + Win32 + + + release_static + x64 + + + Release + Win32 + + + Release + x64 + + + smtcomp + Win32 + + + smtcomp + x64 + + + Trace + Win32 + + + Trace + x64 + + + + {4A7E5A93-19D8-4382-8950-FB2EDEC7A76E} + lib + Win32Proj + + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + true + + + Application + + + StaticLibrary + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + + + StaticLibrary + Unicode + true + + + Application + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + true + + + Application + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + true + + + StaticLibrary + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + AllRules.ruleset + + + + + AllRules.ruleset + AllRules.ruleset + + + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + z3lib + z3lib + z3lib + z3lib + z3lib + z3_dbg + z3lib + z3lib + z3lib + z3lib + z3lib + z3lib + z3lib + z3lib + z3lib + z3lib + z3lib + z3lib + z3lib + z3lib + z3lib + z3lib + z3lib + z3_dbg + z3lib + z3lib + z3lib + z3lib + z3lib + z3lib + z3lib + z3lib + z3lib + z3lib + z3lib + z3lib + z3lib + z3lib + + + + Disabled + WIN32;_DEBUG;Z3DEBUG;_LIB;_TRACE;_WINDOWS;ASYNC_COMMANDS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + true + + + Level3 + ProgramDatabase + StreamingSIMDExtensions2 + + + $(OutDir)z3lib.lib + %(AdditionalLibraryDirectories) + MachineX86 + + + + + X64 + + + Disabled + WIN32;Z3DEBUG;_DEBUG;_LIB;_TRACE;_WINDOWS;ASYNC_COMMANDS;_AMD64_;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + ProgramDatabase + + + $(OutDir)z3lib.lib + %(AdditionalLibraryDirectories) + MachineX64 + + + + + Full + false + WIN32;NDEBUG;_LIB;_WINDOWS;ASYNC_COMMANDS;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + StreamingSIMDExtensions2 + + + $(OutDir)z3lib.lib + MachineX86 + + + + + X64 + + + MaxSpeed + false + WIN32;NDEBUG;_LIB;_WINDOWS;_AMD64_;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level3 + ProgramDatabase + + + MachineX64 + + + + + false + WIN32;NDEBUG;_LIB;_WINDOWS;_TRACE;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level3 + ProgramDatabase + StreamingSIMDExtensions2 + + + + + X64 + + + WIN32;NDEBUG;_LIB;_WINDOWS;_TRACE;_AMD64_;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level3 + ProgramDatabase + + + + + /INCLUDE:compile_term %(AdditionalOptions) + false + WIN32;Z3DEBUG;_LIB;_WINDOWS;_TRACE;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level3 + ProgramDatabase + StreamingSIMDExtensions2 + + + + + X64 + + + /INCLUDE:compile_term %(AdditionalOptions) + false + WIN32;_DEBUG;Z3DEBUG;_LIB;_WINDOWS;_TRACE;_AMD64_;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level3 + ProgramDatabase + + + + + Full + true + Speed + true + WIN32;NDEBUG;_LIB;_WINDOWS;%(PreprocessorDefinitions) + + + MultiThreadedDLL + false + StreamingSIMDExtensions2 + Fast + + + Level3 + ProgramDatabase + + + + + X64 + + + Full + true + Speed + true + WIN32;NDEBUG;_LIB;_WINDOWS;_AMD64_;%(PreprocessorDefinitions) + + + MultiThreadedDLL + false + StreamingSIMDExtensions2 + Fast + + + Level3 + ProgramDatabase + + + + + Full + false + _NO_OMP_;WIN32;NDEBUG;_LIB;_WINDOWS;%(PreprocessorDefinitions) + MultiThreaded + + + Level3 + ProgramDatabase + false + StreamingSIMDExtensions2 + + + $(OutDir)z3lib.lib + false + + + + + Full + false + WIN32;NDEBUG;_LIB;_WINDOWS;_EXTERNAL_RELEASE;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level3 + ProgramDatabase + StreamingSIMDExtensions2 + + + $(OutDir)z3lib.lib + + + + + Full + false + WIN32;NDEBUG;_LIB;_WINDOWS;_Z3_COMMERCIAL;_EXTERNAL_RELEASE;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level3 + ProgramDatabase + StreamingSIMDExtensions2 + + + $(OutDir)z3lib.lib + + + + + X64 + + + MaxSpeed + false + WIN32;NDEBUG;_LIB;_WINDOWS;_EXTERNAL_RELEASE;_AMD64_;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level3 + ProgramDatabase + + + + + X64 + + + MaxSpeed + false + WIN32;NDEBUG;_LIB;_WINDOWS;_Z3_COMMERCIAL;_EXTERNAL_RELEASE;_AMD64_;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level3 + ProgramDatabase + + + + + Full + false + WIN32;NDEBUG;_LIB;_WINDOWS;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + + + Level3 + ProgramDatabase + StreamingSIMDExtensions2 + + + + + Full + false + WIN32;NDEBUG;Z3DEBUG;_LIB;_WINDOWS;_EXTERNAL_RELEASE;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level3 + ProgramDatabase + StreamingSIMDExtensions2 + + + $(OutDir)z3_dbg.lib + + + + + X64 + + + MaxSpeed + false + WIN32;NDEBUG;_LIB;_WINDOWS;_AMD64_;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level3 + ProgramDatabase + + + $(OutDir)z3_dbg.lib + + + + + Disabled + C:\Program Files\Microsoft HPC Pack 2008 SDK\Include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;Z3DEBUG;_LIB;_TRACE;ASYNC_COMMANDS;_WINDOWS;_Z3_BUILD_PARALLEL_SMT;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + true + + + Level3 + EditAndContinue + StreamingSIMDExtensions2 + + + $(OutDir)z3lib.lib + %(AdditionalLibraryDirectories) + + + + + Disabled + C:\Program Files\Microsoft HPC Pack 2008 SDK\Include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;Z3DEBUG;_LIB;_TRACE;_WINDOWS;_Z3_BUILD_PARALLEL_SMT;_AMD64_;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + true + + + Level3 + ProgramDatabase + + + $(OutDir)z3lib.lib + %(AdditionalLibraryDirectories) + + + + + Full + false + C:\Program Files\Microsoft HPC Pack 2008 SDK\Include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;_WINDOWS;ASYNC_COMMANDS;_Z3_BUILD_PARALLEL_SMT;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + StreamingSIMDExtensions2 + + + $(OutDir)z3$(ProjectName).lib + + + + + Full + false + C:\Program Files\Microsoft HPC Pack 2008 SDK\Include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;_WINDOWS;_Z3_BUILD_PARALLEL_SMT;_AMD64_;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + + + + + Full + false + C:\Program Files\Microsoft HPC Pack 2008 SDK\Include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;_WINDOWS;_Z3_BUILD_PARALLEL_SMT;_Z3_BUILD_PARALLEL_MPI;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + StreamingSIMDExtensions2 + + + + + C:\Program Files\Microsoft HPC Pack 2008 SDK\Include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;_WINDOWS;_Z3_BUILD_PARALLEL_SMT;_Z3_BUILD_PARALLEL_MPI;_AMD64_;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + ProgramDatabase + + + + + Disabled + C:\Program Files\Microsoft HPC Pack 2008 SDK\Include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;Z3DEBUG;_LIB;_TRACE;_WINDOWS;_Z3_BUILD_PARALLEL_SMT;_Z3_BUILD_PARALLEL_MPI;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + true + + + Level3 + EditAndContinue + StreamingSIMDExtensions2 + + + $(OutDir)z3lib.lib + %(AdditionalLibraryDirectories) + + + + + Full + false + WIN32;NDEBUG;_LIB;_WINDOWS;SMTCOMP;%(PreprocessorDefinitions) + MultiThreadedDLL + false + + + Level4 + ProgramDatabase + StreamingSIMDExtensions2 + + + $(OutDir)z3lib.lib + + + + + X64 + + + MaxSpeed + false + WIN32;NDEBUG;_LIB;_WINDOWS;_AMD64_;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level3 + ProgramDatabase + + + + + Full + false + WIN32;NDEBUG;_LIB;_WINDOWS;%(PreprocessorDefinitions) + MultiThreaded + false + + + Level3 + ProgramDatabase + StreamingSIMDExtensions2 + + + $(OutDir)z3lib.lib + + + + + X64 + + + MaxSpeed + false + WIN32;NDEBUG;_LIB;_WINDOWS;_AMD64_;%(PreprocessorDefinitions) + MultiThreaded + + + Level3 + ProgramDatabase + + + + + Full + false + WIN32;NDEBUG;_LIB;_WINDOWS;_EXTERNAL_RELEASE;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level3 + ProgramDatabase + + + $(OutDir)z3lib.lib + + + + + X64 + + + MaxSpeed + false + WIN32;NDEBUG;_LIB;_WINDOWS;_EXTERNAL_RELEASE;_AMD64_;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level3 + ProgramDatabase + + + + + Full + false + WIN32;NDEBUG;_LIB;_WINDOWS;_EXTERNAL_RELEASE;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level3 + ProgramDatabase + + + $(OutDir)z3lib.lib + + + + + X64 + + + MaxSpeed + false + WIN32;NDEBUG;_LIB;_WINDOWS;_AMD64_;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + + + + + Full + false + C:\Program Files\Microsoft HPC Pack 2008 SDK\Include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;_WINDOWS;_EXTERNAL_RELEASE;_Z3_BUILD_PARALLEL_SMT;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + StreamingSIMDExtensions2 + + + + + X64 + + + Full + false + C:\Program Files\Microsoft HPC Pack 2008 SDK\Include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;_WINDOWS;_Z3_BUILD_PARALLEL_SMT;_EXTERNAL_RELEASE;_AMD64_;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + + + + + false + MultiThreaded + Level3 + Full + _NO_OMP_;_AMD64_;WIN32;NDEBUG;_LIB;_WINDOWS;%(PreprocessorDefinitions) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/lib.vcxproj.user b/lib/lib.vcxproj.user new file mode 100644 index 000000000..ace9a86ac --- /dev/null +++ b/lib/lib.vcxproj.user @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/lib/linear_eq_solver.h b/lib/linear_eq_solver.h new file mode 100644 index 000000000..e7dd6700b --- /dev/null +++ b/lib/linear_eq_solver.h @@ -0,0 +1,152 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + linear_eq_solver.h + +Abstract: + + Simple equational solver template for any number field. + No special optimization, just the basics for solving small systems. + It is a solver target to dense system of equations. + Main client: Sparse Modular GCD algorithm. + +Author: + + Leonardo (leonardo) 2012-01-22 + +Notes: + +--*/ +#ifndef _LINEAR_EQ_SOLVER_H_ +#define _LINEAR_EQ_SOLVER_H_ + +template +class linear_eq_solver { + typedef typename numeral_manager::numeral numeral; + numeral_manager & m; + unsigned n; // number of variables + vector > A; + svector b; +public: + linear_eq_solver(numeral_manager & _m):m(_m), n(0) { SASSERT(m.field()); } + ~linear_eq_solver() { flush(); } + + void flush() { + SASSERT(b.size() == A.size()); + unsigned sz = A.size(); + for (unsigned i = 0; i < sz; i++) { + svector & as = A[i]; + m.del(b[i]); + SASSERT(as.size() == n); + for (unsigned j = 0; j < n; j++) + m.del(as[j]); + } + A.reset(); + b.reset(); + n = 0; + } + + void resize(unsigned _n) { + if (n != _n) { + flush(); + n = _n; + for (unsigned i = 0; i < n; i++) { + A.push_back(svector()); + svector & as = A.back(); + for (unsigned j = 0; j < n; j++) { + as.push_back(numeral()); + } + b.push_back(numeral()); + } + } + } + + void reset() { + for (unsigned i = 0; i < n; i++) { + svector & A_i = A[i]; + for (unsigned j = 0; j < n; j++) { + m.set(A_i[j], 0); + } + m.set(b[i], 0); + } + } + + // Set row i with _as[0]*x_0 + ... + _as[n-1]*x_{n-1} = b + void add(unsigned i, numeral const * _as, numeral const & _b) { + SASSERT(i < n); + m.set(b[i], _b); + svector & A_i = A[i]; + for (unsigned j = 0; j < n; j++) { + m.set(A_i[j], _as[j]); + } + } + + // Return true if the system of equations has a solution. + // Return false if the matrix is singular + bool solve(numeral * xs) { + for (unsigned k = 0; k < n; k++) { + TRACE("linear_eq_solver", tout << "iteration " << k << "\n"; display(tout);); + // find pivot + unsigned i = k; + for (; i < n; i++) { + if (!m.is_zero(A[i][k])) + break; + } + if (i == n) + return false; // matrix is singular + A[k].swap(A[i]); // swap rows + svector & A_k = A[k]; + numeral & A_k_k = A_k[k]; + SASSERT(!m.is_zero(A_k_k)); + // normalize row + for (unsigned i = k+1; i < n; i++) + m.div(A_k[i], A_k_k, A_k[i]); + m.div(b[k], A_k_k, b[k]); + m.set(A_k_k, 1); + // check if first k-1 positions are zero + DEBUG_CODE({ for (unsigned i = 0; i < k; i++) { SASSERT(m.is_zero(A_k[i])); } }); + // for all rows below pivot + for (unsigned i = k+1; i < n; i++) { + svector & A_i = A[i]; + numeral & A_i_k = A_i[k]; + for (unsigned j = k+1; j < n; j++) { + m.submul(A_i[j], A_i_k, A_k[j], A_i[j]); + } + m.submul(b[i], A_i_k, b[k], b[i]); + m.set(A_i_k, 0); + } + } + unsigned k = n; + while (k > 0) { + --k; + TRACE("linear_eq_solver", tout << "iteration " << k << "\n"; display(tout);); + SASSERT(m.is_one(A[k][k])); + // save result + m.set(xs[k], b[k]); + // back substitute + unsigned i = k; + while (i > 0) { + --i; + m.submul(b[i], A[i][k], b[k], b[i]); + m.set(A[i][k], 0); + } + } + return true; + } + + void display(std::ostream & out) const { + for (unsigned i = 0; i < A.size(); i++) { + SASSERT(A[i].size() == n); + for (unsigned j = 0; j < n; j++) { + m.display(out, A[i][j]); + out << " "; + } + m.display(out, b[i]); out << "\n"; + } + } +}; + + +#endif diff --git a/lib/linear_equation.cpp b/lib/linear_equation.cpp new file mode 100644 index 000000000..0b8828674 --- /dev/null +++ b/lib/linear_equation.cpp @@ -0,0 +1,279 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + linear_equation.cpp + +Abstract: + + Basic infrastructure for managing linear equations of the form: + + a_1 * x_1 + ... + a_n * x_n = 0 + +Author: + + Leonardo de Moura (leonardo) 2011-06-28 + +Revision History: + +--*/ +#include"linear_equation.h" + +/** + \brief Return the position of variable x_i in the linear equation. + Return UINT_MAX, if the variable is not in the linear_equation. +*/ +unsigned linear_equation::pos(unsigned x_i) const { + int low = 0; + int high = m_size - 1; + while (true) { + int mid = low + ((high - low) / 2); + var x_mid = m_xs[mid]; + if (x_i > x_mid) { + low = mid + 1; + if (low > high) + return UINT_MAX; + } + else if (x_i < x_mid) { + high = mid - 1; + if (low > high) + return UINT_MAX; + } + else { + return mid; + } + } +} + +void linear_equation_manager::display(std::ostream & out, linear_equation const & eq) const { + unsigned sz = eq.m_size; + for (unsigned i = 0; i < sz; i++) { + if (i > 0) + out << " + "; + out << m.to_string(eq.m_as[i]) << "*x" << eq.m_xs[i]; + } + out << " = 0"; +} + +linear_equation * linear_equation_manager::mk(unsigned sz, mpq * as, var * xs, bool normalized) { + SASSERT(sz > 1); + + // compute lcm of the denominators + mpz l; + mpz r; + m.set(l, as[0].denominator()); + for (unsigned i = 1; i < sz; i++) { + m.set(r, as[i].denominator()); + m.lcm(r, l, l); + } + + TRACE("linear_equation_mk", tout << "lcm: " << m.to_string(l) << "\n";); + + // copy l * as to m_int_buffer. + m_int_buffer.reset(); + for (unsigned i = 0; i < sz; i++) { + TRACE("linear_equation_mk", tout << "before as[" << i << "]: " << m.to_string(as[i]) << "\n";); + m.mul(l, as[i], as[i]); + TRACE("linear_equation_mk", tout << "after as[" << i << "]: " << m.to_string(as[i]) << "\n";); + SASSERT(m.is_int(as[i])); + m_int_buffer.push_back(as[i].numerator()); + } + + linear_equation * new_eq = mk(sz, m_int_buffer.c_ptr(), xs, normalized); + + m.del(r); + m.del(l); + + return new_eq; +} + +linear_equation * linear_equation_manager::mk_core(unsigned sz, mpz * as, var * xs) { + SASSERT(sz > 0); + DEBUG_CODE({ + for (unsigned i = 1; i < sz; i++) { + SASSERT(xs[i-1] < xs[i]); + } + }); + + TRACE("linear_equation_bug", for (unsigned i = 0; i < sz; i++) tout << m.to_string(as[i]) << "*x" << xs[i] << " "; tout << "\n";); + + mpz g; + m.set(g, as[0]); + for (unsigned i = 1; i < sz; i++) { + if (m.is_one(g)) + break; + if (m.is_neg(as[i])) { + m.neg(as[i]); + m.gcd(g, as[i], g); + m.neg(as[i]); + } + else { + m.gcd(g, as[i], g); + } + } + if (!m.is_one(g)) { + for (unsigned i = 0; i < sz; i++) { + m.div(as[i], g, as[i]); + } + } + + TRACE("linear_equation_bug", + tout << "g: " << m.to_string(g) << "\n"; + for (unsigned i = 0; i < sz; i++) tout << m.to_string(as[i]) << "*x" << xs[i] << " "; tout << "\n";); + + m.del(g); + + unsigned obj_sz = linear_equation::get_obj_size(sz); + void * mem = m_allocator.allocate(obj_sz); + linear_equation * new_eq = new (mem) linear_equation(); + mpz * new_as = reinterpret_cast(reinterpret_cast(new_eq) + sizeof(linear_equation)); + double * new_app_as = reinterpret_cast(reinterpret_cast(new_as) + sz * sizeof(mpz)); + var * new_xs = reinterpret_cast(reinterpret_cast(new_app_as) + sz * sizeof(double)); + for (unsigned i = 0; i < sz; i++) { + new (new_as + i) mpz(); + m.set(new_as[i], as[i]); + new_app_as[i] = m.get_double(as[i]); + var x_i = xs[i]; + new_xs[i] = x_i; + } + new_eq->m_size = sz; + new_eq->m_as = new_as; + new_eq->m_approx_as = new_app_as; + new_eq->m_xs = new_xs; + return new_eq; +} + +linear_equation * linear_equation_manager::mk(unsigned sz, mpz * as, var * xs, bool normalized) { + if (!normalized) { + for (unsigned i = 0; i < sz; i++) { + var x = xs[i]; + m_mark.reserve(x+1, false); + m_val_buffer.reserve(x+1); + + if (m_mark[x]) { + m.add(m_val_buffer[x], as[i], m_val_buffer[x]); + } + else { + m.set(m_val_buffer[x], as[i]); + m_mark[x] = true; + } + } + + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + var x = xs[i]; + if (m_mark[x]) { + if (!m.is_zero(m_val_buffer[x])) { + xs[j] = xs[i]; + m.set(as[j], m_val_buffer[x]); + j++; + } + m_mark[x] = false; + } + } + sz = j; + if (sz <= 1) + return 0; + } + else { + DEBUG_CODE({ + for (unsigned i = 0; i < sz; i++) { + var x = xs[i]; + m_mark.reserve(x+1, false); + SASSERT(!m_mark[x]); + m_mark[x] = true; + } + for (unsigned i = 0; i < sz; i++) { + var x = xs[i]; + m_mark[x] = false; + } + }); + } + + for (unsigned i = 0; i < sz; i++) { + var x = xs[i]; + m_val_buffer.reserve(x+1); + m.swap(m_val_buffer[x], as[i]); + } + std::sort(xs, xs+sz); + for (unsigned i = 0; i < sz; i++) { + var x = xs[i]; + m.swap(as[i], m_val_buffer[x]); + } + + return mk_core(sz, as, xs); +} + +linear_equation * linear_equation_manager::mk(mpz const & b1, linear_equation const & eq1, mpz const & b2, linear_equation const & eq2) { + SASSERT(!m.is_zero(b1)); + SASSERT(!m.is_zero(b2)); + mpz tmp, new_a; + m_int_buffer.reset(); + m_var_buffer.reset(); + unsigned sz1 = eq1.size(); + unsigned sz2 = eq2.size(); + unsigned i1 = 0; + unsigned i2 = 0; + while (true) { + if (i1 == sz1) { + // copy remaining entries from eq2 + while (i2 < sz2) { + m_int_buffer.push_back(eq2.a(i2)); + m.mul(m_int_buffer.back(), b2, m_int_buffer.back()); + m_var_buffer.push_back(eq2.x(i2)); + i2++; + } + break; + } + if (i2 == sz2) { + // copy remaining entries from eq1 + while (i1 < sz1) { + m_int_buffer.push_back(eq1.a(i1)); + m.mul(m_int_buffer.back(), b1, m_int_buffer.back()); + m_var_buffer.push_back(eq1.x(i1)); + i1++; + } + break; + } + var x1 = eq1.x(i1); + var x2 = eq2.x(i2); + if (x1 < x2) { + m_int_buffer.push_back(eq1.a(i1)); + m.mul(m_int_buffer.back(), b1, m_int_buffer.back()); + m_var_buffer.push_back(eq1.x(i1)); + i1++; + } + else if (x1 > x2) { + m_int_buffer.push_back(eq2.a(i2)); + m.mul(m_int_buffer.back(), b2, m_int_buffer.back()); + m_var_buffer.push_back(eq2.x(i2)); + i2++; + } + else { + m.mul(eq1.a(i1), b1, tmp); + m.addmul(tmp, b2, eq2.a(i2), new_a); + if (!m.is_zero(new_a)) { + m_int_buffer.push_back(new_a); + m_var_buffer.push_back(eq1.x(i1)); + } + i1++; + i2++; + } + } + m.del(tmp); + m.del(new_a); + SASSERT(m_int_buffer.size() == m_var_buffer.size()); + if (m_int_buffer.empty()) + return 0; + return mk_core(m_int_buffer.size(), m_int_buffer.c_ptr(), m_var_buffer.c_ptr()); +} + +void linear_equation_manager::del(linear_equation * eq) { + for (unsigned i = 0; i < eq->m_size; i++) { + m.del(eq->m_as[i]); + } + unsigned obj_sz = linear_equation::get_obj_size(eq->m_size); + m_allocator.deallocate(obj_sz, eq); +} + diff --git a/lib/linear_equation.h b/lib/linear_equation.h new file mode 100644 index 000000000..8b38a30e4 --- /dev/null +++ b/lib/linear_equation.h @@ -0,0 +1,85 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + linear_equation.h + +Abstract: + + Basic infrastructure for managing linear equations of the form: + + a_1 * x_1 + ... + a_n * x_n = 0 + +Author: + + Leonardo de Moura (leonardo) 2011-06-28 + +Revision History: + +--*/ +#ifndef _LINEAR_EQUATION_H_ +#define _LINEAR_EQUATION_H_ + +#include"mpq.h" +#include"small_object_allocator.h" +#include"numeral_buffer.h" +#include"double_manager.h" + +class linear_equation { +public: + typedef unsigned var; +private: + static unsigned get_obj_size(unsigned sz) { return sizeof(linear_equation) + sz * (sizeof(mpz) + sizeof(double) + sizeof(var)); } + friend class linear_equation_manager; + unsigned m_size; + mpz * m_as; // precise coefficients + double * m_approx_as; // approximated coefficients + var * m_xs; // var ids + linear_equation() {} +public: + unsigned size() const { return m_size; } + mpz const & a(unsigned idx) const { SASSERT(idx < m_size); return m_as[idx]; } + double approx_a(unsigned idx) const { SASSERT(idx < m_size); return m_approx_as[idx]; } + var x(unsigned idx) const { SASSERT(idx < m_size); return m_xs[idx]; } + unsigned pos(unsigned x_i) const; + void get_a(double_manager & m, unsigned idx, double & r) const { r = m_approx_as[idx]; } + template + void get_a(NumManager & m, unsigned idx, mpq & r) const { m.set(r, m_as[idx]); } + template + void get_a(NumManager & m, unsigned idx, mpz & r) const { m.set(r, m_as[idx]); } +}; + +class linear_equation_manager { +public: + typedef unsynch_mpq_manager numeral_manager; + typedef linear_equation::var var; + typedef numeral_buffer mpz_buffer; +private: + typedef svector var_buffer; + + small_object_allocator & m_allocator; + numeral_manager & m; + mpz_buffer m_int_buffer; + mpz_buffer m_val_buffer; + char_vector m_mark; + var_buffer m_var_buffer; + + linear_equation * mk_core(unsigned sz, mpz * as, var * xs); + +public: + linear_equation_manager(numeral_manager & _m, small_object_allocator & a):m_allocator(a), m(_m), m_int_buffer(m), m_val_buffer(m) {} + ~linear_equation_manager() {} + + linear_equation * mk(unsigned sz, mpq * as, var * xs, bool normalized = false); + linear_equation * mk(unsigned sz, mpz * as, var * xs, bool normalized = false); + void del(linear_equation * eq); + + // Return b1 * eq1 + b2 * eq2 + // return 0 if the b1 * eq1 + b2 * eq2 == 0 + linear_equation * mk(mpz const & b1, linear_equation const & eq1, mpz const & b2, linear_equation const & eq2); + + void display(std::ostream & out, linear_equation const & eq) const; +}; + +#endif diff --git a/lib/list.h b/lib/list.h new file mode 100644 index 000000000..931962dfd --- /dev/null +++ b/lib/list.h @@ -0,0 +1,96 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + list.h + +Abstract: + + Simple list data structure. It is meant to be used with region allocators. + +Author: + + Leonardo de Moura (leonardo) 2007-07-10. + +Revision History: + +--*/ +#ifndef _LIST_H_ +#define _LIST_H_ + +#include"buffer.h" +#include"region.h" + +template +class list { + T m_head; + list * m_tail; + +public: + list(T const & h, list * t = 0): + m_head(h), + m_tail(t) { + } + + T const & head() const { return m_head; } + list * tail() const { return m_tail; } + T & head() { return m_head; } + list * & tail() { return m_tail; } + + class iterator { + list const * m_curr; + public: + iterator(list const * c = 0):m_curr(c) {} + T const & operator*() const { return m_curr->head(); } + iterator & operator++() { m_curr = m_curr->tail(); return *this; } + iterator operator++(int) { iterator tmp = *this; ++*this; return tmp; } + bool operator==(iterator const & it) { return m_curr == it.m_curr; } + bool operator!=(iterator const & it) { return m_curr != it.m_curr; } + }; + + typedef iterator const_iterator; + iterator begin() const { return iterator(this); } + iterator end() const { return iterator(0); } +}; + +/** + \brief Return the list length. +*/ +template +unsigned length(list * l) { + unsigned r = 0; + while(l) { + l = l->tail(); + r++; + } + return r; +} + +/** + \brief Non destructive apppend operation. The new nodes are allocated + using the given region allocator. +*/ +template +list * append(region & r, list * l1, list * l2) { + if (l2 == 0) { + return l1; + } + ptr_buffer > buffer; + while (l1) { + buffer.push_back(l1); + l1 = l1->tail(); + } + list * result = l2; + typename ptr_buffer >::const_iterator it = buffer.end(); + typename ptr_buffer >::const_iterator begin = buffer.begin(); + while (it != begin) { + --it; + list * curr = *it; + result = new (r) list(curr->head(), result); + } + return result; +} + +#endif /* _LIST_H_ */ + diff --git a/lib/lockless_queue.h b/lib/lockless_queue.h new file mode 100644 index 000000000..83d9226c4 --- /dev/null +++ b/lib/lockless_queue.h @@ -0,0 +1,115 @@ +/*++ +Copyright (c) 2008 Microsoft Corporation + +Module Name: + + lockless_queue.h + +Abstract: + + A queue that doesn't require locking, as long as only + one thread pushes and another thread pops. + +Author: + + Christoph Wintersteiger (t-cwinte) 2008-12-26 + +Revision History: + +--*/ + +#ifndef _LOCKLESS_QUEUE_H_ +#define _LOCKLESS_QUEUE_H_ + +#include +#include "debug.h" + +template +class lockless_queue { +protected: + unsigned m_head; + unsigned m_tail; + unsigned m_size; + T *m_items; +public: + typedef T value_type; + + lockless_queue(unsigned size=4096) : + m_head(0), + m_tail(0), + m_size(size), + m_items(0) { + if(m_size>0) { + m_items = alloc_vect(m_size); + SASSERT(m_items!=0); + } + } + + lockless_queue(const lockless_queue &other) { + m_items = 0; + m_size = m_head = m_tail = 0; + this->operator=(other); + } + + ~lockless_queue() { + if(m_items) { + dealloc_vect(m_items, m_size); + m_items=0; + } + } + + inline bool push(const T& elem) { + volatile unsigned head = m_head; + + if(((head+1)%m_size) == m_tail) + return false; // queue is full. + + m_items[head++] = elem; + head %= m_size; + m_head = head; + return true; + } + + inline bool pop() { + volatile unsigned tail = m_tail; + + if(tail == m_head) + return false; // queue is empty. + + tail = (tail+1) % m_size; + m_tail = tail; + return true; + } + + inline T& back() const { + SASSERT(!empty()); + return m_items[m_tail]; + } + + inline bool empty() const { + return (m_tail == m_head); + } + + inline bool full() const { + return (m_tail == ((m_head+1)%m_size)); + } + + lockless_queue& operator=(const lockless_queue &other) { + if(m_size!=other.m_size) { + if(m_items) dealloc_vect(m_items, m_size); + m_items = alloc_vect(other.m_size); + m_size = other.m_size; + } + + for(size_t cur = other.m_tail; cur!=other.m_head; cur = (cur+1)%m_size) + m_items[cur] = other.m_items[cur]; + + m_tail = other.m_tail; + m_head = other.m_head; + + return *this; + } + +}; + +#endif diff --git a/lib/lpo.cpp b/lib/lpo.cpp new file mode 100644 index 000000000..d7af7aec5 --- /dev/null +++ b/lib/lpo.cpp @@ -0,0 +1,184 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + lpo.h + +Abstract: + + Lexicographical Path Ordering + +Author: + + Leonardo de Moura (leonardo) 2008-02-01. + +Revision History: + +--*/ +#include"lpo.h" + +/** + \brief Check whether the variable in t1 occurs in t2. +*/ +bool lpo::occurs(expr_offset const & t1, expr_offset const & t2) { + SASSERT(is_var(t1.get_expr())); + if (is_ground(t2.get_expr())) + return false; + m_todo.reset(); + m_todo.push_back(t2); + while (!m_todo.empty()) { + expr_offset t = m_todo.back(); + m_todo.pop_back(); + t = find(t); + expr * n = t.get_expr(); + if (is_ground(n)) + continue; + unsigned offset = t.get_offset(); + unsigned j; + switch (n->get_kind()) { + case AST_VAR: + if (t == t1) + return true; + break; + case AST_APP: + j = to_app(n)->get_num_args(); + while (j > 0) { + --j; + expr * arg = to_app(n)->get_arg(j); + if (!is_ground(arg)) + m_todo.push_back(expr_offset(arg, offset)); + } + break; + default: + UNREACHABLE(); + } + } + return false; +} + +inline bool lpo::greater(expr_offset s, expr_offset t, unsigned depth) { + return lpo::compare(s, t, depth) == GREATER; +} + +/** + \brief Return true if s >_{lpo} t_i forall children t_i of t. +*/ +bool lpo::dominates_args(expr_offset s, expr_offset t, unsigned depth) { + SASSERT(is_app(t.get_expr())); + unsigned num_args = to_app(t.get_expr())->get_num_args(); + unsigned off = t.get_offset(); + for (unsigned i = 0; i < num_args; i++) { + expr * t_i = to_app(t.get_expr())->get_arg(i); + if (!greater(s, expr_offset(t_i, off), depth+1)) + return false; + } + return true; +} + +/** + \brief Return true if s_i >=_{lpo} t for some arg s_i of s. + */ +bool lpo::arg_dominates_expr(expr_offset s, expr_offset t, unsigned depth) { + SASSERT(is_app(s.get_expr())); + unsigned num_args = to_app(s.get_expr())->get_num_args(); + unsigned off = s.get_offset(); + for (unsigned i = 0; i < num_args; i++) { + expr * s_i = to_app(s.get_expr())->get_arg(i); + result r = compare(expr_offset(s_i, off), t, depth+1); + if (r == EQUAL || r == GREATER) + return true; + } + return false; +} + +order::result lpo::lex_compare(expr_offset s, expr_offset t, unsigned depth) { + SASSERT(is_app(s.get_expr())); + SASSERT(is_app(t.get_expr())); + app * _s = to_app(s.get_expr()); + app * _t = to_app(t.get_expr()); + unsigned num_args1 = _s->get_num_args(); + unsigned num_args2 = _t->get_num_args(); + unsigned num_args = std::min(num_args1, num_args2); + unsigned off1 = s.get_offset(); + unsigned off2 = t.get_offset(); + result r = EQUAL; + for (unsigned i = 0; i < num_args; i++) { + r = compare(expr_offset(_s->get_arg(i), off1), expr_offset(_t->get_arg(i), off2), depth+1); + if (r != EQUAL) + break; + } + if (r == EQUAL) { + if (num_args1 > num_args2) + return GREATER; + if (num_args1 < num_args2) + return NOT_GTEQ; + } + return r; +} + +inline order::result lpo::compare_core(expr_offset s, expr_offset t, unsigned depth) { + s = find(s); + t = find(t); + + if (max_depth(depth)) + return UNKNOWN; + + if (is_var(s.get_expr())) + return s == t ? EQUAL : UNCOMPARABLE; + else if (is_var(t.get_expr())) + return occurs(t, s) ? GREATER : UNCOMPARABLE; + else { + func_decl * f = to_app(s.get_expr())->get_decl(); + func_decl * g = to_app(t.get_expr())->get_decl(); + if (f_greater(f, g)) + return dominates_args(s, t, depth) ? GREATER : NOT_GTEQ; + else if (f != g) + return arg_dominates_expr(s, t, depth) ? GREATER : NOT_GTEQ; + else { + result r = lex_compare(s, t, depth); + if (r == GREATER) { + if (dominates_args(s, t, depth)) + return GREATER; + } + else if (r == EQUAL) + return EQUAL; + return to_app(s.get_expr())->get_num_args() > 1 && arg_dominates_expr(s, t, depth) ? GREATER : NOT_GTEQ; + } + } +} + +order::result lpo::compare(expr_offset s, expr_offset t, unsigned depth) { + TRACE("lpo", tout << "comparing:\n" << mk_pp(s.get_expr(), m_manager) << "\n" << mk_pp(t.get_expr(), m_manager) << "\n";); + result r = compare_core(s, t, depth); + TRACE("lpo", tout << "result of comparing:\n" << mk_pp(s.get_expr(), m_manager) << "\n" << mk_pp(t.get_expr(), m_manager) << "\nresult: " << r << "\n";); + return r; +} + +bool lpo::greater(expr_offset const & t1, expr_offset const & t2, substitution * s) { + m_subst = s; + return greater(t1, t2, static_cast(0)); +} + +order::result lpo::compare(expr_offset const & t1, expr_offset const & t2, substitution * s) { + m_subst = s; + result r = compare(t1, t2, static_cast(0)); + if (r != NOT_GTEQ) + return r; + r = compare(t2, t1, static_cast(0)); + if (r == GREATER) + return LESSER; + if (r == UNKNOWN) + return UNKNOWN; + return UNCOMPARABLE; +} + +int lpo::compare_ge(expr_offset const & t1, expr_offset const & t2, substitution * s) { + m_subst = s; + result r = compare(t1, t2, static_cast(0)); + switch (r) { + case GREATER: return 1; + case EQUAL: return 0; + default: return -1; + } +} diff --git a/lib/lpo.h b/lib/lpo.h new file mode 100644 index 000000000..df18e78e1 --- /dev/null +++ b/lib/lpo.h @@ -0,0 +1,49 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + lpo.h + +Abstract: + + Lexicographical Path Ordering + +Author: + + Leonardo de Moura (leonardo) 2008-02-01. + +Revision History: + +--*/ +#ifndef _LPO_H_ +#define _LPO_H_ + +#include"order.h" +#include"vector.h" +#include"map.h" + +class lpo : public order { + svector m_todo; + + bool occurs(expr_offset const & t1, expr_offset const & t2); + bool greater(expr_offset s, expr_offset t, unsigned depth); + bool dominates_args(expr_offset s, expr_offset t, unsigned depth); + bool arg_dominates_expr(expr_offset s, expr_offset t, unsigned depth); + result lex_compare(expr_offset s, expr_offset t, unsigned depth); + result compare_core(expr_offset s, expr_offset t, unsigned depth); + result compare(expr_offset s, expr_offset t, unsigned depth); + bool max_depth(unsigned d) { /* TODO */ return false; } +public: + lpo(ast_manager & m, precedence * p):order(m, p) {} + virtual ~lpo() {} + + virtual result compare(expr_offset const & t1, expr_offset const & t2, substitution * s); + result compare(expr * t1, expr * t2) { return compare(expr_offset(t1, 0), expr_offset(t2, 0), static_cast(0)); } + + virtual bool greater(expr_offset const & t1, expr_offset const & t2, substitution * s); + bool greater(expr_offset const & t1, expr_offset const & t2) { return greater(t1, t2); } + virtual int compare_ge(expr_offset const & t1, expr_offset const & t2, substitution * s); +}; + +#endif /* _LPO_H_ */ diff --git a/lib/lru_cache.cpp b/lib/lru_cache.cpp new file mode 100644 index 000000000..cb61410b7 --- /dev/null +++ b/lib/lru_cache.cpp @@ -0,0 +1,424 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + lru_cache.cpp + +Abstract: + + expr -> expr LRU cache + +Author: + + Leonardo (leonardo) 2011-04-12 + +Notes: + +--*/ +#include"lru_cache.h" +#include"ast_ll_pp.h" + +#define MIN_MAX_SIZE 1024 +#define INITIAL_CAPACITY 128 + +lru_cache::cell * lru_cache::allocate(unsigned capacity) { + cell * mem = static_cast(memory::allocate(sizeof(cell) * capacity)); + memset(mem, 0, sizeof(cell)*capacity); + return mem; +} + +void lru_cache::deallocate(cell * table) { + memory::deallocate(table); +} + +lru_cache::cell * lru_cache::copy_table(cell * old_head, cell * new_table, unsigned new_capacity) { + SASSERT(old_head); + cell * it = old_head; + cell * new_head = 0; + cell * prev = 0; + do { + expr * k = it->m_key; + unsigned h = k->hash(); + unsigned mask = new_capacity - 1; + unsigned idx = h & mask; + + cell * begin = new_table + idx; + cell * end = new_table + new_capacity; + cell * curr = begin; + + for (; curr != end; ++curr) { + if (curr->m_key == 0) { + curr->m_key = k; + curr->m_value = it->m_value; + LCS_CODE(curr->m_hits = it->m_hits;); + LCS_CODE(curr->m_birthday = it->m_birthday;); + if (prev == 0) { + new_head = curr; + } + else { + prev->m_next = curr; + curr->m_prev = prev; + } + goto end; + } + } + for (curr = new_table; curr != begin; ++curr) { + if (curr->m_key == 0) { + curr->m_key = k; + curr->m_value = it->m_value; + SASSERT(prev != 0); + prev->m_next = curr; + curr->m_prev = prev; + } + goto end; + } + UNREACHABLE(); + end: + prev = curr; + it = it->m_next; + } + while (it != old_head); + prev->m_next = new_head; + new_head->m_prev = prev; + return new_head; +} + +void lru_cache::expand_table() { + SASSERT(m_head); + unsigned new_capacity = m_capacity * 2; + TRACE("lru_cache", tout << "expanding table new_capacity: " << new_capacity << "\n";); + cell * new_table = allocate(new_capacity); + m_head = copy_table(m_head, new_table, new_capacity); + deallocate(m_table); + m_table = new_table; + m_capacity = new_capacity; + m_num_deleted = 0; + SASSERT(check_invariant()); +} + +void lru_cache::remove_deleted() { + SASSERT(m_head); + TRACE("lru_cache", tout << "removing deleted entries\n";); + cell * new_table = allocate(m_capacity); + m_head = copy_table(m_head, new_table, m_capacity); + deallocate(m_table); + m_table = new_table; + m_num_deleted = 0; + SASSERT(check_invariant()); +} + +void lru_cache::init() { + if (m_max_size < MIN_MAX_SIZE) + m_max_size = MIN_MAX_SIZE; + m_size = 0; + m_capacity = INITIAL_CAPACITY; + m_table = allocate(m_capacity); + m_head = 0; + m_num_deleted = 0; +} + +lru_cache::lru_cache(ast_manager & m): + m_manager(m), + m_max_size(m.get_num_asts() * 100) { + init(); + TRACE("lru_cache", tout << "new lru cache, max-size: " << m_max_size << "\n";); +} + +lru_cache::lru_cache(ast_manager & m, unsigned max_size): + m_manager(m), + m_max_size(max_size) { + init(); +} + +void lru_cache::dec_refs() { + if (m_head) { + cell * curr = m_head; +#ifdef Z3DEBUG + unsigned sz = 0; +#endif + do { + m_manager.dec_ref(curr->m_key); + m_manager.dec_ref(curr->m_value); + curr = curr->m_next; + DEBUG_CODE(sz++;); + } + while (curr != m_head); + SASSERT(sz == m_size); + } +} + +lru_cache::~lru_cache() { + TRACE("lru_cache", tout << "destructor invoked size: " << m_size << ", m_head: " << m_head << "\n";); + LCS_CODE({ + if (m_head) { + unsigned used = 0; + cell * curr = m_head; + do { + if (curr->m_hits > 0) + used++; + curr = curr->m_next; + } + while (curr != m_head); + verbose_stream() << "[lru_cache] num-used: " << used << " size: " << m_size << "\n"; + } + }); + SASSERT(check_invariant()); + dec_refs(); + deallocate(m_table); +} + +void lru_cache::del_least_used() { + SASSERT(m_head); + SASSERT(m_size >= 2); + cell * c = m_head->m_prev; + TRACE("lru_cache", tout << "del least used: " << mk_bounded_pp(c->m_key, m_manager, 3) << "\n";); + LCS_CODE({ + static unsigned non_zero = 0; + static unsigned long long total_hits; + static unsigned counter = 0; + if (c->m_hits > 0) { + counter++; + total_hits += c->m_hits; + non_zero++; + if (non_zero % 1000 == 0) + verbose_stream() << "[lru_cache] cell with non-zero hits was deleted: " << non_zero << " avg: " << ((double)total_hits/(double) counter) << std::endl; + } + }); + SASSERT(c->m_prev != c); + SASSERT(c->m_next != c); + m_manager.dec_ref(c->m_key); + m_manager.dec_ref(c->m_value); + c->m_prev->m_next = c->m_next; + c->m_next->m_prev = c->m_prev; + SASSERT(m_head->m_prev == c->m_prev); + c->m_key = reinterpret_cast(1); + c->m_prev = 0; + c->m_next = 0; + m_size--; + m_num_deleted++; + CASSERT("lru_cache", check_invariant()); + if (m_num_deleted * 3 > m_capacity) + remove_deleted(); +} + +void lru_cache::add_front(cell * c) { + SASSERT(c->m_next == 0); + SASSERT(c->m_prev == 0); + if (m_head == 0) { + c->m_next = c; + c->m_prev = c; + m_head = c; + } + else { + c->m_prev = m_head->m_prev; + c->m_next = m_head; + m_head->m_prev->m_next = c; + m_head->m_prev = c; + m_head = c; + } + CASSERT("lru_cache", check_invariant()); + SASSERT(m_head == c); +} + +void lru_cache::move_front(cell * c) { + SASSERT(m_head); + SASSERT(c->m_next); + SASSERT(c->m_prev); + if (m_head != c) { + c->m_prev->m_next = c->m_next; + c->m_next->m_prev = c->m_prev; + + c->m_prev = m_head->m_prev; + c->m_next = m_head; + + m_head->m_prev->m_next = c; + m_head->m_prev = c; + + m_head = c; + } + CASSERT("lru_cache", check_invariant()); + SASSERT(m_head == c); +} + +void lru_cache::insert(expr * k, expr * v) { + LCS_CODE(m_time++;); + if (m_size == m_max_size) + del_least_used(); + else if (m_size * 2 > m_capacity) + expand_table(); + SASSERT(m_size < m_max_size); + unsigned h = k->hash(); + unsigned mask = m_capacity - 1; + unsigned idx = h & mask; + + cell * begin = m_table + idx; + cell * end = m_table + m_capacity; + cell * curr = begin; + cell * del_cell = 0; + +#define INSERT_LOOP() \ + if (curr->m_key == 0) { \ + cell * new_cell; \ + if (del_cell) { \ + new_cell = del_cell; \ + m_num_deleted--; \ + } \ + else { \ + new_cell = curr; \ + } \ + m_manager.inc_ref(k); \ + m_manager.inc_ref(v); \ + new_cell->m_key = k; \ + new_cell->m_value = v; \ + LCS_CODE(new_cell->m_hits = 0;); \ + LCS_CODE(new_cell->m_birthday = m_time;); \ + m_size++; \ + add_front(new_cell); \ + return; \ + } \ + if (curr->m_key == reinterpret_cast(1)) { \ + del_cell = curr; \ + continue; \ + } \ + if (curr->m_key == k) { \ + m_manager.inc_ref(v); \ + m_manager.dec_ref(curr->m_value); \ + curr->m_value = v; \ + LCS_CODE(curr->m_hits = 0;); \ + LCS_CODE(curr->m_birthday = m_time;); \ + move_front(curr); \ + return; \ + } + + for (; curr != end; ++curr) { + INSERT_LOOP(); + } + for (curr = m_table; curr != begin; ++curr) { + INSERT_LOOP(); + } + UNREACHABLE(); +} + +expr * lru_cache::find(expr * k) { + unsigned h = k->hash(); + unsigned mask = m_capacity - 1; + unsigned idx = h & mask; + +#ifdef LRU_CACHE_STATISTICS + static unsigned long long total_age = 0; + static unsigned long long max_time = 0; + static unsigned counter = 0; +#define COLLECT() \ + if (curr->m_hits == 0) { \ + counter ++; \ + unsigned age = m_time - curr->m_birthday; \ + if (age > max_time) \ + max_time = age; \ + total_age += age; \ + if (counter % 1000 == 0) \ + verbose_stream() << "[lru_cache] avg time for first hit: " << ((double) total_age / (double) counter) << " max time: " << max_time << "\n"; \ + } +#endif + + cell * begin = m_table + idx; + cell * end = m_table + m_capacity; + cell * curr = begin; + for (; curr != end; ++curr) { + if (curr->m_key == k) { + // LCS_CODE(COLLECT()); + LCS_CODE(if (curr->m_hits == 0 && m_time - curr->m_birthday >= 5000) return 0;) + LCS_CODE(curr->m_hits++;); + move_front(curr); + return curr->m_value; + } + if (curr->m_key == 0) + return 0; + } + for (curr = m_table; curr != begin; ++curr) { + if (curr->m_key == k) { + // LCS_CODE(COLLECT()); + LCS_CODE(curr->m_hits++;); + LCS_CODE(if (curr->m_hits == 0 && m_time - curr->m_birthday >= 5000) return 0;); + move_front(curr); + return curr->m_value; + } + if (curr->m_key == 0) + return 0; + } + return 0; +} + +void lru_cache::reset() { + TRACE("lru_cache", tout << "reset... m_size: " << m_size << "\n";); + LCS_CODE(m_time = 0;); + if (m_head) { + cell * curr = m_head; +#ifdef Z3DEBUG + unsigned sz = 0; +#endif + do { + m_manager.dec_ref(curr->m_key); + m_manager.dec_ref(curr->m_value); + cell * next = curr->m_next; + curr->m_key = 0; + curr->m_value = 0; + curr->m_next = 0; + curr->m_prev = 0; + LCS_CODE(curr->m_hits = 0;); + LCS_CODE(curr->m_birthday = 0;); + curr = next; + DEBUG_CODE(sz++;); + } + while (curr != m_head); + SASSERT(sz == m_size); + m_head = 0; + m_size = 0; + SASSERT(check_invariant()); + } +} + +void lru_cache::cleanup() { + dec_refs(); + deallocate(m_table); + m_capacity = INITIAL_CAPACITY; + m_table = allocate(m_capacity); + m_head = 0; + m_size = 0; + m_num_deleted = 0; +} + +bool lru_cache::check_invariant() const { + SASSERT(m_size <= m_max_size); + cell * begin = m_table; + cell * end = m_table + m_capacity; + unsigned sz = 0; + if (m_head) { + cell * curr = m_head; + do { + sz++; + SASSERT(curr->m_key != 0 && curr->m_key != reinterpret_cast(1)); + SASSERT(curr->m_next->m_prev == curr); + SASSERT(curr->m_prev->m_next == curr); + SASSERT(curr < end); + SASSERT(curr >= begin); + curr = curr->m_next; + } + while (curr != m_head); + } + SASSERT(m_size == sz); + sz = 0; + unsigned num_deleted = 0; + for (cell * it = begin; it != end; it++) { + if (it->m_key == reinterpret_cast(1)) { + num_deleted++; + continue; + } + if (it->m_key != 0) { + sz++; + } + } + SASSERT(m_size == sz); + SASSERT(m_num_deleted == num_deleted); + return true; +} diff --git a/lib/lru_cache.h b/lib/lru_cache.h new file mode 100644 index 000000000..b9c72a01c --- /dev/null +++ b/lib/lru_cache.h @@ -0,0 +1,80 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + lru_cache.h + +Abstract: + + expr -> expr LRU cache + +Author: + + Leonardo (leonardo) 2011-04-12 + +Notes: + +--*/ +#ifndef _LRU_CACHE_H_ +#define _LRU_CACHE_H_ + +#include"ast.h" + +// #define LRU_CACHE_STATISTICS + +#ifdef LRU_CACHE_STATISTICS +#define LCS_CODE(CODE) { CODE } +#else +#define LCS_CODE(CODE) +#endif + +class lru_cache { + struct cell { + expr * m_key; + expr * m_value; + cell * m_prev; + cell * m_next; +#ifdef LRU_CACHE_STATISTICS + unsigned m_hits; + unsigned m_birthday; +#endif + }; + + ast_manager & m_manager; + cell * m_table; + cell * m_head; + unsigned m_size; + unsigned m_max_size; + unsigned m_capacity; + unsigned m_num_deleted; +#ifdef LRU_CACHE_STATISTICS + unsigned m_time; +#endif + + static cell * allocate(unsigned capacity); + static void deallocate(cell * table); + static cell * copy_table(cell * old_head, cell * new_table, unsigned new_capacity); + + void del_least_used(); + void add_front(cell * c); + void move_front(cell * c); + void expand_table(); + void remove_deleted(); + void init(); + void dec_refs(); +public: + lru_cache(ast_manager & m); + lru_cache(ast_manager & m, unsigned max_size); + ~lru_cache(); + void insert(expr * k, expr * v); + expr * find(expr * k); + void reset(); + void cleanup(); + unsigned size() const { return m_size; } + unsigned capacity() const { return m_capacity; } + bool empty() const { return m_size == 0; } + bool check_invariant() const; +}; + +#endif diff --git a/lib/lu.cpp b/lib/lu.cpp new file mode 100644 index 000000000..b2cf0fd88 --- /dev/null +++ b/lib/lu.cpp @@ -0,0 +1,1792 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + lu.cpp + +Abstract: + + Simple LU factorization module based on the paper: + + "Maintaining LU factors of a General Sparse Matrix" + P. E. Gill, W. Murray, M. Saunders, M. Wright + +Author: + + Leonardo de Moura (leonardo) 2011-06-09 + +Revision History: + +--*/ +#include"lu.h" + +template +void lu::todo::init(unsigned capacity) { + m_elem2len.reset(); + m_elem2pos.reset(); + m_elems_per_len.reset(); + m_elem2len.resize(capacity, UINT_MAX); + m_elem2pos.resize(capacity, UINT_MAX); + m_elems_per_len.resize(capacity+1); + m_size = 0; +} + +template +void lu::todo::display(std::ostream & out) const { + vector::const_iterator it = m_elems_per_len.begin(); + vector::const_iterator end = m_elems_per_len.end(); + for (unsigned idx = 0; it != end; ++it, ++idx) { + unsigned_vector const & v = *it; + if (!v.empty()) { + out << idx << ": ["; + unsigned_vector::const_iterator it2 = v.begin(); + unsigned_vector::const_iterator end2 = v.end(); + for (bool first = true; it2 != end2; ++it2) { + if (first) first = false; else out << " "; + out << *it2; + } + out << "] "; + } + } +} + +template +void lu::todo::updt_len(unsigned elem, unsigned len) { + erase(elem); + m_elem2len[elem] = len; + unsigned pos = m_elems_per_len[len].size(); + m_elems_per_len[len].push_back(elem); + m_elem2pos[elem] = pos; + m_size++; +} + +template +void lu::todo::erase(unsigned elem) { + if (!contains(elem)) + return; + unsigned len = m_elem2len[elem]; + unsigned pos = m_elem2pos[elem]; + unsigned_vector & v = m_elems_per_len[len]; + SASSERT(v[pos] == elem); + if (pos != v.size() - 1) { + unsigned last_elem = v.back(); + m_elem2pos[last_elem] = pos; + v[pos] = last_elem; + } + v.pop_back(); + m_elem2pos[elem] = UINT_MAX; + m_size--; +} + +template +void lu::todo::iterator::find_next() { + unsigned sz = m_todo.m_elems_per_len.size(); + while (m_i < sz) { + if (m_j < m_todo.m_elems_per_len[m_i].size()) + return; + m_j = 0; + m_i++; + } +} + +// ----------------------- +// +// Main +// +// ----------------------- +template +lu::lu(manager & m, params_ref const & p): + m_manager(m), + m_tmp_xU_vector(m, 0), + m_tmp_replace_column_vector(m, 0), + m_tmp_vector(m, 0), + m_tmp_row(m, 0), + m_tmp_col(m, 0) { + m_sz = 0; + CASSERT("lu", check_invariant()); + ini = false; + updt_params(p); +} + +template +lu::~lu() { + m().del(m_mu); + + // temporary values + m().del(tol); + m().del(C_max); + m().del(A_ij); + m().del(A_best); + m().del(A_aux); + m().del(tmp); + m().del(mu_best); + m().del(mu_1); + + del_nums(L.A); + del_nums(U.A); + del_nums(T.A); +} + +template +void lu::updt_params(params_ref const & p) { + unsigned mu = p.get_uint(":lu-mu", 100); // it was 10... + m().set(m_mu, mu); + m_selection_cutoff = p.get_uint(":lu-selection-cutoff", 10); +} + +/** + \brief Delete the numerals in the given vector, and reset it. +*/ +template +void lu::del_nums(numeral_vector & nums) { + typename numeral_vector::iterator it = nums.begin(); + typename numeral_vector::iterator end = nums.end(); + for (; it != end; ++it) { + m().del(*it); + } + nums.reset(); +} + +template +void lu::reset() { + m_sz = 0; + + P.reset(m_sz); + Q.reset(m_sz); + + del_nums(L.A); + L.indc.reset(); + L.indr.reset(); + + del_nums(U.A); + U.indr.reset(); + U.begr.reset(); + U.endr.reset(); + U.num_entries = 0; + + T.indr.reset(); + T.begr.reset(); + T.endr.reset(); + del_nums(T.A); + T.indc.reset(); + T.begc.reset(); + T.endc.reset(); + T.num_entries = 0; + + locw.reset(); +} + +// ----------------------- +// +// Initialization +// +// ----------------------- +#define INI_COL_SZ 16 +#define INV_FILLIN_FACTOR 5 +#define COMPRESSION_FACTOR 4 + +template +inline unsigned lu::fillin_for(unsigned sz) { + return (sz / INV_FILLIN_FACTOR) + 1; +} + +template +void lu::init(unsigned size) { + reset(); + + m_num_replacements = 0; + + m_sz = size; + + m_todo_rows.init(size); + m_todo_cols.init(size); + m_enabled_rows.reset(); + m_enabled_cols.reset(); + m_enabled_rows.resize(m_sz, true); + m_enabled_cols.resize(m_sz, true); + + P.reset(m_sz); + Q.reset(m_sz); + + locw.reset(); // force it to be reinitialized, it may contain unintended content if LU was interrupted due to floating point imprecision. + locw.resize(m_sz, UINT_MAX); + + ini = size > 0; + ini_irow = 0; + T.begr.push_back(0); + T.endr.push_back(0); + T.num_entries = 0; + + SASSERT(T.begc.empty()); + SASSERT(T.endc.empty()); + + T.A.resize(m_sz * INI_COL_SZ); + T.indc.resize(m_sz * INI_COL_SZ, UINT_MAX); + unsigned locc = 0; + for (unsigned i = 0; i < m_sz; i++) { + T.begc.push_back(locc); + T.endc.push_back(locc); + locc += INI_COL_SZ; + } + + SASSERT(T.begc.size() == m_sz); + SASSERT(T.endc.size() == m_sz); + + m_tmp_vector.reset(m_sz); + m_tmp_col.reset(m_sz); + m_tmp_xU_vector.reset(m_sz); + m_tmp_replace_column_vector.reset(m_sz); +} + +template +void lu::add_entry(numeral const & a, unsigned x) { + SASSERT(T.endc[x] >= T.begc[x]); + SASSERT(T.endc[x] - T.begc[x] + 1 <= m_sz); // has space for another element in column x + SASSERT(ini); + SASSERT(x < m_sz); + TRACE("lu", tout << "add_entry(" << m().to_string(a) << ", " << x << ")\n";); + if (T.endc[x] == T.indc.size()) { + // expand last column + T.A.push_back(numeral()); + m().set(T.A.back(), a); + T.indc.push_back(ini_irow); + T.endc[x]++; + SASSERT(T.endc[x] == T.indc.size()); + } + else { + if (T.indc[T.endc[x]] != UINT_MAX) { + TRACE("lu", tout << "moving column to the end: " << x << "\n";); + move_col_to_end(x); + } + SASSERT(T.indc[T.endc[x]] == UINT_MAX); + // use free space + unsigned pos = T.endc[x]; + m().set(T.A[pos], a); + T.indc[pos] = ini_irow; + T.endc[x]++; + } + T.indr.push_back(x); + T.endr.back()++; + T.num_entries++; + SASSERT(T.endc[x] >= T.begc[x]); + SASSERT(T.endc[x] - T.begc[x] <= m_sz); +} + +template +void lu::move_col_to_end(unsigned c) { + SASSERT(T.endc[c] >= T.begc[c]); + SASSERT(T.endc[c] - T.begc[c] <= m_sz); + unsigned begin = T.begc[c]; + unsigned end = T.endc[c]; + T.begc[c] = T.indc.size(); + T.endc[c] = T.begc[c] + end - begin; + for (unsigned j = begin; j < end; j++) { + T.A.push_back(numeral()); + m().swap(T.A.back(), T.A[j]); + unsigned r = T.indc[j]; + T.indc.push_back(r); + T.indc[j] = UINT_MAX; // mark as free + } + unsigned sz = end - begin; + unsigned fillin = fillin_for(sz); + for (unsigned i = 0; i < fillin; i++) { + T.indc.push_back(UINT_MAX); // leave space + T.A.push_back(numeral()); + } + SASSERT(T.endc[c] >= T.begc[c]); + SASSERT(T.endc[c] - T.begc[c] <= m_sz); +} + +template +void lu::move_row_to_end(unsigned r) { + unsigned begin = T.begr[r]; + unsigned end = T.endr[r]; + T.begr[r] = T.indr.size(); + T.endr[r] = T.begr[r] + end - begin; + for (unsigned j = begin; j < end; j++) { + unsigned c = T.indr[j]; + T.indr.push_back(c); + T.indr[j] = UINT_MAX; // mark as free + } + unsigned sz = end - begin; + unsigned fillin = fillin_for(sz); + for (unsigned i = 0; i < fillin; i++) + T.indr.push_back(UINT_MAX); +} + +template +void lu::end_row() { + TRACE("lu", tout << "end_row()\n";); + SASSERT(ini); + ini_irow++; + if (ini_irow == m_sz) { + // completed initialization + ini = false; + CASSERT("lu", check_T()); + } + else { + unsigned sz = T.endr.back() - T.begr.back(); + unsigned fillin = fillin_for(sz); + for (unsigned i = 0; i < fillin; i++) + T.indr.push_back(UINT_MAX); // leave space + T.begr.push_back(T.indr.size()); + T.endr.push_back(T.indr.size()); + } +} + +// ----------------------- +// +// Factorization +// +// ----------------------- + +// Initialize the todo vectors: todo_rows and todo_cols. +// The auxiliary vectors iplocr and iplocc are also initialized. +template +void lu::init_fact() { + for (unsigned i = 0; i < m_sz; i++) { + SASSERT(T.endr[i] >= T.begr[i]); + SASSERT(T.endc[i] >= T.begc[i]); + SASSERT(T.endr[i] - T.begr[i] <= m_sz); + SASSERT(T.endc[i] - T.begc[i] <= m_sz); + m_todo_rows.updt_len(i, T.endr[i] - T.begr[i]); + m_todo_cols.updt_len(i, T.endc[i] - T.begc[i]); + } + m_marked_rows.reset(); + m_marked_rows.resize(m_sz, false); +} + +template +bool lu::stability_test(unsigned rin, unsigned cin, bool improvingM) { + if (NM::precise()) { + if (improvingM) { + // must save coefficient of rin, cin in A_best + unsigned begin = T.begc[cin]; + unsigned end = T.endc[cin]; + for (unsigned j = begin; j < end; j++) { + unsigned r = T.indc[j]; + if (rin == r) { + m().set(A_best, T.A[j]); + break; + } + } + return true; + } + else { + return false; + } + } + // Stability test for imprecise numerals + // See section 5.3 of "Maintaining LU factors of a General Sparse Matrix" + m().set(C_max, 0); + m().set(tol, 0); + m().set(A_ij, 0); + bool C_max_init = false; + unsigned begin = T.begc[cin]; + unsigned end = T.endc[cin]; +#if 0 + static unsigned stability_test_counter = 0; + static unsigned stability_test_cost = 0; + stability_test_counter++; + stability_test_cost += end - begin; + if (stability_test_counter % 1000000 == 0) { + verbose_stream() << "[stability-test] cost: " << stability_test_cost << " num-calls: " << stability_test_counter + << " selection-cutoff: " << m_selection_cutoff << " sz: " << m_sz + << " avg-col-sz: " << stability_test_cost / stability_test_counter << "\n"; + } +#endif + for (unsigned j = begin; j < end; j++) { + unsigned r = T.indc[j]; + if (rin == r) { + m().set(A_ij, T.A[j]); + m().set(tol, A_ij); + m().abs(tol); + if (improvingM) { + // tol = |A_ij| / mu + m().mul(tol, m_mu, tol); + } + else { + // tol = |A_ij| / mu_best + m().mul(tol, mu_best, tol); + } + if (C_max_init && m().ge(C_max, tol)) { + TRACE("stability", tout << "failure 1. C_max: " << m().to_string(C_max) << ", tol: " << m().to_string(tol) + << ", A_ij: " << m().to_string(A_ij) << ", m_mu: " << m().to_string(m_mu) + << ", mu_best: " << m().to_string(mu_best) << ", improvingM: " << improvingM << "\n";); + return false; + } + continue; + } + if (!enabled_row(r)) + continue; + m().set(A_aux, T.A[j]); + m().abs(A_aux); + if (m().gt(A_aux, C_max)) { + m().set(C_max, A_aux); + C_max_init = true; + if (m().is_pos(tol)) { + // if tol was already set test, then reject if C_max >= tol + if (m().ge(C_max, tol)) { + TRACE("stability", tout << "failure 2. C_max: " << m().to_string(C_max) << ", tol: " << m().to_string(tol) + << ", A_ij: " << m().to_string(A_ij) << ", m_mu: " << m().to_string(m_mu) + << ", mu_best: " << m().to_string(mu_best) << "\n";); + return false; + } + } + } + } + + m().set(A_best, A_ij); + m().set(mu_best, C_max); + m().abs(A_ij); + m().div(mu_best, A_ij, mu_best); + CTRACE("stability", m().is_zero(mu_best), tout << "found: A_ij: " << m().to_string(A_ij) << " mu_best: " << m().to_string(mu_best) + << " C_max: " << m().to_string(C_max) << "\n";); + return true; +} + +/** + \brief Select the next pivot and store in (r_out, c_out) + The coefficient is stored in A_best. +*/ +template +void lu::select_pivot(unsigned & r_out, unsigned & c_out) { + unsigned M_best = UINT_MAX; + unsigned M2_best = UINT_MAX; + m().set(mu_best, 0); + + SASSERT(m_todo_rows.size() == m_todo_cols.size()); + TRACE("lu_todo", + tout << "rows: "; m_todo_rows.display(tout); tout << "\n"; + tout << "cols: "; m_todo_cols.display(tout); tout << "\n";); + + typename todo::iterator it(m_todo_rows); + unsigned counter = 0; + while (!it.at_end()) { + unsigned r = it.curr(); + it.next(); + counter++; + TRACE("lu_todo", tout << "row r: " << r << ", r_out: " << r_out << ", c_out: " << c_out << "\n";); + SASSERT(enabled_row(r)); + if (counter >= m_selection_cutoff && M_best != UINT_MAX) + break; + CTRACE("stability", counter > m_selection_cutoff, + tout << "counter: " << counter << " mu_best: " << m().to_string(mu_best) << " M_best: " << M_best << "\n";); + unsigned lenr = m_todo_rows.len(r); + unsigned begin = T.begr[r]; + unsigned end = T.endr[r]; +#if 0 + // ----------------------------------- + // enable this block of code to simulate singular matrix exception after a couple of pivoting steps... +#define NUM_STEPS_UNTIL_EXCEPTION 10 + static unsigned g_num_pivots = 0; + if (!m().precise()) { + g_num_pivots++; + if (g_num_pivots == NUM_STEPS_UNTIL_EXCEPTION) { + g_num_pivots = 0; + throw lu_exception("singular matrix"); + } + } + //------------------------------------ +#endif + if (lenr == 0) + throw lu_exception("singular matrix"); + for (unsigned j = begin; j < end; j++) { + unsigned c = T.indr[j]; + if (!enabled_col(c)) + continue; + unsigned lenc = m_todo_cols.len(c); + unsigned M = (lenc - 1) * (lenr - 1); + TRACE("lu_todo", tout << "c: " << c << ", M: " << M << ", M_best: " << M_best << "\n";); + if (M > M_best) + continue; + bool improving; + if (NM::precise()) + improving = M_best == UINT_MAX || M < M_best || (M == M_best && lenc < M2_best); + else + improving = M_best == UINT_MAX || M < M_best; + if (stability_test(r, c, improving)) { + M_best = M; + M2_best = lenc; + r_out = r; + c_out = c; + CTRACE("stability", !m().precise(), tout << "mu_best: " << m().to_string(mu_best) << "\n";); + } + else { + CTRACE("stability", !m().precise(), + tout << "failed stability, improving: " << improving << " mu_best: " << m().to_string(mu_best) << "\n";); + } + } + } + + if (M_best == UINT_MAX) + throw lu_exception("failed stability test"); + + typename todo::iterator it2(m_todo_cols); + counter = 0; + while (!it2.at_end()) { + unsigned c = it2.curr(); + it2.next(); + counter++; + TRACE("lu_todo", tout << "col c: " << c << ", r_out: " << r_out << ", c_out: " << c_out << "\n";); + SASSERT(enabled_col(c)); + if (counter >= m_selection_cutoff) + break; + unsigned lenc = m_todo_cols.len(c); + unsigned begin = T.begc[c]; + unsigned end = T.endc[c]; + for (unsigned j = begin; j < end; j++) { + unsigned r = T.indc[j]; + if (!enabled_row(r)) + continue; + unsigned lenr = m_todo_rows.len(r); + unsigned M = (lenc - 1) * (lenr - 1); + TRACE("lu_todo", tout << "r: " << r << ", M: " << M << ", M_best: " << M_best << "\n";); + if (M > M_best) + continue; + bool improving; + if (NM::precise()) + improving = M < M_best || (M == M_best && lenc < M2_best); + else + improving = M < M_best; + if (stability_test(r, c, M < M_best)) { + M_best = M; + M2_best = lenc; + r_out = r; + c_out = c; + } + } + } +} + +/** + \brief For debugging: checks whether all position in locw are UINT_MAX. +*/ +template +bool lu::check_locw() const { + for (unsigned i = 0; i < m_sz; i++) { + SASSERT(locw[i] == UINT_MAX); + } + return true; +} + +template +inline void lu::dec_lenr(unsigned r) { + SASSERT(m_todo_rows.len(r) > 0); + SASSERT(enabled_row(r)); + m_todo_rows.updt_len(r, m_todo_rows.len(r) - 1); +} + +template +inline void lu::inc_lenr(unsigned r) { + SASSERT(enabled_row(r)); + m_todo_rows.updt_len(r, m_todo_rows.len(r) + 1); +} + +template +inline void lu::dec_lenc(unsigned c) { + SASSERT(m_todo_cols.len(c) > 0); + SASSERT(enabled_col(c)); + m_todo_cols.updt_len(c, m_todo_cols.len(c) - 1); +} + +template +inline void lu::inc_lenc(unsigned c) { + SASSERT(enabled_col(c)); + m_todo_cols.updt_len(c, m_todo_cols.len(c) + 1); +} + +/** + \brief Remove the disabled columns from the row r. +*/ +template +void lu::del_disabled_cols(unsigned r) { + unsigned begin = T.begr[r]; + unsigned end = T.endr[r]; + unsigned j = begin; + for (unsigned i = begin; i < end; i++) { + unsigned c = T.indr[i]; + if (!enabled_col(c)) + continue; + T.indr[j] = c; + j++; + } + T.endr[r] = j; + for (; j < end; j++) + T.indr[j] = UINT_MAX; +} + +/** + \brief Remove colum c from row r. + It also removes any disabled column. +*/ +template +void lu::del_row_entry(unsigned r, unsigned c) { + unsigned begin = T.begr[r]; + unsigned end = T.endr[r]; + TRACE("del_row_entry", tout << "del_row_entry, r: " << r << ", c: " << c << ", begin: " << begin << ", end: " << end << "\n";); + unsigned j = begin; + for (unsigned i = begin; i < end; i++) { + unsigned c_prime = T.indr[i]; + if (c_prime == c || !enabled_col(c_prime)) + continue; + T.indr[j] = c_prime; + j++; + } + SASSERT(j < end); + T.endr[r] = j; + for (; j < end; j++) + T.indr[j] = UINT_MAX; + TRACE("del_row_entry", tout << "after del_row_entry, begin: " << T.begr[r] << ", end: " << T.endr[r] << "\n";); +} + +/** + \brief Compress T rows +*/ +template +void lu::compress_rows() { + unsigned_vector new_indr; + for (unsigned r = 0; r < m_sz; r++) { + unsigned begin = T.begr[r]; + unsigned end = T.endr[r]; + T.begr[r] = new_indr.size(); + for (unsigned i = begin; i < end; i++) { + new_indr.push_back(T.indr[i]); + } + T.endr[r] = new_indr.size(); + unsigned fillin = T.endr[r] - T.begr[r]; + for (unsigned i = 0; i < fillin; i++) + T.indr.push_back(UINT_MAX); + } + T.indr.swap(new_indr); + TRACE("lu_bug", tout << "compressed rows\n";); + CASSERT("lu", check_T()); +} + +/** + \brief Compress T columns +*/ +template +void lu::compress_columns() { + CASSERT("lu", check_T()); + unsigned_vector new_indc; + numeral_vector new_A; + for (unsigned c = 0; c < m_sz; c++) { + unsigned begin = T.begc[c]; + unsigned end = T.endc[c]; + T.begc[c] = new_indc.size(); + for (unsigned i = begin; i < end; i++) { + new_A.push_back(numeral()); + m().swap(new_A.back(), T.A[i]); + new_indc.push_back(T.indc[i]); + } + T.endc[c] = new_indc.size(); + unsigned fillin = T.endc[c] - T.begc[c]; + for (unsigned i = 0; i < fillin; i++) + T.indc.push_back(UINT_MAX); + } + T.indc.swap(new_indc); + T.A.swap(new_A); + // I don't need to delete the elements of new_A, since I did not use m().set, but m().swap. + TRACE("lu_bug", tout << "compressed columns\n";); + CASSERT("lu", check_T()); +} + +template +void lu::compress_if_needed() { + if (T.indr.size() > COMPRESSION_FACTOR * T.num_entries) + compress_rows(); + if (T.indc.size() > COMPRESSION_FACTOR * T.num_entries) + compress_columns(); + CASSERT("lu", check_lenr() && check_lenc()); +} + +template +void lu::add_row_entry(unsigned r, unsigned c) { + if (T.endr[r] == T.indr.size()) { + // expand last row + T.indr.push_back(c); + T.endr[r]++; + SASSERT(T.endr[r] == T.indr.size()); + } + else { + if (T.indr[T.endr[r]] != UINT_MAX) + move_row_to_end(r); + // use free space + SASSERT(T.indr[T.endr[r]] == UINT_MAX); + T.indr[T.endr[r]] = c; + T.endr[r]++; + } +} + +template +void lu::add_col_entry(unsigned r, unsigned c, numeral const & a) { + if (T.endc[c] == T.indc.size()) { + // expand last column + T.A.push_back(numeral()); + m().set(T.A.back(), a); + T.indc.push_back(r); + T.endc[c]++; + SASSERT(T.endc[c] == T.indc.size()); + } + else { + if (T.indc[T.endc[c]] != UINT_MAX) + move_col_to_end(c); + SASSERT(T.indc[T.endc[c]] == UINT_MAX); + // use free space + unsigned pos = T.endc[c]; + m().set(T.A[pos], a); + T.indc[pos] = r; + T.endc[c]++; + } +} + +template +void lu::process_pivot_core(unsigned r, unsigned c) { + SASSERT(m_todo_cols.len(c) > 0); + if (m_todo_cols.len(c) == 1) { + // simple case... just update lenc for every c_prime != c in r. + unsigned begin = T.begr[r]; + unsigned end = T.endr[r]; + for (unsigned k = begin; k < end; k++) { + unsigned c_prime = T.indr[k]; + if (c_prime != c) { + SASSERT(enabled_col(c_prime)); + dec_lenc(c_prime); + } + } + return; + } + +#ifdef Z3DEBUG + unsigned num_set_locw = 0; +#endif + // Compute multipliers and update L + m_toadd_rows.reset(); + unsigned begin = T.begc[c]; + unsigned end = T.endc[c]; + for (unsigned k = begin; k < end; k++) { + unsigned r_prime = T.indc[k]; + if (r_prime == r) { + SASSERT(m().eq(A_best, T.A[k])); + continue; + } + + if (!enabled_row(r_prime)) + continue; + + // mu_1 = - A_(r_prime, c) / A_(r,c) + m().set(mu_1, T.A[k]); + m().div(mu_1, A_best, mu_1); + // hack: mu_1 contains A_(r_prime, c) / A_(r,c) at this point + // since we have to store -mu_1 in L + // store (- mu_1, r_prime, r) in L + L.A.push_back(numeral()); + m().set(L.A.back(), mu_1); + // r_prime is the row being updated + // with r_prime += mu_1 * r + L.indc.push_back(r_prime); + L.indr.push_back(r); + + m().neg(mu_1); + // Now, mu_1 contains -A_(r_prime, c) / A_(r,c) + + // little hack: temporarily store mu_1 at T.A[k] and save position at locw + m().set(T.A[k], mu_1); + locw[r_prime] = k; + DEBUG_CODE(num_set_locw++;); + m_toadd_rows.push_back(r_prime); + + SASSERT(T_row_contains(r_prime, c)); + // decrement the length of the row since column c will be removed. + dec_lenr(r_prime); + + // the row entry (r_prime, c) is actually removed by del_disabled_cols + T.num_entries--; + } + + // Update every non-zero column of r different from c. + begin = T.begr[r]; + end = T.endr[r]; + TRACE("dec_len_bug", tout << "row r: " << r << ", begin: " << begin << ", end: " << end << "\n";); + for (unsigned k = begin; k < end; k++) { + unsigned c_prime = T.indr[k]; + TRACE("dec_len_bug", tout << "processing c_prime: " << c_prime << ", c: " << c << "\n";); + if (c_prime == c) + continue; + // The row is going to be disabled, so decrementing length of c_prime from r. + // Remark: don't need to do that for c, since it will be disabled. + dec_lenc(c_prime); + TRACE("dec_len_bug", tout << "c_prime: " << c_prime << ", lenc[c_prime] : " << m_todo_cols.len(c_prime) << "\n";); + + SASSERT(enabled_col(c_prime)); + unsigned begin2 = T.begc[c_prime]; + unsigned end2 = T.endc[c_prime]; + // Find coefficient of (r, c_prime) and store it in A_aux + for (unsigned i = begin2; true; i++) { + SASSERT(i < end2); + if (T.indc[i] == r) { + m().set(A_aux, T.A[i]); + break; + } + } + + // Update column + unsigned j = begin2; + for (unsigned i = begin2; i < end2; i++) { + unsigned r_prime = T.indc[i]; + if (r_prime == r // row r is not going to be modified + || !enabled_row(r_prime) // row was already processed (i.e., it is already in the triangular part) + || locw[r_prime] == UINT_MAX // row is not being updated + ) { + if (i != j) { + T.indc[j] = T.indc[i]; + m().set(T.A[j], T.A[i]); + T.indc[i] = UINT_MAX; + } + j++; + continue; + } + // mark row as visited + m_marked_rows[r_prime] = true; + // update T.A[j] with T.A[i] + mu_1 * A_aux, + // where mu_1 is stored at T.A[locw[r_prime]] + m().addmul(T.A[i], T.A[locw[r_prime]], A_aux, T.A[j]); + T.indc[j] = T.indc[i]; + if (i != j) + T.indc[i] = UINT_MAX; + if (m().is_zero(T.A[j])) { + // entry was canceled + dec_lenr(r_prime); + dec_lenc(c_prime); + T.indc[j] = UINT_MAX; + del_row_entry(r_prime, c_prime); + T.num_entries--; + } + else { + j++; + } + } + + T.endc[c_prime] = j; + // Process rows that were not visited. + unsigned num = m_toadd_rows.size(); + for (unsigned i = 0; i < num; i++) { + unsigned r_prime = m_toadd_rows[i]; + if (m_marked_rows[r_prime]) { + // row was already added + m_marked_rows[r_prime] = false; + continue; + } + // add entry (r_prime, c_prime) with coefficient mu_1 * A_aux + // where mu_1 is stored at T.A[locw[r_prime]] + add_row_entry(r_prime, c_prime); + m().mul(T.A[locw[r_prime]], A_aux, tmp); + add_col_entry(r_prime, c_prime, tmp); + inc_lenr(r_prime); + inc_lenc(c_prime); + T.num_entries++; + } + } + + // Reset column c + begin = T.begc[c]; + end = T.endc[c]; + unsigned j = begin; + for (unsigned k = begin; k < end; k++) { + unsigned r_prime = T.indc[k]; + if (r_prime == r || !enabled_row(r_prime)) { + if (j != k) { + T.indc[j] = T.indc[k]; + m().set(T.A[j], T.A[k]); + T.indc[k] = UINT_MAX; + } + j++; + continue; + } + SASSERT(locw[r_prime] != UINT_MAX); + locw[r_prime] = UINT_MAX; + DEBUG_CODE(num_set_locw--;); + } + SASSERT(num_set_locw == 0); + T.endc[c] = j; +} + +template +void lu::process_pivot(unsigned i, unsigned r, unsigned c) { + CASSERT("lu", check_locw()); + del_disabled_cols(r); + SASSERT(T.begr[r] < T.endr[r]); + process_pivot_core(r, c); + + m_todo_rows.erase(r); + m_todo_cols.erase(c); + m_enabled_rows[r] = false; + m_enabled_cols[c] = false; + + P.swap(i, P.inv(r)); + Q.swap(i, Q.inv(c)); + CASSERT("lu", check_locw()); +} + +template +void lu::copy_T_to_U() { + U.num_entries = T.num_entries; + U.begr.resize(m_sz); + U.endr.resize(m_sz); + // reserve space for each row + unsigned pos = 0; + for (unsigned r = 0; r < m_sz; r++) { + U.begr[r] = pos; + U.endr[r] = pos; + unsigned r_sz = T.endr[r] - T.begr[r]; + pos += r_sz; + pos += fillin_for(r_sz); + } + U.A.resize(pos); + U.indr.resize(pos, UINT_MAX); + + // fill rows + for (unsigned c = 0; c < m_sz; c++) { + unsigned c_prime = Q(c); // make sure the first element in each row is the pivot; + unsigned begin = T.begc[c_prime]; + unsigned end = T.endc[c_prime]; + CTRACE("lu_bug", end < begin + 1 || end > begin + c + 1, + tout << "begin: " << begin << ", end: " << end << ", c: " << c << ", c_prime: " << c_prime << "\n"; + display_T(tout); tout << "P: " << P << "\nQ: " << Q << "\n";); + SASSERT(end >= begin + 1); + SASSERT(end <= begin + c + 1); + for (unsigned t_idx = begin; t_idx < end; t_idx++) { + unsigned r = T.indc[t_idx]; + unsigned u_idx = U.endr[r]; + SASSERT(U.indr[u_idx] == UINT_MAX); + m().swap(U.A[u_idx], T.A[t_idx]); + U.indr[u_idx] = c_prime; + U.endr[r]++; + } + } + + SASSERT(check_U()); +} + +template +void lu::fact() { + SASSERT(!ini); + TRACE("fact", display_T(tout);); + init_fact(); + + CASSERT("lu", check_lenr() && check_lenc()); + unsigned r, c; + for (unsigned i = 0; i < m_sz; i++) { + select_pivot(r, c); + + TRACE("lu_pivot", tout << "pivot: " << r << " " << c << "\n";); + + process_pivot(i, r, c); + + TRACE("lu_pivot", tout << "P: " << P << "\nQ: " << Q << "\n";); + CASSERT("lu", check_lenr() && check_lenc()); + + TRACE("lu_pivot_detail", display_T(tout);); + compress_if_needed(); + } + copy_T_to_U(); +} + +// During factorization, the following invariant must hold. +// For every row r, enabled_row(r) => m_todo_rows.len(r) == number of enabled columns in r. +// For every column c, enabled_col(c) => m_todo_cols.len(c) == number of enabled rows in c. + +template +bool lu::check_lenr() const { + for (unsigned r = 0; r < m_sz; r++) { + if (enabled_row(r)) { + unsigned len = 0; + unsigned begin = T.begr[r]; + unsigned end = T.endr[r]; + for (unsigned k = begin; k < end; k++) { + unsigned c = T.indr[k]; + CTRACE("lu_bug", c == UINT_MAX, tout << "r: " << r << ", c: " << c << "\n";); + if (enabled_col(c)) + len++; + } + CTRACE("lu_bug", m_todo_rows.len(r) != len, tout << "failed for row: " << r << " len: " << len << " lenr[r]: " << m_todo_rows.len(r) << "\n"; + display_T(tout);); + SASSERT(m_todo_rows.len(r) == len); + } + } + return true; +} + +template +bool lu::check_lenc() const { + for (unsigned c = 0; c < m_sz; c++) { + if (enabled_col(c)) { + unsigned len = 0; + unsigned begin = T.begc[c]; + unsigned end = T.endc[c]; + for (unsigned k = begin; k < end; k++) { + unsigned r = T.indc[k]; + if (enabled_row(r)) + len++; + } + CTRACE("lu_bug", m_todo_cols.len(c) != len, tout << "failed for column: " << c << " len: " << len << " lenc[c]: " + << m_todo_cols.len(c) << "\n"; display_T(tout);); + SASSERT(m_todo_cols.len(c) == len); + } + } + return true; +} + +// ----------------------- +// +// Invariants +// +// ----------------------- + +template +bool lu::check_P() const { + SASSERT(P.check_invariant()); + return true; +} + +template +bool lu::check_Q() const { + SASSERT(Q.check_invariant()); + return true; +} + +template +bool lu::check_L() const { + SASSERT(L.A.size() == L.indc.size()); + SASSERT(L.A.size() == L.indr.size()); + for (unsigned i = 0; i < L.A.size(); i++) { + SASSERT(L.indc[i] < m_sz); + SASSERT(L.indr[i] < m_sz); + } + return true; +} + +template +bool lu::check_U() const { + unsigned num_entries = 0; + SASSERT(U.begr.size() == m_sz); + SASSERT(U.endr.size() == m_sz); + for (unsigned r = 0; r < m_sz; r++) { + SASSERT(U.begr[r] <= U.endr[r]); + SASSERT(U.endr[r] <= U.A.size()); + for (unsigned j = U.begr[r]; j < U.endr[r]; j++) { + num_entries++; + unsigned c = U.indr[j]; + SASSERT(c < m_sz); // valid variable/column + // it is really upper triangular + CTRACE("lu_bug", Q.inv(c) < P.inv(r), + tout << "c: " << c << ", r: " << r << ", Q.inv(c): " << Q.inv(c) << ", P.inv(r): " << P.inv(r) << "\n"; display_U(tout);); + SASSERT(Q.inv(c) >= P.inv(r)); + } + } + SASSERT(num_entries == U.num_entries); + return true; +} + +/** + \brief Return true if the T column c contains a reference to row r. +*/ +template +bool lu::T_col_contains(unsigned c, unsigned r) const { + for (unsigned i = T.begc[c]; i < T.endc[c]; i++) + if (T.indc[i] == r) + return true; + return false; +} + +/** + \brief Return true if the T row r contains a reference to column c. +*/ +template +bool lu::T_row_contains(unsigned r, unsigned c) const { + for (unsigned i = T.begr[r]; i < T.endr[r]; i++) + if (T.indr[i] == c) + return true; + return false; +} + +template +bool lu::check_T() const { + SASSERT(T.begr.size() == m_sz); + SASSERT(T.endr.size() == m_sz); + + SASSERT(T.A.size() == T.indc.size()); + SASSERT(T.begc.size() == m_sz); + SASSERT(T.endc.size() == m_sz); + + for (unsigned i = 0; i < m_sz; i++) { + SASSERT(T.begr[i] <= T.endr[i]); + SASSERT(T.endr[i] <= T.indr.size()); + for (unsigned j = T.begr[i]; j < T.endr[i]; j++) { + if (enabled_col(T.indr[j])) { + SASSERT(T.indr[j] < m_sz); + SASSERT(T_col_contains(T.indr[j], i)); + } + } + + SASSERT(T.begc[i] <= T.endc[i]); + SASSERT(T.endc[i] <= T.indc.size()); + for (unsigned j = T.begc[i]; j < T.endc[i]; j++) { + if (enabled_row(T.indc[j])) { + SASSERT(T.indc[j] < m_sz); + CTRACE("lu_bug", !T_row_contains(T.indc[j], i), tout << "T.indc[j]: " << T.indc[j] << ", i: " << i << "\n"; display_T(tout);); + SASSERT(T_row_contains(T.indc[j], i)); + } + } + } + return true; +} + +template +bool lu::check_invariant() const { + SASSERT(check_P()); + SASSERT(check_Q()); + if (!ini) { + SASSERT(check_L()); + SASSERT(check_U()); + } + SASSERT(locw.size() == m_sz); + return true; +} + +template +void lu::display_T(std::ostream & out) const { + for (unsigned r = 0; r < m_sz; r++) { + unsigned begin_r = T.begr[r]; + unsigned end_r = T.endr[r]; + for (unsigned j = begin_r; j < end_r; j++) { + unsigned c = T.indr[j]; + if (j > begin_r) + out << " "; + unsigned begin_c = T.begc[c]; + unsigned end_c = T.endc[c]; + unsigned i; + for (i = begin_c; i < end_c; i++) { + if (T.indc[i] == r) { + // found coeff + out << m().to_string(T.A[i]) << "*x" << c; + break; + } + } + if (i == end_c) { + out << "*x" << c; + } + } + out << "\n"; + } +} + +template +void lu::display_U(std::ostream & out, unsigned_vector const * var_ids) const { + out << "U:\n"; + for (unsigned i = 0; i < m_sz; i++) { + unsigned begin = U.begr[i]; + unsigned end = U.endr[i]; + for (unsigned j = begin; j < end; j++) { + if (j > begin) + out << " "; + out << m().to_string(U.A[j]) << "*x"; + if (var_ids) + out << (*var_ids)[U.indr[j]]; + else + out << U.indr[j]; + } + out << "\n"; + } +} + +template +void lu::display_L(std::ostream & out) const { + out << "L: "; + unsigned sz = L.A.size(); + for (unsigned i = 0; i < sz; i++) { + out << "(" << L.indc[i] << ", " << L.indr[i] << ", " << m().to_string(L.A[i]) << ")"; + } + out << "\n"; +} + +template +void lu::display(std::ostream & out, unsigned_vector const * var_ids) const { + out << "P: " << P << "\n"; + out << "Q: " << Q << "\n"; + display_U(out, var_ids); + display_L(out); +} + +template +lu::dense_vector::dense_vector(manager & m, unsigned sz): + m_manager(m) { + m_in_non_zeros.resize(sz, false); + m_values.resize(sz); +} + +template +lu::dense_vector::~dense_vector() { + reset(); +} + +template +void lu::dense_vector::reset() { + iterator it = begin_non_zeros(); + iterator end = end_non_zeros(); + for (; it != end; ++it) { + m().reset(m_values[*it]); + m_in_non_zeros[*it] = false; + } + m_non_zeros.reset(); +} + +template +void lu::dense_vector::reset(unsigned new_sz) { + reset(); + m_in_non_zeros.resize(new_sz, false); + m_values.resize(new_sz); +} + +template +void lu::solve_Lx_eq_y(dense_vector & y) { + TRACE("lu_solve", tout << "before: Lx = y\n"; y.display(tout); tout << "\n";); + SASSERT(y.size() == m_sz); // compatible size + SASSERT(&(y.m()) == &(m())); // same manager + unsigned sz = L.A.size(); + unsigned k = 0; + while (k < sz) { + TRACE("Lx_eq_y", tout << "(" << L.indc[k] << " " << L.indr[k] << " " << m().to_string(L.A[k]) << ")\n"; y.display(tout); tout << "\n";); + unsigned j_k = L.indr[k]; // L.indr contains column numbers. + numeral const & y_j = y[j_k]; + if (m().is_zero(y_j)) { + for (; k < sz && L.indr[k] == j_k; k++) + ; + continue; + } + numeral const & mu_k = L.A[k]; + unsigned i_k = L.indc[k]; // L.indc contains row numbers + numeral & y_i = y.get(i_k); + m().submul(y_i, mu_k, y_j, y_i); + k++; + } + TRACE("lu_solve", tout << "after Lx = y\n"; y.display(tout); tout << "\n";); +} + +template +void lu::solve_Ux_eq_y(dense_vector & y) { + TRACE("lu_solve", tout << "before: Ux = y\n"; y.display(tout); tout << "\n";); + TRACE("lu_solve_PQ", tout << "P: " << P << "\nQ: " << Q << "\n";); + // TODO: super-sparse case, where the number of zeros in y is much smaller than m_sz + SASSERT(y.size() == m_sz); // compatible size + SASSERT(&(y.m()) == &(m())); // same manager + numeral delta; + unsigned i = m_sz; + dense_vector & x = m_tmp_xU_vector; + x.reset(); + while (i > 0) { + --i; + unsigned i_prime = P(i); + unsigned j_prime = Q(i); + unsigned begin = U.begr[i_prime]; + unsigned end = U.endr[i_prime]; + TRACE("lu_solve_bug", tout << "i_prime: " << i_prime << ", j_prime: " << j_prime << "\n"; + tout << "y: "; y.display(tout); tout << "\n"; + tout << "x: "; x.display(tout); tout << "\n";); + SASSERT(end >= begin + 1); // row must be non empty + CTRACE("lu_bug", U.indr[begin] != j_prime, tout << "i: " << i << ", i_prime: " << i_prime << ", j_prime: " << j_prime + << ", U.indr[begin]: " << U.indr[begin] << "\n"; display_U(tout);); + SASSERT(U.indr[begin] == j_prime); // j_prime must be in the first position + if (end == begin + 1) { + // row of size one + if (m().is_zero(y[i_prime])) + continue; + numeral & x_j = x.get(j_prime); + m().div(y[i_prime], U.A[begin], x_j); + } + else { + // row has at least two elements. + SASSERT(end > begin + 1); + numeral const & a = U.A[begin]; + m().reset(delta); + for (unsigned k = begin+1; k < end; k++) { + unsigned c = U.indr[k]; + TRACE("lu_solve_bug", tout << "c: " << c << ", x[c]: " << m().to_string(x[c]) << ", delta: " << m().to_string(delta) << "\n";); + m().addmul(delta, U.A[k], x[c], delta); + } + if (m().is_zero(delta) && m().is_zero(y[i_prime])) + continue; + numeral & x_j = x.get(j_prime); + m().sub(y[i_prime], delta, x_j); + m().div(x_j, a, x_j); + } + } + y.swap(x); + m().del(delta); + TRACE("lu_solve", tout << "after: Ux = y\n"; y.display(tout); tout << "\n";); +} + +template +void lu::solve_xL_eq_y(dense_vector & y) { + TRACE("lu_solve", tout << "before: xL = y\n"; y.display(tout); tout << "\n";); + SASSERT(y.size() == m_sz); // compatible size + SASSERT(&(y.m()) == &(m())); // same manager + unsigned k = L.A.size(); + while (k > 0) { + --k; + unsigned i_k = L.indc[k]; + numeral const & y_i = y[i_k]; + if (m().is_zero(y_i)) + continue; + numeral const & mu_k = L.A[k]; + unsigned j_k = L.indr[k]; + numeral & y_j = y.get(j_k); + m().submul(y_j, mu_k, y_i, y_j); + } + TRACE("lu_solve", tout << "after: xL = y\n"; y.display(tout); tout << "\n";); +} + +template +void lu::solve_xU_eq_y(dense_vector & y) { + // TODO: super-sparse case, where the number of zeros in y is much smaller than m_sz + TRACE("lu_solve", tout << "before: xU = y\n"; y.display(tout); tout << "\n";); + TRACE("lu_solve_PQ", tout << "P: " << P << "\nQ: " << Q << "\n";); + SASSERT(y.size() == m_sz); // compatible size + SASSERT(&(y.m()) == &(m())); // same manager + dense_vector & x = m_tmp_xU_vector; + x.reset(); + for (unsigned i = 0; i < m_sz; i++) { + unsigned i_prime = P(i); + unsigned j_prime = Q(i); + TRACE("lu_solve_step", tout << "i: " << i << ", P(i): " << P(i) << ", Q(i): " << Q(i) << ", x: "; x.display(tout); + tout << ", y[j_prime]: " << m().to_string(y[j_prime]) << "\n";); + if (!m().is_zero(y[j_prime])) { + numeral & x_i = x.get(i_prime); + m().add(x_i, y[j_prime], x_i); + } + if (m().is_zero(x[i_prime])) + continue; + numeral & x_i = x.get(i_prime); + unsigned begin = U.begr[i_prime]; + unsigned end = U.endr[i_prime]; + SASSERT(end >= begin + 1); // row must be non empty + SASSERT(U.indr[begin] == j_prime); + numeral const & a = U.A[begin]; + m().div(x_i, a, x_i); + for (unsigned k = begin+1; k < end; k++) { + // propagate x_i + unsigned c = U.indr[k]; + numeral & x_j = x.get(P(Q.inv(c))); + m().submul(x_j, U.A[k], x_i, x_j); + } + } + TRACE("lu_solve_step", tout << "x: "; x.display(tout); tout << "\n";); + y.swap(x); + TRACE("lu_solve", tout << "after: xU = y\n"; y.display(tout); tout << "\n";); +} + +// ----------------------- +// +// Column replacement +// +// ----------------------- + +/** + \brief Remove colum c from U row r. +*/ +template +void lu::del_U_row_entry(unsigned r, unsigned c) { + unsigned begin = U.begr[r]; + unsigned end = U.endr[r]; + TRACE("del_row_entry", tout << "del_U_row_entry, r: " << r << ", c: " << c << ", begin: " << begin << ", end: " << end << "\n";); + for (unsigned i = begin; i < end; i++) { + unsigned c_prime = U.indr[i]; + if (c_prime == c) { + U.num_entries--; + i++; + for (; i < end; i++) { + U.indr[i-1] = U.indr[i]; + m().swap(U.A[i-1], U.A[i]); + } + U.indr[end-1] = UINT_MAX; // mark as free + U.endr[r] = end-1; + return; + } + } +} + +/** + \brief Compress U rows +*/ +template +void lu::compress_U_rows() { + unsigned_vector new_indr; + numeral_vector new_A; + for (unsigned r = 0; r < m_sz; r++) { + unsigned begin = U.begr[r]; + unsigned end = U.endr[r]; + U.begr[r] = new_indr.size(); + for (unsigned i = begin; i < end; i++) { + new_A.push_back(numeral()); + m().swap(new_A.back(), U.A[i]); + new_indr.push_back(U.indr[i]); + } + U.endr[r] = new_indr.size(); + unsigned fillin = U.endr[r] - U.begr[r]; + for (unsigned i = 0; i < fillin; i++) + U.indr.push_back(UINT_MAX); + } + U.indr.swap(new_indr); + U.A.swap(new_A); + TRACE("lu_bug", tout << "compressed U rows\n";); + CASSERT("lu", check_U()); +} + +template +void lu::compress_U_if_needed() { + if (U.indr.size() > COMPRESSION_FACTOR * U.num_entries) { + compress_U_rows(); + CASSERT("lu", check_U()); + } +} + +template +void lu::move_U_row_to_end(unsigned r) { + unsigned begin = U.begr[r]; + unsigned end = U.endr[r]; + U.begr[r] = U.indr.size(); + U.endr[r] = U.begr[r] + end - begin; + for (unsigned j = begin; j < end; j++) { + U.A.push_back(numeral()); + m().swap(U.A.back(), U.A[j]); + unsigned c = U.indr[j]; + U.indr.push_back(c); + U.indr[j] = UINT_MAX; // mark as free + } + unsigned sz = end - begin; + unsigned fillin = fillin_for(sz); + for (unsigned i = 0; i < fillin; i++) { + U.A.push_back(numeral()); + U.indr.push_back(UINT_MAX); + } +} + +template +void lu::add_U_row_entry(unsigned r, unsigned c, numeral const & a) { + TRACE("add_U_row_entry", tout << "r: " << r << ", c: " << c << ", a: " << m().to_string(a) << " row:\n"; + for (unsigned i = U.begr[r]; i < U.endr[r]; i++) tout << m().to_string(U.A[i]) << "*x" << U.indr[i] << " "; tout << "\n";); + U.num_entries++; + if (U.endr[r] == U.indr.size()) { + // expand last row + U.A.push_back(numeral()); + m().set(U.A.back(), a); + U.indr.push_back(c); + U.endr[r]++; + SASSERT(U.endr[r] == U.indr.size()); + } + else { + if (U.indr[U.endr[r]] != UINT_MAX) + move_U_row_to_end(r); + // use free space + SASSERT(U.indr[U.endr[r]] == UINT_MAX); + unsigned pos = U.endr[r]; + m().set(U.A[pos], a); + U.indr[pos] = c; + U.endr[r]++; + } +} + +/** + \brief Remove colum c from U row r. +*/ +template +void lu::add_replace_U_row_entry(unsigned r, unsigned c, numeral const & a) { + unsigned begin = U.begr[r]; + unsigned end = U.endr[r]; + TRACE("del_row_entry", tout << "replace_U_row_entry, r: " << r << ", c: " << c << ", begin: " << begin << ", end: " << end << "\n";); + for (unsigned i = begin; i < end; i++) { + unsigned c_prime = U.indr[i]; + if (c_prime == c) { + m().set(U.A[i], a); + return; + } + } + add_U_row_entry(r, c, a); +} + +/** + \brief Just replace the column. After the replacement PUQ is not triangular anymore. +*/ +template +unsigned lu::replace_U_column_core(unsigned j, dense_vector & new_col) { + unsigned max = 0; + // Traverse rows in pivotal order, removing/updating entries. + // We can stop at Q.inv(j) because: + // For every entry (r,c) in U Q.inv(c) >= P.inv(r) + // Thus j does not occur in any r' such that Q.inv(j) < P.inv(r') + unsigned stop_at = Q.inv(j); + for (unsigned i = 0; i <= stop_at; i++) { + unsigned r = P(i); + if (m().is_zero(new_col[r])) { + del_U_row_entry(r, j); + } + else { + max = i; + // replace/add + add_replace_U_row_entry(r, j, new_col[r]); + // reset entry in new_col + numeral & v = new_col.get(r); + m().reset(v); + } + } + + // Add remaining rows of new_col to U + unsigned_vector::const_iterator it = new_col.begin_non_zeros(); + unsigned_vector::const_iterator end = new_col.m_non_zeros.end(); + for (; it != end; ++it) { + unsigned r = *it; + if (m().is_zero(new_col[r])) + continue; + if (P.inv(r) > max) + max = P.inv(r); + add_U_row_entry(r, j, new_col[r]); + } + return max; +} + +/** + \brief Return true if PUQ is triangular with the exception of column c_prime. +*/ +template +bool lu::check_U_except_col(unsigned c_prime) const { + for (unsigned r = 0; r < m_sz; r++) { + for (unsigned j = U.begr[r]; j < U.endr[r]; j++) { + unsigned c = U.indr[j]; + if (c == c_prime) + continue; + SASSERT(Q.inv(c) >= P.inv(r)); + } + } + return true; +} + +/** + \brief Return true if PUQ is triangular with the exception of row r_prime. +*/ +template +bool lu::check_U_except_row(unsigned r_prime) const { + for (unsigned r = 0; r < m_sz; r++) { + if (r == r_prime) + continue; + for (unsigned j = U.begr[r]; j < U.endr[r]; j++) { + unsigned c = U.indr[j]; + SASSERT(Q.inv(c) >= P.inv(r)); + } + } + return true; +} + +template +void lu::replace_U_column(unsigned j, dense_vector & new_col) { + TRACE("lu_replace", tout << "replacing U column j: " << j << " with\n"; new_col.display_non_zeros(tout); tout << "\n";); + unsigned k = replace_U_column_core(j, new_col); + if (k <= Q.inv(j)) { + TRACE("lu_replace", tout << "result 1:\n"; display(tout);); + CASSERT("lu", check_U()); + if (m().is_zero(U.A[U.begr[P(Q.inv(j))]])) + throw lu_exception("bad new column"); // column does not have the pivot. + return; + } + + TRACE("lu_replace", tout << "after replace_core:\n"; display_U(tout); + tout << "P: " << P << "\n"; tout << "Q: " << Q << "\n"; + tout << "Q.inv(j): " << Q.inv(j) << ", k: " << k << "\n";); + + // fix spike at j + CASSERT("lu", check_U_except_col(j)); + P.move_after(Q.inv(j), k); + Q.move_after(Q.inv(j), k); + + TRACE("lu_replace", tout << "P: " << P << "\n"; tout << "Q: " << Q << "\n";); + + // fix row P_k + unsigned P_k = P(k); + unsigned Q_k = Q(k); + CASSERT("lu", check_U_except_row(P_k)); + + // 1. search for smallest bad column + unsigned smallest = k; + unsigned begin = U.begr[P_k]; + unsigned end = U.endr[P_k]; + for (unsigned i = begin; i < end; i++) { + unsigned c = U.indr[i]; + unsigned inv_c = Q.inv(c); + if (inv_c < smallest) + smallest = inv_c; + } + + if (smallest == k) { + // nothing to be done + TRACE("lu_replace", tout << "result 2:\n"; display(tout);); + if (m().is_zero(U.A[U.begr[P_k]])) + throw lu_exception("bad new column"); // column does not have the pivot. + CASSERT("lu", check_U()); + return; + } + + // 2. eliminate bad columns using other rows. + // 2.a. save coefficients in tmp dense vector + dense_vector & row_k = m_tmp_replace_column_vector; + row_k.reset(); + begin = U.begr[P_k]; + end = U.endr[P_k]; + for (unsigned i = begin; i < end; i++) { + unsigned c = U.indr[i]; + U.indr[i] = UINT_MAX; // mark as free + numeral & a = row_k.get(c); + m().swap(a, U.A[i]); + } + U.num_entries -= end - begin; + U.endr[P_k] = begin; + + // 2.b eliminate columns in m_to_fix from P_k + for (; smallest < k; smallest++) { + unsigned c = Q(smallest); + numeral const & a_k = row_k[c]; + if (m().is_zero(a_k)) + continue; + + // use row r to eliminate c + unsigned r = P(Q.inv(c)); + SASSERT(U.indr[U.begr[r]] == c); + numeral const & a_r = U.A[U.begr[r]]; + + // to eliminate c from k, we must add (-a_k/a_r * r) to row_k + // Save transformation at L + m().set(mu_1, a_k); + m().div(mu_1, a_r, mu_1); + L.A.push_back(numeral()); + m().set(L.A.back(), mu_1); + L.indc.push_back(P_k); + L.indr.push_back(r); + m().neg(mu_1); + // k += mu_1 * r + begin = U.begr[r]; + end = U.endr[r]; + for (unsigned i = begin; i < end; i++) { + unsigned c_prime = U.indr[i]; + numeral & a_prime = row_k.get(c_prime); + if (c_prime == c) + m().reset(a_prime); + else + m().addmul(a_prime, mu_1, U.A[i], a_prime); + } + } + + CTRACE("lu_bug", m().is_zero(row_k[Q_k]), row_k.display_non_zeros(tout); tout << "\n";); + + // 2.c Copy row_k back to U + SASSERT(!NM::precise() || !m().is_zero(row_k[Q_k])); + if (m().is_zero(row_k[Q_k])) + throw lu_exception("singular matrix"); + + // Add pivot first + SASSERT(U.begr[P_k] == U.endr[P_k]); // the row k in U is empty + add_U_row_entry(P_k, Q_k, row_k[Q_k]); + + // Copy remaining elements + typename dense_vector::iterator it3 = row_k.begin_non_zeros(); + typename dense_vector::iterator end3 = row_k.end_non_zeros(); + for (; it3 != end3; ++it3) { + unsigned c = *it3; + if (c == Q_k) + continue; // already added + if (m().is_zero(row_k[c])) + continue; + add_U_row_entry(P_k, c, row_k[c]); + } + + TRACE("lu_replace", tout << "result 3:\n"; display(tout);); + CASSERT("lu", check_U()); + compress_U_if_needed(); +} + +template +void lu::replace_column(unsigned j, dense_vector & new_col) { + TRACE("lu_replace", tout << "replacing column j: " << j << " with\n"; new_col.display_non_zeros(tout); tout << "\n";); + SASSERT(j < m_sz); + solve_Lx_eq_y(new_col); + replace_U_column(j, new_col); + m_num_replacements++; +} + +// ----------------------- +// +// Dense vector +// +// ----------------------- + +// Make sure that every position in m_non_zeros really contains a non-zero value. +template +void lu::dense_vector::elim_zeros() { + unsigned_vector::iterator it = m_non_zeros.begin(); + unsigned_vector::iterator end = m_non_zeros.end(); + unsigned_vector::iterator it2 = it; + for (; it != end; ++it) { + unsigned idx = *it; + SASSERT(m_in_non_zeros[idx]); + if (m().is_zero(m_values[idx])) { + m().reset(m_values[idx]); + m_in_non_zeros[idx] = false; + continue; + } + *it2 = idx; + ++it2; + } + m_non_zeros.set_end(it2); +} + + +template +void lu::dense_vector::display(std::ostream & out) const { + out << "("; + unsigned sz = m_values.size(); + for (unsigned i = 0; i < sz; i++) { + if (i > 0) out << " "; + out << m().to_string(m_values[i]); + } + out << ")"; +} + +template +void lu::dense_vector::display_non_zeros(std::ostream & out) const { + out << "("; + bool first = true; + for (unsigned i = 0; i < m_non_zeros.size(); i++) { + unsigned pos = m_non_zeros[i]; + if (m().is_zero(m_values[pos])) + continue; + if (first) first = false; else out << " "; + out << m().to_string(m_values[pos]) << ":" << pos; + } + out << ")"; +} + +template +void lu::dense_vector::display_pol(std::ostream & out) const { + bool first = true; + for (unsigned i = 0; i < m_non_zeros.size(); i++) { + unsigned pos = m_non_zeros[i]; + if (m().is_zero(m_values[pos])) + continue; + if (first) first = false; else out << " + "; + out << m().to_string(m_values[pos]) << "*x" << pos; + } +} + + +template class lu; +template class lu; + diff --git a/lib/lu.h b/lib/lu.h new file mode 100644 index 000000000..16dd5a6ab --- /dev/null +++ b/lib/lu.h @@ -0,0 +1,404 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + lu.h + +Abstract: + + Simple LU factorization module based on the paper: + + "Maintaining LU factors of a General Sparse Matrix" + P. E. Gill, W. Murray, M. Saunders, M. Wright + +Author: + + Leonardo de Moura (leonardo) 2011-06-09 + +Revision History: + +--*/ +#ifndef _LU_H_ +#define _LU_H_ + +#include"vector.h" +#include"mpq.h" +#include"double_manager.h" +#include"permutation.h" +#include"params.h" +#include"strategy_exception.h" + +MK_ST_EXCEPTION(lu_exception); + +template +class lu { +public: + typedef NumManager manager; + typedef typename NumManager::numeral numeral; + typedef svector numeral_vector; + +private: + manager & m_manager; + + // Configuration + numeral m_mu; // maximum multiplier when selecting a pivot + unsigned m_selection_cutoff; + + // Matrix size + unsigned m_sz; // supporting only square matrices + + // Permutations + permutation P; + permutation Q; + + // L + // + // It is 3 parallel vectors representing the sequence (product) of matrices + // L[0] L[1] ... L[m-1] + // where each L[i] is a tuple (A[i], indc[i], indr[i]). + // Each tuple represents a triangular factor. That is, an identity matrix + // where the position at row indc[i], and column indr[i] contains the value A[i]. + // Remark: The product L[0] L[1] ... L[n-1] is not really a triangular matrix. + struct L_file { + numeral_vector A; + unsigned_vector indc; + unsigned_vector indr; + }; + L_file L; + + + // U + // + // It is not really upper triangular, but the product PUQ is. + // The rows of U are stored in the parallel vectors (A, indr) + // Only the non-zero values are stored at U. + // The non-zeros of row i start at position begr[i] and end at + // position endr[i] of the parallel vectors (A, indr). + // The length of the row is endr[i] - begr[i]. + // The coefficients are stored in A, and the column ids at indr. + // + // The factorization of a matrix A is represented as: + // L[0] L[1] ... L[m-1] P U Q + struct U_file { + numeral_vector A; + unsigned_vector indr; + unsigned_vector begr; + unsigned_vector endr; + + unsigned num_entries; + U_file():num_entries(0) {} + }; + U_file U; + + // The actual factorization + + + // T_file: temporary file used for factorization + struct T_file { + // row list + unsigned_vector indr; + unsigned_vector begr; + unsigned_vector endr; + + // column list + numeral_vector A; + unsigned_vector indc; + unsigned_vector begc; + unsigned_vector endc; + + unsigned num_entries; + T_file():num_entries(0) {} + }; + T_file T; + + // Auxiliary fields + unsigned_vector locw; + + // ----------------------- + // + // Main + // + // ----------------------- +public: + lu(manager & m, params_ref const & p); + ~lu(); + + manager & m() const { return m_manager; } + + void updt_params(params_ref const & p); + + void reset(); + + unsigned size() const { return m_sz; } + +protected: + void del_nums(numeral_vector & nums); + + // ----------------------- + // + // Initialization + // + // ----------------------- +public: + // Contract for setting up the initial matrix: + // lu.init(size) + // - for each row r in the matrix + // - for each non-zero (a,x) in the row + // lu.add_entry(a, x) + // lu.end_row() + void init(unsigned size); + void add_entry(numeral const & a, unsigned x); + void end_row(); + +protected: + // auxiliary fields used during initialization + bool ini; // try if the matrix T is being setup using the protocol above + unsigned ini_irow; + unsigned fillin_for(unsigned sz); + void move_col_to_end(unsigned x); + void move_row_to_end(unsigned x); + + // ----------------------- + // + // Factorization + // + // ----------------------- +public: + void fact(); + +protected: + class todo { + unsigned_vector m_elem2len; + unsigned_vector m_elem2pos; + vector m_elems_per_len; + unsigned m_size; + public: + todo():m_size(0) {} + bool contains(unsigned elem) const { return m_elem2pos[elem] != UINT_MAX; } + void init(unsigned capacity); + void updt_len(unsigned elem, unsigned len); + unsigned len(unsigned elem) const { return m_elem2len[elem]; } + void erase(unsigned elem); + unsigned size() const { return m_size; } + void display(std::ostream & out) const; + class iterator { + todo const & m_todo; + unsigned m_i; + unsigned m_j; + unsigned m_c; + void find_next(); + public: + iterator(todo const & t):m_todo(t), m_i(0), m_j(0), m_c(0) { if (!at_end()) find_next(); } + bool at_end() const { return m_c == m_todo.m_size; } + unsigned curr() const { + unsigned_vector const & v_i = m_todo.m_elems_per_len[m_i]; + return v_i[m_j]; + } + void next() { SASSERT(!at_end()); m_j++; m_c++; find_next(); } + }; + }; + + todo m_todo_rows; + todo m_todo_cols; + svector m_enabled_rows; + svector m_enabled_cols; + + bool enabled_row(unsigned r) const { return m_enabled_rows[r]; } + bool enabled_col(unsigned c) const { return m_enabled_cols[c]; } + + unsigned_vector m_toadd_rows; + svector m_marked_rows; + + // Temporary numerals + // I do not use local numerals to avoid memory leaks + numeral tol; + numeral C_max; + numeral A_ij; + numeral A_best; + numeral A_aux; + numeral tmp; + numeral mu_best; + numeral mu_1; + + void init_fact(); + bool stability_test(unsigned rin, unsigned cin, bool improvingM); + void select_pivot(unsigned & r_out, unsigned & c_out); + void process_pivot_core(unsigned r, unsigned c); + void process_pivot(unsigned i, unsigned r, unsigned c); + bool check_locw() const; + void dec_lenr(unsigned r); + void inc_lenr(unsigned r); + void dec_lenc(unsigned c); + void inc_lenc(unsigned c); + void del_row_entry(unsigned r, unsigned c); + void del_disabled_cols(unsigned r); + void add_row_entry(unsigned r, unsigned c); + void add_col_entry(unsigned r, unsigned c, numeral const & a); + void compress_rows(); + void compress_columns(); + void compress_if_needed(); + void copy_T_to_U(); + + bool check_lenr() const; + bool check_lenc() const; + + // ----------------------- + // + // Solving + // + // ----------------------- +public: + + // Temporary vector used to interact with different solvers. + // The vector has support for tracking non-zeros. + class dense_vector { + public: + typedef typename lu::manager manager; + typedef typename lu::numeral numeral; + private: + friend class lu; + manager & m_manager; + unsigned_vector m_non_zeros; // positions that may contain non-zeros. if a position is not here, then it must contain a zero + char_vector m_in_non_zeros; // m_in_non_zeros[i] == true if m_non_zeros contains i. + numeral_vector m_values; + public: + dense_vector(manager & m, unsigned sz); + ~dense_vector(); + + manager & m() const { return m_manager; } + + void reset(); + void reset(unsigned new_sz); + + unsigned size() const { return m_values.size(); } + numeral const & operator[](unsigned idx) const { return m_values[idx]; } + + void swap(dense_vector & other) { + m_non_zeros.swap(other.m_non_zeros); + m_in_non_zeros.swap(other.m_in_non_zeros); + m_values.swap(other.m_values); + } + + // Get a given position for performing an update. + // idx is inserted into m_non_zeros. + numeral & get(unsigned idx) { + if (!m_in_non_zeros[idx]) { + m_in_non_zeros[idx] = true; + m_non_zeros.push_back(idx); + } + return m_values[idx]; + } + + typedef unsigned_vector::const_iterator iterator; + + // iterator for positions that may contain non-zeros + iterator begin_non_zeros() const { return m_non_zeros.begin(); } + iterator end_non_zeros() const { return m_non_zeros.end(); } + + void display(std::ostream & out) const; + void display_non_zeros(std::ostream & out) const; + void display_pol(std::ostream & out) const; + + void elim_zeros(); + }; + + // Solve: Lx = y + // The result is stored in y. + void solve_Lx_eq_y(dense_vector & y); + + // Solve: PUQx = y + // The result is stored in y. + void solve_Ux_eq_y(dense_vector & y); + + // Solve: LPUQx = y + // The result is stored in y. + void solve_Ax_eq_y(dense_vector & y) { + solve_Lx_eq_y(y); + solve_Ux_eq_y(y); + } + + // Solve: xL = y + // The result is stored in y. + void solve_xL_eq_y(dense_vector & y); + + // Solve: xPUQ = y + // The result is stored in y. + void solve_xU_eq_y(dense_vector & y); + + // Solve: xA = y + // The result is stored in y. + void solve_xA_eq_y(dense_vector & y) { + solve_xU_eq_y(y); + solve_xL_eq_y(y); + } + +private: + dense_vector m_tmp_xU_vector; + dense_vector m_tmp_replace_column_vector; + dense_vector m_tmp_vector; + dense_vector m_tmp_row; + +public: + dense_vector & get_tmp_vector() { return m_tmp_vector; } + dense_vector & get_tmp_row(unsigned size) { m_tmp_row.reset(size); return m_tmp_row; } + + // ----------------------- + // + // Column replacement + // + // ----------------------- +public: + void replace_column(unsigned j, dense_vector & new_col); + void replace_U_column(unsigned j, dense_vector & new_col); + unsigned get_num_replacements() const { return m_num_replacements; } + dense_vector & get_tmp_col() { return m_tmp_col; } + +private: + unsigned m_num_replacements; + dense_vector m_tmp_col; + + void del_U_row_entry(unsigned r, unsigned c); + void compress_U_rows(); + void compress_U_if_needed(); + void move_U_row_to_end(unsigned r); + void add_U_row_entry(unsigned r, unsigned c, numeral const & a); + void add_replace_U_row_entry(unsigned r, unsigned c, numeral const & a); + unsigned replace_U_column_core(unsigned j, dense_vector & new_col); + bool check_U_except_col(unsigned c) const; + bool check_U_except_row(unsigned r) const; + + // ----------------------- + // + // Invariants + // + // ----------------------- +public: + bool check_P() const; + bool check_Q() const; + bool check_L() const; + bool check_U() const; + bool T_col_contains(unsigned c, unsigned r) const; + bool T_row_contains(unsigned r, unsigned c) const; + bool check_T() const; + bool check_invariant() const; + + void display_T(std::ostream & out) const; + void display_U(std::ostream & out, unsigned_vector const * var_ids = 0) const; + void display_L(std::ostream & out) const; + void display(std::ostream & out, unsigned_vector const * var_ids = 0) const; + + // ----------------------- + // + // Info + // + // ----------------------- +public: + unsigned L_size() const { return L.indc.size(); } + unsigned U_size() const { return U.num_entries; } +}; + +typedef lu rational_lu; +typedef lu double_lu; + +#endif diff --git a/lib/luby.cpp b/lib/luby.cpp new file mode 100644 index 000000000..39eec40bb --- /dev/null +++ b/lib/luby.cpp @@ -0,0 +1,33 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + luby.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-03-04. + +Revision History: + +--*/ +#include + +unsigned get_luby(unsigned i) { + if (i == 1) + return 1; + double k = log(static_cast(i+1))/log(static_cast(2)); + + if (k == floor(k + 0.5)) + return static_cast(pow(2,k-1)); + else { + k = static_cast(floor(k)); + return get_luby(i - static_cast(pow(2, k)) + 1); + } +} + diff --git a/lib/luby.h b/lib/luby.h new file mode 100644 index 000000000..2931cde10 --- /dev/null +++ b/lib/luby.h @@ -0,0 +1,31 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + luby.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-03-04. + +Revision History: + +--*/ +#ifndef _LUBY_H_ +#define _LUBY_H_ + +/** + \brief Return the i-th element of the Luby sequence: 1,1,2,1,1,2,4,1,1,2,1,1,2,4,8,... + + get_luby(i) = 2^{i-1} if i = 2^k -1 + get_luby(i) = get_luby(i - 2^{k-1} + 1) if 2^{k-1} <= i < 2^k - 1 +*/ +unsigned get_luby(unsigned i); + +#endif /* _LUBY_H_ */ + diff --git a/lib/machine.h b/lib/machine.h new file mode 100644 index 000000000..75dfde7a4 --- /dev/null +++ b/lib/machine.h @@ -0,0 +1,30 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + machine.h + +Abstract: + + Machine/OS dependent configuration + +Author: + + Leonardo de Moura (leonardo) 2006-09-13. + +Revision History: + +--*/ + +#ifndef _MACHINE_H_ +#define _MACHINE_H_ + +#ifdef _AMD64_ +#define PTR_ALIGNMENT 3 +#else +#define PTR_ALIGNMENT 2 +#endif + +#endif /* _MACHINE_H_ */ + diff --git a/lib/macro_finder.cpp b/lib/macro_finder.cpp new file mode 100644 index 000000000..35cb5891f --- /dev/null +++ b/lib/macro_finder.cpp @@ -0,0 +1,223 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + macro_finder.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-04-05. + +Revision History: + +--*/ + +#include"macro_finder.h" +#include"occurs.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" + +bool macro_finder::is_macro(expr * n, app * & head, expr * & def) { + if (!is_quantifier(n) || !to_quantifier(n)->is_forall()) + return false; + TRACE("macro_finder", tout << "processing: " << mk_pp(n, m_manager) << "\n";); + expr * body = to_quantifier(n)->get_expr(); + unsigned num_decls = to_quantifier(n)->get_num_decls(); + return m_util.is_simple_macro(body, num_decls, head, def); +} + +/** + \brief Detect macros of the form + 1- (forall (X) (= (+ (f X) (R X)) c)) + 2- (forall (X) (<= (+ (f X) (R X)) c)) + 3- (forall (X) (>= (+ (f X) (R X)) c)) + + The second and third cases are first converted into + (forall (X) (= (f X) (+ c (* -1 (R x)) (k X)))) + and + (forall (X) (<= (k X) 0)) when case 2 + (forall (X) (>= (k X) 0)) when case 3 + + For case 2 & 3, the new quantifiers are stored in new_exprs and new_prs. +*/ +bool macro_finder::is_arith_macro(expr * n, proof * pr, expr_ref_vector & new_exprs, proof_ref_vector & new_prs) { + if (!is_quantifier(n) || !to_quantifier(n)->is_forall()) + return false; + arith_simplifier_plugin * as = get_arith_simp(); + arith_util & autil = as->get_arith_util(); + expr * body = to_quantifier(n)->get_expr(); + unsigned num_decls = to_quantifier(n)->get_num_decls(); + + if (!autil.is_le(body) && !autil.is_ge(body) && !m_manager.is_eq(body)) + return false; + if (!as->is_add(to_app(body)->get_arg(0))) + return false; + app_ref head(m_manager); + expr_ref def(m_manager); + bool inv = false; + if (!m_util.is_arith_macro(body, num_decls, head, def, inv)) + return false; + app_ref new_body(m_manager); + + if (!inv || m_manager.is_eq(body)) + new_body = m_manager.mk_app(to_app(body)->get_decl(), head, def); + else if (as->is_le(body)) + new_body = autil.mk_ge(head, def); + else + new_body = autil.mk_le(head, def); + + quantifier_ref new_q(m_manager); + new_q = m_manager.update_quantifier(to_quantifier(n), new_body); + proof * new_pr = 0; + if (m_manager.proofs_enabled()) { + proof * rw = m_manager.mk_rewrite(n, new_q); + new_pr = m_manager.mk_modus_ponens(pr, rw); + } + if (m_manager.is_eq(body)) { + return m_macro_manager.insert(head->get_decl(), new_q, new_pr); + } + // is ge or le + // + TRACE("macro_finder", tout << "is_arith_macro: is_ge or is_le\n";); + func_decl * f = head->get_decl(); + func_decl * k = m_manager.mk_fresh_func_decl(f->get_name(), symbol::null, f->get_arity(), f->get_domain(), f->get_range()); + app * k_app = m_manager.mk_app(k, head->get_num_args(), head->get_args()); + expr_ref_buffer new_rhs_args(m_manager); + expr_ref new_rhs2(m_manager); + as->mk_add(def, k_app, new_rhs2); + expr * body1 = m_manager.mk_eq(head, new_rhs2); + expr * body2 = m_manager.mk_app(new_body->get_decl(), k_app, as->mk_numeral(rational(0))); + quantifier * q1 = m_manager.update_quantifier(new_q, body1); + expr * patterns[1] = { m_manager.mk_pattern(k_app) }; + quantifier * q2 = m_manager.update_quantifier(new_q, 1, patterns, body2); + new_exprs.push_back(q1); + new_exprs.push_back(q2); + if (m_manager.proofs_enabled()) { + // new_pr : new_q + // rw : [rewrite] new_q ~ q1 & q2 + // mp : [modus_pones new_pr rw] q1 & q2 + // pr1 : [and-elim mp] q1 + // pr2 : [and-elim mp] q2 + app * q1q2 = m_manager.mk_and(q1,q2); + proof * rw = m_manager.mk_oeq_rewrite(new_q, q1q2); + proof * mp = m_manager.mk_modus_ponens(new_pr, rw); + proof * pr1 = m_manager.mk_and_elim(mp, 0); + proof * pr2 = m_manager.mk_and_elim(mp, 1); + new_prs.push_back(pr1); + new_prs.push_back(pr2); + } + return true; +} + +/** + n is of the form: (forall (X) (iff (= (f X) t) def[X])) + + Convert it into: + + (forall (X) (= (f X) (ite def[X] t (k X)))) + (forall (X) (not (= (k X) t))) + + where k is a fresh symbol. + + The new quantifiers and proofs are stored in new_exprs and new_prs +*/ +static void pseudo_predicate_macro2macro(ast_manager & m, app * head, app * t, expr * def, quantifier * q, proof * pr, + expr_ref_vector & new_exprs, proof_ref_vector & new_prs) { + func_decl * f = head->get_decl(); + func_decl * k = m.mk_fresh_func_decl(f->get_name(), symbol::null, f->get_arity(), f->get_domain(), f->get_range()); + app * k_app = m.mk_app(k, head->get_num_args(), head->get_args()); + app * ite = m.mk_ite(def, t, k_app); + app * body_1 = m.mk_eq(head, ite); + app * body_2 = m.mk_not(m.mk_eq(k_app, t)); + quantifier * q1 = m.update_quantifier(q, body_1); + expr * pats[1] = { m.mk_pattern(k_app) }; + quantifier * q2 = m.update_quantifier(q, 1, pats, body_2); // erase patterns + new_exprs.push_back(q1); + new_exprs.push_back(q2); + if (m.proofs_enabled()) { + // r : [rewrite] q ~ q1 & q2 + // pr : q + // mp : [modus_pones pr pr1] q1 & q2 + // pr1 : [and-elim mp] q1 + // pr2 : [and-elim mp] q2 + app * q1q2 = m.mk_and(q1,q2); + proof * r = m.mk_oeq_rewrite(q, q1q2); + proof * mp = m.mk_modus_ponens(pr, r); + proof * pr1 = m.mk_and_elim(mp, 0); + proof * pr2 = m.mk_and_elim(mp, 1); + new_prs.push_back(pr1); + new_prs.push_back(pr2); + } +} + +macro_finder::macro_finder(ast_manager & m, macro_manager & mm): + m_manager(m), + m_macro_manager(mm), + m_util(mm.get_util()) { +} + +macro_finder::~macro_finder() { +} + +bool macro_finder::expand_macros(unsigned num, expr * const * exprs, proof * const * prs, expr_ref_vector & new_exprs, proof_ref_vector & new_prs) { + TRACE("macro_finder", tout << "starting expand_macros:\n"; + m_macro_manager.display(tout);); + bool found_new_macro = false; + for (unsigned i = 0; i < num; i++) { + expr * n = exprs[i]; + proof * pr = m_manager.proofs_enabled() ? prs[i] : 0; + expr_ref new_n(m_manager); + proof_ref new_pr(m_manager); + m_macro_manager.expand_macros(n, pr, new_n, new_pr); + app * head = 0; + expr * def = 0; + app * t = 0; + if (is_macro(new_n, head, def) && m_macro_manager.insert(head->get_decl(), to_quantifier(new_n.get()), new_pr)) { + TRACE("macro_finder_found", tout << "found new macro: " << head->get_decl()->get_name() << "\n" << mk_pp(new_n, m_manager) << "\n";); + found_new_macro = true; + } + else if (is_arith_macro(new_n, new_pr, new_exprs, new_prs)) { + TRACE("macro_finder_found", tout << "found new arith macro:\n" << mk_pp(new_n, m_manager) << "\n";); + found_new_macro = true; + } + else if (m_util.is_pseudo_predicate_macro(new_n, head, t, def)) { + TRACE("macro_finder_found", tout << "found new pseudo macro:\n" << mk_pp(head, m_manager) << "\n" << mk_pp(t, m_manager) << "\n" << + mk_pp(def, m_manager) << "\n";); + pseudo_predicate_macro2macro(m_manager, head, t, def, to_quantifier(new_n), new_pr, new_exprs, new_prs); + found_new_macro = true; + } + else { + new_exprs.push_back(new_n); + if (m_manager.proofs_enabled()) + new_prs.push_back(new_pr); + } + } + return found_new_macro; +} + +void macro_finder::operator()(unsigned num, expr * const * exprs, proof * const * prs, expr_ref_vector & new_exprs, proof_ref_vector & new_prs) { + TRACE("macro_finder", tout << "processing macros...\n";); + expr_ref_vector _new_exprs(m_manager); + proof_ref_vector _new_prs(m_manager); + if (expand_macros(num, exprs, prs, _new_exprs, _new_prs)) { + while (true) { + expr_ref_vector old_exprs(m_manager); + proof_ref_vector old_prs(m_manager); + _new_exprs.swap(old_exprs); + _new_prs.swap(old_prs); + SASSERT(_new_exprs.empty()); + SASSERT(_new_prs.empty()); + if (!expand_macros(old_exprs.size(), old_exprs.c_ptr(), old_prs.c_ptr(), _new_exprs, _new_prs)) + break; + } + } + new_exprs.append(_new_exprs); + new_prs.append(_new_prs); +} + + diff --git a/lib/macro_finder.h b/lib/macro_finder.h new file mode 100644 index 000000000..bfe1d4ecb --- /dev/null +++ b/lib/macro_finder.h @@ -0,0 +1,55 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + macro_finder.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-04-05. + +Revision History: + +--*/ +#ifndef _MACRO_FINDER_H_ +#define _MACRO_FINDER_H_ + +#include"macro_manager.h" +#include"arith_simplifier_plugin.h" + + +bool is_macro_head(expr * n, unsigned num_decls); +bool is_simple_macro(ast_manager & m, expr * n, unsigned num_decls, obj_hashtable const * forbidden_set, app * & head, expr * & def); +inline bool is_simple_macro(ast_manager & m, expr * n, unsigned num_decls, app * & head, expr * & def) { + return is_simple_macro(m, n, num_decls, 0, head, def); +} + +/** + \brief Macro finder is responsible for finding universally quantified sub-formulas that can be used + as macros. +*/ +class macro_finder { + ast_manager & m_manager; + macro_manager & m_macro_manager; + macro_util & m_util; + arith_simplifier_plugin * get_arith_simp() { return m_util.get_arith_simp(); } + bool expand_macros(unsigned num, expr * const * exprs, proof * const * prs, expr_ref_vector & new_exprs, proof_ref_vector & new_prs); + bool is_arith_macro(expr * n, proof * pr, expr_ref_vector & new_exprs, proof_ref_vector & new_prs); + + bool is_macro(expr * n, app * & head, expr * & def); + bool is_pseudo_head(expr * n, unsigned num_decls, app * & head, app * & t); + bool is_pseudo_predicate_macro(expr * n, app * & head, app * & t, expr * & def); + +public: + macro_finder(ast_manager & m, macro_manager & mm); + ~macro_finder(); + void operator()(unsigned n, expr * const * exprs, proof * const * prs, expr_ref_vector & new_exprs, proof_ref_vector & new_prs); +}; + +#endif /* _MACRO_FINDER_H_ */ + diff --git a/lib/macro_manager.cpp b/lib/macro_manager.cpp new file mode 100644 index 000000000..abcc97882 --- /dev/null +++ b/lib/macro_manager.cpp @@ -0,0 +1,319 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + macro_manager.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-04-05. + +Revision History: + + Christoph Wintersteiger (t-cwinte), 2010-04-13: Added cycle detection for macro definitions + Leonardo de Moura (leonardo) 2010-12-15: Moved dependency management to func_decl_dependencies.h + +--*/ +#include"macro_manager.h" +#include"for_each_expr.h" +#include"var_subst.h" +#include"ast_pp.h" +#include"recurse_expr_def.h" + +macro_manager::macro_manager(ast_manager & m, simplifier & s): + m_manager(m), + m_simplifier(s), + m_util(m, s), + m_decls(m), + m_macros(m), + m_macro_prs(m), + m_forbidden(m), + m_deps(m) { + m_util.set_forbidden_set(&m_forbidden_set); +} + +macro_manager::~macro_manager() { +} + +void macro_manager::push_scope() { + m_scopes.push_back(scope()); + scope & s = m_scopes.back(); + s.m_decls_lim = m_decls.size(); + s.m_forbidden_lim = m_forbidden.size(); +} + +void macro_manager::pop_scope(unsigned num_scopes) { + unsigned new_lvl = m_scopes.size() - num_scopes; + scope & s = m_scopes[new_lvl]; + restore_decls(s.m_decls_lim); + restore_forbidden(s.m_forbidden_lim); + m_scopes.shrink(new_lvl); +} + +void macro_manager::restore_decls(unsigned old_sz) { + unsigned sz = m_decls.size(); + for (unsigned i = old_sz; i < sz; i++) { + m_decl2macro.erase(m_decls.get(i)); + m_deps.erase(m_decls.get(i)); + if (m_manager.proofs_enabled()) + m_decl2macro_pr.erase(m_decls.get(i)); + } + m_decls.shrink(old_sz); + m_macros.shrink(old_sz); + if (m_manager.proofs_enabled()) + m_macro_prs.shrink(old_sz); +} + +void macro_manager::restore_forbidden(unsigned old_sz) { + unsigned sz = m_forbidden.size(); + for (unsigned i = old_sz; i < sz; i++) + m_forbidden_set.erase(m_forbidden.get(i)); + m_forbidden.shrink(old_sz); +} + +void macro_manager::reset() { + m_decl2macro.reset(); + m_decl2macro_pr.reset(); + m_decls.reset(); + m_macros.reset(); + m_macro_prs.reset(); + m_scopes.reset(); + m_forbidden_set.reset(); + m_forbidden.reset(); + m_deps.reset(); +} + +bool macro_manager::insert(func_decl * f, quantifier * m, proof * pr) { + TRACE("macro_insert", tout << "trying to create macro: " << f->get_name() << "\n" << mk_pp(m, m_manager) << "\n";); + + // if we already have a macro for f then return false; + if (m_decls.contains(f)) { + TRACE("macro_insert", tout << "we already have a macro for: " << f->get_name() << "\n";); + return false; + } + + app * head; + expr * definition; + get_head_def(m, f, head, definition); + + func_decl_set * s = m_deps.mk_func_decl_set(); + m_deps.collect_func_decls(definition, s); + if (!m_deps.insert(f, s)) { + return false; + } + + // add macro + m_decl2macro.insert(f, m); + m_decls.push_back(f); + m_macros.push_back(m); + if (m_manager.proofs_enabled()) { + m_macro_prs.push_back(pr); + m_decl2macro_pr.insert(f, pr); + } + + TRACE("macro_insert", tout << "A macro was successfully created for: " << f->get_name() << "\n";); + + // Nothing's forbidden anymore; if something's bad, we detected it earlier. + // mark_forbidden(m->get_expr()); + return true; +} + +namespace macro_manager_ns { + struct proc { + obj_hashtable & m_forbidden_set; + func_decl_ref_vector & m_forbidden; + proc(obj_hashtable & s, func_decl_ref_vector & v):m_forbidden_set(s), m_forbidden(v) {} + void operator()(var * n) {} + void operator()(quantifier * n) {} + void operator()(app * n) { + func_decl * d = n->get_decl(); + if (n->get_num_args() > 0 && n->get_family_id() == null_family_id && !m_forbidden_set.contains(d)) { + m_forbidden_set.insert(d); + m_forbidden.push_back(d); + } + } + }; +}; + +/** + \brief Mark all func_decls used in exprs as forbidden. +*/ +void macro_manager::mark_forbidden(unsigned n, expr * const * exprs) { + expr_mark visited; + macro_manager_ns::proc p(m_forbidden_set, m_forbidden); + for (unsigned i = 0; i < n; i++) + for_each_expr(p, visited, exprs[i]); +} + +void macro_manager::get_head_def(quantifier * q, func_decl * d, app * & head, expr * & def) const { + app * body = to_app(q->get_expr()); + SASSERT(m_manager.is_eq(body) || m_manager.is_iff(body)); + expr * lhs = to_app(body)->get_arg(0); + expr * rhs = to_app(body)->get_arg(1); + SASSERT(is_app_of(lhs, d) || is_app_of(rhs, d)); + SASSERT(!is_app_of(lhs, d) || !is_app_of(rhs, d)); + if (is_app_of(lhs, d)) { + head = to_app(lhs); + def = rhs; + } + else { + head = to_app(rhs); + def = lhs; + } +} + +void macro_manager::display(std::ostream & out) { + unsigned sz = m_decls.size(); + for (unsigned i = 0; i < sz; i++) { + func_decl * f = m_decls.get(i); + quantifier * q = 0; + m_decl2macro.find(f, q); + app * head; + expr * def; + get_head_def(q, f, head, def); + SASSERT(q); + out << mk_pp(head, m_manager) << " ->\n" << mk_pp(def, m_manager) << "\n"; + } +} + +func_decl * macro_manager::get_macro_interpretation(unsigned i, expr_ref & interp) const { + func_decl * f = m_decls.get(i); + quantifier * q = m_macros.get(i); + app * head; + expr * def; + get_head_def(q, f, head, def); + TRACE("macro_bug", + tout << f->get_name() << "\n" << mk_pp(head, m_manager) << "\n" << mk_pp(q, m_manager) << "\n";); + m_util.mk_macro_interpretation(head, def, interp); + return f; +} + +macro_manager::macro_expander::macro_expander(ast_manager & m, macro_manager & mm, simplifier & s): + simplifier(m), + m_macro_manager(mm) { + // REMARK: theory simplifier should not be used by macro_expander... + // is_arith_macro rewrites a quantifer such as: + // forall (x Int) (= (+ x (+ (f x) 1)) 2) + // into + // forall (x Int) (= (f x) (+ 1 (* -1 x))) + // The goal is to make simple macro detection detect the arith macro. + // The arith simplifier will undo this transformation. + // borrow_plugins(s); + enable_ac_support(false); +} + +macro_manager::macro_expander::~macro_expander() { + // release_plugins(); +} + +void macro_manager::macro_expander::reduce1_quantifier(quantifier * q) { + simplifier::reduce1_quantifier(q); + // If a macro was expanded in a pattern, we must erase it since it may not be a valid pattern anymore. + // The MAM assumes valid patterns, and it crashes if invalid patterns are provided. + // For example, it will crash if the pattern does not contain all variables. + // + // Alternative solution: use pattern_validation to check if the pattern is still valid. + // I'm not sure if this is a good solution, since the pattern may be meaningless after the macro expansion. + // So, I'm just erasing them. + expr * new_q_expr = 0; + proof * new_q_pr = 0; + get_cached(q, new_q_expr, new_q_pr); + if (!is_quantifier(new_q_expr)) + return; + quantifier * new_q = to_quantifier(new_q_expr); + bool erase_patterns = false; + if (q->get_num_patterns() != new_q->get_num_patterns() || + q->get_num_no_patterns() != new_q->get_num_no_patterns()) { + erase_patterns = true; + } + else { + for (unsigned i = 0; !erase_patterns && i < q->get_num_patterns(); i++) { + if (q->get_pattern(i) != new_q->get_pattern(i)) + erase_patterns = true; + } + for (unsigned i = 0; !erase_patterns && i < q->get_num_no_patterns(); i++) { + if (q->get_no_pattern(i) != new_q->get_no_pattern(i)) + erase_patterns = true; + } + } + if (erase_patterns) { + ast_manager & m = get_manager(); + expr * new_new_q = m.update_quantifier(new_q, 0, 0, 0, 0, new_q->get_expr()); + // we can use the same proof since new_new_q and new_q are identical modulo patterns/annotations + cache_result(q, new_new_q, new_q_pr); + } +} + +bool macro_manager::macro_expander::get_subst(expr * _n, expr_ref & r, proof_ref & p) { + if (!is_app(_n)) + return false; + app * n = to_app(_n); + quantifier * q = 0; + func_decl * d = n->get_decl(); + TRACE("macro_manager_bug", tout << "trying to expand:\n" << mk_pp(n, m_manager) << "\nd:\n" << d->get_name() << "\n";); + if (m_macro_manager.m_decl2macro.find(d, q)) { + TRACE("macro_manager", tout << "expanding: " << mk_pp(n, m_manager) << "\n";); + app * head = 0; + expr * def = 0; + m_macro_manager.get_head_def(q, d, head, def); + unsigned num = n->get_num_args(); + SASSERT(head && def); + ptr_buffer subst_args; + subst_args.resize(num, 0); + for (unsigned i = 0; i < num; i++) { + var * v = to_var(head->get_arg(i)); + SASSERT(v->get_idx() < num); + unsigned nidx = num - v->get_idx() - 1; + SASSERT(subst_args[nidx] == 0); + subst_args[nidx] = n->get_arg(i); + } + var_subst s(m_manager); + s(def, num, subst_args.c_ptr(), r); + if (m_manager.proofs_enabled()) { + expr_ref instance(m_manager); + s(q->get_expr(), num, subst_args.c_ptr(), instance); + proof * qi_pr = m_manager.mk_quant_inst(m_manager.mk_or(m_manager.mk_not(q), instance), num, subst_args.c_ptr()); + proof * q_pr = 0; + m_macro_manager.m_decl2macro_pr.find(d, q_pr); + SASSERT(q_pr != 0); + proof * prs[2] = { qi_pr, q_pr }; + p = m_manager.mk_unit_resolution(2, prs); + } + else { + p = 0; + } + return true; + } + return false; +} + +void macro_manager::expand_macros(expr * n, proof * pr, expr_ref & r, proof_ref & new_pr) { + if (has_macros()) { + // Expand macros with "real" proof production support (NO rewrite*) + expr_ref old_n(m_manager); + proof_ref old_pr(m_manager); + old_n = n; + old_pr = pr; + for (;;) { + macro_expander proc(m_manager, *this, m_simplifier); + proof_ref n_eq_r_pr(m_manager); + TRACE("macro_manager_bug", tout << "expand_macros:\n" << mk_pp(n, m_manager) << "\n";); + proc(old_n, r, n_eq_r_pr); + new_pr = m_manager.mk_modus_ponens(old_pr, n_eq_r_pr); + if (r.get() == old_n.get()) + return; + old_n = r; + old_pr = new_pr; + } + } + else { + r = n; + new_pr = pr; + } +} + diff --git a/lib/macro_manager.h b/lib/macro_manager.h new file mode 100644 index 000000000..480f37d9c --- /dev/null +++ b/lib/macro_manager.h @@ -0,0 +1,99 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + macro_manager.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-04-05. + +Revision History: + +--*/ +#ifndef _MACRO_MANAGER_H_ +#define _MACRO_MANAGER_H_ + +#include"ast_util.h" +#include"obj_hashtable.h" +#include"simplifier.h" +#include"recurse_expr.h" +#include"func_decl_dependencies.h" +#include"macro_util.h" + +/** + \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. + + 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. +*/ +class macro_manager { + ast_manager & m_manager; + simplifier & m_simplifier; + macro_util m_util; + + obj_map m_decl2macro; // func-decl -> quantifier + obj_map m_decl2macro_pr; // func-decl -> quantifier_proof + func_decl_ref_vector m_decls; + quantifier_ref_vector m_macros; + proof_ref_vector m_macro_prs; + obj_hashtable m_forbidden_set; + func_decl_ref_vector m_forbidden; + struct scope { + unsigned m_decls_lim; + unsigned m_forbidden_lim; + }; + svector m_scopes; + + func_decl_dependencies m_deps; + + void restore_decls(unsigned old_sz); + void restore_forbidden(unsigned old_sz); + + class macro_expander : public simplifier { + protected: + macro_manager & m_macro_manager; + virtual bool get_subst(expr * n, expr_ref & r, proof_ref & p); + virtual void reduce1_quantifier(quantifier * q); + public: + macro_expander(ast_manager & m, macro_manager & mm, simplifier & s); + ~macro_expander(); + }; + friend class macro_expander; + +public: + macro_manager(ast_manager & m, simplifier & s); + ~macro_manager(); + ast_manager & get_manager() const { return m_manager; } + macro_util & get_util() { return m_util; } + bool insert(func_decl * f, quantifier * m, proof * pr); + bool has_macros() const { return !m_macros.empty(); } + void push_scope(); + void pop_scope(unsigned num_scopes); + void reset(); + void mark_forbidden(unsigned n, expr * const * exprs); + void mark_forbidden(expr * e) { mark_forbidden(1, &e); } + bool is_forbidden(func_decl * d) const { return m_forbidden_set.contains(d); } + obj_hashtable const & get_forbidden_set() const { return m_forbidden_set; } + void display(std::ostream & out); + unsigned get_num_macros() const { return m_decls.size(); } + unsigned get_first_macro_last_level() const { return m_scopes.empty() ? 0 : m_scopes.back().m_decls_lim; } + func_decl * get_macro_func_decl(unsigned i) const { return m_decls.get(i); } + func_decl * get_macro_interpretation(unsigned i, expr_ref & interp) const; + quantifier * get_macro_quantifier(func_decl * f) const { quantifier * q = 0; m_decl2macro.find(f, q); return q; } + void get_head_def(quantifier * q, func_decl * d, app * & head, expr * & def) const; + void expand_macros(expr * n, proof * pr, expr_ref & r, proof_ref & new_pr); + + +}; + +#endif /* _MACRO_MANAGER_H_ */ + diff --git a/lib/macro_substitution.cpp b/lib/macro_substitution.cpp new file mode 100644 index 000000000..9cf337d21 --- /dev/null +++ b/lib/macro_substitution.cpp @@ -0,0 +1,184 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + macro_substitution.cpp + +Abstract: + + Mapping from func_decl to quantifiers of the form + Forall X. f(X) = T[X] + Forall X. f(X) iff C[X] + +Author: + + Leonardo (leonardo) 2012-02-17 + +Notes: + +--*/ +#include"macro_substitution.h" +#include"ref_util.h" + +typedef obj_map func_decl2proof; +typedef obj_map func_decl2expr_dependency; + +void macro_substitution::init() { + if (proofs_enabled()) + m_decl2macro_pr = alloc(func_decl2proof); + if (unsat_core_enabled()) + m_decl2macro_dep = alloc(func_decl2expr_dependency); +} + +macro_substitution::macro_substitution(ast_manager & m): + m_manager(m), + m_cores_enabled(false), + m_proofs_enabled(m.proofs_enabled()) { + init(); +} + +macro_substitution::macro_substitution(ast_manager & m, bool cores_enabled): + m_manager(m), + m_cores_enabled(cores_enabled), + m_proofs_enabled(m.proofs_enabled()) { + init(); +} + +macro_substitution::macro_substitution(ast_manager & m, bool cores_enabled, bool proofs_enabled): + m_manager(m), + m_cores_enabled(cores_enabled), + m_proofs_enabled(proofs_enabled) { + SASSERT(!proofs_enabled || m.proofs_enabled()); + init(); +} + +macro_substitution::~macro_substitution() { + reset(); +} + +void macro_substitution::reset() { + dec_ref_map_key_values(m_manager, m_decl2macro); + if (proofs_enabled()) + dec_ref_map_values(m_manager, *m_decl2macro_pr); + if (unsat_core_enabled()) + dec_ref_map_values(m_manager, *m_decl2macro_dep); +} + +void macro_substitution::cleanup() { + reset(); + m_decl2macro.finalize(); + if (proofs_enabled()) + m_decl2macro_pr->finalize(); + if (unsat_core_enabled()) + m_decl2macro_dep->finalize(); +} + +void macro_substitution::insert(func_decl * f, quantifier * q, proof * pr, expr_dependency * dep) { + DEBUG_CODE({ + app * body = to_app(q->get_expr()); + SASSERT(m_manager.is_eq(body) || m_manager.is_iff(body)); + expr * lhs = body->get_arg(0); + expr * rhs = body->get_arg(1); + SASSERT(is_app_of(lhs, f) || is_app_of(rhs, f)); + }); + obj_map::obj_map_entry * entry = m_decl2macro.insert_if_not_there2(f, 0); + if (entry->get_data().m_value == 0) { + // new entry + m_manager.inc_ref(f); + m_manager.inc_ref(q); + entry->get_data().m_value = q; + if (proofs_enabled()) { + SASSERT(!m_decl2macro_pr->contains(f)); + m_decl2macro_pr->insert(f, pr); + m_manager.inc_ref(pr); + } + if (unsat_core_enabled()) { + SASSERT(!m_decl2macro_dep->contains(f)); + m_decl2macro_dep->insert(f, dep); + m_manager.inc_ref(dep); + } + } + else { + // replacing entry + m_manager.inc_ref(q); + m_manager.dec_ref(entry->get_data().m_value); + entry->get_data().m_value = q; + if (proofs_enabled()) { + obj_map::obj_map_entry * entry_pr = m_decl2macro_pr->find_core(f); + SASSERT(entry_pr != 0); + m_manager.inc_ref(pr); + m_manager.dec_ref(entry_pr->get_data().m_value); + entry_pr->get_data().m_value = pr; + } + if (unsat_core_enabled()) { + obj_map::obj_map_entry * entry_dep = m_decl2macro_dep->find_core(f); + SASSERT(entry_dep != 0); + m_manager.inc_ref(dep); + m_manager.dec_ref(entry_dep->get_data().m_value); + entry_dep->get_data().m_value = dep; + } + } +} + +void macro_substitution::erase(func_decl * f) { + if (proofs_enabled()) { + proof * pr = 0; + if (m_decl2macro_pr->find(f, pr)) { + m_manager.dec_ref(pr); + m_decl2macro_pr->erase(f); + } + } + if (unsat_core_enabled()) { + expr_dependency * dep = 0; + if (m_decl2macro_dep->find(f, dep)) { + m_manager.dec_ref(dep); + m_decl2macro_dep->erase(f); + } + } + quantifier * q = 0; + if (m_decl2macro.find(f, q)) { + m_manager.dec_ref(f); + m_manager.dec_ref(q); + m_decl2macro.erase(f); + } +} + +void macro_substitution::get_head_def(quantifier * q, func_decl * f, app * & head, expr * & def) { + app * body = to_app(q->get_expr()); + SASSERT(m_manager.is_eq(body) || m_manager.is_iff(body)); + expr * lhs = to_app(body)->get_arg(0); + expr * rhs = to_app(body)->get_arg(1); + SASSERT(is_app_of(lhs, f) || is_app_of(rhs, f)); + SASSERT(!is_app_of(lhs, f) || !is_app_of(rhs, f)); + if (is_app_of(lhs, f)) { + head = to_app(lhs); + def = rhs; + } + else { + head = to_app(rhs); + def = lhs; + } +} + +bool macro_substitution::find(func_decl * f, quantifier * & q, proof * & pr) { + if (m_decl2macro.find(f, q)) { + if (proofs_enabled()) + m_decl2macro_pr->find(f, pr); + return true; + } + return false; +} + +bool macro_substitution::find(func_decl * f, quantifier * & q, proof * & pr, expr_dependency * & dep) { + if (m_decl2macro.find(f, q)) { + if (proofs_enabled()) + m_decl2macro_pr->find(f, pr); + if (unsat_core_enabled()) + m_decl2macro_dep->find(f, dep); + return true; + } + return false; +} + + diff --git a/lib/macro_substitution.h b/lib/macro_substitution.h new file mode 100644 index 000000000..d3c9b3c9e --- /dev/null +++ b/lib/macro_substitution.h @@ -0,0 +1,59 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + macro_substitution.h + +Abstract: + + Mapping from func_decl to quantifiers of the form + Forall X. f(X) = T[X] + Forall X. f(X) iff C[X] + +Author: + + Leonardo (leonardo) 2012-02-17 + +Notes: + +--*/ +#ifndef _MACRO_SUBSTITUTION_H_ +#define _MACRO_SUBSTITUTION_H_ + +#include"ast.h" + +class macro_substitution { + ast_manager & m_manager; + obj_map m_decl2macro; + scoped_ptr > m_decl2macro_pr; + scoped_ptr > m_decl2macro_dep; + unsigned m_cores_enabled:1; + unsigned m_proofs_enabled:1; + + void init(); +public: + macro_substitution(ast_manager & m); + macro_substitution(ast_manager & m, bool cores_enabled); + macro_substitution(ast_manager & m, bool cores_enabled, bool proofs_enabled); + ~macro_substitution(); + + ast_manager & m() const { return m_manager; } + + bool proofs_enabled() const { return m_proofs_enabled; } + bool unsat_core_enabled() const { return m_cores_enabled; } + + bool empty() const { return m_decl2macro.empty(); } + + void insert(func_decl * f, quantifier * m, proof * pr, expr_dependency * dep = 0); + void erase(func_decl * f); + bool contains(func_decl * f) { return m_decl2macro.contains(f); } + bool find(func_decl * f, quantifier * & q, proof * & pr); + bool find(func_decl * f, quantifier * & q, proof * & pr, expr_dependency * & dep); + void get_head_def(quantifier * q, func_decl * f, app * & head, expr * & def); + + void reset(); + void cleanup(); +}; + +#endif diff --git a/lib/macro_util.cpp b/lib/macro_util.cpp new file mode 100644 index 000000000..059ea5e47 --- /dev/null +++ b/lib/macro_util.cpp @@ -0,0 +1,928 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + macro_util.cpp + +Abstract: + + Macro finding goodies. + They are used during preprocessing (MACRO_FINDER=true), and model building. + +Author: + + Leonardo de Moura (leonardo) 2010-12-15. + +Revision History: + +--*/ +#include"macro_util.h" +#include"occurs.h" +#include"arith_simplifier_plugin.h" +#include"basic_simplifier_plugin.h" +#include"bv_simplifier_plugin.h" +#include"var_subst.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"ast_util.h" +#include"for_each_expr.h" +#include"well_sorted.h" + +macro_util::macro_util(ast_manager & m, simplifier & s): + m_manager(m), + m_simplifier(s), + m_arith_simp(0), + m_bv_simp(0), + m_basic_simp(0), + m_forbidden_set(0), + m_curr_clause(0) { +} + +arith_simplifier_plugin * macro_util::get_arith_simp() const { + if (m_arith_simp == 0) { + const_cast(this)->m_arith_simp = static_cast(m_simplifier.get_plugin(m_manager.get_family_id("arith"))); + } + SASSERT(m_arith_simp != 0); + return m_arith_simp; +} + +bv_simplifier_plugin * macro_util::get_bv_simp() const { + if (m_bv_simp == 0) { + const_cast(this)->m_bv_simp = static_cast(m_simplifier.get_plugin(m_manager.get_family_id("bv"))); + } + SASSERT(m_bv_simp != 0); + return m_bv_simp; +} + +basic_simplifier_plugin * macro_util::get_basic_simp() const { + if (m_basic_simp == 0) { + const_cast(this)->m_basic_simp = static_cast(m_simplifier.get_plugin(m_manager.get_basic_family_id())); + } + SASSERT(m_basic_simp != 0); + return m_basic_simp; +} + +bool macro_util::is_bv(expr * n) const { + return get_bv_simp()->is_bv(n); +} + +bool macro_util::is_bv_sort(sort * s) const { + return get_bv_simp()->is_bv_sort(s); +} + +bool macro_util::is_add(expr * n) const { + return get_arith_simp()->is_add(n) || get_bv_simp()->is_add(n); +} + +bool macro_util::is_times_minus_one(expr * n, expr * & arg) const { + return get_arith_simp()->is_times_minus_one(n, arg) || get_bv_simp()->is_times_minus_one(n, arg); +} + +bool macro_util::is_le(expr * n) const { + return get_arith_simp()->is_le(n) || get_bv_simp()->is_le(n); +} + +bool macro_util::is_le_ge(expr * n) const { + return get_arith_simp()->is_le_ge(n) || get_bv_simp()->is_le_ge(n); +} + +poly_simplifier_plugin * macro_util::get_poly_simp_for(sort * s) const { + if (is_bv_sort(s)) + return get_bv_simp(); + else + return get_arith_simp(); +} + +app * macro_util::mk_zero(sort * s) const { + poly_simplifier_plugin * ps = get_poly_simp_for(s); + ps->set_curr_sort(s); + return ps->mk_zero(); +} + +void macro_util::mk_sub(expr * t1, expr * t2, expr_ref & r) const { + if (is_bv(t1)) { + get_bv_simp()->mk_sub(t1, t2, r); + } + else { + get_arith_simp()->mk_sub(t1, t2, r); + } +} + +void macro_util::mk_add(expr * t1, expr * t2, expr_ref & r) const { + if (is_bv(t1)) { + get_bv_simp()->mk_add(t1, t2, r); + } + else { + get_arith_simp()->mk_add(t1, t2, r); + } +} + +void macro_util::mk_add(unsigned num_args, expr * const * args, sort * s, expr_ref & r) const { + if (num_args == 0) { + r = mk_zero(s); + return; + } + poly_simplifier_plugin * ps = get_poly_simp_for(s); + ps->set_curr_sort(s); + ps->mk_add(num_args, args, r); +} + +/** + \brief Return true if \c n is an application of the form + + (f x_{k_1}, ..., x_{k_n}) + + where f is uninterpreted + n == num_decls + x_{k_i}'s are variables + and {k_1, ..., k_n } is equals to the set {0, ..., num_decls-1} +*/ +bool macro_util::is_macro_head(expr * n, unsigned num_decls) const { + if (is_app(n) && + !to_app(n)->get_decl()->is_associative() && + to_app(n)->get_family_id() == null_family_id && + to_app(n)->get_num_args() == num_decls) { + sbuffer var2pos; + var2pos.resize(num_decls, -1); + for (unsigned i = 0; i < num_decls; i++) { + expr * c = to_app(n)->get_arg(i); + if (!is_var(c)) + return false; + unsigned idx = to_var(c)->get_idx(); + if (idx >= num_decls || var2pos[idx] != -1) + return false; + var2pos[idx] = i; + } + return true; + } + return false; +} + +/** + \brief Return true if n is of the form + + (= (f x_{k_1}, ..., x_{k_n}) t) OR + (iff (f x_{k_1}, ..., x_{k_n}) t) + + where + + is_macro_head((f x_{k_1}, ..., x_{k_n})) returns true AND + t does not contain f AND + f is not in forbidden_set + + In case of success + head will contain (f x_{k_1}, ..., x_{k_n}) AND + def will contain t + +*/ +bool macro_util::is_left_simple_macro(expr * n, unsigned num_decls, app * & head, expr * & def) const { + if (m_manager.is_eq(n) || m_manager.is_iff(n)) { + expr * lhs = to_app(n)->get_arg(0); + expr * rhs = to_app(n)->get_arg(1); + if (is_macro_head(lhs, num_decls) && !is_forbidden(to_app(lhs)->get_decl()) && !occurs(to_app(lhs)->get_decl(), rhs)) { + head = to_app(lhs); + def = rhs; + return true; + } + } + return false; +} + +/** + \brief Return true if n is of the form + + (= t (f x_{k_1}, ..., x_{k_n})) OR + (iff t (f x_{k_1}, ..., x_{k_n})) + + where + + is_macro_head((f x_{k_1}, ..., x_{k_n})) returns true AND + t does not contain f AND + f is not in forbidden_set + + In case of success + head will contain (f x_{k_1}, ..., x_{k_n}) AND + def will contain t + +*/ +bool macro_util::is_right_simple_macro(expr * n, unsigned num_decls, app * & head, expr * & def) const { + if (m_manager.is_eq(n) || m_manager.is_iff(n)) { + expr * lhs = to_app(n)->get_arg(0); + expr * rhs = to_app(n)->get_arg(1); + if (is_macro_head(rhs, num_decls) && !is_forbidden(to_app(rhs)->get_decl()) && !occurs(to_app(rhs)->get_decl(), lhs)) { + head = to_app(rhs); + def = lhs; + return true; + } + } + return false; +} + +/** + \brief Return true if n contains f. The method ignores the sub-expression \c exception. + + \remark n is a "polynomial". +*/ +bool macro_util::poly_contains_head(expr * n, func_decl * f, expr * exception) const { + expr * curr = n; + unsigned num_args; + expr * const * args; + if (is_add(n)) { + num_args = to_app(n)->get_num_args(); + args = to_app(n)->get_args(); + } + else { + num_args = 1; + args = &n; + } + for (unsigned i = 0; i < num_args; i++) { + expr * arg = args[i]; + if (arg != exception && occurs(f, arg)) + return true; + } + return false; +} + +bool macro_util::is_arith_macro(expr * n, unsigned num_decls, app_ref & head, expr_ref & def, bool & inv) const { + // TODO: obsolete... we should move to collect_arith_macro_candidates + arith_simplifier_plugin * as = get_arith_simp(); + if (!m_manager.is_eq(n) && !as->is_le(n) && !as->is_ge(n)) + return false; + expr * lhs = to_app(n)->get_arg(0); + expr * rhs = to_app(n)->get_arg(1); + + if (!as->is_numeral(rhs)) + return false; + + inv = false; + ptr_buffer args; + expr * h = 0; + unsigned lhs_num_args; + expr * const * lhs_args; + if (is_add(lhs)) { + lhs_num_args = to_app(lhs)->get_num_args(); + lhs_args = to_app(lhs)->get_args(); + } + else { + lhs_num_args = 1; + lhs_args = &lhs; + } + for (unsigned i = 0; i < lhs_num_args; i++) { + expr * arg = lhs_args[i]; + expr * neg_arg; + if (h == 0 && + is_macro_head(arg, num_decls) && + !is_forbidden(to_app(arg)->get_decl()) && + !poly_contains_head(lhs, to_app(arg)->get_decl(), arg)) { + h = arg; + } + else if (h == 0 && as->is_times_minus_one(arg, neg_arg) && + is_macro_head(neg_arg, num_decls) && + !is_forbidden(to_app(neg_arg)->get_decl()) && + !poly_contains_head(lhs, to_app(neg_arg)->get_decl(), arg)) { + h = neg_arg; + inv = true; + } + else { + args.push_back(arg); + } + } + if (h == 0) + return false; + head = to_app(h); + expr_ref tmp(m_manager); + as->mk_add(args.size(), args.c_ptr(), tmp); + if (inv) + as->mk_sub(tmp, rhs, def); + else + as->mk_sub(rhs, tmp, def); + return true; +} + +/** + \brief Auxiliary function for is_pseudo_predicate_macro. It detects the pattern (= (f X) t) +*/ +bool macro_util::is_pseudo_head(expr * n, unsigned num_decls, app * & head, app * & t) { + if (!m_manager.is_eq(n)) + return false; + expr * lhs = to_app(n)->get_arg(0); + expr * rhs = to_app(n)->get_arg(1); + if (!is_ground(lhs) && !is_ground(rhs)) + return false; + sort * s = m_manager.get_sort(lhs); + if (m_manager.is_uninterp(s)) + return false; + sort_size sz = s->get_num_elements(); + if (sz.is_finite() && sz.size() == 1) + return false; + if (is_macro_head(lhs, num_decls)) { + head = to_app(lhs); + t = to_app(rhs); + return true; + } + if (is_macro_head(rhs, num_decls)) { + head = to_app(rhs); + t = to_app(lhs); + return true; + } + return false; +} + +/** + \brief Returns true if n if of the form (forall (X) (iff (= (f X) t) def[X])) + where t is a ground term, (f X) is the head. +*/ +bool macro_util::is_pseudo_predicate_macro(expr * n, app * & head, app * & t, expr * & def) { + if (!is_quantifier(n) || !to_quantifier(n)->is_forall()) + return false; + TRACE("macro_util", tout << "processing: " << mk_pp(n, m_manager) << "\n";); + expr * body = to_quantifier(n)->get_expr(); + unsigned num_decls = to_quantifier(n)->get_num_decls(); + if (!m_manager.is_iff(body)) + return false; + expr * lhs = to_app(body)->get_arg(0); + expr * rhs = to_app(body)->get_arg(1); + if (is_pseudo_head(lhs, num_decls, head, t) && + !is_forbidden(head->get_decl()) && + !occurs(head->get_decl(), rhs)) { + def = rhs; + return true; + } + if (is_pseudo_head(rhs, num_decls, head, t) && + !is_forbidden(head->get_decl()) && + !occurs(head->get_decl(), lhs)) { + def = lhs; + return true; + } + return false; +} + +/** + \brief A quasi-macro head is of the form f[X_1, ..., X_n], + where n == num_decls, f[X_1, ..., X_n] is a term starting with symbol f, f is uninterpreted, + contains all universally quantified variables as arguments. + Note that, some arguments of f[X_1, ..., X_n] may not be variables. + + Examples of quasi-macros: + f(x_1, x_1 + x_2, x_2) for num_decls == 2 + g(x_1, x_1) for num_decls == 1 + + Return true if \c n is a quasi-macro. Store the macro head in \c head, and the conditions to apply the macro in \c cond. +*/ +bool macro_util::is_quasi_macro_head(expr * n, unsigned num_decls) const { + if (is_app(n) && + to_app(n)->get_family_id() == null_family_id && + to_app(n)->get_num_args() >= num_decls) { + unsigned num_args = to_app(n)->get_num_args(); + sbuffer found_vars; + found_vars.resize(num_decls, false); + unsigned num_found_vars = 0; + for (unsigned i = 0; i < num_args; i++) { + expr * arg = to_app(n)->get_arg(i); + if (is_var(arg)) { + unsigned idx = to_var(arg)->get_idx(); + if (idx >= num_decls) + return false; + if (found_vars[idx] == false) { + found_vars[idx] = true; + num_found_vars++; + } + } + else { + if (occurs(to_app(n)->get_decl(), arg)) + return false; + } + } + return num_found_vars == num_decls; + } + return false; +} + +/** + \brief Convert a quasi-macro head into a macro head, and store the conditions under + which it is valid in cond. +*/ +void macro_util::quasi_macro_head_to_macro_head(app * qhead, unsigned num_decls, app_ref & head, expr_ref & cond) const { + unsigned num_args = qhead->get_num_args(); + sbuffer found_vars; + found_vars.resize(num_decls, false); + ptr_buffer new_args; + ptr_buffer new_conds; + unsigned next_var_idx = num_decls; + for (unsigned i = 0; i < num_args; i++) { + expr * arg = qhead->get_arg(i); + if (is_var(arg)) { + unsigned idx = to_var(arg)->get_idx(); + SASSERT(idx < num_decls); + if (found_vars[idx] == false) { + found_vars[idx] = true; + new_args.push_back(arg); + continue; + } + } + var * new_var = m_manager.mk_var(next_var_idx, m_manager.get_sort(arg)); + next_var_idx++; + expr * new_cond = m_manager.mk_eq(new_var, arg); + new_args.push_back(new_var); + new_conds.push_back(new_cond); + } + get_basic_simp()->mk_and(new_conds.size(), new_conds.c_ptr(), cond); + head = m_manager.mk_app(qhead->get_decl(), new_args.size(), new_args.c_ptr()); +} + +/** + \brief Given a macro defined by head and def, stores an interpretation for head->get_decl() in interp. + This method assumes is_macro_head(head, head->get_num_args()) returns true, + and def does not contain head->get_decl(). + + See normalize_expr +*/ +void macro_util::mk_macro_interpretation(app * head, expr * def, expr_ref & interp) const { + SASSERT(is_macro_head(head, head->get_num_args())); + SASSERT(!occurs(head->get_decl(), def)); + normalize_expr(head, def, interp); +} + +/** + \brief The variables in head may be in the wrong order. + Example: f(x_1, x_0) instead of f(x_0, x_1) + This method is essentially renaming the variables in t. + Suppose t is g(x_1, x_0 + x_1) + This method will store g(x_0, x_1 + x_0) in norm_t. + + f(x_1, x_2) --> f(x_0, x_1) + f(x_3, x_2) --> f(x_0, x_1) +*/ +void macro_util::normalize_expr(app * head, expr * t, expr_ref & norm_t) const { + expr_ref_buffer var_mapping(m_manager); + bool changed = false; + unsigned num_args = head->get_num_args(); + unsigned max = num_args; + for (unsigned i = 0; i < num_args; i++) { + var * v = to_var(head->get_arg(i)); + if (v->get_idx() >= max) + max = v->get_idx() + 1; + } + TRACE("normalize_expr_bug", + tout << "head: " << mk_pp(head, m_manager) << "\n"; + tout << "applying substitution to:\n" << mk_bounded_pp(t, m_manager) << "\n";); + for (unsigned i = 0; i < num_args; i++) { + var * v = to_var(head->get_arg(i)); + if (v->get_idx() != i) { + changed = true; + var * new_var = m_manager.mk_var(i, v->get_sort()); + CTRACE("normalize_expr_bug", v->get_idx() >= num_args, tout << mk_pp(v, m_manager) << ", num_args: " << num_args << "\n";); + SASSERT(v->get_idx() < max); + var_mapping.setx(max - v->get_idx() - 1, new_var); + } + else { + var_mapping.setx(max - i - 1, v); + } + } + if (changed) { + // REMARK: t may have nested quantifiers... So, I must use the std order for variable substitution. + var_subst subst(m_manager); + TRACE("macro_util_bug", + tout << "head: " << mk_pp(head, m_manager) << "\n"; + tout << "applying substitution to:\n" << mk_ll_pp(t, m_manager) << "\nsubstitituion:\n"; + for (unsigned i = 0; i < var_mapping.size(); i++) { + tout << "#" << i << " -> " << mk_pp(var_mapping[i], m_manager) << "\n"; + }); + subst(t, var_mapping.size(), var_mapping.c_ptr(), norm_t); + SASSERT(is_well_sorted(m_manager, norm_t)); + } + else { + norm_t = t; + } +} + +// ----------------------------- +// +// "Hint" support +// See comment at is_hint_atom +// for a definition of what a hint is. +// +// ----------------------------- + +bool is_hint_head(expr * n, ptr_buffer & vars) { + if (!is_app(n)) + return false; + if (to_app(n)->get_decl()->is_associative() || to_app(n)->get_family_id() != null_family_id) + return false; + unsigned num_args = to_app(n)->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = to_app(n)->get_arg(i); + if (is_var(arg)) + vars.push_back(to_var(arg)); + } + return !vars.empty(); +} + +/** + \brief Returns true if the variables in n is a subset of \c vars. +*/ +bool vars_of_is_subset(expr * n, ptr_buffer const & vars) { + if (is_ground(n)) + return true; + obj_hashtable visited; + ptr_buffer todo; + todo.push_back(n); + while (!todo.empty()) { + expr * curr = todo.back(); + todo.pop_back(); + if (is_var(curr)) { + if (std::find(vars.begin(), vars.end(), to_var(curr)) == vars.end()) + return false; + } + else if (is_app(curr)) { + unsigned num_args = to_app(curr)->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = to_app(curr)->get_arg(i); + if (is_ground(arg)) + continue; + if (visited.contains(arg)) + continue; + visited.insert(arg); + todo.push_back(arg); + } + } + else { + SASSERT(is_quantifier(curr)); + return false; // do no support nested quantifier... being conservative. + } + } + return true; +} + +/** + \brief (= lhs rhs) is a hint atom if + lhs is of the form (f t_1 ... t_n) + and all variables occurring in rhs are direct arguments of lhs. +*/ +bool is_hint_atom(expr * lhs, expr * rhs) { + ptr_buffer vars; + if (!is_hint_head(lhs, vars)) + return false; + return !occurs(to_app(lhs)->get_decl(), rhs) && vars_of_is_subset(rhs, vars); +} + +void hint_to_macro_head(ast_manager & m, app * head, unsigned num_decls, app_ref & new_head) { + unsigned num_args = head->get_num_args(); + ptr_buffer new_args; + sbuffer found_vars; + found_vars.resize(num_decls, false); + unsigned next_var_idx = num_decls; + for (unsigned i = 0; i < num_args; i++) { + expr * arg = head->get_arg(i); + if (is_var(arg)) { + unsigned idx = to_var(arg)->get_idx(); + SASSERT(idx < num_decls); + if (found_vars[idx] == false) { + found_vars[idx] = true; + new_args.push_back(arg); + continue; + } + } + var * new_var = m.mk_var(next_var_idx, m.get_sort(arg)); + next_var_idx++; + new_args.push_back(new_var); + } + new_head = m.mk_app(head->get_decl(), new_args.size(), new_args.c_ptr()); +} + +/** + \brief Return true if n can be viewed as a polynomial "hint" based on head. + That is, n (but the monomial exception) only uses the variables in head, and does not use + head->get_decl(). + is_hint_head(head, vars) must also return true +*/ +bool macro_util::is_poly_hint(expr * n, app * head, expr * exception) { + TRACE("macro_util_hint", tout << "is_poly_hint n:\n" << mk_pp(n, m_manager) << "\nhead:\n" << mk_pp(head, m_manager) << "\nexception:\n" + << mk_pp(exception, m_manager) << "\n";); + ptr_buffer vars; + if (!is_hint_head(head, vars)) { + TRACE("macro_util_hint", tout << "failed because head is not hint head\n";); + return false; + } + func_decl * f = head->get_decl(); + unsigned num_args; + expr * const * args; + if (is_add(n)) { + num_args = to_app(n)->get_num_args(); + args = to_app(n)->get_args(); + } + else { + num_args = 1; + args = &n; + } + for (unsigned i = 0; i < num_args; i++) { + expr * arg = args[i]; + if (arg != exception && (occurs(f, arg) || !vars_of_is_subset(arg, vars))) { + TRACE("macro_util_hint", tout << "failed because of:\n" << mk_pp(arg, m_manager) << "\n";); + return false; + } + } + TRACE("macro_util_hint", tout << "succeeded\n";); + return true; + +} + +// ----------------------------- +// +// Macro candidates +// +// ----------------------------- + + +macro_util::macro_candidates::macro_candidates(ast_manager & m): + m_defs(m), + m_conds(m) { +} + +void macro_util::macro_candidates::reset() { + m_fs.reset(); + m_defs.reset(); + m_conds.reset(); + m_ineq.reset(); + m_satisfy.reset(); + m_hint.reset(); +} + +void macro_util::macro_candidates::insert(func_decl * f, expr * def, expr * cond, bool ineq, bool satisfy_atom, bool hint) { + m_fs.push_back(f); + m_defs.push_back(def); + m_conds.push_back(cond); + m_ineq.push_back(ineq); + m_satisfy.push_back(satisfy_atom); + m_hint.push_back(hint); +} + +// ----------------------------- +// +// Macro util +// +// ----------------------------- + +void macro_util::insert_macro(app * head, expr * def, expr * cond, bool ineq, bool satisfy_atom, bool hint, macro_candidates & r) { + expr_ref norm_def(m_manager); + expr_ref norm_cond(m_manager); + normalize_expr(head, def, norm_def); + if (cond != 0) + normalize_expr(head, cond, norm_cond); + else if (!hint) + norm_cond = m_manager.mk_true(); + SASSERT(!hint || norm_cond.get() == 0); + r.insert(head->get_decl(), norm_def.get(), norm_cond.get(), ineq, satisfy_atom, hint); +} + +void macro_util::insert_quasi_macro(app * head, unsigned num_decls, expr * def, expr * cond, bool ineq, bool satisfy_atom, + bool hint, macro_candidates & r) { + if (!is_macro_head(head, head->get_num_args())) { + app_ref new_head(m_manager); + expr_ref extra_cond(m_manager); + expr_ref new_cond(m_manager); + if (!hint) { + quasi_macro_head_to_macro_head(head, num_decls, new_head, extra_cond); + if (cond == 0) + new_cond = extra_cond; + else + get_basic_simp()->mk_and(cond, extra_cond, new_cond); + } + else { + hint_to_macro_head(m_manager, head, num_decls, new_head); + } + insert_macro(new_head, def, new_cond, ineq, satisfy_atom, hint, r); + } + else { + insert_macro(head, def, cond, ineq, satisfy_atom, hint, r); + } +} + +bool macro_util::rest_contains_decl(func_decl * f, expr * except_lit) { + if (m_curr_clause == 0) + return false; + SASSERT(is_clause(m_manager, m_curr_clause)); + unsigned num_lits = get_clause_num_literals(m_manager, m_curr_clause); + for (unsigned i = 0; i < num_lits; i++) { + expr * l = get_clause_literal(m_manager, m_curr_clause, i); + if (l != except_lit && occurs(f, l)) + return true; + } + return false; +} + +void macro_util::get_rest_clause_as_cond(expr * except_lit, expr_ref & extra_cond) { + if (m_curr_clause == 0) + return; + SASSERT(is_clause(m_manager, m_curr_clause)); + basic_simplifier_plugin * bs = get_basic_simp(); + expr_ref_buffer neg_other_lits(m_manager); + unsigned num_lits = get_clause_num_literals(m_manager, m_curr_clause); + for (unsigned i = 0; i < num_lits; i++) { + expr * l = get_clause_literal(m_manager, m_curr_clause, i); + if (l != except_lit) { + expr_ref neg_l(m_manager); + bs->mk_not(l, neg_l); + neg_other_lits.push_back(neg_l); + } + } + if (neg_other_lits.empty()) + return; + get_basic_simp()->mk_and(neg_other_lits.size(), neg_other_lits.c_ptr(), extra_cond); +} + +void macro_util::collect_poly_args(expr * n, expr * exception, ptr_buffer & args) { + args.reset(); + bool stop = false; + unsigned num_args; + expr * const * _args; + if (is_add(n)) { + num_args = to_app(n)->get_num_args(); + _args = to_app(n)->get_args(); + } + else { + num_args = 1; + _args = &n; + } + for (unsigned i = 0; i < num_args; i++) { + expr * arg = _args[i]; + if (arg != exception) + args.push_back(arg); + } +} + +void macro_util::add_arith_macro_candidate(app * head, unsigned num_decls, expr * def, expr * atom, bool ineq, bool hint, macro_candidates & r) { + expr_ref cond(m_manager); + if (!hint) + get_rest_clause_as_cond(atom, cond); + insert_quasi_macro(head, num_decls, def, cond, ineq, true, hint, r); +} + +void macro_util::collect_arith_macro_candidates(expr * lhs, expr * rhs, expr * atom, unsigned num_decls, bool is_ineq, macro_candidates & r) { + if (!is_add(lhs) && m_manager.is_eq(atom)) // this case is a simple macro. + return; + bool stop = false; + ptr_buffer args; + unsigned lhs_num_args; + expr * const * lhs_args; + if (is_add(lhs)) { + lhs_num_args = to_app(lhs)->get_num_args(); + lhs_args = to_app(lhs)->get_args(); + } + else { + lhs_num_args = 1; + lhs_args = &lhs; + } + for (unsigned i = 0; i < lhs_num_args; i++) { + expr * arg = lhs_args[i]; + expr * neg_arg; + if (!is_app(arg)) + continue; + func_decl * f = to_app(arg)->get_decl(); + + bool _is_arith_macro = + is_quasi_macro_head(arg, num_decls) && + !is_forbidden(f) && + !poly_contains_head(lhs, f, arg) && + !occurs(f, rhs) && + !rest_contains_decl(f, atom); + bool _is_poly_hint = !_is_arith_macro && is_poly_hint(lhs, to_app(arg), arg); + + if (_is_arith_macro || _is_poly_hint) { + collect_poly_args(lhs, arg, args); + expr_ref rest(m_manager); + mk_add(args.size(), args.c_ptr(), m_manager.get_sort(arg), rest); + expr_ref def(m_manager); + mk_sub(rhs, rest, def); + add_arith_macro_candidate(to_app(arg), num_decls, def, atom, is_ineq, _is_poly_hint, r); + } + else if (is_times_minus_one(arg, neg_arg) && is_app(neg_arg)) { + f = to_app(neg_arg)->get_decl(); + bool _is_arith_macro = + is_quasi_macro_head(neg_arg, num_decls) && + !is_forbidden(f) && + !poly_contains_head(lhs, f, arg) && + !occurs(f, rhs) && + !rest_contains_decl(f, atom); + bool _is_poly_hint = !_is_arith_macro && is_poly_hint(lhs, to_app(neg_arg), arg); + + if (_is_arith_macro || _is_poly_hint) { + collect_poly_args(lhs, arg, args); + expr_ref rest(m_manager); + mk_add(args.size(), args.c_ptr(), m_manager.get_sort(arg), rest); + expr_ref def(m_manager); + mk_sub(rest, rhs, def); + add_arith_macro_candidate(to_app(neg_arg), num_decls, def, atom, is_ineq, _is_poly_hint, r); + } + } + } +} + +void macro_util::collect_arith_macro_candidates(expr * atom, unsigned num_decls, macro_candidates & r) { + TRACE("macro_util_hint", tout << "collect_arith_macro_candidates:\n" << mk_pp(atom, m_manager) << "\n";); + if (!m_manager.is_eq(atom) && !is_le_ge(atom)) + return; + expr * lhs = to_app(atom)->get_arg(0); + expr * rhs = to_app(atom)->get_arg(1); + bool is_ineq = !m_manager.is_eq(atom); + collect_arith_macro_candidates(lhs, rhs, atom, num_decls, is_ineq, r); + collect_arith_macro_candidates(rhs, lhs, atom, num_decls, is_ineq, r); +} + +/** + \brief Collect macro candidates for atom \c atom. + The candidates are stored in \c r. + + The following post-condition holds: + + for each i in [0, r.size() - 1] + we have a conditional macro of the form + + r.get_cond(i) IMPLIES f(x_1, ..., x_n) = r.get_def(i) + + where + f == r.get_fs(i) .., x_n), f is uninterpreted and x_1, ..., x_n are variables. + r.get_cond(i) and r.get_defs(i) do not contain f or variables not in {x_1, ..., x_n} + + The idea is to use r.get_defs(i) as the interpretation for f in a model M whenever r.get_cond(i) + + Given a model M and values { v_1, ..., v_n } + Let M' be M{x_1 -> v_1, ..., v_n -> v_n} + + Note that M'(f(x_1, ..., x_n)) = M(f)(v_1, ..., v_n) + + Then, IF we have that M(f)(v_1, ..., v_n) = M'(r.get_def(i)) AND + M'(r.get_cond(i)) = true + THEN M'(atom) = true + + That is, if the conditional macro is used then the atom is satisfied when M'(r.get_cond(i)) = true + + IF r.is_ineq(i) = false, then + M(f)(v_1, ..., v_n) ***MUST BE*** M'(r.get_def(i)) whenever M'(r.get_cond(i)) = true + + IF r.satisfy_atom(i) = true, then we have the stronger property: + + Then, IF we have that (M'(r.get_cond(i)) = true IMPLIES M(f)(v_1, ..., v_n) = M'(r.get_def(i))) + THEN M'(atom) = true +*/ +void macro_util::collect_macro_candidates_core(expr * atom, unsigned num_decls, macro_candidates & r) { + if (m_manager.is_eq(atom) || m_manager.is_iff(atom)) { + expr * lhs = to_app(atom)->get_arg(0); + expr * rhs = to_app(atom)->get_arg(1); + if (is_quasi_macro_head(lhs, num_decls) && + !is_forbidden(to_app(lhs)->get_decl()) && + !occurs(to_app(lhs)->get_decl(), rhs) && + !rest_contains_decl(to_app(lhs)->get_decl(), atom)) { + expr_ref cond(m_manager); + get_rest_clause_as_cond(atom, cond); + insert_quasi_macro(to_app(lhs), num_decls, rhs, cond, false, true, false, r); + } + else if (is_hint_atom(lhs, rhs)) { + insert_quasi_macro(to_app(lhs), num_decls, rhs, 0, false, true, true, r); + } + + + if (is_quasi_macro_head(rhs, num_decls) && + !is_forbidden(to_app(rhs)->get_decl()) && + !occurs(to_app(rhs)->get_decl(), lhs) && + !rest_contains_decl(to_app(rhs)->get_decl(), atom)) { + expr_ref cond(m_manager); + get_rest_clause_as_cond(atom, cond); + insert_quasi_macro(to_app(rhs), num_decls, lhs, cond, false, true, false, r); + } + else if (is_hint_atom(rhs, lhs)) { + insert_quasi_macro(to_app(rhs), num_decls, lhs, 0, false, true, true, r); + } + } + + collect_arith_macro_candidates(atom, num_decls, r); +} + +void macro_util::collect_macro_candidates(expr * atom, unsigned num_decls, macro_candidates & r) { + m_curr_clause = 0; + r.reset(); + collect_macro_candidates_core(atom, num_decls, r); +} + +void macro_util::collect_macro_candidates(quantifier * q, macro_candidates & r) { + r.reset(); + expr * n = q->get_expr(); + if (has_quantifiers(n)) + return; + unsigned num_decls = q->get_num_decls(); + SASSERT(m_curr_clause == 0); + if (is_clause(m_manager, n)) { + m_curr_clause = n; + unsigned num_lits = get_clause_num_literals(m_manager, n); + for (unsigned i = 0; i < num_lits; i++) + collect_macro_candidates_core(get_clause_literal(m_manager, n, i), num_decls, r); + m_curr_clause = 0; + } + else { + collect_macro_candidates_core(n, num_decls, r); + } +} + + + diff --git a/lib/macro_util.h b/lib/macro_util.h new file mode 100644 index 000000000..9bc32af69 --- /dev/null +++ b/lib/macro_util.h @@ -0,0 +1,144 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + macro_util.h + +Abstract: + + Macro finding goodies. + They are used during preprocessing (MACRO_FINDER=true), and model building. + +Author: + + Leonardo de Moura (leonardo) 2010-12-15. + +Revision History: + +--*/ +#ifndef _MACRO_UTIL_H_ +#define _MACRO_UTIL_H_ + +#include"ast.h" +#include"obj_hashtable.h" +#include"simplifier.h" + +class poly_simplifier_plugin; +class arith_simplifier_plugin; +class bv_simplifier_plugin; +class basic_simplifier_plugin; + +class macro_util { +public: + /** + \brief See collect_macro_candidates. + */ + class macro_candidates { + ptr_vector m_fs; + expr_ref_vector m_defs; + expr_ref_vector m_conds; + svector m_ineq; // true if the macro is based on an inequality instead of equality. + svector m_satisfy; + svector m_hint; // macro did not contain all universal variables in the quantifier. + friend class macro_util; + ast_manager & get_manager() { return m_conds.get_manager(); } + + public: + macro_candidates(ast_manager & m); + ~macro_candidates() { reset(); } + + void reset(); + void insert(func_decl * f, expr * def, expr * cond, bool ineq, bool satisfy_atom, bool hint); + bool empty() const { return m_fs.empty(); } + unsigned size() const { return m_fs.size(); } + func_decl * get_f(unsigned i) const { return m_fs[i]; } + expr * get_def(unsigned i) const { return m_defs.get(i); } + expr * get_cond(unsigned i) const { return m_conds.get(i); } + bool ineq(unsigned i) const { return m_ineq[i]; } + bool satisfy_atom(unsigned i) const { return m_satisfy[i]; } + bool hint(unsigned i) const { return m_hint[i]; } + }; + +private: + ast_manager & m_manager; + simplifier & m_simplifier; + arith_simplifier_plugin * m_arith_simp; + bv_simplifier_plugin * m_bv_simp; + basic_simplifier_plugin * m_basic_simp; + obj_hashtable * m_forbidden_set; + + bool is_forbidden(func_decl * f) const { return m_forbidden_set != 0 && m_forbidden_set->contains(f); } + bool poly_contains_head(expr * n, func_decl * f, expr * exception) const; + + void collect_arith_macros(expr * n, unsigned num_decls, unsigned max_macros, bool allow_cond_macros, + macro_candidates & r); + + void normalize_expr(app * head, expr * t, expr_ref & norm_t) const; + void insert_macro(app * head, expr * def, expr * cond, bool ineq, bool satisfy_atom, bool hint, macro_candidates & r); + void insert_quasi_macro(app * head, unsigned num_decls, expr * def, expr * cond, bool ineq, bool satisfy_atom, bool hint, + macro_candidates & r); + + expr * m_curr_clause; // auxiliary var used in collect_macro_candidates. + + // Return true if m_curr_clause contains f in a literal different from except_lit + bool rest_contains_decl(func_decl * f, expr * except_lit); + // Store in extra_cond (and (not l_1) ... (not l_n)) where l_i's are the literals of m_curr_clause that are different from except_lit. + void get_rest_clause_as_cond(expr * except_lit, expr_ref & extra_cond); + void collect_poly_args(expr * n, expr * exception, ptr_buffer & args); + void add_arith_macro_candidate(app * head, unsigned num_decls, expr * def, expr * atom, bool ineq, bool hint, macro_candidates & r); + void collect_arith_macro_candidates(expr * lhs, expr * rhs, expr * atom, unsigned num_decls, bool ineq, macro_candidates & r); + void collect_arith_macro_candidates(expr * atom, unsigned num_decls, macro_candidates & r); + void collect_macro_candidates_core(expr * atom, unsigned num_decls, macro_candidates & r); + bool is_poly_hint(expr * n, app * head, expr * exception); + + +public: + macro_util(ast_manager & m, simplifier & s); + void set_forbidden_set(obj_hashtable * s) { m_forbidden_set = s; } + + arith_simplifier_plugin * get_arith_simp() const; + bv_simplifier_plugin * get_bv_simp() const; + basic_simplifier_plugin * get_basic_simp() const; + + bool is_macro_head(expr * n, unsigned num_decls) const; + bool is_left_simple_macro(expr * n, unsigned num_decls, app * & head, expr * & def) const; + bool is_right_simple_macro(expr * n, unsigned num_decls, app * & head, expr * & def) const; + bool is_simple_macro(expr * n, unsigned num_decls, app * & head, expr * & def) const { + return is_left_simple_macro(n, num_decls, head, def) || is_right_simple_macro(n, num_decls, head, def); + } + + bool is_arith_macro(expr * n, unsigned num_decls, app_ref & head, expr_ref & def, bool & inv) const; + bool is_arith_macro(expr * n, unsigned num_decls, app_ref & head, expr_ref & def) const { + bool inv; + return is_arith_macro(n, num_decls, head, def, inv); + } + + bool is_pseudo_head(expr * n, unsigned num_decls, app * & head, app * & t); + bool is_pseudo_predicate_macro(expr * n, app * & head, app * & t, expr * & def); + + bool is_quasi_macro_head(expr * n, unsigned num_decls) const; + void quasi_macro_head_to_macro_head(app * qhead, unsigned num_decls, app_ref & head, expr_ref & cond) const; + + void mk_macro_interpretation(app * head, expr * def, expr_ref & interp) const; + + void collect_macro_candidates(expr * atom, unsigned num_decls, macro_candidates & r); + void collect_macro_candidates(quantifier * q, macro_candidates & r); + + // + // Auxiliary goodness that allows us to manipulate BV and Arith polynomials. + // + bool is_bv(expr * n) const; + bool is_bv_sort(sort * s) const; + app * mk_zero(sort * s) const; + bool is_add(expr * n) const; + bool is_times_minus_one(expr * n, expr * & arg) const; + bool is_le(expr * n) const; + bool is_le_ge(expr * n) const; + void mk_sub(expr * t1, expr * t2, expr_ref & r) const; + void mk_add(expr * t1, expr * t2, expr_ref & r) const; + void mk_add(unsigned num_args, expr * const * args, sort * s, expr_ref & r) const; + poly_simplifier_plugin * get_poly_simp_for(sort * s) const; +}; + +#endif diff --git a/lib/mam.cpp b/lib/mam.cpp new file mode 100644 index 000000000..ba97d8939 --- /dev/null +++ b/lib/mam.cpp @@ -0,0 +1,3987 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + mam.cpp + +Abstract: + + Matching Abstract Machine + +Author: + + Leonardo de Moura (leonardo) 2007-02-13. + +Revision History: + +--*/ +#include"mam.h" +#include"smt_context.h" +#include"pool.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"trail.h" +#include"stopwatch.h" +#include"ast_smt2_pp.h" +#include + +// #define _PROFILE_MAM + +// ----------------------------------------- +// Flags for _PROFILE_MAM +// +// send profiling information to stdout +#define _PROFILE_MAM_TO_STDOUT +// threshold in secs for being considered expensive +#define _PROFILE_MAM_THRESHOLD 30.0 +// dump expensive (> _PROFILE_MAM_THRESHOLD) code trees whenever execute_core is executed. +#define _PROFILE_MAM_EXPENSIVE +// +#define _PROFILE_MAM_EXPENSIVE_FREQ 100000 +// +// ----------------------------------------- + +// #define _PROFILE_PATH_TREE +// ----------------------------------------- +// Flags for _PROFILE_PATH_TREE +// +#define _PROFILE_PATH_TREE_THRESHOLD 20000 +// +// ----------------------------------------- + +#define IS_CGR_SUPPORT true + +namespace smt { + // ------------------------------------ + // + // Trail + // + // ------------------------------------ + + class mam_impl; + + typedef trail_stack mam_trail_stack; + + typedef trail mam_trail; + + template + class mam_value_trail : public value_trail { + public: + mam_value_trail(T & value):value_trail(value) {} + }; + + + // ------------------------------------ + // + // Auxiliary + // + // ------------------------------------ + class label_hasher { + svector m_lbl2hash; // cache: lbl_id -> hash + + void mk_lbl_hash(unsigned lbl_id) { + unsigned a = 17; + unsigned b = 3; + unsigned c = lbl_id; + mix(a, b, c); + m_lbl2hash[lbl_id] = c & (APPROX_SET_CAPACITY - 1); + } + + public: + unsigned char operator()(func_decl * lbl) { + unsigned lbl_id = lbl->get_decl_id(); + if (lbl_id >= m_lbl2hash.size()) + m_lbl2hash.resize(lbl_id + 1, -1); + if (m_lbl2hash[lbl_id] == -1) { + mk_lbl_hash(lbl_id); + } + SASSERT(m_lbl2hash[lbl_id] >= 0); + return m_lbl2hash[lbl_id]; + } + + void display(std::ostream & out) const { + out << "lbl-hasher:\n"; + bool first = true; + for (unsigned i = 0; i < m_lbl2hash.size(); i++) { + if (m_lbl2hash[i] != -1) { + if (first) + first = false; + else + out << ", "; + out << i << " -> " << static_cast(m_lbl2hash[i]); + } + } + out << "\n"; + } + }; + + // ------------------------------------ + // + // Instructions + // + // ------------------------------------ + typedef enum { + INIT1=0, INIT2, INIT3, INIT4, INIT5, INIT6, INITN, + BIND1, BIND2, BIND3, BIND4, BIND5, BIND6, BINDN, + YIELD1, YIELD2, YIELD3, YIELD4, YIELD5, YIELD6, YIELDN, + COMPARE, CHECK, FILTER, CFILTER, PFILTER, CHOOSE, NOOP, CONTINUE, + GET_ENODE, + GET_CGR1, GET_CGR2, GET_CGR3, GET_CGR4, GET_CGR5, GET_CGR6, GET_CGRN, + IS_CGR + } opcode; + + struct instruction { + opcode m_opcode; + instruction * m_next; +#ifdef _PROFILE_MAM + unsigned m_counter; // how often it was executed +#endif + bool is_init() const { + return m_opcode >= INIT1 && m_opcode <= INITN; + } + }; + + struct compare : public instruction { + unsigned m_reg1; + unsigned m_reg2; + }; + + struct check : public instruction { + unsigned m_reg; + enode * m_enode; + }; + + struct filter : public instruction { + unsigned m_reg; + approx_set m_lbl_set; + }; + + struct pcheck : public instruction { + enode * m_enode; + approx_set m_lbl_set; + }; + + /** + \brief Copy m_enode to register m_oreg + */ + struct get_enode_instr : public instruction { + unsigned m_oreg; + enode * m_enode; + }; + + struct choose: public instruction { + choose * m_alt; + }; + + /** + \brief A depth-2 joint. It is used in CONTINUE instruction. + There are 3 forms of joints + 1) Variables: (f ... X ...) + 2) Ground terms: (f ... t ...) + 3) depth 2 joint: (f ... (g ... X ...) ...) + Joint2 stores the declartion g, and the position of variable X, and its idx. + + \remark Z3 has no support for depth 3 joints (f ... (g ... (h ... X ...) ...) ....) + */ + struct joint2 { + func_decl * m_decl; + unsigned m_arg_pos; + unsigned m_reg; // register that contains the variable + joint2(func_decl * f, unsigned pos, unsigned r):m_decl(f), m_arg_pos(pos), m_reg(r) {} + }; + +#define NULL_TAG 0 +#define GROUND_TERM_TAG 1 +#define VAR_TAG 2 +#define NESTED_VAR_TAG 3 + + struct cont: public instruction { + func_decl * m_label; + unsigned short m_num_args; + unsigned m_oreg; + approx_set m_lbl_set; // singleton set containing m_label + /* + The following field is an array of tagged pointers. + Each positon contains: + 1- null (no joint), NULL_TAG + 2- a boxed integer (i.e., register that contains the variable bind) VAR_TAG + 3- an enode pointer (ground term) GROUND_TERM_TAG + 4- or, a joint2 pointer. NESTED_VAR_TAG + + The size of the array is m_num_args. + */ + enode * m_joints[0]; + }; + + struct bind : public instruction { + func_decl * m_label; + unsigned short m_num_args; + unsigned m_ireg; + unsigned m_oreg; + }; + + struct get_cgr : public instruction { + func_decl * m_label; + approx_set m_lbl_set; + unsigned short m_num_args; + unsigned m_oreg; + unsigned m_iregs[0]; + }; + + struct yield : public instruction { + quantifier * m_qa; + app * m_pat; + unsigned short m_num_bindings; + unsigned m_bindings[0]; + }; + + struct is_cgr : public instruction { + unsigned m_ireg; + func_decl * m_label; + unsigned short m_num_args; + unsigned m_iregs[0]; + }; + + void display_num_args(std::ostream & out, unsigned num_args) { + if (num_args <= 6) { + out << num_args; + } + else { + out << "N"; + } + } + + void display_bind(std::ostream & out, const bind & b) { + out << "(BIND"; + display_num_args(out, b.m_num_args); + out << " " << b.m_label->get_name() << " " << b.m_ireg << " " << b.m_oreg << ")"; + } + + void display_get_cgr(std::ostream & out, const get_cgr & c) { + out << "(GET_CGR"; + display_num_args(out, c.m_num_args); + out << " " << c.m_label->get_name() << " " << c.m_oreg; + for (unsigned i = 0; i < c.m_num_args; i++) + out << " " << c.m_iregs[i]; + out << ")"; + } + + void display_is_cgr(std::ostream & out, const is_cgr & c) { + out << "(IS_CGR " << c.m_label->get_name() << " " << c.m_ireg; + for (unsigned i = 0; i < c.m_num_args; i++) + out << " " << c.m_iregs[i]; + out << ")"; + } + + void display_yield(std::ostream & out, const yield & y) { + out << "(YIELD"; + display_num_args(out, y.m_num_bindings); + out << " #" << y.m_qa->get_id(); + for (unsigned i = 0; i < y.m_num_bindings; i++) { + out << " " << y.m_bindings[i]; + } + out << ")"; + } + + void display_joints(std::ostream & out, unsigned num_joints, enode * const * joints) { + for (unsigned i = 0; i < num_joints; i++) { + if (i > 0) + out << " "; + enode * bare = joints[i]; + switch (GET_TAG(bare)) { + case NULL_TAG: out << "nil"; break; + case GROUND_TERM_TAG: out << "#" << UNTAG(enode*, bare)->get_owner_id(); break; + case VAR_TAG: out << UNBOXINT(bare); break; + case NESTED_VAR_TAG: out << "(" << UNTAG(joint2*, bare)->m_decl->get_name() << " " << UNTAG(joint2*, bare)->m_arg_pos << " " << UNTAG(joint2*, bare)->m_reg << ")"; break; + } + } + } + + void display_continue(std::ostream & out, const cont & c) { + out << "(CONTINUE " << c.m_label->get_name() << " " << c.m_num_args << " " << c.m_oreg << " " + << c.m_lbl_set << " ("; + display_joints(out, c.m_num_args, c.m_joints); + out << "))"; + } + + void display_filter(std::ostream & out, char const * op, filter const & instr) { + out << "(" << op << " " << instr.m_reg + << " " << instr.m_lbl_set << ")"; + } + + std::ostream & operator<<(std::ostream & out, const instruction & instr) { + switch (instr.m_opcode) { + case INIT1: case INIT2: case INIT3: case INIT4: case INIT5: case INIT6: case INITN: + out << "(INIT"; + if (instr.m_opcode <= INIT6) + out << (instr.m_opcode - INIT1 + 1); + else + out << "N"; + out << ")"; + break; + case BIND1: case BIND2: case BIND3: case BIND4: case BIND5: case BIND6: case BINDN: + display_bind(out, static_cast(instr)); + break; + case GET_CGR1: case GET_CGR2: case GET_CGR3: case GET_CGR4: case GET_CGR5: case GET_CGR6: case GET_CGRN: + display_get_cgr(out, static_cast(instr)); + break; + case IS_CGR: + display_is_cgr(out, static_cast(instr)); + break; + case YIELD1: case YIELD2: case YIELD3: case YIELD4: case YIELD5: case YIELD6: case YIELDN: + display_yield(out, static_cast(instr)); + break; + case CONTINUE: + display_continue(out, static_cast(instr)); + break; + case COMPARE: + out << "(COMPARE " << static_cast(instr).m_reg1 << " " + << static_cast(instr).m_reg2 << ")"; + break; + case CHECK: + out << "(CHECK " << static_cast(instr).m_reg + << " #" << static_cast(instr).m_enode->get_owner_id() << ")"; + break; + case FILTER: + display_filter(out, "FILTER", static_cast(instr)); + break; + case CFILTER: + display_filter(out, "CFILTER", static_cast(instr)); + break; + case PFILTER: + display_filter(out, "PFILTER", static_cast(instr)); + break; + case GET_ENODE: + out << "(GET_ENODE " << static_cast(instr).m_oreg << " #" << + static_cast(instr).m_enode->get_owner_id() << ")"; + break; + case CHOOSE: + out << "(CHOOSE)"; + break; + case NOOP: + out << "(NOOP)"; + break; + } +#ifdef _PROFILE_MAM + out << "[" << instr.m_counter << "]"; +#endif + return out; + } + + // ------------------------------------ + // + // Code Tree + // + // ------------------------------------ + + inline enode * get_enode(context & ctx, app * n) { + SASSERT(ctx.e_internalized(n)); + enode * e = ctx.get_enode(n); + SASSERT(e); + return e; + } + inline enode * mk_enode(context & ctx, quantifier * qa, app * n) { + ctx.internalize(n, false, ctx.get_generation(qa)); + enode * e = ctx.get_enode(n); + SASSERT(e); + return e; + } + + class code_tree { + label_hasher & m_lbl_hasher; + func_decl * m_root_lbl; + unsigned m_num_args; //!< we need this information to avoid the nary *,+ crash bug + unsigned char m_filter_candidates; + unsigned m_num_regs; + unsigned m_num_choices; + instruction * m_root; + enode_vector m_candidates; +#ifdef Z3DEBUG + context * m_context; + ptr_vector m_patterns; +#endif +#ifdef _PROFILE_MAM + stopwatch m_watch; + unsigned m_counter; +#endif + friend class compiler; + friend class code_tree_manager; + + void display_seq(std::ostream & out, instruction * head, unsigned indent) const { + for (unsigned i = 0; i < indent; i++) { + out << " "; + } + instruction * curr = head; + out << *curr; + curr = curr->m_next; + while (curr != 0 && curr->m_opcode != CHOOSE && curr->m_opcode != NOOP) { + out << " "; + out << *curr; + curr = curr->m_next; + } + out << "\n"; + if (curr != 0) { + display_children(out, static_cast(curr), indent + 1); + } + } + + void display_children(std::ostream & out, choose * first_child, unsigned indent) const { + choose * curr = first_child; + while (curr != 0) { + display_seq(out, curr, indent); + curr = curr->m_alt; + } + } + +#ifdef Z3DEBUG + void display_label_hashes_core(std::ostream & out, app * p) const { + if (p->is_ground()) { + enode * e = get_enode(*m_context, p); + SASSERT(e->has_lbl_hash()); + out << "#" << e->get_owner_id() << ":" << e->get_lbl_hash() << " "; + } + else { + out << p->get_decl()->get_name() << ":" << m_lbl_hasher(p->get_decl()) << " "; + for (unsigned i = 0; i < p->get_num_args(); i++) { + expr * arg = p->get_arg(i); + if (is_app(arg)) + display_label_hashes(out, to_app(arg)); + } + } + } + + void display_label_hashes(std::ostream & out, app * p) const { + ast_manager & m = m_context->get_manager(); + if (m.is_pattern(p)) { + for (unsigned i = 0; i < p->get_num_args(); i++) { + expr * arg = p->get_arg(i); + if (is_app(arg)) { + display_label_hashes_core(out, to_app(arg)); + out << "\n"; + } + } + } + else { + display_label_hashes_core(out, p); + out << "\n"; + } + } +#endif + + public: + code_tree(label_hasher & h, func_decl * lbl, unsigned short num_args, bool filter_candidates): + m_lbl_hasher(h), + m_root_lbl(lbl), + m_num_args(num_args), + m_filter_candidates(filter_candidates), + m_num_regs(num_args + 1), + m_num_choices(0), + m_root(0) { + DEBUG_CODE(m_context = 0;); +#ifdef _PROFILE_MAM + m_counter = 0; +#endif + } + +#ifdef _PROFILE_MAM + ~code_tree() { +#ifdef _PROFILE_MAM_TO_STDOUT + std::cout << "killing code tree for: " << m_root_lbl->get_name() << " " << static_cast(m_watch.get_seconds() * 1000) << "\n"; display(std::cout); +#endif + } + + stopwatch & get_watch() { + return m_watch; + } + + void inc_counter() { + m_counter++; + } + + unsigned get_counter() const { + return m_counter; + } +#endif + + unsigned expected_num_args() const { + return m_num_args; + } + + unsigned get_num_regs() const { + return m_num_regs; + } + + unsigned get_num_choices() const { + return m_num_choices; + } + + func_decl * get_root_lbl() const { + return m_root_lbl; + } + + bool filter_candidates() const { + return m_filter_candidates != 0; + } + + const instruction * get_root() const { + return m_root; + } + + void add_candidate(enode * n) { + m_candidates.push_back(n); + } + + bool has_candidates() const { + return !m_candidates.empty(); + } + + void reset_candidates() { + m_candidates.reset(); + } + + enode_vector const & get_candidates() const { + return m_candidates; + } + +#ifdef Z3DEBUG + void set_context(context * ctx) { + SASSERT(m_context == 0); + m_context = ctx; + } + + ptr_vector & get_patterns() { + return m_patterns; + } +#endif + + void display(std::ostream & out) const { +#ifdef Z3DEBUG + if (m_context) { + ast_manager & m = m_context->get_manager(); + out << "patterns:\n"; + ptr_vector::const_iterator it = m_patterns.begin(); + ptr_vector::const_iterator end = m_patterns.end(); + for (; it != end; ++it) + out << mk_pp(*it, m) << "\n"; + } +#endif + out << "function: " << m_root_lbl->get_name(); +#ifdef _PROFILE_MAM + out << " " << m_watch.get_seconds() << " secs, [" << m_counter << "]"; +#endif + out << "\n"; + out << "num. regs: " << m_num_regs << "\n" + << "num. choices: " << m_num_choices << "\n"; + display_seq(out, m_root, 0); + } + }; + + inline std::ostream & operator<<(std::ostream & out, code_tree const & tree) { + tree.display(out); + return out; + } + + // ------------------------------------ + // + // Code Tree Manager + // + // ------------------------------------ + + class code_tree_manager { + label_hasher & m_lbl_hasher; + mam_trail_stack & m_trail_stack; + region & m_region; + + template + OP * mk_instr(opcode op, unsigned size) { + void * mem = m_region.allocate(size); + OP * r = new (mem) OP; + r->m_opcode = op; + r->m_next = 0; +#ifdef _PROFILE_MAM + r->m_counter = 0; +#endif + return r; + } + + instruction * mk_init(unsigned n) { + SASSERT(n >= 1); + opcode op = n <= 6 ? static_cast(INIT1 + n - 1) : INITN; + return mk_instr(op, sizeof(instruction)); + } + + public: + code_tree_manager(label_hasher & h, mam_trail_stack & s): + m_lbl_hasher(h), + m_trail_stack(s), + m_region(s.get_region()) { + } + + code_tree * mk_code_tree(func_decl * lbl, unsigned short num_args, bool filter_candidates) { + code_tree * r = alloc(code_tree,m_lbl_hasher, lbl, num_args, filter_candidates); + r->m_root = mk_init(num_args); + return r; + } + + joint2 * mk_joint2(func_decl * f, unsigned pos, unsigned reg) { + return new (m_region) joint2(f, pos, reg); + } + + compare * mk_compare(unsigned reg1, unsigned reg2) { + compare * r = mk_instr(COMPARE, sizeof(compare)); + r->m_reg1 = reg1; + r->m_reg2 = reg2; + return r; + } + + check * mk_check(unsigned reg, enode * n) { + check * r = mk_instr(CHECK, sizeof(check)); + r->m_reg = reg; + r->m_enode = n; + return r; + } + + filter * mk_filter_core(opcode op, unsigned reg, approx_set s) { + filter * r = mk_instr(op, sizeof(filter)); + r->m_reg = reg; + r->m_lbl_set = s; + return r; + } + + filter * mk_filter(unsigned reg, approx_set s) { + return mk_filter_core(FILTER, reg, s); + } + + filter * mk_pfilter(unsigned reg, approx_set s) { + return mk_filter_core(PFILTER, reg, s); + } + + filter * mk_cfilter(unsigned reg, approx_set s) { + return mk_filter_core(CFILTER, reg, s); + } + + get_enode_instr * mk_get_enode(unsigned reg, enode * n) { + get_enode_instr * s = mk_instr(GET_ENODE, sizeof(get_enode_instr)); + s->m_oreg = reg; + s->m_enode = n; + return s; + } + + choose * mk_choose(choose * alt) { + choose * r = mk_instr(CHOOSE, sizeof(choose)); + r->m_alt = alt; + return r; + } + + choose * mk_noop() { + choose * r = mk_instr(NOOP, sizeof(choose)); + r->m_alt = 0; + return r; + } + + bind * mk_bind(func_decl * lbl, unsigned short num_args, unsigned ireg, unsigned oreg) { + SASSERT(num_args >= 1); + opcode op = num_args <= 6 ? static_cast(BIND1 + num_args - 1) : BINDN; + bind * r = mk_instr(op, sizeof(bind)); + r->m_label = lbl; + r->m_num_args = num_args; + r->m_ireg = ireg; + r->m_oreg = oreg; + return r; + } + + get_cgr * mk_get_cgr(func_decl * lbl, unsigned oreg, unsigned num_args, unsigned const * iregs) { + SASSERT(num_args >= 1); + opcode op = num_args <= 6 ? static_cast(GET_CGR1 + num_args - 1) : GET_CGRN; + get_cgr * r = mk_instr(op, sizeof(get_cgr) + num_args * sizeof(unsigned)); + r->m_label = lbl; + r->m_lbl_set.insert(m_lbl_hasher(lbl)); + r->m_oreg = oreg; + r->m_num_args = num_args; + memcpy(r->m_iregs, iregs, sizeof(unsigned) * num_args); + return r; + } + + is_cgr * mk_is_cgr(func_decl * lbl, unsigned ireg, unsigned num_args, unsigned const * iregs) { + SASSERT(num_args >= 1); + is_cgr * r = mk_instr(IS_CGR, sizeof(is_cgr) + num_args * sizeof(unsigned)); + r->m_label = lbl; + r->m_ireg = ireg; + r->m_num_args = num_args; + memcpy(r->m_iregs, iregs, sizeof(unsigned) * num_args); + return r; + } + + yield * mk_yield(quantifier * qa, app * pat, unsigned num_bindings, unsigned * bindings) { + SASSERT(num_bindings >= 1); + opcode op = num_bindings <= 6 ? static_cast(YIELD1 + num_bindings - 1) : YIELDN; + yield * y = mk_instr(op, sizeof(yield) + num_bindings * sizeof(unsigned)); + y->m_qa = qa; + y->m_pat = pat; + y->m_num_bindings = num_bindings; + memcpy(y->m_bindings, bindings, sizeof(unsigned) * num_bindings); + return y; + } + + cont * mk_cont(func_decl * lbl, unsigned short num_args, unsigned oreg, + approx_set const & s, enode * const * joints) { + SASSERT(num_args >= 1); + cont * r = mk_instr(CONTINUE, sizeof(cont) + num_args * sizeof(enode*)); + r->m_label = lbl; + r->m_num_args = num_args; + r->m_oreg = oreg; + r->m_lbl_set = s; + memcpy(r->m_joints, joints, num_args * sizeof(enode *)); + return r; + } + + void set_next(instruction * instr, instruction * new_next) { + m_trail_stack.push(mam_value_trail(instr->m_next)); + instr->m_next = new_next; + } + + void save_num_regs(code_tree * tree) { + m_trail_stack.push(mam_value_trail(tree->m_num_regs)); + } + + void save_num_choices(code_tree * tree) { + m_trail_stack.push(mam_value_trail(tree->m_num_choices)); + } + + void insert_new_lbl_hash(filter * instr, unsigned h) { + m_trail_stack.push(mam_value_trail(instr->m_lbl_set)); + instr->m_lbl_set.insert(h); + } + }; + + // ------------------------------------ + // + // Compiler: Pattern ---> Code Tree + // + // ------------------------------------ + + class compiler { + context & m_context; + ast_manager & m_ast_manager; + code_tree_manager & m_ct_manager; + label_hasher & m_lbl_hasher; + bool m_use_filters; + ptr_vector m_registers; + unsigned_vector m_todo; // list of registers that have patterns to be processed. + unsigned_vector m_aux; + int_vector m_vars; // -1 the variable is unbound, >= 0 is the register that contains the variable + quantifier * m_qa; + app * m_mp; + code_tree * m_tree; + unsigned m_num_choices; + bool m_is_tmp_tree; + svector m_mp_already_processed; + + struct pcheck_checked { + func_decl * m_label; + enode * m_enode; + }; + + + typedef enum { NOT_CHECKED, + CHECK_SET, + CHECK_SINGLETON } check_mark; + + svector m_mark; + unsigned_vector m_to_reset; + ptr_vector m_compatible; + ptr_vector m_incompatible; + + ptr_vector m_seq; + + void set_register(unsigned reg, expr * p) { + m_registers.setx(reg, p, 0); + } + + check_mark get_check_mark(unsigned reg) const { + return m_mark.get(reg, NOT_CHECKED); + } + + void set_check_mark(unsigned reg, check_mark m) { + m_mark.setx(reg, m, NOT_CHECKED); + } + + void init(code_tree * t, quantifier * qa, app * mp, unsigned first_idx) { + SASSERT(m_ast_manager.is_pattern(mp)); +#ifdef Z3DEBUG + svector::iterator it = m_mark.begin(); + svector::iterator end = m_mark.end(); + for (; it != end; ++it) { + SASSERT(*it == NOT_CHECKED); + } +#endif + m_tree = t; + m_qa = qa; + m_mp = mp; + m_num_choices = 0; + m_todo.reset(); + m_registers.fill(0); + + app * p = to_app(mp->get_arg(first_idx)); + SASSERT(t->get_root_lbl() == p->get_decl()); + unsigned num_args = p->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + set_register(i+1, p->get_arg(i)); + m_todo.push_back(i+1); + } + unsigned num_decls = m_qa->get_num_decls(); + if (num_decls > m_vars.size()) { + m_vars.resize(num_decls, -1); + } + for (unsigned j = 0; j < num_decls; j++) { + m_vars[j] = -1; + } + } + + /** + \brief Return true if all arguments of n are bound variables. + That is, during execution time, the variables will be already bound + */ + bool all_args_are_bound_vars(app * n) { + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = n->get_arg(i); + if (!is_var(arg)) + return false; + if (m_vars[to_var(arg)->get_idx()] == -1) + return false; + } + return true; + } + + /** + \see get_stats + */ + void get_stats_core(app * n, unsigned & sz, unsigned & num_unbound_vars) { + sz++; + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = n->get_arg(i); + if (is_var(arg)) { + sz++; + unsigned var_id = to_var(arg)->get_idx(); + if (m_vars[var_id] == -1) + num_unbound_vars++; + } + else if (is_app(arg)) { + get_stats_core(to_app(arg), sz, num_unbound_vars); + } + } + } + + /** + \brief Return statistics for the given pattern + \remark Patterns are small. So, it doesn't hurt to use a recursive function. + */ + void get_stats(app * n, unsigned & sz, unsigned & num_unbound_vars) { + sz = 0; + num_unbound_vars = 0; + return get_stats_core(n, sz, num_unbound_vars); + } + + /** + \brief Process registers in m_todo. The registers in m_todo + that produce non-BIND operations are processed first. Then, + a single BIND operation b is produced. + + After executing this method m_todo will contain the + registers in m_todo that produce BIND operations and were + not processed, and the registers generated when the + operation b was produced. + + \remark The new operations are appended to m_seq. + */ + void linearise_core() { + m_aux.reset(); + app * first_app = 0; + unsigned first_app_reg; + unsigned first_app_sz; + unsigned first_app_num_unbound_vars; + // generate first the non-BIND operations + unsigned_vector::iterator it = m_todo.begin(); + unsigned_vector::iterator end = m_todo.end(); + for (; it != end; ++it) { + unsigned reg = *it; + expr * p = m_registers[reg]; + SASSERT(!is_quantifier(p)); + if (is_var(p)) { + unsigned var_id = to_var(p)->get_idx(); + if (m_vars[var_id] != -1) + m_seq.push_back(m_ct_manager.mk_compare(m_vars[var_id], reg)); + else + m_vars[var_id] = reg; + continue; + } + + SASSERT(is_app(p)); + + if (to_app(p)->is_ground()) { + // ground applications are viewed as constants, and eagerly + // converted into enodes. + enode * e = mk_enode(m_context, m_qa, to_app(p)); + m_seq.push_back(m_ct_manager.mk_check(reg, e)); + set_check_mark(reg, NOT_CHECKED); // reset mark, register was fully processed. + continue; + } + + if (m_use_filters && get_check_mark(reg) != CHECK_SINGLETON) { + func_decl * lbl = to_app(p)->get_decl(); + approx_set s(m_lbl_hasher(lbl)); + m_seq.push_back(m_ct_manager.mk_filter(reg, s)); + set_check_mark(reg, CHECK_SINGLETON); + } + + if (first_app) { +#if 0 + m_aux.push_back(reg); +#else + // Try to select the best first_app + if (first_app_num_unbound_vars == 0) { + // first_app doesn't have free vars... so it is the best choice... + m_aux.push_back(reg); + } + else { + unsigned sz; + unsigned num_unbound_vars; + get_stats(to_app(p), sz, num_unbound_vars); + if (num_unbound_vars == 0 || + sz > first_app_sz || + (sz == first_app_sz && num_unbound_vars < first_app_num_unbound_vars)) { + // change the first_app + m_aux.push_back(first_app_reg); + first_app = to_app(p); + first_app_reg = reg; + first_app_sz = sz; + first_app_num_unbound_vars = num_unbound_vars; + } + else { + m_aux.push_back(reg); + } + } +#endif + } + else { + first_app = to_app(p); + first_app_reg = reg; + get_stats(first_app, first_app_sz, first_app_num_unbound_vars); + } + } + + if (first_app) { + // m_todo contains at least one (non-ground) application. + func_decl * lbl = first_app->get_decl(); + unsigned short num_args = first_app->get_num_args(); + if (IS_CGR_SUPPORT && all_args_are_bound_vars(first_app)) { + // use IS_CGR instead of BIND + sbuffer iregs; + for (unsigned i = 0; i < num_args; i++) { + expr * arg = to_app(first_app)->get_arg(i); + SASSERT(is_var(arg)); + SASSERT(m_vars[to_var(arg)->get_idx()] != -1); + iregs.push_back(m_vars[to_var(arg)->get_idx()]); + } + m_seq.push_back(m_ct_manager.mk_is_cgr(lbl, first_app_reg, num_args, iregs.c_ptr())); + } + else { + // Generate a BIND operation for this application. + unsigned oreg = m_tree->m_num_regs; + m_tree->m_num_regs += num_args; + for (unsigned j = 0; j < num_args; j++) { + set_register(oreg + j, first_app->get_arg(j)); + m_aux.push_back(oreg + j); + } + m_seq.push_back(m_ct_manager.mk_bind(lbl, num_args, first_app_reg, oreg)); + m_num_choices++; + } + set_check_mark(first_app_reg, NOT_CHECKED); // reset mark, register was fully processed. + } + + // make m_aux the new todo list. + m_todo.swap(m_aux); + } + + /** + \brief Return the number of already bound vars in n. + + \remark Patterns are small. So, it doesn't hurt to use a recursive function. + */ + unsigned get_num_bound_vars_core(app * n, bool & has_unbound_vars) { + unsigned r = 0; + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = n->get_arg(i); + if (is_var(arg)) { + unsigned var_id = to_var(arg)->get_idx(); + if (m_vars[var_id] != -1) + r++; + else + has_unbound_vars = true; + } + else if (is_app(arg)) { + r += get_num_bound_vars_core(to_app(arg), has_unbound_vars); + } + } + return r; + } + + unsigned get_num_bound_vars(app * n, bool & has_unbound_vars) { + has_unbound_vars = false; + return get_num_bound_vars_core(n, has_unbound_vars); + } + + /** + \brief Compile a pattern where all free variables are already bound. + Return the register where the enode congruent to f will be stored. + */ + unsigned gen_mp_filter(app * n) { + if (is_ground(n)) { + unsigned oreg = m_tree->m_num_regs; + m_tree->m_num_regs += 1; + enode * e = mk_enode(m_context, m_qa, n); + m_seq.push_back(m_ct_manager.mk_get_enode(oreg, e)); + return oreg; + } + + sbuffer iregs; + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = n->get_arg(i); + if (is_var(arg)) { + SASSERT(m_vars[to_var(arg)->get_idx()] != -1); + if (m_vars[to_var(arg)->get_idx()] == -1) + verbose_stream() << "BUG.....\n"; + iregs.push_back(m_vars[to_var(arg)->get_idx()]); + } + else { + iregs.push_back(gen_mp_filter(to_app(arg))); + } + } + unsigned oreg = m_tree->m_num_regs; + m_tree->m_num_regs += 1; + m_seq.push_back(m_ct_manager.mk_get_cgr(n->get_decl(), oreg, num_args, iregs.c_ptr())); + return oreg; + } + + /** + \brief Process the rest of a multi-pattern. That is the patterns different from first_idx + */ + void linearise_multi_pattern(unsigned first_idx) { + unsigned num_args = m_mp->get_num_args(); + // multi_pattern support + for (unsigned i = 1; i < num_args; i++) { + // select the pattern with the biggest number of bound variables + app * best = 0; + unsigned best_num_bvars = 0; + unsigned best_j = 0; + bool found_bounded_mp = false; + for (unsigned j = 0; j < m_mp->get_num_args(); j++) { + if (std::find(m_mp_already_processed.begin(), m_mp_already_processed.end(), j) != m_mp_already_processed.end()) + continue; + app * p = to_app(m_mp->get_arg(j)); + bool has_unbound_vars = false; + unsigned num_bvars = get_num_bound_vars(p, has_unbound_vars); + if (!has_unbound_vars) { + best = p; + best_j = j; + found_bounded_mp = true; + break; + } + if (best == 0 || (num_bvars > best_num_bvars)) { + best = p; + best_num_bvars = num_bvars; + best_j = j; + } + } + m_mp_already_processed.push_back(best_j); + SASSERT(best != 0); + app * p = best; + func_decl * lbl = p->get_decl(); + unsigned short num_args = p->get_num_args(); + approx_set s; + if (m_use_filters) + s.insert(m_lbl_hasher(lbl)); + + if (found_bounded_mp) { + gen_mp_filter(p); + } + else { + // USE CONTINUE + unsigned oreg = m_tree->m_num_regs; + m_tree->m_num_regs += num_args; + ptr_buffer joints; + bool has_depth1_joint = false; // VAR_TAG or GROUND_TERM_TAG + for (unsigned j = 0; j < num_args; j++) { + expr * curr = p->get_arg(j); + SASSERT(!is_quantifier(curr)); + set_register(oreg + j, curr); + m_todo.push_back(oreg + j); + + if ((is_var(curr) && m_vars[to_var(curr)->get_idx()] >= 0) + || + (is_app(curr) && (to_app(curr)->is_ground()))) + has_depth1_joint = true; + } + + if (has_depth1_joint) { + for (unsigned j = 0; j < num_args; j++) { + expr * curr = p->get_arg(j); + + if (is_var(curr)) { + unsigned var_id = to_var(curr)->get_idx(); + if (m_vars[var_id] >= 0) + joints.push_back(BOXTAGINT(enode *, m_vars[var_id], VAR_TAG)); + else + joints.push_back(NULL_TAG); + continue; + } + + SASSERT(is_app(curr)); + + if (to_app(curr)->is_ground()) { + enode * e = mk_enode(m_context, m_qa, to_app(curr)); + joints.push_back(TAG(enode *, e, GROUND_TERM_TAG)); + continue; + } + + joints.push_back(0); + } + } + else { + // Only try to use depth2 joints if there is no depth1 joint. + for (unsigned j = 0; j < num_args; j++) { + expr * curr = p->get_arg(j); + if (!is_app(curr)) { + joints.push_back(0); + continue; + } + unsigned num_args2 = to_app(curr)->get_num_args(); + unsigned k = 0; + for (; k < num_args2; k++) { + expr * arg = to_app(curr)->get_arg(k); + if (!is_var(arg)) + continue; + unsigned var_id = to_var(arg)->get_idx(); + if (m_vars[var_id] < 0) + continue; + joint2 * new_joint = m_ct_manager.mk_joint2(to_app(curr)->get_decl(), k, m_vars[var_id]); + joints.push_back(TAG(enode *, new_joint, NESTED_VAR_TAG)); + break; // found a new joint + } + if (k == num_args2) + joints.push_back(0); // didn't find joint + } + } + SASSERT(joints.size() == num_args); + m_seq.push_back(m_ct_manager.mk_cont(lbl, num_args, oreg, s, joints.c_ptr())); + m_num_choices++; + while (!m_todo.empty()) + linearise_core(); + } + } + } + + /** + \brief Produce the operations for the registers in m_todo. + */ + void linearise(instruction * head, unsigned first_idx) { + m_seq.reset(); + m_mp_already_processed.reset(); + m_mp_already_processed.push_back(first_idx); + while (!m_todo.empty()) + linearise_core(); + + if (m_mp->get_num_args() > 1) + linearise_multi_pattern(first_idx); + +#ifdef Z3DEBUG + for (unsigned i = 0; i < m_qa->get_num_decls(); i++) { + CTRACE("mam_new_bug", m_vars[i] < 0, tout << mk_ismt2_pp(m_qa, m_ast_manager) << "\ni: " << i << " m_vars[i]: " << m_vars[i] << "\n"; + tout << "m_mp:\n" << mk_ismt2_pp(m_mp, m_ast_manager) << "\n"; + tout << "tree:\n" << *m_tree << "\n"; + ); + SASSERT(m_vars[i] >= 0); + } +#endif + SASSERT(head->m_next == 0); + m_seq.push_back(m_ct_manager.mk_yield(m_qa, m_mp, m_qa->get_num_decls(), reinterpret_cast(m_vars.begin()))); + + ptr_vector::iterator it = m_seq.begin(); + ptr_vector::iterator end = m_seq.end(); + for (; it != end; ++it) { + instruction * curr = *it; + head->m_next = curr; + head = curr; + } + } + + void set_next(instruction * instr, instruction * new_next) { + if (m_is_tmp_tree) + instr->m_next = new_next; + else + m_ct_manager.set_next(instr, new_next); + } + + /* + The nodes in the bottom of the code-tree can have a lot of children in big examples. + Example: + parent-node: + (CHOOSE) (CHECK #1 10) (YIELD ...) + (CHOOSE) (CHECK #2 10) (YIELD ...) + (CHOOSE) (CHECK #3 10) (YIELD ...) + (CHOOSE) (CHECK #4 10) (YIELD ...) + (CHOOSE) (CHECK #5 10) (YIELD ...) + (CHOOSE) (CHECK #6 10) (YIELD ...) + (CHOOSE) (CHECK #7 10) (YIELD ...) + (CHOOSE) (CHECK #8 10) (YIELD ...) + ... + The method find_best_child will traverse this big list, and usually will not find + a compatible child. So, I limit the number of simple code sequences that can be + traversed. + */ +#define FIND_BEST_CHILD_THRESHOLD 64 + + choose * find_best_child(choose * first_child) { + unsigned num_too_simple = 0; + choose * best_child = 0; + unsigned max_compatibility = 0; + choose * curr_child = first_child; + while (curr_child != 0) { + bool simple = false; + unsigned curr_compatibility = get_compatibility_measure(curr_child, simple); + if (simple) { + num_too_simple++; + if (num_too_simple > FIND_BEST_CHILD_THRESHOLD) + return 0; // it is unlikely we will find a compatible node + } + if (curr_compatibility > max_compatibility) { + best_child = curr_child; + max_compatibility = curr_compatibility; + } + curr_child = curr_child->m_alt; + } + return best_child; + } + + bool is_compatible(bind * instr) const { + unsigned ireg = instr->m_ireg; + expr * n = m_registers[ireg]; + return + n != 0 && + is_app(n) && + // It is wasteful to use a bind of a ground term. + // Actually, in the rest of the code I assume that. + !is_ground(n) && + to_app(n)->get_decl() == instr->m_label && + to_app(n)->get_num_args() == instr->m_num_args; + } + + bool is_compatible(compare * instr) const { + unsigned reg1 = instr->m_reg1; + unsigned reg2 = instr->m_reg2; + return + m_registers[reg1] != 0 && + m_registers[reg2] != 0 && + is_var(m_registers[reg1]) && + is_var(m_registers[reg2]) && + m_registers[reg1] == m_registers[reg2]; + } + + bool is_compatible(check * instr) const { + unsigned reg = instr->m_reg; + enode * n = instr->m_enode; + if (m_registers[reg] == 0) + return false; + if (!is_app(m_registers[reg])) + return false; + if (!to_app(m_registers[reg])->is_ground()) + return false; + enode * n_prime = mk_enode(m_context, m_qa, to_app(m_registers[reg])); + // it is safe to compare the roots because the modifications + // on the code tree are chronological. + return n->get_root() == n_prime->get_root(); + } + + /** + \brief Get the label hash of the pattern stored at register reg. + + If the pattern is a ground application, then it is viewed as a + constant. In this case, we use the field get_lbl_hash() in the enode + associated with it. + */ + unsigned get_pat_lbl_hash(unsigned reg) const { + SASSERT(m_registers[reg] != 0); + SASSERT(is_app(m_registers[reg])); + app * p = to_app(m_registers[reg]); + if (p->is_ground()) { + enode * e = mk_enode(m_context, m_qa, p); + if (!e->has_lbl_hash()) + e->set_lbl_hash(m_context); + return e->get_lbl_hash(); + } + else { + func_decl * lbl = p->get_decl(); + return m_lbl_hasher(lbl); + } + } + + /** + \brief We say a check operation is semi compatible if + it access a register that was not yet processed, + and given reg = instr->m_reg + 1- is_ground(m_registers[reg]) + 2- get_pat_lbl_hash(reg) == m_enode->get_lbl_hash() + + If that is the case, then a CFILTER is created + */ + bool is_semi_compatible(check * instr) const { + unsigned reg = instr->m_reg; + return + m_registers[reg] != 0 && + // if the register was already checked by another filter, then it doesn't make sense + // to check it again. + get_check_mark(reg) == NOT_CHECKED && + is_ground(m_registers[reg]) && + get_pat_lbl_hash(reg) == instr->m_enode->get_lbl_hash(); + } + + /** + \brief FILTER is not compatible with ground terms anymore. + See CFILTER is the filter used for ground terms. + */ + bool is_compatible(filter * instr) const { + unsigned reg = instr->m_reg; + if (m_registers[reg] != 0 && is_app(m_registers[reg]) && !is_ground(m_registers[reg])) { + // FILTER is fully compatible if it already contains + // the label hash of the pattern stored at reg. + unsigned elem = get_pat_lbl_hash(reg); + return instr->m_lbl_set.may_contain(elem); + } + return false; + } + + bool is_cfilter_compatible(filter * instr) const { + unsigned reg = instr->m_reg; + // only ground terms are considered in CFILTERS + if (m_registers[reg] != 0 && is_ground(m_registers[reg])) { + // FILTER is fully compatible if it already contains + // the label hash of the pattern stored at reg. + unsigned elem = get_pat_lbl_hash(reg); + return instr->m_lbl_set.may_contain(elem); + } + return false; + } + + /** + \brief See comments at is_semi_compatible(check * instr) and is_compatible(filter * instr). + Remark: FILTER is not compatible with ground terms anymore + */ + bool is_semi_compatible(filter * instr) const { + unsigned reg = instr->m_reg; + return + m_registers[reg] != 0 && + get_check_mark(reg) == NOT_CHECKED && + is_app(m_registers[reg]) && + !is_ground(m_registers[reg]); + } + + bool is_compatible(cont * instr) const { + unsigned oreg = instr->m_oreg; + for (unsigned i = 0; i < instr->m_num_args; i++) + if (m_registers[oreg + i] != 0) + return false; + return true; + } + + // Threshold for a code sequence (in number of instructions) to be considered simple. +#define SIMPLE_SEQ_THRESHOLD 4 + + /** + \brief Return a "compatibility measure" that quantifies how + many operations in the branch starting at child are compatible + with the patterns in the registers stored in m_todo. + + Set simple to true, if the tree starting at child is too simple + (no branching and less than SIMPLE_SEQ_THRESHOLD instructions) + */ + unsigned get_compatibility_measure(choose * child, bool & simple) { + simple = true; + m_to_reset.reset(); + unsigned weight = 0; + unsigned num_instr = 0; + instruction * curr = child->m_next; + while (curr != 0 && curr->m_opcode != CHOOSE && curr->m_opcode != NOOP) { + num_instr++; + switch (curr->m_opcode) { + case BIND1: case BIND2: case BIND3: case BIND4: case BIND5: case BIND6: case BINDN: + if (is_compatible(static_cast(curr))) { + weight += 4; // the weight of BIND is bigger than COMPARE and CHECK + unsigned ireg = static_cast(curr)->m_ireg; + app * n = to_app(m_registers[ireg]); + unsigned oreg = static_cast(curr)->m_oreg; + unsigned num_args = static_cast(curr)->m_num_args; + SASSERT(n->get_num_args() == num_args); + for (unsigned i = 0; i < num_args; i++) { + set_register(oreg + i, n->get_arg(i)); + m_to_reset.push_back(oreg + i); + } + } + break; + case COMPARE: + if (is_compatible(static_cast(curr))) + weight += 2; + break; + case CHECK: + if (is_compatible(static_cast(curr))) + weight += 2; + else if (m_use_filters && is_semi_compatible(static_cast(curr))) + weight += 1; + break; + case CFILTER: + if (is_cfilter_compatible(static_cast(curr))) + weight += 2; + break; + case FILTER: + if (is_compatible(static_cast(curr))) + weight += 2; + else if (is_semi_compatible(static_cast(curr))) + weight += 1; + break; + // TODO: Try to reuse IS_CGR instruction + default: + break; + } + curr = curr->m_next; + } + if (num_instr > SIMPLE_SEQ_THRESHOLD || (curr != 0 && curr->m_opcode == CHOOSE)) + simple = false; + unsigned_vector::iterator it = m_to_reset.begin(); + unsigned_vector::iterator end = m_to_reset.end(); + for (; it != end; ++it) + m_registers[*it] = 0; + return weight; + } + + void insert(instruction * head, unsigned first_mp_idx) { + for (;;) { + m_compatible.reset(); + m_incompatible.reset(); + TRACE("mam_compiler_detail", tout << "processing head: " << *head << "\n";); + instruction * curr = head->m_next; + instruction * last = head; + while (curr != 0 && curr->m_opcode != CHOOSE && curr->m_opcode != NOOP) { + TRACE("mam_compiler_detail", tout << "processing instr: " << *curr << "\n";); + switch (curr->m_opcode) { + case BIND1: case BIND2: case BIND3: case BIND4: case BIND5: case BIND6: case BINDN: + if (is_compatible(static_cast(curr))) { + TRACE("mam_compiler_detail", tout << "compatible\n";); + unsigned ireg = static_cast(curr)->m_ireg; + SASSERT(m_todo.contains(ireg)); + m_todo.erase(ireg); + set_check_mark(ireg, NOT_CHECKED); + m_compatible.push_back(curr); + app * app = to_app(m_registers[ireg]); + unsigned oreg = static_cast(curr)->m_oreg; + unsigned num_args = static_cast(curr)->m_num_args; + for (unsigned i = 0; i < num_args; i++) { + set_register(oreg + i, app->get_arg(i)); + m_todo.push_back(oreg + i); + } + } + else { + TRACE("mam_compiler_detail", tout << "incompatible\n";); + m_incompatible.push_back(curr); + } + break; + case CHECK: + if (is_compatible(static_cast(curr))) { + TRACE("mam_compiler_detail", tout << "compatible\n";); + unsigned reg = static_cast(curr)->m_reg; + SASSERT(m_todo.contains(reg)); + m_todo.erase(reg); + set_check_mark(reg, NOT_CHECKED); + m_compatible.push_back(curr); + } + else if (m_use_filters && is_semi_compatible(static_cast(curr))) { + TRACE("mam_compiler_detail", tout << "semi compatible\n";); + unsigned reg = static_cast(curr)->m_reg; + enode * n1 = static_cast(curr)->m_enode; + // n1->has_lbl_hash may be false, even + // when update_filters is invoked before + // executing this method. + // + // Reason: n1 is a ground subterm of a ground term T. + // I incorrectly assumed n1->has_lbl_hash() was true because + // update_filters executes set_lbl_hash for all maximal ground terms. + // And, I also incorrectly assumed that all arguments of check were + // maximal ground terms. This is not true. For example, assume + // the code_tree already has the pattern + // (f (g x) z) + // So, when the pattern (f (g b) x) is compiled a check instruction + // is created for a ground subterm b of the maximal ground term (g b). + if (!n1->has_lbl_hash()) + n1->set_lbl_hash(m_context); + unsigned h1 = n1->get_lbl_hash(); + unsigned h2 = get_pat_lbl_hash(reg); + approx_set s(h1); + s.insert(h2); + filter * new_instr = m_ct_manager.mk_cfilter(reg, s); + set_check_mark(reg, CHECK_SET); + m_compatible.push_back(new_instr); + m_incompatible.push_back(curr); + } + else { + TRACE("mam_compiler_detail", tout << "incompatible\n";); + m_incompatible.push_back(curr); + } + break; + case COMPARE: + if (is_compatible(static_cast(curr))) { + TRACE("mam_compiler_detail", tout << "compatible\n";); + unsigned reg1 = static_cast(curr)->m_reg1; + unsigned reg2 = static_cast(curr)->m_reg2; + SASSERT(m_todo.contains(reg2)); + m_todo.erase(reg1); + m_todo.erase(reg2); + SASSERT(is_var(m_registers[reg1])); + unsigned var_id = to_var(m_registers[reg1])->get_idx(); + if (m_vars[var_id] == -1) + m_vars[var_id] = reg1; + m_compatible.push_back(curr); + } + else { + TRACE("mam_compiler_detail", tout << "incompatible\n";); + m_incompatible.push_back(curr); + } + break; + case CFILTER: + SASSERT(m_use_filters); + if (is_cfilter_compatible(static_cast(curr))) { + unsigned reg = static_cast(curr)->m_reg; + SASSERT(static_cast(curr)->m_lbl_set.size() == 1); + set_check_mark(reg, CHECK_SINGLETON); + m_compatible.push_back(curr); + } + else { + m_incompatible.push_back(curr); + } + break; + case FILTER: + SASSERT(m_use_filters); + if (is_compatible(static_cast(curr))) { + TRACE("mam_compiler_detail", tout << "compatible\n";); + unsigned reg = static_cast(curr)->m_reg; + CTRACE("mam_compiler_bug", !m_todo.contains(reg), { + for (unsigned i = 0; i < m_todo.size(); i++) { tout << m_todo[i] << " "; } + tout << "\nregisters:\n"; + for (unsigned i = 0; i < m_registers.size(); i++) { if (m_registers[i]) { tout << i << ":\n" << mk_pp(m_registers[i], m_ast_manager) << "\n"; } } + tout << "quantifier:\n" << mk_pp(m_qa, m_ast_manager) << "\n"; + tout << "pattern:\n" << mk_pp(m_mp, m_ast_manager) << "\n"; + }); + SASSERT(m_todo.contains(reg)); + if (static_cast(curr)->m_lbl_set.size() == 1) + set_check_mark(reg, CHECK_SINGLETON); + else + set_check_mark(reg, CHECK_SET); + m_compatible.push_back(curr); + } + else if (is_semi_compatible(static_cast(curr))) { + TRACE("mam_compiler_detail", tout << "semi compatible\n";); + unsigned reg = static_cast(curr)->m_reg; + CTRACE("mam_compiler_bug", !m_todo.contains(reg), { + for (unsigned i = 0; i < m_todo.size(); i++) { tout << m_todo[i] << " "; } + tout << "\nregisters:\n"; + for (unsigned i = 0; i < m_registers.size(); i++) { if (m_registers[i]) { tout << i << ":\n" << mk_pp(m_registers[i], m_ast_manager) << "\n"; } } + tout << "quantifier:\n" << mk_pp(m_qa, m_ast_manager) << "\n"; + tout << "pattern:\n" << mk_pp(m_mp, m_ast_manager) << "\n"; + }); + SASSERT(m_todo.contains(reg)); + unsigned h = get_pat_lbl_hash(reg); + TRACE("mam_lbl_bug", + tout << "curr_set: " << static_cast(curr)->m_lbl_set << "\n"; + tout << "new hash: " << h << "\n";); + set_check_mark(reg, CHECK_SET); + approx_set const & s = static_cast(curr)->m_lbl_set; + if (s.size() > 1) { + m_ct_manager.insert_new_lbl_hash(static_cast(curr), h); + m_compatible.push_back(curr); + } + else { + SASSERT(s.size() == 1); + approx_set new_s(s); + new_s.insert(h); + filter * new_instr = m_ct_manager.mk_filter(reg, new_s); + m_compatible.push_back(new_instr); + m_incompatible.push_back(curr); + } + } + else { + TRACE("mam_compiler_detail", tout << "incompatible\n";); + m_incompatible.push_back(curr); + } + break; + default: + TRACE("mam_compiler_detail", tout << "incompatible\n";); + m_incompatible.push_back(curr); + break; + } + last = curr; + curr = curr->m_next; + } + + TRACE("mam_compiler", tout << *head << " " << head << "\n"; + tout << "m_compatible.size(): " << m_compatible.size() << "\n"; + tout << "m_incompatible.size(): " << m_incompatible.size() << "\n";); + + if (m_incompatible.empty()) { + // sequence starting at head is fully compatible + SASSERT(curr != 0); + SASSERT(curr->m_opcode == CHOOSE); + choose * first_child = static_cast(curr); + choose * best_child = find_best_child(first_child); + if (best_child == 0) { + // There is no compatible child + // Suppose the sequence is: + // head -> c1 -> ... -> (cn == last) -> first_child; + // Then we should add + // head -> c1 -> ... -> (cn == last) -> new_child + // new_child: CHOOSE(first_child) -> linearise + choose * new_child = m_ct_manager.mk_choose(first_child); + m_num_choices++; + set_next(last, new_child); + linearise(new_child, first_mp_idx); + // DONE + return; + } + else { + head = best_child; + // CONTINUE from best_child + } + } + else { + SASSERT(head->is_init() || !m_compatible.empty()); + SASSERT(!m_incompatible.empty()); + // Suppose the sequence is: + // head -> c1 -> i1 -> c2 -> c3 -> i2 -> first_child_head + // where c_j are the compatible instructions, and i_j are the incompatible ones + // Then the sequence starting at head should become + // head -> c1 -> c2 -> c3 -> new_child_head1 + // new_child_head1:CHOOSE(new_child_head2) -> i1 -> i2 -> first_child_head + // new_child_head2:NOOP -> linearise() + instruction * first_child_head = curr; + choose * new_child_head2 = m_ct_manager.mk_noop(); + SASSERT(new_child_head2->m_alt == 0); + choose * new_child_head1 = m_ct_manager.mk_choose(new_child_head2); + m_num_choices++; + // set: head -> c1 -> c2 -> c3 -> new_child_head1 + curr = head; + ptr_vector::iterator it1 = m_compatible.begin(); + ptr_vector::iterator end1 = m_compatible.end(); + for (; it1 != end1; ++it1) { + set_next(curr, *it1); + curr = *it1; + } + set_next(curr, new_child_head1); + // set: new_child_head1:CHOOSE(new_child_head2) -> i1 -> i2 -> first_child_head + curr = new_child_head1; + ptr_vector::iterator it2 = m_incompatible.begin(); + ptr_vector::iterator end2 = m_incompatible.end(); + for (; it2 != end2; ++it2) { + if (curr == new_child_head1) + curr->m_next = *it2; // new_child_head1 is a new node, I don't need to save trail + else + set_next(curr, *it2); + curr = *it2; + } + set_next(curr, first_child_head); + // build new_child_head2:NOOP -> linearise() + linearise(new_child_head2, first_mp_idx); + // DONE + return; + } + } + } + + + public: + compiler(context & ctx, code_tree_manager & ct_mg, label_hasher & h, bool use_filters = true): + m_context(ctx), + m_ast_manager(ctx.get_manager()), + m_ct_manager(ct_mg), + m_lbl_hasher(h), + m_use_filters(use_filters) { + } + + context & get_context() { + return m_context; + } + + /** + \brief Create a new code tree for the given quantifier. + + - mp: is a pattern of qa that will be used to create the code tree + + - first_idx: index of mp that will be the "head" (first to be processed) of the multi-pattern. + */ + code_tree * mk_tree(quantifier * qa, app * mp, unsigned first_idx, bool filter_candidates) { + SASSERT(m_ast_manager.is_pattern(mp)); + app * p = to_app(mp->get_arg(first_idx)); + unsigned num_args = p->get_num_args(); + code_tree * r = m_ct_manager.mk_code_tree(p->get_decl(), num_args, filter_candidates); + init(r, qa, mp, first_idx); + linearise(r->m_root, first_idx); + r->m_num_choices = m_num_choices; + TRACE("mam_compiler", tout << "new tree for:\n" << mk_pp(mp, m_ast_manager) << "\n" << *r;); + return r; + } + + /** + \brief Insert a pattern into the code tree. + + - is_tmp_tree: trail for update operations is created if is_tmp_tree = false. + */ + void insert(code_tree * tree, quantifier * qa, app * mp, unsigned first_idx, bool is_tmp_tree) { + if (tree->expected_num_args() != to_app(mp->get_arg(first_idx))->get_num_args()) { + // We have to check the number of arguments because of nary + and * operators. + // The E-matching engine that was built when all + and * applications were binary. + // We ignore the pattern if it does not have the expected number of arguments. + // This is not the ideal solution, but it avoids possible crashes. + return; + } + m_is_tmp_tree = is_tmp_tree; + TRACE("mam_compiler", tout << "updating tree with:\n" << mk_pp(mp, m_ast_manager) << "\n";); + TRACE("mam_bug", tout << "before insertion\n" << *tree << "\n";); + if (!is_tmp_tree) + m_ct_manager.save_num_regs(tree); + init(tree, qa, mp, first_idx); + m_num_choices = tree->m_num_choices; + insert(tree->m_root, first_idx); + TRACE("mam_bug", + tout << "m_num_choices: " << m_num_choices << "\n";); + if (m_num_choices > tree->m_num_choices) { + if (!is_tmp_tree) + m_ct_manager.save_num_choices(tree); + tree->m_num_choices = m_num_choices; + } + TRACE("mam_bug", + tout << "m_num_choices: " << m_num_choices << "\n"; + tout << "new tree:\n" << *tree;); + } + }; + +#ifdef Z3DEBUG + bool check_lbls(enode * n) { + approx_set lbls; + approx_set plbls; + enode * first = n; + do { + lbls |= n->get_lbls(); + plbls |= n->get_plbls(); + n = n->get_next(); + } + while (first != n); + SASSERT(n->get_root()->get_lbls() == lbls); + SASSERT(n->get_root()->get_plbls() == plbls); + return true; + } +#endif + + // ------------------------------------ + // + // Code Tree Interpreter + // + // ------------------------------------ + + struct backtrack_point { + const instruction * m_instr; + unsigned m_old_max_generation; + unsigned m_old_used_enodes_size; + union { + enode * m_curr; + struct { + enode_vector * m_to_recycle; + enode * const * m_it; + enode * const * m_end; + }; + }; + }; + + typedef svector backtrack_stack; + + class interpreter { + context & m_context; + ast_manager & m_ast_manager; + mam & m_mam; + bool m_use_filters; + enode_vector m_registers; + enode_vector m_bindings; + enode_vector m_args; + backtrack_stack m_backtrack_stack; + unsigned m_top; + const instruction * m_pc; + std::ostream* m_trace_stream; + + // auxiliary temporary variables + unsigned m_max_generation; // the maximum generation of an app enode processed. + unsigned m_curr_max_generation; // temporary var used to store a copy of m_max_generation + unsigned m_num_args; + unsigned m_oreg; + unsigned m_ireg; + enode * m_n1; + enode * m_n2; + enode * m_app; + instruction * m_alt; + const bind * m_b; + ptr_vector m_used_enodes; + unsigned m_curr_used_enodes_size; + ptr_vector m_pattern_instances; // collect the pattern instances... used for computing min_top_generation and max_top_generation + + pool m_pool; + + enode_vector * mk_enode_vector() { + enode_vector * r = m_pool.mk(); + r->reset(); + return r; + } + + void recycle_enode_vector(enode_vector * v) { + m_pool.recycle(v); + } + + void update_max_generation(enode * n) { + m_max_generation = std::max(m_max_generation, n->get_generation()); +#ifndef SMTCOMP + if (m_trace_stream != NULL) + m_used_enodes.push_back(n); +#endif + } + + // We have to provide the number of expected arguments because we have flat-assoc applications such as +. + // Flat-assoc applications may have arbitrary number of arguments. + enode * get_first_f_app(func_decl * lbl, unsigned num_expected_args, enode * curr) { + enode * first = curr; + do { + if (curr->get_decl() == lbl && curr->is_cgr() && curr->get_num_args() == num_expected_args) { + update_max_generation(curr); + return curr; + } + curr = curr->get_next(); + } + while (curr != first); + return 0; + } + + enode * get_next_f_app(func_decl * lbl, unsigned num_expected_args, enode * first, enode * curr) { + curr = curr->get_next(); + while (curr != first) { + if (curr->get_decl() == lbl && curr->is_cgr() && curr->get_num_args() == num_expected_args) { + update_max_generation(curr); + return curr; + } + curr = curr->get_next(); + } + return 0; + } + + /** + \brief Execute the is_cgr instruction. + Return true if succeeded, and false if backtracking is needed. + */ + bool exec_is_cgr(is_cgr const * pc) { + unsigned num_args = pc->m_num_args; + enode * n = m_registers[pc->m_ireg]; + func_decl * f = pc->m_label; + enode * first = n; + switch (num_args) { + case 1: + m_args[0] = m_registers[pc->m_iregs[0]]->get_root(); + SASSERT(n != 0); + do { + if (n->get_decl() == f && + n->get_arg(0)->get_root() == m_args[0]) { + update_max_generation(n); + return true; + } + n = n->get_next(); + } + while (n != first); + return false; + case 2: + m_args[0] = m_registers[pc->m_iregs[0]]->get_root(); + m_args[1] = m_registers[pc->m_iregs[1]]->get_root(); + SASSERT(n != 0); + do { + if (n->get_decl() == f && + n->get_arg(0)->get_root() == m_args[0] && + n->get_arg(1)->get_root() == m_args[1]) { + update_max_generation(n); + return true; + } + n = n->get_next(); + } + while (n != first); + return false; + default: { + m_args.reserve(num_args+1, 0); + for (unsigned i = 0; i < num_args; i++) + m_args[i] = m_registers[pc->m_iregs[i]]->get_root(); + SASSERT(n != 0); + do { + if (n->get_decl() == f) { + unsigned i = 0; + for (; i < num_args; i++) { + if (n->get_arg(i)->get_root() != m_args[i]) + break; + } + if (i == num_args) { + update_max_generation(n); + return true; + } + } + n = n->get_next(); + } + while (n != first); + return false; + } } + } + + enode_vector * mk_depth1_vector(enode * n, func_decl * f, unsigned i); + + enode_vector * mk_depth2_vector(joint2 * j2, func_decl * f, unsigned i); + + enode * init_continue(cont const * c, unsigned expected_num_args); + + void display_reg(std::ostream & out, unsigned reg); + + void display_instr_input_reg(std::ostream & out, instruction const * instr); + + void display_pc_info(std::ostream & out); + +#define INIT_ARGS_SIZE 16 + + public: + interpreter(context & ctx, mam & m, bool use_filters, std::ostream *trace_stream): + m_context(ctx), + m_ast_manager(ctx.get_manager()), + m_mam(m), + m_use_filters(use_filters), + m_trace_stream(trace_stream) { + m_args.resize(INIT_ARGS_SIZE, 0); + } + + ~interpreter() { + } + + void init(code_tree * t) { + TRACE("mam_bug", tout << "preparing to match tree:\n" << *t << "\n";); + m_registers.reserve(t->get_num_regs(), 0); + m_bindings.reserve(t->get_num_regs(), 0); + if (m_backtrack_stack.size() < t->get_num_choices()) + m_backtrack_stack.resize(t->get_num_choices()); + } + + void execute(code_tree * t) { + TRACE("trigger_bug", tout << "execute for code tree:\n"; t->display(tout);); + init(t); + enode_vector::const_iterator it = t->get_candidates().begin(); + enode_vector::const_iterator end = t->get_candidates().end(); + if (t->filter_candidates()) { + for (; it != end; ++it) { + enode * app = *it; + if (!app->is_marked() && app->is_cgr()) { + execute_core(t, app); + app->set_mark(); + } + } + it = t->get_candidates().begin(); + for (; it != end; ++it) { + enode * app = *it; + if (app->is_marked()) + app->unset_mark(); + } + } + else { + for (; it != end; ++it) { + enode * app = *it; + TRACE("trigger_bug", tout << "candidate\n" << mk_ismt2_pp(app->get_owner(), m_ast_manager) << "\n";); + if (app->is_cgr()) { + TRACE("trigger_bug", tout << "is_cgr\n";); + execute_core(t, app); + } + } + } + } + + // init(t) must be invoked before execute_core + void execute_core(code_tree * t, enode * n); + + // Return the min generation of the enodes in m_pattern_instances. + unsigned get_min_top_generation() const { + SASSERT(!m_pattern_instances.empty()); + unsigned min = m_pattern_instances[0]->get_generation(); + for (unsigned i = 1; i < m_pattern_instances.size(); i++) { + unsigned curr = m_pattern_instances[i]->get_generation(); + if (min > curr) + min = curr; + } + return min; + } + + // Return the max generation of the enodes in m_pattern_instances. + unsigned get_max_top_generation() const { + SASSERT(!m_pattern_instances.empty()); + unsigned max = m_pattern_instances[0]->get_generation(); + for (unsigned i = 1; i < m_pattern_instances.size(); i++) { + unsigned curr = m_pattern_instances[i]->get_generation(); + if (max < curr) + max = curr; + } + return max; + } + }; + + /** + \brief Return a vector with the relevant f-parents of n such that n is the i-th argument. + */ + enode_vector * interpreter::mk_depth1_vector(enode * n, func_decl * f, unsigned i) { + enode_vector * v = mk_enode_vector(); + n = n->get_root(); + enode_vector::const_iterator it = n->begin_parents(); + enode_vector::const_iterator end = n->end_parents(); + for (; it != end; ++it) { + enode * p = *it; + if (p->get_decl() == f && + m_context.is_relevant(p) && + p->is_cgr() && + p->get_arg(i)->get_root() == n) { + v->push_back(p); + } + } + return v; + } + + /** + \brief Return a vector with the relevant f-parents of each p in joint2 where n is the i-th argument. + We say a p is in joint2 if p is the g-parent of m_registers[j2->m_reg] where g is j2->m_decl, + and m_registers[j2->m_reg] is the argument j2->m_arg_pos. + */ + enode_vector * interpreter::mk_depth2_vector(joint2 * j2, func_decl * f, unsigned i) { + enode * n = m_registers[j2->m_reg]->get_root(); + if (n->get_num_parents() == 0) + return 0; + enode_vector * v = mk_enode_vector(); + enode_vector::const_iterator it1 = n->begin_parents(); + enode_vector::const_iterator end1 = n->end_parents(); + for (; it1 != end1; ++it1) { + enode * p = *it1; + if (p->get_decl() == j2->m_decl && + m_context.is_relevant(p) && + p->is_cgr() && + p->get_arg(j2->m_arg_pos)->get_root() == n) { + // p is in joint2 + p = p->get_root(); + enode_vector::const_iterator it2 = p->begin_parents(); + enode_vector::const_iterator end2 = p->end_parents(); + for (; it2 != end2; ++it2) { + enode * p2 = *it2; + if (p2->get_decl() == f && + m_context.is_relevant(p2) && + p2->is_cgr() && + p2->get_arg(i)->get_root() == p) { + v->push_back(p2); + } + } + } + } + return v; + } + + enode * interpreter::init_continue(cont const * c, unsigned expected_num_args) { + func_decl * lbl = c->m_label; + unsigned min_sz = m_context.get_num_enodes_of(lbl); + unsigned short num_args = c->m_num_args; + enode * r; + // quick filter... check if any of the joint points have zero parents... + for (unsigned i = 0; i < num_args; i++) { + void * bare = c->m_joints[i]; + enode * n = 0; + switch (GET_TAG(bare)) { + case NULL_TAG: + goto non_depth1; + case GROUND_TERM_TAG: + n = UNTAG(enode *, bare); + break; + case VAR_TAG: + n = m_registers[UNBOXINT(bare)]; + break; + case NESTED_VAR_TAG: + goto non_depth1; + } + r = n->get_root(); + if (m_use_filters && r->get_plbls().empty_intersection(c->m_lbl_set)) + return 0; + if (r->get_num_parents() == 0) + return 0; + non_depth1: + ; + } + // traverse each joint and select the best one. + enode_vector * best_v = 0; + for (unsigned i = 0; i < num_args; i++) { + enode * bare = c->m_joints[i]; + enode_vector * curr_v; + switch (GET_TAG(bare)) { + case NULL_TAG: + curr_v = 0; + break; + case GROUND_TERM_TAG: + curr_v = mk_depth1_vector(UNTAG(enode *, bare), lbl, i); + break; + case VAR_TAG: + curr_v = mk_depth1_vector(m_registers[UNBOXINT(bare)], lbl, i); + break; + case NESTED_VAR_TAG: + curr_v = mk_depth2_vector(UNTAG(joint2 *, bare), lbl, i); + break; + } + if (curr_v != 0) { + if (curr_v->size() < min_sz && (best_v == 0 || curr_v->size() < best_v->size())) { + if (best_v) + recycle_enode_vector(best_v); + best_v = curr_v; + if (best_v->empty()) { + recycle_enode_vector(best_v); + return 0; + } + } + else { + recycle_enode_vector(curr_v); + } + } + } + backtrack_point & bp = m_backtrack_stack[m_top]; + bp.m_instr = c; + bp.m_old_max_generation = m_max_generation; + bp.m_old_used_enodes_size = m_used_enodes.size(); + if (best_v == 0) { + TRACE("mam_bug", tout << "m_top: " << m_top << ", m_backtrack_stack.size(): " << m_backtrack_stack.size() << "\n"; + tout << *c << "\n";); + bp.m_to_recycle = 0; + bp.m_it = m_context.begin_enodes_of(lbl); + bp.m_end = m_context.end_enodes_of(lbl); + } + else { + SASSERT(!best_v->empty()); + bp.m_to_recycle = best_v; + bp.m_it = best_v->begin(); + bp.m_end = best_v->end(); + } + // find application with the right number of arguments + for (; bp.m_it != bp.m_end; ++bp.m_it) { + enode * curr = *bp.m_it; + if (curr->get_num_args() == expected_num_args && m_context.is_relevant(curr)) + break; + } + if (bp.m_it == bp.m_end) + return 0; + m_top++; + update_max_generation(*(bp.m_it)); + return *(bp.m_it); + } + + void interpreter::display_reg(std::ostream & out, unsigned reg) { + out << "reg[" << reg << "]: "; + enode * n = m_registers[reg]; + if (!n) { + out << "nil\n"; + } + else { + out << "#" << n->get_owner_id() << ", root: " << n->get_root()->get_owner_id(); + if (m_use_filters) + out << ", lbls: " << n->get_root()->get_lbls() << " "; + out << "\n"; + out << mk_pp(n->get_owner(), m_ast_manager) << "\n"; + } + } + + void interpreter::display_instr_input_reg(std::ostream & out, const instruction * instr) { + switch (instr->m_opcode) { + case INIT1: case INIT2: case INIT3: case INIT4: case INIT5: case INIT6: case INITN: + display_reg(out, 0); + break; + case BIND1: case BIND2: case BIND3: case BIND4: case BIND5: case BIND6: case BINDN: + display_reg(out, static_cast(instr)->m_ireg); + break; + case COMPARE: + display_reg(out, static_cast(instr)->m_reg1); + display_reg(out, static_cast(instr)->m_reg2); + break; + case CHECK: + display_reg(out, static_cast(instr)->m_reg); + break; + case FILTER: + display_reg(out, static_cast(instr)->m_reg); + break; + case YIELD1: case YIELD2: case YIELD3: case YIELD4: case YIELD5: case YIELD6: case YIELDN: + for (unsigned i = 0; i < static_cast(instr)->m_num_bindings; i++) { + display_reg(out, static_cast(instr)->m_bindings[i]); + } + break; + default: + break; + } + } + + void interpreter::display_pc_info(std::ostream & out) { + out << "executing: " << *m_pc << "\n"; + out << "m_pc: " << m_pc << ", next: " << m_pc->m_next; + if (m_pc->m_opcode == CHOOSE) + out << ", alt: " << static_cast(m_pc)->m_alt; + out << "\n"; + display_instr_input_reg(out, m_pc); + } + + void interpreter::execute_core(code_tree * t, enode * n) { + TRACE("trigger_bug", tout << "interpreter::execute_core\n"; t->display(tout); tout << "\nenode\n" << mk_ismt2_pp(n->get_owner(), m_ast_manager) << "\n";); + unsigned since_last_check = 0; + +#ifdef _PROFILE_MAM +#ifdef _PROFILE_MAM_EXPENSIVE + if (t->get_watch().get_seconds() > _PROFILE_MAM_THRESHOLD && t->get_counter() % _PROFILE_MAM_EXPENSIVE_FREQ == 0) { + std::cout << "EXPENSIVE\n"; + t->display(std::cout); + } +#endif + t->get_watch().start(); + t->inc_counter(); +#endif + // It doesn't make sense to process an irrelevant enode. + TRACE("mam_execute_core", tout << "EXEC " << t->get_root_lbl()->get_name() << "\n";); + SASSERT(m_context.is_relevant(n)); + m_pattern_instances.reset(); + m_pattern_instances.push_back(n); + m_max_generation = n->get_generation(); +#ifndef SMTCOMP + if (m_trace_stream != NULL) { + m_used_enodes.reset(); + m_used_enodes.push_back(n); + } +#endif + m_pc = t->get_root(); + m_registers[0] = n; + m_top = 0; + + main_loop: + TRACE("mam_int", display_pc_info(tout);); +#ifdef _PROFILE_MAM + const_cast(m_pc)->m_counter++; +#endif + switch (m_pc->m_opcode) { + case INIT1: + m_app = m_registers[0]; + if (m_app->get_num_args() != 1) + goto backtrack; + m_registers[1] = m_app->get_arg(0); + m_pc = m_pc->m_next; + goto main_loop; + + case INIT2: + m_app = m_registers[0]; + if (m_app->get_num_args() != 2) + goto backtrack; + m_registers[1] = m_app->get_arg(0); + m_registers[2] = m_app->get_arg(1); + m_pc = m_pc->m_next; + goto main_loop; + + case INIT3: + m_app = m_registers[0]; + if (m_app->get_num_args() != 3) + goto backtrack; + m_registers[1] = m_app->get_arg(0); + m_registers[2] = m_app->get_arg(1); + m_registers[3] = m_app->get_arg(2); + m_pc = m_pc->m_next; + goto main_loop; + + case INIT4: + m_app = m_registers[0]; + if (m_app->get_num_args() != 4) + goto backtrack; + m_registers[1] = m_app->get_arg(0); + m_registers[2] = m_app->get_arg(1); + m_registers[3] = m_app->get_arg(2); + m_registers[4] = m_app->get_arg(3); + m_pc = m_pc->m_next; + goto main_loop; + + case INIT5: + m_app = m_registers[0]; + if (m_app->get_num_args() != 5) + goto backtrack; + m_registers[1] = m_app->get_arg(0); + m_registers[2] = m_app->get_arg(1); + m_registers[3] = m_app->get_arg(2); + m_registers[4] = m_app->get_arg(3); + m_registers[5] = m_app->get_arg(4); + m_pc = m_pc->m_next; + goto main_loop; + + case INIT6: + m_app = m_registers[0]; + if (m_app->get_num_args() != 6) + goto backtrack; + m_registers[1] = m_app->get_arg(0); + m_registers[2] = m_app->get_arg(1); + m_registers[3] = m_app->get_arg(2); + m_registers[4] = m_app->get_arg(3); + m_registers[5] = m_app->get_arg(4); + m_registers[6] = m_app->get_arg(5); + m_pc = m_pc->m_next; + goto main_loop; + + case INITN: + m_app = m_registers[0]; + m_num_args = m_app->get_num_args(); + for (unsigned i = 0; i < m_num_args; i++) + m_registers[i+1] = m_app->get_arg(i); + m_pc = m_pc->m_next; + goto main_loop; + + case COMPARE: + m_n1 = m_registers[static_cast(m_pc)->m_reg1]; + m_n2 = m_registers[static_cast(m_pc)->m_reg2]; + SASSERT(m_n1 != 0); + SASSERT(m_n2 != 0); + if (m_n1->get_root() != m_n2->get_root()) + goto backtrack; + m_pc = m_pc->m_next; + goto main_loop; + + case CHECK: + m_n1 = m_registers[static_cast(m_pc)->m_reg]; + m_n2 = static_cast(m_pc)->m_enode; + SASSERT(m_n1 != 0); + SASSERT(m_n2 != 0); + if (m_n1->get_root() != m_n2->get_root()) + goto backtrack; + m_pc = m_pc->m_next; + goto main_loop; + + /* CFILTER AND FILTER are handled differently by the compiler + The compiler will never merge two CFILTERs with different m_lbl_set fields. + Essentially, CFILTER is used to combine CHECK statements, and FILTER for BIND + */ + case CFILTER: + case FILTER: + m_n1 = m_registers[static_cast(m_pc)->m_reg]->get_root(); + if (static_cast(m_pc)->m_lbl_set.empty_intersection(m_n1->get_lbls())) + goto backtrack; + m_pc = m_pc->m_next; + goto main_loop; + + case PFILTER: + m_n1 = m_registers[static_cast(m_pc)->m_reg]->get_root(); + if (static_cast(m_pc)->m_lbl_set.empty_intersection(m_n1->get_plbls())) + goto backtrack; + m_pc = m_pc->m_next; + goto main_loop; + + case CHOOSE: + m_backtrack_stack[m_top].m_instr = m_pc; + m_backtrack_stack[m_top].m_old_max_generation = m_max_generation; + m_backtrack_stack[m_top].m_old_used_enodes_size = m_used_enodes.size(); + m_top++; + m_pc = m_pc->m_next; + goto main_loop; + case NOOP: + SASSERT(static_cast(m_pc)->m_alt == 0); + m_pc = m_pc->m_next; + goto main_loop; + + case BIND1: +#define BIND_COMMON() \ + m_n1 = m_registers[static_cast(m_pc)->m_ireg]; \ + SASSERT(m_n1 != 0); \ + m_oreg = static_cast(m_pc)->m_oreg; \ + m_curr_max_generation = m_max_generation; \ + m_curr_used_enodes_size = m_used_enodes.size(); \ + m_app = get_first_f_app(static_cast(m_pc)->m_label, static_cast(m_pc)->m_num_args, m_n1); \ + if (!m_app) \ + goto backtrack; \ + TRACE("mam_int", tout << "bind candidate: " << mk_pp(m_app->get_owner(), m_ast_manager) << "\n";); \ + m_backtrack_stack[m_top].m_instr = m_pc; \ + m_backtrack_stack[m_top].m_old_max_generation = m_curr_max_generation; \ + m_backtrack_stack[m_top].m_old_used_enodes_size = m_curr_used_enodes_size; \ + m_backtrack_stack[m_top].m_curr = m_app; \ + m_top++; + + BIND_COMMON(); + m_registers[m_oreg] = m_app->get_arg(0); + m_pc = m_pc->m_next; + goto main_loop; + + case BIND2: + BIND_COMMON(); + m_registers[m_oreg] = m_app->get_arg(0); + m_registers[m_oreg+1] = m_app->get_arg(1); + m_pc = m_pc->m_next; + goto main_loop; + + case BIND3: + BIND_COMMON(); + m_registers[m_oreg] = m_app->get_arg(0); + m_registers[m_oreg+1] = m_app->get_arg(1); + m_registers[m_oreg+2] = m_app->get_arg(2); + m_pc = m_pc->m_next; + goto main_loop; + + case BIND4: + BIND_COMMON(); + m_registers[m_oreg] = m_app->get_arg(0); + m_registers[m_oreg+1] = m_app->get_arg(1); + m_registers[m_oreg+2] = m_app->get_arg(2); + m_registers[m_oreg+3] = m_app->get_arg(3); + m_pc = m_pc->m_next; + goto main_loop; + + case BIND5: + BIND_COMMON(); + m_registers[m_oreg] = m_app->get_arg(0); + m_registers[m_oreg+1] = m_app->get_arg(1); + m_registers[m_oreg+2] = m_app->get_arg(2); + m_registers[m_oreg+3] = m_app->get_arg(3); + m_registers[m_oreg+4] = m_app->get_arg(4); + m_pc = m_pc->m_next; + goto main_loop; + + case BIND6: + BIND_COMMON(); + m_registers[m_oreg] = m_app->get_arg(0); + m_registers[m_oreg+1] = m_app->get_arg(1); + m_registers[m_oreg+2] = m_app->get_arg(2); + m_registers[m_oreg+3] = m_app->get_arg(3); + m_registers[m_oreg+4] = m_app->get_arg(4); + m_registers[m_oreg+5] = m_app->get_arg(5); + m_pc = m_pc->m_next; + goto main_loop; + + case BINDN: + BIND_COMMON(); + m_num_args = static_cast(m_pc)->m_num_args; + for (unsigned i = 0; i < m_num_args; i++) + m_registers[m_oreg+i] = m_app->get_arg(i); + m_pc = m_pc->m_next; + goto main_loop; + + case YIELD1: + m_bindings[0] = m_registers[static_cast(m_pc)->m_bindings[0]]; +#define ON_MATCH(NUM) \ + m_max_generation = std::max(m_max_generation, get_max_generation(NUM, m_bindings.begin())); \ + m_mam.on_match(static_cast(m_pc)->m_qa, \ + static_cast(m_pc)->m_pat, \ + NUM, \ + m_bindings.begin(), \ + m_max_generation, m_used_enodes) + ON_MATCH(1); + goto backtrack; + + case YIELD2: + m_bindings[0] = m_registers[static_cast(m_pc)->m_bindings[1]]; + m_bindings[1] = m_registers[static_cast(m_pc)->m_bindings[0]]; + ON_MATCH(2); + goto backtrack; + + case YIELD3: + m_bindings[0] = m_registers[static_cast(m_pc)->m_bindings[2]]; + m_bindings[1] = m_registers[static_cast(m_pc)->m_bindings[1]]; + m_bindings[2] = m_registers[static_cast(m_pc)->m_bindings[0]]; + ON_MATCH(3); + goto backtrack; + + case YIELD4: + m_bindings[0] = m_registers[static_cast(m_pc)->m_bindings[3]]; + m_bindings[1] = m_registers[static_cast(m_pc)->m_bindings[2]]; + m_bindings[2] = m_registers[static_cast(m_pc)->m_bindings[1]]; + m_bindings[3] = m_registers[static_cast(m_pc)->m_bindings[0]]; + ON_MATCH(4); + goto backtrack; + + case YIELD5: + m_bindings[0] = m_registers[static_cast(m_pc)->m_bindings[4]]; + m_bindings[1] = m_registers[static_cast(m_pc)->m_bindings[3]]; + m_bindings[2] = m_registers[static_cast(m_pc)->m_bindings[2]]; + m_bindings[3] = m_registers[static_cast(m_pc)->m_bindings[1]]; + m_bindings[4] = m_registers[static_cast(m_pc)->m_bindings[0]]; + ON_MATCH(5); + goto backtrack; + + case YIELD6: + m_bindings[0] = m_registers[static_cast(m_pc)->m_bindings[5]]; + m_bindings[1] = m_registers[static_cast(m_pc)->m_bindings[4]]; + m_bindings[2] = m_registers[static_cast(m_pc)->m_bindings[3]]; + m_bindings[3] = m_registers[static_cast(m_pc)->m_bindings[2]]; + m_bindings[4] = m_registers[static_cast(m_pc)->m_bindings[1]]; + m_bindings[5] = m_registers[static_cast(m_pc)->m_bindings[0]]; + ON_MATCH(6); + goto backtrack; + + case YIELDN: + m_num_args = static_cast(m_pc)->m_num_bindings; + for (unsigned i = 0; i < m_num_args; i++) + m_bindings[i] = m_registers[static_cast(m_pc)->m_bindings[m_num_args - i - 1]]; + ON_MATCH(m_num_args); + goto backtrack; + + case GET_ENODE: + m_registers[static_cast(m_pc)->m_oreg] = static_cast(m_pc)->m_enode; + m_pc = m_pc->m_next; + goto main_loop; + + case GET_CGR1: +#define GET_CGR_COMMON() \ + m_n1 = m_context.get_enode_eq_to(static_cast(m_pc)->m_label, static_cast(m_pc)->m_num_args, m_args.c_ptr()); \ + if (m_n1 == 0 || !m_context.is_relevant(m_n1)) \ + goto backtrack; \ + m_registers[static_cast(m_pc)->m_oreg] = m_n1; \ + m_pc = m_pc->m_next; \ + goto main_loop; + +#define SET_VAR(IDX) \ + m_args[IDX] = m_registers[static_cast(m_pc)->m_iregs[IDX]]; \ + if (m_use_filters && static_cast(m_pc)->m_lbl_set.empty_intersection(m_args[IDX]->get_root()->get_plbls())) { \ + TRACE("trigger_bug", tout << "m_args[IDX]->get_root():\n" << mk_ismt2_pp(m_args[IDX]->get_root()->get_owner(), m_ast_manager) << "\n"; \ + tout << "cgr set: "; static_cast(m_pc)->m_lbl_set.display(tout); tout << "\n"; \ + tout << "node set: "; m_args[IDX]->get_root()->get_plbls().display(tout); tout << "\n";); \ + goto backtrack; \ + } + + SET_VAR(0); + GET_CGR_COMMON(); + + case GET_CGR2: + SET_VAR(0); + SET_VAR(1); + GET_CGR_COMMON(); + + case GET_CGR3: + SET_VAR(0); + SET_VAR(1); + SET_VAR(2); + GET_CGR_COMMON(); + + case GET_CGR4: + SET_VAR(0); + SET_VAR(1); + SET_VAR(2); + SET_VAR(3); + GET_CGR_COMMON(); + + case GET_CGR5: + SET_VAR(0); + SET_VAR(1); + SET_VAR(2); + SET_VAR(3); + SET_VAR(4); + GET_CGR_COMMON(); + + case GET_CGR6: + SET_VAR(0); + SET_VAR(1); + SET_VAR(2); + SET_VAR(3); + SET_VAR(4); + SET_VAR(5); + GET_CGR_COMMON(); + + case GET_CGRN: + m_num_args = static_cast(m_pc)->m_num_args; + m_args.reserve(m_num_args, 0); + for (unsigned i = 0; i < m_num_args; i++) + m_args[i] = m_registers[static_cast(m_pc)->m_iregs[i]]; + GET_CGR_COMMON(); + + case IS_CGR: + if (!exec_is_cgr(static_cast(m_pc))) + goto backtrack; + m_pc = m_pc->m_next; + goto main_loop; + + case CONTINUE: + m_num_args = static_cast(m_pc)->m_num_args; + m_oreg = static_cast(m_pc)->m_oreg; + m_app = init_continue(static_cast(m_pc), m_num_args); + if (m_app == 0) + goto backtrack; + m_pattern_instances.push_back(m_app); + TRACE("mam_int", tout << "continue candidate:\n" << mk_ll_pp(m_app->get_owner(), m_ast_manager);); + for (unsigned i = 0; i < m_num_args; i++) + m_registers[m_oreg+i] = m_app->get_arg(i); + m_pc = m_pc->m_next; + goto main_loop; + + } + + backtrack: + TRACE("mam_int", tout << "backtracking.\n";); + if (m_top == 0) { + TRACE("mam_int", tout << "no more alternatives.\n";); +#ifdef _PROFILE_MAM + t->get_watch().stop(); +#endif + return; // no more alternatives + } + backtrack_point & bp = m_backtrack_stack[m_top - 1]; + m_max_generation = bp.m_old_max_generation; +#ifndef SMTCOMP + if (m_trace_stream != NULL) + m_used_enodes.shrink(bp.m_old_used_enodes_size); +#endif + TRACE("mam_int", tout << "backtrack top: " << bp.m_instr << " " << *(bp.m_instr) << "\n";); +#ifdef _PROFILE_MAM + if (bp.m_instr->m_opcode != CHOOSE) // CHOOSE has a different status. It is a control flow backtracking. + const_cast(bp.m_instr)->m_counter++; +#endif + + if (since_last_check++ > 100) { + since_last_check = 0; + if (m_context.resource_limits_exceeded()) { + // Soft timeout... + // Cleanup before exiting + while (m_top != 0) { + backtrack_point & bp = m_backtrack_stack[m_top - 1]; + if (bp.m_instr->m_opcode == CONTINUE && bp.m_to_recycle) + recycle_enode_vector(bp.m_to_recycle); + m_top--; + } +#ifdef _PROFILE_MAM + t->get_watch().stop(); +#endif + return; + } + } + + switch (bp.m_instr->m_opcode) { + case CHOOSE: + m_pc = static_cast(bp.m_instr)->m_alt; + TRACE("mam_int", tout << "alt: " << m_pc << "\n";); + SASSERT(m_pc != 0); + m_top--; + goto main_loop; + case BIND1: +#define BBIND_COMMON() m_b = static_cast(bp.m_instr); \ + m_n1 = m_registers[m_b->m_ireg]; \ + m_app = get_next_f_app(m_b->m_label, m_b->m_num_args, m_n1, bp.m_curr); \ + if (m_app == 0) { \ + m_top--; \ + goto backtrack; \ + } \ + bp.m_curr = m_app; \ + TRACE("mam_int", tout << "bind next candidate:\n" << mk_ll_pp(m_app->get_owner(), m_ast_manager);); \ + m_oreg = m_b->m_oreg + + BBIND_COMMON(); + m_registers[m_oreg] = m_app->get_arg(0); + m_pc = m_b->m_next; + goto main_loop; + + case BIND2: + BBIND_COMMON(); + m_registers[m_oreg] = m_app->get_arg(0); + m_registers[m_oreg+1] = m_app->get_arg(1); + m_pc = m_b->m_next; + goto main_loop; + + case BIND3: + BBIND_COMMON(); + m_registers[m_oreg] = m_app->get_arg(0); + m_registers[m_oreg+1] = m_app->get_arg(1); + m_registers[m_oreg+2] = m_app->get_arg(2); + m_pc = m_b->m_next; + goto main_loop; + + case BIND4: + BBIND_COMMON(); + m_registers[m_oreg] = m_app->get_arg(0); + m_registers[m_oreg+1] = m_app->get_arg(1); + m_registers[m_oreg+2] = m_app->get_arg(2); + m_registers[m_oreg+3] = m_app->get_arg(3); + m_pc = m_b->m_next; + goto main_loop; + + case BIND5: + BBIND_COMMON(); + m_registers[m_oreg] = m_app->get_arg(0); + m_registers[m_oreg+1] = m_app->get_arg(1); + m_registers[m_oreg+2] = m_app->get_arg(2); + m_registers[m_oreg+3] = m_app->get_arg(3); + m_registers[m_oreg+4] = m_app->get_arg(4); + m_pc = m_b->m_next; + goto main_loop; + + case BIND6: + BBIND_COMMON(); + m_registers[m_oreg] = m_app->get_arg(0); + m_registers[m_oreg+1] = m_app->get_arg(1); + m_registers[m_oreg+2] = m_app->get_arg(2); + m_registers[m_oreg+3] = m_app->get_arg(3); + m_registers[m_oreg+4] = m_app->get_arg(4); + m_registers[m_oreg+5] = m_app->get_arg(5); + m_pc = m_b->m_next; + goto main_loop; + + case BINDN: + BBIND_COMMON(); + m_num_args = m_b->m_num_args; + for (unsigned i = 0; i < m_num_args; i++) + m_registers[m_oreg+i] = m_app->get_arg(i); + m_pc = m_b->m_next; + goto main_loop; + + case CONTINUE: + ++bp.m_it; + for (; bp.m_it != bp.m_end; ++bp.m_it) { + m_app = *bp.m_it; + const cont * c = static_cast(bp.m_instr); + // bp.m_it may reference an enode in [begin_enodes_of(lbl), end_enodes_of(lbl)) + // This enodes are not necessarily relevant. + // So, we must check whether m_context.is_relevant(m_app) is true or not. + if (m_app->get_num_args() == c->m_num_args && m_context.is_relevant(m_app)) { + // update the pattern instance + SASSERT(!m_pattern_instances.empty()); + m_pattern_instances.pop_back(); + m_pattern_instances.push_back(m_app); + + // continue succeeded + update_max_generation(m_app); + TRACE("mam_int", tout << "continue next candidate:\n" << mk_ll_pp(m_app->get_owner(), m_ast_manager);); + m_num_args = c->m_num_args; + m_oreg = c->m_oreg; + for (unsigned i = 0; i < m_num_args; i++) + m_registers[m_oreg+i] = m_app->get_arg(i); + m_pc = c->m_next; + goto main_loop; + } + } + // continue failed + if (bp.m_to_recycle) + recycle_enode_vector(bp.m_to_recycle); + m_top--; + goto backtrack; + + default: + UNREACHABLE(); + } + } // end of execute_core + + void display_trees(std::ostream & out, const ptr_vector & trees) { + ptr_vector::const_iterator it = trees.begin(); + ptr_vector::const_iterator end = trees.end(); + unsigned lbl = 0; + for (; it != end; ++it, ++lbl) { + code_tree * tree = *it; + if (tree) { + out << "tree for f" << lbl << "\n"; + out << *tree; + } + } + } + + // ------------------------------------ + // + // A mapping from func_label -> code tree. + // + // ------------------------------------ + class code_tree_map { + ast_manager & m_ast_manager; + compiler & m_compiler; + ptr_vector m_trees; // mapping: func_label -> tree + mam_trail_stack & m_trail_stack; +#ifdef Z3DEBUG + context * m_context; +#endif + + class mk_tree_trail : public mam_trail { + ptr_vector & m_trees; + unsigned m_lbl_id; + public: + mk_tree_trail(ptr_vector & t, unsigned id):m_trees(t), m_lbl_id(id) {} + virtual void undo(mam_impl & m) { + dealloc(m_trees[m_lbl_id]); + m_trees[m_lbl_id] = 0; + } + }; + + public: + code_tree_map(ast_manager & m, compiler & c, mam_trail_stack & s): + m_ast_manager(m), + m_compiler(c), + m_trail_stack(s) { + } + +#ifdef Z3DEBUG + void set_context(context * c) { m_context = c; } +#endif + + ~code_tree_map() { + std::for_each(m_trees.begin(), m_trees.end(), delete_proc()); + } + + /** + \brief Add a pattern to the code tree map. + + - mp: is used a pattern for qa. + + - first_idx: index to be used as head of the multi-pattern mp + */ + void add_pattern(quantifier * qa, app * mp, unsigned first_idx) { + SASSERT(m_ast_manager.is_pattern(mp)); + SASSERT(first_idx < mp->get_num_args()); + app * p = to_app(mp->get_arg(first_idx)); + func_decl * lbl = p->get_decl(); + unsigned lbl_id = lbl->get_decl_id(); + m_trees.reserve(lbl_id+1, 0); + if (m_trees[lbl_id] == 0) { + m_trees[lbl_id] = m_compiler.mk_tree(qa, mp, first_idx, false); + SASSERT(m_trees[lbl_id]->expected_num_args() == p->get_num_args()); + DEBUG_CODE(m_trees[lbl_id]->set_context(m_context);); + m_trail_stack.push(mk_tree_trail(m_trees, lbl_id)); + } + else { + code_tree * tree = m_trees[lbl_id]; + // We have to check the number of arguments because of nary + and * operators. + // The E-matching engine that was built when all + and * applications were binary. + // We ignore the pattern if it does not have the expected number of arguments. + // This is not the ideal solution, but it avoids possible crashes. + if (tree->expected_num_args() == p->get_num_args()) { + m_compiler.insert(tree, qa, mp, first_idx, false); + } + } + DEBUG_CODE(m_trees[lbl_id]->get_patterns().push_back(mp); + m_trail_stack.push(push_back_trail(m_trees[lbl_id]->get_patterns()));); + TRACE("trigger_bug", tout << "after add_pattern, first_idx: " << first_idx << "\n"; m_trees[lbl_id]->display(tout);); + } + + void reset() { + std::for_each(m_trees.begin(), m_trees.end(), delete_proc()); + m_trees.reset(); + } + + code_tree * get_code_tree_for(func_decl * lbl) const { + unsigned lbl_id = lbl->get_decl_id(); + if (lbl_id < m_trees.size()) + return m_trees[lbl_id]; + else + return 0; + } + + ptr_vector::iterator begin_code_trees() { + return m_trees.begin(); + } + + ptr_vector::iterator end_code_trees() { + return m_trees.end(); + } + }; + + // ------------------------------------ + // + // Path trees AKA inverted path index + // + // ------------------------------------ + + /** + \brief Temporary object used to encode a path of the form: + + f.1 -> g.2 -> h.0 + + These objects are used to update the inverse path index data structure. + For example, in the path above, given an enode n, I want to follow the + parents p_0 of n that are f-applications, and n is the second argument, + then for each such p_0, I want to follow the parents p_1 of p_0 that + are g-applications, and p_0 is the third argument. Finally, I want to + follow the p_2 parents of p_1 that are h-applications and p_1 is the + first argument of p_2. + + To improve the filtering power of the inverse path index, I'm also + storing a ground argument (when possible) in the inverted path index. + the idea is to have paths of the form + + f.1:t.2 -> g.2 -> h.0:s.1 + + The extra pairs t.2 and s.1 are an extra filter on the parents. + The idea is that I want only the f-parents s.t. the third argument is equal to t. + */ + struct path { + func_decl * m_label; + unsigned short m_arg_idx; + unsigned short m_ground_arg_idx; + enode * m_ground_arg; + unsigned m_pattern_idx; + path * m_child; + path (func_decl * lbl, unsigned short arg_idx, unsigned short ground_arg_idx, enode * ground_arg, unsigned pat_idx, path * child): + m_label(lbl), + m_arg_idx(arg_idx), + m_ground_arg_idx(ground_arg_idx), + m_ground_arg(ground_arg), + m_pattern_idx(pat_idx), + m_child(child) { + SASSERT(ground_arg != 0 || ground_arg_idx == 0); + } + }; + + bool is_equal(path const * p1, path const * p2) { + for (;;) { + if (p1->m_label != p2->m_label || + p1->m_arg_idx != p2->m_arg_idx || + p1->m_pattern_idx != p2->m_pattern_idx || + (p1->m_child == 0) != (p2->m_child == 0)) { + return false; + } + + if (p1->m_child == 0 && p2->m_child == 0) + return true; + + p1 = p1->m_child; + p2 = p2->m_child; + } + } + + typedef ptr_vector paths; + + /** + \brief Inverted path index data structure. See comments at struct path. + */ + struct path_tree { + func_decl * m_label; + unsigned short m_arg_idx; + unsigned short m_ground_arg_idx; + enode * m_ground_arg; + code_tree * m_code; + approx_set m_filter; + path_tree * m_sibling; + path_tree * m_first_child; + enode_vector * m_todo; // temporary field used to collect candidates +#ifdef _PROFILE_PATH_TREE + stopwatch m_watch; + unsigned m_counter; + unsigned m_num_eq_visited; + unsigned m_num_neq_visited; + bool m_already_displayed; //!< true if the path_tree was already displayed after reaching _PROFILE_PATH_TREE_THRESHOLD +#endif + + path_tree(path * p, label_hasher & h): + m_label(p->m_label), + m_arg_idx(p->m_arg_idx), + m_ground_arg_idx(p->m_ground_arg_idx), + m_ground_arg(p->m_ground_arg), + m_code(0), + m_filter(h(p->m_label)), + m_sibling(0), + m_first_child(0), + m_todo(0) { +#ifdef _PROFILE_PATH_TREE + m_counter = 0; + m_num_eq_visited = 0; + m_num_neq_visited = 0; + m_already_displayed = false; +#endif + } + + void display(std::ostream & out, unsigned indent) { + path_tree * curr = this; + while (curr != 0) { + for (unsigned i = 0; i < indent; i++) out << " "; + out << curr->m_label->get_name() << ":" << curr->m_arg_idx; + if (curr->m_ground_arg) + out << ":#" << curr->m_ground_arg->get_owner_id() << ":" << curr->m_ground_arg_idx; + out << " " << m_filter << " " << m_code; +#ifdef _PROFILE_PATH_TREE + out << ", counter: " << m_counter << ", num_eq_visited: " << m_num_eq_visited << ", num_neq_visited: " << m_num_neq_visited + << ", avg. : " << static_cast(m_num_neq_visited)/static_cast(m_num_neq_visited+m_num_eq_visited); +#endif + out << "\n"; + curr->m_first_child->display(out, indent+1); + curr = curr->m_sibling; + } + } + }; + + typedef std::pair path_tree_pair; + + // ------------------------------------ + // + // Matching Abstract Machine Implementation + // + // ------------------------------------ + class mam_impl : public mam { + protected: + ast_manager & m_ast_manager; + bool m_use_filters; + mam_trail_stack m_trail_stack; + label_hasher m_lbl_hasher; + code_tree_manager m_ct_manager; + compiler m_compiler; + interpreter m_interpreter; + code_tree_map m_trees; + + ptr_vector m_tmp_trees; + ptr_vector m_tmp_trees_to_delete; + ptr_vector m_to_match; + typedef std::pair qp_pair; + svector m_new_patterns; // recently added patterns + + // m_is_plbl[f] is true, then when f(c_1, ..., c_n) becomes relevant, + // for each c_i. c_i->get_root()->lbls().insert(lbl_hash(f)) + svector m_is_plbl; + // m_is_clbl[f] is true, then when n=f(c_1, ..., c_n) becomes relevant, + // n->get_root()->lbls().insert(lbl_hash(f)) + svector m_is_clbl; // children labels + + // auxiliary field used to update data-structures... + typedef ptr_vector func_decls; + vector m_var_parent_labels; + + region & m_region; + region m_tmp_region; + path_tree_pair m_pp[APPROX_SET_CAPACITY][APPROX_SET_CAPACITY]; + path_tree * m_pc[APPROX_SET_CAPACITY][APPROX_SET_CAPACITY]; + pool m_pool; + + // temporary field used to update path trees. + vector m_var_paths; + // temporary field used to collect candidates + ptr_vector m_todo; + + obj_hashtable m_shared_enodes; // ground terms that appear in patterns. + + enode * m_r1; // temp field + enode * m_r2; // temp field + + class add_shared_enode_trail; + friend class add_shared_enode_trail; + + class add_shared_enode_trail : public mam_trail { + enode * m_enode; + public: + add_shared_enode_trail(enode * n):m_enode(n) {} + virtual void undo(mam_impl & m) { m.m_shared_enodes.erase(m_enode); } + }; + +#ifdef Z3DEBUG + bool m_check_missing_instances; +#endif + + enode_vector * mk_tmp_vector() { + enode_vector * r = m_pool.mk(); + r->reset(); + return r; + } + + void recycle(enode_vector * v) { + m_pool.recycle(v); + } + + void add_candidate(code_tree * t, enode * app) { + if (t != 0) { + TRACE("mam_candidate", tout << "adding candidate:\n" << mk_ll_pp(app->get_owner(), m_ast_manager);); + if (!t->has_candidates()) + m_to_match.push_back(t); + t->add_candidate(app); + } + } + + void add_candidate(enode * app) { + func_decl * lbl = app->get_decl(); + add_candidate(m_trees.get_code_tree_for(lbl), app); + } + + bool is_plbl(func_decl * lbl) const { + unsigned lbl_id = lbl->get_decl_id(); + return lbl_id < m_is_plbl.size() && m_is_plbl[lbl_id]; + } + bool is_clbl(func_decl * lbl) const { + unsigned lbl_id = lbl->get_decl_id(); + return lbl_id < m_is_clbl.size() && m_is_clbl[lbl_id]; + } + + void update_lbls(enode * n, unsigned elem) { + approx_set & r_lbls = n->get_root()->get_lbls(); + if (!r_lbls.may_contain(elem)) { + m_trail_stack.push(mam_value_trail(r_lbls)); + r_lbls.insert(elem); + } + } + + void update_clbls(func_decl * lbl) { + unsigned lbl_id = lbl->get_decl_id(); + m_is_clbl.reserve(lbl_id+1, false); + TRACE("trigger_bug", tout << "update_clbls: " << lbl->get_name() << " is already clbl: " << m_is_clbl[lbl_id] << "\n";); + TRACE("mam_bug", tout << "update_clbls: " << lbl->get_name() << " is already clbl: " << m_is_clbl[lbl_id] << "\n";); + if (m_is_clbl[lbl_id]) + return; + m_trail_stack.push(set_bitvector_trail(m_is_clbl, lbl_id)); + SASSERT(m_is_clbl[lbl_id]); + unsigned h = m_lbl_hasher(lbl); + enode_vector::const_iterator it = m_context.begin_enodes_of(lbl); + enode_vector::const_iterator end = m_context.end_enodes_of(lbl); + for (; it != end; ++it) { + enode * app = *it; + if (m_context.is_relevant(app)) { + update_lbls(app, h); + TRACE("mam_bug", tout << "updating labels of: #" << app->get_owner_id() << "\n"; + tout << "new_elem: " << h << "\n"; + tout << "lbls: " << app->get_lbls() << "\n"; + tout << "r.lbls: " << app->get_root()->get_lbls() << "\n";); + } + } + } + + void update_children_plbls(enode * app, unsigned char elem) { + unsigned num_args = app->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + enode * c = app->get_arg(i); + approx_set & r_plbls = c->get_root()->get_plbls(); + if (!r_plbls.may_contain(elem)) { + m_trail_stack.push(mam_value_trail(r_plbls)); + r_plbls.insert(elem); + TRACE("trigger_bug", tout << "updating plabels of:\n" << mk_ismt2_pp(c->get_root()->get_owner(), m_ast_manager) << "\n"; + tout << "new_elem: " << static_cast(elem) << "\n"; + tout << "plbls: " << c->get_root()->get_plbls() << "\n";); + TRACE("mam_bug", tout << "updating plabels of: #" << c->get_root()->get_owner_id() << "\n"; + tout << "new_elem: " << static_cast(elem) << "\n"; + tout << "plbls: " << c->get_root()->get_plbls() << "\n";); + + } + } + } + + void update_plbls(func_decl * lbl) { + unsigned lbl_id = lbl->get_decl_id(); + m_is_plbl.reserve(lbl_id+1, false); + TRACE("trigger_bug", tout << "update_plbls: " << lbl->get_name() << " is already plbl: " << m_is_plbl[lbl_id] << ", lbl_id: " << lbl_id << "\n"; + tout << "mam: " << this << "\n";); + TRACE("mam_bug", tout << "update_plbls: " << lbl->get_name() << " is already plbl: " << m_is_plbl[lbl_id] << "\n";); + if (m_is_plbl[lbl_id]) + return; + m_trail_stack.push(set_bitvector_trail(m_is_plbl, lbl_id)); + SASSERT(m_is_plbl[lbl_id]); + SASSERT(is_plbl(lbl)); + unsigned h = m_lbl_hasher(lbl); + enode_vector::const_iterator it = m_context.begin_enodes_of(lbl); + enode_vector::const_iterator end = m_context.end_enodes_of(lbl); + for (; it != end; ++it) { + enode * app = *it; + if (m_context.is_relevant(app)) + update_children_plbls(app, h); + } + } + + void reset_pp_pc() { + for (unsigned i = 0; i < APPROX_SET_CAPACITY; i++) { + for (unsigned j = 0; j < APPROX_SET_CAPACITY; j++) { + m_pp[i][j].first = 0; + m_pp[i][j].second = 0; + m_pc[i][j] = 0; + } + } + } + + code_tree * mk_code(quantifier * qa, app * mp, unsigned pat_idx) { + SASSERT(m_ast_manager.is_pattern(mp)); + return m_compiler.mk_tree(qa, mp, pat_idx, true); + } + + void insert_code(path_tree * t, quantifier * qa, app * mp, unsigned pat_idx) { + SASSERT(m_ast_manager.is_pattern(mp)); + m_compiler.insert(t->m_code, qa, mp, pat_idx, false); + } + + path_tree * mk_path_tree(path * p, quantifier * qa, app * mp) { + SASSERT(m_ast_manager.is_pattern(mp)); + SASSERT(p != 0); + unsigned pat_idx = p->m_pattern_idx; + path_tree * head = 0; + path_tree * curr = 0; + path_tree * prev = 0; + while (p != 0) { + curr = new (m_region) path_tree(p, m_lbl_hasher); + if (prev) + prev->m_first_child = curr; + if (!head) + head = curr; + prev = curr; + p = p->m_child; + } + curr->m_code = mk_code(qa, mp, pat_idx); + m_trail_stack.push(new_obj_trail(curr->m_code)); + return head; + } + + void insert(path_tree * t, path * p, quantifier * qa, app * mp) { + SASSERT(m_ast_manager.is_pattern(mp)); + path_tree * head = t; + path_tree * prev_sibling = 0; + bool found_label = false; + while (t != 0) { + if (t->m_label == p->m_label) { + found_label = true; + if (t->m_arg_idx == p->m_arg_idx && + t->m_ground_arg == p->m_ground_arg && + t->m_ground_arg_idx == p->m_ground_arg_idx + ) { + // found compatible node + if (t->m_first_child == 0) { + if (p->m_child == 0) { + SASSERT(t->m_code != 0); + insert_code(t, qa, mp, p->m_pattern_idx); + } + else { + m_trail_stack.push(set_ptr_trail(t->m_first_child)); + t->m_first_child = mk_path_tree(p->m_child, qa, mp); + } + } + else { + if (p->m_child == 0) { + if (t->m_code) { + insert_code(t, qa, mp, p->m_pattern_idx); + } + else { + m_trail_stack.push(set_ptr_trail(t->m_code)); + t->m_code = mk_code(qa, mp, p->m_pattern_idx); + m_trail_stack.push(new_obj_trail(t->m_code)); + } + } + else { + insert(t->m_first_child, p->m_child, qa, mp); + } + } + return; + } + } + prev_sibling = t; + t = t->m_sibling; + } + m_trail_stack.push(set_ptr_trail(prev_sibling->m_sibling)); + prev_sibling->m_sibling = mk_path_tree(p, qa, mp); + if (!found_label) { + m_trail_stack.push(value_trail(head->m_filter)); + head->m_filter.insert(m_lbl_hasher(p->m_label)); + } + } + + void update_pc(unsigned char h1, unsigned char h2, path * p, quantifier * qa, app * mp) { + if (m_pc[h1][h2]) { + insert(m_pc[h1][h2], p, qa, mp); + } + else { + m_trail_stack.push(set_ptr_trail(m_pc[h1][h2])); + m_pc[h1][h2] = mk_path_tree(p, qa, mp); + } + TRACE("mam_path_tree_updt", + tout << "updated path tree:\n"; + m_pc[h1][h2]->display(tout, 2);); + } + + void update_pp(unsigned char h1, unsigned char h2, path * p1, path * p2, quantifier * qa, app * mp) { + if (h1 == h2) { + SASSERT(m_pp[h1][h2].second == 0); + if (m_pp[h1][h2].first) { + insert(m_pp[h1][h2].first, p1, qa, mp); + if (!is_equal(p1, p2)) + insert(m_pp[h1][h2].first, p2, qa, mp); + } + else { + m_trail_stack.push(set_ptr_trail(m_pp[h1][h2].first)); + m_pp[h1][h2].first = mk_path_tree(p1, qa, mp); + insert(m_pp[h1][h2].first, p2, qa, mp); + } + } + else { + if (h1 > h2) { + std::swap(h1, h2); + std::swap(p1, p2); + } + + if (m_pp[h1][h2].first) { + SASSERT(m_pp[h1][h2].second); + insert(m_pp[h1][h2].first, p1, qa, mp); + insert(m_pp[h1][h2].second, p2, qa, mp); + } + else { + SASSERT(m_pp[h1][h2].second == 0); + m_trail_stack.push(set_ptr_trail(m_pp[h1][h2].first)); + m_trail_stack.push(set_ptr_trail(m_pp[h1][h2].second)); + m_pp[h1][h2].first = mk_path_tree(p1, qa, mp); + m_pp[h1][h2].second = mk_path_tree(p2, qa, mp); + } + } + TRACE("mam_path_tree_updt", + tout << "updated path tree:\n"; + SASSERT(h1 <= h2); + m_pp[h1][h2].first->display(tout, 2); + if (h1 != h2) { + m_pp[h1][h2].second->display(tout, 2); + }); + } + + void update_vars(unsigned short var_id, path * p, quantifier * qa, app * mp) { + paths & var_paths = m_var_paths[var_id]; + bool found = false; + paths::iterator it = var_paths.begin(); + paths::iterator end = var_paths.end(); + for (; it != end; ++it) { + path * curr_path = *it; + if (is_equal(p, curr_path)) + found = true; + func_decl * lbl1 = curr_path->m_label; + func_decl * lbl2 = p->m_label; + update_plbls(lbl1); + update_plbls(lbl2); + update_pp(m_lbl_hasher(lbl1), m_lbl_hasher(lbl2), curr_path, p, qa, mp); + } + if (!found) + var_paths.push_back(p); + } + + enode * get_ground_arg(app * pat, quantifier * qa, unsigned & pos) { + pos = 0; + unsigned num_args = pat->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = pat->get_arg(i); + if (is_ground(arg)) { + pos = i; + return mk_enode(m_context, qa, to_app(arg)); + } + } + return 0; + } + + /** + \brief Update inverted path index with respect to pattern pat in the context of path p. + pat is a sub-expression of mp->get_arg(pat_idx). mp is a multi-pattern of qa. + If p == 0, then mp->get_arg(pat_idx) == pat. + */ + void update_filters(app * pat, path * p, quantifier * qa, app * mp, unsigned pat_idx) { + unsigned short num_args = pat->get_num_args(); + unsigned ground_arg_pos = 0; + enode * ground_arg = get_ground_arg(pat, qa, ground_arg_pos); + func_decl * plbl = pat->get_decl(); + for (unsigned short i = 0; i < num_args; i++) { + expr * child = pat->get_arg(i); + path * new_path = new (m_tmp_region) path(plbl, i, ground_arg_pos, ground_arg, pat_idx, p); + + if (is_var(child)) { + update_vars(to_var(child)->get_idx(), new_path, qa, mp); + continue; + } + + SASSERT(is_app(child)); + + if (to_app(child)->is_ground()) { + enode * n = mk_enode(m_context, qa, to_app(child)); + update_plbls(plbl); + if (!n->has_lbl_hash()) + n->set_lbl_hash(m_context); + TRACE("mam_bug", + tout << "updating pc labels " << plbl->get_name() << " " << + static_cast(n->get_lbl_hash()) << "\n"; + tout << "#" << n->get_owner_id() << " " << n->get_root()->get_lbls() << "\n"; + tout << "relevant: " << m_context.is_relevant(n) << "\n";); + update_pc(m_lbl_hasher(plbl), n->get_lbl_hash(), new_path, qa, mp); + continue; + } + + func_decl * clbl = to_app(child)->get_decl(); + TRACE("mam_bug", tout << "updating pc labels " << plbl->get_name() << " " << clbl->get_name() << "\n";); + update_plbls(plbl); + update_clbls(clbl); + update_pc(m_lbl_hasher(plbl), m_lbl_hasher(clbl), new_path, qa, mp); + update_filters(to_app(child), new_path, qa, mp, pat_idx); + } + } + + /** + \brief Update inverted path index. + */ + void update_filters(quantifier * qa, app * mp) { + TRACE("mam_bug", tout << "updating filters using:\n" << mk_pp(mp, m_ast_manager) << "\n";); + unsigned num_vars = qa->get_num_decls(); + if (num_vars >= m_var_paths.size()) + m_var_paths.resize(num_vars+1); + for (unsigned i = 0; i < num_vars; i++) + m_var_paths[i].reset(); + m_tmp_region.reset(); + // Given a multi-pattern (p_1, ..., p_n) + // We need to update the filters using patterns: + // (p_1, p_2, ..., p_n) + // (p_2, p_1, ..., p_n) + // ... + // (p_n, p_2, ..., p_1) + unsigned num_patterns = mp->get_num_args(); + for (unsigned i = 0; i < num_patterns; i++) { + app * pat = to_app(mp->get_arg(i)); + update_filters(pat, 0, qa, mp, i); + } + } + + void display_filter_info(std::ostream & out) { + for (unsigned i = 0; i < APPROX_SET_CAPACITY; i++) { + for (unsigned j = 0; j < APPROX_SET_CAPACITY; j++) { + if (m_pp[i][j].first) { + out << "pp[" << i << "][" << j << "]:\n"; + m_pp[i][j].first->display(out, 1); + if (i != j) { + m_pp[i][j].second->display(out, 1); + } + } + if (m_pc[i][j]) { + out << "pc[" << i << "][" << j << "]:\n"; + m_pc[i][j]->display(out, 1); + } + } + } + } + + /** + \brief Check equality modulo the equality m_r1 = m_r2 + */ + bool is_eq(enode * n1, enode * n2) { + return + n1->get_root() == n2->get_root() || + (n1->get_root() == m_r1 && n2->get_root() == m_r2) || + (n2->get_root() == m_r1 && n1->get_root() == m_r2); + } + + /** + \brief Collect new E-matching candidates using the inverted path index t. + */ + void collect_parents(enode * r, path_tree * t) { + if (t == 0) + return; +#ifdef _PROFILE_PATH_TREE + t->m_watch.start(); +#endif + m_todo.reset(); + enode_vector * to_unmark = mk_tmp_vector(); + enode_vector * to_unmark2 = mk_tmp_vector(); + SASSERT(to_unmark->empty()); + SASSERT(to_unmark2->empty()); + t->m_todo = mk_tmp_vector(); + t->m_todo->push_back(r); + m_todo.push_back(t); + unsigned head = 0; + while (head < m_todo.size()) { + path_tree * t = m_todo[head]; +#ifdef _PROFILE_PATH_TREE + t->m_counter++; +#endif + TRACE("mam_path_tree", + tout << "processing:\n"; + t->display(tout, 2);); + enode_vector * v = t->m_todo; + approx_set & filter = t->m_filter; + head++; + +#ifdef _PROFILE_PATH_TREE + static unsigned counter = 0; + static unsigned total_sz = 0; + static unsigned max_sz = 0; + counter++; + total_sz += v->size(); + if (v->size() > max_sz) + max_sz = v->size(); + if (counter % 100000 == 0) + std::cout << "Avg. " << static_cast(total_sz)/static_cast(counter) << ", Max. " << max_sz << "\n"; +#endif + + enode_vector::iterator it1 = v->begin(); + enode_vector::iterator end1 = v->end(); + for (; it1 != end1; ++it1) { + // Two different kinds of mark are used: + // - enode mark field: it is used to mark the already processed parents. + // - enode mark2 field: it is used to mark the roots already added to be processed in the next level. + // + // In a previous version of Z3, the "enode mark field" was used to mark both cases. This is incorrect, + // and Z3 may fail to find potential new matches. + // + // The file regression\acu.sx exposed this problem. + enode * curr_child = (*it1)->get_root(); + + if (m_use_filters && curr_child->get_plbls().empty_intersection(filter)) + continue; + +#ifdef _PROFILE_PATH_TREE + static unsigned counter2 = 0; + static unsigned total_sz2 = 0; + static unsigned max_sz2 = 0; + counter2++; + total_sz2 += curr_child->get_num_parents(); + if (curr_child->get_num_parents() > max_sz2) + max_sz2 = curr_child->get_num_parents(); + if (counter2 % 100000 == 0) + std::cout << "Avg2. " << static_cast(total_sz2)/static_cast(counter2) << ", Max2. " << max_sz2 << "\n"; +#endif + + TRACE("mam_path_tree", tout << "processing: #" << curr_child->get_owner_id() << "\n";); + enode_vector::const_iterator it2 = curr_child->begin_parents(); + enode_vector::const_iterator end2 = curr_child->end_parents(); + for (; it2 != end2; ++it2) { + enode * curr_parent = *it2; +#ifdef _PROFILE_PATH_TREE + if (curr_parent->is_eq()) + t->m_num_eq_visited++; + else + t->m_num_neq_visited++; +#endif + // Remark: equality is never in the inverted path index. + if (curr_parent->is_eq()) + continue; + func_decl * lbl = curr_parent->get_decl(); + bool is_flat_assoc = lbl->is_flat_associative(); + enode * curr_parent_root = curr_parent->get_root(); + enode * curr_parent_cg = curr_parent->get_cg(); + TRACE("mam_path_tree", tout << "processing parent:\n" << mk_pp(curr_parent->get_owner(), m_ast_manager) << "\n";); + TRACE("mam_path_tree", tout << "parent is marked: " << curr_parent->is_marked() << "\n";); + if (filter.may_contain(m_lbl_hasher(lbl)) && + !curr_parent->is_marked() && + (curr_parent_cg == curr_parent || !is_eq(curr_parent_cg, curr_parent_root)) && + m_context.is_relevant(curr_parent) + ) { + path_tree * curr_tree = t; + while (curr_tree) { + if (curr_tree->m_label == lbl && + // Starting at Z3 3.0, some associative operators (e.g., + and *) are represented using n-ary applications. + // In this cases, we say the declarations is is_flat_assoc(). + // The MAM was implemented in Z3 2.0 when the following invariant was true: + // For every application f(x_1, ..., x_n) of a function symbol f, n = f->get_arity(). + // Starting at Z3 3.0, this is only true if !f->is_flat_associative(). + // Thus, we need the extra checks. + (!is_flat_assoc || (curr_tree->m_arg_idx < curr_parent->get_num_args() && + curr_tree->m_ground_arg_idx < curr_parent->get_num_args()))) { + enode * curr_parent_child = curr_parent->get_arg(curr_tree->m_arg_idx)->get_root(); + if (// Filter 1. the curr_child is equal to child of the current parent. + curr_child == curr_parent_child && + // Filter 2. + ( + // curr_tree has no support for the filter based on a ground argument. + curr_tree->m_ground_arg == 0 || + // checks whether the child of the parent is equal to the expected ground argument. + is_eq(curr_tree->m_ground_arg, curr_parent->get_arg(curr_tree->m_ground_arg_idx)) + )) { + if (curr_tree->m_code) { + TRACE("mam_path_tree", tout << "found candidate\n";); + add_candidate(curr_tree->m_code, curr_parent); + } + if (curr_tree->m_first_child) { + path_tree * child = curr_tree->m_first_child; + if (child->m_todo == 0) { + child->m_todo = mk_tmp_vector(); + m_todo.push_back(child); + } + if (!curr_parent_root->is_marked2()) { + child->m_todo->push_back(curr_parent_root); + } + } + } + } + curr_tree = curr_tree->m_sibling; + } + curr_parent->set_mark(); + to_unmark->push_back(curr_parent); + if (!curr_parent_root->is_marked2()) { + curr_parent_root->set_mark2(); + to_unmark2->push_back(curr_parent_root); + } + } + } + } + recycle(t->m_todo); + t->m_todo = 0; + // remove both marks. + unmark_enodes(to_unmark->size(), to_unmark->c_ptr()); + unmark_enodes2(to_unmark2->size(), to_unmark2->c_ptr()); + to_unmark->reset(); + to_unmark2->reset(); + } + recycle(to_unmark); + recycle(to_unmark2); +#ifdef _PROFILE_PATH_TREE + t->m_watch.stop(); + if (t->m_counter % _PROFILE_PATH_TREE_THRESHOLD == 0) { + std::cout << "EXPENSIVE " << t->m_watch.get_seconds() << " secs, counter: " << t->m_counter << "\n"; + t->display(std::cout, 0); + t->m_already_displayed = true; + } +#endif + } + + void process_pp(enode * r1, enode * r2) { + approx_set & plbls1 = r1->get_plbls(); + approx_set & plbls2 = r2->get_plbls(); + TRACE("incremental_matcher", tout << "pp: plbls1: " << plbls1 << ", plbls2: " << plbls2 << "\n";); + TRACE("mam_info", tout << "pp: " << plbls1.size() * plbls2.size() << "\n";); + if (!plbls1.empty() && !plbls2.empty()) { + approx_set::iterator it1 = plbls1.begin(); + approx_set::iterator end1 = plbls1.end(); + for (; it1 != end1; ++it1) { + unsigned plbl1 = *it1; + SASSERT(plbls1.may_contain(plbl1)); + approx_set::iterator it2 = plbls2.begin(); + approx_set::iterator end2 = plbls2.end(); + for (; it2 != end2; ++it2) { + unsigned plbl2 = *it2; + SASSERT(plbls2.may_contain(plbl2)); + unsigned n_plbl1 = plbl1; + unsigned n_plbl2 = plbl2; + enode * n_r1 = r1; + enode * n_r2 = r2; + if (n_plbl1 > n_plbl2) { + std::swap(n_plbl1, n_plbl2); + std::swap(n_r1, n_r2); + } + if (n_plbl1 == n_plbl2) { + SASSERT(m_pp[n_plbl1][n_plbl2].second == 0); + if (n_r1->get_num_parents() <= n_r2->get_num_parents()) + collect_parents(n_r1, m_pp[n_plbl1][n_plbl2].first); + else + collect_parents(n_r2, m_pp[n_plbl1][n_plbl2].first); + } + else { + SASSERT(n_plbl1 < n_plbl2); + if (n_r1->get_num_parents() <= n_r2->get_num_parents()) + collect_parents(n_r1, m_pp[n_plbl1][n_plbl2].first); + else + collect_parents(n_r2, m_pp[n_plbl1][n_plbl2].second); + } + } + } + } + } + + void process_pc(enode * r1, enode * r2) { + approx_set & plbls = r1->get_plbls(); + approx_set & clbls = r2->get_lbls(); + if (!plbls.empty() && !clbls.empty()) { + approx_set::iterator it1 = plbls.begin(); + approx_set::iterator end1 = plbls.end(); + for (; it1 != end1; ++it1) { + unsigned plbl1 = *it1; + SASSERT(plbls.may_contain(plbl1)); + approx_set::iterator it2 = clbls.begin(); + approx_set::iterator end2 = clbls.end(); + for (; it2 != end2; ++it2) { + unsigned lbl2 = *it2; + SASSERT(clbls.may_contain(lbl2)); + collect_parents(r1, m_pc[plbl1][lbl2]); + } + } + } + } + + void match_new_patterns() { + TRACE("mam_new_pat", tout << "matching new patterns:\n";); + m_tmp_trees_to_delete.reset(); + svector::iterator it1 = m_new_patterns.begin(); + svector::iterator end1 = m_new_patterns.end(); + for (; it1 != end1; ++it1) { + quantifier * qa = it1->first; + app * mp = it1->second; + SASSERT(m_ast_manager.is_pattern(mp)); + app * p = to_app(mp->get_arg(0)); + func_decl * lbl = p->get_decl(); + if (m_context.get_num_enodes_of(lbl) > 0) { + unsigned lbl_id = lbl->get_decl_id(); + m_tmp_trees.reserve(lbl_id+1, 0); + if (m_tmp_trees[lbl_id] == 0) { + m_tmp_trees[lbl_id] = m_compiler.mk_tree(qa, mp, 0, false); + m_tmp_trees_to_delete.push_back(lbl); + } + else { + m_compiler.insert(m_tmp_trees[lbl_id], qa, mp, 0, true); + } + } + } + + ptr_vector::iterator it2 = m_tmp_trees_to_delete.begin(); + ptr_vector::iterator end2 = m_tmp_trees_to_delete.end(); + for (; it2 != end2; ++it2) { + func_decl * lbl = *it2; + unsigned lbl_id = lbl->get_decl_id(); + code_tree * tmp_tree = m_tmp_trees[lbl_id]; + SASSERT(tmp_tree != 0); + SASSERT(m_context.get_num_enodes_of(lbl) > 0); + m_interpreter.init(tmp_tree); + enode_vector::const_iterator it3 = m_context.begin_enodes_of(lbl); + enode_vector::const_iterator end3 = m_context.end_enodes_of(lbl); + for (; it3 != end3; ++it3) { + enode * app = *it3; + if (m_context.is_relevant(app)) + m_interpreter.execute_core(tmp_tree, app); + } + m_tmp_trees[lbl_id] = 0; + dealloc(tmp_tree); + } + m_new_patterns.reset(); + } + + void collect_ground_exprs(quantifier * qa, app * mp) { + ptr_buffer todo; + unsigned num_patterns = mp->get_num_args(); + for (unsigned i = 0; i < num_patterns; i++) { + app * pat = to_app(mp->get_arg(i)); + TRACE("mam_pat", tout << mk_ismt2_pp(qa, m_ast_manager) << "\npat:\n" << mk_ismt2_pp(pat, m_ast_manager) << "\n";); + SASSERT(!pat->is_ground()); + todo.push_back(pat); + } + while (!todo.empty()) { + app * n = todo.back(); + todo.pop_back(); + if (n->is_ground()) { + enode * e = mk_enode(m_context, qa, n); + m_trail_stack.push(add_shared_enode_trail(e)); + m_shared_enodes.insert(e); + } + else { + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = n->get_arg(i); + if (is_app(arg)) + todo.push_back(to_app(arg)); + } + } + } + } + + public: + mam_impl(context & ctx, bool use_filters, std::ostream *trace): + mam(ctx, trace), + m_ast_manager(ctx.get_manager()), + m_use_filters(use_filters), + m_trail_stack(*this), + m_ct_manager(m_lbl_hasher, m_trail_stack), + m_compiler(ctx, m_ct_manager, m_lbl_hasher, use_filters), + m_interpreter(ctx, *this, use_filters, trace), + m_trees(m_ast_manager, m_compiler, m_trail_stack), + m_region(m_trail_stack.get_region()), + m_r1(0), + m_r2(0) { + DEBUG_CODE(m_trees.set_context(&ctx);); + DEBUG_CODE(m_check_missing_instances = false;); + reset_pp_pc(); + } + + virtual ~mam_impl() { + m_trail_stack.reset(); + } + + virtual void add_pattern(quantifier * qa, app * mp) { + SASSERT(m_ast_manager.is_pattern(mp)); + TRACE("trigger_bug", tout << "adding pattern\n" << mk_ismt2_pp(qa, m_ast_manager) << "\n" << mk_ismt2_pp(mp, m_ast_manager) << "\n";); + TRACE("mam_bug", tout << "adding pattern\n" << mk_pp(qa, m_ast_manager) << "\n" << mk_pp(mp, m_ast_manager) << "\n";); + // Z3 checks if a pattern is ground or not before solving. + // Ground patterns are discarded. + // However, the simplifier may turn a non-ground pattern into a ground one. + // So, we should check it again here. + unsigned num_patterns = mp->get_num_args(); + for (unsigned i = 0; i < num_patterns; i++) + if (is_ground(mp->get_arg(i))) + return; // ignore multi-pattern containing ground pattern. + update_filters(qa, mp); + collect_ground_exprs(qa, mp); + m_new_patterns.push_back(qp_pair(qa, mp)); + // The matching abstract machine implements incremental + // e-matching. So, for a multi-pattern [ p_1, ..., p_n ], + // we have to make n insertions. In the i-th insertion, + // the pattern p_i is assumed to be the first one. + for (unsigned i = 0; i < num_patterns; i++) + m_trees.add_pattern(qa, mp, i); + } + + virtual void push_scope() { + m_trail_stack.push_scope(); + } + + virtual void pop_scope(unsigned num_scopes) { + if (!m_to_match.empty()) { + ptr_vector::iterator it = m_to_match.begin(); + ptr_vector::iterator end = m_to_match.end(); + for (; it != end; ++it) { + code_tree * t = *it; + t->reset_candidates(); + } + m_to_match.reset(); + } + m_new_patterns.reset(); + m_trail_stack.pop_scope(num_scopes); + } + + virtual void reset() { + m_trail_stack.reset(); + m_trees.reset(); + m_to_match.reset(); + m_new_patterns.reset(); + m_is_plbl.reset(); + m_is_clbl.reset(); + reset_pp_pc(); + m_tmp_region.reset(); + } + + virtual void display(std::ostream& out) { + out << "mam:\n"; + m_lbl_hasher.display(out); + ptr_vector::iterator it = m_trees.begin_code_trees(); + ptr_vector::iterator end = m_trees.end_code_trees(); + for (; it != end; ++it) { + if (*it) + (*it)->display(out); + } + } + + virtual void match() { + TRACE("trigger_bug", tout << "match\n"; display(tout);); + ptr_vector::iterator it = m_to_match.begin(); + ptr_vector::iterator end = m_to_match.end(); + for (; it != end; ++it) { + code_tree * t = *it; + SASSERT(t->has_candidates()); + m_interpreter.execute(t); + t->reset_candidates(); + } + m_to_match.reset(); + if (!m_new_patterns.empty()) { + match_new_patterns(); + m_new_patterns.reset(); + } + } + + virtual void rematch(bool use_irrelevant) { + ptr_vector::iterator it = m_trees.begin_code_trees(); + ptr_vector::iterator end = m_trees.end_code_trees(); + unsigned lbl = 0; + for (; it != end; ++it, ++lbl) { + code_tree * t = *it; + if (t) { + m_interpreter.init(t); + func_decl * lbl = t->get_root_lbl(); + enode_vector::const_iterator it2 = m_context.begin_enodes_of(lbl); + enode_vector::const_iterator end2 = m_context.end_enodes_of(lbl); + for (; it2 != end2; ++it2) { + enode * curr = *it2; + if (use_irrelevant || m_context.is_relevant(curr)) + m_interpreter.execute_core(t, curr); + } + } + } + } + +#ifdef Z3DEBUG + virtual bool check_missing_instances() { + TRACE("missing_instance", tout << "checking for missing instances...\n";); + flet l(m_check_missing_instances, true); + rematch(false); + return true; + } +#endif + + virtual void on_match(quantifier * qa, app * pat, unsigned num_bindings, enode * const * bindings, unsigned max_generation, ptr_vector & used_enodes) { + TRACE("trigger_bug", tout << "found match\n";); +#ifdef Z3DEBUG + if (m_check_missing_instances) { + if (!m_context.slow_contains_instance(qa, num_bindings, bindings)) { + TRACE("missing_instance", + tout << "qa:\n" << mk_ll_pp(qa, m_ast_manager) << "\npat:\n" << mk_ll_pp(pat, m_ast_manager); + for (unsigned i = 0; i < num_bindings; i++) + tout << "#" << bindings[i]->get_owner_id() << "\n" << mk_ll_pp(bindings[i]->get_owner(), m_ast_manager) << "\n"; + ); + UNREACHABLE(); + } + return; + } + for (unsigned i = 0; i < num_bindings; i++) { + SASSERT(bindings[i]->get_generation() <= max_generation); + } +#endif + m_context.add_instance(qa, pat, num_bindings, bindings, max_generation, m_interpreter.get_min_top_generation(), m_interpreter.get_max_top_generation(), used_enodes); + } + + virtual bool is_shared(enode * n) const { + return m_shared_enodes.contains(n); + } + + // This method is invoked when n becomes relevant. + // If lazy == true, then n is not added to the list of candidate enodes for matching. That is, the method just updates the lbls. + virtual void relevant_eh(enode * n, bool lazy) { + TRACE("trigger_bug", tout << "relevant_eh:\n" << mk_ismt2_pp(n->get_owner(), m_ast_manager) << "\n"; + tout << "mam: " << this << "\n";); + TRACE("mam", tout << "relevant_eh: #" << n->get_owner_id() << "\n";); + if (n->has_lbl_hash()) + update_lbls(n, n->get_lbl_hash()); + + if (n->get_num_args() > 0) { + func_decl * lbl = n->get_decl(); + unsigned h = m_lbl_hasher(lbl); + TRACE("trigger_bug", tout << "lbl: " << lbl->get_name() << " is_clbl(lbl): " << is_clbl(lbl) + << ", is_plbl(lbl): " << is_plbl(lbl) << ", h: " << h << "\n"; + tout << "lbl_id: " << lbl->get_decl_id() << "\n";); + if (is_clbl(lbl)) + update_lbls(n, h); + if (is_plbl(lbl)) + update_children_plbls(n, h); + TRACE("mam_bug", tout << "adding relevant candidate:\n" << mk_ll_pp(n->get_owner(), m_ast_manager) << "\n";); + if (!lazy) + add_candidate(n); + } + } + + virtual bool has_work() const { + return !m_to_match.empty() || !m_new_patterns.empty(); + } + + virtual void add_eq_eh(enode * r1, enode * r2) { + flet l1(m_r1, r1); + flet l2(m_r2, r2); + + TRACE("mam", tout << "add_eq_eh: #" << r1->get_owner_id() << " #" << r2->get_owner_id() << "\n";); + TRACE("mam_inc_bug_detail", m_context.display(tout);); + TRACE("mam_inc_bug", + tout << "before:\n#" << r1->get_owner_id() << " #" << r2->get_owner_id() << "\n"; + tout << "r1.lbls: " << r1->get_lbls() << "\n"; + tout << "r2.lbls: " << r2->get_lbls() << "\n"; + tout << "r1.plbls: " << r1->get_plbls() << "\n"; + tout << "r2.plbls: " << r2->get_plbls() << "\n";); + + process_pc(r1, r2); + process_pc(r2, r1); + process_pp(r1, r2); + + approx_set r1_plbls = r1->get_plbls(); + approx_set & r2_plbls = r2->get_plbls(); + approx_set r1_lbls = r1->get_lbls(); + approx_set & r2_lbls = r2->get_lbls(); + + m_trail_stack.push(mam_value_trail(r2_lbls)); + m_trail_stack.push(mam_value_trail(r2_plbls)); + r2_lbls |= r1_lbls; + r2_plbls |= r1_plbls; + TRACE("mam_inc_bug", + tout << "after:\n"; + tout << "r1.lbls: " << r1->get_lbls() << "\n"; + tout << "r2.lbls: " << r2->get_lbls() << "\n"; + tout << "r1.plbls: " << r1->get_plbls() << "\n"; + tout << "r2.plbls: " << r2->get_plbls() << "\n";); + SASSERT(approx_subset(r1->get_plbls(), r2->get_plbls())); + SASSERT(approx_subset(r1->get_lbls(), r2->get_lbls())); + } + }; + + mam * mk_mam(context & ctx, std::ostream *trace) { + return alloc(mam_impl, ctx, true, trace); + } +}; + diff --git a/lib/mam.h b/lib/mam.h new file mode 100644 index 000000000..cee6f6e2f --- /dev/null +++ b/lib/mam.h @@ -0,0 +1,74 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + mam.h + +Abstract: + + Matching Abstract Machine + +Author: + + Leonardo de Moura (leonardo) 2007-02-13. + +Revision History: + +--*/ +#ifndef _MAM_H_ +#define _MAM_H_ + +#include"ast.h" +#include"smt_types.h" + +namespace smt { + /** + \brief Matching Abstract Machine (MAM) + */ + class mam { + protected: + context & m_context; + std::ostream * m_trace_stream; + public: + mam(context & ctx, std::ostream *trace): + m_context(ctx), + m_trace_stream(trace) { + } + + virtual ~mam() { + } + + virtual void add_pattern(quantifier * q, app * mp) = 0; + + virtual void push_scope() = 0; + + virtual void pop_scope(unsigned num_scopes) = 0; + + virtual void match() = 0; + + virtual void rematch(bool use_irrelevant = false) = 0; + + virtual bool has_work() const = 0; + + virtual void relevant_eh(enode * n, bool lazy) = 0; + + virtual void add_eq_eh(enode * r1, enode * r2) = 0; + + virtual void reset() = 0; + + virtual void display(std::ostream& out) = 0; + + virtual void on_match(quantifier * q, app * pat, unsigned num_bindings, enode * const * bindings, unsigned max_generation, ptr_vector & used_enodes) = 0; + + virtual bool is_shared(enode * n) const = 0; + +#ifdef Z3DEBUG + virtual bool check_missing_instances() = 0; +#endif + }; + + mam * mk_mam(context & ctx, std::ostream *trace); +}; + +#endif /* _MAM_H_ */ diff --git a/lib/map.h b/lib/map.h new file mode 100644 index 000000000..3e8e10891 --- /dev/null +++ b/lib/map.h @@ -0,0 +1,286 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + map.h + +Abstract: + + Simple mapping based on the hashtable. + +Author: + + Leonardo de Moura (leonardo) 2006-10-14. + +Revision History: + +--*/ +#ifndef _MAP_H_ +#define _MAP_H_ + +#include"hashtable.h" + +template +struct _key_data { + Key m_key; + Value m_value; + _key_data() { + } + _key_data(Key const & k): + m_key(k) { + } + _key_data(Key const & k, Value const & v): + m_key(k), + m_value(v) { + } +}; + +template +class table2map { +public: + typedef Entry entry; + typedef typename Entry::key key; + typedef typename Entry::value value; + typedef typename Entry::key_data key_data; + + + struct entry_hash_proc : private HashProc { + entry_hash_proc(HashProc const & p): + HashProc(p) { + } + + unsigned operator()(key_data const & d) const { + return HashProc::operator()(d.m_key); + } + }; + + struct entry_eq_proc : private EqProc { + entry_eq_proc(EqProc const & p): + EqProc(p) { + } + + bool operator()(key_data const & d1, key_data const & d2) const { + return EqProc::operator()(d1.m_key, d2.m_key); + } + }; + + typedef core_hashtable table; + + table m_table; + +public: + table2map(HashProc const & h = HashProc(), EqProc const & e = EqProc()): + m_table(DEFAULT_HASHTABLE_INITIAL_CAPACITY, entry_hash_proc(h), entry_eq_proc(e)) { + } + + typedef typename table::iterator iterator; + + void reset() { + m_table.reset(); + } + + void finalize() { + m_table.finalize(); + } + + bool empty() const { + return m_table.empty(); + } + + unsigned size() const { + return m_table.size(); + } + + unsigned capacity() const { + return m_table.capacity(); + } + + iterator begin() const { + return m_table.begin(); + } + + iterator end() const { + return m_table.end(); + } + + void insert(key const & k, value const & v) { + m_table.insert(key_data(k, v)); + } + + key_data const & insert_if_not_there(key const & k, value const & v) { + return m_table.insert_if_not_there(key_data(k, v)); + } + + entry * insert_if_not_there2(key const & k, value const & v) { + return m_table.insert_if_not_there2(key_data(k, v)); + } + + entry * find_core(key const & k) const { + return m_table.find_core(key_data(k)); + } + + bool find(key const & k, value & v) const { + entry * e = find_core(k); + if (e) { + v = e->get_data().m_value; + } + return (0 != e); + } + + value const& get(key const& k, value const& default_value) const { + entry* e = find_core(k); + if (e) { + return e->m_value; + } + else { + return default_value; + } + } + + iterator find_iterator(key const & k) const { + return m_table.find(key_data(k)); + } + + value const & find(key const& k) const { + entry * e = find_core(k); + SASSERT(e); + return e->get_data().m_value; + } + + value & find(key const& k) { + entry * e = find_core(k); + SASSERT(e); + return e->get_data().m_value; + } + + value const& operator[](key const& k) const { return find(k); } + + value& operator[](key const& k) { return find(k); } + + + bool contains(key const & k) const { + return find_core(k) != 0; + } + + void remove(key const & k) { + m_table.remove(key_data(k)); + } + + void erase(key const & k) { + remove(k); + } + + unsigned long long get_num_collision() const { return m_table.get_num_collision(); } + + void swap(table2map & other) { + m_table.swap(other.m_table); + } + +#ifdef Z3DEBUG + + bool check_invariant() { + return m_table.check_invariant(); + } + +#endif +}; + +template +class default_map_entry : public default_hash_entry<_key_data > { +public: + typedef Key key; + typedef Value value; + typedef _key_data key_data; +}; + + +template +class map : public table2map, HashProc, EqProc> { +public: + map(HashProc const & h = HashProc(), EqProc const & e = EqProc()): + table2map, HashProc, EqProc>(h, e) { + } +}; + +template +struct _key_ptr_data { + Key * m_key; + Value m_value; + _key_ptr_data(): + m_key(0) { + } + _key_ptr_data(Key * k): + m_key(k) { + } + _key_ptr_data(Key * k, Value const & v): + m_key(k), + m_value(v) { + } +}; + +template +class ptr_map_entry { +public: + typedef _key_ptr_data key_data; + typedef _key_ptr_data data; +private: + unsigned m_hash; //!< cached hash code + data m_data; +public: + typedef Key * key; + typedef Value value; + unsigned get_hash() const { return m_hash; } + bool is_free() const { return m_data.m_key == 0; } + bool is_deleted() const { return m_data.m_key == reinterpret_cast(1); } + bool is_used() const { return m_data.m_key != reinterpret_cast(0) && m_data.m_key != reinterpret_cast(1); } + key_data const & get_data() const { return m_data; } + key_data & get_data() { return m_data; } + void set_data(key_data const & d) { m_data = d; } + void set_hash(unsigned h) { m_hash = h; } + void mark_as_deleted() { m_data.m_key = reinterpret_cast(1); } + void mark_as_free() { m_data.m_key = 0; } +}; + +template +class ptr_addr_map_entry { +public: + typedef _key_ptr_data key_data; + typedef _key_ptr_data data; +private: + data m_data; +public: + typedef Key * key; + typedef Value value; + unsigned get_hash() const { return get_ptr_hash(m_data.m_key); } + bool is_free() const { return m_data.m_key == 0; } + bool is_deleted() const { return m_data.m_key == reinterpret_cast(1); } + bool is_used() const { return m_data.m_key != reinterpret_cast(0) && m_data.m_key != reinterpret_cast(1); } + key_data const & get_data() const { return m_data; } + key_data & get_data() { return m_data; } + void set_data(key_data const & d) { m_data = d; } + void set_hash(unsigned h) { SASSERT(h == get_ptr_hash(m_data.m_key)); /* do nothing */ } + void mark_as_deleted() { m_data.m_key = reinterpret_cast(1); } + void mark_as_free() { m_data.m_key = 0; } +}; + +template +class ptr_addr_map : public table2map, ptr_hash, ptr_eq > { +public: +}; + +struct u_hash { unsigned operator()(unsigned u) const { return u; } }; + +struct u_eq { bool operator()(unsigned u1, unsigned u2) const { return u1 == u2; } }; + +struct size_t_eq { bool operator()(size_t u1, size_t u2) const { return u1 == u2; } }; + +struct int_eq { bool operator()(int u1, int u2) const { return u1 == u2; } }; + +template +class u_map : public map {}; + +template +class size_t_map : public map {}; + +#endif diff --git a/lib/marker.h b/lib/marker.h new file mode 100644 index 000000000..277f2816d --- /dev/null +++ b/lib/marker.h @@ -0,0 +1,56 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + marker.h + +Abstract: + + Auxiliary object for managing markings + +Author: + + Leonardo de Moura (leonardo) 2008-02-07. + +Revision History: + +--*/ +#ifndef _MARKER_H_ +#define _MARKER_H_ + +#include"vector.h" + +/** + \brief Keep track of all marked objects. Unmark them when the method + unmark or destructor is invoked. +*/ +template +class marker { + ptr_vector m_to_unmark; +public: + ~marker() { + unmark(); + } + + void mark(T * obj) { + obj->set_mark(true); + m_to_unmark.push_back(obj); + } + + bool is_marked(T * obj) const { + return obj->is_marked(); + } + + void unmark() { + typename ptr_vector::iterator it = m_to_unmark.begin(); + typename ptr_vector::iterator end = m_to_unmark.end(); + for (; it != end; ++it) { + T * obj = *it; + obj->set_mark(false); + } + m_to_unmark.reset(); + } +}; + +#endif /* _MARKER_H_ */ diff --git a/lib/matcher.cpp b/lib/matcher.cpp new file mode 100644 index 000000000..cf0eff6b4 --- /dev/null +++ b/lib/matcher.cpp @@ -0,0 +1,81 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + matcher.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-02. + +Revision History: + +--*/ +#include"matcher.h" + +matcher::matcher(ast_manager & m): + m_manager(m) { +} + +bool matcher::operator()(expr * e1, expr * e2, substitution & s) { + reset(); + m_subst = &s; + m_todo.push_back(expr_pair(e1, e2)); + while (!m_todo.empty()) { + expr_pair const & p = m_todo.back(); + // if (m_cache.contains(p)) { + // m_todo.pop_back(); + // continue; + // } + + if (is_var(p.first)) { + expr_offset r; + if (m_subst->find(to_var(p.first), 0, r)) { + if (r.get_expr() != p.second) + return false; + } + else { + m_subst->insert(to_var(p.first), 0, expr_offset(p.second, 1)); + } + m_todo.pop_back(); + continue; + } + + + if (is_var(p.second)) + return false; + + app * n1 = to_app(p.first); + app * n2 = to_app(p.second); + + if (n1->get_decl() != n2->get_decl()) + return false; + + unsigned num_args1 = n1->get_num_args(); + if (num_args1 != n2->get_num_args()) + return false; + + m_todo.pop_back(); + + if (num_args1 == 0) + continue; + + // m_cache.insert(p); + unsigned j = num_args1; + while (j > 0) { + --j; + m_todo.push_back(expr_pair(n1->get_arg(j), n2->get_arg(j))); + } + } + return true; +} + +void matcher::reset() { + // m_cache.reset(); + m_todo.reset(); +} diff --git a/lib/matcher.h b/lib/matcher.h new file mode 100644 index 000000000..662cfd371 --- /dev/null +++ b/lib/matcher.h @@ -0,0 +1,64 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + matcher.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-02. + +Revision History: + +--*/ +#ifndef _MATCHER_H_ +#define _MATCHER_H_ + +#include"substitution.h" +#include"hashtable.h" + +/** + \brief Functor for matching expressions. +*/ +class matcher { + typedef std::pair expr_pair; + typedef pair_hash, obj_ptr_hash > expr_pair_hash; + typedef hashtable > cache; + + ast_manager & m_manager; + substitution * m_subst; + cache m_cache; + svector m_todo; + + void reset(); + +public: + matcher(ast_manager & m); + + /** + \brief Return true if e2 is an instance of e1. + In case of success (result is true), it will store the substitution that makes e1 equals to e2 into s. + + For example: + 1) e1 = f(g(x), x), e2 = f(g(h(a)), h(a)) + The result is true, and s will contain x -> h(a) + + 2) e1 = f(a, x) e2 = f(x, a) + The result is false. + + 3) e1 = f(x, x) e2 = f(y, a) + The result is false + + 4) e1 = f(x, y) e2 = f(h(z), a) + The result is true, and s contains x->h(z) and y->a + */ + bool operator()(expr * e1, expr * e2, substitution & s); +}; + +#endif /* _MATCHER_H_ */ + diff --git a/lib/max_bv_sharing_tactic.cpp b/lib/max_bv_sharing_tactic.cpp new file mode 100644 index 000000000..462c9f3e2 --- /dev/null +++ b/lib/max_bv_sharing_tactic.cpp @@ -0,0 +1,337 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + max_bv_sharing_tactic.cpp + +Abstract: + + Rewriter for "maximing" the number of shared terms. + The idea is to rewrite AC terms to maximize sharing. + This rewriter is particularly useful for reducing + the number of Adders and Multipliers before "bit-blasting". + +Author: + + Leonardo de Moura (leonardo) 2011-12-29. + +Revision History: + +--*/ +#include"tactical.h" +#include"bv_decl_plugin.h" +#include"rewriter_def.h" +#include"obj_pair_hashtable.h" +#include"ast_lt.h" +#include"cooperate.h" + +class max_bv_sharing_tactic : public tactic { + + struct rw_cfg : public default_rewriter_cfg { + typedef std::pair expr_pair; + typedef obj_pair_hashtable set; + bv_util m_util; + set m_add_apps; + set m_mul_apps; + set m_xor_apps; + set m_or_apps; + unsigned long long m_max_memory; + unsigned m_max_steps; + unsigned m_max_args; + + ast_manager & m() const { return m_util.get_manager(); } + + rw_cfg(ast_manager & m, params_ref const & p): + m_util(m) { + updt_params(p); + } + + void cleanup() { + m_add_apps.finalize(); + m_mul_apps.finalize(); + m_or_apps.finalize(); + m_xor_apps.finalize(); + } + + void updt_params(params_ref const & p) { + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + m_max_steps = p.get_uint(":max-steps", UINT_MAX); + m_max_args = p.get_uint(":max-args", 128); + } + + bool max_steps_exceeded(unsigned num_steps) const { + cooperate("max bv sharing"); + if (memory::get_allocation_size() > m_max_memory) + throw tactic_exception(TACTIC_MAX_MEMORY_MSG); + return num_steps > m_max_steps; + } + + set & f2set(func_decl * f) { + switch (f->get_decl_kind()) { + case OP_BADD: return m_add_apps; + case OP_BMUL: return m_mul_apps; + case OP_BXOR: return m_xor_apps; + case OP_BOR: return m_or_apps; + default: + UNREACHABLE(); + return m_or_apps; // avoid compilation error + } + } + + expr * reuse(set & s, func_decl * f, expr * arg1, expr * arg2) { + if (s.contains(expr_pair(arg1, arg2))) + return m().mk_app(f, arg1, arg2); + if (s.contains(expr_pair(arg2, arg1))) + return m().mk_app(f, arg2, arg1); + return 0; + } + + struct ref_count_lt { + bool operator()(expr * t1, expr * t2) const { + if (t1->get_ref_count() < t2->get_ref_count()) + return true; + return (t1->get_ref_count() == t2->get_ref_count()) && lt(t1, t2); + } + }; + + br_status reduce_ac_app(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + set & s = f2set(f); + + if (num_args == 2) { + if (!m_util.is_numeral(args[0]) && !m_util.is_numeral(args[1])) + s.insert(expr_pair(args[0], args[1])); + return BR_FAILED; + } + + ptr_buffer _args; + bool first = false; + expr * num = 0; + for (unsigned i = 0; i < num_args; i++) { + expr * arg = args[i]; + if (num == 0 && m_util.is_numeral(arg)) { + if (i == 0) first = true; + num = arg; + } + else { + _args.push_back(arg); + } + } + num_args = _args.size(); + + + // std::sort(_args.begin(), _args.end(), ref_count_lt()); + // std::sort(_args.begin(), _args.end(), ast_to_lt()); + + try_to_reuse: + if (num_args > 1 && num_args < m_max_args) { + for (unsigned i = 0; i < num_args - 1; i++) { + for (unsigned j = i + 1; j < num_args; j++) { + expr * r = reuse(s, f, _args[i], _args[j]); + if (r != 0) { + TRACE("bv_sharing_detail", tout << "reusing args: " << i << " " << j << "\n";); + _args[i] = r; + SASSERT(num_args > 1); + for (unsigned w = j; w < num_args - 1; w++) { + _args[w] = _args[w+1]; + } + num_args--; + goto try_to_reuse; + } + } + } + } + + // TODO: + // some benchmarks are more efficiently solved using a tree-like structure (better sharing) + // other benchmarks are more efficiently solved using a chain-like structure (better propagation for arguments "closer to the output"). + // + // One possible solution is to do a global analysis that finds a good order that increases sharing without affecting + // propagation. + // + // Another cheap trick is to create an option, and try both for a small amount of time. +#if 0 + SASSERT(num_args > 0); + if (num_args == 1) { + result = _args[0]; + } + else { + // ref_count_lt is not a total order on expr's + std::stable_sort(_args.c_ptr(), _args.c_ptr() + num_args, ref_count_lt()); + result = m().mk_app(f, _args[0], _args[1]); + for (unsigned i = 2; i < num_args; i++) { + result = m().mk_app(f, result.get(), _args[i]); + } + } + if (num != 0) { + if (first) + result = m().mk_app(f, num, result); + else + result = m().mk_app(f, result, num); + } + return BR_DONE; +#else + // Create "tree-like circuit" + while (true) { + TRACE("bv_sharing_detail", tout << "tree-loop: num_args: " << num_args << "\n";); + unsigned j = 0; + for (unsigned i = 0; i < num_args; i += 2, j++) { + if (i == num_args - 1) { + _args[j] = _args[i]; + } + else { + s.insert(expr_pair(_args[i], _args[i+1])); + _args[j] = m().mk_app(f, _args[i], _args[i+1]); + } + } + num_args = j; + if (num_args == 1) { + if (num == 0) { + result = _args[0]; + } + else { + if (first) + result = m().mk_app(f, num, _args[0]); + else + result = m().mk_app(f, _args[0], num); + } + return BR_DONE; + } + } +#endif + } + + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + if (f->get_family_id() != m_util.get_family_id()) + return BR_FAILED; + switch (f->get_decl_kind()) { + case OP_BADD: + case OP_BMUL: + case OP_BOR: + case OP_BXOR: + result_pr = 0; + return reduce_ac_app(f, num, args, result); + default: + return BR_FAILED; + } + } + }; + + struct rw : public rewriter_tpl { + rw_cfg m_cfg; + + rw(ast_manager & m, params_ref const & p): + rewriter_tpl(m, m.proofs_enabled(), m_cfg), + m_cfg(m, p) { + } + }; + + struct imp { + rw m_rw; + unsigned m_num_steps; + + imp(ast_manager & m, params_ref const & p): + m_rw(m, p) { + } + + ast_manager & m() const { return m_rw.m(); } + + void set_cancel(bool f) { + m_rw.set_cancel(f); + } + + void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + mc = 0; pc = 0; core = 0; + tactic_report report("max-bv-sharing", *g); + bool produce_proofs = g->proofs_enabled(); + + expr_ref new_curr(m()); + proof_ref new_pr(m()); + unsigned size = g->size(); + for (unsigned idx = 0; idx < size; idx++) { + if (g->inconsistent()) + break; + expr * curr = g->form(idx); + m_rw(curr, new_curr, new_pr); + m_num_steps += m_rw.get_num_steps(); + + if (produce_proofs) { + proof * pr = g->pr(idx); + new_pr = m().mk_modus_ponens(pr, new_pr); + } + g->update(idx, new_curr, new_pr, g->dep(idx)); + } + m_rw.cfg().cleanup(); + g->inc_depth(); + result.push_back(g.get()); + TRACE("qe", g->display(tout);); + SASSERT(g->is_well_sorted()); + } + }; + + imp * m_imp; + params_ref m_params; +public: + max_bv_sharing_tactic(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(max_bv_sharing_tactic, m, m_params); + } + + virtual ~max_bv_sharing_tactic() { + dealloc(m_imp); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + m_imp->m_rw.cfg().updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + insert_max_memory(r); + insert_max_steps(r); + r.insert(":max-args", CPK_UINT, + "(default: 128) maximum number of arguments (per application) that will be considered by the greedy (quadratic) heuristic."); + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + (*m_imp)(in, result, mc, pc, core); + } + + virtual void cleanup() { + ast_manager & m = m_imp->m(); + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + } + + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + +tactic * mk_max_bv_sharing_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(max_bv_sharing_tactic, m, p)); +} + diff --git a/lib/max_bv_sharing_tactic.h b/lib/max_bv_sharing_tactic.h new file mode 100644 index 000000000..1c016ebf9 --- /dev/null +++ b/lib/max_bv_sharing_tactic.h @@ -0,0 +1,32 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + max_bv_sharing_tactic.h + +Abstract: + + Rewriter for "maximing" the number of shared terms. + The idea is to rewrite AC terms to maximize sharing. + This rewriter is particularly useful for reducing + the number of Adders and Multipliers before "bit-blasting". + +Author: + + Leonardo de Moura (leonardo) 2011-12-29. + +Revision History: + +--*/ +#ifndef _MAX_BV_SHARING_TACTIC_H_ +#define _MAX_BV_SHARING_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_max_bv_sharing_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif + diff --git a/lib/maximise_ac_sharing.cpp b/lib/maximise_ac_sharing.cpp new file mode 100644 index 000000000..d5cf69a72 --- /dev/null +++ b/lib/maximise_ac_sharing.cpp @@ -0,0 +1,192 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + maximise_ac_sharing.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-10-22. + +Revision History: + +--*/ + +#include"maximise_ac_sharing.h" +#include"ast_pp.h" +#include"basic_simplifier_plugin.h" + +maximise_ac_sharing::ac_plugin::ac_plugin(symbol const & fname, ast_manager & m, maximise_ac_sharing & owner): + simplifier_plugin(fname, m), + m_owner(owner) { +} + +void maximise_ac_sharing::ac_plugin::register_kind(decl_kind k) { + m_kinds.push_back(k); +} + +maximise_ac_sharing::ac_plugin::~ac_plugin() { +} + +bool maximise_ac_sharing::ac_plugin::reduce(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + decl_kind k = f->get_kind(); + if (!f->is_associative()) + return false; + if (num_args <= 2) + return false; + if (std::find(m_kinds.begin(), m_kinds.end(), k) == m_kinds.end()) + return false; + ptr_buffer _args; + expr * numeral = 0; + if (m_owner.is_numeral(args[0])) { + numeral = args[0]; + for (unsigned i = 1; i < num_args; i++) + _args.push_back(args[i]); + num_args--; + } + else { + _args.append(num_args, args); + } + + TRACE("ac_sharing_detail", tout << "before-reuse: num_args: " << num_args << "\n";); + +#define MAX_NUM_ARGS_FOR_OPT 128 + + // Try to reuse already created circuits. + TRACE("ac_sharing_detail", tout << "args: "; for (unsigned i = 0; i < num_args; i++) tout << mk_pp(_args[i], m_manager) << "\n";); + try_to_reuse: + if (num_args > 1 && num_args < MAX_NUM_ARGS_FOR_OPT) { + for (unsigned i = 0; i < num_args - 1; i++) { + for (unsigned j = i + 1; j < num_args; j++) { + if (m_owner.contains(f, _args[i], _args[j])) { + TRACE("ac_sharing_detail", tout << "reusing args: " << i << " " << j << "\n";); + _args[i] = m_manager.mk_app(f, _args[i], _args[j]); + SASSERT(num_args > 1); + for (unsigned w = j; w < num_args - 1; w++) { + _args[w] = _args[w+1]; + } + num_args--; + goto try_to_reuse; + } + } + } + } + + + // Create "tree-like circuit" + while (true) { + TRACE("ac_sharing_detail", tout << "tree-loop: num_args: " << num_args << "\n";); + unsigned j = 0; + for (unsigned i = 0; i < num_args; i += 2, j++) { + if (i == num_args - 1) { + _args[j] = _args[i]; + } + else { + m_owner.insert(f, _args[i], _args[i+1]); + _args[j] = m_manager.mk_app(f, _args[i], _args[i+1]); + } + } + num_args = j; + if (num_args == 1) { + if (numeral == 0) { + result = _args[0]; + } + else { + result = m_manager.mk_app(f, numeral, _args[0]); + } + TRACE("ac_sharing_detail", tout << "result: " << mk_pp(result, m_manager) << "\n";); + return true; + } + } + + UNREACHABLE(); + return false; +} + +bool maximise_ac_sharing::contains(func_decl * f, expr * arg1, expr * arg2) { + entry e(f, arg1, arg2); + return m_cache.contains(&e); +} + +void maximise_ac_sharing::insert(func_decl * f, expr * arg1, expr * arg2) { + entry * e = new (m_region) entry(f, arg1, arg2); + m_entries.push_back(e); + m_cache.insert(e); + m_manager.inc_ref(arg1); + m_manager.inc_ref(arg2); +} + +maximise_ac_sharing::maximise_ac_sharing(ast_manager & m): + m_manager(m), + m_simplifier(m), + m_init(false) { + basic_simplifier_plugin* basic_simp = alloc(basic_simplifier_plugin,m); + m_simplifier.register_plugin(basic_simp); +} + +maximise_ac_sharing::~maximise_ac_sharing() { + restore_entries(0); +} + +void maximise_ac_sharing::operator()(expr * s, expr_ref & r, proof_ref & p) { + init(); + m_simplifier.operator()(s, r, p); +} + +void maximise_ac_sharing::push_scope() { + init(); + m_scopes.push_back(m_entries.size()); + m_region.push_scope(); +} + +void maximise_ac_sharing::pop_scope(unsigned num_scopes) { + SASSERT(num_scopes <= m_scopes.size()); + unsigned new_lvl = m_scopes.size() - num_scopes; + unsigned old_lim = m_scopes[new_lvl]; + restore_entries(old_lim); + m_region.pop_scope(num_scopes); + m_scopes.shrink(new_lvl); +} + +void maximise_ac_sharing::restore_entries(unsigned old_lim) { + unsigned i = m_entries.size(); + while (i != old_lim) { + --i; + entry * e = m_entries[i]; + m_manager.dec_ref(e->m_arg1); + m_manager.dec_ref(e->m_arg2); + } + m_entries.shrink(old_lim); +} + +void maximise_ac_sharing::reset() { + restore_entries(0); + m_entries.reset(); + m_cache.reset(); + m_simplifier.reset(); + m_region.reset(); + m_scopes.reset(); +} + +void maximise_bv_sharing::init_core() { + maximise_ac_sharing::ac_plugin * p = alloc(maximise_ac_sharing::ac_plugin, symbol("bv"), get_manager(), *this); + p->register_kind(OP_BADD); + p->register_kind(OP_BMUL); + p->register_kind(OP_BOR); + p->register_kind(OP_BAND); + register_plugin(p); +} + +bool maximise_bv_sharing::is_numeral(expr * n) const { + return m_util.is_numeral(n); +} + +maximise_bv_sharing::maximise_bv_sharing(ast_manager & m): + maximise_ac_sharing(m), + m_util(m) { +} diff --git a/lib/maximise_ac_sharing.h b/lib/maximise_ac_sharing.h new file mode 100644 index 000000000..bc3b3e67a --- /dev/null +++ b/lib/maximise_ac_sharing.h @@ -0,0 +1,124 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + maximise_ac_sharing.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-10-22. + +Revision History: + +--*/ +#ifndef _MAXIMISE_AC_SHARING_H_ +#define _MAXIMISE_AC_SHARING_H_ + +#include"simplifier.h" +#include"hashtable.h" +#include"bv_decl_plugin.h" +#include"region.h" + +/** + \brief Functor used to maximise the amount of shared terms in an expression. + The idea is to rewrite AC terms to maximise sharing. + Example: + + (f (bvadd a (bvadd b c)) (bvadd a (bvadd b d))) + + is rewritten to: + + (f (bvadd (bvadd a b) c) (bvadd (bvadd a b) d)) + + \warning This class uses an opportunistic heuristic to maximise sharing. + There is no guarantee that the optimal expression will be produced. +*/ +class maximise_ac_sharing { + + struct entry { + func_decl * m_decl; + expr * m_arg1; + expr * m_arg2; + + entry(func_decl * d = 0, expr * arg1 = 0, expr * arg2 = 0):m_decl(d), m_arg1(arg1), m_arg2(arg2) { + SASSERT((d == 0 && arg1 == 0 && arg2 == 0) || (d != 0 && arg1 != 0 && arg2 != 0)); + if (arg1->get_id() > arg2->get_id()) + std::swap(m_arg1, m_arg2); + } + + unsigned hash() const { + unsigned a = m_decl->get_id(); + unsigned b = m_arg1->get_id(); + unsigned c = m_arg2->get_id(); + mix(a,b,c); + return c; + } + + bool operator==(entry const & e) const { + return m_decl == e.m_decl && m_arg1 == e.m_arg1 && m_arg2 == e.m_arg2; + } + }; + + typedef ptr_hashtable, deref_eq > cache; + +protected: + class ac_plugin : public simplifier_plugin { + maximise_ac_sharing & m_owner; + svector m_kinds; //!< kinds to be processed + public: + ac_plugin(symbol const & fname, ast_manager & m, maximise_ac_sharing & owner); + void register_kind(decl_kind k); + virtual ~ac_plugin(); + virtual bool reduce(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + }; + + friend class ac_plugin; + +private: + ast_manager & m_manager; + simplifier m_simplifier; + bool m_init; + region m_region; + cache m_cache; + ptr_vector m_entries; + unsigned_vector m_scopes; + + bool contains(func_decl * f, expr * arg1, expr * arg2); + void insert(func_decl * f, expr * arg1, expr * arg2); + void restore_entries(unsigned old_lim); + void init() { + if (!m_init) { + init_core(); + m_init = true; + } + } +protected: + virtual void init_core() = 0; + virtual bool is_numeral(expr * n) const = 0; + void register_plugin(ac_plugin * p) { m_simplifier.register_plugin(p); } +public: + maximise_ac_sharing(ast_manager & m); + virtual ~maximise_ac_sharing(); + void operator()(expr * s, expr_ref & r, proof_ref & p); + void push_scope(); + void pop_scope(unsigned num_scopes); + void reset(); + ast_manager & get_manager() { return m_manager; } +}; + +class maximise_bv_sharing : public maximise_ac_sharing { + bv_util m_util; +protected: + virtual void init_core(); + virtual bool is_numeral(expr * n) const; +public: + maximise_bv_sharing(ast_manager & m); +}; + +#endif /* _MAXIMISE_AC_SHARING_H_ */ + diff --git a/lib/mem_stat.cpp b/lib/mem_stat.cpp new file mode 100644 index 000000000..c0753dea9 --- /dev/null +++ b/lib/mem_stat.cpp @@ -0,0 +1,56 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + mem_stat.cpp + +Abstract: + + Memory usage statistics + +Author: + + Leonardo de Moura (leonardo) 2006-11-09. + +Revision History: + +--*/ + +#ifdef _WINDOWS +#include +#include +#include + +double get_max_heap_size() { + DWORD processID = GetCurrentProcessId(); + HANDLE hProcess; + PROCESS_MEMORY_COUNTERS pmc; + + hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | + PROCESS_VM_READ, + FALSE, processID); + double result = -1.0; + + if (NULL == hProcess) { + return -1.0; + } + + if (GetProcessMemoryInfo( hProcess, &pmc, sizeof(pmc))) { + result = static_cast(pmc.PeakWorkingSetSize) / static_cast(1024*1024); + } + + CloseHandle( hProcess ); + + return result; +} + +#else + +double get_max_heap_size() { + // not available in this platform + return -1.0; +} + +#endif + diff --git a/lib/mem_stat.h b/lib/mem_stat.h new file mode 100644 index 000000000..84c7d0096 --- /dev/null +++ b/lib/mem_stat.h @@ -0,0 +1,25 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + mem_stat.h + +Abstract: + + Memory usage statistics + +Author: + + Leonardo de Moura (leonardo) 2006-11-09. + +Revision History: + +--*/ +#ifndef _MEM_STAT_H_ +#define _MEM_STAT_H_ + +double get_max_heap_size(); + +#endif /* _MEM_STAT_H_ */ + diff --git a/lib/memory_manager.cpp b/lib/memory_manager.cpp new file mode 100644 index 000000000..3aeb65d3b --- /dev/null +++ b/lib/memory_manager.cpp @@ -0,0 +1,260 @@ +#include +#include"memory_manager.h" +#include"rational.h" +#include"prime_generator.h" +#include"debug.h" + +// If PROFILE_MEMORY is defined, Z3 will display the amount of memory used, and the number of synchronization steps during finalization +// #define PROFILE_MEMORY +void initialize_symbols(); +void finalize_symbols(); + +out_of_memory_error::out_of_memory_error():z3_error(ERR_MEMOUT) { +} + +volatile bool g_memory_out_of_memory = false; +long long g_memory_alloc_size = 0; +long long g_memory_max_size = 0; +long long g_memory_max_used_size = 0; +long long g_memory_watermark = 0; +bool g_exit_when_out_of_memory = false; +char const * g_out_of_memory_msg = "ERROR: out of memory"; + +void memory::exit_when_out_of_memory(bool flag, char const * msg) { + g_exit_when_out_of_memory = flag; + if (flag && msg) + g_out_of_memory_msg = msg; +} + +static void throw_out_of_memory() { + #pragma omp critical (z3_memory_manager) + { + if (!g_memory_out_of_memory) { + g_memory_out_of_memory = true; + } + } + if (g_exit_when_out_of_memory) { + std::cerr << g_out_of_memory_msg << "\n"; + exit(ERR_MEMOUT); + } + else { + throw out_of_memory_error(); + } +} + +#ifdef PROFILE_MEMORY +unsigned g_synch_counter = 0; +class mem_usage_report { +public: + ~mem_usage_report() { + std::cerr << "(memory :max " << g_memory_max_used_size + << " :final " << g_memory_alloc_size + << " :synch " << g_synch_counter << ")" << std::endl; + } +}; +mem_usage_report g_info; +#endif + +void memory::initialize(size_t max_size) { + g_memory_out_of_memory = false; + g_memory_max_size = max_size; + rational::initialize(); + initialize_symbols(); +} + +bool memory::is_out_of_memory() { + bool r = false; + #pragma omp critical (z3_memory_manager) + { + r = g_memory_out_of_memory; + } + return r; +} + +void memory::set_high_watermark(size_t watermak) { + // This method is only safe to invoke at initialization time, that is, before the threads are created. + g_memory_watermark = watermak; +} + +bool memory::above_high_watermark() { + if (g_memory_watermark == 0) + return false; + bool r; + #pragma omp critical (z3_memory_manager) + { + r = g_memory_watermark < g_memory_alloc_size; + } + return r; +} + +void memory::set_max_size(size_t max_size) { + // This method is only safe to invoke at initialization time, that is, before the threads are created. + g_memory_max_size = max_size; +} + +static bool g_finalizing = false; + +void memory::finalize() { + g_finalizing = true; + finalize_debug(); + finalize_trace(); + finalize_symbols(); + rational::finalize(); + prime_iterator::finalize(); +} + + +unsigned long long memory::get_allocation_size() { + long long r; + #pragma omp critical (z3_memory_manager) + { + r = g_memory_alloc_size; + } + if (r < 0) + r = 0; + return r; +} + +unsigned long long memory::get_max_used_memory() { + unsigned long long r; + #pragma omp critical (z3_memory_manager) + { + r = g_memory_max_used_size; + } + return r; +} + +void memory::display_max_usage(std::ostream & os) { + unsigned long long mem = get_max_used_memory(); + os << "max. heap size: " + << static_cast(mem)/static_cast(1024*1024) + << " Mbytes\n"; +} + +void memory::display_i_max_usage(std::ostream & os) { + unsigned long long mem = get_max_used_memory(); + std::cout << "MEMORY " + << static_cast(mem)/static_cast(1024*1024) + << "\n"; +} + +void memory::deallocate(char const * file, int line, void * p) { + deallocate(p); + TRACE_CODE(if (!g_finalizing) TRACE("memory", tout << "dealloc " << std::hex << p << std::dec << " " << file << ":" << line << "\n";);); +} + +void * memory::allocate(char const* file, int line, char const* obj, size_t s) { + void * r = allocate(s); + TRACE("memory", tout << "alloc " << std::hex << r << std::dec << " " << file << ":" << line << " " << obj << " " << s << "\n";); + return r; +} + +#if defined(_WINDOWS) || defined(_USE_THREAD_LOCAL) +// ================================== +// ================================== +// THREAD LOCAL VERSION +// ================================== +// ================================== + + +// We only integrate the local thread counters with the global one +// when the local counter > SYNCH_THRESHOLD +#define SYNCH_THRESHOLD 100000 + +#ifdef _WINDOWS +// Actually this is VS specific instead of Windows specific. +__declspec(thread) long long g_memory_thread_alloc_size = 0; +#else +// GCC style +__thread long long g_memory_thread_alloc_size = 0; +#endif + +static void synchronize_counters(bool allocating) { +#ifdef PROFILE_MEMORY + g_synch_counter++; +#endif + + bool out_of_mem = false; + #pragma omp critical (z3_memory_manager) + { + g_memory_alloc_size += g_memory_thread_alloc_size; + 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) + out_of_mem = true; + } + g_memory_thread_alloc_size = 0; + if (out_of_mem && allocating) { + throw_out_of_memory(); + } +} + +void memory::deallocate(void * p) { + size_t * sz_p = reinterpret_cast(p) - 1; + size_t sz = *sz_p; + void * real_p = reinterpret_cast(sz_p); + g_memory_thread_alloc_size -= sz; + free(real_p); + if (g_memory_thread_alloc_size < -SYNCH_THRESHOLD) { + synchronize_counters(false); + } +} + +void * memory::allocate(size_t s) { + if (s == 0) + return 0; + s = s + sizeof(size_t); // we allocate an extra field! + void * r = malloc(s); + if (r == 0) { + throw_out_of_memory(); + } + *(static_cast(r)) = s; + g_memory_thread_alloc_size += s; + if (g_memory_thread_alloc_size > SYNCH_THRESHOLD) { + synchronize_counters(true); + } + return static_cast(r) + 1; // we return a pointer to the location after the extra field +} + +#else +// ================================== +// ================================== +// NO THREAD LOCAL VERSION +// ================================== +// ================================== +// allocate & deallocate without using thread local storage + +void memory::deallocate(void * p) { + size_t * sz_p = reinterpret_cast(p) - 1; + size_t sz = *sz_p; + void * real_p = reinterpret_cast(sz_p); + #pragma omp critical (z3_memory_manager) + { + g_memory_alloc_size -= sz; + } + free(real_p); +} + +void * memory::allocate(size_t s) { + if (s == 0) + return 0; + s = s + sizeof(size_t); // we allocate an extra field! + bool out_of_mem = false; + #pragma omp critical (z3_memory_manager) + { + g_memory_alloc_size += s; + 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) + out_of_mem = true; + } + if (out_of_mem) + throw_out_of_memory(); + void * r = malloc(s); + if (r == 0) + throw_out_of_memory(); + *(static_cast(r)) = s; + return static_cast(r) + 1; // we return a pointer to the location after the extra field +} + +#endif diff --git a/lib/memory_manager.h b/lib/memory_manager.h new file mode 100644 index 000000000..2cabc70b7 --- /dev/null +++ b/lib/memory_manager.h @@ -0,0 +1,105 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + memory_manager.h + +Abstract: + + Custom memory layer. + +Author: + + Nikolaj Bjorner (nbjorner) 2007-07-24 + +Revision History: + +--*/ +#ifndef _MEMORY_H_ +#define _MEMORY_H_ + +#include +#include +#include"z3_exception.h" + +class out_of_memory_error : public z3_error { +public: + out_of_memory_error(); +}; + +class memory { +public: + static bool is_out_of_memory(); + static void initialize(size_t max_size); + static void set_high_watermark(size_t watermak); + static bool above_high_watermark(); + static void set_max_size(size_t max_size); + static void finalize(); + static void display_max_usage(std::ostream& os); + static void display_i_max_usage(std::ostream& os); + static void deallocate(void* p); + static void* allocate(size_t s); + static void deallocate(char const* file, int line, void* p); + static void* allocate(char const* file, int line, char const* obj, size_t s); + static unsigned long long get_allocation_size(); + static unsigned long long get_max_used_memory(); + // temporary hack to avoid out-of-memory crash in z3.exe + static void exit_when_out_of_memory(bool flag, char const * msg); +}; + + +#if _DEBUG + +#define alloc(T,...) new (memory::allocate(__FILE__,__LINE__,#T, sizeof(T))) T(__VA_ARGS__) +#define dealloc(_ptr_) deallocf(__FILE__,__LINE__,_ptr_) + +template +void deallocf(char const* file, int line, T * ptr) { + if (ptr == 0) return; + ptr->~T(); + memory::deallocate(file, line, ptr); +} + +#else + +#define alloc(T,...) new (memory::allocate(sizeof(T))) T(__VA_ARGS__) + +template +void dealloc(T * ptr) { + if (ptr == 0) return; + ptr->~T(); + memory::deallocate(ptr); +} + +#endif + +template +T * alloc_vect(unsigned sz) { + T * r = static_cast(memory::allocate(sizeof(T) * sz)); + T * curr = r; + for (unsigned i = 0; i < sz; i++, curr++) + new (curr) T(); + return r; +} + +template +void dealloc_vect(T * ptr, unsigned sz) { + if (ptr == 0) return; + T * curr = ptr; + for (unsigned i = 0; i < sz; i++, curr++) + curr->~T(); + memory::deallocate(ptr); +} + +#define alloc_svect(T, sz) static_cast(memory::allocate(sizeof(T) * sz)) + +template +void dealloc_svect(T * ptr) { + if (ptr == 0) return; + memory::deallocate(ptr); +} + + +#endif /* _MEMORY_H_ */ + diff --git a/lib/mip_tactic.cpp b/lib/mip_tactic.cpp new file mode 100644 index 000000000..5408b1f9b --- /dev/null +++ b/lib/mip_tactic.cpp @@ -0,0 +1,135 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + mip_tactic.cpp + +Abstract: + + Tactic for solvig MIP (mixed integer) problem. + This is a temporary tactic. It should be deleted + after theory_arith is upgraded. + +Author: + + Leonardo (leonardo) 2012-02-26 + +Notes: + +--*/ +#include"tactical.h" +#include"smt_solver_exp.h" + +class mip_tactic : public tactic { + struct imp; + ast_manager & m; + params_ref m_params; + statistics m_stats; + scoped_ptr m_solver; + + void init_solver() { + smt::solver_exp * new_solver = alloc(smt::solver_exp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_solver = new_solver; + } + } + +public: + mip_tactic(ast_manager & _m, params_ref const & p): + m(_m), + m_params(p) { + } + + virtual tactic * translate(ast_manager & m) { + return alloc(mip_tactic, m, m_params); + } + + virtual ~mip_tactic() {} + + virtual void updt_params(params_ref const & p) { + m_params = p; + } + virtual void collect_param_descrs(param_descrs & r) { + } + + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + bool produce_models = g->models_enabled(); + mc = 0; pc = 0; core = 0; result.reset(); + tactic_report report("mip", *g); + fail_if_proof_generation("mip", g); + fail_if_unsat_core_generation("mip", g); + + g->elim_redundancies(); + if (g->inconsistent()) { + result.push_back(g.get()); + return; + } + + init_solver(); + m_solver->assert_goal(*g); + + lbool r; + try { + r = m_solver->check(); + } + catch (strategy_exception & ex) { + // solver_exp uses assertion_sets and strategy_exception's + throw tactic_exception(ex.msg()); + } + + m_solver->collect_statistics(m_stats); + + if (r == l_false) { + g->reset(); + g->assert_expr(m.mk_false()); + } + else if (r == l_true) { + g->reset(); + if (produce_models) { + model_ref md; + m_solver->get_model(md); + mc = model2model_converter(md.get()); + } + } + else { + // failed + } + g->inc_depth(); + result.push_back(g.get()); + TRACE("mip", g->display(tout);); + SASSERT(g->is_well_sorted()); + } + + virtual void cleanup() { + if (m_solver) + m_solver->collect_statistics(m_stats); + #pragma omp critical (tactic_cancel) + { + m_solver = 0; + } + } + + virtual void collect_statistics(statistics & st) const { + st.copy(m_stats); + } + + virtual void reset_statistics() { + m_stats.reset(); + } + + virtual void set_cancel(bool f) { + if (m_solver) + m_solver->set_cancel(f); + } +}; + +tactic * mk_mip_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(mip_tactic, m, p)); +} diff --git a/lib/mip_tactic.h b/lib/mip_tactic.h new file mode 100644 index 000000000..06dad2694 --- /dev/null +++ b/lib/mip_tactic.h @@ -0,0 +1,30 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + mip_tactic.h + +Abstract: + + Tactic for solvig MIP (mixed integer) problem. + This is a temporary tactic. It should be deleted + after theory_arith is upgraded. + +Author: + + Leonardo (leonardo) 2012-02-26 + +Notes: + +--*/ +#ifndef _MIP_TACTIC_H_ +#define _MIP_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_mip_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/mk_database.sh b/lib/mk_database.sh new file mode 100644 index 000000000..0d8f986ad --- /dev/null +++ b/lib/mk_database.sh @@ -0,0 +1,3 @@ +#!/bin/sh +dos2unix database.smt +./gen database.smt | awk 'BEGIN { print "char const * g_pattern_database =" } { print "\"" $0 "\\n\"" } END { print ";" }' > database.h diff --git a/lib/mk_simplified_app.cpp b/lib/mk_simplified_app.cpp new file mode 100644 index 000000000..da615e195 --- /dev/null +++ b/lib/mk_simplified_app.cpp @@ -0,0 +1,105 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + mk_simplified_app.cpp + +Abstract: + + Functor for creating new simplified applications + +Author: + + Leonardo (leonardo) 2011-06-06 + +Notes: + +--*/ +#include"mk_simplified_app.h" +#include"bool_rewriter.h" +#include"arith_rewriter.h" +#include"bv_rewriter.h" +#include"datatype_rewriter.h" +#include"array_rewriter.h" +#include"float_rewriter.h" + +struct mk_simplified_app::imp { + ast_manager & m; + bool_rewriter m_b_rw; + arith_rewriter m_a_rw; + bv_rewriter m_bv_rw; + array_rewriter m_ar_rw; + datatype_rewriter m_dt_rw; + float_rewriter m_f_rw; + + imp(ast_manager & _m, params_ref const & p): + m(_m), + m_b_rw(m, p), + m_a_rw(m, p), + m_bv_rw(m, p), + m_ar_rw(m, p), + m_dt_rw(m), + m_f_rw(m, p) { + } + + br_status mk_core(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + family_id fid = f->get_family_id(); + if (fid == null_family_id) + return BR_FAILED; + br_status st = BR_FAILED; + if (fid == m_b_rw.get_fid()) { + decl_kind k = f->get_decl_kind(); + if (k == OP_EQ) { + // theory dispatch for = + SASSERT(num == 2); + family_id s_fid = m.get_sort(args[0])->get_family_id(); + if (s_fid == m_a_rw.get_fid()) + st = m_a_rw.mk_eq_core(args[0], args[1], result); + else if (s_fid == m_bv_rw.get_fid()) + st = m_bv_rw.mk_eq_core(args[0], args[1], result); + else if (s_fid == m_dt_rw.get_fid()) + st = m_dt_rw.mk_eq_core(args[0], args[1], result); + else if (s_fid == m_f_rw.get_fid()) + st = m_f_rw.mk_eq_core(args[0], args[1], 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()) + return m_a_rw.mk_app_core(f, num, args, result); + if (fid == m_bv_rw.get_fid()) + return m_bv_rw.mk_app_core(f, num, args, result); + if (fid == m_ar_rw.get_fid()) + return m_ar_rw.mk_app_core(f, num, args, result); + if (fid == m_dt_rw.get_fid()) + return m_dt_rw.mk_app_core(f, num, args, result); + if (fid == m_f_rw.get_fid()) + return m_f_rw.mk_app_core(f, num, args, result); + return BR_FAILED; + } +}; + + +mk_simplified_app::mk_simplified_app(ast_manager & m, params_ref const & p): + m_imp(alloc(imp, m, p)) { +} + +mk_simplified_app::~mk_simplified_app() { + dealloc(m_imp); +} + +br_status mk_simplified_app::mk_core(func_decl * decl, unsigned num, expr * const * args, expr_ref & result) { + return m_imp->mk_core(decl, num, args, result); +} + +void mk_simplified_app::operator()(func_decl * decl, unsigned num, expr * const * args, expr_ref & result) { + result = 0; + mk_core(decl, num, args, result); + if (!result) + result = m_imp->m.mk_app(decl, num, args); + // TODO: if the result of mk_core is different from BR_FAILED, then the + // result is not really simplified. +} diff --git a/lib/mk_simplified_app.h b/lib/mk_simplified_app.h new file mode 100644 index 000000000..ca458cc16 --- /dev/null +++ b/lib/mk_simplified_app.h @@ -0,0 +1,37 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + mk_simplified_app.h + +Abstract: + + Functor for creating new simplified applications + +Author: + + Leonardo (leonardo) 2011-06-06 + +Notes: + +--*/ +#ifndef _MK_SIMPLIFIED_APP_H_ +#define _MK_SIMPLIFIED_APP_H_ + +#include"ast.h" +#include"params.h" +#include"rewriter_types.h" + +class mk_simplified_app { + struct imp; + imp * m_imp; +public: + mk_simplified_app(ast_manager & m, params_ref const & p = params_ref()); + ~mk_simplified_app(); + + br_status mk_core(func_decl * decl, unsigned num, expr * const * args, expr_ref & result); + void operator()(func_decl * decl, unsigned num, expr * const * args, expr_ref & result); +}; + +#endif diff --git a/lib/model.cpp b/lib/model.cpp new file mode 100644 index 000000000..9f2569ce5 --- /dev/null +++ b/lib/model.cpp @@ -0,0 +1,234 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + model.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2011-04-30. + +Revision History: + +--*/ +#include"model.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"var_subst.h" +#include"front_end_params.h" +#include"array_decl_plugin.h" +#include"well_sorted.h" +#include"used_symbols.h" +#include"model_evaluator.h" + +model::model(ast_manager & m): + model_core(m) { +} + +model::~model() { + decl2expr::iterator it1 = m_interp.begin(); + decl2expr::iterator end1 = m_interp.end(); + for (; it1 != end1; ++it1) { + m_manager.dec_ref(it1->m_key); + m_manager.dec_ref(it1->m_value); + } + + decl2finterp::iterator it2 = m_finterp.begin(); + decl2finterp::iterator end2 = m_finterp.end(); + for (; it2 != end2; ++it2) { + m_manager.dec_ref(it2->m_key); + dealloc(it2->m_value); + } + + sort2universe::iterator it3 = m_usort2universe.begin(); + sort2universe::iterator end3 = m_usort2universe.end(); + for (; it3 != end3; ++it3) { + m_manager.dec_ref(it3->m_key); + m_manager.dec_array_ref(it3->m_value->size(), it3->m_value->c_ptr()); + dealloc(it3->m_value); + } +} + +void model::register_decl(func_decl * d, expr * v) { + SASSERT(d->get_arity() == 0); + decl2expr::obj_map_entry * entry = m_interp.insert_if_not_there2(d, 0); + if (entry->get_data().m_value == 0) { + // new entry + m_decls.push_back(d); + m_const_decls.push_back(d); + m_manager.inc_ref(d); + m_manager.inc_ref(v); + entry->get_data().m_value = v; + } + else { + // replacing entry + m_manager.inc_ref(v); + m_manager.dec_ref(entry->get_data().m_value); + entry->get_data().m_value = v; + } +} + +void model::register_decl(func_decl * d, func_interp * fi) { + SASSERT(d->get_arity() > 0); + decl2finterp::obj_map_entry * entry = m_finterp.insert_if_not_there2(d, 0); + if (entry->get_data().m_value == 0) { + // new entry + m_decls.push_back(d); + m_func_decls.push_back(d); + m_manager.inc_ref(d); + entry->get_data().m_value = fi; + } + else { + // replacing entry + if (fi != entry->get_data().m_value) + dealloc(entry->get_data().m_value); + entry->get_data().m_value = fi; + } +} + + +void model::copy_const_interps(model const & source) { + decl2expr::iterator it1 = source.m_interp.begin(); + decl2expr::iterator end1 = source.m_interp.end(); + for (; it1 != end1; ++it1) { + register_decl(it1->m_key, it1->m_value); + } +} + +void model::copy_func_interps(model const & source) { + decl2finterp::iterator it2 = source.m_finterp.begin(); + decl2finterp::iterator end2 = source.m_finterp.end(); + for (; it2 != end2; ++it2) { + register_decl(it2->m_key, it2->m_value->copy()); + } +} + +void model::copy_usort_interps(model const & source) { + sort2universe::iterator it3 = source.m_usort2universe.begin(); + sort2universe::iterator end3 = source.m_usort2universe.end(); + for (; it3 != end3; ++it3) { + register_usort(it3->m_key, it3->m_value->size(), it3->m_value->c_ptr()); + } +} + +model * model::copy() const { + model * m = alloc(model, m_manager); + + m->copy_const_interps(*this); + m->copy_func_interps(*this); + m->copy_usort_interps(*this); + + return m; +} + +// Remark: eval is for backward compatibility. We should use model_evaluator. +bool model::eval(expr * e, expr_ref & result, bool model_completion) { + model_evaluator ev(*this); + ev.set_model_completion(model_completion); + try { + ev(e, result); + return true; + } + catch (model_evaluator_exception &) { + return false; + } +} + +struct model::value_proc : public some_value_proc { + model & m_model; + value_proc(model & m):m_model(m) {} + virtual expr * operator()(sort * s) { + ptr_vector * u = 0; + if (m_model.m_usort2universe.find(s, u)) { + if (u->size() > 0) + return u->get(0); + } + return 0; + } +}; + +expr * model::get_some_value(sort * s) { + value_proc p(*this); + return m_manager.get_some_value(s, &p); +} + +ptr_vector const & model::get_universe(sort * s) const { + ptr_vector * u = 0; + m_usort2universe.find(s, u); + SASSERT(u != 0); + return *u; +} + +bool model::has_uninterpreted_sort(sort * s) const { + ptr_vector * u = 0; + m_usort2universe.find(s, u); + return u != 0; +} + +unsigned model::get_num_uninterpreted_sorts() const { + return m_usorts.size(); +} + +sort * model::get_uninterpreted_sort(unsigned idx) const { + return m_usorts[idx]; +} + +void model::register_usort(sort * s, unsigned usize, expr * const * universe) { + SASSERT(m_manager.is_uninterp(s)); + sort2universe::obj_map_entry * entry = m_usort2universe.insert_if_not_there2(s, 0); + m_manager.inc_array_ref(usize, universe); + if (entry->get_data().m_value == 0) { + // new entry + m_usorts.push_back(s); + m_manager.inc_ref(s); + ptr_vector * new_u = alloc(ptr_vector); + new_u->append(usize, universe); + entry->get_data().m_value = new_u; + } + else { + // updating + ptr_vector * u = entry->get_data().m_value; + SASSERT(u); + m_manager.dec_array_ref(u->size(), u->c_ptr()); + u->append(usize, universe); + } +} + +model * model::translate(ast_translation & translator) const { + model * res = alloc(model, translator.to()); + + // Translate const interps + decl2expr::iterator it1 = m_interp.begin(); + decl2expr::iterator end1 = m_interp.end(); + for (; it1 != end1; ++it1) { + res->register_decl(translator(it1->m_key), translator(it1->m_value)); + } + + // Translate func interps + decl2finterp::iterator it2 = m_finterp.begin(); + decl2finterp::iterator end2 = m_finterp.end(); + for (; it2 != end2; ++it2) { + func_interp * fi = it2->m_value; + res->register_decl(translator(it2->m_key), fi->translate(translator)); + } + + // Translate usort interps + sort2universe::iterator it3 = m_usort2universe.begin(); + sort2universe::iterator end3 = m_usort2universe.end(); + for (; it3 != end3; ++it3) { + ptr_vector new_universe; + for (unsigned i=0; im_value->size(); i++) + new_universe.push_back(translator(it3->m_value->get(i))); + res->register_usort(translator(it3->m_key), + new_universe.size(), + new_universe.c_ptr()); + } + + return res; +} + diff --git a/lib/model.h b/lib/model.h new file mode 100644 index 000000000..70c8723fa --- /dev/null +++ b/lib/model.h @@ -0,0 +1,69 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + model.h + +Abstract: + + Model for satisfiable formulas + +Author: + + Leonardo de Moura (leonardo) 2011-04-30. + +Revision History: + +--*/ +#ifndef _MODEL_H_ +#define _MODEL_H_ + +#include"model_core.h" +#include"ref.h" +#include"ast_translation.h" + +class model : public model_core { +protected: + typedef obj_map*> sort2universe; + + ptr_vector m_usorts; + sort2universe m_usort2universe; + struct value_proc; + +public: + model(ast_manager & m); + virtual ~model(); + + void copy_func_interps(model const & source); + void copy_const_interps(model const & source); + void copy_usort_interps(model const & source); + + model * copy() const; + + bool eval(func_decl * f, expr_ref & r) const { return model_core::eval(f, r); } + bool eval(expr * e, expr_ref & result, bool model_completion = false); + + expr * get_some_value(sort * s); + + virtual ptr_vector const & get_universe(sort * s) const; + virtual unsigned get_num_uninterpreted_sorts() const; + virtual sort * get_uninterpreted_sort(unsigned idx) const; + bool has_uninterpreted_sort(sort * s) const; + + // + // Primitives for building models + // + void register_decl(func_decl * d, expr * v); + void register_decl(func_decl * f, func_interp * fi); + void register_usort(sort * s, unsigned usize, expr * const * universe); + + // Model translation + // + model * translate(ast_translation & translator) const; +}; + +typedef ref model_ref; + +#endif /* _MODEL_H_ */ + diff --git a/lib/model2expr.cpp b/lib/model2expr.cpp new file mode 100644 index 000000000..05aea6ee7 --- /dev/null +++ b/lib/model2expr.cpp @@ -0,0 +1,157 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + model2expr.cpp + +Abstract: + + Convert model to logical formula that forces it. + +Author: + + Nikolaj Bjorner (nbjorner) 2012-09-17 + +Revision History: + +--*/ +#include "model2expr.h" +#include "for_each_ast.h" +#include "bool_rewriter.h" +#include "var_subst.h" + +struct for_each_symbol_proc { + symbol_set& m_symbols; + for_each_symbol_proc(symbol_set& syms): m_symbols(syms) {} + + void operator()(func_decl* n) { + m_symbols.insert(n->get_name()); + } + + void operator()(quantifier* n) { + for (unsigned i = 0; i < n->get_num_decls(); ++i) { + m_symbols.insert(n->get_decl_name(i)); + } + } + + void operator()(var* n) {} + void operator()(sort* s) {} + void operator()(app* a) {} +}; + +void mk_fresh_name::add(ast* a) { + for_each_symbol_proc proc(m_symbols); + for_each_ast(proc, a); +} + +symbol mk_fresh_name::next() { + for (; ; ++m_num) { + for(; m_char <= 'Z'; ++m_char) { + std::stringstream _name; + _name << m_char; + if (m_num > 0) _name << m_num; + ++m_char; + symbol name(_name.str().c_str()); + if (!m_symbols.contains(name)) { + return name; + } + } + m_char = 'A'; + } +} + +static void mk_entry_cond(unsigned arity, func_entry const* entry, expr_ref& result) { + ast_manager& m = result.get_manager(); + expr_ref_vector conjs(m); + for (unsigned i = 0; i < arity; ++i) { + expr* e = entry->get_arg(i); + if (is_var(e) && to_var(e)->get_idx() == i) { + // no-op + } + else { + conjs.push_back(m.mk_eq(m.mk_var(i, m.get_sort(e)), e)); + } + } + bool_rewriter(m).mk_and(conjs.size(), conjs.c_ptr(), result); +} + +void model2expr(model& md, expr_ref& result) { + ast_manager& m = result.get_manager(); + + expr_ref_vector conjs(m); + expr_ref tmp(m); + unsigned sz; + + sz = md.get_num_constants(); + for (unsigned i = 0; i < sz; ++i) { + func_decl* c = md.get_constant(i); + expr* v = md.get_const_interp(c); + conjs.push_back(m.mk_eq(m.mk_const(c), v)); + } + + sz = md.get_num_functions(); + for (unsigned i = 0; i < sz; ++i) { + + func_decl* f = md.get_function(i); + func_interp* fi = md.get_func_interp(f); + + // Register names. + mk_fresh_name fresh_name; + unsigned num_entries = fi->num_entries(); + fresh_name.add(f); + for (unsigned j = 0; j < num_entries; ++j) { + func_entry const* entry = fi->get_entry(j); + fresh_name.add(entry->get_result()); + for (unsigned k = 0; k < f->get_arity(); ++k) { + fresh_name.add(entry->get_arg(k)); + } + } + + expr_ref func(m), cond(m); + expr_ref_vector args(m); + for (unsigned j = 0; j < f->get_arity(); ++j) { + args.push_back(m.mk_var(j, f->get_domain(j))); + } + func = m.mk_app(f, args.size(), args.c_ptr()); + if (fi->is_partial()) { + if (num_entries == 0) { + continue; + } + mk_entry_cond(f->get_arity(), fi->get_entry(num_entries-1), cond); + tmp = m.mk_implies(cond, m.mk_eq(func, fi->get_entry(num_entries-1)->get_result())); + for (unsigned j = num_entries-1; j > 0; ) { + --j; + mk_entry_cond(f->get_arity(), fi->get_entry(j), cond); + tmp = m.mk_ite(cond, m.mk_eq(func, fi->get_entry(j)->get_result()), tmp); + } + } + else { + fresh_name.add(fi->get_else()); + tmp = fi->get_else(); + for (unsigned j = num_entries; j > 0; ) { + --j; + mk_entry_cond(f->get_arity(), fi->get_entry(j), cond); + tmp = m.mk_ite(cond, fi->get_entry(j)->get_result(), tmp); + } + tmp = m.mk_eq(func, tmp); + } + ptr_vector sorts; + expr_ref_vector rev_vars(m); + svector names; + unsigned sz = f->get_arity(); + for (unsigned j = 0; j < sz; ++j) { + sorts.push_back(f->get_domain(j)); + rev_vars.push_back(m.mk_var(sz-j-1, f->get_domain(j))); + names.push_back(fresh_name.next()); + } + if (f->get_arity() > 0) { + var_subst vs(m, false); + vs(tmp, rev_vars.size(), rev_vars.c_ptr(), tmp); + tmp = m.mk_forall(sorts.size(), sorts.c_ptr(), names.c_ptr(), tmp); + } + conjs.push_back(tmp); + } + + bool_rewriter(m).mk_and(conjs.size(), conjs.c_ptr(), result); +} diff --git a/lib/model2expr.h b/lib/model2expr.h new file mode 100644 index 000000000..9a63d6b8c --- /dev/null +++ b/lib/model2expr.h @@ -0,0 +1,43 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + model2expr.h + +Abstract: + + Convert model to logical formula that forces it. + +Author: + + Nikolaj Bjorner (nbjorner) 2012-09-17 + +Revision History: + +--*/ +#ifndef _MODEL2EXPR_H_ +#define _MODEL2EXPR_H_ + +#include"model.h" + +void model2expr(model& m, expr_ref& result); + +inline void model2expr(model_ref& md, expr_ref& result) { model2expr(*md.get(), result); } + +// TODO: move +typedef hashtable symbol_set; + +class mk_fresh_name { + symbol_set m_symbols; + char m_char; + unsigned m_num; +public: + mk_fresh_name(): m_char('A'), m_num(0) {} + void add(ast* a); + symbol next(); +}; + + +#endif /* _MODEL2EXPR_H_ */ + diff --git a/lib/model_converter.cpp b/lib/model_converter.cpp new file mode 100644 index 000000000..7ac3e898a --- /dev/null +++ b/lib/model_converter.cpp @@ -0,0 +1,150 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + model_converter.h + +Abstract: + + Abstract interface for converting models. + +Author: + + Leonardo (leonardo) 2011-04-21 + +Notes: + +--*/ +#include"model_converter.h" +#include"model_v2_pp.h" + +class concat_model_converter : public concat_converter { +public: + concat_model_converter(model_converter * mc1, model_converter * mc2):concat_converter(mc1, mc2) {} + + virtual void operator()(model_ref & m) { + this->m_c2->operator()(m); + this->m_c1->operator()(m); + } + + virtual void operator()(model_ref & m, unsigned goal_idx) { + this->m_c2->operator()(m, goal_idx); + this->m_c1->operator()(m, 0); + } + + virtual char const * get_name() const { return "concat-model-converter"; } + + virtual model_converter * translate(ast_translation & translator) { + return this->translate_core(translator); + } +}; + +model_converter * concat(model_converter * mc1, model_converter * mc2) { + if (mc1 == 0) + return mc2; + if (mc2 == 0) + return mc1; + return alloc(concat_model_converter, mc1, mc2); +} + +class concat_star_model_converter : public concat_star_converter { +public: + concat_star_model_converter(model_converter * mc1, unsigned num, model_converter * const * mc2s, unsigned * szs): + concat_star_converter(mc1, num, mc2s, szs) { + } + + virtual void operator()(model_ref & m) { + // TODO: delete method after conversion is complete + UNREACHABLE(); + } + + virtual void operator()(model_ref & m, unsigned goal_idx) { + unsigned num = this->m_c2s.size(); + for (unsigned i = 0; i < num; i++) { + if (goal_idx < this->m_szs[i]) { + // found the model converter that should be used + model_converter * c2 = this->m_c2s[i]; + if (c2) + c2->operator()(m, goal_idx); + if (m_c1) + this->m_c1->operator()(m, i); + return; + } + // invalid goal + goal_idx -= this->m_szs[i]; + } + UNREACHABLE(); + } + + virtual char const * get_name() const { return "concat-star-model-converter"; } + + virtual model_converter * translate(ast_translation & translator) { + return this->translate_core(translator); + } +}; + +model_converter * concat(model_converter * mc1, unsigned num, model_converter * const * mc2s, unsigned * szs) { + SASSERT(num > 0); + if (num == 1) + return concat(mc1, mc2s[0]); + unsigned i; + for (i = 0; i < num; i++) { + if (mc2s[i] != 0) + break; + } + if (i == num) { + // all mc2s are 0 + return mc1; + } + return alloc(concat_star_model_converter, mc1, num, mc2s, szs); +} + +class model2mc : public model_converter { + model_ref m_model; +public: + model2mc(model * m):m_model(m) {} + virtual ~model2mc() {} + + virtual void operator()(model_ref & m) { + m = m_model; + } + + virtual void operator()(model_ref & m, unsigned goal_idx) { + m = m_model; + } + + virtual void cancel() { + } + + virtual void display(std::ostream & out) { + out << "(model->model-converter-wrapper\n"; + model_v2_pp(out, *m_model); + out << ")\n"; + } + + virtual model_converter * translate(ast_translation & translator) { + model * m = m_model->translate(translator); + return alloc(model2mc, m); + } +}; + +model_converter * model2model_converter(model * m) { + if (m == 0) + return 0; + return alloc(model2mc, m); +} + +void model_converter2model(ast_manager & mng, model_converter * mc, model_ref & m) { + if (mc) { + m = alloc(model, mng); + (*mc)(m, 0); + } +} + +void apply(model_converter_ref & mc, model_ref & m, unsigned gidx) { + if (mc) { + (*mc)(m, gidx); + } +} + diff --git a/lib/model_converter.h b/lib/model_converter.h new file mode 100644 index 000000000..14961daf1 --- /dev/null +++ b/lib/model_converter.h @@ -0,0 +1,59 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + model_converter.h + +Abstract: + + Abstract interface for converting models. + +Author: + + Leonardo (leonardo) 2011-04-21 + +Notes: + +--*/ +#ifndef _MODEL_CONVERTER_H_ +#define _MODEL_CONVERTER_H_ + +#include"model.h" +#include"converter.h" +#include"ref.h" + +class model_converter : public converter { +public: + virtual void operator()(model_ref & m) {} // TODO: delete + + virtual void operator()(model_ref & m, unsigned goal_idx) { + // TODO: make it virtual after the transition to goal/tactic/tactical is complete + SASSERT(goal_idx == 0); + operator()(m); + } + + virtual model_converter * translate(ast_translation & translator) = 0; +}; + +typedef ref model_converter_ref; + +model_converter * concat(model_converter * mc1, model_converter * mc2); + +/** + \brief \c mc1 is the model converter for a sequence of subgoals of size \c num. + Given an i in [0, num), mc2s[i] is the model converter for subgoal i, + and num_subgoals[i] is the number of subgoals of subgoals[i]. +*/ +model_converter * concat(model_converter * mc1, unsigned num, model_converter * const * mc2s, unsigned * num_subgoals); + +model_converter * model2model_converter(model * m); + +void model_converter2model(ast_manager & mng, model_converter * mc, model_ref & m); + +void apply(model_converter_ref & mc, model_ref & m, unsigned gidx); + +typedef sref_vector model_converter_ref_vector; +typedef sref_buffer model_converter_ref_buffer; + +#endif diff --git a/lib/model_core.cpp b/lib/model_core.cpp new file mode 100644 index 000000000..4351713db --- /dev/null +++ b/lib/model_core.cpp @@ -0,0 +1,34 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + model_core.cpp + +Abstract: + + Base class for models. + +Author: + + Leonardo de Moura (leonardo) 2011-04-30. + +Revision History: + +--*/ +#include"model_core.h" + +bool model_core::eval(func_decl* f, expr_ref & r) const { + if (f->get_arity() == 0) { + r = get_const_interp(f); + return r != 0; + } + else { + func_interp * fi = get_func_interp(f); + if (fi != 0) { + r = fi->get_interp(); + return r != 0; + } + return false; + } +} diff --git a/lib/model_core.h b/lib/model_core.h new file mode 100644 index 000000000..72d7a321b --- /dev/null +++ b/lib/model_core.h @@ -0,0 +1,73 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + model_core.h + +Abstract: + + Base class for models. + +Author: + + Leonardo de Moura (leonardo) 2011-04-30. + +Revision History: + +--*/ +#ifndef _MODEL_CORE_H +#define _MODEL_CORE_H + +#include"ast.h" +#include"obj_hashtable.h" +#include"func_interp.h" + +class model_core { +protected: + typedef obj_map decl2expr; + typedef obj_map decl2finterp; + ast_manager & m_manager; + unsigned m_ref_count; + decl2expr m_interp; //!< interpretation for uninterpreted constants + decl2finterp m_finterp; //!< interpretation for uninterpreted functions + ptr_vector m_decls; //!< domain of m_interp + ptr_vector m_const_decls; + ptr_vector m_func_decls; + +public: + model_core(ast_manager & m):m_manager(m), m_ref_count(0) {} + virtual ~model_core() {} + + ast_manager & get_manager() const { return m_manager; } + + unsigned get_num_decls() const { return m_decls.size(); } + func_decl * get_decl(unsigned i) const { return m_decls[i]; } + bool has_interpretation(func_decl * d) const { return m_interp.contains(d) || m_finterp.contains(d); } + expr * get_const_interp(func_decl * d) const { expr * v; return m_interp.find(d, v) ? v : 0; } + func_interp * get_func_interp(func_decl * d) const { func_interp * fi; return m_finterp.find(d, fi) ? fi : 0; } + + bool eval(func_decl * f, expr_ref & r) const; + + unsigned get_num_constants() const { return m_const_decls.size(); } + unsigned get_num_functions() const { return m_func_decls.size(); } + func_decl * get_constant(unsigned i) const { return m_const_decls[i]; } + func_decl * get_function(unsigned i) const { return m_func_decls[i]; } + + virtual ptr_vector const & get_universe(sort * s) const = 0; + virtual unsigned get_num_uninterpreted_sorts() const = 0; + virtual sort * get_uninterpreted_sort(unsigned idx) const = 0; + + // + // Reference counting + // + void inc_ref() { ++m_ref_count; } + void dec_ref() { + --m_ref_count; + if (m_ref_count == 0) { + dealloc(this); + } + } +}; + +#endif diff --git a/lib/model_evaluator.cpp b/lib/model_evaluator.cpp new file mode 100644 index 000000000..6519323ef --- /dev/null +++ b/lib/model_evaluator.cpp @@ -0,0 +1,274 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + model_evaluator.cpp + +Abstract: + + Evaluate expressions in a given model. + +Author: + + Leonardo de Moura (leonardo) 2011-04-30. + +Revision History: + +--*/ +#include"model.h" +#include"rewriter_types.h" +#include"model_evaluator.h" +#include"bool_rewriter.h" +#include"arith_rewriter.h" +#include"bv_rewriter.h" +#include"datatype_rewriter.h" +#include"array_rewriter.h" +#include"float_rewriter.h" +#include"rewriter_def.h" +#include"cooperate.h" + +struct evaluator_cfg : public default_rewriter_cfg { + model & m_model; + bool_rewriter m_b_rw; + arith_rewriter m_a_rw; + bv_rewriter m_bv_rw; + array_rewriter m_ar_rw; + datatype_rewriter m_dt_rw; + float_rewriter m_f_rw; + unsigned long long m_max_memory; + unsigned m_max_steps; + bool m_model_completion; + bool m_cache; + + evaluator_cfg(ast_manager & m, model & md, params_ref const & p): + m_model(md), + m_b_rw(m), + // We must allow customers to set parameters for arithmetic rewriter/evaluator. + // In particular, the maximum degree of algebraic numbers that will be evaluated. + m_a_rw(m, p), + m_bv_rw(m), + // See comment above. We want to allow customers to set :sort-store + m_ar_rw(m, p), + m_dt_rw(m), + m_f_rw(m) { + m_b_rw.set_flat(false); + m_a_rw.set_flat(false); + m_bv_rw.set_flat(false); + m_bv_rw.set_mkbv2num(true); + updt_params(p); + } + + void updt_params(params_ref const & p) { + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + m_max_steps = p.get_uint(":max-steps", UINT_MAX); + m_model_completion = p.get_bool(":model-completion", false); + m_cache = p.get_bool(":cache", true); + } + + ast_manager & m() const { return m_model.get_manager(); } + + // Try to use the entries to quickly evaluate the fi + bool eval_fi(func_interp * fi, unsigned num, expr * const * args, expr_ref & result) { + if (fi->num_entries() == 0) + return false; // let get_macro handle it. + + SASSERT(fi->get_arity() == num); + + bool actuals_are_values = true; + + for (unsigned i = 0; actuals_are_values && i < num; i++) { + actuals_are_values = m().is_value(args[i]); + } + + if (!actuals_are_values) + return false; // let get_macro handle it + + func_entry * entry = fi->get_entry(args); + if (entry != 0) { + result = entry->get_result(); + return true; + } + + return false; + } + + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + result_pr = 0; + family_id fid = f->get_family_id(); + if (fid == null_family_id) { + if (num == 0) { + expr * val = m_model.get_const_interp(f); + if (val != 0) { + result = val; + return BR_DONE; + } + + if (m_model_completion) { + sort * s = f->get_range(); + expr * val = m_model.get_some_value(s); + m_model.register_decl(f, val); + result = val; + return BR_DONE; + } + return BR_FAILED; + } + SASSERT(num > 0); + func_interp * fi = m_model.get_func_interp(f); + if (fi != 0 && eval_fi(fi, num, args, result)) { + TRACE("model_evaluator", tout << "reduce_app " << f->get_name() << "\n"; + for (unsigned i = 0; i < num; i++) tout << mk_ismt2_pp(args[i], m()) << "\n"; + tout << "---->\n" << mk_ismt2_pp(result, m()) << "\n";); + return BR_DONE; + } + } + + if (fid == m_b_rw.get_fid()) { + decl_kind k = f->get_decl_kind(); + if (k == OP_EQ) { + // theory dispatch for = + SASSERT(num == 2); + family_id s_fid = m().get_sort(args[0])->get_family_id(); + br_status st = BR_FAILED; + if (s_fid == m_a_rw.get_fid()) + st = m_a_rw.mk_eq_core(args[0], args[1], result); + else if (s_fid == m_bv_rw.get_fid()) + st = m_bv_rw.mk_eq_core(args[0], args[1], result); + else if (s_fid == m_dt_rw.get_fid()) + st = m_dt_rw.mk_eq_core(args[0], args[1], result); + else if (s_fid == m_f_rw.get_fid()) + st = m_f_rw.mk_eq_core(args[0], args[1], 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()) + return m_a_rw.mk_app_core(f, num, args, result); + if (fid == m_bv_rw.get_fid()) + return m_bv_rw.mk_app_core(f, num, args, result); + if (fid == m_ar_rw.get_fid()) + return m_ar_rw.mk_app_core(f, num, args, result); + if (fid == m_dt_rw.get_fid()) + return m_dt_rw.mk_app_core(f, num, args, result); + if (fid == m_f_rw.get_fid()) + return m_f_rw.mk_app_core(f, num, args, result); + return BR_FAILED; + } + + bool get_macro(func_decl * f, expr * & def, quantifier * & q, proof * & def_pr) { + if (f->get_family_id() == null_family_id) { + func_interp * fi = m_model.get_func_interp(f); + + if (fi != 0) { + if (fi->is_partial()) { + if (m_model_completion) { + sort * s = f->get_range(); + expr * val = m_model.get_some_value(s); + fi->set_else(val); + } + else { + return false; + } + } + + def = fi->get_interp(); + SASSERT(def != 0); + return true; + } + + if (m_model_completion) { + sort * s = f->get_range(); + expr * val = m_model.get_some_value(s); + func_interp * new_fi = alloc(func_interp, m(), f->get_arity()); + new_fi->set_else(val); + m_model.register_decl(f, new_fi); + def = val; + return true; + } + } + return false; + } + + bool max_steps_exceeded(unsigned num_steps) const { + cooperate("model evaluator"); + if (memory::get_allocation_size() > m_max_memory) + throw rewriter_exception(TACTIC_MAX_MEMORY_MSG); + return num_steps > m_max_steps; + } + + bool cache_results() const { return m_cache; } + +}; + +template class rewriter_tpl; + +struct model_evaluator::imp : public rewriter_tpl { + evaluator_cfg m_cfg; + imp(model & md, params_ref const & p): + rewriter_tpl(md.get_manager(), + false, // no proofs for evaluator + m_cfg), + m_cfg(md.get_manager(), md, p) { + } +}; + +model_evaluator::model_evaluator(model & md, params_ref const & p) { + m_imp = alloc(imp, md, p); +} + +ast_manager & model_evaluator::m() const { + return m_imp->m(); +} + +model_evaluator::~model_evaluator() { + dealloc(m_imp); +} + +void model_evaluator::updt_params(params_ref const & p) { + m_imp->cfg().updt_params(p); +} + +void model_evaluator::get_param_descrs(param_descrs & r) { + insert_max_memory(r); + insert_max_steps(r); + r.insert(":model-completion", CPK_BOOL, "(default: false) assigns an interpretation to symbols that are not intepreted by the model."); + r.insert(":cache", CPK_BOOL, "(default: true) cache intermediate results."); +} + +void model_evaluator::set_model_completion(bool f) { + m_imp->cfg().m_model_completion = f; +} + +unsigned model_evaluator::get_num_steps() const { + return m_imp->get_num_steps(); +} + +void model_evaluator::cancel() { + #pragma omp critical (model_evaluator) + { + m_imp->cancel(); + } +} + +void model_evaluator::cleanup(params_ref const & p) { + model & md = m_imp->cfg().m_model; + #pragma omp critical (model_evaluator) + { + dealloc(m_imp); + m_imp = alloc(imp, md, p); + } +} + +void model_evaluator::reset(params_ref const & p) { + m_imp->reset(); + updt_params(p); +} + +void model_evaluator::operator()(expr * t, expr_ref & result) { + TRACE("model_evaluator", tout << mk_ismt2_pp(t, m()) << "\n";); + m_imp->operator()(t, result); +} + + + diff --git a/lib/model_evaluator.h b/lib/model_evaluator.h new file mode 100644 index 000000000..aad06739b --- /dev/null +++ b/lib/model_evaluator.h @@ -0,0 +1,51 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + model_evaluator.h + +Abstract: + + Evaluate expressions in a given model. + +Author: + + Leonardo de Moura (leonardo) 2011-04-30. + +Revision History: + +--*/ +#ifndef _MODEL_EVALUATOR_H_ +#define _MODEL_EVALUATOR_H_ + +#include"ast.h" +#include"rewriter_types.h" +#include"params.h" +class model; + +typedef rewriter_exception model_evaluator_exception; + +class model_evaluator { + struct imp; + imp * m_imp; +public: + model_evaluator(model & m, params_ref const & p = params_ref()); + ~model_evaluator(); + + ast_manager & m () const; + void set_model_completion(bool f); + + void updt_params(params_ref const & p); + static void get_param_descrs(param_descrs & r); + + void operator()(expr * t, expr_ref & r); + + void cancel(); + void cleanup(params_ref const & p = params_ref()); + void reset(params_ref const & p = params_ref()); + + unsigned get_num_steps() const; +}; + +#endif diff --git a/lib/model_evaluator_params.cpp b/lib/model_evaluator_params.cpp new file mode 100644 index 000000000..bfb4e2ee5 --- /dev/null +++ b/lib/model_evaluator_params.cpp @@ -0,0 +1,76 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + model_evaluator_params.cpp + +Abstract: + + New parameter setting support for rewriter. + +Author: + + Leonardo (leonardo) 2011-04-22 + +Notes: + +--*/ +#include"model_evaluator_params.h" + +model_evaluator_params::model_evaluator_params() { + reset(); +} + +void model_evaluator_params::reset() { + m_model_completion = false; + m_cache = true; + m_max_steps = UINT_MAX; + m_max_memory = UINT_MAX; +} + +#define PARAM(name) param_names.push_back(name) + +void model_evaluator_params::get_params(svector & param_names) const { + PARAM(":model-completion"); + PARAM(":cache"); + PARAM(":max-steps"); + PARAM(":max-memory"); +} + +#define DESCR(NAME, DR) if (strcmp(name, NAME) == 0) return DR + +char const * model_evaluator_params::get_param_descr(char const * name) const { + DESCR(":model-completion", "(default: false) assigns an interpretation to symbols that are not intepreted by the model."); + DESCR(":cache", "(default: true) cache intermediate results."); + DESCR(":max-steps", "(default: infty) maximum number of steps."); + DESCR(":max-memory", "(default: infty) maximum amount of memory in megabytes."); + return 0; +} + +#define RBOOL(NAME) if (strcmp(name, NAME) == 0) return CPK_BOOL +#define RUINT(NAME) if (strcmp(name, NAME) == 0) return CPK_UINT + +param_kind model_evaluator_params::get_param_kind(char const * name) const { + RBOOL(":model-completion"); + RBOOL(":cache"); + RUINT(":max-steps"); + RUINT(":max-memory"); + return CPK_INVALID; +} + +#define SET(NAME, FIELD) if (strcmp(name, NAME) == 0) { FIELD = value; return true; } + +bool model_evaluator_params::set_bool_param(char const * name, bool value) { + SET(":model-completion", m_model_completion); + SET(":cache", m_cache); + return false; +} + +bool model_evaluator_params::set_uint_param(char const * name, unsigned value) { + SET(":max-steps", m_max_steps); + SET(":max-memory", m_max_memory); + return false; +} + + diff --git a/lib/model_params.cpp b/lib/model_params.cpp new file mode 100644 index 000000000..ca3693c97 --- /dev/null +++ b/lib/model_params.cpp @@ -0,0 +1,37 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + model_params.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-08-23. + +Revision History: + +--*/ + +#include"model_params.h" + +void model_params::register_params(ini_params & p) { + p.register_bool_param("MODEL_PARTIAL", m_model_partial, "enable/disable partial function interpretations", true); + p.register_bool_param("MODEL_HIDE_UNUSED_PARTITIONS", m_model_hide_unused_partitions, "hide unused partitions, some partitions are associated with internal terms/formulas created by Z3", true); + p.register_bool_param("MODEL_V1", m_model_v1_pp, + "use Z3 version 1.x pretty printer", true); + p.register_bool_param("MODEL_V2", m_model_v2_pp, + "use Z3 version 2.x (x <= 16) pretty printer", true); + p.register_bool_param("MODEL_COMPACT", m_model_compact, + "try to compact function graph (i.e., function interpretations that are lookup tables", true); + p.register_bool_param("MODEL_COMPLETION", m_model_completion, + "assigns an interptetation to symbols that do not have one in the current model, when evaluating expressions in the current model", true); + p.register_bool_param("MODEL_DISPLAY_ARG_SORT", m_model_display_arg_sort, + "display the sort of each argument when printing function interpretations", true); +} + + diff --git a/lib/model_params.h b/lib/model_params.h new file mode 100644 index 000000000..9ab351a6c --- /dev/null +++ b/lib/model_params.h @@ -0,0 +1,47 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + model_params.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-08-23. + +Revision History: + +--*/ +#ifndef _MODEL_PARAMS_H_ +#define _MODEL_PARAMS_H_ + +#include"ini_file.h" + +struct model_params { + bool m_model_partial; + bool m_model_hide_unused_partitions; + bool m_model_compact; + bool m_model_v1_pp; + bool m_model_v2_pp; + bool m_model_completion; + bool m_model_display_arg_sort; + + model_params(): + m_model_partial(false), + m_model_hide_unused_partitions(true), + m_model_compact(false), + m_model_v1_pp(false), + m_model_v2_pp(false), + m_model_completion(false), + m_model_display_arg_sort(true) { + } + + void register_params(ini_params & p); +}; + +#endif /* _MODEL_PARAMS_H_ */ + diff --git a/lib/model_pp.cpp b/lib/model_pp.cpp new file mode 100644 index 000000000..a8e8a0880 --- /dev/null +++ b/lib/model_pp.cpp @@ -0,0 +1,102 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + model_pp.cpp + +Abstract: + + Pretty printer for models for debugging purposes. + +Author: + + Leonardo de Moura (leonardo) + +Revision History: + + +--*/ +#include"model_pp.h" +#include"model_core.h" +#include"ast_pp.h" +#include"ast_smt2_pp.h" +#include"used_symbols.h" +#include"pp.h" + +static void display_uninterp_sorts(std::ostream & out, model_core const & md) { + ast_manager & m = md.get_manager(); + unsigned sz = md.get_num_uninterpreted_sorts(); + for (unsigned i = 0; i < sz; i++) { + sort * s = md.get_uninterpreted_sort(i); + out << "(define-sort " << mk_pp(s, m); + ptr_vector const & univ = md.get_universe(s); + ptr_vector::const_iterator it = univ.begin(); + ptr_vector::const_iterator end = univ.end(); + for (; it != end; ++it) { + out << " " << mk_ismt2_pp(*it, m); + } + out << ")\n"; + } +} + +static void display_constants(std::ostream & out, model_core const & md) { + ast_manager & m = md.get_manager(); + unsigned sz = md.get_num_constants(); + for (unsigned i = 0; i < sz; i++) { + func_decl * c = md.get_constant(i); + char const * d = "(define "; + std::string n = c->get_name().str(); + unsigned indent = static_cast(n.length() + strlen(d) + 1); + out << d << n << " " << mk_ismt2_pp(md.get_const_interp(c), m, indent) << ")\n"; + } +} + +static void display_functions(std::ostream & out, model_core const & md) { + ast_manager & m = md.get_manager(); + unsigned sz = md.get_num_functions(); + for (unsigned i = 0; i < sz; i++) { + func_decl * f = md.get_function(i); + out << "(define (" << f->get_name(); + unsigned arity = f->get_arity(); + func_interp * fi = md.get_func_interp(f); + for (unsigned j = 0; j < arity; j++) { + out << " " << "x!" << j; + } + out << ")\n"; + + unsigned num_entries = fi->num_entries(); + for (unsigned j = 0; j < num_entries; j++) { + func_entry const * curr = fi->get_entry(j); + out << " (if "; + if (arity > 1) + out << "(and "; + for (unsigned j = 0; j < arity; j++) { + out << "(= x!" << j << " " << mk_ismt2_pp(curr->get_arg(j), m) << ")"; + if (j + 1 < arity) + out << " "; + } + if (arity > 1) + out << ")"; + out << " " << mk_ismt2_pp(curr->get_result(), m) << "\n"; + } + if (num_entries > 0) + out << " "; + if (fi->is_partial()) + out << " #unspecified"; + else { + pp_params const & params = get_pp_default_params(); + out << " " << mk_ismt2_pp(fi->get_else(), m, params, 5, arity, "x"); + } + for (unsigned j = 0; j < num_entries; j++) + out << ")"; + out << ")\n"; + } +} + + +void model_pp(std::ostream & out, model_core const & md) { + display_uninterp_sorts(out, md); + display_constants(out, md); + display_functions(out, md); +} diff --git a/lib/model_pp.h b/lib/model_pp.h new file mode 100644 index 000000000..31a6083a7 --- /dev/null +++ b/lib/model_pp.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + model_pp.h + +Abstract: + + Pretty printer for models for debugging purposes. + +Author: + + Leonardo de Moura (leonardo) + +Revision History: + + +--*/ +#ifndef _MODEL_PP_H_ +#define _MODEL_PP_H_ + +#include +class model_core; + +void model_pp(std::ostream & out, model_core const & m); + +#endif diff --git a/lib/model_smt2_pp.cpp b/lib/model_smt2_pp.cpp new file mode 100644 index 000000000..031c5d151 --- /dev/null +++ b/lib/model_smt2_pp.cpp @@ -0,0 +1,348 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + model_smt2_pp.cpp + +Abstract: + + Pretty printer for models using SMT2 format. + +Author: + + Leonardo de Moura (leonardo) + +Revision History: + + +--*/ +#include"model_smt2_pp.h" +#include"ast_smt2_pp.h" +#include"func_decl_dependencies.h" +#include"pp.h" +using namespace format_ns; + +static void pp_indent(std::ostream & out, unsigned indent) { + for (unsigned i = 0; i < indent; i++) + out << " "; +} + +static unsigned pp_symbol(std::ostream & out, symbol const & s) { + if (is_smt2_quoted_symbol(s)) { + std::string str = mk_smt2_quoted_symbol(s); + out << str; + return static_cast(str.length()); + } + else if (s.is_numerical()) { + std::string str = s.str(); + out << str; + return static_cast(str.length()); + } + else { + out << s.bare_str(); + return static_cast(strlen(s.bare_str())); + } +} + +#define TAB_SZ 2 + +class model_smt2_pp_ctx { +public: + virtual ast_manager & m() const = 0; + virtual void display(std::ostream & out, sort * s, unsigned indent = 0) = 0; + virtual void display(std::ostream & out, expr * n, unsigned indent = 0) = 0; + virtual void pp(sort * s, format_ns::format_ref & r) = 0; + virtual void pp(func_decl * f, format_ns::format_ref & r) = 0; + virtual void pp(expr * n, format_ns::format_ref & r) = 0; + virtual void pp(expr * n, unsigned num_vars, char const * var_prefix, format_ns::format_ref & r, sbuffer & var_names) = 0; +}; + +class model_smt2_pp_cmd_ctx : public model_smt2_pp_ctx { + cmd_context & m_ctx; +public: + model_smt2_pp_cmd_ctx(cmd_context & ctx):m_ctx(ctx) {} + virtual ast_manager & m() const { return m_ctx.m(); } + virtual void display(std::ostream & out, sort * s, unsigned indent = 0) { m_ctx.display(out, s, indent); } + virtual void display(std::ostream & out, expr * n, unsigned indent = 0) { m_ctx.display(out, n, indent); } + virtual void pp(sort * s, format_ns::format_ref & r) { r = m_ctx.pp(s); } + virtual void pp(func_decl * f, format_ns::format_ref & r) { return m_ctx.pp(f, r); } + virtual void pp(expr * n, format_ns::format_ref & r) { return m_ctx.pp(n, r); } + virtual void pp(expr * n, unsigned num_vars, char const * var_prefix, format_ns::format_ref & r, sbuffer & var_names) { + return m_ctx.pp(n, num_vars, var_prefix, r, var_names); + } +}; + +class model_smt2_pp_simple_ctx : public model_smt2_pp_ctx { + ast_manager & m_manager; + smt2_pp_environment_dbg m_env; +public: + model_smt2_pp_simple_ctx(ast_manager & m):m_manager(m), m_env(m) {} + virtual ast_manager & m() const { return m_manager; } + virtual void display(std::ostream & out, sort * s, unsigned indent = 0) { out << mk_ismt2_pp(s, m(), indent); } + virtual void display(std::ostream & out, expr * n, unsigned indent = 0) { out << mk_ismt2_pp(n, m(), indent); } + virtual void pp(sort * s, format_ns::format_ref & r) { mk_smt2_format(s, m_env, get_pp_default_params(), r); } + virtual void pp(func_decl * f, format_ns::format_ref & r) { mk_smt2_format(f, m_env, get_pp_default_params(), r); } + virtual void pp(expr * n, format_ns::format_ref & r) { + sbuffer buf; + mk_smt2_format(n, m_env, get_pp_default_params(), 0, 0, r, buf); + } + virtual void pp(expr * n, unsigned num_vars, char const * var_prefix, format_ns::format_ref & r, sbuffer & var_names) { + mk_smt2_format(n, m_env, get_pp_default_params(), num_vars, var_prefix, r, var_names); + } +}; + +static void pp_uninterp_sorts(std::ostream & out, model_smt2_pp_ctx & ctx, model_core const & md, unsigned indent) { + ast_manager & m = ctx.m(); + ptr_buffer f_conds; + unsigned num = md.get_num_uninterpreted_sorts(); + for (unsigned i = 0; i < num; i++) { + sort * s = md.get_uninterpreted_sort(i); + ptr_vector const & u = md.get_universe(s); + std::ostringstream buffer; + buffer << "universe for "; + ctx.display(buffer, s, 13); + buffer << ":\n"; + pp_indent(buffer, TAB_SZ); + ptr_vector::const_iterator it = u.begin(); + ptr_vector::const_iterator end = u.end(); + for (; it != end; ++it) { + SASSERT(is_app(*it)); + app * c = to_app(*it); + pp_symbol(buffer, c->get_decl()->get_name()); + buffer << " "; + } + buffer << "\n-----------"; + std::string buffer_str = buffer.str(); + unsigned len = static_cast(buffer_str.length()); + pp_indent(out, indent); + out << ";; "; + for (unsigned i = 0; i < len; i++) { + char c = buffer_str[i]; + if (c == '\n') { + out << "\n"; + pp_indent(out, indent); + out << ";; "; + } + else { + out << c; + } + } + out << "\n"; + pp_indent(out, indent); + out << ";; definitions for universe elements:\n"; + it = u.begin(); + for (; it != end; ++it) { + app * c = to_app(*it); + pp_indent(out, indent); + out << "(declare-fun "; + unsigned len = pp_symbol(out, c->get_decl()->get_name()); + out << " () "; + ctx.display(out, c->get_decl()->get_range(), indent + len + 16); + out << ")\n"; + } + pp_indent(out, indent); + out << ";; cardinality constraint:\n"; + f_conds.reset(); + format * var = mk_string(m, "x"); + it = u.begin(); + for (; it != end; ++it) { + app * c = to_app(*it); + symbol csym = c->get_decl()->get_name(); + std::string cname; + if (is_smt2_quoted_symbol(csym)) + cname = mk_smt2_quoted_symbol(csym); + else + cname = csym.str(); + format * c_args[2] = { var, mk_string(m, cname.c_str()) }; + f_conds.push_back(mk_seq1(m, c_args, c_args+2, f2f(), "=")); + } + SASSERT(!f_conds.empty()); + format * f_cond; + if (f_conds.size() > 1) + f_cond = mk_seq1(m, f_conds.begin(), f_conds.end(), f2f(), "or"); + else + f_cond = f_conds[0]; + format_ref f_s(fm(m)); + ctx.pp(s, f_s); + format * f_args[2] = { mk_compose(m, + mk_string(m, "((x "), + mk_indent(m, 4, mk_compose(m, f_s.get(), mk_string(m, "))")))), + f_cond }; + format_ref f_card(fm(m)); + f_card = mk_indent(m, indent, mk_seq1(m, f_args, f_args+2, f2f(), "forall")); + pp_indent(out, indent); + pp(out, f_card, m, get_pp_default_params()); + out << "\n"; + pp_indent(out, indent); + out << ";; -----------\n"; + } +} + +static void pp_consts(std::ostream & out, model_smt2_pp_ctx & ctx, model_core const & md, unsigned indent) { + unsigned num = md.get_num_constants(); + for (unsigned i = 0; i < num; i++) { + func_decl * c = md.get_constant(i); + expr * c_i = md.get_const_interp(c); + pp_indent(out, indent); + out << "(define-fun "; + unsigned len = pp_symbol(out, c->get_name()); + out << " () "; + ctx.display(out, c->get_range(), indent + len + 16); + out << "\n"; + pp_indent(out, indent + TAB_SZ); + ctx.display(out, c_i); + out << ")\n"; + } +} + +void sort_fun_decls(ast_manager & m, model_core const & md, ptr_buffer & result) { + func_decl_set visited; + ptr_vector todo; + unsigned sz = md.get_num_functions(); + for (unsigned i = 0; i < sz; i++) { + func_decl * f = md.get_function(i); + if (visited.contains(f)) + continue; + visited.insert(f); + todo.push_back(f); + while (!todo.empty()) { + func_decl * curr = todo.back(); + func_interp * curr_i = md.get_func_interp(curr); + SASSERT(curr_i != 0); + if (!curr_i->is_partial()) { + func_decl_set deps; + bool all_visited = true; + collect_func_decls(m, curr_i->get_else(), deps); + func_decl_set::iterator it2 = deps.begin(); + func_decl_set::iterator end2 = deps.end(); + for (; it2 != end2; ++it2) { + func_decl * d = *it2; + if (d->get_arity() > 0 && md.has_interpretation(d) && !visited.contains(d)) { + todo.push_back(d); + visited.insert(d); + all_visited = false; + } + } + if (!all_visited) + continue; + } + todo.pop_back(); + result.push_back(curr); + } + } +} + +static void pp_funs(std::ostream & out, model_smt2_pp_ctx & ctx, model_core const & md, unsigned indent) { + ast_manager & m = ctx.m(); + sbuffer var_names; + ptr_buffer f_var_names; + ptr_buffer f_arg_decls; + ptr_buffer f_entries; + ptr_buffer f_entry_conds; + ptr_buffer func_decls; + sort_fun_decls(m, md, func_decls); + for (unsigned i = 0; i < func_decls.size(); i++) { + func_decl * f = func_decls[i]; + func_interp * f_i = md.get_func_interp(f); + SASSERT(f->get_arity() == f_i->get_arity()); + format_ref body(fm(m)); + var_names.reset(); + if (f_i->is_partial()) { + body = mk_string(m, "#unspecified"); + for (unsigned j = 0; j < f->get_arity(); j++) { + std::stringstream strm; + strm << "x!" << (j+1); + var_names.push_back(symbol(strm.str().c_str())); + } + } + else { + ctx.pp(f_i->get_else(), f->get_arity(), "x", body, var_names); + } + TRACE("model_smt2_pp", for (unsigned i = 0; i < var_names.size(); i++) tout << var_names[i] << "\n";); + f_var_names.reset(); + for (unsigned i = 0; i < f->get_arity(); i++) + f_var_names.push_back(mk_string(m, var_names[i].bare_str())); + f_arg_decls.reset(); + for (unsigned i = 0; i < f->get_arity(); i++) { + format_ref f_domain(fm(m)); + ctx.pp(f->get_domain(i), f_domain); + format * args[2] = { f_var_names[i], f_domain.get() }; + f_arg_decls.push_back(mk_seq5(m, args, args+2, f2f())); + } + format * f_domain = mk_seq5(m, f_arg_decls.begin(), f_arg_decls.end(), f2f()); + format_ref f_range(fm(m)); + ctx.pp(f->get_range(), f_range); + if (f_i->num_entries() > 0) { + f_entries.reset(); + for (unsigned i = 0; i < f_i->num_entries(); i++) { + func_entry const * e = f_i->get_entry(i); + f_entry_conds.reset(); + for (unsigned j = 0; j < f->get_arity(); j++) { + format_ref f_arg(fm(m)); + ctx.pp(e->get_arg(j), f_arg); + format * eq_args[2] = { f_var_names[j], f_arg.get() }; + f_entry_conds.push_back(mk_seq1(m, eq_args, eq_args+2, f2f(), "=")); + } + format * f_entry_cond; + SASSERT(!f_entry_conds.empty()); + if (f_entry_conds.size() > 1) + f_entry_cond = mk_seq1(m, f_entry_conds.begin(), f_entry_conds.end(), f2f(), "and"); + else + f_entry_cond = f_entry_conds[0]; + format_ref f_result(fm(m)); + ctx.pp(e->get_result(), f_result); + if (i > 0) + f_entries.push_back(mk_line_break(m)); + f_entries.push_back(mk_group(m, mk_compose(m, + mk_string(m, "(ite "), + mk_indent(m, 5, f_entry_cond), + mk_indent(m, TAB_SZ, mk_compose(m, + mk_line_break(m), + f_result.get()))))); + } + f_entries.push_back(mk_indent(m, TAB_SZ, mk_compose(m, + mk_line_break(m), + body.get()))); + for (unsigned i = 0; i < f_i->num_entries(); i++) + f_entries.push_back(mk_string(m, ")")); + body = mk_compose(m, f_entries.size(), f_entries.c_ptr()); + } + format_ref def(fm(m)); + std::string fname; + if (is_smt2_quoted_symbol(f->get_name())) + fname = mk_smt2_quoted_symbol(f->get_name()); + else + fname = f->get_name().str(); + def = mk_indent(m, indent, mk_compose(m, + mk_compose(m, + mk_string(m, "(define-fun "), + mk_string(m, fname.c_str()), + mk_string(m, " "), + mk_compose(m, + f_domain, + mk_string(m, " "), + f_range)), + mk_indent(m, TAB_SZ, mk_compose(m, + mk_line_break(m), + body.get(), + mk_string(m, ")"))))); + pp_indent(out, indent); + pp(out, def.get(), m, get_pp_default_params()); + out << "\n"; + } +} + +void model_smt2_pp(std::ostream & out, cmd_context & ctx, model_core const & m, unsigned indent) { + model_smt2_pp_cmd_ctx _ctx(ctx); + pp_uninterp_sorts(out, _ctx, m, indent); + pp_consts(out, _ctx, m, indent); + pp_funs(out, _ctx, m, indent); +} + +void model_smt2_pp(std::ostream & out, ast_manager & m, model_core const & md, unsigned indent) { + model_smt2_pp_simple_ctx _ctx(m); + pp_uninterp_sorts(out, _ctx, md, indent); + pp_consts(out, _ctx, md, indent); + pp_funs(out, _ctx, md, indent); +} diff --git a/lib/model_smt2_pp.h b/lib/model_smt2_pp.h new file mode 100644 index 000000000..29a7cddf2 --- /dev/null +++ b/lib/model_smt2_pp.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + model_smt2_pp.h + +Abstract: + + Pretty printer for models using SMT2 format. + +Author: + + Leonardo de Moura (leonardo) + +Revision History: + + +--*/ +#ifndef _MODEL_SMT2_PP_H_ +#define _MODEL_SMT2_PP_H_ + +#include"cmd_context.h" + +void model_smt2_pp(std::ostream & out, cmd_context & ctx, model_core const & m, unsigned indent); +void model_smt2_pp(std::ostream & out, ast_manager & m, model_core const & md, unsigned indent); + +#endif diff --git a/lib/model_v2_pp.cpp b/lib/model_v2_pp.cpp new file mode 100644 index 000000000..9073ddece --- /dev/null +++ b/lib/model_v2_pp.cpp @@ -0,0 +1,82 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + model_v2_pp.cpp + +Abstract: + + V2 Pretty printer for models. (backward compatibility) + +Author: + + Leonardo de Moura (leonardo) + +Revision History: +--*/ +#include"model_v2_pp.h" +#include"model_core.h" +#include"ast_pp.h" + +static void display_function(std::ostream & out, model_core const & md, func_decl * f, bool partial) { + ast_manager & m = md.get_manager(); + func_interp * g = md.get_func_interp(f); + out << f->get_name() << " -> {\n"; + unsigned num_entries = g->num_entries(); + unsigned arity = g->get_arity(); + char const * else_str = num_entries == 0 ? " " : " else -> "; + unsigned else_indent = static_cast(strlen(else_str)); + for (unsigned i = 0; i < num_entries; i++) { + func_entry const * entry = g->get_entry(i); + out << " "; + for (unsigned j = 0; j < arity; j++) { + expr * arg = entry->get_arg(j); + out << mk_pp(arg, m); + out << " "; + } + out << "-> "; + out << mk_pp(entry->get_result(), m); + out << "\n"; + } + if (partial) { + out << else_str << "#unspecified\n"; + } + else { + expr * else_val = g->get_else(); + out << else_str; + if (else_val) + out << mk_pp(else_val, m, else_indent); + else + out << "#unspecified"; + out << "\n"; + } + out << "}\n"; +} + +static void display_functions(std::ostream & out, model_core const & md, bool partial) { + unsigned sz = md.get_num_functions(); + for (unsigned i = 0; i < sz; i++) { + func_decl * f = md.get_function(i); + display_function(out, md, f, partial); + } +} + +static void display_constants(std::ostream & out, model_core const & md) { + ast_manager & m = md.get_manager(); + unsigned sz = md.get_num_constants(); + for (unsigned i = 0; i < sz; i++) { + func_decl * d = md.get_constant(i); + + std::string name = d->get_name().str(); + const char * arrow = " -> "; + out << name << arrow; + unsigned indent = static_cast(name.length() + strlen(arrow)); + out << mk_pp(md.get_const_interp(d), m, indent) << "\n"; + } +} + +void model_v2_pp(std::ostream & out, model_core const & md, bool partial) { + display_constants(out, md); + display_functions(out, md, partial); +} diff --git a/lib/model_v2_pp.h b/lib/model_v2_pp.h new file mode 100644 index 000000000..9121f023a --- /dev/null +++ b/lib/model_v2_pp.h @@ -0,0 +1,26 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + model_v2_pp.h + +Abstract: + + V2 Pretty printer for models. (backward compatibility) + +Author: + + Leonardo de Moura (leonardo) + +Revision History: +--*/ +#ifndef _MODEL_V2_PP_H_ +#define _MODEL_V2_PP_H_ + +#include +class model_core; + +void model_v2_pp(std::ostream & out, model_core const & m, bool partial = false); + +#endif diff --git a/lib/mpbq.cpp b/lib/mpbq.cpp new file mode 100644 index 000000000..9af1c852e --- /dev/null +++ b/lib/mpbq.cpp @@ -0,0 +1,907 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + mpbq.cpp + +Abstract: + + Binary Rational Numbers + + A binary rational is a number of the form a/2^k. + All integers are binary rationals. + Binary rational numbers can be implemented more efficiently than rationals. + Binary rationals form a Ring. + They are not closed under division. + In Z3, they are used to implement algebraic numbers. + The root isolation operations only use division by 2. + +Author: + + Leonardo de Moura (leonardo) 2011-11-24. + +Revision History: + +--*/ +#include +#include"mpbq.h" + +#ifdef Z3DEBUG +#define MPBQ_DEBUG +#endif + +rational to_rational(mpbq const & v) { + rational r(v.numerator()); + rational twok; + twok = power(rational(2), v.k()); + return r/twok; +} + +mpbq_manager::mpbq_manager(unsynch_mpz_manager & m): + m_manager(m) { +} + +mpbq_manager::~mpbq_manager() { + del(m_addmul_tmp); + m_manager.del(m_tmp); + m_manager.del(m_tmp2); + m_manager.del(m_select_int_tmp1); + m_manager.del(m_select_int_tmp2); + m_manager.del(m_select_small_tmp); + del(m_select_small_tmp1); + del(m_select_small_tmp2); + m_manager.del(m_div_tmp1); + m_manager.del(m_div_tmp2); + m_manager.del(m_div_tmp3); +} + +void mpbq_manager::reset(mpbq_vector & v) { + unsigned sz = v.size(); + for (unsigned i = 0; i < sz; i++) + reset(v[i]); + v.reset(); +} + +void mpbq_manager::normalize(mpbq & a) { + if (a.m_k == 0) + return; + if (m_manager.is_zero(a.m_num)) { + a.m_k = 0; + return; + } +#ifdef MPBQ_DEBUG + rational r = to_rational(a); +#endif + unsigned k = m_manager.power_of_two_multiple(a.m_num); + if (k > a.m_k) + k = a.m_k; + m_manager.machine_div2k(a.m_num, k); + a.m_k -= k; +#ifdef MPBQ_DEBUG + rational new_r = to_rational(a); + SASSERT(r == new_r); +#endif +} + +int mpbq_manager::magnitude_lb(mpbq const & a) { + if (m_manager.is_zero(a.m_num)) + return 0; + if (m_manager.is_pos(a.m_num)) + return m_manager.log2(a.m_num) - a.m_k; + return m_manager.mlog2(a.m_num) - a.m_k + 1; +} + +int mpbq_manager::magnitude_ub(mpbq const & a) { + if (m_manager.is_zero(a.m_num)) + return 0; + if (m_manager.is_pos(a.m_num)) + return m_manager.log2(a.m_num) - a.m_k + 1; + return m_manager.mlog2(a.m_num) - a.m_k; +} + +void mpbq_manager::mul2(mpbq & a) { + if (a.m_k == 0) + m_manager.mul2k(a.m_num, 1); + else + a.m_k--; +} + +void mpbq_manager::mul2k(mpbq & a, unsigned k) { + if (k == 0) + return; + if (a.m_k < k) { + m_manager.mul2k(a.m_num, k - a.m_k); + a.m_k = 0; + } + else { + SASSERT(a.m_k >= k); + a.m_k -= k; + } +} + +void mpbq_manager::add(mpbq const & a, mpbq const & b, mpbq & r) { +#ifdef MPBQ_DEBUG + rational _a = to_rational(a); + rational _b = to_rational(b); +#endif + if (a.m_k == b.m_k) { + m_manager.add(a.m_num, b.m_num, r.m_num); + r.m_k = a.m_k; + } + else if (a.m_k < b.m_k) { + m_manager.mul2k(a.m_num, b.m_k - a.m_k, m_tmp); + m_manager.add(b.m_num, m_tmp, r.m_num); + r.m_k = b.m_k; + } + else { + SASSERT(a.m_k > b.m_k); + m_manager.mul2k(b.m_num, a.m_k - b.m_k, m_tmp); + m_manager.add(a.m_num, m_tmp, r.m_num); + r.m_k = a.m_k; + } + normalize(r); +#ifdef MPBQ_DEBUG + rational _r = to_rational(r); + SASSERT(_a + _b == _r); +#endif +} + +void mpbq_manager::add(mpbq const & a, mpz const & b, mpbq & r) { +#ifdef MPBQ_DEBUG + rational _a = to_rational(a); + rational _b(b); +#endif + if (a.m_k == 0) { + m_manager.add(a.m_num, b, r.m_num); + r.m_k = a.m_k; + } + else { + m_manager.mul2k(b, a.m_k, m_tmp); + m_manager.add(a.m_num, m_tmp, r.m_num); + r.m_k = a.m_k; + } + normalize(r); +#ifdef MPBQ_DEBUG + rational _r = to_rational(r); + TRACE("mpbq_bug", tout << "add a: " << _a << ", b: " << _b << ", r: " << _r << ", expected: " << (_a + _b) << "\n";); + SASSERT(_a + _b == _r); +#endif +} + +void mpbq_manager::sub(mpbq const & a, mpbq const & b, mpbq & r) { +#ifdef MPBQ_DEBUG + rational _a = to_rational(a); + rational _b = to_rational(b); +#endif + if (a.m_k == b.m_k) { + m_manager.sub(a.m_num, b.m_num, r.m_num); + r.m_k = a.m_k; + } + else if (a.m_k < b.m_k) { + m_manager.mul2k(a.m_num, b.m_k - a.m_k, m_tmp); + m_manager.sub(m_tmp, b.m_num, r.m_num); + r.m_k = b.m_k; + } + else { + SASSERT(a.m_k > b.m_k); + m_manager.mul2k(b.m_num, a.m_k - b.m_k, m_tmp); + m_manager.sub(a.m_num, m_tmp, r.m_num); + r.m_k = a.m_k; + } + normalize(r); +#ifdef MPBQ_DEBUG + rational _r = to_rational(r); + TRACE("mpbq_bug", tout << "sub a: " << _a << ", b: " << _b << ", r: " << _r << ", expected: " << (_a - _b) << "\n";); + SASSERT(_a - _b == _r); +#endif +} + +void mpbq_manager::sub(mpbq const & a, mpz const & b, mpbq & r) { +#ifdef MPBQ_DEBUG + rational _a = to_rational(a); + rational _b(b); +#endif + if (a.m_k == 0) { + m_manager.sub(a.m_num, b, r.m_num); + r.m_k = a.m_k; + } + else { + m_manager.mul2k(b, a.m_k, m_tmp); + m_manager.sub(a.m_num, m_tmp, r.m_num); + r.m_k = a.m_k; + } + normalize(r); +#ifdef MPBQ_DEBUG + rational _r = to_rational(r); + SASSERT(_a - _b == _r); +#endif +} + +void mpbq_manager::mul(mpbq const & a, mpbq const & b, mpbq & r) { +#ifdef MPBQ_DEBUG + rational _a = to_rational(a); + rational _b = to_rational(b); +#endif + m_manager.mul(a.m_num, b.m_num, r.m_num); + r.m_k = a.m_k + b.m_k; + if (a.m_k == 0 || b.m_k == 0) { + // if a.m_k and b.m_k are greater than 0, then there is no point in normalizing r. + normalize(r); + } +#ifdef MPBQ_DEBUG + rational _r = to_rational(r); + SASSERT(_a * _b == _r); +#endif +} + +void mpbq_manager::mul(mpbq const & a, mpz const & b, mpbq & r) { +#ifdef MPBQ_DEBUG + rational _a = to_rational(a); + rational _b(b); +#endif + m_manager.mul(a.m_num, b, r.m_num); + r.m_k = a.m_k; + normalize(r); +#ifdef MPBQ_DEBUG + rational _r = to_rational(r); + SASSERT(_a * _b == _r); +#endif +} + +void mpbq_manager::power(mpbq & a, unsigned k) { + SASSERT(static_cast(k) * static_cast(a.k()) <= static_cast(UINT_MAX)); + // We don't need to normalize because: + // If a.m_k == 0, then a is an integer, and the result be an integer + // If a.m_k > 0, then a.m_num must be odd, and the (a.m_num)^k will also be odd + a.m_k *= k; + m_manager.power(a.m_num, k, a.m_num); +} + +bool mpbq_manager::root_lower(mpbq & a, unsigned n) { + bool r = m_manager.root(a.m_num, n); + if (!r) + m_manager.dec(a.m_num); + if (a.m_k % n == 0) { + a.m_k /= n; + normalize(a); + return r; + } + else if (m_manager.is_neg(a.m_num)) { + a.m_k /= n; + normalize(a); + return false; + } + else { + a.m_k /= n; + a.m_k++; + normalize(a); + return false; + } +} + +bool mpbq_manager::root_upper(mpbq & a, unsigned n) { + bool r = m_manager.root(a.m_num, n); + if (a.m_k % n == 0) { + a.m_k /= n; + normalize(a); + return r; + } + else if (m_manager.is_neg(a.m_num)) { + a.m_k /= n; + a.m_k++; + normalize(a); + return false; + } + else { + a.m_k /= n; + normalize(a); + return false; + } +} + +bool mpbq_manager::lt(mpbq const & a, mpbq const & b) { + // TODO: try the following trick when k1 != k2 + // Given, a = n1/2^k1 b = n2/2^k2 + // Suppose n1 > 0 and n2 > 0, + // Then, we have, n1 <= 2^{log2(n1) - k1} 2^{log2(n2) - 1 - k2} <= n2 + // Thus, log2(n1) - k1 < log2(n2) - 1 - k2 implies a < b + // Similarly: log2(n2) - k2 < log2(n1) - 1 - k1 implies b < a + // That is we compare the "magnitude" of the numbers before performing mul2k + // + // If n1 < 0 and n2 < 0, a similar trick can be implemented using mlog2 instead log2. + // + // It seems the trick is not useful when n1 and n2 are small + // numbers, and k1 and k2 very small < 8. Since, no bignumber + // computation is needed for mul2k. + + if (a.m_k == b.m_k) { + return m_manager.lt(a.m_num, b.m_num); + } + else if (a.m_k < b.m_k) { + m_manager.mul2k(a.m_num, b.m_k - a.m_k, m_tmp); + return m_manager.lt(m_tmp, b.m_num); + } + else { + SASSERT(a.m_k > b.m_k); + m_manager.mul2k(b.m_num, a.m_k - b.m_k, m_tmp); + return m_manager.lt(a.m_num, m_tmp); + } +} + +bool mpbq_manager::lt_1div2k(mpbq const & a, unsigned k) { + if (m_manager.is_nonpos(a.m_num)) + return true; + if (a.m_k <= k) { + // since a.m_num >= 1 + return false; + } + else { + SASSERT(a.m_k > k); + m_manager.mul2k(mpz(1), a.m_k - k, m_tmp); + return m_manager.lt(a.m_num, m_tmp); + } +} + +bool mpbq_manager::eq(mpbq const & a, mpq const & b) { + if (is_int(a) && m_manager.is_one(b.denominator())) + return m_manager.eq(a.m_num, b.numerator()); + m_manager.mul2k(b.numerator(), a.m_k, m_tmp); + m_manager.mul(a.m_num, b.denominator(), m_tmp2); + return m_manager.eq(m_tmp, m_tmp2); +} + +bool mpbq_manager::lt(mpbq const & a, mpq const & b) { + if (is_int(a) && m_manager.is_one(b.denominator())) + return m_manager.lt(a.m_num, b.numerator()); + m_manager.mul(a.m_num, b.denominator(), m_tmp); + m_manager.mul2k(b.numerator(), a.m_k, m_tmp2); + return m_manager.lt(m_tmp, m_tmp2); +} + +bool mpbq_manager::le(mpbq const & a, mpq const & b) { + if (is_int(a) && m_manager.is_one(b.denominator())) + return m_manager.le(a.m_num, b.numerator()); + m_manager.mul(a.m_num, b.denominator(), m_tmp); + m_manager.mul2k(b.numerator(), a.m_k, m_tmp2); + return m_manager.le(m_tmp, m_tmp2); +} + +bool mpbq_manager::lt(mpbq const & a, mpz const & b) { + if (is_int(a)) + return m_manager.lt(a.m_num, b); + m_manager.mul2k(b, a.m_k, m_tmp); + return m_manager.lt(a.m_num, m_tmp); +} + +bool mpbq_manager::le(mpbq const & a, mpz const & b) { + if (is_int(a)) + return m_manager.le(a.m_num, b); + m_manager.mul2k(b, a.m_k, m_tmp); + return m_manager.le(a.m_num, m_tmp); +} + +std::string mpbq_manager::to_string(mpbq const & a) { + std::ostringstream buffer; + buffer << m_manager.to_string(a.m_num); + if (a.m_k == 1) + buffer << "/2"; + else if (a.m_k > 1) + buffer << "/2^" << a.m_k; + return buffer.str(); +} + +void mpbq_manager::display(std::ostream & out, mpbq const & a) { + out << m_manager.to_string(a.m_num); + if (a.m_k > 0) + out << "/2"; + if (a.m_k > 1) + out << "^" << a.m_k; +} + +void mpbq_manager::display_smt2(std::ostream & out, mpbq const & a, bool decimal) { + if (a.m_k == 0) { + m_manager.display_smt2(out, a.m_num, decimal); + } + else { + out << "(/ "; + m_manager.display_smt2(out, a.m_num, decimal); + out << " "; + out << "(^ 2"; + if (decimal) out << ".0"; + out << " " << a.m_k; + if (decimal) out << ".0"; + out << "))"; + } +} + +void mpbq_manager::display_decimal(std::ostream & out, mpbq const & a, unsigned prec) { + if (is_int(a)) { + out << m_manager.to_string(a.m_num); + return; + } + mpz two(2); + mpz ten(10); + mpz two_k; + mpz n1, v1; + if (m_manager.is_neg(a.m_num)) + out << "-"; + m_manager.set(v1, a.m_num); + m_manager.abs(v1); + m_manager.power(two, a.m_k, two_k); + m_manager.rem(v1, two_k, n1); + m_manager.div(v1, two_k, v1); + SASSERT(!m_manager.is_zero(n1)); + out << m_manager.to_string(v1); + out << "."; + for (unsigned i = 0; i < prec; i++) { + m_manager.mul(n1, ten, n1); + m_manager.div(n1, two_k, v1); + m_manager.rem(n1, two_k, n1); + out << m_manager.to_string(v1); + if (m_manager.is_zero(n1)) + goto end; + } + out << "?"; + end: + m_manager.del(n1); + m_manager.del(v1); + m_manager.del(two_k); +} + +void mpbq_manager::display_decimal(std::ostream & out, mpbq const & a, mpbq const & b, unsigned prec) { + mpz two(2); + mpz ten(10); + mpz two_k1, two_k2; + mpz n1, v1, n2, v2; + if (m_manager.is_neg(a.m_num) != m_manager.is_neg(b.m_num)) { + out << "?"; + return; + } + if (m_manager.is_neg(a.m_num)) + out << "-"; + m_manager.set(v1, a.m_num); + m_manager.abs(v1); + m_manager.set(v2, b.m_num); + m_manager.abs(v2); + m_manager.power(two, a.m_k, two_k1); + m_manager.power(two, b.m_k, two_k2); + m_manager.rem(v1, two_k1, n1); + m_manager.rem(v2, two_k2, n2); + m_manager.div(v1, two_k1, v1); + m_manager.div(v2, two_k2, v2); + if (!m_manager.eq(v1, v2)) { + out << "?"; + goto end; + } + + out << m_manager.to_string(v1); + if (m_manager.is_zero(n1) && m_manager.is_zero(n2)) + goto end; // number is an integer + out << "."; + for (unsigned i = 0; i < prec; i++) { + m_manager.mul(n1, ten, n1); + m_manager.mul(n2, ten, n2); + m_manager.div(n1, two_k1, v1); + m_manager.div(n2, two_k2, v2); + if (m_manager.eq(v1, v2)) { + out << m_manager.to_string(v1); + } + else { + out << "?"; + goto end; + } + m_manager.rem(n1, two_k1, n1); + m_manager.rem(n2, two_k2, n2); + if (m_manager.is_zero(n1) && m_manager.is_zero(n2)) + goto end; // number is precise + } + out << "?"; + end: + m_manager.del(n1); + m_manager.del(v1); + m_manager.del(n2); + m_manager.del(v2); + m_manager.del(two_k1); + m_manager.del(two_k2); +} + +bool mpbq_manager::to_mpbq(mpq const & q, mpbq & bq) { + mpz const & n = q.numerator(); + mpz const & d = q.denominator(); + unsigned shift; + if (m_manager.is_one(d)) { + set(bq, n); + SASSERT(eq(bq, q)); + return true; + } + else if (m_manager.is_power_of_two(d, shift)) { + SASSERT(shift>=1); + unsigned k = shift; + set(bq, n, k); + SASSERT(eq(bq, q)); + return true; + } + else { + unsigned k = m_manager.log2(d); + set(bq, n, k+1); + return false; + } +} + +void mpbq_manager::refine_upper(mpq const & q, mpbq & l, mpbq & u) { + SASSERT(lt(l, q) && gt(u, q)); + SASSERT(!m_manager.is_power_of_two(q.denominator())); + // l < q < u + mpbq mid; + while (true) { + add(l, u, mid); + div2(mid); + if (gt(mid, q)) { + swap(u, mid); + del(mid); + SASSERT(lt(l, q) && gt(u, q)); + return; + } + swap(l, mid); + } +} + +void mpbq_manager::refine_lower(mpq const & q, mpbq & l, mpbq & u) { + SASSERT(lt(l, q) && gt(u, q)); + SASSERT(!m_manager.is_power_of_two(q.denominator())); + // l < q < u + mpbq mid; + while (true) { + add(l, u, mid); + div2(mid); + if (lt(mid, q)) { + swap(l, mid); + del(mid); + SASSERT(lt(l, q) && gt(u, q)); + return; + } + swap(u, mid); + } +} + +// sectect integer in [lower, upper] +bool mpbq_manager::select_integer(mpbq const & lower, mpbq const & upper, mpz & r) { + if (is_int(lower)) { + m_manager.set(r, lower.m_num); + return true; + } + if (is_int(upper)) { + m_manager.set(r, upper.m_num); + return true; + } + mpz & ceil_lower = m_select_int_tmp1; + mpz & floor_upper = m_select_int_tmp2; + ceil(m_manager, lower, ceil_lower); + floor(m_manager, upper, floor_upper); + if (m_manager.le(ceil_lower, floor_upper)) { + m_manager.set(r, ceil_lower); + return true; + } + return false; +} + +// select integer in (lower, upper] +bool mpbq_manager::select_integer(unsynch_mpq_manager & qm, mpq const & lower, mpbq const & upper, mpz & r) { + if (is_int(upper)) { + m_manager.set(r, upper.m_num); + return true; + } + mpz & ceil_lower = m_select_int_tmp1; + mpz & floor_upper = m_select_int_tmp2; + if (qm.is_int(lower)) { + m_manager.set(ceil_lower, lower.numerator()); + m_manager.inc(ceil_lower); + } + else { + scoped_mpz tmp(qm); + qm.ceil(lower, tmp); + m_manager.set(ceil_lower, tmp); + } + floor(m_manager, upper, floor_upper); + if (m_manager.le(ceil_lower, floor_upper)) { + m_manager.set(r, ceil_lower); + return true; + } + return false; +} + +// sectect integer in [lower, upper) +bool mpbq_manager::select_integer(unsynch_mpq_manager & qm, mpbq const & lower, mpq const & upper, mpz & r) { + if (is_int(lower)) { + m_manager.set(r, lower.m_num); + return true; + } + mpz & ceil_lower = m_select_int_tmp1; + mpz & floor_upper = m_select_int_tmp2; + ceil(m_manager, lower, ceil_lower); + if (qm.is_int(upper)) { + m_manager.set(floor_upper, upper.numerator()); + m_manager.dec(floor_upper); + } + else { + scoped_mpz tmp(qm); + qm.floor(upper, tmp); + m_manager.set(floor_upper, tmp); + } + if (m_manager.le(ceil_lower, floor_upper)) { + m_manager.set(r, ceil_lower); + return true; + } + return false; +} + +// sectect integer in (lower, upper) +bool mpbq_manager::select_integer(unsynch_mpq_manager & qm, mpq const & lower, mpq const & upper, mpz & r) { + mpz & ceil_lower = m_select_int_tmp1; + mpz & floor_upper = m_select_int_tmp2; + + if (qm.is_int(lower)) { + m_manager.set(ceil_lower, lower.numerator()); + m_manager.inc(ceil_lower); + } + else { + scoped_mpz tmp(qm); + qm.ceil(lower, tmp); + m_manager.set(ceil_lower, tmp); + } + + if (qm.is_int(upper)) { + m_manager.set(floor_upper, upper.numerator()); + m_manager.dec(floor_upper); + } + else { + scoped_mpz tmp(qm); + qm.floor(upper, tmp); + m_manager.set(floor_upper, tmp); + } + + if (m_manager.le(ceil_lower, floor_upper)) { + m_manager.set(r, ceil_lower); + return true; + } + return false; +} + +#define LINEAR_SEARCH_THRESHOLD 8 + +void mpbq_manager::select_small_core(mpbq const & lower, mpbq const & upper, mpbq & r) { + SASSERT(le(lower, upper)); + mpz & aux = m_select_small_tmp; + if (select_integer(lower, upper, aux)) { + set(r, aux); + return; + } + + // At this point we know that k=0 does not work, since there is no integer + // in the interval [lower, upper] + unsigned min_k = 0; + unsigned max_k = std::min(lower.m_k, upper.m_k); + + if (max_k <= LINEAR_SEARCH_THRESHOLD) { + unsigned k = 0; + mpbq & l2k = m_select_small_tmp1; + mpbq & u2k = m_select_small_tmp2; + set(l2k, lower); + set(u2k, upper); + while (true) { + k++; + mul2(l2k); + mul2(u2k); + if (select_integer(l2k, u2k, aux)) { + set(r, aux, k); + break; + } + } + } + else { + mpbq & l2k = m_select_small_tmp1; + mpbq & u2k = m_select_small_tmp2; + while (true) { + unsigned mid_k = min_k + (max_k - min_k)/2; + set(l2k, lower); + set(u2k, upper); + mul2k(l2k, mid_k); + mul2k(u2k, mid_k); + if (select_integer(l2k, u2k, aux)) + max_k = mid_k; + else + min_k = mid_k + 1; + if (min_k == max_k) { + if (max_k == mid_k) { + set(r, aux, max_k); + } + else { + set(l2k, lower); + set(u2k, upper); + mul2k(l2k, max_k); + mul2k(u2k, max_k); + VERIFY(select_integer(l2k, u2k, aux)); + set(r, aux, max_k); + } + break; + } + } + } + SASSERT(le(lower, r)); + SASSERT(le(r, upper)); +} + +bool mpbq_manager::select_small(mpbq const & lower, mpbq const & upper, mpbq & r) { + if (gt(lower, upper)) + return false; + select_small_core(lower, upper, r); + return true; +} + + +void mpbq_manager::select_small_core(unsynch_mpq_manager & qm, mpq const & lower, mpbq const & upper, mpbq & r) { + TRACE("select_small", tout << "lower (q): " << qm.to_string(lower) << ", upper (bq): " << to_string(upper) << "\n";); + SASSERT(gt(upper, lower)); + mpz & aux = m_select_small_tmp; + if (select_integer(qm, lower, upper, aux)) { + set(r, aux); + return; + } + + // At this point we know that k=0 does not work, since there is no integer + // in the interval [lower, upper] + unsigned k = 0; + scoped_mpq l2k(qm); + mpq two(2); + mpbq & u2k = m_select_small_tmp2; + qm.set(l2k, lower); + set(u2k, upper); + while (true) { + k++; + qm.mul(l2k, two, l2k); + mul2(u2k); + if (select_integer(qm, l2k, u2k, aux)) { + set(r, aux, k); + break; + } + } +} + +void mpbq_manager::select_small_core(unsynch_mpq_manager & qm, mpbq const & lower, mpq const & upper, mpbq & r) { + SASSERT(lt(lower, upper)); + mpz & aux = m_select_small_tmp; + if (select_integer(qm, lower, upper, aux)) { + set(r, aux); + return; + } + + // At this point we know that k=0 does not work, since there is no integer + // in the interval [lower, upper] + unsigned k = 0; + mpbq & l2k = m_select_small_tmp2; + scoped_mpq u2k(qm); + mpq two(2); + set(l2k, lower); + qm.set(u2k, upper); + while (true) { + k++; + mul2(l2k); + qm.mul(u2k, two, u2k); + if (select_integer(qm, l2k, u2k, aux)) { + set(r, aux, k); + break; + } + } +} + +void mpbq_manager::select_small_core(unsynch_mpq_manager & qm, mpq const & lower, mpq const & upper, mpbq & r) { + SASSERT(qm.lt(lower, upper)); + mpz & aux = m_select_small_tmp; + if (select_integer(qm, lower, upper, aux)) { + set(r, aux); + return; + } + + // At this point we know that k=0 does not work, since there is no integer + // in the interval [lower, upper] + unsigned k = 0; + scoped_mpq l2k(qm); + scoped_mpq u2k(qm); + mpq two(2); + qm.set(l2k, lower); + qm.set(u2k, upper); + while (true) { + k++; + qm.mul(l2k, two, l2k); + qm.mul(u2k, two, u2k); + if (select_integer(qm, l2k, u2k, aux)) { + set(r, aux, k); + break; + } + } +} + +void mpbq_manager::approx(mpbq & a, unsigned k, bool to_plus_inf) { + if (a.m_k <= k) + return; +#ifdef MPBQ_DEBUG + scoped_mpbq old_a(*this); + old_a = a; +#endif + bool sgn = m_manager.is_neg(a.m_num); + bool _inc = (sgn != to_plus_inf); + unsigned shift = a.m_k - k; + m_manager.abs(a.m_num); + m_manager.machine_div2k(a.m_num, shift); + if (_inc) + m_manager.inc(a.m_num); + if (sgn) + m_manager.neg(a.m_num); + a.m_k = k; + normalize(a); +#ifdef MPBQ_DEBUG + if (to_plus_inf) { + SASSERT(lt(old_a, a)); + } + else { + SASSERT(lt(a, old_a)); + } +#endif +} + +void mpbq_manager::approx_div(mpbq const & a, mpbq const & b, mpbq & c, unsigned k, bool to_plus_inf) { + SASSERT(!is_zero(b)); + unsigned k_prime; + if (m_manager.is_power_of_two(b.m_num, k_prime)) { + // The division is precise, so we ignore k and to_plus_inf + SASSERT(b.m_k == 0); // remark: b.m_num is odd when b.m_k > 0 + m_manager.set(c.m_num, a.m_num); + if (a.m_k == 0) { + c.m_k = k_prime; + normalize(c); + } + else { + c.m_k = a.m_k + k_prime; + // there is not need to normalize since the least significant bit of a must be 1. + } + } + else if (m_manager.divides(b.m_num, a.m_num)) { + // result is also precise + m_manager.div(a.m_num, b.m_num, c.m_num); + if (a.m_k >= b.m_k) { + c.m_k = a.m_k - b.m_k; + } + else { + m_manager.mul2k(c.m_num, b.m_k - a.m_k); + c.m_k = 0; + } + normalize(c); + } + else { + bool sgn = is_neg(a) != is_neg(b); + mpz & abs_a = m_div_tmp1; + mpz & norm_a = m_div_tmp2; + mpz & abs_b = m_div_tmp3; + m_manager.set(abs_a, a.m_num); + m_manager.abs(abs_a); + m_manager.set(abs_b, b.m_num); + m_manager.abs(abs_b); + if (a.m_k > b.m_k) { + if (k >= a.m_k - b.m_k) + m_manager.mul2k(abs_a, k - (a.m_k - b.m_k), norm_a); + else + m_manager.machine_div2k(abs_a, (a.m_k - b.m_k) - k, norm_a); + } + else { + m_manager.mul2k(abs_a, k + b.m_k - a.m_k, norm_a); + } + c.m_k = k; + m_manager.div(norm_a, abs_b, c.m_num); + if (sgn != to_plus_inf) + m_manager.inc(c.m_num); + if (sgn) + m_manager.neg(c.m_num); + normalize(c); + } +} diff --git a/lib/mpbq.h b/lib/mpbq.h new file mode 100644 index 000000000..3427ab59f --- /dev/null +++ b/lib/mpbq.h @@ -0,0 +1,362 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + mpbq.h + +Abstract: + + Binary Rational Numbers + + A binary rational is a number of the form a/2^k. + All integers are binary rationals. + Binary rational numbers can be implemented more efficiently than rationals. + Binary rationals form a Ring. + They are not closed under division. + In Z3, they are used to implement algebraic numbers. + The root isolation operations only use division by 2. + +Author: + + Leonardo de Moura (leonardo) 2011-11-24. + +Revision History: + +--*/ +#ifndef _MPBQ_H_ +#define _MPBQ_H_ + +#include"mpq.h" +#include"rational.h" +#include"vector.h" + +class mpbq { + mpz m_num; + unsigned m_k; // we don't need mpz here. 2^(2^32-1) is a huge number, we will not even be able to convert the mpbq into an mpq + friend class mpbq_manager; +public: + mpbq():m_num(0), m_k(0) {} + mpbq(int v):m_num(v), m_k(0) {} + mpbq(int v, unsigned k):m_num(v), m_k(k) {} + mpz const & numerator() const { return m_num; } + unsigned k() const { return m_k; } + void swap(mpbq & other) { m_num.swap(other.m_num); std::swap(m_k, other.m_k); } +}; + +inline void swap(mpbq & m1, mpbq & m2) { m1.swap(m2); } + +typedef svector mpbq_vector; + +class mpbq_manager { + unsynch_mpz_manager & m_manager; + mpz m_tmp; + mpz m_tmp2; + mpbq m_addmul_tmp; + mpz m_select_int_tmp1; + mpz m_select_int_tmp2; + mpz m_select_small_tmp; + mpbq m_select_small_tmp1; + mpbq m_select_small_tmp2; + mpz m_div_tmp1, m_div_tmp2, m_div_tmp3; + void normalize(mpbq & a); + bool select_integer(mpbq const & lower, mpbq const & upper, mpz & r); + bool select_integer(unsynch_mpq_manager & qm, mpq const & lower, mpbq const & upper, mpz & r); + bool select_integer(unsynch_mpq_manager & qm, mpbq const & lower, mpq const & upper, mpz & r); + bool select_integer(unsynch_mpq_manager & qm, mpq const & lower, mpq const & upper, mpz & r); + +public: + static bool precise() { return true; } + static bool field() { return false; } + typedef mpbq numeral; + + mpbq_manager(unsynch_mpz_manager & m); + ~mpbq_manager(); + + static void swap(mpbq & a, mpbq & b) { a.swap(b); } + + void del(mpbq & a) { m_manager.del(a.m_num); } + void reset(mpbq & a) { m_manager.reset(a.m_num); a.m_k = 0; } + void reset(mpbq_vector & v); + void set(mpbq & a, int n) { m_manager.set(a.m_num, n); a.m_k = 0; } + void set(mpbq & a, unsigned n) { m_manager.set(a.m_num, n); a.m_k = 0; } + void set(mpbq & a, int n, unsigned k) { m_manager.set(a.m_num, n); a.m_k = k; normalize(a); } + void set(mpbq & a, mpz const & n, unsigned k) { m_manager.set(a.m_num, n); a.m_k = k; normalize(a); } + void set(mpbq & a, mpz const & n) { m_manager.set(a.m_num, n); a.m_k = 0; } + void set(mpbq & a, mpbq const & b) { m_manager.set(a.m_num, b.m_num); a.m_k = b.m_k; } + void set(mpbq & a, int64 n, unsigned k) { m_manager.set(a.m_num, n); a.m_k = k; normalize(a); } + + bool is_int(mpbq const & a) const { return a.m_k == 0; } + void get_numerator(mpbq const & a, mpz & n) { m_manager.set(n, a.m_num); } + unsigned get_denominator_power(mpbq const & a) { return a.m_k; } + + bool is_zero(mpbq const & a) const { return m_manager.is_zero(a.m_num); } + bool is_nonzero(mpbq const & a) const { return !is_zero(a); } + bool is_one(mpbq const & a) const { return a.m_k == 0 && m_manager.is_one(a.m_num); } + bool is_pos(mpbq const & a) const { return m_manager.is_pos(a.m_num); } + bool is_neg(mpbq const & a) const { return m_manager.is_neg(a.m_num); } + bool is_nonpos(mpbq const & a) const { return m_manager.is_nonpos(a.m_num); } + bool is_nonneg(mpbq const & a) const { return m_manager.is_nonneg(a.m_num); } + + void add(mpbq const & a, mpbq const & b, mpbq & r); + void add(mpbq const & a, mpz const & b, mpbq & r); + void sub(mpbq const & a, mpbq const & b, mpbq & r); + void sub(mpbq const & a, mpz const & b, mpbq & r); + void mul(mpbq const & a, mpbq const & b, mpbq & r); + void mul(mpbq const & a, mpz const & b, mpbq & r); + // r <- a + b*c + void addmul(mpbq const & a, mpbq const & b, mpbq const & c, mpbq & r) { + mul(b, c, m_addmul_tmp); + add(a, m_addmul_tmp, r); + } + void addmul(mpbq const & a, mpz const & b, mpbq const & c, mpbq & r) { + mul(c, b, m_addmul_tmp); + add(a, m_addmul_tmp, r); + } + void neg(mpbq & a) { m_manager.neg(a.m_num); } + // when dividing by 2, we only need to normalize if m_k was zero. + void div2(mpbq & a) { bool old_k_zero = (a.m_k == 0); a.m_k++; if (old_k_zero) normalize(a); } + void div2k(mpbq & a, unsigned k) { bool old_k_zero = (a.m_k == 0); a.m_k += k; if (old_k_zero) normalize(a); } + void mul2(mpbq & a); + void mul2k(mpbq & a, unsigned k); + void power(mpbq & a, unsigned k); + void power(mpbq const & a, unsigned k, mpbq & b) { set(b, a); power(b, k); } + + /** + \brief Return true if a^{1/n} is a binary rational, and store the result in a. + Otherwise, return false and return an lower bound based on the integer root of the + numerator and denominator/n + */ + bool root_lower(mpbq & a, unsigned n); + bool root_lower(mpbq const & a, unsigned n, mpbq & r) { set(r, a); return root_lower(r, n); } + + /** + \brief Return true if a^{1/n} is a binary rational, and store the result in a. + Otherwise, return false and return an upper bound based on the integer root of the + numerator and denominator/n + */ + bool root_upper(mpbq & a, unsigned n); + bool root_upper(mpbq const & a, unsigned n, mpbq & r) { set(r, a); return root_upper(r, n); } + + bool eq(mpbq const & a, mpbq const & b) { return a.m_k == b.m_k && m_manager.eq(a.m_num, b.m_num); } + bool lt(mpbq const & a, mpbq const & b); + bool neq(mpbq const & a, mpbq const & b) { return !eq(a, b); } + bool gt(mpbq const & a, mpbq const & b) { return lt(b, a); } + bool ge(mpbq const & a, mpbq const & b) { return le(b, a); } + bool le(mpbq const & a, mpbq const & b) { return !gt(a, b); } + + bool eq(mpbq const & a, mpq const & b); + bool lt(mpbq const & a, mpq const & b); + bool le(mpbq const & a, mpq const & b); + bool neq(mpbq const & a, mpq const & b) { return !eq(a, b); } + bool gt(mpbq const & a, mpq const & b) { return !le(a, b); } + bool ge(mpbq const & a, mpq const & b) { return !lt(a, b); } + + bool eq(mpbq const & a, mpz const & b) { return m_manager.eq(a.m_num, b) && a.m_k == 0; } + bool lt(mpbq const & a, mpz const & b); + bool le(mpbq const & a, mpz const & b); + bool neq(mpbq const & a, mpz const & b) { return !eq(a, b); } + bool gt(mpbq const & a, mpz const & b) { return !le(a, b); } + bool ge(mpbq const & a, mpz const & b) { return !lt(a, b); } + + /** + \brief Return the magnitude of a = b/2^k. + It is defined as: + a == 0 -> 0 + a > 0 -> log2(b) - k Note that 2^{log2(b) - k} <= a <= 2^{log2(b) - k + 1} + a < 0 -> mlog2(b) - k + 1 Note that -2^{mlog2(b) - k + 1} <= a <= -2^{mlog2(b) - k} + + Remark: mlog2(b) = log2(-b) + + Examples: + + 5/2^3 log2(5) - 3 = -1 + 21/2^2 log2(21) - 2 = 2 + -3/2^4 log2(3) - 4 + 1 = -2 + */ + int magnitude_lb(mpbq const & a); + + /** + \brief Similar to magnitude_lb + + a == 0 -> 0 + a > 0 -> log2(b) - k + 1 a <= 2^{log2(b) - k + 1} + a < 0 -> mlog2(b) - k a <= -2^{mlog2(b) - k} + */ + int magnitude_ub(mpbq const & a); + + /** + \brief Return true if a < 1/2^k + */ + bool lt_1div2k(mpbq const & a, unsigned k); + + std::string to_string(mpbq const & a); + + /** + \brief Return true if q (= c/d) is a binary rational, + and store it in bq (as a binary rational). + Otherwise return false, and set bq to c/2^{k+1} + where k = log2(d) + */ + bool to_mpbq(mpq const & q, mpbq & bq); + + /** + \brief Given a rational q which cannot be represented as a binary rational, + and an interval (l, u) s.t. l < q < u. This method stores in u, a u' s.t. + q < u' < u. + In the refinement process, the lower bound l may be also refined to l' + s.t. l < l' < q + */ + void refine_upper(mpq const & q, mpbq & l, mpbq & u); + + /** + \brief Similar to refine_upper. + */ + void refine_lower(mpq const & q, mpbq & l, mpbq & u); + + template + void floor(mpz_manager & m, mpbq const & a, mpz & f) { + if (is_int(a)) { + m.set(f, a.m_num); + return; + } + bool is_neg_num = is_neg(a); + m.machine_div2k(a.m_num, a.m_k, f); + if (is_neg_num) + m.sub(f, mpz(1), f); + } + + template + void ceil(mpz_manager & m, mpbq const & a, mpz & c) { + if (is_int(a)) { + m.set(c, a.m_num); + return; + } + bool is_pos_num = is_pos(a); + m.machine_div2k(a.m_num, a.m_k, c); + if (is_pos_num) + m.add(c, mpz(1), c); + } + + /** + \brief Select some number in the interval [lower, upper]. + Return true if succeeded, and false if lower > upper. + This method tries to minimize the size (in bits) of r. + For example, it will select an integer in [lower, upper] + if the interval contains one. + */ + bool select_small(mpbq const & lower, mpbq const & upper, mpbq & r); + /** + \brief Similar to select_small, but assumes lower <= upper + */ + void select_small_core(mpbq const & lower, mpbq const & upper, mpbq & r); + + // Select some number in the interval (lower, upper] + void select_small_core(unsynch_mpq_manager & qm, mpq const & lower, mpbq const & upper, mpbq & r); + // Select some number in the interval [lower, upper) + void select_small_core(unsynch_mpq_manager & qm, mpbq const & lower, mpq const & upper, mpbq & r); + // Select some number in the interval (lower, upper) + void select_small_core(unsynch_mpq_manager & qm, mpq const & lower, mpq const & upper, mpbq & r); + + + void display(std::ostream & out, mpbq const & a); + void display_decimal(std::ostream & out, mpbq const & a, unsigned prec = 8); + /** + \brief Display a in decimal while its digits match b digits. + This function is useful when a and b are representing an interval [a,b] which + contains an algebraic number + */ + void display_decimal(std::ostream & out, mpbq const & a, mpbq const & b, unsigned prec); + + void display_smt2(std::ostream & out, mpbq const & a, bool decimal); + + /** + \brief Approximate n as b/2^k' s.t. k' <= k. + + if get_denominator_power(n) <= k, then n is not modified. + + if get_denominator_power(n) > k, then + if to_plus_inf, old(n) < b/2^k' + otherwise, b/2^k' < old(n) + */ + void approx(mpbq & n, unsigned k, bool to_plus_inf); + + /** + \brief Approximated division c <- a/b + + The result is precise when: + 1) b is a power of two + 2) get_numerator(b) divides get_numerator(a) + + When the result is not precise, |c - a/b| <= 1/2^k + Actually, we have that + to_plus_inf => c - a/b <= 1/2^k + not to_plus_inf => a/b - c <= 1/2^k + */ + void approx_div(mpbq const & a, mpbq const & b, mpbq & c, unsigned k=32, bool to_plus_inf=false); +}; + +/** + \brief Convert a binary rational into a rational +*/ +template +void to_mpq(mpq_manager & m, mpbq const & source, mpq & target) { + mpq two(2); + m.power(two, source.k(), target); + m.inv(target); + m.mul(source.numerator(), target, target); +} + +/** + \brief Convert a binary rational into a rational. +*/ +rational to_rational(mpbq const & m); + +typedef _scoped_numeral scoped_mpbq; +typedef _scoped_numeral_vector scoped_mpbq_vector; + + +#define MPBQ_MK_COMPARISON_CORE(EXTERNAL, INTERNAL, TYPE) \ +inline bool EXTERNAL(scoped_mpbq const & a, TYPE const & b) { \ + mpbq_manager & m = a.m(); \ + scoped_mpbq _b(m); \ + m.set(_b, b); \ + return m.INTERNAL(a, _b); \ +} + +#define MPBQ_MK_COMPARISON(EXTERNAL, INTERNAL) \ +MPBQ_MK_COMPARISON_CORE(EXTERNAL, INTERNAL, int) \ +MPBQ_MK_COMPARISON_CORE(EXTERNAL, INTERNAL, mpz) \ + +MPBQ_MK_COMPARISON(operator==, eq); +MPBQ_MK_COMPARISON(operator!=, neq); +MPBQ_MK_COMPARISON(operator<, lt); +MPBQ_MK_COMPARISON(operator<=, le); +MPBQ_MK_COMPARISON(operator>, gt); +MPBQ_MK_COMPARISON(operator>=, ge); + +#undef MPBQ_MK_COMPARISON +#undef MPBQ_MK_COMPARISON_CORE + +#define MPBQ_MK_BINARY_CORE(EXTERNAL, INTERNAL, TYPE) \ +inline scoped_mpbq EXTERNAL(scoped_mpbq const & a, TYPE const & b) { \ + mpbq_manager & m = a.m(); \ + scoped_mpbq _b(m); \ + m.set(_b, b); \ + scoped_mpbq r(m); \ + m.INTERNAL(a, _b, r); \ + return r; \ +} + +#define MPBQ_MK_BINARY(EXTERNAL, INTERNAL) \ +MPBQ_MK_BINARY_CORE(EXTERNAL, INTERNAL, int) \ +MPBQ_MK_BINARY_CORE(EXTERNAL, INTERNAL, mpz) \ + +MPBQ_MK_BINARY(operator+, add) +MPBQ_MK_BINARY(operator-, sub) +MPBQ_MK_BINARY(operator*, mul) + +#undef MPBQ_MK_BINARY +#undef MPBQ_MK_BINARY_CORE + +#endif diff --git a/lib/mpbqi.h b/lib/mpbqi.h new file mode 100644 index 000000000..8a9171072 --- /dev/null +++ b/lib/mpbqi.h @@ -0,0 +1,50 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + mpbqi.h + +Abstract: + + Binary Rational Number Intervals + +Author: + + Leonardo de Moura (leonardo) 2012-01-04 + +Revision History: + +--*/ +#ifndef _MPBQI_H_ +#define _MPBQI_H_ + +#include"mpbq.h" +#include"basic_interval.h" + +class mpbqi_manager : public basic_interval_manager { + typedef basic_interval_manager super; +public: + mpbqi_manager(mpbq_manager & m):super(m) {} + + void set(interval & a, interval const & b) { super::set(a, b); } + void set(interval & a, bound const & lower, bound const & upper) { super::set(a, lower, upper); } + void set(interval & a, bound const & n) { super::set(a, n); } + void set(interval & a, mpz const & n) { + m().set(a.lower(), n); + m().set(a.upper(), n); + } + + void add(interval const & a, interval const & b, interval & c) { super::add(a, b, c); } + void add(interval const & a, mpz const & b, interval & c) { + m().add(a.lower(), b, c.lower()); + m().add(a.upper(), b, c.upper()); + } + + +}; + +typedef mpbqi_manager::interval mpbqi; +typedef mpbqi_manager::scoped_interval scoped_mpbqi; + +#endif diff --git a/lib/mpf.cpp b/lib/mpf.cpp new file mode 100644 index 000000000..6c542f1af --- /dev/null +++ b/lib/mpf.cpp @@ -0,0 +1,1572 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + mpf.cpp + +Abstract: + + Multi Precision Floating Point Numbers + +Author: + + Christoph Wintersteiger (cwinter) 2011-12-01. + +Revision History: + +--*/ +#include +#include"mpf.h" + +mpf::mpf() : + ebits(0), + sbits(0), + sign(false), + significand(0), + exponent(0) { +} + +void mpf::set(unsigned _ebits, unsigned _sbits) { + ebits = _ebits; + sbits = _sbits; + sign = false; + exponent = 0; +} + +mpf::mpf(unsigned _ebits, unsigned _sbits): + significand(0) { + set(ebits, sbits); +} + +mpf::mpf(mpf const & other) { + // It is safe if the mpz numbers are small. + // I need it for resize method in vector. + // UNREACHABLE(); +} + +mpf::~mpf() { +} + +void mpf::swap(mpf & other) { + unsigned tmp = ebits; + ebits = other.ebits; + other.ebits = tmp; + tmp = sbits; + sbits = other.sbits; + other.sbits = tmp; + tmp = sign; + sign = other.sign; + other.sign = tmp; + significand.swap(other.significand); + std::swap(exponent, other.exponent); +} + + +mpf_manager::mpf_manager() : + m_mpz_manager(m_mpq_manager), + m_powers2(m_mpz_manager) { +} + +mpf_manager::~mpf_manager() { +} + +void mpf_manager::set(mpf & o, unsigned ebits, unsigned sbits, int value) { + COMPILE_TIME_ASSERT(sizeof(int) == 4); + + o.sign = false; + o.ebits = ebits; + o.sbits = sbits; + + TRACE("mpf_dbg", tout << "set: value = " << value << std::endl;); + if (value == 0) { + mk_pzero(ebits, sbits, o); + } + else { + unsigned uval=value; + if (value < 0) { + o.sign = true; + if (value == INT_MIN) + uval = 0x80000000; + else + uval = -value; + } + + o.exponent = 31; + while ((uval & 0x80000000) == 0) { + uval <<= 1; + o.exponent--; + } + + m_mpz_manager.set(o.significand, uval & 0x7FFFFFFF); // remove the "1." part + + // align with sbits. + if (sbits > 31) + m_mpz_manager.mul2k(o.significand, sbits-32); + else + m_mpz_manager.machine_div2k(o.significand, 32-sbits); + } + + TRACE("mpf_dbg", tout << "set: res = " << to_string(o) << std::endl;); +} + +void mpf_manager::set(mpf & o, unsigned ebits, unsigned sbits, mpf_rounding_mode rm, int n, int d) { + scoped_mpq tmp(m_mpq_manager); + m_mpq_manager.set(tmp, n, d); + set(o, ebits, sbits, rm, tmp); +} + +void mpf_manager::set(mpf & o, unsigned ebits, unsigned sbits, double value) { + // double === mpf(11, 53) + COMPILE_TIME_ASSERT(sizeof(double) == 8); + + uint64 raw = *reinterpret_cast(&value); + bool sign = (raw >> 63) != 0; + int64 e = ((raw & 0x7FF0000000000000ull) >> 52) - 1023; + uint64 s = raw & 0x000FFFFFFFFFFFFFull; + + TRACE("mpf_dbg", tout << "set: " << value << " is: raw=" << raw << " (double)" << + " sign=" << sign << " s=" << s << " e=" << e << std::endl;); + + SASSERT(-1023 <= e && e <= +1024); + + o.ebits = ebits; + o.sbits = sbits; + o.sign = sign; + + if (e <= -((0x01ll<<(ebits-1))-1)) + o.exponent = mk_bot_exp(ebits); + else if (e >= (0x01ll<<(ebits-1))) + o.exponent = mk_top_exp(ebits); + else + o.exponent = e; + + m_mpz_manager.set(o.significand, s); + + if (sbits < 53) + m_mpz_manager.machine_div2k(o.significand, 53-sbits); + else if (sbits > 53) + m_mpz_manager.mul2k(o.significand, sbits-53); + + TRACE("mpf_dbg", tout << "set: res = " << to_string(o) << std::endl;); +} + +void mpf_manager::set(mpf & o, unsigned ebits, unsigned sbits, float value) { + // single === mpf(8, 24) + COMPILE_TIME_ASSERT(sizeof(float) == 4); + + unsigned int raw = *reinterpret_cast(&value); + bool sign = (raw >> 31) != 0; + signed int e = ((raw & 0x7F800000) >> 23) - 127; + unsigned int s = raw & 0x007FFFFF; + + TRACE("mpf_dbg", tout << "set: " << value << " is: raw=" << raw << " (float)" << + " sign=" << sign << " s=" << s << " e=" << e << std::endl;); + + SASSERT(-127 <= e && e <= +128); + + o.ebits = ebits; + o.sbits = sbits; + o.sign = sign; + + if (e <= -((0x01ll<<(ebits-1))-1)) + o.exponent = mk_bot_exp(ebits); + else if (e >= (0x01ll<<(ebits-1))) + o.exponent = mk_top_exp(ebits); + else + o.exponent = e; + + m_mpz_manager.set(o.significand, s); + + if (sbits < 24) + m_mpz_manager.machine_div2k(o.significand, 24-sbits); + else if (sbits > 24) + m_mpz_manager.mul2k(o.significand, sbits-24); + + TRACE("mpf_dbg", tout << "set: res = " << to_string_raw(o) << std::endl;); +} + +void mpf_manager::set(mpf & o, unsigned ebits, unsigned sbits, mpf_rounding_mode rm, mpq const & value) { + TRACE("mpf_dbg", tout << "set: " << m_mpq_manager.to_string(value) << " [" << ebits << "/" << sbits << "]"<< std::endl;); + + if (m_mpq_manager.is_zero(value)) + mk_pzero(ebits, sbits, o); + else { + o.ebits = ebits; + o.sbits = sbits; + o.sign = m_mpq_manager.is_neg(value); + + m_mpz_manager.set(o.significand, 0); + const mpz & p = m_powers2(sbits+2); + signed lz = 0; + + o.exponent = sbits+2; + + // CMW: This could be optimized considerably. + scoped_mpz t(m_mpz_manager); + retry: + m_mpz_manager.mul2k(value.numerator(), lz, t); + m_mpz_manager.machine_div(t, value.denominator(), o.significand); + m_mpz_manager.abs(o.significand); + if (m_mpz_manager.lt(o.significand, p)) { + lz++; + goto retry; + } + o.exponent -= lz; + + bool sticky = false; + while (m_mpz_manager.ge(o.significand, m_powers2(sbits+3))) { + sticky = sticky || !m_mpz_manager.is_even(o.significand); + m_mpz_manager.machine_div2k(o.significand, 1); + o.exponent++; + } + if (sticky && m_mpz_manager.is_even(o.significand)) + m_mpz_manager.inc(o.significand); + + TRACE("mpf_dbg", tout << "QUOTIENT = " << m_mpz_manager.to_string(o.significand) << " shift=" << lz << std::endl;); + + SASSERT(m_mpz_manager.ge(o.significand, m_powers2(sbits+2))); + round(rm, o); + } + + TRACE("mpf_dbg", tout << "set: res = " << to_string(o) << std::endl;); +} + +void mpf_manager::set(mpf & o, unsigned ebits, unsigned sbits, mpf_rounding_mode rm, char const * value) { + TRACE("mpf_dbg", tout << "set: " << value << " [" << ebits << "/" << sbits << "]"<< std::endl;); + + o.ebits = ebits; + o.sbits = sbits; + + // We expect [i].[f]P[e], where P means that the exponent is interpreted as 2^e instead of 10^e. + + std::string v(value); + size_t e_pos = v.find('p'); + if (e_pos == std::string::npos) e_pos = v.find('P'); + + std::string f, e; + + f = (e_pos != std::string::npos) ? v.substr(0, e_pos) : v; + e = (e_pos != std::string::npos) ? v.substr(e_pos+1) : "0"; + + TRACE("mpf_dbg", tout << " f = " << f << " e = " << e << std::endl;); + + // [Leo]: potential memory leak. moving q and ex to scoped versions + scoped_mpq q(m_mpq_manager); + m_mpq_manager.set(q, f.c_str()); + + scoped_mpz ex(m_mpq_manager); + m_mpz_manager.set(ex, e.c_str()); + + set(o, ebits, sbits, rm, q, ex); + + TRACE("mpf_dbg", tout << "set: res = " << to_string(o) << std::endl;); +} + +void mpf_manager::set(mpf & o, unsigned ebits, unsigned sbits, mpf_rounding_mode rm, mpq const & significand, mpz const & exponent) { + // Assumption: this represents significand * 2^exponent. + TRACE("mpf_dbg", tout << "set: sig = " << m_mpq_manager.to_string(significand) << " exp = " << m_mpz_manager.to_string(exponent) << std::endl;); + + o.ebits = ebits; + o.sbits = sbits; + o.sign = m_mpq_manager.is_neg(significand); + + if (m_mpq_manager.is_zero(significand)) + mk_zero(ebits, sbits, o.sign, o); + else { + // [Leo]: The following two lines may produce a memory leak. Moving to scoped version + // mpq sig; + // mpz exp; + scoped_mpq sig(m_mpq_manager); + scoped_mpz exp(m_mpq_manager); + + m_mpq_manager.set(sig, significand); + m_mpq_manager.abs(sig); + m_mpz_manager.set(exp, exponent); + + // Normalize + while (m_mpq_manager.ge(sig, 2)) { + m_mpq_manager.div(sig, mpq(2), sig); + m_mpz_manager.inc(exp); + } + + while (m_mpq_manager.lt(sig, 1)) { + m_mpq_manager.mul(sig, 2, sig); + m_mpz_manager.dec(exp); + } + + // 1.0 <= sig < 2.0 + SASSERT((m_mpq_manager.le(1, sig) && m_mpq_manager.lt(sig, 2))); + + TRACE("mpf_dbg", tout << "sig = " << m_mpq_manager.to_string(sig) << " exp = " << m_mpz_manager.to_string(exp) << std::endl;); + + m_mpz_manager.set(o.significand, 0); + for (unsigned i = 0; i < (sbits+3); i++) { + m_mpz_manager.mul2k(o.significand, 1); + if (!m_mpq_manager.lt(sig, 1)) { + m_mpz_manager.inc(o.significand); + m_mpq_manager.dec(sig); + } + m_mpq_manager.mul(sig, 2, sig); + } + + if (m_mpz_manager.is_small(exp)) { + o.exponent = m_mpz_manager.get_int64(exp); + round(rm, o); + } + else + mk_inf(ebits, sbits, o.sign, o); // CMW: output warning message? throw exception? + } + + TRACE("mpf_dbg", tout << "set: res = " << to_string(o) << std::endl;); +} + +void mpf_manager::set(mpf & o, unsigned ebits, unsigned sbits, bool sign, uint64 significand, int exponent) { + // Assumption: this represents (sign * -1) * (significand/2^sbits) * 2^exponent. + o.ebits = ebits; + o.sbits = sbits; + o.sign = sign; + m_mpz_manager.set(o.significand, significand); + o.exponent = exponent; + + DEBUG_CODE({ + SASSERT(m_mpz_manager.lt(o.significand, m_powers2(sbits-1))); + SASSERT(o.exponent <= mk_top_exp(ebits)); + SASSERT(o.exponent >= mk_bot_exp(ebits)); + }); +} + +void mpf_manager::set(mpf & o, unsigned ebits, unsigned sbits, bool sign, mpz const & significand, mpf_exp_t exponent) { + // Assumption: this represents (sign * -1) * (significand/2^sbits) * 2^exponent. + o.ebits = ebits; + o.sbits = sbits; + o.sign = sign; + m_mpz_manager.set(o.significand, significand); + o.exponent = exponent; +} + +void mpf_manager::set(mpf & o, mpf const & x) { + o.ebits = x.ebits; + o.sbits = x.sbits; + o.sign = x.sign; + o.exponent = x.exponent; + m_mpz_manager.set(o.significand, x.significand); +} + +void mpf_manager::set(mpf & o, unsigned ebits, unsigned sbits, mpf_rounding_mode rm, mpf const & x) { + if (is_nan(x)) + mk_nan(ebits, sbits, o); + else if (is_inf(x)) + mk_inf(ebits, sbits, x.sign, o); + else if (is_zero(x)) + mk_zero(ebits, sbits, x.sign, o); + else { + set(o, x); + unpack(o, true); + + o.ebits = ebits; + o.sbits = sbits; + + signed ds = sbits - x.sbits; + if (ds > 0) + { + m_mpz_manager.mul2k(o.significand, ds); + round(rm, o); + } + else if (ds < 0) + { + bool sticky = false; + while (ds < 0) + { + if (!m_mpz_manager.is_even(o.significand)) sticky = true; + m_mpz_manager.machine_div2k(o.significand, 1); + ds++; + } + if (sticky && m_mpz_manager.is_even(o.significand)) + m_mpz_manager.inc(o.significand); + round(rm, o); + } + } +} + +void mpf_manager::abs(mpf & o) { + o.sign = false; +} + +void mpf_manager::abs(mpf const & x, mpf & o) { + set(o, x); + abs(o); +} + +void mpf_manager::neg(mpf & o) { + if (!is_nan(o)) + o.sign = !o.sign; +} + +void mpf_manager::neg(mpf const & x, mpf & o) { + set(o, x); + neg(o); +} + +bool mpf_manager::is_zero(mpf const & x) { + return has_bot_exp(x) && m_mpz_manager.is_zero(sig(x)); +} + +bool mpf_manager::is_one(mpf const & x) { + return m_mpz_manager.is_zero(sig(x)) && exp(x) == 0; +} + +bool mpf_manager::is_neg(mpf const & x) { + return x.sign && !is_nan(x); +} + +bool mpf_manager::is_pos(mpf const & x) { + return !x.sign && !is_nan(x); +} + +bool mpf_manager::is_nzero(mpf const & x) { + return x.sign && is_zero(x); +} + +bool mpf_manager::is_pzero(mpf const & x) { + return !x.sign && is_zero(x); +} + +bool mpf_manager::eq(mpf const & x, mpf const & y) { + SASSERT(x.sbits == y.sbits && x.ebits == y.ebits); + if (is_nan(x) || is_nan(y)) + return false; + else if (is_zero(x) && is_zero(y)) + return true; + else if (sgn(x) != sgn(y)) + return false; + else + return exp(x)==exp(y) && m_mpz_manager.eq(sig(x), sig(y)); +} + +bool mpf_manager::lt(mpf const & x, mpf const & y) { + SASSERT(x.sbits == y.sbits && x.ebits == y.ebits); + if (is_nan(x) || is_nan(y)) + return false; + else if (is_zero(x) && is_zero(y)) + return false; + else if (sgn(x)) { + if (!sgn(y)) + return true; + else + // CMW: Problem with denormal numbers? + return exp(y) < exp(x) || + (exp(y) == exp(x) && m_mpz_manager.lt(sig(y), sig(x))); + } + else { // !sgn(x) + if (sgn(y)) + return false; + else + // CMW: Problem with denormal numbers? + return exp(x) < exp(y) || + (exp(x)==exp(y) && m_mpz_manager.lt(sig(x), sig(y))); + } +} + +bool mpf_manager::lte(mpf const & x, mpf const & y) { + SASSERT(x.sbits == y.sbits && x.ebits == y.ebits); + return lt(x, y) || eq(x, y); +} + +bool mpf_manager::gt(mpf const & x, mpf const & y) { + SASSERT(x.sbits == y.sbits && x.ebits == y.ebits); + if (is_nan(x) || is_nan(y)) + return false; + else if (is_zero(x) && is_zero(y)) + return false; + else + return !lte(x, y); +} + +bool mpf_manager::gte(mpf const & x, mpf const & y) { + SASSERT(x.sbits == y.sbits && x.ebits == y.ebits); + return gt(x, y) || eq(x, y); +} + +void mpf_manager::add(mpf_rounding_mode rm, mpf const & x, mpf const & y, mpf & o) { + add_sub(rm, x, y, o, false); +} + +void mpf_manager::sub(mpf_rounding_mode rm, mpf const & x, mpf const & y, mpf & o) { + add_sub(rm, x, y, o, true); +} + +void mpf_manager::add_sub(mpf_rounding_mode rm, mpf const & x, mpf const & y, mpf & o, bool sub) { + SASSERT(x.sbits == y.sbits && x.ebits == y.ebits); + + bool sgn_y = sgn(y) ^ sub; + + if (is_nan(x)) + mk_nan(x.ebits, x.sbits, o); + else if (is_nan(y)) + mk_nan(x.ebits, x.sbits, o); + else if (is_inf(x)) { + if (is_inf(y) && (sgn(x) ^ sgn_y)) + mk_nan(x.ebits, x.sbits, o); + else + set(o, x); + } + else if (is_inf(y)) { + if (is_inf(x) && (sgn(x) ^ sgn_y)) + mk_nan(x.ebits, x.sbits, o); + else { + set(o, y); + o.sign = sgn_y; + } + } + else if (is_zero(x) && is_zero(y)) { + if (sgn(x) && sgn_y) + set(o, x); + else if (rm == MPF_ROUND_TOWARD_NEGATIVE) + mk_nzero(x.ebits, x.sbits, o); + else + mk_pzero(x.ebits, x.sbits, o); + } + else if (is_zero(x)) { + set(o, y); + o.sign = sgn_y; + } + else if (is_zero(y)) + set(o, x); + else { + o.ebits = x.ebits; + o.sbits = x.sbits; + + SASSERT(is_normal(x) || is_denormal(x)); + SASSERT(is_normal(y) || is_denormal(y)); + + scoped_mpf a(*this), b(*this); + set(a, x); + set(b, y); + b.get().sign = sgn_y; + + // Unpack a/b, this inserts the hidden bit and adjusts the exponent. + unpack(a, true); + unpack(b, false); + + if (exp(b) > exp(a)) + a.swap(b); + + mpf_exp_t exp_delta = exp(a) - exp(b); + + SASSERT(exp(a) >= exp(b)); + SASSERT(exp_delta >= 0); + + mpf_exp_t u_delta = exp_delta; + if (u_delta > x.sbits+2) + u_delta = x.sbits+2; + + TRACE("mpf_dbg", tout << "A = " << to_string(a) << std::endl;); + TRACE("mpf_dbg", tout << "B = " << to_string(b) << std::endl;); + TRACE("mpf_dbg", tout << "d = " << u_delta << std::endl;); + + TRACE("mpf_dbg", tout << "UP A = " << to_string(a) << std::endl;); + TRACE("mpf_dbg", tout << "UP B = " << to_string(b) << std::endl;); + + // Introduce 3 extra bits into both numbers. + m_mpz_manager.mul2k(a.significand(), 3, a.significand()); + m_mpz_manager.mul2k(b.significand(), 3, b.significand()); + + // Alignment shift with sticky bit computation. + SASSERT(u_delta <= INT_MAX); + scoped_mpz sticky_rem(m_mpz_manager); + m_mpz_manager.machine_div_rem(b.significand(), m_powers2((int)u_delta), b.significand(), sticky_rem); + if (!m_mpz_manager.is_zero(sticky_rem) && m_mpz_manager.is_even(b.significand())) + m_mpz_manager.inc(b.significand()); + + TRACE("mpf_dbg", tout << "A' = " << m_mpz_manager.to_string(a.significand()) << std::endl;); + TRACE("mpf_dbg", tout << "B' = " << m_mpz_manager.to_string(b.significand()) << std::endl;); + + // Significand addition + if (sgn(a) ^ sgn(b)) { + TRACE("mpf_dbg", tout << "SUBTRACTING" << std::endl;); + m_mpz_manager.sub(a.significand(), b.significand(), o.significand); + } + else { + TRACE("mpf_dbg", tout << "ADDING" << std::endl;); + m_mpz_manager.add(a.significand(), b.significand(), o.significand); + } + + TRACE("mpf_dbg", tout << "sum[-2:sbits+2] = " << m_mpz_manager.to_string(o.significand) << std::endl;); + + if (m_mpz_manager.is_zero(o.significand)) + mk_zero(o.ebits, o.sbits, rm == MPF_ROUND_TOWARD_NEGATIVE, o); + else { + bool neg = m_mpz_manager.is_neg(o.significand); + TRACE("mpf_dbg", tout << "NEG=" << neg << std::endl;); + m_mpz_manager.abs(o.significand); + TRACE("mpf_dbg", tout << "fs[-1:sbits+2] = " << m_mpz_manager.to_string(o.significand) << std::endl;); + o.sign = ((!a.sign() && b.sign() && neg) || + ( a.sign() && !b.sign() && !neg) || + ( a.sign() && b.sign())); + o.exponent = a.exponent(); + round(rm, o); + } + } +} + +void mpf_manager::mul(mpf_rounding_mode rm, mpf const & x, mpf const & y, mpf & o) { + TRACE("mpf_mul_bug", tout << "rm: " << rm << "\n"; + tout << "X: " << to_string(x) << "\n"; + tout << "Y: " << to_string(y) << "\n";); + SASSERT(x.sbits == y.sbits && x.ebits == y.ebits); + + TRACE("mpf_dbg", tout << "X = " << to_string(x) << std::endl;); + TRACE("mpf_dbg", tout << "Y = " << to_string(y) << std::endl;); + + if (is_nan(x)) + mk_nan(x.ebits, x.sbits, o); + else if (is_nan(y)) + mk_nan(x.ebits, x.sbits, o); + else if (is_pinf(x)) { + if (is_zero(y)) + mk_nan(x.ebits, x.sbits, o); + else + mk_inf(x.ebits, x.sbits, sgn(y), o); + } + else if (is_pinf(y)) { + if (is_zero(x)) + mk_nan(x.ebits, x.sbits, o); + else + mk_inf(x.ebits, x.sbits, sgn(x), o); + } + else if (is_ninf(x)) { + if (is_zero(y)) + mk_nan(x.ebits, x.sbits, o); + else + mk_inf(x.ebits, x.sbits, !sgn(y), o); + } + else if (is_ninf(y)) { + if (is_zero(x)) + mk_nan(x.ebits, x.sbits, o); + else + mk_inf(x.ebits, x.sbits, !sgn(x), o); + } + else if (is_zero(x) || is_zero(y)) { + set(o, x); + o.sign = x.sign ^ y.sign; + } + else { + o.ebits = x.ebits; + o.sbits = x.sbits; + o.sign = x.sign ^ y.sign; + + scoped_mpf a(*this, x.ebits, x.sbits), b(*this, x.ebits, x.sbits); + set(a, x); + set(b, y); + unpack(a, true); + unpack(b, true); + + TRACE("mpf_dbg", tout << "A = " << to_string(a) << std::endl;); + TRACE("mpf_dbg", tout << "B = " << to_string(b) << std::endl;); + + o.exponent = a.exponent() + b.exponent(); + + TRACE("mpf_dbg", tout << "A' = " << m_mpz_manager.to_string(a.significand()) << std::endl;); + TRACE("mpf_dbg", tout << "B' = " << m_mpz_manager.to_string(b.significand()) << std::endl;); + + m_mpz_manager.mul(a.significand(), b.significand(), o.significand); + + TRACE("mpf_dbg", tout << "PRODUCT = " << to_string(o) << std::endl;); + + // Remove the extra bits, keeping a sticky bit. + scoped_mpz sticky_rem(m_mpz_manager); + m_mpz_manager.machine_div_rem(o.significand, m_powers2(x.sbits-4), o.significand, sticky_rem); + if (!m_mpz_manager.is_zero(sticky_rem) && m_mpz_manager.is_even(o.significand)) + m_mpz_manager.inc(o.significand); + + round(rm, o); + } + TRACE("mpf_mul_bug", tout << "result: " << to_string(o) << "\n";); +} + +void mpf_manager::div(mpf_rounding_mode rm, mpf const & x, mpf const & y, mpf & o) { + SASSERT(x.sbits == y.sbits && x.ebits == y.ebits); + + TRACE("mpf_dbg", tout << "X = " << to_string(x) << std::endl;); + TRACE("mpf_dbg", tout << "Y = " << to_string(y) << std::endl;); + + if (is_nan(x)) + mk_nan(x.ebits, x.sbits, o); + else if (is_nan(y)) + mk_nan(x.ebits, x.sbits, o); + else if (is_pinf(x)) { + if (is_inf(y)) + mk_nan(x.ebits, x.sbits, o); + else + mk_inf(x.ebits, x.sbits, sgn(y), o); + } + else if (is_pinf(y)) { + if (is_inf(x)) + mk_nan(x.ebits, x.sbits, o); + else + mk_zero(x.ebits, x.sbits, (x.sign ^ y.sign) == 1, o); + } + else if (is_ninf(x)) { + if (is_inf(y)) + mk_nan(x.ebits, x.sbits, o); + else + mk_inf(x.ebits, x.sbits, !sgn(y), o); + } + else if (is_ninf(y)) { + if (is_inf(x)) + mk_nan(x.ebits, x.sbits, o); + else + mk_zero(x.ebits, x.sbits, (x.sign ^ y.sign) == 1, o); + } + else if (is_zero(y)) { + if (is_zero(x)) + mk_nan(x.ebits, x.sbits, o); + else + mk_inf(x.ebits, x.sbits, sgn(x), o); + } + else { + o.ebits = x.ebits; + o.sbits = x.sbits; + o.sign = x.sign ^ y.sign; + + scoped_mpf a(*this), b(*this); + set(a, x); + set(b, y); + unpack(a, true); + unpack(b, true); + + TRACE("mpf_dbg", tout << "A = " << to_string(a) << std::endl;); + TRACE("mpf_dbg", tout << "B = " << to_string(b) << std::endl;); + + o.exponent = a.exponent() - b.exponent(); + + TRACE("mpf_dbg", tout << "A' = " << m_mpz_manager.to_string(a.significand()) << std::endl;); + TRACE("mpf_dbg", tout << "B' = " << m_mpz_manager.to_string(b.significand()) << std::endl;); + + unsigned extra_bits = x.sbits + 2; + m_mpz_manager.mul2k(a.significand(), x.sbits + extra_bits); + m_mpz_manager.machine_div(a.significand(), b.significand(), o.significand); + + TRACE("mpf_dbg", tout << "QUOTIENT = " << to_string(o) << std::endl;); + + // Remove the extra bits, keeping a sticky bit. + scoped_mpz sticky_rem(m_mpz_manager); + m_mpz_manager.machine_div_rem(o.significand, m_powers2(extra_bits-2), o.significand, sticky_rem); + if (!m_mpz_manager.is_zero(sticky_rem) && m_mpz_manager.is_even(o.significand)) + m_mpz_manager.inc(o.significand); + + TRACE("mpf_dbg", tout << "QUOTIENT' = " << to_string(o) << std::endl;); + + round(rm, o); + } +} + +void mpf_manager::fused_mul_add(mpf_rounding_mode rm, mpf const & x, mpf const & y, mpf const &z, mpf & o) { + // CMW: Is this precise enough? + mul(rm, x, y, o); + add(rm, o, z, o); +} + +void my_mpz_sqrt(unsynch_mpz_manager & m, unsigned sbits, bool odd_exp, mpz & in, mpz & o) { + scoped_mpz lower(m), upper(m); + scoped_mpz mid(m), product(m), diff(m); + // we have lower <= a.significand <= upper and we need 1.[52+3 bits] in the bounds. + // since we comapre upper*upper to a.significand further down, we need a.significand + // to be of twice the size. + m.set(lower, 1); + m.mul2k(lower, sbits+2-1); + + if (odd_exp) + m.mul2k(in, 4, upper); + else + m.mul2k(in, 3, upper); + + if (m.eq(lower, upper)) + m.set(o, lower); + + while (m.neq(lower, upper)) { + STRACE("mpf_dbg", tout << "SIG = " << m.to_string(in) << + " LOWER = " << m.to_string(lower) << + " UPPER = " << m.to_string(upper) << std::endl;); + m.sub(upper, lower, diff); + if (m.is_one(diff)) { + m.mul(lower, lower, product); + if (m.eq(product, in)) { + STRACE("mpf_dbg", tout << "choosing lower" << std::endl;); + m.set(o, lower); + } + else { + STRACE("mpf_dbg", tout << "choosing upper" << std::endl;); + m.set(o, upper); // chosing upper is like a sticky bit here. + } + break; + } + + m.add(lower, upper, mid); + m.machine_div2k(mid, 1); + m.mul(mid, mid, product); + + STRACE("mpf_dbg", tout << "MID = " << m.to_string(mid) << + " PROD = " << m.to_string(product) << std::endl;); + + if (m.lt(product, in)) + m.set(lower, mid); + else if (m.gt(product, in)) + m.set(upper, mid); + else { + SASSERT(m.eq(product, in)); + m.set(o, mid); + break; + } + } +} + +void mpf_manager::sqrt(mpf_rounding_mode rm, mpf const & x, mpf & o) { + SASSERT(x.ebits > 0 && x.sbits > 0); + + TRACE("mpf_dbg", tout << "X = " << to_string(x) << std::endl;); + + if (is_nan(x) || is_ninf(x)) + mk_nan(x.ebits, x.sbits, o); + else if (is_pinf(x)) + set(o, x); + else if (x.sign) { + if (!m_mpz_manager.is_zero(x.significand)) + mk_nan(x.ebits, x.sbits, o); + else + mk_nzero(x.ebits, x.sbits, o); + } + else { + o.ebits = x.ebits; + o.sbits = x.sbits; + o.sign = false; + + scoped_mpf a(*this); + set(a, x); + unpack(a, true); + + TRACE("mpf_dbg", tout << "A = " << to_string(a) << std::endl;); + + m_mpz_manager.mul2k(a.significand(), x.sbits + ((a.exponent() % 2)?6:5)); + // my_mpz_sqrt(m_mpz_manager, x.sbits, a.exponent % 2 ? true : false, a.significand, o.significand); + if (!m_mpz_manager.root(a.significand(), 2, o.significand)) + { + // If the result is inexact, it is 1 too large. + // We need a sticky bit in the last position here, so we fix that. + if (m_mpz_manager.is_even(o.significand)) + m_mpz_manager.dec(o.significand); + TRACE("mpf_dbg", tout << "dec'ed " << m_mpz_manager.to_string(o.significand) << std::endl;); + } + o.exponent = a.exponent() >> 1; + + round_sqrt(rm, o); + } + + TRACE("mpf_dbg", tout << "SQRT = " << to_string(o) << std::endl;); +} + +void mpf_manager::round_to_integral(mpf_rounding_mode rm, mpf const & x, mpf & o) { + SASSERT(x.ebits > 0 && x.sbits > 0); + + TRACE("mpf_dbg", tout << "X = " << to_string(x) << std::endl;); + + if (is_nan(x)) + mk_nan(x.ebits, x.sbits, o); + else if (is_inf(x)) + set(o, x); + else if (x.exponent < 0) + mk_zero(x.ebits, x.sbits, x.sign, o); + else if (x.exponent >= x.sbits-1) + set(o, x); + else { + SASSERT(x.exponent >= 0 && x.exponent < x.sbits-1); + + o.ebits = x.ebits; + o.sbits = x.sbits; + o.sign = x.sign; + + scoped_mpf a(*this); + set(a, x); + unpack(a, true); + + TRACE("mpf_dbg", tout << "A = " << to_string(a) << std::endl;); + + o.exponent = a.exponent(); + m_mpz_manager.set(o.significand, a.significand()); + + unsigned q = (unsigned) o.exponent; + unsigned shift = o.sbits-q-1; + TRACE("mpf_dbg", tout << "Q = " << q << " shift=" << shift << std::endl;); + m_mpz_manager.machine_div2k(o.significand, shift); + m_mpz_manager.mul2k(o.significand, shift+3); + + round(rm, o); + } + + TRACE("mpf_dbg", tout << "INTEGRAL = " << to_string(o) << std::endl;); +} + +void mpf_manager::to_mpz(mpf const & x, unsynch_mpz_manager & zm, mpz & o) { + // x is assumed to be unpacked. + SASSERT(x.exponent < INT_MAX); + + zm.set(o, x.significand); + if (x.sign) zm.neg(o); + int e = (int)x.exponent - x.sbits + 1; + if (e < 0) + zm.machine_div2k(o, -e); + else + zm.mul2k(o, e); +} + +void mpf_manager::rem(mpf const & x, mpf const & y, mpf & o) { + SASSERT(x.sbits == y.sbits && x.ebits == y.ebits); + + TRACE("mpf_dbg", tout << "X = " << to_string(x) << std::endl;); + TRACE("mpf_dbg", tout << "Y = " << to_string(y) << std::endl;); + + if (is_nan(x) || is_nan(y)) + mk_nan(x.ebits, x.sbits, o); + else if (is_inf(x)) + mk_nan(x.ebits, x.sbits, o); + else if (is_inf(y)) + set(o, x); + else if (is_zero(x)) + set(o, x); + else if (is_zero(y)) + mk_nan(x.ebits, x.sbits, o); + else { + o.ebits = x.ebits; + o.sbits = x.sbits; + o.sign = x.sign; + + scoped_mpf a(*this), b(*this); + set(a, x); + set(b, y); + unpack(a, true); + unpack(b, true); + + TRACE("mpf_dbg", tout << "A = " << to_string(a) << std::endl;); + TRACE("mpf_dbg", tout << "B = " << to_string(b) << std::endl;); + + if (a.exponent() < b.exponent()) + set(o, x); + else { + mpf_exp_t exp_diff = a.exponent() - b.exponent(); + SASSERT(exp_diff >= 0); + TRACE("mpf_dbg", tout << "exp_diff = " << exp_diff << std::endl;); + + SASSERT(exp_diff < INT_MAX); + // CMW: This requires rather a lot of memory. There are algorithms that trade space for time by + // computing only a small chunk of the remainder bits at a time. + unsigned extra_bits = (unsigned) exp_diff; + m_mpz_manager.mul2k(a.significand(), extra_bits); + m_mpz_manager.rem(a.significand(), b.significand(), o.significand); + + TRACE("mpf_dbg", tout << "REM' = " << to_string(o) << std::endl;); + + if (m_mpz_manager.is_zero(o.significand)) + mk_zero(o.ebits, o.sbits, o.sign, o); + else { + o.exponent = b.exponent(); + m_mpz_manager.mul2k(o.significand, 3); // rounding bits + round(MPF_ROUND_NEAREST_TEVEN, o); + } + } + } + + TRACE("mpf_dbg", tout << "REMAINDER = " << to_string(o) << std::endl;); +} + +void mpf_manager::maximum(mpf const & x, mpf const & y, mpf & o) { + if (is_nan(x)) + set(o, y); + else if (is_nan(y) || (sgn(y) && is_zero(x) && is_zero(y))) + set(o, x); + else if (gt(x, y)) + set(o, x); + else + set(o, y); +} + +void mpf_manager::minimum(mpf const & x, mpf const & y, mpf & o) { + if (is_nan(x)) + set(o, y); + else if (is_nan(y) || (sgn(x) && is_zero(x) && is_zero(y))) + set(o, x); + else if (lt(x, y)) + set(o, x); + else + set(o, y); +} + +std::string mpf_manager::to_string(mpf const & x) { + std::string res; + + if (is_nan(x)) + res = "NaN"; + else { + res = sgn(x) ? "-" : "+"; + + if (is_inf(x)) + res += "INF"; + else if (is_zero(x)) + res += "0"; + else { + scoped_mpz num(m_mpq_manager), denom(m_mpq_manager); + num = 0; + denom = 1; + mpf_exp_t exponent; + + if (is_denormal(x)) + exponent = mk_min_exp(x.ebits); + else { + m_mpz_manager.set(num, 1); + m_mpz_manager.mul2k(num, x.sbits-1, num); + exponent = exp(x); + } + + m_mpz_manager.add(num, sig(x), num); + m_mpz_manager.mul2k(denom, x.sbits-1, denom); + + //TRACE("mpf_dbg", tout << "SIG=" << m_mpq_manager.to_string(sig(x)) << std::endl; ); + //TRACE("mpf_dbg", tout << "NUM=" << m_mpq_manager.to_string(num) << std::endl;); + //TRACE("mpf_dbg", tout << "DEN=" << m_mpq_manager.to_string(denom) << std::endl;); + //TRACE("mpf_dbg", tout << "EXP=" << exponent << std::endl;); + + scoped_mpq r(m_mpq_manager); + m_mpq_manager.set(r, num); + m_mpq_manager.div(r, denom, r); + + std::stringstream ss; + m_mpq_manager.display_decimal(ss, r, x.sbits); + ss << "p" << exponent; // "p" means 2^exp + res += ss.str(); + } + } + + //DEBUG_CODE( + // res += " " + to_string_raw(x); + //); + + return res; +} + +std::string mpf_manager::to_rational_string(mpf const & x) { + scoped_mpq q(m_mpq_manager); + to_rational(x, q); + return m_mpq_manager.to_string(q); +} + +void mpf_manager::display_decimal(std::ostream & out, mpf const & a, unsigned k) { + scoped_mpq q(m_mpq_manager); + to_rational(a, q); + m_mpq_manager.display_decimal(out, q, k); +} + +void mpf_manager::display_smt2(std::ostream & out, mpf const & a, bool decimal) { + scoped_mpq q(m_mpq_manager); + to_rational(a, q); + m_mpq_manager.display_smt2(out, q, decimal); +} + +std::string mpf_manager::to_string_raw(mpf const & x) { + std::string res; + res += "["; + res += (x.sign?"-":"+"); + res += " "; + res += m_mpz_manager.to_string(sig(x)); + res += " "; + std::stringstream ss(""); + ss << exp(x); + res += ss.str(); + if (is_normal(x)) + res += " N"; + else + res += " D"; + res += "]"; + return res; +} + +void mpf_manager::to_rational(mpf const & x, unsynch_mpq_manager & qm, mpq & o) { + scoped_mpf a(*this); + scoped_mpz n(m_mpq_manager), d(m_mpq_manager); + set(a, x); + unpack(a, true); + + m_mpz_manager.set(n, a.significand()); + if (a.sign()) m_mpz_manager.neg(n); + m_mpz_manager.power(2, a.sbits() - 1, d); + if (a.exponent() >= 0) + m_mpz_manager.mul2k(n, (unsigned)a.exponent()); + else + m_mpz_manager.mul2k(d, (unsigned)-a.exponent()); + + qm.set(o, n, d); +} + +double mpf_manager::to_double(mpf const & x) { + SASSERT(x.ebits <= 11 && x.sbits <= 53); + uint64 raw = 0; + int64 sig = 0, exp = 0; + + sig = m_mpz_manager.get_uint64(x.significand); + sig <<= 53 - x.sbits; + + if (has_top_exp(x)) + exp = 1024; + else if (has_bot_exp(x)) + exp = -1023; + else + exp = x.exponent; + + exp += 1023; + + raw = (exp << 52) | sig; + + if (x.sign) + raw = raw | 0x8000000000000000ull; + + return *reinterpret_cast(&raw); +} + +float mpf_manager::to_float(mpf const & x) { + SASSERT(x.ebits <= 8 && x.sbits <= 24); + unsigned int raw = 0; + unsigned int sig = 0, exp = 0; + + uint64 q = m_mpz_manager.get_uint64(x.significand); + SASSERT(q < 4294967296ull); + sig = q & 0x00000000FFFFFFFF; + sig <<= 24 - x.sbits; + + if (has_top_exp(x)) + exp = +128; + else if (has_bot_exp(x)) + exp = -127; + else { + int64 q = x.exponent; + SASSERT(q < 4294967296ll); + exp = q & 0x00000000FFFFFFFF; + } + + exp += 127; + + raw = (exp << 23) | sig; + + if (x.sign) + raw = raw | 0x80000000; + + return *reinterpret_cast(&raw); +} + +bool mpf_manager::is_nan(mpf const & x) { + return has_top_exp(x) && !m_mpz_manager.is_zero(sig(x)); +} + +bool mpf_manager::is_inf(mpf const & x) { + return has_top_exp(x) && m_mpz_manager.is_zero(sig(x)); +} + +bool mpf_manager::is_pinf(mpf const & x) { + return !x.sign && is_inf(x); +} + +bool mpf_manager::is_ninf(mpf const & x) { + return x.sign && is_inf(x); +} + +bool mpf_manager::is_normal(mpf const & x) { + return !has_bot_exp(x) && !has_top_exp(x); +} + +bool mpf_manager::is_denormal(mpf const & x) { + return has_bot_exp(x); +} + +bool mpf_manager::is_int(mpf const & x) { + if (!is_normal(x)) + return false; + + if (exp(x) >= x.sbits-1) + return true; + else if (exp(x) < 0) + return false; + else + { + SASSERT(x.exponent >= 0 && x.exponent < x.sbits-1); + + scoped_mpz t(m_mpz_manager); + m_mpz_manager.set(t, sig(x)); + unsigned shift = x.sbits - ((unsigned)exp(x)) - 1; + do { + if (m_mpz_manager.is_odd(t)) + return false; + m_mpz_manager.machine_div2k(t, 1); + } + while (--shift != 0); + + return true; + } +} + +bool mpf_manager::has_bot_exp(mpf const & x) { + return exp(x) == mk_bot_exp(x.ebits); +} + +bool mpf_manager::has_top_exp(mpf const & x) { + return exp(x) == mk_top_exp(x.ebits); +} + +mpf_exp_t mpf_manager::mk_bot_exp(unsigned ebits) { + SASSERT(ebits > 0); + return m_mpz_manager.get_int64(m_powers2.m1(ebits-1, true)); +} + +mpf_exp_t mpf_manager::mk_top_exp(unsigned ebits) { + SASSERT(ebits > 0); + return m_mpz_manager.get_int64(m_powers2(ebits-1)); +} + +mpf_exp_t mpf_manager::mk_min_exp(unsigned ebits) { + SASSERT(ebits > 0); + mpf_exp_t r = m_mpz_manager.get_int64(m_powers2.m1(ebits-1, true)); + return r+1; +} + +mpf_exp_t mpf_manager::mk_max_exp(unsigned ebits) { + SASSERT(ebits > 0); + return m_mpz_manager.get_int64(m_powers2.m1(ebits-1, false)); +} + +void mpf_manager::mk_nzero(unsigned ebits, unsigned sbits, mpf & o) { + o.sbits = sbits; + o.ebits = ebits; + o.exponent = mk_bot_exp(ebits); + m_mpz_manager.set(o.significand, 0); + o.sign = true; +} + +void mpf_manager::mk_pzero(unsigned ebits, unsigned sbits, mpf & o) { + o.sbits = sbits; + o.ebits = ebits; + o.exponent = mk_bot_exp(ebits); + m_mpz_manager.set(o.significand, 0); + o.sign = false; +} + +void mpf_manager::mk_zero(unsigned ebits, unsigned sbits, bool sign, mpf & o) { + if (sign) + mk_nzero(ebits, sbits, o); + else + mk_pzero(ebits, sbits, o); +} + +void mpf_manager::mk_nan(unsigned ebits, unsigned sbits, mpf & o) { + o.sbits = sbits; + o.ebits = ebits; + o.exponent = mk_top_exp(ebits); + // This is a quiet NaN, i.e., the first bit should be 1. + m_mpz_manager.set(o.significand, m_powers2(sbits-1)); + m_mpz_manager.dec(o.significand); + o.sign = false; +} + +void mpf_manager::mk_max_value(unsigned ebits, unsigned sbits, bool sign, mpf & o) { + o.sbits = sbits; + o.ebits = ebits; + o.sign = sign; + o.exponent = mk_top_exp(ebits) - 1; + m_mpz_manager.set(o.significand, m_powers2.m1(sbits-1, false)); +} + +void mpf_manager::mk_inf(unsigned ebits, unsigned sbits, bool sign, mpf & o) { + o.sbits = sbits; + o.ebits = ebits; + o.sign = sign; + o.exponent = mk_top_exp(ebits); + m_mpz_manager.set(o.significand, 0); +} + +void mpf_manager::mk_pinf(unsigned ebits, unsigned sbits, mpf & o) { + mk_inf(ebits, sbits, false, o); +} + +void mpf_manager::mk_ninf(unsigned ebits, unsigned sbits, mpf & o) { + mk_inf(ebits, sbits, true, o); +} + +void mpf_manager::unpack(mpf & o, bool normalize) { + // Insert the hidden bit or adjust the exponent of denormal numbers. + if (is_normal(o)) + m_mpz_manager.add(o.significand, m_powers2(o.sbits-1), o.significand); + else { + o.exponent = mk_bot_exp(o.ebits) + 1; + if (normalize && !m_mpz_manager.is_zero(o.significand)) { + const mpz & p = m_powers2(o.sbits-1); + while (m_mpz_manager.gt(p, o.significand)) { + o.exponent--; + m_mpz_manager.mul2k(o.significand, 1, o.significand); + } + } + } +} + +void mpf_manager::mk_round_inf(mpf_rounding_mode rm, mpf & o) { + if (!o.sign) { + if (rm == MPF_ROUND_TOWARD_ZERO || rm == MPF_ROUND_TOWARD_NEGATIVE) + mk_max_value(o.ebits, o.sbits, o.sign, o); + else + mk_inf(o.ebits, o.sbits, o.sign, o); + } + else { + if (rm == MPF_ROUND_TOWARD_ZERO || rm == MPF_ROUND_TOWARD_POSITIVE) + mk_max_value(o.ebits, o.sbits, o.sign, o); + else + mk_inf(o.ebits, o.sbits, o.sign, o); + } +} + +void mpf_manager::round(mpf_rounding_mode rm, mpf & o) { + // Assumptions: o.significand is of the form f[-1:0] . f[1:sbits-1] [guard,round,sticky], + // i.e., it has 2 + (sbits-1) + 3 = sbits + 4 bits. + + TRACE("mpf_dbg", tout << "RND: " << to_string(o) << std::endl;); + + // Structure of the rounder: + // (s, e_out, f_out) == (s, exprd(s, post(e, sigrd(s, f)))). + + bool UNFen = false; // Are these supposed to be persistent flags accross calls? + bool OVFen = false; + + mpf_exp_t e_max_norm = mk_max_exp(o.ebits); + mpf_exp_t e_min_norm = mk_min_exp(o.ebits); + scoped_mpz temporary(m_mpq_manager); + + TRACE("mpf_dbg", tout << "e_min_norm = " << e_min_norm << std::endl << + "e_max_norm = " << e_max_norm << std::endl;); + + const mpz & p_m1 = m_powers2(o.sbits+2); + const mpz & p_m2 = m_powers2(o.sbits+3); + + TRACE("mpf_dbg", tout << "p_m1 = " << m_mpz_manager.to_string(p_m1) << std::endl << + "p_m2 = " << m_mpz_manager.to_string(p_m2) << std::endl;); + + bool OVF1 = o.exponent > e_max_norm || // Exponent OVF + (o.exponent == e_max_norm && m_mpz_manager.ge(o.significand, p_m2)); + + TRACE("mpf_dbg", tout << "OVF1 = " << OVF1 << std::endl;); + + int lz = 0; + scoped_mpz t(m_mpq_manager); + m_mpz_manager.set(t, p_m2); + while (m_mpz_manager.gt(t, o.significand)) { + m_mpz_manager.machine_div2k(t, 1); + lz++; + } + + TRACE("mpf_dbg", tout << "LZ = " << lz << std::endl;); + + m_mpz_manager.set(t, o.exponent); + m_mpz_manager.inc(t); + m_mpz_manager.sub(t, lz, t); + m_mpz_manager.set(temporary, e_min_norm); + m_mpz_manager.sub(t, temporary, t); + bool TINY = m_mpz_manager.is_neg(t); + + TRACE("mpf_dbg", tout << "TINY = " << TINY << std::endl;); + + mpf_exp_t alpha = 3 << (o.ebits-2); + mpf_exp_t beta = o.exponent - lz + 1; + + TRACE("mpf_dbg", tout << "alpha = " << alpha << std::endl << + "beta = " << beta << std::endl; ); + + scoped_mpz sigma(m_mpq_manager); + sigma = 0; + + if (TINY && !UNFen) { + m_mpz_manager.set(sigma, o.exponent); + m_mpz_manager.sub(sigma, temporary, sigma); + m_mpz_manager.inc(sigma); + } + else + m_mpz_manager.set(sigma, lz); + + scoped_mpz limit(m_mpq_manager); + limit = o.sbits + 2; + m_mpz_manager.neg(limit); + if (m_mpz_manager.lt(sigma, limit)) { + m_mpz_manager.set(sigma, limit); + } + + // Normalization shift + + TRACE("mpf_dbg", tout << "Shift distance: " << m_mpz_manager.to_string(sigma) << " " << ((m_mpz_manager.is_nonneg(sigma))?"(LEFT)":"(RIGHT)") << std::endl;); + + bool sticky = !m_mpz_manager.is_even(o.significand); + m_mpz_manager.machine_div2k(o.significand, 1); // Let f' = f_r/2 + + if (!m_mpz_manager.is_zero(sigma)) { + if (m_mpz_manager.is_neg(sigma)) { // Right shift + unsigned sigma_uint = (unsigned) -m_mpz_manager.get_int64(sigma); // sigma is capped, this is safe. + if (sticky) + m_mpz_manager.machine_div2k(o.significand, sigma_uint); + else + { + scoped_mpz sticky_rem(m_mpz_manager); + m_mpz_manager.machine_div_rem(o.significand, m_powers2(sigma_uint), o.significand, sticky_rem); + sticky = !m_mpz_manager.is_zero(sticky_rem); + } + } + else { // Left shift + unsigned sh_m = static_cast(m_mpz_manager.get_int64(sigma)); + m_mpz_manager.mul2k(o.significand, sh_m, o.significand); + m_mpz_manager.set(sigma, 0); + } + } + + TRACE("mpf_dbg", tout << "Before sticky: " << to_string(o) << std::endl;); + + // Make sure o.significand is a [sbits+2] bit number (i.e. f1[0:sbits+1] == f1[0:sbits-1][round][sticky]) + sticky = sticky || !m_mpz_manager.is_even(o.significand); + m_mpz_manager.machine_div2k(o.significand, 1); + if (sticky && m_mpz_manager.is_even(o.significand)) + m_mpz_manager.inc(o.significand); + + if (OVF1 && OVFen) { + o.exponent = beta; + o.exponent -= alpha; + } + else if (TINY && UNFen) { + o.exponent = beta; + o.exponent += alpha; + } + else if (TINY && !UNFen) + o.exponent = e_min_norm; + else + o.exponent = beta; + + TRACE("mpf_dbg", tout << "Shifted: " << to_string(o) << std::endl;); + + const mpz & p_sig = m_powers2(o.sbits); + SASSERT(TINY || (m_mpz_manager.ge(o.significand, p_sig))); + + // Significand rounding (sigrd) + + sticky = !m_mpz_manager.is_even(o.significand); // new sticky bit! + m_mpz_manager.machine_div2k(o.significand, 1); + bool round = !m_mpz_manager.is_even(o.significand); + m_mpz_manager.machine_div2k(o.significand, 1); + + bool last = !m_mpz_manager.is_even(o.significand); + + TRACE("mpf_dbg", tout << "sign=" << o.sign << " last=" << last << " round=" << round << " sticky=" << sticky << std::endl;); + TRACE("mpf_dbg", tout << "before rounding decision: " << to_string(o) << std::endl;); + + // The significand has the right size now, but we might have to increment it + // depending on the sign, the last/round/sticky bits, and the rounding mode. + bool inc = false; + switch (rm) { + case MPF_ROUND_NEAREST_TEVEN: inc = round && (last || sticky); break; + case MPF_ROUND_NEAREST_TAWAY: inc = round; break; // CMW: Check this. + case MPF_ROUND_TOWARD_POSITIVE: inc = (!o.sign && (round || sticky)); break; + case MPF_ROUND_TOWARD_NEGATIVE: inc = (o.sign && (round || sticky)); break; + case MPF_ROUND_TOWARD_ZERO: inc = false; break; + default: UNREACHABLE(); + } + + if (inc) { + TRACE("mpf_dbg", tout << "Rounding increment -> significand +1" << std::endl;); + m_mpz_manager.inc(o.significand); + } + else + TRACE("mpf_dbg", tout << "Rounding increment -> significand +0" << std::endl;); + + TRACE("mpf_dbg", tout << "Rounded significand: " << to_string(o) << std::endl;); + + bool SIGovf = false; + + // Post normalization (post) + + if (m_mpz_manager.ge(o.significand, p_sig)) { + m_mpz_manager.machine_div2k(o.significand, 1); + o.exponent++; + } + + if (o.exponent > e_max_norm) + SIGovf = true; + + TRACE("mpf_dbg", tout << "Post-normalized: " << to_string(o) << std::endl;); + + TRACE("mpf_dbg", tout << "SIGovf = " << SIGovf << std::endl;); + + // Exponent rounding (exprd) + + bool o_has_max_exp = (o.exponent > e_max_norm); + bool OVF2 = SIGovf && o_has_max_exp; + + TRACE("mpf_dbg", tout << "OVF2 = " << OVF2 << std::endl;); + TRACE("mpf_dbg", tout << "o_has_max_exp = " << o_has_max_exp << std::endl;); + + if (!OVFen && SIGovf && o_has_max_exp) + mk_round_inf(rm, o); + else { + const mpz & p = m_powers2(o.sbits-1); + TRACE("mpf_dbg", tout << "P: " << m_mpz_manager.to_string(p_m1) << std::endl;); + + if (m_mpz_manager.ge(o.significand, p)) { + TRACE("mpf_dbg", tout << "NORMAL: " << m_mpz_manager.to_string(o.significand) << std::endl;); + m_mpz_manager.sub(o.significand, p, o.significand); + } + else { + TRACE("mpf_dbg", tout << "DENORMAL: " << m_mpz_manager.to_string(o.significand) << std::endl;); + o.exponent = mk_bot_exp(o.ebits); + } + } + + TRACE("mpf_dbg", tout << "ROUNDED = " << to_string(o) << std::endl;); +} + +void mpf_manager::round_sqrt(mpf_rounding_mode rm, mpf & o) { + TRACE("mpf_dbg", tout << "RND-SQRT: " << to_string(o) << std::endl;); + + bool sticky = !m_mpz_manager.is_even(o.significand); + m_mpz_manager.machine_div2k(o.significand, 1); + sticky = sticky || !m_mpz_manager.is_even(o.significand); + m_mpz_manager.machine_div2k(o.significand, 1); + bool round = !m_mpz_manager.is_even(o.significand); + m_mpz_manager.machine_div2k(o.significand, 1); + bool last = !m_mpz_manager.is_even(o.significand); + + bool inc = false; + + // Specialized rounding for sqrt, as there are no negative cases (or half-way cases?) + switch(rm) { + case MPF_ROUND_NEAREST_TEVEN: + case MPF_ROUND_NEAREST_TAWAY: inc = (round && sticky); break; + case MPF_ROUND_TOWARD_NEGATIVE: break; + case MPF_ROUND_TOWARD_ZERO: break; + case MPF_ROUND_TOWARD_POSITIVE: inc = round || sticky; break; + default: UNREACHABLE(); + } + + TRACE("mpf_dbg", tout << "last=" << last << " round=" << round << " sticky=" << sticky << " --> inc=" << inc << std::endl;); + + if (inc) + m_mpz_manager.inc(o.significand); + + m_mpz_manager.sub(o.significand, m_powers2(o.sbits-1), o.significand); + + TRACE("mpf_dbg", tout << "ROUNDED = " << to_string(o) << std::endl;); +} + +unsigned mpf_manager::prev_power_of_two(mpf const & a) { + SASSERT(!is_nan(a) && !is_pinf(a) && !is_ninf(a)); + if (!is_pos(a)) + return 0; + if (a.exponent <= -static_cast(a.sbits)) + return 0; // Number is smaller than 1 + return static_cast(a.sbits + a.exponent - 1); +} diff --git a/lib/mpf.h b/lib/mpf.h new file mode 100644 index 000000000..284b0ba7f --- /dev/null +++ b/lib/mpf.h @@ -0,0 +1,278 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + mpf.h + +Abstract: + + Multi Precision Floating Point Numbers + +Author: + + Christoph Wintersteiger (cwinter) 2011-12-01. + +Revision History: + +--*/ +#ifndef _MPF_H_ +#define _MPF_H_ + +#include +#include"mpz.h" +#include"mpq.h" +#include"map.h" +#include"scoped_numeral.h" +#include"scoped_numeral_vector.h" +#include"hash.h" + +typedef enum { + MPF_ROUND_NEAREST_TEVEN, + MPF_ROUND_NEAREST_TAWAY, + MPF_ROUND_TOWARD_POSITIVE, + MPF_ROUND_TOWARD_NEGATIVE, + MPF_ROUND_TOWARD_ZERO +} mpf_rounding_mode; + +typedef int64 mpf_exp_t; + +class mpf { + friend class mpf_manager; + friend class scoped_mpf; + unsigned ebits:15; + unsigned sbits:16; + unsigned sign:1; // counts as one sbit. + mpz significand; + mpf_exp_t exponent; + mpf & operator=(mpf const & other) { UNREACHABLE(); return *this; } + void set(unsigned ebits, unsigned sbits); +public: + mpf(); + mpf(unsigned ebits, unsigned sbits); + mpf(mpf const & other); + ~mpf(); + unsigned get_ebits() const { return ebits; } + unsigned get_sbits() const { return sbits; } + void swap(mpf & other); +}; + +class mpf_manager { + unsynch_mpq_manager m_mpq_manager; + unsynch_mpz_manager & m_mpz_manager; // A mpq_manager is a mpz_manager, reusing it. + +public: + typedef mpf numeral; + + mpf_manager(); + ~mpf_manager(); + + void reset(mpf & o, unsigned ebits, unsigned sbits) { set(o, ebits, sbits, 0); } + void set(mpf & o, unsigned ebits, unsigned sbits, int value); + void set(mpf & o, unsigned ebits, unsigned sbits, mpf_rounding_mode rm, int n, int d); + void set(mpf & o, unsigned ebits, unsigned sbits, float value); + void set(mpf & o, unsigned ebits, unsigned sbits, double value); + void set(mpf & o, unsigned ebits, unsigned sbits, mpf_rounding_mode rm, mpq const & value); + void set(mpf & o, unsigned ebits, unsigned sbits, mpf_rounding_mode rm, char const * value); + void set(mpf & o, unsigned ebits, unsigned sbits, mpf_rounding_mode rm, mpq const & significand, mpz const & exponent); + void set(mpf & o, unsigned ebits, unsigned sbits, bool sign, uint64 significand, int exponent); + void set(mpf & o, unsigned ebits, unsigned sbits, bool sign, mpz const & significand, mpf_exp_t exponent); + void set(mpf & o, mpf const & x); + void set(mpf & o, unsigned ebits, unsigned sbits, mpf_rounding_mode rm, mpf const & x); + + void del(mpf & x) { + m_mpz_manager.del(x.significand); + } + + void abs(mpf & o); + void abs(mpf const & x, mpf & o); + + void neg(mpf & o); + void neg(mpf const & x, mpf & o); + + bool is_zero(mpf const & x); + bool is_neg(mpf const & x); + bool is_pos(mpf const & x); + bool is_one(mpf const & x); + + bool is_nzero(mpf const & x); + bool is_pzero(mpf const & x); + + // structural eq + bool eq_core(mpf const & x, mpf const & y) { + return + x.ebits == y.ebits && + x.sbits == y.sbits && + x.sign == y.sign && + m_mpz_manager.eq(x.significand, y.significand) && + x.exponent == y.exponent; + } + + bool eq(mpf const & x, mpf const & y); + bool lt(mpf const & x, mpf const & y); + bool lte(mpf const & x, mpf const & y); + bool le(mpf const & x, mpf const & y) { return lte(x, y); } + bool gt(mpf const & x, mpf const & y); + bool gte(mpf const & x, mpf const & y); + bool ge(mpf const & x, mpf const & y) { return gte(x, y); } + + void add(mpf_rounding_mode rm, mpf const & x, mpf const & y, mpf & o); + void sub(mpf_rounding_mode rm, mpf const & x, mpf const & y, mpf & o); + void mul(mpf_rounding_mode rm, mpf const & x, mpf const & y, mpf & o); + void div(mpf_rounding_mode rm, mpf const & x, mpf const & y, mpf & o); + + void fused_mul_add(mpf_rounding_mode rm, mpf const & x, mpf const & y, mpf const &z, mpf & o); + + void sqrt(mpf_rounding_mode rm, mpf const & x, mpf & o); + + void round_to_integral(mpf_rounding_mode rm, mpf const & x, mpf & o); + + void rem(mpf const & x, mpf const & y, mpf & o); + + void maximum(mpf const & x, mpf const & y, mpf & o); + void minimum(mpf const & x, mpf const & y, mpf & o); + + std::string to_string(mpf const & a); + std::string to_rational_string(mpf const & a); + void display_decimal(std::ostream & out, mpf const & a, unsigned k); + void display_smt2(std::ostream & out, mpf const & a, bool decimal); + + // Convert x into a mpq numeral. zm is the manager that owns o. + void to_rational(mpf const & x, unsynch_mpq_manager & qm, mpq & o); + void to_rational(mpf const & x, scoped_mpq & o) { to_rational(x, o.m(), o); } + + double to_double(mpf const & x); + float to_float(mpf const & x); + + bool sgn(mpf const & x) const { return x.sign; } + const mpz & sig(mpf const & x) const { return x.significand; } + const mpf_exp_t & exp(mpf const & x) const { return x.exponent; } + + bool is_nan(mpf const & x); + bool is_inf(mpf const & x); + bool is_pinf(mpf const & x); + bool is_ninf(mpf const & x); + bool is_normal(mpf const & x); + bool is_denormal(mpf const & x); + bool is_regular(mpf const & x) { return x.sbits == 0 || is_normal(x) || is_denormal(x); } + + bool is_int(mpf const & x); + + void mk_zero(unsigned ebits, unsigned sbits, bool sign, mpf & o); + void mk_nzero(unsigned ebits, unsigned sbits, mpf & o); + void mk_pzero(unsigned ebits, unsigned sbits, mpf & o); + void mk_nan(unsigned ebits, unsigned sbits, mpf & o); + void mk_inf(unsigned ebits, unsigned sbits, bool sign, mpf & o); + void mk_pinf(unsigned ebits, unsigned sbits, mpf & o); + void mk_ninf(unsigned ebits, unsigned sbits, mpf & o); + + std::string to_string_raw(mpf const & a); + + unsynch_mpz_manager & mpz_manager(void) { return m_mpz_manager; } + unsynch_mpq_manager & mpq_manager(void) { return m_mpq_manager; } + + unsigned hash(mpf const & a) { + return hash_u_u(m_mpz_manager.hash(a.significand), + m_mpz_manager.hash(hash_ull(a.exponent))); + } + + void mk_max_value(unsigned ebits, unsigned sbits, bool sign, mpf & o); + mpf_exp_t mk_bot_exp(unsigned ebits); + mpf_exp_t mk_top_exp(unsigned ebits); + mpf_exp_t mk_max_exp(unsigned ebits); + mpf_exp_t mk_min_exp(unsigned ebits); + + /** + \brief Return the biggest k s.t. 2^k <= a. + + \remark Return 0 if a is not positive. + */ + unsigned prev_power_of_two(mpf const & a); + +protected: + bool has_bot_exp(mpf const & x); + bool has_top_exp(mpf const & x); + + void unpack(mpf & o, bool normalize); + void add_sub(mpf_rounding_mode rm, mpf const & x, mpf const & y, mpf & o, bool sub); + void round(mpf_rounding_mode rm, mpf & o); + void round_sqrt(mpf_rounding_mode rm, mpf & o); + + void mk_round_inf(mpf_rounding_mode rm, mpf & o); + + // Convert x into a mpz numeral. zm is the manager that owns o. + void to_mpz(mpf const & x, unsynch_mpz_manager & zm, mpz & o); + void to_mpz(mpf const & x, scoped_mpz & o) { to_mpz(x, o.m(), o); } + + class powers2 { + unsynch_mpz_manager & m; + u_map m_p; + u_map m_pn; + u_map m_pm1; + u_map m_pm1n; + public: + powers2(unsynch_mpz_manager & m) : m(m) {} + ~powers2() { + dispose(m_p); + dispose(m_pn); + dispose(m_pm1); + dispose(m_pm1n); + } + + void dispose(u_map & map) { + for (u_map::iterator it = map.begin(); it != map.end(); it++) { + m.del(*it->m_value); + dealloc(it->m_value); + } + } + + const mpz & operator()(unsigned n, bool negated = false) { + u_map & map = (negated) ? m_pn : m_p; + u_map::iterator it = map.find_iterator(n); + if (it != map.end()) + return *it->m_value; + else { + mpz * new_obj = alloc(mpz); + map.insert(n, new_obj); + m.power(unsynch_mpz_manager::mk_z(2), n, *new_obj); + if (negated) m.neg(*new_obj); + return *new_obj; + } + } + + const mpz & m1(unsigned n, bool negated=false) { // (2 ^ n) - 1 + u_map & map = (negated) ? m_pm1n : m_pm1; + u_map::iterator it = map.find_iterator(n); + if (it != map.end()) + return *it->m_value; + else { + mpz * new_obj = alloc(mpz); + map.insert(n, new_obj); + m.power(unsynch_mpz_manager::mk_z(2), n, *new_obj); + m.dec(*new_obj); + if (negated) m.neg(*new_obj); + return *new_obj; + } + } + }; + +public: + powers2 m_powers2; +}; + +class scoped_mpf : public _scoped_numeral { + friend class mpf_manager; + mpz & significand() { return get().significand; } + bool sign() const { return get().sign; } + mpf_exp_t exponent() const { return get().exponent; } + unsigned sbits() const { return get().sbits; } + void set(unsigned ebits, unsigned sbits) { get().set(ebits, sbits); } +public: + scoped_mpf(mpf_manager & m):_scoped_numeral(m) {} + scoped_mpf(scoped_mpf const & n):_scoped_numeral(n) {} + scoped_mpf(mpf_manager & m, unsigned ebits, unsigned sbits):_scoped_numeral(m) { set(ebits, sbits); } +}; + +typedef _scoped_numeral_vector scoped_mpf_vector; + +#endif diff --git a/lib/mpff.cpp b/lib/mpff.cpp new file mode 100644 index 000000000..892a7fe24 --- /dev/null +++ b/lib/mpff.cpp @@ -0,0 +1,1413 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + mpff.cpp + +Abstract: + + Multi precision fast floating point numbers. + The implementation is not compliant with the IEEE standard. + For a compliant implementation, see mpf.h + +Author: + + Leonardo de Moura (leonardo) 2012-09-12 + +Revision History: + +--*/ +#include +#include +#include"mpff.h" +#include"mpn.h" +#include"mpz.h" +#include"mpq.h" +#include"bit_util.h" +#include"trace.h" + +COMPILE_TIME_ASSERT(sizeof(mpn_digit) == sizeof(unsigned)); +COMPILE_TIME_ASSERT(sizeof(unsigned) == 4); + +// MIN_MSW is an shorthand for 0x8000..00, i.e., the minimal most significand word. +#define MIN_MSW (1u << (sizeof(unsigned) * 8 - 1)) + +mpff_manager::mpff_manager(unsigned prec, unsigned initial_capacity) { + SASSERT(initial_capacity > 0); + m_precision = prec; + m_precision_bits = prec * 8 * sizeof(unsigned); + m_capacity = initial_capacity; + m_to_plus_inf = false; + m_significands.resize(initial_capacity * prec, 0); + for (unsigned i = 0; i < MPFF_NUM_BUFFERS; i++) + m_buffers[i].resize(2 * prec, 0); + // Reserve space for zero + unsigned zero_sig_idx = m_id_gen.mk(); + SASSERT(zero_sig_idx == 0); + set(m_one, 1); +} + +mpff_manager::~mpff_manager() { + del(m_one); +} + +void mpff_manager::expand() { + m_capacity = 2*m_capacity; + m_significands.resize(m_capacity * m_precision, 0); +} + +void mpff_manager::allocate(mpff & n) { + SASSERT(n.m_sig_idx == 0); + unsigned sig_idx = m_id_gen.mk(); + ensure_capacity(sig_idx); + n.m_sig_idx = sig_idx; + DEBUG_CODE({ + unsigned * s = sig(n); + for (unsigned i = 0; i < m_precision; i++) { + SASSERT(s[i] == 0); + } + }); +} + +void mpff_manager::to_buffer(unsigned idx, mpff const & n) const { + SASSERT(idx < MPFF_NUM_BUFFERS); + svector & b = const_cast(this)->m_buffers[idx]; + unsigned * s = sig(n); + for (unsigned i = 0; i < m_precision; i++) + b[i] = s[i]; +} + +void mpff_manager::to_buffer_ext(unsigned idx, mpff const & n) const { + SASSERT(idx < MPFF_NUM_BUFFERS); + svector & b = const_cast(this)->m_buffers[idx]; + unsigned * s = sig(n); + unsigned j = m_precision; + for (unsigned i = 0; i < m_precision; i++, j++) { + b[i] = s[i]; + b[j] = 0; + } +} + +void mpff_manager::to_buffer_shifting(unsigned idx, mpff const & n) const { + SASSERT(idx < MPFF_NUM_BUFFERS); + svector & b = const_cast(this)->m_buffers[idx]; + unsigned * s = sig(n); + unsigned j = m_precision; + for (unsigned i = 0; i < m_precision; i++, j++) { + b[i] = 0; + b[j] = s[i]; + } +} + +void mpff_manager::del(mpff & n) { + unsigned sig_idx = n.m_sig_idx; + if (sig_idx != 0) { + m_id_gen.recycle(sig_idx); + unsigned * s = sig(n); + for (unsigned i = 0; i < m_precision; i++) + s[i] = 0; + } +} + +void mpff_manager::reset(mpff & n) { + del(n); + n.m_sign = false; + n.m_sig_idx = 0; + n.m_exponent = 0; + SASSERT(check(n)); +} + +bool mpff_manager::is_int(mpff const & n) const { + if (n.m_exponent >= 0) + return true; // cheap case + if (n.m_exponent <= -static_cast(m_precision_bits)) + return false; + return !has_one_at_first_k_bits(m_precision, sig(n), -n.m_exponent); +} + +bool mpff_manager::is_int64(mpff const & n) const { + SASSERT(m_precision >= 2); + if (is_zero(n)) + return true; + int max_exp = -static_cast(sizeof(unsigned) * 8 * (m_precision - 2)); + if (n.m_exponent < max_exp) { + return n.m_exponent > -static_cast(m_precision_bits) && + !has_one_at_first_k_bits(m_precision, sig(n), -n.m_exponent); + } + else if (n.m_exponent == max_exp) { + // handle INT64_MIN case + unsigned * s = sig(n); + return is_neg(n) && s[m_precision-1] == 0x80000000u && ::is_zero(m_precision-1, s); + } + else { + return false; + } +} + +bool mpff_manager::is_uint64(mpff const & n) const { + SASSERT(m_precision >= 2); + if (is_zero(n)) + return true; + return + n.m_sign == 0 && + n.m_exponent <= -static_cast(sizeof(unsigned) * 8 * (m_precision - 2)) && + n.m_exponent > -static_cast(m_precision_bits) && + !has_one_at_first_k_bits(m_precision, sig(n), -n.m_exponent); +} + +uint64 mpff_manager::get_uint64(mpff const & a) const { + SASSERT(is_uint64(a)); + if (is_zero(a)) return 0; + int exp = -a.m_exponent - sizeof(unsigned) * 8 * (m_precision - 2); + SASSERT(exp >= 0); + uint64 * s = reinterpret_cast(sig(a) + (m_precision - 2)); + return *s >> exp; +} + +int64 mpff_manager::get_int64(mpff const & a) const { + SASSERT(is_int64(a)); + if (is_zero(a)) return 0; + int exp = -a.m_exponent - sizeof(unsigned) * 8 * (m_precision - 2); + SASSERT(exp >= 0); + uint64 * s = reinterpret_cast(sig(a) + (m_precision - 2)); + // INT64_MIN case + if (exp == 0 && *s == 0x8000000000000000ull && is_neg(a)) { + return INT64_MIN; + } + else { + int64 r = *s >> exp; + if (is_neg(a)) + r = -r; + return r; + } +} + +// Return true if n is 1 or -1 +bool mpff_manager::is_abs_one(mpff const & n) const { + // That is, check whether + // n.exponent == 1 - m_precision_bits + // n.significand == 0b10000...0 (that is, only the highest bit is set in the significand). + if (n.m_exponent != 1 - static_cast(m_precision_bits)) + return false; + unsigned * s = sig(n); + if (s[m_precision - 1] != 0x80000000u) + return false; + for (unsigned i = 0; i < m_precision - 1; i++) + if (s[i] != 0) + return false; + return true; +} + +bool mpff_manager::is_two(mpff const & n) const { + // That is, check whether + // n.exponent = 2 - m_precision_bits + // n.significand == 0b10000...0 (that is, only the highest bit is set in the significand). + if (is_neg(n)) + return false; + if (n.m_exponent != 2 - static_cast(m_precision_bits)) + return false; + unsigned * s = sig(n); + if (s[m_precision - 1] != 0x80000000u) + return false; + for (unsigned i = 0; i < m_precision - 1; i++) + if (s[i] != 0) + return false; + return true; +} + +void mpff_manager::set(mpff & n, int v) { + if (v == 0) { + reset(n); + } + else { + if (v < 0) { + set(n, static_cast(-v)); + n.m_sign = 1; + } + else { + set(n, static_cast(v)); + } + } + SASSERT(check(n)); +} + +void mpff_manager::set(mpff & n, unsigned v) { + if (v == 0) { + reset(n); + } + else { + allocate_if_needed(n); + n.m_sign = 0; + int num_leading_zeros = nlz_core(v); + n.m_exponent = static_cast(8 * sizeof(unsigned)) - num_leading_zeros - static_cast(m_precision_bits); + v <<= num_leading_zeros; + unsigned * s = sig(n); + s[m_precision - 1] = v; + for (unsigned i = 0; i < m_precision - 1; i++) + s[i] = 0; + } + SASSERT(check(n)); +} + +void mpff_manager::set(mpff & n, int64 v) { + if (v == 0) { + reset(n); + } + else { + if (v < 0) { + set(n, static_cast(-v)); + n.m_sign = 1; + } + else { + set(n, static_cast(v)); + } + } + SASSERT(check(n)); + SASSERT(get_int64(n) == v); +} + +void mpff_manager::set(mpff & n, uint64 v) { +#ifdef Z3DEBUG + uint64 old_v = v; +#endif + if (v == 0) { + reset(n); + } + else { + allocate_if_needed(n); + n.m_sign = 0; + unsigned * _v = reinterpret_cast(&v); + int num_leading_zeros = nlz(2, _v); + n.m_exponent = static_cast(8 * sizeof(uint64)) - num_leading_zeros - static_cast(m_precision_bits); + v <<= num_leading_zeros; + SASSERT(m_precision >= 2); + unsigned * s = sig(n); + s[m_precision-1] = _v[1]; + s[m_precision-2] = _v[0]; + for (unsigned i = 0; i < m_precision - 2; i++) + s[i] = 0; + } + SASSERT(check(n)); + SASSERT(get_uint64(n) == old_v); +} + +void mpff_manager::set(mpff & n, int num, unsigned den) { + scoped_mpff a(*this), b(*this); + set(a, num); + set(b, den); + div(a, b, n); + SASSERT(check(n)); +} + +void mpff_manager::set(mpff & n, int64 num, uint64 den) { + scoped_mpff a(*this), b(*this); + set(a, num); + set(b, den); + div(a, b, n); + SASSERT(check(n)); +} + +void mpff_manager::set(mpff & n, mpff const & v) { + if (is_zero(v)) { + reset(n); + return; + } + if (&n == &v) + return; + allocate_if_needed(n); + n.m_sign = v.m_sign; + n.m_exponent = v.m_exponent; + unsigned * s1 = sig(n); + unsigned * s2 = sig(v); + for (unsigned i = 0; i < m_precision; i++) + s1[i] = s2[i]; + SASSERT(check(n)); +} + +template +void mpff_manager::set_core(mpff & n, mpz_manager & m, mpz const & v) { + TRACE("mpff", tout << "mpz->mpff\n"; m.display(tout, v); tout << "\n";); + if (m.is_int64(v)) { + TRACE("mpff", tout << "is_int64 " << m.get_int64(v) << "\n";); + set(n, m.get_int64(v)); + } + else if (m.is_uint64(v)) { + TRACE("mpff", tout << "is_uint64\n";); + set(n, m.get_uint64(v)); + } + else { + allocate_if_needed(n); + svector & w = m_set_buffer; + n.m_sign = m.decompose(v, w); + while (w.size() < m_precision) { + w.push_back(0); + } + TRACE("mpff", tout << "w words: "; for (unsigned i = 0; i < w.size(); i++) tout << w[i] << " "; tout << "\n";); + unsigned w_sz = w.size(); + SASSERT(w_sz >= m_precision); + unsigned num_leading_zeros = nlz(w_sz, w.c_ptr()); + shl(w_sz, w.c_ptr(), num_leading_zeros, w_sz, w.c_ptr()); + unsigned * s = sig(n); + unsigned i = m_precision; + unsigned j = w_sz; + while (i > 0) { + --i; + --j; + s[i] = w[j]; + } + n.m_exponent = j * 8 * sizeof(unsigned) - static_cast(num_leading_zeros); + if ((n.m_sign == 1) != m_to_plus_inf) { + // must check whether it is precise or not + while (j > 0) { + --j; + if (w[j] != 0) { + // imprecision detected. + inc_significand(n); + } + } + // it is precise + } + } + TRACE("mpff", tout << "mpz->mpff result:\n"; display_raw(tout, n); tout << "\n";); + SASSERT(check(n)); +} + +void mpff_manager::set(mpff & n, unsynch_mpz_manager & m, mpz const & v) { + set_core(n, m, v); +} + +void mpff_manager::set(mpff & n, synch_mpz_manager & m, mpz const & v) { + set_core(n, m, v); +} + +template +void mpff_manager::set_core(mpff & n, mpq_manager & m, mpq const & v) { + // TODO: improve precision? + scoped_mpff num(*this), den(*this); + set_core(num, m, v.numerator()); + { + flet l(m_to_plus_inf, !m_to_plus_inf); + set_core(den, m, v.denominator()); + } + div(num, den, n); + SASSERT(check(n)); +} + +void mpff_manager::set(mpff & n, unsynch_mpq_manager & m, mpq const & v) { + set_core(n, m, v); +} + +void mpff_manager::set(mpff & n, synch_mpq_manager & m, mpq const & v) { + set_core(n, m, v); +} + +bool mpff_manager::eq(mpff const & a, mpff const & b) const { + if (is_zero(a) && is_zero(b)) + return true; + if (is_zero(a) || is_zero(b)) + return false; + if (a.m_sign != b.m_sign || + a.m_exponent != b.m_exponent) + return false; + unsigned * s1 = sig(a); + unsigned * s2 = sig(b); + for (unsigned i = 0; i < m_precision; i++) + if (s1[i] != s2[i]) + return false; + return true; +} + +bool mpff_manager::lt(mpff const & a, mpff const & b) const { + STRACE("mpff_trace", tout << "[mpff] ("; display(tout, a); tout << " < "; display(tout, b); tout << ") == ";); + if (is_zero(a)) { + if (is_zero(b) || is_neg(b)) { + STRACE("mpff_trace", tout << "(1 == 0)\n";); + return false; + } + else { + STRACE("mpff_trace", tout << "(1 == 1)\n";); + SASSERT(is_pos(b)); + return true; + } + } + if (is_zero(b)) { + SASSERT(!is_zero(a)); + if (is_neg(a)) { + STRACE("mpff_trace", tout << "(1 == 1)\n";); + return true; + } + else { + SASSERT(is_pos(a)); + STRACE("mpff_trace", tout << "(1 == 0)\n";); + return false; + } + } + SASSERT(!is_zero(a)); + SASSERT(!is_zero(b)); + if (a.m_sign == 1) { + if (b.m_sign == 0) { + STRACE("mpff_trace", tout << "(1 == 1)\n";); + return true; // neg < pos + } + // case: neg neg + bool r = + b.m_exponent < a.m_exponent || + (a.m_exponent == b.m_exponent && ::lt(m_precision, sig(b), sig(a))); + STRACE("mpff_trace", tout << "(" << r << " == 1)\n";); + return r; + } + else { + if (b.m_sign == 1) { + STRACE("mpff_trace", tout << "(1 == 0)\n";); + return false; // pos < neg + } + // case: pos pos + bool r = + a.m_exponent < b.m_exponent || + (a.m_exponent == b.m_exponent && ::lt(m_precision, sig(a), sig(b))); + STRACE("mpff_trace", tout << "(" << r << " == 1)\n";); + return r; + } +} + +void mpff_manager::inc_significand(unsigned * s, int64 & exp) { + if (!::inc(m_precision, s)) { + SASSERT(::is_zero(m_precision, s)); + s[m_precision - 1] = MIN_MSW; + SASSERT(exp != INT64_MAX); + exp++; + } +} + +void mpff_manager::inc_significand(mpff & a) { + unsigned * s = sig(a); + if (!::inc(m_precision, s)) { + // Overflow happened, a was of the form 0xFFFF...FF + // Now, it must be 0x000...000 + SASSERT(::is_zero(m_precision, s)); + // Set it to 0x80000...000, and increment exponent by 1. + s[m_precision - 1] = MIN_MSW; + if (a.m_exponent == INT_MAX) + throw overflow_exception(); + a.m_exponent++; + } +} + +void mpff_manager::dec_significand(mpff & a) { + SASSERT(!is_minus_epsilon(a) && !is_zero(a) && !is_plus_epsilon(a)); + unsigned * s = sig(a); + for (unsigned i = 0; i < m_precision - 1; i++) { + s[i]--; + if (s[i] != UINT_MAX) + return; + } + s[m_precision - 1]--; + if (s[m_precision - 1] < MIN_MSW) { + s[m_precision - 1] = UINT_MAX; + a.m_exponent--; + } +} + +bool mpff_manager::min_significand(mpff const & a) const { + unsigned * s = sig(a); + return s[m_precision - 1] == MIN_MSW && ::is_zero(m_precision - 1, s); +} + +bool mpff_manager::is_minus_epsilon(mpff const & a) const { + return a.m_sign == 1 && a.m_exponent == INT_MIN && min_significand(a); +} + +bool mpff_manager::is_plus_epsilon(mpff const & a) const { + return a.m_sign == 0 && a.m_exponent == INT_MIN && min_significand(a); +} + +void mpff_manager::set_min_significand(mpff & a) { + // Since the most significand bit of the most significand word must be 1 in our representation, + // we have that 0x8000..00 is the minimal significand + unsigned * s = sig(a); + s[m_precision - 1] = MIN_MSW; + for (unsigned i = 0; i < m_precision - 1; i++) + s[i] = 0; +} + +void mpff_manager::set_max_significand(mpff & a) { + SASSERT(!is_zero(a)); + unsigned * s = sig(a); + for (unsigned i = 0; i < m_precision; i++) + s[i] = UINT_MAX; +} + +void mpff_manager::set_plus_epsilon(mpff & n) { + allocate_if_needed(n); + n.m_sign = 0; + n.m_exponent = INT_MIN; + set_min_significand(n); + SASSERT(check(n)); +} + +void mpff_manager::set_minus_epsilon(mpff & n) { + set_plus_epsilon(n); + n.m_sign = 1; + SASSERT(check(n)); +} + +void mpff_manager::set_max(mpff & n) { + allocate_if_needed(n); + n.m_sign = 0; + n.m_exponent = INT_MAX; + set_max_significand(n); + SASSERT(check(n)); +} + +void mpff_manager::set_min(mpff & n) { + set_max(n); + n.m_sign = 1; + SASSERT(check(n)); +} + +void mpff_manager::next(mpff & a) { + if (is_zero(a)) { + set_plus_epsilon(a); + } + else if (is_minus_epsilon(a)) { + reset(a); + } + else if (a.m_sign == 0) { + inc_significand(a); + } + else { + dec_significand(a); + } + SASSERT(check(a)); +} + +void mpff_manager::prev(mpff & a) { + if (is_zero(a)) { + set_minus_epsilon(a); + } + else if (is_plus_epsilon(a)) { + reset(a); + } + else if (a.m_sign == 0) { + dec_significand(a); + } + else { + inc_significand(a); + } + SASSERT(check(a)); +} + +void mpff_manager::set_big_exponent(mpff & a, int64 e) { + SASSERT(e > INT_MAX || e < INT_MIN); + if (e > INT_MAX) { + if (a.m_sign == 1) { + if (m_to_plus_inf) + set_min(a); + else + throw overflow_exception(); + } + else { + if (m_to_plus_inf) + throw overflow_exception(); + else + set_max(a); + } + } + else { + SASSERT(e < INT_MIN); + if (a.m_sign == 1) { + if (m_to_plus_inf) + reset(a); + else + set_minus_epsilon(a); + } + else { + if (m_to_plus_inf) + set_plus_epsilon(a); + else + reset(a); + } + } +} + +void mpff_manager::add_sub(bool is_sub, mpff const & a, mpff const & b, mpff & c) { + if (is_zero(a)) { + set(c, b); + if (is_sub) + neg(c); + return; + } + + if (is_zero(b)) { + set(c, a); + return; + } + + TRACE("mpff", tout << (is_sub ? "sub" : "add") << "("; display(tout, a); tout << ", "; display(tout, b); tout << ")\n";); + + // Remark: any result returned by sig(...) may be invalid after a call to allocate_if_needed() + // So, we must invoke allocate_if_needed(c) before we invoke sig(a) and sig(b). + allocate_if_needed(c); + + bool sgn_a, sgn_b; + int exp_a, exp_b; + unsigned * sig_a, * sig_b; + + if (a.m_exponent >= b.m_exponent) { + sgn_a = a.m_sign != 0; + sgn_b = b.m_sign != 0; + exp_a = a.m_exponent; + exp_b = b.m_exponent; + sig_a = sig(a); + sig_b = sig(b); + if (is_sub) + sgn_b = !sgn_b; + } + else { + sgn_a = b.m_sign != 0; + sgn_b = a.m_sign != 0; + exp_a = b.m_exponent; + exp_b = a.m_exponent; + sig_a = sig(b); + sig_b = sig(a); + if (is_sub) + sgn_a = !sgn_a; + } + + SASSERT(exp_a >= exp_b); + + unsigned * n_sig_b; // normalized sig_b + + // Make sure that a and b have the same exponent. + if (exp_a > exp_b) { + unsigned shift = exp_a - exp_b; + n_sig_b = m_buffers[0].c_ptr(); + shr(m_precision, sig_b, shift, m_precision, n_sig_b); + if (sgn_b != m_to_plus_inf && has_one_at_first_k_bits(m_precision, sig_b, shift)) { + // Precision was lost when normalizing the significand. + // The current rounding mode forces us to bump the significand. + // Remark: an overflow cannot happen when incrementing the significand. + VERIFY(::inc(m_precision, n_sig_b)); + } + } + else { + SASSERT(exp_a == exp_b); + n_sig_b = sig_b; + } + + // Compute c + if (sgn_a == sgn_b) { + c.m_sign = sgn_a; + unsigned * sig_r = m_buffers[1].c_ptr(); + size_t r_sz; + add_full(sig_a, m_precision, n_sig_b, m_precision, sig_r, m_precision + 1, &r_sz, 0); + SASSERT(r_sz <= m_precision + 1); + unsigned num_leading_zeros = nlz(m_precision + 1, sig_r); + SASSERT(num_leading_zeros >= sizeof(unsigned) * 8 - 1); // num_leading_zeros >= 31 + SASSERT(num_leading_zeros < sizeof(unsigned) * 8 * (m_precision + 1)); // result is not zero. + unsigned * sig_c = sig(c); + if (num_leading_zeros == sizeof(unsigned) * 8) { + // no shift is needed + c.m_exponent = exp_a; + for (unsigned i = 0; i < m_precision; i++) + sig_c[i] = sig_r[i]; + } + else if (num_leading_zeros == sizeof(unsigned) * 8 - 1) { + // shift 1 right + bool _inc_significand = ((c.m_sign == 1) != m_to_plus_inf) && has_one_at_first_k_bits(m_precision*2, sig_r, 1); + int64 exp_c = exp_a; + exp_c++; + shr(m_precision + 1, sig_r, 1, m_precision, sig_c); + if (_inc_significand) + inc_significand(sig_c, exp_c); + set_exponent(c, exp_c); + } + else { + SASSERT(num_leading_zeros > sizeof(unsigned) * 8); + num_leading_zeros -= sizeof(unsigned) * 8; // remove 1 word bits. + // Now, we can assume sig_r has size m_precision + SASSERT(num_leading_zeros > 0); + // shift left num_leading_zeros + int64 exp_c = exp_a; + exp_c -= num_leading_zeros; + shl(m_precision, sig_r, num_leading_zeros, m_precision, sig_c); + set_exponent(c, exp_c); + } + } + else { + unsigned borrow; + SASSERT(sgn_a != sgn_b); + unsigned * sig_c = sig(c); + if (::lt(m_precision, sig_a, n_sig_b)) { + c.m_sign = sgn_b; + sub_diff(n_sig_b, m_precision, sig_a, m_precision, sig_c, &borrow, 0); + } + else { + c.m_sign = sgn_a; + sub_diff(sig_a, m_precision, n_sig_b, m_precision, sig_c, &borrow, 0); + } + SASSERT(borrow == 0); + unsigned num_leading_zeros = nlz(m_precision, sig_c); + if (num_leading_zeros == m_precision_bits) { + reset(c); + } + else if (num_leading_zeros > 0) { + int64 exp_c = exp_a; + exp_c -= num_leading_zeros; + shl(m_precision, sig_c, num_leading_zeros, m_precision, sig_c); + set_exponent(c, exp_c); + } + else { + SASSERT(num_leading_zeros == 0); + c.m_exponent = exp_a; + } + } + TRACE("mpff", tout << "result: "; display(tout, c); tout << "\n";); + SASSERT(check(c)); +} + +void mpff_manager::add(mpff const & a, mpff const & b, mpff & c) { + STRACE("mpff_trace", tout << "[mpff] "; display(tout, a); tout << " + "; display(tout, b); tout << " " << (m_to_plus_inf ? "<=" : ">=") << " ";); + add_sub(false, a, b, c); + STRACE("mpff_trace", display(tout, c); tout << "\n";); +} + +void mpff_manager::sub(mpff const & a, mpff const & b, mpff & c) { + STRACE("mpff_trace", tout << "[mpff] "; display(tout, a); tout << " - "; display(tout, b); tout << " " << (m_to_plus_inf ? "<=" : ">=") << " ";); + add_sub(true, a, b, c); + STRACE("mpff_trace", display(tout, c); tout << "\n";); +} + +void mpff_manager::mul(mpff const & a, mpff const & b, mpff & c) { + STRACE("mpff_trace", tout << "[mpff] ("; display(tout, a); tout << ") * ("; display(tout, b); tout << ") " << (m_to_plus_inf ? "<=" : ">=") << " ";); + if (is_zero(a) || is_zero(b)) { + reset(c); + } + else { + allocate_if_needed(c); + TRACE("mpff", tout << "mul("; display(tout, a); tout << ", "; display(tout, b); tout << ")\n";); + c.m_sign = a.m_sign ^ b.m_sign; + // use int64 to make sure we do not have overflows + int64 exp_a = a.m_exponent; + int64 exp_b = b.m_exponent; + int64 exp_c = exp_a + exp_b; + // store result in m_buffers[0] + unsigned * r = m_buffers[0].c_ptr(); + multiply(sig(a), m_precision, sig(b), m_precision, r, 0); + // r has 2*m_precision_bits bits + unsigned num_leading_zeros = nlz(m_precision*2, r); + SASSERT(num_leading_zeros <= m_precision_bits); + TRACE("mpff", tout << "num_leading_zeros: " << num_leading_zeros << "\n";); + // We must shift right (m_precision_bits - num_leading_zeros) + // If r does not have a 1 bit in the first (m_precision_bits - num_leading_zeros), then the result is precise. + unsigned shift = m_precision_bits - num_leading_zeros; + // Imprecision == "lost digits" + // If c.m_sign --> result became bigger (e.g., -3.1 --> -3) + // If !c.m_sign --> result became smaller (e.g., 3.1 --> 3) + // Thus, when we are imprecise, we only need to bump the significand when: + // 1) !c.m_sign && m_to_plus_inf + // 2) c.m_sign && !m_to_plus_inf + bool _inc_significand = ((c.m_sign == 1) != m_to_plus_inf) && has_one_at_first_k_bits(m_precision*2, r, shift); + TRACE("mpff", + tout << "c.m_sign: " << c.m_sign << ", m_to_plus_inf: " << m_to_plus_inf + << "\nhas_one_at_first_k_bits: " << has_one_at_first_k_bits(m_precision*2, r, shift) << "\n"; + tout << "_inc_significand: " << _inc_significand << "\n";); + exp_c += shift; + unsigned * s_c = sig(c); + shr(m_precision*2, r, shift, m_precision, s_c); + if (_inc_significand) + inc_significand(s_c, exp_c); + set_exponent(c, exp_c); + TRACE("mpff", tout << "result: "; display(tout, c); tout << "\n";); + SASSERT(check(c)); + } + STRACE("mpff_trace", display(tout, c); tout << "\n";); +} + +void mpff_manager::div(mpff const & a, mpff const & b, mpff & c) { + if (is_zero(b)) + throw div0_exception(); + STRACE("mpff_trace", tout << "[mpff] ("; display(tout, a); tout << ") / ("; display(tout, b); tout << ") " << (m_to_plus_inf ? "<=" : ">=") << " ";); + if (is_zero(a)) { + reset(c); + } +#if 1 + else if (is_two(b)) { + set(c, a); + int64 exp_c = a.m_exponent; + exp_c--; + set_exponent(c, exp_c); + } +#endif + else { + allocate_if_needed(c); + c.m_sign = a.m_sign ^ b.m_sign; + // use int64 to make sure we do not have overflows + int64 exp_a = a.m_exponent; + int64 exp_b = b.m_exponent; + int64 exp_c = exp_a - exp_b; + + exp_c -= m_precision_bits; // we will multiplying (shifting) a by 2^m_precision_bits. + // copy a to buffer 0, and shift by m_precision_bits + to_buffer_shifting(0, a); + unsigned * _a = m_buffers[0].c_ptr(); + unsigned * q = m_buffers[1].c_ptr(); + unsigned q_sz = m_precision + 1; // 2*m_precision - m_precision + 1 + unsigned * r = m_buffers[2].c_ptr(); + unsigned r_sz = m_precision; + SASSERT(!::is_zero(2*m_precision, _a)); + SASSERT(!::is_zero(m_precision, sig(b))); + SASSERT(nlz(2*m_precision, _a) == 0); + // Thus it is always the case that _a > b since size(a) = 2*size(b) + // Actually, a is much bigger than b. + // b is at most 2^m_precision_bits - 1 + // a is at least 2^(2*m_precision_bits - 1) + // Thus the quotient of a/b cannot be zero + // Actually, quotient of a/b must be >= 2^(2*m_precision_bits - 1)/(2^m_precision_bits - 1) + divide(_a, 2*m_precision, + sig(b), m_precision, + reciprocal_1_NULL, + q, + r, + 0); + TRACE("mpff_div", + unsigned j = q_sz; + while (j > 0) { --j; tout << std::hex << std::setfill('0') << std::setw(2*sizeof(unsigned)) << q[j]; tout << " "; } + tout << std::dec << "\n";); + SASSERT(!::is_zero(q_sz, q)); + unsigned num_leading_zeros = nlz(q_sz, q); + SASSERT(num_leading_zeros < q_sz * 8 * sizeof(unsigned)); + unsigned q_bits = q_sz * 8 * sizeof(unsigned); + SASSERT(num_leading_zeros < q_bits); + unsigned actual_q_bits = q_bits - num_leading_zeros; + bool _inc_significand; + unsigned * s_c = sig(c); + if (actual_q_bits > m_precision_bits) { + unsigned shift = actual_q_bits - m_precision_bits; + // We are imprecise if the remainder is != 0 or if we lost a bit when shifting. + // See comment in mul(...) + _inc_significand = + ((c.m_sign == 1) != m_to_plus_inf) && + (has_one_at_first_k_bits(q_sz, q, shift) || + !::is_zero(r_sz, r)); + exp_c += shift; + shr(q_sz, q, shift, m_precision, s_c); + } + else { + // We are imprecise only if the remainder is != 0 + _inc_significand = + ((c.m_sign == 1) != m_to_plus_inf) && + !::is_zero(r_sz, r); + if (actual_q_bits < m_precision_bits) { + unsigned shift = m_precision_bits - actual_q_bits; + exp_c -= shift; + shl(q_sz, q, shift, m_precision, s_c); + } + else { + SASSERT(actual_q_bits == m_precision_bits); + ::copy(q_sz, q, m_precision, s_c); + } + } + if (_inc_significand) + inc_significand(s_c, exp_c); + set_exponent(c, exp_c); + SASSERT(check(c)); + } + STRACE("mpff_trace", display(tout, c); tout << "\n";); +} + +void mpff_manager::floor(mpff & n) { + if (n.m_exponent >= 0) + return; + STRACE("mpff_trace", tout << "[mpff] Floor["; display(tout, n); tout << "] == ";); + if (n.m_exponent <= -static_cast(m_precision_bits)) { + // number is between (-1, 1) + if (n.m_sign == 0) + reset(n); + else + set(n, -1); + } + else { + unsigned * s = sig(n); + if (n.m_sign == 1 && has_one_at_first_k_bits(m_precision, s, -n.m_exponent)) { + shr(m_precision, s, -n.m_exponent, m_precision, s); + VERIFY(::inc(m_precision, s)); + int num_leading_zeros = nlz(m_precision, s); + SASSERT(num_leading_zeros == -n.m_exponent || num_leading_zeros == -n.m_exponent - 1); + if (num_leading_zeros != -n.m_exponent) { + shl(m_precision, s, -n.m_exponent - 1, m_precision, s); + n.m_exponent++; + } + else { + shl(m_precision, s, -n.m_exponent, m_precision, s) ; + } + } + else { + // reset first n.m_exponent bits + shr(m_precision, s, -n.m_exponent, m_precision, s); + shl(m_precision, s, -n.m_exponent, m_precision, s); + } + } + SASSERT(check(n)); + STRACE("mpff_trace", display(tout, n); tout << "\n";); +} + +void mpff_manager::ceil(mpff & n) { + if (n.m_exponent >= 0) + return; + STRACE("mpff_trace", tout << "[mpff] Ceiling["; display(tout, n); tout << "] == ";); + if (n.m_exponent <= -static_cast(m_precision_bits)) { + // number is between (-1, 1) + if (n.m_sign == 0) + set(n, 1); + else + reset(n); + } + else { + unsigned * s = sig(n); + if (n.m_sign == 0 && has_one_at_first_k_bits(m_precision, s, -n.m_exponent)) { + shr(m_precision, s, -n.m_exponent, m_precision, s); + VERIFY(::inc(m_precision, s)); + int num_leading_zeros = nlz(m_precision, s); + SASSERT(num_leading_zeros == -n.m_exponent || num_leading_zeros == -n.m_exponent - 1); + if (num_leading_zeros != -n.m_exponent) { + shl(m_precision, s, -n.m_exponent - 1, m_precision, s); + n.m_exponent++; + } + else { + shl(m_precision, s, -n.m_exponent, m_precision, s) ; + } + } + else { + // reset first n.m_exponent bits + shr(m_precision, s, -n.m_exponent, m_precision, s); + shl(m_precision, s, -n.m_exponent, m_precision, s); + } + } + SASSERT(check(n)); + STRACE("mpff_trace", display(tout, n); tout << "\n";); +} + +void mpff_manager::power(mpff const & a, unsigned p, mpff & b) { +#ifdef _TRACE + scoped_mpff _a(*this); _a = a; + unsigned _p = p; +#endif +#define SMALL_POWER 8 + SASSERT(check(a)); + if (is_zero(a)) { + SASSERT(p != 0); + reset(b); + } + else if (p == 0) { + set(b, 1); + } + else if (p == 1) { + set(b, a); + } + else if (p == 2) { + mul(a, a, b); + } + else if (p <= SMALL_POWER && &a != &b) { + SASSERT(p > 2); + --p; + set(b, a); + while (p > 0) { + --p; + mul(a, b, b); + } + } + else { + unsigned * s = sig(a); + if (s[m_precision - 1] == 0x80000000u && ::is_zero(m_precision - 1, s)) { + allocate_if_needed(b); + if (p % 2 == 0) + b.m_sign = 0; + else + b.m_sign = a.m_sign; + int64 exp = a.m_exponent; + exp *= p; + if (exp > INT_MAX || exp < INT_MIN) + throw overflow_exception(); + exp += (m_precision_bits - 1)*(p - 1); + if (exp > INT_MAX || exp < INT_MIN) + throw overflow_exception(); + unsigned * r = sig(b); + r[m_precision - 1] = 0x80000000u; + for (unsigned i = 0; i < m_precision - 1; i++) + r[i] = 0; + b.m_exponent = static_cast(exp); + } + else { + unsigned mask = 1; + scoped_mpff pw(*this); + set(pw, a); + set(b, 1); + while (mask <= p) { + if (mask & p) + mul(b, pw, b); + mul(pw, pw, pw); + mask = mask << 1; + } + } + } + STRACE("mpff_trace", tout << "[mpff] ("; display(tout, _a); tout << ") ^ " << _p << (m_to_plus_inf ? "<=" : ">="); display(tout, b); tout << "\n";); + TRACE("mpff_power", display_raw(tout, b); tout << "\n";); + SASSERT(check(b)); +} + +bool mpff_manager::is_power_of_two(mpff const & a, unsigned & k) const { + if (!is_power_of_two(a)) + return false; + int64 exp = a.m_exponent + m_precision_bits - 1; + SASSERT(exp >= 0); + k = static_cast(exp); + return true; +} + +bool mpff_manager::is_power_of_two(mpff const & a) const { + unsigned * s = sig(a); + return is_pos(a) && a.m_exponent > -static_cast(m_precision_bits) && s[m_precision - 1] == 0x80000000u && ::is_zero(m_precision - 1, s); +} + +template +void mpff_manager::significand_core(mpff const & n, mpz_manager & m, mpz & t) { + m.set(t, m_precision, sig(n)); +} + +void mpff_manager::significand(mpff const & n, unsynch_mpz_manager & m, mpz & t) { + significand_core(n, m, t); +} + +void mpff_manager::significand(mpff const & n, synch_mpz_manager & m, mpz & t) { + significand_core(n, m, t); +} + +template +void mpff_manager::to_mpz_core(mpff const & n, mpz_manager & m, mpz & t) { + SASSERT(is_int(n)); + int exp = n.m_exponent; + if (exp < 0) { + SASSERT(exp > -static_cast(m_precision_bits)); + to_buffer(0, n); + unsigned * b = m_buffers[0].c_ptr(); + shr(m_precision, b, -exp, m_precision, b); + m.set(t, m_precision, b); + } + else { + m.set(t, m_precision, sig(n)); + if (exp > 0) { + _scoped_numeral > p(m); + m.set(p, 2); + m.power(p, exp, p); + m.mul(t, p, t); + } + } + if (is_neg(n)) + m.neg(t); +} + +void mpff_manager::to_mpz(mpff const & n, unsynch_mpz_manager & m, mpz & t) { + to_mpz_core(n, m, t); +} + +void mpff_manager::to_mpz(mpff const & n, synch_mpz_manager & m, mpz & t) { + to_mpz_core(n, m, t); +} + +template +void mpff_manager::to_mpq_core(mpff const & n, mpq_manager & m, mpq & t) { + int exp = n.m_exponent; + TRACE("mpff_to_mpq", tout << "to_mpq: "; display(tout, n); tout << "\nexp: " << exp << "\n";); + if (exp < 0 && exp > -static_cast(m_precision_bits) && !has_one_at_first_k_bits(m_precision, sig(n), -n.m_exponent)) { + to_buffer(0, n); + unsigned * b = m_buffers[0].c_ptr(); + shr(m_precision, b, -exp, m_precision, b); + m.set(t, m_precision, b); + } + else { + m.set(t, m_precision, sig(n)); + if (exp != 0) { + _scoped_numeral > p(m); + m.set(p, 2); + unsigned abs_exp; + if (exp < 0) { + // Avoid -INT_MIN == INT_MIN issue. It is not really useful, since we will run out of memory anyway. + if (exp == INT_MIN) + abs_exp = static_cast(-static_cast(INT_MIN)); + else + abs_exp = -exp; + } + else { + abs_exp = exp; + } + m.power(p, abs_exp, p); + if (exp < 0) + m.div(t, p, t); + else + m.mul(t, p, t); + } + } + if (is_neg(n)) + m.neg(t); +} + +void mpff_manager::to_mpq(mpff const & n, unsynch_mpq_manager & m, mpq & t) { + to_mpq_core(n, m, t); +} + +void mpff_manager::to_mpq(mpff const & n, synch_mpq_manager & m, mpq & t) { + to_mpq_core(n, m, t); +} + +void mpff_manager::display_raw(std::ostream & out, mpff const & n) const { + if (is_neg(n)) + out << "-"; + unsigned * s = sig(n); + unsigned i = m_precision; + while(i > 0) { + --i; + out << std::hex << std::setfill('0') << std::setw(2 * sizeof(unsigned)) << s[i]; + } + out << "*2^" << std::dec << n.m_exponent; +} + +void mpff_manager::display(std::ostream & out, mpff const & n) const { + if (is_neg(n)) + out << "-"; + to_buffer_ext(0, n); + svector & u_buffer = const_cast(this)->m_buffers[0]; + int num_trailing_zeros = ntz(m_precision, u_buffer.c_ptr()); + int shift = 0; + int64 exp = n.m_exponent; // use int64 to avoid -INT_MIN == INT_MIN issue + if (exp < 0) { + if (num_trailing_zeros >= -exp) { + shift = static_cast(-exp); + exp = 0; + } + else { + shift = num_trailing_zeros; + exp += num_trailing_zeros; + } + } + if (shift > 0) + shr(m_precision, u_buffer.c_ptr(), shift, u_buffer.c_ptr()); + sbuffer str_buffer(11*m_precision, 0); + out << mp_decimal(u_buffer.c_ptr(), m_precision, str_buffer.begin(), str_buffer.size(), 0); + if (exp > 0) { + if (exp <= 63) { + uint64 _exp = 1; + _exp <<= exp; + out << "*" << _exp; + } + else { + out << "*2"; + if (exp > 1) { + out << "^"; + out << exp; + } + } + } + else if (exp < 0) { + exp = -exp; + if (exp <= 63) { + uint64 _exp = 1; + _exp <<= exp; + out << "/" << _exp; + } + else { + out << "/2"; + if (exp > 1) { + out << "^"; + out << exp; + } + } + } +} + +void mpff_manager::display_decimal(std::ostream & out, mpff const & n, unsigned prec, unsigned min_exponent) const { + // The result in scientific notation when n.m_exponent >= min_exponent or n.m_exponent <= - min_exponent - m_precision_bits + int64 exp = n.m_exponent; + if (exp >= min_exponent || exp <= -static_cast(min_exponent) - m_precision_bits || is_int(n)) { + display(out, n); + return; + } + if (is_neg(n)) + out << "-"; + unsigned word_sz = 8 * sizeof(unsigned); + if (exp >= 0) { + sbuffer buffer; + unsigned num_extra_words = 1 + static_cast(exp/word_sz); + unsigned shift = word_sz - exp%word_sz; + for (unsigned i = 0; i < num_extra_words; i++) + buffer.push_back(0); + unsigned * s = sig(n); + for (unsigned i = 0; i < m_precision; i++) + buffer.push_back(s[i]); + shr(buffer.size(), buffer.c_ptr(), shift, buffer.size(), buffer.c_ptr()); + sbuffer str_buffer(11*buffer.size(), 0); + out << mp_decimal(buffer.c_ptr(), buffer.size(), str_buffer.begin(), str_buffer.size(), 0); + } + else { + sbuffer buffer1, buffer2; + sbuffer buffer3; + exp = -exp; + unsigned num_words = 1 + static_cast(exp/word_sz); + unsigned num_extra_words = m_precision < num_words ? num_words - m_precision : 0; + num_extra_words++; + unsigned * s = sig(n); + for (unsigned i = 0; i < m_precision; i++) { + buffer1.push_back(s[i]); + buffer2.push_back(0); + buffer3.push_back(0); + } + for (unsigned i = 0; i < num_extra_words; i++) { + buffer1.push_back(0); + buffer2.push_back(0); + } + unsigned ten = 10; + sbuffer pw_buffer; + pw_buffer.resize(num_words, 0); + pw_buffer[0] = 1; + shl(num_words, pw_buffer.c_ptr(), static_cast(exp), num_words, pw_buffer.c_ptr()); + if (num_words > m_precision) { + out << "0"; + } + else { + divide(buffer1.c_ptr(), m_precision, + pw_buffer.c_ptr(), num_words, + reciprocal_1_NULL, + buffer3.c_ptr(), + buffer2.c_ptr(), + 0); + sbuffer str_buffer(11*buffer3.size(), 0); + out << mp_decimal(buffer3.c_ptr(), buffer3.size(), str_buffer.begin(), str_buffer.size(), 0); + SASSERT(!::is_zero(buffer2.size(), buffer2.c_ptr())); // otherwise n is an integer + ::copy(buffer2.size(), buffer2.c_ptr(), buffer1.size(), buffer1.c_ptr()); + } + out << "."; + // buffer1 contain the fractional part + unsigned i = 0; + unsigned sz1 = buffer1.size(); + while (sz1 > 0 && buffer1[sz1-1] == 0) + --sz1; + SASSERT(sz1 > 0); // otherwise the number is an integer + while (sz1 > 0) { + if (i >= prec) { + out << "?"; + return; + } + i = i + 1; + multiply(buffer1.c_ptr(), sz1, &ten, 1, buffer2.c_ptr(), 0); + unsigned sz2 = sz1 + 1; + while (sz2 > 0 && buffer2[sz2-1] == 0) + --sz2; + SASSERT(sz2 > 0); + if (num_words > sz2) { + out << "0"; + sz1 = sz2; + ::copy(sz2, buffer2.c_ptr(), sz1, buffer1.c_ptr()); + SASSERT(sz1 == 0 || !::is_zero(sz1, buffer1.c_ptr())); + } + else { + divide(buffer2.c_ptr(), sz2, + pw_buffer.c_ptr(), num_words, + reciprocal_1_NULL, + buffer3.c_ptr(), + buffer1.c_ptr(), + 0); + out << buffer3[0]; + sz1 = num_words; + while (sz1 > 0 && buffer1[sz1-1] == 0) + --sz1; + SASSERT(sz1 == 0 || !::is_zero(sz1, buffer1.c_ptr())); + } + } + } +} + +void mpff_manager::display_smt2(std::ostream & out, mpff const & n, bool decimal) const { + if (is_neg(n)) + out << "(- "; + to_buffer_ext(0, n); + svector & u_buffer = const_cast(this)->m_buffers[0]; + int num_trailing_zeros = ntz(m_precision, u_buffer.c_ptr()); + int shift = 0; + int64 exp = n.m_exponent; + if (exp < 0) { + if (num_trailing_zeros >= -exp) { + shift = static_cast(-exp); + exp = 0; + } + else { + shift = num_trailing_zeros; + exp += num_trailing_zeros; + } + } + if (shift > 0) + shr(m_precision, u_buffer.c_ptr(), shift, u_buffer.c_ptr()); + + if (exp > 0) + out << "(* "; + else if (exp < 0) + out << "(/ "; + + sbuffer str_buffer(11*m_precision, 0); + out << mp_decimal(u_buffer.c_ptr(), m_precision, str_buffer.begin(), str_buffer.size(), 0); + if (decimal) out << ".0"; + + if (exp != 0) { + if (exp < 0) exp = -exp; + if (exp <= 63) { + uint64 _exp = 1; + _exp <<= exp; + out << _exp; + if (decimal) out << ".0"; + } + else { + out << " (^ 2"; + if (decimal) out << ".0"; + out << " " << exp; + if (decimal) out << ".0"; + out << ")"; + } + out << ")"; + } + if (is_neg(n)) + out << ")"; +} + +std::string mpff_manager::to_string(mpff const & a) const { + std::ostringstream buffer; + display(buffer, a); + return buffer.str(); +} + +std::string mpff_manager::to_rational_string(mpff const & a) const { + // The exponent may be too big to encode without using 2^ + return to_string(a); +} + +unsigned mpff_manager::prev_power_of_two(mpff const & a) { + if (!is_pos(a)) + return 0; + if (a.m_exponent <= -static_cast(m_precision_bits)) + return 0; // Number is smaller than 1 + SASSERT(static_cast(a.m_exponent) + static_cast(m_precision_bits) - 1 >= 0); + SASSERT(static_cast(a.m_exponent) + static_cast(m_precision_bits) - 1 <= static_cast(static_cast(UINT_MAX))); + return m_precision_bits + a.m_exponent - 1; +} + +bool mpff_manager::check(mpff const & n) const { + // n is zero or the most significand bit of the most significand word is 1. + unsigned * s = sig(n); + SASSERT(is_zero(n) || (s[m_precision - 1] & MIN_MSW) != 0); + // if n is zero, then the sign must be 0 + SASSERT(!is_zero(n) || n.m_sign == 0); + // if n is zero, then all bits must be 0. + SASSERT(!is_zero(n) || ::is_zero(m_precision, sig(n))); + // if n is zero, then exponent must be 0. + SASSERT(!is_zero(n) || n.m_exponent == 0); + return true; +} diff --git a/lib/mpff.h b/lib/mpff.h new file mode 100644 index 000000000..0295901c4 --- /dev/null +++ b/lib/mpff.h @@ -0,0 +1,479 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + mpff.h + +Abstract: + + Multi precision fast floating point numbers. + The implementation is not compliant with the IEEE standard. + For an IEEE compliant implementation, see mpf.h + + There are only two rounding modes: towards plus or minus inf. + +Author: + + Leonardo de Moura (leonardo) 2012-09-12 + +Revision History: + +--*/ +#ifndef _MPFF_H_ +#define _MPFF_H_ + +#include"id_gen.h" +#include"util.h" +#include"vector.h" +#include"z3_exception.h" +#include"scoped_numeral.h" +#include"scoped_numeral_vector.h" + +class mpff_manager; + +class mpff { + friend class mpff_manager; + unsigned m_sign:1; + unsigned m_sig_idx:31; // position where the significand is stored in the mpff_manager. + int m_exponent; +public: + mpff(): + m_sign(0), + m_sig_idx(0), + m_exponent(0) { + } + + void swap(mpff & other) { + unsigned sign = m_sign; m_sign = other.m_sign; other.m_sign = sign; + unsigned sig_idx = m_sig_idx; m_sig_idx = other.m_sig_idx; other.m_sig_idx = sig_idx; + std::swap(m_exponent, other.m_exponent); + } +}; + +inline void swap(mpff & m1, mpff & m2) { m1.swap(m2); } + +class mpz; +class mpq; +template class mpz_manager; +template class mpq_manager; +typedef mpz_manager synch_mpz_manager; +typedef mpz_manager unsynch_mpz_manager; +typedef mpq_manager synch_mpq_manager; +typedef mpq_manager unsynch_mpq_manager; + +class mpff_manager { + // Some restrictions on mpff numbers + // + // - The exponent is always a machine integer. The main point is that 2^(2^31) is a huge number, + // we will not even be able to convert the mpff into mpq. Formulas that need this kind of huge number + // are usually out-of-reach for Z3. + // + // - The significand size is measured in words of 32-bit. The number of words is always even. + // This decision makes sure that the size (in bits) of mpff numbers is always a multiple of 64. + // Thus mpff objs can be easily packed in 64-bit machines. + // + // - The smallest mpff numeral has 128-bits total. mpff structure has always 64-bits. + // The minimal size for the significand is 64-bits. + // + // - All mpff numerals in a given manager use the same number of words for storing the significand. + // This is different from the mpf_manager where the same manager can be used to manipulate floating point numbers + // of different precision. + // + // - In the encoding used for mpff numbers, the most significand bit of the most significand word is always 1. + // The only exception is the number zero. + // For example, assuming we are using 64-bits for the significand, the number 1 is encoded as + // (sign = 0, significand = 0x800..0, exponent = -63) + // Note that, in this representation, the smallest positive integer is: + // (sign = 0, significand = 0x800..0, exponent = INT_MIN) + // instead of + // (sign = 0, significand = 0x000..1, exponent = INT_MIN) + // + // Remarks: + // + // - All values of type int, unsigned, int64 and uint64 can be precisely represented as mpff numerals. + // + // - Hardware float and double values (corresponding to rationals) can also be precisely represented as mpff numberals. + // That is, NaN, +oo and -oo are not supported by this module. + // + // - An exception (mpff_manager::exception) is thrown if overflow occurs. This can happen because the exponent is + // represented as a machine integer. + // + // - There are only two rounding modes: towards plus infinity and towards minus infinity. + // The rounding mode can be dynamically modified. + // + // - The mpff numerals are stored in a dynamic array. + // Type mpff is just an index (unsigned) into this array. + + unsigned m_precision; //!< Number of words in the significand. Must be an even number. + unsigned m_precision_bits; //!< Number of bits in the significand. Must be 32*m_precision. + vector m_significands; //!< Array containing all significands. + unsigned m_capacity; //!< Number of significands that can be stored in m_significands. + bool m_to_plus_inf; //!< If True, then round to plus infinity, otherwise to minus infinity + id_gen m_id_gen; + static const unsigned MPFF_NUM_BUFFERS = 4; + svector m_buffers[MPFF_NUM_BUFFERS]; + svector m_set_buffer; + mpff m_one; + + unsigned * sig(mpff const & n) const { return m_significands.c_ptr() + (n.m_sig_idx * m_precision); } + + void ensure_capacity(unsigned sig_idx) { + while (sig_idx >= m_capacity) + expand(); + } + + void expand(); + + void allocate_if_needed(mpff & n) { + if (n.m_sig_idx == 0) + allocate(n); + } + + void allocate(mpff & n); + + // copy n to buffer idx. + void to_buffer(unsigned idx, mpff const & n) const; + // copy n to buffer idx and add m_precision zeros. + void to_buffer_ext(unsigned idx, mpff const & n) const; + // copy (and shift by m_precision_bits) n to buffer idx + void to_buffer_shifting(unsigned idx, mpff const & n) const; + + void inc_significand(unsigned * s, int64 & exp); + void inc_significand(mpff & a); + void dec_significand(mpff & a); + bool min_significand(mpff const & a) const; + void set_min_significand(mpff & a); + void set_max_significand(mpff & a); + void set_big_exponent(mpff & a, int64 e); + void set_exponent(mpff & a, int64 e) { + if (e > INT_MAX || e < INT_MIN) + set_big_exponent(a, e); + else + a.m_exponent = static_cast(e); + } + + template + void set_core(mpff & n, mpz_manager & m, mpz const & v); + + template + void set_core(mpff & n, mpq_manager & m, mpq const & v); + + template + void to_mpz_core(mpff const & n, mpz_manager & m, mpz & t); + + template + void to_mpq_core(mpff const & n, mpq_manager & m, mpq & t); + + template + void significand_core(mpff const & n, mpz_manager & m, mpz & r); + + void add_sub(bool is_sub, mpff const & a, mpff const & b, mpff & c); + +public: + typedef mpff numeral; + static bool precise() { return false; } + static bool field() { return true; } + + class exception : public z3_exception { + virtual char const * msg() const { return "multi-precision floating point (mpff) exception"; } + }; + + class overflow_exception : public exception { + virtual char const * msg() const { return "multi-precision floating point (mpff) overflow"; } + }; + + class div0_exception : public exception { + virtual char const * msg() const { return "multi-precision floating point (mpff) division by zero"; } + }; + + mpff_manager(unsigned prec = 2, unsigned initial_capacity = 1024); + ~mpff_manager(); + + void round_to_plus_inf() { m_to_plus_inf = true; } + void round_to_minus_inf() { m_to_plus_inf = false; } + void set_rounding(bool to_plus_inf) { m_to_plus_inf = to_plus_inf; } + bool rounding_to_plus_inf() const { return m_to_plus_inf; } + + /** + \brief Return the exponent of n. + */ + static int exponent(mpff const & n) { return n.m_exponent; } + + /** + \brief Update the exponent of n. + + \remark It is a NOOP if n is zero. + */ + void set_exponent(mpff & n, int exp) { if (is_zero(n)) return; n.m_exponent = exp; SASSERT(check(n)); } + + /** + \brief Return the significand as a mpz numeral. + */ + void significand(mpff const & n, unsynch_mpz_manager & m, mpz & r); + void significand(mpff const & n, synch_mpz_manager & m, mpz & r); + + /** + \brief Return true if n is negative + */ + static bool sign(mpff const & n) { return is_neg(n); } + + /** + \brief Set n to zero. + */ + void reset(mpff & n); + + /** + \brief Return true if n is an integer. + */ + bool is_int(mpff const & n) const; + + /** + \brief Return true if n is zero. + */ + static bool is_zero(mpff const & n) { return n.m_sig_idx == 0; } + + /** + \brief Return true if n is positive. + */ + static bool is_pos(mpff const & n) { return n.m_sign == 0 && !is_zero(n); } + + /** + \brief Return true if n is negative. + */ + static bool is_neg(mpff const & n) { return n.m_sign != 0; } + + /** + \brief Return true if n is non positive. + */ + static bool is_nonpos(mpff const & n) { return !is_pos(n); } + + /** + \brief Return true if n is non negative. + */ + static bool is_nonneg(mpff const & n) { return !is_neg(n); } + + /** + \brief Return true if the absolute value of n is 1. + */ + bool is_abs_one(mpff const & n) const; + + /** + \brief Return true if n is one. + */ + bool is_one(mpff const & n) const { return is_pos(n) && is_abs_one(n); } + + /** + \brief Return true if n is minus one. + */ + bool is_minus_one(mpff const & n) const { return is_neg(n) && is_abs_one(n); } + + /** + \brief Return true if n is two. + */ + bool is_two(mpff const & n) const; + + /** + \brief Return true if \c a is the smallest representable negative number. + */ + bool is_minus_epsilon(mpff const & a) const; + + /** + \brief Return true if \c a is the smallest representable positive number. + */ + bool is_plus_epsilon(mpff const & a) const; + + /** + \brief Return true if \c a is an integer and fits in an int64 machine integer. + */ + bool is_int64(mpff const & a) const; + + /** + \brief Return true if \c a is a non-negative integer and fits in an int64 machine integer. + */ + bool is_uint64(mpff const & a) const; + + /** + \brief Delete the resources associated with n. + */ + void del(mpff & n); + + /** + \brief a <- -a + */ + static void neg(mpff & a) { if (!is_zero(a)) a.m_sign = !a.m_sign; } + + /** + \brief a <- |a| + */ + static void abs(mpff & a) { a.m_sign = 0; } + + static void swap(mpff & a, mpff & b) { a.swap(b); } + + /** + \brief c <- a + b + */ + void add(mpff const & a, mpff const & b, mpff & c); + + /** + \brief c <- a - b + */ + void sub(mpff const & a, mpff const & b, mpff & c); + + /** + \brief a <- a + 1 + */ + void inc(mpff & a) { add(a, m_one, a); } + + /** + \brief a <- a - 1 + */ + void dec(mpff & a) { sub(a, m_one, a); } + + /** + \brief c <- a * b + */ + void mul(mpff const & a, mpff const & b, mpff & c); + + /** + \brief c <- a / b + + \pre !is_zero(b) + */ + void div(mpff const & a, mpff const & b, mpff & c); + + /** + \brief a <- 1/a + + \pre !is_zero(a); + */ + void inv(mpff & a) { div(m_one, a, a); } + void inv(mpff const & a, mpff & b) { set(b, a); inv(b); } + + /** + \brief b <- a^k + */ + void power(mpff const & a, unsigned k, mpff & b); + + /** + \brief Return true if \c a is a power of 2. That is, a is equal to 2^k for some k >= 0. + */ + bool is_power_of_two(mpff const & a, unsigned & k) const; + bool is_power_of_two(mpff const & a) const; + + bool eq(mpff const & a, mpff const & b) const; + bool neq(mpff const & a, mpff const & b) const { return !eq(a, b); } + bool lt(mpff const & a, mpff const & b) const; + bool gt(mpff const & a, mpff const & b) const { return lt(b, a); } + bool le(mpff const & a, mpff const & b) const { return !lt(b, a); } + bool ge(mpff const & a, mpff const & b) const { return !lt(a, b); } + + void set(mpff & n, int v); + void set(mpff & n, unsigned v); + void set(mpff & n, int64 v); + void set(mpff & n, uint64 v); + void set(mpff & n, int num, unsigned den); + void set(mpff & n, int64 num, uint64 den); + void set(mpff & n, mpff const & v); + void set(mpff & n, unsynch_mpz_manager & m, mpz const & v); + void set(mpff & n, synch_mpz_manager & m, mpz const & v); + void set(mpff & n, unsynch_mpq_manager & m, mpq const & v); + void set(mpff & n, synch_mpq_manager & m, mpq const & v); + void set_plus_epsilon(mpff & n); + void set_minus_epsilon(mpff & n); + void set_max(mpff & n); + void set_min(mpff & n); + + /** + \brief n <- floor(n) + */ + void floor(mpff & n); + void floor(mpff const & n, mpff & o) { set(o, n); floor(o); } + + /** + \brief n <- ceil(n) + */ + void ceil(mpff & n); + void ceil(mpff const & n, mpff & o) { set(o, n); ceil(o); } + + /** + \brief Update \c a to the next representable float. + + Throws an exception if \c a is the maximal representable float. + */ + void next(mpff & a); + /** + \brief Update \c a to the previous representable float. + + Throws an exception if \c a is the minimal representable float. + */ + void prev(mpff & a); + + /** + \brief Convert n into a mpz numeral. + + \pre is_int(n) + + \remark if exponent(n) is too big, we may run out of memory. + */ + void to_mpz(mpff const & n, unsynch_mpz_manager & m, mpz & t); + + /** + \brief Convert n into a mpz numeral. + + \pre is_int(n) + + \remark if exponent(n) is too big, we may run out of memory. + */ + void to_mpz(mpff const & n, synch_mpz_manager & m, mpz & t); + + /** + \brief Convert n into a mpq numeral. + + \remark if exponent(n) is too big, we may run out of memory. + */ + void to_mpq(mpff const & n, unsynch_mpq_manager & m, mpq & t); + + /** + \brief Convert n into a mpq numeral. + + \remark if exponent(n) is too big, we may run out of memory. + */ + void to_mpq(mpff const & n, synch_mpq_manager & m, mpq & t); + + /** + \brief Return n as an int64. + + \pre is_int64(n) + */ + int64 get_int64(mpff const & n) const; + + /** + \brief Return n as an uint64. + + \pre is_uint64(n) + */ + uint64 get_uint64(mpff const & n) const; + + /** + \brief Return the biggest k s.t. 2^k <= a. + + \remark Return 0 if a is not positive. + */ + unsigned prev_power_of_two(mpff const & a); + + void display_raw(std::ostream & out, mpff const & n) const; + void display(std::ostream & out, mpff const & n) const; + void display_decimal(std::ostream & out, mpff const & n, unsigned prec=32, unsigned max_power=128) const; + void display_smt2(std::ostream & out, mpff const & n, bool decimal=true) const; + + std::string to_string(mpff const & a) const; + std::string to_rational_string(mpff const & a) const; + + bool check(mpff const & n) const; +}; + +typedef _scoped_numeral scoped_mpff; +typedef _scoped_numeral_vector scoped_mpff_vector; + +#endif diff --git a/lib/mpfx.cpp b/lib/mpfx.cpp new file mode 100644 index 000000000..cf6f8338f --- /dev/null +++ b/lib/mpfx.cpp @@ -0,0 +1,866 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + mpfx.h + +Abstract: + + Multi precision fixed point numbers. + +Author: + + Leonardo de Moura (leonardo) 2012-09-19 + +Revision History: + +--*/ +#include +#include +#include"mpfx.h" +#include"mpn.h" +#include"mpz.h" +#include"mpq.h" +#include"bit_util.h" +#include"trace.h" + +mpfx_manager::mpfx_manager(unsigned int_sz, unsigned frac_sz, unsigned initial_capacity) { + SASSERT(initial_capacity > 0); + SASSERT(int_sz > 0); + SASSERT(frac_sz > 0); + m_int_part_sz = int_sz; + m_frac_part_sz = frac_sz; + m_total_sz = m_int_part_sz + m_frac_part_sz; + m_words.resize(initial_capacity * m_total_sz, 0); + m_capacity = initial_capacity; + m_to_plus_inf = false; + m_buffer0.resize(2*m_total_sz, 0); + m_buffer1.resize(2*m_total_sz, 0); + m_buffer2.resize(2*m_total_sz, 0); + unsigned zero_sig_idx = m_id_gen.mk(); + SASSERT(zero_sig_idx == 0); + set(m_one, 1); +} + +mpfx_manager::~mpfx_manager() { + del(m_one); +} + +void mpfx_manager::expand() { + m_capacity = 2*m_capacity; + m_words.resize(m_capacity * m_total_sz, 0); +} + +void mpfx_manager::allocate(mpfx & n) { + SASSERT(n.m_sig_idx == 0); + unsigned sig_idx = m_id_gen.mk(); + ensure_capacity(sig_idx); + n.m_sig_idx = sig_idx; + SASSERT(::is_zero(m_total_sz, words(n))); +} + +unsigned mpfx_manager::sz(unsigned * ws) const { + SASSERT(!::is_zero(m_total_sz, ws)); + unsigned r = m_total_sz; + while (true) { + SASSERT(r > 0); + --r; + if (ws[r] != 0) + return r + 1; + } +} + +void mpfx_manager::del(mpfx & n) { + unsigned sig_idx = n.m_sig_idx; + if (sig_idx != 0) { + m_id_gen.recycle(sig_idx); + unsigned * w = words(n); + for (unsigned i = 0; i < m_total_sz; i++) + w[i] = 0; + } +} + +void mpfx_manager::reset(mpfx & n) { + del(n); + n.m_sign = false; + n.m_sig_idx = 0; + SASSERT(check(n)); +} + +bool mpfx_manager::is_int(mpfx const & n) const { + unsigned * w = words(n); + for (unsigned i = 0; i < m_frac_part_sz; i++) + if (w[i] != 0) + return false; + return true; +} + +bool mpfx_manager::is_abs_one(mpfx const & n) const { + unsigned * w = words(n); + return is_int(n) && w[m_frac_part_sz] == 1 && ::is_zero(m_int_part_sz - 1, w + m_frac_part_sz + 1); +} + +bool mpfx_manager::is_int64(mpfx const & a) const { + if (!is_int(a)) + return false; + if (is_zero(a) || m_int_part_sz <= 1) + return true; + unsigned * w = words(a); + w += m_frac_part_sz; + if (w[1] < 0x80000000u || (w[1] == 0x80000000u && is_neg(a))) { + for (unsigned i = 2; i < m_int_part_sz; i++) + if (w[i] != 0) + return false; + return true; + } + else { + return false; + } +} + +bool mpfx_manager::is_uint64(mpfx const & a) const { + if (!is_int(a) || is_neg(a)) + return false; + if (is_zero(a) || m_int_part_sz <= 2) + return true; + unsigned * w = words(a); + for (unsigned i = m_frac_part_sz + 2; i < m_total_sz; i++) + if (w[i] != 0) + return false; + return true; +} + +void mpfx_manager::set(mpfx & n, int v) { + if (v == 0) { + reset(n); + } + else { + if (v < 0) { + set(n, static_cast(-v)); + n.m_sign = 1; + } + else { + set(n, static_cast(v)); + } + } + SASSERT(get_int64(n) == v); + SASSERT(check(n)); +} + +void mpfx_manager::set(mpfx & n, unsigned v) { + if (v == 0) { + reset(n); + } + else { + allocate_if_needed(n); + n.m_sign = 0; + unsigned * w = words(n); + for (unsigned i = 0; i < m_total_sz; i++) + w[i] = 0; + w[m_frac_part_sz] = v; + } + SASSERT(is_int(n)); + SASSERT(get_uint64(n) == v); + SASSERT(check(n)); +} + +void mpfx_manager::set(mpfx & n, int64 v) { + if (m_int_part_sz == 1) { + if (v < -static_cast(static_cast(UINT_MAX)) || + v > static_cast(static_cast(UINT_MAX))) + throw overflow_exception(); + } + if (v == 0) { + reset(n); + } + else { + if (v < 0) { + set(n, static_cast(-v)); + n.m_sign = 1; + } + else { + set(n, static_cast(v)); + } + } + SASSERT(is_int(n)); + SASSERT(get_int64(n) == v); + SASSERT(check(n)); +} + +void mpfx_manager::set(mpfx & n, uint64 v) { + if (m_int_part_sz == 1) { + if (v > static_cast(UINT_MAX)) + throw overflow_exception(); + } + + if (v == 0) { + reset(n); + } + else { + allocate_if_needed(n); + n.m_sign = 0; + unsigned * _v = reinterpret_cast(&v); + unsigned * w = words(n); + for (unsigned i = 0; i < m_total_sz; i++) + w[i] = 0; + w[m_frac_part_sz] = _v[0]; + if (m_int_part_sz == 1) { + SASSERT(_v[1] == 0); + } + else { + w[m_frac_part_sz+1] = _v[1]; + } + } + SASSERT(is_int(n)); + SASSERT(get_uint64(n) == v); + SASSERT(check(n)); +} + +void mpfx_manager::set(mpfx & n, int num, unsigned den) { + scoped_mpfx a(*this), b(*this); + set(a, num); + set(b, den); + div(a, b, n); + SASSERT(check(n)); +} + +void mpfx_manager::set(mpfx & n, int64 num, uint64 den) { + scoped_mpfx a(*this), b(*this); + set(a, num); + set(b, den); + div(a, b, n); + SASSERT(check(n)); +} + +void mpfx_manager::set(mpfx & n, mpfx const & v) { + if (is_zero(v)) { + reset(n); + return; + } + allocate_if_needed(n); + n.m_sign = v.m_sign; + unsigned * w1 = words(n); + unsigned * w2 = words(v); + for (unsigned i = 0; i < m_total_sz; i++) + w1[i] = w2[i]; + SASSERT(check(n)); +} + +template +void mpfx_manager::set_core(mpfx & n, mpz_manager & m, mpz const & v) { + if (m.is_zero(v)) { + reset(n); + } + else { + m_tmp_digits.reset(); + allocate_if_needed(n); + n.m_sign = m.decompose(v, m_tmp_digits); + unsigned sz = m_tmp_digits.size(); + if (sz > m_int_part_sz) + throw overflow_exception(); + unsigned * w = words(n); + for (unsigned i = 0; i < m_frac_part_sz; i++) + w[i] = 0; + ::copy(sz, m_tmp_digits.c_ptr(), m_int_part_sz, w + m_frac_part_sz); + } + SASSERT(check(n)); +} + +void mpfx_manager::set(mpfx & n, unsynch_mpz_manager & m, mpz const & v) { + set_core(n, m, v); +} + +void mpfx_manager::set(mpfx & n, synch_mpz_manager & m, mpz const & v) { + set_core(n, m, v); +} + +template +void mpfx_manager::set_core(mpfx & n, mpq_manager & m, mpq const & v) { + if (m.is_int(v)) { + set_core(n, m, v.numerator()); + } + else { + allocate_if_needed(n); + _scoped_numeral > tmp(m); + n.m_sign = is_neg(n); + m.mul2k(v.numerator(), 8 * sizeof(unsigned) * m_frac_part_sz, tmp); + m.abs(tmp); + if ((n.m_sign == 1) != m_to_plus_inf && !m.divides(v.denominator(), tmp)) { + m.div(tmp, v.denominator(), tmp); + m.inc(tmp); + } + else { + m.div(tmp, v.denominator(), tmp); + } + m_tmp_digits.reset(); + m.decompose(tmp, m_tmp_digits); + unsigned sz = m_tmp_digits.size(); + if (sz > m_total_sz) + throw overflow_exception(); + unsigned * w = words(n); + ::copy(sz, m_tmp_digits.c_ptr(), m_total_sz, w); + } + SASSERT(check(n)); +} + +void mpfx_manager::set(mpfx & n, unsynch_mpq_manager & m, mpq const & v) { + set_core(n, m, v); +} + +void mpfx_manager::set(mpfx & n, synch_mpq_manager & m, mpq const & v) { + set_core(n, m, v); +} + +bool mpfx_manager::eq(mpfx const & a, mpfx const & b) const { + if (is_zero(a) && is_zero(b)) + return true; + if (is_zero(a) || is_zero(b)) + return false; + if (a.m_sign != b.m_sign) + return false; + unsigned * w1 = words(a); + unsigned * w2 = words(b); + for (unsigned i = 0; i < m_total_sz; i++) + if (w1[i] != w2[i]) + return false; + return true; +} + +bool mpfx_manager::lt(mpfx const & a, mpfx const & b) const { + STRACE("mpfx_trace", tout << "[mpfx] ("; display(tout, a); tout << " < "; display(tout, b); tout << ") == ";); + bool r; + if (is_zero(a)) { + r = !is_zero(b) && !is_neg(b); + } + else if (is_zero(b)) { + r = is_neg(a); + } + else { + SASSERT(!is_zero(a)); + SASSERT(!is_zero(b)); + if (is_neg(a)) { + r = is_pos(b) || ::lt(m_total_sz, words(b), words(a)); + } + else { + SASSERT(is_pos(a)); + r = is_pos(b) && ::lt(m_total_sz, words(a), words(b)); + } + } + STRACE("mpfx_trace", tout << "(" << r << " == 1)\n";); + return r; +} + +void mpfx_manager::add_sub(bool is_sub, mpfx const & a, mpfx const & b, mpfx & c) { + if (is_zero(a)) { + set(c, b); + if (is_sub) + neg(c); + return; + } + + if (is_zero(b)) { + set(c, a); + return; + } + + TRACE("mpfx", tout << (is_sub ? "sub" : "add") << "("; display(tout, a); tout << ", "; display(tout, b); tout << ")\n";); + + allocate_if_needed(c); + + bool sgn_a = a.m_sign; + bool sgn_b = b.m_sign; + unsigned * w_a = words(a); + unsigned * w_b = words(b); + + if (is_sub) + sgn_b = !sgn_b; + + // Compute c + unsigned * w_c = words(c); + if (sgn_a == sgn_b) { + c.m_sign = sgn_a; + if (!::add(m_total_sz, w_a, w_b, w_c)) + throw overflow_exception(); + } + else { + unsigned borrow; + SASSERT(sgn_a != sgn_b); + if (::lt(m_total_sz, w_a, w_b)) { + c.m_sign = sgn_b; + sub_diff(w_b, m_total_sz, w_a, m_total_sz, w_c, &borrow, 0); + SASSERT(!::is_zero(m_total_sz, w_c)); + } + else { + c.m_sign = sgn_a; + sub_diff(w_a, m_total_sz, w_b, m_total_sz, w_c, &borrow, 0); + if (::is_zero(m_total_sz, w_c)) + reset(c); + } + SASSERT(borrow == 0); + } + TRACE("mpfx", tout << "result: "; display(tout, c); tout << "\n";); + SASSERT(check(c)); +} + +void mpfx_manager::add(mpfx const & a, mpfx const & b, mpfx & c) { + STRACE("mpfx_trace", tout << "[mpfx] "; display(tout, a); tout << " + "; display(tout, b); tout << " == ";); + add_sub(false, a, b, c); + STRACE("mpfx_trace", display(tout, c); tout << "\n";); +} + +void mpfx_manager::sub(mpfx const & a, mpfx const & b, mpfx & c) { + STRACE("mpfx_trace", tout << "[mpfx] "; display(tout, a); tout << " - "; display(tout, b); tout << " == ";); + add_sub(true, a, b, c); + STRACE("mpfx_trace", display(tout, c); tout << "\n";); +} + +void mpfx_manager::mul(mpfx const & a, mpfx const & b, mpfx & c) { + STRACE("mpfx_trace", tout << "[mpfx] ("; display(tout, a); tout << ") * ("; display(tout, b); tout << ") " << (m_to_plus_inf ? "<=" : ">=") << " ";); + if (is_zero(a) || is_zero(b)) { + reset(c); + } + else { + allocate_if_needed(c); + c.m_sign = a.m_sign ^ b.m_sign; + unsigned * r = m_buffer0.c_ptr(); + multiply(words(a), m_total_sz, words(b), m_total_sz, r, 0); + // round result + unsigned * _r = r + m_frac_part_sz; + if ((c.m_sign == 1) != m_to_plus_inf && !::is_zero(m_frac_part_sz, r)) { + if (!::inc(m_total_sz, _r)) + throw overflow_exception(); + } + // check for overflows + if (!::is_zero(m_int_part_sz, _r + m_total_sz)) + throw overflow_exception(); + // copy result to c + unsigned * w_c = words(c); + for (unsigned i = 0; i < m_total_sz; i++) + w_c[i] = _r[i]; + } + STRACE("mpfx_trace", display(tout, c); tout << "\n";); + SASSERT(check(c)); +} + +void mpfx_manager::div(mpfx const & a, mpfx const & b, mpfx & c) { + if (is_zero(b)) + throw div0_exception(); + STRACE("mpfx_trace", tout << "[mpfx] ("; display(tout, a); tout << ") / ("; display(tout, b); tout << ") " << (m_to_plus_inf ? "<=" : ">=") << " ";); + if (is_zero(a)) { + reset(c); + } + else { + allocate_if_needed(c); + c.m_sign = a.m_sign ^ b.m_sign; + unsigned * w_a = words(a); + unsigned * w_a_shft = m_buffer0.c_ptr(); + unsigned a_shft_sz = sz(w_a) + m_frac_part_sz; + // copy a to buffer 0, and shift by m_frac_part_sz + for (unsigned i = 0; i < m_frac_part_sz; i++) + w_a_shft[i] = 0; + for (unsigned i = 0; i < m_total_sz; i++) + w_a_shft[i+m_frac_part_sz] = w_a[i]; + unsigned * w_b = words(b); + unsigned b_sz = sz(w_b); + unsigned * w_q = m_buffer1.c_ptr(); + if (b_sz > a_shft_sz) { + if ((c.m_sign == 1) != m_to_plus_inf) + set_epsilon(c); + else + reset(c); + } + else { + unsigned q_sz = a_shft_sz - b_sz + 1; + unsigned * w_r = m_buffer2.c_ptr(); + unsigned r_sz = b_sz; + divide(w_a_shft, a_shft_sz, + w_b, b_sz, + reciprocal_1_NULL, + w_q, + w_r, + 0); + for (unsigned i = m_total_sz; i < q_sz; i++) + if (w_q[i] != 0) + throw overflow_exception(); + if (((c.m_sign == 1) != m_to_plus_inf) && !::is_zero(r_sz, w_r)) { + // round the result + if (!::inc(m_total_sz, w_q)) + throw overflow_exception(); + } + unsigned * w_c = words(c); + bool zero_q = true; + if (m_total_sz >= q_sz) { + unsigned i; + for (i = 0; i < q_sz; i++) { + if (w_q[i] != 0) + zero_q = false; + w_c[i] = w_q[i]; + } + for (; i < m_total_sz; i++) + w_c[i] = 0; + } + else { + for (unsigned i = 0; i < m_total_sz; i++) { + if (w_q[i] != 0) + zero_q = false; + w_c[i] = w_q[i]; + } + } + if (zero_q) { + if ((c.m_sign == 1) != m_to_plus_inf) + set_epsilon(c); + else + reset(c); + } + } + } + STRACE("mpfx_trace", display(tout, c); tout << "\n";); + SASSERT(check(c)); +} + +void mpfx_manager::div2k(mpfx & a, unsigned k) { + STRACE("mpfx_trace", tout << "[mpfx] ("; display(tout, a); tout << ") / (2^" << k << ") " << (m_to_plus_inf ? "<=" : ">=") << " ";); + if (!is_zero(a) && k > 0) { + unsigned * w = words(a); + bool _inc = ((a.m_sign == 1) != m_to_plus_inf) && has_one_at_first_k_bits(m_total_sz, w, k); + shr(m_total_sz, w, k, m_total_sz, w); + if (_inc) { + VERIFY(::inc(m_total_sz, w)); + SASSERT(!::is_zero(m_total_sz, w)); + } + else if (::is_zero(m_total_sz, w)) { + reset(a); + } + } + STRACE("mpfx_trace", display(tout, a); tout << "\n";); + SASSERT(check(a)); +} + +void mpfx_manager::set_epsilon(mpfx & n) { + unsigned * w = words(n); + w[0] = 1; + for (unsigned i = 1; i < m_total_sz; i++) + w[i] = 0; +} + +void mpfx_manager::set_minus_epsilon(mpfx & n) { + set_epsilon(n); + n.m_sign = true; + SASSERT(check(n)); +} + +void mpfx_manager::set_plus_epsilon(mpfx & n) { + set_epsilon(n); + n.m_sign = 0; + SASSERT(check(n)); +} + +void mpfx_manager::floor(mpfx & n) { + STRACE("mpfx_trace", tout << "[mpfx] Floor["; display(tout, n); tout << "] == ";); + unsigned * w = words(n); + if (is_neg(n)) { + bool is_int = true; + for (unsigned i = 0; i < m_frac_part_sz; i++) { + if (w[i] != 0) { + is_int = false; + w[i] = 0; + } + } + if (!is_int && !::inc(m_int_part_sz, w + m_frac_part_sz)) + throw overflow_exception(); + } + else { + for (unsigned i = 0; i < m_frac_part_sz; i++) + w[i] = 0; + } + if (::is_zero(m_int_part_sz, w + m_frac_part_sz)) + reset(n); + SASSERT(check(n)); + STRACE("mpfx_trace", display(tout, n); tout << "\n";); +} + +void mpfx_manager::ceil(mpfx & n) { + STRACE("mpfx_trace", tout << "[mpfx] Ceiling["; display(tout, n); tout << "] == ";); + unsigned * w = words(n); + if (is_pos(n)) { + bool is_int = true; + for (unsigned i = 0; i < m_frac_part_sz; i++) { + if (w[i] != 0) { + is_int = false; + w[i] = 0; + } + } + if (!is_int && !::inc(m_int_part_sz, w + m_frac_part_sz)) + throw overflow_exception(); + } + else { + for (unsigned i = 0; i < m_frac_part_sz; i++) + w[i] = 0; + } + if (::is_zero(m_int_part_sz, w + m_frac_part_sz)) + reset(n); + SASSERT(check(n)); + STRACE("mpfx_trace", display(tout, n); tout << "\n";); +} + +void mpfx_manager::power(mpfx const & a, unsigned p, mpfx & b) { +#ifdef _TRACE + scoped_mpfx _a(*this); _a = a; + unsigned _p = p; +#endif +#define SMALL_POWER 8 + SASSERT(check(a)); + if (is_zero(a)) { + SASSERT(p != 0); + reset(b); + } + else if (p == 0) { + set(b, 1); + } + else if (p == 1) { + set(b, a); + } + else if (p == 2) { + mul(a, a, b); + } + else if (p <= SMALL_POWER && &a != &b) { + SASSERT(p > 2); + --p; + set(b, a); + while (p > 0) { + --p; + mul(a, b, b); + } + } + else { + unsigned mask = 1; + scoped_mpfx pw(*this); + set(pw, a); + set(b, 1); + while (mask <= p) { + if (mask & p) + mul(b, pw, b); + mul(pw, pw, pw); + mask = mask << 1; + } + } + STRACE("mpfx_trace", tout << "[mpfx] ("; display(tout, _a); tout << ") ^ " << _p << (m_to_plus_inf ? "<=" : ">="); display(tout, b); tout << "\n";); + TRACE("mpfx_power", display_raw(tout, b); tout << "\n";); + SASSERT(check(b)); +} + + +bool mpfx_manager::is_power_of_two(mpfx const & a, unsigned & k) const { + if (!is_int(a) || is_zero(a)) + return false; + unsigned * w = words(a); + unsigned i = m_total_sz; + while (true) { + SASSERT (i > m_frac_part_sz); + --i; + if (w[i] != 0) { + if (!::is_power_of_two(w[i])) + return false; + k = (i - m_frac_part_sz) * 8 * sizeof(unsigned) + log2(w[i]); + while (i > m_frac_part_sz) { + --i; + if (w[i] != 0) + return false; + } + return true; + } + } +} + +bool mpfx_manager::is_power_of_two(mpfx const & a) const { + unsigned k; + return is_power_of_two(a, k); +} + +int64 mpfx_manager::get_int64(mpfx const & n) const { + SASSERT(is_int64(n)); + unsigned * w = words(n); + w += m_frac_part_sz; + uint64 r = *reinterpret_cast(w); + if (r == 0x8000000000000000ull) { + SASSERT(is_neg(n)); + return INT64_MIN; + } + else { + return is_neg(n) ? -static_cast(r) : r; + } +} + +uint64 mpfx_manager::get_uint64(mpfx const & n) const { + SASSERT(is_uint64(n)); + unsigned * w = words(n); + w += m_frac_part_sz; + return *reinterpret_cast(w); +} + +template +void mpfx_manager::to_mpz_core(mpfx const & n, mpz_manager & m, mpz & t) { + SASSERT(is_int(n)); + unsigned * w = words(n); + m.set(t, m_int_part_sz, w+m_frac_part_sz); + if (is_neg(n)) + m.neg(t); +} + +void mpfx_manager::to_mpz(mpfx const & n, unsynch_mpz_manager & m, mpz & t) { + to_mpz_core(n, m, t); +} + +void mpfx_manager::to_mpz(mpfx const & n, synch_mpz_manager & m, mpz & t) { + to_mpz_core(n, m, t); +} + +template +void mpfx_manager::to_mpq_core(mpfx const & n, mpq_manager & m, mpq & t) { + _scoped_numeral > a(m), b(m); + + unsigned * w = words(n); + m.set(a, m_total_sz, w); + + m.set(b, 1); + m.mul2k(b, sizeof(unsigned)*8*m_frac_part_sz); + + m.rat_div(a, b, t); + + if (is_neg(n)) + m.neg(t); +} + +void mpfx_manager::to_mpq(mpfx const & n, unsynch_mpq_manager & m, mpq & t) { + to_mpq_core(n, m, t); +} + +void mpfx_manager::to_mpq(mpfx const & n, synch_mpq_manager & m, mpq & t) { + to_mpq_core(n, m, t); +} + +void mpfx_manager::display_raw(std::ostream & out, mpfx const & n) const { + if (is_neg(n)) + out << "-"; + unsigned * w = words(n); + unsigned i = m_total_sz; + while(i > 0) { + if (i == m_frac_part_sz) + out << "."; + --i; + out << std::hex << std::setfill('0') << std::setw(2 * sizeof(unsigned)) << w[i]; + } +} + +void mpfx_manager::display(std::ostream & out, mpfx const & n) const { + if (is_neg(n)) + out << "-"; + unsigned * w = words(n); + unsigned sz = m_total_sz; + unsigned shift = UINT_MAX; + if (is_int(n)) { + w += m_frac_part_sz; + sz -= m_frac_part_sz; + } + else { + shift = ntz(m_total_sz, w); + if (shift > 0) + shr(m_total_sz, w, shift, m_total_sz, w); + } + + sbuffer str_buffer(11*sz, 0); + out << mp_decimal(w, sz, str_buffer.begin(), str_buffer.size(), 0); + if (!is_int(n)) { + SASSERT(shift != UINT_MAX); + // reverse effect of shr + if (shift > 0) + shl(m_total_sz, w, shift, m_total_sz, w); + // display denominator as a power of 2 + unsigned k = sizeof(unsigned)*8*m_frac_part_sz - shift; + out << "/2"; + if (k > 1) + out << "^" << k; + } +} + +void mpfx_manager::display_smt2(std::ostream & out, mpfx const & n) const { + if (is_neg(n)) + out << "(- "; + unsigned * w = words(n); + unsigned sz = m_total_sz; + if (is_int(n)) { + w += m_frac_part_sz; + sz -= m_frac_part_sz; + } + else { + out << "(/ "; + } + sbuffer str_buffer(11*sz, 0); + out << mp_decimal(w, sz, str_buffer.begin(), str_buffer.size(), 0); + if (!is_int(n)) { + out << " "; + unsigned * w = m_buffer0.c_ptr(); + for (unsigned i = 0; i < m_frac_part_sz; i++) + w[i] = 0; + w[m_frac_part_sz] = 1; + sbuffer str_buffer2(11*(m_frac_part_sz+1), 0); + out << mp_decimal(w, m_frac_part_sz+1, str_buffer2.begin(), str_buffer2.size(), 0); + out << ")"; + } + if (is_neg(n)) + out << ")"; +} + +void mpfx_manager::display_decimal(std::ostream & out, mpfx const & n, unsigned prec) const { + if (is_neg(n)) + out << "-"; + unsigned * w = words(n); + sbuffer str_buffer(11*m_int_part_sz, 0); + out << mp_decimal(w + m_frac_part_sz, m_int_part_sz, str_buffer.begin(), str_buffer.size(), 0); + if (!is_int(n)) { + out << "."; + unsigned * frac = m_buffer0.c_ptr(); + ::copy(m_frac_part_sz, w, m_frac_part_sz, frac); + unsigned ten = 10; + unsigned * n_frac = m_buffer1.c_ptr(); + bool frac_is_zero = false; + unsigned i = 0; + while (!frac_is_zero) { + if (i >= prec) { + out << "?"; + return; + } + multiply(frac, m_frac_part_sz, &ten, 1, n_frac, 0); + frac_is_zero = ::is_zero(m_frac_part_sz, n_frac); + SASSERT(n_frac[m_frac_part_sz] <= 9); + if (!frac_is_zero || n_frac[m_frac_part_sz] != 0) + out << n_frac[m_frac_part_sz]; + n_frac[m_frac_part_sz] = 0; + std::swap(frac, n_frac); + i++; + } + } +} + +std::string mpfx_manager::to_string(mpfx const & a) const { + std::ostringstream buffer; + display(buffer, a); + return buffer.str(); +} + +std::string mpfx_manager::to_rational_string(mpfx const & a) const { + return to_string(a); +} + +bool mpfx_manager::check(mpfx const & a) const { + SASSERT(!is_zero(a) || a.m_sign == 0); + SASSERT(is_zero(a) == ::is_zero(m_total_sz, words(a))); + return true; +} + +unsigned mpfx_manager::prev_power_of_two(mpfx const & a) { + if (!is_pos(a)) + return 0; + return m_int_part_sz * sizeof(unsigned) * 8 - nlz(m_int_part_sz, words(a) + m_frac_part_sz) - 1; +} diff --git a/lib/mpfx.h b/lib/mpfx.h new file mode 100644 index 000000000..476da79e3 --- /dev/null +++ b/lib/mpfx.h @@ -0,0 +1,398 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + mpfx.h + +Abstract: + + Multi precision fixed point numbers. + +Author: + + Leonardo de Moura (leonardo) 2012-09-19 + +Revision History: + +--*/ +#ifndef _MPFX_H_ +#define _MPFX_H_ + +#include"id_gen.h" +#include"util.h" +#include"vector.h" +#include"z3_exception.h" +#include"scoped_numeral.h" +#include"scoped_numeral_vector.h" + +class mpfx_manager; + +class mpfx { + friend class mpfx_manager; + unsigned m_sign:1; + unsigned m_sig_idx:31; // position where the data is stored in the mpfx_manager. +public: + mpfx(): + m_sign(0), + m_sig_idx(0) { + } + + void swap(mpfx & other) { + unsigned sign = m_sign; m_sign = other.m_sign; other.m_sign = sign; + unsigned sig_idx = m_sig_idx; m_sig_idx = other.m_sig_idx; other.m_sig_idx = sig_idx; + } +}; + +inline void swap(mpfx & m1, mpfx & m2) { m1.swap(m2); } + +class mpz; +class mpq; +template class mpz_manager; +template class mpq_manager; +typedef mpz_manager synch_mpz_manager; +typedef mpz_manager unsynch_mpz_manager; +typedef mpq_manager synch_mpq_manager; +typedef mpq_manager unsynch_mpq_manager; + +class mpfx_manager { + // Every mpfx numeral from a given mpfx_manager uses the same number of words + // to encode the integer and fractional parts. + // + // The number of words used to encode the integer part may be different from the number of words + // used to encode the fractional part. + // + // There are two rounding modes: towards plus infinity, and towards minus infinity. + // + // If the result of an operation does not fit in the integer part, then an overflow exception is thrown. + // + // If the fractional part uses n words, then the error of every operation is less than 1/2^(32*n). + // + // Machine integer values (int, unsigned, int64, uint64) can be easily converted into mpfx numerals. + // + // The result of addition and subtraction operations are always precise. Note that overflows will trigger + // an exception instead of an incorrect result. + // + unsigned m_int_part_sz; + unsigned m_frac_part_sz; + unsigned m_total_sz; //!< == m_int_part_sz + m_frac_part_sz + unsigned_vector m_words; //!< Array containing all words + unsigned m_capacity; //!< Number of mpfx numerals that can be stored in m_words. + bool m_to_plus_inf; //!< If True, then round to plus infinity, otherwise to minus infinity + id_gen m_id_gen; + unsigned_vector m_buffer0, m_buffer1, m_buffer2; + unsigned_vector m_tmp_digits; + mpfx m_one; + + unsigned * words(mpfx const & n) const { return m_words.c_ptr() + (n.m_sig_idx * m_total_sz); } + unsigned sz(unsigned * ws) const; + + void ensure_capacity(unsigned sig_idx) { + while (sig_idx >= m_capacity) + expand(); + } + + void expand(); + + void allocate_if_needed(mpfx & n) { + if (n.m_sig_idx == 0) + allocate(n); + } + + void allocate(mpfx & n); + + void set_epsilon(mpfx & n); + + void add_sub(bool is_sub, mpfx const & a, mpfx const & b, mpfx & c); + + template + void set_core(mpfx & n, mpz_manager & m, mpz const & v); + + template + void set_core(mpfx & n, mpq_manager & m, mpq const & v); + + template + void to_mpz_core(mpfx const & n, mpz_manager & m, mpz & t); + + template + void to_mpq_core(mpfx const & n, mpq_manager & m, mpq & t); + +public: + typedef mpfx numeral; + static bool precise() { return false; } + static bool field() { return true; } + + class exception : public z3_exception { + virtual char const * msg() const { return "multi-precision fixed point (mpfx) exception"; } + }; + + class overflow_exception : public exception { + virtual char const * msg() const { return "multi-precision fixed point (mpfx) overflow"; } + }; + + class div0_exception : public exception { + virtual char const * msg() const { return "multi-precision fixed point (mpfx) division by zero"; } + }; + + mpfx_manager(unsigned int_sz = 2, unsigned frac_sz = 1, unsigned initial_capacity = 1024); + ~mpfx_manager(); + + void round_to_plus_inf() { m_to_plus_inf = true; } + void round_to_minus_inf() { m_to_plus_inf = false; } + void set_rounding(bool to_plus_inf) { m_to_plus_inf = to_plus_inf; } + bool rounding_to_plus_inf() const { return m_to_plus_inf; } + + /** + \brief Return true if n is negative + */ + static bool sign(mpfx const & n) { return is_neg(n); } + + /** + \brief Set n to zero. + */ + void reset(mpfx & n); + + /** + \brief Return true if n is an integer. + */ + bool is_int(mpfx const & n) const; + + /** + \brief Return true if n is zero. + */ + static bool is_zero(mpfx const & n) { return n.m_sig_idx == 0; } + + /** + \brief Return true if n is positive. + */ + static bool is_pos(mpfx const & n) { return n.m_sign == 0 && !is_zero(n); } + + /** + \brief Return true if n is negative. + */ + static bool is_neg(mpfx const & n) { return n.m_sign != 0; } + + /** + \brief Return true if n is non positive. + */ + static bool is_nonpos(mpfx const & n) { return !is_pos(n); } + + /** + \brief Return true if n is non negative. + */ + static bool is_nonneg(mpfx const & n) { return !is_neg(n); } + + /** + \brief Return true if the absolute value of n is 1. + */ + bool is_abs_one(mpfx const & n) const; + + /** + \brief Return true if n is one. + */ + bool is_one(mpfx const & n) const { return is_pos(n) && is_abs_one(n); } + + /** + \brief Return true if n is minus one. + */ + bool is_minus_one(mpfx const & n) const { return is_neg(n) && is_abs_one(n); } + + /** + \brief Return true if \c a is an integer and fits in an int64 machine integer. + */ + bool is_int64(mpfx const & a) const; + + /** + \brief Return true if \c a is a non-negative integer and fits in an int64 machine integer. + */ + bool is_uint64(mpfx const & a) const; + + /** + \brief Delete the resources associated with n. + */ + void del(mpfx & n); + + /** + \brief a <- -a + */ + static void neg(mpfx & a) { if (!is_zero(a)) a.m_sign = !a.m_sign; } + + /** + \brief a <- |a| + */ + static void abs(mpfx & a) { a.m_sign = 0; } + + static void swap(mpfx & a, mpfx & b) { a.swap(b); } + + /** + \brief c <- a + b + */ + void add(mpfx const & a, mpfx const & b, mpfx & c); + + /** + \brief c <- a - b + */ + void sub(mpfx const & a, mpfx const & b, mpfx & c); + + /** + \brief a <- a + 1 + */ + void inc(mpfx & a) { add(a, m_one, a); } + + /** + \brief a <- a - 1 + */ + void dec(mpfx & a) { sub(a, m_one, a); } + + /** + \brief c <- a * b + */ + void mul(mpfx const & a, mpfx const & b, mpfx & c); + + /** + \brief c <- a / b + + \pre !is_zero(b) + */ + void div(mpfx const & a, mpfx const & b, mpfx & c); + + /** + \brief a <- 1/a + + \pre !is_zero(a); + */ + void inv(mpfx & a) { div(m_one, a, a); } + void inv(mpfx const & a, mpfx & b) { set(b, a); inv(b); } + + /** + \brief a <- a/2^k + */ + void div2k(mpfx & a, unsigned k); + + /** + \brief b <- a/2^k + */ + void div2k(mpfx const & a, unsigned k, mpfx & b) { set(b, a); div2k(b, k); } + + /** + \brief a <- a/2 + */ + void div2(mpfx & a) { div2k(a, 1); } + + /** + \brief b <- a/2 + */ + void div2(mpfx const & a, mpfx & b) { div2k(a, 1, b); } + + /** + \brief b <- a^k + */ + void power(mpfx const & a, unsigned k, mpfx & b); + + /** + \brief Return true if \c a is a power of 2. That is, a is equal to 2^k for some k >= 0. + */ + bool is_power_of_two(mpfx const & a, unsigned & k) const; + bool is_power_of_two(mpfx const & a) const; + + bool eq(mpfx const & a, mpfx const & b) const; + bool neq(mpfx const & a, mpfx const & b) const { return !eq(a, b); } + bool lt(mpfx const & a, mpfx const & b) const; + bool gt(mpfx const & a, mpfx const & b) const { return lt(b, a); } + bool le(mpfx const & a, mpfx const & b) const { return !lt(b, a); } + bool ge(mpfx const & a, mpfx const & b) const { return !lt(a, b); } + + void set(mpfx & n, int v); + void set(mpfx & n, unsigned v); + void set(mpfx & n, int64 v); + void set(mpfx & n, uint64 v); + void set(mpfx & n, int num, unsigned den); + void set(mpfx & n, int64 num, uint64 den); + void set(mpfx & n, mpfx const & v); + void set(mpfx & n, unsynch_mpz_manager & m, mpz const & v); + void set(mpfx & n, synch_mpz_manager & m, mpz const & v); + void set(mpfx & n, unsynch_mpq_manager & m, mpq const & v); + void set(mpfx & n, synch_mpq_manager & m, mpq const & v); + + /** + \brief Set n to the smallest representable numeral greater than zero. + */ + void set_plus_epsilon(mpfx & n); + + /** + \brief Set n to the greatest representable numeral less than zero. + */ + void set_minus_epsilon(mpfx & n); + + /** + \brief n <- floor(n) + */ + void floor(mpfx & n); + void floor(mpfx const & n, mpfx & o) { set(o, n); floor(o); } + + /** + \brief n <- ceil(n) + */ + void ceil(mpfx & n); + void ceil(mpfx const & n, mpfx & o) { set(o, n); ceil(o); } + + /** + \brief Return n as an int64. + + \pre is_int64(n) + */ + int64 get_int64(mpfx const & n) const; + + /** + \brief Return n as an uint64. + + \pre is_uint64(n) + */ + uint64 get_uint64(mpfx const & n) const; + + /** + \brief Convert n into a mpz numeral. + + \pre is_int(n) + */ + void to_mpz(mpfx const & n, unsynch_mpz_manager & m, mpz & t); + + /** + \brief Convert n into a mpz numeral. + + \pre is_int(n) + */ + void to_mpz(mpfx const & n, synch_mpz_manager & m, mpz & t); + + /** + \brief Convert n into a mpq numeral. + */ + void to_mpq(mpfx const & n, unsynch_mpq_manager & m, mpq & t); + + /** + \brief Convert n into a mpq numeral. + */ + void to_mpq(mpfx const & n, synch_mpq_manager & m, mpq & t); + + /** + \brief Return the biggest k s.t. 2^k <= a. + + \remark Return 0 if a is not positive. + */ + unsigned prev_power_of_two(mpfx const & a); + + void display(std::ostream & out, mpfx const & n) const; + void display_smt2(std::ostream & out, mpfx const & n) const; + void display_decimal(std::ostream & out, mpfx const & n, unsigned prec = UINT_MAX) const; + void display_raw(std::ostream & out, mpfx const & n) const; + + std::string to_string(mpfx const & a) const; + std::string to_rational_string(mpfx const & a) const; + + bool check(mpfx const & a) const; +}; + +typedef _scoped_numeral scoped_mpfx; +typedef _scoped_numeral_vector scoped_mpfx_vector; + +#endif diff --git a/lib/mpn.cpp b/lib/mpn.cpp new file mode 100644 index 000000000..2cc35776f --- /dev/null +++ b/lib/mpn.cpp @@ -0,0 +1,439 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + mpn.cpp + +Abstract: + + Multi Precision Natural Numbers + +Author: + + Christoph Wintersteiger (cwinter) 2011-11-16. + +Revision History: + +--*/ +#include"debug.h" +#include"trace.h" +#include"buffer.h" +#include"mpn.h" + +#define max(a,b) (((a) > (b)) ? (a) : (b)) + +typedef uint64 mpn_double_digit; +COMPILE_TIME_ASSERT(sizeof(mpn_double_digit) == 2 * sizeof(mpn_digit)); + +mpn_manager static_mpn_manager; + +const mpn_digit mpn_manager::zero = 0; + +mpn_manager::mpn_manager() { +#ifdef _DEBUG + trace_enabled=true; +#endif +} + +mpn_manager::~mpn_manager() { +} + +int mpn_manager::compare(mpn_digit const * a, size_t const lnga, + mpn_digit const * b, size_t const lngb) const { + int res = 0; + + #ifdef _DEBUG + if (trace_enabled) + STRACE("mpn", tout << "[mpn] "; ); + #endif + + trace(a, lnga); + + size_t j = max(lnga, lngb) - 1; + for (; j != (size_t)-1 && res == 0; j--) { + mpn_digit const & u_j = (j < lnga) ? a[j] : zero; + mpn_digit const & v_j = (j < lngb) ? b[j] : zero; + if (u_j > v_j) + res = 1; + else if (u_j < v_j) + res = -1; + } + + #ifdef _DEBUG + if (trace_enabled) + STRACE("mpn", tout << ((res == 1) ? " > " : (res == -1) ? " < " : " == "); ); + #endif + + trace_nl(b, lngb); + return res; +} + +bool mpn_manager::add(mpn_digit const * a, size_t const lnga, + mpn_digit const * b, size_t const lngb, + mpn_digit * c, size_t const lngc_alloc, + size_t * plngc) const { + trace(a, lnga, b, lngb, "+"); + // Essentially Knuth's Algorithm A + size_t len = max(lnga, lngb); + SASSERT(lngc_alloc == len+1 && len > 0); + mpn_digit k = 0; + mpn_digit r; + bool c1, c2; + for (size_t j = 0; j < len; j++) { + mpn_digit const & u_j = (j < lnga) ? a[j] : zero; + mpn_digit const & v_j = (j < lngb) ? b[j] : zero; + r = u_j + v_j; c1 = r < u_j; + c[j] = r + k; c2 = c[j] < r; + k = c1 | c2; + } + c[len] = k; + size_t &os = *plngc; + for (os = len+1; os > 1 && c[os-1] == 0; ) os--; + SASSERT(os > 0 && os <= len+1); + trace_nl(c, os); + return true; // return k != 0? What would MSBignum return? +} + +bool mpn_manager::sub(mpn_digit const * a, size_t const lnga, + mpn_digit const * b, size_t const lngb, + mpn_digit * c, mpn_digit * pborrow) const { + trace(a, lnga, b, lngb, "-"); + // Essentially Knuth's Algorithm S + size_t len = max(lnga, lngb); + mpn_digit & k = *pborrow; k = 0; + mpn_digit r; + bool c1, c2; + for (size_t j = 0; j < len; j++) { + mpn_digit const & u_j = (j < lnga) ? a[j] : zero; + mpn_digit const & v_j = (j < lngb) ? b[j] : zero; + r = u_j - v_j; c1 = r > u_j; + c[j] = r - k; c2 = c[j] > r; + k = c1 | c2; + } + trace_nl(c, lnga); + return true; // return k != 0? +} + +bool mpn_manager::mul(mpn_digit const * a, size_t const lnga, + mpn_digit const * b, size_t const lngb, + mpn_digit * c) const { + trace(a, lnga, b, lngb, "*"); + // Essentially Knuth's Algorithm M. + // Perhaps implement a more efficient version, see e.g., Knuth, Section 4.3.3. + size_t i; + mpn_digit k; + +#define DIGIT_BITS (sizeof(mpn_digit)*8) +#define HALF_BITS (sizeof(mpn_digit)*4) + + for (unsigned i = 0; i < lnga; i++) + c[i] = 0; + + for (size_t j = 0; j < lngb; j++) { + mpn_digit const & v_j = b[j]; + if (v_j == 0) { // This branch may be omitted according to Knuth. + c[j+lnga] = 0; + } + else { + k = 0; + for (i = 0; i < lnga; i++) { + mpn_digit const & u_i = a[i]; + mpn_double_digit t; + t = ((mpn_double_digit)u_i * (mpn_double_digit)v_j) + + (mpn_double_digit) c[i+j] + + (mpn_double_digit) k; + + c[i+j] = (t << DIGIT_BITS) >> DIGIT_BITS; + k = t >> DIGIT_BITS; + } + c[j+lnga] = k; + } + } + + trace_nl(c, lnga+lngb); + return true; +} + +#define MASK_FIRST (~((mpn_digit)(-1) >> 1)) +#define FIRST_BITS(N, X) ((X) >> (DIGIT_BITS-(N))) +#define LAST_BITS(N, X) (((X) << (DIGIT_BITS-(N))) >> (DIGIT_BITS-(N))) +#define BASE ((mpn_double_digit)0x01 << DIGIT_BITS) + +bool mpn_manager::div(mpn_digit const * numer, size_t const lnum, + mpn_digit const * denom, size_t const lden, + mpn_digit * quot, + mpn_digit * rem) { + trace(numer, lnum, denom, lden, "/"); + bool res = false; + + if (lnum < lden) { + for (size_t i = 0; i < (lnum-lden+1); i++) + quot[i] = 0; + for (size_t i = 0; i < lden; i++) + rem[i] = (i < lnum) ? numer[i] : 0; + return false; + } + + bool all_zero = true; + for (size_t i = 0; i < lden && all_zero; i++) + if (denom[i] != zero) all_zero = false; + + if (all_zero) { + // Division by 0. What would the MSBignum divide function do? + UNREACHABLE(); + return res; + } + + SASSERT(denom[lden-1] != 0); + + if (lnum == 1 && lden == 1) { + *quot = numer[0] / denom[0]; + *rem = numer[0] % denom[0]; + } + else if (lnum < lden || (lnum == lden && numer[lnum-1] < denom[lden-1])) { + *quot = 0; + for (size_t i = 0; i < lden; i++) + rem[i] = (i < lnum) ? numer[i] : 0; + } + else { + size_t d = div_normalize(numer, lnum, denom, lden, u, v); + if (lden == 1) + res = div_1(u, v[0], quot); + else + res = div_n(u, v, quot, rem); + div_unnormalize(u, v, d, rem); + } + + // STRACE("mpn_dbg", display_raw(tout, quot, lnum - lden + 1); tout << ", "; + // display_raw(tout, rem, lden); tout << std::endl; ); + trace_nl(quot, lnum-lden+1); + + trace(numer, lnum, denom, lden, "%"); + trace_nl(rem, lden); + +#ifdef _DEBUG + mpn_sbuffer temp(lnum+1, 0); + mul(quot, lnum-lden+1, denom, lden, temp.c_ptr()); + size_t real_size; + add(temp.c_ptr(), lnum, rem, lden, temp.c_ptr(), lnum+1, &real_size); + bool ok = true; + for (size_t i = 0; i < lnum && ok; i++) + if (temp[i] != numer[i]) ok = false; + if (temp[lnum] != 0) ok = false; + CTRACE("mpn_dbg", !ok, tout << "DIV BUG: quot * denom + rem = "; display_raw(tout, temp.c_ptr(), lnum+1); tout << std::endl; ); + SASSERT(ok); +#endif + + return res; +} + +size_t mpn_manager::div_normalize(mpn_digit const * numer, size_t const lnum, + mpn_digit const * denom, size_t const lden, + mpn_sbuffer & n_numer, + mpn_sbuffer & n_denom) const +{ + size_t d = 0; + while (((denom[lden-1] << d) & MASK_FIRST) == 0) d++; + SASSERT(d < DIGIT_BITS); + + n_numer.resize(lnum+1); + n_denom.resize(lden); + + if (d == 0) { + n_numer[lnum] = 0; + for (size_t i = 0; i < lnum; i++) + n_numer[i] = numer[i]; + for (size_t i = 0; i < lden; i++) + n_denom[i] = denom[i]; + } + else { + mpn_digit q = FIRST_BITS(d, numer[lnum-1]); + n_numer[lnum] = q; + for (size_t i = lnum-1; i > 0; i--) + n_numer[i] = (numer[i] << d) | FIRST_BITS(d, numer[i-1]); + n_numer[0] = numer[0] << d; + for (size_t i = lden-1; i > 0; i--) + n_denom[i] = denom[i] << d | FIRST_BITS(d, denom[i-1]); + n_denom[0] = denom[0] << d; + } + + STRACE("mpn_norm", tout << "Normalized: n_numer="; display_raw(tout, n_numer.c_ptr(), n_numer.size()); + tout << " n_denom="; display_raw(tout, n_denom.c_ptr(), n_denom.size()); tout << std::endl; ); + + return d; +} + +void mpn_manager::div_unnormalize(mpn_sbuffer & numer, mpn_sbuffer & denom, + size_t const d, mpn_digit * rem) const { + if (d == 0) { + for (size_t i = 0; i < denom.size(); i++) + rem[i] = numer[i]; + } + else { + for (size_t i = 0; i < denom.size()-1; i++) + rem[i] = numer[i] >> d | (LAST_BITS(d, numer[i+1]) << (DIGIT_BITS-d)); + rem[denom.size()-1] = numer[denom.size()-1] >> d; + } +} + +bool mpn_manager::div_1(mpn_sbuffer & numer, mpn_digit const denom, + mpn_digit * quot) const +{ + mpn_double_digit q_hat, temp, r_hat, ms; + mpn_digit borrow; + + for (size_t j = numer.size()-1; j > 0; j--) { + temp = (((mpn_double_digit)numer[j]) << DIGIT_BITS) | ((mpn_double_digit)numer[j-1]); + q_hat = temp / (mpn_double_digit) denom; + r_hat = temp % (mpn_double_digit) denom; + if (q_hat >= BASE) { + UNREACHABLE(); // is this reachable with normalized v? + } + SASSERT(q_hat < BASE); + ms = temp - (q_hat * (mpn_double_digit) denom); + borrow = ms > temp; + numer[j-1] = (mpn_digit) ms; + numer[j] = ms >> DIGIT_BITS; + quot[j-1] = (mpn_digit) q_hat; + if (borrow) { + quot[j-1]--; + numer[j] = numer[j-1] + denom; + } + STRACE("mpn_div1", tout << "j=" << j << " q_hat=" << q_hat << " r_hat=" << r_hat; + tout << " ms=" << ms; + tout << " new numer="; display_raw(tout, numer.c_ptr(), numer.size()); + tout << " borrow=" << borrow; + tout << std::endl; ); + } + + return true; // return rem != 0 or something like that? +} + +bool mpn_manager::div_n(mpn_sbuffer & numer, mpn_sbuffer const & denom, + mpn_digit * quot, mpn_digit * rem) { + SASSERT(denom.size() > 1); + + // This is essentially Knuth's Algorithm D. + size_t m = numer.size() - denom.size(); + size_t n = denom.size(); + + SASSERT(numer.size() == m+n); + + t_ms.resize(n+1); + + mpn_double_digit q_hat, temp, r_hat; + mpn_digit borrow; + + for (size_t j = m-1; j != (size_t)-1; j--) { + temp = (((mpn_double_digit)numer[j+n]) << DIGIT_BITS) | ((mpn_double_digit)numer[j+n-1]); + q_hat = temp / (mpn_double_digit) denom[n-1]; + r_hat = temp % (mpn_double_digit) denom[n-1]; + recheck: + if (q_hat >= BASE || + ((q_hat * denom[n-2]) > ((r_hat << DIGIT_BITS) + numer[j+n-2]))) { + q_hat--; + r_hat += denom[n-1]; + if (r_hat < BASE) goto recheck; + } + SASSERT(q_hat < BASE); + // Replace numer[j+n]...numer[j] with + // numer[j+n]...numer[j] - q * (denom[n-1]...denom[0]) + mpn_digit q_hat_small = (mpn_digit)q_hat; + #ifdef _DEBUG + trace_enabled = false; + #endif + mul(&q_hat_small, 1, denom.c_ptr(), n, t_ms.c_ptr()); + sub(&numer[j], n+1, t_ms.c_ptr(), n+1, &numer[j], &borrow); + quot[j] = q_hat_small; + if (borrow) { + quot[j]--; + t_ab.resize(n+2); + size_t real_size; + add(denom.c_ptr(), n, &numer[j], n+1, t_ab.c_ptr(), n+2, &real_size); + for (size_t i = 0; i < n+1; i++) + numer[j+i] = t_ab[i]; + } + #ifdef _DEBUG + trace_enabled = true; + #endif + STRACE("mpn_div", tout << "q_hat=" << q_hat << " r_hat=" << r_hat; + tout << " t_ms="; display_raw(tout, t_ms.c_ptr(), n); + tout << " new numer="; display_raw(tout, numer.c_ptr(), m+n+1); + tout << " borrow=" << borrow; + tout << std::endl; ); + } + + return true; // return rem != 0 or something like that? +} + +char * mpn_manager::to_string(mpn_digit const * a, size_t const lng, char * buf, size_t const lbuf) const { + SASSERT(buf && lbuf > 0); + STRACE("mpn_to_string", tout << "[mpn] to_string "; display_raw(tout, a, lng); tout << " == "; ); + + if (lng == 1) { +#ifdef _WINDOWS + sprintf_s(buf, lbuf, "%u", *a); +#else + snprintf(buf, lbuf, "%u", *a); +#endif + } + else { + mpn_sbuffer temp(lng, 0), t_numer(lng+1, 0), t_denom(1, 0); + for (unsigned i = 0; i < lng; i++) + temp[i] = a[i]; + + size_t j = 0; + mpn_digit rem; + mpn_digit ten = 10; + while (!temp.empty() && (temp.size() > 1 || temp[0] != 0)) { + size_t d = div_normalize(&temp[0], temp.size(), &ten, 1, t_numer, t_denom); + div_1(t_numer, t_denom[0], &temp[0]); + div_unnormalize(t_numer, t_denom, d, &rem); + buf[j++] = '0' + rem; + while (temp.size() > 0 && temp.back() == 0) + temp.pop_back(); + } + buf[j] = 0; + + j--; + size_t mid = (j/2) + ((j % 2) ? 1 : 0); + for (size_t i = 0; i < mid; i++) + std::swap(buf[i], buf[j-i]); + } + + STRACE("mpn_to_string", tout << buf << std::endl; ); + + return buf; +} + +void mpn_manager::display_raw(std::ostream & out, mpn_digit const * a, size_t const lng) const { + out << "["; + for (size_t i = lng-1; i != (size_t)-1; i-- ) { out << a[i]; if (i != 0) out << "|"; } + out << "]"; +} + +void mpn_manager::trace(mpn_digit const * a, size_t const lnga, + mpn_digit const * b, size_t const lngb, + const char * op) const { +#ifdef _DEBUG + if (trace_enabled) + STRACE("mpn", tout << "[mpn] " << to_string(a, lnga, char_buf, sizeof(char_buf)); + tout << " " << op << " " << to_string(b, lngb, char_buf, sizeof(char_buf)); + tout << " == "; ); +#endif +} + +void mpn_manager::trace(mpn_digit const * a, size_t const lnga) const { +#ifdef _DEBUG + if (trace_enabled) + STRACE("mpn", tout << to_string(a, lnga, char_buf, sizeof(char_buf)); ); +#endif +} + +void mpn_manager::trace_nl(mpn_digit const * a, size_t const lnga) const { +#ifdef _DEBUG + if (trace_enabled) + STRACE("mpn", tout << to_string(a, lnga, char_buf, sizeof(char_buf)) << std::endl; ); +#endif +} diff --git a/lib/mpn.h b/lib/mpn.h new file mode 100644 index 000000000..f995004c2 --- /dev/null +++ b/lib/mpn.h @@ -0,0 +1,196 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + mpn.h + +Abstract: + + Multi Precision Natural Numbers + +Author: + + Christoph Wintersteiger (cwinter) 2011-11-16. + +Revision History: + +--*/ +#ifndef _MPN_H_ +#define _MPN_H_ + +#include +#include"util.h" +#include"buffer.h" + +// we supply a definition of a basic max because mpz relies on it. +#define max(a,b) (((a) > (b)) ? (a) : (b)) + +typedef unsigned int mpn_digit; + +class mpn_manager { +public: + mpn_manager(); + ~mpn_manager(); + + int compare(mpn_digit const * a, size_t const lnga, + mpn_digit const * b, size_t const lngb) const; + + bool add(mpn_digit const * a, size_t const lnga, + mpn_digit const * b, size_t const lngb, + mpn_digit *c, size_t const lngc_alloc, + size_t * plngc) const; + + bool sub(mpn_digit const * a, size_t const lnga, + mpn_digit const * b, size_t const lngb, + mpn_digit * c, mpn_digit * pborrow) const; + + bool mul(mpn_digit const * a, size_t const lnga, + mpn_digit const * b, size_t const lngb, + mpn_digit * c) const; + + bool div(mpn_digit const * numer, size_t const lnum, + mpn_digit const * denom, size_t const lden, + mpn_digit * quot, + mpn_digit * rem); + + char * to_string(mpn_digit const * a, size_t const lng, + char * buf, size_t const lbuf) const; + +private: + #ifdef _AMD64_ + class mpn_sbuffer : public sbuffer { + public: + mpn_sbuffer() : sbuffer() {} + + mpn_sbuffer(size_t nsz, const mpn_digit & elem = 0) : + sbuffer(static_cast(nsz), elem) + { + } + void resize(size_t nsz, const mpn_digit & elem = 0) { + sbuffer::resize(static_cast(nsz), elem); + } + + mpn_digit & operator[](size_t idx) { + return sbuffer::operator[](static_cast(idx)); + } + + const mpn_digit & operator[](size_t idx) const { + return sbuffer::operator[](static_cast(idx)); + } + }; + #else + typedef sbuffer mpn_sbuffer; + #endif + + static const mpn_digit zero; + mpn_sbuffer u, v, t_ms, t_ab; + void display_raw(std::ostream & out, mpn_digit const * a, size_t const lng) const; + + size_t div_normalize(mpn_digit const * numer, size_t const lnum, + mpn_digit const * denom, size_t const lden, + mpn_sbuffer & n_numer, + mpn_sbuffer & n_denom) const; + + void div_unnormalize(mpn_sbuffer & numer, mpn_sbuffer & denom, + size_t const d, mpn_digit * rem) const; + + bool div_1(mpn_sbuffer & numer, mpn_digit const denom, + mpn_digit * quot) const; + + bool div_n(mpn_sbuffer & numer, mpn_sbuffer const & denom, + mpn_digit * quot, mpn_digit * rem); + + #ifdef _DEBUG + mutable char char_buf[4096]; + bool trace_enabled; + #endif + + void trace(mpn_digit const * a, size_t const lnga, + mpn_digit const * b, size_t const lngb, + const char * op) const; + + void trace(mpn_digit const * a, size_t const lnga) const; + void trace_nl(mpn_digit const * a, size_t const lnga) const; + +public: + // This function is needed because of the static_mpn_manager global variable. + // It must be invoked by the memory_manager during finalization. + // After we remove MSBignum from the code base, the global variable will + // not be needed anymore, and we will be able to eliminate this function. + void finalize() { + u.finalize(); + v.finalize(); + t_ms.finalize(); + t_ab.finalize(); + } +}; + + +// MSBignum compatible interface +// Note: The `owner' parameter is ignored. We use separate mpn_manager objects for the +// same purpose. Multiple owners are not supported in these compatibility functions, +// instead a static mpn_manager is used. + +extern mpn_manager static_mpn_manager; + +typedef unsigned int digit_t; + +typedef struct { + mpn_digit multiplier; + size_t shiftamt; +} reciprocal_1_t; + +#define reciprocal_1_NULL ((reciprocal_1_t*)0) + +inline int compare_diff(digit_t const * a, size_t const lnga, + digit_t const * b, size_t const lngb) +{ + return static_mpn_manager.compare(a, lnga, b, lngb); +} + +inline char * mp_decimal(digit_t const * a, size_t const lng, // Number to be converted and its length + char * buf, size_t const lbuf, // output buffer and its length + int owner) +{ + return static_mpn_manager.to_string(a, lng, buf, lbuf); +} + +inline bool add_full(digit_t const * a, size_t const lnga, + digit_t const * b, size_t const lngb, + digit_t *c, size_t const lngc_alloc, + size_t * plngc, + int owner) +{ + return static_mpn_manager.add(a, lnga, b, lngb, c, lngc_alloc, plngc); +} + +inline bool sub_diff(digit_t const * a, size_t const lnga, + digit_t const * b, size_t const lngb, + digit_t * c, digit_t * pborrow, + int owner) +{ + return static_mpn_manager.sub(a, lnga, b, lngb, c, pborrow); +} + +inline bool multiply(digit_t const * a, size_t const lnga, + digit_t const * b, size_t const lngb, + digit_t * c, + int owner) +{ + return static_mpn_manager.mul(a, lnga, b, lngb, c); +} + +inline bool divide(digit_t const * numer, size_t const lnum, + digit_t const * denom, size_t const lden, + reciprocal_1_t const * supplied_reciprocal, /* reciprocal_t struct for this denominator, + or reciprocal_1_NULL + if not previously precomputed */ + digit_t * quot, /* Quotient -- length MAX(lnum - lden + 1, 0) */ + digit_t * rem, /* Remainder -- length lden */ + int owner) +{ + return static_mpn_manager.div(numer, lnum, denom, lden, quot, rem); +} + +#endif diff --git a/lib/mpq.cpp b/lib/mpq.cpp new file mode 100644 index 000000000..eda937029 --- /dev/null +++ b/lib/mpq.cpp @@ -0,0 +1,317 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + mpq.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-06-21. + +Revision History: + +--*/ +#include"mpq.h" +#include"warning.h" +#include"z3_exception.h" + +template +mpq_manager::mpq_manager() { +} + +template +mpq_manager::~mpq_manager() { + del(m_n_tmp); + del(m_add_tmp1); + del(m_add_tmp2); + del(m_lt_tmp1); + del(m_lt_tmp2); + del(m_addmul_tmp); +} + + +template +bool mpq_manager::rat_lt(mpq const & a, mpq const & b) { + mpz const & na = a.numerator(); + mpz const & nb = b.numerator(); + int sign_a = this->sign(na); + int sign_b = this->sign(nb); + if (sign_a < 0) { + if (sign_b >= 0) return true; + } + else if (sign_a == 0) { + if (sign_b > 0) return true; + SASSERT(sign_b <= 0); return false; + } + else { + SASSERT(sign_a > 0); + if (sign_b <= 0) return false; + } + SASSERT((sign_a > 0 && sign_b > 0) || + (sign_a < 0 && sign_b < 0)); + mpz const & da = a.denominator(); + mpz const & db = b.denominator(); + + if (SYNCH) { + mpq tmp1; + mpq tmp2; + mul(na, db, tmp1); + mul(nb, da, tmp2); + bool r = lt(tmp1, tmp2); + del(tmp1); + del(tmp2); + return r; + } + else { + mul(na, db, m_lt_tmp1); + mul(nb, da, m_lt_tmp2); + return lt(m_lt_tmp1, m_lt_tmp2); + } +} + +template +void mpq_manager::floor(mpq const & a, mpz & f) { + if (is_int(a)) { + set(f, a.m_num); + return; + } + bool is_neg_num = is_neg(a.m_num); + machine_div(a.m_num, a.m_den, f); + if (is_neg_num) + sub(f, this->mk_z(1), f); +} + +template +void mpq_manager::ceil(mpq const & a, mpz & c) { + if (is_int(a)) { + set(c, a.m_num); + return; + } + bool is_pos_num = is_pos(a.m_num); + machine_div(a.m_num, a.m_den, c); + if (is_pos_num) + add(c, this->mk_z(1), c); +} + +template +void mpq_manager::gcd(unsigned sz, mpq const * as, mpq & g) { + switch (sz) { + case 0: + reset(g); + return; + case 1: + set(g, as[0]); + abs(g); + return; + default: + break; + } + gcd(as[0], as[1], g); + for (unsigned i = 2; i < sz; i++) { + if (is_one(g)) + return; + gcd(g, as[i], g); + } +} + +template +std::string mpq_manager::to_string(mpq const & a) const { + if (is_int(a)) + return to_string(a.m_num); + return to_string(a.m_num) + "/" + to_string(a.m_den); +} + +template +void mpq_manager::display(std::ostream & out, mpq const & a) const { + if (is_int(a)) { + display(out, a.m_num); + } + else { + display(out, a.m_num); + out << "/"; + display(out, a.m_den); + } +} + +template +void mpq_manager::display_smt2(std::ostream & out, mpq const & a, bool decimal) const { + if (is_int(a)) { + display_smt2(out, a.m_num, decimal); + } + else { + out << "(/ "; + display_smt2(out, a.m_num, decimal); + out << " "; + display_smt2(out, a.m_den, decimal); + out << ")"; + } +} + +template +void mpq_manager::display_decimal(std::ostream & out, mpq const & a, unsigned prec) { + mpz n1, d1, v1; + get_numerator(a, n1); + get_denominator(a, d1); + if (is_neg(a)) { + out << "-"; + neg(n1); + } + mpz ten(10); + div(n1, d1, v1); + display(out, v1); + rem(n1, d1, n1); + if (is_zero(n1)) + goto end; // number is an integer + out << "."; + for (unsigned i = 0; i < prec; i++) { + mul(n1, ten, n1); + div(n1, d1, v1); + SASSERT(lt(v1, ten)); + display(out, v1); + rem(n1, d1, n1); + if (is_zero(n1)) + goto end; // number is precise + } + out << "?"; + end: + del(ten); del(n1); del(d1); del(v1); +} + +template +void mpq_manager::set(mpq & a, char const * val) { + reset(a.m_num); + mpz ten(10); + _scoped_numeral > tmp(*this); + char const * str = val; + bool sign = false; + while (str[0] == ' ') ++str; + if (str[0] == '-') + sign = true; + while (str[0] && (str[0] != '/') && (str[0] != '.') && (str[0] != 'e') && (str[0] != 'E')) { + if ('0' <= str[0] && str[0] <= '9') { + SASSERT(str[0] - '0' <= 9); + mul(a.m_num, ten, tmp); + add(tmp, this->mk_z(str[0] - '0'), a.m_num); + } + ++str; + } + TRACE("mpq_set", tout << "[before] a: " << to_string(a) << "\n";); + if (str[0] == '/' || str[0] == '.' || str[0] == 'e' || str[0] == 'E') { + bool is_rat = str[0] == '/'; + _scoped_numeral > tmp2(*this); + set(tmp2, 1); + bool has_den = false; + if (str[0] == '/' || str[0] == '.') { + has_den = true; + ++str; + reset(a.m_den); + while (str[0] && (str[0] != 'e') && (str[0] != 'E')) { + if ('0' <= str[0] && str[0] <= '9') { + mul(a.m_den, ten, tmp); + add(tmp, this->mk_z(str[0] - '0'), a.m_den); + if (!is_rat) + mul(tmp2, ten, tmp2); + } + ++str; + } + } + unsigned long long exp = 0; + bool exp_sign = false; + if (str[0] == 'e' || str[0] == 'E') { + if (is_rat) + throw default_exception("mixing rational/scientific notation"); + ++str; + if (str[0] == '-') { + exp_sign = true; + ++str; + } + while (str[0]) { + if ('0' <= str[0] && str[0] <= '9') { + SASSERT(str[0] - '0' <= 9); + exp = (10*exp) + (str[0] - '0'); + } + TRACE("mpq_set", tout << "[exp]: " << exp << ", str[0]: " << (str[0] - '0') << std::endl;); + ++str; + } + } + if (!is_rat) { + // a <- a.m_num + a.m_den/tmp2 + if (exp > static_cast(UINT_MAX)) + throw default_exception("exponent is too big"); + _scoped_numeral > b(*this); + if (has_den) { + set(b, a.m_den, tmp2); + set(a.m_den, 1); + add(a, b, a); + } + if (exp > 0) { + _scoped_numeral > _exp(*this); + _scoped_numeral > _ten(*this); + set(_ten, 10); + power(_ten, static_cast(exp), _exp); + TRACE("mpq_set", tout << "a: " << to_string(a) << ", exp_sign:" << exp_sign << ", exp: " << exp << " " << to_string(_exp) << std::endl;); + if (exp_sign) + div(a, _exp, a); + else + mul(a, _exp, a); + } + } + else { + // rational case + if (is_zero(a.m_den)) + throw default_exception("division by zero"); + } + } + else { + reset_denominator(a); + } + if (sign) + neg(a.m_num); + normalize(a); +} + +template +void mpq_manager::power(mpq const & a, unsigned p, mpq & b) { + unsigned mask = 1; + mpq power; + set(power, a); + set(b, 1); + while (mask <= p) { + if (mask & p) + mul(b, power, b); + mul(power, power, power); + mask = mask << 1; + } + del(power); +} + +template +double mpq_manager::get_double(mpq const & a) const { + double n; + double d; + n = get_double(a.m_num); + d = get_double(a.m_den); + SASSERT(d > 0.0); + return n/d; +} + +template +bool mpq_manager::root(mpq const & a, unsigned n, mpq & r) { + return root(a.m_num, n, r.m_num) && root(a.m_den, n, r.m_den); +} + +template +unsigned mpq_manager::prev_power_of_two(mpq const & a) { + _scoped_numeral > _tmp(*this); + floor(a, _tmp); + return prev_power_of_two(_tmp); +} + +template class mpq_manager; +template class mpq_manager; + diff --git a/lib/mpq.h b/lib/mpq.h new file mode 100644 index 000000000..26a34aff6 --- /dev/null +++ b/lib/mpq.h @@ -0,0 +1,848 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + mpq.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-06-21. + +Revision History: + +--*/ +#ifndef _MPQ_H_ +#define _MPQ_H_ + +#include"mpz.h" +#include"trace.h" + +class mpq { + mpz m_num; + mpz m_den; + friend class mpq_manager; + friend class mpq_manager; + mpq & operator=(mpq const & other) { UNREACHABLE(); return *this; } +public: + mpq(int v):m_num(v), m_den(1) {} + mpq():m_den(1) {} + void swap(mpq & other) { m_num.swap(other.m_num); m_den.swap(other.m_den); } + mpz const & numerator() const { return m_num; } + mpz const & denominator() const { return m_den; } +}; + +inline void swap(mpq & m1, mpq & m2) { m1.swap(m2); } + +template +class mpq_manager : public mpz_manager { + mpz m_n_tmp; + mpz m_add_tmp1; + mpz m_add_tmp2; + mpq m_addmul_tmp; + mpq m_lt_tmp1; + mpq m_lt_tmp2; + + void reset_denominator(mpq & a) { + del(a.m_den); + a.m_den.m_val = 1; + } + + void normalize(mpq & a) { + if (SYNCH) { + mpz tmp; + gcd(a.m_num, a.m_den, tmp); + if (is_one(tmp)) { + del(tmp); + return; + } + div(a.m_num, tmp, a.m_num); + div(a.m_den, tmp, a.m_den); + del(tmp); + } + else { + gcd(a.m_num, a.m_den, m_n_tmp); + if (is_one(m_n_tmp)) + return; + div(a.m_num, m_n_tmp, a.m_num); + div(a.m_den, m_n_tmp, a.m_den); + } + } + + void rat_add(mpq const & a, mpq const & b, mpq & c) { + STRACE("rat_mpq", tout << "[mpq] " << to_string(a) << " + " << to_string(b) << " == ";); + if (SYNCH) { + mpz tmp1, tmp2; + mul(a.m_num, b.m_den, tmp1); + mul(b.m_num, a.m_den, tmp2); + mul(a.m_den, b.m_den, c.m_den); + add(tmp1, tmp2, c.m_num); + normalize(c); + del(tmp1); + del(tmp2); + } + else { + mul(a.m_num, b.m_den, m_add_tmp1); + mul(b.m_num, a.m_den, m_add_tmp2); + mul(a.m_den, b.m_den, c.m_den); + add(m_add_tmp1, m_add_tmp2, c.m_num); + normalize(c); + } + STRACE("rat_mpq", tout << to_string(c) << "\n";); + } + + void rat_add(mpq const & a, mpz const & b, mpq & c) { + STRACE("rat_mpq", tout << "[mpq] " << to_string(a) << " + " << to_string(b) << " == ";); + if (SYNCH) { + mpz tmp1; + mul(b, a.m_den, tmp1); + set(c.m_den, a.m_den); + add(a.m_num, tmp1, c.m_num); + normalize(c); + del(tmp1); + } + else { + mul(b, a.m_den, m_add_tmp1); + set(c.m_den, a.m_den); + add(a.m_num, m_add_tmp1, c.m_num); + normalize(c); + } + STRACE("rat_mpq", tout << to_string(c) << "\n";); + } + + void rat_sub(mpq const & a, mpq const & b, mpq & c) { + STRACE("rat_mpq", tout << "[mpq] " << to_string(a) << " - " << to_string(b) << " == ";); + if (SYNCH) { + mpz tmp1, tmp2; + mul(a.m_num, b.m_den, tmp1); + mul(b.m_num, a.m_den, tmp2); + mul(a.m_den, b.m_den, c.m_den); + sub(tmp1, tmp2, c.m_num); + normalize(c); + del(tmp1); + del(tmp2); + } + else { + mul(a.m_num, b.m_den, m_add_tmp1); + mul(b.m_num, a.m_den, m_add_tmp2); + mul(a.m_den, b.m_den, c.m_den); + sub(m_add_tmp1, m_add_tmp2, c.m_num); + normalize(c); + } + STRACE("rat_mpq", tout << to_string(c) << "\n";); + } + + void rat_mul(mpq const & a, mpq const & b, mpq & c) { + STRACE("rat_mpq", tout << "[mpq] " << to_string(a) << " * " << to_string(b) << " == ";); + mul(a.m_num, b.m_num, c.m_num); + mul(a.m_den, b.m_den, c.m_den); + normalize(c); + STRACE("rat_mpq", tout << to_string(c) << "\n";); + } + + void rat_mul(mpz const & a, mpq const & b, mpq & c) { + STRACE("rat_mpq", tout << "[mpq] " << to_string(a) << " * " << to_string(b) << " == ";); + mul(a, b.m_num, c.m_num); + set(c.m_den, b.m_den); + normalize(c); + STRACE("rat_mpq", tout << to_string(c) << "\n";); + } + + bool rat_lt(mpq const & a, mpq const & b); + +public: + typedef mpq numeral; + typedef mpq rational; + typedef mpz integer; + static bool precise() { return true; } + static bool field() { return true; } + + mpq_manager(); + + ~mpq_manager(); + + void reset(mpz & a) { mpz_manager::reset(a); } + + void reset(mpq & a) { + reset(a.m_num); + reset_denominator(a); + } + + static bool is_small(mpz const & a) { return mpz_manager::is_small(a); } + + static bool is_small(mpq const & a) { return is_small(a.m_num) && is_small(a.m_den); } + + static mpq mk_q(int v) { return mpq(v); } + + mpq mk_q(int n, int d) { mpq r; set(r, n, d); return r; } + + void del(mpz & a) { mpz_manager::del(a); } + + void del(mpq & a) { + del(a.m_num); + del(a.m_den); + } + + void get_numerator(mpq const & a, mpz & n) { set(n, a.m_num); } + + void get_denominator(mpq const & a, mpz & d) { set(d, a.m_den); } + + void get_numerator(mpq const & a, mpq & n) { get_numerator(a, n.m_num); reset_denominator(n); } + + void get_denominator(mpq const & a, mpq & d) { get_denominator(a, d.m_num); reset_denominator(d); } + + void neg(mpz & a) { mpz_manager::neg(a); } + + void neg(mpq & a) { mpz_manager::neg(a.m_num); } + + void abs(mpz & a) { mpz_manager::abs(a); } + + void abs(mpq & a) { mpz_manager::abs(a.m_num); } + + static int sign(mpz const & a) { return mpz_manager::sign(a); } + + static int sign(mpq const & a) { return mpz_manager::sign(a.m_num); } + + static bool is_pos(mpz const & a) { return mpz_manager::is_pos(a); } + + static bool is_neg(mpz const & a) { return mpz_manager::is_neg(a); } + + static bool is_zero(mpz const & a) { return mpz_manager::is_zero(a); } + + static bool is_nonpos(mpz const & a) { return mpz_manager::is_nonpos(a); } + + static bool is_nonneg(mpz const & a) { return mpz_manager::is_nonneg(a); } + + static bool is_pos(mpq const & a) { return is_pos(a.m_num); } + + static bool is_neg(mpq const & a) { return is_neg(a.m_num); } + + static bool is_zero(mpq const & a) { return is_zero(a.m_num); } + + static bool is_nonpos(mpq const & a) { return is_nonpos(a.m_num); } + + static bool is_nonneg(mpq const & a) { return is_nonneg(a.m_num); } + + static bool is_one(mpz const & a) { return mpz_manager::is_one(a); } + + static bool is_one(mpq const & a) { return is_one(a.m_num) && is_one(a.m_den); } + + static bool is_minus_one(mpz const & a) { return mpz_manager::is_minus_one(a); } + + static bool is_minus_one(mpq const & a) { return is_minus_one(a.m_num) && is_one(a.m_den); } + + void floor(mpq const & a, mpz & f); + + void floor(mpq const & a, mpq & f) { + floor(a, f.m_num); + reset_denominator(f); + } + + void ceil(mpq const & a, mpz & f); + + void ceil(mpq const & a, mpq & f) { + ceil(a, f.m_num); + reset_denominator(f); + } + + static bool is_int(mpq const & a) { return is_one(a.m_den); } + + std::string to_string(mpq const & a) const; + std::string to_rational_string(numeral const & a) { return to_string(a); } + + std::string to_string(mpz const & a) const { return mpz_manager::to_string(a); } + + void display(std::ostream & out, mpz const & a) const { return mpz_manager::display(out, a); } + + void display(std::ostream & out, mpq const & a) const; + + void display_smt2(std::ostream & out, mpz const & a, bool decimal) const { return mpz_manager::display_smt2(out, a, decimal); } + + void display_smt2(std::ostream & out, mpq const & a, bool decimal) const; + + void display_decimal(std::ostream & out, mpq const & a, unsigned prec); + + void add(mpz const & a, mpz const & b, mpz & c) { mpz_manager::add(a, b, c); } + + void add(mpq const & a, mpq const & b, mpq & c) { + STRACE("mpq", tout << "[mpq] " << to_string(a) << " + " << to_string(b) << " == ";); + if (is_int(a) && is_int(b)) { + mpz_manager::add(a.m_num, b.m_num, c.m_num); + reset_denominator(c); + } + else + rat_add(a, b, c); + STRACE("mpq", tout << to_string(c) << "\n";); + } + + void add(mpq const & a, mpz const & b, mpq & c) { + STRACE("mpq", tout << "[mpq] " << to_string(a) << " + " << to_string(b) << " == ";); + if (is_int(a)) { + mpz_manager::add(a.m_num, b, c.m_num); + reset_denominator(c); + } + else { + rat_add(a, b, c); + } + STRACE("mpq", tout << to_string(c) << "\n";); + } + + void sub(mpz const & a, mpz const & b, mpz & c) { mpz_manager::sub(a, b, c); } + + void sub(mpq const & a, mpq const & b, mpq & c) { + STRACE("mpq", tout << "[mpq] " << to_string(a) << " - " << to_string(b) << " == ";); + if (is_int(a) && is_int(b)) { + mpz_manager::sub(a.m_num, b.m_num, c.m_num); + reset_denominator(c); + } + else + rat_sub(a, b, c); + STRACE("mpq", tout << to_string(c) << "\n";); + } + + void inc(mpz & a) { mpz_manager::inc(a); } + + void dec(mpz & a) { mpz_manager::dec(a); } + + void inc(mpq & a) { add(a, mpz(1), a); } + + void dec(mpq & a) { add(a, mpz(-1), a); } + + void mul(mpz const & a, mpz const & b, mpz & c) { mpz_manager::mul(a, b, c); } + + void mul(mpz const & a, mpz const & b, mpq & c) { + mpz_manager::mul(a, b, c.m_num); + reset_denominator(c); + } + + void mul(mpq const & a, mpq const & b, mpq & c) { + STRACE("mpq", tout << "[mpq] " << to_string(a) << " * " << to_string(b) << " == ";); + if (is_int(a) && is_int(b)) { + mpz_manager::mul(a.m_num, b.m_num, c.m_num); + reset_denominator(c); + } + else + rat_mul(a, b, c); + STRACE("mpq", tout << to_string(c) << "\n";); + } + + void mul(mpz const & a, mpq const & b, mpq & c) { + STRACE("mpq", tout << "[mpq] " << to_string(a) << " * " << to_string(b) << " == ";); + if (is_int(b)) { + mpz_manager::mul(a, b.m_num, c.m_num); + reset_denominator(c); + } + else + rat_mul(a, b, c); + STRACE("mpq", tout << to_string(c) << "\n";); + } + + void addmul(mpz const & a, mpz const & b, mpz const & c, mpz & d) { + return mpz_manager::addmul(a, b, c, d); + } + + void submul(mpz const & a, mpz const & b, mpz const & c, mpz & d) { + return mpz_manager::submul(a, b, c, d); + } + + // d <- a + b*c + void addmul(mpq const & a, mpq const & b, mpq const & c, mpq & d) { + if (is_one(b)) { + add(a, c, d); + } + else if (is_minus_one(b)) { + sub(a, c, d); + } + else { + if (SYNCH) { + mpq tmp; + mul(b,c,tmp); + add(a,tmp,d); + del(tmp); + } + else { + mul(b,c,m_addmul_tmp); + add(a, m_addmul_tmp, d); + } + } + } + + // d <- a + b*c + void addmul(mpq const & a, mpz const & b, mpq const & c, mpq & d) { + if (is_one(b)) { + add(a, c, d); + } + else if (is_minus_one(b)) { + sub(a, c, d); + } + else { + if (SYNCH) { + mpq tmp; + mul(b,c,tmp); + add(a,tmp,d); + del(tmp); + } + else { + mul(b,c,m_addmul_tmp); + add(a, m_addmul_tmp, d); + } + } + } + + + // d <- a - b*c + void submul(mpq const & a, mpq const & b, mpq const & c, mpq & d) { + if (is_one(b)) { + sub(a, c, d); + } + else if (is_minus_one(b)) { + add(a, c, d); + } + else { + if (SYNCH) { + mpq tmp; + mul(b,c,tmp); + sub(a,tmp,d); + del(tmp); + } + else { + mul(b,c,m_addmul_tmp); + sub(a, m_addmul_tmp, d); + } + } + } + + // d <- a - b*c + void submul(mpq const & a, mpz const & b, mpq const & c, mpq & d) { + if (is_one(b)) { + sub(a, c, d); + } + else if (is_minus_one(b)) { + add(a, c, d); + } + else { + if (SYNCH) { + mpq tmp; + mul(b,c,tmp); + sub(a,tmp,d); + del(tmp); + } + else { + mul(b,c,m_addmul_tmp); + sub(a, m_addmul_tmp, d); + } + } + } + + void inv(mpq & a) { + SASSERT(!is_zero(a)); + if (is_neg(a)) { + neg(a.m_num); + neg(a.m_den); + } + mpz_manager::swap(a.m_num, a.m_den); + } + + void inv(mpq const & a, mpq & b) { + set(b, a); + inv(b); + } + + void div(mpq const & a, mpq const & b, mpq & c) { + STRACE("mpq", tout << "[mpq] " << to_string(a) << " / " << to_string(b) << " == ";); + if (&b == &c) { + mpz tmp; // it is not safe to use c.m_num at this point. + mul(a.m_num, b.m_den, tmp); + mul(a.m_den, b.m_num, c.m_den); + set(c.m_num, tmp); + del(tmp); + } + else { + mul(a.m_num, b.m_den, c.m_num); + mul(a.m_den, b.m_num, c.m_den); + } + + if (mpz_manager::is_neg(c.m_den)) { + neg(c.m_num); + neg(c.m_den); + } + normalize(c); + STRACE("mpq", tout << to_string(c) << "\n";); + } + + void div(mpq const & a, mpz const & b, mpq & c) { + STRACE("mpq", tout << "[mpq] " << to_string(a) << " / " << to_string(b) << " == ";); + set(c.m_num, a.m_num); + mul(a.m_den, b, c.m_den); + if (mpz_manager::is_neg(b)) { + neg(c.m_num); + neg(c.m_den); + } + normalize(c); + STRACE("mpq", tout << to_string(c) << "\n";); + } + + void acc_div(mpq & a, mpz const & b) { + STRACE("mpq", tout << "[mpq] " << to_string(a) << " / " << to_string(b) << " == ";); + mul(a.m_den, b, a.m_den); + if (mpz_manager::is_neg(b)) { + neg(a.m_num); + neg(a.m_den); + } + normalize(a); + STRACE("mpq", tout << to_string(a) << "\n";); + } + + void machine_div(mpz const & a, mpz const & b, mpz & c) { mpz_manager::machine_div(a, b, c); } + + void div(mpz const & a, mpz const & b, mpz & c) { mpz_manager::div(a, b, c); } + + void rat_div(mpz const & a, mpz const & b, mpq & c) { + set(c.m_num, a); + set(c.m_den, b); + normalize(c); + } + + void machine_idiv(mpq const & a, mpq const & b, mpq & c) { + SASSERT(is_int(a) && is_int(b)); + machine_div(a.m_num, b.m_num, c.m_num); + reset_denominator(c); + } + + void machine_idiv(mpq const & a, mpq const & b, mpz & c) { + SASSERT(is_int(a) && is_int(b)); + machine_div(a.m_num, b.m_num, c); + } + + void idiv(mpq const & a, mpq const & b, mpq & c) { + SASSERT(is_int(a) && is_int(b)); + div(a.m_num, b.m_num, c.m_num); + reset_denominator(c); + } + + void idiv(mpq const & a, mpq const & b, mpz & c) { + SASSERT(is_int(a) && is_int(b)); + div(a.m_num, b.m_num, c); + } + + void rem(mpz const & a, mpz const & b, mpz & c) { mpz_manager::rem(a, b, c); } + + void rem(mpq const & a, mpq const & b, mpq & c) { + SASSERT(is_int(a) && is_int(b)); + rem(a.m_num, b.m_num, c.m_num); + reset_denominator(c); + } + + void rem(mpq const & a, mpq const & b, mpz & c) { + SASSERT(is_int(a) && is_int(b)); + rem(a.m_num, b.m_num, c); + } + + void mod(mpz const & a, mpz const & b, mpz & c) { mpz_manager::mod(a, b, c); } + + void mod(mpq const & a, mpq const & b, mpq & c) { + SASSERT(is_int(a) && is_int(b)); + mod(a.m_num, b.m_num, c.m_num); + reset_denominator(c); + } + + void mod(mpq const & a, mpq const & b, mpz & c) { + SASSERT(is_int(a) && is_int(b)); + mod(a.m_num, b.m_num, c); + } + + static unsigned hash(mpz const & a) { return mpz_manager::hash(a); } + + static unsigned hash(mpq const & a) { return hash(a.m_num); } + + bool eq(mpz const & a, mpz const & b) { return mpz_manager::eq(a, b); } + + bool eq(mpq const & a, mpq const & b) { + return eq(a.m_num, b.m_num) && eq(a.m_den, b.m_den); + } + + bool lt(mpz const & a, mpz const & b) { return mpz_manager::lt(a, b); } + + bool lt(mpq const & a, mpq const & b) { + if (is_int(a) && is_int(b)) + return lt(a.m_num, b.m_num); + else + return rat_lt(a, b); + } + + bool neq(mpz const & a, mpz const & b) { return mpz_manager::neq(a, b); } + + bool gt(mpz const & a, mpz const & b) { return mpz_manager::gt(a, b); } + + bool ge(mpz const & a, mpz const & b) { return mpz_manager::ge(a, b); } + + bool le(mpz const & a, mpz const & b) { return mpz_manager::le(a, b); } + + bool neq(mpq const & a, mpq const & b) { return !eq(a, b); } + + bool gt(mpq const & a, mpq const & b) { return lt(b, a); } + + bool ge(mpq const & a, mpq const & b) { return !lt(a, b); } + + bool le(mpq const & a, mpq const & b) { return !lt(b, a); } + + void gcd(mpz const & a, mpz const & b, mpz & c) { mpz_manager::gcd(a, b, c); } + + void gcd(unsigned sz, mpz const * as, mpz & g) { mpz_manager::gcd(sz, as, g); } + + void gcd(unsigned sz, mpq const * as, mpq & g); + + void gcd(mpq const & a, mpq const & b, mpq & c) { + SASSERT(is_int(a) && is_int(b)); + gcd(a.m_num, b.m_num, c.m_num); + reset_denominator(c); + } + + void gcd(mpz const & r1, mpz const & r2, mpz & a, mpz & b, mpz & g) { mpz_manager::gcd(r1, r2, a, b, g); } + + void gcd(mpq const & r1, mpq const & r2, mpq & a, mpq & b, mpq & g) { + SASSERT(is_int(r1) && is_int(r2)); + reset_denominator(a); + reset_denominator(b); + reset_denominator(g); + gcd(r1.m_num, r2.m_num, a.m_num, b.m_num, g.m_num); + } + + void lcm(mpz const & a, mpz const & b, mpz & c) { mpz_manager::lcm(a, b, c); } + + void lcm(mpq const & a, mpq const & b, mpq & c) { + SASSERT(is_int(a) && is_int(b)); + lcm(a.m_num, b.m_num, c.m_num); + reset_denominator(c); + } + + bool divides(mpz const & a, mpz const & b) { return mpz_manager::divides(a, b); } + + bool divides(mpq const & a, mpq const & b) { + SASSERT(is_int(a) && is_int(b)); + return divides(a.m_num, b.m_num); + } + + void bitwise_or(mpz const & a, mpz const & b, mpz & c) { return mpz_manager::bitwise_or(a, b, c); } + + void bitwise_or(mpq const & a, mpq const & b, mpq & c) { + SASSERT(is_int(a) && is_int(b)); + bitwise_or(a.m_num, b.m_num, c.m_num); + reset_denominator(c); + } + + void bitwise_and(mpz const & a, mpz const & b, mpz & c) { return mpz_manager::bitwise_and(a, b, c); } + + void bitwise_and(mpq const & a, mpq const & b, mpq & c) { + SASSERT(is_int(a) && is_int(b)); + bitwise_and(a.m_num, b.m_num, c.m_num); + reset_denominator(c); + } + + void bitwise_xor(mpz const & a, mpz const & b, mpz & c) { return mpz_manager::bitwise_xor(a, b, c); } + + void bitwise_xor(mpq const & a, mpq const & b, mpq & c) { + SASSERT(is_int(a) && is_int(b)); + bitwise_xor(a.m_num, b.m_num, c.m_num); + reset_denominator(c); + } + + void bitwise_not(unsigned sz, mpz const & a, mpz & c) { return mpz_manager::bitwise_not(sz, a, c); } + + void bitwise_not(unsigned sz, mpq const & a, mpq & c) { + SASSERT(is_int(a)); + bitwise_not(sz, a.m_num, c.m_num); + reset_denominator(c); + } + + void set(mpz & target, mpz const & source) { mpz_manager::set(target, source); } + + void set(mpq & target, mpq const & source) { + set(target.m_num, source.m_num); + set(target.m_den, source.m_den); + } + + void set(mpz & a, int val) { mpz_manager::set(a, val); } + + void set(mpq & a, int val) { + set(a.m_num, val); + reset_denominator(a); + } + + void set(mpq & a, int n, int d) { + SASSERT(d != 0); + if (d < 0) { + n = -n; + d = -d; + } + set(a.m_num, n); + set(a.m_den, d); + normalize(a); + } + + void set(mpq & a, int64 n, uint64 d) { + SASSERT(d != 0); + set(a.m_num, n); + set(a.m_den, d); + normalize(a); + } + + void set(mpq & a, mpz const & n, mpz const & d) { + if (is_neg(d)) { + set(a.m_num, n); + set(a.m_den, d); + neg(a.m_num); + neg(a.m_den); + } + else { + set(a.m_num, n); + set(a.m_den, d); + } + normalize(a); + } + + void set(mpz & a, unsigned val) { mpz_manager::set(a, val); } + + void set(mpq & a, unsigned val) { + set(a.m_num, val); + reset_denominator(a); + } + + void set(mpz & a, char const * val) { mpz_manager::set(a, val); } + + void set(mpq & a, char const * val); + + void set(mpz & a, int64 val) { mpz_manager::set(a, val); } + + void set(mpq & a, int64 val) { + set(a.m_num, val); + reset_denominator(a); + } + + void set(mpz & a, uint64 val) { mpz_manager::set(a, val); } + + void set(mpq & a, uint64 val) { + set(a.m_num, val); + reset_denominator(a); + } + + void set(mpq & a, mpz const & val) { + mpz_manager::set(a.m_num, val); + reset_denominator(a); + } + + void set(mpz & a, unsigned sz, digit_t const * digits) { mpz_manager::set(a, sz, digits); } + + void set(mpq & a, unsigned sz, digit_t const * digits) { + mpz_manager::set(a.m_num, sz, digits); + reset_denominator(a); + } + + void swap(mpz & a, mpz & b) { mpz_manager::swap(a, b); } + + void swap(mpq & a, mpq & b) { + swap(a.m_num, b.m_num); + swap(a.m_den, b.m_den); + } + + void swap_numerator(mpz & a, mpq & b) { + swap(a, b.m_num); + } + + bool is_uint64(mpz const & a) const { return mpz_manager::is_uint64(a); } + + bool is_int64(mpz const & a) const { return mpz_manager::is_int64(a); } + + uint64 get_uint64(mpz const & a) const { return mpz_manager::get_uint64(a); } + + int64 get_int64(mpz const & a) const { return mpz_manager::get_int64(a); } + + bool is_uint64(mpq const & a) const { return is_int(a) && is_uint64(a.m_num); } + + bool is_int64(mpq const & a) const { return is_int(a) && is_int64(a.m_num); } + + uint64 get_uint64(mpq const & a) const { SASSERT(is_uint64(a)); return get_uint64(a.m_num); } + + int64 get_int64(mpq const & a) const { SASSERT(is_int64(a)); return get_int64(a.m_num); } + + double get_double(mpz const & a) const { return mpz_manager::get_double(a); } + + double get_double(mpq const & a) const; + + void power(mpz const & a, unsigned p, mpz & b) { mpz_manager::power(a, p, b); } + + void power(mpq const & a, unsigned p, mpq & b); + + bool is_power_of_two(mpz const & a, unsigned & shift) { return mpz_manager::is_power_of_two(a, shift); } + + bool is_power_of_two(mpq const & a, unsigned & shift) { return is_int(a) && is_power_of_two(a.m_num, shift); } + + unsigned bitsize(mpz const & a) { return mpz_manager::bitsize(a); } + unsigned bitsize(mpq const & a) { return is_int(a) ? bitsize(a.m_num) : bitsize(a.m_num) + bitsize(a.m_den); } + + /** + \brief Return true if the number is a perfect square, and + store the square root in 'root'. + If the number n is positive and the result is false, then + root will contain the smallest integer r such that r*r > n. + */ + bool is_perfect_square(mpz const & a, mpz & r) { return mpz_manager::is_perfect_square(a, r); } + + /** + \brief Return true if the numerator and denominators are perfect squares. + Store the square root in root. + If the result is false, then the value of root should be ignored. + */ + bool is_perfect_square(mpq const & a, mpq & r) { + if (is_int(a)) { + reset_denominator(r); + return is_perfect_square(a.m_num, r.m_num); + } + if (is_perfect_square(a.m_num, r.m_num) && is_perfect_square(a.m_den, r.m_den)) { + normalize(r); + return true; + } + return false; + } + + bool root(mpz & a, unsigned n) { return mpz_manager::root(a, n); } + bool root(mpz const & a, unsigned n, mpz & r) { return mpz_manager::root(a, n, r); } + + /** + \brief Return true if n-th root of a is rational, and store result in r. + */ + bool root(mpq const & a, unsigned n, mpq & r); + + /** + \brief Return the biggest k s.t. 2^k <= a. + + \remark Return 0 if a is not positive. + */ + unsigned prev_power_of_two(mpz const & a) { return mpz_manager::prev_power_of_two(a); } + unsigned prev_power_of_two(mpq const & a); + + bool is_int_perfect_square(mpq const & a, mpq & r) { + SASSERT(is_int(a)); + reset_denominator(r); + return is_perfect_square(a.m_num, r.m_num); + } + + bool is_even(mpz const & a) { return mpz_manager::is_even(a); } + + bool is_even(mpq const & a) { return is_int(a) && is_even(a.m_num); } + +}; + +typedef mpq_manager synch_mpq_manager; +typedef mpq_manager unsynch_mpq_manager; + +typedef _scoped_numeral scoped_mpq; +typedef _scoped_numeral scoped_synch_mpq; +typedef _scoped_numeral_vector scoped_mpq_vector; + +#endif /* _MPQ_H_ */ + diff --git a/lib/mpq_inf.cpp b/lib/mpq_inf.cpp new file mode 100644 index 000000000..6f6d805f2 --- /dev/null +++ b/lib/mpq_inf.cpp @@ -0,0 +1,42 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + mpq_inf.cpp + +Abstract: + + MPQ numbers with infinitesimals + +Author: + + Leonardo de Moura (leonardo) 2011-06-28 + +Revision History: + +--*/ +#include"mpq_inf.h" + +template +std::string mpq_inf_manager::to_string(mpq_inf const & a) const { + if (m.is_zero(a.second)) + return m.to_string(a.first); + + std::string s = "("; + s += m.to_string(a.first); + if (m.is_neg(a.second)) + s += " -e*"; + else + s += " +e*"; + mpq tmp; + m.set(tmp, a.second); + m.abs(tmp); + s += m.to_string(tmp); + m.del(tmp); + s += ")"; + return s; +} + +template class mpq_inf_manager; +template class mpq_inf_manager; diff --git a/lib/mpq_inf.h b/lib/mpq_inf.h new file mode 100644 index 000000000..008333b0b --- /dev/null +++ b/lib/mpq_inf.h @@ -0,0 +1,256 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + mpq_inf.h + +Abstract: + + MPQ numbers with infinitesimals + +Author: + + Leonardo de Moura (leonardo) 2011-06-28 + +Revision History: + +--*/ +#ifndef _MPQ_INF_H_ +#define _MPQ_INF_H_ + +#include"mpq.h" +#include"hash.h" +#include"params.h" + +typedef std::pair mpq_inf; + +template +class mpq_inf_manager { + mpq_manager & m; + double m_inf; +public: + typedef mpq_inf numeral; + + mpq_inf_manager(mpq_manager & _m, params_ref const & p = params_ref()):m(_m) { + updt_params(p); + } + + void updt_params(params_ref const & p) { + m_inf = p.get_double(":infinitesimal-as-double", 0.00001); + } + + enum inf_kind { NEG=-1, ZERO, POS }; + + void reset(mpq_inf & a) { + m.reset(a.first); + m.reset(a.second); + } + + unsigned hash(mpq_inf const & a) const { return hash_u_u(m.hash(a.first), m.hash(a.second)); } + + void del(mpq_inf & a) { + m.del(a.first); + m.del(a.second); + } + + void swap(mpq_inf & a, mpq_inf & b) { + m.swap(a.first, b.first); + m.swap(a.second, b.second); + } + + void set(mpq_inf & a, mpq_inf const & b) { + m.set(a.first, b.first); + m.set(a.second, b.second); + } + + void set(mpq_inf & a, mpq const & r) { + m.set(a.first, r); + m.reset(a.second); + } + + void set(mpq_inf & a, mpq const & r, inf_kind k) { + m.set(a.first, r); + switch (k) { + case NEG: m.set(a.second, -1); break; + case ZERO: m.reset(a.second); break; + case POS: m.set(a.second, 1); break; + } + } + + void set(mpq_inf & a, mpq const & r, mpq const & i) { + m.set(a.first, r); + m.set(a.second, i); + } + + bool is_int(mpq_inf const & a) const { return m.is_int(a.first) && m.is_zero(a.second); } + + bool is_rational(mpq_inf const & a) const { return m.is_zero(a.second); } + + void get_rational(mpq_inf const & a, mpq & r) { m.set(r, a.first); } + + void get_infinitesimal(mpq_inf const & a, mpq & r) { m.set(r, a.second); } + + double get_double(mpq_inf const & a) { + double r = m.get_double(a.first); + if (m.is_pos(a.second)) + return r + m_inf; + else if (m.is_neg(a.second)) + return r - m_inf; + else + return r; + } + + bool is_zero(mpq_inf const & a) const { + return m.is_zero(a.first) && m.is_zero(a.second); + } + + bool eq(mpq_inf const & a, mpq_inf const & b) const { + return m.eq(a.first, b.first) && m.eq(a.second, b.second); + } + + bool eq(mpq_inf const & a, mpq const & b) const { + return m.eq(a.first, b) && m.is_zero(a.second); + } + + bool eq(mpq_inf const & a, mpq const & b, inf_kind k) const { + if (!m.eq(a.first, b)) + return false; + switch (k) { + case NEG: return m.is_minus_one(a.second); + case ZERO: return m.is_zero(a.second); + case POS: return m.is_one(a.second); + } + UNREACHABLE(); + return false; + } + + bool lt(mpq_inf const & a, mpq_inf const & b) const { + return m.lt(a.first, b.first) || (m.lt(a.second, b.second) && m.eq(a.first, b.first)); + } + + bool lt(mpq_inf const & a, mpq const & b) const { + return m.lt(a.first, b) || (m.is_neg(a.second) && m.eq(a.first, b)); + } + + bool lt(mpq_inf const & a, mpq const & b, inf_kind k) const { + if (m.lt(a.first, b)) + return true; + if (m.eq(a.first, b)) { + switch (k) { + case NEG: return m.lt(a.second, mpq(-1)); + case ZERO: return m.is_neg(a.second); + case POS: return m.lt(a.second, mpq(1)); + } + UNREACHABLE(); + } + return false; + } + + bool gt(mpq_inf const & a, mpq_inf const & b) const { return lt(b, a); } + + bool gt(mpq_inf const & a, mpq const & b) const { + return m.gt(a.first, b) || (m.is_pos(a.second) && m.eq(a.first, b)); + } + + bool gt(mpq_inf const & a, mpq const & b, inf_kind k) const { + if (m.gt(a.first, b)) + return true; + if (m.eq(a.first, b)) { + switch (k) { + case NEG: return m.gt(a.second, mpq(-1)); + case ZERO: return m.is_pos(a.second); + case POS: return m.gt(a.second, mpq(1)); + } + UNREACHABLE(); + } + return false; + } + + bool le(mpq_inf const & a, mpq_inf const & b) const { return !gt(a, b); } + + bool le(mpq_inf const & a, mpq const & b) const { return !gt(a, b); } + + bool le(mpq_inf const & a, mpq const & b, inf_kind k) const { return !gt(a, b, k); } + + bool ge(mpq_inf const & a, mpq_inf const & b) const { return !lt(a, b); } + + bool ge(mpq_inf const & a, mpq const & b) const { return !lt(a, b); } + + bool ge(mpq_inf const & a, mpq const & b, inf_kind k) const { return !lt(a, b, k); } + + void add(mpq_inf const & a, mpq_inf const & b, mpq_inf & c) { + m.add(a.first, b.first, c.first); + m.add(a.second, b.second, c.second); + } + + void sub(mpq_inf const & a, mpq_inf const & b, mpq_inf & c) { + m.sub(a.first, b.first, c.first); + m.sub(a.second, b.second, c.second); + } + + void add(mpq_inf const & a, mpq const & b, mpq_inf & c) { + m.add(a.first, b, c.first); + m.set(c.second, a.second); + } + + void sub(mpq_inf const & a, mpq const & b, mpq_inf & c) { + m.sub(a.first, b, c.first); + m.set(c.second, a.second); + } + + void mul(mpq_inf const & a, mpq const & b, mpq_inf & c) { + m.mul(a.first, b, c.first); + m.mul(a.second, b, c.second); + } + + void mul(mpq_inf const & a, mpz const & b, mpq_inf & c) { + m.mul(b, a.first, c.first); + m.mul(b, a.second, c.second); + } + + void inc(mpq_inf & a) { + m.inc(a.first); + } + + void dec(mpq_inf & a) { + m.dec(a.first); + } + + void neg(mpq_inf & a) { + m.neg(a.first); + m.neg(a.second); + } + + void ceil(mpq_inf const & a, mpq & b) { + if (m.is_int(a.first)) { + // special cases for k - delta*epsilon where k is an integer + if (m.is_pos(a.first)) + m.add(a.first, mpq(1), b); // ceil(k + delta*epsilon) --> k+1 + else + m.set(b, a.first); + } + else { + m.ceil(a.first, b); + } + } + + void floor(mpq_inf const & a, mpq & b) { + if (m.is_int(a.first)) { + if (m.is_neg(a.first)) + m.sub(a.first, mpq(1), b); // floor(k - delta*epsilon) --> k-1 + else + m.set(b, a.first); + } + else { + m.floor(a.first, b); + } + } + + std::string to_string(mpq_inf const & a) const; +}; + +typedef mpq_inf_manager synch_mpq_inf_manager; +typedef mpq_inf_manager unsynch_mpq_inf_manager; + +#endif diff --git a/lib/mpz.cpp b/lib/mpz.cpp new file mode 100644 index 000000000..c9a6dc1f1 --- /dev/null +++ b/lib/mpz.cpp @@ -0,0 +1,2116 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + mpz.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-06-17. + +Revision History: + +--*/ +#include +#include"mpz.h" +#include"buffer.h" +#include"trace.h" +#include"hash.h" +#include"bit_util.h" + +#if defined(_MP_MSBIGNUM) +#define COMPILER COMPILER_VC +#ifndef NDEBUG +#define NDEBUG +#endif +#ifdef ARRAYSIZE +#undef ARRAYSIZE +#endif +#include "..\msbig_rational\msbignum.h" +#elif defined(_MP_INTERNAL) +#include"mpn.h" +#elif defined(_MP_GMP) +#include +#else +#error No multi-precision library selected. +#endif + +// Available GCD algorithms +// #define EUCLID_GCD +// #define BINARY_GCD +// #define LS_BINARY_GCD +// #define LEHMER_GCD + +#if defined(_MP_GMP) || (defined(_MP_MSBIGNUM) && defined(_AMD64_)) +// 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 +#endif + +template +static T gcd_core(T u, T v) { + if (u == 0) + return v; + if (v == 0) + return u; + + int k; + + for (k = 0; ((u | v) & 1) == 0; ++k) { + u >>= 1; + v >>= 1; + } + + while ((u & 1) == 0) + u >>= 1; + + do { + while ((v & 1) == 0) + v >>= 1; + + if (u < v) { + v -= u; + } + else { + T diff = u - v; + u = v; + v = diff; + } + v >>= 1; + } while (v != 0); + + return u << k; +} + +unsigned u_gcd(unsigned u, unsigned v) { return gcd_core(u, v); } +uint64 u64_gcd(uint64 u, uint64 v) { return gcd_core(u, v); } + +template +mpz_manager::mpz_manager(): + m_allocator("mpz_manager") { + if (SYNCH) + omp_init_nest_lock(&m_lock); +#ifndef _MP_GMP + if (sizeof(digit_t) == sizeof(uint64)) { + // 64-bit machine + m_init_cell_capacity = 4; + } + else { + m_init_cell_capacity = 6; + } + for (unsigned i = 0; i < 2; i++) { + m_tmp[i] = allocate(m_init_cell_capacity); + m_arg[i] = allocate(m_init_cell_capacity); + m_arg[i]->m_size = 1; + } + set(m_int_min, -static_cast(INT_MIN)); +#else + // GMP + mpz_init(m_tmp); + mpz_init(m_tmp2); + mpz_init(m_two32); + mpz_set_ui(m_two32, UINT_MAX); + mpz_set_ui(m_tmp, 1); + mpz_add(m_two32, m_two32, m_tmp); + m_arg[0] = allocate(); + m_arg[1] = allocate(); + mpz_init(m_uint64_max); + unsigned max_l = static_cast(UINT64_MAX); + unsigned max_h = static_cast(UINT64_MAX >> 32); + mpz_set_ui(m_uint64_max, max_h); + mpz_mul(m_uint64_max, m_two32, m_uint64_max); + mpz_set_ui(m_tmp, max_l); + mpz_add(m_uint64_max, m_uint64_max, m_tmp); + mpz_init(m_int64_max); + + max_l = static_cast(INT64_MAX % static_cast(UINT_MAX)); + max_h = static_cast(INT64_MAX / static_cast(UINT_MAX)); + mpz_set_ui(m_int64_max, max_h); + mpz_set_ui(m_tmp, UINT_MAX); + mpz_mul(m_int64_max, m_tmp, m_int64_max); + mpz_set_ui(m_tmp, max_l); + mpz_add(m_int64_max, m_tmp, m_int64_max); +#endif + + mpz one(1); + set(m_two64, UINT64_MAX); + add(m_two64, one, m_two64); +} + +template +mpz_manager::~mpz_manager() { + del(m_two64); +#ifndef _MP_GMP + del(m_int_min); + for (unsigned i = 0; i < 2; i++) { + deallocate(m_tmp[i]); + deallocate(m_arg[i]); + } +#else + mpz_clear(m_tmp); + mpz_clear(m_tmp2); + mpz_clear(m_two32); + deallocate(m_arg[0]); + deallocate(m_arg[1]); + mpz_clear(m_uint64_max); + mpz_clear(m_int64_max); +#endif + if (SYNCH) + omp_destroy_nest_lock(&m_lock); +} + +template +void mpz_manager::set_big_i64(mpz & c, int64 v) { +#ifndef _MP_GMP + if (is_small(c)) { + c.m_ptr = allocate(m_init_cell_capacity); + } + SASSERT(capacity(c) >= m_init_cell_capacity); + uint64 _v; + if (v < 0) { + _v = -v; + c.m_val = -1; + } + else { + _v = v; + c.m_val = 1; + } + if (sizeof(digit_t) == sizeof(uint64)) { + // 64-bit machine + digits(c)[0] = static_cast(_v); + c.m_ptr->m_size = 1; + } + else { + // 32-bit machine + digits(c)[0] = static_cast(_v); + digits(c)[1] = static_cast(_v >> 32); + c.m_ptr->m_size = digits(c)[1] == 0 ? 1 : 2; + } +#else + if (is_small(c)) { + c.m_ptr = allocate(); + } + uint64 _v; + bool sign; + if (v < 0) { + _v = -v; + sign = true; + } + else { + _v = v; + sign = false; + } + mpz_set_ui(*c.m_ptr, static_cast(_v)); + mpz_set_ui(m_tmp, static_cast(_v >> 32)); + mpz_mul(m_tmp, m_tmp, m_two32); + mpz_add(*c.m_ptr, *c.m_ptr, m_tmp); + if (sign) + mpz_neg(*c.m_ptr, *c.m_ptr); +#endif +} + +template +void mpz_manager::set_big_ui64(mpz & c, uint64 v) { +#ifndef _MP_GMP + if (is_small(c)) { + c.m_ptr = allocate(m_init_cell_capacity); + } + SASSERT(capacity(c) >= m_init_cell_capacity); + c.m_val = 1; + if (sizeof(digit_t) == sizeof(uint64)) { + // 64-bit machine + digits(c)[0] = static_cast(v); + c.m_ptr->m_size = 1; + } + else { + // 32-bit machine + digits(c)[0] = static_cast(v); + digits(c)[1] = static_cast(v >> 32); + c.m_ptr->m_size = digits(c)[1] == 0 ? 1 : 2; + } +#else + if (is_small(c)) { + c.m_ptr = allocate(); + } + mpz_set_ui(*c.m_ptr, static_cast(v)); + mpz_set_ui(m_tmp, static_cast(v >> 32)); + mpz_mul(m_tmp, m_tmp, m_two32); + mpz_add(*c.m_ptr, *c.m_ptr, m_tmp); +#endif +} + +#ifndef _MP_GMP +template +template +void mpz_manager::set(mpz & a, int sign, unsigned sz) { +#if 0 + static unsigned max_sz = 0; + if (sz > max_sz) { + max_sz = sz; + verbose_stream() << "max_sz: " << max_sz << "\n"; + } +#endif + unsigned i = sz; + for (; i > 0; --i) { + if (m_tmp[IDX]->m_digits[i-1] != 0) + break; + } + + if (i == 0) { + // m_tmp[IDX] is zero + reset(a); + return; + } + + if (i == 1 && m_tmp[IDX]->m_digits[0] <= INT_MAX) { + // m_tmp[IDX] fits is a fixnum + del(a); + a.m_val = sign < 0 ? -static_cast(m_tmp[IDX]->m_digits[0]) : static_cast(m_tmp[IDX]->m_digits[0]); + return; + } + + a.m_val = sign; + std::swap(a.m_ptr, m_tmp[IDX]); + a.m_ptr->m_size = i; + if (!m_tmp[IDX]) // 'a' was a small number + m_tmp[IDX] = allocate(m_init_cell_capacity); +} +#endif + +template +void mpz_manager::set(mpz & a, char const * val) { + reset(a); + mpz ten(10); + mpz tmp; + char const * str = val; + bool sign = false; + while (str[0] == ' ') ++str; + if (str[0] == '-') + sign = true; + while (str[0]) { + if ('0' <= str[0] && str[0] <= '9') { + SASSERT(str[0] - '0' <= 9); + mul(a, ten, tmp); + add(tmp, mk_z(str[0] - '0'), a); + } + ++str; + } + del(tmp); + if (sign) + neg(a); +} + +template +void mpz_manager::set(mpz & target, unsigned sz, digit_t const * digits) { + // remove zero digits + while (sz > 0 && digits[sz - 1] == 0) + sz--; + if (sz == 0) + reset(target); + else if (sz == 1) + set(target, digits[0]); + else { +#ifndef _MP_GMP + target.m_val = 1; // number is positive. + if (is_small(target)) { + unsigned c = sz < m_init_cell_capacity ? m_init_cell_capacity : sz; + target.m_ptr = allocate(c); + target.m_ptr->m_size = sz; + target.m_ptr->m_capacity = c; + memcpy(target.m_ptr->m_digits, digits, sizeof(digit_t) * sz); + } + else { + if (capacity(target) < sz) { + SASSERT(sz > m_init_cell_capacity); + deallocate(target.m_ptr); + target.m_ptr = allocate(sz); + target.m_ptr->m_size = sz; + target.m_ptr->m_capacity = sz; + memcpy(target.m_ptr->m_digits, digits, sizeof(digit_t) * sz); + } + else { + target.m_ptr->m_size = sz; + memcpy(target.m_ptr->m_digits, digits, sizeof(digit_t) * sz); + } + } +#else + mk_big(target); + // reset + mpz_set_ui(*target.m_ptr, digits[sz - 1]); + SASSERT(sz > 0); + unsigned i = sz - 1; + while (i > 0) { + --i; + mpz_mul_2exp(*target.m_ptr, *target.m_ptr, 32); + mpz_set_ui(m_tmp, digits[i]); + mpz_add(*target.m_ptr, *target.m_ptr, m_tmp); + } +#endif + } +} + +#ifndef _MP_GMP +template +template +void mpz_manager::big_add_sub(mpz const & a, mpz const & b, mpz & c) { + int sign_a; + int sign_b; + mpz_cell * cell_a; + mpz_cell * cell_b; + get_sign_cell<0>(a, sign_a, cell_a); + get_sign_cell<1>(b, sign_b, cell_b); + if (SUB) + sign_b = -sign_b; + size_t real_sz; + if (sign_a == sign_b) { + unsigned sz = max(cell_a->m_size, cell_b->m_size)+1; + ensure_tmp_capacity<0>(sz); + add_full(cell_a->m_digits, + cell_a->m_size, + cell_b->m_digits, + cell_b->m_size, + m_tmp[0]->m_digits, + sz, + &real_sz, + 0); + SASSERT(real_sz <= sz); + set<0>(c, sign_a, static_cast(real_sz)); + } + else { + digit_t borrow; + int r = compare_diff(cell_a->m_digits, + cell_a->m_size, + cell_b->m_digits, + cell_b->m_size); + if (r == 0) { + reset(c); + } + else if (r < 0) { + // a < b + unsigned sz = cell_b->m_size; + ensure_tmp_capacity<0>(sz); + sub_diff(cell_b->m_digits, + cell_b->m_size, + cell_a->m_digits, + cell_a->m_size, + m_tmp[0]->m_digits, + &borrow, + 0); + SASSERT(borrow == 0); + set<0>(c, sign_b, sz); + } + else { + // a > b + unsigned sz = cell_a->m_size; + ensure_tmp_capacity<0>(sz); + sub_diff(cell_a->m_digits, + cell_a->m_size, + cell_b->m_digits, + cell_b->m_size, + m_tmp[0]->m_digits, + &borrow, + 0); + SASSERT(borrow == 0); + set<0>(c, sign_a, sz); + } + } +} +#endif + +template +void mpz_manager::big_add(mpz const & a, mpz const & b, mpz & c) { +#ifndef _MP_GMP + big_add_sub(a, b, c); +#else + // GMP version + mpz_t * arg0; + mpz_t * arg1; + get_arg<0>(a, arg0); + get_arg<1>(b, arg1); + mk_big(c); + mpz_add(*c.m_ptr, *arg0, *arg1); +#endif +} + +template +void mpz_manager::big_sub(mpz const & a, mpz const & b, mpz & c) { +#ifndef _MP_GMP + big_add_sub(a, b, c); +#else + // GMP version + mpz_t * arg0; + mpz_t * arg1; + get_arg<0>(a, arg0); + get_arg<1>(b, arg1); + mk_big(c); + mpz_sub(*c.m_ptr, *arg0, *arg1); +#endif +} + +template +void mpz_manager::big_mul(mpz const & a, mpz const & b, mpz & c) { +#ifndef _MP_GMP + int sign_a; + int sign_b; + mpz_cell * cell_a; + mpz_cell * cell_b; + get_sign_cell<0>(a, sign_a, cell_a); + get_sign_cell<1>(b, sign_b, cell_b); + unsigned sz = cell_a->m_size + cell_b->m_size; + ensure_tmp_capacity<0>(sz); + multiply(cell_a->m_digits, + cell_a->m_size, + cell_b->m_digits, + cell_b->m_size, + m_tmp[0]->m_digits, + 0); + set<0>(c, sign_a == sign_b ? 1 : -1, sz); +#else + // GMP version + mpz_t * arg0; + mpz_t * arg1; + get_arg<0>(a, arg0); + get_arg<1>(b, arg1); + mk_big(c); + mpz_mul(*c.m_ptr, *arg0, *arg1); +#endif +} + +#ifndef _MP_GMP +template +template +void mpz_manager::quot_rem_core(mpz const & a, mpz const & b, mpz & q, mpz & r) { + /* + +26 / +7 = +3, remainder is +5 + -26 / +7 = -3, remainder is -5 + +26 / -7 = -3, remainder is +5 + -26 / -7 = +3, remainder is -5 + */ + int sign_a; + int sign_b; + mpz_cell * cell_a; + mpz_cell * cell_b; + get_sign_cell<0>(a, sign_a, cell_a); + get_sign_cell<1>(b, sign_b, cell_b); + if (cell_b->m_size > cell_a->m_size) { + if (MODE == REM_ONLY || MODE == QUOT_AND_REM) + set(r, a); + if (MODE == QUOT_ONLY || MODE == QUOT_AND_REM) + reset(q); + return; + } + unsigned q_sz = cell_a->m_size - cell_b->m_size + 1; + unsigned r_sz = cell_b->m_size; + ensure_tmp_capacity<0>(q_sz); + ensure_tmp_capacity<1>(r_sz); + divide(cell_a->m_digits, + cell_a->m_size, + cell_b->m_digits, + cell_b->m_size, + reciprocal_1_NULL, + m_tmp[0]->m_digits, + m_tmp[1]->m_digits, + 0); + if (MODE == QUOT_ONLY || MODE == QUOT_AND_REM) + set<0>(q, sign_a == sign_b ? 1 : -1, q_sz); + if (MODE == REM_ONLY || MODE == QUOT_AND_REM) + set<1>(r, sign_a, r_sz); +} +#endif + +template +void mpz_manager::big_div_rem(mpz const & a, mpz const & b, mpz & q, mpz & r) { +#ifndef _MP_GMP + quot_rem_core(a, b, q, r); +#else + // GMP version + mpz_t * arg0; + mpz_t * arg1; + get_arg<0>(a, arg0); + get_arg<1>(b, arg1); + mk_big(q); + mk_big(r); + mpz_tdiv_qr(*q.m_ptr, *r.m_ptr, *arg0, *arg1); +#endif +} + +template +void mpz_manager::big_div(mpz const & a, mpz const & b, mpz & c) { +#ifndef _MP_GMP + mpz dummy; + quot_rem_core(a, b, c, dummy); + SASSERT(is_zero(dummy)); +#else + // GMP version + mpz_t * arg0; + mpz_t * arg1; + get_arg<0>(a, arg0); + get_arg<1>(b, arg1); + mk_big(c); + mpz_tdiv_q(*c.m_ptr, *arg0, *arg1); +#endif +} + +template +void mpz_manager::big_rem(mpz const & a, mpz const & b, mpz & c) { +#ifndef _MP_GMP + mpz dummy; + quot_rem_core(a, b, dummy, c); + SASSERT(is_zero(dummy)); +#else + // GMP version + mpz_t * arg0; + mpz_t * arg1; + get_arg<0>(a, arg0); + get_arg<1>(b, arg1); + mk_big(c); + mpz_tdiv_r(*c.m_ptr, *arg0, *arg1); +#endif +} + +template +void mpz_manager::gcd(mpz const & a, mpz const & b, mpz & c) { + if (is_small(a) && is_small(b)) { + int _a = a.m_val; + int _b = b.m_val; + if (_a < 0) _a = -_a; + if (_b < 0) _b = -_b; + unsigned r = u_gcd(_a, _b); + // Remark: r is (INT_MAX + 1) + // If a == b == INT_MIN + set(c, r); + } + else { +#ifdef _MP_GMP + mpz_t * arg0; + mpz_t * arg1; + get_arg<0>(a, arg0); + get_arg<1>(b, arg1); + mk_big(c); + mpz_gcd(*c.m_ptr, *arg0, *arg1); + return; +#endif + if (is_zero(a)) { + set(c, b); + abs(c); + return; + } + if (is_zero(b)) { + set(c, a); + abs(c); + return; + } +#ifdef BINARY_GCD + // Binary GCD for big numbers + // - It doesn't use division + // - The initial experiments, don't show any performance improvement + // - It only works with _MP_INTERNAL and _MP_MSBIGNUM + mpz u, v, diff; + set(u, a); + set(v, b); + abs(u); + abs(v); + + unsigned k_u = power_of_two_multiple(u); + unsigned k_v = power_of_two_multiple(v); + unsigned k = k_u < k_v ? k_u : k_v; + + machine_div2k(u, k_u); + + while (true) { + machine_div2k(v, k_v); + + if (lt(u, v)) { + sub(v, u, v); + } + else { + sub(u, v, diff); + swap(u, v); + swap(v, diff); + } + + if (is_zero(v) || is_one(v)) + break; + + // reset least significant bit + if (is_small(v)) + v.m_val &= ~1; + else + v.m_ptr->m_digits[0] &= ~static_cast(1); + k_v = power_of_two_multiple(v); + } + + mul2k(u, k, c); + del(u); del(v); del(diff); +#endif // BINARY_GCD + +#ifdef EUCLID_GCD + mpz tmp1; + mpz tmp2; + mpz aux; + set(tmp1, a); + set(tmp2, b); + abs(tmp1); + abs(tmp2); + if (lt(tmp1, tmp2)) + swap(tmp1, tmp2); + if (is_zero(tmp2)) { + swap(c, tmp1); + } + else { + while (true) { + if (is_uint64(tmp1) && is_uint64(tmp2)) { + set(c, u64_gcd(get_uint64(tmp1), get_uint64(tmp2))); + break; + } + rem(tmp1, tmp2, aux); + if (is_zero(aux)) { + swap(c, tmp2); + break; + } + swap(tmp1, tmp2); + swap(tmp2, aux); + } + } + del(tmp1); del(tmp2); del(aux); +#endif // EUCLID_GCD + +#ifdef LS_BINARY_GCD + mpz u, v, t, u1, u2; + set(u, a); + set(v, b); + abs(u); + abs(v); + if (lt(u, v)) + swap(u, v); + while (!is_zero(v)) { + // Basic idea: + // compute t = 2^e*v such that t <= u < 2t + // u := min{u - t, 2t - u} + // + // The assignment u := min{u - t, 2t - u} + // can be replaced with u := u - t + // + // Since u and v are positive, we have: + // 2^{log2(u)} <= u < 2^{(log2(u) + 1)} + // 2^{log2(v)} <= v < 2^{(log2(v) + 1)} + // --> + // 2^{log2(v)}*2^{log2(u)-log2(v)} <= v*2^{log2(u)-log2(v)} < 2^{log2(v) + 1}*2^{log2(u)-log2(v)} + // --> + // 2^{log2(u)} <= v*2^{log2(u)-log2(v)} < 2^{log2(u) + 1} + // + // Now, let t be v*2^{log2(u)-log2(v)} + // If t <= u, then we found t + // Otherwise t = t div 2 + unsigned k_u = log2(u); + unsigned k_v = log2(v); + SASSERT(k_v <= k_u); + unsigned e = k_u - k_v; + mul2k(v, e, t); + sub(u, t, u1); + if (is_neg(u1)) { + // t is too big + machine_div2k(t, 1); + // Now, u1 contains u - 2t + neg(u1); + // Now, u1 contains 2t - u + sub(u, t, u2); // u2 := u - t + } + else { + // u1 contains u - t + mul2k(t, 1); + sub(t, u, u2); + // u2 contains 2t - u + } + SASSERT(is_nonneg(u1)); + SASSERT(is_nonneg(u2)); + if (lt(u1, u2)) + swap(u, u1); + else + swap(u, u2); + if (lt(u, v)) + swap(u,v); + } + swap(u, c); + del(u); del(v); del(t); del(u1); del(u2); +#endif // LS_BINARY_GCD + +#ifdef LEHMER_GCD + // For now, it only works if sizeof(digit_t) == sizeof(unsigned) + COMPILE_TIME_ASSERT(sizeof(digit_t) == sizeof(unsigned)); + + int64 a_hat, b_hat, A, B, C, D, T, q, a_sz, b_sz; + mpz a1, b1, t, r, tmp; + set(a1, a); + set(b1, b); + abs(a1); + abs(b1); + if (lt(a1, b1)) + swap(a1, b1); + while (true) { + SASSERT(ge(a1, b1)); + if (is_small(b1)) { + if (is_small(a1)) { + unsigned r = u_gcd(a1.m_val, b1.m_val); + set(c, r); + break; + } + else { + while (!is_zero(b1)) { + SASSERT(ge(a1, b1)); + rem(a1, b1, tmp); + swap(a1, b1); + swap(b1, tmp); + } + swap(c, a1); + break; + } + } + SASSERT(!is_small(a1)); + SASSERT(!is_small(b1)); + a_sz = a1.m_ptr->m_size; + b_sz = b1.m_ptr->m_size; + SASSERT(b_sz <= a_sz); + a_hat = a1.m_ptr->m_digits[a_sz - 1]; + b_hat = (b_sz == a_sz) ? b1.m_ptr->m_digits[b_sz - 1] : 0; + A = 1; + B = 0; + C = 0; + D = 1; + while (true) { + // Loop invariants + SASSERT(a_hat + A <= static_cast(UINT_MAX) + 1); + SASSERT(a_hat + B < static_cast(UINT_MAX) + 1); + SASSERT(b_hat + C < static_cast(UINT_MAX) + 1); + SASSERT(b_hat + D <= static_cast(UINT_MAX) + 1); + // overflows can't happen since I'm using int64 + if (b_hat + C == 0 || b_hat + D == 0) + break; + q = (a_hat + A)/(b_hat + C); + if (q != (a_hat + B)/(b_hat + D)) + break; + T = A - q*C; + A = C; + C = T; + T = B - q*D; + B = D; + D = T; + T = a_hat - q*b_hat; + a_hat = b_hat; + b_hat = T; + } + SASSERT(ge(a1, b1)); + if (B == 0) { + rem(a1, b1, t); + swap(a1, b1); + swap(b1, t); + SASSERT(ge(a1, b1)); + } + else { + // t <- A*a1 + set(tmp, A); + mul(a1, tmp, t); + // t <- t + B*b1 + set(tmp, B); + addmul(t, tmp, b1, t); + // r <- C*a1 + set(tmp, C); + mul(a1, tmp, r); + // r <- r + D*b1 + set(tmp, D); + addmul(r, tmp, b1, r); + // a <- t + swap(a1, t); + // b <- r + swap(b1, r); + SASSERT(ge(a1, b1)); + } + } + del(a1); del(b1); del(r); del(t); del(tmp); +#endif // LEHMER_GCD + } +} + +template +unsigned mpz_manager::size_info(mpz const & a) { + if (is_small(a)) + return 1; +#ifndef _MP_GMP + return a.m_ptr->m_size + 1; +#else + return mpz_size(*a.m_ptr); +#endif +} + +template +struct mpz_manager::sz_lt { + mpz_manager & m; + mpz const * m_as; + + sz_lt(mpz_manager & _m, mpz const * as):m(_m), m_as(as) {} + + bool operator()(unsigned p1, unsigned p2) { + return m.size_info(m_as[p1]) < m.size_info(m_as[p2]); + } +}; + +template +void mpz_manager::gcd(unsigned sz, mpz const * as, mpz & g) { +#if 0 + // Optimization: sort numbers by size. Motivation: compute the gcd of the small ones first. + // The optimization did not really help. + switch (sz) { + case 0: + reset(g); + return; + case 1: + set(g, as[0]); + abs(g); + return; + case 2: + gcd(as[0], as[1], g); + return; + default: + break; + } + unsigned i; + for (i = 0; i < sz; i++) { + if (!is_small(as[i])) + break; + } + if (i != sz) { + // array has big numbers + sbuffer p; + for (i = 0; i < sz; i++) + p.push_back(i); + sz_lt lt(*this, as); + std::sort(p.begin(), p.end(), lt); + TRACE("mpz_gcd", for (unsigned i = 0; i < sz; i++) tout << p[i] << ":" << size_info(as[p[i]]) << " "; tout << "\n";); + gcd(as[p[0]], as[p[1]], g); + for (i = 2; i < sz; i++) { + if (is_one(g)) + return; + gcd(g, as[p[i]], g); + } + return; + } + else { + gcd(as[0], as[1], g); + for (unsigned i = 2; i < sz; i++) { + if (is_one(g)) + return; + gcd(g, as[i], g); + } + } +#else + // Vanilla implementation + switch (sz) { + case 0: + reset(g); + return; + case 1: + set(g, as[0]); + abs(g); + return; + default: + break; + } + gcd(as[0], as[1], g); + for (unsigned i = 2; i < sz; i++) { + if (is_one(g)) + return; + gcd(g, as[i], g); + } +#endif +} + +template +void mpz_manager::gcd(mpz const & r1, mpz const & r2, mpz & a, mpz & b, mpz & r) { + mpz tmp1, tmp2; + mpz aux, quot; + set(tmp1, r1); + set(tmp2, r2); + set(a, 1); + set(b, 0); + mpz nexta, nextb; + set(nexta, 0); + set(nextb, 1); + + abs(tmp1); + abs(tmp2); + if (lt(tmp1, tmp2)) { + swap(tmp1, tmp2); + swap(nexta, nextb); + swap(a, b); + } + + // tmp1 >= tmp2 >= 0 + // quot_rem in one function would be faster. + while (is_pos(tmp2)) { + SASSERT(ge(tmp1, tmp2)); + + // aux = tmp2 + set(aux, tmp2); + // quot = div(tmp1, tmp2); + machine_div(tmp1, tmp2, quot); + // tmp2 = tmp1 % tmp2 + rem(tmp1, tmp2, tmp2); + // tmp1 = aux + set(tmp1, aux); + // aux = nexta + set(aux, nexta); + // nexta = a - (quot*nexta) + mul(quot, nexta, nexta); + sub(a, nexta, nexta); + // a = axu + set(a, aux); + // aux = nextb + set(aux, nextb); + // nextb = b - (quot*nextb) + mul(nextb, quot, nextb); + sub(b, nextb, nextb); + // b = aux + set(b, aux); + } + + if (is_neg(r1)) + neg(a); + if (is_neg(r2)) + neg(b); + + // SASSERT((a*r1) + (b*r2) == tmp1); +#ifdef Z3DEBUG + mul(a, r1, nexta); + mul(b, r2, nextb); + add(nexta, nextb, nexta); + SASSERT(eq(nexta, tmp1)); +#endif + set(r, tmp1); + del(tmp1); + del(tmp2); + del(aux); + del(quot); + del(nexta); + del(nextb); +} + +template +void mpz_manager::lcm(mpz const & a, mpz const & b, mpz & c) { + if (is_one(b)) { + set(c, a); + TRACE("lcm_bug", tout << "1. lcm(" << to_string(a) << ", " << to_string(b) << ") = " << to_string(c) << "\n";); + } + else if (is_one(a) || eq(a, b)) { + set(c, b); + TRACE("lcm_bug", tout << "2. lcm(" << to_string(a) << ", " << to_string(b) << ") = " << to_string(c) << "\n";); + } + else { + mpz r; + gcd(a, b, r); + TRACE("lcm_bug", tout << "gcd(" << to_string(a) << ", " << to_string(b) << ") = " << to_string(r) << "\n";); + if (eq(r, a)) { + set(c, b); + TRACE("lcm_bug", tout << "3. lcm(" << to_string(a) << ", " << to_string(b) << ") = " << to_string(c) << "\n";); + } + else if (eq(r, b)) { + set(c, a); + TRACE("lcm_bug", tout << "4. lcm(" << to_string(a) << ", " << to_string(b) << ") = " << to_string(c) << "\n";); + } + else { + // c contains gcd(a, b) + // so c divides a, and machine_div(a, c) is equal to div(a, c) + machine_div(a, r, r); + mul(r, b, c); + TRACE("lcm_bug", tout << "5. lcm(" << to_string(a) << ", " << to_string(b) << ") = " << to_string(c) << "\n";); + } + del(r); + } +} + +template +void mpz_manager::bitwise_or(mpz const & a, mpz const & b, mpz & c) { + SASSERT(is_nonneg(a)); + SASSERT(is_nonneg(b)); + TRACE("mpz", tout << "is_small(a): " << is_small(a) << ", is_small(b): " << is_small(b) << "\n";); + if (is_small(a) && is_small(b)) { + del(c); + c.m_val = a.m_val | b.m_val; + } + else { +#ifndef _MP_GMP + mpz a1, b1, a2, b2, m, tmp; + set(a1, a); + set(b1, b); + set(m, 1); + reset(c); + while (!is_zero(a1) && !is_zero(b1)) { + TRACE("mpz", tout << "a1: " << to_string(a1) << ", b1: " << to_string(b1) << "\n";); + mod(a1, m_two64, a2); + mod(b1, m_two64, b2); + TRACE("mpz", tout << "a2: " << to_string(a2) << ", b2: " << to_string(b2) << "\n";); + uint64 v = get_uint64(a2) | get_uint64(b2); + TRACE("mpz", tout << "uint(a2): " << get_uint64(a2) << ", uint(b2): " << get_uint64(b2) << "\n";); + set(tmp, v); + mul(tmp, m, tmp); + add(c, tmp, c); // c += m * v + mul(m, m_two64, m); + div(a1, m_two64, a1); + div(b1, m_two64, b1); + } + if (!is_zero(a1)) { + mul(a1, m, a1); + add(c, a1, c); + } + if (!is_zero(b1)) { + mul(b1, m, b1); + add(c, b1, c); + } + del(a1); del(b1); del(a2); del(b2); del(m); del(tmp); +#else + mpz_t * arg0; + mpz_t * arg1; + get_arg<0>(a, arg0); + get_arg<1>(b, arg1); + mk_big(c); + mpz_ior(*c.m_ptr, *arg0, *arg1); +#endif + } +} + +template +void mpz_manager::bitwise_and(mpz const & a, mpz const & b, mpz & c) { + SASSERT(is_nonneg(a)); + SASSERT(is_nonneg(b)); + if (is_small(a) && is_small(b)) { + del(c); + c.m_val = a.m_val & b.m_val; + } + else { +#ifndef _MP_GMP + mpz a1, b1, a2, b2, m, tmp; + set(a1, a); + set(b1, b); + set(m, 1); + reset(c); + while (!is_zero(a1) && !is_zero(b1)) { + mod(a1, m_two64, a2); + mod(b1, m_two64, b2); + uint64 v = get_uint64(a2) & get_uint64(b2); + set(tmp, v); + mul(tmp, m, tmp); + add(c, tmp, c); // c += m * v + mul(m, m_two64, m); + div(a1, m_two64, a1); + div(b1, m_two64, b1); + } + del(a1); del(b1); del(a2); del(b2); del(m); del(tmp); +#else + mpz_t * arg0; + mpz_t * arg1; + get_arg<0>(a, arg0); + get_arg<1>(b, arg1); + mk_big(c); + mpz_and(*c.m_ptr, *arg0, *arg1); +#endif + } +} + +template +void mpz_manager::bitwise_xor(mpz const & a, mpz const & b, mpz & c) { + SASSERT(is_nonneg(a)); + SASSERT(is_nonneg(b)); + if (is_small(a) && is_small(b)) { + set_i64(c, i64(a) ^ i64(b)); + } + else { +#ifndef _MP_GMP + mpz a1, b1, a2, b2, m, tmp; + set(a1, a); + set(b1, b); + set(m, 1); + reset(c); + while (!is_zero(a1) && !is_zero(b1)) { + mod(a1, m_two64, a2); + mod(b1, m_two64, b2); + uint64 v = get_uint64(a2) ^ get_uint64(b2); + set(tmp, v); + mul(tmp, m, tmp); + add(c, tmp, c); // c += m * v + mul(m, m_two64, m); + div(a1, m_two64, a1); + div(b1, m_two64, b1); + } + if (!is_zero(a1)) { + mul(a1, m, a1); + add(c, a1, c); + } + if (!is_zero(b1)) { + mul(b1, m, b1); + add(c, b1, c); + } + del(a1); del(b1); del(a2); del(b2); del(m); del(tmp); +#else + mpz_t * arg0; + mpz_t * arg1; + get_arg<0>(a, arg0); + get_arg<1>(b, arg1); + mk_big(c); + mpz_xor(*c.m_ptr, *arg0, *arg1); +#endif + } +} + +template +void mpz_manager::bitwise_not(unsigned sz, mpz const & a, mpz & c) { + SASSERT(is_nonneg(a)); + if (is_small(a) && sz <= 63) { + int64 mask = (static_cast(1) << sz) - static_cast(1); + set_i64(c, (~ i64(a)) & mask); + } + else { + mpz a1, a2, m, tmp; + set(a1, a); + set(m, 1); + set(c, 0); + while (sz > 0) { + mod(a1, m_two64, a2); + uint64 n = get_uint64(a2); + uint64 v = ~n; + SASSERT(~v == n); + if (sz < 64) { + uint64 mask = (1ull << static_cast(sz)) - 1ull; + v = mask & v; + } + TRACE("bitwise_not", tout << "sz: " << sz << ", v: " << v << ", n: " << n << "\n";); + set(tmp, v); + SASSERT(get_uint64(tmp) == v); + mul(tmp, m, tmp); + add(c, tmp, c); // c += m * v + mul(m, m_two64, m); + div(a1, m_two64, a1); + sz -= (sz<64) ? sz : 64; + } + del(a1); del(a2); del(m); del(tmp); + TRACE("bitwise_not", tout << "sz: " << sz << " a: " << to_string(a) << " c: " << to_string(c) << "\n";); + } +} + +template +void mpz_manager::big_set(mpz & target, mpz const & source) { +#ifndef _MP_GMP + if (&target == &source) + return; + target.m_val = source.m_val; + if (is_small(target)) { + target.m_ptr = allocate(capacity(source)); + target.m_ptr->m_size = size(source); + target.m_ptr->m_capacity = capacity(source); + memcpy(target.m_ptr->m_digits, source.m_ptr->m_digits, sizeof(digit_t) * size(source)); + } + else { + if (capacity(target) < size(source)) { + deallocate(target.m_ptr); + target.m_ptr = allocate(capacity(source)); + target.m_ptr->m_size = size(source); + target.m_ptr->m_capacity = capacity(source); + memcpy(target.m_ptr->m_digits, source.m_ptr->m_digits, sizeof(digit_t) * size(source)); + } + else { + target.m_ptr->m_size = size(source); + memcpy(target.m_ptr->m_digits, source.m_ptr->m_digits, sizeof(digit_t) * size(source)); + } + } +#else + // GMP version + mk_big(target); + mpz_set(*target.m_ptr, *source.m_ptr); +#endif +} + +template +int mpz_manager::big_compare(mpz const & a, mpz const & b) { +#ifndef _MP_GMP + int sign_a; + int sign_b; + mpz_cell * cell_a; + mpz_cell * cell_b; + get_sign_cell<0>(a, sign_a, cell_a); + get_sign_cell<1>(b, sign_b, cell_b); + + if (sign_a > 0) { + // a is positive + if (sign_b > 0) { + // a & b are positive + return compare_diff(cell_a->m_digits, + cell_a->m_size, + cell_b->m_digits, + cell_b->m_size); + } + else { + // b is negative + return 1; // a > b + } + } + else { + // a is negative + if (sign_b > 0) { + // b is positive + return -1; // a < b + } + else { + // a & b are negative + return compare_diff(cell_b->m_digits, + cell_b->m_size, + cell_a->m_digits, + cell_a->m_size); + } + } +#else + // GMP version + mpz_t * arg0; + mpz_t * arg1; + get_arg<0>(a, arg0); + get_arg<1>(b, arg1); + return mpz_cmp(*arg0, *arg1); +#endif +} + +template +bool mpz_manager::is_uint64(mpz const & a) const { +#ifndef _MP_GMP + if (a.m_val < 0) + return false; + if (is_small(a)) + return true; + if (sizeof(digit_t) == sizeof(uint64)) { + return size(a) <= 1; + } + else { + return size(a) <= 2; + } +#else + // GMP version + if (is_small(a)) + return a.m_val >= 0; + return is_nonneg(a) && mpz_cmp(*a.m_ptr, m_uint64_max) <= 0; +#endif +} + +template +bool mpz_manager::is_int64(mpz const & a) const { + if (is_small(a)) + return true; +#ifndef _MP_GMP + if (!is_uint64(a)) + return false; + uint64 num = get_uint64(a); + uint64 msb = static_cast(1) << 63; + uint64 msb_val = msb & num; + if (a.m_val >= 0) { + // non-negative number. + return (0 == msb_val); + } + else { + // negative number. + // either the high bit is 0, or + // the number is 2^64 which can be represented. + // + return 0 == msb_val || (msb_val == num); + } +#else + // GMP version + return mpz_cmp(*a.m_ptr, m_int64_max) <= 0; +#endif +} + +template +uint64 mpz_manager::get_uint64(mpz const & a) const { + if (is_small(a)) + return static_cast(a.m_val); +#ifndef _MP_GMP + SASSERT(a.m_ptr->m_size > 0); + if (a.m_ptr->m_size == 1) + return digits(a)[0]; + if (sizeof(digit_t) == sizeof(uint64)) + // 64-bit machine + return digits(a)[0]; + else + // 32-bit machine + return ((static_cast(digits(a)[1]) << 32) | (static_cast(digits(a)[0]))); +#else + // GMP version + if (sizeof(uint64) == sizeof(unsigned long)) { + return mpz_get_ui(*a.m_ptr); + } + else { + mpz_manager * _this = const_cast(this); + mpz_set(_this->m_tmp, *a.m_ptr); + mpz_mod(_this->m_tmp, m_tmp, m_two32); + uint64 r = static_cast(mpz_get_ui(m_tmp)); + mpz_set(_this->m_tmp, *a.m_ptr); + mpz_div(_this->m_tmp, m_tmp, m_two32); + r += static_cast(mpz_get_ui(m_tmp)) << static_cast(32); + return r; + } +#endif +} + +template +int64 mpz_manager::get_int64(mpz const & a) const { + if (is_small(a)) + return static_cast(a.m_val); +#ifndef _MP_GMP + SASSERT(is_int64(a)); + uint64 num = get_uint64(a); + if (a.m_val < 0) { + if (num != 0 && (num << 1) == 0) + return INT64_MIN; + return -static_cast(num); + } + return static_cast(num); +#else + // GMP + if (sizeof(int64) == sizeof(long) || mpz_fits_slong_p(*a.m_ptr)) { + return mpz_get_si(*a.m_ptr); + } + else { + mpz_manager * _this = const_cast(this); + mpz_mod(_this->m_tmp, *a.m_ptr, m_two32); + int64 r = static_cast(mpz_get_ui(m_tmp)); + mpz_div(_this->m_tmp, *a.m_ptr, m_two32); + r += static_cast(mpz_get_si(m_tmp)) << static_cast(32); + return r; + } +#endif +} + +template +double mpz_manager::get_double(mpz const & a) const { + if (is_small(a)) + return static_cast(a.m_val); +#ifndef _MP_GMP + double r = 0.0; + double d = 1.0; + unsigned sz = size(a); + for (unsigned i = 0; i < sz; i++) { + r += d * static_cast(digits(a)[i]); + if (sizeof(digit_t) == sizeof(uint64)) + d *= static_cast(UINT64_MAX); // 64-bit version + else + d *= static_cast(UINT_MAX); // 32-bit version + } + return a.m_val < 0 ? -r : r; +#else + return mpz_get_d(*a.m_ptr); +#endif +} + +template +void mpz_manager::display(std::ostream & out, mpz const & a) const { + if (is_small(a)) { + out << a.m_val; + } + else { +#ifndef _MP_GMP + if (a.m_val < 0) + out << "-"; + if (sizeof(digit_t) == 4) { + sbuffer buffer(11*size(a), 0); + out << mp_decimal(digits(a), size(a), buffer.begin(), buffer.size(), 0); + } + else { + sbuffer buffer(21*size(a), 0); + out << mp_decimal(digits(a), size(a), buffer.begin(), buffer.size(), 0); + } +#else + // GMP version + size_t sz = mpz_sizeinbase(*a.m_ptr, 10) + 2; + sbuffer buffer(sz, 0); + mpz_get_str(buffer.c_ptr(), 10, *a.m_ptr); + out << buffer.c_ptr(); +#endif + } +} + +template +void mpz_manager::display_smt2(std::ostream & out, mpz const & a, bool decimal) const { + if (is_neg(a)) { + mpz_manager * _this = const_cast*>(this); + _scoped_numeral > tmp(*_this); + _this->set(tmp, a); + _this->neg(tmp); + out << "(- "; + display(out, tmp); + if (decimal) + out << ".0"; + out << ")"; + } + else { + display(out, a); + if (decimal) + out << ".0"; + } +} + +template +std::string mpz_manager::to_string(mpz const & a) const { + std::ostringstream buffer; + display(buffer, a); + return buffer.str(); +} + +template +unsigned mpz_manager::hash(mpz const & a) { + if (is_small(a)) + return a.m_val; +#ifndef _MP_GMP + unsigned sz = size(a); + if (sz == 1) + return static_cast(digits(a)[0]); + return string_hash(reinterpret_cast(digits(a)), sz * sizeof(digit_t), 17); +#else + return mpz_get_si(*a.m_ptr); +#endif +} + +template +void mpz_manager::power(mpz const & a, unsigned p, mpz & b) { +#ifdef _MP_GMP + if (!is_small(a)) { + mk_big(b); + mpz_pow_ui(*b.m_ptr, *a.m_ptr, p); + return; + } +#endif +#ifndef _MP_GMP + if (is_small(a)) { + if (a.m_val == 2) { + if (p < 8 * sizeof(int) - 1) { + del(b); + b.m_val = 1 << p; + } + else { + unsigned sz = p/(8 * sizeof(digit_t)) + 1; + unsigned shift = p%(8 * sizeof(digit_t)); + SASSERT(sz > 0); + allocate_if_needed(b, sz); + SASSERT(b.m_ptr->m_capacity >= sz); + b.m_ptr->m_size = sz; + for (unsigned i = 0; i < sz - 1; i++) + b.m_ptr->m_digits[i] = 0; + b.m_ptr->m_digits[sz-1] = 1 << shift; + b.m_val = 1; + } + return; + } + if (a.m_val == 0) { + SASSERT(p != 0); + reset(b); + return; + } + if (a.m_val == 1) { + set(b, 1); + return; + } + } +#endif + // general purpose + unsigned mask = 1; + mpz power; + set(power, a); + set(b, 1); + while (mask <= p) { + if (mask & p) + mul(b, power, b); + mul(power, power, power); + mask = mask << 1; + } + del(power); +} + +template +bool mpz_manager::is_power_of_two(mpz const & a) { + unsigned shift; + return is_power_of_two(a, shift); +} + +template +bool mpz_manager::is_power_of_two(mpz const & a, unsigned & shift) { + if (is_nonpos(a)) + return false; + if (is_small(a)) { + if (::is_power_of_two(a.m_val)) { + shift = ::log2(a.m_val); + return true; + } + else { + return false; + } + } +#ifndef _MP_GMP + mpz_cell * c = a.m_ptr; + unsigned sz = c->m_size; + digit_t * ds = c->m_digits; + for (unsigned i = 0; i < sz - 1; i++) { + if (ds[i] != 0) + return false; + } + digit_t v = ds[sz-1]; + if (!(v & (v - 1)) && v) { + shift = log2(a); + return true; + } + else { + return false; + } +#else + if (mpz_popcount(*a.m_ptr) == 1) { + shift = log2(a); + return true; + } + else { + return false; + } +#endif +} + +// Expand capacity of a +#ifndef _MP_GMP +template +void mpz_manager::ensure_capacity(mpz & a, unsigned capacity) { + if (capacity <= 1) + return; + if (capacity < m_init_cell_capacity) + capacity = m_init_cell_capacity; + if (is_small(a)) { + a.m_ptr = allocate(capacity); + SASSERT(a.m_ptr->m_capacity == capacity); + if (a.m_val == INT_MIN) { + unsigned intmin_sz = m_int_min.m_ptr->m_size; + for (unsigned i = 0; i < intmin_sz; i++) + a.m_ptr->m_digits[i] = m_int_min.m_ptr->m_digits[i]; + a.m_val = -1; + a.m_ptr->m_size = m_int_min.m_ptr->m_size; + } + else if (a.m_val < 0) { + a.m_ptr->m_digits[0] = -a.m_val; + a.m_val = -1; + a.m_ptr->m_size = 1; + } + else { + a.m_ptr->m_digits[0] = a.m_val; + a.m_val = 1; + a.m_ptr->m_size = 1; + } + } + else { + if (a.m_ptr->m_capacity >= capacity) + return; + mpz_cell * new_cell = allocate(capacity); + SASSERT(new_cell->m_capacity == capacity); + unsigned old_sz = a.m_ptr->m_size; + new_cell->m_size = old_sz; + for (unsigned i = 0; i < old_sz; i++) + new_cell->m_digits[i] = a.m_ptr->m_digits[i]; + deallocate(a.m_ptr); + a.m_ptr = new_cell; + } +} + +template +void mpz_manager::normalize(mpz & a) { + mpz_cell * c = a.m_ptr; + digit_t * ds = c->m_digits; + unsigned i = c->m_size; + for (; i > 0; --i) { + if (ds[i-1] != 0) + break; + } + + if (i == 0) { + // a is zero... + reset(a); + return; + } + + if (i == 1 && ds[0] <= INT_MAX) { + // a is small + int val = a.m_val < 0 ? -static_cast(ds[0]) : static_cast(ds[0]); + del(a); + a.m_val = val; + return; + } + // adjust size + c->m_size = i; +} +#endif + +template +void mpz_manager::machine_div2k(mpz & a, unsigned k) { + if (k == 0 || is_zero(a)) + return; + if (is_small(a)) { + if (k < 32) { + int twok = 1 << k; + a.m_val /= twok; + } + else { + a.m_val = 0; + } + return; + } +#ifndef _MP_GMP + unsigned digit_shift = k / (8 * sizeof(digit_t)); + mpz_cell * c = a.m_ptr; + unsigned sz = c->m_size; + if (digit_shift >= sz) { + reset(a); + return; + } + unsigned bit_shift = k % (8 * sizeof(digit_t)); + unsigned comp_shift = (8 * sizeof(digit_t)) - bit_shift; + unsigned new_sz = sz - digit_shift; + SASSERT(new_sz >= 1); + digit_t * ds = c->m_digits; + TRACE("mpz_2k", tout << "bit_shift: " << bit_shift << ", comp_shift: " << comp_shift << ", new_sz: " << new_sz << ", sz: " << sz << "\n";); + if (new_sz < sz) { + unsigned i = 0; + unsigned j = digit_shift; + if (bit_shift != 0) { + for (; i < new_sz - 1; i++, j++) { + ds[i] = ds[j]; + ds[i] >>= bit_shift; + ds[i] |= (ds[j+1] << comp_shift); + } + ds[i] = ds[j]; + ds[i] >>= bit_shift; + } + else { + for (; i < new_sz; i++, j++) { + ds[i] = ds[j]; + } + } + } + else { + SASSERT(new_sz == sz); + SASSERT(bit_shift != 0); + unsigned i = 0; + for (; i < new_sz - 1; i++) { + ds[i] >>= bit_shift; + ds[i] |= (ds[i+1] << comp_shift); + } + ds[i] >>= bit_shift; + } + + c->m_size = new_sz; + normalize(a); +#else + MPZ_BEGIN_CRITICAL(); + mpz_t * arg0; + get_arg<0>(a, arg0); + mpz_tdiv_q_2exp(m_tmp, *arg0, k); + mk_big(a); + mpz_swap(*a.m_ptr, m_tmp); + MPZ_END_CRITICAL(); +#endif +} + +#ifndef _MP_GMP +static void display_bits(std::ostream & out, digit_t a) { + for (unsigned i = 0; i < sizeof(digit_t) * 8; i++) { + if (a % 2 == 0) + out << "0"; + else + out << "1"; + a /= 2; + } +} +#endif + +template +void mpz_manager::mul2k(mpz & a, unsigned k) { + if (k == 0 || is_zero(a)) + return; + if (is_small(a) && k < 32) { + set_i64(a, i64(a) * (static_cast(1) << k)); + return; + } +#ifndef _MP_GMP + TRACE("mpz_mul2k", tout << "mul2k\na: " << to_string(a) << "\nk: " << k << "\n";); + unsigned word_shift = k / (8 * sizeof(digit_t)); + unsigned bit_shift = k % (8 * sizeof(digit_t)); + unsigned old_sz = is_small(a) ? 1 : a.m_ptr->m_size; + unsigned new_sz = old_sz + word_shift + 1; + ensure_capacity(a, new_sz); + TRACE("mpz_mul2k", tout << "word_shift: " << word_shift << "\nbit_shift: " << bit_shift << "\nold_sz: " << old_sz << "\nnew_sz: " << new_sz + << "\na after ensure capacity:\n" << to_string(a) << "\n";); + SASSERT(!is_small(a)); + mpz_cell * cell_a = a.m_ptr; + old_sz = cell_a->m_size; + digit_t * ds = cell_a->m_digits; + for (unsigned i = old_sz; i < new_sz; i++) + ds[i] = 0; + cell_a->m_size = new_sz; + + if (word_shift > 0) { + unsigned j = old_sz; + unsigned i = old_sz + word_shift; + while (j > 0) { + --j; --i; + ds[i] = ds[j]; + } + while (i > 0) { + --i; + ds[i] = 0; + } + } + if (bit_shift > 0) { + DEBUG_CODE({ + for (unsigned i = 0; i < word_shift; i++) { + SASSERT(ds[i] == 0); + } + }); + unsigned comp_shift = (8 * sizeof(digit_t)) - bit_shift; + digit_t prev = 0; + for (unsigned i = word_shift; i < new_sz; i++) { + digit_t new_prev = (ds[i] >> comp_shift); + ds[i] <<= bit_shift; + ds[i] |= prev; + prev = new_prev; + } + } + normalize(a); + TRACE("mpz_mul2k", tout << "mul2k result:\n" << to_string(a) << "\n";); +#else + mpz_t * arg0; + get_arg<0>(a, arg0); + mk_big(a); + mpz_mul_2exp(*a.m_ptr, *arg0, k); +#endif +} + +#ifndef _MP_GMP +COMPILE_TIME_ASSERT(sizeof(digit_t) == 4 || sizeof(digit_t) == 8); +#endif + +template +unsigned mpz_manager::power_of_two_multiple(mpz const & a) { + if (is_zero(a)) + return 0; + if (is_small(a)) { + unsigned r = 0; + int v = a.m_val; +#define COUNT_DIGIT_RIGHT_ZEROS() \ + if (v % (1 << 16) == 0) { \ + r += 16; \ + v /= (1 << 16); \ + } \ + if (v % (1 << 8) == 0) { \ + r += 8; \ + v /= (1 << 8); \ + } \ + if (v % (1 << 4) == 0) { \ + r += 4; \ + v /= (1 << 4); \ + } \ + if (v % (1 << 2) == 0) { \ + r += 2; \ + v /= (1 << 2); \ + } \ + if (v % 2 == 0) { \ + r++; \ + } + COUNT_DIGIT_RIGHT_ZEROS(); + return r; + } +#ifndef _MP_GMP + mpz_cell * c = a.m_ptr; + unsigned sz = c->m_size; + unsigned r = 0; + digit_t * source = c->m_digits; + for (unsigned i = 0; i < sz; i++) { + if (source[i] != 0) { + digit_t v = source[i]; + if (sizeof(digit_t) == 8) { + // TODO: we can remove this if after we move to MPN + // In MPN the digit_t is always an unsigned integer + if (static_cast(v) % (static_cast(1) << 32) == 0) { + r += 32; + v = static_cast(static_cast(v) / (static_cast(1) << 32)); + } + } + COUNT_DIGIT_RIGHT_ZEROS(); + return r; + } + r += (8 * sizeof(digit_t)); + } + return r; +#else + return mpz_scan1(*a.m_ptr, 0); +#endif +} + +template +unsigned mpz_manager::log2(mpz const & a) { + if (is_nonpos(a)) + return 0; + if (is_small(a)) + return ::log2(a.m_val); +#ifndef _MP_GMP + COMPILE_TIME_ASSERT(sizeof(digit_t) == 8 || sizeof(digit_t) == 4); + mpz_cell * c = a.m_ptr; + unsigned sz = c->m_size; + digit_t * ds = c->m_digits; + if (sizeof(digit_t) == 8) + return (sz - 1)*64 + uint64_log2(ds[sz-1]); + else + return (sz - 1)*32 + ::log2(static_cast(ds[sz-1])); +#else + unsigned r = mpz_sizeinbase(*a.m_ptr, 2); + SASSERT(r > 0); + return r - 1; +#endif +} + +template +unsigned mpz_manager::mlog2(mpz const & a) { + if (is_nonneg(a)) + return 0; + if (is_small(a)) + return ::log2(-a.m_val); +#ifndef _MP_GMP + COMPILE_TIME_ASSERT(sizeof(digit_t) == 8 || sizeof(digit_t) == 4); + mpz_cell * c = a.m_ptr; + unsigned sz = c->m_size; + digit_t * ds = c->m_digits; + if (sizeof(digit_t) == 8) + return (sz - 1)*64 + uint64_log2(ds[sz-1]); + else + return (sz - 1)*32 + ::log2(static_cast(ds[sz-1])); +#else + mpz_neg(m_tmp, *a.m_ptr); + unsigned r = mpz_sizeinbase(m_tmp, 2); + SASSERT(r > 0); + return r - 1; +#endif +} + +template +unsigned mpz_manager::bitsize(mpz const & a) { + if (is_nonneg(a)) + return log2(a) + 1; + else + return mlog2(a) + 1; +} + +template +bool mpz_manager::is_perfect_square(mpz const & a, mpz & root) { + if (is_neg(a)) + return false; + reset(root); + if (is_zero(a)) { + return true; + } + if (is_one(a)) { + set(root, 1); + return true; + } + + mpz lo, hi, mid, sq_lo, sq_hi, sq_mid; + set(lo, 1); + set(hi, a); + set(sq_lo, 1); + mul(hi, hi, sq_hi); + bool result; + // lo*lo <= *this < hi*hi + while (true) { + SASSERT(lt(lo, hi)); + SASSERT(le(sq_lo, a) && lt(a, sq_hi)); + if (eq(sq_lo, a)) { + set(root, lo); + result = true; + break; + } + + mpz & tmp = mid; + add(lo, mpz(1), tmp); + if (eq(tmp, hi)) { + set(root, hi); + result = false; + break; + } + + add(hi, lo, tmp); + div(tmp, mpz(2), mid); + + SASSERT(lt(lo, mid) && lt(mid, hi)); + + mul(mid, mid, sq_mid); + + if (gt(sq_mid, a)) { + set(hi, mid); + set(sq_hi, sq_mid); + } + else { + set(lo, mid); + set(sq_lo, sq_mid); + } + } + del(lo); + del(hi); + del(mid); + del(sq_lo); + del(sq_hi); + del(sq_mid); + return result; +} + + +static unsigned div_l(unsigned k, unsigned n) { + return k/n; +} + +static unsigned div_u(unsigned k, unsigned n) { + return k%n == 0 ? k/n : k/n + 1; +} + +template +bool mpz_manager::root(mpz & a, unsigned n) { + SASSERT(n % 2 != 0 || is_nonneg(a)); + if (is_zero(a)) { + return true; // precise + } + + // Initial approximation + // + // We have that: + // a > 0 -> 2^{log2(a)} <= a <= 2^{(log2(a) + 1)} + // a < 0 -> -2^{log2(a) + 1} <= a <= -2^{log2(a)} + // + // Thus + // a > 0 -> 2^{div_l(log2(a), n)} <= a^{1/n} <= 2^{div_u(log2(a) + 1, n)} + // a < 0 -> -2^{div_u(log2(a) + 1, n)} <= a^{1/n} <= -2^{div_l(log2(a), n)} + // + mpz lower; + mpz upper; + mpz mid; + mpz mid_n; + + if (is_pos(a)) { + unsigned k = log2(a); + power(mpz(2), div_l(k, n), lower); + power(mpz(2), div_u(k + 1, n), upper); + } + else { + unsigned k = mlog2(a); + power(mpz(2), div_u(k + 1, n), lower); + power(mpz(2), div_l(k, n), upper); + neg(lower); + neg(upper); + } + + bool result; + SASSERT(le(lower, upper)); + if (eq(lower, upper)) { + swap(a, lower); + result = true; + } + else { + // Refine using bisection. TODO: use Newton's method if this is a bottleneck + while (true) { + add(upper, lower, mid); + machine_div2k(mid, 1); + TRACE("mpz", tout << "upper: "; display(tout, upper); tout << "\nlower: "; display(tout, lower); tout << "\nmid: "; display(tout, mid); tout << "\n";); + power(mid, n, mid_n); + if (eq(mid_n, a)) { + swap(a, mid); + result = true; + break; + } + if (eq(mid, lower) || eq(mid, upper)) { + swap(a, upper); + result = false; + break; + } + if (lt(mid_n, a)) { + // new lower bound + swap(mid, lower); + } + else { + SASSERT(lt(a, mid_n)); + // new upper bound + swap(mid, upper); + } + } + } + del(lower); + del(upper); + del(mid); + del(mid_n); + return result; +} + +template +bool mpz_manager::decompose(mpz const & a, svector & digits) { + digits.reset(); + if (is_small(a)) { + if (a.m_val < 0) { + digits.push_back(-a.m_val); + return true; + } + else { + digits.push_back(a.m_val); + return false; + } + } + else { +#ifndef _MP_GMP + mpz_cell * cell_a = a.m_ptr; + unsigned sz = cell_a->m_size; + for (unsigned i = 0; i < sz; i++) { + digits.push_back(cell_a->m_digits[i]); + } + return a.m_val < 0; +#else + bool r = is_neg(a); + mpz_set(m_tmp, *a.m_ptr); + mpz_abs(m_tmp, m_tmp); + while (mpz_sgn(m_tmp) != 0) { + mpz_tdiv_r_2exp(m_tmp2, m_tmp, 32); + unsigned v = mpz_get_ui(m_tmp2); + digits.push_back(v); + mpz_tdiv_q_2exp(m_tmp, m_tmp, 32); + } + return r; +#endif + } +} + +template +bool mpz_manager::divides(mpz const & a, mpz const & b) { + _scoped_numeral > tmp(*this); + bool r; + if (is_zero(a)) { + // I assume 0 | 0. + // Remark a|b is a shorthand for (exists x. a x = b) + // If b is zero, any x will do. If b != 0, then a does not divide b + r = is_zero(b); + } + else { + rem(b, a, tmp); + r = is_zero(tmp); + } + STRACE("divides", tout << "[mpz] Divisible["; display(tout, b); tout << ", "; display(tout, a); tout << "] == " << (r?"True":"False") << "\n";); + TRACE("divides_bug", tout << "tmp: "; display(tout, tmp); tout << "\n";); + return r; +} + +template class mpz_manager; +template class mpz_manager; diff --git a/lib/mpz.h b/lib/mpz.h new file mode 100644 index 000000000..72faea771 --- /dev/null +++ b/lib/mpz.h @@ -0,0 +1,798 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + mpz.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-06-17. + +Revision History: + +--*/ +#ifndef _MPZ_H_ +#define _MPZ_H_ + +#include +#include +#include"util.h" +#include"small_object_allocator.h" +#include"trace.h" +#include"scoped_numeral.h" +#include"scoped_numeral_vector.h" +#include"z3_omp.h" + +unsigned u_gcd(unsigned u, unsigned v); +uint64 u64_gcd(uint64 u, uint64 v); + +#ifdef _MP_GMP +typedef unsigned digit_t; +#endif + +#ifdef _MSC_VER +#pragma warning(disable : 4200) +#endif + +template class mpz_manager; +template class mpq_manager; + +#if !defined(_MP_GMP) && !defined(_MP_MSBIGNUM) && !defined(_MP_INTERNAL) +#ifdef _WINDOWS +#define _MP_INTERNAL +#else +#define _MP_GMP +#endif +#endif + +#if defined(_MP_MSBIGNUM) +typedef size_t digit_t; +#elif defined(_MP_INTERNAL) +typedef unsigned int digit_t; +#endif + +#ifndef _MP_GMP +class mpz_cell { + unsigned m_size; + unsigned m_capacity; + digit_t m_digits[0]; + friend class mpz_manager; + friend class mpz_manager; +}; +#else +#include +#endif + +/** + \brief Multi-precision integer. + + If m_ptr == 0, the it is a small number and the value is stored at m_val. + Otherwise, m_val contains the sign (-1 negative, 1 positive), and m_ptr points to a mpz_cell that + store the value. <<< This last statement is true only in Windows. +*/ +class mpz { + int m_val; +#ifndef _MP_GMP + mpz_cell * m_ptr; +#else + mpz_t * m_ptr; +#endif + friend class mpz_manager; + friend class mpz_manager; + friend class mpq_manager; + friend class mpq_manager; + friend class mpq; + friend class mpbq; + friend class mpbq_manager; + mpz & operator=(mpz const & other) { UNREACHABLE(); return *this; } +public: + mpz(int v):m_val(v), m_ptr(0) {} + mpz():m_val(0), m_ptr(0) {} + void swap(mpz & other) { + std::swap(m_val, other.m_val); + std::swap(m_ptr, other.m_ptr); + } +}; + +inline void swap(mpz & m1, mpz & m2) { m1.swap(m2); } + +template +class mpz_manager { + small_object_allocator m_allocator; + omp_nest_lock_t m_lock; +#define MPZ_BEGIN_CRITICAL() if (SYNCH) omp_set_nest_lock(&m_lock); +#define MPZ_END_CRITICAL() if (SYNCH) omp_unset_nest_lock(&m_lock); + +#ifndef _MP_GMP + unsigned m_init_cell_capacity; + mpz_cell * m_tmp[2]; + mpz_cell * m_arg[2]; + mpz m_int_min; + + static unsigned cell_size(unsigned capacity) { return sizeof(mpz_cell) + sizeof(digit_t) * capacity; } + + mpz_cell * allocate(unsigned capacity) { + SASSERT(capacity >= m_init_cell_capacity); + mpz_cell * cell = reinterpret_cast(m_allocator.allocate(cell_size(capacity))); + cell->m_capacity = capacity; + return cell; + } + + // make sure that n is a big number and has capacity equal to at least c. + void allocate_if_needed(mpz & n, unsigned c) { + if (c < m_init_cell_capacity) + c = m_init_cell_capacity; + if (is_small(n)) { + n.m_val = 1; + n.m_ptr = allocate(c); + n.m_ptr->m_capacity = c; + } + else if (capacity(n) < c) { + deallocate(n.m_ptr); + n.m_val = 1; + n.m_ptr = allocate(c); + n.m_ptr->m_capacity = c; + } + } + + void deallocate(mpz_cell * ptr) { + m_allocator.deallocate(cell_size(ptr->m_capacity), ptr); + } + + /** + \brief Make sure that m_tmp[IDX] can hold the given number of digits + */ + template + void ensure_tmp_capacity(unsigned capacity) { + if (m_tmp[IDX]->m_capacity >= capacity) + return; + deallocate(m_tmp[IDX]); + unsigned new_capacity = (3 * capacity + 1) >> 1; + m_tmp[IDX] = allocate(new_capacity); + SASSERT(m_tmp[IDX]->m_capacity >= capacity); + } + + // Expand capacity of a while preserving its content. + void ensure_capacity(mpz & a, unsigned sz); + + void normalize(mpz & a); +#else + // GMP code + mpz_t m_tmp, m_tmp2; + mpz_t m_two32; + mpz_t * m_arg[2]; + mpz_t m_uint64_max; + mpz_t m_int64_max; + + mpz_t * allocate() { + mpz_t * cell = reinterpret_cast(m_allocator.allocate(sizeof(mpz_t))); + mpz_init(*cell); + return cell; + } + + void deallocate(mpz_t * ptr) { mpz_clear(*ptr); m_allocator.deallocate(sizeof(mpz_t), ptr); } +#endif + mpz m_two64; + + /** + \brief Set \c a with the value stored at m_tmp[IDX], and the given sign. + \c sz is an overapproximation of the the size of the number stored at \c tmp. + */ + template + void set(mpz & a, int sign, unsigned sz); + + static int64 i64(mpz const & a) { return static_cast(a.m_val); } + + void set_big_i64(mpz & c, int64 v); + + void set_i64(mpz & c, int64 v) { + if (v >= INT_MIN && v <= INT_MAX) { + del(c); + c.m_val = static_cast(v); + } + else { + MPZ_BEGIN_CRITICAL(); + set_big_i64(c, v); + MPZ_END_CRITICAL(); + } + } + + void set_big_ui64(mpz & c, uint64 v); + +#ifndef _MP_GMP + static unsigned capacity(mpz const & c) { return c.m_ptr->m_capacity; } + + static unsigned size(mpz const & c) { return c.m_ptr->m_size; } + + static digit_t * digits(mpz const & c) { return c.m_ptr->m_digits; } + + template + void get_sign_cell(mpz const & a, int & sign, mpz_cell * & cell) { + if (is_small(a)) { + if (a.m_val == INT_MIN) { + sign = -1; + cell = m_int_min.m_ptr; + } + else { + cell = m_arg[IDX]; + SASSERT(cell->m_size == 1); + if (a.m_val < 0) { + sign = -1; + cell->m_digits[0] = -a.m_val; + } + else { + sign = 1; + cell->m_digits[0] = a.m_val; + } + } + } + else { + sign = a.m_val; + cell = a.m_ptr; + } + } +#else + // GMP code + + template + void get_arg(mpz const & a, mpz_t * & result) { + if (is_small(a)) { + result = m_arg[IDX]; + mpz_set_si(*result, a.m_val); + } + else { + result = a.m_ptr; + } + } + + void mk_big(mpz & a) { + if (a.m_ptr == 0) + a.m_ptr = allocate(); + } +#endif + +#ifndef _MP_GMP + template + void big_add_sub(mpz const & a, mpz const & b, mpz & c); +#endif + + void big_add(mpz const & a, mpz const & b, mpz & c); + + void big_sub(mpz const & a, mpz const & b, mpz & c); + + void big_mul(mpz const & a, mpz const & b, mpz & c); + + void big_set(mpz & target, mpz const & source); + +#ifndef _MP_GMP + +#define QUOT_ONLY 0 +#define REM_ONLY 1 +#define QUOT_AND_REM 2 +#define qr_mode int + + template + void quot_rem_core(mpz const & a, mpz const & b, mpz & q, mpz & r); +#endif + + void big_div_rem(mpz const & a, mpz const & b, mpz & q, mpz & r); + + void big_div(mpz const & a, mpz const & b, mpz & c); + + void big_rem(mpz const & a, mpz const & b, mpz & c); + + int big_compare(mpz const & a, mpz const & b); + + unsigned size_info(mpz const & a); + struct sz_lt; + +public: + static bool precise() { return true; } + static bool field() { return false; } + + typedef mpz numeral; + + mpz_manager(); + + ~mpz_manager(); + + static bool is_small(mpz const & a) { return a.m_ptr == 0; } + + static mpz mk_z(int val) { return mpz(val); } + + void del(mpz & a) { + if (a.m_ptr != 0) { + MPZ_BEGIN_CRITICAL(); + deallocate(a.m_ptr); + MPZ_END_CRITICAL(); + a.m_ptr = 0; + } + } + + void add(mpz const & a, mpz const & b, mpz & c) { + STRACE("mpz", tout << "[mpz] " << to_string(a) << " + " << to_string(b) << " == ";); + if (is_small(a) && is_small(b)) { + set_i64(c, i64(a) + i64(b)); + } + else { + MPZ_BEGIN_CRITICAL(); + big_add(a, b, c); + MPZ_END_CRITICAL(); + } + STRACE("mpz", tout << to_string(c) << "\n";); + } + + void sub(mpz const & a, mpz const & b, mpz & c) { + STRACE("mpz", tout << "[mpz] " << to_string(a) << " - " << to_string(b) << " == ";); + if (is_small(a) && is_small(b)) { + set_i64(c, i64(a) - i64(b)); + } + else { + MPZ_BEGIN_CRITICAL(); + big_sub(a, b, c); + MPZ_END_CRITICAL(); + } + STRACE("mpz", tout << to_string(c) << "\n";); + } + + void inc(mpz & a) { add(a, mpz(1), a); } + + void dec(mpz & a) { add(a, mpz(-1), a); } + + void mul(mpz const & a, mpz const & b, mpz & c) { + STRACE("mpz", tout << "[mpz] " << to_string(a) << " * " << to_string(b) << " == ";); + if (is_small(a) && is_small(b)) { + set_i64(c, i64(a) * i64(b)); + } + else { + MPZ_BEGIN_CRITICAL(); + big_mul(a, b, c); + MPZ_END_CRITICAL(); + } + STRACE("mpz", tout << to_string(c) << "\n";); + } + + // d <- a + b*c + void addmul(mpz const & a, mpz const & b, mpz const & c, mpz & d) { + if (is_one(b)) { + add(a, c, d); + } + else if (is_minus_one(b)) { + sub(a, c, d); + } + else { + mpz tmp; + mul(b,c,tmp); + add(a,tmp,d); + del(tmp); + } + } + + + // d <- a + b*c + void submul(mpz const & a, mpz const & b, mpz const & c, mpz & d) { + if (is_one(b)) { + sub(a, c, d); + } + else if (is_minus_one(b)) { + add(a, c, d); + } + else { + mpz tmp; + mul(b,c,tmp); + sub(a,tmp,d); + del(tmp); + } + } + + + void machine_div_rem(mpz const & a, mpz const & b, mpz & q, mpz & r) { + STRACE("mpz", tout << "[mpz-ext] divrem(" << to_string(a) << ", " << to_string(b) << ") == ";); + if (is_small(a) && is_small(b)) { + int64 _a = i64(a); + int64 _b = i64(b); + set_i64(q, _a / _b); + set_i64(r, _a % _b); + } + else { + MPZ_BEGIN_CRITICAL(); + big_div_rem(a, b, q, r); + MPZ_END_CRITICAL(); + } + STRACE("mpz", tout << "(" << to_string(q) << ", " << to_string(r) << ")\n";); + } + + void machine_div(mpz const & a, mpz const & b, mpz & c) { + STRACE("mpz", tout << "[mpz-ext] machine-div(" << to_string(a) << ", " << to_string(b) << ") == ";); + if (is_small(a) && is_small(b)) { + set_i64(c, i64(a) / i64(b)); + } + else { + MPZ_BEGIN_CRITICAL(); + big_div(a, b, c); + MPZ_END_CRITICAL(); + } + STRACE("mpz", tout << to_string(c) << "\n";); + } + + void rem(mpz const & a, mpz const & b, mpz & c) { + STRACE("mpz", tout << "[mpz-ext] rem(" << to_string(a) << ", " << to_string(b) << ") == ";); + if (is_small(a) && is_small(b)) { + set_i64(c, i64(a) % i64(b)); + } + else { + MPZ_BEGIN_CRITICAL(); + big_rem(a, b, c); + MPZ_END_CRITICAL(); + } + STRACE("mpz", tout << to_string(c) << "\n";); + } + + void div(mpz const & a, mpz const & b, mpz & c) { + STRACE("mpz", tout << "[mpz-ext] div(" << to_string(a) << ", " << to_string(b) << ") == ";); + if (is_neg(a)) { + mpz tmp; + machine_div_rem(a, b, c, tmp); + if (!is_zero(tmp)) { + if (is_neg(b)) + add(c, mk_z(1), c); + else + sub(c, mk_z(1), c); + } + del(tmp); + } + else { + machine_div(a, b, c); + } + STRACE("mpz", tout << to_string(c) << "\n";); + } + + void mod(mpz const & a, mpz const & b, mpz & c) { + STRACE("mpz", tout << "[mpz-ext] mod(" << to_string(a) << ", " << to_string(b) << ") == ";); + rem(a, b, c); + if (is_neg(c)) { + if (is_pos(b)) + add(c, b, c); + else + sub(c, b, c); + } + STRACE("mpz", tout << to_string(c) << "\n";); + } + + void neg(mpz & a) { + STRACE("mpz", tout << "[mpz] 0 - " << to_string(a) << " == ";); + if (is_small(a) && a.m_val == INT_MIN) { + // neg(INT_MIN) is not a small int + set_big_i64(a, - static_cast(INT_MIN)); + return; + } +#ifndef _MP_GMP + a.m_val = -a.m_val; +#else + if (is_small(a)) { + a.m_val = -a.m_val; + } + else { + mpz_neg(*a.m_ptr, *a.m_ptr); + } +#endif + STRACE("mpz", tout << to_string(a) << "\n";); + } + + void abs(mpz & a) { + if (is_small(a)) { + if (a.m_val < 0) { + if (a.m_val == INT_MIN) { + // abs(INT_MIN) is not a small int + set_big_i64(a, - static_cast(INT_MIN)); + } + else + a.m_val = -a.m_val; + } + } + else { +#ifndef _MP_GMP + a.m_val = 1; +#else + mpz_abs(*a.m_ptr, *a.m_ptr); +#endif + } + } + + static bool is_pos(mpz const & a) { +#ifndef _MP_GMP + return a.m_val > 0; +#else + if (is_small(a)) + return a.m_val > 0; + else + return mpz_sgn(*a.m_ptr) > 0; +#endif + } + + static bool is_neg(mpz const & a) { +#ifndef _MP_GMP + return a.m_val < 0; +#else + if (is_small(a)) + return a.m_val < 0; + else + return mpz_sgn(*a.m_ptr) < 0; +#endif + } + + static bool is_zero(mpz const & a) { +#ifndef _MP_GMP + return a.m_val == 0; +#else + if (is_small(a)) + return a.m_val == 0; + else + return mpz_sgn(*a.m_ptr) == 0; +#endif + } + + static int sign(mpz const & a) { +#ifndef _MP_GMP + return a.m_val; +#else + if (is_small(a)) + return a.m_val; + else + return mpz_sgn(*a.m_ptr); +#endif + } + + static bool is_nonpos(mpz const & a) { return !is_pos(a); } + + static bool is_nonneg(mpz const & a) { return !is_neg(a); } + + bool eq(mpz const & a, mpz const & b) { + if (is_small(a) && is_small(b)) { + return a.m_val == b.m_val; + } + else { + MPZ_BEGIN_CRITICAL(); + bool res = big_compare(a, b) == 0; + MPZ_END_CRITICAL(); + return res; + } + } + + bool lt(mpz const & a, mpz const & b) { + if (is_small(a) && is_small(b)) { + return a.m_val < b.m_val; + } + else { + MPZ_BEGIN_CRITICAL(); + bool res = big_compare(a, b) < 0; + MPZ_END_CRITICAL(); + return res; + } + } + + bool neq(mpz const & a, mpz const & b) { return !eq(a, b); } + + bool gt(mpz const & a, mpz const & b) { return lt(b, a); } + + bool ge(mpz const & a, mpz const & b) { return !lt(a, b); } + + bool le(mpz const & a, mpz const & b) { return !lt(b, a); } + + void gcd(mpz const & a, mpz const & b, mpz & c); + + void gcd(unsigned sz, mpz const * as, mpz & g); + + /** + \brief Extended Euclid: + r1*a + r2*b = g + */ + void gcd(mpz const & r1, mpz const & r2, mpz & a, mpz & b, mpz & g); + + void lcm(mpz const & a, mpz const & b, mpz & c); + + /** + \brief Return true if a | b + */ + bool divides(mpz const & a, mpz const & b); + + // not a field + void inv(mpz & a) { + SASSERT(false); + } + + void bitwise_or(mpz const & a, mpz const & b, mpz & c); + + void bitwise_and(mpz const & a, mpz const & b, mpz & c); + + void bitwise_xor(mpz const & a, mpz const & b, mpz & c); + + void bitwise_not(unsigned sz, mpz const & a, mpz & c); + + void set(mpz & target, mpz const & source) { + if (is_small(source)) { + del(target); + target.m_val = source.m_val; + } + else { + MPZ_BEGIN_CRITICAL(); + big_set(target, source); + MPZ_END_CRITICAL(); + } + } + + void set(mpz & a, int val) { + del(a); + a.m_val = val; + } + + void set(mpz & a, unsigned val) { + if (val <= INT_MAX) + set(a, static_cast(val)); + else + set(a, static_cast(static_cast(val))); + } + + void set(mpz & a, char const * val); + + void set(mpz & a, int64 val) { + set_i64(a, val); + } + + void set(mpz & a, uint64 val) { + if (val < INT_MAX) { + del(a); + a.m_val = static_cast(val); + } + else { + MPZ_BEGIN_CRITICAL(); + set_big_ui64(a, val); + MPZ_END_CRITICAL(); + } + } + + void set(mpz & target, unsigned sz, digit_t const * digits); + + void reset(mpz & a) { + del(a); + a.m_val = 0; + } + + void swap(mpz & a, mpz & b) { + std::swap(a.m_val, b.m_val); + std::swap(a.m_ptr, b.m_ptr); + } + + bool is_uint64(mpz const & a) const; + + bool is_int64(mpz const & a) const; + + uint64 get_uint64(mpz const & a) const; + + int64 get_int64(mpz const & a) const; + + double get_double(mpz const & a) const; + + std::string to_string(mpz const & a) const; + + void display(std::ostream & out, mpz const & a) const; + + /** + \brief Display mpz number in SMT 2.0 format. + If decimal == true, then ".0" is appended. + */ + void display_smt2(std::ostream & out, mpz const & a, bool decimal) const; + + static unsigned hash(mpz const & a); + + static bool is_one(mpz const & a) { +#ifndef _MP_GMP + return is_small(a) && a.m_val == 1; +#else + if (is_small(a)) + return a.m_val == 1; + return mpz_cmp_si(*a.m_ptr, 1) == 0; +#endif + } + + static bool is_minus_one(mpz const & a) { +#ifndef _MP_GMP + return is_small(a) && a.m_val == -1; +#else + if (is_small(a)) + return a.m_val == -1; + return mpz_cmp_si(*a.m_ptr, -1) == 0; +#endif + } + + void power(mpz const & a, unsigned p, mpz & b); + + bool is_power_of_two(mpz const & a); + + bool is_power_of_two(mpz const & a, unsigned & shift); + + void machine_div2k(mpz & a, unsigned k); + + void machine_div2k(mpz const & a, unsigned k, mpz & r) { set(r, a); machine_div2k(r, k); } + + void mul2k(mpz & a, unsigned k); + + void mul2k(mpz const & a, unsigned k, mpz & r) { set(r, a); mul2k(r, k); } + + /** + \brief Return largest k s.t. n is a multiple of 2^k + */ + unsigned power_of_two_multiple(mpz const & n); + + /** + \brief Return the position of the most significant bit. + Return 0 if the number is negative + */ + unsigned log2(mpz const & n); + + /** + \brief log2(-n) + Return 0 if the number is nonegative + */ + unsigned mlog2(mpz const & n); + + /** + \brief Return the bit-size of n. This method is mainly used for collecting statistics. + */ + unsigned bitsize(mpz const & n); + + /** + \brief Return true if the number is a perfect square, and + store the square root in 'root'. + If the number n is positive and the result is false, then + root will contain the smallest integer r such that r*r > n. + */ + bool is_perfect_square(mpz const & a, mpz & root); + + /** + \brief Return the biggest k s.t. 2^k <= a. + + \remark Return 0 if a is not positive. + */ + unsigned prev_power_of_two(mpz const & a) { return log2(a); } + + /** + \brief Return true if a^{1/n} is an integer, and store the result in a. + Otherwise return false, and update a with the smallest + integer r such that r*r > n. + + \remark This method assumes that if n is even, then a is nonegative + */ + bool root(mpz & a, unsigned n); + bool root(mpz const & a, unsigned n, mpz & r) { set(r, a); return root(r, n); } + + bool is_even(mpz const & a) { + if (is_small(a)) + return !(a.m_val & 0x1); +#ifndef _MP_GMP + return !(0x1 & digits(a)[0]); +#else + return mpz_even_p(*a.m_ptr); +#endif + } + + bool is_odd(mpz const & n) { return !is_even(n); } + + // Store the digits of n into digits, and return the sign. + bool decompose(mpz const & n, svector & digits); +}; + +typedef mpz_manager synch_mpz_manager; +typedef mpz_manager unsynch_mpz_manager; + +typedef _scoped_numeral scoped_mpz; +typedef _scoped_numeral scoped_synch_mpz; +typedef _scoped_numeral_vector scoped_mpz_vector; + +#endif /* _MPZ_H_ */ + diff --git a/lib/mpzzp.h b/lib/mpzzp.h new file mode 100644 index 000000000..a81c5d204 --- /dev/null +++ b/lib/mpzzp.h @@ -0,0 +1,285 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + mpzzp.h + +Abstract: + + Combines Z ring, GF(p) finite field, and Z_p ring (when p is not a prime) + in a single manager; + + That is, the manager may be dynamically configured + to be Z Ring, GF(p), etc. + +Author: + + Leonardo 2012-01-17. + +Revision History: + + This code is based on mpzp.h. + In the future, it will replace it. + +--*/ +#ifndef _MPZZP_H_ +#define _MPZZP_H_ + +#include "mpz.h" + +class mpzzp_manager { + typedef unsynch_mpz_manager numeral_manager; + numeral_manager & m_manager; + + bool m_z; + // instead the usual [0..p) we will keep the numbers in [lower, upper] + mpz m_p, m_lower, m_upper; + bool m_p_prime; + mpz m_inv_tmp1, m_inv_tmp2, m_inv_tmp3; + mpz m_div_tmp; + + bool is_p_normalized_core(mpz const & x) const { + return m().ge(x, m_lower) && m().le(x, m_upper); + } + + void setup_p() { + SASSERT(m().is_pos(m_p) && !m().is_one(m_p)); + bool even = m().is_even(m_p); + m().div(m_p, 2, m_upper); + m().set(m_lower, m_upper); + m().neg(m_lower); + if (even) { + m().inc(m_lower); + } + TRACE("mpzzp", tout << "lower: " << m_manager.to_string(m_lower) << ", upper: " << m_manager.to_string(m_upper) << "\n";); + } + + void p_normalize_core(mpz & x) { + SASSERT(!m_z); + m().rem(x, m_p, x); + if (m().gt(x, m_upper)) { + m().sub(x, m_p, x); + } else { + if (m().lt(x, m_lower)) { + m().add(x, m_p, x); + } + } + SASSERT(is_p_normalized(x)); + } + +public: + typedef mpz numeral; + static bool precise() { return true; } + bool field() { return !m_z && m_p_prime; } + bool finite() const { return !m_z; } + bool modular() const { return !m_z; } + + mpzzp_manager(numeral_manager & _m): + m_manager(_m), + m_z(true) { + } + + mpzzp_manager(numeral_manager & _m, mpz const & p, bool prime = true): + m_manager(_m), + m_z(false) { + m().set(m_p, p); + setup_p(); + } + + mpzzp_manager(numeral_manager & _m, uint64 p, bool prime = true): + m_manager(_m), + m_z(false) { + m().set(m_p, p); + setup_p(); + } + + ~mpzzp_manager() { + m().del(m_p); + m().del(m_lower); + m().del(m_upper); + m().del(m_inv_tmp1); + m().del(m_inv_tmp2); + m().del(m_inv_tmp3); + m().del(m_div_tmp); + } + + bool is_p_normalized(mpz const & x) const { + return m_z || is_p_normalized_core(x); + } + + void p_normalize(mpz & x) { + if (!m_z) + p_normalize_core(x); + SASSERT(is_p_normalized(x)); + } + + numeral_manager & m() const { return m_manager; } + + mpz const & p() const { return m_p; } + + void set_z() { m_z = true; } + void set_zp(mpz const & new_p) { m_z = false; m_p_prime = true; m().set(m_p, new_p); setup_p(); } + void set_zp(uint64 new_p) { m_z = false; m_p_prime = true; m().set(m_p, new_p); setup_p(); } + // p = p^2 + void set_p_sq() { SASSERT(!m_z); m_p_prime = false; m().mul(m_p, m_p, m_p); setup_p(); } + void set_zp_swap(mpz & new_p) { SASSERT(!m_z); m().swap(m_p, new_p); setup_p(); } + + void reset(mpz & a) { m().reset(a); } + bool is_small(mpz const & a) { return m().is_small(a); } + void del(mpz & a) { m().del(a); } + void neg(mpz & a) { m().neg(a); p_normalize(a); } + void abs(mpz & a) { m().abs(a); p_normalize(a); } + bool is_zero(mpz const & a) { SASSERT(is_p_normalized(a)); return numeral_manager::is_zero(a); } + bool is_one(mpz const & a) { SASSERT(is_p_normalized(a)); return numeral_manager::is_one(a); } + bool is_pos(mpz const & a) { SASSERT(is_p_normalized(a)); return numeral_manager::is_pos(a); } + bool is_neg(mpz const & a) { SASSERT(is_p_normalized(a)); return numeral_manager::is_neg(a); } + bool is_nonpos(mpz const & a) { SASSERT(is_p_normalized(a)); return numeral_manager::is_nonpos(a); } + bool is_nonneg(mpz const & a) { SASSERT(is_p_normalized(a)); return numeral_manager::is_nonneg(a); } + bool is_minus_one(mpz const & a) { SASSERT(is_p_normalized(a)); return numeral_manager::is_minus_one(a); } + bool eq(mpz const & a, mpz const & b) { SASSERT(is_p_normalized(a) && is_p_normalized(b)); return m().eq(a, b); } + bool lt(mpz const & a, mpz const & b) { SASSERT(is_p_normalized(a) && is_p_normalized(b)); return m().lt(a, b); } + bool le(mpz const & a, mpz const & b) { SASSERT(is_p_normalized(a) && is_p_normalized(b)); return m().le(a, b); } + bool gt(mpz const & a, mpz const & b) { SASSERT(is_p_normalized(a) && is_p_normalized(b)); return m().gt(a, b); } + bool ge(mpz const & a, mpz const & b) { SASSERT(is_p_normalized(a) && is_p_normalized(b)); return m().ge(a, b); } + std::string to_string(mpz const & a) const { + SASSERT(is_p_normalized(a)); + return m().to_string(a); + } + void display(std::ostream & out, mpz const & a) const { m().display(out, a); } + void add(mpz const & a, mpz const & b, mpz & c) { SASSERT(is_p_normalized(a) && is_p_normalized(b)); m().add(a, b, c); p_normalize(c); } + void sub(mpz const & a, mpz const & b, mpz & c) { SASSERT(is_p_normalized(a) && is_p_normalized(b)); m().sub(a, b, c); p_normalize(c); } + void inc(mpz & a) { SASSERT(is_p_normalized(a)); m().inc(a); p_normalize(a); } + void dec(mpz & a) { SASSERT(is_p_normalized(a)); m().dec(a); p_normalize(a); } + void mul(mpz const & a, mpz const & b, mpz & c) { SASSERT(is_p_normalized(a) && is_p_normalized(b)); m().mul(a, b, c); p_normalize(c); } + void addmul(mpz const & a, mpz const & b, mpz const & c, mpz & d) { + SASSERT(is_p_normalized(a) && is_p_normalized(b) && is_p_normalized(c)); m().addmul(a, b, c, d); p_normalize(d); + } + // d <- a - b*c + void submul(mpz const & a, mpz const & b, mpz const & c, mpz & d) { + SASSERT(is_p_normalized(a)); + SASSERT(is_p_normalized(b)); + SASSERT(is_p_normalized(c)); + m().submul(a, b, c, d); + p_normalize(d); + } + + void inv(mpz & a) { + if (m_z) { + UNREACHABLE(); + } + else { + SASSERT(!is_zero(a)); + // eulers theorem a^(p - 2), but gcd could be more efficient + // a*t1 + p*t2 = 1 => a*t1 = 1 (mod p) => t1 is the inverse (t3 == 1) + TRACE("mpzp_inv_bug", tout << "a: " << m().to_string(a) << ", p: " << m().to_string(m_p) << "\n";); + p_normalize(a); + TRACE("mpzp_inv_bug", tout << "after normalization a: " << m().to_string(a) << "\n";); + m().gcd(a, m_p, m_inv_tmp1, m_inv_tmp2, m_inv_tmp3); + TRACE("mpzp_inv_bug", tout << "tmp1: " << m().to_string(m_inv_tmp1) << "\ntmp2: " << m().to_string(m_inv_tmp2) + << "\ntmp3: " << m().to_string(m_inv_tmp3) << "\n";); + p_normalize(m_inv_tmp1); + m().swap(a, m_inv_tmp1); + SASSERT(m().is_one(m_inv_tmp3)); // otherwise p is not prime and inverse is not defined + } + } + + void swap(mpz & a, mpz & b) { + SASSERT(is_p_normalized(a) && is_p_normalized(b)); + m().swap(a, b); + } + + bool divides(mpz const & a, mpz const & b) { return (field() && !is_zero(a)) || m().divides(a, b); } + + // a/b = a*inv(b) + void div(mpz const & a, mpz const & b, mpz & c) { + if (m_z) { + return m().div(a, b, c); + } + else { + SASSERT(m_p_prime); + SASSERT(is_p_normalized(a)); + m().set(m_div_tmp, b); + inv(m_div_tmp); + mul(a, m_div_tmp, c); + SASSERT(is_p_normalized(c)); + } + } + + static unsigned hash(mpz const & a) { return numeral_manager::hash(a); } + + void gcd(mpz const & a, mpz const & b, mpz & c) { + SASSERT(is_p_normalized(a) && is_p_normalized(b)); + m().gcd(a, b, c); + SASSERT(is_p_normalized(c)); + } + + void gcd(unsigned sz, mpz const * as, mpz & g) { + m().gcd(sz, as, g); + SASSERT(is_p_normalized(g)); + } + + void gcd(mpz const & r1, mpz const & r2, mpz & a, mpz & b, mpz & g) { + SASSERT(is_p_normalized(r1) && is_p_normalized(r2)); + m().gcd(r1, r2, a, b, g); + p_normalize(a); + p_normalize(b); + } + + void set(mpz & a, mpz & val) { m().set(a, val); p_normalize(a); } + void set(mpz & a, int val) { m().set(a, val); p_normalize(a); } + void set(mpz & a, unsigned val) { m().set(a, val); p_normalize(a); } + void set(mpz & a, char const * val) { m().set(a, val); p_normalize(a); } + void set(mpz & a, int64 val) { m().set(a, val); p_normalize(a); } + void set(mpz & a, uint64 val) { m().set(a, val); p_normalize(a); } + void set(mpz & a, mpz const & val) { m().set(a, val); p_normalize(a); } + + bool is_uint64(mpz & a) const { const_cast(this)->p_normalize(a); return m().is_uint64(a); } + bool is_int64(mpz & a) const { const_cast(this)->p_normalize(a); return m().is_int64(a); } + uint64 get_uint64(mpz & a) const { const_cast(this)->p_normalize(a); return m().get_uint64(a); } + int64 get_int64(mpz & a) const { const_cast(this)->p_normalize(a); return m().get_int64(a); } + double get_double(mpz & a) const { const_cast(this)->p_normalize(a); return m().get_double(a); } + void power(mpz const & a, unsigned k, mpz & b) { + SASSERT(is_p_normalized(a)); + unsigned mask = 1; + mpz power; + set(power, a); + set(b, 1); + while (mask <= k) { + if (mask & k) + mul(b, power, b); + mul(power, power, power); + mask = mask << 1; + } + del(power); + } + bool is_perfect_square(mpz const & a, mpz & root) { + if (m_z) { + return m().is_perfect_square(a, root); + } + else { + NOT_IMPLEMENTED_YET(); + return false; + } + } + + bool is_uint64(mpz const & a) const { return m().is_uint64(a); } + bool is_int64(mpz const & a) const { return m().is_int64(a); } + uint64 get_uint64(mpz const & a) const { return m().get_uint64(a); } + int64 get_int64(mpz const & a) const { return m().get_int64(a); } + + void mul2k(mpz & a, unsigned k) { m().mul2k(a, k); p_normalize(a); } + void mul2k(mpz const & a, unsigned k, mpz & r) { m().mul2k(a, k, r); p_normalize(r); } + unsigned power_of_two_multiple(mpz const & n) { return m().power_of_two_multiple(n); } + unsigned log2(mpz const & n) { return m().log2(n); } + unsigned mlog2(mpz const & n) { return m().mlog2(n); } + void machine_div2k(mpz & a, unsigned k) { m().machine_div2k(a, k); SASSERT(is_p_normalized(a)); } + void machine_div2k(mpz const & a, unsigned k, mpz & r) { m().machine_div2k(a, k, r); SASSERT(is_p_normalized(r)); } + + bool root(mpz & a, unsigned n) { SASSERT(!modular()); return m().root(a, n); } + bool root(mpz const & a, unsigned n, mpz & r) { SASSERT(!modular()); return m().root(a, n, r); } + +}; + +#endif + diff --git a/lib/name_exprs.cpp b/lib/name_exprs.cpp new file mode 100644 index 000000000..eea7a924f --- /dev/null +++ b/lib/name_exprs.cpp @@ -0,0 +1,167 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + name_exprs.h + +Abstract: + + Goodies for naming nested expressions. + +Author: + + Leonardo (leonardo) 2011-10-06 + +Notes: + +--*/ +#include"name_exprs.h" +#include"rewriter_def.h" +#include"ast_smt2_pp.h" + +class name_exprs_core : public name_exprs { + struct cfg : public default_rewriter_cfg { + ast_manager & m_manager; + defined_names & m_defined_names; + expr_predicate & m_pred; + + app_ref m_r; + proof_ref m_pr; + + expr_ref_vector * m_def_exprs; + proof_ref_vector * m_def_proofs; + + cfg(ast_manager & m, defined_names & n, expr_predicate & pred): + m_manager(m), + m_defined_names(n), + m_pred(pred), + m_r(m), + m_pr(m), + m_def_exprs(0), + m_def_proofs(0) { + } + + void gen_name_for_expr(expr * n, expr * & t, proof * & t_pr) { + expr_ref new_def(m_manager); + proof_ref new_def_pr(m_manager); + + if (m_defined_names.mk_name(n, new_def, new_def_pr, m_r, m_pr)) { + m_def_exprs->push_back(new_def); + if (m_manager.proofs_enabled()) + m_def_proofs->push_back(new_def_pr); + } + + t = m_r.get(); + t_pr = m_pr.get(); + } + + bool get_subst(expr * s, expr * & t, proof * & t_pr) { + TRACE("name_exprs", tout << "get_subst:\n" << mk_ismt2_pp(s, m_manager) << "\n";); + if (m_pred(s)) { + gen_name_for_expr(s, t, t_pr); + return true; + } + return false; + } + }; + + typedef rewriter_tpl rw; + + cfg m_cfg; + rw m_rw; + +public: + name_exprs_core(ast_manager & m, defined_names & n, expr_predicate & pred): + m_cfg(m, n, pred), + m_rw(m, m.proofs_enabled(), m_cfg) { + } + + virtual ~name_exprs_core() { + } + + virtual void operator()(expr * n, expr_ref_vector & new_defs, proof_ref_vector & new_def_proofs, expr_ref & r, proof_ref & p) { + m_cfg.m_def_exprs = &new_defs; + m_cfg.m_def_proofs = &new_def_proofs; + m_rw(n, r, p); + TRACE("name_exprs", tout << mk_ismt2_pp(n, m_rw.m()) << "\n---->\n" << mk_ismt2_pp(r, m_rw.m()) << "\n";); + } + + virtual void set_cancel(bool f) { + m_rw.set_cancel(f); + } + + virtual void reset() { + m_rw.reset(); + } +}; + +name_exprs * mk_expr_namer(ast_manager & m, defined_names & n, expr_predicate & pred) { + return alloc(name_exprs_core, m, n, pred); +} + +class name_quantifier_labels : public name_exprs_core { + class pred : public expr_predicate { + ast_manager & m_manager; + public: + pred(ast_manager & m):m_manager(m) {} + virtual bool operator()(expr * t) { + return is_quantifier(t) || m_manager.is_label(t); + } + }; + + pred m_pred; +public: + name_quantifier_labels(ast_manager & m, defined_names & n): + name_exprs_core(m, n, m_pred), + m_pred(m) { + } + + virtual ~name_quantifier_labels() { + } +}; + +name_exprs * mk_quantifier_label_namer(ast_manager & m, defined_names & n) { + return alloc(name_quantifier_labels, m, n); +} + +class name_nested_formulas : public name_exprs_core { + struct pred : public expr_predicate { + ast_manager & m_manager; + expr * m_root; + + pred(ast_manager & m):m_manager(m), m_root(0) {} + + virtual bool operator()(expr * t) { + TRACE("name_exprs", tout << "name_nested_formulas::pred:\n" << mk_ismt2_pp(t, m_manager) << "\n";); + if (is_app(t)) + return to_app(t)->get_family_id() == m_manager.get_basic_family_id() && to_app(t)->get_num_args() > 0 && t != m_root; + return m_manager.is_label(t) || is_quantifier(t); + } + }; + + pred m_pred; + +public: + name_nested_formulas(ast_manager & m, defined_names & n): + name_exprs_core(m, n, m_pred), + m_pred(m) { + } + + virtual ~name_nested_formulas() { + } + + virtual void operator()(expr * n, expr_ref_vector & new_defs, proof_ref_vector & new_def_proofs, expr_ref & r, proof_ref & p) { + m_pred.m_root = n; + TRACE("name_exprs", tout << "operator()\n";); + name_exprs_core::operator()(n, new_defs, new_def_proofs, r, p); + } +}; + +name_exprs * mk_nested_formula_namer(ast_manager & m, defined_names & n) { + return alloc(name_nested_formulas, m, n); +} + +void del_name_exprs(name_exprs * functor) { + dealloc(functor); +} diff --git a/lib/name_exprs.h b/lib/name_exprs.h new file mode 100644 index 000000000..a725297ec --- /dev/null +++ b/lib/name_exprs.h @@ -0,0 +1,65 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + name_exprs.h + +Abstract: + + Goodies for naming nested expressions. + +Author: + + Leonardo (leonardo) 2011-10-06 + +Notes: + +--*/ +#ifndef _NAME_EXPRS_H_ +#define _NAME_EXPRS_H_ + +#include"ast.h" +#include"defined_names.h" + +class expr_predicate { +public: + virtual bool operator()(expr * t) = 0; +}; + +class name_exprs { +public: + virtual ~name_exprs() {} + 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 + expr_ref & r, // [OUT] resultant expression + proof_ref & p // [OUT] proof for (iff n p) + ) = 0; + + virtual void set_cancel(bool f) = 0; + void cancel() { set_cancel(true); } + void reset_cancel() { set_cancel(false); } + virtual void reset() = 0; +}; + +/** + \brief Create an expression "namer" that will create replace nested expressions that satisfy pred with new + fresh declarations. +*/ +name_exprs * mk_expr_namer(ast_manager & m, defined_names & n, expr_predicate & pred); + +/** + \brief Create an expression "namer" that will replace quantifiers and labels with new fresh declarations. +*/ +name_exprs * mk_quantifier_label_namer(ast_manager & m, defined_names & n); + +/** + \brief Create an expression "namer" that will replace all nested formulas and term if-then-elses with + fresh declarations. +*/ +name_exprs * mk_nested_formula_namer(ast_manager & m, defined_names & n); + +void del_name_exprs(name_exprs * functor); + +#endif diff --git a/lib/nat_set.h b/lib/nat_set.h new file mode 100644 index 000000000..79e982f20 --- /dev/null +++ b/lib/nat_set.h @@ -0,0 +1,88 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + nat_set.h + +Abstract: + + Set of natural number with fast reset method. + +Author: + + Leonardo de Moura (leonardo) 2007-03-27. + +Revision History: + +--*/ +#ifndef _NAT_SET_H_ +#define _NAT_SET_H_ + +#include"vector.h" +#include"limits.h" + +class nat_set { + unsigned m_curr_timestamp; + svector m_timestamps; + +public: + nat_set(unsigned s = 0): + m_curr_timestamp(0), + m_timestamps() { + if (s > 0) { + m_timestamps.resize(s, 0); + } + } + + // A nat_set is a function from [0..s-1] -> boolean. + // This method sets the domain of this function. + void set_domain(unsigned s) { + m_timestamps.resize(s, 0); + } + + unsigned get_domain() const { + return m_timestamps.size(); + } + + // Assure that v is in the domain of the set. + void assure_domain(unsigned v) { + if (v >= get_domain()) { + set_domain(v+1); + } + } + + bool contains(unsigned v) const { + return m_timestamps[v] > m_curr_timestamp; + } + + void insert(unsigned v) { + m_timestamps[v] = m_curr_timestamp + 1; + } + + void remove(unsigned v) { + m_timestamps[v] = m_curr_timestamp; + } + + void reset() { + m_curr_timestamp++; + if (m_curr_timestamp == UINT_MAX) { + m_timestamps.fill(0); + m_curr_timestamp = 0; + } + } + + bool empty() const { + svector::const_iterator it = m_timestamps.begin(); + svector::const_iterator end = m_timestamps.end(); + for (; it != end; ++it) { + if (*it > m_curr_timestamp) { + return false; + } + } + return true; + } +}; + +#endif /* _NAT_SET_H_ */ + diff --git a/lib/ni_solver.cpp b/lib/ni_solver.cpp new file mode 100644 index 000000000..be7272d36 --- /dev/null +++ b/lib/ni_solver.cpp @@ -0,0 +1,230 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + ni_solver.cpp + +Abstract: + Wrappers for smt::solver that are non-incremental & (quasi-incremental). + +Author: + + Leonardo (leonardo) 2011-03-30 + +Notes: + +--*/ +#include"ni_solver.h" +#include"smt_solver.h" +#include"cmd_context.h" + +class ni_smt_solver : public solver { +protected: + cmd_context & m_cmd_ctx; + smt::solver * m_context; + progress_callback * m_callback; +public: + ni_smt_solver(cmd_context & ctx):m_cmd_ctx(ctx), m_context(0), m_callback(0) {} + + virtual ~ni_smt_solver() { + if (m_context != 0) + dealloc(m_context); + } + + virtual void init(ast_manager & m, symbol const & logic) { + // do nothing + } + + virtual void collect_statistics(statistics & st) const { + if (m_context == 0) { + return; + } + else { + m_context->collect_statistics(st); + } + } + + virtual void reset() { + if (m_context != 0) { + #pragma omp critical (ni_solver) + { + dealloc(m_context); + m_context = 0; + } + } + } + + virtual void assert_expr(expr * t) { + // do nothing + } + + virtual void push() { + // do nothing + } + + virtual void pop(unsigned n) { + // do nothing + } + + virtual unsigned get_scope_level() const { + return m_cmd_ctx.num_scopes(); + } + + void assert_exprs() { + ptr_vector::const_iterator it = m_cmd_ctx.begin_assertions(); + ptr_vector::const_iterator end = m_cmd_ctx.end_assertions(); + for (; it != end; ++it) { + m_context->assert_expr(*it); + } + } + + void init_solver() { + reset(); + #pragma omp critical (ni_solver) + { + m_context = alloc(smt::solver, m_cmd_ctx.m(), m_cmd_ctx.params()); + } + if (m_cmd_ctx.has_logic()) + m_context->set_logic(m_cmd_ctx.get_logic()); + if (m_callback) + m_context->set_progress_callback(m_callback); + assert_exprs(); + } + + virtual lbool check_sat(unsigned num_assumptions, expr * const * assumptions) { + // erase current solver, and create a new one. + init_solver(); + + if (num_assumptions == 0) { + return m_context->setup_and_check(); + } + else { + return m_context->check(num_assumptions, assumptions); + } + } + + virtual void get_unsat_core(ptr_vector & r) { + SASSERT(m_context); + unsigned sz = m_context->get_unsat_core_size(); + for (unsigned i = 0; i < sz; i++) + r.push_back(m_context->get_unsat_core_expr(i)); + } + + virtual void get_model(model_ref & m) { + SASSERT(m_context); + m_context->get_model(m); + } + + virtual proof * get_proof() { + SASSERT(m_context); + return m_context->get_proof(); + } + + virtual std::string reason_unknown() const { + SASSERT(m_context); + return m_context->last_failure_as_string(); + } + + virtual void get_labels(svector & r) { + SASSERT(m_context); + buffer tmp; + m_context->get_relevant_labels(0, tmp); + r.append(tmp.size(), tmp.c_ptr()); + } + + virtual void set_cancel(bool f) { + #pragma omp critical (ni_solver) + { + if (m_context) + m_context->set_cancel(f); + } + } + + virtual void set_progress_callback(progress_callback * callback) { + m_callback = callback; + if (m_context) + m_context->set_progress_callback(callback); + } +}; + +solver * mk_non_incremental_smt_solver(cmd_context & ctx) { + return alloc(ni_smt_solver, ctx); +} + +class qi_smt_solver : public ni_smt_solver { + bool m_inc_mode; +public: + qi_smt_solver(cmd_context & ctx):ni_smt_solver(ctx), m_inc_mode(false) {} + + virtual ~qi_smt_solver() {} + + virtual void init(ast_manager & m, symbol const & logic) { + if (m_inc_mode) { + init_solver(); + m_inc_mode = true; + } + } + + virtual void reset() { + ni_smt_solver::reset(); + m_inc_mode = false; + } + + void switch_to_inc() { + if (!m_inc_mode) { + init_solver(); + m_inc_mode = true; + } + SASSERT(m_inc_mode); + } + + virtual void assert_expr(expr * t) { + if (m_context != 0 && !m_inc_mode) { + // solver was already created to solve a check_sat query... + switch_to_inc(); + } + if (m_inc_mode) { + SASSERT(m_context); + m_context->assert_expr(t); + } + } + + virtual void push() { + switch_to_inc(); + SASSERT(m_context); + m_context->push(); + SASSERT(m_inc_mode); + } + + virtual void pop(unsigned n) { + switch_to_inc(); + SASSERT(m_context); + m_context->pop(n); + SASSERT(m_inc_mode); + } + + virtual unsigned get_scope_level() const { + if (!m_inc_mode) + return 0; + else + return m_context->get_scope_level(); + } + + + virtual lbool check_sat(unsigned num_assumptions, expr * const * assumptions) { + if (!m_inc_mode) { + lbool r = ni_smt_solver::check_sat(num_assumptions, assumptions); + SASSERT(!m_inc_mode); + return r; + } + else { + return m_context->check(num_assumptions, assumptions); + } + } +}; + + +solver * mk_quasi_incremental_smt_solver(cmd_context & ctx) { + return alloc(qi_smt_solver, ctx); +} diff --git a/lib/ni_solver.h b/lib/ni_solver.h new file mode 100644 index 000000000..c78707319 --- /dev/null +++ b/lib/ni_solver.h @@ -0,0 +1,30 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + ni_solver.h + +Abstract: + Wrappers for smt::context that are non-incremental & (quasi-incremental). + +Author: + + Leonardo (leonardo) 2011-03-30 + +Notes: + +--*/ +#ifndef _NI_SOLVER_H_ +#define _NI_SOLVER_H_ + +#include"solver.h" +class cmd_context; + +// Creates a solver that restarts from scratch for every call to check_sat +solver * mk_non_incremental_smt_solver(cmd_context & ctx); + +// Creates a solver that restarts from scratch for the first call to check_sat, and then moves to incremental behavior. +solver * mk_quasi_incremental_smt_solver(cmd_context & ctx); + +#endif diff --git a/lib/nla2bv_tactic.cpp b/lib/nla2bv_tactic.cpp new file mode 100644 index 000000000..a9033bcd3 --- /dev/null +++ b/lib/nla2bv_tactic.cpp @@ -0,0 +1,480 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + nla2bv_tactic.cpp + +Abstract: + + Convert quantified NIA problems to bounded bit-vector arithmetic problems. + +Author: + + Nikolaj (nbjorner) 2011-05-3 + +Notes: + Ported to tactic framework on 2012-02-28 + The original file was called qfnla2bv.cpp + +--*/ +#include "tactical.h" +#include "arith_decl_plugin.h" +#include "bv_decl_plugin.h" +#include "for_each_expr.h" +#include "expr_replacer.h" +#include "optional.h" +#include "bv2int_rewriter.h" +#include "bv2real_rewriter.h" +#include "extension_model_converter.h" +#include "filter_model_converter.h" +#include "bound_manager.h" +#include "obj_pair_hashtable.h" +#include "ast_smt2_pp.h" + +// +// +// 1. for each variable, determine bounds (s.t., non-negative variables +// have unsigned bit-vectors). +// +// 2. replace uninterpreted variables of sort int by +// expressions of the form +- bv2int(b) +- k +// where k is a slack. +// +// 3. simplify resulting assertion set to reduce occurrences of bv2int. +// + +class nla2bv_tactic : public tactic { + class imp { + typedef rational numeral; + ast_manager & m_manager; + bool m_is_sat_preserving; + arith_util m_arith; + bv_util m_bv; + bv2real_util m_bv2real; + bv2int_rewriter_ctx m_bv2int_ctx; + bound_manager m_bounds; + expr_substitution m_subst; + func_decl_ref_vector m_vars; + expr_ref_vector m_defs; + expr_ref_vector m_trail; + unsigned m_num_bits; + unsigned m_default_bv_size; + ref m_fmc; + + public: + imp(ast_manager & m, params_ref const& p): + m_manager(m), + m_is_sat_preserving(true), + m_arith(m), + m_bv(m), + m_bv2real(m, rational(p.get_uint(":nla2bv-root",2)), rational(p.get_uint(":nla2bv-divisor",2)), p.get_uint(":nla2bv-max-bv-size", UINT_MAX)), + m_bv2int_ctx(m, p), + m_bounds(m), + m_subst(m), + m_vars(m), + m_defs(m), + m_trail(m), + m_fmc(0) { + m_default_bv_size = m_num_bits = p.get_uint(":nla2bv-bv-size", 4); + } + + ~imp() {} + + + void operator()(goal & g, model_converter_ref & mc) { + TRACE("nla2bv", g.display(tout); + tout << "Muls: " << count_mul(g) << "\n"; + ); + m_fmc = alloc(filter_model_converter, m_manager); + m_bounds(g); + collect_power2(g); + if(!collect_vars(g)) { + throw tactic_exception("goal is not in the fragment supported by nla2bv"); + } + tactic_report report("nla->bv", g); + substitute_vars(g); + TRACE("nla2bv", g.display(tout << "substitute vars\n");); + reduce_bv2int(g); + reduce_bv2real(g); + TRACE("nla2bv", g.display(tout << "after reduce\n");); + extension_model_converter * evc = alloc(extension_model_converter, m_manager); + mc = concat(m_fmc.get(), evc); + for (unsigned i = 0; i < m_vars.size(); ++i) { + evc->insert(m_vars[i].get(), m_defs[i].get()); + } + for (unsigned i = 0; i < m_bv2real.num_aux_decls(); ++i) { + m_fmc->insert(m_bv2real.get_aux_decl(i)); + } + IF_VERBOSE(TACTIC_VERBOSITY_LVL, verbose_stream() << "(nla->bv :sat-preserving " << m_is_sat_preserving << ")\n";); + TRACE("nla2bv_verbose", g.display(tout);); + TRACE("nla2bv", tout << "Muls: " << count_mul(g) << "\n";); + g.inc_depth(); + if (!is_sat_preserving()) + g.updt_prec(goal::UNDER); + } + + bool const& is_sat_preserving() const { return m_is_sat_preserving; } + + private: + void set_satisfiability_preserving(bool f) { + m_is_sat_preserving = f; + } + + void collect_power2(goal & g) { + m_bv2int_ctx.collect_power2(g); + obj_map const& p2 = m_bv2int_ctx.power2(); + if (p2.empty()) return; + obj_map::iterator it = p2.begin(), end = p2.end(); + for (; it != end; ++it) { + expr* v = it->m_value; + unsigned num_bits = m_bv.get_bv_size(v); + expr* w = m_bv.mk_bv2int(m_bv.mk_bv_shl(m_bv.mk_numeral(1, num_bits), v)); + m_trail.push_back(w); + m_subst.insert(it->m_key, w); + TRACE("nla2bv", tout << mk_ismt2_pp(it->m_key, m_manager) << " " << mk_ismt2_pp(w, m_manager) << "\n";); + } + // eliminate the variables that are power of two. + substitute_vars(g); + m_subst.reset(); + } + + + // eliminate bv2int from formula + void reduce_bv2int(goal & g) { + bv2int_rewriter_star reduce(m_manager, m_bv2int_ctx); + expr_ref r(m_manager); + for (unsigned i = 0; i < g.size(); ++i) { + reduce(g.form(i), r); + g.update(i, r); + } + assert_side_conditions(g, m_bv2int_ctx.num_side_conditions(), + m_bv2int_ctx.side_conditions()); + } + + // eliminate bv2real from formula + void reduce_bv2real(goal & g) { + bv2real_rewriter_star reduce(m_manager, m_bv2real); + expr_ref r(m_manager); + for (unsigned i = 0; i < g.size(); ++i) { + reduce(g.form(i), r); + if (m_bv2real.contains_bv2real(r)) { + throw tactic_exception("nla2bv could not eliminate reals"); + } + g.update(i, r); + } + assert_side_conditions(g, m_bv2real.num_side_conditions(), + m_bv2real.side_conditions()); + } + + void assert_side_conditions(goal & g, unsigned sz, expr * const * conditions) { + for (unsigned i = 0; i < sz; ++i) { + g.assert_expr(conditions[i]); + set_satisfiability_preserving(false); + } + TRACE("nla2bv", + for (unsigned i = 0; i < sz; ++i) { + tout << mk_ismt2_pp(conditions[i], m_manager) << "\n"; + }); + } + + // substitute variables by bit-vectors + void substitute_vars(goal & g) { + scoped_ptr er = mk_default_expr_replacer(m_manager); + er->set_substitution(&m_subst); + expr_ref r(m_manager); + for (unsigned i = 0; i < g.size(); ++i) { + (*er)(g.form(i), r); + g.update(i, r); + } + } + + // ----------------- + // collect uninterpreted variables in problem. + // create a substitution from the variables to + // bit-vector terms. + // + void add_var(app* n) { + if (m_arith.is_int(n)) { + add_int_var(n); + } + else { + SASSERT(m_arith.is_real(n)); + add_real_var(n); + } + } + + void add_int_var(app* n) { + expr_ref s_bv(m_manager); + sort_ref bv_sort(m_manager); + optional low, up; + numeral tmp; + bool is_strict; + if (m_bounds.has_lower(n, tmp, is_strict)) { + SASSERT(!is_strict); + low = tmp; + } + if (m_bounds.has_upper(n, tmp, is_strict)) { + SASSERT(!is_strict); + up = tmp; + } + // + // [low .. up] + // num_bits = log2(1 + |up - low|) or m_num_bits + // + unsigned num_bits = m_num_bits; + if (up && low) { + num_bits = log2(abs(*up - *low)+numeral(1)); + } + else { + TRACE("nla2bv", tout << "no bounds for " << mk_ismt2_pp(n, m_manager) << "\n";); + set_satisfiability_preserving(false); + } + bv_sort = m_bv.mk_sort(num_bits); + std::string name = n->get_decl()->get_name().str(); + s_bv = m_manager.mk_fresh_const(name.c_str(), bv_sort); + m_fmc->insert(to_app(s_bv)->get_decl()); + s_bv = m_bv.mk_bv2int(s_bv); + if (low) { + if (!(*low).is_zero()) { + // low <= s_bv + // ~> + // replace s_bv by s_bv + low + // add 'low' to model for n. + // + s_bv = m_arith.mk_add(s_bv, m_arith.mk_numeral(*low, true)); + } + } + else if (up) { + // s_bv <= up + // ~> + // replace s_bv by up - s_bv + // + s_bv = m_arith.mk_sub(m_arith.mk_numeral(*up, true), s_bv); + } + else { + s_bv = m_arith.mk_sub(s_bv, m_arith.mk_numeral(m_bv.power_of_two(num_bits-1), true)); + } + + m_trail.push_back(s_bv); + m_subst.insert(n, s_bv); + m_vars.push_back(n->get_decl()); + m_defs.push_back(s_bv); + } + + void add_real_var(app* n) { + expr_ref s_bv(m_manager), s_bvr(m_manager), s(m_manager), t(m_manager); + sort_ref bv_sort(m_manager); + bv_sort = m_bv.mk_sort(m_num_bits); + set_satisfiability_preserving(false); + std::string name = n->get_decl()->get_name().str(); + s = m_manager.mk_fresh_const(name.c_str(), bv_sort); + name += "_r"; + t = m_manager.mk_fresh_const(name.c_str(), bv_sort); + m_fmc->insert(to_app(s)->get_decl()); + m_fmc->insert(to_app(t)->get_decl()); + s_bv = m_bv2real.mk_bv2real(s, t); + m_trail.push_back(s_bv); + m_subst.insert(n, s_bv); + m_vars.push_back(n->get_decl()); + + // use version without bv2real function. + m_bv2real.mk_bv2real_reduced(s, t, s_bvr); + m_defs.push_back(s_bvr); + } + + + // update number of bits based on the largest constant used. + void update_num_bits(app* n) { + bool is_int; + numeral nm; + if (m_arith.is_numeral(n, nm, is_int) && is_int) { + nm = abs(nm); + unsigned l = log2(nm); + if (m_num_bits <= l) { + m_num_bits = l+1; + } + } + } + + unsigned log2(rational const& n) { + rational pow(1), two(2); + unsigned sz = 0; + while (pow < n) { + ++sz; + pow *= two; + } + if (sz == 0) sz = 1; + return sz; + } + + class get_uninterp_proc { + imp& m_imp; + ptr_vector m_vars; + bool m_in_supported_fragment; + public: + get_uninterp_proc(imp& s): m_imp(s), m_in_supported_fragment(true) {} + ptr_vector const& vars() { return m_vars; } + void operator()(var * n) { + m_in_supported_fragment = false; + } + void operator()(app* n) { + arith_util& a = m_imp.m_arith; + ast_manager& m = a.get_manager(); + if (a.is_int(n) && + is_uninterp_const(n)) { + m_vars.push_back(n); + } + else if (a.is_real(n) && + is_uninterp_const(n)) { + m_vars.push_back(n); + } + else if (m.is_bool(n) && is_uninterp_const(n)) { + + } + else if (!(a.is_mul(n) || + a.is_add(n) || + a.is_sub(n) || + a.is_le(n) || + a.is_lt(n) || + a.is_ge(n) || + a.is_gt(n) || + a.is_numeral(n) || + a.is_uminus(n) || + m_imp.m_bv2real.is_pos_le(n) || + m_imp.m_bv2real.is_pos_lt(n) || + n->get_family_id() == a.get_manager().get_basic_family_id())) { + TRACE("nla2bv", tout << "Not supported: " << mk_ismt2_pp(n, a.get_manager()) << "\n";); + m_in_supported_fragment = false; + } + m_imp.update_num_bits(n); + } + void operator()(quantifier* q) { + m_in_supported_fragment = false; + } + bool is_supported() const { return m_in_supported_fragment; } + }; + + bool collect_vars(goal const & g) { + get_uninterp_proc fe_var(*this); + for_each_expr_at(fe_var, g); + for (unsigned i = 0; i < fe_var.vars().size(); ++i) { + add_var(fe_var.vars()[i]); + } + return fe_var.is_supported() && !fe_var.vars().empty(); + } + + class count_mul_proc { + imp& m_imp; + unsigned m_count; + public: + count_mul_proc(imp& s): m_imp(s), m_count(0) {} + unsigned count() const { return m_count; } + void operator()(var * n) {} + void operator()(app* n) { + if (m_imp.m_arith.is_mul(n)) { + m_count += n->get_num_args()-1; + } + if (m_imp.m_bv.is_bv_mul(n)) { + unsigned num_vars = 0; + for (unsigned j = 0; j < n->get_num_args(); ++j) { + if (!m_imp.m_bv.is_numeral(n->get_arg(j))) { + ++num_vars; + } + } + if (num_vars > 1) { + m_count += num_vars - 1; + } + } + } + void operator()(quantifier* q) {} + }; + + unsigned count_mul(goal const & g) { + count_mul_proc c(*this); + for_each_expr_at(c, g); + return c.count(); + } + }; + + params_ref m_params; + imp * m_imp; + + struct scoped_set_imp { + nla2bv_tactic & m_owner; + scoped_set_imp(nla2bv_tactic & o, imp & i): + m_owner(o) { + #pragma omp critical (tactic_cancel) + { + m_owner.m_imp = &i; + } + } + + ~scoped_set_imp() { + #pragma omp critical (tactic_cancel) + { + m_owner.m_imp = 0; + } + } + }; + +public: + nla2bv_tactic(params_ref const & p): + m_params(p), + m_imp(0) { + } + + virtual tactic * translate(ast_manager & m) { + return alloc(nla2bv_tactic, m_params); + } + + virtual ~nla2bv_tactic() { + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + } + + virtual void collect_param_descrs(param_descrs & r) { + r.insert(":nla2bv-max-bv-size", CPK_UINT, "(default: inf) maximum bit-vector size used by nla2bv tactic"); + r.insert(":nla2bv-bv-size", CPK_UINT, "(default: 4) default bit-vector size used by nla2bv tactic."); + r.insert(":nla2bv-root", CPK_UINT, "(default: 2) nla2bv tactic encodes reals into bit-vectors using expressions of the form a+b*sqrt(c), this parameter sets the value of c used in the encoding."); + r.insert(":nla2bv-divisor", CPK_UINT, "(default: 2) nla2bv tactic parameter."); + } + + /** + \brief Modify a goal to use bounded bit-vector + arithmetic in place of non-linear integer arithmetic. + \return false if transformation is not possible. + */ + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + fail_if_proof_generation("nla2bv", g); + fail_if_unsat_core_generation("nla2bv", g); + mc = 0; pc = 0; core = 0; result.reset(); + + imp proc(g->m(), m_params); + scoped_set_imp setter(*this, proc); + proc(*(g.get()), mc); + + result.push_back(g.get()); + SASSERT(g->is_well_sorted()); + } + + virtual void cleanup(void) { + } +}; + +tactic * mk_nla2bv_tactic(ast_manager & m, params_ref const & p) { + return alloc(nla2bv_tactic, p); +} + + + + + + diff --git a/lib/nla2bv_tactic.h b/lib/nla2bv_tactic.h new file mode 100644 index 000000000..0b0febb1f --- /dev/null +++ b/lib/nla2bv_tactic.h @@ -0,0 +1,30 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + nla2bv_tactic.cpp + +Abstract: + + Convert quantified NIA problems to bounded bit-vector arithmetic problems. + +Author: + + Nikolaj (nbjorner) 2011-05-3 + +Notes: + Ported to tactic framework on 2012-02-28 + +--*/ +#ifndef _NLA2BV_TACTIC_H_ +#define _NLA2BV_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_nla2bv_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif + diff --git a/lib/nlarith_util.cpp b/lib/nlarith_util.cpp new file mode 100644 index 000000000..039b57acf --- /dev/null +++ b/lib/nlarith_util.cpp @@ -0,0 +1,2024 @@ +#include "ast.h" +#include "nlarith_util.h" +#include "arith_decl_plugin.h" +#include "ast_pp.h" +#include "qe.h" +#include "expr_replacer.h" +#include "arith_rewriter.h" +#include "arith_simplifier_plugin.h" +#include "expr_functors.h" + +namespace nlarith { + + typedef app_ref_vector poly; + enum comp { LE, LT, EQ, NE}; + typedef vector polys; + typedef svector comps; + + class util::literal_set { + app_ref m_inf; + app_ref m_sup; + app* m_x; + + app_ref_vector m_lits; + vector m_polys; + svector m_comps; + + public: + literal_set(ast_manager& m) : m_inf(m), m_sup(m), m_x(0), m_lits(m) {} + unsigned size() const { return m_lits.size(); } + + app_ref_vector& lits() { return m_lits; } + vector& polys() { return m_polys; } + svector& comps() { return m_comps; } + + poly const& get_poly(unsigned i) const { return m_polys[i]; } + // Note: comp comp(unsigned i) is not valid C++. + // it works on VC++, but it is rejected by gcc. + comp compare(unsigned i) const { return m_comps[i]; } + app* literal(unsigned i) const { return m_lits[i]; } + + app* x() const { return m_x; } + void set_x(app* x) { SASSERT(!m_x); m_x = x; } + + app* x_inf() { + if (!m_inf) { + mk_const("inf", m_inf); + } + return m_inf; + } + + app* x_sup() { + if (!m_sup) { + mk_const("sup", m_sup); + } + return m_sup; + } + + private: + void mk_const(char const* suffix, app_ref& v) { + ast_manager& m = m_lits.get_manager(); + std::string name = m_x->get_decl()->get_name().str(); + name += suffix; + sort* r = m.get_sort(m_x); + v= m.mk_const(symbol(name.c_str()), r); + } + }; + + class util::imp { + + ast_manager& m_manager; + arith_util m_arith; + bool m_enable_linear; + app_ref m_zero; + app_ref m_one; + front_end_params m_params; + basic_simplifier_plugin m_bs; + arith_simplifier_plugin m_rw; + arith_rewriter m_rw1; + expr_ref_vector m_trail; + + ast_manager& m() const { return m_manager; } + arith_util& a() { return m_arith; } + app* z() { return m_zero.get();} + app* one() { return m_one.get(); } + + std::ostream& display(std::ostream& out, comp c) { + switch(c) { + case LE: out << "<="; return out; + case LT: out << "<"; return out; + case EQ: out << "="; return out; + case NE: out << "!="; return out; + } + return out; + } + + public: + imp(ast_manager& m) : + m_manager(m), m_arith(m), + m_enable_linear(false), + m_zero(num(0),m), m_one(num(1),m), + m_bs(m), + m_rw(m, m_bs, m_params), + m_rw1(m), m_trail(m) { + } + + // + // create branches and substitutions according to case analysis. + // + bool create_branches(app* x, unsigned num_lits, expr* const* lits, + branch_conditions& branch_conds) { + polys polys; + comps comps; + contains_app contains_x(m(), x); + branch_conds.reset(); + m_trail.reset(); // use scope? + + if (!a().is_real(x)) { + return false; + } + + if (!get_polys(contains_x, num_lits, lits, polys, comps, &branch_conds, 0)) { + TRACE("nlarith", + tout << "could not extract polynomials " << mk_pp(x, m()) << "\n"; + for (unsigned i = 0; i < num_lits; ++i) { + tout << mk_pp(lits[i], m()) << " "; + } + tout << "\n"; + ); + return false; + } + if (is_degree_two_plus(polys)) { + return false; + } + if (!m_enable_linear && is_linear(polys)) { + TRACE("nlarith", tout << "this is a linear problem " << mk_pp(x,m()) << "\n"; display(tout, polys);); + return false; + } + unsigned idx; + if (has_single_degree2(polys, comps, idx)) { + for (unsigned i = 0; i < polys.size(); ++i) { + create_branch_l(idx, i, polys, comps, branch_conds); + } + } + else { + for (unsigned i = 0; i < polys.size(); ++i) { + create_branch(i, polys, comps, branch_conds); + } + } + inf_branch(polys, comps, branch_conds); + TRACE("nlarith", + for (unsigned i = 0; i < num_lits; ++i) { + tout << mk_pp(lits[i], m()) << " "; + } + tout << "\n"; + display_branching(tout, x, branch_conds); + ); + return true; + } + + void set_enable_linear(bool enable_linear) { m_enable_linear = enable_linear; } + + void extract_non_linear(unsigned sz, app* const* es, ptr_vector& nl_vars) { + ast_mark visit; + for (unsigned i = 0; i < sz; ++i) { + extract_non_linear(es[i], visit, nl_vars); + } + } + + void extract_non_linear(expr* e, ptr_vector& nl_vars) { + ast_mark visit; + extract_non_linear(e, visit, nl_vars); + } + + void extract_non_linear(expr* e, ast_mark& visit, ptr_vector& nl_vars) { + if (visit.is_marked(e)) { + return; + } + ast_mark nonlin; + ptr_vector todo; + todo.push_back(e); + while(!todo.empty()) { + e = todo.back(); + todo.pop_back(); + if (is_var(e)) { + continue; + } + if (is_quantifier(e)) { + e = to_quantifier(e)->get_expr(); + if (!visit.is_marked(e)) { + todo.push_back(e); + } + } + SASSERT(is_app(e)); + app* a = to_app(e); + bool nl = m_enable_linear || nonlin.is_marked(e) || is_nonlinear(a); + if (is_arithmetical(a)) { + // TBD: overshoots in the case of 'ite' expressions. + for (unsigned i = 0; i < a->get_num_args(); ++i) { + expr* arg = a->get_arg(i); + bool nl2 = nonlin.is_marked(arg); + if (!visit.is_marked(arg) || (nl && !nl2)) { + todo.push_back(to_app(arg)); + visit.mark(arg, true); + if (nl) { + nonlin.mark(arg, true); + } + } + } + } + else if (is_variable(a)) { + if (nl) { + nl_vars.push_back(a); + } + } + else { + for (unsigned i = 0; i < a->get_num_args(); ++i) { + expr* arg = a->get_arg(i); + if (!visit.is_marked(arg) || !nonlin.is_marked(arg)) { + todo.push_back(to_app(arg)); + visit.mark(arg, true); + nonlin.mark(arg, true); + } + } + } + } + + TRACE("nlarith", + tout << "Non-linear variables: "; + for (unsigned i = 0; i < nl_vars.size(); ++i) { + tout << mk_pp(nl_vars[i], m()) << " "; + } + tout << "\n";); + } + + + private: + void track(expr* e) { + m_trail.push_back(e); + } + + app* mk_lt(expr* p) { + expr_ref r(m()); + m_rw.mk_lt(p, z(), r); + track(r); + return to_app(r); + } + app* mk_le(expr* p) { + expr_ref r(m()); + m_rw.mk_le(p, z(), r); + track(r); + return to_app(r); + } + app* mk_gt(expr* p) { return mk_lt(mk_uminus(p)); } + app* mk_ge(expr* p) { return mk_le(mk_uminus(p)); } + app* mk_eq(expr* p) { + expr_ref r(m()); + m_bs.mk_eq(p, z(), r); + track(r); + return to_app(r); + } + app* mk_ne(expr* p) { + expr_ref r(m()); + m_bs.mk_eq(p, z(), r); + m_bs.mk_not(r, r); + track(r); + return to_app(r); + } + app* num(int i) { return a().mk_numeral(rational(i), false); } + + + + // + // TBD: perhaps merge with arith_rewriter using a app_ref buffer? + // + app* mk_uminus(expr* e) { + expr_ref r(m()); + m_rw.mk_uminus(e, r); + track(r); + return to_app(r); + } + + app* mk_add(unsigned sz, expr* const* args) { + expr_ref r(m()); + m_rw.mk_add(sz, args, r); + track(r); + return to_app(r); + } + + app* mk_add(expr* t, expr* s) { + expr_ref r(m()); + expr* args[2] = { t, s}; + m_rw.mk_add(2, args, r); + track(r); + return to_app(r); + } + + app* mk_add(expr* t, expr* s, expr* u) { + return mk_add(t, mk_add(s, u)); + } + + app* mk_mul(expr* t, expr* s) { + expr_ref r(m()); + expr* args[2] = { t, s}; + m_rw.mk_mul(2, args, r); + track(r); + return to_app(r); + } + + app* mk_sub(expr* t, expr* s) { + expr_ref r(m()); + expr* args[2] = { t, s}; + m_rw.mk_sub(2, args, r); + track(r); + return to_app(r); + } + + app* mk_mul(expr* t, expr* s, expr* u) { + return mk_mul(t, mk_mul(s, u)); + } + + app* mk_and(unsigned num_args, expr* const* args) { + expr_ref r(m()); + m_bs.mk_and(num_args, args, r); + track(r); + return to_app(r); + } + + app* mk_and(expr* a, expr* b) { + expr* args[2] = { a, b }; + return mk_and(2, args); + } + + app* mk_or(unsigned num_args, expr* const* args) { + expr_ref r(m()); + m_bs.mk_or(num_args, args, r); + track(r); + return to_app(r); + } + + app* mk_or(expr* a, expr* b) { + expr* args[2] = { a, b }; + return mk_or(2, args); + } + void display_branching( + std::ostream& out, app* x, + branch_conditions const& bc) const { + out << mk_pp(x, m()) << ":\n"; + for (unsigned i = 0; i < bc.preds().size(); ++i) { + out << "Pred: " << mk_pp(bc.preds()[i], m()) << "\n"; + } + + for (unsigned i = 0; i < bc.branches().size(); ++i) { + out << "Branch:\n" << mk_pp(bc.branches()[i], m()) << "\n"; + for (unsigned j = 0; j < bc.subst()[i].size(); ++j) { + out << mk_pp(bc.preds()[j], m()) << " |-> " + << mk_pp(bc.subst(i)[j], m()) << "\n"; + } + out << "Def: " << mk_pp(bc.def(i), m()) << "\n"; + } + } + + struct abc_poly { + app_ref m_a; + app_ref m_b; + app_ref m_c; + abc_poly(imp& I, app* a, app* b, app* c): + m_a(a, I.m()), m_b(b, I.m()), m_c(c, I.m()) {} + }; + + + struct sqrt_form { + app_ref m_a; + int m_b; + app_ref m_c; + app_ref m_d; + sqrt_form(imp& I, app* a, int b, app* c, app* d) : + m_a(a, I.m()), m_b(b), m_c(c, I.m()), m_d(d, I.m()) { + SASSERT(d != I.z()); + } + void display(std::ostream& out) const { + ast_manager& m = m_a.get_manager(); + out << "(/ (+ " << mk_pp(m_a, m) << " (* " << m_b << " (sqrt " << mk_pp(m_c, m) << "))) " << mk_pp(m_d, m) << ")"; + } + }; + + expr* mk_abs(expr* e) { + return m().mk_ite(mk_lt(e), mk_uminus(e), e); + } + + + // + // result = (a + b*sqrt(c))/d + // + expr* to_expr(sqrt_form const& s) { + arith_util& A = a(); + expr* result; + // result = (a + b*sqrt(c))/d + if (s.m_c == z() || s.m_b == 0) { + result = A.mk_div(s.m_a, s.m_d); + } + else { + expr* half = A.mk_numeral(rational(1,2), false); + result = A.mk_div(mk_add(s.m_a, mk_mul(num(s.m_b), A.mk_power(mk_abs(s.m_c), half))), s.m_d); + } + return result; + } + + + // + // + // Given p(x): ax^2 + bx + c < 0 + // then the derivative is d p(x)/dx = 2ax + b + // cases: + // 1. a != 0, b != 0: + // zero: (- b +- sqrt(b^2 - 4ac))/ 2a + // then slope of x at zero is: + // 2a*zero + b = +- sqrt(..), + // so the slope is given by the sign of the solution. + // + // return zero + epsilon * (if sign > 0 then -1 else 1) + // + // 2. a = 0, b != 0: + // zero : -c/b + // slope is b. + // return -c/b + epsilon * (if b > 0 then -1 else 1) + // + // Given p(x): ax^2 + bx + c <= 0, ax^2 + bx + c = 0, + // use epsilon = 0. + // Given p(x): ax^2 + bx + c <= 0, ax^2 + bx + c = 0, + // use epsilon as in < case. + // + + expr* mk_def(comp cmp, abc_poly const& p, sqrt_form const& s) { + expr* result = to_expr(s); + if (is_strict(cmp)) { + if (p.m_a == z()) { + result = mk_add(result, mk_mul(mk_epsilon(), m().mk_ite(mk_lt(p.m_b),num(1),num(-1)))); + } + else { + if (s.m_b > 0) { + result = mk_add(result, mk_mul(num(-1),mk_epsilon())); + } + else { + result = mk_add(result, mk_epsilon()); + } + } + } + return result; + } + + // + // TBD: Compute an espilon based on the terms + // used in the constraints. + // + expr* mk_epsilon() { + return a().mk_numeral(rational(1,10000), false); + } + + // + // TBD: Compute an inf based on the terms + // used in the constraints. Eg., use a symbolic + // constant for epsilon and inf and then solve for + // it postiori. + // + expr* mk_inf() { + return a().mk_numeral(rational(-10000), false); + } + + // lower bounds for each case: + // a*x^2 + b*x + c + eps = 0 & a = 0 & b = 0 => x < 0 + // a*x^2 + b*x + c + eps = 0 & a = 0 & b != 0 => x < - (c / b) < - (c^2 +1) * (1 + 1/b^2) + // a*x^2 + b*x + c + eps = 0 & a != 0 => x < (-|b| - sqrt(b^2 - 4ac))/2|a| < - (b^2*(1 + 1/a^2) + (c^2+1)) + + app* sq(expr* e) { + return mk_mul(e,e); + } + + app* sq1(expr * e) { + return mk_add(num(1), sq(e)); + } + + app* inv(expr * e) { + return a().mk_div(num(1), e); + } + + expr* mk_inf(branch_conditions const& bc) { + return mk_inf(); +#if 0 + if (bc.size() > 0) { + // pick a number lower than the symbolic lower bounds. + for(unsigned i = 0; i < bc.size(); ++i) { + expr * a = bc.a(i); + expr * b = bc.b(i); + expr * c = bc.c(i); + expr * e = + m().mk_ite( + mk_eq(a), + m().mk_ite( + mk_eq(b), + num(0), + mk_mul(mk_add(sq(c),num(1)), sq1(inv(b)))), + mk_add(mk_mul(sq(b),sq1(inv(a))), sq1(c))); + r = mk_add(e, r); + } + return mk_uminus(r); + } +#endif + } + + void inf_branch( + polys const& polys, comps const& comps, branch_conditions& bc) { + // /\_j p_j -> p_j[-oo / x] + app_ref t1(m()); + expr_ref_vector es(m()), subst(m()); + for (unsigned j = 0; j < polys.size(); ++j) { + minus_inf_subst sub(*this); + apply_subst(sub, comps[j], polys[j], t1); + es.push_back(m().mk_implies(bc.preds(j), t1)); + subst.push_back(t1); + TRACE("nlarith_verbose", + display(tout << "inf", polys[j]); + display(tout, comps[j]); + tout << " 0 [-oo] --> " << mk_pp(t1.get(), m()) << "\n";); + } + TRACE("nlarith", tout << "inf-branch\n";); + bc.add_branch(mk_and(es.size(), es.c_ptr()), m().mk_true(), subst, mk_inf(bc), z(), z(), z()); + } + + void create_branch_l(unsigned j, unsigned i, polys const& polys, comps const& comps, + branch_conditions& bc) { + comp cmp = comps[i]; + poly const& p = polys[i]; + if (i == j) cmp = LE; // non-strict to avoid epsilon substitution mode. + app* a, *b, *c; + get_coefficients(p, a, b, c); + app_ref t1(m()); + expr_ref a2(m()), d(m()), t2(m()), cond(m()); + expr_ref_vector es(m()), subst(m()); + + if (b != z()) { + sqrt_form e0(*this, mk_uminus(c), 0, z(), b); + // a_i = 0 /\ b_i != 0 /\ phi[e_i/x] + TRACE("nlarith", display(tout << "a_i != 0 & b_i != 0 & hi[e_i / x]", p);tout<<"\n";); + scoped_ptr rp = mk_default_expr_replacer(m()); + expr_substitution sub(m()); + sub.insert(a, z()); + rp->set_substitution(&sub); + if (a != z()) es.push_back(mk_eq(a)); + es.push_back(mk_ne(b)); + cond = mk_and(es.size(), es.c_ptr()); + es.push_back(bc.preds(i)); + for (unsigned k = 0; k < polys.size(); ++k) { + mk_subst(cmp, polys[k], comps[k], e0, t1); + (*rp)(t1, t2); + es.push_back(m().mk_implies(bc.preds(k), t2)); + subst.push_back(t1); + } + bc.add_branch(mk_and(es.size(), es.c_ptr()), cond, subst, mk_def(cmp, abc_poly(*this, z(), b, c), e0), a, b, c); + } + + if (i == j && a != z()) { + // a != 0 & phi[-b/(2a)/x] + TRACE("nlarith", display(tout << "a != 0 & phi[-b/2a / x]", p);tout<<"\n";); + app* a2 = mk_mul(num(2), a); + sqrt_form e1(*this, mk_uminus(b), 0, z(), a2); + es.reset(); + subst.reset(); + cond = mk_ne(a); + es.push_back(cond); + es.push_back(bc.preds(i)); + + for (unsigned k = 0; k < polys.size(); ++k) { + mk_subst(cmp, polys[k], comps[k], e1, t1); + es.push_back(m().mk_implies(bc.preds(k), t1)); + subst.push_back(t1); + } + bc.add_branch(mk_and(es.size(), es.c_ptr()), cond, subst, mk_def(cmp, abc_poly(*this, a2, b, z()),e1), a, b, c); + } + } + + void create_branch(unsigned i, polys const& polys, comps const& comps, branch_conditions& bc) { + comp cmp = comps[i]; + poly const& p = polys[i]; + app* a, *b, *c; + get_coefficients(p, a, b, c); + app_ref t1(m()), a2(m()), d(m()); + expr_ref cond(m()), t2(m()), branch(m()); + expr_ref_vector es(m()), subst(m()); + d = mk_sub(mk_mul(b,b), mk_mul(num(4), a, c)); + a2 = mk_mul(a, num(2)); + + TRACE("nlarith", + display(tout, p); tout << "\n"; + tout << "a:" << mk_pp(a, m()) << "\n"; + tout << "b:" << mk_pp(b,m()) << "\n"; + tout << "c:" << mk_pp(c,m()) << "\n"; + tout << "d:" << mk_pp(d, m()) << "\n";); + // p & a = 0 & b != 0 & /\_j p_j -> p_j[e0/x] + // p & a != 0 & 0 <= d & /\_j p_j -> (p_j[e1/x] \/ p_j[e2/x]) + // or: + // p & a = 0 & b != 0 /\_j p_j -> p[e0+eps/x] + // p & a != 0 & 0 <= d /\_j p_j -> p[e1+eps/x] \/ p[e2+eps/x] + + if (b != z()) { + sqrt_form e0(*this, mk_uminus(c), 0, z(), b); + es.reset(); + subst.reset(); + scoped_ptr rp = mk_default_expr_replacer(m()); + expr_substitution sub(m()); + sub.insert(a, z()); + rp->set_substitution(&sub); + if (a != z()) es.push_back(mk_eq(a)); + es.push_back(mk_ne(b)); + cond = mk_and(es.size(), es.c_ptr()); + es.push_back(bc.preds(i)); + for (unsigned j = 0; j < polys.size(); ++j) { + mk_subst(cmp, polys[j], comps[j], e0, t1); + (*rp)(t1, t2); + es.push_back(m().mk_implies(bc.preds(j), t2)); + subst.push_back(t2); + } + branch = mk_and(es.size(), es.c_ptr()); + bc.add_branch(branch, cond, subst, mk_def(cmp, abc_poly(*this, z(), b, c), e0), a, b, c); + } + + if (a != z()) { + sqrt_form e1(*this, mk_uminus(b), 1, d, a2); + sqrt_form e2(*this, mk_uminus(b), -1, d, a2); + es.reset(); + subst.reset(); + es.push_back(mk_ne(a)); + es.push_back(mk_ge(d)); + cond = mk_and(es.size(), es.c_ptr()); + es.push_back(bc.preds(i)); + for (unsigned j = 0; j < polys.size(); ++j) { + mk_subst(cmp, polys[j], comps[j], e1, t1); + es.push_back(m().mk_implies(bc.preds(j), t1)); + subst.push_back(t1); + } + branch = mk_and(es.size(), es.c_ptr()); + bc.add_branch(branch, cond, subst, mk_def(cmp, abc_poly(*this, a, b, c), e1), a, b, c); + TRACE("nlarith", tout << mk_pp(branch,m()) << "\n";); + + TRACE("nlarith", + tout << "0 <= " << mk_pp(d,m()) << "\n"; + tout << mk_pp(mk_ge(d), m()) << "\n";); + + es.resize(3); + subst.reset(); + for (unsigned j = 0; j < polys.size(); ++j) { + mk_subst(cmp, polys[j], comps[j], e2, t1); + es.push_back(m().mk_implies(bc.preds(j), t1)); + subst.push_back(t1); + } + branch = mk_and(es.size(), es.c_ptr()); + bc.add_branch(branch, cond, subst, mk_def(cmp, abc_poly(*this, a, b, c), e2), a, b, c); + TRACE("nlarith", tout << mk_pp(branch,m()) << "\n";); + } + } + + bool is_strict(comp c) const { + return c == LT || c == NE; + } + void mk_subst(comp c1, poly const& p, comp c, sqrt_form const& e, app_ref& r) { + sqrt_subst sub(*this, e); + if (is_strict(c1)) { + plus_eps_subst sub2(*this, sub); + apply_subst(sub2, c, p, r); + } + else { + apply_subst(sub, c, p, r); + } + TRACE("nlarith_verbose", + display(tout, p); + display(tout, c); + e.display(tout << " 0 "); + tout << " --> " << mk_pp(r.get(), m()) << "\n";); + } + + void get_coefficients(poly const& p, app*& a, app*& b, app*& c) { + a = b = c = z(); + if (p.size() > 0) c = p[0]; + if (p.size() > 1) b = p[1]; + if (p.size() > 2) a = p[2]; + SASSERT(p.size() <= 3); + } + + bool is_le(expr* e, expr*& s, expr*& t) { + return a().is_ge(e, t, s) || a().is_le(e, s, t); + } + bool is_lt(expr* e, expr*& s, expr*& t) { + return a().is_gt(e, t, s) || a().is_lt(e, s, t); + } + + bool is_degree_two_plus(polys const& ps) { + for (unsigned i = 0; i < ps.size(); ++i) { + if (ps[i].size() > 3) { + TRACE("nlarith", tout << "not second-degree: "; + display(tout, ps[i]); tout <<"\n"; ); + return true; + } + } + return false; + } + + bool is_linear(polys& ps) const { + rational n; + for (unsigned i = 0; i < ps.size(); ++i) { + if (ps[i].size() > 2) return false; + if (ps[i].size() == 2) { + if (is_numeral(ps[i][1].get(), n)) { + ps[i][1] = m_arith.mk_numeral(n, false); + } + else { + return false; + } + } + } + return true; + } + + + bool has_single_degree2(polys const& ps, comps const& comps, unsigned& idx) const { + unsigned n = 0; + for (unsigned i = 0; i < ps.size(); ++i) { + if (ps[i].size() == 3) { + ++n; + idx = i; + if (comps[i] == EQ) { + return false; + } + } + } + return n == 1; + } + /** + \brief Create branch conditions for each atomic formulI. + */ + bool get_polys(contains_app& contains_x, unsigned num_lits, expr* const* lits, + polys& polys, comps& comps, branch_conditions* bc, + app_ref_vector* literals) { + ast_manager& M = m(); + expr* e1, *e2, *e3; + app_ref t(M); + poly p(M); + comp c; + + for (unsigned i = 0; i < num_lits; ++i) { + if (!contains_x(lits[i])) { + continue; + } + // e1 <= e2 + if (is_le(lits[i], e1, e2)) { + t = mk_sub(e1, e2); + c = LE; + } + // ! (e2 <= e3) <=> e3 < e2 + else if (M.is_not(lits[i], e1) && is_le(e1, e2, e3)) { + t = mk_sub(e3, e2); + c = LT; + } + // e1 < e2 + else if (is_lt(lits[i], e1, e2)) { + t = mk_sub(e1, e2); + c = LT; + } + // ! (e2 < e3) <=> e3 <= e2 + else if (M.is_not(lits[i], e1) && is_lt(e1, e2, e3)) { + t = mk_sub(e3, e2); + c = LE; + } + else if (M.is_eq(lits[i], e1, e2)) { + t = mk_sub(e1, e2); + c = EQ; + } + else if (M.is_not(lits[i], e1) && M.is_eq(e1, e2, e3)) { + t = mk_sub(e2, e3); + c = NE; + } + else { + return false; + } + if (!get_decomposition(t, contains_x, p)) { + return false; + } + polys.push_back(p); + comps.push_back(c); + if (bc) { + bc->add_pred(lits[i]); + } + if (literals) { + literals->push_back(to_app(lits[i])); + } + TRACE("nlarith_verbose", + tout << mk_pp(lits[i], m()) << " -> "; + display(tout, p); tout << "\n"; ); + } + return true; + } + + + void display(std::ostream& out, poly const& p) { + out << "("; + for (unsigned i = 0; i < p.size(); ++i) { + out << i << ": " << mk_pp(p[i], m()); + if (i + 1 < p.size()) out << ", "; + } + out << ")"; + } + + void display(std::ostream& out, polys const& ps) { + for (unsigned i = 0; i < ps.size(); ++i) { + display(out, ps[i]); + out << " "; + } + } + + + bool is_numeral(expr* t, rational& n) const { + if (!is_app(t)) return false; + app* e = to_app(t); + func_decl* f = e->get_decl(); + if (f->get_family_id() != m_arith.get_family_id()) { + return false; + } + rational m; + switch(f->get_decl_kind()) { + case OP_ADD: +#define MK_AOP(_mk_op_) \ + SASSERT(e->get_num_args() > 0); \ + if (!is_numeral(e->get_arg(0), n)) return false; \ + for (unsigned i = 1; i < e->get_num_args(); ++i) { \ + if (!is_numeral(e->get_arg(i), m)) return false; \ + n = n _mk_op_ m; \ + } + MK_AOP(+); + return true; + case OP_MUL: + MK_AOP(*); + return true; + case OP_SUB: + MK_AOP(-); + return true; + case OP_UMINUS: + if (!is_numeral(e->get_arg(0), n)) return false; + n.neg(); + return true; + case OP_NUM: + return m_arith.is_numeral(e, n); + default: + return false; + } + } + /** + \brief Decompose polynomial into sum of powers of 'x'. + + p = result[0] + x*result[1] + x*x*result[2] + ... + */ + bool get_decomposition(expr* t, contains_app& contains_x, poly& result) { + result.reset(); + if (!is_app(t)) { + return false; + } + app* e = to_app(t); + if (!contains_x(e)) { + result.push_back(e); + return true; + } + if (contains_x.x() == e) { + result.push_back(z()); + result.push_back(one()); + return true; + } + + func_decl* f = e->get_decl(); + if (f->get_family_id() != a().get_family_id()) { + return false; + } + poly r(m()); + switch(f->get_decl_kind()) { + case OP_ADD: +#define MK_OP(_mk_op_) \ + SASSERT(e->get_num_args() > 0); \ + if (!get_decomposition(e->get_arg(0), contains_x, result)) return false;\ + for (unsigned i = 1; i < e->get_num_args(); ++i) { \ + if (!get_decomposition(e->get_arg(i), contains_x, r)) return false; \ + _mk_op_(result, r); \ + } + MK_OP(mk_add); + return true; + case OP_MUL: + MK_OP(mk_mul); + return true; + case OP_SUB: + MK_OP(mk_sub); + return true; + case OP_UMINUS: + if(!get_decomposition(e->get_arg(0), contains_x, result)) return false; + mk_uminus(result); + return true; + default: + TRACE("nlarith", tout << "Cannot decompose " << mk_pp(f, m()) << "\n" << mk_pp(e, m()) << "\n";); + return false; + } + } + + void mk_uminus(poly& p) { + for (unsigned i = 0; i < p.size(); ++i) { + p[i] = mk_uminus(p[i].get()); + } + } + + void mk_sub(poly& r, poly const& other) { + for (unsigned i = 0; i < r.size() && i < other.size(); ++i) { + r[i] = mk_sub(r[i].get(), other[i]); + } + for (unsigned i = r.size(); i < other.size(); ++i) { + r.push_back(mk_uminus(other[i])); + } + } + + void mk_add(poly& r, poly const& other) { + for (unsigned i = 0; i < r.size() && i < other.size(); ++i) { + r[i] = mk_add(r[i].get(), other[i]); + } + for (unsigned i = r.size(); i < other.size(); ++i) { + r.push_back(other[i]); + } + } + + // r[0]*o[0] + // r[0]*o[1] + r[1]*o[0] + // r[0]*o[2] + r[1]*o[1] + r[2]*o[0] + // + // r[sz-1]*o[sz-2] + r[sz-2]*o[sz-1] + // r[sz-1]*o[sz-1] + void mk_mul(poly& r, poly const& other) { + poly result(m()); + for (unsigned i = 0; i + 1 < r.size() + other.size(); ++i) { + app_ref t(z(), m()); + for (unsigned j = 0; j <= i && j < r.size(); ++j) { + unsigned k = i - j; + if (k < other.size()) { + t = mk_add(t, mk_mul(r[j].get(),other[k])); + } + } + result.push_back(t); + } + TRACE("nlarith_verbose", display(tout, r); display(tout <<" * ", other); display(tout << " = ", result); tout <<"\n";); + r.reset(); + r.append(result.size(), result.c_ptr()); + } + + void mk_mul(poly& p, expr* e) { + for (unsigned i = 0; i < p.size(); ++i) { + p[i] = mk_mul(p[i].get(), e); + } + } + + void mk_add(poly& p, unsigned shift, expr* e) { + while (p.size() <= shift) { + p.push_back(z()); + } + p[shift] = mk_add(p[shift].get(), e); + } + + + /** + \brief Symbolic differentiation with respect to 'x'. + + result = [p[1], 2*p[2], + 3*p[3],..,(num_terms-1)*p[num_terms-1]] + + */ + + void mk_differentiate(poly const& p, app_ref_vector& result) { + for (unsigned i = 1; i < p.size(); ++i) { + result.push_back(mk_mul(num(i), p[i])); + } + } + + class isubst { + protected: + imp& m_imp; + public: + isubst(imp& i) : m_imp(i) {} + virtual void mk_lt(poly const& p, app_ref& r) = 0; + virtual void mk_eq(poly const& p, app_ref& r) = 0; + virtual void mk_le(poly const& p, app_ref& r) { + imp& I = m_imp; + app_ref r1(I.m()), r2(I.m()); + mk_lt(p, r1); + mk_eq(p, r2); + r = I.mk_or(r1, r2); + } + virtual void mk_ne(poly const& p, app_ref& r) { + imp& I = m_imp; + mk_eq(p, r); + r = I.m().mk_not(r); + } + }; + + void apply_subst(isubst& sub, comp c, poly const& p, app_ref& r) { + switch(c) { + case EQ: sub.mk_eq(p, r); return; + case LE: sub.mk_le(p, r); return; + case LT: sub.mk_lt(p, r); return; + case NE: sub.mk_ne(p, r); return; + } + } + + class basic_subst : public isubst { + app* m_x; + public: + basic_subst(imp& i, app* x) : isubst(i), m_x(x) {} + virtual void mk_lt(poly const& p, app_ref& r) { + imp& I = m_imp; + app_ref result(I.m()); + I.mk_polynomial(m_x, p, result); + r = I.mk_lt(result); + } + virtual void mk_eq(poly const& p, app_ref& r) { + imp& I = m_imp; + app_ref result(I.m()); + I.mk_polynomial(m_x, p, result); + r = I.mk_eq(result); + } + }; + + class sqrt_subst : public isubst { + bool m_even; + sqrt_form const& m_s; + public: + sqrt_subst(imp& i, sqrt_form const& s): isubst(i), m_s(s) {} + + // p[e/x] < 0: (a*parity(d) < 0 /\ 0 < a*a - b*b*c) \/ + // (b*parity(d) <= 0 /\ (a*parity(d) < 0 \/ a*a - b*b*c < 0)) + virtual void mk_lt(poly const& p, app_ref& r) { + imp& I = m_imp; + ast_manager& m = I.m(); + app_ref a(m), b(m), c(m_s.m_c), d(m); + I.mk_instantiate(p, m_s, a, b, d); + app_ref ad(a, m), bd(b, m), aabbc(m); + if (is_even(p.size())) { + ad = I.mk_mul(a, d); + bd = I.mk_mul(b, d); + } + if (m_s.m_b == 0) { + r = I.mk_lt(ad); + } + else { + aabbc = I.mk_sub(I.mk_mul(a,a), I.mk_mul(b,b,c)); + r = I.mk_or(I.mk_and(I.mk_lt(ad), I.mk_gt(aabbc)), + I.mk_and(I.mk_le(bd), I.mk_or(I.mk_lt(ad), I.mk_lt(aabbc)))); + } + } + + + // p[e/x] = 0: a*b <= 0 & a*a - b*b*c = 0 + virtual void mk_eq(poly const& p, app_ref& r) { + imp& I = m_imp; + ast_manager& m = I.m(); + app_ref a(m), b(m), c(m_s.m_c),d(m), aabbc(m); + I.mk_instantiate(p, m_s, a, b, d); + if (m_s.m_b == 0) { + r = I.mk_eq(a); + } + else { + aabbc = I.mk_sub(I.mk_mul(a, a), I.mk_mul(b, b, c)); + r = I.mk_and(I.mk_le(I.mk_mul(a, b)), I.mk_eq(aabbc)); + } + } + + // p[e/x] <= 0: a*parity(d) <= 0 /\ 0 <= a*a - b*b*c \/ b*parity(d) <= 0 /\ a*a - b*b*c <= 0 + virtual void mk_le(poly const& p, app_ref& r) { + imp& I = m_imp; + ast_manager& m = I.m(); + app_ref a(m), b(m), c(m_s.m_c), d(m); + I.mk_instantiate(p, m_s, a, b, d); + app_ref ad(a, m), bd(b, m), aabbc(m); + if (is_even(p.size())) { + ad = I.mk_mul(a, d); + bd = I.mk_mul(b, d); + } + if (m_s.m_b == 0) { + r = I.mk_le(ad); + } + else { + aabbc = I.mk_sub(I.mk_mul(a, a), I.mk_mul(b, b, c)); + r = I.mk_or(I.mk_and(I.mk_le(ad), I.mk_ge(aabbc)), + I.mk_and(I.mk_le(bd), I.mk_le(aabbc))); + } + } + }; + + class plus_eps_subst : public isubst { + isubst& m_s; + /** + \brief compute nu(p): + nu(p) = p < 0 if degree(x) = 0 + nu(p) = p < 0 \/ (p = 0 /\ nu(p')) + + Then p(x+epsilon) < 0 iff nu(p(x)) + */ + + void mk_nu(poly const& p, app_ref& r) { + imp& I = m_imp; + ast_manager& m = I.m(); + app_ref_vector t1(m); + app_ref t3(m), t4(m); + m_s.mk_lt(p, r); + if (p.size() > 1) { + m_s.mk_eq(p, t3); + I.mk_differentiate(p, t1); + mk_nu(t1, t4); + // p < 0 \/ (p = 0 /\ nu(p')) + r = I.mk_or(r, I.mk_and(t3, t4)); + } + } + + public: + plus_eps_subst(imp& i, isubst& s) : isubst(i), m_s(s) {} + + virtual void mk_lt(poly const& p, app_ref& r) { mk_nu(p, r); } + + // /\ p[i] = 0 + virtual void mk_eq(poly const& p, app_ref& r) { r = m_imp.mk_zero(p); } + }; + + class minus_eps_subst : public isubst { + isubst& m_s; + /** + \brief compute nu(p): + nu(p, t) = p < 0 if degree(x) = 0 + nu(p, f) = p > 0 if degree(x) = 0 + nu(p, t) = p < 0 \/ (p = 0 /\ nu(p', f)) + nu(p, f) = p > 0 \/ (p = 0 /\ nu(p', t)) + + Then p(x-epsilon) < 0 iff nu(p(x), t) + */ + + void mk_lt(poly const& p, bool even, app_ref& r) { + imp& I = m_imp; + if (even) { + m_s.mk_lt(p, r); + } + else { + poly p1(p); + I.mk_uminus(p1); + m_s.mk_lt(p1, r); + } + } + + void mk_nu(poly const& p, bool even, app_ref& r) { + imp& I = m_imp; + ast_manager& m = I.m(); + app_ref_vector t1(m); + app_ref t3(m), t4(m); + mk_lt(p, even, r); + if (p.size() > 1) { + m_s.mk_eq(p, t3); + I.mk_differentiate(p, t1); + mk_nu(t1, !even, t4); + // p < 0 \/ (p = 0 /\ nu(p')) + r = I.mk_or(r, I.mk_and(t3, t4)); + } + } + public: + minus_eps_subst(imp& i, isubst& s) : isubst(i), m_s(s) {} + + virtual void mk_lt(poly const& p, app_ref& r) { mk_nu(p, true, r); } + + // /\ p[i] = 0 + virtual void mk_eq(poly const& p, app_ref& r) { r = m_imp.mk_zero(p); } + }; + + class minus_inf_subst : public isubst { + /** + \brief compute mu(p) given by. + + p = p[0] + x*p[1] + x*x*p[2] + ... + + mu(p) = p[num_terms-1]*(-1)^(parity num_terms-1) < 0 \/ + p[num_terms-1] = 0 /\ + mu(num_terms-1, terms) + */ + + app* mk_lt(poly const& p, unsigned i) { + imp& I = m_imp; + ast_manager& m = I.m(); + if (i == 0) { + return m.mk_false(); + } + --i; + expr* t = p[i]; + app* e = is_even(i)?I.mk_lt(t):I.mk_gt(t); + if (i == 0) { + return e; + } + else { + return I.mk_or(e, I.mk_and(I.mk_eq(t), mk_lt(p, i))); + } + } + public: + minus_inf_subst(imp& i) : isubst(i) {} + + virtual void mk_lt(poly const& p, app_ref& r) { + r = mk_lt(p, p.size()); + } + + // /\ p[i] = 0 + virtual void mk_eq(poly const& p, app_ref& r) { r = m_imp.mk_zero(p); } + }; + + + class plus_inf_subst : public isubst { + + app* mk_lt(poly const& p, unsigned i) { + imp& I = m_imp; + ast_manager& m = I.m(); + if (i == 0) { + return m.mk_false(); + } + --i; + expr* t = p[i]; + app* e = I.mk_lt(t); + if (i == 0) { + return e; + } + else { + return I.mk_or(e, I.mk_and(I.mk_eq(t), mk_lt(p, i))); + } + } + public: + plus_inf_subst(imp& i) : isubst(i) {} + + virtual void mk_lt(poly const& p, app_ref& r) { r = mk_lt(p, p.size()); } + + // /\ p[i] = 0 + virtual void mk_eq(poly const& p, app_ref& r) { r = m_imp.mk_zero(p); } + }; + + /** + \brief create polynomail expression. + + result = p[0] + x*p[1] + x*x*p[2] + ... + */ + void mk_polynomial(app* x, poly const& p, app_ref& result) { + if (p.empty()) { + result = z(); + return; + } + app_ref xx(x, m()); + expr_ref_vector tmp(m()); + tmp.push_back(p[0]); + for (unsigned i = 1; i < p.size(); ++i) { + tmp.push_back(mk_mul(xx.get(), p[i])); + xx = mk_mul(x, xx.get()); + } + result = mk_add(tmp.size(), tmp.c_ptr()); + } + + app* mk_zero(poly const& p) { + app_ref_vector tmp(m()); + mk_zero(p, tmp); + return mk_and(tmp.size(), reinterpret_cast(tmp.c_ptr())); + } + + void mk_zero(poly const& p, app_ref_vector& zeros) { + for (unsigned i = 0; i < p.size(); ++i) { + zeros.push_back(mk_eq(p[i])); + } + } + + + /** + \brief Formal replacement of x by (a + b*sqrt(c))/d in p. + + where: + p = p[0] + x*p[1] + x*x*p[2] + ... + + The result is an expression (a' + b'*sqrt(c))/d' + */ + + void mk_instantiate(poly const& p, + sqrt_form const& s, + app_ref& ar, app_ref& br, app_ref& dr) { + app* a = s.m_a, *c = s.m_c, *d = s.m_d; + app_ref b(num(s.m_b), m()); + br = z(); + dr = one(); + if (p.empty()) { + ar = z(); + return; + } + unsigned i = p.size() - 1; + ar = p[i]; + while (i > 0) { + --i; + // compute + // p[i] + x * (ar + br*sqrt(c))/dr + // = + // p[i] + (a + b*sqrt(c))/d * (ar + br*sqrt(c))/dr + // = + // p[i] + (a*ar + b*br*c + (a*br + ar*b)*sqrt(c))/d*dr + // = + // (d*dr*p[i] + a*ar + b*br*c + (a*br + ar*b)*sqrt(c))/d*dr + // + app_ref tmp1(mk_add(mk_mul(d, dr, p[i]), mk_mul(a, ar), mk_mul(b, br, c)), m()); + br = mk_add(mk_mul(a, br), mk_mul(ar, b)); + dr = mk_mul(d, dr); + ar = tmp1; + } + TRACE("nlarith_verbose", + display(tout, p); + s.display(tout << " "); + tout << " " << mk_pp(ar, m()) << " " << mk_pp(br, m()) << " " << mk_pp(dr, m()) << "\n";); + } + + static bool is_even(unsigned n) { return 0 == (n&0x1); } + + bool is_variable(app* e) { + return + a().is_real(e) && + e->get_family_id() == null_family_id && + e->get_num_args() == 0; + } + + bool is_arithmetical(app* e) { + if (e->get_family_id() == m().get_basic_family_id()) { + return true; + } + if (e->get_family_id() == a().get_family_id()) { + return true; + } + return false; + } + + bool is_nonlinear(app* e) { + if (a().is_mul(e)) { + unsigned n = 0; + for (unsigned i = 0; n < 2 && i < e->get_num_args(); ++i) { + if (!a().is_numeral(e->get_arg(i))) { + ++n; + } + } + return n == 2; + } + return false; + } + + private: + + // u = v*q + r + void quot_rem(poly const& u, poly const& v, poly& q, poly& r, app_ref& lc, unsigned& power) { + lc = v.empty()?num(0):v[v.size()-1]; + power = 0; + if (u.size() < v.size() || v.size() == 0) { + q.reset(); + r.reset(); + r.append(u); + return; + } + SASSERT(u.size() >= v.size() && v.size() > 0); + unsigned n = v.size()-1; + if (a().is_numeral(v[n])) { + numeric_quot_rem(u, v, q, r); + } + else { + pseudo_quot_rem(u, v, q, r, power); + } + } + + // + // Compute q and r such that + // u = v*q + r, + // assuming the leading coefficient of v is a numeral. + // + void numeric_quot_rem(poly const& u, poly const& v, poly& q, poly& r) { + SASSERT(u.size() > 0 && v.size() > 0); + unsigned m = u.size()-1, n = v.size()-1; + q.reset(); + r.reset(); + r.append(u); + rational v_n; + VERIFY(a().is_numeral(v[n], v_n)); + app_ref v_inv(a().mk_numeral(rational(1)/v_n, false), m_manager); + bool is_one = v_n.is_one(); + for (int k = m-n+1; k > 0; ) { + --k; + if (is_one) { + q[k] = u[n+k]; + } + else { + q[k] = mk_mul(u[n+k], v_inv.get()); + } + for (int j = n + k - 1; j >= k; --j) { + r[j] = mk_sub(r[j].get(), mk_mul(q[k].get(), v[j-k])); + } + } + SASSERT(test_quot_rem(u, v, q, r)); + } + + // + // Compute q and r such that + // lc(v)^{m-n+1}*u = v*q + r, + // where lc(v) is the leading coefficient of v + // of degree 'n' and the most significant coefficient + // in u has degree 'm'. + // + void pseudo_quot_rem(poly const& u, poly const& v, poly& q, poly& r, unsigned& power) { + SASSERT(u.size() > 0 && v.size() > 0); + unsigned m = u.size()-1, n = v.size()-1; + app* v_n = v[n]; + power = m- n + 1; + q.reset(); + r.reset(); + r.append(u); + q.resize(m-n+1); + poly powers_v(m_manager); + powers_v.push_back(num(1)); + for (unsigned i = 1; i < m-n+2; ++i) { + powers_v[i] = mk_mul(powers_v[i-1].get(), v_n); + } + for (int k = m-n+1; k > 0; ) { + --k; + q[k] = mk_mul(u[n+k], powers_v[k].get()); + for (int j = n + k; j > 0; ) { + --j; + r[j] = mk_mul(v_n, r[j].get()); // n + k != j + if (j >= k) { + r[j] = mk_sub(r[j].get(), mk_mul(r[n+k].get(), v[j-k])); + } + } + } + DEBUG_CODE( + poly u1(u); + mk_mul(u1, powers_v[m-n+1].get()); + SASSERT(test_quot_rem(u1, v, q, r)); + ); + } + + // validate: u = q*v + r + bool test_quot_rem(poly const& u, poly const& v, poly const& q, poly const& r) { + poly u1(u), q1(q); + mk_mul(q1, v); + mk_add(q1, r); + mk_sub(q1, u); + for (unsigned i = 0; i < q1.size(); ++i) { + if (z() != q1[i].get()) { + TRACE("nlarith", display(tout, q1);); + return false; + } + } + return true; + } + + /** + \brief create case split predicates for polynomial elimination. + + */ + + void mk_derivative(poly& p) { + if(p.empty()) { + return; + } + if (p.size() > 1) { + p[0] = p[1].get(); + for (unsigned i = 1; i + 1 < p.size(); ++i) { + p[i] = mk_mul(num(i), p[i+1].get()); + } + } + p.resize(p.size()-1); + } + + void mk_derivative(unsigned k, poly& p) { + for (unsigned i = 0; i < k; ++i) { + mk_derivative(p); + } + } + + void mk_inf_sign(isubst& sub, util::literal_set const& literals, app_ref& fml, app_ref_vector& new_atoms) { + new_atoms.reset(); + expr_ref_vector equivs(m()); + app_ref tmp(m()); + for (unsigned i = 0; i < literals.size(); ++i) { + if (literals.compare(i) == EQ) { + continue; + } + apply_subst(sub, literals.compare(i), literals.get_poly(i), tmp); + equivs.push_back(m().mk_implies(literals.literal(i), tmp)); + new_atoms.push_back(tmp); + } + fml = mk_and(equivs.size(), equivs.c_ptr()); + } + void mk_plus_inf_sign(util::literal_set const& literals, app_ref& fml, app_ref_vector& new_atoms) { + plus_inf_subst sub(*this); + mk_inf_sign(sub, literals, fml, new_atoms); + } + void mk_minus_inf_sign(util::literal_set const& literals, app_ref& fml, app_ref_vector& new_atoms) { + minus_inf_subst sub(*this); + mk_inf_sign(sub, literals, fml, new_atoms); + } + + + // one of the literals is 0 at x_sup and x_inf, other + // literals have their derivative close. + + void mk_bound(util::literal_set& literals, app_ref& fml, app_ref_vector& new_atoms) { + new_atoms.reset(); + app_ref tmp(m()); + expr_ref_vector conjs(m()); + mk_exists_zero(literals, true, 0, conjs, new_atoms); + mk_same_sign (literals, true, conjs, new_atoms); + mk_exists_zero(literals, false, 0, conjs, new_atoms); + mk_same_sign (literals, false, conjs, new_atoms); + mk_lt(literals.x(), literals.x_inf(), conjs, new_atoms); + mk_lt(literals.x_sup(), literals.x(), conjs, new_atoms); + fml = mk_and(conjs.size(), conjs.c_ptr()); + } + void mk_lt(app* x, app* y, expr_ref_vector& conjs, app_ref_vector& new_atoms) { + app* atm = mk_lt(mk_sub(x,y)); + new_atoms.push_back(atm); + conjs.push_back(atm); + } + void mk_exists_zero(util::literal_set& literals, bool is_sup, poly const* p1, expr_ref_vector& conjs, app_ref_vector& new_atoms) { + app* x = is_sup?literals.x_sup():literals.x_inf(); + expr_ref_vector ors(m()); + app_ref fml(m()); + basic_subst sub(*this, x); + for (unsigned i = 0; i < literals.size(); ++i) { + if (literals.compare(i) == EQ) { + continue; + } + apply_subst(sub, EQ, literals.get_poly(i), fml); + new_atoms.push_back(fml); + ors.push_back(fml); + } + if (p1) { + apply_subst(sub, EQ, *p1, fml); + new_atoms.push_back(fml); + ors.push_back(fml); + } + conjs.push_back(mk_or(ors.size(), ors.c_ptr())); + } + + /* + z < x < y: + z is sup, y is inf + /\_j (p_j(x) < 0 -> p_j(z) < 0 \/ p_j(z) = 0 /\ p'_j(z) < 0) /\ + /\_j (p_j(x) < 0 -> p_j(y) < 0 \/ p'_j(y) = 0 /\ -p'_j(y) < 0) + */ + void mk_same_sign(util::literal_set& literals, bool is_sup, expr_ref_vector& conjs, app_ref_vector& new_atoms) { + app* x = is_sup?literals.x_sup():literals.x_inf(); + app_ref fml(m()); + for (unsigned i = 0; i < literals.size(); ++i) { + switch(literals.compare(i)) { + case EQ: + break; + case LT: + mk_same_sign( + x, is_sup, + literals.get_poly(i), literals.literal(i), + fml, new_atoms); + conjs.push_back(fml); + break; + default: + UNREACHABLE(); + break; + } + } + } + + void mk_same_sign(app* x, bool is_sup, poly const& p, app* l, + app_ref& fml, app_ref_vector& new_atoms) { + basic_subst sub0(*this, x); + if (is_sup) { + plus_eps_subst sub(*this, sub0); + apply_subst(sub, LT, p, fml); + } + else { + minus_eps_subst sub(*this, sub0); + apply_subst(sub, LT, p, fml); + } + collect_atoms(fml, new_atoms); + fml = m().mk_implies(l, fml); + } + + void collect_atoms(app* fml, app_ref_vector& atoms) { + ptr_vector todo; + todo.push_back(fml); + while (!todo.empty()) { + fml = todo.back(); + todo.pop_back(); + if (m().is_and(fml) || m().is_or(fml)) { + unsigned sz = fml->get_num_args(); + for (unsigned i = 0; i < sz; ++i) { + todo.push_back(to_app(fml->get_arg(i))); + } + } + else { + atoms.push_back(fml); + } + } + } + + class simple_branch : public util::branch { + app_ref m_cnstr; + app_ref_vector m_atoms; + svector m_updates; + public: + simple_branch(ast_manager& m, app* cnstr): + m_cnstr(cnstr, m), m_atoms(m) {} + virtual ~simple_branch() {} + virtual app* get_constraint() { return m_cnstr.get(); } + virtual void get_updates(ptr_vector& atoms, svector& updates) { + for (unsigned i = 0; i < m_atoms.size(); ++i) { + atoms.push_back(m_atoms[i].get()); + updates.push_back(m_updates[i]); + } + } + void update(app* a, util::atom_update u) { + m_atoms.push_back(a); + m_updates.push_back(u); + } + void insert(app* a) { update(a, util::INSERT); } + void remove(app* a) { update(a, util::REMOVE); } + }; + + class ins_rem_branch : public simple_branch { + public: + ins_rem_branch(ast_manager& m, app* a, app* r, app* cnstr): + simple_branch(m, cnstr) { insert(a); remove(r); } + virtual ~ins_rem_branch() {} + }; + + /** + \brief Compute branches given u = 0 & v = 0. + u has degree m, v has degree n. + m >= n + 1. u = 0 & lc(v) = 0 & v' = 0 remove v = 0, add v' = 0 + 2. let q, r be such that, m >= n + lc(v)^{m-n+1}*u = v*q + r + then + v = 0 & r = 0 remove u = 0, add r = 0 + */ + + void get_sign_branches_eq(util::literal_set& lits, unsigned i, unsigned j, ptr_vector& branches) { + SASSERT(lits.compare(i) == EQ); + SASSERT(lits.compare(j) == EQ); + poly const* u = &lits.get_poly(i); + poly const* v = &lits.get_poly(j); + app* l0 = lits.literal(i); + app* l1 = lits.literal(j); + if (u->size() < v->size()) { + std::swap(u, v); + std::swap(l0, l1); + } + app_ref lc_v0(m()), v2_eq(m()), r_eq(m()), lc(m()); + poly v2(m()), q(m()), r(m()); + unsigned n = v->size()-1; + + basic_subst sub(*this, lits.x()); + unsigned power; + v2.set(*v); + v2.resize(n); + quot_rem(*u, *v, q, r, lc_v0, power); + lc_v0 = mk_eq(lc); + sub.mk_eq(v2, v2_eq); + sub.mk_eq(r, r_eq); + + branches.push_back(alloc(ins_rem_branch, m(), v2_eq, l1, mk_and(lc_v0, v2_eq))); + branches.push_back(alloc(ins_rem_branch, m(), r_eq, l0, r_eq)); + // TBD: add constraints that coefficients to l0 are non-zero? + branches.push_back(alloc(simple_branch, m(), m().mk_not(l0))); + branches.push_back(alloc(simple_branch, m(), m().mk_not(l1))); + } + + /** + \brief Compute branch where all predicates are non-zero. + + p_infty \/ p_minus_infty \/ mk_bound + + where mk_bound = + z < x < y /\ + (\/_j p_j(z) = 0) /\_j (p_j(x) < 0 -> p_j(z) < 0 \/ p_j(z) = 0 /\ p'_j(z) < 0) /\ + (\/_j p_j(y) = 0) /\_j (p_j(x) < 0 -> p_j(y) < 0 \/ p'_j(y) = 0 /\ -p'_j(z) < 0) + + p_j ranges over predicates 'p_j(x) < 0' + + */ + void get_sign_branches_neq(util::literal_set& lits, ptr_vector& branches) { + app_ref_vector new_atoms(m()); + app_ref fml(m()); + branches.push_back(mk_inf_branch(lits, true)); + branches.push_back(mk_inf_branch(lits, false)); + + mk_bound(lits, fml, new_atoms); + simple_branch* br = alloc(simple_branch, m(), fml); + swap_atoms(br, lits.lits(), new_atoms); + branches.push_back(br); + } + + util::branch* mk_inf_branch(util::literal_set& literals, bool is_pos) { + app_ref fml(m()); + app_ref_vector new_atoms(m()); + if (is_pos) { + mk_plus_inf_sign(literals, fml, new_atoms); + } + else { + mk_minus_inf_sign(literals, fml, new_atoms); + } + simple_branch* br = alloc(simple_branch, m(), fml); + swap_atoms(br, literals.lits(), new_atoms); + return br; + } + + void swap_atoms(simple_branch* br, app_ref_vector const& old_atoms, app_ref_vector const& new_atoms) { + for (unsigned i = 0; i < old_atoms.size(); ++i) { + br->remove(old_atoms[i]); + } + for (unsigned i = 0; i < new_atoms.size(); ++i) { + br->insert(new_atoms[i]); + } + } + + /** + \brief Compute branches where one equality holds. + + p != 0 \/ + lc(p) = 0 \/ + p' = 0 \/ + p_j(x) < 0 -> p_j(infty) < 0 \/ + p_j(x) < 0 -> p_j(-infty) < 0 \/ + p(z) < 0 < p(y) /\ p'(x) > 0 /\ m_bound(-p') \/ + p(y) < 0 < p(z) /\ p'(x) < 0 /\ m_bound(p') + + where mk_bound(q) = + z < x < y /\ + /\_j p_j(x) < 0 -> r_j(x) < 0 + (\/_j r_j(z) = 0) /\_j (p_j(x) < 0 -> r_j(x) < 0 /\ r_j(z-epsilon) < 0 + (\/_j r_j(y) = 0) /\_j (p_j(x) < 0 -> r_j(x) < 0 /\ r_j(y+epsilon) < 0 + + p_j ranges over predicates 'p_j(x) < 0', q(x) < 0 + r_j is obtained by quot_rem(p, p_j, q_j, r_j) + + z < x < y: + z is sup, y is inf + */ + void get_sign_branches_eq_neq(util::literal_set& lits, unsigned i, ptr_vector& branches) { + SASSERT(lits.size() > i); + SASSERT(lits.compare(i) == EQ); + poly const& p = lits.get_poly(i); + poly p1(m()); + mk_differentiate(p, p1); + app_ref eq(m()), lc_p0(m()), l1(m()); + basic_subst sub_x(*this, lits.x()); + apply_subst(sub_x, EQ, p1, eq); + lc_p0 = mk_eq(p[p.size()-1]); + poly p2(p); + p2.resize(p.size()-1); + apply_subst(sub_x, EQ, p2, l1); + + branches.push_back(alloc(simple_branch, m(), m().mk_not(lits.literal(i)))); + branches.push_back(alloc(simple_branch, m(), eq)); + branches.push_back(alloc(ins_rem_branch, m(), l1, lits.literal(i), lc_p0)); + branches.push_back(mk_inf_branch(lits, true)); + branches.push_back(mk_inf_branch(lits, false)); + branches.push_back(mk_bound_ext(lits, p, p1, lits.x())); + } + + simple_branch* mk_bound_ext(util::literal_set& lits, + poly const& p, poly const& p1, app* x) { + // + // Assuming p(x) = 0, p'(x) != 0, lc(p) != 0 + // x < y < z + // (p'(x) < 0 & p(y) < 0 < p(z) | p'(x) > 0 & p(y) > 0 > p(z)) + // \/ p'(y) = 0 \/_j p_j(y) = 0 + // \/ p'(z) = 0 \/_j p_j(z) = 0 + // /\_j p_j(x) < 0 -> sign_adjust(lc, parity, r_j(y+epsilon)) + // /\_j p_j(x) < 0 -> sign_adjust(lc, parity, r_j(z-epsilon)) + // p'(x) < 0 -> r(y+epsilon) < 0 & r(z-epsilon) < 0 + // p'(x) > 0 -> r(y+epsilon) > 0 & r(z-epsilon) > 0 + // sign_adjust(lc, even, r) = r < 0 + // sign_adjust(lc, odd, r) = (lc > 0 -> r < 0) & (lc < 0 -> r > 0) + // + + app_ref eq(m()), fml(m()), l1(m()), l2(m()), l3(m()); + app_ref p1_lt0(m()), p1_gt0(m()); + app_ref_vector new_atoms(m()); + expr_ref_vector conjs(m()); + poly p_m(p), p1_m(p1); + mk_uminus(p_m); + mk_uminus(p1_m); + + mk_lt(lits.x(), lits.x_inf(), conjs, new_atoms); // y < x < z + mk_lt(lits.x_sup(), lits.x(), conjs, new_atoms); + basic_subst sub_x(*this, x); + basic_subst sub_y(*this, lits.x_sup()); + basic_subst sub_z(*this, lits.x_inf()); + apply_subst(sub_y, LT, p, l1); // p(y) < 0 + apply_subst(sub_z, LT, p_m,l2); // 0 < p(z) + apply_subst(sub_x, LT, p1_m, p1_gt0); // p1(x) > 0 + new_atoms.push_back(l1); + new_atoms.push_back(l2); + new_atoms.push_back(p1_gt0); + conjs.push_back(m().mk_implies(p1_gt0, mk_and(l1, l2))); // p'(x) > 0 -> p(y) < 0 < p(z) + + apply_subst(sub_y, LT, p_m, l1); // p(y) > 0 + apply_subst(sub_z, LT, p, l2); // 0 > p(z) + apply_subst(sub_x, LT, p1, p1_lt0); // p1(x) < 0 + new_atoms.push_back(l1); + new_atoms.push_back(l2); + new_atoms.push_back(p1_lt0); + conjs.push_back(m().mk_implies(p1_lt0, mk_and(l1, l2))); // p'(x) < 0 -> p(y) > 0 > p(z) + + conjs.push_back(fml); + mk_exists_zero(lits, true, &p1, conjs, new_atoms); // p'(z) = 0 \/_j p_j(z) = 0 + mk_exists_zero(lits, false, &p1, conjs, new_atoms); // p'(y) = 0 \/_j p_j(y) = 0 + + for (unsigned i = 0; i < lits.size(); ++i) { + if (lits.compare(i) == LT) { + mk_bound_ext(lits.literal(i), lits.get_poly(i), p, lits.x_sup(), lits.x_inf(), conjs, new_atoms); + } + } + // p'(x) < 0 -> r(y+epsilon) < 0 & r(z-epsilon) < 0 + // p'(x) > 0 -> r(y+epsilon) > 0 & r(z-epsilon) > 0 + mk_bound_ext(p1_lt0, p1, p, lits.x_sup(), lits.x_inf(), conjs, new_atoms); + mk_bound_ext(p1_gt0, p1_m, p, lits.x_sup(), lits.x_inf(), conjs, new_atoms); + fml = mk_and(conjs.size(), conjs.c_ptr()); + simple_branch* br = alloc(simple_branch, m(), fml); + swap_atoms(br, lits.lits(), new_atoms); + return br; + } + + void mk_bound_ext(app* l_j, poly const& p_j, poly const& p, app* y, app* z, expr_ref_vector& conjs, app_ref_vector& new_atoms) { + poly q(m()), r(m()); + app_ref eq(m()), fml(m()), l1(m()), l2(m()), l3(m()), l4(m()); + // lc(p)^{m-n+1}*p_i = p*q + r + app_ref lc(m()), lc_m(m()); + basic_subst sub_y(*this, y); + basic_subst sub_z(*this, z); + unsigned power; + quot_rem(p_j, p, q, r, lc, power); + poly r_m(r); + mk_uminus(r_m); + lc_m = mk_uminus(lc); + plus_eps_subst sub_ye(*this, sub_y); + minus_eps_subst sub_ze(*this, sub_z); + + // p_j(x) < 0 -> sign_adjust(lc, parity, r_j(y+epsilon)) + // p_j(x) < 0 -> sign_adjust(lc, parity, r_j(z-epsilon)) + if ((power % 2) == 0) { + apply_subst(sub_ye, LT, r, l1); + apply_subst(sub_ze, LT, r, l2); + fml = mk_and(l1, l2); + } + else { + apply_subst(sub_ye, LT, r, l1); + apply_subst(sub_ye, LT, r_m, l2); + l1 = m().mk_implies(mk_lt(lc_m), l1); + l2 = m().mk_implies(mk_lt(lc), l2); + apply_subst(sub_ze, LT, r_m, l3); + apply_subst(sub_ze, LT, r_m, l4); + l3 = m().mk_implies(mk_lt(lc_m), l3); + l4 = m().mk_implies(mk_lt(lc), l4); + expr* args[4] = { l1, l2, l3, l4 }; + fml = mk_and(4, args); + } + collect_atoms(fml, new_atoms); + fml = m().mk_implies(l_j, fml); + conjs.push_back(fml); + } + + public: + /** + \brief Generate branch formulas depending on the current evaluation of literals. + There are 3 cases: + 1. Two or more equalities are true + 2. Precisely one equality is true + 3. No equality is true + */ + void get_sign_branches(util::literal_set& lits, util::eval& eval, + ptr_vector& branches) { + m_trail.reset(); + unsigned z1 = UINT_MAX, z2 = UINT_MAX; + for (unsigned i = 0; i < lits.size(); ++i) { + if (lits.compare(i) == EQ && l_true == eval(lits.literal(i))) { + if (z1 == UINT_MAX) { + z1 = i; + } + else { + SASSERT(z2 == UINT_MAX); + z2 = i; + break; + } + } + } + if (z1 == UINT_MAX) { + get_sign_branches_neq(lits, branches); + } + else if (z2 == UINT_MAX) { + get_sign_branches_eq_neq(lits, z1, branches); + } + else { + get_sign_branches_eq(lits, z1, z2, branches); + } + } + + + bool get_sign_literals(util::atoms const& atoms, util::eval& eval, util::literal_set*& lits) { + // TBD: use 'eval' to select non-linear literals that are relevant. + m_trail.reset(); + ptr_vector nlvars, atms; + util::atoms::iterator it = atoms.begin(), end = atoms.end(); + for (; it != end; ++it) { + atms.push_back(*it); + } + extract_non_linear(atms.size(), atms.begin(), nlvars); + if (nlvars.empty()) { + lits = 0; + return true; + } + app* x = nlvars.back(); + contains_app contains_x(m(), x); + expr* const* _atoms = (expr*const*)atms.begin(); + lits = alloc(util::literal_set, m()); + lits->set_x(x); + if (get_polys(contains_x, atms.size(), _atoms, lits->polys(), lits->comps(), 0, &lits->lits())) { + return true; + } + dealloc(lits); + lits = 0; + return false; + } + + // Sign matrix algorithm (Cohen-Hormander) + public: + enum sign { Negative, Zero, NonZero, Positive, Unknown }; + typedef svector sign_vector; + typedef vector sign_matrix; + void mk_sign_matrix(vector const& polys, sign_matrix& result) { + + } + private: + // remove points that don't contain Zero + void condense(sign_matrix& mat) { + unsigned i = 0, j = 0; + SASSERT(mat.size() % 2 == 0); + for (; i + 1 < mat.size(); i += 2) { + if (mat[i+1].contains(Zero)) { + if (i != j) { + mat[j] = mat[i]; + mat[j+1] = mat[i+1]; + } + j += 2; + } + } + mat.resize(j); + } + + // set sign of p(x) to sign of q_i(x) where p_i(x) = 0 + void infer_psign(sign_vector& pqs) { + unsigned n = pqs.size()/2; + for (unsigned i = 0; i < n; ++i) { + if (Zero == pqs[i]) { + sign s = pqs[i+n]; + pqs.resize(n); + cons(s, pqs); + return; + } + } + pqs.resize(n); + cons(Unknown, pqs); + } + + void cons(sign s, sign_vector& v) { + for (unsigned i = 0; i < v.size(); ++i) { + std::swap(s, v[i]); + } + v.push_back(s); + } + + // Deduce matrix for p, p1, .., pn from p', p1, .., pn, q0, .., qn + void deduce_matrix(sign_matrix& m) { + for (unsigned i = 0; i < m.size(); ++i) { + infer_psign(m[i]); + } + condense(m); + } + + }; + + util::util(ast_manager& m) { + m_imp = alloc(imp, m); + } + + util::~util() { dealloc(m_imp); } + + + bool util::create_branches(app* x, unsigned num_lits, expr* const* lits, branch_conditions& bc) { + return m_imp->create_branches(x, num_lits, lits, bc); + } + + void util::set_enable_linear(bool enable_linear) { m_imp->set_enable_linear(enable_linear); } + + void util::extract_non_linear(expr* e, ptr_vector& nl_vars) { + m_imp->extract_non_linear(e, nl_vars); + } + + void util::deallocate(literal_set* lits) { + dealloc(lits); + } + + bool util::get_sign_literals(atoms const& atoms, eval& ev, literal_set*& lits) { + return m_imp->get_sign_literals(atoms, ev, lits); + } + + void util::get_sign_branches(literal_set& lits, eval& ev, ptr_vector& branches) { + m_imp->get_sign_branches(lits, ev, branches); + } +}; + + diff --git a/lib/nlarith_util.h b/lib/nlarith_util.h new file mode 100644 index 000000000..a43ad2e18 --- /dev/null +++ b/lib/nlarith_util.h @@ -0,0 +1,151 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + nlarith_util.h + +Abstract: + + Utilities for nln-linear arithmetic quantifier elimination and + solving. + +Author: + + Nikolaj (nbjorner) 2011-05-13 + +Notes: + +--*/ +#ifndef _NLARITH_UTIL_H_ +#define _NLARITH_UTIL_H_ + +#include "ast.h" +#include "lbool.h" + +namespace nlarith { + + /** + \brief A summary for branch side conditions and substitutions. + + Each branch in a split comprises of: + - preds - a sequence of predicates used for the branching. + - branches - a sequence of branch side conditions + - subst - a sequence of substitutions that replace 'preds' by formulas + not containing the eliminated variable + - constraints - a sequence of side constraints to add to the main formula. + */ + class branch_conditions { + expr_ref_vector m_branches; + expr_ref_vector m_preds; + vector m_subst; + expr_ref_vector m_constraints; + expr_ref_vector m_defs; + expr_ref_vector m_a; + expr_ref_vector m_b; + expr_ref_vector m_c; + + public: + branch_conditions(ast_manager& m) : m_branches(m), m_preds(m), m_constraints(m), m_defs(m), m_a(m), m_b(m), m_c(m) {} + void add_pred(expr* p) { m_preds.push_back(p); } + void add_branch(expr* branch, expr* cond, expr_ref_vector const& subst, expr* def, expr* a, expr* b, expr* c) { + m_branches.push_back(branch); + m_constraints.push_back(cond); + m_subst.push_back(subst); + m_defs.push_back(def); + m_a.push_back(a); + m_b.push_back(b); + m_c.push_back(c); + } + expr* preds(unsigned i) const { return m_preds[i]; } + expr* branches(unsigned i) const { return m_branches[i]; } + expr* constraints(unsigned i) const { return m_constraints[i]; } + expr* def(unsigned i) const { return m_defs[i]; } + expr* a(unsigned i) const { return m_a[i]; } + expr* b(unsigned i) const { return m_b[i]; } + expr* c(unsigned i) const { return m_c[i]; } + expr_ref_vector const& subst(unsigned i) const { return m_subst[i]; } + expr_ref_vector const& branches() const { return m_branches; } + expr_ref_vector const& preds() const { return m_preds; } + vector const& subst() const { return m_subst; } + expr_ref_vector const& constraints() const { return m_constraints; } + void reset() { + m_branches.reset(); m_preds.reset(); m_subst.reset(); + m_constraints.reset(); m_defs.reset(); + m_a.reset(); m_b.reset(); m_c.reset(); + } + + unsigned size() const { return branches().size(); } + unsigned num_preds() const { return preds().size(); } + }; + + class util { + class imp; + imp* m_imp; + public: + util(ast_manager& m); + ~util(); + + /** + \brief Enable handling of linear variables. + */ + void set_enable_linear(bool enable_linear); + + /** + \brief Create branches for non-linear variable x. + */ + bool create_branches(app* x, unsigned nl, expr* const* lits, branch_conditions& bc); + /** + \brief Extract non-linear variables from ground formula. + + \requires a ground formula. + */ + void extract_non_linear(expr* e, ptr_vector& nl_vars); + + /** + \brief literal sets. Opaque state. + */ + + class literal_set; + + static void deallocate(literal_set* lits); + + + + /** + \brief Sign-based branching. v2. + */ + typedef obj_hashtable atoms; + + class eval { + public: + virtual ~eval() {} + virtual lbool operator()(app* a) = 0; + }; + + enum atom_update { INSERT, REMOVE }; + + class branch { + public: + virtual ~branch() {} + virtual app* get_constraint() = 0; + virtual void get_updates(ptr_vector& atoms, svector& updates) = 0; + }; + + /** + \brief select literals that contain non-linear variables. + */ + bool get_sign_literals(atoms const& atoms, eval& eval, literal_set*& lits); + + /** + \brief given selected literals, generate branch conditions. + */ + void get_sign_branches(literal_set& lits, eval& eval, ptr_vector& branches); + + + + }; + +}; + +#endif diff --git a/lib/nlsat_assignment.h b/lib/nlsat_assignment.h new file mode 100644 index 000000000..5169d5fb6 --- /dev/null +++ b/lib/nlsat_assignment.h @@ -0,0 +1,78 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + nlsat_assignment.h + +Abstract: + + Assignment: Var -> Algebraic Number + +Author: + + Leonardo de Moura (leonardo) 2012-01-08. + +Revision History: + +--*/ +#ifndef _NLSAT_ASSIGNMENT_H_ +#define _NLSAT_ASSIGNMENT_H_ + +#include"nlsat_types.h" +#include"algebraic_numbers.h" + +namespace nlsat { + + /** + \brief A mapping from variables to values. + This mapping is used to encode the current partial interpretation in nlsat. + */ + class assignment : public polynomial::var2anum { + scoped_anum_vector m_values; + svector m_assigned; + public: + assignment(anum_manager & _m):m_values(_m) {} + virtual ~assignment() {} + anum_manager & am() const { return m_values.m(); } + void swap(assignment & other) { + m_values.swap(other.m_values); + m_assigned.swap(other.m_assigned); + } + void set_core(var x, anum & v) { + m_values.reserve(x+1, anum()); + m_assigned.reserve(x+1, false); + m_assigned[x] = true; + am().swap(m_values[x], v); + } + void set(var x, anum const & v) { + m_values.reserve(x+1, anum()); + m_assigned.reserve(x+1, false); + m_assigned[x] = true; + am().set(m_values[x], v); + } + void reset(var x) { if (x < m_assigned.size()) m_assigned[x] = false; } + bool is_assigned(var x) const { return m_assigned.get(x, false); } + anum const & value(var x) const { return m_values[x]; } + virtual anum_manager & m() const { return am(); } + virtual bool contains(var x) const { return is_assigned(x); } + virtual anum const & operator()(var x) const { SASSERT(is_assigned(x)); return value(x); } + }; + + /** + \brief Wrapper for temporarily unassigning a given variable y. + That is, given an assignment M, M' = undef_var_assignment(M, y) is identical + to M, but y is unassigned in M' + */ + class undef_var_assignment : public polynomial::var2anum { + assignment const & m_assignment; + var m_y; + public: + undef_var_assignment(assignment const & a, var y):m_assignment(a), m_y(y) {} + virtual anum_manager & m() const { return m_assignment.am(); } + virtual bool contains(var x) const { return x != m_y && m_assignment.is_assigned(x); } + virtual anum const & operator()(var x) const { return m_assignment.value(x); } + }; +}; + +#endif diff --git a/lib/nlsat_clause.cpp b/lib/nlsat_clause.cpp new file mode 100644 index 000000000..b83485ba3 --- /dev/null +++ b/lib/nlsat_clause.cpp @@ -0,0 +1,49 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + nlsat_clause.cpp + +Abstract: + + Clauses used in the Nonlinear arithmetic satisfiability procedure. + +Author: + + Leonardo de Moura (leonardo) 2012-01-02. + +Revision History: + +--*/ +#include"nlsat_clause.h" + +namespace nlsat { + + clause::clause(unsigned id, unsigned sz, literal const * lits, bool learned, assumption_set as): + m_id(id), + m_size(sz), + m_capacity(sz), + m_learned(learned), + m_activity(0), + m_assumptions(as) { + for (unsigned i = 0; i < sz; i++) { + m_lits[i] = lits[i]; + } + } + + bool clause::contains(literal l) const { + for (unsigned i = 0; i < m_size; i++) + if (m_lits[i] == l) + return true; + return false; + } + + bool clause::contains(bool_var v) const { + for (unsigned i = 0; i < m_size; i++) + if (m_lits[i].var() == v) + return true; + return false; + } + +}; diff --git a/lib/nlsat_clause.h b/lib/nlsat_clause.h new file mode 100644 index 000000000..549cb69fa --- /dev/null +++ b/lib/nlsat_clause.h @@ -0,0 +1,61 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + nlsat_clause.h + +Abstract: + + Clauses used in the Nonlinear arithmetic satisfiability procedure. + +Author: + + Leonardo de Moura (leonardo) 2012-01-02. + +Revision History: + +--*/ +#ifndef _NLSAT_CLAUSE_H_ +#define _NLSAT_CLAUSE_H_ + +#include"nlsat_types.h" +#include"vector.h" + +namespace nlsat { + + class clause { + friend class solver; + unsigned m_id; + unsigned m_size; + unsigned m_capacity:31; + unsigned m_learned:1; + unsigned m_activity; + assumption_set m_assumptions; + literal m_lits[0]; + static size_t get_obj_size(unsigned num_lits) { return sizeof(clause) + num_lits * sizeof(literal); } + size_t get_size() const { return get_obj_size(m_capacity); } + clause(unsigned id, unsigned sz, literal const * lits, bool learned, assumption_set as); + public: + unsigned size() const { return m_size; } + unsigned id() const { return m_id; } + literal & operator[](unsigned idx) { SASSERT(idx < m_size); return m_lits[idx]; } + literal const & operator[](unsigned idx) const { SASSERT(idx < m_size); return m_lits[idx]; } + bool is_learned() const { return m_learned; } + literal * begin() { return m_lits; } + literal * end() { return m_lits + m_size; } + literal const * c_ptr() const { return m_lits; } + void inc_activity() { m_activity++; } + void set_activity(unsigned v) { m_activity = v; } + unsigned get_activity() const { return m_activity; } + bool contains(literal l) const; + bool contains(bool_var v) const; + void shrink(unsigned num_lits) { SASSERT(num_lits <= m_size); if (num_lits < m_size) { m_size = num_lits; } } + assumption_set assumptions() const { return m_assumptions; } + }; + + typedef ptr_vector clause_vector; + +}; + +#endif diff --git a/lib/nlsat_evaluator.cpp b/lib/nlsat_evaluator.cpp new file mode 100644 index 000000000..92c5634a1 --- /dev/null +++ b/lib/nlsat_evaluator.cpp @@ -0,0 +1,679 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + nlsat_evaluator.cpp + +Abstract: + + Helper class for computing the infeasible intervals of an + arithmetic literal. + +Author: + + Leonardo de Moura (leonardo) 2012-01-12. + +Revision History: + +--*/ +#include"nlsat_evaluator.h" + +namespace nlsat { + + struct evaluator::imp { + assignment const & m_assignment; + pmanager & m_pm; + small_object_allocator & m_allocator; + anum_manager & m_am; + interval_set_manager m_ism; + scoped_anum_vector m_tmp_values; + scoped_anum_vector m_add_roots_tmp; + scoped_anum_vector m_inf_tmp; + + // sign tables: light version + struct sign_table { + anum_manager & m_am; + struct section { + anum m_root; + unsigned m_pos; + }; + svector
m_sections; + unsigned_vector m_sorted_sections; // refs to m_sections + unsigned_vector m_poly_sections; // refs to m_sections + svector m_poly_signs; + struct poly_info { + unsigned m_num_roots; + unsigned m_first_section; // idx in m_poly_sections; + unsigned m_first_sign; // idx in m_poly_signs; + poly_info(unsigned num, unsigned first_section, unsigned first_sign): + m_num_roots(num), + m_first_section(first_section), + m_first_sign(first_sign) { + } + }; + svector m_info; + + sign_table(anum_manager & am):m_am(am) {} + + ~sign_table() { + reset(); + } + + void reset() { + unsigned sz = m_sections.size(); + for (unsigned i = 0; i < sz; i++) + m_am.del(m_sections[i].m_root); + m_sections.reset(); + m_sorted_sections.reset(); + m_poly_sections.reset(); + m_poly_signs.reset(); + m_info.reset(); + } + + unsigned mk_section(anum & v, unsigned pos) { + unsigned new_id = m_sections.size(); + m_sections.push_back(section()); + section & new_s = m_sections.back(); + m_am.set(new_s.m_root, v); + new_s.m_pos = pos; + return new_id; + } + + // Merge the new roots of a polynomial p into m_sections & m_sorted_sections. + // Store the section ids for the new roots in p_section_ids + unsigned_vector new_sorted_sections; + void merge(anum_vector & roots, unsigned_vector & p_section_ids) { + new_sorted_sections.reset(); // new m_sorted_sections + unsigned i1 = 0; + unsigned sz1 = m_sorted_sections.size(); + unsigned i2 = 0; + unsigned sz2 = roots.size(); + unsigned j = 0; + while (i1 < sz1 && i2 < sz2) { + unsigned s1_id = m_sorted_sections[i1]; + section & s1 = m_sections[s1_id]; + anum & r2 = roots[i2]; + int c = m_am.compare(s1.m_root, r2); + if (c == 0) { + new_sorted_sections.push_back(s1_id); + p_section_ids.push_back(s1_id); + s1.m_pos = j; + i1++; + i2++; + } + else if (c < 0) { + new_sorted_sections.push_back(s1_id); + s1.m_pos = j; + i1++; + } + else { + // create new section + unsigned new_id = mk_section(r2, j); + + // + new_sorted_sections.push_back(new_id); + p_section_ids.push_back(new_id); + i2++; + } + j++; + } + SASSERT(i1 == sz1 || i2 == sz2); + while (i1 < sz1) { + unsigned s1_id = m_sorted_sections[i1]; + section & s1 = m_sections[s1_id]; + + new_sorted_sections.push_back(s1_id); + s1.m_pos = j; + + i1++; + j++; + } + while (i2 < sz2) { + anum & r2 = roots[i2]; + // create new section + unsigned new_id = mk_section(r2, j); + + // + new_sorted_sections.push_back(new_id); + p_section_ids.push_back(new_id); + i2++; + j++; + } + m_sorted_sections.swap(new_sorted_sections); + } + + /** + \brief Add polynomial with the given roots and signs. + */ + unsigned_vector p_section_ids; + void add(anum_vector & roots, svector & signs) { + p_section_ids.reset(); + if (!roots.empty()) + merge(roots, p_section_ids); + unsigned first_sign = m_poly_signs.size(); + unsigned first_section = m_poly_sections.size(); + unsigned num_signs = signs.size(); + // Must normalize signs since we use arithmetic operations such as * + // during evaluation. + // Without normalization, overflows may happen, and wrong results may be produced. + for (unsigned i = 0; i < num_signs; i++) + m_poly_signs.push_back(normalize_sign(signs[i])); + m_poly_sections.append(p_section_ids); + m_info.push_back(poly_info(roots.size(), first_section, first_sign)); + SASSERT(check_invariant()); + } + + /** + \brief Add constant polynomial + */ + void add_const(int sign) { + unsigned first_sign = m_poly_signs.size(); + unsigned first_section = m_poly_sections.size(); + m_poly_signs.push_back(normalize_sign(sign)); + m_info.push_back(poly_info(0, first_section, first_sign)); + } + + unsigned num_cells() const { + return m_sections.size() * 2 + 1; + } + + /** + \brief Return true if the given cell is a section (i.e., root) + */ + bool is_section(unsigned c) const { + SASSERT(c < num_cells()); + return c % 2 == 1; + } + + /** + \brief Return true if the given cell is a sector (i.e., an interval between roots, or (-oo, min-root), or (max-root, +oo)). + */ + bool is_sector(unsigned c) const { + SASSERT(c < num_cells()); + return c % 2 == 0; + } + + /** + \brief Return the root id associated with the given section. + + \pre is_section(c) + */ + unsigned get_root_id(unsigned c) const { + SASSERT(is_section(c)); + return m_sorted_sections[c/2]; + } + + // Return value of root idx. + // If idx == UINT_MAX, it return zero (this is a hack to simplify the infeasible_intervals method + anum const & get_root(unsigned idx) const { + static anum zero; + if (idx == UINT_MAX) + return zero; + SASSERT(idx < m_sections.size()); + return m_sections[idx].m_root; + } + + static unsigned section_id_to_cell_id(unsigned section_id) { + return section_id*2 + 1; + } + + // Return the cell_id of the root i of pinfo + unsigned cell_id(poly_info const & pinfo, unsigned i) const { + return section_id_to_cell_id(m_sections[m_poly_sections[pinfo.m_first_section + i]].m_pos); + } + + // Return the sign idx of pinfo + int sign(poly_info const & pinfo, unsigned i) const { + return m_poly_signs[pinfo.m_first_sign + i]; + } + +#define LINEAR_SEARCH_THRESHOLD 8 + int sign_at(unsigned info_id, unsigned c) const { + poly_info const & pinfo = m_info[info_id]; + unsigned num_roots = pinfo.m_num_roots; + if (num_roots < LINEAR_SEARCH_THRESHOLD) { + unsigned i = 0; + for (; i < num_roots; i++) { + unsigned section_cell_id = cell_id(pinfo, i); + if (section_cell_id == c) + return 0; + else if (section_cell_id > c) + break; + } + return sign(pinfo, i); + } + else { + if (num_roots == 0) + return sign(pinfo, 0); + unsigned root_1_cell_id = cell_id(pinfo, 0); + unsigned root_n_cell_id = cell_id(pinfo, num_roots - 1); + if (c < root_1_cell_id) + return sign(pinfo, 0); + else if (c == root_1_cell_id || c == root_n_cell_id) + return 0; + else if (c > root_n_cell_id) + return sign(pinfo, num_roots); + int low = 0; + int high = num_roots-1; + while (true) { + SASSERT(0 <= low && high < static_cast(num_roots)); + SASSERT(cell_id(pinfo, low) < c); + SASSERT(c < cell_id(pinfo, high)); + if (high == low + 1) { + SASSERT(cell_id(pinfo, low) < c); + SASSERT(c < cell_id(pinfo, low+1)); + return sign(pinfo, low+1); + } + SASSERT(high > low + 1); + int mid = low + ((high - low)/2); + SASSERT(low < mid && mid < high); + unsigned mid_cell_id = cell_id(pinfo, mid); + if (mid_cell_id == c) { + return 0; + } + if (c < mid_cell_id) { + high = mid; + } + else { + SASSERT(mid_cell_id < c); + low = mid; + } + } + } + } + + 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()); + return true; + } + + // Display sign table for the given variable + void display(std::ostream & out) const { + out << "sections:\n "; + for (unsigned i = 0; i < m_sections.size(); i++) { + if (i > 0) out << " < "; + m_am.display_decimal(out, m_sections[m_sorted_sections[i]].m_root); + } + out << "\n"; + out << "sign variations:\n"; + for (unsigned i = 0; i < m_info.size(); i++) { + out << " "; + for (unsigned j = 0; j < num_cells(); j++) { + if (j > 0) + out << " "; + int s = sign_at(i, j); + if (s < 0) out << "-"; + else if (s == 0) out << "0"; + else out << "+"; + } + out << "\n"; + } + } + + // Display sign table for the given variable + void display_raw(std::ostream & out) const { + out << "sections:\n "; + for (unsigned i = 0; i < m_sections.size(); i++) { + if (i > 0) out << " < "; + m_am.display_decimal(out, m_sections[m_sorted_sections[i]].m_root); + } + out << "\n"; + out << "poly_info:\n"; + for (unsigned i = 0; i < m_info.size(); i++) { + out << " roots:"; + poly_info const & info = m_info[i]; + for (unsigned j = 0; j < info.m_num_roots; j++) { + out << " "; + out << m_poly_sections[info.m_first_section+j]; + } + out << ", signs:"; + for (unsigned j = 0; j < info.m_num_roots+1; j++) { + out << " "; + int s = m_poly_signs[info.m_first_sign+j]; + if (s < 0) out << "-"; + else if (s == 0) out << "0"; + else out << "+"; + } + out << "\n"; + } + } + }; + + sign_table m_sign_table_tmp; + + imp(assignment const & x2v, pmanager & pm, small_object_allocator & allocator): + m_assignment(x2v), + m_pm(pm), + m_allocator(allocator), + m_am(m_assignment.am()), + m_ism(m_am, allocator), + m_tmp_values(m_am), + m_add_roots_tmp(m_am), + m_inf_tmp(m_am), + m_sign_table_tmp(m_am) { + } + + var max_var(poly const * p) const { + return m_pm.max_var(p); + } + + /** + \brief Return the sign of the polynomial in the current interpration. + + \pre All variables of p are assigned in the current interpretation. + */ + int eval_sign(poly * p) { + // TODO: check if it is useful to cache results + SASSERT(m_assignment.is_assigned(max_var(p))); + return m_am.eval_sign_at(polynomial_ref(p, m_pm), m_assignment); + } + + bool satisfied(int sign, atom::kind k) { + return + (sign == 0 && (k == atom::EQ || k == atom::ROOT_EQ || k == atom::ROOT_LE || k == atom::ROOT_GE)) || + (sign < 0 && (k == atom::LT || k == atom::ROOT_LT || k == atom::ROOT_LE)) || + (sign > 0 && (k == atom::GT || k == atom::ROOT_GT || k == atom::ROOT_GE)); + } + + bool satisfied(int sign, atom::kind k, bool neg) { + bool r = satisfied(sign, k); + return neg ? !r : r; + } + + bool eval_ineq(ineq_atom * a, bool neg) { + SASSERT(m_assignment.is_assigned(a->max_var())); + // all variables of a were already assigned... + atom::kind k = a->get_kind(); + unsigned sz = a->size(); + int sign = 1; + for (unsigned i = 0; i < sz; i++) { + int curr_sign = eval_sign(a->p(i)); + if (a->is_even(i) && curr_sign < 0) + curr_sign = 1; + sign = sign * curr_sign; + if (sign == 0) + break; + } + return satisfied(sign, k, neg); + } + + bool eval_root(root_atom * a, bool neg) { + SASSERT(m_assignment.is_assigned(a->max_var())); + // all variables of a were already assigned... + atom::kind k = a->get_kind(); + scoped_anum_vector & roots = m_tmp_values; + roots.reset(); + m_am.isolate_roots(polynomial_ref(a->p(), m_pm), undef_var_assignment(m_assignment, a->x()), roots); + SASSERT(a->i() > 0); + if (a->i() > roots.size()) + return false; // p does have sufficient roots + int sign = m_am.compare(m_assignment.value(a->x()), roots[a->i() - 1]); + return satisfied(sign, k, neg); + } + + bool eval(atom * a, bool neg) { + return a->is_ineq_atom() ? eval_ineq(to_ineq_atom(a), neg) : eval_root(to_root_atom(a), neg); + } + + svector m_add_signs_tmp; + void add(poly * p, var x, sign_table & t) { + SASSERT(m_pm.max_var(p) <= x); + if (m_pm.max_var(p) < x) { + t.add_const(eval_sign(p)); + } + else { + // isolate roots of p + scoped_anum_vector & roots = m_add_roots_tmp; + svector & signs = m_add_signs_tmp; + roots.reset(); + signs.reset(); + TRACE("nlsat_evaluator", tout << "x: " << x << " max_var(p): " << m_pm.max_var(p) << "\n";); + // Note: I added undef_var_assignment in the following statement, to allow us to obtain the infeasible interval sets + // even when the maximal variable is assigned. I need this feature to minimize conflict cores. + m_am.isolate_roots(polynomial_ref(p, m_pm), undef_var_assignment(m_assignment, x), roots, signs); + t.add(roots, signs); + } + } + + // Evalute the sign of p1^e1*...*pn^en (of atom a) in cell c of table t. + int sign_at(ineq_atom * a, sign_table const & t, unsigned c) const { + int sign = 1; + unsigned num_ps = a->size(); + for (unsigned i = 0; i < num_ps; i++) { + int curr_sign = t.sign_at(i, c); + TRACE("nlsat_evaluator_bug", tout << "sign of i: " << i << " at cell " << c << "\n"; + m_pm.display(tout, a->p(i)); + tout << "\nsign: " << curr_sign << "\n";); + if (a->is_even(i) && curr_sign < 0) + curr_sign = 1; + sign = sign * curr_sign; + if (sign == 0) + break; + } + return sign; + } + + interval_set_ref infeasible_intervals(ineq_atom * a, bool neg) { + sign_table & table = m_sign_table_tmp; + table.reset(); + unsigned num_ps = a->size(); + var x = a->max_var(); + for (unsigned i = 0; i < num_ps; i++) { + add(a->p(i), x, table); + TRACE("nlsat_evaluator_bug", tout << "table after:\n"; m_pm.display(tout, a->p(i)); tout << "\n"; table.display_raw(tout);); + + } + TRACE("nlsat_evaluator", + tout << "sign table for:\n"; + for (unsigned i = 0; i < num_ps; i++) { m_pm.display(tout, a->p(i)); tout << "\n"; } + table.display(tout);); + + interval_set_ref result(m_ism); + interval_set_ref set(m_ism); + literal jst(a->bvar(), neg); + atom::kind k = a->get_kind(); + + anum dummy; + bool prev_sat = true; + bool prev_inf = true; + bool prev_open = true; + unsigned prev_root_id = UINT_MAX; + + unsigned num_cells = table.num_cells(); + for (unsigned c = 0; c < num_cells; c++) { + TRACE("nlsat_evaluator", + tout << "cell: " << c << "\n"; + tout << "prev_sat: " << prev_sat << "\n"; + tout << "prev_inf: " << prev_inf << "\n"; + tout << "prev_open: " << prev_open << "\n"; + tout << "prev_root_id: " << prev_root_id << "\n"; + tout << "processing cell: " << c << "\n"; + tout << "interval_set so far:\n" << result << "\n";); + int sign = sign_at(a, table, c); + TRACE("nlsat_evaluator", tout << "sign: " << sign << "\n";); + if (satisfied(sign, k, neg)) { + // current cell is satisfied + if (!prev_sat) { + SASSERT(c > 0); + // add interval + bool curr_open; + unsigned curr_root_id; + if (table.is_section(c)) { + curr_open = true; + curr_root_id = table.get_root_id(c); + } + else { + SASSERT(table.is_section(c-1)); + curr_open = false; + curr_root_id = table.get_root_id(c-1); + } + set = m_ism.mk(prev_open, prev_inf, table.get_root(prev_root_id), + curr_open, false, table.get_root(curr_root_id), jst); + result = m_ism.mk_union(result, set); + prev_sat = true; + } + } + else { + // current cell is not satisfied + if (prev_sat) { + if (c == 0) { + if (num_cells == 1) { + // (-oo, oo) + result = m_ism.mk(true, true, dummy, true, true, dummy, jst); + } + else { + // save -oo as begining of infeasible interval + prev_open = true; + prev_inf = true; + prev_root_id = UINT_MAX; + } + } + else { + SASSERT(c > 0); + prev_inf = false; + if (table.is_section(c)) { + prev_open = false; + prev_root_id = table.get_root_id(c); + TRACE("nlsat_evaluator", tout << "updated prev_root_id: " << prev_root_id << " using cell: " << c << "\n";); + } + else { + SASSERT(table.is_section(c-1)); + prev_open = true; + prev_root_id = table.get_root_id(c-1); + TRACE("nlsat_evaluator", tout << "updated prev_root_id: " << prev_root_id << " using cell: " << (c - 1) << "\n";); + } + } + prev_sat = false; + } + if (c == num_cells - 1) { + // last cell add interval with (prev, oo) + set = m_ism.mk(prev_open, prev_inf, table.get_root(prev_root_id), + true, true, dummy, jst); + result = m_ism.mk_union(result, set); + } + } + } + TRACE("nlsat_evaluator", tout << "interval_set: " << result << "\n";); + return result; + } + + interval_set_ref infeasible_intervals(root_atom * a, bool neg) { + atom::kind k = a->get_kind(); + unsigned i = a->i(); + SASSERT(i > 0); + literal jst(a->bvar(), neg); + anum dummy; + scoped_anum_vector & roots = m_tmp_values; + roots.reset(); + var x = a->max_var(); + // Note: I added undef_var_assignment in the following statement, to allow us to obtain the infeasible interval sets + // even when the maximal variable is assigned. I need this feature to minimize conflict cores. + m_am.isolate_roots(polynomial_ref(a->p(), m_pm), undef_var_assignment(m_assignment, x), roots); + interval_set_ref result(m_ism); + + if (i > roots.size()) { + // p does have sufficient roots + // atom is false by definition + if (neg) { + result = m_ism.mk_empty(); + } + else { + result = m_ism.mk(true, true, dummy, true, true, dummy, jst); // (-oo, oo) + } + } + else { + anum const & r_i = roots[i-1]; + switch (k) { + case atom::ROOT_EQ: + if (neg) { + result = m_ism.mk(false, false, r_i, false, false, r_i, jst); // [r_i, r_i] + } + else { + interval_set_ref s1(m_ism), s2(m_ism); + s1 = m_ism.mk(true, true, dummy, true, false, r_i, jst); // (-oo, r_i) + s2 = m_ism.mk(true, false, r_i, true, true, dummy, jst); // (r_i, oo) + result = m_ism.mk_union(s1, s2); + } + break; + case atom::ROOT_LT: + if (neg) + result = m_ism.mk(true, true, dummy, true, false, r_i, jst); // (-oo, r_i) + else + result = m_ism.mk(false, false, r_i, true, true, dummy, jst); // [r_i, oo) + break; + case atom::ROOT_GT: + if (neg) + result = m_ism.mk(true, false, r_i, true, true, dummy, jst); // (r_i, oo) + else + result = m_ism.mk(true, true, dummy, false, false, r_i, jst); // (-oo, r_i] + break; + case atom::ROOT_LE: + if (neg) + result = m_ism.mk(true, true, dummy, false, false, r_i, jst); // (-oo, r_i] + else + result = m_ism.mk(true, false, r_i, true, true, dummy, jst); // (r_i, oo) + break; + case atom::ROOT_GE: + if (neg) + result = m_ism.mk(false, false, r_i, true, true, dummy, jst); // [r_i, oo) + else + result = m_ism.mk(true, true, dummy, true, false, r_i, jst); // (-oo, r_i) + break; + default: + UNREACHABLE(); + break; + } + } + TRACE("nlsat_evaluator", tout << "interval_set: " << result << "\n";); + return result; + } + + interval_set_ref infeasible_intervals(atom * a, bool neg) { + return a->is_ineq_atom() ? infeasible_intervals(to_ineq_atom(a), neg) : infeasible_intervals(to_root_atom(a), neg); + } + }; + + evaluator::evaluator(assignment const & x2v, pmanager & pm, small_object_allocator & allocator) { + m_imp = alloc(imp, x2v, pm, allocator); + } + + evaluator::~evaluator() { + dealloc(m_imp); + } + + interval_set_manager & evaluator::ism() const { + return m_imp->m_ism; + } + + bool evaluator::eval(atom * a, bool neg) { + return m_imp->eval(a, neg); + } + + interval_set_ref evaluator::infeasible_intervals(atom * a, bool neg) { + return m_imp->infeasible_intervals(a, neg); + } + + void evaluator::push() { + // do nothing + } + + void evaluator::pop(unsigned num_scopes) { + // do nothing + } +}; diff --git a/lib/nlsat_evaluator.h b/lib/nlsat_evaluator.h new file mode 100644 index 000000000..166416902 --- /dev/null +++ b/lib/nlsat_evaluator.h @@ -0,0 +1,61 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + nlsat_evaluator.h + +Abstract: + + Helper class for computing the infeasible intervals of an + arithmetic literal. + +Author: + + Leonardo de Moura (leonardo) 2012-01-12. + +Revision History: + +--*/ +#ifndef _NLSAT_EVALUATOR_H_ +#define _NLSAT_EVALUATOR_H_ + +#include"nlsat_types.h" +#include"nlsat_assignment.h" +#include"nlsat_interval_set.h" + +namespace nlsat { + + class evaluator { + struct imp; + imp * m_imp; + public: + evaluator(assignment const & x2v, pmanager & pm, small_object_allocator & allocator); + ~evaluator(); + + interval_set_manager & ism() const; + + /** + \brief Evaluate the given literal in the current model. + All variables in the atom must be assigned. + + The result is true if the literal is satisfied, and false otherwise. + */ + bool eval(atom * a, bool neg); + + /** + \brief Return the infeasible interval set for the given literal. + All but the a->max_var() must be assigned in the current model. + + Let x be a->max_var(). Then, the resultant set specifies which + values of x falsify the given literal. + */ + interval_set_ref infeasible_intervals(atom * a, bool neg); + + void push(); + void pop(unsigned num_scopes); + }; + +}; + +#endif diff --git a/lib/nlsat_explain.cpp b/lib/nlsat_explain.cpp new file mode 100644 index 000000000..ca58b15af --- /dev/null +++ b/lib/nlsat_explain.cpp @@ -0,0 +1,1343 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + nlsat_explain.cpp + +Abstract: + + Functor that implements the "explain" procedure defined in Dejan and Leo's paper. + +Author: + + Leonardo de Moura (leonardo) 2012-01-13. + +Revision History: + +--*/ +#include"nlsat_explain.h" +#include"nlsat_assignment.h" +#include"nlsat_evaluator.h" +#include"algebraic_numbers.h" +#include"ref_buffer.h" + +namespace nlsat { + + typedef polynomial::polynomial_ref_vector polynomial_ref_vector; + typedef ref_buffer polynomial_ref_buffer; + + struct explain::imp { + solver & m_solver; + assignment const & m_assignment; + atom_vector const & m_atoms; + atom_vector const & m_x2eq; + anum_manager & m_am; + polynomial::cache & m_cache; + pmanager & m_pm; + polynomial_ref_vector m_ps; + polynomial_ref_vector m_psc_tmp; + polynomial_ref_vector m_factors; + scoped_anum_vector m_roots_tmp; + bool m_simplify_cores; + bool m_full_dimensional; + bool m_minimize_cores; + + struct todo_set { + polynomial::cache & m_cache; + polynomial_ref_vector m_set; + svector m_in_set; + + todo_set(polynomial::cache & u):m_cache(u), m_set(u.pm()) {} + + void reset() { + pmanager & pm = m_set.m(); + unsigned sz = m_set.size(); + for (unsigned i = 0; i < sz; i++) { + m_in_set[pm.id(m_set.get(i))] = false; + } + m_set.reset(); + } + + void insert(poly * p) { + pmanager & pm = m_set.m(); + p = m_cache.mk_unique(p); + unsigned pid = pm.id(p); + if (m_in_set.get(pid, false)) + return; + m_in_set.setx(pid, true, false); + m_set.push_back(p); + } + + bool empty() const { return m_set.empty(); } + + // Return max variable in todo_set + var max_var() const { + pmanager & pm = m_set.m(); + var max = null_var; + unsigned sz = m_set.size(); + for (unsigned i = 0; i < sz; i++) { + var x = pm.max_var(m_set.get(i)); + SASSERT(x != null_var); + if (max == null_var || x > max) + max = x; + } + return max; + } + + /** + \brief Remove the maximal polynomials from the set and store + them in max_polys. Return the maximal variable + */ + var remove_max_polys(polynomial_ref_vector & max_polys) { + max_polys.reset(); + var x = max_var(); + pmanager & pm = m_set.m(); + unsigned sz = m_set.size(); + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + poly * p = m_set.get(i); + var y = pm.max_var(p); + SASSERT(y <= x); + if (y == x) { + max_polys.push_back(p); + m_in_set[pm.id(p)] = false; + } + else { + m_set.set(j, p); + j++; + } + } + m_set.shrink(j); + return x; + } + }; + + // temporary field for store todo set of polynomials + todo_set m_todo; + + // temporary fields for preprocessing core + scoped_literal_vector m_core1; + scoped_literal_vector m_core2; + + // temporary fields for storing the result + scoped_literal_vector * m_result; + svector m_already_added_literal; + + evaluator & m_evaluator; + + imp(solver & s, assignment const & x2v, polynomial::cache & u, atom_vector const & atoms, atom_vector const & x2eq, + evaluator & ev): + m_solver(s), + m_assignment(x2v), + m_atoms(atoms), + m_x2eq(x2eq), + m_am(x2v.am()), + m_cache(u), + m_pm(u.pm()), + m_ps(m_pm), + m_psc_tmp(m_pm), + m_factors(m_pm), + m_roots_tmp(m_am), + m_todo(u), + m_core1(s), + m_core2(s), + m_result(0), + m_evaluator(ev) { + m_simplify_cores = false; + m_full_dimensional = false; + m_minimize_cores = false; + } + + ~imp() { + } + + void display(std::ostream & out, polynomial_ref const & p) const { + m_pm.display(out, p, m_solver.display_proc()); + } + + void display(std::ostream & out, polynomial_ref_vector const & ps, char const * delim = "\n") const { + for (unsigned i = 0; i < ps.size(); i++) { + if (i > 0) + out << delim; + m_pm.display(out, ps.get(i), m_solver.display_proc()); + } + } + + void display(std::ostream & out, literal l) const { m_solver.display(out, l); } + void display_var(std::ostream & out, var x) const { m_solver.display(out, x); } + void display(std::ostream & out, unsigned sz, literal const * ls) { + for (unsigned i = 0; i < sz; i++) { + display(out, ls[i]); + out << "\n"; + } + } + void display(std::ostream & out, literal_vector const & ls) { + display(out, ls.size(), ls.c_ptr()); + } + void display(std::ostream & out, scoped_literal_vector const & ls) { + display(out, ls.size(), ls.c_ptr()); + } + + /** + \brief Add literal to the result vector. + */ + void add_literal(literal l) { + SASSERT(m_result != 0); + SASSERT(l != true_literal); + if (l == false_literal) + return; + unsigned lidx = l.index(); + if (m_already_added_literal.get(lidx, false)) + return; + TRACE("nlsat_explain", tout << "adding literal: " << lidx << "\n"; m_solver.display(tout, l); tout << "\n";); + m_already_added_literal.setx(lidx, true, false); + m_result->push_back(l); + } + + /** + \brief Reset m_already_added vector using m_result + */ + void reset_already_added() { + SASSERT(m_result != 0); + unsigned sz = m_result->size(); + for (unsigned i = 0; i < sz; i++) + m_already_added_literal[(*m_result)[i].index()] = false; + } + + + /** + \brief evaluate the given polynomial in the current interpretation. + max_var(p) must be assigned in the current interpretation. + */ + int sign(polynomial_ref const & p) { + TRACE("nlsat_explain", tout << "p: " << p << "\n";); + SASSERT(max_var(p) == null_var || m_assignment.is_assigned(max_var(p))); + return m_am.eval_sign_at(p, m_assignment); + } + + /** + \brief Wrapper for factorization + */ + void factor(polynomial_ref & p, polynomial_ref_vector & fs) { + // TODO: add params, caching + TRACE("nlsat_factor", tout << "factor\n" << p << "\n";); + fs.reset(); + m_cache.factor(p.get(), fs); + } + + /** + \brief Wrapper for psc chain computation + */ + void psc_chain(polynomial_ref & p, polynomial_ref & q, unsigned x, polynomial_ref_vector & result) { + // TODO caching + SASSERT(max_var(p) == max_var(q)); + SASSERT(max_var(p) == x); + m_cache.psc_chain(p, q, x, result); + } + + /** + \breif Store in ps the polynomials occurring in the given literals. + */ + void collect_polys(unsigned num, literal const * ls, polynomial_ref_vector & ps) { + ps.reset(); + for (unsigned i = 0; i < num; i++) { + atom * a = m_atoms[ls[i].var()]; + SASSERT(a != 0); + if (a->is_ineq_atom()) { + unsigned sz = to_ineq_atom(a)->size(); + for (unsigned j = 0; j < sz; j++) + ps.push_back(to_ineq_atom(a)->p(j)); + } + else { + ps.push_back(to_root_atom(a)->p()); + } + } + } + + /** + \brief Add literal p != 0 into m_result. + */ + ptr_vector m_zero_fs; + svector m_is_even; + void add_zero_assumption(polynomial_ref & p) { + // If p is of the form p1^n1 * ... * pk^nk, + // then only the factors that are zero in the current interpretation needed to be considered. + // I don't want to create a nested conjunction in the clause. + // Then, I assert p_i1 * ... * p_im != 0 + factor(p, m_factors); + unsigned num_factors = m_factors.size(); + m_zero_fs.reset(); + m_is_even.reset(); + polynomial_ref f(m_pm); + for (unsigned i = 0; i < num_factors; i++) { + f = m_factors.get(i); + if (sign(f) == 0) { + m_zero_fs.push_back(m_factors.get(i)); + m_is_even.push_back(false); + } + } + SASSERT(!m_zero_fs.empty()); // one of the factors must be zero in the current interpretation, since p is zero in it. + literal l = m_solver.mk_ineq_literal(atom::EQ, m_zero_fs.size(), m_zero_fs.c_ptr(), m_is_even.c_ptr()); + l.neg(); + TRACE("nlsat_explain", tout << "adding (zero assumption) literal:\n"; display(tout, l); tout << "\n";); + add_literal(l); + } + + void add_simple_assumption(atom::kind k, poly * p, bool sign = false) { + SASSERT(k == atom::EQ || k == atom::LT || k == atom::GT); + bool is_even = false; + bool_var b = m_solver.mk_ineq_atom(k, 1, &p, &is_even); + literal l(b, !sign); + add_literal(l); + } + + void add_assumption(atom::kind k, poly * p, bool sign = false) { + // TODO: factor + add_simple_assumption(k, p, sign); + } + + /** + \brief Eliminate "vanishing leading coefficients" of p. + That is, coefficients that vanish in the current + interpretation. The resultant p is a reduct of p s.t. its + leading coefficient does not vanish in the current + interpretation. If all coefficients of p vanish, then + the resultant p is the zero polynomial. + */ + void elim_vanishing(polynomial_ref & p) { + SASSERT(!is_const(p)); + var x = max_var(p); + unsigned k = degree(p, x); + SASSERT(k > 0); + polynomial_ref lc(m_pm); + polynomial_ref reduct(m_pm); + while (true) { + if (is_const(p)) + return; + if (k == 0) { + // x vanished from p, peek next maximal variable + x = max_var(p); + SASSERT(x != null_var); + k = degree(p, x); + } + if (m_pm.nonzero_const_coeff(p, x, k)) + return; // lc is a nonzero constant + lc = m_pm.coeff(p, x, k, reduct); + if (!is_zero(lc)) { + if (sign(lc) != 0) + return; + // lc is not the zero polynomial, but it vanished in the current interpretaion. + // so we keep searching... + add_zero_assumption(lc); + } + if (k == 0) { + // all coefficients of p vanished in the current interpretation, + // and were added as assumptions. + p = m_pm.mk_zero(); + return; + } + k--; + p = reduct; + } + } + + /** + Eliminate vanishing coefficients of polynomials in ps. + The coefficients that are zero (i.e., vanished) are added + as assumptions into m_result. + */ + void elim_vanishing(polynomial_ref_vector & ps) { + unsigned j = 0; + unsigned sz = ps.size(); + polynomial_ref p(m_pm); + for (unsigned i = 0; i < sz; i++) { + p = ps.get(i); + elim_vanishing(p); + if (!is_const(p)) { + ps.set(j, p); + j++; + } + } + ps.shrink(j); + } + + /** + Normalize literal with respect to given maximal variable. + The basic idea is to eliminate vanishing (leading) coefficients from a (arithmetic) literal, + and factors from lower stages. + + The vanishing coefficients and factors from lower stages are added as assumptions to the lemma + being generated. + + Example 1) + Assume + - l is of the form (y^2 - 2)*x^3 + y*x + 1 > 0 + - x is the maximal variable + - y is assigned to sqrt(2) + Thus, (y^2 - 2) the coefficient of x^3 vanished. This method returns + y*x + 1 > 0 and adds the assumption (y^2 - 2) = 0 to the lemma + + Example 2) + Assume + - l is of the form (x + 2)*(y - 1) > 0 + - x is the maximal variable + - y is assigned to 0 + (x + 2) < 0 is returned and assumption (y - 1) < 0 is added as an assumption. + + Remark: root atoms are not normalized + */ + literal normalize(literal l, var max) { + bool_var b = l.var(); + if (b == true_bool_var) + return l; + SASSERT(m_atoms[b] != 0); + if (m_atoms[b]->is_ineq_atom()) { + polynomial_ref_buffer ps(m_pm); + sbuffer is_even; + polynomial_ref p(m_pm); + ineq_atom * a = to_ineq_atom(m_atoms[b]); + int atom_sign = 1; + unsigned sz = a->size(); + bool normalized = false; // true if the literal needs to be normalized + for (unsigned i = 0; i < sz; i++) { + p = a->p(i); + if (max_var(p) == max) + elim_vanishing(p); // eliminate vanishing coefficients of max + if (is_const(p) || max_var(p) < max) { + int s = sign(p); + if (!is_const(p)) { + SASSERT(max_var(p) != null_var); + SASSERT(max_var(p) < max); + // factor p is a lower stage polynomial, so we should add assumption to justify p being eliminated + if (s == 0) + add_simple_assumption(atom::EQ, p); // add assumption p = 0 + else if (a->is_even(i)) + add_simple_assumption(atom::EQ, p, true); // add assumption p != 0 + else if (s < 0) + add_simple_assumption(atom::LT, p); // add assumption p < 0 + else + add_simple_assumption(atom::GT, p); // add assumption p > 0 + } + if (s == 0) { + bool atom_val = a->get_kind() == atom::EQ; + bool lit_val = l.sign() ? !atom_val : atom_val; + return lit_val ? true_literal : false_literal; + } + else if (s == -1 && a->is_odd(i)) { + atom_sign = -atom_sign; + } + normalized = true; + } + else { + if (p != a->p(i)) { + SASSERT(!m_pm.eq(p, a->p(i))); + normalized = true; + } + is_even.push_back(a->is_even(i)); + ps.push_back(p); + } + } + if (ps.empty()) { + SASSERT(atom_sign != 0); + // LHS is positive or negative. It is positive if atom_sign > 0 and negative if atom_sign < 0 + bool atom_val; + if (a->get_kind() == atom::EQ) + atom_val = false; + else if (a->get_kind() == atom::LT) + atom_val = atom_sign < 0; + else + atom_val = atom_sign > 0; + bool lit_val = l.sign() ? !atom_val : atom_val; + return lit_val ? true_literal : false_literal; + } + else if (normalized) { + atom::kind new_k = a->get_kind(); + if (atom_sign < 0) + new_k = atom::flip(new_k); + literal new_l = m_solver.mk_ineq_literal(new_k, ps.size(), ps.c_ptr(), is_even.c_ptr()); + if (l.sign()) + new_l.neg(); + return new_l; + } + else { + SASSERT(atom_sign > 0); + return l; + } + } + else { + return l; + } + } + + /** + Normalize literals (in the conflicting core) with respect + to given maximal variable. The basic idea is to eliminate + vanishing (leading) coefficients (and factors from lower + stages) from (arithmetic) literals, + */ + void normalize(scoped_literal_vector & C, var max) { + unsigned sz = C.size(); + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + literal new_l = normalize(C[i], max); + if (new_l == true_literal) + continue; + if (new_l == false_literal) { + // false literal was created. The assumptions added are sufficient for implying the conflict. + C.reset(); + return; + } + C.set(j, new_l); + j++; + } + C.shrink(j); + } + + var max_var(poly const * p) { return m_pm.max_var(p); } + + /** + \brief Return the maximal variable in a set of nonconstant polynomials. + */ + var max_var(polynomial_ref_vector const & ps) { + if (ps.empty()) + return null_var; + var max = max_var(ps.get(0)); + SASSERT(max != null_var); // there are no constant polynomials in ps + unsigned sz = ps.size(); + for (unsigned i = 1; i < sz; i++) { + var curr = m_pm.max_var(ps.get(i)); + SASSERT(curr != null_var); + if (curr > max) + max = curr; + } + return max; + } + + polynomial::var max_var(literal l) { + atom * a = m_atoms[l.var()]; + if (a != 0) + return a->max_var(); + else + return null_var; + } + + /** + \brief Return the maximal variable in the given set of literals + */ + var max_var(unsigned sz, literal const * ls) { + var max = null_var; + for (unsigned i = 0; i < sz; i++) { + literal l = ls[i]; + atom * a = m_atoms[l.var()]; + if (a != 0) { + var x = a->max_var(); + SASSERT(x != null_var); + if (max == null_var || x > max) + max = x; + } + } + return max; + } + + /** + \brief Move the polynomials in q in ps that do not contain x to qs. + */ + void keep_p_x(polynomial_ref_vector & ps, var x, polynomial_ref_vector & qs) { + unsigned sz = ps.size(); + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + poly * q = ps.get(i); + if (max_var(q) != x) { + qs.push_back(q); + } + else { + ps.set(j, q); + j++; + } + } + ps.shrink(j); + } + + /** + \brief Add factors of p to todo + */ + void add_factors(polynomial_ref & p) { + if (is_const(p)) + return; + elim_vanishing(p); + if (is_const(p)) + return; +#if 1 + TRACE("nlsat_explain", tout << "adding factors of\n"; display(tout, p); tout << "\n";); + factor(p, m_factors); + polynomial_ref f(m_pm); + for (unsigned i = 0; i < m_factors.size(); i++) { + f = m_factors.get(i); + elim_vanishing(f); + if (!is_const(f)) { + TRACE("nlsat_explain", tout << "adding factor:\n"; display(tout, f); tout << "\n";); + m_todo.insert(f); + } + } +#else + m_todo.insert(normalize(p)); +#endif + } + + /** + \brief Add leading coefficients of the polynomials in ps. + + \pre all polynomials in ps contain x + + Remark: the leading coefficients do not vanish in the current model, + since all polynomials in ps were pre-processed using elim_vanishing. + */ + void add_lc(polynomial_ref_vector & ps, var x) { + polynomial_ref p(m_pm); + polynomial_ref lc(m_pm); + unsigned sz = ps.size(); + for (unsigned i = 0; i < sz; i++) { + p = ps.get(i); + unsigned k = degree(p, x); + SASSERT(k > 0); + TRACE("nlsat_explain", tout << "add_lc, x: "; display_var(tout, x); tout << "\nk: " << k << "\n"; display(tout, p); tout << "\n";); + if (m_pm.nonzero_const_coeff(p, x, k)) { + TRACE("nlsat_explain", tout << "constant coefficient, skipping...\n";); + continue; + } + lc = m_pm.coeff(p, x, k); + SASSERT(sign(lc) != 0); + SASSERT(!is_const(lc)); + add_factors(lc); + } + } + + /** + \brief Add v-psc(p, q, x) into m_todo + */ + void psc(polynomial_ref & p, polynomial_ref & q, var x) { + polynomial_ref_vector & S = m_psc_tmp; + polynomial_ref s(m_pm); + TRACE("nlsat_explain", tout << "computing psc of\n"; display(tout, p); tout << "\n"; display(tout, q); tout << "\n";); + + psc_chain(p, q, x, S); + unsigned sz = S.size(); + for (unsigned i = 0; i < sz; i++) { + s = S.get(i); + TRACE("nlsat_explain", tout << "processing psc(" << i << ")\n"; display(tout, s); tout << "\n";); + if (is_zero(s)) { + TRACE("nlsat_explain", tout << "skipping psc is the zero polynomial\n";); + continue; + } + if (is_const(s)) { + TRACE("nlsat_explain", tout << "done, psc is a constant\n";); + return; + } + if (sign(s) == 0) { + TRACE("nlsat_explain", tout << "psc vanished, adding zero assumption\n";); + add_zero_assumption(s); + continue; + } + TRACE("nlsat_explain", + tout << "adding v-psc of\n"; + display(tout, p); + tout << "\n"; + display(tout, q); + tout << "\n---->\n"; + display(tout, s); + tout << "\n";); + // s did not vanish completely, but its leading coefficient may have vanished + add_factors(s); + return; + } + } + + /** + \brief For each p in ps, add v-psc(x, p, p') into m_todo + + \pre all polynomials in ps contain x + + Remark: the leading coefficients do not vanish in the current model, + since all polynomials in ps were pre-processed using elim_vanishing. + */ + void psc_discriminant(polynomial_ref_vector & ps, var x) { + polynomial_ref p(m_pm); + polynomial_ref p_prime(m_pm); + unsigned sz = ps.size(); + for (unsigned i = 0; i < sz; i++) { + p = ps.get(i); + if (degree(p, x) < 2) + continue; + p_prime = derivative(p, x); + psc(p, p_prime, x); + } + } + + /** + \brief For each p and q in ps, p != q, add v-psc(x, p, q) into m_todo + + \pre all polynomials in ps contain x + + Remark: the leading coefficients do not vanish in the current model, + since all polynomials in ps were pre-processed using elim_vanishing. + */ + void psc_resultant(polynomial_ref_vector & ps, var x) { + polynomial_ref p(m_pm); + polynomial_ref q(m_pm); + unsigned sz = ps.size(); + for (unsigned i = 0; i < sz - 1; i++) { + p = ps.get(i); + for (unsigned j = i + 1; j < sz; j++) { + q = ps.get(j); + psc(p, q, x); + } + } + } + + void add_root_literal(atom::kind k, var y, unsigned i, poly * p) { + scoped_mpz c(m_pm.m()); + bool_var b; + bool lsign; + if (m_pm.degree(p, y) == 1 && m_pm.const_coeff(p, y, 1, c)) { + SASSERT(!m_pm.m().is_zero(c)); + // literal can be expressed using a linear ineq_atom + polynomial_ref p_prime(m_pm); + p_prime = p; + if (m_pm.m().is_neg(c)) + p_prime = neg(p_prime); + p = p_prime.get(); + switch (k) { + case atom::ROOT_EQ: k = atom::EQ; lsign = false; break; + case atom::ROOT_LT: k = atom::LT; lsign = false; break; + case atom::ROOT_GT: k = atom::GT; lsign = false; break; + case atom::ROOT_LE: k = atom::GT; lsign = true; break; + case atom::ROOT_GE: k = atom::LT; lsign = true; break; + default: + UNREACHABLE(); + break; + } + bool is_even = false; + b = m_solver.mk_ineq_atom(k, 1, &p, &is_even); + } + else { + b = m_solver.mk_root_atom(k, y, i, p); + lsign = false; + } + lsign = !lsign; // adding as an assumption + literal l(b, lsign); + TRACE("nlsat_explain", tout << "adding literal\n"; display(tout, l); tout << "\n";); + add_literal(l); + } + + /** + Add one or two literals that specify in which cell of variable y the current interpretation is. + One literal is added for the cases: + - y in (-oo, min) where min is the minimal root of the polynomials p2 in ps + We add literal + ! (y < root_1(p2)) + - y in (max, oo) where max is the maximal root of the polynomials p1 in ps + We add literal + ! (y > root_k(p1)) where k is the number of real roots of p + - y = r where r is the k-th root of a polynomial p in ps + We add literal + ! (y = root_k(p)) + Two literals are added when + - y in (l, u) where (l, u) does not contain any root of polynomials p in ps, and + l is the i-th root of a polynomial p1 in ps, and u is the j-th root of a polynomial p2 in ps. + We add literals + ! (y > root_i(p1)) or !(y < root_j(p2)) + */ + void add_cell_lits(polynomial_ref_vector & ps, var y) { + SASSERT(m_assignment.is_assigned(y)); + bool lower_inf = true; + bool upper_inf = true; + scoped_anum_vector & roots = m_roots_tmp; + scoped_anum lower(m_am); + scoped_anum upper(m_am); + anum const & y_val = m_assignment.value(y); + TRACE("nlsat_explain", tout << "adding literals for "; display_var(tout, y); tout << " -> "; + m_am.display_decimal(tout, y_val); tout << "\n";); + polynomial_ref p_lower(m_pm); + unsigned i_lower; + polynomial_ref p_upper(m_pm); + unsigned i_upper; + polynomial_ref p(m_pm); + unsigned sz = ps.size(); + for (unsigned k = 0; k < sz; k++) { + p = ps.get(k); + if (max_var(p) != y) + continue; + roots.reset(); + // Variable y is assigned in m_assignment. We must temporarily unassign it. + // Otherwise, the isolate_roots procedure will assume p is a constant polynomial. + m_am.isolate_roots(p, undef_var_assignment(m_assignment, y), roots); + unsigned num_roots = roots.size(); + for (unsigned i = 0; i < num_roots; i++) { + TRACE("nlsat_explain", tout << "comparing root: "; m_am.display_decimal(tout, roots[i]); tout << "\n";); + int s = m_am.compare(y_val, roots[i]); + if (s == 0) { + // y_val == roots[i] + // add literal + // ! (y = root_i(p)) + add_root_literal(atom::ROOT_EQ, y, i+1, p); + return; + } + else if (s < 0) { + // y_val < roots[i] + + // check if roots[i] is a better upper bound + if (upper_inf || m_am.lt(roots[i], upper)) { + upper_inf = false; + m_am.set(upper, roots[i]); + p_upper = p; + i_upper = i+1; + } + } + else if (s > 0) { + // roots[i] < y_val + + // check if roots[i] is a better lower bound + if (lower_inf || m_am.lt(lower, roots[i])) { + lower_inf = false; + m_am.set(lower, roots[i]); + p_lower = p; + i_lower = i+1; + } + } + } + } + + if (!lower_inf) + add_root_literal(m_full_dimensional ? atom::ROOT_GE : atom::ROOT_GT, y, i_lower, p_lower); + if (!upper_inf) + add_root_literal(m_full_dimensional ? atom::ROOT_LE : atom::ROOT_LT, y, i_upper, p_upper); + } + + /** + \brief Return true if all polynomials in ps are univariate in x. + */ + bool all_univ(polynomial_ref_vector const & ps, var x) { + unsigned sz = ps.size(); + for (unsigned i = 0; i < sz; i++) { + poly * p = ps.get(i); + if (max_var(p) != x) + return false; + if (!m_pm.is_univariate(p)) + return false; + } + return true; + } + + /** + \brief Apply model-based projection operation defined in our paper. + */ + void project(polynomial_ref_vector & ps, var max_x) { + if (ps.empty()) + return; + m_todo.reset(); + for (unsigned i = 0; i < ps.size(); i++) + m_todo.insert(ps.get(i)); + var x = m_todo.remove_max_polys(ps); + // Remark: after vanishing coefficients are eliminated, ps may not contain max_x anymore + if (x < max_x) + add_cell_lits(ps, x); + while (true) { + if (all_univ(ps, x) && m_todo.empty()) { + m_todo.reset(); + break; + } + TRACE("nlsat_explain", tout << "project loop, processing var "; display_var(tout, x); tout << "\npolynomials\n"; + display(tout, ps); tout << "\n";); + add_lc(ps, x); + psc_discriminant(ps, x); + psc_resultant(ps, x); + if (m_todo.empty()) + break; + x = m_todo.remove_max_polys(ps); + add_cell_lits(ps, x); + } + } + + bool check_already_added() const { + for (unsigned i = 0; i < m_already_added_literal.size(); i++) { + SASSERT(m_already_added_literal[i] == false); + } + return true; + } + + /* + Conflicting core simplification using equations. + The idea is to use equations to reduce the complexity of the + conflicting core. + + Basic idea: + Let l be of the form + h > 0 + and eq of the form + p = 0 + + Using pseudo-division we have that: + lc(p)^d h = q p + r + where q and r are the pseudo-quotient and pseudo-remainder + d is the integer returned by the pseudo-division algorithm. + lc(p) is the leading coefficient of p + If d is even or sign(lc(p)) > 0, we have that + sign(h) = sign(r) + Otherwise + sign(h) = -sign(r) flipped the sign + + We have the following rules + + If + (C and h > 0) implies false + Then + 1. (C and p = 0 and lc(p) != 0 and r > 0) implies false if d is even + 2. (C and p = 0 and lc(p) > 0 and r > 0) implies false if lc(p) > 0 and d is odd + 3. (C and p = 0 and lc(p) < 0 and r < 0) implies false if lc(p) < 0 and d is odd + + If + (C and h = 0) implies false + Then + (C and p = 0 and lc(p) != 0 and r = 0) implies false + + If + (C and h < 0) implies false + Then + 1. (C and p = 0 and lc(p) != 0 and r < 0) implies false if d is even + 2. (C and p = 0 and lc(p) > 0 and r < 0) implies false if lc(p) > 0 and d is odd + 3. (C and p = 0 and lc(p) < 0 and r > 0) implies false if lc(p) < 0 and d is odd + + Good cases: + - lc(p) is a constant + - p = 0 is already in the conflicting core + - p = 0 is linear + + We only use equations from the conflicting core and lower stages. + Equations from lower stages are automatically added to the lemma. + */ + struct eq_info { + poly const * m_eq; + polynomial::var m_x; + unsigned m_k; + poly * m_lc; + int m_lc_sign; + bool m_lc_const; + bool m_lc_add; + bool m_lc_add_ineq; + void add_lc_ineq() { + m_lc_add = true; + m_lc_add_ineq = true; + } + void add_lc_diseq() { + if (!m_lc_add) { + m_lc_add = true; + m_lc_add_ineq = false; + } + } + }; + void simplify(literal l, eq_info & info, var max, scoped_literal & new_lit) { + bool_var b = l.var(); + atom * a = m_atoms[b]; + SASSERT(a != 0); + if (a->is_root_atom()) { + new_lit = l; + return; + } + ineq_atom * _a = to_ineq_atom(a); + unsigned num_factors = _a->size(); + if (num_factors == 1 && _a->p(0) == info.m_eq) { + new_lit = l; + return; + } + TRACE("nlsat_simplify_core", tout << "trying to simplify literal\n"; display(tout, l); tout << "\nusing equation\n"; + m_pm.display(tout, info.m_eq, m_solver.display_proc()); tout << "\n";); + int atom_sign = 1; + bool modified_lit = false; + polynomial_ref_buffer new_factors(m_pm); + sbuffer new_factors_even; + polynomial_ref new_factor(m_pm); + for (unsigned s = 0; s < num_factors; s++) { + poly * f = _a->p(s); + bool is_even = _a->is_even(s); + if (m_pm.degree(f, info.m_x) < info.m_k) { + new_factors.push_back(f); + new_factors_even.push_back(is_even); + continue; + } + modified_lit = true; + unsigned d; + m_pm.pseudo_remainder(f, info.m_eq, info.m_x, d, new_factor); + // adjust sign based on sign of lc of eq + if (d % 2 == 1 && // d is odd + !is_even && // degree of the factor is odd + info.m_lc_sign < 0 // lc of the eq is negative + ) { + atom_sign = -atom_sign; // flipped the sign of the current literal + } + if (is_const(new_factor)) { + int s = sign(new_factor); + if (s == 0) { + bool atom_val = a->get_kind() == atom::EQ; + bool lit_val = l.sign() ? !atom_val : atom_val; + new_lit = lit_val ? true_literal : false_literal; + return; + } + else if (s == -1 && !is_even) { + atom_sign = -atom_sign; + } + } + else { + new_factors.push_back(new_factor); + new_factors_even.push_back(is_even); + if (!info.m_lc_const) { + if (d % 2 == 0 || // d is even + is_even || // rewriting a factor of even degree, sign flip doesn't matter + _a->get_kind() == atom::EQ) { // rewriting an equation, sign flip doesn't matter + info.add_lc_diseq(); + } + else { + info.add_lc_ineq(); + } + } + } + } + if (modified_lit) { + atom::kind new_k = _a->get_kind(); + if (atom_sign < 0) + new_k = atom::flip(new_k); + new_lit = m_solver.mk_ineq_literal(new_k, new_factors.size(), new_factors.c_ptr(), new_factors_even.c_ptr()); + if (l.sign()) + new_lit.neg(); + TRACE("nlsat_simplify_core", tout << "simplified literal:\n"; display(tout, new_lit); tout << "\n";); + if (max_var(new_lit) < max) { + // The conflicting core may have redundant literals. + // We should check whether new_lit is true in the current model, and discard it if that is the case + lbool val = m_solver.value(new_lit); + SASSERT(val != l_undef); + if (m_solver.value(new_lit) == l_false) + add_literal(new_lit); + new_lit = true_literal; + return; + } + new_lit = normalize(new_lit, max); + TRACE("nlsat_simplify_core", tout << "simplified literal after normalization:\n"; display(tout, new_lit); tout << "\n";); + } + else { + new_lit = l; + } + } + + bool simplify(scoped_literal_vector & C, poly const * eq, var max) { + bool modified_core = false; + eq_info info; + info.m_eq = eq; + info.m_x = m_pm.max_var(info.m_eq); + info.m_k = m_pm.degree(eq, info.m_x); + polynomial_ref lc_eq(m_pm); + lc_eq = m_pm.coeff(eq, info.m_x, info.m_k); + info.m_lc = lc_eq.get(); + info.m_lc_sign = sign(lc_eq); + info.m_lc_add = false; + info.m_lc_const = m_pm.is_const(lc_eq); + SASSERT(info.m_lc != 0); + scoped_literal new_lit(m_solver); + unsigned sz = C.size(); + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + literal l = C[i]; + new_lit = null_literal; + simplify(l, info, max, new_lit); + SASSERT(new_lit != null_literal); + if (l == new_lit) { + C.set(j, l); + j++; + continue; + } + modified_core = true; + if (new_lit == true_literal) + continue; + if (new_lit == false_literal) { + // false literal was created. The assumptions added are sufficient for implying the conflict. + j = 0; // force core to be reset + break; + } + C.set(j, new_lit); + j++; + } + C.shrink(j); + if (info.m_lc_add) { + if (info.m_lc_add_ineq) + add_assumption(info.m_lc_sign < 0 ? atom::LT : atom::GT, info.m_lc); + else + add_assumption(atom::EQ, info.m_lc, true); + } + return modified_core; + } + + /** + \brief (try to) Select an equation from C. Returns 0 if C does not contain any equality. + This method selects the equation of minimal degree in max. + */ + poly * select_eq(scoped_literal_vector & C, var max) { + poly * r = 0; + unsigned min_d = UINT_MAX; + unsigned sz = C.size(); + for (unsigned i = 0; i < sz; i++) { + literal l = C[i]; + if (l.sign()) + continue; + bool_var b = l.var(); + atom * a = m_atoms[b]; + SASSERT(a != 0); + if (a->get_kind() != atom::EQ) + continue; + ineq_atom * _a = to_ineq_atom(a); + if (_a->size() > 1) + continue; + if (_a->is_even(0)) + continue; + unsigned d = m_pm.degree(_a->p(0), max); + SASSERT(d > 0); + if (d < min_d) { + r = _a->p(0); + min_d = d; + if (min_d == 1) + break; + } + } + return r; + } + + /** + \brief Select an equation eq s.t. + max_var(eq) < max, and + it can be used to rewrite a literal in C. + Return 0, if such equation was not found. + */ + var_vector m_select_tmp; + ineq_atom * select_lower_stage_eq(scoped_literal_vector & C, var max) { + var_vector & xs = m_select_tmp; + unsigned sz = C.size(); + for (unsigned i = 0; i < sz; i++) { + literal l = C[i]; + bool_var b = l.var(); + atom * a = m_atoms[b]; + if (a->is_root_atom()) + continue; // we don't rewrite root atoms + ineq_atom * _a = to_ineq_atom(a); + unsigned num_factors = _a->size(); + for (unsigned j = 0; j < num_factors; j++) { + poly * p = _a->p(j); + xs.reset(); + m_pm.vars(p, xs); + unsigned xs_sz = xs.size(); + for (unsigned k = 0; k < xs_sz; k++) { + var y = xs[k]; + if (y >= max) + continue; + atom * eq = m_x2eq[y]; + if (eq == 0) + continue; + SASSERT(eq->is_ineq_atom()); + SASSERT(to_ineq_atom(eq)->size() == 1); + SASSERT(!to_ineq_atom(eq)->is_even(0)); + poly * eq_p = to_ineq_atom(eq)->p(0); + SASSERT(m_pm.degree(eq_p, y) > 0); + // TODO: create a parameter + // In the current experiments, using equations with non constant coefficients produces a blowup + if (!m_pm.nonzero_const_coeff(eq_p, y, m_pm.degree(eq_p, y))) + continue; + if (m_pm.degree(p, y) >= m_pm.degree(eq_p, y)) + return to_ineq_atom(eq); + } + } + } + return 0; + } + + /** + \brief Simplify the core using equalities. + */ + void simplify(scoped_literal_vector & C, var max) { + // Simplify using equations in the core + while (!C.empty()) { + poly * eq = select_eq(C, max); + if (eq == 0) + break; + TRACE("nlsat_simplify_core", tout << "using equality for simplifying core\n"; + m_pm.display(tout, eq, m_solver.display_proc()); tout << "\n";); + if (!simplify(C, eq, max)) + break; + } + // Simplify using equations using variables from lower stages. + while (!C.empty()) { + ineq_atom * eq = select_lower_stage_eq(C, max); + if (eq == 0) + break; + SASSERT(eq->size() == 1); + SASSERT(!eq->is_even(0)); + poly * eq_p = eq->p(0); + VERIFY(simplify(C, eq_p, max)); + // add equation as an assumption + add_literal(literal(eq->bvar(), true)); + } + } + + /** + \brief Main procedure. The explain the given unsat core, and store the result in m_result + */ + void main(unsigned num, literal const * ls) { + if (num == 0) + return; + collect_polys(num, ls, m_ps); + var max_x = max_var(m_ps); + TRACE("nlsat_explain", tout << "polynomials in the conflict:\n"; display(tout, m_ps); tout << "\n";); + elim_vanishing(m_ps); + project(m_ps, max_x); + } + + void process2(unsigned num, literal const * ls) { + if (m_simplify_cores) { + m_core2.reset(); + m_core2.append(num, ls); + var max = max_var(num, ls); + SASSERT(max != null_var); + normalize(m_core2, max); + TRACE("nlsat_explain", tout << "core after normalization\n"; display(tout, m_core2);); + simplify(m_core2, max); + TRACE("nlsat_explain", tout << "core after simplify\n"; display(tout, m_core2);); + main(m_core2.size(), m_core2.c_ptr()); + m_core2.reset(); + } + else { + main(num, ls); + } + } + + // Auxiliary method for core minimization. + literal_vector m_min_newtodo; + bool minimize_core(literal_vector & todo, literal_vector & core) { + SASSERT(!todo.empty()); + literal_vector & new_todo = m_min_newtodo; + new_todo.reset(); + interval_set_manager & ism = m_evaluator.ism(); + interval_set_ref r(ism); + // Copy the union of the infeasible intervals of core into r. + unsigned sz = core.size(); + for (unsigned i = 0; i < sz; i++) { + literal l = core[i]; + atom * a = m_atoms[l.var()]; + SASSERT(a != 0); + interval_set_ref inf = m_evaluator.infeasible_intervals(a, l.sign()); + r = ism.mk_union(inf, r); + if (ism.is_full(r)) { + // Done + return false; + } + } + TRACE("nlsat_mininize", tout << "interval set after adding partial core:\n" << r << "\n";); + if (todo.size() == 1) { + // Done + core.push_back(todo[0]); + return false; + } + // Copy the union of the infeasible intervals of todo into r until r becomes full. + sz = todo.size(); + for (unsigned i = 0; i < sz; i++) { + literal l = todo[i]; + atom * a = m_atoms[l.var()]; + SASSERT(a != 0); + interval_set_ref inf = m_evaluator.infeasible_intervals(a, l.sign()); + r = ism.mk_union(inf, r); + if (ism.is_full(r)) { + // literal l must be in the core + core.push_back(l); + new_todo.swap(todo); + return true; + } + else { + new_todo.push_back(l); + } + } + UNREACHABLE(); + return true; + } + + literal_vector m_min_todo; + literal_vector m_min_core; + void minimize(unsigned num, literal const * ls, scoped_literal_vector & r) { + literal_vector & todo = m_min_todo; + literal_vector & core = m_min_core; + todo.reset(); core.reset(); + todo.append(num, ls); + while (true) { + TRACE("nlsat_mininize", tout << "core minimization:\n"; display(tout, todo); tout << "\nCORE:\n"; display(tout, core);); + if (!minimize_core(todo, core)) + break; + std::reverse(todo.begin(), todo.end()); + TRACE("nlsat_mininize", tout << "core minimization:\n"; display(tout, todo); tout << "\nCORE:\n"; display(tout, core);); + if (!minimize_core(todo, core)) + break; + } + TRACE("nlsat_mininize", tout << "core:\n"; display(tout, core);); + r.append(core.size(), core.c_ptr()); + } + + void process(unsigned num, literal const * ls) { + if (m_minimize_cores && num > 1) { + m_core1.reset(); + minimize(num, ls, m_core1); + process2(m_core1.size(), m_core1.c_ptr()); + m_core1.reset(); + } + else { + process2(num, ls); + } + } + + void operator()(unsigned num, literal const * ls, scoped_literal_vector & result) { + SASSERT(check_already_added()); + SASSERT(num > 0); + TRACE("nlsat_explain", tout << "[explain] set of literals is infeasible in the current interpretation\n"; display(tout, num, ls);); + // exit(0); + m_result = &result; + process(num, ls); + reset_already_added(); + m_result = 0; + TRACE("nlsat_explain", tout << "[explain] result\n"; display(tout, result);); + CASSERT("nlsat", check_already_added()); + } + }; + + explain::explain(solver & s, assignment const & x2v, polynomial::cache & u, atom_vector const & atoms, atom_vector const & x2eq, + evaluator & ev) { + m_imp = alloc(imp, s, x2v, u, atoms, x2eq, ev); + } + + explain::~explain() { + dealloc(m_imp); + } + + void explain::reset() { + m_imp->m_core1.reset(); + m_imp->m_core2.reset(); + } + + void explain::set_simplify_cores(bool f) { + m_imp->m_simplify_cores = f; + } + + void explain::set_full_dimensional(bool f) { + m_imp->m_full_dimensional = f; + } + + void explain::set_minimize_cores(bool f) { + m_imp->m_minimize_cores = f; + } + + void explain::operator()(unsigned n, literal const * ls, scoped_literal_vector & result) { + (*m_imp)(n, ls, result); + } + +}; diff --git a/lib/nlsat_explain.h b/lib/nlsat_explain.h new file mode 100644 index 000000000..08f6201be --- /dev/null +++ b/lib/nlsat_explain.h @@ -0,0 +1,64 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + nlsat_explain.h + +Abstract: + + Functor that implements the "explain" procedure defined in Dejan and Leo's paper. + +Author: + + Leonardo de Moura (leonardo) 2012-01-13. + +Revision History: + +--*/ +#ifndef _NLSAT_EXPLAIN_H_ +#define _NLSAT_EXPLAIN_H_ + +#include"nlsat_solver.h" +#include"nlsat_scoped_literal_vector.h" +#include"polynomial_cache.h" + +namespace nlsat { + class evaluator; + + class explain { + struct imp; + imp * m_imp; + public: + explain(solver & s, assignment const & x2v, polynomial::cache & u, atom_vector const & atoms, atom_vector const & x2eq, + evaluator & ev); + ~explain(); + + void reset(); + void set_simplify_cores(bool f); + void set_full_dimensional(bool f); + void set_minimize_cores(bool f); + + /** + \brief Given a set of literals ls[0], ... ls[n-1] s.t. + - n > 0 + - all of them are arithmetic literals. + - all of them have the same maximal variable. + - (ls[0] and ... and ls[n-1]) is infeasible in the current interpretation. + + Let x be the maximal variable in {ls[0], ..., ls[n-1]}. + + Remark: the current interpretation assigns all variables in ls[0], ..., ls[n-1] but x. + + This procedure stores in result a set of literals: s_1, ..., s_m + s.t. + - (s_1 or ... or s_m or ~ls[0] or ... or ~ls[n-1]) is a valid clause + - s_1, ..., s_m do not contain variable x. + - s_1, ..., s_m are false in the current interpretation + */ + void operator()(unsigned n, literal const * ls, scoped_literal_vector & result); + }; + +}; + +#endif diff --git a/lib/nlsat_interval_set.cpp b/lib/nlsat_interval_set.cpp new file mode 100644 index 000000000..68609cb3f --- /dev/null +++ b/lib/nlsat_interval_set.cpp @@ -0,0 +1,762 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + nlsat_interval_set.cpp + +Abstract: + + Sets of disjoint infeasible intervals. + +Author: + + Leonardo de Moura (leonardo) 2012-01-11. + +Revision History: + +--*/ +#include"nlsat_interval_set.h" +#include"algebraic_numbers.h" +#include"buffer.h" + +namespace nlsat { + + struct interval { + unsigned m_lower_open:1; + unsigned m_upper_open:1; + unsigned m_lower_inf:1; + unsigned m_upper_inf:1; + literal m_justification; + anum m_lower; + anum m_upper; + }; + + class interval_set { + public: + static unsigned get_obj_size(unsigned num) { return sizeof(interval_set) + num*sizeof(interval); } + unsigned m_num_intervals; + unsigned m_ref_count:31; + unsigned m_full:1; + interval m_intervals[0]; + }; + + void display(std::ostream & out, anum_manager & am, interval const & curr) { + if (curr.m_lower_inf) { + out << "(-oo, "; + } + else { + if (curr.m_lower_open) + out << "("; + else + out << "["; + am.display_decimal(out, curr.m_lower); + out << ", "; + } + if (curr.m_justification.sign()) + out << "~"; + out << "p"; + out << curr.m_justification.var() << ", "; + if (curr.m_upper_inf) { + out << "oo)"; + } + else { + am.display_decimal(out, curr.m_upper); + if (curr.m_upper_open) + out << ")"; + else + out << "]"; + } + } + + bool check_interval(anum_manager & am, interval const & i) { + if (i.m_lower_inf) { + SASSERT(i.m_lower_open); + } + if (i.m_upper_inf) { + SASSERT(i.m_upper_open); + } + if (!i.m_lower_inf && !i.m_upper_inf) { + int s = am.compare(i.m_lower, i.m_upper); + TRACE("nlsat_interval", tout << "lower: "; am.display_decimal(tout, i.m_lower); tout << ", upper: "; am.display_decimal(tout, i.m_upper); + tout << "\ns: " << s << "\n";); + SASSERT(s <= 0); + if (s == 0) { + SASSERT(!i.m_lower_open && !i.m_upper_open); + } + } + return true; + } + + bool check_no_overlap(anum_manager & am, interval const & curr, interval const & next) { + SASSERT(!curr.m_upper_inf); + SASSERT(!next.m_lower_inf); + int sign = am.compare(curr.m_upper, next.m_lower); + CTRACE("nlsat", sign > 0, display(tout, am, curr); tout << " "; display(tout, am, next); tout << "\n";); + SASSERT(sign <= 0); + if (sign == 0) { + SASSERT(curr.m_upper_open || next.m_lower_open); + } + return true; + } + + // Check if the intervals are valid, ordered, and are disjoint. + bool check_interval_set(anum_manager & am, unsigned sz, interval const * ints) { + for (unsigned i = 0; i < sz; i++) { + interval const & curr = ints[i]; + SASSERT(check_interval(am, curr)); + if (i < sz - 1) { + interval const & next = ints[i+1]; + SASSERT(check_no_overlap(am, curr, next)); + } + } + return true; + } + + interval_set_manager::interval_set_manager(anum_manager & m, small_object_allocator & a): + m_am(m), + m_allocator(a) { + } + + interval_set_manager::~interval_set_manager() { + } + + void interval_set_manager::del(interval_set * s) { + if (s == 0) + return; + unsigned num = s->m_num_intervals; + unsigned obj_sz = interval_set::get_obj_size(num); + for (unsigned i = 0; i < num; i++) { + m_am.del(s->m_intervals[i].m_lower); + m_am.del(s->m_intervals[i].m_upper); + } + s->~interval_set(); + m_allocator.deallocate(obj_sz, s); + } + + void interval_set_manager::dec_ref(interval_set * s) { + SASSERT(s->m_ref_count > 0); + s->m_ref_count--; + if (s->m_ref_count == 0) + del(s); + } + + void interval_set_manager::inc_ref(interval_set * s) { + s->m_ref_count++; + } + + interval_set * interval_set_manager::mk(bool lower_open, bool lower_inf, anum const & lower, + bool upper_open, bool upper_inf, anum const & upper, + literal justification) { + void * mem = m_allocator.allocate(interval_set::get_obj_size(1)); + interval_set * new_set = new (mem) interval_set(); + new_set->m_num_intervals = 1; + new_set->m_ref_count = 0; + new_set->m_full = lower_inf && upper_inf; + interval * i = new (new_set->m_intervals) interval(); + i->m_lower_open = lower_open; + i->m_lower_inf = lower_inf; + i->m_upper_open = upper_open; + i->m_upper_inf = upper_inf; + i->m_justification = justification; + if (!lower_inf) + m_am.set(i->m_lower, lower); + if (!upper_inf) + m_am.set(i->m_upper, upper); + SASSERT(check_interval_set(m_am, 1, new_set->m_intervals)); + return new_set; + } + + inline int compare_lower_lower(anum_manager & am, interval const & i1, interval const & i2) { + if (i1.m_lower_inf && i2.m_lower_inf) + return 0; + if (i1.m_lower_inf) + return -1; + if (i2.m_lower_inf) + return 1; + SASSERT(!i1.m_lower_inf && !i2.m_lower_inf); + int s = am.compare(i1.m_lower, i2.m_lower); + if (s != 0) + return s; + if (i1.m_lower_open == i2.m_lower_open) + return 0; + if (i1.m_lower_open) + return 1; + else + return -1; + } + + inline int compare_upper_upper(anum_manager & am, interval const & i1, interval const & i2) { + if (i1.m_upper_inf && i2.m_upper_inf) + return 0; + if (i1.m_upper_inf) + return 1; + if (i2.m_upper_inf) + return -1; + SASSERT(!i1.m_upper_inf && !i2.m_upper_inf); + int s = am.compare(i1.m_upper, i2.m_upper); + if (s != 0) + return s; + if (i1.m_upper_open == i2.m_upper_open) + return 0; + if (i1.m_upper_open) + return -1; + else + return 1; + } + + inline int compare_upper_lower(anum_manager & am, interval const & i1, interval const & i2) { + if (i1.m_upper_inf || i2.m_lower_inf) + return 1; + SASSERT(!i1.m_upper_inf && !i2.m_lower_inf); + int s = am.compare(i1.m_upper, i2.m_lower); + if (s != 0) + return s; + if (!i1.m_upper_open && !i2.m_lower_open) + return 0; + return -1; + } + + typedef sbuffer interval_buffer; + + // Given two interval in an interval set s.t. curr occurs before next. + // We say curr and next are "adjacent" iff + // there is no "space" between them. + bool adjacent(anum_manager & am, interval const & curr, interval const & next) { + SASSERT(!curr.m_upper_inf); + SASSERT(!next.m_lower_inf); + int sign = am.compare(curr.m_upper, next.m_lower); + SASSERT(sign <= 0); + if (sign == 0) { + SASSERT(curr.m_upper_open || next.m_lower_open); + return !curr.m_upper_open || !next.m_lower_open; + } + return false; + } + + inline void push_back(anum_manager & am, interval_buffer & buf, + bool lower_open, bool lower_inf, anum const & lower, + bool upper_open, bool upper_inf, anum const & upper, + literal justification) { + buf.push_back(interval()); + interval & i = buf.back(); + i.m_lower_open = lower_open; + i.m_lower_inf = lower_inf; + am.set(i.m_lower, lower); + i.m_upper_open = upper_open; + i.m_upper_inf = upper_inf; + am.set(i.m_upper, upper); + i.m_justification = justification; + SASSERT(check_interval(am, i)); + } + + inline void push_back(anum_manager & am, interval_buffer & buf, interval const & i) { + push_back(am, buf, + i.m_lower_open, i.m_lower_inf, i.m_lower, + i.m_upper_open, i.m_upper_inf, i.m_upper, + i.m_justification); + } + + inline interval_set * mk_interval(small_object_allocator & allocator, interval_buffer & buf, bool full) { + unsigned sz = buf.size(); + void * mem = allocator.allocate(interval_set::get_obj_size(sz)); + interval_set * new_set = new (mem) interval_set(); + new_set->m_full = full; + new_set->m_ref_count = 0; + new_set->m_num_intervals = sz; + memcpy(new_set->m_intervals, buf.c_ptr(), sizeof(interval)*sz); + return new_set; + } + + interval_set * interval_set_manager::mk_union(interval_set const * s1, interval_set const * s2) { + TRACE("nlsat_interval", tout << "mk_union\ns1: "; display(tout, s1); tout << "\ns2: "; display(tout, s2); tout << "\n";); + if (s1 == 0 || s1 == s2) + return const_cast(s2); + if (s2 == 0) + return const_cast(s1); + if (s1->m_full) + return const_cast(s1); + if (s2->m_full) + return const_cast(s2); + interval_buffer result; + unsigned sz1 = s1->m_num_intervals; + unsigned sz2 = s2->m_num_intervals; + unsigned i1 = 0; + unsigned i2 = 0; + while (true) { + if (i1 >= sz1) { + while (i2 < sz2) { + TRACE("nlsat_interval", tout << "adding remaining intervals from s2: "; nlsat::display(tout, m_am, s2->m_intervals[i2]); tout << "\n";); + push_back(m_am, result, s2->m_intervals[i2]); + i2++; + } + break; + } + if (i2 >= sz2) { + while (i1 < sz1) { + TRACE("nlsat_interval", tout << "adding remaining intervals from s1: "; nlsat::display(tout, m_am, s1->m_intervals[i1]); tout << "\n";); + push_back(m_am, result, s1->m_intervals[i1]); + i1++; + } + break; + } + interval const & int1 = s1->m_intervals[i1]; + interval const & int2 = s2->m_intervals[i2]; + int l1_l2_sign = compare_lower_lower(m_am, int1, int2); + int u1_u2_sign = compare_upper_upper(m_am, int1, int2); + TRACE("nlsat_interval", + tout << "i1: " << i1 << ", i2: " << i2 << "\n"; + tout << "int1: "; nlsat::display(tout, m_am, int1); tout << "\n"; + tout << "int2: "; nlsat::display(tout, m_am, int2); tout << "\n";); + if (l1_l2_sign <= 0) { + if (u1_u2_sign == 0) { + // Cases: + // 1) [ ] + // [ ] + // + // 2) [ ] + // [ ] + // + TRACE("nlsat_interval", tout << "l1_l2_sign <= 0, u1_u2_sign == 0\n";); + push_back(m_am, result, int1); + i1++; + i2++; + } + else if (u1_u2_sign > 0) { + // Cases: + // + // 1) [ ] + // [ ] + // + // 2) [ ] + // [ ] + i2++; + TRACE("nlsat_interval", tout << "l1_l2_sign <= 0, u1_u2_sign > 0\n";); + // i1 may consume other intervals of s2 + } + else { + SASSERT(u1_u2_sign < 0); + int u1_l2_sign = compare_upper_lower(m_am, int1, int2); + if (u1_l2_sign < 0) { + SASSERT(l1_l2_sign < 0); + // Cases: + // 1) [ ] + // [ ] + TRACE("nlsat_interval", tout << "l1_l2_sign <= 0, u1_u2_sign < 0, u1_l2_sign < 0\n";); + push_back(m_am, result, int1); + i1++; + } + else if (u1_l2_sign == 0) { + SASSERT(l1_l2_sign <= 0); + SASSERT(!int1.m_upper_open && !int2.m_lower_open); + SASSERT(!int2.m_lower_inf); + TRACE("nlsat_interval", tout << "l1_l2_sign <= 0, u1_u2_sign < 0, u1_l2_sign == 0\n";); + // Cases: + if (l1_l2_sign != 0) { + SASSERT(l1_l2_sign < 0); + // 1) [ ] + // [ ] + SASSERT(!int2.m_lower_open); + push_back(m_am, result, + int1.m_lower_open, int1.m_lower_inf, int1.m_lower, + true /* open */, false /* not +oo */, int1.m_upper, + int1.m_justification); + i1++; + } + else { + SASSERT(l1_l2_sign == 0); + // 2) u <<< int1 is a singleton + // [ ] + // just consume int1 + i1++; + } + } + else { + SASSERT(l1_l2_sign <= 0); + SASSERT(u1_u2_sign < 0); + SASSERT(u1_l2_sign > 0); + TRACE("nlsat_interval", tout << "l1_l2_sign <= 0, u1_u2_sign < 0, u1_l2_sign > 0\n";); + if (l1_l2_sign == 0) { + // Case: + // 1) [ ] + // [ ] + // just consume int1 + i1++; + } + else { + SASSERT(l1_l2_sign < 0); + SASSERT(u1_u2_sign < 0); + SASSERT(u1_l2_sign > 0); + // 2) [ ] + // [ ] + push_back(m_am, result, + int1.m_lower_open, int1.m_lower_inf, int1.m_lower, + !int2.m_lower_open, false /* not +oo */, int2.m_lower, + int1.m_justification); + i1++; + } + } + } + } + else { + SASSERT(l1_l2_sign > 0); + if (u1_u2_sign == 0) { + TRACE("nlsat_interval", tout << "l1_l2_sign > 0, u1_u2_sign == 0\n";); + // Case: + // 1) [ ] + // [ ] + // + push_back(m_am, result, int2); + i1++; + i2++; + } + else if (u1_u2_sign < 0) { + TRACE("nlsat_interval", tout << "l1_l2_sign > 0, u1_u2_sign > 0\n";); + // Case: + // 1) [ ] + // [ ] + i1++; + // i2 may consume other intervals of s1 + } + else { + int u2_l1_sign = compare_upper_lower(m_am, int2, int1); + if (u2_l1_sign < 0) { + TRACE("nlsat_interval", tout << "l1_l2_sign > 0, u1_u2_sign > 0, u2_l1_sign < 0\n";); + // Case: + // 1) [ ] + // [ ] + push_back(m_am, result, int2); + i2++; + } + else if (u2_l1_sign == 0) { + TRACE("nlsat_interval", tout << "l1_l2_sign > 0, u1_u2_sign > 0, u2_l1_sign == 0\n";); + SASSERT(!int1.m_lower_open && !int2.m_upper_open); + SASSERT(!int1.m_lower_inf); + // Case: + // [ ] + // [ ] + push_back(m_am, result, + int2.m_lower_open, int2.m_lower_inf, int2.m_lower, + true /* open */, false /* not +oo */, int2.m_upper, + int2.m_justification); + i2++; + } + else { + TRACE("nlsat_interval", tout << "l1_l2_sign > 0, u1_u2_sign > 0, u2_l1_sign > 0\n";); + SASSERT(l1_l2_sign > 0); + SASSERT(u1_u2_sign > 0); + SASSERT(u2_l1_sign > 0); + // Case: + // [ ] + // [ ] + push_back(m_am, result, + int2.m_lower_open, int2.m_lower_inf, int2.m_lower, + !int1.m_lower_open, false /* not +oo */, int1.m_lower, + int2.m_justification); + i2++; + } + } + } + SASSERT(result.size() <= 1 || + check_no_overlap(m_am, result[result.size() - 2], result[result.size() - 1])); + } + + SASSERT(!result.empty()); + SASSERT(check_interval_set(m_am, result.size(), result.c_ptr())); + // Compress + // Remark: we only combine adjacent intervals when they have the same justification + unsigned j = 0; + unsigned sz = result.size(); + for (unsigned i = 1; i < sz; i++) { + interval & curr = result[j]; + interval & next = result[i]; + if (curr.m_justification == next.m_justification && + adjacent(m_am, curr, next)) { + // merge them + curr.m_upper_inf = next.m_upper_inf; + curr.m_upper_open = next.m_upper_open; + m_am.swap(curr.m_upper, next.m_upper); + } + else { + j++; + if (i != j) { + interval & next_curr = result[j]; + next_curr.m_lower_inf = next.m_lower_inf; + next_curr.m_lower_open = next.m_lower_open; + m_am.swap(next_curr.m_lower, next.m_lower); + next_curr.m_upper_inf = next.m_upper_inf; + next_curr.m_upper_open = next.m_upper_open; + m_am.swap(next_curr.m_upper, next.m_upper); + next_curr.m_justification = next.m_justification; + } + } + } + j++; + for (unsigned i = j; i < sz; i++) { + interval & curr = result[i]; + m_am.del(curr.m_lower); + m_am.del(curr.m_upper); + } + result.shrink(j); + SASSERT(check_interval_set(m_am, result.size(), result.c_ptr())); + sz = j; + SASSERT(sz >= 1); + bool found_slack = !result[0].m_lower_inf || !result[sz-1].m_upper_inf; + // Check if full + for (unsigned i = 0; i < sz - 1 && !found_slack; i++) { + if (!adjacent(m_am, result[i], result[i+1])) + found_slack = true; + } + // Create new interval set + interval_set * new_set = mk_interval(m_allocator, result, !found_slack); + SASSERT(check_interval_set(m_am, sz, new_set->m_intervals)); + return new_set; + } + + bool interval_set_manager::is_full(interval_set const * s) { + if (s == 0) + return false; + return s->m_full == 1; + } + + unsigned interval_set_manager::num_intervals(interval_set const * s) const { + if (s == 0) return 0; + return s->m_num_intervals; + } + + bool interval_set_manager::subset(interval_set const * s1, interval_set const * s2) { + if (s1 == s2) + return true; + if (s1 == 0) + return true; + if (s2 == 0) + return false; + if (s2->m_full) + return true; + if (s1->m_full) + return false; + unsigned sz1 = s1->m_num_intervals; + unsigned sz2 = s2->m_num_intervals; + SASSERT(sz1 > 0 && sz2 > 0); + unsigned i1 = 0; + unsigned i2 = 0; + while (i1 < sz1 && i2 < sz2) { + interval const & int1 = s1->m_intervals[i1]; + interval const & int2 = s2->m_intervals[i2]; + TRACE("nlsat_interval", tout << "subset main loop, i1: " << i1 << ", i2: " << i2 << "\n"; + tout << "int1: "; nlsat::display(tout, m_am, int1); tout << "\n"; + tout << "int2: "; nlsat::display(tout, m_am, int2); tout << "\n";); + if (compare_lower_lower(m_am, int1, int2) < 0) { + TRACE("nlsat_interval", tout << "done\n";); + // interval [int1.lower1, int2.lower2] is not in s2 + // s1: [ ... + // s2: [ ... + return false; + } + while (i2 < sz2) { + interval const & int2 = s2->m_intervals[i2]; + TRACE("nlsat_interval", tout << "inner loop, i2: " << i2 << "\n"; + tout << "int2: "; nlsat::display(tout, m_am, int2); tout << "\n";); + int u1_u2_sign = compare_upper_upper(m_am, int1, int2); + if (u1_u2_sign == 0) { + TRACE("nlsat_interval", tout << "case 1, break\n";); + // consume both + // s1: ... ] + // s2: ... ] + i1++; + i2++; + break; + } + else if (u1_u2_sign < 0) { + TRACE("nlsat_interval", tout << "case 2, break\n";); + // consume only int1, int2 may cover other intervals of s1 + // s1: ... ] + // s2: ... ] + i1++; + break; + } + else { + SASSERT(u1_u2_sign > 0); + int u2_l1_sign = compare_upper_lower(m_am, int2, int1); + TRACE("nlsat_interval", tout << "subset, u2_l1_sign: " << u2_l1_sign << "\n";); + if (u2_l1_sign < 0) { + TRACE("nlsat_interval", tout << "case 3, break\n";); + // s1: [ ... + // s2: [ ... ] ... + i2++; + break; + } + SASSERT(u2_l1_sign >= 0); + // s1: [ ... ] + // s2: [ ... ] + if (i2 == sz2 - 1) { + TRACE("nlsat_interval", tout << "case 4, done\n";); + // s1: ... ] + // s2: ...] + // the interval [int2.upper, int1.upper] is not in s2 + return false; // last interval of s2 + } + interval const & next2 = s2->m_intervals[i2+1]; + if (!adjacent(m_am, int2, next2)) { + TRACE("nlsat_interval", tout << "not adjacent, done\n";); + // s1: ... ] + // s2: ... ] [ + // the interval [int2.upper, min(int1.upper, next2.lower)] is not in s2 + return false; + } + TRACE("nlsat_interval", tout << "continue..\n";); + // continue with adjacent interval of s2 + // s1: ... ] + // s2: ..][ ... + i2++; + } + } + } + return i1 == sz1; + } + + bool interval_set_manager::set_eq(interval_set const * s1, interval_set const * s2) { + if (s1 == 0 || s2 == 0) + return s1 == s2; + if (s1->m_full || s2->m_full) + return s1->m_full == s2->m_full; + // TODO: check if bottleneck, then replace simple implementation + return subset(s1, s2) && subset(s2, s1); + } + + bool interval_set_manager::eq(interval_set const * s1, interval_set const * s2) { + if (s1 == 0 || s2 == 0) + return s1 == s2; + if (s1->m_num_intervals != s2->m_num_intervals) + return false; + for (unsigned i = 0; i < s1->m_num_intervals; i++) { + interval const & int1 = s1->m_intervals[i]; + interval const & int2 = s2->m_intervals[i]; + if (int1.m_lower_inf != int2.m_lower_inf || + int1.m_lower_open != int2.m_lower_open || + int1.m_upper_inf != int2.m_upper_inf || + int1.m_upper_open != int2.m_upper_open || + int1.m_justification != int2.m_justification || + !m_am.eq(int1.m_lower, int2.m_lower) || + !m_am.eq(int1.m_upper, int2.m_upper)) + return false; + } + return true; + } + + void interval_set_manager::get_justifications(interval_set const * s, literal_vector & js) { + js.reset(); + unsigned num = num_intervals(s); + for (unsigned i = 0; i < num; i++) { + literal l = s->m_intervals[i].m_justification; + unsigned lidx = l.index(); + if (m_already_visited.get(lidx, false)) + continue; + m_already_visited.setx(lidx, true, false); + js.push_back(l); + } + for (unsigned i = 0; i < num; i++) { + literal l = s->m_intervals[i].m_justification; + unsigned lidx = l.index(); + m_already_visited[lidx] = false; + } + } + + interval_set * interval_set_manager::get_interval(interval_set const * s, unsigned idx) const { + SASSERT(idx < num_intervals(s)); + interval_buffer result; + push_back(m_am, result, s->m_intervals[idx]); + bool found_slack = !result[0].m_lower_inf || !result[0].m_upper_inf; + interval_set * new_set = mk_interval(m_allocator, result, !found_slack); + SASSERT(check_interval_set(m_am, result.size(), new_set->m_intervals)); + return new_set; + } + + void interval_set_manager::peek_in_complement(interval_set const * s, anum & w, bool randomize) { + SASSERT(!is_full(s)); + if (s == 0) { + if (randomize) { + int num = m_rand() % 2 == 0 ? 1 : -1; +#define MAX_RANDOM_DEN_K 4 + int den_k = (m_rand() % MAX_RANDOM_DEN_K); + int den = 1 << den_k; + scoped_mpq _w(m_am.qm()); + m_am.qm().set(_w, num, den); + m_am.set(w, _w); + return; + } + else { + m_am.set(w, 0); + return; + } + } + + unsigned n = 0; + + unsigned num = num_intervals(s); + if (!s->m_intervals[0].m_lower_inf) { + // lower is not -oo + n++; + m_am.int_lt(s->m_intervals[0].m_lower, w); + if (!randomize) + return; + } + if (!s->m_intervals[num-1].m_upper_inf) { + // upper is not oo + n++; + if (n == 1 || m_rand()%n == 0) + m_am.int_gt(s->m_intervals[num-1].m_upper, w); + if (!randomize) + return; + } + + // Try to find a gap that is not an unit. + for (unsigned i = 1; i < num; i++) { + if (m_am.lt(s->m_intervals[i-1].m_upper, s->m_intervals[i].m_lower)) { + n++; + if (n == 1 || m_rand()%n == 0) + m_am.select(s->m_intervals[i-1].m_upper, s->m_intervals[i].m_lower, w); + if (!randomize) + return; + } + } + + if (n > 0) + return; + + // Try to find a rational + unsigned irrational_i = UINT_MAX; + for (unsigned i = 1; i < num; i++) { + if (s->m_intervals[i-1].m_upper_open && s->m_intervals[i].m_lower_open) { + SASSERT(m_am.eq(s->m_intervals[i-1].m_upper, s->m_intervals[i].m_lower)); // otherwise we would have found it in the previous step + if (m_am.is_rational(s->m_intervals[i-1].m_upper)) { + m_am.set(w, s->m_intervals[i-1].m_upper); + return; + } + if (irrational_i == UINT_MAX) + irrational_i = i-1; + } + } + SASSERT(irrational_i != UINT_MAX); + // Last option: peek irrational witness :-( + SASSERT(s->m_intervals[irrational_i].m_upper_open && s->m_intervals[irrational_i+1].m_lower_open); + m_am.set(w, s->m_intervals[irrational_i].m_upper); + } + + void interval_set_manager::display(std::ostream & out, interval_set const * s) const { + if (s == 0) { + out << "{}"; + return; + } + out << "{"; + for (unsigned i = 0; i < s->m_num_intervals; i++) { + if (i > 0) + out << ", "; + nlsat::display(out, m_am, s->m_intervals[i]); + } + out << "}"; + if (s->m_full) + out << "*"; + } + +}; diff --git a/lib/nlsat_interval_set.h b/lib/nlsat_interval_set.h new file mode 100644 index 000000000..70822f405 --- /dev/null +++ b/lib/nlsat_interval_set.h @@ -0,0 +1,123 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + nlsat_interval_set.h + +Abstract: + + Sets of disjoint infeasible intervals. + +Author: + + Leonardo de Moura (leonardo) 2012-01-11. + +Revision History: + +--*/ +#ifndef _NLSAT_INTERVAL_SET_H_ +#define _NLSAT_INTERVAL_SET_H_ + +#include"nlsat_types.h" + +namespace nlsat { + + class interval_set; + + class interval_set_manager { + anum_manager & m_am; + small_object_allocator & m_allocator; + svector m_already_visited; + random_gen m_rand; + void del(interval_set * s); + public: + interval_set_manager(anum_manager & m, small_object_allocator & a); + ~interval_set_manager(); + + void set_seed(unsigned s) { m_rand.set_seed(s); } + + /** + \brief Return the empty set. + */ + interval_set * mk_empty() { return 0; } + + /** + \brief Return a set of composed of a single interval. + */ + interval_set * mk(bool lower_open, bool lower_inf, anum const & lower, + bool upper_open, bool upper_inf, anum const & upper, + literal justification); + + /** + \brief Return the union of two sets. + */ + interval_set * mk_union(interval_set const * s1, interval_set const * s2); + + /** + \brief Reference counting + */ + void dec_ref(interval_set * s); + void inc_ref(interval_set * s); + + /** + \brief Return true if s is the empty set. + */ + bool is_empty(interval_set const * s) { + return s == 0; + } + + /** + \brief Return true if the set contains all real numbers. + */ + bool is_full(interval_set const * s); + + /** + `\brief Return true if s1 is a subset of s2. + */ + bool subset(interval_set const * s1, interval_set const * s2); + + /** + \brief Return true if s1 and s2 cover the same subset of R. + The justifications are ignored + */ + bool set_eq(interval_set const * s1, interval_set const * s2); + + /** + \brief Return true if s1 and s2 are the same (the justifications are taking into account). + */ + bool eq(interval_set const * s1, interval_set const * s2); + + /** + \brief Return a set of literals that justify s. + */ + void get_justifications(interval_set const * s, literal_vector & js); + + void display(std::ostream & out, interval_set const * s) const; + + unsigned num_intervals(interval_set const * s) const; + + /** + \brief (For debugging purposes) Return one of the intervals in s. + \pre idx < num_intervals() + */ + interval_set * get_interval(interval_set const * s, unsigned idx) const; + + /** + \brief Select a witness w in the complement of s. + + \pre !is_full(s) + */ + void peek_in_complement(interval_set const * s, anum & w, bool randomize); + }; + + typedef obj_ref interval_set_ref; + + inline std::ostream & operator<<(std::ostream & out, interval_set_ref const & s) { + s.m().display(out, s); + return out; + } + +}; + +#endif diff --git a/lib/nlsat_justification.h b/lib/nlsat_justification.h new file mode 100644 index 000000000..4422f48d2 --- /dev/null +++ b/lib/nlsat_justification.h @@ -0,0 +1,90 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + nlsat_justification.h + +Abstract: + + An explanation for a (Boolean) assignment in the + nlsat procedure + +Author: + + Leonardo de Moura (leonardo) 2012-01-10. + +Revision History: + +--*/ +#ifndef _NLSAT_JUSTIFICATION_H_ +#define _NLSAT_JUSTIFICATION_H_ + +#include"nlsat_types.h" +#include"tptr.h" + +namespace nlsat { + + // There are two kinds of justifications in nlsat: + // + // - clause + // + // - lazy_justification: it is a set of arithmetic literals s.t. + // the maximal variable in each literal is the same. + // The set is inconsistent in the current model. + // Thus, our nonlinear procedure may be applied to it + // to produce a clause. + // + + class lazy_justification { + unsigned m_num_literals; + literal m_literals[0]; + public: + static unsigned get_obj_size(unsigned num) { return sizeof(lazy_justification) + sizeof(literal)*num; } + lazy_justification(unsigned num, literal const * lits): + m_num_literals(num) { + memcpy(m_literals, lits, sizeof(literal)*num); + } + unsigned size() const { return m_num_literals; } + literal operator[](unsigned i) const { SASSERT(i < size()); return m_literals[i]; } + literal const * lits() const { return m_literals; } + }; + + class justification { + void * m_data; + public: + enum kind { NULL_JST = 0, DECISION, CLAUSE, LAZY }; + justification():m_data(TAG(void *, static_cast(0), NULL_JST)) { SASSERT(is_null()); } + justification(bool):m_data(TAG(void *, static_cast(0), DECISION)) { SASSERT(is_decision()); } + justification(clause * c):m_data(TAG(void *, c, CLAUSE)) { SASSERT(is_clause()); } + justification(lazy_justification * j):m_data(TAG(void *, j, LAZY)) { SASSERT(is_lazy()); } + kind get_kind() const { return static_cast(GET_TAG(m_data)); } + bool is_null() const { return get_kind() == NULL_JST; } + bool is_decision() const { return get_kind() == DECISION; } + bool is_clause() const { return get_kind() == CLAUSE; } + bool is_lazy() const { return get_kind() == LAZY; } + clause * get_clause() const { return UNTAG(clause*, m_data); } + lazy_justification * get_lazy() const { return UNTAG(lazy_justification*, m_data); } + bool operator==(justification other) const { return m_data == other.m_data; } + bool operator!=(justification other) const { return m_data != other.m_data; } + }; + + const justification null_justification; + const justification decided_justification(true); + + inline justification mk_clause_jst(clause const * c) { return justification(const_cast(c)); } + inline justification mk_lazy_jst(small_object_allocator & a, unsigned num, literal const * lits) { + void * mem = a.allocate(lazy_justification::get_obj_size(num)); + return justification(new (mem) lazy_justification(num, lits)); + } + + inline void del_jst(small_object_allocator & a, justification jst) { + if (jst.is_lazy()) { + lazy_justification * ptr = jst.get_lazy(); + unsigned obj_sz = lazy_justification::get_obj_size(ptr->size()); + a.deallocate(obj_sz, ptr); + } + } +}; + +#endif diff --git a/lib/nlsat_scoped_literal_vector.h b/lib/nlsat_scoped_literal_vector.h new file mode 100644 index 000000000..e76110d5d --- /dev/null +++ b/lib/nlsat_scoped_literal_vector.h @@ -0,0 +1,89 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + nlsat_scoped_literal_vector.h + +Abstract: + + Scoped vector for nlsat literals. + Just to be "cancel" safe. + +Author: + + Leonardo de Moura (leonardo) 2012-01-13. + +Revision History: + +--*/ +#ifndef _NLSAT_SCOPED_LITERAL_VECTOR_H_ +#define _NLSAT_SCOPED_LITERAL_VECTOR_H_ + +#include"nlsat_solver.h" + +namespace nlsat { + + class scoped_literal_vector { + solver & m_solver; + literal_vector m_lits; + public: + scoped_literal_vector(solver & s):m_solver(s) {} + ~scoped_literal_vector() { reset(); } + unsigned size() const { return m_lits.size(); } + bool empty() const { return m_lits.empty(); } + literal operator[](unsigned i) const { return m_lits[i]; } + void reset() { + unsigned sz = m_lits.size(); + for (unsigned i = 0; i < sz; i++) { + m_solver.dec_ref(m_lits[i]); + } + m_lits.reset(); + } + void push_back(literal l) { + m_solver.inc_ref(l); + m_lits.push_back(l); + } + void set(unsigned i, literal l) { + m_solver.inc_ref(l); + m_solver.dec_ref(m_lits[i]); + m_lits[i] = l; + } + literal const * c_ptr() const { return m_lits.c_ptr(); } + void shrink(unsigned new_sz) { + SASSERT(new_sz <= m_lits.size()); + unsigned sz = m_lits.size(); + if (new_sz == sz) + return; + for (unsigned i = new_sz; i < sz; i++) { + m_solver.dec_ref(m_lits[i]); + } + m_lits.shrink(new_sz); + } + void append(unsigned sz, literal const * ls) { + for (unsigned i = 0; i < sz; i++) + push_back(ls[i]); + } + }; + + + class scoped_literal { + solver & m_solver; + literal m_lit; + public: + scoped_literal(solver & s):m_solver(s), m_lit(null_literal) {} + ~scoped_literal() { m_solver.dec_ref(m_lit); } + scoped_literal & operator=(literal l) { + m_solver.inc_ref(l); + m_solver.dec_ref(m_lit); + m_lit = l; + return *this; + } + scoped_literal & operator=(scoped_literal const & l) { return operator=(l.m_lit); } + operator literal&() { return m_lit; } + operator literal const &() const { return m_lit; } + void neg() { m_lit.neg(); } + }; +}; + +#endif diff --git a/lib/nlsat_solver.cpp b/lib/nlsat_solver.cpp new file mode 100644 index 000000000..79ae334e3 --- /dev/null +++ b/lib/nlsat_solver.cpp @@ -0,0 +1,2683 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + nlsat_solver.cpp + +Abstract: + + Nonlinear arithmetic satisfiability procedure. The procedure is + complete for nonlinear real arithmetic, but it also has limited + support for integers. + +Author: + + Leonardo de Moura (leonardo) 2012-01-02. + +Revision History: + +--*/ +#include"nlsat_solver.h" +#include"nlsat_clause.h" +#include"nlsat_assignment.h" +#include"nlsat_justification.h" +#include"nlsat_evaluator.h" +#include"nlsat_explain.h" +#include"algebraic_numbers.h" +#include"tactic.h" +#include"chashtable.h" +#include"id_gen.h" +#include"dependency.h" +#include"polynomial_cache.h" +#include"permutation.h" + +#define NLSAT_EXTRA_VERBOSE + +#ifdef NLSAT_EXTRA_VERBOSE +#define NLSAT_VERBOSE(CODE) IF_VERBOSE(10, CODE) +#else +#define NLSAT_VERBOSE(CODE) ((void)0) +#endif + +namespace nlsat { + + typedef chashtable ineq_atom_table; + typedef chashtable root_atom_table; + + // for apply_permutation procedure + void swap(clause * & c1, clause * & c2) { + std::swap(c1, c2); + } + + struct solver::imp { + struct dconfig { + typedef imp value_manager; + typedef small_object_allocator allocator; + typedef void * value; + static const bool ref_count = false; + }; + typedef dependency_manager assumption_manager; + typedef assumption_manager::dependency * _assumption_set; + typedef obj_ref assumption_set_ref; + + + typedef polynomial::cache cache; + typedef ptr_vector interval_set_vector; + + solver & m_solver; + small_object_allocator m_allocator; + unsynch_mpq_manager m_qm; + pmanager m_pm; + cache m_cache; + anum_manager m_am; + assumption_manager m_asm; + assignment m_assignment; // partial interpretation + evaluator m_evaluator; + interval_set_manager & m_ism; + ineq_atom_table m_ineq_atoms; + root_atom_table m_root_atoms; + + id_gen m_cid_gen; + clause_vector m_clauses; // set of clauses + clause_vector m_learned; // set of learned clauses + + unsigned m_num_bool_vars; + atom_vector m_atoms; // bool_var -> atom + svector m_bvalues; // boolean assigment + unsigned_vector m_levels; // bool_var -> level + svector m_justifications; + vector m_bwatches; // bool_var (that are not attached to atoms) -> clauses where it is maximal + svector m_dead; // mark dead boolean variables + id_gen m_bid_gen; + + svector m_is_int; // m_is_int[x] is true if variable is integer + vector m_watches; // var -> clauses where variable is maximal + interval_set_vector m_infeasible; // var -> to a set of interval where the variable cannot be assigned to. + atom_vector m_var2eq; // var -> to asserted equality + var_vector m_perm; // var -> var permutation of the variables + var_vector m_inv_perm; + // m_perm: internal -> external + // m_inv_perm: external -> internal + struct perm_display_var_proc : public display_var_proc { + var_vector & m_perm; + display_var_proc m_default_display_var; + display_var_proc const * m_proc; // display external var ids + perm_display_var_proc(var_vector & perm): + m_perm(perm), + m_proc(0) { + } + virtual void operator()(std::ostream & out, var x) const { + if (m_proc == 0) + m_default_display_var(out, x); + else + (*m_proc)(out, m_perm[x]); + } + }; + perm_display_var_proc m_display_var; + + explain m_explain; + + bool_var m_bk; // current Boolean variable we are processing + var m_xk; // current arith variable we are processing + + unsigned m_scope_lvl; + + struct trail { + enum kind { BVAR_ASSIGNMENT, INFEASIBLE_UPDT, NEW_LEVEL, NEW_STAGE, UPDT_EQ }; + kind m_kind; + union { + bool_var m_b; + interval_set * m_old_set; + atom * m_old_eq; + }; + trail(bool_var b):m_kind(BVAR_ASSIGNMENT), m_b(b) {} + trail(interval_set * old_set):m_kind(INFEASIBLE_UPDT), m_old_set(old_set) {} + trail(bool stage):m_kind(stage ? NEW_STAGE : NEW_LEVEL) {} + trail(atom * a):m_kind(UPDT_EQ), m_old_eq(a) {} + }; + svector m_trail; + + anum m_zero; + bool m_cancel; + + // configuration + unsigned long long m_max_memory; + unsigned m_lazy; // how lazy the solver is: 0 - satisfy all learned clauses, 1 - process only unit and empty learned clauses, 2 - use only conflict clauses for resolving conflicts + bool m_simplify_cores; + bool m_reorder; + bool m_randomize; + bool m_random_order; + unsigned m_random_seed; + unsigned m_max_conflicts; + + // statistics + unsigned m_conflicts; + unsigned m_propagations; + unsigned m_decisions; + unsigned m_stages; + unsigned m_irrational_assignments; // number of irrational witnesses + + imp(solver & s, params_ref const & p): + m_solver(s), + m_allocator("nlsat"), + m_pm(m_qm, &m_allocator), + m_cache(m_pm), + m_am(m_qm, p, &m_allocator), + m_asm(*this, m_allocator), + m_assignment(m_am), + m_evaluator(m_assignment, m_pm, m_allocator), + m_ism(m_evaluator.ism()), + m_num_bool_vars(0), + m_display_var(m_perm), + m_explain(s, m_assignment, m_cache, m_atoms, m_var2eq, m_evaluator), + m_scope_lvl(0), + m_lemma(s), + m_lazy_clause(s), + m_lemma_assumptions(m_asm) { + updt_params(p); + reset_statistics(); + m_cancel = false; + mk_true_bvar(); + } + + ~imp() { + m_explain.reset(); + m_lemma.reset(); + m_lazy_clause.reset(); + undo_until_size(0); + del_clauses(); + del_unref_atoms(); + } + + void mk_true_bvar() { + bool_var b = mk_bool_var(); + SASSERT(b == true_bool_var); + literal true_lit(b, false); + mk_clause(1, &true_lit, false, 0); + } + + void updt_params(params_ref const & p) { + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + m_lazy = p.get_uint(":lazy", 0); + m_simplify_cores = p.get_bool(":simplify-conflicts", true); + bool min_cores = p.get_bool(":minimize-conflicts", false); + m_reorder = p.get_bool(":reorder", true); + m_randomize = p.get_bool(":randomize", true); + m_max_conflicts = p.get_uint(":max-conflicts", UINT_MAX); + m_random_order = p.get_bool(":shuffle-vars", false); + m_random_seed = p.get_uint(":seed", 0); + m_ism.set_seed(m_random_seed); + m_explain.set_simplify_cores(m_simplify_cores); + m_explain.set_minimize_cores(min_cores); + m_am.updt_params(p); + } + + void set_cancel(bool f) { + m_pm.set_cancel(f); + m_am.set_cancel(f); + m_cancel = f; + } + + void checkpoint() { + if (m_cancel) throw solver_exception(TACTIC_CANCELED_MSG); + if (memory::get_allocation_size() > m_max_memory) throw solver_exception(TACTIC_MAX_MEMORY_MSG); + } + + // ----------------------- + // + // Basic + // + // ----------------------- + + unsigned num_bool_vars() const { + return m_num_bool_vars; + } + + unsigned num_vars() const { + return m_is_int.size(); + } + + bool is_int(var x) const { + return m_is_int[x]; + } + + void inc_ref(assumption) {} + + void dec_ref(assumption) {} + + void inc_ref(_assumption_set a) { + if (a != 0) m_asm.inc_ref(a); + } + + void dec_ref(_assumption_set a) { + if (a != 0) m_asm.dec_ref(a); + } + + void inc_ref(bool_var b) { + if (b == null_bool_var) + return; + if (m_atoms[b] == 0) + return; + m_atoms[b]->inc_ref(); + } + + void inc_ref(literal l) { + inc_ref(l.var()); + } + + void dec_ref(bool_var b) { + if (b == null_bool_var) + return; + atom * a = m_atoms[b]; + if (a == 0) + return; + SASSERT(a->ref_count() > 0); + a->dec_ref(); + if (a->ref_count() == 0) + del(a); + } + + void dec_ref(literal l) { + dec_ref(l.var()); + } + + bool is_arith_atom(bool_var b) const { return m_atoms[b] != 0; } + + bool is_arith_literal(literal l) const { return is_arith_atom(l.var()); } + + var max_var(poly const * p) const { + return m_pm.max_var(p); + } + + var max_var(bool_var b) const { + if (!is_arith_atom(b)) + return null_var; + else + return m_atoms[b]->max_var(); + } + + var max_var(literal l) const { + return max_var(l.var()); + } + + /** + \brief Return the maximum variable occurring in cls. + */ + var max_var(unsigned sz, literal const * cls) const { + var x = null_var; + for (unsigned i = 0; i < sz; i++) { + literal l = cls[i]; + if (is_arith_literal(l)) { + var y = max_var(l); + if (x == null_var || y > x) + x = y; + } + } + return x; + } + + var max_var(clause const & cls) const { + return max_var(cls.size(), cls.c_ptr()); + } + + /** + \brief Return the maximum Boolean variable occurring in cls. + */ + bool_var max_bvar(clause const & cls) const { + bool_var b = null_bool_var; + unsigned sz = cls.size(); + for (unsigned i = 0; i < sz; i++) { + literal l = cls[i]; + if (b == null_bool_var || l.var() > b) + b = l.var(); + } + return b; + } + + /** + \brief Return the degree of the maximal variable of the given atom + */ + unsigned degree(atom const * a) const { + if (a->is_ineq_atom()) { + unsigned max = 0; + unsigned sz = to_ineq_atom(a)->size(); + var x = a->max_var(); + for (unsigned i = 0; i < sz; i++) { + unsigned d = m_pm.degree(to_ineq_atom(a)->p(i), x); + if (d > max) + max = d; + } + return max; + } + else { + return m_pm.degree(to_root_atom(a)->p(), a->max_var()); + } + } + + /** + \brief Return the degree of the maximal variable in c + */ + unsigned degree(clause const & c) const { + var x = max_var(c); + if (x == null_var) + return 0; + unsigned max = 0; + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) { + literal l = c[i]; + atom const * a = m_atoms[l.var()]; + if (a == 0) + continue; + unsigned d = degree(a); + if (d > max) + max = d; + } + return max; + } + + // ----------------------- + // + // Variable, Atoms, Clauses & Assumption creation + // + // ----------------------- + + bool_var mk_bool_var_core() { + bool_var b = m_bid_gen.mk(); + m_num_bool_vars++; + m_atoms .setx(b, 0, 0); + m_bvalues .setx(b, l_undef, l_undef); + m_levels .setx(b, UINT_MAX, UINT_MAX); + m_justifications.setx(b, null_justification, null_justification); + m_bwatches .setx(b, clause_vector(), clause_vector()); + m_dead .setx(b, false, true); + return b; + } + + bool_var mk_bool_var() { + return mk_bool_var_core(); + } + + var mk_var(bool is_int) { + var x = m_pm.mk_var(); + SASSERT(x == num_vars()); + m_is_int. push_back(is_int); + m_watches. push_back(clause_vector()); + m_infeasible.push_back(0); + m_var2eq. push_back(0); + m_perm. push_back(x); + m_inv_perm. push_back(x); + SASSERT(m_is_int.size() == m_watches.size()); + SASSERT(m_is_int.size() == m_infeasible.size()); + SASSERT(m_is_int.size() == m_var2eq.size()); + SASSERT(m_is_int.size() == m_perm.size()); + SASSERT(m_is_int.size() == m_inv_perm.size()); + return x; + } + + void deallocate(ineq_atom * a) { + unsigned obj_sz = ineq_atom::get_obj_size(a->size()); + a->~ineq_atom(); + m_allocator.deallocate(obj_sz, a); + } + + void deallocate(root_atom * a) { + a->~root_atom(); + m_allocator.deallocate(sizeof(root_atom), a); + } + + void del(bool_var b) { + SASSERT(m_bwatches[b].empty()); + SASSERT(m_bvalues[b] == l_undef); + m_num_bool_vars--; + m_dead[b] = true; + m_atoms[b] = 0; + m_bid_gen.recycle(b); + } + + void del(ineq_atom * a) { + SASSERT(a->ref_count() == 0); + m_ineq_atoms.erase(a); + del(a->bvar()); + unsigned sz = a->size(); + for (unsigned i = 0; i < sz; i++) + m_pm.dec_ref(a->p(i)); + deallocate(a); + } + + void del(root_atom * a) { + SASSERT(a->ref_count() == 0); + m_root_atoms.erase(a); + del(a->bvar()); + m_pm.dec_ref(a->p()); + deallocate(a); + } + + void del(atom * a) { + if (a == 0) + return ; + if (a->is_ineq_atom()) + del(to_ineq_atom(a)); + else + del(to_root_atom(a)); + } + + // Delete atoms with ref_count == 0 + void del_unref_atoms() { + unsigned sz = m_atoms.size(); + for (unsigned i = 0; i < sz; i++) { + del(m_atoms[i]); + } + } + + bool_var mk_ineq_atom(atom::kind k, unsigned sz, poly * const * ps, bool const * is_even) { + SASSERT(sz >= 1); + SASSERT(k == atom::LT || k == atom::GT || k == atom::EQ); + int sign = 1; + polynomial_ref p(m_pm); + ptr_buffer uniq_ps; + var max = null_var; + for (unsigned i = 0; i < sz; i++) { + p = m_pm.flip_sign_if_lm_neg(ps[i]); + if (p.get() != ps[i]) + sign = -sign; + var curr_max = max_var(p.get()); + if (curr_max > max || max == null_var) + max = curr_max; + uniq_ps.push_back(m_cache.mk_unique(p)); + TRACE("nlsat_table_bug", tout << "p: " << p << ", uniq: " << uniq_ps.back() << "\n";); + } + void * mem = m_allocator.allocate(ineq_atom::get_obj_size(sz)); + if (sign < 0) + k = atom::flip(k); + ineq_atom * new_atom = new (mem) ineq_atom(k, sz, uniq_ps.c_ptr(), is_even, max); + TRACE("nlsat_table_bug", ineq_atom::hash_proc h; + tout << "mk_ineq_atom hash: " << h(new_atom) << "\n"; display(tout, *new_atom, m_display_var); tout << "\n";); + ineq_atom * old_atom = m_ineq_atoms.insert_if_not_there(new_atom); + SASSERT(old_atom->max_var() == max); + if (old_atom != new_atom) { + deallocate(new_atom); + return old_atom->bvar(); + } + bool_var b = mk_bool_var_core(); + m_atoms[b] = new_atom; + new_atom->m_bool_var = b; + for (unsigned i = 0; i < sz; i++) { + m_pm.inc_ref(new_atom->p(i)); + } + return b; + } + + literal mk_ineq_literal(atom::kind k, unsigned sz, poly * const * ps, bool const * is_even) { + SASSERT(k == atom::LT || k == atom::GT || k == atom::EQ); + if (sz == 0) { + switch (k) { + case atom::LT: return false_literal; // 1 < 0 + case atom::EQ: return false_literal; // 1 == 0 + case atom::GT: return true_literal; // 1 > 0 + default: + UNREACHABLE(); + return null_literal; + } + } + else { + return literal(mk_ineq_atom(k, sz, ps, is_even), false); + } + } + + bool_var mk_root_atom(atom::kind k, var x, unsigned i, poly * p) { + SASSERT(i > 0); + SASSERT(x >= max_var(p)); + SASSERT(k == atom::ROOT_LT || k == atom::ROOT_GT || k == atom::ROOT_EQ || k == atom::ROOT_LE || k == atom::ROOT_GE); + polynomial_ref p1(m_pm); + p1 = m_pm.flip_sign_if_lm_neg(p); // flipping the sign of the polynomial will not change its roots. + poly * uniq_p = m_cache.mk_unique(p1); + void * mem = m_allocator.allocate(sizeof(root_atom)); + root_atom * new_atom = new (mem) root_atom(k, x, i, uniq_p); + root_atom * old_atom = m_root_atoms.insert_if_not_there(new_atom); + SASSERT(old_atom->max_var() == x); + if (old_atom != new_atom) { + deallocate(new_atom); + return old_atom->bvar(); + } + bool_var b = mk_bool_var_core(); + m_atoms[b] = new_atom; + new_atom->m_bool_var = b; + m_pm.inc_ref(new_atom->p()); + return b; + } + + void attach_clause(clause & cls) { + var x = max_var(cls); + if (x != null_var) { + m_watches[x].push_back(&cls); + } + else { + bool_var b = max_bvar(cls); + m_bwatches[b].push_back(&cls); + } + } + + void deattach_clause(clause & cls) { + var x = max_var(cls); + if (x != null_var) { + m_watches[x].erase(&cls); + } + else { + bool_var b = max_bvar(cls); + m_bwatches[b].erase(&cls); + } + } + + void deallocate(clause * cls) { + size_t obj_sz = clause::get_obj_size(cls->size()); + cls->~clause(); + m_allocator.deallocate(obj_sz, cls); + } + + void del_clause(clause * cls) { + deattach_clause(*cls); + m_cid_gen.recycle(cls->id()); + unsigned sz = cls->size(); + for (unsigned i = 0; i < sz; i++) + dec_ref((*cls)[i]); + _assumption_set a = static_cast<_assumption_set>(cls->assumptions()); + dec_ref(a); + deallocate(cls); + } + + void del_clauses(ptr_vector & cs) { + unsigned sz = cs.size(); + for (unsigned i = 0; i < sz; i++) + del_clause(cs[i]); + } + + void del_clauses() { + del_clauses(m_clauses); + del_clauses(m_learned); + } + + // We use a simple heuristic to sort literals + // - bool literals < arith literals + // - sort literals based on max_var + // - sort literal with the same max_var using degree + // break ties using the fact that ineqs are usually cheaper to process than eqs. + struct lit_lt { + imp & m; + lit_lt(imp & _m):m(_m) {} + + bool operator()(literal l1, literal l2) const { + atom * a1 = m.m_atoms[l1.var()]; + atom * a2 = m.m_atoms[l2.var()]; + if (a1 == 0 && a2 == 0) + return l1.index() < l2.index(); + if (a1 == 0) + return true; + if (a2 == 0) + return false; + var x1 = a1->max_var(); + var x2 = a2->max_var(); + if (x1 < x2) + return true; + if (x1 > x2) + return false; + SASSERT(x1 == x2); + unsigned d1 = m.degree(a1); + unsigned d2 = m.degree(a2); + if (d1 < d2) + return true; + if (d1 > d2) + return false; + if (!a1->is_eq() && a2->is_eq()) + return true; + if (a1->is_eq() && !a2->is_eq()) + return false; + return l1.index() < l2.index(); + } + }; + + clause * mk_clause(unsigned num_lits, literal const * lits, bool learned, _assumption_set a) { + SASSERT(num_lits > 0); + unsigned cid = m_cid_gen.mk(); + void * mem = m_allocator.allocate(clause::get_obj_size(num_lits)); + clause * cls = new (mem) clause(cid, num_lits, lits, learned, a); + for (unsigned i = 0; i < num_lits; i++) + inc_ref(lits[i]); + inc_ref(a); + TRACE("nlsat_sort", tout << "mk_clause:\n"; display(tout, *cls); tout << "\n";); + std::sort(cls->begin(), cls->end(), lit_lt(*this)); + TRACE("nlsat_sort", tout << "after sort:\n"; display(tout, *cls); tout << "\n";); + if (learned) + m_learned.push_back(cls); + else + m_clauses.push_back(cls); + attach_clause(*cls); + return cls; + } + + void mk_clause(unsigned num_lits, literal const * lits, assumption a) { + SASSERT(num_lits > 0); + _assumption_set as = 0; + if (a != 0) + as = m_asm.mk_leaf(a); + mk_clause(num_lits, lits, false, as); + } + + // ----------------------- + // + // Search + // + // ----------------------- + + void save_assign_trail(bool_var b) { + m_trail.push_back(trail(b)); + } + + void save_set_updt_trail(interval_set * old_set) { + m_trail.push_back(trail(old_set)); + } + + void save_updt_eq_trail(atom * old_eq) { + m_trail.push_back(trail(old_eq)); + } + + void save_new_stage_trail() { + m_trail.push_back(trail(true)); + } + + void save_new_level_trail() { + m_trail.push_back(trail(false)); + } + + void undo_bvar_assignment(bool_var b) { + m_bvalues[b] = l_undef; + m_levels[b] = UINT_MAX; + del_jst(m_allocator, m_justifications[b]); + m_justifications[b] = null_justification; + if (m_atoms[b] == 0 && b < m_bk) + m_bk = b; + } + + void undo_set_updt(interval_set * old_set) { + SASSERT(m_xk != null_var); + var x = m_xk; + m_ism.dec_ref(m_infeasible[x]); + m_infeasible[x] = old_set; + } + + void undo_new_stage() { + SASSERT(m_xk != null_var); + if (m_xk == 0) { + m_xk = null_var; + } + else { + m_xk--; + m_assignment.reset(m_xk); + } + } + + void undo_new_level() { + SASSERT(m_scope_lvl > 0); + m_scope_lvl--; + m_evaluator.pop(1); + } + + void undo_updt_eq(atom * a) { + SASSERT(m_xk != null_var); + m_var2eq[m_xk] = a; + } + + template + void undo_until(Predicate const & pred) { + while (pred()) { + trail & t = m_trail.back(); + switch (t.m_kind) { + case trail::BVAR_ASSIGNMENT: + undo_bvar_assignment(t.m_b); + break; + case trail::INFEASIBLE_UPDT: + undo_set_updt(t.m_old_set); + break; + case trail::NEW_STAGE: + undo_new_stage(); + break; + case trail::NEW_LEVEL: + undo_new_level(); + break; + case trail::UPDT_EQ: + undo_updt_eq(t.m_old_eq); + break; + default: + break; + } + m_trail.pop_back(); + } + } + + struct size_pred { + svector & m_trail; + unsigned m_old_size; + size_pred(svector & trail, unsigned old_size):m_trail(trail), m_old_size(old_size) {} + bool operator()() const { return m_trail.size() > m_old_size; } + }; + + // Keep undoing until trail has the given size + void undo_until_size(unsigned old_size) { + SASSERT(m_trail.size() >= old_size); + undo_until(size_pred(m_trail, old_size)); + } + + struct stage_pred { + var const & m_xk; + var m_target; + stage_pred(var const & xk, var target):m_xk(xk), m_target(target) {} + bool operator()() const { return m_xk != m_target; } + }; + + // Keep undoing until stage is new_xk + void undo_until_stage(var new_xk) { + undo_until(stage_pred(m_xk, new_xk)); + } + + struct level_pred { + unsigned const & m_scope_lvl; + unsigned m_new_lvl; + level_pred(unsigned const & scope_lvl, unsigned new_lvl):m_scope_lvl(scope_lvl), m_new_lvl(new_lvl) {} + bool operator()() const { return m_scope_lvl > m_new_lvl; } + }; + + // Keep undoing until level is new_lvl + void undo_until_level(unsigned new_lvl) { + undo_until(level_pred(m_scope_lvl, new_lvl)); + } + + struct unassigned_pred { + bool_var m_b; + svector const & m_bvalues; + unassigned_pred(svector const & bvalues, bool_var b): + m_b(b), + m_bvalues(bvalues) {} + bool operator()() const { return m_bvalues[m_b] != l_undef; } + }; + + // Keep undoing until b is unassigned + void undo_until_unassigned(bool_var b) { + undo_until(unassigned_pred(m_bvalues, b)); + SASSERT(m_bvalues[b] == l_undef); + } + + /** + \brief Create a new scope level + */ + void new_level() { + m_evaluator.push(); + m_scope_lvl++; + save_new_level_trail(); + } + + /** + \brief Return the value of the given literal that was assigned by the search + engine. + */ + lbool assigned_value(literal l) const { + bool_var b = l.var(); + if (l.sign()) + return ~m_bvalues[b]; + else + return m_bvalues[b]; + } + + /** + \brief Assign literal using the given justification + */ + void assign(literal l, justification j) { + TRACE("nlsat", tout << "assigning literal:\n"; display(tout, l); + tout << "\njustification kind: " << j.get_kind() << "\n";); + SASSERT(assigned_value(l) == l_undef); + SASSERT(j != null_justification); + SASSERT(!j.is_null()); + if (j.is_decision()) + m_decisions++; + else + m_propagations++; + bool_var b = l.var(); + m_bvalues[b] = to_lbool(!l.sign()); + m_levels[b] = m_scope_lvl; + m_justifications[b] = j; + save_assign_trail(b); + updt_eq(b); + TRACE("nlsat_assign", tout << "b" << b << " -> " << m_bvalues[b] << " " << m_atoms[b] << "\n";); + } + + /** + \brief Create a "case-split" + */ + void decide(literal l) { + new_level(); + assign(l, decided_justification); + } + + /** + \brief Return the value of a literal as defined in Dejan and Leo's paper. + */ + lbool value(literal l) { + lbool val = assigned_value(l); + if (val != l_undef) + return val; + bool_var b = l.var(); + atom * a = m_atoms[b]; + if (a == 0) + return l_undef; + var max = a->max_var(); + if (!m_assignment.is_assigned(max)) + return l_undef; + TRACE("value_bug", tout << "value of: "; display(tout, l); tout << "\n"; tout << "xk: " << m_xk << ", a->max_var(): " << a->max_var() << "\n"; + display_assignment(tout); + display_bool_assignment(tout);); + return to_lbool(m_evaluator.eval(a, l.sign())); + } + + /** + \brief Return true if the given clause is already satisfied in the current partial interpretation. + */ + bool is_satisfied(clause const & cls) const { + unsigned sz = cls.size(); + for (unsigned i = 0; i < sz; i++) { + if (const_cast(this)->value(cls[i]) == l_true) + return true; + } + return false; + } + + /** + \brief Return true if the given clause is false in the current partial interpretation. + */ + bool is_inconsistent(unsigned sz, literal const * cls) { + for (unsigned i = 0; i < sz; i++) { + if (value(cls[i]) != l_false) { + TRACE("is_inconsistent", tout << "literal is not false:\n"; display(tout, cls[i]); tout << "\n";); + return false; + } + } + return true; + } + + /** + \brief Process a clauses that contains only Boolean literals. + */ + bool process_boolean_clause(clause const & cls) { + SASSERT(m_xk == null_var); + unsigned num_undef = 0; + unsigned first_undef = UINT_MAX; + unsigned sz = cls.size(); + for (unsigned i = 0; i < sz; i++) { + literal l = cls[i]; + SASSERT(m_atoms[l.var()] == 0); + SASSERT(value(l) != l_true); + if (value(l) == l_false) + continue; + SASSERT(value(l) == l_undef); + num_undef++; + if (first_undef == UINT_MAX) + first_undef = i; + } + if (num_undef == 0) + return false; + SASSERT(first_undef != UINT_MAX); + if (num_undef == 1) + assign(cls[first_undef], mk_clause_jst(&cls)); // unit clause + else + decide(cls[first_undef]); + return true; + } + + /** + \brief assign l to true, because l + (justification of) s is infeasible in RCF in the current interpretation. + */ + literal_vector core; + void R_propagate(literal l, interval_set const * s, bool include_l = true) { + m_ism.get_justifications(s, core); + if (include_l) + core.push_back(~l); + assign(l, mk_lazy_jst(m_allocator, core.size(), core.c_ptr())); + SASSERT(value(l) == l_true); + } + + /** + \brief m_infeasible[m_xk] <- m_infeasible[m_xk] Union s + */ + void updt_infeasible(interval_set const * s) { + SASSERT(m_xk != null_var); + interval_set * xk_set = m_infeasible[m_xk]; + save_set_updt_trail(xk_set); + interval_set_ref new_set(m_ism); + TRACE("nlsat_inf_set", tout << "updating infeasible set\n"; m_ism.display(tout, xk_set); tout << "\n"; m_ism.display(tout, s); tout << "\n";); + new_set = m_ism.mk_union(s, xk_set); + TRACE("nlsat_inf_set", tout << "new infeasible set:\n"; m_ism.display(tout, new_set); tout << "\n";); + SASSERT(!m_ism.is_full(new_set)); + m_ism.inc_ref(new_set); + m_infeasible[m_xk] = new_set; + } + + /** + \brief Update m_var2eq mapping. + */ + void updt_eq(bool_var b) { + if (!m_simplify_cores) + return; + if (m_bvalues[b] != l_true) + return; + atom * a = m_atoms[b]; + if (a == 0 || a->get_kind() != atom::EQ || to_ineq_atom(a)->size() > 1 || to_ineq_atom(a)->is_even(0)) + return; + var x = m_xk; + SASSERT(a->max_var() == x); + SASSERT(x != null_var); + if (m_var2eq[x] != 0 && degree(m_var2eq[x]) <= degree(a)) + return; // we only update m_var2eq if the new equality has smaller degree + TRACE("simplify_core", tout << "Saving equality for "; m_display_var(tout, x); tout << " (x" << x << ") "; + tout << "scope-lvl: " << scope_lvl() << "\n"; display(tout, literal(b, false)); tout << "\n";); + save_updt_eq_trail(m_var2eq[x]); + m_var2eq[x] = a; + } + + /** + \brief Process a clause that contains nonlinar arithmetic literals + + If satisfy_learned is true, then learned clauses are satisfied even if m_lazy > 0 + */ + bool process_arith_clause(clause const & cls, bool satisfy_learned) { + if (!satisfy_learned && m_lazy >= 2 && cls.is_learned()) + return true; // ignore lemmas in super lazy mode + SASSERT(m_xk == max_var(cls)); + unsigned num_undef = 0; // number of undefined literals + unsigned first_undef = UINT_MAX; // position of the first undefined literal + interval_set_ref first_undef_set(m_ism); // infeasible region of the first undefined literal + interval_set * xk_set = m_infeasible[m_xk]; // current set of infeasible interval for current variable + SASSERT(!m_ism.is_full(xk_set)); + unsigned sz = cls.size(); + for (unsigned i = 0; i < sz; i++) { + checkpoint(); + literal l = cls[i]; + if (value(l) == l_false) + continue; + SASSERT(value(l) == l_undef); + SASSERT(max_var(l) == m_xk); + bool_var b = l.var(); + atom * a = m_atoms[b]; + SASSERT(a != 0); + interval_set_ref curr_set(m_ism); + TRACE("nlsat_inf_set", tout << "xk: " << m_xk << ", max_var(l): " << max_var(l) << ", l: "; display(tout, l); tout << "\n";); + curr_set = m_evaluator.infeasible_intervals(a, l.sign()); + TRACE("nlsat_inf_set", tout << "infeasible set for literal: "; display(tout, l); tout << "\n"; m_ism.display(tout, curr_set); tout << "\n";); + if (m_ism.is_empty(curr_set)) { + TRACE("nlsat_inf_set", tout << "infeasible set is empty, found literal\n";); + R_propagate(l, 0); + SASSERT(is_satisfied(cls)); + return true; + } + if (m_ism.is_full(curr_set)) { + TRACE("nlsat_inf_set", tout << "infeasible set is R, skip literal\n";); + R_propagate(~l, 0); + continue; + } + if (m_ism.subset(curr_set, xk_set)) { + TRACE("nlsat_inf_set", tout << "infeasible set is a subset of current set, found literal\n";); + R_propagate(l, xk_set); + return true; + } + interval_set_ref tmp(m_ism); + tmp = m_ism.mk_union(curr_set, xk_set); + if (m_ism.is_full(tmp)) { + TRACE("nlsat_inf_set", tout << "infeasible set + current set = R, skip literal\n";); + R_propagate(~l, tmp, false); + continue; + } + num_undef++; + if (first_undef == UINT_MAX) { + first_undef = i; + first_undef_set = curr_set; + } + } + TRACE("nlsat_inf_set", tout << "num_undef: " << num_undef << "\n";); + if (num_undef == 0) + return false; + SASSERT(first_undef != UINT_MAX); + if (num_undef == 1) { + // unit clause + assign(cls[first_undef], mk_clause_jst(&cls)); + updt_infeasible(first_undef_set); + } + else if ( satisfy_learned || + !cls.is_learned() /* must always satisfy input clauses */ || + m_lazy == 0 /* if not in lazy mode, we also satiffy lemmas */) { + decide(cls[first_undef]); + updt_infeasible(first_undef_set); + } + else { + TRACE("nlsat_lazy", tout << "skipping clause, satisfy_learned: " << satisfy_learned << ", cls.is_learned(): " << cls.is_learned() + << ", lazy: " << m_lazy << "\n";); + } + return true; + } + + /** + \brief Try to satisfy the given clause. Return true if succeeded. + + If satisfy_learned is true, then (arithmetic) learned clauses are satisfied even if m_lazy > 0 + */ + bool process_clause(clause const & cls, bool satisfy_learned) { + TRACE("nlsat", tout << "processing clause:\n"; display(tout, cls); tout << "\n";); + if (is_satisfied(cls)) + return true; + if (m_xk == null_var) + return process_boolean_clause(cls); + else + return process_arith_clause(cls, satisfy_learned); + } + + /** + \brief Try to satisfy the given "set" of clauses. + Return 0, if the set was satisfied, or the violating clause otherwise + */ + clause * process_clauses(clause_vector const & cs) { + unsigned sz = cs.size(); + for (unsigned i = 0; i < sz; i++) { + clause * c = cs[i]; + if (!process_clause(*c, false)) + return c; + } + return 0; // succeeded + } + + /** + \brief Make sure m_bk is the first unassigned pure Boolean variable. + Set m_bk == null_bool_var if there is no unassigned pure Boolean variable. + */ + void peek_next_bool_var() { + while (m_bk < m_atoms.size()) { + if (!m_dead[m_bk] && m_atoms[m_bk] == 0 && m_bvalues[m_bk] == l_undef) { + return; + } + m_bk++; + } + m_bk = null_bool_var; + } + + /** + \brief Create a new stage. See Dejan and Leo's paper. + */ + void new_stage() { + m_stages++; + save_new_stage_trail(); + if (m_xk == null_var) + m_xk = 0; + else + m_xk++; + } + + /** + \brief Assign m_xk + */ + void select_witness() { + scoped_anum w(m_am); + SASSERT(!m_ism.is_full(m_infeasible[m_xk])); + m_ism.peek_in_complement(m_infeasible[m_xk], w, m_randomize); + TRACE("nlsat", + tout << "infeasible intervals: "; m_ism.display(tout, m_infeasible[m_xk]); tout << "\n"; + tout << "assigning "; m_display_var(tout, m_xk); tout << "(x" << m_xk << ") -> " << w << "\n";); + TRACE("nlsat_root", tout << "value as root object: "; m_am.display_root(tout, w); tout << "\n";); + if (!m_am.is_rational(w)) + m_irrational_assignments++; + m_assignment.set_core(m_xk, w); + } + + /** + \brief main procedure + */ + lbool search() { + TRACE("nlsat", tout << "starting search...\n"; display(tout); tout << "\nvar order:\n"; display_vars(tout);); + TRACE("nlsat_proof", tout << "ASSERTED\n"; display(tout);); + TRACE("nlsat_proof_sk", tout << "ASSERTED\n"; display_abst(tout);); + TRACE("nlsat_mathematica", display_mathematica(tout);); + m_bk = 0; + m_xk = null_var; + while (true) { + CASSERT("nlsat", check_satisfied()); + if (m_xk == null_var) { + peek_next_bool_var(); + if (m_bk == null_bool_var) + new_stage(); // move to arith vars + } + else { + new_stage(); // peek next arith var + } + TRACE("nlsat_bug", tout << "xk: x" << m_xk << " bk: b" << m_bk << "\n";); + if (m_bk == null_bool_var && m_xk >= num_vars()) { + TRACE("nlsat", tout << "found model\n"; display_assignment(tout); display_bool_assignment(tout);); + return l_true; // all variables were assigned, and all clauses were satisfied. + } + TRACE("nlsat", tout << "processing variable "; + if (m_xk != null_var) m_display_var(tout, m_xk); else tout << "boolean b" << m_bk; tout << "\n";); + while (true) { + checkpoint(); + clause * conflict_clause; + if (m_xk == null_var) + conflict_clause = process_clauses(m_bwatches[m_bk]); + else + conflict_clause = process_clauses(m_watches[m_xk]); + if (conflict_clause == 0) + break; + if (!resolve(*conflict_clause)) + return l_false; + if (m_conflicts >= m_max_conflicts) + return l_undef; + } + + if (m_xk == null_var) { + if (m_bvalues[m_bk] == l_undef) { + decide(literal(m_bk, true)); + m_bk++; + } + } + else { + select_witness(); + } + } + } + + lbool check() { + TRACE("nlsat_smt2", display_smt2(tout);); + TRACE("nlsat_fd", tout << "is_full_dimensional: " << is_full_dimensional() << "\n";); + m_xk = null_var; + m_explain.set_full_dimensional(is_full_dimensional()); + if (m_random_order) { + shuffle_vars(); + } + else if (m_reorder) { + heuristic_reorder(); + } + sort_watched_clauses(); + lbool r = search(); + CTRACE("nlsat_model", r == l_true, tout << "model before restore order\n"; display_assignment(tout); display_bool_assignment(tout);); + if (m_reorder) + restore_order(); + CTRACE("nlsat_model", r == l_true, tout << "model\n"; display_assignment(tout); display_bool_assignment(tout);); + return r; + } + + // ----------------------- + // + // Conflict Resolution + // + // ----------------------- + svector m_marks; // bool_var -> bool temp mark used during conflict resolution + unsigned m_num_marks; + scoped_literal_vector m_lemma; + scoped_literal_vector m_lazy_clause; + assumption_set_ref m_lemma_assumptions; // assumption tracking + + // Conflict resolution invariant: a marked literal is in m_lemma or on the trail stack. + + bool check_marks() { + for (unsigned i = 0; i < m_marks.size(); i++) { + SASSERT(m_marks[i] == 0); + } + return true; + } + + unsigned scope_lvl() const { return m_scope_lvl; } + + bool is_marked(bool_var b) const { return m_marks.get(b, 0) == 1; } + + void mark(bool_var b) { m_marks.setx(b, 1, 0); } + + void reset_mark(bool_var b) { m_marks[b] = 0; } + + void reset_marks() { + unsigned sz = m_lemma.size(); + for (unsigned i = 0; i < sz; i++) { + reset_mark(m_lemma[i].var()); + } + } + + void process_antecedent(literal antecedent) { + bool_var b = antecedent.var(); + TRACE("nlsat_resolve", tout << "resolving antecedent, l:\n"; display(tout, antecedent); tout << "\n";); + if (assigned_value(antecedent) == l_undef) { + // antecedent must be false in the current arith interpretation + SASSERT(value(antecedent) == l_false); + if (!is_marked(b)) { + SASSERT(is_arith_atom(b) && max_var(b) < m_xk); // must be in a previous stage + TRACE("nlsat_resolve", tout << "literal is unassigned, but it is false in arithmetic interpretation, adding it to lemma\n";); + mark(b); + m_lemma.push_back(antecedent); + } + return; + } + + unsigned b_lvl = m_levels[b]; + TRACE("nlsat_resolve", tout << "b_lvl: " << b_lvl << ", is_marked(b): " << is_marked(b) << ", m_num_marks: " << m_num_marks << "\n";); + if (!is_marked(b)) { + mark(b); + if (b_lvl == scope_lvl() /* same level */ && max_var(b) == m_xk /* same stage */) { + TRACE("nlsat_resolve", tout << "literal is in the same level and stage, increasing marks\n";); + m_num_marks++; + } + else { + TRACE("nlsat_resolve", tout << "previous level or stage, adding literal to lemma\n"; + tout << "max_var(b): " << max_var(b) << ", m_xk: " << m_xk << ", lvl: " << b_lvl << ", scope_lvl: " << scope_lvl() << "\n";); + m_lemma.push_back(antecedent); + } + } + } + + void resolve_clause(bool_var b, unsigned sz, literal const * c) { + TRACE("nlsat_proof", tout << "resolving "; if (b != null_bool_var) display_atom(tout, b); tout << "\n"; display(tout, sz, c); tout << "\n";); + TRACE("nlsat_proof_sk", tout << "resolving "; if (b != null_bool_var) tout << "b" << b; tout << "\n"; display_abst(tout, sz, c); tout << "\n";); + + for (unsigned i = 0; i < sz; i++) { + if (c[i].var() != b) + process_antecedent(c[i]); + } + } + + void resolve_clause(bool_var b, clause const & c) { + TRACE("nlsat_resolve", tout << "resolving clause for b: " << b << "\n";); + resolve_clause(b, c.size(), c.c_ptr()); + m_lemma_assumptions = m_asm.mk_join(static_cast<_assumption_set>(c.assumptions()), m_lemma_assumptions); + } + + void resolve_lazy_justification(bool_var b, lazy_justification const & jst) { + TRACE("nlsat_resolve", tout << "resolving lazy_justification for b: " << b << "\n";); + unsigned sz = jst.size(); + + // Dump lemma as Mathematica formula that must be true, + // if the current interpretation (really) makes the core in jst infeasible. + TRACE("nlsat_mathematica", + tout << "assignment lemma\n"; + literal_vector core; + for (unsigned i = 0; i < sz; i++) { + core.push_back(~jst[i]); + } + display_mathematica_lemma(tout, core.size(), core.c_ptr(), true);); + + m_lazy_clause.reset(); + m_explain(jst.size(), jst.lits(), m_lazy_clause); + for (unsigned i = 0; i < sz; i++) + m_lazy_clause.push_back(~jst[i]); + + // lazy clause is a valid clause + TRACE("nlsat_mathematica", display_mathematica_lemma(tout, m_lazy_clause.size(), m_lazy_clause.c_ptr());); + TRACE("nlsat_proof_sk", tout << "theory lemma\n"; display_abst(tout, m_lazy_clause.size(), m_lazy_clause.c_ptr()); tout << "\n";); + TRACE("nlsat_resolve", + tout << "m_xk: " << m_xk << ", "; m_display_var(tout, m_xk); tout << "\n"; + tout << "new valid clause:\n"; + display(tout, m_lazy_clause.size(), m_lazy_clause.c_ptr()); tout << "\n";); + + DEBUG_CODE({ + unsigned sz = m_lazy_clause.size(); + for (unsigned i = 0; i < sz; i++) { + literal l = m_lazy_clause[i]; + if (l.var() != b) { + SASSERT(value(l) == l_false); + } + else { + SASSERT(value(l) == l_true); + SASSERT(!l.sign() || m_bvalues[b] == l_false); + SASSERT(l.sign() || m_bvalues[b] == l_true); + } + } + }); + resolve_clause(b, m_lazy_clause.size(), m_lazy_clause.c_ptr()); + } + + /** + \brief Return true if all literals in ls are from previous stages. + */ + bool only_literals_from_previous_stages(unsigned num, literal const * ls) const { + for (unsigned i = 0; i < num; i++) { + if (max_var(ls[i]) == m_xk) + return false; + } + return true; + } + + /** + \brief Return the maximum scope level in ls. + + \pre This method assumes value(ls[i]) is l_false for i in [0, num) + */ + unsigned max_scope_lvl(unsigned num, literal const * ls) { + unsigned max = 0; + for (unsigned i = 0; i < num; i++) { + literal l = ls[i]; + bool_var b = l.var(); + SASSERT(value(ls[i]) == l_false); + if (assigned_value(l) == l_false) { + unsigned lvl = m_levels[b]; + if (lvl > max) + max = lvl; + } + else { + // l must be a literal from a previous stage that is false in the current intepretation + SASSERT(assigned_value(l) == l_undef); + SASSERT(max_var(b) != null_var); + SASSERT(m_xk != null_var); + SASSERT(max_var(b) < m_xk); + } + } + return max; + } + + /** + \brief Remove literals of the given lvl (that are in the current stage) from lemma. + + \pre This method assumes value(ls[i]) is l_false for i in [0, num) + */ + void remove_literals_from_lvl(scoped_literal_vector & lemma, unsigned lvl) { + TRACE("nlsat_resolve", tout << "removing literals from lvl: " << lvl << " and stage " << m_xk << "\n";); + unsigned sz = lemma.size(); + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + literal l = lemma[i]; + bool_var b = l.var(); + SASSERT(is_marked(b)); + SASSERT(value(lemma[i]) == l_false); + if (assigned_value(l) == l_false && m_levels[b] == lvl && max_var(b) == m_xk) { + m_num_marks++; + continue; + } + lemma.set(j, l); + j++; + } + lemma.shrink(j); + } + + /** + \brief Return true if it is a Boolean lemma. + */ + bool is_bool_lemma(unsigned sz, literal const * ls) const { + for (unsigned i = 0; i < sz; i++) { + if (m_atoms[ls[i].var()] != 0) + return false; + } + return true; + } + + + /** + Return the maximal decision level in lemma for literals in the first sz-1 positions that + are at the same stage. If all these literals are from previous stages, + we just backtrack the current level. + */ + unsigned find_new_level_arith_lemma(unsigned sz, literal const * lemma) { + SASSERT(!is_bool_lemma(sz, lemma)); + unsigned new_lvl = 0; + bool found_lvl = false; + for (unsigned i = 0; i < sz - 1; i++) { + literal l = lemma[i]; + if (max_var(l) == m_xk) { + bool_var b = l.var(); + if (!found_lvl) { + found_lvl = true; + new_lvl = m_levels[b]; + } + else { + if (m_levels[b] > new_lvl) + new_lvl = m_levels[b]; + } + } + } + SASSERT(!found_lvl || new_lvl < scope_lvl()); + if (!found_lvl) { + TRACE("nlsat_resolve", tout << "fail to find new lvl, using previous one\n";); + new_lvl = scope_lvl() - 1; + } + return new_lvl; + } + + /** + \brief Return true if the conflict was solved. + */ + bool resolve(clause const & conflict) { + clause const * conflict_clause = &conflict; + start: + SASSERT(check_marks()); + TRACE("nlsat_proof", tout << "STARTING RESOLUTION\n";); + TRACE("nlsat_proof_sk", tout << "STARTING RESOLUTION\n";); + m_conflicts++; + TRACE("nlsat", tout << "resolve, conflicting clause:\n"; display(tout, *conflict_clause); tout << "\n"; + tout << "xk: "; if (m_xk != null_var) m_display_var(tout, m_xk); else tout << ""; tout << "\n"; + tout << "scope_lvl: " << scope_lvl() << "\n"; + tout << "current assignment\n"; display_assignment(tout); display_bool_assignment(tout);); + + // static unsigned counter = 0; + // counter++; + // if (counter > 6) + // exit(0); + + m_num_marks = 0; + m_lemma.reset(); + m_lemma_assumptions = 0; + + resolve_clause(null_bool_var, *conflict_clause); + + unsigned top = m_trail.size(); + bool found_decision; + while (true) { + found_decision = false; + while (m_num_marks > 0) { + SASSERT(top > 0); + trail & t = m_trail[top-1]; + SASSERT(t.m_kind != trail::NEW_STAGE); // we only mark literals that are in the same stage + if (t.m_kind == trail::BVAR_ASSIGNMENT) { + bool_var b = t.m_b; + if (is_marked(b)) { + TRACE("nlsat_resolve", tout << "found marked bool_var: " << b << "\n"; display_atom(tout, b); tout << "\n";); + m_num_marks--; + reset_mark(b); + justification jst = m_justifications[b]; + switch (jst.get_kind()) { + case justification::CLAUSE: + resolve_clause(b, *(jst.get_clause())); + break; + case justification::LAZY: + resolve_lazy_justification(b, *(jst.get_lazy())); + break; + case justification::DECISION: + SASSERT(m_num_marks == 0); + found_decision = true; + TRACE("nlsat_resolve", tout << "found decision\n";); + m_lemma.push_back(literal(b, m_bvalues[b] == l_true)); + break; + default: + UNREACHABLE(); + break; + } + } + } + top--; + } + + // m_lemma is an implicating clause after backtracking current scope level. + if (found_decision) + break; + + // If lemma only contains literals from previous stages, then we can stop. + // We make progress by returning to a previous stage with additional information (new lemma) + // that forces us to select a new partial interpretation + if (only_literals_from_previous_stages(m_lemma.size(), m_lemma.c_ptr())) + break; + + // Conflict does not depend on the current decision, and it is still in the current stage. + // We should find + // - the maximal scope level in the lemma + // - remove literal assigned in the scope level from m_lemma + // - backtrack to this level + // - and continue conflict resolution from there + // - we must bump m_num_marks for literals removed from m_lemma + unsigned max_lvl = max_scope_lvl(m_lemma.size(), m_lemma.c_ptr()); + TRACE("nlsat_resolve", tout << "conflict does not depend on current decision, backtracking to level: " << max_lvl << "\n";); + SASSERT(max_lvl < scope_lvl()); + remove_literals_from_lvl(m_lemma, max_lvl); + undo_until_level(max_lvl); + top = m_trail.size(); + TRACE("nlsat_resolve", tout << "scope_lvl: " << scope_lvl() << " num marks: " << m_num_marks << "\n";); + SASSERT(scope_lvl() == max_lvl); + } + + TRACE("nlsat_proof", tout << "New lemma\n"; display(tout, m_lemma); tout << "\n=========================\n";); + TRACE("nlsat_proof_sk", tout << "New lemma\n"; display_abst(tout, m_lemma); tout << "\n=========================\n";); + + if (m_lemma.empty()) { + TRACE("nlsat", tout << "empty clause generated\n";); + return false; // problem is unsat, empty clause was generated + } + + reset_marks(); // remove marks from the literals in m_lemmas. + TRACE("nlsat", tout << "new lemma:\n"; display(tout, m_lemma.size(), m_lemma.c_ptr()); tout << "\n"; + tout << "found_decision: " << found_decision << "\n";); + + // There are two possibilities: + // 1) m_lemma contains only literals from previous stages, and they + // are false in the current interpretation. We make progress + // by returning to a previous stage with additional information (new clause) + // that forces us to select a new partial interpretation + // >>> Return to some previous stage (we may also backjump many decisions and stages). + // + // 2) m_lemma contains at most one literal from the current level (the last literal). + // Moreover, this literal was a decision, but the new lemma forces it to + // be assigned to a different value. + // >>> In this case, we remain in the same stage but, we add a new asserted literal + // in a previous scope level. We may backjump many decisions. + // + unsigned sz = m_lemma.size(); + clause * new_cls = 0; + if (!found_decision) { + // Case 1) + // We just have to find the maximal variable in m_lemma, and return to that stage + // Remark: the lemma may contain only boolean literals, in this case new_max_var == null_var; + var new_max_var = max_var(sz, m_lemma.c_ptr()); + TRACE("nlsat_resolve", tout << "backtracking to stage: " << new_max_var << ", curr: " << m_xk << "\n";); + undo_until_stage(new_max_var); + SASSERT(m_xk == new_max_var); + new_cls = mk_clause(sz, m_lemma.c_ptr(), true, m_lemma_assumptions.get()); + TRACE("nlsat", tout << "new_level: " << scope_lvl() << "\nnew_stage: " << new_max_var << " "; + if (new_max_var != null_var) m_display_var(tout, new_max_var); tout << "\n";); + } + else { + SASSERT(scope_lvl() >= 1); + // Case 2) + if (is_bool_lemma(m_lemma.size(), m_lemma.c_ptr())) { + // boolean lemma, we just backtrack until the last literal is unassigned. + bool_var max_bool_var = m_lemma[m_lemma.size()-1].var(); + undo_until_unassigned(max_bool_var); + } + else { + // We must find the maximal decision level in literals in the first sz-1 positions that + // are at the same stage. If all these literals are from previous stages, + // we just backtrack the current level. + unsigned new_lvl = find_new_level_arith_lemma(m_lemma.size(), m_lemma.c_ptr()); + TRACE("nlsat", tout << "backtracking to new level: " << new_lvl << ", curr: " << m_scope_lvl << "\n";); + undo_until_level(new_lvl); + } + new_cls = mk_clause(sz, m_lemma.c_ptr(), true, m_lemma_assumptions.get()); + } + NLSAT_VERBOSE(display(verbose_stream(), *new_cls); verbose_stream() << "\n";); + if (!process_clause(*new_cls, true)) { + TRACE("nlsat", tout << "new clause triggered another conflict, restarting conflict resolution...\n";); + // we are still in conflict + conflict_clause = new_cls; + goto start; + } + TRACE("nlsat_resolve_done", display_assignment(tout); display_bool_assignment(tout);); + return true; + } + + + // ----------------------- + // + // Debugging + // + // ----------------------- + + bool check_watches() const { + for (var x = 0; x < num_vars(); x++) { + clause_vector const & cs = m_watches[x]; + unsigned sz = cs.size(); + for (unsigned i = 0; i < sz; i++) { + clause const & c = *(cs[i]); + SASSERT(max_var(c) == x); + } + } + return true; + } + + bool check_bwatches() const { + for (bool_var b = 0; b < m_bwatches.size(); b++) { + clause_vector const & cs = m_bwatches[b]; + unsigned sz = cs.size(); + for (unsigned i = 0; i < sz; i++) { + clause const & c = *(cs[i]); + SASSERT(max_var(c) == null_var); + SASSERT(max_bvar(c) == b); + } + } + return true; + } + + bool check_invariant() const { + SASSERT(check_watches()); + SASSERT(check_bwatches()); + return true; + } + + bool check_satisfied(clause_vector const & cs) const { + unsigned sz = cs.size(); + for (unsigned i = 0; i < sz; i++) { + clause const & c = *(cs[i]); + if (!is_satisfied(c)) { + TRACE("nlsat", tout << "not satisfied\n"; display(tout, c); tout << "\n";); + UNREACHABLE(); + } + } + return true; + } + + bool check_satisfied() const { + TRACE("nlsat", tout << "bk: b" << m_bk << ", xk: x" << m_xk << "\n"; if (m_xk != null_var) { m_display_var(tout, m_xk); tout << "\n"; }); + unsigned num = m_atoms.size(); + if (m_bk != null_bool_var) + num = m_bk; + for (bool_var b = 0; b < num; b++) { + SASSERT(check_satisfied(m_bwatches[b])); + } + if (m_xk != null_var) { + for (var x = 0; x < m_xk; x++) { + SASSERT(check_satisfied(m_watches[x])); + } + } + return true; + } + + // ----------------------- + // + // Statistics + // + // ----------------------- + + void collect_statistics(statistics & st) { + st.update("nlsat conflicts", m_conflicts); + st.update("nlsat propagations", m_propagations); + st.update("nlsat decisions", m_decisions); + st.update("nlsat stages", m_stages); + st.update("nlsat irrational assignments", m_irrational_assignments); + } + + void reset_statistics() { + m_conflicts = 0; + m_propagations = 0; + m_decisions = 0; + m_stages = 0; + m_irrational_assignments = 0; + } + + // ----------------------- + // + // Variable reodering + // + // ----------------------- + + struct var_info_collector { + pmanager & pm; + atom_vector const & m_atoms; + unsigned_vector m_max_degree; + unsigned_vector m_num_occs; + + var_info_collector(pmanager & _pm, atom_vector const & atoms, unsigned num_vars): + pm(_pm), + m_atoms(atoms) { + m_max_degree.resize(num_vars, 0); + m_num_occs.resize(num_vars, 0); + } + + var_vector m_vars; + void collect(poly * p) { + m_vars.reset(); + pm.vars(p, m_vars); + unsigned sz = m_vars.size(); + for (unsigned i = 0; i < sz; i++) { + var x = m_vars[i]; + unsigned k = pm.degree(p, x); + m_num_occs[x]++; + if (k > m_max_degree[x]) + m_max_degree[x] = k; + } + } + + void collect(literal l) { + bool_var b = l.var(); + atom * a = m_atoms[b]; + if (a == 0) + return; + if (a->is_ineq_atom()) { + unsigned sz = to_ineq_atom(a)->size(); + for (unsigned i = 0; i < sz; i++) { + collect(to_ineq_atom(a)->p(i)); + } + } + else { + collect(to_root_atom(a)->p()); + } + } + + void collect(clause const & c) { + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) + collect(c[i]); + } + + void collect(clause_vector const & cs) { + unsigned sz = cs.size(); + for (unsigned i = 0; i < sz; i++) + collect(*(cs[i])); + } + + void display(std::ostream & out, display_var_proc const & proc) { + unsigned sz = m_num_occs.size(); + for (unsigned i = 0; i < sz; i++) { + proc(out, i); out << " -> " << m_max_degree[i] << " : " << m_num_occs[i] << "\n"; + } + } + }; + + struct reorder_lt { + var_info_collector const & m_info; + reorder_lt(var_info_collector const & info):m_info(info) {} + bool operator()(var x, var y) const { + // high degree first + if (m_info.m_max_degree[x] < m_info.m_max_degree[y]) + return false; + if (m_info.m_max_degree[x] > m_info.m_max_degree[y]) + return true; + // more constrained first + if (m_info.m_num_occs[x] < m_info.m_num_occs[y]) + return false; + if (m_info.m_num_occs[x] > m_info.m_num_occs[y]) + return true; + return x < y; + } + }; + + // Order variables by degree and number of occurrences + void heuristic_reorder() { + unsigned num = num_vars(); + var_info_collector collector(m_pm, m_atoms, num); + collector.collect(m_clauses); + collector.collect(m_learned); + TRACE("nlsat_reorder", collector.display(tout, m_display_var);); + var_vector new_order; + for (var x = 0; x < num; x++) { + new_order.push_back(x); + } + std::sort(new_order.begin(), new_order.end(), reorder_lt(collector)); + TRACE("nlsat_reorder", + tout << "new order: "; for (unsigned i = 0; i < num; i++) tout << new_order[i] << " "; tout << "\n";); + var_vector perm; + perm.resize(num, 0); + for (var x = 0; x < num; x++) { + perm[new_order[x]] = x; + } + reorder(perm.size(), perm.c_ptr()); + SASSERT(check_invariant()); + } + + void shuffle_vars() { + var_vector p; + unsigned num = num_vars(); + for (var x = 0; x < num; x++) { + p.push_back(x); + } + random_gen r(m_random_seed); + shuffle(p.size(), p.c_ptr(), r); + reorder(p.size(), p.c_ptr()); + } + + /** + \brief Reorder variables using the giving permutation. + p maps internal variables to their new positions + */ + void reorder(unsigned sz, var const * p) { + TRACE("nlsat_reorder", tout << "solver before variable reorder\n"; display(tout); + display_vars(tout); + tout << "\npermutation:\n"; + for (unsigned i = 0; i < sz; i++) tout << p[i] << " "; tout << "\n"; + ); + SASSERT(num_vars() == sz); + TRACE("nlsat_bool_assignment_bug", tout << "before reset watches\n"; display_bool_assignment(tout);); + reset_watches(); + assignment new_assignment(m_am); + for (var x = 0; x < num_vars(); x++) { + if (m_assignment.is_assigned(x)) + new_assignment.set(p[x], m_assignment.value(x)); + } + var_vector new_inv_perm; + new_inv_perm.resize(sz); + // the undo_until_size(0) statement erases the Boolean assignment. + // undo_until_size(0) + undo_until_stage(null_var); + m_cache.reset(); + DEBUG_CODE({ + for (var x = 0; x < num_vars(); x++) { + SASSERT(m_watches[x].empty()); + } + }); + // update m_perm mapping + for (unsigned ext_x = 0; ext_x < sz; ext_x++) { + // p: internal -> new pos + // m_perm: internal -> external + // m_inv_perm: external -> internal + new_inv_perm[ext_x] = p[m_inv_perm[ext_x]]; + m_perm.set(new_inv_perm[ext_x], ext_x); + } + svector is_int; + is_int.swap(m_is_int); + for (var x = 0; x < sz; x++) { + m_is_int.setx(p[x], is_int[x], false); + SASSERT(m_infeasible[x] == 0); + } + m_inv_perm.swap(new_inv_perm); + DEBUG_CODE({ + for (var x = 0; x < num_vars(); x++) { + SASSERT(x == m_inv_perm[m_perm[x]]); + SASSERT(m_watches[x].empty()); + } + }); + m_pm.rename(sz, p); + del_ill_formed_lemmas(); + TRACE("nlsat_bool_assignment_bug", tout << "before reinit cache\n"; display_bool_assignment(tout);); + reinit_cache(); + m_assignment.swap(new_assignment); + reattach_arith_clauses(m_clauses); + reattach_arith_clauses(m_learned); + TRACE("nlsat_reorder", tout << "solver after variable reorder\n"; display(tout); display_vars(tout);); + } + + /** + \brief Restore variable order. + */ + void restore_order() { + // m_perm: internal -> external + // m_inv_perm: external -> internal + var_vector p; + p.append(m_perm); + reorder(p.size(), p.c_ptr()); + DEBUG_CODE({ + for (var x = 0; x < num_vars(); x++) { + SASSERT(m_perm[x] == x); + SASSERT(m_inv_perm[x] == x); + } + }); + } + + /** + \brief After variable reordering some lemmas containing root atoms may be ill-formed. + */ + void del_ill_formed_lemmas() { + unsigned sz = m_learned.size(); + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + clause * c = m_learned[i]; + if (ill_formed(*c)) { + del_clause(c); + } + else { + m_learned[j] = c; + j++; + } + } + m_learned.shrink(j); + } + + /** + \brief Return true if the clause contains an ill formed root atom + */ + bool ill_formed(clause const & c) { + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) { + bool_var b = c[i].var(); + atom * a = m_atoms[b]; + if (a == 0) + continue; + if (a->is_ineq_atom()) + continue; + if (to_root_atom(a)->x() < max_var(to_root_atom(a)->p())) + return true; + } + return false; + } + + /** + \brief reinsert all polynomials in the unique cache + */ + void reinit_cache() { + reinit_cache(m_clauses); + reinit_cache(m_learned); + } + void reinit_cache(clause_vector const & cs) { + unsigned sz = cs.size(); + for (unsigned i = 0; i < sz; i++) + reinit_cache(*(cs[i])); + } + void reinit_cache(clause const & c) { + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) + reinit_cache(c[i]); + } + void reinit_cache(literal l) { + bool_var b = l.var(); + atom * a = m_atoms[b]; + if (a == 0) + return; + if (a->is_ineq_atom()) { + var max = 0; + unsigned sz = to_ineq_atom(a)->size(); + for (unsigned i = 0; i < sz; i++) { + poly * p = to_ineq_atom(a)->p(i); + VERIFY(m_cache.mk_unique(p) == p); + var x = m_pm.max_var(p); + if (x > max) + max = x; + } + a->m_max_var = max; + } + else { + poly * p = to_root_atom(a)->p(); + VERIFY(m_cache.mk_unique(p) == p); + a->m_max_var = m_pm.max_var(p); + } + } + + void reset_watches() { + unsigned num = num_vars(); + for (var x = 0; x < num; x++) { + m_watches[x].reset(); + } + } + + void reattach_arith_clauses(clause_vector const & cs) { + unsigned sz = cs.size(); + for (unsigned i = 0; i < sz; i++) { + clause & c = *cs[i]; + var x = max_var(c); + if (x != null_var) + m_watches[x].push_back(&c); + } + } + + // ----------------------- + // + // Solver initialization + // + // ----------------------- + + struct degree_lt { + unsigned_vector & m_degrees; + degree_lt(unsigned_vector & ds):m_degrees(ds) {} + bool operator()(unsigned i1, unsigned i2) const { + if (m_degrees[i1] < m_degrees[i2]) + return true; + if (m_degrees[i1] > m_degrees[i2]) + return false; + return i1 < i2; + } + }; + + unsigned_vector m_cs_degrees; + unsigned_vector m_cs_p; + void sort_clauses_by_degree(unsigned sz, clause ** cs) { + if (sz <= 1) + return; + TRACE("nlsat_reorder_clauses", tout << "before:\n"; for (unsigned i = 0; i < sz; i++) { display(tout, *(cs[i])); tout << "\n"; }); + m_cs_degrees.reset(); + m_cs_p.reset(); + for (unsigned i = 0; i < sz; i++) { + m_cs_p.push_back(i); + m_cs_degrees.push_back(degree(*(cs[i]))); + } + std::sort(m_cs_p.begin(), m_cs_p.end(), degree_lt(m_cs_degrees)); + TRACE("nlsat_reorder_clauses", tout << "permutation: "; ::display(tout, m_cs_p.begin(), m_cs_p.end()); tout << "\n";); + apply_permutation(sz, cs, m_cs_p.c_ptr()); + TRACE("nlsat_reorder_clauses", tout << "after:\n"; for (unsigned i = 0; i < sz; i++) { display(tout, *(cs[i])); tout << "\n"; }); + } + + void sort_watched_clauses() { + unsigned num = num_vars(); + for (unsigned i = 0; i < num; i++) { + clause_vector & ws = m_watches[i]; + sort_clauses_by_degree(ws.size(), ws.c_ptr()); + } + } + + // ----------------------- + // + // Full dimensional + // + // A problem is in the full dimensional fragment if it does + // not contain equalities or non-strict inequalities. + // + // ----------------------- + + bool is_full_dimensional(literal l) const { + atom * a = m_atoms[l.var()]; + if (a == 0) + return true; + switch (a->get_kind()) { + case atom::EQ: return l.sign(); + case atom::LT: return !l.sign(); + case atom::GT: return !l.sign(); + case atom::ROOT_EQ: return l.sign(); + case atom::ROOT_LT: return !l.sign(); + case atom::ROOT_GT: return !l.sign(); + case atom::ROOT_LE: return l.sign(); + case atom::ROOT_GE: return l.sign(); + default: + UNREACHABLE(); + return false; + } + } + + bool is_full_dimensional(clause const & c) const { + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) { + if (!is_full_dimensional(c[i])) + return false; + } + return true; + } + + bool is_full_dimensional(clause_vector const & cs) const { + unsigned sz = cs.size(); + for (unsigned i = 0; i < sz; i++) { + if (!is_full_dimensional(*(cs[i]))) + return false; + } + return true; + } + + bool is_full_dimensional() const { + return is_full_dimensional(m_clauses); + } + + // ----------------------- + // + // Pretty printing + // + // ----------------------- + + void display_assignment(std::ostream & out, display_var_proc const & proc) const { + for (var x = 0; x < num_vars(); x++) { + if (m_assignment.is_assigned(x)) { + proc(out, x); + out << " -> "; + m_am.display_decimal(out, m_assignment.value(x)); + out << "\n"; + } + } + } + + void display_bool_assignment(std::ostream & out, display_var_proc const & proc) const { + unsigned sz = m_atoms.size(); + for (bool_var b = 0; b < sz; b++) { + if (m_atoms[b] == 0 && m_bvalues[b] != l_undef) { + out << "b" << b << " -> " << (m_bvalues[b] == l_true ? "true" : "false") << "\n"; + } + } + TRACE("nlsat_bool_assignment", + for (bool_var b = 0; b < sz; b++) { + out << "b" << b << " -> " << m_bvalues[b] << " " << m_atoms[b] << "\n"; + }); + } + + bool display_mathematica_assignment(std::ostream & out) const { + bool first = true; + for (var x = 0; x < num_vars(); x++) { + if (m_assignment.is_assigned(x)) { + if (first) + first = false; + else + out << " && "; + out << "x" << x << " == "; + m_am.display_mathematica(out, m_assignment.value(x)); + } + } + return !first; + } + + void display_assignment(std::ostream & out) const { + display_assignment(out, m_display_var); + } + + void display_bool_assignment(std::ostream & out) const { + display_bool_assignment(out, m_display_var); + } + + void display(std::ostream & out, ineq_atom const & a, display_var_proc const & proc, bool use_star = false) const { + unsigned sz = a.size(); + for (unsigned i = 0; i < sz; i++) { + if (use_star && i > 0) + out << "*"; + bool is_even = a.is_even(i); + if (is_even || sz > 1) + out << "("; + m_pm.display(out, a.p(i), proc, use_star); + if (is_even || sz > 1) + out << ")"; + if (is_even) + out << "^2"; + } + switch (a.get_kind()) { + case atom::LT: out << " < 0"; break; + case atom::GT: out << " > 0"; break; + case atom::EQ: out << " = 0"; break; + default: UNREACHABLE(); break; + } + } + + void display_mathematica(std::ostream & out, ineq_atom const & a) const { + unsigned sz = a.size(); + for (unsigned i = 0; i < sz; i++) { + if (i > 0) + out << "*"; + bool is_even = a.is_even(i); + if (sz > 1) + out << "("; + if (is_even) + out << "("; + m_pm.display(out, a.p(i), display_var_proc(), true); + if (is_even) + out << "^2)"; + if (sz > 1) + out << ")"; + } + switch (a.get_kind()) { + case atom::LT: out << " < 0"; break; + case atom::GT: out << " > 0"; break; + case atom::EQ: out << " == 0"; break; + default: UNREACHABLE(); break; + } + } + + void display_smt2(std::ostream & out, ineq_atom const & a, display_var_proc const & proc) const { + switch (a.get_kind()) { + case atom::LT: out << "(< "; break; + case atom::GT: out << "(> "; break; + case atom::EQ: out << "(= "; break; + default: UNREACHABLE(); break; + } + unsigned sz = a.size(); + if (sz > 1) + out << "(* "; + for (unsigned i = 0; i < sz; i++) { + if (i > 0) out << " "; + if (a.is_even(i)) { + out << "(* "; + m_pm.display_smt2(out, a.p(i), proc); + out << " "; + m_pm.display_smt2(out, a.p(i), proc); + out << ")"; + } + else { + m_pm.display_smt2(out, a.p(i), proc); + } + } + if (sz > 1) + out << ")"; + out << " 0)"; + } + + void display(std::ostream & out, root_atom const & a, display_var_proc const & proc) const { + proc(out, a.x()); + switch (a.get_kind()) { + case atom::ROOT_LT: out << " < "; break; + case atom::ROOT_GT: out << " > "; break; + case atom::ROOT_LE: out << " <= "; break; + case atom::ROOT_GE: out << " >= "; break; + case atom::ROOT_EQ: out << " = "; break; + default: UNREACHABLE(); break; + } + out << "root[" << a.i() << "]("; + m_pm.display(out, a.p(), proc); + out << ")"; + } + + struct mathematica_var_proc : public display_var_proc { + var m_x; + public: + mathematica_var_proc(var x):m_x(x) {} + virtual void operator()(std::ostream & out, var x) const { + if (m_x == x) + out << "#1"; + else + out << "x" << x; + } + }; + + void display_mathematica(std::ostream & out, root_atom const & a) const { + out << "x" << a.x(); + switch (a.get_kind()) { + case atom::ROOT_LT: out << " < "; break; + case atom::ROOT_GT: out << " > "; break; + case atom::ROOT_LE: out << " <= "; break; + case atom::ROOT_GE: out << " >= "; break; + case atom::ROOT_EQ: out << " == "; break; + default: UNREACHABLE(); break; + } + out << "Root["; + m_pm.display(out, a.p(), mathematica_var_proc(a.x()), true); + out << " &, " << a.i() << "]"; + } + + void display_smt2(std::ostream & out, root_atom const & a) const { + NOT_IMPLEMENTED_YET(); + } + + void display(std::ostream & out, atom const & a, display_var_proc const & proc) const { + if (a.is_ineq_atom()) + display(out, static_cast(a), proc); + else + display(out, static_cast(a), proc); + } + + void display_mathematica(std::ostream & out, atom const & a) const { + if (a.is_ineq_atom()) + display_mathematica(out, static_cast(a)); + else + display_mathematica(out, static_cast(a)); + } + + void display_smt2(std::ostream & out, atom const & a, display_var_proc const & proc) const { + if (a.is_ineq_atom()) + display_smt2(out, static_cast(a), proc); + else + display_smt2(out, static_cast(a), proc); + } + + void display_atom(std::ostream & out, bool_var b, display_var_proc const & proc) const { + if (b == 0) + out << "true"; + else if (m_atoms[b] == 0) + out << "b" << b; + else + display(out, *(m_atoms[b]), proc); + } + + void display_atom(std::ostream & out, bool_var b) const { + display_atom(out, b, m_display_var); + } + + void display_mathematica_atom(std::ostream & out, bool_var b) const { + if (b == 0) + out << "(0 < 1)"; + else if (m_atoms[b] == 0) + out << "b" << b; + else + display_mathematica(out, *(m_atoms[b])); + } + + void display_smt2_atom(std::ostream & out, bool_var b, display_var_proc const & proc) const { + if (b == 0) + out << "true"; + else if (m_atoms[b] == 0) + out << "b" << b; + else + display_smt2(out, *(m_atoms[b]), proc); + } + + void display(std::ostream & out, literal l, display_var_proc const & proc) const { + if (l.sign()) { + bool_var b = l.var(); + out << "!"; + if (m_atoms[b] != 0) + out << "("; + display_atom(out, b, proc); + if (m_atoms[b] != 0) + out << ")"; + } + else { + display_atom(out, l.var(), proc); + } + } + + void display(std::ostream & out, literal l) const { + display(out, l, m_display_var); + } + + void display_mathematica(std::ostream & out, literal l) const { + if (l.sign()) { + bool_var b = l.var(); + out << "!"; + if (m_atoms[b] != 0) + out << "("; + display_mathematica_atom(out, b); + if (m_atoms[b] != 0) + out << ")"; + } + else { + display_mathematica_atom(out, l.var()); + } + } + + void display_smt2(std::ostream & out, literal l, display_var_proc const & proc) const { + if (l.sign()) { + bool_var b = l.var(); + out << "(not "; + display_smt2_atom(out, b, proc); + out << ")"; + } + else { + display_smt2_atom(out, l.var(), proc); + } + } + + void display_assumptions(std::ostream & out, _assumption_set s) const { + + } + + void display(std::ostream & out, unsigned num, literal const * ls, display_var_proc const & proc) const { + for (unsigned i = 0; i < num; i++) { + if (i > 0) + out << " or "; + display(out, ls[i], proc); + } + } + + void display(std::ostream & out, unsigned num, literal const * ls) { + display(out, num, ls, m_display_var); + } + + void display(std::ostream & out, scoped_literal_vector const & cs) { + display(out, cs.size(), cs.c_ptr(), m_display_var); + } + + void display(std::ostream & out, clause const & c, display_var_proc const & proc) const { + if (c.assumptions() != 0) { + display_assumptions(out, static_cast<_assumption_set>(c.assumptions())); + out << " |- "; + } + display(out, c.size(), c.c_ptr(), proc); + } + + void display(std::ostream & out, clause const & c) const { + display(out, c, m_display_var); + } + + void display_smt2(std::ostream & out, unsigned num, literal const * ls, display_var_proc const & proc) const { + if (num == 0) { + out << "false"; + } + else if (num == 1) { + display_smt2(out, ls[0], proc); + } + else { + out << "(or"; + for (unsigned i = 0; i < num; i++) { + out << " "; + display_smt2(out, ls[i], proc); + } + out << ")"; + } + } + + void display_smt2(std::ostream & out, clause const & c, display_var_proc const & proc = display_var_proc()) const { + display_smt2(out, c.size(), c.c_ptr(), proc); + } + + void display_abst(std::ostream & out, literal l) const { + if (l.sign()) { + bool_var b = l.var(); + out << "!"; + if (b == true_bool_var) + out << "true"; + else + out << "b" << b; + } + else { + out << "b" << l.var(); + } + } + + void display_abst(std::ostream & out, unsigned num, literal const * ls) const { + for (unsigned i = 0; i < num; i++) { + if (i > 0) + out << " or "; + display_abst(out, ls[i]); + } + } + + void display_abst(std::ostream & out, scoped_literal_vector const & cs) const { + display_abst(out, cs.size(), cs.c_ptr()); + } + + void display_abst(std::ostream & out, clause const & c) const { + display_abst(out, c.size(), c.c_ptr()); + } + + void display_mathematica(std::ostream & out, clause const & c) const { + out << "("; + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) { + if (i > 0) + out << " || "; + display_mathematica(out, c[i]); + } + out << ")"; + } + + // Debugging support: + // Display generated lemma in Mathematica format. + // Mathematica must reduce lemma to True (modulo resource constraints). + void display_mathematica_lemma(std::ostream & out, unsigned num, literal const * ls, bool include_assignment = false) const { + out << "Resolve[ForAll[{"; + // var definition + for (unsigned i = 0; i < num_vars(); i++) { + if (i > 0) + out << ", "; + out << "x" << i; + } + out << "}, "; + if (include_assignment) { + out << "!("; + if (!display_mathematica_assignment(out)) + out << "0 < 1"; // nothing was printed + out << ") || "; + } + for (unsigned i = 0; i < num; i++) { + if (i > 0) + out << " || "; + display_mathematica(out, ls[i]); + } + out << "], Reals]\n"; // end of exists + } + + void display(std::ostream & out, clause_vector const & cs, display_var_proc const & proc) const { + unsigned sz = cs.size(); + for (unsigned i = 0; i < sz; i++) { + display(out, *(cs[i]), proc); + out << "\n"; + } + } + + void display(std::ostream & out, clause_vector const & cs) const { + display(out, cs, m_display_var); + } + + void display_mathematica(std::ostream & out, clause_vector const & cs) const { + unsigned sz = cs.size(); + for (unsigned i = 0; i < sz; i++) { + if (i > 0) out << ",\n"; + out << " "; + display_mathematica(out, *(cs[i])); + } + } + + void display_abst(std::ostream & out, clause_vector const & cs) const { + unsigned sz = cs.size(); + for (unsigned i = 0; i < sz; i++) { + display_abst(out, *(cs[i])); + out << "\n"; + } + } + + void display(std::ostream & out, display_var_proc const & proc) const { + display(out, m_clauses, proc); + if (!m_learned.empty()) { + out << "Lemmas:\n"; + display(out, m_learned, proc); + } + } + + void display_mathematica(std::ostream & out) const { + out << "{\n"; + display_mathematica(out, m_clauses); + out << "}\n"; + } + + void display_abst(std::ostream & out) const { + display_abst(out, m_clauses); + if (!m_learned.empty()) { + out << "Lemmas:\n"; + display_abst(out, m_learned); + } + } + + void display(std::ostream & out) const { + display(out, m_display_var); + } + + void display_vars(std::ostream & out) const { + for (unsigned i = 0; i < num_vars(); i++) { + out << i << " -> "; m_display_var(out, i); out << "\n"; + } + } + + void display_smt2_arith_decls(std::ostream & out) const { + unsigned sz = m_is_int.size(); + for (unsigned i = 0; i < sz; i++) { + if (m_is_int[i]) + out << "(declare-fun x" << i << " () Int)\n"; + else + out << "(declare-fun x" << i << " () Real)\n"; + } + } + + void display_smt2_bool_decls(std::ostream & out) const { + unsigned sz = m_atoms.size(); + for (unsigned i = 0; i < sz; i++) { + if (m_atoms[i] == 0) + out << "(declare-fun b" << i << " () Bool)\n"; + } + } + + void display_smt2(std::ostream & out) const { + display_smt2_bool_decls(out); + display_smt2_arith_decls(out); + out << "(assert (and true\n"; + unsigned sz = m_clauses.size(); + for (unsigned i = 0; i < sz; i++) { + display_smt2(out, *(m_clauses[i])); + out << "\n"; + } + out << "))\n(check-sat)" << std::endl; + } + }; + + solver::solver(params_ref const & p) { + m_imp = alloc(imp, *this, p); + } + + solver::~solver() { + dealloc(m_imp); + } + + lbool solver::check() { + return m_imp->check(); + } + + void solver::set_cancel(bool f) { + m_imp->set_cancel(f); + } + + void solver::collect_param_descrs(param_descrs & d) { + insert_max_memory(d); + algebraic_numbers::manager::collect_param_descrs(d); + d.insert(":max-conflicts", CPK_UINT, "(default: inf) maximum number of conflicts."); + d.insert(":shuffle-vars", CPK_BOOL, "(default: false) use a variable order."); + d.insert(":seed", CPK_UINT, "(default: 0) random seed."); + d.insert(":randomize", CPK_BOOL, "(default: true) randomize selection of a witness in nlsat."); + d.insert(":reorder", CPK_BOOL, "(default: true) reorder variables."); + d.insert(":lazy", CPK_UINT, "(default: 0) how lazy the solver is."); + d.insert(":simplify-conflicts", CPK_BOOL, "(default: true) simplify conflicts using equalities before resolving them in nlsat solver."); + } + + unsynch_mpq_manager & solver::qm() { + return m_imp->m_qm; + } + + anum_manager & solver::am() { + return m_imp->m_am; + } + + pmanager & solver::pm() { + return m_imp->m_pm; + } + + void solver::set_display_var(display_var_proc const & proc) { + m_imp->m_display_var.m_proc = &proc; + } + + bool solver::is_int(var x) const { + return m_imp->is_int(x); + } + + bool_var solver::mk_bool_var() { + return m_imp->mk_bool_var(); + } + + atom * solver::bool_var2atom(bool_var b) { + return m_imp->m_atoms[b]; + } + + var solver::mk_var(bool is_int) { + return m_imp->mk_var(is_int); + } + + bool_var solver::mk_ineq_atom(atom::kind k, unsigned sz, poly * const * ps, bool const * is_even) { + return m_imp->mk_ineq_atom(k, sz, ps, is_even); + } + + literal solver::mk_ineq_literal(atom::kind k, unsigned sz, poly * const * ps, bool const * is_even) { + return m_imp->mk_ineq_literal(k, sz, ps, is_even); + } + + bool_var solver::mk_root_atom(atom::kind k, var x, unsigned i, poly * p) { + return m_imp->mk_root_atom(k, x, i, p); + } + + void solver::inc_ref(bool_var b) { + m_imp->inc_ref(b); + } + + void solver::dec_ref(bool_var b) { + m_imp->dec_ref(b); + } + + void solver::mk_clause(unsigned num_lits, literal * lits, assumption a) { + return m_imp->mk_clause(num_lits, lits, a); + } + + void solver::display(std::ostream & out) const { + m_imp->display(out); + } + + void solver::display(std::ostream & out, literal l) const { + m_imp->display(out, l); + } + + void solver::display(std::ostream & out, var x) const { + m_imp->m_display_var(out, x); + } + + display_var_proc const & solver::display_proc() const { + return m_imp->m_display_var; + } + + anum const & solver::value(var x) const { + if (m_imp->m_assignment.is_assigned(x)) + return m_imp->m_assignment.value(x); + return m_imp->m_zero; + } + + lbool solver::bvalue(bool_var b) const { + return m_imp->m_bvalues[b]; + } + + lbool solver::value(literal l) const { + return m_imp->value(l); + } + + bool solver::is_interpreted(bool_var b) const { + return m_imp->m_atoms[b] != 0; + } + + void solver::reset_statistics() { + return m_imp->reset_statistics(); + } + + void solver::collect_statistics(statistics & st) { + return m_imp->collect_statistics(st); + } + + +}; diff --git a/lib/nlsat_solver.h b/lib/nlsat_solver.h new file mode 100644 index 000000000..dcee46f05 --- /dev/null +++ b/lib/nlsat_solver.h @@ -0,0 +1,187 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + nlsat_solver.h + +Abstract: + + Nonlinear arithmetic satisfiability procedure. The procedure is + complete for nonlinear real arithmetic, but it also has limited + support for integers. + +Author: + + Leonardo de Moura (leonardo) 2012-01-02. + +Revision History: + +--*/ +#ifndef _NLSAT_SOLVER_H_ +#define _NLSAT_SOLVER_H_ + +#include"nlsat_types.h" +#include"params.h" +#include"statistics.h" + +namespace nlsat { + + class solver { + struct imp; + imp * m_imp; + public: + solver(params_ref const & p); + ~solver(); + + /** + \brief Return reference to rational manager. + */ + unsynch_mpq_manager & qm(); + + /** + \brief Return reference to algebraic number manager. + */ + anum_manager & am(); + + /** + \brief Return a reference to the polynomial manager used by the solver. + */ + pmanager & pm(); + + void set_display_var(display_var_proc const & proc); + + // ----------------------- + // + // Variable, Atoms, Clauses & Assumption creation + // + // ----------------------- + + /** + \brief Create a fresh boolean variable that is not associated with any + nonlinear arithmetic atom. + */ + bool_var mk_bool_var(); + + /** + \brief Create a real/integer variable. + */ + var mk_var(bool is_int); + + /** + \brief Create an atom of the form: p=0, p<0, p>0 + Where p = ps[0]^e[0]*...*ps[sz-1]^e[sz-1] + + e[i] = 1 if is_even[i] is false + e[i] = 2 if is_even[i] is true + + \pre sz > 0 + */ + bool_var mk_ineq_atom(atom::kind k, unsigned sz, poly * const * ps, bool const * is_even); + + /** + \brief Create a literal for the p=0, p<0, p>0. + Where p = ps[0]^e[0]*...*ps[sz-1]^e[sz-1] for sz > 0 + p = 1 for sz = 0 + + e[i] = 1 if is_even[i] is false + e[i] = 2 if is_even[i] is true + */ + literal mk_ineq_literal(atom::kind k, unsigned sz, poly * const * ps, bool const * is_even); + + /** + \brief Create an atom of the form: x=root[i](p), xroot[i](p) + */ + bool_var mk_root_atom(atom::kind k, var x, unsigned i, poly * p); + + void inc_ref(bool_var b); + void inc_ref(literal l) { inc_ref(l.var()); } + void dec_ref(bool_var b); + void dec_ref(literal l) { dec_ref(l.var()); } + + /** + \brief Create a new clause. + */ + void mk_clause(unsigned num_lits, literal * lits, assumption a = 0); + + // ----------------------- + // + // Basic + // + // ----------------------- + + /** + \brief Return the number of Boolean variables. + */ + unsigned num_bool_vars() const; + + /** + \brief Get atom associated with Boolean variable. Return 0 if there is none. + */ + atom * bool_var2atom(bool_var b); + + /** + \brief Return number of integer/real variables + */ + unsigned num_vars() const; + + bool is_int(var x) const; + + // ----------------------- + // + // Search + // + // ----------------------- + lbool check(); + + // ----------------------- + // + // Model + // + // ----------------------- + anum const & value(var x) const; + lbool bvalue(bool_var b) const; + bool is_interpreted(bool_var b) const; + + lbool value(literal l) const; + + // ----------------------- + // + // Misc + // + // ----------------------- + void updt_params(params_ref const & p); + static void collect_param_descrs(param_descrs & d); + + void set_cancel(bool f); + void collect_statistics(statistics & st); + void reset_statistics(); + void display_status(std::ostream & out) const; + + // ----------------------- + // + // Pretty printing + // + // ----------------------- + + /** + \brief Display solver's state. + */ + void display(std::ostream & out) const; + + /** + \brief Display literal + */ + void display(std::ostream & out, literal l) const; + + /** + \brief Display variable + */ + void display(std::ostream & out, var x) const; + + display_var_proc const & display_proc() const; + }; + +}; + +#endif diff --git a/lib/nlsat_tactic.cpp b/lib/nlsat_tactic.cpp new file mode 100644 index 000000000..8b3c80b17 --- /dev/null +++ b/lib/nlsat_tactic.cpp @@ -0,0 +1,258 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + nlsat_tactic.cpp + +Abstract: + + Tactic for using nonlinear procedure. + +Author: + + Leonardo (leonardo) 2012-01-02 + +Notes: + +--*/ +#include"tactical.h" +#include"goal2nlsat.h" +#include"nlsat_solver.h" +#include"model.h" +#include"expr2var.h" +#include"arith_decl_plugin.h" +#include"ast_smt2_pp.h" +#include"z3_exception.h" +#include"algebraic_numbers.h" + +class nlsat_tactic : public tactic { + struct expr_display_var_proc : public nlsat::display_var_proc { + ast_manager & m; + expr_ref_vector m_var2expr; + expr_display_var_proc(ast_manager & _m):m(_m), m_var2expr(_m) {} + virtual void operator()(std::ostream & out, nlsat::var x) const { + if (x < m_var2expr.size()) + out << mk_ismt2_pp(m_var2expr.get(x), m); + else + out << "x!" << x; + } + }; + + struct imp { + ast_manager & m; + params_ref m_params; + expr_display_var_proc m_display_var; + nlsat::solver m_solver; + goal2nlsat m_g2nl; + + imp(ast_manager & _m, params_ref const & p): + m(_m), + m_params(p), + m_display_var(_m), + m_solver(p) { + } + + void updt_params(params_ref const & p) { + m_params = p; + m_solver.updt_params(p); + } + + void set_cancel(bool f) { + m_solver.set_cancel(f); + m_g2nl.set_cancel(f); + } + + bool contains_unsupported(expr_ref_vector & b2a, expr_ref_vector & x2t) { + for (unsigned x = 0; x < x2t.size(); x++) { + if (!is_uninterp_const(x2t.get(x))) { + TRACE("unsupported", tout << "unsupported atom:\n" << mk_ismt2_pp(x2t.get(x), m) << "\n";); + return true; + } + } + for (unsigned b = 0; b < b2a.size(); b++) { + expr * a = b2a.get(b); + if (a == 0) + continue; + if (is_uninterp_const(a)) + continue; + if (m_solver.is_interpreted(b)) + continue; // arithmetic atom + TRACE("unsupported", tout << "unsupported atom:\n" << mk_ismt2_pp(a, m) << "\n";); + return true; // unsupported + } + return false; + } + + // Return false if nlsat assigned noninteger value to an integer variable. + bool mk_model(expr_ref_vector & b2a, expr_ref_vector & x2t, model_converter_ref & mc) { + bool ok = true; + model_ref md = alloc(model, m); + arith_util util(m); + for (unsigned x = 0; x < x2t.size(); x++) { + expr * t = x2t.get(x); + if (!is_uninterp_const(t)) + continue; + expr * v; + try { + v = util.mk_numeral(m_solver.value(x), util.is_int(t)); + } + catch (z3_error & ex) { + throw ex; + } + catch (z3_exception &) { + v = util.mk_to_int(util.mk_numeral(m_solver.value(x), false)); + ok = false; + } + md->register_decl(to_app(t)->get_decl(), v); + } + for (unsigned b = 0; b < b2a.size(); b++) { + expr * a = b2a.get(b); + if (a == 0 || !is_uninterp_const(a)) + continue; + lbool val = m_solver.bvalue(b); + if (val == l_undef) + continue; // don't care + md->register_decl(to_app(a)->get_decl(), val == l_true ? m.mk_true() : m.mk_false()); + } + mc = model2model_converter(md.get()); + return ok; + } + + void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + mc = 0; pc = 0; core = 0; + tactic_report report("nlsat", *g); + + if (g->is_decided()) { + result.push_back(g.get()); + return; + } + + fail_if_proof_generation("nlsat", g); + + expr2var a2b(m); + expr2var t2x(m); + m_g2nl(*g, m_params, m_solver, a2b, t2x); + + m_display_var.m_var2expr.reset(); + t2x.mk_inv(m_display_var.m_var2expr); + m_solver.set_display_var(m_display_var); + + lbool st = m_solver.check(); + + if (st == l_undef) { + } + else if (st == l_true) { + expr_ref_vector x2t(m); + expr_ref_vector b2a(m); + a2b.mk_inv(b2a); + t2x.mk_inv(x2t); + if (!contains_unsupported(b2a, x2t)) { + // If mk_model is false it means that the model produced by nlsat + // assigns noninteger values to integer variables + if (mk_model(b2a, x2t, mc)) { + // result goal is trivially SAT + g->reset(); + } + } + } + else { + // TODO: extract unsat core + g->assert_expr(m.mk_false(), 0, 0); + } + g->inc_depth(); + result.push_back(g.get()); + TRACE("nlsat", g->display(tout);); + SASSERT(g->is_well_sorted()); + } + }; + + imp * m_imp; + params_ref m_params; + statistics m_stats; + + struct scoped_set_imp { + nlsat_tactic & m_owner; + scoped_set_imp(nlsat_tactic & o, imp & i):m_owner(o) { + #pragma omp critical (tactic_cancel) + { + m_owner.m_imp = &i; + } + } + + ~scoped_set_imp() { + m_owner.m_imp->m_solver.collect_statistics(m_owner.m_stats); + #pragma omp critical (tactic_cancel) + { + m_owner.m_imp = 0; + } + } + }; + +public: + nlsat_tactic(params_ref const & p): + m_params(p) { + m_imp = 0; + } + + virtual tactic * translate(ast_manager & m) { + return alloc(nlsat_tactic, m_params); + } + + virtual ~nlsat_tactic() { + SASSERT(m_imp == 0); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + } + + virtual void collect_param_descrs(param_descrs & r) { + goal2nlsat::collect_param_descrs(r); + nlsat::solver::collect_param_descrs(r); + algebraic_numbers::manager::collect_param_descrs(r); + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + try { + imp local_imp(in->m(), m_params); + scoped_set_imp setter(*this, local_imp); + local_imp(in, result, mc, pc, core); + } + catch (z3_error & ex) { + throw ex; + } + catch (z3_exception & ex) { + throw tactic_exception(ex.msg()); + } + } + + virtual void cleanup() {} + + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } + + virtual void collect_statistics(statistics & st) const { + st.copy(m_stats); + } + + virtual void reset_statistics() { + m_stats.reset(); + } +}; + +tactic * mk_nlsat_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(nlsat_tactic, p)); +} + diff --git a/lib/nlsat_tactic.h b/lib/nlsat_tactic.h new file mode 100644 index 000000000..93dfe74db --- /dev/null +++ b/lib/nlsat_tactic.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + nlsat_tactic.h + +Abstract: + + Tactic for using nonlinear procedure. + +Author: + + Leonardo (leonardo) 2012-01-02 + +Notes: + +--*/ +#ifndef _NLSAT_TACTIC_H_ +#define _NLSAT_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_nlsat_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/nlsat_types.cpp b/lib/nlsat_types.cpp new file mode 100644 index 000000000..956be4c5e --- /dev/null +++ b/lib/nlsat_types.cpp @@ -0,0 +1,70 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + nlsat_types.cpp + +Abstract: + + Basic types used in the nonlinear arithmetic satisfiability procedure. + +Author: + + Leonardo de Moura (leonardo) 2012-01-02. + +Revision History: + +--*/ +#include"nlsat_types.h" +#include"debug.h" +#include"hash.h" +#include"polynomial.h" + +namespace nlsat { + + ineq_atom::ineq_atom(kind k, unsigned sz, poly * const * ps, bool const * is_even, var max_var): + atom(k, max_var), + m_size(sz) { + for (unsigned i = 0; i < m_size; i++) { + m_ps[i] = TAG(poly *, ps[i], is_even[i] ? 1 : 0); + } + SASSERT(is_ineq_atom()); + } + + unsigned ineq_atom::hash_proc::operator()(ineq_atom const * a) const { + return get_composite_hash(a, a->m_size); + } + + bool ineq_atom::eq_proc::operator()(ineq_atom const * a1, ineq_atom const * a2) const { + if (a1->m_size != a2->m_size || a1->m_kind != a2->m_kind) + return false; + unsigned sz = a1->m_size; + for (unsigned i = 0; i < sz; i++) { + if (a1->m_ps[i] != a2->m_ps[i]) + return false; + } + return true; + } + + root_atom::root_atom(kind k, var x, unsigned i, poly * p): + atom(k, x), + m_x(x), + m_i(i), + m_p(p) { + SASSERT(is_root_atom()); + } + + unsigned root_atom::hash_proc::operator()(root_atom const * a) const { + unsigned _a = a->m_x; + unsigned _b = ((a->m_i << 2) | (a->m_kind)); + unsigned _c = polynomial::manager::id(a->m_p); + mix(_a, _b, _c); + return _c; + } + + bool root_atom::eq_proc::operator()(root_atom const * a1, root_atom const * a2) const { + return a1->m_kind == a2->m_kind && a1->m_x == a2->m_x && a1->m_i == a2->m_i && a1->m_p == a2->m_p; + } + +}; diff --git a/lib/nlsat_types.h b/lib/nlsat_types.h new file mode 100644 index 000000000..cd3a61a10 --- /dev/null +++ b/lib/nlsat_types.h @@ -0,0 +1,150 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + nlsat_types.h + +Abstract: + + Basic types used in the nonlinear arithmetic satisfiability procedure. + +Author: + + Leonardo de Moura (leonardo) 2012-01-02. + +Revision History: + +--*/ +#ifndef _NLSAT_TYPES_H_ +#define _NLSAT_TYPES_H_ + +#include"polynomial.h" +#include"buffer.h" +#include"sat_types.h" +#include"z3_exception.h" + +namespace algebraic_numbers { + class anum; + class manager; +}; + +namespace nlsat { +#define NLSAT_VB_LVL 10 + typedef void * assumption; + typedef void * assumption_set; + + typedef sat::bool_var bool_var; + typedef sat::bool_var_vector bool_var_vector; + const bool_var null_bool_var = sat::null_bool_var; + typedef sat::literal literal; + const literal null_literal = sat::null_literal; + typedef sat::literal_vector literal_vector; + + inline literal to_literal(unsigned x) { return sat::to_literal(x); } + + typedef polynomial::var var; + typedef polynomial::var_vector var_vector; + typedef polynomial::manager pmanager; + typedef polynomial::polynomial poly; + const var null_var = polynomial::null_var; + + const var true_bool_var = 0; + const literal true_literal(true_bool_var, false); + const literal false_literal(true_bool_var, true); + + typedef polynomial::display_var_proc display_var_proc; + + class atom; + class ineq_atom; // atoms of the form: p=0, p>0, p<0, where p is a product of polynomials + class root_atom; // atoms of the form: x=root[i](p), xroot[i](p), where x is a variable and p a polynomial. + + class clause; + class solver; + + class atom { + public: + enum kind { EQ=0, LT, GT, ROOT_EQ=10, ROOT_LT, ROOT_GT, ROOT_LE, ROOT_GE }; + static kind flip(kind k) { + SASSERT(k == EQ || k == LT || k == GT); + if (k == LT) return GT; + if (k == GT) return LT; + return EQ; + } + protected: + friend class solver; + kind m_kind; + unsigned m_ref_count; + bool_var m_bool_var; + var m_max_var; + public: + atom(kind k, var max_var):m_kind(k), m_ref_count(0), m_bool_var(null_bool_var), m_max_var(max_var) {} + bool is_eq() const { return m_kind == EQ || m_kind == ROOT_EQ; } + + bool is_ineq_atom() const { return m_kind <= GT; } + bool is_root_atom() const { return m_kind >= ROOT_EQ; } + kind get_kind() const { return m_kind; } + bool_var bvar() const { return m_bool_var; } + var max_var() const { return m_max_var; } + unsigned ref_count() const { return m_ref_count; } + void inc_ref() { m_ref_count++; } + void dec_ref() { SASSERT(m_ref_count > 0); m_ref_count--; } + }; + + typedef ptr_vector atom_vector; + + class ineq_atom : public atom { + friend class solver; + unsigned m_size; + poly * m_ps[0]; + ineq_atom(kind k, unsigned sz, poly * const * ps, bool const * is_even, var max_var); + static unsigned get_obj_size(unsigned sz) { return sizeof(ineq_atom) + sizeof(poly*)*sz; } + public: + unsigned size() const { return m_size; } + poly * p(unsigned i) const { SASSERT(i < size()); return UNTAG(poly*, m_ps[i]); } + // Return true if i-th factor has odd degree + bool is_odd(unsigned i) const { SASSERT(i < size()); return GET_TAG(m_ps[i]) == 0; } + bool is_even(unsigned i) const { return !is_odd(i); } + struct khasher { unsigned operator()(ineq_atom const * a) const { return a->m_kind; } }; + struct chasher { unsigned operator()(ineq_atom const * a, unsigned idx) const { return polynomial::manager::id(a->p(idx)); } }; + struct hash_proc { unsigned operator()(ineq_atom const * a) const; }; + struct eq_proc { bool operator()(ineq_atom const * a1, ineq_atom const * a2) const; }; + }; + + class root_atom : public atom { + friend class solver; + var m_x; + unsigned m_i; + poly * m_p; + root_atom(kind k, var x, unsigned i, poly * p); + public: + var x() const { return m_x; } + unsigned i() const { return m_i; } + poly * p() const { return m_p; } + struct hash_proc { unsigned operator()(root_atom const * a) const; }; + struct eq_proc { bool operator()(root_atom const * a1, root_atom const * a2) const; }; + }; + + inline ineq_atom * to_ineq_atom(atom * a) { SASSERT(a->is_ineq_atom()); return static_cast(a); } + inline root_atom * to_root_atom(atom * a) { SASSERT(a->is_root_atom()); return static_cast(a); } + inline ineq_atom const * to_ineq_atom(atom const * a) { SASSERT(a->is_ineq_atom()); return static_cast(a); } + inline root_atom const * to_root_atom(atom const * a) { SASSERT(a->is_root_atom()); return static_cast(a); } + + typedef algebraic_numbers::anum anum; + typedef algebraic_numbers::manager anum_manager; + + class solver_exception : public default_exception { + public: + solver_exception(char const * msg):default_exception(msg) {} + }; + + class assignment; + + inline int normalize_sign(int s) { + if (s < 0) return -1; + if (s == 0) return 0; + return 1; + } +}; + +#endif diff --git a/lib/nnf.cpp b/lib/nnf.cpp new file mode 100644 index 000000000..10ae1cb01 --- /dev/null +++ b/lib/nnf.cpp @@ -0,0 +1,1030 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + nnf.cpp + +Abstract: + + Negation Normal Form & Skolemization + +Author: + + Leonardo (leonardo) 2008-01-11 + +Notes: + Major revision on 2011-10-06 + +--*/ +#include"nnf.h" +#include"warning.h" +#include"used_vars.h" +#include"well_sorted.h" +#include"var_subst.h" + +#include"name_exprs.h" +#include"act_cache.h" +#include"cooperate.h" + +#include"ast_smt2_pp.h" + +// Old strategy framework +#include"assertion_set_strategy.h" + +// New framework +#include"tactical.h" + +class skolemizer { + typedef act_cache cache; + + ast_manager & m_manager; + symbol m_sk_hack; + bool m_sk_hack_enabled; + cache m_cache; + cache m_cache_pr; + + void process(quantifier * q, expr_ref & r, proof_ref & p) { + used_vars uv; + uv(q); + SASSERT(is_well_sorted(m(), q)); + unsigned sz = uv.get_max_found_var_idx_plus_1(); + ptr_buffer sorts; + expr_ref_vector args(m()); + for (unsigned i = 0; i < sz; i++) { + sort * s = uv.get(i); + if (s != 0) { + sorts.push_back(s); + args.push_back(m().mk_var(i, s)); + } + } + + TRACE("skolemizer", tout << "skid: " << q->get_skid() << "\n";); + + expr_ref_vector substitution(m()); + unsigned num_decls = q->get_num_decls(); + for (unsigned i = num_decls; i > 0; ) { + --i; + sort * r = q->get_decl_sort(i); + func_decl * sk_decl = m().mk_fresh_func_decl(q->get_decl_name(i), q->get_skid(), sorts.size(), sorts.c_ptr(), r); + app * sk = m().mk_app(sk_decl, args.size(), args.c_ptr()); + substitution.push_back(sk); + } + // + // (VAR 0) is in the first position of substitution. + // (VAR num_decls-1) is in the last position. + // + for (unsigned i = 0; i < sz; i++) { + sort * s = uv.get(i); + if (s != 0) + substitution.push_back(m().mk_var(i, s)); + else + substitution.push_back(0); + } + // + // (VAR num_decls) ... (VAR num_decls+sz-1) + // are in positions num_decls .. num_decls+sz-1 + // + std::reverse(substitution.c_ptr(), substitution.c_ptr() + substitution.size()); + // + // (VAR 0) should be in the last position of substitution. + // + var_subst s(m()); + SASSERT(is_well_sorted(m(), q->get_expr())); + expr_ref tmp(m()); + expr * body = q->get_expr(); + if (m_sk_hack_enabled) { + unsigned num_patterns = q->get_num_patterns(); + for (unsigned i = 0; i < num_patterns; ++i) { + expr * p = q->get_pattern(i); + if (is_sk_hack(p)) { + expr * sk_hack = to_app(p)->get_arg(0); + if (q->is_forall()) // check whether is in negative/positive context. + tmp = m().mk_or(body, m().mk_not(sk_hack)); // negative context + else + tmp = m().mk_and(body, sk_hack); // positive context + body = tmp; + } + } + } + s(body, substitution.size(), substitution.c_ptr(), r); + SASSERT(is_well_sorted(m(), r)); + p = 0; + if (m().proofs_enabled()) { + if (q->is_forall()) + p = m().mk_skolemization(m().mk_not(q), m().mk_not(r)); + else + p = m().mk_skolemization(q, r); + } + } + +public: + skolemizer(ast_manager & m, params_ref const & p): + m_manager(m), + m_sk_hack("sk_hack"), + m_cache(m), + m_cache_pr(m) { + updt_params(p); + } + + void updt_params(params_ref const & p) { + m_sk_hack_enabled = p.get_bool(":nnf-sk-hack", false); + } + + static void get_param_descrs(param_descrs & r) { + r.insert(":nnf-sk-hack", CPK_BOOL, "(default: false) hack for VCC"); + } + + ast_manager & m() const { return m_manager; } + + void operator()(quantifier * q, expr_ref & r, proof_ref & p) { + r = m_cache.find(q); + if (r.get() != 0) { + p = 0; + if (m().proofs_enabled()) + p = static_cast(m_cache_pr.find(q)); + } + else { + process(q, r, p); + m_cache.insert(q, r); + if (m().proofs_enabled()) + m_cache_pr.insert(q, p); + } + } + + bool is_sk_hack(expr * p) const { + SASSERT(m().is_pattern(p)); + if (to_app(p)->get_num_args() != 1) + return false; + expr * body = to_app(p)->get_arg(0); + if (!is_app(body)) + return false; + func_decl * f = to_app(body)->get_decl(); + if (!(f->get_name() == m_sk_hack && f->get_arity() == 1)) + return false; + if (!m().is_bool(body)) { + warning_msg("sk_hack constant must return a Boolean"); + return false; + } + return true; + } +}; + +typedef default_exception nnf_params_exception; +typedef default_exception nnf_exception; + +struct nnf::imp { + + struct frame { + expr * m_curr; + unsigned m_i:28; + unsigned m_pol:1; // pos/neg polarity + unsigned m_in_q:1; // true if m_curr is nested in a quantifier + unsigned m_new_child:1; + unsigned m_cache_result:1; + unsigned m_spos; // top of the result stack, when the frame was created. + frame(expr * n, bool pol, bool in_q, bool cache_res, unsigned spos): + m_curr(n), + m_i(0), + m_pol(pol), + m_in_q(in_q), + m_new_child(false), + m_cache_result(cache_res), + m_spos(spos) { + } + }; + + // There are four caches: +#define NEG_NQ_CIDX 0 // negative polarity and not nested in a quantifier +#define POS_NQ_CIDX 1 // positive polarity and not nested in a quantifier +#define NEG_Q_CIDX 2 // negative polarity and nested in a quantifier +#define POS_Q_CIDX 3 // positive polarity and nested in a quantifier + + ast_manager & m_manager; + svector m_frame_stack; + expr_ref_vector m_result_stack; + + typedef act_cache cache; + cache * m_cache[4]; + + expr_ref_vector m_todo_defs; + proof_ref_vector m_todo_proofs; + + // proof generation goodness ---- + proof_ref_vector m_result_pr_stack; + cache * m_cache_pr[4]; + // ------------------------------ + + skolemizer m_skolemizer; + + // configuration ---------------- + nnf_mode m_mode; + bool m_ignore_labels; + bool m_skolemize; + // ------------------------------ + + name_exprs * m_name_nested_formulas; + name_exprs * m_name_quant; + + symbol m_skolem; + + volatile bool m_cancel; + unsigned long long m_max_memory; // in bytes + + imp(ast_manager & m, defined_names & n, params_ref const & p): + m_manager(m), + m_result_stack(m), + m_todo_defs(m), + m_todo_proofs(m), + m_result_pr_stack(m), + m_skolemizer(m, p), + m_skolem("skolem"), + m_cancel(false) { + updt_local_params(p); + for (unsigned i = 0; i < 4; i++) { + m_cache[i] = alloc(act_cache, m); + if (m.proofs_enabled()) + m_cache_pr[i] = alloc(act_cache, m); + } + m_name_nested_formulas = mk_nested_formula_namer(m, n); + m_name_quant = mk_quantifier_label_namer(m, n); + } + + ast_manager & m() const { return m_manager; } + + bool proofs_enabled() const { return m().proofs_enabled(); } + + ~imp() { + for (unsigned i = 0; i < 4; i++) { + dealloc(m_cache[i]); + if (proofs_enabled()) + dealloc(m_cache_pr[i]); + } + del_name_exprs(m_name_nested_formulas); + del_name_exprs(m_name_quant); + } + + void updt_params(params_ref const & p) { + updt_local_params(p); + m_skolemizer.updt_params(p); + } + + void updt_local_params(params_ref const & p) { + symbol mode_sym = p.get_sym(":nnf-mode", m_skolem); + if (mode_sym == m_skolem) + m_mode = NNF_SKOLEM; + else if (mode_sym == "full") + m_mode = NNF_FULL; + else if (mode_sym == "quantifiers") + m_mode = NNF_QUANT; + else + throw nnf_params_exception("invalid NNF mode"); + + TRACE("nnf", tout << "nnf-mode: " << m_mode << " " << mode_sym << "\n" << p << "\n";); + + m_ignore_labels = p.get_bool(":nnf-ignore-labels", false); + m_skolemize = p.get_bool(":skolemize", true); + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + } + + static void get_param_descrs(param_descrs & r) { + insert_max_memory(r); + r.insert(":nnf-mode", CPK_SYMBOL, + "(default: skolem) NNF translation mode: skolem (skolem normal form), quantifiers (skolem normal form + quantifiers in NNF), full"); + r.insert(":nnf-ignore-labels", CPK_BOOL, + "(default: false) remove/ignore labels in the input formula, this option is ignored if proofs are enabled"); + r.insert(":skolemize", CPK_BOOL, + "(default: true) skolemize (existential force) quantifiers"); + skolemizer::get_param_descrs(r); + } + + void reset() { + m_frame_stack.reset(); + m_result_stack.reset(); + m_result_pr_stack.reset(); + m_todo_defs.reset(); + m_todo_proofs.reset(); + } + + void reset_cache() { + for (unsigned i = 0; i < 4; i++) { + m_cache[i]->reset(); + if (proofs_enabled()) + m_cache_pr[i]->reset(); + } + } + + void push_frame(expr * t, bool pol, bool in_q, bool cache_res) { + m_frame_stack.push_back(frame(t, pol, in_q, cache_res, m_result_stack.size())); + } + + static unsigned get_cache_idx(bool pol, bool in_q) { + return static_cast(in_q) * 2 + static_cast(pol); + } + + void cache_result(expr * t, bool pol, bool in_q, expr * v, proof * pr) { + unsigned idx = get_cache_idx(pol, in_q); + m_cache[idx]->insert(t, v); + if (proofs_enabled()) + m_cache_pr[idx]->insert(t, pr); + } + + expr * get_cached(expr * t, bool pol, bool in_q) const { + return m_cache[get_cache_idx(pol, in_q)]->find(t); + } + + proof * get_cached_pr(expr * t, bool pol, bool in_q) const { + SASSERT(proofs_enabled()); + return static_cast(m_cache_pr[get_cache_idx(pol, in_q)]->find(t)); + } + + /** + \brief Return true if the result for (t, pol, in_q) is already cached, + and store the result on the stack. + */ + bool process_cached(expr * t, bool pol, bool in_q) { + expr * r = get_cached(t, pol, in_q); + if (r) { + m_result_stack.push_back(r); + if (proofs_enabled()) { + proof * pr = get_cached_pr(t, pol, in_q); + m_result_pr_stack.push_back(pr); + SASSERT(m_result_stack.size() == m_result_pr_stack.size()); + } + m_frame_stack.pop_back(); + set_new_child_flag(t, r); + return true; + } + return false; + } + + void set_cancel(bool f) { + m_cancel = f; + } + + void checkpoint() { + cooperate("nnf"); + if (memory::get_allocation_size() > m_max_memory) + throw nnf_exception(STE_MAX_MEMORY_MSG); + if (m_cancel) + throw nnf_exception(STE_CANCELED_MSG); + } + + void set_new_child_flag() { + if (!m_frame_stack.empty()) + m_frame_stack.back().m_new_child = true; + } + + void set_new_child_flag(expr * old_t, expr * new_t) { + if (old_t != new_t) + set_new_child_flag(); + } + + void skip(expr * t, bool pol) { + expr * r = pol ? t : m().mk_not(t); + m_result_stack.push_back(r); + if (proofs_enabled()) { + m_result_pr_stack.push_back(m().mk_oeq_reflexivity(r)); + SASSERT(m_result_stack.size() == m_result_pr_stack.size()); + } + } + + bool visit(expr * t, bool pol, bool in_q) { + SASSERT(m().is_bool(t)); + + if (m_mode == NNF_SKOLEM || (m_mode == NNF_QUANT && !in_q)) { + if (!has_quantifiers(t) && !has_labels(t)) { + skip(t, pol); + return true; // t does not need to be processed + } + } + + bool cache_res = t->get_ref_count() > 1; + + if (cache_res) { + expr * r = get_cached(t, pol, in_q); + if (r) { + m_result_stack.push_back(r); + set_new_child_flag(t, r); + if (proofs_enabled()) { + proof * pr = get_cached_pr(t, pol, in_q); + m_result_pr_stack.push_back(pr); + SASSERT(m_result_stack.size() == m_result_pr_stack.size()); + } + return true; // t was already processed + } + } + + switch (t->get_kind()) { + case AST_APP: + if (to_app(t)->get_num_args() == 0) { + skip(t, pol); + return true; + } + else { + push_frame(t, pol, in_q, cache_res); + return false; + } + case AST_QUANTIFIER: + push_frame(t, pol, in_q, cache_res); + return false; + case AST_VAR: + skip(t, pol); + return true; + default: + UNREACHABLE(); + return true; + } + } + + proof * mk_proof(bool pol, unsigned num_parents, proof * const * parents, app * old_e, app * new_e) { + if (pol) { + if (old_e->get_decl() == new_e->get_decl()) + return m().mk_oeq_congruence(old_e, new_e, num_parents, parents); + else + return m().mk_nnf_pos(old_e, new_e, num_parents, parents); + } + else + return m().mk_nnf_neg(old_e, new_e, num_parents, parents); + } + + bool process_and_or(app * t, frame & fr) { + unsigned num_args = t->get_num_args(); + while (fr.m_i < num_args) { + expr * arg = t->get_arg(fr.m_i); + fr.m_i++; + if (!visit(arg, fr.m_pol, fr.m_in_q)) + return false; + } + app * r; + if (m().is_and(t) == fr.m_pol) + r = m().mk_and(t->get_num_args(), m_result_stack.c_ptr() + fr.m_spos); + else + r = m().mk_or(t->get_num_args(), m_result_stack.c_ptr() + fr.m_spos); + + m_result_stack.shrink(fr.m_spos); + m_result_stack.push_back(r); + if (proofs_enabled()) { + proof * pr = mk_proof(fr.m_pol, t->get_num_args(), m_result_pr_stack.c_ptr() + fr.m_spos, t, r); + m_result_pr_stack.shrink(fr.m_spos); + m_result_pr_stack.push_back(pr); + SASSERT(m_result_stack.size() == m_result_pr_stack.size()); + } + return true; + } + + bool process_not(app * t, frame & fr) { + if (fr.m_i == 0) { + fr.m_i = 1; + if (!visit(t->get_arg(0), !fr.m_pol, fr.m_in_q)) + return false; + } + expr * r = m_result_stack.back(); + proof * pr = 0; + if (proofs_enabled()) { + pr = m_result_pr_stack.back(); + if (!fr.m_pol) { + pr = m().mk_nnf_neg(t, r, 1, &pr); + m_result_pr_stack.pop_back(); + m_result_pr_stack.push_back(pr); + SASSERT(m_result_stack.size() == m_result_pr_stack.size()); + } + } + return true; + } + + bool process_implies(app * t, frame & fr) { + SASSERT(t->get_num_args() == 2); + switch (fr.m_i) { + case 0: + fr.m_i = 1; + if (!visit(t->get_arg(0), !fr.m_pol, fr.m_in_q)) + return false; + case 1: + fr.m_i = 2; + if (!visit(t->get_arg(1), fr.m_pol, fr.m_in_q)) + return false; + default: + break; + } + + app * r; + if (fr.m_pol) + r = m().mk_or(2, m_result_stack.c_ptr() + fr.m_spos); + else + r = m().mk_and(2, m_result_stack.c_ptr() + fr.m_spos); + + m_result_stack.shrink(fr.m_spos); + m_result_stack.push_back(r); + if (proofs_enabled()) { + proof * pr = mk_proof(fr.m_pol, 2, m_result_pr_stack.c_ptr() + fr.m_spos, t, r); + m_result_pr_stack.shrink(fr.m_spos); + m_result_pr_stack.push_back(pr); + SASSERT(m_result_stack.size() == m_result_pr_stack.size()); + } + return true; + } + + bool process_ite(app * t, frame & fr) { + SASSERT(t->get_num_args() == 3); + switch (fr.m_i) { + case 0: + fr.m_i = 1; + if (!visit(t->get_arg(0), true, fr.m_in_q)) + return false; + case 1: + fr.m_i = 2; + if (!visit(t->get_arg(0), false, fr.m_in_q)) + return false; + case 2: + fr.m_i = 3; + if (!visit(t->get_arg(1), fr.m_pol, fr.m_in_q)) + return false; + case 3: + fr.m_i = 4; + if (!visit(t->get_arg(2), fr.m_pol, fr.m_in_q)) + return false; + default: + break; + } + + expr * const * rs = m_result_stack.c_ptr() + fr.m_spos; + expr * _cond = rs[0]; + expr * _not_cond = rs[1]; + expr * _then = rs[2]; + expr * _else = rs[3]; + + app * r = m().mk_and(m().mk_or(_not_cond, _then), m().mk_or(_cond, _else)); + m_result_stack.shrink(fr.m_spos); + m_result_stack.push_back(r); + if (proofs_enabled()) { + proof * pr = mk_proof(fr.m_pol, 4, m_result_pr_stack.c_ptr() + fr.m_spos, t, r); + m_result_pr_stack.shrink(fr.m_spos); + m_result_pr_stack.push_back(pr); + SASSERT(m_result_stack.size() == m_result_pr_stack.size()); + } + return true; + } + + bool is_eq(app * t) const { return m().is_eq(t) || m().is_iff(t); } + + bool process_iff_xor(app * t, frame & fr) { + SASSERT(t->get_num_args() == 2); + switch (fr.m_i) { + case 0: + fr.m_i = 1; + if (!visit(t->get_arg(0), true, fr.m_in_q)) + return false; + case 1: + fr.m_i = 2; + if (!visit(t->get_arg(0), false, fr.m_in_q)) + return false; + case 2: + fr.m_i = 3; + if (!visit(t->get_arg(1), true, fr.m_in_q)) + return false; + case 3: + fr.m_i = 4; + if (!visit(t->get_arg(1), false, fr.m_in_q)) + return false; + default: + break; + } + + expr * const * rs = m_result_stack.c_ptr() + fr.m_spos; + expr * lhs = rs[0]; + expr * not_lhs = rs[1]; + expr * rhs = rs[2]; + expr * not_rhs = rs[3]; + + app * r; + if (is_eq(t) == fr.m_pol) + r = m().mk_and(m().mk_or(not_lhs, rhs), m().mk_or(lhs, not_rhs)); + else + r = m().mk_and(m().mk_or(lhs, rhs), m().mk_or(not_lhs, not_rhs)); + m_result_stack.shrink(fr.m_spos); + m_result_stack.push_back(r); + if (proofs_enabled()) { + proof * pr = mk_proof(fr.m_pol, 4, m_result_pr_stack.c_ptr() + fr.m_spos, t, r); + m_result_pr_stack.shrink(fr.m_spos); + m_result_pr_stack.push_back(pr); + SASSERT(m_result_stack.size() == m_result_pr_stack.size()); + } + return true; + } + + bool process_eq(app * t, frame & fr) { + if (m().is_bool(t->get_arg(0))) + return process_iff_xor(t, fr); + else + return process_default(t, fr); + } + + bool process_default(app * t, frame & fr) { + SASSERT(fr.m_i == 0); + if (m_mode == NNF_FULL || t->has_quantifiers() || t->has_labels()) { + expr_ref n2(m()); + proof_ref pr2(m()); + if (m_mode == NNF_FULL || (m_mode != NNF_SKOLEM && fr.m_in_q)) + m_name_nested_formulas->operator()(t, m_todo_defs, m_todo_proofs, n2, pr2); + else + m_name_quant->operator()(t, m_todo_defs, m_todo_proofs, n2, pr2); + + if (!fr.m_pol) + n2 = m().mk_not(n2); + + m_result_stack.push_back(n2); + if (proofs_enabled()) { + if (!fr.m_pol) { + proof * prs[1] = { pr2 }; + pr2 = m().mk_oeq_congruence(m().mk_not(t), static_cast(n2.get()), 1, prs); + } + m_result_pr_stack.push_back(pr2); + SASSERT(m_result_stack.size() == m_result_pr_stack.size()); + } + } + else { + skip(t, fr.m_pol); + } + return true; + } + + bool process_label(app * t, frame & fr) { + if (fr.m_i == 0) { + fr.m_i = 1; + if (!visit(t->get_arg(0), fr.m_pol, fr.m_in_q)) + return false; + } + + expr * arg = m_result_stack.back(); + proof * arg_pr = proofs_enabled() ? m_result_pr_stack.back() : 0; + + if (m_ignore_labels && !proofs_enabled()) + return true; // the result is already on the stack + + + buffer names; + bool pos; + m().is_label(t, pos, names); + expr_ref r(m()); + proof_ref pr(m()); + if (fr.m_pol == pos) { + expr * lbl_lit = m().mk_label_lit(names.size(), names.c_ptr()); + r = m().mk_and(arg, lbl_lit); + if (proofs_enabled()) { + expr_ref aux(m_manager); + aux = m().mk_label(true, names.size(), names.c_ptr(), arg); + pr = m().mk_transitivity(mk_proof(fr.m_pol, 1, &arg_pr, t, to_app(aux)), + m().mk_iff_oeq(m().mk_rewrite(aux, r))); + } + } + else { + r = arg; + if (proofs_enabled()) { + proof * p1 = m().mk_iff_oeq(m().mk_rewrite(t, t->get_arg(0))); + pr = m().mk_transitivity(p1, arg_pr); + } + } + + m_result_stack.pop_back(); + m_result_stack.push_back(r); + if (proofs_enabled()) { + m_result_pr_stack.pop_back(); + m_result_pr_stack.push_back(pr); + SASSERT(m_result_stack.size() == m_result_pr_stack.size()); + } + return true; + } + + bool process_app(app * t, frame & fr) { + SASSERT(m().is_bool(t)); + if (t->get_family_id() == m().get_basic_family_id()) { + switch (static_cast(t->get_decl_kind())) { + case OP_AND: case OP_OR: + return process_and_or(t, fr); + case OP_NOT: + return process_not(t, fr); + case OP_IMPLIES: + return process_implies(t, fr); + case OP_ITE: + return process_ite(t, fr); + case OP_IFF: + case OP_XOR: + return process_iff_xor(t, fr); + case OP_EQ: + return process_eq(t, fr); + default: + break; + } + } + + if (m().is_label(t)) { + return process_label(t, fr); + } + + return process_default(t, fr); + } + + bool process_var(var * v, frame & fr) { + skip(v, fr.m_pol); + return true; + } + + bool process_quantifier(quantifier * q, frame & fr) { + expr_ref r(m()); + proof_ref pr(m()); + if (fr.m_i == 0) { + fr.m_i = 1; + if (q->is_forall() == fr.m_pol || !m_skolemize) { + if (!visit(q->get_expr(), fr.m_pol, true)) + return false; + } + else { + m_skolemizer(q, r, pr); + if (!visit(r, !q->is_forall(), fr.m_in_q)) + return false; + } + } + + if (q->is_forall() == fr.m_pol || !m_skolemize) { + expr * new_expr = m_result_stack.back(); + proof * new_expr_pr = proofs_enabled() ? m_result_pr_stack.back() : 0; + + ptr_buffer new_patterns; + + if (q->is_forall() == fr.m_pol) { + // collect non sk_hack patterns + unsigned num_patterns = q->get_num_patterns(); + for (unsigned i = 0; i < num_patterns; i++) { + expr * pat = q->get_pattern(i); + if (!m_skolemizer.is_sk_hack(pat)) + new_patterns.push_back(pat); + } + } + else { + // New quantifier has existential force. + // So, ignore patterns + } + + quantifier * new_q = 0; + proof * new_q_pr = 0; + if (fr.m_pol) { + new_q = m().update_quantifier(q, new_patterns.size(), new_patterns.c_ptr(), new_expr); + if (proofs_enabled()) + new_q_pr = m().mk_nnf_pos(q, new_q, 1, &new_expr_pr); + } + else { + new_q = m().update_quantifier(q, !q->is_forall(), new_patterns.size(), new_patterns.c_ptr(), new_expr); + if (proofs_enabled()) + new_q_pr = m().mk_nnf_neg(q, new_q, 1, &new_expr_pr); + } + + m_result_stack.pop_back(); + m_result_stack.push_back(new_q); + if (proofs_enabled()) { + m_result_pr_stack.pop_back(); + m_result_pr_stack.push_back(new_q_pr); + SASSERT(m_result_stack.size() == m_result_pr_stack.size()); + } + } + else { + // Quantifier was skolemized. + // The result is already on the stack. + // However, the proof must be updated + if (proofs_enabled()) { + m_skolemizer(q, r, pr); // retrieve the proof + pr = m().mk_transitivity(pr, m_result_pr_stack.back()); + m_result_pr_stack.pop_back(); + m_result_pr_stack.push_back(pr); + SASSERT(m_result_stack.size() == m_result_pr_stack.size()); + } + } + return true; + } + + void recover_result(expr * t, expr_ref & result, proof_ref & result_pr) { + // recover result from the top of the stack. + result = m_result_stack.back(); + m_result_stack.pop_back(); + SASSERT(m_result_stack.empty()); + if (proofs_enabled()) { + result_pr = m_result_pr_stack.back(); + m_result_pr_stack.pop_back(); + if (result_pr.get() == 0) + result_pr = m().mk_reflexivity(t); + SASSERT(m_result_pr_stack.empty()); + } + } + + void process(expr * t, expr_ref & result, proof_ref & result_pr) { + TRACE("nnf", tout << "processing:\n" << mk_ismt2_pp(t, m()) << "\n";); + SASSERT(m().is_bool(t)); + + if (visit(t, true /* positive polarity */, false /* not nested in quantifier */)) { + recover_result(t, result, result_pr); + return; + } + SASSERT(!m_frame_stack.empty()); + while (!m_frame_stack.empty()) { + checkpoint(); + frame & fr = m_frame_stack.back(); + expr * t = fr.m_curr; + + if (fr.m_i == 0 && t->get_ref_count() > 1 && process_cached(t, fr.m_pol, fr.m_in_q)) + continue; + + bool status; + + switch (t->get_kind()) { + case AST_APP: + status = process_app(to_app(t), fr); + break; + case AST_QUANTIFIER: + status = process_quantifier(to_quantifier(t), fr); + break; + case AST_VAR: + status = process_var(to_var(t), fr); + break; + default: + UNREACHABLE(); + status = true; + break; + } + + if (status) { + if (fr.m_cache_result) + cache_result(fr.m_curr, fr.m_pol, fr.m_in_q, m_result_stack.back(), proofs_enabled() ? m_result_pr_stack.back() : 0); + m_frame_stack.pop_back(); + } + } + recover_result(t, result, result_pr); + } + + void operator()(expr * n, expr_ref_vector & new_defs, proof_ref_vector & new_def_proofs, expr_ref & r, proof_ref & pr) { + reset(); + process(n, r, pr); + unsigned old_sz1 = new_defs.size(); + unsigned old_sz2 = new_def_proofs.size(); + + for (unsigned i = 0; i < m_todo_defs.size(); i++) { + expr_ref dr(m()); + proof_ref dpr(m()); + process(m_todo_defs.get(i), dr, dpr); + new_defs.push_back(dr); + if (proofs_enabled()) { + proof * new_pr = m().mk_modus_ponens(m_todo_proofs.get(i), dpr); + new_def_proofs.push_back(new_pr); + } + } + std::reverse(new_defs.c_ptr() + old_sz1, new_defs.c_ptr() + new_defs.size()); + std::reverse(new_def_proofs.c_ptr() + old_sz2, new_def_proofs.c_ptr() + new_def_proofs.size()); + } +}; + + +nnf::nnf(ast_manager & m, defined_names & n, params_ref const & p) { + TRACE("nnf", tout << "nnf constructor: " << p << "\n";); + m_imp = alloc(imp, m, n, p); +} + +nnf::nnf(ast_manager & m, defined_names & n, nnf_params & np) { + params_ref p; + if (np.m_nnf_mode == NNF_FULL) + p.set_sym(":nnf-mode", symbol("full")); + else if (np.m_nnf_mode == NNF_QUANT) + p.set_sym(":nnf-mode", symbol("quantifiers")); + + if (np.m_nnf_ignore_labels) + p.set_bool(":nnf-ignore-labels", true); + + if (np.m_nnf_sk_hack) + p.set_bool(":nnf-sk-hack", true); + m_imp = alloc(imp, m, n, p); +} + +nnf::~nnf() { + dealloc(m_imp); +} + +void nnf::operator()(expr * n, expr_ref_vector & new_defs, proof_ref_vector & new_def_proofs, expr_ref & r, proof_ref & p) { + m_imp->operator()(n, new_defs, new_def_proofs, r, p); + TRACE("nnf_result", tout << mk_ismt2_pp(n, m_imp->m()) << "\nNNF result:\n" << mk_ismt2_pp(r, m_imp->m()) << "\n";); +} + +void nnf::updt_params(params_ref const & p) { + m_imp->updt_params(p); +} + +void nnf::get_param_descrs(param_descrs & r) { + imp::get_param_descrs(r); +} + +void nnf::set_cancel(bool f) { + m_imp->set_cancel(f); +} + +void nnf::reset() { + m_imp->reset(); +} + +void nnf::reset_cache() { + m_imp->reset_cache(); +} + +// TODO: delete after conversion to new tactic framework is done. +class nnf_strategy : public assertion_set_strategy { + params_ref m_params; + nnf * m_nnf; + + struct set_nnf { + nnf_strategy & m_owner; + + set_nnf(nnf_strategy & owner, nnf & n): + m_owner(owner) { + #pragma omp critical (nnf_strategy) + { + m_owner.m_nnf = &n; + } + } + + ~set_nnf() { + #pragma omp critical (nnf_strategy) + { + m_owner.m_nnf = 0; + } + } + }; +public: + nnf_strategy(params_ref const & p): + m_params(p), + m_nnf(0) { + TRACE("nnf", tout << "nnf_strategy constructor: " << p << "\n";); + } + + virtual ~nnf_strategy() {} + + virtual void updt_params(params_ref const & p) { m_params = p; } + static void get_param_descrs(param_descrs & r) { nnf::get_param_descrs(r); } + virtual void collect_param_descrs(param_descrs & r) { get_param_descrs(r); } + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + TRACE("nnf", tout << "params: " << m_params << "\n"; s.display(tout);); + SASSERT(is_well_sorted(s)); + as_st_report report("nnf", s); + mc = 0; + if (s.inconsistent()) + return; + ast_manager & m = s.m(); + defined_names dnames(m); + nnf local_nnf(m, dnames, m_params); + set_nnf setter(*this, local_nnf); + + expr_ref_vector defs(m); + proof_ref_vector def_prs(m); + + expr_ref new_curr(m); + proof_ref new_pr(m); + + unsigned sz = s.size(); + for (unsigned i = 0; i < sz; i++) { + expr * curr = s.form(i); + local_nnf(curr, defs, def_prs, new_curr, new_pr); + if (m.proofs_enabled()) { + proof * pr = s.pr(i); + new_pr = m.mk_modus_ponens(pr, new_pr); + } + s.update(i, new_curr, new_pr); + } + + sz = defs.size(); + for (unsigned i = 0; i < sz; i++) { + if (m.proofs_enabled()) + s.assert_expr(defs.get(i), def_prs.get(i)); + else + s.assert_expr(defs.get(i), 0); + } + TRACE("nnf", s.display(tout);); + SASSERT(is_well_sorted(s)); + } + + virtual void cleanup() {} + virtual void set_cancel(bool f) { + #pragma omp critical (nnf_strategy) + { + if (m_nnf) + m_nnf->set_cancel(f); + } + } +}; + +as_st * mk_snf(params_ref const & p) { + return alloc(nnf_strategy, p); +} + +as_st * mk_nnf(params_ref const & p) { + params_ref new_p(p); + new_p.set_sym(":nnf-mode", symbol("full")); + TRACE("nnf", tout << "mk_nnf: " << new_p << "\n";); + return using_params(mk_snf(), new_p); +} + diff --git a/lib/nnf.h b/lib/nnf.h new file mode 100644 index 000000000..10f6ba128 --- /dev/null +++ b/lib/nnf.h @@ -0,0 +1,68 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + nnf.h + +Abstract: + + Negation Normal Form & Skolemization + +Author: + + Leonardo (leonardo) 2008-01-11 + +Notes: + Major revision on 2011-10-06 + +--*/ +#ifndef _NNF_H_ +#define _NNF_H_ + +#include"ast.h" +#include"nnf_params.h" +#include"params.h" +#include"defined_names.h" + +class nnf { + struct imp; + imp * m_imp; +public: + nnf(ast_manager & m, defined_names & n, params_ref const & p = params_ref()); + nnf(ast_manager & m, defined_names & n, nnf_params & params); // for backward compatibility + ~nnf(); + + void operator()(expr * n, // [IN] expression that should be put into NNF + expr_ref_vector & new_defs, // [OUT] new definitions + proof_ref_vector & new_def_proofs, // [OUT] proofs of the new definitions + expr_ref & r, // [OUT] resultant expression + proof_ref & p // [OUT] proof for (~ n r) + ); + + void updt_params(params_ref const & p); + static void get_param_descrs(param_descrs & r); + + void cancel() { set_cancel(true); } + void reset_cancel() { set_cancel(false); } + void set_cancel(bool f); + + void reset(); + void reset_cache(); +}; + +// Old strategy framework +class assertion_set_strategy; +// Skolem Normal Form +assertion_set_strategy * mk_snf(params_ref const & p = params_ref()); +// Negation Normal Form +assertion_set_strategy * mk_nnf(params_ref const & p = params_ref()); + +// New strategy framework +class tactic; +// Skolem Normal Form +tactic * mk_snf_tactic(ast_manager & m, params_ref const & p = params_ref()); +// Negation Normal Form +tactic * mk_nnf_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif /* _NNF_H_ */ diff --git a/lib/nnf_params.cpp b/lib/nnf_params.cpp new file mode 100644 index 000000000..044ca155e --- /dev/null +++ b/lib/nnf_params.cpp @@ -0,0 +1,26 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + nnf_params.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-14. + +Revision History: + +--*/ +#include"nnf_params.h" + +void nnf_params::register_params(ini_params & p) { + p.register_unsigned_param("NNF_FACTOR", m_nnf_factor, "the maximum growth factor during NNF translation (auxiliary definitions are introduced if the threshold is reached)"); + p.register_int_param("NNF_MODE", 0, 3, reinterpret_cast(m_nnf_mode), "NNF translation mode: 0 - skolem normal form, 1 - 0 + quantifiers in NNF, 2 - 1 + opportunistic, 3 - full"); + p.register_bool_param("NNF_IGNORE_LABELS", m_nnf_ignore_labels, "remove/ignore labels in the input formula, this option is ignored if proofs are enabled"); + p.register_bool_param("NNF_SK_HACK", m_nnf_sk_hack, "hack for VCC"); +} diff --git a/lib/nnf_params.h b/lib/nnf_params.h new file mode 100644 index 000000000..f759a93ac --- /dev/null +++ b/lib/nnf_params.h @@ -0,0 +1,74 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + nnf_params.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-14. + +Revision History: + +--*/ +#ifndef _NNF_PARAMS_H_ +#define _NNF_PARAMS_H_ + +#include"ini_file.h" + +/** + \brief NNF translation mode. The cheapest mode is NNF_SKOLEM, and + the most expensive is NNF_FULL. +*/ +enum nnf_mode { + NNF_SKOLEM, /* A subformula is put into NNF only if it contains + quantifiers or labels. The result of the + transformation will be in skolem normal form. + If a formula is too expensive to be put into NNF, + then nested quantifiers and labels are renamed. + + This mode is sufficient when using E-matching. + */ + NNF_QUANT, /* A subformula is put into NNF if it contains + quantifiers, labels, or is in the scope of a + quantifier. The result of the transformation will be + in skolem normal form, and the body of quantifiers + will be in NNF. If a ground formula is too expensive to + be put into NNF, then nested quantifiers and labels + are renamed. + + This mode is sufficient when using Superposition + Calculus. + + Remark: If the problem does not contain quantifiers, + then NNF_QUANT is identical to NNF_SKOLEM. + */ + NNF_OPPORTUNISTIC, /* Similar to NNF_QUANT, but a subformula is + also put into NNF, if it is + cheap. Otherwise, the nested quantifiers and + labels are renamed. */ + NNF_FULL /* Everything is put into NNF. */ +}; + +struct nnf_params { + nnf_mode m_nnf_mode; + unsigned m_nnf_factor; + bool m_nnf_ignore_labels; + bool m_nnf_sk_hack; + nnf_params(): + m_nnf_mode(NNF_SKOLEM), + m_nnf_factor(4), + m_nnf_ignore_labels(false), + m_nnf_sk_hack(false) { + } + + void register_params(ini_params & p); +}; + +#endif /* _NNF_PARAMS_H_ */ + diff --git a/lib/nnf_tactic.cpp b/lib/nnf_tactic.cpp new file mode 100644 index 000000000..157c7078e --- /dev/null +++ b/lib/nnf_tactic.cpp @@ -0,0 +1,126 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + nnf_tactic.cpp + +Abstract: + + NNF tactic + +Author: + + Leonardo de Moura (leonardo) 2011-12-28. + +Revision History: + +--*/ +#include"nnf.h" +#include"tactical.h" + +class nnf_tactic : public tactic { + params_ref m_params; + nnf * m_nnf; + + struct set_nnf { + nnf_tactic & m_owner; + + set_nnf(nnf_tactic & owner, nnf & n): + m_owner(owner) { + #pragma omp critical (nnf_tactic) + { + m_owner.m_nnf = &n; + } + } + + ~set_nnf() { + #pragma omp critical (nnf_tactic) + { + m_owner.m_nnf = 0; + } + } + }; +public: + nnf_tactic(params_ref const & p): + m_params(p), + m_nnf(0) { + TRACE("nnf", tout << "nnf_tactic constructor: " << p << "\n";); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(nnf_tactic, m_params); + } + + virtual ~nnf_tactic() {} + + virtual void updt_params(params_ref const & p) { m_params = p; } + + virtual void collect_param_descrs(param_descrs & r) { nnf::get_param_descrs(r); } + + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + TRACE("nnf", tout << "params: " << m_params << "\n"; g->display(tout);); + SASSERT(g->is_well_sorted()); + mc = 0; pc = 0; core = 0; + tactic_report report("nnf", *g); + bool produce_proofs = g->proofs_enabled(); + + ast_manager & m = g->m(); + defined_names dnames(m); + nnf local_nnf(m, dnames, m_params); + set_nnf setter(*this, local_nnf); + + expr_ref_vector defs(m); + proof_ref_vector def_prs(m); + + expr_ref new_curr(m); + proof_ref new_pr(m); + + unsigned sz = g->size(); + for (unsigned i = 0; i < sz; i++) { + expr * curr = g->form(i); + local_nnf(curr, defs, def_prs, new_curr, new_pr); + if (produce_proofs) { + proof * pr = g->pr(i); + new_pr = m.mk_modus_ponens(pr, new_pr); + } + g->update(i, new_curr, new_pr, g->dep(i)); + } + + sz = defs.size(); + for (unsigned i = 0; i < sz; i++) { + if (produce_proofs) + g->assert_expr(defs.get(i), def_prs.get(i), 0); + else + g->assert_expr(defs.get(i), 0, 0); + } + g->inc_depth(); + result.push_back(g.get()); + TRACE("nnf", g->display(tout);); + SASSERT(g->is_well_sorted()); + } + + virtual void cleanup() {} + virtual void set_cancel(bool f) { + #pragma omp critical (nnf_tactic) + { + if (m_nnf) + m_nnf->set_cancel(f); + } + } +}; + +tactic * mk_snf_tactic(ast_manager & m, params_ref const & p) { + return alloc(nnf_tactic, p); +} + +tactic * mk_nnf_tactic(ast_manager & m, params_ref const & p) { + params_ref new_p(p); + new_p.set_sym(":nnf-mode", symbol("full")); + TRACE("nnf", tout << "mk_nnf: " << new_p << "\n";); + return using_params(mk_snf_tactic(m, p), new_p); +} diff --git a/lib/nnf_tactic.h b/lib/nnf_tactic.h new file mode 100644 index 000000000..573031af3 --- /dev/null +++ b/lib/nnf_tactic.h @@ -0,0 +1,30 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + nnf_tactic.h + +Abstract: + + NNF tactic + +Author: + + Leonardo de Moura (leonardo) 2011-12-28. + +Revision History: + +--*/ +#ifndef _NNF_TACTIC_H_ +#define _NNF_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_snf_tactic(ast_manager & m, params_ref const & p = params_ref()); +tactic * mk_nnf_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif + diff --git a/lib/normalize_bounds_tactic.cpp b/lib/normalize_bounds_tactic.cpp new file mode 100644 index 000000000..5a9310ff0 --- /dev/null +++ b/lib/normalize_bounds_tactic.cpp @@ -0,0 +1,211 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + normalize_bounds_tactic.cpp + +Abstract: + + Replace x with x' + l, when l <= x + where x' is a fresh variable. + Note that, after the transformation 0 <= x'. + +Author: + + Leonardo de Moura (leonardo) 2011-10-21. + +Revision History: + +--*/ +#include"tactical.h" +#include"bound_manager.h" +#include"th_rewriter.h" +#include"extension_model_converter.h" +#include"filter_model_converter.h" +#include"arith_decl_plugin.h" +#include"expr_substitution.h" +#include"ast_smt2_pp.h" + +class normalize_bounds_tactic : public tactic { + struct imp { + ast_manager & m; + bound_manager m_bm; + arith_util m_util; + th_rewriter m_rw; + bool m_normalize_int_only; + + imp(ast_manager & _m, params_ref const & p): + m(_m), + m_bm(m), + m_util(m), + m_rw(m, p) { + updt_params(p); + } + + void updt_params_core(params_ref const & p) { + m_normalize_int_only = p.get_bool(":norm-int-only", true); + } + + void updt_params(params_ref const & p) { + m_rw.updt_params(p); + updt_params_core(p); + } + + void set_cancel(bool f) { + m_rw.set_cancel(f); + } + + bool is_target(expr * var, rational & val) { + bool strict; + return + is_uninterp_const(var) && + (!m_normalize_int_only || m_util.is_int(var)) && + m_bm.has_lower(var, val, strict) && + !val.is_zero(); + } + + bool is_target(expr * var) { + rational val; + return is_target(var, val); + } + + bool has_lowers() { + bound_manager::iterator it = m_bm.begin(); + bound_manager::iterator end = m_bm.end(); + for (; it != end; ++it) { + TRACE("normalize_bounds_tactic", + rational val; bool strict; + tout << mk_ismt2_pp(*it, m) << " has_lower: " << m_bm.has_lower(*it, val, strict) << " val: " << val << "\n";); + if (is_target(*it)) + return true; + } + return false; + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + mc = 0; pc = 0; core = 0; + bool produce_models = in->models_enabled(); + bool produce_proofs = in->proofs_enabled(); + tactic_report report("normalize-bounds", *in); + + m_bm(*in); + + if (!has_lowers()) { + result.push_back(in.get()); + // did not increase depth since it didn't do anything. + return; + } + + extension_model_converter * mc1 = 0; + filter_model_converter * mc2 = 0; + if (produce_models) { + mc1 = alloc(extension_model_converter, m); + mc2 = alloc(filter_model_converter, m); + mc = concat(mc2, mc1); + } + + unsigned num_norm_bounds = 0; + expr_substitution subst(m); + rational val; + bound_manager::iterator it = m_bm.begin(); + bound_manager::iterator end = m_bm.end(); + for (; it != end; ++it) { + expr * x = *it; + if (is_target(x, val)) { + num_norm_bounds++; + sort * s = m.get_sort(x); + app * x_prime = m.mk_fresh_const(0, s); + expr * def = m_util.mk_add(x_prime, m_util.mk_numeral(val, s)); + subst.insert(x, def); + if (produce_models) { + mc1->insert(to_app(x)->get_decl(), def); + mc2->insert(x_prime->get_decl()); + } + } + } + + report_tactic_progress(":normalized-bounds", num_norm_bounds); + + m_rw.set_substitution(&subst); + expr_ref new_curr(m); + proof_ref new_pr(m); + unsigned size = in->size(); + for (unsigned idx = 0; idx < size; idx++) { + expr * curr = in->form(idx); + m_rw(curr, new_curr, new_pr); + if (produce_proofs) { + proof * pr = in->pr(idx); + new_pr = m.mk_modus_ponens(pr, new_pr); + } + in->update(idx, new_curr, new_pr, in->dep(idx)); + } + TRACE("normalize_bounds_tactic", in->display(tout);); + in->inc_depth(); + result.push_back(in.get()); + } + }; + + imp * m_imp; + params_ref m_params; + +public: + normalize_bounds_tactic(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(normalize_bounds_tactic, m, m_params); + } + + virtual ~normalize_bounds_tactic() { + dealloc(m_imp); + } + + virtual void updt_params(params_ref const & p) { + m_imp->updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + insert_produce_models(r); + r.insert(":norm-int-only", CPK_BOOL, "(default: true) normalize only the bounds of integer constants."); + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + (*m_imp)(in, result, mc, pc, core); + } + + virtual void cleanup() { + ast_manager & m = m_imp->m; + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + d = m_imp; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + } + +protected: + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + +tactic * mk_normalize_bounds_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(normalize_bounds_tactic, m, p)); +} diff --git a/lib/normalize_bounds_tactic.h b/lib/normalize_bounds_tactic.h new file mode 100644 index 000000000..8805a221e --- /dev/null +++ b/lib/normalize_bounds_tactic.h @@ -0,0 +1,30 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + normalize_bounds_tactic.h + +Abstract: + + Replace x with x' + l, when l <= x + where x' is a fresh variable. + Note that, after the transformation 0 <= x'. + +Author: + + Leonardo de Moura (leonardo) 2011-10-21. + +Revision History: + +--*/ +#ifndef _NORMALIZE_BOUNDS_TACTIC_H_ +#define _NORMALIZE_BOUNDS_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_normalize_bounds_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/normalize_vars.cpp b/lib/normalize_vars.cpp new file mode 100644 index 000000000..9f25b71de --- /dev/null +++ b/lib/normalize_vars.cpp @@ -0,0 +1,88 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + normalize_vars.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-16. + +Revision History: + +--*/ +#include"normalize_vars.h" + +expr * normalize_vars::operator()(expr * n) { + SASSERT(m_todo.empty()); + m_todo.push_back(n); + while (!m_todo.empty()) { + n = m_todo.back(); + if (m_cache.contains(n)) { + m_todo.pop_back(); + continue; + } + if (is_var(n)) { + m_todo.pop_back(); + unsigned idx = to_var(n)->get_idx(); + var * new_var = m_map.get(idx, 0); + if (new_var == 0) { + new_var = m_manager.mk_var(m_next_var, to_var(n)->get_sort()); + m_next_var++; + m_new_vars.push_back(new_var); + m_map.setx(idx, new_var, 0); + } + SASSERT(new_var->get_sort() == to_var(n)->get_sort()); + m_cache.insert(n, new_var); + } + else { + SASSERT(is_app(n)); + bool visited = true; + unsigned num_args = to_app(n)->get_num_args(); + unsigned j = num_args; + while (j > 0) { + --j; + expr * child = to_app(n)->get_arg(j); + if (!m_cache.contains(child)) { + m_todo.push_back(child); + visited = false; + } + } + if (visited) { + m_todo.pop_back(); + m_new_children.reset(); + bool modified = false; + for (unsigned i = 0; i < num_args; i++) { + expr * child = to_app(n)->get_arg(i); + expr * new_child = 0; + m_cache.find(child, new_child); + SASSERT(new_child); + if (child != new_child) + modified = true; + m_new_children.push_back(new_child); + } + if (!modified) + m_cache.insert(n, n); + else + m_cache.insert(n, m_manager.mk_app(to_app(n)->get_decl(), m_new_children.size(), m_new_children.c_ptr())); + } + } + } + expr * r = 0; + m_cache.find(n, r); + SASSERT(r); + return r; +} + +void normalize_vars::reset() { + m_cache.reset(); + m_map.reset(); + m_new_vars.reset(); + m_next_var = 0; +} + diff --git a/lib/normalize_vars.h b/lib/normalize_vars.h new file mode 100644 index 000000000..7d2768a54 --- /dev/null +++ b/lib/normalize_vars.h @@ -0,0 +1,47 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + normalize_vars.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-16. + +Revision History: + +--*/ +#ifndef _NORMALIZE_VARS_H_ +#define _NORMALIZE_VARS_H_ + +#include"ast.h" +#include"obj_hashtable.h" + +class normalize_vars { + ast_manager & m_manager; + var_ref_vector m_new_vars; + unsigned m_next_var; + ptr_vector m_map; + typedef obj_map cache; + cache m_cache; + ptr_vector m_todo; + ptr_vector m_new_children; +public: + normalize_vars(ast_manager & m): + m_manager(m), + m_new_vars(m) { + } + + expr * operator()(expr * n); + void reset(); + unsigned get_num_vars() const { return m_new_vars.size(); } + var * const * get_vars() const { return m_new_vars.c_ptr(); } +}; + +#endif /* _NORMALIZE_VARS_H_ */ + diff --git a/lib/nra_tactic.cpp b/lib/nra_tactic.cpp new file mode 100644 index 000000000..623b7e55e --- /dev/null +++ b/lib/nra_tactic.cpp @@ -0,0 +1,47 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + nra_tactic.cpp + +Abstract: + + Tactic for NRA + +Author: + + Leonardo (leonardo) 2012-03-13 + +Notes: + +--*/ +#include"tactical.h" +#include"simplify_tactic.h" +#include"propagate_values_tactic.h" +#include"smt_tactic.h" +#include"nnf_tactic.h" +#include"qe_tactic.h" +#include"qfnra_nlsat_tactic.h" +#include"probe_arith.h" + +tactic * mk_nra_tactic(ast_manager & m, params_ref const& p) { + params_ref p1 = p; + p1.set_uint(":seed", 11); + p1.set_bool(":factor", false); + params_ref p2 = p; + p2.set_uint(":seed", 13); + p2.set_bool(":factor", false); + + return and_then(mk_simplify_tactic(m, p), + mk_nnf_tactic(m, p), + mk_propagate_values_tactic(m, p), + mk_qe_tactic(m, p), + cond(mk_is_qfnra_probe(), + or_else(try_for(mk_qfnra_nlsat_tactic(m, p), 5000), + try_for(mk_qfnra_nlsat_tactic(m, p1), 10000), + mk_qfnra_nlsat_tactic(m, p2)), + mk_smt_tactic(p))); +} + + diff --git a/lib/nra_tactic.h b/lib/nra_tactic.h new file mode 100644 index 000000000..3fc22393b --- /dev/null +++ b/lib/nra_tactic.h @@ -0,0 +1,24 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + nra_tactic.h + +Abstract: + + Tactic for NRA + +Author: + + Leonardo (leonardo) 2012-03-13 + +Notes: + +--*/ +#ifndef _NRA_TACTIC_H_ +#define _NRA_TACTIC_H_ + +tactic * mk_nra_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/num_occurs.cpp b/lib/num_occurs.cpp new file mode 100644 index 000000000..ee4b76258 --- /dev/null +++ b/lib/num_occurs.cpp @@ -0,0 +1,92 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + num_occurs.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-27. + +Revision History: + +--*/ + +#include"num_occurs.h" +#include"assertion_set.h" +#include"goal.h" + +void num_occurs::process(expr * t, expr_fast_mark1 & visited) { + ptr_buffer stack; + +#define VISIT(ARG) { \ + if (!m_ignore_ref_count1 || ARG->get_ref_count() > 1) { \ + obj_map::obj_map_entry * entry = m_num_occurs.insert_if_not_there2(ARG, 0); \ + entry->get_data().m_value++; \ + } \ + if (!visited.is_marked(ARG)) { \ + visited.mark(ARG, true); \ + stack.push_back(ARG); \ + } \ + } + + VISIT(t); + + while (!stack.empty()) { + expr * t = stack.back(); + stack.pop_back(); + unsigned j; + switch (t->get_kind()) { + case AST_APP: + j = to_app(t)->get_num_args(); + while (j > 0) { + --j; + expr * arg = to_app(t)->get_arg(j); + VISIT(arg); + } + break; + case AST_QUANTIFIER: + if (!m_ignore_quantifiers) { + expr * child = to_quantifier(t)->get_expr(); + VISIT(child); + } + break; + default: + break; + } + } +} + +void num_occurs::operator()(expr * t) { + expr_fast_mark1 visited; + process(t, visited); +} + +void num_occurs::operator()(unsigned num, expr * const * ts) { + expr_fast_mark1 visited; + for (unsigned i = 0; i < num; i++) { + process(ts[i], visited); + } +} + +// TODO delete +void num_occurs::operator()(assertion_set const & s) { + expr_fast_mark1 visited; + unsigned sz = s.size(); + for (unsigned i = 0; i < sz; i++) { + process(s.form(i), visited); + } +} + +void num_occurs::operator()(goal const & g) { + expr_fast_mark1 visited; + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + process(g.form(i), visited); + } +} diff --git a/lib/num_occurs.h b/lib/num_occurs.h new file mode 100644 index 000000000..01d160ac5 --- /dev/null +++ b/lib/num_occurs.h @@ -0,0 +1,59 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + num_occurs.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-27. + +Revision History: + +--*/ +#ifndef _NUM_OCCURS_H_ +#define _NUM_OCCURS_H_ + +#include"ast.h" +#include"obj_hashtable.h" + +class assertion_set; // TODO delete +class goal; + +/** + \brief Functor for computing the number of occurrences of each sub-expression in a expression F. +*/ +class num_occurs { + bool m_ignore_ref_count1; + bool m_ignore_quantifiers; + obj_map m_num_occurs; + + void process(expr * t, expr_fast_mark1 & visited); +public: + num_occurs(bool ignore_ref_count1 = false, bool ignore_quantifiers = false): + m_ignore_ref_count1(ignore_ref_count1), + m_ignore_quantifiers(ignore_quantifiers) { + } + + void reset() { m_num_occurs.reset(); } + + void operator()(expr * t); + void operator()(unsigned num, expr * const * ts); + void operator()(assertion_set const & s); // TODO delete + void operator()(goal const & s); + + unsigned get_num_occs(expr * n) const { + unsigned val; + if (m_num_occurs.find(n, val)) + return val; + return 0; + } +}; + +#endif /* _NUM_OCCURS_H_ */ + diff --git a/lib/numeral_buffer.h b/lib/numeral_buffer.h new file mode 100644 index 000000000..236e2f623 --- /dev/null +++ b/lib/numeral_buffer.h @@ -0,0 +1,91 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + numeral_buffer.h + +Abstract: + + Basic buffer for managing big nums. + +Author: + + Leonardo de Moura (leonardo) 2011-06-18. + +Revision History: + +--*/ +#ifndef _NUMERAL_BUFFER_H_ +#define _NUMERAL_BUFFER_H_ + +#include"vector.h" + +template +class numeral_buffer { + NumeralManager & m_manager; + svector m_buffer; +public: + typedef Numeral numeral; + typedef Numeral data; + typedef NumeralManager manager; + + numeral_buffer(NumeralManager & m):m_manager(m) {} + + ~numeral_buffer() { + reset(); + } + + NumeralManager & m() const { return m_manager; } + + unsigned size() const { return m_buffer.size(); } + + bool empty() const { return m_buffer.empty(); } + + void push_back(Numeral const & num) { + m_buffer.push_back(Numeral()); + m().set(m_buffer.back(), num); + } + + void pop_back() { + m().del(m_buffer.back()); + m_buffer.pop_back(); + } + + Numeral & back() { + return m_buffer.back(); + } + + Numeral const & back() const { + return m_buffer.back(); + } + + Numeral const & operator[](unsigned idx) const { + return m_buffer[idx]; + } + + Numeral & operator[](unsigned idx) { + return m_buffer[idx]; + } + + void reset() { + typename vector::iterator it = m_buffer.begin(); + typename vector::iterator end = m_buffer.end(); + for (; it != end; ++it) + m().del(*it); + m_buffer.reset(); + } + + Numeral * c_ptr() { return m_buffer.c_ptr(); } + + void reserve(unsigned sz) { + m_buffer.reserve(sz); + } + + void swap(svector & other) { + m_buffer.swap(other); + } + +}; + +#endif diff --git a/lib/numeral_factory.cpp b/lib/numeral_factory.cpp new file mode 100644 index 000000000..9653322e3 --- /dev/null +++ b/lib/numeral_factory.cpp @@ -0,0 +1,52 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + numeral_factory.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-10-28. + +Revision History: + +--*/ +#include"numeral_factory.h" +#include"ast_pp.h" + +app * arith_factory::mk_value_core(rational const & val, sort * s) { + return m_util.mk_numeral(val, s); +} + +arith_factory::arith_factory(ast_manager & m): + numeral_factory(m, m.get_family_id("arith")), + m_util(m) { +} + +arith_factory::~arith_factory() { +} + +app * arith_factory::mk_value(rational const & val, bool is_int) { + return numeral_factory::mk_value(val, is_int ? m_util.mk_int() : m_util.mk_real()); +} + +bv_factory::bv_factory(ast_manager & m): + numeral_factory(m, m.get_family_id("bv")), + m_util(m) { +} + +bv_factory::~bv_factory() { +} + +app * bv_factory::mk_value_core(rational const & val, sort * s) { + return m_util.mk_numeral(val, s); +} + +app * bv_factory::mk_value(rational const & val, unsigned bv_size) { + return numeral_factory::mk_value(val, m_util.mk_sort(bv_size)); +} diff --git a/lib/numeral_factory.h b/lib/numeral_factory.h new file mode 100644 index 000000000..1c3621db6 --- /dev/null +++ b/lib/numeral_factory.h @@ -0,0 +1,57 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + numeral_factory.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-10-28. + +Revision History: + +--*/ +#ifndef _NUMERAL_FACTORY_H_ +#define _NUMERAL_FACTORY_H_ + +#include"value_factory.h" +#include"arith_decl_plugin.h" +#include"bv_decl_plugin.h" + +class numeral_factory : public simple_factory { +public: + numeral_factory(ast_manager & m, family_id fid):simple_factory(m, fid) {} + virtual ~numeral_factory() {} +}; + +class arith_factory : public numeral_factory { + arith_util m_util; + + virtual app * mk_value_core(rational const & val, sort * s); + +public: + arith_factory(ast_manager & m); + virtual ~arith_factory(); + + app * mk_value(rational const & val, bool is_int); +}; + +class bv_factory : public numeral_factory { + bv_util m_util; + + virtual app * mk_value_core(rational const & val, sort * s); + +public: + bv_factory(ast_manager & m); + virtual ~bv_factory(); + + app * mk_value(rational const & val, unsigned bv_size); +}; + +#endif /* _NUMERAL_FACTORY_H_ */ + diff --git a/lib/obj_hashtable.h b/lib/obj_hashtable.h new file mode 100644 index 000000000..603898af8 --- /dev/null +++ b/lib/obj_hashtable.h @@ -0,0 +1,219 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + obj_hashtable.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-16. + +Revision History: + +--*/ +#ifndef _OBJ_HASHTABLE_H_ +#define _OBJ_HASHTABLE_H_ + +#include"hash.h" +#include"hashtable.h" + + +/** + \brief Special entry for a hashtable of obj pointers (i.e., + objects that have a hash() method). + This entry uses 0x0 and 0x1 to represent HT_FREE and HT_DELETED. +*/ +template +class obj_hash_entry { + T * m_ptr; +public: + typedef T * data; + obj_hash_entry():m_ptr(0) {} + unsigned get_hash() const { return m_ptr->hash(); } + bool is_free() const { return m_ptr == 0; } + bool is_deleted() const { return m_ptr == reinterpret_cast(1); } + bool is_used() const { return m_ptr != reinterpret_cast(0) && m_ptr != reinterpret_cast(1); } + T * get_data() const { return m_ptr; } + T * & get_data() { return m_ptr; } + void set_data(T * d) { m_ptr = d; } + void set_hash(unsigned h) { SASSERT(h == m_ptr->hash()); } + void mark_as_deleted() { m_ptr = reinterpret_cast(1); } + void mark_as_free() { m_ptr = 0; } +}; + +template +class obj_hashtable : public core_hashtable, obj_ptr_hash, ptr_eq > { +public: + obj_hashtable(unsigned initial_capacity = DEFAULT_HASHTABLE_INITIAL_CAPACITY): + core_hashtable, obj_ptr_hash, ptr_eq >(initial_capacity) {} +}; + +template +class obj_map { +public: + struct key_data { + Key * m_key; + Value m_value; + key_data():m_key(0) { + } + key_data(Key * k): + m_key(k) { + } + key_data(Key * k, Value const & v): + m_key(k), + m_value(v) { + } + Value const & get_value() const { return m_value; } + unsigned hash() const { return m_key->hash(); } + bool operator==(key_data const & other) const { return m_key == other.m_key; } + }; + + class obj_map_entry { + key_data m_data; + public: + typedef key_data data; + obj_map_entry() {} + unsigned get_hash() const { return m_data.hash(); } + bool is_free() const { return m_data.m_key == 0; } + bool is_deleted() const { return m_data.m_key == reinterpret_cast(1); } + bool is_used() const { return m_data.m_key != reinterpret_cast(0) && m_data.m_key != reinterpret_cast(1); } + key_data const & get_data() const { return m_data; } + key_data & get_data() { return m_data; } + void set_data(key_data const & d) { m_data = d; } + void set_hash(unsigned h) { SASSERT(h == m_data.hash()); } + void mark_as_deleted() { m_data.m_key = reinterpret_cast(1); } + void mark_as_free() { m_data.m_key = 0; } + }; + + typedef core_hashtable, default_eq > table; + + table m_table; + +public: + obj_map(): + m_table(DEFAULT_HASHTABLE_INITIAL_CAPACITY) {} + + typedef typename table::iterator iterator; + typedef Key key; + typedef Value value; + + void reset() { + m_table.reset(); + } + + void finalize() { + m_table.finalize(); + } + + bool empty() const { + return m_table.empty(); + } + + unsigned size() const { + return m_table.size(); + } + + unsigned capacity() const { + return m_table.capacity(); + } + + iterator begin() const { + return m_table.begin(); + } + + iterator end() const { + return m_table.end(); + } + + void insert(Key * k, Value const & v) { + m_table.insert(key_data(k, v)); + } + + key_data const & insert_if_not_there(Key * k, Value const & v) { + return m_table.insert_if_not_there(key_data(k, v)); + } + + obj_map_entry * insert_if_not_there2(Key * k, Value const & v) { + return m_table.insert_if_not_there2(key_data(k, v)); + } + + obj_map_entry * find_core(Key * k) const { + return m_table.find_core(key_data(k)); + } + + bool find(Key * k, Value & v) const { + obj_map_entry * e = find_core(k); + if (e) { + v = e->get_data().m_value; + } + return (0 != e); + } + + value const & find(key * k) const { + obj_map_entry * e = find_core(k); + SASSERT(e); + return e->get_data().m_value; + } + + value & find(key * k) { + obj_map_entry * e = find_core(k); + SASSERT(e); + return e->get_data().m_value; + } + + iterator find_iterator(Key * k) const { + return m_table.find(key_data(k)); + } + + bool contains(Key * k) const { + return find_core(k) != 0; + } + + void remove(Key * k) { + m_table.remove(key_data(k)); + } + + void erase(Key * k) { + remove(k); + } + + unsigned long long get_num_collision() const { return m_table.get_num_collision(); } + + void swap(obj_map & other) { + m_table.swap(other.m_table); + } +}; + +/** + \brief Reset and deallocate the values stored in a mapping of the form obj_map +*/ +template +void reset_dealloc_values(obj_map & m) { + typename obj_map::iterator it = m.begin(); + typename obj_map::iterator end = m.end(); + for (; it != end; ++it) { + dealloc(it->m_value); + } + m.reset(); +} + +/** + \brief Remove the key k from the mapping m, and delete the value associated with k. +*/ +template +void erase_dealloc_value(obj_map & m, Key * k) { + Value * v = 0; + bool contains = m.find(k, v); + m.erase(k); + if (contains) { + dealloc(v); + } +} + +#endif /* _OBJ_HASHTABLE_H_ */ + diff --git a/lib/obj_mark.h b/lib/obj_mark.h new file mode 100644 index 000000000..739989d9e --- /dev/null +++ b/lib/obj_mark.h @@ -0,0 +1,53 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + obj_mark.h + +Abstract: + + A mapping from object to boolean (for objects that can be mapped to unsigned integers). + +Author: + + Leonardo de Moura (leonardo) 2008-01-02. + +Revision History: + +--*/ +#ifndef _OBJ_MARK_H_ +#define _OBJ_MARK_H_ + +#include"bit_vector.h" + +template +struct default_t2uint { + unsigned operator()(T const & obj) const { return obj.get_id(); } +}; + +template > +class obj_mark { + T2UInt m_proc; + BV m_marks; +public: + obj_mark(T2UInt const & p = T2UInt()):m_proc(p) {} + bool is_marked(T const & obj) const { + unsigned id = m_proc(obj); + return id < m_marks.size() && m_marks.get(id); + } + bool is_marked(T * obj) const { return is_marked(*obj); } + void mark(T const & obj, bool flag) { + unsigned id = m_proc(obj); + if (id >= m_marks.size()) { + m_marks.resize(id+1, 0); + } + m_marks.set(id, flag); + } + void mark(T const * obj, bool flag) { mark(*obj, flag); } + void mark(T const & obj) { mark(obj, true); } + void mark(T const * obj) { mark(obj, true); } + void reset() { m_marks.reset(); } +}; + +#endif /* _OBJ_MARK_H_ */ diff --git a/lib/obj_pair_hashtable.h b/lib/obj_pair_hashtable.h new file mode 100644 index 000000000..47132b14f --- /dev/null +++ b/lib/obj_pair_hashtable.h @@ -0,0 +1,174 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + obj_pair_hashtable.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-19. + +Revision History: + +--*/ +#ifndef _OBJ_PAIR_HASHTABLE_H_ +#define _OBJ_PAIR_HASHTABLE_H_ + +#include"hash.h" +#include"hashtable.h" + +/** + \brief Special entry for a hashtable of pairs of obj pointers (i.e., + objects that have a hash() method). + This entry uses 0x0 and 0x1 to represent HT_FREE and HT_DELETED. +*/ +template +class obj_pair_hash_entry { + unsigned m_hash; // cached hash code + std::pair m_data; + +public: + typedef std::pair data; + obj_pair_hash_entry():m_data(static_cast(0),static_cast(0)) {} + unsigned get_hash() const { return m_hash; } + bool is_free() const { return m_data.first == 0; } + bool is_deleted() const { return m_data.first == reinterpret_cast(1); } + bool is_used() const { return m_data.first != reinterpret_cast(0) && m_data.first != reinterpret_cast(1); } + data const & get_data() const { return m_data; } + data & get_data() { return m_data; } + void set_data(data const d) { m_data = d; } + void set_hash(unsigned h) { m_hash = h; } + void mark_as_deleted() { m_data.first = reinterpret_cast(1); } + void mark_as_free() { m_data.first = 0; } +}; + +template +class obj_pair_hashtable : public core_hashtable, obj_ptr_pair_hash, default_eq > > { +public: + obj_pair_hashtable(unsigned initial_capacity = DEFAULT_HASHTABLE_INITIAL_CAPACITY): + core_hashtable, obj_ptr_pair_hash, default_eq > >(initial_capacity) { + } +}; + +template +class obj_pair_map { +protected: + class entry; +public: + class key_data { + Key1 * m_key1; + Key2 * m_key2; + Value m_value; + unsigned m_hash; + friend class entry; + public: + key_data(): + m_key1(0), + m_key2(0), + m_hash(0) { + } + key_data(Key1 * k1, Key2 * k2): + m_key1(k1), + m_key2(k2) { + m_hash = combine_hash(m_key1->hash(), m_key2->hash()); + } + key_data(Key1 * k1, Key2 * k2, const Value & v): + m_key1(k1), + m_key2(k2), + m_value(v) { + m_hash = combine_hash(m_key1->hash(), m_key2->hash()); + } + unsigned hash() const { return m_hash; } + bool operator==(key_data const & other) const { return m_key1 == other.m_key1 && m_key2 == other.m_key2; } + Key1 * get_key1() const { return m_key1; } + Key2 * get_key2() const { return m_key2; } + Value const & get_value() const { return m_value; } + }; +protected: + class entry { + key_data m_data; + public: + typedef key_data data; + entry() {} + unsigned get_hash() const { return m_data.hash(); } + bool is_free() const { return m_data.m_key1 == 0; } + bool is_deleted() const { return m_data.m_key1 == reinterpret_cast(1); } + bool is_used() const { return m_data.m_key1 != reinterpret_cast(0) && m_data.m_key1 != reinterpret_cast(1); } + key_data const & get_data() const { return m_data; } + key_data & get_data() { return m_data; } + void set_data(key_data const & d) { m_data = d; } + void set_hash(unsigned h) { SASSERT(h == m_data.hash()); } + void mark_as_deleted() { m_data.m_key1 = reinterpret_cast(1); } + void mark_as_free() { m_data.m_key1 = 0; } + }; + + typedef core_hashtable, default_eq > table; + + table m_table; + + entry * find_core(Key1 * k1, Key2 * k2) const { + return m_table.find_core(key_data(k1, k2)); + } + +public: + obj_pair_map(): + m_table(DEFAULT_HASHTABLE_INITIAL_CAPACITY) {} + + typedef typename table::iterator iterator; + + void reset() { + m_table.reset(); + } + + bool empty() const { + return m_table.empty(); + } + + unsigned size() const { + return m_table.size(); + } + + unsigned capacity() const { + return m_table.capacity(); + } + + iterator begin() const { + return m_table.begin(); + } + + iterator end() const { + return m_table.end(); + } + + void insert(Key1 * k1, Key2 * k2, Value const & v) { + m_table.insert(key_data(k1, k2, v)); + } + + key_data const & insert_if_not_there(Key1 * k1, Key2 * k2, Value const & v) { + return m_table.insert_if_not_there(key_data(k1, k2, v)); + } + + bool find(Key1 * k1, Key2 * k2, Value & v) const { + entry * e = find_core(k1, k2); + if (e) { + v = e->get_data().get_value(); + } + return (0 != e); + } + + bool contains(Key1 * k1, Key2 * k2) const { + return find_core(k1, k2) != 0; + } + + void erase(Key1 * k1, Key2 * k2) { + m_table.remove(key_data(k1, k2)); + } +}; + +#endif /* _OBJ_PAIR_HASHTABLE_H_ */ + diff --git a/lib/obj_pair_set.h b/lib/obj_pair_set.h new file mode 100644 index 000000000..2ec6a402f --- /dev/null +++ b/lib/obj_pair_set.h @@ -0,0 +1,50 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + obj_pair_set.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2011-04-19 + +Revision History: + +--*/ +#ifndef _OBJ_PAIR_SET_H_ +#define _OBJ_PAIR_SET_H_ + +#include"chashtable.h" + +template +class obj_pair_set { +public: + typedef std::pair obj_pair; +protected: + struct hash_proc { + unsigned operator()(obj_pair const & p) const { return combine_hash(p.first->hash(), p.second->hash()); } + }; + struct eq_proc { + bool operator()(obj_pair const & p1, obj_pair const & p2) const { return p1 == p2; } + }; + typedef chashtable set; + set m_set; +public: + obj_pair_set() {} + void insert(T1 * t1, T2 * t2) { m_set.insert(obj_pair(t1, t2)); } + void insert(obj_pair const & p) { m_set.insert(p); } + bool insert_if_not_there(T1 * t1, T2 * t2) { return m_set.insert_if_not_there2(obj_pair(t1, t2)); } + bool insert_if_not_there(obj_pair const & p) { return m_set.insert_if_not_there2(p); } + void erase(T1 * t1, T2 * t2) { return m_set.erase(obj_pair(t1, t2)); } + void erase(obj_pair const & p) { return m_set.erase(p); } + bool contains(T1 * t1, T2 * t2) const { return m_set.contains(obj_pair(t1, t2)); } + bool contains(obj_pair const & p) const { return m_set.contains(p); } + void reset() { m_set.reset(); } +}; + +#endif diff --git a/lib/obj_ref.h b/lib/obj_ref.h new file mode 100644 index 000000000..194f1b46f --- /dev/null +++ b/lib/obj_ref.h @@ -0,0 +1,139 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + obj_ref.h + +Abstract: + + Smart pointer. + +Author: + + Leonardo de Moura (leonardo) 2008-01-03. + +Revision History: + +--*/ +#ifndef _OBJ_REF_H_ +#define _OBJ_REF_H_ + +/** + Smart pointer for T objects. + TManager must provide the functions: + - void dec_ref(T * obj) + - void inc_ref(T * obj) +*/ +template +class obj_ref { + T * m_obj; + TManager & m_manager; + + void dec_ref() { if (m_obj) m_manager.dec_ref(m_obj); } + void inc_ref() { if (m_obj) m_manager.inc_ref(m_obj); } + +public: + typedef TManager manager; + + obj_ref(T * n, TManager & m): + m_obj(n), + m_manager(m) { + inc_ref(); + } + + explicit obj_ref(TManager & m): + m_obj(0), + m_manager(m) { + } + + obj_ref(obj_ref const & n): + m_obj(n.m_obj), + m_manager(n.m_manager) { + inc_ref(); + } + + ~obj_ref() { dec_ref(); } + + TManager & get_manager() const { return m_manager; } + + TManager & m() const { return m_manager; } + + T * operator->() const { return m_obj; } + + T * get() const { return m_obj; } + + operator bool() const { return m_obj != 0; } + + bool operator!() const { return m_obj == 0; } + + operator T*() const { return m_obj; } + + T const & operator*() const { return *m_obj; } + + obj_ref & operator=(T * n) { + if (n) { + m_manager.inc_ref(n); + } + dec_ref(); + m_obj = n; + return *this; + } + + obj_ref & operator=(obj_ref & n) { + SASSERT(&m_manager == &n.m_manager); + n.inc_ref(); + dec_ref(); + m_obj = n.m_obj; + return *this; + } + + void reset() { + dec_ref(); + m_obj = 0; + } + + void swap(obj_ref & n) { + std::swap(m_obj, n.m_obj); + } + + /** + \brief Steal ownership without decrementing the reference counter. + */ + T * steal() { + T * r = m_obj; + m_obj = 0; + return r; + } +}; + +template +inline bool operator==(obj_ref const & n1, obj_ref const & n2) { + return n1.get() == n2.get(); +} + +template +inline bool operator==(obj_ref const & n1, obj_ref const & n2) { + return n1.get() == n2.get(); +} + +template +inline bool operator!=(obj_ref const & n1, obj_ref const & n2) { + return n1.get() != n2.get(); +} + +template +inline bool operator!=(obj_ref const & n1, obj_ref const & n2) { + return n1.get() != n2.get(); +} + +template +inline void dec_range_ref(IT const & begin, IT const & end, TManager & m) { + for (IT it = begin; it != end; ++it) { + if (*it) { + m.dec_ref(*it); + } + } +} + +#endif /* _OBJ_REF_H_ */ diff --git a/lib/obj_triple_hashtable.h b/lib/obj_triple_hashtable.h new file mode 100644 index 000000000..6a5361afe --- /dev/null +++ b/lib/obj_triple_hashtable.h @@ -0,0 +1,180 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + obj_triple_hashtable.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-19. + +Revision History: + +--*/ +#ifndef _OBJ_TRIPLE_HASHTABLE_H_ +#define _OBJ_TRIPLE_HASHTABLE_H_ + +#include"hashtable.h" + +/** + \brief Special entry for a hashtable of pairs of obj pointers (i.e., + objects that have a hash() method). + This entry uses 0x0 and 0x1 to represent HT_FREE and HT_DELETED. +*/ + + +template +class obj_triple_hash_entry { + unsigned m_hash; // cached hash code + triple m_data; + +public: + typedef triple data; + obj_triple_hash_entry():m_data(0,0,0) {} + unsigned get_hash() const { return m_hash; } + bool is_free() const { return m_data.first == 0; } + bool is_deleted() const { return m_data.first == reinterpret_cast(1); } + bool is_used() const { return m_data.first != reinterpret_cast(0) && m_data.first != reinterpret_cast(1); } + data const & get_data() const { return m_data; } + data & get_data() { return m_data; } + void set_data(data const d) { m_data = d; } + void set_hash(unsigned h) { m_hash = h; } + void mark_as_deleted() { m_data.first = reinterpret_cast(1); } + void mark_as_free() { m_data.first = 0; } +}; + +template + class obj_triple_hashtable : public core_hashtable, obj_ptr_triple_hash, default_eq > > { +public: + obj_triple_hashtable(unsigned initial_capacity = DEFAULT_HASHTABLE_INITIAL_CAPACITY): + core_hashtable, obj_ptr_triple_hash, default_eq > >(initial_capacity) { + } +}; + +template +class obj_triple_map { +protected: + class entry; +public: + class key_data { + Key1 * m_key1; + Key2 * m_key2; + Key3 * m_key3; + Value m_value; + unsigned m_hash; + friend class entry; + public: + key_data(): + m_key1(0), + m_key2(0), + m_key3(0), + m_hash(0) { + } + key_data(Key1 * k1, Key2 * k2, Key3 * k3): + m_key1(k1), + m_key2(k2), + m_key3(k3){ + m_hash = combine_hash(combine_hash(m_key1->hash(), m_key2->hash()), m_key3->hash()); + } + key_data(Key1 * k1, Key2 * k2, Key3* k3, const Value & v): + m_key1(k1), + m_key2(k2), + m_key3(k3), + m_value(v) { + m_hash = combine_hash(combine_hash(m_key1->hash(), m_key2->hash()), m_key3->hash()); + } + unsigned hash() const { return m_hash; } + bool operator==(key_data const & other) const { return m_key1 == other.m_key1 && m_key2 == other.m_key2 && m_key3 == other.m_key3; } + Key1 * get_key1() const { return m_key1; } + Key2 * get_key2() const { return m_key2; } + Key3 * get_key3() const { return m_key3; } + Value const & get_value() const { return m_value; } + }; +protected: + class entry { + key_data m_data; + public: + typedef key_data data; + entry() {} + unsigned get_hash() const { return m_data.hash(); } + bool is_free() const { return m_data.m_key1 == 0; } + bool is_deleted() const { return m_data.m_key1 == reinterpret_cast(1); } + bool is_used() const { return m_data.m_key1 != reinterpret_cast(0) && m_data.m_key1 != reinterpret_cast(1); } + key_data const & get_data() const { return m_data; } + key_data & get_data() { return m_data; } + void set_data(key_data const & d) { m_data = d; } + void set_hash(unsigned h) { SASSERT(h == m_data.hash()); } + void mark_as_deleted() { m_data.m_key1 = reinterpret_cast(1); } + void mark_as_free() { m_data.m_key1 = 0; } + }; + + typedef core_hashtable, default_eq > table; + + table m_table; + + entry * find_core(Key1 * k1, Key2 * k2, Key3 * k3) const { + return m_table.find_core(key_data(k1, k2, k3)); + } + +public: + obj_triple_map(): + m_table(DEFAULT_HASHTABLE_INITIAL_CAPACITY) {} + + typedef typename table::iterator iterator; + + void reset() { + m_table.reset(); + } + + bool empty() const { + return m_table.empty(); + } + + unsigned size() const { + return m_table.size(); + } + + unsigned capacity() const { + return m_table.capacity(); + } + + iterator begin() const { + return m_table.begin(); + } + + iterator end() const { + return m_table.end(); + } + + void insert(Key1 * k1, Key2 * k2, Key3* k3, Value const & v) { + m_table.insert(key_data(k1, k2, k3, v)); + } + + key_data const & insert_if_not_there(Key1 * k1, Key2 * k2, Key3 * k3, Value const & v) { + return m_table.insert_if_not_there(key_data(k1, k2, k3, v)); + } + + bool find(Key1 * k1, Key2 * k2,Key3 * k3, Value & v) const { + entry * e = find_core(k1, k2, k3); + if (e) { + v = e->get_data().get_value(); + } + return (0 != e); + } + + bool contains(Key1 * k1, Key2 * k2, Key3 * k3) const { + return find_core(k1, k2, k3) != 0; + } + + void erase(Key1 * k1, Key2 * k2, Key3 * k3) { + m_table.remove(key_data(k1, k2, k3)); + } +}; + +#endif /* _OBJ_TRIPLE_HASHTABLE_H_ */ + diff --git a/lib/object_allocator.h b/lib/object_allocator.h new file mode 100644 index 000000000..96e705da7 --- /dev/null +++ b/lib/object_allocator.h @@ -0,0 +1,289 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + object_allocator.h + +Abstract: + + Yet another object allocator. This allocator is supposed to be efficient + when there is a collection of worker threads accessing it. + +Author: + + Leonardo de Moura (leonardo) 2010-06-09. + +Revision History: + +--*/ +#ifndef _OBJECT_ALLOCATOR_H_ +#define _OBJECT_ALLOCATOR_H_ + +#include"util.h" +#include"vector.h" + +#define DEFAULT_NUM_WORKERS 8 +#define NUM_OBJECTS_PER_PAGE 1024 + +template +struct do_nothing_reset_proc { +public: + void operator()(T * obj) {} +}; + +template +struct simple_reset_proc { +public: + void operator()(T * obj) { obj->reset(); } +}; + +/** + \brief Allocator for T objects. This allocator is supposed to be efficient even + when a collection of working threads are accessing it. + + Assumptions: + - T must have an empty constructor. + + - The destructors for T objects are only invoked when the object_allocator is deleted. + + - The destructors are not invoked if CallDestructors == false. + + - The functor ResetProc is invoked for \c ptr when recycle(ptr) or recycle(worker_id, ptr) are invoked. + + The default ResetProc does nothing. +*/ +template > +class object_allocator : public ResetProc { + + /** + \brief Auxiliary allocator for storing object into chunks of memory. + */ + class region { + ptr_vector m_pages; + unsigned m_idx; //!< next position in the current page. + + void allocate_new_page() { + T * new_page = static_cast(memory::allocate(sizeof(T) * NUM_OBJECTS_PER_PAGE)); + m_pages.push_back(new_page); + m_idx = 0; + } + + void call_destructors_for_page(T * page, unsigned end) { + T * page_end = page + end; + for (; page < page_end; page++) + page->~T(); + } + + void call_destructors() { + if (CallDestructors) { + SASSERT(!m_pages.empty()); + typename ptr_vector::iterator it = m_pages.begin(); + typename ptr_vector::iterator end = m_pages.end(); + end--; + call_destructors_for_page(*end, m_idx); + for (; it != end; ++it) + call_destructors_for_page(*it, NUM_OBJECTS_PER_PAGE); + } + } + + void free_memory() { + call_destructors(); + typename ptr_vector::iterator it = m_pages.begin(); + typename ptr_vector::iterator end = m_pages.end(); + for (; it != end; ++it) + memory::deallocate(*it); + } + + public: + region() { + allocate_new_page(); + } + + ~region() { + free_memory(); + } + + template + T * allocate() { + SASSERT(!m_pages.empty()); + T * r = m_pages.back() + m_idx; + if (construct) new (r) T(); + m_idx++; + if (m_idx == NUM_OBJECTS_PER_PAGE) + allocate_new_page(); + return r; + } + + void reset() { + free_memory(); + m_pages.reset(); + allocate_new_page(); + } + + unsigned get_objects_count() { + return (m_pages.size() - 1) * NUM_OBJECTS_PER_PAGE + m_idx; + } + }; + +#ifdef Z3DEBUG + bool m_concurrent; //!< True when the allocator can be accessed concurrently. +#endif + ptr_vector m_regions; + vector > m_free_lists; + + template + T * allocate_core(unsigned idx) { + ptr_vector & free_list = m_free_lists[idx]; + if (!free_list.empty()) { + T * r = free_list.back(); + free_list.pop_back(); + return r; + } + return m_regions[idx]->allocate(); + } + + void recycle_core(unsigned idx, T * ptr) { + ResetProc::operator()(ptr); + m_free_lists[idx].push_back(ptr); + } + +public: + object_allocator(ResetProc const & r = ResetProc()):ResetProc(r) { + DEBUG_CODE(m_concurrent = false;); + reserve(DEFAULT_NUM_WORKERS); + } + + ~object_allocator() { + std::for_each(m_regions.begin(), m_regions.end(), delete_proc()); + } + + /** + \brief Enable/Disable concurrent access. + */ + void enable_concurrent(bool flag) { + DEBUG_CODE(m_concurrent = flag;); + } + + /** + \brief Make sure that \c num_workers can access this object allocator concurrently. + This method must only be invoked if the allocator is not in concurrent mode. + */ + void reserve(unsigned num_workers) { + SASSERT(!m_concurrent); + unsigned old_capacity = capacity(); + if (num_workers > old_capacity) { + m_regions.resize(num_workers); + m_free_lists.resize(num_workers); + for (unsigned i = old_capacity; i < capacity(); i++) { + m_regions[i] = alloc(region); + } + } + } + + /** + \brief Return the number of workers supported by this object allocator. + */ + unsigned capacity() const { + return m_regions.size(); + } + + /** + \brief Free all memory allocated using this allocator. + This method must only be invoked when the allocator is not in concurrent mode. + */ + void reset() { + SASSERT(!m_concurrent); + unsigned c = capacity(); + for (unsigned i = 0; i < c; i++) { + m_regions[i]->reset(); + m_free_lists[i].reset(); + } + } + + /** + \brief Allocate a new object. + This method must only be invoked when the object_allocator is not in concurrent mode. + */ + template + T * allocate() { + SASSERT(!m_concurrent); + return allocate_core(0); + } + + + /** + \brief Recycle the given object. + This method must only be invoked when the object_allocator is not in concurrent mode. + + \remark It is OK to recycle an object allocated by a worker when the object_allocator was + in concurrent mode. + */ + void recycle(T * ptr) { + SASSERT(!m_concurrent); + recycle_core(0, ptr); + } + + /** + \brief Allocate a new object for the given worker. + This method must only be invoked when the object_allocator is in concurrent mode. + */ + template + T * allocate(unsigned worker_id) { + SASSERT(m_concurrent); + return allocate_core(worker_id); + } + + /** + \brief Recycle the given object. + This method must only be invoked when the object_allocator is in concurrent mode. + + \remark It is OK to recycle an object allocated by a different worker, or allocated when the + object_allocator was not in concurrent mode. + */ + void recycle(unsigned worker_id, T * ptr) { + SASSERT(m_concurrent); + return recycle_core(worker_id, ptr); + } + + /** + \brief Wrapper for currying worker_id in allocate and recycle methods. + */ + class worker_object_allocator { + object_allocator & m_owner; + unsigned m_worker_id; + + friend class object_allocator; + + worker_object_allocator(object_allocator & owner, unsigned id):m_owner(owner), m_worker_id(id) {} + public: + template + T * allocate() { return m_owner.allocate(m_worker_id); } + + void recycle(T * ptr) { return m_owner.recycle(m_worker_id, ptr); } + }; + + /** + \brief Return a wrapper for allocating memory for the given worker. + The wrapper remains valid even when the object_allocator is not in concurrent mode. + However, the methods allocate/recycle of the wrapper must only be invoked when the object_allocator is in concurrent mode. + */ + worker_object_allocator get_worker_allocator(unsigned worker_id) { + SASSERT(worker_id < capacity()); + return worker_object_allocator(*this, worker_id); + } + + unsigned get_objects_count() const { + unsigned count = 0; + unsigned n_regions = m_regions.size(); + for (unsigned i = 0; i < n_regions; i++) { + count += m_regions[i]->get_objects_count(); + count -= m_free_lists[i].size(); + } + return count; + } + +}; + +#endif /* _OBJECT_ALLOCATOR_H_ */ + diff --git a/lib/occf_tactic.cpp b/lib/occf_tactic.cpp new file mode 100644 index 000000000..f8f6174b9 --- /dev/null +++ b/lib/occf_tactic.cpp @@ -0,0 +1,252 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + occf_tactic.cpp + +Abstract: + + Put clauses in the assertion set in + OOC (one constraint per clause) form. + Constraints occuring in formulas that + are not clauses are ignored. + The formula can be put into CNF by + using mk_sat_preprocessor strategy. + +Author: + + Leonardo de Moura (leonardo) 2011-12-28. + +Revision History: + +--*/ +#include"tactical.h" +#include"occf_tactic.h" +#include"filter_model_converter.h" +#include"cooperate.h" + +class occf_tactic : public tactic { + struct imp { + ast_manager & m; + volatile bool m_cancel; + filter_model_converter * m_mc; + + imp(ast_manager & _m): + m(_m) { + m_cancel = false; + } + + void set_cancel(bool f) { + m_cancel = f; + } + + void checkpoint() { + if (m_cancel) + throw tactic_exception(TACTIC_CANCELED_MSG); + cooperate("occf"); + } + + bool is_literal(expr * t) const { + expr * atom; + return is_uninterp_const(t) || (m.is_not(t, atom) && is_uninterp_const(atom)); + } + + bool is_constraint(expr * t) const { + return !is_literal(t); + } + + bool is_target(app * cls) { + SASSERT(m.is_or(cls)); + bool found = false; + unsigned num = cls->get_num_args(); + for (unsigned i = 0; i < num; i++) { + if (is_constraint(cls->get_arg(i))) { + if (found) + return true; + found = true; + } + } + return false; + } + + struct bvar_info { + expr * m_bvar; + unsigned m_gen_pos:1; + unsigned m_gen_neg:1; + bvar_info():m_bvar(0), m_gen_pos(false), m_gen_neg(false) {} + bvar_info(expr * var, bool sign): + m_bvar(var), + m_gen_pos(!sign), + m_gen_neg(sign) { + } + }; + + typedef obj_map cnstr2bvar; + + expr * get_aux_lit(cnstr2bvar & c2b, expr * cnstr, goal_ref const & g) { + bool sign = false; + while (m.is_not(cnstr)) { + cnstr = to_app(cnstr)->get_arg(0); + sign = !sign; + } + + cnstr2bvar::obj_map_entry * entry = c2b.find_core(cnstr); + if (entry == 0) + return 0; + bvar_info & info = entry->get_data().m_value; + if (sign) { + if (!info.m_gen_neg) { + info.m_gen_neg = true; + g->assert_expr(m.mk_or(info.m_bvar, m.mk_not(cnstr)), 0, 0); + } + return m.mk_not(info.m_bvar); + } + else { + if (!info.m_gen_pos) { + info.m_gen_pos = true; + g->assert_expr(m.mk_or(m.mk_not(info.m_bvar), cnstr), 0, 0); + } + return info.m_bvar; + } + } + + expr * mk_aux_lit(cnstr2bvar & c2b, expr * cnstr, bool produce_models, goal_ref const & g) { + bool sign = false; + while (m.is_not(cnstr)) { + cnstr = to_app(cnstr)->get_arg(0); + sign = !sign; + } + + SASSERT(!c2b.contains(cnstr)); + expr * bvar = m.mk_fresh_const(0, m.mk_bool_sort()); + if (produce_models) + m_mc->insert(to_app(bvar)->get_decl()); + c2b.insert(cnstr, bvar_info(bvar, sign)); + if (sign) { + g->assert_expr(m.mk_or(bvar, m.mk_not(cnstr)), 0, 0); + return m.mk_not(bvar); + } + else { + g->assert_expr(m.mk_or(m.mk_not(bvar), cnstr), 0, 0); + return bvar; + } + } + + void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + mc = 0; pc = 0; core = 0; + + fail_if_proof_generation("occf", g); + + bool produce_models = g->models_enabled(); + tactic_report report("occf", *g); + + m_mc = 0; + + ptr_vector new_lits; + + cnstr2bvar c2b; + + unsigned sz = g->size(); + for (unsigned i = 0; i < sz; i++) { + checkpoint(); + expr * f = g->form(i); + expr_dependency * d = g->dep(i); + if (!m.is_or(f)) + continue; + app * cls = to_app(f); + if (!is_target(cls)) + continue; + if (produce_models && !m_mc) { + m_mc = alloc(filter_model_converter, m); + mc = m_mc; + } + expr * keep = 0; + new_lits.reset(); + unsigned num = cls->get_num_args(); + for (unsigned j = 0; j < num; j++) { + expr * l = cls->get_arg(j); + if (is_constraint(l)) { + expr * new_l = get_aux_lit(c2b, l, g); + if (new_l != 0) { + new_lits.push_back(new_l); + } + else if (keep == 0) { + keep = l; + } + else { + new_l = mk_aux_lit(c2b, l, produce_models, g); + new_lits.push_back(new_l); + } + } + else { + new_lits.push_back(l); + } + } + if (keep != 0) + new_lits.push_back(keep); + g->update(i, m.mk_or(new_lits.size(), new_lits.c_ptr()), 0, d); + } + g->inc_depth(); + result.push_back(g.get()); + TRACE("occf", g->display(tout);); + SASSERT(g->is_well_sorted()); + } + }; + + imp * m_imp; +public: + occf_tactic(ast_manager & m) { + m_imp = alloc(imp, m); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(occf_tactic, m); + } + + virtual ~occf_tactic() { + dealloc(m_imp); + } + + virtual void updt_params(params_ref const & p) {} + virtual void collect_param_descrs(param_descrs & r) {} + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + (*m_imp)(in, result, mc, pc, core); + } + + virtual void cleanup() { + ast_manager & m = m_imp->m; + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + } + +protected: + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + +tactic * mk_occf_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(occf_tactic, m)); +} + diff --git a/lib/occf_tactic.h b/lib/occf_tactic.h new file mode 100644 index 000000000..06acf1d4e --- /dev/null +++ b/lib/occf_tactic.h @@ -0,0 +1,34 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + occf_tactic.h + +Abstract: + + Put clauses in the assertion set in + OOC (one constraint per clause) form. + Constraints occuring in formulas that + are not clauses are ignored. + The formula can be put into CNF by + using mk_sat_preprocessor strategy. + +Author: + + Leonardo de Moura (leonardo) 2011-12-28. + +Revision History: + +--*/ +#ifndef _OCCF_TACTIC_H_ +#define _OCCF_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_occf_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif + diff --git a/lib/occurs.cpp b/lib/occurs.cpp new file mode 100644 index 000000000..9d2360351 --- /dev/null +++ b/lib/occurs.cpp @@ -0,0 +1,77 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + occurs.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-06-07. + +Revision History: + +--*/ +#include"occurs.h" + +#include"for_each_expr.h" + +// ----------------------------------- +// +// Occurs check +// +// ----------------------------------- + +namespace occurs_namespace { + struct found {}; + + struct proc { + expr * m_n; + +#define CHECK() { if (n == m_n) throw found(); } + + proc(expr * n):m_n(n) {} + void operator()(var const * n) { CHECK(); } + void operator()(app const * n) { CHECK(); } + void operator()(quantifier const * n) { CHECK(); } + }; + + struct decl_proc { + func_decl * m_d; + + decl_proc(func_decl * d):m_d(d) {} + void operator()(var const * n) { } + void operator()(app const * n) { if (n->get_decl() == m_d) throw found(); } + void operator()(quantifier const * n) { } + }; + + +}; + +// Return true if n1 occurs in n2 +bool occurs(expr * n1, expr * n2) { + occurs_namespace::proc p(n1); + try { + quick_for_each_expr(p, n2); + } + catch (occurs_namespace::found) { + return true; + } + return false; +} + +bool occurs(func_decl * d, expr * n) { + occurs_namespace::decl_proc p(d); + try { + quick_for_each_expr(p, n); + } + catch (occurs_namespace::found) { + return true; + } + return false; +} + diff --git a/lib/occurs.h b/lib/occurs.h new file mode 100644 index 000000000..1aa6b47a4 --- /dev/null +++ b/lib/occurs.h @@ -0,0 +1,36 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + occurs.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-06-07. + +Revision History: + +--*/ +#ifndef _OCCURS_H_ +#define _OCCURS_H_ + +class expr; +class func_decl; + +/** + \brief Return true if n1 occurs in n2 +*/ +bool occurs(expr * n1, expr * n2); + +/** + \brief Return true if d is used in n +*/ +bool occurs(func_decl * d, expr * n); + +#endif /* _OCCURS_H_ */ + diff --git a/lib/old_interval.cpp b/lib/old_interval.cpp new file mode 100644 index 000000000..ffc3331be --- /dev/null +++ b/lib/old_interval.cpp @@ -0,0 +1,642 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + old_interval.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-12-09. + +Revision History: + +--*/ + +#include"old_interval.h" + +void ext_numeral::neg() { + switch (m_kind) { + case MINUS_INFINITY: m_kind = PLUS_INFINITY; break; + case FINITE: m_value.neg(); break; + case PLUS_INFINITY: m_kind = MINUS_INFINITY; break; + } +} + +ext_numeral & ext_numeral::operator+=(ext_numeral const & other) { + SASSERT(!is_infinite() || !other.is_infinite() || m_kind == other.m_kind); + if (is_infinite()) + return *this; + SASSERT(m_kind == FINITE); + switch (other.m_kind) { + case MINUS_INFINITY: + m_kind = MINUS_INFINITY; + m_value.reset(); + return *this; + case FINITE: + m_value += other.m_value; + return *this; + case PLUS_INFINITY: + m_kind = PLUS_INFINITY; + m_value.reset(); + return *this; + } + UNREACHABLE(); + return *this; +} + +ext_numeral & ext_numeral::operator-=(ext_numeral const & other) { + SASSERT(!is_infinite() || !other.is_infinite() || (m_kind != other.m_kind)); + if (is_infinite()) + return *this; + SASSERT(m_kind == FINITE); + switch (other.m_kind) { + case MINUS_INFINITY: + m_kind = PLUS_INFINITY; + m_value.reset(); + return *this; + case FINITE: + m_value -= other.m_value; + return *this; + case PLUS_INFINITY: + m_kind = MINUS_INFINITY; + m_value.reset(); + return *this; + } + UNREACHABLE(); + return *this; +} + +ext_numeral & ext_numeral::operator*=(ext_numeral const & other) { + if (is_zero() || other.is_zero()) { + m_kind = FINITE; + m_value.reset(); + return *this; + } + + if (is_infinite() || other.is_infinite()) { + if (sign() == other.sign()) + m_kind = PLUS_INFINITY; + else + m_kind = MINUS_INFINITY; + m_value.reset(); + return *this; + } + + SASSERT(m_kind == FINITE); + m_value *= other.m_value; + return *this; +} + +void ext_numeral::expt(unsigned n) { + switch (m_kind) { + case MINUS_INFINITY: + if (n % 2 == 0) + m_kind = PLUS_INFINITY; + return; + case FINITE: + m_value = m_value.expt(n); + break; + case PLUS_INFINITY: + // do nothing + break; + } +} + +void ext_numeral::inv() { + SASSERT(!is_zero()); + if (is_infinite()) { + m_kind = FINITE; + m_value.reset(); + } + else { + m_value = rational(1) / m_value; + } +} + +void ext_numeral::display(std::ostream & out) const { + switch (m_kind) { + case MINUS_INFINITY: + out << "-oo"; + break; + case FINITE: + out << m_value; + break; + case PLUS_INFINITY: + out << "oo"; + break; + } +} + +bool operator==(ext_numeral const & n1, ext_numeral const & n2) { + return n1.m_kind == n2.m_kind && (n1.is_infinite() || n1.m_value == n2.m_value); +} + +bool operator<(ext_numeral const & n1, ext_numeral const & n2) { + if (n1.is_infinite()) + return n1.m_kind == ext_numeral::MINUS_INFINITY && n2.m_kind != ext_numeral::MINUS_INFINITY; + if (n2.is_infinite()) + return n2.m_kind != ext_numeral::MINUS_INFINITY; + return n1.m_value < n2.m_value; +} + +/** + \brief Create interval (-oo, oo) +*/ +interval::interval(v_dependency_manager & m): + m_manager(m), + m_lower(false), + m_upper(true), + m_lower_open(true), + m_upper_open(true), + m_lower_dep(0), + m_upper_dep(0) { +} + +/** + \brief Create intervals [l,u], (l,u], [l, u), (l,u), where l and u are numerals. +*/ +interval::interval(v_dependency_manager & m, rational const & lower, bool l_open, v_dependency * l_dep, rational const & upper, bool u_open, v_dependency * u_dep): + m_manager(m), + m_lower(lower), + m_upper(upper), + m_lower_open(l_open), + m_upper_open(u_open), + m_lower_dep(l_dep), + m_upper_dep(u_dep) { + SASSERT(lower <= upper); + SASSERT(lower != upper || !l_open || !u_open); +} + +/** + \brief Create intervals [l,u], (l,u], [l, u), (l,u), where l and u are ext_numerals. +*/ +interval::interval(v_dependency_manager & m, ext_numeral const & lower, bool l_open, v_dependency * l_dep, ext_numeral const & upper, bool u_open, v_dependency * u_dep): + m_manager(m), + m_lower(lower), + m_upper(upper), + m_lower_open(l_open), + m_upper_open(u_open), + m_lower_dep(l_dep), + m_upper_dep(u_dep) { + SASSERT(lower <= upper); + SASSERT(lower != upper || !l_open || !u_open); +} + +/** + \brief Create interval [val,val] +*/ +interval::interval(v_dependency_manager & m, rational const & val, v_dependency * l_dep, v_dependency * u_dep): + m_manager(m), + m_lower(val), + m_upper(val), + m_lower_open(false), + m_upper_open(false), + m_lower_dep(l_dep), + m_upper_dep(u_dep) { +} + +/** + \brief Create intervals (-oo, val], (-oo, val), [val, oo), (val, oo) +*/ +interval::interval(v_dependency_manager & m, rational const & val, bool open, bool lower, v_dependency * d): + m_manager(m) { + if (lower) { + m_lower = ext_numeral(val); + m_lower_open = open; + m_lower_dep = d; + m_upper = ext_numeral(true); + m_upper_open = true; + m_upper_dep = 0; + } + else { + m_lower = ext_numeral(false); + m_lower_open = true; + m_lower_dep = 0; + m_upper = ext_numeral(val); + m_upper_open = open; + m_upper_dep = d; + } +} + +interval::interval(interval const & other): + m_manager(other.m_manager), + m_lower(other.m_lower), + m_upper(other.m_upper), + m_lower_open(other.m_lower_open), + m_upper_open(other.m_upper_open), + m_lower_dep(other.m_lower_dep), + m_upper_dep(other.m_upper_dep) { +} + +interval & interval::operator=(interval const & other) { + m_lower = other.m_lower; + m_upper = other.m_upper; + m_lower_open = other.m_lower_open; + m_upper_open = other.m_upper_open; + m_lower_dep = other.m_lower_dep; + m_upper_dep = other.m_upper_dep; + return *this; +} + +interval & interval::operator+=(interval const & other) { + m_lower += other.m_lower; + m_upper += other.m_upper; + m_lower_open |= other.m_lower_open; + m_upper_open |= other.m_upper_open; + m_lower_dep = m_lower.is_infinite() ? 0 : m_manager.mk_join(m_lower_dep, other.m_lower_dep); + m_upper_dep = m_upper.is_infinite() ? 0 : m_manager.mk_join(m_upper_dep, other.m_upper_dep); + return *this; +} + +void interval::neg() { + std::swap(m_lower, m_upper); + std::swap(m_lower_open, m_upper_open); + std::swap(m_lower_dep, m_upper_dep); + m_lower.neg(); + m_upper.neg(); +} + +interval & interval::operator-=(interval const & other) { + interval tmp(other); + tmp.neg(); + return operator+=(tmp); +} + +v_dependency * interval::join(v_dependency * d1, v_dependency * d2, v_dependency * d3, v_dependency * d4) { + return m_manager.mk_join(m_manager.mk_join(d1, d2), m_manager.mk_join(d3,d4)); +} + +/** + \brief Create a new v_dependency using d1, d2, and (opt1 or opt2). +*/ +v_dependency * interval::join_opt(v_dependency * d1, v_dependency * d2, v_dependency * opt1, v_dependency * opt2) { + if (opt1 == d1 || opt1 == d2) + return join(d1, d2); + if (opt2 == d1 || opt2 == d2) + return join(d1, d2); + if (opt1 == 0 || opt2 == 0) + return join(d1, d2); + // TODO: more opts... + return join(d1, d2, opt1); +} + +interval & interval::operator*=(interval const & other) { +#if Z3DEBUG || _TRACE + bool contains_zero1 = contains_zero(); + bool contains_zero2 = other.contains_zero(); +#endif + if (is_zero()) { + return *this; + } + if (other.is_zero()) { + *this = other; + return *this; + } + + ext_numeral const & a = m_lower; + ext_numeral const & b = m_upper; + ext_numeral const & c = other.m_lower; + ext_numeral const & d = other.m_upper; + bool a_o = m_lower_open; + bool b_o = m_upper_open; + bool c_o = other.m_lower_open; + bool d_o = other.m_upper_open; + v_dependency * a_d = m_lower_dep; + v_dependency * b_d = m_upper_dep; + v_dependency * c_d = other.m_lower_dep; + v_dependency * d_d = other.m_upper_dep; + + TRACE("interval_bug", tout << "operator*= " << *this << " " << other << "\n";); + + if (is_N()) { + if (other.is_N()) { + // x <= b <= 0, y <= d <= 0 --> b*d <= x*y + // a <= x <= b <= 0, c <= y <= d <= 0 --> x*y <= a*c (we can use the fact that x or y is always negative (i.e., b is neg or d is neg)) + TRACE("interval_bug", tout << "(N, N)\n";); + ext_numeral new_lower = b * d; + ext_numeral new_upper = a * c; + // if b = 0 (and the interval is closed), then the lower bound is closed + m_lower_open = (is_N0() || other.is_N0()) ? false : (b_o || d_o); + m_upper_open = a_o || c_o; SASSERT(a.is_neg() && c.is_neg()); + m_lower = new_lower; + m_upper = new_upper; + m_lower_dep = m_lower.is_infinite() ? 0 : join(b_d, d_d); + m_upper_dep = m_upper.is_infinite() ? 0 : join_opt(a_d, c_d, b_d, d_d); + } + else if (other.is_M()) { + // a <= x <= b <= 0, y <= d, d > 0 --> a*d <= x*y (uses the fact that b is not positive) + // a <= x <= b <= 0, c <= y, c < 0 --> x*y <= a*c (uses the fact that b is not positive) + TRACE("interval_bug", tout << "(N, M)\n";); + ext_numeral new_lower = a * d; SASSERT(new_lower.is_neg()); + ext_numeral new_upper = a * c; SASSERT(new_upper.is_pos()); + m_lower_open = a_o || d_o; + m_upper_open = a_o || c_o; + m_lower = new_lower; + m_upper = new_upper; + m_lower_dep = m_lower.is_infinite() ? 0 : join(a_d, d_d, b_d); + m_upper_dep = m_upper.is_infinite() ? 0 : join(a_d, c_d, b_d); + } + else { + // a <= x <= b <= 0, 0 <= c <= y <= d --> a*d <= x*y (uses the fact that x is neg (b is not positive) or y is pos (c is not negative)) + // x <= b <= 0, 0 <= c <= y --> x*y <= b*c + TRACE("interval_bug", tout << "(N, P)\n";); + SASSERT(other.is_P()); + ext_numeral new_lower = a * d; + ext_numeral new_upper = b * c; + bool is_N0_old = is_N0(); // see comment in (P, N) case + m_lower_open = a_o || d_o; SASSERT(a.is_neg() && d.is_pos()); + m_upper_open = (is_N0_old || other.is_P0()) ? false : (b_o || c_o); + m_lower = new_lower; + m_upper = new_upper; + m_lower_dep = m_lower.is_infinite() ? 0 : join_opt(a_d, d_d, b_d, c_d); + m_upper_dep = m_upper.is_infinite() ? 0 : join(b_d, c_d); + } + } + else if (is_M()) { + if (other.is_N()) { + // b > 0, x <= b, c <= y <= d <= 0 --> b*c <= x*y (uses the fact that d is not positive) + // a < 0, a <= x, c <= y <= d <= 0 --> x*y <= a*c (uses the fact that d is not positive) + TRACE("interval_bug", tout << "(M, N)\n";); + ext_numeral new_lower = b * c; SASSERT(new_lower.is_neg()); + ext_numeral new_upper = a * c; SASSERT(new_upper.is_pos()); + m_lower_open = b_o || c_o; SASSERT(b.is_pos() && c.is_neg()); + m_upper_open = a_o || c_o; SASSERT(a.is_neg() && c.is_neg()); + m_lower = new_lower; + m_upper = new_upper; + m_lower_dep = m_lower.is_infinite() ? 0 : join(b_d, c_d, d_d); + m_upper_dep = m_upper.is_infinite() ? 0 : join(a_d, c_d, d_d); + } + else if (other.is_M()) { + TRACE("interval_bug", tout << "(M, M)\n";); + SASSERT(!a.is_zero() && !b.is_zero() && !c.is_zero() && !d.is_zero()); + ext_numeral ad = a*d; SASSERT(!ad.is_zero()); + ext_numeral bc = b*c; SASSERT(!bc.is_zero()); + ext_numeral ac = a*c; SASSERT(!ac.is_zero()); + ext_numeral bd = b*d; SASSERT(!bd.is_zero()); + bool ad_o = a_o || d_o; + bool bc_o = b_o || c_o; + bool ac_o = a_o || c_o; + bool bd_o = b_o || d_o; + if (ad < bc || (ad == bc && !ad_o && bc_o)) { + m_lower = ad; + m_lower_open = ad_o; + } + else { + m_lower = bc; + m_lower_open = bc_o; + } + if (ac > bd || (ac == bd && !ac_o && bd_o)) { + m_upper = ac; + m_upper_open = ac_o; + } + else { + m_upper = bd; + m_upper_open = bd_o; + } + m_lower_dep = m_lower.is_infinite() ? 0 : join(a_d, b_d, c_d, d_d); + m_upper_dep = m_upper.is_infinite() ? 0 : join(a_d, b_d, c_d, d_d); + } + else { + // a < 0, a <= x, 0 <= c <= y <= d --> a*d <= x*y (uses the fact that c is not negative) + // b > 0, x <= b, 0 <= c <= y <= d --> x*y <= b*d (uses the fact that c is not negative) + TRACE("interval_bug", tout << "(M, P)\n";); + SASSERT(other.is_P()); + ext_numeral new_lower = a * d; SASSERT(new_lower.is_neg()); + ext_numeral new_upper = b * d; SASSERT(new_upper.is_pos()); + m_lower_open = a_o || d_o; SASSERT(a.is_neg() && d.is_pos()); + m_upper_open = b_o || d_o; SASSERT(b.is_pos() && d.is_pos()); + m_lower = new_lower; + m_upper = new_upper; + m_lower_dep = m_lower.is_infinite() ? 0 : join(a_d, d_d, c_d); + m_upper_dep = m_upper.is_infinite() ? 0 : join(b_d, d_d, c_d); + } + } + else { + SASSERT(is_P()); + if (other.is_N()) { + // 0 <= a <= x <= b, c <= y <= d <= 0 --> x*y <= b*c (uses the fact that x is pos (a is not neg) or y is neg (d is not pos)) + // 0 <= a <= x, y <= d <= 0 --> a*d <= x*y + TRACE("interval_bug", tout << "(P, N)\n";); + ext_numeral new_lower = b * c; + ext_numeral new_upper = a * d; + bool is_P0_old = is_P0(); // cache the value of is_P0(), since it may be affected by the next update. + m_lower_open = b_o || c_o; SASSERT(b.is_pos() && c.is_neg()); + m_upper_open = (is_P0_old || other.is_N0()) ? false : a_o || d_o; + m_lower = new_lower; + m_upper = new_upper; + m_lower_dep = m_lower.is_infinite() ? 0 : join_opt(b_d, c_d, a_d, d_d); + m_upper_dep = m_upper.is_infinite() ? 0 : join(a_d, d_d); + } + else if (other.is_M()) { + // 0 <= a <= x <= b, c <= y --> b*c <= x*y (uses the fact that a is not negative) + // 0 <= a <= x <= b, y <= d --> x*y <= b*d (uses the fact that a is not negative) + TRACE("interval_bug", tout << "(P, M)\n";); + ext_numeral new_lower = b * c; SASSERT(new_lower.is_neg()); + ext_numeral new_upper = b * d; SASSERT(new_upper.is_pos()); + m_lower_open = b_o || c_o; + m_upper_open = b_o || d_o; + m_lower = new_lower; + m_upper = new_upper; + m_lower_dep = m_lower.is_infinite() ? 0 : join(b_d, c_d, a_d); + m_upper_dep = m_upper.is_infinite() ? 0 : join(b_d, d_d, a_d); + } + else { + // 0 <= a <= x, 0 <= c <= y --> a*c <= x*y + // x <= b, y <= d --> x*y <= b*d (uses the fact that x is pos (a is not negative) or y is pos (c is not negative)) + TRACE("interval_bug", tout << "(P, P)\n";); + SASSERT(other.is_P()); + ext_numeral new_lower = a * c; + ext_numeral new_upper = b * d; + m_lower_open = (is_P0() || other.is_P0()) ? false : a_o || c_o; + m_upper_open = b_o || d_o; SASSERT(b.is_pos() && d.is_pos()); + m_lower = new_lower; + m_upper = new_upper; + m_lower_dep = m_lower.is_infinite() ? 0 : join(a_d, c_d); + m_upper_dep = m_upper.is_infinite() ? 0 : join_opt(b_d, d_d, a_d, c_d); + } + } + TRACE("interval_bug", tout << "operator*= result: " << *this << "\n";); + CTRACE("interval", !(!(contains_zero1 || contains_zero2) || contains_zero()), + tout << "contains_zero1: " << contains_zero1 << ", contains_zero2: " << contains_zero2 << ", contains_zero(): " << contains_zero() << "\n";); + SASSERT(!(contains_zero1 || contains_zero2) || contains_zero()); + return *this; +} + +bool interval::contains_zero() const { + TRACE("interval_zero_bug", tout << "contains_zero info: " << *this << "\n"; + tout << "m_lower.is_neg(): " << m_lower.is_neg() << "\n"; + tout << "m_lower.is_zero: " << m_lower.is_zero() << "\n"; + tout << "m_lower_open: " << m_lower_open << "\n"; + tout << "m_upper.is_pos(): " << m_upper.is_pos() << "\n"; + tout << "m_upper.is_zero: " << m_upper.is_zero() << "\n"; + tout << "m_upper_open: " << m_upper_open << "\n"; + tout << "result: " << ((m_lower.is_neg() || (m_lower.is_zero() && !m_lower_open)) && (m_upper.is_pos() || (m_upper.is_zero() && !m_upper_open))) << "\n";); + return + (m_lower.is_neg() || (m_lower.is_zero() && !m_lower_open)) && + (m_upper.is_pos() || (m_upper.is_zero() && !m_upper_open)); +} + +bool interval::contains(rational const& v) const { + if (!inf().is_infinite()) { + if (v < inf().to_rational()) return false; + if (v == inf().to_rational() && m_lower_open) return false; + } + if (!sup().is_infinite()) { + if (v > sup().to_rational()) return false; + if (v == sup().to_rational() && m_upper_open) return false; + } + return true; +} + +interval & interval::inv() { + // If the interval [l,u] does not contain 0, then 1/[l,u] = [1/u, 1/l] + SASSERT(!contains_zero()); + if (is_P1()) { + // 0 < a <= x --> 1/x <= 1/a + // 0 < a <= x <= b --> 1/b <= 1/x (use lower and upper bounds) + ext_numeral new_lower = m_upper; SASSERT(!m_upper.is_zero()); + new_lower.inv(); + ext_numeral new_upper; + if (m_lower.is_zero()) { + SASSERT(m_lower_open); + ext_numeral plus_infinity(true); + new_upper = plus_infinity; + } + else { + new_upper = m_lower; + new_upper.inv(); + } + m_lower = new_lower; + m_upper = new_upper; + std::swap(m_lower_open, m_upper_open); + v_dependency * new_upper_dep = m_lower_dep; + SASSERT(!m_lower.is_infinite()); + m_lower_dep = m_manager.mk_join(m_lower_dep, m_upper_dep); + m_upper_dep = new_upper_dep; + } + else if (is_N1()) { + // x <= a < 0 --> 1/a <= 1/x + // b <= x <= a < 0 --> 1/b <= 1/x (use lower and upper bounds) + ext_numeral new_upper = m_lower; SASSERT(!m_lower.is_zero()); + new_upper.inv(); + ext_numeral new_lower; + if (m_upper.is_zero()) { + SASSERT(m_upper_open); + ext_numeral minus_infinity(false); + new_lower = minus_infinity; + } + else { + new_lower = m_upper; + new_lower.inv(); + } + m_lower = new_lower; + m_upper = new_upper; + std::swap(m_lower_open, m_upper_open); + v_dependency * new_lower_dep = m_upper_dep; + SASSERT(!m_upper.is_infinite()); + m_upper_dep = m_manager.mk_join(m_lower_dep, m_upper_dep); + m_lower_dep = new_lower_dep; + } + else { + UNREACHABLE(); + } + return *this; +} + +interval & interval::operator/=(interval const & other) { + SASSERT(!other.contains_zero()); + if (is_zero()) { + // 0/other = 0 if other != 0 + TRACE("interval", other.display_with_dependencies(tout);); + if (other.m_lower.is_pos() || (other.m_lower.is_zero() && other.m_lower_open)) { + // other.lower > 0 + m_lower_dep = join(m_lower_dep, other.m_lower_dep); + m_upper_dep = join(m_upper_dep, other.m_lower_dep); + } + else { + // assertion must hold since !other.contains_zero() + SASSERT(other.m_upper.is_neg() || (other.m_upper.is_zero() && other.m_upper_open)); + // other.upper < 0 + m_lower_dep = join(m_lower_dep, other.m_upper_dep); + m_upper_dep = join(m_upper_dep, other.m_upper_dep); + } + return *this; + } + else { + interval tmp(other); + tmp.inv(); + return operator*=(tmp); + } +} + +void interval::expt(unsigned n) { + if (n == 1) + return; + if (n % 2 == 0) { + if (m_lower.is_pos()) { + // [l, u]^n = [l^n, u^n] if l > 0 + // 0 < a <= x --> a^n <= x^n (lower bound guarantees that is positive) + // 0 < a <= x <= b --> x^n <= b^n (use lower and upper bound -- need the fact that x is positive) + m_lower.expt(n); + m_upper.expt(n); + m_upper_dep = m_upper.is_infinite() ? 0 : m_manager.mk_join(m_lower_dep, m_upper_dep); + } + else if (m_upper.is_neg()) { + // [l, u]^n = [u^n, l^n] if u < 0 + // a <= x <= b < 0 --> x^n <= a^n (use lower and upper bound -- need the fact that x is negative) + // x <= b < 0 --> b^n <= x^n + std::swap(m_lower, m_upper); + std::swap(m_lower_open, m_upper_open); + std::swap(m_lower_dep, m_upper_dep); + m_lower.expt(n); + m_upper.expt(n); + m_upper_dep = m_upper.is_infinite() ? 0 : m_manager.mk_join(m_lower_dep, m_upper_dep); + } + else { + // [l, u]^n = [0, max{l^n, u^n}] otherwise + // we need both bounds to justify upper bound + TRACE("interval", tout << "before: " << m_lower << " " << m_upper << " " << n << "\n";); + m_lower.expt(n); + m_upper.expt(n); + TRACE("interval", tout << "after: " << m_lower << " " << m_upper << "\n";); + if (m_lower > m_upper || (m_lower == m_upper && !m_lower_open && m_upper_open)) { + m_upper = m_lower; + m_upper_open = m_lower_open; + } + m_upper_dep = m_upper.is_infinite() ? 0 : m_manager.mk_join(m_lower_dep, m_upper_dep); + m_lower = ext_numeral(0); + m_lower_open = false; + m_lower_dep = 0; + } + } + else { + // Remark: when n is odd x^n is monotonic. + m_lower.expt(n); + m_upper.expt(n); + } +} + +void interval::display(std::ostream & out) const { + out << (m_lower_open ? "(" : "[") << m_lower << ", " << m_upper << (m_upper_open ? ")" : "]"); +} + +void interval::display_with_dependencies(std::ostream & out) const { + ptr_vector vs; + m_manager.linearize(m_lower_dep, vs); + m_manager.linearize(m_upper_dep, vs); + out << "["; + display(out); + out << ", "; + bool first = true; + ::display(out, vs.begin(), vs.end(), ", ", first); + out << "]"; +} + + + + diff --git a/lib/old_interval.h b/lib/old_interval.h new file mode 100644 index 000000000..ff2ae4b21 --- /dev/null +++ b/lib/old_interval.h @@ -0,0 +1,138 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + old_interval.h + +Abstract: + + Old interval class. It is still used in some modules. + +Author: + + Leonardo de Moura (leonardo) 2008-12-09. + +Revision History: + +--*/ +#ifndef _OLD_INTERVAL_H_ +#define _OLD_INTERVAL_H_ + +#include"rational.h" +#include"dependency.h" + +class ext_numeral { +public: + enum kind { MINUS_INFINITY, FINITE, PLUS_INFINITY }; +private: + kind m_kind; + rational m_value; + explicit ext_numeral(kind k):m_kind(k) {} +public: + ext_numeral():m_kind(FINITE) {} /* zero */ + explicit ext_numeral(bool plus_infinity):m_kind(plus_infinity ? PLUS_INFINITY : MINUS_INFINITY) {} + explicit ext_numeral(rational const & val):m_kind(FINITE), m_value(val) {} + explicit ext_numeral(int i):m_kind(FINITE), m_value(i) {} + ext_numeral(ext_numeral const & other):m_kind(other.m_kind), m_value(other.m_value) {} + bool is_infinite() const { return m_kind != FINITE; } + bool sign() const { return m_kind == MINUS_INFINITY || (m_kind == FINITE && m_value.is_neg()); } + void neg(); + bool is_zero() const { return m_kind == FINITE && m_value.is_zero(); } + bool is_neg() const { return sign(); } + bool is_pos() const { return !is_neg() && !is_zero(); } + rational const & to_rational() const { SASSERT(!is_infinite()); return m_value; } + ext_numeral & operator+=(ext_numeral const & other); + ext_numeral & operator-=(ext_numeral const & other); + ext_numeral & operator*=(ext_numeral const & other); + void inv(); + void expt(unsigned n); + void display(std::ostream & out) const; + friend bool operator==(ext_numeral const & n1, ext_numeral const & n2); + friend bool operator<(ext_numeral const & n1, ext_numeral const & n2); +}; + +bool operator==(ext_numeral const & n1, ext_numeral const & n2); +bool operator<(ext_numeral const & n1, ext_numeral const & n2); +inline bool operator!=(ext_numeral const & n1, ext_numeral const & n2) { return !operator==(n1,n2); } +inline bool operator>(ext_numeral const & n1, ext_numeral const & n2) { return operator<(n2, n1); } +inline bool operator<=(ext_numeral const & n1, ext_numeral const & n2) { return !operator>(n1, n2); } +inline bool operator>=(ext_numeral const & n1, ext_numeral const & n2) { return !operator<(n1, n2); } +inline ext_numeral operator+(ext_numeral const & n1, ext_numeral const & n2) { return ext_numeral(n1) += n2; } +inline ext_numeral operator-(ext_numeral const & n1, ext_numeral const & n2) { return ext_numeral(n1) -= n2; } +inline ext_numeral operator*(ext_numeral const & n1, ext_numeral const & n2) { return ext_numeral(n1) *= n2; } +inline std::ostream & operator<<(std::ostream & out, ext_numeral const & n) { n.display(out); return out; } + +class old_interval { + v_dependency_manager & m_manager; + ext_numeral m_lower; + ext_numeral m_upper; + bool m_lower_open; + bool m_upper_open; + v_dependency * m_lower_dep; // justification for the lower bound + v_dependency * m_upper_dep; // justification for the upper bound + + v_dependency * join(v_dependency * d1, v_dependency * d2) { return m_manager.mk_join(d1, d2); } + v_dependency * join(v_dependency * d1, v_dependency * d2, v_dependency * d3) { return m_manager.mk_join(m_manager.mk_join(d1, d2), d3); } + v_dependency * join(v_dependency * d1, v_dependency * d2, v_dependency * d3, v_dependency * d4); + v_dependency * join_opt(v_dependency * d1, v_dependency * d2, v_dependency * opt1, v_dependency * opt2); + +public: + explicit old_interval(v_dependency_manager & m); + explicit old_interval(v_dependency_manager & m, rational const & lower, bool l_open, v_dependency * l_dep, rational const & upper, bool u_open, v_dependency * u_dep); + explicit old_interval(v_dependency_manager & m, rational const & val, v_dependency * l_dep = 0, v_dependency * u_dep = 0); + explicit old_interval(v_dependency_manager & m, rational const & val, bool open, bool lower, v_dependency * d); + explicit old_interval(v_dependency_manager & m, ext_numeral const& lower, bool l_open, v_dependency * l_dep, ext_numeral const & upper, bool u_open, v_dependency * u_dep); + old_interval(old_interval const & other); + + bool minus_infinity() const { return m_lower.is_infinite(); } + bool plus_infinity() const { return m_upper.is_infinite(); } + bool is_lower_open() const { return m_lower_open; } + bool is_upper_open() const { return m_upper_open; } + v_dependency * get_lower_dependencies() const { return m_lower_dep; } + v_dependency * get_upper_dependencies() const { return m_upper_dep; } + rational const & get_lower_value() const { SASSERT(!minus_infinity()); return m_lower.to_rational(); } + rational const & get_upper_value() const { SASSERT(!plus_infinity()); return m_upper.to_rational(); } + old_interval & operator=(old_interval const & other); + old_interval & operator+=(old_interval const & other); + old_interval & operator-=(old_interval const & other); + old_interval & operator*=(old_interval const & other); + old_interval & operator*=(rational const & value); + old_interval & operator/=(old_interval const & other); + bool operator==(old_interval const & other) const { return m_lower == other.m_lower && m_upper == other.m_upper && m_lower_open == other.m_lower_open && m_upper_open == other.m_upper_open; } + bool contains_zero() const; + bool contains(rational const& v) const; + old_interval & inv(); + void expt(unsigned n); + void neg(); + void display(std::ostream & out) const; + void display_with_dependencies(std::ostream & out) const; + bool is_P() const { return m_lower.is_pos() || m_lower.is_zero(); } + bool is_P0() const { return m_lower.is_zero() && !m_lower_open; } + bool is_P1() const { return m_lower.is_pos() || (m_lower.is_zero() && m_lower_open); } + bool is_N() const { return m_upper.is_neg() || m_upper.is_zero(); } + bool is_N0() const { return m_upper.is_zero() && !m_upper_open; } + bool is_N1() const { return m_upper.is_neg() || (m_upper.is_zero() && m_upper_open); } + bool is_M() const { return m_lower.is_neg() && m_upper.is_pos(); } + bool is_zero() const { return m_lower.is_zero() && m_upper.is_zero(); } + + ext_numeral const& inf() const { return m_lower; } + ext_numeral const& sup() const { return m_upper; } +}; + +inline old_interval operator+(old_interval const & i1, old_interval const & i2) { return old_interval(i1) += i2; } +inline old_interval operator-(old_interval const & i1, old_interval const & i2) { return old_interval(i1) -= i2; } +inline old_interval operator*(old_interval const & i1, old_interval const & i2) { return old_interval(i1) *= i2; } +inline old_interval operator/(old_interval const & i1, old_interval const & i2) { return old_interval(i1) /= i2; } +inline old_interval expt(old_interval const & i, unsigned n) { old_interval tmp(i); tmp.expt(n); return tmp; } +inline std::ostream & operator<<(std::ostream & out, old_interval const & i) { i.display(out); return out; } + +struct interval_detail{}; +inline std::pair wd(old_interval const & i) { interval_detail d; return std::make_pair(i, d); } +inline std::ostream & operator<<(std::ostream & out, std::pair const & p) { p.first.display_with_dependencies(out); return out; } + +// allow "customers" of this file to keep using interval +#define interval old_interval + +#endif /* _OLD_INTERVAL_H_ */ + diff --git a/lib/optional.h b/lib/optional.h new file mode 100644 index 000000000..3a020c034 --- /dev/null +++ b/lib/optional.h @@ -0,0 +1,165 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + optional.h + +Abstract: + + Discriminated union of a type T. + It defines the notion of initialized/uninitialized objects. + +Author: + + Leonardo de Moura (leonardo) 2006-09-29. + +Revision History: + +--*/ + +#ifndef _OPTIONAL_H_ +#define _OPTIONAL_H_ + +template +class optional { + char m_obj[sizeof(T)]; + char m_initialized; + + void construct(const T & val) { + m_initialized = 1; + new (reinterpret_cast(m_obj)) T(val); + } + + void destroy() { + if (m_initialized == 1) { + reinterpret_cast(m_obj)->~T(); + } + m_initialized = 0; + } + +public: + optional(): + m_initialized(0) {} + + explicit optional(const T & val) { + construct(val); + } + + optional(const optional & val): + m_initialized(0) { + if (val.m_initialized == 1) { + construct(*val); + } + } + + ~optional() { + destroy(); + } + + static optional const & undef() { static optional u; return u; } + + bool initialized() const { return m_initialized == 1; } + operator bool() const { return m_initialized == 1; } + bool operator!() const { return m_initialized == 0; } + + T * get() const { + if (m_initialized == 1) { + return reinterpret_cast(m_obj); + } + else { + return 0; + } + } + + void set_invalid() { + if (m_initialized == 1) { + destroy(); + } + } + + T * operator->() { + SASSERT(m_initialized==1); + return reinterpret_cast(m_obj); + } + + T const * operator->() const { + SASSERT(m_initialized==1); + return reinterpret_cast(m_obj); + } + + const T & operator*() const { + SASSERT(m_initialized==1); + return *reinterpret_cast(m_obj); + } + + T & operator*() { + SASSERT(m_initialized==1); + return *reinterpret_cast(m_obj); + } + + optional & operator=(const T & val) { + destroy(); + construct(val); + return * this; + } + + optional & operator=(const optional & val) { + if (&val != this) { + destroy(); + if (val.m_initialized) { + construct(*val); + } + } + return *this; + } +}; + + +/** + \brief Template specialization for pointers. NULL represents uninitialized pointers. + */ +template +class optional { + T * m_ptr; + + static optional m_undef; + +public: + + optional():m_ptr(0) {} + + explicit optional(T * val):m_ptr(val) {} + + optional(const optional & val):m_ptr(val.m_ptr) {} + + static optional const & undef() { return m_undef; } + + bool initialized() const { return m_ptr != 0 ; } + + operator bool() const { return m_ptr != 0; } + + bool operator!() const { return m_ptr == 0; } + + void reset() { m_ptr = 0; } + + optional & operator=(T * val) { + m_ptr = val; + return *this; + } + + optional & operator=(const optional & val) { + m_ptr = val.m_ptr; + return *this; + } + + T ** operator->() { return &m_ptr; } + + T * operator*() const { return m_ptr; } + + T * & operator*() { return m_ptr; } +}; + + +#endif /* _OPTIONAL_H_ */ + diff --git a/lib/order.cpp b/lib/order.cpp new file mode 100644 index 000000000..ebf9cdca1 --- /dev/null +++ b/lib/order.cpp @@ -0,0 +1,51 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + order.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-15. + +Revision History: + +--*/ + +#include"order.h" + +bool order::equal(expr_offset const & _t1, expr_offset const & _t2, substitution * s) { + if (_t1 == _t2) + return true; + if (s == 0) + return false; + m_eq_todo.reset(); + m_eq_todo.push_back(expr_offset_pair(_t1, _t2)); + while (!m_eq_todo.empty()) { + expr_offset_pair const & p = m_eq_todo.back(); + expr_offset t1 = find(p.first); + expr_offset t2 = find(p.second); + m_eq_todo.pop_back(); + if (t1 == t2) + continue; + expr * n1 = t1.get_expr(); + expr * n2 = t2.get_expr(); + if (!is_app(n1) || !is_app(n2)) + return false; + if (to_app(n1)->get_decl() != to_app(n2)->get_decl()) + return false; + if (to_app(n1)->get_num_args() != to_app(n2)->get_num_args()) + return false; + unsigned num = to_app(n1)->get_num_args(); + for (unsigned i = 0; i < num; i++) + m_eq_todo.push_back(expr_offset_pair(expr_offset(to_app(n1)->get_arg(i), t1.get_offset()), + expr_offset(to_app(n2)->get_arg(i), t2.get_offset()))); + } + return true; +} + diff --git a/lib/order.h b/lib/order.h new file mode 100644 index 000000000..93d8a9eb0 --- /dev/null +++ b/lib/order.h @@ -0,0 +1,87 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + order.h + +Abstract: + + Abstract class for term orderings. + +Author: + + Leonardo de Moura (leonardo) 2008-01-28. + +Revision History: + +--*/ +#ifndef _ORDER_H_ +#define _ORDER_H_ + +#include"substitution.h" +#include"precedence.h" +#include"trace.h" + +class order { +protected: + ast_manager & m_manager; + precedence * m_precedence; + substitution * m_subst; + + typedef std::pair expr_offset_pair; + svector m_eq_todo; + + expr_offset find(expr_offset t) { + while (m_subst && is_var(t.get_expr()) && m_subst->find(to_var(t.get_expr()), t.get_offset(), t)) + ; + return t; + } + + bool f_greater(func_decl * f, func_decl * g) const { + bool r = m_precedence->compare(f, g) > 0; + TRACE("order", tout << f->get_name() << " greater than " << g->get_name() << " == " << r << "\n";); + return r; + } +public: + enum result { + UNKNOWN, + UNCOMPARABLE, + EQUAL, + GREATER, + LESSER, + NOT_GTEQ + }; + static bool ok(result r) { return r == EQUAL || r == GREATER || r == LESSER; } + order(ast_manager & m, precedence * p):m_manager(m), m_precedence(p) { SASSERT(p); } + virtual ~order() { dealloc(m_precedence); } + virtual void reserve(unsigned num_offsets, unsigned num_vars) {} + virtual void reserve_offsets(unsigned num_offsets) {} + virtual void reserve_vars(unsigned num_vars) {} + ast_manager & get_manager() { return m_manager; } + + virtual result compare(expr_offset const & t1, expr_offset const & t2, substitution * s) = 0; + result compare(expr * t1, expr * t2, unsigned offset, substitution * s) { return compare(expr_offset(t1, offset), expr_offset(t2, offset), s); } + result compare(expr * t1, expr * t2) { return compare(expr_offset(t1, 0), expr_offset(t2, 0), 0); } + virtual bool greater(expr_offset const & t1, expr_offset const & t2, substitution * s) = 0; + bool greater(expr * t1, expr * t2) { return greater(expr_offset(t1,0), expr_offset(t2,0), 0); } + bool greater(expr * t1, expr * t2, substitution * s) { return greater(expr_offset(t1,0), expr_offset(t2,0), s); } + bool greater(expr * t1, expr * t2, unsigned offset, substitution * s) { + return greater(expr_offset(t1, offset), expr_offset(t2, offset), s); + } + /** + \brief Return a value > 0 if t1 is greater than t2, 0 if t1 == t2, and < 0 otherwise (uncomparable, unknown, lesser). + */ + virtual int compare_ge(expr_offset const & t1, expr_offset const & t2, substitution * s) = 0; + + /** + \brief Return true if the given terms are equal modulo the given substitution + */ + bool equal(expr_offset const & t1, expr_offset const & t2, substitution * s); + + bool equal(expr * t1, expr * t2, unsigned offset = 0, substitution * s = 0) { + return equal(expr_offset(t1, offset), expr_offset(t2, offset), s); + } +}; + +#endif /* _ORDER_H_ */ diff --git a/lib/order_params.cpp b/lib/order_params.cpp new file mode 100644 index 000000000..47fee103d --- /dev/null +++ b/lib/order_params.cpp @@ -0,0 +1,27 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + order_params.cpp + +Abstract: + + Term ordering parameters. + +Author: + + Leonardo de Moura (leonardo) 2008-01-28. + +Revision History: + +--*/ +#include"order_params.h" + +void order_params::register_params(ini_params & p) { + p.register_symbol_list_param("PRECEDENCE", m_order_precedence, "describe a (partial) precedence for the term ordering used in the Superposition Calculus module. The precedence is lists of function symbols. Example: PRECEDENCE=\"(f, g, h)\""); + p.register_symbol_list_param("PRECEDENCE_GEN", m_order_precedence_gen, "describe how a total precedence order is generated. The generator is a sequence of simple (partial) orders with an optional '-' (indicating the next (partial) order should be inverted). The available simple (partial) orders are: user (the order specified by precedence); arity; interpreted (interpreted function symbols are considered smaller); definition (defined function symbols are considered bigger); frequency; arbitrary (total arbitrary order generated by Z3). Example: PRECEDENCE_GEN=\"user interpreted - arity arbitraty\""); + p.register_symbol_nat_list_param("ORDER_WEIGHTS", m_order_weights, "describe a (partial) assignment of weights to function symbols for term orderings (e.g., KBO). The assigment is a list of pairs of the form f:n where f is a string and n is a natural. Example: WEIGHTS=\"(f:1, g:2, h:3)\""); + p.register_unsigned_param("ORDER_VAR_WEIGHT", m_order_var_weight, "weight of variables in term orderings (e.g., KBO)"); + p.register_int_param("ORDER", 0, 1, reinterpret_cast(m_order_kind), "Term ordering: 0 - KBO, 1 - LPO"); +} diff --git a/lib/order_params.h b/lib/order_params.h new file mode 100644 index 000000000..17b7fa238 --- /dev/null +++ b/lib/order_params.h @@ -0,0 +1,44 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + order_params.h + +Abstract: + + Term ordering parameters. + +Author: + + Leonardo de Moura (leonardo) 2008-01-28. + +Revision History: + +--*/ +#ifndef _ORDER_PARAMS_H_ +#define _ORDER_PARAMS_H_ + +#include"ini_file.h" + +enum order_kind { + ORD_KBO, + ORD_LPO +}; + +struct order_params { + svector m_order_precedence; + svector m_order_precedence_gen; + svector m_order_weights; + unsigned m_order_var_weight; + order_kind m_order_kind; + + order_params(): + m_order_var_weight(1), + m_order_kind(ORD_KBO) { + } + + void register_params(ini_params & p); +}; + +#endif /* _ORDER_PARAMS_H_ */ diff --git a/lib/page.cpp b/lib/page.cpp new file mode 100644 index 000000000..8fddcc075 --- /dev/null +++ b/lib/page.cpp @@ -0,0 +1,68 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + page.cpp + +Abstract: + + Goodies for manipulating pages of memory. + +Author: + + Leonardo de Moura (leonardo) 2011-02-27. + +Revision History: + +--*/ +#include"page.h" +#include"debug.h" + +inline void set_page_header(char * page, char * prev, bool default_page) { + size_t header = reinterpret_cast(prev) | static_cast(default_page); + reinterpret_cast(page)[-1] = header; + SASSERT(is_default_page(page) == default_page); + SASSERT(prev_page(page) == prev); +} + +inline char * alloc_page(size_t s) { char * r = alloc_svect(char, s+PAGE_HEADER_SZ); return r + PAGE_HEADER_SZ; } + +inline void del_page(char * page) { dealloc_svect(page - PAGE_HEADER_SZ); } + +void del_pages(char * page) { + while (page != 0) { + char * prev = prev_page(page); + del_page(page); + page = prev; + } +} + +char * allocate_default_page(char * prev, char * & free_pages) { + char * r; + if (free_pages) { + r = free_pages; + free_pages = prev_page(free_pages); + } + else { + r = alloc_page(DEFAULT_PAGE_SIZE); + } + set_page_header(r, prev, true); + return r; +} + +char * allocate_page(char * prev, size_t sz) { + char * r = alloc_page(sz); + set_page_header(r, prev, false); + return r; +} + +void recycle_page(char * p, char * & free_pages) { + if (is_default_page(p)) { + set_page_header(p, free_pages, true); + free_pages = p; + } + else { + del_page(p); + } +} diff --git a/lib/page.h b/lib/page.h new file mode 100644 index 000000000..2c8527497 --- /dev/null +++ b/lib/page.h @@ -0,0 +1,42 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + page.h + +Abstract: + + Goodies for manipulating pages of memory. + +Author: + + Leonardo de Moura (leonardo) 2011-02-27. + +Revision History: + +--*/ +#ifndef _PAGE_H_ +#define _PAGE_H_ + +#include"memory_manager.h" + +#define PAGE_HEADER_SZ sizeof(size_t) +#define DEFAULT_PAGE_SIZE (8192 - PAGE_HEADER_SZ) +#define PAGE_HEADER_MASK (static_cast(-1) - 1) + +inline char * prev_page(char * page) { + size_t tagged_ptr = reinterpret_cast(page)[-1]; + return reinterpret_cast(tagged_ptr & PAGE_HEADER_MASK); +} +inline bool is_default_page(char * page) { + size_t tagged_ptr = reinterpret_cast(page)[-1]; + return static_cast(tagged_ptr & 1); +} +inline char * end_of_default_page(char * p) { return p + DEFAULT_PAGE_SIZE; } +void del_pages(char * page); +char * allocate_default_page(char * prev, char * & free_pages); +char * allocate_page(char * prev, size_t sz); +void recycle_page(char * p, char * & free_pages); + +#endif diff --git a/lib/parameters.h b/lib/parameters.h new file mode 100644 index 000000000..80c0d52d6 --- /dev/null +++ b/lib/parameters.h @@ -0,0 +1,32 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + parameters.h + +Abstract: + + Settings and parameters supplied to pre-processing and solver + modules. + +Author: + + Leonardo de Moura (leonardo) 2006-10-18. + Nikolaj Bjorner (nbjorner) 2007-02-15 + +Revision History: + 2007-02-15, nbjorner. + Hoisted out from simplify_parser.h and core_theory_types.h + in order to share functionality with SMTLIB and other + front-ends without bringing in the simplifier and core_theory. + +--*/ +#ifndef _PARAMETERS_H_ +#define _PARAMETERS_H_ + +#include"sat_params.h" +#include"core_theory_params.h" +#include"front_end_params.h" + +#endif diff --git a/lib/parametric_cmd.cpp b/lib/parametric_cmd.cpp new file mode 100644 index 000000000..bc06d3ee6 --- /dev/null +++ b/lib/parametric_cmd.cpp @@ -0,0 +1,64 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + parametric_cmd.h + +Abstract: + A generic parametric cmd. + +Author: + + Leonardo (leonardo) 2011-04-22 + +Notes: + +--*/ +#include +#include"parametric_cmd.h" + +char const * parametric_cmd::get_descr(cmd_context & ctx) const { + if (m_descr == 0) { + const_cast(this)->m_descr = alloc(string_buffer<>); + m_descr->append(get_main_descr()); + m_descr->append("\nThe following options are available:\n"); + std::ostringstream buf; + pdescrs(ctx).display(buf, 2); + m_descr->append(buf.str().c_str()); + } + return m_descr->c_str(); +} + +cmd_arg_kind parametric_cmd::next_arg_kind(cmd_context & ctx) const { + if (m_last == symbol::null) return CPK_KEYWORD; + return pdescrs(ctx).get_kind(m_last); +} + +void parametric_cmd::set_next_arg(cmd_context & ctx, symbol const & s) { + if (m_last == symbol::null) { + m_last = s; + if (pdescrs(ctx).get_kind(m_last.bare_str()) == CPK_INVALID) + throw cmd_exception("invalid keyword argument"); + return; + } + m_params.set_sym(m_last.bare_str(), s); + m_last = symbol::null; +} + +param_descrs const & parametric_cmd::pdescrs(cmd_context & ctx) const { + if (!m_pdescrs) { + parametric_cmd * _this = const_cast(this); + _this->m_pdescrs = alloc(param_descrs); + _this->init_pdescrs(ctx, *(_this->m_pdescrs)); + } + return *m_pdescrs; +} + + + + + + + + diff --git a/lib/parametric_cmd.h b/lib/parametric_cmd.h new file mode 100644 index 000000000..4acf41b9e --- /dev/null +++ b/lib/parametric_cmd.h @@ -0,0 +1,76 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + parametric_cmd.h + +Abstract: + A generic parametric cmd. + +Author: + + Leonardo (leonardo) 2011-04-22 + +Notes: + +--*/ +#ifndef _PARAMETRIC_CMD_H_ +#define _PARAMETRIC_CMD_H_ + +#include"params.h" +#include"symbol.h" +#include"string_buffer.h" +#include"cmd_context_types.h" + +class parametric_cmd : public cmd { +public: + symbol m_last; + string_buffer<> * m_descr; + params_ref m_params; + scoped_ptr m_pdescrs; +public: + parametric_cmd(char const * name):cmd(name), m_descr(0) {} + virtual ~parametric_cmd() { if (m_descr) dealloc(m_descr); } + virtual void init_pdescrs(cmd_context & ctx, param_descrs & d) = 0; + param_descrs const & pdescrs(cmd_context & ctx) const; + params_ref const & ps() const { return m_params; } + virtual char const * get_main_descr() const = 0; + virtual char const * get_descr(cmd_context & ctx) const; + virtual unsigned get_arity() const { return VAR_ARITY; } + virtual void prepare(cmd_context & ctx) { m_last = symbol::null; m_params.reset(); } + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const; + virtual void set_next_arg(cmd_context & ctx, symbol const & s); + virtual void set_next_arg(cmd_context & ctx, unsigned val) { + m_params.set_uint(m_last, val); + m_last = symbol::null; + } + virtual void set_next_arg(cmd_context & ctx, bool val) { + m_params.set_bool(m_last, val); + m_last = symbol::null; + } + virtual void set_next_arg(cmd_context & ctx, rational const & val) { + m_params.set_rat(m_last, val); + m_last = symbol::null; + } + virtual void set_next_arg(cmd_context & ctx, char const * val) { + m_params.set_str(m_last, val); + m_last = symbol::null; + } + virtual void set_next_arg(cmd_context & ctx, sort * s) { + m_params.set_sort(m_last, s); + m_last = symbol::null; + } + virtual void set_next_arg(cmd_context & ctx, expr * t) { + m_params.set_expr(m_last, t); + m_last = symbol::null; + } + virtual void set_next_arg(cmd_context & ctx, func_decl * f) { + m_params.set_func_decl(m_last, f); + m_last = symbol::null; + } +}; + +#endif + + diff --git a/lib/params.cpp b/lib/params.cpp new file mode 100644 index 000000000..bcb3ce078 --- /dev/null +++ b/lib/params.cpp @@ -0,0 +1,863 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + params.cpp + +Abstract: + + Parameters + +Author: + + Leonardo (leonardo) 2011-05-09 + +Notes: + +--*/ +#include"params.h" +#include"ast.h" +#include"rational.h" +#include"symbol.h" +#include"dictionary.h" + +struct param_descrs::imp { + typedef std::pair info; + dictionary m_info; + svector m_names; + + void insert(symbol const & name, param_kind k, char const * descr) { + SASSERT(!name.is_numerical()); + info i; + if (m_info.find(name, i)) { + SASSERT(i.first == k); + return; + } + m_info.insert(name, info(k, descr)); + m_names.push_back(name); + } + + void erase(symbol const & name) { + m_info.erase(name); + } + + param_kind get_kind(symbol const & name) const { + info i; + if (m_info.find(name, i)) + return i.first; + return CPK_INVALID; + } + + unsigned size() const { + return m_names.size(); + } + + symbol get_param_name(unsigned idx) const { + return m_names[idx]; + } + + struct lt { + bool operator()(symbol const & s1, symbol const & s2) const { return strcmp(s1.bare_str(), s2.bare_str()) < 0; } + }; + + void display(std::ostream & out, unsigned indent) const { + svector names; + dictionary::iterator it = m_info.begin(); + dictionary::iterator end = m_info.end(); + for (; it != end; ++it) { + names.push_back(it->m_key); + } + std::sort(names.begin(), names.end(), lt()); + svector::iterator it2 = names.begin(); + svector::iterator end2 = names.end(); + for (; it2 != end2; ++it2) { + for (unsigned i = 0; i < indent; i++) out << " "; + out << *it2; + info d; + d.second = 0; + m_info.find(*it2, d); + SASSERT(d.second); + out << " (" << d.first << ") " << d.second << "\n"; + } + } +}; + +param_descrs::param_descrs() { + m_imp = alloc(imp); +} + +param_descrs::~param_descrs() { + dealloc(m_imp); +} + +void param_descrs::insert(symbol const & name, param_kind k, char const * descr) { + m_imp->insert(name, k, descr); +} + +void param_descrs::insert(char const * name, param_kind k, char const * descr) { + insert(symbol(name), k, descr); +} + +void param_descrs::erase(symbol const & name) { + m_imp->erase(name); +} + +void param_descrs::erase(char const * name) { + erase(symbol(name)); +} + +param_kind param_descrs::get_kind(symbol const & name) const { + return m_imp->get_kind(name); +} + +param_kind param_descrs::get_kind(char const * name) const { + return get_kind(symbol(name)); +} + +unsigned param_descrs::size() const { + return m_imp->size(); +} + +symbol param_descrs::get_param_name(unsigned i) const { + return m_imp->get_param_name(i); +} + +void param_descrs::display(std::ostream & out, unsigned indent) const { + return m_imp->display(out, indent); +} + +void insert_max_memory(param_descrs & r) { + r.insert(":max-memory", CPK_UINT, "(default: infty) maximum amount of memory in megabytes."); +} + +void insert_max_steps(param_descrs & r) { + r.insert(":max-steps", CPK_UINT, "(default: infty) maximum number of steps."); +} + +void insert_produce_models(param_descrs & r) { + r.insert(":produce-models", CPK_BOOL, "(default: false) model generation."); +} + +void insert_produce_proofs(param_descrs & r) { + r.insert(":produce-proofs", CPK_BOOL, "(default: false) proof generation."); +} + +void insert_timeout(param_descrs & r) { + r.insert(":timeout", CPK_UINT, "(default: infty) timeout in milliseconds."); +} + +class params { + friend class params_ref; + struct value { + param_kind m_kind; + union { + bool m_bool_value; + unsigned m_uint_value; + double m_double_value; + char const * m_str_value; + char const * m_sym_value; + rational * m_rat_value; + ast * m_ast_value; + }; + }; + typedef std::pair entry; + ast_manager * m_manager; + svector m_entries; + unsigned m_ref_count; + + void del_value(entry & e); + void del_values(); + +public: + params():m_manager(0), m_ref_count(0) {} + ~params() { + reset(); + } + + 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 set_manager(ast_manager & m); + + bool empty() const { return m_entries.empty(); } + bool contains(symbol const & k) const; + bool contains(char const * k) const; + + void reset(); + void reset(symbol const & k); + void reset(char const * k); + + void validate(param_descrs const & p) const { + svector::const_iterator it = m_entries.begin(); + svector::const_iterator end = m_entries.end(); + for (; it != end; ++it) { + param_kind expected = p.get_kind(it->first); + if (expected == CPK_INVALID) + throw default_exception("unknown parameter '%s'", it->first.str().c_str()); + if (it->second.m_kind != expected) + throw default_exception("parameter kind mismatch '%s'", it->first.str().c_str()); + } + } + + // getters + bool get_bool(symbol const & k, bool _default) const; + bool get_bool(char const * k, bool _default) const; + unsigned get_uint(symbol const & k, unsigned _default) const; + unsigned get_uint(char const * k, unsigned _default) const; + double get_double(symbol const & k, double _default) const; + double get_double(char const * k, double _default) const; + char const * get_str(symbol const & k, char const * _default) const; + char const * get_str(char const * k, char const * _default) const; + rational get_rat(symbol const & k, rational const & _default) const; + rational get_rat(char const * k, rational const & _default) const; + symbol get_sym(symbol const & k, symbol const & _default) const; + symbol get_sym(char const * k, symbol const & _default) const; + expr * get_expr(symbol const & k, expr * _default) const; + expr * get_expr(char const * k, expr * _default) const; + func_decl * get_func_decl(symbol const & k, func_decl * _default) const; + func_decl * get_func_decl(char const * k, func_decl * _default) const; + sort * get_sort(symbol const & k, sort * _default) const; + sort * get_sort(char const * k, sort * _default) const; + + // setters + void set_bool(symbol const & k, bool v); + void set_bool(char const * k, bool v); + void set_uint(symbol const & k, unsigned v); + void set_uint(char const * k, unsigned v); + void set_double(symbol const & k, double v); + void set_double(char const * k, double v); + void set_str(symbol const & k, char const * v); + void set_str(char const * k, char const * v); + void set_rat(symbol const & k, rational const & v); + void set_rat(char const * k, rational const & v); + void set_sym(symbol const & k, symbol const & v); + void set_sym(char const * k, symbol const & v); + void set_expr(symbol const & k, expr * v); + void set_expr(char const * k, expr * v); + void set_func_decl(symbol const & k, func_decl * v); + void set_func_decl(char const * k, func_decl * v); + void set_sort(symbol const & k, sort * v); + void set_sort(char const * k, sort * v); + + void display(std::ostream & out) const { + out << "(params"; + svector::const_iterator it = m_entries.begin(); + svector::const_iterator end = m_entries.end(); + for (; it != end; ++it) { + out << " " << it->first; + switch (it->second.m_kind) { + case CPK_BOOL: + out << " " << it->second.m_bool_value; + break; + case CPK_UINT: + out << " " <second.m_uint_value; + break; + case CPK_DOUBLE: + out << " " << it->second.m_double_value; + break; + case CPK_NUMERAL: + out << " " << *(it->second.m_rat_value); + break; + case CPK_SYMBOL: + out << " " << symbol::mk_symbol_from_c_ptr(it->second.m_sym_value); + break; + case CPK_STRING: + out << " " << it->second.m_str_value; + break; + case CPK_EXPR: + case CPK_FUNC_DECL: + case CPK_SORT: + out << " #" << it->second.m_ast_value->get_id(); + break; + default: + UNREACHABLE(); + break; + } + } + out << ")"; + } +}; + +params_ref::~params_ref() { + if (m_params) + m_params->dec_ref(); +} + +params_ref::params_ref(params_ref const & p): + m_params(0) { + operator=(p); +} + +void params_ref::display(std::ostream & out) const { + if (m_params) + m_params->display(out); + else + out << "(params)"; +} + +void params_ref::validate(param_descrs const & p) const { + if (m_params) + m_params->validate(p); +} + +params_ref & params_ref::operator=(params_ref const & p) { + if (p.m_params) + p.m_params->inc_ref(); + if (m_params) + m_params->dec_ref(); + m_params = p.m_params; + return *this; +} + +void params_ref::copy(params_ref const & src) { + if (m_params == 0) + operator=(src); + else { + init(); + copy_core(src.m_params); + } +} + +void params_ref::copy_core(params const * src) { + if (src == 0) + return; + svector::const_iterator it = src->m_entries.begin(); + svector::const_iterator end = src->m_entries.end(); + for (; it != end; ++it) { + switch (it->second.m_kind) { + case CPK_BOOL: + m_params->set_bool(it->first, it->second.m_bool_value); + break; + case CPK_UINT: + m_params->set_uint(it->first, it->second.m_uint_value); + break; + case CPK_DOUBLE: + m_params->set_double(it->first, it->second.m_double_value); + break; + case CPK_NUMERAL: + m_params->set_rat(it->first, *(it->second.m_rat_value)); + break; + case CPK_SYMBOL: + m_params->set_sym(it->first, symbol::mk_symbol_from_c_ptr(it->second.m_sym_value)); + break; + case CPK_STRING: + m_params->set_str(it->first, it->second.m_str_value); + break; + case CPK_EXPR: + m_params->set_expr(it->first, static_cast(it->second.m_ast_value)); + break; + case CPK_FUNC_DECL: + m_params->set_func_decl(it->first, static_cast(it->second.m_ast_value)); + break; + case CPK_SORT: + m_params->set_sort(it->first, static_cast(it->second.m_ast_value)); + break; + default: + UNREACHABLE(); + break; + } + } +} + +void params_ref::init() { + if (!m_params) { + m_params = alloc(params); + m_params->inc_ref(); + } + else if (m_params->m_ref_count > 1) { + params * old = m_params; + m_params = alloc(params); + m_params->inc_ref(); + m_params->m_manager = old->m_manager; + copy_core(old); + old->dec_ref(); + } + + SASSERT(m_params->m_ref_count == 1); +} + +bool params_ref::get_bool(symbol const & k, bool _default) const { return m_params ? m_params->get_bool(k, _default) : _default; } +bool params_ref::get_bool(char const * k, bool _default) const { return m_params ? m_params->get_bool(k, _default) : _default; } +unsigned params_ref::get_uint(symbol const & k, unsigned _default) const { return m_params ? m_params->get_uint(k, _default) : _default; } +unsigned params_ref::get_uint(char const * k, unsigned _default) const { return m_params ? m_params->get_uint(k, _default) : _default; } +double params_ref::get_double(symbol const & k, double _default) const { return m_params ? m_params->get_double(k, _default) : _default; } +double params_ref::get_double(char const * k, double _default) const { return m_params ? m_params->get_double(k, _default) : _default; } +char const * params_ref::get_str(symbol const & k, char const * _default) const { return m_params ? m_params->get_str(k, _default) : _default; } +char const * params_ref::get_str(char const * k, char const * _default) const { return m_params ? m_params->get_str(k, _default) : _default; } +expr * params_ref::get_expr(symbol const & k, expr * _default) const { return m_params ? m_params->get_expr(k, _default) : _default; } +expr * params_ref::get_expr(char const * k, expr * _default) const { return m_params ? m_params->get_expr(k, _default) : _default; } +func_decl * params_ref::get_func_decl(symbol const & k, func_decl * _default) const { return m_params ? m_params->get_func_decl(k, _default) : _default; } +func_decl * params_ref::get_func_decl(char const * k, func_decl * _default) const { return m_params ? m_params->get_func_decl(k, _default) : _default; } +sort * params_ref::get_sort(symbol const & k, sort * _default) const { return m_params ? m_params->get_sort(k, _default) : _default; } +sort * params_ref::get_sort(char const * k, sort * _default) const { return m_params ? m_params->get_sort(k, _default) : _default; } + +rational params_ref::get_rat(symbol const & k, rational const & _default) const { + return m_params ? m_params->get_rat(k, _default) : _default; +} + +rational params_ref::get_rat(char const * k, rational const & _default) const { + return m_params ? m_params->get_rat(k, _default) : _default; +} + +symbol params_ref::get_sym(symbol const & k, symbol const & _default) const { + return m_params ? m_params->get_sym(k, _default) : _default; +} + +symbol params_ref::get_sym(char const * k, symbol const & _default) const { + return m_params ? m_params->get_sym(k, _default) : _default; +} + +void params_ref::set_manager(ast_manager & m) { + init(); + m_params->set_manager(m); +} + +bool params_ref::empty() const { + if (!m_params) + return true; + return m_params->empty(); +} + +bool params_ref::contains(symbol const & k) const { + if (!m_params) + return false; + return m_params->contains(k); +} + +bool params_ref::contains(char const * k) const { + if (!m_params) + return false; + return m_params->contains(k); +} + +void params_ref::reset() { + if (m_params) + m_params->reset(); +} + +void params_ref::reset(symbol const & k) { + if (m_params) + m_params->reset(k); +} + +void params_ref::reset(char const * k) { + if (m_params) + m_params->reset(k); +} + +void params_ref::set_bool(symbol const & k, bool v) { + init(); + m_params->set_bool(k, v); +} + +void params_ref::set_bool(char const * k, bool v) { + init(); + m_params->set_bool(k, v); +} + +void params_ref::set_uint(symbol const & k, unsigned v) { + init(); + m_params->set_uint(k, v); +} + +void params_ref::set_uint(char const * k, unsigned v) { + init(); + m_params->set_uint(k, v); +} + +void params_ref::set_double(symbol const & k, double v) { + init(); + m_params->set_double(k, v); +} + +void params_ref::set_double(char const * k, double v) { + init(); + m_params->set_double(k, v); +} + +void params_ref::set_str(symbol const & k, char const * v) { + init(); + m_params->set_str(k, v); +} + +void params_ref::set_str(char const * k, char const * v) { + init(); + m_params->set_str(k, v); +} + +void params_ref::set_rat(symbol const & k, rational const & v) { + init(); + m_params->set_rat(k, v); +} + +void params_ref::set_rat(char const * k, rational const & v) { + init(); + m_params->set_rat(k, v); +} + +void params_ref::set_sym(symbol const & k, symbol const & v) { + init(); + m_params->set_sym(k, v); +} + +void params_ref::set_sym(char const * k, symbol const & v) { + init(); + m_params->set_sym(k, v); +} + +void params_ref::set_expr(symbol const & k, expr * v) { + init(); + m_params->set_expr(k, v); +} + +void params_ref::set_expr(char const * k, expr * v) { + init(); + m_params->set_expr(k, v); +} + +void params_ref::set_func_decl(symbol const & k, func_decl * v) { + init(); + m_params->set_func_decl(k, v); +} + +void params_ref::set_func_decl(char const * k, func_decl * v) { + init(); + m_params->set_func_decl(k, v); +} + +void params_ref::set_sort(symbol const & k, sort * v) { + init(); + m_params->set_sort(k, v); +} + +void params_ref::set_sort(char const * k, sort * v) { + init(); + m_params->set_sort(k, v); +} + +void params::del_value(entry & e) { + switch (e.second.m_kind) { + case CPK_NUMERAL: + if (e.second.m_kind == CPK_NUMERAL) + dealloc(e.second.m_rat_value); + break; + case CPK_EXPR: + case CPK_SORT: + case CPK_FUNC_DECL: + SASSERT(m_manager); + m_manager->dec_ref(e.second.m_ast_value); + return; + default: + return; + } +} + +void params::set_manager(ast_manager & m) { + m_manager = &m; +} + +#define TRAVERSE_ENTRIES(CODE) { \ + svector::iterator it = m_entries.begin(); \ + svector::iterator end = m_entries.end(); \ + for (; it != end; ++it) { \ + CODE \ + } \ +} + +#define TRAVERSE_CONST_ENTRIES(CODE) { \ + svector::const_iterator it = m_entries.begin(); \ + svector::const_iterator end = m_entries.end(); \ + for (; it != end; ++it) { \ + CODE \ + } \ +} + +void params::del_values() { + TRAVERSE_ENTRIES(del_value(*it);); +} + +#define CONTAINS(k) { \ + if (empty()) \ + return false; \ + TRAVERSE_CONST_ENTRIES(if (it->first == k) return true;); \ + return false; \ +} + +bool params::contains(symbol const & k) const { + CONTAINS(k); +} + +bool params::contains(char const * k) const { + CONTAINS(k); +} + +void params::reset() { + del_values(); + m_entries.finalize(); + SASSERT(empty()); +} + +#define RESET(k) { \ + if (empty()) return; \ + TRAVERSE_ENTRIES(if (it->first == k) { \ + svector::iterator it2 = it; \ + del_value(*it2); \ + ++it; \ + for (; it != end; ++it, ++it2) { \ + *it2 = *it; \ + } \ + m_entries.pop_back(); \ + return; \ + }); \ +} + +void params::reset(symbol const & k) { + RESET(k); +} + +void params::reset(char const * k) { + RESET(k); +} + +#define GET_VALUE(MATCH_CODE, KIND) { \ + if (empty()) return _default; \ + TRAVERSE_CONST_ENTRIES(if (it->first == k && it->second.m_kind == KIND) { \ + MATCH_CODE \ + }); \ + return _default; \ +} + +#define GET_SIMPLE_VALUE(FIELD_NAME, KIND) GET_VALUE(return it->second.FIELD_NAME;, KIND) + +bool params::get_bool(symbol const & k, bool _default) const { + GET_SIMPLE_VALUE(m_bool_value, CPK_BOOL); +} + +bool params::get_bool(char const * k, bool _default) const { + GET_SIMPLE_VALUE(m_bool_value, CPK_BOOL); +} + +unsigned params::get_uint(symbol const & k, unsigned _default) const { + GET_SIMPLE_VALUE(m_uint_value, CPK_UINT); +} + +unsigned params::get_uint(char const * k, unsigned _default) const { + GET_SIMPLE_VALUE(m_uint_value, CPK_UINT); +} + +double params::get_double(symbol const & k, double _default) const { + GET_SIMPLE_VALUE(m_double_value, CPK_DOUBLE); +} + +double params::get_double(char const * k, double _default) const { + GET_SIMPLE_VALUE(m_double_value, CPK_DOUBLE); +} + +char const * params::get_str(symbol const & k, char const * _default) const { + GET_SIMPLE_VALUE(m_str_value, CPK_STRING); +} + +char const * params::get_str(char const * k, char const * _default) const { + GET_SIMPLE_VALUE(m_str_value, CPK_STRING); +} + +rational params::get_rat(symbol const & k, rational const & _default) const { + if (empty()) return _default; + TRAVERSE_CONST_ENTRIES(if (it->first == k) { + if (it->second.m_kind == CPK_NUMERAL) { + return *(it->second.m_rat_value); + } + if (it->second.m_kind == CPK_UINT) { + return rational(static_cast(it->second.m_uint_value)); + } + }); + return _default; +} + +rational params::get_rat(char const * k, rational const & _default) const { + if (empty()) return _default; + TRAVERSE_CONST_ENTRIES(if (it->first == k) { + if (it->second.m_kind == CPK_NUMERAL) { + return *(it->second.m_rat_value); + } + if (it->second.m_kind == CPK_UINT) { + return rational(static_cast(it->second.m_uint_value)); + } + }); + return _default; +} + +symbol params::get_sym(symbol const & k, symbol const & _default) const { + GET_VALUE(return symbol::mk_symbol_from_c_ptr(it->second.m_sym_value);, CPK_SYMBOL); +} + +symbol params::get_sym(char const * k, symbol const & _default) const { + GET_VALUE(return symbol::mk_symbol_from_c_ptr(it->second.m_sym_value);, CPK_SYMBOL); +} + +expr * params::get_expr(symbol const & k, expr * _default) const { + GET_VALUE(return static_cast(it->second.m_ast_value);, CPK_EXPR); +} + +expr * params::get_expr(char const * k, expr * _default) const { + GET_VALUE(return static_cast(it->second.m_ast_value);, CPK_EXPR); +} + +func_decl * params::get_func_decl(symbol const & k, func_decl * _default) const { + GET_VALUE(return static_cast(it->second.m_ast_value);, CPK_FUNC_DECL); +} + +func_decl * params::get_func_decl(char const * k, func_decl * _default) const { + GET_VALUE(return static_cast(it->second.m_ast_value);, CPK_FUNC_DECL); +} + +sort * params::get_sort(symbol const & k, sort * _default) const { + GET_VALUE(return static_cast(it->second.m_ast_value);, CPK_SORT); +} + +sort * params::get_sort(char const * k, sort * _default) const { + GET_VALUE(return static_cast(it->second.m_ast_value);, CPK_SORT); +} + +#define SET_VALUE(MATCH_CODE, ADD_CODE) { \ + TRAVERSE_ENTRIES(if (it->first == k) { \ + MATCH_CODE \ + return; \ + }); \ + ADD_CODE \ +} + +#define SET_SIMPLE_VALUE(FIELD_NAME, KIND) SET_VALUE({ \ + del_value(*it); \ + it->second.m_kind = KIND; \ + it->second.FIELD_NAME = v; \ +}, \ +{ \ + entry new_entry; \ + new_entry.first = symbol(k); \ + new_entry.second.m_kind = KIND; \ + new_entry.second.FIELD_NAME = v; \ + m_entries.push_back(new_entry); \ +}) + +// setters +void params::set_bool(symbol const & k, bool v) { + SET_SIMPLE_VALUE(m_bool_value, CPK_BOOL); +} + +void params::set_bool(char const * k, bool v) { + SET_SIMPLE_VALUE(m_bool_value, CPK_BOOL); +} + +void params::set_uint(symbol const & k, unsigned v) { + SET_SIMPLE_VALUE(m_uint_value, CPK_UINT); +} + +void params::set_uint(char const * k, unsigned v) { + SET_SIMPLE_VALUE(m_uint_value, CPK_UINT); +} + +void params::set_double(symbol const & k, double v) { + SET_SIMPLE_VALUE(m_double_value, CPK_DOUBLE); +} + +void params::set_double(char const * k, double v) { + SET_SIMPLE_VALUE(m_double_value, CPK_DOUBLE); +} + +void params::set_str(symbol const & k, char const * v) { + SET_SIMPLE_VALUE(m_str_value, CPK_STRING); +} + +void params::set_str(char const * k, char const * v) { + SET_SIMPLE_VALUE(m_str_value, CPK_STRING); +} + +#define SET_RAT_VALUE() SET_VALUE({ \ + if (it->second.m_kind != CPK_NUMERAL) { \ + del_value(*it); \ + it->second.m_kind = CPK_NUMERAL; \ + it->second.m_rat_value = alloc(rational); \ + } \ + *(it->second.m_rat_value) = v; \ +}, \ +{ \ + entry new_entry; \ + new_entry.first = symbol(k); \ + new_entry.second.m_kind = CPK_NUMERAL; \ + new_entry.second.m_rat_value = alloc(rational); \ + *(new_entry.second.m_rat_value) = v; \ + m_entries.push_back(new_entry); \ +}) + +void params::set_rat(symbol const & k, rational const & v) { + SET_RAT_VALUE(); +} + +void params::set_rat(char const * k, rational const & v) { + SET_RAT_VALUE(); +} + +#define SET_SYM_VALUE() SET_VALUE({ \ + del_value(*it); \ + it->second.m_kind = CPK_SYMBOL; \ + it->second.m_sym_value = v.bare_str(); \ +}, \ +{ \ + entry new_entry; \ + new_entry.first = symbol(k); \ + new_entry.second.m_kind = CPK_SYMBOL; \ + new_entry.second.m_sym_value = v.bare_str(); \ + m_entries.push_back(new_entry); \ +}) + +void params::set_sym(symbol const & k, symbol const & v) { + SET_SYM_VALUE(); +} + +void params::set_sym(char const * k, symbol const & v) { + SET_SYM_VALUE(); +} + +#define SET_AST_VALUE(KIND) { \ + SASSERT(m_manager); \ + m_manager->inc_ref(v); \ + SET_VALUE({ \ + del_value(*it); \ + it->second.m_kind = KIND; \ + it->second.m_ast_value = v; \ + }, \ + { \ + entry new_entry; \ + new_entry.first = symbol(k); \ + new_entry.second.m_kind = KIND; \ + new_entry.second.m_ast_value = v; \ + m_entries.push_back(new_entry); \ + })} + + +void params::set_expr(symbol const & k, expr * v) { + SET_AST_VALUE(CPK_EXPR); +} + +void params::set_expr(char const * k, expr * v) { + SET_AST_VALUE(CPK_EXPR); +} + +void params::set_func_decl(symbol const & k, func_decl * v) { + SET_AST_VALUE(CPK_FUNC_DECL); +} + +void params::set_func_decl(char const * k, func_decl * v) { + SET_AST_VALUE(CPK_FUNC_DECL); +} + +void params::set_sort(symbol const & k, sort * v) { + SET_AST_VALUE(CPK_SORT); +} + +void params::set_sort(char const * k, sort * v) { + SET_AST_VALUE(CPK_SORT); +} + diff --git a/lib/params.h b/lib/params.h new file mode 100644 index 000000000..f2a5e4da8 --- /dev/null +++ b/lib/params.h @@ -0,0 +1,128 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + params.h + +Abstract: + + Parameters. + +Author: + + Leonardo (leonardo) 2011-04-22 + +Notes: + +--*/ +#ifndef _PARAMS_H_ +#define _PARAMS_H_ + +#include"cmd_context_types.h" +#include"vector.h" +class ast; +class ast_manager; + +typedef cmd_arg_kind param_kind; + +class params; +class param_descrs; + +class params_ref { + params * m_params; + void init(); + void copy_core(params const * p); +public: + params_ref():m_params(0) {} + params_ref(params_ref const & p); + ~params_ref(); + + params_ref & operator=(params_ref const & p); + + // copy params from p + void copy(params_ref const & src); + void append(params_ref const & src) { copy(src); } + + bool get_bool(symbol const & k, bool _default) const; + bool get_bool(char const * k, bool _default) const; + unsigned get_uint(symbol const & k, unsigned _default) const; + unsigned get_uint(char const * k, unsigned _default) const; + double get_double(symbol const & k, double _default) const; + double get_double(char const * k, double _default) const; + char const * get_str(symbol const & k, char const * _default) const; + char const * get_str(char const * k, char const * _default) const; + rational get_rat(symbol const & k, rational const & _default) const; + rational get_rat(char const * k, rational const & _default) const; + symbol get_sym(symbol const & k, symbol const & _default) const; + symbol get_sym(char const * k, symbol const & _default) const; + expr * get_expr(symbol const & k, expr * _default) const; + expr * get_expr(char const * k, expr * _default) const; + func_decl * get_func_decl(symbol const & k, func_decl * _default) const; + func_decl * get_func_decl(char const * k, func_decl * _default) const; + sort * get_sort(symbol const & k, sort * _default) const; + sort * get_sort(char const * k, sort * _default) const; + + void set_manager(ast_manager & m); + + bool empty() const; + bool contains(symbol const & k) const; + bool contains(char const * k) const; + + void reset(); + void reset(symbol const & k); + void reset(char const * k); + + void set_bool(symbol const & k, bool v); + void set_bool(char const * k, bool v); + void set_uint(symbol const & k, unsigned v); + void set_uint(char const * k, unsigned v); + void set_double(symbol const & k, double v); + void set_double(char const * k, double v); + void set_str(symbol const & k, char const * v); + void set_str(char const * k, char const * v); + void set_rat(symbol const & k, rational const & v); + void set_rat(char const * k, rational const & v); + void set_sym(symbol const & k, symbol const & v); + void set_sym(char const * k, symbol const & v); + void set_expr(symbol const & k, expr * v); + void set_expr(char const * k, expr * v); + void set_func_decl(symbol const & k, func_decl * v); + void set_func_decl(char const * k, func_decl * v); + void set_sort(symbol const & k, sort * v); + void set_sort(char const * k, sort * v); + + void display(std::ostream & out) const; + + void validate(param_descrs const & p) const; +}; + +inline std::ostream & operator<<(std::ostream & out, params_ref const & ref) { + ref.display(out); + return out; +} + +class param_descrs { + struct imp; + imp * m_imp; +public: + param_descrs(); + ~param_descrs(); + void insert(char const * name, param_kind k, char const * descr); + void insert(symbol const & name, param_kind k, char const * descr); + void erase(char const * name); + void erase(symbol const & name); + param_kind get_kind(char const * name) const; + param_kind get_kind(symbol const & name) const; + void display(std::ostream & out, unsigned indent = 0) const; + unsigned size() const; + symbol get_param_name(unsigned idx) const; +}; + +void insert_max_memory(param_descrs & r); +void insert_max_steps(param_descrs & r); +void insert_produce_models(param_descrs & r); +void insert_produce_proofs(param_descrs & r); +void insert_timeout(param_descrs & r); + +#endif diff --git a/lib/params2front_end_params.cpp b/lib/params2front_end_params.cpp new file mode 100644 index 000000000..35a7175d5 --- /dev/null +++ b/lib/params2front_end_params.cpp @@ -0,0 +1,82 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + params2front_end_params.h + +Abstract: + + Backward compatibility utilities for parameter setting + +Author: + + Leonardo de Moura (leonardo) 2011-05-19. + +Revision History: + +--*/ +#include"front_end_params.h" +#include"params.h" + +/** + Update front_end_params using s. + Only the most frequently used options are updated. + + This function is mainly used to allow smt::context to be used in + the new strategy framework. +*/ +void params2front_end_params(params_ref const & s, front_end_params & t) { + t.m_quant_elim = s.get_bool(":elim-quant", t.m_quant_elim); + t.m_relevancy_lvl = s.get_uint(":relevancy", t.m_relevancy_lvl); + TRACE("qi_cost", s.display(tout); tout << "\n";); + t.m_qi_cost = s.get_str(":qi-cost", t.m_qi_cost.c_str()); + t.m_mbqi = s.get_bool(":mbqi", t.m_mbqi); + t.m_mbqi_max_iterations = s.get_uint(":mbqi-max-iterations", t.m_mbqi_max_iterations); + t.m_random_seed = s.get_uint(":random-seed", t.m_random_seed); + t.m_model = s.get_bool(":produce-models", t.m_model); + if (s.get_bool(":produce-proofs", false)) + t.m_proof_mode = PGM_FINE; + t.m_well_sorted_check = s.get_bool(":check-sorts", t.m_well_sorted_check); + t.m_qi_eager_threshold = s.get_double(":qi-eager-threshold", t.m_qi_eager_threshold); + t.m_qi_lazy_threshold = s.get_double(":qi-lazy-threshold", t.m_qi_lazy_threshold); + t.m_solver = s.get_bool(":solver", t.m_solver); + t.m_preprocess = s.get_bool(":preprocess", t.m_preprocess); + t.m_hi_div0 = s.get_bool(":hi-div0", t.m_hi_div0); + t.m_auto_config = s.get_bool(":auto-config", t.m_auto_config); + t.m_array_simplify = s.get_bool(":array-old-simplifier", t.m_array_simplify); + t.m_arith_branch_cut_ratio = s.get_uint(":arith-branch-cut-ratio", t.m_arith_branch_cut_ratio); + t.m_arith_expand_eqs = s.get_bool(":arith-expand-eqs", t.m_arith_expand_eqs); + + if (s.get_bool(":arith-greatest-error-pivot", false)) + t.m_arith_pivot_strategy = ARITH_PIVOT_GREATEST_ERROR; + else if (s.get_bool(":arith-least-error-pivot", false)) + t.m_arith_pivot_strategy = ARITH_PIVOT_LEAST_ERROR; +} + +/** + \brief Copy parameters (from s) that affect the semantics of Z3 (e.g., HI_DIV0). + It also copies the model construction parameter. Thus, model construction + can be enabled at the command line. +*/ +void front_end_params2params(front_end_params const & s, params_ref & t) { + if (s.m_model) + t.set_bool(":produce-models", true); + if (!s.m_hi_div0) + t.set_bool(":hi-div0", false); +} + +/** + \brief Bridge for using params_ref with smt::context. +*/ +void solver_front_end_params_descrs(param_descrs & r) { + r.insert(":hi-div0", CPK_BOOL, "(default: true) if true, then Z3 uses the usual hardware interpretation for division (rem, mod) by zero. Otherwise, these operations are considered uninterpreted"); + r.insert(":relevancy", CPK_UINT, "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"); + r.insert(":mbqi", CPK_BOOL, "model based quantifier instantiation (MBQI)"); + r.insert(":mbqi-max-iterations", CPK_UINT, "maximum number of rounds of MBQI"); + r.insert(":random-seed", CPK_UINT, "random seed for smt solver"); + r.insert(":qi-eager-threshold", CPK_DOUBLE, "threshold for eager quantifier instantiation"); + r.insert(":qi-lazy-threshold", CPK_DOUBLE, "threshold for lazy quantifier instantiation"); + r.insert(":auto_config", CPK_BOOL, "use heuristics to automatically configure smt solver"); + r.insert(":arith-branch-cut-ratio", CPK_UINT, "branch&bound / gomory cut ratio"); +} diff --git a/lib/params2front_end_params.h b/lib/params2front_end_params.h new file mode 100644 index 000000000..936928d4a --- /dev/null +++ b/lib/params2front_end_params.h @@ -0,0 +1,31 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + params2front_end_params.h + +Abstract: + + Backward compatibility utilities for parameter setting + +Author: + + Leonardo de Moura (leonardo) 2011-05-19. + +Revision History: + +--*/ +#ifndef _PARAMS2FRONT_END_PARAMS_H_ +#define _PARAMS2FRONT_END_PARAMS_H_ + +class params_ref; +struct front_end_params; + +void params2front_end_params(params_ref const & s, front_end_params & t); + +void front_end_params2params(front_end_params const & s, params_ref & t); + +void solver_front_end_params_descrs(param_descrs & r); + +#endif diff --git a/lib/parray.h b/lib/parray.h new file mode 100644 index 000000000..ace2d9913 --- /dev/null +++ b/lib/parray.h @@ -0,0 +1,621 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + parray.h + +Abstract: + + Persistent Arrays. + +Author: + + Leonardo de Moura (leonardo) 2011-02-21. + +Revision History: + +--*/ +#ifndef _PARRAY_H_ +#define _PARRAY_H_ + +#include"vector.h" +#include"trace.h" + +template +class parray_manager { +public: + typedef typename C::value value; + typedef typename C::value_manager value_manager; + typedef typename C::allocator allocator; +private: + static size_t capacity(value * vs) { + return vs == 0 ? 0 : (reinterpret_cast(vs))[-1]; + } + + value * allocate_values(size_t c) { + size_t * mem = static_cast(m_allocator.allocate(sizeof(value)*c + sizeof(size_t))); + *mem = c; + ++mem; + value * r = reinterpret_cast(mem); + SASSERT(capacity(r) == c); + TRACE("parray_mem", tout << "allocated values[" << c << "]: " << r << "\n";); + return r; + } + + void deallocate_values(value * vs) { + if (vs == 0) + return; + size_t c = capacity(vs); + TRACE("parray_mem", tout << "deallocated values[" << c << "]: " << vs << "\n";); + size_t * mem = reinterpret_cast(vs); + --mem; + m_allocator.deallocate(sizeof(value)*c + sizeof(size_t), mem); + } + + enum ckind { SET, PUSH_BACK, POP_BACK, ROOT }; + struct cell { + unsigned m_ref_count:30; + unsigned m_kind:2; + union { + unsigned m_idx; + unsigned m_size; + }; + value m_elem; + union { + cell * m_next; + value * m_values; + }; + + ckind kind() const { return static_cast(m_kind); } + + unsigned idx() const { SASSERT(kind() != ROOT); return m_idx; } + unsigned size() const { SASSERT(kind() == ROOT); return m_size; } + cell * next() const { SASSERT(kind() != ROOT); return m_next; } + value const & elem() const { SASSERT(kind() == SET || kind() == PUSH_BACK); return m_elem; } + cell(ckind k):m_ref_count(1), m_kind(k), m_size(0), m_values(0) {} + }; + + value_manager & m_vmanager; + allocator & m_allocator; + ptr_vector m_get_values_tmp; + ptr_vector m_reroot_tmp; + + void inc_ref(value const & v) { + if (C::ref_count) + m_vmanager.inc_ref(v); + } + + void dec_ref(value const & v) { + if (C::ref_count) + m_vmanager.dec_ref(v); + } + + void dec_ref(unsigned sz, value * vs) { + if (C::ref_count) + for (unsigned i = 0; i < sz; i++) + m_vmanager.dec_ref(vs[i]); + } + + cell * mk(ckind k) { + cell * r = new (m_allocator.allocate(sizeof(cell))) cell(k); + TRACE("parray_mem", tout << "allocated cell: " << r << "\n";); + return r; + } + + void del(cell * c) { + while (true) { + cell * next = 0; + switch (c->kind()) { + case SET: + case PUSH_BACK: + dec_ref(c->elem()); + next = c->next(); + break; + case POP_BACK: + next = c->next(); + break; + case ROOT: + dec_ref(c->size(), c->m_values); + deallocate_values(c->m_values); + break; + } + TRACE("parray_mem", tout << "deallocated cell: " << c << "\n";); + c->~cell(); + m_allocator.deallocate(sizeof(cell), c); + if (next == 0) + return; + SASSERT(next->m_ref_count > 0); + next->m_ref_count--; + if (next->m_ref_count > 0) + return; + c = next; + } + } + + void inc_ref(cell * c) { + if (!c) return; + c->m_ref_count++; + } + + void dec_ref(cell * c) { + if (!c) return; + TRACE("parray_mem", tout << "dec_ref(" << c << "), ref_count: " << c->m_ref_count << "\n";); + SASSERT(c->m_ref_count > 0); + c->m_ref_count--; + if (c->m_ref_count == 0) + del(c); + } + + void expand(value * & vs) { + size_t curr_capacity = capacity(vs); + size_t new_capacity = curr_capacity == 0 ? 2 : (3 * curr_capacity + 1) >> 1; + value * new_vs = allocate_values(new_capacity); + if (curr_capacity > 0) { + for (size_t i = 0; i < curr_capacity; i++) + new_vs[i] = vs[i]; + deallocate_values(vs); + } + vs = new_vs; + } + + void rset(value * vs, unsigned i, value const & v) { + inc_ref(v); + dec_ref(vs[i]); + vs[i] = v; + } + + void rset(cell * c, unsigned i, value const & v) { + SASSERT(c->kind() == ROOT); + SASSERT(i < c->size()); + rset(c->m_values, i, v); + } + + void rpush_back(value * & vs, unsigned & sz, value const & v) { + if (sz == capacity(vs)) + expand(vs); + SASSERT(sz < capacity(vs)); + inc_ref(v); + vs[sz] = v; + sz++; + } + + void rpush_back(cell * c, value const & v) { + SASSERT(c->kind() == ROOT); + rpush_back(c->m_values, c->m_size, v); + } + + void rpop_back(value * vs, unsigned & sz) { + sz--; + dec_ref(vs[sz]); + } + + void rpop_back(cell * c) { + SASSERT(c->kind() == ROOT); + rpop_back(c->m_values, c->m_size); + } + + void copy_values(value * s, unsigned sz, value * & t) { + SASSERT(t == 0); + t = allocate_values(capacity(s)); + for (unsigned i = 0; i < sz; i++) { + t[i] = s[i]; + inc_ref(t[i]); + } + } + + unsigned get_values(cell * s, value * & vs) { + ptr_vector & cs = m_get_values_tmp; + cs.reset(); + cell * r = s; + while (r->kind() != ROOT) { + cs.push_back(r); + r = r->next(); + } + SASSERT(r->kind() == ROOT); + unsigned sz = r->m_size; + vs = 0; + copy_values(r->m_values, sz, vs); + unsigned i = cs.size(); + while (i > 0) { + --i; + cell * curr = cs[i]; + switch (curr->kind()) { + case SET: + rset(vs, curr->m_idx, curr->m_elem); + break; + case POP_BACK: + rpop_back(vs, sz); + break; + case PUSH_BACK: + rpush_back(vs, sz, curr->m_elem); + break; + case ROOT: + UNREACHABLE(); + break; + } + } + return sz; + } + + void unfold(cell * c) { + if (c->kind() == ROOT) + return; + value * vs; + unsigned sz = get_values(c, vs); + dec_ref(c->m_next); + if (c->kind() == SET || c->kind() == PUSH_BACK) + dec_ref(c->m_elem); + c->m_next = 0; + c->m_kind = ROOT; + c->m_size = sz; + c->m_values = vs; + SASSERT(c->kind() == ROOT); + } + +public: + class ref { + cell * m_ref; + unsigned m_updt_counter; // counter for minimizing memory consumption when using preserve_roots option + ref(cell * r):m_ref(r), m_updt_counter(0) {} + bool root() const { return m_ref->kind() == ROOT; } + bool unshared() const { return m_ref->m_ref_count == 1; } + friend class parray_manager; + public: + ref():m_ref(0), m_updt_counter(0) {} + }; + +public: + parray_manager(value_manager & m, allocator & a): + m_vmanager(m), + m_allocator(a) { + } + + value_manager & manager() { return m_vmanager; } + + void mk(ref & r) { + dec_ref(r.m_ref); + cell * new_c = mk(ROOT); + r.m_ref = new_c; + r.m_updt_counter = 0; + SASSERT(new_c->m_ref_count == 1); + } + + void del(ref & r) { + dec_ref(r.m_ref); + r.m_ref = 0; + r.m_updt_counter = 0; + } + + void copy(ref const & s, ref & t) { + inc_ref(s.m_ref); + dec_ref(t.m_ref); + t.m_ref = s.m_ref; + t.m_updt_counter = 0; + } + + unsigned size(ref const & r) const { + cell * c = r.m_ref; + if (c == 0) return 0; + while (true) { + switch (c->kind()) { + case SET: + c = c->next(); + break; + case PUSH_BACK: + return c->idx() + 1; + case POP_BACK: + return c->idx() - 1; + case ROOT: + return c->size(); + } + } + } + + bool empty(ref const & r) const { return size(r) == 0; } + + value const & get(ref const & r, unsigned i) const { + SASSERT(i < size(r)); + + unsigned trail_sz = 0; + cell * c = r.m_ref; + + while (true) { + if (trail_sz > C::max_trail_sz) { + const_cast(this)->reroot(const_cast(r)); + SASSERT(r.m_ref->kind() == ROOT); + return r.m_ref->m_values[i]; + } + switch (c->kind()) { + case SET: + case PUSH_BACK: + if (i == c->idx()) + return c->elem(); + trail_sz++; + c = c->next(); + break; + case POP_BACK: + trail_sz++; + c = c->next(); + break; + case ROOT: + return c->m_values[i]; + } + } + } + + void set(ref & r, unsigned i, value const & v) { + SASSERT(i < size(r)); + if (r.root()) { + if (r.unshared()) { + rset(r.m_ref, i, v); + return; + } + if (C::preserve_roots) { + if (r.m_updt_counter > size(r)) { + unshare(r); + SASSERT(r.unshared()); + SASSERT(r.m_updt_counter == 0); + rset(r.m_ref, i, v); + return; + } + r.m_updt_counter++; + cell * c = r.m_ref; + cell * new_c = mk(ROOT); + new_c->m_size = c->m_size; + new_c->m_values = c->m_values; + inc_ref(new_c); + c->m_kind = SET; + c->m_idx = i; + c->m_elem = c->m_values[i]; + inc_ref(c->m_elem); + c->m_next = new_c; + dec_ref(c); + r.m_ref = new_c; + rset(new_c, i, v); + SASSERT(new_c->m_ref_count == 2); + return; + } + } + cell * new_c = mk(SET); + new_c->m_idx = i; + inc_ref(v); + new_c->m_elem = v; + new_c->m_next = r.m_ref; + r.m_ref = new_c; + SASSERT(new_c->m_ref_count == 1); + } + + void set(ref const & s, unsigned i, value const & v, ref & r) { + SASSERT(i < size(s)); + if (&s == &r) { + set(r, i, v); + return; + } + copy(s, r); + set(r, i, v); + } + + void push_back(ref & r, value const & v) { + if (r.m_ref == 0) + mk(r); + if (r.root()) { + if (r.unshared()) { + rpush_back(r.m_ref, v); + return; + } + if (C::preserve_roots) { + if (r.m_updt_counter > size(r)) { + unshare(r); + SASSERT(r.unshared()); + SASSERT(r.m_updt_counter == 0); + rpush_back(r.m_ref, v); + return; + } + r.m_updt_counter++; + cell * c = r.m_ref; + SASSERT(c->m_ref_count > 1); + cell * new_c = mk(ROOT); + new_c->m_size = c->m_size; + new_c->m_values = c->m_values; + inc_ref(new_c); + c->m_kind = POP_BACK; + c->m_idx = new_c->m_size + 1; + c->m_next = new_c; + dec_ref(c); + r.m_ref = new_c; + rpush_back(new_c, v); + SASSERT(new_c->m_ref_count == 2); + return; + } + } + cell * new_c = mk(PUSH_BACK); + new_c->m_idx = size(r.m_ref); + inc_ref(v); + new_c->m_elem = v; + new_c->m_next = r.m_ref; + r.m_ref = new_c; + SASSERT(new_c->m_ref_count == 1); + } + + void push_back(ref const & s, value const & v, ref & r) { + if (&s == &r) { + push_back(r, v); + return; + } + copy(s, r); + push_back(r, v); + } + + void pop_back(ref & r) { + SASSERT(!empty(r)); + if (r.root()) { + if (r.unshared()) { + rpop_back(r.m_ref); + return; + } + if (C::preserve_roots) { + if (r.m_updt_counter > size(r)) { + unshare(r); + SASSERT(r.unshared()); + SASSERT(r.m_updt_counter == 0); + rpop_back(r.m_ref); + return; + } + r.m_updt_counter++; + cell * c = r.m_ref; + SASSERT(c->m_ref_count > 1); + cell * new_c = mk(ROOT); + new_c->m_size = c->m_size; + new_c->m_values = c->m_values; + inc_ref(new_c); + c->m_kind = PUSH_BACK; + c->m_idx = new_c->m_size - 1; + c->m_elem = new_c->m_values[c->m_idx]; + inc_ref(c->m_elem); + c->m_next = new_c; + dec_ref(c); + r.m_ref = new_c; + rpop_back(new_c); + SASSERT(new_c->m_ref_count == 2); + return; + } + } + cell * new_c = mk(POP_BACK); + new_c->m_idx = size(r.m_ref); + new_c->m_next = r.m_ref; + r.m_ref = new_c; + SASSERT(new_c->m_ref_count == 1); + } + + void pop_back(ref const & s, ref & r) { + SASSERT(!empty(s)); + if (&s == &r) { + pop_back(r); + return; + } + copy(s, r); + pop_back(r); + } + + void unshare(ref & r) { + if (r.root() && r.unshared()) + return; + cell * c = r.m_ref; + cell * new_c = mk(ROOT); + new_c->m_size = get_values(c, new_c->m_values); + SASSERT(new_c->m_ref_count == 1); + dec_ref(c); + r.m_ref = new_c; + r.m_updt_counter = 0; + SASSERT(r.root()); + SASSERT(r.unshared()); + } + + void unfold(ref & r) { + if (r.root()) + return; + unfold(r.m_ref); + r.m_updt_counter = 0; + SASSERT(r.root()); + } + + void reroot(ref & r) { + if (r.root()) + return; + ptr_vector & cs = m_reroot_tmp; + cs.reset(); + unsigned r_sz = size(r); + unsigned trail_split_idx = r_sz / C::factor; + unsigned i = 0; + cell * c = r.m_ref; + while (c->kind() != ROOT && i < trail_split_idx) { + cs.push_back(c); + c = c->next(); + i++; + } + if (c->kind() != ROOT) { + // root is too far away. + unfold(c); + } + SASSERT(c->kind() == ROOT); + i = cs.size(); + while (i > 0) { + --i; + cell * p = cs[i]; + SASSERT(c->m_kind == ROOT); + unsigned sz = c->m_size; + value * vs = c->m_values; + SASSERT(p->m_kind != ROOT); + SASSERT(p->m_next == c); + switch (p->m_kind) { + case SET: + c->m_kind = SET; + c->m_idx = p->m_idx; + c->m_elem = vs[c->m_idx]; + vs[p->m_idx] = p->m_elem; + break; + case PUSH_BACK: + c->m_kind = POP_BACK; + if (sz == capacity(vs)) + expand(vs); + c->m_idx = sz; + vs[sz] = p->m_elem; + sz++; + break; + case POP_BACK: + c->m_kind = PUSH_BACK; + --sz; + c->m_idx = sz; + c->m_elem = vs[sz]; + break; + case ROOT: + UNREACHABLE(); + break; + } + inc_ref(p); + c->m_next = p; + // p does not point to c anymore + dec_ref(c); + p->m_kind = ROOT; + p->m_size = sz; + p->m_values = vs; + c = p; + } + SASSERT(c == r.m_ref); + SASSERT(c->kind() == ROOT); + SASSERT(c->m_size == r_sz); + r.m_updt_counter = 0; + SASSERT(r.root()); + } + + void display_info(std::ostream & out, ref const & r) { + cell * c = r.m_ref; + if (c == 0) { + out << ""; + return; + } + while (true) { + out << "cell[" << c << ", "; + switch (c->kind()) { + case SET: out << "set, " << c->m_idx; break; + case PUSH_BACK: out << "push, " << c->m_idx; break; + case POP_BACK: out << "pop, " << c->m_idx; break; + case ROOT: out << "root, " << c->m_size << ", " << capacity(c->m_values); break; + } + out << "]#" << c->m_ref_count; + if (c->kind() == ROOT) + break; + out << " -> "; + c = c->next(); + } + } +}; + +template +struct dummy_value_manager { + void inc_ref(T const &) {} + void dec_ref(T const &) {} +}; + +#endif diff --git a/lib/parser_params.cpp b/lib/parser_params.cpp new file mode 100644 index 000000000..000885fa5 --- /dev/null +++ b/lib/parser_params.cpp @@ -0,0 +1,14 @@ +#include "parser_params.h" + +parser_params::parser_params() : + m_dump_goal_as_smt(false), + m_display_error_for_vs(false) { +} + +void parser_params::register_params(ini_params & p) { + p.register_bool_param("DUMP_GOAL_AS_SMT", m_dump_goal_as_smt, "write goal back to output in SMT format"); + p.register_bool_param("DISPLAY_ERROR_FOR_VISUAL_STUDIO", m_display_error_for_vs, "display error messages in Visual Studio format"); +} + + + diff --git a/lib/parser_params.h b/lib/parser_params.h new file mode 100644 index 000000000..eaab4fd81 --- /dev/null +++ b/lib/parser_params.h @@ -0,0 +1,33 @@ +/*++ +Copyright (c) 2008 Microsoft Corporation + +Module Name: + + parser_params.h + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2008-04-21. + +Revision History: + +--*/ +#ifndef _PARSER_PARAMS_H_ +#define _PARSER_PARAMS_H_ + +#include"ini_file.h" + +struct parser_params { + bool m_dump_goal_as_smt; // re-print goal as SMT benchmark. + bool m_display_error_for_vs; // print error in vs format. + + parser_params(); + void register_params(ini_params & p); +}; + +#endif /* _PARSER_PARAMS_H_ */ + diff --git a/lib/pattern_inference.cpp b/lib/pattern_inference.cpp new file mode 100644 index 000000000..07e649905 --- /dev/null +++ b/lib/pattern_inference.cpp @@ -0,0 +1,744 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + pattern_inference.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2006-12-08. + +Revision History: + +--*/ +#include"pattern_inference.h" +#include"ast_ll_pp.h" +#include"ast_pp.h" +#include"ast_util.h" +#include"warning.h" +#include"arith_decl_plugin.h" +#include"pull_quant.h" +#include"well_sorted.h" +#include"for_each_expr.h" + +void smaller_pattern::save(expr * p1, expr * p2) { + expr_pair e(p1, p2); + if (!m_cache.contains(e)) { + TRACE("smaller_pattern_proc", tout << "saving: " << p1->get_id() << " " << p2->get_id() << "\n";); + m_cache.insert(e); + m_todo.push_back(e); + } +} + +bool smaller_pattern::process(expr * p1, expr * p2) { + m_todo.reset(); + m_cache.reset(); + save(p1, p2); + while (!m_todo.empty()) { + expr_pair & curr = m_todo.back(); + p1 = curr.first; + p2 = curr.second; + m_todo.pop_back(); + ast_kind k1 = p1->get_kind(); + if (k1 != AST_VAR && k1 != p2->get_kind()) + return false; + switch (k1) { + case AST_APP: { + app * app1 = to_app(p1); + app * app2 = to_app(p2); + unsigned num1 = app1->get_num_args(); + if (num1 != app2->get_num_args() || app1->get_decl() != app2->get_decl()) + return false; + for (unsigned i = 0; i < num1; i++) + save(app1->get_arg(i), app2->get_arg(i)); + break; + } + case AST_VAR: { + unsigned idx = to_var(p1)->get_idx(); + if (idx < m_bindings.size()) { + if (m_bindings[idx] == 0) + m_bindings[idx] = p2; + else if (m_bindings[idx] != p2) + return false; + } + // it is a variable bound by an external quantifier + else if (p1 != p2) + return false; + break; + } + default: + if (p1 != p2) + return false; + break; + } + } + return true; +} + +bool smaller_pattern::operator()(unsigned num_bindings, expr * p1, expr * p2) { + m_bindings.resize(num_bindings); + for (unsigned i = 0; i < num_bindings; i++) + m_bindings[i] = 0; + return process(p1, p2); +} + +pattern_inference::pattern_inference(ast_manager & m, pattern_inference_params & params): + simplifier(m), + m_params(params), + m_bfid(m.get_basic_family_id()), + m_afid(m.get_family_id("arith")), + m_le(m), + m_nested_arith_only(true), + m_block_loop_patterns(params.m_pi_block_loop_patterns), + m_candidates(m), + m_pattern_weight_lt(m_candidates_info), + m_collect(m, *this), + m_contains_subpattern(*this), + m_database(m) { + if (params.m_pi_arith == AP_NO) + register_forbidden_family(m_afid); + enable_ac_support(false); +} + +void pattern_inference::collect::operator()(expr * n, unsigned num_bindings) { + SASSERT(m_info.empty()); + SASSERT(m_todo.empty()); + SASSERT(m_cache.empty()); + m_num_bindings = num_bindings; + m_todo.push_back(entry(n, 0)); + while (!m_todo.empty()) { + entry & e = m_todo.back(); + n = e.m_node; + unsigned delta = e.m_delta; + TRACE("collect", tout << "processing: " << n->get_id() << " " << delta << " kind: " << n->get_kind() << "\n";); + TRACE("collect_info", tout << mk_pp(n, m_manager) << "\n";); + if (visit_children(n, delta)) { + m_todo.pop_back(); + save_candidate(n, delta); + } + } + reset(); +} + +inline void pattern_inference::collect::visit(expr * n, unsigned delta, bool & visited) { + entry e(n, delta); + if (!m_cache.contains(e)) { + m_todo.push_back(e); + visited = false; + } +} + +bool pattern_inference::collect::visit_children(expr * n, unsigned delta) { + bool visited = true; + unsigned i; + switch (n->get_kind()) { + case AST_APP: + i = to_app(n)->get_num_args(); + while (i > 0) { + --i; + visit(to_app(n)->get_arg(i), delta, visited); + } + break; + case AST_QUANTIFIER: + visit(to_quantifier(n)->get_expr(), delta + to_quantifier(n)->get_num_decls(), visited); + break; + default: + break; + } + return visited; +} + +inline void pattern_inference::collect::save(expr * n, unsigned delta, info * i) { + m_cache.insert(entry(n, delta), i); + if (i != 0) + m_info.push_back(i); +} + +void pattern_inference::collect::save_candidate(expr * n, unsigned delta) { + switch (n->get_kind()) { + case AST_VAR: { + unsigned idx = to_var(n)->get_idx(); + if (idx >= delta) { + idx = idx - delta; + uint_set free_vars; + if (idx < m_num_bindings) + free_vars.insert(idx); + info * i = 0; + if (delta == 0) + i = alloc(info, m_manager, n, free_vars, 1); + else + i = alloc(info, m_manager, m_manager.mk_var(idx, to_var(n)->get_sort()), free_vars, 1); + save(n, delta, i); + } + else { + save(n, delta, 0); + } + return; + } + case AST_APP: { + app * c = to_app(n); + func_decl * decl = c->get_decl(); + if (m_owner.is_forbidden(c)) { + save(n, delta, 0); + return; + } + + if (c->get_num_args() == 0) { + save(n, delta, alloc(info, m_manager, n, uint_set(), 1)); + return; + } + + ptr_buffer buffer; + bool changed = false; // false if none of the children is mapped to a node different from itself. + uint_set free_vars; + unsigned size = 1; + unsigned num = c->get_num_args(); + for (unsigned i = 0; i < num; i++) { + expr * child = c->get_arg(i); + info * child_info = 0; +#ifdef Z3DEBUG + bool found = +#endif + m_cache.find(entry(child, delta), child_info); + SASSERT(found); + if (child_info == 0) { + save(n, delta, 0); + return; + } + buffer.push_back(child_info->m_node.get()); + free_vars |= child_info->m_free_vars; + size += child_info->m_size; + if (child != child_info->m_node.get()) + changed = true; + } + + app * new_node = 0; + if (changed) + new_node = m_manager.mk_app(decl, buffer.size(), buffer.c_ptr()); + else + new_node = to_app(n); + save(n, delta, alloc(info, m_manager, new_node, free_vars, size)); + // Remark: arithmetic patterns are only used if they are nested inside other terms. + // That is, we never consider x + 1 as pattern. On the other hand, f(x+1) can be a pattern + // if arithmetic is not in the forbidden list. + // + // Remark: The rule above has an exception. The operators (div, idiv, mod) are allowed to be + // used as patterns even when they are not nested in other terms. The motivation is that + // Z3 currently doesn't implement them (i.e., they are uninterpreted). So, some users add axioms + // stating properties about these operators. + family_id fid = c->get_family_id(); + decl_kind k = c->get_decl_kind(); + if (!free_vars.empty() && + (fid != m_afid || (fid == m_afid && !m_owner.m_nested_arith_only && (k == OP_DIV || k == OP_IDIV || k == OP_MOD || k == OP_REM || k == OP_MUL)))) { + TRACE("pattern_inference", tout << "potential candidate: \n" << mk_pp(new_node, m_manager) << "\n";); + m_owner.add_candidate(new_node, free_vars, size); + } + return; + } + default: + save(n, delta, 0); + return; + } +} + + +void pattern_inference::collect::reset() { + m_cache.reset(); + std::for_each(m_info.begin(), m_info.end(), delete_proc()); + m_info.reset(); + SASSERT(m_todo.empty()); +} + +void pattern_inference::add_candidate(app * n, uint_set const & free_vars, unsigned size) { + for (unsigned i = 0; i < m_num_no_patterns; i++) { + if (n == m_no_patterns[i]) + return; + } + + if (!m_candidates_info.contains(n)) { + m_candidates_info.insert(n, info(free_vars, size)); + m_candidates.push_back(n); + } +} + + +/** + \brief Copy the non-looping patterns in m_candidates to result when m_params.m_pi_block_loop_patterns = true. + Otherwise, copy m_candidates to result. +*/ +void pattern_inference::filter_looping_patterns(ptr_vector & result) { + unsigned num = m_candidates.size(); + for (unsigned i1 = 0; i1 < num; i1++) { + app * n1 = m_candidates.get(i1); + expr2info::obj_map_entry * e1 = m_candidates_info.find_core(n1); + SASSERT(e1); + uint_set const & s1 = e1->get_data().m_value.m_free_vars; + if (m_block_loop_patterns) { + bool smaller = false; + for (unsigned i2 = 0; i2 < num; i2++) { + if (i1 != i2) { + app * n2 = m_candidates.get(i2); + expr2info::obj_map_entry * e2 = m_candidates_info.find_core(n2); + if (e2) { + uint_set const & s2 = e2->get_data().m_value.m_free_vars; + // Remark: the comparison operator only makes sense if both AST nodes + // contain the same number of variables. + // Example: + // (f X Y) <: (f (g X Z W) Y) + if (s1 == s2 && m_le(m_num_bindings, n1, n2) && !m_le(m_num_bindings, n2, n1)) { + smaller = true; + break; + } + } + } + } + if (!smaller) + result.push_back(n1); + else + m_candidates_info.erase(n1); + } + else { + result.push_back(n1); + } + } +} + + + +inline void pattern_inference::contains_subpattern::save(expr * n) { + unsigned id = n->get_id(); + m_already_processed.assure_domain(id); + if (!m_already_processed.contains(id)) { + m_todo.push_back(n); + m_already_processed.insert(id); + } +} + +bool pattern_inference::contains_subpattern::operator()(expr * n) { + m_already_processed.reset(); + m_todo.reset(); + expr2info::obj_map_entry * _e = m_owner.m_candidates_info.find_core(n); + SASSERT(_e); + uint_set const & s1 = _e->get_data().m_value.m_free_vars; + save(n); + unsigned num; + while (!m_todo.empty()) { + expr * curr = m_todo.back(); + m_todo.pop_back(); + switch (curr->get_kind()) { + case AST_APP: + if (curr != n) { + expr2info::obj_map_entry * e = m_owner.m_candidates_info.find_core(curr); + if (e) { + uint_set const & s2 = e->get_data().m_value.m_free_vars; + SASSERT(s2.subset_of(s1)); + if (s1 == s2) { + TRACE("pattern_inference", tout << mk_pp(n, m_owner.m_manager) << "\nis bigger than\n" << mk_pp(to_app(curr), m_owner.m_manager) << "\n";); + return true; + } + } + } + num = to_app(curr)->get_num_args(); + for (unsigned i = 0; i < num; i++) + save(to_app(curr)->get_arg(i)); + break; + case AST_VAR: + break; + default: + UNREACHABLE(); + } + } + return false; +} + +/** + Return true if n contains a direct/indirect child that is also a + pattern, and contains the same number of free variables. +*/ +inline bool pattern_inference::contains_subpattern(expr * n) { + return m_contains_subpattern(n); +} + +/** + \brief Copy a pattern p in patterns to result, if there is no + direct/indirect child of p in patterns which contains the same set + of variables. + + Remark: Every pattern p in patterns is also a member of + m_pattern_map. +*/ +void pattern_inference::filter_bigger_patterns(ptr_vector const & patterns, ptr_vector & result) { + ptr_vector::const_iterator it = patterns.begin(); + ptr_vector::const_iterator end = patterns.end(); + for (; it != end; ++it) { + app * curr = *it; + if (!contains_subpattern(curr)) + result.push_back(curr); + } +} + + +bool pattern_inference::pattern_weight_lt::operator()(expr * n1, expr * n2) const { + expr2info::obj_map_entry * e1 = m_candidates_info.find_core(n1); + expr2info::obj_map_entry * e2 = m_candidates_info.find_core(n2); + SASSERT(e1 != 0); + SASSERT(e2 != 0); + info const & i1 = e1->get_data().m_value; + info const & i2 = e2->get_data().m_value; + unsigned num_free_vars1 = i1.m_free_vars.num_elems(); + unsigned num_free_vars2 = i2.m_free_vars.num_elems(); + return num_free_vars1 > num_free_vars2 || (num_free_vars1 == num_free_vars2 && i1.m_size < i2.m_size); +} + +/** + \brief Create unary patterns (single expressions that contain all + bound variables). If a candidate does not contain all bound + variables, then it is copied to remaining_candidate_patterns. The + new patterns are stored in result. +*/ +void pattern_inference::candidates2unary_patterns(ptr_vector const & candidate_patterns, + ptr_vector & remaining_candidate_patterns, + app_ref_buffer & result) { + ptr_vector::const_iterator it = candidate_patterns.begin(); + ptr_vector::const_iterator end = candidate_patterns.end(); + for (; it != end; ++it) { + app * candidate = *it; + expr2info::obj_map_entry * e = m_candidates_info.find_core(candidate); + info const & i = e->get_data().m_value; + if (i.m_free_vars.num_elems() == m_num_bindings) { + app * new_pattern = m_manager.mk_pattern(candidate); + result.push_back(new_pattern); + } + else { + remaining_candidate_patterns.push_back(candidate); + } + } +} + +// TODO: this code is too inefficient when the number of candidate +// patterns is too big. +// HACK: limit the number of case-splits: +#define MAX_SPLITS 32 + +void pattern_inference::candidates2multi_patterns(unsigned max_num_patterns, + ptr_vector const & candidate_patterns, + app_ref_buffer & result) { + SASSERT(!candidate_patterns.empty()); + m_pre_patterns.push_back(alloc(pre_pattern)); + unsigned sz = candidate_patterns.size(); + unsigned num_splits = 0; + for (unsigned j = 0; j < m_pre_patterns.size(); j++) { + pre_pattern * curr = m_pre_patterns[j]; + if (curr->m_free_vars.num_elems() == m_num_bindings) { + app * new_pattern = m_manager.mk_pattern(curr->m_exprs.size(), curr->m_exprs.c_ptr()); + result.push_back(new_pattern); + if (result.size() >= max_num_patterns) + return; + } + else if (curr->m_idx < sz) { + app * n = candidate_patterns[curr->m_idx]; + expr2info::obj_map_entry * e = m_candidates_info.find_core(n); + uint_set const & s = e->get_data().m_value.m_free_vars; + if (!s.subset_of(curr->m_free_vars)) { + pre_pattern * new_p = alloc(pre_pattern,*curr); + new_p->m_exprs.push_back(n); + new_p->m_free_vars |= s; + new_p->m_idx++; + m_pre_patterns.push_back(new_p); + + if (num_splits < MAX_SPLITS) { + m_pre_patterns[j] = 0; + curr->m_idx++; + m_pre_patterns.push_back(curr); + num_splits++; + } + } + else { + m_pre_patterns[j] = 0; + curr->m_idx++; + m_pre_patterns.push_back(curr); + } + } + TRACE("pattern_inference", tout << "m_pre_patterns.size(): " << m_pre_patterns.size() << + "\nnum_splits: " << num_splits << "\n";); + } +} + +void pattern_inference::reset_pre_patterns() { + std::for_each(m_pre_patterns.begin(), m_pre_patterns.end(), delete_proc()); + m_pre_patterns.reset(); +} + + +static void dump_app_vector(std::ostream & out, ptr_vector const & v, ast_manager & m) { + ptr_vector::const_iterator it = v.begin(); + ptr_vector::const_iterator end = v.end(); + for (; it != end; ++it) + out << mk_pp(*it, m) << "\n"; +} +bool pattern_inference::is_forbidden(app * n) const { + func_decl const * decl = n->get_decl(); + if (is_ground(n)) + return false; + // Remark: skolem constants should not be used in patterns, since they do not + // occur outside of the quantifier. That is, Z3 will never match this kind of + // pattern. + if (m_params.m_pi_avoid_skolems && decl->is_skolem()) { + CTRACE("pattern_inference_skolem", decl->is_skolem(), tout << "ignoring: " << mk_pp(n, m_manager) << "\n";); + return true; + } + if (is_forbidden(decl)) + return true; + return false; +} + +bool pattern_inference::has_preferred_patterns(ptr_vector & candidate_patterns, app_ref_buffer & result) { + if (m_preferred.empty()) + return false; + bool found = false; + ptr_vector::const_iterator it = candidate_patterns.begin(); + ptr_vector::const_iterator end = candidate_patterns.end(); + for (; it != end; ++it) { + app * candidate = *it; + if (m_preferred.contains(to_app(candidate)->get_decl())) { + expr2info::obj_map_entry * e = m_candidates_info.find_core(candidate); + info const & i = e->get_data().m_value; + if (i.m_free_vars.num_elems() == m_num_bindings) { + TRACE("pattern_inference", tout << "found preferred pattern:\n" << mk_pp(candidate, m_manager) << "\n";); + app * p = m_manager.mk_pattern(candidate); + result.push_back(p); + found = true; + } + } + } + return found; +} + +void pattern_inference::mk_patterns(unsigned num_bindings, + expr * n, + unsigned num_no_patterns, + expr * const * no_patterns, + app_ref_buffer & result) { + m_num_bindings = num_bindings; + m_num_no_patterns = num_no_patterns; + m_no_patterns = no_patterns; + + m_collect(n, num_bindings); + + TRACE("pattern_inference", + tout << mk_pp(n, m_manager); + tout << "\ncandidates:\n"; + unsigned num = m_candidates.size(); + for (unsigned i = 0; i < num; i++) { + tout << mk_pp(m_candidates.get(i), m_manager) << "\n"; + }); + + if (!m_candidates.empty()) { + m_tmp1.reset(); + filter_looping_patterns(m_tmp1); + TRACE("pattern_inference", + tout << "candidates after removing looping-patterns:\n"; + dump_app_vector(tout, m_tmp1, m_manager);); + SASSERT(!m_tmp1.empty()); + if (!has_preferred_patterns(m_tmp1, result)) { + // continue if there are no preferred patterns + m_tmp2.reset(); + filter_bigger_patterns(m_tmp1, m_tmp2); + SASSERT(!m_tmp2.empty()); + TRACE("pattern_inference", + tout << "candidates after removing bigger patterns:\n"; + dump_app_vector(tout, m_tmp2, m_manager);); + m_tmp1.reset(); + candidates2unary_patterns(m_tmp2, m_tmp1, result); + unsigned num_extra_multi_patterns = m_params.m_pi_max_multi_patterns; + if (result.empty()) + num_extra_multi_patterns++; + if (num_extra_multi_patterns > 0 && !m_tmp1.empty()) { + // m_pattern_weight_lt is not a total order + std::stable_sort(m_tmp1.begin(), m_tmp1.end(), m_pattern_weight_lt); + TRACE("pattern_inference", + tout << "candidates after sorting:\n"; + dump_app_vector(tout, m_tmp1, m_manager);); + candidates2multi_patterns(num_extra_multi_patterns, m_tmp1, result); + } + } + } + + reset_pre_patterns(); + m_candidates_info.reset(); + m_candidates.reset(); +} + +#include"database.h" // defines g_pattern_database + +void pattern_inference::reduce1_quantifier(quantifier * q) { + TRACE("pattern_inference", tout << "processing:\n" << mk_pp(q, m_manager) << "\n";); + if (!q->is_forall()) { + simplifier::reduce1_quantifier(q); + return; + } + + int weight = q->get_weight(); + + if (m_params.m_pi_use_database) { + m_database.initialize(g_pattern_database); + app_ref_vector new_patterns(m_manager); + unsigned new_weight; + if (m_database.match_quantifier(q, new_patterns, new_weight)) { +#ifdef Z3DEBUG + for (unsigned i = 0; i < new_patterns.size(); i++) { SASSERT(is_well_sorted(m_manager, new_patterns.get(i))); } +#endif + quantifier_ref new_q(m_manager); + if (q->get_num_patterns() > 0) { + // just update the weight... + TRACE("pattern_inference", tout << "updating weight to: " << new_weight << "\n" << mk_pp(q, m_manager) << "\n";); + new_q = m_manager.update_quantifier_weight(q, new_weight); + } + else { + quantifier_ref tmp(m_manager); + tmp = m_manager.update_quantifier(q, new_patterns.size(), (expr**) new_patterns.c_ptr(), q->get_expr()); + new_q = m_manager.update_quantifier_weight(tmp, new_weight); + TRACE("pattern_inference", tout << "found patterns in database, weight: " << new_weight << "\n" << mk_pp(new_q, m_manager) << "\n";); + } + proof * pr = 0; + if (m_manager.fine_grain_proofs()) + pr = m_manager.mk_rewrite(q, new_q); + cache_result(q, new_q, pr); + return; + } + } + + if (q->get_num_patterns() > 0) { + simplifier::reduce1_quantifier(q); + return; + } + + if (m_params.m_pi_nopat_weight >= 0) + weight = m_params.m_pi_nopat_weight; + + SASSERT(q->get_num_patterns() == 0); + expr * new_body; + proof * new_body_pr; + get_cached(q->get_expr(), new_body, new_body_pr); + + ptr_buffer new_no_patterns; + unsigned num_no_patterns = q->get_num_no_patterns(); + for (unsigned i = 0; i < num_no_patterns; i++) { + expr * new_pattern; + proof * new_pattern_pr; + get_cached(q->get_no_pattern(i), new_pattern, new_pattern_pr); + new_no_patterns.push_back(new_pattern); + } + + app_ref_buffer new_patterns(m_manager); + + if (m_params.m_pi_arith == AP_CONSERVATIVE) + m_forbidden.push_back(m_afid); + + mk_patterns(q->get_num_decls(), new_body, new_no_patterns.size(), new_no_patterns.c_ptr(), new_patterns); + + if (new_patterns.empty() && !new_no_patterns.empty()) { + if (new_patterns.empty()) { + mk_patterns(q->get_num_decls(), new_body, 0, 0, new_patterns); + if (m_params.m_pi_warnings && !new_patterns.empty()) { + warning_msg("ignoring nopats annotation because Z3 couldn't find any other pattern (quantifier id: %s)", q->get_qid().str().c_str()); + } + } + } + + if (m_params.m_pi_arith == AP_CONSERVATIVE) { + m_forbidden.pop_back(); + if (new_patterns.empty()) { + flet l1(m_block_loop_patterns, false); // allow looping patterns + mk_patterns(q->get_num_decls(), new_body, new_no_patterns.size(), new_no_patterns.c_ptr(), new_patterns); + if (!new_patterns.empty()) { + weight = std::max(weight, static_cast(m_params.m_pi_arith_weight)); + if (m_params.m_pi_warnings) { + warning_msg("using arith. in pattern (quantifier id: %s), the weight was increased to %d (this value can be modified using PI_ARITH_WEIGHT=).", + q->get_qid().str().c_str(), weight); + } + } + } + } + + if (m_params.m_pi_arith != AP_NO && new_patterns.empty()) { + if (new_patterns.empty()) { + flet l1(m_nested_arith_only, false); // try to find a non-nested arith pattern + flet l2(m_block_loop_patterns, false); // allow looping patterns + mk_patterns(q->get_num_decls(), new_body, new_no_patterns.size(), new_no_patterns.c_ptr(), new_patterns); + if (!new_patterns.empty()) { + weight = std::max(weight, static_cast(m_params.m_pi_non_nested_arith_weight)); + if (m_params.m_pi_warnings) { + warning_msg("using non nested arith. pattern (quantifier id: %s), the weight was increased to %d (this value can be modified using PI_NON_NESTED_ARITH_WEIGHT=).", + q->get_qid().str().c_str(), weight); + } + // verbose_stream() << mk_pp(q, m_manager) << "\n"; + } + } + } + + quantifier_ref new_q(m_manager); + new_q = m_manager.update_quantifier(q, new_patterns.size(), (expr**) new_patterns.c_ptr(), new_body); + if (weight != q->get_weight()) + new_q = m_manager.update_quantifier_weight(new_q, weight); + proof_ref pr(m_manager); + if (m_manager.fine_grain_proofs()) { + if (new_body_pr == 0) + new_body_pr = m_manager.mk_reflexivity(new_body); + pr = m_manager.mk_quant_intro(q, new_q, new_body_pr); + } + + if (new_patterns.empty() && m_params.m_pi_pull_quantifiers) { + pull_quant pull(m_manager); + expr_ref new_expr(m_manager); + proof_ref new_pr(m_manager); + pull(new_q, new_expr, new_pr); + quantifier * new_new_q = to_quantifier(new_expr); + if (new_new_q != new_q) { + mk_patterns(new_new_q->get_num_decls(), new_new_q->get_expr(), 0, 0, new_patterns); + if (!new_patterns.empty()) { + if (m_params.m_pi_warnings) { + warning_msg("pulled nested quantifier to be able to find an useable pattern (quantifier id: %s)", q->get_qid().str().c_str()); + } + new_q = m_manager.update_quantifier(new_new_q, new_patterns.size(), (expr**) new_patterns.c_ptr(), new_new_q->get_expr()); + if (m_manager.fine_grain_proofs()) { + pr = m_manager.mk_transitivity(pr, new_pr); + pr = m_manager.mk_transitivity(pr, m_manager.mk_quant_intro(new_new_q, new_q, m_manager.mk_reflexivity(new_q->get_expr()))); + } + TRACE("pattern_inference", tout << "pulled quantifier:\n" << mk_pp(new_q, m_manager) << "\n";); + } + } + } + + if (new_patterns.empty()) { + if (m_params.m_pi_warnings) { + warning_msg("failed to find a pattern for quantifier (quantifier id: %s)", q->get_qid().str().c_str()); + } + TRACE("pi_failed", tout << mk_pp(q, m_manager) << "\n";); + } + + if (new_patterns.empty() && new_body == q->get_expr()) { + cache_result(q, q, 0); + return; + } + + cache_result(q, new_q, pr); + +} + + +#if 0 +// unused +static void dump_expr_vector(std::ostream & out, ptr_vector const & v, ast_manager & m) { + ptr_vector::const_iterator it = v.begin(); + ptr_vector::const_iterator end = v.end(); + for (; it != end; ++it) + out << mk_pp(*it, m) << "\n"; +} +#endif + diff --git a/lib/pattern_inference.h b/lib/pattern_inference.h new file mode 100644 index 000000000..057a74f76 --- /dev/null +++ b/lib/pattern_inference.h @@ -0,0 +1,248 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + pattern_inference.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2006-12-08. + +Revision History: + +--*/ +#ifndef _PATTERN_INFERENCE_H_ +#define _PATTERN_INFERENCE_H_ + +#include"ast.h" +#include"simplifier.h" +#include"pattern_inference_params.h" +#include"vector.h" +#include"uint_set.h" +#include"nat_set.h" +#include"obj_hashtable.h" +#include"obj_pair_hashtable.h" +#include"map.h" +#include"expr_pattern_match.h" + +/** + \brief A pattern p_1 is smaller than a pattern p_2 iff + every instance of p_2 is also an instance of p_1. + + Example: f(X) is smaller than f(g(X)) because + every instance of f(g(X)) is also an instance of f(X). +*/ +class smaller_pattern { + ast_manager & m_manager; + ptr_vector m_bindings; + + typedef std::pair expr_pair; + typedef obj_pair_hashtable cache; + + svector m_todo; + cache m_cache; + void save(expr * p1, expr * p2); + bool process(expr * p1, expr * p2); + + smaller_pattern & operator=(smaller_pattern const &); + +public: + + smaller_pattern(ast_manager & m): + m_manager(m) { + } + + bool operator()(unsigned num_bindings, expr * p1, expr * p2); +}; + +class pattern_inference : public simplifier { + pattern_inference_params & m_params; + family_id m_bfid; + family_id m_afid; + svector m_forbidden; + obj_hashtable m_preferred; + smaller_pattern m_le; + unsigned m_num_bindings; + unsigned m_num_no_patterns; + expr * const * m_no_patterns; + bool m_nested_arith_only; + bool m_block_loop_patterns; + + struct info { + uint_set m_free_vars; + unsigned m_size; + info(uint_set const & vars, unsigned size): + m_free_vars(vars), + m_size(size) { + } + info(): + m_free_vars(), + m_size(0) { + } + }; + + typedef obj_map expr2info; + + expr2info m_candidates_info; // candidate -> set of free vars + size + app_ref_vector m_candidates; + + ptr_vector m_tmp1; + ptr_vector m_tmp2; + ptr_vector m_todo; + + // Compare candidates patterns based on their usefulness + // p1 < p2 if + // - p1 has more free variables than p2 + // - p1 and p2 has the same number of free variables, + // and p1 is smaller than p2. + struct pattern_weight_lt { + expr2info & m_candidates_info; + pattern_weight_lt(expr2info & i): + m_candidates_info(i) { + } + bool operator()(expr * n1, expr * n2) const; + }; + + pattern_weight_lt m_pattern_weight_lt; + + // + // Functor for collecting candidates. + // + class collect { + struct entry { + expr * m_node; + unsigned m_delta; + entry():m_node(0), m_delta(0) {} + entry(expr * n, unsigned d):m_node(n), m_delta(d) {} + unsigned hash() const { + return hash_u_u(m_node->get_id(), m_delta); + } + bool operator==(entry const & e) const { + return m_node == e.m_node && m_delta == e.m_delta; + } + }; + + struct info { + expr_ref m_node; + uint_set m_free_vars; + unsigned m_size; + info(ast_manager & m, expr * n, uint_set const & vars, unsigned sz): + m_node(n, m), m_free_vars(vars), m_size(sz) {} + }; + + ast_manager & m_manager; + pattern_inference & m_owner; + family_id m_afid; + unsigned m_num_bindings; + typedef map, default_eq > cache; + cache m_cache; + ptr_vector m_info; + svector m_todo; + + void visit(expr * n, unsigned delta, bool & visited); + bool visit_children(expr * n, unsigned delta); + void save(expr * n, unsigned delta, info * i); + void save_candidate(expr * n, unsigned delta); + void reset(); + public: + collect(ast_manager & m, pattern_inference & o):m_manager(m), m_owner(o), m_afid(m.get_family_id("arith")) {} + void operator()(expr * n, unsigned num_bindings); + }; + + collect m_collect; + + void add_candidate(app * n, uint_set const & s, unsigned size); + + void filter_looping_patterns(ptr_vector & result); + + bool has_preferred_patterns(ptr_vector & candidate_patterns, app_ref_buffer & result); + + void filter_bigger_patterns(ptr_vector const & patterns, ptr_vector & result); + + class contains_subpattern { + pattern_inference & m_owner; + nat_set m_already_processed; + ptr_vector m_todo; + void save(expr * n); + public: + contains_subpattern(pattern_inference & owner): + m_owner(owner) {} + bool operator()(expr * n); + }; + + contains_subpattern m_contains_subpattern; + + bool contains_subpattern(expr * n); + + struct pre_pattern { + ptr_vector m_exprs; // elements of the pattern. + uint_set m_free_vars; // set of free variables in m_exprs + unsigned m_idx; // idx of the next candidate to process. + pre_pattern(): + m_idx(0) { + } + }; + + ptr_vector m_pre_patterns; + expr_pattern_match m_database; + + void candidates2unary_patterns(ptr_vector const & candidate_patterns, + ptr_vector & remaining_candidate_patterns, + app_ref_buffer & result); + + void candidates2multi_patterns(unsigned max_num_patterns, + ptr_vector const & candidate_patterns, + app_ref_buffer & result); + + void reset_pre_patterns(); + + /** + \brief All minimal unary patterns (i.e., expressions that + contain all bound variables) are copied to result. If there + are unary patterns, then at most num_extra_multi_patterns multi + patterns are created. If there are no unary pattern, then at + most 1 + num_extra_multi_patterns multi_patterns are created. + */ + void mk_patterns(unsigned num_bindings, // IN number of bindings. + expr * n, // IN node where the patterns are going to be extracted. + unsigned num_no_patterns, // IN num. patterns that should not be used. + expr * const * no_patterns, // IN patterns that should not be used. + app_ref_buffer & result); // OUT result + + virtual void reduce1_quantifier(quantifier * q); + +public: + pattern_inference(ast_manager & m, pattern_inference_params & params); + + void register_forbidden_family(family_id fid) { + SASSERT(fid != m_bfid); + m_forbidden.push_back(fid); + } + + /** + \brief Register f as a preferred function symbol. The inference algorithm + gives preference to patterns rooted by this kind of function symbol. + */ + void register_preferred(func_decl * f) { + m_preferred.insert(f); + } + + void register_preferred(unsigned num, func_decl * const * fs) { for (unsigned i = 0; i < num; i++) register_preferred(fs[i]); } + + bool is_forbidden(func_decl const * decl) const { + family_id fid = decl->get_family_id(); + if (fid == m_bfid && decl->get_decl_kind() != OP_TRUE && decl->get_decl_kind() != OP_FALSE) + return true; + return std::find(m_forbidden.begin(), m_forbidden.end(), fid) != m_forbidden.end(); + } + + bool is_forbidden(app * n) const; +}; + +#endif /* _PATTERN_INFERENCE_H_ */ + diff --git a/lib/pattern_inference_params.cpp b/lib/pattern_inference_params.cpp new file mode 100644 index 000000000..38fc24dca --- /dev/null +++ b/lib/pattern_inference_params.cpp @@ -0,0 +1,37 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + pattern_inference_params.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-03-24. + +Revision History: + +--*/ +#include"pattern_inference_params.h" + +void pattern_inference_params::register_params(ini_params & p) { + p.register_unsigned_param("PI_MAX_MULTI_PATTERNS", m_pi_max_multi_patterns, + "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"); + p.register_bool_param("PI_BLOCK_LOOOP_PATTERNS", m_pi_block_loop_patterns, + "block looping patterns during pattern inference"); + p.register_int_param("PI_ARITH", 0, 2, reinterpret_cast(m_pi_arith), + "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."); + p.register_bool_param("PI_USE_DATABASE", m_pi_use_database); + p.register_unsigned_param("PI_ARITH_WEIGHT", m_pi_arith_weight, "default weight for quantifiers where the only available pattern has nested arithmetic terms."); + p.register_unsigned_param("PI_NON_NESTED_ARITH_WEIGHT", m_pi_non_nested_arith_weight, "default weight for quantifiers where the only available pattern has non nested arithmetic terms."); + p.register_bool_param("PI_PULL_QUANTIFIERS", m_pi_pull_quantifiers, "pull nested quantifiers, if no pattern was found."); + p.register_int_param("PI_NOPAT_WEIGHT", m_pi_nopat_weight, "set weight of quantifiers without patterns, if negative the weight is not changed."); + p.register_bool_param("PI_AVOID_SKOLEMS", m_pi_avoid_skolems); + p.register_bool_param("PI_WARNINGS", m_pi_warnings, "enable/disable warning messages in the pattern inference module."); +} + + diff --git a/lib/pattern_inference_params.h b/lib/pattern_inference_params.h new file mode 100644 index 000000000..79d7b4d87 --- /dev/null +++ b/lib/pattern_inference_params.h @@ -0,0 +1,59 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + pattern_inference_params.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-03-24. + +Revision History: + +--*/ +#ifndef _PATTERN_INFERENCE_PARAMS_H_ +#define _PATTERN_INFERENCE_PARAMS_H_ + +#include"ini_file.h" + +enum arith_pattern_inference_kind { + AP_NO, // do not infer patterns with arithmetic terms + AP_CONSERVATIVE, // only infer patterns with arithmetic terms if there is no other option + AP_FULL // always use patterns with arithmetic terms +}; + +struct pattern_inference_params { + unsigned m_pi_max_multi_patterns; + bool m_pi_block_loop_patterns; + arith_pattern_inference_kind m_pi_arith; + bool m_pi_use_database; + unsigned m_pi_arith_weight; + unsigned m_pi_non_nested_arith_weight; + bool m_pi_pull_quantifiers; + int m_pi_nopat_weight; + bool m_pi_avoid_skolems; + bool m_pi_warnings; + + pattern_inference_params(): + m_pi_max_multi_patterns(0), + m_pi_block_loop_patterns(true), + m_pi_arith(AP_CONSERVATIVE), + m_pi_use_database(false), + m_pi_arith_weight(5), + m_pi_non_nested_arith_weight(10), + m_pi_pull_quantifiers(true), + m_pi_nopat_weight(-1), + m_pi_avoid_skolems(true), + m_pi_warnings(false) { + } + + void register_params(ini_params & p); +}; + +#endif /* _PATTERN_INFERENCE_PARAMS_H_ */ + diff --git a/lib/pattern_validation.cpp b/lib/pattern_validation.cpp new file mode 100644 index 000000000..fe3292312 --- /dev/null +++ b/lib/pattern_validation.cpp @@ -0,0 +1,105 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + pattern_validation.cpp + +Abstract: + + Code for checking whether a pattern is valid or not. + +Author: + + Leonardo de Moura (leonardo) 2006-12-08. + +Revision History: + +--*/ + +#include"pattern_validation.h" +#include"for_each_expr.h" +#include"warning.h" + +#include"ast_pp.h" + +struct pattern_validation_functor { + uint_set & m_found_vars; + unsigned m_num_bindings; + unsigned m_num_new_bindings; + bool m_result; + bool m_found_a_var; + family_id m_bfid; + family_id m_lfid; + + pattern_validation_functor(uint_set & found_vars, unsigned num_bindings, unsigned num_new_bindings, + family_id bfid, family_id lfid): + m_found_vars(found_vars), + m_num_bindings(num_bindings), + m_num_new_bindings(num_new_bindings), + m_result(true), + m_found_a_var(false), + m_bfid(bfid), + m_lfid(lfid) { + } + + bool is_forbidden(func_decl const * decl) { + family_id fid = decl->get_family_id(); + if (fid == m_bfid && decl->get_decl_kind() != OP_TRUE && decl->get_decl_kind() != OP_FALSE) + return true; + if (fid == m_lfid) + return true; + return false; + } + + void operator()(app * n) { + func_decl * decl = to_app(n)->get_decl(); + if (is_forbidden(decl)) { + warning_msg("'%s' cannot be used in patterns.", decl->get_name().str().c_str()); + m_result = false; + } + } + + void operator()(var * v) { + unsigned idx = to_var(v)->get_idx(); + if (idx >= m_num_bindings) { + warning_msg("free variables cannot be used in patterns."); + m_result = false; + return; + } + if (idx < m_num_new_bindings) { + m_found_a_var = true; + m_found_vars.insert(idx); + } + } + + void operator()(quantifier * q) { m_result = false; } +}; + +bool pattern_validator::process(uint_set & found_vars, unsigned num_bindings, unsigned num_new_bindings, expr * n) { + // I'm traversing the DAG as a tree, this is not a problem since pattern are supposed to be small ASTs. + if (n->get_kind() == AST_VAR) { + warning_msg("invalid pattern: variable."); + return false; + } + + pattern_validation_functor f(found_vars, num_bindings, num_new_bindings, m_bfid, m_lfid); + for_each_expr(f, n); + if (!f.m_result) + return false; + if (!f.m_found_a_var) { + warning_msg("pattern does contain any variable."); + return false; + } + return true; +} + +bool pattern_validator::operator()(unsigned num_bindings, unsigned num_new_bindings, expr * n) { + uint_set found_vars; + if (!process(found_vars, num_bindings, num_new_bindings, n)) + return false; + bool r = found_vars.num_elems() == num_new_bindings; + if (!r) + warning_msg("pattern does not contain all quantified variables."); + return r; +} diff --git a/lib/pattern_validation.h b/lib/pattern_validation.h new file mode 100644 index 000000000..d78502a8f --- /dev/null +++ b/lib/pattern_validation.h @@ -0,0 +1,44 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + pattern_validation.h + +Abstract: + + Code for checking whether a pattern is valid or not. + +Author: + + Leonardo de Moura (leonardo) 2006-12-08. + +Revision History: + +--*/ +#ifndef _PATTERN_VALIDATION_H_ +#define _PATTERN_VALIDATION_H_ + +#include"ast.h" +#include"uint_set.h" +#include"vector.h" + +class pattern_validator { + family_id m_bfid; + family_id m_lfid; + + bool process(uint_set & found_vars, unsigned num_bindings, unsigned num_new_bindings, expr * n); + +public: + pattern_validator(ast_manager const & m): + m_bfid(m.get_basic_family_id()), + m_lfid(m.get_family_id("label")) { + } + + bool operator()(unsigned num_bindings, unsigned num_new_bindings, expr * n); + + bool operator()(unsigned num_new_bindings, expr * n) { return operator()(UINT_MAX, num_new_bindings, n); } +}; + +#endif /* _PATTERN_VALIDATION_H_ */ + diff --git a/lib/pb2bv_model_converter.cpp b/lib/pb2bv_model_converter.cpp new file mode 100644 index 000000000..3e17ea675 --- /dev/null +++ b/lib/pb2bv_model_converter.cpp @@ -0,0 +1,102 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + pb2bv_model_converter.cpp + +Abstract: + + Model converter for the pb2bv tactic. + +Author: + + Christoph (cwinter) 2012-02-15 + +Notes: + +--*/ +#include"trace.h" +#include"arith_decl_plugin.h" +#include"model_v2_pp.h" +#include"pb2bv_model_converter.h" + +pb2bv_model_converter::pb2bv_model_converter(ast_manager & _m, obj_map const & c2bit, bound_manager const & bm): + m(_m) { + obj_map::iterator it = c2bit.begin(); + obj_map::iterator end = c2bit.end(); + for ( ; it != end; it++) { + m_c2bit.push_back(func_decl_pair(it->m_key, to_app(it->m_value)->get_decl())); + m.inc_ref(it->m_key); + m.inc_ref(to_app(it->m_value)->get_decl()); + } + bound_manager::iterator it2 = bm.begin(); + bound_manager::iterator end2 = bm.end(); + for (; it2 != end2; ++it2) { + expr * c = *it2; + SASSERT(is_uninterp_const(c)); + func_decl * d = to_app(c)->get_decl(); + if (!c2bit.contains(d)) { + SASSERT(d->get_arity() == 0); + m.inc_ref(d); + m_c2bit.push_back(func_decl_pair(d, static_cast(0))); + } + } +} + +pb2bv_model_converter::~pb2bv_model_converter() { + svector::const_iterator it = m_c2bit.begin(); + svector::const_iterator end = m_c2bit.end(); + for (; it != end; ++it) { + m.dec_ref(it->first); + m.dec_ref(it->second); + } +} + +void pb2bv_model_converter::operator()(model_ref & md) { + (*this)(md, 0); +} + +void pb2bv_model_converter::operator()(model_ref & md, unsigned goal_idx) { + SASSERT(goal_idx == 0); + TRACE("pb2bv", tout << "converting model:\n"; model_v2_pp(tout, *md); display(tout);); + arith_util a_util(m); + + svector::const_iterator it = m_c2bit.begin(); + svector::const_iterator end = m_c2bit.end(); + for (; it != end; ++it) { + if (it->second) { + expr * val = md->get_const_interp(it->second); + if (val == 0 || m.is_false(val)) { + /* false's and don't cares get the integer 0 solution*/ + md->register_decl(it->first, a_util.mk_numeral(rational(0), true)); + } + else { + md->register_decl(it->first, a_util.mk_numeral(rational(1), true)); + } + } + else { + // it->first is a don't care. + md->register_decl(it->first, a_util.mk_numeral(rational(0), true)); + } + } +} + +void pb2bv_model_converter::display(std::ostream & out) { + out << "(pb2bv-model-converter"; + svector::const_iterator it = m_c2bit.begin(); + svector::const_iterator end = m_c2bit.end(); + for (; it != end; ++it) { + out << "\n (" << it->first->get_name() << " "; + if (it->second == 0) + out << "0"; + else + out << it->second->get_name(); + out << ")"; + } + out << ")\n"; +} + +model_converter * pb2bv_model_converter::translate(ast_translation & translator) { + NOT_IMPLEMENTED_YET(); +} diff --git a/lib/pb2bv_model_converter.h b/lib/pb2bv_model_converter.h new file mode 100644 index 000000000..ada012d1d --- /dev/null +++ b/lib/pb2bv_model_converter.h @@ -0,0 +1,39 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + pb2bv_model_converter.h + +Abstract: + + Model converter for the pb2bv tactic. + +Author: + + Christoph (cwinter) 2012-02-15 + +Notes: + +--*/ +#ifndef _PB2BV_MODEL_CONVERTER_ +#define _PB2BV_MODEL_CONVERTER_ + +#include"model_converter.h" +#include"bound_manager.h" + +class pb2bv_model_converter : public model_converter { + typedef std::pair func_decl_pair; + + ast_manager & m; + svector m_c2bit; +public: + pb2bv_model_converter(ast_manager & _m, obj_map const & c2bit, bound_manager const & bm); + virtual ~pb2bv_model_converter(); + virtual void operator()(model_ref & md); + virtual void operator()(model_ref & md, unsigned goal_idx); + virtual void display(std::ostream & out); + virtual model_converter * translate(ast_translation & translator); +}; + +#endif diff --git a/lib/pb2bv_tactic.cpp b/lib/pb2bv_tactic.cpp new file mode 100644 index 000000000..36cc801dc --- /dev/null +++ b/lib/pb2bv_tactic.cpp @@ -0,0 +1,1051 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + pb2bv_tactic.cpp + +Abstract: + + Tactic for converting Pseudo-Boolean constraints to BV + +Author: + + Christoph (cwinter) 2012-02-15 + +Notes: + +--*/ +#include"tactical.h" +#include"cooperate.h" +#include"bound_manager.h" +#include"bool_rewriter.h" +#include"rewriter_def.h" +#include"ref_util.h" +#include"arith_decl_plugin.h" +#include"trace.h" +#include"ast_smt2_pp.h" +#include"expr_substitution.h" +#include"filter_model_converter.h" +#include"pb2bv_model_converter.h" +#include"pb2bv_tactic.h" + +class pb2bv_tactic : public tactic { +public: + struct non_pb {}; + + struct only_01_visitor { + typedef rational numeral; + ast_manager & m; + arith_util & m_util; + bound_manager & m_bm; + + only_01_visitor(arith_util & u, bound_manager & bm): + m(u.get_manager()), + m_util(u), + m_bm(bm) { + } + + void throw_non_pb(expr * n) { + TRACE("pb2bv", tout << "Not pseudo-Boolean: " << mk_ismt2_pp(n, m) << "\n";); + throw non_pb(); + } + + void operator()(var * n) { + throw_non_pb(n); + } + + void operator()(app * n) { + family_id fid = n->get_family_id(); + if (fid == m.get_basic_family_id()) { + // all basic family ops (but term-ite and distinct) are OK + if (m.is_term_ite(n) || m.is_distinct(n)) + throw_non_pb(n); + return; + } + + if (fid == m_util.get_family_id()) { + // check if linear + switch (n->get_decl_kind()) { + case OP_LE: case OP_GE: case OP_LT: case OP_GT: + case OP_ADD: case OP_NUM: + return; + case OP_MUL: + if (n->get_num_args() != 2) + throw_non_pb(n); + if (!m_util.is_numeral(n->get_arg(0))) + throw_non_pb(n); + return; + default: + throw_non_pb(n); + } + } + + if (is_uninterp_const(n)) { + if (m.is_bool(n)) + return; // boolean variables are ok + if (m_util.is_int(n)) { + numeral l, u; bool s; + if (m_bm.has_lower(n, l, s) && + m_bm.has_upper(n, u, s) && + (l.is_zero() || l.is_one()) && + (u.is_zero() || u.is_one())) + return; + } + } + + throw_non_pb(n); + } + + void operator()(quantifier * n) { + throw_non_pb(n); + } + }; +private: + + struct imp { + typedef rational numeral; + + ast_manager & m; + bound_manager m_bm; + bool_rewriter m_b_rw; + arith_util m_arith_util; + bv_util m_bv_util; + expr_dependency_ref_vector m_new_deps; + + bool m_produce_models; + bool m_produce_unsat_cores; + + unsigned m_all_clauses_limit; + unsigned m_cardinality_limit; + unsigned long long m_max_memory; + // m_const2bit should be a map, since we want constant time access to it, and avoid quadratic behavior. + // It is ok to use a vector at the model converter because we don't need to search that vector. + obj_map m_const2bit; + obj_map m_not_const2bit; + expr_ref_vector m_temporary_ints; + + expr_dependency_ref m_used_dependencies; + + struct lit { + expr * m_v; + public: + lit(expr * v, bool sign = false):m_v(TAG(expr*, v, sign)) {} + bool sign() const { return GET_TAG(m_v) == 1; } + expr * var() const { return UNTAG(expr*, m_v); } + void neg() { + #ifdef Z3DEBUG + bool s = sign(); + #endif + m_v = TAG(expr*, UNTAG(expr*, m_v), !sign()); + SASSERT(s == !sign()); + } + }; + + struct monomial { + numeral m_a; + lit m_lit; + monomial(lit l):m_a(1), m_lit(l) {} + monomial(numeral const & a, lit l):m_a(a), m_lit(l) {} + }; + + typedef vector polynomial; + + struct monomial_lt { + bool operator()(monomial const & m1, monomial const & m2) const { return m1.m_a > m2.m_a; } + }; + + enum constraint_kind { EQ, GE, LE }; + + struct failed {}; + + struct visitor { + imp & m_owner; + + visitor(imp & o):m_owner(o) {} + + void throw_failed(expr * n) { + throw failed(); + } + + void operator()(var * n) { + throw_failed(n); + } + + void operator()(app * n) { + } + + void operator()(quantifier * n) { + throw_failed(n); + } + }; + + void checkpoint() { + cooperate("pb2bv"); + if (memory::get_allocation_size() > m_max_memory) + throw tactic_exception(TACTIC_MAX_MEMORY_MSG); + } + + void quick_pb_check(goal_ref const & g) { + expr_fast_mark1 visited; + only_01_visitor proc(m_arith_util, m_bm); + unsigned sz = g->size(); + for (unsigned i = 0; i < sz; i++) { + expr * f = g->form(i); + for_each_expr_core(proc, visited, f); + } + } + + struct rw_cfg : public default_rewriter_cfg { + ast_manager & m; + imp & owner; + expr_ref m_saved_res; + + rw_cfg(imp & o): + m(o.m), + owner(o), + m_saved_res(m) { + } + + bool max_steps_exceeded(unsigned num_steps) const { + cooperate("pb2bv"); + if (memory::get_allocation_size() > owner.m_max_memory) + throw tactic_exception(TACTIC_MAX_MEMORY_MSG); + return false; + } + + bool get_subst(expr * s, expr * & t, proof * & t_pr) { + t_pr = 0; + if (owner.is_constraint_core(s)) { + owner.convert(to_app(s), m_saved_res, true, false); + t = m_saved_res; + TRACE("pb2bv_convert", tout << mk_ismt2_pp(s, m) << "\n-->\n" << mk_ismt2_pp(t, m) << "\n";); + return true; + } + return false; + } + }; + + struct rw : public rewriter_tpl { + rw_cfg m_cfg; + + rw(imp & o): + rewriter_tpl(o.m, false, m_cfg), + m_cfg(o) { + } + }; + + rw m_rw; + + struct pb2bv_all_clauses { + imp & m_owner; + ast_manager & m; + unsigned m_size; + vector m_sums; + expr_ref_vector m_lits; + ptr_vector m_cls; + polynomial const * m_pol; + expr_ref_vector m_result; + + pb2bv_all_clauses(imp & owner): + m_owner(owner), + m(m_owner.m), + m_lits(m), + m_result(m) { + } + + void init_lits(polynomial const & p) { + polynomial::const_iterator it = p.begin(); + polynomial::const_iterator end = p.end(); + for (; it != end; ++it) + m_lits.push_back(m_owner.mon_lit2lit(it->m_lit)); + } + + void init_sums(polynomial const & p) { + SASSERT(m_sums.empty()); + m_size = p.size(); + m_sums.resize(m_size); + unsigned i = m_size; + while (i > 0) { + --i; + if (i == m_size - 1) + m_sums[i] = p[i].m_a; + else + m_sums[i] = p[i].m_a + m_sums[i+1]; + } + } + + void process(unsigned idx, numeral c) { + if (c.is_nonpos()) + return; + + if (idx == m_size || m_sums[idx] < c) { + SASSERT(c.is_pos()); + // conflict 0 >= c > 0 + switch (m_cls.size()) { + case 0: m_result.push_back(m.mk_false()); break; + case 1: m_result.push_back(m_cls[0]); break; + default: m_result.push_back(m.mk_or(m_cls.size(), m_cls.c_ptr())); + } + return; + } + + m_owner.checkpoint(); + + m_cls.push_back(m_lits.get(idx)); + process(idx+1, c); + m_cls.pop_back(); + process(idx+1, c - (*m_pol)[idx].m_a); + } + + void operator()(polynomial const & m_p, numeral const & m_c, expr_ref & r) { + m_pol = &(m_p); + init_sums(m_p); + init_lits(m_p); + process(0, m_c); + m_owner.m_b_rw.mk_and(m_result.size(), m_result.c_ptr(), r); + } + }; + + void display(std::ostream & out, polynomial const & m_p, numeral const & m_c) const { + polynomial::const_iterator it = m_p.begin(); + polynomial::const_iterator end = m_p.end(); + for (bool first = true; it != end; ++it) { + if (!first) + out << " + "; + first = false; + if (!it->m_a.is_one()) + out << it->m_a << "*"; + if (it->m_lit.sign()) + out << "~"; + out << mk_ismt2_pp(it->m_lit.var(), m); + } + out << " >= " << m_c << "\n"; + } + + expr * int2lit(app * x, bool sign = false) { + func_decl * fd = x->get_decl(); + obj_map & const2lit = sign ? m_not_const2bit : m_const2bit; + + expr * r = 0; + const2lit.find(fd, r); + if (r != 0) + return r; + + r = m.mk_fresh_const(0, m.mk_bool_sort()); + expr * not_r = m.mk_not(r); + m_const2bit.insert(fd, r); + m_not_const2bit.insert(fd, not_r); + m.inc_ref(fd); + m.inc_ref(r); + m.inc_ref(not_r); + + return sign ? not_r : r; + } + + expr * mon_lit2lit(monomial const & mo) { + return int2lit(to_app(mo.m_lit.var()), mo.m_lit.sign()); + } + + expr * mk_unit(expr * t, bool sign) { + return mon_lit2lit(lit(t, sign)); + } + + static bool is_cardinality(polynomial const & m_p, numeral const & m_c) { + for (unsigned i = 0; i < m_p.size(); i++) { + if (!m_p[i].m_a.is_one()) + return false; + } + return true; + } + + void bitblast_pbc(polynomial & m_p, numeral const & m_c, expr_ref & r) { + bool is_card = is_cardinality(m_p, m_c); + + if (is_card && numeral(m_p.size()) < m_c) { + r = m.mk_false(); + return; + } + + if (is_card && m_c.is_one()) { + ptr_buffer args; + for (unsigned i = 0; i < m_p.size(); i++) { + args.push_back(mon_lit2lit(m_p[i])); + } + r = m.mk_or(args.size(), args.c_ptr()); + return; + } + + if (is_card && m_c == numeral(m_p.size())) { + ptr_buffer args; + for (unsigned i = 0; i < m_p.size(); i++) { + args.push_back(mon_lit2lit(m_p[i])); + } + m_b_rw.mk_and(args.size(), args.c_ptr(), r); + return; + } + + if (m_p.size() <= m_all_clauses_limit) { + pb2bv_all_clauses proc(*this); + proc(m_p, m_c, r); + return; + } + + if (is_card) { + SASSERT(m_c < numeral(m_p.size())); // After normalization, this should be true. + SASSERT(m_c.is_unsigned()); // Otherwise this is not going to fit into memory... + + unsigned n = m_p.size(); + unsigned k = m_c.get_unsigned(); + unsigned rowsz = n - k + 1; + + unsigned long long cost = k * rowsz; + if (cost <= static_cast(m_cardinality_limit)) { + SASSERT(rowsz > 0); + + expr_ref_vector tmp(m); + tmp.resize(rowsz, m.mk_true()); + + for (unsigned i = 0; i < k; i++) { + for (unsigned j = 0; j < rowsz; j++) { + expr_ref new_ite(m); + m_b_rw.mk_ite(mon_lit2lit(m_p[i + j]), + tmp.get(j), + j == 0 ? m.mk_false() : tmp.get(j-1), + new_ite); + tmp.set(j, new_ite.get()); + } + } + + TRACE("pb2bv_bv", tout << "BV Cardinality: " << mk_ismt2_pp(tmp.back(), m) << std::endl;); + r = tmp.back(); + return; + } + } + + TRACE("pb2bv_bv_detail", tout << "encoding:\n"; display(tout, m_p, m_c);); + // [Leo] improving number of bits needed. + // using (sum-of-coeffs).get_num_bits() + numeral sum; + for (unsigned i = 0; i < m_p.size(); i++) { + monomial const & mo = m_p[i]; + SASSERT(mo.m_a.is_pos()); + sum += mo.m_a; + } + + if (sum < m_c) { + // trivially false. + r = m.mk_false(); + return; + } + + unsigned bits = sum.get_num_bits(); + + TRACE("num_bits_bug", tout << "bits: " << bits << " sum: " << sum << " size: " << m_p.size() << "\n";); + + // [Leo]: The following assertion should hold, right? + // I mean, the constraints are normalized, then mo.m_a <= m_c for every monomial in cnstr. + // [Christoph]: I agree and never saw it violated so far! + SASSERT(m_c.get_num_bits() <= bits); + + ptr_buffer lhs_args; + + for (unsigned i = 0; i < m_p.size(); i++) { + monomial const & mo = m_p[i]; + // encode using if-then-else + expr * bv_monom = + m.mk_ite(mon_lit2lit(mo.m_lit), + m_bv_util.mk_numeral(mo.m_a, bits), + m_bv_util.mk_numeral(numeral(0), bits)); + lhs_args.push_back(bv_monom); + } + + expr * lhs = m.mk_app(m_bv_util.get_family_id(), OP_BADD, lhs_args.size(), lhs_args.c_ptr()); + expr * rhs = m_bv_util.mk_numeral(m_c, bits); + + r = m_bv_util.mk_ule(rhs, lhs); + } + + void split(polynomial & m_p, numeral & m_c, polynomial & m_clause) { + if (m_p.size() <= 2 || m_c.is_one()) + return; + + if (m_p[0].m_a != m_c || m_p[1].m_a != m_c) + return; // nothing to do. + + unsigned sz = m_p.size(); + unsigned i; + for (i = 2; i < sz; i++) { + if (m_p[i].m_a != m_c) + break; + } + + SASSERT (i < sz); + + // copy lits [0, i) to m_clause + for (unsigned j = 0; j < i; j++) + m_clause.push_back(monomial(numeral(1), m_p[j].m_lit)); + + app * new_var = m.mk_fresh_const(0, m_arith_util.mk_int()); + m_temporary_ints.push_back(new_var); + + m_clause.push_back(monomial(numeral(1), lit(new_var, true))); + + // remove monomials [0, i) from m_p and add new_var in the beginning + for (unsigned j = i; j < sz; j++) { + m_p[j - i + 1] = m_p[j]; + } + m_p.shrink(sz - i + 1); + m_p[0] = monomial(m_c, lit(new_var, false)); + } + + void mk_pbc(polynomial & m_p, numeral & m_c, expr_ref & r, bool enable_split) { + if (m_c.is_nonpos()) { + // constraint is equivalent to true. + r = m.mk_true(); + return; + } + polynomial::iterator it = m_p.begin(); + polynomial::iterator end = m_p.end(); + numeral a_gcd = it->m_a; + for (; it != end; ++it) { + if (it->m_a > m_c) + it->m_a = m_c; // trimming coefficients + a_gcd = gcd(a_gcd, it->m_a); + } + SASSERT(a_gcd.is_pos()); + if (!a_gcd.is_one()) { + it = m_p.begin(); + for (; it != end; ++it) + it->m_a /= a_gcd; + m_c = ceil(m_c/a_gcd); + } + it = m_p.begin(); + numeral a_sum; + for (; it != end; ++it) { + a_sum += m_c; + if (a_sum >= m_c) + break; + } + if (a_sum < m_c) { + // constraint is equivalent to false. + r = m.mk_false(); + return; + } + polynomial clause; + TRACE("split_bug", display(tout, m_p, m_c);); + if (enable_split) + split(m_p, m_c, clause); + TRACE("split_bug", display(tout, m_p, m_c); display(tout, clause, rational(1));); + if (clause.empty()) { + bitblast_pbc(m_p, m_c, r); + } + else { + expr_ref r1(m); + expr_ref r2(m); + bitblast_pbc(m_p, m_c, r1); + bitblast_pbc(clause, numeral(1), r2); + TRACE("split_bug", tout << mk_ismt2_pp(r1, m) << "\nAND\n" << mk_ismt2_pp(r2, m) << "\n";); + m_b_rw.mk_and(r1, r2, r); + } + } + + void adjust(bool & pos, constraint_kind & k, numeral & c) { + if (!pos) { + if (k == LE) { + // not (lhs <= c) --> lhs > c --> lhs >= c+1 + pos = true; + k = GE; + c++; + } + else if (k == GE) { + // not (lhs >= c) --> lhs < c --> lhs <= c-1 + pos = true; + k = LE; + c--; + } + } + SASSERT(pos || k == EQ); + } + + void throw_non_pb(expr * n) { + TRACE("pb2bv", tout << "Not pseudo-Boolean: " << mk_ismt2_pp(n, m) << "\n";); + throw non_pb(); + } + + // check if polynomial is encoding + // a_0*x_0 + a_0*~y_0 + ... + a_{n-1}*x_{n - 1} + a_{n - 1}*~y_{n - 1} = c + // x_0 = y_0, ..., x_{n - 1} = y_{n - 1} + bool is_eq_vector(polynomial const & p, numeral const & c) { + TRACE("is_eq_vector", display(tout, p, c);); + unsigned sz = p.size(); + if (sz % 2 == 1) + return false; // size must be even + // I implemented only the easy (and very common) case, where a_i = 2^{n-i-1} and c = 2^n - 1 + unsigned n = sz/2; + if (c != m_bv_util.power_of_two(n) - numeral(1)) + return false; + for (unsigned i = 0; i < n; i++) { + monomial const & m1 = p[i*2]; + monomial const & m2 = p[i*2+1]; + if (m1.m_lit.sign() == m2.m_lit.sign()) + return false; + if (m1.m_a != m2.m_a) + return false; + if (m1.m_a != m_bv_util.power_of_two(n - i - 1)) + return false; + } + return true; + } + + void add_bounds_dependencies(expr * a) { + m_used_dependencies = m.mk_join(m_used_dependencies, m_bm.lower_dep(a)); + m_used_dependencies = m.mk_join(m_used_dependencies, m_bm.upper_dep(a)); + } + + void convert(app * t, expr_ref & r, bool pos, bool root) { + constraint_kind k; + expr * lhs, * rhs; + if (m.is_eq(t, lhs, rhs)) { + if (is_uninterp_const(lhs) && is_uninterp_const(rhs)) { + add_bounds_dependencies(lhs); + add_bounds_dependencies(rhs); + r = m.mk_iff(mon_lit2lit(lit(lhs, false)), + mon_lit2lit(lit(rhs, !pos))); + return; + } + k = EQ; + } + else if (m_arith_util.is_le(t, lhs, rhs)) { + k = LE; + } + else if (m_arith_util.is_ge(t, lhs, rhs)) { + k = GE; + } + else { + throw_non_pb(t); + } + + numeral c; bool is_int; + if (m_arith_util.is_numeral(lhs, c)) { + adjust(pos, k, c); + if (is_uninterp_const(rhs)) { + add_bounds_dependencies(rhs); + + if (k == EQ) { + bool sign = c.is_zero(); + if (!pos) sign = !sign; + r = mk_unit(rhs, sign); + } + else if ((c.is_zero() && k == LE) || + (c.is_one() && k == GE)) { + // redundant 0 <= x, 1 >= x + TRACE("pb2bv", tout << "discarding:\n" << mk_ismt2_pp(t, m) << "\n";); + SASSERT(pos); + r = m.mk_true(); + } + else { + SASSERT((c.is_zero() && k == GE) || + (c.is_one() && k == LE)); + // unit 0 >= x, 1 <= x + SASSERT(pos); + r = mk_unit(rhs, k == GE); + } + return; + } + throw_non_pb(t); + } + if (!m_arith_util.is_numeral(rhs, c, is_int) || !is_int) + throw_non_pb(t); + + adjust(pos, k, c); + + if (is_uninterp_const(lhs)) { + add_bounds_dependencies(lhs); + + if (k == EQ) { + TRACE("pb2bv_bug", tout << "c: " << c << "\n";); + if (!c.is_zero() && !c.is_one()) { + // x = k --> true where k is not 0 or 1 + r = pos ? m.mk_false() : m.mk_true(); + } + else { + bool sign = c.is_zero(); + if (!pos) sign = !sign; + r = mk_unit(lhs, sign); + } + return; + } + else { + // Our atom is of the form: (<= lhs c) or (>= lhs c) + // c may be different from 0,1. + if (k == LE) { + // x <= c >= 1 + if (c >= numeral(1)) { + TRACE("pb2bv", tout << "discarding:\n" << mk_ismt2_pp(t, m) << "\n";); + r = m.mk_true(); + return; + } + else if (c.is_neg()) { + // x <= c < 0 + r = m.mk_false(); + return; + } + SASSERT(c.is_zero()); + } + else if (k == GE) { + if (c.is_nonpos()) { + TRACE("pb2bv", tout << "discarding:\n" << mk_ismt2_pp(t, m) << "\n";); + // x >= a <= 0 + r = m.mk_true(); + return; + } + else if (c > numeral(1)) { + // x >= a > 1 + r = m.mk_false(); + return; + } + SASSERT(c.is_one()); + } + CTRACE("pb2bv", !(c.is_zero() || c.is_one()), + tout << "BUG: " << mk_ismt2_pp(t, m) << "\nk: " << k << " " << c << "\n";); + SASSERT(c.is_zero() || c.is_one()); + SASSERT(!((c.is_zero() && k == GE) || + (c.is_one() && k == LE))); + + CTRACE("pb2bv_bug", !((c.is_zero() && k == LE) || (c.is_one() && k == GE)), + tout << "c: " << c << ", k: " << k << "\n"; + tout << "t: " << mk_ismt2_pp(t, m) << "\n";); + SASSERT((c.is_zero() && k == LE) || + (c.is_one() && k == GE)); + // x <= 0, x >= 1 + SASSERT(pos); + r = mk_unit(lhs, k == LE); + return; + } + } + + if (!m_arith_util.is_add(lhs)) + throw_non_pb(t); + + unsigned sz = to_app(lhs)->get_num_args(); + expr * const * ms = to_app(lhs)->get_args(); + expr * a, * x; + for (unsigned i = 0; i < sz; i++) { + expr * m = ms[i]; + if (is_uninterp_const(m)) + continue; + if (m_arith_util.is_mul(m, a, x) && m_arith_util.is_numeral(a) && is_uninterp_const(x)) + continue; + throw_non_pb(t); + } + + // is pb constraint. + numeral a_val; + polynomial m_p; + numeral m_c; + m_c = c; + for (unsigned i = 0; i < sz; i++) { + expr * m = ms[i]; + if (is_uninterp_const(m)) { + add_bounds_dependencies(m); + m_p.push_back(monomial(lit(m))); + } + else if (m_arith_util.is_mul(m, a, x) && m_arith_util.is_numeral(a, a_val)) { + add_bounds_dependencies(x); + if (a_val.is_neg()) { + a_val.neg(); + // -a x --> -a(1-!x) ==> -a + a!x, + m_c += a_val; + m_p.push_back(monomial(a_val, lit(x, true))); + } + else { + m_p.push_back(monomial(a_val, lit(x))); + } + } + else { + UNREACHABLE(); + } + } + + std::stable_sort(m_p.begin(), m_p.end(), monomial_lt()); + + if (k == GE) { + mk_pbc(m_p, m_c, r, root); + } + else if (k == LE) { + m_c.neg(); + for (unsigned i = 0; i < sz; i++) { + monomial & m = m_p[i]; + SASSERT(m.m_a.is_nonneg()); + m_c += m.m_a; + m.m_lit.neg(); + } + mk_pbc(m_p, m_c, r, root); + } + else { + SASSERT(k == EQ); + + if (is_eq_vector(m_p, m_c)) { + TRACE("is_eq_vector", tout << "found eq vector\n";); + unsigned sz = m_p.size(); + expr_ref_vector eqs(m); + for (unsigned i = 0; i < sz; i += 2) { + app * x_i = to_app(m_p[i].m_lit.var()); + app * y_i = to_app(m_p[i+1].m_lit.var()); + eqs.push_back(m.mk_eq(int2lit(x_i), int2lit(y_i))); + } + m_b_rw.mk_and(eqs.size(), eqs.c_ptr(), r); + if (!pos) + m_b_rw.mk_not(r, r); + return; + } + + polynomial m_p2; + numeral m_c2 = m_c; + m_c2.neg(); + for (unsigned i = 0; i < sz; i++) { + monomial m = m_p[i]; + SASSERT(m.m_a.is_nonneg()); + m_c2 += m.m_a; + m.m_lit.neg(); + m_p2.push_back(m); + } + expr_ref r1(m); + expr_ref r2(m); + mk_pbc(m_p, m_c, r1, false); + mk_pbc(m_p2, m_c2, r2, false); + TRACE("pb2bv_convert", tout << mk_ismt2_pp(t, m) << "\n"; + display(tout, m_p, m_c); + display(tout, m_p2, m_c2); + tout << "--->\n" << mk_ismt2_pp(r1, m) << "\nAND\n" << mk_ismt2_pp(r2, m) << "\n";); + m_b_rw.mk_and(r1, r2, r); + if (!pos) + m_b_rw.mk_not(r, r); + } + } + + bool is_constraint_core(expr * n) { + return (m.is_eq(n) && m_arith_util.is_int(to_app(n)->get_arg(0))) || m_arith_util.is_le(n) || m_arith_util.is_ge(n); + } + + bool is_constraint(expr * n, expr * & atom, bool & pos) { + pos = true; + while (m.is_not(n)) { + n = to_app(n)->get_arg(0); + pos = !pos; + } + atom = n; + return is_constraint_core(n); + } + + imp(ast_manager & _m, params_ref const & p): + m(_m), + m_bm(m), + m_b_rw(m, p), + m_arith_util(m), + m_bv_util(m), + m_new_deps(m), + m_temporary_ints(m), + m_used_dependencies(m), + m_rw(*this) { + updt_params(p); + m_b_rw.set_flat(false); // no flattening otherwise will blowup the memory + m_b_rw.set_elim_and(true); + } + + ~imp() { + dec_ref_map_key_values(m, m_const2bit); + dec_ref_map_values(m, m_not_const2bit); + m_rw.reset(); + m_bm.reset(); + m_temporary_ints.reset(); + } + + void updt_params(params_ref const & p) { + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + m_all_clauses_limit = p.get_uint(":pb2bv-all-clauses-limit", 8); + m_cardinality_limit = p.get_uint(":pb2bv-cardinality-limit", UINT_MAX); + } + + void collect_param_descrs(param_descrs & r) { + insert_max_memory(r); + r.insert(":pb2bv-all-clauses-limit", CPK_UINT, "(default: 8) maximum number of literals for using equivalent CNF encoding of PB constraint."); + r.insert(":pb2bv-cardinality-limit", CPK_UINT, "(default: inf) limit for using arc-consistent cardinality constraint encoding."); + } + + void set_cancel(bool f) { + m_rw.set_cancel(f); + } + + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + TRACE("pb2bv", g->display(tout);); + SASSERT(g->is_well_sorted()); + fail_if_proof_generation("pb2bv", g); + m_produce_models = g->models_enabled(); + m_produce_unsat_cores = g->unsat_core_enabled(); + mc = 0; pc = 0; core = 0; result.reset(); + tactic_report report("pb2bv", *g); + m_bm.reset(); m_rw.reset(); m_new_deps.reset(); + + if (g->inconsistent()) { + result.push_back(g.get()); + return; + } + + m_bm(*g); + + TRACE("pb2bv", m_bm.display(tout);); + + try { + quick_pb_check(g); + } + catch (non_pb) { + throw tactic_exception("goal is in a fragment unsupported by pb2bv"); + } + + unsigned size = g->size(); + expr_ref_vector new_exprs(m); + expr_dependency_ref_vector new_deps(m); + + try { + expr_ref new_curr(m); + proof_ref new_pr(m); + expr_ref new_f(m); + for (unsigned idx = 0; idx < size; idx++) { + expr * curr = g->form(idx); + expr * atom; + bool pos; + if (is_constraint(curr, atom, pos)) { + convert(to_app(atom), new_f, pos, true); + TRACE("pb2bv_convert", tout << "pos: " << pos << "\n" << mk_ismt2_pp(atom, m) << "\n--->\n" << mk_ismt2_pp(new_f, m) << "\n";); + } + else { + m_rw(curr, new_f); + } + if (m_produce_unsat_cores) { + new_deps.push_back(m.mk_join(m_used_dependencies, g->dep(idx))); + m_used_dependencies.reset(); + } + new_exprs.push_back(new_f); + } + } + catch (non_pb) { + throw tactic_exception("goal is in a fragment unsupported by pb2bv"); + } + + for (unsigned idx = 0; idx < size; idx++) + g->update(idx, new_exprs[idx].get(), 0, (m_produce_unsat_cores) ? new_deps[idx].get() : g->dep(idx)); + + if (m_produce_models) { + filter_model_converter * mc1 = alloc(filter_model_converter, m); + obj_map::iterator it = m_const2bit.begin(); + obj_map::iterator end = m_const2bit.end(); + for (; it != end; ++it) + mc1->insert(to_app(it->m_value)->get_decl()); + // store temp int constants in the filter + unsigned num_temps = m_temporary_ints.size(); + for (unsigned i = 0; i < num_temps; i++) + mc1->insert(to_app(m_temporary_ints.get(i))->get_decl()); + pb2bv_model_converter * mc2 = alloc(pb2bv_model_converter, m, m_const2bit, m_bm); + mc = concat(mc1, mc2); + } + + g->inc_depth(); + result.push_back(g.get()); + TRACE("pb2bv", g->display(tout);); + SASSERT(g->is_well_sorted()); + } + }; + + imp * m_imp; + params_ref m_params; +public: + pb2bv_tactic(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(pb2bv_tactic, m, m_params); + } + + virtual ~pb2bv_tactic() { + dealloc(m_imp); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + m_imp->collect_param_descrs(r); + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + (*m_imp)(in, result, mc, pc, core); + } + + virtual void cleanup() { + ast_manager & m = m_imp->m; + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + d = m_imp; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + } + +protected: + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + +tactic * mk_pb2bv_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(pb2bv_tactic, m, p)); +} + +struct is_pb_probe : public probe { + virtual result operator()(goal const & g) { + try { + ast_manager & m = g.m(); + bound_manager bm(m); + bm(g); + arith_util a_util(m); + expr_fast_mark1 visited; + pb2bv_tactic::only_01_visitor proc(a_util, bm); + + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + expr * f = g.form(i); + for_each_expr_core(proc, visited, f); + } + + return true; + } + catch (pb2bv_tactic::non_pb) { + return false; + } + } +}; + + +probe * mk_is_pb_probe() { + return alloc(is_pb_probe); +} diff --git a/lib/pb2bv_tactic.h b/lib/pb2bv_tactic.h new file mode 100644 index 000000000..9b91d6810 --- /dev/null +++ b/lib/pb2bv_tactic.h @@ -0,0 +1,30 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + pb2bv_tactic.cpp + +Abstract: + + Tactic for converting Pseudo-Boolean constraints to BV + +Author: + + Christoph (cwinter) 2012-02-15 + +Notes: + +--*/ +#ifndef _PB2BV_TACTIC_ +#define _PB2BV_TACTIC_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_pb2bv_tactic(ast_manager & m, params_ref const & p = params_ref()); + +probe * mk_is_pb_probe(); + +#endif diff --git a/lib/pdecl.cpp b/lib/pdecl.cpp new file mode 100644 index 000000000..9d06cb115 --- /dev/null +++ b/lib/pdecl.cpp @@ -0,0 +1,958 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + pdecl.cpp + +Abstract: + + Parametric declarations for SMT-LIB 2.0 + inductive data-types. + +Author: + + Leonardo de Moura (leonardo) 2011-03-02. + +Revision History: + +--*/ +#include"pdecl.h" +#include"datatype_decl_plugin.h" +using namespace format_ns; + +class psort_inst_cache { + unsigned m_num_params; + sort * m_const; + obj_map m_map; // if m_num_params == 1 value is a sort, otherwise it is a reference to another inst_cache +public: + psort_inst_cache(unsigned num_params):m_num_params(num_params), m_const(0) { + } + + ~psort_inst_cache() { SASSERT(m_map.empty()); SASSERT(m_const == 0); } + + void finalize(pdecl_manager & m) { + if (m_num_params == 0) { + SASSERT(m_map.empty()); + if (m_const) + m.m().dec_ref(m_const); + m_const = 0; + } + else { + SASSERT(m_const == 0); + obj_map::iterator it = m_map.begin(); + obj_map::iterator end = m_map.end(); + for (; it != end; ++it) { + m.m().dec_ref((*it).m_key); + if (m_num_params == 1) { + m.m().dec_ref(static_cast((*it).m_value)); + } + else { + psort_inst_cache * child = static_cast((*it).m_value); + child->finalize(m); + child->~psort_inst_cache(); + m.a().deallocate(sizeof(psort_inst_cache), child); + } + } + m_map.reset(); + } + } + + void insert(pdecl_manager & m, sort * const * s, sort * r) { + if (m_num_params == 0) { + SASSERT(m_const == 0); + m.m().inc_ref(r); + m_const = r; + return; + } + psort_inst_cache * curr = this; + while (true) { + if (curr->m_num_params == 1) { + SASSERT(!curr->m_map.contains(*s)); + curr->m_map.insert(*s, r); + m.m().inc_ref(*s); + m.m().inc_ref(r); + return; + } + void * next = 0; + if (!curr->m_map.find(*s, next)) { + next = new (m.a().allocate(sizeof(psort_inst_cache))) psort_inst_cache(curr->m_num_params-1); + curr->m_map.insert(*s, next); + m.m().inc_ref(*s); + } + SASSERT(next != 0); + SASSERT(curr->m_num_params == static_cast(next)->m_num_params + 1); + s++; + curr = static_cast(next); + } + } + + sort * find(sort * const * s) const { + if (m_num_params == 0) + return m_const; + psort_inst_cache const * curr = this; + while (true) { + if (curr->m_num_params == 1) { + void * r = 0; + curr->m_map.find(*s, r); + return static_cast(r); + } + else { + void * next = 0; + curr->m_map.find(*s, next); + if (next == 0) + return 0; + s++; + curr = static_cast(next); + } + } + } + + bool empty() const { return m_num_params == 0 ? m_const == 0 : m_map.empty(); } +}; + +void psort::cache(pdecl_manager & m, sort * const * s, sort * r) { + if (!m_inst_cache) + m_inst_cache = m.mk_inst_cache(m_num_params); + m_inst_cache->insert(m, s, r); +} + +sort * psort::find(sort * const * s) const { + if (!m_inst_cache) + return 0; + return m_inst_cache->find(s); +} + +void psort::finalize(pdecl_manager & m) { + m.del_inst_cache(m_inst_cache); + m_inst_cache = 0; +} + +/** + \brief wrapper for sorts. +*/ +class psort_sort : public psort { + friend class pdecl_manager; + sort * m_sort; + psort_sort(unsigned id, pdecl_manager & m, sort * s):psort(id, 0), m_sort(s) { m.m().inc_ref(m_sort); } + virtual void finalize(pdecl_manager & m) { + m.m().dec_ref(m_sort); + psort::finalize(m); + } + virtual bool check_num_params(pdecl * other) const { return true; } + virtual size_t obj_size() const { return sizeof(psort_sort); } + sort * get_sort() const { return m_sort; } + virtual sort * instantiate(pdecl_manager & m, sort * const * s) { return m_sort; } +public: + virtual ~psort_sort() {} + virtual bool is_sort_wrapper() const { return true; } + virtual char const * hcons_kind() const { return "psort_sort"; } + virtual unsigned hcons_hash() const { return m_sort->get_id(); } + virtual bool hcons_eq(psort const * other) const { + if (other->hcons_kind() != hcons_kind()) + return false; + return m_sort == static_cast(other)->m_sort; + } + virtual void display(std::ostream & out) const { + out << m_sort->get_name(); + } +}; + +class psort_var : public psort { + friend class pdecl_manager; + unsigned m_idx; + psort_var(unsigned id, unsigned num_params, unsigned idx):psort(id, num_params), m_idx(idx) { SASSERT(idx < num_params); } + virtual sort * instantiate(pdecl_manager & m, sort * const * s) { return s[m_idx]; } + virtual size_t obj_size() const { return sizeof(psort_var); } +public: + virtual ~psort_var() {} + virtual char const * hcons_kind() const { return "psort_var"; } + virtual unsigned hcons_hash() const { return hash_u_u(m_num_params, m_idx); } + virtual bool hcons_eq(psort const * other) const { + if (other->hcons_kind() != hcons_kind()) + return false; + return get_num_params() == other->get_num_params() && m_idx == static_cast(other)->m_idx; + } + virtual void display(std::ostream & out) const { + out << "s_" << m_idx; + } +}; + +class psort_app : public psort { + friend class pdecl_manager; + psort_decl * m_decl; + ptr_vector m_args; + + psort_app(unsigned id, unsigned num_params, pdecl_manager & m, psort_decl * d, unsigned num_args, psort * const * args): + psort(id, num_params), + m_decl(d), + m_args(num_args, args) { + m.inc_ref(d); + m.inc_ref(num_args, args); + SASSERT(num_args == m_decl->get_num_params() || m_decl->has_var_params()); + DEBUG_CODE(for (unsigned i = 0; i < num_params; i++) args[i]->check_num_params(this);); + } + + virtual void finalize(pdecl_manager & m) { + m.lazy_dec_ref(m_decl); + m.lazy_dec_ref(m_args.size(), m_args.c_ptr()); + psort::finalize(m); + } + + virtual size_t obj_size() const { return sizeof(psort_app); } + + struct khasher { + unsigned operator()(psort_app const * d) const { return d->m_decl->hash(); } + }; + + struct chasher { + unsigned operator()(psort_app const * d, unsigned idx) const { return d->m_args[idx]->hash(); } + }; + + virtual sort * instantiate(pdecl_manager & m, sort * const * s) { + sort * r = find(s); + if (r) + return r; + sort_ref_buffer args_i(m.m()); + unsigned sz = m_args.size(); + for (unsigned i = 0; i < sz; i++) { + sort * a = m_args[i]->instantiate(m, s); + args_i.push_back(a); + } + r = m_decl->instantiate(m, args_i.size(), args_i.c_ptr()); + cache(m, s, r); + return r; + } + +public: + virtual ~psort_app() {} + virtual char const * hcons_kind() const { return "psort_app"; } + virtual unsigned hcons_hash() const { + return get_composite_hash(const_cast(this), m_args.size()); + } + virtual bool hcons_eq(psort const * other) const { + if (other->hcons_kind() != hcons_kind()) + return false; + if (get_num_params() != other->get_num_params()) + return false; + psort_app const * _other = static_cast(other); + if (m_decl != _other->m_decl) + return false; + SASSERT(m_args.size() == _other->m_args.size()); + unsigned sz = m_args.size(); + for (unsigned i = 0; i < sz; i++) { + if (m_args[i] != _other->m_args[i]) + return false; + } + return true; + } + virtual void display(std::ostream & out) const { + if (m_args.empty()) { + out << m_decl->get_name(); + } + else { + out << "(" << m_decl->get_name(); + unsigned sz = m_args.size(); + for (unsigned i = 0; i < sz; i++) { + out << " "; + m_args[i]->display(out); + } + out << ")"; + } + } +}; + +psort_decl::psort_decl(unsigned id, unsigned num_params, pdecl_manager & m, symbol const & n): + pdecl(id, num_params), + m_name(n), + m_inst_cache(0) { +} + +void psort_decl::finalize(pdecl_manager & m) { + m.del_inst_cache(m_inst_cache); + m_inst_cache = 0; +} + +void psort_decl::cache(pdecl_manager & m, sort * const * s, sort * r) { + if (!m_inst_cache) + m_inst_cache = m.mk_inst_cache(m_num_params); + m_inst_cache->insert(m, s, r); +} + +sort * psort_decl::find(sort * const * s) { + if (!m_inst_cache) + return 0; + return m_inst_cache->find(s); +} + +psort_user_decl::psort_user_decl(unsigned id, unsigned num_params, pdecl_manager & m, symbol const & n, psort * p): + psort_decl(id, num_params, m, n), + m_def(p) { + m.inc_ref(p); + SASSERT(p == 0 || num_params == p->get_num_params()); +} + +void psort_user_decl::finalize(pdecl_manager & m) { + m.dec_ref(m_def); + m_def = 0; + psort_decl::finalize(m); +} + +sort * psort_user_decl::instantiate(pdecl_manager & m, unsigned n, sort * const * s) { + SASSERT(n == m_num_params); + sort * r = find(s); + if (r) + return r; + if (m_def == 0) { + user_sort_plugin * plugin = m.m().get_user_sort_plugin(); + buffer ps; + for (unsigned i = 0; i < n; i++) + ps.push_back(parameter(s[i])); + decl_kind kind = plugin->register_name(m_name); + r = plugin->mk_sort(kind, ps.size(), ps.c_ptr()); + } + else { + r = m_def->instantiate(m, s); + } + cache(m, s, r); + m.save_info(r, this, n, s); + return r; +} + +void display_sort_args(std::ostream & out, unsigned num_params) { + if (num_params > 0) + out << " ("; + for (unsigned i = 0; i < num_params; i++) { + if (i > 0) out << " "; + out << "s_" << i; + } + if (num_params > 0) + out << ") "; +} + +void psort_user_decl::display(std::ostream & out) const { + out << "(declare-sort " << m_name; + display_sort_args(out, m_num_params); + if (m_def) + m_def->display(out); + out << ")"; +} + +psort_builtin_decl::psort_builtin_decl(unsigned id, pdecl_manager & m, symbol const & n, family_id fid, decl_kind k): + psort_decl(id, PSORT_DECL_VAR_PARAMS, m, n), + m_fid(fid), + m_kind(k) { +} + +sort * psort_builtin_decl::instantiate(pdecl_manager & m, unsigned n, sort * const * s) { + if (n == 0) { + sort * r = m.m().mk_sort(m_fid, m_kind); + m.save_info(r, this, 0, s); + return r; + } + else { + buffer params; + for (unsigned i = 0; i < n; i++) + params.push_back(parameter(s[i])); + sort * r = m.m().mk_sort(m_fid, m_kind, n, params.c_ptr()); + m.save_info(r, this, n, s); + return r; + } +} + +sort * psort_builtin_decl::instantiate(pdecl_manager & m, unsigned n, unsigned const * s) { + if (n == 0) { + sort * r = m.m().mk_sort(m_fid, m_kind); + m.save_info(r, this, 0, s); + return r; + } + else { + buffer params; + for (unsigned i = 0; i < n; i++) + params.push_back(parameter(s[i])); + sort * r = m.m().mk_sort(m_fid, m_kind, n, params.c_ptr()); + m.save_info(r, this, n, s); + return r; + } +} + +void psort_builtin_decl::display(std::ostream & out) const { + out << "(declare-builtin-sort " << m_name << ")"; +} + +void ptype::display(std::ostream & out, pdatatype_decl const * const * dts) const { + switch (kind()) { + case PTR_PSORT: get_psort()->display(out); break; + case PTR_REC_REF: out << dts[get_idx()]->get_name(); break; + case PTR_MISSING_REF: out << get_missing_ref(); break; + } +} + +paccessor_decl::paccessor_decl(unsigned id, unsigned num_params, pdecl_manager & m, symbol const & n, ptype const & r): + pdecl(id, num_params), + m_name(n), + m_type(r) { + if (m_type.kind() == PTR_PSORT) + m.inc_ref(r.get_psort()); +} + +void paccessor_decl::finalize(pdecl_manager & m) { + if (m_type.kind() == PTR_PSORT) + m.lazy_dec_ref(m_type.get_psort()); +} + +bool paccessor_decl::has_missing_refs(symbol & missing) const { + if (m_type.kind() == PTR_MISSING_REF) { + missing = m_type.get_missing_ref(); + return true; + } + return false; +} + +bool paccessor_decl::fix_missing_refs(dictionary const & symbol2idx, symbol & missing) { + TRACE("fix_missing_refs", tout << "m_type.kind(): " << m_type.kind() << "\n"; + if (m_type.kind() == PTR_MISSING_REF) tout << m_type.get_missing_ref() << "\n";); + if (m_type.kind() != PTR_MISSING_REF) + return true; + int idx; + if (symbol2idx.find(m_type.get_missing_ref(), idx)) { + m_type = ptype(idx); + SASSERT(m_type.kind() == PTR_REC_REF); + return true; + } + missing = m_type.get_missing_ref(); + return false; +} + +accessor_decl * paccessor_decl::instantiate_decl(pdecl_manager & m, sort * const * s) { + switch (m_type.kind()) { + case PTR_REC_REF: return mk_accessor_decl(m_name, type_ref(m_type.get_idx())); + case PTR_PSORT: return mk_accessor_decl(m_name, type_ref(m_type.get_psort()->instantiate(m, s))); + default: + // missing refs must have been eliminated. + UNREACHABLE(); + return 0; + } +} + +void paccessor_decl::display(std::ostream & out, pdatatype_decl const * const * dts) const { + out << "(" << m_name << " "; + m_type.display(out, dts); + out << ")"; +} + +pconstructor_decl::pconstructor_decl(unsigned id, unsigned num_params, pdecl_manager & m, + symbol const & n, symbol const & r, unsigned num_accessors, paccessor_decl * const * accessors): + pdecl(id, num_params), + m_name(n), + m_recogniser_name(r), + m_accessors(num_accessors, accessors) { + m.inc_ref(num_accessors, accessors); + TRACE("pconstructor_decl", tout << "name: " << n << ", recognizer: " << r << "\n";); +} + +void pconstructor_decl::finalize(pdecl_manager & m) { + m.lazy_dec_ref(m_accessors.size(), m_accessors.c_ptr()); +} + +bool pconstructor_decl::has_missing_refs(symbol & missing) const { + ptr_vector::const_iterator it = m_accessors.begin(); + ptr_vector::const_iterator end = m_accessors.end(); + for (; it != end; ++it) { + if ((*it)->has_missing_refs(missing)) + return true; + } + return false; +} + +bool pconstructor_decl::fix_missing_refs(dictionary const & symbol2idx, symbol & missing) { + ptr_vector::iterator it = m_accessors.begin(); + ptr_vector::iterator end = m_accessors.end(); + for (; it != end; ++it) { + if (!(*it)->fix_missing_refs(symbol2idx, missing)) + return false; + } + return true; +} + +constructor_decl * pconstructor_decl::instantiate_decl(pdecl_manager & m, sort * const * s) { + ptr_buffer as; + ptr_vector::iterator it = m_accessors.begin(); + ptr_vector::iterator end = m_accessors.end(); + for (; it != end; ++it) + as.push_back((*it)->instantiate_decl(m, s)); + return mk_constructor_decl(m_name, m_recogniser_name, as.size(), as.c_ptr()); +} + +void pconstructor_decl::display(std::ostream & out, pdatatype_decl const * const * dts) const { + out << "(" << m_name; + ptr_vector::const_iterator it = m_accessors.begin(); + ptr_vector::const_iterator end = m_accessors.end(); + for (; it != end; ++it) { + out << " "; + (*it)->display(out, dts); + } + out << ")"; +} + +pdatatype_decl::pdatatype_decl(unsigned id, unsigned num_params, pdecl_manager & m, + symbol const & n, unsigned num_constructors, pconstructor_decl * const * constructors): + psort_decl(id, num_params, m, n), + m_constructors(num_constructors, constructors), + m_parent(0) { + m.inc_ref(num_constructors, constructors); +} + +void pdatatype_decl::finalize(pdecl_manager & m) { + m.lazy_dec_ref(m_constructors.size(), m_constructors.c_ptr()); + psort_decl::finalize(m); +} + +bool pdatatype_decl::has_missing_refs(symbol & missing) const { + ptr_vector::const_iterator it = m_constructors.begin(); + ptr_vector::const_iterator end = m_constructors.end(); + for (; it != end; ++it) { + if ((*it)->has_missing_refs(missing)) + return true; + } + return false; +} + +bool pdatatype_decl::fix_missing_refs(dictionary const & symbol2idx, symbol & missing) { + ptr_vector::iterator it = m_constructors.begin(); + ptr_vector::iterator end = m_constructors.end(); + for (; it != end; ++it) { + if (!(*it)->fix_missing_refs(symbol2idx, missing)) + return false; + } + return true; +} + +datatype_decl * pdatatype_decl::instantiate_decl(pdecl_manager & m, sort * const * s) { + ptr_buffer cs; + ptr_vector::iterator it = m_constructors.begin(); + ptr_vector::iterator end = m_constructors.end(); + for (; it != end; ++it) { + cs.push_back((*it)->instantiate_decl(m, s)); + } + return mk_datatype_decl(m_name, cs.size(), cs.c_ptr()); +} + +struct datatype_decl_buffer { + ptr_buffer m_buffer; + ~datatype_decl_buffer() { del_datatype_decls(m_buffer.size(), m_buffer.c_ptr()); } +}; + +sort * pdatatype_decl::instantiate(pdecl_manager & m, unsigned n, sort * const * s) { + SASSERT(m_num_params == n); + sort * r = find(s); + if (r) + return r; + if (m_parent != 0) { + if (m_parent->instantiate(m, s)) { + r = find(s); + SASSERT(r); + return r; + } + } + else { + datatype_decl_buffer dts; + dts.m_buffer.push_back(instantiate_decl(m, s)); + datatype_decl * d_ptr = dts.m_buffer[0]; + sort_ref_vector sorts(m.m()); + bool is_ok = m.get_dt_plugin()->mk_datatypes(1, &d_ptr, sorts); + TRACE("pdatatype_decl", tout << "instantiating " << m_name << " is_ok: " << is_ok << "\n";); + if (is_ok) { + r = sorts.get(0); + cache(m, s, r); + m.save_info(r, this, n, s); + m.notify_new_dt(r); + return r; + } + } + return 0; +} + +void pdatatype_decl::display(std::ostream & out) const { + out << "(declare-datatype " << m_name; + display_sort_args(out, m_num_params); + ptr_vector::const_iterator it = m_constructors.begin(); + ptr_vector::const_iterator end = m_constructors.end(); + for (bool first = true; it != end; ++it) { + if (!first) + out << " "; + if (m_parent) { + (*it)->display(out, m_parent->children()); + } + else { + pdatatype_decl const * dts[1] = { this }; + (*it)->display(out, dts); + } + first = false; + } + out << ")"; +} + +pdatatypes_decl::pdatatypes_decl(unsigned id, unsigned num_params, pdecl_manager & m, + unsigned num_datatypes, pdatatype_decl * const * dts): + pdecl(id, num_params), + m_datatypes(num_datatypes, dts) { + m.inc_ref(num_datatypes, dts); + + ptr_vector::iterator it = m_datatypes.begin(); + ptr_vector::iterator end = m_datatypes.end(); + for (; it != end; ++it) { + SASSERT((*it)->m_parent == 0); + (*it)->m_parent = this; + } +} + +void pdatatypes_decl::finalize(pdecl_manager & m) { + m.lazy_dec_ref(m_datatypes.size(), m_datatypes.c_ptr()); +} + +bool pdatatypes_decl::fix_missing_refs(symbol & missing) { + TRACE("fix_missing_refs", tout << "pdatatypes_decl::fix_missing_refs\n";); + dictionary symbol2idx; + ptr_vector::iterator it = m_datatypes.begin(); + ptr_vector::iterator end = m_datatypes.end(); + for (unsigned idx = 0; it != end; ++it, ++idx) + symbol2idx.insert((*it)->get_name(), idx); + + it = m_datatypes.begin(); + for (unsigned idx = 0; it != end; ++it, ++idx) { + if (!(*it)->fix_missing_refs(symbol2idx, missing)) + return false; + } + return true; +} + +bool pdatatypes_decl::instantiate(pdecl_manager & m, sort * const * s) { + datatype_decl_buffer dts; + ptr_vector::iterator it = m_datatypes.begin(); + ptr_vector::iterator end = m_datatypes.end(); + for (; it != end; ++it) { + dts.m_buffer.push_back((*it)->instantiate_decl(m, s)); + } + sort_ref_vector sorts(m.m()); + bool is_ok = m.get_dt_plugin()->mk_datatypes(dts.m_buffer.size(), dts.m_buffer.c_ptr(), sorts); + if (!is_ok) + return false; + it = m_datatypes.begin(); + for (unsigned i = 0; it != end; ++it, ++i) { + sort * new_dt = sorts.get(i); + (*it)->cache(m, s, new_dt); + m.save_info(new_dt, *it, m_num_params, s); + m.notify_new_dt(new_dt); + } + return true; +} + +struct pdecl_manager::sort_info { + psort_decl * m_decl; + + sort_info(pdecl_manager & m, psort_decl * d): + m_decl(d) { + m.inc_ref(d); + } + virtual ~sort_info() {} + 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; + virtual format * pp(pdecl_manager const & m) const = 0; +}; + +struct pdecl_manager::app_sort_info : public pdecl_manager::sort_info { + ptr_vector m_args; + + app_sort_info(pdecl_manager & m, psort_decl * d, unsigned n, sort * const * s): + sort_info(m, d), + m_args(n, s) { + m.m().inc_array_ref(n, s); + } + + virtual ~app_sort_info() {} + + virtual unsigned obj_size() const { return sizeof(app_sort_info); } + + virtual void finalize(pdecl_manager & m) { + sort_info::finalize(m); + m.m().dec_array_ref(m_args.size(), m_args.c_ptr()); + } + + virtual void display(std::ostream & out, pdecl_manager const & m) const { + if (m_args.empty()) { + out << m_decl->get_name(); + } + else { + out << "(" << m_decl->get_name(); + for (unsigned i = 0; i < m_args.size(); i++) { + out << " "; + m.display(out, m_args[i]); + } + out << ")"; + } + } + + virtual format * pp(pdecl_manager const & m) const { + if (m_args.empty()) { + return mk_string(m.m(), m_decl->get_name().str().c_str()); + } + else { + ptr_buffer b; + for (unsigned i = 0; i < m_args.size(); i++) + b.push_back(m.pp(m_args[i])); + return mk_seq1(m.m(), b.begin(), b.end(), f2f(), m_decl->get_name().str().c_str()); + } + } +}; + +struct pdecl_manager::indexed_sort_info : public pdecl_manager::sort_info { + svector m_indices; + + indexed_sort_info(pdecl_manager & m, psort_decl * d, unsigned n, unsigned const * s): + sort_info(m, d), + m_indices(n, s) { + } + + virtual ~indexed_sort_info() {} + + virtual unsigned obj_size() const { return sizeof(indexed_sort_info); } + + virtual void display(std::ostream & out, pdecl_manager const & m) const { + if (m_indices.empty()) { + out << m_decl->get_name(); + } + else { + out << "(_ " << m_decl->get_name(); + for (unsigned i = 0; i < m_indices.size(); i++) { + out << " " << m_indices[i]; + } + out << ")"; + } + } + + virtual format * pp(pdecl_manager const & m) const { + if (m_indices.empty()) { + return mk_string(m.m(), m_decl->get_name().str().c_str()); + } + else { + ptr_buffer b; + b.push_back(mk_string(m.m(), m_decl->get_name().str().c_str())); + for (unsigned i = 0; i < m_indices.size(); i++) + b.push_back(mk_unsigned(m.m(), m_indices[i])); + return mk_seq1(m.m(), b.begin(), b.end(), f2f(), "_"); + } + } +}; + +void pdecl_manager::init_list() { + SASSERT(m_list == 0); + psort * v = mk_psort_var(1, 0); + ptype T(v); + ptype ListT(0); + paccessor_decl * as[2] = { mk_paccessor_decl(1, symbol("head"), T), + mk_paccessor_decl(1, symbol("tail"), ListT) }; + pconstructor_decl * cs[2] = { mk_pconstructor_decl(1, symbol("nil"), symbol("is-nil"), 0, 0), + mk_pconstructor_decl(1, symbol("insert"), symbol("is-insert"), 2, as) }; + m_list = mk_pdatatype_decl(1, symbol("List"), 2, cs); + inc_ref(m_list); +} + +pdecl_manager::pdecl_manager(ast_manager & m): + m_manager(m), + m_allocator(m.get_allocator()), + m_new_dt_eh(0) { + m_list = 0; + m_datatype_fid = m.get_family_id("datatype"); +} + +pdecl_manager::~pdecl_manager() { + dec_ref(m_list); + reset_sort_info(); + SASSERT(m_sort2psort.empty()); + SASSERT(m_table.empty()); +} + +psort * pdecl_manager::mk_psort_cnst(sort * s) { + psort * r = 0; + if (m_sort2psort.find(s, r)) + return r; + r = new (a().allocate(sizeof(psort_sort))) psort_sort(m_id_gen.mk(), *this, s); + m_sort2psort.insert(s, r); + return r; +} + +psort * pdecl_manager::register_psort(psort * n) { + psort * r = m_table.insert_if_not_there(n); + if (r != n) { + del_decl_core(n); + return r; + } + return n; +} + +psort * pdecl_manager::mk_psort_var(unsigned num_params, unsigned vidx) { + psort_var * n = new (a().allocate(sizeof(psort_var))) psort_var(m_id_gen.mk(), num_params, vidx); + return register_psort(n); +} + +paccessor_decl * pdecl_manager::mk_paccessor_decl(unsigned num_params, symbol const & s, ptype const & p) { + return new (a().allocate(sizeof(paccessor_decl))) paccessor_decl(m_id_gen.mk(), num_params, *this, s, p); +} + +pconstructor_decl * pdecl_manager::mk_pconstructor_decl(unsigned num_params, + symbol const & s, symbol const & r, unsigned num, paccessor_decl * const * as) { + return new (a().allocate(sizeof(pconstructor_decl))) pconstructor_decl(m_id_gen.mk(), num_params, *this, + s, r, num, as); +} + +pdatatype_decl * pdecl_manager::mk_pdatatype_decl(unsigned num_params, symbol const & s, unsigned num, pconstructor_decl * const * cs) { + return new (a().allocate(sizeof(pdatatype_decl))) pdatatype_decl(m_id_gen.mk(), num_params, *this, + s, num, cs); +} + +pdatatypes_decl * pdecl_manager::mk_pdatatypes_decl(unsigned num_params, unsigned num, pdatatype_decl * const * dts) { + return new (a().allocate(sizeof(pdatatypes_decl))) pdatatypes_decl(m_id_gen.mk(), num_params, *this, + num, dts); +} + +psort * pdecl_manager::mk_psort_app(unsigned num_params, psort_decl * d, unsigned num_args, psort * const * args) { + psort * n = new (a().allocate(sizeof(psort_app))) psort_app(m_id_gen.mk(), num_params, *this, d, num_args, args); + return register_psort(n); +} + +psort * pdecl_manager::mk_psort_app(psort_decl * d) { + SASSERT(d->get_num_params() == 0 || d->get_num_params() == PSORT_DECL_VAR_PARAMS); + sort * s = d->instantiate(*this, 0, static_cast(0)); + if (s == 0) + return 0; + return mk_psort_cnst(s); +} + +psort_decl * pdecl_manager::mk_psort_user_decl(unsigned num_params, symbol const & n, psort * def) { + return new (a().allocate(sizeof(psort_user_decl))) psort_user_decl(m_id_gen.mk(), num_params, *this, n, def); +} + +psort_decl * pdecl_manager::mk_psort_builtin_decl(symbol const & n, family_id fid, decl_kind k) { + return new (a().allocate(sizeof(psort_builtin_decl))) psort_builtin_decl(m_id_gen.mk(), *this, n, fid, k); +} + +sort * pdecl_manager::instantiate(psort * p, unsigned num, sort * const * args) { + // ignoring stack overflows... sorts are not deeply nested + return p->instantiate(*this, args); +} + +void pdecl_manager::del_decl_core(pdecl * p) { + TRACE("pdecl_manager", + tout << "del_decl_core:\n"; + if (p->is_psort()) static_cast(p)->display(tout); + else static_cast(p)->display(tout); + tout << "\n";); + size_t sz = p->obj_size(); + m_id_gen.recycle(p->get_id()); + p->finalize(*this); + p->~pdecl(); + m_allocator.deallocate(sz, p); +} + +void pdecl_manager::del_decl(pdecl * p) { + if (p->is_psort()) { + psort * _p = static_cast(p); + if (_p->is_sort_wrapper()) + m_sort2psort.erase(static_cast(_p)->get_sort()); + else + m_table.erase(_p); + } + del_decl_core(p); +} + +void pdecl_manager::del_decls() { + while (!m_to_delete.empty()) { + pdecl * p = m_to_delete.back(); + m_to_delete.pop_back(); + del_decl(p); + } +} + +psort_inst_cache * pdecl_manager::mk_inst_cache(unsigned num_params) { + return new (a().allocate(sizeof(psort_inst_cache))) psort_inst_cache(num_params); +} + +void pdecl_manager::del_inst_cache(psort_inst_cache * c) { + if (c) { + c->finalize(*this); + c->~psort_inst_cache(); + a().deallocate(sizeof(psort_inst_cache), c); + } +} + +datatype_decl_plugin * pdecl_manager::get_dt_plugin() const { + return static_cast(m().get_plugin(m_datatype_fid)); +} + +void pdecl_manager::save_info(sort * s, psort_decl * d, unsigned num_args, sort * const * args) { + if (m_sort2info.contains(s)) + return; + sort_info * info = new (a().allocate(sizeof(app_sort_info))) app_sort_info(*this, d, num_args, args); + m().inc_ref(s); + m_sort2info.insert(s, info); +} + +void pdecl_manager::save_info(sort * s, psort_decl * d, unsigned num_indices, unsigned const * indices) { + if (m_sort2info.contains(s)) + return; + sort_info * info = new (a().allocate(sizeof(indexed_sort_info))) indexed_sort_info(*this, d, num_indices, indices); + m().inc_ref(s); + m_sort2info.insert(s, info); +} + +void pdecl_manager::reset_sort_info() { + obj_map::iterator it = m_sort2info.begin(); + obj_map::iterator end = m_sort2info.end(); + for (; it != end; ++it) { + sort * s = (*it).m_key; + sort_info * info = (*it).m_value; + m().dec_ref(s); + unsigned sz = info->obj_size(); + info->finalize(*this); + info->~sort_info(); + m_allocator.deallocate(sz, info); + } + m_sort2info.reset(); +} + +void pdecl_manager::display(std::ostream & out, sort * s) const { + sort_info * info = 0; + if (m_sort2info.find(s, info)) { + info->display(out, *this); + return; + } + out << s->get_name(); +} + +format * pdecl_manager::pp(sort * s) const { + sort_info * info = 0; + if (m_sort2info.find(s, info)) { + return info->pp(*this); + } + unsigned num_params = s->get_num_parameters(); + if (s->get_family_id() != null_family_id && num_params > 0) { + // Small hack to display FP and BitVec sorts that were not explicitly referenced by the user. + unsigned i = 0; + for (i = 0; i < num_params; i++) { + if (!s->get_parameter(i).is_int()) + break; + } + if (i == num_params) { + // all parameters are integer + ptr_buffer b; + b.push_back(mk_string(m(), s->get_name().str().c_str())); + for (unsigned i = 0; i < num_params; i++) + b.push_back(mk_unsigned(m(), s->get_parameter(i).get_int())); + return mk_seq1(m(), b.begin(), b.end(), f2f(), "_"); + } + } + return mk_string(m(), s->get_name().str().c_str()); +} diff --git a/lib/pdecl.h b/lib/pdecl.h new file mode 100644 index 000000000..36b1dcd51 --- /dev/null +++ b/lib/pdecl.h @@ -0,0 +1,329 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + pdecl.h + +Abstract: + + Parametric declarations for SMT-LIB 2.0 + inductive data-types. + +Author: + + Leonardo de Moura (leonardo) 2011-03-02. + +Revision History: + +--*/ +#ifndef _PDECL_H_ +#define _PDECL_H_ + +#include"ast.h" +#include"obj_hashtable.h" +#include"dictionary.h" +#include"format.h" + +class pdecl_manager; + +class pdecl { +protected: + friend class pdecl_manager; + unsigned m_id; + unsigned m_num_params; + unsigned m_ref_count; + void inc_ref() { m_ref_count++; } + void dec_ref() { SASSERT(m_ref_count > 0); --m_ref_count; } + virtual bool is_psort() const { return false; } + virtual size_t obj_size() const = 0; + 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() {} +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; } + unsigned get_id() const { return m_id; } + unsigned get_ref_count() const { return m_ref_count; } + unsigned hash() const { return m_id; } + virtual void display(std::ostream & out) const {} +}; + +class psort_inst_cache; + +#if defined(__APPLE__) && defined(__MACH__) +// CMW: for some unknown reason, llvm on OSX does not like the name `psort' +#define psort Z3_psort +#endif + +/** + \brief Parametric sorts. +*/ +class psort : public pdecl { +protected: + psort_inst_cache * m_inst_cache; + friend class pdecl_manager; + psort(unsigned id, unsigned num_params):pdecl(id, num_params), m_inst_cache(0) {} + virtual bool is_psort() const { return true; } + virtual void finalize(pdecl_manager & m); + virtual ~psort() {} + virtual void cache(pdecl_manager & m, sort * const * s, sort * r); + virtual sort * find(sort * const * s) const; +public: + virtual bool is_sort_wrapper() const { return false; } + virtual sort * instantiate(pdecl_manager & m, sort * const * s) { return 0; } + // we use hash-consing for psorts. + virtual char const * hcons_kind() const = 0; + virtual unsigned hcons_hash() const = 0; + virtual bool hcons_eq(psort const * other) const = 0; +}; + +// for hash consing +struct psort_hash_proc { unsigned operator()(psort * p) const { return p->hcons_hash(); } }; +struct psort_eq_proc { bool operator()(psort * p1, psort * p2) const { return p1->hcons_eq(p2); } }; +typedef ptr_hashtable psort_table; + +#define PSORT_DECL_VAR_PARAMS UINT_MAX + +class psort_decl : public pdecl { +protected: + friend class pdecl_manager; + symbol m_name; + psort_inst_cache * m_inst_cache; + void cache(pdecl_manager & m, sort * const * s, sort * r); + sort * find(sort * const * s); + psort_decl(unsigned id, unsigned num_params, pdecl_manager & m, symbol const & n); + virtual void finalize(pdecl_manager & m); + virtual ~psort_decl() {} +public: + virtual sort * instantiate(pdecl_manager & m, unsigned n, sort * const * s) = 0; + virtual sort * instantiate(pdecl_manager & m, unsigned n, unsigned const * s) { return 0; } + virtual sort * instantiate(pdecl_manager & m) { return instantiate(m, 0, static_cast(0)); } + // return true if the declaration accepts a variable number of parameters. + // Only builtin declarations can have a variable number of parameters. + bool has_var_params() const { return m_num_params == PSORT_DECL_VAR_PARAMS; } + symbol const & get_name() const { return m_name; } +}; + +class psort_user_decl : public psort_decl { +protected: + friend class pdecl_manager; + psort * m_def; + psort_user_decl(unsigned id, unsigned num_params, pdecl_manager & m, symbol const & n, psort * p); + virtual size_t obj_size() const { return sizeof(psort_user_decl); } + virtual void finalize(pdecl_manager & m); + virtual ~psort_user_decl() {} +public: + virtual sort * instantiate(pdecl_manager & m, unsigned n, sort * const * s); + virtual void display(std::ostream & out) const; +}; + +class psort_builtin_decl : public psort_decl { +protected: + friend class pdecl_manager; + family_id m_fid; + decl_kind m_kind; + psort_builtin_decl(unsigned id, pdecl_manager & m, symbol const & n, family_id fid, decl_kind k); + virtual size_t obj_size() const { return sizeof(psort_builtin_decl); } + virtual ~psort_builtin_decl() {} +public: + virtual sort * instantiate(pdecl_manager & m, unsigned n, sort * const * s); + virtual sort * instantiate(pdecl_manager & m, unsigned n, unsigned const * s); + virtual void display(std::ostream & out) const; +}; + +class datatype_decl_plugin; +class datatype_decl; +class constructor_decl; +class accessor_decl; + +class pdatatypes_decl; +class pdatatype_decl; +class pconstructor_decl; +class paccessor_decl; + +enum ptype_kind { + PTR_PSORT, // psort + PTR_REC_REF, // recursive reference + PTR_MISSING_REF // a symbol, it is useful for building parsers. +}; + +class ptype { + ptype_kind m_kind; + union { + psort * m_sort; + int m_idx; + }; + symbol m_missing_ref; +public: + ptype():m_kind(PTR_PSORT), m_sort(0) {} + ptype(int idx):m_kind(PTR_REC_REF), m_idx(idx) {} + ptype(psort * s):m_kind(PTR_PSORT), m_sort(s) {} + ptype(symbol const & s):m_kind(PTR_MISSING_REF), m_missing_ref(s) {} + ptype_kind kind() const { return m_kind; } + psort * get_psort() const { SASSERT(kind() == PTR_PSORT); return m_sort; } + int get_idx() const { SASSERT(kind() == PTR_REC_REF); return m_idx; } + symbol const & get_missing_ref() const { SASSERT(kind() == PTR_MISSING_REF); return m_missing_ref; } + void display(std::ostream & out, pdatatype_decl const * const * dts) const; +}; + +class paccessor_decl : public pdecl { + friend class pdecl_manager; + friend class pconstructor_decl; + symbol m_name; + ptype m_type; + paccessor_decl(unsigned id, unsigned num_params, pdecl_manager & m, symbol const & n, ptype const & r); + virtual void finalize(pdecl_manager & m); + virtual size_t obj_size() const { return sizeof(paccessor_decl); } + bool has_missing_refs(symbol & missing) const; + bool fix_missing_refs(dictionary const & symbol2idx, symbol & missing); + accessor_decl * instantiate_decl(pdecl_manager & m, sort * const * s); + symbol const & get_name() const { return m_name; } + ptype const & get_type() const { return m_type; } + virtual ~paccessor_decl() {} +public: + void display(std::ostream & out, pdatatype_decl const * const * dts) const; +}; + +class pconstructor_decl : public pdecl { + friend class pdecl_manager; + friend class pdatatype_decl; + symbol m_name; + symbol m_recogniser_name; + ptr_vector m_accessors; + pconstructor_decl(unsigned id, unsigned num_params, pdecl_manager & m, + symbol const & n, symbol const & r, unsigned num_accessors, paccessor_decl * const * accessors); + virtual void finalize(pdecl_manager & m); + virtual size_t obj_size() const { return sizeof(pconstructor_decl); } + bool has_missing_refs(symbol & missing) const; + bool fix_missing_refs(dictionary const & symbol2idx, symbol & missing); + symbol const & get_name() const { return m_name; } + symbol const & get_recognizer_name() const { return m_recogniser_name; } + constructor_decl * instantiate_decl(pdecl_manager & m, sort * const * s); + virtual ~pconstructor_decl() {} +public: + void display(std::ostream & out, pdatatype_decl const * const * dts) const; +}; + +class pdatatype_decl : public psort_decl { + friend class pdecl_manager; + friend class pdatatypes_decl; + ptr_vector m_constructors; + pdatatypes_decl * m_parent; + pdatatype_decl(unsigned id, unsigned num_params, pdecl_manager & m, symbol const & n, + unsigned num_constructors, pconstructor_decl * const * constructors); + virtual void finalize(pdecl_manager & m); + virtual size_t obj_size() const { return sizeof(pdatatype_decl); } + bool fix_missing_refs(dictionary const & symbol2idx, symbol & missing); + datatype_decl * instantiate_decl(pdecl_manager & m, sort * const * s); + virtual ~pdatatype_decl() {} +public: + sort * instantiate(pdecl_manager & m, unsigned n, sort * const * s); + virtual void display(std::ostream & out) const; + bool has_missing_refs(symbol & missing) const; +}; + +/** + \brief Represents a set of parametric mutually recursive inductive data-types. +*/ +class pdatatypes_decl : public pdecl { + friend class pdecl_manager; + friend class pdatatype_decl; + ptr_vector m_datatypes; + pdatatypes_decl(unsigned id, unsigned num_params, pdecl_manager & m, unsigned num_datatypes, pdatatype_decl * const * dts); + virtual void finalize(pdecl_manager & m); + virtual size_t obj_size() const { return sizeof(pdatatypes_decl); } + bool fix_missing_refs(symbol & missing); + bool instantiate(pdecl_manager & m, sort * const * s); + virtual ~pdatatypes_decl() {} +public: + pdatatype_decl const * const * children() const { return m_datatypes.c_ptr(); } +}; + +class new_datatype_eh { +public: + virtual ~new_datatype_eh() {} + virtual void operator()(sort * dt) = 0; +}; + +class pdecl_manager { + ast_manager & m_manager; + small_object_allocator & m_allocator; + id_gen m_id_gen; + obj_map m_sort2psort; + psort_table m_table; + ptr_vector m_to_delete; + pdatatype_decl * m_list; + family_id m_datatype_fid; + new_datatype_eh * m_new_dt_eh; + + struct sort_info; + struct app_sort_info; + struct indexed_sort_info; + + obj_map m_sort2info; // for pretty printing sorts + + void init_list(); + void del_decl_core(pdecl * p); + void del_decl(pdecl * p); + void del_decls(); + psort * register_psort(psort * n); + void reset_sort_info(); +public: + pdecl_manager(ast_manager & m); + ~pdecl_manager(); + ast_manager & m() const { return m_manager; } + small_object_allocator & a() const { return m_allocator; } + family_id get_datatype_fid() const { return m_datatype_fid; } + void set_new_datatype_eh(new_datatype_eh * eh) { m_new_dt_eh = eh; } + psort * mk_psort_cnst(sort * s); + psort * mk_psort_var(unsigned num_params, unsigned vidx); + psort * mk_psort_app(unsigned num_params, psort_decl * d, unsigned num_args, psort * const * args); + psort * mk_psort_app(psort_decl * d); + psort_decl * mk_psort_user_decl(unsigned num_params, symbol const & n, psort * def); + psort_decl * mk_psort_builtin_decl(symbol const & n, family_id fid, decl_kind k); + paccessor_decl * mk_paccessor_decl(unsigned num_params, symbol const & s, ptype const & p); + pconstructor_decl * mk_pconstructor_decl(unsigned num_params, symbol const & s, symbol const & r, unsigned num, paccessor_decl * const * as); + pdatatype_decl * mk_pdatatype_decl(unsigned num_params, symbol const & s, unsigned num, pconstructor_decl * const * cs); + pdatatypes_decl * mk_pdatatypes_decl(unsigned num_params, unsigned num, pdatatype_decl * const * dts); + pdatatype_decl * mk_plist_decl() { if (!m_list) init_list(); return m_list; } + bool fix_missing_refs(pdatatypes_decl * s, symbol & missing) { return s->fix_missing_refs(missing); } + sort * instantiate(psort * s, unsigned num, sort * const * args); + + void lazy_dec_ref(pdecl * p) { p->dec_ref(); if (p->get_ref_count() == 0) m_to_delete.push_back(p); } + template + void lazy_dec_ref(unsigned num, T * const * ps) { for (unsigned i = 0; i < num; i++) lazy_dec_ref(ps[i]); } + void inc_ref(pdecl * p) { if (p) { p->inc_ref(); } } + void dec_ref(pdecl * p) { if (p) { lazy_dec_ref(p); del_decls(); } } + template + void inc_ref(unsigned num, T * const * ps) { for (unsigned i = 0; i < num; i++) inc_ref(ps[i]); } + template + void dec_ref(unsigned num, T * const * ps) { lazy_dec_ref(num, ps); del_decls(); } + psort_inst_cache * mk_inst_cache(unsigned num_params); + void del_inst_cache(psort_inst_cache * c); + void notify_new_dt(sort * dt) { if (m_new_dt_eh) (*m_new_dt_eh)(dt); } + datatype_decl_plugin * get_dt_plugin() const; + + void save_info(sort * s, psort_decl * d, unsigned num_args, sort * const * args); + void save_info(sort * s, psort_decl * d, unsigned num_indices, unsigned const * indices); + void display(std::ostream & out, sort * s) const; + format_ns::format * pp(sort * s) const; +}; + + +typedef obj_ref pdecl_ref; +typedef obj_ref psort_ref; +typedef obj_ref paccessor_decl_ref; +typedef obj_ref pconstructor_decl_ref; +typedef obj_ref pdatatype_decl_ref; +typedef obj_ref pdatatypes_decl_ref; + +typedef ref_vector pdecl_ref_vector; +typedef ref_vector psort_decl_ref_vector; +typedef ref_vector psort_ref_vector; + +typedef ref_buffer paccessor_decl_ref_buffer; +typedef ref_buffer pconstructor_decl_ref_buffer; +typedef ref_buffer pdatatype_decl_ref_buffer; +typedef ref_buffer pdatatypes_decl_ref_buffer; + +#endif diff --git a/lib/pdr_context.cpp b/lib/pdr_context.cpp new file mode 100644 index 000000000..752050940 --- /dev/null +++ b/lib/pdr_context.cpp @@ -0,0 +1,1746 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + pdr_context.cpp + +Abstract: + + PDR predicate transformers and search context. + +Author: + + Nikolaj Bjorner (nbjorner) 2011-11-20. + +Revision History: + + Based on pdr_dl.cpp by + Krystof Hoder (t-khoder) 2011-9-19. + +Notes: + + +--*/ + + +#include +#include "dl_util.h" +#include "rewriter.h" +#include "rewriter_def.h" +#include "var_subst.h" +#include "util.h" +#include "pdr_prop_solver.h" +#include "pdr_context.h" +#include "pdr_generalizers.h" +#include "datatype_decl_plugin.h" +#include "for_each_expr.h" +#include "model_v2_pp.h" +#include "dl_rule_set.h" +#include "unit_subsumption_tactic.h" +#include "model_smt2_pp.h" +#include "dl_mk_rule_inliner.h" +#include "ast_smt2_pp.h" + +namespace pdr { + + + static const unsigned infty_level = UINT_MAX; + + static bool is_infty_level(unsigned lvl) { return lvl == infty_level; } + + static unsigned next_level(unsigned lvl) { return is_infty_level(lvl)?lvl:(lvl+1); } + + // ---------------- + // pred_tansformer + + pred_transformer::pred_transformer(manager& pm, func_decl* head): + pm(pm), m(pm.get_manager()), m_head(head, m), + m_sig(m), m_solver(pm, head->get_name()), + m_invariants(m), m_transition(m), m_initial_state(m), + m_reachable(pm, pm.get_params()) {} + + pred_transformer::~pred_transformer() { + qinst_map::iterator it = m_rule2qinst.begin(), end = m_rule2qinst.end(); + for (; it != end; ++it) { + dealloc(it->m_value); + } + rule2inst::iterator it2 = m_rule2inst.begin(), end2 = m_rule2inst.end(); + for (; it2 != end2; ++it2) { + dealloc(it2->m_value); + } + } + + std::ostream& pred_transformer::display(std::ostream& out) const { + if (!rules().empty()) out << "rules\n"; + for (unsigned i = 0; i < rules().size(); ++i) { + rules()[i]->display_smt2(m, out) << "\n"; + } + out << "transition\n" << mk_pp(transition(), m) << "\n"; + return out; + } + + void pred_transformer::collect_statistics(statistics& st) const { + m_solver.collect_statistics(st); + m_reachable.collect_statistics(st); + st.update("PDR num propagations", m_stats.m_num_propagations); + unsigned np = m_invariants.size(); + for (unsigned i = 0; i < m_levels.size(); ++i) { + np += m_levels[i].size(); + } + st.update("PDR num properties", np); + } + + void pred_transformer::init_sig() { + if (m_sig.empty()) { + for (unsigned i = 0; i < m_head->get_arity(); ++i) { + sort * arg_sort = m_head->get_domain(i); + std::stringstream name_stm; + name_stm << m_head->get_name() << '_' << i; + func_decl_ref stm(m); + stm = m.mk_func_decl(symbol(name_stm.str().c_str()), 0, (sort*const*)0, arg_sort); + m_sig.push_back(pm.get_o_pred(stm, 0)); + } + } + } + + void pred_transformer::ensure_level(unsigned level) { + if (is_infty_level(level)) { + return; + } + while (m_levels.size() <= level) { + m_solver.add_level(); + m_levels.push_back(expr_ref_vector(m)); + } + } + + bool pred_transformer::is_reachable(expr* state) { + return m_reachable.is_reachable(state); + } + + datalog::rule const* pred_transformer::find_rule(model_core const& model) const { + obj_map::iterator it = m_tag2rule.begin(), end = m_tag2rule.end(); + TRACE("pdr", + for (; it != end; ++it) { + expr* pred = it->m_key; + tout << mk_pp(pred, m) << ":\n"; + if (it->m_value) it->m_value->display_smt2(m, tout) << "\n"; + } + ); + + it = m_tag2rule.begin(); + if (m_tag2rule.size() == 1) { + return it->m_value; + } + + expr_ref vl(m); + for (; it != end; ++it) { + expr* pred = it->m_key; + TRACE("pdr", tout << mk_pp(pred, m) << "\n";); + if (model.eval(to_app(pred)->get_decl(), vl) && m.is_true(vl)) { + return it->m_value; + } + } + UNREACHABLE(); + return 0; + } + + void pred_transformer::find_predecessors(datalog::rule const& r, ptr_vector& preds) const { + preds.reset(); + unsigned tail_sz = r.get_uninterpreted_tail_size(); + for (unsigned ti = 0; ti < tail_sz; ti++) { + preds.push_back(r.get_tail(ti)->get_decl()); + } + } + + void pred_transformer::find_predecessors(model_core const& model, ptr_vector& preds) const { + datalog::rule const* r = find_rule(model); + if (r) { + find_predecessors(*r, preds); + } + } + + void pred_transformer::remove_predecessors(expr_ref_vector& literals) { + // remove tags + for (unsigned i = 0; i < literals.size(); ) { + expr* l = literals[i].get(); + m.is_not(l, l); + if (m_tag2rule.contains(l)) { + literals[i] = literals.back(); + literals.pop_back(); + } + else { + ++i; + } + } + } + + void pred_transformer::simplify_formulas(tactic& tac, expr_ref_vector& v) { + goal_ref g(alloc(goal, m, false, false, false)); + for (unsigned j = 0; j < v.size(); ++j) g->assert_expr(v[j].get()); + model_converter_ref mc; + proof_converter_ref pc; + expr_dependency_ref core(m); + goal_ref_buffer result; + tac(g, result, mc, pc, core); + SASSERT(result.size() == 1); + goal* r = result[0]; + v.reset(); + for (unsigned j = 0; j < r->size(); ++j) v.push_back(r->form(j)); + } + + void pred_transformer::simplify_formulas() { + tactic_ref us = mk_unit_subsumption_tactic(m); + simplify_formulas(*us, m_invariants); + for (unsigned i = 0; i < m_levels.size(); ++i) { + expr_ref_vector& v = m_levels[i]; + simplify_formulas(*us, m_levels[i]); + } + } + + expr_ref pred_transformer::get_formulas(unsigned level, bool add_axioms) { + expr_ref_vector res(m); + if (add_axioms) { + res.push_back(pm.get_background()); + res.push_back((level == 0)?initial_state():transition()); + } + res.append(m_invariants); + for (unsigned i = level; i < m_levels.size(); ++i) { + res.append(m_levels[i]); + } + return pm.mk_and(res); + } + + expr_ref pred_transformer::get_propagation_formula(decl2rel const& pts, unsigned level) { + expr_ref result(m), tmp1(m), tmp2(m); + expr_ref_vector conj(m); + if (level == 0) { + conj.push_back(initial_state()); + } + else { + conj.push_back(transition()); + } + conj.push_back(get_formulas(level, true)); + obj_map::iterator it = m_tag2rule.begin(), end = m_tag2rule.end(); + for (; level > 0 && it != end; ++it) { + expr* tag = it->m_key; + datalog::rule const* r = it->m_value; + if (!r) continue; + find_predecessors(*r, m_predicates); + for (unsigned i = 0; i < m_predicates.size(); ++i) { + func_decl* d = m_predicates[i]; + pred_transformer & pt = *pts.find(d); + tmp1 = pt.get_formulas(level-1, false); + TRACE("pdr", tout << mk_pp(tmp1, m) << "\n";); + pm.formula_n2o(tmp1, tmp2, i, false); + conj.push_back(m.mk_implies(tag, tmp2)); + } + } + return pm.mk_and(conj); + } + + bool pred_transformer::propagate_to_next_level(unsigned src_level) { + unsigned tgt_level = next_level(src_level); + ensure_level(next_level(tgt_level)); + expr_ref_vector& src = m_levels[src_level]; + + CTRACE("pdr", !src.empty(), + tout << "propagating " << src_level << " to " << tgt_level; + tout << " for relation " << head()->get_name() << "\n";); + + for (unsigned i = 0; i < src.size(); ) { + expr * curr = src[i].get(); + unsigned stored_lvl; + VERIFY(m_prop2level.find(curr, stored_lvl)); + SASSERT(stored_lvl >= src_level); + bool assumes_level; + if (stored_lvl > src_level) { + TRACE("pdr", tout << "at level: "<< stored_lvl << " " << mk_pp(curr, m) << "\n";); + src[i] = src.back(); + src.pop_back(); + } + else if (is_invariant(tgt_level, curr, false, assumes_level)) { + + add_property(curr, tgt_level); // assumes_level?tgt_level:infty_level + TRACE("pdr", tout << "is invariant: "<< tgt_level << " " << mk_pp(curr, m) << "\n";); + src[i] = src.back(); + src.pop_back(); + ++m_stats.m_num_propagations; + } + else { + TRACE("pdr", tout << "not propagated: " << mk_pp(curr, m) << "\n";); + ++i; + } + } + IF_VERBOSE(2, verbose_stream() << "propagate: "; + if (is_infty_level(src_level)) { + verbose_stream() << "infty"; + } + else verbose_stream() << src_level; + for (unsigned i = 0; i < src.size(); ++i) { + verbose_stream() << mk_pp(src[i].get(), m) << "\n"; + }); + return src.empty(); + } + + bool pred_transformer::add_property1(expr * lemma, unsigned lvl) { + if (is_infty_level(lvl)) { + if (m.is_false(lemma)) { + return false; + } + if (!m_invariants.contains(lemma)) { + TRACE("pdr", tout << "property1: " << head()->get_name() << " " << mk_pp(lemma, m) << "\n";); + m_invariants.push_back(lemma); + m_solver.add_formula(lemma); + return true; + } + else { + TRACE("pdr", tout << "already contained: " << head()->get_name() << " " << mk_pp(lemma, m) << "\n";); + return false; + } + } + ensure_level(lvl); + unsigned old_level; + if (!m_prop2level.find(lemma, old_level) || old_level < lvl) { + TRACE("pdr", tout << "property1: " << lvl << " " << head()->get_name() << " " << mk_pp(lemma, m) << "\n";); + m_levels[lvl].push_back(lemma); + m_prop2level.insert(lemma, lvl); + m_solver.add_level_formula(lemma, lvl); + return true; + } + else { + TRACE("pdr", tout << "old-level: " << old_level << " " << head()->get_name() << " " << mk_pp(lemma, m) << "\n";); + return false; + } + } + + void pred_transformer::add_child_property(pred_transformer& child, expr* lemma, unsigned lvl) { + ensure_level(lvl); + expr_ref_vector fmls(m); + mk_assumptions(child.head(), lemma, fmls); + for (unsigned i = 0; i < fmls.size(); ++i) { + TRACE("pdr", tout << "child property: " << mk_pp(fmls[i].get(), m) << "\n";); + if (is_infty_level(lvl)) { + m_solver.add_formula(fmls[i].get()); + } + else { + m_solver.add_level_formula(fmls[i].get(), lvl); + } + } + } + + void pred_transformer::add_property(expr* lemma, unsigned lvl) { + expr_ref_vector lemmas(m); + datalog::flatten_and(lemma, lemmas); + for (unsigned i = 0; i < lemmas.size(); ++i) { + expr* lemma_i = lemmas[i].get(); + if (add_property1(lemma_i, lvl)) { + IF_VERBOSE(2, verbose_stream() << lvl << " " << mk_pp(lemma_i, m) << "\n";); + for (unsigned j = 0; j < m_use.size(); ++j) { + m_use[j]->add_child_property(*this, lemma_i, next_level(lvl)); + } + } + } + } + + expr_ref pred_transformer::get_cover_delta(int level) { + expr_ref result(m.mk_true(), m), v(m), c(m); + if (level == -1) { + result = pm.mk_and(m_invariants); + } + else if ((unsigned)level < m_levels.size()) { + result = pm.mk_and(m_levels[level]); + } + // replace local constants by bound variables. + expr_substitution sub(m); + for (unsigned i = 0; i < m_sig.size(); ++i) { + c = m.mk_const(pm.o2n(sig(i), 0)); + v = m.mk_var(i, sig(i)->get_range()); + sub.insert(c, v); + } + scoped_ptr rep = mk_default_expr_replacer(m); + rep->set_substitution(&sub); + (*rep)(result); + return result; + } + + void pred_transformer::add_cover(unsigned level, expr* property) { + // replace bound variables by local constants. + expr_ref result(property, m), v(m), c(m); + expr_substitution sub(m); + for (unsigned i = 0; i < m_sig.size(); ++i) { + c = m.mk_const(pm.o2n(sig(i), 0)); + v = m.mk_var(i, sig(i)->get_range()); + sub.insert(v, c); + } + scoped_ptr rep = mk_default_expr_replacer(m); + rep->set_substitution(&sub); + (*rep)(result); + TRACE("pdr", tout << "cover:\n" << mk_pp(result, m) << "\n";); + // add the property. + add_property(result, level); + } + + lbool pred_transformer::is_reachable(model_node& n, expr_ref_vector* core) { + unsigned level = n.level(); + expr* state = n.state(); + model_ref model; + ensure_level(level); + prop_solver::scoped_level _sl(m_solver, level); + TRACE("pdr", tout << "is-reachable: " << head()->get_name() << " level: " << level << "\n"; + tout << mk_pp(state, m) << "\n";); + + bool assumes_level; + lbool is_sat = m_solver.check_conjunction_as_assumptions(state, core, &model, assumes_level); + if (is_sat == l_true && core) { + core->reset(); + model2cube(*model, *core); + n.set_model(model); + } + return is_sat; + } + + bool pred_transformer::is_invariant(unsigned level, expr* states, bool inductive, bool& assumes_level, expr_ref_vector* core) { + expr_ref_vector conj(m); + expr_ref tmp(m); + + conj.push_back(m.mk_not(states)); + + if (inductive) { + mk_assumptions(head(), states, conj); + } + tmp = pm.mk_and(conj); + prop_solver::scoped_level _sl(m_solver, level); + return m_solver.check_conjunction_as_assumptions(tmp, core, 0, assumes_level) == l_false; + } + + void pred_transformer::mk_assumptions(func_decl* head, expr* fml, expr_ref_vector& result) { + expr_ref tmp1(m), tmp2(m); + obj_map::iterator it = m_tag2rule.begin(), end = m_tag2rule.end(); + for (; it != end; ++it) { + expr* pred = it->m_key; + datalog::rule const* r = it->m_value; + if (!r) continue; + find_predecessors(*r, m_predicates); + for (unsigned i = 0; i < m_predicates.size(); i++) { + func_decl* d = m_predicates[i]; + if (d == head) { + // tmp1 = (m_tag2rule.size() == 1)?fml:m.mk_implies(pred, fml); + tmp1 = m.mk_implies(pred, fml); + pm.formula_n2o(tmp1, tmp2, i); + result.push_back(tmp2); + } + } + } + } + + void pred_transformer::initialize(decl2rel const& pts) { + m_initial_state = m.mk_false(); + m_transition = m.mk_true(); + init_rules(pts, m_initial_state, m_transition); + th_rewriter rw(m); + rw(m_transition); + rw(m_initial_state); + m_solver.add_formula(m_transition); + m_solver.add_level_formula(m_initial_state, 0); + TRACE("pdr", + tout << "Initial state: " << mk_pp(m_initial_state, m) << "\n"; + tout << "Transition: " << mk_pp(m_transition, m) << "\n";); + SASSERT(is_app(m_initial_state)); + m_reachable.add_init(to_app(m_initial_state)); + } + + void pred_transformer::init_rules(decl2rel const& pts, expr_ref& init, expr_ref& transition) { + expr_ref_vector transitions(m); + ptr_vector tr_rules; + datalog::rule const* rule; + expr_ref_vector disj(m); + app_ref pred(m); + for (unsigned i = 0; i < rules().size(); ++i) { + init_rule(pts, *rules()[i], init, tr_rules, transitions); + } + switch(transitions.size()) { + case 0: + transition = m.mk_false(); + break; + case 1: + // create a dummy tag. + pred = m.mk_fresh_const(head()->get_name().str().c_str(), m.mk_bool_sort()); + rule = tr_rules[0]; + m_tag2rule.insert(pred, rule); + m_rule2tag.insert(rule, pred.get()); + transitions.push_back(pred); + transition = pm.mk_and(transitions); + break; + default: + for (unsigned i = 0; i < transitions.size(); ++i) { + pred = m.mk_fresh_const(head()->get_name().str().c_str(), m.mk_bool_sort()); + rule = tr_rules[i]; + m_tag2rule.insert(pred, rule); + m_rule2tag.insert(rule, pred); + disj.push_back(pred); + transitions[i] = m.mk_implies(pred, transitions[i].get()); + } + transitions.push_back(m.mk_or(disj.size(), disj.c_ptr())); + transition = pm.mk_and(transitions); + break; + } + } + + void pred_transformer::init_rule( + decl2rel const& pts, + datalog::rule const& rule, + expr_ref& init, + ptr_vector& rules, + expr_ref_vector& transitions) + { + // Predicates that are variable representatives. Other predicates at + // positions the variables occur are made equivalent with these. + expr_ref_vector conj(m); + app_ref_vector& var_reprs = *(alloc(app_ref_vector, m)); + qinst* qi = 0; + + unsigned ut_size = rule.get_uninterpreted_tail_size(); + unsigned t_size = rule.get_tail_size(); + SASSERT(ut_size <= t_size); + init_atom(pts, rule.get_head(), var_reprs, conj, UINT_MAX); + for (unsigned i = 0; i < ut_size; ++i) { + if (rule.is_neg_tail(i)) { + throw default_exception("PDR does not support negated predicates in rule tails"); + } + init_atom(pts, rule.get_tail(i), var_reprs, conj, i); + } + for (unsigned i = ut_size; i < t_size; ++i) { + ground_free_vars(rule.get_tail(i), var_reprs); + } + SASSERT(check_filled(var_reprs)); + expr_ref_vector tail(m); + for (unsigned i = ut_size; i < t_size; ++i) { + tail.push_back(rule.get_tail(i)); + } + datalog::flatten_and(tail); + for (unsigned i = 0; i < tail.size(); ++i) { + expr_ref tmp(m); + var_subst(m, false)(tail[i].get(), var_reprs.size(), (expr*const*)var_reprs.c_ptr(), tmp); + quantifier* q; + if (datalog::rule_manager::is_forall(m, tmp, q)) { + + if (!qi) { + qi = alloc(qinst, m); + } + // + // The contract is to instantiate q with + // sufficient witnesses to validate body. + // + qi->quantifiers.push_back(q); + tmp = m.mk_true(); + } + conj.push_back(tmp); + TRACE("pdr", tout << mk_pp(tail[i].get(), m) << "\n" << mk_pp(tmp, m) << "\n";); + SASSERT(is_ground(tmp)); + } + expr_ref fml = pm.mk_and(conj); + TRACE("pdr", tout << mk_pp(fml, m) << "\n";); + SASSERT(is_ground(fml)); + if (m.is_false(fml)) { + dealloc(qi); + qi = 0; + // no-op. + } + else { + if (ut_size == 0 && !qi) { + init = m.mk_or(init, fml); + } + transitions.push_back(fml); + rules.push_back(&rule); + } + if (qi) { + m_rule2qinst.insert(&rule, qi); + } + m_rule2inst.insert(&rule,&var_reprs); + } + + bool pred_transformer::check_filled(app_ref_vector const& v) const { + for (unsigned i = 0; i < v.size(); ++i) { + if (!v[i]) return false; + } + return true; + } + + // create constants for free variables in tail. + void pred_transformer::ground_free_vars(expr* e, app_ref_vector& vars) { + ptr_vector sorts; + get_free_vars(e, sorts); + while (vars.size() < sorts.size()) { + vars.push_back(0); + } + for (unsigned i = 0; i < sorts.size(); ++i) { + if (sorts[i] && !vars[i].get()) { + vars[i] = m.mk_fresh_const("aux", sorts[i]); + } + } + } + + // create names for variables used in relations. + void pred_transformer::init_atom( + decl2rel const& pts, + app * atom, + app_ref_vector& var_reprs, + expr_ref_vector& conj, + unsigned tail_idx + ) + { + unsigned arity = atom->get_num_args(); + func_decl* head = atom->get_decl(); + pred_transformer& pt = *pts.find(head); + for (unsigned i = 0; i < arity; i++) { + app_ref rep(m); + + if (tail_idx == UINT_MAX) { + rep = m.mk_const(pm.o2n(pt.sig(i), 0)); + } + else { + rep = m.mk_const(pm.o2o(pt.sig(i), 0, tail_idx)); + } + + expr * arg = atom->get_arg(i); + if (is_var(arg)) { + var * v = to_var(arg); + unsigned var_idx = v->get_idx(); + if (var_idx >= var_reprs.size()) { + var_reprs.resize(var_idx+1); + } + expr * repr = var_reprs[var_idx].get(); + if (repr) { + conj.push_back(m.mk_eq(rep, repr)); + } + else { + var_reprs[var_idx] = rep; + } + } + else { + SASSERT(is_app(arg)); + conj.push_back(m.mk_eq(rep, arg)); + } + } + } + + void pred_transformer::add_premises(decl2rel const& pts, unsigned lvl, expr_ref_vector& r) { + r.push_back(pm.get_background()); + r.push_back((lvl == 0)?initial_state():transition()); + for (unsigned i = 0; i < rules().size(); ++i) { + add_premises(pts, lvl, *rules()[i], r); + } + } + + void pred_transformer::close(expr* e) { + m_reachable.add_reachable(e); + } + + void pred_transformer::add_premises(decl2rel const& pts, unsigned lvl, datalog::rule& rule, expr_ref_vector& r) { + find_predecessors(rule, m_predicates); + for (unsigned i = 0; i < m_predicates.size(); ++i) { + expr_ref tmp(m); + func_decl* head = m_predicates[i]; + pred_transformer& pt = *pts.find(head); + expr_ref inv = pt.get_formulas(lvl, false); + if (!m.is_true(inv)) { + pm.formula_n2o(inv, tmp, i, true); + r.push_back(tmp); + } + } + } + + void pred_transformer::inherit_properties(pred_transformer& other) { + SASSERT(m_head == other.m_head); + obj_map::iterator it = other.m_prop2level.begin(); + obj_map::iterator end = other.m_prop2level.end(); + + for (; it != end; ++it) { + add_property(it->m_key, it->m_value); + } + for (unsigned i = 0; i < other.m_invariants.size(); ++i) { + expr* e = other.m_invariants[i].get(); + m_invariants.push_back(e); + m_solver.add_formula(e); + } + } + + void pred_transformer::model2cube(app* c, expr* val, expr_ref_vector& res) const { + datatype_util dt(m); + if (m.is_bool(val)) { + res.push_back(m.is_true(val)?c:m.mk_not(c)); + } + else if (is_app(val) && dt.is_constructor(to_app(val))) { + func_decl* f = to_app(val)->get_decl(); + func_decl* r = dt.get_constructor_recognizer(f); + res.push_back(m.mk_app(r,c)); + ptr_vector const& acc = *dt.get_constructor_accessors(f); + for (unsigned i = 0; i < acc.size(); ++i) { + model2cube(m.mk_app(acc[i], c), to_app(val)->get_arg(i), res); + } + } + else { + res.push_back(m.mk_eq(c, val)); + } + } + + void pred_transformer::model2cube(const model_core& mdl, func_decl * d, expr_ref_vector& res) const { + expr_ref interp(m); + get_value_from_model(mdl, d, interp); + app* c = m.mk_const(d); + model2cube(c, interp, res); + } + + void pred_transformer::model2cube(const model_core & mdl, expr_ref_vector & res) const { + unsigned sz = mdl.get_num_constants(); + for (unsigned i = 0; i < sz; i++) { + func_decl * d = mdl.get_constant(i); + SASSERT(d->get_arity()==0); + if (!m_solver.is_aux_symbol(d)) { + model2cube(mdl, d, res); + } + } + } + + void pred_transformer::model2properties( + const model_core & mdl, + unsigned index, + model_node const& n, + expr_ref_vector & res) const { + expr_ref tr1(transition(), m), tr2(m); + expr_ref_vector trs(m), refs(m); + expr_substitution sub(m); + unsigned sz = mdl.get_num_constants(); + obj_map > equivs; + + for (unsigned i = 0; i < sz; i++) { + func_decl * d = mdl.get_constant(i); + expr_ref interp(m); + ptr_vector cs; + obj_map >::obj_map_entry* e; + get_value_from_model(mdl, d, interp); + app* c = m.mk_const(d); + refs.push_back(c); + refs.push_back(interp); + if (m.is_bool(d->get_range())) { + sub.insert(c, interp); + } + else { + e = equivs.insert_if_not_there2(interp, cs); + e->get_data().m_value.push_back(c); + } + } + obj_map >::iterator it = equivs.begin(), end = equivs.end(); + for (; it != end; ++it) { + // + // determine equivalence class representative. + // it is either one of the indexed variables, or it + // is the constant evaluated by the model. + // + ptr_vector const& cs = it->m_value; + expr* rep = 0; + for (unsigned i = 0; !rep && i < cs.size(); ++i) { + if (pm.is_o(cs[i]->get_decl(), index)) { + rep = cs[i]; + } + } + if (!rep) { + rep = it->m_key; + } + for (unsigned i = 0; i < cs.size(); ++i) { + if (!pm.is_o(cs[i]->get_decl(), index)) { + sub.insert(cs[i], rep); + } + } + } + scoped_ptr rep = mk_default_expr_replacer(m); + rep->set_substitution(&sub); + (*rep)(tr1); + th_rewriter rw(m); + rw(tr1); + TRACE("pdr", tout << "Transition:\n" << mk_pp(tr1, m) << "\n";); + pm.formula_o2n(tr1, tr2, index); + datalog::flatten_and(tr2, trs); + res.append(trs); + TRACE("pdr", tout << "Reduced transition:\n" << mk_pp(tr2, m) << "\n";); + + // + // Take state-invariant for indexed formula and select the literals + // that are true under the assignment. + // + // +#if 0 + IF_VERBOSE(2, verbose_stream() << "index: " << index << "\n" + << "level: " << n.level() << "\n" + << mk_pp(n.pt().get_propagation_formula(m_rels, n.level()), m) << "\n" + << "propagation formula\n" + << mk_pp(n.parent()->pt().get_propagation_formula(m_rels, n.level()+1), m) << "\n";); +#endif + } + + // ---------------- + // model_node + + void model_node::set_closed() { + pt().close(state()); + m_closed = true; + } + + expr_ref model_node::get_trace() const { + pred_transformer& p = pt(); + ast_manager& m = p.get_manager(); + manager& pm = p.get_pdr_manager(); + TRACE("pdr", model_v2_pp(tout, model());); + func_decl* f = p.head(); + unsigned arity = f->get_arity(); + expr_ref_vector args(m); + func_decl_ref v(m); + for (unsigned i = 0; i < arity; ++i) { + v = pm.o2n(p.sig(i),0); + expr* e = model().get_const_interp(v); + if (e) { + args.push_back(e); + } + else { + args.push_back(m.mk_const(v)); + } + } + return expr_ref(m.mk_app(f, args.size(), args.c_ptr()), m); + } + + + void model_node::mk_instantiate(datalog::rule_ref& r0, datalog::rule_ref& r1, expr_ref_vector& binding) { + ast_manager& m = pt().get_manager(); + expr_ref_vector conjs(m); + obj_map model; + datalog::flatten_and(state(), conjs); + for (unsigned i = 0; i < conjs.size(); ++i) { + expr* e = conjs[i].get(), *e1, *e2; + if (m.is_eq(e, e1, e2) || m.is_iff(e, e1, e2)) { + if (m.is_value(e2)) { + model.insert(e1, e2); + } + else if (m.is_value(e1)) { + model.insert(e2, e1); + } + } + else if (m.is_not(e, e1)) { + model.insert(e1, m.mk_false()); + } + else { + model.insert(e, m.mk_true()); + } + } + r0 = const_cast(pt().find_rule(*m_model.get())); + SASSERT(r0); + app_ref_vector& inst = pt().get_inst(r0); + TRACE("pdr", tout << mk_pp(state(), m) << "\n";); + for (unsigned i = 0; i < inst.size(); ++i) { + expr* v; + if (model.find(inst[i].get(), v)) { + binding.push_back(v); + } + else { + binding.push_back(m.mk_var(i, m.get_sort(inst[i].get()))); + } + } + r1 = r0; + if (!inst.empty()) { + r1.get_manager().substitute(r1, binding.size(), binding.c_ptr()); + } + } + + + + std::ostream& model_node::display(std::ostream& out, unsigned indent) { + for (unsigned i = 0; i < indent; ++i) out << " "; + out << m_level << " " << m_pt.head()->get_name() << " " << (m_closed?"closed":"open") << "\n"; + for (unsigned i = 0; i < indent; ++i) out << " "; + out << " " << mk_pp(m_state, m_state.get_manager(), indent) << "\n"; + for (unsigned i = 0; i < children().size(); ++i) { + children()[i]->display(out, indent + 1); + } + return out; + } + + unsigned model_node::index() const { + model_node* p = parent(); + if (!p) return 0; + for (unsigned i = 0; i < p->children().size(); ++i) { + if (this == p->children()[i]) return i; + } + UNREACHABLE(); + return 0; + } + + void model_node::get_properties(expr_ref_vector& props) const { + model_node* p = parent(); + if (p) { + p->pt().model2properties(p->model(), index(), *this, props); + } + } + + // ---------------- + // model_search + + model_node* model_search::next() { + if (m_leaves.empty()) { + return 0; + } + model_node* result = m_leaves.back(); + m_leaves.pop_back(); + return result; + } + + void model_search::add_leaf(model_node& n) { + unsigned& count = cache(n).insert_if_not_there2(n.state(), 0)->get_data().m_value; + ++count; + if (count == 1) { + set_leaf(n); + } + else { + n.set_pre_closed(); + } + } + + void model_search::set_leaf(model_node& n) { + erase_children(n); + SASSERT(n.is_open()); + if (m_bfs) { + m_leaves.push_front(&n); + } + else { + m_leaves.push_back(&n); + } + } + + void model_search::set_root(model_node* root) { + reset(); + m_root = root; + SASSERT(cache(*root).empty()); + cache(*root).insert(root->state(), 1); + set_leaf(*root); + } + + obj_map& model_search::cache(model_node const& n) { + unsigned l = n.level(); + if (l >= m_cache.size()) { + m_cache.resize(l + 1); + } + return m_cache[l]; + } + + void model_search::erase_children(model_node& n) { + ptr_vector todo, nodes; + todo.append(n.children()); + erase_leaf(n); + n.reset(); + while (!todo.empty()) { + model_node* m = todo.back(); + todo.pop_back(); + nodes.push_back(m); + todo.append(m->children()); + erase_leaf(*m); + remove_node(*m); + } + std::for_each(nodes.begin(), nodes.end(), delete_proc()); + } + + void model_search::remove_node(model_node& n) { + if (0 == --cache(n).find(n.state())) { + cache(n).remove(n.state()); + } + } + + void model_search::erase_leaf(model_node& n) { + + if (n.children().empty() && n.is_open()) { + std::deque::iterator + it = m_leaves.begin(), + end = m_leaves.end(); + for (; it != end; ++it) { + if (*it == &n) { + m_leaves.erase(it); + break; + } + } + } + } + + std::ostream& model_search::display(std::ostream& out) const { + if (m_root) { + m_root->display(out, 0); + } + out << "goals\n"; + std::deque::const_iterator + it = m_leaves.begin(), + end = m_leaves.end(); + for (; it != end; ++it) { + (*it)->display(out, 1); + } + return out; + } + + expr_ref model_search::get_trace() const { + pred_transformer& pt = get_root().pt(); + ast_manager& m = pt.get_manager(); + manager& pm = pt.get_pdr_manager(); + expr_ref result(m.mk_true(),m); + expr_ref_vector rules(m); + ptr_vector nodes; + nodes.push_back(m_root); + while (!nodes.empty()) { + model_node* current = nodes.back(); + nodes.pop_back(); + rules.push_back(current->get_trace()); + nodes.append(current->children()); + } + return pm.mk_and(rules); + } + + proof_ref model_search::get_proof_trace(context const& ctx) const { + pred_transformer& pt = get_root().pt(); + ast_manager& m = pt.get_manager(); + datalog::context& dctx = ctx.get_context(); + datalog::rule_manager& rm = dctx.get_rule_manager(); + datalog::rule_unifier unifier(rm, dctx, m); + datalog::dl_decl_util util(m); + datalog::rule_ref r0(rm), r1(rm); + obj_map cache; + obj_map rules; + ptr_vector todo; + proof_ref_vector trail(m); + datalog::rule_ref_vector rules_trail(rm); + proof* pr = 0; + todo.push_back(m_root); + while (!todo.empty()) { + model_node* n = todo.back(); + if (cache.find(n->state(), pr)) { + todo.pop_back(); + continue; + } + ptr_vector pfs; + ptr_vector rls; + ptr_vector const& chs = n->children(); + pfs.push_back(0); + rls.push_back(0); + for (unsigned i = 0; i < chs.size(); ++i) { + if (cache.find(chs[i]->state(), pr)) { + pfs.push_back(pr); + rls.push_back(rules.find(chs[i]->state())); + } + else { + todo.push_back(chs[i]); + } + } + if (pfs.size() != 1 + chs.size()) { + continue; + } + proof_ref rl(m); + expr_ref fml0(m); + expr_ref_vector binding(m); + n->mk_instantiate(r0, r1, binding); + r0->to_formula(fml0); + proof_ref p1(m), p2(m); + p1 = m.mk_asserted(fml0); + pfs[0] = p1; + rls[0] = r1; + TRACE("pdr", + tout << "root: " << mk_pp(p1.get(), m) << "\n"; + for (unsigned i = 0; i < binding.size(); ++i) { + tout << mk_pp(binding[i].get(), m) << "\n"; + } + for (unsigned i = 1; i < pfs.size(); ++i) { + tout << mk_pp(pfs[i], m) << "\n"; + } + ); + datalog::rule_ref reduced_rule(rm), r3(rm); + reduced_rule = rls[0]; + // check if binding is identity. + bool binding_is_id = true; + for (unsigned i = 0; binding_is_id && i < binding.size(); ++i) { + expr* v = binding[i].get(); + binding_is_id = is_var(v) && to_var(v)->get_idx() == i; + } + if (rls.size() > 1 || !binding_is_id) { + vector substs; + svector > positions; + substs.push_back(binding); // TODO base substitution. + for (unsigned i = 1; i < rls.size(); ++i) { + datalog::rule& src = *rls[i]; + bool unified = unifier.unify_rules(*reduced_rule, 0, src); + if (!unified) { + std::cout << "Could not unify rules: "; + reduced_rule->display(dctx, std::cout); + src.display(dctx, std::cout); + } + expr_ref_vector sub1 = unifier.get_rule_subst(*reduced_rule, true); + TRACE("pdr", + for (unsigned k = 0; k < sub1.size(); ++k) { + tout << mk_pp(sub1[k].get(), m) << " "; + } + tout << "\n"; + ); + for (unsigned j = 0; j < substs.size(); ++j) { + // TODO. apply sub1 to subst. + } + + positions.push_back(std::make_pair(i,0)); + substs.push_back(unifier.get_rule_subst(src, false)); + VERIFY(unifier.apply(*reduced_rule.get(), 0, src, r3)); + reduced_rule = r3; + } + expr_ref fml_concl(m); + reduced_rule->to_formula(fml_concl); + p1 = util.mk_hyper_resolve(pfs.size(), pfs.c_ptr(), fml_concl, positions, substs); + } + cache.insert(n->state(), p1); + rules.insert(n->state(), reduced_rule); + trail.push_back(p1); + rules_trail.push_back(reduced_rule); + todo.pop_back(); + } + return proof_ref(cache.find(m_root->state()), m); + } + + model_search::~model_search() { + TRACE("pdr", tout << "\n";); + reset(); + } + + void model_search::reset() { + if (m_root) { + erase_children(*m_root); + remove_node(*m_root); + dealloc(m_root); + m_root = 0; + } + } + + // ---------------- + // context + + context::context( + front_end_params& fparams, + params_ref const& params, + ast_manager& m + ) + : m_fparams(fparams), + m_params(params), + m(m), + m_context(0), + m_quantifier_inst(*this, m), + m_pm(m_fparams, m_params, m), + m_query_pred(m), + m_query(0), + m_search(m_params.get_bool(":bfs-model-search", true)), + m_last_result(l_undef), + m_inductive_lvl(0), + m_cancel(false) + { + } + + context::~context() { + reset(); + } + + void context::reset() { + TRACE("pdr", tout << "\n";); + cleanup(); + decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); + for (; it != end; ++it) { + dealloc(it->m_value); + } + m_rels.reset(); + std::for_each(m_model_generalizers.begin(), m_model_generalizers.end(), delete_proc()); + std::for_each(m_core_generalizers.begin(), m_core_generalizers.end(), delete_proc()); + m_model_generalizers.reset(); + m_core_generalizers.reset(); + m_search.reset(); + m_query = 0; + m_last_result = l_undef; + m_inductive_lvl = 0; + } + + void context::init_rules(datalog::rule_set& rules, decl2rel& rels) { + m_context = &rules.get_context(); + // Allocate collection of predicate transformers + datalog::rule_set::decl2rules::iterator dit = rules.begin_grouped_rules(), dend = rules.end_grouped_rules(); + decl2rel::obj_map_entry* e; + for (; dit != dend; ++dit) { + func_decl* pred = dit->m_key; + TRACE("pdr", tout << mk_pp(pred, m) << "\n";); + SASSERT(!rels.contains(pred)); + e = rels.insert_if_not_there2(pred, alloc(pred_transformer, get_pdr_manager(), pred)); + datalog::rule_vector const& pred_rules = *dit->m_value; + for (unsigned i = 0; i < pred_rules.size(); ++i) { + e->get_data().m_value->add_rule(pred_rules[i]); + } + } + // Initialize use list dependencies + decl2rel::iterator it = rels.begin(), end = rels.end(); + for (; it != end; ++it) { + func_decl* pred = it->m_key; + pred_transformer* pt = it->m_value, *pt_user; + obj_hashtable const& deps = rules.get_dependencies().get_deps(pred); + obj_hashtable::iterator itf = deps.begin(), endf = deps.end(); + for (; itf != endf; ++itf) { + TRACE("pdr", tout << mk_pp(pred, m) << " " << mk_pp(*itf, m) << "\n";); + if (!rels.find(*itf, pt_user)) { + pt_user = alloc(pred_transformer, get_pdr_manager(), *itf); + rels.insert(*itf, pt_user); + } + pt_user->add_use(pt); + } + } + + // Initialize the predicate transformers. + it = rels.begin(), end = rels.end(); + for (; it != end; ++it) { + pred_transformer& rel = *it->m_value; + rel.initialize(rels); + TRACE("pdr", rel.display(tout); ); + } + } + + void context::update_rules(datalog::rule_set& rules) { + decl2rel rels; + init_rules(rules, rels); + decl2rel::iterator it = rels.begin(), end = rels.end(); + for (; it != end; ++it) { + pred_transformer* pt = 0; + if (m_rels.find(it->m_key, pt)) { + it->m_value->inherit_properties(*pt); + } + } + reset(); + it = rels.begin(), end = rels.end(); + for (; it != end; ++it) { + m_rels.insert(it->m_key, it->m_value); + } + init_model_generalizers(); + init_core_generalizers(); + VERIFY(m_rels.find(m_query_pred, m_query)); + } + + unsigned context::get_num_levels(func_decl* p) { + pred_transformer* pt = 0; + if (m_rels.find(p, pt)) { + return pt->get_num_levels(); + } + else { + IF_VERBOSE(10, verbose_stream() << "did not find predicate " << p->get_name() << "\n";); + return 0; + } + } + + expr_ref context::get_cover_delta(int level, func_decl* p) { + pred_transformer* pt = 0; + if (m_rels.find(p, pt)) { + return pt->get_cover_delta(level); + } + else { + IF_VERBOSE(10, verbose_stream() << "did not find predicate " << p->get_name() << "\n";); + return expr_ref(m.mk_true(), m); + } + } + + void context::add_cover(int level, func_decl* p, expr* property) { + pred_transformer* pt = 0; + if (!m_rels.find(p, pt)) { + pt = alloc(pred_transformer, get_pdr_manager(), p); + m_rels.insert(p, pt); + IF_VERBOSE(10, verbose_stream() << "did not find predicate " << p->get_name() << "\n";); + } + unsigned lvl = (level == -1)?infty_level:((unsigned)level); + pt->add_cover(lvl, property); + } + + + class context::is_propositional_proc { + ast_manager& m; + arith_util a; + bool m_is_propositional; + public: + is_propositional_proc(ast_manager& m):m(m), a(m), m_is_propositional(true) {} + void operator()(expr* e) { + if (m_is_propositional) { + + if (!m.is_bool(e) && !a.is_int_real(e)) { + m_is_propositional = false; + } + else if (!is_app(e)) { + m_is_propositional = false; + } + else if (to_app(e)->get_num_args() > 0 && + to_app(e)->get_family_id() != m.get_basic_family_id() && + to_app(e)->get_family_id() != a.get_family_id()) { + m_is_propositional = false; + } + } + } + bool is_propositional() { return m_is_propositional; } + }; + + bool context::is_propositional() { + expr_fast_mark1 mark; + is_propositional_proc proc(m); + decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); + for (; proc.is_propositional() && it != end; ++it) { + quick_for_each_expr(proc, mark, it->m_value->transition()); + } + return proc.is_propositional(); + } + + class context::is_bool_proc { + ast_manager& m; + bool m_is_bool; + public: + is_bool_proc(ast_manager& m):m(m), m_is_bool(true) {} + void operator()(expr* e) { + if (m_is_bool) { + + if (!m.is_bool(e)) { + m_is_bool = false; + } + else if (!is_app(e)) { + m_is_bool = false; + } + else if (to_app(e)->get_num_args() > 0 && + to_app(e)->get_family_id() != m.get_basic_family_id()) { + m_is_bool = false; + } + } + } + bool is_bool() { return m_is_bool; } + }; + + bool context::is_bool() { + expr_fast_mark1 mark; + is_bool_proc proc(m); + decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); + for (; proc.is_bool() && it != end; ++it) { + quick_for_each_expr(proc, mark, it->m_value->transition()); + } + return proc.is_bool(); + } + + + void context::init_model_generalizers() { + if (is_propositional()) { + m_model_generalizers.push_back(alloc(bool_model_evaluation_generalizer, *this, m)); + } + else { + m_model_generalizers.push_back(alloc(model_evaluation_generalizer, *this, m)); + } + + if (m_params.get_bool(":use-farkas-model", false)) { + m_model_generalizers.push_back(alloc(model_farkas_generalizer, *this)); + } + + if (m_params.get_bool(":use-precondition-generalizer", false)) { + m_model_generalizers.push_back(alloc(model_precond_generalizer, *this)); + } + } + + void context::init_core_generalizers() { + if (m_params.get_bool(":use-multicore-generalizer", false)) { + m_core_generalizers.push_back(alloc(core_multi_generalizer, *this)); + } + if (m_params.get_bool(":use-farkas", true) && !is_bool()) { + m_core_generalizers.push_back(alloc(core_farkas_generalizer, *this, m, m_fparams)); + } + if (m_params.get_bool(":use-farkas-properties", false)) { + m_core_generalizers.push_back(alloc(core_farkas_properties_generalizer, *this)); + } + if (m_params.get_bool(":use-inductive-generalizer", true)) { + m_core_generalizers.push_back(alloc(core_bool_inductive_generalizer, *this, 0)); + } + if (m_params.get_bool(":use-interpolants", false)) { + m_core_generalizers.push_back(alloc(core_interpolant_generalizer, *this)); + } + if (m_params.get_bool(":inductive-reachability-check", false)) { + m_core_generalizers.push_back(alloc(core_induction_generalizer, *this)); + } + } + + void context::get_level_property(unsigned lvl, expr_ref_vector& res, vector& rs) const { + decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); + for (; it != end; ++it) { + pred_transformer* r = it->m_value; + if (r->head() == m_query_pred) { + continue; + } + expr_ref conj = r->get_formulas(lvl, false); + m_pm.formula_n2o(0, false, conj); + res.push_back(conj); + ptr_vector sig(r->head()->get_arity(), r->sig()); + rs.push_back(relation_info(m, r->head(), sig, conj)); + } + } + + void context::simplify_formulas() { + decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); + for (; it != end; ++it) { + pred_transformer* r = it->m_value; + r->simplify_formulas(); + } + } + + lbool context::solve() { + m_last_result = l_undef; + try { + solve_impl(); + UNREACHABLE(); + } + catch (model_exception) { + check_quantifiers(); + IF_VERBOSE(1, verbose_stream() << "\n"; m_search.display(verbose_stream());); + m_last_result = l_true; + return l_true; + } + catch (inductive_exception) { + simplify_formulas(); + m_last_result = l_false; + TRACE("pdr", display_certificate(tout);); + IF_VERBOSE(1, display_certificate(verbose_stream());); + return l_false; + } + catch (unknown_exception) { + return l_undef; + } + return l_undef; + } + + void context::cancel() { + m_cancel = true; + } + + void context::cleanup() { + m_cancel = false; + } + + void context::checkpoint() { + if (m_cancel) { + throw default_exception("pdr canceled"); + } + } + + /** + \brief retrieve answer. + */ + expr_ref context::get_answer() { + switch(m_last_result) { + case l_true: return mk_sat_answer(); + case l_false: return mk_unsat_answer(); + default: return expr_ref(m.mk_true(), m); + } + } + + void context::get_model(model_ref& md) { + SASSERT(m_last_result == l_false); + expr_ref_vector refs(m); + vector rs; + get_level_property(m_inductive_lvl, refs, rs); + inductive_property ex(m, m_mc, rs); + ex.to_model(md); + } + + proof_ref context::get_proof() const { + datalog::scoped_coarse_proof _sc(m); + proof_ref proof(m); + SASSERT(m_last_result == l_true); + proof = m_search.get_proof_trace(*this); + apply(m, m_pc.get(), proof); + return proof; + } + + + /** + \brief Retrieve satisfying assignment with explanation. + */ + expr_ref context::mk_sat_answer() const { + if (m_params.get_bool(":generate-proof-trace", false)) { + proof_ref pr = get_proof(); + return expr_ref(pr.get(), m); + } + return m_search.get_trace(); + } + + expr_ref context::mk_unsat_answer() const { + expr_ref_vector refs(m); + vector rs; + get_level_property(m_inductive_lvl, refs, rs); + inductive_property ex(m, const_cast(m_mc), rs); + return ex.to_expr(); + } + + void context::solve_impl() { + unsigned lvl = 0; + bool reachable; + while (true) { + checkpoint(); + reachable = check_reachability(lvl); + if (reachable) { + throw model_exception(); + } + if (lvl != 0) { + propagate(lvl); + } + lvl++; + m_stats.m_max_depth = std::max(m_stats.m_max_depth, lvl); + IF_VERBOSE(1,verbose_stream() << "Entering level "<m_value; + if (pt->has_quantifiers()) return true; + } + return false; + } + + // + // Pick a potential counter example state. + // Initialize a search tree using that counter-example. + // If the counter-example expands to a full model, then + // the search tree is a model, otherwise obtain the next + // query state. + // + bool context::check_reachability(unsigned level) { + expr_ref state(m.mk_true(), m); + model_node* root = alloc(model_node, 0, state, *m_query, level); + m_search.set_root(root); + + while (model_node* node = m_search.next()) { + IF_VERBOSE(2, verbose_stream() << "Expand node: " << node->level() << "\n";); + checkpoint(); + expand_node(*node); + } + return root->is_closed(); + } + + void context::close_node(model_node& n) { + n.set_closed(); + model_node* p = n.parent(); + while (p && p->is_1closed()) { + p->set_closed(); + p = p->parent(); + } + } + + void context::check_pre_closed(model_node& n) { + for (unsigned i = 0; i < n.children().size(); ++i) { + if (!n.children()[i]->is_closed()) return; + } + n.set_pre_closed(); + model_node* p = n.parent(); + while (p && p->is_1closed()) { + p->set_pre_closed(); + p = p->parent(); + } + } + + void context::expand_node(model_node& n) { + SASSERT(n.is_open()); + expr_ref_vector cube(m); + + if (n.pt().is_reachable(n.state())) { + TRACE("pdr", tout << "reachable\n";); + close_node(n); + } + else { + switch (expand_state(n, cube)) { + case l_true: + if (n.level() == 0) { + TRACE("pdr", tout << "reachable\n";); + close_node(n); + } + else { + TRACE("pdr", tout << "node: " << &n << "\n"; + expr_ref cb(m.mk_and(cube.size(),cube.c_ptr()), m); + tout << mk_pp(cb.get(), m) << "\n";); + for (unsigned i = 0; i < m_model_generalizers.size(); ++i) { + (*m_model_generalizers[i])(n, cube); + } + create_children(n, m_pm.mk_and(cube)); + } + break; + case l_false: { + bool uses_level = true; + for (unsigned i = 0; !cube.empty() && i < m_core_generalizers.size(); ++i) { + (*m_core_generalizers[i])(n, cube, uses_level); + } + expr_ref ncube(m_pm.mk_not_and(cube), m); + TRACE("pdr", tout << "invariant state: " << (uses_level?"":"(inductive) ") << mk_pp(ncube, m) << "\n";); + n.pt().add_property(ncube, uses_level?n.level():infty_level); + CASSERT("pdr",n.level() == 0 || check_invariant(n.level()-1)); + model_node* p = n.parent(); + if (p) { + m_search.set_leaf(*p); + } + break; + } + case l_undef: { + TRACE("pdr", tout << "unknown state: " << mk_pp(m_pm.mk_and(cube), m) << "\n";); + throw unknown_exception(); + } + } + } + } + + // + // check if predicate transformer has a satisfiable predecessor state. + // returns either a satisfiable predecessor state or + // return a property that blocks state and is implied by the + // predicate transformer (or some unfolding of it). + // + lbool context::expand_state(model_node& n, expr_ref_vector& result) { + return n.pt().is_reachable(n, &result); + } + + void context::propagate(unsigned max_prop_lvl) { + if (m_params.get_bool(":simplify-formulas-pre", false)) { + simplify_formulas(); + } + for (unsigned lvl = 0; lvl <= max_prop_lvl; lvl++) { + checkpoint(); + bool all_propagated = true; + decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); + for (; it != end; ++it) { + checkpoint(); + pred_transformer& r = *it->m_value; + all_propagated = r.propagate_to_next_level(lvl) && all_propagated; + } + CASSERT("pdr", check_invariant(lvl)); + + if (all_propagated && lvl == max_prop_lvl) { + m_inductive_lvl = lvl; + throw inductive_exception(); + } + } + if (m_params.get_bool(":simplify-formulas-post", false)) { + simplify_formulas(); + } + } + + // create children states from model cube. + void context::create_children(model_node& n, expr* model) { + expr_ref_vector literals(m), sub_lits(m); + expr_ref o_cube(m), n_cube(m); + datalog::flatten_and(model, literals); + ptr_vector preds; + unsigned level = n.level(); + SASSERT(level > 0); + n.pt().find_predecessors(n.model(), preds); + n.pt().remove_predecessors(literals); + TRACE("pdr", tout << mk_pp(model, m) << "\n"; + model_v2_pp(tout, n.model()); + for (unsigned i = 0; i < preds.size(); ++i) { + tout << mk_pp(preds[i], m) << "\n"; + } + ); + for (unsigned i = 0; i < preds.size(); ++i) { + pred_transformer& pt = *m_rels.find(preds[i]); + SASSERT(pt.head() == preds[i]); + assign_ref_vector(sub_lits, literals); + m_pm.filter_o_atoms(sub_lits, i); + o_cube = m_pm.mk_and(sub_lits); + m_pm.formula_o2n(o_cube, n_cube, i); + model_node* child = alloc(model_node, &n, n_cube, pt, level-1); + ++m_stats.m_num_nodes; + m_search.add_leaf(*child); + } + check_pre_closed(n); + + TRACE("pdr", m_search.display(tout);); + } + + void context::collect_statistics(statistics& st) const { + decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); + for (it = m_rels.begin(); it != end; ++it) { + it->m_value->collect_statistics(st); + } + st.update("PDR num unfoldings", m_stats.m_num_nodes); + st.update("PDR max depth", m_stats.m_max_depth); + m_pm.collect_statistics(st); + + for (unsigned i = 0; i < m_model_generalizers.size(); ++i) { + m_model_generalizers[i]->collect_statistics(st); + } + for (unsigned i = 0; i < m_core_generalizers.size(); ++i) { + m_core_generalizers[i]->collect_statistics(st); + } + } + + + std::ostream& context::display(std::ostream& out) const { + decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); + for (; it != end; ++it) { + it->m_value->display(out); + } + m_search.display(out); + return out; + } + + bool context::check_invariant(unsigned lvl) { + decl2rel::iterator it = m_rels.begin(), end = m_rels.end(); + for (; it != end; ++it) { + checkpoint(); + if (!check_invariant(lvl, it->m_key)) { + return false; + } + } + return true; + } + + bool context::check_invariant(unsigned lvl, func_decl* fn) { + smt::solver ctx(m, m_fparams); + pred_transformer& pt = *m_rels.find(fn); + expr_ref_vector conj(m); + expr_ref inv = pt.get_formulas(next_level(lvl), false); + if (m.is_true(inv)) return true; + pt.add_premises(m_rels, lvl, conj); + conj.push_back(m.mk_not(inv)); + expr_ref fml(m.mk_and(conj.size(), conj.c_ptr()), m); + ctx.assert_expr(fml); + lbool result = ctx.check(); + TRACE("pdr", tout << "Check invariant level: " << lvl << " " << result << "\n" << mk_pp(fml, m) << "\n";); + return result == l_false; + } + + void context::display_certificate(std::ostream& strm) const { + switch(m_last_result) { + case l_false: { + expr_ref_vector refs(m); + vector rs; + get_level_property(m_inductive_lvl, refs, rs); + inductive_property ex(m, const_cast(m_mc), rs); + strm << ex.to_string(); + break; + } + case l_true: { + strm << mk_pp(mk_sat_answer(), m); + break; + } + case l_undef: { + strm << "unknown"; + break; + } + } + } + +} diff --git a/lib/pdr_context.h b/lib/pdr_context.h new file mode 100644 index 000000000..e0ac2c53d --- /dev/null +++ b/lib/pdr_context.h @@ -0,0 +1,403 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + pdr_context.h + +Abstract: + + PDR for datalog + +Author: + + Nikolaj Bjorner (nbjorner) 2011-11-20. + +Revision History: + +--*/ + +#ifndef _PDR_CONTEXT_H_ +#define _PDR_CONTEXT_H_ + +#include +#include "pdr_manager.h" +#include "dl_base.h" +#include "pdr_prop_solver.h" +#include "pdr_reachable_cache.h" +#include "pdr_quantifiers.h" + +namespace datalog { + class rule_set; + class context; +}; + +namespace pdr { + + class pred_transformer; + class model_node; + + typedef obj_map qinst_map; + typedef obj_map rule2inst; + typedef obj_map decl2rel; + + // + // Predicate transformer state. + // A predicate transformer corresponds to the + // set of rules that have the same head predicates. + // + + class pred_transformer { + + struct stats { + unsigned m_num_propagations; + stats() { reset(); } + void reset() { memset(this, 0, sizeof(*this)); } + }; + + manager& pm; // pdr-manager + ast_manager& m; // manager + + func_decl_ref m_head; // predicate + func_decl_ref_vector m_sig; // signature + ptr_vector m_use; // places where 'this' is referenced. + ptr_vector m_rules; // rules used to derive transformer + prop_solver m_solver; // solver context + vector m_levels; // level formulas + expr_ref_vector m_invariants; // properties that are invariant. + obj_map m_prop2level; // map property to level where it occurs. + obj_map m_tag2rule; // map tag predicate to rule. + obj_map m_rule2tag; // map rule to predicate tag. + qinst_map m_rule2qinst; // map tag to quantifier instantiation. + rule2inst m_rule2inst; // map rules to instantiations of indices + expr_ref m_transition; // transition relation. + expr_ref m_initial_state; // initial state. + reachable_cache m_reachable; + ptr_vector m_predicates; + stats m_stats; + + void init_sig(); + void ensure_level(unsigned level); + bool add_property1(expr * lemma, unsigned lvl); // add property 'p' to state at level lvl. + void add_child_property(pred_transformer& child, expr* lemma, unsigned lvl); + void mk_assumptions(func_decl* head, expr* fml, expr_ref_vector& result); + + // Initialization + void init_rules(decl2rel const& pts, expr_ref& init, expr_ref& transition); + void init_rule(decl2rel const& pts, datalog::rule const& rule, expr_ref& init, + ptr_vector& rules, expr_ref_vector& transition); + void init_atom(decl2rel const& pts, app * atom, app_ref_vector& var_reprs, expr_ref_vector& conj, unsigned tail_idx); + void ground_free_vars(expr* e, app_ref_vector& vars); + + void model2cube(const model_core& md, func_decl * d, expr_ref_vector& res) const; + void model2cube(app* c, expr* val, expr_ref_vector& res) const; + + void simplify_formulas(tactic& tac, expr_ref_vector& fmls); + + // Debugging + bool check_filled(app_ref_vector const& v) const; + + void add_premises(decl2rel const& pts, unsigned lvl, datalog::rule& rule, expr_ref_vector& r); + + public: + pred_transformer(manager& pm, func_decl* head); + ~pred_transformer(); + + 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) { init_sig(); return m_sig[i].get(); } // signature + func_decl* const* sig() { init_sig(); return m_sig.c_ptr(); } + expr* transition() const { return m_transition; } + expr* initial_state() const { return m_initial_state; } + bool has_quantifiers() const { return !m_rule2qinst.empty(); } + qinst* get_quantifiers(datalog::rule const* r) const { qinst* q = 0; m_rule2qinst.find(r, q); return q; } + expr* rule2tag(datalog::rule const* r) { return m_rule2tag.find(r); } + unsigned get_num_levels() { return m_levels.size(); } + expr_ref get_cover_delta(int level); + void add_cover(unsigned level, expr* property); + + std::ostream& display(std::ostream& strm) const; + + void collect_statistics(statistics& st) const; + + bool is_reachable(expr* state); + void remove_predecessors(expr_ref_vector& literals); + void find_predecessors(datalog::rule const& r, ptr_vector& predicates) const; + void find_predecessors(model_core const& model, ptr_vector& preds) const; + datalog::rule const* find_rule(model_core const& model) const; + + bool propagate_to_next_level(unsigned level); + void add_property(expr * lemma, unsigned lvl); // add property 'p' to state at level. + + lbool is_reachable(model_node& n, expr_ref_vector* core); + bool is_invariant(unsigned level, expr* co_state, bool inductive, bool& assumes_level, expr_ref_vector* core = 0); + + expr_ref get_formulas(unsigned level, bool add_axioms); + + void simplify_formulas(); + + expr_ref get_propagation_formula(decl2rel const& pts, unsigned level); + + manager& get_pdr_manager() const { return pm; } + ast_manager& get_manager() const { return m; } + + void model2cube(const model_core & mdl, expr_ref_vector & res) const; + void model2properties(const model_core & mdl, unsigned index, model_node const& n, expr_ref_vector & res) const; + + void add_premises(decl2rel const& pts, unsigned lvl, expr_ref_vector& r); + + void close(expr* e); + + app_ref_vector& get_inst(datalog::rule const* r) { return *m_rule2inst.find(r);} + + void inherit_properties(pred_transformer& other); + + }; + + + // structure for counter-example search. + class model_node { + model_node* m_parent; + pred_transformer& m_pt; + expr_ref m_state; + model_ref m_model; + ptr_vector m_children; + unsigned m_level; + bool m_closed; + public: + model_node(model_node* parent, expr_ref& state, pred_transformer& pt, unsigned level): + m_parent(parent), m_pt(pt), m_state(state), m_model(0), m_level(level), m_closed(false) { + if (m_parent) { + m_parent->m_children.push_back(this); + SASSERT(m_parent->m_level == level+1); + SASSERT(m_parent->m_level > 0); + } + } + void set_model(model_ref& m) { m_model = m; } + unsigned level() const { return m_level; } + expr* state() const { return m_state; } + ptr_vector const& children() { return m_children; } + pred_transformer& pt() const { return m_pt; } + model_node* parent() const { return m_parent; } + model_core const& model() const { return *m_model; } + unsigned index() const; + void get_properties(expr_ref_vector& props) const; + + bool is_closed() const { return m_closed; } + bool is_open() const { return !is_closed(); } + + bool is_1closed() { + if (is_closed()) return true; + if (m_children.empty()) return false; + for (unsigned i = 0; i < m_children.size(); ++i) { + if (m_children[i]->is_open()) return false; + } + return true; + } + + void set_closed(); + void set_pre_closed() { m_closed = true; } + void reset() { m_children.reset(); } + + expr_ref get_trace() const; + void mk_instantiate(datalog::rule_ref& r0, datalog::rule_ref& r1, expr_ref_vector& binding); + + std::ostream& display(std::ostream& out, unsigned indent); + }; + + class model_search { + bool m_bfs; + model_node* m_root; + std::deque m_leaves; + vector > m_cache; + + obj_map& cache(model_node const& n); + void erase_children(model_node& n); + void erase_leaf(model_node& n); + void remove_node(model_node& n); + public: + model_search(bool bfs): m_bfs(bfs), m_root(0) {} + + ~model_search(); + + void reset(); + + model_node* next(); + + void add_leaf(model_node& n); // add fresh node. + + void set_leaf(model_node& n); // Set node as leaf, remove children. + + void set_root(model_node* n); + + model_node& get_root() const { return *m_root; } + + std::ostream& display(std::ostream& out) const; + + expr_ref get_trace() const; + + proof_ref get_proof_trace(context const& ctx) const; + }; + + struct model_exception { }; + struct inductive_exception {}; + + class context; + + // 'state' is satisifiable with predecessor 'cube'. + // Generalize predecessor still forcing satisfiability. + class model_generalizer { + protected: + context& m_ctx; + public: + model_generalizer(context& ctx): m_ctx(ctx) {} + virtual ~model_generalizer() {} + virtual void operator()(model_node& n, expr_ref_vector& cube) = 0; + virtual void collect_statistics(statistics& st) {} + }; + + // 'state' is unsatisfiable at 'level' with 'core'. + // Minimize or weaken core. + class core_generalizer { + protected: + context& m_ctx; + public: + core_generalizer(context& ctx): m_ctx(ctx) {} + virtual ~core_generalizer() {} + virtual void operator()(model_node& n, expr_ref_vector& core, bool& uses_level) = 0; + virtual void collect_statistics(statistics& st) {} + }; + + class context { + + struct stats { + unsigned m_num_nodes; + unsigned m_max_depth; + stats() { reset(); } + void reset() { memset(this, 0, sizeof(*this)); } + }; + + + front_end_params& m_fparams; + params_ref const& m_params; + ast_manager& m; + datalog::context* m_context; + quantifier_model_checker m_quantifier_inst; + manager m_pm; + decl2rel m_rels; // Map from relation predicate to fp-operator. + func_decl_ref m_query_pred; + pred_transformer* m_query; + model_search m_search; + lbool m_last_result; + unsigned m_inductive_lvl; + ptr_vector m_model_generalizers; + ptr_vector m_core_generalizers; + stats m_stats; + volatile bool m_cancel; + model_converter_ref m_mc; + proof_converter_ref m_pc; + + // Functions used by search. + void solve_impl(); + bool check_reachability(unsigned level); + void check_quantifiers(); + bool has_quantifiers() const; + void propagate(unsigned max_prop_lvl); + void close_node(model_node& n); + void check_pre_closed(model_node& n); + void expand_node(model_node& n); + lbool expand_state(model_node& n, expr_ref_vector& cube); + void create_children(model_node& n, expr* cube); + expr_ref mk_sat_answer() const; + expr_ref mk_unsat_answer() const; + + // Generate inductive property + void get_level_property(unsigned lvl, expr_ref_vector& res, vector & rs) const; + + + // Initialization + class is_propositional_proc; + bool is_propositional(); + class is_bool_proc; + bool is_bool(); + void init_model_generalizers(); + void init_core_generalizers(); + + bool check_invariant(unsigned lvl); + bool check_invariant(unsigned lvl, func_decl* fn); + + void checkpoint(); + + void init_rules(datalog::rule_set& rules, decl2rel& transformers); + + void simplify_formulas(); + + 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. + */ + context( + front_end_params& fparams, + params_ref const& params, + ast_manager& m); + + ~context(); + + front_end_params& get_fparams() const { return m_fparams; } + params_ref const& get_params() const { return m_params; } + ast_manager& get_manager() const { return m; } + manager& get_pdr_manager() { 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_context() const { SASSERT(m_context); return *m_context; } + + expr_ref get_answer(); + + void collect_statistics(statistics& st) const; + + std::ostream& display(std::ostream& strm) const; + + void display_certificate(std::ostream& strm) const; + + lbool solve(); + + void cancel(); + + void cleanup(); + + void reset(); + + void set_query(func_decl* q) { m_query_pred = q; } + + void set_model_converter(model_converter_ref& mc) { m_mc = mc; } + + void set_proof_converter(proof_converter_ref& pc) { m_pc = pc; } + + void update_rules(datalog::rule_set& rules); + + void set_axioms(expr* axioms) { m_pm.set_background(axioms); } + + void refine(qi& q, datalog::rule_set& rules) { m_quantifier_inst.refine(q, rules); } + + unsigned get_num_levels(func_decl* p); + + expr_ref get_cover_delta(int level, func_decl* p); + + void add_cover(int level, func_decl* pred, expr* property); + + void get_model(model_ref& md); + + proof_ref get_proof() const; + + }; + +}; + +#endif diff --git a/lib/pdr_dl_interface.cpp b/lib/pdr_dl_interface.cpp new file mode 100644 index 000000000..15d7755c1 --- /dev/null +++ b/lib/pdr_dl_interface.cpp @@ -0,0 +1,217 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + pdr_dl.cpp + +Abstract: + + SMT2 interface for the datalog PDR + +Author: + + Krystof Hoder (t-khoder) 2011-9-22. + +Revision History: + +--*/ + +#include "dl_cmds.h" +#include "dl_context.h" +#include "dl_mk_coi_filter.h" +#include "dl_mk_interp_tail_simplifier.h" +#include "dl_mk_subsumption_checker.h" +#include "dl_mk_rule_inliner.h" +#include "dl_rule.h" +#include "dl_rule_transformer.h" +#include "smt2parser.h" +#include "pdr_context.h" +#include "pdr_dl_interface.h" +#include "dl_rule_set.h" +#include "dl_mk_slice.h" + +using namespace pdr; + +dl_interface::dl_interface(datalog::context& ctx) : + m_ctx(ctx), + m_pdr_rules(ctx), + m_old_rules(ctx), + m_context(0) { + m_context = alloc(pdr::context, ctx.get_fparams(), ctx.get_params(), ctx.get_manager()); +} + + +dl_interface::~dl_interface() { + dealloc(m_context); +} + + +// +// Check if the new rules are weaker so that we can +// re-use existing context. +// +void dl_interface::check_reset() { + datalog::rule_ref_vector const& new_rules = m_ctx.get_rules().get_rules(); + datalog::rule_ref_vector const& old_rules = m_old_rules.get_rules(); + for (unsigned i = 0; i < new_rules.size(); ++i) { + bool found = false; + for (unsigned j = 0; !found && j < old_rules.size(); ++j) { + if (m_ctx.check_subsumes(*old_rules[j], *new_rules[i])) { + found = true; + } + } + if (!found) { + CTRACE("pdr", (old_rules.size() > 0), new_rules[i]->display(m_ctx, tout << "Fresh rule ");); + m_context->reset(); + break; + } + } + m_old_rules.reset(); + m_old_rules.add_rules(new_rules.size(), new_rules.c_ptr()); +} + + +lbool dl_interface::query(expr * query) { + //we restore the initial state in the datalog context + m_ctx.ensure_opened(); + m_pdr_rules.reset(); + m_ctx.get_rmanager().reset_relations(); + ast_manager& m = m_ctx.get_manager(); + datalog::relation_manager& rm = m_ctx.get_rmanager(); + datalog::rule_manager& rule_manager = m_ctx.get_rule_manager(); + datalog::rule_set old_rules(m_ctx.get_rules()); + func_decl_ref query_pred(m); + datalog::rule_ref_vector query_rules(rule_manager); + datalog::rule_ref query_rule(rule_manager); + rule_manager.mk_query(query, query_pred, query_rules, query_rule); + m_ctx.add_rules(query_rules); + expr_ref bg_assertion = m_ctx.get_background_assertion(); + + check_reset(); + + TRACE("pdr", + if (!m.is_true(bg_assertion)) { + tout << "axioms:\n"; + tout << mk_pp(bg_assertion, m) << "\n"; + } + tout << "query: " << mk_pp(query, m) << "\n"; + tout << "rules:\n"; + m_ctx.display_rules(tout); + ); + + model_converter_ref mc = datalog::mk_skip_model_converter(); + proof_converter_ref pc; + if (m_ctx.get_params().get_bool(":generate-proof-trace", false)) { + pc = datalog::mk_skip_proof_converter(); + } + m_ctx.set_output_predicate(query_pred); + m_ctx.apply_default_transformation(mc, pc); + + if (m_ctx.get_params().get_bool(":slice", true)) { + datalog::rule_transformer transformer(m_ctx); + datalog::mk_slice* slice = alloc(datalog::mk_slice, m_ctx); + transformer.register_plugin(slice); + m_ctx.transform_rules(transformer, mc, pc); + query_pred = slice->get_predicate(query_pred.get()); + m_ctx.set_output_predicate(query_pred); + } + + IF_VERBOSE(2, m_ctx.display_rules(verbose_stream());); + m_pdr_rules.add_rules(m_ctx.get_rules()); + m_pdr_rules.close(); + m_ctx.reopen(); + m_ctx.replace_rules(old_rules); + + if (m_pdr_rules.get_rules().empty()) { + return l_false; + } + + m_context->set_proof_converter(pc); + m_context->set_model_converter(mc); + m_context->set_query(query_pred); + m_context->set_axioms(bg_assertion); + m_context->update_rules(m_pdr_rules); + + + while (true) { + try { + return m_context->solve(); + } + catch (pdr::qi& q) { + m_context->refine(q, m_pdr_rules); + } + } +} + +expr_ref dl_interface::get_cover_delta(int level, func_decl* pred) { + return m_context->get_cover_delta(level, pred); +} + +void dl_interface::add_cover(int level, func_decl* pred, expr* property) { + m_context->add_cover(level, pred, property); +} + +unsigned dl_interface::get_num_levels(func_decl* pred) { + return m_context->get_num_levels(pred); +} + +void dl_interface::collect_statistics(statistics& st) const { + m_context->collect_statistics(st); +} + +void dl_interface::display_certificate(std::ostream& out) const { + m_context->display_certificate(out); +} + +expr_ref dl_interface::get_answer() { + return m_context->get_answer(); +} + +void dl_interface::cancel() { + m_context->cancel(); +} + +void dl_interface::cleanup() { + m_context->cleanup(); +} + +void dl_interface::updt_params() { + dealloc(m_context); + m_context = alloc(pdr::context, m_ctx.get_fparams(), m_ctx.get_params(), m_ctx.get_manager()); +} + +void dl_interface::collect_params(param_descrs& p) { + p.insert(":bfs-model-search", CPK_BOOL, "PDR: (default true) use BFS strategy for expanding model search"); + p.insert(":use-farkas", CPK_BOOL, "PDR: (default true) use lemma generator based on Farkas (for linear real arithmetic)"); + PRIVATE_PARAMS(p.insert(":use-farkas-model", CPK_BOOL, "PDR: (default false) enable using Farkas generalization through model propagation");); + PRIVATE_PARAMS(p.insert(":use-precondition-generalizer", CPK_BOOL, "PDR: (default false) enable generalizations from weakest pre-conditions");); + PRIVATE_PARAMS(p.insert(":use-multicore-generalizer", CPK_BOOL, + "PDR: (default false) extract multiple cores for blocking states");); + PRIVATE_PARAMS(p.insert(":use-farkas-properties", CPK_BOOL, + "PDR: (default false) experimental Farkas lemma generation method");); + PRIVATE_PARAMS(p.insert(":use-inductive-generalizer", CPK_BOOL, + "PDR: (default true) generalize lemmas using induction strengthening");); + PRIVATE_PARAMS(p.insert(":use-interpolants", CPK_BOOL, + "PDR: (default false) use iZ3 interpolation for lemma generation");); + PRIVATE_PARAMS(p.insert(":dump-interpolants", CPK_BOOL, + "PDR: (default false) display interpolants");); + p.insert(":cache-mode", CPK_UINT, "PDR: use no (0 - default) symbolic (1) or explicit cache (2) for model search"); + PRIVATE_PARAMS(p.insert(":inductive-reachability-check", CPK_BOOL, + "PDR: (default false) assume negation of the cube on the previous level when " + "checking for reachability (not only during cube weakening)");); + PRIVATE_PARAMS(p.insert(":max-num-contexts", CPK_UINT, + "PDR: (default 500) maximal number of contexts to create");); + PRIVATE_PARAMS(p.insert(":try-minimize-core", CPK_BOOL, + "PDR: (default false) try to reduce core size (before inductive minimization)");); + PRIVATE_PARAMS(p.insert(":simplify-formulas-pre", CPK_BOOL, + "PDR: (default false) simplify derived formulas before inductive propagation");); + PRIVATE_PARAMS(p.insert(":simplify-formulas-post", CPK_BOOL, + "PDR: (default false) simplify derived formulas after inductive propagation");); + PRIVATE_PARAMS(p.insert(":slice", CPK_BOOL, + "PDR: (default true) simplify clause set using slicing");); + p.insert(":generate-proof-trace", CPK_BOOL, + "PDR: (default false) trace for 'sat' answer as proof object"); + + +} diff --git a/lib/pdr_dl_interface.h b/lib/pdr_dl_interface.h new file mode 100644 index 000000000..994f4da07 --- /dev/null +++ b/lib/pdr_dl_interface.h @@ -0,0 +1,74 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + pdr_dl_interface.h + +Abstract: + + SMT2 interface for the datalog PDR + +Author: + + Krystof Hoder (t-khoder) 2011-9-22. + +Revision History: + +--*/ + +#ifndef _PDR_DL_INTERFACE_H_ +#define _PDR_DL_INTERFACE_H_ + +#include "lbool.h" +#include "dl_rule.h" +#include "dl_rule_set.h" +#include "statistics.h" + +namespace datalog { + class context; +} + +namespace pdr { + + class context; + + class dl_interface { + datalog::context& m_ctx; + datalog::rule_set m_pdr_rules; + datalog::rule_set m_old_rules; + context* m_context; + + void check_reset(); + + public: + dl_interface(datalog::context& ctx); + ~dl_interface(); + + lbool query(expr* query); + + void cancel(); + + void cleanup(); + + void display_certificate(std::ostream& out) const; + + void collect_statistics(statistics& st) const; + + expr_ref get_answer(); + + unsigned get_num_levels(func_decl* pred); + + expr_ref get_cover_delta(int level, func_decl* pred); + + void add_cover(int level, func_decl* pred, expr* property); + + static void collect_params(param_descrs& p); + + void updt_params(); + + }; +} + + +#endif diff --git a/lib/pdr_farkas_learner.cpp b/lib/pdr_farkas_learner.cpp new file mode 100644 index 000000000..4cb441cbd --- /dev/null +++ b/lib/pdr_farkas_learner.cpp @@ -0,0 +1,934 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + pdr_farkas_learner.cpp + +Abstract: + + Proviced abstract interface and some inplementations of algorithms + for strenghtning lemmas + +Author: + + Krystof Hoder (t-khoder) 2011-11-1. + +Revision History: + +--*/ + +#include "ast_smt2_pp.h" +#include "array_decl_plugin.h" +#include "bool_rewriter.h" +#include "dl_decl_plugin.h" +#include "for_each_expr.h" +#include "dl_util.h" +#include "rewriter.h" +#include "rewriter_def.h" +#include "pdr_util.h" +#include "pdr_farkas_learner.h" +#include "th_rewriter.h" +#include "smtparser.h" +#include "pdr_interpolant_provider.h" +#include "ast_ll_pp.h" +#include "arith_bounds_tactic.h" + +#define PROOF_MODE PGM_FINE +//#define PROOF_MODE PGM_COARSE + +namespace pdr { + + class farkas_learner::constr { + + ast_manager& m; + arith_util a; + app_ref_vector m_ineqs; + vector m_coeffs; + + void mk_coerce(expr*& e1, expr*& e2) { + if (a.is_int(e1) && a.is_real(e2)) { + e1 = a.mk_to_real(e1); + } + else if (a.is_int(e2) && a.is_real(e1)) { + e2 = a.mk_to_real(e2); + } + } + + app* mk_add(expr* e1, expr* e2) { + mk_coerce(e1, e2); + return a.mk_add(e1, e2); + } + + app* mk_le(expr* e1, expr* e2) { + mk_coerce(e1, e2); + return a.mk_le(e1, e2); + } + + app* mk_ge(expr* e1, expr* e2) { + mk_coerce(e1, e2); + return a.mk_ge(e1, e2); + } + + app* mk_gt(expr* e1, expr* e2) { + mk_coerce(e1, e2); + return a.mk_gt(e1, e2); + } + + app* mk_lt(expr* e1, expr* e2) { + mk_coerce(e1, e2); + return a.mk_lt(e1, e2); + } + + void mul(rational const& c, expr* e, expr_ref& res) { + expr_ref tmp(m); + if (c.is_one()) { + tmp = e; + } + else { + tmp = a.mk_mul(a.mk_numeral(c, a.is_int(e)), e); + } + res = mk_add(res, tmp); + } + + bool is_int_sort(app* c) { + SASSERT(m.is_eq(c) || a.is_le(c) || a.is_lt(c) || a.is_gt(c) || a.is_ge(c)); + SASSERT(a.is_int(c->get_arg(0)) || a.is_real(c->get_arg(0))); + return a.is_int(c->get_arg(0)); + } + + bool is_int_sort() { + SASSERT(!m_ineqs.empty()); + return is_int_sort(m_ineqs[0].get()); + } + + void normalize_coeffs() { + rational l(1); + for (unsigned i = 0; i < m_coeffs.size(); ++i) { + l = lcm(l, denominator(m_coeffs[i])); + } + if (!l.is_one()) { + for (unsigned i = 0; i < m_coeffs.size(); ++i) { + m_coeffs[i] *= l; + } + } + } + + app* mk_one() { + return a.mk_numeral(rational(1), true); + } + + app* fix_sign(bool is_pos, app* c) { + expr* x, *y; + SASSERT(m.is_eq(c) || a.is_le(c) || a.is_lt(c) || a.is_gt(c) || a.is_ge(c)); + bool is_int = is_int_sort(c); + if (is_int && is_pos && (a.is_lt(c, x, y) || a.is_gt(c, y, x))) { + return mk_le(mk_add(x, mk_one()), y); + } + if (is_int && !is_pos && (a.is_le(c, x, y) || a.is_ge(c, y, x))) { + // !(x <= y) <=> x > y <=> x >= y + 1 + return mk_ge(x, mk_add(y, mk_one())); + } + if (is_pos) { + return c; + } + if (a.is_le(c, x, y)) return mk_gt(x, y); + if (a.is_lt(c, x, y)) return mk_ge(x, y); + if (a.is_ge(c, x, y)) return mk_lt(x, y); + if (a.is_gt(c, x, y)) return mk_le(x, y); + UNREACHABLE(); + return c; + } + + public: + constr(ast_manager& m) : m(m), a(m), m_ineqs(m) {} + + /** add a multiple of constraint c to the current constr */ + void add(rational const & coef, app * c) { + bool is_pos = true; + expr* e; + while (m.is_not(c, e)) { + is_pos = !is_pos; + c = to_app(e); + } + + if (!coef.is_zero() && !m.is_true(c)) { + m_coeffs.push_back(coef); + m_ineqs.push_back(fix_sign(is_pos, c)); + } + } + + // + // Extract the complement of premises multiplied by Farkas coefficients. + // + void get(expr_ref& res) { + if (m_coeffs.empty()) { + res = m.mk_false(); + return; + } + bool is_int = is_int_sort(); + if (is_int) { + normalize_coeffs(); + } + TRACE("pdr", + for (unsigned i = 0; i < m_coeffs.size(); ++i) { + tout << m_coeffs[i] << ": " << mk_pp(m_ineqs[i].get(), m) << "\n"; + } + ); + app_ref zero(a.mk_numeral(rational::zero(), is_int), m); + res = zero; + bool is_strict = false; + bool is_eq = true; + expr* x, *y; + for (unsigned i = 0; i < m_coeffs.size(); ++i) { + app* c = m_ineqs[i].get(); + if (m.is_eq(c, x, y)) { + mul(m_coeffs[i], x, res); + mul(-m_coeffs[i], y, res); + } + if (a.is_lt(c, x, y) || a.is_gt(c, y, x)) { + mul(m_coeffs[i], x, res); + mul(-m_coeffs[i], y, res); + is_strict = true; + is_eq = false; + } + if (a.is_le(c, x, y) || a.is_ge(c, y, x)) { + mul(m_coeffs[i], x, res); + mul(-m_coeffs[i], y, res); + is_eq = false; + } + } + zero = a.mk_numeral(rational::zero(), a.is_int(res)); + if (is_eq) { + res = m.mk_eq(res, zero); + } + else if (is_strict) { + res = mk_lt(res, zero); + } + else { + res = mk_le(res, zero); + } + res = m.mk_not(res); + th_rewriter rw(m); + proof_ref pr(m); + expr_ref tmp(m); + rw(res, tmp, pr); + res = tmp; + } + }; + + farkas_learner::farkas_learner(front_end_params& params, ast_manager& outer_mgr) + : m_proof_params(get_proof_params(params)), + m(PROOF_MODE), + m_brwr(m), + p2o(m, outer_mgr), + o2p(outer_mgr, m), + m_simplifier(mk_arith_bounds_tactic(outer_mgr)) + { + m.register_decl_plugins(); + m_ctx = alloc(smt::solver, m, m_proof_params); + } + + front_end_params farkas_learner::get_proof_params(front_end_params& orig_params) { + front_end_params res(orig_params); + res.m_proof_mode = PROOF_MODE; + res.m_arith_bound_prop = BP_NONE; + // temp hack to fix the build + // res.m_conflict_resolution_strategy = CR_ALL_DECIDED; + res.m_arith_auto_config_simplex = true; + res.m_arith_propagate_eqs = false; + res.m_arith_eager_eq_axioms = false; + res.m_arith_eq_bounds = false; + return res; + } + + class farkas_learner::equality_expander_cfg : public default_rewriter_cfg + { + ast_manager& m; + arith_util m_ar; + public: + equality_expander_cfg(ast_manager& m) : m(m), m_ar(m) {} + + bool get_subst(expr * s, expr * & t, proof * & t_pr) { + expr * x, *y; + if (m.is_eq(s, x, y) && (m_ar.is_int(x) || m_ar.is_real(x))) { + t = m.mk_and(m_ar.mk_ge(x, y), m_ar.mk_le(x, y)); + return true; + } + else { + return false; + } + } + }; + + class collect_pure_proc { + func_decl_set& m_symbs; + public: + collect_pure_proc(func_decl_set& s):m_symbs(s) {} + + void operator()(app* a) { + if (a->get_family_id() == null_family_id) { + m_symbs.insert(a->get_decl()); + } + } + void operator()(var*) {} + void operator()(quantifier*) {} + }; + + + bool farkas_learner::get_lemma_guesses(expr * A_ext, expr * B_ext, expr_ref_vector& lemmas) + { + expr_ref A(o2p(A_ext), m); + expr_ref B(o2p(B_ext), m); + proof_ref pr(m); + expr_ref tmp(m); + expr_ref_vector ilemmas(m); + equality_expander_cfg ee_rwr_cfg(m); + rewriter_tpl ee_rwr(m, false, ee_rwr_cfg); + + lemmas.reset(); + + ee_rwr(A, A); + ee_rwr(B, B); + + expr_set bs; + func_decl_set Bsymbs; + expr_ref_vector blist(m); + datalog::flatten_and(B, blist); + for (unsigned i = 0; i < blist.size(); ++i) { + bs.insert(blist[i].get()); + } + collect_pure_proc collect_proc(Bsymbs); + for_each_expr(collect_proc, B); + + if (!m_ctx) { + m_ctx = alloc(smt::solver, m, m_proof_params); + } + + m_ctx->push(); + m_ctx->assert_expr(A); + expr_set::iterator it = bs.begin(), end = bs.end(); + for (; it != end; ++it) { + m_ctx->assert_expr(*it); + } + lbool res = m_ctx->check(); + bool is_unsat = res == l_false; + if (is_unsat) { + pr = m_ctx->get_proof(); + get_lemmas(m_ctx->get_proof(), bs, Bsymbs, A, ilemmas); + for (unsigned i = 0; i < ilemmas.size(); ++i) { + lemmas.push_back(p2o(ilemmas[i].get())); + } + simplify_lemmas(lemmas); + } + m_ctx->pop(1); + + IF_VERBOSE(3, { + for (unsigned i = 0; i < lemmas.size(); ++i) { + verbose_stream() << "B': " << mk_pp(ilemmas[i].get(), m) << "\n"; + } + }); + + TRACE("farkas_learner", + tout << (is_unsat?"unsat":"sat") << "\n"; + tout << "A: " << mk_pp(A_ext, m_ctx->m()) << "\n"; + tout << "B: " << mk_pp(B_ext, m_ctx->m()) << "\n"; + for (unsigned i = 0; i < lemmas.size(); ++i) { + tout << "B': " << mk_pp(ilemmas[i].get(), m) << "\n"; + }); + DEBUG_CODE( + if (is_unsat) { + m_ctx->push(); + m_ctx->assert_expr(A); + for (unsigned i = 0; i < ilemmas.size(); ++i) { + m_ctx->assert_expr(ilemmas[i].get()); + } + lbool res2 = m_ctx->check(); + SASSERT(l_false == res2); + m_ctx->pop(1); + } + ); + return is_unsat; + } + + // + // Perform simple subsumption check of lemmas. + // + void farkas_learner::simplify_lemmas(expr_ref_vector& lemmas) { + goal_ref g(alloc(goal, lemmas.get_manager(), false, false, false)); + TRACE("farkas_simplify_lemmas", + for (unsigned i = 0; i < lemmas.size(); ++i) { + tout << mk_pp(lemmas[i].get(), lemmas.get_manager()) << "\n"; + }); + + for (unsigned i = 0; i < lemmas.size(); ++i) { + g->assert_expr(lemmas[i].get()); + } + model_converter_ref mc; + proof_converter_ref pc; + expr_dependency_ref core(m); + goal_ref_buffer result; + (*m_simplifier)(g, result, mc, pc, core); + lemmas.reset(); + SASSERT(result.size() == 1); + goal* r = result[0]; + for (unsigned i = 0; i < r->size(); ++i) { + lemmas.push_back(r->form(i)); + } + } + + + void farkas_learner::combine_constraints(unsigned n, app * const * lits, rational const * coeffs, expr_ref& res) + { + constr res_c(m); + for(unsigned i = 0; i < n; ++i) { + res_c.add(coeffs[i], lits[i]); + } + res_c.get(res); + } + + class farkas_learner::constant_replacer_cfg : public default_rewriter_cfg + { + ast_manager& m; + const obj_map& m_translation; + public: + constant_replacer_cfg(ast_manager& m, const obj_map& translation) + : m(m), m_translation(translation) + { } + + bool get_subst(expr * s, expr * & t, proof * & t_pr) { + return m_translation.find(s, t); + } + }; + + // every uninterpreted symbol is in symbs + class is_pure_expr_proc { + func_decl_set const& m_symbs; + public: + struct non_pure {}; + + is_pure_expr_proc(func_decl_set const& s):m_symbs(s) {} + + void operator()(app* a) { + if (a->get_family_id() == null_family_id) { + if (!m_symbs.contains(a->get_decl())) { + throw non_pure(); + } + } + } + void operator()(var*) {} + void operator()(quantifier*) {} + }; + + bool farkas_learner::is_pure_expr(func_decl_set const& symbs, expr* e) const { + is_pure_expr_proc proc(symbs); + try { + for_each_expr(proc, e); + } + catch (is_pure_expr_proc::non_pure) { + return false; + } + return true; + }; + + + /** + Revised version of Farkas strengthener. + 1. Mark B-pure nodes as derivations that depend only on B. + 2. Collect B-influenced nodes + 3. (optional) Permute B-pure units over resolution steps to narrow dependencies on B. + 4. Weaken B-pure units for resolution with Farkas Clauses. + 5. Add B-pure units elsewhere. + + Rules: + - hypothesis h |- h + + H |- false + - lemma ---------- + |- not H + + Th |- L \/ C H |- not L + - th-lemma ------------------------- + H |- C + + Note: C is false for theory axioms, C is unit literal for propagation. + + - rewrite |- t = s + + H |- t = s + - monotonicity ---------------- + H |- f(t) = f(s) + + H |- t = s H' |- s = u + - trans ---------------------- + H, H' |- t = u + + H |- C \/ L H' |- not L + - unit_resolve ------------------------ + H, H' |- C + + H |- a ~ b H' |- a + - mp -------------------- + H, H' |- b + + - def-axiom |- C + + - asserted |- f + + Mark nodes by: + - Hypotheses + - Dependency on bs + - Dependency on A + + A node is unit derivable from bs if: + - It has no hypotheses. + - It depends on bs. + - It does not depend on A. + + NB: currently unit derivable is not symmetric: A clause can be + unit derivable, but a unit literal with hypotheses is not. + This is clearly wrong, because hypotheses are just additional literals + in a clausal version. + + NB: the routine is not interpolating, though an interpolating variant would + be preferrable because then we can also use it for model propagation. + + We collect the unit derivable nodes from bs. + These are the weakenings of bs, besides the + units under Farkas. + + */ + void farkas_learner::get_lemmas(proof* root, expr_set const& bs, func_decl_set const& Bsymbs, expr* A, expr_ref_vector& lemmas) { + + proof_ref pr(root, m); + permute_unit_resolution(pr); + + ptr_vector hyprefs; + obj_map hypmap; + ast_mark b_depend, a_depend, visited, b_closed; + expr_set* empty_set = alloc(expr_set); + hyprefs.push_back(empty_set); + ptr_vector todo; + TRACE("pdr_verbose", tout << mk_pp(pr, m) << "\n";); + todo.push_back(pr); + while (!todo.empty()) { + proof* p = todo.back(); + SASSERT(m.is_proof(p)); + if (visited.is_marked(p)) { + todo.pop_back(); + continue; + } + bool all_visit = true; + for (unsigned i = 0; i < m.get_num_parents(p); ++i) { + expr* arg = p->get_arg(i); + SASSERT(m.is_proof(arg)); + if (!visited.is_marked(arg)) { + all_visit = false; + todo.push_back(to_app(arg)); + } + } + if (!all_visit) { + continue; + } + visited.mark(p, true); + todo.pop_back(); + + // retrieve hypotheses and dependencies on A, bs. + bool b_dep = false, a_dep = false; + expr_set* hyps = empty_set; + for (unsigned i = 0; i < m.get_num_parents(p); ++i) { + expr* arg = p->get_arg(i); + a_dep = a_dep || a_depend.is_marked(arg); + b_dep = b_dep || b_depend.is_marked(arg); + expr_set* hyps2 = hypmap.find(arg); + if (hyps != hyps2 && !hyps2->empty()) { + if (hyps->empty()) { + hyps = hyps2; + } + else { + expr_set* hyps3 = alloc(expr_set); + datalog::set_union(*hyps3, *hyps); + datalog::set_union(*hyps3, *hyps2); + hyps = hyps3; + hyprefs.push_back(hyps); + } + } + } + hypmap.insert(p, hyps); + a_depend.mark(p, a_dep); + b_depend.mark(p, b_dep); + +#define IS_B_PURE(_p) (b_depend.is_marked(_p) && !a_depend.is_marked(_p) && hypmap.find(_p)->empty()) + + // Add lemmas that depend on bs, have no hypotheses, don't depend on A. + if ((!hyps->empty() || a_depend.is_marked(p)) && + b_depend.is_marked(p) && !is_farkas_lemma(p)) { + for (unsigned i = 0; i < m.get_num_parents(p); ++i) { + app* arg = to_app(p->get_arg(i)); + if (IS_B_PURE(arg)) { + if (is_pure_expr(Bsymbs, m.get_fact(arg))) { + TRACE("farkas_learner", + tout << "Add: " << mk_pp(m.get_fact(arg), m) << "\n"; + tout << mk_pp(arg, m) << "\n"; + ); + lemmas.push_back(m.get_fact(arg)); + } + else { + get_asserted(p, bs, b_closed, lemmas); + b_closed.mark(p, true); + } + } + } + } + + switch(p->get_decl_kind()) { + case PR_ASSERTED: + if (bs.contains(m.get_fact(p))) { + b_depend.mark(p, true); + } + else { + SASSERT(m.get_fact(p) == A); + a_depend.mark(p, true); + } + break; + case PR_HYPOTHESIS: { + SASSERT(hyps == empty_set); + hyps = alloc(expr_set); + hyps->insert(m.get_fact(p)); + hyprefs.push_back(hyps); + hypmap.insert(p, hyps); + break; + } + case PR_DEF_AXIOM: { + if (!is_pure_expr(Bsymbs, m.get_fact(p))) { + a_depend.mark(p, true); + } + break; + } + case PR_LEMMA: { + expr_set* hyps2 = alloc(expr_set); + hyprefs.push_back(hyps2); + datalog::set_union(*hyps2, *hyps); + hyps = hyps2; + expr* fml = m.get_fact(p); + hyps->remove(fml); + if (m.is_or(fml)) { + for (unsigned i = 0; i < to_app(fml)->get_num_args(); ++i) { + expr* f = to_app(fml)->get_arg(i); + expr_ref hyp(m); + m_brwr.mk_not(f, hyp); + hyps->remove(hyp); + } + } + hypmap.insert(p, hyps); + break; + } + case PR_TH_LEMMA: { + if (!is_farkas_lemma(p)) break; + + SASSERT(m.has_fact(p)); + unsigned prem_cnt = m.get_num_parents(p); + func_decl * d = p->get_decl(); + SASSERT(d->get_num_parameters() >= prem_cnt+2); + SASSERT(d->get_parameter(0).get_symbol() == "arith"); + SASSERT(d->get_parameter(1).get_symbol() == "farkas"); + parameter const* params = d->get_parameters() + 2; + + app_ref_vector lits(m); + expr_ref tmp(m); + unsigned num_b_pures = 0; + rational coef; + vector coeffs; + + TRACE("farkas_learner", + for (unsigned i = 0; i < prem_cnt; ++i) { + VERIFY(params[i].is_rational(coef)); + proof* prem = to_app(p->get_arg(i)); + bool b_pure = IS_B_PURE(prem); + tout << (b_pure?"B":"A") << " " << coef << " " << mk_pp(m.get_fact(prem), m) << "\n"; + } + tout << mk_pp(m.get_fact(p), m) << "\n"; + ); + + // NB. Taking 'abs' of coefficients is a workaround. + // The Farkas coefficient extraction in arith_core must be wrong. + // The coefficients would be always positive relative to the theory lemma. + + for(unsigned i = 0; i < prem_cnt; ++i) { + expr * prem_e = p->get_arg(i); + SASSERT(is_app(prem_e)); + proof * prem = to_app(prem_e); + + if(IS_B_PURE(prem)) { + ++num_b_pures; + } + else { + VERIFY(params[i].is_rational(coef)); + lits.push_back(to_app(m.get_fact(prem))); + coeffs.push_back(abs(coef)); + } + } + params += prem_cnt; + if (prem_cnt + 2 < d->get_num_parameters()) { + unsigned num_args = 1; + expr* fact = m.get_fact(p); + expr* const* args = &fact; + if (m.is_or(fact)) { + app* _or = to_app(fact); + num_args = _or->get_num_args(); + args = _or->get_args(); + } + SASSERT(prem_cnt + 2 + num_args == d->get_num_parameters()); + for (unsigned i = 0; i < num_args; ++i) { + expr* prem_e = args[i]; + m_brwr.mk_not(prem_e, tmp); + VERIFY(params[i].is_rational(coef)); + SASSERT(is_app(tmp)); + lits.push_back(to_app(tmp)); + coeffs.push_back(abs(coef)); + } + + } + SASSERT(coeffs.size() == lits.size()); + if (num_b_pures > 0) { + expr_ref res(m); + combine_constraints(coeffs.size(), lits.c_ptr(), coeffs.c_ptr(), res); + TRACE("farkas_learner", tout << "Add: " << mk_pp(res, m) << "\n";); + lemmas.push_back(res); + b_closed.mark(p, true); + } + } + default: + break; + } + } + + std::for_each(hyprefs.begin(), hyprefs.end(), delete_proc()); + } + + void farkas_learner::get_asserted(proof* p, expr_set const& bs, ast_mark& b_closed, expr_ref_vector& lemmas) { + ast_mark visited; + proof* p0 = p; + ptr_vector todo; + todo.push_back(p); + + while (!todo.empty()) { + p = todo.back(); + todo.pop_back(); + if (visited.is_marked(p) || b_closed.is_marked(p)) { + continue; + } + visited.mark(p, true); + for (unsigned i = 0; i < m.get_num_parents(p); ++i) { + expr* arg = p->get_arg(i); + SASSERT(m.is_proof(arg)); + todo.push_back(to_app(arg)); + } + if (p->get_decl_kind() == PR_ASSERTED && + bs.contains(m.get_fact(p))) { + TRACE("farkas_learner", + tout << mk_ll_pp(p0,m) << "\n"; + tout << "Add: " << mk_pp(p,m) << "\n";); + lemmas.push_back(m.get_fact(p)); + b_closed.mark(p, true); + } + } + } + + // permute unit resolution over Theory lemmas to track premises. + void farkas_learner::permute_unit_resolution(proof_ref& pr) { + expr_ref_vector refs(m); + obj_map cache; + permute_unit_resolution(refs, cache, pr); + } + void farkas_learner::permute_unit_resolution(expr_ref_vector& refs, obj_map& cache, proof_ref& pr) { + proof* pr2 = 0; + proof_ref_vector parents(m); + proof_ref prNew(pr); + if (cache.find(pr, pr2)) { + pr = pr2; + return; + } + + for (unsigned i = 0; i < m.get_num_parents(pr); ++i) { + prNew = m.get_parent(pr, i); + permute_unit_resolution(refs, cache, prNew); + parents.push_back(prNew); + } + + prNew = pr; + if (pr->get_decl_kind() == PR_UNIT_RESOLUTION && + parents[0]->get_decl_kind() == PR_TH_LEMMA) { + /* + Unit resolution: + T1: (or l_1 ... l_n l_1' ... l_m') + T2: (not l_1) + ... + T(n+1): (not l_n) + [unit-resolution T1 ... T(n+1)]: (or l_1' ... l_m') + Th lemma: + T1: (not l_1) + ... + Tn: (not l_n) + [th-lemma T1 ... Tn]: (or l_{n+1} ... l_m) + + Such that (or l_1 .. l_n l_{n+1} .. l_m) is a theory axiom. + + Implement conversion: + + T1 |- not l_1 ... Tn |- not l_n + ------------------------------- TH_LEMMA + (or k_1 .. k_m j_1 ... j_m) S1 |- not k_1 ... Sm |- not k_m + -------------------------------------------------------------- UNIT_RESOLUTION + (or j_1 .. j_m) + + + |-> + + T1 |- not l_1 ... Tn |- not l_n S1 |- not k_1 ... Sm |- not k_m + ---------------------------------------------------------------- TH_LEMMA + (or j_1 .. j_m) + + */ + proof_ref_vector premises(m); + proof* thLemma = parents[0].get(); + for (unsigned i = 0; i < m.get_num_parents(thLemma); ++i) { + premises.push_back(m.get_parent(thLemma, i)); + } + for (unsigned i = 1; i < parents.size(); ++i) { + premises.push_back(parents[i].get()); + } + parameter const* params = thLemma->get_decl()->get_parameters(); + unsigned num_params = thLemma->get_decl()->get_num_parameters(); + SASSERT(params[0].is_symbol()); + family_id tid = m.get_family_id(params[0].get_symbol()); + SASSERT(tid != null_family_id); + prNew = m.mk_th_lemma(tid, m.get_fact(pr), + premises.size(), premises.c_ptr(), num_params-1, params+1); + } + else { + ptr_vector args; + for (unsigned i = 0; i < parents.size(); ++i) { + args.push_back(parents[i].get()); + } + if (m.has_fact(pr)) { + args.push_back(m.get_fact(pr)); + } + prNew = m.mk_app(pr->get_decl(), args.size(), args.c_ptr()); + } + + cache.insert(pr, prNew); + refs.push_back(prNew); + pr = prNew; + } + + + bool farkas_learner::is_farkas_lemma(expr* e) { + app * a; + func_decl* d; + symbol sym; + return + is_app(e) && + (a = to_app(e), d = a->get_decl(), true) && + PR_TH_LEMMA == a->get_decl_kind() && + d->get_num_parameters() >= 2 && + d->get_parameter(0).is_symbol(sym) && sym == "arith" && + d->get_parameter(1).is_symbol(sym) && sym == "farkas" && + d->get_num_parameters() >= m.get_num_parents(to_app(e)) + 2; + }; + + + void farkas_learner::test() { + front_end_params params; + enable_trace("farkas_learner"); + + bool res; + ast_manager m; + m.register_decl_plugins(); + arith_util a(m); + pdr::farkas_learner fl(params, m); + expr_ref_vector lemmas(m); + + sort_ref int_s(a.mk_int(), m); + expr_ref x(m.mk_const(symbol("x"), int_s), m); + expr_ref y(m.mk_const(symbol("y"), int_s), m); + expr_ref z(m.mk_const(symbol("z"), int_s), m); + expr_ref u(m.mk_const(symbol("u"), int_s), m); + expr_ref v(m.mk_const(symbol("v"), int_s), m); + + // A: x > y >= z + // B: x < z + // Farkas: x <= z + expr_ref A(m.mk_and(a.mk_gt(x,y), a.mk_ge(y,z)),m); + expr_ref B(a.mk_gt(z,x),m); + res = fl.get_lemma_guesses(A, B, lemmas); + std::cout << "\nres: " << res << "\nlemmas: " << pp_cube(lemmas, m) << "\n"; + + // A: x > y >= z + 2 + // B: x = 1, z = 8 + // Farkas: x <= z + 2 + expr_ref one(a.mk_numeral(rational(1), true), m); + expr_ref two(a.mk_numeral(rational(2), true), m); + expr_ref eight(a.mk_numeral(rational(8), true), m); + A = m.mk_and(a.mk_gt(x,y),a.mk_ge(y,a.mk_add(z,two))); + B = m.mk_and(m.mk_eq(x,one), m.mk_eq(z, eight)); + res = fl.get_lemma_guesses(A, B, lemmas); + std::cout << "\nres: " << res << "\nlemmas: " << pp_cube(lemmas, m) << "\n"; + + // A: x > y >= z \/ x >= u > z + // B: z > x + 1 + // Farkas: z >= x + A = m.mk_or(m.mk_and(a.mk_gt(x,y),a.mk_ge(y,z)),m.mk_and(a.mk_ge(x,u),a.mk_gt(u,z))); + B = a.mk_gt(z, a.mk_add(x,one)); + res = fl.get_lemma_guesses(A, B, lemmas); + std::cout << "\nres: " << res << "\nlemmas: " << pp_cube(lemmas, m) << "\n"; + + // A: (x > y >= z \/ x >= u > z \/ u > v) + // B: z > x + 1 & not (u > v) + // Farkas: z >= x & not (u > v) + A = m.mk_or(m.mk_and(a.mk_gt(x,y),a.mk_ge(y,z)),m.mk_and(a.mk_ge(x,u),a.mk_gt(u,z)), a.mk_gt(u, v)); + B = m.mk_and(a.mk_gt(z, a.mk_add(x,one)), m.mk_not(a.mk_gt(u, v))); + res = fl.get_lemma_guesses(A, B, lemmas); + std::cout << "\nres: " << res << "\nlemmas: " << pp_cube(lemmas, m) << "\n"; + + } + + void farkas_learner::collect_statistics(statistics& st) const { + if (m_ctx) { + m_ctx->collect_statistics(st); + } + } + + + void farkas_learner::test(char const* filename) { + if (!filename) { + test(); + return; + } + ast_manager m; + m.register_decl_plugins(); + scoped_ptr p = smtlib::parser::create(m); + p->initialize_smtlib(); + + if (!p->parse_file(filename)) { + warning_msg("Failed to parse file %s\n", filename); + return; + } + expr_ref A(m), B(m); + + smtlib::theory::expr_iterator it = p->get_benchmark()->begin_axioms(); + smtlib::theory::expr_iterator end = p->get_benchmark()->end_axioms(); + A = m.mk_and(static_cast(end-it), it); + + it = p->get_benchmark()->begin_formulas(); + end = p->get_benchmark()->end_formulas(); + B = m.mk_and(static_cast(end-it), it); + + front_end_params params; + pdr::farkas_learner fl(params, m); + expr_ref_vector lemmas(m); + bool res = fl.get_lemma_guesses(A, B, lemmas); + std::cout << "lemmas: " << pp_cube(lemmas, m) << "\n"; + } +}; + diff --git a/lib/pdr_farkas_learner.h b/lib/pdr_farkas_learner.h new file mode 100644 index 000000000..f90419908 --- /dev/null +++ b/lib/pdr_farkas_learner.h @@ -0,0 +1,112 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + pdr_farkas_learner.h + +Abstract: + + SMT2 interface for the datalog PDR + +Author: + + Krystof Hoder (t-khoder) 2011-11-1. + +Revision History: + +--*/ + +#ifndef _PDR_FARKAS_LEARNER_H_ +#define _PDR_FARKAS_LEARNER_H_ + +#include "arith_decl_plugin.h" +#include "ast_translation.h" +#include "bv_decl_plugin.h" +#include "smt_solver.h" +#include "pdr_manager.h" +#include "bool_rewriter.h" +#include "pdr_util.h" +#include "front_end_params.h" +#include "tactic.h" + + +namespace pdr { + +class farkas_learner { + class farkas_collector; + class asserted_premise_collector; + class constant_replacer_cfg; + class equality_expander_cfg; + class constr; + + typedef obj_hashtable expr_set; + + front_end_params m_proof_params; + ast_manager m; + bool_rewriter m_brwr; /** bool rewriter for m_proof_mgr */ + scoped_ptr m_ctx; + scoped_ptr m_simplifier; + + + static front_end_params get_proof_params(front_end_params& orig_params); + + // + // all ast objects passed to private functions have m_proof_mgs as their ast_manager + // + + ast_translation p2o; /** Translate expression from inner ast_manager to outer one */ + ast_translation o2p; /** Translate expression from outer ast_manager to inner one */ + + + /** All ast opbjects here are in the m_proof_mgs */ + void get_lemma_guesses_internal(proof * p, expr* A, expr * B, expr_ref_vector& lemmas); + + bool farkas2lemma(proof * fstep, expr* A, expr * B, expr_ref& res); + + void combine_constraints(unsigned cnt, app * const * constrs, rational const * coeffs, expr_ref& res); + + bool try_ensure_lemma_in_language(expr_ref& lemma, expr* A, const func_decl_set& lang); + + bool is_farkas_lemma(expr* e); + + void get_lemmas(proof* root, expr_set const& bs, func_decl_set const& Bsymbs, expr* A, expr_ref_vector& lemmas); + + void get_asserted(proof* p, expr_set const& bs, ast_mark& b_closed, expr_ref_vector& lemmas); + + void permute_unit_resolution(proof_ref& pr); + + void permute_unit_resolution(expr_ref_vector& refs, obj_map& cache, proof_ref& pr); + + bool is_pure_expr(func_decl_set const& symbs, expr* e) const; + + static void test(); + + void simplify_lemmas(expr_ref_vector& lemmas); + +public: + farkas_learner(front_end_params& params, ast_manager& m); + + /** + All ast objects have the ast_manager which was passed as + an argument to the constructor (i.e. m_outer_mgr) + + B is a conjunction of literals. + A && B is unsat, equivalently A => ~B is valid + Find a weakened B' such that + A && B' is unsat and B' uses vocabulary (and constants) in common with A. + return lemmas to weaken B. + */ + + bool get_lemma_guesses(expr * A, expr * B, expr_ref_vector& lemmas); + + void collect_statistics(statistics& st) const; + + static void test(char const* filename); + +}; + + +} + +#endif diff --git a/lib/pdr_generalizers.cpp b/lib/pdr_generalizers.cpp new file mode 100644 index 000000000..445b60665 --- /dev/null +++ b/lib/pdr_generalizers.cpp @@ -0,0 +1,807 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + pdr_generalizers.cpp + +Abstract: + + Generalizers of satisfiable states and unsat cores. + +Author: + + Nikolaj Bjorner (nbjorner) 2011-11-20. + +Revision History: + +--*/ + + +#include "pdr_context.h" +#include "pdr_farkas_learner.h" +#include "pdr_interpolant_provider.h" +#include "pdr_generalizers.h" +#include "expr_abstract.h" +#include "var_subst.h" + + +namespace pdr { + + static void solve_for_next_vars(expr_ref& F, model_node& n, expr_substitution& sub) { + ast_manager& m = F.get_manager(); + manager& pm = n.pt().get_pdr_manager(); + const model_core & mdl = n.model(); + unsigned sz = mdl.get_num_constants(); + expr_ref_vector refs(m); + + for (unsigned i = 0; i < sz; i++) { + func_decl * d = mdl.get_constant(i); + expr_ref interp(m); + ptr_vector cs; + if (m.is_bool(d->get_range())) { + get_value_from_model(mdl, d, interp); + app* c = m.mk_const(d); + refs.push_back(c); + refs.push_back(interp); + sub.insert(c, interp); + } + } + scoped_ptr rep = mk_default_expr_replacer(m); + rep->set_substitution(&sub); + (*rep)(F); + th_rewriter rw(m); + rw(F); + ptr_vector todo; + todo.push_back(F); + expr* e1, *e2; + while (!todo.empty()) { + expr* e = todo.back(); + todo.pop_back(); + if (m.is_and(e)) { + todo.append(to_app(e)->get_num_args(), to_app(e)->get_args()); + } + else if ((m.is_eq(e, e1, e2) && pm.is_n(e1) && pm.is_o_formula(e2)) || + (m.is_eq(e, e2, e1) && pm.is_n(e1) && pm.is_o_formula(e2))) { + sub.insert(e1, e2); + TRACE("pdr", tout << mk_pp(e1, m) << " |-> " << mk_pp(e2, m) << "\n";); + } + } + } + + // + // eliminate conjuncts from cube as long as state is satisfied. + // + void model_evaluation_generalizer::operator()(model_node& n, expr_ref_vector& cube) { + ptr_vector forms; + forms.push_back(n.state()); + forms.push_back(n.pt().transition()); + m_model_evaluator.minimize_model(forms, cube); + } + + // + // eliminate conjuncts from cube as long as state is satisfied. + // + void bool_model_evaluation_generalizer::operator()(model_node& n, expr_ref_vector& cube) { + ptr_vector forms; + forms.push_back(n.state()); + forms.push_back(n.pt().transition()); + m_model_evaluator.minimize_model(forms, cube); + } + + // + // main propositional induction generalizer. + // drop literals one by one from the core and check if the core is still inductive. + // + void core_bool_inductive_generalizer::operator()(model_node& n, expr_ref_vector& core, bool& uses_level) { + if (core.size() <= 1) { + return; + } + ast_manager& m = core.get_manager(); + TRACE("pdr", for (unsigned i = 0; i < core.size(); ++i) { tout << mk_pp(core[i].get(), m) << "\n"; } "\n";); + unsigned num_failures = 0, i = 0, num_changes = 0; + while (i < core.size() && (!m_failure_limit || num_failures <= m_failure_limit)) { + expr_ref lit(m), state(m); + lit = core[i].get(); + core[i] = m.mk_true(); + state = m.mk_not(n.pt().get_pdr_manager().mk_and(core)); + bool assumes_level = false; + if (n.pt().is_invariant(n.level(), state, true, assumes_level, 0)) { + num_failures = 0; + core[i] = core.back(); + core.pop_back(); + TRACE("pdr", tout << "Remove: " << mk_pp(lit, m) << "\n";); + ++num_changes; + uses_level = assumes_level; + } + else { + core[i] = lit; + ++num_failures; + ++i; + } + } + TRACE("pdr", tout << "changes: " << num_changes << " index: " << i << " size: " << core.size() << "\n";); + } + + // + // extract multiple cores from unreachable state. + // + void core_multi_generalizer::operator()(model_node& n, expr_ref_vector& core, bool& uses_level) { + ast_manager& m = core.get_manager(); + manager& pm = n.pt().get_pdr_manager(); + expr_ref_vector states(m), cores(m); + datalog::flatten_and(n.state(), states); + obj_hashtable core_exprs; + for (unsigned i = 0; i < core.size(); ++i) { + core_exprs.insert(core[i].get()); + } + TRACE("pdr", + tout << "Old core:\n"; for (unsigned i = 0; i < core.size(); ++i) tout << mk_pp(core[i].get(), m) << "\n"; + tout << "State:\n"; for (unsigned i = 0; i < states.size(); ++i) tout << mk_pp(states[i].get(), m) << "\n"; + ); + cores.push_back(pm.mk_and(core)); + bool change = false; + for (unsigned i = 0; i < states.size(); ++i) { + expr_ref tmp(states[i].get(), m), new_state(m); + expr_ref_vector new_core(m); + if (core_exprs.contains(tmp.get())) { + states[i] = m.mk_true(); + new_state = m.mk_not(pm.mk_and(states)); + bool assumes_level; + if (n.pt().is_invariant(n.level(), new_state, false, assumes_level, &new_core)) { +#if 0 + for (unsigned j = 0; j < new_core.size(); ++j) { + core_exprs.insert(new_core[j].get()); + } +#endif + if (assumes_level) { + uses_level = true; + } + tmp = pm.mk_and(new_core); + TRACE("pdr", tout << "New core:\n" << mk_pp(tmp, m) << "\n";); + cores.push_back(tmp); + change = true; + } + else { + states[i] = tmp; + } + } + } + if (change) { + core.reset(); + core.push_back(pm.mk_or(cores)); + TRACE("pdr", tout << "New Cores:\n" << mk_pp(core[0].get(), m) << "\n";); + } + } + + // + // for each disjunct of core: + // weaken predecessor. + // + + core_farkas_generalizer::core_farkas_generalizer(context& ctx, ast_manager& m, front_end_params& p): + core_generalizer(ctx), + m_farkas_learner(p, m) + {} + + void core_farkas_generalizer::operator()(model_node& n, expr_ref_vector& core, bool& uses_level) { + front_end_params& p = m_ctx.get_fparams(); + ast_manager& m = n.pt().get_manager(); + manager& pm = n.pt().get_pdr_manager(); + if (core.empty()) return; + expr_ref A(m), B(pm.mk_and(core)), C(m); + expr_ref_vector Bs(m); + pm.get_or(B, Bs); + A = n.pt().get_propagation_formula(m_ctx.get_pred_transformers(), n.level()); + + bool change = false; + for (unsigned i = 0; i < Bs.size(); ++i) { + expr_ref_vector lemmas(m); + C = Bs[i].get(); + if (m_farkas_learner.get_lemma_guesses(A, B, lemmas)) { + TRACE("pdr", + tout << "Old core:\n" << mk_pp(B, m) << "\n"; + tout << "New core:\n" << mk_pp(pm.mk_and(lemmas), m) << "\n";); + Bs[i] = pm.mk_and(lemmas); + change = true; + } + } + if (change) { + C = pm.mk_or(Bs); + TRACE("pdr", tout << "prop:\n" << mk_pp(A,m) << "\ngen:" << mk_pp(B, m) << "\nto: " << mk_pp(C, m) << "\n";); + core.reset(); + datalog::flatten_and(C, core); + uses_level = true; + } + } + + void core_farkas_generalizer::collect_statistics(statistics& st) const { + m_farkas_learner.collect_statistics(st); + } + + void model_precond_generalizer::operator()(model_node& n, expr_ref_vector& cube) { + ast_manager& m = n.pt().get_manager(); + manager& pm = n.pt().get_pdr_manager(); + expr_ref A(m), state(m); + expr_ref_vector states(m); + A = n.pt().get_formulas(n.level(), true); + + // extract substitution for next-state variables. + expr_substitution sub(m); + solve_for_next_vars(A, n, sub); + scoped_ptr rep = mk_default_expr_replacer(m); + rep->set_substitution(&sub); + A = m.mk_and(A, n.state()); + (*rep)(A); + + datalog::flatten_and(A, states); + + for (unsigned i = 0; i < states.size(); ++i) { + expr* s = states[i].get(); + if (pm.is_o_formula(s) && pm.is_homogenous_formula(s)) { + cube.push_back(s); + } + } + TRACE("pdr", for (unsigned i = 0; i < cube.size(); ++i) tout << mk_pp(cube[i].get(), m) << "\n";); + } + + /** + < F, phi, i + 1 > + / \ + < G, psi, i > < H, theta, i > + core + + Given: + 1. psi => core + 2. Gi => not core + 3. phi & psi & theta => F_{i+1} + + Then, by weakening 2: + Gi => (F_{i+1} => not (phi & core & theta)) + + Find interpolant I, such that + + Gi => I, I => (F_{i+1} => not (phi & core' & theta')) + + where core => core', theta => theta' + + This implementation checks if + + Gi => (F_{i+1} => not (phi & theta)) + + */ + void core_interpolant_generalizer::operator()(model_node& n, expr_ref_vector& core, bool& uses_level) { + if (!n.parent()) { + return; + } + manager& pm = n.pt().get_pdr_manager(); + ast_manager& m = n.pt().get_manager(); + model_node& p = *n.parent(); + + // find index of node into parent. + unsigned index = 0; + for (; index < p.children().size() && (&n != p.children()[index]); ++index); + SASSERT(index < p.children().size()); + + expr_ref G(m), F(m), r(m), B(m), I(m), cube(m); + expr_ref_vector fmls(m); + + F = p.pt().get_formulas(p.level(), true); + G = n.pt().get_formulas(n.level(), true); + pm.formula_n2o(index, false, G); + + // get formulas from siblings. + for (unsigned i = 0; i < p.children().size(); ++i) { + if (i != index) { + pm.formula_n2o(p.children()[i]->state(), r, i, true); + fmls.push_back(r); + } + } + fmls.push_back(F); + fmls.push_back(p.state()); + B = pm.mk_and(fmls); + + // when G & B is unsat, find I such that G => I, I => not B + lbool res = pm.get_interpolator().get_interpolant(G, B, I); + + TRACE("pdr", + tout << "Interpolating:\n" << mk_pp(G, m) << "\n" << mk_pp(B, m) << "\n"; + if (res == l_true) tout << mk_pp(I, m) << "\n"; else tout << "failed\n";); + + if(res == l_true) { + pm.formula_o2n(I, cube, index, true); + TRACE("pdr", tout << "After renaming: " << mk_pp(cube, m) << "\n";); + core.reset(); + datalog::flatten_and(cube, core); + uses_level = true; + } + } + + + // + // < F, phi, i + 1> + // | + // < G, psi, i > + // + // where: + // + // p(x) <- F(x,y,p,q) + // q(x) <- G(x,y) + // + // Hyp: + // Q_k(x) => phi(x) j <= k <= i + // Q_k(x) => R_k(x) j <= k <= i + 1 + // Q_k(x) <=> Trans(Q_{k-1}) j < k <= i + 1 + // Conclusion: + // Q_{i+1}(x) => phi(x) + // + class core_induction_generalizer::imp { + context& m_ctx; + manager& pm; + ast_manager& m; + + // + // Create predicate Q_level + // + func_decl_ref mk_pred(unsigned level, func_decl* f) { + func_decl_ref result(m); + std::ostringstream name; + name << f->get_name() << "_" << level; + symbol sname(name.str().c_str()); + result = m.mk_func_decl(sname, f->get_arity(), f->get_domain(), f->get_range()); + return result; + } + + // + // Create formula exists y . z . F[Q_{level-1}, x, y, z] + // + expr_ref mk_transition_rule( + expr_ref_vector const& reps, + unsigned level, + datalog::rule const& rule) + { + expr_ref_vector conj(m), sub(m); + expr_ref result(m); + ptr_vector sorts; + svector names; + unsigned ut_size = rule.get_uninterpreted_tail_size(); + unsigned t_size = rule.get_tail_size(); + if (0 == level && 0 < ut_size) { + result = m.mk_false(); + return result; + } + app* atom = rule.get_head(); + SASSERT(atom->get_num_args() == reps.size()); + + for (unsigned i = 0; i < reps.size(); ++i) { + expr* arg = atom->get_arg(i); + if (is_var(arg)) { + unsigned idx = to_var(arg)->get_idx(); + if (idx >= sub.size()) sub.resize(idx+1); + if (sub[idx].get()) { + conj.push_back(m.mk_eq(sub[idx].get(), reps[i])); + } + else { + sub[idx] = reps[i]; + } + } + else { + conj.push_back(m.mk_eq(arg, reps[i])); + } + } + for (unsigned i = 0; 0 < level && i < ut_size; i++) { + app* atom = rule.get_tail(i); + func_decl* head = atom->get_decl(); + func_decl_ref fn = mk_pred(level-1, head); + conj.push_back(m.mk_app(fn, atom->get_num_args(), atom->get_args())); + } + for (unsigned i = ut_size; i < t_size; i++) { + conj.push_back(rule.get_tail(i)); + } + result = pm.mk_and(conj); + if (!sub.empty()) { + expr_ref tmp = result; + var_subst(m, false)(tmp, sub.size(), sub.c_ptr(), result); + } + get_free_vars(result, sorts); + for (unsigned i = 0; i < sorts.size(); ++i) { + if (!sorts[i]) { + sorts[i] = m.mk_bool_sort(); + } + names.push_back(symbol(sorts.size() - i - 1)); + } + if (!sorts.empty()) { + sorts.reverse(); + result = m.mk_exists(sorts.size(), sorts.c_ptr(), names.c_ptr(), result); + } + return result; + } + + expr_ref bind_head(expr_ref_vector const& reps, expr* fml) { + expr_ref result(m); + expr_abstract(m, 0, reps.size(), reps.c_ptr(), fml, result); + ptr_vector sorts; + svector names; + unsigned sz = reps.size(); + for (unsigned i = 0; i < sz; ++i) { + sorts.push_back(m.get_sort(reps[sz-i-1])); + names.push_back(symbol(sz-i-1)); + } + if (sz > 0) { + result = m.mk_forall(sorts.size(), sorts.c_ptr(), names.c_ptr(), result); + } + return result; + } + + expr_ref_vector mk_reps(pred_transformer& pt) { + expr_ref_vector reps(m); + expr_ref rep(m); + for (unsigned i = 0; i < pt.head()->get_arity(); ++i) { + rep = m.mk_const(pm.o2n(pt.sig(i), 0)); + reps.push_back(rep); + } + return reps; + } + + // + // extract transition axiom: + // + // forall x . p_lvl(x) <=> exists y z . F[p_{lvl-1}(y), q_{lvl-1}(z), x] + // + expr_ref mk_transition_axiom(pred_transformer& pt, unsigned level) { + expr_ref fml(m.mk_false(), m), tr(m); + expr_ref_vector reps = mk_reps(pt); + ptr_vector const& rules = pt.rules(); + for (unsigned i = 0; i < rules.size(); ++i) { + tr = mk_transition_rule(reps, level, *rules[i]); + fml = (i == 0)?tr.get():m.mk_or(fml, tr); + } + func_decl_ref fn = mk_pred(level, pt.head()); + fml = m.mk_iff(m.mk_app(fn, reps.size(), reps.c_ptr()), fml); + fml = bind_head(reps, fml); + return fml; + } + + // + // Create implication: + // Q_level(x) => phi(x) + // + expr_ref mk_predicate_property(unsigned level, pred_transformer& pt, expr* phi) { + expr_ref_vector reps = mk_reps(pt); + func_decl_ref fn = mk_pred(level, pt.head()); + expr_ref fml(m); + fml = m.mk_implies(m.mk_app(fn, reps.size(), reps.c_ptr()), phi); + fml = bind_head(reps, fml); + return fml; + } + + + + public: + imp(context& ctx): m_ctx(ctx), pm(ctx.get_pdr_manager()), m(ctx.get_manager()) {} + + // + // not exists y . F(x,y) + // + expr_ref mk_blocked_transition(pred_transformer& pt, unsigned level) { + SASSERT(level > 0); + expr_ref fml(m.mk_true(), m); + expr_ref_vector reps = mk_reps(pt), fmls(m); + ptr_vector const& rules = pt.rules(); + for (unsigned i = 0; i < rules.size(); ++i) { + fmls.push_back(m.mk_not(mk_transition_rule(reps, level, *rules[i]))); + } + fml = pm.mk_and(fmls); + TRACE("pdr", tout << mk_pp(fml, m) << "\n";); + return fml; + } + + expr_ref mk_induction_goal(pred_transformer& pt, unsigned level, unsigned depth) { + SASSERT(level >= depth); + expr_ref_vector conjs(m); + ptr_vector pts; + unsigned_vector levels; + // negated goal + expr_ref phi = mk_blocked_transition(pt, level); + conjs.push_back(m.mk_not(mk_predicate_property(level, pt, phi))); + pts.push_back(&pt); + levels.push_back(level); + // Add I.H. + for (unsigned lvl = level-depth; lvl < level; ++lvl) { + if (lvl > 0) { + expr_ref psi = mk_blocked_transition(pt, lvl); + conjs.push_back(mk_predicate_property(lvl, pt, psi)); + pts.push_back(&pt); + levels.push_back(lvl); + } + } + // Transitions: + for (unsigned qhead = 0; qhead < pts.size(); ++qhead) { + pred_transformer& qt = *pts[qhead]; + unsigned lvl = levels[qhead]; + + // Add transition definition and properties at level. + conjs.push_back(mk_transition_axiom(qt, lvl)); + conjs.push_back(mk_predicate_property(lvl, qt, qt.get_formulas(lvl, true))); + + // Enqueue additional hypotheses + ptr_vector const& rules = qt.rules(); + if (lvl + depth < level || lvl == 0) { + continue; + } + for (unsigned i = 0; i < rules.size(); ++i) { + datalog::rule& r = *rules[i]; + unsigned ut_size = r.get_uninterpreted_tail_size(); + for (unsigned j = 0; j < ut_size; ++j) { + func_decl* f = r.get_tail(j)->get_decl(); + pred_transformer* rt = m_ctx.get_pred_transformers().find(f); + bool found = false; + for (unsigned k = 0; !found && k < levels.size(); ++k) { + found = (rt == pts[k] && levels[k] + 1 == lvl); + } + if (!found) { + levels.push_back(lvl-1); + pts.push_back(rt); + } + } + } + } + + expr_ref result = pm.mk_and(conjs); + TRACE("pdr", tout << mk_pp(result, m) << "\n";); + return result; + } + }; + + // + // Instantiate Peano induction schema. + // + void core_induction_generalizer::operator()(model_node& n, expr_ref_vector& core, bool& uses_level) { + model_node* p = n.parent(); + if (p == 0) { + return; + } + unsigned depth = 2; + imp imp(m_ctx); + ast_manager& m = core.get_manager(); + expr_ref goal = imp.mk_induction_goal(p->pt(), p->level(), depth); + smt::solver ctx(m, m_ctx.get_fparams(), m_ctx.get_params()); + ctx.assert_expr(goal); + lbool r = ctx.check(); + TRACE("pdr", tout << r << "\n"; + for (unsigned i = 0; i < core.size(); ++i) { + tout << mk_pp(core[i].get(), m) << "\n"; + }); + if (r == l_false) { + core.reset(); + expr_ref phi = imp.mk_blocked_transition(p->pt(), p->level()); + core.push_back(m.mk_not(phi)); + uses_level = true; + } + } + + + // + // cube => n.state() & formula + // so n.state() & cube & ~formula is unsat + // so weaken cube while result is still unsat. + // + void model_farkas_generalizer::operator()(model_node& n, expr_ref_vector& cube) { + ast_manager& m = n.pt().get_manager(); + manager& pm = n.pt().get_pdr_manager(); + front_end_params& p = m_ctx.get_fparams(); + farkas_learner learner(p, m); + expr_ref A0(m), A(m), B(m), state(m); + expr_ref_vector states(m); + + A0 = n.pt().get_formulas(n.level(), true); + + // extract substitution for next-state variables. + expr_substitution sub(m); + solve_for_next_vars(A0, n, sub); + scoped_ptr rep = mk_default_expr_replacer(m); + rep->set_substitution(&sub); + (*rep)(A0); + A0 = m.mk_not(A0); + + state = n.state(); + (*rep)(state); + + datalog::flatten_and(state, states); + + ptr_vector preds; + n.pt().find_predecessors(n.model(), preds); + + TRACE("pdr", for (unsigned i = 0; i < cube.size(); ++i) tout << mk_pp(cube[i].get(), m) << "\n";); + + for (unsigned i = 0; i < preds.size(); ++i) { + pred_transformer& pt = m_ctx.get_pred_transformer(preds[i]); + SASSERT(pt.head() == preds[i]); + expr_ref_vector lemmas(m), o_cube(m), other(m), o_state(m), other_state(m); + pm.partition_o_atoms(cube, o_cube, other, i); + pm.partition_o_atoms(states, o_state, other_state, i); + TRACE("pdr", + tout << "cube:\n"; + for (unsigned i = 0; i < cube.size(); ++i) tout << mk_pp(cube[i].get(), m) << "\n"; + tout << "o_cube:\n"; + for (unsigned i = 0; i < o_cube.size(); ++i) tout << mk_pp(o_cube[i].get(), m) << "\n"; + tout << "other:\n"; + for (unsigned i = 0; i < other.size(); ++i) tout << mk_pp(other[i].get(), m) << "\n"; + tout << "o_state:\n"; + for (unsigned i = 0; i < o_state.size(); ++i) tout << mk_pp(o_state[i].get(), m) << "\n"; + tout << "other_state:\n"; + for (unsigned i = 0; i < other_state.size(); ++i) tout << mk_pp(other_state[i].get(), m) << "\n"; + ); + A = m.mk_and(A0, pm.mk_and(other), pm.mk_and(other_state)); + B = m.mk_and(pm.mk_and(o_cube), pm.mk_and(o_state)); + + TRACE("pdr", + tout << "A: " << mk_pp(A, m) << "\n"; + tout << "B: " << mk_pp(B, m) << "\n";); + + if (learner.get_lemma_guesses(A, B, lemmas)) { + cube.append(lemmas); + cube.append(o_state); + TRACE("pdr", + tout << "New lemmas:\n"; + for (unsigned i = 0; i < lemmas.size(); ++i) { + tout << mk_pp(lemmas[i].get(), m) << "\n"; + } + ); + } + } + TRACE("pdr", for (unsigned i = 0; i < cube.size(); ++i) tout << mk_pp(cube[i].get(), m) << "\n";); + } + + // + // use properties from predicate transformer to strengthen state. + // + void core_farkas_properties_generalizer::operator()(model_node& n, expr_ref_vector& core, bool& uses_level) { + if (core.empty()) return; + + front_end_params& p = m_ctx.get_fparams(); + ast_manager& m = n.pt().get_manager(); + manager& pm = n.pt().get_pdr_manager(); + expr_ref_vector lemmas(m), properties(m); + expr_ref A(m), B(m); + farkas_learner learner(p, m); + n.get_properties(properties); + A = n.pt().get_propagation_formula(m_ctx.get_pred_transformers(), n.level()); + B = m.mk_and(properties.size(), properties.c_ptr()); + if (learner.get_lemma_guesses(A, B, lemmas)) { + TRACE("pdr", + tout << "Properties:\n"; + for (unsigned i = 0; i < properties.size(); ++i) { + tout << mk_pp(properties[i].get(), m) << "\n"; + } + tout << mk_pp(B, m) << "\n"; + tout << "New core:\n"; + for (unsigned i = 0; i < lemmas.size(); ++i) { + tout << mk_pp(lemmas[i].get(), m) << "\n"; + }); + core.append(lemmas); // disjunction? + uses_level = true; + } + } + +}; + + + +#if 0 + + void model_farkas_generalizer::extract_eqs(expr_ref_vector const& lits, eqs& eqs) { + expr* e; + rational vl; + bool is_int; + for (unsigned i = 0; i < lits.size(); ++i) { + if (is_eq(lits[i], e, vl, is_int)) { + eqs.push_back(std::make_pair(e, vl)); + } + } + } + + void model_farkas_generalizer::solve_eqs(expr_ref& A, expr_ref_vector& other, eqs const& o_eqs) { + if (o_eqs.empty()) { + return; + } + + expr_substitution sub(m); + scoped_ptr rep = mk_default_expr_replacer(m); + rep->set_substitution(&sub); + + expr* e1; + rational vl1; + bool is_int; + for (unsigned i = 0; i < other.size(); ++i) { + if (is_eq(other[i].get(), e1, vl1, is_int)) { + unsigned k = m_random(o_eqs.size()); + for (unsigned j = 0; j < o_eqs.size(); ++j) { + unsigned l = (j+k)%o_eqs.size(); + expr* e2 = o_eqs[l].first; + if (m.get_sort(e1) == m.get_sort(e2)) { + sub.insert(e1, mk_offset(e2, vl1-o_eqs[l].second)); + other[i] = m.mk_true(); + break; + } + } + } + } + (*rep)(A); + other.push_back(A); + A = m.mk_and(other.size(), other.c_ptr()); + } + + expr* model_farkas_generalizer::mk_offset(expr* e, rational const& r) { + if (r.is_zero()) { + return e; + } + else { + return a.mk_add(e, a.mk_numeral(r, a.is_int(e))); + } + } + + void model_farkas_generalizer::connect_vars(ptr_vector& vars, vector& vals, expr_ref_vector& lits) { + switch(vars.size()) { + case 0: + break; + case 1: + lits.push_back(m.mk_eq(vars[0], a.mk_numeral(vals[0], a.is_int(vars[0])))); + break; + case 2: + lits.push_back(m.mk_eq(vars[0], a.mk_numeral(vals[0], a.is_int(vars[0])))); + lits.push_back(m.mk_eq(vars[0], mk_offset(vars[1], vals[0]-vals[1]))); + break; + default: { + ptr_vector new_vars; + vector new_vals; + unsigned j, i = m_random(vars.size()); + new_vars.push_back(vars[i]); + new_vals.push_back(vals[i]); + lits.push_back(m.mk_eq(vars[i], a.mk_numeral(vals[i], a.is_int(vars[i])))); + vars.erase(vars.begin() + i); + vals.erase(vals.begin() + i); + while (!vars.empty()) { + i = m_random(vars.size()); + j = m_random(new_vars.size()); + lits.push_back(m.mk_eq(vars[i], mk_offset(new_vars[j], vals[i]-new_vals[j]))); + new_vars.push_back(vars[i]); + new_vals.push_back(vals[i]); + vars.erase(vars.begin() + i); + vals.erase(vals.begin() + i); + } + break; + } + } + } + + bool model_farkas_generalizer::is_eq(expr* e, expr*& r, rational& vl, bool& is_int) { + expr* r2 = 0; + return + (m.is_eq(e, r, r2) && a.is_numeral(r2, vl, is_int)) || + (m.is_eq(e, r2, r) && a.is_numeral(r2, vl, is_int)); + } + + void model_farkas_generalizer::relativize(expr_ref_vector& literals) { + ast_manager& m = literals.get_manager(); + ptr_vector ints, reals; + vector int_vals, real_vals; + expr* e; + rational vl; + bool is_int; + for (unsigned i = 0; i < literals.size(); ) { + if (is_eq(literals[i].get(), e, vl, is_int)) { + (is_int?ints:reals).push_back(e); + (is_int?int_vals:real_vals).push_back(vl); + literals[i] = literals.back(); + literals.pop_back(); + } + else { + ++i; + } + } + connect_vars(ints, int_vals, literals); + connect_vars(reals, real_vals, literals); + } + +#endif diff --git a/lib/pdr_generalizers.h b/lib/pdr_generalizers.h new file mode 100644 index 000000000..5f1c01941 --- /dev/null +++ b/lib/pdr_generalizers.h @@ -0,0 +1,104 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + pdr_generalizers.h + +Abstract: + + Generalizer plugins. + +Author: + + Nikolaj Bjorner (nbjorner) 2011-11-22. + +Revision History: + +--*/ + +#ifndef _PDR_GENERALIZERS_H_ +#define _PDR_GENERALIZERS_H_ + +#include "pdr_context.h" +#include "arith_decl_plugin.h" + +namespace pdr { + + class bool_model_evaluation_generalizer : public model_generalizer { + ternary_model_evaluator m_model_evaluator; + public: + bool_model_evaluation_generalizer(context& ctx, ast_manager& m) : model_generalizer(ctx), m_model_evaluator(m) {} + virtual ~bool_model_evaluation_generalizer() {} + virtual void operator()(model_node& n, expr_ref_vector& cube); + }; + + class core_bool_inductive_generalizer : public core_generalizer { + unsigned m_failure_limit; + public: + core_bool_inductive_generalizer(context& ctx, unsigned failure_limit) : core_generalizer(ctx), m_failure_limit(failure_limit) {} + virtual ~core_bool_inductive_generalizer() {} + virtual void operator()(model_node& n, expr_ref_vector& core, bool& uses_level); + }; + + class core_farkas_generalizer : public core_generalizer { + farkas_learner m_farkas_learner; + public: + core_farkas_generalizer(context& ctx, ast_manager& m, front_end_params& p); + virtual ~core_farkas_generalizer() {} + virtual void operator()(model_node& n, expr_ref_vector& core, bool& uses_level); + virtual void collect_statistics(statistics& st) const; + }; + + class model_precond_generalizer : public model_generalizer { + public: + model_precond_generalizer(context& ctx): model_generalizer(ctx) {} + virtual ~model_precond_generalizer() {} + virtual void operator()(model_node& n, expr_ref_vector& cube); + }; + + class model_farkas_generalizer : public model_generalizer { + public: + model_farkas_generalizer(context& ctx) : model_generalizer(ctx) {} + virtual ~model_farkas_generalizer() {} + virtual void operator()(model_node& n, expr_ref_vector& cube); + }; + + class core_farkas_properties_generalizer : public core_generalizer { + public: + core_farkas_properties_generalizer(context& ctx): core_generalizer(ctx) {} + virtual ~core_farkas_properties_generalizer() {} + virtual void operator()(model_node& n, expr_ref_vector& core, bool& uses_level); + }; + + class model_evaluation_generalizer : public model_generalizer { + th_rewriter_model_evaluator m_model_evaluator; + public: + model_evaluation_generalizer(context& ctx, ast_manager& m) : model_generalizer(ctx), m_model_evaluator(m) {} + virtual ~model_evaluation_generalizer() {} + virtual void operator()(model_node& n, expr_ref_vector& cube); + }; + + class core_multi_generalizer : public core_generalizer { + public: + core_multi_generalizer(context& ctx): core_generalizer(ctx) {} + virtual ~core_multi_generalizer() {} + virtual void operator()(model_node& n, expr_ref_vector& core, bool& uses_level); + }; + + class core_interpolant_generalizer : public core_generalizer { + public: + core_interpolant_generalizer(context& ctx): core_generalizer(ctx) {} + virtual ~core_interpolant_generalizer() {} + virtual void operator()(model_node& n, expr_ref_vector& core, bool& uses_level); + }; + + class core_induction_generalizer : public core_generalizer { + class imp; + public: + core_induction_generalizer(context& ctx): core_generalizer(ctx) {} + virtual ~core_induction_generalizer() {} + virtual void operator()(model_node& n, expr_ref_vector& core, bool& uses_level); + }; +}; +#endif diff --git a/lib/pdr_interpolant_provider.cpp b/lib/pdr_interpolant_provider.cpp new file mode 100644 index 000000000..6c0dd3c6b --- /dev/null +++ b/lib/pdr_interpolant_provider.cpp @@ -0,0 +1,380 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + pdr_interpolant_provider.cpp + +Abstract: + + Interface for obtaining interpolants. + + This file is Windows specific. + +Author: + + Krystof Hoder (t-khoder) 2011-10-19. + +Revision History: + +--*/ + +//disables the warning on deprecation of fgets function -- didn't really find by what it should be replaced +#pragma warning(disable: 4995) + +#include +#include "ast_smt_pp.h" +#include "cmd_context.h" +#include "for_each_expr.h" +#include "obj_hashtable.h" +#include "smt2parser.h" +#include "rewriter.h" +#include "rewriter_def.h" +#include "pdr_util.h" +#include "pdr_interpolant_provider.h" +#include "expr_context_simplifier.h" + +#ifdef _WINDOWS +#include +#include +#include +#include + +/** +Requirements for the use of this object: + +The directory where the expcutable is must contain also executable +PdrInterpolator.exe. + +This executable takes one argument with a file name that contains +two SMTLIB problem instances, each terminated by string "\n##next##\n". + +The output of the executable is given to the standard output and +is also terminated by the "\n##next##\n" string. + +If formulas in the two input problems are unsatisfiable, they problem +is printed at the output in the format +(assert FORM) + +If the formulas are satisfiable, "0" is output and if the result cannot +be determined, the output is "?". (Both are still followed by "\n##next##\n"). + +*/ +class interpolant_provider_impl : public interpolant_provider +{ + static std::string s_terminator_str; + static std::string s_satisfiable_str; + static std::string s_unknown_str; + + std::string m_exec_name; + params_ref const & m_params; + + /** + If non-empty, contains name of a temporary file that is used for passing the + interpolation problem to the interpolating engine. + */ + std::string m_tmp_file_name; + + simplifier m_simpl; + + PROCESS_INFORMATION m_pi; + STARTUPINFOA m_si; + HANDLE m_in_rd; + HANDLE m_out_rd; + HANDLE m_in_wr; + HANDLE m_out_wr; + + + class used_symbol_inserter { + typedef obj_hashtable func_decl_set; + typedef obj_hashtable sort_set; + + ast_manager& m; + cmd_context& m_cctx; + + func_decl_set m_funcs; + sort_set m_sorts; + + void handle_sort(sort * s) { + if(s->get_family_id()!=null_family_id || m_sorts.contains(s)) { + return; + } + m_sorts.insert(s); + NOT_IMPLEMENTED_YET(); + //we should insert it into the context somehow, but now not sure how and + //we don't deal with user defined sorts (yet)... + //m_cctx.insert(s); + } + void handle_func_decl(func_decl * fn) { + if(fn->get_family_id()!=null_family_id || m_funcs.contains(fn)) { + return; + } + m_funcs.insert(fn); + m_cctx.insert(fn); + } + + public: + + used_symbol_inserter(cmd_context& cctx) : m(cctx.m()), m_cctx(cctx) {} + + void operator()(var * n) { + handle_sort(n->get_sort()); + } + void operator()(app * n) { + if (is_uninterp(n)) { + handle_func_decl(n->get_decl()); + } + handle_sort(n->get_decl()->get_range()); + } + void operator()(quantifier * n) { + unsigned sz = n->get_num_decls(); + for(unsigned i=0; iget_decl_sort(i)); + } + } + }; + + class form_fixer_cfg : public default_rewriter_cfg + { + ast_manager& m; + public: + form_fixer_cfg(ast_manager& m) : m(m) {} + + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, + proof_ref & result_pr) + { + if(m.is_or(f) && num==0) { + result = m.mk_false(); + return BR_DONE; + } + return BR_FAILED; + } + }; + + + std::string get_tmp_file_name(); + + std::string execute_interpolator(std::string input); + + void simplify_expr(expr* f, expr_ref& result); + void output_interpolant_problem(std::ostream& out, expr * f1, expr * f2); + +public: + + interpolant_provider_impl(ast_manager& m, params_ref const& params, std::string exec_name) + : interpolant_provider(m), + m_params(params), + m_exec_name(exec_name), + m_simpl(m) { + memset(&m_si, 0, sizeof(m_si)); + memset(&m_pi, 0, sizeof(m_pi)); + } + + ~interpolant_provider_impl(); + + + /** + If (f1 /\ f2) is unsatisfiable, return true and into res assign a formula + I such that f1 --> I, I --> ~f2, and the language if I is in the intersection + of languages of f1 and f2. + + If (f1 /\ f2) is satisfiable, return false. + */ + virtual lbool get_interpolant(expr * f1, expr * f2, expr_ref& res); +}; + +std::string interpolant_provider_impl::s_terminator_str = ";##next##"; +std::string interpolant_provider_impl::s_satisfiable_str = ";#sat#"; +std::string interpolant_provider_impl::s_unknown_str = ";#unknown#"; + +interpolant_provider_impl::~interpolant_provider_impl() { + if(m_tmp_file_name.size()!=0) { + DeleteFileA(m_tmp_file_name.c_str()); + } + CloseHandle(m_pi.hProcess); + CloseHandle(m_pi.hThread); + CloseHandle(m_in_rd); + CloseHandle(m_in_wr); + CloseHandle(m_out_rd); + CloseHandle(m_out_wr); +} + +void interpolant_provider_impl::simplify_expr(expr* f, expr_ref& result) { + expr_ref rwr_f(m); + form_fixer_cfg fixer(m); + rewriter_tpl rwr(m, false, fixer); + rwr(f, rwr_f); + proof_ref pr(m); + m_simpl(rwr_f, result, pr); +} + + +std::string interpolant_provider_impl::get_tmp_file_name() { + //return "c:\\test.txt"; + if(m_tmp_file_name.length()!=0) { + return m_tmp_file_name; + } + char path[MAX_PATH]; + if(GetTempPathA(256, path)==0) { + throw default_exception("cannot get temp directory"); + } + + std::stringstream name_prefix_builder; + + name_prefix_builder<<"pdr"<(input.size()), &wr, 0)) { + throw default_exception("Cold not write to pipe"); + } + + std::string result; + char line[256]; + while (true) { + memset(line, 0, sizeof(line)); + if (!ReadFile(m_out_rd, line, sizeof(line)-1, &rd, 0)) { + throw default_exception("Cold not write to pipe"); + } + result += line; + if (strstr(result.c_str(), s_terminator_str.c_str())) { + return result; + } + } +} + +lbool interpolant_provider_impl::get_interpolant(expr * f1, expr * f2, expr_ref& res) { + std::ostringstream prb; + output_interpolant_problem(prb, f1, f2); + std::string res_text = execute_interpolator(prb.str()); + if(strstr(res_text.c_str(), s_satisfiable_str.c_str())) { + return l_false; + } + if(strstr(res_text.c_str(), s_unknown_str.c_str())) { + return l_undef; + } + + front_end_params dummy_params; + cmd_context cctx(dummy_params, false, &m); + for_each_expr(used_symbol_inserter(cctx), f1); + + parse_smt2_commands(cctx, std::istringstream(res_text), false); + + ptr_vector::const_iterator ait = cctx.begin_assertions(); + ptr_vector::const_iterator aend = cctx.end_assertions(); + if(ait+1!=aend) { + throw default_exception("invalid interpolator output"); + } + res = *ait; + if (m_params.get_bool(":dump-interpolants", false)) { + interpolant_provider::output_interpolant(m, f1, f2, res); + } + return l_true; +} + +interpolant_provider * interpolant_provider::mk(ast_manager& m, params_ref const& p) +{ + char self_name[MAX_PATH]; + GetModuleFileNameA(NULL, self_name, MAX_PATH); + char * last_backslash = strrchr(self_name,'\\'); + if(last_backslash==NULL) { + throw default_exception("GetModuleFileNameA did not return full path to the executable"); + } + //we cut the string at the last backslash + *last_backslash = 0; + + std::string exec_name = self_name + std::string(".\\PdrInterpolator.exe"); + + return alloc(interpolant_provider_impl, m, p, exec_name); +} + +#else + +interpolant_provider * +interpolant_provider::mk(ast_manager& m, params_ref const& p) { + // interpolations are windows specific and private. + return 0; +} + + +#endif + + +void interpolant_provider::output_interpolant(ast_manager& m, expr* A, expr* B, expr* I) { + static unsigned file_num = 0; + + std::ostringstream filename; + filename << "interpolation_" << file_num++ << ".smt"; + std::ofstream out(filename.str().c_str()); + + ast_smt_pp pp(m); + pp.add_assumption(A); + pp.display(out, B); + std::ostringstream strm; + strm << ";" << mk_pp(I, m) << "\n"; + + buffer buff; + std::string s = strm.str(); + char const* i_str = s.c_str(); + while (*i_str) { + buff.push_back(*i_str); + if (*i_str == '\n') { + buff.push_back(';'); + } + ++i_str; + } + buff.push_back(0); + out << buff.c_ptr(); + out << "##next##\n"; + out.close(); +} diff --git a/lib/pdr_interpolant_provider.h b/lib/pdr_interpolant_provider.h new file mode 100644 index 000000000..84197a265 --- /dev/null +++ b/lib/pdr_interpolant_provider.h @@ -0,0 +1,54 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + interpolant_provider.h + +Abstract: + + Interface for obtaining interpolants. + +Author: + + Krystof Hoder (t-khoder) 2011-10-19. + +Revision History: + +--*/ + +#include "ast.h" +#include "lbool.h" +#include "model.h" +#include "params.h" + +#ifndef _PDR_INTERPOLANT_PROVIDER_H_ +#define _PDR_INTERPOLANT_PROVIDER_H_ + +class interpolant_provider +{ +protected: + ast_manager & m; + + interpolant_provider(ast_manager& m) : m(m) {} + +public: + + virtual ~interpolant_provider() {} + + /** + If (f1 /\ f2) is unsatisfiable, return true and into res assign a formula + I such that f1 --> I, I --> ~f2, and the language if I is in the intersection + of languages of f1 and f2. + + If (f1 /\ f2) is satisfiable, return false. + */ + virtual lbool get_interpolant(expr * f1, expr * f2, expr_ref& res) = 0; + + static interpolant_provider * mk(ast_manager& m, params_ref const& p); + + static void output_interpolant(ast_manager& m, expr* A, expr* B, expr* I); +}; + + +#endif diff --git a/lib/pdr_manager.cpp b/lib/pdr_manager.cpp new file mode 100644 index 000000000..1543526a9 --- /dev/null +++ b/lib/pdr_manager.cpp @@ -0,0 +1,342 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + pdr_manager.cpp + +Abstract: + + A manager class for PDR, taking care of creating of AST + objects and conversions between them. + +Author: + + Krystof Hoder (t-khoder) 2011-8-25. + +Revision History: + +--*/ + +#include +#include "pdr_manager.h" +#include "ast_smt2_pp.h" +#include "for_each_expr.h" +#include "has_free_vars.h" +#include "expr_replacer.h" +#include "expr_abstract.h" +#include "model2expr.h" +#include "model_smt2_pp.h" +#include "model_converter.h" + + +namespace pdr { + + class collect_decls_proc { + func_decl_set& m_bound_decls; + func_decl_set& m_aux_decls; + public: + collect_decls_proc(func_decl_set& bound_decls, func_decl_set& aux_decls): + m_bound_decls(bound_decls), + m_aux_decls(aux_decls) { + } + + void operator()(app* a) { + if (a->get_family_id() == null_family_id) { + func_decl* f = a->get_decl(); + if (!m_bound_decls.contains(f)) { + m_aux_decls.insert(f); + } + } + } + void operator()(var* v) {} + void operator()(quantifier* q) {} + }; + + typedef hashtable symbol_set; + + expr_ref inductive_property::fixup_clause(expr* fml) const { + expr_ref_vector disjs(m); + datalog::flatten_or(fml, disjs); + switch(disjs.size()) { + case 0: return expr_ref(m.mk_false(), m); + case 1: return expr_ref(disjs[0].get(), m); + default: return expr_ref(m.mk_or(disjs.size(), disjs.c_ptr()), m); + } + } + + expr_ref inductive_property::fixup_clauses(expr* fml) const { + expr_ref_vector conjs(m); + datalog::flatten_and(fml, conjs); + for (unsigned i = 0; i < conjs.size(); ++i) { + conjs[i] = fixup_clause(conjs[i].get()); + } + switch(conjs.size()) { + case 0: return expr_ref(m.mk_true(), m); + case 1: return expr_ref(conjs[0].get(), m); + default: return expr_ref(m.mk_and(conjs.size(), conjs.c_ptr()), m); + } + } + + std::string inductive_property::to_string() const { + std::stringstream stm; + model_ref md; + expr_ref result(m); + to_model(md); + model_smt2_pp(stm, m, *md.get(), 0); + return stm.str(); + } + + void inductive_property::to_model(model_ref& md) const { + md = alloc(model, m); + vector const& rs = m_relation_info; + expr_ref_vector conjs(m); + for (unsigned i = 0; i < rs.size(); ++i) { + relation_info ri(rs[i]); + func_decl * pred = ri.m_pred; + expr_ref prop = fixup_clauses(ri.m_body); + func_decl_ref_vector const& sig = ri.m_vars; + expr_ref q(m); + expr_ref_vector sig_vars(m); + for (unsigned j = 0; j < sig.size(); ++j) { + sig_vars.push_back(m.mk_const(sig[sig.size()-j-1])); + } + expr_abstract(m, 0, sig_vars.size(), sig_vars.c_ptr(), prop, q); + if (sig.empty()) { + md->register_decl(pred, q); + } + else { + func_interp* fi = alloc(func_interp, m, sig.size()); + fi->set_else(q); + md->register_decl(pred, fi); + } + } + apply(const_cast(m_mc), md, 0); + } + + expr_ref inductive_property::to_expr() const { + model_ref md; + expr_ref result(m); + to_model(md); + model2expr(md, result); + return result; + } + + + void inductive_property::display(ptr_vector const& rules, std::ostream& out) const { + func_decl_set bound_decls, aux_decls; + collect_decls_proc collect_decls(bound_decls, aux_decls); + + for (unsigned i = 0; i < m_relation_info.size(); ++i) { + bound_decls.insert(m_relation_info[i].m_pred); + func_decl_ref_vector const& sig = m_relation_info[i].m_vars; + for (unsigned j = 0; j < sig.size(); ++j) { + bound_decls.insert(sig[j]); + } + for_each_expr(collect_decls, m_relation_info[i].m_body); + } + for (unsigned i = 0; i < rules.size(); ++i) { + bound_decls.insert(rules[i]->get_head()->get_decl()); + } + for (unsigned i = 0; i < rules.size(); ++i) { + unsigned u_sz = rules[i]->get_uninterpreted_tail_size(); + unsigned t_sz = rules[i]->get_tail_size(); + for (unsigned j = u_sz; j < t_sz; ++j) { + for_each_expr(collect_decls, rules[i]->get_tail(j)); + } + } + smt2_pp_environment_dbg env(m); + pp_params params; + func_decl_set::iterator it = aux_decls.begin(), end = aux_decls.end(); + for (; it != end; ++it) { + func_decl* f = *it; + ast_smt2_pp(out, f, env, params); + out << "\n"; + } + + out << to_string() << "\n"; + for (unsigned i = 0; i < rules.size(); ++i) { + out << "(push)\n"; + out << "(assert (not\n"; + rules[i]->display_smt2(m, out); + out << "))\n"; + out << "(check-sat)\n"; + out << "(pop)\n"; + } + } + + vector manager::get_state_suffixes() { + vector res; + res.push_back("_n"); + return res; + } + + manager::manager(front_end_params& fparams, params_ref const& params, ast_manager& manager) : + m(manager), + m_fparams(fparams), + m_params(params), + m_brwr(m), + m_mux(m, get_state_suffixes()), + m_background(m.mk_true(), m), + m_contexts(fparams, params, m), + m_next_unique_num(0) + { + } + + + void manager::add_new_state(func_decl * s) { + SASSERT(s->get_arity()==0); //we currently don't support non-constant states + decl_vector vect; + SASSERT(o_index(0)==1); //we assume this in the number of retrieved symbols + m_mux.create_tuple(s, s->get_arity(), s->get_domain(), s->get_range(), 2, vect); + m_o0_preds.push_back(vect[o_index(0)]); + } + + func_decl * manager::get_o_pred(func_decl* s, unsigned idx) + { + func_decl * res = m_mux.try_get_by_prefix(s, o_index(idx)); + if(res) { return res; } + add_new_state(s); + res = m_mux.try_get_by_prefix(s, o_index(idx)); + SASSERT(res); + return res; + } + + func_decl * manager::get_n_pred(func_decl* s) + { + func_decl * res = m_mux.try_get_by_prefix(s, n_index()); + if(res) { return res; } + add_new_state(s); + res = m_mux.try_get_by_prefix(s, n_index()); + SASSERT(res); + return res; + } + + void manager::mk_model_into_cube(const expr_ref_vector & mdl, expr_ref & res) { + m_brwr.mk_and(mdl.size(), mdl.c_ptr(), res); + } + + void manager::mk_core_into_cube(const expr_ref_vector & core, expr_ref & res) { + m_brwr.mk_and(core.size(), core.c_ptr(), res); + } + + void manager::mk_cube_into_lemma(expr * cube, expr_ref & res) { + m_brwr.mk_not(cube, res); + } + + void manager::mk_lemma_into_cube(expr * lemma, expr_ref & res) { + m_brwr.mk_not(lemma, res); + } + + expr_ref manager::mk_and(unsigned sz, expr* const* exprs) { + expr_ref result(m); + m_brwr.mk_and(sz, exprs, result); + return result; + } + + expr_ref manager::mk_or(unsigned sz, expr* const* exprs) { + expr_ref result(m); + m_brwr.mk_or(sz, exprs, result); + return result; + } + + expr_ref manager::mk_not_and(expr_ref_vector const& conjs) { + expr_ref result(m), e(m); + expr_ref_vector es(conjs); + datalog::flatten_and(es); + for (unsigned i = 0; i < es.size(); ++i) { + m_brwr.mk_not(es[i].get(), e); + es[i] = e; + } + m_brwr.mk_or(es.size(), es.c_ptr(), result); + return result; + } + + void manager::get_or(expr* e, expr_ref_vector& result) { + result.push_back(e); + for (unsigned i = 0; i < result.size(); ) { + e = result[i].get(); + if (m.is_or(e)) { + result.append(to_app(e)->get_num_args(), to_app(e)->get_args()); + result[i] = result.back(); + result.pop_back(); + } + else { + ++i; + } + } + } + + bool manager::try_get_state_and_value_from_atom(expr * atom0, app *& state, app_ref& value) + { + if(!is_app(atom0)) { + return false; + } + app * atom = to_app(atom0); + expr * arg1; + expr * arg2; + app * candidate_state; + app_ref candidate_value(m); + if(m.is_not(atom, arg1)) { + if(!is_app(arg1)) { + return false; + } + candidate_state = to_app(arg1); + candidate_value = m.mk_false(); + } + else if(m.is_eq(atom, arg1, arg2)) { + if(!is_app(arg1) || !is_app(arg2)) { + return false; + } + if(!m_mux.is_muxed(to_app(arg1)->get_decl())) { + std::swap(arg1, arg2); + } + candidate_state = to_app(arg1); + candidate_value = to_app(arg2); + } + else { + candidate_state = atom; + candidate_value = m.mk_true(); + } + if(!m_mux.is_muxed(candidate_state->get_decl())) { + return false; + } + state = candidate_state; + value = candidate_value; + return true; + } + + bool manager::try_get_state_decl_from_atom(expr * atom, func_decl *& state) { + app_ref dummy_value_holder(m); + app * s; + if(try_get_state_and_value_from_atom(atom, s, dummy_value_holder)) { + state = s->get_decl(); + return true; + } + else { + return false; + } + } + + + interpolant_provider& manager::get_interpolator() { + if(!m_interpolator) { + m_interpolator = interpolant_provider::mk(m, get_params()); + } + return *m_interpolator; + } + + + bool manager::implication_surely_holds(expr * lhs, expr * rhs, expr * bg) { + smt::solver sctx(m, get_fparams()); + if(bg) { + sctx.assert_expr(bg); + } + sctx.assert_expr(lhs); + expr_ref neg_rhs(m.mk_not(rhs),m); + sctx.assert_expr(neg_rhs); + lbool smt_res = sctx.check(); + return smt_res==l_false; + } + +}; diff --git a/lib/pdr_manager.h b/lib/pdr_manager.h new file mode 100644 index 000000000..e7bfb38c8 --- /dev/null +++ b/lib/pdr_manager.h @@ -0,0 +1,307 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + pdr_manager.h + +Abstract: + + A manager class for PDR, taking care of creating of AST + objects and conversions between them. + +Author: + + Krystof Hoder (t-khoder) 2011-8-25. + +Revision History: + +--*/ + +#ifndef _PDR_MANAGER_H_ +#define _PDR_MANAGER_H_ + +#include +#include +#include "bool_rewriter.h" +#include "expr_replacer.h" +#include "expr_substitution.h" +#include "map.h" +#include "ref_vector.h" +#include "smt_solver.h" +#include "pdr_util.h" +#include "pdr_sym_mux.h" +#include "pdr_farkas_learner.h" +#include "pdr_interpolant_provider.h" +#include "pdr_smt_context_manager.h" +#include "dl_rule.h" + + +namespace smt { + class context; +} + +namespace pdr { + + struct relation_info { + func_decl_ref m_pred; + func_decl_ref_vector m_vars; + expr_ref m_body; + relation_info(ast_manager& m, func_decl* pred, ptr_vector const& vars, expr* b): + m_pred(pred, m), m_vars(m, vars.size(), vars.c_ptr()), m_body(b, m) {} + relation_info(relation_info const& other): m_pred(other.m_pred), m_vars(other.m_vars), m_body(other.m_body) {} + }; + + class unknown_exception {}; + + class inductive_property { + ast_manager& m; + model_converter_ref m_mc; + vector m_relation_info; + expr_ref fixup_clauses(expr* property) const; + expr_ref fixup_clause(expr* clause) const; + public: + inductive_property(ast_manager& m, model_converter_ref& mc, vector const& relations): + m(m), + m_mc(mc), + m_relation_info(relations) {} + + std::string to_string() const; + + expr_ref to_expr() const; + + void to_model(model_ref& md) const; + + void display(ptr_vector const& rules, std::ostream& out) const; + }; + + class manager + { + ast_manager& m; + front_end_params& m_fparams; + params_ref const& m_params; + + mutable bool_rewriter m_brwr; + + sym_mux m_mux; + expr_ref m_background; + decl_vector m_o0_preds; + pdr::smt_context_manager m_contexts; + + /** whenever we need an unique number, we get this one and increase */ + unsigned m_next_unique_num; + + /** + It would make more sense to have interpolantor inside the prop_solver, + however we have one prop_solver instance in each relation. + Each instance of interpolant_provider creates a temporary file and + interpolant_provider can be shared, so it makes more sence to have + it in pdr_manager which is created only once. + */ + + scoped_ptr m_interpolator; + + + static vector get_state_suffixes(); + + unsigned n_index() const { return 0; } + unsigned o_index(unsigned i) const { return i+1; } + + void add_new_state(func_decl * s); + + public: + manager(front_end_params& fparams, params_ref const& params, + ast_manager & manager); + + ast_manager& get_manager() const { return m; } + front_end_params& get_fparams() const { return m_fparams; } + params_ref const& get_params() const { return m_params; } + bool_rewriter& get_brwr() const { return m_brwr; } + + expr_ref mk_and(unsigned sz, expr* const* exprs); + expr_ref mk_and(expr_ref_vector const& exprs) { + return mk_and(exprs.size(), exprs.c_ptr()); + } + expr_ref mk_and(expr* a, expr* b) { + expr* args[2] = { a, b }; + return mk_and(2, args); + } + expr_ref mk_or(unsigned sz, expr* const* exprs); + expr_ref mk_or(expr_ref_vector const& exprs) { + return mk_or(exprs.size(), exprs.c_ptr()); + } + + expr_ref mk_not_and(expr_ref_vector const& exprs); + + void get_or(expr* e, expr_ref_vector& result); + + //"o" predicates stand for the old states and "n" for the new states + func_decl * get_o_pred(func_decl * s, unsigned idx); + func_decl * get_n_pred(func_decl * s); + + /** + Marks symbol as non-model which means it will not appear in models collected by + get_state_cube_from_model function. + This is to take care of auxiliary symbols introduced by the disjunction relations + to relativize lemmas coming from disjuncts. + */ + void mark_as_non_model(func_decl * p) { + m_mux.mark_as_non_model(p); + } + + + func_decl * const * begin_o0_preds() const { return m_o0_preds.begin(); } + func_decl * const * end_o0_preds() const { return m_o0_preds.end(); } + + bool is_state_pred(func_decl * p) const { return m_mux.is_muxed(p); } + func_decl * to_o0(func_decl * p) { return m_mux.conv(m_mux.get_primary(p), 0, o_index(0)); } + + bool is_o(func_decl * p, unsigned idx) const { + return m_mux.has_index(p, o_index(idx)); + } + bool is_o(expr* e, unsigned idx) const { + return is_app(e) && is_o(to_app(e)->get_decl(), idx); + } + bool is_o(func_decl * p) const { + unsigned idx; + return m_mux.try_get_index(p, idx) && idx!=n_index(); + } + bool is_o(expr* e) const { + return is_app(e) && is_o(to_app(e)->get_decl()); + } + bool is_n(func_decl * p) const { + return m_mux.has_index(p, n_index()); + } + bool is_n(expr* e) const { + return is_app(e) && is_n(to_app(e)->get_decl()); + } + + /** true if p should not appead in models propagates into child relations */ + bool is_non_model_sym(func_decl * p) const + { return m_mux.is_non_model_sym(p); } + + + /** true if f doesn't contain any n predicates */ + bool is_o_formula(expr * f) const { + return !m_mux.contains(f, n_index()); + } + + /** true if f contains only o state preds of index o_idx */ + bool is_o_formula(expr * f, unsigned o_idx) const { + return m_mux.is_homogenous_formula(f, o_index(o_idx)); + } + /** true if f doesn't contain any o predicates */ + bool is_n_formula(expr * f) const { + return m_mux.is_homogenous_formula(f, n_index()); + } + + func_decl * o2n(func_decl * p, unsigned o_idx) const { + return m_mux.conv(p, o_index(o_idx), n_index()); + } + func_decl * o2o(func_decl * p, unsigned src_idx, unsigned tgt_idx) const { + return m_mux.conv(p, o_index(src_idx), o_index(tgt_idx)); + } + func_decl * n2o(func_decl * p, unsigned o_idx) const { + return m_mux.conv(p, n_index(), o_index(o_idx)); + } + + void formula_o2n(expr * f, expr_ref & result, unsigned o_idx, bool homogenous=true) const + { m_mux.conv_formula(f, o_index(o_idx), n_index(), result, homogenous); } + + void formula_n2o(expr * f, expr_ref & result, unsigned o_idx, bool homogenous=true) const + { m_mux.conv_formula(f, n_index(), o_index(o_idx), result, homogenous); } + + void formula_n2o(unsigned o_idx, bool homogenous, expr_ref & result) const + { m_mux.conv_formula(result.get(), n_index(), o_index(o_idx), result, homogenous); } + + void formula_o2o(expr * src, expr_ref & tgt, unsigned src_idx, unsigned tgt_idx, bool homogenous=true) const + { m_mux.conv_formula(src, o_index(src_idx), o_index(tgt_idx), tgt, homogenous); } + + /** + Return true if all state symbols which e contains are of one kind (either "n" or one of "o"). + */ + bool is_homogenous_formula(expr * e) const { + return m_mux.is_homogenous_formula(e); + } + + /** + Return true iff both s1 and s2 are either "n" or "o" of the same index. + If one (or both) of them are not state symbol, return false. + */ + bool have_different_state_kinds(func_decl * s1, func_decl * s2) const { + unsigned i1, i2; + return m_mux.try_get_index(s1, i1) && m_mux.try_get_index(s2, i2) && i1!=i2; + } + + /** + Increase indexes of state symbols in formula by dist. + The 'N' index becomes 'O' index with number dist-1. + */ + void formula_shift(expr * src, expr_ref & tgt, unsigned dist) const { + SASSERT(n_index()==0); + SASSERT(o_index(0)==1); + m_mux.shift_formula(src, dist, tgt); + } + + void mk_model_into_cube(const expr_ref_vector & mdl, expr_ref & res); + void mk_core_into_cube(const expr_ref_vector & core, expr_ref & res); + void mk_cube_into_lemma(expr * cube, expr_ref & res); + void mk_lemma_into_cube(expr * lemma, expr_ref & res); + + /** + Remove from vec all atoms that do not have an "o" state. + The order of elements in vec may change. + An assumption is that atoms having "o" state of given index + do not have "o" states of other indexes or "n" states. + */ + void filter_o_atoms(expr_ref_vector& vec, unsigned o_idx) const + { m_mux.filter_idx(vec, o_index(o_idx)); } + void filter_n_atoms(expr_ref_vector& vec) const + { m_mux.filter_idx(vec, n_index()); } + + /** + Partition literals into o_lits and others. + */ + void partition_o_atoms(expr_ref_vector const& lits, + expr_ref_vector& o_lits, + expr_ref_vector& other, + unsigned o_idx) const { + m_mux.partition_o_idx(lits, o_lits, other, o_index(o_idx)); + } + + void filter_out_non_model_atoms(expr_ref_vector& vec) const + { m_mux.filter_non_model_lits(vec); } + + bool try_get_state_and_value_from_atom(expr * atom, app *& state, app_ref& value); + bool try_get_state_decl_from_atom(expr * atom, func_decl *& state); + + void get_state_cube_from_model(const model_core & mdl, expr_ref_vector & cube) const + { return m_mux.get_muxed_cube_from_model(mdl, cube); } + + std::string pp_model(const model_core & mdl) const + { return m_mux.pp_model(mdl); } + + + void set_background(expr* b) { m_background = b; } + + expr* get_background() const { return m_background; } + + interpolant_provider& get_interpolator(); + + /** + Return true if we can show that lhs => rhs. The function can have false negatives + (i.e. when smt::context returns unknown), but no false positives. + + bg is background knowledge and can be null + */ + bool implication_surely_holds(expr * lhs, expr * rhs, expr * bg=0); + + unsigned get_unique_num() { return m_next_unique_num++; } + + pdr::smt_context* mk_fresh() { return m_contexts.mk_fresh(); } + + void collect_statistics(statistics& st) const { m_contexts.collect_statistics(st); } + }; +} + +#endif diff --git a/lib/pdr_prop_solver.cpp b/lib/pdr_prop_solver.cpp new file mode 100644 index 000000000..e41ff04b6 --- /dev/null +++ b/lib/pdr_prop_solver.cpp @@ -0,0 +1,297 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + prop_solver.cpp + +Abstract: + + SMT solver abstraction for PDR. + +Author: + + Krystof Hoder (t-khoder) 2011-8-17. + +Revision History: + +--*/ + +#include +#include "model.h" +#include "pdr_util.h" +#include "pdr_prop_solver.h" +#include "ast_smt2_pp.h" +#include "dl_util.h" +#include "model_pp.h" +#include "front_end_params.h" + +#define CTX_VERB_LBL 21 + +// +// Auxiliary structure to introduce propositional names for assumptions that are not +// propositional. It is to work with the smt::context's restriction +// that assumptions be propositional atoms. +// + +namespace pdr { + + class prop_solver::safe_assumptions { + prop_solver& s; + ast_manager& m; + expr_ref_vector m_atoms; + obj_map m_fresh2expr; + obj_map m_expr2fresh; + unsigned m_num_fresh; + + app * mk_fresh(expr* atom) { + app* res; + SASSERT(!is_var(atom)); //it doesn't make sense to introduce names to variables + if (m_expr2fresh.find(atom, res)) { + return res; + } + SASSERT(s.m_fresh.size() >= m_num_fresh); + if (m_num_fresh == s.m_fresh.size()) { + std::stringstream name; + name << "pdr_proxy_" << s.m_fresh.size(); + res = m.mk_const(symbol(name.str().c_str()), m.mk_bool_sort()); + s.m_fresh.push_back(res); + s.m_aux_symbols.insert(res->get_decl()); + } + else { + res = s.m_fresh[m_num_fresh].get(); + } + ++m_num_fresh; + m_expr2fresh.insert(atom, res); + m_fresh2expr.insert(res, atom); + expr_ref equiv(m.mk_eq(atom, res), m); + s.m_ctx->assert_expr(equiv); + TRACE("pdr_verbose", tout << "name asserted " << mk_pp(equiv, m) << "\n";); + return res; + } + + void mk_safe(expr_ref_vector& conjs) { + datalog::flatten_and(conjs); + for (unsigned i = 0; i < conjs.size(); ++i) { + expr * atom = conjs[i].get(); + bool negated = m.is_not(atom, atom); //remove negation + SASSERT(!m.is_true(atom)); + if (!is_uninterp(atom) || to_app(atom)->get_num_args() != 0) { + app * name = mk_fresh(atom); + conjs[i] = negated?m.mk_not(name):name; + } + } + } + + public: + safe_assumptions(prop_solver& s, expr_ref_vector const& assumptions): + s(s), m(s.m), m_atoms(assumptions), m_num_fresh(0) { + mk_safe(m_atoms); + } + + ~safe_assumptions() { + } + + expr_ref_vector const& atoms() const { return m_atoms; } + + void undo_naming(expr_ref_vector& literals) { + for (unsigned i = 0; i < literals.size(); ++i) { + expr * atom = literals[i].get(), *orig_atom; + bool negated = m.is_not(atom, atom); //remove negation + SASSERT(is_app(atom)); //only apps can be used in safe cubes + if (m_fresh2expr.find(to_app(atom), orig_atom)) { + literals[i] = negated?m.mk_not(orig_atom):orig_atom; + } + } + } + }; + + + prop_solver::prop_solver(manager& pm, symbol const& name) : + m_fparams(pm.get_fparams()), + m(pm.get_manager()), + m_pm(pm), + m_name(name), + m_try_minimize_core(pm.get_params().get_bool(":try-minimize-core", false)), + m_ctx(pm.mk_fresh()), + m_pos_level_atoms(m), + m_neg_level_atoms(m), + m_fresh(m), + m_in_level(false) + { + m_ctx->assert_expr(m_pm.get_background()); + } + + void prop_solver::add_level() { + unsigned idx = level_cnt(); + std::stringstream name; + name << m_name << "#level_" << idx; + func_decl * lev_pred = m.mk_fresh_func_decl(name.str().c_str(), 0, 0,m.mk_bool_sort()); + m_aux_symbols.insert(lev_pred); + m_level_preds.push_back(lev_pred); + + app_ref pos_la(m.mk_const(lev_pred), m); + app_ref neg_la(m.mk_not(pos_la.get()), m); + + m_pos_level_atoms.push_back(pos_la); + m_neg_level_atoms.push_back(neg_la); + + m_level_atoms_set.insert(pos_la.get()); + m_level_atoms_set.insert(neg_la.get()); + } + + void prop_solver::ensure_level(unsigned lvl) { + while (lvl>=level_cnt()) { + add_level(); + } + } + + unsigned prop_solver::level_cnt() const { + return m_level_preds.size(); + } + + void prop_solver::push_level_atoms(unsigned level, expr_ref_vector& tgt) const { + unsigned lev_cnt = level_cnt(); + for (unsigned i=0; i=level; + app * lev_atom = active ? m_neg_level_atoms[i] : m_pos_level_atoms[i]; + tgt.push_back(lev_atom); + } + } + + void prop_solver::add_formula(expr * form) { + SASSERT(!m_in_level); + m_ctx->assert_expr(form); + IF_VERBOSE(CTX_VERB_LBL, verbose_stream() << "$ asserted " << mk_pp(form, m) << "\n";); + TRACE("pdr", tout << "add_formula: " << mk_pp(form, m) << "\n";); + } + + void prop_solver::add_level_formula(expr * form, unsigned level) { + ensure_level(level); + app * lev_atom = m_pos_level_atoms[level].get(); + app_ref lform(m.mk_or(form, lev_atom), m); + add_formula(lform.get()); + } + + + lbool prop_solver::check_safe_assumptions( + const expr_ref_vector& atoms, + expr_ref_vector* core, + model_ref * mdl, + bool& assumes_level) + { + flet _model(m_fparams.m_model, mdl != 0); + expr_ref_vector expr_atoms(m); + expr_atoms.append(atoms.size(), atoms.c_ptr()); + assumes_level = false; + + if (m_in_level) { + push_level_atoms(m_current_level, expr_atoms); + } + + lbool result = m_ctx->check(expr_atoms); + + TRACE("pdr", + tout << mk_pp(m_pm.mk_and(expr_atoms), m) << "\n"; + tout << result << "\n";); + + if (result == l_true && mdl) { + m_ctx->get_model(*mdl); + TRACE("pdr_verbose", model_pp(tout, **mdl); ); + } + + unsigned core_size = m_ctx->get_unsat_core_size(); + + if (result == l_false && !core) { + for (unsigned i = 0; i < core_size; ++i) { + if (m_level_atoms_set.contains(m_ctx->get_unsat_core_expr(i))) { + assumes_level = true; + break; + } + } + } + + if (result == l_false && core) { + core->reset(); + for (unsigned i = 0; i < core_size; ++i) { + expr * core_expr = m_ctx->get_unsat_core_expr(i); + SASSERT(is_app(core_expr)); + + if (m_level_atoms_set.contains(core_expr)) { + assumes_level = true; + continue; + } + if (m_ctx->is_aux_predicate(core_expr)) { + continue; + } + core->push_back(to_app(core_expr)); + } + TRACE("pdr", + tout << mk_pp(m_pm.mk_and(expr_atoms), m) << "\n"; + tout << "core_exprs: "; + for (unsigned i = 0; i < core_size; ++i) { + tout << mk_pp(m_ctx->get_unsat_core_expr(i), m) << " "; + } + tout << "\n"; + tout << "core: " << mk_pp(m_pm.mk_and(*core), m) << "\n"; + ); + SASSERT(expr_atoms.size() >= core->size()); + } + return result; + } + + lbool prop_solver::check_assumptions( + const expr_ref_vector & atoms, + expr_ref_vector * core, + model_ref * mdl, + bool& assumes_level) + { + return check_assumptions_and_formula(atoms, m.mk_true(), core, mdl, assumes_level); + } + + lbool prop_solver::check_conjunction_as_assumptions( + expr * conj, + expr_ref_vector * core, + model_ref * mdl, + bool& assumes_level) { + expr_ref_vector asmp(m); + asmp.push_back(conj); + return check_assumptions(asmp, core, mdl, assumes_level); + } + + lbool prop_solver::check_assumptions_and_formula( + const expr_ref_vector & atoms, expr * form, + expr_ref_vector * core, + model_ref * mdl, + bool& assumes_level) + { + pdr::smt_context::scoped _scoped(*m_ctx); + safe_assumptions safe(*this, atoms); + m_ctx->assert_expr(form); + CTRACE("pdr", !m.is_true(form), tout << "check with formula: " << mk_pp(form, m) << "\n";); + lbool res = check_safe_assumptions(safe.atoms(), core, mdl, assumes_level); + if (res == l_false && core && m_try_minimize_core) { + unsigned sz = core->size(); + bool assumes_level1 = false; + lbool res2 = check_safe_assumptions(*core, core, mdl, assumes_level1); + if (res2 == l_false && sz > core->size()) { + res = res2; + assumes_level = assumes_level1; + IF_VERBOSE(1, verbose_stream() << "reduced core size from " << sz << " to " << core->size() << "\n";); + } + } + if (core) { + safe.undo_naming(*core); + } + // + // we don't have to undo model naming, as from the model + // we extract the values for state variables directly + // + return res; + } + + void prop_solver::collect_statistics(statistics& st) const { + } + + +} diff --git a/lib/pdr_prop_solver.h b/lib/pdr_prop_solver.h new file mode 100644 index 000000000..37ac25adb --- /dev/null +++ b/lib/pdr_prop_solver.h @@ -0,0 +1,125 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + prop_solver.h + +Abstract: + + SAT solver abstraction for PDR. + +Author: + + Krystof Hoder (t-khoder) 2011-8-17. + +Revision History: + +--*/ + +#ifndef _PROP_SOLVER_H_ +#define _PROP_SOLVER_H_ + +#include +#include +#include +#include "ast.h" +#include "obj_hashtable.h" +#include "smt_solver.h" +#include "util.h" +#include "vector.h" +#include "pdr_manager.h" +#include "pdr_smt_context_manager.h" + +namespace pdr { + class prop_solver { + private: + front_end_params& m_fparams; + ast_manager& m; + manager& m_pm; + symbol m_name; + bool m_try_minimize_core; + scoped_ptr m_ctx; + decl_vector m_level_preds; + app_ref_vector m_pos_level_atoms; // atoms used to identify level + app_ref_vector m_neg_level_atoms; // + obj_hashtable m_level_atoms_set; + app_ref_vector m_fresh; // predicates for assumptions + func_decl_set m_aux_symbols; + bool m_in_level; + unsigned m_current_level; // set when m_in_level + + /** Add level atoms activating certain level into a vector */ + void push_level_atoms(unsigned level, expr_ref_vector & tgt) const; + + void ensure_level(unsigned lvl); + + lbool check_safe_assumptions( + const expr_ref_vector & atoms, + expr_ref_vector * core, + model_ref * mdl, + bool& assumes_level); + + class safe_assumptions; + + public: + prop_solver(pdr::manager& pm, symbol const& name); + + /** return true is s is a symbol introduced by prop_solver */ + bool is_aux_symbol(func_decl * s) const { + return + m_aux_symbols.contains(s) || + m_ctx->is_aux_predicate(s); + } + + void add_level(); + unsigned level_cnt() const; + + class scoped_level { + bool& m_lev; + public: + scoped_level(prop_solver& ps, unsigned lvl):m_lev(ps.m_in_level) { + SASSERT(!m_lev); m_lev = true; ps.m_current_level = lvl; + } + ~scoped_level() { m_lev = false; } + }; + + void add_formula(expr * form); + void add_level_formula(expr * form, unsigned level); + + /** + * Return true iff conjunction of atoms is consistent with the current state of + * the solver. + * + * If the conjunction of atoms is inconsistent with the solver state and core is non-zero, + * core will contain an unsatisfiable core of atoms. + * + * If the conjunction of atoms is consistent with the solver state and o_model is non-zero, + * o_model will contain the "o" literals true in the assignment. + */ + lbool check_assumptions( + const expr_ref_vector & atoms, + expr_ref_vector * core, model_ref * mdl, + bool& assumes_level); + + lbool check_conjunction_as_assumptions( + expr * conj, expr_ref_vector * core, + model_ref * mdl, bool& assumes_level); + + /** + * Like check_assumptions, except it also asserts an extra formula + */ + lbool check_assumptions_and_formula( + const expr_ref_vector & atoms, + expr * form, + expr_ref_vector * core, + model_ref * mdl, + bool& assumes_level); + + void collect_statistics(statistics& st) const; + + }; +} + + +#endif diff --git a/lib/pdr_quantifiers.cpp b/lib/pdr_quantifiers.cpp new file mode 100644 index 000000000..6ba7c2d5e --- /dev/null +++ b/lib/pdr_quantifiers.cpp @@ -0,0 +1,503 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + pdr_quantifiers.cpp + +Abstract: + + Module for handling quantifiers in rule bodies. + +Author: + + Nikolaj Bjorner (nbjorner) 2012-05-19. + +Revision History: + +--*/ + +#include "pdr_quantifiers.h" +#include "pdr_context.h" +#include "model_smt2_pp.h" +#include "ast_smt2_pp.h" +#include "qe.h" +#include "var_subst.h" +#include "dl_rule_set.h" + + +namespace pdr { + + /** + \brief model check a potential model against quantifiers in bodies of rules. + + \return true if the model rooted in 'root' is checks with the quantifiers, otherwise + 'false' and a set of instantiations that contradict the current model. + */ + + static void get_nodes(model_node& root, ptr_vector& nodes) { + ptr_vector todo; + todo.push_back(&root); + while (!todo.empty()) { + model_node* n = todo.back(); + todo.pop_back(); + nodes.push_back(n); + todo.append(n->children().size(), n->children().c_ptr()); + } + } + + + void quantifier_model_checker::generalize_binding(expr_ref_vector const& binding, vector& bindings) { + expr_ref_vector new_binding(m); + generalize_binding(binding, 0, new_binding, bindings); + } + + void quantifier_model_checker::generalize_binding( + expr_ref_vector const& binding, unsigned idx, + expr_ref_vector& new_binding, vector& bindings) { + if (idx == binding.size()) { + bool non_zero = false; + for (unsigned i = 0; i < binding.size(); ++i) { + if (new_binding[i].get()) { + non_zero = true; + } + else { + new_binding[i] = binding[i]; + } + } + if (non_zero) { + TRACE("pdr", + for (unsigned i = 0; i < new_binding.size(); ++i) { + tout << mk_pp(new_binding[i].get(), m) << " "; + } + tout << "\n";); + bindings.push_back(new_binding); + } + return; + } + model_node& node = *m_current_node; + expr_ref_vector ands(m); + expr* e1, *e2; + datalog::flatten_and(node.state(), ands); + new_binding.push_back(0); + generalize_binding(binding, idx + 1, new_binding, bindings); + for (unsigned i = 0; i < ands.size(); ++i) { + if (m.is_eq(ands[i].get(), e1, e2)) { + if (e2 == binding[idx]) { + new_binding[new_binding.size()-1] = e1; + generalize_binding(binding, idx + 1, new_binding, bindings); + } + } + } + new_binding.pop_back(); + } + + + void quantifier_model_checker::add_binding(quantifier* q, expr_ref_vector& binding) { + apply_binding(q, binding); + vector bindings; + generalize_binding(binding, bindings); + for (unsigned i = 0; i < bindings.size(); ++i) { + apply_binding(q, bindings[i]); + } + } + + void quantifier_model_checker::apply_binding(quantifier* q, expr_ref_vector& binding) { + app_ref_vector& var_inst = m_current_pt->get_inst(m_current_rule); + expr_substitution sub(m); + for (unsigned i = 0; i < var_inst.size(); ++i) { + expr* v = var_inst[i].get(); + sub.insert(v, m.mk_var(i, m.get_sort(v))); + } + scoped_ptr rep = mk_default_expr_replacer(m); + rep->set_substitution(&sub); + expr_ref e(m); + var_subst vs(m, false); + vs(q->get_expr(), binding.size(), binding.c_ptr(), e); + (*rep)(e); + m_rules.push_back(m_current_rule); + m_apps.push_back(to_app(e)); + TRACE("pdr", tout << mk_pp(e, m) << "\n";); + } + + void quantifier_model_checker::model_check(model_node& root) { + m_apps.reset(); + m_rules.reset(); + ptr_vector nodes; + get_nodes(root, nodes); + for (unsigned i = nodes.size(); i > 0; ) { + --i; + model_check_node(*nodes[i]); + } + if (m_apps.empty()) { + return; + } + qi q(m); + for (unsigned i = 0; i < m_apps.size(); ++i) { + q.add(m_rules[i], m_apps[i].get()); + } + throw q; + } + + // As & not Body_i is satisfiable + // then instantiate with model for parameters to Body_i + + bool quantifier_model_checker::find_instantiations(qinst& qi, unsigned level) { + return + find_instantiations_proof_based(qi, level); // || + // find_instantiations_qe_based(qi, level); + } + + bool quantifier_model_checker::find_instantiations_model_based(qinst& qi, unsigned level) { + bool found_instance = false; + expr_ref C(m); + front_end_params fparams; + smt::solver solver(m, fparams); + solver.assert_expr(m_A); + for (unsigned i = 0; i < m_Bs.size(); ++i) { + expr_ref_vector fresh_vars(m); + quantifier* q = qi.quantifiers[i].get(); + for (unsigned j = 0; j < q->get_num_decls(); ++j) { + fresh_vars.push_back(m.mk_fresh_const("V",q->get_decl_sort(j))); + } + var_subst varsubst(m, false); + varsubst(m_Bs[i].get(), fresh_vars.size(), fresh_vars.c_ptr(), C); + TRACE("pdr", tout << "updated propagation formula: " << mk_pp(C,m) << "\n";); + + solver.push(); + // TBD: what to do with the different tags when unfolding the same predicate twice? + solver.assert_expr(m.mk_not(C)); + lbool result = solver.check(); + if (result == l_true) { + found_instance = true; + model_ref mr; + solver.get_model(mr); + TRACE("pdr", model_smt2_pp(tout, m, *mr, 0);); + + expr_ref_vector insts(m); + for (unsigned j = 0; j < fresh_vars.size(); ++j) { + expr* interp = mr->get_const_interp(to_app(fresh_vars[j].get())->get_decl()); + if (interp) { + insts.push_back(interp); + } + else { + insts.push_back(fresh_vars[j].get()); + } + TRACE("pdr", tout << mk_pp(insts.back(), m) << "\n";); + } + add_binding(q, insts); + } + solver.pop(1); + } + return found_instance; + } + + // + // Build: + // + // A & forall x . B1 & forall y . B2 & ... + // = + // not exists x y . (!A or !B1 or !B2 or ...) + // + // Find an instance that satisfies formula. + // (or find all instances?) + // + bool quantifier_model_checker::find_instantiations_qe_based(qinst& qi, unsigned level) { + expr_ref_vector fmls(m), conjs(m), fresh_vars(m); + app_ref_vector all_vars(m); + expr_ref C(m); + qe::def_vector defs(m); + front_end_params fparams; + qe::expr_quant_elim qe(m, fparams); + for (unsigned i = 0; i < m_Bs.size(); ++i) { + quantifier* q = qi.quantifiers[i].get(); + unsigned num_decls = q->get_num_decls(); + unsigned offset = all_vars.size(); + for (unsigned j = 0; j < num_decls; ++j) { + all_vars.push_back(m.mk_fresh_const("V",q->get_decl_sort(j))); + } + var_subst varsubst(m, false); + varsubst(m_Bs[i].get(), num_decls, (expr**)(all_vars.c_ptr() + offset), C); + fmls.push_back(C); + } + conjs.push_back(m_A); + conjs.push_back(m.mk_not(m.mk_and(fmls.size(), fmls.c_ptr()))); + // add previous instances. + expr* r = m.mk_and(m_Bs.size(), m_Bs.c_ptr()); + m_trail.push_back(r); + expr* inst; + if (!m_bound.find(m_current_rule, r, inst)) { + TRACE("pdr", tout << "did not find: " << mk_pp(r, m) << "\n";); + m_trail.push_back(r); + inst = m.mk_true(); + m_bound.insert(m_current_rule, r, inst); + } + else { + TRACE("pdr", tout << "blocking: " << mk_pp(inst, m) << "\n";); + conjs.push_back(inst); + } + C = m.mk_and(conjs.size(), conjs.c_ptr()); + lbool result = qe.first_elim(all_vars.size(), all_vars.c_ptr(), C, defs); + TRACE("pdr", tout << mk_pp(C.get(), m) << "\n" << result << "\n";); + if (result != l_true) { + return false; + } + inst = m.mk_and(inst, m.mk_not(C)); + m_trail.push_back(inst); + m_bound.insert(m_current_rule, r, inst); + TRACE("pdr", + tout << "Instantiating\n"; + for (unsigned i = 0; i < defs.size(); ++i) { + tout << defs.var(i)->get_name() << " " << mk_pp(defs.def(i), m) << "\n"; + } + ); + expr_substitution sub(m); + for (unsigned i = 0; i < defs.size(); ++i) { + sub.insert(m.mk_const(defs.var(i)), defs.def(i)); + } + scoped_ptr rep = mk_default_expr_replacer(m); + rep->set_substitution(&sub); + for (unsigned i = 0; i < all_vars.size(); ++i) { + expr_ref tmp(all_vars[i].get(), m); + (*rep)(tmp); + all_vars[i] = to_app(tmp); + } + unsigned offset = 0; + for (unsigned i = 0; i < m_Bs.size(); ++i) { + quantifier* q = qi.quantifiers[i].get(); + unsigned num_decls = q->get_num_decls(); + expr_ref_vector new_binding(m); + for (unsigned j = 0; j < num_decls; ++j) { + new_binding.push_back(all_vars[offset+j].get()); + } + offset += num_decls; + add_binding(q, new_binding); + } + return true; + } + + class collect_insts { + ast_manager& m; + ptr_vector m_binding; + vector m_bindings; + ptr_vector m_quantifiers; + public: + collect_insts(ast_manager& m): m(m) { } + + void operator()(expr* n) { + expr* not_q_or_i, *e1, *e2, *e3; + if (m.is_quant_inst(n, not_q_or_i, m_binding)) { + VERIFY(m.is_or(not_q_or_i, e1, e2)); + VERIFY(m.is_not(e1, e3)); + SASSERT(is_quantifier(e3)); + m_quantifiers.push_back(to_quantifier(e3)); + m_bindings.push_back(expr_ref_vector(m,m_binding.size(), m_binding.c_ptr())); + m_binding.reset(); + } + else if ((m.is_rewrite(n, e1, e2) || + (m.is_rewrite_star(n) && + (e3 = to_app(n)->get_arg(to_app(n)->get_num_args()-1), + e1 = to_app(e3)->get_arg(0), + e2 = to_app(e3)->get_arg(1), + true))) && + is_quantifier(e1) && m.is_false(e2)) { + quantifier* q = to_quantifier(e1); + m_quantifiers.push_back(q); + m_bindings.push_back(expr_ref_vector(m)); + expr_ref_vector& b = m_bindings.back(); + for (unsigned i = 0; i < q->get_num_decls(); ++i) { + b.push_back(m.mk_fresh_const("V", q->get_decl_sort(i))); + } + } + } + + void reset() { + m_quantifiers.reset(); + m_bindings.reset(); + } + + unsigned size() const { return m_quantifiers.size(); } + ptr_vector const& quantifiers() const { return m_quantifiers; } + vector const& bindings() const { return m_bindings; } + + }; + + bool quantifier_model_checker::find_instantiations_proof_based(qinst& qi, unsigned level) { + bool found_instance = false; + TRACE("pdr", tout << mk_pp(m_A,m) << "\n";); + + ast_manager mp(PGM_COARSE); + ast_translation tr(m, mp); + ast_translation rev_tr(mp, m); + expr_ref_vector fmls(mp); + front_end_params fparams; + fparams.m_proof_mode = PGM_COARSE; + // TBD: does not work on integers: fparams.m_mbqi = true; + expr_ref C(m); + fmls.push_back(tr(m_A.get())); + for (unsigned i = 0; i < m_Bs.size(); ++i) { + C = m.update_quantifier(qi.quantifiers[i].get(), m_Bs[i].get()); + fmls.push_back(tr(C.get())); + } + TRACE("pdr", + tout << "assert\n"; + for (unsigned i = 0; i < fmls.size(); ++i) { + tout << mk_pp(fmls[i].get(), mp) << "\n"; + }); + smt::solver solver(mp, fparams); + for (unsigned i = 0; i < fmls.size(); ++i) { + solver.assert_expr(fmls[i].get()); + } + lbool result = solver.check(); + if (result != l_false) { + TRACE("pdr", tout << result << "\n";); + return found_instance; + } + map qid_map; + quantifier* q; + for (unsigned i = 0; i < qi.quantifiers.size(); ++i) { + q = qi.quantifiers[i].get(); + qid_map.insert(q->get_qid(), q); + } + + proof* p = solver.get_proof(); + TRACE("pdr", tout << mk_ismt2_pp(p, mp) << "\n";); + collect_insts collector(mp); + for_each_expr(collector, p); + ptr_vector const& quants = collector.quantifiers(); + + for (unsigned i = 0; i < collector.size(); ++i) { + symbol qid = quants[i]->get_qid(); + if (!qid_map.find(qid, q)) { + TRACE("pdr", tout << "Could not find quantifier " + << mk_pp(quants[i], mp) << "\n";); + continue; + } + expr_ref_vector const& binding = collector.bindings()[i]; + + TRACE("pdr", tout << "Instantiating:\n" << mk_pp(quants[i], mp) << "\n"; + for (unsigned j = 0; j < binding.size(); ++j) { + tout << mk_pp(binding[j], mp) << " "; + } + tout << "\n";); + + expr_ref_vector new_binding(m); + for (unsigned j = 0; j < binding.size(); ++j) { + new_binding.push_back(rev_tr(binding[j])); + } + add_binding(q, new_binding); + found_instance = true; + } + if (collector.size() == 0) { + // Try to create dummy instances. + for (unsigned i = 0; i < m_Bs.size(); ++i) { + q = qi.quantifiers[i].get(); + expr_ref_vector binding(m); + for (unsigned j = 0; j < q->get_num_decls(); ++j) { + binding.push_back(m.mk_fresh_const("B", q->get_decl_sort(j))); + } + add_binding(q, binding); + } + found_instance = true; + } + return found_instance; + } + + + void quantifier_model_checker::model_check_node(model_node& node) { + TRACE("pdr", node.display(tout, 0);); + pred_transformer& pt = node.pt(); + manager& pm = pt.get_pdr_manager(); + expr_ref A(m), B(m), C(m); + expr_ref_vector As(m); + m_Bs.reset(); + if (!pt.has_quantifiers()) { + return; + } + // + // nodes from leaves that are repeated + // inside the search tree don't have models. + // + if (!(&node.model())) { + return; + } + m_current_rule = pt.find_rule(node.model()); + m_current_pt = &pt; + m_current_node = &node; + if (!m_current_rule) { + return; + } + qinst* qi = pt.get_quantifiers(m_current_rule); + if (!qi) { + return; + } + unsigned level = node.level(); + unsigned previous_level = (level == 0)?0:(level-1); + + As.push_back(pt.get_propagation_formula(m_ctx.get_pred_transformers(), level)); + As.push_back(node.state()); + As.push_back(pt.rule2tag(m_current_rule)); + m_A = pm.mk_and(As); + + // Add quantifiers: + scoped_ptr rep = mk_default_expr_replacer(m); + for (unsigned j = 0; j < qi->quantifiers.size(); ++j) { + quantifier* q = qi->quantifiers[j].get(); + app* a = to_app(q->get_expr()); + func_decl* f = a->get_decl(); + pred_transformer& pt2 = m_ctx.get_pred_transformer(f); + B = pt2.get_formulas(previous_level, true); + TRACE("pdr", tout << mk_pp(B, m) << "\n";); + + expr_substitution sub(m); + for (unsigned i = 0; i < a->get_num_args(); ++i) { + expr* v = m.mk_const(pm.o2n(pt2.sig(i),0)); + sub.insert(v, a->get_arg(i)); + } + rep->set_substitution(&sub); + // TBD: exclude previous bindings here. + (*rep)(B); + m_Bs.push_back(B); + TRACE("pdr", + tout << mk_pp(q, m) << "\n"; + tout << "propagation formula: " << mk_pp(B, m) << "\n";); + } + find_instantiations(*qi, level); + } + + void quantifier_model_checker::refine(qi& q, datalog::rule_set& rules) { + IF_VERBOSE(1, verbose_stream() << "instantiating quantifiers\n";); + datalog::rule_manager& rule_m = rules.get_rule_manager(); + datalog::rule_set new_rules(rules.get_context()); + datalog::rule_set::iterator it = rules.begin(), end = rules.end(); + for (; it != end; ++it) { + datalog::rule* r = *it; + app_ref_vector body(m); + for (unsigned i = 0; i < q.size(); ++i) { + if (r == q.get_rule(i)) { + body.push_back(q.get_app(i)); + } + } + if (body.empty()) { + new_rules.add_rule(r); + } + else { + for (unsigned i = 0; i < r->get_tail_size(); ++i) { + body.push_back(r->get_tail(i)); + } + datalog::rule_ref_vector rules(rule_m); + expr_ref rule(m.mk_implies(m.mk_and(body.size(), (expr*const*)body.c_ptr()), r->get_head()), m); + rule_m.mk_rule(rule, rules, r->name()); + for (unsigned i = 0; i < rules.size(); ++i) { + new_rules.add_rule(rules[i].get()); + } + } + } + new_rules.close(); + rules.reset(); + rules.add_rules(new_rules); + rules.close(); + m_ctx.update_rules(rules); + TRACE("pdr", rules.display(tout);); + } +}; diff --git a/lib/pdr_quantifiers.h b/lib/pdr_quantifiers.h new file mode 100644 index 000000000..45e8640e4 --- /dev/null +++ b/lib/pdr_quantifiers.h @@ -0,0 +1,111 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + pdr_quantifiers.h + +Abstract: + + Module for handling quantifiers in rule bodies. + +Author: + + Nikolaj Bjorner (nbjorner) 2012-05-19. + +Revision History: + +--*/ + +#ifndef _PDR_QUANTIFIERS_H_ +#define _PDR_QUANTIFIERS_H_ + +#include "ast.h" +#include "dl_rule.h" +#include "obj_pair_hashtable.h" + +namespace datalog { + class rule_set; +}; + +namespace pdr { + + class model_node; + class pred_transformer; + class context; + + struct qinst { + quantifier_ref_vector quantifiers; // quantifiers in rule body. + func_decl_ref_vector predicates; // predicates in order of bindings. + expr_ref_vector bindings; // the actual instantiations of the predicates + qinst(ast_manager& m): quantifiers(m), predicates(m), bindings(m) {} + }; + + class qi { + ptr_vector m_rules; + app_ref_vector m_apps; + public: + qi(ast_manager& m) : m_apps(m) {} + void add(datalog::rule const* r, app* a) { + m_rules.push_back(r); + m_apps.push_back(a); + } + unsigned size() const { return m_rules.size(); } + datalog::rule const* get_rule(unsigned i) const { return m_rules[i]; } + app* get_app(unsigned i) const { return m_apps[i]; } + }; + + + class quantifier_model_checker { + context& m_ctx; + ast_manager& m; + obj_pair_map m_bound; + expr_ref_vector m_trail; + expr_ref m_A; + expr_ref_vector m_Bs; + pred_transformer* m_current_pt; + datalog::rule const* m_current_rule; + model_node* m_current_node; + + ptr_vector m_rules; + app_ref_vector m_apps; + + void model_check_node(model_node& node); + + bool find_instantiations(qinst& qi, unsigned level); + + bool find_instantiations_model_based(qinst& qi, unsigned level); + + bool find_instantiations_proof_based(qinst& qi, unsigned level); + + bool find_instantiations_qe_based(qinst& qi, unsigned level); + + void add_binding(quantifier* q, expr_ref_vector& binding); + + void apply_binding(quantifier* q, expr_ref_vector& binding); + + void generalize_binding(expr_ref_vector const& binding, vector& bindings); + + void generalize_binding(expr_ref_vector const& binding, unsigned idx, expr_ref_vector& new_binding, vector& bindings); + + public: + quantifier_model_checker(context& ctx, ast_manager& m): + m_ctx(ctx), + m(m), m_trail(m), m_A(m), m_Bs(m), + m_current_pt(0), m_current_rule(0), + m_current_node(0), m_apps(m) {} + + /** + \brief model check a potential model against quantifiers in bodies of rules. + + \return true if the model rooted in 'root' is checks with the quantifiers, otherwise + 'false' and a set of instantiations that contradict the current model. + */ + + void model_check(model_node& root); + + void refine(qi& q, datalog::rule_set& rules); + }; + +}; +#endif diff --git a/lib/pdr_reachable_cache.cpp b/lib/pdr_reachable_cache.cpp new file mode 100644 index 000000000..baa18839c --- /dev/null +++ b/lib/pdr_reachable_cache.cpp @@ -0,0 +1,126 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + reachable_cache.cpp + +Abstract: + + Object for caching of reachable states. + +Author: + + Krystof Hoder (t-khoder) 2011-9-14. + +Revision History: + +--*/ + +#include "pdr_reachable_cache.h" + +namespace pdr { + + reachable_cache::reachable_cache(pdr::manager & pm, params_ref const& params) + : m(pm.get_manager()), + m_pm(pm), + m_ctx(0), + m_ref_holder(m), + m_disj_connector(m), + m_cache_hits(0), + m_cache_miss(0), + m_cache_inserts(0), + m_cache_mode((datalog::PDR_CACHE_MODE)params.get_uint(":cache-mode",0)) { + if (m_cache_mode == datalog::CONSTRAINT_CACHE) { + m_ctx = pm.mk_fresh(); + m_ctx->assert_expr(m_pm.get_background()); + } + } + + + void reachable_cache::add_disjuncted_formula(expr * f) { + app_ref new_connector(m.mk_fresh_const("disj_conn", m.mk_bool_sort()), m); + app_ref neg_new_connector(m.mk_not(new_connector), m); + app_ref extended_form(m); + + if(m_disj_connector) { + extended_form = m.mk_or(m_disj_connector, neg_new_connector, f); + } + else { + extended_form = m.mk_or(neg_new_connector, f); + } + if (m_ctx) { + m_ctx->assert_expr(extended_form); + } + + m_disj_connector = new_connector; + } + + void reachable_cache::add_reachable(expr * cube) { + + switch (m_cache_mode) { + case datalog::NO_CACHE: + break; + + case datalog::HASH_CACHE: + m_cache_inserts++; + m_cache.insert(cube); + m_ref_holder.push_back(cube); + break; + + case datalog::CONSTRAINT_CACHE: + m_cache_inserts++; + TRACE("pdr", tout << mk_pp(cube, m) << "\n";); + add_disjuncted_formula(cube); + break; + + default: + UNREACHABLE(); + } + } + + bool reachable_cache::is_reachable(expr * cube) { + bool found = false; + switch (m_cache_mode) { + case datalog::NO_CACHE: + return false; + + case datalog::HASH_CACHE: + found = m_cache.contains(cube); + break; + + case datalog::CONSTRAINT_CACHE: { + if(!m_disj_connector) { + found = false; + break; + } + expr * connector = m_disj_connector.get(); + expr_ref_vector assms(m); + assms.push_back(connector); + m_ctx->push(); + m_ctx->assert_expr(cube); + lbool res = m_ctx->check(assms); + m_ctx->pop(); + + TRACE("pdr", tout << "is_reachable: " << res << " " << mk_pp(cube, m) << "\n";); + + found = res == l_true; + break; + } + + default: + UNREACHABLE(); + break; + } + if (found) m_cache_hits++; m_cache_miss++; + return found; + } + + void reachable_cache::collect_statistics(statistics& st) const { + st.update("cache inserts", m_cache_inserts); + st.update("cache miss", m_cache_miss); + st.update("cache hits", m_cache_hits); + } + + +} diff --git a/lib/pdr_reachable_cache.h b/lib/pdr_reachable_cache.h new file mode 100644 index 000000000..d40e0e703 --- /dev/null +++ b/lib/pdr_reachable_cache.h @@ -0,0 +1,59 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + reachable_cache.h + +Abstract: + + Object for caching of reachable states. + +Author: + + Krystof Hoder (t-khoder) 2011-9-14. + +Revision History: + +--*/ + +#include "ast.h" +#include "params.h" +#include "ref_vector.h" +#include "pdr_manager.h" +#include "pdr_smt_context_manager.h" + +#ifndef _REACHABLE_CACHE_H_ +#define _REACHABLE_CACHE_H_ + +namespace pdr { + class reachable_cache { + ast_manager & m; + manager & m_pm; + scoped_ptr m_ctx; + ast_ref_vector m_ref_holder; + app_ref m_disj_connector; + obj_hashtable m_cache; + unsigned m_cache_hits; + unsigned m_cache_miss; + unsigned m_cache_inserts; + datalog::PDR_CACHE_MODE m_cache_mode; + + void add_disjuncted_formula(expr * f); + + public: + reachable_cache(pdr::manager & pm, params_ref const& params); + + void add_init(app * f) { add_disjuncted_formula(f); } + + /** add cube whose all models are reachable */ + void add_reachable(expr * cube); + + /** return true if there is a model of cube which is reachable */ + bool is_reachable(expr * cube); + + void collect_statistics(statistics& st) const; + }; +} + +#endif diff --git a/lib/pdr_smt_context_manager.cpp b/lib/pdr_smt_context_manager.cpp new file mode 100644 index 000000000..6b881a019 --- /dev/null +++ b/lib/pdr_smt_context_manager.cpp @@ -0,0 +1,133 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + pdr_smt_context_manager.cpp + +Abstract: + + Manager of smt contexts + +Author: + + Nikolaj Bjorner (nbjorner) 2011-11-26. + +Revision History: + +--*/ + +#include "pdr_smt_context_manager.h" +#include "has_free_vars.h" +#include "ast_pp.h" +#include +#include "front_end_params.h" + +namespace pdr { + + smt_context::smt_context(smt_context_manager& p, ast_manager& m, app* pred): + m_parent(p), + m_pred(pred, m), + m_in_delay_scope(false), + m_pushed(false) + {} + + bool smt_context::is_aux_predicate(func_decl* p) { + return m_parent.is_aux_predicate(p); + } + + smt_context::scoped::scoped(smt_context& ctx): m_ctx(ctx) { + SASSERT(!m_ctx.m_in_delay_scope); + SASSERT(!m_ctx.m_pushed); + m_ctx.m_in_delay_scope = true; + } + + smt_context::scoped::~scoped() { + SASSERT(m_ctx.m_in_delay_scope); + if (m_ctx.m_pushed) { + m_ctx.pop(); + m_ctx.m_pushed = false; + } + m_ctx.m_in_delay_scope = false; + } + + + _smt_context::_smt_context(smt::solver & ctx, smt_context_manager& p, app* pred): + smt_context(p, ctx.m(), pred), + m_context(ctx) + {} + + void _smt_context::assert_expr(expr* e) { + ast_manager& m = m_context.m(); + if (m.is_true(e)) { + return; + } + CTRACE("pdr", has_free_vars(e), tout << mk_pp(e, m) << "\n";); + SASSERT(!has_free_vars(e)); + if (m_in_delay_scope && !m_pushed) { + m_context.push(); + m_pushed = true; + } + expr_ref fml(m); + fml = m_pushed?e:m.mk_implies(m_pred, e); + m_context.assert_expr(fml); + } + + lbool _smt_context::check(expr_ref_vector& assumptions) { + ast_manager& m = m_pred.get_manager(); + if (!m.is_true(m_pred)) { + assumptions.push_back(m_pred); + } + lbool result = m_context.check(assumptions.size(), assumptions.c_ptr()); + if (!m.is_true(m_pred)) { + assumptions.pop_back(); + } + return result; + } + + void _smt_context::get_model(model_ref& model) { + m_context.get_model(model); + } + + smt_context_manager::smt_context_manager(front_end_params& fp, params_ref const& p, ast_manager& m): + m_fparams(fp), m_max_num_contexts(p.get_uint(":max-num-contexts", 500)), + m(m), m_num_contexts(0), m_predicate_list(m) {} + + + smt_context_manager::~smt_context_manager() { + TRACE("pdr",tout << "\n";); + std::for_each(m_contexts.begin(), m_contexts.end(), delete_proc()); + } + + smt_context* smt_context_manager::mk_fresh() { + ++m_num_contexts; + app_ref pred(m); + smt::solver * ctx = 0; + if (m_max_num_contexts == 0) { + m_contexts.push_back(alloc(smt::solver, m, m_fparams)); + pred = m.mk_true(); + ctx = m_contexts[m_num_contexts-1]; + } + else { + if (m_contexts.size() < m_max_num_contexts) { + m_contexts.push_back(alloc(smt::solver, m, m_fparams)); + } + std::stringstream name; + name << "#context" << m_num_contexts; + pred = m.mk_const(symbol(name.str().c_str()), m.mk_bool_sort()); + m_predicate_list.push_back(pred); + m_predicate_set.insert(pred->get_decl()); + ctx = m_contexts[(m_num_contexts-1)%m_max_num_contexts]; + } + return alloc(_smt_context, *ctx, *this, pred); + } + + void smt_context_manager::collect_statistics(statistics& st) const { + for (unsigned i = 0; i < m_contexts.size(); ++i) { + m_contexts[i]->collect_statistics(st); + } + } + + +}; + diff --git a/lib/pdr_smt_context_manager.h b/lib/pdr_smt_context_manager.h new file mode 100644 index 000000000..718c5dd02 --- /dev/null +++ b/lib/pdr_smt_context_manager.h @@ -0,0 +1,105 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + pdr_smt_context_manager.h + +Abstract: + + Manager of smt contexts + +Author: + + Nikolaj Bjorner (nbjorner) 2011-11-26. + +Revision History: + +--*/ + +#ifndef _PDR_SMT_CONTEXT_MANAGER_H_ +#define _PDR_SMT_CONTEXT_MANAGER_H_ + +#include "smt_solver.h" +#include "sat_solver.h" +#include "func_decl_dependencies.h" + +namespace pdr { + + class smt_context_manager; + + class smt_context { + protected: + app_ref m_pred; + smt_context_manager& m_parent; + bool m_in_delay_scope; + bool m_pushed; + public: + smt_context(smt_context_manager& p, ast_manager& m, app* pred); + virtual ~smt_context() {} + virtual void assert_expr(expr* e) = 0; + virtual lbool check(expr_ref_vector& assumptions) = 0; + virtual void get_model(model_ref& model) = 0; + virtual unsigned get_unsat_core_size() = 0; + virtual expr* get_unsat_core_expr(unsigned i) = 0; + virtual void push() = 0; + virtual void pop() = 0; + bool is_aux_predicate(func_decl* p); + bool is_aux_predicate(expr* p) { return is_app(p) && is_aux_predicate(to_app(p)->get_decl()); } + class scoped { + smt_context& m_ctx; + public: + scoped(smt_context& ctx); + ~scoped(); + }; + }; + + class _smt_context : public smt_context { + smt::solver & m_context; + public: + _smt_context(smt::solver & ctx, smt_context_manager& p, app* pred); + virtual ~_smt_context() {} + virtual void assert_expr(expr* e); + virtual lbool check(expr_ref_vector& assumptions); + virtual void get_model(model_ref& model); + virtual void push() { m_context.push(); } + virtual void pop() { m_context.pop(1); } + virtual unsigned get_unsat_core_size() { return m_context.get_unsat_core_size(); } + virtual expr* get_unsat_core_expr(unsigned i) { return m_context.get_unsat_core_expr(i); } + }; + + // TBD: + class sat_context : public smt_context { + sat::solver m_solver; + public: + sat_context(smt::solver & ctx, smt_context_manager& p, app* pred); + virtual ~sat_context() {} + virtual void assert_expr(expr* e); + virtual lbool check(expr_ref_vector& assumptions); + virtual void get_model(model_ref& model); + virtual void pop() { m_solver.pop(1); } + virtual void push() { m_solver.push(); } + // TBD: add unsat core extraction with sat::solver. + virtual unsigned get_unsat_core_size(); + virtual expr* get_unsat_core_expr(unsigned i); + }; + + class smt_context_manager { + front_end_params& m_fparams; + ast_manager& m; + unsigned m_max_num_contexts; + ptr_vector m_contexts; + unsigned m_num_contexts; + app_ref_vector m_predicate_list; + func_decl_set m_predicate_set; + public: + smt_context_manager(front_end_params& fp, params_ref const& p, ast_manager& m); + ~smt_context_manager(); + smt_context* mk_fresh(); + void collect_statistics(statistics& st) const; + bool is_aux_predicate(func_decl* p) const { return m_predicate_set.contains(p); } + }; + +}; + +#endif diff --git a/lib/pdr_sym_mux.cpp b/lib/pdr_sym_mux.cpp new file mode 100644 index 000000000..f17177a7f --- /dev/null +++ b/lib/pdr_sym_mux.cpp @@ -0,0 +1,562 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sym_mux.cpp + +Abstract: + + A symbol multiplexer that helps with having multiple versions of each of a set of symbols. + +Author: + + Krystof Hoder (t-khoder) 2011-9-8. + +Revision History: + +--*/ + +#include +#include "ast_pp.h" +#include "for_each_expr.h" +#include "model.h" +#include "rewriter.h" +#include "rewriter_def.h" +#include "pdr_util.h" +#include "pdr_sym_mux.h" + +using namespace pdr; + +sym_mux::sym_mux(ast_manager & m, const vector & suffixes) + : m(m), m_ref_holder(m), m_next_sym_suffix_idx(0), m_suffixes(suffixes) +{ + unsigned suf_sz = m_suffixes.size(); + for(unsigned i = 0; i < suf_sz; ++i) { + symbol suff_sym = symbol(m_suffixes[i].c_str()); + m_used_suffixes.insert(suff_sym); + } +} + +std::string sym_mux::get_suffix(unsigned i) const { + while(m_suffixes.size() <= i) { + std::string new_suffix; + symbol new_syffix_sym; + do { + std::stringstream stm; + stm<<'_'<0); + while(tuple.size()get_name().str(); + for(unsigned i=0; iget_arity()==arity); + SASSERT(tuple[i]->get_range()==range); + //domain should match as well, but we won't bother checking an array equality + } + else { + std::string name = pre+get_suffix(i); + tuple[i] = m.mk_func_decl(symbol(name.c_str()), arity, domain, range); + } + m_ref_holder.push_back(tuple[i]); + m_sym2idx.insert(tuple[i], i); + m_sym2prim.insert(tuple[i], tuple[0]); + } + + m_prim2all.insert(tuple[0], tuple); + m_prefix2prim.insert(prefix, tuple[0]); + m_prim2prefix.insert(tuple[0], prefix); + m_prim_preds.push_back(tuple[0]); + m_ref_holder.push_back(prefix); +} + +void sym_mux::ensure_tuple_size(func_decl * prim, unsigned sz) const { + SASSERT(m_prim2all.contains(prim)); + decl_vector& tuple = m_prim2all.find_core(prim)->get_data().m_value; + SASSERT(tuple[0]==prim); + + if(sz <= tuple.size()) { return; } + + func_decl * prefix; + TRUSTME(m_prim2prefix.find(prim, prefix)); + std::string prefix_name = prefix->get_name().bare_str(); + for(unsigned i=tuple.size(); iget_arity(), + prefix->get_domain(), prefix->get_range()); + + tuple.push_back(new_sym); + m_ref_holder.push_back(new_sym); + m_sym2idx.insert(new_sym, i); + m_sym2prim.insert(new_sym, prim); + } +} + +func_decl * sym_mux::conv(func_decl * sym, unsigned src_idx, unsigned tgt_idx) const +{ + if(src_idx==tgt_idx) { return sym; } + func_decl * prim = (src_idx==0) ? sym : get_primary(sym); + if(tgt_idx>src_idx) { + ensure_tuple_size(prim, tgt_idx+1); + } + decl_vector & sym_vect = m_prim2all.find_core(prim)->get_data().m_value; + SASSERT(sym_vect[src_idx]==sym); + return sym_vect[tgt_idx]; +} + + +func_decl * sym_mux::get_or_create_symbol_by_prefix(func_decl* prefix, unsigned idx, + unsigned arity, sort * const * domain, sort * range) +{ + func_decl * prim = try_get_primary_by_prefix(prefix); + if(prim) { + SASSERT(prim->get_arity()==arity); + SASSERT(prim->get_range()==range); + //domain should match as well, but we won't bother checking an array equality + + return conv(prim, 0, idx); + } + + decl_vector syms; + create_tuple(prefix, arity, domain, range, idx+1, syms); + return syms[idx]; +} + +bool sym_mux::is_muxed_lit(expr * e, unsigned idx) const +{ + if(!is_app(e)) { return false; } + app * a = to_app(e); + if(m.is_not(a) && is_app(a->get_arg(0))) { + a = to_app(a->get_arg(0)); + } + return is_muxed(a->get_decl()); +} + + +struct sym_mux::formula_checker +{ + formula_checker(const sym_mux & parent, bool all, unsigned idx) : + m_parent(parent), m_all(all), m_idx(idx), + m_found_what_needed(false) + { + } + + void operator()(expr * e) + { + if(m_found_what_needed || !is_app(e)) { return; } + + func_decl * sym = to_app(e)->get_decl(); + unsigned sym_idx; + if(!m_parent.try_get_index(sym, sym_idx)) { return; } + + bool have_idx = sym_idx==m_idx; + + if( m_all ? (!have_idx) : have_idx ) { + m_found_what_needed = true; + } + + } + + bool all_have_idx() const + { + SASSERT(m_all); //we were looking for the queried property + return !m_found_what_needed; + } + + bool some_with_idx() const + { + SASSERT(!m_all); //we were looking for the queried property + return m_found_what_needed; + } + +private: + const sym_mux & m_parent; + bool m_all; + unsigned m_idx; + + /** + If we check whether all muxed symbols are of given index, we look for + counter-examples, checking whether form contains a muxed symbol of an index, + we look for symbol of index m_idx. + */ + bool m_found_what_needed; +}; + +bool sym_mux::contains(expr * e, unsigned idx) const +{ + formula_checker chck(*this, false, idx); + for_each_expr(chck, m_visited, e); + m_visited.reset(); + return chck.some_with_idx(); +} + +bool sym_mux::is_homogenous_formula(expr * e, unsigned idx) const +{ + formula_checker chck(*this, true, idx); + for_each_expr(chck, m_visited, e); + m_visited.reset(); + return chck.all_have_idx(); +} + +bool sym_mux::is_homogenous(const expr_ref_vector & vect, unsigned idx) const +{ + expr * const * begin = vect.c_ptr(); + expr * const * end = begin + vect.size(); + for(expr * const * it = begin; it!=end; it++) { + if(!is_homogenous_formula(*it, idx)) { + return false; + } + } + return true; +} + + +class sym_mux::hmg_checker { + const sym_mux & m_parent; + + bool m_found_idx; + unsigned m_idx; + bool m_multiple_indexes; + +public: + hmg_checker(const sym_mux & parent) : + m_parent(parent), m_found_idx(false), m_multiple_indexes(false) + { + } + + void operator()(expr * e) + { + if(m_multiple_indexes || !is_app(e)) { return; } + + func_decl * sym = to_app(e)->get_decl(); + unsigned sym_idx; + if(!m_parent.try_get_index(sym, sym_idx)) { return; } + + if(!m_found_idx) { + m_found_idx = true; + m_idx = sym_idx; + return; + } + if(m_idx==sym_idx) { return; } + m_multiple_indexes = true; + } + + bool has_multiple_indexes() const + { + return m_multiple_indexes; + } +}; + +bool sym_mux::is_homogenous_formula(expr * e) const { + hmg_checker chck(*this); + for_each_expr(chck, m_visited, e); + m_visited.reset(); + return !chck.has_multiple_indexes(); +} + + +struct sym_mux::conv_rewriter_cfg : public default_rewriter_cfg +{ +private: + ast_manager & m; + const sym_mux & m_parent; + unsigned m_from_idx; + unsigned m_to_idx; + bool m_homogenous; +public: + conv_rewriter_cfg(const sym_mux & parent, unsigned from_idx, unsigned to_idx, bool homogenous) + : m(parent.get_manager()), + m_parent(parent), + m_from_idx(from_idx), + m_to_idx(to_idx), + m_homogenous(homogenous) {} + + bool get_subst(expr * s, expr * & t, proof * & t_pr) { + if(!is_app(s)) { return false; } + app * a = to_app(s); + func_decl * sym = a->get_decl(); + if(!m_parent.has_index(sym, m_from_idx)) { + SASSERT(!m_homogenous || !m_parent.is_muxed(sym)); + return false; + } + func_decl * tgt = m_parent.conv(sym, m_from_idx, m_to_idx); + + t = m.mk_app(tgt, a->get_args()); + return true; + } +}; + +void sym_mux::conv_formula(expr * f, unsigned src_idx, unsigned tgt_idx, expr_ref & res, bool homogenous) const +{ + if(src_idx==tgt_idx) { + res = f; + return; + } + conv_rewriter_cfg r_cfg(*this, src_idx, tgt_idx, homogenous); + rewriter_tpl rwr(m, false, r_cfg); + rwr(f, res); +} + +struct sym_mux::shifting_rewriter_cfg : public default_rewriter_cfg +{ +private: + ast_manager & m; + const sym_mux & m_parent; + int m_shift; +public: + shifting_rewriter_cfg(const sym_mux & parent, int shift) + : m(parent.get_manager()), + m_parent(parent), + m_shift(shift) {} + + bool get_subst(expr * s, expr * & t, proof * & t_pr) { + if(!is_app(s)) { return false; } + app * a = to_app(s); + func_decl * sym = a->get_decl(); + + unsigned idx; + if(!m_parent.try_get_index(sym, idx)) { + return false; + } + SASSERT(static_cast(idx)+m_shift>=0); + func_decl * tgt = m_parent.conv(sym, idx, idx+m_shift); + t = m.mk_app(tgt, a->get_args()); + return true; + } +}; + +void sym_mux::shift_formula(expr * f, int dist, expr_ref & res) const +{ + if(dist==0) { + res = f; + return; + } + shifting_rewriter_cfg r_cfg(*this, dist); + rewriter_tpl rwr(m, false, r_cfg); + rwr(f, res); +} + +void sym_mux::conv_formula_vector(const expr_ref_vector & vect, unsigned src_idx, unsigned tgt_idx, + expr_ref_vector & res) const +{ + res.reset(); + expr * const * begin = vect.c_ptr(); + expr * const * end = begin + vect.size(); + for(expr * const * it = begin; it!=end; it++) { + expr_ref converted(m); + conv_formula(*it, src_idx, tgt_idx, converted); + res.push_back(converted); + } +} + +void sym_mux::filter_idx(expr_ref_vector & vect, unsigned idx) const { + unsigned i = 0; + while (i < vect.size()) { + expr* e = vect[i].get(); + if(contains(e, idx) && is_homogenous_formula(e, idx)) { + i++; + } + else { + //we don't allow mixing states inside vector elements + SASSERT(!contains(e, idx)); + vect[i] = vect.back(); + vect.pop_back(); + } + } +} + +void sym_mux::partition_o_idx( + expr_ref_vector const& lits, + expr_ref_vector& o_lits, + expr_ref_vector& other, unsigned idx) const { + + for (unsigned i = 0; i < lits.size(); ++i) { + if (contains(lits[i], idx) && is_homogenous_formula(lits[i], idx)) { + o_lits.push_back(lits[i]); + } + else { + other.push_back(lits[i]); + } + } +} + + + +class sym_mux::nonmodel_sym_checker { + const sym_mux & m_parent; + + bool m_found; +public: + nonmodel_sym_checker(const sym_mux & parent) : + m_parent(parent), m_found(false) + { + } + + void operator()(expr * e) + { + if(m_found || !is_app(e)) { return; } + + func_decl * sym = to_app(e)->get_decl(); + + if(m_parent.is_non_model_sym(sym)) { + m_found = true; + } + } + + bool found() const + { + return m_found; + } +}; + +bool sym_mux::has_nonmodel_symbol(expr * e) const { + nonmodel_sym_checker chck(*this); + for_each_expr(chck, e); + return chck.found(); +} + +void sym_mux::filter_non_model_lits(expr_ref_vector & vect) const { + unsigned i=0; + while(iget_arity()==0); + expr_ref interp(m); + get_value_from_model(mdl, d, interp); + + app_ref constant(m.mk_const(d), m); + app_ref lit(m); + if(m.is_bool(d->get_range())) { + if(m.is_true(interp)) { + lit = constant; + } + else { + SASSERT(m.is_false(interp)); + lit = m.mk_not(constant); + } + } + else { + lit = m.mk_eq(constant, interp); + } + res.push_back(lit); + } + //LOGV(5, " got cube "<get_name(), sym2->get_name()); + } +}; + +std::string sym_mux::pp_model(const model_core & mdl) const { + decl_vector consts; + unsigned sz = mdl.get_num_constants(); + for (unsigned i = 0; i < sz; i++) { + func_decl * d = mdl.get_constant(i); + consts.push_back(d); + } + + std::sort(consts.begin(), consts.end(), decl_idx_comparator(*this)); + + std::stringstream res; + + decl_vector::iterator end = consts.end(); + for(decl_vector::iterator it = consts.begin(); it!=end; it++) { + func_decl * d = *it; + std::string name = d->get_name().str(); + const char * arrow = " -> "; + res << name << arrow; + unsigned indent = static_cast(name.length() + strlen(arrow)); + res << mk_pp(mdl.get_const_interp(d), m, indent) << "\n"; + + if(it+1!=end) { + unsigned idx1, idx2; + if(!try_get_index(*it, idx1)) { idx1 = UINT_MAX; } + if(!try_get_index(*(it+1), idx2)) { idx2 = UINT_MAX; } + if(idx1!=idx2) { res << "\n"; } + } + } + return res.str(); +} + + +#if 0 + +class sym_mux::index_renamer_cfg : public default_rewriter_cfg{ + const sym_mux & m_parent; + unsigned m_idx; + +public: + index_renamer_cfg(const sym_mux & p, unsigned idx) : m_parent(p), m_idx(idx) {} + + bool get_subst(expr * s, expr * & t, proof * & t_pr) { + if (!is_app(s)) return false; + app * a = to_app(s); + if (a->get_family_id() != null_family_id) { + return false; + } + func_decl * sym = a->get_decl(); + unsigned idx; + if(!m_parent.try_get_index(sym, idx)) { + return false; + } + if (m_idx == idx) { + return false; + } + ast_manager& m = m_parent.get_manager(); + symbol name = symbol((sym->get_name().str() + "!").c_str()); + func_decl * tgt = m.mk_func_decl(name, sym->get_arity(), sym->get_domain(), sym->get_range()); + t = m.mk_app(tgt, a->get_num_args(), a->get_args()); + return true; + } +}; + +#endif diff --git a/lib/pdr_sym_mux.h b/lib/pdr_sym_mux.h new file mode 100644 index 000000000..6cc268721 --- /dev/null +++ b/lib/pdr_sym_mux.h @@ -0,0 +1,238 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sym_mux.h + +Abstract: + + A symbol multiplexer that helps with having multiple versions of each of a set of symbols. + +Author: + + Krystof Hoder (t-khoder) 2011-9-8. + +Revision History: + +--*/ + +#ifndef _SYM_MUX_H_ +#define _SYM_MUX_H_ + +#include "ast.h" +#include "map.h" +#include "vector.h" + +class model_core; + +namespace pdr { +class sym_mux +{ +public: + typedef ptr_vector app_vector; + typedef ptr_vector decl_vector; +private: + typedef obj_map sym2u; + typedef obj_map sym2dv; + typedef obj_map sym2sym; + typedef obj_map sym2pred; + typedef hashtable symbols; + + ast_manager & m; + mutable ast_ref_vector m_ref_holder; + mutable expr_mark m_visited; + + mutable unsigned m_next_sym_suffix_idx; + mutable symbols m_used_suffixes; + /** Here we have default suffixes for each of the variants */ + mutable vector m_suffixes; + + + /** + Primary symbol is the 0-th variant. This member maps from primary symbol + to vector of all its variants (including the primary variant). + */ + sym2dv m_prim2all; + + /** + For each symbol contains its variant index + */ + mutable sym2u m_sym2idx; + /** + For each symbol contains its primary variant + */ + mutable sym2sym m_sym2prim; + + /** + Maps prefixes passed to the create_tuple to + the primary symbol created from it. + */ + sym2pred m_prefix2prim; + + /** + Maps pripary symbols to prefixes that were used to create them. + */ + sym2sym m_prim2prefix; + + decl_vector m_prim_preds; + + obj_hashtable m_non_model_syms; + + struct formula_checker; + struct conv_rewriter_cfg; + struct shifting_rewriter_cfg; + class decl_idx_comparator; + class hmg_checker; + class nonmodel_sym_checker; + class index_renamer_cfg; + + std::string get_suffix(unsigned i) const; + void ensure_tuple_size(func_decl * prim, unsigned sz) const; + + expr_ref isolate_o_idx(expr* e, unsigned idx) const; +public: + sym_mux(ast_manager & m, const vector & suffixes); + + ast_manager & get_manager() const { return m; } + + bool is_muxed(func_decl * sym) const { return m_sym2idx.contains(sym); } + + bool try_get_index(func_decl * sym, unsigned & idx) const { + return m_sym2idx.find(sym,idx); + } + + bool has_index(func_decl * sym, unsigned idx) const { + unsigned actual_idx; + return try_get_index(sym, actual_idx) && idx==actual_idx; + } + + /** Return primary symbol. sym must be muxed. */ + func_decl * get_primary(func_decl * sym) const { + func_decl * prim; + TRUSTME(m_sym2prim.find(sym, prim)); + return prim; + } + + /** + Return primary symbol created from prefix, or 0 if the prefix was never used. + */ + func_decl * try_get_primary_by_prefix(func_decl* prefix) const { + func_decl * res; + if(!m_prefix2prim.find(prefix, res)) { + return 0; + } + return res; + } + + /** + Return symbol created from prefix, or 0 if the prefix was never used. + */ + func_decl * try_get_by_prefix(func_decl* prefix, unsigned idx) const { + func_decl * prim = try_get_primary_by_prefix(prefix); + if(!prim) { + return 0; + } + return conv(prim, 0, idx); + } + + /** + Marks symbol as non-model which means it will not appear in models collected by + get_muxed_cube_from_model function. + This is to take care of auxiliary symbols introduced by the disjunction relations + to relativize lemmas coming from disjuncts. + */ + void mark_as_non_model(func_decl * sym) { + SASSERT(is_muxed(sym)); + m_non_model_syms.insert(get_primary(sym)); + } + + func_decl * get_or_create_symbol_by_prefix(func_decl* prefix, unsigned idx, + unsigned arity, sort * const * domain, sort * range); + + + + bool is_muxed_lit(expr * e, unsigned idx) const; + + bool is_non_model_sym(func_decl * s) const { + return is_muxed(s) && m_non_model_syms.contains(get_primary(s)); + } + + /** + Create a multiplexed tuple of propositional constants. + Symbols may be suplied in the tuple vector, + those beyond the size of the array and those with corresponding positions + assigned to zero will be created using prefix. + Tuple length must be at least one. + */ + void create_tuple(func_decl* prefix, unsigned arity, sort * const * domain, sort * range, + unsigned tuple_length, decl_vector & tuple); + + /** + Return true if the only multiplexed symbols which e contains are of index idx. + */ + bool is_homogenous_formula(expr * e, unsigned idx) const; + bool is_homogenous(const expr_ref_vector & vect, unsigned idx) const; + + /** + Return true if all multiplexed symbols which e contains are of one index. + */ + bool is_homogenous_formula(expr * e) const; + + /** + Return true if expression e contains a muxed symbol of index idx. + */ + bool contains(expr * e, unsigned idx) const; + + + /** + Convert symbol sym which has to be of src_idx variant into variant tgt_idx. + */ + func_decl * conv(func_decl * sym, unsigned src_idx, unsigned tgt_idx) const; + + + /** + Convert src_idx symbols in formula f variant into tgt_idx. + If homogenous is true, formula cannot contain symbols of other variants. + */ + void conv_formula(expr * f, unsigned src_idx, unsigned tgt_idx, expr_ref & res, bool homogenous=true) const; + void conv_formula_vector(const expr_ref_vector & vect, unsigned src_idx, unsigned tgt_idx, + expr_ref_vector & res) const; + + /** + Shifts the muxed symbols in f by dist. Dist can be negative, but it should never shift + symbol index to a negative value. + */ + void shift_formula(expr * f, int dist, expr_ref & res) const; + + /** + Remove from vect literals (atoms or negations of atoms) of symbols + that contain multiplexed symbols with indexes other than idx. + + Each of the literals can contain only symbols multiplexed with one index + (this trivially holds if the literals are propositional). + + Order of elements in vect may be modified by this function + */ + void filter_idx(expr_ref_vector & vect, unsigned idx) const; + + /** + Partition literals into o_literals and others. + */ + void partition_o_idx(expr_ref_vector const& lits, + expr_ref_vector& o_lits, + expr_ref_vector& other, unsigned idx) const; + + bool has_nonmodel_symbol(expr * e) const; + void filter_non_model_lits(expr_ref_vector & vect) const; + + func_decl * const * begin_prim_preds() const { return m_prim_preds.begin(); } + func_decl * const * end_prim_preds() const { return m_prim_preds.end(); } + + void get_muxed_cube_from_model(const model_core & model, expr_ref_vector & res) const; + + std::string pp_model(const model_core & mdl) const; +}; +} + +#endif diff --git a/lib/pdr_util.cpp b/lib/pdr_util.cpp new file mode 100644 index 000000000..d3e73a2f4 --- /dev/null +++ b/lib/pdr_util.cpp @@ -0,0 +1,841 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + pdr_util.cpp + +Abstract: + + Utility functions for PDR. + +Author: + + Krystof Hoder (t-khoder) 2011-8-19. + +Revision History: + + +Notes: + + +--*/ + +#include +#include "arith_simplifier_plugin.h" +#include "array_decl_plugin.h" +#include "ast_pp.h" +#include "basic_simplifier_plugin.h" +#include "bv_simplifier_plugin.h" +#include "bool_rewriter.h" +#include "dl_util.h" +#include "for_each_expr.h" +#include "front_end_params.h" +#include "model.h" +#include "model_v2_pp.h" +#include "ref_vector.h" +#include "rewriter.h" +#include "rewriter_def.h" +#include "util.h" +#include "pdr_manager.h" +#include "pdr_prop_solver.h" +#include "pdr_util.h" +#include "arith_decl_plugin.h" + +namespace pdr { + +unsigned ceil_log2(unsigned u) +{ + if (u==0) { return 0; } + unsigned pow2 = next_power_of_two(u); + return get_num_1bits(pow2-1); +} + + +std::string pp_cube(const ptr_vector& model, ast_manager& m) { + return pp_cube(model.size(), model.c_ptr(), m); +} +std::string pp_cube(const expr_ref_vector& model, ast_manager& m) { + return pp_cube(model.size(), model.c_ptr(), m); +} +std::string pp_cube(const app_ref_vector& model, ast_manager& m) { + return pp_cube(model.size(), model.c_ptr(), m); +} + +std::string pp_cube(const app_vector& model, ast_manager& m) { + return pp_cube(model.size(), model.c_ptr(), m); +} + +std::string pp_cube(unsigned sz, app * const * lits, ast_manager& m) { + return pp_cube(sz, reinterpret_cast(lits), m); +} + +std::string pp_cube(unsigned sz, expr * const * lits, ast_manager& m) { + std::stringstream res; + res << "("; + expr * const * end = lits+sz; + for (expr * const * it = lits; it!=end; it++) { + res << mk_pp(*it, m); + if (it+1!=end) { + res << ", "; + } + } + res << ")"; + return res.str(); +} + + +///////////////////////// +// model_evaluator_base +// + + +void model_evaluator_base::minimize_model(ptr_vector const & formulas, expr_ref_vector & model) +{ + ast_manager & m = model.get_manager(); + bool has_unknown, has_false; + DEBUG_CODE( + check_model(formulas, model, has_unknown, has_false); + if (has_false) { + std::cout<<"formulas: "<& m_assignment; +public: + expr_rewriter_cfg(const obj_map& assignment) + : m_assignment(assignment) + { + } + + bool get_subst(expr * s, expr * & t, proof * & t_pr) { + return m_assignment.find(s,t); + } +}; + +void th_rewriter_model_evaluator::setup_assignment(expr_ref_vector const& model, obj_map& assignment) { + + for (unsigned i = 0; i < model.size(); ++i) { + expr * mlit = model[i]; //model literal + if (is_uninterp(mlit)) { + assignment.insert(mlit, m.mk_true()); + } + expr * arg1; + expr * arg2; + if (m.is_not(mlit, arg1)) { + assignment.insert(arg1, m.mk_false()); + } + else if (m.is_eq(mlit, arg1, arg2)) { + if (!is_uninterp(arg1)) { + std::swap(arg1, arg2); + } + if (is_uninterp(arg1)) { + assignment.insert(arg1, arg2); + } + else { + assignment.insert(mlit, m.mk_true()); + } + } + } +} + +void th_rewriter_model_evaluator::check_model(ptr_vector const & formulas, expr_ref_vector & model, + bool & has_unknown, bool & has_false) +{ + obj_map assignment; + + setup_assignment(model, assignment); + + expr_rewriter_cfg r_cfg(assignment); + rewriter_tpl rwr(m, false, r_cfg); + + has_false = false; + has_unknown = false; + + for (unsigned i = 0; i < formulas.size(); ++i) { + expr * form = formulas[i]; + expr_ref asgn_form(m); + rwr(form, asgn_form); + expr_ref simpl_form(m); + m_rewriter(asgn_form, simpl_form); + if (m.is_false(simpl_form)) { + has_false = true; + } + else if (!m.is_true(simpl_form)) { + IF_VERBOSE(7, verbose_stream() << "formula evaluated as unknown:\noriginal: " + << mk_pp(form, m) << "\n" + << "simplified: " << mk_pp(simpl_form,m) << "\n";); + has_unknown = true; + } + } + m_rewriter.reset(); +} + + //00 -- non-visited + //01 -- X + //10 -- false + //11 -- true + +#define UNKNOWN(x) (!m1.is_marked(x) && !m2.is_marked(x)) +#define SET_UNKNOWN(x) { TRACE("pdr_verbose", tout << "unknown: " << mk_pp(x,m) << "\n";); m1.reset_mark(x); m2.reset_mark(x); } +#define IS_X(x) (!m1.is_marked(x) && m2.is_marked(x)) +#define IS_FALSE(x) (m1.is_marked(x) && !m2.is_marked(x)) +#define IS_TRUE(x) (m1.is_marked(x) && m2.is_marked(x)) +#define SET_X(x) { SASSERT(UNKNOWN(x)); m2.mark(x); } +#define SET_V(x) { SASSERT(UNKNOWN(x)); m1.mark(x); } +#define SET_FALSE(x) { SASSERT(UNKNOWN(x)); m1.mark(x); } +#define SET_TRUE(x) { SASSERT(UNKNOWN(x)); m1.mark(x); m2.mark(x); } +#define SET_BOOL(x, v) { if (v) { SET_TRUE(x); } else { SET_FALSE(x); } } +#define GET_VALUE(x) m_values.find(x) +#define SET_VALUE(x,y) { SET_V(x); TRACE("pdr_verbose", tout << mk_pp(x,m) << " " << y << "\n";); m_values.insert(x, y); } + +void ternary_model_evaluator::add_model(expr* e) { + expr* nlit, *var, *val; + rational r; + // SASSERT(UNKNOWN(e)); + if (m.is_eq(e, var, val)) { + if (!is_uninterp(var)) { + std::swap(var, val); + } + if (m.is_true(val) && UNKNOWN(var)) { + SET_TRUE(var); + } + else if (m.is_false(val) && UNKNOWN(var)) { + SET_FALSE(var); + } + else if (m_arith.is_numeral(val, r) && UNKNOWN(var)) { + SET_VALUE(var, r); + } + else { + TRACE("pdr_verbose", tout << "no value for " << mk_pp(val, m) << "\n";); + } + } + else if (m.is_not(e, nlit)) { + SET_FALSE(nlit); + } + else if (m.is_bool(e)) { + SET_TRUE(e); + } + else { + TRACE("pdr_verbose", tout << "no value set of " << mk_pp(e, m) << "\n";); + } +} + +void ternary_model_evaluator::del_model(expr* e) { + expr* nlit, *var, *val; + if (m.is_eq(e, var, val)) { + if (!is_uninterp(var)) { + std::swap(var, val); + } + SET_UNKNOWN(var); + m_values.remove(var); + } + else if (m.is_not(e, nlit)) { + SET_UNKNOWN(nlit); + } + else if (m.is_bool(e)) { + SET_UNKNOWN(e); + } + else { + TRACE("pdr_verbose", tout << "no value set of " << mk_pp(e, m) << "\n";); + } +} + +void ternary_model_evaluator::setup_model(expr_ref_vector const& model) { + m_values.reset(); + for (unsigned i = 0; i < model.size(); ++i) { + expr* e = model[i]; + if (UNKNOWN(e)) { + add_model(e); + } + } + m_level1 = m1.get_level(); + m_level2 = m2.get_level(); +} + +void ternary_model_evaluator::minimize_model(ptr_vector const & formulas, expr_ref_vector & model) +{ + setup_model(model); + + TRACE("pdr_verbose", + for (unsigned i = 0; i < model.size(); ++i) tout << mk_pp(model[i].get(), m) << "\n"; + tout << "formulas\n"; + for (unsigned i = 0; i < formulas.size(); ++i) tout << mk_pp(formulas[i], m) << "\n"; + ); + for (unsigned i = 0; i < model.size(); ) { + expr_ref removed(model[i].get(), m); + if (i + 1 < model.size()) { + model[i] = model.back(); + } + model.pop_back(); + del_model(removed); + m1.set_level(m_level1); + m2.set_level(m_level2); + + bool formulas_hold = check_model(formulas); + m1.set_level(m_level1); + m2.set_level(m_level2); + + + if (!formulas_hold) { + // if we introduced unknown, we restore the removed element + add_model(removed); + m_level1 = m1.get_level(); + m_level2 = m2.get_level(); + if (i < model.size()) { + model.push_back(model[i].get()); + model[i] = removed; + } + else { + model.push_back(removed); + } + i++; + } + } + m1.reset(); + m2.reset(); + m_values.reset(); +} + + +bool ternary_model_evaluator::check_model(ptr_vector const& formulas) { + ptr_vector todo; + assign_vector_with_casting(todo, formulas); + + expr *argCond, *argThen, *argElse, *arg; + rational r, r2; + + while(!todo.empty()) { + expr * curr_e = todo.back(); + unsigned pre_curr_depth = todo.size()-1; + + if (!is_app(curr_e)) { + todo.pop_back(); + continue; + } + app * curr = to_app(curr_e); + +#define ARG1 curr->get_arg(0) +#define ARG2 curr->get_arg(1) + + if (!UNKNOWN(curr)) { + todo.pop_back(); + continue; + } + unsigned arity = curr->get_num_args(); + + if (curr->get_family_id() == m_arith.get_family_id()) { + bool all_set = true, some_x = false; + for (unsigned i = 0; !some_x && i < arity; ++i) { + expr* arg = curr->get_arg(i); + if (UNKNOWN(arg)) { + todo.push_back(arg); + all_set = false; + } + else if (IS_X(arg)) { + some_x = true; + } + } + if (some_x) { + SET_X(curr); + } + else if (!all_set) { + continue; + } + else { + switch(curr->get_decl_kind()) { + case OP_NUM: + VERIFY(m_arith.is_numeral(curr,r)); + SET_VALUE(curr, r); + break; + case OP_IRRATIONAL_ALGEBRAIC_NUM: + SET_X(curr); + break; + case OP_LE: + SET_BOOL(curr, GET_VALUE(ARG1) <= GET_VALUE(ARG2)); + break; + case OP_GE: + SET_BOOL(curr, GET_VALUE(ARG1) >= GET_VALUE(ARG2)); + break; + case OP_LT: + SET_BOOL(curr, GET_VALUE(ARG1) < GET_VALUE(ARG2)); + break; + case OP_GT: + SET_BOOL(curr, GET_VALUE(ARG1) > GET_VALUE(ARG2)); + break; + case OP_ADD: + r = rational::zero(); + for (unsigned i = 0; i < arity; ++i) { + r += GET_VALUE(curr->get_arg(i)); + } + SET_VALUE(curr, r); + break; + case OP_SUB: + r = GET_VALUE(curr->get_arg(0)); + for (unsigned i = 1; i < arity; ++i) { + r -= GET_VALUE(curr->get_arg(i)); + } + SET_VALUE(curr, r); + break; + case OP_UMINUS: + SASSERT(arity == 1); + SET_VALUE(curr, GET_VALUE(curr->get_arg(0))); + break; + case OP_MUL: + r = rational::one(); + for (unsigned i = 0; i < arity; ++i) { + r *= GET_VALUE(curr->get_arg(i)); + } + SET_VALUE(curr, r); + break; + case OP_DIV: + SASSERT(arity == 2); + r = GET_VALUE(ARG2); + if (r.is_zero()) { + SET_X(curr); + } + else { + SET_VALUE(curr, GET_VALUE(ARG1) / r); + } + break; + case OP_IDIV: + SASSERT(arity == 2); + r = GET_VALUE(ARG2); + if (r.is_zero()) { + SET_X(curr); + } + else { + SET_VALUE(curr, div(GET_VALUE(ARG1), r)); + } + break; + case OP_REM: + // rem(v1,v2) = if v2 >= 0 then mod(v1,v2) else -mod(v1,v2) + SASSERT(arity == 2); + r = GET_VALUE(ARG2); + if (r.is_zero()) { + SET_X(curr); + } + else { + r2 = mod(GET_VALUE(ARG1), r); + if (r.is_neg()) r2.neg(); + SET_VALUE(curr, r2); + } + break; + case OP_MOD: + SASSERT(arity == 2); + r = GET_VALUE(ARG2); + if (r.is_zero()) { + SET_X(curr); + } + else { + SET_VALUE(curr, mod(GET_VALUE(ARG1), r)); + } + break; + case OP_TO_REAL: + SASSERT(arity == 1); + SET_VALUE(curr, GET_VALUE(ARG1)); + break; + case OP_TO_INT: + SASSERT(arity == 1); + SET_VALUE(curr, floor(GET_VALUE(ARG1))); + break; + case OP_IS_INT: + SASSERT(arity == 1); + SET_BOOL(curr, GET_VALUE(ARG1).is_int()); + break; + case OP_POWER: + SET_X(curr); + break; + default: + UNREACHABLE(); + break; + } + } + } + else if (curr->get_family_id() == m_bv.get_family_id()) { + throw default_exception("PDR engine currently does not support bit-vectors"); + } + else if (curr->get_family_id() == m.get_basic_family_id()) { + expr* arg1, *arg2; + if (m.is_and(curr)) { + // we're adding unknowns on the top of the todo stack, if there is none added, + // all arguments were known + bool has_x = false, has_false = false; + for (unsigned j = 0; !has_false && j < arity; ++j) { + expr * arg = curr->get_arg(j); + if (IS_FALSE(arg)) { + has_false = true; + } + else if (IS_X(arg)) { + has_x = true; + } + else if (UNKNOWN(arg)) { + todo.push_back(arg); + } + } + if (has_false) { + SET_FALSE(curr); + } + else { + if (todo.back() != curr) { + continue; + } + else if (has_x) { + SET_X(curr); + } + else { + SET_TRUE(curr); + } + } + } + else if (m.is_or(curr)) { + bool has_x = false, has_true = false; + for (unsigned j = 0; !has_true && j < arity; ++j) { + expr * arg = curr->get_arg(j); + if (IS_TRUE(arg)) { + has_true = true; + } + else if (IS_X(arg)) { + has_x = true; + } + else if (UNKNOWN(arg)) { + todo.push_back(arg); + } + } + if (has_true) { + SET_TRUE(curr); + } + else { + if (todo.back() != curr) { + continue; + } + else if (has_x) { + SET_X(curr); + } + else { + SET_FALSE(curr); + } + } + } + else if (m.is_not(curr, arg)) { + if (UNKNOWN(arg)) { + todo.push_back(arg); + continue; + } + if (IS_TRUE(arg)) { + SET_FALSE(curr); + } + else if (IS_FALSE(arg)) { + SET_TRUE(curr); + } + else { + SASSERT(IS_X(arg)); + SET_X(curr); + } + } + else if (m.is_implies(curr, arg1, arg2)) { + if (IS_FALSE(arg1) || IS_TRUE(arg2)) { + SET_TRUE(curr); + } + else if (UNKNOWN(arg1) || UNKNOWN(arg2)) { + if (UNKNOWN(arg1)) { todo.push_back(arg1); } + if (UNKNOWN(arg2)) { todo.push_back(arg2); } + continue; + } + else if (IS_TRUE(arg1) && IS_FALSE(arg2)) { + SET_FALSE(curr); + } + else { + SASSERT(IS_X(arg1) || IS_X(arg2)); + SET_X(curr); + } + } + else if (m.is_iff(curr, arg1, arg2) || + (m.is_eq(curr, arg1, arg2) && m.is_bool(arg1))) { + if (IS_X(arg1) || IS_X(arg2)) { + SET_X(curr); + } + else if (UNKNOWN(arg1) || UNKNOWN(arg2)) { + if (UNKNOWN(arg1)) { todo.push_back(arg1); } + if (UNKNOWN(arg2)) { todo.push_back(arg2); } + continue; + } + else { + bool val = IS_TRUE(arg1)==IS_TRUE(arg2); + SASSERT(val == (IS_FALSE(arg1)==IS_FALSE(arg2))); + if (val) { + SET_TRUE(curr); + } + else { + SET_FALSE(curr); + } + } + } + else if (m.is_ite(curr, argCond, argThen, argElse) && m.is_bool(argThen)) { + if (IS_TRUE(argCond)) { + if (IS_TRUE(argThen)) { SET_TRUE(curr); } + else if (IS_FALSE(argThen)) { SET_FALSE(curr); } + else if (IS_X(argThen)) { SET_X(curr); } + else { + todo.push_back(argThen); + continue; + } + } + else if (IS_FALSE(argCond)) { + if (IS_TRUE(argElse)) { SET_TRUE(curr); } + else if (IS_FALSE(argElse)) { SET_FALSE(curr); } + else if (IS_X(argElse)) { SET_X(curr); } + else { + todo.push_back(argElse); + continue; + } + } + else if (IS_TRUE(argThen) && IS_TRUE(argElse)) { + SET_TRUE(curr); + } + else if (IS_FALSE(argThen) && IS_FALSE(argElse)) { + SET_FALSE(curr); + } + else if (IS_X(argCond) && (IS_X(argThen) || IS_X(argElse)) ) { + SET_X(curr); + } else if (UNKNOWN(argCond) || UNKNOWN(argThen) || UNKNOWN(argElse)) { + if (UNKNOWN(argCond)) { todo.push_back(argCond); } + if (UNKNOWN(argThen)) { todo.push_back(argThen); } + if (UNKNOWN(argElse)) { todo.push_back(argElse); } + continue; + } + else { + SASSERT(IS_X(argCond)); + SASSERT((IS_TRUE(argThen) && IS_FALSE(argElse)) || + (IS_FALSE(argThen) && IS_TRUE(argElse))); + SET_X(curr); + } + } + else if (m.is_true(curr)) { + SET_TRUE(curr); + } + else if (m.is_false(curr)) { + SET_FALSE(curr); + } + else if (m.is_eq(curr, arg1, arg2) && arg1 == arg2) { + SET_TRUE(curr); + } + else if (m.is_eq(curr, arg1, arg2)) { + if (UNKNOWN(arg1)) { + todo.push_back(arg1); + } + if (UNKNOWN(arg2)) { + todo.push_back(arg2); + } + if (curr != todo.back()) { + continue; + } + if (IS_X(arg1) || IS_X(arg2)) { + SET_X(curr); + } + else { + SET_BOOL(curr, GET_VALUE(arg1) == GET_VALUE(arg2)); + } + } + else if (m.is_ite(curr, argCond, argThen, argElse) && m_arith.is_int_real(argThen)) { + if (IS_TRUE(argCond) || (argThen == argElse)) { + if (UNKNOWN(argThen)) { + todo.push_back(argThen); + continue; + } + if (IS_X(argThen)) { + SET_X(curr); + } + else { + SET_VALUE(curr, GET_VALUE(argThen)); + } + } + else if (IS_FALSE(argCond)) { + if (UNKNOWN(argElse)) { + todo.push_back(argElse); + continue; + } + if (IS_X(argElse)) { + SET_X(curr); + } + else { + SET_VALUE(curr, GET_VALUE(argElse)); + } + } + else if (UNKNOWN(argCond)) { + todo.push_back(argCond); + continue; + } + else { + SET_X(curr); + } + } + else { + UNREACHABLE(); + } + } + else { + TRACE("pdr_verbse", tout << "Not evaluated " << mk_pp(curr, m) << "\n";); + SET_X(curr); + } + + IF_VERBOSE(35,verbose_stream() << "assigned "<get_num_parameters(); + + ptr_vector domain; + domain.push_back(arr_sort); + + //we push params of the array as remaining arguments of the store. The first + //num_params-1 parameters are indices and the last one is the range of the array + for (unsigned i=0; iget_parameter(i).get_ast())); + } + + return m.mk_func_decl(array_fid, OP_STORE, + arr_sort->get_num_parameters(), arr_sort->get_parameters(), + domain.size(), domain.c_ptr(), arr_sort); +} + +void get_as_array_value(const model_core & mdl, expr * arr_e, expr_ref& res) { + ast_manager& m = mdl.get_manager(); + array_util pl(m); + SASSERT(pl.is_as_array(arr_e)); + + app * arr = to_app(arr_e); + + unsigned sz = 0; + func_decl_ref f(pl.get_as_array_func_decl(arr), m); + sort * arr_sort = arr->get_decl()->get_range(); + func_interp* g = mdl.get_func_interp(f); + + res = pl.mk_const_array(arr_sort, g->get_else()); + + unsigned arity = f->get_arity(); + + sz = g->num_entries(); + if (sz) { + func_decl_ref store_fn(mk_store(m, arr_sort), m); + ptr_vector store_args; + for (unsigned i = 0; i < sz; ++i) { + const func_entry * fe = g->get_entry(i); + store_args.reset(); + store_args.push_back(res); + store_args.append(arity, fe->get_args()); + store_args.push_back(fe->get_result()); + res = m.mk_app(store_fn, store_args.size(), store_args.c_ptr()); + } + } +} + +void get_value_from_model(const model_core & mdl, func_decl * f, expr_ref& res) { + SASSERT(f->get_arity()==0); + ast_manager& m = mdl.get_manager(); + + res = mdl.get_const_interp(f); + + array_util pl(m); + + if (pl.is_as_array(res)) { + get_as_array_value(mdl, res, res); + } +} + +void get_cube_from_model(const model_core & mdl, expr_ref_vector & res, pdr::prop_solver& solver) +{ + ast_manager& m = res.get_manager(); + + res.reset(); + unsigned sz = mdl.get_num_constants(); + for (unsigned i = 0; i < sz; i++) { + func_decl * d = mdl.get_constant(i); + + if (solver.is_aux_symbol(d)) { + continue; + } + + SASSERT(d->get_arity()==0); + expr_ref interp(m); + pdr::get_value_from_model(mdl, d, interp); + + app_ref constant(m.mk_const(d), m); + app_ref lit(m); + if (m.is_bool(d->get_range())) { + if (m.is_true(interp)) { + lit = constant; + } + else { + SASSERT(m.is_false(interp)); + lit = m.mk_not(constant); + } + } + else { + lit = m.mk_eq(constant, interp); + } + res.push_back(lit); + } +} + +} diff --git a/lib/pdr_util.h b/lib/pdr_util.h new file mode 100644 index 000000000..c16f39bba --- /dev/null +++ b/lib/pdr_util.h @@ -0,0 +1,236 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + pdr_util.h + +Abstract: + + Utility functions for PDR. + +Author: + + Krystof Hoder (t-khoder) 2011-8-19. + +Revision History: + +--*/ + +#ifndef _PDR_UTIL_H_ +#define _PDR_UTIL_H_ + +#include "ast.h" +#include "ast_pp.h" +#include "obj_hashtable.h" +#include "ref_vector.h" +#include "simplifier.h" +#include "th_rewriter.h" +#include "trace.h" +#include "vector.h" +#include "arith_decl_plugin.h" +#include "bv_decl_plugin.h" + + +struct front_end_params; +class model; +class model_core; + +namespace pdr { + class manager; + class prop_solver; + +/** + * Return the ceiling of base 2 logarithm of a number, + * or zero if the nmber is zero. + */ +unsigned ceil_log2(unsigned u); + +typedef ptr_vector app_vector; +typedef ptr_vector decl_vector; +typedef obj_hashtable func_decl_set; + + +std::string pp_cube(const ptr_vector& model, ast_manager& manager); +std::string pp_cube(const expr_ref_vector& model, ast_manager& manager); +std::string pp_cube(const ptr_vector& model, ast_manager& manager); +std::string pp_cube(const app_ref_vector& model, ast_manager& manager); +std::string pp_cube(unsigned sz, app * const * lits, ast_manager& manager); +std::string pp_cube(unsigned sz, expr * const * lits, ast_manager& manager); + + +template +void assign_vector_with_casting(TgtVect& tgt, const SrcVect& src) +{ + SASSERT(static_cast(&tgt)!=static_cast(&src)); + tgt.reset(); + typename SrcVect::data const * begin = src.c_ptr(); + typename SrcVect::data const * end = begin + src.size(); + for(typename SrcVect::data const * it = begin; it!=end; it++) + { + tgt.push_back(static_cast(*it)); + } +/* tgt.reset(); + typename SrcVect::const_iterator end = src.end(); + for(typename SrcVect::const_iterator it = src.begin(); it!=end; it++) + { + tgt.push_back(static_cast(*it)); + }*/ +} + +template +void append_ref_vector_with_casting(ref_vector & tgt, const ref_vector & src) +{ + SASSERT(static_cast(&tgt)!=static_cast(&src)); + SrcType * const * begin = src.c_ptr(); + SrcType * const * end = begin + src.size(); + for(SrcType * const * it = begin; it!=end; it++) + { + tgt.push_back(static_cast(*it)); + } +} + +template +void assign_ref_vector_with_casting(ref_vector & tgt, const ref_vector & src) +{ + SASSERT(static_cast(&tgt)!=static_cast(&src)); + tgt.reset(); + append_ref_vector_with_casting(tgt, src); +} + +template +ref_vector& assign_ref_vector(ref_vector & tgt, const ref_vector & src) +{ + SASSERT(static_cast(&tgt)!=static_cast(&src)); + //we support assignment only on vectors with the same manager + SASSERT(&tgt.get_manager()==&src.get_manager()); + tgt.reset(); + tgt.append(src); + return tgt; +} + +template +void sort_ref_vect(ref_vector & vect, Comp cmp) +{ + Type * * begin = vect.c_ptr(); + Type * * end = begin + vect.size(); + std::sort(begin, end, cmp); +} + +/** +Into res put all elements that are in left but not in right. + +This function can change the order of elements in left. +*/ +template +void vect_set_diff(ref_vector & left, ref_vector & right, + ref_vector & res, Comp cmp) +{ + sort_ref_vect(left, cmp); + sort_ref_vect(right, cmp); + + res.reset(); + + Type * const * lbegin = left.c_ptr(); + Type * const * lend = lbegin + left.size(); + Type * const * lit = lbegin; + Type * const * rbegin = right.c_ptr(); + Type * const * rend = rbegin + right.size(); + Type * const * rit = rbegin; + + while(lit!=lend) { + while(rit!=rend && lit!=lend && *rit==*lit) { + ++rit; + ++lit; + } + if(lit==lend) { + return; + } + while(rit!=rend && cmp(*rit, *lit)) { + ++rit; + } + while(lit!=lend && (rit==rend || cmp(*lit, *rit))) { + res.push_back(*lit); + ++lit; + } + } +} + +/** +Ensure all elements from src are in tgt + +This function can change the order of elements in tgt and src. +*/ +template +void vect_set_union(ref_vector & tgt, ref_vector & src, Comp cmp) +{ + ref_vector diff(tgt.get_manager()); + vect_set_diff(src, tgt, diff, cmp); + tgt.append(diff); +} + + +class model_evaluator_base { +protected: + virtual void check_model(ptr_vector const & formulas, + expr_ref_vector & model, bool & has_unknown, bool & has_false) = 0; +public: + virtual void minimize_model(ptr_vector const & formulas, expr_ref_vector & model); +}; + +class th_rewriter_model_evaluator : public model_evaluator_base { + class expr_rewriter_cfg; + ast_manager& m; + th_rewriter m_rewriter; + + void setup_assignment(expr_ref_vector const& model, obj_map& assignment); + +protected: + virtual void check_model(ptr_vector const & formulas, + expr_ref_vector & model, bool & has_unknown, + bool & has_false); +public: + th_rewriter_model_evaluator(ast_manager& m) : m(m), m_rewriter(m) {} +}; + +class ternary_model_evaluator : public model_evaluator_base { + ast_manager& m; + arith_util m_arith; + bv_util m_bv; + obj_map m_values; + + //00 -- non-visited + //01 -- X + //10 -- false + //11 -- true + ast_fast_mark1 m1; + ast_fast_mark2 m2; + unsigned m_level1; + unsigned m_level2; + + void setup_model(expr_ref_vector const& model); + void add_model(expr* e); + void del_model(expr* e); + +protected: + bool check_model(ptr_vector const & formulas); + virtual void check_model(ptr_vector const & formulas, expr_ref_vector & model, + bool & has_unknown, bool & has_false) { + UNREACHABLE(); + } + +public: + ternary_model_evaluator(ast_manager& m) : m(m), m_arith(m), m_bv(m) {} + virtual void minimize_model(ptr_vector const & formulas, expr_ref_vector & model); +}; + +void get_value_from_model(const model_core & mdl, func_decl * f, expr_ref& res); + +/** +If the solver argument is non-zero, we will exclude its auxiliary symbols from the generated cube. +*/ +void get_cube_from_model(const model_core & mdl, expr_ref_vector & res, pdr::prop_solver& solver); + +} + +#endif diff --git a/lib/permutation.cpp b/lib/permutation.cpp new file mode 100644 index 000000000..a5c1b2e8b --- /dev/null +++ b/lib/permutation.cpp @@ -0,0 +1,76 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + permutation.cpp + +Abstract: + + Goodies for managing permutations. + +Author: + + Leonardo de Moura (leonardo) 2011-06-10. + +Revision History: + +--*/ +#include"permutation.h" + +permutation::permutation(unsigned size) { + reset(size); +} + +void permutation::reset(unsigned size) { + m_p.reset(); + m_inv_p.reset(); + for (unsigned i = 0; i < size; i++) { + m_p.push_back(i); + m_inv_p.push_back(i); + } +} + +void permutation::swap(unsigned i, unsigned j) { + unsigned i_prime = m_p[i]; + unsigned j_prime = m_p[j]; + std::swap(m_p[i], m_p[j]); + std::swap(m_inv_p[i_prime], m_inv_p[j_prime]); +} + +/** + \brief Move i after j. +*/ +void permutation::move_after(unsigned i, unsigned j) { + if (i >= j) + return; + unsigned i_prime = m_p[i]; + for (unsigned k = i; k < j; k++) { + m_p[k] = m_p[k+1]; + m_inv_p[m_p[k]] = k; + } + m_p[j] = i_prime; + m_inv_p[i_prime] = j; + SASSERT(check_invariant()); +} + +void permutation::display(std::ostream & out) const { + unsigned n = m_p.size(); + for (unsigned i = 0; i < n; i++) { + if (i > 0) + out << " "; + out << i << ":" << m_p[i]; + } +} + +bool permutation::check_invariant() const { + SASSERT(m_p.size() == m_inv_p.size()); + unsigned n = m_p.size(); + for (unsigned i = 0; i < n; i++) { + SASSERT(m_p[i] < n); + SASSERT(m_inv_p[i] < n); + SASSERT(m_p[m_inv_p[i]] == i); + SASSERT(m_inv_p[m_p[i]] == i); + } + return true; +} diff --git a/lib/permutation.h b/lib/permutation.h new file mode 100644 index 000000000..29e17cf68 --- /dev/null +++ b/lib/permutation.h @@ -0,0 +1,93 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + permutation.h + +Abstract: + + Simple abstraction for managing permutations. + +Author: + + Leonardo de Moura (leonardo) 2011-06-10. + +Revision History: + +--*/ +#ifndef _PERMUTATION_H_ +#define _PERMUTATION_H_ + +#include +#include"vector.h" + +class permutation { + unsigned_vector m_p; + unsigned_vector m_inv_p; +public: + permutation(unsigned size = 0); + void reset(unsigned size = 0); + + unsigned operator()(unsigned i) const { return m_p[i]; } + unsigned inv(unsigned i_prime) const { return m_inv_p[i_prime]; } + + void swap(unsigned i, unsigned j); + void move_after(unsigned i, unsigned j); + + void display(std::ostream & out) const; + bool check_invariant() const; +}; + +inline std::ostream & operator<<(std::ostream & out, permutation const & p) { + p.display(out); + return out; +} + +/** + \brief Apply permutation p to data. + The algorithm does not use any extra memory. + + Requirement: swap(T, T) must be available. + + This version will perform destructive updates to p. + Use apply_permutation if p must not be preserved +*/ +template +void apply_permutation_core(unsigned sz, T * data, unsigned * p) { + int * p1 = reinterpret_cast(p); + for (int i = 0; i < static_cast(sz); i++) { + if (p1[i] < 0) + continue; // already processed + int j = i; + while (true) { + SASSERT(j >= 0); + int p_j = p1[j]; + SASSERT(p_j >= 0); + SASSERT(p_j < static_cast(sz)); + p1[j] = - p1[j] - 1; // mark as done + if (p_j == i) + break; // cycle starting at i is done + swap(data[j], data[p_j]); + j = p_j; + } + } +} + +/** + \brief Apply permutation p to data. + The algorithm does not use any extra memory. + + Requirement: swap(T, T) must be available. +*/ +template +void apply_permutation(unsigned sz, T * data, unsigned const * p) { + apply_permutation_core(sz, data, const_cast(p)); + // restore p + int * p1 = reinterpret_cast(const_cast(p)); + for (unsigned i = 0; i < sz; i++) { + p1[i] = - p1[i] - 1; + } +} + +#endif diff --git a/lib/plugin_manager.h b/lib/plugin_manager.h new file mode 100644 index 000000000..b9abd0315 --- /dev/null +++ b/lib/plugin_manager.h @@ -0,0 +1,66 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + plugin_manager.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-09-18. + +Revision History: + +--*/ +#ifndef _PLUGIN_MANAGER_H_ +#define _PLUGIN_MANAGER_H_ + +#include"util.h" + +template +class plugin_manager { + ptr_vector m_fid2plugins; + ptr_vector m_plugins; +public: + ~plugin_manager() { + std::for_each(m_plugins.begin(), m_plugins.end(), delete_proc()); + } + + /** + \brief Release ownership of the plugins. + */ + void release() { + m_fid2plugins.reset(); + m_plugins.reset(); + } + + void register_plugin(Plugin * p) { + SASSERT(p); + family_id fid = p->get_family_id(); + SASSERT(m_fid2plugins.get(fid, 0) == 0); + m_fid2plugins.setx(fid, p, 0); + m_plugins.push_back(p); + } + + Plugin * get_plugin(family_id fid) const { + if (fid == null_family_id) { + return 0; + } + return m_fid2plugins.get(fid, 0); + } + + typename ptr_vector::const_iterator begin() const { + return m_plugins.begin(); + } + + typename ptr_vector::const_iterator end() const { + return m_plugins.end(); + } +}; + +#endif /* _PLUGIN_MANAGER_H_ */ + diff --git a/lib/poly_rewriter.h b/lib/poly_rewriter.h new file mode 100644 index 000000000..9bdaf4959 --- /dev/null +++ b/lib/poly_rewriter.h @@ -0,0 +1,167 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + poly_rewriter.h + +Abstract: + + Basic rewriting rules for Polynomials. + +Author: + + Leonardo (leonardo) 2011-04-08 + +Notes: + +--*/ +#ifndef _POLY_REWRITER_H_ +#define _POLY_REWRITER_H_ + +#include"ast.h" +#include"obj_hashtable.h" +#include"rewriter_types.h" +#include"params.h" + +template +class poly_rewriter : public Config { +public: + static char const * g_ste_blowup_msg; +protected: + typedef typename Config::numeral numeral; + sort * m_curr_sort; + obj_map m_expr2pos; + bool m_flat; + bool m_som; + unsigned m_som_blowup; + bool m_sort_sums; + bool m_hoist_mul; + bool m_hoist_cmul; + + bool is_numeral(expr * n) const { return Config::is_numeral(n); } + bool is_numeral(expr * n, numeral & r) const { return Config::is_numeral(n, r); } + bool is_zero(expr * n) const { return Config::is_zero(n); } + bool is_minus_one(expr * n) const { return Config::is_minus_one(n); } + void normalize(numeral & c) { Config::normalize(c, m_curr_sort); } + app * mk_numeral(numeral const & r) { return Config::mk_numeral(r, m_curr_sort); } + decl_kind add_decl_kind() const { return Config::add_decl_kind(); } + decl_kind mul_decl_kind() const { return Config::mul_decl_kind(); } + bool use_power() const { return Config::use_power(); } + decl_kind power_decl_kind() const { return Config::power_decl_kind(); } + bool is_power(expr * t) const { return is_app_of(t, get_fid(), power_decl_kind()); } + expr * get_power_body(expr * t, rational & k); + struct mon_pw_lt; // functor used to sort monomial elements when use_power() == true + + expr * mk_mul_app(unsigned num_args, expr * const * args); + expr * mk_mul_app(numeral const & c, expr * arg); + expr * mk_add_app(unsigned num_args, expr * const * args); + + br_status mk_flat_mul_core(unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_nflat_mul_core(unsigned num_args, expr * const * args, expr_ref & result); + + expr * get_power_product(expr * t); + expr * get_power_product(expr * t, numeral & a); + + br_status mk_flat_add_core(unsigned num_args, expr * const * args, expr_ref & result); + br_status mk_nflat_add_core(unsigned num_args, expr * const * args, expr_ref & result); + + void set_curr_sort(sort * s) { m_curr_sort = s; } + + expr * const * get_monomials(expr * & t, unsigned & sz) { + if (is_add(t)) { + sz = to_app(t)->get_num_args(); + return to_app(t)->get_args(); + } + else { + sz = 1; + return &t; + } + } + + br_status cancel_monomials(expr * lhs, expr * rhs, bool move, expr_ref & lhs_result, expr_ref & rhs_result); + + bool hoist_multiplication(expr_ref& som); + expr* merge_muls(expr* x, expr* y); + + struct hoist_cmul_lt; + bool is_mul(expr * t, numeral & c, expr * & pp); + void hoist_cmul(expr_ref_buffer & args); + +public: + poly_rewriter(ast_manager & m, params_ref const & p = params_ref()): + Config(m), + m_curr_sort(0), + m_sort_sums(false) { + updt_params(p); + SASSERT(!m_som || m_flat); // som of monomials form requires flattening to be enabled. + SASSERT(!m_som || !m_hoist_mul); // som is mutually exclusive with hoisting multiplication. + updt_params(p); + } + + ast_manager & m() const { return Config::m(); } + family_id get_fid() const { return Config::get_fid(); } + + void updt_params(params_ref const & p); + static void get_param_descrs(param_descrs & r); + + void set_flat(bool f) { m_flat = f; } + + void set_sort_sums(bool f) { m_sort_sums = f; } + + bool is_add(expr * n) const { return is_app_of(n, get_fid(), add_decl_kind()); } + bool is_mul(expr * n) const { return is_app_of(n, get_fid(), mul_decl_kind()); } + bool is_add(func_decl * f) const { return is_decl_of(f, get_fid(), add_decl_kind()); } + bool is_mul(func_decl * f) const { return is_decl_of(f, get_fid(), mul_decl_kind()); } + + br_status mk_mul_core(unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(num_args > 0); + if (num_args == 1) { + result = args[0]; + return BR_DONE; + } + set_curr_sort(m().get_sort(args[0])); + return m_flat ? + mk_flat_mul_core(num_args, args, result) : + mk_nflat_mul_core(num_args, args, result); + } + br_status mk_add_core(unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(num_args > 0); + if (num_args == 1) { + result = args[0]; + return BR_DONE; + } + set_curr_sort(m().get_sort(args[0])); + return m_flat ? + mk_flat_add_core(num_args, args, result) : + mk_nflat_add_core(num_args, args, result); + } + void mk_add(unsigned num_args, expr * const * args, expr_ref & result) { + if (mk_add_core(num_args, args, result) == BR_FAILED) + result = mk_add_app(num_args, args); + } + void mk_add(expr* a1, expr* a2, expr_ref& result) { + expr* args[2] = { a1, a2 }; + mk_add(2, args, result); + } + + void mk_mul(unsigned num_args, expr * const * args, expr_ref & result) { + if (mk_mul_core(num_args, args, result) == BR_FAILED) + result = mk_mul_app(num_args, args); + } + void mk_mul(expr* a1, expr* a2, expr_ref& result) { + expr* args[2] = { a1, a2 }; + mk_mul(2, args, result); + } + // The result of the following functions is never BR_FAILED + br_status mk_uminus(expr * arg, expr_ref & result); + br_status mk_sub(unsigned num_args, expr * const * args, expr_ref & result); + + void mk_sub(expr* a1, expr* a2, expr_ref& result) { + expr* args[2] = { a1, a2 }; + mk_sub(2, args, result); + } +}; + + +#endif diff --git a/lib/poly_rewriter_def.h b/lib/poly_rewriter_def.h new file mode 100644 index 000000000..1a22c44e2 --- /dev/null +++ b/lib/poly_rewriter_def.h @@ -0,0 +1,932 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + poly_rewriter_def.h + +Abstract: + + Basic rewriting rules for Polynomials. + +Author: + + Leonardo (leonardo) 2011-04-08 + +Notes: + +--*/ +#include"poly_rewriter.h" +#include"ast_lt.h" +#include"ast_ll_pp.h" +#include"ast_smt2_pp.h" + +template +char const * poly_rewriter::g_ste_blowup_msg = "sum of monomials blowup"; + + +template +void poly_rewriter::updt_params(params_ref const & p) { + m_flat = p.get_bool(":flat", true); + m_som = p.get_bool(":som", false); + m_hoist_mul = p.get_bool(":hoist-mul", false); + m_hoist_cmul = p.get_bool(":hoist-cmul", false); + m_som_blowup = p.get_uint(":som-blowup", UINT_MAX); +} + +template +void poly_rewriter::get_param_descrs(param_descrs & r) { + r.insert(":som", CPK_BOOL, "(default: false) put polynomials in som-of-monomials form."); + r.insert(":som-blowup", CPK_UINT, "(default: infty) maximum number of monomials generated when putting a polynomial in sum-of-monomials normal form"); + r.insert(":hoist-mul", CPK_BOOL, "(default: false) hoist multiplication over summation to minimize number of multiplications"); + r.insert(":hoist-cmul", CPK_BOOL, "(default: false) hoist constant multiplication over summation to minimize number of multiplications"); +} + +template +expr * poly_rewriter::mk_add_app(unsigned num_args, expr * const * args) { + switch (num_args) { + case 0: return mk_numeral(numeral(0)); + case 1: return args[0]; + default: return m().mk_app(get_fid(), add_decl_kind(), num_args, args); + } +} + +// t = (^ x y) --> return x, and set k = y if k is an integer >= 1 +// Otherwise return t and set k = 1 +template +expr * poly_rewriter::get_power_body(expr * t, rational & k) { + if (!is_power(t)) { + k = rational(1); + return t; + } + if (is_numeral(to_app(t)->get_arg(1), k) && k.is_int() && k > rational(1)) { + return to_app(t)->get_arg(0); + } + k = rational(1); + return t; +} + +template +expr * poly_rewriter::mk_mul_app(unsigned num_args, expr * const * args) { + switch (num_args) { + case 0: + return mk_numeral(numeral(1)); + case 1: + return args[0]; + default: + if (use_power()) { + rational k_prev; + expr * prev = get_power_body(args[0], k_prev); + rational k; + ptr_buffer new_args; +#define PUSH_POWER() { \ + if (k_prev.is_one()) { \ + new_args.push_back(prev); \ + } \ + else { \ + expr * pargs[2] = { prev, mk_numeral(k_prev) }; \ + new_args.push_back(m().mk_app(get_fid(), power_decl_kind(), 2, pargs)); \ + } \ + } + + for (unsigned i = 1; i < num_args; i++) { + expr * arg = get_power_body(args[i], k); + if (arg == prev) { + k_prev += k; + } + else { + PUSH_POWER(); + prev = arg; + k_prev = k; + } + } + PUSH_POWER(); + SASSERT(new_args.size() > 0); + if (new_args.size() == 1) { + return new_args[0]; + } + else { + return m().mk_app(get_fid(), mul_decl_kind(), new_args.size(), new_args.c_ptr()); + } + } + else { + return m().mk_app(get_fid(), mul_decl_kind(), num_args, args); + } + } +} + +template +expr * poly_rewriter::mk_mul_app(numeral const & c, expr * arg) { + if (c.is_one()) { + return arg; + } + else { + expr * new_args[2] = { mk_numeral(c), arg }; + return mk_mul_app(2, new_args); + } +} + +template +br_status poly_rewriter::mk_flat_mul_core(unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(num_args >= 2); + // only try to apply flattening if it is not already in one of the flat monomial forms + // - (* c x) + // - (* c (* x_1 ... x_n)) + if (num_args != 2 || !is_numeral(args[0]) || (is_mul(args[1]) && is_numeral(to_app(args[1])->get_arg(0)))) { + unsigned i; + for (i = 0; i < num_args; i++) { + if (is_mul(args[i])) + break; + } + if (i < num_args) { + // input has nested monomials. + ptr_buffer flat_args; + // we need the todo buffer to handle: (* (* c (* x_1 ... x_n)) (* d (* y_1 ... y_n))) + ptr_buffer todo; + flat_args.append(i, args); + for (unsigned j = i; j < num_args; j++) { + if (is_mul(args[j])) { + todo.push_back(args[j]); + while (!todo.empty()) { + expr * curr = todo.back(); + todo.pop_back(); + if (is_mul(curr)) { + unsigned k = to_app(curr)->get_num_args(); + while (k > 0) { + --k; + todo.push_back(to_app(curr)->get_arg(k)); + } + } + else { + flat_args.push_back(curr); + } + } + } + else { + flat_args.push_back(args[j]); + } + } + TRACE("poly_rewriter", + tout << "flat mul:\n"; + for (unsigned i = 0; i < num_args; i++) tout << mk_bounded_pp(args[i], m()) << "\n"; + tout << "---->\n"; + for (unsigned i = 0; i < flat_args.size(); i++) tout << mk_bounded_pp(flat_args[i], m()) << "\n";); + br_status st = mk_nflat_mul_core(flat_args.size(), flat_args.c_ptr(), result); + if (st == BR_FAILED) { + result = mk_mul_app(flat_args.size(), flat_args.c_ptr()); + return BR_DONE; + } + return st; + } + } + return mk_nflat_mul_core(num_args, args, result); +} + + +template +struct poly_rewriter::mon_pw_lt { + poly_rewriter & m_owner; + mon_pw_lt(poly_rewriter & o):m_owner(o) {} + + bool operator()(expr * n1, expr * n2) const { + rational k; + return lt(m_owner.get_power_body(n1, k), + m_owner.get_power_body(n2, k)); + } +}; + + +template +br_status poly_rewriter::mk_nflat_mul_core(unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(num_args >= 2); + // cheap case + numeral a; + if (num_args == 2 && is_numeral(args[0], a) && !a.is_one() && !a.is_zero() && + (is_var(args[1]) || to_app(args[1])->get_decl()->get_family_id() != get_fid())) + return BR_FAILED; + numeral c(1); + unsigned num_coeffs = 0; + unsigned num_add = 0; + expr * var = 0; + for (unsigned i = 0; i < num_args; i++) { + expr * arg = args[i]; + if (is_numeral(arg, a)) { + num_coeffs++; + c *= a; + } + else { + var = arg; + if (is_add(arg)) + num_add++; + } + } + + normalize(c); + // (* c_1 ... c_n) --> c_1*...*c_n + if (num_coeffs == num_args) { + result = mk_numeral(c); + return BR_DONE; + } + + // (* s ... 0 ... r) --> 0 + if (c.is_zero()) { + result = mk_numeral(c); + return BR_DONE; + } + + if (num_coeffs == num_args - 1) { + SASSERT(var != 0); + // (* c_1 ... c_n x) --> x if c_1*...*c_n == 1 + if (c.is_one()) { + result = var; + return BR_DONE; + } + + numeral c_prime; + if (is_mul(var)) { + // apply basic simplification even when flattening is not enabled. + // (* c1 (* c2 x)) --> (* c1*c2 x) + if (to_app(var)->get_num_args() == 2 && is_numeral(to_app(var)->get_arg(0), c_prime)) { + c *= c_prime; + normalize(c); + result = mk_mul_app(c, to_app(var)->get_arg(1)); + return BR_REWRITE1; + } + else { + // var is a power-product + return BR_FAILED; + } + } + + if (num_add == 0 || m_hoist_cmul) { + SASSERT(!is_add(var) || m_hoist_cmul); + if (num_args == 2 && args[1] == var) { + DEBUG_CODE({ + numeral c_prime; + SASSERT(is_numeral(args[0], c_prime) && c == c_prime); + }); + // it is already simplified + return BR_FAILED; + } + + // (* c_1 ... c_n x) --> (* c_1*...*c_n x) + result = mk_mul_app(c, var); + return BR_DONE; + } + else { + SASSERT(is_add(var)); + // (* c_1 ... c_n (+ t_1 ... t_m)) --> (+ (* c_1*...*c_n t_1) ... (* c_1*...*c_n t_m)) + ptr_buffer new_add_args; + unsigned num = to_app(var)->get_num_args(); + for (unsigned i = 0; i < num; i++) { + new_add_args.push_back(mk_mul_app(c, to_app(var)->get_arg(i))); + } + result = mk_add_app(new_add_args.size(), new_add_args.c_ptr()); + TRACE("mul_bug", tout << "result: " << mk_bounded_pp(result, m(),5) << "\n";); + return BR_REWRITE2; + } + } + + SASSERT(num_coeffs <= num_args - 2); + + if (!m_som || num_add == 0) { + ptr_buffer new_args; + expr * prev = 0; + bool ordered = true; + for (unsigned i = 0; i < num_args; i++) { + expr * curr = args[i]; + if (is_numeral(curr)) + continue; + if (prev != 0 && lt(curr, prev)) + ordered = false; + new_args.push_back(curr); + prev = curr; + } + TRACE("poly_rewriter", + for (unsigned i = 0; i < new_args.size(); i++) { + if (i > 0) + tout << (lt(new_args[i-1], new_args[i]) ? " < " : " !< "); + tout << mk_ismt2_pp(new_args[i], m()); + } + tout << "\nordered: " << ordered << "\n";); + if (ordered && num_coeffs == 0 && !use_power()) + return BR_FAILED; + if (!ordered) { + if (use_power()) + std::sort(new_args.begin(), new_args.end(), mon_pw_lt(*this)); + else + std::sort(new_args.begin(), new_args.end(), ast_to_lt()); + TRACE("poly_rewriter", + tout << "after sorting:\n"; + for (unsigned i = 0; i < new_args.size(); i++) { + if (i > 0) + tout << (lt(new_args[i-1], new_args[i]) ? " < " : " !< "); + tout << mk_ismt2_pp(new_args[i], m()); + } + tout << "\n";); + } + SASSERT(new_args.size() >= 2); + result = mk_mul_app(new_args.size(), new_args.c_ptr()); + result = mk_mul_app(c, result); + TRACE("poly_rewriter", tout << "mk_nflat_mul_core result:\n" << mk_ismt2_pp(result, m()) << "\n";); + return BR_DONE; + } + + SASSERT(m_som && num_add > 0); + + sbuffer szs; + sbuffer it; + sbuffer sums; + for (unsigned i = 0; i < num_args; i ++) { + it.push_back(0); + expr * arg = args[i]; + if (is_add(arg)) { + sums.push_back(const_cast(to_app(arg)->get_args())); + szs.push_back(to_app(arg)->get_num_args()); + } + else { + sums.push_back(const_cast(args + i)); + szs.push_back(1); + SASSERT(sums.back()[0] == arg); + } + } + expr_ref_buffer sum(m()); // must be ref_buffer because we may throw an exception + ptr_buffer m_args; + TRACE("som", tout << "starting som...\n";); + do { + TRACE("som", for (unsigned i = 0; i < it.size(); i++) tout << it[i] << " "; + tout << "\n";); + if (sum.size() > m_som_blowup) + throw rewriter_exception(g_ste_blowup_msg); + m_args.reset(); + for (unsigned i = 0; i < num_args; i++) { + expr * const * v = sums[i]; + expr * arg = v[it[i]]; + m_args.push_back(arg); + } + sum.push_back(mk_mul_app(m_args.size(), m_args.c_ptr())); + } + while (product_iterator_next(szs.size(), szs.c_ptr(), it.c_ptr())); + result = mk_add_app(sum.size(), sum.c_ptr()); + return BR_REWRITE2; +} + +template +br_status poly_rewriter::mk_flat_add_core(unsigned num_args, expr * const * args, expr_ref & result) { + unsigned i; + for (i = 0; i < num_args; i++) { + if (is_add(args[i])) + break; + } + if (i < num_args) { + // has nested ADDs + ptr_buffer flat_args; + flat_args.append(i, args); + for (; i < num_args; i++) { + expr * arg = args[i]; + // Remark: all rewrites are depth 1. + if (is_add(arg)) { + unsigned num = to_app(arg)->get_num_args(); + for (unsigned j = 0; j < num; j++) + flat_args.push_back(to_app(arg)->get_arg(j)); + } + else { + flat_args.push_back(arg); + } + } + br_status st = mk_nflat_add_core(flat_args.size(), flat_args.c_ptr(), result); + if (st == BR_FAILED) { + result = mk_add_app(flat_args.size(), flat_args.c_ptr()); + return BR_DONE; + } + return st; + } + return mk_nflat_add_core(num_args, args, result); +} + +template +inline expr * poly_rewriter::get_power_product(expr * t) { + if (is_mul(t) && to_app(t)->get_num_args() == 2 && is_numeral(to_app(t)->get_arg(0))) + return to_app(t)->get_arg(1); + return t; +} + +template +inline expr * poly_rewriter::get_power_product(expr * t, numeral & a) { + if (is_mul(t) && to_app(t)->get_num_args() == 2 && is_numeral(to_app(t)->get_arg(0), a)) + return to_app(t)->get_arg(1); + a = numeral(1); + return t; +} + +template +bool poly_rewriter::is_mul(expr * t, numeral & c, expr * & pp) { + if (!is_mul(t) || to_app(t)->get_num_args() != 2) + return false; + if (!is_numeral(to_app(t)->get_arg(0), c)) + return false; + pp = to_app(t)->get_arg(1); + return true; +} + +template +struct poly_rewriter::hoist_cmul_lt { + poly_rewriter & m_r; + hoist_cmul_lt(poly_rewriter & r):m_r(r) {} + + bool operator()(expr * t1, expr * t2) const { + expr * pp1, * pp2; + numeral c1, c2; + bool is_mul1 = m_r.is_mul(t1, c1, pp1); + bool is_mul2 = m_r.is_mul(t2, c2, pp2); + if (!is_mul1 && is_mul2) + return true; + if (is_mul1 && !is_mul2) + return false; + if (!is_mul1 && !is_mul2) + return t1->get_id() < t2->get_id(); + if (c1 < c2) + return true; + if (c1 > c2) + return false; + return pp1->get_id() < pp2->get_id(); + } +}; + +template +void poly_rewriter::hoist_cmul(expr_ref_buffer & args) { + unsigned sz = args.size(); + std::sort(args.c_ptr(), args.c_ptr() + sz, hoist_cmul_lt(*this)); + numeral c, c_prime; + ptr_buffer pps; + expr * pp, * pp_prime; + unsigned j = 0; + unsigned i = 0; + while (i < sz) { + expr * mon = args[i]; + if (is_mul(mon, c, pp) && i < sz - 1) { + expr * mon_prime = args[i+1]; + if (is_mul(mon_prime, c_prime, pp_prime) && c == c_prime) { + // found target + pps.reset(); + pps.push_back(pp); + pps.push_back(pp_prime); + i += 2; + while (i < sz && is_mul(args[i], c_prime, pp_prime) && c == c_prime) { + pps.push_back(pp_prime); + i++; + } + SASSERT(is_numeral(to_app(mon)->get_arg(0), c_prime) && c == c_prime); + expr * mul_args[2] = { to_app(mon)->get_arg(0), mk_add_app(pps.size(), pps.c_ptr()) }; + args.set(j, mk_mul_app(2, mul_args)); + j++; + continue; + } + } + args.set(j, mon); + j++; + i++; + } + args.resize(j); +} + +template +br_status poly_rewriter::mk_nflat_add_core(unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(num_args >= 2); + numeral c; + unsigned num_coeffs = 0; + numeral a; + expr_fast_mark1 visited; // visited.is_marked(power_product) if the power_product occurs in args + expr_fast_mark2 multiple; // multiple.is_marked(power_product) if power_product occurs more than once + bool has_multiple = false; + expr * prev = 0; + bool ordered = true; + for (unsigned i = 0; i < num_args; i++) { + expr * arg = args[i]; + if (is_numeral(arg, a)) { + num_coeffs++; + c += a; + } + else { + // arg is not a numeral + if (m_sort_sums && ordered) { + if (prev != 0 && lt(arg, prev)) + ordered = false; + prev = arg; + } + } + + arg = get_power_product(arg); + if (visited.is_marked(arg)) { + multiple.mark(arg); + has_multiple = true; + } + else { + visited.mark(arg); + } + } + normalize(c); + SASSERT(m_sort_sums || ordered); + TRACE("sort_sums", + tout << "ordered: " << ordered << "\n"; + for (unsigned i = 0; i < num_args; i++) tout << mk_ismt2_pp(args[i], m()) << "\n";); + + if (has_multiple) { + // expensive case + buffer coeffs; + m_expr2pos.reset(); + // compute the coefficient of power products that occur multiple times. + for (unsigned i = 0; i < num_args; i++) { + expr * arg = args[i]; + if (is_numeral(arg)) + continue; + expr * pp = get_power_product(arg, a); + if (!multiple.is_marked(pp)) + continue; + unsigned pos; + if (m_expr2pos.find(pp, pos)) { + coeffs[pos] += a; + } + else { + m_expr2pos.insert(pp, coeffs.size()); + coeffs.push_back(a); + } + } + expr_ref_buffer new_args(m()); + if (!c.is_zero()) { + new_args.push_back(mk_numeral(c)); + } + // copy power products with non zero coefficients to new_args + visited.reset(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = args[i]; + if (is_numeral(arg)) + continue; + expr * pp = get_power_product(arg); + if (!multiple.is_marked(pp)) { + new_args.push_back(arg); + } + else if (!visited.is_marked(pp)) { + visited.mark(pp); + unsigned pos = UINT_MAX; + m_expr2pos.find(pp, pos); + SASSERT(pos != UINT_MAX); + a = coeffs[pos]; + normalize(a); + if (!a.is_zero()) + new_args.push_back(mk_mul_app(a, pp)); + } + } + if (m_hoist_cmul) { + hoist_cmul(new_args); + } + else if (m_sort_sums) { + if (c.is_zero()) + std::sort(new_args.c_ptr(), new_args.c_ptr() + new_args.size(), ast_to_lt()); + else + std::sort(new_args.c_ptr() + 1, new_args.c_ptr() + new_args.size() - 1, ast_to_lt()); + } + result = mk_add_app(new_args.size(), new_args.c_ptr()); + if (hoist_multiplication(result)) { + return BR_REWRITE_FULL; + } + return BR_DONE; + } + else { + SASSERT(!has_multiple); + if (ordered && !m_hoist_mul && !m_hoist_cmul) { + if (num_coeffs == 0) + return BR_FAILED; + if (num_coeffs == 1 && is_numeral(args[0], a) && !a.is_zero()) + return BR_FAILED; + } + expr_ref_buffer new_args(m()); + if (!c.is_zero()) + new_args.push_back(mk_numeral(c)); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = args[i]; + if (is_numeral(arg)) + continue; + new_args.push_back(arg); + } + if (m_hoist_cmul) { + hoist_cmul(new_args); + } + else if (!ordered) { + if (c.is_zero()) + std::sort(new_args.c_ptr(), new_args.c_ptr() + new_args.size(), ast_to_lt()); + else + std::sort(new_args.c_ptr() + 1, new_args.c_ptr() + new_args.size() - 1, ast_to_lt()); + } + result = mk_add_app(new_args.size(), new_args.c_ptr()); + if (hoist_multiplication(result)) { + return BR_REWRITE_FULL; + } + return BR_DONE; + } +} + + +template +br_status poly_rewriter::mk_uminus(expr * arg, expr_ref & result) { + numeral a; + set_curr_sort(m().get_sort(arg)); + if (is_numeral(arg, a)) { + a.neg(); + normalize(a); + result = mk_numeral(a); + return BR_DONE; + } + else { + result = mk_mul_app(numeral(-1), arg); + return BR_REWRITE1; + } +} + +template +br_status poly_rewriter::mk_sub(unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(num_args > 0); + if (num_args == 1) { + result = args[0]; + return BR_DONE; + } + set_curr_sort(m().get_sort(args[0])); + expr * minus_one = mk_numeral(numeral(-1)); + ptr_buffer new_args; + new_args.push_back(args[0]); + for (unsigned i = 1; i < num_args; i++) { + expr * aux_args[2] = { minus_one, args[i] }; + new_args.push_back(mk_mul_app(2, aux_args)); + } + result = mk_add_app(new_args.size(), new_args.c_ptr()); + return BR_REWRITE2; +} + +/** + \brief Cancel/Combine monomials that occur is the left and right hand sides. + + \remark If move = true, then all non-constant monomials are moved to the left-hand-side. +*/ +template +br_status poly_rewriter::cancel_monomials(expr * lhs, expr * rhs, bool move, expr_ref & lhs_result, expr_ref & rhs_result) { + set_curr_sort(m().get_sort(lhs)); + unsigned lhs_sz; + expr * const * lhs_monomials = get_monomials(lhs, lhs_sz); + unsigned rhs_sz; + expr * const * rhs_monomials = get_monomials(rhs, rhs_sz); + + expr_fast_mark1 visited; // visited.is_marked(power_product) if the power_product occurs in lhs or rhs + expr_fast_mark2 multiple; // multiple.is_marked(power_product) if power_product occurs more than once + bool has_multiple = false; + + numeral c(0); + numeral a; + unsigned num_coeffs = 0; + + for (unsigned i = 0; i < lhs_sz; i++) { + expr * arg = lhs_monomials[i]; + if (is_numeral(arg, a)) { + c += a; + num_coeffs++; + } + else { + visited.mark(get_power_product(arg)); + } + } + + if (move && num_coeffs == 0 && is_numeral(rhs)) + return BR_FAILED; + + for (unsigned i = 0; i < rhs_sz; i++) { + expr * arg = rhs_monomials[i]; + if (is_numeral(arg, a)) { + c -= a; + num_coeffs++; + } + else { + expr * pp = get_power_product(arg); + if (visited.is_marked(pp)) { + multiple.mark(pp); + has_multiple = true; + } + } + } + + normalize(c); + + if (!has_multiple && num_coeffs <= 1) { + if (move) { + if (is_numeral(rhs)) + return BR_FAILED; + } + else { + if (num_coeffs == 0 || is_numeral(rhs)) + return BR_FAILED; + } + } + + buffer coeffs; + m_expr2pos.reset(); + for (unsigned i = 0; i < lhs_sz; i++) { + expr * arg = lhs_monomials[i]; + if (is_numeral(arg)) + continue; + expr * pp = get_power_product(arg, a); + if (!multiple.is_marked(pp)) + continue; + unsigned pos; + if (m_expr2pos.find(pp, pos)) { + coeffs[pos] += a; + } + else { + m_expr2pos.insert(pp, coeffs.size()); + coeffs.push_back(a); + } + } + + for (unsigned i = 0; i < rhs_sz; i++) { + expr * arg = rhs_monomials[i]; + if (is_numeral(arg)) + continue; + expr * pp = get_power_product(arg, a); + if (!multiple.is_marked(pp)) + continue; + unsigned pos = UINT_MAX; + m_expr2pos.find(pp, pos); + SASSERT(pos != UINT_MAX); + coeffs[pos] -= a; + } + + + ptr_buffer new_lhs_monomials; + new_lhs_monomials.push_back(0); // save space for coefficient if needed + // copy power products with non zero coefficients to new_lhs_monomials + visited.reset(); + for (unsigned i = 0; i < lhs_sz; i++) { + expr * arg = lhs_monomials[i]; + if (is_numeral(arg)) + continue; + expr * pp = get_power_product(arg); + if (!multiple.is_marked(pp)) { + new_lhs_monomials.push_back(arg); + } + else if (!visited.is_marked(pp)) { + visited.mark(pp); + unsigned pos = UINT_MAX; + m_expr2pos.find(pp, pos); + SASSERT(pos != UINT_MAX); + a = coeffs[pos]; + if (!a.is_zero()) + new_lhs_monomials.push_back(mk_mul_app(a, pp)); + } + } + + ptr_buffer new_rhs_monomials; + new_rhs_monomials.push_back(0); // save space for coefficient if needed + for (unsigned i = 0; i < rhs_sz; i++) { + expr * arg = rhs_monomials[i]; + if (is_numeral(arg)) + continue; + expr * pp = get_power_product(arg, a); + if (!multiple.is_marked(pp)) { + if (move) { + if (!a.is_zero()) { + if (a.is_minus_one()) { + new_lhs_monomials.push_back(pp); + } + else { + a.neg(); + SASSERT(!a.is_one()); + expr * args[2] = { mk_numeral(a), pp }; + new_lhs_monomials.push_back(mk_mul_app(2, args)); + } + } + } + else { + new_rhs_monomials.push_back(arg); + } + } + } + + bool c_at_rhs = false; + if (move) { + if (m_sort_sums) { + // + 1 to skip coefficient + std::sort(new_lhs_monomials.begin() + 1, new_lhs_monomials.end(), ast_to_lt()); + } + c_at_rhs = true; + } + else if (new_rhs_monomials.size() == 1) { // rhs is empty + c_at_rhs = true; + } + else if (new_lhs_monomials.size() > 1) { + c_at_rhs = true; + } + + if (c_at_rhs) { + c.neg(); + normalize(c); + new_rhs_monomials[0] = mk_numeral(c); + lhs_result = mk_add_app(new_lhs_monomials.size() - 1, new_lhs_monomials.c_ptr() + 1); + rhs_result = mk_add_app(new_rhs_monomials.size(), new_rhs_monomials.c_ptr()); + } + else { + new_lhs_monomials[0] = mk_numeral(c); + lhs_result = mk_add_app(new_lhs_monomials.size(), new_lhs_monomials.c_ptr()); + rhs_result = mk_add_app(new_rhs_monomials.size() - 1, new_rhs_monomials.c_ptr() + 1); + } + return BR_DONE; +} + +#define TO_BUFFER(_tester_, _buffer_, _e_) \ + _buffer_.push_back(_e_); \ + for (unsigned _i = 0; _i < _buffer_.size(); ) { \ + expr* _e = _buffer_[_i]; \ + if (_tester_(_e)) { \ + app* a = to_app(_e); \ + _buffer_[_i] = a->get_arg(0); \ + for (unsigned _j = 1; _j < a->get_num_args(); ++_j) { \ + _buffer_.push_back(a->get_arg(_j)); \ + } \ + } \ + else { \ + ++_i; \ + } \ + } \ + +template +bool poly_rewriter::hoist_multiplication(expr_ref& som) { + if (!m_hoist_mul) { + return false; + } + ptr_buffer adds, muls; + TO_BUFFER(is_add, adds, som); + buffer valid(adds.size(), true); + obj_map mul_map; + unsigned j; + bool change = false; + for (unsigned k = 0; k < adds.size(); ++k) { + expr* e = adds[k]; + muls.reset(); + TO_BUFFER(is_mul, muls, e); + for (unsigned i = 0; i < muls.size(); ++i) { + e = muls[i]; + if (is_numeral(e)) { + continue; + } + if (mul_map.find(e, j) && valid[j] && j != k) { + m_curr_sort = m().get_sort(adds[k]); + adds[j] = merge_muls(adds[j], adds[k]); + adds[k] = mk_numeral(rational(0)); + valid[j] = false; + valid[k] = false; + change = true; + break; + } + else { + mul_map.insert(e, k); + } + } + } + if (!change) { + return false; + } + + som = mk_add_app(adds.size(), adds.c_ptr()); + + + return true; +} + +template +expr* poly_rewriter::merge_muls(expr* x, expr* y) { + ptr_buffer m1, m2; + TO_BUFFER(is_mul, m1, x); + TO_BUFFER(is_mul, m2, y); + unsigned k = 0; + for (unsigned i = 0; i < m1.size(); ++i) { + x = m1[i]; + bool found = false; + unsigned j; + for (j = k; j < m2.size(); ++j) { + found = m2[j] == x; + if (found) break; + } + if (found) { + std::swap(m1[i],m1[k]); + std::swap(m2[j],m2[k]); + ++k; + } + } + m_curr_sort = m().get_sort(x); + SASSERT(k > 0); + SASSERT(m1.size() >= k); + SASSERT(m2.size() >= k); + expr* args[2] = { mk_mul_app(m1.size()-k, m1.c_ptr()+k), + mk_mul_app(m2.size()-k, m2.c_ptr()+k) }; + if (k == m1.size()) { + m1.push_back(0); + } + m1[k] = mk_add_app(2, args); + return mk_mul_app(k+1, m1.c_ptr()); +} diff --git a/lib/poly_simplifier_plugin.cpp b/lib/poly_simplifier_plugin.cpp new file mode 100644 index 000000000..13e5748dc --- /dev/null +++ b/lib/poly_simplifier_plugin.cpp @@ -0,0 +1,812 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + poly_simplifier_plugin.cpp + +Abstract: + + Abstract class for families that have polynomials. + +Author: + + Leonardo (leonardo) 2008-01-08 + +--*/ +#include"poly_simplifier_plugin.h" +#include"ast_pp.h" +#include"ast_util.h" +#include"ast_smt2_pp.h" + +poly_simplifier_plugin::poly_simplifier_plugin(symbol const & fname, ast_manager & m, decl_kind add, decl_kind mul, decl_kind uminus, decl_kind sub, + decl_kind num): + simplifier_plugin(fname, m), + m_ADD(add), + m_MUL(mul), + m_SUB(sub), + m_UMINUS(uminus), + m_NUM(num), + m_curr_sort(0), + m_curr_sort_zero(0) { +} + +expr * poly_simplifier_plugin::mk_add(unsigned num_args, expr * const * args) { + SASSERT(num_args > 0); +#ifdef Z3DEBUG + // check for incorrect use of mk_add + for (unsigned i = 0; i < num_args; i++) { + SASSERT(!is_zero(args[i])); + } +#endif + if (num_args == 1) + return args[0]; + else + return m_manager.mk_app(m_fid, m_ADD, num_args, args); +} + +expr * poly_simplifier_plugin::mk_mul(unsigned num_args, expr * const * args) { + SASSERT(num_args > 0); +#ifdef Z3DEBUG + // check for incorrect use of mk_mul + set_curr_sort(args[0]); + SASSERT(!is_zero(args[0])); + numeral k; + for (unsigned i = 0; i < num_args; i++) { + SASSERT(!is_numeral(args[i], k) || !k.is_one()); + SASSERT(i == 0 || !is_numeral(args[i])); + } +#endif + if (num_args == 1) + return args[0]; + else if (num_args == 2) + return m_manager.mk_app(m_fid, m_MUL, args[0], args[1]); + else if (is_numeral(args[0])) + return m_manager.mk_app(m_fid, m_MUL, args[0], m_manager.mk_app(m_fid, m_MUL, num_args - 1, args+1)); + else + return m_manager.mk_app(m_fid, m_MUL, num_args, args); +} + +expr * poly_simplifier_plugin::mk_mul(numeral const & c, expr * body) { + numeral c_prime; + c_prime = norm(c); + if (c_prime.is_zero()) + return 0; + if (body == 0) + return mk_numeral(c_prime); + if (c_prime.is_one()) + return body; + set_curr_sort(body); + expr * args[2] = { mk_numeral(c_prime), body }; + return mk_mul(2, args); +} + +/** + \brief Traverse args, and copy the non-numeral exprs to result, and accumulate the + value of the numerals in k. +*/ +void poly_simplifier_plugin::process_monomial(unsigned num_args, expr * const * args, numeral & k, ptr_buffer & result) { + rational v; + for (unsigned i = 0; i < num_args; i++) { + expr * arg = args[i]; + if (is_numeral(arg, v)) + k *= v; + else + result.push_back(arg); + } +} + +#ifdef Z3DEBUG +/** + \brief Return true if m is a wellformed monomial. +*/ +bool poly_simplifier_plugin::wf_monomial(expr * m) const { + SASSERT(!is_add(m)); + if (is_mul(m)) { + app * curr = to_app(m); + expr * pp = 0; + if (is_numeral(curr->get_arg(0))) + pp = curr->get_arg(1); + else + pp = curr; + if (is_mul(pp)) { + for (unsigned i = 0; i < to_app(pp)->get_num_args(); i++) { + expr * arg = to_app(pp)->get_arg(i); + CTRACE("wf_monomial_bug", is_mul(arg), + tout << "m: " << mk_ismt2_pp(m, m_manager) << "\n"; + tout << "pp: " << mk_ismt2_pp(pp, m_manager) << "\n"; + tout << "arg: " << mk_ismt2_pp(arg, m_manager) << "\n"; + tout << "i: " << i << "\n"; + ); + SASSERT(!is_mul(arg)); + SASSERT(!is_numeral(arg)); + } + } + } + return true; +} + +/** + \brief Return true if m is a wellformed polynomial. +*/ +bool poly_simplifier_plugin::wf_polynomial(expr * m) const { + if (is_add(m)) { + for (unsigned i = 0; i < to_app(m)->get_num_args(); i++) { + expr * arg = to_app(m)->get_arg(i); + SASSERT(!is_add(arg)); + SASSERT(wf_monomial(arg)); + } + } + else if (is_mul(m)) { + SASSERT(wf_monomial(m)); + } + return true; +} +#endif + +/** + \brief Functor used to sort the elements of a monomial. + Force numeric constants to be in the beginning. +*/ +struct monomial_element_lt_proc { + poly_simplifier_plugin & m_plugin; + monomial_element_lt_proc(poly_simplifier_plugin & p):m_plugin(p) {} + bool operator()(expr * m1, expr * m2) const { + SASSERT(!m_plugin.is_numeral(m1) || !m_plugin.is_numeral(m2)); + if (m_plugin.is_numeral(m1)) + return true; + if (m_plugin.is_numeral(m2)) + return false; + return m1->get_id() < m2->get_id(); + } +}; + +/** + \brief Create a monomial (* args). +*/ +void poly_simplifier_plugin::mk_monomial(unsigned num_args, expr * * args, expr_ref & result) { + switch(num_args) { + case 0: + result = mk_one(); + break; + case 1: + result = args[0]; + break; + default: + std::sort(args, args + num_args, monomial_element_lt_proc(*this)); + result = mk_mul(num_args, args); + SASSERT(wf_monomial(result)); + break; + } +} + +/** + \brief Return the body of the monomial. That is, the monomial without a coefficient. + Examples: (* 2 (* x y)) ==> (* x y) + (* x x) ==> (* x x) + x ==> x + 10 ==> 10 +*/ +expr * poly_simplifier_plugin::get_monomial_body(expr * m) { + TRACE("get_monomial_body_bug", tout << mk_pp(m, m_manager) << "\n";); + SASSERT(wf_monomial(m)); + if (!is_mul(m)) + return m; + if (is_numeral(to_app(m)->get_arg(0))) + return to_app(m)->get_arg(1); + return m; +} + +inline bool is_essentially_var(expr * n, family_id fid) { + SASSERT(is_var(n) || is_app(n)); + return is_var(n) || to_app(n)->get_family_id() != fid; +} + +/** + \brief Hack for ordering monomials. + We want an order << where + - (* c1 m1) << (* c2 m2) when m1->get_id() < m2->get_id(), and c1 and c2 are numerals. + - c << m when c is a numeral, and m is not. + + So, this method returns -1 for numerals, and the id of the body of the monomial +*/ +int poly_simplifier_plugin::get_monomial_body_order(expr * m) { + if (is_essentially_var(m, m_fid)) { + return m->get_id(); + } + else if (is_mul(m)) { + if (is_numeral(to_app(m)->get_arg(0))) + return to_app(m)->get_arg(1)->get_id(); + else + return m->get_id(); + } + else if (is_numeral(m)) { + return -1; + } + else { + return m->get_id(); + } +} + +void poly_simplifier_plugin::get_monomial_coeff(expr * m, numeral & result) { + SASSERT(!is_numeral(m)); + SASSERT(wf_monomial(m)); + if (!is_mul(m)) + result = numeral::one(); + else if (is_numeral(to_app(m)->get_arg(0), result)) + return; + else + result = numeral::one(); +} + +/** + \brief Return true if n1 and n2 can be written as k1 * t and k2 * t, where k1 and + k2 are numerals, or n1 and n2 are both numerals. +*/ +bool poly_simplifier_plugin::eq_monomials_modulo_k(expr * n1, expr * n2) { + bool is_num1 = is_numeral(n1); + bool is_num2 = is_numeral(n2); + if (is_num1 != is_num2) + return false; + if (is_num1 && is_num2) + return true; + return get_monomial_body(n1) == get_monomial_body(n2); +} + +/** + \brief Return (k1 + k2) * t (or (k1 - k2) * t when inv = true), where n1 = k1 * t, and n2 = k2 * t + Return false if the monomials cancel each other. +*/ +bool poly_simplifier_plugin::merge_monomials(bool inv, expr * n1, expr * n2, expr_ref & result) { + numeral k1; + numeral k2; + bool is_num1 = is_numeral(n1, k1); + bool is_num2 = is_numeral(n2, k2); + SASSERT(is_num1 == is_num2); + if (!is_num1 && !is_num2) { + get_monomial_coeff(n1, k1); + get_monomial_coeff(n2, k2); + SASSERT(eq_monomials_modulo_k(n1, n2)); + } + if (inv) + k1 -= k2; + else + k1 += k2; + if (k1.is_zero()) + return false; + if (is_num1 && is_num2) { + result = mk_numeral(k1); + } + else { + expr * b = get_monomial_body(n1); + if (k1.is_one()) + result = b; + else + result = m_manager.mk_app(m_fid, m_MUL, mk_numeral(k1), b); + } + return true; +} + +/** + \brief Return a monomial equivalent to -n. +*/ +void poly_simplifier_plugin::inv_monomial(expr * n, expr_ref & result) { + set_curr_sort(n); + SASSERT(wf_monomial(n)); + rational v; + SASSERT(n != 0); + TRACE("inv_monomial_bug", tout << "n:\n" << mk_ismt2_pp(n, m_manager) << "\n";); + if (is_numeral(n, v)) { + TRACE("inv_monomial_bug", tout << "is numeral\n";); + v.neg(); + result = mk_numeral(v); + } + else { + TRACE("inv_monomial_bug", tout << "is not numeral\n";); + numeral k; + get_monomial_coeff(n, k); + expr * b = get_monomial_body(n); + k.neg(); + if (k.is_one()) + result = b; + else + result = m_manager.mk_app(m_fid, m_MUL, mk_numeral(k), b); + } +} + +/** + \brief Add a monomial n to result. +*/ +template +void poly_simplifier_plugin::add_monomial_core(expr * n, expr_ref_vector & result) { + if (is_zero(n)) + return; + if (Inv) { + expr_ref n_prime(m_manager); + inv_monomial(n, n_prime); + result.push_back(n_prime); + } + else { + result.push_back(n); + } +} + +void poly_simplifier_plugin::add_monomial(bool inv, expr * n, expr_ref_vector & result) { + if (inv) + add_monomial_core(n, result); + else + add_monomial_core(n, result); +} + +/** + \brief Copy the monomials in n to result. The monomials are inverted if inv is true. + Equivalent monomials are merged. +*/ +template +void poly_simplifier_plugin::process_sum_of_monomials_core(expr * n, expr_ref_vector & result) { + SASSERT(wf_polynomial(n)); + if (is_add(n)) { + for (unsigned i = 0; i < to_app(n)->get_num_args(); i++) + add_monomial_core(to_app(n)->get_arg(i), result); + } + else { + add_monomial_core(n, result); + } +} + +void poly_simplifier_plugin::process_sum_of_monomials(bool inv, expr * n, expr_ref_vector & result) { + if (inv) + process_sum_of_monomials_core(n, result); + else + process_sum_of_monomials_core(n, result); +} + +/** + \brief Copy the (non-numeral) monomials in n to result. The monomials are inverted if inv is true. + Equivalent monomials are merged. The constant (numeral) monomials are accumulated in k. +*/ +void poly_simplifier_plugin::process_sum_of_monomials(bool inv, expr * n, expr_ref_vector & result, numeral & k) { + SASSERT(wf_polynomial(n)); + numeral val; + if (is_add(n)) { + for (unsigned i = 0; i < to_app(n)->get_num_args(); i++) { + expr * arg = to_app(n)->get_arg(i); + if (is_numeral(arg, val)) { + k += inv ? -val : val; + } + else { + add_monomial(inv, arg, result); + } + } + } + else if (is_numeral(n, val)) { + k += inv ? -val : val; + } + else { + add_monomial(inv, n, result); + } +} + +/** + \brief Functor used to sort monomials. + Force numeric constants to be in the beginning of a polynomial. +*/ +struct monomial_lt_proc { + poly_simplifier_plugin & m_plugin; + monomial_lt_proc(poly_simplifier_plugin & p):m_plugin(p) {} + bool operator()(expr * m1, expr * m2) const { + return m_plugin.get_monomial_body_order(m1) < m_plugin.get_monomial_body_order(m2); + } +}; + +void poly_simplifier_plugin::mk_sum_of_monomials_core(unsigned sz, expr ** ms, expr_ref & result) { + switch (sz) { + case 0: + result = mk_zero(); + break; + case 1: + result = ms[0]; + break; + default: + result = mk_add(sz, ms); + break; + } +} + +/** + \brief Return true if m is essentially a variable, or is of the form (* c x), + where c is a numeral and x is essentially a variable. + Store the "variable" in x. +*/ +bool poly_simplifier_plugin::is_simple_monomial(expr * m, expr * & x) { + if (is_essentially_var(m, m_fid)) { + x = m; + return true; + } + if (is_app(m) && to_app(m)->get_num_args() == 2) { + expr * arg1 = to_app(m)->get_arg(0); + expr * arg2 = to_app(m)->get_arg(1); + if (is_numeral(arg1) && is_essentially_var(arg2, m_fid)) { + x = arg2; + return true; + } + } + return false; +} + +/** + \brief Return true if all monomials are simple, and each "variable" occurs only once. + The method assumes the monomials were sorted using monomial_lt_proc. +*/ +bool poly_simplifier_plugin::is_simple_sum_of_monomials(expr_ref_vector & monomials) { + expr * last_var = 0; + expr * curr_var = 0; + unsigned size = monomials.size(); + for (unsigned i = 0; i < size; i++) { + expr * m = monomials.get(i); + if (!is_simple_monomial(m, curr_var)) + return false; + if (curr_var == last_var) + return false; + last_var = curr_var; + } + return true; +} + +/** + \brief Store in result the sum of the given monomials. +*/ +void poly_simplifier_plugin::mk_sum_of_monomials(expr_ref_vector & monomials, expr_ref & result) { + switch (monomials.size()) { + case 0: + result = mk_zero(); + break; + case 1: + result = monomials.get(0); + break; + default: { + std::sort(monomials.c_ptr(), monomials.c_ptr() + monomials.size(), monomial_lt_proc(*this)); + if (is_simple_sum_of_monomials(monomials)) { + mk_sum_of_monomials_core(monomials.size(), monomials.c_ptr(), result); + return; + } + ptr_buffer new_monomials; + expr * last_body = 0; + numeral last_coeff; + numeral coeff; + unsigned sz = monomials.size(); + for (unsigned i = 0; i < sz; i++) { + expr * m = monomials.get(i); + expr * body = 0; + if (!is_numeral(m, coeff)) { + body = get_monomial_body(m); + get_monomial_coeff(m, coeff); + } + if (last_body == body) { + last_coeff += coeff; + continue; + } + expr * new_m = mk_mul(last_coeff, last_body); + if (new_m) + new_monomials.push_back(new_m); + last_body = body; + last_coeff = coeff; + } + expr * new_m = mk_mul(last_coeff, last_body); + if (new_m) + new_monomials.push_back(new_m); + TRACE("mk_sum", for (unsigned i = 0; i < monomials.size(); i++) tout << mk_pp(monomials.get(i), m_manager) << "\n"; + tout << "======>\n"; + for (unsigned i = 0; i < new_monomials.size(); i++) tout << mk_pp(new_monomials.get(i), m_manager) << "\n";); + mk_sum_of_monomials_core(new_monomials.size(), new_monomials.c_ptr(), result); + break; + } } +} + +/** + \brief Auxiliary template for mk_add_core +*/ +template +void poly_simplifier_plugin::mk_add_core_core(unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(num_args >= 2); + expr_ref_vector monomials(m_manager); + process_sum_of_monomials_core(args[0], monomials); + for (unsigned i = 1; i < num_args; i++) { + process_sum_of_monomials_core(args[i], monomials); + } + TRACE("mk_add_core_bug", + for (unsigned i = 0; i < monomials.size(); i++) { + SASSERT(monomials.get(i) != 0); + tout << mk_ismt2_pp(monomials.get(i), m_manager) << "\n"; + }); + mk_sum_of_monomials(monomials, result); +} + +/** + \brief Return a sum of monomials. The method assume that each arg in args is a sum of monomials. + If inv is true, then all but the first argument in args are inverted. +*/ +void poly_simplifier_plugin::mk_add_core(bool inv, unsigned num_args, expr * const * args, expr_ref & result) { + TRACE("mk_add_core_bug", + for (unsigned i = 0; i < num_args; i++) { + SASSERT(args[i] != 0); + tout << mk_ismt2_pp(args[i], m_manager) << "\n"; + }); + switch (num_args) { + case 0: + result = mk_zero(); + break; + case 1: + result = args[0]; + break; + default: + if (inv) + mk_add_core_core(num_args, args, result); + else + mk_add_core_core(num_args, args, result); + break; + } +} + +void poly_simplifier_plugin::mk_add(unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(num_args > 0); + set_curr_sort(args[0]); + mk_add_core(false, num_args, args, result); +} + +void poly_simplifier_plugin::mk_add(expr * arg1, expr * arg2, expr_ref & result) { + expr * args[2] = { arg1, arg2 }; + mk_add(2, args, result); +} + +void poly_simplifier_plugin::mk_sub(unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(num_args > 0); + set_curr_sort(args[0]); + mk_add_core(true, num_args, args, result); +} + +void poly_simplifier_plugin::mk_sub(expr * arg1, expr * arg2, expr_ref & result) { + expr * args[2] = { arg1, arg2 }; + mk_sub(2, args, result); +} + +void poly_simplifier_plugin::mk_uminus(expr * arg, expr_ref & result) { + set_curr_sort(arg); + rational v; + if (is_numeral(arg, v)) { + v.neg(); + result = mk_numeral(v); + } + else { + expr_ref zero(mk_zero(), m_manager); + mk_sub(zero.get(), arg, result); + } +} + +/** + \brief Add monomial n to result, the coeff of n is stored in k. +*/ +void poly_simplifier_plugin::append_to_monomial(expr * n, numeral & k, ptr_buffer & result) { + SASSERT(wf_monomial(n)); + rational val; + if (is_numeral(n, val)) { + k *= val; + return; + } + get_monomial_coeff(n, val); + k *= val; + n = get_monomial_body(n); + + if (is_mul(n)) { + for (unsigned i = 0; i < to_app(n)->get_num_args(); i++) + result.push_back(to_app(n)->get_arg(i)); + } + else { + result.push_back(n); + } +} + +/** + \brief Return a sum of monomials that is equivalent to (* args[0] ... args[num_args-1]). + This method assumes that each arg[i] is a sum of monomials. +*/ +void poly_simplifier_plugin::mk_mul(unsigned num_args, expr * const * args, expr_ref & result) { + if (num_args == 1) { + result = args[0]; + return; + } + rational val; + if (num_args == 2 && is_numeral(args[0], val) && is_essentially_var(args[1], m_fid)) { + if (val.is_one()) + result = args[1]; + else if (val.is_zero()) + result = args[0]; + else + result = mk_mul(num_args, args); + return; + } + if (num_args == 2 && is_essentially_var(args[0], m_fid) && is_numeral(args[1], val)) { + if (val.is_one()) + result = args[0]; + else if (val.is_zero()) + result = args[1]; + else { + expr * inv_args[2] = { args[1], args[0] }; + result = mk_mul(2, inv_args); + } + return; + } + + TRACE("mk_mul_bug", + for (unsigned i = 0; i < num_args; i++) { + tout << mk_pp(args[i], m_manager) << "\n"; + }); + set_curr_sort(args[0]); + buffer szs; + buffer it; + vector > sums; + for (unsigned i = 0; i < num_args; i ++) { + it.push_back(0); + expr * arg = args[i]; + SASSERT(wf_polynomial(arg)); + sums.push_back(ptr_vector()); + ptr_vector & v = sums.back(); + if (is_add(arg)) { + v.append(to_app(arg)->get_num_args(), to_app(arg)->get_args()); + } + else { + v.push_back(arg); + } + szs.push_back(v.size()); + } + expr_ref_vector monomials(m_manager); + do { + rational k(1); + ptr_buffer m; + for (unsigned i = 0; i < num_args; i++) { + ptr_vector & v = sums[i]; + expr * arg = v[it[i]]; + TRACE("mk_mul_bug", tout << "k: " << k << " arg: " << mk_pp(arg, m_manager) << "\n";); + append_to_monomial(arg, k, m); + TRACE("mk_mul_bug", tout << "after k: " << k << "\n";); + } + expr_ref num(m_manager); + if (!k.is_zero() && !k.is_one()) { + num = mk_numeral(k); + m.push_back(num); + // bit-vectors can normalize + // to 1 during + // internalization. + if (is_numeral(num, k) && k.is_one()) { + m.pop_back(); + } + } + if (!k.is_zero()) { + expr_ref new_monomial(m_manager); + TRACE("mk_mul_bug", + for (unsigned i = 0; i < m.size(); i++) { + tout << mk_pp(m[i], m_manager) << "\n"; + }); + mk_monomial(m.size(), m.c_ptr(), new_monomial); + TRACE("mk_mul_bug", tout << "new_monomial:\n" << mk_pp(new_monomial, m_manager) << "\n";); + add_monomial_core(new_monomial, monomials); + } + } + while (product_iterator_next(szs.size(), szs.c_ptr(), it.c_ptr())); + mk_sum_of_monomials(monomials, result); +} + +void poly_simplifier_plugin::mk_mul(expr * arg1, expr * arg2, expr_ref & result) { + expr * args[2] = { arg1, arg2 }; + mk_mul(2, args, result); +} + +bool poly_simplifier_plugin::reduce_distinct(unsigned num_args, expr * const * args, expr_ref & result) { + set_reduce_invoked(); + unsigned i = 0; + for (; i < num_args; i++) + if (!is_numeral(args[i])) + break; + if (i == num_args) { + // all arguments are numerals + // check if arguments are different... + ptr_buffer buffer; + buffer.append(num_args, args); + std::sort(buffer.begin(), buffer.end(), ast_lt_proc()); + for (unsigned i = 0; i < num_args; i++) { + if (i > 0 && buffer[i] == buffer[i-1]) { + result = m_manager.mk_false(); + return true; + } + } + result = m_manager.mk_true(); + return true; + } + return false; +} + +bool poly_simplifier_plugin::reduce(func_decl * f, unsigned num_args, rational const * mults, expr * const * args, expr_ref & result) { + set_reduce_invoked(); + if (is_decl_of(f, m_fid, m_ADD)) { + SASSERT(num_args > 0); + set_curr_sort(args[0]); + expr_ref_buffer args1(m_manager); + for (unsigned i = 0; i < num_args; ++i) { + expr * arg = args[i]; + rational m = norm(mults[i]); + if (m.is_zero()) { + // skip + } + else if (m.is_one()) { + args1.push_back(arg); + } + else { + expr_ref k(m_manager); + k = mk_numeral(m); + expr_ref new_arg(m_manager); + mk_mul(k, args[i], new_arg); + args1.push_back(new_arg); + } + } + if (args1.empty()) { + result = mk_zero(); + } + else { + mk_add(args1.size(), args1.c_ptr(), result); + } + return true; + } + else { + return simplifier_plugin::reduce(f, num_args, mults, args, result); + } +} + +/** + \brief Return true if n is can be put into the form (+ v t) or (+ (- v) t) + \c inv = true will contain true if (- v) is found, and false otherwise. +*/ +bool poly_simplifier_plugin::is_var_plus_ground(expr * n, bool & inv, var * & v, expr_ref & t) { + if (!is_add(n) || is_ground(n)) + return false; + + ptr_buffer args; + v = 0; + expr * curr = to_app(n); + bool stop = false; + inv = false; + while (!stop) { + expr * arg; + expr * neg_arg; + if (is_add(curr)) { + arg = to_app(curr)->get_arg(0); + curr = to_app(curr)->get_arg(1); + } + else { + arg = curr; + stop = true; + } + if (is_ground(arg)) { + TRACE("model_checker_bug", tout << "pushing:\n" << mk_pp(arg, m_manager) << "\n";); + args.push_back(arg); + } + else if (is_var(arg)) { + if (v != 0) + return false; // already found variable + v = to_var(arg); + } + else if (is_times_minus_one(arg, neg_arg) && is_var(neg_arg)) { + if (v != 0) + return false; // already found variable + v = to_var(neg_arg); + inv = true; + } + else { + return false; // non ground term. + } + } + if (v == 0) + return false; // did not find variable + SASSERT(!args.empty()); + mk_add(args.size(), args.c_ptr(), t); + return true; +} diff --git a/lib/poly_simplifier_plugin.h b/lib/poly_simplifier_plugin.h new file mode 100644 index 000000000..66ecf4bb0 --- /dev/null +++ b/lib/poly_simplifier_plugin.h @@ -0,0 +1,151 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + poly_simplifier_plugin.h + +Abstract: + + Abstract class for families that have polynomials. + +Author: + + Leonardo (leonardo) 2008-01-08 + +--*/ +#ifndef _POLY_SIMPLIFIER_PLUGIN_H_ +#define _POLY_SIMPLIFIER_PLUGIN_H_ + +#include "simplifier_plugin.h" + +/** + \brief Abstract class that provides simplification functions for polynomials. +*/ +class poly_simplifier_plugin : public simplifier_plugin { +protected: + typedef rational numeral; + decl_kind m_ADD; + decl_kind m_MUL; + decl_kind m_SUB; + decl_kind m_UMINUS; + decl_kind m_NUM; + sort * m_curr_sort; + expr * m_curr_sort_zero; + + expr * mk_add(unsigned num_args, expr * const * args); + expr * mk_add(expr * arg1, expr * arg2) { expr * args[2] = { arg1, arg2 }; return mk_add(2, args); } + expr * mk_mul(unsigned num_args, expr * const * args); + expr * mk_mul(expr * arg1, expr * arg2) { expr * args[2] = { arg1, arg2 }; return mk_mul(2, args); } + expr * mk_sub(unsigned num_args, expr * const * args) { return m_manager.mk_app(m_fid, m_SUB, num_args, args); } + expr * mk_uminus(expr * arg) { return m_manager.mk_app(m_fid, m_UMINUS, arg); } + + void process_monomial(unsigned num_args, expr * const * args, numeral & k, ptr_buffer & result); + void mk_monomial(unsigned num_args, expr * * args, expr_ref & result); + bool eq_monomials_modulo_k(expr * n1, expr * n2); + bool merge_monomials(bool inv, expr * n1, expr * n2, expr_ref & result); + template + void add_monomial_core(expr * n, expr_ref_vector & result); + void add_monomial(bool inv, expr * n, expr_ref_vector & result); + template + void process_sum_of_monomials_core(expr * n, expr_ref_vector & result); + void process_sum_of_monomials(bool inv, expr * n, expr_ref_vector & result); + void process_sum_of_monomials(bool inv, expr * n, expr_ref_vector & result, numeral & k); + void mk_sum_of_monomials(expr_ref_vector & monomials, expr_ref & result); + template + void mk_add_core_core(unsigned num_args, expr * const * args, expr_ref & result); + void mk_add_core(bool inv, unsigned num_args, expr * const * args, expr_ref & result); + void append_to_monomial(expr * n, numeral & k, ptr_buffer & result); + expr * mk_mul(numeral const & c, expr * body); + void mk_sum_of_monomials_core(unsigned sz, expr ** ms, expr_ref & result); + bool is_simple_sum_of_monomials(expr_ref_vector & monomials); + bool is_simple_monomial(expr * m, expr * & x); + +public: + poly_simplifier_plugin(symbol const & fname, ast_manager & m, decl_kind add, decl_kind mul, decl_kind uminus, decl_kind sub, decl_kind num); + virtual ~poly_simplifier_plugin() {} + + /** + \brief Return true if the given expression is a numeral, and store its value in \c val. + */ + virtual bool is_numeral(expr * n, numeral & val) const = 0; + bool is_numeral(expr * n) const { return is_app_of(n, m_fid, m_NUM); } + bool is_zero(expr * n) const { + SASSERT(m_curr_sort_zero != 0); + SASSERT(m_manager.get_sort(n) == m_manager.get_sort(m_curr_sort_zero)); + return n == m_curr_sort_zero; + } + bool is_zero_safe(expr * n) { + set_curr_sort(m_manager.get_sort(n)); + return is_zero(n); + } + virtual bool is_minus_one(expr * n) const = 0; + virtual expr * get_zero(sort * s) const = 0; + + + /** + \brief Return true if n is of the form (* -1 r) + */ + bool is_times_minus_one(expr * n, expr * & r) const { + if (is_mul(n) && to_app(n)->get_num_args() == 2 && is_minus_one(to_app(n)->get_arg(0))) { + r = to_app(n)->get_arg(1); + return true; + } + return false; + } + + /** + \brief Return true if n is of the form: a <= b or a >= b. + */ + virtual bool is_le_ge(expr * n) const = 0; + + /** + \brief Return a constant representing the giving numeral and sort m_curr_sort. + */ + virtual app * mk_numeral(numeral const & n) = 0; + app * mk_zero() { return mk_numeral(numeral::zero()); } + app * mk_one() { return mk_numeral(numeral::one()); } + app * mk_minus_one() { return mk_numeral(numeral::minus_one()); } + + /** + \brief Normalize the given numeral with respect to m_curr_sort + */ + virtual numeral norm(numeral const & n) = 0; + + void set_curr_sort(sort * s) { + if (s != m_curr_sort) { + // avoid virtual function call + m_curr_sort = s; + m_curr_sort_zero = get_zero(m_curr_sort); + } + } + void set_curr_sort(expr * n) { set_curr_sort(m_manager.get_sort(n)); } + + bool is_add(expr const * n) const { return is_app_of(n, m_fid, m_ADD); } + bool is_mul(expr const * n) const { return is_app_of(n, m_fid, m_MUL); } + void mk_add(unsigned num_args, expr * const * args, expr_ref & result); + void mk_add(expr * arg1, expr * arg2, expr_ref & result); + void mk_sub(unsigned num_args, expr * const * args, expr_ref & result); + void mk_sub(expr * arg1, expr * arg2, expr_ref & result); + void mk_uminus(expr * arg, expr_ref & result); + void mk_mul(unsigned num_args, expr * const * args, expr_ref & result); + void mk_mul(expr * arg1, expr * arg2, expr_ref & result); + + virtual bool reduce_distinct(unsigned num_args, expr * const * args, expr_ref & result); + + virtual bool reduce(func_decl * f, unsigned num_args, rational const * mults, expr * const * args, expr_ref & result); + + expr * get_monomial_body(expr * m); + int get_monomial_body_order(expr * m); + void get_monomial_coeff(expr * m, numeral & result); + void inv_monomial(expr * n, expr_ref & result); + + bool is_var_plus_ground(expr * n, bool & inv, var * & v, expr_ref & t); + +#ifdef Z3DEBUG + bool wf_monomial(expr * m) const; + bool wf_polynomial(expr * m) const; +#endif +}; + +#endif /* _POLY_SIMPLIFIER_PLUGIN_H_ */ diff --git a/lib/polynomial.cpp b/lib/polynomial.cpp new file mode 100644 index 000000000..1b5c7965f --- /dev/null +++ b/lib/polynomial.cpp @@ -0,0 +1,7386 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + polynomial.cpp + +Abstract: + + Goodies for creating and handling polynomials. + +Author: + + Leonardo (leonardo) 2011-11-15 + +Notes: + +--*/ +#include"polynomial.h" +#include"vector.h" +#include"chashtable.h" +#include"small_object_allocator.h" +#include"id_gen.h" +#include"buffer.h" +#include"scoped_ptr_vector.h" +#include"cooperate.h" +#include"upolynomial_factorization.h" +#include"polynomial_factorization.h" +#include"polynomial_primes.h" +#include"permutation.h" +#include"algebraic_numbers.h" +#include"mpzzp.h" +#include"timeit.h" +#include"linear_eq_solver.h" +#include"scoped_numeral_buffer.h" +#include"ref_buffer.h" + +namespace polynomial { + + factor_params::factor_params(): + m_max_p(UINT_MAX), + m_p_trials(1), + m_max_search_size(UINT_MAX) { + } + + factor_params::factor_params(unsigned max_p, unsigned p_trials, unsigned max_search_size): + m_max_p(max_p), + m_p_trials(p_trials), + m_max_search_size(max_search_size) { + } + + void factor_params::updt_params(params_ref const & p) { + m_max_p = p.get_uint(":factor-max-prime", UINT_MAX); + m_p_trials = p.get_uint(":factor-num-primes", 1); + m_max_search_size = p.get_uint(":factor-max-search-size", UINT_MAX); + } + + void factor_params::get_param_descrs(param_descrs & r) { + r.insert(":factor-max-search-size", CPK_UINT, "(default: infty) 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."); + r.insert(":factor-max-prime", CPK_UINT, "(default: infty) 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."); + r.insert(":factor-num-primes", CPK_UINT, "(default: 1) 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."); + } + + typedef ptr_vector monomial_vector; + + void var2degree::display(std::ostream & out) const { + bool first = true; + out << "["; + for (unsigned i = 0; i < m_var2degree.size(); ++ i) { + if (m_var2degree.size() > 0) { + if (!first) { + out << ","; + } + out << "x" << i << "^" << m_var2degree[i]; + if (first) { + first = false; + } + } + } + out << "]"; + } + + // ----------------------------------- + // + // Monomials + // + // ----------------------------------- + + /** + \brief power: var + exponent + */ + class power : public std::pair { + public: + power():std::pair() {} + power(var v, unsigned d):std::pair(v, d) {} + var get_var() const { return first; } + unsigned degree() const { return second; } + unsigned & degree() { return second; } + void set_var(var x) { first = x; } + + struct lt_var { + bool operator()(power const & p1, power const & p2) { + SASSERT(p1.get_var() != p2.get_var()); + return p1.get_var() < p2.get_var(); + } + }; + + struct lt_degree { + bool operator()(power const & p1, power const & p2) { + return p1.degree() < p2.degree(); + } + }; + }; + + + std::ostream & operator<<(std::ostream & out, power const & p) { + out << "x" << p.get_var(); + if (p.degree() != 1) + out << "^" << p.degree(); + return out; + } + + /** + \brief Return true if the variables in pws are sorted in increasing order and are distinct. + */ + bool is_valid_power_product(unsigned sz, power const * pws) { + for (unsigned i = 1; i < sz; i++) { + if (pws[i-1].get_var() >= pws[i].get_var()) + return false; + } + return true; + } + + /** + \brief Return total degree of the given power product. + */ + unsigned power_product_total_degree(unsigned sz, power const * pws) { + unsigned r = 0; + for (unsigned i = 0; i < sz; i++) + r += pws[i].degree(); + return r; + } + + + /** + \brief Monomials (power products) + */ + class monomial { + unsigned m_ref_count; + unsigned m_id; //!< unique id + unsigned m_total_degree; //!< total degree of the monomial + unsigned m_size; //!< number of powers + unsigned m_hash; + power m_powers[0]; + friend class tmp_monomial; + + void sort() { + std::sort(m_powers, m_powers + m_size, power::lt_var()); + } + public: + static unsigned hash_core(unsigned sz, power const * pws) { + return string_hash(reinterpret_cast(const_cast(pws)), sz*sizeof(power), 11); + } + + struct hash_proc { + unsigned operator()(monomial const * m) const { + return m->m_hash; + } + }; + + struct eq_proc { + bool operator()(monomial const * m1, monomial const * m2) const { + if (m1->size() != m2->size() || m1->hash() != m2->hash()) + return false; + // m_total_degree must not be used as a filter, because it is not updated in temporary monomials. + for (unsigned i = 0; i < m1->m_size; i++) { + if (m1->get_power(i) != m2->get_power(i)) + return false; + } + + return true; + } + }; + + static unsigned get_obj_size(unsigned sz) { return sizeof(monomial) + sz * sizeof(power); } + + monomial(unsigned id, unsigned sz, power const * pws, unsigned h): + m_ref_count(0), + m_id(id), + m_total_degree(0), + m_size(sz), + m_hash(h) { + for (unsigned i = 0; i < sz; i ++) { + power const & pw = pws[i]; + m_powers[i] = pw; + SASSERT(i == 0 || get_var(i) > get_var(i-1)); + SASSERT(degree(i) > 0); + m_total_degree += degree(i); + } + } + + unsigned hash() const { return m_hash; } + + unsigned ref_count() const { return m_ref_count; } + + void inc_ref() { m_ref_count++; } + + void dec_ref() { SASSERT(m_ref_count > 0); m_ref_count--; } + + bool is_valid() const { + return is_valid_power_product(m_size, m_powers) && power_product_total_degree(m_size, m_powers) == m_total_degree; + } + + unsigned id() const { return m_id; } + + unsigned size() const { return m_size; } + + unsigned total_degree() const { return m_total_degree; } + + power const & get_power(unsigned idx) const { SASSERT(idx < size()); return m_powers[idx]; } + + power const * get_powers() const { return m_powers; } + + var get_var(unsigned idx) const { return get_power(idx).get_var(); } + + unsigned degree(unsigned idx) const { return get_power(idx).degree(); } + + var max_var() const { + if (m_size == 0) + return null_var; + return get_var(m_size - 1); + } + + unsigned max_var_degree() const { + if (m_size == 0) + return 0; + return degree(m_size - 1); + } + +#define SMALL_MONOMIAL 8 + + unsigned index_of(var x) const { + if (m_size == 0) + return UINT_MAX; + unsigned last = m_size - 1; + if (get_var(last) == x) + return last; + if (m_size < SMALL_MONOMIAL) { + // use linear search for small monomials + // search backwards since we usually ask for the degree of "big" variables + unsigned i = last; + while (i > 0) { + --i; + if (get_var(i) == x) + return i; + } + return UINT_MAX; + } + else { + // use binary search for big monomials + int low = 0; + int high = last; + while (true) { + int mid = low + ((high - low)/2); + var x_mid = get_var(mid); + if (x > x_mid) { + low = mid + 1; + } + else if (x < x_mid) { + high = mid - 1; + } + else { + return mid; + } + if (low > high) + return UINT_MAX; + } + } + } + + unsigned degree_of(var x) const { + unsigned pos = index_of(x); + if (pos == UINT_MAX) + return 0; + return degree(pos); + } + + + // Given the subset S of variables that are smaller than x, + // then return the maximal one. + var max_smaller_than_core(var x) const { + if (m_size == 0) + return null_var; + if (m_size < SMALL_MONOMIAL) { + // use linear search for small monomials + // search backwards since we usually ask for the degree of "big" variables + unsigned i = m_size; + while (i > 0) { + --i; + if (get_var(i) < x) + return get_var(i); + } + return null_var; + } + else { + // use binary search for big monomials + int low = 0; + int high = m_size-1; + if (x <= get_var(low)) { + return null_var; + } + if (x > get_var(high)) { + return get_var(high); + } + if (x == get_var(high)) { + SASSERT(high > 0); + return get_var(high-1); + } + while (true) { + SASSERT(0 <= low && high < static_cast(m_size)); + SASSERT(get_var(low) < x); + SASSERT(x < get_var(high)); + SASSERT(low < high); + if (high == low + 1) { + SASSERT(get_var(low) < x); + SASSERT(x < get_var(low+1)); + return get_var(low); + } + SASSERT(high > low + 1); + int mid = low + ((high - low)/2); + SASSERT(low < mid && mid < high); + var x_mid = get_var(mid); + if (x_mid == x) { + SASSERT(low < mid && mid < high && high < static_cast(m_size)); + SASSERT(get_var(mid-1) < x && x == get_var(mid)); + return get_var(mid-1); + } + if (x < x_mid) { + high = mid; + } + else { + SASSERT(x_mid < x); + low = mid; + } + } + } + } + + var max_smaller_than(var x) const { + SASSERT(x != null_var); + var y = max_smaller_than_core(x); + DEBUG_CODE({ + bool found = false; + for (unsigned i = 0; i < m_size; i++) { + if (get_var(i) < x) { + CTRACE("poly_bug", !(y != null_var && get_var(i) <= y), + tout << "m: "; display(tout); tout << "\n"; + tout << "x: " << x << "\n"; + tout << "y: " << y << "\n"; + tout << "i: " << i << "\n"; + tout << "get_var(i): " << get_var(i) << "\n";); + SASSERT(y != null_var && get_var(i) <= y); + } + if (get_var(i) == y) + found = true; + } + SASSERT(y == null_var || (y < x && found)); + }); + return y; + } + + void display(std::ostream & out, display_var_proc const & proc = display_var_proc(), bool use_star = false) const { + if (m_size == 0) { + out << "1"; + return; + } + for (unsigned i = 0; i < m_size; i++) { + if (i > 0) { + if (use_star) + out << "*"; + else + out << " "; + } + proc(out, get_var(i)); + if (degree(i) > 1) + out << "^" << degree(i); + } + } + + void display_smt2(std::ostream & out, display_var_proc const & proc = display_var_proc()) const { + if (m_size == 0) { + out << "1"; + } + else if (m_size == 1 && degree(0) == 1) { + proc(out, get_var(0)); + } + else { + out << "(*"; + for (unsigned i = 0; i < m_size; i++) { + var x = get_var(i); + unsigned k = degree(i); + SASSERT(k > 0); + for (unsigned j = 0; j < k; j++) { + out << " "; + proc(out, x); + } + } + } + out << ")"; + } + + bool is_unit() const { return m_size == 0; } + + /** + \brief Return true if the degree of every variable is even. + */ + bool is_power_of_two() const { + for (unsigned i = 0; i < m_size; i++) { + if (degree(i) % 2 == 1) + return false; + } + return true; + } + + bool is_square() const { + for (unsigned i = 0; i < m_size; i++) { + if (degree(i) % 2 != 0) + return false; + } + return true; + } + + void rename(unsigned sz, var const * xs) { + for (unsigned i = 0; i < m_size; i++) { + power & pw = m_powers[i]; + pw.set_var(xs[pw.get_var()]); + } + sort(); + m_hash = hash_core(m_size, m_powers); + } + }; + + inline void swap(monomial * & m1, monomial * & m2) { std::swap(m1, m2); } + + typedef chashtable monomial_table; + + /** + \brief Mapping from monomials to positions. + */ + class monomial2pos { + unsigned_vector m_m2pos; + public: + unsigned get(monomial const * m) { + unsigned id = m->id(); + m_m2pos.reserve(id+1, UINT_MAX); + return m_m2pos[id]; + } + + void reset(monomial const * m) { + unsigned id = m->id(); + SASSERT(id < m_m2pos.size()); + m_m2pos[id] = UINT_MAX; + } + + void set(monomial const * m, unsigned pos) { + unsigned id = m->id(); + m_m2pos.reserve(id+1, UINT_MAX); + SASSERT(m_m2pos[id] == UINT_MAX); + m_m2pos[id] = pos; + } + + /** + \brief Save the position of the monomials in p. + */ + template + void set(Poly const * p) { + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + set(p->m(i), i); + } + } + + /** + \brief Undo the effects of save_pos. + */ + template + void reset(Poly const * p) { + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + reset(p->m(i)); + } + } + }; + + +#define TMP_INITIAL_CAPACITY 128 + + /** + \brief Wrapper for temporary monomials. + */ + class tmp_monomial { + monomial * m_ptr; + unsigned m_capacity; //!< maximum number of arguments supported by m_ptr; + + monomial * allocate(unsigned capacity) { + void * mem = memory::allocate(monomial::get_obj_size(capacity)); + return new (mem) monomial(UINT_MAX, 0, 0, 0); + } + + void deallocate(monomial * ptr, unsigned capacity) { + memory::deallocate(ptr); + } + + void increase_capacity(unsigned new_capacity) { + SASSERT(new_capacity > m_capacity); + deallocate(m_ptr, m_capacity); + m_ptr = allocate(new_capacity); + m_capacity = new_capacity; + } + + void expand_capacity(unsigned new_capacity) { + SASSERT(new_capacity > m_capacity); + monomial * new_ptr = allocate(new_capacity); + new_ptr->m_size = m_ptr->m_size; + memcpy(new_ptr->m_powers, m_ptr->m_powers, sizeof(power)*m_ptr->m_size); + deallocate(m_ptr, m_capacity); + m_ptr = new_ptr; + m_capacity = new_capacity; + } + public: + tmp_monomial(): + m_ptr(allocate(TMP_INITIAL_CAPACITY)), + m_capacity(TMP_INITIAL_CAPACITY) { + } + + ~tmp_monomial() { + deallocate(m_ptr, m_capacity); + } + + void init(unsigned sz, power const * pws) { + if (sz > m_capacity) + increase_capacity(sz * 2); + SASSERT(sz < m_capacity); + m_ptr->m_size = sz; + memcpy(m_ptr->m_powers, pws, sizeof(power) * sz); + } + + void reset() { + m_ptr->m_size = 0; + } + + unsigned size() const { + return m_ptr->m_size; + } + + void push_back(power const & pw) { + if (m_ptr->m_size >= m_capacity) + expand_capacity(m_ptr->m_size * 2); + m_ptr->m_powers[m_ptr->m_size] = pw; + m_ptr->m_size++; + } + + monomial * get_ptr() { + unsigned sz = m_ptr->m_size; + m_ptr->m_hash = monomial::hash_core(sz, m_ptr->m_powers); + return m_ptr; + } + + void reserve(unsigned capacity) { + if (capacity > m_capacity) + increase_capacity(capacity * 2); + } + + void set_size(unsigned sz) { + SASSERT(sz <= m_capacity); + m_ptr->m_size = sz; + } + + void set_power(unsigned idx, power const & pw) { + SASSERT(idx < m_capacity); + m_ptr->m_powers[idx] = pw; + } + + power const & get_power(unsigned idx) const { return m_ptr->m_powers[idx]; } + + power const * get_powers() const { return m_ptr->m_powers; } + }; + + /** + \brief Compare m1 and m2 using a lexicographical order + + Return + - -1 if m1 <_lex m2, + - 0 if m1 = m2, + - 1 if m1 >_lex m2 + + The biggest variable dominates + x3^3 > x3^2 x1^2 > x3 x2^2 x_1 > x1^3 + + Remark: in out representation the biggest variable is in the last position. + */ + int lex_compare(monomial const * m1, monomial const * m2) { + if (m1 == m2) + return 0; + int sz1 = m1->size(); + int sz2 = m2->size(); + int idx1 = sz1 - 1; + int idx2 = sz2 - 1; + while (idx1 >= 0 && idx2 >= 0) { + power const & pw1 = m1->get_power(idx1); + power const & pw2 = m2->get_power(idx2); + if (pw1.get_var() == pw2.get_var()) { + if (pw1.degree() == pw2.degree()) { + idx1--; + idx2--; + continue; + } + return pw1.degree() < pw2.degree() ? -1 : 1; + } + return pw1.get_var() > pw2.get_var() ? 1 : -1; + } + SASSERT(idx1 >= 0 || idx2 >= 0); + SASSERT(idx1 < 0 || idx2 < 0); + return idx1 < 0 ? -1 : 1; + } + + /** + Similar to lex_compare, but min_var is assumed to be the minimal variable. + */ + int lex_compare2(monomial const * m1, monomial const * m2, var min_var) { + if (m1 == m2) + return 0; + int sz1 = m1->size(); + int sz2 = m2->size(); + int idx1 = sz1 - 1; + int idx2 = sz2 - 1; + unsigned min_var_degree1 = 0; + unsigned min_var_degree2 = 0; + while (idx1 >= 0 && idx2 >= 0) { + power const & pw1 = m1->get_power(idx1); + power const & pw2 = m2->get_power(idx2); + if (pw1.get_var() == min_var) { + min_var_degree1 = pw1.degree(); + idx1--; + if (pw2.get_var() == min_var) { + min_var_degree2 = pw2.degree(); + idx2--; + } + continue; + } + if (pw2.get_var() == min_var) { + min_var_degree2 = pw2.degree(); + idx2--; + continue; + } + if (pw1.get_var() == pw2.get_var()) { + if (pw1.degree() == pw2.degree()) { + idx1--; + idx2--; + continue; + } + return pw1.degree() < pw2.degree() ? -1 : 1; + } + return pw1.get_var() > pw2.get_var() ? 1 : -1; + } + if (idx1 == idx2) { + SASSERT(min_var_degree1 != min_var_degree2); + return min_var_degree1 < min_var_degree2 ? -1 : 1; + } + return idx1 < 0 ? -1 : 1; + } + + struct lex_lt2 { + var m_min; + lex_lt2(var m):m_min(m) {} + bool operator()(monomial * m1, monomial * m2) const { + TRACE("lex_bug", tout << "min: x" << m_min << "\n"; m1->display(tout); tout << "\n"; m2->display(tout); tout << "\n";); + return lex_compare2(m1, m2, m_min) < 0; + } + }; + + /** + \brief Compare m1 and m2 using a graded lexicographical order + + \see lex_compare + */ + int graded_lex_compare(monomial const * m1, monomial const * m2) { + unsigned t1 = m1->total_degree(); + unsigned t2 = m2->total_degree(); + if (t1 == t2) + return lex_compare(m1, m2); + else + return t1 < t2 ? -1 : 1; + } + + /** + \brief Compare submonomials m1[start1, end1) and m2[start2, end2) using reverse lexicographical order + */ + int rev_lex_compare(monomial const * m1, unsigned start1, unsigned end1, monomial const * m2, unsigned start2, unsigned end2) { + SASSERT(end1 >= start1); + SASSERT(end2 >= start2); + unsigned idx1 = end1; + unsigned idx2 = end2; + while(idx1 > start1 && idx2 > start2) { + --idx1; + --idx2; + power const & pw1 = m1->get_power(idx1); + power const & pw2 = m2->get_power(idx2); + if (pw1.get_var() == pw2.get_var()) { + if (pw1.degree() == pw2.degree()) { + // Remark: the submonomials have the same total degree, but they are not equal. So, idx1 > 0 and idx2 > 0. + SASSERT(idx1 > start1 && idx2 > start2); + continue; + } + return pw1.degree() > pw2.degree() ? -1 : 1; + } + return pw1.get_var() > pw2.get_var() ? -1 : 1; + } + SASSERT(idx1 == start1 || idx2 == start2); + if (idx1 == start1) + return idx2 == start2 ? 0 : -1; + SASSERT(idx2 == start2 && idx1 != start1); + return 1; + } + + /** + \brief Compare m1 and m2 using reverse lexicographical order. + + \see lex_compare + */ + int rev_lex_compare(monomial const * m1, monomial const * m2) { + if (m1 == m2) + return 0; + return rev_lex_compare(m1, 0, m1->size(), m2, 0, m2->size()); + } + + /** + \brief Compare m1 and m2 using graded reverse lexicographical order. + + \see lex_compare + */ + int graded_rev_lex_compare(monomial const * m1, monomial const * m2) { + unsigned t1 = m1->total_degree(); + unsigned t2 = m2->total_degree(); + if (t1 == t2) + return rev_lex_compare(m1, m2); + else + return t1 < t2 ? -1 : 1; + } + + struct graded_lex_gt { + bool operator()(monomial const * m1, monomial const * m2) { return graded_lex_compare(m1, m2) < 0; } + }; + + /** + \brief + */ + class monomial_manager { + unsigned m_ref_count; + small_object_allocator * m_allocator; + bool m_own_allocator; + monomial_table m_monomials; + id_gen m_mid_gen; // id generator for monomials + unsigned m_next_var; + monomial * m_unit; + tmp_monomial m_mk_tmp; + tmp_monomial m_tmp1; + tmp_monomial m_tmp2; + tmp_monomial m_tmp3; + svector m_powers_tmp; + public: + monomial_manager(small_object_allocator * a = 0) { + m_ref_count = 0; + m_next_var = 0; + if (a == 0) { + m_allocator = alloc(small_object_allocator, "polynomial"); + m_own_allocator = true; + } + else { + m_allocator = a; + m_own_allocator = false; + } + m_unit = mk_monomial(0, static_cast(0)); + inc_ref(m_unit); + } + + ~monomial_manager() { + dec_ref(m_unit); + CTRACE("polynomial", !m_monomials.empty(), + tout << "monomials leaked\n"; + monomial_table::iterator it = m_monomials.begin(); monomial_table::iterator end = m_monomials.end(); + for (; it != end; ++it) { + (*it)->display(tout); tout << "\n"; + }); + SASSERT(m_monomials.empty()); + if (m_own_allocator) + dealloc(m_allocator); + } + + void inc_ref() { + m_ref_count++; + } + + void dec_ref() { + SASSERT(m_ref_count > 0); + m_ref_count--; + if (m_ref_count == 0) + dealloc(this); + } + + small_object_allocator & allocator() { return *m_allocator; } + + var mk_var() { + var r = m_next_var; + m_next_var++; + return r; + } + + unsigned num_vars() const { + return m_next_var; + } + + bool is_valid(var x) const { + return x < m_next_var; + } + + void del(monomial * m) { + unsigned obj_sz = monomial::get_obj_size(m->size()); + m_monomials.erase(m); + m_mid_gen.recycle(m->id()); + m_allocator->deallocate(obj_sz, m); + } + + void inc_ref(monomial * m) { + m->inc_ref(); + } + + void dec_ref(monomial * m) { + m->dec_ref(); + if (m->ref_count() == 0) + del(m); + } + + monomial * mk_unit() { return m_unit; } + + monomial * mk_monomial(tmp_monomial & tmp) { + monomial * tmp_ptr = tmp.get_ptr(); + monomial * & m = m_monomials.insert_if_not_there(tmp_ptr); + if (m != tmp_ptr) + return m; + void * mem = m_allocator->allocate(monomial::get_obj_size(tmp_ptr->size())); + unsigned id = m_mid_gen.mk(); + monomial * r = new (mem) monomial(id, tmp_ptr->size(), tmp_ptr->get_powers(), tmp_ptr->hash()); + m = r; + SASSERT(m_monomials.contains(r)); + SASSERT(*(m_monomials.find_core(r)) == r); + return r; + } + + monomial * mk_monomial(unsigned sz, power const * pws) { + SASSERT(is_valid_power_product(sz, pws)); + m_mk_tmp.init(sz, pws); + return mk_monomial(m_mk_tmp); + } + + monomial * convert(monomial const * src) { + unsigned sz = src->size(); + for (unsigned i = 0; i < sz; i++) { + var x = src->get_var(i); + while (x >= num_vars()) { + mk_var(); + } + SASSERT(x < num_vars()); + } + return mk_monomial(src->size(), src->get_powers()); + } + + monomial * mk_monomial(var x) { + SASSERT(is_valid(x)); + power pw(x, 1); + return mk_monomial(1, &pw); + } + + monomial * mk_monomial(var x, unsigned k) { + if (k == 0) + return m_unit; + SASSERT(is_valid(x)); + power pw(x, k); + return mk_monomial(1, &pw); + } + + monomial * mk_monomial(unsigned sz, var * xs) { + if (sz == 0) + return m_unit; + if (sz == 1) + return mk_monomial(xs[0]); + m_powers_tmp.reset(); + std::sort(xs, xs+sz); + SASSERT(is_valid(xs[0])); + m_powers_tmp.push_back(power(xs[0], 1)); + for (unsigned i = 1; i < sz; i++) { + var x = xs[i]; + SASSERT(is_valid(x)); + power & last = m_powers_tmp.back(); + if (x == last.get_var()) + last.degree()++; + else + m_powers_tmp.push_back(power(x, 1)); + } + return mk_monomial(m_powers_tmp.size(), m_powers_tmp.c_ptr()); + } + + monomial * mul(unsigned sz1, power const * pws1, unsigned sz2, power const * pws2) { + SASSERT(is_valid_power_product(sz1, pws1)); + SASSERT(is_valid_power_product(sz2, pws2)); + tmp_monomial & product_tmp = m_tmp1; + product_tmp.reserve(sz1 + sz2); // product has at most sz1 + sz2 powers + unsigned i1 = 0, i2 = 0; + unsigned j = 0; + while (true) { + if (i1 == sz1) { + // copy 2 + for (; i2 < sz2; i2++, j++) + product_tmp.set_power(j, pws2[i2]); + break; + } + if (i2 == sz2) { + // copy 1 + for (; i1 < sz1; i1++, j++) + product_tmp.set_power(j, pws1[i1]); + break; + } + power const & pw1 = pws1[i1]; + power const & pw2 = pws2[i2]; + unsigned v1 = pw1.get_var(); + unsigned v2 = pw2.get_var(); + if (v1 == v2) { + product_tmp.set_power(j, power(v1, pw1.degree() + pw2.degree())); + i1++; + i2++; + } + else if (v1 < v2) { + product_tmp.set_power(j, pw1); + i1++; + } + else { + SASSERT(v1 > v2); + product_tmp.set_power(j, pw2); + i2++; + } + j++; + } + product_tmp.set_size(j); + TRACE("monomial_mul_bug", + tout << "before mk_monomial\n"; + tout << "pws1: "; for (unsigned i = 0; i < sz1; i++) tout << pws1[i] << " "; tout << "\n"; + tout << "pws2: "; for (unsigned i = 0; i < sz2; i++) tout << pws2[i] << " "; tout << "\n"; + tout << "product_tmp: "; for (unsigned i = 0; i < product_tmp.size(); i++) tout << product_tmp.get_power(i) << " "; + tout << "\n";); + monomial * r = mk_monomial(product_tmp); + TRACE("monomial_mul_bug", + tout << "j: " << j << "\n"; + tout << "r: "; r->display(tout); tout << "\n"; + tout << "pws1: "; for (unsigned i = 0; i < sz1; i++) tout << pws1[i] << " "; tout << "\n"; + tout << "pws2: "; for (unsigned i = 0; i < sz2; i++) tout << pws2[i] << " "; tout << "\n"; + tout << "product_tmp: "; for (unsigned i = 0; i < product_tmp.size(); i++) tout << product_tmp.get_power(i) << " "; + tout << "\n";); + SASSERT(r->is_valid()); + SASSERT(r->total_degree() == power_product_total_degree(sz1, pws1) + power_product_total_degree(sz2, pws2)); + return r; + } + + monomial * mul(monomial const * m1, monomial const * m2) { + if (m1 == m_unit) + return const_cast(m2); + if (m2 == m_unit) + return const_cast(m1); + return mul(m1->size(), m1->get_powers(), m2->size(), m2->get_powers()); + } + + + template + bool div_core(unsigned sz1, power const * pws1, unsigned sz2, power const * pws2, tmp_monomial & r) { + if (STORE_RESULT) + r.reserve(sz1); // r has at most sz1 arguments. + unsigned i1 = 0; + unsigned i2 = 0; + unsigned j = 0; + if (sz1 < sz2) + return false; // pws2 does not divide pws1 + while (true) { + if (i2 == sz2) { + if (STORE_RESULT) { + for (; i1 < sz1; i1++, j++) + r.set_power(j, pws1[i1]); + r.set_size(j); + } + return true; + } + if (i1 == sz1) + return false; // pws2 does not divide pws1 + power const & pw1 = pws1[i1]; + power const & pw2 = pws2[i2]; + unsigned v1 = pw1.get_var(); + unsigned v2 = pw2.get_var(); + if (v1 == v2) { + unsigned d1 = pw1.degree(); + unsigned d2 = pw2.degree(); + if (d1 < d2) + return false; // pws2 does not divide pws1 + if (STORE_RESULT) { + if (d1 > d2) { + r.set_power(j, power(v1, d1 - d2)); + j++; + } + } + i1++; + i2++; + } + else if (v1 < v2) { + if (STORE_RESULT) { + r.set_power(j, pw1); + j++; + } + i1++; + } + else { + SASSERT(v1 > v2); + return false; // pws2 does not divide pws1 + } + } + } + + bool div(monomial const * m1, monomial const * m2) { + if (m1->total_degree() < m2->total_degree()) + return false; + if (m1 == m2) + return true; + return div_core(m1->size(), m1->get_powers(), m2->size(), m2->get_powers(), m_tmp1); + } + + bool div(monomial const * m1, monomial const * m2, monomial * & r) { + if (m1->total_degree() < m2->total_degree()) + return false; + if (m1 == m2) { + r = m_unit; + return true; + } + tmp_monomial & div_tmp = m_tmp1; + if (div_core(m1->size(), m1->get_powers(), m2->size(), m2->get_powers(), div_tmp)) { + r = mk_monomial(div_tmp); + return true; + } + return false; + } + + /** + \brief Compute the gcd of pws1 and pws2, store it in g, and pws1/g in r1, and pws2/g in r2 + Return true if the gcd is not 1. If the result is false, then g, r1 and r2 should not be used. + */ + bool gcd_core(unsigned sz1, power const * pws1, unsigned sz2, power const * pws2, tmp_monomial & g, tmp_monomial & r1, tmp_monomial & r2) { + g.reserve(std::min(sz1, sz2)); + r1.reserve(sz2); // r1 has at most num_args2 arguments + r2.reserve(sz1); // r2 has at most num_args1 arguments + bool found = false; + unsigned i1 = 0; + unsigned i2 = 0; + unsigned j1 = 0; + unsigned j2 = 0; + unsigned j3 = 0; + while (true) { + if (i1 == sz1) { + if (found) { + for (; i2 < sz2; i2++, j2++) + r2.set_power(j2, pws2[i2]); + r1.set_size(j1); + r2.set_size(j2); + g.set_size(j3); + return true; + } + return false; + } + if (i2 == sz2) { + if (found) { + for (; i1 < sz1; i1++, j1++) + r1.set_power(j1, pws1[i1]); + r1.set_size(j1); + r2.set_size(j2); + g.set_size(j3); + return true; + } + return false; + } + power const & pw1 = pws1[i1]; + power const & pw2 = pws2[i2]; + unsigned v1 = pw1.get_var(); + unsigned v2 = pw2.get_var(); + if (v1 == v2) { + found = true; + unsigned d1 = pw1.degree(); + unsigned d2 = pw2.degree(); + if (d1 > d2) { + r1.set_power(j1, power(v1, d1 - d2)); + g.set_power(j3, pw2); + j1++; + j3++; + } + else if (d2 > d1) { + r2.set_power(j2, power(v2, d2 - d1)); + g.set_power(j3, pw1); + j2++; + j3++; + } + else { + SASSERT(d1 == d2); + g.set_power(j3, pw1); + j3++; + } + i1++; + i2++; + } + else if (v1 < v2) { + r1.set_power(j1, pw1); + j1++; + i1++; + } + else { + SASSERT(v1 > v2); + r2.set_power(j2, pw2); + j2++; + i2++; + } + } + } + + monomial * gcd(monomial const * m1, monomial const * m2, monomial * & q1, monomial * & q2) { + if (gcd_core(m1->size(), m1->get_powers(), m2->size(), m2->get_powers(), m_tmp1, m_tmp2, m_tmp3)) { + q1 = mk_monomial(m_tmp2); + q2 = mk_monomial(m_tmp3); + return mk_monomial(m_tmp1); + } + else { + // gcd is one + q1 = const_cast(m2); + q2 = const_cast(m1); + return m_unit; + } + } + + bool unify(monomial const * m1, monomial const * m2, monomial * & q1, monomial * & q2) { + if (gcd_core(m1->size(), m1->get_powers(), m2->size(), m2->get_powers(), m_tmp1, m_tmp2, m_tmp3)) { + q1 = mk_monomial(m_tmp2); + q2 = mk_monomial(m_tmp3); + return true; + } + return false; + } + + monomial * pw(monomial const * m, unsigned k) { + if (k == 0) + return m_unit; + if (k == 1) + return const_cast(m); + unsigned sz = m->size(); + tmp_monomial & pw_tmp = m_tmp1; + pw_tmp.reserve(sz); + for (unsigned i = 0; i < sz; i++) + pw_tmp.set_power(i, power(m->get_var(i), m->degree(i)*k)); + pw_tmp.set_size(sz); + return mk_monomial(pw_tmp); + } + + monomial * sqrt(monomial const * m) { + SASSERT(m_unit != 0); + if (m == m_unit) + return m_unit; + unsigned sz = m->size(); + tmp_monomial & sqrt_tmp = m_tmp1; + sqrt_tmp.reserve(sz); + for (unsigned i = 0; i < sz; i++) { + if (m->degree(i) % 2 == 1) + return 0; + sqrt_tmp.set_power(i, power(m->get_var(i), m->degree(i) / 2)); + } + sqrt_tmp.set_size(sz); + return mk_monomial(sqrt_tmp); + } + + /** + \brief Return m/x^k + */ + monomial * div_x_k(monomial const * m, var x, unsigned k) { + SASSERT(is_valid(x)); + unsigned sz = m->size(); + tmp_monomial & elim_tmp = m_tmp1; + elim_tmp.reserve(sz); + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + power const & pw = m->get_power(i); + var y = pw.get_var(); + if (x != y) { + elim_tmp.set_power(j, pw); + j++; + } + else { + SASSERT(k <= pw.degree()); + unsigned d = pw.degree(); + if (k < d) { + elim_tmp.set_power(j, power(y, d - k)); + j++; + } + } + } + elim_tmp.set_size(j); + return mk_monomial(elim_tmp); + } + + /** + \brief Return m/x^n where n == m->degree_of(x) + */ + monomial * div_x(monomial const * m, var x) { + SASSERT(is_valid(x)); + unsigned sz = m->size(); + tmp_monomial & elim_tmp = m_tmp1; + elim_tmp.reserve(sz); + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + power const & pw = m->get_power(i); + var y = pw.get_var(); + if (x != y) { + elim_tmp.set_power(j, pw); + j++; + } + } + elim_tmp.set_size(j); + return mk_monomial(elim_tmp); + } + + monomial * derivative(monomial const * m, var x) { + SASSERT(is_valid(x)); + unsigned sz = m->size(); + tmp_monomial & derivative_tmp = m_tmp1; + derivative_tmp.reserve(sz); + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + power const & pw = m->get_power(i); + var y = pw.get_var(); + if (x == y) { + unsigned d = pw.degree(); + if (d > 1) { + derivative_tmp.set_power(j, power(y, d-1)); + j++; + } + } + else { + derivative_tmp.set_power(j, pw); + j++; + } + } + derivative_tmp.set_size(j); + return mk_monomial(derivative_tmp); + } + + void rename(unsigned sz, var const * xs) { + SASSERT(m_ref_count <= 1); + SASSERT(sz == num_vars()); + DEBUG_CODE({ + // check whether xs is really a permutation + svector found; + found.resize(num_vars(), false); + for (unsigned i = 0; i < sz; i++) { + SASSERT(xs[i] < num_vars()); + SASSERT(!found[xs[i]]); + found[xs[i]] = true; + } + }); + monomial_table new_table; + monomial_table::iterator it = m_monomials.begin(); + monomial_table::iterator end = m_monomials.end(); + for (; it != end; ++it) { + monomial * m = *it; + m->rename(sz, xs); + SASSERT(!new_table.contains(m)); + new_table.insert(m); + } + m_monomials.swap(new_table); + } + + }; + + + /** + We maintain the following invariant: + The first monomial m of every non-zero polynomial p contains: + 1) the maximal variable x of p, + 2) and the degree of x in m is maximal in p. + */ + class polynomial { + public: + typedef manager::numeral numeral; + private: + unsigned m_ref_count; + unsigned m_id:31; + unsigned m_lex_sorted:1; + unsigned m_size; + numeral * m_as; + monomial ** m_ms; + + void lex_sort(unsigned start, unsigned end, var x, vector & buckets, unsigned_vector & p) { + SASSERT(end > start); + unsigned max_degree = 0; + for (unsigned i = start, j = 0; i < end; i++, j++) { + monomial * m = m_ms[i]; + unsigned d = m->degree_of(x); + buckets.reserve(d+1); + buckets[d].push_back(j); + if (d > max_degree) + max_degree = d; + } + p.reset(); + unsigned i = max_degree + 1; + while (i > 0) { + --i; + p.append(buckets[i]); + buckets[i].reset(); + } + SASSERT(p.size() == end - start); + apply_permutation(p.size(), m_as + start, p.c_ptr()); + apply_permutation_core(p.size(), m_ms + start, p.c_ptr()); // p is not needed anymore after this command + i = start; + while (i < end) { + monomial * m = m_ms[i]; + unsigned d = m->degree_of(x); + if (d == 0) { + // x does not occur in m + // since we sorted, x should not in the rest + // we should find the maximal variable variable smaller than x in [i, end) + var y = max_smaller_than(i, end, x); + if (y != null_var) + lex_sort(i, end, y, buckets, p); + return; + } + unsigned j = i + 1; + for (; j < end; j++) { + unsigned d_j = m_ms[j]->degree_of(x); + SASSERT(d_j <= d); // it is sorted + if (d_j < d) + break; + } + SASSERT(j == end || m_ms[j]->degree_of(x) < d); + // sort interval [i, j) using the maximal variable y smaller than x + if (j > i + 1) { + // only need to sort if the interval has more than one element. + var y = max_smaller_than(i, j, x); + if (y != null_var) + lex_sort(i, j, y, buckets, p); + } + i = j; + } + } + + public: + unsigned ref_count() const { return m_ref_count; } + void inc_ref() { m_ref_count++; } + void dec_ref() { SASSERT(m_ref_count > 0); m_ref_count--; } + + static unsigned get_obj_size(unsigned n) { return sizeof(polynomial) + n * (sizeof(numeral) + sizeof(monomial*)); } + + /** + \brief Partial order used to implement the polynomial invariant that guarantees + that the first monomial contains the maximal variable in the polynomial, and it + occurs with maximal degree. + + Return true if m1 > m2 in this partial order. + */ + static bool po_gt(monomial const * m1, monomial const * m2) { + if (m1->size() == 0) + return false; + if (m2->size() == 0) + return true; + if (m1->max_var() < m2->max_var()) + return false; + if (m1->max_var() > m2->max_var()) + return true; + SASSERT(m1->max_var() == m2->max_var()); + return m1->max_var_degree() > m2->max_var_degree(); + } + + // swap monomials at positions 0 and pos + void swap_0_pos(unsigned pos) { + if (pos != 0) { + swap(m_as[0], m_as[pos]); + std::swap(m_ms[0], m_ms[pos]); + } + } + + polynomial(mpzzp_manager & nm, unsigned id, unsigned sz, numeral * as, monomial * const * ms, numeral * as_mem, monomial ** ms_mem): + m_ref_count(0), + m_id(id), + m_lex_sorted(false), + m_size(sz), + m_as(as_mem), + m_ms(ms_mem) { + if (sz > 0) { + unsigned max_pos = 0; + for (unsigned i = 0; i < sz; i++) { + new (m_as + i) numeral(); // initialize the big number at m_as[i] + swap(m_as[i], as[i]); + SASSERT(ms[i]->ref_count() > 0); + m_ms[i] = ms[i]; + if (i > 0 && po_gt(m_ms[i], m_ms[max_pos])) + max_pos = i; + } + swap_0_pos(max_pos); + } + } + + // Return the maximal variable y occuring in [m_ms + start, m_ms + end) that is smaller than x + var max_smaller_than(unsigned start, unsigned end, var x) { + var max = null_var; + for (unsigned i = start; i < end; i++) { + var y = m_ms[i]->max_smaller_than(x); + if (y != null_var && (max == null_var || y > max)) + max = y; + } + return max; + } + + bool lex_sorted() const { + return m_lex_sorted; + } + + // Put monomials in lexicographical order + void lex_sort(vector & buckets, unsigned_vector & p, mpzzp_manager & nm) { + if (m_lex_sorted) + return; + if (size() <= 1) { + m_lex_sorted = true; + return; + } + lex_sort(0, size(), m(0)->max_var(), buckets, p); + m_lex_sorted = true; + DEBUG_CODE({ + for (unsigned i = 0; i < m_size - 1; i++) { + CTRACE("poly_bug", lex_compare(m_ms[i], m_ms[i+1]) <= 0, + tout << "i: " << i << "\npoly: "; display(tout, nm); tout << "\n";); + SASSERT(lex_compare(m_ms[i], m_ms[i+1]) > 0); + } + }); + } + + /** + \brief Make sure that the first monomial contains the maximal variable x occuring in the polynomial, + and x occurs with maximal degree. + */ + void make_first_maximal() { + if (m_size <= 1) + return; + unsigned max_pos = 0; + for (unsigned i = 1; i < m_size; i++) { + if (po_gt(m_ms[i], m_ms[max_pos])) + max_pos = i; + } + swap_0_pos(max_pos); + m_lex_sorted = false; + } + + /** + \brief Return the position of the maximal monomial with + respect to graded lexicographical order. Return UINT_MAX + if polynomial is zero. + */ + unsigned graded_lex_max_pos() const { + if (m_size == 0) + return UINT_MAX; + unsigned max_pos = 0; + for (unsigned i = 1; i < m_size; i++) { + if (graded_lex_compare(m_ms[i], m_ms[max_pos]) > 0) + max_pos = i; + } + return max_pos; + } + + /** + \brief Return the position of the minimal monomial with + respect to graded lexicographical order. Return UINT_MAX + if polynomial is zero. + */ + unsigned graded_lex_min_pos() const { + if (m_size == 0) + return UINT_MAX; + unsigned min_pos = 0; + for (unsigned i = 1; i < m_size; i++) { + if (graded_lex_compare(m_ms[i], m_ms[min_pos]) < 0) + min_pos = i; + } + return min_pos; + } + + unsigned id() const { return m_id; } + unsigned size() const { return m_size; } + monomial * m(unsigned idx) const { SASSERT(idx < size()); return m_ms[idx]; } + numeral const & a(unsigned idx) const { SASSERT(idx < size()); return m_as[idx]; } + numeral & a(unsigned idx) { SASSERT(idx < size()); return m_as[idx]; } + numeral const * as() const { return m_as; } + + bool is_zero() const { return m_size == 0; } + + void display(std::ostream & out, mpzzp_manager & nm, display_var_proc const & proc = display_var_proc(), bool use_star = false) const { + if (is_zero()) { + out << "0"; + return; + } + + for (unsigned i = 0; i < m_size; i++) { + numeral const & a_i = a(i); + _scoped_numeral abs_a_i(nm); + nm.set(abs_a_i, a_i); + nm.abs(abs_a_i); + + numeral const & a_prime = abs_a_i; + if (i > 0) { + if (nm.is_neg(a_i)) + out << " - "; + else + out << " + "; + } + else { + if (nm.is_neg(a_i)) + out << "- "; + } + + if (m(i)->is_unit()) { + out << nm.to_string(a_prime); + } + else if (nm.is_one(a_prime)) { + m(i)->display(out, proc, use_star); + } + else { + out << nm.to_string(a_prime); + if (use_star) + out << "*"; + else + out << " "; + m(i)->display(out, proc, use_star); + } + } + } + + static void display_num_smt2(std::ostream & out, mpzzp_manager & nm, numeral const & a) { + if (nm.is_neg(a)) { + out << "(- "; + _scoped_numeral abs_a(nm); + nm.set(abs_a, a); + nm.neg(abs_a); + nm.display(out, abs_a); + out << ")"; + } + else { + nm.display(out, a); + } + } + + void display_mon_smt2(std::ostream & out, mpzzp_manager & nm, display_var_proc const & proc, unsigned i) const { + SASSERT(i < m_size); + monomial const * m_i = m(i); + numeral const & a_i = a(i); + if (m_i->size() == 0) { + display_num_smt2(out, nm, a_i); + } + else if (nm.is_one(a_i)) { + m_i->display(out, proc); + } + else { + out << "(* "; + display_num_smt2(out, nm, a_i); + m_i->display(out, proc); + out << ")"; + } + } + + void display_smt2(std::ostream & out, mpzzp_manager & nm, display_var_proc const & proc = display_var_proc()) const { + if (m_size == 0) { + out << "0"; + } + else if (m_size == 1) { + display_mon_smt2(out, nm, proc, 0); + } + else { + out << "(+"; + for (unsigned i = 0; i < m_size; i++) { + out << " "; + display_mon_smt2(out, nm, proc, i); + } + out << ")"; + } + } + + void display(std::ostream & out, mpzzp_manager & nm, bool use_star) const { + display(out, nm, display_var_proc(), use_star); + } + + }; + + manager::factors::factors(manager & _m):m_manager(_m), m_total_factors(0) { + m().m().set(m_constant, 1); + } + + manager::factors::~factors() { + reset(); + m().m().del(m_constant); + } + + void manager::factors::reset() { + for (unsigned i = 0; i < m_factors.size(); ++ i) { + m().dec_ref(m_factors[i]); + } + m_factors.reset(); + m_degrees.reset(); + m_total_factors = 0; + m().m().set(m_constant, 1); + } + + void manager::factors::push_back(polynomial * p, unsigned degree) { + SASSERT(p != 0 && degree > 0); + m_factors.push_back(p); + m_degrees.push_back(degree); + m_total_factors += degree; + m().inc_ref(p); + } + + void manager::factors::multiply(polynomial_ref & out) const { + if (m_factors.empty()) { + out = m().mk_const(m_constant); + } + else { + // multiply the factors + for (unsigned i = 0; i < m_factors.size(); ++ i) { + polynomial_ref current(m_factors[i], m()); + if (m_degrees[i] > 1) { + m().pw(current, m_degrees[i], current); + } + if (i == 0) { + out = current; + } else { + out = m().mul(out, current); + } + } + // multiply the constant + out = m().mul(m_constant, out); + } + } + + void manager::factors::display(std::ostream & out) const { + out << m().m().to_string(get_constant()); + for (unsigned i = 0; i < m_factors.size(); ++ i) { + out << " * ("; + m_manager.display(out, m_factors[i]); + out << ")^" << m_degrees[i]; + } + } + + void manager::factors::set_constant(numeral const & constant) { + m().m().set(m_constant, constant); + } + + void manager::factors::set_degree(unsigned i, unsigned degree) { + SASSERT(i > 0); + m_total_factors -= m_degrees[i]; + m_total_factors += m_degrees[i] = degree; + } + + polynomial_ref manager::factors::operator[](unsigned i) const { + return polynomial_ref(m_factors[i], m()); + } + + unsigned manager::id(monomial const * m) { + return m->id(); + } + + unsigned manager::id(polynomial const * p) { + return p->id(); + } + + bool manager::is_unit(monomial const * m) { + return m->size() == 0; + } + + bool manager::is_zero(polynomial const * p) { + return p->size() == 0; + } + + bool manager::is_const(polynomial const * p) { + return is_zero(p) || (p->size() == 1 && is_unit(p->m(0))); + } + + bool manager::is_univariate(monomial const * m) { + return m->size() <= 1; + } + + bool manager::is_univariate(polynomial const * p) { + unsigned sz = p->size(); + if (is_const(p)) + return true; + monomial * m = p->m(0); + var x = max_var(p); + for (unsigned i = 0; i < sz; i++) { + m = p->m(i); + if (m->size() == 1 && m->get_var(0) == x) + continue; + if (m->size() == 0) + continue; + return false; + } + return true; + } + + unsigned manager::size(polynomial const * p) { + return p->size(); + } + + polynomial::numeral const & manager::coeff(polynomial const * p, unsigned i) { + return p->a(i); + } + + polynomial::numeral const & manager::univ_coeff(polynomial const * p, unsigned k) { + static numeral zero(0); + SASSERT(is_univariate(p)); + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + if (p->m(i)->total_degree() == k) + return p->a(i); + } + return zero; + } + + monomial * manager::get_monomial(polynomial const * p, unsigned i) { + return p->m(i); + } + + unsigned manager::total_degree(monomial const * m) { + return m->total_degree(); + } + + unsigned manager::size(monomial const * m) { + return m->size(); + } + + var manager::get_var(monomial const * m, unsigned i) { + return m->get_var(i); + } + + unsigned manager::degree(monomial const * m, unsigned i) { + return m->degree(i); + } + + unsigned manager::degree_of(monomial const * m, var x) { + return m->degree_of(x); + } + + bool manager::is_linear(monomial const * m) { + return m->size() == 0 || (m->size() == 1 && m->degree(0) == 1); + } + + bool manager::is_linear(polynomial const * p) { + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + if (!is_linear(p->m(0))) + return false; + } + return true; + } + + unsigned manager::degree(polynomial const * p, var x) { + unsigned sz = p->size(); + if (sz == 0) + return 0; + monomial * m = p->m(0); + unsigned msz = m->size(); + if (msz == 0) + return 0; // see polynomial invariant. + if (m->get_var(msz - 1) == x) { + // x is the maximal variable in p + return m->degree(msz - 1); + } + unsigned r = 0; + // use slow (linear) scan. + for (unsigned i = 0; i < sz; i++) { + unsigned d = p->m(i)->degree_of(x); + if (d > r) + r = d; + } + return r; + } + + var manager::max_var(polynomial const * p) { + if (p->size() == 0) + return null_var; + monomial * m = p->m(0); + return m->max_var(); + } + + unsigned manager::total_degree(polynomial const * p) { + // use linear scan... if it turns out to be too slow, I should cache total_degree in polynomial + unsigned r = 0; + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + unsigned t = p->m(i)->total_degree(); + if (t > r) + r = t; + } + return r; + } + + struct manager::imp { + typedef upolynomial::manager up_manager; + typedef mpzzp_manager numeral_manager; // refine numeral_manager + + typedef _scoped_numeral scoped_numeral; + typedef _scoped_numeral_vector scoped_numeral_vector; + + manager & m_wrapper; + numeral_manager m_manager; + up_manager m_upm; + monomial_manager * m_monomial_manager; + polynomial_vector m_polynomials; + id_gen m_pid_gen; // id generator for polynomials + del_eh * m_del_eh; + polynomial * m_zero; + numeral m_zero_numeral; + polynomial * m_unit_poly; + monomial2pos m_m2pos; + tmp_monomial m_tmp1; + numeral_vector m_rat2numeral; + numeral_vector m_tmp_linear_as; + monomial_vector m_tmp_linear_ms; + unsigned_vector m_degree2pos; + bool m_use_sparse_gcd; + bool m_use_prs_gcd; + volatile bool m_cancel; + + // Debugging method: check if the coefficients of p are in the numeral_manager. + bool consistent_coeffs(polynomial const * p) { + scoped_numeral a(m_manager); + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + m_manager.set(a, p->a(i)); + SASSERT(m_manager.eq(a, p->a(i))); + } + return true; + } + + /** + \brief Divide as by the GCD of as. + Return true, if the GCD is not 1. + */ + static bool normalize_numerals(numeral_manager & m, numeral_vector & as) { + unsigned sz = as.size(); + if (sz == 0) + return false; + scoped_numeral g(m); + m.gcd(as.size(), as.c_ptr(), g); + if (m.is_one(g)) + return false; + SASSERT(m.is_pos(g)); + for (unsigned i = 0; i < sz; i++) { + m.div(as[i], g, as[i]); + } + return true; + } + + /** + \brief Som-of-monomials buffer. + This a temporary datastructure for building polynomials. + + The following idiom should be used: + Invoke add(...), addmul(...) several times, and then invoke mk() to obtain the final polynomial. + */ + class som_buffer { + imp * m_owner; + monomial2pos m_m2pos; + numeral_vector m_tmp_as; + monomial_vector m_tmp_ms; + + /** + \brief Remove zeros from m_tmp_as & m_tmp_ms. + The reference counters of eliminated m_tmp_ms are decremented. + m_m2pos is reset. That is for every m in m_tmp_ms, m_m2pos[m->id()] == UINT_MAX + */ + void remove_zeros(bool normalize) { + numeral_manager & mng = m_owner->m_manager; + SASSERT(m_tmp_ms.size() == m_tmp_as.size()); + unsigned sz = m_tmp_ms.size(); + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + monomial * m = m_tmp_ms[i]; + m_m2pos.reset(m); + if (mng.is_zero(m_tmp_as[i])) { + mng.reset(m_tmp_as[i]); + m_owner->dec_ref(m_tmp_ms[i]); + } + else { + if (i != j) { + SASSERT(m_tmp_ms[j] != m); + m_tmp_ms[j] = m; + swap(m_tmp_as[j], m_tmp_as[i]); + } + j++; + } + } + DEBUG_CODE({ + for (unsigned i = j; i < sz; i++) { + SASSERT(mng.is_zero(m_tmp_as[i])); + } + }); + m_tmp_as.shrink(j); + m_tmp_ms.shrink(j); + if (normalize) { + normalize_numerals(mng, m_tmp_as); + } + } + + public: + som_buffer():m_owner(0) {} + + void reset() { + if (empty()) + return; + numeral_manager & mng = m_owner->m_manager; + SASSERT(m_tmp_ms.size() == m_tmp_as.size()); + unsigned sz = m_tmp_ms.size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m = m_tmp_ms[i]; + m_m2pos.reset(m); + mng.reset(m_tmp_as[i]); + m_owner->dec_ref(m_tmp_ms[i]); + } + m_tmp_as.reset(); + m_tmp_ms.reset(); + } + + void set_owner(imp * o) { m_owner = o; } + + unsigned size() const { return m_tmp_ms.size(); } + + bool empty() const { return m_tmp_ms.empty(); } + + monomial * m(unsigned i) const { return m_tmp_ms[i]; } + + numeral const & a(unsigned i) const { return m_tmp_as[i]; } + + /** + \brief Return the position of the maximal monomial with + respect to graded lexicographical order. + + Return UINT_MAX if empty. + */ + unsigned graded_lex_max_pos() const { + numeral_manager & mng = m_owner->m_manager; + unsigned max_pos = UINT_MAX; + unsigned sz = m_tmp_as.size(); + for (unsigned i = 0; i < sz; i++) { + if (!mng.is_zero(m_tmp_as[i])) { + if (max_pos == UINT_MAX) { + max_pos = i; + } + else { + if (graded_lex_compare(m_tmp_ms[i], m_tmp_ms[max_pos]) > 0) + max_pos = i; + } + } + } + return max_pos; + } + + /** + \brief Store a*m*p into the buffer. + m_m2pos is updated with the position of the monomials in m_tmp_ms. + + The reference counter of new monomials added into the buffer is increased. + */ + template + void addmul_core(numeral const & a, monomial const * m, PolyType const * p) { + numeral_manager & mng = m_owner->m_manager; + if (mng.is_zero(a)) + return; + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + if (CheckZeros && mng.is_zero(p->a(i))) + continue; + monomial * m2 = p->m(i); + m2 = m_owner->mul(m, m2); + unsigned pos = m_m2pos.get(m2); + if (pos == UINT_MAX) { + m_m2pos.set(m2, m_tmp_ms.size()); + m_tmp_ms.push_back(m2); + m_owner->inc_ref(m2); + m_tmp_as.push_back(numeral()); + mng.mul(a, p->a(i), m_tmp_as.back()); + } + else { + mng.addmul(m_tmp_as[pos], a, p->a(i), m_tmp_as[pos]); + } + } + } + + void addmul(numeral const & a, monomial const * m, polynomial const * p) { + return addmul_core(a, m, p); + } + + void addmul(numeral const & a, monomial const * m, som_buffer const * p) { + return addmul_core(a, m, p); + } + + void addmul(numeral const & a, monomial const * m, som_buffer const & p) { + return addmul(a, m, &p); + } + + void addmul(numeral const & a, som_buffer const * p) { + return addmul(a, m_owner->mk_unit(), p); + } + + void addmul(numeral const & a, som_buffer const & p) { + return addmul(a, &p); + } + + void addmul(monomial const * m, som_buffer const * p) { + numeral one(1); + return addmul(one, m, p); + } + + void addmul(monomial const * m, som_buffer const & p) { + return addmul(m, &p); + } + + /** + \brief Store p into the buffer. + m_m2pos is updated with the position of the monomials in m_tmp_ms. + + The reference counter of new monomials added into the buffer is increased. + */ + void add(polynomial const * p) { + numeral_manager & mng = m_owner->m_manager; + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m2 = p->m(i); + unsigned pos = m_m2pos.get(m2); + if (pos == UINT_MAX) { + m_m2pos.set(m2, m_tmp_ms.size()); + m_tmp_ms.push_back(m2); + m_owner->inc_ref(m2); + m_tmp_as.push_back(numeral()); + mng.set(m_tmp_as.back(), p->a(i)); + } + else { + mng.add(m_tmp_as[pos], p->a(i), m_tmp_as[pos]); + } + } + } + + /** + \brief Add 'a*m' into m_tmp_as and m_tmp_ms. + m_m2pos is updated with the position of the monomials in m_tmp_ms. + + The reference counter of m is increased. + */ + void add(numeral const & a, monomial * m) { + numeral_manager & mng = m_owner->m_manager; + if (mng.is_zero(a)) + return; + unsigned pos = m_m2pos.get(m); + if (pos == UINT_MAX) { + m_m2pos.set(m, m_tmp_ms.size()); + m_owner->inc_ref(m); + m_tmp_ms.push_back(m); + m_tmp_as.push_back(numeral()); + mng.set(m_tmp_as.back(), a); + } + else { + mng.add(m_tmp_as[pos], a, m_tmp_as[pos]); + } + } + + /** + \brief Add 'a' (that is, a*m_unit) into m_tmp_as and m_tmp_ms. + m_m2pos is updated with the position of the monomials in m_tmp_ms. + + The reference counter of m_unit is increased. + */ + void add(numeral const & a) { + add(a, m_owner->mk_unit()); + } + + void sort_graded_lex() { + std::sort(m_tmp_ms.begin(), m_tmp_ms.end(), graded_lex_gt()); + numeral_vector new_as; + unsigned sz = m_tmp_ms.size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m = m_tmp_ms[i]; + unsigned pos = m_m2pos.get(m); + new_as.push_back(numeral()); + swap(new_as.back(), m_tmp_as[pos]); + m_m2pos.reset(m); + m_m2pos.set(m, i); + } + m_tmp_as.swap(new_as); + } + + // For each monomial m + // If m contains x^k and k >= x2d[x] and x2d[x] != 0, then set coefficient of m to 0. + void mod_d(var2degree const & x2d) { + numeral_manager & mng = m_owner->m_manager; + unsigned sz = m_tmp_ms.size(); + for (unsigned i = 0; i < sz; i++) { + if (mng.is_zero(m_tmp_as[i])) + continue; + monomial * m = m_tmp_ms[i]; + unsigned msz = m->size(); + unsigned j; + for (j = 0; j < msz; j++) { + var x = m->get_var(j); + unsigned dx = x2d.degree(x); + if (dx == 0) + continue; + if (m->degree(j) >= dx) + break; + } + if (j < msz) { + mng.reset(m_tmp_as[i]); + } + } + } + + polynomial * mk(bool normalize = false) { + remove_zeros(normalize); + polynomial * p = m_owner->mk_polynomial_core(m_tmp_as.size(), m_tmp_as.c_ptr(), m_tmp_ms.c_ptr()); + m_tmp_as.reset(); + m_tmp_ms.reset(); + return p; + } + + void display(std::ostream & out) const { + SASSERT(m_tmp_ms.size() == m_tmp_as.size()); + numeral_manager & mng = m_owner->m_manager; + for (unsigned i = 0; i < m_tmp_as.size(); i++) { + if (i > 0) out << " + "; + out << mng.to_string(m_tmp_as[i]) << "*"; m_tmp_ms[i]->display(out); + } + out << "\n"; + } + }; + + class som_buffer_vector { + imp * m_owner; + ptr_vector m_buffers; + + void ensure_capacity(unsigned sz) { + unsigned old_sz = m_buffers.size(); + for (unsigned i = old_sz; i < sz; i++) { + som_buffer * new_buffer = alloc(som_buffer); + if (m_owner) + new_buffer->set_owner(m_owner); + m_buffers.push_back(new_buffer); + } + SASSERT(m_buffers.size() >= sz); + } + + public: + som_buffer_vector() { + m_owner = 0; + } + + ~som_buffer_vector() { + unsigned sz = m_buffers.size(); + for (unsigned i = 0; i < sz; i++) { + dealloc(m_buffers[i]); + } + } + + void set_owner(imp * owner) { + SASSERT(m_owner == owner || m_owner == 0); + if (m_owner == 0) { + m_owner = owner; + unsigned sz = m_buffers.size(); + for (unsigned i = 0; i < sz; i++) { + m_buffers[i]->set_owner(m_owner); + } + } + } + + som_buffer * operator[](unsigned idx) { + ensure_capacity(idx+1); + return m_buffers[idx]; + } + + void reset(unsigned sz) { + if (sz > m_buffers.size()) + sz = m_buffers.size(); + for (unsigned i = 0; i < sz; i++) { + m_buffers[i]->reset(); + } + } + + void reset() { + reset(m_buffers.size()); + } + + }; + + /** + \brief Cheap version of som_buffer. + In this buffer, each monomial can be added at most once. + */ + class cheap_som_buffer { + imp * m_owner; + numeral_vector m_tmp_as; + monomial_vector m_tmp_ms; + public: + cheap_som_buffer():m_owner(0) {} + + void set_owner(imp * o) { m_owner = o; } + bool empty() const { return m_tmp_ms.empty(); } + + /** + \brief Add a*m to the buffer, the content of a is reset. + */ + void add_reset(numeral & a, monomial * m) { + SASSERT(std::find(m_tmp_ms.begin(), m_tmp_ms.end(), m) == m_tmp_ms.end()); + numeral_manager & mng = m_owner->m_manager; + if (mng.is_zero(a)) + return; + m_tmp_as.push_back(numeral()); + swap(m_tmp_as.back(), a); + m_owner->inc_ref(m); + m_tmp_ms.push_back(m); + } + + /** + \brief Add a*m to the buffer. + */ + void add(numeral const & a, monomial * m) { + SASSERT(std::find(m_tmp_ms.begin(), m_tmp_ms.end(), m) == m_tmp_ms.end()); + numeral_manager & mng = m_owner->m_manager; + if (mng.is_zero(a)) + return; + m_tmp_as.push_back(numeral()); + mng.set(m_tmp_as.back(), a); + m_owner->inc_ref(m); + m_tmp_ms.push_back(m); + } + + /** + \brief Add a*m*p to the buffer. + */ + void addmul(numeral const & a, monomial const * m, polynomial const * p) { + numeral_manager & mng = m_owner->m_manager; + if (mng.is_zero(a)) + return; + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m2 = p->m(i); + m2 = m_owner->mul(m, m2); + // m2 is not in m_tmp_ms + SASSERT(std::find(m_tmp_ms.begin(), m_tmp_ms.end(), m2) == m_tmp_ms.end()); + m_owner->inc_ref(m2); + m_tmp_ms.push_back(m2); + m_tmp_as.push_back(numeral()); + mng.mul(a, p->a(i), m_tmp_as.back()); + } + } + + bool normalize() { + return normalize_numerals(m_owner->m_manager, m_tmp_as); + } + + void reset() { + if (empty()) + return; + numeral_manager & mng = m_owner->m_manager; + unsigned sz = m_tmp_ms.size(); + for (unsigned i = 0; i < sz; i++) { + mng.del(m_tmp_as[i]); + m_owner->dec_ref(m_tmp_ms[i]); + } + m_tmp_as.reset(); + m_tmp_ms.reset(); + } + + polynomial * mk() { + polynomial * new_p = m_owner->mk_polynomial_core(m_tmp_as.size(), m_tmp_as.c_ptr(), m_tmp_ms.c_ptr()); + m_tmp_as.reset(); + m_tmp_ms.reset(); + return new_p; + } + }; + + som_buffer m_som_buffer; + som_buffer m_som_buffer2; + cheap_som_buffer m_cheap_som_buffer; + cheap_som_buffer m_cheap_som_buffer2; + + void init() { + m_del_eh = 0; + m_som_buffer.set_owner(this); + m_som_buffer2.set_owner(this); + m_cheap_som_buffer.set_owner(this); + m_cheap_som_buffer2.set_owner(this); + m_zero = mk_polynomial_core(0, 0, 0); + m().set(m_zero_numeral, 0); + inc_ref(m_zero); + numeral one(1); + m_unit_poly = mk_const_core(one); + inc_ref(m_unit_poly); + m_use_sparse_gcd = true; + m_use_prs_gcd = false; + m_cancel = false; + } + + imp(manager & w, unsynch_mpz_manager & m, monomial_manager * mm): + m_wrapper(w), + m_manager(m), + m_upm(m) { + if (mm == 0) + mm = alloc(monomial_manager); + m_monomial_manager = mm; + m_monomial_manager->inc_ref(); + init(); + } + + imp(manager & w, unsynch_mpz_manager & m, small_object_allocator * a): + m_wrapper(w), + m_manager(m), + m_upm(m) { + m_monomial_manager = alloc(monomial_manager, a); + m_monomial_manager->inc_ref(); + init(); + } + + ~imp() { + dec_ref(m_zero); + dec_ref(m_unit_poly); + m_som_buffer.reset(); + m_som_buffer2.reset(); + m_cheap_som_buffer.reset(); + m_cheap_som_buffer2.reset(); + m_manager.del(m_zero_numeral); + m_mgcd_iterpolators.flush(); + m_mgcd_skeletons.reset(); + DEBUG_CODE({ + TRACE("polynomial", + tout << "leaked polynomials\n"; + for (unsigned i = 0; i < m_polynomials.size(); i++) { + if (m_polynomials[i] != 0) { + m_polynomials[i]->display(tout, m_manager); + tout << "\n"; + } + }); + m_polynomials.reset(); + }); + SASSERT(m_polynomials.empty()); + m_monomial_manager->dec_ref(); + } + + void set_cancel(bool f) { + m_cancel = f; + m_upm.set_cancel(f); + } + + void checkpoint() { + if (m_cancel) { + set_cancel(false); + throw polynomial_exception("canceled"); + } + cooperate("polynomial"); + } + + mpzzp_manager & m() const { return const_cast(this)->m_manager; } + manager & pm() const { return m_wrapper; } + up_manager & upm() { return m_upm; } + monomial_manager & mm() const { return *m_monomial_manager; } + + var mk_var() { + return mm().mk_var(); + } + + unsigned num_vars() const { + return mm().num_vars(); + } + + bool is_valid(var x) const { + return mm().is_valid(x); + } + + void add_del_eh(del_eh * eh) { + eh->m_next = m_del_eh; + m_del_eh = eh; + } + + void remove_del_eh(del_eh * eh) { + SASSERT(eh != 0); + SASSERT(m_del_eh != 0); + if (m_del_eh == eh) { + m_del_eh = m_del_eh->m_next; + } + else { + del_eh * curr = m_del_eh; + while (curr) { + if (curr->m_next == eh) { + curr->m_next = curr->m_next->m_next; + return; + } + curr = curr->m_next; + } + UNREACHABLE(); + } + } + + void del(polynomial * p) { + TRACE("polynomial", tout << "deleting: "; p->display(tout, m_manager); tout << "\n";); + if (m_del_eh != 0) { + del_eh * curr = m_del_eh; + do { + (*curr)(p); + curr = curr->m_next; + } + while (curr != 0); + } + unsigned sz = p->size(); + unsigned obj_sz = polynomial::get_obj_size(sz); + for (unsigned i = 0; i < sz; i++) { + m_manager.del(p->a(i)); + dec_ref(p->m(i)); + } + unsigned id = p->id(); + m_pid_gen.recycle(id); + m_polynomials[id] = 0; + mm().allocator().deallocate(obj_sz, p); + } + + void inc_ref(monomial * m) { + mm().inc_ref(m); + } + + void dec_ref(monomial * m) { + mm().dec_ref(m); + } + + void inc_ref(polynomial * p) { + p->inc_ref(); + } + + void dec_ref(polynomial * p) { + p->dec_ref(); + if (p->ref_count() == 0) + del(p); + } + + vector m_lex_sort_buckets; + unsigned_vector m_lex_sort_permutation; + void lex_sort(polynomial const * p) { + const_cast(p)->lex_sort(m_lex_sort_buckets, m_lex_sort_permutation, m_manager); + } + + struct poly_khasher { unsigned operator()(polynomial const * p) const { return 17; } }; + + struct poly_chasher { + unsigned operator()(polynomial const * p, unsigned idx) const { + return hash_u_u(p->m(idx)->hash(), numeral_manager::hash(p->a(idx))); + } + }; + + unsigned hash(polynomial const * p) { + if (p->size() == 0) + return 31; + lex_sort(const_cast(p)); + return get_composite_hash(p, p->size(), poly_khasher(), poly_chasher()); + } + + polynomial * mk_polynomial_core(unsigned sz, numeral * as, monomial * const * ms) { + unsigned obj_sz = polynomial::get_obj_size(sz); + void * mem = mm().allocator().allocate(obj_sz); + void * as_mem = static_cast(mem) + sizeof(polynomial); + void * ms_mem = static_cast(as_mem) + sizeof(numeral)*sz; + unsigned id = m_pid_gen.mk(); + polynomial * p = new (mem) polynomial(m_manager, id, sz, as, ms, static_cast(as_mem), static_cast(ms_mem)); + m_polynomials.reserve(id+1); + SASSERT(m_polynomials[id] == 0); + m_polynomials[id] = p; + return p; + } + + polynomial * mk_zero() { + return m_zero; + } + + polynomial * mk_one() { + return m_unit_poly; + } + + monomial * mk_unit() { return mm().mk_unit(); } + + monomial * mk_monomial(tmp_monomial & tmp) { return mm().mk_monomial(tmp); } + + monomial * mk_monomial(var x) { return mm().mk_monomial(x); } + + monomial * mk_monomial(var x, unsigned k) { return mm().mk_monomial(x, k); } + + monomial * mk_monomial(unsigned sz, var * xs) { return mm().mk_monomial(sz, xs); } + + monomial * mk_monomial(unsigned sz, power const * pws) { return mm().mk_monomial(sz, pws); } + + monomial * convert(monomial const * src) { return mm().convert(src); } + + polynomial * mk_const_core(numeral & a) { + monomial * u = mk_unit(); + inc_ref(u); + return mk_polynomial_core(1, &a, &u); + } + + polynomial * mk_const(numeral & a) { + if (m_manager.is_zero(a)) + return mk_zero(); + if (m_manager.is_one(a)) + return mk_one(); + return mk_const_core(a); + } + + polynomial * mk_const(rational const & a) { + SASSERT(a.is_int()); + scoped_numeral tmp(m_manager); + m_manager.set(tmp, a.to_mpq().numerator()); + polynomial * p = mk_const(tmp); + return p; + } + + polynomial * mk_polynomial(var x, unsigned k) { + SASSERT(is_valid(x)); + numeral one(1); + monomial * m = mk_monomial(x, k); + inc_ref(m); + return mk_polynomial_core(1, &one, &m); + } + + polynomial * mk_polynomial(unsigned sz, numeral * as, monomial * const * ms) { + m_som_buffer.reset(); + for (unsigned i = 0; i < sz; i++) { + m_som_buffer.add(as[i], ms[i]); + } + return m_som_buffer.mk(); + } + + /** + \brief Convert rationals into numerals at m_rat2numeral + */ + void rational2numeral(unsigned sz, rational const * as) { + SASSERT(m_rat2numeral.empty()); + for (unsigned i = 0; i < sz; i++) { + SASSERT(as[i].is_int()); + m_rat2numeral.push_back(numeral()); + m_manager.set(m_rat2numeral.back(), as[i].to_mpq().numerator()); + } + } + + void reset_tmp_as2() { + DEBUG_CODE({ + for (unsigned i = 0; i < m_rat2numeral.size(); i++) { + SASSERT(m_manager.is_zero(m_rat2numeral[i])); + } + }); + m_rat2numeral.reset(); + } + + polynomial * mk_polynomial(unsigned sz, rational const * as, monomial * const * ms) { + rational2numeral(sz, as); + polynomial * p = mk_polynomial(sz, m_rat2numeral.c_ptr(), ms); + reset_tmp_as2(); + return p; + } + + polynomial * mk_univariate(var x, unsigned n, numeral * as) { + SASSERT(m_cheap_som_buffer.empty()); + unsigned k = n+1; + while (k > 0) { + --k; + if (m_manager.is_zero(as[k])) { + m_manager.del(as[k]); + continue; + } + m_cheap_som_buffer.add_reset(as[k], mk_monomial(x, k)); + } + return m_cheap_som_buffer.mk(); + } + + polynomial * mk_univariate(var x, unsigned n, rational const * as) { + SASSERT(is_valid(x)); + rational2numeral(n+1, as); + polynomial * p = mk_univariate(x, n, m_rat2numeral.c_ptr()); + reset_tmp_as2(); + return p; + } + + polynomial * mk_linear(unsigned sz, numeral * as, var const * xs, numeral & c) { + SASSERT(m_tmp_linear_as.empty()); + SASSERT(m_tmp_linear_ms.empty()); + for (unsigned i = 0; i < sz; i++) { + if (m_manager.is_zero(as[i])) + continue; + m_tmp_linear_as.push_back(numeral()); + swap(m_tmp_linear_as.back(), as[i]); + m_tmp_linear_ms.push_back(mk_monomial(xs[i])); + } + if (!m_manager.is_zero(c)) { + m_tmp_linear_as.push_back(numeral()); + swap(m_tmp_linear_as.back(), c); + m_tmp_linear_ms.push_back(mk_unit()); + } + polynomial * p = mk_polynomial(m_tmp_linear_as.size(), m_tmp_linear_as.c_ptr(), m_tmp_linear_ms.c_ptr()); + m_tmp_linear_as.reset(); + m_tmp_linear_ms.reset(); + return p; + } + + polynomial * mk_linear(unsigned sz, rational const * as, var const * xs, rational const & c) { + SASSERT(c.is_int()); + rational2numeral(sz, as); + numeral tmp_c; + m_manager.set(tmp_c, c.to_mpq().numerator()); + polynomial * p = mk_linear(sz, m_rat2numeral.c_ptr(), xs, tmp_c); + SASSERT(m_manager.is_zero(tmp_c)); + reset_tmp_as2(); + return p; + } + + + monomial * mul(monomial const * m1, monomial const * m2) { + return mm().mul(m1, m2); + } + + bool div(monomial const * m1, monomial const * m2) { + return mm().div(m1, m2); + } + + bool div(monomial const * m1, monomial const * m2, monomial * & r) { + return mm().div(m1, m2, r); + } + + monomial * gcd(monomial const * m1, monomial const * m2, monomial * & q1, monomial * & q2) { + return mm().gcd(m1, m2, q1, q2); + } + + bool unify(monomial const * m1, monomial const * m2, monomial * & q1, monomial * & q2) { + return mm().unify(m1, m2, q1, q2); + } + + monomial * pw(monomial const * m, unsigned k) { + return mm().pw(m, k); + } + + monomial * sqrt(monomial const * m) { + return mm().sqrt(m); + } + + polynomial * addmul(numeral const & a1, monomial const * m1, polynomial const * p1, numeral const & a2, monomial const * m2, polynomial const * p2) { + m_som_buffer.reset(); + m_som_buffer.addmul(a1, m1, p1); + m_som_buffer.addmul(a2, m2, p2); + return m_som_buffer.mk(); + } + + polynomial * addmul(polynomial const * p1, numeral const & a2, monomial const * m2, polynomial const * p2) { + numeral one(1); + return addmul(one, mk_unit(), p1, a2, m2, p2); + } + + polynomial * addmul(polynomial const * p1, numeral const & a2, polynomial const * p2) { + return addmul(p1, a2, mk_unit(), p2); + } + + polynomial * add(polynomial const * p1, polynomial const * p2) { + numeral one(1); + return addmul(one, mk_unit(), p1, one, mk_unit(), p2); + } + + polynomial * sub(polynomial const * p1, polynomial const * p2) { + numeral one(1); + numeral minus_one; // It is incorrect to initialize with -1 when numeral_manager is GF_2 + m_manager.set(minus_one, -1); + return addmul(one, mk_unit(), p1, minus_one, mk_unit(), p2); + } + + /** + \brief Return p1*p2 + a + */ + polynomial * muladd(polynomial const * p1, polynomial const * p2, numeral const & a) { + if (is_zero(p1) || is_zero(p2)) { + return mk_const(a); + } + m_som_buffer.reset(); + unsigned sz1 = p1->size(); + for (unsigned i = 0; i < sz1; i++) { + checkpoint(); + numeral const & a1 = p1->a(i); + monomial * m1 = p1->m(i); + m_som_buffer.addmul(a1, m1, p2); + } + m_som_buffer.add(a); + return m_som_buffer.mk(); + } + + polynomial * mul(polynomial const * p1, polynomial const * p2) { + numeral zero(0); + return muladd(p1, p2, zero); + } + + polynomial * mul(numeral const & a, monomial const * m, polynomial const * p) { + if (m_manager.is_zero(a)) + return m_zero; + if (m_manager.is_one(a) && m == mk_unit()) + return const_cast(p); + SASSERT(m_cheap_som_buffer.empty()); + m_cheap_som_buffer.addmul(a, m, p); + return m_cheap_som_buffer.mk(); + } + + polynomial * mul(monomial const * m, polynomial const * p) { + numeral one(1); + return mul(one, m, p); + } + + polynomial * mul(numeral const & a, polynomial const * p) { + return mul(a, mk_unit(), p); + } + + /** + \brief Return a*p1*p2 + */ + polynomial * mul(numeral const & a, polynomial const * p1, polynomial const * p2) { + if (m_manager.is_zero(a) || is_zero(p1) || is_zero(p2)) + return mk_zero(); + scoped_numeral new_a1(m_manager); + m_som_buffer.reset(); + unsigned sz1 = p1->size(); + for (unsigned i = 0; i < sz1; i++) { + checkpoint(); + numeral const & a1 = p1->a(i); + m_manager.mul(a, a1, new_a1); + monomial * m1 = p1->m(i); + m_som_buffer.addmul(new_a1, m1, p2); + } + return m_som_buffer.mk(); + } + + // Divide coefficients of p by d. + // This methods assumes that all coefficients of p are divisible by d. + polynomial * div(polynomial * p, numeral const & d) { + SASSERT(m_cheap_som_buffer.empty()); + unsigned sz = p->size(); + scoped_numeral a(m_manager); + for (unsigned i = 0; i < sz; i++) { + m_manager.div(p->a(i), d, a); + m_cheap_som_buffer.add(a, p->m(i)); + } + return m_cheap_som_buffer.mk(); + } + + polynomial * mul(rational const & a, polynomial const * p) { + SASSERT(a.is_int()); + scoped_numeral tmp(m_manager); + m_manager.set(tmp, a.to_mpq().numerator()); + polynomial * new_p = mul(tmp, p); + return new_p; + } + + /** + \brief Return m/x^k + */ + monomial * div_x_k(monomial const * m, var x, unsigned k) { + return mm().div_x_k(m, x, k); + } + + /** + \brief Return m/x^n where n == m->degree_of(x) + */ + monomial * div_x(monomial const * m, var x) { + return mm().div_x(m, x); + } + + bool is_p_normalized(polynomial const * p) const { + for (unsigned i = 0; i < p->size(); i++) { + SASSERT(m().is_p_normalized(p->a(i))); + } + return true; + } + + /** + \brief (Incremental) Newton interpolation for multivariate polynomials. + Creates a polynomial on x of degree at most d with coefficients in Z[y1, ..., ym], + using d+1 sample points. + Sample points are provided using the method add, and the interpolating polynomial + is created using mk() method. + + \pre manager must be configured in Zp (modular) mode. + We need this requeriment because we use the inverse operation. + */ + class newton_interpolator { + imp & pm; + scoped_numeral_vector m_inputs; + scoped_numeral_vector m_invs; + polynomial_ref_vector m_vs; + mpzzp_manager & m() const { return pm.m(); } + public: + newton_interpolator(imp & _pm):pm(_pm), m_inputs(m()), m_invs(m()), m_vs(pm.m_wrapper) { + m_invs.push_back(numeral(0)); + } + + void reset() { + m_inputs.reset(); + m_invs.shrink(1); + m_vs.reset(); + SASSERT(m().is_zero(m_invs[0])); + } + + scoped_numeral_vector const & inputs() const { return m_inputs; } + + unsigned num_sample_points() const { return m_inputs.size(); } + + /** + \brief Add a new datapoint + */ + void add(numeral const & input, polynomial const * output) { + TRACE("newton", tout << m().to_string(input) << " -> "; output->display(tout, m()); tout << "\n";); + SASSERT(m().modular()); + unsigned sz = num_sample_points(); + if (sz > 0) { + unsigned k = sz; + // add new inverse + scoped_numeral product(m()); + scoped_numeral aux(m()); + SASSERT(!m().eq(input, m_inputs[0])); + m().sub(input, m_inputs[0], product); + for (unsigned i = 1; i <= k - 1; i++) { + SASSERT(!m().eq(input, m_inputs[i])); + m().sub(input, m_inputs[i], aux); + m().mul(product, aux, product); + } + m().inv(product); + m_inputs.push_back(input); + m_invs.push_back(product); + TRACE("newton", tout << "invs[" << k << "]: " << product << "\n";); + SASSERT(m().eq(m_inputs[k], input)); + // Compute newton's coefficient + polynomial_ref temp(pm.m_wrapper); + polynomial_ref aux_poly(pm.m_wrapper); + temp = m_vs.get(k-1); + for (int j = k - 2; j >= 0; j--) { + // temp <- temp*(input - m_inputs[j]) + vs[j] + m().sub(input, m_inputs[j], aux); + SASSERT(m().is_p_normalized(aux)); + aux_poly = pm.mul(aux, temp); + temp = pm.add(aux_poly, m_vs.get(j)); + SASSERT(pm.is_p_normalized(temp)); + } + // new vs <- (output - temp)*invs[sz] + aux_poly = pm.sub(output, temp); + SASSERT(pm.is_p_normalized(aux_poly)); + aux_poly = pm.mul(m_invs[sz], aux_poly); + SASSERT(pm.is_p_normalized(aux_poly)); + m_vs.push_back(aux_poly); + TRACE("newton", tout << "vs[" << k << "]: " << aux_poly << "\n";); + } + else { + m_inputs.push_back(input); + m_vs.push_back(const_cast(output)); + } + } + + // Convert newton form to standard form + void mk(var x, polynomial_ref & r) { + SASSERT(m().modular()); + polynomial_ref u(pm.m_wrapper); + polynomial_ref aux_poly(pm.m_wrapper); + int num = num_sample_points(); + int d = num - 1; + SASSERT(num > 0); + u = m_vs.get(d); + scoped_numeral c(m()); + for (int k = d - 1; k >= 0; k--) { + TRACE("newton", tout << "u: " << u << "\n";); + // create polynomial (x - inputs[k]) + m().set(c, m_inputs[k]); + m().neg(c); + numeral one(1); + aux_poly = pm.mk_linear(1, &one, &x, c); + TRACE("newton", tout << "(x - inputs[k]): " << aux_poly << "\n";); + // u <- u * (x - inputs[k]) + vs[k] + aux_poly = pm.mul(u, aux_poly); + u = pm.add(aux_poly, m_vs.get(k)); + } + TRACE("newton", tout << "result u: " << u << "\n";); + r = u; + } + }; + + /** + \brief Newton interpolation for multivariate polynomials. + Creates a polynomial on x of degree at most d with coefficients in Z[y1, ..., ym], + using d+1 sample points. + The sample points are store in the vectors inputs and outputs. Both must have size d+1. + + \pre manager must be configured in Zp (modular) mode. + We need this requeriment because we use the inverse operation. + */ + void newton_interpolation(var x, unsigned d, numeral const * inputs, polynomial * const * outputs, polynomial_ref & r) { + SASSERT(m().modular()); + newton_interpolator interpolator(*this); + for (unsigned i = 0; i <= d; i++) + interpolator.add(inputs[i], outputs[i]); + interpolator.mk(x, r); + } + + class newton_interpolator_vector { + imp * m_imp; + ptr_vector m_data; + public: + newton_interpolator_vector():m_imp(0) {} + + ~newton_interpolator_vector() { + flush(); + } + + void flush() { + unsigned sz = m_data.size(); + for (unsigned i = 0; i < sz; i++) + dealloc(m_data[i]); + m_data.reset(); + } + + void set_owner(imp * owner) { + SASSERT(m_imp == 0 || m_imp == owner); + m_imp = owner; + } + + newton_interpolator & operator[](unsigned idx) { + SASSERT(m_imp); + while (idx >= m_data.size()) { + m_data.push_back(alloc(newton_interpolator, *m_imp)); + } + return *(m_data[idx]); + } + }; + + /** + \brief Represents a polynomial skeleton of a multivariate polynomial Z[Y1, ..., Yn] + with coefficients in Z[X] + */ + struct skeleton { + struct entry { + monomial * m_monomial; // a monomial in Z[Y1, ..., Y1] + unsigned m_first_power_idx; // position (in m_powers) of the powers of X that are the coefficient of this monomial + unsigned m_num_powers; // size of the coefficient of this monomial. + entry(monomial * m, unsigned first_idx): + m_monomial(m), + m_first_power_idx(first_idx), + m_num_powers(1) { + } + unsigned num_powers() const { return m_num_powers; } + monomial * m() const { return m_monomial; } + }; + imp & pm; + var m_x; + svector m_entries; + unsigned_vector m_powers; + ptr_vector m_orig_monomials; + unsigned m_max_powers; // maximal number of powers associated with an entry + + skeleton(imp & _pm, polynomial * p, var x):pm(_pm), m_x(x) { + m_max_powers = 0; + ptr_buffer ms; + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + ms.push_back(p->m(i)); + } + std::sort(ms.begin(), ms.end(), lex_lt2(x)); + monomial * prev = 0; + for (unsigned i = 0; i < sz; i++) { + monomial * orig_m = ms[i]; + monomial * m; + unsigned k = orig_m->degree_of(x); + if (k > 0) + m = pm.div_x(orig_m, x); + else + m = orig_m; + if (m == prev) { + unsigned & num_powers = m_entries.back().m_num_powers; + num_powers++; + if (num_powers > m_max_powers) + m_max_powers = num_powers; + } + else { + prev = m; + pm.inc_ref(m); + m_entries.push_back(entry(m, m_powers.size())); + if (m_max_powers == 0) + m_max_powers = 1; + } + pm.inc_ref(orig_m); + m_orig_monomials.push_back(orig_m); + m_powers.push_back(k); + } + TRACE("skeleton", + tout << "x: x" << m_x << "\n"; + tout << "max: " << m_max_powers << "\n"; + tout << "p: "; p->display(tout, pm.m()); tout << "\n"; + tout << "skeleton: "; display(tout); tout << "\n";); + DEBUG_CODE({ + unsigned sz = m_entries.size(); + for (unsigned i = 1; i < sz; i++) { + SASSERT(lex_compare(m_entries[i-1].m_monomial, m_entries[i].m_monomial) < 0); + } + }); + } + + ~skeleton() { + unsigned sz = m_entries.size(); + for (unsigned i = 0; i < sz; i++) { + pm.dec_ref(m_entries[i].m_monomial); + } + sz = m_orig_monomials.size(); + for (unsigned i = 0; i < sz; i++) { + pm.dec_ref(m_orig_monomials[i]); + } + } + + unsigned get_entry_idx(monomial * m) { + unsigned sz = m_entries.size(); + for (unsigned i = 0; i < sz; i++) { + if (m_entries[i].m_monomial == m) + return i; + } + return UINT_MAX; + } + + unsigned num_entries() const { return m_entries.size(); } + + entry const & operator[](unsigned idx) const { return m_entries[idx]; } + + unsigned ith_power(entry const & e, unsigned i) const { SASSERT(i < e.m_num_powers); return m_powers[e.m_first_power_idx + i]; } + + monomial * ith_orig_monomial(entry const & e, unsigned i) const { SASSERT(i < e.m_num_powers); return m_orig_monomials[e.m_first_power_idx + i]; } + + unsigned max_num_powers() const { return m_max_powers; } + + void display(std::ostream & out) { + unsigned sz = m_entries.size(); + for (unsigned i = 0; i < sz; i++) { + entry & e = m_entries[i]; + if (i > 0) out << " "; + out << "("; + for (unsigned j = 0; j < e.m_num_powers; j++) { + if (j > 0) out << " "; + out << "x" << m_x << "^"; + out << m_powers[e.m_first_power_idx + j]; + } + out << ")*"; + e.m_monomial->display(out); + } + } + }; + + class sparse_interpolator { + skeleton * m_skeleton; + numeral_vector m_inputs; + numeral_vector m_outputs; + public: + sparse_interpolator(skeleton * sk):m_skeleton(sk) { + // reserve space output values associated with each entry + if (sk) { + unsigned sz = sk->num_entries(); + for (unsigned i = 0; i < sz; i++) { + unsigned num_powers = (*sk)[i].num_powers(); + for (unsigned j = 0; j < num_powers; j++) { + m_outputs.push_back(numeral()); + } + } + } + } + + ~sparse_interpolator() { + if (m_skeleton) { + numeral_manager & m = m_skeleton->pm.m(); + for (unsigned i = 0; i < m_inputs.size(); i++) + m.del(m_inputs[i]); + for (unsigned i = 0; i < m_outputs.size(); i++) + m.del(m_outputs[i]); + } + } + + void reset() { + numeral_manager & m = m_skeleton->pm.m(); + for (unsigned i = 0; i < m_inputs.size(); i++) { + m.del(m_inputs[i]); + } + m_inputs.reset(); + } + + bool ready() const { + return m_inputs.size() == m_skeleton->max_num_powers(); + } + + bool add(numeral const & in, polynomial const * q) { + SASSERT(m_skeleton); + SASSERT(m_inputs.size() < m_skeleton->max_num_powers()); + numeral_manager & m = m_skeleton->pm.m(); + unsigned input_idx = m_inputs.size(); + m_inputs.push_back(numeral()); + m.set(m_inputs.back(), in); + unsigned sz = q->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * mon = q->m(i); + unsigned entry_idx = m_skeleton->get_entry_idx(mon); + if (entry_idx == UINT_MAX) + return false; + skeleton::entry const & e = (*m_skeleton)[entry_idx]; + if (input_idx >= e.num_powers()) + continue; + unsigned output_idx = e.m_first_power_idx + input_idx; + m.set(m_outputs[output_idx], q->a(i)); + } + return true; + } + + bool mk(polynomial_ref & r) { + SASSERT(m_skeleton); + numeral_manager & m = m_skeleton->pm.m(); + scoped_numeral_vector cs(m); + scoped_numeral_vector new_as(m); + scoped_numeral_vector as(m); + ptr_buffer mons; + scoped_numeral aux(m); + linear_eq_solver solver(m); + unsigned sz = m_skeleton->num_entries(); + for (unsigned k = 0; k < sz; k++) { + skeleton::entry const & e = (*m_skeleton)[k]; + unsigned num_pws = e.num_powers(); + solver.resize(num_pws); + new_as.resize(num_pws); + for (unsigned i = 0; i < num_pws; i++) { + numeral & in = m_inputs[i]; + cs.reset(); + for (unsigned j = 0; j < num_pws; j++) { + m.power(in, m_skeleton->ith_power(e, j), aux); + cs.push_back(aux); + } + unsigned output_idx = e.m_first_power_idx + i; + TRACE("sparse_interpolator", tout << "adding new equation:\n"; + for (unsigned i = 0; i < num_pws; i++) { + tout << m.to_string(cs[i]) << " "; + } + tout << "\n";); + solver.add(i, cs.c_ptr(), m_outputs[output_idx]); + } + TRACE("sparse_interpolator", + tout << "find coefficients of:\n"; + for (unsigned i = 0; i < num_pws; i++) { + m_skeleton->ith_orig_monomial(e, i)->display(tout); tout << "\n"; + } + tout << "system of equations:\n"; + solver.display(tout);); + if (!solver.solve(new_as.c_ptr())) + return false; + for (unsigned i = 0; i < num_pws; i++) { + if (!m.is_zero(new_as[i])) { + as.push_back(new_as[i]); + mons.push_back(m_skeleton->ith_orig_monomial(e, i)); + } + } + } + r = m_skeleton->pm.mk_polynomial(as.size(), as.c_ptr(), mons.c_ptr()); + return true; + } + }; + + svector m_found_vars; + void vars(polynomial const * p, var_vector & xs) { + xs.reset(); + m_found_vars.reserve(num_vars(), false); + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + unsigned msz = m->size(); + for (unsigned j = 0; j < msz; j++) { + var x = m->get_var(j); + if (!m_found_vars[x]) { + m_found_vars[x] = true; + xs.push_back(x); + } + } + } + // reset m_found_vars + sz = xs.size(); + for (unsigned i = 0; i < sz; i++) + m_found_vars[xs[i]] = false; + } + + typedef sbuffer power_buffer; + typedef sbuffer unsigned_buffer; + typedef sbuffer var_buffer; + + /** + Store in pws the variables occuring in p and their (minimal or maximal) degrees. + */ + unsigned_vector m_var_degrees_tmp; + template + void var_degrees(polynomial const * p, power_buffer & pws) { + pws.reset(); + unsigned_vector & var2pos = m_var_degrees_tmp; + var2pos.reserve(num_vars(), UINT_MAX); + + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + unsigned msz = m->size(); + for (unsigned j = 0; j < msz; j++) { + var x = m->get_var(j); + unsigned k = m->degree(j); + unsigned pos = var2pos[x]; + if (pos == UINT_MAX) { + var2pos[x] = pws.size(); + pws.push_back(power(x, k)); + } + else if (Max && k > pws[pos].degree()) { + pws[pos].degree() = k; + } + else if (!Max && k < pws[pos].degree()) { + pws[pos].degree() = k; + } + } + } + + sz = pws.size(); + for (unsigned i = 0; i < sz; i++) { + SASSERT(var2pos[pws[i].get_var()] != UINT_MAX); + var2pos[pws[i].get_var()] = UINT_MAX; + } + } + + void var_max_degrees(polynomial const * p, power_buffer & pws) { + var_degrees(p, pws); + } + + void var_min_degrees(polynomial const * p, power_buffer & pws) { + var_degrees(p, pws); + } + + polynomial * coeff(polynomial const * p, var x, unsigned k) { + SASSERT(is_valid(x)); + SASSERT(m_cheap_som_buffer.empty()); + TRACE("coeff_bug", tout << "p: "; p->display(tout, m_manager); tout << "\nx: " << x << ", k: " << k << "\n";); + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + unsigned d = m->degree_of(x); + if (d == k) + m_cheap_som_buffer.add(p->a(i), div_x(m, x)); + } + return m_cheap_som_buffer.mk(); + } + + /** + Let p be of the form q_k(yvec)*x^k + ...+ q_0(yvec) + Store the polynomials q_k(yvec), ..., q_0(yvec) in the som_buffer_vector. + */ + void coeffs(polynomial const * p, var x, som_buffer_vector & cs) { + cs.set_owner(this); + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + unsigned d = m->degree_of(x); + som_buffer * c = cs[d]; + c->add(p->a(i), div_x(m, x)); + } + } + + /** + \brief Return a polynomial h that is the coefficient of x^k in p. + Store the reduct (p - h x^k) into \c reduct. + */ + polynomial * coeff(polynomial const * p, var x, unsigned k, polynomial_ref & reduct) { + SASSERT(is_valid(x)); + SASSERT(m_cheap_som_buffer.empty()); + SASSERT(m_cheap_som_buffer2.empty()); + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + unsigned d = m->degree_of(x); + if (d == k) + m_cheap_som_buffer.add(p->a(i), div_x(m, x)); + else + m_cheap_som_buffer2.add(p->a(i), m); + } + reduct = m_cheap_som_buffer2.mk(); + return m_cheap_som_buffer.mk(); + } + + /** + \brief Return true if the coefficient of x^k is just a constant. + Store it in c. + */ + bool const_coeff(polynomial const * p, var x, unsigned k, numeral & c) { + SASSERT(is_valid(x)); + m_manager.reset(c); + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + unsigned d = m->degree_of(x); + if (d == k) { + unsigned msz = m->size(); + if ((k > 0 && msz > 1) || (k == 0 && msz > 0)) + return false; + m_manager.set(c, p->a(i)); + } + } + return true; + } + + bool nonzero_const_coeff(polynomial const * p, var x, unsigned k) { + scoped_numeral c(m_manager); + return const_coeff(p, x, k, c) && !m_manager.is_zero(c); + } + + /** + \brief Extract the integer content of p. + */ + void ic(polynomial const * p, numeral & a) { + if (is_zero(p)) { + m_manager.reset(a); + return; + } + if (is_const(p)) { + m_manager.set(a, p->a(0)); + return; + } + m_manager.set(a, p->a(0)); + unsigned sz = p->size(); + for (unsigned i = 1; i < sz; i++) { + if (m_manager.is_one(a)) + return; + m_manager.gcd(a, p->a(i), a); + } + } + + /** + \brief Sum of the absolute values of the coefficients. + */ + void abs_norm(polynomial const * p, numeral & norm) { + m_manager.reset(norm); + scoped_numeral tmp(m_manager); + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; ++ i) { + m_manager.set(tmp, p->a(i)); + m_manager.abs(tmp); + m_manager.add(norm, tmp, norm); + } + } + + /** + \brief Arbitrary leading integer coefficient. + */ + numeral const & numeral_lc(polynomial const * p, var x) { + int sz = p->size(); + if (sz == 0) { + return m_zero_numeral; + } else { + return p->a(0); + } + } + + numeral const & numeral_tc(polynomial const * p) { + int sz = p->size(); + if (sz == 0) { + return m_zero_numeral; + } + else { + monomial * u = mk_unit(); + for (int i = 0; i < sz; ++ i) { + if (p->m(i) == u) + return p->a(i); + } + return m_zero_numeral; + } + } + + /** + \brief Extract the integer content of p. + p = a*c s.t. the GCD of the coefficients of c is one. + */ + void ic(polynomial const * p, numeral & a, polynomial_ref & c) { + if (is_zero(p)) { + m_manager.reset(a); + c = const_cast(p); + return; + } + if (is_const(p)) { + m_manager.set(a, p->a(0)); + c = mk_one(); + return; + } + unsigned sz = p->size(); + m_manager.gcd(sz, p->as(), a); + if (m_manager.is_one(a)) { + c = const_cast(p); + return; + } + m_cheap_som_buffer.reset(); + scoped_numeral ai(m_manager); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + m_manager.div(p->a(i), a, ai); + m_cheap_som_buffer.add_reset(ai, m); + } + c = m_cheap_som_buffer.mk(); + } + + // Flip the sign of p, if the leading monomial is negative + polynomial * flip_sign_if_lm_neg_core(polynomial const * p) { + if (is_zero(p)) + return const_cast(p); + unsigned glex_max_pos = p->graded_lex_max_pos(); + SASSERT(glex_max_pos != UINT_MAX); + if (m_manager.is_neg(p->a(glex_max_pos))) + return neg(p); + else + return const_cast(p); + } + + void flip_sign_if_lm_neg(polynomial_ref & p) { + p = flip_sign_if_lm_neg_core(p); + } + + /** + \brief Extract the integer content, content and primitive part of p with respect to + variable x. + */ + void iccp(polynomial const * p, var x, numeral & i, polynomial_ref & c, polynomial_ref & pp) { + TRACE("polynomial", tout << "iccp x" << x << "\n"; p->display(tout, m_manager); tout << "\n";); + if (is_zero(p)) { + m_manager.set(i, 0); + c = mk_one(); + pp = const_cast(p); + return; + } + if (is_const(p)) { + m_manager.set(i, p->a(0)); + c = mk_one(); + pp = mk_one(); + return; + } + unsigned d = degree(p, x); + if (d == 0) { + ic(p, i, c); + pp = mk_one(); + return; + } + // Apply filter and collect powers of x occuring in p + // The quick filter is the following: + // If p contains a monomial x^k and no monomial of the form m*x^k m != 1, then + // c = m_unit_poly + // To detect that we use a map (iccp_powers) from k to counters. + // We traverse p and update the map using the following rules: + // - found monomial x^k then iccp_powers[k]++; + // - found monomial m*x^k then iccp_powers[k]+=2; + // If after traversing p, there is a k s.t. iccp_powers[k] == 1, we know c == 1 + // We store iccp_powers the powers of x occuring in p. + sbuffer iccp_filter; + sbuffer iccp_powers; + iccp_filter.resize(d+1, 0); + iccp_powers.reset(); + for (unsigned j = 0; j <= d; j++) + iccp_filter[j] = 0; + unsigned sz = p->size(); + for (unsigned j = 0; j < sz; j++) { + monomial * m = p->m(j); + unsigned k = m->degree_of(x); + TRACE("polynomial", tout << "degree of x" << x << " at "; m->display(tout); tout << " is " << k << "\n";); + if (iccp_filter[k] == 0) + iccp_powers.push_back(k); + if (m->size() == (k > 0 ? 1 : 0)) + iccp_filter[k]++; + else + iccp_filter[k]+=2; + } + SASSERT(!iccp_powers.empty()); + unsigned num_powers = iccp_powers.size(); + for (unsigned j = 0; j < num_powers; j++) { + SASSERT(iccp_filter[iccp_powers[j]] > 0); + if (iccp_filter[iccp_powers[j]] == 1) { + ic(p, i, pp); + c = mk_one(); + return; + } + } + // Extract integer content + ic(p, i, pp); + TRACE("polynomial", tout << "p: "; p->display(tout, m_manager); tout << "\ni: " << m_manager.to_string(i) << "\npp: " << pp << "\n";); + // Compute c using the gcd of coeffs of x^k for k's in iccp_powers + polynomial_ref ci(pm()); + c = coeff(pp, x, iccp_powers[0]); + for (unsigned j = 1; j < num_powers; j++) { + ci = coeff(pp, x, iccp_powers[j]); + gcd(c, ci, c); + if (is_const(c)) { + c = mk_one(); + return; + } + } + SASSERT(!is_const(c)); + // make sure the sign of the leading monomial is positive + flip_sign_if_lm_neg(c); + TRACE("polynomial", tout << "pp: " << pp << "\nc: " << c << "\n";); + pp = exact_div(pp, c); + } + + void iccp(polynomial const * p, numeral & i, polynomial_ref & c, polynomial_ref & pp) { + iccp(p, max_var(p), i, c, pp); + } + + void pp(polynomial const * p, var x, polynomial_ref & pp) { + scoped_numeral i(m_manager); + polynomial_ref c(pm()); + iccp(p, x, i, c, pp); + } + + bool is_primitive(polynomial const * p, var x) { + scoped_numeral i(m_manager); + polynomial_ref c(pm()); + polynomial_ref pp(pm()); + iccp(p, x, i, c, pp); + return eq(p, pp); + } + + polynomial * lc(polynomial const * p, var x) { + return coeff(p, x, degree(p, x)); + } + + void gcd_prs(polynomial const * u, polynomial const * v, var x, polynomial_ref & r) { + TRACE("polynomial_gcd", tout << "gcd prs with x" << x << " for\nu: "; + u->display(tout, m_manager); tout << "\nv: "; v->display(tout, m_manager); tout << "\n";); + if (degree(u, x) < degree(v, x)) + std::swap(u, v); + scoped_numeral i_u(m_manager), i_v(m_manager); + polynomial_ref c_u(pm()), c_v(pm()); + polynomial_ref pp_u(pm()), pp_v(pm()); + scoped_numeral d_a(m_manager); + polynomial_ref d_r(pm()); + polynomial_ref g(pm()), h(pm()), rem(pm()), new_h(pm()); + + iccp(u, x, i_u, c_u, pp_u); + iccp(v, x, i_v, c_v, pp_v); + + gcd(c_u, c_v, d_r); + m_manager.gcd(i_u, i_v, d_a); + TRACE("polynomial_gcd_detail", + tout << "After GCD of the content\n"; + tout << "u: "; u->display(tout, m_manager); tout << "\n"; + tout << "v: "; v->display(tout, m_manager); tout << "\n"; + tout << "i_u: " << i_u << "\n"; + tout << "i_v: " << i_v << "\n"; + tout << "c_u: " << c_u << "\n"; + tout << "c_v: " << c_v << "\n"; + tout << "pp_u: " << pp_u << "\n"; + tout << "pp_v: " << pp_v << "\n"; + tout << "d_r: " << d_r << "\nd_a: " << d_a << "\n";); + + g = mk_one(); + h = mk_one(); + + unsigned counter = 0; + while (true) { + SASSERT(degree(pp_u, x) >= degree(pp_v, x)); + unsigned delta = degree(pp_u, x) - degree(pp_v, x); + TRACE("polynomial_gcd_detail", + tout << "iteration: " << counter << "\n"; + tout << "gcd loop\npp_u: " << pp_u << "\npp_v: " << pp_v << "\ndelta: " << delta << "\n";); + counter++; + exact_pseudo_remainder(pp_u, pp_v, x, rem); + if (is_zero(rem)) { + TRACE("polynomial", tout << "rem is zero...\npp_v: " << pp_v << "\n";); + flip_sign_if_lm_neg(pp_v); + pp(pp_v, x, r); + r = mul(d_a, d_r, r); + return; + } + if (is_const(rem)) { + TRACE("polynomial", tout << "rem is a constant: " << rem << "\nr: " << d_r << "\nd_a: " << d_a << "\n";); + r = mul(d_a, d_r); + return; + } + pp_u = pp_v; + // pp_v <- rem/g*h^{delta} + pp_v = exact_div(rem, g); + // delta is usually a small number, so I do not compute h^delta + for (unsigned i = 0; i < delta; i++) + pp_v = exact_div(pp_v, h); + g = lc(pp_u, x); + // h <- h^{1-delta}*g^{delta} + new_h = mk_one(); + for (unsigned i = 0; i < delta; i++) + new_h = mul(new_h, g); + if (delta > 1) { + for (unsigned i = 0; i < delta - 1; i++) + new_h = exact_div(new_h, h); + } + h = new_h; + } + } + + // Store in r <- gcd(content(u, x), v) + void gcd_content(polynomial const * u, var x, polynomial const * v, polynomial_ref & r) { + scoped_numeral i_u(m_manager); + polynomial_ref c_u(pm()); + polynomial_ref pp_u(pm()); + + iccp(u, x, i_u, c_u, pp_u); + c_u = mul(i_u, c_u); + gcd(c_u, v, r); + } + + // TODO: implement euclid algorithm when m_manager in Zp mode + void euclid_gcd(polynomial const * u, polynomial const * v, polynomial_ref & r) { + SASSERT(m().modular()); + CTRACE("mgcd", !is_univariate(u) || !is_univariate(v), + tout << "euclid_gcd, polynomials are not univariate\n"; u->display(tout, m()); tout << "\n"; v->display(tout, m()); tout << "\n";); + SASSERT(is_univariate(u)); + SASSERT(is_univariate(v)); + if (is_zero(u)) { + r = const_cast(v); + flip_sign_if_lm_neg(r); + return; + } + if (is_zero(v)) { + r = const_cast(u); + flip_sign_if_lm_neg(r); + return; + } + if (u == v) { + r = const_cast(u); + flip_sign_if_lm_neg(r); + return; + } + if (is_const(u) || is_const(v)) { + scoped_numeral i_u(m_manager), i_v(m_manager); + ic(v, i_v); + ic(u, i_u); + scoped_numeral a(m_manager); + m_manager.gcd(i_v, i_u, a); + r = mk_const(a); + return; + } + // Maybe map it to univariate case + gcd_prs(u, v, max_var(u), r); + } + + // Combine two different modular images using Chinese Remainder theorem + // The new bound is stored in b2 + void CRA_combine_images(polynomial const * C1, scoped_numeral const & b1, polynomial const * C2, scoped_numeral & b2, polynomial_ref & r) { + lex_sort(C1); + lex_sort(C2); + TRACE("CRA", tout << "C1: "; C1->display(tout, m()); tout << "\nC2: "; C2->display(tout, m()); tout << "\n";); + SASSERT(m_cheap_som_buffer.empty()); + SASSERT(!m().m().is_even(b1)); + SASSERT(!m().m().is_even(b2)); + cheap_som_buffer & R = m_cheap_som_buffer; + scoped_numeral inv1(m()); + scoped_numeral inv2(m()); + scoped_numeral g(m()); + m().gcd(b1, b2, inv1, inv2, g); + SASSERT(m().is_one(g)); + TRACE("CRA", tout << "b1: " << b1 << ", b2: " << b2 << ", inv1: " << inv1 << ", inv2: " << inv2 << "\n";); + // b1*inv1 + b2.inv2 = 1 + // inv1 is the inverse of b1 mod b2 + // inv2 is the inverse of b2 mod b1 + m().m().mod(inv1, b2, inv1); + m().m().mod(inv2, b1, inv2); + TRACE("CRA", tout << "inv1: " << inv1 << ", inv2: " << inv2 << "\n";); + scoped_numeral a1(m()); + scoped_numeral a2(m()); + m().mul(b2, inv2, a1); // a1 is the multiplicator for coefficients of C1 + m().mul(b1, inv1, a2); // a2 is the multiplicator for coefficients of C2 + TRACE("CRA", tout << "a1: " << a1 << ", a2: " << a2 << "\n";); + // new bound + scoped_numeral new_bound(m()); + m().mul(b1, b2, new_bound); + scoped_numeral lower(m()); + scoped_numeral upper(m()); + scoped_numeral new_a(m()), tmp1(m()), tmp2(m()), tmp3(m()); + m().div(new_bound, 2, upper); + m().set(lower, upper); + m().neg(lower); + TRACE("CRA", tout << "lower: " << lower << ", upper: " << upper << "\n";); + + #define ADD(A1, A2, M) { \ + m().mul(A1, a1, tmp1); \ + m().mul(A2, a2, tmp2); \ + m().add(tmp1, tmp2, tmp3); \ + m().m().mod(tmp3, new_bound, new_a); \ + if (m().gt(new_a, upper)) \ + m().sub(new_a, new_bound, new_a); \ + R.add(new_a, M); \ + } + + numeral zero(0); + unsigned i1 = 0; + unsigned i2 = 0; + unsigned sz1 = C1->size(); + unsigned sz2 = C2->size(); + while (true) { + if (i1 == sz1) { + while (i2 < sz2) { + TRACE("CRA", tout << "adding C2 rest\n";); + ADD(zero, C2->a(i2), C2->m(i2)); + i2++; + } + break; + } + if (i2 == sz2) { + while (i1 < sz1) { + TRACE("CRA", tout << "adding C1 rest\n";); + ADD(C1->a(i1), zero, C1->m(i1)); + i1++; + } + break; + } + monomial * m1 = C1->m(i1); + monomial * m2 = C2->m(i2); + int s = lex_compare(m1, m2); + if (s == 0) { + ADD(C1->a(i1), C2->a(i2), m1); + TRACE("CRA", + tout << "C1->a(i1): " << m().to_string(C1->a(i1)) << ", C2->a(i2): " << m().to_string(C2->a(i2)) << ", new_a: " << new_a << "\n"; + tout << "tmp1: " << tmp1 << ", tmp2: " << tmp2 << ", tmp3: " << tmp3 << "\n";); + i1++; + i2++; + } + else if (s > 0) { + TRACE("CRA", tout << "C1 mon biggerr, adding it...\n";); + ADD(C1->a(i1), zero, m1); + i1++; + } + else { + TRACE("CRA", tout << "C2 mon bigger, adding it...\n";); + ADD(zero, C2->a(i2), m2); + i2++; + } + } + m().set(b2, new_bound); + r = R.mk(); + } + + void uni_mod_gcd(polynomial const * u, polynomial const * v, polynomial_ref & r) { + TRACE("mgcd", tout << "univ_modular_gcd\nu: "; u->display(tout, m_manager); tout << "\nv: "; v->display(tout, m_manager); tout << "\n";); + SASSERT(!m().modular()); + SASSERT(is_univariate(u)); + SASSERT(!is_const(u) && !is_const(v)); + SASSERT(max_var(u) == max_var(v)); + var x = max_var(u); + scoped_numeral c_u(m()), c_v(m()); + polynomial_ref pp_u(pm()), pp_v(pm()); + ic(u, c_u, pp_u); + ic(v, c_v, pp_v); + + scoped_numeral d_a(m()); + m_manager.gcd(c_u, c_v, d_a); + + scoped_numeral lc_u(m()); + scoped_numeral lc_v(m()); + unsigned d_u = degree(pp_u, x); + unsigned d_v = degree(pp_v, x); + lc_u = univ_coeff(pp_u, d_u); + lc_v = univ_coeff(pp_v, d_v); + scoped_numeral lc_g(m()); + m().gcd(lc_u, lc_v, lc_g); + + polynomial_ref u_Zp(m_wrapper); + polynomial_ref v_Zp(m_wrapper); + + polynomial_ref C_star(m_wrapper); + scoped_numeral bound(m()); + polynomial_ref q(m_wrapper); + + polynomial_ref candidate(m_wrapper); + + scoped_numeral p(m()); + for (unsigned i = 0; i < NUM_BIG_PRIMES; i++) { + m().set(p, g_big_primes[i]); + TRACE("mgcd", tout << "trying prime: " << p << "\n";); + { + scoped_set_zp setZp(m_wrapper, p); + u_Zp = normalize(pp_u); + v_Zp = normalize(pp_v); + if (degree(u_Zp, x) < d_u) { + TRACE("mgcd", tout << "bad prime, leading coefficient vanished\n";); + continue; // bad prime + } + if (degree(v_Zp, x) < d_v) { + TRACE("mgcd", tout << "bad prime, leading coefficient vanished\n";); + continue; // bad prime + } + euclid_gcd(u_Zp, v_Zp, q); + // normalize so that lc_g is leading coefficient of q + q = mk_glex_monic(q); + scoped_numeral c(m()); + m().set(c, lc_g); + q = mul(c, q); + } + TRACE("mgcd", tout << "new q:\n" << q << "\n";); + if (is_const(q)) { + TRACE("mgcd", tout << "done, modular gcd is one\n";); + if (m().is_one(d_a)) + r = q; // GCD is one + else + r = mk_const(d_a); + return; + } + if (C_star.get() == 0) { + C_star = q; + m().set(bound, p); + } + else { + if (degree(q, x) < degree(C_star, x)) { + // discard accumulated image, it was affected by unlucky primes + TRACE("mgcd", tout << "discarding image\n";); + C_star = q; + m().set(bound, p); + } + else { + CRA_combine_images(q, p, C_star, bound, C_star); + TRACE("mgcd", tout << "new combined:\n" << C_star << "\n";); + } + } + pp(C_star, x, candidate); + TRACE("mgcd", tout << "candidate:\n" << candidate << "\n";); + scoped_numeral lc_candidate(m()); + lc_candidate = univ_coeff(candidate, degree(candidate, x)); + if (m().divides(lc_candidate, lc_g) && + divides(candidate, pp_u) && + divides(candidate, pp_v)) { + TRACE("mgcd", tout << "found GCD\n";); + r = mul(d_a, candidate); + flip_sign_if_lm_neg(r); + TRACE("mgcd", tout << "r: " << r << "\n";); + return; + } + } + // Oops, modular GCD failed, not enough primes + // fallback to prs + gcd_prs(u, v, x, r); + } + + typedef ref_buffer polynomial_ref_buffer; + + /** + Compute the content and primitive parts of p, when p is viewed as a multivariate polynomial Zp[y_1, ..., y_n] + with coefficients in Zp[x]. + */ + som_buffer_vector m_iccp_ZpX_buffers; + void iccp_ZpX(polynomial const * p, var x, numeral & ci, polynomial_ref & c, polynomial_ref & pp) { + SASSERT(m().modular()); + TRACE("mgcd_detail", tout << "iccp_ZpX, p: "; p->display(tout, m()); tout << "\nvar x" << x << "\n";); + if (is_zero(p)) { + TRACE("mgcd_detail", tout << "iccp_ZpX, p is zero\n";); + m_manager.set(ci, 0); + c = mk_one(); + pp = const_cast(p); + return; + } + if (is_const(p)) { + TRACE("mgcd_detail", tout << "iccp_ZpX, p is constant\n";); + m_manager.set(ci, p->a(0)); + c = mk_one(); + pp = mk_one(); + return; + } + unsigned d = degree(p, x); + if (d == 0) { + TRACE("mgcd_detail", tout << "iccp_ZpX, degree(p, x) == 0\n";); + ic(p, ci, pp); + c = mk_one(); + return; + } + // 1) traverse monomials of p, and mark the monomials that contain p, also compute the minimal degree of x in p. + ref_buffer no_x_ms(m_wrapper); // monomials that do not contains x + unsigned min_degree = UINT_MAX; // min degree of x in p + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + unsigned k = m->degree_of(x); + if (k == 0) { + // if m is not marked + if (m_m2pos.get(m) == UINT_MAX) { + no_x_ms.push_back(m); + m_m2pos.set(m, 1); // it is just a mark + } + } + if (k < min_degree) + min_degree = k; + } + SASSERT(min_degree == 0 || no_x_ms.empty()); + if (min_degree > 0) { + SASSERT(no_x_ms.empty()); + // nothing was marked. + // divide by x^min_degree + TRACE("mgcd_detail", tout << "iccp_ZpX, all monomials contain x" << x << ", dividing by x" << x << "^" << min_degree << "\n";); + polynomial_ref xmin(m_wrapper); + polynomial_ref new_p(m_wrapper); + xmin = mk_polynomial(x, min_degree); + new_p = exact_div(p, xmin); + iccp_ZpX(new_p, x, ci, c, pp); + c = mul(xmin, c); + return; + } + // 2) if for some marked monomial m (i.e., the ones that do not contain x), there is no monomial m*x^k in p, + // then c = 1 + unsigned num_marked = no_x_ms.size(); + unsigned num_unmarked = 0; + monomial_ref tmp_m(m_wrapper); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + unsigned k = m->degree_of(x); + if (k == 0) + continue; + tmp_m = div_x(m, x); + SASSERT(tmp_m != m); // since x is in m, but not in tmp_m + if (m_m2pos.get(tmp_m) == 1) { + num_unmarked++; + m_m2pos.reset(tmp_m); + SASSERT(m_m2pos.get(tmp_m) == UINT_MAX); + } + } + SASSERT(num_unmarked <= num_marked); + if (num_unmarked < num_marked) { + // reset remaining marks + for (unsigned i = 0; i < num_marked; i++) + m_m2pos.reset(no_x_ms[i]); + TRACE("mgcd_detail", tout << "iccp_ZpX, cheap case... invoking ic\n";); + ic(p, ci, pp); + c = mk_one(); + return; + } + // 3) expensive case + // Basic idea: separate a*m*x^k into a*x^k and m, put a*x^k into the som_buffer associated with m. + // The mapping between m is som_buffers is given by m_m2pos + + // Extract integer content + ic(p, ci, pp); + no_x_ms.reset(); + som_buffer_vector & som_buffers = m_iccp_ZpX_buffers; + som_buffers.set_owner(this); + for (unsigned i = 0; i < sz; i++) { + monomial * m = pp->m(i); + unsigned k = m->degree_of(x); + if (k != 0) { + tmp_m = div_x(m, x); + m = tmp_m.get(); + } + unsigned pos = m_m2pos.get(m); + if (pos == UINT_MAX) { + pos = no_x_ms.size(); + no_x_ms.push_back(m); + m_m2pos.set(m, pos); + } + som_buffer * som = som_buffers[pos]; + som->add(pp->a(i), mk_monomial(x, k)); + } + unsigned num_ms = no_x_ms.size(); + for (unsigned i = 0; i < num_ms; i++) + m_m2pos.reset(no_x_ms[i]); + SASSERT(num_ms > 0); + // Compute GCD of all som_buffers + polynomial_ref g(m_wrapper); + polynomial_ref new_g(m_wrapper); + g = som_buffers[0]->mk(); + for (unsigned i = 1; i < num_ms; i++) { + polynomial_ref a(m_wrapper); + a = som_buffers[i]->mk(); + SASSERT(is_univariate(a)); + euclid_gcd(g, a, new_g); + g = new_g; + if (is_const(g)) + break; + } + if (!is_const(g)) { + CTRACE("content_bug", !divides(g, pp), + tout << "GF(" << m().m().to_string(m().p()) << ")\n"; + tout << "pp: "; pp->display(tout, m()); tout << "\n"; tout << "var: x" << x << "\n"; + tout << "content: " << g << "\n";); + c = g; + pp = exact_div(pp, c); + } + else { + c = mk_one(); + } + som_buffers.reset(num_ms); + } + + // Return the leading coefficient (with respect to glex) of p when + // p is viewed as a multivariate polynomial Zp[y_1, ..., y_n] with coefficients in Zp[x]. + polynomial * lc_glex_ZpX(polynomial const * p, var x) { + // collect a*x^k of maximal monomial with respect to glex + m_som_buffer.reset(); + monomial_ref max_m(m_wrapper); + monomial_ref tmp_m(m_wrapper); + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + unsigned k = m->degree_of(x); + if (k != 0) { + tmp_m = div_x(m, x); + m = tmp_m.get(); + } + if (max_m == 0 || graded_lex_compare(m, max_m) > 0) { + // found new maximal monomial + m_som_buffer.reset(); + max_m = m; + m_som_buffer.add(p->a(i), mk_monomial(x, k)); + } + else if (max_m == m) { + // found another a*x^k of max_m + m_som_buffer.add(p->a(i), mk_monomial(x, k)); + } + } + SASSERT(!m_som_buffer.empty()); + TRACE("mgcd_detail", tout << "maximal monomial: "; max_m->display(tout); tout << "\n";); + return m_som_buffer.mk(); + } + + // Wrapper for iccp_ZpX + void primitive_ZpX(polynomial const * p, var x, polynomial_ref & pp) { + scoped_numeral ci(m()); + polynomial_ref c(m_wrapper); + iccp_ZpX(p, x, ci, c, pp); + } + + // select a new random value in GF(p) that is not in vals, and store it in r + void peek_fresh(scoped_numeral_vector const & vals, unsigned p, scoped_numeral & r) { + SASSERT(vals.size() < p); // otherwise we cant keep the fresh value + unsigned sz = vals.size(); + while (true) { + m().set(r, rand() % p); + // check if fresh value... + unsigned k = 0; + for (; k < sz; k++) { + if (m().eq(vals[k], r)) + break; + } + if (k == sz) + return; // value is fresh + } + } + + newton_interpolator_vector m_mgcd_iterpolators; + scoped_ptr_vector m_mgcd_skeletons; + + struct sparse_mgcd_failed {}; + + // Auxiliary recursive function used in multivariate modular GCD + void mod_gcd_rec(polynomial const * u, polynomial const * v, unsigned p, + unsigned idx, var_buffer const & vars, polynomial_ref & r) { + TRACE("mgcd", tout << "mod_gcd_rec\nu: "; u->display(tout, m_manager, true); tout << "\nv: "; v->display(tout, m_manager, true); tout << "\n";); + unsigned num_vars = vars.size(); + SASSERT(num_vars > 0); + if (idx == num_vars - 1) { + SASSERT(is_univariate(u)); + SASSERT(is_univariate(v)); + euclid_gcd(u, v, r); + TRACE("mgcd", tout << "mod_gcd_rec result: "; r->display(tout, m_manager, true); tout << "\n";); + return; + } + + var x = vars[idx]; + scoped_numeral ci_u(m()), ci_v(m()); + polynomial_ref c_u(m_wrapper), pp_u(m_wrapper), lc_u(m_wrapper); + polynomial_ref c_v(m_wrapper), pp_v(m_wrapper), lc_v(m_wrapper); + iccp_ZpX(u, x, ci_u, c_u, pp_u); + iccp_ZpX(v, x, ci_v, c_v, pp_v); + lc_u = lc_glex_ZpX(pp_u, x); + lc_v = lc_glex_ZpX(pp_v, x); + scoped_numeral ci_g(m()); + polynomial_ref c_g(m_wrapper); + polynomial_ref lc_g(m_wrapper); + TRACE("mgcd_detail", + tout << "idx: " << idx << "\n"; + tout << "x" << x << "\n"; + tout << "pp_u = "; pp_u->display(tout, m_manager, true); tout << "\n"; + tout << "pp_v = "; pp_v->display(tout, m_manager, true); tout << "\n"; + tout << "c_u = "; c_u->display(tout, m_manager, true); tout << "\n"; + tout << "c_v = "; c_v->display(tout, m_manager, true); tout << "\n"; + tout << "lc_u = "; lc_u->display(tout, m_manager, true); tout << "\n"; + tout << "lc_v = "; lc_v->display(tout, m_manager, true); tout << "\n"; + tout << "ci_u = " << ci_u << "\n"; + tout << "ci_v = " << ci_v << "\n";); + m().gcd(ci_u, ci_v, ci_g); + euclid_gcd(c_u, c_v, c_g); + euclid_gcd(lc_u, lc_v, lc_g); + TRACE("mgcd_detail", + tout << "c_g = "; c_g->display(tout, m_manager, true); tout << "\n"; + tout << "lc_g = "; lc_g->display(tout, m_manager, true); tout << "\n"; + tout << "ci_g = " << ci_g << "\n";); + + skeleton * sk = m_mgcd_skeletons[idx]; + + // use dense interpolation if skeleton is not available + newton_interpolator & interpolator = m_mgcd_iterpolators[idx]; + sparse_interpolator sinterpolator(sk); + + polynomial_ref u1(m_wrapper), v1(m_wrapper), q(m_wrapper); + scoped_numeral val(m()); + scoped_numeral lc_g_val(m()); + polynomial_ref H(m_wrapper), C(m_wrapper); + polynomial_ref lc_H(m_wrapper); + unsigned min_deg_q = UINT_MAX; + var next_x = vars[idx+1]; + unsigned counter = 0; + + for (;; counter++) { + while (true) { + peek_fresh(interpolator.inputs(), p, val); + // the selected value must satisfy lc_g(val) != 0 + univ_eval(lc_g, x, val, lc_g_val); + if (!m().is_zero(lc_g_val)) + break; + } + TRACE("mgcd", tout << "x" << x << " -> " << val << "\n";); + u1 = substitute(pp_u, 1, &x, &(val.get())); + v1 = substitute(pp_v, 1, &x, &(val.get())); + mod_gcd_rec(u1, v1, p, idx+1, vars, q); + q = mk_glex_monic(q); + q = mul(lc_g_val, q); + var q_var = max_var(q); + unsigned deg_q = q_var == null_var ? 0 : degree(q, q_var); + TRACE("mgcd_detail", tout << "counter: " << counter << "\nidx: " << idx << "\nq: " << q << "\ndeg_q: " << deg_q << "\nmin_deg_q: " << + min_deg_q << "\nnext_x: x" << next_x << "\nmax_var(q): " << q_var << "\n";); + if (deg_q < min_deg_q) { + TRACE("mgcd_detail", tout << "reseting...\n";); + counter = 0; + min_deg_q = deg_q; + // start from scratch + if (sk == 0) { + interpolator.reset(); + interpolator.add(val, q); + } + else { + sinterpolator.reset(); + if (!sinterpolator.add(val, q)) + throw sparse_mgcd_failed(); + } + } + else if (deg_q == min_deg_q) { + TRACE("mgcd_detail", tout << "adding sample point...\n";); + if (sk == 0) { + interpolator.add(val, q); + } + else { + if (!sinterpolator.add(val, q)) + throw sparse_mgcd_failed(); + } + } + else { + TRACE("mgcd", tout << "skipping q...\n";); + continue; + } + bool found_candidate = false; + if (sk == 0) { + SASSERT(interpolator.num_sample_points() > 0); + interpolator.mk(x, H); + TRACE("mgcd_detail", tout << "idx: " << idx << "\ncandidate H: " << H << "\n";); + lc_H = lc_glex_ZpX(H, x); + TRACE("mgcd_detail", tout << "idx: " << idx << "\nlc_H: " << lc_H << "\nlc_g: " << lc_g << "\n";); + if (eq(lc_H, lc_g)) { + found_candidate = true; + } + } + else { + if (sinterpolator.ready()) { + if (!sinterpolator.mk(H)) + throw sparse_mgcd_failed(); + found_candidate = true; + } + } + + bool done = false; + if (found_candidate) { + if (degree(H, x) > 0) + primitive_ZpX(H, x, C); + else + C = normalize(H); + TRACE("mgcd_detail", tout << "C: " << C << "\npp_u: " << pp_u << "\npp_v: " << pp_v << "\ndivides(C, pp_u): " << + divides(C, pp_u) << "\ndivides(C, pp_v): " << divides(C, pp_v) << "\n";); + if (divides(C, pp_u) && divides(C, pp_v)) { + r = mul(c_g, C); + r = mul(ci_g, r); + done = true; + } + else if (min_deg_q == 0) { + r = c_g; + r = mul(ci_g, r); + done = true; + } + else if (sk != 0) { + throw sparse_mgcd_failed(); + } + } + + if (done) { + TRACE("mgcd", tout << "idx: " << idx << "\nresult: " << r << "\n";); + if (sk == 0 && m_use_sparse_gcd) { + // save skeleton + skeleton * new_sk = alloc(skeleton, *this, H, x); + m_mgcd_skeletons.set(idx, new_sk); + } + return; + } + } + } + + // Multivariate modular GCD algorithm + void mod_gcd(polynomial const * u, polynomial const * v, + power_buffer const & u_var_degrees, power_buffer const & v_var_degrees, + polynomial_ref & r) { + m_mgcd_iterpolators.set_owner(this); + TRACE("mgcd", tout << "mod_gcd\nu: "; u->display(tout, m_manager, true); tout << "\nv: "; v->display(tout, m_manager, true); tout << "\n";); + TRACE("mgcd_call", tout << "mod_gcd\nu: "; u->display(tout, m_manager, true); tout << "\nv: "; v->display(tout, m_manager, true); tout << "\n";); + SASSERT(!m().modular()); + // u and v contain the same set of variables + SASSERT(u_var_degrees.size() == v_var_degrees.size()); + unsigned num_vars = u_var_degrees.size(); + SASSERT(num_vars > 1); // should use uni_mod_gcd if univariate + var_buffer vars; + power_buffer var_min_degrees; + for (unsigned i = 0; i < num_vars; i++) { + SASSERT(u_var_degrees[i].get_var() == v_var_degrees[i].get_var()); + var x = u_var_degrees[i].get_var(); + unsigned d = std::min(u_var_degrees[i].degree(), v_var_degrees[i].degree()); + var_min_degrees.push_back(power(x, d)); + } + std::sort(var_min_degrees.begin(), var_min_degrees.end(), power::lt_degree()); + m_mgcd_skeletons.reset(); + for (unsigned i = 0; i < num_vars; i++) { + vars.push_back(var_min_degrees[i].get_var()); + m_mgcd_skeletons.push_back(0); + } + + scoped_numeral c_u(m()), c_v(m()); + polynomial_ref pp_u(pm()), pp_v(pm()); + ic(u, c_u, pp_u); + ic(v, c_v, pp_v); + + scoped_numeral d_a(m()); + m_manager.gcd(c_u, c_v, d_a); + + unsigned mm_u_pos = pp_u->graded_lex_max_pos(); // position of the maximal monomial in u + unsigned mm_v_pos = pp_v->graded_lex_max_pos(); // position of the maximal monomial in v + scoped_numeral lc_u(m()); + scoped_numeral lc_v(m()); + lc_u = pp_u->a(mm_u_pos); + lc_v = pp_v->a(mm_v_pos); + scoped_numeral lc_g(m()); + m().gcd(lc_u, lc_v, lc_g); + + polynomial_ref u_Zp(m_wrapper); + polynomial_ref v_Zp(m_wrapper); + polynomial_ref C_star(m_wrapper); + scoped_numeral bound(m()); + polynomial_ref q(m_wrapper); + polynomial_ref candidate(m_wrapper); + scoped_numeral p(m()); + + for (unsigned i = 0; i < NUM_BIG_PRIMES; i++) { + m().set(p, g_big_primes[i]); + TRACE("mgcd", tout << "trying prime: " << p << "\n";); + { + scoped_set_zp setZp(m_wrapper, p); + u_Zp = normalize(pp_u); + if (u_Zp->size() != pp_u->size()) { + TRACE("mgcd", tout << "bad prime, coefficient(s) vanished\n";); + continue; // bad prime some monomial vanished + } + v_Zp = normalize(pp_v); + if (v_Zp->size() != pp_v->size()) { + TRACE("mgcd", tout << "bad prime, coefficient(s) vanished\n";); + continue; // bad prime some monomial vanished + } + TRACE("mgcd", tout << "u_Zp: " << u_Zp << "\nv_Zp: " << v_Zp << "\n";); + mod_gcd_rec(u_Zp, v_Zp, g_big_primes[i], 0, vars, q); + q = mk_glex_monic(q); + scoped_numeral c(m()); + m().set(c, lc_g); + q = mul(c, q); + } + TRACE("mgcd", tout << "new q:\n" << q << "\n";); + if (is_const(q)) { + TRACE("mgcd", tout << "done, modular gcd is one\n";); + r = mk_const(d_a); + return; + } + if (C_star.get() == 0) { + C_star = q; + m().set(bound, p); + } + else { + monomial * max_C_star = C_star->m(C_star->graded_lex_max_pos()); + monomial * max_q = q->m(q->graded_lex_max_pos()); + if (graded_lex_compare(max_q, max_C_star) < 0) { + // Discard accumulated image, it was affected by unlucky primes + // maximal monomial of q is smaller than maximal monomial of C_star + TRACE("mgcd", tout << "discarding image\n";); + C_star = q; + m().set(bound, p); + } + else { + CRA_combine_images(q, p, C_star, bound, C_star); + TRACE("mgcd", tout << "new combined:\n" << C_star << "\n";); + } + } + candidate = normalize(C_star); + TRACE("mgcd", tout << "candidate:\n" << candidate << "\n";); + scoped_numeral lc_candidate(m()); + lc_candidate = candidate->a(candidate->graded_lex_max_pos()); + if (m().divides(lc_candidate, lc_g) && + divides(candidate, pp_u) && + divides(candidate, pp_v)) { + TRACE("mgcd", tout << "found GCD\n";); + r = mul(d_a, candidate); + flip_sign_if_lm_neg(r); + TRACE("mgcd", tout << "r: " << r << "\n";); + return; + } + } + // Oops, modular GCD failed, not enough primes + // fallback to prs + gcd_prs(u, v, max_var(u), r); + } + + void gcd(polynomial const * u, polynomial const * v, polynomial_ref & r) { + power_buffer u_var_degrees; + power_buffer v_var_degrees; + TRACE("gcd_calls", tout << "gcd\nu: "; u->display(tout, m_manager); tout << "\nv: "; v->display(tout, m_manager); tout << "\n";); + TRACE("polynomial_gcd", + tout << "gcd\nu: "; u->display(tout, m_manager); tout << "\nv: "; v->display(tout, m_manager); + tout << "\nis_zero(u): " << is_zero(u) << ", is_const(u): " << is_const(u) << "\n"; + tout << "is_zero(v): " << is_zero(v) << ", is_const(v): " << is_const(v) << "\n"; + tout << "modular: " << m().modular() << "\n";); + if (is_zero(u)) { + r = const_cast(v); + flip_sign_if_lm_neg(r); + return; + } + if (is_zero(v)) { + r = const_cast(u); + flip_sign_if_lm_neg(r); + return; + } + if (u == v) { + r = const_cast(u); + flip_sign_if_lm_neg(r); + return; + } + if (is_const(u) || is_const(v)) { + scoped_numeral i_u(m_manager), i_v(m_manager); + ic(v, i_v); + ic(u, i_u); + scoped_numeral a(m_manager); + m_manager.gcd(i_v, i_u, a); + r = mk_const(a); + return; + } + + // Search for a variable x that occurs only in u or v. + var_max_degrees(u, u_var_degrees); std::sort(u_var_degrees.begin(), u_var_degrees.end(), power::lt_var()); + var_max_degrees(v, v_var_degrees); std::sort(v_var_degrees.begin(), v_var_degrees.end(), power::lt_var()); + + TRACE("polynomial_gcd", + tout << "u var info\n"; for (unsigned i = 0; i < u_var_degrees.size(); i++) tout << u_var_degrees[i] << " "; tout << "\n"; + tout << "v var info\n"; for (unsigned i = 0; i < v_var_degrees.size(); i++) tout << v_var_degrees[i] << " "; tout << "\n";); + var x = null_var; + bool u_found = false; + bool v_found = false; + unsigned i = 0; + unsigned u_sz = u_var_degrees.size(); + unsigned v_sz = v_var_degrees.size(); + unsigned sz = std::min(u_sz, v_sz); + for (; i < sz; i++) { + var xu = u_var_degrees[i].get_var(); + var xv = v_var_degrees[i].get_var(); + if (xu < xv) { + x = xu; + u_found = true; + break; + } + if (xu > xv) { + x = xv; + v_found = true; + break; + } + } + if (!u_found && !v_found && i < u_sz) { + x = u_var_degrees[i].get_var(); + u_found = true; + } + if (!u_found && !v_found && i < v_sz) { + x = v_var_degrees[i].get_var(); + v_found = true; + } + + if (u_found) { + // u has a variable x that v doesn't have. + // Thus, gcd(u, v) = gcd(content(u, x), v) + gcd_content(u, x, v, r); + return; + } + + if (v_found) { + // v has a variable x that u doesn't have. + // Thus, gcd(u, v) = gcd(u, content(v, x)) + gcd_content(v, x, u, r); + return; + } + + // TODO: + // Try to find a variable x that occurs linearly in u or v + // In this case, the GCD is linear or constant in x. + // Assume x occurs linearly in u. Then, + // gcd(u, v) = gcd(content(u, x), content(v, x)) if pp(u, x) does not divide pp(v, x) + // gcd(u, v) = gcd(content(u, x), content(v, x))*pp(u,x) if pp(u, x) divides pp(v, x) + // + + // select variable with minimal degree + x = u_var_degrees[sz - 1].get_var(); // give preference to maximal variable + SASSERT(u_var_degrees[sz - 1].get_var() == v_var_degrees[sz - 1].get_var()); + SASSERT(max_var(u) == max_var(v)); + SASSERT(max_var(u) == x); +#if 0 + unsigned min_k = std::max(m_u_var_degrees[sz - 1].degree(), m_v_var_degrees[sz - 1].degree()); + unsigned max_var_bias = 2; // the basic procedures are optimized for operating on the maximal variable. So, we have a bias to select the maximal one + if (min_k > max_var_bias) { + min_k -= max_var_bias; + i = sz - 1; + while (i > 0) { + --i; + SASSERT(m_u_var_degrees[i].get_var() == m_v_var_degrees[i].get_var()); + unsigned k = std::max(m_u_var_degrees[i].degree(), m_v_var_degrees[i].degree()); + if (k < min_k) { + x = m_u_var_degrees[i].get_var(); + min_k = k; + } + } + } +#endif + if (!m().modular() && !m_use_prs_gcd) { + SASSERT(max_var(u) == max_var(v)); + if (is_univariate(u)) { + SASSERT(is_univariate(v)); + uni_mod_gcd(u, v, r); + } + else { + try { + #ifdef Z3DEBUG + polynomial_ref orig_u(m_wrapper); + polynomial_ref orig_v(m_wrapper); + if (is_debug_enabled("mgcd_check")) { + orig_u = const_cast(u); + orig_v = const_cast(v); + } + #endif + + mod_gcd(u, v, u_var_degrees, v_var_degrees, r); + + #ifdef Z3DEBUG + if (is_debug_enabled("mgcd_check")) { + polynomial_ref r2(m_wrapper); + flet use_prs(m_use_prs_gcd, false); + gcd_prs(orig_u, orig_v, x, r2); + CTRACE("mgcd_bug", !eq(r, r2), tout << "u: " << orig_u << "\nv: " << orig_v << "\nr1: " << r << "\nr2: " << r2 << "\n";); + SASSERT(eq(r, r2)); + } + #endif + } + catch (sparse_mgcd_failed) { + flet use_prs(m_use_prs_gcd, false); + gcd_prs(u, v, x, r); + } + } + } + else { + gcd_prs(u, v, x, r); + } + } + + monomial * derivative(monomial const * m, var x) { + return mm().derivative(m, x); + } + + polynomial * derivative(polynomial const * p, var x) { + SASSERT(is_valid(x)); + SASSERT(m_cheap_som_buffer.empty()); + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + unsigned d = m->degree_of(x); + TRACE("polynomial", m->display(tout); tout << " degree_of x" << x << ": " << d << "\n";); + if (d > 0) { + scoped_numeral n(m_manager); + m_manager.set(n, d); + scoped_numeral a(m_manager); + m_manager.mul(p->a(i), n, a); + m_cheap_som_buffer.add_reset(a, derivative(m, x)); + } + } + return m_cheap_som_buffer.mk(); + } + + void square_free(polynomial const * p, polynomial_ref & r) { + if (is_zero(p)) { + r = mk_zero(); + return; + } + if (is_const(p)) { + r = const_cast(p); + return; + } + + var x = max_var(p); + scoped_numeral i(m_manager); + polynomial_ref c(pm()), pp(pm()); + iccp(p, x, i, c, pp); + polynomial_ref sqf_c(pm()); + square_free(c, sqf_c); + + polynomial_ref pp_prime(pm()); + pp_prime = derivative(pp, x); + polynomial_ref g(pm()); + gcd(pp, pp_prime, g); + if (is_const(g)) { + if (eq(sqf_c, c)) { + r = const_cast(p); + return; + } + } + else { + pp = exact_div(pp, g); + } + r = mul(i, sqf_c); + r = mul(r, pp); + } + + bool is_square_free(polynomial const * p) { + polynomial_ref r(pm()); + square_free(p, r); + SASSERT(!eq(p, r) || p == r.get()); // this is a property of the square_free procedure + return p == r.get(); + } + + void square_free(polynomial const * p, var x, polynomial_ref & r) { + if (is_zero(p)) { + r = mk_zero(); + return; + } + if (is_const(p)) { + r = const_cast(p); + return; + } + + polynomial_ref p_prime(pm()); + p_prime = derivative(p, x); + polynomial_ref g(pm()); + gcd(p, p_prime, g); + if (is_const(g)) { + r = const_cast(p); + } + else { + r = exact_div(p, g); + } + } + + bool is_square_free(polynomial const * p, var x) { + polynomial_ref r(pm()); + square_free(p, x, r); + SASSERT(!eq(p, r) || p == r.get()); // this is a property of the square_free procedure + return p == r.get(); + } + + void pw(polynomial const * p, unsigned k, polynomial_ref & r) { + if (k == 0) { + r = mk_one(); + return; + } + if (k == 1) { + r = const_cast(p); + return; + } + polynomial_ref result(pm()); + result = const_cast(p); + for (unsigned i = 1; i < k; i++) + result = mul(result, const_cast(p)); + r = result; +#if 0 + SASSERT(k >= 2); + unsigned mask = 1; + polynomial_ref p2(pm()); + p2 = const_cast(p); + r = mk_one(); + while (true) { + if (mask & k) + r = mul(r, p2); + mask = mask << 1; + if (mask > k) + return; + p2 = mul(p2, p2); + } +#endif + } + + bool eq(polynomial const * p1, polynomial const * p2) { + if (p1 == p2) + return true; + unsigned sz1 = p1->size(); + unsigned sz2 = p2->size(); + if (sz1 != sz2) + return false; + if (sz1 == 0) + return true; + if (max_var(p1) != max_var(p2)) + return false; + m_m2pos.set(p1); + for (unsigned i = 0; i < sz2; i++) { + unsigned pos1 = m_m2pos.get(p2->m(i)); + if (pos1 == UINT_MAX || !m_manager.eq(p1->a(pos1), p2->a(i))) { + m_m2pos.reset(p1); + return false; + } + } + m_m2pos.reset(p1); + return true; + } + + polynomial * compose_1_div_x(polynomial const * p) { + SASSERT(is_univariate(p)); + if (is_const(p)) + return const_cast(p); + SASSERT(m_cheap_som_buffer.empty()); + var x = max_var(p); + unsigned n = degree(p, x); + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + SASSERT(m->size() <= 1); + monomial * new_m = mk_monomial(x, n - m->degree_of(x)); + m_cheap_som_buffer.add(p->a(i), new_m); + } + return m_cheap_som_buffer.mk(); + } + + void push_power(sbuffer & pws, var x, unsigned d) { + if (d > 0) + pws.push_back(power(x, d)); + } + + polynomial * compose_x_div_y(polynomial const * p, var y) { + SASSERT(is_univariate(p)); + SASSERT(max_var(p) != y); + if (is_const(p)) + return const_cast(p); + SASSERT(m_cheap_som_buffer.empty()); + var x = max_var(p); + unsigned n = degree(p, x); + unsigned sz = p->size(); + sbuffer pws; + for (unsigned i = 0; i < sz; i++) { + unsigned k = p->m(i)->degree_of(x); + pws.reset(); + if (x < y) { + push_power(pws, x, k); + push_power(pws, y, n - k); + } + else { + push_power(pws, y, n - k); + push_power(pws, x, k); + } + monomial * new_m = mk_monomial(pws.size(), pws.c_ptr()); + m_cheap_som_buffer.add(p->a(i), new_m); + } + return m_cheap_som_buffer.mk(); + } + + polynomial * compose_y(polynomial const * p, var y) { + SASSERT(is_valid(y)); + SASSERT(is_univariate(p)); + if (y == max_var(p) || is_const(p)) + return const_cast(p); + SASSERT(m_cheap_som_buffer.empty()); + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + SASSERT(m->size() <= 1); + monomial * new_m; + if (m->size() == 0) + new_m = m; + else + new_m = mk_monomial(y, m->degree(0)); + m_cheap_som_buffer.add(p->a(i), new_m); + } + return m_cheap_som_buffer.mk(); + } + + polynomial * compose_minus_x(polynomial const * p) { + SASSERT(is_univariate(p)); + if (is_const(p)) + return const_cast(p); + SASSERT(m_cheap_som_buffer.empty()); + scoped_numeral a(m_manager); + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + if (m->total_degree() % 2 == 0) { + m_cheap_som_buffer.add(p->a(i), p->m(i)); + } + else { + m_manager.set(a, p->a(i)); + m_manager.neg(a); + m_cheap_som_buffer.add(a, p->m(i)); + } + } + return m_cheap_som_buffer.mk(); + } + + /** + \brief Store the positions (in m_degree2pos) of the monomials of an univariate polynomial. + */ + void save_degree2pos(polynomial const * p) { + SASSERT(is_univariate(p)); + var x = max_var(p); + unsigned n = degree(p, x); + m_degree2pos.reserve(n+1, UINT_MAX); + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + SASSERT(m->size() <= 1); + SASSERT(m_degree2pos[m->total_degree()] == UINT_MAX); + m_degree2pos[m->total_degree()] = i; + } + } + + /** + \brief Undo the modifications in m_degree2pos performed by save_degree2pos. + */ + void reset_degree2pos(polynomial const * p) { + SASSERT(is_univariate(p)); + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + SASSERT(m->size() <= 1); + SASSERT(m_degree2pos[m->total_degree()] == i); + m_degree2pos[m->total_degree()] = UINT_MAX; + } + } + + /** + \brief Given an univariate polynomial p(x) and a polynomial q(y_1, ..., y_n), + return a polynomial r(y_1, ..., y_n) = p(q(y_1, ..., y_n)). + */ + void compose(polynomial const * p, polynomial const * q, polynomial_ref & r) { + SASSERT(is_univariate(p)); + if (is_const(p)) { + r = const_cast(p); + return; + } + var x = max_var(p); + unsigned d = degree(p, x); + save_degree2pos(p); + scoped_numeral a(m()); + m_manager.set(a, p->a(m_degree2pos[d])); + r = mk_const(a); + for (unsigned i = 1; i <= d; i++) { + unsigned pos = m_degree2pos[d-i]; + if (pos != UINT_MAX) + m_manager.set(a, p->a(pos)); + else + m_manager.reset(a); + r = muladd(q, r, a); + } + reset_degree2pos(p); + } + + polynomial * mk_x_minus_y(var x, var y) { + numeral zero(0); + numeral one(1); + numeral minus_one; // It is not safe to initialize with -1 when numeral_manager is GF_2 + m_manager.set(minus_one, -1); + numeral as[2] = { one, minus_one }; + var xs[2] = { x, y }; + return mk_linear(2, as, xs, zero); + } + + void compose_x_minus_y(polynomial const * p, var y, polynomial_ref & r) { + SASSERT(is_valid(y)); + SASSERT(is_univariate(p)); + var x = max_var(p); + if (y == max_var(p)) { + r = coeff(p, x, 0); + return; + } + polynomial_ref x_minus_y(pm()); + x_minus_y = mk_x_minus_y(x, y); + return compose(p, x_minus_y, r); + } + + polynomial * mk_x_plus_y(var x, var y) { + numeral zero(0); + numeral one(1); + numeral as[2] = { one, one }; + var xs[2] = { x, y }; + return mk_linear(2, as, xs, zero); + } + + void compose_x_plus_y(polynomial const * p, var y, polynomial_ref & r) { + SASSERT(is_valid(y)); + SASSERT(is_univariate(p)); + var x = max_var(p); + polynomial_ref x_plus_y(pm()); + x_plus_y = mk_x_plus_y(x, y); + return compose(p, x_plus_y, r); + } + + // Return the polynomial x - c + polynomial * mk_x_minus_c(var x, numeral const & c) { + numeral as[2]; + m_manager.set(as[0], c); + m_manager.set(as[1], 1); + m_manager.neg(as[0]); + polynomial * p = mk_univariate(x, 1, as); + TRACE("polynomial", tout << "x - c: "; p->display(tout, m_manager); tout << "\n";); + m_manager.del(as[0]); + m_manager.del(as[1]); + return p; + } + + void compose_x_minus_c(polynomial const * p, numeral const & c, polynomial_ref & r) { + SASSERT(is_univariate(p)); + if (m_manager.is_zero(c)) { + r = const_cast(p); + return; + } + var x = max_var(p); + polynomial_ref x_minus_c(pm()); + x_minus_c = mk_x_minus_c(x, c); + return compose(p, x_minus_c, r); + } + + /** + \brief Template for computing several variations of pseudo division algorithm. + If degree(p) < degree(q) --> Q = m_zero, d = 0, R = p + + The following property is guaranteed by this method + + l(q)^d * p = Q * q + R + + where l(q) is coeff(q, x, degree(q, x)) + + Possible configurations: + Exact_d == true, then d = degree(p) - degree(q) + 1. + If Exact_d == false, then d <= degree(p) - degree(q) + 1. + + If Quotient == false, Q is not computed. + + If ModD == true, then x2d must be different from 0. + Moreover, p and q are assumed to be normalized modulo x2d. + For all x, x2d->degree(x) > 0 implies degree(p, x) < x2d->degree(x) and degree(q, x) < x2d->degree(x) + Moreover, the division algorithm will compute Q and R modulo x2d. + */ + template + void pseudo_division_core(polynomial const * p, polynomial const * q, var x, unsigned & d, polynomial_ref & Q, polynomial_ref & R, + var2degree const * x2d = 0) { + SASSERT(is_valid(x)); + SASSERT(!ModD || x2d != 0); + TRACE("polynomial", tout << "pseudo_division\np: "; p->display(tout, m_manager); + tout << "\nq: "; q->display(tout, m_manager); tout << "\nx: " << x << "\n";); + polynomial * A = const_cast(p); + polynomial * B = const_cast(q); + unsigned deg_A = degree(A, x); + unsigned deg_B = degree(B, x); + if (deg_B == 0) { + R = m_zero; + if (Quotient) { + if (Exact_d) { + d = deg_A /* - deg_B */ + 1; + // Since degree(B) = 0, lc(B) == B + // lc(B)^d * A = Q * B + R --> (using R == 0, lc(B) == B) + // B^d * A = Q * B + // Thus, Q = A * B^{d-1} + if (d == 1) { + Q = A; + return; + } + polynomial_ref Bdm1(pm()); + pw(B, d - 1, Bdm1); + Q = mul(A, Bdm1); + if (ModD) + Q = mod_d(Q, *x2d); + } + else { + d = 1; + Q = A; + } + } + return; + } + if (deg_B > deg_A) { + Q = m_zero; + R = A; + d = 0; + } + scoped_numeral minus_a(m_manager); + polynomial_ref l_B(pm()); // leading coefficient of B (that is, coefficient of x^(deg_B)) + polynomial_ref r_B(pm()); // reduct of B (that is, B without leading coefficient) + l_B = coeff(B, x, deg_B, r_B); + d = 0; + R = A; + Q = m_zero; + while (true) { + checkpoint(); + TRACE("polynomial", + tout << "A: "; A->display(tout, m_manager); tout << "\n"; + tout << "B: "; B->display(tout, m_manager); tout << "\n"; + tout << "l_B: "; l_B->display(tout, m_manager); tout << "\n"; + tout << "r_B: "; r_B->display(tout, m_manager); tout << "\n"; + tout << "Q: "; Q->display(tout, m_manager); tout << "\n"; + tout << "R: "; R->display(tout, m_manager); tout << "\n"; + tout << "d: " << d << "\n";); + unsigned deg_R = degree(R, x); + if (deg_B > deg_R) { + if (Exact_d) { + // Adjust Q and R + unsigned exact_d = deg_A - deg_B + 1; + SASSERT(d <= exact_d); + if (d < exact_d) { + unsigned e = exact_d - d; + polynomial_ref l_B_e(pm()); + pw(l_B, e, l_B_e); + TRACE("polynomial", tout << "l_B_e: " << l_B_e << "\n";); + if (Quotient) { + Q = mul(l_B_e, Q); + if (ModD) + Q = mod_d(Q, *x2d); + } + R = mul(l_B_e, R); + if (ModD) + R = mod_d(R, *x2d); + } + } + return; + } + // S <- l_R * x^(deg_R - deg_B) + // R <- l_B * R - S * B + // Note that the goal is to cancel x^deg_R in R. + // m_som_buffer will contain the new monomials of R. + m_som_buffer.reset(); + // We can accomplish that by traversing the current R, and + // - For each monomial a * m * x^deg_R --> m_som_buffer.addmul(-a, m * x^(deg_R - deg_B), r_B) + // Note that m*x^(deg_R - deg_B) is div_x_k(m*x^deg_R, deg_B) + // - For other monomials a*m, ---> m_som_buffer.addmul(a, m, l_B) + // Note that, with this trick and don't need to create the temporary polynomials l_B * R and S * B + // + // If the quotient needs to be computed, we have that + // S <- l_R * x^(deg_R - deg_B) + // Q <- l_B * Q + S + // The new monomials of Q are stored in m_som_buffer2 + // When traversing R, for each monomial a*m*x^deg_R we add m_som_buffer2.add(a, m*x^(deg_R - deg_B)) + m_som_buffer2.reset(); + // + unsigned sz = R->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m = R->m(i); + numeral const & a = R->a(i); + if (m->degree_of(x) == deg_R) { + monomial_ref m_prime(pm()); + m_prime = div_x_k(m, x, deg_B); + CTRACE("polynomial", m->degree_of(x) != deg_R - deg_B, + tout << "deg_R: " << deg_R << ", deg_B: " << deg_B << ", x: " << x << "\n"; + m->display(tout); tout << ", "; m_prime->display(tout); tout << "\n";); + SASSERT(m->degree_of(x) == deg_R); + SASSERT(m_prime->degree_of(x) == deg_R - deg_B); + if (Quotient) { + m_som_buffer2.add(a, m_prime); + } + m_manager.set(minus_a, a); + m_manager.neg(minus_a); + m_som_buffer.addmul(minus_a, m_prime, r_B); + } + else { + m_som_buffer.addmul(a, m, l_B); + } + } + if (ModD) + m_som_buffer.mod_d(*x2d); + R = m_som_buffer.mk(); + if (Quotient) { + // m_som_buffer2 contains new monomials of Q <- l_B Q + S + // We have already copied S to m_som_buffer2. + // To add l_B * Q, we just traverse Q executing addmul(Q->a(i), Q->m(i), l_B) + unsigned sz = Q->size(); + for (unsigned i = 0; i < sz; i++) { + m_som_buffer2.addmul(Q->a(i), Q->m(i), l_B); + } + if (ModD) + m_som_buffer2.mod_d(*x2d); + Q = m_som_buffer2.mk(); + } + d++; + } + } + + void exact_pseudo_remainder(polynomial const * p, polynomial const * q, var x, polynomial_ref & R) { + unsigned d; + polynomial_ref Q(pm()); + pseudo_division_core(p, q, x, d, Q, R); + } + + void pseudo_remainder(polynomial const * p, polynomial const * q, var x, unsigned & d, polynomial_ref & R) { +#ifdef Z3DEBUG + polynomial_ref old_p(pm()); + polynomial_ref old_q(pm()); + old_p = const_cast(p); // R may be aliasing p and q + old_q = const_cast(q); + polynomial_ref Q(pm()); + pseudo_division_core(p, q, x, d, Q, R); + // debugging code + // check if lc(q)^d * p = Q * q + R + polynomial_ref l(pm()); + l = coeff(old_q, x, degree(q, x)); + polynomial_ref ld(pm()); + pw(l, d, ld); + polynomial_ref lhs(pm()); + lhs = mul(ld, old_p); + polynomial_ref rhs(pm()); + rhs = mul(Q, old_q); + rhs = add(rhs, R); + bool is_eq = eq(lhs, rhs); + TRACE("pseudo_remainder", + tout << "pseudo_division bug\n"; + tout << "p: "; old_p->display(tout, m_manager); tout << "\n"; + tout << "q: "; old_q->display(tout, m_manager); tout << "\n"; + tout << "Q: " << Q << "\nR: " << R << "\n"; + tout << "l^d: " << ld << "\nlhs: " << lhs << "\nrhs: " << rhs << "\n";); + SASSERT(is_eq); +#else + polynomial_ref Q(pm()); + pseudo_division_core(p, q, x, d, Q, R); +#endif + } + + void exact_pseudo_division(polynomial const * p, polynomial const * q, var x, polynomial_ref & Q, polynomial_ref & R) { + unsigned d; + pseudo_division_core(p, q, x, d, Q, R); + } + + void pseudo_division(polynomial const * p, polynomial const * q, var x, unsigned & d, polynomial_ref & Q, polynomial_ref & R) { + pseudo_division_core(p, q, x, d, Q, R); + } + + polynomial * exact_div(polynomial const * p, numeral const & c) { + SASSERT(!m().is_zero(c)); + + som_buffer & R = m_som_buffer; + R.reset(); + + numeral tmp; + + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; ++ i) { + SASSERT(m().divides(c, p->a(i))); + m().div(p->a(i), c, tmp); + if (!m().is_zero(tmp)) { + R.add(tmp, p->m(i)); + } + } + + m().del(tmp); + + return R.mk(); + } + + polynomial * exact_div(polynomial const * p, polynomial const * q) { + TRACE("polynomial", tout << "exact division\np: "; p->display(tout, m_manager); tout << "\nq: "; q->display(tout, m_manager); tout << "\n";); + if (is_zero(p)) + return const_cast(p); + SASSERT(!is_zero(q)); + m_som_buffer.reset(); + m_som_buffer2.reset(); + som_buffer & R = m_som_buffer; + som_buffer & C = m_som_buffer2; + R.add(p); + unsigned max_q = q->graded_lex_max_pos(); + monomial * m_q = q->m(max_q); + numeral const & a_q = q->a(max_q); + monomial_ref m_r_q_ref(pm()); + scoped_numeral a_r_q(m_manager); + while (true) { + checkpoint(); + unsigned max_R = R.graded_lex_max_pos(); + if (max_R == UINT_MAX) { + // R is empty + R.reset(); + return C.mk(); + } + monomial const * m_r = R.m(max_R); + numeral const & a_r = R.a(max_R); + monomial * m_r_q = 0; + bool q_div_r = div(m_r, m_q, m_r_q); + TRACE("polynomial", tout << "m_r: "; m_r->display(tout); tout << "\nm_q: "; m_q->display(tout); tout << "\n"; + if (m_r_q) { tout << "m_r_q: "; m_r_q->display(tout); tout << "\n"; }); + SASSERT(q_div_r); + m_r_q_ref = m_r_q; + m_manager.div(a_r, a_q, a_r_q); + C.add(a_r_q, m_r_q); // C <- C + (a_r/a_q)*(m_r/m_q) + m_manager.neg(a_r_q); + R.addmul(a_r_q, m_r_q, q); // R <- R - (a_r/a_q)*(m_r/m_q)*Q + } + } + + // Return true if q divides p. + bool divides(polynomial const * q, polynomial const * p) { + TRACE("polynomial", tout << "divides\nq: "; q->display(tout, m_manager); tout << "\np: "; p->display(tout, m_manager); tout << "\n";); + TRACE("divides", tout << "divides\nq: "; q->display(tout, m_manager); tout << "\np: "; p->display(tout, m_manager); tout << "\n";); + if (is_zero(p)) + return true; + SASSERT(!is_zero(q)); + m_som_buffer.reset(); + m_som_buffer2.reset(); + som_buffer & R = m_som_buffer; + R.add(p); + unsigned max_q = q->graded_lex_max_pos(); + monomial * m_q = q->m(max_q); + numeral const & a_q = q->a(max_q); + monomial_ref m_r_q_ref(pm()); + scoped_numeral a_r_q(m_manager); + while (true) { + checkpoint(); + unsigned max_R = R.graded_lex_max_pos(); + if (max_R == UINT_MAX) { + // R is empty + return true; + } + monomial const * m_r = R.m(max_R); + numeral const & a_r = R.a(max_R); + monomial * m_r_q = 0; + bool q_div_r = div(m_r, m_q, m_r_q); + m_r_q_ref = m_r_q; + TRACE("polynomial", tout << "m_r: "; m_r->display(tout); tout << "\nm_q: "; m_q->display(tout); tout << "\n"; + if (m_r_q) { tout << "m_r_q: "; m_r_q->display(tout); tout << "\n"; }); + if (!q_div_r) + return false; + if (!m_manager.divides(a_q, a_r)) + return false; + m_manager.div(a_r, a_q, a_r_q); + m_manager.neg(a_r_q); + R.addmul(a_r_q, m_r_q, q); // R <- R - (a_r/a_q)*(m_r/m_q)*Q + } + } + + void quasi_resultant(polynomial const * p, polynomial const * q, var x, polynomial_ref & r) { + // For h_0 = p and h_1 = q, we compute the following sequence + // using the pseudo remainder procedure + // + // l(h_1)^d_1 * h_0 = q_1 * h_1 + h_2 + // l(h_2)^d_2 * h_1 = q_2 * h_2 + h_3 + // l(h_3)^d_3 * h_2 = q_3 * h_3 + h_4 + // ... + // l(h_n)^d_n * h_{n-1} = q_n * h_n + h_{n+1} + // + // where + // degree(h_i, x) > 0 for all i in [0, n], and + // degree(h_{n+1}, x) == 0 + // + // l(h_i) is the leading coefficient of h_i with respect to variable x. + // l(h_i) is in general a polynomial. + // For example, l( y*x^2 + x^2 + y^2 x + 1 ) = y + 1 + // + // d^i is an unsigned integer. It is introduce by the pseudo remainder procedure + // because the coefficients of x^k do not form a field. That is, they are elements of a polynomial ring Q[y_1, ..., y_n]. + // Their values are irrelevant for the correctness of this procedure. + // + // Observation 1: + // If h_0 and h_1 are polynomials in Q[y_1, ..., y_n, x], + // then h_{n+1} is a polynomial in Q[y_1, ..., y_n]. + // + // Observation 2: + // For any (complex values) a_1, ..., a_n, b, + // if h_0(a_1, ..., a_n, b) = h_1(a_1, ..., a_n, b) = 0 + // then for any h_i in the sequence, h_i(a_1, ..., a_n, b) = 0. + // In particular, h_{n+1}(a_1, ..., a_n) = 0 + // + // Observation 3: + // The procedure terminates because degree(h_i, x) > degree(h_{i+1}, x) + // for all i >= 1 + // + // Observation 4: + // If h_{n+1} is the zero polynomial, then + // For any complex numbers a_1, ..., a_n + // the univariate polynomials p(a_1, ..., a_n, x) and q(a_1, ..., a_n, x) in C[x] + // have a common root. + // Reason: + // If h_n(a_1, ..., a_n, x) is not the zero polynomial, then it is the GCD of p(a_1, ..., a_n, x) and q(a_1, ..., a_n, x), + // and it contains the common roots. + // If h_n(a_1, ..., a_n, x) is the zero polynomial, then + // we consider h_{n-1}(a_1, ..., a_n, x). If it is not the zero polynomial then it is the GCD and we are done, + // otherwise we consider h_{n-2}(a_1, ..., a_n, x), and we continue the same process. + // Thus, eventually we find a h_i(a_1, ..., a_n, x) for i > 1 that is the GCD, or q(a_1, ..., a_n, x) is the zero polynomial, + // and any polynomial p(a_1, ..., a_n, x) has a common root with the zero polynomial. + // + SASSERT(degree(p, x) > 0); + SASSERT(degree(q, x) > 0); + polynomial_ref h_0(pm()); + polynomial_ref h_1(pm()); + polynomial_ref h_2(pm()); + if (degree(p, x) < degree(q, x)) + std::swap(p, q); + h_0 = const_cast(p); + h_1 = const_cast(q); + unsigned d; + while (true) { + SASSERT(degree(h_1, x) <= degree(h_0, x)); + pseudo_remainder(h_0, h_1, x, d, h_2); + TRACE("polynomial", tout << "h_0: " << h_0 << "\nh_1: " << h_1 << "\nh_2: " << h_2 << "\n";); + SASSERT(degree(h_2, x) < degree(h_1, x)); + // We have that + // l(h_1)^d h_0 = Q h_1 + h_2. + // Q is the quotient of the division. + // l(h_1) is the leading coefficient of h_1. + // From this equation, we have that any zero of h_0 and h_1 is also a zero of h_2 + if (degree(h_2, x) == 0) { + r = h_2; + return; + } + h_0 = h_1; + h_1 = h_2; + // this computation will terminate since the pseudo_remainder guarantees that + // degree(h_2, x) < degree(h_1, x) + } + } + + // sign = sign * (-1)^(deg_A * deg_B) + static void update_sign(unsigned deg_A, unsigned deg_B, bool & sign) { + if (deg_A % 2 == 0 || deg_B % 2 == 0) + return; + sign = !sign; + } + + /** + \brief Compute the resultant of p and q with respect to r. + + The Resultant is usually defined as the determinant of the + Sylvester matrix for p and q. This matrix is built using + the coefficients of p and q with respect to x. + + Given p and q polynomials in Q[y_1, ..., y_n, x], r is a polynomial in Q[y_1, ..., y_n]. + + Property 1) + If p and q can be written as + p = a_m * (x - alpha_1) * ... * (x - alpha_m) + q = b_n * (x - beta_1) * ... * (x - beta_n) + Then, + Res(p, q, x) = a_m^n * b_n^m * Product_{i in [1,m], j in [1, n]} (alpha_i - beta_j) + + Remark: if p and q are univariate polynomials, then alpha_i's and beta_j's are the roots + of p and q, then Res(p, q, x) is the product of the differences of their roots modulo + a constant. + + Property 2) + For any (complex values) a_1, ..., a_n, b, + if p(a_1, ..., a_n, b) = q(a_1, ..., a_n, b) = 0, then r(a_1, ..., b_n) = 0 + + Property 3) + There are polynomials U and V in Q[y_1, ..., y_n, x] s.t. + p*U + q*V = r + s.t. + deg(U, x) < deg(q, x) + and + deg(V, x) < deg(p, x) + + We use Res(p, q, x) to denote the resultant of p and q. + + Property 4) (follows from 1) + If Res(p, q, x) = 0, then p and q have a common divisor. + + Resultant Calculus: + Let A and B be two polynomials of degree m and n on variable x. + Let c and d be numerals. + + Res(A, B, x) = (-1)^(m*n) * Res(B, A, x) + Res(cA, B, x) = c^n * Res(A, B, x) + Res(c, B, x) = c^n + Res(0, B, x) = 0 if n > 0 + Res(c, d, x) = 1 + Res(A, B1*B2, x) = Res(A, B1, x)*Res(A, B2, x) + Res(A, A*Q + B, x) = coeff(A, x)^(l-n) * Res(A, B, x) where l = deg(A*Q + R) + + The last equation suggests an approach for computing the Resultant using + pseudo-division instead of determinants. + + Given A and B s.t. degree(A, x) = m >= n = degree(B, x) + Then lc^d * A = Q * B + R + where lc = coeff(B, x), and + pseudo_division(A, B, x, d, Q, R); + r = degree(R) + Then we have: + (lc^d)^n * Res(A, B) = Res(A * l^d, B) = Res(Q * B + R, B) = (-1)^(m*n) * Res(B, Q * B + R) = (-1)^(m*n) * lc^(m-r) * Res(B, R) + + So, + lc^(d*n) * Res(A, B) = (-1)^(m*n) * lc^(m-r) * Res(B, R) + + From the pseudo-division algorithm, we have that: + 1) 1 <= d <= m - n + 1 + 2) 0 <= r < n <= m + + d*n >= m >= m-r + + So, if d*n == m-r + Res(A, B) = (-1)^(m*n) * Res(R, B) + if d*n > m-r + Res(A, B) = (-1)^(m*n) * exact_div(Res(R, B), lc^(d*n - m + r)) + if d*n < m-r + Res(A, B) = (-1)^(m*n) * mul(Res(R, B), lc^(m - r - d * n)) + */ + void resultant(polynomial const * p, polynomial const * q, var x, polynomial_ref & result) { + polynomial_ref A(pm()); + polynomial_ref B(pm()); + A = const_cast(p); + B = const_cast(q); + TRACE("resultant", tout << "resultant(A, B, x)\nA: " << A << "\nB: " << B << "\nx: " << x << "\n";); + // Res(0, B) = Res(A, 0) = 0 + if (is_zero(A) || is_zero(B)) { + result = mk_zero(); + return; + } + // Res(c, d, x) = 1 c and d are constants + // Res(c, B, x) = c^{deg(B)} + if (is_const(A)) { + if (is_const(B)) + result = mk_one(); + else + pw(A, degree(B, x), result); + return; + } + // Res(A, d, x) = d^{deg(A)} + if (is_const(B)) { + pw(B, degree(A, x), result); + return; + } + + // decompose A and B into + // A = iA*cA*ppA + // B = iB*cB*ppB + scoped_numeral iA(m_manager), iB(m_manager); + polynomial_ref cA(pm()), cB(pm()); + polynomial_ref ppA(pm()), ppB(pm()); + iccp(A, x, iA, cA, ppA); + iccp(B, x, iB, cB, ppB); + cA = mul(iA, cA); + cB = mul(iB, cB); + // At this point, A = cA*ppA and B = cB*ppB, where cA and cB are the content of A and B, and ppA and ppB the primitive polynomials + polynomial_ref t(pm()); + // t <- cA^{deg(B)}*cB^{deg(A)} + pw(cA, degree(B, x), cA); + pw(cB, degree(A, x), cB); + t = mul(cA, cB); + A = ppA; + B = ppB; + // + TRACE("resultant", tout << "resultant(A, B, x) after normalization\nA: " << A << "\nB: " << B << "\nx: " << x << "\n"; + tout << "t: " << t << "\n";); + + int s = 1; + unsigned degA = degree(A, x); + unsigned degB = degree(B, x); + if (degA < degB) { + A.swap(B); + if (degA % 2 == 1 && degB % 2 == 1) + s = -1; + } + + polynomial_ref R(pm()); + polynomial_ref g(pm()); + polynomial_ref h(pm()); + polynomial_ref new_h(pm()); + // g <- 1 + g = mk_one(); + // h <- 1 + h = mk_one(); + + while (true) { + TRACE("resultant", tout << "A: " << A << "\nB: " << B << "\n";); + degA = degree(A, x); + degB = degree(B, x); + SASSERT(degA >= degB); + unsigned delta = degA - degB; + if (degA % 2 == 1 && degB % 2 == 1) + s = -s; + // lc(B)^delta+1 A = BQ + R + exact_pseudo_remainder(A, B, x, R); + A = B; + // B <- R/g*h^{delta} + B = exact_div(R, g); + for (unsigned i = 0; i < delta; i++) + B = exact_div(B, h); + // g <- lc(A) + g = lc(A, x); + // h <- g^delta * h^{1-delta} + new_h = mk_one(); + pw(g, delta, new_h); + if (delta > 1) { + for (unsigned i = 0; i < delta - 1; i++) + new_h = exact_div(new_h, h); + } + h = new_h; + if (degree(B, x) == 0) { + unsigned degA = degree(A, x); + // h <- lc(B)^{deg(A)} * h^{1-deg(A)} + new_h = lc(B, x); + pw(new_h, degA, new_h); + if (degA > 1) { + for (unsigned i = 0; i < degA - 1; i++) + new_h = exact_div(new_h, h); + } + h = new_h; + // result <- s*t*h + result = mul(t, h); + if (s < 0) + result = neg(result); + return; + } + } + } + + /** + \brief Return the discriminant of p with respect to x. + + Disc(p, x) = (-1)^(m * (m-1)/2) * Resultant(p, dp/dx, x) / coeff(p, x) + dp/dx is the derivative of p with respect to x. + */ + void discriminant(polynomial const * p, var x, polynomial_ref & r) { + polynomial_ref p_prime(pm()); + unsigned m = degree(p, x); + if (m == 0) { + r = m_zero; + return; + } + p_prime = derivative(p, x); + resultant(p, p_prime, x, r); + bool sign = (static_cast(m) * static_cast(m-1))%4 != 0; + TRACE("resultant", tout << "discriminant sign: " << sign << "\n";); + scoped_numeral lc(m_manager); + if (const_coeff(p, x, m, lc)) { + if (sign) + m_manager.neg(lc); + r = div(r, lc); + } + else { + if (sign) + r = neg(r); + polynomial_ref c(pm()); + c = coeff(p, x, m); + r = exact_div(r, c); + } + } + + void subresultant_chain(polynomial const * p, polynomial const * q, var x, polynomial_ref_vector & sRes) { + // REMARK: the code does not work if deg_p == deg_q + unsigned deg_p = degree(p, x); + unsigned deg_q = degree(q, x); + if (deg_p < deg_q) { + std::swap(p, q); + std::swap(deg_p, deg_q); + } + SASSERT(deg_p > 0); + unsigned n = deg_p; + sRes.reset(); + sRes.resize(n + 1); // the sequence is from 0 ... n + sRes.set(n, const_cast(p)); + sRes.set(n - 1, const_cast(q)); + + polynomial_ref R_j_plus_1(pm()); + polynomial_ref prem(pm()); + polynomial_ref newS(pm()); + + unsigned j = n - 1; + while (j > 0) { + SASSERT(degree(sRes.get(j+1), x) == j+1); // sRes_{j+1} is regular + if (j == n-1) + R_j_plus_1 = mk_one(); // by definition of PSC chain + else + R_j_plus_1 = coeff(sRes.get(j+1), x, j+1); + unsigned r = degree(sRes.get(j), x); + if (r == j) { + // sRes_j is regular + + exact_pseudo_remainder(sRes.get(j+1), sRes.get(j), x, prem); + TRACE("psc", tout << "j: " << j << "\nsRes_j+1: "; sRes.get(j+1)->display(tout, m_manager); + tout << "\nsRes_j: "; sRes.get(j)->display(tout, m_manager); + tout << "\nprem: " << prem << "\n";); + // sRes_{j-1} = prem/R_j_plus_1^2 + newS = exact_div(prem, R_j_plus_1); + newS = exact_div(newS, R_j_plus_1); + sRes.set(j-1, newS); + j--; + } + else { + SASSERT(r < j); + // sRes_j is defective + + // sRes_{j-1} = sRes_{j-2} = ... = sRes_{r+1} = 0 + for (int i = j-1; i >= static_cast(r+1); i--) + sRes.set(i, mk_zero()); + + // sRes_r = lc(sRes_j)^{j-r} * sRes_j / R_j_plus_1^{j-r} + newS = lc(sRes.get(j), x); + pw(newS, j-r, newS); + newS = mul(newS, sRes.get(j)); + for (unsigned i = 0; i < j-r; i++) + newS = exact_div(newS, R_j_plus_1); + sRes.set(r, newS); + + // If r > 0, we also compute sRes_{r-1} + if (r > 0) { + exact_pseudo_remainder(sRes.get(j+1), sRes.get(j), x, prem); + // sRes_{r-1} = prem/(-R_j_plus_1)^{j-r+2} + newS = prem; + for (unsigned i = 0; i < j-r+2; i++) + newS = exact_div(newS, R_j_plus_1); + if ((j-r+2)%2 == 1) + newS = neg(newS); + sRes.set(r-1, newS); + j = r - 1; + } + else { + j = 0; + } + } + } + } + + void psc_chain1(polynomial const * p, polynomial const * q, var x, polynomial_ref_vector & S) { + subresultant_chain(p, q, x, S); + unsigned sz = S.size(); + TRACE("psc", tout << "subresultant_chain\n"; + for (unsigned i = 0; i < sz; i++) { tout << "i: " << i << " "; S.get(i)->display(tout, m_manager); tout << "\n"; }); + for (unsigned i = 0; i < sz - 1; i++) { + S.set(i, coeff(S.get(i), x, i)); + } + S.set(sz-1, mk_one()); + } + + // Store in S a list of the non-zero principal subresultant coefficients of A and B + // If i < j then psc_{i}(A,B) precedes psc_{j}(A,B) in S. + // The leading coefficients of A and B are not included in S. + void psc_chain2(polynomial const * A, polynomial const * B, var x, polynomial_ref_vector & S) { + polynomial_ref G1(pm()); + polynomial_ref G2(pm()); + polynomial_ref G3(pm()); + polynomial_ref Gh3(pm()); + polynomial_ref g1(pm()), h0(pm()), hs0(pm()), h1(pm()), hs1(pm()); + unsigned n1 = degree(A, x); + unsigned n2 = degree(B, x); + if (n1 > n2) { + G1 = const_cast(A); + G2 = const_cast(B); + } + else { + G1 = const_cast(B); + G2 = const_cast(A); + std::swap(n1, n2); + } + unsigned d0 = 0; + unsigned d1 = n1 - n2; + unsigned i = 1; + unsigned n3; + S.reset(); + while (true) { + // Compute Gh_{i+2} + if (!is_zero(G2)) { + exact_pseudo_remainder(G1, G2, x, Gh3); + n3 = degree(Gh3, x); + if (!is_zero(Gh3) && d1%2 == 0) + Gh3 = neg(Gh3); + } + + // Compute hi + if (i > 1) { + g1 = lc(G1, x); + pw(g1, d0, h1); + if (i > 2) { + pw(h0, d0 - 1, hs0); + h1 = exact_div(h1, hs0); + S.push_back(h1); + if (is_zero(G2)) { + std::reverse(S.c_ptr(), S.c_ptr() + S.size()); + return; + } + } + } + + // Compute G_{i+2} + if (i == 1 || is_zero(Gh3)) { + G3 = Gh3; + } + else { + pw(h1, d1, hs1); + hs1 = mul(g1, hs1); + G3 = exact_div(Gh3, hs1); + hs1 = 0; + } + + // prepare for next iteration + n1 = n2; + n2 = n3; + d0 = d1; + d1 = n1 - n2; + G1 = G2; + G2 = G3; + if (i > 1) + h0 = h1; + i = i + 1; + } + } + + // Optimized calculation of S_e using "Dichotomous Lazard" + void Se_Lazard(unsigned d, polynomial const * lc_S_d, polynomial const * S_d_1, var x, polynomial_ref & S_e) { + unsigned n = d - degree(S_d_1, x) - 1; + TRACE("Lazard", tout << "lc_S_d: "; lc_S_d->display(tout, m_manager); tout << "\nS_d_1: "; S_d_1->display(tout, m_manager); + tout << "\nn: " << n << "\n";); + if (n == 0) { + S_e = const_cast(S_d_1); + return; + } + polynomial_ref X(pm()); + X = lc(S_d_1, x); + polynomial const * Y = lc_S_d; + unsigned a = 1 << log2(n); + TRACE("Lazard", tout << "a: " << a << "\n";); + SASSERT(a <= n); + SASSERT(n < 2*a); + polynomial_ref C(pm()); + C = X; + n = n - a; + while (a != 1) { + a = a / 2; + // C <- C^2/Y + C = mul(C, C); + C = exact_div(C, Y); + TRACE("Lazard", tout << "loop a: " << a << "\nC : " << C << "\n";); + if (n >= a) { + // C <- C*X/Y + C = mul(C, X); + C = exact_div(C, Y); + n = n - a; + TRACE("Lazard", tout << "if, C: " << C << "\n";); + } + } + TRACE("Lazard", tout << "C: " << C << "\nY: " << Y << "\n";); + S_e = mul(C, S_d_1); + S_e = exact_div(S_e, Y); + } + + // Optimized calculation of S_{e-1} for optimized psc_chain + void optimized_S_e_1(unsigned d, unsigned e, polynomial const * A, polynomial const * S_d_1, polynomial const * S_e, polynomial const * s, + var x, polynomial_ref & S_e_1) { + SASSERT(d == degree(A, x)); + SASSERT(e == degree(S_d_1, x)); + polynomial_ref c_d_1(pm()), s_e(pm()), x_j(pm()), tmp(pm()); + c_d_1 = lc(S_d_1, x); + s_e = lc(S_e, x); + polynomial_ref_buffer H(pm()); + x_j = mk_one(); + for (unsigned j = 0; j <= e - 1; j++) { + // H_j <- s_e * x^j + x_j = mk_polynomial(x, j); + H.push_back(mul(s_e, x_j)); + } + SASSERT(H.size() == e); + // H_e <- s_e * x^e - S_e + x_j = mk_polynomial(x, e); + x_j = mul(s_e, x_j); + H.push_back(sub(x_j, S_e)); + SASSERT(H.size() == e+1); + polynomial_ref x_pol(pm()), xH(pm()), xHe(pm()); + x_pol = mk_polynomial(x, 1); + for (unsigned j = e + 1; j <= d - 1; j++) { + // H_j <- x H_{j-1} - (coeff(x H_{j-1}, e) * S_{d-1})/c_{d-1} + xH = mul(x_pol, H[j-1]); + xHe = coeff(xH, x, e); + tmp = mul(xHe, S_d_1); + tmp = exact_div(tmp, c_d_1); + H.push_back(sub(xH, tmp)); + } + SASSERT(H.size() == d); + // D <- (Sum coeff(A,j) * H[j])/lc(A) + polynomial_ref D(pm()); + D = mk_zero(); + for (unsigned j = 0; j < d; j++) { + tmp = coeff(A, x, j); + tmp = mul(tmp, H[j]); + D = add(D, tmp); + } + polynomial_ref lc_A(pm()); + lc_A = lc(A, x); + D = exact_div(D, lc_A); + // S_e_1 = (-1)^(d-e+1) [c_{d-1} (x H[j-1] + D) - coeff(x H[j-1], e)*S_d-1]/s + xH = mul(x_pol, H[d-1]); + xHe = coeff(xH, x, e); + xHe = mul(xHe, S_d_1); + S_e_1 = add(xH, D); + S_e_1 = mul(c_d_1, S_e_1); + S_e_1 = sub(S_e_1, xHe); + S_e_1 = exact_div(S_e_1, s); + if ((d-e+1) % 2 == 1) + S_e_1 = neg(S_e_1); + } + + void psc_chain_optimized_core(polynomial const * P, polynomial const * Q, var x, polynomial_ref_vector & S) { + TRACE("psc_chain_classic", tout << "P: "; P->display(tout, m_manager); tout << "\nQ: "; Q->display(tout, m_manager); tout << "\n";); + unsigned degP = degree(P, x); + unsigned degQ = degree(Q, x); + SASSERT(degP >= degQ); + polynomial_ref A(pm()), B(pm()), C(pm()), minus_Q(pm()), lc_Q(pm()), ps(pm()); + + lc_Q = lc(Q, x); + polynomial_ref s(pm()); + // s <- lc(Q)^(deg(P)-deg(Q)) + pw(lc_Q, degP - degQ, s); + minus_Q = neg(Q); + // A <- Q + A = const_cast(Q); + // B <- prem(P, -Q) + exact_pseudo_remainder(P, minus_Q, x, B); + while (true) { + unsigned d = degree(A, x); + unsigned e = degree(B, x); + if (is_zero(B)) + return; + TRACE("psc_chain_classic", tout << "A: " << A << "\nB: " << B << "\ns: " << s << "\nd: " << d << ", e: " << e << "\n";); + // B is S_{d-1} + ps = coeff(B, x, d-1); + if (!is_zero(ps)) + S.push_back(ps); + unsigned delta = d - e; + if (delta > 1) { + // C <- S_e + // Optimized S_e calculation + // s = lc(S_d) at this point + Se_Lazard(d, s, B, x, C); + + // C is S_e + ps = coeff(C, x, e); + if (!is_zero(ps)) + S.push_back(ps); + } + else { + SASSERT(delta == 0 || delta == 1); + C = B; + } + if (e == 0) + return; + // B <- optimized S_e_1 + optimized_S_e_1(d, e, A, B, C, s, x, B); + // A <- C + A = C; + // s <- lc(A) + s = lc(A, x); + } + } + + void psc_chain_optimized(polynomial const * P, polynomial const * Q, var x, polynomial_ref_vector & S) { + SASSERT(degree(P, x) > 0); + SASSERT(degree(Q, x) > 0); + S.reset(); + if (degree(P, x) >= degree(Q, x)) + psc_chain_optimized_core(P, Q, x, S); + else + psc_chain_optimized_core(Q, P, x, S); + if (S.empty()) + S.push_back(mk_zero()); + std::reverse(S.c_ptr(), S.c_ptr() + S.size()); + } + + void psc_chain_classic_core(polynomial const * P, polynomial const * Q, var x, polynomial_ref_vector & S) { + TRACE("psc_chain_classic", tout << "P: "; P->display(tout, m_manager); tout << "\nQ: "; Q->display(tout, m_manager); tout << "\n";); + unsigned degP = degree(P, x); + unsigned degQ = degree(Q, x); + SASSERT(degP >= degQ); + polynomial_ref A(pm()), B(pm()), C(pm()), minus_Q(pm()), lc_Q(pm()), lc_B(pm()), lc_A(pm()); + polynomial_ref tmp1(pm()), tmp2(pm()), s_delta(pm()), minus_B(pm()), ps(pm()); + + lc_Q = lc(Q, x); + polynomial_ref s(pm()); + // s <- lc(Q)^(deg(P)-deg(Q)) + pw(lc_Q, degP - degQ, s); + minus_Q = neg(Q); + // A <- Q + A = const_cast(Q); + // B <- prem(P, -Q) + exact_pseudo_remainder(P, minus_Q, x, B); + while (true) { + unsigned d = degree(A, x); + unsigned e = degree(B, x); + if (is_zero(B)) + return; + TRACE("psc_chain_classic", tout << "A: " << A << "\nB: " << B << "\ns: " << s << "\nd: " << d << ", e: " << e << "\n";); + // B is S_{d-1} + ps = coeff(B, x, d-1); + if (!is_zero(ps)) + S.push_back(ps); + unsigned delta = d - e; + if (delta > 1) { + // C <- S_e + // Standard S_e calculation + // C <- (lc(B)^(delta-1) B) / s^(delta-1) + lc_B = lc(B, x); + pw(lc_B, delta-1, lc_B); + lc_B = mul(lc_B, B); + pw(s, delta - 1, s_delta); // s_delta <- s^(delta-1) + C = exact_div(lc_B, s_delta); + + // s_delta <- s^delta + s_delta = mul(s_delta, s); + // C is S_e + ps = coeff(C, x, e); + if (!is_zero(ps)) + S.push_back(ps); + + } + else { + SASSERT(delta == 0 || delta == 1); + C = B; + // s_delta <- s^delta + pw(s, delta, s_delta); + } + if (e == 0) + return; + // B <- prem(A, -B)/(s^delta * lc(A) + lc_A = lc(A, x); + minus_B = neg(B); + exact_pseudo_remainder(A, minus_B, x, tmp1); + tmp2 = mul(lc_A, s_delta); + B = exact_div(tmp1, tmp2); + // A <- C + A = C; + // s <- lc(A) + s = lc(A, x); + } + } + + void psc_chain_classic(polynomial const * P, polynomial const * Q, var x, polynomial_ref_vector & S) { + SASSERT(degree(P, x) > 0); + SASSERT(degree(Q, x) > 0); + S.reset(); + if (degree(P, x) >= degree(Q, x)) + psc_chain_classic_core(P, Q, x, S); + else + psc_chain_classic_core(Q, P, x, S); + if (S.empty()) + S.push_back(mk_zero()); + std::reverse(S.c_ptr(), S.c_ptr() + S.size()); + } + + void psc_chain(polynomial const * A, polynomial const * B, var x, polynomial_ref_vector & S) { + // psc_chain1(A, B, x, S); + // psc_chain2(A, B, x, S); + // psc_chain_classic(A, B, x, S); + psc_chain_optimized(A, B, x, S); + } + + polynomial * normalize(polynomial const * p) { + if (is_zero(p)) + return const_cast(p); + unsigned sz = p->size(); + if (m().modular()) { + unsigned i = 0; + for (; i < sz; i++) { + if (!m().is_p_normalized(p->a(i))) + break; + } + if (i < sz) { + m_cheap_som_buffer.reset(); + scoped_numeral a(m_manager); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + m_manager.set(a, p->a(i)); + m_cheap_som_buffer.add_reset(a, m); + } + m_cheap_som_buffer.normalize(); + return m_cheap_som_buffer.mk(); + } + } + scoped_numeral g(m_manager); + m_manager.gcd(sz, p->as(), g); + if (m_manager.is_one(g)) + return const_cast(p); + m_cheap_som_buffer.reset(); + scoped_numeral a(m_manager); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + m_manager.div(p->a(i), g, a); + m_cheap_som_buffer.add_reset(a, m); + } + return m_cheap_som_buffer.mk(); + } + + polynomial * neg(polynomial const * p) { + SASSERT(m_cheap_som_buffer.empty()); + scoped_numeral minus_a(m_manager); + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + m_manager.set(minus_a, p->a(i)); + m_manager.neg(minus_a); + m_cheap_som_buffer.add(minus_a, p->m(i)); + } + return m_cheap_som_buffer.mk(); + } + + // Return true if is a(i)*m(i) is a perfect square. + // Store sqrt of a(i) in sqrt_a + bool is_perfect_square(polynomial const * p, unsigned i, numeral & sqrt_a) { + monomial * m = p->m(i); + if (!m->is_square()) + return false; + numeral const & a = p->a(i); + if (!m_manager.is_perfect_square(a, sqrt_a)) + return false; + return true; + } + + bool sqrt(polynomial const * p, polynomial_ref & r) { + SASSERT(p != 0); + if (is_zero(p)) { + r = const_cast(p); + return true; + } + scoped_numeral a(m_manager); + TRACE("sqrt_bug", + tout << "sqrt: "; p->display(tout, m_manager); tout << "\n"; + tout << "min pos: " << p->graded_lex_min_pos() << "\n"; + tout << "max pos: " << p->graded_lex_max_pos() << "\n";); + // Quick Check: the minimal monomial must be a perfect square + unsigned min_pos = p->graded_lex_min_pos(); + if (!is_perfect_square(p, min_pos, a)) + return false; + // Quick Check: the maximal monomial must be a perfect square + unsigned max_pos = p->graded_lex_max_pos(); + if (!is_perfect_square(p, max_pos, a)) + return false; + // Compute square root using + // (m_1 + ... + m_k)^2 == + // (m_1)m_1 + // (2m_1 + m_2)m_2 + // (2m_1 + 2m_2 + m_3)m_3 + // ... + // + // R <- m1 + // C <- p - m1*m1 + // while (true) { + // if (is_zero(C)) + // return true; + // m <- biggest monomial in C + // if (m is not divisible by 2*m1) + // return false; + // m' <- m/2m1 + // C <- C - 2*R*m' - m'*m' + // R <- R + m' + // } + // + // The loop above terminates because total degree of the + // maximal monomial in C decreases in each iteration. + monomial * m1 = sqrt(p->m(max_pos)); + SASSERT(m1 != 0); + som_buffer & R = m_som_buffer; + som_buffer & C = m_som_buffer2; + R.reset(); + C.reset(); + numeral two; + m_manager.set(two, 2); + scoped_numeral two_a(m_manager); + m_manager.mul(a, two, two_a); + // R <- a * m1 + R.add(a, m1); + // C <- p - m1*m1 + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + if (i == max_pos) + continue; + C.add(p->a(i), p->m(i)); + } + scoped_numeral a_i(m_manager); + scoped_numeral aux(m_manager); + monomial_ref m_aux(pm()); + while (true) { + checkpoint(); + TRACE("sqrt_bug", tout << "R: "; R.display(tout); tout << "C: "; C.display(tout);); + unsigned curr_max = C.graded_lex_max_pos(); + if (curr_max == UINT_MAX) { + // C is empty + C.reset(); + r = R.mk(); + return true; + } + monomial * m = C.m(curr_max); + monomial * m_i; + if (!div(m, m1, m_i)) { + // m1 does not divide maximal monomial of C. + R.reset(); + C.reset(); + return false; + } + // 2*a does not divide maximal coefficient of C + if (!m_manager.divides(two_a, C.a(curr_max))) + return false; + m_manager.div(C.a(curr_max), two_a, a_i); + + // C <- C - 2*R*a_i*m_i - a_i*a_i*m_i*m_i + unsigned R_sz = R.size(); + for (unsigned j = 0; j < R_sz; j++) { + if (m_manager.is_zero(R.a(j))) + continue; + m_manager.mul(R.a(j), a_i, aux); + m_manager.mul(aux, two, aux); + m_manager.neg(aux); + m_aux = mul(R.m(j), m_i); + C.add(aux, m_aux); + } + m_manager.mul(a_i, a_i, aux); + m_manager.neg(aux); + m_aux = mul(m_i, m_i); + C.add(aux, m_aux); + // R <- R + a_i*m_i + R.add(a_i, m_i); + } + } + + void rename(unsigned sz, var const * xs) { + TRACE("rename", for (unsigned i = 0; i < sz; i++) tout << xs[i] << " "; tout << "\n"; + tout << "polynomials before rename\n"; + for (unsigned i = 0; i < m_polynomials.size(); i++) { + if (m_polynomials[i] == 0) + continue; + m_polynomials[i]->display(tout, m_manager); + tout << "\n"; + }); + mm().rename(sz, xs); + // we must traverse the polynomial vector, and update the first monomial, + // since it may not contain anymore the maximal variable with maximal degree. + polynomial_vector::iterator it2 = m_polynomials.begin(); + polynomial_vector::iterator end2 = m_polynomials.end(); + for (; it2 != end2; ++it2) { + polynomial * p = *it2; + if (p == 0) + continue; + p->make_first_maximal(); + SASSERT(p->size() <= 1 || !p->lex_sorted()); + } + TRACE("rename", + tout << "polynomials after rename\n"; + for (unsigned i = 0; i < m_polynomials.size(); i++) { + if (m_polynomials[i] == 0) + continue; + m_polynomials[i]->display(tout, m_manager); + tout << "\n"; + }); + } + + bool is_pos(polynomial const * p) { + bool found_unit = false; + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + if (!p->m(i)->is_power_of_two()) + return false; + if (p->m(i) == mk_unit()) + found_unit = true; + if (!m_manager.is_pos(p->a(i))) + return false; + } + return found_unit; + } + + bool is_neg(polynomial const * p) { + bool found_unit = false; + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + if (!p->m(i)->is_power_of_two()) + return false; + if (p->m(i) == mk_unit()) + found_unit = true; + if (!m_manager.is_neg(p->a(i))) + return false; + } + return found_unit; + } + + bool is_nonpos(polynomial const * p) { + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + if (!p->m(i)->is_power_of_two()) + return false; + if (!m_manager.is_neg(p->a(i))) + return false; + } + return true; + } + + bool is_nonneg(polynomial const * p) { + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + if (!p->m(i)->is_power_of_two()) + return false; + if (!m_manager.is_pos(p->a(i))) + return false; + } + return true; + } + + // Functor used to compute the maximal degree of each variable in a polynomial p. + class var_max_degree { + unsigned_vector m_max_degree; + var_vector m_xs; + public: + void init(polynomial const * p) { + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + unsigned msz = m->size(); + for (unsigned j = 0; j < msz; j++) { + var x = m->get_var(j); + unsigned k = m->degree(j); + unsigned max_k = m_max_degree.get(x, 0); + if (k > max_k) { + if (max_k == 0) + m_xs.push_back(x); + m_max_degree.setx(x, m->degree(j), 0); + } + } + } + } + + void reset() { + unsigned sz = m_xs.size(); + for (unsigned i = 0; i < sz; i++) { + m_max_degree[m_xs[i]] = 0; + } + m_xs.reset(); + } + + unsigned operator()(var x) const { + return m_max_degree.get(x, 0); + } + + unsigned num_vars() const { return m_xs.size(); } + + var const * vars() const { return m_xs.c_ptr(); } + }; + + struct scoped_var_max_degree { + var_max_degree & m; + scoped_var_max_degree(var_max_degree & _m, polynomial const * p): + m(_m) { + m.init(p); + } + ~scoped_var_max_degree() { + m.reset(); + } + unsigned operator()(var x) const { + return m(x); + } + unsigned num_vars() const { return m.num_vars(); } + var const * vars() const { return m.vars(); } + }; + + var_max_degree m_var_max_degree; + + // This method uses the tmp fields: m_found_vars, m_var_max_degree. + polynomial * substitute(polynomial const * p, var2mpq const & x2v) { + scoped_var_max_degree var2max_degree(m_var_max_degree, p); + unsigned xs_sz = var2max_degree.num_vars(); + var const * xs = var2max_degree.vars(); + bool found = false; + for (unsigned i = 0; i < xs_sz; i++) { + var x = xs[i]; + if (x2v.contains(x) && var2max_degree(x) > 0) { + found = true; + break; + } + } + if (!found) + return const_cast(p); + scoped_numeral new_a(m_manager); + scoped_numeral tmp(m_manager); + m_found_vars.reserve(num_vars(), false); + m_som_buffer.reset(); + som_buffer & R = m_som_buffer; + tmp_monomial & new_m = m_tmp1; + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + unsigned msz = m->size(); + unsigned new_msz = 0; + m_manager.set(new_a, p->a(i)); + new_m.reserve(msz); + for (unsigned j = 0; j < msz; j++) { + var x = m->get_var(j); + unsigned k = m->degree(j); + if (x2v.contains(x)) { + unsigned max_k = var2max_degree(x); + m_found_vars[x] = true; + mpq const & x_value = x2v(x); + m_manager.power(x_value.numerator(), k, tmp); + m_manager.mul(tmp, new_a, new_a); + if (k < max_k) { + m_manager.power(x_value.denominator(), max_k - k, tmp); + m_manager.mul(tmp, new_a, new_a); + } + } + else { + new_m.set_power(new_msz, m->get_power(j)); + new_msz++; + } + } + // For each variable x in xs that does not occur in m, I + // should include (x2v(x).denominator())^{var2max_degree(x)} to new_a + for (unsigned j = 0; j < xs_sz; j++) { + var x = xs[j]; + if (m_found_vars[x]) + continue; + if (x2v.contains(x)) { + m_manager.power(x2v(x).denominator(), var2max_degree(x), tmp); + m_manager.mul(tmp, new_a, new_a); + } + } + // Reset m_found_vars + for (unsigned j = 0; j < msz; j++) { + var x = m->get_var(j); + m_found_vars[x] = false; + } + // Add new_a*new_m to R + if (!m_manager.is_zero(new_a)) { + new_m.set_size(new_msz); + R.add(new_a, mk_monomial(new_m)); + } + } + return R.mk(true); + } + + struct var_pos { + unsigned_vector m_pos; + + void init(unsigned sz, var const * xs) { + for (unsigned i = 0; i < sz; i++) { + SASSERT(m_pos.get(xs[i], UINT_MAX) == UINT_MAX); + m_pos.setx(xs[i], i, UINT_MAX); + } + } + + void reset(unsigned sz, var const * xs) { + for (unsigned i = 0; i < sz; i++) { + SASSERT(m_pos.get(xs[i], UINT_MAX) != UINT_MAX); + m_pos[xs[i]] = UINT_MAX; + } + } + + unsigned operator()(var x) const { return m_pos.get(x, UINT_MAX); } + }; + + struct scoped_var_pos { + var_pos & m; + unsigned m_xs_sz; + var const * m_xs; + scoped_var_pos(var_pos & p, unsigned xs_sz, var const * xs): + m(p), + m_xs_sz(xs_sz), + m_xs(xs) { + m.init(m_xs_sz, m_xs); + } + ~scoped_var_pos() { + m.reset(m_xs_sz, m_xs); + } + unsigned operator()(var x) const { return m(x); } + }; + + var_pos m_var_pos; + + struct var2mpq_wrapper : public var2mpq { + scoped_var_pos m_var_pos; + mpq const * m_vs; + var2mpq_wrapper(unsigned xs_sz, var const * xs, mpq const * vs, var_pos & buffer): + m_var_pos(buffer, xs_sz, xs), + m_vs(vs) { + } + virtual unsynch_mpq_manager & m() const { UNREACHABLE(); static unsynch_mpq_manager m; return m; } + virtual bool contains(var x) const { return m_var_pos(x) != UINT_MAX; } + virtual mpq const & operator()(var x) const { return m_vs[m_var_pos(x)]; } + }; + + polynomial * substitute(polynomial const * p, unsigned xs_sz, var const * xs, mpq const * vs) { + var2mpq_wrapper x2v(xs_sz, xs, vs, m_var_pos); + return substitute(p, x2v); + } + + polynomial * substitute(polynomial const * p, unsigned xs_sz, var const * xs, numeral const * vs) { + TRACE("polynomial", tout << "substitute num_vars: " << xs_sz << "\n"; + for (unsigned i = 0; i < xs_sz; i++) { tout << "x" << xs[i] << " -> " << m_manager.to_string(vs[i]) << "\n"; }); + scoped_var_pos var2pos(m_var_pos, xs_sz, xs); + scoped_numeral new_a(m_manager); + scoped_numeral tmp(m_manager); + m_som_buffer.reset(); + som_buffer & R = m_som_buffer; + tmp_monomial & new_m = m_tmp1; + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + unsigned msz = m->size(); + unsigned new_msz = 0; + m_manager.set(new_a, p->a(i)); + new_m.reserve(msz); + for (unsigned j = 0; j < msz; j++) { + var x = m->get_var(j); + unsigned k = m->degree(j); + unsigned pos = var2pos(x); + if (pos != UINT_MAX) { + m_manager.power(vs[pos], k, tmp); + m_manager.mul(tmp, new_a, new_a); + } + else { + SASSERT(var2pos(x) == UINT_MAX); // x is not in xs + new_m.set_power(new_msz, m->get_power(j)); + new_msz++; + } + } + new_m.set_size(new_msz); + TRACE("polynomial", tout << "processing " << m_manager.to_string(p->a(i)) << " "; m->display(tout); tout << "\n"; + tout << "new_a: " << m_manager.to_string(new_a) << " "; mk_monomial(new_m)->display(tout); tout << "\n";); + R.add(new_a, mk_monomial(new_m)); + } + return R.mk(); + } + + + /** + Auxiliary method used to implement t_eval. + If evaluates the sub-polynomial p composed of the monomials at positions [start, end) + where all variables > x are ignored. + + var2pos is a mapping from variables to the positions. + vs[var2pos(x)] contains the value of x. + */ + template + void t_eval_core(polynomial * p, ValManager & vm, var2value const & x2v, + unsigned start, unsigned end, var x, typename ValManager::numeral & r) { + TRACE("eval_bug", tout << "p: "; p->display(tout, m()); tout << "\n"; + tout << "start: " << start << ", end: " << end << ", x: " << x << "\n";); + SASSERT(start < end); + SASSERT(end <= p->size()); + SASSERT(is_valid(x)); + _scoped_numeral aux(vm); + if (end == start + 1) { + vm.set(r, p->a(start)); + monomial * m = p->m(start); + SASSERT(m->degree_of(x) > 0); + unsigned sz = m->size(); + for (unsigned i = 0; i < sz; i++) { + var y = m->get_var(i); + if (y > x) + break; + SASSERT(x2v.contains(y)); + unsigned d = m->degree(i); + vm.power(x2v(y), d, aux); + vm.mul(r, aux, r); + } + } + else { + SASSERT(x2v.contains(x)); + typename ValManager::numeral const & x_value = x2v(x); + vm.reset(r); + unsigned i = start; + while (i < end) { + checkpoint(); + unsigned d = p->m(i)->degree_of(x); + if (d == 0) { + var y = p->max_smaller_than(i, end, x); + if (y == null_var) { + SASSERT(end == i+1); + vm.add(r, p->a(i), r); + } + else { + t_eval_core(p, vm, x2v, i, end, y, aux.get()); + vm.add(r, aux, r); + } + break; + } + unsigned j = i+1; + unsigned next_d = 0; + for (; j < end; j++) { + unsigned d_j = p->m(j)->degree_of(x); + SASSERT(d_j <= d); + if (d_j < d) { + next_d = d_j; + break; + } + } + SASSERT(j == end || p->m(j)->degree_of(x) < d); + var y = p->max_smaller_than(i, j, x); + if (y == null_var) { + SASSERT(j == i+1); + vm.set(aux, p->a(i)); + } + else { + t_eval_core(p, vm, x2v, i, j, y, aux.get()); + } + vm.add(r, aux, r); + vm.power(x_value, d - next_d, aux); + vm.mul(r, aux, r); + i = j; + } + } + TRACE("eval_bug", tout << "result for start: " << start << ", end: " << end << ", x: " << x << "\n"; + tout << "r: "; vm.display(tout, r); tout << "\n";); + } + + template + void t_eval(polynomial * p, var2value const & x2v, typename ValManager::numeral & r) { + ValManager & vm = x2v.m(); + if (is_zero(p)) { + vm.reset(r); + return; + } + if (is_const(p)) { + SASSERT(size(p)==1); + vm.set(r, p->a(0)); + return; + } + lex_sort(p); // lex_sort just reorders the monomials of p. That is, p still represents the same polynomial + t_eval_core(p, vm, x2v, 0, p->size(), max_var(p), r); + } + + class single_var2value : public var2value { + numeral_manager & m_manager; + var m_x; + numeral const & m_val; + public: + single_var2value(numeral_manager & m, var x, numeral const & val):m_manager(m), m_x(x), m_val(val) {} + virtual numeral_manager & m() const { return m_manager; } + virtual bool contains(var x) const { return m_x == x; } + virtual numeral const & operator()(var x) const { SASSERT(m_x == x); return m_val; } + }; + + void univ_eval(polynomial const * p, var x, numeral const & val, numeral & r) { + SASSERT(is_univariate(p)); + if (is_zero(p)) + m().set(r, 0); + else if (is_const(p)) + m().set(r, p->a(0)); + else + t_eval(const_cast(p), single_var2value(m(),x, val), r); + } + + void eval(polynomial const * p, var2mpbqi const & x2v, mpbqi & r) { + t_eval(const_cast(p), x2v, r); + } + + void eval(polynomial const * p, var2mpq const & x2v, mpq & r) { + t_eval(const_cast(p), x2v, r); + } + + void eval(polynomial const * p, var2anum const & x2v, anum & r) { + t_eval(const_cast(p), x2v, r); + } + + // Return the variable with minimal degree in p + // That is min_x s.t. forall x in p degree(p, min_x) <= degree(p, x) + var get_min_degree_var(polynomial const * p) { + SASSERT(!is_const(p)); + scoped_var_max_degree var2max_degree(m_var_max_degree, p); + unsigned num_vars = var2max_degree.num_vars(); + var const * xs = var2max_degree.vars(); + var min_x = null_var; + unsigned deg_min = UINT_MAX; + for (unsigned i = 0; i < num_vars; i++) { + var x_i = xs[i]; + unsigned deg_x_i = var2max_degree(x_i); + if (deg_x_i < deg_min) { + min_x = x_i; + deg_min = deg_x_i; + } + } + return min_x; + } + + void acc_constant(factors & r, numeral const & c) { + TRACE("factor_bug", tout << "acc_constant, c: "; m_manager.display(tout, c); tout << "\n";); + scoped_numeral new_c(m_manager); + m_manager.mul(r.get_constant(), c, new_c); + r.set_constant(new_c); + } + + void flip_sign(factors & r) { + scoped_numeral new_c(m_manager); + m_manager.set(new_c, r.get_constant()); + m_manager.neg(new_c); + r.set_constant(new_c); + } + + void factor_1_sqf_pp(polynomial const * p, factors & r, var x, unsigned k) { + SASSERT(degree(p, x) == 1); + SASSERT(is_primitive(p, x)); + SASSERT(is_square_free(p, x)); + TRACE("factor", tout << "factor square free (degree == 1):\n"; p->display(tout, m_manager); tout << "\n";); + // easy case + r.push_back(const_cast(p), k); + } + + void factor_2_sqf_pp(polynomial const * p, factors & r, var x, unsigned k) { + SASSERT(degree(p, x) == 2); + SASSERT(is_primitive(p, x)); + SASSERT(is_square_free(p, x)); + TRACE("factor", tout << "factor square free (degree == 2):\n"; p->display(tout, m_manager); tout << "\n";); + + polynomial_ref a(pm()); + polynomial_ref b(pm()); + polynomial_ref c(pm()); + a = coeff(p, x, 2); + b = coeff(p, x, 1); + c = coeff(p, x, 0); + TRACE("factor", tout << "a: " << a << "\nb: " << b << "\nc: " << c << "\n";); + // make sure the leading monomoal of a is positive + bool flipped_coeffs = false; + SASSERT(!is_zero(a)); + unsigned a_glex_max_pos = a->graded_lex_max_pos(); + SASSERT(a_glex_max_pos != UINT_MAX); + if (m_manager.is_neg(a->a(a_glex_max_pos))) { + a = neg(a); + b = neg(b); + c = neg(c); + flipped_coeffs = true; + } + // Create the discriminant: b^2 - 4*a*c + polynomial_ref b2(pm()); + b2 = mul(b, b); + polynomial_ref ac(pm()); + ac = mul(a, c); + polynomial_ref disc(pm()); + numeral m_four; + m_manager.set(m_four, -4); + disc = addmul(b2, m_four, mk_unit(), ac); + // discriminant must be different from 0, since p is square free + SASSERT(!is_zero(disc)); + polynomial_ref disc_sqrt(pm()); + TRACE("factor", tout << "disc: " << disc << "\n";); + if (!sqrt(disc, disc_sqrt)) { + // p is irreducible + r.push_back(const_cast(p), k); + return; + } + if (flipped_coeffs && k % 2 == 1) { + // if k is ODD, and we flipped the coefficients, + // we must also flip the sign of r. + flip_sign(r); + } + DEBUG_CODE({ + polynomial_ref tmp(pm()); + tmp = mul(disc_sqrt, disc_sqrt); + SASSERT(eq(disc, tmp)); + }); + TRACE("factor", tout << "disc_sqrt: " << disc_sqrt << "\n";); + // p = cont*(2*a*x + b - disc_sqrt)*(2*a*x + b + disc_sqrt) + numeral two; + m_manager.set(two, 2); + polynomial_ref f1(pm()); + polynomial_ref f2(pm()); + monomial_ref mx(pm()); + mx = mk_monomial(x); + polynomial_ref two_ax(pm()); + two_ax = mul(two, mx, a); + f1 = add(two_ax, b); + f2 = f1; + f1 = sub(f1, disc_sqrt); + f2 = add(f2, disc_sqrt); + TRACE("factor", tout << "before pp\nf1: " << f1 << "\nf2: " << f2 << "\n"; + polynomial_ref cf1(pm()); m_wrapper.content(f1, x, cf1); + polynomial_ref cf2(pm()); m_wrapper.content(f2, x, cf2); + tout << "content(f1): " << cf1 << "\ncontent(f2): " << cf2 << "\n";); + pp(f1, x, f1); + pp(f2, x, f2); + TRACE("factor", tout << "f1: " << f1 << "\nf2: " << f2 << "\n";); + DEBUG_CODE({ + polynomial_ref f1f2(pm()); + f1f2 = mul(f1, f2); + if (flipped_coeffs) + f1f2 = neg(f1f2); + SASSERT(eq(f1f2, p)); + }); + r.push_back(f1, k); + r.push_back(f2, k); + } + + void factor_sqf_pp_univ(polynomial const * p, factors & r, unsigned k, factor_params const & params) { + SASSERT(is_univariate(p)); + SASSERT(is_square_free(p, max_var(p))); + SASSERT(is_primitive(p, max_var(p))); + SASSERT(!is_zero(p)); + TRACE("factor", tout << "factor square free univariate:\n"; p->display(tout, m_manager); tout << "\n";); + + // Convert polynomial into a upolynomial, and execute univariate factorization. + var x = max_var(p); + up_manager::scoped_numeral_vector p1(upm().m()); + polynomial_ref p_ref(pm()); + p_ref = const_cast(p); + upm().to_numeral_vector(p_ref, p1); + up_manager::factors fs(upm()); + upolynomial::factor_square_free(upm(), p1, fs, params); + SASSERT(m().is_one(fs.get_constant()) || m().is_minus_one(fs.get_constant())); + + if (fs.distinct_factors() == 1 && fs.get_degree(0) == 1) { + // p is irreducible + r.push_back(const_cast(p), k); + } + else { + // Convert factors back into polynomial objects + TRACE("factor_bug", tout << "factoring fs constant: " << m().to_string(fs.get_constant()) << "\np:\n"; + p->display(tout, m()); tout << "\n";); + polynomial_ref f(pm()); + unsigned num_factors = fs.distinct_factors(); + for (unsigned i = 0; i < num_factors; i++) { + numeral_vector const & f1 = fs[i]; + unsigned k1 = fs.get_degree(i); + f = to_polynomial(f1.size(), f1.c_ptr(), x); + TRACE("factor_bug", + tout << "uni-factor:\n"; upm().display(tout, f1); tout << "\n"; + tout << "factor:\n" << f << "\n";); + r.push_back(f, k*k1); + } + TRACE("factor_bug", tout << "end-factors...\n";); + SASSERT(m().is_one(fs.get_constant()) || m().is_minus_one(fs.get_constant())); + if (m().is_minus_one(fs.get_constant()) && k % 2 == 1) + flip_sign(r); + } + } + + void factor_n_sqf_pp(polynomial const * p, factors & r, var x, unsigned k) { + SASSERT(degree(p, x) > 2); + SASSERT(is_primitive(p, x)); + SASSERT(is_square_free(p, x)); + TRACE("factor", tout << "factor square free (degree > 2):\n"; p->display(tout, m_manager); tout << "\n";); + + // TODO: invoke Dejan's procedure + r.push_back(const_cast(p), k); + } + + void factor_sqf_pp(polynomial const * p, factors & r, var x, unsigned k, factor_params const & params) { + SASSERT(degree(p, x) > 0); + SASSERT(is_primitive(p, x)); + SASSERT(is_square_free(p, x)); + SASSERT(!is_zero(p)); + + unsigned deg_x = degree(p, x); + if (deg_x == 1) + factor_1_sqf_pp(p, r, x, k); + else if (is_univariate(p)) + factor_sqf_pp_univ(p, r, k, params); + else if (deg_x == 2) + factor_2_sqf_pp(p, r, x, k); + else + factor_n_sqf_pp(p, r, x, k); + } + + void factor_core(polynomial const * p, factors & r, factor_params const & params) { + TRACE("factor", tout << "factor_core\np: "; p->display(tout, m_manager); tout << "\n";); + TRACE("factor_bug", tout << "factors r.get_constant(): " << m_manager.to_string(r.get_constant()) << "\n";); + SASSERT(!is_zero(p)); + if (is_const(p)) { + SASSERT(!is_zero(p)); + SASSERT(p->size() == 1); + acc_constant(r, p->a(0)); + return; + } + var x = get_min_degree_var(p); + SASSERT(degree(p, x) > 0); + scoped_numeral i(m_manager); + polynomial_ref c(pm()), pp(pm()); + iccp(p, x, i, c, pp); + TRACE("factor", tout << "i: " << i << "\n";); + acc_constant(r, i); + factor_core(c, r, params); + + polynomial_ref C(pm()); + C = pp; + // Let C be a primitive polynomial of the form: P_1^1 * P_2^2 * ... * P_k^k, where each P_i is square free + polynomial_ref C_prime(pm()); + C_prime = derivative(C, x); + polynomial_ref B(pm()), A(pm()), D(pm()); + gcd(C, C_prime, B); + if (is_const(B)) { + // C must be of the form P_1 (square free) + SASSERT(degree(C, x) > 0); + factor_sqf_pp(C, r, x, 1, params); + } + else { + // B is of the form P_2 * P_3^2 * ... * P_k^{k-1} + A = exact_div(C, B); + // A is of the form P_1 * P_2 * ... * P_k + unsigned j = 1; + while (!is_const(A)) { + SASSERT(is_primitive(A, x)); + SASSERT(is_square_free(A, x)); + SASSERT(degree(A, x) > 0); + checkpoint(); + TRACE("factor", tout << "factor_core main loop j: " << j << "\nA: " << A << "\nB: " << B << "\n";); + // A is of the form P_j * P_{j+1} * P_{j+2} * ... * P_k + // B is of the form P_{j+1} * P_{j+2}^2 * ... * P_k^{k - j - 2} + gcd(A, B, D); + // D is of the form P_{j+1} * P_{j+2} * ... * P_k + C = exact_div(A, D); + // C is of the form P_j + if (!is_const(C)) { + SASSERT(degree(C, x) > 0); + factor_sqf_pp(C, r, x, j, params); + } + else { + TRACE("factor", tout << "const C: " << C << "\n";); + SASSERT(C->size() == 1); + SASSERT(m_manager.is_one(C->a(0)) || m_manager.is_minus_one(C->a(0))); + if (m_manager.is_minus_one(C->a(0)) && j % 2 == 1) + flip_sign(r); + } + B = exact_div(B, D); + // B is of the form P_{j+2} * ... * P_k^{k - j - 3} + A = D; + // D is of the form P_{j+1} * P_{j+2} * ... * P_k + j++; + } + SASSERT(eq(mk_one(), A)); + } + } + + void factor(polynomial const * p, factors & r, factor_params const & params) { + if (is_zero(p)) { + r.set_constant(mpz(0)); + return; + } + factor_core(p, r, params); + TRACE("factor_bug", tout << "[factor] end, r.get_constant(): " << m_manager.to_string(r.get_constant()) << "\n";); + } + + polynomial * to_polynomial(unsigned sz, numeral const * p, var x) { + if (sz == 0) + return mk_zero(); + _scoped_numeral_buffer coeffs(m_manager); + for (unsigned i = 0; i < sz; i++) { + coeffs.push_back(numeral()); + m_manager.set(coeffs.back(), p[i]); + } + return mk_univariate(x, sz-1, coeffs.c_ptr()); + } + + polynomial * mk_glex_monic(polynomial const * p) { + SASSERT(m_manager.field()); + if (is_zero(p)) + return const_cast(p); + unsigned pos = p->graded_lex_max_pos(); + if (m_manager.is_one(p->a(pos))) + return const_cast(p); + scoped_numeral inv_c(m()); + scoped_numeral new_a(m()); + m().set(inv_c, p->a(pos)); + m().inv(inv_c); + m_cheap_som_buffer.reset(); + cheap_som_buffer & R = m_cheap_som_buffer; + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + m().set(new_a, p->a(i)); + m().mul(new_a, inv_c, new_a); + R.add(new_a, p->m(i)); + } + return R.mk(); + } + + som_buffer_vector m_translate_buffers; + polynomial * translate(polynomial const * p, var x, numeral const & v) { + unsigned deg_x = degree(p, x); + if (deg_x == 0 || m().is_zero(v)) + return const_cast(p); + som_buffer_vector & as = m_translate_buffers; + m_translate_buffers.reset(deg_x+1); + coeffs(p, x, as); + for (unsigned i = 1; i <= deg_x; i++) { + checkpoint(); + for (unsigned k = deg_x-i; k <= deg_x-1; k++) { + as[k]->addmul(v, as[k+1]); + } + } + monomial_ref xk(pm()); + som_buffer & R = m_som_buffer; + R.reset(); + for (unsigned k = 0; k <= deg_x; k++) { + xk = mk_monomial(x, k); + R.addmul(xk, as[k]); + } + m_translate_buffers.reset(deg_x+1); + return R.mk(); + } + + void translate(polynomial const * p, unsigned xs_sz, var const * xs, numeral const * vs, polynomial_ref & r) { + r = const_cast(p); + if (xs_sz == 0 || is_const(p)) + return; + for (unsigned i = 0; i < xs_sz; i++) + r = translate(r, xs[i], vs[i]); + } + + polynomial * mod_d(polynomial const * p, var2degree const & x2d) { + if (is_const(p)) + return const_cast(p); + + cheap_som_buffer & R = m_cheap_som_buffer; + R.reset(); + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + monomial * m = p->m(i); + unsigned msz = m->size(); + unsigned j; + for (j = 0; j < msz; j++) { + var x = m->get_var(j); + unsigned dx = x2d.degree(x); + if (dx == 0) + continue; + if (m->degree(j) >= dx) + break; + } + if (j == msz) { + R.add(p->a(i), p->m(i)); + } + } + return R.mk(); + } + + void exact_pseudo_division_mod_d(polynomial const * p, polynomial const * q, var x, var2degree const & x2d, polynomial_ref & Q, polynomial_ref & R) { + unsigned d; + pseudo_division_core(p, q, x, d, Q, R, &x2d); + } + }; + + manager::manager(numeral_manager & m, monomial_manager * mm) { + m_imp = alloc(imp, *this, m, mm); + } + + manager::manager(numeral_manager & m, small_object_allocator * a) { + m_imp = alloc(imp, *this, m, a); + } + + manager::~manager() { + dealloc(m_imp); + } + + numeral_manager & manager::m() const { + return m_imp->m_manager.m(); + } + + monomial_manager & manager::mm() const { + return m_imp->mm(); + } + + bool manager::modular() const { + return m_imp->m().modular(); + } + + numeral const & manager::p() const { + return m_imp->m().p(); + } + + void manager::set_z() { + return m_imp->m().set_z(); + } + + void manager::set_zp(numeral const & p) { + return m_imp->m().set_zp(p); + } + + void manager::set_zp(uint64 p) { + return m_imp->m().set_zp(p); + } + + small_object_allocator & manager::allocator() const { + return m_imp->mm().allocator(); + } + + void manager::set_cancel(bool f) { + m_imp->set_cancel(f); + } + + void manager::add_del_eh(del_eh * eh) { + m_imp->add_del_eh(eh); + } + + void manager::remove_del_eh(del_eh * eh) { + m_imp->remove_del_eh(eh); + } + + var manager::mk_var() { + return m_imp->mk_var(); + } + + unsigned manager::num_vars() const { + return m_imp->num_vars(); + } + + void manager::inc_ref(monomial * m) { + if (m) + m->inc_ref(); + } + + void manager::dec_ref(monomial * m) { + if (m) + m_imp->dec_ref(m); + } + + void manager::inc_ref(polynomial * p) { + if (p) + p->inc_ref(); + } + + void manager::dec_ref(polynomial * p) { + if (p) + m_imp->dec_ref(p); + } + + void manager::lex_sort(polynomial * p) { + m_imp->lex_sort(p); + } + + unsigned manager::hash(monomial const * m) { + return m->hash(); + } + + unsigned manager::hash(polynomial const * p) { + return m_imp->hash(p); + } + + polynomial * manager::coeff(polynomial const * p, var x, unsigned k) { + return m_imp->coeff(p, x, k); + } + + polynomial * manager::coeff(polynomial const * p, var x, unsigned k, polynomial_ref & reduct) { + return m_imp->coeff(p, x, k, reduct); + } + + bool manager::nonzero_const_coeff(polynomial const * p, var x, unsigned k) { + return m_imp->nonzero_const_coeff(p, x, k); + } + + bool manager::const_coeff(polynomial const * p, var x, unsigned k, numeral & c) { + return m_imp->const_coeff(p, x, k, c); + } + + monomial * manager::mk_unit() { + return m_imp->mk_unit(); + } + + monomial * manager::mk_monomial(var x) { + return m_imp->mk_monomial(x); + } + + monomial * manager::mk_monomial(var x, unsigned k) { + return m_imp->mk_monomial(x, k); + } + + monomial * manager::mk_monomial(unsigned sz, var * xs) { + return m_imp->mk_monomial(sz, xs); + } + + monomial * manager::convert(monomial const * src) { + return m_imp->convert(src); + } + + monomial * manager::mul(monomial const * m1, monomial const * m2) { + return m_imp->mul(m1, m2); + } + + bool manager::div(monomial const * m1, monomial const * m2) { + return m_imp->div(m1, m2); + } + + bool manager::div(monomial const * m1, monomial const * m2, monomial * & r) { + return m_imp->div(m1, m2, r); + } + + void manager::newton_interpolation(var x, unsigned d, numeral const * inputs, polynomial * const * outputs, polynomial_ref & r) { + return m_imp->newton_interpolation(x, d, inputs, outputs, r); + } + + monomial * manager::gcd(monomial const * m1, monomial const * m2, monomial * & q1, monomial * & q2) { + return m_imp->gcd(m1, m2, q1, q2); + } + + bool manager::unify(monomial const * m1, monomial const * m2, monomial * & q1, monomial * & q2) { + return m_imp->unify(m1, m2, q1, q2); + } + + monomial * manager::pw(monomial const * m, unsigned k) { + return m_imp->pw(m, k); + } + + polynomial * manager::mk_zero() { + return m_imp->mk_zero(); + } + + polynomial * manager::mk_const(numeral & a) { + return m_imp->mk_const(a); + } + + polynomial * manager::mk_const(rational const & a) { + return m_imp->mk_const(a); + } + + polynomial * manager::mk_polynomial(var x, unsigned k) { + return m_imp->mk_polynomial(x, k); + } + + polynomial * manager::mk_polynomial(unsigned sz, numeral * as, monomial * const * ms) { + return m_imp->mk_polynomial(sz, as, ms); + } + + polynomial * manager::mk_polynomial(unsigned sz, rational const * as, monomial * const * ms) { + return m_imp->mk_polynomial(sz, as, ms); + } + + polynomial * manager::mk_linear(unsigned sz, rational const * as, var const * xs, rational const & c) { + return m_imp->mk_linear(sz, as, xs, c); + } + + polynomial * manager::mk_linear(unsigned sz, numeral * as, var const * xs, numeral & c) { + return m_imp->mk_linear(sz, as, xs, c); + } + + polynomial * manager::mk_univariate(var x, unsigned n, numeral * as) { + return m_imp->mk_univariate(x, n, as); + } + + polynomial * manager::neg(polynomial const * p) { + return m_imp->neg(p); + } + + polynomial * manager::add(polynomial const * p1, polynomial const * p2) { + return m_imp->add(p1, p2); + } + + polynomial * manager::sub(polynomial const * p1, polynomial const * p2) { + return m_imp->sub(p1, p2); + } + + polynomial * manager::addmul(numeral const & a1, monomial const * m1, polynomial const * p1, + numeral const & a2, monomial const * m2, polynomial const * p2) { + return m_imp->addmul(a1, m1, p1, a2, m2, p2); + } + + polynomial * manager::addmul(polynomial const * p1, numeral const & a2, monomial const * m2, polynomial const * p2) { + return m_imp->addmul(p1, a2, m2, p2); + } + + polynomial * manager::addmul(polynomial const * p1, numeral const & a2, polynomial const * p2) { + return m_imp->addmul(p1, a2, p2); + } + + polynomial * manager::mul(numeral const & a, polynomial const * p) { + return m_imp->mul(a, p); + } + + polynomial * manager::mul(rational const & a, polynomial const * p) { + return m_imp->mul(a, p); + } + + polynomial * manager::mul(polynomial const * p1, polynomial const * p2) { + return m_imp->mul(p1, p2); + } + + polynomial * manager::mul(numeral const & a, monomial const * m, polynomial const * p) { + return m_imp->mul(a, m, p); + } + + polynomial * manager::mul(monomial const * m, polynomial const * p) { + return m_imp->mul(m, p); + } + + void manager::pw(polynomial const * p, unsigned k, polynomial_ref & r) { + m_imp->pw(p, k, r); + } + + void manager::int_content(polynomial const * p, numeral & c) { + m_imp->ic(p, c); + } + + void manager::abs_norm(polynomial const * p, numeral & norm) { + m_imp->abs_norm(p, norm); + } + + polynomial::numeral const & manager::numeral_lc(polynomial const * p, var x) { + return m_imp->numeral_lc(p, x); + } + + polynomial::numeral const & manager::numeral_tc(polynomial const * p) { + return m_imp->numeral_tc(p); + } + + void manager::content(polynomial const * p, var x, numeral & i, polynomial_ref & c) { + polynomial_ref pp(*this); + m_imp->iccp(p, x, i, c, pp); + } + + void manager::content(polynomial const * p, var x, polynomial_ref & c) { + scoped_numeral i(m_imp->m_manager.m()); + content(p, x, i, c); + if (!m_imp->m_manager.is_one(i)) { + c = mul(i, c); + } + } + + void manager::primitive(polynomial const * p, var x, polynomial_ref & pp) { + m_imp->pp(p, x, pp); + } + + void manager::icpp(polynomial const * p, var x, numeral & i, polynomial_ref & c, polynomial_ref & pp) { + m_imp->iccp(p, x, i, c, pp); + } + + polynomial * manager::flip_sign_if_lm_neg(polynomial const * p) { + return m_imp->flip_sign_if_lm_neg_core(p); + } + + void manager::gcd(polynomial const * p, polynomial const * q, polynomial_ref & g) { + m_imp->gcd(p, q, g); + } + + polynomial * manager::derivative(polynomial const * p, var x) { + return m_imp->derivative(p, x); + } + + void manager::square_free(polynomial const * p, var x, polynomial_ref & r) { + m_imp->square_free(p, x, r); + } + + bool manager::is_square_free(polynomial const * p, var x) { + return m_imp->is_square_free(p, x); + } + + void manager::square_free(polynomial const * p, polynomial_ref & r) { + m_imp->square_free(p, r); + } + + bool manager::is_square_free(polynomial const * p) { + return m_imp->is_square_free(p); + } + + bool manager::eq(polynomial const * p1, polynomial const * p2) { + return m_imp->eq(p1, p2); + } + + polynomial * manager::compose_y(polynomial const * p, var y) { + return m_imp->compose_y(p, y); + } + + polynomial * manager::compose_minus_x(polynomial const * p) { + return m_imp->compose_minus_x(p); + } + + polynomial * manager::compose_1_div_x(polynomial const * p) { + return m_imp->compose_1_div_x(p); + } + + polynomial * manager::compose_x_div_y(polynomial const * p, var y) { + return m_imp->compose_x_div_y(p, y); + } + + void manager::compose(polynomial const * p, polynomial const * q, polynomial_ref & r) { + m_imp->compose(p, q, r); + } + + void manager::compose_x_minus_y(polynomial const * p, var y, polynomial_ref & r) { + m_imp->compose_x_minus_y(p, y, r); + } + + void manager::compose_x_plus_y(polynomial const * p, var y, polynomial_ref & r) { + m_imp->compose_x_plus_y(p, y, r); + } + + void manager::compose_x_minus_c(polynomial const * p, numeral const & c, polynomial_ref & r) { + m_imp->compose_x_minus_c(p, c, r); + } + + bool manager::sqrt(polynomial const * p, polynomial_ref & r) { + return m_imp->sqrt(p, r); + } + + polynomial * manager::normalize(polynomial const * p) { + return m_imp->normalize(p); + } + + void manager::exact_pseudo_remainder(polynomial const * p, polynomial const * q, var x, polynomial_ref & R) { + m_imp->exact_pseudo_remainder(p, q, x, R); + } + + void manager::pseudo_remainder(polynomial const * p, polynomial const * q, var x, unsigned & d, polynomial_ref & R) { + m_imp->pseudo_remainder(p, q, x, d, R); + } + + void manager::exact_pseudo_division(polynomial const * p, polynomial const * q, var x, polynomial_ref & Q, polynomial_ref & R) { + m_imp->exact_pseudo_division(p, q, x, Q, R); + } + + void manager::pseudo_division(polynomial const * p, polynomial const * q, var x, unsigned & d, polynomial_ref & Q, polynomial_ref & R) { + m_imp->pseudo_division(p, q, x, d, Q, R); + } + + polynomial * manager::exact_div(polynomial const * p, polynomial const * q) { + return m_imp->exact_div(p, q); + } + + polynomial * manager::exact_div(polynomial const * p, numeral const & c) { + return m_imp->exact_div(p, c); + } + + bool manager::divides(polynomial const * q, polynomial const * p) { + return m_imp->divides(q, p); + } + + void manager::quasi_resultant(polynomial const * p, polynomial const * q, var x, polynomial_ref & r) { + m_imp->quasi_resultant(p, q, x, r); + } + + void manager::resultant(polynomial const * p, polynomial const * q, var x, polynomial_ref & r) { + m_imp->resultant(p, q, x, r); + } + + void manager::discriminant(polynomial const * p, var x, polynomial_ref & r) { + m_imp->discriminant(p, x, r); + } + + void manager::psc_chain(polynomial const * p, polynomial const * q, var x, polynomial_ref_vector & S) { + m_imp->psc_chain(p, q, x, S); + } + + bool manager::is_pos(polynomial const * p) { + return m_imp->is_pos(p); + } + + bool manager::is_neg(polynomial const * p) { + return m_imp->is_neg(p); + } + + bool manager::is_nonpos(polynomial const * p) { + return m_imp->is_nonpos(p); + } + + bool manager::is_nonneg(polynomial const * p) { + return m_imp->is_nonneg(p); + } + + void manager::rename(unsigned sz, var const * xs) { + return m_imp->rename(sz, xs); + } + + void manager::vars(polynomial const * p, var_vector & xs) { + m_imp->vars(p, xs); + } + + polynomial * manager::substitute(polynomial const * p, var2mpq const & x2v) { + return m_imp->substitute(p, x2v); + } + + polynomial * manager::substitute(polynomial const * p, unsigned xs_sz, var const * xs, mpq const * vs) { + return m_imp->substitute(p, xs_sz, xs, vs); + } + + polynomial * manager::substitute(polynomial const * p, unsigned xs_sz, var const * xs, numeral const * vs) { + return m_imp->substitute(p, xs_sz, xs, vs); + } + + void manager::factor(polynomial const * p, factors & r, factor_params const & params) { + m_imp->factor(p, r, params); + } + + polynomial * manager::to_polynomial(unsigned sz, numeral const * p, var x) { + return m_imp->to_polynomial(sz, p, x); + } + + polynomial * manager::mk_glex_monic(polynomial const * p) { + return m_imp->mk_glex_monic(p); + } + + polynomial * manager::translate(polynomial const * p, var x, numeral const & v) { + return m_imp->translate(p, x, v); + } + + void manager::translate(polynomial const * p, unsigned xs_sz, var const * xs, numeral const * vs, polynomial_ref & r) { + return m_imp->translate(p, xs_sz, xs, vs, r); + } + + polynomial * manager::mod_d(polynomial const * p, var2degree const & x2d) { + return m_imp->mod_d(p, x2d); + } + + void manager::exact_pseudo_division_mod_d(polynomial const * p, polynomial const * q, var x, var2degree const & x2d, + polynomial_ref & Q, polynomial_ref & R) { + m_imp->exact_pseudo_division_mod_d(p, q, x, x2d, Q, R); + } + + void manager::eval(polynomial const * p, var2mpbqi const & x2v, mpbqi & r) { + return m_imp->eval(p, x2v, r); + } + + void manager::eval(polynomial const * p, var2mpq const & x2v, mpq & r) { + return m_imp->eval(p, x2v, r); + } + + void manager::eval(polynomial const * p, var2anum const & x2v, algebraic_numbers::anum & r) { + return m_imp->eval(p, x2v, r); + } + + void manager::display(std::ostream & out, monomial const * m, display_var_proc const & proc, bool user_star) const { + m->display(out, proc, user_star); + } + + void manager::display(std::ostream & out, polynomial const * p, display_var_proc const & proc, bool use_star) const { + SASSERT(m_imp->consistent_coeffs(p)); + p->display(out, m_imp->m_manager, proc, use_star); + } + + void manager::display_smt2(std::ostream & out, polynomial const * p, display_var_proc const & proc) const { + p->display_smt2(out, m_imp->m_manager, proc); + } +}; + +polynomial::polynomial * convert(polynomial::manager & sm, polynomial::polynomial * p, polynomial::manager & tm, + polynomial::var x, unsigned max_d) { + ptr_buffer ms; + polynomial::numeral_manager & nm = tm.m(); + _scoped_numeral_buffer as(nm); + unsigned sz = sm.size(p); + if (&sm == &tm) { + // same source and target manager. + // So, we just return p + return p; + } + else if (&(sm.mm()) == &(tm.mm())) { + // polynomial managers share the same monomial manager. + // So, we don't need to convert monomials. + for (unsigned i = 0; i < sz; i++) { + polynomial::monomial * m = sm.get_monomial(p, i); + if (x == polynomial::null_var || sm.degree_of(m, x) <= max_d) { + ms.push_back(m); + as.push_back(polynomial::numeral()); + nm.set(as.back(), sm.coeff(p, i)); + } + } + } + else { + for (unsigned i = 0; i < sz; i++) { + polynomial::monomial * m = sm.get_monomial(p, i); + if (x == polynomial::null_var || sm.degree_of(m, x) <= max_d) { + ms.push_back(tm.convert(m)); + as.push_back(polynomial::numeral()); + nm.set(as.back(), sm.coeff(p, i)); + } + } + } + return tm.mk_polynomial(as.size(), as.c_ptr(), ms.c_ptr()); +} + +std::ostream & operator<<(std::ostream & out, polynomial_ref_vector const & seq) { + unsigned sz = seq.size(); + for (unsigned i = 0; i < sz; i++) { + seq.m().display(out, seq.get(i)); + out << "\n"; + } + return out; +} diff --git a/lib/polynomial.h b/lib/polynomial.h new file mode 100644 index 000000000..f6370e5fa --- /dev/null +++ b/lib/polynomial.h @@ -0,0 +1,1380 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + polynomial.h + +Abstract: + + Goodies for creating and handling polynomials. + +Author: + + Leonardo (leonardo) 2011-11-15 + +Notes: + +--*/ +#ifndef _POLYNOMIAL_H_ +#define _POLYNOMIAL_H_ + +#include"mpz.h" +#include"rational.h" +#include"obj_ref.h" +#include"ref_vector.h" +#include"z3_exception.h" +#include"scoped_numeral.h" +#include"scoped_numeral_vector.h" +#include"params.h" +#include"mpbqi.h" + +class small_object_allocator; + +namespace algebraic_numbers { + class anum; + class manager; +}; + +namespace polynomial { + typedef unsigned var; + const var null_var = UINT_MAX; + typedef svector var_vector; + class monomial; + + int lex_compare(monomial const * m1, monomial const * m2); + int lex_compare2(monomial const * m1, monomial const * m2, var min_var); + int graded_lex_compare(monomial const * m1, monomial const * m2); + int rev_lex_compare(monomial const * m1, monomial const * m2); + int graded_rev_lex_compare(monomial const * m1, monomial const * m2); + + // It is used only for signing cancellation. + class polynomial_exception : public default_exception { + public: + polynomial_exception(char const * msg):default_exception(msg) {} + }; + + /** + \brief A mapping from variables to degree + */ + class var2degree { + unsigned_vector m_var2degree; + public: + void set_degree(var x, unsigned d) { m_var2degree.setx(x, d, 0); } + unsigned degree(var x) const { return m_var2degree.get(x, 0); } + void display(std::ostream & out) const; + friend std::ostream & operator<<(std::ostream & out, var2degree const & ideal) { ideal.display(out); return out; } + }; + + template + class var2value { + public: + virtual ValManager & m() const = 0; + virtual bool contains(var x) const = 0; + virtual Value const & operator()(var x) const = 0; + }; + + typedef var2value var2mpq; + typedef var2value var2mpbqi; + typedef var2value var2anum; + + class monomial_manager; + + /** + \brief Parameters for polynomial factorization. + */ + struct factor_params { + unsigned m_max_p; //!< factor in GF_p using primes p <= m_max_p (default UINT_MAX) + unsigned m_p_trials; //!< Number of different finite factorizations: G_p1 ... G_pk, where k < m_p_trials + unsigned m_max_search_size; //!< Threshold on the search space. + factor_params(); + factor_params(unsigned max_p, unsigned p_trials, unsigned max_search_size); + void updt_params(params_ref const & p); + static void get_param_descrs(param_descrs & r); + }; + + struct display_var_proc { + virtual void operator()(std::ostream & out, var x) const { out << "x" << x; } + }; + + class polynomial; + class manager; + typedef obj_ref monomial_ref; + typedef obj_ref polynomial_ref; + typedef ref_vector polynomial_ref_vector; + typedef ptr_vector polynomial_vector; + + class manager { + public: + typedef unsynch_mpz_manager numeral_manager; + typedef numeral_manager::numeral numeral; + typedef svector numeral_vector; + typedef _scoped_numeral scoped_numeral; + typedef _scoped_numeral_vector scoped_numeral_vector; + + /** + \brief Contains a factorization of a polynomial of the form c * (f_1)^k_1 * ... (f_n)^k_n + */ + class factors { + vector m_factors; + svector m_degrees; + manager & m_manager; + numeral m_constant; + unsigned m_total_factors; + public: + factors(manager & m); + ~factors(); + + /** + \brief Numer of distinct factors (not counting multiplicities). + */ + unsigned distinct_factors() const { return m_factors.size(); } + + /** + \brief Numer of distinct factors (counting multiplicities). + */ + unsigned total_factors() const { return m_total_factors; } + + /** + \brief Returns the factor at given position. + */ + polynomial_ref operator[](unsigned i) const; + + /** + \brief Returns the constant (c above). + */ + numeral const & get_constant() const { return m_constant; } + + /** + \brief Sets the constant. + */ + void set_constant(numeral const & constant); + + /** + \brief Returns the degree of a factor (k_i above). + */ + unsigned get_degree(unsigned i) const { return m_degrees[i]; } + + /** + \brief Sets the degree of a factor. + */ + void set_degree(unsigned i, unsigned degree); + + /** + \brief Adds a polynomial to the factorization. + */ + void push_back(polynomial * p, unsigned degree); + + /** + \brief Returns the polynomial that this factorization represents. + */ + void multiply(polynomial_ref & out) const; + + manager & m() const { return m_manager; } + manager & pm() const { return m_manager; } + + void display(std::ostream& out) const; + + void reset(); + + friend std::ostream & operator<<(std::ostream & out, factors const & f) { + f.display(out); + return out; + } + }; + + struct imp; + private: + imp * m_imp; + public: + manager(numeral_manager & m, monomial_manager * mm = 0); + manager(numeral_manager & m, small_object_allocator * a); + ~manager(); + + numeral_manager & m() const; + monomial_manager & mm() const; + small_object_allocator & allocator() const; + + /** + \brief Return true if Z_p[X1, ..., Xn] + */ + bool modular() const; + /** + \brief Return p in Z_p[X1, ..., Xn] + \pre modular + */ + numeral const & p() const; + + /** + \brief Set manager as Z[X1, ..., Xn] + */ + void set_z(); + /** + \brief Set manager as Z_p[X1, ..., Xn] + */ + void set_zp(numeral const & p); + void set_zp(uint64 p); + + void set_cancel(bool f); + + /** + \brief Abstract event handler. + */ + class del_eh { + friend class manager; + del_eh * m_next; + public: + del_eh():m_next(0) {} + virtual void operator()(polynomial * p) = 0; + }; + + /** + \brief Install a "delete polynomial" event handler. + The even hanlder is not owned by the polynomial manager. + If eh = 0, then it uninstall the event handler. + */ + void add_del_eh(del_eh * eh); + void remove_del_eh(del_eh * eh); + + /** + \brief Create a new variable. + */ + var mk_var(); + + /** + \brief Return the number of variables in the manager. + */ + unsigned num_vars() const; + + /** + \brief Return true if x is a valid variable in this manager. + */ + bool is_valid(var x) const { return x < num_vars(); } + + /** + \brief Increment reference counter. + */ + void inc_ref(polynomial * p); + void inc_ref(monomial * m); + + /** + \brief Decrement reference counter. + */ + void dec_ref(polynomial * p); + void dec_ref(monomial * m); + + /** + \brief Return an unique id associated with \c m. + This id can be used to implement efficient mappings from monomial to data. + */ + static unsigned id(monomial const * m); + + /** + \brief Return an unique id associated with \c m. + This id can be used to implement efficient mappings from polynomial to data. + */ + static unsigned id(polynomial const * p); + + /** + \brief Return true if \c m is the unit monomial. + */ + static bool is_unit(monomial const * m); + + /** + \brief Return true if \c p is the zero polynomial. + */ + static bool is_zero(polynomial const * p); + + /** + \brief Return true if \c p is the constant polynomial. + */ + static bool is_const(polynomial const * p); + + /** + \brief Return true if \c m is univariate. + */ + static bool is_univariate(monomial const * m); + + /** + \brief Return true if \c p is an univariate polynomial. + */ + static bool is_univariate(polynomial const * p); + + /** + \brief Return true if m is linear (i.e., it is of the form 1 or x). + */ + static bool is_linear(monomial const * m); + + /** + \brief Return true if all monomials in p are linear. + */ + static bool is_linear(polynomial const * p); + + /** + \brief Return the degree of variable x in p. + */ + static unsigned degree(polynomial const * p, var x); + + /** + \brief Return the polynomial total degree. That is, + the degree of the monomial of maximal degree in p. + */ + static unsigned total_degree(polynomial const * p); + + /** + \brief Return the number of monomials in p. + */ + static unsigned size(polynomial const * p); + + /** + \brief Return the maximal variable occurring in p. + + Return null_var if p is a constant polynomial. + */ + static var max_var(polynomial const * p); + + /** + \brief Return the coefficient of the i-th monomial in p. + + \pre i < size(p) + */ + static numeral const & coeff(polynomial const * p, unsigned i); + + /** + \brief Given an univariate polynomial, return the coefficient of x_k + */ + static numeral const & univ_coeff(polynomial const * p, unsigned k); + + /** + \brief Return a polynomial h that is the coefficient of x^k in p. + if p does not contain any monomial containing x^k, then return 0. + */ + polynomial * coeff(polynomial const * p, var x, unsigned k); + + polynomial * coeff(polynomial const * p, var x, unsigned k, polynomial_ref & reduct); + + /** + \brief Return true if the coefficient of x^k in p is an integer != 0. + */ + bool nonzero_const_coeff(polynomial const * p, var x, unsigned k); + + /** + \brief Return true if the coefficient of x^k in p is an integer, and store it in c. + */ + bool const_coeff(polynomial const * p, var x, unsigned k, numeral & c); + + /** + \brief Store in c the integer content of p. + */ + void int_content(polynomial const * p, numeral & c); + + /** + \brief Returns sum of the absolute value of the coefficients. + */ + void abs_norm(polynomial const * p, numeral & norm); + + /** + \brief Return the leading integer coefficient (among the loeding power of x, one is picked arbitrary). + */ + numeral const & numeral_lc(polynomial const * p, var x); + + /** + \brief Return the trailing integer coefficient (i.e. the constant term). + */ + numeral const & numeral_tc(polynomial const * p); + + /** + \brief Return the content i*c of p with respect to variable x. + If p is a polynomial in Z[y_1, ..., y_k, x], then c is a polynomial in Z[y_1, ..., y_k] + */ + void content(polynomial const * p, var x, numeral & i, polynomial_ref & c); + void content(polynomial const * p, var x, polynomial_ref & c); + + /** + \brief Return the primitive polynomial of p with respect to variable x. + If p is a polynomial in Z[y_1, ..., y_k, x], then pp is a polynomial in Z[y_1, ..., y_k, x] + */ + void primitive(polynomial const * p, var x, polynomial_ref & pp); + + /** + \brief Return the integer content, content, and primitive polynomials of p with respect to x. + i*c*pp = p + If p is a polynomial in Z[y_1, ..., y_k, x], then + c is a polynomial in Z[y_1, ..., y_k] + pp is a polynomial in Z[y_1, ..., y_k, x] + */ + void icpp(polynomial const * p, var x, numeral & i, polynomial_ref & c, polynomial_ref & pp); + + polynomial * flip_sign_if_lm_neg(polynomial const * p); + + /** + \breif Return the gcd g of p and q. + */ + void gcd(polynomial const * p, polynomial const * q, polynomial_ref & g); + + /** + \brief Return the i-th monomial of p. + */ + static monomial * get_monomial(polynomial const * p, unsigned i); + + /** + \brief Return the total degree of the given monomial. + */ + static unsigned total_degree(monomial const * m); + + /** + \brief Return the size (number of variables) of the given monomial. + */ + static unsigned size(monomial const * m); + + /** + \brief Convert a monomial created in a different manager. + */ + monomial * convert(monomial const * m); + + /** + \brief Return the i-th variable in the given monomial. + + \pre i < size(m) + */ + static var get_var(monomial const * m, unsigned i); + + /** + \brief Return the degree of the i-th variable in the given monomial. + + \pre i < size(m) + */ + static unsigned degree(monomial const * m, unsigned i); + + /** + \brief Return the degree of x in the given monomial. + */ + static unsigned degree_of(monomial const * m, var x); + + /** + \brief Return hash code for the given monomial. + */ + static unsigned hash(monomial const * m); + + /** + \brief Return hash code for the given polynomial. + */ + unsigned hash(polynomial const * p); + + /** + \brief Create the unit monomial. That is, the monomial of size zero. + */ + monomial * mk_unit(); + + /** + \brief Create the zero polynomial. That is, the polynomial of size zero. + */ + polynomial * mk_zero(); + + /** + \brief Create the constant polynomial \c r. + + \warning r is a number managed by the numeral_manager in the polynomial manager + + \warning r is reset. + */ + polynomial * mk_const(numeral & r); + + /** + \brief Create the constant polynomial \c r. + + \pre r must be an integer + */ + polynomial * mk_const(rational const & r); + + /** + \brief Create an univariate monomial. + */ + monomial * mk_monomial(var x); + + /** + \brief Create an univariate monomial. + */ + monomial * mk_monomial(var x, unsigned k); + + /** + \brief Create the monomial + + xs[0]*...*xs[sz-1] + + Remark: xs may contain duplicate variables. + + \warning The elements of xs will be reordered. + */ + monomial * mk_monomial(unsigned sz, var * xs); + + /** + \brief Create the polynomial x^k + */ + polynomial * mk_polynomial(var x, unsigned k = 1); + + /** + \brief Create the polynomial + + as[0]*ms[0] + ... + as[sz-1]*ms[sz - 1] + + \pre as's must be integers + */ + polynomial * mk_polynomial(unsigned sz, rational const * as, monomial * const * ms); + + /** + \brief Create the polynomial + + as[0]*ms[0] + ... + as[sz-1]*ms[sz - 1] + + \warning as's are numbers managed by mpq_manager in the polynomial manager + + \warning the numerals in as are reset. + */ + polynomial * mk_polynomial(unsigned sz, numeral * as, monomial * const * ms); + + /** + \brief Create the linear polynomial + + as[0]*xs[0] + ... + as[sz-1]*xs[sz-1] + c + + \pre as's must be integers + */ + polynomial * mk_linear(unsigned sz, rational const * as, var const * xs, rational const & c); + + /** + \brief Create the linear polynomial + + as[0]*xs[0] + ... + as[sz-1]*xs[sz-1] + c + + \warning as's are numbers managed by mpq_manager in the polynomial manager + + \warning the numerals in as are reset. + */ + polynomial * mk_linear(unsigned sz, numeral * as, var const * xs, numeral & c); + + /** + \brief Create an univariate polynomial of degree n + + as[0] + as[1]*x + as[2]*x^2 + ... + as[n]*x^n + + \warning \c as must contain n+1 elements. + */ + polynomial * mk_univariate(var x, unsigned n, numeral * as); + + /** + \brief Return -p + */ + polynomial * neg(polynomial const * p); + + /** + \brief Return p1 + p2 + */ + polynomial * add(polynomial const * p1, polynomial const * p2); + + /** + \brief Return p1 - p2 + */ + polynomial * sub(polynomial const * p1, polynomial const * p2); + + /** + \brief Return a1*m1*p1 + a2*m2*p2 + */ + polynomial * addmul(numeral const & a1, monomial const * m1, polynomial const * p1, numeral const & a2, monomial const * m2, polynomial const * p2); + + /** + \brief Return p1 + a2*m2*p2 + */ + polynomial * addmul(polynomial const * p1, numeral const & a2, monomial const * m2, polynomial const * p2); + + /** + \brief Return p1 + a2*p2 + */ + polynomial * addmul(polynomial const * p1, numeral const & a2, polynomial const * p2); + + /** + \brief Return a * p + */ + polynomial * mul(numeral const & a, polynomial const * p); + polynomial * mul(rational const & a, polynomial const * p); + + /** + \brief Return p1 * p2 + */ + polynomial * mul(polynomial const * p1, polynomial const * p2); + + /** + \brief Return m1 * m2 + */ + monomial * mul(monomial const * m1, monomial const * m2); + + /** + \brief Return a * m * p + */ + polynomial * mul(numeral const & a, monomial const * m, polynomial const * p); + + /** + \brief Return m * p + */ + polynomial * mul(monomial const * m, polynomial const * p); + + /** + \brief Return true if m2 divides m1 + */ + bool div(monomial const * m1, monomial const * m2); + + /** + \brief Return true if m2 divides m1, and store the result in r. + */ + bool div(monomial const * m1, monomial const * m2, monomial * & r); + + /** + \brief Newton interpolation algorithm for multivariate polynomials. + */ + void newton_interpolation(var x, unsigned d, numeral const * inputs, polynomial * const * outputs, polynomial_ref & r); + + /** + \brief Return the GCD of m1 and m2. + Store in q1 and q2 monomials s.t. + m1 = gcd * q1 + m2 = gcd * q2 + */ + monomial * gcd(monomial const * m1, monomial const * m2, monomial * & q1, monomial * & q2); + + /** + \brief Return true if the gcd of m1 and m2 is not 1. + In that case, store in q1 and q2 monomials s.t. + m1 = gcd * q1 + m2 = gcd * q2 + */ + bool unify(monomial const * m1, monomial const * m2, monomial * & q1, monomial * & q2); + + /** + \brief Return m^k + */ + monomial * pw(monomial const * m, unsigned k); + + /** + \brief Return the polynomial p^k + */ + void pw(polynomial const * p, unsigned k, polynomial_ref & r); + + /** + \brief Return dp/dx + */ + polynomial * derivative(polynomial const * p, var x); + + /** + \brief r := square free part of p with respect to x + */ + void square_free(polynomial const * p, var x, polynomial_ref & r); + + /** + \brief Return true if p is square free with respect to x + */ + bool is_square_free(polynomial const * p, var x); + + /** + \brief r := square free part of p + */ + void square_free(polynomial const * p, polynomial_ref & r); + + /** + \brief Return true if p is square free + */ + bool is_square_free(polynomial const * p); + + /** + \brief Return true if p1 == p2. + */ + bool eq(polynomial const * p1, polynomial const * p2); + + /** + \brief Rename variables using the given permutation. + + sz must be num_vars() + */ + void rename(unsigned sz, var const * xs); + + /** + \brief Given an univariate polynomial p(x), + return the polynomial x^n * p(1/x), where n = degree(p) + + If u is a nonzero root of p, then 1/u is a root the resultant polynomial. + */ + polynomial * compose_1_div_x(polynomial const * p); + + /** + \brief Given an univariate polynomial p(x), + return the polynomial y^n * p(x/y), where n = degree(p) + */ + polynomial * compose_x_div_y(polynomial const * p, var y); + + /** + \brief Given an univariate polynomial p(x), return p(-x) + */ + polynomial * compose_minus_x(polynomial const * p); + + /** + \brief Given an univariate polynomial p(x) and a polynomial q(y_1, ..., y_n), + return a polynomial r(y_1, ..., y_n) = p(q(y_1, ..., y_n)). + */ + void compose(polynomial const * p, polynomial const * q, polynomial_ref & r); + + /** + \brief Given an univariate polynomial p(x), return the polynomial r(y) = p(y) + */ + polynomial * compose_y(polynomial const * p, var y); + + /** + \brief Given an univariate polynomial p(x), return the polynomial r(x, y) = p(x - y). + The result is stored in r. + */ + void compose_x_minus_y(polynomial const * p, var y, polynomial_ref & r); + + /** + \brief Given an univariate polynomial p(x), return the polynomial r(x, y) = p(x + y). + The result is stored in r. + */ + void compose_x_plus_y(polynomial const * p, var y, polynomial_ref & r); + + /** + \brief Given an univariate polynomial p(x), return the polynomial r(x) = p(x - c). + The result is stored in r. + */ + void compose_x_minus_c(polynomial const * p, numeral const & c, polynomial_ref & r); + + /** + \brief Return the exact pseudo remainder of p by q, assuming x is the maximal variable. + + See comments at pseudo_division_core at polynomial.cpp for a description of exact pseudo division. + */ + void exact_pseudo_remainder(polynomial const * p, polynomial const * q, var x, polynomial_ref & R); + + void exact_pseudo_remainder(polynomial const * p, polynomial const * q, polynomial_ref & R) { + exact_pseudo_remainder(p, q, max_var(q), R); + } + + /** + \brief Return the pseudo remainder of p by q, assuming x is the maximal variable. + + See comments at pseudo_division_core at polynomial.cpp for a description of exact pseudo division. + */ + void pseudo_remainder(polynomial const * p, polynomial const * q, var x, unsigned & d, polynomial_ref & R); + + void pseudo_remainder(polynomial const * p, polynomial const * q, unsigned & d, polynomial_ref & R) { + pseudo_remainder(p, q, max_var(q), d, R); + } + + /** + \brief Return the exact pseudo division quotient and remainder. + + See comments at pseudo_division_core at polynomial.cpp for a description of exact pseudo division. + */ + void exact_pseudo_division(polynomial const * p, polynomial const * q, var x, polynomial_ref & Q, polynomial_ref & R); + + void exact_pseudo_division(polynomial const * p, polynomial const * q, polynomial_ref & Q, polynomial_ref & R) { + exact_pseudo_division(p, q, max_var(q), Q, R); + } + + /** + \brief Return the pseudo division quotient and remainder. + + See comments at pseudo_division_core at polynomial.cpp for a description of exact pseudo division. + */ + void pseudo_division(polynomial const * p, polynomial const * q, var x, unsigned & d, polynomial_ref & Q, polynomial_ref & R); + + void pseudo_division(polynomial const * p, polynomial const * q, unsigned & d, polynomial_ref & Q, polynomial_ref & R) { + pseudo_division(p, q, max_var(q), d, Q, R); + } + + /** + \brief Return p/q if q divides p. + + \pre q divides p. + + \remark p and q may be multivariate polynomials. + */ + polynomial * exact_div(polynomial const * p, polynomial const * q); + + /** + \brief Return true if q divides p. + */ + bool divides(polynomial const * q, polynomial const * p); + + /** + \brief Return p/c if c divides p. + + \pre c divides p. + + \remark p may be multivariate polynomial. + */ + polynomial * exact_div(polynomial const * p, numeral const & c); + + /** + \brief Store in r the quasi-resultant of p and q with respect to variable x. + + Assume p and q are polynomials in Q[y_1, ..., y_n, x]. + Then r is a polynomial in Q[y_1, ..., y_n]. + Moreover, Forall a_1, ..., a_n, b, + if p(a_1, ..., a_n, b) = q(a_1, ..., a_n, b) = 0 + then r(a_1, ..., a_n) = 0 + + \pre p and q must contain x. + + \remark if r is the zero polynomial, then for any complex numbers a_1, ..., a_n, + the univariate polynomials p(a_1, ..., a_n, x) and q(a_1, ..., a_n, x) in C[x] have + a common root. C is the field of the complex numbers. + */ + void quasi_resultant(polynomial const * p, polynomial const * q, var x, polynomial_ref & r); + + /** + \brief Store in r the resultant of p and q with respect to variable x. + See comments in polynomial.cpp for more details + */ + void resultant(polynomial const * p, polynomial const * q, var x, polynomial_ref & r); + + /** + \brief Stroe in r the discriminant of p with respect to variable x. + discriminant(p, x, r) == resultant(p, derivative(p, x), x, r) + */ + void discriminant(polynomial const * p, var x, polynomial_ref & r); + + /** + \brief Store in S the principal subresultant coefficients for p and q. + */ + void psc_chain(polynomial const * p, polynomial const * q, var x, polynomial_ref_vector & S); + + /** + \brief Make sure the GCD of the coefficients is one. + + Make sure that the polynomial is a member of the polynomial ring of this manager. + If manager is Z_p[X1, ..., Xn], and polynomial is in Z[X1, ..., Xn] return a new + one where all coefficients are in Z_p + */ + polynomial * normalize(polynomial const * p); + + /** + \brief Return true if p is a square, and store its square root in r. + */ + bool sqrt(polynomial const * p, polynomial_ref & r); + + /** + \brief Return true if p is always positive for any assignment of its variables. + + This is an incomplete check. This method just check if all monomials are powers of two, + and the coefficients are positive. + */ + bool is_pos(polynomial const * p); + + /** + \brief Return true if p is always negative for any assignment of its variables. + + This is an incomplete check. + */ + bool is_neg(polynomial const * p); + + /** + \brief Return true if p is always non-positive for any assignment of its variables. + + This is an incomplete check. + */ + bool is_nonpos(polynomial const * p); + + /** + \brief Return true if p is always non-negative for any assignment of its variables. + + This is an incomplete check. + */ + bool is_nonneg(polynomial const * p); + + /** + \brief Make sure the monomials in p are sorted using lexicographical order. + Remark: the maximal monomial is at position 0. + */ + void lex_sort(polynomial * p); + + /** + \brief Collect variables that occur in p into xs + */ + void vars(polynomial const * p, var_vector & xs); + + /** + \brief Evaluate polynomial p using the assignment [x_1 -> v_1, ..., x_n -> v_n]. + The result is store in r. + All variables occurring in p must be in xs. + */ + void eval(polynomial const * p, var2mpbqi const & x2v, mpbqi & r); + void eval(polynomial const * p, var2mpq const & x2v, mpq & r); + void eval(polynomial const * p, var2anum const & x2v, algebraic_numbers::anum & r); + + /** + \brief Apply substitution [x_1 -> v_1, ..., x_n -> v_n]. + That is, given p \in Z[x_1, ..., x_n, y_1, ..., y_m] return a polynomial + r \in Z[y_1, ..., y_m]. + Moreover forall a_1, ..., a_m in Q + sign(r(a_1, ..., a_m)) == sign(p(v_1, ..., v_n, a_1, ..., a_m)) + */ + polynomial * substitute(polynomial const * p, var2mpq const & x2v); + polynomial * substitute(polynomial const * p, unsigned xs_sz, var const * xs, mpq const * vs); + + /** + \brief Apply substitution [x_1 -> v_1, ..., x_n -> v_n]. + That is, given p \in Z[x_1, ..., x_n, y_1, ..., y_m] return + polynomial r(y_1, ..., y_m) = p(v_1, ..., v_n, y_1, ..., y_m) in Z[y_1, ..., y_m]. + */ + polynomial * substitute(polynomial const * p, unsigned xs_sz, var const * xs, numeral const * vs); + + /** + \brief Apply substitution [x -> v]. + That is, given p \in Z[x, y_1, ..., y_m] return + polynomial r(y_1, ..., y_m) = p(v, y_1, ..., y_m) in Z[y_1, ..., y_m]. + */ + polynomial * substitute(polynomial const * p, var x, numeral const & v) { + return substitute(p, 1, &x, &v); + } + + /** + \brief Factorize the given polynomial p and store its factors in r. + */ + void factor(polynomial const * p, factors & r, factor_params const & params = factor_params()); + + /** + \brief Dense univariate polynomial to sparse polynomial. + */ + polynomial * to_polynomial(unsigned sz, numeral const * p, var x); + polynomial * to_polynomial(numeral_vector const & p, var x) { + return to_polynomial(p.size(), p.c_ptr(), x); + } + + /** + \brief Make the leading monomial (with respect to graded lexicographical order) monic. + + \pre numeral_manager must be a field. + */ + polynomial * mk_glex_monic(polynomial const * p); + + /** + \brief Return p'(y_1, ..., y_n, x) = p(y_1, ..., y_n, x + v) + */ + polynomial * translate(polynomial const * p, var x, numeral const & v); + + /** + \brief Store p'(y_1, ..., y_n, x_1, ..., x_m) = p(y_1, ..., y_n, x_1 + v_1, ..., x_m + v_m) + into r. + */ + void translate(polynomial const * p, unsigned xs_sz, var const * xs, numeral const * vs, polynomial_ref & r); + void translate(polynomial const * p, var_vector const & xs, numeral_vector const & vs, polynomial_ref & r) { + SASSERT(xs.size() == vs.size()); + translate(p, xs.size(), xs.c_ptr(), vs.c_ptr(), r); + } + + /** + \brief Remove monomials m if it contains x^k and x2d[x] >= k + */ + polynomial * mod_d(polynomial const * p, var2degree const & x2d); + + /** + \brief (exact) Pseudo division modulo var->degree mapping. + */ + void exact_pseudo_division_mod_d(polynomial const * p, polynomial const * q, var x, var2degree const & x2d, polynomial_ref & Q, polynomial_ref & R); + + void display(std::ostream & out, monomial const * m, display_var_proc const & proc = display_var_proc(), bool use_star = true) const; + + void display(std::ostream & out, polynomial const * p, display_var_proc const & proc = display_var_proc(), bool use_star = false) const; + + void display_smt2(std::ostream & out, polynomial const * p, display_var_proc const & proc = display_var_proc()) const; + + friend std::ostream & operator<<(std::ostream & out, polynomial_ref const & p) { + p.m().display(out, p); + return out; + } + }; + + typedef manager::factors factors; + typedef manager::numeral numeral; + typedef manager::numeral_manager numeral_manager; + typedef manager::scoped_numeral scoped_numeral; + typedef manager::scoped_numeral_vector scoped_numeral_vector; + + class scoped_set_z { + manager & m; + bool m_modular; + scoped_numeral m_p; + public: + scoped_set_z(manager & _m):m(_m), m_modular(m.modular()), m_p(m.m()) { m_p = m.p(); m.set_z(); } + ~scoped_set_z() { if (m_modular) m.set_zp(m_p); } + }; + + class scoped_set_zp { + manager & m; + bool m_modular; + scoped_numeral m_p; + public: + scoped_set_zp(manager & _m, numeral const & p):m(_m), m_modular(m.modular()), m_p(m.m()) { m_p = m.p(); m.set_zp(p); } + scoped_set_zp(manager & _m, uint64 p):m(_m), m_modular(m.modular()), m_p(m.m()) { m_p = m.p(); m.set_zp(p); } + ~scoped_set_zp() { if (m_modular) m.set_zp(m_p); else m.set_z(); } + }; +}; + +typedef polynomial::polynomial_ref polynomial_ref; +typedef polynomial::polynomial_ref_vector polynomial_ref_vector; + +polynomial::polynomial * convert(polynomial::manager & sm, polynomial::polynomial * p, polynomial::manager & tm, + polynomial::var x = polynomial::null_var, unsigned max_d = UINT_MAX); + +inline polynomial::polynomial * convert(polynomial::manager & sm, polynomial_ref const & p, polynomial::manager & tm) { + SASSERT(&sm == &(p.m())); + return convert(sm, p.get(), tm); +} + +inline polynomial_ref neg(polynomial_ref const & p) { + polynomial::manager & m = p.m(); + return polynomial_ref(m.neg(p), m); +} + +inline polynomial_ref operator-(polynomial_ref const & p) { + polynomial::manager & m = p.m(); + return polynomial_ref(m.neg(p), m); +} + +inline polynomial_ref operator+(polynomial_ref const & p1, polynomial_ref const & p2) { + polynomial::manager & m = p1.m(); + return polynomial_ref(m.add(p1, p2), m); +} + +inline polynomial_ref operator+(polynomial_ref const & p1, int n) { + polynomial::manager & m = p1.m(); + polynomial_ref tmp(m.mk_const(rational(n)), m); + return p1 + tmp; +} + +inline polynomial_ref operator+(polynomial_ref const & p1, rational const & n) { + polynomial::manager & m = p1.m(); + polynomial_ref tmp(m.mk_const(n), m); + return p1 + tmp; +} + +inline polynomial_ref operator+(polynomial::numeral const & n, polynomial_ref const & p) { + polynomial::manager & m = p.m(); + polynomial_ref tmp(m.mk_const(n), m); + return p + tmp; +} + +inline polynomial_ref operator+(polynomial_ref const & p, polynomial::numeral const & n) { + return operator+(n, p); +} + +inline polynomial_ref operator-(polynomial_ref const & p1, polynomial_ref const & p2) { + polynomial::manager & m = p1.m(); + return polynomial_ref(m.sub(p1, p2), m); +} + +inline polynomial_ref operator-(polynomial_ref const & p1, int n) { + polynomial::manager & m = p1.m(); + polynomial_ref tmp(m.mk_const(rational(n)), m); + return p1 - tmp; +} + +inline polynomial_ref operator-(polynomial_ref const & p1, rational const & n) { + polynomial::manager & m = p1.m(); + polynomial_ref tmp(m.mk_const(n), m); + return p1 - tmp; +} + +inline polynomial_ref operator-(polynomial::numeral const & n, polynomial_ref const & p) { + polynomial::manager & m = p.m(); + polynomial_ref tmp(m.mk_const(n), m); + return p - tmp; +} + +inline polynomial_ref operator-(polynomial_ref const & p, polynomial::numeral const & n) { + return operator-(n, p); +} + +inline polynomial_ref operator*(polynomial_ref const & p1, polynomial_ref const & p2) { + polynomial::manager & m = p1.m(); + return polynomial_ref(m.mul(p1, p2), m); +} + +inline polynomial_ref operator*(rational const & n, polynomial_ref const & p) { + polynomial::manager & m = p.m(); + return polynomial_ref(m.mul(n, p), m); +} + +inline polynomial_ref operator*(int n, polynomial_ref const & p) { + polynomial::manager & m = p.m(); + return polynomial_ref(m.mul(rational(n), p), m); +} + +inline polynomial_ref operator*(polynomial::numeral const & n, polynomial_ref const & p) { + polynomial::manager & m = p.m(); + return polynomial_ref(m.mul(n, p), m); +} + +inline polynomial_ref operator*(polynomial_ref const & p, polynomial::numeral const & n) { + return operator*(n, p); +} + +inline bool eq(polynomial_ref const & p1, polynomial_ref const & p2) { + polynomial::manager & m = p1.m(); + return m.eq(p1, p2); +} + +inline polynomial_ref operator^(polynomial_ref const & p, int k) { + SASSERT(k > 0); + polynomial::manager & m = p.m(); + polynomial_ref r(m); + m.pw(p, k, r); + return polynomial_ref(r); +} + +inline polynomial_ref operator^(polynomial_ref const & p, unsigned k) { + return operator^(p, static_cast(k)); +} + +inline polynomial_ref derivative(polynomial_ref const & p, polynomial::var x) { + polynomial::manager & m = p.m(); + return polynomial_ref(m.derivative(p, x), m); +} + +inline bool is_zero(polynomial_ref const & p) { + return p.m().is_zero(p); +} + +inline bool is_const(polynomial_ref const & p) { + return p.m().is_const(p); +} + +inline bool is_linear(polynomial_ref const & p) { + return p.m().is_linear(p); +} + +inline bool is_univariate(polynomial_ref const & p) { + return p.m().is_univariate(p); +} + +inline unsigned total_degree(polynomial_ref const & p) { + return p.m().total_degree(p); +} + +inline polynomial::var max_var(polynomial_ref const & p) { + return p.m().max_var(p); +} + +inline unsigned degree(polynomial_ref const & p, polynomial::var x) { + return p.m().degree(p, x); +} + +inline unsigned size(polynomial_ref const & p) { + return p.m().size(p); +} + +inline polynomial_ref coeff(polynomial_ref const & p, polynomial::var x, unsigned k) { + polynomial::manager & m = p.m(); + return polynomial_ref(m.coeff(p, x, k), m); +} + +inline polynomial_ref lc(polynomial_ref const & p, polynomial::var x) { + polynomial::manager & m = p.m(); + return polynomial_ref(m.coeff(p, x, degree(p, x)), m); +} + +inline polynomial::numeral const & univ_coeff(polynomial_ref const & p, unsigned k) { + return p.m().univ_coeff(p, k); +} + +inline polynomial_ref content(polynomial_ref const & p, polynomial::var x) { + polynomial::manager & m = p.m(); + polynomial_ref r(m); + m.content(p, x, r); + return r; +} + +inline polynomial_ref primitive(polynomial_ref const & p, polynomial::var x) { + polynomial::manager & m = p.m(); + polynomial_ref r(m); + m.primitive(p, x, r); + return r; +} + +inline polynomial_ref gcd(polynomial_ref const & p, polynomial_ref const & q) { + polynomial::manager & m = p.m(); + polynomial_ref r(m); + m.gcd(p, q, r); + return r; +} + +inline bool is_square_free(polynomial_ref const & p, polynomial::var x) { + return p.m().is_square_free(p, x); +} + +inline polynomial_ref square_free(polynomial_ref const & p, polynomial::var x) { + polynomial::manager & m = p.m(); + polynomial_ref r(m); + m.square_free(p, x, r); + return r; +} + +inline bool is_square_free(polynomial_ref const & p) { + return p.m().is_square_free(p); +} + +inline polynomial_ref square_free(polynomial_ref const & p) { + polynomial::manager & m = p.m(); + polynomial_ref r(m); + m.square_free(p, r); + return r; +} + +inline polynomial_ref compose_y(polynomial_ref const & p, polynomial::var y) { + polynomial::manager & m = p.m(); + return polynomial_ref(m.compose_y(p, y), m); +} + +inline polynomial_ref compose_1_div_x(polynomial_ref const & p) { + polynomial::manager & m = p.m(); + return polynomial_ref(m.compose_1_div_x(p), m); +} + +inline polynomial_ref compose_x_div_y(polynomial_ref const & p, polynomial::var y) { + polynomial::manager & m = p.m(); + return polynomial_ref(m.compose_x_div_y(p, y), m); +} + +inline polynomial_ref compose_minus_x(polynomial_ref const & p) { + polynomial::manager & m = p.m(); + return polynomial_ref(m.compose_minus_x(p), m); +} + +inline polynomial_ref compose(polynomial_ref const & p, polynomial_ref const & g) { + polynomial::manager & m = p.m(); + polynomial_ref r(m); + m.compose(p, g, r); + return polynomial_ref(r); +} + +inline polynomial_ref compose_x_minus_y(polynomial_ref const & p, polynomial::var y) { + polynomial::manager & m = p.m(); + polynomial_ref r(m); + m.compose_x_minus_y(p, y, r); + return polynomial_ref(r); +} + +inline polynomial_ref compose_x_plus_y(polynomial_ref const & p, polynomial::var y) { + polynomial::manager & m = p.m(); + polynomial_ref r(m); + m.compose_x_plus_y(p, y, r); + return polynomial_ref(r); +} + +inline polynomial_ref compose_x_minus_c(polynomial_ref const & p, polynomial::numeral const & c) { + polynomial::manager & m = p.m(); + polynomial_ref r(m); + m.compose_x_minus_c(p, c, r); + return polynomial_ref(r); +} + +inline polynomial_ref exact_div(polynomial_ref const & p, polynomial_ref const & q) { + polynomial::manager & m = p.m(); + return polynomial_ref(m.exact_div(p, q), m); +} + +inline polynomial_ref normalize(polynomial_ref const & p) { + polynomial::manager & m = p.m(); + return polynomial_ref(m.normalize(p), m); +} + +inline bool sqrt(polynomial_ref const & p, polynomial_ref & r) { + return p.m().sqrt(p, r); +} + +inline polynomial_ref exact_pseudo_remainder(polynomial_ref const & p, polynomial_ref const & q, polynomial::var x) { + polynomial::manager & m = p.m(); + polynomial_ref r(m); + m.exact_pseudo_remainder(p, q, x, r); + return polynomial_ref(r); +} + +inline polynomial_ref exact_pseudo_remainder(polynomial_ref const & p, polynomial_ref const & q) { + return exact_pseudo_remainder(p, q, max_var(q)); +} + +inline polynomial_ref pseudo_remainder(polynomial_ref const & p, polynomial_ref const & q, polynomial::var x, unsigned & d) { + polynomial::manager & m = p.m(); + polynomial_ref r(m); + m.pseudo_remainder(p, q, x, d, r); + return polynomial_ref(r); +} + +inline polynomial_ref pseudo_remainder(polynomial_ref const & p, polynomial_ref const & q, unsigned & d) { + return pseudo_remainder(p, q, max_var(q), d); +} + +inline polynomial_ref exact_pseudo_division(polynomial_ref const & p, polynomial_ref const & q, polynomial::var x, + polynomial_ref & R) { + polynomial::manager & m = p.m(); + polynomial_ref Q(m); + m.exact_pseudo_division(p, q, x, Q, R); + return polynomial_ref(Q); +} + +inline polynomial_ref exact_pseudo_division(polynomial_ref const & p, polynomial_ref const & q, polynomial_ref & R) { + return exact_pseudo_division(p, q, max_var(q), R); +} + +inline polynomial_ref pseudo_division(polynomial_ref const & p, polynomial_ref const & q, polynomial::var x, unsigned & d, polynomial_ref & R) { + polynomial::manager & m = p.m(); + polynomial_ref Q(m); + m.pseudo_division(p, q, x, d, Q, R); + return polynomial_ref(Q); +} + +inline polynomial_ref pseudo_division(polynomial_ref const & p, polynomial_ref const & q, unsigned & d, polynomial_ref & R) { + return pseudo_division(p, q, max_var(q), d, R); +} + +inline polynomial_ref quasi_resultant(polynomial_ref const & p, polynomial_ref const & q, unsigned x) { + polynomial::manager & m = p.m(); + polynomial_ref r(m); + m.quasi_resultant(p, q, x, r); + return polynomial_ref(r); +} + +inline polynomial_ref resultant(polynomial_ref const & p, polynomial_ref const & q, unsigned x) { + polynomial::manager & m = p.m(); + polynomial_ref r(m); + m.resultant(p, q, x, r); + return polynomial_ref(r); +} + +inline polynomial_ref discriminant(polynomial_ref const & p, unsigned x) { + polynomial::manager & m = p.m(); + polynomial_ref r(m); + m.discriminant(p, x, r); + return polynomial_ref(r); +} + +inline bool is_pos(polynomial_ref const & p) { + return p.m().is_pos(p); +} + +inline bool is_neg(polynomial_ref const & p) { + return p.m().is_neg(p); +} + +inline bool is_nonpos(polynomial_ref const & p) { + return p.m().is_nonpos(p); +} + +inline bool is_nonneg(polynomial_ref const & p) { + return p.m().is_nonneg(p); +} + +inline void factor(polynomial_ref const & p, polynomial::factors & r, polynomial::factor_params const & params = polynomial::factor_params()) { + p.m().factor(p, r, params); +} + +std::ostream & operator<<(std::ostream & out, polynomial_ref_vector const & seq); + +#endif diff --git a/lib/polynomial_cache.cpp b/lib/polynomial_cache.cpp new file mode 100644 index 000000000..dee0e722d --- /dev/null +++ b/lib/polynomial_cache.cpp @@ -0,0 +1,235 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + polynomial_cache.cpp + +Abstract: + + "Hash-consing" for polynomials + +Author: + + Leonardo (leonardo) 2012-01-07 + +Notes: + +--*/ +#include"polynomial_cache.h" +#include"chashtable.h" + +namespace polynomial { + + struct poly_hash_proc { + manager & m; + poly_hash_proc(manager & _m):m(_m) {} + unsigned operator()(polynomial const * p) const { return m.hash(p); } + }; + + struct poly_eq_proc { + manager & m; + poly_eq_proc(manager & _m):m(_m) {} + bool operator()(polynomial const * p1, polynomial const * p2) const { return m.eq(p1, p2); } + }; + + struct psc_chain_entry { + polynomial const * m_p; + polynomial const * m_q; + var m_x; + unsigned m_hash; + unsigned m_result_sz; + polynomial ** m_result; + + psc_chain_entry(polynomial const * p, polynomial const * q, var x, unsigned h): + m_p(p), + m_q(q), + m_x(x), + m_hash(h), + m_result_sz(0), + m_result(0) { + } + + struct hash_proc { unsigned operator()(psc_chain_entry const * entry) const { return entry->m_hash; } }; + + struct eq_proc { + bool operator()(psc_chain_entry const * e1, psc_chain_entry const * e2) const { + return e1->m_p == e2->m_p && e1->m_q == e2->m_q && e1->m_x == e2->m_x; + } + }; + }; + + struct factor_entry { + polynomial const * m_p; + unsigned m_hash; + unsigned m_result_sz; + polynomial ** m_result; + + factor_entry(polynomial const * p, unsigned h): + m_p(p), + m_hash(h), + m_result_sz(0), + m_result(0) { + } + + struct hash_proc { unsigned operator()(factor_entry const * entry) const { return entry->m_hash; } }; + + struct eq_proc { + bool operator()(factor_entry const * e1, factor_entry const * e2) const { + return e1->m_p == e2->m_p; + } + }; + }; + + typedef chashtable polynomial_table; + typedef chashtable psc_chain_cache; + typedef chashtable factor_cache; + + struct cache::imp { + manager & m; + polynomial_table m_poly_table; + psc_chain_cache m_psc_chain_cache; + factor_cache m_factor_cache; + polynomial_ref_vector m_cached_polys; + svector m_in_cache; + small_object_allocator & m_allocator; + + imp(manager & _m):m(_m), m_poly_table(poly_hash_proc(m), poly_eq_proc(m)), m_cached_polys(m), m_allocator(m.allocator()) { + } + + ~imp() { + reset_psc_chain_cache(); + reset_factor_cache(); + } + + void del_psc_chain_entry(psc_chain_entry * entry) { + if (entry->m_result_sz != 0) + m_allocator.deallocate(sizeof(polynomial*)*entry->m_result_sz, entry->m_result); + entry->~psc_chain_entry(); + m_allocator.deallocate(sizeof(psc_chain_entry), entry); + } + + void del_factor_entry(factor_entry * entry) { + if (entry->m_result_sz != 0) + m_allocator.deallocate(sizeof(polynomial*)*entry->m_result_sz, entry->m_result); + entry->~factor_entry(); + m_allocator.deallocate(sizeof(factor_entry), entry); + } + + void reset_psc_chain_cache() { + psc_chain_cache::iterator it = m_psc_chain_cache.begin(); + psc_chain_cache::iterator end = m_psc_chain_cache.end(); + for (; it != end; ++it) { + del_psc_chain_entry(*it); + } + m_psc_chain_cache.reset(); + } + + void reset_factor_cache() { + factor_cache::iterator it = m_factor_cache.begin(); + factor_cache::iterator end = m_factor_cache.end(); + for (; it != end; ++it) { + del_factor_entry(*it); + } + m_factor_cache.reset(); + } + + unsigned pid(polynomial * p) const { return m.id(p); } + + polynomial * mk_unique(polynomial * p) { + if (m_in_cache.get(pid(p), false)) + return p; + polynomial * p_prime = m_poly_table.insert_if_not_there(p); + if (p == p_prime) { + m_cached_polys.push_back(p_prime); + m_in_cache.setx(pid(p_prime), true, false); + } + return p_prime; + } + + void psc_chain(polynomial * p, polynomial * q, var x, polynomial_ref_vector & S) { + p = mk_unique(p); + q = mk_unique(q); + unsigned h = hash_u_u(pid(p), pid(q)); + psc_chain_entry * entry = new (m_allocator.allocate(sizeof(psc_chain_entry))) psc_chain_entry(p, q, x, h); + psc_chain_entry * old_entry = m_psc_chain_cache.insert_if_not_there(entry); + if (entry != old_entry) { + entry->~psc_chain_entry(); + m_allocator.deallocate(sizeof(psc_chain_entry), entry); + S.reset(); + for (unsigned i = 0; i < old_entry->m_result_sz; i++) { + S.push_back(old_entry->m_result[i]); + } + } + else { + m.psc_chain(p, q, x, S); + unsigned sz = S.size(); + entry->m_result_sz = sz; + entry->m_result = static_cast(m_allocator.allocate(sizeof(polynomial*)*sz)); + for (unsigned i = 0; i < sz; i++) { + polynomial * h = mk_unique(S.get(i)); + S.set(i, h); + entry->m_result[i] = h; + } + } + } + + void factor(polynomial * p, polynomial_ref_vector & distinct_factors) { + distinct_factors.reset(); + p = mk_unique(p); + unsigned h = hash_u(pid(p)); + factor_entry * entry = new (m_allocator.allocate(sizeof(factor_entry))) factor_entry(p, h); + factor_entry * old_entry = m_factor_cache.insert_if_not_there(entry); + if (entry != old_entry) { + entry->~factor_entry(); + m_allocator.deallocate(sizeof(factor_entry), entry); + distinct_factors.reset(); + for (unsigned i = 0; i < old_entry->m_result_sz; i++) { + distinct_factors.push_back(old_entry->m_result[i]); + } + } + else { + factors fs(m); + m.factor(p, fs); + unsigned sz = fs.distinct_factors(); + entry->m_result_sz = sz; + entry->m_result = static_cast(m_allocator.allocate(sizeof(polynomial*)*sz)); + for (unsigned i = 0; i < sz; i++) { + polynomial * h = mk_unique(fs[i]); + distinct_factors.push_back(h); + entry->m_result[i] = h; + } + } + } + }; + + cache::cache(manager & m) { + m_imp = alloc(imp, m); + } + + cache::~cache() { + dealloc(m_imp); + } + + manager & cache::m() const { + return m_imp->m; + } + + polynomial * cache::mk_unique(polynomial * p) { + return m_imp->mk_unique(p); + } + + void cache::psc_chain(polynomial const * p, polynomial const * q, var x, polynomial_ref_vector & S) { + m_imp->psc_chain(const_cast(p), const_cast(q), x, S); + } + + void cache::factor(polynomial const * p, polynomial_ref_vector & distinct_factors) { + m_imp->factor(const_cast(p), distinct_factors); + } + + void cache::reset() { + manager & _m = m(); + dealloc(m_imp); + m_imp = alloc(imp, _m); + } +}; diff --git a/lib/polynomial_cache.h b/lib/polynomial_cache.h new file mode 100644 index 000000000..d3b695f5a --- /dev/null +++ b/lib/polynomial_cache.h @@ -0,0 +1,44 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + polynomial_cache.h + +Abstract: + + "Hash-consing" for polynomials + +Author: + + Leonardo (leonardo) 2012-01-07 + +Notes: + +--*/ +#ifndef _POLYNOMIAL_CACHE_H_ +#define _POLYNOMIAL_CACHE_H_ + +#include"polynomial.h" + +namespace polynomial { + + /** + \brief Functor for creating unique polynomials and caching results of operations + */ + class cache { + struct imp; + imp * m_imp; + public: + cache(manager & m); + ~cache(); + manager & m() const; + manager & pm() const { return m(); } + polynomial * mk_unique(polynomial * p); + void psc_chain(polynomial const * p, polynomial const * q, var x, polynomial_ref_vector & S); + void factor(polynomial const * p, polynomial_ref_vector & distinct_factors); + void reset(); + }; +}; + +#endif diff --git a/lib/polynomial_cmds.cpp b/lib/polynomial_cmds.cpp new file mode 100644 index 000000000..7b37b750e --- /dev/null +++ b/lib/polynomial_cmds.cpp @@ -0,0 +1,241 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + polynomial_cmds.cpp + +Abstract: + Commands for debugging polynomial module. + +Author: + + Leonardo (leonardo) 2011-12-23 + +Notes: + +--*/ +#include +#include"cmd_context.h" +#include"cmd_util.h" +#include"scoped_timer.h" +#include"scoped_ctrl_c.h" +#include"cancel_eh.h" +#include"ast_smt2_pp.h" +#include"expr2polynomial.h" +#include"parametric_cmd.h" +#include"mpq.h" +#include"algebraic_numbers.h" +#include"pp.h" +#include"pp_params.h" +#include"polynomial_var2value.h" +#include"expr2var.h" + +static void to_poly(cmd_context & ctx, expr * t) { + polynomial::numeral_manager nm; + polynomial::manager pm(nm); + default_expr2polynomial expr2poly(ctx.m(), pm); + polynomial::polynomial_ref p(pm); + polynomial::scoped_numeral d(nm); + if (!expr2poly.to_polynomial(t, p, d)) { + throw cmd_exception("expression is not a polynomial"); + } + expr_ref r(ctx.m()); + expr2poly.to_expr(p, true, r); + if (!nm.is_one(d)) + ctx.regular_stream() << "(* " << nm.to_string(d) << " "; + ctx.display(ctx.regular_stream(), r); + if (!nm.is_one(d)) + ctx.regular_stream() << ")"; + ctx.regular_stream() << std::endl; +} + +static void factor(cmd_context & ctx, expr * t, polynomial::factor_params const & ps) { + polynomial::numeral_manager nm; + polynomial::manager pm(nm); + default_expr2polynomial expr2poly(ctx.m(), pm); + polynomial::polynomial_ref p(pm); + polynomial::scoped_numeral d(nm); + if (!expr2poly.to_polynomial(t, p, d)) { + throw cmd_exception("expression is not a polynomial"); + } + polynomial::factors fs(pm); + factor(p, fs, ps); + ctx.regular_stream() << "(factors"; + rational f0(fs.get_constant()); + f0 = f0 / rational(d); + ctx.regular_stream() << std::endl << f0; + unsigned num_factors = fs.distinct_factors(); + expr_ref f(ctx.m()); + for (unsigned i = 0; i < num_factors; i++) { + ctx.regular_stream() << std::endl; + if (fs.get_degree(i) > 1) + ctx.regular_stream() << "(^ "; + expr2poly.to_expr(fs[i], true, f); + ctx.display(ctx.regular_stream(), f); + if (fs.get_degree(i) > 1) + ctx.regular_stream() << " " << fs.get_degree(i) << ")"; + } + ctx.regular_stream() << ")" << std::endl; +} + + +class poly_isolate_roots_cmd : public cmd { + struct context { + arith_util m_util; + unsynch_mpq_manager m_qm; + polynomial::manager m_pm; + algebraic_numbers::manager m_am; + polynomial_ref m_p; + default_expr2polynomial m_expr2poly; + polynomial::var m_var; + typedef polynomial::simple_var2value x2v; + x2v m_x2v; + + context(ast_manager & m): + m_util(m), + m_pm(m_qm), + m_am(m_qm), + m_p(m_pm), + m_expr2poly(m, m_pm), + m_var(polynomial::null_var), + m_x2v(m_am) { + } + + void set_next_arg(cmd_context & ctx, expr * arg) { + if (m_p.get() == 0) { + scoped_mpz d(m_qm); + if (!m_expr2poly.to_polynomial(arg, m_p, d)) + throw cmd_exception("expression is not a polynomial"); + } + else if (m_var == polynomial::null_var) { + if (!m_expr2poly.is_var(arg)) + throw cmd_exception("invalid assignment, argument is not a variable in the given polynomial"); + m_var = m_expr2poly.get_mapping().to_var(arg); + } + else { + rational k; + scoped_anum v(m_am); + if (m_util.is_numeral(arg, k)) { + m_am.set(v, k.to_mpq()); + } + else if (m_util.is_irrational_algebraic_numeral(arg)) { + m_am.set(v, m_util.to_irrational_algebraic_numeral(arg)); + } + else { + throw cmd_exception("invalid assignment, argument is not a value"); + } + m_x2v.push_back(m_var, v); + m_var = polynomial::null_var; + } + } + + void execute(cmd_context & ctx) { + if (m_p.get() == 0) + throw cmd_exception("polynomial expected"); + polynomial::var_vector xs; + m_pm.vars(m_p, xs); + unsigned num_assigned = 0; + for (unsigned i = 0; i < xs.size(); i++) { + if (m_x2v.contains(xs[i])) + num_assigned++; + } + if (num_assigned != xs.size() && num_assigned + 1 != xs.size()) + throw cmd_exception("given assignment is not sufficient to make the given polynomial univariate"); + scoped_anum_vector rs(m_am); + m_am.isolate_roots(m_p, m_x2v, rs); + ctx.regular_stream() << "(roots"; + for (unsigned i = 0; i < rs.size(); i++) { + ctx.regular_stream() << std::endl; + if (!get_pp_default_params().m_pp_decimal) + m_am.display_root_smt2(ctx.regular_stream(), rs[i]); + else + m_am.display_decimal(ctx.regular_stream(), rs[i]); + } + ctx.regular_stream() << ")" << std::endl; + } + }; + + scoped_ptr m_ctx; + +public: + poly_isolate_roots_cmd(char const * name = "poly/isolate-roots"):cmd(name), m_ctx(0) {} + + virtual char const * get_usage() const { return " ( )*"; } + + virtual char const * get_descr(cmd_context & ctx) const { return "isolate the roots a multivariate polynomial modulo an assignment"; } + + virtual unsigned get_arity() const { return VAR_ARITY; } + + virtual void prepare(cmd_context & ctx) { + m_ctx = alloc(context, ctx.m()); + } + + virtual void finalize(cmd_context & ctx) { + m_ctx = 0; + } + + virtual void failure_cleanup(cmd_context & ctx) { + m_ctx = 0; + } + + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { + return CPK_EXPR; + } + + virtual void set_next_arg(cmd_context & ctx, expr * arg) { + m_ctx->set_next_arg(ctx, arg); + } + + virtual void execute(cmd_context & ctx) { + m_ctx->execute(ctx); + m_ctx = 0; + } +}; + + +UNARY_CMD(to_poly_cmd, "to-poly", "", "convert expression into sum-of-monomials form", CPK_EXPR, expr *, to_poly(ctx, arg);); + +class poly_factor_cmd : public parametric_cmd { + expr * m_target; +public: + poly_factor_cmd(char const * name = "poly/factor"):parametric_cmd(name) {} + + virtual char const * get_usage() const { return " ( )*"; } + + virtual char const * get_main_descr() const { + return "factor a polynomial"; + } + + virtual void init_pdescrs(cmd_context & ctx, param_descrs & p) { + polynomial::factor_params::get_param_descrs(p); + } + + virtual void prepare(cmd_context & ctx) { + parametric_cmd::prepare(ctx); + m_target = 0; + } + + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { + if (m_target == 0) return CPK_EXPR; + return parametric_cmd::next_arg_kind(ctx); + } + + virtual void set_next_arg(cmd_context & ctx, expr * arg) { + m_target = arg; + } + + virtual void execute(cmd_context & ctx) { + polynomial::factor_params ps; + ps.updt_params(m_params); + factor(ctx, m_target, ps); + } +}; + +void install_polynomial_cmds(cmd_context & ctx) { +#ifndef _EXTERNAL_RELEASE + ctx.insert(alloc(to_poly_cmd)); + ctx.insert(alloc(poly_factor_cmd)); + ctx.insert(alloc(poly_isolate_roots_cmd)); +#endif +} diff --git a/lib/polynomial_cmds.h b/lib/polynomial_cmds.h new file mode 100644 index 000000000..b2b368833 --- /dev/null +++ b/lib/polynomial_cmds.h @@ -0,0 +1,24 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + polynomial_cmds.h + +Abstract: + Commands for debugging polynomial module. + +Author: + + Leonardo (leonardo) 2011-12-23 + +Notes: + +--*/ +#ifndef _POLYNOMIAL_CMDS_H_ +#define _POLYNOMIAL_CMDS_H_ + +class cmd_context; +void install_polynomial_cmds(cmd_context & ctx); + +#endif diff --git a/lib/polynomial_factorization.cpp b/lib/polynomial_factorization.cpp new file mode 100644 index 000000000..e0f050411 --- /dev/null +++ b/lib/polynomial_factorization.cpp @@ -0,0 +1,1143 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + polynomial_factorization.cpp + +Abstract: + + Implementation of polynomial factorization. + +Author: + + Dejan (t-dejanj) 2011-11-15 + +Notes: + + [1] Elwyn Ralph Berlekamp. Factoring Polynomials over Finite Fields. Bell System Technical Journal, + 46(8-10):1853–1859, 1967. + [2] Donald Ervin Knuth. The Art of Computer Programming, volume 2: Seminumerical Algorithms. Addison Wesley, third + edition, 1997. + [3] Henri Cohen. A Course in Computational Algebraic Number Theory. Springer Verlag, 1993. + +--*/ +#if 0 +// disabled for reorg + +#include"trace.h" +#include"util.h" +#include"polynomial_factorization.h" +#include"upolynomial_factorization_int.h" +#include"prime_generator.h" + +using namespace std; + +namespace polynomial { + +typedef upolynomial::manager::scoped_numeral scoped_numeral; + +/** + Generates a substitution of values for f -> f_univariate in order to reduce the factorization to the + univariate case. + + @param f multivariate polynomial (square-free, primitive, vars(f) > 1) + @param x the variable we want to keep as the univarate one + @param f_lc the leading coefficient of f in x + @param vars the vector of all variables from f witouth x + @param size the bound to use for selecting the values, i.e. |a_i| <= size + @param a the output values corresponding to vairables in (place place for x should be ignored) + @param f_univariate the output substitution +*/ +void generate_substitution_values( + polynomial_ref const & f, var x, polynomial_ref const & f_lc, var_vector const & vars, unsigned & size, + upolynomial::numeral_vector & a, upolynomial::manager upm, upolynomial::numeral_vector & f_u) { + + SASSERT(a.size() == vars.size()); + + TRACE("polynomial::factorization", tout << "polynomial::generate_substitution_values(f = " << f << ", f_lc = " << f_lc << ")";); + + // f = f_n x^n + ... + f_0, square-free and primitive + // this means + // f_lc = f_n + // since f is primitive, + // we are looking for numbers a_i such that + // (1) f_lc doesn't vanish + // (2) f_u = f(a_0, ..., a_n, x) is square-free + + manager & pm = f.m(); + numeral_manager & nm = pm.m(); + + // polynomial to use for subtituting into the lc(f) + polynomial_ref f_lc_subst(pm); + + // random generator + random_gen generator; + + // increase the size every once in a while (RETHINK THIS) + unsigned inc_size_c = 0; + unsigned inc_size_c_max = size; + + while (true) { + + // see if we should increase the size of the substitution + if ((++ inc_size_c) % inc_size_c_max == 0) { + size ++; + inc_size_c = 0; + inc_size_c_max *= 2; + } + + // the head coefficient we'll substitute in + f_lc_subst = f_lc; + + bool vanished = false; + for (unsigned i = 0; i < vars.size() && !vanished; ++ i) { + SASSERT(vars[i] != x); + + // the value for x_i + nm.set(a[i], (int)generator(2*size+1) - (int)size); + + // substitute + f_lc_subst = pm.substitute(f_lc_subst, x, a[i]); + + // did it vanish + vanished = pm.is_zero(f_lc_subst); + } + + if (vanished) { + // leading coefficient vanished, try again + continue; + } + + // substitute into f and get the univariate one + polynomial_ref f_subst(pm); + f_subst = pm.substitute(f, vars.size(), vars.c_ptr(), a.c_ptr()); + upm.to_numeral_vector(f_subst, f_u); + + // if the result is not square-free we try again + if (!upm.is_square_free(f_u)) + continue; + + // found it, break + break; + } +} + +/** + \brief Bound for the coefficients of the factorst of the multivariate polynomial f. R + Returns power of p -> p^e that covers the bound + + We use the gelfond bound here: + d_i: degree of x_i in f(x1, ..., x_n) + bound = |f| * 2^(\sum d_i - (n-1)/2)) +*/ +void multivariate_factor_coefficient_bound(polynomial_ref const & f, var x, numeral const & p, unsigned & e, numeral & p_e, var2degree & d) { + manager & pm = f.m(); + numeral_manager & nm = pm.m(); + + // compute g = lc(f)*f + polynomial_ref f_lc(pm), g(pm); + f_lc = pm.coeff(f, x, pm.degree(f, x)); + g = pm.mul(f, f_lc); + + // get the norm + scoped_numeral g_norm(nm); + pm.abs_norm(g, g_norm); + + // get the variable degrees + var_vector vars; + pm.vars(f, vars); + unsigned power = 0; + for (unsigned i = 0; i < vars.size(); ++ i) { + unsigned c_d = pm.degree(g, vars[i]); + d.set_degree(vars[i], c_d + 1); + power += c_d; + } + power = power - (vars.size()-1)/2; + + // compute the bound + scoped_numeral bound(nm); + nm.set(bound, 2); + nm.power(bound, power, bound); + nm.mul(g_norm, bound, bound); + + // return the first power of two that is bigger than the norm + e = 1; + nm.set(p_e, p); + while (nm.lt(p_e, bound)) { + nm.mul(p_e, p_e, p_e); + e *= 2; + } +} + +// check that A*S+B*T=C in zp mod ideal +bool check_solve(zp_manager & zp_pm, var2degree const & ideal, + zp_polynomial_ref const & A, zp_polynomial_ref const & B, zp_polynomial_ref const & C, + zp_polynomial_ref const & S, zp_polynomial_ref const & T) { + zp_polynomial_ref AS(zp_pm), BT(zp_pm), sum(zp_pm); + AS = zp_pm.mul(A, S); AS = zp_pm.mod_d(AS, ideal); + BT = zp_pm.mul(B, T); BT = zp_pm.mod_d(BT, ideal); + sum = zp_pm.add(AS, BT); + + TRACE("polynomial::factorization::multivariate", + tout << "zp_pm = Z_" << zp_pm.m().m().to_string(zp_pm.m().p()) << endl; + tout << "ideal = " << ideal << endl; + tout << "A = " << A << endl; + tout << "B = " << B << endl; + tout << "S = " << S << endl; + tout << "T = " << T << endl; + tout << "C = " << C << endl; + tout << "sum = " << sum << endl; + ); + + bool result = zp_pm.eq(sum, C); + return result; +} + +/** + Solve the equation A*S + B*T = C, given, AU + BV = 1, with deg(T) < deg(A) + S = U*C + tB + T = V*C - tA + we divide VC with A to get (T, t) +*/ +template +void solve(zp_manager & zp_pm, var x, var2degree const & ideal, + zp_polynomial_ref const & A, zp_polynomial_ref const & U, + zp_polynomial_ref const & B, zp_polynomial_ref const & V, + zp_polynomial_ref const & C, + output_manager & out_pm, + typename output_manager::polynomial_ref & S_out, + typename output_manager::polynomial_ref & T_out) { + TRACE("polynomial::factorization::multivariate", + tout << "polynomial::solve(" << endl; + tout << "zp_pm = Z_" << zp_pm.m().m().to_string(zp_pm.m().p()) << endl; + tout << "ideal = " << ideal << endl; + tout << "A = " << A << endl; + tout << "B = " << B << endl; + tout << "U = " << U << endl; + tout << "V = " << V << endl; + tout << "C = " << C << endl; + ); + + // solution is S = C*U + tB, T = C*V - tA + zp_polynomial_ref CV(zp_pm); + CV = zp_pm.mul(C, V); CV = zp_pm.mod_d(CV, ideal); + zp_polynomial_ref CU(zp_pm); + CU = zp_pm.mul(C, U); CU = zp_pm.mod_d(CU, ideal); + zp_polynomial_ref t(zp_pm), T(zp_pm); + zp_pm.exact_pseudo_division_mod_d(CV, A, x, ideal, t, T); + + zp_polynomial_ref tB(zp_pm); + tB = zp_pm.mul(t, B); tB = zp_pm.mod_d(tB, ideal); + zp_polynomial_ref S(zp_pm); + S = zp_pm.add(CU, tB); + + SASSERT(check_solve(zp_pm, ideal, A, B, C, S, T)); + + // convert to the other manager + S_out = convert(zp_pm, S, out_pm); + T_out = convert(zp_pm, T, out_pm); + + TRACE("polynomial::factorization::multivariate", + tout << "CU = " << CU << endl; + tout << "CV = " << CV << endl; + tout << "t = " << t << endl; + tout << "--- solution ---" << endl; + tout << "S = " << S_out << endl; + tout << "T = " << T_out << endl; + ); +} + +/** + A, B, U, V: multivariate polynomials in Z_p[x, ...], mod ..., also the output polynomials, y is not there + C: C = A*B in Z_p[x, ...] mod p, ... + + ideal: all the vars we care about in the ideal + + y, the variable we are lifting is not in ideal_vars, we will add it + + A monic, A*U+B*V = 1 + + p is not necessary prime, it's a power of a prime + + we're doing quadratic lifting here + + output: added y, i.e. + * all polynomials in Z_p[x, ..., y] mod (..., y^d) +*/ +void multivariate_hansel_lift_ideal( + zp_manager & zp_pm, var x, + zp_polynomial_ref const & C, + zp_polynomial_ref & A, zp_polynomial_ref & U, zp_polynomial_ref & B, zp_polynomial_ref & V, + var2degree & ideal, var y, unsigned d) { + numeral_manager & nm = zp_pm.m().m(); + + TRACE("polynomial::factorization::multivariate", + tout << "polynomial::multiratiate_hensel_lift_ideal" << endl; + tout << "zp_pm is Z_" << zp_pm.m().m().to_string(zp_pm.m().p()) << endl; + tout << "x = x" << x << endl; + tout << "y = x" << y << endl; + tout << "C = " << C << endl; + tout << "A = " << A << endl; + tout << "B = " << B << endl; + tout << "U = " << U << endl; + tout << "V = " << V << endl; + tout << "ideal = " << ideal << endl; + ); + + // constant 1 + scoped_numeral one(nm); + nm.set(one, 1); + zp_polynomial_ref one_p(zp_pm); + one_p = zp_pm.mk_const(one); + + SASSERT(zp_pm.degree(A, y) == 0 && zp_pm.degree(B, y) == 0 && zp_pm.degree(U, y) == 0 && zp_pm.degree(V, y) == 0); + + // update the ideal, and start with y + ideal.set_degree(y, 1); + unsigned current_d = 1; + zp_polynomial_ref current_power(zp_pm); + current_power = zp_pm.mk_polynomial(y); + + // lift quadratic until we are over the asked for + while (current_d < d) { + + TRACE("polynomial::factorization::multivariate", + tout << "zp_pm = Z_" << nm.to_string(zp_pm.m().p()) << endl; + tout << "ideal = " << ideal << endl; + tout << "C = " << C << endl; + tout << "A = " << A << endl; + tout << "B = " << B << endl; + ); + + // again, classic hensel: + // since C = A*B mod (p, ideal, y^k) we know that (C - A*B) = 0 mod (p, ideal, y^k) + // f = (C - A*B) mod (p, ideal, y^k) and thus divisible by y^current_d = current_power + zp_polynomial_ref f(zp_pm); + f = zp_pm.mul(A, B); + + TRACE("polynomial::factorization::multivariate", + tout << "zp_pm = Z_" << nm.to_string(zp_pm.m().p()) << endl; + tout << "ideal = " << ideal << endl; + tout << "C = " << C << endl; + tout << "A = " << A << endl; + tout << "B = " << B << endl; + tout << "f = " << f << endl; + ); + + f = zp_pm.sub(C, f); + f = zp_pm.exact_div(f, current_power); + f = zp_pm.mod_d(f, ideal); + + TRACE("polynomial::factorization::multivariate", + tout << "A = " << A << endl; + tout << "B = " << B << endl; + ); + + // get the S, T, solution to A*S+B*T = f, with d(T, x) < d(A, x) + // but we know that S = U*f + Bt, T = V*f - At, so we do division + zp_polynomial_ref S(zp_pm), T(zp_pm); + solve(zp_pm, x, ideal, A, U, B, V, f, zp_pm, S, T); + // now, lets lift A and B + // we want A1 = A + T*y^d, B1 = B + S*y^d with A1*B1 = C mod (ideal, y^(2*k)) + // hence A*B + y^d*(A*S+B*T) = C + S = zp_pm.mul(S, current_power); + T = zp_pm.mul(T, current_power); + A = zp_pm.add(A, T); + B = zp_pm.add(B, S); + + TRACE("polynomial::factorization::multivariate", + tout << "A = " << A << endl; + tout << "B = " << B << endl; + ); + + // again, classic quadratic hensel + // we need A*U1 + B*V1 = 1 mod (p, ideal, y^2), from above + // U1 = U + S*y^d, V1 = V + T*y^d, hence A*U + B*V + y^d(S + T) = 1 mod new ideal^2 + // we know that y^d divides (1-UA-BV) so we compute f = (1-UA-BV)/y^d + // UA + VB + y^d(SA + TB) = 1 (mod ideal^2) + // SA + TB = f (mod ideal) + // we solve for S, T again, and do as above + zp_polynomial_ref UA(zp_pm), BV(zp_pm); + f = zp_pm.mk_const(one); + UA = zp_pm.mul(U, A); + BV = zp_pm.mul(V, B); + f = zp_pm.sub(f, UA); f = zp_pm.sub(f, BV); + + TRACE("polynomial::factorization::multivariate", + tout << "ideal = " << ideal << endl; + tout << "current_power = " << current_power << endl; + tout << "UA = " << UA << endl; + tout << "BV = " << BV << endl; + tout << "f = " << f << endl; + tout << "x = x" << x << endl; + tout << "y = x" << y << endl; + ); + + f = zp_pm.exact_div(f, current_power); + f = zp_pm.mod_d(f, ideal); + + // get the S, T, solution to A*S+B*T = f, with d(T, x) < d(A, x) + solve(zp_pm, x, ideal, A, U, B, V, f, zp_pm, S, T); + // now, lets lift U and V + S = zp_pm.mul(S, current_power); + U = zp_pm.add(U, S); + T = zp_pm.mul(T, current_power); + V = zp_pm.add(V, T); + + // lift the ideal + current_d *= 2; + current_power = zp_pm.mul(current_power, current_power); + ideal.set_degree(y, current_d); + + // move, A, B, C, D into the ideal + A = zp_pm.mod_d(A, ideal); + B = zp_pm.mod_d(B, ideal); + S = zp_pm.mod_d(S, ideal); + T = zp_pm.mod_d(T, ideal); + + TRACE("polynomial::factorization::multivariate", + tout << "current_d = " << d << endl; + tout << "zp_pm is Z_" << zp_pm.m().m().to_string(zp_pm.m().p()) << endl; + tout << "x = x" << x << endl; + tout << "y = x" << y << endl; + tout << "C = " << C << endl; + tout << "A = " << A << endl; + tout << "B = " << B << endl; + tout << "U = " << U << endl; + tout << "V = " << V << endl; + tout << "ideal = " << ideal << endl; + ); + + SASSERT(check_solve(zp_pm, ideal, A, B, one_p, U, V)); + } +} + +template +bool are_equal_in( + manager_to_check pm, + typename manager_1::polynomial_ref const & A, + typename manager_2::polynomial_ref const & B) { + typename manager_to_check::polynomial_ref A_pm(pm), B_pm(pm); + + A_pm = convert(A.m(), A, pm); + B_pm = convert(B.m(), B, pm); + + bool equal = pm.eq(A_pm, B_pm); + return equal; +} + +/** + C: target multivariate polynomial mod ideal, p^e, the manager is in p^e + x: main variable + A, B, U, V: univariate polynomials in Z_p[x] such that U*A+B*V=1 mod ideal, these guys managers are in Z_p + + output: A_lifted, B_lifted, A = A_lifted mod ideal + A_lifted*B_lifted = f mod x_i^d_i, p^e +*/ +void multivariate_hansel_lift_zp( + manager & pm, zp_manager & zp_pm, zp_manager & zpe_pm, + zp_polynomial_ref const & C_pe, var x, unsigned e, + zp_polynomial_ref const & A_p, zp_polynomial_ref const & U_p, + zp_polynomial_ref const & B_p, zp_polynomial_ref const & V_p, + var2degree const & ideal, + zp_polynomial_ref & A_lifted, zp_polynomial_ref & B_lifted) { + TRACE("polynomial::factorization::multivariate", + tout << "polynomial::multiratiate_hensel_lift_zp:" << endl; + tout << "zp_pm = Z_" << zp_pm.m().m().to_string(zp_pm.m().p()) << endl; + tout << "zpe_pm = Z_" << zpe_pm.m().m().to_string(zpe_pm.m().p()) << endl; + tout << "x = x" << x << endl; + tout << "ideal = " << ideal << endl; + tout << "C_pe = " << C_pe << "," << endl; + tout << "A_p = " << A_p << "," << endl; + tout << "B_p = " << B_p << "," << endl; + ); + + // fixed zpe_pm + // upolynomial::zp_numeral_manager & zpe_nm = zpe_pm.m(); + // numeral const & pe = zpe_nm.p(); + // fixed zp_pm + upolynomial::zp_numeral_manager & zp_nm = zp_pm.m(); + numeral const & p = zp_nm.p(); + // regular numeral manager and mangager + numeral_manager & nm = zp_nm.m(); + + // sliding zpk_pm mod p^k + upolynomial::zp_numeral_manager zpk_nm(nm, p); + zp_manager zpk_pm(zpk_nm, &zp_pm.mm()); // in the end we copy the result over to zpe + unsigned k = 1; + upolynomial::scoped_numeral pk(nm); + nm.set(pk, zpk_nm.p()); + + // constant 1 + scoped_numeral one(nm); + nm.set(one, 1); + zp_polynomial_ref one_p(zpk_pm); + one_p = zpk_pm.mk_const(one); + + // lift until you get over the requested power of e + zp_polynomial_ref A_pk(zpk_pm), B_pk(zpk_pm), U_pk(zpk_pm), V_pk(zpk_pm); + + A_pk = convert(zp_pm, A_p, zpk_pm); + B_pk = convert(zp_pm, B_p, zpk_pm); + U_pk = convert(zp_pm, U_p, zpk_pm); + V_pk = convert(zp_pm, V_p, zpk_pm); + + TRACE("polynomial::factorization::multivariate", + tout << "zpk_pm = Z_" << zpk_pm.m().m().to_string(zpk_pm.m().p()) << endl; + tout << "A_pk = " << A_pk << endl; + tout << "B_pk = " << B_pk << endl; + tout << "U_pk = " << U_pk << endl; + tout << "V_pk = " << V_pk << endl; + ); + + SASSERT(check_solve(zpk_pm, ideal, A_pk, B_pk, one_p, U_pk, V_pk)); + + while (k < e) { + + // standard hensel: + // (C - AB) and is divisible by p^k, so we compute f = (C - AB)/p^k mod ideal in Z[...] + zp_polynomial_ref f_pk(zpk_pm); + polynomial_ref A_pk_in_Z(pm), B_pk_in_Z(pm), AB_in_Z(pm), f_in_Z(pm); + f_in_Z = convert(zpe_pm, C_pe, pm); + A_pk_in_Z = convert(zpk_pm, A_pk, pm); + B_pk_in_Z = convert(zpk_pm, B_pk, pm); + AB_in_Z = pm.mul(A_pk_in_Z, B_pk_in_Z); + AB_in_Z = pm.mod_d(AB_in_Z, ideal); + f_in_Z = pm.sub(f_in_Z, AB_in_Z); + f_in_Z = pm.exact_div(f_in_Z, pk); + f_in_Z = pm.mod_d(f_in_Z, ideal); + f_pk = convert(pm, f_in_Z, zpk_pm); + + TRACE("polynomial::factorization::multivariate", + tout << "zpk_pm = Z_" << zpk_pm.m().m().to_string(zpk_pm.m().p()) << endl; + tout << "f_pk = " << f_pk << endl; + ); + + // standard hensel we need to lift to p^(2k) + // we have U*A+V*B = 1, C = A*B, so p^k divides C - AB + // we want A1 = A + p^k*S, B1 = B + p^k*T, and also + // C - (A + p^k*S)*(B + p^k*T) = 0 mod (p^2k) + // C - A*B = p^k (T*A + S*B), i.e. + // f = (C - A*B)/p^k = (T*A + S*B), so we solve this equation in Z_p^k + polynomial_ref S_in_Z(pm), T_in_Z(pm); + solve(zpk_pm, x, ideal, A_pk, U_pk, B_pk, V_pk, f_pk, pm, T_in_Z, S_in_Z); + + TRACE("polynomial::factorization::multivariate", + tout << "zpk_pm = Z_" << zpk_pm.m().m().to_string(zpk_pm.m().p()) << endl; + tout << "S_in_Z = " << S_in_Z << endl; + tout << "T_in_Z = " << T_in_Z << endl; + ); + + // lift A and B to, A = A + p^k*S, B = B + p^k*T + polynomial_ref A_next_in_Z(pm), B_next_in_Z(pm); + S_in_Z = pm.mul(pk, S_in_Z); + S_in_Z = pm.mod_d(S_in_Z, ideal); + A_next_in_Z = convert(zpk_pm, A_pk, pm); + A_next_in_Z = pm.add(A_next_in_Z, S_in_Z); + T_in_Z = pm.mul(pk, T_in_Z); + T_in_Z = pm.mod_d(T_in_Z, ideal); + B_next_in_Z = convert(zpk_pm, B_pk, pm); + B_next_in_Z = pm.add(B_next_in_Z, T_in_Z); + + TRACE("polynomial::factorization::multivariate", + tout << "pk = " << nm.to_string(pk) << endl; + tout << "zpk_pm = Z_" << zpk_pm.m().m().to_string(zpk_pm.m().p()) << endl; + tout << "S_in_Z = " << S_in_Z << endl; + tout << "T_in_Z = " << T_in_Z << endl; + tout << "A_pk = " << A_pk << endl; + tout << "B_pk = " << B_pk << endl; + tout << "A_next_in_Z = " << A_next_in_Z << endl; + tout << "B_next_in_Z = " << B_next_in_Z << endl; + ); + + bool eq1 = are_equal_in(zpk_pm, A_next_in_Z, A_pk); + SASSERT(eq1); + bool eq2 = are_equal_in(zpk_pm, B_next_in_Z, B_pk); + SASSERT(eq2); + + // again, classic quadratic hensel + // we need A*U1 + B*V1 = 1 mod p^2k, from above + // U1 = U + p^k*S, V1 = V + p^k*T, hence A*U + B*V + p^k*(S + T) = 1 mod (p^2k) + // we know that p^k divides (1-UA-BV) so we compute f = (1-UA-BV)/p^k + // UA + VB + p^k(SA + TB) = 1 (mod p^k) + // SA + TB = f (mod ideal) + // we solve for S, T again, and do as above + polynomial_ref U_pk_in_Z(pm), V_pk_in_Z(pm), UA_in_Z(pm), BV_in_Z(pm); + U_pk_in_Z = convert(zpk_pm, U_pk, pm); + V_pk_in_Z = convert(zpk_pm, V_pk, pm); + f_in_Z = pm.mk_const(one); + UA_in_Z = pm.mul(U_pk_in_Z, A_next_in_Z); + UA_in_Z = pm.mod_d(UA_in_Z, ideal); + BV_in_Z = pm.mul(V_pk_in_Z, B_next_in_Z); + BV_in_Z = pm.mod_d(BV_in_Z, ideal); + f_in_Z = pm.sub(f_in_Z, UA_in_Z); + f_in_Z = pm.sub(f_in_Z, BV_in_Z); + + TRACE("polynomial::factorization::multivariate", + tout << "pk = " << nm.to_string(pk) << endl; + tout << "zpk_pm = Z_" << zpk_pm.m().m().to_string(zpk_pm.m().p()) << endl; + tout << "U_pk_in_Z = " << U_pk_in_Z << endl; + tout << "V_pk_in_Z = " << V_pk_in_Z << endl; + tout << "UA_in_Z = " << UA_in_Z << endl; + tout << "BV_in_Z = " << BV_in_Z << endl; + tout << "f_in_Z = " << f_in_Z << endl; + ); + + f_in_Z = pm.exact_div(f_in_Z, pk); + f_pk = convert(pm, f_in_Z, zpk_pm); + + // get the S, T, solution to A*S+B*T = f, with d(T, x) < d(A, x) + solve(zpk_pm, x, ideal, A_pk, U_pk, B_pk, V_pk, f_pk, pm, S_in_Z, T_in_Z); + + TRACE("polynomial::factorization::multivariate", + tout << "pk = " << nm.to_string(pk) << endl; + tout << "zpk_pm = Z_" << zpk_pm.m().m().to_string(zpk_pm.m().p()) << endl; + tout << "S_in_Z = " << S_in_Z << endl; + tout << "T_in_Z = " << T_in_Z << endl; + ); + + // go to the next zpk + scoped_numeral next_pk(nm); + nm.mul(pk, pk, next_pk); + zpk_nm.set_p(next_pk); + + TRACE("polynomial::factorization::multivariate", + tout << "zp_pk = Z_" << zpk_pm.m().m().to_string(zpk_pm.m().p()) << endl; + ); + + // lift U + zp_polynomial_ref S_pk(zpk_pm); + S_in_Z = pm.mul(pk, S_in_Z); + S_pk = convert(pm, S_in_Z, zpk_pm); + + TRACE("polynomial::factorization::multivariate", + tout << "S_pk = " << S_pk << endl; + ); + + U_pk = zpk_pm.add(U_pk, S_pk); + // lift V + zp_polynomial_ref T_pk(zpk_pm); + T_in_Z = pm.mul(pk, T_in_Z); + T_pk = convert(pm, T_in_Z, zpk_pm); + + TRACE("polynomial::factorization::multivariate", + tout << "T_pk = " << T_pk << endl; + ); + + V_pk = zpk_pm.add(V_pk, T_pk); + + // lift A and B + TRACE("polynomial::factorization::multivariate", + tout << "A_pk_in_Z = " << A_pk_in_Z << endl; + tout << "B_pk_in_Z = " << B_pk_in_Z << endl; + ); + A_pk = convert(pm, A_pk_in_Z, zpk_pm); + B_pk = convert(pm, B_pk_in_Z, zpk_pm); + + // move to the next pk + k *= 2; + nm.set(pk, next_pk); + + TRACE("polynomial::factorization::multivariate", + tout << "zp_pk = Z_" << zpk_pm.m().m().to_string(zpk_pm.m().p()) << endl; + tout << "A_pk = " << A_pk << endl; + tout << "B_pk = " << B_pk << endl; + tout << "U_pk = " << U_pk << endl; + tout << "V_pk = " << V_pk << endl; + tout << "C_pe = " << C_pe << endl; + ); + + SASSERT(check_solve(zpk_pm, ideal, A_pk, B_pk, one_p, U_pk, V_pk)); + } + + // now convert to the non-sliding zpe_manager + SASSERT(k == e); + A_lifted = convert(zpk_pm, A_pk, zpe_pm); + B_lifted = convert(zpk_pm, B_pk, zpe_pm); +} + +/** + f: target multivariate polynomial mod x_i^d_i, p^e + x: main variable + all_vars: all variables (including x) + A, B, U, V: univariate polynomials in Z_p[x] such that U*A+B*V=1 from ext gcd + + output: A_lifted, B_lifted d(A) = d(A_lifted), A = A_lifted mod x_i^d_i, p + A_lifted*B_lifted = f mod x_i^d_i, p^e +*/ +void multivariate_hensel_lift( + manager & pm, zp_manager & zp_pm, zp_manager & zpe_pm, + zp_polynomial_ref const & f, var x, unsigned e, var_vector const & all_vars, + upolynomial::zp_manager & zp_upm, + upolynomial::numeral_vector const & U, upolynomial::numeral_vector const & A, + upolynomial::numeral_vector const & V, upolynomial::numeral_vector const & B, + var2degree & target_ideal, zp_polynomial_ref & A_lifted, zp_polynomial_ref & B_lifted) { + upolynomial::zp_numeral_manager & zp_nm = zp_upm.m(); + upolynomial::numeral_manager & nm = zp_nm.m(); + + TRACE("polynomial::factorization::multivariate", + tout << "polynomial::multiratiate_hensel_lift(" << endl; + tout << "f = " << f << "," << endl; + tout << "x = x" << x << "," << endl; + tout << "e = " << e << "," << endl; + tout << "U = "; zp_upm.display(tout, U); tout << "," << endl; + tout << "A = "; zp_upm.display(tout, A); tout << "," << endl; + tout << "V = "; zp_upm.display(tout, V); tout << "," << endl; + tout << "B = "; zp_upm.display(tout, B); tout << "," << endl; + tout << "target_ideal = " << target_ideal << "," << endl; + tout << "p = " << nm.to_string(zp_pm.m().p()) << endl; + tout << "pe = " << nm.to_string(zpe_pm.m().p()) << endl; + ); + + // multivariate versions of A, B, U, V that we keep lifting over ideal x_i^d_i + zp_polynomial_ref A_m_p(zp_pm), B_m_p(zp_pm), U_m_p(zp_pm), V_m_p(zp_pm); + A_m_p = zp_pm.to_polynomial(A, x); + B_m_p = zp_pm.to_polynomial(B, x); + U_m_p = zp_pm.to_polynomial(U, x); + V_m_p = zp_pm.to_polynomial(V, x); + + TRACE("polynomial::factorization::multivariate", + tout << "A_m_p = " << A_m_p << endl; + tout << "B_m_p = " << B_m_p << endl; + tout << "U_m_p = " << U_m_p << endl; + tout << "V_m_p = " << V_m_p << endl; + ); + + // the the target in Z_p[...] + zp_polynomial_ref C_m_p(zp_pm); + C_m_p = convert(zpe_pm, f, zp_pm); + + // lift each variable individually + var2degree lifted_ideal; + unsigned_vector lifted_degs; + for (unsigned i = 0; i < all_vars.size(); ++ i) { + if (all_vars[i] == x) { + // skip the main variable + continue; + } + // current variable and degree we are lifting to, y^(d_y), at least + var y = all_vars[i]; + // lift to y^(d_y) + multivariate_hansel_lift_ideal(zp_pm, x, C_m_p, A_m_p, U_m_p, B_m_p, V_m_p, lifted_ideal, y, target_ideal.degree(y)); + } + + TRACE("polynomial::factorization::multivariate", + tout << "A_m_p = " << A_m_p << endl; + tout << "B_m_p = " << B_m_p << endl; + tout << "U_m_p = " << U_m_p << endl; + tout << "V_m_p = " << V_m_p << endl; + tout << "lifted_ideal = " << lifted_ideal << endl; + ); + + // now lift it all to p^e + multivariate_hansel_lift_zp(pm, zp_pm, zpe_pm, f, x, e, A_m_p, U_m_p, B_m_p, V_m_p, lifted_ideal, A_lifted, B_lifted); +} + + +/** + f: multivariate polynomial + x: main variable + all_vars: all variables (including x) + f_u: f mod x_1, ..., x_n (excluding mod x), i.e. this is f(0, x), f_u is square_free + f_u_zp_factors: monic factors of f_u (mod p), pairvise gcd = 1 + + we're lifting the factors to mod x_1^d_1, ..., x_n&d_n (excliding x), mod p^e + i.e. such that f congruent to the new factors. output goes to f_zpe factors. +*/ +void multivariate_hensel_lift( + manager & pm, zp_manager & zp_pm, zp_manager & zpe_pm, + polynomial_ref const & f, var x, unsigned e, var_vector const & all_vars, + upolynomial::manager & upm, upolynomial::numeral_vector const & f_u, + upolynomial::zp_factors const & f_u_zp_factors, + var2degree & target_ideal, + zp_factors & f_zpe_factors) { + SASSERT(f_u_zp_factors.distinct_factors() > 1); + + TRACE("polynomial::factorization::multivariate", + tout << "polynomial::multivariate_hensel_lift(" << endl; + tout << "f = " << f << "," << endl; + tout << "x = x" << x << "," << endl; + tout << "e = " << e << "," << endl; + tout << "f_u = "; upm.display(tout, f_u); tout << "," << endl; + tout << "f_u_zp_factors" << f_u_zp_factors << "," << endl; + tout << "target_ideal = " << target_ideal << "," << endl; + tout << "f_zpe_factors = " << f_zpe_factors << ")" << endl; + ); + + // managers and all + numeral_manager & nm = pm.m(); + upolynomial::zp_manager & zp_upm = f_u_zp_factors.upm(); + // upolynomial::zp_numeral_manager & zp_nm = zp_upm.m(); + upolynomial::zp_numeral_manager & zpe_nm = zpe_pm.m(); + upolynomial::zp_manager zpe_upm(zpe_nm); + + // the targed product we want (mod x_i^d_i, mod p^e) + zp_polynomial_ref f_target_zpe(zpe_pm); + f_target_zpe = convert(pm, f, zpe_pm); + f_target_zpe = zpe_pm.mod_d(f_target_zpe, target_ideal); + + TRACE("polynomial::factorization::multivariate", + tout << "target_ideal = " << target_ideal << endl; + tout << "f_target_zpe = " << f_target_zpe << endl; + ); + + // we do the product by doing individual lifting like in the univarate case + zp_polynomial_ref B(zp_pm), C_p(zp_pm); + zp_polynomial_ref A_lifted(zpe_pm), B_lifted(zpe_pm); + upolynomial::scoped_numeral_vector B_u(nm), C_u(nm), tmp_u(nm); + upolynomial::scoped_numeral_vector U(nm), V(nm); + for (int i = 0, i_end = f_u_zp_factors.distinct_factors() - 1; i < i_end; ++ i) + { + // get the univarate ones to lift now + upolynomial::numeral_vector const & A_u = f_u_zp_factors[i]; + // current starting product is f_target_zpe(0, x) in *Z_p* + zp_upm.to_numeral_vector(f_target_zpe, x, C_u); + // we get the rest into B (mod p) + zp_upm.exact_div(C_u, A_u, B_u); + + TRACE("polynomial::factorization::multivariate", + tout << "p = " << nm.to_string(zp_upm.m().p()) << endl; + tout << "f_target_zpe = " << f_target_zpe << endl; + tout << "A_u = "; upm.display(tout, A_u); tout << endl; + tout << "B_u = "; upm.display(tout, B_u); tout << endl; + tout << "C_u = "; upm.display(tout, C_u); tout << endl; + ); + + // and get the U, V, such that A*U+B*V = 1 + zp_upm.ext_gcd(A_u, B_u, U, V, tmp_u); + + TRACE("polynomial::factorization::multivariate", + tout << "U = "; upm.display(tout, U); tout << endl; + tout << "V = "; upm.display(tout, V); tout << endl; + tout << "gcd = "; upm.display(tout, tmp_u); tout << endl; + ); + + // do the lifting for this pair + multivariate_hensel_lift(pm, zp_pm, zpe_pm, f_target_zpe, x, e, all_vars, zp_upm, U, A_u, V, B_u, target_ideal, A_lifted, B_lifted); + + // add the lifted A to the output + f_zpe_factors.push_back(A_lifted, 1); + // move to the new target by dividing with the lifted A + f_target_zpe = zpe_pm.exact_div(f_target_zpe, A_lifted); + } + + // add the last f_target + f_zpe_factors.push_back(f_target_zpe, 1); +} + +class mfactorization_combination_iterator : public upolynomial::factorization_combination_iterator_base { + + /** main variable */ + var m_x; + +public: + + mfactorization_combination_iterator(zp_factors const & factors, var x) + : upolynomial::factorization_combination_iterator_base(factors) + {} + + /** + \brief Filter the ones not in the degree set. + */ + bool filter_current() const { + return false; + } + + /** + \brief Returns the degree of the current selection. + */ + unsigned current_degree() const { + unsigned degree = 0; + zp_manager & pm = m_factors.pm(); + for (unsigned i = 0; i < left_size(); ++ i) { + degree += pm.degree(m_factors[m_current[i]], m_x); + } + return degree; + } + + void left(zp_polynomial_ref & out) const { + SASSERT(m_current_size > 0); + zp_manager & zp_pm = m_factors.pm(); + out = m_factors[m_current[0]]; + for (int i = 1; i < m_current_size; ++ i) { + out = zp_pm.mul(out, m_factors[m_current[i]]); + } + } + + void get_left_tail_coeff(numeral const & m, numeral & out) { + zp_manager & zp_pm = m_factors.pm(); + upolynomial::zp_numeral_manager & zp_nm = zp_pm.m(); + zp_nm.set(out, m); + for (int i = 0; i < m_current_size; ++ i) { + zp_nm.mul(out, zp_pm.numeral_tc(m_factors[m_current[i]]), out); + } + } + + void get_right_tail_coeff(numeral const & m, numeral & out) { + zp_manager & zp_pm = m_factors.pm(); + upolynomial::zp_numeral_manager & zp_nm = zp_pm.m(); + zp_nm.set(out, m); + + unsigned current = 0; + unsigned selection_i = 0; + + // selection is ordered, so we just take the ones in between that are not disable + while (current < m_factors.distinct_factors()) { + if (!m_enabled[current]) { + // by skipping the disabled we never skip a selected one + current ++; + } else { + if (selection_i >= m_current.size() || (int) current < m_current[selection_i]) { + SASSERT(m_factors.get_degree(current) == 1); + zp_nm.mul(out, zp_pm.numeral_tc(m_factors[current]), out); + current ++; + } else { + current ++; + selection_i ++; + } + } + } + } + + void right(zp_polynomial_ref & out) const { + SASSERT(m_current_size > 0); + zp_manager & zp_pm = m_factors.pm(); + upolynomial::zp_numeral_manager & zp_nm = zp_pm.m(); + + unsigned current = 0; + unsigned selection_i = 0; + + numeral one; + zp_nm.set(one, 1); + out = zp_pm.mk_const(one); + + // selection is ordered, so we just take the ones in between that are not disable + while (current < m_factors.distinct_factors()) { + if (!m_enabled[current]) { + // by skipping the disabled we never skip a selected one + current ++; + } else { + if (selection_i >= m_current.size() || (int) current < m_current[selection_i]) { + SASSERT(m_factors.get_degree(current) == 1); + out = zp_pm.mul(out, m_factors[current]); + current ++; + } else { + current ++; + selection_i ++; + } + } + } + } +}; + + +// the multivariate factorization +bool factor_square_free_primitive(polynomial_ref const & f, factors & f_factors) { + + TRACE("polynomial::factorization", tout << "polynomial::factor_square_free_primitive(f = " << f << ", factors = " << f_factors << ")" << endl;); + + manager & pm = f.m(); + numeral_manager & nm = pm.m(); + + // to start with, maybe this should be part of input + var x = pm.max_var(f); + // get all the variables + var_vector vars, vars_no_x; + pm.vars(f, vars); + for(unsigned i = 0; i < vars.size(); ++ i) { + if (vars[i] != x) { + vars_no_x.push_back(vars[i]); + } + } + SASSERT(vars.size() > 1); + + // degree of the main variable + unsigned x_degree = pm.degree(f, x); + // the leading coefficient + polynomial_ref f_lc(pm); + f_lc = pm.coeff(f, x, x_degree); + + // the vector of values we substitute + upolynomial::scoped_numeral_vector a(nm); + + // the univariate polynomial + upolynomial::manager upm(nm); + upolynomial::scoped_numeral_vector f_u(upm); + + // generate the values to substitute and substitute them to get f_u(x) = f(a, x), the univariate version of f + unsigned size = 1; + a.resize(vars_no_x.size()); + for (unsigned i = 0; i < a.size(); ++ i) { nm.reset(a[i]); } + generate_substitution_values(f, x, f_lc, vars_no_x, size, a, upm, f_u); + + TRACE("polynomial::factorization::multivariate", + tout << "f_u = "; upm.display(tout, f_u); tout << endl; + tout << "substitution:" << endl; + for (unsigned i = 0; i < vars_no_x.size(); ++ i) { + tout << "x" << vars[i] << " -> " << nm.to_string(a[i]) << endl; + }); + + // the primitive part of f_u + scoped_numeral f_u_lc(nm); + upolynomial::scoped_numeral_vector f_u_pp(nm); + upm.get_primitive_and_content(f_u, f_u_pp, f_u_lc); + + TRACE("polynomial::factorization::multivariate", + tout << "f_u_lc" << nm.to_string(f_u_lc) << endl; + tout << "f_u_pp = "; upm.display(tout, f_u_pp); tout << endl; + ); + + // factor the univariate one + upolynomial::factors factors_u(upm); + upolynomial::factor_square_free(upm, f_u, factors_u); + + TRACE("polynomial::factorization::multivariate", + tout << "factors_u = " << factors_u << endl; + ); + + // if there is no univariate factors, it's irreducible + if (factors_u.distinct_factors() == 1) { + f_factors.push_back(f, 1); + return false; + } + + // translate f with a, so that we work modulo x_i^k and not (x_i - a_i)^k + polynomial_ref f_t(pm); + // Do the translation, we must have that a[x] = 0 + pm.translate(f, vars_no_x, a, f_t); + + TRACE("polynomial::factorization::multivariate", + tout << "f_t = " << f_t << endl; + ); + + // the zp manager stuff, we'll be changing the base + upolynomial::zp_numeral_manager zp_nm(nm, 2); + upolynomial::zp_manager zp_upm(zp_nm); + + // get the first prime number p that keeps f square-free + scoped_numeral p(nm); + prime_iterator prime_it; + upolynomial::scoped_numeral_vector f_u_pp_zp(nm); + do { + // create Z_p with the next prime + nm.set(p, prime_it.next()); + // translate to Zp[x] + zp_nm.set_p(p); + upolynomial::to_zp_manager(zp_upm, f_u_pp, f_u_pp_zp); + } while (!zp_upm.is_square_free(f_u_pp_zp)); + + TRACE("polynomial::factorization::multivariate", + tout << "p = " << p << endl; + tout << "f_t = " << f_t << endl; + ); + + // convert factors of f to factors modulo p (monic ones) + upolynomial::zp_factors factors_u_zp(zp_upm); + upolynomial::scoped_numeral_vector current_factor(nm); + for (unsigned i = 0; i < factors_u.distinct_factors(); ++ i) { + upolynomial::to_zp_manager(zp_upm, factors_u[i], current_factor); + zp_upm.mk_monic(current_factor); + factors_u_zp.push_back_swap(current_factor, 1); + } + + TRACE("polynomial::factorization::multivariate", + tout << "factors_u_zp = " << factors_u_zp << endl; + ); + + // compute factor coefficient bound (pover p^e) of the translated f with the leading coefficient added, i.e. + // f_t*lc(f_t, x) = f_t*lc(f, x) + unsigned e; + scoped_numeral p_e(nm); + var2degree target_ideal; + upolynomial::scoped_numeral f_t_lc(nm); + nm.set(f_t_lc, pm.numeral_lc(f_t, x)); + polynomial_ref f_t_with_lc(pm); + f_t_with_lc = pm.mul(f_t_lc, f_t); + multivariate_factor_coefficient_bound(f_t_with_lc, x, p, e, p_e, target_ideal); + + TRACE("polynomial::factorization::multivariate", + tout << "target_ideal = " << target_ideal << endl; + ); + + // do the multivariate lifting of the translated one f_t + upolynomial::zp_numeral_manager zpe_nm(nm, p_e); + zp_manager zpe_pm(zpe_nm, &pm.mm()); + zp_manager zp_pm(zp_nm, &pm.mm()); + zp_factors factors_zpe(zpe_pm); + multivariate_hensel_lift(pm, zp_pm, zpe_pm, f_t, x, e, vars, upm, f_u_pp_zp, factors_u_zp, target_ideal, factors_zpe); + + TRACE("polynomial::factorization::multivariate", + tout << "factors_zpe = " << factors_zpe << endl; + ); + + // try the factors from the lifted combinations + factors f_t_factors(pm); + bool remove = false; + mfactorization_combination_iterator it(factors_zpe, x); + unsigned max_degre = pm.degree(f_t, x) / 2; + zp_polynomial_ref zpe_trial_factor(zpe_pm); + while (it.next(remove)) { + // + // our bound ensures we can extract the right factors of degree at most 1/2 of the original + // so, if out trial factor has degree bigger than 1/2, we need to take the rest of the factors + // but, if we take the rest and it works, it doesn't meen that the rest is factorized, so we still take out + // the original factor + // + bool using_left = it.current_degree() <= max_degre; + if (using_left) { + // do a quick check first + it.left(zpe_trial_factor); + } else { + // do a quick check first + it.right(zpe_trial_factor); + } + // add the lc(f_pp) to the trial divisor + zpe_trial_factor = zpe_pm.mul(f_t_lc, zpe_trial_factor); + polynomial_ref trial_factor(pm), trial_factor_quo(pm); + trial_factor = convert(zpe_pm, zpe_trial_factor, pm); + bool true_factor = true; + trial_factor_quo = pm.exact_div(f_t_with_lc, trial_factor); + // if division is precise we have a factor + if (true_factor) { + if (!using_left) { + // as noted above, we still use the original factor + trial_factor.swap(trial_factor_quo); + } + // We need to get the content out of the factor + upolynomial::scoped_numeral trial_factor_cont(nm); + pm.int_content(f_t, trial_factor_cont); + trial_factor = pm.exact_div(trial_factor, trial_factor_cont); + // add the factor + f_t_factors.push_back(trial_factor, 1); + // we continue with the int-primitive quotient (with the lc added back) + // but we also have to keep lc(f_t)*f_t + pm.int_content(trial_factor_quo, f_t_lc); // content + trial_factor_quo = pm.exact_div(trial_factor_quo, f_t_lc); + nm.set(f_t_lc, pm.numeral_lc(trial_factor_quo, x)); + f_t = pm.mul(f_t_lc, trial_factor_quo); + // but we also remove it from the iterator + remove = true; + } else { + // don't remove this combination + remove = false; + } + } + + // translate the factor back + for (unsigned i = 0; i < a.size(); ++ i) { + nm.neg(a[i]); + } + for (unsigned i = 0; i < f_t_factors.distinct_factors(); ++ i) { + polynomial_ref factor(pm); + pm.translate(f_t_factors[i], vars_no_x, a, factor); + f_factors.push_back(factor, 1); + } + + TRACE("polynomial::factorization", tout << "polynomial::factor_square_free_primitive(f = " << f << ") => " << f_factors << endl;); + return true; +} + +}; // end polynomial namespace + +#endif diff --git a/lib/polynomial_factorization.h b/lib/polynomial_factorization.h new file mode 100644 index 000000000..48b4efd98 --- /dev/null +++ b/lib/polynomial_factorization.h @@ -0,0 +1,65 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + polynomial_factorization.h + +Abstract: + + Methods for factoring polynomials. + +Author: + + Dejan (t-dejanj) 2011-11-29 + +Notes: + + [1] Elwyn Ralph Berlekamp. Factoring Polynomials over Finite Fields. Bell System Technical Journal, + 46(8-10):1853–1859, 1967. + [2] Donald Ervin Knuth. The Art of Computer Programming, volume 2: Seminumerical Algorithms. Addison Wesley, third + edition, 1997. + [3] Henri Cohen. A Course in Computational Algebraic Number Theory. Springer Verlag, 1993. + +--*/ + +#pragma once + +#if 0 +// Disabled for reorg +#include "polynomial.h" +#include "upolynomial.h" +#include "bit_vector.h" +#include "z3_exception.h" + +namespace polynomial { + + /** + \brief Something to throw when we are in trouble. + */ + class factorization_exception : public default_exception { + public: + factorization_exception(char const * msg) : default_exception(msg) {} + }; + + /** + \brief Factor the polynomial f from Z[x1, ..., x_n]. Returns the index of the last factor that is completely + factored. I.e., if the method returns m, then f_1, ..., f_m are true irreducible factors, and the rest might + be further reducible. + */ + unsigned factor(polynomial_ref & f, factors & factors); + + /** + \brief Factor the square-free primitive polynomial f from Z[x1, ..., x_n]. Returns true if the factorization + was sucesseful, i.e. it was completed and the result is complete. Otherwise the quarantee is that the all but + the last factor are irreducible. + */ + bool factor_square_free_primitive(polynomial_ref const & f, factors & factors); +} + +inline std::ostream & operator<<(std::ostream & out, polynomial::factors & factors) { + factors.display(out); + return out; +} + +#endif diff --git a/lib/polynomial_primes.h b/lib/polynomial_primes.h new file mode 100644 index 000000000..9b74c879c --- /dev/null +++ b/lib/polynomial_primes.h @@ -0,0 +1,72 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + polynomial_primes.h + +Abstract: + + Some prime numbers for modular computations. + +Author: + + Leonardo (leonardo) 2011-12-21 + +Notes: + +--*/ +#ifndef _POLYNOMIAL_PRIMES_H_ +#define _POLYNOMIAL_PRIMES_H_ + +namespace polynomial { +#define NUM_SMALL_PRIMES 11 + const unsigned g_small_primes[NUM_SMALL_PRIMES] = { + 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53 + }; + +#if 0 +#define NUM_BIG_PRIMES 77 + const unsigned g_big_primes[NUM_BIG_PRIMES] = { + 16777259, 16777289, 16777291, 16777331, 16777333, 16777337, 16777381, + 16777421, 16777441, 16777447, 16777469, 16777499, 16777507, 16777531, + 16777571, 16777577, 16777597, 16777601, 16777619, 16777633, 16777639, + 16777643, 16777669, 16777679, 16777681, 16777699, 16777711, 16777721, + 16777723, 16777729, 16777751, 16777777, 16777781, 16777807, 16777811, + 16777823, 16777829, 16777837, 16777853, 16777903, 16777907, 16777949, + 16777967, 16777973, 16777987, 16777991, 16778009, 16778023, 16778071, + 16778077, 16778089, 16778123, 16778129, 16778137, 16778147, 16778173, + 16778227, 16778231, 16778291, 16778309, 16778357, 16778383, 16778401, + 16778413, 16778429, 16778441, 16778449, 16778453, 16778497, 16778521, + 16778537, 16778543, 16778549, 16778561, 16778603, 16778623, 16778627 + }; +#else +#define NUM_BIG_PRIMES 231 + const unsigned g_big_primes[NUM_BIG_PRIMES] = { + 39103, 39107, 39113, 39119, 39133, 39139, 39157, 39161, 39163, 39181, 39191, + 39199, 39209, 39217, 39227, 39229, 39233, 39239, 39241, 39251, 39293, 39301, + 39313, 39317, 39323, 39341, 39343, 39359, 39367, 39371, 39373, 39383, 39397, + 39409, 39419, 39439, 39443, 39451, 39461, 39499, 39503, 39509, 39511, 39521, + 39541, 39551, 39563, 39569, 39581, 39607, 39619, 39623, 39631, 39659, 39667, + 39671, 39679, 39703, 39709, 39719, 39727, 39733, 39749, 39761, 39769, 39779, + 39791, 39799, 39821, 39827, 39829, 39839, 39841, 39847, 39857, 39863, 39869, + 39877, 39883, 39887, 39901, 39929, 39937, 39953, 39971, 39979, 39983, 39989, + 40009, 40013, 40031, 40037, 40039, 40063, 40087, 40093, 40099, 40111, 40123, + 40127, 40129, 40151, 40153, 40163, 40169, 40177, 40189, 40193, 40213, 40231, + 40237, 40241, 40253, 40277, 40283, 40289, 40343, 40351, 40357, 40361, 40387, + 40423, 40427, 40429, 40433, 40459, 40471, 40483, 40487, 40493, 40499, 40507, + 40519, 40529, 40531, 40543, 40559, 40577, 40583, 40591, 40597, 40609, 40627, + 40637, 40639, 40693, 40697, 40699, 40709, 40739, 40751, 40759, 40763, 40771, + 40787, 40801, 40813, 40819, 40823, 40829, 40841, 40847, 40849, 40853, 40867, + 40879, 40883, 40897, 40903, 40927, 40933, 40939, 40949, 40961, 40973, 40993, + 41011, 41017, 41023, 41039, 41047, 41051, 41057, 41077, 41081, 41113, 41117, + 41131, 41141, 41143, 41149, 41161, 41177, 41179, 41183, 41189, 41201, 41203, + 41213, 41221, 41227, 41231, 41233, 41243, 41257, 41263, 41269, 41281, 41299, + 41333, 41341, 41351, 41357, 41381, 41387, 41389, 41399, 41411, 41413, 41443, + 41453, 41467, 41479, 41491, 41507, 41513, 41519, 41521, 41539, 41543, 41549 + }; +#endif + +}; + +#endif diff --git a/lib/polynomial_var2value.h b/lib/polynomial_var2value.h new file mode 100644 index 000000000..708b03725 --- /dev/null +++ b/lib/polynomial_var2value.h @@ -0,0 +1,50 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + polynomial_var2value.h + +Abstract: + + Simple implementations of the var2value interface. + +Author: + + Leonardo (leonardo) 2012-01-05 + +Notes: + +--*/ +#ifndef _POLYNOMIAL_VAR2VALUE_H_ +#define _POLYNOMIAL_VAR2VALUE_H_ + +#include"polynomial.h" +#include"scoped_numeral_vector.h" + +namespace polynomial { + + // default implementation used for debugging purposes + template + class simple_var2value : public var2value { + var_vector m_xs; + _scoped_numeral_vector m_vs; + public: + simple_var2value(ValManager & m):m_vs(m) {} + void push_back(var x, typename ValManager::numeral const & v) { m_xs.push_back(x); m_vs.push_back(v); } + virtual ValManager & m() const { return m_vs.m(); } + virtual bool contains(var x) const { return std::find(m_xs.begin(), m_xs.end(), x) != m_xs.end(); } + virtual typename ValManager::numeral const & operator()(var x) const { + for (unsigned i = 0; i < m_xs.size(); i++) + if (m_xs[i] == x) + return m_vs[i]; + UNREACHABLE(); + static typename ValManager::numeral zero; + return zero; + } + }; + +}; + + +#endif diff --git a/lib/pool.h b/lib/pool.h new file mode 100644 index 000000000..bc79a0828 --- /dev/null +++ b/lib/pool.h @@ -0,0 +1,50 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + pool.h + +Abstract: + + Object pool. + +Author: + + Leonardo de Moura (leonardo) 2007-02-15. + +Revision History: + +--*/ +#ifndef _POOL_H_ +#define _POOL_H_ + +#include"util.h" +#include"vector.h" + +template +class pool { + ptr_vector m_objs; +public: + ~pool() { + std::for_each(m_objs.begin(), m_objs.end(), delete_proc()); + } + + T * mk() { + if (m_objs.empty()) { + return alloc(T); + } + else { + T * r = m_objs.back(); + m_objs.pop_back(); + return r; + } + } + + void recycle(T * obj) { + m_objs.push_back(obj); + } +}; + +#endif /* _POOL_H_ */ + diff --git a/lib/pop_scopes.h b/lib/pop_scopes.h new file mode 100644 index 000000000..1871d4682 --- /dev/null +++ b/lib/pop_scopes.h @@ -0,0 +1,37 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + pop_scopes.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-02. + +Revision History: + +--*/ +#ifndef _POP_SCOPES_H_ +#define _POP_SCOPES_H_ + +#define POP_SCOPES(_num_scopes, _lim, _trail, _action) \ + if (_num_scopes > 0) \ + { \ + unsigned scope_lvl = _lim.size(); \ + unsigned new_lvl = scope_lvl - _num_scopes; \ + unsigned curr_size = _trail.size(); \ + unsigned old_size = _lim[new_lvl]; \ + for (unsigned i = curr_size-1; i >= old_size && i != static_cast(-1); --i) { \ + _action; \ + } \ + _trail.shrink(old_size); \ + _lim.shrink(new_lvl); \ + } + +#endif /* _POP_SCOPES_H_ */ + diff --git a/lib/pp.cpp b/lib/pp.cpp new file mode 100644 index 000000000..43f19e166 --- /dev/null +++ b/lib/pp.cpp @@ -0,0 +1,148 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + pp.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-20. + +Revision History: + +--*/ +#include"pp.h" +using namespace format_ns; + +pp_params g_pp_params; + +void set_pp_default_params(pp_params const & p) { + g_pp_params = p; +} + +void register_pp_params(ini_params & p) { + g_pp_params.register_params(p); +} + +pp_params const & get_pp_default_params() { + return g_pp_params; +} + +static std::pair space_upto_line_break(ast_manager & m, format * f) { + unsigned r; + SASSERT(f->get_family_id() == fm(m).get_family_id("format")); + decl_kind k = f->get_decl_kind(); + switch(k) { + case OP_STRING: { + size_t len = strlen(f->get_decl()->get_parameter(0).get_symbol().bare_str()); + return std::make_pair(static_cast(len), false); + } + case OP_CHOICE: + return space_upto_line_break(m, to_app(f->get_arg(0))); + case OP_COMPOSE: + r = 0; + for (unsigned i = 0; i < f->get_num_args(); i++) { + std::pair pair = space_upto_line_break(m, to_app(f->get_arg(i))); + r += pair.first; + if (pair.second) + return std::make_pair(r, true); + } + return std::make_pair(r, false); + case OP_INDENT: + return space_upto_line_break(m, to_app(f->get_arg(0))); + case OP_LINE_BREAK: + case OP_LINE_BREAK_EXT: + return std::make_pair(0, true); + default: + return std::make_pair(0, false); + } +} + +inline bool fits(ast_manager & m, format * f, unsigned space_left) { + unsigned s = space_upto_line_break(m, f).first; + TRACE("fits", tout << "s: " << s << " space_left " << space_left << "\n";); + return s <= space_left; +} + +void pp(std::ostream & out, format * f, ast_manager & m, pp_params const & p) { + unsigned pos = 0; + unsigned ribbon_pos = 0; + unsigned line = 0; + unsigned len; + unsigned i; + int space_left; + svector > todo; + todo.push_back(std::make_pair(f, 0)); + app_ref space(mk_string(m, " "), fm(m)); + while (!todo.empty()) { + if (line >= p.m_pp_max_num_lines) + return; + std::pair pair = todo.back(); + format * f = pair.first; + unsigned indent = pair.second; + todo.pop_back(); + SASSERT(f->get_family_id() == fm(m).get_family_id("format")); + switch (f->get_decl_kind()) { + case OP_STRING: + if (p.m_pp_bounded && pos > p.m_pp_max_width) + break; + len = static_cast(strlen(f->get_decl()->get_parameter(0).get_symbol().bare_str())); + if (p.m_pp_bounded && pos + len > p.m_pp_max_width) { + out << "..."; + break; + } + pos += len; + ribbon_pos += len; + out << f->get_decl()->get_parameter(0).get_symbol(); + break; + case OP_INDENT: + todo.push_back(std::make_pair(to_app(f->get_arg(0)), + std::min(indent + f->get_decl()->get_parameter(0).get_int(), + p.m_pp_max_indent))); + break; + case OP_COMPOSE: + i = f->get_num_args(); + while (i > 0) { + --i; + todo.push_back(std::make_pair(to_app(f->get_arg(i)), indent)); + } + break; + case OP_CHOICE: + space_left = std::min(p.m_pp_max_width - pos, p.m_pp_max_ribbon - pos); + if (space_left > 0 && fits(m, to_app(f->get_arg(0)), space_left)) + todo.push_back(std::make_pair(to_app(f->get_arg(0)), indent)); + else + todo.push_back(std::make_pair(to_app(f->get_arg(1)), indent)); + break; + case OP_LINE_BREAK: + case OP_LINE_BREAK_EXT: + if (p.m_pp_single_line) { + todo.push_back(std::make_pair(space, indent)); + break; + } + pos = indent; + ribbon_pos = 0; + line++; + if (line < p.m_pp_max_num_lines) { + out << "\n"; + for (unsigned i = 0; i < indent; i++) + out << " "; + } + else + out << "...\n"; + break; + default: + break; + } + } +} + +void pp(std::ostream & out, format_ns::format * f, ast_manager & m) { + pp(out, f, m, g_pp_params); +} + diff --git a/lib/pp.h b/lib/pp.h new file mode 100644 index 000000000..43c1de7b7 --- /dev/null +++ b/lib/pp.h @@ -0,0 +1,34 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + pp.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-20. + +Revision History: + +--*/ +#ifndef _PP_H_ +#define _PP_H_ + +#include"format.h" +#include"pp_params.h" + +void set_pp_default_params(pp_params const & p); +void register_pp_params(ini_params & p); + +pp_params const & get_pp_default_params(); + +void pp(std::ostream & out, format_ns::format * f, ast_manager & m, pp_params const & p); +void pp(std::ostream & out, format_ns::format * f, ast_manager & m); + +#endif /* _PP_H_ */ + diff --git a/lib/pp_params.cpp b/lib/pp_params.cpp new file mode 100644 index 000000000..0cf3e455d --- /dev/null +++ b/lib/pp_params.cpp @@ -0,0 +1,57 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + pp_params.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-20. + +Revision History: + +--*/ + +#include"pp_params.h" + +pp_params::pp_params(): + m_pp_max_indent(UINT_MAX), + m_pp_max_num_lines(UINT_MAX), + m_pp_max_width(80), + m_pp_max_ribbon(80), + m_pp_max_depth(5), + m_pp_min_alias_size(10), + m_pp_decimal(false), + m_pp_decimal_precision(10), + m_pp_bv_lits(true), + m_pp_bv_neg(false), + m_pp_flat_assoc(true), + m_pp_fixed_indent(false), + m_pp_single_line(false), + m_pp_bounded(false), + m_pp_simplify_implies(false) { +} + +void pp_params::register_params(ini_params & p) { + p.register_unsigned_param("PP_MAX_INDENT", m_pp_max_indent, "max. indentation in pretty printer", true); + p.register_unsigned_param("PP_MAX_NUM_LINES", m_pp_max_num_lines, "max. number of lines to be displayed in pretty printer", true); + p.register_unsigned_param("PP_MAX_WIDTH", m_pp_max_width, "max. width in pretty printer", true); + p.register_unsigned_param("PP_MAX_RIBBON", m_pp_max_ribbon, "max. ribbon (width - indentation) in pretty printer", true); + p.register_unsigned_param("PP_MAX_DEPTH", m_pp_max_depth, "max. term depth (when pretty printing SMT2 terms/formulas)", true); + p.register_unsigned_param("PP_MIN_ALIAS_SIZE", m_pp_min_alias_size, "min. size for creating an alias for a shared term (when pretty printing SMT2 terms/formulas)", true); + p.register_bool_param("PP_DECIMAL", m_pp_decimal, "pretty print real numbers using decimal notation (the output may be truncated). Z3 adds a '?' if the value is not precise", true); + p.register_unsigned_param("PP_DECIMAL_PRECISION", m_pp_decimal_precision, "maximum number of decimal places to be used when PP_DECIMAL=true", true); + p.register_bool_param("PP_BV_LITERALS", m_pp_bv_lits, "use Bit-Vector literals (e.g, #x0F and #b0101) during pretty printing", true); + p.register_bool_param("PP_BV_NEG", m_pp_bv_neg, "use bvneg when displaying Bit-Vector literals where the most significant bit is 1", true); + p.register_bool_param("PP_FLAT_ASSOC", m_pp_flat_assoc, "flat associative operators (when pretty printing SMT2 terms/formulas)", true); + p.register_bool_param("PP_FIXED_INDENT", m_pp_fixed_indent, "use a fixed indentation for applications", true); + p.register_bool_param("PP_SINGLE_LINE", m_pp_single_line, "ignore line breaks when true", true); + p.register_bool_param("PP_BOUNDED", m_pp_bounded, "ignore characters exceeding max widht", true); + p.register_bool_param("PP_SIMPLIFY_IMPLIES", m_pp_simplify_implies, "simplify nested implications for pretty printing", true); +} + diff --git a/lib/pp_params.h b/lib/pp_params.h new file mode 100644 index 000000000..970f97968 --- /dev/null +++ b/lib/pp_params.h @@ -0,0 +1,46 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + pp_params.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-20. + +Revision History: + +--*/ +#ifndef _PP_PARAMS_H_ +#define _PP_PARAMS_H_ + +#include"ini_file.h" + +struct pp_params { + unsigned m_pp_max_indent; // max. indentation + unsigned m_pp_max_num_lines; // max. num. lines + unsigned m_pp_max_width; // max. width + unsigned m_pp_max_ribbon; // max. ribbon: width - indentation + unsigned m_pp_max_depth; + unsigned m_pp_min_alias_size; + bool m_pp_decimal; // display reals using decimals + unsigned m_pp_decimal_precision; // max. number of decimal places + bool m_pp_bv_lits; + bool m_pp_bv_neg; // use bvneg to display bit-vector literals which the most significant bit is 1 + bool m_pp_flat_assoc; + bool m_pp_fixed_indent; + bool m_pp_single_line; // ignore line breaks if true + bool m_pp_bounded; // ignore characters exceeding max width. + bool m_pp_simplify_implies; // simplify nested implications during pretty printing + + pp_params(); + void register_params(ini_params & p); +}; + +#endif /* _PP_PARAMS_H_ */ + diff --git a/lib/precedence.cpp b/lib/precedence.cpp new file mode 100644 index 000000000..797a723d9 --- /dev/null +++ b/lib/precedence.cpp @@ -0,0 +1,191 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + precedence.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-08. + +Revision History: + +--*/ +#include"precedence.h" +#include"warning.h" + +lex_precedence::lex_precedence(unsigned n, precedence ** ps): + m_precedences(n, ps) { +} + +lex_precedence::~lex_precedence() { + std::for_each(m_precedences.begin(), m_precedences.end(), delete_proc()); +} + +int lex_precedence::compare(func_decl * f, func_decl * g) { + int r = 0; + ptr_vector::iterator it = m_precedences.begin(); + ptr_vector::iterator end = m_precedences.end(); + for (; it != end; ++it) { + r = (*it)->compare(f, g); + if (r != 0) + return r; + } + return r; +} + +inv_precedence::inv_precedence(precedence * p): + m_precedence(p) { + SASSERT(p); +} + +inv_precedence::~inv_precedence() { + dealloc(m_precedence); +} + +int inv_precedence::compare(func_decl * f, func_decl * g) { + return m_precedence->compare(g, f); +} + +int arbitrary_precedence::compare(func_decl * f, func_decl * g) { + return static_cast(f->get_decl_id()) - static_cast(g->get_decl_id()); +} + +int arity_precedence::compare(func_decl * f, func_decl * g) { + return static_cast(f->get_arity()) - static_cast(g->get_arity()); +} + +int interpreted_precedence::compare(func_decl * f, func_decl * g) { + return static_cast(f->get_family_id() == null_family_id) - static_cast(g->get_family_id() == null_family_id); +} + +inline int ext_precedence::get_func_pos(func_decl * f) { + unsigned id = f->get_decl_id(); + return m_cached.get(id, m_undefined); +} + +int ext_precedence::compare(func_decl * f, func_decl * g) { + return get_func_pos(f) - get_func_pos(g); +} + +ext_precedence::ext_precedence(ast_manager & m, unsigned num_decls, func_decl ** decls): + m_undefined(num_decls), + m_cached_domain(m) { + for (unsigned i = 0; i < num_decls; i++) { + m_cached.setx(decls[i]->get_decl_id(), i, m_undefined); + m_cached_domain.push_back(decls[i]); + } +} + +ext_precedence::~ext_precedence() { +} + +int abstract_user_precedence::get_decl_pos(decl * d) { + unsigned id = d->get_decl_id(); + int pos = m_cached.get(id, -1); + if (pos == -1) { + if (!m_symbol2pos.find(d->get_name(), pos)) + pos = m_undefined; + m_cached.setx(id, pos, -1); + SASSERT(pos != -1); + } + return pos; +} + +abstract_user_precedence::abstract_user_precedence(ast_manager & m, unsigned num_syms, symbol * syms): + m_undefined(num_syms), + m_cached_domain(m) { + for (unsigned i = 0; i < num_syms; i++) + m_symbol2pos.insert(syms[i], i); +} + +abstract_user_precedence::~abstract_user_precedence() { +} + +int user_precedence::compare(func_decl * f, func_decl * g) { + return get_decl_pos(f) - get_decl_pos(g); +} + +int user_sort_precedence::compare(func_decl * f, func_decl * g) { + return get_decl_pos(f->get_range()) - get_decl_pos(g->get_range()); +} + +static precedence * mk_default_precedence(ast_manager & m, order_params const & params) { + ptr_buffer ps; + if (!params.m_order_precedence.empty()) + ps.push_back(alloc(user_precedence, m, params.m_order_precedence.size(), params.m_order_precedence.c_ptr())); + ps.push_back(alloc(interpreted_precedence)); + ps.push_back(alloc(arity_precedence)); + ps.push_back(alloc(arbitrary_precedence)); + return alloc(lex_precedence, ps.size(), ps.c_ptr()); +} + +static precedence * mk_inv_precedence(bool inv, precedence * p) { + return inv ? alloc(inv_precedence,p) : p; +} + +static precedence * mk_lex_precedence(ptr_buffer const & ps) { + unsigned sz = ps.size(); + if (sz == 0) + return alloc(arbitrary_precedence); + else if (sz == 1) + return ps[0]; + else + return alloc(lex_precedence, sz, ps.c_ptr()); +} + +precedence * mk_precedence(ast_manager & m, order_params const & params) { + if (params.m_order_precedence_gen.empty()) + return mk_default_precedence(m, params); + + symbol user("user"); + symbol definition("definition"); + symbol interpreted("interpreted"); + symbol frequency("frequency"); + symbol arity("arity"); + symbol arbitrary("arbitrary"); + symbol inv("-"); + + ptr_buffer ps; + + svector::const_iterator it = params.m_order_precedence_gen.begin(); + svector::const_iterator end = params.m_order_precedence_gen.end(); + bool prev_inv = false; + for (; it != end; ++it) { + symbol curr = *it; + if (curr == user) { + if (params.m_order_precedence.empty()) + ps.push_back(mk_inv_precedence(prev_inv, alloc(user_precedence, m, params.m_order_precedence.size(), params.m_order_precedence.c_ptr()))); + } + else if (curr == definition) { + warning_msg("definition precedence was not implement yet."); + } + else if (curr == interpreted) { + ps.push_back(mk_inv_precedence(prev_inv, alloc(interpreted_precedence))); + } + else if (curr == frequency) { + warning_msg("frequency precedence was not implement yet."); + } + else if (curr == arity) { + ps.push_back(mk_inv_precedence(prev_inv, alloc(arity_precedence))); + } + else if (curr == arbitrary) { + ps.push_back(mk_inv_precedence(prev_inv, alloc(arbitrary_precedence))); + // it is pointless to continue, arbitrary_precedence is a total order + return mk_lex_precedence(ps); + } + else if (curr == inv) { + prev_inv = true; + } + else { + warning_msg("invalid precedence generator: ignoring atom '%s'.", curr.bare_str()); + } + } + + return mk_lex_precedence(ps); +} diff --git a/lib/precedence.h b/lib/precedence.h new file mode 100644 index 000000000..82cf81be1 --- /dev/null +++ b/lib/precedence.h @@ -0,0 +1,142 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + precedence.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-08. + +Revision History: + +--*/ +#ifndef _PRECEDENCE_H_ +#define _PRECEDENCE_H_ + +#include"ast.h" +#include"order_params.h" + +/** + \brief Abstract functor used to implement an order on function symbols. +*/ +class precedence { +public: + virtual ~precedence() {} + virtual int compare(func_decl * f, func_decl * g) = 0; + bool operator()(func_decl * f, func_decl * g) { return compare(f, g) < 0; } +}; + +/** + \brief Compose different precedence functors using lexicographical order. +*/ +class lex_precedence : public precedence { + ptr_vector m_precedences; +public: + lex_precedence(unsigned n, precedence ** ps); + virtual ~lex_precedence(); + virtual int compare(func_decl * f, func_decl * g); +}; + +/** + \brief Invert functor + */ +class inv_precedence : public precedence { + precedence * m_precedence; +public: + inv_precedence(precedence * p); + virtual ~inv_precedence(); + virtual int compare(func_decl * f, func_decl * g); +}; + +/** + \brief An arbitrary total order based on the func_decl ids. +*/ +class arbitrary_precedence : public precedence { +public: + virtual int compare(func_decl * f, func_decl * g); +}; + +/** + \brief Precedence based on the arity. + + \remark This is not a total order, so it must be combined + with other precedence functors (e.g., arbitrary_precedence). +*/ +class arity_precedence : public precedence { +public: + virtual int compare(func_decl * f, func_decl * g); +}; + +/** + \brief Interpreted function symbols are smaller. + */ +class interpreted_precedence : public precedence { +public: + virtual int compare(func_decl * f, func_decl * g); +}; + +/** + \brief A precedence given as a sequence of func_decls. + This functor is used to encapsulate automatically/externally generated + precedences. +*/ +class ext_precedence : public precedence { + unsigned m_undefined; // position for func_decl's not specified by the user. + int_vector m_cached; // mapping: decl -> int + decl_ref_vector m_cached_domain; + + int get_func_pos(func_decl * f); +public: + ext_precedence(ast_manager & m, unsigned num_decls, func_decl ** decls); + virtual ~ext_precedence(); + virtual int compare(func_decl * f, func_decl * g); +}; + +/** + \brief Abstract class for user precedences based on + function or sort symbols. +*/ +class abstract_user_precedence : public precedence { +protected: + symbol_table m_symbol2pos; + unsigned m_undefined; // position for symbols not specified by the user. + int_vector m_cached; // mapping: decl -> int + decl_ref_vector m_cached_domain; + + int get_decl_pos(decl * d); +public: + abstract_user_precedence(ast_manager & m, unsigned num_syms, symbol * syms); + virtual ~abstract_user_precedence(); +}; + +/** + \brief A user defined precedence given as a sequence of symbols. + + \remark User provided precedences are usually not total. +*/ +class user_precedence : public abstract_user_precedence { +public: + user_precedence(ast_manager & m, unsigned num_syms, symbol * syms):abstract_user_precedence(m, num_syms, syms) {} + virtual int compare(func_decl * f, func_decl * g); +}; + +/** + \brief A user defined precedence given as a sequence of sort symbols. + The functions are ordered based on their range sort. +*/ +class user_sort_precedence : public abstract_user_precedence { +public: + user_sort_precedence(ast_manager & m, unsigned num_syms, symbol * syms):abstract_user_precedence(m, num_syms, syms) {} + virtual int compare(func_decl * f, func_decl * g); +}; + +precedence * mk_precedence(ast_manager & m, order_params const & params); + +#endif /* _PRECEDENCE_H_ */ + diff --git a/lib/preprocessor.cpp b/lib/preprocessor.cpp new file mode 100644 index 000000000..703fecc0f --- /dev/null +++ b/lib/preprocessor.cpp @@ -0,0 +1,155 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + preprocessor.cpp + +Abstract: + + Preprocessor + +Author: + + Leonardo de Moura (leonardo) 2008-01-17. + +Revision History: + +--*/ +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"preprocessor.h" +#include"for_each_expr.h" +#include"num_occurs.h" + +preprocessor::preprocessor(ast_manager & m, defined_names & d, simplifier & s, preprocessor_params & p): + m_params(p), + m_manager(m), + m_simp(s), + m_nnf(m, d, p), + m_cnf(m, d, p), + m_der(m), + m_push_app_ite(s, p.m_lift_ite == LI_CONSERVATIVE), + m_cnf_todo(m), + m_cnf_todo_prs(m), + m_push_todo(m), + m_push_todo_prs(m) { + switch (m_params.m_cnf_mode) { + case CNF_QUANT: + if (m_params.m_nnf_mode == NNF_SKOLEM) + m_params.m_nnf_mode = NNF_QUANT; + break; + case CNF_OPPORTUNISTIC: + if (m_params.m_nnf_mode == NNF_SKOLEM) + m_params.m_nnf_mode = NNF_QUANT; + break; + case CNF_FULL: + m_params.m_nnf_mode = NNF_FULL; + break; + default: + break; + } +} + +#ifdef _TRACE +struct num_occurs_pp { + + ast_manager & m_manager; + std::ostream & m_out; + num_occurs m_occurs; + + num_occurs_pp(ast_manager & m, std::ostream & out, expr * root): + m_manager(m), + m_out(out) { + m_occurs(root); + } + void operator()(var * n) {} + void operator()(app * n) { + unsigned val = m_occurs.get_num_occs(n); + if (val > 1 && m_manager.is_bool(n)) + m_out << "#" << n->get_id() << " -> " << val << " " << n->get_ref_count() << "\n"; + } + void operator()(quantifier * n) {} +}; +#endif + +void preprocessor::operator()(expr * e, proof * in_pr, expr_ref_vector & result, proof_ref_vector & result_prs) { + m_cnf_todo.reset(); + m_cnf_todo_prs.reset(); + + expr_ref r1(m_manager); + proof_ref pr1(m_manager); + m_simp(e, r1, pr1); + in_pr = m_manager.mk_modus_ponens(in_pr, pr1); + + expr_ref r2(m_manager); + proof_ref pr2(m_manager); + m_nnf(r1, m_cnf_todo, m_cnf_todo_prs, r2, pr2); + in_pr = m_manager.mk_modus_ponens(in_pr, pr2); + + TRACE("preprocessor", tout << mk_ll_pp(r2, m_manager); + num_occurs_pp proc(m_manager, tout, r2); + for_each_expr(proc, r2);); + + m_cnf_todo.push_back(r2); + m_cnf_todo_prs.push_back(in_pr); + + unsigned sz = m_cnf_todo.size(); + for (unsigned i = 0; i < sz; i++) { + m_push_todo.reset(); + m_push_todo_prs.reset(); + + expr * e = m_cnf_todo.get(i); + if (m_params.m_lift_ite != LI_NONE) { + m_push_app_ite(e, r1, pr1); + } + else { + r1 = e; + pr1 = 0; + } + + TRACE("preprocessor", tout << mk_ll_pp(r1, m_manager);); + + expr_ref aux(r1, m_manager); + m_simp(aux, r1, pr2); + pr1 = m_manager.mk_transitivity(pr1, pr2); + + TRACE("preprocessor", tout << mk_ll_pp(r1, m_manager);); + + aux = r1; + m_der(aux, r1, pr2); + pr1 = m_manager.mk_transitivity(pr1, pr2); + + TRACE("preprocessor", tout << mk_ll_pp(r1, m_manager);); + + if (m_manager.proofs_enabled()) + in_pr = m_manager.mk_modus_ponens(m_cnf_todo_prs.get(i), pr1); + else + in_pr = 0; + + aux = r1; + m_cnf(aux, m_push_todo, m_push_todo_prs, r1, pr1); + m_push_todo.push_back(r1); + + TRACE("preprocessor", tout << mk_ll_pp(r1, m_manager);); + + if (m_manager.proofs_enabled()) { + in_pr = m_manager.mk_modus_ponens(in_pr, pr1); + m_push_todo_prs.push_back(in_pr); + } + + unsigned sz2 = m_push_todo.size(); + for (unsigned j = 0; j < sz2; j++) { + expr * e = m_push_todo.get(j); + m_simp(e, r1, pr1); + + TRACE("preprocessor", tout << mk_ll_pp(r1, m_manager);); + + if (m_manager.proofs_enabled()) + in_pr = m_manager.mk_modus_ponens(m_push_todo_prs.get(j), pr1); + else + in_pr = 0; + push_assertion(m_manager, r1, in_pr, result, result_prs); + } + } +} diff --git a/lib/preprocessor.h b/lib/preprocessor.h new file mode 100644 index 000000000..c7ce9e847 --- /dev/null +++ b/lib/preprocessor.h @@ -0,0 +1,51 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + preprocessor.h + +Abstract: + + Preprocess AST before adding them to the logical context + +Author: + + Leonardo de Moura (leonardo) 2008-01-17. + +Revision History: + +--*/ +#ifndef _PREPROCESSOR_H_ +#define _PREPROCESSOR_H_ + +#include"preprocessor_params.h" +#include"simplifier.h" +#include"pattern_inference.h" +#include"nnf.h" +#include"cnf.h" +#include"der.h" +#include"push_app_ite.h" + +/** + \brief Functor used to preprocess expressions before adding them to + the logical context. +*/ +class preprocessor { + preprocessor_params & m_params; + ast_manager & m_manager; + simplifier & m_simp; + nnf m_nnf; + cnf m_cnf; + der_star m_der; + push_app_ite m_push_app_ite; + expr_ref_vector m_cnf_todo; + proof_ref_vector m_cnf_todo_prs; + expr_ref_vector m_push_todo; + proof_ref_vector m_push_todo_prs; +public: + preprocessor(ast_manager & m, defined_names & d, simplifier & s, preprocessor_params & p); + void operator()(expr * e, proof * in_pr, expr_ref_vector & result, proof_ref_vector & result_prs); +}; + +#endif /* _PREPROCESSOR_H_ */ diff --git a/lib/preprocessor_params.h b/lib/preprocessor_params.h new file mode 100644 index 000000000..1b582ff83 --- /dev/null +++ b/lib/preprocessor_params.h @@ -0,0 +1,140 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + preprocessor_params.h + +Abstract: + + Preprocess AST before adding them to the logical context + +Author: + + Leonardo de Moura (leonardo) 2008-01-17. + +Revision History: + +--*/ +#ifndef _PREPROCESSOR_PARAMS_H_ +#define _PREPROCESSOR_PARAMS_H_ + +#include"nnf_params.h" +#include"cnf_params.h" +#include"pattern_inference_params.h" +#include"bit_blaster_params.h" +#include"bv_simplifier_params.h" + +enum lift_ite_kind { + LI_NONE, + LI_CONSERVATIVE, + LI_FULL +}; + +enum q_arith_kind { + QA_NONE, + QA_COOPER, + QA_OMEGA, + QA_ALTERNATE +}; + +struct preprocessor_params : public nnf_params, public cnf_params, public pattern_inference_params, + public bit_blaster_params, public bv_simplifier_params { + lift_ite_kind m_lift_ite; + lift_ite_kind m_ng_lift_ite; // lift ite for non ground terms + bool m_pull_cheap_ite_trees; + bool m_pull_nested_quantifiers; + bool m_eliminate_term_ite; + bool m_eliminate_and; // represent (and a b) as (not (or (not a) (not b))) + bool m_reverse_implies; // translate (implies a b) into (or b (not a)) + bool m_macro_finder; + bool m_solver; + bool m_propagate_values; + bool m_propagate_booleans; + bool m_context_simplifier; + bool m_strong_context_simplifier; + bool m_refine_inj_axiom; + bool m_eliminate_bounds; + bool m_quant_elim; + bool m_nlquant_elim; + bool m_der; + bool m_simplify_bit2int; + bool m_nnf_cnf; + bool m_distribute_forall; + bool m_reduce_args; + bool m_pre_demod; + bool m_quasi_macros; + bool m_restricted_quasi_macros; + bool m_max_bv_sharing; + bool m_pre_simplifier; + +public: + preprocessor_params(): + m_lift_ite(LI_NONE), + m_ng_lift_ite(LI_NONE), + m_pull_cheap_ite_trees(false), + m_pull_nested_quantifiers(false), + m_eliminate_term_ite(false), + m_eliminate_and(true), + m_macro_finder(false), + m_solver(false), + m_propagate_values(true), + m_propagate_booleans(false), // TODO << check peformance + m_context_simplifier(false), + m_strong_context_simplifier(false), + m_refine_inj_axiom(true), + m_eliminate_bounds(false), + m_quant_elim(false), + m_nlquant_elim(false), + m_der(false), + m_simplify_bit2int(false), + m_nnf_cnf(true), + m_distribute_forall(false), + m_reduce_args(false), + m_pre_demod(false), + m_quasi_macros(false), + m_restricted_quasi_macros(false), + m_max_bv_sharing(true), + m_pre_simplifier(true) { + } + + void register_params(ini_params & p) { + nnf_params::register_params(p); + cnf_params::register_params(p); + pattern_inference_params::register_params(p); + bit_blaster_params::register_params(p); + bv_simplifier_params::register_params(p); + p.register_int_param("LIFT_ITE", 0, 2, reinterpret_cast(m_lift_ite), "ite term lifting: 0 - no lifting, 1 - conservative, 2 - full"); + p.register_int_param("NG_LIFT_ITE", 0, 2, reinterpret_cast(m_ng_lift_ite), "ite (non-ground) term lifting: 0 - no lifting, 1 - conservative, 2 - full"); + p.register_bool_param("ELIM_TERM_ITE", m_eliminate_term_ite, "eliminate term if-then-else in the preprocessor"); + p.register_bool_param("ELIM_AND", m_eliminate_and, "represent (and a b) as (not (or (not a) (not b)))"); + p.register_bool_param("MACRO_FINDER", m_macro_finder, "try to find universally quantified formulas that can be viewed as macros"); + p.register_bool_param("SOLVER", m_solver, "enable solver during preprocessing step", true); + p.register_bool_param("PROPAGATE_VALUES", m_propagate_values, "propagate values during preprocessing step"); + p.register_bool_param("PROPAGATE_BOOLEANS", m_propagate_booleans, "propagate boolean values during preprocessing step"); + p.register_bool_param("PULL_CHEAP_ITE_TREES", m_pull_cheap_ite_trees); + p.register_bool_param("PULL_NESTED_QUANTIFIERS", m_pull_nested_quantifiers, "eliminate nested quantifiers by moving nested quantified variables to the outermost quantifier, it is unnecessary if the formula is converted into CNF"); + p.register_bool_param("CONTEXT_SIMPLIFIER", m_context_simplifier, + "Simplify Boolean sub-expressions if they already appear in context", true); + p.register_bool_param("STRONG_CONTEXT_SIMPLIFIER", m_strong_context_simplifier, + "Simplify Boolean sub-expressions by using full satisfiability queries", true); + p.register_bool_param("REFINE_INJ_AXIOM", m_refine_inj_axiom); + p.register_bool_param("ELIM_BOUNDS", m_eliminate_bounds, "cheap Fourier-Motzkin"); + + p.register_bool_param("ELIM_QUANTIFIERS", m_quant_elim, + "Use quantifier elimination procedures on Boolean, Bit-vector, Arithmetic and Array variables", true); + p.register_bool_param("ELIM_NLARITH_QUANTIFIERS", m_nlquant_elim, + "Eliminate non-linear quantifiers", true); + p.register_bool_param("DER", m_der); + p.register_bool_param("BIT2INT", m_simplify_bit2int, "hoist bit2int conversions over arithmetical expressions"); + p.register_bool_param("DISTRIBUTE_FORALL", m_distribute_forall); + p.register_bool_param("REDUCE_ARGS", m_reduce_args); + p.register_bool_param("PRE_DEMODULATOR", m_pre_demod, "apply demodulators during preprocessing step"); + p.register_bool_param("QUASI_MACROS", m_quasi_macros); + p.register_bool_param("RESTRICTED_QUASI_MACROS", m_restricted_quasi_macros); + p.register_bool_param("BV_MAX_SHARING", m_max_bv_sharing); + p.register_bool_param("PRE_SIMPLIFIER", m_pre_simplifier); + } +}; + +#endif /* _PREPROCESSOR_PARAMS_H_ */ diff --git a/lib/prime_generator.cpp b/lib/prime_generator.cpp new file mode 100644 index 000000000..a82400305 --- /dev/null +++ b/lib/prime_generator.cpp @@ -0,0 +1,129 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + prime_generator.cpp + +Abstract: + + Prime generator + +Author: + + Leonardo (leonardo) 2011-12-23 + +Notes: + +--*/ +#include"prime_generator.h" + +#define PRIME_LIST_MAX_SIZE 1<<20 + +prime_generator::prime_generator() { + m_primes.push_back(2); + m_primes.push_back(3); + process_next_k_numbers(128); +} + +void prime_generator::process_next_k_numbers(uint64 k) { + svector todo; + uint64 begin = m_primes.back() + 2; + uint64 end = begin + k; + for (uint64 i = begin; i < end; i+=2) { + todo.push_back(i); + } + unsigned j = 1; + SASSERT(m_primes[j] == 3); + while (!todo.empty()) { + unsigned sz = m_primes.size(); + for (; j < sz; j++) { + uint64 p = m_primes[j]; + unsigned todo_sz = todo.size(); + unsigned k1 = 0; + unsigned k2 = 0; + for (; k1 < todo_sz; k1++) { + if (todo[k1] % p == 0) + continue; + todo[k2] = todo[k1]; + k2++; + } + todo.shrink(k2); + if (k2 == 0) + return; + if (p > (todo[k2-1] / p) + 1) { + // all numbers in todo are primes + for (unsigned k1 = 0; k1 < k2; k1++) { + m_primes.push_back(todo[k1]); + } + return; + } + } + uint64 p = m_primes.back(); + p = p*p; + unsigned todo_sz = todo.size(); + unsigned k1 = 0; + for (k1 = 0; k1 < todo_sz; k1++) { + if (todo[k1] < p) { + m_primes.push_back(todo[k1]); + } + break; + } + unsigned k2 = 0; + for (; k1 < todo_sz; k1++, k2++) { + todo[k2] = todo[k1]; + } + todo.shrink(k2); + } +} + +void prime_generator::finalize() { + m_primes.finalize(); +} + +uint64 prime_generator::operator()(unsigned idx) { + if (idx < m_primes.size()) + return m_primes[idx]; + if (idx > PRIME_LIST_MAX_SIZE) + throw prime_generator_exception("prime generator capacity exceeded"); + process_next_k_numbers(1024); + if (idx < m_primes.size()) + return m_primes[idx]; + while (idx <= m_primes.size()) + process_next_k_numbers(1024*16); + return m_primes[idx]; +} + +prime_generator g_prime_generator; + +prime_iterator::prime_iterator(prime_generator * g):m_idx(0) { + if (g == 0) { + m_generator = &g_prime_generator; + m_global = true; + } + else { + m_generator = g; + m_global = false; + } +} + +uint64 prime_iterator::next() { + unsigned idx = m_idx; + m_idx++; + if (!m_global) { + return (*m_generator)(idx); + } + else { + uint64 r; + #pragma omp critical (prime_iterator) + { + r = (*m_generator)(idx); + } + return r; + } +} + +void prime_iterator::finalize() { + g_prime_generator.finalize(); +} + diff --git a/lib/prime_generator.h b/lib/prime_generator.h new file mode 100644 index 000000000..efb675737 --- /dev/null +++ b/lib/prime_generator.h @@ -0,0 +1,53 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + prime_generator.h + +Abstract: + + Prime generator + +Author: + + Leonardo (leonardo) 2011-12-23 + +Notes: + +--*/ +#ifndef _PRIME_GENERATOR_H_ +#define _PRIME_GENERATOR_H_ + +#include"vector.h" +#include"z3_exception.h" +#include"util.h" + +class prime_generator_exception : public default_exception { +public: + prime_generator_exception(char const * msg):default_exception(msg) {} +}; + +/** + \brief Prime generator +*/ +class prime_generator { + svector m_primes; + void process_next_k_numbers(uint64 k); +public: + prime_generator(); + uint64 operator()(unsigned idx); + void finalize(); +}; + +class prime_iterator { + unsigned m_idx; + prime_generator * m_generator; + bool m_global; +public: + prime_iterator(prime_generator * g = 0); + uint64 next(); + static void finalize(); +}; + +#endif diff --git a/lib/probe.cpp b/lib/probe.cpp new file mode 100644 index 000000000..2940e93b9 --- /dev/null +++ b/lib/probe.cpp @@ -0,0 +1,449 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + probe.cpp + +Abstract: + + Evaluates/Probes a goal. + + A probe is used to build tactics (aka strategies) that + makes decisions based on the structure of a goal. + +Author: + + Leonardo de Moura (leonardo) 2011-10-13. + +Revision History: + +--*/ +#include"probe.h" +#include"arith_decl_plugin.h" +#include"bv_decl_plugin.h" +#include"goal_util.h" + +class memory_probe : public probe { +public: + virtual result operator()(goal const & g) { + return result(static_cast(memory::get_allocation_size())/static_cast(1024*1024)); + } +}; + +probe * mk_memory_probe() { + return alloc(memory_probe); +} + +class depth_probe : public probe { +public: + virtual result operator()(goal const & g) { + return result(g.depth()); + } +}; + +class size_probe : public probe { +public: + virtual result operator()(goal const & g) { + return result(g.size()); + } +}; + +class num_exprs_probe : public probe { +public: + virtual result operator()(goal const & g) { + return result(g.num_exprs()); + } +}; + +probe * mk_depth_probe() { + return alloc(depth_probe); +} + +probe * mk_size_probe() { + return alloc(size_probe); +} + +probe * mk_num_exprs_probe() { + return alloc(num_exprs_probe); +} + +class unary_probe : public probe { +protected: + probe * m_p; +public: + unary_probe(probe * p): + m_p(p) { + SASSERT(p); + p->inc_ref(); + } + + ~unary_probe() { + m_p->dec_ref(); + } + +}; + +class bin_probe : public probe { +protected: + probe * m_p1; + probe * m_p2; +public: + bin_probe(probe * p1, probe * p2): + m_p1(p1), + m_p2(p2) { + SASSERT(p1); + SASSERT(p2); + p1->inc_ref(); + p2->inc_ref(); + } + + ~bin_probe() { + m_p1->dec_ref(); + m_p2->dec_ref(); + } +}; + +class not_probe : public unary_probe { +public: + not_probe(probe * p):unary_probe(p) {} + virtual result operator()(goal const & g) { + return result(!m_p->operator()(g).is_true()); + } +}; + +class and_probe : public bin_probe { +public: + and_probe(probe * p1, probe * p2):bin_probe(p1, p2) {} + virtual result operator()(goal const & g) { + return result(m_p1->operator()(g).is_true() && m_p2->operator()(g).is_true()); + } +}; + +class or_probe : public bin_probe { +public: + or_probe(probe * p1, probe * p2):bin_probe(p1, p2) {} + virtual result operator()(goal const & g) { + return result(m_p1->operator()(g).is_true() || m_p2->operator()(g).is_true()); + } +}; + +class eq_probe : public bin_probe { +public: + eq_probe(probe * p1, probe * p2):bin_probe(p1, p2) {} + virtual result operator()(goal const & g) { + return result(m_p1->operator()(g).get_value() == m_p2->operator()(g).get_value()); + } +}; + +class le_probe : public bin_probe { +public: + le_probe(probe * p1, probe * p2):bin_probe(p1, p2) {} + virtual result operator()(goal const & g) { + return result(m_p1->operator()(g).get_value() <= m_p2->operator()(g).get_value()); + } +}; + +class add_probe : public bin_probe { +public: + add_probe(probe * p1, probe * p2):bin_probe(p1, p2) {} + virtual result operator()(goal const & g) { + return result(m_p1->operator()(g).get_value() + m_p2->operator()(g).get_value()); + } +}; + +class sub_probe : public bin_probe { +public: + sub_probe(probe * p1, probe * p2):bin_probe(p1, p2) {} + virtual result operator()(goal const & g) { + return result(m_p1->operator()(g).get_value() - m_p2->operator()(g).get_value()); + } +}; + +class mul_probe : public bin_probe { +public: + mul_probe(probe * p1, probe * p2):bin_probe(p1, p2) {} + virtual result operator()(goal const & g) { + return result(m_p1->operator()(g).get_value() * m_p2->operator()(g).get_value()); + } +}; + +class div_probe : public bin_probe { +public: + div_probe(probe * p1, probe * p2):bin_probe(p1, p2) {} + virtual result operator()(goal const & g) { + return result(m_p1->operator()(g).get_value() / m_p2->operator()(g).get_value()); + } +}; + +class const_probe : public probe { + double m_val; +public: + const_probe(double v):m_val(v) {} + + virtual result operator()(goal const & g) { + return result(m_val); + } +}; + +probe * mk_const_probe(double v) { + return alloc(const_probe, v); +} + +probe * mk_not(probe * p) { + return alloc(not_probe, p); +} + +probe * mk_and(probe * p1, probe * p2) { + return alloc(and_probe, p1, p2); +} + +probe * mk_or(probe * p1, probe * p2) { + return alloc(or_probe, p1, p2); +} + +probe * mk_implies(probe * p1, probe * p2) { + return mk_or(mk_not(p1), p2); +} + +probe * mk_eq(probe * p1, probe * p2) { + return alloc(eq_probe, p1, p2); +} + +probe * mk_neq(probe * p1, probe * p2) { + return mk_not(mk_eq(p1, p2)); +} + +probe * mk_le(probe * p1, probe * p2) { + return alloc(le_probe, p1, p2); +} + +probe * mk_ge(probe * p1, probe * p2) { + return mk_le(p2, p1); +} + +probe * mk_lt(probe * p1, probe * p2) { + return mk_not(mk_ge(p1, p2)); +} + +probe * mk_gt(probe * p1, probe * p2) { + return mk_lt(p2, p1); +} + +probe * mk_add(probe * p1, probe * p2) { + return alloc(add_probe, p1, p2); +} + +probe * mk_mul(probe * p1, probe * p2) { + return alloc(mul_probe, p1, p2); +} + +probe * mk_sub(probe * p1, probe * p2) { + return alloc(sub_probe, p1, p2); +} + +probe * mk_div(probe * p1, probe * p2) { + return alloc(div_probe, p1, p2); +} + +struct is_non_propositional_predicate { + struct found {}; + ast_manager & m; + + is_non_propositional_predicate(ast_manager & _m):m(_m) {} + void operator()(var *) { throw found(); } + void operator()(quantifier *) { throw found(); } + void operator()(app * n) { + if (!m.is_bool(n)) + throw found(); + + family_id fid = n->get_family_id(); + if (fid == m.get_basic_family_id()) + return; + + if (is_uninterp_const(n)) + return; + + throw found(); + } +}; + +struct is_non_qfbv_predicate { + struct found {}; + ast_manager & m; + bv_util u; + + is_non_qfbv_predicate(ast_manager & _m):m(_m), u(m) {} + + void operator()(var *) { throw found(); } + + void operator()(quantifier *) { throw found(); } + + void operator()(app * n) { + if (!m.is_bool(n) && !u.is_bv(n)) + throw found(); + family_id fid = n->get_family_id(); + if (fid == m.get_basic_family_id()) + return; + if (fid == u.get_family_id()) + return; + if (is_uninterp_const(n)) + return; + + throw found(); + } +}; + +class is_propositional_probe : public probe { +public: + virtual result operator()(goal const & g) { + return !test(g); + } +}; + +class is_qfbv_probe : public probe { +public: + virtual result operator()(goal const & g) { + return !test(g); + } +}; + +probe * mk_is_propositional_probe() { + return alloc(is_propositional_probe); +} + +probe * mk_is_qfbv_probe() { + return alloc(is_qfbv_probe); +} + +class num_consts_probe : public probe { + bool m_bool; // If true, track only boolean constants. Otherwise, track only non boolean constants. + char const * m_family; // (Ignored if m_bool == true), if != 0 and m_bool == true, then track only constants of the given family. + struct proc { + ast_manager & m; + bool m_bool; + family_id m_fid; + unsigned m_counter; + proc(ast_manager & _m, bool b, char const * family):m(_m), m_bool(b), m_counter(0) { + if (family != 0) + m_fid = m.get_family_id(family); + else + m_fid = null_family_id; + } + void operator()(quantifier *) {} + void operator()(var *) {} + void operator()(app * n) { + if (n->get_num_args() == 0 && !m.is_value(n)) { + if (m_bool) { + if (m.is_bool(n)) + m_counter++; + } + else { + if (m_fid == null_family_id) { + if (!m.is_bool(n)) + m_counter++; + } + else { + if (m.get_sort(n)->get_family_id() == m_fid) + m_counter++; + } + } + } + } + }; + +public: + num_consts_probe(bool b, char const * f): + m_bool(b), m_family(f) { + } + virtual result operator()(goal const & g) { + proc p(g.m(), m_bool, m_family); + unsigned sz = g.size(); + expr_fast_mark1 visited; + for (unsigned i = 0; i < sz; i++) { + for_each_expr_core(p, visited, g.form(i)); + } + return result(p.m_counter); + } +}; + +probe * mk_num_consts_probe() { + return alloc(num_consts_probe, false, 0); +} + +probe * mk_num_bool_consts_probe() { + return alloc(num_consts_probe, true, 0); +} + +probe * mk_num_arith_consts_probe() { + return alloc(num_consts_probe, false, "arith"); +} + +probe * mk_num_bv_consts_probe() { + return alloc(num_consts_probe, false, "bv"); +} + +class produce_proofs_probe : public probe { +public: + virtual result operator()(goal const & g) { + return g.proofs_enabled(); + } +}; + +class produce_models_probe : public probe { +public: + virtual result operator()(goal const & g) { + return g.models_enabled(); + } +}; + +class produce_unsat_cores_probe : public probe { +public: + virtual result operator()(goal const & g) { + return g.unsat_core_enabled(); + } +}; + +probe * mk_produce_proofs_probe() { + return alloc(produce_proofs_probe); +} + +probe * mk_produce_models_probe() { + return alloc(produce_models_probe); +} + +probe * mk_produce_unsat_cores_probe() { + return alloc(produce_unsat_cores_probe); +} + +struct has_pattern_probe : public probe { + struct found {}; + + struct proc { + void operator()(var * n) {} + void operator()(app * n) {} + void operator()(quantifier * n) { + if (n->get_num_patterns() > 0 || n->get_num_no_patterns() > 0) + throw found(); + } + }; +public: + virtual result operator()(goal const & g) { + try { + expr_fast_mark1 visited; + proc p; + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + quick_for_each_expr(p, visited, g.form(i)); + } + return false; + } + catch (found) { + return true; + } + } +}; + +probe * mk_has_pattern_probe() { + return alloc(has_pattern_probe); +} diff --git a/lib/probe.h b/lib/probe.h new file mode 100644 index 000000000..269480b15 --- /dev/null +++ b/lib/probe.h @@ -0,0 +1,91 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + probe.h + +Abstract: + + Evaluates/Probes a goal. + + A probe is used to build tactics (aka strategies) that + makes decisions based on the structure of a goal. + + The current implementation is very simple. + +Author: + + Leonardo de Moura (leonardo) 2011-10-13. + +Revision History: + +--*/ +#ifndef _PROBE_H_ +#define _PROBE_H_ + +#include"goal.h" + +class probe { +public: + class result { + double m_value; + public: + result(double v = 0.0):m_value(v) {} + result(unsigned v):m_value(static_cast(v)) {} + result(int v):m_value(static_cast(v)) {} + result(bool b):m_value(b ? 1.0 : 0.0) {} + + bool is_true() const { return m_value != 0.0; } + double get_value() const { return m_value; } + }; + +private: + unsigned m_ref_count; + +public: + probe():m_ref_count(0) {} + virtual ~probe() {} + + void inc_ref() { ++m_ref_count; } + void dec_ref() { SASSERT(m_ref_count > 0); --m_ref_count; if (m_ref_count == 0) dealloc(this); } + + virtual result operator()(goal const & g) = 0; +}; + +typedef ref probe_ref; + +probe * mk_memory_probe(); +probe * mk_depth_probe(); +probe * mk_size_probe(); +probe * mk_num_exprs_probe(); +probe * mk_const_probe(double val); +probe * mk_num_consts_probe(); +probe * mk_num_bool_consts_probe(); +probe * mk_num_arith_consts_probe(); +probe * mk_num_bv_consts_probe(); +probe * mk_produce_proofs_probe(); +probe * mk_produce_models_probe(); +probe * mk_produce_unsat_cores_probe(); +probe * mk_has_pattern_probe(); + +// Some basic combinators for probes +probe * mk_not(probe * p1); +probe * mk_and(probe * p1, probe * p2); +probe * mk_or(probe * p1, probe * p2); +probe * mk_implies(probe * p1, probe * p2); +probe * mk_eq(probe * p1, probe * p2); +probe * mk_neq(probe * p1, probe * p2); +probe * mk_le(probe * p1, probe * p2); +probe * mk_lt(probe * p1, probe * p2); +probe * mk_ge(probe * p1, probe * p2); +probe * mk_gt(probe * p1, probe * p2); +probe * mk_add(probe * p1, probe * p2); +probe * mk_sub(probe * p1, probe * p2); +probe * mk_mul(probe * p1, probe * p2); +probe * mk_div(probe * p1, probe * p2); + +probe * mk_is_propositional_probe(); +probe * mk_is_qfbv_probe(); + +#endif diff --git a/lib/probe_arith.cpp b/lib/probe_arith.cpp new file mode 100644 index 000000000..dcb64e6d3 --- /dev/null +++ b/lib/probe_arith.cpp @@ -0,0 +1,421 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + probe_arith.cpp + +Abstract: + + Some probes for arithmetic problems. + +Author: + + Leonardo de Moura (leonardo) 2012-03-01. + +Revision History: + +--*/ +#include"probe.h" +#include"expr2polynomial.h" +#include"for_each_expr.h" +#include"arith_decl_plugin.h" +#include"goal_util.h" + +class arith_degree_probe : public probe { + struct proc { + ast_manager & m; + unsynch_mpq_manager m_qm; + polynomial::manager m_pm; + default_expr2polynomial m_expr2poly; + arith_util m_util; + unsigned m_max_degree; + unsigned long long m_acc_degree; + unsigned m_counter; + + proc(ast_manager & _m):m(_m), m_pm(m_qm), m_expr2poly(m, m_pm), m_util(m) { + m_max_degree = 0; + m_acc_degree = 0; + m_counter = 0; + } + + void updt_degree(polynomial_ref const & p) { + unsigned deg = m_pm.total_degree(p); + if (deg > m_max_degree) + m_max_degree = deg; + m_acc_degree += deg; + m_counter++; + } + + void process(app * n) { + expr * lhs = n->get_arg(0); + expr * rhs = n->get_arg(1); + polynomial_ref p1(m_pm); + polynomial_ref p2(m_pm); + scoped_mpz d1(m_qm); + scoped_mpz d2(m_qm); + m_expr2poly.to_polynomial(lhs, p1, d1); + m_expr2poly.to_polynomial(rhs, p2, d2); + updt_degree(p1); + updt_degree(p2); + } + + void operator()(var * n) {} + void operator()(quantifier * n) {} + void operator()(app * n) { + if (m_util.is_le(n) || m_util.is_lt(n) || m_util.is_gt(n) || m_util.is_ge(n)) + process(n); + if (m.is_eq(n) && m_util.is_int_real(n->get_arg(0))) + process(n); + } + }; + + bool m_avg; +public: + arith_degree_probe(bool avg):m_avg(avg) {} + + virtual result operator()(goal const & g) { + proc p(g.m()); + for_each_expr_at(p, g); + if (m_avg) + return p.m_counter == 0 ? 0.0 : static_cast(p.m_acc_degree)/static_cast(p.m_counter); + else + return p.m_max_degree; + } +}; + +class arith_bw_probe : public probe { + struct proc { + ast_manager & m; + arith_util m_util; + unsigned m_max_bw; + unsigned long long m_acc_bw; + unsigned m_counter; + + proc(ast_manager & _m):m(_m), m_util(m) { + m_max_bw = 0; + m_acc_bw = 0; + m_counter = 0; + } + + void operator()(var * n) {} + void operator()(quantifier * n) {} + void operator()(app * n) { + rational val; + if (m_util.is_numeral(n, val)) { + unsigned bw = val.bitsize(); + if (bw > m_max_bw) + m_max_bw = bw; + m_acc_bw += bw; + m_counter++; + } + } + + }; + + bool m_avg; +public: + arith_bw_probe(bool avg):m_avg(avg) {} + + virtual result operator()(goal const & g) { + proc p(g.m()); + for_each_expr_at(p, g); + if (m_avg) + return p.m_counter == 0 ? 0.0 : static_cast(p.m_acc_bw)/static_cast(p.m_counter); + else + return p.m_max_bw; + } +}; + +probe * mk_arith_avg_degree_probe() { + return alloc(arith_degree_probe, true); +} + +probe * mk_arith_max_degree_probe() { + return alloc(arith_degree_probe, false); +} + +probe * mk_arith_avg_bw_probe() { + return alloc(arith_bw_probe, true); +} + +probe * mk_arith_max_bw_probe() { + return alloc(arith_bw_probe, false); +} + +struct is_non_qflira_functor { + struct found {}; + ast_manager & m; + arith_util u; + bool m_int; + bool m_real; + + is_non_qflira_functor(ast_manager & _m, bool _int, bool _real):m(_m), u(m), m_int(_int), m_real(_real) {} + + void operator()(var *) { throw found(); } + + void operator()(quantifier *) { throw found(); } + + bool compatible_sort(app * n) const { + if (m.is_bool(n)) + return true; + if (m_int && u.is_int(n)) + return true; + if (m_real && u.is_real(n)) + return true; + return false; + } + + void operator()(app * n) { + if (!compatible_sort(n)) + throw found(); + family_id fid = n->get_family_id(); + if (fid == m.get_basic_family_id()) + return; + if (fid == u.get_family_id()) { + switch (n->get_decl_kind()) { + case OP_LE: case OP_GE: case OP_LT: case OP_GT: + case OP_ADD: case OP_NUM: + return; + case OP_MUL: + if (n->get_num_args() != 2) + throw found(); + if (!u.is_numeral(n->get_arg(0))) + throw found(); + return; + case OP_TO_REAL: + if (!m_real) + throw found(); + break; + default: + throw found(); + } + return; + } + if (is_uninterp_const(n)) + return; + throw found(); + } +}; + +static bool is_qflia(goal const & g) { + is_non_qflira_functor p(g.m(), true, false); + return !test(g, p); +} + +class is_qflia_probe : public probe { +public: + virtual result operator()(goal const & g) { + return is_qflia(g); + } +}; + +static bool is_qflra(goal const & g) { + is_non_qflira_functor p(g.m(), false, true); + return !test(g, p); +} + +class is_qflra_probe : public probe { +public: + virtual result operator()(goal const & g) { + return is_qflra(g); + } +}; + +static bool is_qflira(goal const & g) { + is_non_qflira_functor p(g.m(), true, true); + return !test(g, p); +} + +class is_qflira_probe : public probe { +public: + virtual result operator()(goal const & g) { + return is_qflira(g); + } +}; + +static bool is_lp(goal const & g) { + ast_manager & m = g.m(); + arith_util u(m); + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + expr * f = g.form(i); + bool sign = false; + while (m.is_not(f, f)) + sign = !sign; + if (m.is_eq(f) && !sign) { + if (m.get_sort(to_app(f)->get_arg(0))->get_family_id() != u.get_family_id()) + return false; + continue; + } + if (u.is_le(f) || u.is_ge(f) || u.is_lt(f) || u.is_gt(f)) + continue; + return false; + } + return true; +} + +static bool is_ilp(goal const & g) { + if (!is_qflia(g)) + return false; + if (has_term_ite(g)) + return false; + return is_lp(g); +} + +static bool is_mip(goal const & g) { + if (!is_qflira(g)) + return false; + if (has_term_ite(g)) + return false; + return is_lp(g); +} + +class is_ilp_probe : public probe { +public: + virtual result operator()(goal const & g) { + return is_ilp(g); + } +}; + +class is_mip_probe : public probe { +public: + virtual result operator()(goal const & g) { + return is_mip(g); + } +}; + +probe * mk_is_qflia_probe() { + return alloc(is_qflia_probe); +} + +probe * mk_is_qflra_probe() { + return alloc(is_qflra_probe); +} + +probe * mk_is_qflira_probe() { + return alloc(is_qflira_probe); +} + +probe * mk_is_ilp_probe() { + return alloc(is_ilp_probe); +} + +probe * mk_is_mip_probe() { + return alloc(is_mip_probe); +} + + +struct is_non_nira_functor { + struct found {}; + ast_manager & m; + arith_util u; + bool m_int; + bool m_real; + bool m_quant; + + is_non_nira_functor(ast_manager & _m, bool _int, bool _real, bool _quant):m(_m), u(m), m_int(_int), m_real(_real), m_quant(_quant) {} + + void operator()(var * x) { + if (!m_quant) + throw found(); + sort * s = x->get_sort(); + if (m_int && u.is_int(s)) + return; + if (m_real && u.is_real(s)) + return; + throw found(); + } + + void operator()(quantifier *) { + if (!m_quant) + throw found(); + } + + bool compatible_sort(app * n) const { + if (m.is_bool(n)) + return true; + if (m_int && u.is_int(n)) + return true; + if (m_real && u.is_real(n)) + return true; + return false; + } + + void operator()(app * n) { + if (!compatible_sort(n)) + throw found(); + family_id fid = n->get_family_id(); + if (fid == m.get_basic_family_id()) + return; + if (fid == u.get_family_id()) + return; + if (is_uninterp_const(n)) + return; + throw found(); + } +}; + +static bool is_qfnia(goal const & g) { + is_non_nira_functor p(g.m(), true, false, false); + return !test(g, p); +} + +static bool is_qfnra(goal const & g) { + is_non_nira_functor p(g.m(), false, true, false); + return !test(g, p); +} + +static bool is_nia(goal const & g) { + is_non_nira_functor p(g.m(), true, false, true); + return !test(g, p); +} + +static bool is_nra(goal const & g) { + is_non_nira_functor p(g.m(), false, true, true); + return !test(g, p); +} + +class is_qfnia_probe : public probe { +public: + virtual result operator()(goal const & g) { + return is_qfnia(g); + } +}; + +class is_qfnra_probe : public probe { +public: + virtual result operator()(goal const & g) { + return is_qfnra(g); + } +}; + +class is_nia_probe : public probe { +public: + virtual result operator()(goal const & g) { + return is_nia(g); + } +}; + +class is_nra_probe : public probe { +public: + virtual result operator()(goal const & g) { + return is_nra(g); + } +}; + +probe * mk_is_qfnia_probe() { + return alloc(is_qfnia_probe); +} + +probe * mk_is_qfnra_probe() { + return alloc(is_qfnra_probe); +} + +probe * mk_is_nia_probe() { + return alloc(is_nia_probe); +} + +probe * mk_is_nra_probe() { + return alloc(is_nra_probe); +} diff --git a/lib/probe_arith.h b/lib/probe_arith.h new file mode 100644 index 000000000..3824027db --- /dev/null +++ b/lib/probe_arith.h @@ -0,0 +1,39 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + probe_arith.h + +Abstract: + + Some probes for arithmetic problems. + +Author: + + Leonardo de Moura (leonardo) 2012-03-01. + +Revision History: + +--*/ +#ifndef _PROBE_ARITH_H_ +#define _PROBE_ARITH_H_ + +class probe; +probe * mk_arith_avg_bw_probe(); +probe * mk_arith_max_bw_probe(); +probe * mk_arith_avg_degree_probe(); +probe * mk_arith_max_degree_probe(); + +probe * mk_is_qflia_probe(); +probe * mk_is_qflra_probe(); +probe * mk_is_qflira_probe(); +probe * mk_is_ilp_probe(); +probe * mk_is_mip_probe(); + +probe * mk_is_qfnia_probe(); +probe * mk_is_qfnra_probe(); +probe * mk_is_nia_probe(); +probe * mk_is_nra_probe(); + +#endif diff --git a/lib/progress_callback.h b/lib/progress_callback.h new file mode 100644 index 000000000..6397d7bc4 --- /dev/null +++ b/lib/progress_callback.h @@ -0,0 +1,33 @@ +/*++ +Copyright (c) 2008 Microsoft Corporation + +Module Name: + + progress_callback.h + +Abstract: + + Virtual callback for reporting progress. + +Author: + + Michal Moskal (micmo) 2009-02-17. + +Revision History: + +--*/ +#ifndef _PROGRESS_CALLBACK_H_ +#define _PROGRESS_CALLBACK_H_ + +class progress_callback { +public: + virtual ~progress_callback() {} + + // Called approx. every m_progress_sampling_freq miliseconds + virtual void slow_progress_sample() { } + + // Called on every check for reqsource limit exceeded (mach more frequent). + virtual void fast_progress_sample() { } +}; + +#endif diff --git a/lib/proof_checker.cpp b/lib/proof_checker.cpp new file mode 100644 index 000000000..9e2a3f260 --- /dev/null +++ b/lib/proof_checker.cpp @@ -0,0 +1,1335 @@ +#include "proof_checker.h" +#include "ast_ll_pp.h" +#include "ast_pp.h" +#include "spc_decl_plugin.h" +#include "ast_smt_pp.h" +#include "arith_decl_plugin.h" +#include "front_end_params.h" +#include "th_rewriter.h" + +#define IS_EQUIV(_e_) (m_manager.is_eq(_e_) || m_manager.is_iff(_e_)) + +#define SAME_OP(_d1_, _d2_) ((_d1_ == _d2_) || (IS_EQUIV(_d1_) && IS_EQUIV(_d2_))) + +proof_checker::hyp_decl_plugin::hyp_decl_plugin() : + m_cons(0), + m_atom(0), + m_nil(0), + m_cell(0) { +} + +void proof_checker::hyp_decl_plugin::finalize() { + m_manager->dec_ref(m_cons); + m_manager->dec_ref(m_atom); + m_manager->dec_ref(m_nil); + m_manager->dec_ref(m_cell); +} + +void proof_checker::hyp_decl_plugin::set_manager(ast_manager* m, family_id id) { + decl_plugin::set_manager(m,id); + m_cell = m->mk_sort(symbol("cell"), sort_info(id, CELL_SORT)); + m_cons = m->mk_func_decl(symbol("cons"), m_cell, m_cell, m_cell, func_decl_info(id, OP_CONS)); + m_atom = m->mk_func_decl(symbol("atom"), m->mk_bool_sort(), m_cell, func_decl_info(id, OP_ATOM)); + m_nil = m->mk_const_decl(symbol("nil"), m_cell, func_decl_info(id, OP_NIL)); + m->inc_ref(m_cell); + m->inc_ref(m_cons); + m->inc_ref(m_atom); + m->inc_ref(m_nil); +} + +sort * proof_checker::hyp_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const* parameters) { + SASSERT(k == CELL_SORT); + return m_cell; +} + +func_decl * proof_checker::hyp_decl_plugin::mk_func_decl(decl_kind k) { + switch(k) { + case OP_CONS: return m_cons; + case OP_ATOM: return m_atom; + case OP_NIL: return m_nil; + default: + UNREACHABLE(); + return 0; + } +} + +func_decl * proof_checker::hyp_decl_plugin::mk_func_decl( + decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + return mk_func_decl(k); +} + +func_decl * proof_checker::hyp_decl_plugin::mk_func_decl( + decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned num_args, expr * const * args, sort * range) { + return mk_func_decl(k); +} + +void proof_checker::hyp_decl_plugin::get_op_names(svector & op_names, symbol const & logic) { + if (logic == symbol::null) { + op_names.push_back(builtin_name("cons", OP_CONS)); + op_names.push_back(builtin_name("atom", OP_ATOM)); + op_names.push_back(builtin_name("nil", OP_NIL)); + } +} + +void proof_checker::hyp_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { + if (logic == symbol::null) { + sort_names.push_back(builtin_name("cell", CELL_SORT)); + } +} + +proof_checker::proof_checker(ast_manager& m) : m_manager(m), m_todo(m), m_marked(), m_pinned(m), m_nil(m), + m_dump_lemmas(false), m_logic("AUFLIA"), m_proof_lemma_id(0) { + symbol fam_name("proof_hypothesis"); + if (!m.has_plugin(fam_name)) { + m.register_plugin(fam_name, alloc(hyp_decl_plugin)); + } + m_hyp_fid = m.get_family_id(fam_name); + m_spc_fid = m.get_family_id("spc"); + m_nil = m_manager.mk_const(m_hyp_fid, OP_NIL); +} + +bool proof_checker::check(proof* p, expr_ref_vector& side_conditions) { + proof_ref curr(m_manager); + m_todo.push_back(p); + + bool result = true; + while (result && !m_todo.empty()) { + curr = m_todo.back(); + m_todo.pop_back(); + result = check1(curr.get(), side_conditions); + if (!result) { + IF_VERBOSE(0, ast_ll_pp(verbose_stream() << "Proof check failed\n", m_manager, curr.get());); + UNREACHABLE(); + } + } + + m_hypotheses.reset(); + m_pinned.reset(); + m_todo.reset(); + m_marked.reset(); + + return result; +} + +bool proof_checker::check1(proof* p, expr_ref_vector& side_conditions) { + if (p->get_family_id() == m_manager.get_basic_family_id()) { + return check1_basic(p, side_conditions); + } + if (p->get_family_id() == m_spc_fid) { + return check1_spc(p, side_conditions); + } + return false; +} + +bool proof_checker::check1_spc(proof* p, expr_ref_vector& side_conditions) { + decl_kind k = p->get_decl_kind(); + bool is_univ = false; + expr_ref fact(m_manager), fml(m_manager); + expr_ref body(m_manager), fml1(m_manager), fml2(m_manager); + sort_ref_vector sorts(m_manager); + proof_ref p1(m_manager), p2(m_manager); + proof_ref_vector proofs(m_manager); + + if (match_proof(p, proofs)) { + for (unsigned i = 0; i < proofs.size(); ++i) { + add_premise(proofs[i].get()); + } + } + switch(k) { + case PR_DEMODULATION: { + if (match_proof(p, p1) && + match_fact(p, fact) && + match_fact(p1.get(), fml) && + match_quantifier(fml.get(), is_univ, sorts, body) && + is_univ) { + // TBD: check that fml is an instance of body. + return true; + } + return false; + } + case PR_SPC_REWRITE: + case PR_SUPERPOSITION: + case PR_EQUALITY_RESOLUTION: + case PR_SPC_RESOLUTION: + case PR_FACTORING: + case PR_SPC_DER: { + if (match_fact(p, fact)) { + expr_ref_vector rewrite_eq(m_manager); + rewrite_eq.push_back(fact.get()); + for (unsigned i = 0; i < proofs.size(); ++i) { + if (match_fact(proofs[i].get(), fml)) { + rewrite_eq.push_back(m_manager.mk_not(fml.get())); + } + } + expr_ref rewrite_cond(m_manager); + rewrite_cond = m_manager.mk_or(rewrite_eq.size(), rewrite_eq.c_ptr()); + side_conditions.push_back(rewrite_cond.get()); + return true; + } + return false; + } + default: + UNREACHABLE(); + } + return false; +} + +bool proof_checker::check1_basic(proof* p, expr_ref_vector& side_conditions) { + decl_kind k = p->get_decl_kind(); + expr_ref fml0(m_manager), fml1(m_manager), fml2(m_manager), fml(m_manager); + expr_ref t1(m_manager), t2(m_manager); + expr_ref s1(m_manager), s2(m_manager); + expr_ref u1(m_manager), u2(m_manager); + expr_ref fact(m_manager), body1(m_manager), body2(m_manager); + expr_ref l1(m_manager), l2(m_manager), r1(m_manager), r2(m_manager); + func_decl_ref d1(m_manager), d2(m_manager), d3(m_manager); + proof_ref p0(m_manager), p1(m_manager), p2(m_manager); + proof_ref_vector proofs(m_manager); + func_decl_ref f1(m_manager), f2(m_manager); + expr_ref_vector terms1(m_manager), terms2(m_manager), terms(m_manager); + sort_ref_vector decls1(m_manager), decls2(m_manager); + + if (match_proof(p, proofs)) { + for (unsigned i = 0; i < proofs.size(); ++i) { + add_premise(proofs.get(i)); + } + } + + switch(k) { + case PR_UNDEF: + return true; + case PR_ASSERTED: + return true; + case PR_GOAL: + return true; + case PR_MODUS_PONENS: { + if (match_fact(p, fact) && + match_proof(p, p0, p1) && + match_fact(p0.get(), fml0) && + match_fact(p1.get(), fml1) && + (match_implies(fml1.get(), t1, t2) || match_iff(fml1.get(), t1, t2)) && + (fml0.get() == t1.get()) && + (fact.get() == t2.get())) { + return true; + } + UNREACHABLE(); + return false; + } + case PR_REFLEXIVITY: { + if (match_fact(p, fact) && + match_proof(p) && + (match_equiv(fact, t1, t2) || match_oeq(fact, t1, t2)) && + (t1.get() == t2.get())) { + return true; + } + UNREACHABLE(); + return false; + } + case PR_SYMMETRY: { + if (match_fact(p, fact) && + match_proof(p, p1) && + match_fact(p1.get(), fml) && + match_binary(fact.get(), d1, l1, r1) && + match_binary(fml.get(), d2, l2, r2) && + SAME_OP(d1.get(), d2.get()) && + l1.get() == r2.get() && + r1.get() == l2.get()) { + // TBD d1, d2 is a symmetric predicate + return true; + } + UNREACHABLE(); + return false; + } + case PR_TRANSITIVITY: { + if (match_fact(p, fact) && + match_proof(p, p1, p2) && + match_fact(p1.get(), fml1) && + match_fact(p2.get(), fml2) && + match_binary(fact.get(), d1, t1, t2) && + match_binary(fml1.get(), d2, s1, s2) && + match_binary(fml2.get(), d3, u1, u2) && + d1.get() == d2.get() && + d2.get() == d3.get() && + t1.get() == s1.get() && + s2.get() == u1.get() && + u2.get() == t2.get()) { + // TBD d1 is some transitive predicate. + return true; + } + UNREACHABLE(); + return false; + } + case PR_TRANSITIVITY_STAR: { + if (match_fact(p, fact) && + match_binary(fact.get(), d1, t1, t2)) { + u_map vertices; + // TBD check that d1 is transitive, symmetric. + for (unsigned i = 0; i < proofs.size(); ++i) { + if (match_fact(proofs[i].get(), fml) && + match_binary(fml.get(), d2, s1, s2) && + d1.get() == d2.get()) { + unsigned id1 = s1->get_id(); + unsigned id2 = s2->get_id(); +#define INSERT(_id) if (vertices.contains(_id)) vertices.remove(_id); else vertices.insert(_id, true); + INSERT(id1); + INSERT(id2); + } + else { + UNREACHABLE(); + return false; + } + } + return + vertices.size() == 2 && + vertices.contains(t1->get_id()) && + vertices.contains(t2->get_id()); + } + UNREACHABLE(); + return false; + } + case PR_MONOTONICITY: { + TRACE("proof_checker", tout << mk_bounded_pp(p, m_manager, 3) << "\n";); + if (match_fact(p, fact) && + match_binary(fact.get(), d1, t1, t2) && + match_app(t1.get(), f1, terms1) && + match_app(t2.get(), f2, terms2) && + f1.get() == f2.get() && + terms1.size() == terms2.size()) { + // TBD: d1 is monotone. + for (unsigned i = 0; i < terms1.size(); ++i) { + expr* term1 = terms1[i].get(); + expr* term2 = terms2[i].get(); + if (term1 != term2) { + bool found = false; + for(unsigned j = 0; j < proofs.size() && !found; ++j) { + found = + match_fact(proofs[j].get(), fml) && + match_binary(fml.get(), d2, s1, s2) && + SAME_OP(d1.get(), d2.get()) && + s1.get() == term1 && + s2.get() == term2; + } + if (!found) { + UNREACHABLE(); + return false; + } + } + } + return true; + } + UNREACHABLE(); + return false; + } + case PR_QUANT_INTRO: { + if (match_proof(p, p1) && + match_fact(p, fact) && + match_fact(p1.get(), fml) && + (match_iff(fact.get(), t1, t2) || match_oeq(fact.get(), t1, t2)) && + (match_iff(fml.get(), s1, s2) || match_oeq(fml.get(), s1, s2)) && + m_manager.is_oeq(fact.get()) == m_manager.is_oeq(fml.get()) && + is_quantifier(t1.get()) && + is_quantifier(t2.get()) && + to_quantifier(t1.get())->get_expr() == s1.get() && + to_quantifier(t2.get())->get_expr() == s2.get() && + to_quantifier(t1.get())->get_num_decls() == to_quantifier(t2.get())->get_num_decls() && + to_quantifier(t1.get())->is_forall() == to_quantifier(t2.get())->is_forall()) { + quantifier* q1 = to_quantifier(t1.get()); + quantifier* q2 = to_quantifier(t2.get()); + for (unsigned i = 0; i < q1->get_num_decls(); ++i) { + if (q1->get_decl_sort(i) != q2->get_decl_sort(i)) { + // term is not well-typed. + UNREACHABLE(); + return false; + } + } + return true; + } + UNREACHABLE(); + return false; + } + case PR_DISTRIBUTIVITY: { + if (match_fact(p, fact) && + match_proof(p) && + match_equiv(fact.get(), t1, t2)) { + side_conditions.push_back(fact.get()); + return true; + } + UNREACHABLE(); + return false; + } + case PR_AND_ELIM: { + expr_ref_vector terms(m_manager); + if (match_proof(p, p1) && + match_fact(p, fact) && + match_fact(p1.get(), fml) && + match_and(fml.get(), terms)) { + for (unsigned i = 0; i < terms.size(); ++i) { + if (terms[i].get() == fact.get()) { + return true; + } + } + } + UNREACHABLE(); + return false; + } + case PR_NOT_OR_ELIM: { + expr_ref_vector terms(m_manager); + if (match_proof(p, p1) && + match_fact(p, fact) && + match_fact(p1.get(), fml) && + match_not(fml.get(), fml1) && + match_or(fml1.get(), terms)) { + for (unsigned i = 0; i < terms.size(); ++i) { + if (match_negated(terms[i].get(), fact.get())) { + return true; + } + } + } + UNREACHABLE(); + return false; + } + case PR_REWRITE: { + if (match_fact(p, fact) && + match_proof(p) && + match_equiv(fact.get(), t1, t2)) { + side_conditions.push_back(fact.get()); + return true; + } + IF_VERBOSE(0, verbose_stream() << "Expected proof of equality:\n" << mk_bounded_pp(p, m_manager);); + return false; + } + case PR_REWRITE_STAR: { + if (match_fact(p, fact) && + match_equiv(fact.get(), t1, t2)) { + expr_ref_vector rewrite_eq(m_manager); + rewrite_eq.push_back(fact.get()); + for (unsigned i = 0; i < proofs.size(); ++i) { + if (match_fact(proofs[i].get(), fml)) { + rewrite_eq.push_back(m_manager.mk_not(fml.get())); + } + } + expr_ref rewrite_cond(m_manager); + rewrite_cond = m_manager.mk_or(rewrite_eq.size(), rewrite_eq.c_ptr()); + side_conditions.push_back(rewrite_cond.get()); + return true; + } + IF_VERBOSE(0, verbose_stream() << "Expected proof of equality:\n" << mk_bounded_pp(p, m_manager);); + return false; + } + case PR_PULL_QUANT: { + if (match_proof(p) && + match_fact(p, fact) && + match_iff(fact.get(), t1, t2) && + is_quantifier(t2.get())) { + // TBD: check the enchilada. + return true; + } + IF_VERBOSE(0, verbose_stream() << "Expected proof of equivalence with a quantifier:\n" << mk_bounded_pp(p, m_manager);); + return false; + } + case PR_PULL_QUANT_STAR: { + if (match_proof(p) && + match_fact(p, fact) && + match_iff(fact.get(), t1, t2)) { + // TBD: check the enchilada. + return true; + } + IF_VERBOSE(0, verbose_stream() << "Expected proof of equivalence:\n" << mk_bounded_pp(p, m_manager);); + return false; + } + case PR_PUSH_QUANT: { + if (match_proof(p) && + match_fact(p, fact) && + match_iff(fact.get(), t1, t2) && + is_quantifier(t1.get()) && + match_and(to_quantifier(t1.get())->get_expr(), terms1) && + match_and(t2.get(), terms2) && + terms1.size() == terms2.size()) { + quantifier * q1 = to_quantifier(t1.get()); + for (unsigned i = 0; i < terms1.size(); ++i) { + if (is_quantifier(terms2[i].get()) && + to_quantifier(terms2[i].get())->get_expr() == terms1[i].get() && + to_quantifier(terms2[i].get())->get_num_decls() == q1->get_num_decls()) { + // ok. + } + else { + return false; + } + } + } + UNREACHABLE(); + return false; + } + case PR_ELIM_UNUSED_VARS: { + if (match_proof(p) && + match_fact(p, fact) && + match_iff(fact.get(), t1, t2)) { + // TBD: + // match_quantifier(t1.get(), is_forall1, decls1, body1) + // filter out decls1 that occur in body1. + // if list is empty, then t2 could be just body1. + // otherwise t2 is also a quantifier. + return true; + } + UNREACHABLE(); + return false; + } + case PR_DER: { + bool is_forall = false; + if (match_proof(p) && + match_fact(p, fact) && + match_iff(fact.get(), t1, t2) && + match_quantifier(t1, is_forall, decls1, body1) && + is_forall && + match_or(body1.get(), terms1)) { + // TBD: check that terms are set of equalities. + // t2 is an instance of a predicate in terms1 + return true; + } + UNREACHABLE(); + return false; + } + case PR_HYPOTHESIS: { + // TBD all branches with hyptheses must be closed by a later lemma. + if (match_proof(p) && + match_fact(p, fml)) { + return true; + } + return false; + } + case PR_LEMMA: { + if (match_proof(p, p1) && + match_fact(p, fact) && + match_fact(p1.get(), fml) && + m_manager.is_false(fml.get())) { + expr_ref_vector hypotheses(m_manager); + expr_ref_vector ors(m_manager); + get_hypotheses(p1.get(), hypotheses); + if (hypotheses.size() == 1 && match_negated(hypotheses.get(0), fact)) { + // Suppose fact is (or a b c) and hypothesis is (not (or a b c)) + // That is, (or a b c) should be viewed as a 'quoted expression' and a unary clause, + // instead of a clause with three literals. + return true; + } + get_ors(fact.get(), ors); + for (unsigned i = 0; i < hypotheses.size(); ++i) { + bool found = false; + unsigned j; + for (j = 0; !found && j < ors.size(); ++j) { + found = match_negated(ors[j].get(), hypotheses[i].get()); + } + if (!found) { + TRACE("pr_lemma_bug", + tout << "i: " << i << "\n"; + tout << "ORs:\n"; + for (unsigned i = 0; i < ors.size(); i++) { + tout << mk_pp(ors.get(i), m_manager) << "\n"; + } + tout << "HYPOTHESIS:\n"; + for (unsigned i = 0; i < hypotheses.size(); i++) { + tout << mk_pp(hypotheses.get(i), m_manager) << "\n"; + }); + UNREACHABLE(); + return false; + } + TRACE("proof_checker", tout << "Matched:\n"; + ast_ll_pp(tout, m_manager, hypotheses[i].get()); + ast_ll_pp(tout, m_manager, ors[j-1].get());); + } + return true; + } + UNREACHABLE(); + return false; + } + case PR_UNIT_RESOLUTION: { + if (match_fact(p, fact) && + proofs.size() == 2 && + match_fact(proofs[0].get(), fml1) && + match_fact(proofs[1].get(), fml2) && + match_negated(fml1.get(), fml2.get()) && + m_manager.is_false(fact.get())) { + return true; + } + if (match_fact(p, fact) && + proofs.size() > 1 && + match_fact(proofs.get(0), fml) && + match_or(fml.get(), terms1)) { + for (unsigned i = 1; i < proofs.size(); ++i) { + if (!match_fact(proofs.get(i), fml2)) { + return false; + } + bool found = false; + for (unsigned j = 0; !found && j < terms1.size(); ++j) { + if (match_negated(terms1.get(j), fml2)) { + found = true; + if (j + 1 < terms1.size()) { + terms1[j] = terms1.get(terms1.size()-1); + } + terms1.resize(terms1.size()-1); + } + } + if (!found) { + TRACE("pr_unit_bug", + tout << "Parents:\n"; + for (unsigned i = 0; i < proofs.size(); i++) { + expr_ref p(m_manager); + match_fact(proofs.get(i), p); + tout << mk_pp(p, m_manager) << "\n"; + } + tout << "Fact:\n"; + tout << mk_pp(fact, m_manager) << "\n"; + tout << "Clause:\n"; + tout << mk_pp(fml, m_manager) << "\n"; + tout << "Could not find premise " << mk_pp(fml2, m_manager) << "\n"; + ); + + UNREACHABLE(); + return false; + } + } + switch(terms1.size()) { + case 0: + return m_manager.is_false(fact.get()); + case 1: + return fact.get() == terms1[0].get(); + default: { + if (match_or(fact.get(), terms2)) { + for (unsigned i = 0; i < terms1.size(); ++i) { + bool found = false; + expr* term1 = terms1[i].get(); + for (unsigned j = 0; !found && j < terms2.size(); ++j) { + found = term1 == terms2[j].get(); + } + if (!found) { + IF_VERBOSE(0, verbose_stream() << "Premise not found:" << mk_pp(term1, m_manager) << "\n";); + return false; + } + } + return true; + } + IF_VERBOSE(0, verbose_stream() << "Conclusion is not a disjunction:\n"; + verbose_stream() << mk_pp(fml.get(), m_manager) << "\n"; + verbose_stream() << mk_pp(fact.get(), m_manager) << "\n";); + + return false; + } + + } + } + UNREACHABLE(); + return false; + } + case PR_IFF_TRUE: { + // iff_true(?rule(?p1, ?fml), (iff ?fml true)) + if (match_proof(p, p1) && + match_fact(p, fact) && + match_fact(p1.get(), fml1) && + match_iff(fact.get(), l1, r1) && + fml1.get() == l1.get() && + r1.get() == m_manager.mk_true()) { + return true; + } + UNREACHABLE(); + return false; + } + case PR_IFF_FALSE: { + // iff_false(?rule(?p1, (not ?fml)), (iff ?fml false)) + if (match_proof(p, p1) && + match_fact(p, fact) && + match_fact(p1.get(), fml1) && + match_iff(fact.get(), l1, r1) && + match_not(fml1.get(), t1) && + t1.get() == l1.get() && + r1.get() == m_manager.mk_false()) { + return true; + } + UNREACHABLE(); + return false; + } + case PR_COMMUTATIVITY: { + // commutativity(= (?c ?t1 ?t2) (?c ?t2 ?t1)) + if (match_fact(p, fact) && + match_proof(p) && + match_equiv(fact.get(), t1, t2) && + match_binary(t1.get(), d1, s1, s2) && + match_binary(t2.get(), d2, u1, u2) && + s1.get() == u2.get() && + s2.get() == u1.get() && + d1.get() == d2.get() && + d1->is_commutative()) { + return true; + } + UNREACHABLE(); + return false; + } + case PR_DEF_AXIOM: { + // axiom(?fml) + if (match_fact(p, fact) && + match_proof(p) && + m_manager.is_bool(fact.get())) { + return true; + } + UNREACHABLE(); + return false; + } + case PR_DEF_INTRO: { + // def_intro(?fml) + // + // ?fml: forall x . ~p(x) or e(x) and forall x . ~e(x) or p(x) + // : forall x . ~cond(x) or f(x) = then(x) and forall x . cond(x) or f(x) = else(x) + // : forall x . f(x) = e(x) + // + if (match_fact(p, fact) && + match_proof(p) && + m_manager.is_bool(fact.get())) { + return true; + } + UNREACHABLE(); + return false; + } + case PR_APPLY_DEF: { + if (match_fact(p, fact) && + match_oeq(fact.get(), t1, t2)) { + // TBD: must definitions be in proofs? + return true; + } + UNREACHABLE(); + return false; + } + case PR_IFF_OEQ: { + // axiom(?rule(?p1,(iff ?t1 ?t2)), (~ ?t1 ?t2)) + if (match_fact(p, fact) && + match_proof(p, p1) && + match_oeq(fact.get(), t1, t2) && + match_fact(p1.get(), fml) && + match_iff(fml.get(), s1, s2) && + s1.get() == t1.get() && + s2.get() == t2.get()) { + return true; + } + UNREACHABLE(); + return false; + } + case PR_NNF_POS: { + // TBD: + return true; + } + case PR_NNF_NEG: { + // TBD: + return true; + } + case PR_NNF_STAR: { + // TBD: + return true; + } + case PR_SKOLEMIZE: { + // (exists ?x (p ?x y)) -> (p (sk y) y) + // (not (forall ?x (p ?x y))) -> (not (p (sk y) y)) + if (match_fact(p, fact) && + match_oeq(fact.get(), t1, t2)) { + quantifier* q = 0; + expr* e = t1.get(); + bool is_forall = false; + if (match_not(t1.get(), s1)) { + e = s1.get(); + is_forall = true; + } + if (is_quantifier(e)) { + q = to_quantifier(e); + // TBD check that quantifier is properly instantiated + return is_forall == q->is_forall(); + } + } + UNREACHABLE(); + return false; + } + case PR_CNF_STAR: { + for (unsigned i = 0; i < proofs.size(); ++i) { + if (match_op(proofs[i].get(), PR_DEF_INTRO, terms)) { + // ok + } + else { + UNREACHABLE(); + return false; + } + } + // coarse grain CNF conversion. + return true; + } + case PR_MODUS_PONENS_OEQ: { + if (match_fact(p, fact) && + match_proof(p, p0, p1) && + match_fact(p0.get(), fml0) && + match_fact(p1.get(), fml1) && + match_oeq(fml1.get(), t1, t2) && + fml0.get() == t1.get() && + fact.get() == t2.get()) { + return true; + } + UNREACHABLE(); + return false; + } + case PR_TH_LEMMA: { + SASSERT(p->get_decl()->get_num_parameters() > 0); + SASSERT(p->get_decl()->get_parameter(0).is_symbol()); + if (symbol("arith") == p->get_decl()->get_parameter(0).get_symbol()) { + return check_arith_proof(p); + } + dump_proof(p); + return true; + } + case PR_QUANT_INST: { + // TODO + return true; + } + default: + UNREACHABLE(); + return false; + } +} + +bool proof_checker::match_fact(proof* p, expr_ref& fact) { + if (m_manager.is_proof(p) && + m_manager.has_fact(p)) { + fact = m_manager.get_fact(p); + return true; + } + return false; +} + +void proof_checker::add_premise(proof* p) { + if (!m_marked.is_marked(p)) { + m_marked.mark(p, true); + m_todo.push_back(p); + } +} + +bool proof_checker::match_proof(proof* p) { + return + m_manager.is_proof(p) && + m_manager.get_num_parents(p) == 0; +} + +bool proof_checker::match_proof(proof* p, proof_ref& p0) { + if (m_manager.is_proof(p) && + m_manager.get_num_parents(p) == 1) { + p0 = m_manager.get_parent(p, 0); + return true; + } + return false; +} + +bool proof_checker::match_proof(proof* p, proof_ref& p0, proof_ref& p1) { + if (m_manager.is_proof(p) && + m_manager.get_num_parents(p) == 2) { + p0 = m_manager.get_parent(p, 0); + p1 = m_manager.get_parent(p, 1); + return true; + } + return false; +} + +bool proof_checker::match_proof(proof* p, proof_ref_vector& parents) { + if (m_manager.is_proof(p)) { + for (unsigned i = 0; i < m_manager.get_num_parents(p); ++i) { + parents.push_back(m_manager.get_parent(p, i)); + } + return true; + } + return false; +} + + +bool proof_checker::match_binary(expr* e, func_decl_ref& d, expr_ref& t1, expr_ref& t2) { + if (e->get_kind() == AST_APP && + to_app(e)->get_num_args() == 2) { + d = to_app(e)->get_decl(); + t1 = to_app(e)->get_arg(0); + t2 = to_app(e)->get_arg(1); + return true; + } + return false; +} + + +bool proof_checker::match_app(expr* e, func_decl_ref& d, expr_ref_vector& terms) { + if (e->get_kind() == AST_APP) { + d = to_app(e)->get_decl(); + for (unsigned i = 0; i < to_app(e)->get_num_args(); ++i) { + terms.push_back(to_app(e)->get_arg(i)); + } + return true; + } + return false; +} + +bool proof_checker::match_quantifier(expr* e, bool& is_univ, sort_ref_vector& sorts, expr_ref& body) { + if (is_quantifier(e)) { + quantifier* q = to_quantifier(e); + is_univ = q->is_forall(); + body = q->get_expr(); + for (unsigned i = 0; i < q->get_num_decls(); ++i) { + sorts.push_back(q->get_decl_sort(i)); + } + return true; + } + return false; +} + +bool proof_checker::match_op(expr* e, decl_kind k, expr_ref& t1, expr_ref& t2) { + if (e->get_kind() == AST_APP && + to_app(e)->get_family_id() == m_manager.get_basic_family_id() && + to_app(e)->get_decl_kind() == k && + to_app(e)->get_num_args() == 2) { + t1 = to_app(e)->get_arg(0); + t2 = to_app(e)->get_arg(1); + return true; + } + return false; +} + +bool proof_checker::match_op(expr* e, decl_kind k, expr_ref_vector& terms) { + if (e->get_kind() == AST_APP && + to_app(e)->get_family_id() == m_manager.get_basic_family_id() && + to_app(e)->get_decl_kind() == k) { + for (unsigned i = 0; i < to_app(e)->get_num_args(); ++i) { + terms.push_back(to_app(e)->get_arg(i)); + } + return true; + } + return false; +} + + +bool proof_checker::match_op(expr* e, decl_kind k, expr_ref& t) { + if (e->get_kind() == AST_APP && + to_app(e)->get_family_id() == m_manager.get_basic_family_id() && + to_app(e)->get_decl_kind() == k && + to_app(e)->get_num_args() == 1) { + t = to_app(e)->get_arg(0); + return true; + } + return false; +} + +bool proof_checker::match_not(expr* e, expr_ref& t) { + return match_op(e, OP_NOT, t); +} + +bool proof_checker::match_or(expr* e, expr_ref_vector& terms) { + return match_op(e, OP_OR, terms); +} + +bool proof_checker::match_and(expr* e, expr_ref_vector& terms) { + return match_op(e, OP_AND, terms); +} + +bool proof_checker::match_iff(expr* e, expr_ref& t1, expr_ref& t2) { + return match_op(e, OP_IFF, t1, t2); +} + +bool proof_checker::match_equiv(expr* e, expr_ref& t1, expr_ref& t2) { + return match_oeq(e, t1, t2) || match_eq(e, t1, t2); +} + +bool proof_checker::match_implies(expr* e, expr_ref& t1, expr_ref& t2) { + return match_op(e, OP_IMPLIES, t1, t2); +} + +bool proof_checker::match_eq(expr* e, expr_ref& t1, expr_ref& t2) { + return match_op(e, OP_EQ, t1, t2) || match_iff(e, t1, t2); +} + +bool proof_checker::match_oeq(expr* e, expr_ref& t1, expr_ref& t2) { + return match_op(e, OP_OEQ, t1, t2); +} + +bool proof_checker::match_negated(expr* a, expr* b) { + expr_ref t(m_manager); + return + (match_not(a, t) && t.get() == b) || + (match_not(b, t) && t.get() == a); +} + +void proof_checker::get_ors(expr* e, expr_ref_vector& ors) { + ptr_buffer buffer; + if (m_manager.is_or(e)) { + app* a = to_app(e); + ors.append(a->get_num_args(), a->get_args()); + } + else { + ors.push_back(e); + } +} + + +void proof_checker::get_hypotheses(proof* p, expr_ref_vector& ante) { + ptr_vector stack; + expr* h = 0; + expr_ref hyp(m_manager); + + stack.push_back(p); + while (!stack.empty()) { + p = stack.back(); + SASSERT(m_manager.is_proof(p)); + if (m_hypotheses.contains(p)) { + stack.pop_back(); + continue; + } + if (is_hypothesis(p) && match_fact(p, hyp)) { + hyp = mk_atom(hyp.get()); + m_pinned.push_back(hyp.get()); + m_hypotheses.insert(p, hyp.get()); + stack.pop_back(); + continue; + } + // in this system all hypotheses get bound by lemmas. + if (m_manager.is_lemma(p)) { + m_hypotheses.insert(p, mk_nil()); + stack.pop_back(); + continue; + } + bool all_found = true; + ptr_vector hyps; + for (unsigned i = 0; i < m_manager.get_num_parents(p); ++i) { + proof* p_i = m_manager.get_parent(p, i); + if (m_hypotheses.find(p_i, h)) { + hyps.push_back(h); + } + else { + stack.push_back(p_i); + all_found = false; + } + } + if (all_found) { + h = mk_hyp(hyps.size(), hyps.c_ptr()); + m_pinned.push_back(h); + m_hypotheses.insert(p, h); + stack.pop_back(); + } + } + + // + // dis-assemble the set of obtained hypotheses. + // + if (!m_hypotheses.find(p, h)) { + UNREACHABLE(); + } + + ptr_buffer hyps; + ptr_buffer todo; + expr_mark mark; + todo.push_back(h); + expr_ref a(m_manager), b(m_manager); + + while (!todo.empty()) { + h = todo.back(); + + todo.pop_back(); + if (mark.is_marked(h)) { + continue; + } + mark.mark(h, true); + if (match_cons(h, a, b)) { + todo.push_back(a.get()); + todo.push_back(b.get()); + } + else if (match_atom(h, a)) { + ante.push_back(a.get()); + } + else { + SASSERT(match_nil(h)); + } + } + TRACE("proof_checker", + { + ast_ll_pp(tout << "Getting hypotheses from: ", m_manager, p); + tout << "Found hypotheses:\n"; + for (unsigned i = 0; i < ante.size(); ++i) { + ast_ll_pp(tout, m_manager, ante[i].get()); + } + }); + +} + +bool proof_checker::match_nil(expr* e) const { + return + is_app(e) && + to_app(e)->get_family_id() == m_hyp_fid && + to_app(e)->get_decl_kind() == OP_NIL; +} + +bool proof_checker::match_cons(expr* e, expr_ref& a, expr_ref& b) const { + if (is_app(e) && + to_app(e)->get_family_id() == m_hyp_fid && + to_app(e)->get_decl_kind() == OP_CONS) { + a = to_app(e)->get_arg(0); + b = to_app(e)->get_arg(1); + return true; + } + return false; +} + + +bool proof_checker::match_atom(expr* e, expr_ref& a) const { + if (is_app(e) && + to_app(e)->get_family_id() == m_hyp_fid && + to_app(e)->get_decl_kind() == OP_ATOM) { + a = to_app(e)->get_arg(0); + return true; + } + return false; +} + +expr* proof_checker::mk_atom(expr* e) { + return m_manager.mk_app(m_hyp_fid, OP_ATOM, e); +} + +expr* proof_checker::mk_cons(expr* a, expr* b) { + return m_manager.mk_app(m_hyp_fid, OP_CONS, a, b); +} + +expr* proof_checker::mk_nil() { + return m_nil.get(); +} + +bool proof_checker::is_hypothesis(proof* p) const { + return + m_manager.is_proof(p) && + p->get_decl_kind() == PR_HYPOTHESIS; +} + +expr* proof_checker::mk_hyp(unsigned num_hyps, expr * const * hyps) { + expr* result = 0; + for (unsigned i = 0; i < num_hyps; ++i) { + if (!match_nil(hyps[i])) { + if (result) { + result = mk_cons(result, hyps[i]); + } + else { + result = hyps[i]; + } + } + } + if (result == 0) { + return mk_nil(); + } + else { + return result; + } +} + +void proof_checker::dump_proof(proof * pr) { + if (!m_dump_lemmas) + return; + SASSERT(m_manager.has_fact(pr)); + expr * consequent = m_manager.get_fact(pr); + unsigned num = m_manager.get_num_parents(pr); + ptr_buffer antecedents; + for (unsigned i = 0; i < num; i++) { + proof * a = m_manager.get_parent(pr, i); + SASSERT(m_manager.has_fact(a)); + antecedents.push_back(m_manager.get_fact(a)); + } + dump_proof(antecedents.size(), antecedents.c_ptr(), consequent); +} + +void proof_checker::dump_proof(unsigned num_antecedents, expr * const * antecedents, expr * consequent) { + char buffer[128]; +#ifdef _WINDOWS + sprintf_s(buffer, ARRAYSIZE(buffer), "proof_lemma_%d.smt", m_proof_lemma_id); +#else + sprintf(buffer, "proof_lemma_%d.smt", m_proof_lemma_id); +#endif + std::ofstream out(buffer); + ast_smt_pp pp(m_manager); + pp.set_benchmark_name("lemma"); + pp.set_status("unsat"); + pp.set_logic(m_logic.c_str()); + for (unsigned i = 0; i < num_antecedents; i++) + pp.add_assumption(antecedents[i]); + expr_ref n(m_manager); + n = m_manager.mk_not(consequent); + pp.display(out, n); + out.close(); + m_proof_lemma_id++; +} + +bool proof_checker::check_arith_literal(bool is_pos, app* lit0, rational const& coeff, expr_ref& sum, bool& is_strict) { + ast_manager& m = m_manager; + arith_util a(m); + app* lit = lit0; + + if (m.is_not(lit)) { + lit = to_app(lit->get_arg(0)); + is_pos = !is_pos; + } + if (!a.is_le(lit) && !a.is_lt(lit) && !a.is_ge(lit) && !a.is_gt(lit) && !m.is_eq(lit)) { + std::cout << mk_pp(lit, m) << "\n"; + return false; + } + SASSERT(lit->get_num_args() == 2); + sort* s = m.get_sort(lit->get_arg(0)); + bool is_int = a.is_int(s); + + if (!is_int && is_pos && (a.is_gt(lit) || a.is_lt(lit))) { + is_strict = true; + } + if (!is_int && !is_pos && (a.is_ge(lit) || a.is_le(lit))) { + is_strict = true; + } + + + SASSERT(a.is_int(s) || a.is_real(s)); + expr_ref sign1(m), sign2(m), term(m); + sign1 = a.mk_numeral(m.is_eq(lit)?coeff:abs(coeff), s); + sign2 = a.mk_numeral(m.is_eq(lit)?-coeff:-abs(coeff), s); + if (!sum.get()) { + sum = a.mk_numeral(rational(0), s); + } + + expr* a0 = lit->get_arg(0); + expr* a1 = lit->get_arg(1); + + if (is_pos && (a.is_ge(lit) || a.is_gt(lit))) { + std::swap(a0, a1); + } + if (!is_pos && (a.is_le(lit) || a.is_lt(lit))) { + std::swap(a0, a1); + } + + // + // Multiplying by coefficients over strict + // and non-strict inequalities: + // + // (a <= b) * 2 + // (a - b <= 0) * 2 + // (2a - 2b <= 0) + + // (a < b) * 2 <=> + // (a +1 <= b) * 2 <=> + // 2a + 2 <= 2b <=> + // 2a+2-2b <= 0 + + bool strict_ineq = + is_pos?(a.is_gt(lit) || a.is_lt(lit)):(a.is_ge(lit) || a.is_le(lit)); + + if (is_int && strict_ineq) { + sum = a.mk_add(sum, sign1); + } + + term = a.mk_mul(sign1, a0); + sum = a.mk_add(sum, term); + term = a.mk_mul(sign2, a1); + sum = a.mk_add(sum, term); + +#if 1 + { + th_rewriter rw(m); + rw(sum); + } + + std::cout << coeff << "\n" << mk_pp(lit0, m) << "\n" << mk_pp(sum, m) << "\n"; +#endif + + return true; +} + +bool proof_checker::check_arith_proof(proof* p) { + func_decl* d = p->get_decl(); + SASSERT(PR_TH_LEMMA == p->get_decl_kind()); + SASSERT(d->get_parameter(0).get_symbol() == "arith"); + ast_manager& m = m_manager; + unsigned num_params = d->get_num_parameters(); + arith_util autil(m); + + SASSERT(num_params > 0); + if (num_params == 1) { + dump_proof(p); + return true; + } + expr_ref fact(m); + proof_ref_vector proofs(m_manager); + + if (!match_fact(p, fact)) { + UNREACHABLE(); + return false; + } + + if (d->get_parameter(1).get_symbol() != symbol("farkas")) { + dump_proof(p); + return true; + } + expr_ref sum(m); + bool is_strict = false; + unsigned offset = 0; + vector coeffs; + rational lc(1); + for (unsigned i = 2; i < d->get_num_parameters(); ++i) { + parameter const& p = d->get_parameter(i); + if (!p.is_rational()) { + UNREACHABLE(); + return false; + } + coeffs.push_back(p.get_rational()); + lc = lcm(lc, denominator(coeffs.back())); + } + if (!lc.is_one()) { + for (unsigned i = 0; i < coeffs.size(); ++i) { + coeffs[i] = lc*coeffs[i]; + } + } + + unsigned num_parents = m.get_num_parents(p); + for (unsigned i = 0; i < num_parents; i++) { + proof * a = m.get_parent(p, i); + SASSERT(m.has_fact(a)); + if (!check_arith_literal(true, to_app(m.get_fact(a)), coeffs[offset++], sum, is_strict)) { + return false; + } + } + + if (m.is_or(fact)) { + app* disj = to_app(fact); + unsigned num_args = disj->get_num_args(); + for (unsigned i = 0; i < num_args; ++i) { + app* lit = to_app(disj->get_arg(i)); + if (!check_arith_literal(false, lit, coeffs[offset++], sum, is_strict)) { + return false; + } + } + } + else if (!m.is_false(fact)) { + if (!check_arith_literal(false, to_app(fact), coeffs[offset++], sum, is_strict)) { + return false; + } + } + + if (!sum.get()) { + return false; + } + + sort* s = m.get_sort(sum); + + + if (is_strict) { + sum = autil.mk_lt(sum, autil.mk_numeral(rational(0), s)); + } + else { + sum = autil.mk_le(sum, autil.mk_numeral(rational(0), s)); + } + + th_rewriter rw(m); + rw(sum); + + if (!m.is_false(sum)) { + std::cout << "Arithmetic proof check failed: " << mk_pp(sum, m) << "\n"; + m_dump_lemmas = true; + dump_proof(p); + return false; + } + + return true; +} diff --git a/lib/proof_checker.h b/lib/proof_checker.h new file mode 100644 index 000000000..8c4740ea2 --- /dev/null +++ b/lib/proof_checker.h @@ -0,0 +1,116 @@ +/*++ +Copyright (c) 2008 Microsoft Corporation + +Module Name: + + proof_checker.h + +Abstract: + + Proof checker. + +Author: + + Nikolaj Bjorner (nbjorner) 2008-03-07. + +Revision History: + +--*/ +#ifndef _PROOF_CHECKER_H_ +#define _PROOF_CHECKER_H_ + +#include "ast.h" +#include "map.h" + +class proof_checker { + ast_manager& m_manager; + proof_ref_vector m_todo; + expr_mark m_marked; + expr_ref_vector m_pinned; + obj_map m_hypotheses; + family_id m_hyp_fid; + family_id m_spc_fid; + app_ref m_nil; + bool m_dump_lemmas; + std::string m_logic; + unsigned m_proof_lemma_id; + enum hyp_decl_kind { + OP_CONS, + OP_ATOM, + OP_NIL + }; + enum hyp_sort_kind { + CELL_SORT + }; + class hyp_decl_plugin : public decl_plugin { + protected: + func_decl* m_cons; + func_decl* m_atom; + func_decl* m_nil; + sort* m_cell; + virtual void set_manager(ast_manager * m, family_id id); + func_decl * mk_func_decl(decl_kind k); + public: + hyp_decl_plugin(); + + virtual ~hyp_decl_plugin() {} + + virtual void finalize(); + + virtual decl_plugin * mk_fresh() { return alloc(hyp_decl_plugin); } + + virtual sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const* parameters); + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned num_args, expr * const * args, sort * range); + virtual void get_op_names(svector & op_names, symbol const & logic); + virtual void get_sort_names(svector & sort_names, symbol const & logic); + }; +public: + proof_checker(ast_manager& m); + void set_dump_lemmas(char const * logic = "AUFLIA") { m_dump_lemmas = true; m_logic = logic; } + bool check(proof* p, expr_ref_vector& side_conditions); +private: + bool check1(proof* p, expr_ref_vector& side_conditions); + bool check1_basic(proof* p, expr_ref_vector& side_conditions); + bool check1_spc(proof* p, expr_ref_vector& side_conditions); + bool check_arith_proof(proof* p); + bool check_arith_literal(bool is_pos, app* lit, rational const& coeff, expr_ref& sum, bool& is_strict); + bool match_fact(proof* p, expr_ref& fact); + void add_premise(proof* p); + bool match_proof(proof* p); + bool match_proof(proof* p, proof_ref& p0); + bool match_proof(proof* p, proof_ref& p0, proof_ref& p1); + bool match_proof(proof* p, proof_ref_vector& parents); + bool match_binary(expr* e, func_decl_ref& d, expr_ref& t1, expr_ref& t2); + bool match_op(expr* e, decl_kind k, expr_ref& t1, expr_ref& t2); + bool match_op(expr* e, decl_kind k, expr_ref& t); + bool match_op(expr* e, decl_kind k, expr_ref_vector& terms); + bool match_iff(expr* e, expr_ref& t1, expr_ref& t2); + bool match_implies(expr* e, expr_ref& t1, expr_ref& t2); + bool match_eq(expr* e, expr_ref& t1, expr_ref& t2); + bool match_oeq(expr* e, expr_ref& t1, expr_ref& t2); + bool match_not(expr* e, expr_ref& t); + bool match_or(expr* e, expr_ref_vector& terms); + bool match_and(expr* e, expr_ref_vector& terms); + bool match_app(expr* e, func_decl_ref& d, expr_ref_vector& terms); + bool match_quantifier(expr*, bool& is_univ, sort_ref_vector&, expr_ref& body); + bool match_negated(expr* a, expr* b); + bool match_equiv(expr* a, expr_ref& t1, expr_ref& t2); + void get_ors(expr* e, expr_ref_vector& ors); + void get_hypotheses(proof* p, expr_ref_vector& ante); + + bool match_nil(expr* e) const; + bool match_cons(expr* e, expr_ref& a, expr_ref& b) const; + bool match_atom(expr* e, expr_ref& a) const; + expr* mk_nil(); + expr* mk_cons(expr* a, expr* b); + expr* mk_atom(expr* e); + bool is_hypothesis(proof* p) const; + expr* mk_hyp(unsigned num_hyps, expr * const * hyps); + void dump_proof(proof * pr); + void dump_proof(unsigned num_antecedents, expr * const * antecedents, expr * consequent); +}; + +#endif diff --git a/lib/proof_converter.cpp b/lib/proof_converter.cpp new file mode 100644 index 000000000..fa58d5208 --- /dev/null +++ b/lib/proof_converter.cpp @@ -0,0 +1,162 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + proof_converter.cpp + +Abstract: + + Abstract interface for converting proofs, and basic combinators + +Author: + + Leonardo (leonardo) 2011-11-14 + +Notes: + +--*/ +#include"proof_converter.h" +#include"ast_smt2_pp.h" + +class concat_proof_converter : public concat_converter { +public: + concat_proof_converter(proof_converter * pc1, proof_converter * pc2):concat_converter(pc1, pc2) {} + + virtual char const * get_name() const { return "concat-proof-converter"; } + + virtual void operator()(ast_manager & m, unsigned num_source, proof * const * source, proof_ref & result) { + proof_ref tmp(m); + this->m_c2->operator()(m, num_source, source, tmp); + proof * new_source = tmp.get(); + this->m_c1->operator()(m, 1, &new_source, result); + } + + virtual proof_converter * translate(ast_translation & translator) { + return this->translate_core(translator); + } +}; + +proof_converter * concat(proof_converter * pc1, proof_converter * pc2) { + if (pc1 == 0) + return pc2; + if (pc2 == 0) + return pc1; + return alloc(concat_proof_converter, pc1, pc2); +} + +class concat_star_proof_converter : public concat_star_converter { +public: + concat_star_proof_converter(proof_converter * pc1, unsigned num, proof_converter * const * pc2s, unsigned * szs): + concat_star_converter(pc1, num, pc2s, szs) { + } + + virtual char const * get_name() const { return "concat-star-proof-converter"; } + + virtual void operator()(ast_manager & m, unsigned num_source, proof * const * source, proof_ref & result) { + unsigned num = this->m_szs.size(); +#ifdef Z3DEBUG + unsigned sum = 0; + for (unsigned i = 0; i < num; i++) { + sum += this->m_szs[i]; + } + SASSERT(sum == num_source); +#endif + proof_ref_buffer tmp_prs(m); + for (unsigned i = 0; i < num; i++) { + unsigned sz = m_szs[i]; + proof_converter * c2 = m_c2s[i]; + proof_ref pr(m); + if (c2) { + (*c2)(m, sz, source, pr); + } + else { + SASSERT(sz == 1); + pr = *source; + } + source += sz; + tmp_prs.push_back(pr.get()); + } + if (m_c1) { + (*m_c1)(m, tmp_prs.size(), tmp_prs.c_ptr(), result); + } + else { + SASSERT(tmp_prs.size() == 1); + result = tmp_prs[0]; + } + } + + virtual proof_converter * translate(ast_translation & translator) { + return this->translate_core(translator); + } +}; + +proof_converter * concat(proof_converter * pc1, unsigned num, proof_converter * const * pc2s, unsigned * szs) { + SASSERT(num > 0); + if (num == 1) + return concat(pc1, pc2s[0]); + unsigned i; + for (i = 0; i < num; i++) { + if (pc2s[i] != 0) + break; + } + if (i == num) { + // all pc2s are 0 + return pc1; + } + return alloc(concat_star_proof_converter, pc1, num, pc2s, szs); +} + +class proof2pc : public proof_converter { + proof_ref m_pr; +public: + proof2pc(ast_manager & m, proof * pr):m_pr(pr, m) {} + virtual ~proof2pc() {} + + virtual void operator()(ast_manager & m, unsigned num_source, proof * const * source, proof_ref & result) { + SASSERT(num_source == 0); + result = m_pr; + } + + virtual proof_converter * translate(ast_translation & translator) { + return alloc(proof2pc, translator.to(), translator(m_pr.get())); + } + + virtual void display(std::ostream & out) { + out << "(proof->proof-converter-wrapper\n" << mk_ismt2_pp(m_pr.get(), m_pr.get_manager()) << ")\n"; + } +}; + +proof_converter * proof2proof_converter(ast_manager & m, proof * pr) { + if (pr == 0) + return 0; + return alloc(proof2pc, m, pr); +} + +void apply(ast_manager & m, proof_converter * pc, proof_ref & pr) { + if (pc) { + proof * _pr = pr.get(); + (*pc)(m, 1, &_pr, pr); + } +} + +/** + Let pc2s be a buffer of proof converters that are wrappers for proofs. + That is, they are functors of the form: unit -> Proof + Then, this function applies pc1 to the proofs produced by pc2s's and store + the resultant proof in result. + + pc1 and pc2s must be different from 0. +*/ +void apply(ast_manager & m, proof_converter_ref & pc1, proof_converter_ref_buffer & pc2s, proof_ref & result) { + SASSERT(pc1); + proof_ref_buffer prs(m); + unsigned sz = pc2s.size(); + for (unsigned i = 0; i < sz; i++) { + proof_ref pr(m); + SASSERT(pc2s[i]); // proof production is enabled + pc2s[i]->operator()(m, 0, 0, pr); + prs.push_back(pr); + } + (*pc1)(m, sz, prs.c_ptr(), result); +} diff --git a/lib/proof_converter.h b/lib/proof_converter.h new file mode 100644 index 000000000..fd2f5b877 --- /dev/null +++ b/lib/proof_converter.h @@ -0,0 +1,52 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + proof_converter.h + +Abstract: + + Abstract interface for converting proofs, and basic combinators. + +Author: + + Leonardo (leonardo) 2011-04-26 + +Notes: + +--*/ +#ifndef _PROOF_CONVERTER_H_ +#define _PROOF_CONVERTER_H_ + +#include"ast.h" +#include"converter.h" +#include"ref.h" + +class proof_converter : public converter { +public: + virtual ~proof_converter() { } + virtual void operator()(ast_manager & m, unsigned num_source, proof * const * source, proof_ref & result) = 0; + virtual proof_converter * translate(ast_translation & translator) = 0; +}; + +typedef ref proof_converter_ref; + +proof_converter * concat(proof_converter * pc1, proof_converter * pc2); + +/** + \brief \c pc1 is the proof converter for a sequence of subgoals of size \c num. + Given an i in [0, num), pc2s[i] is the proof converter for subgoal i, + and num_subgoals[i] is the number of subgoals of subgoals[i]. +*/ +proof_converter * concat(proof_converter * pc1, unsigned num, proof_converter * const * pc2s, unsigned * num_subgoals); + +proof_converter * proof2proof_converter(ast_manager & m, proof * pr); + +typedef sref_vector proof_converter_ref_vector; +typedef sref_buffer proof_converter_ref_buffer; + +void apply(ast_manager & m, proof_converter_ref & pc1, proof_converter_ref_buffer & pc2s, proof_ref & result); +void apply(ast_manager & m, proof_converter * pc, proof_ref & pr); + +#endif diff --git a/lib/propagate_ineqs_tactic.cpp b/lib/propagate_ineqs_tactic.cpp new file mode 100644 index 000000000..12954005f --- /dev/null +++ b/lib/propagate_ineqs_tactic.cpp @@ -0,0 +1,563 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + propagate_ineqs_tactic.h + +Abstract: + + This tactic performs the following tasks: + + - Propagate bounds using the bound_propagator. + - Eliminate subsumed inequalities. + For example: + x - y >= 3 + can be replaced with true if we know that + x >= 3 and y <= 0 + + - Convert inequalities of the form p <= k and p >= k into p = k, + where p is a polynomial and k is a constant. + + This strategy assumes the input is in arith LHS mode. + This can be achieved by using option :arith-lhs true in the + simplifier. + +Author: + + Leonardo (leonardo) 2012-02-19 + +Notes: + +--*/ +#include"tactical.h" +#include"bound_propagator.h" +#include"arith_decl_plugin.h" +#include"simplify_tactic.h" +#include"ast_smt2_pp.h" + +class propagate_ineqs_tactic : public tactic { + struct imp; + imp * m_imp; + params_ref m_params; +public: + propagate_ineqs_tactic(ast_manager & m, params_ref const & p); + + virtual tactic * translate(ast_manager & m) { + return alloc(propagate_ineqs_tactic, m, m_params); + } + + virtual ~propagate_ineqs_tactic(); + + virtual void updt_params(params_ref const & p); + virtual void collect_param_descrs(param_descrs & r) {} + + virtual void operator()(goal_ref const & g, goal_ref_buffer & result, model_converter_ref & mc, proof_converter_ref & pc, expr_dependency_ref & core); + + virtual void cleanup(); +protected: + virtual void set_cancel(bool f); +}; + +tactic * mk_propagate_ineqs_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(propagate_ineqs_tactic, m, p)); +} + +struct propagate_ineqs_tactic::imp { + ast_manager & m; + unsynch_mpq_manager nm; + small_object_allocator m_allocator; + bound_propagator bp; + arith_util m_util; + typedef bound_propagator::var a_var; + obj_map m_expr2var; + expr_ref_vector m_var2expr; + + typedef numeral_buffer mpq_buffer; + typedef svector var_buffer; + + mpq_buffer m_num_buffer; + var_buffer m_var_buffer; + goal_ref m_new_goal; + + imp(ast_manager & _m, params_ref const & p): + m(_m), + m_allocator("ineq-simplifier"), + bp(nm, m_allocator, p), + m_util(m), + m_var2expr(m), + m_num_buffer(nm) { + updt_params_core(p); + } + + void updt_params_core(params_ref const & p) { + } + + void updt_params(params_ref const & p) { + updt_params_core(p); + bp.updt_params(p); + } + + void display_bounds(std::ostream & out) { + unsigned sz = m_var2expr.size(); + mpq k; + bool strict; + unsigned ts; + for (unsigned x = 0; x < sz; x++) { + if (bp.lower(x, k, strict, ts)) + out << nm.to_string(k) << " " << (strict ? "<" : "<="); + else + out << "-oo <"; + out << " " << mk_ismt2_pp(m_var2expr.get(x), m) << " "; + if (bp.upper(x, k, strict, ts)) + out << (strict ? "<" : "<=") << " " << nm.to_string(k); + else + out << "< oo"; + out << "\n"; + } + } + + a_var mk_var(expr * t) { + if (m_util.is_to_real(t)) + t = to_app(t)->get_arg(0); + a_var x; + if (m_expr2var.find(t, x)) + return x; + x = m_var2expr.size(); + bp.mk_var(x, m_util.is_int(t)); + m_var2expr.push_back(t); + m_expr2var.insert(t, x); + return x; + } + + void expr2linear_pol(expr * t, mpq_buffer & as, var_buffer & xs) { + mpq c_mpq_val; + if (m_util.is_add(t)) { + rational c_val; + unsigned num = to_app(t)->get_num_args(); + for (unsigned i = 0; i < num; i++) { + expr * mon = to_app(t)->get_arg(i); + expr * c, * x; + if (m_util.is_mul(mon, c, x) && m_util.is_numeral(c, c_val)) { + nm.set(c_mpq_val, c_val.to_mpq()); + as.push_back(c_mpq_val); + xs.push_back(mk_var(x)); + } + else { + as.push_back(mpq(1)); + xs.push_back(mk_var(mon)); + } + } + } + else { + as.push_back(mpq(1)); + xs.push_back(mk_var(t)); + } + nm.del(c_mpq_val); + } + + a_var mk_linear_pol(expr * t) { + a_var x; + if (m_expr2var.find(t, x)) + return x; + x = mk_var(t); + if (m_util.is_add(t)) { + m_num_buffer.reset(); + m_var_buffer.reset(); + expr2linear_pol(t, m_num_buffer, m_var_buffer); + m_num_buffer.push_back(mpq(-1)); + m_var_buffer.push_back(x); + bp.mk_eq(m_num_buffer.size(), m_num_buffer.c_ptr(), m_var_buffer.c_ptr()); + } + return x; + } + + enum kind { EQ, LE, GE }; + + bool process(expr * t) { + bool sign = false; + while (m.is_not(t, t)) + sign = !sign; + bool strict = false; + kind k; + if (m.is_eq(t)) { + if (sign) + return false; + k = EQ; + } + else if (m_util.is_le(t)) { + if (sign) { + k = GE; + strict = true; + } + else { + k = LE; + } + } + else if (m_util.is_ge(t)) { + if (sign) { + k = LE; + strict = true; + } + else { + k = GE; + } + } + else { + return false; + } + expr * lhs = to_app(t)->get_arg(0); + expr * rhs = to_app(t)->get_arg(1); + if (m_util.is_numeral(lhs)) { + std::swap(lhs, rhs); + if (k == LE) + k = GE; + else if (k == GE) + k = LE; + } + + rational c; + if (!m_util.is_numeral(rhs, c)) + return false; + a_var x = mk_linear_pol(lhs); + mpq c_prime; + nm.set(c_prime, c.to_mpq()); + if (k == EQ) { + SASSERT(!strict); + bp.assert_lower(x, c_prime, false); + bp.assert_upper(x, c_prime, false); + } + else if (k == LE) { + bp.assert_upper(x, c_prime, strict); + } + else { + SASSERT(k == GE); + bp.assert_lower(x, c_prime, strict); + } + return true; + } + + bool collect_bounds(goal const & g) { + bool found = false; + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + expr * t = g.form(i); + if (process(t)) + found = true; + else + m_new_goal->assert_expr(t); // save non-bounds here + } + return found; + } + + bool lower_subsumed(expr * p, mpq const & k, bool strict) { + if (!m_util.is_add(p)) + return false; + m_num_buffer.reset(); + m_var_buffer.reset(); + expr2linear_pol(p, m_num_buffer, m_var_buffer); + mpq implied_k; + bool implied_strict; + bool result = + bp.lower(m_var_buffer.size(), m_num_buffer.c_ptr(), m_var_buffer.c_ptr(), implied_k, implied_strict) && + (nm.gt(implied_k, k) || (nm.eq(implied_k, k) && (!strict || implied_strict))); + nm.del(implied_k); + return result; + } + + bool upper_subsumed(expr * p, mpq const & k, bool strict) { + if (!m_util.is_add(p)) + return false; + m_num_buffer.reset(); + m_var_buffer.reset(); + expr2linear_pol(p, m_num_buffer, m_var_buffer); + mpq implied_k; + bool implied_strict; + bool result = + bp.upper(m_var_buffer.size(), m_num_buffer.c_ptr(), m_var_buffer.c_ptr(), implied_k, implied_strict) && + (nm.lt(implied_k, k) || (nm.eq(implied_k, k) && (!strict || implied_strict))); + nm.del(implied_k); + return result; + } + + void restore_bounds() { + mpq l, u; + bool strict_l, strict_u, has_l, has_u; + unsigned ts; + unsigned sz = m_var2expr.size(); + for (unsigned x = 0; x < sz; x++) { + expr * p = m_var2expr.get(x); + has_l = bp.lower(x, l, strict_l, ts); + has_u = bp.upper(x, u, strict_u, ts); + if (!has_l && !has_u) + continue; + if (has_l && has_u && nm.eq(l, u) && !strict_l && !strict_u) { + // l <= p <= l --> p = l + m_new_goal->assert_expr(m.mk_eq(p, m_util.mk_numeral(rational(l), m_util.is_int(p)))); + continue; + } + if (has_l && !lower_subsumed(p, l, strict_l)) { + if (strict_l) + m_new_goal->assert_expr(m.mk_not(m_util.mk_le(p, m_util.mk_numeral(rational(l), m_util.is_int(p))))); + else + m_new_goal->assert_expr(m_util.mk_ge(p, m_util.mk_numeral(rational(l), m_util.is_int(p)))); + } + if (has_u && !upper_subsumed(p, u, strict_u)) { + if (strict_u) + m_new_goal->assert_expr(m.mk_not(m_util.mk_ge(p, m_util.mk_numeral(rational(u), m_util.is_int(p))))); + else + m_new_goal->assert_expr(m_util.mk_le(p, m_util.mk_numeral(rational(u), m_util.is_int(p)))); + } + } + } + + bool is_x_minus_y_eq_0(expr * t, expr * & x, expr * & y) { + expr * lhs, * rhs, * m1, * m2; + if (m.is_eq(t, lhs, rhs) && m_util.is_zero(rhs) && m_util.is_add(lhs, m1, m2)) { + if (m_util.is_times_minus_one(m2, y) && is_uninterp_const(m1)) { + x = m1; + return true; + } + if (m_util.is_times_minus_one(m1, y) && is_uninterp_const(m2)) { + x = m2; + return true; + } + } + return false; + } + + bool is_unbounded(expr * t) { + a_var x; + if (m_expr2var.find(t, x)) + return !bp.has_lower(x) && !bp.has_upper(x); + return true; + } + + bool lower(expr * t, mpq & k, bool & strict) { + unsigned ts; + a_var x; + if (m_expr2var.find(t, x)) + return bp.lower(x, k, strict, ts); + return false; + } + + bool upper(expr * t, mpq & k, bool & strict) { + unsigned ts; + a_var x; + if (m_expr2var.find(t, x)) + return bp.upper(x, k, strict, ts); + return false; + } + + void find_ite_bounds(expr * root) { + TRACE("find_ite_bounds_bug", display_bounds(tout);); + expr * n = root; + expr * target = 0; + expr * c, * t, * e; + expr * x, * y; + bool has_l, has_u; + mpq l_min, u_max; + bool l_strict, u_strict; + mpq curr; + bool curr_strict; + while (true) { + TRACE("find_ite_bounds_bug", tout << mk_ismt2_pp(n, m) << "\n";); + + if (m.is_ite(n, c, t, e)) { + if (is_x_minus_y_eq_0(t, x, y)) + n = e; + else if (is_x_minus_y_eq_0(e, x, y)) + n = t; + else + break; + } + else if (is_x_minus_y_eq_0(n, x, y)) { + n = 0; + } + else { + break; + } + + TRACE("find_ite_bounds_bug", tout << "x: " << mk_ismt2_pp(x, m) << ", y: " << mk_ismt2_pp(y, m) << "\n"; + if (target) { + tout << "target: " << mk_ismt2_pp(target, m) << "\n"; + tout << "has_l: " << has_l << " " << nm.to_string(l_min) << " has_u: " << has_u << " " << nm.to_string(u_max) << "\n"; + }); + + if (is_unbounded(y)) + std::swap(x, y); + + if (!is_unbounded(x)) { + TRACE("find_ite_bounds_bug", tout << "x is already bounded\n";); + break; + } + + if (target == 0) { + target = x; + if (lower(y, curr, curr_strict)) { + has_l = true; + nm.set(l_min, curr); + l_strict = curr_strict; + } + else { + has_l = false; + TRACE("find_ite_bounds_bug", tout << "y does not have lower\n";); + } + if (upper(y, curr, curr_strict)) { + has_u = true; + nm.set(u_max, curr); + u_strict = curr_strict; + } + else { + has_u = false; + TRACE("find_ite_bounds_bug", tout << "y does not have upper\n";); + } + } + else if (target == x) { + if (has_l) { + if (lower(y, curr, curr_strict)) { + if (nm.lt(curr, l_min) || (!curr_strict && l_strict && nm.eq(curr, l_min))) { + nm.set(l_min, curr); + l_strict = curr_strict; + } + } + else { + has_l = false; + TRACE("find_ite_bounds_bug", tout << "y does not have lower\n";); + } + } + if (has_u) { + if (upper(y, curr, curr_strict)) { + if (nm.gt(curr, u_max) || (curr_strict && !u_strict && nm.eq(curr, u_max))) { + nm.set(u_max, curr); + u_strict = curr_strict; + } + } + else { + has_u = false; + TRACE("find_ite_bounds_bug", tout << "y does not have upper\n";); + } + } + } + else { + break; + } + + if (!has_l && !has_u) + break; + + if (n == 0) { + TRACE("find_ite_bounds", tout << "found bounds for: " << mk_ismt2_pp(target, m) << "\n"; + tout << "has_l: " << has_l << " " << nm.to_string(l_min) << " l_strict: " << l_strict << "\n"; + tout << "has_u: " << has_u << " " << nm.to_string(u_max) << " u_strict: " << u_strict << "\n"; + tout << "root:\n" << mk_ismt2_pp(root, m) << "\n";); + a_var x = mk_var(target); + if (has_l) + bp.assert_lower(x, l_min, l_strict); + if (has_u) + bp.assert_upper(x, u_max, u_strict); + break; + } + } + nm.del(l_min); + nm.del(u_max); + nm.del(curr); + } + + void find_ite_bounds() { + unsigned sz = m_new_goal->size(); + for (unsigned i = 0; i < sz; i++) { + expr * f = m_new_goal->form(i); + if (m.is_ite(f)) + find_ite_bounds(to_app(f)); + } + bp.propagate(); + TRACE("find_ite_bounds", display_bounds(tout);); + } + + void operator()(goal * g, goal_ref & r) { + tactic_report report("propagate-ineqs", *g); + + m_new_goal = alloc(goal, *g, true); + m_new_goal->inc_depth(); + r = m_new_goal.get(); + if (!collect_bounds(*g)) { + m_new_goal = 0; + r = g; + return; // nothing to be done + } + + TRACE("propagate_ineqs_tactic", g->display(tout); display_bounds(tout); tout << "bound propagator:\n"; bp.display(tout);); + + bp.propagate(); + + report_tactic_progress(":bound-propagations", bp.get_num_propagations()); + report_tactic_progress(":bound-false-alarms", bp.get_num_false_alarms()); + + if (bp.inconsistent()) { + r->reset(); + r->assert_expr(m.mk_false()); + return; + } + + // find_ite_bounds(); // did not help + + restore_bounds(); + + TRACE("propagate_ineqs_tactic", tout << "after propagation:\n"; display_bounds(tout); bp.display(tout);); + TRACE("propagate_ineqs_tactic", r->display(tout);); + } + + void set_cancel(bool f) { + // TODO + } +}; + +propagate_ineqs_tactic::propagate_ineqs_tactic(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); +} + +propagate_ineqs_tactic::~propagate_ineqs_tactic() { + dealloc(m_imp); +} + +void propagate_ineqs_tactic::updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); +} + +void propagate_ineqs_tactic::operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + fail_if_proof_generation("propagate-ineqs", g); + fail_if_unsat_core_generation("propagate-ineqs", g); + mc = 0; pc = 0; core = 0; result.reset(); + goal_ref r; + (*m_imp)(g.get(), r); + result.push_back(r.get()); + SASSERT(r->is_well_sorted()); +} + +void propagate_ineqs_tactic::set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); +} + +void propagate_ineqs_tactic::cleanup() { + ast_manager & m = m_imp->m; + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + d = m_imp; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } +} diff --git a/lib/propagate_ineqs_tactic.h b/lib/propagate_ineqs_tactic.h new file mode 100644 index 000000000..e86a6dc2e --- /dev/null +++ b/lib/propagate_ineqs_tactic.h @@ -0,0 +1,42 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + propagate_ineqs_tactic.h + +Abstract: + + This tactic performs the following tasks: + + - Propagate bounds using the bound_propagator. + - Eliminate subsumed inequalities. + For example: + x - y >= 3 + can be replaced with true if we know that + x >= 3 and y <= 0 + + - Convert inequalities of the form p <= k and p >= k into p = k, + where p is a polynomial and k is a constant. + + This strategy assumes the input is in arith LHS mode. + This can be achieved by using option :arith-lhs true in the + simplifier. + +Author: + + Leonardo (leonardo) 2012-02-19 + +Notes: + +--*/ +#ifndef _PROPAGATE_INEQS_TACTIC_H_ +#define _PROPAGATE_INEQS_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_propagate_ineqs_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/propagate_values_tactic.cpp b/lib/propagate_values_tactic.cpp new file mode 100644 index 000000000..41b714172 --- /dev/null +++ b/lib/propagate_values_tactic.cpp @@ -0,0 +1,275 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + propagate_values_tactic.cpp + +Abstract: + + Propagate values using equalities of the form (= t v) where v is a value, + and atoms t and (not t) + +Author: + + Leonardo de Moura (leonardo) 2011-12-28. + +Revision History: + +--*/ +#include"tactical.h" +#include"propagate_values_tactic.h" +#include"th_rewriter.h" +#include"ast_smt2_pp.h" +#include"expr_substitution.h" +#include"goal_shared_occs.h" + +class propagate_values_tactic : public tactic { + struct imp { + ast_manager & m_manager; + th_rewriter m_r; + scoped_ptr m_subst; + goal * m_goal; + goal_shared_occs m_occs; + unsigned m_idx; + unsigned m_max_rounds; + bool m_modified; + + imp(ast_manager & m, params_ref const & p): + m_manager(m), + m_r(m, p), + m_goal(0), + m_occs(m, true /* track atoms */) { + updt_params_core(p); + } + + void updt_params_core(params_ref const & p) { + m_max_rounds = p.get_uint(":max-rounds", 4); + } + + void updt_params(params_ref const & p) { + m_r.updt_params(p); + updt_params_core(p); + } + + ast_manager & m() const { return m_manager; } + + void set_cancel(bool f) { + m_r.set_cancel(f); + } + + bool is_shared(expr * t) { + return m_occs.is_shared(t); + } + + bool is_shared_neg(expr * t, expr * & atom) { + if (!m().is_not(t)) + return false; + atom = to_app(t)->get_arg(0); + return is_shared(atom); + } + + bool is_shared_eq(expr * t, expr * & lhs, expr * & value) { + if (!m().is_eq(t)) + return false; + expr * arg1 = to_app(t)->get_arg(0); + expr * arg2 = to_app(t)->get_arg(1); + if (m().is_value(arg1) && is_shared(arg2)) { + lhs = arg2; + value = arg1; + return true; + } + if (m().is_value(arg2) && is_shared(arg1)) { + lhs = arg1; + value = arg2; + return true; + } + return false; + } + + void push_result(expr * new_curr, proof * new_pr) { + if (m_goal->proofs_enabled()) { + proof * pr = m_goal->pr(m_idx); + new_pr = m().mk_modus_ponens(pr, new_pr); + } + + expr_dependency_ref new_d(m()); + if (m_goal->unsat_core_enabled()) { + new_d = m_goal->dep(m_idx); + expr_dependency * used_d = m_r.get_used_dependencies(); + if (used_d != 0) { + new_d = m().mk_join(new_d, used_d); + m_r.reset_used_dependencies(); + } + } + + m_goal->update(m_idx, new_curr, new_pr, new_d); + + if (is_shared(new_curr)) { + m_subst->insert(new_curr, m().mk_true(), m().mk_iff_true(new_pr), new_d); + } + expr * atom; + if (is_shared_neg(new_curr, atom)) { + m_subst->insert(atom, m().mk_false(), m().mk_iff_false(new_pr), new_d); + } + expr * lhs, * value; + if (is_shared_eq(new_curr, lhs, value)) { + TRACE("shallow_context_simplifier_bug", tout << "found eq:\n" << mk_ismt2_pp(new_curr, m()) << "\n";); + m_subst->insert(lhs, value, new_pr, new_d); + } + } + + void process_current() { + expr * curr = m_goal->form(m_idx); + expr_ref new_curr(m()); + proof_ref new_pr(m()); + + if (!m_subst->empty()) { + m_r(curr, new_curr, new_pr); + } + else { + new_curr = curr; + if (m().proofs_enabled()) + new_pr = m().mk_reflexivity(curr); + } + + TRACE("shallow_context_simplifier_bug", tout << mk_ismt2_pp(curr, m()) << "\n---->\n" << mk_ismt2_pp(new_curr, m()) << "\n";); + push_result(new_curr, new_pr); + if (new_curr != curr) + m_modified = true; + } + + void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + mc = 0; pc = 0; core = 0; + tactic_report report("propagate-values", *g); + m_goal = g.get(); + + bool forward = true; + expr_ref new_curr(m()); + proof_ref new_pr(m()); + unsigned size = m_goal->size(); + m_idx = 0; + m_modified = false; + unsigned round = 0; + + if (m_goal->inconsistent()) + goto end; + + m_subst = alloc(expr_substitution, m(), g->unsat_core_enabled(), g->proofs_enabled()); + m_r.set_substitution(m_subst.get()); + m_occs(*m_goal); + + while (true) { + TRACE("propagate_values", m_goal->display(tout);); + if (forward) { + for (; m_idx < size; m_idx++) { + process_current(); + if (m_goal->inconsistent()) + goto end; + } + if (m_subst->empty() && !m_modified) + goto end; + m_occs(*m_goal); + m_idx = m_goal->size(); + forward = false; + m_subst->reset(); + m_r.set_substitution(m_subst.get()); // reset, but keep substitution + } + else { + while (m_idx > 0) { + m_idx--; + process_current(); + if (m_goal->inconsistent()) + goto end; + } + if (!m_modified) + goto end; + m_subst->reset(); + m_r.set_substitution(m_subst.get()); // reset, but keep substitution + m_modified = false; + m_occs(*m_goal); + m_idx = 0; + size = m_goal->size(); + forward = true; + } + round++; + if (round >= m_max_rounds) + break; + IF_VERBOSE(100, verbose_stream() << "starting new round, goal size: " << m_goal->num_exprs() << std::endl;); + TRACE("propgate_values", tout << "round finished\n"; m_goal->display(tout); tout << "\n";); + } + end: + m_goal->elim_redundancies(); + m_goal->inc_depth(); + result.push_back(m_goal); + SASSERT(m_goal->is_well_sorted()); + TRACE("propagate_values", m_goal->display(tout);); + m_goal = 0; + } + }; + + imp * m_imp; + params_ref m_params; +public: + propagate_values_tactic(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(propagate_values_tactic, m, m_params); + } + + virtual ~propagate_values_tactic() { + dealloc(m_imp); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + th_rewriter::get_param_descrs(r); + r.insert(":max-rounds", CPK_UINT, "(default: 2) maximum number of rounds."); + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + (*m_imp)(in, result, mc, pc, core); + } + + virtual void cleanup() { + ast_manager & m = m_imp->m(); + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + } + +protected: + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + +tactic * mk_propagate_values_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(propagate_values_tactic, m, p)); +} + diff --git a/lib/propagate_values_tactic.h b/lib/propagate_values_tactic.h new file mode 100644 index 000000000..6a5e5a1ef --- /dev/null +++ b/lib/propagate_values_tactic.h @@ -0,0 +1,29 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + propagate_values_tactic.h + +Abstract: + + Propagate values using equalities of the form (= t v) where v is a value, + and atoms t and (not t) + +Author: + + Leonardo de Moura (leonardo) 2011-12-28. + +Revision History: + +--*/ +#ifndef _PROPAGATE_VALUES_TACTIC_H_ +#define _PROPAGATE_VALUES_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_propagate_values_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/proto_model.cpp b/lib/proto_model.cpp new file mode 100644 index 000000000..34f017eb8 --- /dev/null +++ b/lib/proto_model.cpp @@ -0,0 +1,608 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + proto_model.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-03-08. + +Revision History: + +--*/ +#include"proto_model.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"var_subst.h" +#include"front_end_params.h" +#include"array_decl_plugin.h" +#include"well_sorted.h" +#include"used_symbols.h" +#include"model_v2_pp.h" + +proto_model::proto_model(ast_manager & m, simplifier & s, model_params const & p): + model_core(m), + m_params(p), + m_asts(m), + m_simplifier(s), + m_afid(m.get_family_id(symbol("array"))) { + register_factory(alloc(basic_factory, m)); + m_user_sort_factory = alloc(user_sort_factory, m); + register_factory(m_user_sort_factory); +} + +void proto_model::reset_finterp() { + decl2finterp::iterator it = m_finterp.begin(); + decl2finterp::iterator end = m_finterp.end(); + for (; it != end; ++it) { + dealloc(it->m_value); + } +} + +proto_model::~proto_model() { + reset_finterp(); +} + +void proto_model::register_decl(func_decl * d, expr * v) { + SASSERT(d->get_arity() == 0); + if (m_interp.contains(d)) { + DEBUG_CODE({ + expr * old_v = 0; + m_interp.find(d, old_v); + SASSERT(old_v == v); + }); + return; + } + SASSERT(!m_interp.contains(d)); + m_decls.push_back(d); + m_asts.push_back(d); + m_asts.push_back(v); + m_interp.insert(d, v); + m_const_decls.push_back(d); +} + +void proto_model::register_decl(func_decl * d, func_interp * fi, bool aux) { + SASSERT(d->get_arity() > 0); + SASSERT(!m_finterp.contains(d)); + m_decls.push_back(d); + m_asts.push_back(d); + m_finterp.insert(d, fi); + m_func_decls.push_back(d); + if (aux) + m_aux_decls.insert(d); +} + +/** + \brief Set new_fi as the new interpretation for f. + If f_aux != 0, then assign the old interpretation of f to f_aux. + If f_aux == 0, then delete the old interpretation of f. + + f_aux is marked as a auxiliary declaration. +*/ +void proto_model::reregister_decl(func_decl * f, func_interp * new_fi, func_decl * f_aux) { + func_interp * fi = get_func_interp(f); + if (fi == 0) { + register_decl(f, new_fi); + } + else { + if (f_aux != 0) { + register_decl(f_aux, fi); + m_aux_decls.insert(f_aux); + } + else { + dealloc(fi); + } + m_finterp.insert(f, new_fi); + } +} + +expr * proto_model::mk_some_interp_for(func_decl * d) { + SASSERT(!has_interpretation(d)); + expr * r = get_some_value(d->get_range()); // if t is a function, then it will be the constant function. + if (d->get_arity() == 0) { + register_decl(d, r); + } + else { + func_interp * new_fi = alloc(func_interp, m_manager, d->get_arity()); + new_fi->set_else(r); + register_decl(d, new_fi); + } + return r; +} + +/** + \brief Evaluate the expression e in the current model, and store the result in \c result. + It returns \c true if succeeded, and false otherwise. If the evaluation fails, + then r contains a term that is simplified as much as possible using the interpretations + available in the model. + + When model_completion == true, if the model does not assign an interpretation to a + declaration it will build one for it. Moreover, partial functions will also be completed. + So, if model_completion == true, the evaluator never fails if it doesn't contain quantifiers. +*/ +bool proto_model::eval(expr * e, expr_ref & result, bool model_completion) { + bool is_ok = true; + SASSERT(is_well_sorted(m_manager, e)); + TRACE("model_eval", tout << mk_pp(e, m_manager) << "\n"; + tout << "sort: " << mk_pp(m_manager.get_sort(e), m_manager) << "\n";); + + obj_map eval_cache; + expr_ref_vector trail(m_manager); + sbuffer, 128> todo; + ptr_buffer args; + expr * null = static_cast(0); + todo.push_back(std::make_pair(e, null)); + + expr * a; + expr * expanded_a; + while (!todo.empty()) { + std::pair & p = todo.back(); + a = p.first; + expanded_a = p.second; + if (expanded_a != 0) { + expr * r = 0; + eval_cache.find(expanded_a, r); + SASSERT(r != 0); + todo.pop_back(); + eval_cache.insert(a, r); + TRACE("model_eval", + tout << "orig:\n" << mk_pp(a, m_manager) << "\n"; + tout << "after beta reduction:\n" << mk_pp(expanded_a, m_manager) << "\n"; + tout << "new:\n" << mk_pp(r, m_manager) << "\n";); + } + else { + switch(a->get_kind()) { + case AST_APP: { + app * t = to_app(a); + bool visited = true; + args.reset(); + unsigned num_args = t->get_num_args(); + for (unsigned i = 0; i < num_args; ++i) { + expr * arg = 0; + if (!eval_cache.find(t->get_arg(i), arg)) { + visited = false; + todo.push_back(std::make_pair(t->get_arg(i), null)); + } + else { + args.push_back(arg); + } + } + if (!visited) { + continue; + } + SASSERT(args.size() == t->get_num_args()); + expr_ref new_t(m_manager); + func_decl * f = t->get_decl(); + + if (!has_interpretation(f)) { + // the model does not assign an interpretation to f. + SASSERT(new_t.get() == 0); + if (f->get_family_id() == null_family_id) { + if (model_completion) { + // create an interpretation for f. + new_t = mk_some_interp_for(f); + } + else { + is_ok = false; + } + } + if (new_t.get() == 0) { + // t is interpreted or model completion is disabled. + m_simplifier.mk_app(f, num_args, args.c_ptr(), new_t); + trail.push_back(new_t); + if (!is_app(new_t) || to_app(new_t)->get_decl() != f) { + // if the result is not of the form (f ...), then assume we must simplify it. + expr * new_new_t = 0; + if (!eval_cache.find(new_t.get(), new_new_t)) { + todo.back().second = new_t; + todo.push_back(std::make_pair(new_t, null)); + continue; + } + else { + new_t = new_new_t; + } + } + } + } + else { + // the model has an interpretaion for f. + if (num_args == 0) { + // t is a constant + new_t = get_const_interp(f); + } + else { + // t is a function application + SASSERT(new_t.get() == 0); + // try to use function graph first + func_interp * fi = get_func_interp(f); + SASSERT(fi->get_arity() == num_args); + expr_ref r1(m_manager); + // fi may be partial... + if (!fi->eval(m_simplifier, args.c_ptr(), r1)) { + SASSERT(fi->is_partial()); // fi->eval only fails when fi is partial. + if (model_completion) { + expr * r = get_some_value(f->get_range()); + fi->set_else(r); + SASSERT(!fi->is_partial()); + new_t = r; + } + else { + // f is an uninterpreted function, there is no need to use m_simplifier.mk_app + new_t = m_manager.mk_app(f, num_args, args.c_ptr()); + trail.push_back(new_t); + is_ok = false; + } + } + else { + SASSERT(r1); + trail.push_back(r1); + expr * r2 = 0; + if (!eval_cache.find(r1.get(), r2)) { + todo.back().second = r1; + todo.push_back(std::make_pair(r1, null)); + continue; + } + else { + new_t = r2; + } + } + } + } + TRACE("model_eval", + tout << "orig:\n" << mk_pp(t, m_manager) << "\n"; + tout << "new:\n" << mk_pp(new_t, m_manager) << "\n";); + todo.pop_back(); + SASSERT(new_t.get() != 0); + eval_cache.insert(t, new_t); + break; + } + case AST_VAR: + SASSERT(a != 0); + eval_cache.insert(a, a); + todo.pop_back(); + break; + case AST_QUANTIFIER: + is_ok = false; // evaluator does not handle quantifiers. + SASSERT(a != 0); + eval_cache.insert(a, a); + todo.pop_back(); + break; + default: + UNREACHABLE(); + break; + } + } + } + + if (!eval_cache.find(e, a)) { + TRACE("model_eval", tout << "FAILED e: " << mk_bounded_pp(e, m_manager) << "\n";); + UNREACHABLE(); + } + + result = a; + TRACE("model_eval", + ast_ll_pp(tout << "original: ", m_manager, e); + ast_ll_pp(tout << "evaluated: ", m_manager, a); + ast_ll_pp(tout << "reduced: ", m_manager, result.get()); + tout << "sort: " << mk_pp(m_manager.get_sort(e), m_manager) << "\n"; + ); + SASSERT(is_well_sorted(m_manager, result.get())); + return is_ok; +} + +/** + \brief Replace uninterpreted constants occurring in fi->get_else() + by their interpretations. +*/ +void proto_model::cleanup_func_interp(func_interp * fi, func_decl_set & found_aux_fs) { + if (fi->is_partial()) + return; + expr * fi_else = fi->get_else(); + TRACE("model_bug", tout << "cleaning up:\n" << mk_pp(fi_else, m_manager) << "\n";); + + obj_map cache; + expr_ref_vector trail(m_manager); + ptr_buffer todo; + ptr_buffer args; + todo.push_back(fi_else); + + expr * a; + while (!todo.empty()) { + a = todo.back(); + if (is_uninterp_const(a)) { + todo.pop_back(); + func_decl * a_decl = to_app(a)->get_decl(); + expr * ai = get_const_interp(a_decl); + if (ai == 0) { + ai = get_some_value(a_decl->get_range()); + register_decl(a_decl, ai); + } + cache.insert(a, ai); + } + else { + switch(a->get_kind()) { + case AST_APP: { + app * t = to_app(a); + bool visited = true; + args.reset(); + unsigned num_args = t->get_num_args(); + for (unsigned i = 0; i < num_args; ++i) { + expr * arg = 0; + if (!cache.find(t->get_arg(i), arg)) { + visited = false; + todo.push_back(t->get_arg(i)); + } + else { + args.push_back(arg); + } + } + if (!visited) { + continue; + } + func_decl * f = t->get_decl(); + if (m_aux_decls.contains(f)) + found_aux_fs.insert(f); + expr_ref new_t(m_manager); + m_simplifier.mk_app(f, num_args, args.c_ptr(), new_t); + if (t != new_t.get()) + trail.push_back(new_t); + todo.pop_back(); + cache.insert(t, new_t); + break; + } + default: + SASSERT(a != 0); + cache.insert(a, a); + todo.pop_back(); + break; + } + } + } + + if (!cache.find(fi_else, a)) { + UNREACHABLE(); + } + + fi->set_else(a); +} + +void proto_model::remove_aux_decls_not_in_set(ptr_vector & decls, func_decl_set const & s) { + unsigned sz = decls.size(); + unsigned i = 0; + unsigned j = 0; + for (; i < sz; i++) { + func_decl * f = decls[i]; + if (!m_aux_decls.contains(f) || s.contains(f)) { + decls[j] = f; + j++; + } + } + decls.shrink(j); +} + + +/** + \brief Replace uninterpreted constants occurring in the func_interp's get_else() + by their interpretations. +*/ +void proto_model::cleanup() { + func_decl_set found_aux_fs; + decl2finterp::iterator it = m_finterp.begin(); + decl2finterp::iterator end = m_finterp.end(); + for (; it != end; ++it) { + func_interp * fi = (*it).m_value; + cleanup_func_interp(fi, found_aux_fs); + } + + // remove auxiliary declarations that are not used. + if (found_aux_fs.size() != m_aux_decls.size()) { + remove_aux_decls_not_in_set(m_decls, found_aux_fs); + remove_aux_decls_not_in_set(m_func_decls, found_aux_fs); + + func_decl_set::iterator it2 = m_aux_decls.begin(); + func_decl_set::iterator end2 = m_aux_decls.end(); + for (; it2 != end2; ++it2) { + func_decl * faux = *it2; + if (!found_aux_fs.contains(faux)) { + TRACE("cleanup_bug", tout << "eliminating " << faux->get_name() << "\n";); + func_interp * fi = 0; + m_finterp.find(faux, fi); + SASSERT(fi != 0); + m_finterp.erase(faux); + dealloc(fi); + } + } + m_aux_decls.swap(found_aux_fs); + } +} + +value_factory * proto_model::get_factory(family_id fid) { + return m_factories.get_plugin(fid); +} + +void proto_model::freeze_universe(sort * s) { + SASSERT(m_manager.is_uninterp(s)); + m_user_sort_factory->freeze_universe(s); +} + +/** + \brief Return the known universe of an uninterpreted sort. +*/ +obj_hashtable const & proto_model::get_known_universe(sort * s) const { + SASSERT(m_manager.is_uninterp(s)); + return m_user_sort_factory->get_known_universe(s); +} + +ptr_vector const & proto_model::get_universe(sort * s) const { + ptr_vector & tmp = const_cast(this)->m_tmp; + tmp.reset(); + obj_hashtable const & u = get_known_universe(s); + obj_hashtable::iterator it = u.begin(); + obj_hashtable::iterator end = u.end(); + for (; it != end; ++it) + tmp.push_back(*it); + return tmp; +} + +unsigned proto_model::get_num_uninterpreted_sorts() const { + return m_user_sort_factory->get_num_sorts(); +} + +sort * proto_model::get_uninterpreted_sort(unsigned idx) const { + SASSERT(idx < get_num_uninterpreted_sorts()); + return m_user_sort_factory->get_sort(idx); +} + +/** + \brief Return true if the given sort is uninterpreted and has a finite interpretation + in the model. +*/ +bool proto_model::is_finite(sort * s) const { + return m_manager.is_uninterp(s) && m_user_sort_factory->is_finite(s); +} + +expr * proto_model::get_some_value(sort * s) { + family_id fid = s->get_family_id(); + if (fid == null_family_id) { + return m_user_sort_factory->get_some_value(s); + } + value_factory * f = get_factory(fid); + if (f) + return f->get_some_value(s); + // there is no factory for the family id, then assume s is uninterpreted. + return m_user_sort_factory->get_some_value(s); +} + +bool proto_model::get_some_values(sort * s, expr_ref & v1, expr_ref & v2) { + family_id fid = s->get_family_id(); + if (fid == null_family_id) { + return m_user_sort_factory->get_some_values(s, v1, v2); + } + value_factory * f = get_factory(fid); + if (f) + return f->get_some_values(s, v1, v2); + else + return false; +} + +expr * proto_model::get_fresh_value(sort * s) { + family_id fid = s->get_family_id(); + if (fid == null_family_id) + return m_user_sort_factory->get_fresh_value(s); + value_factory * f = get_factory(fid); + if (f) + return f->get_fresh_value(s); + else + // Use user_sort_factory if the theory has no support for model construnction. + // This is needed when dummy theories are used for arithmetic or arrays. + return m_user_sort_factory->get_fresh_value(s); +} + +void proto_model::register_value(expr * n) { + sort * s = m_manager.get_sort(n); + family_id fid = s->get_family_id(); + if (fid == null_family_id) { + m_user_sort_factory->register_value(n); + } + else { + value_factory * f = get_factory(fid); + if (f) + f->register_value(n); + } +} + +bool proto_model::is_array_value(expr * v) const { + return is_app_of(v, m_afid, OP_AS_ARRAY); +} + +void proto_model::compress() { + ptr_vector::iterator it = m_func_decls.begin(); + ptr_vector::iterator end = m_func_decls.end(); + for (; it != end; ++it) { + func_decl * f = *it; + func_interp * fi = get_func_interp(f); + SASSERT(fi != 0); + fi->compress(); + } +} + +/** + \brief Complete the interpretation fi of f if it is partial. + If f does not have an interpretation in the given model, then this is a noop. +*/ +void proto_model::complete_partial_func(func_decl * f) { + func_interp * fi = get_func_interp(f); + if (fi && fi->is_partial()) { + expr * else_value = 0; +#if 0 + // For UFBV benchmarks, setting the "else" to false is not a good idea. + // TODO: find a permanent solution. A possibility is to add another option. + if (m_manager.is_bool(f->get_range())) { + else_value = m_manager.mk_false(); + } + else { + else_value = fi->get_max_occ_result(); + if (else_value == 0) + else_value = get_some_value(f->get_range()); + } +#else + else_value = fi->get_max_occ_result(); + if (else_value == 0) + else_value = get_some_value(f->get_range()); +#endif + fi->set_else(else_value); + } +} + +/** + \brief Set the (else) field of function interpretations... +*/ +void proto_model::complete_partial_funcs() { + if (m_params.m_model_partial) + return; + + ptr_vector::iterator it = m_func_decls.begin(); + ptr_vector::iterator end = m_func_decls.end(); + for (; it != end; ++it) + complete_partial_func(*it); +} + +model * proto_model::mk_model() { + TRACE("proto_model", tout << "mk_model\n"; model_v2_pp(tout, *this);); + model * m = alloc(model, m_manager); + + decl2expr::iterator it1 = m_interp.begin(); + decl2expr::iterator end1 = m_interp.end(); + for (; it1 != end1; ++it1) { + m->register_decl(it1->m_key, it1->m_value); + } + + decl2finterp::iterator it2 = m_finterp.begin(); + decl2finterp::iterator end2 = m_finterp.end(); + for (; it2 != end2; ++it2) { + m->register_decl(it2->m_key, it2->m_value); + } + m_finterp.reset(); // m took the ownership of the func_interp's + + unsigned sz = get_num_uninterpreted_sorts(); + for (unsigned i = 0; i < sz; i++) { + sort * s = get_uninterpreted_sort(i); + TRACE("proto_model", tout << "copying uninterpreted sorts...\n" << mk_pp(s, m_manager) << "\n";); + ptr_buffer buf; + obj_hashtable const & u = get_known_universe(s); + obj_hashtable::iterator it = u.begin(); + obj_hashtable::iterator end = u.end(); + for (; it != end; ++it) { + buf.push_back(*it); + } + m->register_usort(s, buf.size(), buf.c_ptr()); + } + + return m; +} diff --git a/lib/proto_model.h b/lib/proto_model.h new file mode 100644 index 000000000..bd5609f33 --- /dev/null +++ b/lib/proto_model.h @@ -0,0 +1,119 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + proto_model.h + +Abstract: + + This is the old model object. + smt::context used it during model construction and for + reporting the model for external consumers. + The whole value_factory "business" was due to model construction + and unnecessary for external consumers. + Future solvers will not use value_factory objects for + helping during model construction. + + After smt::context finishes building the model, it is converted + into a new (light) model object. + +Author: + + Leonardo de Moura (leonardo) 2007-03-08. + +Revision History: + +--*/ +#ifndef _PROTO_MODEL_H_ +#define _PROTO_MODEL_H_ + +#include"model_core.h" +#include"model_params.h" +#include"value_factory.h" +#include"plugin_manager.h" +#include"simplifier.h" +#include"arith_decl_plugin.h" +#include"func_decl_dependencies.h" +#include"model.h" + +class proto_model : public model_core { + model_params const & m_params; + ast_ref_vector m_asts; + plugin_manager m_factories; + user_sort_factory * m_user_sort_factory; + simplifier & m_simplifier; + family_id m_afid; //!< array family id: hack for displaying models in V1.x style + func_decl_set m_aux_decls; + ptr_vector m_tmp; + + void reset_finterp(); + + expr * mk_some_interp_for(func_decl * d); + + // Invariant: m_decls subset m_func_decls union m_const_decls union union m_value_decls + // Invariant: m_func_decls subset m_decls + // Invariant: m_const_decls subset m_decls + + void remove_aux_decls_not_in_set(ptr_vector & decls, func_decl_set const & s); + void cleanup_func_interp(func_interp * fi, func_decl_set & found_aux_fs); + + +public: + proto_model(ast_manager & m, simplifier & s, model_params const & p); + virtual ~proto_model(); + + model_params const & get_model_params() const { return m_params; } + + void register_factory(value_factory * f) { m_factories.register_plugin(f); } + + bool eval(expr * e, expr_ref & result, bool model_completion = false); + + bool is_array_value(expr * v) const; + + value_factory * get_factory(family_id fid); + + expr * get_some_value(sort * s); + + bool get_some_values(sort * s, expr_ref & v1, expr_ref & v2); + + expr * get_fresh_value(sort * s); + + void register_value(expr * n); + + // + // Primitives for building models + // + void register_decl(func_decl * d, expr * v); + void register_decl(func_decl * f, func_interp * fi, bool aux = false); + void reregister_decl(func_decl * f, func_interp * new_fi, func_decl * f_aux); + void compress(); + void cleanup(); + + // + // Primitives for building finite interpretations for uninterpreted sorts, + // and retrieving the known universe. + // + void freeze_universe(sort * s); + bool is_finite(sort * s) const; + obj_hashtable const & get_known_universe(sort * s) const; + virtual ptr_vector const & get_universe(sort * s) const; + virtual unsigned get_num_uninterpreted_sorts() const; + virtual sort * get_uninterpreted_sort(unsigned idx) const; + + // + // Complete partial function interps + // + void complete_partial_func(func_decl * f); + void complete_partial_funcs(); + + // + // Create final model object. + // proto_model is corrupted after that. + model * mk_model(); +}; + +typedef ref proto_model_ref; + +#endif /* _PROTO_MODEL_H_ */ + diff --git a/lib/ptr_scoped_buffer.h b/lib/ptr_scoped_buffer.h new file mode 100644 index 000000000..43a9eb19c --- /dev/null +++ b/lib/ptr_scoped_buffer.h @@ -0,0 +1,56 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + ptr_scoped_buffer.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2011-03-03. + +Revision History: + +--*/ +#ifndef _PTR_SCOPED_BUFFER_H_ +#define _PTR_SCOPED_BUFFER_H_ + +#include"util.h" +#include"debug.h" +#include"buffer.h" + +template > +class ptr_scoped_buffer : private ptr_buffer { + D m_deallocator; + void deallocate_all() { + typename ptr_buffer::iterator it = ptr_buffer::begin(); + typename ptr_buffer::iterator end = ptr_buffer::end(); + for (; it != end; ++it) + m_deallocator(*it); + } +public: + typedef typename ptr_buffer::const_iterator const_iterator; + ptr_scoped_buffer(D const & m = D()):ptr_buffer(), m_deallocator(m) {} + ~ptr_scoped_buffer() { deallocate_all(); } + void reset() { deallocate_all(); ptr_buffer::reset(); } + void finalize() { deallocate_all(); ptr_buffer::finalize(); } + /** \brief Release ownership of the pointers stored in the buffer */ + void release() { ptr_buffer::reset(); } + unsigned size() const { return ptr_buffer::size(); } + bool empty() const { return ptr_buffer::empty(); } + const_iterator begin() const { return ptr_buffer::begin(); } + const_iterator end() const { return ptr_buffer::end(); } + void push_back(T * elem) { return ptr_buffer::push_back(elem); } + T * back() const { return ptr_buffer::back(); } + void pop_back() { m_deallocator(back()); ptr_buffer::pop_back(); } + T * get(unsigned idx) const { return ptr_buffer::get(idx); } + void set(unsigned idx, T * e) { T * old_e = get(idx); if (e != old_e) m_deallocator(old_e); ptr_buffer::set(idx, e); } + void append(unsigned n, T * const * elems) { ptr_buffer::append(n, elems); } +}; + +#endif + diff --git a/lib/pull_ite_tree.cpp b/lib/pull_ite_tree.cpp new file mode 100644 index 000000000..080dd5545 --- /dev/null +++ b/lib/pull_ite_tree.cpp @@ -0,0 +1,213 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + pull_ite_tree.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-22. + +Revision History: + +--*/ +#include"pull_ite_tree.h" +#include"recurse_expr_def.h" +#include"for_each_expr.h" +#include"ast_pp.h" + +pull_ite_tree::pull_ite_tree(ast_manager & m, simplifier & s): + m_manager(m), + m_simplifier(s), + m_cache(m) { +} + +void pull_ite_tree::cache_result(expr * n, expr * r, proof * pr) { + m_cache.insert(n, r, pr); +} + +void pull_ite_tree::visit(expr * n, bool & visited) { + if (!is_cached(n)) { + m_todo.push_back(n); + visited = false; + } +} + +bool pull_ite_tree::visit_children(expr * n) { + if (m_manager.is_ite(n)) { + bool visited = true; + visit(to_app(n)->get_arg(1), visited); + visit(to_app(n)->get_arg(2), visited); + return visited; + } + else { + return true; + } +} + +void pull_ite_tree::reduce(expr * n) { + // Remark: invoking the simplifier to build the new expression saves a lot of memory. + if (m_manager.is_ite(n)) { + expr * c = to_app(n)->get_arg(0); + expr * t_old = to_app(n)->get_arg(1); + expr * e_old = to_app(n)->get_arg(2); + expr * t = 0; + proof * t_pr = 0; + expr * e = 0; + proof * e_pr = 0; + get_cached(t_old, t, t_pr); + get_cached(e_old, e, e_pr); + expr_ref r(m_manager); + expr * args[3] = {c, t, e}; + m_simplifier.mk_app(to_app(n)->get_decl(), 3, args, r); + if (!m_manager.proofs_enabled()) { + // expr * r = m_manager.mk_ite(c, t, e); + cache_result(n, r, 0); + } + else { + // t_pr is a proof for (m_p ... t_old ...) == t + // e_pr is a proof for (m_p ... e_old ...) == e + expr_ref old(m_manager); + expr_ref p_t_old(m_manager); + expr_ref p_e_old(m_manager); + old = mk_p_arg(n); // (m_p ... n ...) where n is (ite c t_old e_old) + p_t_old = mk_p_arg(t_old); // (m_p ... t_old ...) + p_e_old = mk_p_arg(e_old); // (m_p ... e_old ...) + expr_ref tmp1(m_manager); + tmp1 = m_manager.mk_ite(c, p_t_old, p_e_old); // (ite c (m_p ... t_old ...) (m_p ... e_old ...)) + proof * pr1 = m_manager.mk_rewrite(old, tmp1); // proof for (m_p ... (ite c t_old e_old) ...) = (ite c (m_p ... t_old ...) (m_p ... e_old ...)) + expr_ref tmp2(m_manager); + tmp2 = m_manager.mk_ite(c, t, e); // (ite c t e) + proof * pr2 = 0; // it will contain a proof for (ite c (m_p ... t_old ...) (m_p ... e_old ...)) = (ite c t e) + proof * pr3 = 0; // it will contain a proof for (m_p ... (ite c t_old e_old) ...) = (ite c t e) + proof * proofs[2]; + unsigned num_proofs = 0; + if (t_pr != 0) { + proofs[num_proofs] = t_pr; + num_proofs++; + } + if (e_pr != 0) { + proofs[num_proofs] = e_pr; + num_proofs++; + } + if (num_proofs > 0) { + pr2 = m_manager.mk_congruence(to_app(tmp1), to_app(tmp2), num_proofs, proofs); + pr3 = m_manager.mk_transitivity(pr1, pr2); + } + else { + pr3 = pr1; + } + proof * pr4 = 0; // it will contain a proof for (ite c t e) = r + proof * pr5 = 0; // it will contain a proof for (m_p ... (ite c t_old e_old) ...) = r + if (tmp2 != r) { + pr4 = m_manager.mk_rewrite(tmp2, r); + pr5 = m_manager.mk_transitivity(pr3, pr4); + } + else { + pr5 = pr3; + } + cache_result(n, r, pr5); + } + } + else { + expr_ref r(m_manager); + m_args[m_arg_idx] = n; + m_simplifier.mk_app(m_p, m_args.size(), m_args.c_ptr(), r); + if (!m_manager.proofs_enabled()) { + // expr * r = m_manager.mk_app(m_p, m_args.size(), m_args.c_ptr()); + cache_result(n, r, 0); + } + else { + expr_ref old(m_manager); + proof * p; + old = mk_p_arg(n); + if (old == r) + p = 0; + else + p = m_manager.mk_rewrite(old, r); + cache_result(n, r, p); + } + } +} + +void pull_ite_tree::operator()(app * n, app_ref & r, proof_ref & pr) { + unsigned num_args = n->get_num_args(); + m_args.resize(num_args); + m_p = n->get_decl(); + expr * ite = 0; + for (unsigned i = 0; i < num_args; i++) { + expr * arg = n->get_arg(i); + if (ite) { + m_args[i] = arg; + } + else if (m_manager.is_ite(arg)) { + m_arg_idx = i; + m_args[i] = 0; + ite = arg; + } + else { + m_args[i] = arg; + } + } + if (!ite) { + r = n; + pr = 0; + return; + } + m_todo.push_back(ite); + while (!m_todo.empty()) { + expr * n = m_todo.back(); + if (is_cached(n)) + m_todo.pop_back(); + else if (visit_children(n)) { + m_todo.pop_back(); + reduce(n); + } + } + SASSERT(is_cached(ite)); + expr * _r = 0; + proof * _pr = 0; + get_cached(ite, _r, _pr); + r = to_app(_r); + pr = _pr; + m_cache.reset(); + m_todo.reset(); +} + +pull_ite_tree_star::pull_ite_tree_star(ast_manager & m, simplifier & s): + simplifier(m), + m_proc(m, s) { + borrow_plugins(s); +} + +bool pull_ite_tree_star::get_subst(expr * n, expr_ref & r, proof_ref & p) { + if (is_app(n) && is_target(to_app(n))) { + app_ref tmp(m_manager); + m_proc(to_app(n), tmp, p); + r = tmp; + return true; + } + return false; +} + +bool pull_cheap_ite_tree_star::is_target(app * n) const { + bool r = + n->get_num_args() == 2 && + n->get_family_id() != null_family_id && + m_manager.is_bool(n) && + (m_manager.is_value(n->get_arg(0)) || m_manager.is_value(n->get_arg(1))) && + (m_manager.is_term_ite(n->get_arg(0)) || m_manager.is_term_ite(n->get_arg(1))); + TRACE("pull_ite_target", tout << mk_pp(n, m_manager) << "\nresult: " << r << "\n";); + return r; +} + + + + + + diff --git a/lib/pull_ite_tree.h b/lib/pull_ite_tree.h new file mode 100644 index 000000000..9350b7339 --- /dev/null +++ b/lib/pull_ite_tree.h @@ -0,0 +1,101 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + pull_ite_tree.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-22. + +Revision History: + +--*/ +#ifndef _PULL_ITE_TREE_H_ +#define _PULL_ITE_TREE_H_ + +#include"ast.h" +#include"simplifier.h" +#include"recurse_expr.h" +#include"obj_hashtable.h" +#include"expr_map.h" + +/** + \brief Functor for applying the following transformation + F[(p (ite c t1 t2) args)] = F'[(ite c t1 t2), p, args] + + F'[(ite c t1 t2), p, args] = (ite c F'[t1, p, args] F'[t2, p, args]) + F'[t, p, args] = (p t args) +*/ +class pull_ite_tree { + ast_manager & m_manager; + simplifier & m_simplifier; + func_decl * m_p; + ptr_vector m_args; + unsigned m_arg_idx; //!< position of the ite argument + expr_map m_cache; + ptr_vector m_todo; + + bool is_cached(expr * n) const { return m_cache.contains(n); } + void get_cached(expr * n, expr * & r, proof * & p) const { m_cache.get(n, r, p); } + void cache_result(expr * n, expr * r, proof * pr); + void visit(expr * n, bool & visited); + bool visit_children(expr * n); + void reduce(expr * n); + /** + \brief Creante an application (m_p ... n ...) where n is the argument m_arg_idx and the other arguments + are in m_args. + */ + expr * mk_p_arg(expr * n) { + m_args[m_arg_idx] = n; + return m_manager.mk_app(m_p, m_args.size(), m_args.c_ptr()); + } +public: + pull_ite_tree(ast_manager & m, simplifier & s); + /** + \brief Apply the transformation above if n contains an ite-expression. + Store the result in r. If n does not contain an ite-expression, then + store n in r. + + When proof generation is enabled, pr is a proof for n = r. + */ + void operator()(app * n, app_ref & r, proof_ref & pr); +}; + +/** + \brief Functor for applying the pull_ite_tree on subexpressions n that + satisfy the is_target virtual predicate. +*/ +class pull_ite_tree_star : public simplifier { +protected: + pull_ite_tree m_proc; + virtual bool get_subst(expr * n, expr_ref & r, proof_ref & p); +public: + pull_ite_tree_star(ast_manager & m, simplifier & s); + virtual ~pull_ite_tree_star() { release_plugins(); } + virtual bool is_target(app * n) const = 0; +}; + +/** + \brief Apply pull_ite_tree on predicates of the form + (p ite v) and (p v ite) + + where: + - p is an interpreted predicate + - ite is an ite-term expression + - v is a value +*/ +class pull_cheap_ite_tree_star : public pull_ite_tree_star { +public: + pull_cheap_ite_tree_star(ast_manager & m, simplifier & s):pull_ite_tree_star(m, s) {} + virtual ~pull_cheap_ite_tree_star() {} + virtual bool is_target(app * n) const; +}; + +#endif /* _PULL_ITE_TREE_H_ */ + diff --git a/lib/pull_quant.cpp b/lib/pull_quant.cpp new file mode 100644 index 000000000..fa531c73a --- /dev/null +++ b/lib/pull_quant.cpp @@ -0,0 +1,385 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + pull_quant.cpp + +Abstract: + + Pull nested quantifiers. + +Author: + + Leonardo (leonardo) 2008-01-20 + +Notes: + +--*/ +#include"pull_quant.h" +#include"ast_pp.h" +#include"for_each_expr.h" + +void pull_quant::pull_quant1(func_decl * d, unsigned num_children, expr * const * children, expr_ref & result) { + ptr_buffer var_sorts; + buffer var_names; + symbol qid; + int w = INT_MAX; + + // The input formula is in Skolem normal form... + // So all children are forall (positive context) or exists (negative context). + // Remark: (AND a1 ...) may be represented (NOT (OR (NOT a1) ...))) + // So, when pulling a quantifier over a NOT, it becomes an exists. + + if (m_manager.is_not(d)) { + SASSERT(num_children == 1); + expr * child = children[0]; + if (is_quantifier(child)) { + quantifier * q = to_quantifier(child); + expr * body = q->get_expr(); + result = m_manager.update_quantifier(q, !q->is_forall(), m_manager.mk_not(body)); + } + else { + result = m_manager.mk_not(child); + } + return; + } + + bool found_quantifier = false; + bool forall_children; + + for (unsigned i = 0; i < num_children; i++) { + expr * child = children[i]; + if (is_quantifier(child)) { + + if (!found_quantifier) { + found_quantifier = true; + forall_children = is_forall(child); + } + else { + // Since the initial formula was in SNF, all children must be EXISTS or FORALL. + SASSERT(forall_children == is_forall(child)); + } + + quantifier * nested_q = to_quantifier(child); + if (var_sorts.empty()) { + // use the qid of one of the nested quantifiers. + qid = nested_q->get_qid(); + } + w = std::min(w, nested_q->get_weight()); + unsigned j = nested_q->get_num_decls(); + while (j > 0) { + --j; + var_sorts.push_back(nested_q->get_decl_sort(j)); + symbol s = nested_q->get_decl_name(j); + if (std::find(var_names.begin(), var_names.end(), s) != var_names.end()) + var_names.push_back(m_manager.mk_fresh_var_name(s.is_numerical() ? 0 : s.bare_str())); + else + var_names.push_back(s); + } + } + } + + if (!var_sorts.empty()) { + SASSERT(found_quantifier); + // adjust the variable ids in formulas in new_children + expr_ref_buffer new_adjusted_children(m_manager); + expr_ref adjusted_child(m_manager); + unsigned num_decls = var_sorts.size(); + unsigned shift_amount = 0; + TRACE("pull_quant", tout << "Result num decls:" << num_decls << "\n";); + for (unsigned i = 0; i < num_children; i++) { + expr * child = children[i]; + if (!is_quantifier(child)) { + // increment the free variables in child by num_decls because + // child will be in the scope of num_decls bound variables. + m_shift(child, num_decls, adjusted_child); + TRACE("pull_quant", tout << "shifted by: " << num_decls << "\n" << + mk_pp(child, m_manager) << "\n---->\n" << mk_pp(adjusted_child, m_manager) << "\n";); + } + else { + quantifier * nested_q = to_quantifier(child); + SASSERT(num_decls >= nested_q->get_num_decls()); + // Assume nested_q is of the form + // forall xs. P(xs, ys) + // where xs (ys) represents the set of bound (free) variables. + // + // - the index of the variables xs must be increased by shift_amount. + // That is, the number of new bound variables that will precede the bound + // variables xs. + // + // - the index of the variables ys must be increased by num_decls - nested_q->get_num_decls. + // That is, the total number of new bound variables that will be in the scope + // of nested_q->get_expr(). + m_shift(nested_q->get_expr(), + nested_q->get_num_decls(), // bound for shift1/shift2 + num_decls - nested_q->get_num_decls(), // shift1 (shift by this ammount if var idx >= bound) + shift_amount, // shift2 (shift by this ammount if var idx < bound) + adjusted_child); + TRACE("pull_quant", tout << "shifted bound: " << nested_q->get_num_decls() << " shift1: " << shift_amount << + " shift2: " << (num_decls - nested_q->get_num_decls()) << "\n" << mk_pp(nested_q->get_expr(), m_manager) << + "\n---->\n" << mk_pp(adjusted_child, m_manager) << "\n";); + shift_amount += nested_q->get_num_decls(); + } + new_adjusted_children.push_back(adjusted_child); + } + + // Remark: patterns are ignored. + // This is ok, since this functor is used in one of the following cases: + // + // 1) Superposition calculus is being used, so the + // patterns are useless. + // + // 2) No patterns were provided, and the functor is used + // to increase the effectiveness of the pattern inference + // procedure. + // + // 3) MBQI + std::reverse(var_sorts.begin(), var_sorts.end()); + std::reverse(var_names.begin(), var_names.end()); + result = m_manager.mk_quantifier(forall_children, + var_sorts.size(), + var_sorts.c_ptr(), + var_names.c_ptr(), + m_manager.mk_app(d, new_adjusted_children.size(), new_adjusted_children.c_ptr()), + w, + qid); + } + else { + SASSERT(!found_quantifier); + result = m_manager.mk_app(d, num_children, children); + } +} + +void pull_quant::pull_quant1(quantifier * q, expr * new_expr, expr_ref & result) { + // The original formula was in SNF, so the original quantifiers must be universal. + SASSERT(is_forall(q)); + if (is_forall(new_expr)) { + quantifier * nested_q = to_quantifier(new_expr); + ptr_buffer var_sorts; + buffer var_names; + var_sorts.append(q->get_num_decls(), const_cast(q->get_decl_sorts())); + var_sorts.append(nested_q->get_num_decls(), const_cast(nested_q->get_decl_sorts())); + var_names.append(q->get_num_decls(), const_cast(q->get_decl_names())); + var_names.append(nested_q->get_num_decls(), const_cast(nested_q->get_decl_names())); + // Remark: patterns are ignored. + // See comment in reduce1_app + result = m_manager.mk_forall(var_sorts.size(), + var_sorts.c_ptr(), + var_names.c_ptr(), + nested_q->get_expr(), + std::min(q->get_weight(), nested_q->get_weight()), + q->get_qid()); + } + else { + SASSERT(!is_quantifier(new_expr)); + result = m_manager.update_quantifier(q, new_expr); + } +} + +void pull_quant::pull_quant1(expr * n, expr_ref & result) { + if (is_app(n)) + pull_quant1(to_app(n)->get_decl(), to_app(n)->get_num_args(), to_app(n)->get_args(), result); + else if (is_quantifier(n)) + pull_quant1(to_quantifier(n), to_quantifier(n)->get_expr(), result); + else + result = n; +} + +// Code for proof generation... +void pull_quant::pull_quant2(expr * n, expr_ref & r, proof_ref & pr) { + pr = 0; + if (is_app(n)) { + expr_ref_buffer new_args(m_manager); + expr_ref new_arg(m_manager); + ptr_buffer proofs; + unsigned num = to_app(n)->get_num_args(); + for (unsigned i = 0; i < num; i++) { + expr * arg = to_app(n)->get_arg(i); + pull_quant1(arg , new_arg); + new_args.push_back(new_arg); + if (new_arg != arg) + proofs.push_back(m_manager.mk_pull_quant(arg, to_quantifier(new_arg))); + } + pull_quant1(to_app(n)->get_decl(), new_args.size(), new_args.c_ptr(), r); + if (m_manager.fine_grain_proofs()) { + app * r1 = m_manager.mk_app(to_app(n)->get_decl(), new_args.size(), new_args.c_ptr()); + proof * p1 = proofs.empty() ? 0 : m_manager.mk_congruence(to_app(n), r1, proofs.size(), proofs.c_ptr()); + proof * p2 = r1 == r ? 0 : m_manager.mk_pull_quant(r1, to_quantifier(r)); + pr = m_manager.mk_transitivity(p1, p2); + } + } + else if (is_quantifier(n)) { + expr_ref new_expr(m_manager); + pull_quant1(to_quantifier(n)->get_expr(), new_expr); + pull_quant1(to_quantifier(n), new_expr, r); + if (m_manager.fine_grain_proofs()) { + quantifier * q1 = m_manager.update_quantifier(to_quantifier(n), new_expr); + proof * p1 = 0; + if (n != q1) { + proof * p0 = m_manager.mk_pull_quant(to_quantifier(n)->get_expr(), to_quantifier(new_expr)); + p1 = m_manager.mk_quant_intro(to_quantifier(n), q1, p0); + } + proof * p2 = q1 == r ? 0 : m_manager.mk_pull_quant(q1, to_quantifier(r)); + pr = m_manager.mk_transitivity(p1, p2); + } + } + else { + r = n; + } +} + +bool pull_quant::visit_children(expr * n) { + bool visited = true; + unsigned j; + switch(n->get_kind()) { + case AST_APP: + // This transformation is also applied after the formula + // has been converted into a SNF using only OR and NOT. + if (m_manager.is_or(n) || m_manager.is_and(n) || m_manager.is_not(n)) { + j = to_app(n)->get_num_args(); + while (j > 0) { + --j; + visit(to_app(n)->get_arg(j), visited); + } + } + else { + // This class assumes the formula is in skolem normal form. + SASSERT(!has_quantifiers(n)); + } + break; + case AST_QUANTIFIER: + if (to_quantifier(n)->is_forall()) + visit(to_quantifier(n)->get_expr(), visited); + break; + default: + break; + } + return visited; +} + +void pull_quant::reduce1(expr * n) { + switch(n->get_kind()) { + case AST_APP: + reduce1_app(to_app(n)); + break; + case AST_VAR: + cache_result(n, n, 0); + break; + case AST_QUANTIFIER: + reduce1_quantifier(to_quantifier(n)); + break; + default: + UNREACHABLE(); + break; + } +} + +void pull_quant::reduce1_app(app * n) { + if (m_manager.is_or(n) || m_manager.is_and(n) || m_manager.is_not(n)) { + ptr_buffer new_children; + ptr_buffer new_children_proofs; + unsigned num = n->get_num_args(); + for (unsigned i = 0; i < num; i++) { + expr * new_child = 0; + proof * new_child_pr = 0; + get_cached(n->get_arg(i), new_child, new_child_pr); + new_children.push_back(new_child); + if (new_child_pr) { + new_children_proofs.push_back(new_child_pr); + } + } + + expr_ref r(m_manager); + pull_quant1(n->get_decl(), new_children.size(), new_children.c_ptr(), r); + proof * pr = 0; + if (m_manager.fine_grain_proofs()) { + app * n_prime = m_manager.mk_app(n->get_decl(), new_children.size(), new_children.c_ptr()); + TRACE("proof_bug", tout << mk_pp(n, m_manager) << "\n"; + tout << mk_pp(n_prime, m_manager) << "\n";); + proof * p1 = n == n_prime ? 0 : m_manager.mk_congruence(n, n_prime, + new_children_proofs.size(), new_children_proofs.c_ptr()); + proof * p2 = n_prime == r ? 0 : m_manager.mk_pull_quant(n_prime, to_quantifier(r)); + pr = m_manager.mk_transitivity(p1, p2); + } + cache_result(n, r, pr); + return; + } + TRACE("proof_bug", tout << mk_pp(n, m_manager) << "\n";); + cache_result(n, n, 0); +} + +void pull_quant::reduce1_quantifier(quantifier * q) { + if (q->is_forall()) { + expr * new_expr; + proof * new_expr_pr; + get_cached(q->get_expr(), new_expr, new_expr_pr); + expr_ref r(m_manager); + pull_quant1(q, new_expr, r); + proof * pr = 0; + if (m_manager.fine_grain_proofs()) { + quantifier * q_prime = m_manager.update_quantifier(q, new_expr); + proof * p1 = q == q_prime ? 0 : m_manager.mk_quant_intro(q, q_prime, new_expr_pr); + proof * p2 = q_prime == r ? 0 : m_manager.mk_pull_quant(q_prime, to_quantifier(r)); + pr = m_manager.mk_transitivity(p1, p2); + } + cache_result(q, r, pr); + return; + } + // should be unreachable, right? + UNREACHABLE(); + cache_result(q, q, 0); +} + +pull_quant::pull_quant(ast_manager & m): + base_simplifier(m), + m_shift(m) { +} + +void pull_quant::operator()(expr * n, expr_ref & r, proof_ref & p) { + flush_cache(); + m_todo.push_back(n); + while (!m_todo.empty()) { + expr * n = m_todo.back(); + if (is_cached(n)) + m_todo.pop_back(); + else if (visit_children(n)) { + m_todo.pop_back(); + reduce1(n); + } + } + + expr * result; + proof * result_proof; + get_cached(n, result, result_proof); + + r = result; + + switch (m_manager.proof_mode()) { + case PGM_DISABLED: + p = m_manager.mk_undef_proof(); + break; + case PGM_COARSE: + if (result == n) + p = m_manager.mk_reflexivity(n); + else + p = m_manager.mk_pull_quant_star(n, to_quantifier(result)); + break; + case PGM_FINE: + SASSERT(result_proof || result == n); + p = result_proof ? result_proof : m_manager.mk_reflexivity(n); + break; + } +} + +bool pull_nested_quant::visit_quantifier(quantifier * q) { + // do not recurse. + return true; +} + +void pull_nested_quant::reduce1_quantifier(quantifier * q) { + expr_ref r(m_manager); + proof_ref pr(m_manager); + m_pull(q, r, pr); + cache_result(q, r, pr); +} diff --git a/lib/pull_quant.h b/lib/pull_quant.h new file mode 100644 index 000000000..e9b2b5c65 --- /dev/null +++ b/lib/pull_quant.h @@ -0,0 +1,67 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + pull_quant.h + +Abstract: + + Pull nested quantifiers. + +Author: + + Leonardo (leonardo) 2008-01-20 + +Notes: + +--*/ +#ifndef _PULL_QUANT_H_ +#define _PULL_QUANT_H_ + +#include"simplifier.h" +#include"var_subst.h" + +/** + \brief Pull nested quantifiers in a formula. + + \warning It assumes the input formula is in NNF. + + \remark pull_quant(F) is a quantifier if F contains a quantifier. + + \remark If pull_quant(F) is a quantifier then its weight is + Min{weight(Q') | Q' is a quantifier nested in F} +*/ +class pull_quant : public base_simplifier { +protected: + shift_vars m_shift; + bool visit_children(expr * n); + void reduce1(expr *); + void reduce1_app(app * n); + void reduce1_quantifier(quantifier * q); + +public: + pull_quant(ast_manager & m); + virtual ~pull_quant() {} + void operator()(expr * n, expr_ref & r, proof_ref & p); + void reset() { flush_cache(); } + void pull_quant1(func_decl * d, unsigned num_children, expr * const * children, expr_ref & result); + void pull_quant1(quantifier * q, expr * new_expr, expr_ref & result); + void pull_quant1(expr * n, expr_ref & result); + void pull_quant2(expr * n, expr_ref & r, proof_ref & pr); +}; + +/** + \brief After applying this transformation the formula will not + contain nested quantifiers. +*/ +class pull_nested_quant : public simplifier { + pull_quant m_pull; + virtual bool visit_quantifier(quantifier * q); + virtual void reduce1_quantifier(quantifier * q); +public: + pull_nested_quant(ast_manager & m):simplifier(m), m_pull(m) { enable_ac_support(false); } + virtual ~pull_nested_quant() {} +}; + +#endif /* _PULL_QUANT_H_ */ diff --git a/lib/purify_arith_tactic.cpp b/lib/purify_arith_tactic.cpp new file mode 100644 index 000000000..a68a6a000 --- /dev/null +++ b/lib/purify_arith_tactic.cpp @@ -0,0 +1,909 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + purify_arith_tactic.h + +Abstract: + + Tactic for eliminating arithmetic operators: DIV, IDIV, MOD, + TO_INT, and optionally (OP_IRRATIONAL_ALGEBRAIC_NUM). + + This tactic uses the simplifier for also eliminating: + OP_SUB, OP_UMINUS, OP_POWER (optionally), OP_REM, OP_IS_INT. + +Author: + + Leonardo de Moura (leonardo) 2011-12-30. + +Revision History: + +--*/ +#include"tactical.h" +#include"rewriter_def.h" +#include"arith_decl_plugin.h" +#include"algebraic_numbers.h" +#include"nnf_tactic.h" +#include"simplify_tactic.h" +#include"th_rewriter.h" +#include"filter_model_converter.h" +#include"ast_smt2_pp.h" + +/* +---- +Some of the rules needed in the conversion are implemented in +arith_rewriter.cpp. Here is a summary of these rules: + + (^ t (/ p q)) --> (^ (^ t (/ 1 q)) p) + + (^ t n) --> t*...*t + when integer power expansion is requested + + (is-int t) --> t = (to-real (to-int t)) + + (rem t1 t2) --> ite(t2 >= 0, (mod t1 t2), -(mod t1 t2)) + +---- +The tactic implements a set of transformation rules. These rules +create fresh constants or (existential) variables, and add new +constraints to the context. +The context is the set of asserted formulas or a quantifier. + +A rule is represented as: + + From --> To | C + +It means, any expression that matches From is replaced by To, +and the constraints C are added to the context. +For clarity reasons, I write the constraints using ad-hoc notation. + + +Rules + (^ t 0) --> k | t != 0 implies k = 1, t = 0 implies k = 0^0 + where k is fresh + 0^0 is a constant used to capture the meaning of (^ 0 0). + + (^ t (/ 1 n)) --> k | t = k^n + when n is odd + where k is fresh + + (^ t (/ 1 n)) --> k | t >= 0 implies t = k^n, t < 0 implies t = neg-root(t, n) + when n is even + where k is fresh + neg-root is a function symbol used to capture the meaning of a negative root + + (root-obj p(x) i) --> k | p(k) = 0, l < k < u + when root object elimination is requested + where k is fresh + (l, u) is an isolating interval for the i-th root of p. + + (to-int t) --> k | 0 <= to-real(k) - t < 1 + where k is a fresh integer constant/variable + + (/ t1 t2) --> k | t2 != 0 implies k*t2 = t1, t2 = 0 implies k = div-0(t1) + where k is fresh + div-0 is a function symbol used to capture the meaning of division by 0. + + Remark: If it can be shown that t2 != 0, then the div-0(t1) function application + vanishes from the formula. + + (div t1 t2) --> k1 | t2 = 0 \/ t1 = k1 * t2 + k2, + t2 = 0 \/ 0 <= k2, + t2 = 0 \/ k2 < |t2|, + t2 != 0 \/ k1 = idiv-0(t1), + t2 != 0 \/ k2 = mod-0(t1) + k1 is a fresh name for (div t1 t2) + k2 is a fresh name for (mod t1 t2) + + (mod t1 t2) --> k2 | same constraints as above +*/ + +struct purify_arith_decls { + ast_manager & m; + func_decl * m_int_0_pw_0_decl; // decl for: int 0^0 + func_decl * m_real_0_pw_0_decl; // decl for: rel 0^0 + func_decl * m_neg_root_decl; // decl for: even root of negative (real) number + func_decl * m_div_0_decl; // decl for: x/0 + func_decl * m_idiv_0_decl; // decl for: div(x, 0) + func_decl * m_mod_0_decl; // decl for: mod(x, 0) + func_decl * m_asin_u_decl; // decl for: asin(x) when x < -1 or x > 1 + func_decl * m_acos_u_decl; // decl for: acos(x) when x < -1 or x > 1 + + void inc_refs() { + m.inc_ref(m_int_0_pw_0_decl); + m.inc_ref(m_real_0_pw_0_decl); + m.inc_ref(m_neg_root_decl); + m.inc_ref(m_div_0_decl); + m.inc_ref(m_idiv_0_decl); + m.inc_ref(m_mod_0_decl); + m.inc_ref(m_asin_u_decl); + m.inc_ref(m_acos_u_decl); + } + + void dec_refs() { + m.dec_ref(m_int_0_pw_0_decl); + m.dec_ref(m_real_0_pw_0_decl); + m.dec_ref(m_neg_root_decl); + m.dec_ref(m_div_0_decl); + m.dec_ref(m_idiv_0_decl); + m.dec_ref(m_mod_0_decl); + m.dec_ref(m_asin_u_decl); + m.dec_ref(m_acos_u_decl); + } + + purify_arith_decls(arith_util & u): + m(u.get_manager()) { + sort * i = u.mk_int(); + sort * r = u.mk_real(); + m_int_0_pw_0_decl = m.mk_const_decl(symbol("0^0-int"), i); + m_real_0_pw_0_decl = m.mk_const_decl(symbol("0^0-real"), r); + sort * rr[2] = { r, r }; + m_neg_root_decl = m.mk_func_decl(symbol("neg-root"), 2, rr, r); + m_div_0_decl = m.mk_func_decl(symbol("/0"), 1, &r, r); + m_idiv_0_decl = m.mk_func_decl(symbol("div0"), 1, &i, i); + m_mod_0_decl = m.mk_func_decl(symbol("mod0"), 1, &i, i); + m_asin_u_decl = m.mk_func_decl(symbol("asin-u"), 1, &r, r); + m_acos_u_decl = m.mk_func_decl(symbol("acos-u"), 1, &r, r); + inc_refs(); + } + + purify_arith_decls(ast_manager & _m, + func_decl * int_0_pw_0, + func_decl * real_0_pw_0, + func_decl * neg_root, + func_decl * div_0, + func_decl * idiv_0, + func_decl * mod_0, + func_decl * asin_u, + func_decl * acos_u + ): + m(_m), + m_int_0_pw_0_decl(int_0_pw_0), + m_real_0_pw_0_decl(real_0_pw_0), + m_neg_root_decl(neg_root), + m_div_0_decl(div_0), + m_idiv_0_decl(idiv_0), + m_mod_0_decl(mod_0), + m_asin_u_decl(asin_u), + m_acos_u_decl(acos_u) { + inc_refs(); + } + + ~purify_arith_decls() { + dec_refs(); + } +}; + +struct purify_arith_proc { + arith_util & m_util; + purify_arith_decls & m_aux_decls; + bool m_produce_proofs; + bool m_elim_root_objs; + bool m_elim_inverses; + bool m_complete; + + purify_arith_proc(arith_util & u, purify_arith_decls & d, bool produce_proofs, bool elim_root_objs, bool elim_inverses, bool complete): + m_util(u), + m_aux_decls(d), + m_produce_proofs(produce_proofs), + m_elim_root_objs(elim_root_objs), + m_elim_inverses(elim_inverses), + m_complete(complete) { + } + + arith_util & u() { + return m_util; + } + + ast_manager & m() { + return u().get_manager(); + } + + struct rw_cfg : public default_rewriter_cfg { + purify_arith_proc & m_owner; + obj_map m_app2fresh; + obj_map m_app2pr; + expr_ref_vector m_pinned; + expr_ref_vector m_new_cnstrs; + proof_ref_vector m_new_cnstr_prs; + expr_ref m_subst; + proof_ref m_subst_pr; + bool m_in_q; + unsigned m_var_idx; + + rw_cfg(purify_arith_proc & o, bool in_q): + m_owner(o), + m_pinned(o.m()), + m_new_cnstrs(o.m()), + m_new_cnstr_prs(o.m()), + m_subst(o.m()), + m_subst_pr(o.m()), + m_in_q(in_q), + m_var_idx(0) { + } + + ast_manager & m() { return m_owner.m(); } + + arith_util & u() { return m_owner.u(); } + + bool produce_proofs() const { return m_owner.m_produce_proofs; } + bool complete() const { return m_owner.m_complete; } + bool elim_root_objs() const { return m_owner.m_elim_root_objs; } + bool elim_inverses() const { return m_owner.m_elim_inverses; } + + expr * mk_fresh_var(bool is_int) { + if (m_in_q) { + unsigned idx = m_var_idx; + m_var_idx++; + return m().mk_var(idx, is_int ? u().mk_int() : u().mk_real()); + } + else { + return m().mk_fresh_const(0, is_int ? u().mk_int() : u().mk_real()); + } + } + + expr * mk_fresh_real_var() { return mk_fresh_var(false); } + + expr * mk_fresh_int_var() { return mk_fresh_var(true); } + + func_decl * div0_decl() { return m_owner.m_aux_decls.m_div_0_decl; } + func_decl * idiv0_decl() { return m_owner.m_aux_decls.m_idiv_0_decl; } + func_decl * mod0_decl() { return m_owner.m_aux_decls.m_mod_0_decl; } + func_decl * int_0_pw_0_decl() { return m_owner.m_aux_decls.m_int_0_pw_0_decl; } + func_decl * real_0_pw_0_decl() { return m_owner.m_aux_decls.m_real_0_pw_0_decl; } + func_decl * neg_root_decl() { return m_owner.m_aux_decls.m_neg_root_decl; } + func_decl * asin_u_decl() { return m_owner.m_aux_decls.m_asin_u_decl; } + func_decl * acos_u_decl() { return m_owner.m_aux_decls.m_acos_u_decl; } + + expr * mk_int_zero() { return u().mk_numeral(rational(0), true); } + + expr * mk_real_zero() { return u().mk_numeral(rational(0), false); } + + bool already_processed(app * t, expr_ref & result, proof_ref & result_pr) { + expr * r; + if (m_app2fresh.find(t, r)) { + result = r; + if (produce_proofs()) + result_pr = m_app2pr.find(t); + return true; + } + return false; + } + + void mk_def_proof(expr * k, expr * def, proof_ref & result_pr) { + result_pr = 0; + if (produce_proofs()) { + expr * eq = m().mk_eq(k, def); + proof * pr1 = m().mk_def_intro(eq); + result_pr = m().mk_apply_def(k, def, pr1); + } + } + + void push_cnstr_pr(proof * def_pr) { + if (produce_proofs()) + m_new_cnstr_prs.push_back(m().mk_th_lemma(u().get_family_id(), m_new_cnstrs.back(), 1, &def_pr)); + } + + void push_cnstr_pr(proof * def_pr1, proof * def_pr2) { + if (produce_proofs()) { + proof * prs[2] = { def_pr1, def_pr2 }; + m_new_cnstr_prs.push_back(m().mk_th_lemma(u().get_family_id(), m_new_cnstrs.back(), 2, prs)); + } + } + + void push_cnstr(expr * cnstr) { + m_new_cnstrs.push_back(cnstr); + } + + void cache_result(app * t, expr * r, proof * pr) { + m_app2fresh.insert(t, r); + m_pinned.push_back(t); + m_pinned.push_back(r); + if (produce_proofs()) { + m_app2pr.insert(t, pr); + m_pinned.push_back(pr); + } + } + + expr * OR(expr * arg1, expr * arg2) { return m().mk_or(arg1, arg2); } + expr * AND(expr * arg1, expr * arg2) { return m().mk_and(arg1, arg2); } + expr * EQ(expr * lhs, expr * rhs) { return m().mk_eq(lhs, rhs); } + expr * NOT(expr * arg) { return m().mk_not(arg); } + + void process_div(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + app_ref t(m()); + t = m().mk_app(f, num, args); + if (already_processed(t, result, result_pr)) + return; + + expr * k = mk_fresh_real_var(); + result = k; + mk_def_proof(k, t, result_pr); + cache_result(t, result, result_pr); + + expr * x = args[0]; + expr * y = args[1]; + // y = 0 \/ y*k = x + push_cnstr(OR(EQ(y, mk_real_zero()), + EQ(u().mk_mul(y, k), x))); + push_cnstr_pr(result_pr); + + if (complete()) { + // y != 0 \/ k = div-0(x) + push_cnstr(OR(NOT(EQ(y, mk_real_zero())), + EQ(k, m().mk_app(div0_decl(), x)))); + push_cnstr_pr(result_pr); + } + } + + void process_idiv(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + app_ref div_app(m()); + div_app = m().mk_app(f, num, args); + if (already_processed(div_app, result, result_pr)) + return; + + expr * k1 = mk_fresh_int_var(); + result = k1; + mk_def_proof(k1, div_app, result_pr); + cache_result(div_app, result, result_pr); + + expr * k2 = mk_fresh_int_var(); + app_ref mod_app(m()); + proof_ref mod_pr(m()); + mod_app = u().mk_mod(args[0], args[1]); + mk_def_proof(k2, mod_app, mod_pr); + cache_result(mod_app, k2, mod_pr); + + expr * x = args[0]; + expr * y = args[1]; + // (div x y) --> k1 | y = 0 \/ x = k1 * y + k2, + // y = 0 \/ 0 <= k2, + // y = 0 \/ k2 < |y|, + // y != 0 \/ k1 = idiv-0(x), + // y != 0 \/ k2 = mod-0(x) + // We can write y = 0 \/ k2 < |y| as: + // y > 0 implies k2 < y ---> y <= 0 \/ k2 < y + // y < 0 implies k2 < -y ---> y >= 0 \/ k2 < -y + // + expr * zero = mk_int_zero(); + push_cnstr(OR(EQ(y, zero), EQ(x, u().mk_add(u().mk_mul(k1, y), k2)))); + push_cnstr_pr(result_pr, mod_pr); + + push_cnstr(OR(EQ(y, zero), u().mk_le(zero, k2))); + push_cnstr_pr(mod_pr); + + push_cnstr(OR(u().mk_le(y, zero), u().mk_lt(k2, y))); + push_cnstr_pr(mod_pr); + + push_cnstr(OR(u().mk_ge(y, zero), u().mk_lt(k2, u().mk_mul(u().mk_numeral(rational(-1), true), y)))); + push_cnstr_pr(mod_pr); + + if (complete()) { + push_cnstr(OR(NOT(EQ(y, zero)), EQ(k1, m().mk_app(idiv0_decl(), x)))); + push_cnstr_pr(result_pr); + + push_cnstr(OR(NOT(EQ(y, zero)), EQ(k2, m().mk_app(mod0_decl(), x)))); + push_cnstr_pr(mod_pr); + } + } + + void process_mod(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + app_ref t(m()); + t = m().mk_app(f, num, args); + if (already_processed(t, result, result_pr)) + return; + process_idiv(f, num, args, result, result_pr); // it will create mod + VERIFY(already_processed(t, result, result_pr)); + } + + void process_to_int(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + app_ref t(m()); + t = m().mk_app(f, num, args); + if (already_processed(t, result, result_pr)) + return; + + expr * k = mk_fresh_int_var(); + result = k; + mk_def_proof(k, t, result_pr); + cache_result(t, result, result_pr); + + expr * x = args[0]; + // to-real(k) - x >= 0 + expr * diff = u().mk_add(u().mk_to_real(k), u().mk_mul(u().mk_numeral(rational(-1), false), x)); + push_cnstr(u().mk_ge(diff, mk_real_zero())); + push_cnstr_pr(result_pr); + + // not(to-real(k) - x >= 1) + push_cnstr(NOT(u().mk_ge(diff, u().mk_numeral(rational(1), false)))); + push_cnstr_pr(result_pr); + } + + br_status process_power(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + rational y; + if (!u().is_numeral(args[1], y)) + return BR_FAILED; + if (y.is_int() && !y.is_zero()) + return BR_FAILED; + app_ref t(m()); + t = m().mk_app(f, num, args); + if (already_processed(t, result, result_pr)) + return BR_DONE; + + bool is_int = u().is_int(args[0]); + + expr * k = mk_fresh_var(is_int); + result = k; + mk_def_proof(k, t, result_pr); + cache_result(t, result, result_pr); + + expr * x = args[0]; + expr * zero = u().mk_numeral(rational(0), is_int); + expr * one = u().mk_numeral(rational(1), is_int); + if (y.is_zero()) { + // (^ x 0) --> k | x != 0 implies k = 1, x = 0 implies k = 0^0 + push_cnstr(OR(EQ(x, zero), EQ(k, one))); + push_cnstr_pr(result_pr); + if (complete()) { + func_decl * z_pw_z = is_int ? int_0_pw_0_decl() : real_0_pw_0_decl(); + push_cnstr(OR(NOT(EQ(x, zero)), EQ(k, m().mk_const(z_pw_z)))); + push_cnstr_pr(result_pr); + } + } + else if (!is_int) { + SASSERT(!y.is_int()); + SASSERT(numerator(y).is_one()); + rational n = denominator(y); + if (!n.is_even()) { + // (^ x (/ 1 n)) --> k | x = k^n + // when n is odd + push_cnstr(EQ(x, u().mk_power(k, u().mk_numeral(n, false)))); + push_cnstr_pr(result_pr); + } + else { + SASSERT(n.is_even()); + // (^ x (/ 1 n)) --> k | x >= 0 implies (x = k^n and k >= 0), x < 0 implies k = neg-root(x, n) + // when n is even + push_cnstr(OR(NOT(u().mk_ge(x, zero)), + AND(EQ(x, u().mk_power(k, u().mk_numeral(n, false))), + u().mk_ge(k, zero)))); + push_cnstr_pr(result_pr); + if (complete()) { + push_cnstr(OR(u().mk_ge(x, zero), + EQ(k, m().mk_app(neg_root_decl(), x, u().mk_numeral(n, false))))); + push_cnstr_pr(result_pr); + } + } + } + else { + // root not supported for integers. + SASSERT(is_int); + SASSERT(!y.is_int()); + return BR_FAILED; + } + return BR_DONE; + } + + void process_irrat(app * s, expr_ref & result, proof_ref & result_pr) { + if (already_processed(s, result, result_pr)) + return; + + expr * k = mk_fresh_real_var(); + result = k; + mk_def_proof(k, s, result_pr); + cache_result(s, result, result_pr); + + anum_manager & am = u().am(); + anum const & a = u().to_irrational_algebraic_numeral(s); + scoped_mpz_vector p(am.qm()); + am.get_polynomial(a, p); + rational lower, upper; + am.get_lower(a, lower); + am.get_upper(a, upper); + unsigned sz = p.size(); + SASSERT(sz > 2); + ptr_buffer args; + for (unsigned i = 0; i < sz; i++) { + if (am.qm().is_zero(p[i])) + continue; + rational coeff = rational(p[i]); + if (i == 0) { + args.push_back(u().mk_numeral(coeff, false)); + } + else { + expr * m; + if (i == 1) + m = k; + else + m = u().mk_power(k, u().mk_numeral(rational(i), false)); + args.push_back(u().mk_mul(u().mk_numeral(coeff, false), m)); + } + } + SASSERT(args.size() >= 2); + push_cnstr(EQ(u().mk_add(args.size(), args.c_ptr()), mk_real_zero())); + push_cnstr_pr(result_pr); + push_cnstr(u().mk_lt(u().mk_numeral(lower, false), k)); + push_cnstr_pr(result_pr); + push_cnstr(u().mk_lt(k, u().mk_numeral(upper, false))); + push_cnstr_pr(result_pr); + } + + br_status process_asin(func_decl * f, expr * x, expr_ref & result, proof_ref & result_pr) { + if (!elim_inverses()) + return BR_FAILED; + app_ref t(m()); + t = m().mk_app(f, x); + if (already_processed(t, result, result_pr)) + return BR_DONE; + + expr * k = mk_fresh_var(false); + result = k; + mk_def_proof(k, t, result_pr); + cache_result(t, result, result_pr); + + // Constraints: + // -1 <= x <= 1 implies sin(k) = x, -pi/2 <= k <= pi/2 + // If complete() + // x < -1 implies k = asin_u(x) + // x > 1 implies k = asin_u(x) + expr * one = u().mk_numeral(rational(1), false); + expr * mone = u().mk_numeral(rational(-1), false); + expr * pi2 = u().mk_mul(u().mk_numeral(rational(1,2), false), u().mk_pi()); + expr * mpi2 = u().mk_mul(u().mk_numeral(rational(-1,2), false), u().mk_pi()); + // -1 <= x <= 1 implies sin(k) = x, -pi/2 <= k <= pi/2 + push_cnstr(OR(OR(NOT(u().mk_ge(x, mone)), + NOT(u().mk_le(x, one))), + AND(EQ(x, u().mk_sin(k)), + AND(u().mk_ge(k, mpi2), + u().mk_le(k, pi2))))); + push_cnstr_pr(result_pr); + if (complete()) { + // x < -1 implies k = asin_u(x) + // x > 1 implies k = asin_u(x) + push_cnstr(OR(u().mk_ge(x, mone), + EQ(k, m().mk_app(asin_u_decl(), x)))); + push_cnstr_pr(result_pr); + push_cnstr(OR(u().mk_le(x, one), + EQ(k, m().mk_app(asin_u_decl(), x)))); + push_cnstr_pr(result_pr); + } + return BR_DONE; + } + + br_status process_acos(func_decl * f, expr * x, expr_ref & result, proof_ref & result_pr) { + if (!elim_inverses()) + return BR_FAILED; + app_ref t(m()); + t = m().mk_app(f, x); + if (already_processed(t, result, result_pr)) + return BR_DONE; + + expr * k = mk_fresh_var(false); + result = k; + mk_def_proof(k, t, result_pr); + cache_result(t, result, result_pr); + + // Constraints: + // -1 <= x <= 1 implies cos(k) = x, 0 <= k <= pi + // If complete() + // x < -1 implies k = acos_u(x) + // x > 1 implies k = acos_u(x) + expr * one = u().mk_numeral(rational(1), false); + expr * mone = u().mk_numeral(rational(-1), false); + expr * pi = u().mk_pi(); + expr * zero = u().mk_numeral(rational(0), false); + // -1 <= x <= 1 implies cos(k) = x, 0 <= k <= pi + push_cnstr(OR(OR(NOT(u().mk_ge(x, mone)), + NOT(u().mk_le(x, one))), + AND(EQ(x, u().mk_cos(k)), + AND(u().mk_ge(k, zero), + u().mk_le(k, pi))))); + push_cnstr_pr(result_pr); + if (complete()) { + // x < -1 implies k = acos_u(x) + // x > 1 implies k = acos_u(x) + push_cnstr(OR(u().mk_ge(x, mone), + EQ(k, m().mk_app(acos_u_decl(), x)))); + push_cnstr_pr(result_pr); + push_cnstr(OR(u().mk_le(x, one), + EQ(k, m().mk_app(acos_u_decl(), x)))); + push_cnstr_pr(result_pr); + } + return BR_DONE; + } + + br_status process_atan(func_decl * f, expr * x, expr_ref & result, proof_ref & result_pr) { + if (!elim_inverses()) + return BR_FAILED; + app_ref t(m()); + t = m().mk_app(f, x); + if (already_processed(t, result, result_pr)) + return BR_DONE; + + expr * k = mk_fresh_var(false); + result = k; + mk_def_proof(k, t, result_pr); + cache_result(t, result, result_pr); + + // Constraints: + // tan(k) = x, -pi/2 < k < pi/2 + expr * pi2 = u().mk_mul(u().mk_numeral(rational(1,2), false), u().mk_pi()); + expr * mpi2 = u().mk_mul(u().mk_numeral(rational(-1,2), false), u().mk_pi()); + push_cnstr(AND(EQ(x, u().mk_tan(k)), + AND(u().mk_gt(k, mpi2), + u().mk_lt(k, pi2)))); + push_cnstr_pr(result_pr); + return BR_DONE; + } + + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + if (f->get_family_id() != u().get_family_id()) + return BR_FAILED; + switch (f->get_decl_kind()) { + case OP_DIV: + process_div(f, num, args, result, result_pr); + return BR_DONE; + case OP_IDIV: + process_idiv(f, num, args, result, result_pr); + return BR_DONE; + case OP_MOD: + process_mod(f, num, args, result, result_pr); + return BR_DONE; + case OP_TO_INT: + process_to_int(f, num, args, result, result_pr); + return BR_DONE; + case OP_POWER: + return process_power(f, num, args, result, result_pr); + case OP_ASIN: + return process_asin(f, args[0], result, result_pr); + case OP_ACOS: + return process_acos(f, args[0], result, result_pr); + case OP_ATAN: + return process_atan(f, args[0], result, result_pr); + default: + return BR_FAILED; + } + } + + bool get_subst(expr * s, expr * & t, proof * & t_pr) { + if (is_quantifier(s)) { + m_owner.process_quantifier(to_quantifier(s), m_subst, m_subst_pr); + t = m_subst.get(); + t_pr = m_subst_pr.get(); + return true; + } + else if (u().is_irrational_algebraic_numeral(s) && elim_root_objs()) { + process_irrat(to_app(s), m_subst, m_subst_pr); + t = m_subst.get(); + t_pr = m_subst_pr.get(); + return true; + } + return false; + } + }; + + struct rw : public rewriter_tpl { + rw_cfg m_cfg; + rw(purify_arith_proc & o, bool in_q): + rewriter_tpl(o.m(), o.m_produce_proofs, m_cfg), + m_cfg(o, in_q) { + } + }; + + /** + \brief Return the number of (auxiliary) variables needed for converting an expression. + */ + struct num_vars_proc { + arith_util & m_util; + expr_fast_mark1 m_visited; + ptr_vector m_todo; + unsigned m_num_vars; + bool m_elim_root_objs; + + num_vars_proc(arith_util & u, bool elim_root_objs): + m_util(u), + m_elim_root_objs(elim_root_objs) { + } + + void visit(expr * t) { + if (m_visited.is_marked(t)) + return; + m_visited.mark(t); + m_todo.push_back(t); + } + + void process(app * t) { + if (t->get_family_id() == m_util.get_family_id()) { + if (m_util.is_power(t)) { + rational k; + if (m_util.is_numeral(t->get_arg(1), k) && (k.is_zero() || !k.is_int())) { + m_num_vars++; + } + } + else if (m_util.is_div(t) || + m_util.is_idiv(t) || + m_util.is_mod(t) || + m_util.is_to_int(t) || + (m_util.is_irrational_algebraic_numeral(t) && m_elim_root_objs)) { + m_num_vars++; + } + } + unsigned num_args = t->get_num_args(); + for (unsigned i = 0; i < num_args; i++) + visit(t->get_arg(i)); + } + + unsigned operator()(expr * t) { + m_num_vars = 0; + visit(t); + while (!m_todo.empty()) { + expr * t = m_todo.back(); + m_todo.pop_back(); + if (is_app(t)) + process(to_app(t)); + } + m_visited.reset(); + return m_num_vars; + } + }; + + void process_quantifier(quantifier * q, expr_ref & result, proof_ref & result_pr) { + result_pr = 0; + num_vars_proc p(u(), m_elim_root_objs); + expr_ref body(m()); + unsigned num_vars = p(q->get_expr()); + if (num_vars > 0) { + // open space for aux vars + var_shifter shifter(m()); + shifter(q->get_expr(), num_vars, body); + } + else { + body = q->get_expr(); + } + + rw r(*this, true); + expr_ref new_body(m()); + proof_ref new_body_pr(m()); + r(body, new_body, new_body_pr); + TRACE("purify_arith", + tout << "num_vars: " << num_vars << "\n"; + tout << "body: " << mk_ismt2_pp(body, m()) << "\nnew_body: " << mk_ismt2_pp(new_body, m()) << "\n";); + if (num_vars == 0) { + result = m().update_quantifier(q, new_body); + if (m_produce_proofs) + result_pr = m().mk_quant_intro(q, to_quantifier(result.get()), result_pr); + } + else { + expr_ref_vector & cnstrs = r.cfg().m_new_cnstrs; + cnstrs.push_back(new_body); + new_body = m().mk_and(cnstrs.size(), cnstrs.c_ptr()); + ptr_buffer sorts; + buffer names; + for (unsigned i = 0; i < num_vars; i++) { + sorts.push_back(u().mk_real()); + names.push_back(m().mk_fresh_var_name("x")); + } + new_body = m().mk_exists(num_vars, sorts.c_ptr(), names.c_ptr(), new_body); + result = m().update_quantifier(q, new_body); + if (m_produce_proofs) { + proof_ref_vector & cnstr_prs = r.cfg().m_new_cnstr_prs; + cnstr_prs.push_back(result_pr); + // TODO: improve proof + result_pr = m().mk_quant_intro(q, to_quantifier(result.get()), + m().mk_rewrite_star(q->get_expr(), new_body, cnstr_prs.size(), cnstr_prs.c_ptr())); + } + } + } + + void operator()(goal & g, model_converter_ref & mc, bool produce_models) { + rw r(*this, false); + // purify + expr_ref new_curr(m()); + proof_ref new_pr(m()); + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + expr * curr = g.form(i); + r(curr, new_curr, new_pr); + if (m_produce_proofs) { + proof * pr = g.pr(i); + new_pr = m().mk_modus_ponens(pr, new_pr); + } + g.update(i, new_curr, new_pr, g.dep(i)); + } + + // add cnstraints + sz = r.cfg().m_new_cnstrs.size(); + for (unsigned i = 0; i < sz; i++) { + g.assert_expr(r.cfg().m_new_cnstrs.get(i), m_produce_proofs ? r.cfg().m_new_cnstr_prs.get(i) : 0, 0); + } + + // add filter_model_converter to eliminate auxiliary variables from model + if (produce_models) { + filter_model_converter * fmc = alloc(filter_model_converter, m()); + mc = fmc; + obj_map & f2v = r.cfg().m_app2fresh; + obj_map::iterator it = f2v.begin(); + obj_map::iterator end = f2v.end(); + for (; it != end; ++it) { + app * v = to_app(it->m_value); + SASSERT(is_uninterp_const(v)); + fmc->insert(v->get_decl()); + } + } + } +}; + +class purify_arith_tactic : public tactic { + arith_util m_util; + purify_arith_decls m_aux_decls; + params_ref m_params; +public: + purify_arith_tactic(ast_manager & m, params_ref const & p): + m_util(m), + m_aux_decls(m_util), + m_params(p) { + } + + virtual tactic * translate(ast_manager & m) { + return alloc(purify_arith_tactic, m, m_params); + } + + virtual ~purify_arith_tactic() { + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + } + + virtual void collect_param_descrs(param_descrs & r) { + r.insert(":complete", CPK_BOOL, + "(default: true) add constraints to make sure that any interpretation of a underspecified arithmetic operators is a functio. The result will include additional uninterpreted functions/constants: /0, div0, mod0, 0^0, neg-root"); + r.insert(":elim-root-objects", CPK_BOOL, + "(default: true) eliminate root objects."); + r.insert(":elim-inverses", CPK_BOOL, + "(default: true) eliminate inverse trigonometric functions (asin, acos, atan)."); + th_rewriter::get_param_descrs(r); + } + + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + mc = 0; pc = 0; core = 0; + tactic_report report("purify-arith", *g); + bool produce_proofs = g->proofs_enabled(); + bool produce_models = g->models_enabled(); + bool elim_root_objs = m_params.get_bool(":elim-root-objects", true); + bool elim_inverses = m_params.get_bool(":elim-inverses", true); + bool complete = m_params.get_bool(":complete", true); + purify_arith_proc proc(m_util, m_aux_decls, produce_proofs, elim_root_objs, elim_inverses, complete); + + proc(*(g.get()), mc, produce_models); + + g->inc_depth(); + result.push_back(g.get()); + TRACE("purify_arith", g->display(tout);); + SASSERT(g->is_well_sorted()); + } + + virtual void cleanup() { + } + + virtual void set_cancel(bool f) { + } +}; + +tactic * mk_purify_arith_tactic(ast_manager & m, params_ref const & p) { + params_ref elim_rem_p = p; + elim_rem_p.set_bool(":elim-rem", true); + + params_ref skolemize_p; + skolemize_p.set_bool(":skolemize", false); + + return and_then(using_params(mk_snf_tactic(m, skolemize_p), skolemize_p), + using_params(mk_simplify_tactic(m, elim_rem_p), elim_rem_p), + alloc(purify_arith_tactic, m, p), + mk_simplify_tactic(m, p)); +} diff --git a/lib/purify_arith_tactic.h b/lib/purify_arith_tactic.h new file mode 100644 index 000000000..32951431b --- /dev/null +++ b/lib/purify_arith_tactic.h @@ -0,0 +1,58 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + purify_arith_tactic.h + +Abstract: + + Tactic for eliminating arithmetic operators: DIV, IDIV, MOD, + TO_INT, and optionally (OP_IRRATIONAL_ALGEBRAIC_NUM). + + This tactic uses the simplifier for also eliminating: + OP_SUB, OP_UMINUS, OP_POWER (optionally), OP_REM, OP_IS_INT. + + Remarks: + - The semantics of division by zero is not specified. Thus, + uninterpreted functions are used. An ExRCF procedure may + treat the unintepreted function applications as fresh + constants. Then, in any model produced by this procedure, + the interpretation for division by zero must be checked. + + - POWER operator can only be handled if the second argument is a + rational value. The tactic has an option for preserving POWER + operator where the second argument is an integer. + + - The semantics of (^ t (/ 1 k)) is not specified when t < 0 and + k is even. Similarly to the division by zero case, + uninterpreted function symbols are created. + + - The semantics of (^ t 0) is not specified if t == 0. Thus, + uninterpreted function symbols are created. + + - TO_REAL is not really outside of the RCF language + since it is only used for "casting". + + - All quantifiers must occur with positive polarity. + The tactic snf (with skolemization disabled) is applied + to enforce that. + +Author: + + Leonardo de Moura (leonardo) 2011-12-30. + +Revision History: + +--*/ +#ifndef _PURIFY_ARITH_TACTIC_H_ +#define _PURIFY_ARITH_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_purify_arith_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif + diff --git a/lib/push_app_ite.cpp b/lib/push_app_ite.cpp new file mode 100644 index 000000000..cec84d1f5 --- /dev/null +++ b/lib/push_app_ite.cpp @@ -0,0 +1,220 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + push_app_ite.cpp + +Abstract: + + TODO: Write a better ite lifter + + +Author: + + Leonardo de Moura (leonardo) 2008-05-14. + +Revision History: + +--*/ +#include"push_app_ite.h" +#include"ast_pp.h" + +push_app_ite::push_app_ite(simplifier & s, bool conservative): + simplifier(s.get_manager()), + m_conservative(conservative) { + + borrow_plugins(s); +} + +push_app_ite::~push_app_ite() { + // the plugins were borrowed. So, release ownership. + m_plugins.release(); +} + +int push_app_ite::has_ite_arg(unsigned num_args, expr * const * args) { + for (unsigned i = 0; i < num_args; i++) + if (m_manager.is_ite(args[i])) + return i; + return -1; +} + +void push_app_ite::apply(func_decl * decl, unsigned num_args, expr * const * args, expr_ref & r) { + TRACE("push_app_ite", tout << "pushing app...\n";); + int ite_arg_idx = has_ite_arg(num_args, args); + if (ite_arg_idx < 0) { + mk_app(decl, num_args, args, r); + return; + } + app * ite = to_app(args[ite_arg_idx]); + expr * c = ite->get_arg(0); + expr * t = ite->get_arg(1); + expr * e = ite->get_arg(2); + expr ** args_prime = const_cast(args); + expr * old = args_prime[ite_arg_idx]; + args_prime[ite_arg_idx] = t; + expr_ref t_new(m_manager); + apply(decl, num_args, args_prime, t_new); + args_prime[ite_arg_idx] = e; + expr_ref e_new(m_manager); + apply(decl, num_args, args_prime, e_new); + args_prime[ite_arg_idx] = old; + expr * new_args[3] = { c, t_new, e_new }; + mk_app(ite->get_decl(), 3, new_args, r); +} + +/** + \brief Default (conservative) implementation. Return true if there one and only one ite-term argument. +*/ +bool push_app_ite::is_target(func_decl * decl, unsigned num_args, expr * const * args) { + if (m_manager.is_ite(decl)) + return false; + bool found_ite = false; + for (unsigned i = 0; i < num_args; i++) { + if (m_manager.is_ite(args[i]) && !m_manager.is_bool(args[i])) { + if (found_ite) { + if (m_conservative) + return false; + } + else { + found_ite = true; + } + } + } + CTRACE("push_app_ite", found_ite, tout << "found target for push app ite:\n"; + tout << decl->get_name(); + for (unsigned i = 0; i < num_args; i++) tout << " " << mk_pp(args[i], m_manager); + tout << "\n";); + return found_ite; +} + +void push_app_ite::operator()(expr * s, expr_ref & r, proof_ref & p) { + expr * result; + proof * result_proof; + reduce_core(s); + get_cached(s, result, result_proof); + r = result; + switch (m_manager.proof_mode()) { + case PGM_DISABLED: + p = m_manager.mk_undef_proof(); + break; + case PGM_COARSE: + if (result == s) + p = m_manager.mk_reflexivity(s); + else + p = m_manager.mk_rewrite_star(s, result, 0, 0); + break; + case PGM_FINE: + if (result == s) + p = m_manager.mk_reflexivity(s); + else + p = result_proof; + break; + } +} + +void push_app_ite::reduce_core(expr * n) { + if (!is_cached(n)) { + unsigned sz = m_todo.size(); + m_todo.push_back(n); + while (m_todo.size() != sz) { + expr * n = m_todo.back(); + if (is_cached(n)) + m_todo.pop_back(); + else if (visit_children(n)) { + m_todo.pop_back(); + reduce1(n); + } + } + } +} + +bool push_app_ite::visit_children(expr * n) { + bool visited = true; + unsigned j; + switch(n->get_kind()) { + case AST_VAR: + return true; + case AST_APP: + j = to_app(n)->get_num_args(); + while (j > 0) { + --j; + visit(to_app(n)->get_arg(j), visited); + } + return visited; + case AST_QUANTIFIER: + visit(to_quantifier(n)->get_expr(), visited); + return visited; + default: + UNREACHABLE(); + return true; + } +} + +void push_app_ite::reduce1(expr * n) { + switch (n->get_kind()) { + case AST_VAR: + cache_result(n, n, 0); + break; + case AST_APP: + reduce1_app(to_app(n)); + break; + case AST_QUANTIFIER: + reduce1_quantifier(to_quantifier(n)); + break; + default: + UNREACHABLE(); + } +} + +void push_app_ite::reduce1_app(app * n) { + m_args.reset(); + + func_decl * decl = n->get_decl(); + proof_ref p1(m_manager); + get_args(n, m_args, p1); + + expr_ref r(m_manager); + if (is_target(decl, m_args.size(), m_args.c_ptr())) + apply(decl, m_args.size(), m_args.c_ptr(), r); + else + mk_app(decl, m_args.size(), m_args.c_ptr(), r); + + if (!m_manager.fine_grain_proofs()) + cache_result(n, r, 0); + else { + expr * s = m_manager.mk_app(decl, m_args.size(), m_args.c_ptr()); + proof * p; + if (n == r) + p = 0; + else if (r != s) + p = m_manager.mk_transitivity(p1, m_manager.mk_rewrite(s, r)); + else + p = p1; + cache_result(n, r, p); + } +} + +void push_app_ite::reduce1_quantifier(quantifier * q) { + expr * new_body; + proof * new_body_pr; + get_cached(q->get_expr(), new_body, new_body_pr); + + quantifier * new_q = m_manager.update_quantifier(q, new_body); + proof * p = q == new_q ? 0 : m_manager.mk_quant_intro(q, new_q, new_body_pr); + cache_result(q, new_q, p); +} + +bool ng_push_app_ite::is_target(func_decl * decl, unsigned num_args, expr * const * args) { + bool r = push_app_ite::is_target(decl, num_args, args); + if (!r) + return false; + for (unsigned i = 0; i < num_args; i++) + if (!is_ground(args[i])) + return true; + return false; +} + +ng_push_app_ite::ng_push_app_ite(simplifier & s, bool conservative): + push_app_ite(s, conservative) { +} diff --git a/lib/push_app_ite.h b/lib/push_app_ite.h new file mode 100644 index 000000000..2d5c6bc28 --- /dev/null +++ b/lib/push_app_ite.h @@ -0,0 +1,63 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + push_app_ite.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-05-14. + +Revision History: + +--*/ +#ifndef _PUSH_APP_ITE_H_ +#define _PUSH_APP_ITE_H_ + +#include"ast.h" +#include"simplifier.h" + +/** + \brief Functor for applying the following transformation: + + (f s (ite c t1 t2)) ==> (ite c (f s t1) (f s t2)) +*/ +class push_app_ite : public simplifier { +protected: + bool m_conservative; + int has_ite_arg(unsigned num_args, expr * const * args); + void apply(func_decl * decl, unsigned num_args, expr * const * args, expr_ref & result); + virtual bool is_target(func_decl * decl, unsigned num_args, expr * const * args); + void reduce_core(expr * n); + bool visit_children(expr * n); + void reduce1(expr * n); + void reduce1_app(app * n); + void reduce1_quantifier(quantifier * q); + +public: + push_app_ite(simplifier & s, bool conservative = true); + virtual ~push_app_ite(); + void operator()(expr * s, expr_ref & r, proof_ref & p); +}; + +/** + \brief Variation of push_app_ite that applies the transformation on nonground terms only. + + \remark This functor uses the app::is_ground method. This method is not + completly precise, for instance, any term containing a quantifier is marked as non ground. +*/ +class ng_push_app_ite : public push_app_ite { +protected: + virtual bool is_target(func_decl * decl, unsigned num_args, expr * const * args); +public: + ng_push_app_ite(simplifier & s, bool conservative = true); + virtual ~ng_push_app_ite() {} +}; + +#endif /* _PUSH_APP_ITE_H_ */ + diff --git a/lib/qe.cpp b/lib/qe.cpp new file mode 100644 index 000000000..2c384596d --- /dev/null +++ b/lib/qe.cpp @@ -0,0 +1,2617 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + qe.cpp + +Abstract: + + Quantifier elimination procedures + +Author: + + Nikolaj Bjorner (nbjorner) 2010-02-19 + +Revision History: + + +--*/ + +#include "qe.h" +#include "smt_theory.h" +#include "bv_decl_plugin.h" +#include "smt_context.h" +#include "theory_bv.h" +#include "ast_ll_pp.h" +#include "ast_pp.h" +#include "ast_smt_pp.h" +#include "expr_abstract.h" +#include "var_subst.h" +#include "for_each_expr.h" +#include "dl_decl_plugin.h" +#include "nlarith_util.h" +#include "expr_replacer.h" +#include "factor_rewriter.h" +#include "expr_functors.h" +#include "quant_hoist.h" +#include "bool_rewriter.h" +#include "dl_util.h" +#include "th_rewriter.h" +#include "smt_solver.h" +#include "model_evaluator.h" +#include "has_free_vars.h" +#include "rewriter_def.h" +#include "cooperate.h" +#include "tactical.h" +#include "model_v2_pp.h" + + +namespace qe { + + class conjunctions { + ast_manager& m; + ptr_vector m_plugins; // family_id -> plugin + + public: + conjunctions(ast_manager& m) : m(m) {} + + void add_plugin(qe_solver_plugin* p) { + family_id fid = p->get_family_id(); + if (static_cast(m_plugins.size()) <= fid) { + m_plugins.resize(fid + 1); + } + m_plugins[fid] = p; + } + + void get_partition( + expr* fml, + unsigned num_vars, + app* const* vars, + expr_ref& fml_closed, // conjuncts that don't contain any variables from vars. + expr_ref& fml_mixed, // conjuncts that contain terms from vars and non-vars. + expr_ref& fml_open // conjuncts that contain vars (mixed or pure). + ) + { + expr_ref_vector conjs(m); + ast_mark visited; + ast_mark contains_var; + ast_mark contains_uf; + ptr_vector todo; + ptr_vector conjs_closed, conjs_mixed, conjs_open; + + datalog::flatten_and(fml, conjs); + + for (unsigned i = 0; i < conjs.size(); ++i) { + todo.push_back(conjs[i].get()); + } + while (!todo.empty()) { + expr* e = todo.back(); + if (visited.is_marked(e)) { + todo.pop_back(); + continue; + } + + if (is_var(to_app(e), num_vars, vars)) { + contains_var.mark(e, true); + visited.mark(e, true); + todo.pop_back(); + continue; + } + + if (!is_app(e)) { + visited.mark(e, true); + todo.pop_back(); + continue; + } + + bool all_visited = true; + app* a = to_app(e); + if (is_uninterpreted(a)) { + contains_uf.mark(e, true); + } + for (unsigned i = 0; i < a->get_num_args(); ++i) { + expr* arg = a->get_arg(i); + if (!visited.is_marked(arg)) { + all_visited = false; + todo.push_back(arg); + } + else { + if (contains_var.is_marked(arg)) { + contains_var.mark(e, true); + } + if (contains_uf.is_marked(arg)) { + contains_uf.mark(e, true); + } + } + } + if (all_visited) { + todo.pop_back(); + visited.mark(e, true); + } + } + for (unsigned i = 0; i < conjs.size(); ++i) { + expr* e = conjs[i].get(); + bool cv = contains_var.is_marked(e); + bool cu = contains_uf.is_marked(e); + if (cv && cu) { + conjs_mixed.push_back(e); + conjs_open.push_back(e); + } + else if (cv) { + conjs_open.push_back(e); + } + else { + conjs_closed.push_back(e); + } + } + bool_rewriter rewriter(m); + rewriter.mk_and(conjs_closed.size(), conjs_closed.c_ptr(), fml_closed); + rewriter.mk_and(conjs_mixed.size(), conjs_mixed.c_ptr(), fml_mixed); + rewriter.mk_and(conjs_open.size(), conjs_open.c_ptr(), fml_open); + + TRACE("qe", + tout << "closed\n" << mk_ismt2_pp(fml_closed, m) << "\n"; + tout << "open\n" << mk_ismt2_pp(fml_open, m) << "\n"; + tout << "mixed\n" << mk_ismt2_pp(fml_mixed, m) << "\n"; + ); + } + + // + // Partition variables into buckets. + // The var_paritions buckets covering disjoint subsets of + // the conjuncts. The remaining variables in vars are non-partioned. + // + bool partition_vars( + unsigned num_vars, + contains_app** vars, + unsigned num_args, + expr* const* args, + vector& partition + ) + { + unsigned_vector contains_index; + unsigned_vector non_shared; + unsigned_vector non_shared_vars; + union_find_default_ctx df; + union_find uf(df); + + partition.reset(); + + for (unsigned v = 0; v < num_vars; ++v) { + contains_app& contains_x = *vars[v]; + contains_index.reset(); + for (unsigned i = 0; i < num_args; ++i) { + if (contains_x(args[i])) { + contains_index.push_back(i); + TRACE("qe_verbose", tout << "var " << v << " in " << i << "\n";); + } + } + // + // x occurs in more than half of conjuncts. + // Mark it as shared. + // + if (2*contains_index.size() > num_args) { + if (partition.empty()) { + partition.push_back(unsigned_vector()); + } + partition.back().push_back(v); + TRACE("qe_verbose", tout << "majority " << v << "\n";); + continue; + } + // + // Create partition of variables that share conjuncts. + // + unsigned var_x = uf.mk_var(); + SASSERT(var_x == non_shared_vars.size()); + non_shared_vars.push_back(v); + for (unsigned i = 0; i < contains_index.size(); ++i) { + unsigned idx = contains_index[i]; + if (non_shared.size() <= idx) { + non_shared.resize(idx+1,UINT_MAX); + } + unsigned var_y = non_shared[idx]; + if (var_y != UINT_MAX) { + uf.merge(var_x, var_y); + } + else { + non_shared[idx] = var_x; + } + } + } + if (non_shared_vars.empty()) { + return false; + } + + unsigned root0 = uf.find(0); + bool is_partitioned = false; + for (unsigned idx = 1; !is_partitioned && idx < non_shared_vars.size(); ++idx) { + is_partitioned = (uf.find(idx) != root0); + } + if (!is_partitioned) { + return false; + } + + // + // variables are partitioned into more than one + // class. + // + + unsigned_vector roots; + if (!partition.empty()) { + roots.push_back(UINT_MAX); + } + for (unsigned idx = 0; idx < non_shared_vars.size(); ++idx) { + unsigned x = non_shared_vars[idx]; + unsigned r = non_shared_vars[uf.find(idx)]; + TRACE("qe_verbose", tout << "x: " << x << " r: " << r << "\n";); + bool found = false; + for (unsigned i = 0; !found && i < roots.size(); ++i) { + if (roots[i] == r) { + found = true; + partition[i].push_back(x); + } + } + if (!found) { + roots.push_back(r); + partition.push_back(unsigned_vector()); + partition.back().push_back(x); + } + } + + TRACE("qe_verbose", + for (unsigned i = 0; i < partition.size(); ++i) { + for (unsigned j = 0; j < partition[i].size(); ++j) { + tout << " " << mk_ismt2_pp(vars[partition[i][j]]->x(), m);; + } + tout << "\n"; + }); + + SASSERT(partition.size() != 1); + SASSERT(!partition.empty() || roots.size() > 1); + + return true; + } + + private: + + bool is_var(app* x, unsigned num_vars, app* const* vars) { + for (unsigned i = 0; i < num_vars; ++i) { + if (vars[i] == x) { + return true; + } + } + return false; + } + + bool is_uninterpreted(app* a) { + family_id fid = a->get_family_id(); + if (null_family_id == fid) { + return true; + } + if (static_cast(fid) >= m_plugins.size()) { + return true; + } + qe_solver_plugin* p = m_plugins[fid]; + if (!p) { + return true; + } + return p->is_uninterpreted(a); + } + + }; + + // --------------- + // conj_enum + + conj_enum::conj_enum(ast_manager& m, expr* e): m(m), m_conjs(m) { + datalog::flatten_and(e, m_conjs); + } + + void conj_enum::extract_equalities(expr_ref_vector& eqs) { + arith_util arith(m); + obj_hashtable leqs; + expr_ref_vector trail(m); + expr_ref tmp1(m), tmp2(m); + expr *a0, *a1; + eqs.reset(); + conj_enum::iterator it = begin(); + for (; it != end(); ++it) { + expr* e = *it; + + if (m.is_eq(e, a0, a1) && (arith.is_int(a0) || arith.is_real(a0))) { + tmp1 = arith.mk_sub(a0, a1); + eqs.push_back(tmp1); + } + else if (arith.is_le(e, a0, a1) || arith.is_ge(e, a1, a0)) { + tmp1 = arith.mk_sub(a0, a1); + tmp2 = arith.mk_sub(a1, a0); + + if (leqs.contains(tmp2)) { + eqs.push_back(tmp1); + TRACE("qe", tout << "found: " << mk_ismt2_pp(tmp1, m) << "\n";); + } + else { + trail.push_back(tmp1); + leqs.insert(tmp1); + TRACE("qe_verbose", tout << "insert: " << mk_ismt2_pp(tmp1, m) << "\n";); + } + } + else { + // drop equality. + } + } + } + + // ----------------- + // Lift ite from sub-formulas. + // + class lift_ite { + ast_manager& m; + i_expr_pred& m_is_relevant; + th_rewriter m_rewriter; + scoped_ptr m_replace; + public: + lift_ite(ast_manager& m, i_expr_pred& is_relevant) : + m(m), m_is_relevant(is_relevant), m_rewriter(m), m_replace(mk_default_expr_replacer(m)) {} + + bool operator()(expr* fml, expr_ref& result) { + if (m.is_ite(fml)) { + result = fml; + return true; + } + app* ite; + if (find_ite(fml, ite)) { + expr* cond, *th, *el; + VERIFY(m.is_ite(ite, cond, th, el)); + expr_ref tmp1(fml, m), tmp2(fml, m); + m_replace->apply_substitution(ite, th, tmp1); + m_replace->apply_substitution(ite, el, tmp2); + result = m.mk_ite(cond, tmp1, tmp2); + m_rewriter(result); + return true; + } + else { + return false; + } + } + + private: + + bool find_ite(expr* e, app*& ite) { + ptr_vector todo; + ast_mark visited; + todo.push_back(e); + while(!todo.empty()) { + e = todo.back(); + todo.pop_back(); + if (visited.is_marked(e)) { + continue; + } + visited.mark(e, true); + if (!m_is_relevant(e)) { + continue; + } + if (m.is_ite(e)) { + ite = to_app(e); + return true; + } + if (is_app(e)) { + app* a = to_app(e); + unsigned num_args = a->get_num_args(); + for (unsigned i = 0; i < num_args; ++i) { + todo.push_back(a->get_arg(i)); + } + } + } + return false; + } + }; + + // convert formula to NNF. + class nnf { + ast_manager& m; + i_expr_pred& m_is_relevant; + lift_ite m_lift_ite; + obj_map m_pos, m_neg; // memoize positive/negative sub-formulas + expr_ref_vector m_trail; // trail for generated terms + expr_ref_vector m_args; + ptr_vector m_todo; // stack of formulas to visit + svector m_pols; // stack of polarities + bool_rewriter m_rewriter; + + public: + nnf(ast_manager& m, i_expr_pred& is_relevant): + m(m), + m_is_relevant(is_relevant), + m_lift_ite(m, is_relevant), + m_trail(m), + m_args(m), + m_rewriter(m) {} + + void operator()(expr_ref& fml) { + reset(); + get_nnf(fml, true); + } + + void reset() { + m_todo.reset(); + m_trail.reset(); + m_pols.reset(); + m_pos.reset(); + m_neg.reset(); + } + + private: + + bool contains(expr* e, bool p) { + return p?m_pos.contains(e):m_neg.contains(e); + } + + expr* lookup(expr* e, bool p) { + expr* r = 0; + if (p && m_pos.find(e, r)) { + return r; + } + if (!p && m_neg.find(e, r)) { + return r; + } + m_todo.push_back(e); + m_pols.push_back(p); + return 0; + } + + void insert(expr* e, bool p, expr* r) { + if (p) { + m_pos.insert(e, r); + } + else { + m_neg.insert(e, r); + } + TRACE("nnf", + tout << mk_ismt2_pp(e, m) << " " << p << "\n"; + tout << mk_ismt2_pp(r, m) << "\n";); + + m_trail.push_back(r); + } + + void pop() { + m_todo.pop_back(); + m_pols.pop_back(); + } + + void nnf_iff(app* a, bool p) { + SASSERT(m.is_iff(a) || m.is_xor(a)); + expr* a0 = a->get_arg(0); + expr* a1 = a->get_arg(1); + + expr* r1 = lookup(a0, true); + expr* r2 = lookup(a0, false); + expr* p1 = lookup(a1, true); + expr* p2 = lookup(a1, false); + if (r1 && r2 && p1 && p2) { + expr_ref tmp1(m), tmp2(m), tmp(m); + pop(); + if (p) { + m_rewriter.mk_and(r1, p1, tmp1); + m_rewriter.mk_and(r2, p2, tmp2); + m_rewriter.mk_or(tmp1, tmp2, tmp); + } + else { + m_rewriter.mk_or(r1, p1, tmp1); + m_rewriter.mk_or(r2, p2, tmp2); + m_rewriter.mk_and(tmp1, tmp2, tmp); + } + insert(a, p, tmp); + } + } + + void nnf_ite(app* a, bool p) { + SASSERT(m.is_ite(a)); + expr* r1 = lookup(a->get_arg(0), true); + expr* r2 = lookup(a->get_arg(0), false); + expr* th = lookup(a->get_arg(1), p); + expr* el = lookup(a->get_arg(2), p); + if (r1 && r2 && th && el) { + expr_ref tmp1(m), tmp2(m), tmp(m); + pop(); + m_rewriter.mk_and(r1, th, tmp1); + m_rewriter.mk_and(r2, el, tmp2); + m_rewriter.mk_or(tmp1, tmp2, tmp); + TRACE("nnf", + tout << mk_ismt2_pp(a, m) << "\n"; + tout << mk_ismt2_pp(tmp, m) << "\n";); + insert(a, p, tmp); + } + } + + void nnf_implies(app* a, bool p) { + SASSERT(m.is_implies(a)); + expr* r1 = lookup(a->get_arg(0), !p); + expr* r2 = lookup(a->get_arg(1), p); + if (r1 && r2) { + expr_ref tmp(m); + if (p) { + m_rewriter.mk_or(r1, r2, tmp); + } + else { + m_rewriter.mk_and(r1, r2, tmp); + } + insert(a, p, tmp); + } + } + + void nnf_not(app* a, bool p) { + SASSERT(m.is_not(a)); + expr* arg = a->get_arg(0); + expr* e = lookup(arg, !p); + if (e) { + insert(a, p, e); + } + } + + void nnf_and_or(bool is_and, app* a, bool p) { + m_args.reset(); + unsigned num_args = a->get_num_args(); + expr_ref tmp(m); + bool visited = true; + for (unsigned i = 0; i < num_args; ++i) { + expr* arg = a->get_arg(i); + expr* r = lookup(arg, p); + if (r) { + m_args.push_back(r); + } + else { + visited = false; + } + } + if (visited) { + pop(); + if ((p && is_and) || (!p && !is_and)) { + m_rewriter.mk_and(num_args, m_args.c_ptr(), tmp); + } + else { + m_rewriter.mk_or(num_args, m_args.c_ptr(), tmp); + } + insert(a, p, tmp); + } + } + + bool get_nnf(expr_ref& fml, bool p0) { + TRACE("nnf", tout << mk_ismt2_pp(fml.get(), m) << "\n";); + bool p = p0; + unsigned sz = m_todo.size(); + expr_ref tmp(m); + + expr* e = lookup(fml, p); + if (e) { + fml = e; + return true; + } + m_trail.push_back(fml); + + while (sz < m_todo.size()) { + e = m_todo.back(); + p = m_pols.back(); + if (!m_is_relevant(e)) { + pop(); + insert(e, p, p?e:m.mk_not(e)); + continue; + } + if (!is_app(e)) { + return false; + } + if (contains(e, p)) { + pop(); + continue; + } + app* a = to_app(e); + if (m.is_and(e) || m.is_or(e)) { + nnf_and_or(m.is_and(a), a, p); + } + else if (m.is_not(a)) { + nnf_not(a, p); + } + else if (m.is_ite(a)) { + nnf_ite(a, p); + } + else if (m.is_iff(a)) { + nnf_iff(a, p); + } + else if (m.is_xor(a)) { + nnf_iff(a, !p); + } + else if (m.is_implies(a)) { + nnf_implies(a, p); + } + else if (m_lift_ite(e, tmp)) { + if (!get_nnf(tmp, p)) { + return false; + } + pop(); + insert(e, p, tmp); + } + else { + pop(); + insert(e, p, p?e:m.mk_not(e)); + } + + } + fml = lookup(fml, p0); + SASSERT(fml.get()); + return true; + } + }; + + // ---------------------------------- + // Normalize literals in NNF formula. + + class nnf_normalize_literals { + ast_manager& m; + i_expr_pred& m_is_relevant; + i_nnf_atom& m_mk_atom; + obj_map m_cache; + ptr_vector m_todo; + expr_ref_vector m_trail; + ptr_vector m_args; + public: + nnf_normalize_literals(ast_manager& m, i_expr_pred& is_relevant, i_nnf_atom& mk_atom): + m(m), m_is_relevant(is_relevant), m_mk_atom(mk_atom), m_trail(m) {} + + void operator()(expr_ref& fml) { + SASSERT(m_todo.empty()); + m_todo.push_back(fml); + while (!m_todo.empty()) { + expr* e = m_todo.back(); + if (m_cache.contains(e)) { + m_todo.pop_back(); + } + else if (!is_app(e)) { + m_todo.pop_back(); + m_cache.insert(e, e); + } + else if (visit(to_app(e))) { + m_todo.pop_back(); + } + } + fml = m_cache.find(fml); + reset(); + } + + void reset() { + m_cache.reset(); + m_todo.reset(); + m_trail.reset(); + } + + private: + + bool visit(app* e) { + bool all_visit = true; + expr* f = 0; + expr_ref tmp(m); + if (!m_is_relevant(e)) { + m_cache.insert(e, e); + } + else if (m.is_and(e) || m.is_or(e)) { + m_args.reset(); + for (unsigned i = 0; i < e->get_num_args(); ++i) { + if (m_cache.find(e->get_arg(i), f)) { + m_args.push_back(f); + } + else { + all_visit = false; + m_todo.push_back(e->get_arg(i)); + } + } + if (all_visit) { + m_cache.insert(e, m.mk_app(e->get_decl(), m_args.size(), m_args.c_ptr())); + } + } + else if (m.is_not(e, f)) { + SASSERT(!m.is_not(f) && !m.is_and(f) && !m.is_or(f)); + m_mk_atom(f, false, tmp); + m_cache.insert(e, tmp); + m_trail.push_back(tmp); + } + else { + m_mk_atom(e, true, tmp); + m_trail.push_back(tmp); + m_cache.insert(e, tmp); + } + return all_visit; + } + }; + + // ---------------------------- + // Obtain atoms in NNF formula. + + class nnf_collect_atoms { + ast_manager& m; + i_expr_pred& m_is_relevant; + ptr_vector m_todo; + ast_mark m_visited; + public: + nnf_collect_atoms(ast_manager& m, i_expr_pred& is_relevant): + m(m), m_is_relevant(is_relevant) {} + + void operator()(expr* fml, atom_set& pos, atom_set& neg) { + m_todo.push_back(fml); + while (!m_todo.empty()) { + expr* e = m_todo.back(); + m_todo.pop_back(); + if (m_visited.is_marked(e)) { + continue; + } + m_visited.mark(e, true); + if (!is_app(e) || !m_is_relevant(e)) { + continue; + } + app* a = to_app(e); + if (m.is_and(a) || m.is_or(a)) { + for (unsigned i = 0; i < a->get_num_args(); ++i) { + m_todo.push_back(a->get_arg(i)); + } + } + else if (m.is_not(a, e) && is_app(e)) { + neg.insert(to_app(e)); + } + else { + pos.insert(a); + } + } + SASSERT(m_todo.empty()); + m_visited.reset(); + } + }; + + + // -------------------------------- + // Bring formula to NNF, normalize atoms, collect literals. + + class nnf_normalizer { + nnf m_nnf_core; + nnf_collect_atoms m_collect_atoms; + nnf_normalize_literals m_normalize_literals; + public: + nnf_normalizer(ast_manager& m, i_expr_pred& is_relevant, i_nnf_atom& mk_atom): + m_nnf_core(m, is_relevant), + m_normalize_literals(m, is_relevant, mk_atom), + m_collect_atoms(m, is_relevant) + {} + + void operator()(expr_ref& fml, atom_set& pos, atom_set& neg) { + expr_ref orig(fml); + ast_manager& m = fml.get_manager(); + m_nnf_core(fml); + m_normalize_literals(fml); + m_collect_atoms(fml, pos, neg); + TRACE("qe", tout << mk_ismt2_pp(orig, m) << "\n-->\n" << mk_ismt2_pp(fml, m) << "\n";); + } + + void reset() { + m_nnf_core.reset(); + m_normalize_literals.reset(); + } + }; + + void get_nnf(expr_ref& fml, i_expr_pred& pred, i_nnf_atom& mk_atom, atom_set& pos, atom_set& neg) { + nnf_normalizer nnf(fml.get_manager(), pred, mk_atom); + nnf(fml, pos, neg); + } + + // + // Theory plugin for quantifier elimination. + // + + class quant_elim { + public: + virtual ~quant_elim() {} + + virtual lbool eliminate_exists( + unsigned num_vars, app* const* vars, + expr_ref& fml, app_ref_vector& free_vars, bool get_first, def_vector* defs) = 0; + + virtual void set_assumption(expr* fml) = 0; + + virtual void collect_statistics(statistics & st) const = 0; + + virtual void eliminate(bool is_forall, unsigned num_vars, app* const* vars, expr_ref& fml) = 0; + + virtual void set_cancel(bool f) = 0; + + virtual void updt_params(params_ref const& p) {} + + }; + + class search_tree { + typedef map branch_map; + ast_manager& m; + app_ref_vector m_vars; // free variables + app_ref m_var; // 0 or selected free variable + def_vector m_def; // substitution for the variable eliminated relative to the parent. + expr_ref m_fml; // formula whose variables are to be eliminated + app_ref m_assignment; // assignment that got us here. + search_tree* m_parent; // parent pointer + rational m_num_branches; // number of possible branches + ptr_vector m_children; // list of children + branch_map m_branch_index; // branch_id -> child search tree index + atom_set m_pos; + atom_set m_neg; + bool m_pure; // is the node pure (no variables deleted). + + // The invariant captures that search tree nodes are either + // - unit branches (with only one descendant), or + // - unassigned variable and formula + // - assigned formula, but unassigned variable for branching + // - assigned variable and formula with 0 or more branches. + // +#define CHECK_COND(_cond_) if (!(_cond_)) { TRACE("qe", tout << "violated: " << #_cond_ << "\n";); return false; } + bool invariant() const { + CHECK_COND(assignment()); + CHECK_COND(m_children.empty() || fml()); + CHECK_COND(!is_root() || fml()); + CHECK_COND(!fml() || has_var() || m_num_branches.is_zero() || is_unit()); + branch_map::iterator it = m_branch_index.begin(), end = m_branch_index.end(); + for (; it != end; ++it) { + CHECK_COND(it->m_value < m_children.size()); + CHECK_COND(it->m_key < get_num_branches()); + } + for (unsigned i = 0; i < m_children.size(); ++i) { + CHECK_COND(m_children[i]); + CHECK_COND(this == m_children[i]->m_parent); + CHECK_COND(m_children[i]->invariant()); + } + return true; + } +#undef CHECKC_COND + + public: + search_tree(search_tree* parent, ast_manager& m, app* assignment): + m(m), + m_vars(m), + m_var(m), + m_def(m), + m_fml(m), + m_assignment(assignment, m), + m_parent(parent), + m_pure(true) + {} + + ~search_tree() { + reset(); + } + + expr* fml() const { return m_fml; } + + expr_ref& fml_ref() { return m_fml; } + + def_vector const& def() const { return m_def; } + + app* assignment() const { return m_assignment; } + + app* var() const { SASSERT(has_var()); return m_var; } + + bool has_var() const { return 0 != m_var.get(); } + + search_tree* parent() const { return m_parent; } + + bool is_root() const { return !parent(); } + + rational const& get_num_branches() const { SASSERT(has_var()); return m_num_branches; } + + // extract disjunctions + void get_leaves(expr_ref_vector& result) { + SASSERT(is_root()); + ptr_vector todo; + todo.push_back(this); + while (!todo.empty()) { + search_tree* st = todo.back(); + todo.pop_back(); + if (st->m_children.empty() && st->fml() && + st->m_vars.empty() && !st->has_var()) { + result.push_back(st->fml()); + } + for (unsigned i = 0; i < st->m_children.size(); ++i) { + todo.push_back(st->m_children[i]); + } + } + } + + bool get_leaf_rec(expr_ref& fml, def_vector& defs) { + expr* f = this->fml(); + if (m_children.empty() && f && !m.is_false(f) && + m_vars.empty() && !has_var()) { + fml = f; + return true; + } + unsigned sz = defs.size(); + for (unsigned i = 0; i < m_children.size(); ++i) { + search_tree* st = m_children[i]; + defs.append(st->def()); + if (st->get_leaf_rec(fml, defs)) { + return true; + } + defs.shrink(sz); + } + return false; + } + + void get_leaf(expr_ref& fml, def_vector& defs) { + get_leaf_rec(fml, defs); + + // apply nested definitions into place. + expr_substitution sub(m); + scoped_ptr rep = mk_expr_simp_replacer(m); + for (unsigned i = defs.size(); i > 0; ) { + --i; + expr_ref e(m); + e = defs.def(i); + rep->set_substitution(&sub); + (*rep)(e); + sub.insert(m.mk_const(defs.var(i)), e); + defs.def_ref(i) = e; + } + } + + void reset() { + TRACE("qe",tout << "resetting\n";); + for (unsigned i = 0; i < m_children.size(); ++i) { + dealloc(m_children[i]); + } + m_pos.reset(); + m_neg.reset(); + m_children.reset(); + m_vars.reset(); + m_branch_index.reset(); + m_var = 0; + m_def.reset(); + m_num_branches = rational::zero(); + m_pure = true; + } + + void init(expr* fml) { + m_fml = fml; + SASSERT(invariant()); + } + + void add_def(app* v, expr* def) { + if (v && def) { + m_def.push_back(v->get_decl(), def); + } + } + + unsigned num_free_vars() const { return m_vars.size(); } + app* const* free_vars() const { return m_vars.c_ptr(); } + app* free_var(unsigned i) const { return m_vars[i]; } + void reset_free_vars() { m_vars.reset(); } + + atom_set const& pos_atoms() const { return m_pos; } + atom_set const& neg_atoms() const { return m_neg; } + + atom_set& pos_atoms() { return m_pos; } + atom_set& neg_atoms() { return m_neg; } + + // set the branch variable. + void set_var(app* x, rational const& num_branches) { + SASSERT(!m_var.get()); + SASSERT(m_vars.contains(x)); + m_var = x; + m_vars.erase(x); + m_num_branches = num_branches; + SASSERT(invariant()); + } + + // include new variables. + void consume_vars(app_ref_vector& vars) { + while (!vars.empty()) { + m_vars.push_back(vars.back()); + vars.pop_back(); + } + } + + bool is_pure() const { + search_tree const* node = this; + while (node) { + if (!node->m_pure) return false; + node = node->parent(); + } + return true; + } + + + bool is_unit() const { + return m_children.size() == 1 && + m_branch_index.empty(); + } + + bool has_branch(rational const& branch_id) const { return m_branch_index.contains(branch_id); } + + search_tree* child(rational const& branch_id) const { + unsigned idx; + VERIFY(m_branch_index.find(branch_id, idx)); + return m_children[idx]; + } + + search_tree* child() const { + SASSERT(is_unit()); + return m_children[0]; + } + + // remove variable from branch. + void del_var(app* x) { + SASSERT(m_children.empty()); + SASSERT(m_vars.contains(x)); + m_vars.erase(x); + m_pure = false; + } + + // add branch with a given formula + search_tree* add_child(expr* fml) { + SASSERT(m_branch_index.empty()); + SASSERT(m_children.empty()); + m_num_branches = rational(1); + search_tree* st = alloc(search_tree, this, m, m.mk_true()); + m_children.push_back(st); + st->init(fml); + st->m_vars.append(m_vars.size(), m_vars.c_ptr()); + SASSERT(invariant()); + return st; + } + + search_tree* add_child(rational const& branch_id, app* assignment) { + SASSERT(!m_branch_index.contains(branch_id)); + SASSERT(has_var()); + SASSERT(branch_id.is_nonneg() && branch_id < m_num_branches); + unsigned index = m_children.size(); + search_tree* st = alloc(search_tree, this, m, assignment); + m_children.push_back(st); + m_branch_index.insert(branch_id, index); + st->m_vars.append(m_vars.size(), m_vars.c_ptr()); + SASSERT(invariant()); + return st; + } + + void display(std::ostream& out) const { + display(out, ""); + } + + void display(std::ostream& out, char const* indent) const { + + out << indent << "node\n"; + if (m_var) { + out << indent << " var: " << mk_ismt2_pp(m_var.get(), m) << "\n"; + } + for (unsigned i = 0; i < m_vars.size(); ++i) { + out << indent << " free: " << mk_ismt2_pp(m_vars[i], m) << "\n"; + } + if (m_fml) { + out << indent << " fml: " << mk_ismt2_pp(m_fml.get(), m) << "\n"; + } + for (unsigned i = 0; i < m_def.size(); ++i) { + out << indent << " def: " << m_def.var(i)->get_name() << " = " << mk_ismt2_pp(m_def.def(i), m) << "\n"; + } + out << indent << " branches: " << m_num_branches << "\n"; + + std::string new_indent(indent); + new_indent += " "; + for (unsigned i = 0; i < m_children.size(); ++i) { + m_children[i]->display(out, new_indent.c_str()); + } + } + + void display_validate(std::ostream& out) const { + if (m_children.empty()) { + return; + } + expr_ref q(m); + expr* x = m_var; + if (x) { + expr_abstract(m, 0, 1, &x, m_fml, q); + ptr_vector fmls; + for (unsigned i = 0; i < m_children.size(); ++i) { + expr* fml = m_children[i]->fml(); + if (fml) { + fmls.push_back(fml); + } + } + symbol X(m_var->get_decl()->get_name()); + sort* s = m.get_sort(x); + q = m.mk_exists(1, &s, &X, q); + expr_ref tmp(m); + bool_rewriter(m).mk_or(fmls.size(), fmls.c_ptr(), tmp); + expr_ref f(m.mk_not(m.mk_iff(q, tmp)), m); + ast_smt_pp pp(m); + out << "(echo " << m_var->get_decl()->get_name() << ")\n"; + out << "(push)\n"; + pp.display_smt2(out, f); + out << "(pop)\n\n"; + } + for (unsigned i = 0; i < m_children.size(); ++i) { + if (m_children[i]->fml()) { + m_children[i]->display_validate(out); + } + } + } + }; + + // ------------------------- + // i_solver_context + + i_solver_context::~i_solver_context() { + for (unsigned i = 0; i < m_plugins.size(); ++i) { + dealloc(m_plugins[i]); + } + } + + bool i_solver_context::is_relevant::operator()(expr* e) { + for (unsigned i = 0; i < m_s.get_num_vars(); ++i) { + if (m_s.contains(i)(e)) { + return true; + } + } + TRACE("qe_verbose", tout << "Not relevant: " << mk_ismt2_pp(e, m_s.get_manager()) << "\n";); + return false; + } + + bool i_solver_context::is_var(expr* x, unsigned& idx) const { + unsigned nv = get_num_vars(); + for (unsigned i = 0; i < nv; ++i) { + if (get_var(i) == x) { + idx = i; + return true; + } + } + return false; + } + + void i_solver_context::add_plugin(qe_solver_plugin* p) { + family_id fid = p->get_family_id(); + SASSERT(fid != null_family_id); + if (static_cast(m_plugins.size()) <= fid) { + m_plugins.resize(fid+1,0); + } + SASSERT(!m_plugins[fid]); + m_plugins[fid] = p; + } + + bool i_solver_context::has_plugin(app* x) { + ast_manager& m = get_manager(); + family_id fid = m.get_sort(x)->get_family_id(); + return + 0 <= fid && + fid < static_cast(m_plugins.size()) && + m_plugins[fid] != 0; + } + + qe_solver_plugin& i_solver_context::plugin(app* x) { + ast_manager& m = get_manager(); + SASSERT(has_plugin(x)); + return *(m_plugins[m.get_sort(x)->get_family_id()]); + } + + void i_solver_context::mk_atom(expr* e, bool p, expr_ref& result) { + ast_manager& m = get_manager(); + TRACE("qe_verbose", tout << mk_ismt2_pp(e, m) << "\n";); + for (unsigned i = 0; i < m_plugins.size(); ++i) { + qe_solver_plugin* pl = m_plugins[i]; + if (pl && pl->mk_atom(e, p, result)) { + return; + } + } + TRACE("qe_verbose", tout << "No plugin for " << mk_ismt2_pp(e, m) << "\n";); + if (p || m.is_not(e, e)) { + result = e; + } + else { + result = m.mk_not(e); + } + } + + void i_solver_context::mk_atom_fn::operator()(expr* e, bool p, expr_ref& result) { + m_s.mk_atom(e, p, result); + } + + typedef ref_vector_ptr_hash expr_ref_vector_hash; + typedef ref_vector_ptr_eq expr_ref_vector_eq; + typedef hashtable clause_table; + typedef value_trail _value_trail; + + + class quant_elim_plugin : public i_solver_context { + + ast_manager& m; + quant_elim& m_qe; + th_rewriter m_rewriter; + smt::solver m_solver; + bv_util m_bv; + expr_ref_vector m_literals; + + bool_rewriter m_bool_rewriter; + conjunctions m_conjs; + + // maintain queue for variables. + + app_ref_vector m_free_vars; // non-quantified variables + app_ref_vector m_trail; + + expr_ref m_fml; + expr_ref m_subfml; + + obj_map m_var2branch; // var -> bv-var, identify explored branch. + obj_map m_var2contains; // var -> contains_app + obj_map > m_children; // var -> list of dependent children + search_tree m_root; + search_tree* m_current; // current branch + + vector m_partition; // cached latest partition of variables. + + app_ref_vector m_new_vars; // variables added by solvers + bool m_get_first; // get first satisfying branch. + def_vector* m_defs; + nnf_normalizer m_nnf; // nnf conversion + + + public: + + quant_elim_plugin(ast_manager& m, quant_elim& qe, front_end_params& p): + m(m), + m_qe(qe), + m_rewriter(m), + m_solver(m, p), + m_bv(m), + m_literals(m), + m_bool_rewriter(m), + m_conjs(m), + m_free_vars(m), + m_trail(m), + m_fml(m), + m_subfml(m), + m_root(0, m, m.mk_true()), + m_current(0), + m_new_vars(m), + m_get_first(false), + m_defs(0), + m_nnf(m, get_is_relevant(), get_mk_atom()) + { + params_ref params; + params.set_bool(":gcd-rounding", true); + m_rewriter.updt_params(params); + } + + virtual ~quant_elim_plugin() { + reset(); + } + + void reset() { + m_free_vars.reset(); + m_trail.reset(); + obj_map::iterator it = m_var2contains.begin(), end = m_var2contains.end(); + for (; it != end; ++it) { + dealloc(it->m_value); + } + m_var2contains.reset(); + m_var2branch.reset(); + m_root.reset(); + m_new_vars.reset(); + m_fml = 0; + m_defs = 0; + m_nnf.reset(); + } + + void add_plugin(qe_solver_plugin* p) { + i_solver_context::add_plugin(p); + m_conjs.add_plugin(p); + } + + void set_cancel(bool f) { + m_solver.set_cancel(f); + m_rewriter.set_cancel(f); + } + + void check(unsigned num_vars, app* const* vars, + expr* assumption, expr_ref& fml, bool get_first, + app_ref_vector& free_vars, def_vector* defs) { + + reset(); + m_solver.push(); + m_get_first = get_first; + m_defs = defs; + for (unsigned i = 0; i < num_vars; ++i) { + if (has_plugin(vars[i])) { + add_var(vars[i]); + } + else { + m_free_vars.push_back(vars[i]); + } + } + m_root.consume_vars(m_new_vars); + m_current = &m_root; + + // set sub-formula + m_fml = fml; + normalize(m_fml, m_root.pos_atoms(), m_root.neg_atoms()); + expr_ref f(m_fml); + get_max_relevant(get_is_relevant(), f, m_subfml); + if (f.get() != m_subfml.get()) { + m_fml = f; + f = m_subfml; + m_solver.assert_expr(f); + } + m_root.init(f); + TRACE("qe", + for (unsigned i = 0; i < num_vars; ++i) tout << mk_ismt2_pp(vars[i], m) << "\n"; + tout << mk_ismt2_pp(f, m) << "\n";); + + m_solver.assert_expr(m_fml); + if (assumption) m_solver.assert_expr(assumption); + bool is_sat = false; + while (l_false != m_solver.check()) { + is_sat = true; + model_ref model; + m_solver.get_model(model); + TRACE("qe", model_v2_pp(tout, *model);); + model_evaluator model_eval(*model); + final_check(model_eval); + } + + if (!is_sat) { + fml = m.mk_false(); + reset(); + m_solver.pop(1); + return; + } + + if (!get_first) { + expr_ref_vector result(m); + m_root.get_leaves(result); + m_bool_rewriter.mk_or(result.size(), result.c_ptr(), fml); + } + else if (defs) { + m_root.get_leaf(fml, *defs); + } + + TRACE("qe", + tout << "result:" << mk_ismt2_pp(fml, m) << "\n"; + tout << "input: " << mk_ismt2_pp(m_fml, m) << "\n"; + tout << "subformula: " << mk_ismt2_pp(m_subfml, m) << "\n"; + m_root.display(tout); + m_root.display_validate(tout); + for (unsigned i = 0; i < m_free_vars.size(); ++i) tout << mk_ismt2_pp(m_free_vars[i].get(), m) << " "; + tout << "\n"; + ); + + free_vars.append(m_free_vars); + SASSERT(!m_free_vars.empty() || m_solver.inconsistent()); + + if (m_fml.get() != m_subfml.get()) { + scoped_ptr rp = mk_default_expr_replacer(m); + rp->apply_substitution(to_app(m_subfml.get()), fml, m_fml); + fml = m_fml; + } + reset(); + m_solver.pop(1); + f = 0; + } + + void collect_statistics(statistics& st) { + m_solver.collect_statistics(st); + } + + private: + + void final_check(model_evaluator& model_eval) { + TRACE("qe", tout << (m_fml?"fml":"null") << "\n";); + while (can_propagate_assignment(model_eval)) { + propagate_assignment(model_eval); + } + VERIFY(CHOOSE_VAR == update_current(model_eval, true)); + SASSERT(m_current->fml()); + pop(model_eval); + } + + ast_manager& get_manager() { return m; } + + atom_set const& pos_atoms() const { return m_current->pos_atoms(); } + + atom_set const& neg_atoms() const { return m_current->neg_atoms(); } + + unsigned get_num_vars() const { return m_current->num_free_vars(); } + + app* get_var(unsigned idx) const { return m_current->free_var(idx); } + + app* const* get_vars() const { return m_current->free_vars(); } + + contains_app& contains(unsigned idx) { return contains(get_var(idx)); } + + // + // The variable at idx is eliminated (without branching). + // + void elim_var(unsigned idx, expr* _fml, expr* def) { + app* x = get_var(idx); + expr_ref fml(_fml, m); + TRACE("qe", tout << mk_pp(x,m) << " " << mk_pp(def, m) << "\n";); + m_current->set_var(x, rational(1)); + m_current = m_current->add_child(fml); + m_current->add_def(x, def); + m_current->consume_vars(m_new_vars); + normalize(*m_current); + } + + void add_var(app* x) { + m_new_vars.push_back(x); + if (m_var2branch.contains(x)) { + return; + } + contains_app* ca = alloc(contains_app, m, x); + m_var2contains.insert(x, ca); + app* bv = 0; + if (m.is_bool(x) || m_bv.is_bv(x)) { + bv = x; + } + else { + bv = m.mk_fresh_const("b", m_bv.mk_sort(20)); + m_trail.push_back(bv); + } + TRACE("qe", tout << "Add branch var: " << mk_ismt2_pp(x, m) << " " << mk_ismt2_pp(bv, m) << "\n";); + m_var2branch.insert(x, bv); + } + + virtual void add_constraint(bool use_current_val, expr* l1 = 0, expr* l2 = 0, expr* l3 = 0) { + search_tree* node = m_current; + if (!use_current_val) { + node = m_current->parent(); + } + m_literals.reset(); + while (node) { + m_literals.push_back(m.mk_not(node->assignment())); + node = node->parent(); + } + add_literal(l1); + add_literal(l2); + add_literal(l3); + expr_ref fml(m); + fml = m.mk_or(m_literals.size(), m_literals.c_ptr()); + TRACE("qe", tout << mk_ismt2_pp(fml, m) << "\n";); + m_solver.assert_expr(fml); + } + + void blast_or(app* var, expr_ref& fml) { + m_qe.eliminate_exists(1, &var, fml, m_free_vars, false, 0); + } + + lbool eliminate_exists(unsigned num_vars, app* const* vars, expr_ref& fml, bool get_first, def_vector* defs) { + return m_qe.eliminate_exists(num_vars, vars, fml, m_free_vars, get_first, defs); + } + + private: + + void add_literal(expr* l) { + if (l != 0) { + m_literals.push_back(l); + } + } + + void get_max_relevant(i_expr_pred& is_relevant, expr_ref& fml, expr_ref& subfml) { + if (m.is_and(fml) || m.is_or(fml)) { + app* a = to_app(fml); + unsigned num_args = a->get_num_args(); + ptr_buffer r_args; + ptr_buffer i_args; + for (unsigned i = 0; i < num_args; ++i) { + expr* arg = a->get_arg(i); + if (is_relevant(arg)) { + r_args.push_back(arg); + } + else { + i_args.push_back(arg); + } + } + if (r_args.empty() || i_args.empty()) { + subfml = fml; + } + else if (r_args.size() == 1) { + expr_ref tmp(r_args[0], m); + get_max_relevant(is_relevant, tmp, subfml); + i_args.push_back(tmp); + fml = m.mk_app(a->get_decl(), i_args.size(), i_args.c_ptr()); + } + else { + subfml = m.mk_app(a->get_decl(), r_args.size(), r_args.c_ptr()); + i_args.push_back(subfml); + fml = m.mk_app(a->get_decl(), i_args.size(), i_args.c_ptr()); + } + } + else { + subfml = fml; + } + } + + app* get_branch_id(app* x) { + app* result = 0; + VERIFY (m_var2branch.find(x, result)); + return result; + } + + bool extract_partition(ptr_vector& vars) { + if (m_partition.empty()) { + return false; + } + + unsigned_vector& vec = m_partition.back();; + for (unsigned i = 0; i < vec.size(); ++i) { + vars.push_back(m_current->free_var(vec[i])); + } + m_partition.pop_back(); + return true; + } + + enum update_status { CHOOSE_VAR, NEED_PROPAGATION }; + + update_status update_current(model_evaluator& model_eval, bool apply) { + SASSERT(m_fml); + m_current = &m_root; + rational branch, nb; + while (true) { + SASSERT(m_current->fml()); + if (m_current->is_unit()) { + m_current = m_current->child(); + continue; + } + if (!m_current->has_var()) { + return CHOOSE_VAR; + } + + app* x = m_current->var(); + app* b = get_branch_id(x); + nb = m_current->get_num_branches(); + expr_ref fml(m_current->fml(), m); + if (!eval(model_eval, b, branch) || branch >= nb) { + branch = rational::zero(); + } + SASSERT(!branch.is_neg()); + if (!m_current->has_branch(branch)) { + if (apply) { + app_ref assignment(mk_eq_value(b, branch), m); + m_current = m_current->add_child(branch, assignment); + plugin(x).assign(contains(x), fml, branch); + m_current->consume_vars(m_new_vars); + } + return NEED_PROPAGATION; + } + m_current = m_current->child(branch); + if (m_current->fml() == 0) { + SASSERT(!m_current->has_var()); + if (apply) { + expr_ref def(m); + plugin(x).subst(contains(x), branch, fml, m_defs?&def:0); + SASSERT(!contains(x)(fml)); + m_current->consume_vars(m_new_vars); + m_current->init(fml); + m_current->add_def(x, def); + normalize(*m_current); + } + return CHOOSE_VAR; + } + } + } + + void pop(model_evaluator& model_eval) { + // + // Eliminate trivial quantifiers by solving + // variables that can be eliminated. + // + solve_vars(); + expr* fml = m_current->fml(); + // we are done splitting. + if (m_current->num_free_vars() == 0) { + block_assignment(); + return; + } + + expr_ref fml_closed(m), fml_open(m), fml_mixed(m); + unsigned num_vars = m_current->num_free_vars(); + ptr_vector cont; + ptr_vector vars; + for (unsigned i = 0; i < num_vars; ++i) { + cont.push_back(&contains(i)); + vars.push_back(m_current->free_var(i)); + } + m_conjs.get_partition(fml, num_vars, vars.c_ptr(), fml_closed, fml_mixed, fml_open); + if (m.is_and(fml_open) && + m_conjs.partition_vars( + num_vars, cont.c_ptr(), + to_app(fml_open)->get_num_args(), to_app(fml_open)->get_args(), + m_partition)) { + process_partition(); + return; + } + + // + // The current state is satisfiable + // and the closed portion of the formula + // can be used as the quantifier-free portion. + // + if (m.is_true(fml_mixed)) { + m_current = m_current->add_child(fml_closed); + for (unsigned i = 0; m_defs && i < m_current->num_free_vars(); ++i) { + expr_ref val(m); + app* x = m_current->free_var(i); + model_eval(x, val); + // hack: assign may add new variables to the branch. + if (val == x) { + model_ref model; + lbool is_sat = m_solver.check(); + m_solver.get_model(model); + SASSERT(is_sat == l_true); + model_evaluator model_eval2(*model); + model_eval2(x,val); + } + TRACE("qe", tout << mk_pp(x,m) << " " << mk_pp(val, m) << "\n";); + m_current->add_def(x, val); + } + m_current->reset_free_vars(); + block_assignment(); + return; + } + // + // one variable is to be processed. + // + constrain_assignment(); + } + + void normalize(search_tree& st) { + normalize(st.fml_ref(), st.pos_atoms(), st.neg_atoms()); + } + + void normalize(expr_ref& result, atom_set& pos, atom_set& neg) { + m_rewriter(result); + bool simplified = true; + while (simplified) { + simplified = false; + for (unsigned i = 0; !simplified && i < m_plugins.size(); ++i) { + qe_solver_plugin* pl = m_plugins[i]; + simplified = pl && pl->simplify(result); + } + } + TRACE("qe_verbose", tout << "simp: " << mk_ismt2_pp(result.get(), m) << "\n";); + m_nnf(result, pos, neg); + TRACE("qe", tout << "nnf: " << mk_ismt2_pp(result.get(), m) << "\n";); + } + + // + // variable queuing. + // + + + // ------------------------------------------------ + // propagate the assignments to branch + // literals to implied constraints on the + // variable. + // + + bool get_propagate_value(model_evaluator& model_eval, search_tree* node, app*& b, rational& b_val) { + return node->has_var() && eval(model_eval, get_branch_id(node->var()), b_val); + } + + bool can_propagate_assignment(model_evaluator& model_eval) { + return m_fml && NEED_PROPAGATION == update_current(model_eval, false); + } + + void propagate_assignment(model_evaluator& model_eval) { + if (m_fml) { + update_current(model_eval, true); + } + } + + // + // evaluate the Boolean or bit-vector 'b' in + // the current model + // + bool eval(model_evaluator& model_eval, app* b, rational& val) { + unsigned bv_size; + expr_ref res(m); + model_eval(b, res); + SASSERT(m.is_bool(b) || m_bv.is_bv(b)); + if (m.is_true(res)) { + val = rational::one(); + return true; + } + else if (m.is_false(res)) { + val = rational::zero(); + return true; + } + else if (m_bv.is_numeral(res, val, bv_size)) { + return true; + } + else { + return false; + } + } + + // + // create literal 'b = r' + // + app* mk_eq_value(app* b, rational const& vl) { + if (m.is_bool(b)) { + if (vl.is_zero()) return m.mk_not(b); + if (vl.is_one()) return b; + UNREACHABLE(); + } + SASSERT(m_bv.is_bv(b)); + app_ref value(m_bv.mk_numeral(vl, m_bv.get_bv_size(b)), m); + return m.mk_eq(b, value); + } + + + bool is_eq_value(app* e, app*& bv, rational& v) { + unsigned sz = 0; + if (!m.is_eq(e)) return false; + expr* e0 = e->get_arg(0), *e1 = e->get_arg(1); + if (!m_bv.is_bv(e0)) return false; + if (m_bv.is_numeral(e0, v, sz) && is_app(e1)) { + bv = to_app(e1); + return true; + } + if (m_bv.is_numeral(e1, v, sz) && is_app(e0)) { + bv = to_app(e0); + return true; + } + return false; + } + + + // + // the current state is satisfiable. + // all bound decisions have been made. + // + void block_assignment() { + TRACE("qe_verbose", m_solver.display(tout);); + add_constraint(true); + } + + // + // The variable v is to be assigned a value in a range. + // + void constrain_assignment() { + expr* fml = m_current->fml(); + SASSERT(fml); + rational k; + app* x; + if (!find_min_weight(x, k)) { + return; + } + + m_current->set_var(x, k); + if (m_bv.is_bv(x)) { + return; + } + + app* b = get_branch_id(x); + // + // Create implication: + // + // assign_0 /\ ... /\ assign_{v-1} => b(v) <= k-1 + // where + // - assign_i is the current assignment: i = b(i) + // - k is the number of cases for v + // + + if (m.is_bool(b)) { + SASSERT(k <= rational(2)); + return; + } + + SASSERT(m_bv.is_bv(b)); + SASSERT(k.is_pos()); + app_ref max_val(m_bv.mk_numeral(k-rational::one(), m_bv.get_bv_size(b)), m); + expr_ref ub(m_bv.mk_ule(b, max_val), m); + add_constraint(true, ub); + } + + + + // + // Process the partition stored in m_vars. + // + void process_partition() { + expr_ref fml(m_current->fml(), m); + ptr_vector vars; + bool closed = true; + while (extract_partition(vars)) { + lbool r = m_qe.eliminate_exists(vars.size(), vars.c_ptr(), fml, m_free_vars, m_get_first, m_defs); + vars.reset(); + closed = closed && (r != l_undef); + } + TRACE("qe", tout << mk_ismt2_pp(fml, m) << "\n";); + m_current->add_child(fml)->reset_free_vars(); + block_assignment(); + } + + + // variable queueing. + + contains_app& contains(app* x) { + contains_app* result = 0; + VERIFY(m_var2contains.find(x, result)); + return *result; + } + + bool find_min_weight(app*& x, rational& num_branches) { + SASSERT(!m_current->has_var()); + while (m_current->num_free_vars() > 0) { + unsigned weight = UINT_MAX; + unsigned nv = m_current->num_free_vars(); + expr* fml = m_current->fml(); + unsigned index = 0; + for (unsigned i = 0; i < nv; ++i) { + x = get_var(i); + if (!has_plugin(x)) { + break; + } + unsigned weight1 = plugin(get_var(i)).get_weight(contains(i), fml); + if (weight1 < weight) { + index = i; + } + } + x = get_var(index); + if (has_plugin(x) && + plugin(x).get_num_branches(contains(x), fml, num_branches)) { + return true; + } + m_free_vars.push_back(x); + m_current->del_var(x); + } + return false; + } + + // + // Solve for variables in fml. + // Add a unit branch when variables are solved. + // + void solve_vars() { + bool solved = true; + while (solved) { + expr_ref fml(m_current->fml(), m); + conj_enum conjs(m, fml); + solved = false; + for (unsigned i = 0; !solved && i < m_plugins.size(); ++i) { + qe_solver_plugin* p = m_plugins[i]; + solved = p && p->solve(conjs, fml); + SASSERT(m_new_vars.empty()); + } + } + } + + }; + + // ------------------------------------------------ + // quant_elim + + + class quant_elim_new : public quant_elim { + ast_manager& m; + front_end_params& m_fparams; + expr_ref m_assumption; + bool m_produce_models; + ptr_vector m_plugins; + unsigned m_name_counter; // fresh-id + volatile bool m_cancel; + bool m_eliminate_variables_as_block; + + public: + quant_elim_new(ast_manager& m, front_end_params& p) : + m(m), + m_fparams(p), + m_assumption(m), + m_produce_models(m_fparams.m_model), + m_name_counter(0), + m_cancel(false), + m_eliminate_variables_as_block(true) + { + } + + virtual ~quant_elim_new() { + reset(); + } + + void reset() { + for (unsigned i = 0; i < m_plugins.size(); ++i) { + dealloc(m_plugins[i]); + } + } + + void set_cancel(bool f) { + for (unsigned i = 0; i < m_plugins.size(); ++i) { + m_plugins[i]->set_cancel(f); + } + m_cancel = f; + } + + void checkpoint() { + if (m_cancel) + throw tactic_exception(TACTIC_CANCELED_MSG); + cooperate("qe"); + } + + + void collect_statistics(statistics & st) const { + for (unsigned i = 0; i < m_plugins.size(); ++i) { + m_plugins[i]->collect_statistics(st); + } + } + + void updt_params(params_ref const& p) { + m_eliminate_variables_as_block = p.get_bool(":eliminate-variables-as-block", m_eliminate_variables_as_block); + } + + void eliminate(bool is_forall, unsigned num_vars, app* const* vars, expr_ref& fml) { + if (is_forall) { + eliminate_forall_bind(num_vars, vars, fml); + } + else { + eliminate_exists_bind(num_vars, vars, fml); + } + } + + virtual void bind_variables(unsigned num_vars, app* const* vars, expr_ref& fml) { + if (num_vars > 0) { + ptr_vector sorts; + vector names; + ptr_vector free_vars; + for (unsigned i = 0; i < num_vars; ++i) { + contains_app contains_x(m, vars[i]); + if (contains_x(fml)) { + sorts.push_back(m.get_sort(vars[i])); + names.push_back(vars[i]->get_decl()->get_name()); + free_vars.push_back(vars[i]); + } + } + if (!free_vars.empty()) { + expr_ref tmp(m); + expr_abstract(m, 0, free_vars.size(), (expr*const*)free_vars.c_ptr(), fml, tmp); + fml = m.mk_exists(free_vars.size(), sorts.c_ptr(), names.c_ptr(), tmp, 1); + } + } + } + + virtual void set_assumption(expr* fml) { + m_assumption = fml; + } + + + virtual lbool eliminate_exists( + unsigned num_vars, app* const* vars, expr_ref& fml, + app_ref_vector& free_vars, bool get_first, def_vector* defs) { + if (get_first) { + return eliminate_block(num_vars, vars, fml, free_vars, get_first, defs); + } + if (m_eliminate_variables_as_block) { + return eliminate_block(num_vars, vars, fml, free_vars, get_first, defs); + } + for (unsigned i = 0; i < num_vars; ++i) { + lbool r = eliminate_block(1, vars+i, fml, free_vars, get_first, defs); + switch(r) { + case l_false: + return l_false; + case l_undef: + free_vars.append(num_vars-i-1,vars+1+i); + return l_undef; + default: + break; + } + } + return l_true; + } + + private: + + lbool eliminate_block( + unsigned num_vars, app* const* vars, expr_ref& fml, + app_ref_vector& free_vars, bool get_first, def_vector* defs) { + + checkpoint(); + + if (has_quantifiers(fml)) { + free_vars.append(num_vars, vars); + return l_undef; + } + + flet fl1(m_fparams.m_model, true); + flet fl2(m_fparams.m_simplify_bit2int, true); + flet fl3(m_fparams.m_arith_enum_const_mod, true); + flet fl4(m_fparams.m_bv_enable_int2bv2int, true); + flet fl5(m_fparams.m_array_canonize_simplify, true); + flet fl6(m_fparams.m_relevancy_lvl, 0); + TRACE("qe", + for (unsigned i = 0; i < num_vars; ++i) { + tout << mk_ismt2_pp(vars[i], m) << " "; + } + tout << "\n"; + tout << mk_ismt2_pp(fml, m) << "\n"; + ); + + expr_ref fml0(fml, m); + + quant_elim_plugin* th; + pop_context(th); + + th->check(num_vars, vars, m_assumption, fml, get_first, free_vars, defs); + + push_context(th); + TRACE("qe", + for (unsigned i = 0; i < num_vars; ++i) { + tout << mk_ismt2_pp(vars[i], m) << " "; + } + tout << "\n"; + tout << "Input:\n" << mk_ismt2_pp(fml0, m) << "\n"; + tout << "Result:\n" << mk_ismt2_pp(fml, m) << "\n";); + + if (m.is_false(fml)) { + return l_false; + } + if (free_vars.empty()) { + return l_true; + } + return l_undef; + } + + void pop_context(quant_elim_plugin*& th) { + if (m_plugins.empty()) { + th = alloc(quant_elim_plugin, m, *this, m_fparams); + th->add_plugin(mk_bool_plugin(*th)); + th->add_plugin(mk_bv_plugin(*th)); + th->add_plugin(mk_arith_plugin(*th, m_produce_models, m_fparams)); + th->add_plugin(mk_array_plugin(*th)); + th->add_plugin(mk_datatype_plugin(*th)); + th->add_plugin(mk_dl_plugin(*th)); + } + else { + th = m_plugins.back(); + m_plugins.pop_back(); + } + } + + void push_context(quant_elim_plugin* th) { + m_plugins.push_back(th); + th->reset(); + } + + void eliminate_exists_bind(unsigned num_vars, app* const* vars, expr_ref& fml) { + checkpoint(); + app_ref_vector free_vars(m); + eliminate_exists(num_vars, vars, fml, free_vars, false, 0); + bind_variables(free_vars.size(), free_vars.c_ptr(), fml); + } + + void eliminate_forall_bind(unsigned num_vars, app* const* vars, expr_ref& fml) { + expr_ref tmp(m); + bool_rewriter rw(m); + rw.mk_not(fml, tmp); + eliminate_exists_bind(num_vars, vars, tmp); + rw.mk_not(tmp, fml); + } + + }; + + + +#if 0 + // + // Instantiation based quantifier elimination procedure. + // try a few loops of checking satisfiability. + // substitute in model values for bound variables. + // + class quant_elim_inst : public quant_elim { + ast_manager& + public: + quant_elim_inst(ast_manager& m): m(m) {} + + virtual lbool eliminate_exists( + unsigned num_vars, app* const* vars, + expr_ref& fml, app_ref_vector& free_vars, bool get_first, def_vector* defs) { + + } + + virtual void set_assumption(expr* fml) {} + + virtual void collect_statistics(statistics & st) const { + m_solver.collect_statistics(st); + } + + virtual void eliminate(bool is_forall, unsigned num_vars, app* const* vars, expr_ref& fml) { + if (is_forall) { + fml = m.mk_not(fml); + r = eliminate_exists(num_vars, vars, fml, free_vars, false, defs); + fml = m.mk_not(fml); + } + else { + r = eliminate_exists(num_vars, vars, fml, free_vars, false, defs); + } + } + + virtual void set_cancel(bool f) { + m_solver.set_cancel(f); + } + + virtual void updt_params(params_ref const& p) { + m_solver.updt_params(p); + } + + }; +#endif + + + // ------------------------------------------------ + // expr_quant_elim + + expr_quant_elim::expr_quant_elim(ast_manager& m, front_end_params const& fp, params_ref const& p): + m(m), + m_fparams(fp), + m_params(p), + m_trail(m), + m_qe(0), + m_assumption(m.mk_true()), + m_use_new_qe(true) + { + } + + expr_quant_elim::~expr_quant_elim() { + dealloc(m_qe); + } + + void expr_quant_elim::operator()(expr* assumption, expr* fml, expr_ref& result) { + TRACE("qe", tout << "elim input\n" << mk_ismt2_pp(fml, m) << "\n";); + expr_ref_vector bound(m); + result = fml; + m_assumption = assumption; + instantiate_expr(bound, result); + elim(result); + m_trail.reset(); + m_visited.reset(); + abstract_expr(bound.size(), bound.c_ptr(), result); + TRACE("qe", tout << "elim result\n" << mk_ismt2_pp(result, m) << "\n";); + } + + void expr_quant_elim::updt_params(params_ref const& p) { + bool r = p.get_bool(":use-neq-qe", m_use_new_qe); + if (r != m_use_new_qe) { + dealloc(m_qe); + m_qe = 0; + m_use_new_qe = r; + } + init_qe(); + m_qe->updt_params(p); + } + + void expr_quant_elim::collect_param_descrs(param_descrs& r) { + r.insert(":eliminate-variables-as-block", CPK_BOOL, + "(default: true) eliminate variables as a block (true) or one at a time (false)"); + // r.insert(":use-new-qe", CPK_BOOL, "(default: true) invoke quantifier engine based on abstracted solver"); + } + + void expr_quant_elim::init_qe() { + if (!m_qe) { + m_qe = alloc(quant_elim_new, m, const_cast(m_fparams)); + } + } + + + void expr_quant_elim::instantiate_expr(expr_ref_vector& bound, expr_ref& fml) { + ptr_vector sorts; + get_free_vars(fml, sorts); + if (!sorts.empty()) { + expr_ref tmp(m); + for (unsigned i = sorts.size(); i > 0;) { + --i; + sort* s = sorts[i]; + if (!s) { + s = m.mk_bool_sort(); + } + bound.push_back(m.mk_fresh_const("bound", s)); + } + var_subst subst(m); + subst(fml, bound.size(), bound.c_ptr(), tmp); + fml = tmp; + } + } + + void expr_quant_elim::abstract_expr(unsigned sz, expr* const* bound, expr_ref& fml) { + if (sz > 0) { + expr_ref tmp(m); + expr_abstract(m, 0, sz, bound, fml, tmp); + fml = tmp; + } + } + + static void extract_vars(quantifier* q, expr_ref& new_body, app_ref_vector& vars) { + ast_manager& m = new_body.get_manager(); + expr_ref tmp(m); + unsigned nd = q->get_num_decls(); + for (unsigned i = 0; i < nd; ++i) { + vars.push_back(m.mk_fresh_const("x",q->get_decl_sort(i))); + } + expr* const* exprs = (expr* const*)(vars.c_ptr()); + var_subst subst(m); + subst(new_body, vars.size(), exprs, tmp); + inv_var_shifter shift(m); + shift(tmp, vars.size(), new_body); + } + + void expr_quant_elim::elim(expr_ref& result) { + expr_ref tmp(m); + ptr_vector todo; + + m_trail.push_back(result); + todo.push_back(result); + expr* e = 0, *r = 0; + + while (!todo.empty()) { + e = todo.back(); + if (m_visited.contains(e)) { + todo.pop_back(); + continue; + } + + switch(e->get_kind()) { + case AST_APP: { + app* a = to_app(e); + expr_ref_vector args(m); + unsigned num_args = a->get_num_args(); + bool all_visited = true; + for (unsigned i = 0; i < num_args; ++i) { + if (m_visited.find(a->get_arg(i), r)) { + args.push_back(r); + } + else { + todo.push_back(a->get_arg(i)); + all_visited = false; + } + } + if (all_visited) { + r = m.mk_app(a->get_decl(), args.size(), args.c_ptr()); + todo.pop_back(); + m_trail.push_back(r); + m_visited.insert(e, r); + } + break; + } + case AST_QUANTIFIER: { + app_ref_vector vars(m); + quantifier* q = to_quantifier(e); + bool is_fa = q->is_forall(); + tmp = q->get_expr(); + extract_vars(q, tmp, vars); + elim(tmp); + init_qe(); + m_qe->set_assumption(m_assumption); + m_qe->eliminate(is_fa, vars.size(), vars.c_ptr(), tmp); + m_trail.push_back(tmp); + m_visited.insert(e, tmp); + todo.pop_back(); + break; + } + default: + UNREACHABLE(); + break; + } + } + + VERIFY (m_visited.find(result, e)); + result = e; + } + + void expr_quant_elim::collect_statistics(statistics & st) const { + if (m_qe) { + m_qe->collect_statistics(st); + } + } + + lbool expr_quant_elim::first_elim(unsigned num_vars, app* const* vars, expr_ref& fml, def_vector& defs) { + app_ref_vector fvs(m); + init_qe(); + return m_qe->eliminate_exists(num_vars, vars, fml, fvs, true, &defs); + } + + bool expr_quant_elim::solve_for_var(app* var, expr* _fml, expr_ref_vector& terms, expr_ref_vector& fmls) { + expr_ref assms(m.mk_true(), m); + func_decl* v = var->get_decl(); + init_qe(); + while (true) { + def_vector defs(m); + app_ref_vector fvs(m); + m_qe->set_assumption(assms); + expr_ref fml(_fml, m); + lbool is_sat = m_qe->eliminate_exists(1, &var, fml, fvs, true, &defs); + switch (is_sat) { + case l_false: return true; + case l_undef: return false; + default: break; + } + bool found = false; + for (unsigned i = 0; !found && i < defs.size(); ++i) { + if (defs.var(i) == v) { + terms.push_back(defs.def(i)); + fmls.push_back(fml); + found = true; + } + } + if (!found) { + NOT_IMPLEMENTED_YET(); + } + assms = m.mk_and(assms, m.mk_not(fml)); + } + return true; + } + + + void expr_quant_elim::set_cancel(bool f) { + if (m_qe) { + m_qe->set_cancel(f); + } + } + + + + + // ------------------------------------------------ + // expr_quant_elim_star1 + + bool expr_quant_elim_star1::visit_quantifier(quantifier * q) { + if (!is_target(q)) { + return true; + } + bool visited = true; + visit(q->get_expr(), visited); + return visited; + } + + void expr_quant_elim_star1::reduce1_quantifier(quantifier * q) { + if (!is_target(q)) { + cache_result(q, q, 0); + return; + } + ast_manager& m = m_manager; + + quantifier_ref new_q(m); + expr * new_body = 0; + proof * new_pr; + get_cached(q->get_expr(), new_body, new_pr); + new_q = m.update_quantifier(q, new_body); + expr_ref r(m); + m_quant_elim(m_assumption, new_q, r); + if (q == r.get()) { + cache_result(q, q, 0); + return; + } + proof_ref pr(m); + if (m.proofs_enabled()) { + pr = m.mk_rewrite(q, r); // TODO: improve justification + } + cache_result(q, r, pr); + } + + expr_quant_elim_star1::expr_quant_elim_star1(ast_manager& m, front_end_params const& p): + simplifier(m), m_quant_elim(m, p), m_assumption(m.mk_true()) + { + } + + void expr_quant_elim_star1::reduce_with_assumption(expr* ctx, expr* fml, expr_ref& result) { + ast_manager& m = m_manager; + proof_ref pr(m); + m_assumption = ctx; + (*this)(fml, result, pr); + m_assumption = m.mk_true(); + } + + + void hoist_exists(expr_ref& fml, app_ref_vector& vars) { + quantifier_hoister hoister(fml.get_manager()); + hoister.pull_exists(fml, vars, fml); + } + + void mk_exists(unsigned num_bound, app* const* vars, expr_ref& fml) { + ast_manager& m = fml.get_manager(); + expr_ref tmp(m); + expr_abstract(m, 0, num_bound, (expr*const*)vars, fml, tmp); + ptr_vector sorts; + svector names; + for (unsigned i = 0; i < num_bound; ++i) { + sorts.push_back(vars[i]->get_decl()->get_range()); + names.push_back(vars[i]->get_decl()->get_name()); + } + if (num_bound > 0) { + tmp = m.mk_exists(num_bound, sorts.c_ptr(), names.c_ptr(), tmp, 1); + } + fml = tmp; + } + + + class simplify_solver_context : public i_solver_context { + ast_manager& m; + front_end_params m_fparams; + app_ref_vector* m_vars; + expr_ref* m_fml; + ptr_vector m_contains; + atom_set m_pos; + atom_set m_neg; + public: + simplify_solver_context(ast_manager& m): + m(m), + m_vars(0), + m_fml(0) + { + add_plugin(mk_bool_plugin(*this)); + add_plugin(mk_arith_plugin(*this, false, m_fparams)); + } + + virtual ~simplify_solver_context() { reset(); } + + void solve(expr_ref& fml, app_ref_vector& vars) { + init(fml, vars); + bool solved = true; + do { + conj_enum conjs(m, fml); + solved = false; + for (unsigned i = 0; !solved && i < m_plugins.size(); ++i) { + qe_solver_plugin* p = m_plugins[i]; + solved = p && p->solve(conjs, fml); + } + TRACE("qe", tout << (solved?"solved":"not solved") << "\n"; + if (solved) tout << mk_ismt2_pp(fml, m) << "\n";); + } + while (solved); + } + + virtual ast_manager& get_manager() { return m; } + + virtual atom_set const& pos_atoms() const { return m_pos; } + virtual atom_set const& neg_atoms() const { return m_neg; } + + // Access current set of variables to solve + virtual unsigned get_num_vars() const { return m_vars->size(); } + virtual app* get_var(unsigned idx) const { return (*m_vars)[idx].get(); } + virtual app*const* get_vars() const { return m_vars->c_ptr(); } + virtual bool is_var(expr* e, unsigned& idx) const { + for (unsigned i = 0; i < m_vars->size(); ++i) { + if ((*m_vars)[i].get() == e) { + idx = i; + return true; + } + } + return false; + } + + virtual contains_app& contains(unsigned idx) { + return *m_contains[idx]; + } + + // callback to replace variable at index 'idx' with definition 'def' and updated formula 'fml' + virtual void elim_var(unsigned idx, expr* fml, expr* def) { + *m_fml = fml; + m_vars->set(idx, m_vars->get(m_vars->size()-1)); + m_vars->pop_back(); + dealloc(m_contains[idx]); + m_contains[idx] = m_contains[m_contains.size()-1]; + m_contains.pop_back(); + } + + // callback to add new variable to branch. + virtual void add_var(app* x) { + m_vars->push_back(x); + } + + // callback to add constraints in branch. + virtual void add_constraint(bool use_var, expr* l1 = 0, expr* l2 = 0, expr* l3 = 0) { + UNREACHABLE(); + } + // eliminate finite domain variable 'var' from fml. + virtual void blast_or(app* var, expr_ref& fml) { + UNREACHABLE(); + } + + private: + void reset() { + for (unsigned i = 0; i < m_contains.size(); ++i) { + dealloc (m_contains[i]); + } + m_contains.reset(); + } + + void init(expr_ref& fml, app_ref_vector& vars) { + reset(); + m_fml = &fml; + m_vars = &vars; + for (unsigned i = 0; i < vars.size(); ++i) { + m_contains.push_back(alloc(contains_app, m, vars[i].get())); + } + } + }; + + class simplify_rewriter_cfg::impl { + ast_manager& m; + simplify_solver_context m_ctx; + public: + impl(ast_manager& m) : m(m), m_ctx(m) {} + + bool reduce_quantifier( + quantifier * old_q, + expr * new_body, + expr * const * new_patterns, + expr * const * new_no_patterns, + expr_ref & result, + proof_ref & result_pr + ) + { + + bool is_forall = old_q->is_forall(); + app_ref_vector vars(m); + TRACE("qe", tout << "simplifying" << mk_pp(new_body, m) << "\n";); + result = new_body; + extract_vars(old_q, result, vars); + TRACE("qe", tout << "variables extracted" << mk_pp(result, m) << "\n";); + + if (old_q->is_forall()) { + result = m.mk_not(result); + } + m_ctx.solve(result, vars); + if (old_q->is_forall()) { + expr* e = 0; + result = m.is_not(result, e)?e:m.mk_not(result); + } + var_shifter shift(m); + shift(result, vars.size(), result); + expr_abstract(m, 0, vars.size(), (expr*const*)vars.c_ptr(), result, result); + TRACE("qe", tout << "abstracted" << mk_pp(result, m) << "\n";); + ptr_vector sorts; + svector names; + for (unsigned i = 0; i < vars.size(); ++i) { + sorts.push_back(vars[i]->get_decl()->get_range()); + names.push_back(vars[i]->get_decl()->get_name()); + } + if (!vars.empty()) { + result = m.mk_quantifier(old_q->is_forall(), vars.size(), sorts.c_ptr(), names.c_ptr(), result, 1); + } + result_pr = 0; + return true; + } + + }; + + simplify_rewriter_cfg::simplify_rewriter_cfg(ast_manager& m) { + imp = alloc(simplify_rewriter_cfg::impl, m); + } + + simplify_rewriter_cfg::~simplify_rewriter_cfg() { + dealloc(imp); + } + + bool simplify_rewriter_cfg::reduce_quantifier( + quantifier * old_q, + expr * new_body, + expr * const * new_patterns, + expr * const * new_no_patterns, + expr_ref & result, + proof_ref & result_pr + ) { + return imp->reduce_quantifier(old_q, new_body, new_patterns, new_no_patterns, result, result_pr); + } + + bool simplify_rewriter_cfg::pre_visit(expr* e) { + if (!is_quantifier(e)) return true; + quantifier * q = to_quantifier(e); + return (q->get_num_patterns() == 0 && q->get_num_no_patterns() == 0); + } + + void simplify_exists(app_ref_vector& vars, expr_ref& fml) { + front_end_params params; + ast_manager& m = fml.get_manager(); + simplify_solver_context ctx(m); + ctx.solve(fml, vars); + } +} + + +template class rewriter_tpl; + + diff --git a/lib/qe.h b/lib/qe.h new file mode 100644 index 000000000..4a444ac26 --- /dev/null +++ b/lib/qe.h @@ -0,0 +1,357 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + qe.h + +Abstract: + + Quantifier-elimination procedures + +Author: + + Nikolaj Bjorner (nbjorner) 2010-02-19 + +Revision History: + + +--*/ + +#ifndef __QE_H__ +#define __QE_H__ + +#include "ast.h" +#include "front_end_params.h" +#include "statistics.h" +#include "lbool.h" +#include "expr_functors.h" +#include "simplifier.h" +#include "rewriter.h" +#include "model.h" +#include "params.h" + +namespace qe { + + + class i_nnf_atom { + public: + virtual void operator()(expr* e, bool pol, expr_ref& result) = 0; + }; + + typedef obj_hashtable atom_set; + + class qe_solver_plugin; + + + class i_solver_context { + protected: + class is_relevant : public i_expr_pred { + i_solver_context& m_s; + public: + is_relevant(i_solver_context& s):m_s(s) {} + virtual bool operator()(expr* e); + }; + class mk_atom_fn : public i_nnf_atom { + i_solver_context& m_s; + public: + mk_atom_fn(i_solver_context& s) : m_s(s) {} + void operator()(expr* e, bool p, expr_ref& result); + }; + + is_relevant m_is_relevant; + mk_atom_fn m_mk_atom; + ptr_vector m_plugins; // fid -> plugin + + public: + i_solver_context():m_is_relevant(*this), m_mk_atom(*this) {} + + virtual ~i_solver_context(); + + void add_plugin(qe_solver_plugin* p); + + bool has_plugin(app* x); + + qe_solver_plugin& plugin(app* x); + + qe_solver_plugin& plugin(unsigned fid) { return *m_plugins[fid]; } + + void mk_atom(expr* e, bool p, expr_ref& result); + + virtual ast_manager& get_manager() = 0; + + // set of atoms in current formula. + virtual atom_set const& pos_atoms() const = 0; + virtual atom_set const& neg_atoms() const = 0; + + // Access current set of variables to solve + virtual unsigned get_num_vars() const = 0; + virtual app* get_var(unsigned idx) const = 0; + virtual app*const* get_vars() const = 0; + virtual bool is_var(expr* e, unsigned& idx) const; + virtual contains_app& contains(unsigned idx) = 0; + + // callback to replace variable at index 'idx' with definition 'def' and updated formula 'fml' + virtual void elim_var(unsigned idx, expr* fml, expr* def) = 0; + + // callback to add new variable to branch. + virtual void add_var(app* x) = 0; + + // callback to add constraints in branch. + virtual void add_constraint(bool use_var, expr* l1 = 0, expr* l2 = 0, expr* l3 = 0) = 0; + + // eliminate finite domain variable 'var' from fml. + virtual void blast_or(app* var, expr_ref& fml) = 0; + + i_expr_pred& get_is_relevant() { return m_is_relevant; } + + i_nnf_atom& get_mk_atom() { return m_mk_atom; } + }; + + class conj_enum { + ast_manager& m; + expr_ref_vector m_conjs; + public: + conj_enum(ast_manager& m, expr* e); + + class iterator { + conj_enum* m_super; + unsigned m_index; + public: + iterator(conj_enum& c, bool first) : m_super(&c), m_index(first?0:c.m_conjs.size()) {} + expr* operator*() { return m_super->m_conjs[m_index].get(); } + iterator& operator++() { m_index++; return *this; } + bool operator==(iterator const& it) const { return m_index == it.m_index; } + bool operator!=(iterator const& it) const { return m_index != it.m_index; } + }; + + void extract_equalities(expr_ref_vector& eqs); + + iterator begin() { return iterator(*this, true); } + iterator end() { return iterator(*this, false); } + }; + + + // + // interface for plugins to eliminate quantified variables. + // + class qe_solver_plugin { + protected: + ast_manager& m; + family_id m_fid; + i_solver_context& m_ctx; + public: + qe_solver_plugin(ast_manager& m, family_id fid, i_solver_context& ctx) : + m(m), + m_fid(fid), + m_ctx(ctx) + {} + + virtual ~qe_solver_plugin() {} + + family_id get_family_id() { return m_fid; } + + /** + \brief Return number of case splits for variable x in fml. + */ + virtual bool get_num_branches(contains_app& x, expr* fml, rational& num_branches) = 0; + + /** + \brief Given a case split index 'vl' assert the constraints associated with it. + */ + virtual void assign(contains_app& x, expr* fml, rational const& vl) = 0; + + + /** + \brief The case splits associated with 'vl' are satisfiable. + Apply the elimination for fml corresponding to case split. + If def is non-null, then retrieve the realizer corresponding to the case split. + */ + virtual void subst(contains_app& x, rational const& vl, expr_ref& fml, expr_ref* def) = 0; + + + /** + \brief solve quantified variable. + */ + virtual bool solve(conj_enum& conjs, expr* fml) = 0; + + /** + \brief project variable x for fml given model. + + Assumption: model |= fml[x] + + Projection updates fml to fml', such that: + + - fml' -> fml + - model |= fml' + - fml' does not contain x + + return false if the method is not implemented. + */ + virtual bool project(contains_app& x, model_ref& model, expr_ref& fml) { return false; } + + /** + \brief assign branching penalty to variable x for 'fml'. + */ + virtual unsigned get_weight(contains_app& x, expr* fml) { return UINT_MAX; } + + /** + \brief simplify formula. + */ + virtual bool simplify(expr_ref& fml) { return false; } + + /** + \brief Normalize literal during NNF conversion. + */ + virtual bool mk_atom(expr* e, bool p, expr_ref& result) { return false; } + + /** + \brief Determine whether sub-term is uninterpreted with respect to quantifier elimination. + */ + virtual bool is_uninterpreted(app* f) { return true; } + }; + + qe_solver_plugin* mk_datatype_plugin(i_solver_context& ctx); + + qe_solver_plugin* mk_bool_plugin(i_solver_context& ctx); + + qe_solver_plugin* mk_bv_plugin(i_solver_context& ctx); + + qe_solver_plugin* mk_dl_plugin(i_solver_context& ctx); + + qe_solver_plugin* mk_array_plugin(i_solver_context& ctx); + + qe_solver_plugin* mk_arith_plugin(i_solver_context& ctx, bool produce_models, front_end_params& p); + + class def_vector { + func_decl_ref_vector m_vars; + expr_ref_vector m_defs; + public: + def_vector(ast_manager& m): m_vars(m), m_defs(m) {} + void push_back(func_decl* v, expr* e) { + m_vars.push_back(v); + m_defs.push_back(e); + } + void reset() { m_vars.reset(); m_defs.reset(); } + void append(def_vector const& o) { m_vars.append(o.m_vars); m_defs.append(o.m_defs); } + unsigned size() const { return m_defs.size(); } + void shrink(unsigned sz) { m_vars.shrink(sz); m_defs.shrink(sz); } + bool empty() const { return m_defs.empty(); } + func_decl* var(unsigned i) const { return m_vars[i]; } + expr* def(unsigned i) const { return m_defs[i]; } + expr_ref_vector::element_ref def_ref(unsigned i) { return m_defs[i]; } + }; + + class quant_elim; + + class expr_quant_elim { + ast_manager& m; + front_end_params const& m_fparams; + params_ref m_params; + expr_ref_vector m_trail; + obj_map m_visited; + quant_elim* m_qe; + expr* m_assumption; + bool m_use_new_qe; + public: + expr_quant_elim(ast_manager& m, front_end_params const& fp, params_ref const& p = params_ref()); + ~expr_quant_elim(); + + void operator()(expr* assumption, expr* fml, expr_ref& result); + + void collect_statistics(statistics & st) const; + + void updt_params(params_ref const& p); + + void collect_param_descrs(param_descrs& r); + + /** + \brief try to eliminate 'vars' and find first satisfying assignment. + + return l_true if satisfiable, l_false if unsatisfiable, l_undef if + the formula could not be satisfied modulo eliminating the quantified variables. + */ + lbool first_elim(unsigned num_vars, app* const* vars, expr_ref& fml, def_vector& defs); + + /** + \brief solve for (exists (var) fml). + Return false if operation failed. + Return true and list of pairs (t_i, fml_i) in + such that fml[t_1] \/ ... \/ fml[t_n] == (exists (var) fml) + and fml_i == fml[t_1] + */ + bool solve_for_var(app* var, expr* fml, expr_ref_vector& terms, expr_ref_vector& fmls); + + + void set_cancel(bool f); + + private: + void instantiate_expr(expr_ref_vector& bound, expr_ref& fml); + void abstract_expr(unsigned sz, expr* const* bound, expr_ref& fml); + void elim(expr_ref& result); + void init_qe(); + }; + + class expr_quant_elim_star1 : public simplifier { + protected: + expr_quant_elim m_quant_elim; + expr* m_assumption; + virtual bool visit_quantifier(quantifier * q); + virtual void reduce1_quantifier(quantifier * q); + virtual bool is_target(quantifier * q) const { return q->get_num_patterns() == 0 && q->get_num_no_patterns() == 0; } + public: + expr_quant_elim_star1(ast_manager & m, front_end_params const& p); + virtual ~expr_quant_elim_star1() {} + + void collect_statistics(statistics & st) const { + m_quant_elim.collect_statistics(st); + } + + void reduce_with_assumption(expr* ctx, expr* fml, expr_ref& result); + + lbool first_elim(unsigned num_vars, app* const* vars, expr_ref& fml, def_vector& defs) { + return m_quant_elim.first_elim(num_vars, vars, fml, defs); + } + + void set_cancel(bool f) {} // TBD: replace simplifier by rewriter + + }; + + void hoist_exists(expr_ref& fml, app_ref_vector& vars); + + void mk_exists(unsigned num_vars, app* const* vars, expr_ref& fml); + + void get_nnf(expr_ref& fml, i_expr_pred& pred, i_nnf_atom& mk_atom, atom_set& pos, atom_set& neg); + + class simplify_rewriter_cfg : public default_rewriter_cfg { + class impl; + impl* imp; + public: + simplify_rewriter_cfg(ast_manager& m); + + ~simplify_rewriter_cfg(); + + bool reduce_quantifier( + quantifier * old_q, + expr * new_body, + expr * const * new_patterns, + expr * const * new_no_patterns, + expr_ref & result, + proof_ref & result_pr); + + bool pre_visit(expr* e); + + }; + + class simplify_rewriter_star : public rewriter_tpl { + simplify_rewriter_cfg m_cfg; + public: + simplify_rewriter_star(ast_manager& m): + rewriter_tpl(m, false, m_cfg), + m_cfg(m) {} + }; + +}; + +#endif + diff --git a/lib/qe_arith_plugin.cpp b/lib/qe_arith_plugin.cpp new file mode 100644 index 000000000..0dc1a63e2 --- /dev/null +++ b/lib/qe_arith_plugin.cpp @@ -0,0 +1,2541 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + arith_plugin.cpp + +Abstract: + + Eliminate Arithmetical variable from formula + +Author: + + Nikolaj Bjorner (nbjorner) 2010-02-19 + +Revision History: + + +--*/ + +#include "qe.h" +#include "ast_pp.h" +#include "expr_replacer.h" +#include "bool_rewriter.h" +#include "bv_decl_plugin.h" +#include "arith_decl_plugin.h" +#include "arith_eq_solver.h" +#include "arith_rewriter.h" +#include "th_rewriter.h" +#include "factor_rewriter.h" +#include "obj_pair_hashtable.h" +#include "nlarith_util.h" +#include "model_evaluator.h" + +namespace qe { + + class bound { + rational m_coeff; + expr_ref m_term; + bool m_is_strict; + public: + bound(ast_manager& m, rational const& n, expr* t, bool is_strict) : m_coeff(n), m_term(t, m), m_is_strict(is_strict) { + } + bool is_strict() const { return m_is_strict; } + expr* term() const { return m_term.get(); } + rational const& coeff() const { return m_coeff; } + + void update(rational const& k, expr* t) { + m_coeff = k; + m_term = t; + } + + void pp(std::ostream& out, app* x) { + ast_manager& m = m_term.get_manager(); + out << "(<= (+ (* " << coeff() << " " << mk_pp(x, m) + << ") " << mk_pp(term(), m) << ") 0)"; + } + }; + + typedef rational numeral; + + // m_k | (m_a * x + m_term) + class div_constraint { + numeral m_k; + numeral m_a; + expr* m_term; + public: + div_constraint(numeral const& k, numeral const& a, expr* t): + m_k(k), m_a(a), m_term(t) {} + numeral const& a() const { return m_a; } + numeral const& k() const { return m_k; } + expr* t() const { return m_term; } + numeral& a_ref() { return m_a; } + numeral& k_ref() { return m_k; } + expr*& t_ref() { return m_term; } + }; + typedef vector div_constraints; + + class arith_qe_util { + ast_manager& m; + i_solver_context& m_ctx; + public: + arith_util m_arith; // initialize before m_zero_i, etc. + private: + th_rewriter m_rewriter; + arith_eq_solver m_arith_solver; + bv_util m_bv; + + expr_ref m_zero_i; + expr_ref m_one_i; + expr_ref m_minus_one_i; + expr_ref m_zero_r; + expr_ref m_one_r; + expr_ref m_tmp; + public: + scoped_ptr m_replace; + + bool_rewriter m_bool_rewriter; + arith_rewriter m_arith_rewriter; + + arith_qe_util(ast_manager& m, front_end_params& p, i_solver_context& ctx) : + m(m), + m_ctx(ctx), + m_arith(m), + m_rewriter(m), + m_arith_solver(m), + m_bv(m), + m_zero_i(m_arith.mk_numeral(numeral(0), true), m), + m_one_i(m_arith.mk_numeral(numeral(1), true), m), + m_minus_one_i(m_arith.mk_numeral(numeral(-1), true), m), + m_zero_r(m_arith.mk_numeral(numeral(0), false), m), + m_one_r(m_arith.mk_numeral(numeral(1), false), m), + m_replace(mk_default_expr_replacer(m)), + m_bool_rewriter(m), + m_arith_rewriter(m), + m_tmp(m) + { + } + + ast_manager& get_manager() { return m; } + + // + // match e := k*x + rest, where k != 0. + // + bool get_coeff(contains_app& contains_x, expr* p, rational& k, expr_ref& rest) { + app* x = contains_x.x(); + ptr_vector restl, todo; + todo.push_back(p); + bool found = false; + expr* e1, *e2; + while (!todo.empty()) { + expr* e = todo.back(); + todo.pop_back(); + if (m_arith.is_add(e)) { + for (unsigned i = 0; i < to_app(e)->get_num_args(); ++i) { + todo.push_back(to_app(e)->get_arg(i)); + } + } + else if (e == x) { + k = numeral(1); + found = true; + break; + } + else if (m_arith.is_mul(e, e1, e2) && + e1 == x && + m_arith.is_numeral(e2, k)) { + found = true; + break; + } + else if (m_arith.is_mul(e, e1, e2) && + e2 == x && + m_arith.is_numeral(e1, k)) { + found = true; + break; + } + else { + restl.push_back(e); + } + } + if (!found) { + TRACE("qe_verbose", + tout + << "Did not find: " + << mk_pp(x, m) << " in " + << mk_pp(p, m) << "\n"; + ); + + return false; + } + + while (!todo.empty()) { + restl.push_back(todo.back()); + todo.pop_back(); + } + if (restl.empty()) { + rest = mk_zero(x); + } + else { + rest = m_arith.mk_add(restl.size(), restl.c_ptr()); + } + if (contains_x(rest)) { + return false; + } + TRACE("qe_verbose", + tout + << mk_pp(p, m) << " = " + << "(+ (* " << k << " " + << mk_pp(x, m) << ") " + << mk_pp(rest, m) << ")\n"; + ); + return true; + } + + // + // match p := k + rest + // where k is a numeral and rest does not contain numerals. + // + void get_const(expr* p, rational& k, expr_ref& rest) { + ptr_vector todo, restl; + todo.push_back(p); + k = numeral(0); + while(!todo.empty()) { + p = todo.back(); + todo.pop_back(); + if (m_arith.is_add(p)) { + for (unsigned i = 0; i < to_app(p)->get_num_args(); ++i) { + todo.push_back(to_app(p)->get_arg(i)); + } + } + else if (m_arith.is_numeral(p, k)) { + break; + } + else { + restl.push_back(p); + } + } + while (!todo.empty()) { + restl.push_back(todo.back()); + todo.pop_back(); + } + if (restl.empty()) { + rest = mk_zero(p); + } + else { + rest = m_arith.mk_add(restl.size(), restl.c_ptr()); + } + } + + // + // match (not ne) + bool is_neg(app* e, expr_ref& ne) { + if (m.is_not(e)) { + ne = to_app(e)->get_arg(0); + return true; + } + return false; + } + + bool is_le(app* e, expr_ref& p) { + return is_le_ge_core<1>(e, p); + } + + bool is_ge(app* e, expr_ref& p) { + return is_le_ge_core<0>(e, p); + } + + // match e = p < 0 or p > 0 + bool is_lt(app* e, expr_ref& p) { + numeral k; + expr* a1, *a2; + + if (m_arith.is_lt(e, a1, a2) || m_arith.is_gt(e, a2, a1)) { + p = a1; + if (m_arith.is_numeral(a2, k) && k.is_zero()) { + return true; + } + } + else { + return false; + } + p = mk_sub(p, a2); + simplify(p); + return true; + } + + // + // match 0 == p mod k, p mod k == 0 + // + bool is_divides(app* e, numeral& k, expr_ref& p) { + expr* e1, *e2; + if (!m.is_eq(e, e1, e2)) { + return false; + } + return is_divides(e1, e2, k, p) || is_divides(e2, e1, k, p); + } + + bool is_divides(expr* e1, expr* e2, numeral& k, expr_ref& p) { + if (m_arith.is_mod(e2) && + m_arith.is_numeral(e1, k) && + k.is_zero() && + m_arith.is_numeral(to_app(e2)->get_arg(1), k)) { + p = to_app(e2)->get_arg(0); + return true; + } + return false; + } + + bool is_not_divides(app* e, app_ref& n, numeral& k, expr_ref& p) { + if (!m.is_not(e)) { + return false; + } + if (!is_app(to_app(e)->get_arg(0))) { + return false; + } + n = to_app(to_app(e)->get_arg(0)); + return is_divides(n, k, p); + } + + + bool is_real(app* x) const { return m_arith.is_real(x); } + + // + // b*t <= a*s + // + + template + void mk_bound_aux(rational const& a, expr* t, rational const& b, expr* s, expr_ref& result) { + SASSERT(a.is_neg() == b.is_neg()); + expr_ref tt(t, m), ss(s, m), e(m); + // hack to fix wierd gcc compilation error + rational abs_a(a); + rational abs_b(b); + if (abs_a.is_neg()) abs_a.neg(); + if (abs_b.is_neg()) abs_b.neg(); + ss = mk_mul(abs_a, ss); + tt = mk_mul(abs_b, tt); + if(a.is_neg()) { + e = mk_sub(tt, ss); + if (is_strict) { + if (m_arith.is_int(e)) { + e = mk_add(e, m_one_i); + mk_le(e, result); + } + else { + mk_lt(e, result); + } + } + else { + mk_le(e, result); + } + } + else { + e = mk_sub(ss, tt); + if (is_strict) { + if (m_arith.is_int(e)) { + e = mk_add(e, m_one_i); + mk_le(e, result); + } + else { + mk_lt(e, result); + } + } + else { + mk_le(e, result); + } + } + } + + void mk_bound(rational const& a, expr* t, rational const& b, expr* s, expr_ref& result) { + mk_bound_aux(a, t, b, s, result); + } + + void mk_strict_bound(rational const& a, expr* t, rational const& b, expr* s, expr_ref& result) { + mk_bound_aux(a, t, b, s, result); + } + + void mk_divides(numeral n, expr* e, expr_ref& result) { + SASSERT(n.is_int()); + expr_ref tmp1(e, m), tmp2(m); + simplify(tmp1); + m_arith_rewriter.mk_mod(tmp1, mk_numeral(n), tmp2); + m_bool_rewriter.mk_eq(m_zero_i, tmp2, result); + } + + void mk_div(expr* a, numeral const & k, expr_ref& result) { + result = m_arith.mk_div(a, m_arith.mk_numeral(k, false)); + simplify(result); + } + + expr_ref mk_idiv(expr* a, numeral const & k) { + if (k.is_one()) { + return expr_ref(a, m); + } + expr_ref result(m); + result = m_arith.mk_idiv(a, m_arith.mk_numeral(k, true)); + simplify(result); + return result; + } + + expr* mk_numeral(numeral const& k, bool is_int = true) { return m_arith.mk_numeral(k, is_int); } + + expr* mk_numeral(int k, bool is_int) { return mk_numeral(numeral(k),is_int); } + + expr* mk_uminus(expr* e) { + return m_arith.mk_uminus(e); + } + + expr* mk_abs(expr* e) { + rational val; + if (m_arith.is_numeral(e, val)) { + if (val.is_neg()) { + return m_arith.mk_uminus(e); + } + else { + return e; + } + } + else { + return m.mk_ite(m_arith.mk_le(mk_zero(e), e), e, m_arith.mk_uminus(e)); + } + } + + template + expr_ref mk_min_max(unsigned num_args, expr* const* args) { + SASSERT(num_args > 0); + if (num_args == 1) { + return expr_ref(args[0], m); + } + else { + expr_ref e2 = mk_min_max(num_args-1,args+1); + expr* e1 = args[0]; + expr* cmp = is_max?m_arith.mk_le(e1, e2):m_arith.mk_le(e2, e1); + return expr_ref(m.mk_ite(cmp, e2, e1), m); + } + } + + expr_ref mk_max(unsigned num_args, expr* const* args) { + return mk_min_max(num_args, args); + } + + expr_ref mk_min(unsigned num_args, expr* const* args) { + return mk_min_max(num_args, args); + } + + expr* mk_mul(expr* a, expr* b) { return m_arith.mk_mul(a,b); } + + expr* mk_add(expr* a, expr* b) { return m_arith.mk_add(a,b); } + + expr* mk_sub(expr* a, expr* b) { return m_arith.mk_sub(a,b); } + + expr* mk_mul(numeral const& a, expr* b) { + if (a.is_one()) return b; + return m_arith.mk_mul(mk_numeral(a, m_arith.is_int(b)),b); + } + + expr* mk_zero(sort* s) { return m_arith.is_int(s)?m_zero_i:m_zero_r; } + + expr* mk_zero(expr* e) { return m_arith.is_int(e)?m_zero_i:m_zero_r; } + + expr* mk_one(sort* s) { return m_arith.is_int(s)?m_one_i:m_one_r; } + + expr* mk_one(expr* e) { return m_arith.is_int(e)?m_one_i:m_one_r; } + + void mk_le(expr* e, expr_ref& result) { + expr_ref tmp(e, m); + simplify(tmp); + m_arith_rewriter.mk_le(tmp, mk_zero(e), result); + TRACE("qe_verbose", tout << "mk_le " << mk_pp(result, m) << "\n";); + } + + void mk_lt(expr* e, expr_ref& result) { + rational r; + if (m_arith.is_numeral(e, r)) { + if (r.is_neg()) { + result = m.mk_true(); + } + else { + result = m.mk_false(); + } + } + else if (m_arith.is_int(e)) { + result = m_arith.mk_le(e, m_minus_one_i); + } + else { + result = m.mk_not(m_arith.mk_le(mk_zero(e), e)); + } + simplify(result); + TRACE("qe_verbose", tout << "mk_lt " << mk_pp(result, m) << "\n";); + } + + // ax + t = 0 + void mk_eq(rational const& a, app* x, expr* t, expr_ref& result) { + result = m_arith.mk_eq(mk_add(mk_mul(a, x), t), mk_zero(x)); + } + + void mk_and(unsigned sz, expr*const* args, expr_ref& result) { + m_bool_rewriter.mk_and(sz, args, result); + } + + void mk_and(expr* e1, expr* e2, expr_ref& result) { + m_bool_rewriter.mk_and(e1, e2, result); + } + + void add_and(expr* e, ptr_vector& conjs) { + if (m.is_and(e)) { + conjs.append(to_app(e)->get_num_args(), to_app(e)->get_args()); + } + else { + conjs.push_back(e); + } + } + + void mk_flat_and(expr* e1, expr* e2, expr_ref& result) { + ptr_vector conjs; + add_and(e1, conjs); + add_and(e2, conjs); + m_bool_rewriter.mk_and(conjs.size(), conjs.c_ptr(), result); + } + + void mk_or(unsigned sz, expr*const* args, expr_ref& result) { + m_bool_rewriter.mk_or(sz, args, result); + } + + void mk_or(expr* e1, expr* e2, expr_ref& result) { + m_bool_rewriter.mk_or(e1, e2, result); + } + + // + // b*t <= a*s + // + void mk_resolve(app* x, bool is_strict, rational const& a, expr* t, rational const& b, expr* s, expr_ref& result) { + rational abs_a(abs(a)), abs_b(abs(b)); + SASSERT(a.is_neg() == b.is_pos()); + SASSERT(!is_strict || (abs_a.is_one() && abs_b.is_one())); + + expr_ref bt(mk_mul(abs_b, t), m); + expr_ref as(mk_mul(abs_a, s), m); + expr_ref as_bt(mk_add(as, bt), m); + + if(is_strict) { + mk_lt(as_bt, result); + } + else { + mk_le(as_bt, result); + } + + if (!abs_a.is_one() && !abs_b.is_one()) { + // integer resolution case. + SASSERT(!is_strict); + SASSERT(abs_a > rational::one() && abs_b > rational::one()); + expr_ref slack(mk_numeral((abs_a-numeral(1))*(abs_b-numeral(1)), true), m); + expr_ref result1(m), result2(m); + + // a*s + b*t <= 0 + expr_ref as_bt_le_0(result, m), tmp2(m), tmp3(m), tmp4(m); + + // a*s + b*t + (a-1)(b-1) <= 0 + mk_le(m_arith.mk_add(as_bt, slack), result1); + + rational a1 = a, b1 = b; + if (abs_a < abs_b) { + std::swap(abs_a, abs_b); + std::swap(a1, b1); + std::swap(s, t); + std::swap(as, bt); + } + SASSERT(abs_a >= abs_b); + + // 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 + // + + expr_ref sz(mk_add(s, x), m); + + if (b1.is_pos()) { + sz = m_arith.mk_uminus(sz); + } + tmp4 = mk_add(mk_mul(a1, sz), bt); + mk_le(tmp4, tmp3); + + if (to_app(tmp3)->get_arg(0) == x && + m_arith.is_zero(to_app(tmp3)->get_arg(1))) { + // exists z in [0 .. |b|-2] . |b| | (z + s) && z <= 0 + // <=> + // |b| | s + mk_divides(abs_b, s, tmp2); + } + else { + mk_divides(abs_b, sz, tmp2); + mk_and(tmp2, tmp3, tmp4); + mk_big_or(abs_b - numeral(2), x, tmp4, tmp2); + + } + mk_flat_and(as_bt_le_0, tmp2, result2); + mk_or(result1, result2, result); + simplify(result); + } + + TRACE("qe", + { + expr_ref_vector trail(m); + tout << "is_strict: " << (is_strict?"true":"false") << "\n"; + bound(m, a, t, false).pp(tout, x); + tout << "\n"; + bound(m, b, s, false).pp(tout, x); + tout << "\n"; + tout << mk_pp(result, m) << "\n"; + }); + } + + void simplify(expr_ref& p) { + m_rewriter(p); + } + + struct mul_lt { + arith_util& u; + mul_lt(arith_qe_util& u): u(u.m_arith) {} + bool operator()(expr* n1, expr* n2) const { + + expr* x, *y; + if (u.is_mul(n1, x, y) && u.is_numeral(x)) { + n1 = y; + } + if (u.is_mul(n2, x, y) && u.is_numeral(x)) { + n2 = y; + } + return n1->get_id() < n2->get_id(); + } + }; + + void normalize_sum(expr_ref& p) { + simplify(p); + if (!m_arith.is_add(p)) { + return; + } + unsigned sz = to_app(p)->get_num_args(); + ptr_buffer args; + for (unsigned i = 0; i < sz; ++i) { + args.push_back(to_app(p)->get_arg(i)); + } + std::sort(args.begin(), args.end(), mul_lt(*this)); + p = m_arith.mk_add(args.size(), args.c_ptr()); + } + + void pp_div(std::ostream& out, app* x, div_constraint const& div) { + out << div.k() << " | (" << div.a() << "*" << mk_pp(x, m) + << " + " << mk_pp(div.t(), m) << ") "; + } + + void pp_divs(std::ostream& out, app* x, div_constraints const& divs) { + for (unsigned i = 0; i < divs.size(); ++i) { + pp_div(out, x, divs[i]); + out << " "; + } + } + + bool mk_atom(expr* e, bool p, expr_ref& result) { + // retain equalities. + if (!is_app(e)) { + return false; + } + app* a = to_app(e); + + expr_ref t1(m), t2(m); + expr_ref tmp1(m), tmp2(m); + rational k; + expr* a0, *a1; + + if (p && is_divides(a, k, tmp1)) { + result = e; + } + else if (!p && is_divides(a, k, tmp1)) { + m_bool_rewriter.mk_not(e, result); + } + else if (p && m.is_eq(e, a0, a1) && is_arith(a0)) { + t1 = mk_sub(a0, a1); + simplify(t1); + t2 = mk_sub(a1, a0); + simplify(t2); + mk_le(t1, tmp1); + mk_le(t2, tmp2); + mk_and(tmp1, tmp2, result); + } + else if (!p && m.is_eq(e, a0, a1) && m_arith.is_int(a0)) { + tmp1 = mk_sub(a0, a1); + t1 = mk_add(mk_one(a0), tmp1); + simplify(t1); + t2 = mk_sub(mk_one(a0), tmp1); + simplify(t2); + mk_le(t1, tmp1); // a0 < a1 <=> 1 + a0 - a1 <= 0 + mk_le(t2, tmp2); // a0 > a1 <=> 1 - a0 + a1 <= 0 + mk_or(tmp1, tmp2, result); + } + else if (!p && m.is_eq(e, a0, a1) && m_arith.is_real(a0)) { + t1 = mk_sub(a0, a1); + simplify(t1); + t2 = mk_sub(a1, a0); + simplify(t2); + mk_lt(t1, tmp1); + mk_lt(t2, tmp2); + mk_or(tmp1, tmp2, result); + } + else if (!p && (m_arith.is_le(e, a0, a1) || m_arith.is_ge(e, a1, a0))) { + tmp1 = mk_sub(a1, a0); + mk_lt(tmp1, result); + } + else if (p && (m_arith.is_le(e) || m_arith.is_ge(e))) { + result = e; + } + else if (p && (m_arith.is_lt(e, a0, a1) || m_arith.is_gt(e, a1, a0))) { + tmp1 = mk_sub(a0, a1); + mk_lt(tmp1, result); + } + else if (!p && (m_arith.is_lt(e, a0, a1) || m_arith.is_gt(e, a1, a0))) { + tmp1 = mk_sub(a1, a0); + mk_le(tmp1, result); + } + else { + return false; + } + TRACE("qe_verbose", tout << "Atom: " << mk_pp(result, m) << "\n";); + return true; + } + + void mk_bounded_var(rational const& n, app_ref& z_bv, app_ref& z) { + rational two(2), b(n); + unsigned sz = 0; + do { + ++sz; + b = div(b, two); + } + while (b.is_pos()); + sort* s = m_bv.mk_sort(sz); + z_bv = m.mk_fresh_const("z", s); + expr_ref tmp(m); + z = m_bv.mk_bv2int(z_bv); + } + + bool solve(conj_enum& conjs, expr* fml) { + expr_ref_vector eqs(m); + extract_equalities(conjs, eqs); + return reduce_equations(eqs.size(), eqs.c_ptr(), fml); + } + + // ---------------------------------------------------------------------- + // + // Equation solving features. + // + // Extract equalities from current goal. + // + void extract_equalities(conj_enum& conjs, expr_ref_vector& eqs) { + obj_hashtable leqs; + expr_ref_vector trail(m); + expr_ref tmp1(m), tmp2(m); + expr *a0, *a1; + eqs.reset(); + conj_enum::iterator it = conjs.begin(), end = conjs.end(); + for (; it != end; ++it) { + expr* e = *it; + bool is_leq = false; + + if (m.is_eq(e, a0, a1) && is_arith(a0)) { + m_arith_rewriter.mk_sub(a0, a1, tmp1); + simplify(tmp1); + eqs.push_back(tmp1); + } + else if (m_arith.is_le(e, a0, a1) || m_arith.is_ge(e, a1, a0)) { + m_arith_rewriter.mk_sub(a0, a1, tmp1); + is_leq = true; + } + else { + // drop equality. + } + + if (is_leq) { + normalize_sum(tmp1); + tmp2 = m_arith.mk_uminus(tmp1); + normalize_sum(tmp2); + if (leqs.contains(tmp2)) { + eqs.push_back(tmp1); + TRACE("qe", tout << "found: " << mk_pp(tmp1, m) << "\n";); + } + else { + trail.push_back(tmp1); + leqs.insert(tmp1); + TRACE("qe_verbose", tout << "insert: " << mk_pp(tmp1, m) << "\n";); + } + } + } + } + + + private: + + // + // match p <= 0 or p >= 0 + // + template + bool is_le_ge_core(app* e, expr_ref& p) { + numeral k; + expr_ref tmp(m); + expr* a2; + + if (m_arith.is_le(e)) { + p = e->get_arg(1-IS_LE); + a2 = e->get_arg(IS_LE); + if (m_arith.is_numeral(a2, k) && k.is_zero()) { + return true; + } + } + else if (m_arith.is_ge(e)) { + p = e->get_arg(IS_LE); + a2 = e->get_arg(1-IS_LE); + if (m_arith.is_numeral(a2, k) && k.is_zero()) { + return true; + } + } + else { + return false; + } + p = mk_sub(p, a2); + simplify(p); + return true; + } + + bool is_arith(expr* e) { + return m_arith.is_int(e) || m_arith.is_real(e); + } + + void mk_big_or(numeral up, app* x, expr* body, expr_ref& result) { + TRACE("qe", tout << mk_pp(x, m) << " " << mk_pp(body, m) << "\n";); + if (numeral(1) >= up) { + mk_big_or_blast(up, x, body, result); + } + else { + mk_big_or_symbolic_blast(up, x, body, result); + } + } + + void mk_big_or_blast(numeral up, app* x, expr* body, expr_ref& result) { + expr_ref_vector ors(m); + numeral index(0); + while (index <= up) { + expr* n = mk_numeral(index); + result = body; + m_replace->apply_substitution(x, n, result); + ors.push_back(result); + ++index; + } + mk_or(ors.size(), ors.c_ptr(), result); + TRACE("qe", + tout + << "[0 " << up << "] " + << mk_pp(x, m) << "\n" + << mk_pp(body, m) << "\n" + << mk_pp(result, m) << "\n";); + } + + void mk_big_or_symbolic(numeral up, app* x, expr* body, expr_ref& result) { + app_ref z_bv(m); + mk_big_or_symbolic(up, x, body, z_bv, result); + m_ctx.add_var(z_bv); + } + + void mk_big_or_symbolic_blast(numeral up, app* x, expr* body, expr_ref& result) { + app_ref z_bv(m); + mk_big_or_symbolic(up, x, body, z_bv, result); + m_ctx.blast_or(z_bv, result); + } + + void mk_big_or_symbolic(numeral up, app* x, expr* body, app_ref& z_bv, expr_ref& result) { + expr* e1 = m_arith.mk_le(x, m_arith.mk_numeral(up, true)); + mk_flat_and(e1, body, result); + app_ref z(m); + mk_bounded_var(up, z_bv, z); + m_replace->apply_substitution(x, z, result); + } + + + + + + // + // Determine if 'x' can be isolated. + // Return the coefficient if found. + // + bool isolate_x(expr* p, app* x, contains_app& contains_x, numeral& coeff) { + numeral k; + + while (m_arith.is_add(p)) { + bool found_x = false; + expr* next_p = 0; + for (unsigned i = 0; i < to_app(p)->get_num_args(); ++i) { + expr* arg = to_app(p)->get_arg(i); + if (contains_x(arg)) { + if (found_x) { + return false; + } + found_x = true; + next_p = arg; + } + } + if (!next_p) { + return false; + } + p = next_p; + } + + expr *e1, *e2; + if (p == x) { + coeff = numeral(1); + return true; + } + else if (m_arith.is_mul(p, e1, e2) && + m_arith.is_numeral(e1, k) && + e2 == x) { + coeff = k; + return true; + } + else if (m_arith.is_mul(p, e1, e2) && + m_arith.is_numeral(e2, k) && + e1 == x) { + coeff = k; + return true; + } + return false; + } + + // + // Reduce equations. + // Singular equations eliminate variables directly. + // Linear equations eliminate original variables and introduce auxiliary variables. + // + + bool reduce_equations(unsigned num_eqs, expr * const* eqs, expr* fml) { + for (unsigned i = 0; i < num_eqs; ++i) { + if (reduce_equation(eqs[i], fml)) { + return true; + } + } + return false; + } + + bool solve_singular(unsigned var_idx, expr* p, expr* fml) { + rational k; + expr_ref e(m), tmp(m); + + app* x = m_ctx.get_var(var_idx); + + if (!isolate_x(p, x, m_ctx.contains(var_idx), k)) { + return false; + } + if (m_arith.is_int(x) && !(abs(k).is_one())) { + return false; + } + + if (abs(k).is_one()) { + if (k.is_neg()) { + e = m_arith.mk_add(p, x); + } + else { + e = m_arith.mk_sub(x, p); + } + } + else { + SASSERT(!m_arith.is_int(x)); + // p = p' + k*x = 0 + // <=> + // -k*x = p' = p - k*x + // => + // x = (p - k*x)/ -k + expr* ke = m_arith.mk_numeral(-k, false); + tmp = m_arith.mk_mul(ke, x); + tmp = m_arith.mk_add(p, tmp); + e = m_arith.mk_div(tmp, ke); + } + TRACE("qe", + tout << "is singular:\n" + << mk_pp(p, m) << "\n" + << mk_pp(fml, m) << "\n" + << mk_pp(x, m) << " = " + << mk_pp(e, m) << "\n"; + ); + expr_ref result(fml, m); + m_replace->apply_substitution(x, e, result); + simplify(result); + TRACE("qe", + tout << "singular solved:\n" + << mk_pp(result, m) << "\n"; + ); + m_ctx.elim_var(var_idx, result, e); + return true; + } + + bool solve_singular(expr* p, expr* fml) { + unsigned num_vars = m_ctx.get_num_vars(); + for (unsigned i = 0; i < num_vars; ++i) { + if (solve_singular(i, p, fml)) { + return true; + } + } + return false; + } + + bool solve_linear(expr* p, expr* fml) { + vector values; + unsigned num_vars = m_ctx.get_num_vars(); + app*const* vars_ptr = m_ctx.get_vars(); + + if (!is_linear(p, num_vars, vars_ptr, values)) { + return false; + } + + TRACE("qe", tout << "is linear: " << mk_pp(p, m) << "\n";); + SASSERT(values.size() == num_vars + 1); + SASSERT(num_vars > 0); + + unsigned index; + bool is_aux; + // + // The first entry in values is the constant. + // + VERIFY(m_arith_solver.solve_integer_equation(values, index, is_aux)); + + SASSERT(1 <= index && index <= num_vars); + app_ref x(m_ctx.get_var(index-1), m); + app_ref z(m); + expr_ref p1(m); + if (is_aux) { + // An auxiliary variable was introduced in lieu of 'x'. + // it has coefficient 'm' = values[index]. + SASSERT(values[index] >= rational(3)); + z = m.mk_fresh_const("x", m_arith.mk_int()); + m_ctx.add_var(z); + p1 = m_arith.mk_mul(m_arith.mk_numeral(values[index], true), z); + } + else { + // the coefficient to 'x' is -1. + p1 = m_arith.mk_numeral(numeral(0), true); + } + + for (unsigned i = 1; i <= num_vars; ++i) { + numeral k = values[i]; + if (!k.is_zero() && i != index) { + p1 = m_arith.mk_add(p1, m_arith.mk_mul(m_arith.mk_numeral(k, true), m_ctx.get_var(i-1))); + } + } + p1 = m_arith.mk_add(p1, m_arith.mk_numeral(values[0], true)); + + TRACE("qe", + tout << "is linear:\n" + << mk_pp(fml, m) << "\n" + << mk_pp(p, m) << "\n" + << mk_pp(x, m) << " = " + << mk_pp(p1, m) << "\n"; + tout << values[0] << " + "; + for (unsigned i = 0; i < num_vars; ++i) { + tout << " + " << values[i+1] << " * " << mk_pp(m_ctx.get_var(i), m) << " "; + } + tout << " = 0\n"; + ); + expr_ref result(fml, m); + m_replace->apply_substitution(x, p1, result); + simplify(result); + m_ctx.elim_var(index-1, result, p1); + TRACE("qe", tout << "Reduced: " << mk_pp(result, m) << "\n";); + return true; + } + + bool reduce_equation(expr* p, expr* fml) { + TRACE("qe", tout << mk_pp(p, m) << "\n";); + numeral k; + + if (m_arith.is_numeral(p, k) && k.is_zero()) { + return false; + } + + return + solve_singular(p, fml) || + solve_linear(p, fml); + } + + bool find_variable(expr* p, unsigned num_vars, app* const* vars, numeral* values, numeral const& k) { + if (!is_app(p) || to_app(p)->get_num_args() > 0) { + return false; + } + for (unsigned i = 0; i < num_vars; ++i) { + if (p == vars[i]) { + values[i] += k; + return true; + } + } + return false; + } + + bool is_linear(expr* p, unsigned num_vars, app* const* vars, vector& values) { + if (num_vars == 0) { + return false; + } + values.reset(); + for (unsigned i = 0; i <= num_vars; ++i) { + values.push_back(numeral(0)); + } + numeral* vars_ptr = values.c_ptr() + 1; + ptr_vector todo; + numeral k; + expr* e1, *e2; + todo.push_back(p); + while (!todo.empty()) { + p = todo.back(); + todo.pop_back(); + if (m_arith.is_add(p)) { + for (unsigned i = 0; i < to_app(p)->get_num_args(); ++i) { + todo.push_back(to_app(p)->get_arg(i)); + } + } + else if (m_arith.is_mul(p, e1, e2) && + m_arith.is_numeral(e1, k) && + find_variable(e2, num_vars, vars, vars_ptr, k)) { + // ok + } + else if (m_arith.is_mul(p, e1, e2) && + m_arith.is_numeral(e2, k) && + find_variable(e1, num_vars, vars, vars_ptr, k)) { + // ok + } + else if (find_variable(p, num_vars, vars, vars_ptr, k)) { + // ok + } + else if (m_arith.is_numeral(p, k)) { + values[0] += k; + } + else { + TRACE("qe_verbose", tout << "non-linear " << mk_pp(p, m) << "\n";); + return false; + } + } + return true; + } + + }; + + class bounds_proc { + arith_qe_util& m_util; + ast_mark m_mark; + expr_ref_vector m_le_terms, m_ge_terms, m_lt_terms, m_gt_terms; + vector m_le_coeffs, m_ge_coeffs, m_lt_coeffs, m_gt_coeffs; + app_ref_vector m_le_atoms, m_ge_atoms, m_lt_atoms, m_gt_atoms; + + expr_ref_vector m_div_terms; + vector m_div_coeffs, m_div_divisors; + app_ref_vector m_div_atoms; + app_ref m_div_z; + + expr_ref_vector m_nested_div_terms; + vector m_nested_div_coeffs, m_nested_div_divisors; + app_ref_vector m_nested_div_atoms; + app_ref_vector m_nested_div_z; + rational m_d; + + public: + bounds_proc(arith_qe_util& u): + m_util(u), + m_le_terms(u.get_manager()), + m_ge_terms(u.get_manager()), + m_lt_terms(u.get_manager()), + m_gt_terms(u.get_manager()), + m_le_atoms(u.get_manager()), + m_ge_atoms(u.get_manager()), + m_lt_atoms(u.get_manager()), + m_gt_atoms(u.get_manager()), + m_div_terms(u.get_manager()), + m_div_atoms(u.get_manager()), + m_div_z(u.get_manager()), + m_nested_div_terms(u.get_manager()), + m_nested_div_atoms(u.get_manager()), + m_nested_div_z(u.get_manager()) + { + reset(); + } + + + bool get_bound(contains_app& contains_x, app* a) { + ast_manager& m = m_util.get_manager(); + app* x = contains_x.x(); + if (m_mark.is_marked(a) || + get_le_bound(contains_x, a) || + get_lt_bound(contains_x, a) || + get_divides(contains_x, a) || + get_nested_divs(contains_x, a)) { + TRACE("qe_verbose", tout << "Bound for " << mk_pp(x, m) << " within " << mk_pp(a, m) << "\n";); + m_mark.mark(a, true); + return true; + } + else { + TRACE("qe", tout << "No bound for " << mk_pp(x, m) << " within " << mk_pp(a, m) << "\n";); + return false; + } + } + + unsigned lt_size() { return m_lt_terms.size(); } + unsigned le_size() { return m_le_terms.size(); } + unsigned gt_size() { return m_gt_terms.size(); } + unsigned ge_size() { return m_ge_terms.size(); } + unsigned t_size(bool is_l) { return is_l?lt_size():gt_size(); } + unsigned e_size(bool is_l) { return is_l?le_size():ge_size(); } + unsigned size(bool is_strict, bool is_l) { return is_strict?t_size(is_l):e_size(is_l); } + + expr* const* lt() { return m_lt_terms.c_ptr(); } + expr* const* le() { return m_le_terms.c_ptr(); } + expr* const* gt() { return m_gt_terms.c_ptr(); } + expr* const* ge() { return m_ge_terms.c_ptr(); } + expr* const* t(bool is_l) { return is_l?lt():gt(); } + expr* const* e(bool is_l) { return is_l?le():ge(); } + expr* const* exprs(bool is_strict, bool is_l) { return is_strict?t(is_l):e(is_l);} + + rational const* lt_coeffs() { return m_lt_coeffs.c_ptr(); } + rational const* le_coeffs() { return m_le_coeffs.c_ptr(); } + rational const* gt_coeffs() { return m_gt_coeffs.c_ptr(); } + rational const* ge_coeffs() { return m_ge_coeffs.c_ptr(); } + rational const* t_coeffs(bool is_l) { return is_l?lt_coeffs():gt_coeffs(); } + rational const* e_coeffs(bool is_l) { return is_l?le_coeffs():ge_coeffs(); } + rational const* coeffs(bool is_strict, bool is_l) { return is_strict?t_coeffs(is_l):e_coeffs(is_l); } + + app* const* lt_atoms() { return m_lt_atoms.c_ptr(); } + app* const* le_atoms() { return m_le_atoms.c_ptr(); } + app* const* gt_atoms() { return m_gt_atoms.c_ptr(); } + app* const* ge_atoms() { return m_ge_atoms.c_ptr(); } + app* const* t_atoms(bool is_l) { return is_l?lt_atoms():gt_atoms(); } + app* const* e_atoms(bool is_l) { return is_l?le_atoms():ge_atoms(); } + app* const* atoms(bool is_strict, bool is_l) { return is_strict?t_atoms(is_l):e_atoms(is_l); } + + unsigned div_size() const { return m_div_terms.size(); } + app* const* div_atoms() { return m_div_atoms.c_ptr(); } + rational const* div_coeffs() { return m_div_coeffs.c_ptr(); } + expr* const* div_terms() { return m_div_terms.c_ptr(); } + rational const* divisors() { return m_div_divisors.c_ptr(); } + + bool div_z(rational & d, app_ref& z_bv, app_ref& z) { + if (m_div_z.get()) { + z = m_div_z; + z_bv = to_app(z->get_arg(0)); + d = m_d; + return true; + } + if (m_div_terms.empty() && m_nested_div_terms.empty()) { + return false; + } + m_d = rational(1); + for (unsigned i = 0; i < m_div_divisors.size(); ++i) { + m_d = lcm(m_div_divisors[i], m_d); + } + for (unsigned i = 0; i < m_nested_div_divisors.size(); ++i) { + m_d = lcm(m_nested_div_divisors[i], m_d); + } + if (abs(m_d).is_one()) { + return false; + } + m_util.mk_bounded_var(m_d, z_bv, m_div_z); + z = m_div_z; + d = m_d; + return true; + } + + unsigned nested_div_size() const { return m_nested_div_terms.size(); } + app* nested_div_atom(unsigned idx) { return m_nested_div_atoms[idx].get(); } + rational const& nested_div_coeff(unsigned idx) { return m_nested_div_coeffs[idx]; } + expr* nested_div_term(unsigned idx) { return m_nested_div_terms[idx].get(); } + rational const& nested_divisor(unsigned idx) { return m_nested_div_divisors[idx]; } + app* nested_div_z(unsigned idx) { return m_nested_div_z[idx].get(); } + app* nested_div_z_bv(unsigned idx) { return to_app(m_nested_div_z[idx]->get_arg(0)); } + + void reset() { + m_lt_terms.reset(); + m_gt_terms.reset(); + m_ge_terms.reset(); + m_le_terms.reset(); + m_gt_coeffs.reset(); + m_lt_coeffs.reset(); + m_ge_coeffs.reset(); + m_le_coeffs.reset(); + m_lt_atoms.reset(); + m_gt_atoms.reset(); + m_le_atoms.reset(); + m_ge_atoms.reset(); + m_div_terms.reset(); + m_div_coeffs.reset(); + m_div_divisors.reset(); + m_div_atoms.reset(); + m_div_z = 0; + m_nested_div_terms.reset(); + m_nested_div_coeffs.reset(); + m_nested_div_divisors.reset(); + m_nested_div_atoms.reset(); + m_nested_div_z.reset(); + } + + private: + bool get_nested_divs(contains_app& contains_x, app* a) { + ast_manager& m = m_util.get_manager(); + ptr_vector todo; + todo.push_back(a); + rational k1, k2; + expr_ref rest(m); + while (!todo.empty()) { + expr* e = todo.back(); + todo.pop_back(); + if (m_mark.is_marked(e)) { + continue; + } + m_mark.mark(e, true); + if (!contains_x(e)) { + continue; + } + if (contains_x.x() == e) { + return false; + } + if (!is_app(e)) { + return false; + } + a = to_app(e); + if (m_util.m_arith.is_mod(e) && + m_util.m_arith.is_numeral(to_app(e)->get_arg(1), k1) && + m_util.get_coeff(contains_x, to_app(e)->get_arg(0), k2, rest)) { + app_ref z(m), z_bv(m); + m_util.mk_bounded_var(k1, z_bv, z); + m_nested_div_terms.push_back(rest); + m_nested_div_divisors.push_back(k1); + m_nested_div_coeffs.push_back(k2); + m_nested_div_atoms.push_back(a); + m_nested_div_z.push_back(z); + continue; + } + unsigned num_args = a->get_num_args(); + for (unsigned i = 0; i < num_args; ++i) { + todo.push_back(a->get_arg(i)); + } + } + return true; + } + + bool get_le_bound(contains_app& contains_x, app* a) { + ast_manager& m = m_util.get_manager(); + expr_ref p(m), rest(m); + rational k; + if (m_util.is_le(a, p) && m_util.get_coeff(contains_x, p, k, rest)) { + // k*x + rest <= 0 + if (m_util.is_real(contains_x.x())) { + m_util.mk_div(rest, abs(k), rest); + k = k.is_pos()?rational::one():rational::minus_one(); + } + if (k.is_neg()) { + m_le_terms.push_back(rest); + m_le_coeffs.push_back(k); + m_le_atoms.push_back(a); + } + else { + m_ge_terms.push_back(rest); + m_ge_coeffs.push_back(k); + m_ge_atoms.push_back(a); + } + return true; + } + return false; + } + + bool get_lt_bound(contains_app& contains_x, app* a) { + ast_manager& m = m_util.get_manager(); + expr_ref p(m), rest(m), na(m); + rational k; + if (m_util.is_lt(a, p) && m_util.get_coeff(contains_x, p, k, rest)) { + // k*x + rest < 0 + } + else if (m_util.is_neg(a, na) && is_app(na) && + m_util.is_ge(to_app(na), p) && + m_util.get_coeff(contains_x, p, k, rest)) { + // + // not (k*x + rest >= 0) + // <=> + // k*x + rest < 0 + // + } + else { + return false; + } + + SASSERT(m_util.is_real(contains_x.x())); + m_util.mk_div(rest, abs(k), rest); + if (k.is_neg()) { + m_lt_terms.push_back(rest); + m_lt_coeffs.push_back(rational::minus_one()); + m_lt_atoms.push_back(a); + } + else { + m_gt_terms.push_back(rest); + m_gt_coeffs.push_back(rational::one()); + m_gt_atoms.push_back(a); + } + return true; + } + + bool get_divides(contains_app& contains_x, app* a) { + ast_manager& m = m_util.get_manager(); + expr_ref p(m), rest(m); + app_ref a2(m); + numeral k, k2; + + if (m_util.is_divides(a, k, p) && m_util.get_coeff(contains_x, p, k2, rest)) { + m_div_terms.push_back(rest); + m_div_divisors.push_back(k); + m_div_coeffs.push_back(k2); + m_div_atoms.push_back(a); + return true; + } + if (m_util.is_not_divides(a, a2, k, p) && m_util.get_coeff(contains_x, p, k2, rest)) { + m_div_terms.push_back(rest); + m_div_divisors.push_back(k); + m_div_coeffs.push_back(k2); + m_div_atoms.push_back(a2); + return true; + } + return false; + } +public: + void display(std::ostream& out) { + ast_manager& m = m_util.get_manager(); + for (unsigned i = 0; i < lt_size(); ++i) { + out << mk_pp(lt()[i], m) << " < 0\n"; + } + for (unsigned i = 0; i < le_size(); ++i) { + out << mk_pp(le()[i], m) << " < 0\n"; + } + for (unsigned i = 0; i < gt_size(); ++i) { + out << mk_pp(gt()[i], m) << " < 0\n"; + } + for (unsigned i = 0; i < ge_size(); ++i) { + out << mk_pp(ge()[i], m) << " < 0\n"; + } + } + }; + + class x_subst { + arith_qe_util& m_super; + expr_ref m_t; + rational m_coeff; + public: + + x_subst(arith_qe_util& s): + m_super(s), + m_t(s.get_manager()), + m_coeff(rational::one()) + {} + + void set_term(expr* t) { m_t = t; } + + void set_coeff(rational const& k) { m_coeff = k; } + + expr* get_term() const { return m_t; } + + rational get_coeff() const { return m_coeff; } + + expr_ref mk_term(rational const& c, expr* t) { + // return t + c*m_t + ast_manager& m = m_super.get_manager(); + if (m_t.get()) { + return expr_ref(m_super.mk_add(m_super.mk_mul(c, m_t), t), m); + } + else { + return expr_ref(t, m); + } + } + + rational mk_coeff(rational const& k) { + return k * m_coeff; + } + }; + + + struct branch_formula { + expr* m_fml; + app* m_var; + unsigned m_branch; + expr* m_result; + rational m_coeff; + expr* m_term; + + branch_formula(): m_fml(0), m_var(0), m_branch(0), m_result(0), m_term(0) {} + + branch_formula(expr* fml, app* var, unsigned b, expr* r, rational coeff, expr* term): + m_fml(fml), + m_var(var), + m_branch(b), + m_result(r), + m_coeff(coeff), + m_term(term) + {} + + unsigned mk_hash() const { + return mk_mix(m_fml?m_fml->hash():0, m_var?m_var->hash():0, m_branch); + } + + bool mk_eq(branch_formula const& other) const { + return + m_fml == other.m_fml && + m_var == other.m_var && + m_branch == other.m_branch; + } + + struct hash { + typedef branch_formula data; + unsigned operator()(data const& d) const { return d.mk_hash(); } + }; + + struct eq { + typedef branch_formula data; + bool operator()(data const& x, data const& y) const { return x.mk_eq(y); } + }; + }; + + class arith_plugin : public qe_solver_plugin { + typedef obj_pair_map bounds_cache; + typedef obj_pair_map resolve_cache; + typedef hashtable subst_cache; + + arith_qe_util m_util; + expr_ref_vector m_trail; + bounds_cache m_bounds_cache; + subst_cache m_subst; + + public: + arith_plugin(i_solver_context& ctx, ast_manager& m, front_end_params& p): + qe_solver_plugin(m, m.get_family_id("arith"), ctx), + m_util(m, p, ctx), + m_trail(m) + {} + + ~arith_plugin() { + bounds_cache::iterator it = m_bounds_cache.begin(), end = m_bounds_cache.end(); + for (; it != end; ++it) { + dealloc(it->get_value()); + } + } + + virtual void assign(contains_app& contains_x, expr* fml, rational const& vl) { + SASSERT(vl.is_unsigned()); + app* x = contains_x.x(); + unsigned v = vl.get_unsigned(); + expr_ref result(fml, m); + unsigned t_size, e_size; + x_subst x_t(m_util); + + if (get_cache(x, fml, v, result)) { + return; + } + + bounds_proc& bounds = get_bounds(x, fml); + bool is_lower = get_bound_sizes(bounds, x, t_size, e_size); + assign_nested_divs(contains_x, bounds, result); + assign_divs(contains_x, bounds, x_t, result); + + //assign_all(contains_x, fml); + + if (v == 0) { + // + // index is for the infinity case. + // assert v => ~(x <= t) each t + // assert v => (x >= s) each s + // + mk_non_bounds(bounds, true, is_lower, result); + mk_non_bounds(bounds, false, is_lower, result); + + mk_non_resolve(bounds, true, is_lower, result); + mk_non_resolve(bounds, false, is_lower, result); + add_cache(x, fml, v, result, x_t.get_coeff(), x_t.get_term()); + TRACE("qe", + tout << vl << " " << mk_pp(x, m) << "\n"; + tout << mk_pp(fml, m) << "\n"; + tout << mk_pp(result, m) << "\n";); + return; + } + unsigned index = v-1; + bool is_strict = e_size <= index; + bool is_eq = false; + + SASSERT(index < t_size + e_size); + if (is_strict) { + index -= e_size; + TRACE("qe_verbose", bounds.display(tout); ); + } + else if (m_util.is_real(x)) { + SASSERT(0 == (e_size & 0x1)); + is_eq = (0 == (index & 0x1)); + index /= 2; + e_size /= 2; + } + SASSERT(is_strict || index < e_size); + SASSERT(!is_strict || index < t_size); + + // + // index is for the upper/lower-bound case. + // assert v => (x <= t_i) + // assert v => (x <= t_j => t_i <= t_j), add new atom to stack. + // assert v => (x >= s => s <= t_i), add new atom to stack. + // + // assert v => (x < t_j => t_i < t_j) + // + SASSERT(index < bounds.size(is_strict, is_lower)); + expr_ref t(bounds.exprs(is_strict, is_lower)[index], m); + rational a = bounds.coeffs(is_strict, is_lower)[index]; + + t = x_t.mk_term(a, t); + a = x_t.mk_coeff(a); + + mk_bounds(bounds, x, true, is_eq, is_strict, is_lower, index, a, t, result); + mk_bounds(bounds, x, false, is_eq, is_strict, is_lower, index, a, t, result); + + mk_resolve(bounds, x, x_t, true, is_eq, is_strict, is_lower, index, a, t, result); + mk_resolve(bounds, x, x_t, false, is_eq, is_strict, is_lower, index, a, t, result); + add_cache(x, fml, v, result, x_t.get_coeff(), x_t.get_term()); + TRACE("qe", + { + tout << vl << " " << mk_pp(x, m) << "\n"; + tout << mk_pp(fml, m) << "\n"; + tout << mk_pp(result, m) << "\n"; + } + ); + } + + + virtual bool get_num_branches(contains_app& contains_x, expr* fml, rational& nb) { + app* x = contains_x.x(); + if (!update_bounds(contains_x, fml)) { + return false; + } + bounds_proc& bounds = get_bounds(x, fml); + unsigned t_size, e_size; + get_bound_sizes(bounds, x, t_size, e_size); + nb = rational(t_size + e_size + 1); + return true; + } + + virtual void subst(contains_app& contains_x, rational const& vl, expr_ref& fml, expr_ref* def) { + SASSERT(vl.is_unsigned()); + if (def) { + get_def(contains_x, vl.get_unsigned(), fml, *def); + } + VERIFY(get_cache(contains_x.x(), fml, vl.get_unsigned(), fml)); + TRACE("qe", tout << mk_pp(contains_x.x(), m) << " " << vl << "\n" << mk_pp(fml, m) << "\n";); + } + + virtual bool project(contains_app& x, model_ref& model, expr_ref& fml) { + if (!update_bounds(x, fml)) { + TRACE("qe", tout << mk_pp(x.x(), m) << " failed to update bounds\n";); + return false; + } + if (m_util.m_arith.is_real(x.x())) { + return project_real(x, model, fml); + } + else { + return project_int(x, model, fml); + } + } + + + virtual unsigned get_weight(contains_app& contains_x, expr* fml) { + return 2; + } + + virtual bool solve(conj_enum& conjs, expr* fml) { + return m_util.solve(conjs, fml); + } + + virtual bool mk_atom(expr* e, bool p, expr_ref& result) { + return m_util.mk_atom(e, p, result); + } + + virtual bool is_uninterpreted(app* f) { + switch(f->get_decl_kind()) { + case OP_NUM: + case OP_LE: + case OP_LT: + case OP_GE: + case OP_GT: + case OP_ADD: + case OP_SUB: + case OP_UMINUS: + return false; + case OP_MOD: + if(m_util.m_arith.is_numeral(f->get_arg(1))) { + return false; + } + return true; + case OP_MUL: { + arith_util& a = m_util.m_arith; + expr* m, *n; + if (a.is_mul(f, m, n) && (a.is_numeral(m) || a.is_numeral(n))) { + return false; + } + return true; + } + default: + return true; + } + } + + private: + + void get_def(contains_app& contains_x, unsigned v, expr* fml, expr_ref& def) { + app* x = contains_x.x(); + x_subst x_t(m_util); + bounds_proc& bounds = get_bounds(x, fml); + branch_formula bf; + VERIFY (m_subst.find(branch_formula(fml, x, v, 0, rational::zero(), 0), bf)); + x_t.set_term(bf.m_term); + x_t.set_coeff(bf.m_coeff); + + // x is of the form: x_t.get_coeff()*x' + x_t.get_term() + CTRACE("qe", x_t.get_term(), tout << x_t.get_coeff() << " " << mk_pp(x_t.get_term(), m) << "\n";); + // + // a*x + t <= 0 + // a*(c*x' + s) + t <= 0 + // a*c*x' + a*s + t <= 0 + // + + unsigned t_size, e_size, sz; + bool is_lower = get_bound_sizes(bounds, x, t_size, e_size); + bool is_strict; + if (v == 0) { + is_strict = false; + sz = bounds.size(is_strict, !is_lower); + expr_ref_vector terms(m); + if (sz == 0) { + terms.push_back(m_util.mk_zero(x)); + } + for (unsigned i = 0; i < sz; ++i) { + // a*x + term <= 0 + expr_ref term(bounds.exprs(is_strict, !is_lower)[i], m); + rational a = bounds.coeffs(is_strict, !is_lower)[i]; + if (x_t.get_term()) { + // a*(c*x' + s) + term <= 0 + // term <- a*s + term + // a <- a*c + term = m_util.mk_add(m_util.mk_mul(a,x_t.get_term()), term); + a = a * x_t.get_coeff(); + } + SASSERT(a.is_int()); + if (is_lower) { + // a*x + t <= 0 + // <= + // x <= -t div a + SASSERT(a.is_pos()); + term = m_util.mk_idiv(m_util.mk_uminus(term), a); + } + else { + // -a*x + t <= 0 + // <=> + // t <= a*x + // <= + // ((div t a) + 1) <= x + term = m_util.mk_idiv(term, abs(a)); + if (!(abs(a).is_one())) { + term = m_util.mk_add(term, m_util.mk_one(x)); + } + } + terms.push_back(term); + SASSERT(is_lower == a.is_pos()); + TRACE("qe", tout << is_lower << " " << a << " " << mk_pp(term, m) << "\n";); + } + is_strict = true; + sz = bounds.size(is_strict, !is_lower); + for (unsigned i = 0; i < sz; ++i) { + expr_ref term(bounds.exprs(is_strict, !is_lower)[i], m); + SASSERT(abs(bounds.coeffs(is_strict, !is_lower)[i]).is_one()); + // + if (is_lower) { + // x + t < 0 + // <= + // x <= -t -1 + term = m_util.mk_uminus(m_util.mk_add(term, m_util.mk_one(x))); + } + else { + // -x + t < 0 + // <= + // t + 1 <= x + term = m_util.mk_add(term, m_util.mk_one(x)); + } + terms.push_back(term); + } + if (is_lower) { + def = m_util.mk_min(terms.size(), terms.c_ptr()); + } + else { + def = m_util.mk_max(terms.size(), terms.c_ptr()); + } + + if (x_t.get_term()) { + // + // x = x_t.get_coeff()*x' + x_t.get_term() + // => + // x' = (x - x_t.get_term()) div x_t.get_coeff() + // + def = m_util.mk_idiv(m_util.mk_sub(def, x_t.get_term()), x_t.get_coeff()); + } + + m_util.simplify(def); + return; + } + --v; + is_strict = e_size <= v; + bool is_eq = false; + + SASSERT(v < t_size + e_size); + if (is_strict) { + v -= e_size; + TRACE("qe_verbose", bounds.display(tout); ); + } + else if (m_util.is_real(x)) { + SASSERT(0 == (e_size & 0x1)); + is_eq = (0 == (v & 0x1)); + v /= 2; + e_size /= 2; + } + SASSERT(is_strict || v < e_size); + SASSERT(!is_strict || v < t_size); + + // + // index is for the upper/lower-bound case. + // assert v => (x <= t_i) + // + SASSERT(v < bounds.size(is_strict, is_lower)); + expr_ref t(bounds.exprs(is_strict, is_lower)[v], m); + rational a = bounds.coeffs(is_strict, is_lower)[v]; + + t = x_t.mk_term(a, t); + a = x_t.mk_coeff(a); + + def = t; + if (a.is_pos()) { + def = m_util.mk_uminus(def); + } + if (x_t.get_term()) { + def = m_util.mk_idiv(m_util.mk_sub(def, x_t.get_term()), x_t.get_coeff()); + } + if (!a.is_one()) { + def = m_util.mk_idiv(def, a); + } + m_util.simplify(def); + + TRACE("qe", tout << "TBD: " << a << " " << mk_pp(t, m) << "\n";); + } + + expr_ref mk_not(expr* e) { + expr* r; + if (m.is_not(e,r)) { + return expr_ref(r, m); + } + return expr_ref(m.mk_not(e), m); + } + + // + // Projection function for x of type real. + // TBD: model-based selection soundness/completeness? + // when model selects bound different from what is the smaller half, what then? + // shouldn't we find candidate among either lt or gt, and then if both can be found + // only then select which one to go with. Then assign has to be context-aware. + // Perhaps not really: the model is used as a hint. + // + + bool project_real(contains_app& x, model_ref& model, expr_ref& fml) { + SASSERT(m_util.m_arith.is_real(x.x())); + model_evaluator model_eval(*model); + bounds_proc& bounds = get_bounds(x.x(), fml); + bool is_lower = bounds.le_size() + bounds.lt_size() < bounds.ge_size() + bounds.gt_size(); + unsigned e_size = bounds.e_size(is_lower); + unsigned t_size = bounds.t_size(is_lower); + numeral bound1, bound2, vl, x_val; + unsigned idx1, idx2; + bool found1 = find_min_max(is_lower, false, bounds, model_eval, bound1, idx1); + bool found2 = find_min_max(is_lower, true, bounds, model_eval, bound2, idx2); + + if (!found1 && !found2) { + vl = numeral(0); + } + else if (found2 && (!found1 || bound2 <= bound1)) { + // strict indices come after non-strict indices. There + // is a pair of index values for non-strict inequalities + // corresponding to the disjunction (x < t || x = t) + vl = numeral(1 + 2*e_size + idx2); + } + else if (found1 && (!found2 || bound1 < bound2)) { + expr_ref val_x(m); + model_eval(x.x(), val_x); + VERIFY(m_util.m_arith.is_numeral(val_x, x_val)); + if (x_val == bound1) { + vl = numeral(1 + 2*idx1); // even indicates equality between x and bound. + } + else { + vl = numeral(1 + 2*idx1 + 1); // odd indicates strict bound. + } + } + assign(x, fml, vl); + subst(x, vl, fml, 0); + TRACE("qe", tout << mk_pp(fml, m) << "\n";); + return true; + } + + bool project_int(contains_app& x, model_ref& model, expr_ref& fml) { + model_evaluator model_eval(*model); + bounds_proc& bounds = get_bounds(x.x(), fml); + SASSERT(m_util.m_arith.is_int(x.x())); + SASSERT(bounds.lt_size() == 0 && bounds.gt_size() == 0); + bool is_lower = bounds.le_size() < bounds.ge_size(); + numeral bound, vl, x_val; + unsigned idx = bounds.le_size() + bounds.ge_size(); + bool found = find_min_max(is_lower, false, bounds, model_eval, bound, idx); + + if (found) { + SASSERT(idx < bounds.size(false, is_lower)); + vl = numeral(1 + idx); + } + else { + vl = numeral(0); + } + assign(x, fml, vl); + subst(x, vl, fml, 0); + TRACE("qe", tout << mk_pp(fml, m) << "\n";); + + return true; + } + + bool find_min_max(bool is_lower, bool is_strict, bounds_proc& bounds, + model_evaluator& eval, rational& bound, unsigned& idx) { + bool found = false; + unsigned num_bounds = bounds.size(is_strict, is_lower); + rational num; + for (unsigned i = 0; i < num_bounds; ++i) { + expr_ref vl(m); + eval(bounds.atoms(is_strict, is_lower)[i], vl); + if (!m.is_true(vl)) { + continue; + } + eval(bounds.exprs(is_strict, is_lower)[i], vl); + VERIFY(m_util.m_arith.is_numeral(vl, num)); + num /= abs(bounds.coeffs(is_strict,is_lower)[i]); + if (found) { + if (is_lower?(num < bound):(num > bound)) { + bound = num; + idx = i; + } + } + else { + found = true; + idx = i; + bound = num; + } + } + return found; + } + + + bool get_bound_sizes(bounds_proc& bounds, app* x, unsigned& t_size, unsigned& e_size) { + unsigned le_size = bounds.le_size(); + unsigned ge_size = bounds.ge_size(); + if (m_util.is_real(x)) { + le_size *= 2; + ge_size *= 2; + } + if (le_size + bounds.lt_size() < ge_size + bounds.gt_size()) { + e_size = le_size; + t_size = bounds.lt_size(); + return true; + } + else { + e_size = ge_size; + t_size = bounds.gt_size(); + return false; + } + } + + void add_cache(app* x, expr* fml, unsigned v, expr* result, rational coeff, expr* term) { + m_trail.push_back(x); + m_trail.push_back(fml); + m_trail.push_back(result); + if (term) m_trail.push_back(term); + m_subst.insert(branch_formula(fml, x, v, result, coeff, term)); + } + + bool get_cache(app* x, expr* fml, unsigned v, expr_ref& result) { + branch_formula bf; + if (!m_subst.find(branch_formula(fml, x, v, 0, rational::zero(), 0), bf)) { + return false; + } + SASSERT(bf.m_result); + result = bf.m_result; + return true; + } + + void assign_divs(contains_app& contains_x, bounds_proc& bounds, x_subst& x_t, expr_ref& result) { + app* x = contains_x.x(); + + app_ref z(m), z_bv(m); + rational d; + if (!bounds.div_z(d, z_bv, z)) { + return; + } + m_ctx.add_var(z_bv); + + // + // assert + // z < d + // d | (x - z) + // (c | ax + t <-> c | az + t) for each divisor. + // + + // z < d + expr* z_lt_d = m_util.m_arith.mk_le(z, m_util.m_arith.mk_numeral(d-rational(1), true)); + m_ctx.add_constraint(false, z_lt_d); + + // result <- result & z <= d - 1 + SASSERT(!abs(d).is_one()); + rational d1 = d - rational(1); + expr_ref tmp(m); + m_util.m_arith_rewriter.mk_le(z, m_util.m_arith.mk_numeral(d1, true), tmp); + m_util.m_bool_rewriter.mk_and(result, tmp, result); + + // d | (x - z) + expr_ref t1(m), new_atom(m); + t1 = m_util.mk_sub(x, z); + m_util.mk_divides(d, t1, new_atom); + m_ctx.add_constraint(false, new_atom); + + // (c | ax + t <-> c | az + t) for each divisor. + mk_div_equivs(bounds, z, result); + + // update x_t to map x |-> dx + z + x_t.set_term(z); + x_t.set_coeff(d); + } + + // + // (c | ax + t <-> c | az + t) for each divisor. + // + void mk_div_equivs(bounds_proc& bounds, expr* z, expr_ref& result) { + unsigned sz = bounds.div_size(); + app* const* atoms = bounds.div_atoms(); + rational const* coeffs = bounds.div_coeffs(); + expr* const* terms = bounds.div_terms(); + rational const* divisors = bounds.divisors(); + expr_ref new_atom(m), t1(m); + + for (unsigned i = 0; i < sz; ++i) { + app* atm = atoms[i]; + t1 = m_util.mk_add(m_util.mk_mul(coeffs[i], z), terms[i]); + m_util.mk_divides(divisors[i], t1, new_atom); + m_util.m_replace->apply_substitution(atm, new_atom.get(), result); + + m_ctx.add_constraint(false, mk_not(atm), new_atom); + m_ctx.add_constraint(false, mk_not(new_atom), atm); + } + } + + void assign_nested_divs(contains_app& contains_x, bounds_proc& bounds, expr_ref& result) { + unsigned num_nested_divs = bounds.nested_div_size(); + if (num_nested_divs == 0) { + return; + } + app_ref z(m), z_bv(m); + rational d; + VERIFY (bounds.div_z(d, z_bv, z)); + for (unsigned i = 0; i < num_nested_divs; ++i) { + // + // mod_term = arg_0 mod k + // + app* atm = bounds.nested_div_atom(i); + rational const& k = bounds.nested_divisor(i); + + app* z1_bv = bounds.nested_div_z_bv(i); + app* z1 = bounds.nested_div_z(i); + + m_ctx.add_var(z1_bv); + + // + // assert + // z < k + // (coeff*x + rest - z) mod k == 0 + // + + expr* z_lt_k = m_util.m_arith.mk_le(z1, m_util.m_arith.mk_numeral(k-rational(1), true)); + m_ctx.add_constraint(false, z_lt_k); + expr* e1 = m_util.m_arith.mk_sub(atm->get_arg(0), z1); + expr* e2 = atm->get_arg(1); + expr_ref mod_term2(m_util.m_arith.mk_mod(e1, e2), m); + m_util.simplify(mod_term2); + m_ctx.add_constraint(false, m.mk_eq(mod_term2, m_util.mk_zero(mod_term2))); + + m_util.m_replace->apply_substitution(atm, z1, result); + + // + // conjoin (coeff*z + rest - z1) mod k == 0 to result + // + expr_ref mod_eq(m), tmp1(m), tmp2(m); + + tmp2 = m_util.mk_numeral(bounds.nested_div_coeff(i), true); + tmp1 = m_util.m_arith.mk_mul(tmp2, z1); + tmp2 = m_util.m_arith.mk_sub(bounds.nested_div_term(i), z); + tmp2 = m_util.m_arith.mk_add(tmp1, tmp2); + tmp1 = m_util.m_arith.mk_mod(tmp2, bounds.nested_div_atom(i)->get_arg(1)); + + mod_eq = m.mk_eq(tmp1, m_util.mk_zero(z)); + m_util.simplify(mod_eq); + result = m.mk_and(result, mod_eq); + + TRACE("qe", tout << mk_pp(mod_eq, m) << "\n";); + } + } + + bounds_proc& get_bounds(app* x, expr* fml) { + bounds_proc* result = 0; + VERIFY (m_bounds_cache.find(x, fml, result)); + return *result; + } + + void mk_non_bounds(bounds_proc& bounds, bool is_strict, bool is_lower, expr_ref& result) { + unsigned sz = bounds.size(is_strict, is_lower); + for (unsigned i = 0; i < sz; ++i) { + app* e = bounds.atoms(is_strict, is_lower)[i]; + m_ctx.add_constraint(true, mk_not(e)); + m_util.m_replace->apply_substitution(e, m.mk_false(), result); + } + } + + void mk_non_resolve(bounds_proc& bounds, bool is_strict, bool is_lower, expr_ref& result) { + unsigned sz = bounds.size(is_strict, !is_lower); + for (unsigned i = 0; i < sz; ++i) { + app* e = bounds.atoms(is_strict, !is_lower)[i]; + m_ctx.add_constraint(true, e); + m_util.m_replace->apply_substitution(e, m.mk_true(), result); + } + } + + // + // phi[x < t, x <= s, x >= u, x > v] + // + // x = +oo: phi[false, false, true, true] + // x < t: phi[true, t-e < s, t - e >= u, t - e > v] == phi[true, t <= s, t > u, t > v] + // x < s: phi[s-e < t, true, s - e >= u, s - e > v] == phi[s <= t, true, s > u, s > v] + // x = s: phi[s < t, true, s >= u, s > v] + // + // assert + // path1 => x < t + // bounds: + // path1 => x < t' => t < t' when index(t') < index(t) + // path1 => x < t' => t <= t' when index(t') >= index(t) + // path1 => x <= s => t <= s + // resolve: + // path1 => x >= u => t > u + // path1 => x > v => t > v + // symmetry reduction: + // + // + // path2 => x <= s + // bounds: + // path2 => x < s => x < t => s <= t + // path2 => x = s => x < t => s < t + // path2 => x <= s => x <= s' => s < s' when index(s') < index(s) + // path2 => x <= s => x <= s' => s <= s' when index(s') >= index(s) + // resolve: + // path2 => x < s => x >= u => s > u + // path2 => x = s => x >= u => s >= u + // path2 => x <= s => x > v => s > v + // + + + void mk_bound(bool is_strict, bool is_lower, + rational const& a, expr* t, + rational const& b, expr* s, + expr_ref& result) + { + if (is_strict) { + if (is_lower) { + // b*t > a*s + m_util.mk_strict_bound(b, s, a, t, result); + } + else { + // b*t < a*s + m_util.mk_strict_bound(a, t, b, s, result); + } + } + else { + if (is_lower) { + // b*t >= a*s + m_util.mk_bound(b, s, a, t, result); + } + else { + // b*t <= a*s + m_util.mk_bound(a, t, b, s, result); + } + } + m_util.simplify(result); + } + + // + // a*x <= t, a*x < t + // + /* + - bounds + - add_assertion - flag whether to add side-effect to state + - x - the variable to be eliminated + - is_strict - whether to loop over strict inequalities + - is_eq_ctx - whether non-strict inequality is to be treated as equality case. + - is_strict_ctx - whether 'atm' is a strict inequality + - is_lower - whether 'x' is given a lower-bound in 'atm' + - index - index of 'atm' in 'bounds' 'atm = bounds[index]' + - a - coefficient to 'x' in 'atm' + - t - upper/lower bound to 'x' in 'atm' + */ + + + void mk_bounds(bounds_proc& bounds, + app* x, bool is_strict, bool is_eq_ctx, bool is_strict_ctx, bool is_lower, unsigned index, + rational const& a, expr* t, + expr_ref& result) + { + SASSERT(!is_eq_ctx || !is_strict_ctx); + unsigned sz = bounds.size(is_strict, is_lower); + expr_ref tmp(m), eq(m); + bool same_strict = (is_strict == is_strict_ctx); + bool non_strict_real = m_util.is_real(x) && !is_strict_ctx; + app* atm = bounds.atoms(is_strict_ctx, is_lower)[index]; + + for (unsigned i = 0; i < sz; ++i) { + app* e = bounds.atoms(is_strict, is_lower)[i]; + expr* s = bounds.exprs(is_strict, is_lower)[i]; + rational b = bounds.coeffs(is_strict, is_lower)[i]; + + if (same_strict && i == index) { + if (non_strict_real) { + m_util.mk_eq(a, x, t, eq); + TRACE("qe", tout << "a:" << a << " x: " << mk_pp(x, m) << " t: " << mk_pp(t, m) << " eq: " << mk_pp(eq, m) << "\n";); + if (is_eq_ctx) { + m_ctx.add_constraint(true, eq); + } + else { + m_ctx.add_constraint(true, mk_not(eq)); + m_ctx.add_constraint(true, e); + } + } + else { + m_ctx.add_constraint(true, e); + } + m_util.m_replace->apply_substitution(atm, m.mk_true(), result); + continue; + } + + // + // Break symmetries by using index: + // bounds before me are strictly larger. + // Cases: + // ax <= t & ax != t & bx < s => bt <= as + // ax <= t & ax = t & bx < s => bt < as + // bx <= s => bt < as or bt <= as depending on symmetry + // + bool result_is_strict = + (non_strict_real && is_eq_ctx && is_strict) || + (same_strict && i < index); + + mk_bound(result_is_strict, is_lower, a, t, b, s, tmp); + m_util.m_replace->apply_substitution(e, tmp.get(), result); + + TRACE("qe", + tout << (result_is_strict?"strict result":"non-strict result") << "\n"; + tout << (is_strict?"strict":"non-strict") << "\n"; + tout << mk_pp(atm, m) << " & "; + tout << mk_pp(e, m) << " --> "; + tout << mk_pp(tmp.get(), m) << "\n";); + + m_ctx.add_constraint(true, mk_not(e), tmp); + } + } + + // x <= t + // x != t => x >= u => t > u + // x = t => x >= u => t >= u + void mk_resolve(bounds_proc& bounds, + app* x, x_subst& x_t, bool is_strict, bool is_eq_ctx, bool is_strict_ctx, bool is_lower, + unsigned index, + rational const& a, expr* t, expr_ref& result) + { + expr_ref tmp(m); + unsigned sz = bounds.size(is_strict, !is_lower); + bool is_strict_real = !is_eq_ctx && m_util.is_real(x) && !is_strict_ctx; + bool strict_resolve = is_strict || is_strict_ctx || is_strict_real; + app* atm = bounds.atoms(is_strict_ctx, is_lower)[index]; + + for (unsigned i = 0; i < sz; ++i) { + app* e = bounds.atoms(is_strict, !is_lower)[i]; + expr_ref s(bounds.exprs(is_strict, !is_lower)[i], m); + rational b = bounds.coeffs(is_strict, !is_lower)[i]; + SASSERT(!b.is_zero()); + SASSERT(b.is_pos() != a.is_pos()); + + s = x_t.mk_term(b, s); + b = x_t.mk_coeff(b); + m_util.mk_resolve(x, strict_resolve, a, t, b, s, tmp); + m_util.m_replace->apply_substitution(e, tmp.get(), result); + + m_ctx.add_constraint(true, mk_not(e), tmp); + + TRACE("qe_verbose", + tout << mk_pp(atm, m) << " "; + tout << mk_pp(e, m) << " ==> "; + tout << mk_pp(tmp, m) << "\n"; + ); + } + } + + bool update_bounds(bounds_proc& bounds, contains_app& contains_x, expr* fml, atom_set const& tbl, bool is_pos) + { + app_ref tmp(m); + atom_set::iterator it = tbl.begin(), end = tbl.end(); + for (; it != end; ++it) { + app* e = *it; + if (!contains_x(e)) { + continue; + } + + if (!is_pos) { + SASSERT(!m.is_not(e)); + tmp = m.mk_not(e); + e = tmp; + } + + if (!bounds.get_bound(contains_x, e)) { + return false; + } + } + return true; + } + + bool update_bounds(contains_app& contains_x, expr* fml) { + bounds_proc* bounds = 0; + if (m_bounds_cache.find(contains_x.x(), fml, bounds)) { + return true; + } + bounds = alloc(bounds_proc, m_util); + + if (!update_bounds(*bounds, contains_x, fml, m_ctx.pos_atoms(), true)) { + dealloc(bounds); + return false; + } + if (!update_bounds(*bounds, contains_x, fml, m_ctx.neg_atoms(), false)) { + dealloc(bounds); + return false; + } + + m_trail.push_back(contains_x.x()); + m_trail.push_back(fml); + m_bounds_cache.insert(contains_x.x(), fml, bounds); + return true; + } + }; + + // --------------------- + // non-linear arithmetic + class nlarith_plugin : public qe_solver_plugin { + typedef obj_map weight_m; + typedef obj_pair_map bcs_t; + typedef obj_map weights_t; + bcs_t m_cache; + weights_t m_weights; + th_rewriter m_rewriter; + nlarith::util m_util; + scoped_ptr m_replacer; + expr_ref_vector m_trail; + factor_rewriter_star m_factor_rw; + bool m_produce_models; + public: + nlarith_plugin(i_solver_context& ctx, ast_manager& m, bool produce_models) : + qe_solver_plugin(m, m.get_family_id("arith"), ctx), + m_rewriter(m), + m_util(m), + m_replacer(mk_default_expr_replacer(m)), + m_trail(m), + m_factor_rw(m), + m_produce_models(produce_models) { + TRACE("qe", tout << "produce models: " << produce_models << "\n";); + m_util.set_enable_linear(true); // (produce_models); + } + + virtual ~nlarith_plugin() { + bcs_t::iterator it = m_cache.begin(), end = m_cache.end(); + for (; it != end; ++it) { + dealloc(it->get_value()); + } + weights_t::iterator it2 = m_weights.begin(), e2 = m_weights.end(); + for (; it2 != e2; ++it2) { + dealloc(it2->get_value()); + } + } + + virtual bool simplify(expr_ref& fml) { + expr_ref tmp(m), tmp2(m); + m_factor_rw(fml, tmp); + m_rewriter(tmp, tmp2); + if (fml.get() != tmp2.get()) { + fml = tmp2; + return true; + } + return false; + } + + virtual void assign(contains_app& x, expr* fml, rational const& vl) { + nlarith::branch_conditions *brs; + VERIFY (m_cache.find(x.x(), fml, brs)); + SASSERT(vl.is_unsigned()); + SASSERT(vl.get_unsigned() < brs->size()); + expr* branch_fml = brs->branches(vl.get_unsigned()); + expr_ref result(m), tmp(m); + m_factor_rw(branch_fml, tmp); + m_rewriter(tmp, result); + TRACE("qe", tout << vl << " " << mk_pp(result.get(), m) << "\n";); + m_ctx.add_constraint(true, result); + } + + virtual bool get_num_branches(contains_app& x, + expr* fml, rational& num_branches) { + nlarith::branch_conditions *brs; + if (m_cache.find(x.x(), fml, brs)) { + num_branches = rational(brs->size()); + return true; + } + expr_ref_vector lits(m); + update_bounds(lits, m_ctx.pos_atoms(), true); + update_bounds(lits, m_ctx.neg_atoms(), false); + + brs = alloc(nlarith::branch_conditions, m); + + TRACE("nlarith", tout << mk_pp(fml, m) << "\n";); + if (!m_util.create_branches(x.x(), lits.size(), lits.c_ptr(), *brs)) { + TRACE("nlarith", tout << "no branches for " << mk_pp(x.x(), m) << "\n";); + dealloc(brs); + return false; + } + num_branches = rational(brs->size()); + insert_cache(x.x(), fml, brs); + return true; + } + + virtual void subst(contains_app& x, rational const& vl, expr_ref& fml, expr_ref* def) { + nlarith::branch_conditions *brs; + VERIFY (m_cache.find(x.x(), fml, brs)); + SASSERT(vl.is_unsigned()); + SASSERT(vl.get_unsigned() < brs->size()); + unsigned j = vl.get_unsigned(); + expr_substitution sub(m); + for (unsigned i = 0; i < brs->preds().size(); ++i) { + sub.insert(to_app(brs->preds(i)), brs->subst(j)[i]); + } + m_replacer->set_substitution(&sub); + (*m_replacer)(fml); + expr_ref tmp(m.mk_and(brs->constraints(j), fml), m); + m_factor_rw(tmp, fml); + if (def) { + m_factor_rw(brs->def(j), *def); + } + } + + + virtual unsigned get_weight(contains_app& x, expr* fml) { + obj_map* weights = 0; + unsigned weight = 0; + if (!m_weights.find(fml, weights)) { + weights = alloc(weight_m); + m_weights.insert(fml, weights); + m_trail.push_back(fml); + ptr_vector nl_vars; + m_util.extract_non_linear(to_app(fml), nl_vars); + for (unsigned i = 0; i < nl_vars.size(); ++i) { + weights->insert(nl_vars[i], 100); + } + } + if (weights->find(x.x(), weight)) { + return weight; + } + return UINT_MAX; + } + + virtual bool solve(conj_enum& conjs, expr* fml) { return false; } + + // we don't need to modify the atom. + virtual bool mk_atom(expr* e, bool p, expr_ref& result) { return false; } + + virtual bool is_uninterpreted(app* f) { + if (m_produce_models) { + return true; + } + switch(f->get_decl_kind()) { + case OP_NUM: + case OP_LE: + case OP_LT: + case OP_GE: + case OP_GT: + case OP_ADD: + case OP_SUB: + case OP_UMINUS: + return false; + case OP_MUL: { + arith_util a(m); + expr* m, *n; + if (a.is_mul(f, m, n) && (a.is_numeral(m) || a.is_numeral(n))) { + return false; + } + return true; + } + default: + return true; + } + return true; + } + private: + + void insert_cache(app* x, expr* e, nlarith::branch_conditions* brs) { + m_trail.push_back(x); + m_trail.push_back(e); + m_cache.insert(x, e, brs); + } + + void update_bounds(expr_ref_vector& lits, atom_set const& tbl, bool is_pos) { + atom_set::iterator it = tbl.begin(), end = tbl.end(); + for (; it != end; ++it) { + app* e = *it; + lits.push_back(is_pos?e:m.mk_not(e)); + } + } + }; + + + qe_solver_plugin* mk_arith_plugin(i_solver_context& ctx, bool produce_models, front_end_params& p) { + if (p.m_nlquant_elim) { + return alloc(nlarith_plugin, ctx, ctx.get_manager(), produce_models); + } + else { + return alloc(arith_plugin, ctx, ctx.get_manager(), p); + } + } + +} diff --git a/lib/qe_array_plugin.cpp b/lib/qe_array_plugin.cpp new file mode 100644 index 000000000..c013f93f4 --- /dev/null +++ b/lib/qe_array_plugin.cpp @@ -0,0 +1,299 @@ + +#include "qe.h" +#include "array_decl_plugin.h" +#include "expr_replacer.h" +#include "ast_pp.h" +#include "arith_decl_plugin.h" + +namespace qe { + // --------------------- + // arrays + + class array_plugin : public qe_solver_plugin { + + scoped_ptr m_replace; + + public: + + array_plugin(i_solver_context& ctx, ast_manager& m) : + qe_solver_plugin(m, m.get_family_id("array"), ctx), + m_replace(mk_default_expr_replacer(m)) + { + } + + virtual ~array_plugin() {} + + + virtual void assign(contains_app& x, expr* fml, rational const& vl) { + UNREACHABLE(); + } + + virtual bool get_num_branches( contains_app& x, expr* fml, rational& num_branches) { + return false; + } + + virtual void subst(contains_app& x, rational const& vl, expr_ref& fml, expr_ref* def) { + UNREACHABLE(); + } + + + virtual bool solve(conj_enum& conjs, expr* fml) { + + conj_enum::iterator it = conjs.begin(), end = conjs.end(); + for (; it != end; ++it) { + expr* e = *it; + if (m.is_eq(e) && solve_eq(to_app(e), fml)) { + return true; + } + } + expr_ref_vector eqs(m); + conjs.extract_equalities(eqs); + for (unsigned i = 0; i < eqs.size(); ++i) { + TRACE("qe_verbose", + tout << mk_pp(eqs[i].get(), m) << "\n";); + expr* e = eqs[i].get(); + if (solve_eq_zero(e, fml)) { + return true; + } + } + return false; + } + + virtual bool is_uninterpreted(app* f) { + return true; + } + + + private: + + bool solve_eq(app* eq, expr* fml) { + SASSERT(m.is_eq(eq)); + expr* arg1 = eq->get_arg(0); + expr* arg2 = eq->get_arg(1); + return solve_eq(arg1, arg2, fml) || solve_eq(arg2, arg1, fml); + } + + bool solve_eq_zero(expr* e, expr* fml) { + arith_util arith(m); + if (arith.is_add(e)) { + app* a = to_app(e); + expr* e1, *e2; + unsigned sz = a->get_num_args(); + expr_ref_vector args(m); + expr_ref lhs(m), rhs(m); + rational r; + args.append(sz, a->get_args()); + for (unsigned i = 0; i < sz; ++i) { + expr_ref save(m); + save = lhs = args[i].get(); + args[i] = arith.mk_numeral(rational(0), m.get_sort(lhs)); + rhs = arith.mk_uminus(arith.mk_add(args.size(), args.c_ptr())); + if (arith.is_mul(lhs, e1, e2) && + arith.is_numeral(e1, r) && + r.is_minus_one()) { + lhs = to_app(e2); + rhs = arith.mk_uminus(rhs); + } + if (solve_eq(lhs, rhs, fml)) { + return true; + } + args[i] = save; + } + } + return false; + } + + + + bool solve_eq(expr* lhs, expr* rhs, expr* fml) { + + if (!is_app(lhs)) { + return false; + } + + TRACE("qe_verbose", + tout << mk_pp(lhs, m) << + " == " << mk_pp(rhs, m) << "\n";); + expr_ref tmp(m); + app* a = to_app(lhs); + // + // A = t, A not in t. + // + unsigned idx = 0; + if (m_ctx.is_var(a, idx) && + !m_ctx.contains(idx)(rhs)) { + expr_ref result(fml, m); + m_replace->apply_substitution(a, rhs, result); + m_ctx.elim_var(idx, result, rhs); + return true; + } + if (solve_store(a, rhs, fml)) { + return true; + } + if (solve_select(a, rhs, fml)) { + return true; + } + return false; + } + + bool solve_select2(app* lhs, expr* rhs, expr* fml) { + // + // (select (select A i) j) = t, A not in i, j, t + // A |-> (store B' j (store B i t)), where B, B' are fresh. + // + // TBD + return false; + } + + bool solve_select(app* lhs, expr* rhs, expr* fml) { + // + // (select A i) = t, A not in i, v, t + // A |-> (store B i t), where B is fresh. + // + unsigned idx = 0; + vector > args; + if (is_select(lhs, idx, rhs, args) && args.size() == 1) { + contains_app& contains_A = m_ctx.contains(idx); + app* A = contains_A.x(); + app_ref B(m); + expr_ref store_B_i_t(m); + + unsigned num_args = args[0].size(); + B = m.mk_fresh_const("B", m.get_sort(A)); + ptr_buffer args2; + args2.push_back(B); + for (unsigned i = 0; i < num_args; ++i) { + args2.push_back(args[0][i]); + } + args2.push_back(rhs); + + store_B_i_t = m.mk_app(m_fid, OP_STORE, args2.size(), args2.c_ptr()); + + TRACE("qe", + tout << "fml: " << mk_pp(fml, m) << "\n"; + tout << "solved form: " << mk_pp(store_B_i_t, m) << "\n"; + tout << "eq: " << mk_pp(lhs, m) << " == " << mk_pp(rhs, m) << "\n"; + ); + expr_ref result(fml, m); + m_replace->apply_substitution(A, store_B_i_t, result); + m_ctx.add_var(B); + m_ctx.elim_var(idx, result, store_B_i_t); + return true; + + } + return false; + } + + bool is_array_app_of(app* a, unsigned& idx, expr* t, decl_kind k) { + if (!is_app_of(a, m_fid, k)) { + return false; + } + expr* v = a->get_arg(0); + if (!m_ctx.is_var(v, idx)) { + return false; + } + contains_app& contains_v = m_ctx.contains(idx); + + for (unsigned i = 1; i < a->get_num_args(); ++i) { + if (contains_v(a->get_arg(i))) { + return false; + } + } + if (contains_v(t)) { + return false; + } + return true; + } + + + bool solve_store(app* lhs, expr* rhs, expr* fml) { + // + // store(store(A, j, u), i, v) = t, A not in i, j, u, v, t + // -> + // A |-> store(store(t, i, w), j, w') where w, w' are fresh. + // t[i] = v + // store(t, i, v)[j] = u + // + unsigned idx = 0; + vector > args; + if (is_store_update(lhs, idx, rhs, args)) { + contains_app& contains_A = m_ctx.contains(idx); + app* A = contains_A.x(); + app_ref w(m); + + expr_ref store_t(rhs, m), store_T(rhs, m), select_t(m); + ptr_vector args2; + for (unsigned i = args.size(); i > 0; ) { + --i; + args2.reset(); + w = m.mk_fresh_const("w", m.get_sort(args[i].back())); + args2.push_back(store_T); + args2.append(args[i]); + + select_t = m.mk_app(m_fid, OP_SELECT, args2.size()-1, args2.c_ptr()); + fml = m.mk_and(fml, m.mk_eq(select_t, args2.back())); + store_T = m.mk_app(m_fid, OP_STORE, args2.size(), args2.c_ptr()); + + args2[0] = store_t; + args2.back() = w; + store_t = m.mk_app(m_fid, OP_STORE, args2.size(), args2.c_ptr()); + + m_ctx.add_var(w); + } + + TRACE("qe", + tout << "Variable: " << mk_pp(A, m) << "\n"; + tout << "fml: " << mk_pp(fml, m) << "\n"; + tout << "solved form: " << mk_pp(store_t, m) << "\n"; + tout << "eq: " << mk_pp(lhs, m) << " == " << mk_pp(rhs, m) << "\n"; + ); + expr_ref result(fml, m); + m_replace->apply_substitution(A, store_t, result); + m_ctx.elim_var(idx, result, store_t); + return true; + } + return false; + } + + bool is_array_app_of(app* a, unsigned& idx, expr* t, decl_kind k, vector >& args) { + if (m_ctx.is_var(a, idx)) { + contains_app& contains_v = m_ctx.contains(idx); + if (args.empty() || contains_v(t)) { + return false; + } + for (unsigned i = 0; i < args.size(); ++i) { + for (unsigned j = 0; j < args[i].size(); ++j) { + if (contains_v(args[i][j])) { + return false; + } + } + } + return true; + } + if (!is_app_of(a, m_fid, k)) { + return false; + } + args.push_back(ptr_vector()); + for (unsigned i = 1; i < a->get_num_args(); ++i) { + args.back().push_back(a->get_arg(i)); + } + if (!is_app(a->get_arg(0))) { + return false; + } + return is_array_app_of(to_app(a->get_arg(0)), idx, t, k, args); + } + + bool is_store_update(app* a, unsigned& idx, expr* t, vector >& args) { + return is_array_app_of(a, idx, t, OP_STORE, args); + } + + bool is_select(app* a, unsigned& idx, expr* t, vector >& args) { + return is_array_app_of(a, idx, t, OP_SELECT, args); + } + }; + + qe_solver_plugin* mk_array_plugin(i_solver_context& ctx) { + return alloc(array_plugin, ctx, ctx.get_manager()); + } + +} diff --git a/lib/qe_bool_plugin.cpp b/lib/qe_bool_plugin.cpp new file mode 100644 index 000000000..782644c28 --- /dev/null +++ b/lib/qe_bool_plugin.cpp @@ -0,0 +1,180 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + qe_bool_plugin.cpp + +Abstract: + + Eliminate Boolean variable from formula + +Author: + + Nikolaj Bjorner (nbjorner) 2010-02-19 + +Revision History: + +Notes: + + The procedure is a bit naive. + Consider a co-factoring approach that eliminates all Boolean + variables in scope in one shot, similar to what we do with + BDDs. + +--*/ + +#include "qe.h" +#include "expr_replacer.h" +#include "ast_pp.h" +#include "model_evaluator.h" + + +namespace qe { + class bool_plugin : public qe_solver_plugin { + scoped_ptr m_replace; + public: + bool_plugin(i_solver_context& ctx, ast_manager& m): + qe_solver_plugin(m, m.get_basic_family_id(), ctx), + m_replace(mk_default_expr_replacer(m)) + {} + + virtual void assign(contains_app& x, expr* fml, rational const& vl) { + SASSERT(vl.is_zero() || vl.is_one()); + } + + virtual bool get_num_branches(contains_app& x, expr* fml, rational& nb) { + nb = rational(2); + return true; + } + + virtual void subst(contains_app& x, rational const& vl, expr_ref& fml, expr_ref* def) { + SASSERT(vl.is_one() || vl.is_zero()); + expr* tf = (vl.is_one())?m.mk_true():m.mk_false(); + m_replace->apply_substitution(x.x(), tf, 0, fml); + if (def) { + *def = tf; + } + } + + virtual bool project(contains_app& x, model_ref& model, expr_ref& fml) { + model_evaluator model_eval(*model); + expr_ref val_x(m); + rational val; + model_eval(x.x(), val_x); + CTRACE("qe", (!m.is_true(val_x) && !m.is_false(val_x)), + tout << "Boolean is a don't care: " << mk_pp(x.x(), m) << "\n";); + val = m.is_true(val_x)?rational::one():rational::zero(); + subst(x, val, fml, 0); + return true; + } + + virtual unsigned get_weight(contains_app& contains_x, expr* fml) { + app* x = contains_x.x(); + bool p = m_ctx.pos_atoms().contains(x); + bool n = m_ctx.neg_atoms().contains(x); + if (p && n) { + return 3; + } + return 0; + } + + virtual bool solve(conj_enum& conjs,expr* fml) { + return + solve_units(conjs, fml) || + solve_polarized(fml); + } + + virtual bool is_uninterpreted(app* a) { + return false; + } + + private: + + bool solve_units(conj_enum& conjs, expr* _fml) { + expr_ref fml(_fml, m); + conj_enum::iterator it = conjs.begin(), end = conjs.end(); + unsigned idx; + for (; it != end; ++it) { + expr* e = *it; + if (!is_app(e)) { + continue; + } + app* a = to_app(e); + expr* e1; + if (m_ctx.is_var(a, idx)) { + m_replace->apply_substitution(a, m.mk_true(), 0, fml); + m_ctx.elim_var(idx, fml, m.mk_true()); + return true; + } + else if (m.is_not(e, e1) && m_ctx.is_var(e1, idx)) { + m_replace->apply_substitution(to_app(e1), m.mk_false(), 0, fml); + m_ctx.elim_var(idx, fml, m.mk_false()); + return true; + } + } + return false; + } + + bool solve_polarized(expr* _fml) { + unsigned num_vars = m_ctx.get_num_vars(); + expr_ref fml(_fml, m), def(m); + for (unsigned i = 0; i < num_vars; ++i) { + if (m.is_bool(m_ctx.get_var(i)) && + solve_polarized(m_ctx.contains(i), fml, def)) { + m_ctx.elim_var(i, fml, def); + return true; + } + } + return false; + } + + bool solve_polarized( contains_app& contains_x, expr_ref& fml, expr_ref& def) { + app* x = contains_x.x(); + bool p = m_ctx.pos_atoms().contains(x); + bool n = m_ctx.neg_atoms().contains(x); + TRACE("quant_elim", tout << mk_pp(x, m) << " " << mk_pp(fml, m) << "\n";); + if (p && n) { + return false; + } + else if (p && !n) { + atom_set::iterator it = m_ctx.pos_atoms().begin(), end = m_ctx.pos_atoms().end(); + for (; it != end; ++it) { + if (x != *it && contains_x(*it)) return false; + } + it = m_ctx.neg_atoms().begin(), end = m_ctx.neg_atoms().end(); + for (; it != end; ++it) { + if (contains_x(*it)) return false; + } + // only occurrences of 'x' must be in positive atoms + def = m.mk_true(); + m_replace->apply_substitution(x, def, 0, fml); + return true; + } + else if (!p && n) { + atom_set::iterator it = m_ctx.pos_atoms().begin(), end = m_ctx.pos_atoms().end(); + for (; it != end; ++it) { + if (contains_x(*it)) return false; + } + it = m_ctx.neg_atoms().begin(), end = m_ctx.neg_atoms().end(); + for (; it != end; ++it) { + if (x != *it && contains_x(*it)) return false; + } + def = m.mk_false(); + m_replace->apply_substitution(x, def, 0, fml); + return true; + } + else if (contains_x(fml)) { + return false; + } + else { + def = m.mk_false(); + return true; + } + } + }; + + qe_solver_plugin* mk_bool_plugin(i_solver_context& ctx) { + return alloc(bool_plugin, ctx, ctx.get_manager()); + } +} diff --git a/lib/qe_bv_plugin.cpp b/lib/qe_bv_plugin.cpp new file mode 100644 index 000000000..8712061d6 --- /dev/null +++ b/lib/qe_bv_plugin.cpp @@ -0,0 +1,97 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + bv_plugin.cpp + +Abstract: + + Eliminate Bit-vector variable from formula + +Author: + + Nikolaj Bjorner (nbjorner) 2010-02-19 + +Revision History: + +Notes: + + +--*/ + +#include "qe.h" +#include "expr_replacer.h" +#include "bv_decl_plugin.h" +#include "model_evaluator.h" + +namespace qe { + + class bv_plugin : public qe_solver_plugin { + scoped_ptr m_replace; + bv_util m_bv; + public: + bv_plugin(i_solver_context& ctx, ast_manager& m): + qe_solver_plugin(m, m.get_family_id("bv"), ctx), + m_replace(mk_default_expr_replacer(m)), + m_bv(m) + {} + + virtual void assign(contains_app& x, expr* fml, rational const& vl) { + } + + virtual bool get_num_branches(contains_app& x, expr* fml, rational& nb) { + unsigned sz = m_bv.get_bv_size(x.x()); + nb = power(rational(2), sz); + return true; + } + + virtual void subst(contains_app& x, rational const& vl, expr_ref& fml, expr_ref* def) { + app_ref c(m_bv.mk_numeral(vl, m_bv.get_bv_size(x.x())), m); + m_replace->apply_substitution(x.x(), c, 0, fml); + if (def) { + *def = m_bv.mk_numeral(vl, m_bv.get_bv_size(x.x())); + } + } + + virtual bool project(contains_app& x, model_ref& model, expr_ref& fml) { + model_evaluator model_eval(*model); + expr_ref val_x(m); + rational val(0); + unsigned bv_size; + model_eval(x.x(), val_x); + m_bv.is_numeral(val_x, val, bv_size); + subst(x, val, fml, 0); + return true; + } + + virtual unsigned get_weight(contains_app& contains_x, expr* fml) { + return 2; + } + + bool solve(conj_enum& conjs, expr* fml) { return false; } + + virtual bool is_uninterpreted(app* f) { + switch(f->get_decl_kind()) { + case OP_BSDIV0: + case OP_BUDIV0: + case OP_BSREM0: + case OP_BUREM0: + case OP_BSMOD0: + case OP_BSDIV_I: + case OP_BUDIV_I: + case OP_BSREM_I: + case OP_BUREM_I: + case OP_BSMOD_I: + return true; + default: + return false; + } + return false; + } + }; + + qe_solver_plugin* mk_bv_plugin(i_solver_context& ctx) { + return alloc(bv_plugin, ctx, ctx.get_manager()); + } +} diff --git a/lib/qe_cmd.cpp b/lib/qe_cmd.cpp new file mode 100644 index 000000000..218224c51 --- /dev/null +++ b/lib/qe_cmd.cpp @@ -0,0 +1,64 @@ +#include "qe_cmd.h" +#include "qe.h" +#include "cmd_context.h" +#include "parametric_cmd.h" + +class qe_cmd : public parametric_cmd { + expr * m_target; +public: + qe_cmd(char const* name = "elim-quantifiers"):parametric_cmd(name) {} + + virtual char const * get_usage() const { return " ( )*"; } + + virtual char const * get_main_descr() const { + return "apply quantifier elimination to the supplied expression"; + } + + virtual void init_pdescrs(cmd_context & ctx, param_descrs & p) { + insert_timeout(p); + p.insert(":print", CPK_BOOL, "(default: true) print the simplified term."); + p.insert(":print-statistics", CPK_BOOL, "(default: false) print statistics."); + } + + virtual ~qe_cmd() { + } + + virtual void prepare(cmd_context & ctx) { + parametric_cmd::prepare(ctx); + m_target = 0; + } + + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { + if (m_target == 0) return CPK_EXPR; + return parametric_cmd::next_arg_kind(ctx); + } + + virtual void set_next_arg(cmd_context & ctx, expr * arg) { + m_target = arg; + } + + virtual void execute(cmd_context & ctx) { + front_end_params par; + proof_ref pr(ctx.m()); + qe::expr_quant_elim_star1 qe(ctx.m(), par); + expr_ref result(ctx.m()); + + qe(m_target, result, pr); + + if (m_params.get_bool(":print", true)) { + ctx.display(ctx.regular_stream(), result); + ctx.regular_stream() << std::endl; + } + if (m_params.get_bool(":print-statistics", false)) { + statistics st; + qe.collect_statistics(st); + st.display(ctx.regular_stream()); + } + } + +}; + +void install_qe_cmd(cmd_context & ctx, char const * cmd_name) { + ctx.insert(alloc(qe_cmd, cmd_name)); +} + diff --git a/lib/qe_cmd.h b/lib/qe_cmd.h new file mode 100644 index 000000000..3012e8eba --- /dev/null +++ b/lib/qe_cmd.h @@ -0,0 +1,25 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + qe_cmd.h + +Abstract: + SMT2 front-end 'qe' command. + +Author: + + Nikolaj Bjorner (nbjorner) 2011-01-11 + +Notes: + +--*/ +#ifndef _QE_CMD_H_ +#define _QE_CMD_H_ + +class cmd_context; + +void install_qe_cmd(cmd_context & ctx, char const * cmd_name = "elim-quantifiers"); + +#endif diff --git a/lib/qe_datatype_plugin.cpp b/lib/qe_datatype_plugin.cpp new file mode 100644 index 000000000..9584241db --- /dev/null +++ b/lib/qe_datatype_plugin.cpp @@ -0,0 +1,843 @@ +// --------------------- +// datatypes +// Quantifier elimination routine for recursive data-types. +// + + +// +// reduce implementation is modeled after Hodges: +// subst implementation is using QE "for dummies". + +// for dummies: +// ----------- +// +// Step 1. ensure x is recognized. +// +// exists x . phi[x] -> \/_i exists x. R_C(x) & phi[x] +// +// Step 2. non-recursive data-types +// +// exists x . R_C(x) & phi[x] -> exists y . phi[C(y)] +// +// Step 2. recursive data-types, eliminate selectors. +// +// exists x . R_C(x) & phi[x] -> exists y . phi[C(y)], x occurs with sel^C_i(x) +// +// Step 3. (recursive data-types) +// +// Solve equations +// . C[t] = C[s] -> t = s +// . C[x,t] = y -> x = s1(y) /\ t = s2(y) /\ R_C(y) +// . C[x,t] != y -> can remain +// +// The remaining formula is in NNF where occurrences of 'x' are of the form +// x = t_i or t[x] != s_j +// +// The last normalization step is: +// +// exists x . R_C(x) & phi[x = t_i, t[x] != s_j] +// +// -> \/_i R_C(t_i) & phi[t_i/x] \/ phi[false, true] +// +// Justification: +// - We will asume that each of t_i, s_j are constructor terms. +// - We can assume that x \notin t_i, x \notin s_j, or otherwise use simplification. +// - We can assume that x occurs only in equalities or disequalities, or the recognizer, since +// otherwise, we could simplify equalities, or QE does not apply. +// - either x = t_i for some positive t_i, or we create +// diag = C(t_1, ..., C(t_n, .. C(s_1, .. , C(s_m)))) +// and x is different from each t_i, s_j. +// + +// +// reduce: +// -------- +// reduce set of literals containing x. The elimination procedure follows an adaptation of +// the proof (with corrections) in Hodges (shorter model theory). +// +// . x = t - (x \notin t) x is eliminated immediately. +// +// . x != t1, ..., x != t_n - (x \notin t_i) are collected into distrinct_terms. +// +// . recognizer(x) - is stored as pos_recognizer. +// +// . !recognizer(x) - is stored into neg_recognizers. +// +// . L[constructor(..,x,..)] - +// We could assume pre-processing introduces auxiliary +// variable y, Is_constructor(y), accessor_j(y) = arg_j. +// But we apply the following hack: re-introduce x' into vars, +// but also the variable y and the reduced formula. +// +// . L[accessor_i(x)] - if pos_recognizer(x) matches accessor, +// or if complement of neg_recognizers match accessor, then +// introduce x_1, .., x_n corresponding to accessor_i(x). +// +// The only way x is not in the scope of a data-type method is if it is in +// an equality or dis-equality of the form: +// +// . x (!)= acc_1(acc_2(..(acc_i(x)..) - would be false (true) if recognizer(..) +// is declared for each sub-term. +// +// +// - otherwise, each x should be in the scope of an accessor. +// +// Normalized literal elimination: +// +// . exists x . Lits[accessor_i(x)] & Is_constructor(x) +// -> +// exists x_1, .., x_n . Lits[x_1, .., x_n] for each accessor_i(x). +// + +// +// maintain set of equations and disequations with x. +// + +#include "qe.h" +#include "datatype_decl_plugin.h" +#include "expr_replacer.h" +#include "obj_pair_hashtable.h" +#include "for_each_expr.h" +#include "ast_pp.h" +#include "ast_ll_pp.h" + +namespace qe { + + class datatype_atoms { + ast_manager& m; + app_ref_vector m_recognizers; + expr_ref_vector m_eqs; + app_ref_vector m_eq_atoms; + app_ref_vector m_neq_atoms; + app_ref_vector m_unsat_atoms; + expr_ref_vector m_eq_conds; + ast_mark m_mark; + datatype_util m_util; + public: + datatype_atoms(ast_manager& m) : + m(m), + m_recognizers(m), + m_eqs(m), + m_eq_atoms(m), m_neq_atoms(m), m_unsat_atoms(m), m_eq_conds(m), + m_util(m) {} + + bool add_atom(contains_app& contains_x, bool is_pos, app* a) { + app* x = contains_x.x(); + SASSERT(contains_x(a)); + if (m_mark.is_marked(a)) { + return true; + } + m_mark.mark(a, true); + func_decl* f = a->get_decl(); + if (m_util.is_recognizer(f) && a->get_arg(0) == x) { + m_recognizers.push_back(a); + TRACE("quant_elim", tout << "add recognizer:" << mk_pp(a, m) << "\n";); + return true; + } + if (!m.is_eq(a)) { + return false; + } + if (add_eq(contains_x, is_pos, a->get_arg(0), a->get_arg(1))) { + add_atom(a, is_pos); + return true; + } + if (add_eq(contains_x, is_pos, a->get_arg(1), a->get_arg(0))) { + add_atom(a, is_pos); + return true; + } + if (add_unsat_eq(contains_x, a, a->get_arg(0), a->get_arg(1))) { + return true; + } + return false; + } + + unsigned num_eqs() { return m_eqs.size(); } + expr* eq(unsigned i) { return m_eqs[i].get(); } + expr* eq_cond(unsigned i) { return m_eq_conds[i].get(); } + app* eq_atom(unsigned i) { return m_eq_atoms[i].get(); } + + unsigned num_neqs() { return m_neq_atoms.size(); } + app* neq_atom(unsigned i) { return m_neq_atoms[i].get(); } + + unsigned num_recognizers() { return m_recognizers.size(); } + app* recognizer(unsigned i) { return m_recognizers[i].get(); } + + unsigned num_unsat() { return m_unsat_atoms.size(); } + app* unsat_atom(unsigned i) { return m_unsat_atoms[i].get(); } + + private: + + // + // perform occurs check on equality. + // + bool add_unsat_eq(contains_app& contains_x, app* atom, expr* a, expr* b) { + app* x = contains_x.x(); + if (x != a) { + std::swap(a, b); + } + if (x != a) { + return false; + } + if (!contains_x(b)) { + return false; + } + ptr_vector todo; + ast_mark mark; + todo.push_back(b); + while (!todo.empty()) { + b = todo.back(); + todo.pop_back(); + if (mark.is_marked(b)) { + continue; + } + mark.mark(b, true); + if (!is_app(b)) { + continue; + } + if (b == x) { + m_unsat_atoms.push_back(atom); + return true; + } + app* b_app = to_app(b); + if (!m_util.is_constructor(b_app)) { + continue; + } + for (unsigned i = 0; i < b_app->get_num_args(); ++i) { + todo.push_back(b_app->get_arg(i)); + } + } + return false; + } + + void add_atom(app* a, bool is_pos) { + TRACE("quant_elim", tout << "add atom:" << mk_pp(a, m) << " " << (is_pos?"pos":"neg") << "\n";); + if (is_pos) { + m_eq_atoms.push_back(a); + } + else { + m_neq_atoms.push_back(a); + } + } + + bool add_eq(contains_app& contains_x, bool is_pos, expr* a, expr* b) { + if (contains_x(b)) { + return false; + } + if (is_pos) { + return solve_eq(contains_x, a, b, m.mk_true()); + } + else { + return solve_diseq(contains_x, a, b); + } + return false; + } + + bool solve_eq(contains_app& contains_x, expr* _a, expr* b, expr* cond0) { + SASSERT(contains_x(_a)); + SASSERT(!contains_x(b)); + app* x = contains_x.x(); + if (!is_app(_a)) { + return false; + } + app* a = to_app(_a); + if (x == a) { + m_eqs.push_back(b); + m_eq_conds.push_back(cond0); + return true; + } + if (!m_util.is_constructor(a)) { + return false; + } + func_decl* c = a->get_decl(); + func_decl* r = m_util.get_constructor_recognizer(c); + ptr_vector const & acc = *m_util.get_constructor_accessors(c); + SASSERT(acc.size() == a->get_num_args()); + // + // It suffices to solve just the first available equality. + // The others are determined by the first. + // + expr_ref cond(m.mk_and(m.mk_app(r, b), cond0), m); + for (unsigned i = 0; i < a->get_num_args(); ++i) { + expr* l = a->get_arg(i); + if (contains_x(l)) { + expr_ref r(m.mk_app(acc[i], b), m); + if (solve_eq(contains_x, l, r, cond)) { + return true; + } + } + } + return false; + } + + // + // check that some occurrence of 'x' is in a constructor sequence. + // + bool solve_diseq(contains_app& contains_x, expr* a0, expr* b) { + SASSERT(!contains_x(b)); + SASSERT(contains_x(a0)); + app* x = contains_x.x(); + ptr_vector todo; + ast_mark mark; + todo.push_back(a0); + while (!todo.empty()) { + a0 = todo.back(); + todo.pop_back(); + if (mark.is_marked(a0)) { + continue; + } + mark.mark(a0, true); + if (!is_app(a0)) { + continue; + } + app* a = to_app(a0); + if (a == x) { + return true; + } + if (!m_util.is_constructor(a)) { + continue; + } + for (unsigned i = 0; i < a->get_num_args(); ++i) { + todo.push_back(a->get_arg(i)); + } + } + return false; + } + }; + + // + // eliminate foreign variable under datatype functions (constructors). + // (= C(x,y) t) -> (R_C(t) && x = s1(t) && x = s2(t)) + // + + class lift_foreign_vars : public map_proc { + ast_manager& m; + bool m_change; + datatype_util& m_util; + i_solver_context& m_ctx; + public: + lift_foreign_vars(ast_manager& m, datatype_util& util, i_solver_context& ctx): + map_proc(m), m(m), m_change(false), m_util(util), m_ctx(ctx) {} + + bool lift(expr_ref& fml) { + m_change = false; + for_each_expr(*this, fml.get()); + if (m_change) { + fml = get_expr(fml.get()); + TRACE("quant_elim", tout << "lift:\n" << mk_pp(fml.get(), m) << "\n";); + } + return m_change; + } + + void operator()(var* v) { + visit(v); + } + + void operator()(quantifier* q) { + visit(q); + } + + void operator()(app* a) { + expr* l, *r; + if (m.is_eq(a, l, r)) { + if (reduce_eq(a, l, r)) { + m_change = true; + return; + } + if (reduce_eq(a, r, l)) { + m_change = true; + return; + } + } + reconstruct(a); + } + + private: + + bool reduce_eq(app* a, expr* _l, expr* r) { + if (!is_app(_l)) { + return false; + } + app* l = to_app(_l); + if (!m_util.is_constructor(l)) { + return false; + } + + if (!contains_foreign(l)) { + return false; + } + func_decl* c = l->get_decl(); + ptr_vector const& acc = *m_util.get_constructor_accessors(c); + func_decl* rec = m_util.get_constructor_recognizer(c); + expr_ref_vector conj(m); + conj.push_back(m.mk_app(rec, r)); + for (unsigned i = 0; i < acc.size(); ++i) { + expr* r_i = m.mk_app(acc[i], r); + expr* l_i = l->get_arg(i); + conj.push_back(m.mk_eq(l_i, r_i)); + } + expr* e = m.mk_and(conj.size(), conj.c_ptr()); + m_map.insert(a, e, 0); + TRACE("quant_elim", tout << "replace: " << mk_pp(a, m) << " ==> \n" << mk_pp(e, m) << "\n";); + return true; + } + + bool contains_foreign(app* a) { + unsigned num_vars = m_ctx.get_num_vars(); + for (unsigned i = 0; i < num_vars; ++i) { + contains_app& v = m_ctx.contains(i); + sort* s = v.x()->get_decl()->get_range(); + + // + // data-type contains arithmetical term or bit-vector. + // + if (!m_util.is_datatype(s) && + !m.is_bool(s) && + v(a)) { + return true; + } + } + return false; + } + + }; + + + class datatype_plugin : public qe_solver_plugin { + typedef std::pair > subst_clos; + typedef obj_pair_map eqs_cache; + typedef obj_pair_map subst_map; + + datatype_util m_datatype_util; + scoped_ptr m_replace; + eqs_cache m_eqs_cache; + subst_map m_subst_cache; + ast_ref_vector m_trail; + + public: + datatype_plugin(i_solver_context& ctx, ast_manager& m) : + qe_solver_plugin(m, m.get_family_id("datatype"), ctx), + m_datatype_util(m), + m_replace(mk_default_expr_replacer(m)), + m_trail(m) + { + } + + virtual ~datatype_plugin() { + { + eqs_cache::iterator it = m_eqs_cache.begin(), end = m_eqs_cache.end(); + for (; it != end; ++it) { + dealloc(it->get_value()); + } + } + { + subst_map::iterator it = m_subst_cache.begin(), end = m_subst_cache.end(); + for (; it != end; ++it) { + dealloc(it->get_value()); + } + } + + } + + virtual bool get_num_branches( contains_app& x, expr* fml, rational& num_branches) { + sort* s = x.x()->get_decl()->get_range(); + SASSERT(m_datatype_util.is_datatype(s)); + if (m_datatype_util.is_recursive(s)) { + return get_num_branches_rec(x, fml, num_branches); + } + else { + return get_num_branches_nonrec(x, fml, num_branches); + } + } + + + virtual void assign(contains_app& x, expr* fml, rational const& vl) { + sort* s = x.x()->get_decl()->get_range(); + SASSERT(m_datatype_util.is_datatype(s)); + TRACE("quant_elim", tout << mk_pp(x.x(), m) << " " << vl << "\n";); + if (m_datatype_util.is_recursive(s)) { + assign_rec(x, fml, vl); + } + else { + assign_nonrec(x, fml, vl); + } + } + + virtual void subst(contains_app& x, rational const& vl, expr_ref& fml, expr_ref* def) { + sort* s = x.x()->get_decl()->get_range(); + SASSERT(m_datatype_util.is_datatype(s)); + TRACE("quant_elim", tout << mk_pp(x.x(), m) << " " << vl << "\n";); + if (m_datatype_util.is_recursive(s)) { + subst_rec(x, vl, fml); + } + else { + subst_nonrec(x, vl, fml); + } + if (def) { + *def = 0; // TBD + } + } + + virtual unsigned get_weight( contains_app& x, expr* fml) { + return UINT_MAX; + } + + virtual bool solve( conj_enum& conj, expr* fml) { + return false; + } + + virtual bool simplify( expr_ref& fml) { + lift_foreign_vars lift(m, m_datatype_util, m_ctx); + return lift.lift(fml); + } + + virtual bool mk_atom(expr* e, bool p, expr_ref& result) { + return false; + } + + + virtual rational get_cost(contains_app&, expr* fml) { + return rational(0); + } + + private: + + // + // replace x by C(y1,..,yn) where y1,..,yn are fresh variables. + // + void subst_constructor(contains_app& x, func_decl* c, expr_ref& fml) { + subst_clos* sub = 0; + + if (m_subst_cache.find(x.x(), c, sub)) { + m_replace->apply_substitution(x.x(), sub->first, 0, fml); + for (unsigned i = 0; i < sub->second.size(); ++i) { + m_ctx.add_var(sub->second[i]); + } + return; + } + sub = alloc(subst_clos); + unsigned arity = c->get_arity(); + expr_ref_vector vars(m); + for (unsigned i = 0; i < arity; ++i) { + sort* sort_x = c->get_domain()[i]; + app_ref fresh_x(m.mk_fresh_const("x", sort_x), m); + m_ctx.add_var(fresh_x.get()); + vars.push_back(fresh_x.get()); + sub->second.push_back(fresh_x.get()); + } + app_ref t(m.mk_app(c, vars.size(), vars.c_ptr()), m); + m_trail.push_back(x.x()); + m_trail.push_back(c); + m_trail.push_back(t); + + m_replace->apply_substitution(x.x(), t, 0, fml); + sub->first = t; + m_subst_cache.insert(x.x(), c, sub); + } + + void get_recognizers(expr* fml, ptr_vector& recognizers) { + conj_enum conjs(m, fml); + conj_enum::iterator it = conjs.begin(), end = conjs.end(); + for (; it != end; ++it) { + expr* e = *it; + if (is_app(e)) { + app* a = to_app(e); + func_decl* f = a->get_decl(); + if (m_datatype_util.is_recognizer(f)) { + recognizers.push_back(a); + } + } + } + } + + bool has_recognizer(app* x, expr* fml, func_decl*& r, func_decl*& c) { + ptr_vector recognizers; + get_recognizers(fml, recognizers); + for (unsigned i = 0; i < recognizers.size(); ++i) { + app* a = recognizers[i]; + if (a->get_arg(0) == x) { + r = a->get_decl(); + c = m_datatype_util.get_recognizer_constructor(a->get_decl()); + return true; + } + } + return false; + } + + bool get_num_branches_rec( contains_app& x, expr* fml, rational& num_branches) { + sort* s = x.x()->get_decl()->get_range(); + SASSERT(m_datatype_util.is_datatype(s)); + SASSERT(m_datatype_util.is_recursive(s)); + + unsigned sz = m_datatype_util.get_datatype_num_constructors(s); + num_branches = rational(sz); + func_decl* c = 0, *r = 0; + + // + // If 'x' does not yet have a recognizer, then branch according to recognizers. + // + if (!has_recognizer(x.x(), fml, r, c)) { + return true; + } + // + // eliminate 'x' by applying constructor to fresh variables. + // + if (has_selector(x, fml, c)) { + num_branches = rational(1); + return true; + } + + // + // 'x' has a recognizer. Count number of equalities and disequalities. + // + if (update_eqs(x, fml)) { + datatype_atoms& eqs = get_eqs(x.x(), fml); + num_branches = rational(eqs.num_eqs() + 1); + return true; + } + TRACE("quant_elim", tout << "could not get number of branches " << mk_pp(x.x(), m) << "\n";); + return false; + } + + + void assign_rec(contains_app& contains_x, expr* fml, rational const& vl) { + app* x = contains_x.x(); + sort* s = x->get_decl()->get_range(); + func_decl* c = 0, *r = 0; + + // + // If 'x' does not yet have a recognizer, then branch according to recognizers. + // + if (!has_recognizer(x, fml, r, c)) { + c = (*m_datatype_util.get_datatype_constructors(s))[vl.get_unsigned()]; + r = m_datatype_util.get_constructor_recognizer(c); + app* is_c = m.mk_app(r, x); + // assert v => r(x) + m_ctx.add_constraint(true, is_c); + return; + } + + // + // eliminate 'x' by applying constructor to fresh variables. + // + if (has_selector(contains_x, fml, c)) { + return; + } + + // + // 'x' has a recognizer. The branch ID id provided by the index of the equality. + // + datatype_atoms& eqs = get_eqs(x, fml); + SASSERT(vl.is_unsigned()); + unsigned idx = vl.get_unsigned(); + SASSERT(idx <= eqs.num_eqs()); + + if (idx < eqs.num_eqs()) { + expr* t = eqs.eq(idx); + m_ctx.add_constraint(true, m.mk_eq(x, t)); + } + else { + for (unsigned i = 0; i < eqs.num_eqs(); ++i) { + expr* t = eqs.eq(i); + m_ctx.add_constraint(true, m.mk_not(m.mk_eq(x, t))); + } + } + } + + void subst_rec(contains_app& contains_x, rational const& vl, expr_ref& fml) { + app* x = contains_x.x(); + sort* s = x->get_decl()->get_range(); + SASSERT(m_datatype_util.is_datatype(s)); + func_decl* c = 0, *r = 0; + + // + // Add recognizer to formula. + // Introduce auxiliary variable to eliminate. + // + if (!has_recognizer(x, fml, r, c)) { + c = (*m_datatype_util.get_datatype_constructors(s))[vl.get_unsigned()]; + r = m_datatype_util.get_constructor_recognizer(c); + app* is_c = m.mk_app(r, x); + fml = m.mk_and(is_c, fml); + app_ref fresh_x(m.mk_fresh_const("x", s), m); + m_ctx.add_var(fresh_x); + m_replace->apply_substitution(x, fresh_x, 0, fml); + TRACE("quant_elim", tout << "Add recognizer " << mk_pp(is_c, m) << "\n";); + return; + } + + + if (has_selector(contains_x, fml, c)) { + TRACE("quant_elim", tout << "Eliminate selector " << mk_ll_pp(c, m) << "\n";); + subst_constructor(contains_x, c, fml); + return; + } + + // + // 'x' has a recognizer. The branch ID id provided by the index of the equality. + // + datatype_atoms& eqs = get_eqs(x, fml); + SASSERT(vl.is_unsigned()); + unsigned idx = vl.get_unsigned(); + SASSERT(idx <= eqs.num_eqs()); + + for (unsigned i = 0; i < eqs.num_recognizers(); ++i) { + app* rec = eqs.recognizer(i); + if (rec->get_decl() == r) { + m_replace->apply_substitution(rec, m.mk_true(), fml); + } + else { + m_replace->apply_substitution(rec, m.mk_false(), fml); + } + } + + for (unsigned i = 0; i < eqs.num_unsat(); ++i) { + m_replace->apply_substitution(eqs.unsat_atom(i), m.mk_false(), fml); + } + + if (idx < eqs.num_eqs()) { + expr* t = eqs.eq(idx); + expr* c = eqs.eq_cond(idx); + m_replace->apply_substitution(x, t, fml); + if (!m.is_true(c)) { + fml = m.mk_and(c, fml); + } + } + else { + for (unsigned i = 0; i < eqs.num_eqs(); ++i) { + m_replace->apply_substitution(eqs.eq_atom(i), m.mk_false(), fml); + } + + for (unsigned i = 0; i < eqs.num_neqs(); ++i) { + m_replace->apply_substitution(eqs.neq_atom(i), m.mk_true(), fml); + } + } + TRACE("quant_elim", tout << "reduced " << mk_pp(fml.get(), m) << "\n";); + } + + bool get_num_branches_nonrec( contains_app& x, expr* fml, rational& num_branches) { + sort* s = x.x()->get_decl()->get_range(); + unsigned sz = m_datatype_util.get_datatype_num_constructors(s); + num_branches = rational(sz); + func_decl* c = 0, *r = 0; + + if (sz != 1 && has_recognizer(x.x(), fml, r, c)) { + TRACE("quant_elim", tout << mk_pp(x.x(), m) << " has a recognizer\n";); + num_branches = rational(1); + } + TRACE("quant_elim", tout << mk_pp(x.x(), m) << " branches: " << sz << "\n";); + return true; + } + + void assign_nonrec(contains_app& contains_x, expr* fml, rational const& vl) { + app* x = contains_x.x(); + sort* s = x->get_decl()->get_range(); + SASSERT(m_datatype_util.is_datatype(s)); + unsigned sz = m_datatype_util.get_datatype_num_constructors(s); + SASSERT(vl.is_unsigned()); + SASSERT(vl.get_unsigned() < sz); + if (sz == 1) { + return; + } + func_decl* c = 0, *r = 0; + if (has_recognizer(x, fml, r, c)) { + TRACE("quant_elim", tout << mk_pp(x, m) << " has a recognizer\n";); + return; + } + + c = (*m_datatype_util.get_datatype_constructors(s))[vl.get_unsigned()]; + r = m_datatype_util.get_constructor_recognizer(c); + app* is_c = m.mk_app(r, x); + + // assert v => r(x) + + m_ctx.add_constraint(true, is_c); + } + + virtual void subst_nonrec(contains_app& x, rational const& vl, expr_ref& fml) { + sort* s = x.x()->get_decl()->get_range(); + SASSERT(m_datatype_util.is_datatype(s)); + SASSERT(!m_datatype_util.is_recursive(s)); + func_decl* c = 0, *r = 0; + if (has_recognizer(x.x(), fml, r, c)) { + TRACE("quant_elim", tout << mk_pp(x.x(), m) << " has a recognizer\n";); + } + else { + unsigned sz = m_datatype_util.get_datatype_num_constructors(s); + SASSERT(vl.is_unsigned()); + SASSERT(vl.get_unsigned() < sz); + c = (*m_datatype_util.get_datatype_constructors(s))[vl.get_unsigned()]; + } + subst_constructor(x, c, fml); + } + + + class has_select : public i_expr_pred { + app* m_x; + func_decl* m_c; + datatype_util& m_util; + public: + has_select(app* x, func_decl* c, datatype_util& u): m_x(x), m_c(c), m_util(u) {} + + virtual bool operator()(expr* e) { + if (!is_app(e)) return false; + app* a = to_app(e); + if (!m_util.is_accessor(a)) return false; + if (a->get_arg(0) != m_x) return false; + func_decl* f = a->get_decl(); + return m_c == m_util.get_accessor_constructor(f); + } + }; + + bool has_selector(contains_app& x, expr* fml, func_decl* c) { + has_select hs(x.x(), c, m_datatype_util); + check_pred ch(hs, m); + return ch(fml); + } + + datatype_atoms& get_eqs(app* x, expr* fml) { + datatype_atoms* eqs = 0; + VERIFY (m_eqs_cache.find(x, fml, eqs)); + return *eqs; + } + + bool update_eqs(contains_app& contains_x, expr* fml) { + datatype_atoms* eqs = 0; + if (m_eqs_cache.find(contains_x.x(), fml, eqs)) { + return true; + } + eqs = alloc(datatype_atoms, m); + + if (!update_eqs(*eqs, contains_x, fml, m_ctx.pos_atoms(), true)) { + dealloc(eqs); + return false; + } + if (!update_eqs(*eqs, contains_x, fml, m_ctx.neg_atoms(), false)) { + dealloc(eqs); + return false; + } + + m_trail.push_back(contains_x.x()); + m_trail.push_back(fml); + m_eqs_cache.insert(contains_x.x(), fml, eqs); + return true; + } + + bool update_eqs(datatype_atoms& eqs, contains_app& contains_x, expr* fml, atom_set const& tbl, bool is_pos) { + atom_set::iterator it = tbl.begin(), end = tbl.end(); + for (; it != end; ++it) { + app* e = *it; + if (!contains_x(e)) { + continue; + } + if (!eqs.add_atom(contains_x, is_pos, e)) { + return false; + } + } + return true; + } + }; + + qe_solver_plugin* mk_datatype_plugin(i_solver_context& ctx) { + return alloc(datatype_plugin, ctx, ctx.get_manager()); + } +} diff --git a/lib/qe_dl_plugin.cpp b/lib/qe_dl_plugin.cpp new file mode 100644 index 000000000..23f43759d --- /dev/null +++ b/lib/qe_dl_plugin.cpp @@ -0,0 +1,233 @@ + +#include "qe.h" +#include "expr_replacer.h" +#include "dl_decl_plugin.h" +#include "obj_pair_hashtable.h" +#include "ast_pp.h" + + +namespace qe { + + // --------------------- + // dl_plugin + + class eq_atoms { + ast_manager& m; + expr_ref_vector m_eqs; + expr_ref_vector m_neqs; + app_ref_vector m_eq_atoms; + app_ref_vector m_neq_atoms; + public: + eq_atoms(ast_manager& m): + m(m), + m_eqs(m), + m_neqs(m), + m_eq_atoms(m), + m_neq_atoms(m) {} + + unsigned num_eqs() const { return m_eqs.size(); } + expr* eq(unsigned i) const { return m_eqs[i]; } + app* eq_atom(unsigned i) const { return m_eq_atoms[i]; } + unsigned num_neqs() const { return m_neqs.size(); } + app* neq_atom(unsigned i) const { return m_neq_atoms[i]; } + expr* neq(unsigned i) const { return m_neqs[i]; } + void add_eq(app* atom, expr * e) { m_eq_atoms.push_back(atom); m_eqs.push_back(e); } + void add_neq(app* atom, expr* e) { m_neq_atoms.push_back(atom); m_neqs.push_back(e); } + }; + + class dl_plugin : public qe_solver_plugin { + typedef obj_pair_map eqs_cache; + scoped_ptr m_replace; + datalog::dl_decl_util m_util; + expr_ref_vector m_trail; + eqs_cache m_eqs_cache; + + + public: + dl_plugin(i_solver_context& ctx, ast_manager& m) : + qe_solver_plugin(m, m.get_family_id("datalog_relation"), ctx), + m_replace(mk_default_expr_replacer(m)), + m_util(m), + m_trail(m) + { + } + + virtual ~dl_plugin() { + eqs_cache::iterator it = m_eqs_cache.begin(), end = m_eqs_cache.end(); + for (; it != end; ++it) { + dealloc(it->get_value()); + } + } + + + + bool get_num_branches(contains_app & x,expr * fml,rational & num_branches) { + if (!update_eqs(x, fml)) { + return false; + } + eq_atoms& eqs = get_eqs(x.x(), fml); + uint64 domain_size; + if (is_small_domain(x, eqs, domain_size)) { + num_branches = rational(domain_size, rational::ui64()); + } + else { + num_branches = rational(eqs.num_eqs() + 1); + } + return true; + } + + void assign(contains_app & x,expr * fml,const rational & v) { + SASSERT(v.is_unsigned()); + eq_atoms& eqs = get_eqs(x.x(), fml); + unsigned uv = v.get_unsigned(); + uint64 domain_size; + if (is_small_domain(x, eqs, domain_size)) { + SASSERT(v < rational(domain_size, rational::ui64())); + assign_small_domain(x, eqs, uv); + } + else { + assign_large_domain(x, eqs, uv); + } + } + + void subst(contains_app & x,const rational & v,expr_ref & fml, expr_ref* def) { + SASSERT(v.is_unsigned()); + eq_atoms& eqs = get_eqs(x.x(), fml); + unsigned uv = v.get_unsigned(); + uint64 domain_size; + if (is_small_domain(x, eqs, domain_size)) { + SASSERT(uv < domain_size); + subst_small_domain(x, eqs, uv, fml); + } + else { + subst_large_domain(x, eqs, uv, fml); + } + if (def) { + *def = 0; // TBD + } + } + + virtual bool solve(conj_enum& conjs, expr* fml) { return false; } + + private: + + bool is_small_domain(contains_app& x, eq_atoms& eqs, uint64& domain_size) { + VERIFY(m_util.try_get_size(m.get_sort(x.x()), domain_size)); + return domain_size < eqs.num_eqs() + eqs.num_neqs(); + } + + void assign_small_domain(contains_app & x,eq_atoms& eqs, unsigned value) { + expr_ref vl(m_util.mk_numeral(value, m.get_sort(x.x())), m); + expr_ref eq(m.mk_eq(x.x(), vl), m); + m_ctx.add_constraint(true, eq); + } + + void assign_large_domain(contains_app & x,eq_atoms& eqs, unsigned v) { + if (v < eqs.num_eqs()) { + m_ctx.add_constraint(true, eqs.eq_atom(v)); + } + else { + SASSERT(v == eqs.num_eqs()); + for (unsigned i = 0; i < eqs.num_eqs(); ++i) { + expr_ref neq(m.mk_not(eqs.eq_atom(i)), m); + m_ctx.add_constraint(true, neq); + } + + for (unsigned i = 0; i < eqs.num_neqs(); ++i) { + expr_ref neq(m.mk_not(eqs.neq_atom(i)), m); + m_ctx.add_constraint(true, neq); + } + } + } + + void subst_small_domain(contains_app & x,eq_atoms& eqs, unsigned v,expr_ref & fml) { + expr_ref vl(m_util.mk_numeral(v, m.get_sort(x.x())), m); + m_replace->apply_substitution(x.x(), vl, fml); + } + + // assumes that all disequalities can be satisfied. + void subst_large_domain(contains_app & x,eq_atoms& eqs, unsigned w,expr_ref & fml) { + SASSERT(w <= eqs.num_eqs()); + if (w < eqs.num_eqs()) { + expr* e = eqs.eq(w); + m_replace->apply_substitution(x.x(), e, fml); + } + else { + for (unsigned i = 0; i < eqs.num_eqs(); ++i) { + m_replace->apply_substitution(eqs.eq_atom(i), m.mk_false(), fml); + } + + for (unsigned i = 0; i < eqs.num_neqs(); ++i) { + m_replace->apply_substitution(eqs.neq_atom(i), m.mk_true(), fml); + } + } + } + + + eq_atoms& get_eqs(app* x, expr* fml) { + eq_atoms* eqs = 0; + VERIFY(m_eqs_cache.find(x, fml, eqs)); + return *eqs; + } + + bool update_eqs(contains_app& contains_x, expr* fml) { + eq_atoms* eqs = 0; + if (m_eqs_cache.find(contains_x.x(), fml, eqs)) { + return true; + } + eqs = alloc(eq_atoms, m); + + if (!update_eqs(*eqs, contains_x, fml, m_ctx.pos_atoms(), true)) { + dealloc(eqs); + return false; + } + if (!update_eqs(*eqs, contains_x, fml, m_ctx.neg_atoms(), false)) { + dealloc(eqs); + return false; + } + + m_trail.push_back(contains_x.x()); + m_trail.push_back(fml); + m_eqs_cache.insert(contains_x.x(), fml, eqs); + return true; + } + + bool update_eqs(eq_atoms& eqs, contains_app& contains_x, expr* fml, atom_set const& tbl, bool is_pos) { + atom_set::iterator it = tbl.begin(), end = tbl.end(); + expr* x = contains_x.x(); + for (; it != end; ++it) { + app* e = *it; + if (!contains_x(e)) { + continue; + } + if (m_util.is_lt(e)) { + NOT_IMPLEMENTED_YET(); + } + expr* e1, *e2; + if (!m.is_eq(e, e1, e2)) { + TRACE("quant_elim", tout << "Cannot handle: " << mk_pp(e, m) << "\n";); + return false; + } + if (x == e2) { + std::swap(e1, e2); + } + if (contains_x(e2) || x != e1) { + TRACE("quant_elim", tout << "Cannot handle: " << mk_pp(e, m) << "\n";); + return false; + } + if (is_pos) { + eqs.add_eq(e, e2); + } + else { + eqs.add_neq(e, e2); + } + } + return true; + } + + }; + + qe_solver_plugin* mk_dl_plugin(i_solver_context& ctx) { + return alloc(dl_plugin, ctx, ctx.get_manager()); + } +} diff --git a/lib/qe_sat_tactic.cpp b/lib/qe_sat_tactic.cpp new file mode 100644 index 000000000..21a2a50ad --- /dev/null +++ b/lib/qe_sat_tactic.cpp @@ -0,0 +1,706 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qe_sat_tactic.cpp + +Abstract: + + Procedure for quantifier satisfiability using quantifier projection. + Based on generalizations by Bjorner & Monniaux + (see tvm\papers\z3qe\altqe.tex) + +Author: + + Nikolaj Bjorner (nbjorner) 2012-02-24 + +Revision History: + + +--*/ + +#include "qe_sat_tactic.h" +#include "quant_hoist.h" +#include "ast_pp.h" +#include "smt_solver.h" +#include "qe.h" +#include "cooperate.h" +#include "model_v2_pp.h" +#include "expr_replacer.h" +#include "th_rewriter.h" +#include "expr_context_simplifier.h" + +// plugin registration. +// solver specific projection operators. +// connect goals to tactic + +namespace qe { + + class is_relevant_default : public i_expr_pred { + public: + bool operator()(expr* e) { + return true; + } + }; + + class mk_atom_default : public i_nnf_atom { + public: + virtual void operator()(expr* e, bool pol, expr_ref& result) { + if (pol) result = e; + else result = result.get_manager().mk_not(e); + } + }; + + class sat_tactic : public tactic { + + // forall x . not forall y . not forall z . not forall u . fml. + + ast_manager& m; + expr_ref m_false; + volatile bool m_cancel; + front_end_params m_fparams; + params_ref m_params; + unsigned m_extrapolate_strategy_param; + bool m_projection_mode_param; + bool m_strong_context_simplify_param; + bool m_ctx_simplify_local_param; + vector m_vars; + ptr_vector m_solvers; + smt::solver m_solver; + expr_ref m_fml; + expr_ref_vector m_Ms; + expr_ref_vector m_assignments; + is_relevant_default m_is_relevant; + mk_atom_default m_mk_atom; + th_rewriter m_rewriter; + expr_strong_context_simplifier m_ctx_rewriter; + + class solver_context : public i_solver_context { + + ast_manager& m; + sat_tactic& m_super; + smt::solver& m_solver; + atom_set m_pos; + atom_set m_neg; + app_ref_vector m_vars; + expr_ref m_fml; + ptr_vector m_contains_app; + bool m_projection_mode_param; + public: + solver_context(sat_tactic& s, unsigned idx): + m(s.m), + m_super(s), + m_solver(*s.m_solvers[idx+1]), + m_vars(m), + m_fml(m), + m_projection_mode_param(true) {} + + virtual ~solver_context() { + std::for_each(m_contains_app.begin(), m_contains_app.end(), delete_proc()); + } + + void init(expr* fml, unsigned idx) { + m_fml = fml; + for (unsigned j = 0; j < m_super.vars(idx).size(); ++j) { + add_var(m_super.vars(idx)[j]); + } + get_nnf(m_fml, get_is_relevant(), get_mk_atom(), m_pos, m_neg); + } + + void set_projection_mode(bool p) { m_projection_mode_param = p; } + + ast_manager& get_manager() { return m; } + + expr* fml() { return m_fml; } + + // set of atoms in current formula. + virtual atom_set const& pos_atoms() const { return m_pos; } + virtual atom_set const& neg_atoms() const { return m_neg; } + + // Access current set of variables to solve + virtual unsigned get_num_vars() const { return m_vars.size(); } + virtual app* get_var(unsigned idx) const { return m_vars[idx]; } + virtual app*const* get_vars() const { return m_vars.c_ptr(); } + virtual bool is_var(expr* e, unsigned& idx) const { + for (unsigned i = 0; i < m_vars.size(); ++i) { + if (e == m_vars[i]) return (idx = i, true); + } + return false; + } + virtual contains_app& contains(unsigned idx) { return *m_contains_app[idx]; } + + // callback to replace variable at index 'idx' with definition 'def' and updated formula 'fml' + virtual void elim_var(unsigned idx, expr* fml, expr* def) { + m_fml = fml; + m_pos.reset(); + m_neg.reset(); + get_nnf(m_fml, get_is_relevant(), get_mk_atom(), m_pos, m_neg); + m_vars.erase(idx); + dealloc(m_contains_app[idx]); + m_contains_app.erase(m_contains_app.c_ptr() + idx); + } + + // callback to add new variable to branch. + virtual void add_var(app* x) { + m_vars.push_back(x); + m_contains_app.push_back(alloc(contains_app, m, x)); + } + + // callback to add constraints in branch. + virtual void add_constraint(bool use_var, expr* l1 = 0, expr* l2 = 0, expr* l3 = 0) { + ptr_buffer args; + if (l1) args.push_back(l1); + if (l2) args.push_back(l2); + if (l3) args.push_back(l3); + expr_ref cnstr(m.mk_or(args.size(), args.c_ptr()), m); + m_solver.assert_expr(cnstr); + TRACE("qe", tout << "add_constraint " << mk_pp(cnstr,m) << "\n";); + } + + // eliminate finite domain variable 'var' from fml. + virtual void blast_or(app* var, expr_ref& fml) { + expr_ref result(m); + expr_quant_elim qelim(m, m_super.m_fparams); + qe::mk_exists(1, &var, fml); + qelim(m.mk_true(), fml, result); + fml = result; + TRACE("qe", tout << mk_pp(var, m) << " " << mk_pp(fml, m) << "\n";); + } + + void project_var_partial(unsigned i) { + app* x = get_var(i); + m_super.check_success(has_plugin(x)); + qe_solver_plugin& p = plugin(m.get_sort(x)->get_family_id()); + model_ref model; + m_solver.get_model(model); + m_super.check_success(p.project(contains(i), model, m_fml)); + m_super.m_rewriter(m_fml); + TRACE("qe", model_v2_pp(tout, *model); tout << "\n"; + tout << mk_pp(m_fml, m) << "\n";); + elim_var(i, m_fml, 0); + } + + void project_var_full(unsigned i) { + expr_ref result(m); + app* x = get_var(i); + expr_quant_elim qelim(m, m_super.m_fparams); + qe::mk_exists(1, &x, m_fml); + qelim(m.mk_true(), m_fml, result); + m_fml = result; + m_super.m_rewriter(m_fml); + TRACE("qe", tout << mk_pp(m_fml, m) << "\n";); + elim_var(i, m_fml, 0); + } + + void project_var(unsigned i) { + if (m_projection_mode_param) { + project_var_full(i); + } + else { + project_var_partial(i); + } + } + }; + + public: + sat_tactic(ast_manager& m, params_ref const& p = params_ref()): + m(m), + m_false(m.mk_false(), m), + m_cancel(false), + m_params(p), + m_extrapolate_strategy_param(0), + m_projection_mode_param(true), + m_strong_context_simplify_param(true), + m_ctx_simplify_local_param(false), + m_fml(m), + m_Ms(m), + m_assignments(m), + m_rewriter(m), + m_ctx_rewriter(m_fparams, m), + m_solver(m, m_fparams) { + m_fparams.m_model = true; + } + + virtual tactic * translate(ast_manager & m) { + return alloc(sat_tactic, m); + } + + ~sat_tactic() { + for (unsigned i = 0; i < m_solvers.size(); ++i) { + dealloc(m_solvers[i]); + } + } + + virtual void set_cancel(bool f) { + m_cancel = f; + // not thread-safe when solvers are reset. + // TBD: lock - this, reset() and init_Ms. + for (unsigned i = 0; i < m_solvers.size(); ++i) { + m_solvers[i]->set_cancel(f); + } + m_solver.set_cancel(f); + m_ctx_rewriter.set_cancel(f); + } + + virtual void operator()( + goal_ref const& goal, + goal_ref_buffer& result, + model_converter_ref& mc, + proof_converter_ref & pc, + expr_dependency_ref& core) + { + checkpoint(); + reset(); + ptr_vector fmls; + goal->get_formulas(fmls); + m_fml = m.mk_and(fmls.size(), fmls.c_ptr()); + TRACE("qe", tout << "input: " << mk_pp(m_fml,m) << "\n";); + simplify_rewriter_star rw(m); + expr_ref tmp(m); + rw(m_fml, tmp); + m_fml = tmp; + TRACE("qe", tout << "reduced: " << mk_pp(m_fml,m) << "\n";); + skolemize_existential_prefix(); + extract_alt_form(m_fml); + model_ref model; + expr_ref res = qt(0, m.mk_true(), model); + goal->inc_depth(); + if (m.is_false(res)) { + goal->assert_expr(res); + } + else { + goal->reset(); + // equi-satisfiable. What to do with model? + mc = model2model_converter(&*model); + } + result.push_back(goal.get()); + } + + virtual void collect_statistics(statistics & st) const { + for (unsigned i = 0; i < m_solvers.size(); ++i) { + m_solvers[i]->collect_statistics(st); + } + m_solver.collect_statistics(st); + m_ctx_rewriter.collect_statistics(st); + } + + virtual void reset_statistics() { + for (unsigned i = 0; i < m_solvers.size(); ++i) { + m_solvers[i]->reset_statistics(); + } + m_solver.reset_statistics(); + m_ctx_rewriter.reset_statistics(); + } + + virtual void cleanup() {} + + virtual void updt_params(params_ref const & p) { + m_extrapolate_strategy_param = p.get_uint(":extrapolate-strategy", m_extrapolate_strategy_param); + m_projection_mode_param = p.get_bool(":projection-mode", m_projection_mode_param); + m_strong_context_simplify_param = p.get_bool(":strong-context-simplify", m_strong_context_simplify_param); + m_ctx_simplify_local_param = p.get_bool(":strong-context-simplify-local", m_ctx_simplify_local_param); + } + + virtual void collect_param_descrs(param_descrs & r) { + r.insert(":extrapolate-strategy",CPK_UINT, "(default: 0 trivial extrapolation) 1 - nnf strengthening 2 - smt-test 3 - nnf_weakening"); + r.insert(":projection-mode", CPK_BOOL, "(default: true - full) false - partial quantifier instantiation"); + r.insert(":strong-context-simplify", CPK_BOOL, "(default: true) use strong context simplifier on result of quantifier elimination"); + r.insert(":strong-context-simplify-local", CPK_BOOL, "(default: false) use strong context simplifier locally on the new formula only"); + } + + + private: + + unsigned num_alternations() const { return m_vars.size(); } + + void init_Ms() { + for (unsigned i = 0; i < num_alternations(); ++i) { + m_Ms.push_back(m.mk_true()); + m_solvers.push_back(alloc(smt::solver, m, m_fparams, m_params)); + } + m_Ms.push_back(m_fml); + m_solvers.push_back(alloc(smt::solver, m, m_fparams, m_params)); + m_solvers.back()->assert_expr(m_fml); + } + + expr* M(unsigned i) { return m_Ms[i].get(); } + + app_ref_vector const& vars(unsigned i) { return m_vars[i]; } + + smt::solver& solver(unsigned i) { return *m_solvers[i]; } + + void reset() { + m_fml = 0; + m_Ms.reset(); + m_vars.reset(); + } + + void skolemize_existential_prefix() { + quantifier_hoister hoist(m); + expr_ref result(m); + app_ref_vector vars(m); + hoist.pull_exists(m_fml, vars, result); + m_fml = result; + } + + // + // fa x ex y fa z . phi + // fa x ! fa y ! fa z ! (!phi) + // + void extract_alt_form(expr* fml) { + quantifier_hoister hoist(m); + expr_ref result(m); + bool is_fa; + unsigned parity = 0; + m_fml = fml; + while (true) { + app_ref_vector vars(m); + hoist(m_fml, vars, is_fa, result); + if (vars.empty()) { + break; + } + SASSERT(((parity & 0x1) == 0) == is_fa); + ++parity; + TRACE("qe", tout << "Hoist " << (is_fa?"fa":"ex") << "\n"; + for (unsigned i = 0; i < vars.size(); ++i) { + tout << mk_pp(vars[i].get(), m) << " "; + } + tout << "\n"; + tout << mk_pp(result, m) << "\n";); + + m_vars.push_back(vars); + m_fml = result; + } + // + // negate formula if the last quantifier is universal. + // so that normal form fa x ! fa y ! fa z ! psi + // is obtained. + // + if ((parity & 0x1) == 1) { + m_fml = m.mk_not(m_fml); + } + init_Ms(); + checkpoint(); + } + + /** + \brief Main quantifier test algorithm loop. + + */ + expr_ref qt(unsigned i, expr* ctx, model_ref& model) { + model_ref model1; + while (true) { + IF_VERBOSE(1, verbose_stream() << "qt " << i << "\n";); + TRACE("qe", + tout << i << " " << mk_pp(ctx, m) << "\n"; + display(tout);); + checkpoint(); + if (is_sat(i, ctx, model)) { + expr_ref ctxM = extrapolate(i, ctx, M(i)); + if (i == num_alternations()) { + return ctxM; + } + expr_ref res = qt(i+1, ctxM, model1); + if (m.is_false(res)) { + return ctxM; + } + project(i, res); + } + else { + return m_false; + } + } + UNREACHABLE(); + return expr_ref(m); + } + + /** + \brief compute extrapolant + + Assume A & B is sat. + Compute C, such that + 1. A & C is sat, + 2. not B & C is unsat. + (C strengthens B and is still satisfiable with A). + */ + expr_ref extrapolate(unsigned i, expr* A, expr* B) { + switch(m_extrapolate_strategy_param) { + case 0: return trivial_extrapolate(i, A, B); + case 1: return nnf_strengthening_extrapolate(i, A, B); + case 2: return smt_test_extrapolate(i, A, B); + case 3: return nnf_weakening_extrapolate(i, A, B); + default: return trivial_extrapolate(i, A, B); + } + } + + expr_ref trivial_extrapolate(unsigned i, expr* A, expr* B) { + return expr_ref(B, m); + } + + expr_ref conjunction_extrapolate(unsigned i, expr* A, expr* B) { + return expr_ref(m.mk_and(A, B), m); + } + + /** + + Set C = nnf(B), That is, C is the NNF version of B. + For each literal in C in order, replace the literal by False and + check the conditions for extrapolation: + 1. not B & C is unsat, + 2. A & C is sat. + The first check holds by construction, so it is redundant. + The second is not redundant. + Instead of replacing literals in an NNF formula, + one simply asserts the negation of that literal. + */ + expr_ref nnf_strengthening_extrapolate(unsigned i, expr* A, expr* B) { + expr_ref Bnnf(B, m); + atom_set pos, neg; + get_nnf(Bnnf, m_is_relevant, m_mk_atom, pos, neg); + expr_substitution sub(m); + + remove_duplicates(pos, neg); + + // Assumption: B is already asserted in solver[i]. + smt::solver& solver = *m_solvers[i]; + solver.push(); + solver.assert_expr(A); + nnf_strengthen(solver, pos, m.mk_false(), sub); + nnf_strengthen(solver, neg, m.mk_true(), sub); + + + solver.pop(1); + scoped_ptr replace = mk_default_expr_replacer(m); + replace->set_substitution(&sub); + (*replace)(Bnnf); + m_rewriter(Bnnf); + TRACE("qe", + tout << "A: " << mk_pp(A, m) << "\n"; + tout << "B: " << mk_pp(B, m) << "\n"; + tout << "B': " << mk_pp(Bnnf.get(), m) << "\n"; + // solver.display(tout); + ); + DEBUG_CODE( + solver.push(); + solver.assert_expr(m.mk_not(B)); + solver.assert_expr(Bnnf); + lbool is_sat = solver.check(); + TRACE("qe", tout << "is sat: " << is_sat << "\n";); + SASSERT(is_sat == l_false); + solver.pop(1);); + DEBUG_CODE( + solver.push(); + solver.assert_expr(A); + solver.assert_expr(Bnnf); + lbool is_sat = solver.check(); + TRACE("qe", tout << "is sat: " << is_sat << "\n";); + SASSERT(is_sat == l_true); + solver.pop(1);); + + return Bnnf; + } + + void nnf_strengthen(smt::solver& solver, atom_set& atoms, expr* value, expr_substitution& sub) { + atom_set::iterator it = atoms.begin(), end = atoms.end(); + for (; it != end; ++it) { + solver.push(); + solver.assert_expr(m.mk_iff(*it, value)); + lbool is_sat = solver.check(); + solver.pop(1); + if (is_sat == l_true) { + TRACE("qe", tout << "consistent with: " << mk_pp(*it, m) << " " << mk_pp(value, m) << "\n";); + sub.insert(*it, value); + solver.assert_expr(m.mk_iff(*it, value)); + } + checkpoint(); + } + } + + void remove_duplicates(atom_set& pos, atom_set& neg) { + ptr_vector to_delete; + atom_set::iterator it = pos.begin(), end = pos.end(); + for (; it != end; ++it) { + if (neg.contains(*it)) { + to_delete.push_back(*it); + } + } + for (unsigned j = 0; j < to_delete.size(); ++j) { + pos.remove(to_delete[j]); + neg.remove(to_delete[j]); + } + } + + /** + Set C = nnf(B), That is, C is the NNF version of B. + For each literal in C in order, replace the literal by True and + check the conditions for extrapolation + 1. not B & C is unsat, + 2. A & C is sat. + The second holds by construction and is redundant. + The first is not redundant. + */ + expr_ref nnf_weakening_extrapolate(unsigned i, expr* A, expr* B) { + expr_ref Bnnf(B, m); + atom_set pos, neg; + get_nnf(Bnnf, m_is_relevant, m_mk_atom, pos, neg); + remove_duplicates(pos, neg); + expr_substitution sub(m); + + m_solver.push(); + m_solver.assert_expr(A); + m_solver.assert_expr(m.mk_not(B)); + nnf_weaken(m_solver, Bnnf, pos, m.mk_true(), sub); + nnf_weaken(m_solver, Bnnf, neg, m.mk_false(), sub); + scoped_ptr replace = mk_default_expr_replacer(m); + replace->set_substitution(&sub); + (*replace)(Bnnf); + m_rewriter(Bnnf); + m_solver.pop(1); + return Bnnf; + } + + void nnf_weaken(smt::solver& solver, expr_ref& B, atom_set& atoms, expr* value, expr_substitution& sub) { + atom_set::iterator it = atoms.begin(), end = atoms.end(); + for (; it != end; ++it) { + solver.push(); + expr_ref fml = B; + mk_default_expr_replacer(m)->apply_substitution(*it, value, fml); + solver.assert_expr(fml); + if (solver.check() == l_false) { + sub.insert(*it, value); + B = fml; + } + solver.pop(1); + checkpoint(); + } + } + + + /** + Use the model for A & B to extrapolate. + Initially, C is conjunction of literals from B + that are in model of A & B. + The model is a conjunction of literals. + Let us denote this set $C$. We see that the conditions + for extrapolation are satisfied. Furthermore, + C can be generalized by removing literals + from C as long as !B & A & C is unsatisfiable. + */ + + expr_ref smt_test_extrapolate(unsigned i, expr* A, expr* B) { + expr_ref_vector proxies(m), core(m); + obj_map proxy_map; + + checkpoint(); + + m_solver.push(); + m_solver.assert_expr(m.mk_not(B)); + for (unsigned i = 0; i < m_assignments.size(); ++i) { + proxies.push_back(m.mk_fresh_const("proxy",m.mk_bool_sort())); + proxy_map.insert(proxies.back(), m_assignments[i].get()); + m_solver.assert_expr(m.mk_iff(proxies.back(), m_assignments[i].get())); + TRACE("qe", tout << "assignment: " << mk_pp(m_assignments[i].get(), m) << "\n";); + } + lbool is_sat = m_solver.check(proxies.size(), proxies.c_ptr()); + SASSERT(is_sat == l_false); + unsigned core_size = m_solver.get_unsat_core_size(); + for (unsigned i = 0; i < core_size; ++i) { + core.push_back(proxy_map.find(m_solver.get_unsat_core_expr(i))); + } + expr_ref result(m.mk_and(core.size(), core.c_ptr()), m); + TRACE("qe", tout << "core: " << mk_pp(result, m) << "\n"; + tout << mk_pp(A, m) << "\n"; + tout << mk_pp(B, m) << "\n";); + m_solver.pop(1); + return result; + } + + /** + \brief project vars(idx) from fml relative to M(idx). + */ + void project(unsigned idx, expr* _fml) { + SASSERT(idx < num_alternations()); + expr_ref fml(_fml, m); + if (m_strong_context_simplify_param && m_ctx_simplify_local_param) { + m_ctx_rewriter.push(); + m_ctx_rewriter.assert(M(idx)); + m_ctx_rewriter(fml); + m_ctx_rewriter.pop(); + TRACE("qe", tout << mk_pp(_fml, m) << "\n-- context simplify -->\n" << mk_pp(fml, m) << "\n";); + } + solver_context ctx(*this, idx); + ctx.add_plugin(mk_arith_plugin(ctx, false, m_fparams)); + ctx.add_plugin(mk_bool_plugin(ctx)); + ctx.add_plugin(mk_bv_plugin(ctx)); + ctx.init(fml, idx); + ctx.set_projection_mode(m_projection_mode_param); + m_solvers[idx+1]->push(); + while (ctx.get_num_vars() > 0) { + VERIFY(l_true == m_solvers[idx+1]->check()); + ctx.project_var(ctx.get_num_vars()-1); + } + m_solvers[idx+1]->pop(1); + expr_ref not_fml(m.mk_not(ctx.fml()), m); + m_rewriter(not_fml); + if (m_strong_context_simplify_param && !m_ctx_simplify_local_param) { + m_ctx_rewriter.push(); + m_ctx_rewriter.assert(M(idx)); + m_ctx_rewriter(not_fml); + m_ctx_rewriter.pop(); + } + expr_ref tmp(m.mk_and(M(idx), not_fml), m); + m_rewriter(tmp); + m_Ms[idx] = tmp; + m_solvers[idx]->assert_expr(not_fml); + TRACE("qe", + tout << mk_pp(fml, m) << "\n--->\n"; + tout << mk_pp(tmp, m) << "\n";); + } + + void checkpoint() { + if (m_cancel) { + throw tactic_exception(TACTIC_CANCELED_MSG); + } + cooperate("qe-sat"); + } + + void check_success(bool ok) { + if (!ok) { + TRACE("qe", tout << "check failed\n";); + throw tactic_exception(TACTIC_CANCELED_MSG); + } + } + + bool is_sat(unsigned i, expr* ctx, model_ref& model) { + smt::solver& solver = *m_solvers[i]; + solver.push(); + solver.assert_expr(ctx); + lbool r = solver.check(); + m_assignments.reset(); + solver.get_assignments(m_assignments); + solver.pop(1); + check_success(r != l_undef); + if (r == l_true && i == 0) solver.get_model(model); + return r == l_true; + } + + void display(std::ostream& out) { + bool is_fa = true; + for (unsigned i = 0; i < num_alternations(); ++i) { + out << (is_fa?"fa ":"ex "); + for (unsigned j = 0; j < vars(i).size(); ++j) { + out << mk_pp(m_vars[i][j].get(), m) << " "; + } + out << "\n"; + out << mk_pp(m_Ms[i+1].get(), m) << "\n"; + is_fa = !is_fa; + } + } + }; + + tactic * mk_sat_tactic(ast_manager& m, params_ref const& p) { + return alloc(sat_tactic, m, p); + } + +}; diff --git a/lib/qe_sat_tactic.h b/lib/qe_sat_tactic.h new file mode 100644 index 000000000..fce609aa0 --- /dev/null +++ b/lib/qe_sat_tactic.h @@ -0,0 +1,34 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qe_sat_tactic.h + +Abstract: + + Procedure for quantifier satisfiability using quantifier elimination. + +Author: + + Nikolaj Bjorner (nbjorner) 2012-02-24 + +Revision History: + + +--*/ + + +#ifndef __QE_SAT_H__ +#define __QE_SAT_H__ + +#include"tactic.h" + +namespace qe { + + tactic * mk_sat_tactic(ast_manager& m, params_ref const& p); + +}; + + +#endif diff --git a/lib/qe_tactic.cpp b/lib/qe_tactic.cpp new file mode 100644 index 000000000..f17d15d6a --- /dev/null +++ b/lib/qe_tactic.cpp @@ -0,0 +1,153 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + qe_tactic.cpp + +Abstract: + + Quantifier elimination front-end for tactic framework. + +Author: + + Leonardo de Moura (leonardo) 2011-12-28. + +Revision History: + +--*/ +#include"tactical.h" +#include"filter_model_converter.h" +#include"cooperate.h" +#include"qe.h" + +class qe_tactic : public tactic { + struct imp { + ast_manager & m; + front_end_params m_fparams; + volatile bool m_cancel; + qe::expr_quant_elim m_qe; + + imp(ast_manager & _m, params_ref const & p): + m(_m), + m_qe(m, m_fparams) { + updt_params(p); + m_cancel = false; + } + + void updt_params(params_ref const & p) { + m_fparams.m_nlquant_elim = p.get_bool(":qe-nonlinear", false); + m_qe.updt_params(p); + } + + void collect_param_descrs(param_descrs & r) { + m_qe.collect_param_descrs(r); + } + + void set_cancel(bool f) { + m_cancel = f; + m_qe.set_cancel(f); + } + + void checkpoint() { + if (m_cancel) + throw tactic_exception(TACTIC_CANCELED_MSG); + cooperate("qe"); + } + + void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + mc = 0; pc = 0; core = 0; + tactic_report report("qe", *g); + m_fparams.m_model = g->models_enabled(); + proof_ref new_pr(m); + expr_ref new_f(m); + bool produce_proofs = g->proofs_enabled(); + + unsigned sz = g->size(); + for (unsigned i = 0; i < sz; i++) { + checkpoint(); + if (g->inconsistent()) + break; + expr * f = g->form(i); + if (!has_quantifiers(f)) + continue; + m_qe(m.mk_true(), f, new_f); + new_pr = 0; + if (produce_proofs) { + new_pr = m.mk_modus_ponens(g->pr(i), new_pr); + } + g->update(i, new_f, new_pr, g->dep(i)); + } + g->inc_depth(); + result.push_back(g.get()); + TRACE("qe", g->display(tout);); + SASSERT(g->is_well_sorted()); + } + }; + + imp * m_imp; + params_ref m_params; +public: + qe_tactic(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(qe_tactic, m, m_params); + } + + virtual ~qe_tactic() { + dealloc(m_imp); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); + } + + + virtual void collect_param_descrs(param_descrs & r) { + r.insert(":qe-nonlinear", CPK_BOOL, "(default: false) enable virtual term substitution."); + m_imp->collect_param_descrs(r); + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + (*m_imp)(in, result, mc, pc, core); + } + + virtual void cleanup() { + ast_manager & m = m_imp->m; + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + } + +protected: + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + +tactic * mk_qe_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(qe_tactic, m, p)); +} + diff --git a/lib/qe_tactic.h b/lib/qe_tactic.h new file mode 100644 index 000000000..93440a608 --- /dev/null +++ b/lib/qe_tactic.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + qe_tactic.h + +Abstract: + + Quantifier elimination front-end for tactic framework. + +Author: + + Leonardo de Moura (leonardo) 2011-12-28. + +Revision History: + +--*/ +#ifndef _QE_TACTIC_H_ +#define _QE_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_qe_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/qfaufbv_tactic.cpp b/lib/qfaufbv_tactic.cpp new file mode 100644 index 000000000..5f371da0c --- /dev/null +++ b/lib/qfaufbv_tactic.cpp @@ -0,0 +1,65 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qfaufbv_tactic.cpp + +Abstract: + + Tactic for QF_AUFBV benchmarks. + +Author: + + Leonardo (leonardo) 2012-02-23 + +Notes: + +--*/ +#include"solve_eqs_tactic.h" +#include"simplify_tactic.h" +#include"propagate_values_tactic.h" +#include"bit_blaster_tactic.h" +#include"elim_uncnstr_tactic.h" +#include"max_bv_sharing_tactic.h" +#include"bv_size_reduction_tactic.h" +#include"ctx_simplify_tactic.h" +#include"sat_tactic.h" +#include"smt_tactic.h" + +tactic * mk_qfaufbv_tactic(ast_manager & m, params_ref const & p) { + params_ref main_p; + main_p.set_bool(":elim-and", true); + main_p.set_bool(":sort-store", true); + + params_ref simp2_p = p; + simp2_p.set_bool(":som", true); + simp2_p.set_bool(":pull-cheap-ite", true); + simp2_p.set_bool(":push-ite-bv", false); + simp2_p.set_bool(":local-ctx", true); + simp2_p.set_uint(":local-ctx-limit", 10000000); + + params_ref ctx_simp_p; + ctx_simp_p.set_uint(":max-depth", 32); + ctx_simp_p.set_uint(":max-steps", 5000000); + + params_ref solver_p; + solver_p.set_bool(":array-old-simplifier", false); + + tactic * preamble_st = and_then(mk_simplify_tactic(m), + mk_propagate_values_tactic(m), + // using_params(mk_ctx_simplify_tactic(m), ctx_simp_p), + mk_solve_eqs_tactic(m), + mk_elim_uncnstr_tactic(m), + if_no_proofs(if_no_unsat_cores(mk_bv_size_reduction_tactic(m))), + using_params(mk_simplify_tactic(m), simp2_p), + mk_max_bv_sharing_tactic(m) + ); + + tactic * st = using_params(and_then(preamble_st, + using_params(mk_smt_tactic(), solver_p)), + main_p); + + st->updt_params(p); + return st; +} diff --git a/lib/qfaufbv_tactic.h b/lib/qfaufbv_tactic.h new file mode 100644 index 000000000..b9c72cd1e --- /dev/null +++ b/lib/qfaufbv_tactic.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qfaufbv_tactic.h + +Abstract: + + Tactic for QF_AUFBV + +Author: + + Leonardo (leonardo) 2012-02-23 + +Notes: + +--*/ +#ifndef _QFAUFBV_TACTIC_H_ +#define _QFAUFBV_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_qfaufbv_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/qfauflia_tactic.cpp b/lib/qfauflia_tactic.cpp new file mode 100644 index 000000000..422be13d5 --- /dev/null +++ b/lib/qfauflia_tactic.cpp @@ -0,0 +1,52 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qfauflia_tactic.cpp + +Abstract: + + Tactic for QF_AUFLIA + +Author: + + Leonardo (leonardo) 2012-02-21 + +Notes: + +--*/ +#include"tactical.h" +#include"simplify_tactic.h" +#include"propagate_values_tactic.h" +#include"propagate_ineqs_tactic.h" +#include"solve_eqs_tactic.h" +#include"elim_uncnstr_tactic.h" +#include"smt_tactic.h" + +tactic * mk_qfauflia_tactic(ast_manager & m, params_ref const & p) { + params_ref main_p; + main_p.set_bool(":elim-and", true); + main_p.set_bool(":som", true); + main_p.set_bool(":sort-store", true); + + params_ref ctx_simp_p; + ctx_simp_p.set_uint(":max-depth", 30); + ctx_simp_p.set_uint(":max-steps", 5000000); + + params_ref solver_p; + solver_p.set_bool(":array-old-simplifier", false); + + tactic * preamble_st = and_then(mk_simplify_tactic(m), + mk_propagate_values_tactic(m), + mk_solve_eqs_tactic(m), + mk_elim_uncnstr_tactic(m), + mk_simplify_tactic(m) + ); + + tactic * st = and_then(using_params(preamble_st, main_p), + using_params(mk_smt_tactic(), solver_p)); + + st->updt_params(p); + return st; +} diff --git a/lib/qfauflia_tactic.h b/lib/qfauflia_tactic.h new file mode 100644 index 000000000..a063fcd75 --- /dev/null +++ b/lib/qfauflia_tactic.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qfauflia_tactic.h + +Abstract: + + Tactic for QF_AUFLIA + +Author: + + Leonardo (leonardo) 2012-02-21 + +Notes: + +--*/ +#ifndef _QFAUFLIA_TACTIC_H_ +#define _QFAUFLIA_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_qfauflia_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/qfbv_tactic.cpp b/lib/qfbv_tactic.cpp new file mode 100644 index 000000000..5924c5bdf --- /dev/null +++ b/lib/qfbv_tactic.cpp @@ -0,0 +1,118 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qfbv_tactic.cpp + +Abstract: + + Tactic for QF_BV based on bit-blasting + +Author: + + Leonardo (leonardo) 2012-02-22 + +Notes: + +--*/ +#include"tactical.h" +#include"simplify_tactic.h" +#include"propagate_values_tactic.h" +#include"solve_eqs_tactic.h" +#include"elim_uncnstr_tactic.h" +#include"smt_tactic.h" +#include"bit_blaster_tactic.h" +#include"bv1_blaster_tactic.h" +#include"max_bv_sharing_tactic.h" +#include"bv_size_reduction_tactic.h" +#include"aig_tactic.h" +#include"sat_tactic.h" + +#define MEMLIMIT 300 + +tactic * mk_qfbv_tactic(ast_manager & m, params_ref const & p) { + params_ref main_p; + main_p.set_bool(":elim-and", true); + main_p.set_bool(":push-ite-bv", true); + main_p.set_bool(":blast-distinct", true); + + params_ref simp2_p = p; + simp2_p.set_bool(":som", true); + simp2_p.set_bool(":pull-cheap-ite", true); + simp2_p.set_bool(":push-ite-bv", false); + simp2_p.set_bool(":local-ctx", true); + simp2_p.set_uint(":local-ctx-limit", 10000000); + + params_ref local_ctx_p = p; + local_ctx_p.set_bool(":local-ctx", true); + + params_ref solver_p; + solver_p.set_bool(":preprocess", false); // preprocessor of smt::context is not needed. + + params_ref no_flat_p; + no_flat_p.set_bool(":flat", false); + + params_ref ctx_simp_p; + ctx_simp_p.set_uint(":max-depth", 32); + ctx_simp_p.set_uint(":max-steps", 50000000); + + params_ref hoist_p; + hoist_p.set_bool(":hoist-mul", true); + hoist_p.set_bool(":som", false); + + params_ref solve_eq_p; + // conservative guassian elimination. + solve_eq_p.set_uint(":solve-eqs-max-occs", 2); + + params_ref big_aig_p; + big_aig_p.set_bool(":aig-per-assertion", false); + + tactic * preamble_st = and_then(and_then(mk_simplify_tactic(m), + mk_propagate_values_tactic(m), + using_params(mk_solve_eqs_tactic(m), solve_eq_p), + mk_elim_uncnstr_tactic(m), + if_no_proofs(if_no_unsat_cores(mk_bv_size_reduction_tactic(m))), + using_params(mk_simplify_tactic(m), simp2_p)), + // Z3 can solve a couple of extra benchmarks by using :hoist-mul + // but the timeout in SMT-COMP is too small. + // Moreover, it impacted negatively some easy benchmarks. + // We should decide later, if we keep it or not. + using_params(mk_simplify_tactic(m), hoist_p), + mk_max_bv_sharing_tactic(m)); + +#ifdef USE_OLD_SAT_SOLVER + tactic * new_sat = and_then(mk_simplify_tactic(m), + mk_smt_tactic()); +#else + tactic * new_sat = cond(mk_or(mk_produce_proofs_probe(), mk_produce_unsat_cores_probe()), + and_then(mk_simplify_tactic(m), + mk_smt_tactic()), + mk_sat_tactic(m)); +#endif + + tactic * st = using_params(and_then(preamble_st, + // If the user sets HI_DIV0=false, then the formula may contain uninterpreted function + // symbols. In this case, we should not use + cond(mk_is_qfbv_probe(), + cond(mk_is_qfbv_eq_probe(), + and_then(mk_bv1_blaster_tactic(m), + using_params(mk_smt_tactic(), solver_p)), + and_then(mk_bit_blaster_tactic(m), + when(mk_lt(mk_memory_probe(), mk_const_probe(MEMLIMIT)), + 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))))), + new_sat)), + mk_smt_tactic())), + main_p); + + st->updt_params(p); + return st; +} + + diff --git a/lib/qfbv_tactic.h b/lib/qfbv_tactic.h new file mode 100644 index 000000000..3e3eb7224 --- /dev/null +++ b/lib/qfbv_tactic.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qfbv_tactic.h + +Abstract: + + Tactic for QF_BV based on bit-blasting + +Author: + + Leonardo (leonardo) 2012-02-22 + +Notes: + +--*/ +#ifndef _QFBV_TACTIC_ +#define _QFBV_TACTIC_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_qfbv_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/qffpa_tactic.cpp b/lib/qffpa_tactic.cpp new file mode 100644 index 000000000..dd656666c --- /dev/null +++ b/lib/qffpa_tactic.cpp @@ -0,0 +1,38 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qffpa_tactic.cpp + +Abstract: + + Tactic for QF_FPA benchmarks. + +Author: + + Christoph (cwinter) 2012-01-16 + +Notes: + +--*/ +#include"tactical.h" +#include"simplify_tactic.h" +#include"bit_blaster_tactic.h" +#include"sat_tactic.h" +#include"fpa2bv_tactic.h" + +#include"qffpa_tactic.h" + +tactic * mk_qffpa_tactic(ast_manager & m, params_ref const & p) { + params_ref sat_simp_p = p; + sat_simp_p .set_bool(":elim-and", true); + + return and_then(mk_simplify_tactic(m, p), + mk_fpa2bv_tactic(m, p), + using_params(mk_simplify_tactic(m, p), sat_simp_p), + mk_bit_blaster_tactic(m, p), + using_params(mk_simplify_tactic(m, p), sat_simp_p), + mk_sat_tactic(m, p), + mk_fail_if_undecided_tactic()); +} \ No newline at end of file diff --git a/lib/qffpa_tactic.h b/lib/qffpa_tactic.h new file mode 100644 index 000000000..355b96063 --- /dev/null +++ b/lib/qffpa_tactic.h @@ -0,0 +1,29 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qffpa_tactic.h + +Abstract: + + Tactic QF_FPA benchmarks. + +Author: + + Christoph (cwinter) 2012-01-16 + +Notes: + + +--*/ +#ifndef _QFFPA_TACTIC_H_ +#define _QFFPA_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_qffpa_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/qfidl_tactic.cpp b/lib/qfidl_tactic.cpp new file mode 100644 index 000000000..57aef4991 --- /dev/null +++ b/lib/qfidl_tactic.cpp @@ -0,0 +1,111 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qfidl_tactic.cpp + +Abstract: + + Tactic for QF_IDL + +Author: + + Leonardo (leonardo) 2012-02-21 + +Notes: + +--*/ +#include"tactical.h" +#include"simplify_tactic.h" +#include"propagate_values_tactic.h" +#include"propagate_ineqs_tactic.h" +#include"solve_eqs_tactic.h" +#include"elim_uncnstr_tactic.h" +#include"normalize_bounds_tactic.h" +#include"fix_dl_var_tactic.h" +#include"smt_tactic.h" +#include"lia2pb_tactic.h" +#include"pb2bv_tactic.h" +#include"diff_neq_tactic.h" +#include"bit_blaster_tactic.h" +#include"max_bv_sharing_tactic.h" +#include"aig_tactic.h" +#include"sat_tactic.h" + +#define BIG_PROBLEM 5000 + +tactic * mk_qfidl_tactic(ast_manager & m, params_ref const & p) { + params_ref main_p; + main_p.set_bool(":elim-and", true); + main_p.set_bool(":blast-distinct", true); + main_p.set_bool(":som", true); + + params_ref lhs_p; + lhs_p.set_bool(":arith-lhs", true); + + params_ref lia2pb_p; + lia2pb_p.set_uint(":lia2pb-max-bits", 4); + + params_ref pb2bv_p; + pb2bv_p.set_uint(":pb2bv-all-clauses-limit", 8); + + params_ref pull_ite_p; + pull_ite_p.set_bool(":pull-cheap-ite", true); + pull_ite_p.set_bool(":local-ctx", true); + pull_ite_p.set_uint(":local-ctx-limit", 10000000); + + tactic * preamble_st = and_then(and_then(mk_simplify_tactic(m), + mk_fix_dl_var_tactic(m), + mk_propagate_values_tactic(m), + mk_elim_uncnstr_tactic(m) + ), + and_then(mk_solve_eqs_tactic(m), + using_params(mk_simplify_tactic(m), lhs_p), + mk_propagate_values_tactic(m), + mk_normalize_bounds_tactic(m), + mk_solve_eqs_tactic(m))); + + + + params_ref bv_solver_p; + // The cardinality constraint encoding generates a lot of shared if-then-else's that can be flattened. + // Several of them are simplified to and/or. If we flat them, we increase a lot the memory consumption. + bv_solver_p.set_bool(":flat", false); + bv_solver_p.set_bool(":som", false); + // dynamic psm seems to work well. + bv_solver_p.set_sym(":gc-strategy", symbol("dyn-psm")); + + tactic * bv_solver = using_params(and_then(mk_simplify_tactic(m), + mk_propagate_values_tactic(m), + mk_solve_eqs_tactic(m), + mk_max_bv_sharing_tactic(m), + mk_bit_blaster_tactic(m), + mk_aig_tactic(), + mk_sat_tactic(m)), + bv_solver_p); + + tactic * try2bv = + and_then(using_params(mk_lia2pb_tactic(m), lia2pb_p), + mk_propagate_ineqs_tactic(m), + using_params(mk_pb2bv_tactic(m), pb2bv_p), + fail_if(mk_not(mk_is_qfbv_probe())), + bv_solver); + + params_ref diff_neq_p; + diff_neq_p.set_uint(":diff-neq-max-k", 25); + + tactic * st = cond(mk_and(mk_lt(mk_num_consts_probe(), mk_const_probe(static_cast(BIG_PROBLEM))), + mk_and(mk_not(mk_produce_proofs_probe()), + mk_not(mk_produce_unsat_cores_probe()))), + using_params(and_then(preamble_st, + or_else(using_params(mk_diff_neq_tactic(m), diff_neq_p), + try2bv, + mk_smt_tactic())), + main_p), + mk_smt_tactic()); + + st->updt_params(p); + + return st; +} diff --git a/lib/qfidl_tactic.h b/lib/qfidl_tactic.h new file mode 100644 index 000000000..64f560581 --- /dev/null +++ b/lib/qfidl_tactic.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qfidl_tactic.h + +Abstract: + + Tactic for QF_IDL + +Author: + + Leonardo (leonardo) 2012-02-21 + +Notes: + +--*/ +#ifndef _QFIDL_TACTIC_H_ +#define _QFIDL_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_qfidl_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/qflia_tactic.cpp b/lib/qflia_tactic.cpp new file mode 100644 index 000000000..a996015ce --- /dev/null +++ b/lib/qflia_tactic.cpp @@ -0,0 +1,218 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qflia_tactic.cpp + +Abstract: + + Tactic for QF_LIA + +Author: + + Leonardo (leonardo) 2012-02-26 + +Notes: + +--*/ +#include"tactical.h" +#include"simplify_tactic.h" +#include"propagate_values_tactic.h" +#include"propagate_ineqs_tactic.h" +#include"normalize_bounds_tactic.h" +#include"solve_eqs_tactic.h" +#include"elim_uncnstr_tactic.h" +#include"smt_tactic.h" +#include"mip_tactic.h" +#include"add_bounds_tactic.h" +#include"pb2bv_tactic.h" +#include"lia2pb_tactic.h" +#include"ctx_simplify_tactic.h" +#include"bit_blaster_tactic.h" +#include"max_bv_sharing_tactic.h" +#include"aig_tactic.h" +#include"sat_tactic.h" +#include"bound_manager.h" +#include"probe_arith.h" + +struct quasi_pb_probe : public probe { + virtual result operator()(goal const & g) { + bool found_non_01 = false; + bound_manager bm(g.m()); + bm(g); + rational l, u; bool st; + bound_manager::iterator it = bm.begin(); + bound_manager::iterator end = bm.end(); + for (; it != end; ++it) { + expr * t = *it; + if (bm.has_lower(t, l, st) && bm.has_upper(t, u, st) && (l.is_zero() || l.is_one()) && (u.is_zero() || u.is_one())) + continue; + if (found_non_01) + return false; + found_non_01 = true; + } + return true; + } +}; + +probe * mk_quasi_pb_probe() { + return mk_and(mk_not(mk_is_unbounded_probe()), + alloc(quasi_pb_probe)); +} + +// Create SMT solver that does not use cuts +static tactic * mk_no_cut_smt_tactic(unsigned rs) { + params_ref solver_p; + solver_p.set_uint(":arith-branch-cut-ratio", 10000000); + solver_p.set_uint(":random-seed", rs); + return using_params(mk_smt_tactic_using(false), solver_p); +} + +// Create SMT solver that does not use cuts +static tactic * mk_no_cut_no_relevancy_smt_tactic(unsigned rs) { + params_ref solver_p; + solver_p.set_uint(":arith-branch-cut-ratio", 10000000); + solver_p.set_uint(":random-seed", rs); + solver_p.set_uint(":relevancy", 0); + return using_params(mk_smt_tactic_using(false), solver_p); +} + +static tactic * mk_bv2sat_tactic(ast_manager & m) { + params_ref solver_p; + // The cardinality constraint encoding generates a lot of shared if-then-else's that can be flattened. + // Several of them are simplified to and/or. If we flat them, we increase a lot the memory consumption. + solver_p.set_bool(":flat", false); + solver_p.set_bool(":som", false); + // dynamic psm seems to work well. + solver_p.set_sym(":gc-strategy", symbol("dyn-psm")); + + return using_params(and_then(mk_simplify_tactic(m), + mk_propagate_values_tactic(m), + mk_solve_eqs_tactic(m), + mk_max_bv_sharing_tactic(m), + mk_bit_blaster_tactic(m), + mk_aig_tactic(), + mk_sat_tactic(m)), + solver_p); +} + +#define SMALL_SIZE 80000 + +static tactic * mk_pb_tactic(ast_manager & m) { + params_ref pb2bv_p; + pb2bv_p.set_bool(":ite-extra", true); + pb2bv_p.set_uint(":pb2bv-all-clauses-limit", 8); + + return and_then(fail_if_not(mk_is_pb_probe()), + fail_if(mk_produce_proofs_probe()), + fail_if(mk_produce_unsat_cores_probe()), + or_else(and_then(fail_if(mk_ge(mk_num_exprs_probe(), mk_const_probe(SMALL_SIZE))), + fail_if_not(mk_is_ilp_probe()), + try_for(mk_mip_tactic(m), 8000), + mk_fail_if_undecided_tactic()), + and_then(using_params(mk_pb2bv_tactic(m), pb2bv_p), + fail_if_not(mk_is_qfbv_probe()), + mk_bv2sat_tactic(m)))); +} + + +static tactic * mk_lia2sat_tactic(ast_manager & m) { + params_ref pb2bv_p; + pb2bv_p.set_bool(":ite-extra", true); + pb2bv_p.set_uint(":pb2bv-all-clauses-limit", 8); + + return and_then(fail_if(mk_is_unbounded_probe()), + fail_if(mk_produce_proofs_probe()), + fail_if(mk_produce_unsat_cores_probe()), + mk_propagate_ineqs_tactic(m), + mk_normalize_bounds_tactic(m), + mk_lia2pb_tactic(m), + using_params(mk_pb2bv_tactic(m), pb2bv_p), + fail_if_not(mk_is_qfbv_probe()), + mk_bv2sat_tactic(m)); +} + +// Try to find a model for an unbounded ILP problem. +// Fails if the problem is no ILP. +static tactic * mk_ilp_model_finder_tactic(ast_manager & m) { + params_ref add_bounds_p1; + add_bounds_p1.set_rat(":add-bound-lower", rational(-16)); + add_bounds_p1.set_rat(":add-bound-upper", rational(15)); + params_ref add_bounds_p2; + add_bounds_p2.set_rat(":add-bound-lower", rational(-32)); + add_bounds_p2.set_rat(":add-bound-upper", rational(31)); + + return and_then(fail_if_not(mk_and(mk_is_ilp_probe(), mk_is_unbounded_probe())), + fail_if(mk_produce_proofs_probe()), + fail_if(mk_produce_unsat_cores_probe()), + mk_propagate_ineqs_tactic(m), + or_else(try_for(mk_mip_tactic(m), 5000), + try_for(mk_no_cut_smt_tactic(100), 2000), + and_then(using_params(mk_add_bounds_tactic(m), add_bounds_p1), + try_for(mk_lia2sat_tactic(m), 5000)), + try_for(mk_no_cut_smt_tactic(200), 5000), + and_then(using_params(mk_add_bounds_tactic(m), add_bounds_p2), + try_for(mk_lia2sat_tactic(m), 10000)), + mk_mip_tactic(m)), + mk_fail_if_undecided_tactic()); +} + +static tactic * mk_bounded_tactic(ast_manager & m) { + return and_then(fail_if(mk_is_unbounded_probe()), + or_else(try_for(mk_no_cut_smt_tactic(100), 5000), + try_for(mk_no_cut_no_relevancy_smt_tactic(200), 5000), + try_for(mk_no_cut_smt_tactic(300), 15000) + ), + mk_fail_if_undecided_tactic()); +} + +tactic * mk_qflia_tactic(ast_manager & m, params_ref const & p) { + params_ref main_p; + main_p.set_bool(":elim-and", true); + main_p.set_bool(":som", true); + // main_p.set_bool(":push-ite-arith", true); + + params_ref pull_ite_p; + pull_ite_p.set_bool(":pull-cheap-ite", true); + pull_ite_p.set_bool(":push-ite-arith", false); + pull_ite_p.set_bool(":local-ctx", true); + pull_ite_p.set_uint(":local-ctx-limit", 10000000); + + params_ref ctx_simp_p; + ctx_simp_p.set_uint(":max-depth", 30); + ctx_simp_p.set_uint(":max-steps", 5000000); + + params_ref lhs_p; + lhs_p.set_bool(":arith-lhs", true); + + tactic * preamble_st = and_then(and_then(mk_simplify_tactic(m), + mk_propagate_values_tactic(m), + using_params(mk_ctx_simplify_tactic(m), ctx_simp_p), + using_params(mk_simplify_tactic(m), pull_ite_p)), + mk_solve_eqs_tactic(m), + mk_elim_uncnstr_tactic(m), + using_params(mk_simplify_tactic(m), lhs_p) + ); + + params_ref quasi_pb_p; + quasi_pb_p.set_uint(":lia2pb-max-bits", 64); + + params_ref no_cut_p; + no_cut_p.set_uint(":arith-branch-cut-ratio", 10000000); + + + tactic * st = using_params(and_then(preamble_st, + or_else(mk_ilp_model_finder_tactic(m), + mk_pb_tactic(m), + and_then(fail_if_not(mk_quasi_pb_probe()), + using_params(mk_lia2sat_tactic(m), quasi_pb_p), + mk_fail_if_undecided_tactic()), + mk_bounded_tactic(m), + mk_smt_tactic())), + main_p); + + st->updt_params(p); + return st; +} + diff --git a/lib/qflia_tactic.h b/lib/qflia_tactic.h new file mode 100644 index 000000000..e5d5a1f76 --- /dev/null +++ b/lib/qflia_tactic.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qflia_tactic.h + +Abstract: + + Tactic for QF_LRA + +Author: + + Leonardo (leonardo) 2012-02-26 + +Notes: + +--*/ +#ifndef _QFLIA_TACTIC_ +#define _QFLIA_TACTIC_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_qflia_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/qflra_tactic.cpp b/lib/qflra_tactic.cpp new file mode 100644 index 000000000..20182ba84 --- /dev/null +++ b/lib/qflra_tactic.cpp @@ -0,0 +1,71 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qflra_tactic.cpp + +Abstract: + + Tactic for QF_LRA + +Author: + + Leonardo (leonardo) 2012-02-26 + +Notes: + +--*/ +#include"tactical.h" +#include"simplify_tactic.h" +#include"propagate_values_tactic.h" +#include"solve_eqs_tactic.h" +#include"elim_uncnstr_tactic.h" +#include"smt_tactic.h" +#include"mip_tactic.h" +#include"recover_01_tactic.h" +#include"ctx_simplify_tactic.h" +#include"probe_arith.h" + +tactic * mk_qflra_tactic(ast_manager & m, params_ref const & p) { + params_ref pivot_p; + pivot_p.set_bool(":arith-greatest-error-pivot", true); + + params_ref main_p = p; + main_p.set_bool(":elim-and", true); + main_p.set_bool(":som", true); + main_p.set_bool(":blast-distinct", true); + + params_ref ctx_simp_p; + ctx_simp_p.set_uint(":max-depth", 30); + ctx_simp_p.set_uint(":max-steps", 5000000); + + params_ref lhs_p; + lhs_p.set_bool(":arith-lhs", true); + lhs_p.set_bool(":eq2ineq", true); + + params_ref elim_to_real_p; + elim_to_real_p.set_bool(":elim-to-real", true); + + tactic * mip = + and_then(fail_if(mk_produce_proofs_probe()), + fail_if(mk_produce_unsat_cores_probe()), + using_params(and_then(and_then(mk_simplify_tactic(m), + mk_recover_01_tactic(m), + using_params(mk_simplify_tactic(m), elim_to_real_p), + mk_propagate_values_tactic(m)), + using_params(mk_ctx_simplify_tactic(m), ctx_simp_p), + mk_elim_uncnstr_tactic(m), + mk_solve_eqs_tactic(m), + using_params(mk_simplify_tactic(m), lhs_p), + using_params(mk_simplify_tactic(m), elim_to_real_p) + ), + main_p), + fail_if(mk_not(mk_is_mip_probe())), + try_for(mk_mip_tactic(m), 30000), + mk_fail_if_undecided_tactic()); + + return using_params(or_else(mip, + using_params(mk_smt_tactic(), pivot_p)), + p); +} diff --git a/lib/qflra_tactic.h b/lib/qflra_tactic.h new file mode 100644 index 000000000..9da48f064 --- /dev/null +++ b/lib/qflra_tactic.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qflra_tactic.h + +Abstract: + + Tactic for QF_LRA + +Author: + + Leonardo (leonardo) 2012-02-26 + +Notes: + +--*/ +#ifndef _QFLRA_TACTIC_ +#define _QFLRA_TACTIC_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_qflra_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/qfnia_tactic.cpp b/lib/qfnia_tactic.cpp new file mode 100644 index 000000000..e87d36503 --- /dev/null +++ b/lib/qfnia_tactic.cpp @@ -0,0 +1,93 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qflia_tactic.cpp + +Abstract: + + Tactic for QF_NIA + +Author: + + Leonardo (leonardo) 2012-02-28 + +Notes: + +--*/ +#include"tactical.h" +#include"simplify_tactic.h" +#include"propagate_values_tactic.h" +#include"solve_eqs_tactic.h" +#include"elim_uncnstr_tactic.h" +#include"smt_tactic.h" +#include"bit_blaster_tactic.h" +#include"max_bv_sharing_tactic.h" +#include"sat_tactic.h" +#include"nla2bv_tactic.h" +#include"ctx_simplify_tactic.h" +#include"cofactor_term_ite_tactic.h" + +tactic * mk_qfnia_bv_solver(ast_manager & m, params_ref const & p_ref) { + params_ref p = p_ref; + p.set_bool(":flat", false); + p.set_bool(":hi-div0", true); + p.set_bool(":elim-and", true); + p.set_bool(":blast-distinct", true); + + params_ref simp2_p = p; + simp2_p.set_bool(":local-ctx", true); + simp2_p.set_uint(":local-ctx-limit", 10000000); + + + tactic * r = using_params(and_then(mk_simplify_tactic(m), + mk_propagate_values_tactic(m), + using_params(mk_simplify_tactic(m), simp2_p), + mk_max_bv_sharing_tactic(m), + mk_bit_blaster_tactic(m), + mk_sat_tactic(m)), + p); + return r; +} + +tactic * mk_qfnia_premable(ast_manager & m, params_ref const & p_ref) { + params_ref pull_ite_p = p_ref; + pull_ite_p.set_bool(":pull-cheap-ite", true); + pull_ite_p.set_bool(":local-ctx", true); + pull_ite_p.set_uint(":local-ctx-limit", 10000000); + + params_ref ctx_simp_p = p_ref; + ctx_simp_p.set_uint(":max-depth", 30); + ctx_simp_p.set_uint(":max-steps", 5000000); + + params_ref simp_p = p_ref; + simp_p.set_bool(":hoist-mul", true); + + params_ref elim_p = p_ref; + elim_p.set_uint(":max-memory",20); + + return + and_then(mk_simplify_tactic(m), + mk_propagate_values_tactic(m), + using_params(mk_ctx_simplify_tactic(m), ctx_simp_p), + using_params(mk_simplify_tactic(m), pull_ite_p), + mk_elim_uncnstr_tactic(m), + skip_if_failed(using_params(mk_cofactor_term_ite_tactic(m), elim_p)), + using_params(mk_simplify_tactic(m), simp_p)); +} + +tactic * mk_qfnia_sat_solver(ast_manager & m, params_ref const & p) { + params_ref nia2sat_p = p; + nia2sat_p.set_uint(":nla2bv-max-bv-size", 64); + + return and_then(mk_nla2bv_tactic(m, nia2sat_p), + mk_qfnia_bv_solver(m, p), + mk_fail_if_undecided_tactic()); +} + +tactic * mk_qfnia_tactic(ast_manager & m, params_ref const & p) { + return and_then(mk_qfnia_premable(m, p), + or_else(mk_qfnia_sat_solver(m, p), + mk_smt_tactic())); +} diff --git a/lib/qfnia_tactic.h b/lib/qfnia_tactic.h new file mode 100644 index 000000000..a6ed01109 --- /dev/null +++ b/lib/qfnia_tactic.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qfnia_tactic.h + +Abstract: + + Tactic for QF_NIA + +Author: + + Leonardo (leonardo) 2012-02-28 + +Notes: + +--*/ +#ifndef _QFNIA_TACTIC_ +#define _QFNIA_TACTIC_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_qfnia_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/qfnra_nlsat_tactic.cpp b/lib/qfnra_nlsat_tactic.cpp new file mode 100644 index 000000000..7bb6bd019 --- /dev/null +++ b/lib/qfnra_nlsat_tactic.cpp @@ -0,0 +1,63 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qfnra_nlsat_tactic.h + +Abstract: + + Tactic based on nlsat for solving QF_NRA problems + +Author: + + Leonardo (leonardo) 2012-01-23 + +Notes: + +--*/ +#include"tactical.h" + +#include"tseitin_cnf_tactic.h" +#include"degree_shift_tactic.h" +#include"purify_arith_tactic.h" +#include"nlsat_tactic.h" +#include"factor_tactic.h" +#include"simplify_tactic.h" +#include"elim_uncnstr_tactic.h" +#include"propagate_values_tactic.h" +#include"solve_eqs_tactic.h" +#include"elim_term_ite_tactic.h" + +tactic * mk_qfnra_nlsat_tactic(ast_manager & m, params_ref const & p) { + params_ref main_p = p; + main_p.set_bool(":elim-and", true); + main_p.set_bool(":blast-distinct", true); + params_ref purify_p = p; + purify_p.set_bool(":complete", false); // temporary hack, solver does not support uninterpreted functions for encoding (div0 x) applications. So, we replace it application of this kind with an uninterpreted function symbol. + + tactic * factor; + if (p.get_bool(":factor", true)) + factor = mk_factor_tactic(m, p); + else + factor = mk_skip_tactic(); + + return and_then(and_then(using_params(mk_simplify_tactic(m, p), + main_p), + using_params(mk_purify_arith_tactic(m, p), + purify_p), + mk_propagate_values_tactic(m, p), + mk_solve_eqs_tactic(m, p), + mk_elim_uncnstr_tactic(m, p), + mk_elim_term_ite_tactic(m, p)), + and_then(/* mk_degree_shift_tactic(m, p), */ // may affect full dimensionality detection + factor, + mk_solve_eqs_tactic(m, p), + using_params(mk_simplify_tactic(m, p), + main_p), + mk_tseitin_cnf_core_tactic(m, p), + using_params(mk_simplify_tactic(m, p), + main_p), + mk_nlsat_tactic(m, p))); +} + diff --git a/lib/qfnra_nlsat_tactic.h b/lib/qfnra_nlsat_tactic.h new file mode 100644 index 000000000..bcaf4b5ce --- /dev/null +++ b/lib/qfnra_nlsat_tactic.h @@ -0,0 +1,30 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qfnra_nlsat_tactic.h + +Abstract: + + Tactic based on nlsat for solving QF_NRA problems + +Author: + + Leonardo (leonardo) 2012-01-23 + +Notes: + +--*/ +#ifndef _QFNRA_NLSAT_TACTIC_H_ +#define _QFNRA_NLSAT_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_qfnra_nlsat_tactic(ast_manager & m, params_ref const & p = params_ref()); + +MK_SIMPLE_TACTIC_FACTORY(qfnra_nlsat_fct, mk_qfnra_nlsat_tactic(m, p)); + +#endif diff --git a/lib/qfnra_tactic.cpp b/lib/qfnra_tactic.cpp new file mode 100644 index 000000000..d13d9e198 --- /dev/null +++ b/lib/qfnra_tactic.cpp @@ -0,0 +1,53 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qfnra_tactic.cpp + +Abstract: + + Tactic for QF_NRA + +Author: + + Leonardo (leonardo) 2012-02-28 + +Notes: + +--*/ +#include"tactical.h" +#include"simplify_tactic.h" +#include"propagate_values_tactic.h" +#include"nla2bv_tactic.h" +#include"smt_tactic.h" +#include"qfnra_nlsat_tactic.h" + +static tactic * mk_qfnra_sat_solver(ast_manager& m, params_ref const& p, unsigned bv_size) { + params_ref nra2sat_p = p; + nra2sat_p.set_uint(":nla2bv-max-bv-size", p.get_uint(":nla2bv-max-bv-size", bv_size)); + + return and_then(mk_nla2bv_tactic(m, nra2sat_p), + mk_smt_tactic(), + mk_fail_if_undecided_tactic()); +} + +tactic * mk_qfnra_tactic(ast_manager & m, params_ref const& p) { + params_ref p1 = p; + p1.set_uint(":seed", 11); + p1.set_bool(":factor", false); + params_ref p2 = p; + p2.set_uint(":seed", 13); + p2.set_bool(":factor", false); + + return and_then(mk_simplify_tactic(m, p), + mk_propagate_values_tactic(m, p), + or_else(try_for(mk_qfnra_nlsat_tactic(m, p), 5000), + try_for(mk_qfnra_nlsat_tactic(m, p1), 10000), + mk_qfnra_sat_solver(m, p, 4), + and_then(try_for(mk_smt_tactic(), 5000), mk_fail_if_undecided_tactic()), + mk_qfnra_sat_solver(m, p, 6), + mk_qfnra_nlsat_tactic(m, p2))); +} + + diff --git a/lib/qfnra_tactic.h b/lib/qfnra_tactic.h new file mode 100644 index 000000000..85fc453d1 --- /dev/null +++ b/lib/qfnra_tactic.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qfnra_tactic.h + +Abstract: + + Tactic for QF_NRA + +Author: + + Leonardo (leonardo) 2012-02-28 + +Notes: + +--*/ +#ifndef _QFNRA_TACTIC_ +#define _QFNRA_TACTIC_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_qfnra_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/qfuf_tactic.cpp b/lib/qfuf_tactic.cpp new file mode 100644 index 000000000..3acfb8d98 --- /dev/null +++ b/lib/qfuf_tactic.cpp @@ -0,0 +1,40 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qfuf_tactic.cpp + +Abstract: + + Tactic for QF_QFUF benchmarks. + +Author: + + Leonardo de Moura (leonardo) 2012-02-21 + + +Notes: + +--*/ +#include"tactical.h" +#include"simplify_tactic.h" +#include"symmetry_reduce_tactic.h" +#include"solve_eqs_tactic.h" +#include"propagate_values_tactic.h" +#include"smt_tactic.h" + +tactic * mk_qfuf_tactic(ast_manager & m, params_ref const & p) { + params_ref s2_p; + s2_p.set_bool(":pull-cheap-ite", true); + s2_p.set_bool(":local-ctx", true); + s2_p.set_uint(":local-ctx-limit", 10000000); + return and_then(mk_simplify_tactic(m, p), + mk_propagate_values_tactic(m, p), + mk_solve_eqs_tactic(m, p), + using_params(mk_simplify_tactic(m, p), s2_p), + mk_symmetry_reduce_tactic(m, p), + mk_smt_tactic(p)); +} + + diff --git a/lib/qfuf_tactic.h b/lib/qfuf_tactic.h new file mode 100644 index 000000000..14d7f010f --- /dev/null +++ b/lib/qfuf_tactic.h @@ -0,0 +1,29 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qfuf_tactic.h + +Abstract: + + Tactic for QF_QFUF benchmarks. + +Author: + + Leonardo de Moura (leonardo) 2012-02-21 + + +Notes: + +--*/ +#ifndef _QFUF_TACTIC_ +#define _QFUF_TACTIC_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_qfuf_tactic(ast_manager & m, params_ref const & p); + +#endif diff --git a/lib/qfufbv_tactic.cpp b/lib/qfufbv_tactic.cpp new file mode 100644 index 000000000..400ff9891 --- /dev/null +++ b/lib/qfufbv_tactic.cpp @@ -0,0 +1,53 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qfufbv_tactic.cpp + +Abstract: + + Tactic for QF_UFBV + +Author: + + Leonardo (leonardo) 2012-02-27 + +Notes: + +--*/ +#include"tactical.h" +#include"simplify_tactic.h" +#include"propagate_values_tactic.h" +#include"solve_eqs_tactic.h" +#include"elim_uncnstr_tactic.h" +#include"smt_tactic.h" +#include"max_bv_sharing_tactic.h" +#include"bv_size_reduction_tactic.h" +#include"reduce_args_tactic.h" + +tactic * mk_qfufbv_tactic(ast_manager & m, params_ref const & p) { + params_ref main_p; + main_p.set_bool(":elim-and", true); + main_p.set_bool(":blast-distinct", true); + + tactic * preamble_st = and_then(mk_simplify_tactic(m), + mk_propagate_values_tactic(m), + mk_solve_eqs_tactic(m), + mk_elim_uncnstr_tactic(m), + if_no_proofs(if_no_unsat_cores(mk_reduce_args_tactic(m))), + if_no_proofs(if_no_unsat_cores(mk_bv_size_reduction_tactic(m))), + mk_max_bv_sharing_tactic(m) + ); + + tactic * st = using_params(and_then(preamble_st, + mk_smt_tactic()), + main_p); + + //cond(is_qfbv(), + // and_then(mk_bit_blaster(m), + // mk_sat_solver(m)), + // mk_smt_solver()) + st->updt_params(p); + return st; +} diff --git a/lib/qfufbv_tactic.h b/lib/qfufbv_tactic.h new file mode 100644 index 000000000..41446042f --- /dev/null +++ b/lib/qfufbv_tactic.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + qfufbv_tactic.h + +Abstract: + + Tactic for QF_UFBV + +Author: + + Leonardo (leonardo) 2012-02-27 + +Notes: + +--*/ +#ifndef _QFUFBV_TACTIC_ +#define _QFUFBV_TACTIC_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_qfufbv_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/qi_params.h b/lib/qi_params.h new file mode 100644 index 000000000..bd41de12c --- /dev/null +++ b/lib/qi_params.h @@ -0,0 +1,139 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + qi_params.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-15. + +Revision History: + +--*/ +#ifndef _QI_PARAMS_H_ +#define _QI_PARAMS_H_ + +#include"ini_file.h" + +enum quick_checker_mode { + MC_NO, // do not use (cheap) model checking based instantiation + MC_UNSAT, // instantiate unsatisfied instances + MC_NO_SAT // instantiate unsatisfied and not-satisfied instances +}; + +struct qi_params { + bool m_qi_ematching; + std::string m_qi_cost; + std::string m_qi_new_gen; + double m_qi_eager_threshold; + double m_qi_lazy_threshold; + unsigned m_qi_max_eager_multipatterns; + unsigned m_qi_max_lazy_multipattern_matching; + bool m_qi_profile; + unsigned m_qi_profile_freq; + quick_checker_mode m_qi_quick_checker; + bool m_qi_lazy_quick_checker; + bool m_qi_promote_unsat; + unsigned m_qi_max_instances; + bool m_qi_lazy_instantiation; + bool m_qi_conservative_final_check; + + bool m_mbqi; + unsigned m_mbqi_max_cexs; + unsigned m_mbqi_max_cexs_incr; + unsigned m_mbqi_max_iterations; + bool m_mbqi_trace; + unsigned m_mbqi_force_template; + + bool m_instgen; + + qi_params(): + /* + The "weight 0" performance bug + ------------------------------ + + The parameters m_qi_cost and m_qi_new_gen influence quantifier instantiation. + - m_qi_cost: specify the cost of a quantifier instantiation. Z3 will block instantiations using m_qi_eager_threshold and m_qi_lazy_threshold. + - m_qi_new_gen: specify how the "generation" tag of an enode created by quantifier instantiation is set. + + Enodes in the input problem have generation 0. + + Some combinations of m_qi_cost and m_qi_new_gen will prevent Z3 from breaking matching loops. + For example, the "Weight 0" peformace bug was triggered by the following combination: + - m_qi_cost: (+ weight generation) + - m_qi_new_gen: cost + If a quantifier has weight 0, then the cost of instantiating it with a term in the input problem has cost 0. + The new enodes created during the instantiation will be tagged with generation = const = 0. So, every enode + will have generation 0, and consequently every quantifier instantiation will have cost 0. + + Although dangerous, this feature was requested by the Boogie team. In their case, the patterns are carefully constructred, + and there are no matching loops. Moreover, the tag some quantifiers with weight 0 to instruct Z3 to never block their instances. + An example is the select-store axiom. They need this feature to be able to analyze code that contains very long execution paths. + + So, unless requested by the user, the default weight must be > 0. Otherwise, Z3 will execute without any + matching loop detection. + */ + m_qi_cost("(+ weight generation)"), + m_qi_new_gen("cost"), + m_qi_eager_threshold(10.0), + m_qi_lazy_threshold(20.0), // reduced to give a chance to MBQI + m_qi_max_eager_multipatterns(0), + m_qi_max_lazy_multipattern_matching(2), + m_qi_profile(false), + m_qi_profile_freq(UINT_MAX), + m_qi_quick_checker(MC_NO), + m_qi_lazy_quick_checker(true), + m_qi_promote_unsat(true), + m_qi_max_instances(UINT_MAX), + m_qi_lazy_instantiation(false), + m_qi_conservative_final_check(false), +#ifdef _EXTERNAL_RELEASE + m_mbqi(true), // enabled by default +#else + m_mbqi(false), // to avoid Rustan whining that the models are not partial anymore. +#endif + m_mbqi_max_cexs(1), + m_mbqi_max_cexs_incr(1), + m_mbqi_max_iterations(1000), + m_mbqi_trace(false), + m_mbqi_force_template(10), + m_instgen(false) { + } + + void register_params(ini_params & p) { + p.register_unsigned_param("QI_MAX_EAGER_MULTI_PATTERNS", m_qi_max_eager_multipatterns, + "Specify the number of extra multi patterns that are processed eagerly. By default, the prover use at most one multi-pattern eagerly when there is no unary pattern. This value should be smaller than or equal to PI_MAX_MULTI_PATTERNS"); + p.register_unsigned_param("QI_MAX_LAZY_MULTI_PATTERN_MATCHING", m_qi_max_lazy_multipattern_matching, "Maximum number of rounds of matching in a branch for delayed multipatterns. A multipattern is delayed based on the value of QI_MAX_EAGER_MULTI_PATTERNS"); + p.register_string_param("QI_COST", m_qi_cost, "The cost function for quantifier instantiation"); + p.register_string_param("QI_NEW_GEN", m_qi_new_gen, "The function for calculating the generation of newly constructed terms"); + p.register_double_param("QI_EAGER_THRESHOLD", m_qi_eager_threshold, "Threshold for eager quantifier instantiation"); + p.register_double_param("QI_LAZY_THRESHOLD", m_qi_lazy_threshold, "Threshold for lazy quantifier instantiation"); + p.register_bool_param("QI_PROFILE", m_qi_profile); + p.register_unsigned_param("QI_PROFILE_FREQ", m_qi_profile_freq); + p.register_int_param("QI_QUICK_CHECKER", 0, 2, reinterpret_cast(m_qi_quick_checker), "0 - do not use (cheap) model checker, 1 - instantiate instances unsatisfied by current model, 2 - 1 + instantiate instances not satisfied by current model"); + p.register_bool_param("QI_LAZY_QUICK_CHECKER", m_qi_lazy_quick_checker); + p.register_bool_param("QI_PROMOTE_UNSAT", m_qi_promote_unsat); + p.register_unsigned_param("QI_MAX_INSTANCES", m_qi_max_instances); + p.register_bool_param("QI_LAZY_INSTANTIATION", m_qi_lazy_instantiation); + p.register_bool_param("QI_CONSERVATIVE_FINAL_CHECK", m_qi_conservative_final_check); + + + p.register_bool_param("MBQI", m_mbqi, "Model Based Quantifier Instantiation (MBQI)"); + p.register_unsigned_param("MBQI_MAX_CEXS", m_mbqi_max_cexs, "Initial maximal number of counterexamples used in MBQI, each counterexample generates a quantifier instantiation", true); + p.register_unsigned_param("MBQI_MAX_CEXS_INCR", m_mbqi_max_cexs_incr, "Increment for MBQI_MAX_CEXS, the increment is performed after each round of MBQI", true); + p.register_unsigned_param("MBQI_MAX_ITERATIONS", m_mbqi_max_iterations, "Maximum number of rounds of MBQI", true); + p.register_bool_param("MBQI_TRACE", m_mbqi_trace, "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.", true); + p.register_unsigned_param("MBQI_FORCE_TEMPLATE", m_mbqi_force_template, "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", true); + + p.register_bool_param("INST_GEN", m_instgen, "Enable Instantiation Generation solver (disables other quantifier reasoning)", false); + } +}; + +#endif /* _QI_PARAMS_H_ */ + diff --git a/lib/qi_queue.cpp b/lib/qi_queue.cpp new file mode 100644 index 000000000..a2a2cc9de --- /dev/null +++ b/lib/qi_queue.cpp @@ -0,0 +1,484 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + qi_queue.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-15. + +Revision History: + +--*/ +#include"smt_context.h" +#include"qi_queue.h" +#include"warning.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"var_subst.h" +#include"stats.h" + +namespace smt { + + qi_queue::qi_queue(quantifier_manager & qm, context & ctx, qi_params & params, std::ostream *trace_stream): + m_qm(qm), + m_context(ctx), + m_manager(m_context.get_manager()), + m_params(params), + m_checker(m_context), + m_cost_function(m_manager), + m_new_gen_function(m_manager), + m_parser(m_manager), + m_evaluator(m_manager), + m_subst(m_manager), + m_trace_stream(trace_stream), + m_instances(m_manager) { + init_parser_vars(); + m_vals.resize(15, 0.0f); + } + + qi_queue::~qi_queue() { + } + + void qi_queue::setup() { + TRACE("qi_cost", tout << "qi_cost: " << m_params.m_qi_cost << "\n";); + if (!m_parser.parse_string(m_params.m_qi_cost.c_str(), m_cost_function)) + throw default_exception("invalid cost function %s", m_params.m_qi_cost.c_str()); + if (!m_parser.parse_string(m_params.m_qi_new_gen.c_str(), m_new_gen_function)) + throw default_exception("invalid new-gen function %s", m_params.m_qi_new_gen.c_str()); + m_eager_cost_threshold = m_params.m_qi_eager_threshold; + } + + void qi_queue::init_parser_vars() { +#define COST 14 + m_parser.add_var("cost"); +#define MIN_TOP_GENERATION 13 + m_parser.add_var("min_top_generation"); +#define MAX_TOP_GENERATION 12 + m_parser.add_var("max_top_generation"); +#define INSTANCES 11 + m_parser.add_var("instances"); +#define SIZE 10 + m_parser.add_var("size"); +#define DEPTH 9 + m_parser.add_var("depth"); +#define GENERATION 8 + m_parser.add_var("generation"); +#define QUANT_GENERATION 7 + m_parser.add_var("quant_generation"); +#define WEIGHT 6 + m_parser.add_var("weight"); +#define VARS 5 + m_parser.add_var("vars"); +#define PATTERN_WIDTH 4 + m_parser.add_var("pattern_width"); +#define TOTAL_INSTANCES 3 + m_parser.add_var("total_instances"); +#define SCOPE 2 + m_parser.add_var("scope"); +#define NESTED_QUANTIFIERS 1 + m_parser.add_var("nested_quantifiers"); +#define CS_FACTOR 0 + m_parser.add_var("cs_factor"); + } + + quantifier_stat * qi_queue::set_values(quantifier * q, app * pat, unsigned generation, unsigned min_top_generation, unsigned max_top_generation, float cost) { + quantifier_stat * stat = m_qm.get_stat(q); + m_vals[COST] = cost; + m_vals[MIN_TOP_GENERATION] = static_cast(min_top_generation); + m_vals[MAX_TOP_GENERATION] = static_cast(max_top_generation); + m_vals[INSTANCES] = static_cast(stat->get_num_instances_curr_branch()); + m_vals[SIZE] = static_cast(stat->get_size()); + m_vals[DEPTH] = static_cast(stat->get_depth()); + m_vals[GENERATION] = static_cast(generation); + m_vals[QUANT_GENERATION] = static_cast(stat->get_generation()); + m_vals[WEIGHT] = static_cast(q->get_weight()); + m_vals[VARS] = static_cast(q->get_num_decls()); + m_vals[PATTERN_WIDTH] = pat ? static_cast(pat->get_num_args()) : 1.0f; + m_vals[TOTAL_INSTANCES] = static_cast(stat->get_num_instances_curr_search()); + m_vals[SCOPE] = static_cast(m_context.get_scope_level()); + m_vals[NESTED_QUANTIFIERS] = static_cast(stat->get_num_nested_quantifiers()); + m_vals[CS_FACTOR] = static_cast(stat->get_case_split_factor()); + TRACE("qi_queue_detail", for (unsigned i = 0; i < m_vals.size(); i++) { tout << m_vals[i] << " "; } tout << "\n";); + return stat; + } + + float qi_queue::get_cost(quantifier * q, app * pat, unsigned generation, unsigned min_top_generation, unsigned max_top_generation) { + quantifier_stat * stat = set_values(q, pat, generation, min_top_generation, max_top_generation, 0); + float r = m_evaluator(m_cost_function, m_vals.size(), m_vals.c_ptr()); + stat->update_max_cost(r); + return r; + } + + unsigned qi_queue::get_new_gen(quantifier * q, unsigned generation, float cost) { + // max_top_generation and min_top_generation are not available for computing inc_gen + set_values(q, 0, generation, 0, 0, cost); + float r = m_evaluator(m_new_gen_function, m_vals.size(), m_vals.c_ptr()); + return static_cast(r); + } + + void qi_queue::insert(fingerprint * f, app * pat, unsigned generation, unsigned min_top_generation, unsigned max_top_generation) { + quantifier * q = static_cast(f->get_data()); + float cost = get_cost(q, pat, generation, min_top_generation, max_top_generation); + TRACE("qi_queue_detail", + tout << "new instance of " << q->get_qid() << ", weight " << q->get_weight() + << ", generation: " << generation << ", scope_level: " << m_context.get_scope_level() << ", cost: " << cost << "\n"; + for (unsigned i = 0; i < f->get_num_args(); i++) { + tout << "#" << f->get_arg(i)->get_owner_id() << " "; + } + tout << "\n";); + TRACE("new_entries_bug", tout << "[qi:insert]\n";); + m_new_entries.push_back(entry(f, cost, generation)); + } + + void qi_queue::instantiate() { + svector::iterator it = m_new_entries.begin(); + svector::iterator end = m_new_entries.end(); + unsigned since_last_check = 0; + for (; it != end; ++it) { + entry & curr = *it; + fingerprint * f = curr.m_qb; + quantifier * qa = static_cast(f->get_data()); + + if (curr.m_cost <= m_eager_cost_threshold) { + instantiate(curr); + } + else if (m_params.m_qi_promote_unsat && m_checker.is_unsat(qa->get_expr(), f->get_num_args(), f->get_args())) { + // do not delay instances that produce a conflict. + TRACE("qi_unsat", tout << "promoting instance that produces a conflict\n" << mk_pp(qa, m_manager) << "\n";); + instantiate(curr); + } + else { + TRACE("qi_queue", tout << "delaying quantifier instantiation... " << f << "\n" << mk_pp(qa, m_manager) << "\ncost: " << curr.m_cost << "\n";); + m_delayed_entries.push_back(curr); + } + + // Periodically check if we didn't run out of time/memory. + if (since_last_check++ > 100) { + if (m_context.resource_limits_exceeded()) { + // verbose_stream() << "EXCEEDED...\n"; + break; + } + since_last_check = 0; + } + } + m_new_entries.reset(); + TRACE("new_entries_bug", tout << "[qi:instatiate]\n";); + } + + void qi_queue::display_instance_profile(fingerprint * f, quantifier * q, unsigned num_bindings, enode * const * bindings, unsigned proof_id, unsigned generation) { +#ifndef SMTCOMP + if (m_trace_stream != NULL) { + *m_trace_stream << "[instance] "; +#if 1 + *m_trace_stream << static_cast(f); +#else + for (unsigned i = 0; i < num_bindings; i++) { + // I don't want to use mk_pp because it creates expressions for pretty printing. + // This nasty side-effect may change the behavior of Z3. + *m_trace_stream << " #" << bindings[i]->get_owner_id(); + } + +#endif + if (m_manager.proofs_enabled()) + *m_trace_stream << " #" << proof_id; + *m_trace_stream << " ; " << generation; + *m_trace_stream << "\n"; + } +#endif + } + + void qi_queue::instantiate(entry & ent) { + fingerprint * f = ent.m_qb; + quantifier * q = static_cast(f->get_data()); + unsigned generation = ent.m_generation; + unsigned num_bindings = f->get_num_args(); + enode * const * bindings = f->get_args(); + + ent.m_instantiated = true; + + TRACE("qi_queue_profile", + tout << q->get_qid() << ", gen: " << generation; + for (unsigned i = 0; i < num_bindings; i++) tout << " #" << bindings[i]->get_owner_id(); + tout << "\n";); + + if (m_checker.is_sat(q->get_expr(), num_bindings, bindings)) { + TRACE("checker", tout << "instance already satisfied\n";); + return; + } + expr_ref instance(m_manager); + m_subst(q, num_bindings, bindings, instance); + + TRACE("qi_queue", tout << "new instance:\n" << mk_pp(instance, m_manager) << "\n";); + TRACE("qi_queue_instance", tout << "new instance:\n" << mk_pp(instance, m_manager) << "\n";); + expr_ref s_instance(m_manager); + proof_ref pr(m_manager); + simplifier & simp = m_context.get_simplifier(); + simp(instance, s_instance, pr); + TRACE("qi_queue_bug", tout << "new instance after simplification:\n" << mk_pp(s_instance, m_manager) << "\n";); + if (m_manager.is_true(s_instance)) { + TRACE("checker", tout << "reduced to true, before:\n" << mk_ll_pp(instance, m_manager);); +#ifndef SMTCOMP + if (m_trace_stream != NULL) + *m_trace_stream << "[end-of-instance]\n"; +#endif + return; + } + quantifier_stat * stat = m_qm.get_stat(q); + stat->inc_num_instances(); + if (stat->get_num_instances() % m_params.m_qi_profile_freq == 0) { + m_qm.display_stats(verbose_stream(), q); + } + expr_ref lemma(m_manager); + if (m_manager.is_or(s_instance)) { + ptr_vector args; + args.push_back(m_manager.mk_not(q)); + args.append(to_app(s_instance)->get_num_args(), to_app(s_instance)->get_args()); + lemma = m_manager.mk_or(args.size(), args.c_ptr()); + } + else if (m_manager.is_false(s_instance)) { + lemma = m_manager.mk_not(q); + } + else if (m_manager.is_true(s_instance)) { + lemma = s_instance; + } + else { + lemma = m_manager.mk_or(m_manager.mk_not(q), s_instance); + } + m_instances.push_back(lemma); + proof_ref pr1(m_manager); + unsigned proof_id = 0; + if (m_manager.proofs_enabled()) { + expr_ref_vector bindings_e(m_manager); + for (unsigned i = 0; i < num_bindings; ++i) { + bindings_e.push_back(bindings[i]->get_owner()); + } + app * bare_lemma = m_manager.mk_or(m_manager.mk_not(q), instance); + proof * qi_pr = m_manager.mk_quant_inst(bare_lemma, num_bindings, bindings_e.c_ptr()); + proof_id = qi_pr->get_id(); + if (bare_lemma == lemma) { + pr1 = qi_pr; + } + else if (instance == s_instance) { + proof * rw = m_manager.mk_rewrite(bare_lemma, lemma); + pr1 = m_manager.mk_modus_ponens(qi_pr, rw); + } + else { + app * bare_s_lemma = m_manager.mk_or(m_manager.mk_not(q), s_instance); + proof * prs[1] = { pr.get() }; + proof * cg = m_manager.mk_congruence(bare_lemma, bare_s_lemma, 1, prs); + proof * rw = m_manager.mk_rewrite(bare_s_lemma, lemma); + proof * tr = m_manager.mk_transitivity(cg, rw); + pr1 = m_manager.mk_modus_ponens(qi_pr, tr); + } + m_instances.push_back(pr1); + } + TRACE("qi_queue", tout << mk_pp(lemma, m_manager) << "\n#" << lemma->get_id() << ":=\n" << mk_ll_pp(lemma, m_manager);); + m_stats.m_num_instances++; + unsigned gen = get_new_gen(q, generation, ent.m_cost); + display_instance_profile(f, q, num_bindings, bindings, proof_id, gen); + m_context.internalize_instance(lemma, pr1, gen); + TRACE_CODE({ + static unsigned num_useless = 0; + if (m_manager.is_or(lemma)) { + app * n = to_app(lemma); + bool has_unassigned = false; + expr * true_child = 0; + for (unsigned i = 0; i < n->get_num_args(); i++) { + expr * arg = n->get_arg(i); + switch(m_context.get_assignment(arg)) { + case l_undef: has_unassigned = true; break; + case l_true: true_child = arg; break; + default: + break; + } + } + if (true_child && has_unassigned) { + TRACE("qi_queue_profile_detail", tout << "missed:\n" << mk_ll_pp(s_instance, m_manager) << "\n#" << true_child->get_id() << "\n";); + num_useless++; + if (num_useless % 10 == 0) { + TRACE("qi_queue_profile", tout << "num useless: " << num_useless << "\n";); + } + } + } + }); +#ifndef SMTCOMP + if (m_trace_stream != NULL) + *m_trace_stream << "[end-of-instance]\n"; +#endif + } + + void qi_queue::push_scope() { + TRACE("new_entries_bug", tout << "[qi:push-scope]\n";); + m_scopes.push_back(scope()); + SASSERT(m_new_entries.empty()); + scope & s = m_scopes.back(); + s.m_delayed_entries_lim = m_delayed_entries.size(); + s.m_instances_lim = m_instances.size(); + s.m_instantiated_trail_lim = m_instantiated_trail.size(); + } + + void qi_queue::pop_scope(unsigned num_scopes) { + unsigned new_lvl = m_scopes.size() - num_scopes; + scope & s = m_scopes[new_lvl]; + unsigned old_sz = s.m_instantiated_trail_lim; + unsigned sz = m_instantiated_trail.size(); + for (unsigned i = old_sz; i < sz; i++) + m_delayed_entries[m_instantiated_trail[i]].m_instantiated = false; + m_instantiated_trail.shrink(old_sz); + m_delayed_entries.shrink(s.m_delayed_entries_lim); + m_instances.shrink(s.m_instances_lim); + m_new_entries.reset(); + m_scopes.shrink(new_lvl); + TRACE("new_entries_bug", tout << "[qi:pop-scope]\n";); + } + + void qi_queue::reset() { + m_new_entries.reset(); + m_delayed_entries.reset(); + m_instances.reset(); + m_scopes.reset(); + } + + void qi_queue::init_search_eh() { + m_subst.reset(); + } + + bool qi_queue::final_check_eh() { + TRACE("qi_queue", display_delayed_instances_stats(tout); tout << "lazy threshold: " << m_params.m_qi_lazy_threshold + << ", scope_level: " << m_context.get_scope_level() << "\n";); + if (m_params.m_qi_conservative_final_check) { + bool init = false; + float min_cost; + unsigned sz = m_delayed_entries.size(); + for (unsigned i = 0; i < sz; i++) { + entry & e = m_delayed_entries[i]; + fingerprint * f = e.m_qb; + TRACE("qi_queue", tout << f << ", cost: " << e.m_cost << ", instantiated: " << e.m_instantiated << "\n";); + if (!e.m_instantiated && e.m_cost <= m_params.m_qi_lazy_threshold && (!init || e.m_cost < min_cost)) { + init = true; + min_cost = e.m_cost; + } + } + TRACE("qi_queue_min_cost", tout << "min_cost: " << min_cost << ", scope_level: " << m_context.get_scope_level() << "\n";); + bool result = true; + for (unsigned i = 0; i < sz; i++) { + entry & e = m_delayed_entries[i]; + fingerprint * f = e.m_qb; + quantifier * qa = static_cast(f->get_data()); + TRACE("qi_queue", tout << f << ", cost: " << e.m_cost << ", instantiated: " << e.m_instantiated << "\n";); + if (!e.m_instantiated && e.m_cost <= min_cost) { + TRACE("qi_queue", + tout << "lazy quantifier instantiation...\n" << mk_pp(qa, m_manager) << "\ncost: " << e.m_cost << "\n";); + result = false; + m_instantiated_trail.push_back(i); + m_stats.m_num_lazy_instances++; + instantiate(e); + } + } + return result; + } + + bool result = true; + for (unsigned i = 0; i < m_delayed_entries.size(); i++) { + entry & e = m_delayed_entries[i]; + fingerprint * f = e.m_qb; + quantifier * qa = static_cast(f->get_data()); + TRACE("qi_queue", tout << f << ", cost: " << e.m_cost << ", instantiated: " << e.m_instantiated << "\n";); + if (!e.m_instantiated && e.m_cost <= m_params.m_qi_lazy_threshold) { + TRACE("qi_queue", + tout << "lazy quantifier instantiation...\n" << mk_pp(qa, m_manager) << "\ncost: " << e.m_cost << "\n";); + result = false; + m_instantiated_trail.push_back(i); + m_stats.m_num_lazy_instances++; + instantiate(e); + } + } + return result; + } + + struct delayed_qa_info { + unsigned m_num; + float m_min_cost; + float m_max_cost; + delayed_qa_info():m_num(0), m_min_cost(0.0f), m_max_cost(0.0f) {} + }; + + void qi_queue::display_delayed_instances_stats(std::ostream & out) const { + obj_map qa2info; + ptr_vector qas; + svector::const_iterator it = m_delayed_entries.begin(); + svector::const_iterator end = m_delayed_entries.end(); + for (; it != end; ++it) { + entry const & e = *it; + if (e.m_instantiated) + continue; + quantifier * qa = static_cast(e.m_qb->get_data()); + delayed_qa_info info; + if (qa2info.find(qa, info)) { + info.m_num++; + info.m_min_cost = std::min(info.m_min_cost, e.m_cost); + info.m_max_cost = std::min(info.m_max_cost, e.m_cost); + } + else { + qas.push_back(qa); + info.m_num = 1; + info.m_min_cost = e.m_cost; + info.m_max_cost = e.m_cost; + } + qa2info.insert(qa, info); + } + ptr_vector::iterator it2 = qas.begin(); + ptr_vector::iterator end2 = qas.end(); + for (; it2 != end2; ++it2) { + quantifier * qa = *it2; + delayed_qa_info info; + qa2info.find(qa, info); + out << qa->get_qid() << ": " << info.m_num << " [" << info.m_min_cost << ", " << info.m_max_cost << "]\n"; + } + } + + void qi_queue::get_min_max_costs(float & min, float & max) const { + min = 0.0f; + max = 0.0f; + bool found = false; + for (unsigned i = 0; i < m_delayed_entries.size(); i++) { + if (!m_delayed_entries[i].m_instantiated) { + float c = m_delayed_entries[i].m_cost; + if (found) { + min = std::min(min, c); + max = std::max(max, c); + } + else { + found = true; + min = c; + max = c; + } + } + } + } + + void qi_queue::collect_statistics(::statistics & st) const { + st.update("quant instantiations", m_stats.m_num_instances); + st.update("lazy quant instantiations", m_stats.m_num_lazy_instances); + st.update("missed quant instantiations", m_delayed_entries.size()); + float min, max; + get_min_max_costs(min, max); + st.update("min missed qa cost", min); + st.update("max missed qa cost", max); +#if 0 + if (m_params.m_qi_profile) { + out << "missed/delayed quantifier instances:\n"; + display_delayed_instances_stats(out); + } +#endif + } + +}; + diff --git a/lib/qi_queue.h b/lib/qi_queue.h new file mode 100644 index 000000000..43d57d84f --- /dev/null +++ b/lib/qi_queue.h @@ -0,0 +1,105 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + qi_queue.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-15. + +Revision History: + +--*/ +#ifndef _QI_QUEUE_H_ +#define _QI_QUEUE_H_ + +#include"ast.h" +#include"smt_quantifier_stat.h" +#include"smt_checker.h" +#include"smt_quantifier.h" +#include"qi_params.h" +#include"fingerprints.h" +#include"cost_parser.h" +#include"cost_evaluator.h" +#include"cached_var_subst.h" +#include"statistics.h" + +namespace smt { + class context; + + struct qi_queue_stats { + unsigned m_num_instances, m_num_lazy_instances; + void reset() { memset(this, 0, sizeof(qi_queue_stats)); } + qi_queue_stats() { reset(); } + }; + + class qi_queue { + quantifier_manager & m_qm; + context & m_context; + ast_manager & m_manager; + qi_params & m_params; + qi_queue_stats m_stats; + checker m_checker; + expr_ref m_cost_function; + expr_ref m_new_gen_function; + cost_parser m_parser; + cost_evaluator m_evaluator; + cached_var_subst m_subst; + svector m_vals; + double m_eager_cost_threshold; + std::ostream * m_trace_stream; + struct entry { + fingerprint * m_qb; + float m_cost; + unsigned m_generation:31; + unsigned m_instantiated:1; + entry(fingerprint * f, float c, unsigned g):m_qb(f), m_cost(c), m_generation(g), m_instantiated(false) {} + }; + svector m_new_entries; + svector m_delayed_entries; + expr_ref_vector m_instances; + unsigned_vector m_instantiated_trail; + struct scope { + unsigned m_delayed_entries_lim; + unsigned m_instances_lim; + unsigned m_instantiated_trail_lim; + }; + svector m_scopes; + + void init_parser_vars(); + quantifier_stat * set_values(quantifier * q, app * pat, unsigned generation, unsigned min_top_generation, unsigned max_top_generation, float cost); + float get_cost(quantifier * q, app * pat, unsigned generation, unsigned min_top_generation, unsigned max_top_generation); + unsigned get_new_gen(quantifier * q, unsigned generation, float cost); + void instantiate(entry & ent); + void get_min_max_costs(float & min, float & max) const; + void display_instance_profile(fingerprint * f, quantifier * q, unsigned num_bindings, enode * const * bindings, unsigned proof_id, unsigned generation); + + public: + qi_queue(quantifier_manager & qm, context & ctx, qi_params & params, std::ostream *trace); + ~qi_queue(); + void setup(); + /** + \brief Insert a new quantifier in the queue, f contains the quantifier and bindings. + f->get_data() is the quantifier. + */ + void insert(fingerprint * f, app * pat, unsigned generation, unsigned min_top_generation, unsigned max_top_generation); + void instantiate(); + bool has_work() const { return !m_new_entries.empty(); } + void init_search_eh(); + bool final_check_eh(); + void push_scope(); + void pop_scope(unsigned num_scopes); + void reset(); + void display_delayed_instances_stats(std::ostream & out) const; + void collect_statistics(::statistics & st) const; + }; +}; + +#endif /* _QI_QUEUE_H_ */ + diff --git a/lib/quant_hoist.cpp b/lib/quant_hoist.cpp new file mode 100644 index 000000000..b64e71866 --- /dev/null +++ b/lib/quant_hoist.cpp @@ -0,0 +1,239 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + quant_hoist.cpp + +Abstract: + + Quantifier hoisting utility. + +Author: + + Nikolaj Bjorner (nbjorner) 2010-02-19 + +Revision History: + + Hoisted from quant_elim. + +--*/ + +#include "quant_hoist.h" +#include "expr_functors.h" +#include "ast_smt_pp.h" +#include "bool_rewriter.h" +#include "var_subst.h" +#include "ast_pp.h" + + +// +// Bring quantifiers of common type into prenex form. +// +class quantifier_hoister::impl { + ast_manager& m; + bool_rewriter m_rewriter; + +public: + impl(ast_manager& m) : + m(m), + m_rewriter(m) + {} + + void operator()(expr* fml, app_ref_vector& vars, bool& is_fa, expr_ref& result) { + quantifier_type qt = Q_none_pos; + pull_quantifiers(fml, qt, vars, result); + TRACE("qe_verbose", + tout << mk_pp(fml, m) << "\n"; + tout << mk_pp(result, m) << "\n";); + SASSERT(is_positive(qt)); + is_fa = (Q_forall_pos == qt); + } + + void pull_exists(expr* fml, app_ref_vector& vars, expr_ref& result) { + quantifier_type qt = Q_exists_pos; + pull_quantifiers(fml, qt, vars, result); + TRACE("qe_verbose", + tout << mk_pp(fml, m) << "\n"; + tout << mk_pp(result, m) << "\n";); + } + + void pull_quantifier(bool is_forall, expr_ref& fml, app_ref_vector& vars) { + quantifier_type qt = is_forall?Q_forall_pos:Q_exists_pos; + expr_ref result(m); + pull_quantifiers(fml, qt, vars, result); + TRACE("qe_verbose", + tout << mk_pp(fml, m) << "\n"; + tout << mk_pp(result, m) << "\n";); + fml = result; + } + + void extract_quantifier(quantifier* q, app_ref_vector& vars, expr_ref& result) { + unsigned nd = q->get_num_decls(); + for (unsigned i = 0; i < nd; ++i) { + sort* s = q->get_decl_sort(i); + app* a = m.mk_fresh_const("x", s); + vars.push_back(a); + } + expr * const * exprs = (expr* const*) (vars.c_ptr() + vars.size()- nd); + instantiate(m, q, exprs, result); + } + + +private: + + enum quantifier_type { + Q_forall_pos = 0x10, + Q_exists_pos = 0x20, + Q_none_pos = 0x40, + Q_forall_neg = 0x11, + Q_exists_neg = 0x21, + Q_none_neg = 0x41 + }; + + void display(quantifier_type qt, std::ostream& out) { + switch(qt) { + case Q_forall_pos: out << "Forall+"; break; + case Q_exists_pos: out << "Exists+"; break; + case Q_none_pos: out << "None+"; break; + case Q_forall_neg: out << "Forall-"; break; + case Q_exists_neg: out << "Exists-"; break; + case Q_none_neg: out << "None-"; break; + } + } + + quantifier_type& negate(quantifier_type& qt) { + TRACE("qe", display(qt, tout); tout << "\n";); + qt = static_cast(qt ^0x1); + TRACE("qe", display(qt, tout); tout << "\n";); + return qt; + } + + static bool is_negative(quantifier_type qt) { + return 0 != (qt & 0x1); + } + + static bool is_positive(quantifier_type qt) { + return 0 == (qt & 0x1); + } + + static void set_quantifier_type(quantifier_type& qt, bool is_forall) { + switch(qt) { + case Q_forall_pos: SASSERT(is_forall); break; + case Q_forall_neg: SASSERT(!is_forall); break; + case Q_exists_pos: SASSERT(!is_forall); break; + case Q_exists_neg: SASSERT(is_forall); break; + case Q_none_pos: qt = is_forall?Q_forall_pos:Q_exists_pos; break; + case Q_none_neg: qt = is_forall?Q_exists_neg:Q_forall_neg; break; + } + } + + bool is_compatible(quantifier_type qt, bool is_forall) { + switch(qt) { + case Q_forall_pos: return is_forall; + case Q_forall_neg: return !is_forall; + case Q_exists_pos: return !is_forall; + case Q_exists_neg: return is_forall; + case Q_none_pos: return true; + case Q_none_neg: return true; + default: + UNREACHABLE(); + } + return false; + } + + + void pull_quantifiers(expr* fml, quantifier_type& qt, app_ref_vector& vars, expr_ref& result) { + + if (!has_quantifiers(fml)) { + result = fml; + return; + } + + switch(fml->get_kind()) { + case AST_APP: { + expr_ref_vector args(m); + expr_ref tmp(m); + unsigned num_args = 0; + app* a = to_app(fml); + if (m.is_and(fml)) { + num_args = a->get_num_args(); + for (unsigned i = 0; i < num_args; ++i) { + pull_quantifiers(a->get_arg(i), qt, vars, tmp); + args.push_back(tmp); + } + m_rewriter.mk_and(args.size(), args.c_ptr(), result); + } + else if (m.is_or(fml)) { + num_args = to_app(fml)->get_num_args(); + for (unsigned i = 0; i < num_args; ++i) { + pull_quantifiers(to_app(fml)->get_arg(i), qt, vars, tmp); + args.push_back(tmp); + } + m_rewriter.mk_or(args.size(), args.c_ptr(), result); + } + else if (m.is_not(fml)) { + pull_quantifiers(to_app(fml)->get_arg(0), negate(qt), vars, tmp); + negate(qt); + result = m.mk_not(tmp); + } + else if (m.is_implies(fml)) { + pull_quantifiers(to_app(fml)->get_arg(0), negate(qt), vars, tmp); + negate(qt); + pull_quantifiers(to_app(fml)->get_arg(1), qt, vars, result); + result = m.mk_implies(tmp, result); + } + else if (m.is_ite(fml)) { + pull_quantifiers(to_app(fml)->get_arg(1), qt, vars, tmp); + pull_quantifiers(to_app(fml)->get_arg(2), qt, vars, result); + result = m.mk_ite(to_app(fml)->get_arg(0), tmp, result); + } + else { + // the formula contains a quantifier, but it is "inaccessible" + result = fml; + } + break; + } + case AST_QUANTIFIER: { + quantifier* q = to_quantifier(fml); + expr_ref tmp(m); + if (!is_compatible(qt, q->is_forall())) { + result = fml; + break; + } + set_quantifier_type(qt, q->is_forall()); + extract_quantifier(q, vars, tmp); + pull_quantifiers(tmp, qt, vars, result); + break; + } + case AST_VAR: + result = fml; + break; + default: + UNREACHABLE(); + result = fml; + break; + } + } +}; + +quantifier_hoister::quantifier_hoister(ast_manager& m) { + m_impl = alloc(impl, m); +} + +quantifier_hoister::~quantifier_hoister() { + dealloc(m_impl); +} + +void quantifier_hoister::operator()(expr* fml, app_ref_vector& vars, bool& is_fa, expr_ref& result) { + (*m_impl)(fml, vars, is_fa, result); +} + +void quantifier_hoister::pull_exists(expr* fml, app_ref_vector& vars, expr_ref& result) { + m_impl->pull_exists(fml, vars, result); +} + +void quantifier_hoister::pull_quantifier(bool is_forall, expr_ref& fml, app_ref_vector& vars) { + m_impl->pull_quantifier(is_forall, fml, vars); +} + diff --git a/lib/quant_hoist.h b/lib/quant_hoist.h new file mode 100644 index 000000000..878f7840d --- /dev/null +++ b/lib/quant_hoist.h @@ -0,0 +1,64 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + quant_hoist.h + +Abstract: + + Quantifier hoisting utility. + +Author: + + Nikolaj Bjorner (nbjorner) 2010-02-19 + +Revision History: + + Hoisted from quant_elim. + +--*/ + +#ifndef __QUANTIFIER_HOISTER_H_ +#define __QUANTIFIER_HOISTER_H_ + +#include "ast.h" + +class quantifier_hoister { + class impl; + impl* m_impl; +public: + quantifier_hoister(ast_manager& m); + + ~quantifier_hoister(); + + /** + \brief Pull top-most quantifier up. + Create fresh constants for the bound variables. + Return the constants, the quantifier type (forall or exists), and + the sub-formula under the quantifier. + + The list of variables is empty if the formula is quantifier free or + if the existing quantifiers occur under a connective other than + or, and, implies, ite (then and else branch only). + */ + + void operator()(expr* fml, app_ref_vector& vars, bool& is_fa, expr_ref& result); + + /** + \brief Pull top-most existential quantifier up. + + The list of variables is empty if there are no top-level existential quantifier. + */ + void pull_exists(expr* fml, app_ref_vector& vars, expr_ref& result); + + + /** + \brief Pull top-most universal (is_forall=true) or existential (is_forall=false) quantifier up. + + The list of variables is empty if there are no top-level universal/existential quantifier. + */ + void pull_quantifier(bool is_forall, expr_ref& fml, app_ref_vector& vars); +}; + +#endif diff --git a/lib/quant_tactics.cpp b/lib/quant_tactics.cpp new file mode 100644 index 000000000..157202442 --- /dev/null +++ b/lib/quant_tactics.cpp @@ -0,0 +1,106 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + quant_tactics.cpp + +Abstract: + + Tactics for benchmarks containing quantifiers. + +Author: + + Leonardo de Moura (leonardo) 2012-02-21. + +Revision History: + +--*/ +#include"tactical.h" +#include"simplify_tactic.h" +#include"propagate_values_tactic.h" +#include"solve_eqs_tactic.h" +#include"elim_uncnstr_tactic.h" +#include"qe_tactic.h" +#include"ctx_simplify_tactic.h" +#include"smt_tactic.h" + +static tactic * mk_quant_preprocessor(ast_manager & m, bool disable_gaussian = false) { + params_ref pull_ite_p; + pull_ite_p.set_bool(":pull-cheap-ite", true); + pull_ite_p.set_bool(":local-ctx", true); + pull_ite_p.set_uint(":local-ctx-limit", 10000000); + + params_ref ctx_simp_p; + ctx_simp_p.set_uint(":max-depth", 30); + ctx_simp_p.set_uint(":max-steps", 5000000); + + tactic * solve_eqs; + if (disable_gaussian) + solve_eqs = mk_skip_tactic(); + else + solve_eqs = when(mk_not(mk_has_pattern_probe()), mk_solve_eqs_tactic(m)); + + // remark: investigate if gaussian elimination is useful when patterns are not provided. + return and_then(mk_simplify_tactic(m), + mk_propagate_values_tactic(m), + using_params(mk_ctx_simplify_tactic(m), ctx_simp_p), + using_params(mk_simplify_tactic(m), pull_ite_p), + solve_eqs, + mk_elim_uncnstr_tactic(m), + mk_simplify_tactic(m)); +} + +static tactic * mk_no_solve_eq_preprocessor(ast_manager & m) { + return mk_quant_preprocessor(m, true); +} + +tactic * mk_ufnia_tactic(ast_manager & m, params_ref const & p) { + tactic * st = and_then(mk_no_solve_eq_preprocessor(m), + mk_smt_tactic()); + st->updt_params(p); + return st; +} + +tactic * mk_uflra_tactic(ast_manager & m, params_ref const & p) { + tactic * st = and_then(mk_quant_preprocessor(m), + mk_smt_tactic()); + st->updt_params(p); + return st; +} + +tactic * mk_auflia_tactic(ast_manager & m, params_ref const & p) { + params_ref qi_p; + qi_p.set_str(":qi-cost", "0"); + TRACE("qi_cost", qi_p.display(tout); tout << "\n" << qi_p.get_str(":qi-cost", "") << "\n";); + tactic * st = and_then(mk_no_solve_eq_preprocessor(m), + or_else(and_then(fail_if(mk_gt(mk_num_exprs_probe(), mk_const_probe(static_cast(128)))), + using_params(mk_smt_tactic(), qi_p), + mk_fail_if_undecided_tactic()), + mk_smt_tactic())); + st->updt_params(p); + return st; +} + +tactic * mk_auflira_tactic(ast_manager & m, params_ref const & p) { + tactic * st = and_then(mk_quant_preprocessor(m), + mk_smt_tactic()); + st->updt_params(p); + return st; +} + +tactic * mk_aufnira_tactic(ast_manager & m, params_ref const & p) { + tactic * st = and_then(mk_quant_preprocessor(m), + mk_smt_tactic()); + st->updt_params(p); + return st; +} + +tactic * mk_lra_tactic(ast_manager & m, params_ref const & p) { + tactic * st = and_then(mk_quant_preprocessor(m), + mk_qe_tactic(m), + mk_smt_tactic()); + st->updt_params(p); + return st; +} + diff --git a/lib/quant_tactics.h b/lib/quant_tactics.h new file mode 100644 index 000000000..dc8c458c4 --- /dev/null +++ b/lib/quant_tactics.h @@ -0,0 +1,33 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + quant_tactics.h + +Abstract: + + Tactics for benchmarks containing quantifiers. + +Author: + + Leonardo de Moura (leonardo) 2012-02-21. + +Revision History: + +--*/ +#ifndef _QUANT_TACTICS_H_ +#define _QUANT_TACTICS_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_ufnia_tactic(ast_manager & m, params_ref const & p); +tactic * mk_uflra_tactic(ast_manager & m, params_ref const & p); +tactic * mk_auflia_tactic(ast_manager & m, params_ref const & p); +tactic * mk_auflira_tactic(ast_manager & m, params_ref const & p); +tactic * mk_aufnira_tactic(ast_manager & m, params_ref const & p); +tactic * mk_lra_tactic(ast_manager & m, params_ref const & p); + +#endif diff --git a/lib/quasi_macros.cpp b/lib/quasi_macros.cpp new file mode 100644 index 000000000..1b0f0b621 --- /dev/null +++ b/lib/quasi_macros.cpp @@ -0,0 +1,317 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + quasi_macros.cpp + +Abstract: + + + +Author: + + Christoph Wintersteiger (t-cwinte) 2010-04-23 + +Revision History: + +--*/ +#include"quasi_macros.h" +#include"for_each_expr.h" +#include"ast_pp.h" +#include"uint_set.h" +#include"var_subst.h" + +quasi_macros::quasi_macros(ast_manager & m, macro_manager & mm, basic_simplifier_plugin & p, simplifier & s) : + m_manager(m), + m_macro_manager(mm), + m_bsimp(p), + m_simplifier(s), + m_new_vars(m), + m_new_eqs(m), + m_new_qsorts(m) { +} + +quasi_macros::~quasi_macros() { +} + +void quasi_macros::find_occurrences(expr * e) { + unsigned j; + m_todo.reset(); + m_todo.push_back(e); + + // we remember whether we have seen an expr once, or more than once; + // when we see it the second time, we don't have to visit it another time, + // as we are only intersted in finding unique function applications. + m_visited_once.reset(); + m_visited_more.reset(); + + while (!m_todo.empty()) { + expr * cur = m_todo.back(); + m_todo.pop_back(); + + if (m_visited_more.is_marked(cur)) + continue; + + if (m_visited_once.is_marked(cur)) + m_visited_more.mark(cur, true); + + m_visited_once.mark(cur, true); + + switch (cur->get_kind()) { + case AST_VAR: break; + case AST_QUANTIFIER: m_todo.push_back(to_quantifier(cur)->get_expr()); break; + case AST_APP: + if (is_uninterp(cur) && !is_ground(cur)) { + func_decl * f = to_app(cur)->get_decl(); + m_occurrences.insert_if_not_there(f, 0); + occurrences_map::iterator it = m_occurrences.find_iterator(f); + it->m_value++; + } + j = to_app(cur)->get_num_args(); + while (j) + m_todo.push_back(to_app(cur)->get_arg(--j)); + break; + default: UNREACHABLE(); + } + } +}; + +bool quasi_macros::is_unique(func_decl * f) const { + return m_occurrences.find(f) == 1; +} + +struct var_dep_proc { + bit_vector m_bitset; +public: + var_dep_proc(quantifier * q) { m_bitset.resize(q->get_num_decls(), false); } + void operator()(var * n) { m_bitset.set(n->get_idx(), true); } + void operator()(quantifier * n) {} + void operator()(app * n) {} + bool all_used(void) { + for (unsigned i = 0; i < m_bitset.size() ; i++) + if (!m_bitset.get(i)) + return false; + return true; + } +}; + +bool quasi_macros::fully_depends_on(app * a, quantifier * q) const { + // CMW: This checks whether all variables in q are used _somewhere_ deep down in the children of a + + /* var_dep_proc proc(q); + for_each_expr(proc, a); + return proc.all_used(); */ + + // CMW: This code instead checks that all variables appear at least once as a + // direct argument of a, i.e., a->get_arg(i) == v for some i + bit_vector bitset; + bitset.resize(q->get_num_decls(), false); + for (unsigned i = 0 ; i < a->get_num_args() ; i++) { + if (is_var(a->get_arg(i))) + bitset.set(to_var(a->get_arg(i))->get_idx(), true); + } + + for (unsigned i = 0; i < bitset.size() ; i++) { + if (!bitset.get(i)) + return false; + } + + return true; +} + +bool quasi_macros::depends_on(expr * e, func_decl * f) const { + ptr_vector todo; + expr_mark visited; + todo.push_back(e); + while(!todo.empty()) { + expr * cur = todo.back(); + todo.pop_back(); + + if (visited.is_marked(cur)) + continue; + + if (is_app(cur)) { + app * a = to_app(cur); + if (a->get_decl() == f) + return true; + + unsigned j = a->get_num_args(); + while (j>0) + todo.push_back(a->get_arg(--j)); + } + + visited.mark(cur, true); + } + return false; +} + +bool quasi_macros::is_quasi_macro(expr * e, app_ref & a, expr_ref & t) const { + // Our definition of a quasi-macro: + // Forall X. f[X] = T[X], where f[X] is a term starting with symbol f, f is uninterpreted, + // f[X] contains all universally quantified variables, and f does not occur in T[X]. + + if (is_quantifier(e) && to_quantifier(e)->is_forall()) { + quantifier * q = to_quantifier(e); + expr * qe = q->get_expr(); + if ((m_manager.is_eq(qe) || m_manager.is_iff(qe))) { + expr * lhs = to_app(qe)->get_arg(0); + expr * rhs = to_app(qe)->get_arg(1); + + if (is_uninterp(lhs) && is_unique(to_app(lhs)->get_decl()) && + !depends_on(rhs, to_app(lhs)->get_decl()) && fully_depends_on(to_app(lhs), q)) { + a = to_app(lhs); + t = rhs; + return true; + } else if (is_uninterp(rhs) && is_unique(to_app(rhs)->get_decl()) && + !depends_on(lhs, to_app(rhs)->get_decl()) && fully_depends_on(to_app(rhs), q)) { + a = to_app(rhs); + t = lhs; + return true; + } + } else if (m_manager.is_not(qe) && is_uninterp(to_app(qe)->get_arg(0)) && + is_unique(to_app(to_app(qe)->get_arg(0))->get_decl())) { // this is like f(...) = false + a = to_app(to_app(qe)->get_arg(0)); + t = m_manager.mk_false(); + return true; + } else if (is_uninterp(qe) && is_unique(to_app(qe)->get_decl())) { // this is like f(...) = true + a = to_app(qe); + t = m_manager.mk_true(); + return true; + } + } + + return false; +} + +void quasi_macros::quasi_macro_to_macro(quantifier * q, app * a, expr * t, quantifier_ref & macro) { + m_new_var_names.reset(); + m_new_vars.reset(); + m_new_qsorts.reset(); + m_new_eqs.reset(); + + func_decl * f = a->get_decl(); + + // CMW: we rely on the fact that all variables in q appear at least once as + // a direct argument of `a'. + + bit_vector v_seen; + v_seen.resize(q->get_num_decls(), false); + for (unsigned i = 0 ; i < a->get_num_args() ; i++) { + if (!is_var(a->get_arg(i)) || + v_seen.get(to_var(a->get_arg(i))->get_idx())) { + unsigned inx = m_new_var_names.size(); + m_new_name.str(""); + m_new_name << "X" << inx; + m_new_var_names.push_back(symbol(m_new_name.str().c_str())); + m_new_qsorts.push_back(f->get_domain()[i]); + + m_new_vars.push_back(m_manager.mk_var(inx + q->get_num_decls(), f->get_domain()[i])); + m_new_eqs.push_back(m_manager.mk_eq(m_new_vars.back(), a->get_arg(i))); + } else { + var * v = to_var(a->get_arg(i)); + m_new_vars.push_back(v); + v_seen.set(v->get_idx(), true); + } + } + + // Reverse the new variable names and sorts. [CMW: There is a smarter way to do this.] + vector new_var_names_rev; + sort_ref_vector new_qsorts_rev(m_manager); + unsigned i = m_new_var_names.size(); + while (i > 0) { + i--; + new_var_names_rev.push_back(m_new_var_names.get(i)); + new_qsorts_rev.push_back(m_new_qsorts.get(i)); + } + + // We want to keep all the old variables [already reversed] + for (unsigned i = 0 ; i < q->get_num_decls() ; i++) { + new_var_names_rev.push_back(q->get_decl_name(i)); + new_qsorts_rev.push_back(q->get_decl_sort(i)); + } + + // Macro := Forall m_new_vars . appl = ITE( m_new_eqs, t, f_else) + + app_ref appl(m_manager); + expr_ref eq(m_manager); + appl = m_manager.mk_app(f, m_new_vars.size(), m_new_vars.c_ptr()); + + func_decl * fd = m_manager.mk_fresh_func_decl(f->get_name(), symbol("else"), + f->get_arity(), f->get_domain(), + f->get_range()); + expr * f_else = m_manager.mk_app(fd, m_new_vars.size(), m_new_vars.c_ptr()); + + expr_ref ite(m_manager); + ite = m_manager.mk_ite(m_manager.mk_and(m_new_eqs.size(), m_new_eqs.c_ptr()), t, f_else); + + eq = m_manager.mk_eq(appl, ite); + + macro = m_manager.mk_quantifier(true, new_var_names_rev.size(), + new_qsorts_rev.c_ptr(), new_var_names_rev.c_ptr(), eq); +} + +bool quasi_macros::find_macros(unsigned n, expr * const * exprs) { + TRACE("quasi_macros", tout << "Finding quasi-macros in: " << std::endl; + for (unsigned i = 0 ; i < n ; i++) + tout << i << ": " << mk_pp(exprs[i], m_manager) << std::endl; ); + bool res = false; + m_occurrences.reset(); + + + // Find out how many non-ground appearences for each uninterpreted function there are + for ( unsigned i = 0 ; i < n ; i++ ) + find_occurrences(exprs[i]); + + TRACE("quasi_macros", tout << "Occurrences: " << std::endl; + for (occurrences_map::iterator it = m_occurrences.begin(); + it != m_occurrences.end(); + it++) + tout << it->m_key->get_name() << ": " << it->m_value << std::endl; ); + + // Find all macros + for ( unsigned i = 0 ; i < n ; i++ ) { + app_ref a(m_manager); + expr_ref t(m_manager); + if (is_quasi_macro(exprs[i], a, t)) { + quantifier_ref macro(m_manager); + quasi_macro_to_macro(to_quantifier(exprs[i]), a, t, macro); + TRACE("quasi_macros", tout << "Found quasi macro: " << mk_pp(exprs[i], m_manager) << std::endl; + tout << "Macro: " << mk_pp(macro, m_manager) << std::endl; ); + proof * pr = 0; + if (m_manager.proofs_enabled()) + pr = m_manager.mk_def_axiom(macro); + if (m_macro_manager.insert(a->get_decl(), macro, pr)) + res = true; + } + } + + return res; +} + +void quasi_macros::apply_macros(unsigned n, expr * const * exprs, proof * const * prs, expr_ref_vector & new_exprs, proof_ref_vector & new_prs) { + for ( unsigned i = 0 ; i < n ; i++ ) { + expr_ref r(m_manager), rs(m_manager); + proof_ref pr(m_manager), ps(m_manager); + proof * p = m_manager.proofs_enabled() ? prs[i] : 0; + m_macro_manager.expand_macros(exprs[i], p, r, pr); + m_simplifier(r, rs, ps); + new_exprs.push_back(rs); + new_prs.push_back(ps); + } +} + +bool quasi_macros::operator()(unsigned n, expr * const * exprs, proof * const * prs, expr_ref_vector & new_exprs, proof_ref_vector & new_prs) { + if (find_macros(n, exprs)) { + apply_macros(n, exprs, prs, new_exprs, new_prs); + return true; + } else { + // just copy them over + for ( unsigned i = 0 ; i < n ; i++ ) { + new_exprs.push_back(exprs[i]); + if (m_manager.proofs_enabled()) + new_prs.push_back(prs[i]); + } + return false; + } +} diff --git a/lib/quasi_macros.h b/lib/quasi_macros.h new file mode 100644 index 000000000..1731774b2 --- /dev/null +++ b/lib/quasi_macros.h @@ -0,0 +1,69 @@ +/*++ +Copyright (c) 2010 Microsoft Corporation + +Module Name: + + quasi_macros.cpp + +Abstract: + + + +Author: + + Christoph Wintersteiger (t-cwinte) 2010-04-23 + +Revision History: + +--*/ +#ifndef _QUASI_MACROS_H_ +#define _QUASI_MACROS_H_ + +#include +#include"macro_manager.h" +#include"basic_simplifier_plugin.h" +#include"simplifier.h" + +/** + \brief Finds quasi macros and applies them. +*/ +class quasi_macros { + typedef obj_map occurrences_map; + + ast_manager & m_manager; + macro_manager & m_macro_manager; + basic_simplifier_plugin & m_bsimp; + simplifier & m_simplifier; + occurrences_map m_occurrences; + ptr_vector m_todo; + + vector m_new_var_names; + expr_ref_vector m_new_vars; + expr_ref_vector m_new_eqs; + sort_ref_vector m_new_qsorts; + std::stringstream m_new_name; + expr_mark m_visited_once; + expr_mark m_visited_more; + + bool is_unique(func_decl * f) const; + bool fully_depends_on(app * a, quantifier * q) const; + bool depends_on(expr * e, func_decl * f) const; + + bool is_quasi_macro(expr * e, app_ref & a, expr_ref &v) const; + void quasi_macro_to_macro(quantifier * q, app * a, expr * t, quantifier_ref & macro); + + void find_occurrences(expr * e); + bool find_macros(unsigned n, expr * const * exprs); + void apply_macros(unsigned n, expr * const * exprs, proof * const * prs, expr_ref_vector & new_exprs, proof_ref_vector & new_prs); + +public: + quasi_macros(ast_manager & m, macro_manager & mm, basic_simplifier_plugin & p, simplifier & s); + ~quasi_macros(); + + /** + \brief Find pure function macros and apply them. + */ + bool operator()(unsigned n, expr * const * exprs, proof * const * prs, expr_ref_vector & new_exprs, proof_ref_vector & new_prs); +}; + +#endif diff --git a/lib/rational.cpp b/lib/rational.cpp new file mode 100644 index 000000000..0a91bd8da --- /dev/null +++ b/lib/rational.cpp @@ -0,0 +1,41 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + rational.cpp + +Abstract: + + Rational numbers + +Author: + + Leonardo de Moura (leonardo) 2006-09-18. + +Revision History: + +--*/ +#include +#include"util.h" +#include"rational.h" +#ifdef _WINDOWS +#include +#endif + +synch_mpq_manager * rational::g_mpq_manager = 0; +rational rational::m_zero(0); +rational rational::m_one(1); +rational rational::m_minus_one(-1); + +void rational::initialize() { + if (!g_mpq_manager) { + g_mpq_manager = alloc(synch_mpq_manager); + } +} + +void rational::finalize() { + dealloc(g_mpq_manager); + g_mpq_manager = 0; +} + diff --git a/lib/rational.h b/lib/rational.h new file mode 100644 index 000000000..4aa2f5fc6 --- /dev/null +++ b/lib/rational.h @@ -0,0 +1,446 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + rational.h + +Abstract: + + Rational numbers + +Author: + + Leonardo de Moura (leonardo) 2006-09-18. + +Revision History: + +--*/ +#ifndef _RATIONAL_H_ +#define _RATIONAL_H_ + +#include"mpq.h" + +class rational { + mpq m_val; + static rational m_zero; + static rational m_one; + static rational m_minus_one; + + static synch_mpq_manager * g_mpq_manager; + + static synch_mpq_manager & m() { return *g_mpq_manager; } + +public: + static void initialize(); + + static void finalize(); + + rational() {} + + rational(rational const & r) { m().set(m_val, r.m_val); } + + explicit rational(int n) { m().set(m_val, n); } + + explicit rational(unsigned n) { m().set(m_val, n); } + + rational(int n, int d) { m().set(m_val, n, d); } + + rational(mpq const & q) { m().set(m_val, q); } + + rational(mpz const & z) { m().set(m_val, z); } + + explicit rational(char const * v) { m().set(m_val, v); } + + struct i64 {}; + rational(int64 i, i64) { m().set(m_val, i); } + + struct ui64 {}; + rational(uint64 i, ui64) { m().set(m_val, i); } + + ~rational() { m().del(m_val); } + + mpq const & to_mpq() const { return m_val; } + + unsigned bitsize() const { return m().bitsize(m_val); } + + void reset() { m().reset(m_val); } + + bool is_int() const { return m().is_int(m_val); } + + bool is_small() const { return m().is_small(m_val); } + + bool is_big() const { return !is_small(); } + + unsigned hash() const { return m().hash(m_val); } + + struct hash_proc { unsigned operator()(rational const& r) const { return r.hash(); } }; + + struct eq_proc { bool operator()(rational const& r1, rational const& r2) const { return r1 == r2; } }; + + void swap(rational & n) { m().swap(m_val, n.m_val); } + + std::string to_string() const { return m().to_string(m_val); } + + void display(std::ostream & out) const { return m().display(out, m_val); } + + void display_decimal(std::ostream & out, unsigned prec) const { return m().display_decimal(out, m_val, prec); } + + bool is_uint64() const { return m().is_uint64(m_val); } + + bool is_int64() const { return m().is_int64(m_val); } + + uint64 get_uint64() const { return m().get_uint64(m_val); } + + int64 get_int64() const { return m().get_int64(m_val); } + + bool is_unsigned() const { return is_uint64() && (get_uint64() < (1ull << 32)); } + + unsigned get_unsigned() const { + SASSERT(is_unsigned()); + return static_cast(get_uint64()); + } + + bool is_int32() const { + if (is_small()) return true; + // we don't assume that if it is small, then it is int32. + if (!is_int64()) return false; + int64 v = get_int64(); + return INT_MIN <= v && v <= INT_MAX; + } + + double get_double() const { return m().get_double(m_val); } + + rational const & get_rational() const { return *this; } + + rational const & get_infinitesimal() const { return m_zero; } + + rational & operator=(rational const & r) { + m().set(m_val, r.m_val); + return *this; + } + + friend inline rational numerator(rational const & r) { rational result; m().get_numerator(r.m_val, result.m_val); return result; } + + friend inline rational denominator(rational const & r) { rational result; m().get_denominator(r.m_val, result.m_val); return result; } + + rational & operator+=(rational const & r) { + m().add(m_val, r.m_val, m_val); + return *this; + } + + rational & operator-=(rational const & r) { + m().sub(m_val, r.m_val, m_val); + return *this; + } + + rational & operator*=(rational const & r) { + m().mul(m_val, r.m_val, m_val); + return *this; + } + + rational & operator/=(rational const & r) { + m().div(m_val, r.m_val, m_val); + return *this; + } + + rational & operator%=(rational const & r) { + m().rem(m_val, r.m_val, m_val); + return *this; + } + + friend inline rational div(rational const & r1, rational const & r2) { + rational r; + rational::m().idiv(r1.m_val, r2.m_val, r.m_val); + return r; + } + + friend inline void div(rational const & r1, rational const & r2, rational & r) { + rational::m().idiv(r1.m_val, r2.m_val, r.m_val); + } + + friend inline rational machine_div(rational const & r1, rational const & r2) { + rational r; + rational::m().machine_idiv(r1.m_val, r2.m_val, r.m_val); + return r; + } + + friend inline rational mod(rational const & r1, rational const & r2) { + rational r; + rational::m().mod(r1.m_val, r2.m_val, r.m_val); + return r; + } + + friend inline void mod(rational const & r1, rational const & r2, rational & r) { + rational::m().mod(r1.m_val, r2.m_val, r.m_val); + } + + friend inline rational operator%(rational const & r1, rational const & r2) { + rational r; + rational::m().rem(r1.m_val, r2.m_val, r.m_val); + return r; + } + + friend inline rational mod_hat(rational const & a, rational const & b) { + SASSERT(b.is_pos()); + rational r = mod(a,b); + SASSERT(r.is_nonneg()); + rational r2 = r; + r2 *= rational(2); + if (operator<(b, r2)) { + r -= b; + } + return r; + } + + rational & operator++() { + m().add(m_val, m().mk_q(1), m_val); + return *this; + } + + const rational operator++(int) { rational tmp(*this); ++(*this); return tmp; } + + rational & operator--() { + m().sub(m_val, m().mk_q(1), m_val); + return *this; + } + + const rational operator--(int) { rational tmp(*this); --(*this); return tmp; } + + friend inline bool operator==(rational const & r1, rational const & r2) { + return rational::m().eq(r1.m_val, r2.m_val); + } + + friend inline bool operator<(rational const & r1, rational const & r2) { + return rational::m().lt(r1.m_val, r2.m_val); + } + + void neg() { + m().neg(m_val); + } + + bool is_zero() const { + return m().is_zero(m_val); + } + + bool is_one() const { + return m().is_one(m_val); + } + + bool is_minus_one() const { + return m().is_minus_one(m_val); + } + + bool is_neg() const { + return m().is_neg(m_val); + } + + bool is_pos() const { + return m().is_pos(m_val); + } + + bool is_nonneg() const { + return m().is_nonneg(m_val); + } + + bool is_nonpos() const { + return m().is_nonpos(m_val); + } + + bool is_even() const { + return m().is_even(m_val); + } + + friend inline rational floor(rational const & r) { + rational f; + rational::m().floor(r.m_val, f.m_val); + return f; + } + + friend inline rational ceil(rational const & r) { + rational f; + rational::m().ceil(r.m_val, f.m_val); + return f; + } + + rational expt(int n) const { + rational result; + m().power(m_val, n, result.m_val); + return result; + } + + bool is_power_of_two(unsigned & shift) { + return m().is_power_of_two(m_val, shift); + } + + static rational const & zero() { + return m_zero; + } + + static rational const & one() { + return m_one; + } + + static rational const & minus_one() { + return m_minus_one; + } + + void addmul(rational const & c, rational const & k) { + if (c.is_one()) + operator+=(k); + else if (c.is_minus_one()) + operator-=(k); + else { + rational tmp(k); + tmp *= c; + operator+=(tmp); + } + } + + // Perform: this -= c * k + void submul(const rational & c, const rational & k) { + if (c.is_one()) + operator-=(k); + else if (c.is_minus_one()) + operator+=(k); + else { + rational tmp(k); + tmp *= c; + operator-=(tmp); + } + } + + bool is_int_perfect_square(rational & root) const { + return m().is_int_perfect_square(m_val, root.m_val); + } + + bool is_perfect_square(rational & root) const { + return m().is_perfect_square(m_val, root.m_val); + } + + bool root(unsigned n, rational & root) const { + return m().root(m_val, n, root.m_val); + } + + friend inline std::ostream & operator<<(std::ostream & target, rational const & r) { + target << m().to_string(r.m_val); + return target; + } + + friend inline rational gcd(rational const & r1, rational const & r2) { + rational result; + m().gcd(r1.m_val, r2.m_val, result.m_val); + return result; + } + + // + // extended Euclid: + // r1*a + r2*b = gcd + // + friend inline rational gcd(rational const & r1, rational const & r2, rational & a, rational & b) { + rational result; + m().gcd(r1.m_val, r2.m_val, a.m_val, b.m_val, result.m_val); + return result; + } + + + friend inline rational lcm(rational const & r1, rational const & r2) { + rational result; + m().lcm(r1.m_val, r2.m_val, result.m_val); + return result; + } + + friend inline rational bitwise_or(rational const & r1, rational const & r2) { + rational result; + m().bitwise_or(r1.m_val, r2.m_val, result.m_val); + return result; + } + + friend inline rational bitwise_and(rational const & r1, rational const & r2) { + rational result; + m().bitwise_and(r1.m_val, r2.m_val, result.m_val); + return result; + } + + friend inline rational bitwise_xor(rational const & r1, rational const & r2) { + rational result; + m().bitwise_xor(r1.m_val, r2.m_val, result.m_val); + return result; + } + + friend inline rational bitwise_not(unsigned sz, rational const & r1) { + rational result; + m().bitwise_not(sz, r1.m_val, result.m_val); + return result; + } + + friend inline rational abs(rational const & r) { + rational result(r); + m().abs(result.m_val); + return result; + } + + rational to_rational() const { return *this; } + + static bool is_rational() { return true; } + + unsigned get_num_bits() const { + rational two(2); + SASSERT(is_int()); + SASSERT(!is_neg()); + rational n(*this); + unsigned num_bits = 1; + n = div(n, two); + while (n.is_pos()) { + ++num_bits; + n = div(n, two); + } + return num_bits; + } + +}; + +inline bool operator!=(rational const & r1, rational const & r2) { + return !operator==(r1, r2); +} + +inline bool operator>(rational const & r1, rational const & r2) { + return operator<(r2, r1); +} + +inline bool operator<=(rational const & r1, rational const & r2) { + return !operator>(r1, r2); +} + +inline bool operator>=(rational const & r1, rational const & r2) { + return !operator<(r1, r2); +} + +inline rational operator+(rational const & r1, rational const & r2) { + return rational(r1) += r2; +} + +inline rational operator-(rational const & r1, rational const & r2) { + return rational(r1) -= r2; +} + +inline rational operator-(rational const & r) { + rational result(r); + result.neg(); + return result; +} + +inline rational operator*(rational const & r1, rational const & r2) { + return rational(r1) *= r2; +} + +inline rational operator/(rational const & r1, rational const & r2) { + return rational(r1) /= r2; +} + +inline rational power(rational const & r, unsigned p) { + return r.expt(p); +} + +#endif /* _RATIONAL_H_ */ + diff --git a/lib/recover_01_tactic.cpp b/lib/recover_01_tactic.cpp new file mode 100644 index 000000000..76f2938d8 --- /dev/null +++ b/lib/recover_01_tactic.cpp @@ -0,0 +1,446 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + recover_01_tactic.cpp + +Abstract: + + Recover 01 variables + + Search for clauses of the form + p or q or x = 0 + ~p or q or x = k1 + p or ~q or x = k2 + ~p or ~q or x = k1+k2 + + Then, replaces + x with k1*y1 + k2*y2 + p with y1=1 + q with y2=1 + where y1 and y2 are fresh 01 variables + + The clauses are also removed. + +Author: + + Leonardo de Moura (leonardo) 2012-02-17. + +Revision History: + +--*/ +#include"tactical.h" +#include"th_rewriter.h" +#include"extension_model_converter.h" +#include"filter_model_converter.h" +#include"arith_decl_plugin.h" +#include"expr_substitution.h" +#include"dec_ref_util.h" +#include"ast_smt2_pp.h" + +class recover_01_tactic : public tactic { + struct imp { + typedef obj_map > var2clauses; + + ast_manager & m; + var2clauses m_var2clauses; + arith_util m_util; + th_rewriter m_rw; + bool m_produce_models; + unsigned m_cls_max_size; + + imp(ast_manager & _m, params_ref const & p): + m(_m), + m_util(m), + m_rw(m, p) { + updt_params_core(p); + } + + void updt_params_core(params_ref const & p) { + m_cls_max_size = p.get_uint(":recover-01-max-bits", 10); + } + + void updt_params(params_ref const & p) { + m_rw.updt_params(p); + updt_params_core(p); + } + + void set_cancel(bool f) { + m_rw.set_cancel(f); + } + + bool save_clause(expr * c) { + if (!m.is_or(c)) + return false; + func_decl * x = 0; + app * cls = to_app(c); + if (cls->get_num_args() <= 1 || cls->get_num_args() >= m_cls_max_size) + return false; + unsigned sz = cls->get_num_args(); + for (unsigned i = 0; i < sz; i++) { + expr * lit = cls->get_arg(i); + expr * lhs, * rhs, * arg; + if (is_uninterp_const(lit)) { + // positive literal + } + else if (m.is_not(lit, arg) && is_uninterp_const(arg)) { + // negative literal + } + else if (x == 0 && m.is_eq(lit, lhs, rhs)) { + // x = k literal + if (is_uninterp_const(lhs) && m_util.is_numeral(rhs)) { + x = to_app(lhs)->get_decl(); + } + else if (is_uninterp_const(rhs) && m_util.is_numeral(lhs)) { + x = to_app(rhs)->get_decl(); + } + else { + return false; + } + } + else { + return false; + } + } + + if (x != 0) { + var2clauses::obj_map_entry * entry = m_var2clauses.insert_if_not_there2(x, ptr_vector()); + if (entry->get_data().m_value.empty() || entry->get_data().m_value.back()->get_num_args() == cls->get_num_args()) { + entry->get_data().m_value.push_back(cls); + return true; + } + } + return false; + } + + // temporary fields used by operator() and process + extension_model_converter * mc1; + filter_model_converter * mc2; + expr_substitution * subst; + goal_ref new_goal; + obj_map bool2int; + + app * find_zero_cls(func_decl * x, ptr_vector & clauses) { + ptr_vector::iterator it = clauses.begin(); + ptr_vector::iterator end = clauses.end(); + for (; it != end; ++it) { + app * cls = *it; + unsigned num = cls->get_num_args(); + for (unsigned i = 0; i < num; i++) { + expr * lhs, * rhs; + if (m.is_eq(cls->get_arg(i), lhs, rhs)) { + if (is_uninterp_const(lhs) && m_util.is_zero(rhs)) + return cls; + if (is_uninterp_const(rhs) && m_util.is_zero(lhs)) + return cls; + } + } + } + return 0; + } + + // Find coeff (the k of literal (x = k)) of clause cls. + // Store in idx the bit-vector representing the literals. + // Example: idx = 101 if cls has three boolean literals p1, p2, p3 + // where p1 = ~q1, p2 = q2, p3 = ~q3 + // and q1 q2 q3 are the corresponding literals in the + // zero clause. + // Return false, if the boolean literals of cls cannot be matched with the literals + // of zero_cls + bool find_coeff(app * cls, app * zero_cls, unsigned & idx, rational & k) { + unsigned num = zero_cls->get_num_args(); + if (cls->get_num_args() != num) + return false; + idx = 0; + unsigned val = 1; + for (unsigned i = 0; i < num; i++) { + expr * lit = zero_cls->get_arg(i); + if (m.is_eq(lit)) + continue; + // search for lit or ~lit in cls + unsigned j; + for (j = 0; j < num; j++) { + expr * lit2 = cls->get_arg(j); + if (m.is_eq(lit2)) + continue; + if (lit2 == lit) + break; + if (m.is_complement(lit2, lit)) { + idx += val; + break; + } + } + if (j == num) + return false; // cls does not contain literal lit + val *= 2; + } + + // find k + unsigned i; + for (i = 0; i < num; i++) { + expr * lhs, * rhs; + if (m.is_eq(cls->get_arg(i), lhs, rhs) && (m_util.is_numeral(lhs, k) || m_util.is_numeral(rhs, k))) + break; + } + if (i == num) + return false; + + return true; + } + + void mk_ivar(expr * lit, expr_ref & def, bool real_ctx) { + expr * atom; + bool sign; + if (m.is_not(lit, atom)) { + sign = true; + } + else { + atom = lit; + sign = false; + } + SASSERT(is_uninterp_const(atom)); + expr * var; + if (!bool2int.find(atom, var)) { + var = m.mk_fresh_const(0, m_util.mk_int()); + new_goal->assert_expr(m_util.mk_le(m_util.mk_numeral(rational(0), true), var)); + new_goal->assert_expr(m_util.mk_le(var, m_util.mk_numeral(rational(1), true))); + expr * bool_def = m.mk_eq(var, m_util.mk_numeral(rational(1), true)); + subst->insert(atom, bool_def); + if (m_produce_models) { + mc2->insert(to_app(var)->get_decl()); + mc1->insert(to_app(atom)->get_decl(), bool_def); + } + m.inc_ref(atom); + m.inc_ref(var); + bool2int.insert(atom, var); + } + expr * norm_var = real_ctx ? m_util.mk_to_real(var) : var; + if (sign) + def = m_util.mk_sub(m_util.mk_numeral(rational(1), !real_ctx), norm_var); + else + def = norm_var; + } + + bool process(func_decl * x, ptr_vector & clauses) { + unsigned cls_size = clauses.back()->get_num_args(); + unsigned expected_num_clauses = 1 << (cls_size - 1); + if (clauses.size() < expected_num_clauses) // using < instead of != because we tolerate duplicates + return false; + app * zero_cls = find_zero_cls(x, clauses); + if (zero_cls == 0) + return false; + + buffer found; // marks which idx were found + buffer idx2coeff; + found.resize(expected_num_clauses, false); + idx2coeff.resize(expected_num_clauses); + + ptr_vector::iterator it = clauses.begin(); + ptr_vector::iterator end = clauses.end(); + for (; it != end; ++it) { + app * cls = *it; + unsigned idx; rational k; + if (!find_coeff(cls, zero_cls, idx, k)) + return false; + SASSERT(idx < expected_num_clauses); + if (found[idx] && k != idx2coeff[idx]) + return false; + found[idx] = true; + idx2coeff[idx] = k; + } + + unsigned num_bits = cls_size - 1; + // check if idxs are consistent + for (unsigned idx = 0; idx < expected_num_clauses; idx++) { + if (!found[idx]) + return false; // case is missing + rational expected_k; + unsigned idx_aux = idx; + unsigned idx_bit = 1; + for (unsigned j = 0; j < num_bits; j++) { + if (idx_aux % 2 == 1) { + expected_k += idx2coeff[idx_bit]; + } + idx_aux /= 2; + idx_bit *= 2; + } + if (idx2coeff[idx] != expected_k) + return false; + } + + expr_ref_buffer def_args(m); + expr_ref def(m); + bool real_ctx = m_util.is_real(x->get_range()); + unsigned idx_bit = 1; + for (unsigned i = 0; i < cls_size; i++) { + expr * lit = zero_cls->get_arg(i); + if (m.is_eq(lit)) + continue; + mk_ivar(lit, def, real_ctx); + def_args.push_back(m_util.mk_mul(m_util.mk_numeral(idx2coeff[idx_bit], !real_ctx), def)); + idx_bit *= 2; + } + + expr * x_def; + if (def_args.size() == 1) + x_def = def_args[0]; + else + x_def = m_util.mk_add(def_args.size(), def_args.c_ptr()); + + TRACE("recover_01", tout << x->get_name() << " --> " << mk_ismt2_pp(x_def, m) << "\n";); + subst->insert(m.mk_const(x), x_def); + if (m_produce_models) { + mc1->insert(x, x_def); + } + return true; + } + + void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + fail_if_proof_generation("recover-01", g); + fail_if_unsat_core_generation("recover-01", g); + m_produce_models = g->models_enabled(); + mc = 0; pc = 0; core = 0; result.reset(); + tactic_report report("recover-01", *g); + + bool saved = false; + new_goal = alloc(goal, *g, true); + SASSERT(new_goal->depth() == g->depth()); + SASSERT(new_goal->prec() == g->prec()); + new_goal->inc_depth(); + + unsigned sz = g->size(); + for (unsigned i = 0; i < sz; i++) { + expr * f = g->form(i); + if (save_clause(f)) { + saved = true; + } + else { + new_goal->assert_expr(f); + } + } + + if (!saved) { + result.push_back(g.get()); + return; + } + + if (m_produce_models) { + mc1 = alloc(extension_model_converter, m); + mc2 = alloc(filter_model_converter, m); + mc = concat(mc2, mc1); + } + + dec_ref_key_values(m, bool2int); + + unsigned counter = 0; + bool recovered = false; + expr_substitution _subst(m); + subst = &_subst; + var2clauses::iterator it = m_var2clauses.begin(); + var2clauses::iterator end = m_var2clauses.end(); + for (; it != end; ++it) { + if (process(it->m_key, it->m_value)) { + recovered = true; + counter++; + } + else { + ptr_vector::iterator it2 = it->m_value.begin(); + ptr_vector::iterator end2 = it->m_value.end(); + for (; it2 != end2; ++it2) { + new_goal->assert_expr(*it2); + } + } + } + + if (!recovered) { + result.push_back(g.get()); + mc = 0; + return; + } + + report_tactic_progress(":recovered-01-vars", counter); + + m_rw.set_substitution(subst); + expr_ref new_curr(m); + proof_ref new_pr(m); + unsigned size = new_goal->size(); + for (unsigned idx = 0; idx < size; idx++) { + expr * curr = new_goal->form(idx); + m_rw(curr, new_curr); + new_goal->update(idx, new_curr); + } + result.push_back(new_goal.get()); + TRACE("recover_01", new_goal->display(tout);); + SASSERT(new_goal->is_well_sorted()); + } + + ~imp() { + dec_ref_key_values(m, bool2int); + } + }; + + imp * m_imp; + params_ref m_params; +public: + recover_01_tactic(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(recover_01_tactic, m, m_params); + } + + virtual ~recover_01_tactic() { + dealloc(m_imp); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + th_rewriter::get_param_descrs(r); + r.insert(":recover-01-max-bits", CPK_UINT, "(default: 10) maximum number of bits to consider in a clause."); + } + + void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + (*m_imp)(g, result, mc, pc, core); + } + + virtual void cleanup() { + ast_manager & m = m_imp->m; + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + d = m_imp; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + } + +protected: + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + +tactic * mk_recover_01_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(recover_01_tactic, m, p)); +} diff --git a/lib/recover_01_tactic.h b/lib/recover_01_tactic.h new file mode 100644 index 000000000..7962fb342 --- /dev/null +++ b/lib/recover_01_tactic.h @@ -0,0 +1,42 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + recover_01_tactic.h + +Abstract: + + Recover 01 variables + + Search for clauses of the form + p or q or x = 0 + ~p or q or x = k1 + p or ~q or x = k2 + ~p or ~q or x = k1+k2 + + Then, replaces + x with k1*y1 + k2*y2 + p with y1=1 + q with y2=1 + where y1 and y2 are fresh 01 variables + + The clauses are also removed. + +Author: + + Leonardo de Moura (leonardo) 2012-02-17. + +Revision History: + +--*/ +#ifndef _RECOVER_01_TACTIC_H_ +#define _RECOVER_01_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_recover_01_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/recurse_expr.h b/lib/recurse_expr.h new file mode 100644 index 000000000..9cb71872b --- /dev/null +++ b/lib/recurse_expr.h @@ -0,0 +1,47 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + recurse_expr.h + +Abstract: + + Traverse an expression applying a visitor. + +Author: + + Leonardo de Moura (leonardo) 2008-01-11. + +Revision History: + +--*/ +#ifndef _RECURSE_EXPR_H_ +#define _RECURSE_EXPR_H_ + +#include"ast.h" +#include"obj_hashtable.h" + +template +class recurse_expr : public Visitor { + obj_map m_cache; + ptr_vector m_todo; + vector m_results1; + vector m_results2; + + bool is_cached(expr * n) const { T c; return m_cache.find(n, c); } + T get_cached(expr * n) const { T c; m_cache.find(n, c); return c; } + void cache_result(expr * n, T c) { m_cache.insert(n, c); } + + void visit(expr * n, bool & visited); + bool visit_children(expr * n); + void process(expr * n); + +public: + recurse_expr(Visitor const & v = Visitor()):Visitor(v) {} + T operator()(expr * n); + void reset() { m_cache.reset(); m_todo.reset(); } + void finalize() { m_cache.finalize(); m_todo.finalize(); } +}; + +#endif /* _RECURSE_EXPR_H_ */ diff --git a/lib/recurse_expr_def.h b/lib/recurse_expr_def.h new file mode 100644 index 000000000..7638668a2 --- /dev/null +++ b/lib/recurse_expr_def.h @@ -0,0 +1,109 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + recurse_expr_def.h + +Abstract: + + Traverse an expression applying a visitor. + +Author: + + Leonardo de Moura (leonardo) 2008-01-11. + +Revision History: + +--*/ +#ifndef _RECURSE_EXPR_DEF_H_ +#define _RECURSE_EXPR_DEF_H_ + +#include"recurse_expr.h" + +template +inline void recurse_expr::visit(expr * n, bool & visited) { + if (!is_cached(n)) { + m_todo.push_back(n); + visited = false; + } +} + +template +bool recurse_expr::visit_children(expr * n) { + bool visited = true; + unsigned num; + switch (n->get_kind()) { + case AST_APP: + num = to_app(n)->get_num_args(); + for (unsigned j = 0; j < num; j++) + visit(to_app(n)->get_arg(j), visited); + break; + case AST_QUANTIFIER: + if (!IgnorePatterns) { + num = to_quantifier(n)->get_num_patterns(); + for (unsigned j = 0; j < num; j++) + visit(to_quantifier(n)->get_pattern(j), visited); + num = to_quantifier(n)->get_num_no_patterns(); + for (unsigned j = 0; j < num; j++) + visit(to_quantifier(n)->get_no_pattern(j), visited); + } + visit(to_quantifier(n)->get_expr(), visited); + break; + default: + break; + } + return visited; +} + +template +void recurse_expr::process(expr * n) { + unsigned num; + switch (n->get_kind()) { + case AST_APP: + m_results1.reset(); + num = to_app(n)->get_num_args(); + for (unsigned j = 0; j < num; j++) + m_results1.push_back(get_cached(to_app(n)->get_arg(j))); + cache_result(n, this->Visitor::visit(to_app(n), m_results1.c_ptr())); + break; + case AST_VAR: + cache_result(n, this->Visitor::visit(to_var(n))); + break; + case AST_QUANTIFIER: + if (IgnorePatterns) { + cache_result(n, this->Visitor::visit(to_quantifier(n), get_cached(to_quantifier(n)->get_expr()), 0, 0)); + } + else { + m_results1.reset(); + m_results2.reset(); + num = to_quantifier(n)->get_num_patterns(); + for (unsigned j = 0; j < num; j++) + m_results1.push_back(get_cached(to_quantifier(n)->get_pattern(j))); + num = to_quantifier(n)->get_num_no_patterns(); + for (unsigned j = 0; j < num; j++) + m_results2.push_back(get_cached(to_quantifier(n)->get_no_pattern(j))); + cache_result(n, this->Visitor::visit(to_quantifier(n), get_cached(to_quantifier(n)->get_expr()), m_results1.c_ptr(), m_results2.c_ptr())); + } + break; + default: + UNREACHABLE(); + } +} + +template +T recurse_expr::operator()(expr * r) { + m_todo.push_back(r); + while (!m_todo.empty()) { + expr * n = m_todo.back(); + if (is_cached(n)) + m_todo.pop_back(); + else if (visit_children(n)) { + m_todo.pop_back(); + process(n); + } + } + return get_cached(r); +} + +#endif /* _RECURSE_EXPR_DEF_H_ */ diff --git a/lib/reduce_args.cpp b/lib/reduce_args.cpp new file mode 100644 index 000000000..d6e2a3516 --- /dev/null +++ b/lib/reduce_args.cpp @@ -0,0 +1,521 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + reduce_args.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-04-06. + +Revision History: + +--*/ +#include"reduce_args.h" +#include"cooperate.h" +#include"ast_smt2_pp.h" +#include"map.h" +#include"rewriter_def.h" +#include"elim_var_model_converter.h" +#include"filter_model_converter.h" + +struct reduce_args::imp { + ast_manager & m_manager; + bool m_produce_models; + volatile bool m_cancel; + + ast_manager & m() const { return m_manager; } + + imp(ast_manager & m, params_ref const & p): + m_manager(m) { + updt_params(p); + m_cancel = false; + } + + void updt_params(params_ref const & p) { + m_produce_models = p.get_bool(":produce-models", false); + } + + void set_cancel(bool f) { + m_cancel = f; + } + + void checkpoint() { + if (m_cancel) + throw reduce_args_exception(STE_CANCELED_MSG); + cooperate("reduce-args"); + } + + struct find_non_candidates_proc { + ast_manager & m_manager; + obj_hashtable & m_non_cadidates; + + find_non_candidates_proc(ast_manager & m, obj_hashtable & non_cadidates): + m_manager(m), + m_non_cadidates(non_cadidates) { + } + + void operator()(var * n) {} + + void operator()(quantifier * n) {} + + void operator()(app * n) { + if (n->get_num_args() == 0) + return; // ignore constants + func_decl * d = n->get_decl(); + if (d->get_family_id() != null_family_id) + return; // ignore interpreted symbols + if (m_non_cadidates.contains(d)) + return; // it is already in the set. + unsigned j = n->get_num_args(); + while (j > 0) { + --j; + if (m_manager.is_value(n->get_arg(j))) + return; + } + m_non_cadidates.insert(d); + } + }; + + /** + \brief Populate the table non_cadidates with function declarations \c f + such that there is a function application (f t1 ... tn) where t1 ... tn are not values. + */ + void find_non_candidates(assertion_set const & s, obj_hashtable & non_candidates) { + non_candidates.reset(); + find_non_candidates_proc proc(m_manager, non_candidates); + expr_fast_mark1 visited; + unsigned sz = s.size(); + for (unsigned i = 0; i < sz; i++) { + checkpoint(); + quick_for_each_expr(proc, visited, s.form(i)); + } + + TRACE("reduce_args", tout << "non_candidates:\n"; + obj_hashtable::iterator it = non_candidates.begin(); + obj_hashtable::iterator end = non_candidates.end(); + for (; it != end; ++it) { + func_decl * d = *it; + tout << d->get_name() << "\n"; + }); + } + + struct populate_decl2args_proc { + ast_manager & m_manager; + obj_hashtable & m_non_cadidates; + obj_map & m_decl2args; + + populate_decl2args_proc(ast_manager & m, obj_hashtable & nc, obj_map & d): + m_manager(m), m_non_cadidates(nc), m_decl2args(d) {} + + void operator()(var * n) {} + void operator()(quantifier * n) {} + void operator()(app * n) { + if (n->get_num_args() == 0) + return; // ignore constants + func_decl * d = n->get_decl(); + if (d->get_family_id() != null_family_id) + return; // ignore interpreted symbols + if (m_non_cadidates.contains(d)) + return; // declaration is not a candidate + unsigned j = n->get_num_args(); + obj_map::iterator it = m_decl2args.find_iterator(d); + if (it == m_decl2args.end()) { + m_decl2args.insert(d, bit_vector()); + it = m_decl2args.find_iterator(d); + SASSERT(it != m_decl2args.end()); + it->m_value.reserve(j); + while (j > 0) { + --j; + it->m_value.set(j, m_manager.is_value(n->get_arg(j))); + } + } else { + SASSERT(j == it->m_value.size()); + while (j > 0) { + --j; + it->m_value.set(j, it->m_value.get(j) && m_manager.is_value(n->get_arg(j))); + } + } + } + }; + + void populate_decl2args(assertion_set const & s, + obj_hashtable & non_candidates, + obj_map & decl2args) { + expr_fast_mark1 visited; + decl2args.reset(); + populate_decl2args_proc proc(m_manager, non_candidates, decl2args); + unsigned sz = s.size(); + for (unsigned i = 0; i < sz; i++) { + checkpoint(); + quick_for_each_expr(proc, visited, s.form(i)); + } + + // Remove all cases where the simplification is not applicable. + ptr_buffer bad_decls; + obj_map::iterator it = decl2args.begin(); + obj_map::iterator end = decl2args.end(); + for (; it != end; it++) { + bool is_zero = true; + for (unsigned i = 0; i < it->m_value.size() && is_zero ; i++) { + if (it->m_value.get(i)) + is_zero = false; + } + if (is_zero) + bad_decls.push_back(it->m_key); + } + + ptr_buffer::iterator it2 = bad_decls.begin(); + ptr_buffer::iterator end2 = bad_decls.end(); + for (; it2 != end2; ++it2) + decl2args.erase(*it2); + + TRACE("reduce_args", tout << "decl2args:" << std::endl; + for (obj_map::iterator it = decl2args.begin() ; it != decl2args.end() ; it++) { + tout << it->m_key->get_name() << ": "; + for (unsigned i = 0 ; i < it->m_value.size() ; i++) + tout << (it->m_value.get(i) ? "1" : "0"); + tout << std::endl; + }); + } + + struct arg2func_hash_proc { + bit_vector const & m_bv; + + arg2func_hash_proc(bit_vector const & bv):m_bv(bv) {} + unsigned operator()(app const * n) const { + // compute the hash-code using only the arguments where m_bv is true. + unsigned a = 0x9e3779b9; + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + if (!m_bv.get(i)) + continue; // ignore argument + a = hash_u_u(a, n->get_arg(i)->get_id()); + } + return a; + } + }; + + struct arg2func_eq_proc { + bit_vector const & m_bv; + + arg2func_eq_proc(bit_vector const & bv):m_bv(bv) {} + bool operator()(app const * n1, app const * n2) const { + // compare only the arguments where m_bv is true + SASSERT(n1->get_num_args() == n2->get_num_args()); + unsigned num_args = n1->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + if (!m_bv.get(i)) + continue; // ignore argument + if (n1->get_arg(i) != n2->get_arg(i)) + return false; + } + return true; + } + }; + + typedef map arg2func; + typedef obj_map decl2arg2func_map; + + struct reduce_args_ctx { + ast_manager & m_manager; + decl2arg2func_map m_decl2arg2funcs; + + reduce_args_ctx(ast_manager & m): m_manager(m) { + } + + ~reduce_args_ctx() { + obj_map::iterator it = m_decl2arg2funcs.begin(); + obj_map::iterator end = m_decl2arg2funcs.end(); + for (; it != end; ++it) { + arg2func * map = it->m_value; + arg2func::iterator it2 = map->begin(); + arg2func::iterator end2 = map->end(); + for (; it2 != end2; ++it2) { + m_manager.dec_ref(it2->m_key); + m_manager.dec_ref(it2->m_value); + } + dealloc(map); + } + } + }; + + struct populate_decl2arg_set_proc { + ast_manager & m_manager; + obj_map & m_decl2args; + decl2arg2func_map & m_decl2arg2funcs; + + populate_decl2arg_set_proc(ast_manager & m, + obj_map & d, + decl2arg2func_map & ds): + m_manager(m), m_decl2args(d), m_decl2arg2funcs(ds) {} + + void operator()(var * n) {} + void operator()(quantifier * n) {} + + void operator()(app * n) { + if (n->get_num_args() == 0) + return; // ignore constants + func_decl * d = n->get_decl(); + if (d->get_family_id() != null_family_id) + return; // ignore interpreted symbols + obj_map::iterator it = m_decl2args.find_iterator(d); + if (it == m_decl2args.end()) + return; // not reducing the arguments of this declaration + bit_vector & bv = it->m_value; + arg2func * map = 0; + decl2arg2func_map::iterator it2 = m_decl2arg2funcs.find_iterator(d); + if (it2 == m_decl2arg2funcs.end()) { + map = alloc(arg2func, arg2func_hash_proc(bv), arg2func_eq_proc(bv)); + m_decl2arg2funcs.insert(d, map); + } + else { + map = it2->m_value; + } + if (!map->contains(n)) { + // create fresh symbol... + ptr_buffer domain; + unsigned arity = d->get_arity(); + for (unsigned i = 0; i < arity; i++) { + if (!bv.get(i)) + domain.push_back(d->get_domain(i)); + } + func_decl * new_d = m_manager.mk_fresh_func_decl(d->get_name(), symbol::null, domain.size(), domain.c_ptr(), d->get_range()); + map->insert(n, new_d); + m_manager.inc_ref(n); + m_manager.inc_ref(new_d); + } + } + }; + + void populate_decl2arg_set(assertion_set const & s, + obj_map & decl2args, + decl2arg2func_map & decl2arg2funcs) { + expr_fast_mark1 visited; + + populate_decl2arg_set_proc proc(m_manager, decl2args, decl2arg2funcs); + unsigned sz = s.size(); + for (unsigned i = 0; i < sz; i++) { + checkpoint(); + quick_for_each_expr(proc, visited, s.form(i)); + } + } + + struct reduce_args_rw_cfg : public default_rewriter_cfg { + ast_manager & m; + imp & m_owner; + obj_map & m_decl2args; + decl2arg2func_map & m_decl2arg2funcs; + + reduce_args_rw_cfg(imp & owner, obj_map & decl2args, decl2arg2func_map & decl2arg2funcs): + m(owner.m_manager), + m_owner(owner), + m_decl2args(decl2args), + m_decl2arg2funcs(decl2arg2funcs) { + } + + bool max_steps_exceeded(unsigned num_steps) const { + m_owner.checkpoint(); + return false; + } + + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + result_pr = 0; + if (f->get_arity() == 0) + return BR_FAILED; // ignore constants + if (f->get_family_id() != null_family_id) + return BR_FAILED; // ignore interpreted symbols + decl2arg2func_map::iterator it = m_decl2arg2funcs.find_iterator(f); + if (it == m_decl2arg2funcs.end()) + return BR_FAILED; + SASSERT(m_decl2args.contains(f)); + bit_vector & bv = m_decl2args.find(f); + arg2func * map = it->m_value; + app_ref tmp(m); + tmp = m.mk_app(f, num, args); + CTRACE("reduce_args", !map->contains(tmp), + tout << "map does not contain tmp f: " << f->get_name() << "\n"; + tout << mk_ismt2_pp(tmp, m) << "\n"; + arg2func::iterator it = map->begin(); + arg2func::iterator end = map->end(); + for (; it != end; ++it) { + tout << mk_ismt2_pp(it->m_key, m) << "\n---> " << it->m_value->get_name() << "\n"; + }); + SASSERT(map->contains(tmp)); + func_decl * new_f = map->find(tmp); + ptr_buffer new_args; + for (unsigned i = 0; i < num; i++) { + if (!bv.get(i)) + new_args.push_back(args[i]); + } + result = m.mk_app(new_f, new_args.size(), new_args.c_ptr()); + return BR_DONE; + } + }; + + struct reduce_args_rw : rewriter_tpl { + reduce_args_rw_cfg m_cfg; + public: + reduce_args_rw(imp & owner, obj_map & decl2args, decl2arg2func_map & decl2arg2funcs): + rewriter_tpl(owner.m_manager, false, m_cfg), + m_cfg(owner, decl2args, decl2arg2funcs) { + } + }; + + model_converter * mk_mc(obj_map & decl2args, decl2arg2func_map & decl2arg2funcs) { + ptr_buffer new_args; + var_ref_vector new_vars(m_manager); + ptr_buffer new_eqs; + elim_var_model_converter * e_mc = alloc(elim_var_model_converter, m_manager); + filter_model_converter * f_mc = alloc(filter_model_converter, m_manager); + decl2arg2func_map::iterator it = decl2arg2funcs.begin(); + decl2arg2func_map::iterator end = decl2arg2funcs.end(); + for (; it != end; ++it) { + func_decl * f = it->m_key; + arg2func * map = it->m_value; + expr * def = 0; + SASSERT(decl2args.contains(f)); + bit_vector & bv = decl2args.find(f); + new_vars.reset(); + new_args.reset(); + for (unsigned i = 0; i < f->get_arity(); i++) { + new_vars.push_back(m_manager.mk_var(i, f->get_domain(i))); + if (!bv.get(i)) + new_args.push_back(new_vars.back()); + } + arg2func::iterator it2 = map->begin(); + arg2func::iterator end2 = map->end(); + for (; it2 != end2; ++it2) { + app * t = it2->m_key; + func_decl * new_def = it2->m_value; + f_mc->insert(new_def); + SASSERT(new_def->get_arity() == new_args.size()); + app * new_t = m_manager.mk_app(new_def, new_args.size(), new_args.c_ptr()); + if (def == 0) { + def = new_t; + } + else { + new_eqs.reset(); + for (unsigned i = 0; i < f->get_arity(); i++) { + if (bv.get(i)) + new_eqs.push_back(m_manager.mk_eq(new_vars.get(i), t->get_arg(i))); + } + SASSERT(new_eqs.size() > 0); + expr * cond; + if (new_eqs.size() == 1) + cond = new_eqs[0]; + else + cond = m_manager.mk_and(new_eqs.size(), new_eqs.c_ptr()); + def = m_manager.mk_ite(cond, new_t, def); + } + } + SASSERT(def); + e_mc->insert(f, def); + } + return concat(f_mc, e_mc); + } + + void operator()(assertion_set & s, model_converter_ref & mc) { + mc = 0; + if (s.inconsistent()) + return; + if (m().proofs_enabled()) + throw reduce_args_exception("reduce-args does not support proofs"); + as_st_report report("reduce-args", s); + TRACE("reduce_args", s.display(tout);); + + obj_hashtable non_candidates; + obj_map decl2args; + find_non_candidates(s, non_candidates); + populate_decl2args(s, non_candidates, decl2args); + + if (decl2args.empty()) + return; + + ptr_vector arg2funcs; + reduce_args_ctx ctx(m_manager); + populate_decl2arg_set(s, decl2args, ctx.m_decl2arg2funcs); + + reduce_args_rw rw(*this, decl2args, ctx.m_decl2arg2funcs); + + unsigned sz = s.size(); + for (unsigned i = 0; i < sz; i++) { + if (s.inconsistent()) + break; + expr * f = s.form(i); + expr_ref new_f(m_manager); + rw(f, new_f); + s.update(i, new_f); + } + + report_st_progress(":reduced-funcs", decl2args.size()); + + if (m_produce_models) + mc = mk_mc(decl2args, ctx.m_decl2arg2funcs); + + TRACE("reduce_args", s.display(tout); if (mc) mc->display(tout);); + } +}; + +reduce_args::reduce_args(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); +} + +reduce_args::~reduce_args() { + dealloc(m_imp); +} + +ast_manager & reduce_args::m() const { + return m_imp->m(); +} + +void reduce_args::updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); +} + +void reduce_args::get_param_descrs(param_descrs & r) { + insert_produce_models(r); +} + +void reduce_args::operator()(assertion_set & s, model_converter_ref & mc) { + m_imp->operator()(s, mc); +} + +void reduce_args::set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); +} + +void reduce_args::cleanup() { + ast_manager & m = m_imp->m(); + imp * d = m_imp; + #pragma omp critical (as_st_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (as_st_cancel) + { + m_imp = d; + } +} + +void reduce_args::collect_statistics(statistics & st) const { +} + +void reduce_args::reset_statistics() { +} + +as_st * mk_reduce_args(ast_manager & m, params_ref const & p) { + return clean(alloc(reduce_args, m, p)); +} + + diff --git a/lib/reduce_args.h b/lib/reduce_args.h new file mode 100644 index 000000000..8dd20176f --- /dev/null +++ b/lib/reduce_args.h @@ -0,0 +1,92 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + reduce_args.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-04-05. + +Revision History: + + Make it a strategy 2011-07-26. + +--*/ +#ifndef _REDUCE_ARGS_H_ +#define _REDUCE_ARGS_H_ + +#include"assertion_set_strategy.h" + +MK_ST_EXCEPTION(reduce_args_exception); + +/** + \brief Reduce the number of arguments in function applications. + + Example, suppose we have a function f with 2 arguments. + There are 1000 applications of this function, but the first argument is always "a", "b" or "c". + Thus, we replace the f(t1, t2) + with + f_a(t2) if t1 = a + f_b(t2) if t2 = b + f_c(t2) if t2 = c + + Since f_a, f_b, f_c are new symbols, satisfiability is preserved. + + This transformation is very similar in spirit to the Ackermman's reduction. + + This transformation should work in the following way: + + 1- Create a mapping decl2arg_map from declarations to tuples of booleans, an entry [f -> (true, false, true)] + means that f is a declaration with 3 arguments where the first and third arguments are always values. + 2- Traverse the formula and populate the mapping. + For each function application f(t1, ..., tn) do + a) Create a boolean tuple (is_value(t1), ..., is_value(tn)) and do + the logical-and with the tuple that is already in the mapping. If there is no such tuple + in the mapping, we just add a new entry. + + If all entries are false-tuples, then there is nothing to be done. The transformation is not applicable. + + Now, we create a mapping decl2new_decl from (decl, val_1, ..., val_n) to decls. Note that, n may be different for each entry, + but it is the same for the same declaration. + For example, suppose we have [f -> (true, false, true)] in decl2arg_map, and applications f(1, a, 2), f(1, b, 2), f(1, b, 3), f(2, b, 3), f(2, c, 3) in the formula. + Then, decl2arg_map would contain + (f, 1, 2) -> f_1_2 + (f, 1, 3) -> f_1_3 + (f, 2, 3) -> f_2_3 + where f_1_2, f_1_3 and f_2_3 are new function symbols. + Using the new map, we can replace the occurrences of f. +*/ +class reduce_args : public assertion_set_strategy { + struct imp; + imp * m_imp; + params_ref m_params; +public: + reduce_args(ast_manager & m, params_ref const & p = params_ref()); + virtual ~reduce_args(); + + ast_manager & m () const; + + virtual void updt_params(params_ref const & p); + static void get_param_descrs(param_descrs & r); + virtual void collect_param_descrs(param_descrs & r) { get_param_descrs(r); } + + virtual void operator()(assertion_set & s, model_converter_ref & mc); + + virtual void cleanup(); + + virtual void collect_statistics(statistics & st) const; + virtual void reset_statistics(); +protected: + virtual void set_cancel(bool f); +}; + +as_st * mk_reduce_args(ast_manager & m, params_ref const & p = params_ref()); + +#endif /* _REDUCE_ARGS_H_ */ + diff --git a/lib/reduce_args_tactic.cpp b/lib/reduce_args_tactic.cpp new file mode 100644 index 000000000..417fd3bf2 --- /dev/null +++ b/lib/reduce_args_tactic.cpp @@ -0,0 +1,558 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + reduce_args_tactic.cpp + +Abstract: + + Reduce the number of arguments in function applications. + +Author: + + Leonardo (leonardo) 2012-02-19 + +Notes: + +--*/ +#include"tactical.h" +#include"reduce_args.h" +#include"cooperate.h" +#include"ast_smt2_pp.h" +#include"map.h" +#include"rewriter_def.h" +#include"extension_model_converter.h" +#include"filter_model_converter.h" + +/** + \brief Reduce the number of arguments in function applications. + + Example, suppose we have a function f with 2 arguments. + There are 1000 applications of this function, but the first argument is always "a", "b" or "c". + Thus, we replace the f(t1, t2) + with + f_a(t2) if t1 = a + f_b(t2) if t2 = b + f_c(t2) if t2 = c + + Since f_a, f_b, f_c are new symbols, satisfiability is preserved. + + This transformation is very similar in spirit to the Ackermman's reduction. + + This transformation should work in the following way: + + 1- Create a mapping decl2arg_map from declarations to tuples of booleans, an entry [f -> (true, false, true)] + means that f is a declaration with 3 arguments where the first and third arguments are always values. + 2- Traverse the formula and populate the mapping. + For each function application f(t1, ..., tn) do + a) Create a boolean tuple (is_value(t1), ..., is_value(tn)) and do + the logical-and with the tuple that is already in the mapping. If there is no such tuple + in the mapping, we just add a new entry. + + If all entries are false-tuples, then there is nothing to be done. The transformation is not applicable. + + Now, we create a mapping decl2new_decl from (decl, val_1, ..., val_n) to decls. Note that, n may be different for each entry, + but it is the same for the same declaration. + For example, suppose we have [f -> (true, false, true)] in decl2arg_map, and applications f(1, a, 2), f(1, b, 2), f(1, b, 3), f(2, b, 3), f(2, c, 3) in the formula. + Then, decl2arg_map would contain + (f, 1, 2) -> f_1_2 + (f, 1, 3) -> f_1_3 + (f, 2, 3) -> f_2_3 + where f_1_2, f_1_3 and f_2_3 are new function symbols. + Using the new map, we can replace the occurrences of f. +*/ +class reduce_args_tactic : public tactic { + struct imp; + imp * m_imp; +public: + reduce_args_tactic(ast_manager & m); + + virtual tactic * translate(ast_manager & m) { + return alloc(reduce_args_tactic, m); + } + + virtual ~reduce_args_tactic(); + + virtual void operator()(goal_ref const & g, goal_ref_buffer & result, model_converter_ref & mc, proof_converter_ref & pc, expr_dependency_ref & core); + virtual void cleanup(); + virtual void set_cancel(bool f); +}; + +tactic * mk_reduce_args_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(reduce_args_tactic, m)); +} + +struct reduce_args_tactic::imp { + ast_manager & m_manager; + bool m_produce_models; + volatile bool m_cancel; + + ast_manager & m() const { return m_manager; } + + imp(ast_manager & m): + m_manager(m) { + m_cancel = false; + } + + void set_cancel(bool f) { + m_cancel = f; + } + + void checkpoint() { + if (m_cancel) + throw tactic_exception(TACTIC_CANCELED_MSG); + cooperate("reduce-args"); + } + + struct find_non_candidates_proc { + ast_manager & m_manager; + obj_hashtable & m_non_cadidates; + + find_non_candidates_proc(ast_manager & m, obj_hashtable & non_cadidates): + m_manager(m), + m_non_cadidates(non_cadidates) { + } + + void operator()(var * n) {} + + void operator()(quantifier * n) {} + + void operator()(app * n) { + if (n->get_num_args() == 0) + return; // ignore constants + func_decl * d = n->get_decl(); + if (d->get_family_id() != null_family_id) + return; // ignore interpreted symbols + if (m_non_cadidates.contains(d)) + return; // it is already in the set. + unsigned j = n->get_num_args(); + while (j > 0) { + --j; + if (m_manager.is_value(n->get_arg(j))) + return; + } + m_non_cadidates.insert(d); + } + }; + + /** + \brief Populate the table non_cadidates with function declarations \c f + such that there is a function application (f t1 ... tn) where t1 ... tn are not values. + */ + void find_non_candidates(goal const & g, obj_hashtable & non_candidates) { + non_candidates.reset(); + find_non_candidates_proc proc(m_manager, non_candidates); + expr_fast_mark1 visited; + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + checkpoint(); + quick_for_each_expr(proc, visited, g.form(i)); + } + + TRACE("reduce_args", tout << "non_candidates:\n"; + obj_hashtable::iterator it = non_candidates.begin(); + obj_hashtable::iterator end = non_candidates.end(); + for (; it != end; ++it) { + func_decl * d = *it; + tout << d->get_name() << "\n"; + }); + } + + struct populate_decl2args_proc { + ast_manager & m_manager; + obj_hashtable & m_non_cadidates; + obj_map & m_decl2args; + + populate_decl2args_proc(ast_manager & m, obj_hashtable & nc, obj_map & d): + m_manager(m), m_non_cadidates(nc), m_decl2args(d) {} + + void operator()(var * n) {} + void operator()(quantifier * n) {} + void operator()(app * n) { + if (n->get_num_args() == 0) + return; // ignore constants + func_decl * d = n->get_decl(); + if (d->get_family_id() != null_family_id) + return; // ignore interpreted symbols + if (m_non_cadidates.contains(d)) + return; // declaration is not a candidate + unsigned j = n->get_num_args(); + obj_map::iterator it = m_decl2args.find_iterator(d); + if (it == m_decl2args.end()) { + m_decl2args.insert(d, bit_vector()); + it = m_decl2args.find_iterator(d); + SASSERT(it != m_decl2args.end()); + it->m_value.reserve(j); + while (j > 0) { + --j; + it->m_value.set(j, m_manager.is_value(n->get_arg(j))); + } + } else { + SASSERT(j == it->m_value.size()); + while (j > 0) { + --j; + it->m_value.set(j, it->m_value.get(j) && m_manager.is_value(n->get_arg(j))); + } + } + } + }; + + void populate_decl2args(goal const & g, + obj_hashtable & non_candidates, + obj_map & decl2args) { + expr_fast_mark1 visited; + decl2args.reset(); + populate_decl2args_proc proc(m_manager, non_candidates, decl2args); + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + checkpoint(); + quick_for_each_expr(proc, visited, g.form(i)); + } + + // Remove all cases where the simplification is not applicable. + ptr_buffer bad_decls; + obj_map::iterator it = decl2args.begin(); + obj_map::iterator end = decl2args.end(); + for (; it != end; it++) { + bool is_zero = true; + for (unsigned i = 0; i < it->m_value.size() && is_zero ; i++) { + if (it->m_value.get(i)) + is_zero = false; + } + if (is_zero) + bad_decls.push_back(it->m_key); + } + + ptr_buffer::iterator it2 = bad_decls.begin(); + ptr_buffer::iterator end2 = bad_decls.end(); + for (; it2 != end2; ++it2) + decl2args.erase(*it2); + + TRACE("reduce_args", tout << "decl2args:" << std::endl; + for (obj_map::iterator it = decl2args.begin() ; it != decl2args.end() ; it++) { + tout << it->m_key->get_name() << ": "; + for (unsigned i = 0 ; i < it->m_value.size() ; i++) + tout << (it->m_value.get(i) ? "1" : "0"); + tout << std::endl; + }); + } + + struct arg2func_hash_proc { + bit_vector const & m_bv; + + arg2func_hash_proc(bit_vector const & bv):m_bv(bv) {} + unsigned operator()(app const * n) const { + // compute the hash-code using only the arguments where m_bv is true. + unsigned a = 0x9e3779b9; + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + if (!m_bv.get(i)) + continue; // ignore argument + a = hash_u_u(a, n->get_arg(i)->get_id()); + } + return a; + } + }; + + struct arg2func_eq_proc { + bit_vector const & m_bv; + + arg2func_eq_proc(bit_vector const & bv):m_bv(bv) {} + bool operator()(app const * n1, app const * n2) const { + // compare only the arguments where m_bv is true + SASSERT(n1->get_num_args() == n2->get_num_args()); + unsigned num_args = n1->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + if (!m_bv.get(i)) + continue; // ignore argument + if (n1->get_arg(i) != n2->get_arg(i)) + return false; + } + return true; + } + }; + + typedef map arg2func; + typedef obj_map decl2arg2func_map; + + struct reduce_args_ctx { + ast_manager & m_manager; + decl2arg2func_map m_decl2arg2funcs; + + reduce_args_ctx(ast_manager & m): m_manager(m) { + } + + ~reduce_args_ctx() { + obj_map::iterator it = m_decl2arg2funcs.begin(); + obj_map::iterator end = m_decl2arg2funcs.end(); + for (; it != end; ++it) { + arg2func * map = it->m_value; + arg2func::iterator it2 = map->begin(); + arg2func::iterator end2 = map->end(); + for (; it2 != end2; ++it2) { + m_manager.dec_ref(it2->m_key); + m_manager.dec_ref(it2->m_value); + } + dealloc(map); + } + } + }; + + struct populate_decl2arg_set_proc { + ast_manager & m_manager; + obj_map & m_decl2args; + decl2arg2func_map & m_decl2arg2funcs; + + populate_decl2arg_set_proc(ast_manager & m, + obj_map & d, + decl2arg2func_map & ds): + m_manager(m), m_decl2args(d), m_decl2arg2funcs(ds) {} + + void operator()(var * n) {} + void operator()(quantifier * n) {} + + void operator()(app * n) { + if (n->get_num_args() == 0) + return; // ignore constants + func_decl * d = n->get_decl(); + if (d->get_family_id() != null_family_id) + return; // ignore interpreted symbols + obj_map::iterator it = m_decl2args.find_iterator(d); + if (it == m_decl2args.end()) + return; // not reducing the arguments of this declaration + bit_vector & bv = it->m_value; + arg2func * map = 0; + decl2arg2func_map::iterator it2 = m_decl2arg2funcs.find_iterator(d); + if (it2 == m_decl2arg2funcs.end()) { + map = alloc(arg2func, arg2func_hash_proc(bv), arg2func_eq_proc(bv)); + m_decl2arg2funcs.insert(d, map); + } + else { + map = it2->m_value; + } + if (!map->contains(n)) { + // create fresh symbol... + ptr_buffer domain; + unsigned arity = d->get_arity(); + for (unsigned i = 0; i < arity; i++) { + if (!bv.get(i)) + domain.push_back(d->get_domain(i)); + } + func_decl * new_d = m_manager.mk_fresh_func_decl(d->get_name(), symbol::null, domain.size(), domain.c_ptr(), d->get_range()); + map->insert(n, new_d); + m_manager.inc_ref(n); + m_manager.inc_ref(new_d); + } + } + }; + + void populate_decl2arg_set(goal const & g, + obj_map & decl2args, + decl2arg2func_map & decl2arg2funcs) { + expr_fast_mark1 visited; + + populate_decl2arg_set_proc proc(m_manager, decl2args, decl2arg2funcs); + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + checkpoint(); + quick_for_each_expr(proc, visited, g.form(i)); + } + } + + struct reduce_args_rw_cfg : public default_rewriter_cfg { + ast_manager & m; + imp & m_owner; + obj_map & m_decl2args; + decl2arg2func_map & m_decl2arg2funcs; + + reduce_args_rw_cfg(imp & owner, obj_map & decl2args, decl2arg2func_map & decl2arg2funcs): + m(owner.m_manager), + m_owner(owner), + m_decl2args(decl2args), + m_decl2arg2funcs(decl2arg2funcs) { + } + + bool max_steps_exceeded(unsigned num_steps) const { + m_owner.checkpoint(); + return false; + } + + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + result_pr = 0; + if (f->get_arity() == 0) + return BR_FAILED; // ignore constants + if (f->get_family_id() != null_family_id) + return BR_FAILED; // ignore interpreted symbols + decl2arg2func_map::iterator it = m_decl2arg2funcs.find_iterator(f); + if (it == m_decl2arg2funcs.end()) + return BR_FAILED; + SASSERT(m_decl2args.contains(f)); + bit_vector & bv = m_decl2args.find(f); + arg2func * map = it->m_value; + app_ref tmp(m); + tmp = m.mk_app(f, num, args); + CTRACE("reduce_args", !map->contains(tmp), + tout << "map does not contain tmp f: " << f->get_name() << "\n"; + tout << mk_ismt2_pp(tmp, m) << "\n"; + arg2func::iterator it = map->begin(); + arg2func::iterator end = map->end(); + for (; it != end; ++it) { + tout << mk_ismt2_pp(it->m_key, m) << "\n---> " << it->m_value->get_name() << "\n"; + }); + SASSERT(map->contains(tmp)); + func_decl * new_f = map->find(tmp); + ptr_buffer new_args; + for (unsigned i = 0; i < num; i++) { + if (!bv.get(i)) + new_args.push_back(args[i]); + } + result = m.mk_app(new_f, new_args.size(), new_args.c_ptr()); + return BR_DONE; + } + }; + + struct reduce_args_rw : rewriter_tpl { + reduce_args_rw_cfg m_cfg; + public: + reduce_args_rw(imp & owner, obj_map & decl2args, decl2arg2func_map & decl2arg2funcs): + rewriter_tpl(owner.m_manager, false, m_cfg), + m_cfg(owner, decl2args, decl2arg2funcs) { + } + }; + + model_converter * mk_mc(obj_map & decl2args, decl2arg2func_map & decl2arg2funcs) { + ptr_buffer new_args; + var_ref_vector new_vars(m_manager); + ptr_buffer new_eqs; + extension_model_converter * e_mc = alloc(extension_model_converter, m_manager); + filter_model_converter * f_mc = alloc(filter_model_converter, m_manager); + decl2arg2func_map::iterator it = decl2arg2funcs.begin(); + decl2arg2func_map::iterator end = decl2arg2funcs.end(); + for (; it != end; ++it) { + func_decl * f = it->m_key; + arg2func * map = it->m_value; + expr * def = 0; + SASSERT(decl2args.contains(f)); + bit_vector & bv = decl2args.find(f); + new_vars.reset(); + new_args.reset(); + for (unsigned i = 0; i < f->get_arity(); i++) { + new_vars.push_back(m_manager.mk_var(i, f->get_domain(i))); + if (!bv.get(i)) + new_args.push_back(new_vars.back()); + } + arg2func::iterator it2 = map->begin(); + arg2func::iterator end2 = map->end(); + for (; it2 != end2; ++it2) { + app * t = it2->m_key; + func_decl * new_def = it2->m_value; + f_mc->insert(new_def); + SASSERT(new_def->get_arity() == new_args.size()); + app * new_t = m_manager.mk_app(new_def, new_args.size(), new_args.c_ptr()); + if (def == 0) { + def = new_t; + } + else { + new_eqs.reset(); + for (unsigned i = 0; i < f->get_arity(); i++) { + if (bv.get(i)) + new_eqs.push_back(m_manager.mk_eq(new_vars.get(i), t->get_arg(i))); + } + SASSERT(new_eqs.size() > 0); + expr * cond; + if (new_eqs.size() == 1) + cond = new_eqs[0]; + else + cond = m_manager.mk_and(new_eqs.size(), new_eqs.c_ptr()); + def = m_manager.mk_ite(cond, new_t, def); + } + } + SASSERT(def); + e_mc->insert(f, def); + } + return concat(f_mc, e_mc); + } + + void operator()(goal & g, model_converter_ref & mc) { + if (g.inconsistent()) + return; + m_produce_models = g.models_enabled(); + TRACE("reduce_args", g.display(tout);); + tactic_report report("reduce-args", g); + obj_hashtable non_candidates; + obj_map decl2args; + find_non_candidates(g, non_candidates); + populate_decl2args(g, non_candidates, decl2args); + + if (decl2args.empty()) + return; + + ptr_vector arg2funcs; + reduce_args_ctx ctx(m_manager); + populate_decl2arg_set(g, decl2args, ctx.m_decl2arg2funcs); + + reduce_args_rw rw(*this, decl2args, ctx.m_decl2arg2funcs); + + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + if (g.inconsistent()) + break; + expr * f = g.form(i); + expr_ref new_f(m_manager); + rw(f, new_f); + g.update(i, new_f); + } + + report_tactic_progress(":reduced-funcs", decl2args.size()); + + if (m_produce_models) + mc = mk_mc(decl2args, ctx.m_decl2arg2funcs); + + TRACE("reduce_args", g.display(tout); if (mc) mc->display(tout);); + } +}; + +reduce_args_tactic::reduce_args_tactic(ast_manager & m) { + m_imp = alloc(imp, m); +} + +reduce_args_tactic::~reduce_args_tactic() { + dealloc(m_imp); +} + +void reduce_args_tactic::operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + fail_if_proof_generation("reduce-args", g); + fail_if_unsat_core_generation("reduce-args", g); + mc = 0; pc = 0; core = 0; result.reset(); + m_imp->operator()(*(g.get()), mc); + g->inc_depth(); + result.push_back(g.get()); + SASSERT(g->is_well_sorted()); +} + +void reduce_args_tactic::set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); +} + +void reduce_args_tactic::cleanup() { + ast_manager & m = m_imp->m(); + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } +} + diff --git a/lib/reduce_args_tactic.h b/lib/reduce_args_tactic.h new file mode 100644 index 000000000..00955eb83 --- /dev/null +++ b/lib/reduce_args_tactic.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + reduce_args_tactic.h + +Abstract: + + Reduce the number of arguments in function applications. + +Author: + + Leonardo (leonardo) 2012-02-19 + +Notes: + +--*/ +#ifndef _REDUCE_ARGS_TACTIC_H_ +#define _REDUCE_ARGS_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_reduce_args_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/ref.h b/lib/ref.h new file mode 100644 index 000000000..5b56b4d24 --- /dev/null +++ b/lib/ref.h @@ -0,0 +1,124 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + ref.h + +Abstract: + + Simple smart pointer class + +Author: + + Leonardo de Moura (leonardo) 2007-08-14. + +Revision History: + +--*/ +#ifndef _REF_H_ +#define _REF_H_ + +template +class ref { + T * m_ptr; + + void inc_ref() { + if (m_ptr) { + m_ptr->inc_ref(); + } + } + + void dec_ref() { + if (m_ptr) { + m_ptr->dec_ref(); + } + } + +public: + ref(): + m_ptr(0) { + } + + ref(T * ptr): + m_ptr(ptr) { + inc_ref(); + } + + ref(const ref & r): + m_ptr(r.m_ptr) { + inc_ref(); + } + + ~ref() { + dec_ref(); + } + + T * operator->() const { + return m_ptr; + } + + T * get() const { + return m_ptr; + } + + operator bool() const { + return m_ptr != 0; + } + + const T & operator*() const { + return *m_ptr; + } + + T & operator*() { + return *m_ptr; + } + + ref & operator=(T * ptr) { + if (ptr) + ptr->inc_ref(); + dec_ref(); + m_ptr = ptr; + return *this; + } + + ref & operator=(ref & r) { + r.inc_ref(); + dec_ref(); + m_ptr = r.m_ptr; + return *this; + } + + void reset() { + dec_ref(); + m_ptr = 0; + } + + T* detach() { + T* tmp = m_ptr; + m_ptr = 0; + return tmp; + } + + friend bool operator==(const ref & r1, const ref & r2) { + return r1.m_ptr == r2.m_ptr; + } + + friend bool operator!=(const ref & r1, const ref & r2) { + return r1.m_ptr != r2.m_ptr; + } +}; + +/** + \brief Manager for references that are not managed. + This class is used for allowing us to create instantiations of the ref_vector and ref_buffer + templates for unmanaged objects. +*/ +template +class unmanged_ref_manager { + static void inc_ref(T * o) { o->inc_ref(); } + static void dec_ref(T * o) { o->dec_ref(); } +}; + +#endif /* _REF_H_ */ + diff --git a/lib/ref_buffer.h b/lib/ref_buffer.h new file mode 100644 index 000000000..762113dd6 --- /dev/null +++ b/lib/ref_buffer.h @@ -0,0 +1,169 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + ref_buffer.h + +Abstract: + + Buffer of smart pointers. + +Author: + + Leonardo de Moura (leonardo) 2008-01-04. + +Revision History: + +--*/ +#ifndef _REF_BUFFER_H_ +#define _REF_BUFFER_H_ + +#include"buffer.h" +#include"obj_ref.h" +#include"ref_vector.h" + + +/** + \brief Buffer of smart pointers. + Ref must provide the methods + - void dec_ref(T * obj) + - void inc_ref(T * obj) +*/ +template +class ref_buffer_core : public Ref { +protected: + ptr_buffer m_buffer; + + void inc_ref(T * o) { Ref::inc_ref(o); } + void dec_ref(T * o) { Ref::dec_ref(o); } + + void dec_range_ref(T * const * begin, T * const * end) { + for (T * const * it = begin; it < end; ++it) + dec_ref(*it); + } + +public: + typedef T * data; + + ref_buffer_core(Ref const & r = Ref()): + Ref(r) { + } + + ~ref_buffer_core() { + dec_range_ref(m_buffer.begin(), m_buffer.end()); + } + + void push_back(T * n) { + inc_ref(n); + m_buffer.push_back(n); + } + + void pop_back() { + SASSERT(!m_buffer.empty()); + T * n = m_buffer.back(); + m_buffer.pop_back(); + dec_ref(n); + } + + T * back() const { + return m_buffer.back(); + } + + T * & back() { + return m_buffer.back(); + } + + T ** c_ptr() const { + return m_buffer.c_ptr(); + } + + T const * operator[](unsigned idx) const { + return m_buffer[idx]; + } + + T * operator[](unsigned idx) { + return m_buffer[idx]; + } + + void set(unsigned idx, T * n) { + inc_ref(n); + dec_ref(m_buffer[idx]); + m_buffer[idx] = n; + } + + unsigned size() const { + return m_buffer.size(); + } + + bool empty() const { + return m_buffer.empty(); + } + + void reset() { + dec_range_ref(m_buffer.begin(), m_buffer.end()); + m_buffer.reset(); + } + + void finalize() { + dec_range_ref(m_buffer.begin(), m_buffer.end()); + m_buffer.finalize(); + } + + void append(unsigned n, T * const * elems) { + for (unsigned i = 0; i < n; i++) { + push_back(elems[i]); + } + } + + void append(ref_buffer_core const & other) { + append(other.size(), other.c_ptr()); + } + + void resize(unsigned sz) { + if (sz < m_buffer.size()) + dec_range_ref(m_buffer.begin() + sz, m_buffer.end()); + m_buffer.resize(sz, 0); + } + + // set pos idx with elem. If idx >= size, then expand. + void setx(unsigned idx, T * elem) { + if (idx >= size()) { + resize(idx+1); + } + set(idx, elem); + } + +private: + // prevent abuse: + ref_buffer_core& operator=(ref_buffer_core const & other); +}; + + +/** + \brief Buffer of managed references +*/ +template +class ref_buffer : public ref_buffer_core > { + typedef ref_buffer_core > super; +public: + ref_buffer(TManager & m): + super(ref_manager_wrapper(m)) { + } + + ref_buffer(ref_buffer const & other): + super(ref_manager_wrapper(other.m_manager)) { + SASSERT(this->m_buffer.size() == 0); + append(other); + } +}; + +/** + \brief Buffer of unmanaged references +*/ +template +class sref_buffer : public ref_buffer_core > { +public: +}; + +#endif /* _REF_BUFFER_H_ */ diff --git a/lib/ref_util.h b/lib/ref_util.h new file mode 100644 index 000000000..fff315b50 --- /dev/null +++ b/lib/ref_util.h @@ -0,0 +1,91 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + ref_util.h + +Abstract: + + Some goodies for managing reference counters. + +Author: + + Leonardo (leonardo) 2011-06-07 + +Notes: + +--*/ +#ifndef _REF_UTIL_H_ +#define _REF_UTIL_H_ + +/** + \brief Decrement the reference counter of the keys and values stored in the map, + then reset the map. +*/ +template +void dec_ref_map_key_values(Mng1 & m1, Mng2 & m2, Map & map) { + typename Map::iterator it = map.begin(); + typename Map::iterator end = map.end(); + for (; it != end; ++it) { + m1.dec_ref(it->m_key); + m2.dec_ref(it->m_value); + } + map.reset(); +} + +/** + \brief Decrement the reference counter of the keys and values stored in the map, + then reset the map. +*/ +template +void dec_ref_map_key_values(Mng & m, Map & map) { + dec_ref_map_key_values(m, m, map); +} + +/** + \brief Decrement the reference counter of the keys stored in the map, + then reset the map. +*/ +template +void dec_ref_map_keys(Mng & m, Map & map) { + typename Map::iterator it = map.begin(); + typename Map::iterator end = map.end(); + for (; it != end; ++it) { + m.dec_ref(it->m_key); + } + map.reset(); +} + + +/** + \brief Decrement the reference counter of the values stored in the map, + then reset the map. +*/ +template +void dec_ref_map_values(Mng & m, Map & map) { + typename Map::iterator it = map.begin(); + typename Map::iterator end = map.end(); + for (; it != end; ++it) { + m.dec_ref(it->m_value); + } + map.reset(); +} + + +/** + \brief Decrement the reference counter of the values stored in the map, + then reset the map. +*/ +template +void dec_ref_collection_values(Mng & m, C & c) { + typename C::iterator it = c.begin(); + typename C::iterator end = c.end(); + for (; it != end; ++it) { + m.dec_ref(*it); + } + c.reset(); +} + + +#endif diff --git a/lib/ref_vector.h b/lib/ref_vector.h new file mode 100644 index 000000000..155c032a1 --- /dev/null +++ b/lib/ref_vector.h @@ -0,0 +1,349 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + ref_vector.h + +Abstract: + + Vector of smart pointers. + +Author: + + Leonardo de Moura (leonardo) 2008-01-04. + +Revision History: + +--*/ +#ifndef _REF_VECTOR_H_ +#define _REF_VECTOR_H_ + +#include"vector.h" +#include"obj_ref.h" + +/** + \brief Vector of smart pointers. + Ref must provided the methods + - void dec_ref(T * obj) + - void inc_ref(T * obj) +*/ +template +class ref_vector_core : public Ref { +protected: + ptr_vector m_nodes; + + void inc_ref(T * o) { Ref::inc_ref(o); } + void dec_ref(T * o) { Ref::dec_ref(o); } + + void dec_range_ref(T * const * begin, T * const * end) { + for (T * const * it = begin; it < end; ++it) + dec_ref(*it); + } + +public: + typedef T * data; + + ref_vector_core(Ref const & r = Ref()):Ref(r) {} + + ~ref_vector_core() { + dec_range_ref(m_nodes.begin(), m_nodes.end()); + } + + void reset() { + dec_range_ref(m_nodes.begin(), m_nodes.end()); + m_nodes.reset(); + } + + void finalize() { + dec_range_ref(m_nodes.begin(), m_nodes.end()); + m_nodes.finalize(); + } + + void resize(unsigned sz) { + if (sz < m_nodes.size()) + dec_range_ref(m_nodes.begin() + sz, m_nodes.end()); + m_nodes.resize(sz, 0); + } + + void resize(unsigned sz, T * d) { + if (sz < m_nodes.size()) { + dec_range_ref(m_nodes.begin() + sz, m_nodes.end()); + m_nodes.shrink(sz); + } + else { + for (unsigned i = m_nodes.size(); i < sz; i++) + push_back(d); + } + } + + void reserve(unsigned sz) { + if (sz <= m_nodes.size()) + return; + m_nodes.resize(sz, 0); + } + + void shrink(unsigned sz) { + SASSERT(sz <= m_nodes.size()); + dec_range_ref(m_nodes.begin() + sz, m_nodes.end()); + m_nodes.shrink(sz); + } + + void push_back(T * n) { + inc_ref(n); + m_nodes.push_back(n); + } + + void pop_back() { + SASSERT(!m_nodes.empty()); + T * n = m_nodes.back(); + m_nodes.pop_back(); + dec_ref(n); + } + + T * back() const { return m_nodes.back(); } + + unsigned size() const { return m_nodes.size(); } + + bool empty() const { return m_nodes.empty(); } + + T * get(unsigned idx) const { return m_nodes[idx]; } + + T * get(unsigned idx, T * d) const { return m_nodes.get(idx, d); } + + T * const * c_ptr() const { return m_nodes.begin(); } + + T ** c_ptr() { return m_nodes.begin(); } + + void set(unsigned idx, T * n) { + inc_ref(n); + dec_ref(m_nodes[idx]); + m_nodes[idx] = n; + } + + void erase(unsigned idx) { + T * curr = m_nodes[idx]; + m_nodes.erase(m_nodes.begin() + idx); + dec_ref(curr); + } + + void erase(T * elem) { + unsigned sz = size(); + for (unsigned idx = 0; idx < sz; idx++) { + if (m_nodes[idx] == elem) { + erase(idx); + return; + } + } + } + + bool contains(T * elem) const { + unsigned sz = size(); + for (unsigned idx = 0; idx < sz; idx++) + if (m_nodes[idx] == elem) + return true; + return false; + } + + T * operator[](unsigned idx) const { + return m_nodes[idx]; + } + + void append(ref_vector_core const & other) { + for (unsigned i = 0; i < other.size(); ++i) + push_back(other[i]); + } + + void append(unsigned sz, T * const * data) { + for(unsigned i = 0; i < sz; ++i) + push_back(data[i]); + } + + void swap(unsigned idx1, unsigned idx2) { + std::swap(m_nodes[idx1], m_nodes[idx2]); + } + + void reverse() { + unsigned sz = size(); + for (unsigned i = 0; i < sz/2; ++i) { + std::swap(m_nodes[i], m_nodes[sz-i-1]); + } + } +}; + +template +class ref_manager_wrapper { +protected: + TManager & m_manager; +public: + ref_manager_wrapper(TManager & m):m_manager(m) {} + void inc_ref(T * n) { m_manager.inc_ref(n); } + void dec_ref(T * n) { m_manager.dec_ref(n); } +}; + + +/** + \brief Vector of smart pointers. + TManager must provide the functions: + - void dec_ref(T * obj) + - void inc_ref(T * obj) +*/ +template +class ref_vector : public ref_vector_core > { + typedef ref_vector_core > super; +public: + ref_vector(TManager & m): + super(ref_manager_wrapper(m)) { + } + + ref_vector(ref_vector const & other): + super(ref_manager_wrapper(other.m_manager)) { + append(other); + } + + ref_vector(TManager & m, unsigned sz, T * const * data): + super(ref_manager_wrapper(m)) { + append(sz, data); + } + + TManager & get_manager() const { + return this->m_manager; + } + + TManager & m() const { + return get_manager(); + } + + void swap(ref_vector & other) { + SASSERT(&(this->m_manager) == &(other.m_manager)); + this->m_nodes.swap(other.m_nodes); + } + + class element_ref { + T * & m_ref; + TManager & m_manager; + public: + element_ref(T * & ref, TManager & m): + m_ref(ref), + m_manager(m) { + } + + element_ref & operator=(T * n) { + SASSERT(n); + m_manager.inc_ref(n); + m_manager.dec_ref(m_ref); + m_ref = n; + return *this; + } + + element_ref & operator=(element_ref& n) { + *this = n.get(); + return *this; + } + + T * get() const { + return m_ref; + } + + T * operator->() const { + return m_ref; + } + + T const & operator*() const { + SASSERT(m_ref); + return *m_ref; + } + + bool operator==(T * n) const { + return m_ref == n; + } + }; + + T * operator[](unsigned idx) const { return super::operator[](idx); } + + element_ref operator[](unsigned idx) { + return element_ref(this->m_nodes[idx], this->m_manager); + } + + void set(unsigned idx, T * n) { super::set(idx, n); } + + // enable abuse: + ref_vector & set(ref_vector const& other) { + if (this != &other) { + this->reset(); + append(other); + } + return *this; + } + +private: + // prevent abuse: + ref_vector & operator=(ref_vector const & other); +}; + +template +class ref_unmanaged_wrapper { +public: + static void inc_ref(T * n) { if (n) n->inc_ref(); } + static void dec_ref(T * n) { if (n) n->dec_ref(); } +}; + +/** + \brief Vector of unmanaged references. +*/ +template +class sref_vector : public ref_vector_core > { +}; + +/** + \brief Hash utilities on ref_vector pointers. +*/ + +template +struct ref_vector_ptr_hash { + + typedef ref_vector RefV; + + struct hash_proc { + unsigned operator()(RefV* v, unsigned idx) const { + return (*v)[idx]->get_id(); + } + }; + + unsigned operator()(RefV* v) const { + if (!v) { + return 0; + } + unsigned sz = v->size(); + if (sz == 0) { + return 0; + } + return get_composite_hash(v, sz, default_kind_hash_proc(), hash_proc()); + } +}; + +template +struct ref_vector_ptr_eq { + typedef ref_vector RefV; + + bool operator()(RefV* v1, RefV* v2) const { + if (!v1 && !v2) { + return true; + } + if ((!v1 && v2) || (v1 && !v2)) { + return false; + } + if (v1->size() != v2->size()) { + return false; + } + for (unsigned i = 0; i < v1->size(); ++i) { + if ((*v1)[i].get() != (*v2)[i].get()) { + return false; + } + } + return true; + } +}; + + +#endif /* _REF_VECTOR_H_ */ diff --git a/lib/region.cpp b/lib/region.cpp new file mode 100644 index 000000000..0100c60f3 --- /dev/null +++ b/lib/region.cpp @@ -0,0 +1,124 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + region.cpp + +Abstract: + + Region/Arena memory manager + +Author: + + Leonardo de Moura (leonardo) 2006-09-18. + +Revision History: + +--*/ +#include"region.h" + +#ifdef Z3DEBUG + +void region::display_mem_stats(std::ostream & out) const { + out << "num. objects: " << m_chuncks.size() << "\n"; +} + +#else + +#include"tptr.h" +#include"debug.h" +#include"memory_manager.h" +#include"page.h" + +inline void region::allocate_page() { + m_curr_page = allocate_default_page(m_curr_page, m_free_pages); + m_curr_ptr = m_curr_page; + m_curr_end_ptr = end_of_default_page(m_curr_page); +} + +region::region() { + m_curr_page = 0; + m_curr_ptr = 0; + m_curr_end_ptr = 0; + m_free_pages = 0; + m_mark = 0; + allocate_page(); +} + +region::~region() { + del_pages(m_curr_page); + del_pages(m_free_pages); +} + +void * region::allocate(size_t size) { + char * new_curr_ptr = m_curr_ptr + size; + if (new_curr_ptr < m_curr_end_ptr) { + char * result = m_curr_ptr; + m_curr_ptr = ALIGN(char *, new_curr_ptr); + return result; + } + else if (size < DEFAULT_PAGE_SIZE) { + allocate_page(); + char * result = m_curr_ptr; + m_curr_ptr += size; + m_curr_ptr = ALIGN(char *, m_curr_ptr); + return result; + } + else { + // big page + m_curr_page = ::allocate_page(m_curr_page, size); + char * result = m_curr_page; + allocate_page(); + return result; + } +} + +inline void region::recycle_curr_page() { + char * prev = prev_page(m_curr_page); + recycle_page(m_curr_page, m_free_pages); + m_curr_page = prev; +} + +void region::reset() { + while (m_curr_page != 0) { + recycle_curr_page(); + } + SASSERT(m_curr_page == 0); + m_curr_ptr = 0; + m_curr_end_ptr = 0; + m_mark = 0; + allocate_page(); +} + +void region::push_scope() { + char * curr_page = m_curr_page; + char * curr_ptr = m_curr_ptr; + m_mark = new (*this) mark(curr_page, curr_ptr, m_mark); +} + +void region::pop_scope() { + SASSERT(m_mark); + char * old_curr_page = m_mark->m_curr_page; + SASSERT(is_default_page(old_curr_page)); + m_curr_ptr = m_mark->m_curr_ptr; + m_mark = m_mark->m_prev_mark; + while (m_curr_page != old_curr_page) { + recycle_curr_page(); + } + SASSERT(is_default_page(m_curr_page)); + m_curr_end_ptr = end_of_default_page(m_curr_page); +} + +void region::display_mem_stats(std::ostream & out) const { + unsigned n = 0; + char * page = m_curr_page; + while (page != 0) { + n++; + page = prev_page(page); + } + out << "num. pages: " << n << "\n"; +} + +#endif + diff --git a/lib/region.h b/lib/region.h new file mode 100644 index 000000000..7f6bc787e --- /dev/null +++ b/lib/region.h @@ -0,0 +1,121 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + region.h + +Abstract: + + Region/Arena memory manager + +Author: + + Leonardo de Moura (leonardo) 2006-09-13. + +Revision History: + +--*/ +#ifndef _REGION_H_ +#define _REGION_H_ +#include +#include + +#ifdef Z3DEBUG + +#include"vector.h" + +class region { + ptr_vector m_chuncks; + unsigned_vector m_scopes; +public: + ~region() { + reset(); + } + + void * allocate(size_t size) { + char * r = alloc_svect(char, size); + m_chuncks.push_back(r); + return r; + } + + void reset() { + ptr_vector::iterator it = m_chuncks.begin(); + ptr_vector::iterator end = m_chuncks.end(); + for (; it != end; ++it) { + dealloc_svect(*it); + } + m_chuncks.reset(); + m_scopes.reset(); + } + + void push_scope() { + m_scopes.push_back(m_chuncks.size()); + } + + void pop_scope() { + unsigned old_size = m_scopes.back(); + m_scopes.pop_back(); + ptr_vector::iterator it = m_chuncks.begin() + old_size; + ptr_vector::iterator end = m_chuncks.end(); + for (; it != end; ++it) { + dealloc_svect(*it); + } + m_chuncks.shrink(old_size); + } + + void pop_scope(unsigned num_scopes) { + for (unsigned i = 0; i < num_scopes; i++) { + pop_scope(); + } + } + + void display_mem_stats(std::ostream & out) const; +}; + +#else + +/** +\brief Implement explicit region memory manager. +*/ +class region { + struct mark { + char * m_curr_page; + char * m_curr_ptr; + mark * m_prev_mark; + mark(char * page, char * ptr, mark * m):m_curr_page(page), m_curr_ptr(ptr), m_prev_mark(m) {} + }; + char * m_curr_page; + char * m_curr_ptr; //!< Next free space in the current page. + char * m_curr_end_ptr; //!< Point to the end of the current page. + char * m_free_pages; + mark * m_mark; + void allocate_page(); + void recycle_curr_page(); +public: + region(); + ~region(); + void * allocate(size_t size); + void reset(); + void push_scope(); + void pop_scope(); + void pop_scope(unsigned num_scopes) { + for (unsigned i = 0; i < num_scopes; i++) { + pop_scope(); + } + } + void display_mem_stats(std::ostream & out) const; +}; + +#endif + +inline void * operator new(size_t s, region & r) { return r.allocate(s); } + +inline void * operator new[](size_t s, region & r) { return r.allocate(s); } + +inline void operator delete(void *, region & ) { /* do nothing */ } + +inline void operator delete[](void *, region & ) { /* do nothing */ } + +#endif /* _REGION_H_ */ + diff --git a/lib/replace_proof_converter.cpp b/lib/replace_proof_converter.cpp new file mode 100644 index 000000000..45ec6fc2e --- /dev/null +++ b/lib/replace_proof_converter.cpp @@ -0,0 +1,86 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + replace_proof_converter.cpp + +Abstract: + + Proof converter that replaces asserted by sub-proof. + +Author: + + Nikolaj Bjorner (nbjorner) 2012-9-16 + +Revision History: + +--*/ + +#include "replace_proof_converter.h" +#include "expr_functors.h" +#include "ast_pp.h" +#include "for_each_expr.h" + +/** + \brief Replace expressions by other expressions. + + replace_map is caching, so inserting src |-> dst has no effect if + src is a sub-expression of something that has already been visited. + The assumption is that proof replacements are inserted into + the replace_proof_converter in the order that they are introduced, so + there are no such clashes. + + map_proc is used as expr_replacer behaves differently + when proof mode is turned on. +*/ +class replace_map : public map_proc { +public: + replace_map(ast_manager& m): map_proc(m) {} + + void insert(expr* src, expr* dst) { + m_map.insert(src, dst, 0); + } + + void operator()(var* v) { visit(v); } + void operator()(app* a) { if (!get_expr(a)) { reconstruct(a); } } + void operator()(quantifier* q) { visit(q); } + + void apply(expr_ref& e) { + for_each_expr(*this, e); + e = get_expr(e); + } +}; + + +void replace_proof_converter::operator()(ast_manager & m, unsigned num_source, + proof * const * source, proof_ref & result) { + SASSERT(num_source == 1); + replace_map replace(m); + proof_ref p(m); + expr_ref tmp(source[0], m), e(m), f(m); + + // apply the substitution to the prefix before inserting it. + for (unsigned i = 0; i < m_proofs.size(); ++i) { + p = m_proofs[i].get(); + e = p; + replace.apply(e); + f = m.mk_asserted(m.get_fact(p)); + replace.insert(f, e); + TRACE("proof_converter", tout << f->get_id() << " " << mk_pp(f, m) << + "\n|-> " << mk_pp(e, m) << "\n";); + } + replace.apply(tmp); + TRACE("proof_converter", tout << mk_pp(source[0], m) << "\n"; + tout << mk_pp(tmp.get(), m) << "\n";); + result = to_app(tmp); +} + +proof_converter * replace_proof_converter::translate(ast_translation & translator) { + replace_proof_converter* rp = alloc(replace_proof_converter, m); + for (unsigned i = 0; i < m_proofs.size(); ++i) { + rp->insert(translator(m_proofs[i].get())); + } + return rp; +} + diff --git a/lib/replace_proof_converter.h b/lib/replace_proof_converter.h new file mode 100644 index 000000000..8a96416db --- /dev/null +++ b/lib/replace_proof_converter.h @@ -0,0 +1,47 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + replace_proof_converter.h + +Abstract: + + Proof converter to replace asserted leaves by proofs. + + Given a proof P with occurrences of [asserted fml] + Replace [asserted fml] by proofs whose conclusions are fml. + +Author: + + Nikolaj Bjorner (nbjorner) 2012-9-16 + +Revision History: + +--*/ + +#ifndef _REPLACE_PROOF_CONVERTER_H_ +#define _REPLACE_PROOF_CONVERTER_H_ + +#include "proof_converter.h" + +class replace_proof_converter : public proof_converter { + ast_manager& m; + proof_ref_vector m_proofs; +public: + + replace_proof_converter(ast_manager& m): m(m), m_proofs(m) {} + + virtual ~replace_proof_converter() {} + + virtual void operator()(ast_manager & m, unsigned num_source, proof * const * source, proof_ref & result); + + virtual proof_converter * translate(ast_translation & translator); + + void insert(proof* p) { m_proofs.push_back(p); } + + ast_manager& get_manager() { return m; } + +}; + +#endif diff --git a/lib/resource_limit.h b/lib/resource_limit.h new file mode 100644 index 000000000..06d059bcf --- /dev/null +++ b/lib/resource_limit.h @@ -0,0 +1,168 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + resource_limit.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-04-18. + +Revision History: + +--*/ +#ifndef _RESOURCE_LIMIT_H_ +#define _RESOURCE_LIMIT_H_ + +// +// This object is used to limit the availability of "resources" during a search. +// The main applications: +// +// - limiting the number of conflicts. +// +// - limiting the number of branching&bound steps in the ILP +// +// - limiting the number of quantifier instantiations. +// +// - etc. +// +// The idea is that when the resources are exhausted, the bounded_search terminates with +// the undefined result. +// +// Then, the search can restart with bigger limits. +class resource_limit { +public: + virtual ~resource_limit() { + } + + virtual const char * get_description() const = 0; + + // - Reset counters associated with the limit. + virtual void reset_counters() = 0; + + // - The limit was exhausted. + virtual bool exhausted() const = 0; + + // - Return true if the limit is incremented. + virtual bool inc_limit() = 0; + + virtual int get_limit() const = 0; + + virtual void reset() = 0; +}; + +class base_resource_limit : public resource_limit { +protected: + int m_counter; + int m_curr_limit; + int m_init_limit; + int m_max_limit; // <0 if unbounded + const char * m_description; +public: + + base_resource_limit(int init_limit, int max_limit, const char * desc): + m_counter(0), + m_curr_limit(init_limit), + m_init_limit(init_limit), + m_max_limit(max_limit), + m_description(desc) { + } + + virtual ~base_resource_limit() { + } + + int get_counter() const { + return m_counter; + } + + bool inc_counter() { + m_counter++; + return m_counter <= m_curr_limit; + } + + void set_init_limit(int l) { + m_init_limit = l; + m_curr_limit = l; + } + + void set_max_limit(int l) { + m_max_limit = l; + } + + virtual const char * get_description() const { + return m_description; + } + + virtual void reset_counters() { + m_counter = 0; + } + + virtual bool exhausted() const { + return m_counter >= m_curr_limit; + } + + virtual int get_limit() const { + return m_curr_limit; + } + + virtual void reset() { + m_counter = 0; + m_curr_limit = m_init_limit; + } +}; + +class li_resource_limit : public base_resource_limit { + int m_increment; +public: + + li_resource_limit(int init_limit, int max_limit, int inc, const char * desc): + base_resource_limit(init_limit, max_limit, desc), + m_increment(inc) { + } + + virtual ~li_resource_limit() { + } + + virtual bool inc_limit() { + int new_limit = m_curr_limit + m_increment; + if (m_max_limit < 0 || new_limit <= m_max_limit) { + m_curr_limit = new_limit; + return true; + } + TRACE("resource_limit", tout << new_limit << " exhausts " << m_max_limit << "\n";); + return false; + } + +}; + +class ei_resource_limit : public base_resource_limit { + double m_increment_ratio; +public: + + ei_resource_limit(int init_limit, int max_limit, double inc, const char * desc): + base_resource_limit(init_limit, max_limit, desc), + m_increment_ratio(inc) { + } + + virtual ~ei_resource_limit() { + } + + virtual bool inc_limit() { + int new_limit = static_cast(m_curr_limit * m_increment_ratio); + if (m_max_limit < 0 || new_limit <= m_max_limit) { + m_curr_limit = new_limit; + return true; + } + TRACE("resource_limit", tout << new_limit << " exhausts " << m_max_limit << "\n";); + return false; + } +}; + + +#endif /* _RESOURCE_LIMIT_H_ */ + diff --git a/lib/rewriter.cpp b/lib/rewriter.cpp new file mode 100644 index 000000000..e9f66f10c --- /dev/null +++ b/lib/rewriter.cpp @@ -0,0 +1,402 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + rewriter.cpp + +Abstract: + + Lean and mean rewriter + +Author: + + Leonardo (leonardo) 2011-03-31 + +Notes: + +--*/ +#include"rewriter_def.h" +#include"ast_ll_pp.h" +#include"ast_smt2_pp.h" + +void rewriter_core::init_cache_stack() { + SASSERT(m_cache_stack.empty()); + m_cache = alloc(cache, m()); + m_cache_stack.push_back(m_cache); + if (m_proof_gen) { + SASSERT(m_cache_pr_stack.empty()); + m_cache_pr = alloc(cache, m()); + m_cache_pr_stack.push_back(m_cache_pr); + } +} + +void rewriter_core::del_cache_stack() { + std::for_each(m_cache_stack.begin(), m_cache_stack.end(), delete_proc()); + m_cache_stack.finalize(); + m_cache = 0; + if (m_proof_gen) { + std::for_each(m_cache_pr_stack.begin(), m_cache_pr_stack.end(), delete_proc()); + m_cache_pr_stack.finalize(); + m_cache_pr = 0; + } +} + +void rewriter_core::cache_result(expr * k, expr * v) { +#if 0 + // trace for tracking cache usage + verbose_stream() << "1 " << k->get_id() << std::endl; +#endif + SASSERT(!m_proof_gen); + + TRACE("rewriter_cache_result", tout << mk_ismt2_pp(k, m()) << "\n--->\n" << mk_ismt2_pp(v, m()) << "\n";); + + m_cache->insert(k, v); +#if 0 + static unsigned num_cached = 0; + num_cached ++; + if (num_cached % 100000 == 0) + verbose_stream() << "[rewriter] :num-cached " << num_cached << " :capacity " << m_cache->capacity() << " :size " << m_cache->size() + << " :frame-stack-size " << m_frame_stack.size() << std::endl; +#endif +} + +void rewriter_core::cache_result(expr * k, expr * v, proof * pr) { + m_cache->insert(k, v); + SASSERT(m_proof_gen); + m_cache_pr->insert(k, pr); +} + +unsigned rewriter_core::get_cache_size() const { + return m_cache->size(); +} + +void rewriter_core::reset_cache() { + m_cache = m_cache_stack[0]; + m_cache->reset(); + if (m_proof_gen) { + m_cache_pr = m_cache_pr_stack[0]; + m_cache_pr->reset(); + } +} + +// free memory allocated by the rewriter +void rewriter_core::free_memory() { + del_cache_stack(); + m_frame_stack.finalize(); + m_result_stack.finalize(); + m_scopes.finalize(); +} + +void rewriter_core::begin_scope() { + m_scopes.push_back(scope(m_root, m_num_qvars)); + unsigned lvl = m_scopes.size(); + SASSERT(lvl <= m_cache_stack.size()); + SASSERT(!m_proof_gen || m_cache_pr_stack.size() == m_cache_stack.size()); + if (lvl == m_cache_stack.size()) { + m_cache_stack.push_back(alloc(cache, m())); + if (m_proof_gen) + m_cache_pr_stack.push_back(alloc(cache, m())); + } + m_cache = m_cache_stack[lvl]; + m_cache->reset(); + SASSERT(m_cache->empty()); + if (m_proof_gen) { + m_cache_pr = m_cache_pr_stack[lvl]; + m_cache_pr->reset(); + SASSERT(m_cache_pr->empty()); + } +} + +void rewriter_core::end_scope() { + m_cache->reset(); + if (m_proof_gen) + m_cache_pr->reset(); + scope & s = m_scopes.back(); + m_root = s.m_old_root; + m_num_qvars = s.m_old_num_qvars; + m_scopes.pop_back(); + unsigned new_lvl = m_scopes.size(); + m_cache = m_cache_stack[new_lvl]; + if (m_proof_gen) + m_cache_pr = m_cache_pr_stack[new_lvl]; +} + +bool rewriter_core::is_child_of_top_frame(expr * t) const { + if (m_frame_stack.empty()) + return true; + frame const & fr = m_frame_stack.back(); + expr * parent = fr.m_curr; + unsigned num; + switch (parent->get_kind()) { + case AST_APP: + num = to_app(parent)->get_num_args(); + for (unsigned i = 0; i < num; i++) { + if (to_app(parent)->get_arg(i) == t) + return true; + } + return false; + case AST_QUANTIFIER: + num = to_quantifier(parent)->get_num_children(); + for (unsigned i = 0; i < num; i++) { + if (to_quantifier(parent)->get_child(i) == t) + return true; + } + return false; + default: + return false; + } +} + +/** + \brief Eliminate (implicit) reflexivity proofs from m_result_pr_stack starting at position spos. + The implicit reflexivity proof is 0. +*/ +void rewriter_core::elim_reflex_prs(unsigned spos) { + SASSERT(m_proof_gen); + unsigned sz = m_result_pr_stack.size(); + SASSERT(spos <= sz); + unsigned j = spos; + for (unsigned i = spos; i < sz; i++) { + proof * pr = m_result_pr_stack.get(i); + if (pr != 0) { + if (i != j) + m_result_pr_stack.set(j, pr); + j++; + } + } + m_result_pr_stack.shrink(j); +} + +rewriter_core::rewriter_core(ast_manager & m, bool proof_gen): + m_manager(m), + m_proof_gen(proof_gen), + m_result_stack(m), + m_result_pr_stack(m), + m_num_qvars(0) { + init_cache_stack(); +} + +rewriter_core::~rewriter_core() { + del_cache_stack(); +} + +// reset rewriter (macro definitions are not erased) +void rewriter_core::reset() { + SASSERT(!m_cache_stack.empty()); + reset_cache(); + m_frame_stack.reset(); + m_result_stack.reset(); + if (m_proof_gen) + m_result_pr_stack.reset(); + m_root = 0; + m_num_qvars = 0; + m_scopes.reset(); +} + +// free memory & reset (macro definitions are not erased) +void rewriter_core::cleanup() { + free_memory(); + init_cache_stack(); + m_root = 0; + m_num_qvars = 0; +} + + +#ifdef _TRACE +void rewriter_core::display_stack(std::ostream & out, unsigned pp_depth) { + svector::iterator it = m_frame_stack.begin(); + svector::iterator end = m_frame_stack.end(); + for (; it != end; ++it) { + out << mk_bounded_pp(it->m_curr, m(), pp_depth) << "\n"; + out << "state: " << it->m_state << "\n"; + out << "cache: " << it->m_cache_result << ", new_child: " << it->m_new_child << ", max-depth: " << it->m_max_depth << ", i: " << it->m_i << "\n"; + out << "------------------\n"; + } +} +#endif + +bool var_shifter_core::visit(expr * t) { + if (is_ground(t)) { + m_result_stack.push_back(t); + return true; + } + bool c = must_cache(t); + if (c) { + expr * r = get_cached(t); + if (r) { + m_result_stack.push_back(r); + set_new_child_flag(t, r); + return true; + } + } + switch (t->get_kind()) { + case AST_APP: + SASSERT(to_app(t)->get_num_args() > 0); + push_frame(t, c); + return false; + case AST_VAR: + process_var(to_var(t)); + return true; + case AST_QUANTIFIER: + push_frame(t, c); + return false; + default: + UNREACHABLE(); + return true; + } +} + +void var_shifter_core::process_app(app * t, frame & fr) { + unsigned num_args = t->get_num_args(); + while (fr.m_i < num_args) { + expr * arg = t->get_arg(fr.m_i); + fr.m_i++; + if (!visit(arg)) + return; + } + SASSERT(fr.m_spos + num_args == m_result_stack.size()); + expr * new_t; + if (fr.m_new_child) { + expr * const * new_args = m_result_stack.c_ptr() + fr.m_spos; + new_t = m().mk_app(t->get_decl(), num_args, new_args); + } + else { + new_t = t; + } + m_result_stack.shrink(fr.m_spos); + m_result_stack.push_back(new_t); + m_frame_stack.pop_back(); + set_new_child_flag(t, new_t); + if (fr.m_cache_result) + cache_result(t, new_t); +} + +void var_shifter_core::process_quantifier(quantifier * q, frame & fr) { + if (fr.m_i == 0) { + begin_scope(); + m_num_qvars += q->get_num_decls(); + m_root = q->get_expr(); + } + unsigned num_children = q->get_num_children(); + while (fr.m_i < num_children) { + expr * child = q->get_child(fr.m_i); + fr.m_i++; + if (!visit(child)) + return; + } + SASSERT(fr.m_spos + num_children == m_result_stack.size()); + expr * new_q; + if (fr.m_new_child) { + expr * const * it = m_result_stack.c_ptr() + fr.m_spos; + expr * new_expr = *it; + ++it; + expr * const * new_pats = it; + expr * const * new_no_pats = new_pats + q->get_num_patterns(); + new_q = m().update_quantifier(q, q->get_num_patterns(), new_pats, q->get_num_no_patterns(), new_no_pats, new_expr); + } + else { + new_q = q; + } + m_result_stack.shrink(fr.m_spos); + m_result_stack.push_back(new_q); + m_frame_stack.pop_back(); + set_new_child_flag(q, new_q); + end_scope(); + if (fr.m_cache_result) + cache_result(q, new_q); +} + +void var_shifter_core::main_loop(expr * t, expr_ref & r) { + SASSERT(m_cache == m_cache_stack[0]); + SASSERT(m_frame_stack.empty()); + SASSERT(m_result_stack.empty()); + m_root = t; + + if (visit(t)) { + r = m_result_stack.back(); + m_result_stack.pop_back(); + SASSERT(m_result_stack.empty()); + return; + } + SASSERT(!m_frame_stack.empty()); + while (!m_frame_stack.empty()) { + frame & fr = m_frame_stack.back(); + expr * t = fr.m_curr; + if (fr.m_i == 0 && fr.m_cache_result) { + expr * r = get_cached(t); + if (r) { + m_result_stack.push_back(r); + m_frame_stack.pop_back(); + set_new_child_flag(t, r); + continue; + } + } + switch (t->get_kind()) { + case AST_APP: + process_app(to_app(t), fr); + break; + case AST_QUANTIFIER: + process_quantifier(to_quantifier(t), fr); + break; + default: + UNREACHABLE(); + } + } + r = m_result_stack.back(); + m_result_stack.pop_back(); + SASSERT(m_result_stack.empty()); +} + +void var_shifter::operator()(expr * t, unsigned bound, unsigned shift1, unsigned shift2, expr_ref & r) { + if (is_ground(t)) { + r = t; + return; + } + reset_cache(); + m_bound = bound; + m_shift1 = shift1; + m_shift2 = shift2; + main_loop(t, r); +} + +void var_shifter::process_var(var * v) { + unsigned vidx = v->get_idx(); + if (vidx < m_num_qvars) { + m_result_stack.push_back(v); + } + else { + unsigned nvidx = vidx - m_num_qvars; + if (nvidx >= m_bound) + vidx += m_shift1; + else + vidx += m_shift2; + m_result_stack.push_back(m().mk_var(vidx, v->get_sort())); + set_new_child_flag(v); + } +} + +void inv_var_shifter::operator()(expr * t, unsigned shift, expr_ref & r) { + if (is_ground(t)) { + r = t; + return; + } + reset_cache(); + m_shift = shift; + main_loop(t, r); +} + +void inv_var_shifter::process_var(var * v) { + unsigned vidx = v->get_idx(); + if (vidx < m_num_qvars) { + m_result_stack.push_back(v); + } + else { + SASSERT(vidx >= m_num_qvars + m_shift); + vidx -= m_shift; + m_result_stack.push_back(m().mk_var(vidx, v->get_sort())); + set_new_child_flag(v); + } +} + +template class rewriter_tpl; diff --git a/lib/rewriter.h b/lib/rewriter.h new file mode 100644 index 000000000..a9b9bdb4f --- /dev/null +++ b/lib/rewriter.h @@ -0,0 +1,399 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + rewriter.h + +Abstract: + + Lean and mean rewriter + +Author: + + Leonardo (leonardo) 2011-03-31 + +Notes: + +--*/ +#ifndef _REWRITER_H_ +#define _REWRITER_H_ + +#include"ast.h" +#include"rewriter_types.h" +#include"act_cache.h" + +/** + \brief Common infrastructure for AST rewriters. +*/ +class rewriter_core { +protected: + struct frame { + expr * m_curr; + unsigned m_cache_result:1; // true if the result of rewriting m_expr must be cached. + unsigned m_new_child:1; + unsigned m_state:2; + unsigned m_max_depth:2; // bounded rewrite... if m_max_depth == 0, then children are not rewritten. + unsigned m_i:26; + unsigned m_spos; // top of the result stack, when the frame was created. + frame(expr * n, bool cache_res, unsigned st, unsigned max_depth, unsigned spos): + m_curr(n), + m_cache_result(cache_res), + m_new_child(false), + m_state(st), + m_max_depth(max_depth), + m_i(0), + m_spos(spos) { + } + }; + ast_manager & m_manager; + bool m_proof_gen; + typedef act_cache cache; + ptr_vector m_cache_stack; + cache * m_cache; // current cache. + svector m_frame_stack; + expr_ref_vector m_result_stack; + + // proof generation goodness ---- + ptr_vector m_cache_pr_stack; + cache * m_cache_pr; + proof_ref_vector m_result_pr_stack; + // -------------------------- + + expr * m_root; + unsigned m_num_qvars; + struct scope { + expr * m_old_root; + unsigned m_old_num_qvars; + scope(expr * r, unsigned n):m_old_root(r), m_old_num_qvars(n) {} + }; + svector m_scopes; + + // Return true if the rewriting result of the given expression must be cached. + bool must_cache(expr * t) const { + return + t->get_ref_count() > 1 && // t must be a shared expression + t != m_root && // t must not be the root expression + ((is_app(t) && to_app(t)->get_num_args() > 0) || is_quantifier(t)); // t is a (non-constant) application or a quantifier. + } + + void push_frame_core(expr * t, bool cache_res, unsigned st = 0, unsigned max_depth = RW_UNBOUNDED_DEPTH) { + SASSERT(!m_proof_gen || m_result_stack.size() == m_result_pr_stack.size()); + m_frame_stack.push_back(frame(t, cache_res, st, max_depth, m_result_stack.size())); + } + + void push_frame(expr * t, unsigned st = 0) { + push_frame_core(t, must_cache(t), st); + } + + void init_cache_stack(); + void del_cache_stack(); + void reset_cache(); + void cache_result(expr * k, expr * v); + expr * get_cached(expr * k) const { return m_cache->find(k); } + + void cache_result(expr * k, expr * v, proof * pr); + proof * get_cached_pr(expr * k) const { return static_cast(m_cache_pr->find(k)); } + + void free_memory(); + void begin_scope(); + void end_scope(); + bool is_child_of_top_frame(expr * t) const; + void set_new_child_flag(expr * old_t) { + CTRACE("rewriter_bug", !is_child_of_top_frame(old_t), display_stack(tout, 3);); + SASSERT(is_child_of_top_frame(old_t)); + if (!m_frame_stack.empty()) + m_frame_stack.back().m_new_child = true; + } + void set_new_child_flag(expr * old_t, expr * new_t) { if (old_t != new_t) set_new_child_flag(old_t); } + + void elim_reflex_prs(unsigned spos); +public: + rewriter_core(ast_manager & m, bool proof_gen); + ~rewriter_core(); + ast_manager & m() const { return m_manager; } + void reset(); + void cleanup(); +#ifdef _TRACE + void display_stack(std::ostream & out, unsigned pp_depth); +#endif + unsigned get_cache_size() const; +}; + +class var_shifter_core : public rewriter_core { +protected: + bool visit(expr * t); + void process_app(app * t, frame & fr); + virtual void process_var(var * v) = 0; + void process_quantifier(quantifier * q, frame & fr); + void main_loop(expr * t, expr_ref & r); +public: + var_shifter_core(ast_manager & m):rewriter_core(m, false) {} +}; + +/** + \brief Functor used to shift the free variables of an AST by a given amount. + This functor is used by the var_subst functor. + + This functor implements the following functions: + 1) shift(n, s) will return a new AST where all free variables (VAR i) in n, + are replaced by (VAR (+ i s)). + + 2) shift(n, b, s1, s2) is a variant of the previous function such that + for a each free variable (VAR i) is shifted to: + - (VAR i + s1) if i >= b + - (VAR i + s2) if i < b +*/ +class var_shifter : public var_shifter_core { + unsigned m_bound; + unsigned m_shift1; + unsigned m_shift2; + virtual void process_var(var * v); +public: + var_shifter(ast_manager & m):var_shifter_core(m) {} + void operator()(expr * t, unsigned bound, unsigned shift1, unsigned shift2, expr_ref & r); + void operator()(expr * t, unsigned s, expr_ref & r) { + operator()(t, 0, s, 0, r); + } +}; + +/** + \brief Functor used to shift the free variables of an AST by a negative amount. + + Abstract implementation: + + inv_shift(f(c_1, ..., c_n), d, s) = + f(inv_shift(c_1, d, s), ..., inv_shift(c_n, d, s)) + inv_shift(var(i), d, s) = + if (i < d) + var(i) + else + assert(i - s >= d) + var(i-s) + inv_shift((forall (x) P), d, s) = + (forall (x) inv_shift(P, d+1, s)) + + This functor assumes that if we are shifting an expression F by N, then F + does not contain free variables #0, ... #N-1 + + See assertion above. +*/ +class inv_var_shifter : public var_shifter_core { +protected: + unsigned m_shift; + virtual void process_var(var * v); +public: + inv_var_shifter(ast_manager & m):var_shifter_core(m) {} + void operator()(expr * t, unsigned shift, expr_ref & r); +}; + +template +class rewriter_tpl : public rewriter_core { +protected: + // Rewriter maintains a stack of frames. + // Each frame represents an expression that is being rewritten. + // The resultant expressions are store on the Result stack. + // Each frame is in a particular state. + // Let f(t_0,...,t_n) be the expression is the frame Fr. Then Fr can be is one of the + // following states: + // PROCESS_CHILDREN(i) the children t_0, ..., t_{i-1} have already been rewritten, and the result is on the Result stack. + // REWRITE_BUILTIN All t_0, ..., t_n have been rewritten to t_0',...,t_n', and the builtin rewriter (or plugin) produced a new term t + // that can be further rewritten. + // EXPAND_DEF All t_0, ..., t_n have been rewritten to t_0',...,t_n'. + // The function symbol f is a macro, and the body of the macro needs to be rewritten using the t_0',...,t_n' as bindings. + // REWRITE_RULE All t_0, ..., t_n have been rewritten to t_0',...,t_n'. + // There is rewrite rule lhs -> rhs s.t. f(t_0', ..., t_n') matches lhs with substitution delta. + // rhs is then rewritten using delta. + enum state { + PROCESS_CHILDREN, + REWRITE_BUILTIN, + EXPAND_DEF, + REWRITE_RULE + }; + Config & m_cfg; + unsigned m_num_steps; + volatile bool m_cancel; + ptr_vector m_bindings; + var_shifter m_shifter; + expr_ref m_r; + proof_ref m_pr; + proof_ref m_pr2; + + svector & frame_stack() { return this->m_frame_stack; } + svector const & frame_stack() const { return this->m_frame_stack; } + expr_ref_vector & result_stack() { return this->m_result_stack; } + expr_ref_vector const & result_stack() const { return this->m_result_stack; } + proof_ref_vector & result_pr_stack() { return this->m_result_pr_stack; } + proof_ref_vector const & result_pr_stack() const { return this->m_result_pr_stack; } + + void set_new_child_flag(expr * old_t) { + SASSERT(frame_stack().empty() || frame_stack().back().m_state != PROCESS_CHILDREN || this->is_child_of_top_frame(old_t)); + if (!frame_stack().empty()) + frame_stack().back().m_new_child = true; + } + void set_new_child_flag(expr * old_t, expr * new_t) { if (old_t != new_t) set_new_child_flag(old_t); } + + + // cache the result of shared non atomic expressions. + bool cache_results() const { return m_cfg.cache_results(); } + // cache all results share and non shared expressions non atomic expressions. + bool cache_all_results() const { return m_cfg.cache_all_results(); } + // flat non shared AC terms + bool flat_assoc(func_decl * f) const { return m_cfg.flat_assoc(f); } + // rewrite patterns + bool rewrite_patterns() const { return m_cfg.rewrite_patterns(); } + + // check maximum number of scopes + void check_max_scopes() const { + if (m_cfg.max_scopes_exceeded(this->m_scopes.size())) + throw rewriter_exception(TACTIC_MAX_SCOPES_MSG); + } + // check maximum size of the frame stack + void check_max_frames() const { + if (m_cfg.max_frames_exceeded(frame_stack().size())) + throw rewriter_exception(TACTIC_MAX_FRAMES_MSG); + } + // check maximum number of rewriting steps + void check_max_steps() const { + if (m_cfg.max_steps_exceeded(m_num_steps)) + throw rewriter_exception(TACTIC_MAX_STEPS_MSG); + } + + // If pre_visit returns false, then t children are not visited/rewritten. + // This should be used with care, since it may produce incorrect results + // when the rewriter is used to apply variable substitution. + bool pre_visit(expr * t) { + return m_cfg.pre_visit(t); + } + + // Return true if the rewriting result of the given expression must be cached. + bool must_cache(expr * t) const { + if (cache_all_results()) + return t != this->m_root && ((is_app(t) && to_app(t)->get_num_args() > 0) || is_quantifier(t)); + if (cache_results()) + return rewriter_core::must_cache(t); + return false; + } + + bool get_macro(func_decl * f, expr * & def, quantifier * & q, proof * & def_pr) { + return m_cfg.get_macro(f, def, q, def_pr); + } + + void push_frame(expr * t, bool mcache, unsigned max_depth) { + check_max_frames(); + push_frame_core(t, mcache, PROCESS_CHILDREN, max_depth); + } + + void begin_scope() { + check_max_scopes(); + rewriter_core::begin_scope(); + } + + template + void process_var(var * v); + + template + void process_const(app * t); + + template + bool visit(expr * t, unsigned max_depth); + + template + void cache_result(expr * t, expr * new_t, proof * pr, bool c) { + if (c) { + if (!ProofGen) + rewriter_core::cache_result(t, new_t); + else + rewriter_core::cache_result(t, new_t, pr); + } + } + + template + void process_app(app * t, frame & fr); + + template + void process_quantifier(quantifier * q, frame & fr); + + bool first_visit(frame & fr) const { + return fr.m_state == PROCESS_CHILDREN && fr.m_i == 0; + } + + bool not_rewriting() const; + + template + void main_loop(expr * t, expr_ref & result, proof_ref & result_pr); + + template + void resume_core(expr_ref & result, proof_ref & result_pr); + +public: + rewriter_tpl(ast_manager & m, bool proof_gen, Config & cfg); + + ast_manager & m() const { return this->m_manager; } + Config & cfg() { return m_cfg; } + Config const & cfg() const { return m_cfg; } + + void set_cancel(bool f) { m_cancel = f; } + void cancel() { set_cancel(true); } + void reset_cancel() { set_cancel(false); } + + ~rewriter_tpl(); + + void reset(); + void cleanup(); + + void set_bindings(unsigned num_bindings, expr * const * bindings); + void set_inv_bindings(unsigned num_bindings, expr * const * bindings); + void operator()(expr * t, expr_ref & result, proof_ref & result_pr); + void operator()(expr * t, expr_ref & result) { operator()(t, result, m_pr); } + void operator()(expr * n, unsigned num_bindings, expr * const * bindings, expr_ref & result) { + SASSERT(!m_proof_gen); + reset(); + set_inv_bindings(num_bindings, bindings); + operator()(n, result); + } + + void resume(expr_ref & result, proof_ref & result_pr); + void resume(expr_ref & result) { resume(result, m_pr); } + + // Return the number of steps performed by the rewriter in the last call to operator(). + unsigned get_num_steps() const { return m_num_steps; } +}; + +struct default_rewriter_cfg { + bool cache_all_results() const { return false; } + bool cache_results() const { return true; } + bool flat_assoc(func_decl * f) const { return false; } + bool rewrite_patterns() const { return true; } + bool max_scopes_exceeded(unsigned num_scopes) const { return false; } + bool max_frames_exceeded(unsigned num_frames) const { return false; } + bool max_steps_exceeded(unsigned num_steps) const { return false; } + bool pre_visit(expr * t) { return true; } + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { return BR_FAILED; } + bool reduce_quantifier(quantifier * old_q, + expr * new_body, + expr * const * new_patterns, + expr * const * new_no_patterns, + expr_ref & result, + proof_ref & result_pr) { + return false; + } + bool reduce_var(var * t, expr_ref & result, proof_ref & result_pr) { return false; } + bool get_macro(func_decl * d, expr * & def, quantifier * & q, proof * & def_pr) { return false; } + bool get_subst(expr * s, expr * & t, proof * & t_pr) { return false; } + void reset() {} + void cleanup() {} +}; + +struct beta_reducer_cfg : public default_rewriter_cfg { + bool pre_visit(expr * t) { return !is_ground(t); } +}; + +class beta_reducer : public rewriter_tpl { + beta_reducer_cfg m_cfg; +public: + beta_reducer(ast_manager & m): + rewriter_tpl(m, false, m_cfg) {} +}; + +#endif diff --git a/lib/rewriter.txt b/lib/rewriter.txt new file mode 100644 index 000000000..84dd85e15 --- /dev/null +++ b/lib/rewriter.txt @@ -0,0 +1,145 @@ + + +The following classes implement theory specific rewriting rules: + - bool_rewriter + - arith_rewriter + - bv_rewriter + - array_rewriter + - datatype_rewriter + +Each of the provide the method + br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) +where + - f is expected to be a func_decl of the given theory + - If the return value if different from BR_FAILED, the result is stored in result. + +The template rewriter_tpl can be instantiated to implement +rewriters that traverse ASTs applying some kind of transformation/simplification. +The parameter Cfg is expected to be a class with the following methods: + bool cache_all_results() const; + bool cache_results() const; + bool flat_assoc(func_decl * f) const; + bool rewrite_patterns() const; + bool max_scopes_exceeded(unsigned num_scopes) const; + bool max_frames_exceeded(unsigned num_frames) const; + bool max_steps_exceeded(unsigned num_steps) const; + bool pre_visit(expr * t); + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr); + bool reduce_quantifier(quantifier * old_q, + expr * new_body, + expr * const * new_patterns, + expr * const * new_no_patterns, + expr_ref & result, + proof_ref & result_pr); + bool get_macro(func_decl * d, expr * & def, quantifier * & q, proof * & def_pr); + bool get_subst(expr * s, expr * & t, proof * & t_pr); + void reset(); + void cleanup(); + +The class default_rewriter_cfg provides a default implementation for these methods. + +The method reduce_app is the most important one. When implementing new rewriter, +we usually redefine this method. +We should return BR_FAILED, if we do not want to apply a "simplification" to + (f args[0] ... args[num-1]) +This is very important for performance reasons, since the rewriter tries to +reuse AST when BR_FAILED is returned. +If one wants to simply (f args[0] ... args[num-1]) to t, then use: + result = t; + return BR_DONE; +Sometimes, by applying a simplification rule we may create new opportunites for +rewriting. One can instruct the rewriter to revisit the result by returning: + BR_REWRITE1, // rewrite the result (bounded by depth 1) + BR_REWRITE2, // rewrite the result (bounded by depth 2) + BR_REWRITE3, // rewrite the result (bounded by depth 3) + BR_REWRITE_FULL, // rewrite the result unbounded +Example: +Suppose one wants to implement the rewriting rule + (= (ite c t e) v) --> (ite c (= t v) (= e v)) +This rule may create new opportunites for simplification since +it creates the equalities (= t v) and (= e v). +So, to force the rewriter to process these new equalities, +BR_REWRITE2 should be returned. +It is also correct (but unnecessary) to return BR_REWRITE3 and BR_REWRITE_FULL. + +To instantiate a new rewriter, one should +1) Define a new configuration class. + class mycfg : public default_rewriter_cfg { + ... + } +2) Force template instantiation using the new cfg. + templace class rewriter_tpl; + +3) Define a subclass of rewriter_tpl that + owns the new cfg. + + class myrewriter : public rewriter_tpl { + mycfg m_cfg; + public: + myrewriter(ast_manager & m): + rewriter_tpl(m, m.proofs_enabled(), m_cfg), + m_cfg(...) { + } + }; + +The rewriter_tpl template supports proof production. +It can be disabled even when proof productions is +enabled in the ast_manager. + +Examples of rewriter_tpl instances: +- th_rewriter.cpp +- assertion_set_bit_blaster.cpp +- model_evaluator.cpp +- elim_distinct.cpp + +Notes for additional Cfg methods: +- bool rewrite_patterns() const; +If false is returned, then the rewriter will ignore patterns. +- bool pre_visit(expr * t); +If false is returned, then the rewriter will not process t. +This is very useful, for example, for implementing rewriters that will only +"process" the Boolean skeleton. +- bool max_steps_exceeded(unsigned num_steps) const; +If false, it interrupts the execution of the rewriter. +The interruption is performed by throwing an exception. +One can also used this callback to check whether the +amount of memory used is still reasonable. +- bool cache_all_results() const; +By default, the rewriter only caches the results of +non-atomic shared ASTs (get_ref_count() > 1). If true, +is returned, the rewriter will cache all intermediate results. +- bool flat_assoc(func_decl * f) const; +If true is returned, then the rewriter will do flattening of +non-shared nodes. For example, + (+ a (+ b (+ c d))) +will be treated as (+ a b c d) +Shared nodes are not considered. Thus, exponential blowup +in the rewriter stack is avoided. +So, when implementing + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr); +one should not assume that for an associative f, there is not +f-application in args when true is returned by flat_assoc. + +when implementing + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr); +If result_pr is set to 0, and proofs are enabled, then the rewriter will use a generic +"rewrite" proof step for justifying that (f args[0] ... args[num-1]) is equal to result. + +The th_rewriter takes care of flattening. So, in principle, there is +not need to return true in flat_assoc. + + + + + + + + + + + + + + + + diff --git a/lib/rewriter_def.h b/lib/rewriter_def.h new file mode 100644 index 000000000..0bdb08c13 --- /dev/null +++ b/lib/rewriter_def.h @@ -0,0 +1,642 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + rewriter_def.h + +Abstract: + + Lean and mean rewriter + +Author: + + Leonardo (leonardo) 2011-03-31 + +Notes: + +--*/ +#include"rewriter.h" +#include"ast_smt2_pp.h" + +template +template +void rewriter_tpl::process_var(var * v) { + if (m_cfg.reduce_var(v, m_r, m_pr)) { + result_stack().push_back(m_r); + if (ProofGen) { + result_pr_stack().push_back(m_pr); + m_pr = 0; + } + set_new_child_flag(v); + m_r = 0; + return; + } + if (!ProofGen) { + // bindings are only used when Proof Generation is not enabled. + unsigned idx = v->get_idx(); + if (idx < m_bindings.size()) { + expr * r = m_bindings[m_bindings.size() - idx - 1]; + TRACE("process_var", tout << "idx: " << idx << " --> " << mk_ismt2_pp(r, m()) << "\n"; + tout << "bindings:\n"; + for (unsigned i = 0; i < m_bindings.size(); i++) tout << i << ": " << mk_ismt2_pp(m_bindings[i], m()) << "\n";); + if (r != 0) { + if (m_num_qvars == 0 || is_ground(r)) { + result_stack().push_back(r); + } + else { + expr_ref new_term(m()); + m_shifter(r, m_num_qvars, new_term); + result_stack().push_back(new_term); + } + set_new_child_flag(v); + return; + } + } + } + result_stack().push_back(v); + if (ProofGen) + result_pr_stack().push_back(0); // implicit reflexivity +} + +template +template +void rewriter_tpl::process_const(app * t) { + SASSERT(t->get_num_args() == 0); + br_status st = m_cfg.reduce_app(t->get_decl(), 0, 0, m_r, m_pr); + SASSERT(st == BR_FAILED || st == BR_DONE); + if (st == BR_DONE) { + result_stack().push_back(m_r.get()); + if (ProofGen) { + if (m_pr) + result_pr_stack().push_back(m_pr); + else + result_pr_stack().push_back(m().mk_rewrite(t, m_r)); + m_pr = 0; + } + m_r = 0; + set_new_child_flag(t); + } + else { + result_stack().push_back(t); + if (ProofGen) + result_pr_stack().push_back(0); // implicit reflexivity + } +} + +/** + \brief visit expression t. Return true if t was rewritten and the result is on the top m_result_stack. + We may skip t if: + - pre_visit(t) returns false + - max_depth == 0 + - t is already in the cache + Otherwise, return false and add a new frame stack for t with the updated max_depth. +*/ +template +template +bool rewriter_tpl::visit(expr * t, unsigned max_depth) { + TRACE("rewriter_visit", tout << "visiting\n" << mk_ismt2_pp(t, m()) << "\n";); + expr * new_t; + proof * new_t_pr; + if (m_cfg.get_subst(t, new_t, new_t_pr)) { + TRACE("rewriter_subst", tout << "subst\n" << mk_ismt2_pp(t, m()) << "\n---->\n" << mk_ismt2_pp(new_t, m()) << "\n";); + result_stack().push_back(new_t); + set_new_child_flag(t, new_t); + if (ProofGen) + result_pr_stack().push_back(new_t_pr); + return true; + } + if (max_depth == 0) { + result_stack().push_back(t); + if (ProofGen) + result_pr_stack().push_back(0); // implicit reflexivity + return true; // t is not going to be processed + } + SASSERT(max_depth > 0); + SASSERT(max_depth <= RW_UNBOUNDED_DEPTH); + bool c = must_cache(t); + if (c) { +#if 0 + static unsigned checked_cache = 0; + checked_cache ++; + if (checked_cache % 100000 == 0) + std::cerr << "[rewriter] num-cache-checks: " << checked_cache << std::endl; +#endif + expr * r = get_cached(t); + if (r) { + result_stack().push_back(r); + set_new_child_flag(t, r); + if (ProofGen) { + proof * pr = get_cached_pr(t); + result_pr_stack().push_back(pr); + } + return true; + } + } + if (!pre_visit(t)) { + result_stack().push_back(t); + if (ProofGen) + result_pr_stack().push_back(0); // implicit reflexivity + return true; // t is not going to be processed + } + switch (t->get_kind()) { + case AST_APP: + if (to_app(t)->get_num_args() == 0) { + process_const(to_app(t)); + return true; + } + else { + if (max_depth != RW_UNBOUNDED_DEPTH) + max_depth--; + push_frame(t, c, max_depth); + return false; + } + case AST_VAR: + process_var(to_var(t)); + return true; + case AST_QUANTIFIER: + if (max_depth != RW_UNBOUNDED_DEPTH) + max_depth--; + push_frame(t, c, max_depth); + return false; + default: + UNREACHABLE(); + return true; + } +} + +template +template +void rewriter_tpl::process_app(app * t, frame & fr) { + SASSERT(t->get_num_args() > 0); + SASSERT(!frame_stack().empty()); + switch (fr.m_state) { + case PROCESS_CHILDREN: { + unsigned num_args = t->get_num_args(); + while (fr.m_i < num_args) { + expr * arg = t->get_arg(fr.m_i); + fr.m_i++; + if (!visit(arg, fr.m_max_depth)) + return; + } + func_decl * f = t->get_decl(); + + // If AC flattening is enabled, f is associative, t is not shared, and there is a previous frame on the stack. + if (!ProofGen) { + // this optimization is only used when Proof generation is disabled. + if (f->is_associative() && t->get_ref_count() <= 1 && frame_stack().size() > 1) { + frame & prev_fr = frame_stack()[frame_stack().size() - 2]; + if (is_app(prev_fr.m_curr) && + to_app(prev_fr.m_curr)->get_decl() == f && + prev_fr.m_state == PROCESS_CHILDREN && + flat_assoc(f)) { + frame_stack().pop_back(); + set_new_child_flag(t); + return; + } + } + } + + unsigned new_num_args = result_stack().size() - fr.m_spos; + expr * const * new_args = result_stack().c_ptr() + fr.m_spos; + app * new_t; + if (ProofGen) { + elim_reflex_prs(fr.m_spos); + unsigned num_prs = result_pr_stack().size() - fr.m_spos; + if (num_prs == 0) { + new_t = t; + m_pr = 0; + } + else { + new_t = m().mk_app(f, new_num_args, new_args); + m_pr = m().mk_congruence(t, new_t, num_prs, result_pr_stack().c_ptr() + fr.m_spos); + } + } + br_status st = m_cfg.reduce_app(f, new_num_args, new_args, m_r, m_pr2); + TRACE("reduce_app", + tout << mk_ismt2_pp(t, m()) << "\n"; + tout << "st: " << st; + if (m_r) tout << " --->\n" << mk_ismt2_pp(m_r, m()); + tout << "\n";); + if (st != BR_FAILED) { + result_stack().shrink(fr.m_spos); + result_stack().push_back(m_r); + if (ProofGen) { + result_pr_stack().shrink(fr.m_spos); + if (!m_pr2) + m_pr2 = m().mk_rewrite(new_t, m_r); + m_pr = m().mk_transitivity(m_pr, m_pr2); + m_pr2 = 0; + result_pr_stack().push_back(m_pr); + } + if (st == BR_DONE) { + cache_result(t, m_r, m_pr, fr.m_cache_result); + frame_stack().pop_back(); + set_new_child_flag(t); + m_r = 0; + if (ProofGen) + m_pr = 0; + return; + } + else { + fr.m_state = REWRITE_BUILTIN; + SASSERT(st == BR_REWRITE1 || st == BR_REWRITE2 || st == BR_REWRITE3 || st == BR_REWRITE_FULL); + unsigned max_depth = static_cast(st); + SASSERT(0 <= max_depth && max_depth <= RW_UNBOUNDED_DEPTH); + SASSERT((st == BR_REWRITE1) == (max_depth == 0)); + SASSERT((st == BR_REWRITE2) == (max_depth == 1)); + SASSERT((st == BR_REWRITE3) == (max_depth == 2)); + SASSERT((st == BR_REWRITE_FULL) == (max_depth == RW_UNBOUNDED_DEPTH)); + if (max_depth != RW_UNBOUNDED_DEPTH) + max_depth++; + if (visit(m_r, max_depth)) { + if (ProofGen) { + proof_ref pr2(m()), pr1(m()); + pr2 = result_pr_stack().back(); + result_pr_stack().pop_back(); + pr1 = result_pr_stack().back(); + result_pr_stack().pop_back(); + m_pr = m().mk_transitivity(pr1, pr2); + result_pr_stack().push_back(m_pr); + } + m_r = result_stack().back(); + result_stack().pop_back(); + result_stack().pop_back(); + result_stack().push_back(m_r); + cache_result(t, m_r, m_pr, fr.m_cache_result); + frame_stack().pop_back(); + set_new_child_flag(t); + m_r = 0; + if (ProofGen) + m_pr = 0; + return; + } + else { + // frame was created for processing m_r + m_r = 0; + if (ProofGen) + m_pr = 0; + return; + } + } + UNREACHABLE(); + } + // TODO: add rewrite rules support + expr * def; + proof * def_pr; + quantifier * def_q; + // When get_macro succeeds, then + // we know that: + // forall X. f(X) = def[X] + // and def_pr is a proof for this quantifier. + // + // Remark: def_q is only used for proof generation. + // It is the quantifier forall X. f(X) = def[X] + if (get_macro(f, def, def_q, def_pr)) { + SASSERT(!f->is_associative() || !flat_assoc(f)); + SASSERT(new_num_args == t->get_num_args()); + if (is_ground(def)) { + m_r = def; + if (ProofGen) { + SASSERT(def_pr); + m_pr = m().mk_transitivity(m_pr, def_pr); + } + } + else { + if (ProofGen) { + NOT_IMPLEMENTED_YET(); + // We do not support the use of bindings in proof generation mode. + // Thus we have to apply the subsitution here, and + // beta_reducer subst(m()); + // subst.set_bindings(new_num_args, new_args); + // expr_ref r2(m()); + // subst(def, r2); + } + else { + fr.m_state = EXPAND_DEF; + TRACE("get_macro", tout << "f: " << f->get_name() << ", def: \n" << mk_ismt2_pp(def, m()) << "\n"; + tout << "Args num: " << num_args << "\n"; + for (unsigned i = 0; i < num_args; i++) tout << mk_ismt2_pp(new_args[i], m()) << "\n";); + unsigned i = num_args; + while (i > 0) { + --i; + m_bindings.push_back(new_args[i]); + } + result_stack().push_back(def); + TRACE("get_macro", tout << "bindings:\n"; + for (unsigned i = 0; i < m_bindings.size(); i++) tout << i << ": " << mk_ismt2_pp(m_bindings[i], m()) << "\n";); + begin_scope(); + m_num_qvars = 0; + m_root = def; + push_frame(def, false, RW_UNBOUNDED_DEPTH); + return; + } + } + } + else { + if (ProofGen) { + m_r = new_t; + } + else { + if (fr.m_new_child) { + m_r = m().mk_app(f, new_num_args, new_args); + } + else { + TRACE("rewriter_reuse", tout << "reusing:\n" << mk_ismt2_pp(t, m()) << "\n";); + m_r = t; + } + } + } + result_stack().shrink(fr.m_spos); + result_stack().push_back(m_r); + cache_result(t, m_r, m_pr, fr.m_cache_result); + if (ProofGen) { + result_pr_stack().shrink(fr.m_spos); + result_pr_stack().push_back(m_pr); + m_pr = 0; + } + frame_stack().pop_back(); + set_new_child_flag(t, m_r); + m_r = 0; + return; + } + case REWRITE_BUILTIN: + SASSERT(fr.m_spos + 2 == result_stack().size()); + if (ProofGen) { + proof_ref pr2(m()), pr1(m()); + pr2 = result_pr_stack().back(); + result_pr_stack().pop_back(); + pr1 = result_pr_stack().back(); + result_pr_stack().pop_back(); + m_pr = m().mk_transitivity(pr1, pr2); + result_pr_stack().push_back(m_pr); + } + m_r = result_stack().back(); + result_stack().pop_back(); + result_stack().pop_back(); + result_stack().push_back(m_r); + cache_result(t, m_r, m_pr, fr.m_cache_result); + frame_stack().pop_back(); + set_new_child_flag(t); + return; + case EXPAND_DEF: + SASSERT(fr.m_spos + t->get_num_args() + 2 == result_stack().size()); + SASSERT(t->get_num_args() <= m_bindings.size()); + if (!ProofGen) { + m_bindings.shrink(m_bindings.size() - t->get_num_args()); + end_scope(); + m_r = result_stack().back(); + result_stack().shrink(fr.m_spos); + result_stack().push_back(m_r); + cache_result(t, m_r, m_pr, fr.m_cache_result); + frame_stack().pop_back(); + set_new_child_flag(t); + } + else { + // TODO + NOT_IMPLEMENTED_YET(); + } + return; + case REWRITE_RULE: + // support for rewriting rules was not implemented yet. + NOT_IMPLEMENTED_YET(); + break; + default: + UNREACHABLE(); + break; + } +} + +template +template +void rewriter_tpl::process_quantifier(quantifier * q, frame & fr) { + SASSERT(fr.m_state == PROCESS_CHILDREN); + if (fr.m_i == 0) { + if (!ProofGen) { + begin_scope(); + m_root = q->get_expr(); + } + m_num_qvars += q->get_num_decls(); + if (!ProofGen) { + for (unsigned i = 0; i < q->get_num_decls(); i++) + m_bindings.push_back(0); + } + } + unsigned num_children = rewrite_patterns() ? q->get_num_children() : 1; + while (fr.m_i < num_children) { + expr * child = q->get_child(fr.m_i); + fr.m_i++; + if (!visit(child, fr.m_max_depth)) + return; + } + SASSERT(fr.m_spos + num_children == result_stack().size()); + expr * const * it = result_stack().c_ptr() + fr.m_spos; + expr * new_body = *it; + expr * const * new_pats; + expr * const * new_no_pats; + if (rewrite_patterns()) { + new_pats = it + 1; + new_no_pats = new_pats + q->get_num_patterns(); + } + else { + new_pats = q->get_patterns(); + new_no_pats = q->get_no_patterns(); + } + if (ProofGen) { + quantifier * new_q = m().update_quantifier(q, q->get_num_patterns(), new_pats, q->get_num_no_patterns(), new_no_pats, new_body); + m_pr = q == new_q ? 0 : m().mk_quant_intro(q, new_q, result_pr_stack().get(fr.m_spos)); + m_r = new_q; + proof_ref pr2(m()); + if (m_cfg.reduce_quantifier(new_q, new_body, new_pats, new_no_pats, m_r, pr2)) { + m_pr = m().mk_transitivity(m_pr, pr2); + } + TRACE("reduce_quantifier_bug", tout << "m_pr is_null: " << (m_pr.get() == 0) << "\n"; + if (m_pr) tout << mk_ismt2_pp(m_pr, m()) << "\n";); + result_pr_stack().shrink(fr.m_spos); + result_pr_stack().push_back(m_pr); + } + else { + if (!m_cfg.reduce_quantifier(q, new_body, new_pats, new_no_pats, m_r, m_pr)) { + if (fr.m_new_child) { + m_r = m().update_quantifier(q, q->get_num_patterns(), new_pats, q->get_num_no_patterns(), new_no_pats, new_body); + } + else { + TRACE("rewriter_reuse", tout << "reusing:\n" << mk_ismt2_pp(q, m()) << "\n";); + m_r = q; + } + } + } + result_stack().shrink(fr.m_spos); + result_stack().push_back(m_r.get()); + if (!ProofGen) { + SASSERT(q->get_num_decls() <= m_bindings.size()); + m_bindings.shrink(m_bindings.size() - q->get_num_decls()); + end_scope(); + cache_result(q, m_r, m_pr, fr.m_cache_result); + } + else { + cache_result(q, m_r, m_pr, fr.m_cache_result); + m_pr = 0; + } + m_r = 0; + frame_stack().pop_back(); + set_new_child_flag(q, m_r); +} + +template +bool rewriter_tpl::not_rewriting() const { + SASSERT(frame_stack().empty()); + SASSERT(m_cache == m_cache_stack[0]); + return true; +} + +template +rewriter_tpl::rewriter_tpl(ast_manager & m, bool proof_gen, Config & cfg): + rewriter_core(m, proof_gen), + m_cfg(cfg), + m_num_steps(0), + m_cancel(false), + m_shifter(m), + m_r(m), + m_pr(m), + m_pr2(m) { +} + +template +rewriter_tpl::~rewriter_tpl() { +} + +template +void rewriter_tpl::reset() { + m_cfg.reset(); + rewriter_core::reset(); + m_bindings.reset(); + m_shifter.reset(); +} + +template +void rewriter_tpl::cleanup() { + m_cfg.cleanup(); + rewriter_core::cleanup(); + m_bindings.finalize(); + m_shifter.cleanup(); +} + +template +void rewriter_tpl::set_bindings(unsigned num_bindings, expr * const * bindings) { + SASSERT(!m_proof_gen); + SASSERT(not_rewriting()); + m_bindings.reset(); + unsigned i = num_bindings; + while (i > 0) { + --i; + m_bindings.push_back(bindings[i]); + } +} + +template +void rewriter_tpl::set_inv_bindings(unsigned num_bindings, expr * const * bindings) { + SASSERT(!m_proof_gen); + SASSERT(not_rewriting()); + m_bindings.reset(); + for (unsigned i = 0; i < num_bindings; i++) { + m_bindings.push_back(bindings[i]); + } +} + +template +template +void rewriter_tpl::main_loop(expr * t, expr_ref & result, proof_ref & result_pr) { + SASSERT(!ProofGen || result_stack().size() == result_pr_stack().size()); + SASSERT(not_rewriting()); + m_root = t; + m_num_qvars = 0; + m_num_steps = 0; + if (visit(t, RW_UNBOUNDED_DEPTH)) { + result = result_stack().back(); + result_stack().pop_back(); + SASSERT(result_stack().empty()); + if (ProofGen) { + result_pr = result_pr_stack().back(); + result_pr_stack().pop_back(); + if (result_pr.get() == 0) + result_pr = m().mk_reflexivity(t); + SASSERT(result_pr_stack().empty()); + } + return; + } + resume_core(result, result_pr); +} + +/** + \brief Resume the execution when it was interrupted by cancel() or check_max_steps(). +*/ +template +template +void rewriter_tpl::resume_core(expr_ref & result, proof_ref & result_pr) { + SASSERT(!frame_stack().empty()); + while (!frame_stack().empty()) { + if (m_cancel) + throw rewriter_exception(TACTIC_CANCELED_MSG); + SASSERT(!ProofGen || result_stack().size() == result_pr_stack().size()); + frame & fr = frame_stack().back(); + expr * t = fr.m_curr; + TRACE("rewriter_step", tout << "step\n" << mk_ismt2_pp(t, m()) << "\n";); + m_num_steps++; + check_max_steps(); + if (first_visit(fr) && fr.m_cache_result) { + expr * r = get_cached(t); + if (r) { + result_stack().push_back(r); + if (ProofGen) { + proof * pr = get_cached_pr(t); + result_pr_stack().push_back(pr); + } + frame_stack().pop_back(); + set_new_child_flag(t, r); + continue; + } + } + switch (t->get_kind()) { + case AST_APP: + process_app(to_app(t), fr); + break; + case AST_QUANTIFIER: + process_quantifier(to_quantifier(t), fr); + break; + case AST_VAR: + frame_stack().pop_back(); + process_var(to_var(t)); + break; + default: + UNREACHABLE(); + break; + } + } + result = result_stack().back(); + result_stack().pop_back(); + SASSERT(result_stack().empty()); + if (ProofGen) { + result_pr = result_pr_stack().back(); + result_pr_stack().pop_back(); + if (result_pr.get() == 0) + result_pr = m().mk_reflexivity(m_root); + SASSERT(result_pr_stack().empty()); + } +} + +template +void rewriter_tpl::operator()(expr * t, expr_ref & result, proof_ref & result_pr) { + if (m_proof_gen) + main_loop(t, result, result_pr); + else + main_loop(t, result, result_pr); +} + +template +void rewriter_tpl::resume(expr_ref & result, proof_ref & result_pr) { + if (m_proof_gen) + resume_core(result, result_pr); + else + resume_core(result, result_pr); +} diff --git a/lib/rewriter_types.h b/lib/rewriter_types.h new file mode 100644 index 000000000..e2164ef24 --- /dev/null +++ b/lib/rewriter_types.h @@ -0,0 +1,51 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + rewriter_types.h + +Abstract: + + Lean and mean rewriter + +Author: + + Leonardo (leonardo) 2011-04-10 + +Notes: + +--*/ +#ifndef _REWRITER_TYPES_H_ +#define _REWRITER_TYPES_H_ + +#include"tactic_exception.h" + +/** + \brief Builtin rewrite result status +*/ +enum br_status { + BR_REWRITE1, // rewrite the result (bounded by depth 1) + BR_REWRITE2, // rewrite the result (bounded by depth 2) + BR_REWRITE3, // rewrite the result (bounded by depth 3) + BR_REWRITE_FULL, // rewrite the result unbounded + BR_DONE, // done, the result is simplified + BR_FAILED // no builtin rewrite is available +}; + +#define RW_UNBOUNDED_DEPTH 3 +inline br_status unsigned2br_status(unsigned u) { + br_status r = u >= RW_UNBOUNDED_DEPTH ? BR_REWRITE_FULL : static_cast(u); + SASSERT((u == 0) == (r == BR_REWRITE1)); + SASSERT((u == 1) == (r == BR_REWRITE2)); + SASSERT((u == 2) == (r == BR_REWRITE3)); + SASSERT((u >= 3) == (r == BR_REWRITE_FULL)); + return r; +} + +class rewriter_exception : public tactic_exception { +public: + rewriter_exception(char const * msg):tactic_exception(msg) {} +}; + +#endif diff --git a/lib/rpolynomial.cpp b/lib/rpolynomial.cpp new file mode 100644 index 000000000..2ae90bf69 --- /dev/null +++ b/lib/rpolynomial.cpp @@ -0,0 +1,800 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + rpolynomial.cpp + +Abstract: + + Goodies for creating and handling polynomials in dense recursive representation. + +Author: + + Leonardo (leonardo) 2012-06-11 + +Notes: + +--*/ +#include"rpolynomial.h" +#include"tptr.h" +#include"buffer.h" + +namespace rpolynomial { + + typedef void poly_or_num; + + inline bool is_poly(poly_or_num * p) { return GET_TAG(p) == 0; } + inline bool is_num(poly_or_num * p) { return GET_TAG(p) == 1; } + polynomial * to_poly(poly_or_num * p) { SASSERT(is_poly(p)); return UNTAG(polynomial*, p); } + manager::numeral * to_num_ptr(poly_or_num * p) { SASSERT(is_num(p)); return (UNTAG(manager::numeral *, p)); } + manager::numeral & to_num(poly_or_num * p) { return *to_num_ptr(p); } + poly_or_num * to_poly_or_num(polynomial * p) { return TAG(poly_or_num*, p, 0); } + poly_or_num * to_poly_or_num(manager::numeral * n) { return TAG(poly_or_num*, n, 1); } + + class polynomial { + friend class manager; + friend struct manager::imp; + + unsigned m_ref_count; + var m_var; + unsigned m_size; + poly_or_num * m_args[0]; + + public: + unsigned ref_count() const { return m_ref_count; } + void inc_ref() { m_ref_count++; } + void dec_ref() { SASSERT(m_ref_count > 0); m_ref_count--; } + + static unsigned get_obj_size(unsigned n) { return sizeof(polynomial) + n*sizeof(void*); } + + var max_var() const { return m_var; } + unsigned size() const { return m_size; } + poly_or_num * arg(unsigned i) const { SASSERT(i < size()); return m_args[i]; } + }; + + struct manager::imp { + manager & m_wrapper; + numeral_manager & m_manager; + small_object_allocator * m_allocator; + bool m_own_allocator; + volatile bool m_cancel; + + imp(manager & w, numeral_manager & m, small_object_allocator * a): + m_wrapper(w), + m_manager(m), + m_allocator(a), + m_own_allocator(a == 0) { + if (a == 0) + m_allocator = alloc(small_object_allocator, "rpolynomial"); + m_cancel = false; + } + + ~imp() { + if (m_own_allocator) + dealloc(m_allocator); + } + + // Remark: recursive calls should be fine since I do not expect to have polynomials with more than 100 variables. + + manager & pm() const { return m_wrapper; } + + numeral * mk_numeral() { + void * mem = m_allocator->allocate(sizeof(numeral)); + return new (mem) numeral(); + } + + void del_numeral(numeral * n) { + m_manager.del(*n); + m_allocator->deallocate(sizeof(numeral), n); + } + + static void inc_ref(polynomial * p) { + if (p) + p->inc_ref(); + } + + static void inc_ref(poly_or_num * p) { + if (p && is_poly(p)) + inc_ref(to_poly(p)); + } + + void del_poly(polynomial * p) { + SASSERT(p != 0); + ptr_buffer todo; + todo.push_back(p); + while (!todo.empty()) { + p = todo.back(); + todo.pop_back(); + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + poly_or_num * pn = p->arg(i); + if (pn == 0) + continue; + if (is_num(pn)) { + del_numeral(to_num_ptr(pn)); + } + else { + SASSERT(is_poly(p)); + polynomial * p_arg = to_poly(p); + p_arg->dec_ref(); + if (p_arg->ref_count() == 0) { + todo.push_back(p_arg); + } + } + } + unsigned obj_sz = polynomial::get_obj_size(sz); + m_allocator->deallocate(obj_sz, p); + } + } + + void dec_ref(polynomial * p) { + if (p) { + p->dec_ref(); + if (p->ref_count() == 0) + del_poly(p); + } + } + + void dec_ref(poly_or_num * p) { + if (p && is_poly(p)) + dec_ref(to_poly(p)); + } + + static bool is_const(polynomial const * p) { + SASSERT(p == 0 || (p->max_var() == null_var) == (p->size() == 1 && p->arg(0) != 0 && is_num(p->arg(0)))); + return p == 0 || p->max_var() == null_var; + } + + bool is_zero(polynomial const * p) { + return p == 0; + } + + static bool is_univariate(polynomial const * p) { + if (is_const(p)) + return false; + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + poly_or_num * pn = p->arg(i); + if (pn == 0) + continue; + if (is_poly(pn)) + return false; + } + return true; + } + + bool is_monomial(polynomial const * p) { + if (is_const(p)) + return true; + unsigned sz = p->size(); + SASSERT(sz > 0); + SASSERT(p->arg(sz - 1) != 0); + for (unsigned i = 0; i < sz - 1; i++) { + if (p->arg(i) != 0) + return false; + } + SASSERT(is_poly(p->arg(sz - 1))); + return is_monomial(to_poly(p->arg(sz-1))); + } + + unsigned degree(polynomial const * p) { + SASSERT(p != 0); + SASSERT(p->size() > 0); + return p == 0 ? 0 : p->size() - 1; + } + + bool eq(polynomial const * p1, polynomial const * p2) { + if (p1 == p2) + return true; + if (p1 == 0 || p2 == 0) + return false; + if (p1->size() != p2->size()) + return false; + if (p1->max_var() != p2->max_var()) + return false; + unsigned sz = p1->size(); + for (unsigned i = 0; i < sz; i++) { + poly_or_num * pn1 = p1->arg(i); + poly_or_num * pn2 = p2->arg(i); + if (pn1 == 0 && pn2 == 0) + continue; + if (pn1 == 0 || pn2 == 0) + return false; + if (is_num(pn1) && is_num(pn2)) { + if (!m_manager.eq(to_num(pn1), to_num(pn2))) + return false; + } + else if (is_poly(pn1) && is_poly(pn2)) { + if (!eq(to_poly(pn1), to_poly(pn2))) + return false; + } + else { + return false; + } + } + return true; + } + + void inc_ref_args(unsigned sz, poly_or_num * const * args) { + for (unsigned i = 0; i < sz; i++) { + poly_or_num * pn = args[i]; + if (pn == 0 || is_num(pn)) + continue; + inc_ref(to_poly(pn)); + } + } + + void dec_ref_args(unsigned sz, poly_or_num * const * args) { + for (unsigned i = 0; i < sz; i++) { + poly_or_num * pn = args[i]; + if (pn == 0 || is_num(pn)) + continue; + dec_ref(to_poly(pn)); + } + } + + unsigned trim(unsigned sz, poly_or_num * const * args) { + while (sz > 0) { + if (args[sz - 1] != 0) + return sz; + sz--; + } + UNREACHABLE(); + return UINT_MAX; + } + + polynomial * allocate_poly(unsigned sz, poly_or_num * const * args, var max_var) { + SASSERT(sz > 0); + SASSERT((max_var == null_var) == (sz == 1 && is_num(args[0]))); + unsigned obj_sz = polynomial::get_obj_size(sz); + void * mem = m_allocator->allocate(obj_sz); + polynomial * new_pol = new (mem) polynomial(); + new_pol->m_ref_count = 0; + new_pol->m_var = max_var; + new_pol->m_size = sz; + for (unsigned i = 0; i < sz; i++) { + poly_or_num * pn = args[i]; + if (is_poly(pn)) { + inc_ref(to_poly(pn)); + new_pol->m_args[i] = pn; + SASSERT(max_var == null_var || to_poly(pn)->max_var() < max_var); + } + else { + SASSERT(!m_manager.is_zero(to_num(pn))); + new_pol->m_args[i] = pn; + } + } + return new_pol; + } + + poly_or_num * mk_poly_core(unsigned sz, poly_or_num * const * args, var max_var) { + sz = trim(sz, args); + SASSERT(sz > 0); + if (sz == 1) { + poly_or_num * pn0 = args[0]; + SASSERT(!is_num(pn0) || !m_manager.is_zero(to_num(pn0))); + return pn0; + } + SASSERT((max_var == null_var) == (sz == 1 && is_num(args[0]))); + SASSERT(sz > 1); + return to_poly_or_num(allocate_poly(sz, args, max_var)); + } + + polynomial * mk_poly(unsigned sz, poly_or_num * const * args, var max_var) { + poly_or_num * _p = mk_poly_core(sz, args, max_var); + if (_p == 0) + return 0; + else if (is_num(_p)) + return allocate_poly(1, &_p, null_var); + else + return to_poly(_p); + } + + polynomial * mk_const(numeral const & n) { + if (m_manager.is_zero(n)) + return 0; + numeral * a = mk_numeral(); + m_manager.set(*a, n); + poly_or_num * _a = to_poly_or_num(a); + return allocate_poly(1, &_a, null_var); + } + + polynomial * mk_const(rational const & a) { + SASSERT(a.is_int()); + scoped_numeral tmp(m_manager); + m_manager.set(tmp, a.to_mpq().numerator()); + return mk_const(tmp); + } + + polynomial * mk_polynomial(var x, unsigned k) { + SASSERT(x != null_var); + if (k == 0) { + numeral one; + m_manager.set(one, 1); + return mk_const(one); + } + ptr_buffer new_args; + for (unsigned i = 0; i < k; i++) + new_args.push_back(0); + numeral * new_arg = mk_numeral(); + m_manager.set(*new_arg, 1); + new_args.push_back(to_poly_or_num(new_arg)); + return mk_poly(new_args.size(), new_args.c_ptr(), x); + } + + poly_or_num * unpack(polynomial const * p) { + if (p == 0) { + return 0; + } + else if (is_const(p)) { + SASSERT(p->size() == 1); + SASSERT(p->max_var() == null_var); + return p->arg(0); + } + else { + return to_poly_or_num(const_cast(p)); + } + } + + polynomial * pack(poly_or_num * p) { + if (p == 0) + return 0; + else if (is_num(p)) + return mk_poly(1, &p, null_var); + else + return to_poly(p); + } + + poly_or_num * mul_core(numeral const & c, poly_or_num * p) { + if (m_manager.is_zero(c) || p == 0) { + return 0; + } + else if (is_num(p)) { + numeral * r = mk_numeral(); + m_manager.mul(c, to_num(p), *r); + return to_poly_or_num(r); + } + else { + polynomial * _p = to_poly(p); + unsigned sz = _p->size(); + SASSERT(sz > 1); + ptr_buffer new_args; + for (unsigned i = 0; i < sz; i++) { + new_args.push_back(mul_core(c, _p->arg(i))); + } + return mk_poly_core(new_args.size(), new_args.c_ptr(), _p->max_var()); + } + } + + polynomial * mul(numeral const & c, polynomial const * p) { + return pack(mul_core(c, unpack(p))); + } + + polynomial * neg(polynomial const * p) { + numeral minus_one; + m_manager.set(minus_one, -1); + return pack(mul_core(minus_one, unpack(p))); + } + + poly_or_num * add_core(numeral const & c, poly_or_num * p) { + if (m_manager.is_zero(c)) { + return p; + } + else if (p == 0) { + numeral * r = mk_numeral(); + m_manager.set(*r, c); + return to_poly_or_num(r); + } + else if (is_num(p)) { + numeral a; + m_manager.add(c, to_num(p), a); + if (m_manager.is_zero(a)) + return 0; + numeral * new_arg = mk_numeral(); + m_manager.swap(*new_arg, a); + return to_poly_or_num(new_arg); + } + else { + polynomial * _p = to_poly(p); + unsigned sz = _p->size(); + SASSERT(sz > 1); + ptr_buffer new_args; + new_args.push_back(add_core(c, _p->arg(0))); + for (unsigned i = 1; i < sz; i++) + new_args.push_back(_p->arg(1)); + return mk_poly_core(new_args.size(), new_args.c_ptr(), _p->max_var()); + } + } + + polynomial * add(numeral const & c, polynomial const * p) { + return pack(add_core(c, unpack(p))); + } + +#if 0 + polynomial * add_lt(polynomial const * p1, polynomial const * p2) { + // Add non-constant polynomials p1 and p2 when max_var(p1) < max_var(p2) + SASSERT(p1->max_var() != null_var); + SASSERT(p2->max_var() != null_var); + SASSERT(p1->max_var() < p2->max_var()); + + unsigned sz = p2->size(); + ptr_buffer new_args; + poly_or_num * pn0 = p2->arg(0); + if (pn0 == 0) { + new_args.push_back(to_poly_or_num(const_cast(p1))); + } + else if (is_num(pn0)) { + SASSERT(!is_const(p1)); + polynomial * new_arg = add(to_num(pn0), p1); + SASSERT(!is_zero(new_arg)); + SASSERT(!is_const(new_arg)); + new_args.push_back(to_poly_or_num(new_arg)); + } + else { + SASSERT(is_poly(pn0)); + polynomial * new_arg = add(p1, to_poly(pn0)); + new_args.push_back(to_poly_or_num(new_arg)); + } + for (unsigned i = 1; i < sz; i++) + new_args.push_back(p2->arg(i)); + return mk_poly(sz, new_args.c_ptr(), p2->max_var()); + } + + polynomial * add(polynomial const * p1, polynomial const * p2) { + if (is_zero(p1)) + return const_cast(p2); + if (is_zero(p2)) + return const_cast(p1); + var x1 = p1->max_var(); + var x2 = p2->max_var(); + if (x1 == null_var) { + SASSERT(is_const(p1)); + return add(to_num(p1->arg(0)), p2); + } + if (x2 == null_var) { + SASSERT(is_const(p2)); + return add(to_num(p2->arg(0)), p1); + } + if (x1 < x2) + return add_lt(p1, p2); + if (x2 < x1) + return add_lt(p2, p1); + SASSERT(x1 == x2); + unsigned sz1 = p1->size(); + unsigned sz2 = p2->size(); + unsigned msz = std::min(sz1, sz2); + ptr_buffer new_args; + for (unsigned i = 0; i < msz; i++) { + poly_or_num * pn1 = p1->arg(i); + poly_or_num * pn2 = p2->arg(i); + if (pn1 == 0) { + new_args.push_back(pn2); + continue; + } + if (pn2 == 0) { + new_args.push_back(pn1); + continue; + } + SASSERT(pn1 != 0 && pn2 != 0); + if (is_num(pn1)) { + if (is_num(pn2)) { + SASSERT(is_num(pn1) && is_num(pn2)); + numeral a; + m_manager.add(to_num(pn1), to_num(pn2), a); + if (m_manager.is_zero(a)) { + new_args.push_back(0); + } + else { + numeral * new_arg = mk_numeral(); + m_manager.swap(*new_arg, a); + new_args.push_back(to_poly_or_num(new_arg)); + } + } + else { + SASSERT(is_num(pn1) && is_poly(pn2)); + new_args.push_back(to_poly_or_num(add(to_num(pn1), to_poly(pn2)))); + } + } + else { + if (is_num(pn2)) { + SASSERT(is_poly(pn1) && is_num(pn2)); + new_args.push_back(to_poly_or_num(add(to_num(pn2), to_poly(pn1)))); + } + else { + SASSERT(is_poly(pn1) && is_poly(pn2)); + new_args.push_back(to_poly_or_num(add(to_poly(pn1), to_poly(pn2)))); + } + } + } + SASSERT(new_args.size() == sz1 || new_args.size() == sz2); + for (unsigned i = msz; i < sz1; i++) { + new_args.push_back(p1->arg(i)); + } + for (unsigned i = msz; i < sz2; i++) { + new_args.push_back(p2->arg(i)); + } + SASSERT(new_args.size() == std::max(sz1, sz2)); + return mk_poly(new_args.size(), new_args.c_ptr(), x1); + } + + class resetter_mul_buffer; + friend class resetter_mul_buffer; + class resetter_mul_buffer { + imp & m_owner; + ptr_buffer m_buffer; + public: + resetter_mul_buffer(imp & o, ptr_buffer & b):m_owner(o), m_buffer(b) {} + ~resetter_mul_buffer() { + m_owner.dec_ref_args(m_buffer.size(), m_buffer.c_ptr()); + m_buffer.reset(); + } + }; + + void acc_mul_xk(ptr_buffer & mul_buffer, unsigned k, polynomial * p) { + if (mul_buffer[k] == 0) { + mul_buffer[k] = to_poly_or_num(p); + inc_ref(p); + } + else { + polynomial * new_p; + if (is_num(mul_buffer[k])) + new_p = add(to_num(mul_buffer.get(k)), p); + else + new_p = add(p, to_poly(mul_buffer.get(k))); + if (is_zero(new_p)) { + dec_ref(mul_buffer[k]); + mul_buffer[k] = 0; + } + else { + inc_ref(new_p); + dec_ref(mul_buffer[k]); + mul_buffer[k] = to_poly_or_num(new_p); + } + } + } + + void acc_mul_xk(ptr_buffer & mul_buffer, unsigned k, numeral & a) { + if (mul_buffer.get(k) == 0) { + numeral * new_arg = mk_numeral(); + m_manager.swap(*new_arg, a); + mul_buffer[k] = to_poly_or_num(new_arg); + } + else { + if (is_num(mul_buffer[k])) { + m_manager.add(to_num(mul_buffer[k]), a, to_num(mul_buffer[k])); + if (m_manager.is_zero(to_num(mul_buffer[k]))) { + del_numeral(to_num_ptr(mul_buffer[k])); + mul_buffer[k] = 0; + } + } + else { + polynomial * new_p = add(a, to_poly(mul_buffer.get(k))); + if (is_zero(new_p)) { + dec_ref(mul_buffer[k]); + mul_buffer[k] = 0; + } + else { + inc_ref(new_p); + dec_ref(mul_buffer[k]); + mul_buffer[k] = to_poly_or_num(new_p); + } + } + } + } + + polynomial * mul_lt(polynomial const * p1, polynomial const * p2) { + unsigned sz2 = p2->size(); + + // TODO + return 0; + } + + polynomial * mul(polynomial const * p1, polynomial const * p2) { + var x1 = p1->max_var(); + var x2 = p2->max_var(); + if (x1 == null_var) { + SASSERT(is_const(p1)); + return mul(to_num(p1->arg(0)), p2); + } + if (x2 == null_var) { + SASSERT(is_const(p2)); + return mul(to_num(p2->arg(0)), p1); + } + if (x1 < x2) + return mul_lt(p1, p2); + if (x2 < x1) + return mul_lt(p2, p1); + SASSERT(x1 == x2); + if (degree(p1) < degree(p2)) + std::swap(p1, p2); + unsigned sz = degree(p1) * degree(p2) + 1; + ptr_buffer mul_buffer; + resetter_mul_buffer resetter(*this, mul_buffer); + mul_buffer.resize(sz); + unsigned sz1 = p1->size(); + unsigned sz2 = p2->size(); + for (unsigned i1 = 0; i1 < sz1; i1++) { + poly_or_num * pn1 = p1->arg(i1); + if (pn1 == 0) + continue; + for (unsigned i2 = 0; i2 < sz2; i2++) { + poly_or_num * pn2 = p2->arg(i2); + if (pn2 == 0) + continue; + unsigned i = i1+i2; + if (is_num(pn1)) { + if (is_num(pn2)) { + SASSERT(is_num(pn1) && is_num(pn2)); + scoped_numeral a(m_manager); + m_manager.mul(to_num(pn1), to_num(pn2), a); + acc_mul_xk(mul_buffer, i, a); + } + else { + SASSERT(is_num(pn1) && is_poly(pn2)); + polynomial_ref p(pm()); + p = mul(to_num(pn1), to_poly(pn2)); + acc_mul_xk(mul_buffer, i, p); + } + } + else { + if (is_num(pn2)) { + SASSERT(is_poly(pn1) && is_num(pn2)); + polynomial_ref p(pm()); + p = mul(to_num(pn2), to_poly(pn1)); + acc_mul_xk(mul_buffer, i, p); + } + else { + SASSERT(is_poly(pn1) && is_poly(pn2)); + polynomial_ref p(pm()); + p = mul(to_poly(pn2), to_poly(pn1)); + acc_mul_xk(mul_buffer, i, p); + } + } + } + } + return mk_poly(mul_buffer.size(), mul_buffer.c_ptr(), x1); + } +#endif + + void display(std::ostream & out, polynomial const * p, display_var_proc const & proc, bool use_star) { + var x = p->max_var(); + bool first = true; + unsigned i = p->size(); + while (i > 0) { + --i; + poly_or_num * pn = p->arg(i); + if (pn == 0) + continue; + if (first) + first = false; + else + out << " + "; + if (is_num(pn)) { + numeral & a = to_num(pn); + if (i == 0) { + m_manager.display(out, a); + } + else { + if (m_manager.is_one(a)) { + proc(out, x); + if (i > 1) + out << "^" << i; + } + else { + m_manager.display(out, a); + if (use_star) + out << "*"; + else + out << " "; + proc(out, x); + if (i > 1) + out << "^" << i; + } + } + } + else { + SASSERT(is_poly(pn)); + if (i == 0) { + display(out, to_poly(pn), proc, use_star); + } + else { + bool add_paren = false; + if (i > 0) + add_paren = !is_monomial(to_poly(pn)); + if (add_paren) + out << "("; + display(out, to_poly(pn), proc, use_star); + if (add_paren) + out << ")"; + if (i > 0) { + if (use_star) + out << "*"; + else + out << " "; + proc(out, x); + if (i > 1) + out << "^" << i; + } + } + } + } + } + + }; + + manager:: manager(numeral_manager & m, small_object_allocator * a) { + m_imp = alloc(imp, *this, m, a); + } + + manager::~manager() { + dealloc(m_imp); + } + + bool manager::is_zero(polynomial const * p) { + return p == 0; + } + +#if 0 + bool manager::is_const(polynomial const * p) { + return imp::is_const(p); + } + + bool manager::is_univariate(polynomial const * p) { + return imp::is_univariate(p); + } + + bool manager::is_monomial(polynomial const * p) const { + return m_imp->is_monomial(p); + } + + bool manager::eq(polynomial const * p1, polynomial const * p2) { + return m_imp->eq(p1, p2); + } + + polynomial * manager::mk_zero() { + return m_imp->mk_zero(); + } + + polynomial * manager::mk_const(numeral const & r) { + return m_imp->mk_const(r); + } + + polynomial * manager::mk_const(rational const & a) { + return m_imp->mk_const(a); + } + + polynomial * manager::mk_polynomial(var x, unsigned k) { + return m_imp->mk_polynomial(x, k); + } + + polynomial * manager::mul(numeral const & r, polynomial const * p) { + return m_imp->mul(r, p); + } + + polynomial * manager::neg(polynomial const * p) { + return m_imp->neg(p); + } + + polynomial * manager::add(numeral const & r, polynomial const * p) { + return m_imp->add(r, p); + } + + polynomial * manager::add(polynomial const * p1, polynomial const * p2) { + return m_imp->add(p1, p2); + } + + var manager::max_var(polynomial const * p) { + return p->max_var(); + } + + unsigned manager::size(polynomial const * p) { + return p->size(); + } + + void manager::display(std::ostream & out, polynomial const * p, display_var_proc const & proc, bool use_star) const { + return m_imp->display(out, p, proc, use_star); + } +#endif + +}; diff --git a/lib/rpolynomial.h b/lib/rpolynomial.h new file mode 100644 index 000000000..146505887 --- /dev/null +++ b/lib/rpolynomial.h @@ -0,0 +1,208 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + rpolynomial.h + +Abstract: + + Goodies for creating and handling polynomials in dense recursive representation. + +Author: + + Leonardo (leonardo) 2012-06-11 + +Notes: + +--*/ +#ifndef _RPOLYNOMIAL_H_ +#define _RPOLYNOMIAL_H_ + +#include"mpz.h" +#include"rational.h" +#include"obj_ref.h" +#include"ref_vector.h" +#include"z3_exception.h" +#include"polynomial.h" + +namespace rpolynomial { + + typedef polynomial::var var; + const var null_var = polynomial::null_var; + typedef polynomial::var_vector var_vector; + typedef polynomial::display_var_proc display_var_proc; + typedef polynomial::polynomial som_polynomial; + + class polynomial; + class manager; + typedef obj_ref polynomial_ref; + typedef ref_vector polynomial_ref_vector; + typedef ptr_vector polynomial_vector; + + class manager { + public: + typedef unsynch_mpz_manager numeral_manager; + typedef numeral_manager::numeral numeral; + typedef svector numeral_vector; + typedef _scoped_numeral scoped_numeral; + typedef _scoped_numeral_vector scoped_numeral_vector; + struct imp; + private: + imp * m_imp; + public: + manager(numeral_manager & m, small_object_allocator * a = 0); + ~manager(); + + numeral_manager & m() const; + small_object_allocator & allocator() const; + + void set_cancel(bool f); + + /** + \brief Create a new variable. + */ + var mk_var(); + + /** + \brief Return the number of variables in the manager. + */ + unsigned num_vars() const; + + /** + \brief Return true if x is a valid variable in this manager. + */ + bool is_valid(var x) const { return x < num_vars(); } + + /** + \brief Increment reference counter. + */ + void inc_ref(polynomial * p); + + /** + \brief Decrement reference counter. + */ + void dec_ref(polynomial * p); + + /** + \brief Return true if \c p is the zero polynomial. + */ + bool is_zero(polynomial const * p); + + /** + \brief Return true if p1 == p2. + */ + bool eq(polynomial const * p1, polynomial const * p2); + + /** + \brief Return true if \c p is the constant polynomial. + */ + static bool is_const(polynomial const * p); + + /** + \brief Return true if \c p is an univariate polynomial. + */ + static bool is_univariate(polynomial const * p); + + /** + \brief Return true if \c p is a monomial. + */ + bool is_monomial(polynomial const * p) const; + + /** + \brief Return the maximal variable occurring in p. + + Return null_var if p is a constant polynomial. + */ + static var max_var(polynomial const * p); + + /** + \brief Return the size of the polynomail p. + It is the degree(p) on max_var(p) + 1. + */ + static unsigned size(polynomial const * p); + + /** + \brief Return a polynomial h that is the coefficient of max_var(p)^k in p. + if p does not contain any monomial containing max_var(p)^k, then return 0. + */ + polynomial * coeff(polynomial const * p, unsigned k); + + /** + \brief Create the zero polynomial. + */ + polynomial * mk_zero(); + + /** + \brief Create the constant polynomial \c r. + + \warning r is a number managed by the numeral_manager in the polynomial manager + + \warning r is reset. + */ + polynomial * mk_const(numeral const & r); + + /** + \brief Create the constant polynomial \c r. + + \pre r must be an integer + */ + polynomial * mk_const(rational const & r); + + /** + \brief Create the polynomial x^k + */ + polynomial * mk_polynomial(var x, unsigned k = 1); + + polynomial * mul(numeral const & r, polynomial const * p); + + polynomial * neg(polynomial const * p); + + polynomial * add(numeral const & r, polynomial const * p); + + polynomial * add(int c, polynomial const * p); + + polynomial * add(polynomial const * p1, polynomial const * p2); + + /** + \brief Convert the given polynomial in sum-of-monomials form into a polynomial in dense recursive form. + */ + polynomial * translate(som_polynomial const * p); + + void display(std::ostream & out, polynomial const * p, display_var_proc const & proc = display_var_proc(), bool use_star = false) const; + + void display_smt2(std::ostream & out, polynomial const * p, display_var_proc const & proc = display_var_proc()) const; + + friend std::ostream & operator<<(std::ostream & out, polynomial_ref const & p) { + p.m().display(out, p); + return out; + } + }; +}; + +typedef rpolynomial::polynomial_ref rpolynomial_ref; +typedef rpolynomial::polynomial_ref_vector rpolynomial_ref_vector; + +inline rpolynomial_ref neg(rpolynomial_ref const & p) { + rpolynomial::manager & m = p.m(); + return rpolynomial_ref(m.neg(p), m); +} + +inline rpolynomial_ref operator-(rpolynomial_ref const & p) { + rpolynomial::manager & m = p.m(); + return rpolynomial_ref(m.neg(p), m); +} + +inline rpolynomial_ref operator+(int a, rpolynomial_ref const & p) { + rpolynomial::manager & m = p.m(); + return rpolynomial_ref(m.add(a, p), m); +} + +inline rpolynomial_ref operator+(rpolynomial_ref const & p, int a) { + rpolynomial::manager & m = p.m(); + return rpolynomial_ref(m.add(a, p), m); +} + + + +#endif diff --git a/lib/s_integer.cpp b/lib/s_integer.cpp new file mode 100644 index 000000000..f55e77487 --- /dev/null +++ b/lib/s_integer.cpp @@ -0,0 +1,70 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + s_integer.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-06-10. + +Revision History: + +--*/ + +#include"s_integer.h" + +s_integer s_integer::m_zero(0); +s_integer s_integer::m_one(1); +s_integer s_integer::m_minus_one(-1); + +s_integer::s_integer(const char * str) { + m_val = static_cast(strtol(str, 0, 10)); +} + +s_integer power(const s_integer & r, unsigned p) { + unsigned mask = 1; + s_integer result = s_integer(1); + s_integer power = r; + while (mask <= p) { + if (mask & p) { + result *= power; + } + power *= power; + mask = mask << 1; + } + return result; +} + +s_integer gcd(const s_integer & r1, const s_integer & r2) { + SASSERT(r1.is_int() && r2.is_int()); + s_integer tmp1(r1); + s_integer tmp2(r2); + if (tmp1.is_neg()) { + tmp1.neg(); + } + if (tmp2.is_neg()) { + tmp2.neg(); + } + if (tmp1 < tmp2) { + tmp1.swap(tmp2); + } + for(;;) { + s_integer aux = tmp1 % tmp2; + if (aux.is_zero()) { + return tmp2; + } + tmp1 = tmp2; + tmp2 = aux; + } +} + +s_integer lcm(const s_integer & r1, const s_integer & r2) { + s_integer g = gcd(r1, r2); + return (r1 / g) * r2; +} diff --git a/lib/s_integer.h b/lib/s_integer.h new file mode 100644 index 000000000..4e50269c5 --- /dev/null +++ b/lib/s_integer.h @@ -0,0 +1,149 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + integer.h + +Abstract: + + machine s_integer wrapper + +Author: + + Leonardo de Moura (leonardo) 2007-06-01. + +Revision History: + +--*/ +#ifndef _S_INTEGER_H_ +#define _S_INTEGER_H_ + +#include"rational.h" + +class s_integer { + int m_val; + static s_integer m_zero; + static s_integer m_one; + static s_integer m_minus_one; +public: + + unsigned hash() const { + return m_val; + } + + struct hash_proc { unsigned operator()(s_integer const& r) const { return r.hash(); } }; + struct eq_proc { bool operator()(s_integer const& r1, s_integer const& r2) const { return r1 == r2; } }; + + void swap(s_integer & n) { + std::swap(m_val, n.m_val); + } + + std::string to_string() const; + +public: + s_integer(): m_val(0) {} + s_integer(const s_integer & r):m_val(r.m_val) {} + explicit s_integer(int n):m_val(n) {} + struct i64 {}; + explicit s_integer(int64 i, i64):m_val(static_cast(i)) {} + struct ui64 {}; + explicit s_integer(uint64 i, ui64):m_val(static_cast(i)) {} + explicit s_integer(const char * str); + explicit s_integer(const rational & r):m_val(static_cast(r.get_int64())) {} + + void reset() { m_val = 0; } + + static bool is_big() { return false; } + static bool is_int() { return true; } + static bool is_s_integer() { return true; } + static bool is_int64() { return true; } + static bool is_uint64() { return true; } + int get_int() const { return m_val; } + int64 get_int64() const { return m_val; } + uint64 get_uint64() const { return m_val; } + static bool is_unsigned() { return true; } + unsigned get_unsigned() const { return static_cast(m_val); } + s_integer const& get_s_integer() const { return *this; } + s_integer const& get_infinitesimal() const { return zero(); } + static bool is_rational() { return true; } + s_integer const& get_rational() const { return *this; } + s_integer & operator=(const s_integer & r) { m_val = r.m_val; return *this; } + friend inline s_integer numerator(const s_integer & r) { return r; } + friend inline s_integer denominator(const s_integer & r) { return one(); } + s_integer & operator+=(const s_integer & r) { m_val += r.m_val; return *this; } + s_integer & operator-=(const s_integer & r) { m_val -= r.m_val; return *this; } + s_integer & operator*=(const s_integer & r) { m_val *= r.m_val; return *this; } + s_integer & operator/=(const s_integer & r) { m_val /= r.m_val; return *this; } + s_integer & operator%=(const s_integer & r) { m_val %= r.m_val; return *this; } + friend inline s_integer div(const s_integer & r1, const s_integer & r2) { return s_integer(r1.m_val / r2.m_val); } + friend inline s_integer mod(const s_integer & r1, const s_integer & r2) { + s_integer r = r1; + r %= r2; + if (r.is_neg()) { + r += r2; + } + return r; + } + s_integer & operator++() { m_val++; return *this; } + const s_integer operator++(int) { s_integer tmp(*this); ++(*this); return tmp; } + s_integer & operator--() { m_val--; return *this; } + const s_integer operator--(int) { s_integer tmp(*this); --(*this); return tmp; } + friend inline bool operator==(const s_integer & r1, const s_integer & r2) { return r1.m_val == r2.m_val; } + friend inline bool operator<(const s_integer & r1, const s_integer & r2) { return r1.m_val < r2.m_val; } + void neg() { m_val = -m_val; } + bool is_zero() const { return m_val == 0; } + bool is_one() const { return m_val == 1; } + bool is_minus_one() const { return m_val == -1; } + bool is_neg() const { return m_val < 0; } + bool is_pos() const { return m_val > 0; } + bool is_nonneg() const {return m_val >= 0; } + bool is_nonpos() const { return m_val <= 0; } + bool is_even() const { return (!(m_val & 0x1)); } + friend inline s_integer floor(const s_integer & r) { return r; } + friend inline s_integer ceil(const s_integer & r) { return r; } + s_integer expt(int n) const { + s_integer result(1); + for (int i = 0; i < n; i++) { + result *= *this; + } + return result; + } + + static const s_integer & zero() { return m_zero; } + static const s_integer & one() { return m_one; } + static const s_integer & minus_one() { return m_minus_one; } + // Perform: this += c * k + void addmul(const s_integer & c, const s_integer & k) { m_val += c.m_val * k.m_val; } + void submul(const s_integer & c, const s_integer & k) { m_val -= c.m_val * k.m_val; } + friend inline std::ostream & operator<<(std::ostream & target, const s_integer & r) { + target << r.m_val; + return target; + } + + rational to_rational() const { return rational(m_val); } +}; + +inline bool operator!=(const s_integer & r1, const s_integer & r2) { return !operator==(r1, r2); } +inline bool operator>(const s_integer & r1, const s_integer & r2) { return operator<(r2, r1); } +inline bool operator<=(const s_integer & r1, const s_integer & r2) { return !operator>(r1, r2); } +inline bool operator>=(const s_integer & r1, const s_integer & r2) { return !operator<(r1, r2); } +inline s_integer operator+(const s_integer & r1, const s_integer & r2) { return s_integer(r1) += r2; } +inline s_integer operator-(const s_integer & r1, const s_integer & r2) { return s_integer(r1) -= r2; } +inline s_integer operator-(const s_integer & r) { s_integer result(r); result.neg(); return result; } +inline s_integer operator*(const s_integer & r1, const s_integer & r2) { return s_integer(r1) *= r2; } +inline s_integer operator/(const s_integer & r1, const s_integer & r2) { return s_integer(r1) /= r2; } +inline s_integer operator%(const s_integer & r1, const s_integer & r2) { return s_integer(r1) %= r2; } +s_integer power(const s_integer & r, unsigned p); +s_integer gcd(const s_integer & r1, const s_integer & r2); +s_integer lcm(const s_integer & r1, const s_integer & r2); +inline s_integer abs(const s_integer & r) { + s_integer result(r); + if (result.is_neg()) { + result.neg(); + } + return result; +} + +#endif /* _S_INTEGER_H_ */ + diff --git a/lib/sat_asymm_branch.cpp b/lib/sat_asymm_branch.cpp new file mode 100644 index 000000000..bc2a7120d --- /dev/null +++ b/lib/sat_asymm_branch.cpp @@ -0,0 +1,218 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_asymm_branch.cpp + +Abstract: + + SAT solver asymmetric branching + +Author: + + Leonardo de Moura (leonardo) 2011-05-30. + +Revision History: + +--*/ +#include"sat_asymm_branch.h" +#include"sat_solver.h" +#include"stopwatch.h" +#include"trace.h" + +namespace sat { + + asymm_branch::asymm_branch(solver & _s, params_ref const & p): + s(_s), + m_counter(0) { + updt_params(p); + reset_statistics(); + } + + struct clause_size_lt { + bool operator()(clause * c1, clause * c2) const { return c1->size() > c2->size(); } + }; + + struct asymm_branch::report { + asymm_branch & m_asymm_branch; + stopwatch m_watch; + unsigned m_elim_literals; + report(asymm_branch & a): + m_asymm_branch(a), + m_elim_literals(a.m_elim_literals) { + m_watch.start(); + } + + ~report() { + m_watch.stop(); + IF_VERBOSE(SAT_VB_LVL, + verbose_stream() << " (sat-asymm-branch :elim-literals " + << (m_asymm_branch.m_elim_literals - m_elim_literals) + << " :cost " << m_asymm_branch.m_counter + << mem_stat() + << " :time " << std::fixed << std::setprecision(2) << m_watch.get_seconds() << ")\n";); + } + }; + + void asymm_branch::operator()(bool force) { + if (!m_asymm_branch) + return; + s.propagate(false); // must propagate, since it uses s.push() + if (s.m_inconsistent) + return; + if (!force && m_counter > 0) + return; + CASSERT("asymm_branch", s.check_invariant()); + TRACE("asymm_branch_detail", s.display(tout);); + report rpt(*this); + svector saved_phase(s.m_phase); + m_counter = 0; // counter is moving down to capture propagate cost. + int limit = -static_cast(m_asymm_branch_limit); + std::stable_sort(s.m_clauses.begin(), s.m_clauses.end(), clause_size_lt()); + m_counter -= s.m_clauses.size(); + clause_vector::iterator it = s.m_clauses.begin(); + clause_vector::iterator it2 = it; + clause_vector::iterator end = s.m_clauses.end(); + try { + for (; it != end; ++it) { + SASSERT(s.m_qhead == s.m_trail.size()); + if (m_counter < limit || s.inconsistent()) { + *it2 = *it; + ++it2; + continue; + } + s.checkpoint(); + clause & c = *(*it); + m_counter -= c.size(); + if (!process(c)) + continue; // clause was removed + *it2 = *it; + // throw exception to test bug fix: if (it2 != it) throw solver_exception("trigger bug"); + ++it2; + } + s.m_clauses.set_end(it2); + } + catch (solver_exception & ex) { + // put m_clauses in a consistent state... + for (; it != end; ++it, ++it2) { + *it2 = *it; + } + s.m_clauses.set_end(it2); + m_counter = -m_counter; + throw ex; + } + m_counter = -m_counter; + s.m_phase = saved_phase; + CASSERT("asymm_branch", s.check_invariant()); + } + + bool asymm_branch::process(clause & c) { + TRACE("asymm_branch_detail", tout << "processing: " << c << "\n";); + SASSERT(s.scope_lvl() == 0); +#ifdef Z3DEBUG + unsigned trail_sz = s.m_trail.size(); +#endif + SASSERT(!s.inconsistent()); + unsigned sz = c.size(); + SASSERT(sz > 0); + unsigned i; + // check if the clause is already satisfied + for (i = 0; i < sz; i++) { + if (s.value(c[i]) == l_true) { + s.dettach_clause(c); + s.del_clause(c); + return false; + } + } + // try asymmetric branching + // clause must not be used for propagation + s.dettach_clause(c); + s.push(); + for (i = 0; i < sz - 1; i++) { + literal l = c[i]; + SASSERT(!s.inconsistent()); + TRACE("asymm_branch_detail", tout << "assigning: " << ~l << "\n";); + s.assign(~l, justification()); + s.propagate_core(false); // must not use propagate(), since check_missed_propagation may fail for c + if (s.inconsistent()) + break; + } + s.pop(1); + SASSERT(!s.inconsistent()); + SASSERT(s.scope_lvl() == 0); + SASSERT(trail_sz == s.m_trail.size()); + if (i == sz - 1) { + // clause size can't be reduced. + s.attach_clause(c); + return true; + } + // clause can be reduced + unsigned new_sz = i+1; + SASSERT(new_sz >= 1); + SASSERT(new_sz < sz); + TRACE("asymm_branch", tout << c << "\nnew_size: " << new_sz << "\n"; + for (unsigned i = 0; i < c.size(); i++) tout << static_cast(s.value(c[i])) << " "; tout << "\n";); + // cleanup reduced clause + unsigned j = 0; + for (i = 0; i < new_sz; i++) { + literal l = c[i]; + switch (s.value(l)) { + case l_undef: + c[j] = l; + j++; + break; + case l_false: + break; + case l_true: + UNREACHABLE(); + break; + } + } + new_sz = j; + m_elim_literals += sz - new_sz; + switch(new_sz) { + case 0: + s.set_conflict(justification()); + return false; + case 1: + TRACE("asymm_branch", tout << "produced unit clause: " << c[0] << "\n";); + s.assign(c[0], justification()); + s.del_clause(c); + s.propagate_core(false); + return false; // check_missed_propagation() may fail, since m_clauses is not in a consistent state. + case 2: + SASSERT(s.value(c[0]) == l_undef && s.value(c[1]) == l_undef); + s.mk_bin_clause(c[0], c[1], false); + s.del_clause(c); + return false; + default: + c.shrink(new_sz); + s.attach_clause(c); + return true; + } + } + + void asymm_branch::updt_params(params_ref const & p) { + m_asymm_branch = p.get_bool(":asymm-branch", true); + m_asymm_branch_rounds = p.get_uint(":asymm-branch-rounds", 32); + m_asymm_branch_limit = p.get_uint(":asymm-branch-limit", 100000000); + if (m_asymm_branch_limit > INT_MAX) + m_asymm_branch_limit = INT_MAX; + } + + void asymm_branch::collect_param_descrs(param_descrs & d) { + d.insert(":asymm-branch", CPK_BOOL, "(default: true) asymmetric branching."); + d.insert(":asymm-branch-rounds", CPK_UINT, "(default: 32) maximum number of rounds of asymmetric branching."); + d.insert(":asymm-branch-limit", CPK_UINT, "approx. maximum number of literals visited during asymmetric branching."); + } + + void asymm_branch::collect_statistics(statistics & st) { + st.update("elim literals", m_elim_literals); + } + + void asymm_branch::reset_statistics() { + m_elim_literals = 0; + } + +}; diff --git a/lib/sat_asymm_branch.h b/lib/sat_asymm_branch.h new file mode 100644 index 000000000..6ffd239eb --- /dev/null +++ b/lib/sat_asymm_branch.h @@ -0,0 +1,60 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_asymm_branch.h + +Abstract: + + SAT solver asymmetric branching + +Author: + + Leonardo de Moura (leonardo) 2011-05-30. + +Revision History: + +--*/ +#ifndef _SAT_ASYMM_BRANCH_H_ +#define _SAT_ASYMM_BRANCH_H_ + +#include"sat_types.h" +#include"statistics.h" +#include"params.h" + +namespace sat { + class solver; + + class asymm_branch { + struct report; + + solver & s; + int m_counter; + + // config + bool m_asymm_branch; + unsigned m_asymm_branch_rounds; + unsigned m_asymm_branch_limit; + + // stats + unsigned m_elim_literals; + + bool process(clause & c); + public: + asymm_branch(solver & s, params_ref const & p); + + void operator()(bool force = false); + + void updt_params(params_ref const & p); + static void collect_param_descrs(param_descrs & d); + + void collect_statistics(statistics & st); + void reset_statistics(); + + void dec(unsigned c) { m_counter -= c; } + }; + +}; + +#endif diff --git a/lib/sat_clause.cpp b/lib/sat_clause.cpp new file mode 100644 index 000000000..f6d081a83 --- /dev/null +++ b/lib/sat_clause.cpp @@ -0,0 +1,236 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_clause.cpp + +Abstract: + + Clauses + +Author: + + Leonardo de Moura (leonardo) 2011-05-21. + +Revision History: + +--*/ +#include"memory.h" +#include"sat_clause.h" +#include"z3_exception.h" + +namespace sat { + + clause::clause(unsigned id, unsigned sz, literal const * lits, bool learned): + m_id(id), + m_size(sz), + m_capacity(sz), + m_removed(false), + m_learned(learned), + m_used(false), + m_frozen(false), + m_reinit_stack(false), + m_inact_rounds(0) { + memcpy(m_lits, lits, sizeof(literal) * sz); + mark_strengthened(); + SASSERT(check_approx()); + } + + var_approx_set clause::approx(unsigned num, literal const * lits) { + var_approx_set r; + for (unsigned i = 0; i < num; i++) + r.insert(lits[i].var()); + return r; + } + + void clause::update_approx() { + m_approx = approx(m_size, m_lits); + } + + bool clause::check_approx() const { + var_approx_set curr = m_approx; + const_cast(this)->update_approx(); + SASSERT(may_eq(curr, m_approx)); + return true; + } + + bool clause::contains(literal l) const { + for (unsigned i = 0; i < m_size; i++) + if (m_lits[i] == l) + return true; + return false; + } + + bool clause::contains(bool_var v) const { + for (unsigned i = 0; i < m_size; i++) + if (m_lits[i].var() == v) + return true; + return false; + } + + void clause::elim(literal l) { + unsigned i; + for (i = 0; i < m_size; i++) + if (m_lits[i] == l) + break; + SASSERT(i < m_size); + i++; + for (; i < m_size; i++) + m_lits[i-1] = m_lits[i]; + m_size--; + mark_strengthened(); + } + + bool clause::satisfied_by(model const & m) const { + for (unsigned i = 0; i < m_size; i++) { + literal l = m_lits[i]; + if (l.sign()) { + if (m[l.var()] == l_false) + return true; + } + else { + if (m[l.var()] == l_true) + return true; + } + } + return false; + } + + void tmp_clause::set(unsigned num_lits, literal const * lits, bool learned) { + if (m_clause && m_clause->m_capacity < num_lits) { + dealloc_svect(m_clause); + m_clause = 0; + } + if (!m_clause) { + void * mem = alloc_svect(char, clause::get_obj_size(num_lits)); + m_clause = new (mem) clause(UINT_MAX, num_lits, lits, learned); + } + else { + SASSERT(m_clause->m_id == UINT_MAX); + m_clause->m_size = num_lits; + m_clause->m_learned = learned; + memcpy(m_clause->m_lits, lits, sizeof(literal) * num_lits); + } + SASSERT(m_clause->m_size <= m_clause->m_capacity); + for (unsigned i = 0; i < num_lits; i++) { + SASSERT((*m_clause)[i] == lits[i]); + } + } + + clause_allocator::clause_allocator(): + m_allocator("clause-allocator") { +#ifdef _AMD64_ + m_num_segments = 0; +#endif + } + + clause * clause_allocator::get_clause(clause_offset cls_off) const { +#ifdef _AMD64_ + return reinterpret_cast(m_segments[cls_off & c_aligment_mask] + (static_cast(cls_off) & ~c_aligment_mask)); +#else + return reinterpret_cast(cls_off); +#endif + } + +#ifdef _AMD64_ + unsigned clause_allocator::get_segment(size_t ptr) { + SASSERT((ptr & c_aligment_mask) == 0); + ptr &= ~0xFFFFFFFFull; // Keep only high part + unsigned i = 0; + for (i = 0; i < m_num_segments; ++i) + if (m_segments[i] == ptr) + return i; + i = m_num_segments; + m_num_segments++; + if (i > c_max_segments) + throw default_exception("segment out of range"); + m_segments[i] = ptr; + return i; + } +#endif + + clause_offset clause_allocator::get_offset(clause const * ptr) const { +#ifdef _AMD64_ + return static_cast(reinterpret_cast(ptr)) + const_cast(this)->get_segment(reinterpret_cast(ptr)); +#else + return reinterpret_cast(ptr); +#endif + } + + clause * clause_allocator::mk_clause(unsigned num_lits, literal const * lits, bool learned) { + size_t size = clause::get_obj_size(num_lits); +#ifdef _AMD64_ + size_t slot = size >> c_cls_alignment; + if ((size & c_aligment_mask) != 0) + slot++; + size = slot << c_cls_alignment; +#endif + void * mem = m_allocator.allocate(size); + clause * cls = new (mem) clause(m_id_gen.mk(), num_lits, lits, learned); + SASSERT(!learned || cls->is_learned()); + return cls; + } + + void clause_allocator::del_clause(clause * cls) { + m_id_gen.recycle(cls->id()); + size_t size = clause::get_obj_size(cls->m_capacity); +#ifdef _AMD64_ + size_t slot = size >> c_cls_alignment; + if ((size & c_aligment_mask) != 0) + slot++; + size = slot << c_cls_alignment; +#endif + cls->~clause(); + m_allocator.deallocate(size, cls); + } + + std::ostream & operator<<(std::ostream & out, clause const & c) { + out << "("; + for (unsigned i = 0; i < c.size(); i++) { + if (i > 0) out << " "; + out << c[i]; + } + out << ")"; + if (c.was_removed()) out << "x"; + if (c.strengthened()) out << "+"; + if (c.is_learned()) out << "*"; + return out; + } + + std::ostream & operator<<(std::ostream & out, clause_vector const & cs) { + clause_vector::const_iterator it = cs.begin(); + clause_vector::const_iterator end = cs.end(); + for (; it != end; ++it) { + out << *(*it) << "\n"; + } + return out; + } + + bool clause_wrapper::contains(literal l) const { + unsigned sz = size(); + for (unsigned i = 0; i < sz; i++) + if (operator[](i) == l) + return true; + return false; + } + + bool clause_wrapper::contains(bool_var v) const { + unsigned sz = size(); + for (unsigned i = 0; i < sz; i++) + if (operator[](i).var() == v) + return true; + return false; + } + + std::ostream & operator<<(std::ostream & out, clause_wrapper const & c) { + out << "("; + for (unsigned i = 0; i < c.size(); i++) { + if (i > 0) out << " "; + out << c[i]; + } + out << ")"; + return out; + } + +}; diff --git a/lib/sat_clause.h b/lib/sat_clause.h new file mode 100644 index 000000000..87fc5e763 --- /dev/null +++ b/lib/sat_clause.h @@ -0,0 +1,179 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_clause.h + +Abstract: + + Clauses + +Author: + + Leonardo de Moura (leonardo) 2011-05-21. + +Revision History: + +--*/ +#ifndef _SAT_CLAUSE_H_ +#define _SAT_CLAUSE_H_ + +#include"sat_types.h" +#include"small_object_allocator.h" +#include"id_gen.h" + +#ifdef _MSC_VER +#pragma warning(disable : 4200) +#pragma warning(disable : 4355) +#endif + +namespace sat { + + class clause_allocator; + + class clause { + friend class clause_allocator; + friend class tmp_clause; + unsigned m_id; + unsigned m_size; + unsigned m_capacity; + var_approx_set m_approx; + unsigned m_strengthened:1; + unsigned m_removed:1; + unsigned m_learned:1; + unsigned m_used:1; + unsigned m_frozen:1; + unsigned m_reinit_stack:1; + unsigned m_inact_rounds:8; + unsigned m_glue:8; + unsigned m_psm:8; // transient field used during gc + literal m_lits[0]; + + static size_t get_obj_size(unsigned num_lits) { return sizeof(clause) + num_lits * sizeof(literal); } + size_t get_size() const { return get_obj_size(m_capacity); } + clause(unsigned id, unsigned sz, literal const * lits, bool learned); + public: + unsigned id() const { return m_id; } + unsigned size() const { return m_size; } + literal & operator[](unsigned idx) { SASSERT(idx < m_size); return m_lits[idx]; } + literal const & operator[](unsigned idx) const { SASSERT(idx < m_size); return m_lits[idx]; } + bool is_learned() const { return m_learned; } + void unset_learned() { SASSERT(is_learned()); m_learned = false; } + void shrink(unsigned num_lits) { SASSERT(num_lits <= m_size); if (num_lits < m_size) { m_size = num_lits; mark_strengthened(); } } + bool strengthened() const { return m_strengthened; } + void mark_strengthened() { m_strengthened = true; update_approx(); } + void unmark_strengthened() { m_strengthened = false; } + void elim(literal l); + bool was_removed() const { return m_removed; } + void set_removed(bool f) { m_removed = f; } + var_approx_set approx() const { return m_approx; } + void update_approx(); + bool check_approx() const; // for debugging + literal * begin() { return m_lits; } + literal * end() { return m_lits + m_size; } + bool contains(literal l) const; + bool contains(bool_var v) const; + bool satisfied_by(model const & m) const; + void mark_used() { m_used = true; } + void unmark_used() { m_used = false; } + bool was_used() const { return m_used; } + void inc_inact_rounds() { m_inact_rounds++; } + void reset_inact_rounds() { m_inact_rounds = 0; } + unsigned inact_rounds() const { return m_inact_rounds; } + bool frozen() const { return m_frozen; } + void freeze() { SASSERT(is_learned()); SASSERT(!frozen()); m_frozen = true; } + void unfreeze() { SASSERT(is_learned()); SASSERT(frozen()); m_frozen = false; } + static var_approx_set approx(unsigned num, literal const * lits); + void set_glue(unsigned glue) { m_glue = glue > 255 ? 255 : glue; } + unsigned glue() const { return m_glue; } + void set_psm(unsigned psm) { m_psm = psm > 255 ? 255 : psm; } + unsigned psm() const { return m_psm; } + + bool on_reinit_stack() const { return m_reinit_stack; } + void set_reinit_stack(bool f) { m_reinit_stack = f; } + }; + + std::ostream & operator<<(std::ostream & out, clause const & c); + std::ostream & operator<<(std::ostream & out, clause_vector const & cs); + + class bin_clause { + unsigned m_val1; + unsigned m_val2; + public: + bin_clause(literal l1, literal l2, bool learned):m_val1(l1.to_uint()), m_val2((l2.to_uint() << 1) + static_cast(learned)) {} + literal get_literal1() const { return to_literal(m_val1); } + literal get_literal2() const { return to_literal(m_val2 >> 1); } + bool is_learned() const { return (m_val2 & 1) == 1; } + }; + + class tmp_clause { + clause * m_clause; + public: + tmp_clause():m_clause(0) {} + ~tmp_clause() { if (m_clause) dealloc_svect(m_clause); } + clause * get() const { return m_clause; } + void set(unsigned num_lits, literal const * lits, bool learned); + void set(literal l1, literal l2, bool learned) { literal ls[2] = { l1, l2 }; set(2, ls, learned); } + void set(bin_clause const & c) { set(c.get_literal1(), c.get_literal2(), c.is_learned()); } + }; + + /** + \brief Simple clause allocator that allows uint (32bit integers) to be used to reference clauses (even in 64bit machines). + */ + class clause_allocator { + small_object_allocator m_allocator; + id_gen m_id_gen; +#ifdef _AMD64_ + unsigned get_segment(size_t ptr); + static const unsigned c_cls_alignment = 3; + static const unsigned c_max_segments = 1 << c_cls_alignment; + static const size_t c_aligment_mask = (1ull << c_cls_alignment) - 1ull; + unsigned m_num_segments; + size_t m_segments[c_max_segments]; +#endif + public: + clause_allocator(); + clause * get_clause(clause_offset cls_off) const; + clause_offset get_offset(clause const * ptr) const; + clause * mk_clause(unsigned num_lits, literal const * lits, bool learned); + void del_clause(clause * cls); + }; + + /** + \brief Wrapper for clauses & binary clauses. + I do not create clause objects for binary clauses. + clause_ref wraps a clause object or a pair of literals (i.e., a binary clause). + */ + class clause_wrapper { + union { + clause * m_cls; + unsigned m_l1_idx; + }; + unsigned m_l2_idx; + public: + clause_wrapper(literal l1, literal l2):m_l1_idx(l1.to_uint()), m_l2_idx(l2.to_uint()) {} + clause_wrapper(clause & c):m_cls(&c), m_l2_idx(null_literal.to_uint()) {} + + bool is_binary() const { return m_l2_idx != null_literal.to_uint(); } + unsigned size() const { return is_binary() ? 2 : m_cls->size(); } + literal operator[](unsigned idx) const { + SASSERT(idx < size()); + if (is_binary()) + return idx == 0 ? to_literal(m_l1_idx) : to_literal(m_l2_idx); + else + return m_cls->operator[](idx); + } + + bool contains(literal l) const; + bool contains(bool_var v) const; + clause * get_clause() const { SASSERT(!is_binary()); return m_cls; } + }; + + typedef svector clause_wrapper_vector; + + std::ostream & operator<<(std::ostream & out, clause_wrapper const & c); + +}; + +#endif diff --git a/lib/sat_clause_set.cpp b/lib/sat_clause_set.cpp new file mode 100644 index 000000000..ca8e9d841 --- /dev/null +++ b/lib/sat_clause_set.cpp @@ -0,0 +1,84 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_clause_set.cpp + +Abstract: + + Set of clauses + +Author: + + Leonardo de Moura (leonardo) 2011-05-25. + +Revision History: + +--*/ +#include"sat_clause_set.h" + +namespace sat { + + void clause_set::insert(clause & c) { + unsigned id = c.id(); + m_id2pos.reserve(id+1, UINT_MAX); + if (m_id2pos[id] != UINT_MAX) + return; // already in the set + unsigned pos = m_set.size(); + m_id2pos[id] = pos; + m_set.push_back(&c); + CASSERT("clause_set", check_invariant()); + } + + void clause_set::erase(clause & c) { + unsigned id = c.id(); + if (id >= m_id2pos.size()) + return; + unsigned pos = m_id2pos[id]; + if (pos == UINT_MAX) + return; + m_id2pos[id] = UINT_MAX; + unsigned last_pos = m_set.size() - 1; + if (pos != last_pos) { + clause * last_c = m_set[last_pos]; + m_set[pos] = last_c; + m_id2pos[last_c->id()] = pos; + } + m_set.pop_back(); + CASSERT("clause_set", check_invariant()); + } + + clause & clause_set::erase() { + SASSERT(!empty()); + clause & c = *m_set.back(); + m_id2pos[c.id()] = UINT_MAX; + m_set.pop_back(); + return c; + } + + bool clause_set::check_invariant() const { + { + clause_vector::const_iterator it = m_set.begin(); + clause_vector::const_iterator end = m_set.end(); + for (unsigned pos = 0; it != end; ++it, ++pos) { + clause & c = *(*it); + SASSERT(c.id() < m_id2pos.size()); + SASSERT(m_id2pos[c.id()] == pos); + } + } + { + unsigned_vector::const_iterator it = m_id2pos.begin(); + unsigned_vector::const_iterator end = m_id2pos.end(); + for (unsigned id = 0; it != end; ++it, ++id) { + unsigned pos = *it; + if (pos == UINT_MAX) + continue; + SASSERT(pos < m_set.size()); + SASSERT(m_set[pos]->id() == id); + } + } + return true; + } + +}; diff --git a/lib/sat_clause_set.h b/lib/sat_clause_set.h new file mode 100644 index 000000000..ac95e06d5 --- /dev/null +++ b/lib/sat_clause_set.h @@ -0,0 +1,56 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_clause_set.h + +Abstract: + + Set of clauses + +Author: + + Leonardo de Moura (leonardo) 2011-05-25. + +Revision History: + +--*/ +#ifndef _SAT_CLAUSE_SET_H_ +#define _SAT_CLAUSE_SET_H_ + +#include"sat_clause.h" + +namespace sat { + + class clause_set { + unsigned_vector m_id2pos; + clause_vector m_set; + public: + typedef clause_vector::const_iterator iterator; + + bool contains(clause const & cls) const { + if (cls.id() >= m_id2pos.size()) + return false; + return m_id2pos[cls.id()] != UINT_MAX; + } + bool empty() const { return m_set.empty(); } + unsigned size() const { return m_set.size(); } + void insert(clause & c); + void erase(clause & c); + + // erase some clause from the set + clause & erase(); + + void reset() { m_id2pos.reset(); m_set.reset(); } + void finalize() { m_id2pos.finalize(); m_set.finalize(); } + + iterator begin() const { return m_set.begin(); } + iterator end() const { return m_set.end(); } + + bool check_invariant() const; + }; + +}; + +#endif diff --git a/lib/sat_clause_use_list.cpp b/lib/sat_clause_use_list.cpp new file mode 100644 index 000000000..f22e6a822 --- /dev/null +++ b/lib/sat_clause_use_list.cpp @@ -0,0 +1,57 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_clause_use_list.cpp + +Abstract: + + Clause use list + +Author: + + Leonardo de Moura (leonardo) 2011-05-31. + +Revision History: + +--*/ +#include"sat_clause.h" +#include"sat_clause_use_list.h" + +namespace sat { + + bool clause_use_list::check_invariant() const { +#ifdef LAZY_USE_LIST + unsigned sz = 0; + for (unsigned i = 0; i < m_clauses.size(); i++) + if (!m_clauses[i]->was_removed()) + sz++; + SASSERT(sz == m_size); +#endif + return true; + } + +#ifdef LAZY_USE_LIST + void clause_use_list::iterator::consume() { + while (true) { + if (m_i == m_size) + return; + if (!m_clauses[m_i]->was_removed()) { + m_clauses[m_j] = m_clauses[m_i]; + return; + } + m_i++; + } + } +#endif + + clause_use_list::iterator::~iterator() { +#ifdef LAZY_USE_LIST + while (m_i < m_size) + next(); + m_clauses.shrink(m_j); +#endif + } + +}; diff --git a/lib/sat_clause_use_list.h b/lib/sat_clause_use_list.h new file mode 100644 index 000000000..ccf4b4a5b --- /dev/null +++ b/lib/sat_clause_use_list.h @@ -0,0 +1,118 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_clause_use_list.h + +Abstract: + + Clause use list + +Author: + + Leonardo de Moura (leonardo) 2011-05-31. + +Revision History: + +--*/ +#ifndef _SAT_CLAUSE_USE_LIST_H_ +#define _SAT_CLAUSE_USE_LIST_H_ + +#include"sat_types.h" + +namespace sat { + +#define LAZY_USE_LIST + + /** + \brief Clause use list with delayed deletion. + */ + class clause_use_list { + clause_vector m_clauses; +#ifdef LAZY_USE_LIST + unsigned m_size; +#endif + public: + clause_use_list() { +#ifdef LAZY_USE_LIST + m_size = 0; +#endif + } + + unsigned size() const { +#ifdef LAZY_USE_LIST + return m_size; +#else + return m_clauses.size(); +#endif + } + + bool empty() const { return size() == 0; } + + void insert(clause & c) { + SASSERT(!m_clauses.contains(&c)); SASSERT(!c.was_removed()); + m_clauses.push_back(&c); +#ifdef LAZY_USE_LIST + m_size++; +#endif + } + void erase_not_removed(clause & c) { +#ifdef LAZY_USE_LIST + SASSERT(m_clauses.contains(&c)); SASSERT(!c.was_removed()); m_clauses.erase(&c); m_size--; +#else + m_clauses.erase(&c); +#endif + } + void erase(clause & c) { +#ifdef LAZY_USE_LIST + SASSERT(m_clauses.contains(&c)); SASSERT(c.was_removed()); m_size--; +#else + m_clauses.erase(&c); +#endif + } + + void reset() { + m_clauses.finalize(); +#ifdef LAZY_USE_LIST + m_size = 0; +#endif + } + + bool check_invariant() const; + // iterate & compress + class iterator { + clause_vector & m_clauses; + unsigned m_size; + unsigned m_i; +#ifdef LAZY_USE_LIST + unsigned m_j; + void consume(); +#endif + public: + iterator(clause_vector & v):m_clauses(v), m_size(v.size()), m_i(0) { +#ifdef LAZY_USE_LIST + m_j = 0; + consume(); +#endif + } + ~iterator(); + bool at_end() const { return m_i == m_size; } + clause & curr() const { SASSERT(!at_end()); return *(m_clauses[m_i]); } + void next() { + SASSERT(!at_end()); + SASSERT(!m_clauses[m_i]->was_removed()); + m_i++; +#ifdef LAZY_USE_LIST + m_j++; + consume(); +#endif + } + }; + + iterator mk_iterator() const { return iterator(const_cast(this)->m_clauses); } + }; + +}; + +#endif diff --git a/lib/sat_cleaner.cpp b/lib/sat_cleaner.cpp new file mode 100644 index 000000000..4b1ac2c92 --- /dev/null +++ b/lib/sat_cleaner.cpp @@ -0,0 +1,214 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_cleaner.h + +Abstract: + + Eliminate satisfied clauses, and literals assigned to false. + +Author: + + Leonardo de Moura (leonardo) 2011-05-24. + +Revision History: + +--*/ +#include"sat_cleaner.h" +#include"sat_solver.h" +#include"trace.h" +#include"stopwatch.h" + +namespace sat { + + cleaner::cleaner(solver & _s): + s(_s), + m_last_num_units(0), + m_cleanup_counter(0) { + reset_statistics(); + } + + /** + - Delete watch lists of assigned literals. + - Delete satisfied binary watched binary clauses + - Delete watched clauses (they will be reinserted after they are cleaned). + */ + void cleaner::cleanup_watches() { + vector::iterator it = s.m_watches.begin(); + vector::iterator end = s.m_watches.end(); + unsigned l_idx = 0; + for (; it != end; ++it, ++l_idx) { + if (s.value(to_literal(l_idx)) != l_undef) { + it->finalize(); + SASSERT(it->empty()); + continue; + } + TRACE("cleanup_bug", tout << "processing wlist of " << to_literal(l_idx) << "\n";); + watch_list & wlist = *it; + watch_list::iterator it2 = wlist.begin(); + watch_list::iterator it_prev = it2; + watch_list::iterator end2 = wlist.end(); + for (; it2 != end2; ++it2) { + switch (it2->get_kind()) { + case watched::BINARY: + SASSERT(s.value(it2->get_literal()) == l_true || s.value(it2->get_literal()) == l_undef); + if (s.value(it2->get_literal()) == l_undef) { + *it_prev = *it2; + ++it_prev; + } + TRACE("cleanup_bug", tout << "keeping: " << ~to_literal(l_idx) << " " << it2->get_literal() << "\n";); + break; + case watched::TERNARY: + case watched::CLAUSE: + // skip + break; + case watched::EXT_CONSTRAINT: + *it_prev = *it2; + ++it_prev; + break; + default: + UNREACHABLE(); + break; + } + } + wlist.set_end(it_prev); + } + } + + void cleaner::cleanup_clauses(clause_vector & cs) { + clause_vector::iterator it = cs.begin(); + clause_vector::iterator it2 = it; + clause_vector::iterator end = cs.end(); + for (; it != end; ++it) { + clause & c = *(*it); + TRACE("sat_cleaner_bug", tout << "cleaning: " << c << "\n"; + for (unsigned i = 0; i < c.size(); i++) tout << c[i] << ": " << s.value(c[i]) << "\n";); + CTRACE("sat_cleaner_frozen", c.frozen(), tout << c << "\n";); + unsigned sz = c.size(); + unsigned i = 0, j = 0; + bool sat = false; + m_cleanup_counter += sz; + for (; i < sz; i++) { + switch (s.value(c[i])) { + case l_true: + sat = true; + goto end_loop; + case l_false: + m_elim_literals++; + break; + case l_undef: + c[j] = c[i]; + j++; + break; + } + } + end_loop: + CTRACE("sat_cleaner_frozen", c.frozen(), + tout << "sat: " << sat << ", new_size: " << j << "\n"; + tout << mk_lits_pp(j, c.begin()) << "\n";); + if (sat) { + m_elim_clauses++; + s.del_clause(c); + } + else { + unsigned new_sz = j; + CTRACE("sat_cleaner_bug", new_sz < 2, tout << "new_sz: " << new_sz << "\n"; + if (c.size() > 0) tout << "unit: " << c[0] << "\n";); + SASSERT(c.frozen() || new_sz >= 2); + if (new_sz == 0) { + // It can only happen with frozen clauses. + // active clauses would have signed the conflict. + SASSERT(c.frozen()); + s.set_conflict(justification()); + s.del_clause(c); + } + else if (new_sz == 1) { + // It can only happen with frozen clauses. + // active clauses would have propagated the literal + SASSERT(c.frozen()); + s.assign(c[0], justification()); + s.del_clause(c); + } + else { + SASSERT(s.value(c[0]) == l_undef && s.value(c[1]) == l_undef); + if (new_sz == 2) { + TRACE("cleanup_bug", tout << "clause became binary: " << c[0] << " " << c[1] << "\n";); + s.mk_bin_clause(c[0], c[1], c.is_learned()); + s.del_clause(c); + } + else { + c.shrink(new_sz); + *it2 = *it; + it2++; + if (!c.frozen()) { + if (new_sz == 3) + s.attach_ter_clause(c); + else + s.attach_nary_clause(c); + } + } + } + } + } + cs.set_end(it2); + } + + struct cleaner::report { + cleaner & m_cleaner; + stopwatch m_watch; + unsigned m_elim_clauses; + unsigned m_elim_literals; + report(cleaner & c): + m_cleaner(c), + m_elim_clauses(c.m_elim_clauses), + m_elim_literals(c.m_elim_literals) { + m_watch.start(); + } + ~report() { + m_watch.stop(); + IF_VERBOSE(SAT_VB_LVL, + verbose_stream() << " (sat-cleaner :elim-literals " << (m_cleaner.m_elim_literals - m_elim_literals) + << " :elim-clauses " << (m_cleaner.m_elim_clauses - m_elim_clauses) + << " :cost " << m_cleaner.m_cleanup_counter + << mk_stat(m_cleaner.s) + << " :time " << std::fixed << std::setprecision(2) << m_watch.get_seconds() << ")\n";); + } + }; + + /** + \brief Return true if cleaner executed. + */ + bool cleaner::operator()(bool force) { + CASSERT("cleaner_bug", s.check_invariant()); + unsigned trail_sz = s.m_trail.size(); + s.propagate(false); // make sure that everything was propagated. + if (s.m_inconsistent) + return false; + if (m_last_num_units == trail_sz) + return false; // there are no new assigned literals since last time... nothing to be done + if (!force && m_cleanup_counter > 0) + return false; // prevent simplifier from being executed over and over again. + report rpt(*this); + m_last_num_units = trail_sz; + m_cleanup_counter = 0; + cleanup_watches(); + cleanup_clauses(s.m_clauses); + cleanup_clauses(s.m_learned); + s.propagate(false); + CASSERT("cleaner_bug", s.check_invariant()); + return true; + } + + void cleaner::reset_statistics() { + m_elim_clauses = 0; + m_elim_literals = 0; + } + + void cleaner::collect_statistics(statistics & st) { + st.update("elim clauses", m_elim_clauses); + st.update("elim literals", m_elim_literals); + } + +}; diff --git a/lib/sat_cleaner.h b/lib/sat_cleaner.h new file mode 100644 index 000000000..d4306afcd --- /dev/null +++ b/lib/sat_cleaner.h @@ -0,0 +1,53 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_cleaner.h + +Abstract: + + Eliminate satisfied clauses, and literals assigned to false. + +Author: + + Leonardo de Moura (leonardo) 2011-05-24. + +Revision History: + +--*/ +#ifndef _SAT_CLEANER_H_ +#define _SAT_CLEANER_H_ + +#include"sat_types.h" +#include"statistics.h" + +namespace sat { + + class cleaner { + struct report; + + solver & s; + unsigned m_last_num_units; + int m_cleanup_counter; + + // stats + unsigned m_elim_clauses; + unsigned m_elim_literals; + + void cleanup_watches(); + void cleanup_clauses(clause_vector & cs); + public: + cleaner(solver & s); + + bool operator()(bool force = false); + + void collect_statistics(statistics & st); + void reset_statistics(); + + void dec() { m_cleanup_counter--; } + }; + +}; + +#endif diff --git a/lib/sat_config.cpp b/lib/sat_config.cpp new file mode 100644 index 000000000..48aa270e4 --- /dev/null +++ b/lib/sat_config.cpp @@ -0,0 +1,126 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_config.cpp + +Abstract: + + SAT configuration options + +Author: + + Leonardo de Moura (leonardo) 2011-05-21. + +Revision History: + +--*/ +#include"sat_config.h" +#include"sat_types.h" + +namespace sat { + + config::config(params_ref const & p): + m_always_true("always-true"), + m_always_false("always-false"), + m_caching("caching"), + m_random("random"), + m_geometric("geometric"), + m_luby("luby"), + m_dyn_psm("dyn-psm"), + m_psm("psm"), + m_glue("glue"), + m_glue_psm("glue-psm"), + m_psm_glue("psm-glue") { + updt_params(p); + } + + void config::updt_params(params_ref const & p) { + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + + symbol s = p.get_sym(":restart", m_luby); + if (s == m_luby) + m_restart = RS_LUBY; + else if (s == m_geometric) + m_restart = RS_GEOMETRIC; + else + throw sat_param_exception("invalid restart strategy"); + + s = p.get_sym(":phase", m_caching); + if (s == m_always_false) + m_phase = PS_ALWAYS_FALSE; + else if (s == m_always_true) + m_phase = PS_ALWAYS_TRUE; + else if (s == m_caching) + m_phase = PS_CACHING; + else if (s == m_random) + m_phase = PS_RANDOM; + else + throw sat_param_exception("invalid phase selection strategy"); + + m_phase_caching_on = p.get_uint(":phase-caching-on", 400); + m_phase_caching_off = p.get_uint(":phase-caching-off", 100); + + m_restart_initial = p.get_uint(":restart-initial", 100); + m_restart_factor = p.get_double(":restart-factor", 1.5); + + m_random_freq = p.get_double(":random-freq", 0.01); + + m_burst_search = p.get_uint(":burst-search", 100); + + m_max_conflicts = p.get_uint(":max-conflicts", UINT_MAX); + + m_simplify_mult1 = p.get_uint(":simplify-mult1", 300); + m_simplify_mult2 = p.get_double(":simplify-mult2", 1.5); + m_simplify_max = p.get_uint(":simplify-max", 500000); + + s = p.get_sym(":gc-strategy", m_glue_psm); + if (s == m_dyn_psm) { + m_gc_strategy = GC_DYN_PSM; + m_gc_initial = p.get_uint(":gc-initial", 500); + m_gc_increment = p.get_uint(":gc-increment", 100); + m_gc_small_lbd = p.get_uint(":gc-small-lbd", 3); + m_gc_k = p.get_uint(":gc-k", 7); + if (m_gc_k > 255) + m_gc_k = 255; + } + else { + if (s == m_glue_psm) + m_gc_strategy = GC_GLUE_PSM; + else if (s == m_glue) + m_gc_strategy = GC_GLUE; + else if (s == m_psm) + m_gc_strategy = GC_PSM; + else if (s == m_psm_glue) + m_gc_strategy = GC_PSM_GLUE; + else + throw sat_param_exception("invalid gc strategy"); + m_gc_initial = p.get_uint(":gc-initial", 20000); + m_gc_increment = p.get_uint(":gc-increment", 500); + } + m_minimize_lemmas = p.get_bool(":minimize-lemmas", true); + m_dyn_sub_res = p.get_bool(":dyn-sub-res", true); + } + + void config::collect_param_descrs(param_descrs & r) { + insert_max_memory(r); + r.insert(":phase", CPK_SYMBOL, "(default: caching) phase selection strategy: always-false, always-true, caching, random."); + r.insert(":phase-caching-on", CPK_UINT, "(default: 400)"); + r.insert(":phase-caching-off", CPK_UINT, "(default: 100)"); + r.insert(":restart", CPK_SYMBOL, "(default: luby) restart strategy: luby or geometric."); + r.insert(":restart-initial", CPK_UINT, "(default: 100) initial restart (number of conflicts)."); + r.insert(":restart-factor", CPK_DOUBLE, "(default: 1.5) restart increment factor for geometric strategy."); + r.insert(":random-freq", CPK_DOUBLE, "(default: 0.01) frequency of random case splits."); + r.insert(":burst-search", CPK_UINT, "(default: 100) number of conflicts before first global simplification."); + r.insert(":max-conflicts", CPK_UINT, "(default: inf) maximum number of conflicts."); + r.insert(":gc-strategy", CPK_SYMBOL, "(default: glue-psm) garbage collection strategy: psm, glue, glue-psm, dyn-psm."); + r.insert(":gc-initial", CPK_UINT, "(default: 20000) learned clauses garbage collection frequence."); + r.insert(":gc-increment", CPK_UINT, "(default: 500) increment to the garbage collection threshold."); + r.insert(":gc-small-lbd", CPK_UINT, "(default: 3) learned clauses with small LBD are never deleted (only used in dyn-psm)."); + r.insert(":gc-k", CPK_UINT, "(default: 7) learned clauses that are inactive for k gc rounds are permanently deleted (only used in dyn-psm)."); + r.insert(":minimize-lemmas", CPK_BOOL, "(default: true) minimize learned clauses."); + r.insert(":dyn-sub-res", CPK_BOOL, "(default: true) dynamic subsumption resolution for minimizing learned clauses."); + } + +}; diff --git a/lib/sat_config.h b/lib/sat_config.h new file mode 100644 index 000000000..90ebe968b --- /dev/null +++ b/lib/sat_config.h @@ -0,0 +1,91 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_config.h + +Abstract: + + SAT main configuration options. + Sub-components have their own options. + +Author: + + Leonardo de Moura (leonardo) 2011-05-21. + +Revision History: + +--*/ +#ifndef _SAT_CONFIG_H_ +#define _SAT_CONFIG_H_ + +#include"params.h" + +namespace sat { + + enum phase_selection { + PS_ALWAYS_TRUE, + PS_ALWAYS_FALSE, + PS_CACHING, + PS_RANDOM + }; + + enum restart_strategy { + RS_GEOMETRIC, + RS_LUBY + }; + + enum gc_strategy { + GC_DYN_PSM, + GC_PSM, + GC_GLUE, + GC_GLUE_PSM, + GC_PSM_GLUE + }; + + struct config { + unsigned long long m_max_memory; + phase_selection m_phase; + unsigned m_phase_caching_on; + unsigned m_phase_caching_off; + restart_strategy m_restart; + unsigned m_restart_initial; + double m_restart_factor; // for geometric case + double m_random_freq; + unsigned m_burst_search; + unsigned m_max_conflicts; + + unsigned m_simplify_mult1; + double m_simplify_mult2; + unsigned m_simplify_max; + + gc_strategy m_gc_strategy; + unsigned m_gc_initial; + unsigned m_gc_increment; + unsigned m_gc_small_lbd; + unsigned m_gc_k; + + bool m_minimize_lemmas; + bool m_dyn_sub_res; + + symbol m_always_true; + symbol m_always_false; + symbol m_caching; + symbol m_random; + symbol m_geometric; + symbol m_luby; + + symbol m_dyn_psm; + symbol m_psm; + symbol m_glue; + symbol m_glue_psm; + symbol m_psm_glue; + + config(params_ref const & p); + void updt_params(params_ref const & p); + static void collect_param_descrs(param_descrs & d); + }; +}; + +#endif diff --git a/lib/sat_elim_eqs.cpp b/lib/sat_elim_eqs.cpp new file mode 100644 index 000000000..a2c1c7871 --- /dev/null +++ b/lib/sat_elim_eqs.cpp @@ -0,0 +1,220 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_elim_eqs.cpp + +Abstract: + + Helper class for eliminating eqs. + +Author: + + Leonardo de Moura (leonardo) 2011-05-27. + +Revision History: + +--*/ +#include"sat_elim_eqs.h" +#include"sat_solver.h" +#include"trace.h" + +namespace sat { + + elim_eqs::elim_eqs(solver & s): + m_solver(s) { + } + + inline literal norm(literal_vector const & roots, literal l) { + if (l.sign()) + return ~roots[l.var()]; + else + return roots[l.var()]; + } + + void elim_eqs::cleanup_bin_watches(literal_vector const & roots) { + vector::iterator it = m_solver.m_watches.begin(); + vector::iterator end = m_solver.m_watches.end(); + for (unsigned l_idx = 0; it != end; ++it, ++l_idx) { + watch_list & wlist = *it; + literal l1 = ~to_literal(l_idx); + literal r1 = norm(roots, l1); + watch_list::iterator it2 = wlist.begin(); + watch_list::iterator itprev = it2; + watch_list::iterator end2 = wlist.end(); + for (; it2 != end2; ++it2) { + if (it2->is_binary_clause()) { + literal l2 = it2->get_literal(); + literal r2 = norm(roots, l2); + if (r1 == r2) { + m_solver.assign(r1, justification()); + if (m_solver.inconsistent()) + return; + // consume unit + continue; + } + if (r1 == ~r2) { + // consume tautology + continue; + } + if (l1 != r1) { + // add half r1 => r2, the other half ~r2 => ~r1 is added when traversing l2 + m_solver.m_watches[(~r1).index()].push_back(watched(r2, it2->is_learned())); + continue; + } + it2->set_literal(r2); // keep it + } + *itprev = *it2; + itprev++; + } + wlist.set_end(itprev); + } + } + + void elim_eqs::cleanup_clauses(literal_vector const & roots, clause_vector & cs) { + clause_vector::iterator it = cs.begin(); + clause_vector::iterator it2 = it; + clause_vector::iterator end = cs.end(); + for (; it != end; ++it) { + clause & c = *(*it); + TRACE("elim_eqs", tout << "processing: " << c << "\n";); + unsigned sz = c.size(); + unsigned i; + for (i = 0; i < sz; i++) { + literal l = c[i]; + literal r = norm(roots, l); + if (l != r) + break; + } + if (i == sz) { + // clause was not affected + *it2 = *it; + it2++; + continue; + } + if (!c.frozen()) + m_solver.dettach_clause(c); + // apply substitution + for (i = 0; i < sz; i++) { + SASSERT(!m_solver.was_eliminated(c[i].var())); + c[i] = norm(roots, c[i]); + } + std::sort(c.begin(), c.end()); + TRACE("elim_eqs", tout << "after normalization/sorting: " << c << "\n";); + // remove duplicates, and check if it is a tautology + literal l_prev = null_literal; + unsigned j = 0; + for (i = 0; i < sz; i++) { + literal l = c[i]; + if (l == l_prev) + continue; + if (l == ~l_prev) + break; + l_prev = l; + lbool val = m_solver.value(l); + if (val == l_true) + break; // clause was satisfied + if (val == l_false) + continue; // skip + c[j] = l; + j++; + } + if (i < sz) { + // clause is a tautology or was simplified + m_solver.del_clause(c); + continue; + } + if (j == 0) { + // empty clause + m_solver.set_conflict(justification()); + return; + } + TRACE("elim_eqs", tout << "after removing duplicates: " << c << " j: " << j << "\n";); + if (j < sz) + c.shrink(j); + else + c.update_approx(); + SASSERT(c.size() == j); + DEBUG_CODE({ + for (unsigned i = 0; i < c.size(); i++) { + SASSERT(c[i] == norm(roots, c[i])); + } + }); + SASSERT(j >= 1); + switch (j) { + case 1: + m_solver.assign(c[0], justification()); + m_solver.del_clause(c); + break; + case 2: + m_solver.mk_bin_clause(c[0], c[1], c.is_learned()); + m_solver.del_clause(c); + break; + default: + SASSERT(*it == &c); + *it2 = *it; + it2++; + if (!c.frozen()) + m_solver.attach_clause(c); + break; + } + } + cs.set_end(it2); + } + + void elim_eqs::save_elim(literal_vector const & roots, bool_var_vector const & to_elim) { + model_converter & mc = m_solver.m_mc; + bool_var_vector::const_iterator it = to_elim.begin(); + bool_var_vector::const_iterator end = to_elim.end(); + for (; it != end; ++it) { + bool_var v = *it; + literal l(v, false); + literal r = roots[v]; + SASSERT(v != r.var()); + if (m_solver.is_external(v)) { + // cannot really eliminate v, since we have to notify extension of future assignments + m_solver.mk_bin_clause(~l, r, false); + m_solver.mk_bin_clause(l, ~r, false); + } + else { + model_converter::entry & e = mc.mk(model_converter::ELIM_VAR, v); + TRACE("save_elim", tout << "marking as deleted: " << v << " l: " << l << " r: " << r << "\n";); + m_solver.m_eliminated[v] = true; + mc.insert(e, ~l, r); + mc.insert(e, l, ~r); + } + } + } + + bool elim_eqs::check_clauses(literal_vector const & roots) const { + clause_vector * vs[2] = { &m_solver.m_clauses, &m_solver.m_learned }; + for (unsigned i = 0; i < 2; i++) { + clause_vector & cs = *(vs[i]); + clause_vector::iterator it = cs.begin(); + clause_vector::iterator end = cs.end(); + for (; it != end; ++it) { + clause & c = *(*it); + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) { + CTRACE("elim_eqs_bug", m_solver.was_eliminated(c[i].var()), tout << "lit: " << c[i] << " " << norm(roots, c[i]) << "\n"; + tout << c << "\n";); + SASSERT(!m_solver.was_eliminated(c[i].var())); + } + } + } + return true; + } + + void elim_eqs::operator()(literal_vector const & roots, bool_var_vector const & to_elim) { + cleanup_bin_watches(roots); + TRACE("elim_eqs", tout << "after bin cleanup\n"; m_solver.display(tout);); + cleanup_clauses(roots, m_solver.m_clauses); + if (m_solver.inconsistent()) return; + cleanup_clauses(roots, m_solver.m_learned); + if (m_solver.inconsistent()) return; + save_elim(roots, to_elim); + m_solver.propagate(false); + SASSERT(check_clauses(roots)); + } +}; diff --git a/lib/sat_elim_eqs.h b/lib/sat_elim_eqs.h new file mode 100644 index 000000000..21a653383 --- /dev/null +++ b/lib/sat_elim_eqs.h @@ -0,0 +1,40 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_elim_eqs.h + +Abstract: + + Helper class for eliminating eqs. + +Author: + + Leonardo de Moura (leonardo) 2011-05-27. + +Revision History: + +--*/ +#ifndef _SAT_ELIM_EQS_H_ +#define _SAT_ELIM_EQS_H_ + +#include"sat_types.h" + +namespace sat { + class solver; + + class elim_eqs { + solver & m_solver; + void save_elim(literal_vector const & roots, bool_var_vector const & to_elim); + void cleanup_clauses(literal_vector const & roots, clause_vector & cs); + void cleanup_bin_watches(literal_vector const & roots); + bool check_clauses(literal_vector const & roots) const; + public: + elim_eqs(solver & s); + void operator()(literal_vector const & roots, bool_var_vector const & to_elim); + }; + +}; + +#endif diff --git a/lib/sat_extension.h b/lib/sat_extension.h new file mode 100644 index 000000000..a2b43675b --- /dev/null +++ b/lib/sat_extension.h @@ -0,0 +1,46 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_extension.h + +Abstract: + + An abstract class for SAT extensions. + +Author: + + Leonardo de Moura (leonardo) 2011-05-21. + +Revision History: + +--*/ +#ifndef _SAT_EXTENSION_H_ +#define _SAT_EXTENSION_H_ + +#include"sat_types.h" +#include"params.h" + +namespace sat { + + enum check_result { + CR_DONE, CR_CONTINUE, CR_GIVEUP + }; + + class extension { + public: + virtual void propagate(literal l, ext_constraint_idx idx, bool & keep) = 0; + virtual void get_antecedents(literal l, ext_justification_idx idx, literal_vector & r) = 0; + virtual void asserted(literal l) = 0; + virtual check_result check() = 0; + virtual void push() = 0; + virtual void pop(unsigned n) = 0; + virtual void simplify() = 0; + virtual void clauses_modifed() = 0; + virtual lbool get_phase(bool_var v) = 0; + }; + +}; + +#endif diff --git a/lib/sat_iff3_finder.cpp b/lib/sat_iff3_finder.cpp new file mode 100644 index 000000000..789f5dec0 --- /dev/null +++ b/lib/sat_iff3_finder.cpp @@ -0,0 +1,214 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_iff3_finder.cpp + +Abstract: + + Find constraints of the form x = l1 = l2 + That is, search for clauses of the form + ~x \/ l1 \/ ~l2 + ~x \/ ~l1 \/ l2 + x \/ l1 \/ l2 + x \/ ~l1 \/ ~l2 + + The basic idea is to sort the watch lists. + + This information can be used to propagate equivalences + during probing (and search). + + The initial experiments were disappointing. + Not using it on the solver. + +Author: + + Leonardo de Moura (leonardo) 2011-06-04. + +Revision History: + +--*/ +#include"sat_iff3_finder.h" +#include"sat_solver.h" + +namespace sat { + + struct iff3_lt { + bool operator()(watched const & w1, watched const & w2) const { + // keep th binary clauses in the beginning + if (w2.is_binary_clause()) return false; + if (w1.is_binary_clause()) return true; + // + if (w2.is_ternary_clause()) { + if (w1.is_ternary_clause()) { + literal l1_1 = w1.get_literal1(); + literal l1_2 = w1.get_literal2(); + literal l2_1 = w2.get_literal1(); + literal l2_2 = w2.get_literal2(); + if (l1_1.index() < l2_1.index()) return true; + if (l1_1.index() > l2_1.index()) return false; + return l1_2.index() < l2_2.index(); + } + return false; + } + if (w1.is_ternary_clause()) return true; + return false; + } + }; + + static void unmark(svector & marks, literal_vector & to_unmark) { + literal_vector::const_iterator it = to_unmark.begin(); + literal_vector::const_iterator end = to_unmark.end(); + for (; it != end; ++it) { + marks[it->index()] = false; + } + to_unmark.reset(); + } + +#define SMALL_WLIST 16 + + /** + \brief Return true if wlist contains (l1, l2) + It assumes wlist have been sorted using iff3_lt + */ + static bool contains(watch_list const & wlist, literal l1, literal l2) { + watched k(l1, l2); + if (wlist.size() < SMALL_WLIST) + return wlist.contains(k); + iff3_lt lt; + int low = 0; + int high = wlist.size(); + while (true) { + int mid = low + ((high - low) / 2); + watched const & m = wlist[mid]; + if (m == k) + return true; + if (lt(m, k)) { + low = mid + 1; + } + else { + SASSERT(lt(k, m)); + high = mid - 1; + } + if (low > high) + return false; + } + } + + iff3_finder::iff3_finder(solver & _s): + s(_s) { + } + + void iff3_finder::sort_watches() { + vector::iterator it = s.m_watches.begin(); + vector::iterator end = s.m_watches.end(); + for (; it != end; ++it) { + watch_list & wlist = *it; + std::stable_sort(wlist.begin(), wlist.end(), iff3_lt()); + } + } + + void iff3_finder::mk_eq(literal l1, literal l2) { + s.mk_clause(l1, ~l2); + s.mk_clause(~l1, l2); + } + + void iff3_finder::operator()() { + TRACE("iff3_finder", tout << "starting iff3_finder\n";); + sort_watches(); + + unsigned counter = 0; + + svector found; + found.resize(s.num_vars()*2, false); + literal_vector to_unmark; + + typedef std::pair lit_pair; + svector pairs; + + for (bool_var x = 0; x < s.num_vars(); x++) { + literal pos_x(x, false); + literal neg_x(x, true); + watch_list & pos_wlist = s.get_wlist(neg_x); + watch_list & neg_wlist = s.get_wlist(pos_x); + // + TRACE("iff3_finder", + tout << "visiting: " << x << "\n"; + tout << "pos:\n"; + display(tout, s.m_cls_allocator, pos_wlist); + tout << "\nneg:\n"; + display(tout, s.m_cls_allocator, neg_wlist); + tout << "\n--------------\n";); + // traverse the ternary clauses x \/ l1 \/ l2 + bool_var curr_v1 = null_bool_var; + watch_list::iterator it = pos_wlist.begin(); + watch_list::iterator end = pos_wlist.end(); + for (; it != end; ++it) { + if (it->is_binary_clause()) + continue; + if (it->is_ternary_clause()) { + literal l1 = it->get_literal1(); + if (l1.index() < pos_x.index()) + break; // stop + literal l2 = it->get_literal2(); + bool_var v1 = l1.var(); + if (v1 != curr_v1) { + curr_v1 = v1; + unmark(found, to_unmark); + pairs.reset(); + } + if (!l1.sign()) { + if (!found[l2.index()]) { + found[l2.index()] = true; + to_unmark.push_back(l2); + } + } + else { + l2.neg(); + if (found[l2.index()]) { + // Found clauses x \/ v1 \/ l2 and x \/ ~v1 \/ ~l2 + // So, I have to find the clauses + // ~x \/ v1 \/ ~l2 + // ~x \/ ~v1 \/ l2 + if (contains(neg_wlist, literal(v1, false), ~l2) && + contains(neg_wlist, literal(v1, true), l2)) { + // found new iff3 + // x = v1 = l2 + counter++; + // verbose_stream() << counter << ": " << x << " = " << v1 << " = " << l2 << "\n"; + TRACE("iff3_finder", tout << counter << ": " << x << " = " << v1 << " = " << l2 << "\n";); + l1.neg(); + svector::iterator it2 = pairs.begin(); + svector::iterator end2 = pairs.end(); + for (; it2 != end2; ++it2) { + if (it2->first == l1) { + // l2 == it2->second + mk_eq(l2, it2->second); + } + else if (it2->second == l1) { + // l2 == it2->first + mk_eq(l2, it2->first); + } + else if (it2->first == l2) { + // l1 == it2->second + mk_eq(l1, it2->second); + } + else if (it2->second == l2) { + // l1 == it2->first + mk_eq(l1, it2->first); + } + } + pairs.push_back(lit_pair(l1, l2)); + } + } + } + } + else { + break; // stop, no more ternary clauses from this point + } + } + } + } + +}; diff --git a/lib/sat_iff3_finder.h b/lib/sat_iff3_finder.h new file mode 100644 index 000000000..6522375bb --- /dev/null +++ b/lib/sat_iff3_finder.h @@ -0,0 +1,48 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_iff3_finder.h + +Abstract: + + Find constraints of the form x = l1 = l2 + That is, search for clauses of the form + ~x \/ l1 \/ ~l2 + ~x \/ ~l1 \/ l2 + x \/ l1 \/ l2 + x \/ ~l1 \/ ~l2 + + The basic idea is to sort the watch lists. + + This information can be used to propagate equivalences + during probing (and search). + +Author: + + Leonardo de Moura (leonardo) 2011-06-04. + +Revision History: + +--*/ +#ifndef _SAT_IFF3_FINDER_H_ +#define _SAT_IFF3_FINDER_H_ + +#include"sat_types.h" + +namespace sat { + + class iff3_finder { + solver & s; + void sort_watches(); + void mk_eq(literal l1, literal l2); + public: + iff3_finder(solver & s); + + void operator()(); + }; + +}; + +#endif diff --git a/lib/sat_integrity_checker.cpp b/lib/sat_integrity_checker.cpp new file mode 100644 index 000000000..b5d9f1550 --- /dev/null +++ b/lib/sat_integrity_checker.cpp @@ -0,0 +1,221 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_integrity_checker.cpp + +Abstract: + + Checker whether the SAT solver internal datastructures + are consistent or not. + +Author: + + Leonardo de Moura (leonardo) 2011-05-21. + +Revision History: + +--*/ +#include"sat_integrity_checker.h" +#include"sat_solver.h" +#include"trace.h" + +namespace sat { + + integrity_checker::integrity_checker(solver const & _s): + s(_s) { + } + + // for ternary clauses + static bool contains_watched(watch_list const & wlist, literal l1, literal l2) { + return wlist.contains(watched(l1, l2)); + } + + // for nary clauses + static bool contains_watched(watch_list const & wlist, clause const & c, clause_offset cls_off) { + watch_list::const_iterator it = wlist.begin(); + watch_list::const_iterator end = wlist.end(); + for (; it != end; ++it) { + if (it->is_clause()) { + if (it->get_clause_offset() == cls_off) { + // the blocked literal must be in the clause. + literal l = it->get_blocked_literal(); + SASSERT(c.contains(l)); + return true; + } + } + } + UNREACHABLE(); + return false; + } + + bool integrity_checker::check_clause(clause const & c) const { + SASSERT(!c.was_removed()); + for (unsigned i = 0; i < c.size(); i++) { + SASSERT(c[i].var() <= s.num_vars()); + CTRACE("sat_bug", s.was_eliminated(c[i].var()), + tout << "l: " << c[i].var() << "\n"; + tout << "c: " << c << "\n"; + s.display(tout);); + SASSERT(!s.was_eliminated(c[i].var())); + } + + SASSERT(c.check_approx()); + + if (c.frozen()) + return true; + + if (c.size() == 3) { + CTRACE("sat_ter_watch_bug", !contains_watched(s.get_wlist(~c[0]), c[1], c[2]), tout << c << "\n"; + tout << "watch_list:\n"; + sat::display(tout, s.m_cls_allocator, s.get_wlist(~c[0])); + tout << "\n";); + SASSERT(contains_watched(s.get_wlist(~c[0]), c[1], c[2])); + SASSERT(contains_watched(s.get_wlist(~c[1]), c[0], c[2])); + SASSERT(contains_watched(s.get_wlist(~c[2]), c[0], c[1])); + } + else { + if (s.value(c[0]) == l_false || s.value(c[1]) == l_false) { + bool on_prop_stack = false; + for (unsigned i = s.m_qhead; i < s.m_trail.size(); i++) { + if (s.m_trail[i].var() == c[0].var() || + s.m_trail[i].var() == c[1].var()) { + on_prop_stack = true; + break; + } + } + // the clause has been satisfied or all other literals are assigned to false. + if (!on_prop_stack && s.status(c) != l_true) { + for (unsigned i = 2; i < c.size(); i++) { + CTRACE("sat_bug", s.value(c[i]) != l_false, + tout << c << " status: " << s.status(c) << "\n"; + for (unsigned i = 0; i < c.size(); i++) tout << "val(" << i << "): " << s.value(c[i]) << "\n";); + SASSERT(s.value(c[i]) == l_false); + } + } + } + + // the first two literals must be watched. + SASSERT(contains_watched(s.get_wlist(~c[0]), c, s.get_offset(c))); + SASSERT(contains_watched(s.get_wlist(~c[1]), c, s.get_offset(c))); + } + return true; + } + + bool integrity_checker::check_clauses(clause * const * begin, clause * const * end) const { + for (clause * const * it = begin; it != end; ++it) { + SASSERT(check_clause(*(*it))); + } + return true; + } + + bool integrity_checker::check_clauses() const { + return check_clauses(s.begin_clauses(), s.end_clauses()); + } + + bool integrity_checker::check_learned_clauses() const { + unsigned num_frozen = 0; + clause * const * end = s.end_clauses(); + for (clause * const * it = s.begin_clauses(); it != end; ++it) { + clause & c = *(*it); + if (c.frozen()) + num_frozen++; + } + SASSERT(num_frozen == s.m_num_frozen); + return check_clauses(s.begin_learned(), s.end_learned()); + } + + bool integrity_checker::check_assignment() const { + return true; + } + + bool integrity_checker::check_bool_vars() const { + SASSERT(s.m_watches.size() == s.num_vars() * 2); + SASSERT(s.m_assignment.size() == s.num_vars() * 2); + SASSERT(s.m_lit_mark.size() == s.num_vars() * 2); + SASSERT(s.m_justification.size() == s.num_vars()); + SASSERT(s.m_decision.size() == s.num_vars()); + SASSERT(s.m_eliminated.size() == s.num_vars()); + SASSERT(s.m_external.size() == s.num_vars()); + SASSERT(s.m_level.size() == s.num_vars()); + SASSERT(s.m_mark.size() == s.num_vars()); + SASSERT(s.m_activity.size() == s.num_vars()); + SASSERT(s.m_phase.size() == s.num_vars()); + SASSERT(s.m_prev_phase.size() == s.num_vars()); + SASSERT(s.m_assigned_since_gc.size() == s.num_vars()); + for (bool_var v = 0; v < s.num_vars(); v++) { + if (s.was_eliminated(v)) { + SASSERT(s.get_wlist(literal(v, false)).empty()); + SASSERT(s.get_wlist(literal(v, true)).empty()); + } + } + return true; + } + + bool integrity_checker::check_watches() const { + vector::const_iterator it = s.m_watches.begin(); + vector::const_iterator end = s.m_watches.end(); + for (unsigned l_idx = 0; it != end; ++it, ++l_idx) { + literal l = ~to_literal(l_idx); + watch_list const & wlist = *it; + CTRACE("sat_bug", s.was_eliminated(l.var()) && !wlist.empty(), + tout << "l: " << l << "\n"; + s.display_watches(tout); + s.display(tout);); + SASSERT(!s.was_eliminated(l.var()) || wlist.empty()); + watch_list::const_iterator it2 = wlist.begin(); + watch_list::const_iterator end2 = wlist.end(); + for (; it2 != end2; ++it2) { + switch (it2->get_kind()) { + case watched::BINARY: + SASSERT(!s.was_eliminated(it2->get_literal().var())); + CTRACE("sat_watched_bug", !s.get_wlist(~(it2->get_literal())).contains(watched(l, it2->is_learned())), + tout << "l: " << l << " l2: " << it2->get_literal() << "\n"; + tout << "was_eliminated1: " << s.was_eliminated(l.var()); + tout << " was_eliminated2: " << s.was_eliminated(it2->get_literal().var()); + tout << " learned: " << it2->is_learned() << "\n"; + sat::display(tout, s.m_cls_allocator, wlist); + tout << "\n"; + sat::display(tout, s.m_cls_allocator, s.get_wlist(~(it2->get_literal()))); + tout << "\n";); + SASSERT(s.get_wlist(~(it2->get_literal())).contains(watched(l, it2->is_learned()))); + break; + case watched::TERNARY: + SASSERT(!s.was_eliminated(it2->get_literal1().var())); + SASSERT(!s.was_eliminated(it2->get_literal2().var())); + SASSERT(it2->get_literal1().index() < it2->get_literal2().index()); + break; + case watched::CLAUSE: + SASSERT(!s.m_cls_allocator.get_clause(it2->get_clause_offset())->was_removed()); + break; + default: + break; + } + } + } + return true; + } + + bool integrity_checker::check_reinit_stack() const { + clause_wrapper_vector::const_iterator it = s.m_clauses_to_reinit.begin(); + clause_wrapper_vector::const_iterator end = s.m_clauses_to_reinit.end(); + for (; it != end; ++it) { + if (it->is_binary()) + continue; + SASSERT(it->get_clause()->on_reinit_stack()); + } + return true; + } + + bool integrity_checker::operator()() const { + if (s.inconsistent()) + return true; + SASSERT(check_clauses()); + SASSERT(check_learned_clauses()); + SASSERT(check_watches()); + SASSERT(check_bool_vars()); + SASSERT(check_reinit_stack()); + return true; + } +}; diff --git a/lib/sat_integrity_checker.h b/lib/sat_integrity_checker.h new file mode 100644 index 000000000..fc3da8c5d --- /dev/null +++ b/lib/sat_integrity_checker.h @@ -0,0 +1,42 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_integrity_checker.h + +Abstract: + + Checker whether the SAT solver internal datastructures + are consistent or not. + +Author: + + Leonardo de Moura (leonardo) 2011-05-21. + +Revision History: + +--*/ +#ifndef _SAT_INTEGRITY_CHECKER_H_ +#define _SAT_INTEGRITY_CHECKER_H_ + +#include"sat_types.h" + +namespace sat { + class integrity_checker { + solver const & s; + public: + integrity_checker(solver const & s); + + bool check_clause(clause const & c) const; + bool check_clauses(clause * const * begin, clause * const * end) const; + bool check_clauses() const; + bool check_learned_clauses() const; + bool check_assignment() const; + bool check_bool_vars() const; + bool check_watches() const; + bool check_reinit_stack() const; + bool operator()() const; + }; +}; +#endif diff --git a/lib/sat_justification.h b/lib/sat_justification.h new file mode 100644 index 000000000..dd803feeb --- /dev/null +++ b/lib/sat_justification.h @@ -0,0 +1,57 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_justification.h + +Abstract: + + Justifications for SAT assignments + +Author: + + Leonardo de Moura (leonardo) 2011-05-21. + +Revision History: + +--*/ +#ifndef _SAT_JUSTIFICATIONS_H_ +#define _SAT_JUSTIFICATIONS_H_ + +namespace sat { + + class justification { + public: + enum kind { NONE, BINARY, TERNARY, CLAUSE, EXT_JUSTIFICATION }; + private: + unsigned m_val1; + unsigned m_val2; + justification(ext_justification_idx idx, kind k):m_val1(idx), m_val2(k) {} + public: + justification():m_val1(0), m_val2(NONE) {} + justification(literal l):m_val1(l.to_uint()), m_val2(BINARY) {} + justification(literal l1, literal l2):m_val1(l1.to_uint()), m_val2(TERNARY + (l2.to_uint() << 3)) {} + justification(clause_offset cls_off):m_val1(cls_off), m_val2(CLAUSE) {} + justification mk_ext_justification(ext_justification_idx idx) { return justification(idx, EXT_JUSTIFICATION); } + + kind get_kind() const { return static_cast(m_val2 & 7); } + + bool is_none() const { return m_val2 == NONE; } + + bool is_binary_clause() const { return m_val2 == BINARY; } + literal get_literal() const { SASSERT(is_binary_clause()); return to_literal(m_val1); } + + bool is_ternary_clause() const { return get_kind() == TERNARY; } + literal get_literal1() const { SASSERT(is_ternary_clause()); return to_literal(m_val1); } + literal get_literal2() const { SASSERT(is_ternary_clause()); return to_literal(m_val2 >> 3); } + + bool is_clause() const { return m_val2 == CLAUSE; } + clause_offset get_clause_offset() const { return m_val1; } + + bool is_ext_justification() const { return m_val2 == EXT_JUSTIFICATION; } + ext_justification_idx get_ext_justification_idx() const { return m_val1; } + }; +}; + +#endif diff --git a/lib/sat_model_converter.cpp b/lib/sat_model_converter.cpp new file mode 100644 index 000000000..3a39af31b --- /dev/null +++ b/lib/sat_model_converter.cpp @@ -0,0 +1,240 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_model_converter.cpp + +Abstract: + + Low level model converter for SAT solver. + +Author: + + Leonardo de Moura (leonardo) 2011-05-26. + +Revision History: + +--*/ +#include"sat_model_converter.h" +#include"sat_clause.h" +#include"trace.h" + +namespace sat { + + model_converter::model_converter() { + } + + model_converter::~model_converter() { + reset(); + } + + void model_converter::reset() { + m_entries.finalize(); + } + + void model_converter::operator()(model & m) const { + vector::const_iterator begin = m_entries.begin(); + vector::const_iterator it = m_entries.end(); + while (it != begin) { + --it; + SASSERT(it->get_kind() != ELIM_VAR || m[it->var()] == l_undef); + // if it->get_kind() == BLOCK_LIT, then it might be the case that m[it->var()] != l_undef, + // and the following procedure flips its value. + bool sat = false; + bool var_sign; + literal_vector::const_iterator it2 = it->m_clauses.begin(); + literal_vector::const_iterator end2 = it->m_clauses.end(); + for (; it2 != end2; ++it2) { + literal l = *it2; + if (l == null_literal) { + // end of clause + if (!sat) { + m[it->var()] = var_sign ? l_false : l_true; + break; + } + sat = false; + continue; + } + if (sat) + continue; + bool sign = l.sign(); + bool_var v = l.var(); + if (v == it->var()) + var_sign = sign; + if (value_at(l, m) == l_true) + sat = true; + else if (!sat && v != it->var() && m[v] == l_undef) { + // clause can be satisfied by assigning v. + m[v] = sign ? l_false : l_true; + sat = true; + } + } + DEBUG_CODE({ + // all clauses must be satisfied + bool sat = false; + it2 = it->m_clauses.begin(); + for (; it2 != end2; ++it2) { + literal l = *it2; + if (l == null_literal) { + SASSERT(sat); + sat = false; + continue; + } + if (sat) + continue; + if (value_at(l, m) == l_true) + sat = true; + } + }); + } + } + + /** + \brief Test if after applying the model converter, all eliminated clauses are + satisfied by m. + */ + bool model_converter::check_model(model const & m) const { + bool ok = true; + vector::const_iterator begin = m_entries.begin(); + vector::const_iterator it = m_entries.end(); + while (it != begin) { + --it; + bool sat = false; + literal_vector::const_iterator it2 = it->m_clauses.begin(); + literal_vector::const_iterator itbegin = it2; + literal_vector::const_iterator end2 = it->m_clauses.end(); + for (; it2 != end2; ++it2) { + literal l = *it2; + if (l == null_literal) { + // end of clause + if (!sat) { + TRACE("sat_model_bug", tout << "failed eliminated: " << mk_lits_pp(static_cast(it2 - itbegin), itbegin) << "\n";); + ok = false; + } + sat = false; + itbegin = it2; + itbegin++; + continue; + } + if (sat) + continue; + if (value_at(l, m) == l_true) + sat = true; + } + } + return ok; + } + + model_converter::entry & model_converter::mk(kind k, bool_var v) { + m_entries.push_back(entry(k, v)); + entry & e = m_entries.back(); + SASSERT(e.var() == v); + SASSERT(e.get_kind() == k); + return e; + } + + void model_converter::insert(entry & e, clause const & c) { + SASSERT(c.contains(e.var())); + SASSERT(m_entries.begin() <= &e); + SASSERT(&e < m_entries.end()); + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) { + e.m_clauses.push_back(c[i]); + } + e.m_clauses.push_back(null_literal); + TRACE("sat_mc_bug", tout << "adding: " << c << "\n";); + } + + void model_converter::insert(entry & e, literal l1, literal l2) { + SASSERT(l1.var() == e.var() || l2.var() == e.var()); + SASSERT(m_entries.begin() <= &e); + SASSERT(&e < m_entries.end()); + e.m_clauses.push_back(l1); + e.m_clauses.push_back(l2); + e.m_clauses.push_back(null_literal); + TRACE("sat_mc_bug", tout << "adding (binary): " << l1 << " " << l2 << "\n";); + } + + void model_converter::insert(entry & e, clause_wrapper const & c) { + SASSERT(c.contains(e.var())); + SASSERT(m_entries.begin() <= &e); + SASSERT(&e < m_entries.end()); + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) + e.m_clauses.push_back(c[i]); + e.m_clauses.push_back(null_literal); + TRACE("sat_mc_bug", tout << "adding (wrapper): "; for (unsigned i = 0; i < c.size(); i++) tout << c[i] << " "; tout << "\n";); + } + + bool model_converter::check_invariant(unsigned num_vars) const { + // After a variable v occurs in an entry n and the entry has kind ELIM_VAR, + // then the variable must not occur in any other entry occurring after it. + vector::const_iterator it = m_entries.begin(); + vector::const_iterator end = m_entries.end(); + for (; it != end; ++it) { + SASSERT(it->var() < num_vars); + if (it->get_kind() == ELIM_VAR) { + svector::const_iterator it2 = it; + it2++; + for (; it2 != end; ++it2) { + SASSERT(it2->var() != it->var()); + literal_vector::const_iterator it3 = it2->m_clauses.begin(); + literal_vector::const_iterator end3 = it2->m_clauses.end(); + for (; it3 != end3; ++it3) { + CTRACE("sat_model_converter", it3->var() == it->var(), tout << "var: " << it->var() << "\n"; display(tout);); + SASSERT(it3->var() != it->var()); + SASSERT(*it3 == null_literal || it3->var() < num_vars); + } + } + } + } + return true; + } + + void model_converter::display(std::ostream & out) const { + out << "(sat::model-converter"; + vector::const_iterator it = m_entries.begin(); + vector::const_iterator end = m_entries.end(); + for (; it != end; ++it) { + out << "\n (" << (it->get_kind() == ELIM_VAR ? "elim" : "blocked") << " " << it->var(); + bool start = true; + literal_vector::const_iterator it2 = it->m_clauses.begin(); + literal_vector::const_iterator end2 = it->m_clauses.end(); + for (; it2 != end2; ++it2) { + if (start) { + out << "\n ("; + start = false; + } + else { + if (*it2 != null_literal) + out << " "; + } + if (*it2 == null_literal) { + out << ")"; + start = true; + continue; + } + out << *it2; + } + out << ")"; + } + out << ")\n"; + } + + void model_converter::copy(model_converter const & src) { + vector::const_iterator it = src.m_entries.begin(); + vector::const_iterator end = src.m_entries.end(); + for (; it != end; ++it) + m_entries.push_back(*it); + } + + void model_converter::collect_vars(bool_var_set & s) const { + vector::const_iterator it = m_entries.begin(); + vector::const_iterator end = m_entries.end(); + for (; it != end; ++it) { + s.insert(it->m_var); + } + } + +}; diff --git a/lib/sat_model_converter.h b/lib/sat_model_converter.h new file mode 100644 index 000000000..f2c35e364 --- /dev/null +++ b/lib/sat_model_converter.h @@ -0,0 +1,81 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_model_converter.h + +Abstract: + + Low level model converter for SAT solver. + +Author: + + Leonardo de Moura (leonardo) 2011-05-26. + +Revision History: + +--*/ +#ifndef _SAT_MODEL_CONVERTER_H_ +#define _SAT_MODEL_CONVERTER_H_ + +#include"sat_types.h" + +namespace sat { + /** + \brief Stores eliminated variables and Blocked clauses. + It uses these clauses to extend/patch the model produced for the + simplified CNF formula. + + This information may also be used to support incremental solving. + If new clauses are asserted into the SAT engine, then we can + restore the state by re-asserting all clauses in the model + converter. + + This is a low-level model_converter. Given a mapping from bool_var to expr, + it can be converted into a general Z3 model_converter + */ + class model_converter { + public: + enum kind { ELIM_VAR = 0, BLOCK_LIT }; + class entry { + friend class model_converter; + unsigned m_var:31; + unsigned m_kind:1; + literal_vector m_clauses; // the different clauses are separated by null_literal + entry(kind k, bool_var v):m_var(v), m_kind(k) {} + public: + entry(entry const & src): + m_var(src.m_var), + m_kind(src.m_kind), + m_clauses(src.m_clauses) { + } + bool_var var() const { return m_var; } + kind get_kind() const { return static_cast(m_kind); } + }; + private: + vector m_entries; + public: + model_converter(); + ~model_converter(); + void operator()(model & m) const; + + entry & mk(kind k, bool_var v); + void insert(entry & e, clause const & c); + void insert(entry & e, literal l1, literal l2); + void insert(entry & e, clause_wrapper const & c); + + bool empty() const { return m_entries.empty(); } + + void reset(); + bool check_invariant(unsigned num_vars) const; + void display(std::ostream & out) const; + bool check_model(model const & m) const; + + void copy(model_converter const & src); + void collect_vars(bool_var_set & s) const; + }; + +}; + +#endif diff --git a/lib/sat_probing.cpp b/lib/sat_probing.cpp new file mode 100644 index 000000000..f51b16033 --- /dev/null +++ b/lib/sat_probing.cpp @@ -0,0 +1,269 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_probing.cpp + +Abstract: + + Probing (aka failed literal detection). + + +Author: + + Leonardo de Moura (leonardo) 2011-06-04. + +Revision History: + +--*/ +#include"sat_probing.h" +#include"sat_solver.h" + +namespace sat { + probing::probing(solver & _s, params_ref const & p): + s(_s) { + updt_params(p); + reset_statistics(); + m_stopped_at = 0; + m_counter = 0; + } + + // reset the cache for the given literal + void probing::reset_cache(literal l) { + if (l.index() < m_cached_bins.size()) { + m_cached_bins[l.index()].m_available = false; + m_cached_bins[l.index()].m_lits.finalize(); + } + } + + // l implied the literals on the trail stack starting at position old_tr_sz + // Thus, ~l \/ l2 is a binary clause for every l2 on this fragment of the trail stack. + void probing::cache_bins(literal l, unsigned old_tr_sz) { + if (!m_probing_cache) + return; + if (memory::get_allocation_size() > m_probing_cache_limit) + return; // not enough memory to spare + m_cached_bins.reserve(l.index() + 1); + cache_entry & entry = m_cached_bins[l.index()]; + entry.m_available = true; + entry.m_lits.reset(); + unsigned tr_sz = s.m_trail.size(); + for (unsigned i = old_tr_sz; i < tr_sz; i++) { + entry.m_lits.push_back(s.m_trail[i]); + } + } + + // Return true if should keep going. + // It will assert literals implied by l that are already marked + // as assigned. + bool probing::try_lit(literal l, bool updt_cache) { + SASSERT(s.m_qhead == s.m_trail.size()); + SASSERT(s.value(l.var()) == l_undef); + literal_vector * implied_lits = updt_cache ? 0 : cached_implied_lits(l); + if (implied_lits) { + literal_vector::iterator it = implied_lits->begin(); + literal_vector::iterator end = implied_lits->end(); + for (; it != end; ++it) { + if (m_assigned.contains(*it)) { + s.assign(*it, justification()); + m_num_assigned++; + } + } + } + else { + m_to_assert.reset(); + s.push(); + s.assign(l, justification()); + m_counter--; + unsigned old_tr_sz = s.m_trail.size(); + s.propagate(false); + if (s.inconsistent()) { + // ~l must be true + s.pop(1); + s.assign(~l, justification()); + s.propagate(false); + return false; + } + // collect literals that were assigned after assigning l + unsigned tr_sz = s.m_trail.size(); + for (unsigned i = old_tr_sz; i < tr_sz; i++) { + if (m_assigned.contains(s.m_trail[i])) { + m_to_assert.push_back(s.m_trail[i]); + } + } + if (updt_cache) + cache_bins(l, old_tr_sz); + s.pop(1); + + literal_vector::iterator it = m_to_assert.begin(); + literal_vector::iterator end = m_to_assert.end(); + for (; it != end; ++it) { + s.assign(*it, justification()); + m_num_assigned++; + } + } + s.propagate(false); + return !s.inconsistent(); + } + + void probing::process_core(bool_var v) { + TRACE("probing", tout << "processing: " << v << " counter: " << -m_counter << "\n";); + SASSERT(s.m_qhead == s.m_trail.size()); + SASSERT(s.value(v) == l_undef); + m_counter--; + s.push(); + literal l(v, false); + s.assign(l, justification()); + unsigned old_tr_sz = s.m_trail.size(); + s.propagate(false); + if (s.inconsistent()) { + // ~l must be true + s.pop(1); + s.assign(~l, justification()); + s.propagate(false); + m_num_assigned++; + return; + } + // collect literals that were assigned after assigning l + m_assigned.reset(); + unsigned tr_sz = s.m_trail.size(); + for (unsigned i = old_tr_sz; i < tr_sz; i++) { + m_assigned.insert(s.m_trail[i]); + } + cache_bins(l, old_tr_sz); + s.pop(1); + + if (!try_lit(~l, true)) + return; + + if (m_probing_binary) { + watch_list & wlist = s.get_wlist(~l); + watch_list::iterator it = wlist.begin(); + watch_list::iterator end = wlist.end(); + for (; it != end ; ++it) { + if (!it->is_binary_clause()) + break; + literal l2 = it->get_literal(); + if (l.index() > l2.index()) + continue; + if (s.value(l2) != l_undef) + continue; + // verbose_stream() << "probing " << l << " " << l2 << " " << m_counter << "\n"; + if (!try_lit(l2, false)) + return; + if (s.inconsistent()) + return; + } + } + } + + void probing::process(bool_var v) { + int old_counter = m_counter; + unsigned old_num_assigned = m_num_assigned; + process_core(v); + if (m_num_assigned > old_num_assigned) { + // if new variables were assigned when probing x, + // then assume the cost is 0. + m_counter = old_counter; + } + } + + struct probing::report { + probing & m_probing; + stopwatch m_watch; + unsigned m_num_assigned; + report(probing & p): + m_probing(p), + m_num_assigned(p.m_num_assigned) { + m_watch.start(); + } + + ~report() { + m_watch.stop(); + IF_VERBOSE(SAT_VB_LVL, + verbose_stream() << " (sat-probing :probing-assigned " + << (m_probing.m_num_assigned - m_num_assigned) + << " :cost " << m_probing.m_counter; + if (m_probing.m_stopped_at != 0) verbose_stream() << " :stopped-at " << m_probing.m_stopped_at; + verbose_stream() << mem_stat() << " :time " << std::fixed << std::setprecision(2) << m_watch.get_seconds() << ")\n";); + } + }; + + bool probing::operator()(bool force) { + if (!m_probing) + return true; + s.propagate(false); // make sure propagation queue is empty + if (s.inconsistent()) + return true; + CASSERT("probing", s.check_invariant()); + if (!force && m_counter > 0) + return true; + + if (m_probing_cache && memory::get_allocation_size() > m_probing_cache_limit) + m_cached_bins.finalize(); + + report rpt(*this); + bool r = true; + m_counter = 0; + int limit = -static_cast(m_probing_limit); + unsigned i; + unsigned num = s.num_vars(); + for (i = 0; i < num; i++) { + bool_var v = (m_stopped_at + i) % num; + if (m_counter < limit) { + m_stopped_at = v; + r = false; + break; + } + if (s.inconsistent()) { + break; + } + if (s.value(v) != l_undef || s.was_eliminated(v)) { + if (m_probing_cache) { + // cache for v literals is not needed anymore. + reset_cache(literal(v, false)); + reset_cache(literal(v, true)); + } + continue; + } + s.checkpoint(); + process(v); + } + if (r) + m_stopped_at = 0; + m_counter = -m_counter; + if (rpt.m_num_assigned == m_num_assigned) { + // penalize + m_counter *= 2; + } + CASSERT("probing", s.check_invariant()); + free_memory(); + return r; + } + + void probing::updt_params(params_ref const & p) { + m_probing = p.get_bool(":probing", true); + m_probing_limit = p.get_uint(":probing-limit", 5000000); + m_probing_cache = p.get_bool(":probing-cache", true); + m_probing_binary = p.get_bool(":probing-binary", true); + m_probing_cache_limit = megabytes_to_bytes(p.get_uint(":probing-chache-limit", 1024)); + } + + void probing::collect_param_descrs(param_descrs & d) { + // TODO + } + + void probing::free_memory() { + m_assigned.cleanup(); + m_to_assert.finalize(); + } + + void probing::collect_statistics(statistics & st) { + st.update("probing assigned", m_num_assigned); + } + + void probing::reset_statistics() { + m_num_assigned = 0; + } +}; diff --git a/lib/sat_probing.h b/lib/sat_probing.h new file mode 100644 index 000000000..9f3c1ae87 --- /dev/null +++ b/lib/sat_probing.h @@ -0,0 +1,92 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_probing.h + +Abstract: + + Probing (aka failed literal detection). + + +Author: + + Leonardo de Moura (leonardo) 2011-06-04. + +Revision History: + +--*/ +#ifndef _SAT_PROBING_H_ +#define _SAT_PROBING_H_ + +#include"sat_types.h" +#include"params.h" +#include"statistics.h" + +namespace sat { + + class probing { + solver & s; + unsigned m_stopped_at; // where did it stop + literal_set m_assigned; // literals assigned in the first branch + literal_vector m_to_assert; + + // counters + int m_counter; // track cost + + // config + bool m_probing; // enabled/disabled + unsigned m_probing_limit; // max cost per round + bool m_probing_cache; // cache implicit binary clauses + bool m_probing_binary; // try l1 and l2 for binary clauses l1 \/ l2 + unsigned long long m_probing_cache_limit; // memory limit for enabling caching. + + // stats + unsigned m_num_assigned; + + struct cache_entry { + bool m_available; + literal_vector m_lits; + cache_entry():m_available(false) {} + }; + + vector m_cached_bins; + + struct report; + + void reset_cache(literal l); + void cache_bins(literal l, unsigned old_tr_sz); + bool try_lit(literal l, bool updt_cache); + void process(bool_var v); + void process_core(bool_var v); + + public: + probing(solver & s, params_ref const & p); + + bool operator()(bool force = false); + + void updt_params(params_ref const & p); + static void collect_param_descrs(param_descrs & d); + + void free_memory(); + + void collect_statistics(statistics & st); + void reset_statistics(); + + // return the literals implied by l. + // return 0, if the cache is not available + literal_vector * cached_implied_lits(literal l) { + if (!m_probing_cache) return 0; + if (l.index() >= m_cached_bins.size()) return 0; + cache_entry & e = m_cached_bins[l.index()]; + if (!e.m_available) return 0; + return &(e.m_lits); + } + + void dec(unsigned c) { m_counter -= c; } + }; + +}; + +#endif diff --git a/lib/sat_scc.cpp b/lib/sat_scc.cpp new file mode 100644 index 000000000..5e351290f --- /dev/null +++ b/lib/sat_scc.cpp @@ -0,0 +1,241 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_scc.cpp + +Abstract: + + Use binary clauses to compute strongly connected components. + +Author: + + Leonardo de Moura (leonardo) 2011-05-26. + +Revision History: + +--*/ +#include"sat_scc.h" +#include"sat_solver.h" +#include"sat_elim_eqs.h" +#include"stopwatch.h" +#include"trace.h" + +namespace sat { + + scc::scc(solver & s, params_ref const & p): + m_solver(s) { + reset_statistics(); + updt_params(p); + } + + struct frame { + unsigned m_lidx; + bool m_first; + watched * m_it; + watched * m_end; + frame(unsigned lidx, watched * it, watched * end):m_lidx(lidx), m_first(true), m_it(it), m_end(end) {} + }; + typedef svector frames; + + struct scc::report { + scc & m_scc; + stopwatch m_watch; + unsigned m_num_elim; + report(scc & c): + m_scc(c), + m_num_elim(c.m_num_elim) { + m_watch.start(); + } + ~report() { + m_watch.stop(); + IF_VERBOSE(SAT_VB_LVL, + verbose_stream() << " (sat-scc :elim-vars " << (m_scc.m_num_elim - m_num_elim) + << mk_stat(m_scc.m_solver) + << " :time " << std::fixed << std::setprecision(2) << m_watch.get_seconds() << ")\n";); + } + }; + + unsigned scc::operator()() { + if (m_solver.m_inconsistent) + return 0; + if (!m_scc) + return 0; + CASSERT("scc_bug", m_solver.check_invariant()); + report rpt(*this); + TRACE("scc", m_solver.display(tout);); + TRACE("scc_details", m_solver.display_watches(tout);); + unsigned_vector index; + unsigned_vector lowlink; + unsigned_vector s; + svector in_s; + unsigned num_lits = m_solver.num_vars() * 2; + index.resize(num_lits, UINT_MAX); + lowlink.resize(num_lits, UINT_MAX); + in_s.resize(num_lits, false); + literal_vector roots; + roots.resize(m_solver.num_vars(), null_literal); + unsigned next_index = 0; + svector frames; + bool_var_vector to_elim; + + for (unsigned l_idx = 0; l_idx < num_lits; l_idx++) { + if (index[l_idx] != UINT_MAX) + continue; + if (m_solver.was_eliminated(to_literal(l_idx).var())) + continue; + + m_solver.checkpoint(); + +#define NEW_NODE(LIDX) { \ + index[LIDX] = next_index; \ + lowlink[LIDX] = next_index; \ + next_index++; \ + s.push_back(LIDX); \ + in_s[LIDX] = true; \ + watch_list & wlist = m_solver.get_wlist(LIDX); \ + frames.push_back(frame(LIDX, wlist.begin(), wlist.end())); \ + } + + NEW_NODE(l_idx); + + while (!frames.empty()) { + loop: + frame & fr = frames.back(); + unsigned l_idx = fr.m_lidx; + if (!fr.m_first) { + // after visiting child + literal l2 = fr.m_it->get_literal(); + unsigned l2_idx = l2.index(); + SASSERT(index[l2_idx] != UINT_MAX); + if (lowlink[l2_idx] < lowlink[l_idx]) + lowlink[l_idx] = lowlink[l2_idx]; + fr.m_it++; + } + fr.m_first = false; + while (fr.m_it != fr.m_end) { + if (!fr.m_it->is_binary_clause()) { + fr.m_it++; + continue; + } + literal l2 = fr.m_it->get_literal(); + unsigned l2_idx = l2.index(); + if (index[l2_idx] == UINT_MAX) { + NEW_NODE(l2_idx); + goto loop; + } + else if (in_s[l2_idx]) { + if (index[l2_idx] < lowlink[l_idx]) + lowlink[l_idx] = index[l2_idx]; + } + fr.m_it++; + } + // visited all successors + if (lowlink[l_idx] == index[l_idx]) { + // found new SCC + CTRACE("scc_cycle", s.back() != l_idx, { + tout << "cycle: "; + unsigned j = s.size() - 1; + unsigned l2_idx; + do { + l2_idx = s[j]; + j--; + tout << to_literal(l2_idx) << " "; + } + while (l2_idx != l_idx); + tout << "\n"; + }); + + SASSERT(!s.empty()); + literal l = to_literal(l_idx); + bool_var v = l.var(); + if (roots[v] != null_literal) { + // variable was already assigned... just consume stack + TRACE("scc_detail", tout << "consuming stack...\n";); + unsigned l2_idx; + do { + l2_idx = s.back(); + s.pop_back(); + in_s[l2_idx] = false; + SASSERT(roots[to_literal(l2_idx).var()].var() == roots[v].var()); + } + while (l2_idx != l_idx); + } + else { + // check if the SCC has an external variable, and check for conflicts + TRACE("scc_detail", tout << "assigning roots...\n";); + literal r = null_literal; + unsigned j = s.size() - 1; + unsigned l2_idx; + do { + l2_idx = s[j]; + j--; + if (to_literal(l2_idx) == ~l) { + m_solver.set_conflict(justification()); + return 0; + } + if (m_solver.is_external(to_literal(l2_idx).var())) { + r = to_literal(l2_idx); + break; + } + } + while (l2_idx != l_idx); + + if (r == null_literal) { + // SCC does not contain external variable + r = to_literal(l_idx); + } + + TRACE("scc_detail", tout << "r: " << r << "\n";); + + do { + l2_idx = s.back(); + s.pop_back(); + in_s[l2_idx] = false; + literal l2 = to_literal(l2_idx); + bool_var v2 = l2.var(); + if (roots[v2] == null_literal) { + if (l2.sign()) { + roots[v2] = ~r; + } + else { + roots[v2] = r; + } + if (v2 != r.var()) + to_elim.push_back(v2); + } + } + while (l2_idx != l_idx); + } + } + frames.pop_back(); + } + } + TRACE("scc", for (unsigned i = 0; i < roots.size(); i++) { tout << i << " -> " << roots[i] << "\n"; } + tout << "to_elim: "; for (unsigned i = 0; i < to_elim.size(); i++) tout << to_elim[i] << " "; tout << "\n";); + m_num_elim += to_elim.size(); + elim_eqs eliminator(m_solver); + eliminator(roots, to_elim); + TRACE("scc_detail", m_solver.display(tout);); + CASSERT("scc_bug", m_solver.check_invariant()); + return to_elim.size(); + } + + void scc::collect_statistics(statistics & st) { + st.update("elim bool vars", m_num_elim); + } + + void scc::reset_statistics() { + m_num_elim = 0; + } + + void scc::updt_params(params_ref const & p) { + m_scc = p.get_bool(":scc", true); + } + + void scc::collect_param_descrs(param_descrs & d) { + d.insert(":scc", CPK_BOOL, "(default: true) eliminate Boolean variables by computing strongly connected components."); + } + +}; diff --git a/lib/sat_scc.h b/lib/sat_scc.h new file mode 100644 index 000000000..c85cc7d42 --- /dev/null +++ b/lib/sat_scc.h @@ -0,0 +1,48 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_scc.h + +Abstract: + + Use binary clauses to compute strongly connected components. + +Author: + + Leonardo de Moura (leonardo) 2011-05-26. + +Revision History: + +--*/ +#ifndef _SAT_SCC_H_ +#define _SAT_SCC_H_ + +#include"sat_types.h" +#include"statistics.h" +#include"params.h" + +namespace sat { + class solver; + + class scc { + struct report; + solver & m_solver; + // config + bool m_scc; + // stats + unsigned m_num_elim; + public: + scc(solver & s, params_ref const & p); + unsigned operator()(); + + void updt_params(params_ref const & p); + static void collect_param_descrs(param_descrs & d); + + void collect_statistics(statistics & st); + void reset_statistics(); + }; +}; + +#endif diff --git a/lib/sat_simplifier.cpp b/lib/sat_simplifier.cpp new file mode 100644 index 000000000..3f3015967 --- /dev/null +++ b/lib/sat_simplifier.cpp @@ -0,0 +1,1489 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_simplifier.cpp + +Abstract: + + SAT simplification procedures that use a "full" occurrence list: + Subsumption, Blocked Clause Removal, Variable Elimination, ... + + +Author: + + Leonardo de Moura (leonardo) 2011-05-24. + +Revision History: + +--*/ +#include"sat_simplifier.h" +#include"sat_solver.h" +#include"stopwatch.h" +#include"trace.h" + +namespace sat { + + void use_list::init(unsigned num_vars) { + m_use_list.reset(); + unsigned num_lits = 2 * num_vars; + m_use_list.resize(num_lits); + } + + void use_list::insert(clause & c) { + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) { + m_use_list[c[i].index()].insert(c); + } + } + + void use_list::erase(clause & c) { + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) { + m_use_list[c[i].index()].erase(c); + } + } + + void use_list::erase(clause & c, literal l) { + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) { + literal l2 = c[i]; + if (l2 != l) + m_use_list[l2.index()].erase(c); + } + } + + simplifier::simplifier(solver & _s, params_ref const & p): + s(_s), + m_num_calls(0) { + updt_params(p); + reset_statistics(); + } + + simplifier::~simplifier() { + } + + inline watch_list & simplifier::get_wlist(literal l) { return s.get_wlist(l); } + + inline watch_list const & simplifier::get_wlist(literal l) const { return s.get_wlist(l); } + + inline bool simplifier::is_external(bool_var v) const { return s.is_external(v); } + + inline bool simplifier::was_eliminated(bool_var v) const { return s.was_eliminated(v); } + + lbool simplifier::value(bool_var v) const { return s.value(v); } + + lbool simplifier::value(literal l) const { return s.value(l); } + + inline void simplifier::checkpoint() { s.checkpoint(); } + + void simplifier::register_clauses(clause_vector & cs) { + std::stable_sort(cs.begin(), cs.end(), size_lt()); + clause_vector::iterator it = cs.begin(); + clause_vector::iterator end = cs.end(); + for (; it != end; ++it) { + clause & c = *(*it); + if (!c.frozen()) { + m_use_list.insert(c); + if (c.strengthened()) + m_sub_todo.insert(c); + } + } + } + + inline void simplifier::remove_clause_core(clause & c) { + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) + insert_todo(c[i].var()); + m_sub_todo.erase(c); + c.set_removed(true); + TRACE("resolution_bug", tout << "del_clause: " << c << "\n";); + m_need_cleanup = true; + } + + inline void simplifier::remove_clause(clause & c) { + remove_clause_core(c); + m_use_list.erase(c); + } + + inline void simplifier::remove_clause(clause & c, literal l) { + remove_clause_core(c); + m_use_list.erase(c, l); + } + + inline void simplifier::remove_bin_clause_half(literal l1, literal l2, bool learned) { + SASSERT(s.get_wlist(~l1).contains(watched(l2, learned))); + s.get_wlist(~l1).erase(watched(l2, learned)); + } + + void simplifier::init_visited() { + m_visited.reset(); + m_visited.resize(2*s.num_vars(), false); + } + + void simplifier::free_memory() { + m_use_list.finalize(); + m_sub_todo.finalize(); + m_sub_bin_todo.finalize(); + m_visited.finalize(); + m_bs_cs.finalize(); + m_bs_ls.finalize(); + } + + void simplifier::operator()(bool learned) { + if (s.inconsistent()) + return; + if (!m_subsumption && !m_elim_blocked_clauses && !m_resolution) + return; + CASSERT("sat_solver", s.check_invariant()); + TRACE("before_simplifier", s.display(tout);); + s.m_cleaner(true); + m_last_sub_trail_sz = s.m_trail.size(); + TRACE("after_cleanup", s.display(tout);); + CASSERT("sat_solver", s.check_invariant()); + m_need_cleanup = false; + m_use_list.init(s.num_vars()); + init_visited(); + if (learned) + register_clauses(s.m_learned); + register_clauses(s.m_clauses); + + if (!learned && (m_elim_blocked_clauses || m_elim_blocked_clauses_at == m_num_calls)) + elim_blocked_clauses(); + + if (!learned) + m_num_calls++; + + m_sub_counter = m_subsumption_limit; + m_elim_counter = m_res_limit; + unsigned old_num_elim_vars = m_num_elim_vars; + + do { + if (m_subsumption) + subsume(); + if (s.inconsistent()) + return; + if (!learned && m_resolution) + elim_vars(); + if (s.inconsistent()) + return; + if (!m_subsumption || m_sub_counter < 0) + break; + } + while (!m_sub_todo.empty()); + + bool vars_eliminated = m_num_elim_vars > old_num_elim_vars; + + if (!m_need_cleanup) { + if (vars_eliminated) { + // must remove learned clauses with eliminated variables + cleanup_clauses(s.m_learned, true, true); + } + CASSERT("sat_solver", s.check_invariant()); + TRACE("after_simplifier", s.display(tout); tout << "model_converter:\n"; s.m_mc.display(tout);); + free_memory(); + return; + } + cleanup_watches(); + cleanup_clauses(s.m_learned, true, vars_eliminated); + cleanup_clauses(s.m_clauses, false, vars_eliminated); + TRACE("after_simplifier", s.display(tout); tout << "model_converter:\n"; s.m_mc.display(tout);); + CASSERT("sat_solver", s.check_invariant()); + free_memory(); + } + + /** + \brief Eliminate all ternary and clause watches. + */ + void simplifier::cleanup_watches() { + vector::iterator it = s.m_watches.begin(); + vector::iterator end = s.m_watches.end(); + for (; it != end; ++it) { + watch_list & wlist = *it; + watch_list::iterator it2 = wlist.begin(); + watch_list::iterator itprev = it2; + watch_list::iterator end2 = wlist.end(); + for (; it2 != end2; ++it2) { + switch (it2->get_kind()) { + case watched::TERNARY: + case watched::CLAUSE: + // consume + break; + default: + *itprev = *it2; + itprev++; + break; + } + } + wlist.set_end(itprev); + } + } + + void simplifier::cleanup_clauses(clause_vector & cs, bool learned, bool vars_eliminated) { + clause_vector::iterator it = cs.begin(); + clause_vector::iterator it2 = it; + clause_vector::iterator end = cs.end(); + for (; it != end; ++it) { + clause & c = *(*it); + if (c.was_removed()) { + s.del_clause(c); + continue; + } + + if (learned && vars_eliminated) { + unsigned sz = c.size(); + unsigned i; + for (i = 0; i < sz; i++) { + if (was_eliminated(c[i].var())) + break; + } + if (i < sz) { + s.del_clause(c); + continue; + } + } + + if (cleanup_clause(c)) { + s.del_clause(c); + continue; + } + unsigned sz = c.size(); + if (sz == 0) { + s.set_conflict(justification()); + return; + } + if (sz == 1) { + s.assign(c[0], justification()); + s.del_clause(c); + continue; + } + if (sz == 2) { + s.mk_bin_clause(c[0], c[1], c.is_learned()); + s.del_clause(c); + continue; + } + // clause became a problem clause + if (learned && !c.is_learned()) { + SASSERT(!c.frozen()); + s.m_clauses.push_back(&c); + continue; + } + *it2 = *it; + it2++; + if (!c.frozen()) { + if (sz == 3) + s.attach_ter_clause(c); + else + s.attach_nary_clause(c); + } + } + cs.set_end(it2); + } + + void simplifier::mark_all_but(clause const & c, literal l) { + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) { + if (c[i] == l) + continue; + mark_visited(c[i]); + } + } + + void simplifier::unmark_all(clause const & c) { + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) + unmark_visited(c[i]); + } + + /** + \brief Return the variable in c with the minimal number positive+negative occurrences. + */ + bool_var simplifier::get_min_occ_var(clause const & c) const { + literal l_best = c[0]; + unsigned best = m_use_list.get(l_best).size() + m_use_list.get(~l_best).size(); + unsigned sz = c.size(); + for (unsigned i = 1; i < sz; i++) { + literal l = c[i]; + unsigned num = m_use_list.get(l).size() + m_use_list.get(~l).size(); + if (num < best) { + l_best = l; + best = num; + } + } + return l_best.var(); + } + + /** + If c1 subsumes c2, return true + If c2 can self subsumed by c1, return true and store the literal that can be removed from c2 in l. + Otherwise return false + */ + bool simplifier::subsumes1(clause const & c1, clause const & c2, literal & l) { + unsigned sz2 = c2.size(); + for (unsigned i = 0; i < sz2; i++) + mark_visited(c2[i]); + + bool r = true; + l = null_literal; + unsigned sz1 = c1.size(); + for (unsigned i = 0; i < sz1; i++) { + if (!is_marked(c1[i])) { + if (l == null_literal && is_marked(~c1[i])) { + l = ~c1[i]; + } + else { + l = null_literal; + r = false; + break; + } + } + } + + for (unsigned i = 0; i < sz2; i++) + unmark_visited(c2[i]); + return r; + } + + /** + \brief Return the clauses subsumed by c1 and the clauses that can be subsumed resolved using c1. + The collections is populated using the use list of target. + */ + void simplifier::collect_subsumed1_core(clause const & c1, clause_vector & out, literal_vector & out_lits, + literal target) { + clause_use_list const & cs = m_use_list.get(target); + clause_use_list::iterator it = cs.mk_iterator(); + while (!it.at_end()) { + clause & c2 = it.curr(); + CTRACE("resolution_bug", c2.was_removed(), tout << "clause has been removed:\n" << c2 << "\n";); + SASSERT(!c2.was_removed()); + if (&c2 != &c1 && + c1.size() <= c2.size() && + approx_subset(c1.approx(), c2.approx())) { + m_sub_counter -= c1.size() + c2.size(); + literal l; + if (subsumes1(c1, c2, l)) { + out.push_back(&c2); + out_lits.push_back(l); + } + } + it.next(); + } + } + + /** + \brief Return the clauses subsumed by c1 and the clauses that can be subsumed resolved using c1. + */ + void simplifier::collect_subsumed1(clause const & c1, clause_vector & out, literal_vector & out_lits) { + bool_var v = get_min_occ_var(c1); + collect_subsumed1_core(c1, out, out_lits, literal(v, false)); + collect_subsumed1_core(c1, out, out_lits, literal(v, true)); + } + + /** + \brief Perform backward subsumption and self-subsumption resolution using c. + */ + void simplifier::back_subsumption1(clause & c1) { + m_bs_cs.reset(); + m_bs_ls.reset(); + collect_subsumed1(c1, m_bs_cs, m_bs_ls); + SASSERT(m_bs_cs.size() == m_bs_ls.size()); + clause_vector::iterator it = m_bs_cs.begin(); + clause_vector::iterator end = m_bs_cs.end(); + literal_vector::iterator l_it = m_bs_ls.begin(); + for (; it != end; ++it, ++l_it) { + clause & c2 = *(*it); + if (*l_it == null_literal) { + // c2 was subsumed + if (c1.is_learned() && !c2.is_learned()) + c1.unset_learned(); + TRACE("subsumption", tout << c1 << " subsumed " << c2 << "\n";); + remove_clause(c2); + m_num_subsumed++; + } + else { + // subsumption resolution + TRACE("subsumption_resolution", tout << c1 << " sub-ref(" << *l_it << ") " << c2 << "\n";); + elim_lit(c2, *l_it); + m_num_sub_res++; + TRACE("subsumption_resolution", tout << "result: " << c2 << "\n";); + } + if (s.inconsistent()) + break; + } + } + + void simplifier::back_subsumption1(literal l1, literal l2, bool learned) { + m_dummy.set(l1, l2, learned); + clause & c = *(m_dummy.get()); + back_subsumption1(c); + } + + /** + \brief Return the literal in c with the minimal number of occurrences. + */ + literal simplifier::get_min_occ_var0(clause const & c) const { + literal l_best = c[0]; + unsigned best = m_use_list.get(l_best).size(); + unsigned sz = c.size(); + for (unsigned i = 1; i < sz; i++) { + literal l = c[i]; + unsigned num = m_use_list.get(l).size(); + if (num < best) { + l_best = l; + best = num; + } + } + return l_best; + } + + /** + If c1 subsumes c2, return true + Otherwise return false + */ + bool simplifier::subsumes0(clause const & c1, clause const & c2) { + unsigned sz2 = c2.size(); + for (unsigned i = 0; i < sz2; i++) + mark_visited(c2[i]); + + bool r = true; + unsigned sz1 = c1.size(); + for (unsigned i = 0; i < sz1; i++) { + if (!is_marked(c1[i])) { + r = false; + break; + } + } + + for (unsigned i = 0; i < sz2; i++) + unmark_visited(c2[i]); + + return r; + } + + /** + \brief Collect the clauses subsumed by c1 (using the occurrence list of target). + */ + void simplifier::collect_subsumed0_core(clause const & c1, clause_vector & out, literal target) { + clause_use_list const & cs = m_use_list.get(target); + clause_use_list::iterator it = cs.mk_iterator(); + while (!it.at_end()) { + clause & c2 = it.curr(); + SASSERT(!c2.was_removed()); + if (&c2 != &c1 && + c1.size() <= c2.size() && + approx_subset(c1.approx(), c2.approx())) { + m_sub_counter -= c1.size() + c2.size(); + if (subsumes0(c1, c2)) { + out.push_back(&c2); + } + } + it.next(); + } + } + + /** + \brief Collect the clauses subsumed by c1 + */ + void simplifier::collect_subsumed0(clause const & c1, clause_vector & out) { + literal l = get_min_occ_var0(c1); + collect_subsumed0_core(c1, out, l); + } + + + /** + \brief Perform backward subsumption using c1. + */ + void simplifier::back_subsumption0(clause & c1) { + m_bs_cs.reset(); + collect_subsumed0(c1, m_bs_cs); + clause_vector::iterator it = m_bs_cs.begin(); + clause_vector::iterator end = m_bs_cs.end(); + for (; it != end; ++it) { + clause & c2 = *(*it); + // c2 was subsumed + if (c1.is_learned() && !c2.is_learned()) + c1.unset_learned(); + TRACE("subsumption", tout << c1 << " subsumed " << c2 << "\n";); + remove_clause(c2); + m_num_subsumed++; + } + } + + /** + \brief Eliminate false literals from c, and update occurrence lists + + Return true if the clause is satisfied + */ + bool simplifier::cleanup_clause(clause & c) { + bool r = false; + unsigned sz = c.size(); + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + literal l = c[i]; + switch (value(l)) { + case l_undef: + c[j] = l; + j++; + break; + case l_false: + m_need_cleanup = true; + m_use_list.get(l).erase_not_removed(c); + break; + case l_true: + r = true; + c[j] = l; + j++; + break; + } + } + c.shrink(j); + return r; + } + + /** + \brief Eliminate false literals from c. + + Return true if the clause is satisfied + */ + bool simplifier::cleanup_clause(literal_vector & c) { + unsigned sz = c.size(); + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + literal l = c[i]; + switch (value(l)) { + case l_undef: + c[j] = l; + j++; + break; + case l_false: + break; + case l_true: + return true; + } + } + c.shrink(j); + return false; + } + + inline void simplifier::propagate_unit(literal l) { + unsigned old_trail_sz = s.m_trail.size(); + s.assign(l, justification()); + s.propagate_core(false); // must not use propagate(), since s.m_clauses is not in a consistent state. + if (s.inconsistent()) + return; + unsigned new_trail_sz = s.m_trail.size(); + for (unsigned i = old_trail_sz; i < new_trail_sz; i++) { + literal l = s.m_trail[i]; + { + // put clauses with literals assigned to false back into todo-list + clause_use_list & cs = m_use_list.get(~l); + clause_use_list::iterator it = cs.mk_iterator(); + while (!it.at_end()) { + clause & c = it.curr(); + it.next(); + m_sub_todo.insert(c); + } + } + { + // erase satisfied clauses + clause_use_list & cs = m_use_list.get(l); + { + clause_use_list::iterator it = cs.mk_iterator(); + while (!it.at_end()) { + clause & c = it.curr(); + it.next(); + remove_clause(c, l); + } + } + cs.reset(); + } + } + } + + void simplifier::elim_lit(clause & c, literal l) { + TRACE("elim_lit", tout << "processing: " << c << "\n";); + m_need_cleanup = true; + m_num_elim_lits++; + insert_todo(l.var()); + c.elim(l); + clause_use_list & occurs = m_use_list.get(l); + occurs.erase_not_removed(c); + m_sub_counter -= occurs.size()/2; + if (cleanup_clause(c)) { + // clause was satisfied + TRACE("elim_lit", tout << "clause was satisfied\n";); + remove_clause(c); + return; + } + switch (c.size()) { + case 0: + TRACE("elim_lit", tout << "clause is empty\n";); + s.set_conflict(justification()); + return; + case 1: + TRACE("elim_lit", tout << "clause became unit: " << c[0] << "\n";); + propagate_unit(c[0]); + remove_clause(c); + return; + case 2: + TRACE("elim_lit", tout << "clause became binary: " << c[0] << " " << c[1] << "\n";); + s.mk_bin_clause(c[0], c[1], c.is_learned()); + m_sub_bin_todo.push_back(bin_clause(c[0], c[1], c.is_learned())); + remove_clause(c); + return; + default: + TRACE("elim_lit", tout << "result: " << c << "\n";); + m_sub_todo.insert(c); + return; + } + } + + bool simplifier::subsume_with_binaries() { + unsigned init = s.m_rand(); // start in a random place, since subsumption can be aborted + unsigned num_lits = s.m_watches.size(); + for (unsigned i = 0; i < num_lits; i++) { + unsigned l_idx = (i + init) % num_lits; + watch_list & wlist = get_wlist(to_literal(l_idx)); + literal l = ~to_literal(l_idx); + // should not traverse wlist using iterators, since back_subsumption1 may add new binary clauses there + for (unsigned j = 0; j < wlist.size(); j++) { + watched w = wlist[j]; + if (w.is_binary_clause()) { + literal l2 = w.get_literal(); + if (l.index() < l2.index()) { + m_dummy.set(l, l2, w.is_learned()); + clause & c = *(m_dummy.get()); + back_subsumption1(c); + if (w.is_learned() && !c.is_learned()) { + SASSERT(wlist[j] == w); + TRACE("mark_not_learned_bug", + tout << "marking as not learned: " << l2 << " " << wlist[j].is_learned() << "\n";); + wlist[j].mark_not_learned(); + mark_as_not_learned_core(get_wlist(~l2), l); + } + if (s.inconsistent()) + return false; + } + } + } + if (m_sub_counter < 0) + break; + } + return true; + } + + void simplifier::mark_as_not_learned_core(watch_list & wlist, literal l2) { + watch_list::iterator it = wlist.begin(); + watch_list::iterator end = wlist.end(); + for (; it != end; ++it) { + if (it->is_binary_clause() && it->get_literal() == l2 && it->is_learned()) { + it->mark_not_learned(); + return; + } + } + } + + void simplifier::mark_as_not_learned(literal l1, literal l2) { + mark_as_not_learned_core(get_wlist(~l1), l2); + mark_as_not_learned_core(get_wlist(~l2), l1); + } + + struct bin_lt { + bool operator()(watched const & w1, watched const & w2) const { + if (!w1.is_binary_clause()) return false; + if (!w2.is_binary_clause()) return true; + unsigned l1_idx = w1.get_literal().index(); + unsigned l2_idx = w2.get_literal().index(); + if (l1_idx < l2_idx) return true; + if (l1_idx == l2_idx && !w1.is_learned() && w2.is_learned()) return true; + return false; + } + }; + + /** + \brief Eliminate duplicated binary clauses. + */ + void simplifier::elim_dup_bins() { + vector::iterator it = s.m_watches.begin(); + vector::iterator end = s.m_watches.end(); +#ifdef _TRACE + unsigned l_idx = 0; +#endif + unsigned elim = 0; + for (; it != end; ++it) { + checkpoint(); + watch_list & wlist = *it; + std::stable_sort(wlist.begin(), wlist.end(), bin_lt()); + literal last_lit = null_literal; + watch_list::iterator it2 = wlist.begin(); + watch_list::iterator itprev = it2; + watch_list::iterator end2 = wlist.end(); + for (; it2 != end2; ++it2) { + if (!it2->is_binary_clause()) { + *itprev = *it2; + itprev++; + continue; + } + if (it2->get_literal() == last_lit) { + TRACE("subsumption", tout << "eliminating: " << ~to_literal(l_idx) + << " " << it2->get_literal() << "\n";); + elim++; + } + else { + last_lit = it2->get_literal(); + *itprev = *it2; + itprev++; + } + } + wlist.set_end(itprev); + TRACE_CODE(l_idx++;); + } + m_num_subsumed += elim/2; // each binary clause is "eliminated" twice. + } + + struct simplifier::subsumption_report { + simplifier & m_simplifier; + stopwatch m_watch; + unsigned m_num_subsumed; + unsigned m_num_sub_res; + subsumption_report(simplifier & s): + m_simplifier(s), + m_num_subsumed(s.m_num_subsumed), + m_num_sub_res(s.m_num_sub_res) { + m_watch.start(); + } + + ~subsumption_report() { + m_watch.stop(); + IF_VERBOSE(SAT_VB_LVL, + verbose_stream() << " (sat-subsumer :subsumed " + << (m_simplifier.m_num_subsumed - m_num_subsumed) + << " :subsumption-resolution " << (m_simplifier.m_num_sub_res - m_num_sub_res) + << " :threshold " << m_simplifier.m_sub_counter + << mem_stat() + << " :time " << std::fixed << std::setprecision(2) << m_watch.get_seconds() << ")\n";); + } + }; + + void simplifier::subsume() { + subsumption_report rpt(*this); + elim_dup_bins(); + subsume_with_binaries(); + TRACE("subsumption_bug", s.display(tout);); + while (true) { + TRACE("subsumption", tout << "sub_todo size: " << m_sub_todo.size() << "\n";); + + m_sub_counter -= m_sub_bin_todo.size(); + while (!m_sub_bin_todo.empty()) { + checkpoint(); + m_dummy.set(m_sub_bin_todo.back()); + m_sub_bin_todo.pop_back(); + clause & c = *(m_dummy.get()); + bool was_learned = c.is_learned(); + back_subsumption1(c); + if (was_learned && !c.is_learned()) { + mark_as_not_learned(c[0], c[1]); + } + } + + checkpoint(); + + TRACE("subsumption_bug", s.display(tout);); + + if (m_sub_todo.empty()) { + m_last_sub_trail_sz = s.m_trail.size(); + break; + } + + if (m_sub_counter < 0) + break; + + clause & c = m_sub_todo.erase(); + c.unmark_strengthened(); + m_sub_counter--; + TRACE("subsumption", tout << "next: " << c << "\n";); + if (s.m_trail.size() > m_last_sub_trail_sz) { + if (cleanup_clause(c)) { + remove_clause(c); + continue; + } + unsigned sz = c.size(); + if (sz == 0) { + s.set_conflict(justification()); + return; + } + if (sz == 1) { + propagate_unit(c[0]); + remove_clause(c); + continue; + } + if (sz == 2) { + TRACE("subsumption", tout << "clause became binary: " << c << "\n";); + s.mk_bin_clause(c[0], c[1], c.is_learned()); + m_sub_bin_todo.push_back(bin_clause(c[0], c[1], c.is_learned())); + remove_clause(c); + continue; + } + } + TRACE("subsumption", tout << "using: " << c << "\n";); + back_subsumption1(c); + } + } + + struct simplifier::blocked_clause_elim { + class literal_lt { + use_list const & m_use_list; + vector const & m_watches; + public: + literal_lt(use_list const & l, vector const & ws):m_use_list(l), m_watches(ws) {} + + unsigned weight(unsigned l_idx) const { + return 2*m_use_list.get(~to_literal(l_idx)).size() + m_watches[l_idx].size(); + } + + bool operator()(unsigned l_idx1, unsigned l_idx2) const { + return weight(l_idx1) < weight(l_idx2); + } + }; + + class queue { + heap m_queue; + public: + queue(use_list const & l, vector const & ws):m_queue(128, literal_lt(l, ws)) {} + void insert(literal l) { + unsigned idx = l.index(); + m_queue.reserve(idx + 1); + SASSERT(!m_queue.contains(idx)); + m_queue.insert(idx); + } + void decreased(literal l) { + unsigned idx = l.index(); + if (m_queue.contains(idx)) + m_queue.decreased(idx); + else + m_queue.insert(idx); + } + literal next() { SASSERT(!empty()); return to_literal(m_queue.erase_min()); } + bool empty() const { return m_queue.empty(); } + }; + + simplifier & s; + int m_counter; + model_converter & mc; + queue m_queue; + clause_vector m_to_remove; + + blocked_clause_elim(simplifier & _s, unsigned limit, model_converter & _mc, use_list & l, + vector & wlist): + s(_s), + m_counter(limit), + mc(_mc), + m_queue(l, wlist) { + } + + void insert(literal l) { + bool_var v = l.var(); + if (s.is_external(v) || s.was_eliminated(v)) + return; + m_queue.insert(l); + } + + void operator()(unsigned num_vars) { + for (bool_var v = 0; v < num_vars; v++) { + insert(literal(v, false)); + insert(literal(v, true)); + } + while (!m_queue.empty()) { + s.checkpoint(); + if (m_counter < 0) + return; + literal l = m_queue.next(); + process(l); + } + } + + void process(literal l) { + TRACE("blocked_clause", tout << "processing: " << l << "\n";); + model_converter::entry * new_entry = 0; + { + m_to_remove.reset(); + { + clause_use_list & occs = s.m_use_list.get(l); + clause_use_list::iterator it = occs.mk_iterator(); + while (!it.at_end()) { + clause & c = it.curr(); + m_counter -= c.size(); + SASSERT(c.contains(l)); + s.mark_all_but(c, l); + if (all_tautology(l)) { + TRACE("blocked_clause", tout << "new blocked clause: " << c << "\n";); + if (new_entry == 0) + new_entry = &(mc.mk(model_converter::BLOCK_LIT, l.var())); + m_to_remove.push_back(&c); + s.m_num_blocked_clauses++; + mc.insert(*new_entry, c); + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) { + if (c[i] != l) + m_queue.decreased(~c[i]); + } + } + s.unmark_all(c); + it.next(); + } + } + clause_vector::iterator it = m_to_remove.begin(); + clause_vector::iterator end = m_to_remove.end(); + for (; it != end; ++it) { + s.remove_clause(*(*it)); + } + } + { + watch_list & wlist = s.get_wlist(~l); + m_counter -= wlist.size(); + watch_list::iterator it = wlist.begin(); + watch_list::iterator it2 = it; + watch_list::iterator end = wlist.end(); + for (; it != end; ++it) { + if (!it->is_binary_clause()) { + *it2 = *it; + it2++; + continue; + } + literal l2 = it->get_literal(); + s.mark_visited(l2); + if (all_tautology(l)) { + if (new_entry == 0) + new_entry = &(mc.mk(model_converter::BLOCK_LIT, l.var())); + TRACE("blocked_clause", tout << "new blocked clause: " << l2 << " " << l << "\n";); + s.remove_bin_clause_half(l2, l, it->is_learned()); + s.m_num_blocked_clauses++; + m_queue.decreased(~l2); + mc.insert(*new_entry, l, l2); + } + else { + *it2 = *it; + it2++; + } + s.unmark_visited(l2); + } + wlist.set_end(it2); + } + } + + bool all_tautology(literal l) { + { + watch_list & wlist = s.get_wlist(l); + m_counter -= wlist.size(); + watch_list::iterator it = wlist.begin(); + watch_list::iterator end = wlist.end(); + for (; it != end; ++it) { + if (!it->is_binary_non_learned_clause()) + continue; + if (!s.is_marked(~it->get_literal())) + return false; + } + } + { + clause_use_list & neg_occs = s.m_use_list.get(~l); + clause_use_list::iterator it = neg_occs.mk_iterator(); + while (!it.at_end()) { + clause & c = it.curr(); + m_counter -= c.size(); + unsigned sz = c.size(); + unsigned i; + for (i = 0; i < sz; i++) { + if (s.is_marked(~c[i])) + break; + } + if (i == sz) + return false; + it.next(); + } + } + return true; + } + }; + + struct simplifier::blocked_cls_report { + simplifier & m_simplifier; + stopwatch m_watch; + unsigned m_num_blocked_clauses; + blocked_cls_report(simplifier & s): + m_simplifier(s), + m_num_blocked_clauses(s.m_num_blocked_clauses) { + m_watch.start(); + } + + ~blocked_cls_report() { + m_watch.stop(); + IF_VERBOSE(SAT_VB_LVL, + verbose_stream() << " (sat-blocked-clauses :elim-blocked-clauses " + << (m_simplifier.m_num_blocked_clauses - m_num_blocked_clauses) + << mem_stat() + << " :time " << std::fixed << std::setprecision(2) << m_watch.get_seconds() << ")\n";); + } + }; + + void simplifier::elim_blocked_clauses() { + TRACE("blocked_clause_bug", tout << "trail: " << s.m_trail.size() << "\n"; s.display_watches(tout); s.display(tout);); + blocked_cls_report rpt(*this); + blocked_clause_elim elim(*this, m_blocked_clause_limit, s.m_mc, m_use_list, s.m_watches); + elim(s.num_vars()); + } + + unsigned simplifier::get_num_non_learned_bin(literal l) const { + unsigned r = 0; + watch_list const & wlist = get_wlist(~l); + watch_list::const_iterator it = wlist.begin(); + watch_list::const_iterator end = wlist.end(); + for (; it != end; ++it) { + if (it->is_binary_non_learned_clause()) + r++; + } + return r; + } + + unsigned simplifier::get_to_elim_cost(bool_var v) const { + literal pos_l(v, false); + literal neg_l(v, true); + unsigned num_pos = m_use_list.get(pos_l).size(); + unsigned num_neg = m_use_list.get(neg_l).size(); + unsigned num_bin_pos = get_num_non_learned_bin(pos_l); + unsigned num_bin_neg = get_num_non_learned_bin(neg_l); + unsigned cost = 2 * num_pos * num_neg + num_pos * num_bin_neg + num_neg * num_bin_pos; + CTRACE("elim_vars_detail", cost == 0, tout << v << " num_pos: " << num_pos << " num_neg: " << num_neg << " num_bin_pos: " << num_bin_pos + << " num_bin_neg: " << num_bin_neg << " cost: " << cost << "\n";); + return cost; + } + + typedef std::pair bool_var_and_cost; + + struct bool_var_and_cost_lt { + bool operator()(bool_var_and_cost const & p1, bool_var_and_cost const & p2) const { return p1.second < p2.second; } + }; + + void simplifier::order_vars_for_elim(bool_var_vector & r) { + svector tmp; + bool_var_set::iterator it = m_elim_todo.begin(); + bool_var_set::iterator end = m_elim_todo.end(); + for (; it != end; ++it) { + bool_var v = *it; + if (is_external(v)) + continue; + if (was_eliminated(v)) + continue; + if (value(v) != l_undef) + continue; + unsigned c = get_to_elim_cost(v); + tmp.push_back(bool_var_and_cost(v, c)); + } + m_elim_todo.reset(); + std::stable_sort(tmp.begin(), tmp.end(), bool_var_and_cost_lt()); + TRACE("elim_vars", + svector::iterator it = tmp.begin(); + svector::iterator end = tmp.end(); + for (; it != end; ++it) { + tout << "(" << it->first << ", " << it->second << ") "; + } + tout << "\n";); + svector::iterator it2 = tmp.begin(); + svector::iterator end2 = tmp.end(); + for (; it2 != end2; ++it2) { + r.push_back(it2->first); + } + } + + /** + \brief Collect clauses and binary clauses containing l. + */ + void simplifier::collect_clauses(literal l, clause_wrapper_vector & r) { + clause_use_list const & cs = m_use_list.get(l); + clause_use_list::iterator it = cs.mk_iterator(); + while (!it.at_end()) { + r.push_back(clause_wrapper(it.curr())); + SASSERT(r.back().size() == it.curr().size()); + it.next(); + } + + watch_list & wlist = get_wlist(~l); + watch_list::iterator it2 = wlist.begin(); + watch_list::iterator end2 = wlist.end(); + for (; it2 != end2; ++it2) { + if (it2->is_binary_non_learned_clause()) { + r.push_back(clause_wrapper(l, it2->get_literal())); + SASSERT(r.back().size() == 2); + } + } + } + + /** + \brief Resolve clauses c1 and c2. + c1 must contain l. + c2 must contain ~l. + Store result in r. + Return false if the result is a tautology + */ + bool simplifier::resolve(clause_wrapper const & c1, clause_wrapper const & c2, literal l, literal_vector & r) { + CTRACE("resolve_bug", !c1.contains(l), tout << c1 << "\n" << c2 << "\nl: " << l << "\n";); + SASSERT(c1.contains(l)); + SASSERT(c2.contains(~l)); + bool res = true; + unsigned sz = c1.size(); + m_elim_counter -= sz; + for (unsigned i = 0; i < sz; i++) { + literal l2 = c1[i]; + if (l == l2) + continue; + m_visited[l2.index()] = true; + r.push_back(l2); + } + + literal not_l = ~l; + sz = c2.size(); + m_elim_counter -= sz; + for (unsigned i = 0; i < sz; i++) { + literal l2 = c2[i]; + if (not_l == l2) + continue; + if (m_visited[(~l2).index()]) { + res = false; + break; + } + if (!m_visited[l2.index()]) + r.push_back(l2); + } + + sz = c1.size(); + for (unsigned i = 0; i < sz; i++) { + literal l2 = c1[i]; + if (l == l2) + continue; + m_visited[l2.index()] = false; + } + + return res; + } + + void simplifier::save_clauses(model_converter::entry & mc_entry, clause_wrapper_vector const & cs) { + model_converter & mc = s.m_mc; + clause_wrapper_vector::const_iterator it = cs.begin(); + clause_wrapper_vector::const_iterator end = cs.end(); + for (; it != end; ++it) + mc.insert(mc_entry, *it); + } + + void simplifier::add_non_learned_binary_clause(literal l1, literal l2) { + watch_list & wlist1 = s.m_watches[(~l1).index()]; + watch_list & wlist2 = s.m_watches[(~l2).index()]; + watch_list::iterator it1 = wlist1.begin(); + watch_list::iterator end1 = wlist1.end(); + for (; it1 != end1; ++it1) { + if (it1->is_binary_clause() && it1->get_literal() == l2) { + *it1 = watched(l2, false); + watch_list::iterator it2 = wlist2.begin(); + watch_list::iterator end2 = wlist2.end(); + for (; it2 != end2; ++it2) { + if (it2->is_binary_clause() && it2->get_literal() == l1) { + *it2 = watched(l1, false); + break; + } + } + CTRACE("resolve_bug", it2 == end2, + tout << ~l1 << " -> "; + display(tout, s.m_cls_allocator, wlist1); tout << "\n" << ~l2 << " -> "; + display(tout, s.m_cls_allocator, wlist2); tout << "\n";); + SASSERT(it2 != end2); + return; + } + } + wlist1.push_back(watched(l2, false)); + wlist2.push_back(watched(l1, false)); + } + + /** + \brief Eliminate the binary clauses watched by l, when l.var() is being eliminated + */ + void simplifier::remove_bin_clauses(literal l) { + watch_list & wlist = get_wlist(~l); + watch_list::iterator it = wlist.begin(); + watch_list::iterator end = wlist.end(); + for (; it != end; ++it) { + if (it->is_binary_clause()) { + literal l2 = it->get_literal(); + watch_list & wlist2 = get_wlist(~l2); + watch_list::iterator it2 = wlist2.begin(); + watch_list::iterator itprev = it2; + watch_list::iterator end2 = wlist2.end(); + for (; it2 != end2; ++it2) { + if (it2->is_binary_clause() && it2->get_literal() == l) { + TRACE("bin_clause_bug", tout << "removing: " << l << " " << it2->get_literal() << "\n";); + continue; + } + *itprev = *it2; + itprev++; + } + wlist2.set_end(itprev); + } + } + TRACE("bin_clause_bug", tout << "collapsing watch_list of: " << l << "\n";); + wlist.finalize(); + } + + /** + \brief Eliminate the clauses where the variable being eliminated occur. + */ + void simplifier::remove_clauses(clause_use_list const & cs, literal l) { + clause_use_list::iterator it = cs.mk_iterator(); + while (!it.at_end()) { + clause & c = it.curr(); + it.next(); + SASSERT(c.contains(l)); + c.set_removed(true); + m_use_list.erase(c, l); + m_sub_todo.erase(c); + TRACE("resolution_bug", tout << "del_clause (elim_var): " << c << "\n";); + m_need_cleanup = true; + } + } + + bool simplifier::try_eliminate(bool_var v) { + TRACE("resolution_bug", tout << "processing: " << v << "\n";); + if (value(v) != l_undef) + return false; + + literal pos_l(v, false); + literal neg_l(v, true); + unsigned num_bin_pos = get_num_non_learned_bin(pos_l); + unsigned num_bin_neg = get_num_non_learned_bin(neg_l); + clause_use_list & pos_occs = m_use_list.get(pos_l); + clause_use_list & neg_occs = m_use_list.get(neg_l); + unsigned num_pos = pos_occs.size() + num_bin_pos; + unsigned num_neg = neg_occs.size() + num_bin_neg; + m_elim_counter -= num_pos + num_neg; + + TRACE("resolution", tout << v << " num_pos: " << num_pos << " neg_pos: " << num_neg << "\n";); + + if (num_pos >= m_res_occ_cutoff && num_neg >= m_res_occ_cutoff) + return false; + + unsigned before_lits = num_bin_pos*2 + num_bin_neg*2; + + { + clause_use_list::iterator it = pos_occs.mk_iterator(); + while (!it.at_end()) { + before_lits += it.curr().size(); + it.next(); + } + } + + { + clause_use_list::iterator it2 = neg_occs.mk_iterator(); + while (!it2.at_end()) { + before_lits += it2.curr().size(); + it2.next(); + } + } + + TRACE("resolution", tout << v << " num_pos: " << num_pos << " neg_pos: " << num_neg << " before_lits: " << before_lits << "\n";); + + if (num_pos >= m_res_occ_cutoff3 && num_neg >= m_res_occ_cutoff3 && before_lits > m_res_lit_cutoff3 && s.m_clauses.size() > m_res_cls_cutoff2) + return false; + if (num_pos >= m_res_occ_cutoff2 && num_neg >= m_res_occ_cutoff2 && before_lits > m_res_lit_cutoff2 && + s.m_clauses.size() > m_res_cls_cutoff1 && s.m_clauses.size() <= m_res_cls_cutoff2) + return false; + if (num_pos >= m_res_occ_cutoff1 && num_neg >= m_res_occ_cutoff1 && before_lits > m_res_lit_cutoff1 && + s.m_clauses.size() <= m_res_cls_cutoff1) + return false; + + m_pos_cls.reset(); + m_neg_cls.reset(); + collect_clauses(pos_l, m_pos_cls); + collect_clauses(neg_l, m_neg_cls); + + m_elim_counter -= num_pos * num_neg + before_lits; + + TRACE("resolution_detail", tout << "collecting number of after_clauses\n";); + unsigned before_clauses = num_pos + num_neg; + unsigned after_clauses = 0; + clause_wrapper_vector::iterator it1 = m_pos_cls.begin(); + clause_wrapper_vector::iterator end1 = m_pos_cls.end(); + for (; it1 != end1; ++it1) { + clause_wrapper_vector::iterator it2 = m_neg_cls.begin(); + clause_wrapper_vector::iterator end2 = m_neg_cls.end(); + for (; it2 != end2; ++it2) { + m_new_cls.reset(); + if (resolve(*it1, *it2, pos_l, m_new_cls)) { + TRACE("resolution_detail", tout << *it1 << "\n" << *it2 << "\n-->\n"; + for (unsigned i = 0; i < m_new_cls.size(); i++) tout << m_new_cls[i] << " "; tout << "\n";); + after_clauses++; + if (after_clauses > before_clauses) { + TRACE("resolution", tout << "too many after clauses: " << after_clauses << "\n";); + return false; + } + } + } + } + TRACE("resolution", tout << "found var to eliminate, before: " << before_clauses << " after: " << after_clauses << "\n";); + + // eliminate variable + model_converter::entry & mc_entry = s.m_mc.mk(model_converter::ELIM_VAR, v); + save_clauses(mc_entry, m_pos_cls); + save_clauses(mc_entry, m_neg_cls); + s.m_eliminated[v] = true; + remove_bin_clauses(pos_l); + remove_bin_clauses(neg_l); + remove_clauses(pos_occs, pos_l); + remove_clauses(neg_occs, neg_l); + pos_occs.reset(); + neg_occs.reset(); + + m_elim_counter -= num_pos * num_neg + before_lits; + + it1 = m_pos_cls.begin(); + end1 = m_pos_cls.end(); + for (; it1 != end1; ++it1) { + clause_wrapper_vector::iterator it2 = m_neg_cls.begin(); + clause_wrapper_vector::iterator end2 = m_neg_cls.end(); + for (; it2 != end2; ++it2) { + m_new_cls.reset(); + if (!resolve(*it1, *it2, pos_l, m_new_cls)) + continue; + TRACE("resolution_new_cls", tout << *it1 << "\n" << *it2 << "\n-->\n" << m_new_cls << "\n";); + if (cleanup_clause(m_new_cls)) + continue; // clause is already satisfied. + switch (m_new_cls.size()) { + case 0: + s.set_conflict(justification()); + break; + case 1: + propagate_unit(m_new_cls[0]); + break; + case 2: + s.m_stats.m_mk_bin_clause++; + add_non_learned_binary_clause(m_new_cls[0], m_new_cls[1]); + back_subsumption1(m_new_cls[0], m_new_cls[1], false); + break; + default: + if (m_new_cls.size() == 3) + s.m_stats.m_mk_ter_clause++; + else + s.m_stats.m_mk_clause++; + clause * new_c = s.m_cls_allocator.mk_clause(m_new_cls.size(), m_new_cls.c_ptr(), false); + s.m_clauses.push_back(new_c); + m_use_list.insert(*new_c); + if (m_sub_counter > 0) + back_subsumption1(*new_c); + else + back_subsumption0(*new_c); + break; + } + if (s.inconsistent()) + return true; + } + } + + return true; + } + + struct simplifier::elim_var_report { + simplifier & m_simplifier; + stopwatch m_watch; + unsigned m_num_elim_vars; + elim_var_report(simplifier & s): + m_simplifier(s), + m_num_elim_vars(s.m_num_elim_vars) { + m_watch.start(); + } + + ~elim_var_report() { + m_watch.stop(); + IF_VERBOSE(SAT_VB_LVL, + verbose_stream() << " (sat-resolution :elim-bool-vars " + << (m_simplifier.m_num_elim_vars - m_num_elim_vars) + << " :threshold " << m_simplifier.m_elim_counter + << mem_stat() + << " :time " << std::fixed << std::setprecision(2) << m_watch.get_seconds() << ")\n";); + } + }; + + void simplifier::elim_vars() { + elim_var_report rpt(*this); + bool_var_vector vars; + order_vars_for_elim(vars); + + bool_var_vector::iterator it = vars.begin(); + bool_var_vector::iterator end = vars.end(); + for (; it != end; ++it) { + checkpoint(); + if (m_elim_counter < 0) + return; + bool_var v = *it; + if (try_eliminate(v)) { + m_num_elim_vars++; + } + } + + m_pos_cls.finalize(); + m_neg_cls.finalize(); + m_new_cls.finalize(); + } + + void simplifier::updt_params(params_ref const & p) { + m_elim_blocked_clauses = p.get_bool(":elim-blocked-clauses", false); + m_elim_blocked_clauses_at = p.get_uint(":elim-blocked-clauses-at", 2); + m_blocked_clause_limit = p.get_uint(":blocked-clause-limit", 100000000); + m_resolution = p.get_bool(":resolution", true); + m_res_limit = p.get_uint(":resolution-limit", 500000000); + m_res_occ_cutoff = p.get_uint(":res-occ-cutoff", 10); + m_res_occ_cutoff1 = p.get_uint(":res-occ-cutoff-range1", 8); + m_res_occ_cutoff2 = p.get_uint(":res-occ-cutoff-range2", 5); + m_res_occ_cutoff3 = p.get_uint(":res-occ-cutoff-range3", 3); + m_res_lit_cutoff1 = p.get_uint(":res-lit-cutoff-range1", 700); + m_res_lit_cutoff2 = p.get_uint(":res-lit-cutoff-range2", 400); + m_res_lit_cutoff3 = p.get_uint(":res-lit-cutoff-range3", 300); + m_res_cls_cutoff1 = p.get_uint(":res-cls-cutoff1", 100000); + m_res_cls_cutoff2 = p.get_uint(":res-cls-cutoff2", 700000); + m_subsumption = p.get_bool(":subsumption", true); + m_subsumption_limit = p.get_uint(":subsumption-limit", 100000000); + } + + void simplifier::collect_param_descrs(param_descrs & r) { + r.insert(":elim-blocked-clauses", CPK_BOOL, "(default: false) eliminate blocked clauses."); + r.insert(":elim-blocked-clauses-at", CPK_UINT, "(default: 2) eliminate blocked clauses only once at the given simplification round."); + r.insert(":blocked-clause-limit", CPK_UINT, "(default: 100000000) maximum number of literals visited during blocked clause elimination."); + r.insert(":resolution", CPK_BOOL, "(default: true) eliminate boolean variables using resolution."); + r.insert(":resolution-limit", CPK_UINT, "(default: 500000000) approx. maximum number of literals visited during variable elimination."); + r.insert(":res-occ-cutoff", CPK_UINT, "(default: 10) first cutoff (on number of positive/negative occurrences) for Boolean variable elimination."); + r.insert(":res-occ-cutoff-range1", CPK_UINT, "(default: 8) second cutoff (number of positive/negative occurrences) for Boolean variable elimination, for problems containing less than :res-cls-cutoff1 clauses."); + r.insert(":res-occ-cutoff-range2", CPK_UINT, "(default: 5) 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."); + r.insert(":res-occ-cutoff-range3", CPK_UINT, "(default: 3) second cutoff (number of positive/negative occurrences) for Boolean variable elimination, for problems containing more than :res-cls-cutoff2."); + + r.insert(":res-lit-cutoff-range1", CPK_UINT, "(default: 700) second cutoff (total number of literals) for Boolean variable elimination, for problems containing less than :res-cls-cutoff1 clauses."); + r.insert(":res-lit-cutoff-range2", CPK_UINT, "(default: 400) second cutoff (total number of literals) for Boolean variable elimination, for problems containing more than :res-cls-cutoff1 and less than :res-cls-cutoff2."); + r.insert(":res-lit-cutoff-range3", CPK_UINT, "(default: 300) second cutoff (total number of literals) for Boolean variable elimination, for problems containing more than :res-cls-cutoff2."); + + r.insert(":res-cls-cutoff1", CPK_UINT, "(default: 100000000) limit1 - total number of problems clauses for the second cutoff of Boolean variable elimination."); + r.insert(":res-cls-cutoff2", CPK_UINT, "(default: 700000000) limit2 - total number of problems clauses for the second cutoff of Boolean variable elimination."); + + r.insert(":subsumption", CPK_BOOL, "(default: true) eliminate subsumed clauses."); + r.insert(":subsumption-limit", CPK_UINT, "(default: 100000000) approx. maximum number of literals visited during subsumption (and subsumption resolution)."); + } + + void simplifier::collect_statistics(statistics & st) { + st.update("subsumed", m_num_subsumed); + st.update("subsumption resolution", m_num_sub_res); + st.update("elim literals", m_num_elim_lits); + st.update("elim bool vars", m_num_elim_vars); + st.update("elim blocked clauses", m_num_blocked_clauses); + } + + void simplifier::reset_statistics() { + m_num_blocked_clauses = 0; + m_num_subsumed = 0; + m_num_sub_res = 0; + m_num_elim_lits = 0; + m_num_elim_vars = 0; + } +}; diff --git a/lib/sat_simplifier.h b/lib/sat_simplifier.h new file mode 100644 index 000000000..44e2f0dee --- /dev/null +++ b/lib/sat_simplifier.h @@ -0,0 +1,188 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_simplifier.h + +Abstract: + + SAT simplification procedures that use a "full" occurrence list: + Subsumption, Blocked Clause Removal, Variable Elimination, ... + + +Author: + + Leonardo de Moura (leonardo) 2011-05-24. + +Revision History: + +--*/ +#ifndef _SAT_SIMPLIFIER_H_ +#define _SAT_SIMPLIFIER_H_ + +#include"sat_types.h" +#include"sat_clause.h" +#include"sat_clause_set.h" +#include"sat_clause_use_list.h" +#include"sat_watched.h" +#include"sat_model_converter.h" +#include"heap.h" +#include"statistics.h" +#include"params.h" + +namespace sat { + class solver; + + class use_list { + vector m_use_list; + public: + void init(unsigned num_vars); + void insert(clause & c); + void erase(clause & c); + void erase(clause & c, literal l); + clause_use_list & get(literal l) { return m_use_list[l.index()]; } + clause_use_list const & get(literal l) const { return m_use_list[l.index()]; } + void finalize() { m_use_list.finalize(); } + }; + + class simplifier { + solver & s; + unsigned m_num_calls; + use_list m_use_list; + clause_set m_sub_todo; + svector m_sub_bin_todo; + unsigned m_last_sub_trail_sz; // size of the trail since last cleanup + bool_var_set m_elim_todo; + bool m_need_cleanup; + tmp_clause m_dummy; + + // simplifier extra variable fields. + svector m_visited; // transient + + // counters + int m_sub_counter; + int m_elim_counter; + + // config + bool m_elim_blocked_clauses; + unsigned m_elim_blocked_clauses_at; + unsigned m_blocked_clause_limit; + bool m_resolution; + unsigned m_res_limit; + unsigned m_res_occ_cutoff; + unsigned m_res_occ_cutoff1; + unsigned m_res_occ_cutoff2; + unsigned m_res_occ_cutoff3; + unsigned m_res_lit_cutoff1; + unsigned m_res_lit_cutoff2; + unsigned m_res_lit_cutoff3; + unsigned m_res_cls_cutoff1; + unsigned m_res_cls_cutoff2; + + bool m_subsumption; + unsigned m_subsumption_limit; + + // stats + unsigned m_num_blocked_clauses; + unsigned m_num_subsumed; + unsigned m_num_elim_vars; + unsigned m_num_sub_res; + unsigned m_num_elim_lits; + + struct size_lt { + bool operator()(clause const * c1, clause const * c2) const { return c1->size() > c2->size(); } + }; + + void checkpoint(); + + void init_visited(); + void mark_visited(literal l) { m_visited[l.index()] = true; } + void unmark_visited(literal l) { m_visited[l.index()] = false; } + bool is_marked(literal l) const { return m_visited[l.index()] != 0; } + void mark_all_but(clause const & c, literal l); + void unmark_all(clause const & c); + + void register_clauses(clause_vector & cs); + + void remove_clause_core(clause & c); + void remove_clause(clause & c); + void remove_clause(clause & c, literal l); + void remove_bin_clause_half(literal l1, literal l2, bool learned); + + bool_var get_min_occ_var(clause const & c) const; + bool subsumes1(clause const & c1, clause const & c2, literal & l); + void collect_subsumed1_core(clause const & c, clause_vector & out, literal_vector & out_lits, literal target); + void collect_subsumed1(clause const & c, clause_vector & out, literal_vector & out_lits); + clause_vector m_bs_cs; + literal_vector m_bs_ls; + void back_subsumption1(clause & c); + void back_subsumption1(literal l1, literal l2, bool learned); + + literal get_min_occ_var0(clause const & c) const; + bool subsumes0(clause const & c1, clause const & c2); + void collect_subsumed0_core(clause const & c1, clause_vector & out, literal target); + void collect_subsumed0(clause const & c1, clause_vector & out); + void back_subsumption0(clause & c1); + + bool cleanup_clause(clause & c); + bool cleanup_clause(literal_vector & c); + void propagate_unit(literal l); + void elim_lit(clause & c, literal l); + void elim_dup_bins(); + bool subsume_with_binaries(); + void mark_as_not_learned_core(watch_list & wlist, literal l2); + void mark_as_not_learned(literal l1, literal l2); + void subsume(); + + void cleanup_watches(); + void cleanup_clauses(clause_vector & cs, bool learned, bool vars_eliminated); + + bool is_external(bool_var v) const; + bool was_eliminated(bool_var v) const; + lbool value(bool_var v) const; + lbool value(literal l) const; + watch_list & get_wlist(literal l); + watch_list const & get_wlist(literal l) const; + + struct blocked_clause_elim; + void elim_blocked_clauses(); + + unsigned get_num_non_learned_bin(literal l) const; + unsigned get_to_elim_cost(bool_var v) const; + void order_vars_for_elim(bool_var_vector & r); + void collect_clauses(literal l, clause_wrapper_vector & r); + clause_wrapper_vector m_pos_cls; + clause_wrapper_vector m_neg_cls; + literal_vector m_new_cls; + bool resolve(clause_wrapper const & c1, clause_wrapper const & c2, literal l, literal_vector & r); + void save_clauses(model_converter::entry & mc_entry, clause_wrapper_vector const & cs); + void add_non_learned_binary_clause(literal l1, literal l2); + void remove_bin_clauses(literal l); + void remove_clauses(clause_use_list const & cs, literal l); + bool try_eliminate(bool_var v); + void elim_vars(); + + struct blocked_cls_report; + struct subsumption_report; + struct elim_var_report; + + public: + simplifier(solver & s, params_ref const & p); + ~simplifier(); + + void insert_todo(bool_var v) { m_elim_todo.insert(v); } + + void operator()(bool learned); + + void updt_params(params_ref const & p); + static void collect_param_descrs(param_descrs & d); + + void free_memory(); + + void collect_statistics(statistics & st); + void reset_statistics(); + }; +}; + +#endif diff --git a/lib/sat_solver.cpp b/lib/sat_solver.cpp new file mode 100644 index 000000000..3ca32a499 --- /dev/null +++ b/lib/sat_solver.cpp @@ -0,0 +1,2331 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_solver.cpp + +Abstract: + + SAT solver main class. + +Author: + + Leonardo de Moura (leonardo) 2011-05-21. + +Revision History: + +--*/ +#include"sat_solver.h" +#include"strategy_exception.h" +#include"sat_integrity_checker.h" +#include"luby.h" +#include"trace.h" + +// define to update glue during propagation +#define UPDATE_GLUE + +// define to create a copy of the solver before starting the search +// useful for checking models +// #define CLONE_BEFORE_SOLVING + +namespace sat { + + solver::solver(params_ref const & p, extension * ext): + m_cancel(false), + m_config(p), + m_ext(ext), + m_cleaner(*this), + m_simplifier(*this, p), + m_scc(*this, p), + m_asymm_branch(*this, p), + m_probing(*this, p), + m_inconsistent(false), + m_num_frozen(0), + m_activity_inc(128), + m_case_split_queue(m_activity), + m_qhead(0), + m_scope_lvl(0), + m_params(p) { + m_config.updt_params(p); + } + + solver::~solver() { + del_clauses(m_clauses.begin(), m_clauses.end()); + del_clauses(m_learned.begin(), m_learned.end()); + } + + void solver::del_clauses(clause * const * begin, clause * const * end) { + for (clause * const * it = begin; it != end; ++it) { + m_cls_allocator.del_clause(*it); + } + } + + void solver::copy(solver const & src) { + SASSERT(m_mc.empty() && src.m_mc.empty()); + // create new vars + if (num_vars() < src.num_vars()) { + for (bool_var v = num_vars(); v < src.num_vars(); v++) { + SASSERT(!src.was_eliminated(v)); + bool ext = src.m_external[v] != 0; + bool dvar = src.m_decision[v] != 0; + bool_var new_v = mk_var(ext, dvar); + SASSERT(v == new_v); + } + } + { + // copy binary clauses + vector::const_iterator it = src.m_watches.begin(); + vector::const_iterator end = src.m_watches.begin(); + for (unsigned l_idx = 0; it != end; ++it, ++l_idx) { + watch_list const & wlist = *it; + literal l = ~to_literal(l_idx); + watch_list::const_iterator it2 = wlist.begin(); + watch_list::const_iterator end2 = wlist.end(); + for (; it2 != end2; ++it2) { + if (!it2->is_binary_non_learned_clause()) + continue; + literal l2 = it2->get_literal(); + mk_clause(l, l2); + } + } + } + { + literal_vector buffer; + // copy clause + clause_vector::const_iterator it = src.m_clauses.begin(); + clause_vector::const_iterator end = src.m_clauses.end(); + for (; it != end; ++it) { + clause const & c = *(*it); + buffer.reset(); + for (unsigned i = 0; i < c.size(); i++) + buffer.push_back(c[i]); + mk_clause(buffer.size(), buffer.c_ptr()); + } + } + } + + // ----------------------- + // + // Variable & Clause creation + // + // ----------------------- + + bool_var solver::mk_var(bool ext, bool dvar) { + m_stats.m_mk_var++; + bool_var v = m_level.size(); + m_watches.push_back(watch_list()); + m_watches.push_back(watch_list()); + m_assignment.push_back(l_undef); + m_assignment.push_back(l_undef); + m_justification.push_back(justification()); + m_decision.push_back(dvar); + m_eliminated.push_back(false); + m_external.push_back(ext); + m_activity.push_back(0); + m_level.push_back(UINT_MAX); + m_mark.push_back(false); + m_lit_mark.push_back(false); + m_lit_mark.push_back(false); + m_phase.push_back(PHASE_NOT_AVAILABLE); + m_prev_phase.push_back(PHASE_NOT_AVAILABLE); + m_assigned_since_gc.push_back(false); + m_case_split_queue.mk_var_eh(v); + m_simplifier.insert_todo(v); + SASSERT(!was_eliminated(v)); + return v; + } + + void solver::mk_clause(unsigned num_lits, literal * lits) { + DEBUG_CODE({ + for (unsigned i = 0; i < num_lits; i++) + SASSERT(m_eliminated[lits[i].var()] == false); + }); + mk_clause_core(num_lits, lits, false); + } + + void solver::mk_clause(literal l1, literal l2) { + literal ls[2] = { l1, l2 }; + mk_clause(2, ls); + } + + void solver::mk_clause(literal l1, literal l2, literal l3) { + literal ls[3] = { l1, l2, l3 }; + mk_clause(3, ls); + } + + clause * solver::mk_clause_core(unsigned num_lits, literal * lits, bool learned) { + if (!learned) { + TRACE("sat_mk_clause", tout << "mk_clause: " << mk_lits_pp(num_lits, lits) << "\n";); + bool keep = simplify_clause(num_lits, lits); + TRACE("sat_mk_clause", tout << "mk_clause (after simp), keep: " << keep << "\n" << mk_lits_pp(num_lits, lits) << "\n";); + if (!keep) { + return 0; // clause is equivalent to true. + } + } + + switch (num_lits) { + case 0: + set_conflict(justification()); + return 0; + case 1: + assign(lits[0], justification()); + return 0; + case 2: + mk_bin_clause(lits[0], lits[1], learned); + return 0; + case 3: + return mk_ter_clause(lits, learned); + default: + return mk_nary_clause(num_lits, lits, learned); + } + } + + void solver::mk_bin_clause(literal l1, literal l2, bool learned) { + if (propagate_bin_clause(l1, l2)) { + if (scope_lvl() == 0) + return; + if (!learned) + m_clauses_to_reinit.push_back(clause_wrapper(l1, l2)); + } + m_stats.m_mk_bin_clause++; + m_watches[(~l1).index()].push_back(watched(l2, learned)); + m_watches[(~l2).index()].push_back(watched(l1, learned)); + } + + bool solver::propagate_bin_clause(literal l1, literal l2) { + if (value(l2) == l_false) { + m_stats.m_bin_propagate++; + assign(l1, justification(l2)); + return true; + } + else if (value(l1) == l_false) { + m_stats.m_bin_propagate++; + assign(l2, justification(l1)); + return true; + } + return false; + } + + void solver::push_reinit_stack(clause & c) { + m_clauses_to_reinit.push_back(clause_wrapper(c)); + c.set_reinit_stack(true); + } + + clause * solver::mk_ter_clause(literal * lits, bool learned) { + m_stats.m_mk_ter_clause++; + clause * r = m_cls_allocator.mk_clause(3, lits, learned); + bool reinit; + attach_ter_clause(*r, reinit); + if (!learned && reinit) { + TRACE("sat_reinit", tout << "adding to reinit stack: " << *r << "\n";); + push_reinit_stack(*r); + } + if (learned) + m_learned.push_back(r); + else + m_clauses.push_back(r); + return r; + } + + void solver::attach_ter_clause(clause & c, bool & reinit) { + reinit = false; + m_watches[(~c[0]).index()].push_back(watched(c[1], c[2])); + m_watches[(~c[1]).index()].push_back(watched(c[0], c[2])); + m_watches[(~c[2]).index()].push_back(watched(c[0], c[1])); + if (scope_lvl() > 0) { + if (value(c[1]) == l_false && value(c[2]) == l_false) { + m_stats.m_ter_propagate++; + assign(c[0], justification(c[1], c[2])); + reinit = true; + } + else if (value(c[0]) == l_false && value(c[2]) == l_false) { + m_stats.m_ter_propagate++; + assign(c[1], justification(c[0], c[2])); + reinit = true; + } + else if (value(c[0]) == l_false && value(c[1]) == l_false) { + m_stats.m_ter_propagate++; + assign(c[2], justification(c[0], c[1])); + reinit = true; + } + } + } + + clause * solver::mk_nary_clause(unsigned num_lits, literal * lits, bool learned) { + m_stats.m_mk_clause++; + clause * r = m_cls_allocator.mk_clause(num_lits, lits, learned); + SASSERT(!learned || r->is_learned()); + bool reinit; + attach_nary_clause(*r, reinit); + if (!learned && reinit) { + TRACE("sat_reinit", tout << "adding to reinit stack: " << *r << "\n";); + push_reinit_stack(*r); + } + if (learned) + m_learned.push_back(r); + else + m_clauses.push_back(r); + return r; + } + + void solver::attach_nary_clause(clause & c, bool & reinit) { + reinit = false; + clause_offset cls_off = m_cls_allocator.get_offset(&c); + if (scope_lvl() > 0) { + if (c.is_learned()) { + unsigned w2_idx = select_learned_watch_lit(c); + std::swap(c[1], c[w2_idx]); + } + else { + unsigned w1_idx = select_watch_lit(c, 0); + std::swap(c[0], c[w1_idx]); + unsigned w2_idx = select_watch_lit(c, 1); + std::swap(c[1], c[w2_idx]); + } + + if (value(c[0]) == l_false) { + m_stats.m_propagate++; + assign(c[1], justification(cls_off)); + reinit = true; + } + else if (value(c[1]) == l_false) { + m_stats.m_propagate++; + assign(c[0], justification(cls_off)); + reinit = true; + } + } + unsigned some_idx = c.size() >> 1; + literal block_lit = c[some_idx]; + m_watches[(~c[0]).index()].push_back(watched(block_lit, cls_off)); + m_watches[(~c[1]).index()].push_back(watched(block_lit, cls_off)); + } + + void solver::attach_clause(clause & c, bool & reinit) { + SASSERT(c.size() > 2); + reinit = false; + if (c.size() == 3) + attach_ter_clause(c, reinit); + else + attach_nary_clause(c, reinit); + } + + /** + \brief Select a watch literal starting the search at the given position. + This method is only used for clauses created during the search. + + I use the following rules to select a watch literal. + + 1- select a literal l in idx >= starting_at such that value(l) = l_true, + and for all l' in idx' >= starting_at . value(l') = l_true implies lvl(l) <= lvl(l') + + The purpose of this rule is to make the clause inactive for as long as possible. A clause + is inactive when it contains a literal assigned to true. + + 2- if there isn't a literal assigned to true, then select an unassigned literal l in idx >= starting_at + + 3- if there isn't a literal l in idx >= starting_at such that value(l) = l_true or + value(l) = l_undef (that is, all literals at positions >= starting_at are assigned + to false), then peek the literal l such that for all l' starting at starting_at + lvl(l) >= lvl(l') + + Without rule 3, boolean propagation is incomplete, that is, it may miss possible propagations. + + \remark The method select_lemma_watch_lit is used to select the watch literal for regular learned clauses. + */ + unsigned solver::select_watch_lit(clause const & cls, unsigned starting_at) const { + SASSERT(cls.size() >= 2); + unsigned min_true_idx = UINT_MAX; + unsigned max_false_idx = UINT_MAX; + unsigned unknown_idx = UINT_MAX; + unsigned n = cls.size(); + for (unsigned i = starting_at; i < n; i++) { + literal l = cls[i]; + switch(value(l)) { + case l_false: + if (max_false_idx == UINT_MAX || lvl(l) > lvl(cls[max_false_idx])) + max_false_idx = i; + break; + case l_undef: + unknown_idx = i; + break; + case l_true: + if (min_true_idx == UINT_MAX || lvl(l) < lvl(cls[min_true_idx])) + min_true_idx = i; + break; + } + } + if (min_true_idx != UINT_MAX) + return min_true_idx; + if (unknown_idx != UINT_MAX) + return unknown_idx; + SASSERT(max_false_idx != UINT_MAX); + return max_false_idx; + } + + /** + \brief The learned clauses (lemmas) produced by the SAT solver + have the property that the first literal will be implied by it + after backtracking. All other literals are assigned to (or + implied to be) false when the learned clause is created. The + first watch literal will always be the first literal. The + second watch literal is computed by this method. It should be + the literal with the highest decision level. + + // TODO: do we really need this? strength the conflict resolution + */ + unsigned solver::select_learned_watch_lit(clause const & cls) const { + SASSERT(cls.size() >= 2); + unsigned max_false_idx = UINT_MAX; + unsigned num_lits = cls.size(); + for (unsigned i = 1; i < num_lits; i++) { + literal l = cls[i]; + lbool val = value(l); + SASSERT(val == l_false); + if (max_false_idx == UINT_MAX || lvl(l) > lvl(cls[max_false_idx])) + max_false_idx = i; + } + return max_false_idx; + } + + template + bool solver::simplify_clause_core(unsigned & num_lits, literal * lits) const { + std::sort(lits, lits+num_lits); + literal prev = null_literal; + unsigned i = 0; + unsigned j = 0; + for (; i < num_lits; i++) { + literal curr = lits[i]; + lbool val = value(curr); + if (!lvl0 && m_level[curr.var()] > 0) + val = l_undef; + switch (val) { + case l_false: + break; // ignore literal + case l_undef: + if (curr == ~prev) + return false; // clause is equivalent to true + if (curr != prev) { + prev = curr; + if (i != j) + lits[j] = lits[i]; + j++; + } + break; + case l_true: + return false; // clause is equivalent to true + } + } + num_lits = j; + return true; + } + + bool solver::simplify_clause(unsigned & num_lits, literal * lits) const { + if (scope_lvl() == 0) + return simplify_clause_core(num_lits, lits); + else + return simplify_clause_core(num_lits, lits); + } + + void solver::dettach_bin_clause(literal l1, literal l2, bool learned) { + get_wlist(~l1).erase(watched(l2, learned)); + get_wlist(~l2).erase(watched(l1, learned)); + } + + void solver::dettach_clause(clause & c) { + if (c.size() == 3) + dettach_ter_clause(c); + else + dettach_nary_clause(c); + } + + void solver::dettach_nary_clause(clause & c) { + clause_offset cls_off = get_offset(c); + erase_clause_watch(get_wlist(~c[0]), cls_off); + erase_clause_watch(get_wlist(~c[1]), cls_off); + } + + void solver::dettach_ter_clause(clause & c) { + erase_ternary_watch(get_wlist(~c[0]), c[1], c[2]); + erase_ternary_watch(get_wlist(~c[1]), c[0], c[2]); + erase_ternary_watch(get_wlist(~c[2]), c[0], c[1]); + } + + // ----------------------- + // + // Basic + // + // ----------------------- + + void solver::set_conflict(justification c, literal not_l) { + if (m_inconsistent) + return; + TRACE("sat_conflict", tout << "conflict\n";); + // int * p = 0; + // *p = 0; + m_inconsistent = true; + m_conflict = c; + m_not_l = not_l; + } + + void solver::assign_core(literal l, justification j) { + SASSERT(value(l) == l_undef); + TRACE("sat_assign_core", tout << l << "\n";); + if (scope_lvl() == 0) + j = justification(); // erase justification for level 0 + m_assignment[l.index()] = l_true; + m_assignment[(~l).index()] = l_false; + bool_var v = l.var(); + m_level[v] = scope_lvl(); + m_justification[v] = j; + m_phase[v] = static_cast(l.sign()); + m_assigned_since_gc[v] = true; + m_trail.push_back(l); + + if (m_ext && m_external[v]) + m_ext->asserted(l); + + SASSERT(!l.sign() || m_phase[v] == NEG_PHASE); + SASSERT(l.sign() || m_phase[v] == POS_PHASE); + + SASSERT(!l.sign() || value(v) == l_false); + SASSERT(l.sign() || value(v) == l_true); + SASSERT(value(l) == l_true); + SASSERT(value(~l) == l_false); + } + + lbool solver::status(clause const & c) const { + bool found_undef = false; + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) { + switch (value(c[i])) { + case l_true: + return l_true; + case l_undef: + found_undef = true; + break; + default: + break; + } + } + return found_undef ? l_undef : l_false; + } + + // ----------------------- + // + // Propagation + // + // ----------------------- + + bool solver::propagate_core(bool update) { + if (m_inconsistent) + return false; + literal l, not_l, l1, l2; + lbool val1, val2; + bool keep; + while (m_qhead < m_trail.size()) { + checkpoint(); + m_cleaner.dec(); + SASSERT(!m_inconsistent); + l = m_trail[m_qhead]; + TRACE("sat_propagate", tout << "propagating: " << l << "\n";); + m_qhead++; + not_l = ~l; + SASSERT(value(l) == l_true); + SASSERT(value(not_l) == l_false); + watch_list & wlist = m_watches[l.index()]; + m_asymm_branch.dec(wlist.size()); + m_probing.dec(wlist.size()); + watch_list::iterator it = wlist.begin(); + watch_list::iterator it2 = it; + watch_list::iterator end = wlist.end(); +#define CONFLICT_CLEANUP() { \ + for (; it != end; ++it, ++it2) \ + *it2 = *it; \ + wlist.set_end(it2); \ + } + for (; it != end; ++it) { + switch (it->get_kind()) { + case watched::BINARY: + l1 = it->get_literal(); + switch (value(l1)) { + case l_false: + CONFLICT_CLEANUP(); + set_conflict(justification(not_l), ~l1); + return false; + case l_undef: + m_stats.m_bin_propagate++; + assign_core(l1, justification(not_l)); + break; + case l_true: + break; // skip + } + *it2 = *it; + it2++; + break; + case watched::TERNARY: + l1 = it->get_literal1(); + l2 = it->get_literal2(); + val1 = value(l1); + val2 = value(l2); + if (val1 == l_false && val2 == l_undef) { + m_stats.m_ter_propagate++; + assign_core(l2, justification(l1, not_l)); + } + else if (val1 == l_undef && val2 == l_false) { + m_stats.m_ter_propagate++; + assign_core(l1, justification(l2, not_l)); + } + else if (val1 == l_false && val2 == l_false) { + CONFLICT_CLEANUP(); + set_conflict(justification(l1, not_l), ~l2); + return false; + } + *it2 = *it; + it2++; + break; + case watched::CLAUSE: { + if (value(it->get_blocked_literal()) == l_true) { + *it2 = *it; + it2++; + break; + } + clause_offset cls_off = it->get_clause_offset(); + clause & c = *(m_cls_allocator.get_clause(cls_off)); + if (c[0] == not_l) + std::swap(c[0], c[1]); + CTRACE("propagate_bug", c[1] != not_l, tout << "l: " << l << " " << c << "\n";); + SASSERT(c[1] == not_l); + if (value(c[0]) == l_true) { + it2->set_clause(c[0], cls_off); + it2++; + break; + } + literal * l_it = c.begin() + 2; + literal * l_end = c.end(); + for (; l_it != l_end; ++l_it) { + if (value(*l_it) != l_false) { + c[1] = *l_it; + *l_it = not_l; + m_watches[(~c[1]).index()].push_back(watched(c[0], cls_off)); + goto end_clause_case; + } + } + SASSERT(value(c[0]) == l_false || value(c[0]) == l_undef); + if (value(c[0]) == l_false) { + c.mark_used(); + CONFLICT_CLEANUP(); + set_conflict(justification(cls_off)); + return false; + } + else { + *it2 = *it; + it2++; + m_stats.m_propagate++; + c.mark_used(); + assign_core(c[0], justification(cls_off)); +#ifdef UPDATE_GLUE + if (update && c.is_learned() && c.glue() > 2) { + unsigned glue = num_diff_levels(c.size(), c.begin()); + if (glue+1 < c.glue()) { + c.set_glue(glue); + } + } +#endif + } + end_clause_case: + break; + } + case watched::EXT_CONSTRAINT: + SASSERT(m_ext); + m_ext->propagate(l, it->get_ext_constraint_idx(), keep); + if (keep) { + *it2 = *it; + it2++; + } + if (m_inconsistent) { + CONFLICT_CLEANUP(); + return false; + } + break; + default: + UNREACHABLE(); + break; + } + } + wlist.set_end(it2); + } + SASSERT(!m_inconsistent); + return true; + } + + bool solver::propagate(bool update) { + bool r = propagate_core(update); + CASSERT("sat_propagate", check_invariant()); + CASSERT("sat_missed_prop", check_missed_propagation()); + return r; + } + + // ----------------------- + // + // Search + // + // ----------------------- + lbool solver::check() { + SASSERT(scope_lvl() == 0); +#ifdef CLONE_BEFORE_SOLVING + if (m_mc.empty()) { + m_clone = alloc(solver, m_params, 0 /* do not clone extension */); + SASSERT(m_clone); + } +#endif + try { + if (inconsistent()) return l_false; + init_search(); + propagate(false); + if (inconsistent()) return l_false; + cleanup(); + if (m_config.m_max_conflicts > 0 && m_config.m_burst_search > 0) { + m_restart_threshold = m_config.m_burst_search; + lbool r = bounded_search(); + if (r != l_undef) + return r; + pop(scope_lvl()); + m_conflicts_since_restart = 0; + m_restart_threshold = m_config.m_restart_initial; + } + + // iff3_finder(*this)(); + simplify_problem(); + + if (inconsistent()) return l_false; + m_next_simplify = m_config.m_restart_initial * m_config.m_simplify_mult1; + + if (m_config.m_max_conflicts == 0) { + IF_VERBOSE(SAT_VB_LVL, verbose_stream() << "\"abort: max-conflicts = 0\"\n";); + return l_undef; + } + + while (true) { + SASSERT(!inconsistent()); + + lbool r = bounded_search(); + if (r != l_undef) + return r; + + if (m_conflicts > m_config.m_max_conflicts) { + IF_VERBOSE(SAT_VB_LVL, verbose_stream() << "\"abort: max-conflicts = " << m_conflicts << "\"\n";); + return l_undef; + } + + restart(); + if (m_conflicts >= m_next_simplify) { + simplify_problem(); + m_next_simplify = static_cast(m_conflicts * m_config.m_simplify_mult2); + if (m_next_simplify > m_conflicts + m_config.m_simplify_max) + m_next_simplify = m_conflicts + m_config.m_simplify_max; + } + gc(); + } + } + catch (abort_solver) { + return l_undef; + } + } + + bool_var solver::next_var() { + bool_var next; + + if (m_rand() < static_cast(m_config.m_random_freq * random_gen::max_value())) { + if (num_vars() == 0) + return null_bool_var; + next = m_rand() % num_vars(); + TRACE("random_split", tout << "next: " << next << " value(next): " << value(next) << "\n";); + if (value(next) == l_undef && !was_eliminated(next)) + return next; + } + + while (!m_case_split_queue.empty()) { + next = m_case_split_queue.next_var(); + if (value(next) == l_undef && !was_eliminated(next)) + return next; + } + + return null_bool_var; + } + + bool solver::decide() { + bool_var next = next_var(); + if (next == null_bool_var) + return false; + push(); + m_stats.m_decision++; + lbool phase = m_ext ? m_ext->get_phase(next) : l_undef; + + if (phase == l_undef) { + switch (m_config.m_phase) { + case PS_ALWAYS_TRUE: + phase = l_true; + break; + case PS_ALWAYS_FALSE: + phase = l_false; + break; + case PS_CACHING: + if (m_phase_cache_on && m_phase[next] != PHASE_NOT_AVAILABLE) + phase = m_phase[next] == POS_PHASE ? l_true : l_false; + else + phase = l_false; + break; + case PS_RANDOM: + phase = to_lbool((m_rand() % 2) == 0); + break; + default: + UNREACHABLE(); + phase = l_false; + break; + } + } + + SASSERT(phase != l_undef); + literal next_lit(next, phase == l_false); + assign(next_lit, justification()); + TRACE("sat_decide", tout << "next-case-split: " << next_lit << "\n";); + return true; + } + + lbool solver::bounded_search() { + while (true) { + checkpoint(); + while (true) { + propagate(true); + if (!inconsistent()) + break; + if (!resolve_conflict()) + return l_false; + if (m_conflicts > m_config.m_max_conflicts) + return l_undef; + if (m_conflicts_since_restart > m_restart_threshold) + return l_undef; + if (scope_lvl() == 0) { + cleanup(); // cleaner may propagate frozen clauses + if (inconsistent()) + return l_false; + gc(); + } + } + + gc(); + + if (!decide()) { + if (m_ext) { + switch (m_ext->check()) { + case CR_DONE: + mk_model(); + return l_true; + case CR_CONTINUE: + break; + case CR_GIVEUP: + throw abort_solver(); + } + } + else { + mk_model(); + return l_true; + } + } + } + } + + void solver::init_search() { + m_phase_counter = 0; + m_phase_cache_on = false; + m_conflicts = 0; + m_conflicts_since_restart = 0; + m_restart_threshold = m_config.m_restart_initial; + m_luby_idx = 1; + m_conflicts_since_gc = 0; + m_gc_threshold = m_config.m_gc_initial; + m_min_d_tk = 1.0; + m_next_simplify = 0; + m_stopwatch.reset(); + m_stopwatch.start(); + } + + /** + \brief Apply all simplifications. + */ + void solver::simplify_problem() { + SASSERT(scope_lvl() == 0); + + m_cleaner(); + CASSERT("sat_simplify_bug", check_invariant()); + + m_scc(); + CASSERT("sat_simplify_bug", check_invariant()); + + m_simplifier(false); + CASSERT("sat_simplify_bug", check_invariant()); + CASSERT("sat_missed_prop", check_missed_propagation()); + + if (!m_learned.empty()) { + m_simplifier(true); + CASSERT("sat_missed_prop", check_missed_propagation()); + CASSERT("sat_simplify_bug", check_invariant()); + } + + sort_watch_lits(); + CASSERT("sat_simplify_bug", check_invariant()); + + m_probing(); + CASSERT("sat_missed_prop", check_missed_propagation()); + CASSERT("sat_simplify_bug", check_invariant()); + + m_asymm_branch(); + CASSERT("sat_missed_prop", check_missed_propagation()); + CASSERT("sat_simplify_bug", check_invariant()); + + if (m_ext) { + m_ext->clauses_modifed(); + m_ext->simplify(); + } + } + + void solver::sort_watch_lits() { + vector::iterator it = m_watches.begin(); + vector::iterator end = m_watches.end(); + for (; it != end; ++it) { + watch_list & wlist = *it; + std::stable_sort(wlist.begin(), wlist.end(), watched_lt()); + } + } + + void solver::mk_model() { + m_model.reset(); + unsigned num = num_vars(); + m_model.resize(num, l_undef); + for (bool_var v = 0; v < num; v++) { + if (!was_eliminated(v)) + m_model[v] = value(v); + } + TRACE("sat_mc_bug", m_mc.display(tout);); + m_mc(m_model); + TRACE("sat_model", for (bool_var v = 0; v < num; v++) tout << v << ": " << m_model[v] << "\n";); + +#ifndef _EXTERNAL_RELEASE + IF_VERBOSE(SAT_VB_LVL, verbose_stream() << "\"checking model\"\n";); + if (!check_model(m_model)) + throw solver_exception("check model failed"); + if (m_clone) { + IF_VERBOSE(SAT_VB_LVL, verbose_stream() << "\"checking model (on original set of clauses)\"\n";); + if (!m_clone->check_model(m_model)) + throw solver_exception("check model failed (for cloned solver)"); + } +#endif + } + + bool solver::check_model(model const & m) const { + bool ok = true; + clause_vector const * vs[2] = { &m_clauses, &m_learned }; + for (unsigned i = 0; i < 2; i++) { + clause_vector const & cs = *(vs[i]); + clause_vector::const_iterator it = cs.begin(); + clause_vector::const_iterator end = cs.end(); + for (; it != end; ++it) { + clause const & c = *(*it); + if (!c.satisfied_by(m)) { + TRACE("sat_model_bug", tout << "failed: " << c << "\n";); + ok = false; + } + } + } + vector::const_iterator it = m_watches.begin(); + vector::const_iterator end = m_watches.end(); + for (unsigned l_idx = 0; it != end; ++it, ++l_idx) { + literal l = ~to_literal(l_idx); + if (value_at(l, m) != l_true) { + watch_list const & wlist = *it; + watch_list::const_iterator it2 = wlist.begin(); + watch_list::const_iterator end2 = wlist.end(); + for (; it2 != end2; ++it2) { + if (!it2->is_binary_clause()) + continue; + literal l2 = it2->get_literal(); + if (value_at(l2, m) != l_true) { + TRACE("sat_model_bug", tout << "failed binary: " << l << " " << l2 << " learned: " << it2->is_learned() << "\n";); + ok = false; + } + } + } + } + if (!m_mc.check_model(m)) + ok = false; + CTRACE("sat_model_bug", !ok, tout << m << "\n";); + return ok; + } + + void solver::restart() { + m_stats.m_restart++; + IF_VERBOSE(1, + verbose_stream() << "(sat-restart :conflicts " << m_stats.m_conflict << " :decisions " << m_stats.m_decision + << " :restarts " << m_stats.m_restart << mk_stat(*this) + << " :time " << std::fixed << std::setprecision(2) << m_stopwatch.get_current_seconds() << ")\n";); + IF_VERBOSE(30, display_status(verbose_stream());); + pop(scope_lvl()); + m_conflicts_since_restart = 0; + switch (m_config.m_restart) { + case RS_GEOMETRIC: + m_restart_threshold = static_cast(m_restart_threshold * m_config.m_restart_factor); + break; + case RS_LUBY: + m_luby_idx++; + m_restart_threshold = m_config.m_restart_initial * get_luby(m_luby_idx); + break; + default: + UNREACHABLE(); + break; + } + CASSERT("sat_restart", check_invariant()); + } + + // ----------------------- + // + // GC + // + // ----------------------- + + void solver::gc() { + if (m_conflicts_since_gc <= m_gc_threshold) + return; + CASSERT("sat_gc_bug", check_invariant()); + switch (m_config.m_gc_strategy) { + case GC_GLUE: + gc_glue(); + break; + case GC_PSM: + gc_psm(); + break; + case GC_GLUE_PSM: + gc_glue_psm(); + break; + case GC_PSM_GLUE: + gc_psm_glue(); + break; + case GC_DYN_PSM: + if (m_scope_lvl != 0) + return; + gc_dyn_psm(); + break; + default: + UNREACHABLE(); + break; + } + m_conflicts_since_gc = 0; + m_gc_threshold += m_config.m_gc_increment; + CASSERT("sat_gc_bug", check_invariant()); + } + + /** + \brief Lex on (glue, size) + */ + struct glue_lt { + bool operator()(clause const * c1, clause const * c2) const { + if (c1->glue() < c2->glue()) return true; + return c1->glue() == c2->glue() && c1->size() < c2->size(); + } + }; + + /** + \brief Lex on (psm, size) + */ + struct psm_lt { + bool operator()(clause const * c1, clause const * c2) const { + if (c1->psm() < c2->psm()) return true; + return c1->psm() == c2->psm() && c1->size() < c2->size(); + } + }; + + /** + \brief Lex on (glue, psm, size) + */ + struct glue_psm_lt { + bool operator()(clause const * c1, clause const * c2) const { + if (c1->glue() < c2->glue()) return true; + if (c1->glue() > c2->glue()) return false; + if (c1->psm() < c2->psm()) return true; + if (c1->psm() > c2->psm()) return false; + return c1->size() < c2->size(); + } + }; + + /** + \brief Lex on (psm, glue, size) + */ + struct psm_glue_lt { + bool operator()(clause const * c1, clause const * c2) const { + if (c1->psm() < c2->psm()) return true; + if (c1->psm() > c2->psm()) return false; + if (c1->glue() < c2->glue()) return true; + if (c1->glue() > c2->glue()) return false; + return c1->size() < c2->size(); + } + }; + + void solver::gc_glue() { + std::stable_sort(m_learned.begin(), m_learned.end(), glue_lt()); + gc_half("glue"); + } + + void solver::gc_psm() { + save_psm(); + std::stable_sort(m_learned.begin(), m_learned.end(), psm_lt()); + gc_half("psm"); + } + + void solver::gc_glue_psm() { + save_psm(); + std::stable_sort(m_learned.begin(), m_learned.end(), glue_psm_lt()); + gc_half("glue-psm"); + } + + void solver::gc_psm_glue() { + save_psm(); + std::stable_sort(m_learned.begin(), m_learned.end(), psm_glue_lt()); + gc_half("psm-glue"); + } + + /** + \brief Compute the psm of all learned clauses. + */ + void solver::save_psm() { + clause_vector::iterator it = m_learned.begin(); + clause_vector::iterator end = m_learned.end(); + for (; it != end; ++it) { + clause & c = *(*it); + c.set_psm(psm(c)); + } + } + + /** + \brief GC (the second) half of the clauses in the database. + */ + void solver::gc_half(char const * st_name) { + unsigned sz = m_learned.size(); + unsigned new_sz = sz/2; + unsigned j = new_sz; + for (unsigned i = new_sz; i < sz; i++) { + clause & c = *(m_learned[i]); + if (can_delete(c)) { + dettach_clause(c); + del_clause(c); + } + else { + m_learned[j] = &c; + j++; + } + } + new_sz = j; + m_stats.m_gc_clause += sz - new_sz; + m_learned.shrink(new_sz); + IF_VERBOSE(SAT_VB_LVL, verbose_stream() << "(sat-gc :strategy " << st_name << " :deleted " << (sz - new_sz) << ")\n";); + } + + /** + \brief Use gc based on dynamic psm. Clauses are initially frozen. + */ + void solver::gc_dyn_psm() { + // To do gc at scope_lvl() > 0, I will need to use the reinitialization stack, or live with the fact + // that I may miss some propagations for reactivated clauses. + SASSERT(scope_lvl() == 0); + // compute + // d_tk + unsigned h = 0; + unsigned V_tk = 0; + for (bool_var v = 0; v < num_vars(); v++) { + if (m_assigned_since_gc[v]) { + V_tk++; + m_assigned_since_gc[v] = false; + } + if (m_phase[v] != m_prev_phase[v]) { + h++; + m_prev_phase[v] = m_phase[v]; + } + } + double d_tk = V_tk == 0 ? static_cast(num_vars() + 1) : static_cast(h)/static_cast(V_tk); + if (d_tk < m_min_d_tk) + m_min_d_tk = d_tk; + TRACE("sat_frozen", tout << "m_min_d_tk: " << m_min_d_tk << "\n";); + unsigned frozen = 0; + unsigned deleted = 0; + unsigned activated = 0; + clause_vector::iterator it = m_learned.begin(); + clause_vector::iterator it2 = it; + clause_vector::iterator end = m_learned.end(); + for (; it != end; ++it) { + clause & c = *(*it); + if (!c.frozen()) { + // Active clause + if (c.glue() > m_config.m_gc_small_lbd) { + // I never delete clauses with small lbd + if (c.was_used()) { + c.reset_inact_rounds(); + } + else { + c.inc_inact_rounds(); + if (c.inact_rounds() > m_config.m_gc_k) { + dettach_clause(c); + del_clause(c); + m_stats.m_gc_clause++; + deleted++; + continue; + } + } + c.unmark_used(); + if (psm(c) > static_cast(c.size() * m_min_d_tk)) { + // move to frozen; + TRACE("sat_frozen", tout << "freezing size: " << c.size() << " psm: " << psm(c) << " " << c << "\n";); + dettach_clause(c); + c.reset_inact_rounds(); + c.freeze(); + m_num_frozen++; + frozen++; + } + } + } + else { + // frozen clause + clause & c = *(*it); + if (psm(c) <= static_cast(c.size() * m_min_d_tk)) { + c.unfreeze(); + m_num_frozen--; + activated++; + if (!activate_frozen_clause(c)) { + // clause was satisfied, reduced to a conflict, unit or binary clause. + del_clause(c); + continue; + } + } + else { + c.inc_inact_rounds(); + if (c.inact_rounds() > m_config.m_gc_k) { + m_num_frozen--; + del_clause(c); + m_stats.m_gc_clause++; + deleted++; + continue; + } + } + } + *it2 = *it; + ++it2; + } + m_learned.set_end(it2); + IF_VERBOSE(SAT_VB_LVL, verbose_stream() << "(sat-gc :d_tk " << d_tk << " :min-d_tk " << m_min_d_tk << + " :frozen " << frozen << " :activated " << activated << " :deleted " << deleted << ")\n";); + } + + // return true if should keep the clause, and false if we should delete it. + bool solver::activate_frozen_clause(clause & c) { + TRACE("sat_gc", tout << "reactivating:\n" << c << "\n";); + SASSERT(scope_lvl() == 0); + // do some cleanup + unsigned sz = c.size(); + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + literal l = c[i]; + switch (value(l)) { + case l_true: + return false; + case l_false: + break; + case l_undef: + c[j] = c[i]; + j++; + break; + } + } + TRACE("sat_gc", tout << "after cleanup:\n" << mk_lits_pp(j, c.begin()) << "\n";); + unsigned new_sz = j; + switch (new_sz) { + case 0: + set_conflict(justification()); + return false; + case 1: + assign(c[0], justification()); + return false; + case 2: + mk_bin_clause(c[0], c[1], true); + return false; + default: + c.shrink(new_sz); + attach_clause(c); + return true; + } + } + + /** + \brief Compute phase saving measure for the given clause. + */ + unsigned solver::psm(clause const & c) const { + unsigned r = 0; + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) { + literal l = c[i]; + if (l.sign()) { + if (m_phase[l.var()] == NEG_PHASE) + r++; + } + else { + if (m_phase[l.var()] == POS_PHASE) + r++; + } + } + return r; + } + + // ----------------------- + // + // Conflict resolution + // + // ----------------------- + + bool solver::resolve_conflict() { + while (true) { + bool r = resolve_conflict_core(); + // after pop, clauses are reinitialized, this may trigger another conflict. + if (!r) + return false; + if (!inconsistent()) + return true; + } + CASSERT("sat_check_marks", check_marks()); + } + + bool solver::resolve_conflict_core() { + TRACE("sat_conflict", tout << "conflict detected\n";); + + m_stats.m_conflict++; + m_conflicts++; + m_conflicts_since_restart++; + m_conflicts_since_gc++; + + m_conflict_lvl = get_max_lvl(m_not_l, m_conflict); + if (m_conflict_lvl == 0) + return false; + m_lemma.reset(); + + forget_phase_of_vars(m_conflict_lvl); + + unsigned idx = skip_literals_above_conflict_level(); + // save space for first uip + m_lemma.push_back(null_literal); + + unsigned num_marks = 0; + if (m_not_l != null_literal) { + TRACE("sat_conflict", tout << "not_l: " << m_not_l << "\n";); + process_antecedent(m_not_l, num_marks); + } + + literal consequent = m_not_l; + justification js = m_conflict; + + do { + TRACE("sat_conflict_detail", tout << "processing consequent: " << consequent << "\n"; + tout << "num_marks: " << num_marks << ", js kind: " << js.get_kind() << "\n";); + switch (js.get_kind()) { + case justification::NONE: + break; + case justification::BINARY: + process_antecedent(~(js.get_literal()), num_marks); + break; + case justification::TERNARY: + process_antecedent(~(js.get_literal1()), num_marks); + process_antecedent(~(js.get_literal2()), num_marks); + break; + case justification::CLAUSE: { + clause & c = *(m_cls_allocator.get_clause(js.get_clause_offset())); + unsigned i = 0; + if (consequent != null_literal) { + SASSERT(c[0] == consequent || c[1] == consequent); + if (c[0] == consequent) { + i = 1; + } + else { + process_antecedent(~c[0], num_marks); + i = 2; + } + } + unsigned sz = c.size(); + for (; i < sz; i++) + process_antecedent(~c[i], num_marks); + break; + } + case justification::EXT_JUSTIFICATION: { + fill_ext_antecedents(consequent, js); + literal_vector::iterator it = m_ext_antecedents.begin(); + literal_vector::iterator end = m_ext_antecedents.end(); + for (; it != end; ++it) + process_antecedent(*it, num_marks); + break; + } + default: + UNREACHABLE(); + break; + } + + while (true) { + literal l = m_trail[idx]; + if (is_marked(l.var())) + break; + SASSERT(idx > 0); + idx--; + } + + consequent = m_trail[idx]; + bool_var c_var = consequent.var(); + SASSERT(lvl(consequent) == m_conflict_lvl); + js = m_justification[c_var]; + idx--; + num_marks--; + reset_mark(c_var); + } + while (num_marks > 0); + + m_lemma[0] = ~consequent; + TRACE("sat_lemma", tout << "new lemma size: " << m_lemma.size() << "\n" << m_lemma << "\n";); + + if (m_config.m_minimize_lemmas) { + minimize_lemma(); + reset_lemma_var_marks(); + if (m_config.m_dyn_sub_res) + dyn_sub_res(); + TRACE("sat_lemma", tout << "new lemma (after minimization) size: " << m_lemma.size() << "\n" << m_lemma << "\n";); + } + + literal_vector::iterator it = m_lemma.begin(); + literal_vector::iterator end = m_lemma.end(); + unsigned new_scope_lvl = 0; + ++it; + for(; it != end; ++it) { + bool_var var = (*it).var(); + new_scope_lvl = std::max(new_scope_lvl, lvl(var)); + } + + unsigned glue = num_diff_levels(m_lemma.size(), m_lemma.c_ptr()); + + pop(m_scope_lvl - new_scope_lvl); + TRACE("sat_conflict_detail", display(tout); tout << "assignment:\n"; display_assignment(tout);); + clause * lemma = mk_clause_core(m_lemma.size(), m_lemma.c_ptr(), true); + if (lemma) { + lemma->set_glue(glue); + } + decay_activity(); + updt_phase_counters(); + return true; + } + + unsigned solver::get_max_lvl(literal consequent, justification js) { + if (!m_ext) + return scope_lvl(); + + if (scope_lvl() == 0) + return 0; + + unsigned r = 0; + + if (consequent != null_literal) + r = lvl(consequent); + + switch (js.get_kind()) { + case justification::NONE: + break; + case justification::BINARY: + r = std::max(r, lvl(js.get_literal())); + break; + case justification::TERNARY: + r = std::max(r, lvl(js.get_literal1())); + r = std::max(r, lvl(js.get_literal2())); + break; + case justification::CLAUSE: { + clause & c = *(m_cls_allocator.get_clause(js.get_clause_offset())); + unsigned i = 0; + if (consequent != null_literal) { + SASSERT(c[0] == consequent || c[1] == consequent); + if (c[0] == consequent) { + i = 1; + } + else { + r = std::max(r, lvl(c[0])); + i = 2; + } + } + unsigned sz = c.size(); + for (; i < sz; i++) + r = std::max(r, lvl(c[i])); + break; + } + case justification::EXT_JUSTIFICATION: { + fill_ext_antecedents(consequent, js); + literal_vector::iterator it = m_ext_antecedents.begin(); + literal_vector::iterator end = m_ext_antecedents.end(); + for (; it != end; ++it) + r = std::max(r, lvl(*it)); + break; + } + default: + UNREACHABLE(); + break; + } + return r; + } + + /** + \brief Skip literals from levels above m_conflict_lvl. + It returns an index idx such that lvl(m_trail[idx]) <= m_conflict_lvl, and + for all idx' > idx, lvl(m_trail[idx']) > m_conflict_lvl + */ + unsigned solver::skip_literals_above_conflict_level() { + unsigned idx = m_trail.size(); + if (idx == 0) { + return idx; + } + idx--; + // skip literals from levels above the conflict level + while (lvl(m_trail[idx]) > m_conflict_lvl) { + SASSERT(idx > 0); + idx--; + } + return idx; + } + + void solver::process_antecedent(literal antecedent, unsigned & num_marks) { + bool_var var = antecedent.var(); + unsigned var_lvl = lvl(var); + SASSERT(var < num_vars()); + if (!is_marked(var) && var_lvl > 0) { + mark(var); + inc_activity(var); + if (var_lvl == m_conflict_lvl) + num_marks++; + else + m_lemma.push_back(~antecedent); + } + } + + /** + \brief js is an external justification. Collect its antecedents and store at m_ext_antecedents. + */ + void solver::fill_ext_antecedents(literal consequent, justification js) { + SASSERT(js.is_ext_justification()); + SASSERT(m_ext); + m_ext_antecedents.reset(); + m_ext->get_antecedents(consequent, js.get_ext_justification_idx(), m_ext_antecedents); + } + + void solver::forget_phase_of_vars(unsigned from_lvl) { + unsigned head = from_lvl == 0 ? 0 : m_scopes[from_lvl - 1].m_trail_lim; + unsigned sz = m_trail.size(); + for (unsigned i = head; i < sz; i++) { + literal l = m_trail[i]; + bool_var v = l.var(); + TRACE("forget_phase", tout << "forgeting phase of l: " << l << "\n";); + m_phase[v] = PHASE_NOT_AVAILABLE; + } + } + + void solver::updt_phase_counters() { + m_phase_counter++; + if (m_phase_cache_on) { + if (m_phase_counter >= m_config.m_phase_caching_on) { + m_phase_counter = 0; + m_phase_cache_on = false; + } + } + else { + if (m_phase_counter >= m_config.m_phase_caching_off) { + m_phase_counter = 0; + m_phase_cache_on = true; + } + } + } + + /** + \brief Return the number of different levels in lits. + All literals in lits must be assigned. + */ + unsigned solver::num_diff_levels(unsigned num, literal const * lits) { + m_diff_levels.reserve(scope_lvl() + 1, false); + unsigned r = 0; + for (unsigned i = 0; i < num; i++) { + SASSERT(value(lits[i]) != l_undef); + unsigned lit_lvl = lvl(lits[i]); + if (m_diff_levels[lit_lvl] == false) { + m_diff_levels[lit_lvl] = true; + r++; + } + } + // reset m_diff_levels. + for (unsigned i = 0; i < num; i++) + m_diff_levels[lvl(lits[i])] = false; + return r; + } + + /** + \brief Process an antecedent for lemma minimization. + */ + bool solver::process_antecedent_for_minimization(literal antecedent) { + bool_var var = antecedent.var(); + unsigned var_lvl = lvl(var); + if (!is_marked(var) && var_lvl > 0) { + if (m_lvl_set.may_contain(var_lvl)) { + mark(var); + m_unmark.push_back(var); + m_lemma_min_stack.push_back(var); + } + else { + return false; + } + } + return true; + } + + /** + \brief Return true if lit is implied by other marked literals + and/or literals assigned at the base level. + The set lvl_set is used as an optimization. + The idea is to stop the recursive search with a failure + as soon as we find a literal assigned in a level that is not in lvl_set. + */ + bool solver::implied_by_marked(literal lit) { + m_lemma_min_stack.reset(); // avoid recursive function + m_lemma_min_stack.push_back(lit.var()); + unsigned old_size = m_unmark.size(); + + while (!m_lemma_min_stack.empty()) { + bool_var var = m_lemma_min_stack.back(); + m_lemma_min_stack.pop_back(); + justification js = m_justification[var]; + switch(js.get_kind()) { + case justification::NONE: + // it is a decision variable from a previous scope level + if (lvl(var) > 0) { + reset_unmark(old_size); + return false; + } + break; + case justification::BINARY: + if (!process_antecedent_for_minimization(~(js.get_literal()))) { + reset_unmark(old_size); + return false; + } + break; + case justification::TERNARY: + if (!process_antecedent_for_minimization(~(js.get_literal1())) || + !process_antecedent_for_minimization(~(js.get_literal2()))) { + reset_unmark(old_size); + return false; + } + break; + case justification::CLAUSE: { + clause & c = *(m_cls_allocator.get_clause(js.get_clause_offset())); + unsigned i = 0; + if (c[0].var() == var) { + i = 1; + } + else { + SASSERT(c[1].var() == var); + if (!process_antecedent_for_minimization(~c[0])) { + reset_unmark(old_size); + return false; + } + i = 2; + } + unsigned sz = c.size(); + for (; i < sz; i++) { + if (!process_antecedent_for_minimization(~c[i])) { + reset_unmark(old_size); + return false; + } + } + break; + } + case justification::EXT_JUSTIFICATION: { + literal consequent(var, value(var) == l_false); + fill_ext_antecedents(consequent, js); + literal_vector::iterator it = m_ext_antecedents.begin(); + literal_vector::iterator end = m_ext_antecedents.end(); + for (; it != end; ++it) { + if (!process_antecedent_for_minimization(*it)) { + reset_unmark(old_size); + return false; + } + } + break; + } + default: + UNREACHABLE(); + break; + } + } + return true; + } + + /** + \brief Restore the size of m_unmark to old_size, and + unmark variables at positions [old_size, m_unmark.size()). + */ + void solver::reset_unmark(unsigned old_size) { + unsigned curr_size = m_unmark.size(); + for(unsigned i = old_size; i < curr_size; i++) + reset_mark(m_unmark[i]); + m_unmark.shrink(old_size); + } + + /** + \brief Store the levels of the literals at m_lemma in the + approximated set m_lvl_set. + */ + void solver::updt_lemma_lvl_set() { + m_lvl_set.reset(); + literal_vector::const_iterator it = m_lemma.begin(); + literal_vector::const_iterator end = m_lemma.end(); + for(; it != end; ++it) + m_lvl_set.insert(lvl(*it)); + } + + /** + \brief Minimize the number of literals in m_lemma. The main idea is to remove + literals that are implied by other literals in m_lemma and/or literals + assigned at level 0. + */ + void solver::minimize_lemma() { + m_unmark.reset(); + updt_lemma_lvl_set(); + + unsigned sz = m_lemma.size(); + unsigned i = 1; // the first literal is the FUIP + unsigned j = 1; + for (; i < sz; i++) { + literal l = m_lemma[i]; + if (implied_by_marked(l)) { + m_unmark.push_back(l.var()); + } + else { + if (j != i) { + m_lemma[j] = m_lemma[i]; + } + j++; + } + } + + reset_unmark(0); + m_lemma.shrink(j); + m_stats.m_minimized_lits += sz - j; + } + + /** + \brief Reset the mark of the variables in the current lemma. + */ + void solver::reset_lemma_var_marks() { + literal_vector::iterator it = m_lemma.begin(); + literal_vector::iterator end = m_lemma.end(); + SASSERT(!is_marked((*it).var())); + ++it; + for(; it != end; ++it) { + bool_var var = (*it).var(); + reset_mark(var); + } + } + + /** + \brief Apply dynamic subsumption resolution to new lemma. + Only binary and ternary clauses are used. + */ + void solver::dyn_sub_res() { + unsigned sz = m_lemma.size(); + for (unsigned i = 0; i < sz; i++) { + mark_lit(m_lemma[i]); + } + + for (unsigned i = 0; i < sz; i++) { + literal l = m_lemma[i]; + if (!is_marked_lit(l)) + continue; // literal was eliminated + // first use watch lists + watch_list const & wlist = get_wlist(~l); + watch_list::const_iterator it = wlist.begin(); + watch_list::const_iterator end = wlist.end(); + for (; it != end; ++it) { + if (it->is_binary_clause()) { + literal l2 = it->get_literal(); + if (is_marked_lit(~l2)) { + // eliminate ~l2 from lemma because we have the clause l \/ l2 + unmark_lit(~l2); + } + } + else if (it->is_ternary_clause()) { + literal l2 = it->get_literal1(); + literal l3 = it->get_literal2(); + if (is_marked_lit(l2) && is_marked_lit(~l3)) { + // eliminate ~l3 from lemma because we have the clause l \/ l2 \/ l3 + unmark_lit(~l3); + } + else if (is_marked_lit(~l2) && is_marked_lit(l3)) { + // eliminate ~l2 from lemma because we have the clause l \/ l2 \/ l3 + unmark_lit(~l2); + } + } + else { + // May miss some binary/ternary clauses, but that is ok. + // I sort the watch lists at every simplification round. + break; + } + } + // try to use cached implication if available + literal_vector * implied_lits = m_probing.cached_implied_lits(~l); + if (implied_lits) { + literal_vector::iterator it = implied_lits->begin(); + literal_vector::iterator end = implied_lits->end(); + for (; it != end; ++it) { + literal l2 = *it; + if (is_marked_lit(~l2)) { + // eliminate ~l2 from lemma because we have the clause l \/ l2 + unmark_lit(~l2); + } + } + } + } + + // can't eliminat FUIP + SASSERT(is_marked_lit(m_lemma[0])); + + unsigned j = 0; + for (unsigned i = 0; i < sz; i++) { + literal l = m_lemma[i]; + if (is_marked_lit(l)) { + unmark_lit(l); + m_lemma[j] = l; + j++; + } + } + + m_stats.m_dyn_sub_res += sz - j; + + SASSERT(j >= 1); + m_lemma.shrink(j); + } + + + // ----------------------- + // + // Backtracking + // + // ----------------------- + void solver::push() { + SASSERT(!inconsistent()); + SASSERT(m_qhead == m_trail.size()); + m_scopes.push_back(scope()); + scope & s = m_scopes.back(); + m_scope_lvl++; + s.m_trail_lim = m_trail.size(); + s.m_clauses_to_reinit_lim = m_clauses_to_reinit.size(); + s.m_inconsistent = m_inconsistent; + if (m_ext) + m_ext->push(); + } + + void solver::pop(unsigned num_scopes) { + if (num_scopes == 0) + return; + if (m_ext) + m_ext->pop(num_scopes); + SASSERT(num_scopes <= scope_lvl()); + unsigned new_lvl = scope_lvl() - num_scopes; + scope & s = m_scopes[new_lvl]; + m_inconsistent = false; + unassign_vars(s.m_trail_lim); + m_scope_lvl -= num_scopes; + m_scopes.shrink(new_lvl); + reinit_clauses(s.m_clauses_to_reinit_lim); + } + + void solver::unassign_vars(unsigned old_sz) { + SASSERT(old_sz <= m_trail.size()); + unsigned i = m_trail.size(); + while (i != old_sz) { + --i; + literal l = m_trail[i]; + m_assignment[l.index()] = l_undef; + m_assignment[(~l).index()] = l_undef; + bool_var v = l.var(); + SASSERT(value(v) == l_undef); + m_case_split_queue.unassign_var_eh(v); + } + m_trail.shrink(old_sz); + m_qhead = old_sz; + SASSERT(m_qhead == m_trail.size()); + } + + void solver::reinit_clauses(unsigned old_sz) { + unsigned sz = m_clauses_to_reinit.size(); + SASSERT(old_sz <= sz); + unsigned j = old_sz; + for (unsigned i = old_sz; i < sz; i++) { + clause_wrapper cw = m_clauses_to_reinit[i]; + bool reinit = false; + if (cw.is_binary()) { + if (propagate_bin_clause(cw[0], cw[1])) { + if (scope_lvl() > 0) { + m_clauses_to_reinit[j] = cw; + j++; + } + } + } + else { + clause & c = *(cw.get_clause()); + dettach_clause(c); + attach_clause(c, reinit); + if (scope_lvl() > 0 && reinit) { + // clause propagated literal, must keep it in the reinit stack. + m_clauses_to_reinit[j] = cw; + j++; + } + else { + c.set_reinit_stack(false); + } + } + } + m_clauses_to_reinit.shrink(j); + } + + // ----------------------- + // + // Misc + // + // ----------------------- + + void solver::updt_params(params_ref const & p) { + m_params = p; + m_config.updt_params(p); + m_simplifier.updt_params(p); + m_asymm_branch.updt_params(p); + m_probing.updt_params(p); + m_scc.updt_params(p); + m_rand.set_seed(p.get_uint(":random-seed", 0)); + } + + void solver::collect_param_descrs(param_descrs & d) { + config::collect_param_descrs(d); + simplifier::collect_param_descrs(d); + asymm_branch::collect_param_descrs(d); + probing::collect_param_descrs(d); + scc::collect_param_descrs(d); + } + + void solver::set_cancel(bool f) { + m_cancel = f; + } + + void solver::collect_statistics(statistics & st) { + m_stats.collect_statistics(st); + m_cleaner.collect_statistics(st); + m_simplifier.collect_statistics(st); + m_scc.collect_statistics(st); + m_asymm_branch.collect_statistics(st); + m_probing.collect_statistics(st); + } + + void solver::reset_statistics() { + m_stats.reset(); + m_cleaner.reset_statistics(); + m_simplifier.reset_statistics(); + m_asymm_branch.reset_statistics(); + m_probing.reset_statistics(); + } + + // ----------------------- + // + // Activity related stuff + // + // ----------------------- + + void solver::rescale_activity() { + svector::iterator it = m_activity.begin(); + svector::iterator end = m_activity.end(); + for (; it != end; ++it) { + *it >>= 14; + } + m_activity_inc >>= 14; + } + + // ----------------------- + // + // Iterators + // + // ----------------------- + void solver::collect_bin_clauses(svector & r, bool learned) const { + unsigned sz = m_watches.size(); + for (unsigned l_idx = 0; l_idx < sz; l_idx++) { + literal l = to_literal(l_idx); + l.neg(); + watch_list const & wlist = m_watches[l_idx]; + watch_list::const_iterator it = wlist.begin(); + watch_list::const_iterator end = wlist.end(); + for (; it != end; ++it) { + if (!it->is_binary_clause()) + continue; + if (!learned && it->is_learned()) + continue; + literal l2 = it->get_literal(); + if (l.index() > l2.index()) + continue; + TRACE("cleanup_bug", tout << "collected: " << l << " " << l2 << "\n";); + r.push_back(bin_clause(l, l2)); + } + } + } + + // ----------------------- + // + // Debugging + // + // ----------------------- + bool solver::check_invariant() const { + integrity_checker checker(*this); + SASSERT(checker()); + return true; + } + + bool solver::check_marks() const { + for (bool_var v = 0; v < num_vars(); v++) { + SASSERT(!is_marked(v)); + } + return true; + } + + void solver::display_binary(std::ostream & out) const { + unsigned sz = m_watches.size(); + for (unsigned l_idx = 0; l_idx < sz; l_idx++) { + literal l = to_literal(l_idx); + l.neg(); + watch_list const & wlist = m_watches[l_idx]; + watch_list::const_iterator it = wlist.begin(); + watch_list::const_iterator end = wlist.end(); + for (; it != end; ++it) { + if (!it->is_binary_clause()) + continue; + literal l2 = it->get_literal(); + if (l.index() > l2.index()) + continue; + out << "(" << l << " " << l2 << ")\n"; + } + } + } + + void solver::display_units(std::ostream & out) const { + unsigned end = scope_lvl() == 0 ? m_trail.size() : m_scopes[0].m_trail_lim; + for (unsigned i = 0; i < end; i++) { + out << m_trail[i] << " "; + } + if (end != 0) + out << "\n"; + } + + void solver::display(std::ostream & out) const { + out << "(sat\n"; + display_units(out); + display_binary(out); + out << m_clauses << m_learned; + out << ")\n"; + } + + unsigned solver::num_clauses() const { + unsigned num_cls = 0; + num_cls += m_trail.size(); // units; + vector::const_iterator it = m_watches.begin(); + vector::const_iterator end = m_watches.end(); + for (unsigned l_idx = 0; it != end; ++it, ++l_idx) { + literal l = ~to_literal(l_idx); + watch_list const & wlist = *it; + watch_list::const_iterator it2 = wlist.begin(); + watch_list::const_iterator end2 = wlist.end(); + for (; it2 != end2; ++it2) { + if (it2->is_binary_clause() && l.index() < it2->get_literal().index()) + num_cls++; + } + } + clause_vector const * vs[2] = { &m_clauses, &m_learned }; + for (unsigned i = 0; i < 2; i++) { + clause_vector const & cs = *(vs[i]); + num_cls += cs.size(); + } + return num_cls; + } + + void solver::display_dimacs(std::ostream & out) const { + out << "p cnf " << num_vars() << " " << num_clauses() << "\n"; + for (unsigned i = 0; i < m_trail.size(); i++) { + out << dimacs_lit(m_trail[i]) << " 0\n"; + } + vector::const_iterator it = m_watches.begin(); + vector::const_iterator end = m_watches.end(); + for (unsigned l_idx = 0; it != end; ++it, ++l_idx) { + literal l = ~to_literal(l_idx); + watch_list const & wlist = *it; + watch_list::const_iterator it2 = wlist.begin(); + watch_list::const_iterator end2 = wlist.end(); + for (; it2 != end2; ++it2) { + if (it2->is_binary_clause() && l.index() < it2->get_literal().index()) + out << dimacs_lit(l) << " " << dimacs_lit(it2->get_literal()) << " 0\n"; + } + } + clause_vector const * vs[2] = { &m_clauses, &m_learned }; + for (unsigned i = 0; i < 2; i++) { + clause_vector const & cs = *(vs[i]); + clause_vector::const_iterator it = cs.begin(); + clause_vector::const_iterator end = cs.end(); + for (; it != end; ++it) { + clause const & c = *(*it); + unsigned sz = c.size(); + for (unsigned j = 0; j < sz; j++) + out << dimacs_lit(c[j]) << " "; + out << "0\n"; + } + } + } + + void solver::display_watches(std::ostream & out) const { + vector::const_iterator it = m_watches.begin(); + vector::const_iterator end = m_watches.end(); + for (unsigned l_idx = 0; it != end; ++it, ++l_idx) { + watch_list const & wlist = *it; + literal l = to_literal(l_idx); + out << l << ": "; + sat::display(out, m_cls_allocator, wlist); + out << "\n"; + } + } + + void solver::display_assignment(std::ostream & out) const { + for (unsigned i = 0; i < m_trail.size(); i++) + out << m_trail[i] << " "; + out << "\n"; + } + + /** + \brief Return true, if c is a clause containing one unassigned literal. + */ + bool solver::is_unit(clause const & c) const { + bool found_undef = false; + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) { + switch (value(c[i])) { + case l_undef: + if (found_undef) + return false; + found_undef = true; + break; + case l_true: + return false; + case l_false: + break; + } + } + return found_undef; + } + + /** + \brief Return true, if all literals in c are assigned to false. + */ + bool solver::is_empty(clause const & c) const { + unsigned sz = c.size(); + for (unsigned i = 0; i < sz; i++) { + if (value(c[i]) != l_false) + return false; + } + return true; + } + + bool solver::check_missed_propagation(clause_vector const & cs) const { + clause_vector::const_iterator it = cs.begin(); + clause_vector::const_iterator end = cs.end(); + for (; it != end; ++it) { + clause const & c = *(*it); + if (c.frozen()) + continue; + if (is_empty(c) || is_unit(c)) { + TRACE("sat_missed_prop", tout << "missed_propagation: " << c << "\n"; + for (unsigned i = 0; i < c.size(); i++) tout << c[i] << ": " << value(c[i]) << "\n";); + UNREACHABLE(); + } + SASSERT(!is_empty(c)); + SASSERT(!is_unit(c)); + } + return true; + } + + bool solver::check_missed_propagation() const { + if (inconsistent()) + return true; + return check_missed_propagation(m_clauses) && check_missed_propagation(m_learned); + } + + // ----------------------- + // + // Simplification + // + // ----------------------- + void solver::cleanup() { + if (scope_lvl() > 0 || inconsistent()) + return; + if (m_cleaner() && m_ext) + m_ext->clauses_modifed(); + } + + void solver::simplify(bool learned) { + if (scope_lvl() > 0 || inconsistent()) + return; + m_simplifier(learned); + m_simplifier.free_memory(); + if (m_ext) + m_ext->clauses_modifed(); + } + + unsigned solver::scc_bin() { + if (scope_lvl() > 0 || inconsistent()) + return 0; + unsigned r = m_scc(); + if (r > 0 && m_ext) + m_ext->clauses_modifed(); + return r; + } + + void solver::asymmetric_branching() { + if (scope_lvl() > 0 || inconsistent()) + return; + m_asymm_branch(); + if (m_ext) + m_ext->clauses_modifed(); + } + + // ----------------------- + // + // Statistics + // + // ----------------------- + + void solver::display_status(std::ostream & out) const { + unsigned num_bin = 0; + unsigned num_ext = 0; + unsigned num_lits = 0; + vector::const_iterator it = m_watches.begin(); + vector::const_iterator end = m_watches.end(); + for (unsigned l_idx = 0; it != end; ++it, ++l_idx) { + literal l = ~to_literal(l_idx); + watch_list const & wlist = *it; + watch_list::const_iterator it2 = wlist.begin(); + watch_list::const_iterator end2 = wlist.end(); + for (; it2 != end2; ++it2) { + switch (it2->get_kind()) { + case watched::BINARY: + if (l.index() < it2->get_literal().index()) { + num_lits += 2; + num_bin++; + } + break; + case watched::EXT_CONSTRAINT: + num_ext++; + break; + default: + break; + } + } + } + unsigned num_elim = 0; + for (bool_var v = 0; v < num_vars(); v++) { + if (m_eliminated[v]) + num_elim++; + } + unsigned num_ter = 0; + unsigned num_cls = 0; + clause_vector const * vs[2] = { &m_clauses, &m_learned }; + for (unsigned i = 0; i < 2; i++) { + clause_vector const & cs = *(vs[i]); + clause_vector::const_iterator it = cs.begin(); + clause_vector::const_iterator end = cs.end(); + for (; it != end; ++it) { + clause & c = *(*it); + if (c.size() == 3) + num_ter++; + else + num_cls++; + num_lits += c.size(); + } + } + unsigned total_cls = num_cls + num_ter + num_bin; + double mem = static_cast(memory::get_allocation_size())/static_cast(1024*1024); + out << "(sat-status\n"; + out << " :inconsistent " << (m_inconsistent ? "true" : "false") << "\n"; + out << " :vars " << num_vars() << "\n"; + out << " :elim-vars " << num_elim << "\n"; + out << " :lits " << num_lits << "\n"; + out << " :assigned " << m_trail.size() << "\n"; + out << " :binary-clauses " << num_bin << "\n"; + out << " :ternary-clauses " << num_ter << "\n"; + out << " :clauses " << num_cls << "\n"; + out << " :del-clause " << m_stats.m_del_clause << "\n"; + out << " :avg-clause-size " << (total_cls == 0 ? 0.0 : static_cast(num_lits) / static_cast(total_cls)) << "\n"; + out << " :memory " << std::fixed << std::setprecision(2) << mem << ")" << std::endl; + } + + void stats::collect_statistics(statistics & st) const { + st.update("mk bool var", m_mk_var); + st.update("mk binary clause", m_mk_bin_clause); + st.update("mk ternary clause", m_mk_ter_clause); + st.update("mk clause", m_mk_clause); + st.update("gc clause", m_gc_clause); + st.update("del clause", m_del_clause); + st.update("conflicts", m_conflict); + st.update("propagations", m_propagate); + st.update("decisions", m_decision); + st.update("binary propagations", m_bin_propagate); + st.update("ternary propagations", m_ter_propagate); + st.update("restarts", m_restart); + st.update("minimized lits", m_minimized_lits); + st.update("dyn subsumption resolution", m_dyn_sub_res); + } + + void stats::reset() { + m_mk_var = 0; + m_mk_bin_clause = 0; + m_mk_ter_clause = 0; + m_mk_clause = 0; + m_conflict = 0; + m_propagate = 0; + m_bin_propagate = 0; + m_ter_propagate = 0; + m_decision = 0; + m_restart = 0; + m_gc_clause = 0; + m_del_clause = 0; + m_minimized_lits = 0; + m_dyn_sub_res = 0; + } + + void mk_stat::display(std::ostream & out) const { + if (!m_solver.m_clauses.empty()) + out << " :clauses " << m_solver.m_clauses.size(); + if (!m_solver.m_learned.empty()) { + out << " :learned " << (m_solver.m_learned.size() - m_solver.m_num_frozen); + if (m_solver.m_num_frozen > 0) + out << " :frozen " << m_solver.m_num_frozen; + } + out << " :gc-clause " << m_solver.m_stats.m_gc_clause; + out << mem_stat(); + } + + std::ostream & operator<<(std::ostream & out, mk_stat const & stat) { + stat.display(out); + return out; + } + +}; diff --git a/lib/sat_solver.h b/lib/sat_solver.h new file mode 100644 index 000000000..378c070fa --- /dev/null +++ b/lib/sat_solver.h @@ -0,0 +1,428 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_solver.h + +Abstract: + + SAT solver main class. + +Author: + + Leonardo de Moura (leonardo) 2011-05-21. + +Revision History: + +--*/ +#ifndef _SAT_SOLVER_H_ +#define _SAT_SOLVER_H_ + +#include"sat_types.h" +#include"sat_clause.h" +#include"sat_watched.h" +#include"sat_justification.h" +#include"sat_var_queue.h" +#include"sat_extension.h" +#include"sat_config.h" +#include"sat_cleaner.h" +#include"sat_simplifier.h" +#include"sat_scc.h" +#include"sat_asymm_branch.h" +#include"sat_iff3_finder.h" +#include"sat_probing.h" +#include"params.h" +#include"statistics.h" +#include"stopwatch.h" +#include"trace.h" + +namespace sat { + + /** + \brief Main statistic counters. + */ + struct stats { + unsigned m_mk_var; + unsigned m_mk_bin_clause; + unsigned m_mk_ter_clause; + unsigned m_mk_clause; + unsigned m_conflict; + unsigned m_propagate; + unsigned m_bin_propagate; + unsigned m_ter_propagate; + unsigned m_decision; + unsigned m_restart; + unsigned m_gc_clause; + unsigned m_del_clause; + unsigned m_minimized_lits; + unsigned m_dyn_sub_res; + stats() { reset(); } + void reset(); + void collect_statistics(statistics & st) const; + }; + + class solver { + public: + struct abort_solver {}; + protected: + volatile bool m_cancel; + config m_config; + stats m_stats; + extension * m_ext; + random_gen m_rand; + clause_allocator m_cls_allocator; + cleaner m_cleaner; + model m_model; + model_converter m_mc; + simplifier m_simplifier; + scc m_scc; + asymm_branch m_asymm_branch; + probing m_probing; + bool m_inconsistent; + // A conflict is usually a single justification. That is, a justification + // for false. If m_not_l is not null_literal, then m_conflict is a + // justification for l, and the conflict is union of m_no_l and m_conflict; + justification m_conflict; + literal m_not_l; + clause_vector m_clauses; + clause_vector m_learned; + unsigned m_num_frozen; + vector m_watches; + svector m_assignment; + svector m_justification; + svector m_decision; + svector m_mark; + svector m_lit_mark; + svector m_eliminated; + svector m_external; + svector m_level; + svector m_activity; + unsigned m_activity_inc; + svector m_phase; + svector m_prev_phase; + svector m_assigned_since_gc; + bool m_phase_cache_on; + unsigned m_phase_counter; + var_queue m_case_split_queue; + unsigned m_qhead; + unsigned m_scope_lvl; + literal_vector m_trail; + clause_wrapper_vector m_clauses_to_reinit; + struct scope { + unsigned m_trail_lim; + unsigned m_clauses_to_reinit_lim; + bool m_inconsistent; + }; + svector m_scopes; + stopwatch m_stopwatch; + params_ref m_params; + scoped_ptr m_clone; // for debugging purposes + + void del_clauses(clause * const * begin, clause * const * end); + + friend class integrity_checker; + friend class cleaner; + friend class simplifier; + friend class scc; + friend class elim_eqs; + friend class asymm_branch; + friend class probing; + friend class iff3_finder; + friend struct mk_stat; + public: + solver(params_ref const & p, extension * ext); + ~solver(); + + // ----------------------- + // + // Misc + // + // ----------------------- + void updt_params(params_ref const & p); + static void collect_param_descrs(param_descrs & d); + + void set_cancel(bool f); + void collect_statistics(statistics & st); + void reset_statistics(); + void display_status(std::ostream & out) const; + + /** + \brief Copy (non learned) clauses from src to this solver. + Create missing variables if needed. + + \pre the model converter of src and this must be empty + */ + void copy(solver const & src); + + // ----------------------- + // + // Variable & Clause creation + // + // ----------------------- + bool_var mk_var(bool ext = false, bool dvar = true); + void mk_clause(unsigned num_lits, literal * lits); + void mk_clause(literal l1, literal l2); + void mk_clause(literal l1, literal l2, literal l3); + + protected: + void del_clause(clause & c) { m_cls_allocator.del_clause(&c); m_stats.m_del_clause++; } + clause * mk_clause_core(unsigned num_lits, literal * lits, bool learned); + void mk_bin_clause(literal l1, literal l2, bool learned); + bool propagate_bin_clause(literal l1, literal l2); + clause * mk_ter_clause(literal * lits, bool learned); + void attach_ter_clause(clause & c, bool & reinit); + void attach_ter_clause(clause & c) { bool reinit; attach_ter_clause(c, reinit); } + clause * mk_nary_clause(unsigned num_lits, literal * lits, bool learned); + void attach_nary_clause(clause & c, bool & reinit); + void attach_nary_clause(clause & c) { bool reinit; attach_nary_clause(c, reinit); } + void attach_clause(clause & c, bool & reinit); + void attach_clause(clause & c) { bool reinit; attach_clause(c, reinit); } + unsigned select_watch_lit(clause const & cls, unsigned starting_at) const; + unsigned select_learned_watch_lit(clause const & cls) const; + bool simplify_clause(unsigned & num_lits, literal * lits) const; + template + bool simplify_clause_core(unsigned & num_lits, literal * lits) const; + void dettach_bin_clause(literal l1, literal l2, bool learned); + void dettach_clause(clause & c); + void dettach_nary_clause(clause & c); + void dettach_ter_clause(clause & c); + void push_reinit_stack(clause & c); + + // ----------------------- + // + // Basic + // + // ----------------------- + public: + bool inconsistent() const { return m_inconsistent; } + unsigned num_vars() const { return m_level.size(); } + bool is_external(bool_var v) const { return m_external[v] != 0; } + bool was_eliminated(bool_var v) const { return m_eliminated[v] != 0; } + unsigned scope_lvl() const { return m_scope_lvl; } + lbool value(literal l) const { return m_assignment[l.index()]; } + lbool value(bool_var v) const { return m_assignment[literal(v, false).index()]; } + unsigned lvl(bool_var v) const { return m_level[v]; } + unsigned lvl(literal l) const { return m_level[l.var()]; } + void assign(literal l, justification j) { + TRACE("sat_assign", tout << l << " previous value: " << value(l) << "\n";); + switch (value(l)) { + case l_false: set_conflict(j, ~l); break; + case l_undef: assign_core(l, j); break; + case l_true: return; + } + } + void assign_core(literal l, justification jst); + void set_conflict(justification c, literal not_l); + void set_conflict(justification c) { set_conflict(c, null_literal); } + lbool status(clause const & c) const; + clause_offset get_offset(clause const & c) const { return m_cls_allocator.get_offset(&c); } + void checkpoint() { + if (m_cancel) throw solver_exception(TACTIC_CANCELED_MSG); + if (memory::get_allocation_size() > m_config.m_max_memory) throw solver_exception(TACTIC_MAX_MEMORY_MSG); + } + protected: + watch_list & get_wlist(literal l) { return m_watches[l.index()]; } + watch_list const & get_wlist(literal l) const { return m_watches[l.index()]; } + watch_list & get_wlist(unsigned l_idx) { return m_watches[l_idx]; } + bool is_marked(bool_var v) const { return m_mark[v] != 0; } + void mark(bool_var v) { SASSERT(!is_marked(v)); m_mark[v] = true; } + void reset_mark(bool_var v) { SASSERT(is_marked(v)); m_mark[v] = false; } + bool is_marked_lit(literal l) const { return m_lit_mark[l.index()] != 0; } + void mark_lit(literal l) { SASSERT(!is_marked_lit(l)); m_lit_mark[l.index()] = true; } + void unmark_lit(literal l) { SASSERT(is_marked_lit(l)); m_lit_mark[l.index()] = false; } + + // ----------------------- + // + // Propagation + // + // ----------------------- + public: + // if update == true, then glue of learned clauses is updated. + bool propagate(bool update); + + protected: + bool propagate_core(bool update); + + // ----------------------- + // + // Search + // + // ----------------------- + public: + lbool check(); + model const & get_model() const { return m_model; } + model_converter const & get_model_converter() const { return m_mc; } + + protected: + unsigned m_conflicts; + unsigned m_conflicts_since_restart; + unsigned m_restart_threshold; + unsigned m_luby_idx; + unsigned m_conflicts_since_gc; + unsigned m_gc_threshold; + double m_min_d_tk; + unsigned m_next_simplify; + bool decide(); + bool_var next_var(); + lbool bounded_search(); + void init_search(); + void simplify_problem(); + void mk_model(); + bool check_model(model const & m) const; + void restart(); + void sort_watch_lits(); + + // ----------------------- + // + // GC + // + // ----------------------- + protected: + void gc(); + void gc_glue(); + void gc_psm(); + void gc_glue_psm(); + void gc_psm_glue(); + void save_psm(); + void gc_half(char const * st_name); + void gc_dyn_psm(); + bool activate_frozen_clause(clause & c); + unsigned psm(clause const & c) const; + bool can_delete(clause const & c) const { + if (c.on_reinit_stack()) + return false; + if (c.size() == 3) + return true; // not needed to justify anything. + literal l0 = c[0]; + if (value(l0) != l_true) + return true; + justification const & jst = m_justification[l0.var()]; + return !jst.is_clause() || m_cls_allocator.get_clause(jst.get_clause_offset()) != &c; + } + + // ----------------------- + // + // Conflict resolution + // + // ----------------------- + protected: + unsigned m_conflict_lvl; + literal_vector m_lemma; + literal_vector m_ext_antecedents; + bool resolve_conflict(); + bool resolve_conflict_core(); + unsigned get_max_lvl(literal consequent, justification js); + void process_antecedent(literal antecedent, unsigned & num_marks); + void fill_ext_antecedents(literal consequent, justification js); + unsigned skip_literals_above_conflict_level(); + void forget_phase_of_vars(unsigned from_lvl); + void updt_phase_counters(); + svector m_diff_levels; + unsigned num_diff_levels(unsigned num, literal const * lits); + + // lemma minimization + typedef approx_set_tpl level_approx_set; + bool_var_vector m_unmark; + level_approx_set m_lvl_set; + bool_var_vector m_lemma_min_stack; + bool process_antecedent_for_minimization(literal antecedent); + bool implied_by_marked(literal lit); + void reset_unmark(unsigned old_size); + void updt_lemma_lvl_set(); + void minimize_lemma(); + void reset_lemma_var_marks(); + void dyn_sub_res(); + + // ----------------------- + // + // Backtracking + // + // ----------------------- + public: + void push(); + void pop(unsigned num_scopes); + + protected: + void unassign_vars(unsigned old_sz); + void reinit_clauses(unsigned old_sz); + + // ----------------------- + // + // Simplification + // + // ----------------------- + public: + void cleanup(); + void simplify(bool learned = true); + void asymmetric_branching(); + unsigned scc_bin(); + + // ----------------------- + // + // Activity related stuff + // + // ----------------------- + public: + void inc_activity(bool_var v) { + unsigned & act = m_activity[v]; + act += m_activity_inc; + m_case_split_queue.activity_increased_eh(v); + if (act > (1 << 24)) + rescale_activity(); + } + + void decay_activity() { + m_activity_inc *= 11; + m_activity_inc /= 10; + } + + private: + void rescale_activity(); + + // ----------------------- + // + // Iterators + // + // ----------------------- + public: + clause * const * begin_clauses() const { return m_clauses.begin(); } + clause * const * end_clauses() const { return m_clauses.end(); } + clause * const * begin_learned() const { return m_learned.begin(); } + clause * const * end_learned() const { return m_learned.end(); } + typedef std::pair bin_clause; + void collect_bin_clauses(svector & r, bool learned) const; + + // ----------------------- + // + // Debugging + // + // ----------------------- + public: + bool check_invariant() const; + void display(std::ostream & out) const; + void display_watches(std::ostream & out) const; + void display_dimacs(std::ostream & out) const; + + protected: + void display_binary(std::ostream & out) const; + void display_units(std::ostream & out) const; + void display_assignment(std::ostream & out) const; + unsigned num_clauses() const; + bool is_unit(clause const & c) const; + bool is_empty(clause const & c) const; + bool check_missed_propagation(clause_vector const & cs) const; + bool check_missed_propagation() const; + bool check_marks() const; + }; + + struct mk_stat { + solver const & m_solver; + mk_stat(solver const & s):m_solver(s) {} + void display(std::ostream & out) const; + }; + + std::ostream & operator<<(std::ostream & out, mk_stat const & stat); +}; + +#endif diff --git a/lib/sat_solver_strategy.cpp b/lib/sat_solver_strategy.cpp new file mode 100644 index 000000000..94f66990b --- /dev/null +++ b/lib/sat_solver_strategy.cpp @@ -0,0 +1,187 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_solver_strategy.cpp + +Abstract: + + Strategy for using the SAT solver preprocessing capabilities. + +Author: + + Leonardo (leonardo) 2011-05-23 + +Notes: + +--*/ +#include"sat_solver_strategy.h" +#include"assertion_set2sat.h" +#include"sat_solver.h" +#include"filter_model_converter.h" +#include"ast_smt2_pp.h" +#include"model_v2_pp.h" + +struct sat_solver_strategy::imp { + ast_manager & m; + assertion_set2sat m_as2sat; + sat2assertion_set m_sat2as; + sat::solver m_solver; + params_ref m_params; + bool m_produce_models; + + imp(ast_manager & _m, params_ref const & p): + m(_m), + m_solver(p, 0), + m_params(p) { + SASSERT(!m.proofs_enabled()); + m_produce_models = p.get_bool(":produce-models", false); + } + + void operator()(assertion_set & s, model_converter_ref & mc) { + if (s.m().proofs_enabled()) + throw sat::solver_exception("sat solver does not support proofs"); + TRACE("before_sat_solver", s.display(tout);); + s.elim_redundancies(); + if (s.inconsistent()) { + mc = 0; + return; + } + + atom2bool_var map(m); + m_as2sat(s, m_params, m_solver, map); + TRACE("sat_solver_unknown", tout << "interpreted_atoms: " << map.interpreted_atoms() << "\n"; + atom2bool_var::iterator it = map.begin(); + atom2bool_var::iterator end = map.end(); + for (; it != end; ++it) { + if (!is_uninterp_const(it->m_key)) + tout << mk_ismt2_pp(it->m_key, m) << "\n"; + }); + s.reset(); + s.m().compact_memory(); + CASSERT("sat_solver", m_solver.check_invariant()); + IF_VERBOSE(ST_VERBOSITY_LVL, m_solver.display_status(verbose_stream());); + TRACE("sat_dimacs", m_solver.display_dimacs(tout);); + + lbool r = m_solver.check(); + if (r == l_false) { + mc = 0; + s.reset(); + s.assert_expr(m.mk_false(), 0); + return; + } + else if (r == l_true && !map.interpreted_atoms()) { + // register model + model_ref md = alloc(model, m); + sat::model const & ll_m = m_solver.get_model(); + TRACE("sat_solver_strategy", for (unsigned i = 0; i < ll_m.size(); i++) tout << i << ":" << ll_m[i] << " "; tout << "\n";); + atom2bool_var::iterator it = map.begin(); + atom2bool_var::iterator end = map.end(); + for (; it != end; ++it) { + expr * n = it->m_key; + sat::bool_var v = it->m_value; + TRACE("sat_solver_strategy", tout << "extracting value of " << mk_ismt2_pp(n, m) << "\nvar: " << v << "\n";); + switch (sat::value_at(v, ll_m)) { + case l_true: + md->register_decl(to_app(n)->get_decl(), m.mk_true()); + break; + case l_false: + md->register_decl(to_app(n)->get_decl(), m.mk_false()); + break; + default: + break; + } + } + s.reset(); + TRACE("sat_solver_strategy", model_v2_pp(tout, *md);); + mc = model2model_converter(md.get()); + } + else { + // get simplified problem. +#if 0 + IF_VERBOSE(ST_VERBOSITY_LVL, verbose_stream() << "\"formula constains interpreted atoms, recovering formula from sat solver...\"\n";); +#endif + m_solver.pop(m_solver.scope_lvl()); + m_sat2as(m_solver, map, m_params, s, mc); + } + } + + void set_cancel(bool f) { + m_as2sat.set_cancel(f); + m_sat2as.set_cancel(f); + m_solver.set_cancel(f); + } +}; + +sat_solver_strategy::sat_solver_strategy(ast_manager & m, params_ref const & p): + m_imp(0), + m_params(p) { +} + +sat_solver_strategy::~sat_solver_strategy() { + SASSERT(m_imp == 0); +} + +void sat_solver_strategy::updt_params(params_ref const & p) { + m_params = p; +} + +void sat_solver_strategy::get_param_descrs(param_descrs & r) { + assertion_set2sat::collect_param_descrs(r); + sat2assertion_set::collect_param_descrs(r); + sat::solver::collect_param_descrs(r); + insert_produce_models(r); +} + + +struct sat_solver_strategy::scoped_set_imp { + sat_solver_strategy * m_owner; + scoped_set_imp(sat_solver_strategy * o, sat_solver_strategy::imp * i):m_owner(o) { + #pragma omp critical (sat_solver_strategy) + { + m_owner->m_imp = i; + } + } + + ~scoped_set_imp() { + #pragma omp critical (sat_solver_strategy) + { + m_owner->m_imp = 0; + } + } +}; + +void sat_solver_strategy::operator()(assertion_set & s, model_converter_ref & mc) { + imp proc(s.m(), m_params); + scoped_set_imp set(this, &proc); + try { + proc(s, mc); + proc.m_solver.collect_statistics(m_stats); + } + catch (sat::solver_exception & ex) { + proc.m_solver.collect_statistics(m_stats); + throw ex; + } + TRACE("sat_stats", m_stats.display_smt2(tout);); +} + +void sat_solver_strategy::cleanup() { + SASSERT(m_imp == 0); +} + +void sat_solver_strategy::set_cancel(bool f) { + #pragma omp critical (sat_solver_strategy) + { + if (m_imp) + m_imp->set_cancel(f); + } +} + +void sat_solver_strategy::reset_statistics() { + m_stats.reset(); +} + +void sat_solver_strategy::collect_statistics(statistics & st) const { + st.copy(m_stats); +} diff --git a/lib/sat_solver_strategy.h b/lib/sat_solver_strategy.h new file mode 100644 index 000000000..b3cbd22b4 --- /dev/null +++ b/lib/sat_solver_strategy.h @@ -0,0 +1,65 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_solver_strategy.h + +Abstract: + + Strategy for using the SAT solver. + If the assertion set contains theory atoms, then the sat solver just + checks whether the boolean abstraction is satisfiable or not. + +Author: + + Leonardo (leonardo) 2011-06-02 + +Notes: + +--*/ +#ifndef _SAT_SOLVER_STRATEGY_H_ +#define _SAT_SOLVER_STRATEGY_H_ + +#include"assertion_set_strategy.h" + +class assertion_set; + +class sat_solver_strategy : public assertion_set_strategy { + struct imp; + imp * m_imp; + params_ref m_params; + statistics m_stats; + struct scoped_set_imp; +public: + sat_solver_strategy(ast_manager & m, params_ref const & p = params_ref()); + virtual ~sat_solver_strategy(); + + virtual void updt_params(params_ref const & p); + static void get_param_descrs(param_descrs & r); + virtual void collect_param_descrs(param_descrs & r) { get_param_descrs(r); } + + virtual void operator()(assertion_set & s, model_converter_ref & mc); + + virtual void cleanup(); + + virtual void collect_statistics(statistics & st) const; + virtual void reset_statistics(); +protected: + virtual void set_cancel(bool f); +}; + +inline as_st * mk_sat_solver(ast_manager & m, params_ref const & p = params_ref()) { + return clean(alloc(sat_solver_strategy, m, p)); +} + +// Apply only simplification procedures +inline as_st * mk_sat_preprocessor(ast_manager & m, params_ref const & p = params_ref()) { + params_ref p_aux; + p_aux.set_uint(":max-conflicts", 0); + as_st * st = clean(using_params(mk_sat_solver(m), p_aux)); + st->updt_params(p); + return st; +} + +#endif diff --git a/lib/sat_tactic.cpp b/lib/sat_tactic.cpp new file mode 100644 index 000000000..015eb6c66 --- /dev/null +++ b/lib/sat_tactic.cpp @@ -0,0 +1,218 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_tactic.cpp + +Abstract: + + Tactic for using the SAT solver and its preprocessing capabilities. + +Author: + + Leonardo (leonardo) 2011-10-25 + +Notes: + +--*/ +#include"tactical.h" +#include"goal2sat.h" +#include"sat_solver.h" +#include"filter_model_converter.h" +#include"ast_smt2_pp.h" +#include"model_v2_pp.h" + +class sat_tactic : public tactic { + + struct imp { + ast_manager & m; + goal2sat m_goal2sat; + sat2goal m_sat2goal; + sat::solver m_solver; + params_ref m_params; + + imp(ast_manager & _m, params_ref const & p): + m(_m), + m_solver(p, 0), + m_params(p) { + SASSERT(!m.proofs_enabled()); + } + + void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + mc = 0; pc = 0; core = 0; + fail_if_proof_generation("sat", g); + fail_if_unsat_core_generation("sat", g); + bool produce_models = g->models_enabled(); + TRACE("before_sat_solver", g->display(tout);); + g->elim_redundancies(); + + atom2bool_var map(m); + m_goal2sat(*g, m_params, m_solver, map); + TRACE("sat_solver_unknown", tout << "interpreted_atoms: " << map.interpreted_atoms() << "\n"; + atom2bool_var::iterator it = map.begin(); + atom2bool_var::iterator end = map.end(); + for (; it != end; ++it) { + if (!is_uninterp_const(it->m_key)) + tout << mk_ismt2_pp(it->m_key, m) << "\n"; + }); + g->reset(); + g->m().compact_memory(); + + CASSERT("sat_solver", m_solver.check_invariant()); + IF_VERBOSE(TACTIC_VERBOSITY_LVL, m_solver.display_status(verbose_stream());); + TRACE("sat_dimacs", m_solver.display_dimacs(tout);); + + lbool r = m_solver.check(); + if (r == l_false) { + g->assert_expr(m.mk_false(), 0, 0); + } + else if (r == l_true && !map.interpreted_atoms()) { + // register model + if (produce_models) { + model_ref md = alloc(model, m); + sat::model const & ll_m = m_solver.get_model(); + TRACE("sat_tactic", for (unsigned i = 0; i < ll_m.size(); i++) tout << i << ":" << ll_m[i] << " "; tout << "\n";); + atom2bool_var::iterator it = map.begin(); + atom2bool_var::iterator end = map.end(); + for (; it != end; ++it) { + expr * n = it->m_key; + sat::bool_var v = it->m_value; + TRACE("sat_tactic", tout << "extracting value of " << mk_ismt2_pp(n, m) << "\nvar: " << v << "\n";); + switch (sat::value_at(v, ll_m)) { + case l_true: + md->register_decl(to_app(n)->get_decl(), m.mk_true()); + break; + case l_false: + md->register_decl(to_app(n)->get_decl(), m.mk_false()); + break; + default: + break; + } + } + TRACE("sat_tactic", model_v2_pp(tout, *md);); + mc = model2model_converter(md.get()); + } + } + else { + // get simplified problem. +#if 0 + IF_VERBOSE(TACTIC_VERBOSITY_LVL, verbose_stream() << "\"formula constains interpreted atoms, recovering formula from sat solver...\"\n";); +#endif + m_solver.pop(m_solver.scope_lvl()); + m_sat2goal(m_solver, map, m_params, *(g.get()), mc); + } + g->inc_depth(); + result.push_back(g.get()); + } + + void set_cancel(bool f) { + m_goal2sat.set_cancel(f); + m_sat2goal.set_cancel(f); + m_solver.set_cancel(f); + } + }; + + struct scoped_set_imp { + sat_tactic * m_owner; + + scoped_set_imp(sat_tactic * o, imp * i):m_owner(o) { + #pragma omp critical (sat_tactic) + { + m_owner->m_imp = i; + } + } + + ~scoped_set_imp() { + #pragma omp critical (sat_tactic) + { + m_owner->m_imp = 0; + } + } + }; + + imp * m_imp; + params_ref m_params; + statistics m_stats; + +public: + sat_tactic(ast_manager & m, params_ref const & p): + m_imp(0), + m_params(p) { + } + + virtual tactic * translate(ast_manager & m) { + return alloc(sat_tactic, m, m_params); + } + + virtual ~sat_tactic() { + SASSERT(m_imp == 0); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + } + + virtual void collect_param_descrs(param_descrs & r) { + goal2sat::collect_param_descrs(r); + sat2goal::collect_param_descrs(r); + sat::solver::collect_param_descrs(r); + } + + void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + imp proc(g->m(), m_params); + scoped_set_imp set(this, &proc); + try { + proc(g, result, mc, pc, core); + proc.m_solver.collect_statistics(m_stats); + } + catch (sat::solver_exception & ex) { + proc.m_solver.collect_statistics(m_stats); + throw tactic_exception(ex.msg()); + } + TRACE("sat_stats", m_stats.display_smt2(tout);); + } + + virtual void cleanup() { + SASSERT(m_imp == 0); + } + + virtual void collect_statistics(statistics & st) const { + st.copy(m_stats); + } + + virtual void reset_statistics() { + m_stats.reset(); + } + +protected: + virtual void set_cancel(bool f) { + #pragma omp critical (sat_tactic) + { + if (m_imp) + m_imp->set_cancel(f); + } + } + +}; + +tactic * mk_sat_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(sat_tactic, m, p)); +} + +tactic * mk_sat_preprocessor_tactic(ast_manager & m, params_ref const & p) { + params_ref p_aux; + p_aux.set_uint(":max-conflicts", 0); + tactic * t = clean(using_params(mk_sat_tactic(m, p), p_aux)); + t->updt_params(p); + return t; +} + diff --git a/lib/sat_tactic.h b/lib/sat_tactic.h new file mode 100644 index 000000000..405afaccd --- /dev/null +++ b/lib/sat_tactic.h @@ -0,0 +1,30 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_tactic.cpp + +Abstract: + + Tactic for using the SAT solver and its preprocessing capabilities. + +Author: + + Leonardo (leonardo) 2011-10-26 + +Notes: + +--*/ +#ifndef _SAT_TACTIC_H_ +#define _SAT_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_sat_tactic(ast_manager & m, params_ref const & p = params_ref()); + +tactic * mk_sat_preprocessor_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/sat_types.h b/lib/sat_types.h new file mode 100644 index 000000000..32a429dd5 --- /dev/null +++ b/lib/sat_types.h @@ -0,0 +1,237 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_types.h + +Abstract: + + Basic types used in the SAT solver + +Author: + + Leonardo de Moura (leonardo) 2011-05-21. + +Revision History: + +--*/ +#ifndef _SAT_TYPES_H_ +#define _SAT_TYPES_H_ + +#include"debug.h" +#include"approx_set.h" +#include"lbool.h" +#include"tactic_exception.h" +#include"vector.h" +#include + +namespace sat { +#define SAT_VB_LVL 10 + + // TODO: there is some duplication in the sat and smt namespaces. + // The sat namespace should be the base. + // I should cleanup the smt namespace later. + + /** + \brief A boolean variable is just an integer. + */ + typedef unsigned bool_var; + + typedef svector bool_var_vector; + + const bool_var null_bool_var = UINT_MAX >> 1; + + /** + \brief The literal b is represented by the value 2*b, and + the literal (not b) by the value 2*b + 1 + */ + class literal { + unsigned m_val; + explicit literal(unsigned v):m_val(v) {} + public: + literal():m_val(null_bool_var << 1) { + SASSERT(var() == null_bool_var && !sign()); + } + + literal(bool_var v, bool _sign): + m_val((v << 1) + static_cast(_sign)) { + SASSERT(var() == v); + SASSERT(sign() == _sign); + } + + bool_var var() const { + return m_val >> 1; + } + + bool sign() const { + return m_val & 1; + } + + literal unsign() const { + return literal(m_val & ~1); + } + + unsigned index() const { + return m_val; + } + + void neg() { + m_val = m_val ^ 1; + } + + friend literal operator~(literal l) { + return literal(l.m_val ^ 1); + } + + unsigned to_uint() const { return m_val; } + + unsigned hash() const { return to_uint(); } + + friend literal to_literal(unsigned x); + friend bool operator<(literal const & l1, literal const & l2); + friend bool operator==(literal const & l1, literal const & l2); + friend bool operator!=(literal const & l1, literal const & l2); + }; + + const literal null_literal; + + inline literal to_literal(unsigned x) { return literal(x); } + inline bool operator<(literal const & l1, literal const & l2) { return l1.m_val < l2.m_val; } + inline bool operator==(literal const & l1, literal const & l2) { return l1.m_val == l2.m_val; } + inline bool operator!=(literal const & l1, literal const & l2) { return l1.m_val != l2.m_val; } + + inline std::ostream & operator<<(std::ostream & out, literal l) { out << (l.sign() ? "-" : "") << l.var(); return out; } + + typedef svector literal_vector; + + typedef unsigned clause_offset; + typedef unsigned ext_constraint_idx; + typedef unsigned ext_justification_idx; + + struct literal2unsigned { unsigned operator()(literal l) const { return l.to_uint(); } }; + + typedef approx_set_tpl literal_approx_set; + + typedef approx_set_tpl var_approx_set; + + enum phase { + POS_PHASE, NEG_PHASE, PHASE_NOT_AVAILABLE + }; + + class solver; + class clause; + class clause_wrapper; + class integrity_checker; + typedef ptr_vector clause_vector; + + class solver_exception : public tactic_exception { + public: + solver_exception(char const * msg):tactic_exception(msg) {} + }; + + typedef default_exception sat_param_exception; + + typedef svector model; + + inline lbool value_at(bool_var v, model const & m) { return m[v]; } + inline lbool value_at(literal l, model const & m) { lbool r = value_at(l.var(), m); return l.sign() ? ~r : r; } + + inline std::ostream & operator<<(std::ostream & out, model const & m) { + bool first = true; + for (bool_var v = 0; v < m.size(); v++) { + if (m[v] == l_undef) continue; + if (first) first = false; else out << " "; + if (m[v] == l_true) out << v; else out << "-" << v; + } + return out; + } + + class uint_set { + svector m_in_set; + svector m_set; + public: + typedef svector::const_iterator iterator; + void insert(unsigned v) { + m_in_set.reserve(v+1, false); + if (m_in_set[v]) + return; + m_in_set[v] = true; + m_set.push_back(v); + } + + bool contains(unsigned v) const { + return v < m_in_set.size() && m_in_set[v] != 0; + } + + bool empty() const { + return m_set.empty(); + } + + // erase some variable from the set + unsigned erase() { + SASSERT(!empty()); + unsigned v = m_set.back(); + m_set.pop_back(); + m_in_set[v] = false; + return v; + } + iterator begin() const { return m_set.begin(); } + iterator end() const { return m_set.end(); } + void reset() { m_set.reset(); m_in_set.reset(); } + void cleanup() { m_set.finalize(); m_in_set.finalize(); } + }; + + typedef uint_set bool_var_set; + + class literal_set { + uint_set m_set; + public: + void insert(literal l) { m_set.insert(l.index()); } + bool contains(literal l) const { return m_set.contains(l.index()); } + bool empty() const { return m_set.empty(); } + void reset() { m_set.reset(); } + void cleanup() { m_set.cleanup(); } + }; + + struct mem_stat { + }; + + inline std::ostream & operator<<(std::ostream & out, mem_stat const & m) { + double mem = static_cast(memory::get_allocation_size())/static_cast(1024*1024); + out << " :memory " << std::fixed << std::setprecision(2) << mem; + return out; + } + + struct dimacs_lit { + literal m_lit; + dimacs_lit(literal l):m_lit(l) {} + }; + + inline std::ostream & operator<<(std::ostream & out, dimacs_lit const & dl) { + literal l = dl.m_lit; + if (l.sign()) out << "-" << (l.var() + 1); + else out << (l.var() + 1); + return out; + } + + struct mk_lits_pp { + unsigned m_num; + literal const * m_lits; + mk_lits_pp(unsigned num, literal const * ls):m_num(num), m_lits(ls) {} + }; + + inline std::ostream & operator<<(std::ostream & out, mk_lits_pp const & ls) { + for (unsigned i = 0; i < ls.m_num; i++) { + if (i > 0) out << " "; + out << ls.m_lits[i]; + } + return out; + } + + inline std::ostream & operator<<(std::ostream & out, literal_vector const & ls) { + return out << mk_lits_pp(ls.size(), ls.c_ptr()); + } +}; + +#endif diff --git a/lib/sat_var_queue.h b/lib/sat_var_queue.h new file mode 100644 index 000000000..24d5f57a0 --- /dev/null +++ b/lib/sat_var_queue.h @@ -0,0 +1,67 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_var_queue.h + +Abstract: + + SAT variable priority queue. + +Author: + + Leonardo de Moura (leonardo) 2011-05-21. + +Revision History: + +--*/ +#ifndef _SAT_VAR_QUEUE_H_ +#define _SAT_VAR_QUEUE_H_ + +#include"heap.h" +#include"sat_types.h" + +namespace sat { + + class var_queue { + struct lt { + svector & m_activity; + lt(svector & act):m_activity(act) {} + bool operator()(bool_var v1, bool_var v2) const { return m_activity[v1] > m_activity[v2]; } + }; + heap m_queue; + public: + var_queue(svector & act):m_queue(128, lt(act)) {} + + void activity_increased_eh(bool_var v) { + if (m_queue.contains(v)) + m_queue.decreased(v); + } + + void mk_var_eh(bool_var v) { + m_queue.reserve(v+1); + m_queue.insert(v); + } + + void del_var_eh(bool_var v) { + if (m_queue.contains(v)) + m_queue.erase(v); + } + + void unassign_var_eh(bool_var v) { + if (!m_queue.contains(v)) + m_queue.insert(v); + } + + void reset() { + m_queue.reset(); + } + + bool empty() const { return m_queue.empty(); } + + bool_var next_var() { SASSERT(!empty()); return m_queue.erase_min(); } + }; +}; + +#endif diff --git a/lib/sat_watched.cpp b/lib/sat_watched.cpp new file mode 100644 index 000000000..cc442571c --- /dev/null +++ b/lib/sat_watched.cpp @@ -0,0 +1,71 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_watched.cpp + +Abstract: + + Element of the SAT solver watchlist. + +Author: + + Leonardo de Moura (leonardo) 2011-05-24. + +Revision History: + +--*/ +#include"sat_watched.h" +#include"sat_clause.h" + +namespace sat { + + bool erase_clause_watch(watch_list & wlist, clause_offset c) { + watch_list::iterator it = wlist.begin(); + watch_list::iterator end = wlist.end(); + for (; it != end; ++it) { + if (it->is_clause() && it->get_clause_offset() == c) { + watch_list::iterator it2 = it; + ++it; + for (; it != end; ++it) { + *it2 = *it; + ++it2; + } + wlist.set_end(it2); + return true; + } + } + return false; + } + + void display(std::ostream & out, clause_allocator const & ca, watch_list const & wlist) { + watch_list::const_iterator it = wlist.begin(); + watch_list::const_iterator end = wlist.end(); + for (bool first = true; it != end; ++it) { + if (first) + first = false; + else + out << " "; + switch (it->get_kind()) { + case watched::BINARY: + out << it->get_literal(); + if (it->is_learned()) + out << "*"; + break; + case watched::TERNARY: + out << "(" << it->get_literal1() << " " << it->get_literal2() << ")"; + break; + case watched::CLAUSE: + out << "(" << it->get_blocked_literal() << " " << *(ca.get_clause(it->get_clause_offset())) << ")"; + break; + case watched::EXT_CONSTRAINT: + out << it->get_ext_constraint_idx(); + break; + default: + UNREACHABLE(); + } + } + } + +}; diff --git a/lib/sat_watched.h b/lib/sat_watched.h new file mode 100644 index 000000000..83c04e918 --- /dev/null +++ b/lib/sat_watched.h @@ -0,0 +1,136 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_watched.h + +Abstract: + + Element of the SAT solver watchlist. + +Author: + + Leonardo de Moura (leonardo) 2011-05-21. + +Revision History: + +--*/ +#ifndef _SAT_WATCHED_H_ +#define _SAT_WATCHED_H_ + +#include"sat_types.h" +#include"vector.h" + +namespace sat { + /** + A watched element can be: + + 1) A literal: for watched binary clauses + 2) A pair of literals: for watched ternary clauses + 3) A pair (literal, clause-offset): for watched clauses, where the first element of the pair is a literal of the clause. + 4) A external constraint-idx: for external constraints. + + For binary clauses: we use a bit to store whether the binary clause was learned or not. + + Remark: there is not Clause object for binary clauses. + */ + class watched { + public: + enum kind { + BINARY = 0, TERNARY, CLAUSE, EXT_CONSTRAINT + }; + private: + unsigned m_val1; + unsigned m_val2; + public: + watched(literal l, bool learned): + m_val1(l.to_uint()), + m_val2(static_cast(BINARY) + (static_cast(learned) << 2)) { + SASSERT(is_binary_clause()); + SASSERT(get_literal() == l); + SASSERT(is_learned() == learned); + SASSERT(learned || is_binary_non_learned_clause()); + } + + watched(literal l1, literal l2) { + SASSERT(l1 != l2); + if (l1.index() > l2.index()) + std::swap(l1, l2); + m_val1 = l1.to_uint(); + m_val2 = static_cast(TERNARY) + (l2.to_uint() << 2); + SASSERT(is_ternary_clause()); + SASSERT(get_literal1() == l1); + SASSERT(get_literal2() == l2); + } + + watched(literal blocked_lit, clause_offset cls_off): + m_val1(cls_off), + m_val2(static_cast(CLAUSE) + (blocked_lit.to_uint() << 2)) { + SASSERT(is_clause()); + SASSERT(get_blocked_literal() == blocked_lit); + SASSERT(get_clause_offset() == cls_off); + } + + watched(ext_constraint_idx cnstr_idx): + m_val1(cnstr_idx), + m_val2(static_cast(EXT_CONSTRAINT)) { + SASSERT(is_ext_constraint()); + SASSERT(get_ext_constraint_idx() == cnstr_idx); + } + + kind get_kind() const { return static_cast(m_val2 & 3); } + + bool is_binary_clause() const { return get_kind() == BINARY; } + literal get_literal() const { SASSERT(is_binary_clause()); return to_literal(m_val1); } + void set_literal(literal l) { SASSERT(is_binary_clause()); m_val1 = l.to_uint(); } + bool is_learned() const { SASSERT(is_binary_clause()); return (m_val2 >> 2) == 1; } + bool is_binary_non_learned_clause() const { return m_val2 == 0; } + void mark_not_learned() { SASSERT(is_learned()); m_val2 = static_cast(BINARY); SASSERT(!is_learned()); } + + bool is_ternary_clause() const { return get_kind() == TERNARY; } + literal get_literal1() const { SASSERT(is_ternary_clause()); return to_literal(m_val1); } + literal get_literal2() const { SASSERT(is_ternary_clause()); return to_literal(m_val2 >> 2); } + + bool is_clause() const { return get_kind() == CLAUSE; } + clause_offset get_clause_offset() const { SASSERT(is_clause()); return m_val1; } + literal get_blocked_literal() const { SASSERT(is_clause()); return to_literal(m_val2 >> 2); } + void set_clause_offset(clause_offset c) { SASSERT(is_clause()); m_val1 = c; } + void set_blocked_literal(literal l) { SASSERT(is_clause()); m_val2 = static_cast(CLAUSE) + (l.to_uint() << 2); } + void set_clause(literal blocked_lit, clause_offset cls_off) { + m_val1 = cls_off; + m_val2 = static_cast(CLAUSE) + (blocked_lit.to_uint() << 2); + } + + bool is_ext_constraint() const { return get_kind() == EXT_CONSTRAINT; } + ext_constraint_idx get_ext_constraint_idx() const { SASSERT(is_ext_constraint()); return m_val2; } + + bool operator==(watched const & w) const { return m_val1 == w.m_val1 && m_val2 == w.m_val2; } + bool operator!=(watched const & w) const { return !operator==(w); } + }; + + COMPILE_TIME_ASSERT(0 <= watched::BINARY && watched::BINARY <= 3); + COMPILE_TIME_ASSERT(0 <= watched::TERNARY && watched::TERNARY <= 3); + COMPILE_TIME_ASSERT(0 <= watched::CLAUSE && watched::CLAUSE <= 3); + COMPILE_TIME_ASSERT(0 <= watched::EXT_CONSTRAINT && watched::EXT_CONSTRAINT <= 3); + + struct watched_lt { + bool operator()(watched const & w1, watched const & w2) const { + if (w2.is_binary_clause()) return false; + if (w1.is_binary_clause()) return true; + if (w2.is_ternary_clause()) return false; + if (w1.is_ternary_clause()) return true; + return false; + } + }; + + typedef vector watch_list; + + bool erase_clause_watch(watch_list & wlist, clause_offset c); + inline void erase_ternary_watch(watch_list & wlist, literal l1, literal l2) { wlist.erase(watched(l1, l2)); } + + class clause_allocator; + void display(std::ostream & out, clause_allocator const & ca, watch_list const & wlist); +}; + +#endif diff --git a/lib/scanner.cpp b/lib/scanner.cpp new file mode 100644 index 000000000..690c068e4 --- /dev/null +++ b/lib/scanner.cpp @@ -0,0 +1,496 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + scanner.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-03-31. + +Revision History: + +--*/ +#include"scanner.h" + +inline char scanner::read_char() { + if (m_is_interactive) { + ++m_pos; + return m_stream.get(); + } + + if (m_bpos >= m_bend) { + m_buffer[0] = m_last_char; + m_stream.read(m_buffer.c_ptr()+1, m_buffer.size()-1); + m_bend = 1 + static_cast(m_stream.gcount()); + m_bpos = 1; + m_last_char = m_buffer[m_bend-1]; + } + ++m_pos; + if (m_bpos < m_bend) { + return m_buffer[m_bpos++]; + } else { + // increment m_bpos, so unread_char() will work properly + ++m_bpos; + return -1; + } +} + +inline void scanner::unread_char() { + --m_pos; + if (m_is_interactive) { + m_stream.unget(); + } else { + // at most one character can be unread. + SASSERT(m_bpos > 0); + --m_bpos; + } +} + +inline bool scanner::state_ok() { + return m_state != ERROR_TOKEN && m_state != EOF_TOKEN; +} + +void scanner::comment(char delimiter) { + while(state_ok()) { + char ch = read_char(); + if ('\n' == ch) { + ++m_line; + } + if (delimiter == ch || -1 == ch) { + return; + } + } +} + +scanner::token scanner::read_symbol(char ch) { + bool escape = false; + if (m_smt2) + m_string.pop_back(); // remove leading '|' + while (ch != '|' || escape) { + if (ch == EOF) { + // TODO: use error reporting + m_err << "ERROR: unexpected end of file.\n"; + return EOF_TOKEN; + } + if (ch == '\n') { + ++m_line; + } + escape = (ch == '\\'); + m_string.push_back(ch); + ch = read_char(); + } + if (!m_smt2) + m_string.push_back(ch); // don't add trailing '|' + m_string.push_back(0); + m_id = m_string.begin(); + return ID_TOKEN; +} + + +scanner::token scanner::read_id(char first_char) { + char ch; + m_string.reset(); + m_params.reset(); + m_string.push_back(first_char); + + bool is_arith = (m_normalized[(unsigned char) first_char] == '+'); + bool is_alpha = (m_normalized[(unsigned char) first_char] == 'a'); + + ch = read_char(); + // In SMT2 "-20" is an identifier. + if (!m_smt2 && state_ok() && first_char == '-' && m_normalized[(unsigned char) ch] == '0') { + return read_number(ch, false); + } + + if (state_ok() && first_char == '|') { + return read_symbol(ch); + } + + while (state_ok()) { + switch(m_normalized[(unsigned char) ch]) { + case '+': + if (is_arith) { + m_string.push_back(ch); + break; + } + // strings can have hyphens. + if (!is_alpha || ch != '-') { + goto bail_out; + } + case 'a': + case ':': + case '.': + case '0': + if (is_arith) { + goto bail_out; + } + m_string.push_back(ch); + break; + case '[': + m_string.push_back(0); + m_id = m_string.begin(); + if (read_params()) { + return ID_TOKEN; + } + else { + return m_state; + } + default: + goto bail_out; + } + ch = read_char(); + } + return m_state; + + bail_out: + m_string.push_back(0); + m_id = m_string.begin(); + unread_char(); + return ID_TOKEN; +} + +bool scanner::read_params() { + unsigned param_num = 0; + + while (state_ok()) { + char ch = read_char(); + switch (m_normalized[(unsigned char) ch]) { + case '0': + param_num = 10*param_num + (ch - '0'); + break; + case ']': + m_params.push_back(parameter(param_num)); + return true; + case ':': + m_params.push_back(parameter(param_num)); + param_num = 0; + break; + default: + m_string.reset(); + m_string.push_back(ch); + while (true) { + ch = read_char(); + if (ch == ':' || ch == ']') { + m_string.push_back(0); + m_params.push_back(parameter(symbol(m_string.c_ptr()))); + param_num = 0; + if (ch == ':') { + unread_char(); + } + else { + return true; + } + break; + } + if (ch == EOF) { + // TODO: use error reporting + m_err << "ERROR: unexpected character: '" << ((int)ch) << " " << ch << "'.\n"; + m_state = ERROR_TOKEN; + break; + } + m_string.push_back(ch); + } + break; + } + } + return false; +} + +scanner::token scanner::read_number(char first_char, bool is_pos) { + unsigned divide_by = 0; + m_number = rational(first_char - '0'); + m_state = INT_TOKEN; + + while (true) { + char ch = read_char(); + if (m_normalized[(unsigned char) ch] == '0') { + m_number = rational(10)*m_number + rational(ch - '0'); + if (m_state == FLOAT_TOKEN) { + ++divide_by; + } + } + else if (ch == '.') { + m_state = FLOAT_TOKEN; + } + else { + unread_char(); + break; + } + } + if (!is_pos) { + m_number.neg(); + } + if (m_state == FLOAT_TOKEN) { + m_number /= power(rational(10), divide_by); + } + return m_state; +} + +scanner::token scanner::read_string(char delimiter, token result) { + m_string.reset(); + m_params.reset(); + while (true) { + char ch = read_char(); + + if (!state_ok()) { + return m_state; + } + + if (ch == '\n') { + ++m_line; + } + + if (ch == delimiter || ch == EOF) { + m_string.push_back(0); + m_id = m_string.begin(); + return result; + } + + if (ch == '\\') { + m_string.push_back('\\'); + ch = read_char(); + } + m_string.push_back(ch); + } + + return m_state; +} + +scanner::token scanner::read_bv_literal() { + TRACE("scanner", tout << "read_bv_literal\n";); + if (m_bv_token) { + char ch = read_char(); + if (ch == 'x') { + ch = read_char(); + m_number = rational(0); + m_bv_size = 0; + while (true) { + if ('0' <= ch && ch <= '9') { + m_number *= rational(16); + m_number += rational(ch - '0'); + } + else if ('a' <= ch && ch <= 'f') { + m_number *= rational(16); + m_number += rational(10 + (ch - 'a')); + } + else if ('A' <= ch && ch <= 'F') { + m_number *= rational(16); + m_number += rational(10 + (ch - 'A')); + } + else { + unread_char(); + m_state = m_bv_size == 0 ? ERROR_TOKEN : BV_TOKEN; + TRACE("scanner", tout << m_state << ", bv-size: " << m_bv_size << ", INT_TOKEN: " << INT_TOKEN + << ", BV_TOKEN: " << BV_TOKEN << "\n";); + return m_state; + } + m_bv_size += 4; + ch = read_char(); + } + } + else if (ch == 'b') { + ch = read_char(); + m_number = rational(0); + m_bv_size = 0; + while (ch == '0' || ch == '1') { + m_number *= rational(2); + m_number += rational(ch - '0'); + m_bv_size++; + ch = read_char(); + } + unread_char(); + m_state = m_bv_size == 0 ? ERROR_TOKEN : BV_TOKEN; + return m_state; + } + else { + m_state = ERROR_TOKEN; + return m_state; + } + } + else { + // hack for the old parser + char ch = read_char(); + bool is_hex = false; + + m_state = ID_TOKEN; + m_string.reset(); + m_params.reset(); + + // convert to SMT1 format + m_string.push_back('b'); + m_string.push_back('v'); + if (ch == 'x') { + m_string.push_back('h'); + m_string.push_back('e'); + m_string.push_back('x'); + is_hex = true; + } else if (ch == 'b') { + m_string.push_back('b'); + m_string.push_back('i'); + m_string.push_back('n'); + } else { + // TODO: use error reporting + m_err << "ERROR: unexpected character after '#': '" << ((int)ch) << " " << ch << "'.\n"; + m_state = ERROR_TOKEN; + return m_state; + } + + while (true) { + ch = read_char(); + if (ch == '0' || ch == '1' || + (is_hex && + (('0' <= ch && ch <= '9') || + ('a' <= ch && ch <= 'f') || + ('A' <= ch && ch <= 'F')))) { + m_string.push_back(ch); + } else { + unread_char(); + break; + } + } + m_string.push_back(0); + m_id = m_string.begin(); + + return m_state; + } +} + +scanner::scanner(std::istream& stream, std::ostream& err, bool smt2, bool bv_token): + m_line(1), + m_pos(0), + m_id(""), + m_bv_size(UINT_MAX), + m_state(ID_TOKEN), + m_stream(stream), + m_err(err), + m_bpos(1 << 10), + m_bend(1 << 10), + m_last_char(0), + m_smt2(smt2), + m_bv_token(bv_token) { + char ch; + + m_is_interactive = &stream == &std::cin; + m_buffer.resize(m_bpos); + + for (int i = 0; i < 256; ++i) { + m_normalized[i] = (char) i; + } + + m_normalized[static_cast('\t')] = ' '; + m_normalized[static_cast('\r')] = ' '; + + // assert ('a' < 'z'); + for (ch = 'b'; ch <= 'z'; ++ch) { + m_normalized[static_cast(ch)] = 'a'; + } + for (ch = 'A'; ch <= 'Z'; ++ch) { + m_normalized[static_cast(ch)] = 'a'; + } + // assert ('0' < '9', '9' - '0' == 9); + for (ch = '1'; ch <= '9'; ++ch) { + m_normalized[static_cast(ch)] = '0'; + } + + if (m_smt2) { + // SMT2 3.1, "Symbols": ~ ! @ $ % ^ & * _ - + = < > . ? / + m_normalized[static_cast('~')] = 'a'; + m_normalized[static_cast('!')] = 'a'; + m_normalized[static_cast('@')] = 'a'; + m_normalized[static_cast('$')] = 'a'; + m_normalized[static_cast('%')] = 'a'; + m_normalized[static_cast('^')] = 'a'; + m_normalized[static_cast('&')] = 'a'; + m_normalized[static_cast('*')] = 'a'; + m_normalized[static_cast('_')] = 'a'; + m_normalized[static_cast('-')] = 'a'; + m_normalized[static_cast('+')] = 'a'; + m_normalized[static_cast('=')] = 'a'; + m_normalized[static_cast('<')] = 'a'; + m_normalized[static_cast('>')] = 'a'; + m_normalized[static_cast('.')] = 'a'; + m_normalized[static_cast('?')] = 'a'; + m_normalized[static_cast('/')] = 'a'; + + // SMT2 3.1, "Hexadecimals", "Binaries" + m_normalized[static_cast('#')] = '#'; + + m_normalized[static_cast('|')] = '+'; + } else { + m_normalized[static_cast('=')] = '+'; + m_normalized[static_cast('<')] = '+'; + m_normalized[static_cast('>')] = '+'; + m_normalized[static_cast('+')] = '+'; + m_normalized[static_cast('-')] = '+'; + m_normalized[static_cast('*')] = '+'; + m_normalized[static_cast('/')] = '+'; + m_normalized[static_cast('%')] = '+'; + m_normalized[static_cast('~')] = '+'; + m_normalized[static_cast('&')] = '+'; + m_normalized[static_cast('@')] = '+'; + m_normalized[static_cast('#')] = '+'; + m_normalized[static_cast('|')] = '+'; + m_normalized[static_cast('\\')] = '+'; + + m_normalized[static_cast('.')] = '.'; + + m_normalized[static_cast('_')] = 'a'; + m_normalized[static_cast('\'')] = 'a'; + m_normalized[static_cast('!')] = 'a'; + m_normalized[static_cast('?')] = 'a'; + } +} + +scanner::token scanner::scan() { + while (state_ok()) { + char ch = read_char(); + switch (m_normalized[(unsigned char) ch]) { + case ' ': + break; + case '\n': + m_pos = 0; + ++m_line; + break; + case ';': + comment('\n'); + break; + case ':': + return COLON; + case '(': + return LEFT_PAREN; + case ')': + return RIGHT_PAREN; + case '?': + case '$': + case 'a': + case '+': + case '.': + return read_id(ch); + case '{': + return read_string('}',COMMENT_TOKEN); + case '"': + return read_string('"',STRING_TOKEN); + case '0': + return read_number(ch, true); + case '#': + return read_bv_literal(); + case -1: + m_state = EOF_TOKEN; + break; + default: + // TODO: use error reporting + m_err << "ERROR: unexpected character: '" << ((int)ch) << " " << ch << "'.\n"; + m_state = ERROR_TOKEN; + break; + } + } + return m_state; +} + + diff --git a/lib/scanner.h b/lib/scanner.h new file mode 100644 index 000000000..c93c7a82d --- /dev/null +++ b/lib/scanner.h @@ -0,0 +1,92 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + scanner.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-03-31. + +Revision History: + +--*/ +#ifndef _SCANNER_H_ +#define _SCANNER_H_ + +#include"ast.h" + +class scanner { +public: + + enum token { + LEFT_PAREN = 1, + RIGHT_PAREN, + COLON, + ID_TOKEN, + STRING_TOKEN, + COMMENT_TOKEN, + INT_TOKEN, + BV_TOKEN, + FLOAT_TOKEN, + EOF_TOKEN, + ERROR_TOKEN + }; + + scanner(std::istream& stream, std::ostream& err, bool smt2, bool bv_token=false); + + ~scanner() {} + + int get_line() const { return m_line; } + + int get_pos() const { return m_pos; } + + symbol const & get_id() const { return m_id; } + + rational get_number() const { return m_number; } + + unsigned get_bv_size() const { return m_bv_size; } + + vector const & get_params() const { return m_params; } + + token scan(); + +private: + int m_line; + int m_pos; + symbol m_id; + rational m_number; + unsigned m_bv_size; + token m_state; + char m_normalized[256]; + vector m_string; + std::istream& m_stream; + std::ostream& m_err; + vector m_params; + buffer m_buffer; + unsigned m_bpos; + unsigned m_bend; + char m_last_char; + bool m_is_interactive; + bool m_smt2; + bool m_bv_token; + + char read_char(); + token read_symbol(char ch); + void unread_char(); + void comment(char delimiter); + token read_id(char first_char); + bool read_params(); + token read_number(char first_char, bool is_pos); + token read_string(char delimiter, token result); + token read_bv_literal(); + bool state_ok(); +}; + +#endif /* _SCANNER_H_ */ + diff --git a/lib/scoped_ctrl_c.cpp b/lib/scoped_ctrl_c.cpp new file mode 100644 index 000000000..feaceea8d --- /dev/null +++ b/lib/scoped_ctrl_c.cpp @@ -0,0 +1,58 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + scoped_ctrl_c.cpp + +Abstract: + + Scoped control-c handler. + +Author: + + Leonardo de Moura (leonardo) 2011-04-27. + +Revision History: + +--*/ +#include +#include +#include"scoped_ctrl_c.h" + +scoped_ctrl_c * scoped_ctrl_c::g_obj = 0; + +void scoped_ctrl_c::on_ctrl_c(int) { + if (g_obj->m_first) { + g_obj->m_cancel_eh(); + if (g_obj->m_once) { + g_obj->m_first = false; + signal(SIGINT, on_ctrl_c); // re-install the handler + } + } + else { + signal(SIGINT, g_obj->m_old_handler); + raise(SIGINT); + } +} + +scoped_ctrl_c::scoped_ctrl_c(event_handler & eh, bool once, bool enabled): + m_cancel_eh(eh), + m_first(true), + m_once(once), + m_enabled(enabled), + m_old_scoped_ctrl_c(g_obj) { + if (m_enabled) { + g_obj = this; + m_old_handler = signal(SIGINT, on_ctrl_c); + } +} + +scoped_ctrl_c::~scoped_ctrl_c() { + if (m_enabled) { + g_obj = m_old_scoped_ctrl_c; + if (m_old_handler != SIG_ERR) { + signal(SIGINT, m_old_handler); + } + } +} diff --git a/lib/scoped_ctrl_c.h b/lib/scoped_ctrl_c.h new file mode 100644 index 000000000..b3102dda0 --- /dev/null +++ b/lib/scoped_ctrl_c.h @@ -0,0 +1,42 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + scoped_ctrl_c.h + +Abstract: + + Scoped control-c handler. + +Author: + + Leonardo de Moura (leonardo) 2011-04-27. + +Revision History: + +--*/ +#ifndef _SCOPED_CTRL_C_H_ +#define _SCOPED_CTRL_C_H_ + +#include"event_handler.h" + +struct scoped_ctrl_c { + event_handler & m_cancel_eh; + bool m_first; + bool m_once; + bool m_enabled; + void (*m_old_handler)(int); + scoped_ctrl_c * m_old_scoped_ctrl_c; + static scoped_ctrl_c * g_obj; + static void on_ctrl_c(int); +public: + // If once == true, then the cancel_eh is invoked only at the first Ctrl-C. + // The next time, the old signal handler will take over. + // if enabled == false, then scoped_ctrl_c is a noop + scoped_ctrl_c(event_handler & eh, bool once=true, bool enabled=true); + ~scoped_ctrl_c(); + void reset() { m_first = true; } +}; + +#endif diff --git a/lib/scoped_numeral.h b/lib/scoped_numeral.h new file mode 100644 index 000000000..25e6422df --- /dev/null +++ b/lib/scoped_numeral.h @@ -0,0 +1,163 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + scoped_numeral.h + +Abstract: + + Wrapper for easying the pain when using primitive numeral objects such as: + mpz, mpq, mpbq, mpf, mpzp + +Author: + + Leonardo de Moura (leonardo) 2011-12-03 + +Revision History: + +--*/ +#ifndef _SCOPED_NUMERAL_H_ +#define _SCOPED_NUMERAL_H_ + +template +class _scoped_numeral { +public: + typedef typename Manager::numeral numeral; +private: + Manager & m_manager; + numeral m_num; +public: + _scoped_numeral(Manager & m):m_manager(m) {} + _scoped_numeral(_scoped_numeral const & n):m_manager(n.m_manager) { m().set(m_num, n.m_num); } + ~_scoped_numeral() { m_manager.del(m_num); } + + Manager & m() const { return m_manager; } + + operator numeral const &() const { return m_num; } + operator numeral&() { return m_num; } + numeral const & get() const { return m_num; } + numeral & get() { return m_num; } + + _scoped_numeral & operator=(_scoped_numeral & n) { + if (this == &n) + return *this; + m().set(m_num, n.m_num); + return *this; + } + + _scoped_numeral & operator=(int n) { + m().set(m_num, n); + return *this; + } + + _scoped_numeral & operator=(numeral const & n) { + m().set(m_num, n); + return *this; + } + + void reset() { + m().reset(m_num); + } + + void swap(_scoped_numeral & n) { + m_num.swap(n.m_num); + } + + void swap(numeral & n) { + m_num.swap(n); + } + + _scoped_numeral & operator+=(numeral const & a) { + m().add(m_num, a, m_num); + return *this; + } + + _scoped_numeral & operator-=(numeral const & a) { + m().sub(m_num, a, m_num); + return *this; + } + + _scoped_numeral & operator*=(numeral const & a) { + m().mul(m_num, a, m_num); + return *this; + } + + _scoped_numeral & operator/=(numeral const & a) { + m().div(m_num, a, m_num); + return *this; + } + + _scoped_numeral & operator%=(numeral const & a) { + m().rem(m_num, a, m_num); + return *this; + } + + friend bool operator==(_scoped_numeral const & a, numeral const & b) { + return a.m().eq(a, b); + } + + friend bool operator<(_scoped_numeral const & a, numeral const & b) { + return a.m().lt(a, b); + } + + friend bool operator>(_scoped_numeral const & a, numeral const & b) { + return a.m().gt(a, b); + } + + friend bool operator<=(_scoped_numeral const & a, numeral const & b) { + return a.m().le(a, b); + } + + friend bool operator>=(_scoped_numeral const & a, numeral const & b) { + return a.m().ge(a, b); + } + + friend bool is_zero(_scoped_numeral const & a) { + return a.m().is_zero(a); + } + + friend bool is_pos(_scoped_numeral const & a) { + return a.m().is_pos(a); + } + + friend bool is_neg(_scoped_numeral const & a) { + return a.m().is_neg(a); + } + + friend bool is_nonneg(_scoped_numeral const & a) { + return a.m().is_nonneg(a); + } + + friend bool is_nonpos(_scoped_numeral const & a) { + return a.m().is_nonpos(a); + } + + void neg() { + m().neg(m_num); + } + + friend _scoped_numeral operator+(_scoped_numeral const & r1, numeral const & r2) { + return _scoped_numeral(r1) += r2; + } + + friend _scoped_numeral operator-(_scoped_numeral const & r1, numeral const & r2) { + return _scoped_numeral(r1) -= r2; + } + + friend _scoped_numeral operator*(_scoped_numeral const & r1, numeral const & r2) { + return _scoped_numeral(r1) *= r2; + } + + friend _scoped_numeral operator/(_scoped_numeral const & r1, numeral const & r2) { + return _scoped_numeral(r1) /= r2; + } + + friend std::ostream & operator<<(std::ostream & out, _scoped_numeral const & s) { + s.m().display(out, s); + return out; + } + +}; + +#endif diff --git a/lib/scoped_numeral_buffer.h b/lib/scoped_numeral_buffer.h new file mode 100644 index 000000000..7a9d8ef96 --- /dev/null +++ b/lib/scoped_numeral_buffer.h @@ -0,0 +1,68 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + scoped_numeral_buffer.h + +Abstract: + + Wrapper for easying the pain when using buffers of numerals. + +Author: + + Leonardo de Moura (leonardo) 2012-01-23 + +Revision History: + +--*/ +#ifndef _SCOPED_NUMERAL_BUFFER_H_ +#define _SCOPED_NUMERAL_BUFFER_H_ + +#include"buffer.h" + +template +class _scoped_numeral_buffer : public sbuffer { + typedef sbuffer super; + Manager & m_manager; + _scoped_numeral_buffer(_scoped_numeral_buffer const & v); +public: + _scoped_numeral_buffer(Manager & m):m_manager(m) {} + ~_scoped_numeral_buffer() { + reset(); + } + + void reset() { + unsigned sz = this->size(); + for (unsigned i = 0; i < sz; i++) { + m().del(this->operator[](i)); + } + super::reset(); + } + + Manager & m() const { return m_manager; } + + void push_back(typename Manager::numeral const & v) { + super::push_back(typename Manager::numeral()); + m_manager.set(this->back(), v); + } + + void shrink(unsigned sz) { + unsigned old_sz = this->size(); + if (old_sz == sz) + return; + for (unsigned i = sz; i < old_sz; i++) + m().del(this->operator[](i)); + super::shrink(sz); + } + + void resize(unsigned sz) { + unsigned old_sz = this->size(); + if (sz <= old_sz) + shrink(sz); + typename Manager::numeral zero(0); + super::resize(sz, zero); + } +}; + +#endif diff --git a/lib/scoped_numeral_vector.h b/lib/scoped_numeral_vector.h new file mode 100644 index 000000000..4375babb3 --- /dev/null +++ b/lib/scoped_numeral_vector.h @@ -0,0 +1,67 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + scoped_numeral_vector.h + +Abstract: + + Wrapper for easying the pain when using vectors of numerals. + +Author: + + Leonardo de Moura (leonardo) 2011-12-03 + +Revision History: + +--*/ +#ifndef _SCOPED_NUMERAL_VECTOR_H_ +#define _SCOPED_NUMERAL_VECTOR_H_ + +#include"vector.h" + +template +class _scoped_numeral_vector : public svector { + Manager & m_manager; + _scoped_numeral_vector(_scoped_numeral_vector const & v); +public: + _scoped_numeral_vector(Manager & m):m_manager(m) {} + ~_scoped_numeral_vector() { + reset(); + } + + void reset() { + unsigned sz = this->size(); + for (unsigned i = 0; i < sz; i++) { + m().del(this->operator[](i)); + } + svector::reset(); + } + + Manager & m() const { return m_manager; } + + void push_back(typename Manager::numeral const & v) { + svector::push_back(typename Manager::numeral()); + m_manager.set(this->back(), v); + } + + void shrink(unsigned sz) { + unsigned old_sz = this->size(); + if (old_sz == sz) + return; + for (unsigned i = sz; i < old_sz; i++) + m().del(this->operator[](i)); + svector::shrink(sz); + } + + void resize(unsigned sz) { + unsigned old_sz = this->size(); + if (sz <= old_sz) + shrink(sz); + typename Manager::numeral zero(0); + svector::resize(sz, zero); + } +}; + +#endif diff --git a/lib/scoped_ptr_vector.h b/lib/scoped_ptr_vector.h new file mode 100644 index 000000000..24b501693 --- /dev/null +++ b/lib/scoped_ptr_vector.h @@ -0,0 +1,55 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + scoped_ptr_vector.h + +Abstract: + + Basic template of scoped ptrs. + TODO: improve + +Author: + + Leonardo (leonardo) 2011-12-29 + +Notes: + +--*/ +#ifndef _SCOPED_PTR_VECTOR_H_ +#define _SCOPED_PTR_VECTOR_H_ + +#include"vector.h" +#include"util.h" + +template +class scoped_ptr_vector { + ptr_vector m_vector; +public: + ~scoped_ptr_vector() { reset(); } + void reset() { std::for_each(m_vector.begin(), m_vector.end(), delete_proc()); m_vector.reset(); } + void push_back(T * ptr) { m_vector.push_back(ptr); } + T * operator[](unsigned idx) const { return m_vector[idx]; } + void set(unsigned idx, T * ptr) { + if (m_vector[idx] == ptr) + return; + dealloc(m_vector[idx]); + m_vector[idx] = ptr; + } + unsigned size() const { return m_vector.size(); } + bool empty() const { return m_vector.empty(); } + void resize(unsigned sz) { + if (sz < m_vector.size()) { + for (unsigned i = m_vector.size(); i < sz; i++) + dealloc(m_vector[i]); + m_vector.shrink(sz); + } + else { + for (unsigned i = m_vector.size(); i < sz; i++) + push_back(0); + } + } +}; + +#endif diff --git a/lib/scoped_timer.cpp b/lib/scoped_timer.cpp new file mode 100644 index 000000000..008529308 --- /dev/null +++ b/lib/scoped_timer.cpp @@ -0,0 +1,212 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + scoped_timer.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2011-04-26. + +Revision History: + +--*/ +#include"z3_exception.h" +#include"z3_omp.h" +#ifdef _WINDOWS +// Windows +#include +#elif defined(__APPLE__) && defined(__MACH__) +// Mac OS X +#include +#include +#include +#include +#include +#else +// Linux +#include +#include +#include +#include"warning.h" +#define CLOCKID CLOCK_PROCESS_CPUTIME_ID +#define SIG SIGRTMIN +// --------- +#endif + +#include"scoped_timer.h" +#include"util.h" +#include + +struct scoped_timer::imp { + event_handler * m_eh; +#ifdef _WINDOWS + HANDLE m_timer; + bool m_first; +#elif defined(__APPLE__) && defined(__MACH__) + // Mac OS X + pthread_t m_thread_id; + pthread_attr_t m_attributes; + unsigned m_interval; + pthread_cond_t m_condition_var; +#else + // Linux + static void * g_timer; + void (*m_old_handler)(int); + void * m_old_timer; + timer_t m_timerid; +#endif + +#ifdef _WINDOWS + static void CALLBACK abort_proc(PVOID param, BOOLEAN timer_or_wait_fired) { + imp * obj = static_cast(param); + if (obj->m_first) { + obj->m_first = false; + } + else { + obj->m_eh->operator()(); + } + } +#elif defined(__APPLE__) && defined(__MACH__) + // Mac OS X + static void * thread_func(void * arg) { + scoped_timer::imp * st = static_cast(arg); + + pthread_mutex_t mutex; + clock_serv_t host_clock; + struct timespec abstime; + mach_timespec_t now; + unsigned long long nano = static_cast(st->m_interval) * 1000000ull; + + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &host_clock); + + if (pthread_mutex_init(&mutex, NULL) != 0) + throw default_exception("failed to initialize timer mutex"); + if (pthread_cond_init(&st->m_condition_var, NULL) != 0) + throw default_exception("failed to initialize timer condition variable"); + + abstime.tv_sec = nano / 1000000000ull; + abstime.tv_nsec = nano % 1000000000ull; + + pthread_mutex_lock(&mutex); + clock_get_time(host_clock, &now); + ADD_MACH_TIMESPEC(&abstime, &now); + int e = pthread_cond_timedwait(&st->m_condition_var, &mutex, &abstime); + if (e != 0 && e != ETIMEDOUT) + throw default_exception("failed to start timed wait"); + st->m_eh->operator()(); + pthread_mutex_unlock(&mutex); + + if (pthread_mutex_destroy(&mutex) != 0) + throw default_exception("failed to destroy pthread mutex"); + if (pthread_cond_destroy(&st->m_condition_var) != 0) + throw default_exception("failed to destroy pthread condition variable"); + return st; + } +#else + static void sig_handler(int) { + static_cast(g_timer)->m_eh->operator()(); + } +#endif + + + imp(unsigned ms, event_handler * eh): + m_eh(eh) { +#ifdef _WINDOWS + m_first = true; + CreateTimerQueueTimer(&m_timer, + NULL, + abort_proc, + this, + 0, + ms, + WT_EXECUTEINTIMERTHREAD); +#elif defined(__APPLE__) && defined(__MACH__) + // Mac OS X + m_interval = ms; + if (pthread_attr_init(&m_attributes) != 0) + throw default_exception("failed to initialize timer thread attributes"); + if (pthread_create(&m_thread_id, &m_attributes, &thread_func, this) != 0) + throw default_exception("failed to start timer thread"); +#else + // Linux version + if (omp_in_parallel()) { + // It doesn't work in with more than one thread. + // SIGEV_SIGNAL: the event is handled by the process not by the thread that installed the handler. + // SIGEV_THREAD: the event is handled by a new thread (Z3 crashes with this configuration). + // + // It seems the way to go is SIGEV_SIGNAL, but I have to find a way to identify the thread the event is meant to. + return; + } + m_old_timer = g_timer; + g_timer = this; + m_old_handler = signal(SIG, sig_handler); + + struct sigevent sev; + memset(&sev, 0, sizeof(sigevent)); + sev.sigev_notify = SIGEV_SIGNAL; + sev.sigev_signo = SIG; + sev.sigev_value.sival_ptr = &m_timerid; + if (timer_create(CLOCKID, &sev, &m_timerid) == -1) + throw default_exception("failed to create timer"); + + unsigned long long nano = static_cast(ms) * 1000000ull; + struct itimerspec its; + its.it_value.tv_sec = nano / 1000000000ull; + its.it_value.tv_nsec = nano % 1000000000ull; + its.it_interval.tv_sec = 0; // timer experies once + its.it_interval.tv_nsec = 0; + if (timer_settime(m_timerid, 0, &its, NULL) == -1) + throw default_exception("failed to set timer"); +#endif + } + + ~imp() { +#ifdef _WINDOWS + DeleteTimerQueueTimer(NULL, + m_timer, + INVALID_HANDLE_VALUE); +#elif defined(__APPLE__) && defined(__MACH__) + // Mac OS X + pthread_cond_signal(&m_condition_var); // this is okay to fail + if (pthread_join(m_thread_id, NULL) != 0) + throw default_exception("failed to join thread"); + if (pthread_attr_destroy(&m_attributes) != 0) + throw default_exception("failed to destroy pthread attributes object"); +#else + // Linux version + if (omp_in_parallel()) + return; // see comments in the constructor. + timer_delete(m_timerid); + if (m_old_handler != SIG_ERR) + signal(SIG, m_old_handler); + g_timer = m_old_timer; +#endif + } + +}; + +#ifdef _WINDOWS +#elif defined(__APPLE__) && defined(__MACH__) +// Mac OS X +#else +// Linux +void * scoped_timer::imp::g_timer = 0; +#endif + +scoped_timer::scoped_timer(unsigned ms, event_handler * eh) { + if (ms != UINT_MAX) + m_imp = alloc(imp, ms, eh); + else + m_imp = 0; +} + +scoped_timer::~scoped_timer() { + if (m_imp) + dealloc(m_imp); +} diff --git a/lib/scoped_timer.h b/lib/scoped_timer.h new file mode 100644 index 000000000..68df09f9f --- /dev/null +++ b/lib/scoped_timer.h @@ -0,0 +1,32 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + scoped_timer.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2011-04-26. + +Revision History: + +--*/ +#ifndef _SCOPED_TIMER_H_ +#define _SCOPED_TIMER_H_ + +#include"event_handler.h" + +class scoped_timer { + struct imp; + imp * m_imp; +public: + scoped_timer(unsigned ms, event_handler * eh); + ~scoped_timer(); +}; + +#endif diff --git a/lib/seq_decl_plugin.cpp b/lib/seq_decl_plugin.cpp new file mode 100644 index 000000000..44511cd1f --- /dev/null +++ b/lib/seq_decl_plugin.cpp @@ -0,0 +1,265 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + dl_decl_plugin.h + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2011-14-11 + +Revision History: + +--*/ +#include "seq_decl_plugin.h" +#include "arith_decl_plugin.h" +#include "array_decl_plugin.h" +#include + +seq_decl_plugin::seq_decl_plugin(): m_init(false) {} + +void seq_decl_plugin::finalize() { + for (unsigned i = 0; i < m_sigs.size(); ++i) + dealloc(m_sigs[i]); +} + +bool seq_decl_plugin::is_sort_param(sort* s, unsigned& idx) { + return + s->get_name().is_numerical() && + (idx = s->get_name().get_num(), true); +} + +bool seq_decl_plugin::match(ptr_vector& binding, sort* s, sort* sP) { + if (s == sP) return true; + unsigned i; + if (is_sort_param(sP, i)) { + if (binding.size() <= i) binding.resize(i+1); + if (binding[i] && (binding[i] != s)) return false; + binding[i] = s; + return true; + } + + if (s->get_family_id() == sP->get_family_id() && + s->get_decl_kind() == sP->get_decl_kind() && + s->get_name() == sP->get_name()) { + SASSERT(s->get_num_parameters() == sP->get_num_parameters()); + for (unsigned i = 0; i < s->get_num_parameters(); ++i) { + parameter const& p = s->get_parameter(i); + if (p.is_ast() && is_sort(p.get_ast())) { + parameter const& p2 = sP->get_parameter(i); + if (!match(binding, to_sort(p.get_ast()), to_sort(p2.get_ast()))) return false; + } + } + return true; + } + else { + return false; + } +} + +void seq_decl_plugin::match(psig& sig, unsigned dsz, sort *const* dom, sort* range, sort_ref& range_out) { + ptr_vector binding; + ast_manager& m = *m_manager; + if (sig.m_dom.size() != dsz) { + std::ostringstream strm; + strm << "Unexpected number of arguments to '" << sig.m_name << "' "; + strm << sig.m_dom.size() << " arguments expected " << dsz << " given"; + m.raise_exception(strm.str().c_str()); + } + bool is_match = true; + for (unsigned i = 0; is_match && i < dsz; ++i) { + is_match = match(binding, dom[i], sig.m_dom[i].get()); + } + if (range && is_match) { + is_match = match(binding, range, sig.m_range); + } + if (!is_match) { + std::ostringstream strm; + strm << "Sort of polymorphic function '" << sig.m_name << "' "; + strm << "does not match the declared type"; + m.raise_exception(strm.str().c_str()); + } + if (!range && dsz == 0) { + std::ostringstream strm; + strm << "Sort of polymorphic function '" << sig.m_name << "' "; + strm << "is ambiguous. Function takes no arguments and sort of range has not been constrained"; + m.raise_exception(strm.str().c_str()); + } + range_out = apply_binding(binding, sig.m_range); +} + +sort* seq_decl_plugin::apply_binding(ptr_vector const& binding, sort* s) { + unsigned i; + if (is_sort_param(s, i)) { + if (binding.size() <= i || !binding[i]) { + m_manager->raise_exception("Expecting type parameter to be bound"); + } + return binding[i]; + } + if (is_sort_of(s, m_family_id, SEQ_SORT) || is_sort_of(s, m_family_id, RE_SORT)) { + SASSERT(s->get_num_parameters() == 1); + SASSERT(s->get_parameter(0).is_ast()); + SASSERT(is_sort(s->get_parameter(0).get_ast())); + sort* p = apply_binding(binding, to_sort(s->get_parameter(0).get_ast())); + parameter param(p); + return mk_sort(s->get_decl_kind(), 1, ¶m); + } + return s; +} + + +void seq_decl_plugin::init() { + if(m_init) return; + ast_manager& m = *m_manager; + m_init = true; + sort* A = m.mk_sort(symbol((unsigned)0)); + sort* B = m.mk_sort(symbol((unsigned)1)); + parameter paramA(A); + sort* seqA = m.mk_sort(m_family_id, SEQ_SORT, 1, ¶mA); + sort* reA = m.mk_sort(m_family_id, RE_SORT, 1, ¶mA); + sort* seqAseqA[2] = { seqA, seqA }; + sort* seqAA[2] = { seqA, A }; + sort* seqAB[2] = { seqA, B }; + sort* seqAreA[2] = { seqA, reA }; + sort* AseqA[2] = { A, seqA }; + sort* reAreA[2] = { reA, reA }; + sort* AA[2] = { A, A }; + sort* seqABB[3] = { seqA, B, B }; + sort* boolT = m.mk_bool_sort(); + sort* intT = arith_util(m).mk_int(); + sort* predA = array_util(m).mk_array_sort(A, boolT); + m_sigs.resize(LAST_SEQ_OP); + // TBD: have (par ..) construct and load parameterized signature from premable. + m_sigs[OP_SEQ_UNIT] = alloc(psig, m, "seq-unit", 1, 1, &A, seqA); + m_sigs[OP_SEQ_EMPTY] = alloc(psig, m, "seq-empty", 1, 0, 0, seqA); + m_sigs[OP_SEQ_CONCAT] = alloc(psig, m, "seq-concat", 1, 2, seqAseqA, seqA); + m_sigs[OP_SEQ_CONS] = alloc(psig, m, "seq-cons", 1, 2, AseqA, seqA); + m_sigs[OP_SEQ_REV_CONS] = alloc(psig, m, "seq-rev-cons", 1, 2, seqAA, seqA); + m_sigs[OP_SEQ_HEAD] = alloc(psig, m, "seq-head", 1, 1, &seqA, A); + m_sigs[OP_SEQ_TAIL] = alloc(psig, m, "seq-tail", 1, 1, &seqA, seqA); + m_sigs[OP_SEQ_LAST] = alloc(psig, m, "seq-last", 1, 1, &seqA, A); + m_sigs[OP_SEQ_FIRST] = alloc(psig, m, "seq-first", 1, 1, &seqA, seqA); + m_sigs[OP_SEQ_PREFIX_OF] = alloc(psig, m, "seq-prefix-of", 1, 2, seqAseqA, boolT); + m_sigs[OP_SEQ_SUFFIX_OF] = alloc(psig, m, "seq-suffix-of", 1, 2, seqAseqA, boolT); + m_sigs[OP_SEQ_SUBSEQ_OF] = alloc(psig, m, "seq-subseq-of", 1, 2, seqAseqA, boolT); + m_sigs[OP_SEQ_EXTRACT] = alloc(psig, m, "seq-extract", 2, 3, seqABB, seqA); + m_sigs[OP_SEQ_NTH] = alloc(psig, m, "seq-nth", 2, 2, seqAB, A); + m_sigs[OP_SEQ_LENGTH] = alloc(psig, m, "seq-length", 1, 1, &seqA, intT); + m_sigs[OP_RE_PLUS] = alloc(psig, m, "re-plus", 1, 1, &reA, reA); + m_sigs[OP_RE_STAR] = alloc(psig, m, "re-star", 1, 1, &reA, reA); + m_sigs[OP_RE_OPTION] = alloc(psig, m, "re-option", 1, 1, &reA, reA); + m_sigs[OP_RE_RANGE] = alloc(psig, m, "re-range", 1, 2, AA, reA); + m_sigs[OP_RE_CONCAT] = alloc(psig, m, "re-concat", 1, 2, reAreA, reA); + m_sigs[OP_RE_UNION] = alloc(psig, m, "re-union", 1, 2, reAreA, reA); + m_sigs[OP_RE_INTERSECT] = alloc(psig, m, "re-intersect", 1, 2, reAreA, reA); + m_sigs[OP_RE_DIFFERENCE] = alloc(psig, m, "re-difference", 1, 2, reAreA, reA); + m_sigs[OP_RE_COMPLEMENT] = alloc(psig, m, "re-complement", 1, 1, &reA, reA); + m_sigs[OP_RE_LOOP] = alloc(psig, m, "re-loop", 1, 1, &reA, reA); + m_sigs[OP_RE_EMPTY_SEQ] = alloc(psig, m, "re-empty-seq", 1, 0, 0, reA); + m_sigs[OP_RE_EMPTY_SET] = alloc(psig, m, "re-empty-set", 1, 0, 0, reA); + m_sigs[OP_RE_FULL_SET] = alloc(psig, m, "re-full-set", 1, 0, 0, reA); + m_sigs[OP_RE_OF_SEQ] = alloc(psig, m, "re-of-seq", 1, 1, &seqA, reA); + m_sigs[OP_RE_OF_PRED] = alloc(psig, m, "re-of-pred", 1, 1, &predA, reA); + m_sigs[OP_RE_MEMBER] = alloc(psig, m, "re-member", 1, 2, seqAreA, boolT); +} + +sort * seq_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) { + init(); + ast_manager& m = *m_manager; + switch (k) { + case SEQ_SORT: + if (num_parameters != 1) { + m.raise_exception("Invalid sequence sort, expecting one parameter"); + } + if (!parameters[0].is_ast() || !is_sort(parameters[0].get_ast())) { + m.raise_exception("invalid sequence sort, parameter is not a sort"); + } + return m.mk_sort(symbol("Seq"), sort_info(m_family_id, SEQ_SORT, num_parameters, parameters)); + case RE_SORT: + if (num_parameters != 1) { + m.raise_exception("Invalid regex sort, expecting one parameter"); + } + if (!parameters[0].is_ast() || !is_sort(parameters[0].get_ast())) { + m.raise_exception("invalid regex sort, parameter is not a sort"); + } + return m.mk_sort(symbol("RegEx"), sort_info(m_family_id, RE_SORT, num_parameters, parameters)); + default: + UNREACHABLE(); + return 0; + } +} + +func_decl * seq_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + init(); + ast_manager& m = *m_manager; + sort_ref rng(m); + switch(k) { + case OP_SEQ_UNIT: + case OP_SEQ_EMPTY: + case OP_SEQ_CONCAT: + case OP_SEQ_CONS: + case OP_SEQ_REV_CONS: + case OP_SEQ_HEAD: + case OP_SEQ_TAIL: + case OP_SEQ_LAST: + case OP_SEQ_FIRST: + case OP_SEQ_PREFIX_OF: + case OP_SEQ_SUFFIX_OF: + case OP_SEQ_SUBSEQ_OF: + case OP_SEQ_LENGTH: + case OP_RE_PLUS: + case OP_RE_STAR: + case OP_RE_OPTION: + case OP_RE_RANGE: + case OP_RE_CONCAT: + case OP_RE_UNION: + case OP_RE_INTERSECT: + case OP_RE_DIFFERENCE: + case OP_RE_COMPLEMENT: + case OP_RE_EMPTY_SEQ: + case OP_RE_EMPTY_SET: + case OP_RE_OF_SEQ: + case OP_RE_OF_PRED: + case OP_RE_MEMBER: + match(*m_sigs[k], arity, domain, range, rng); + return m.mk_func_decl(m_sigs[k]->m_name, arity, domain, rng, func_decl_info(m_family_id, k)); + case OP_SEQ_EXTRACT: + case OP_SEQ_NTH: + // TBD check numeric arguments for being BVs or integers. + match(*m_sigs[k], arity, domain, range, rng); + return m.mk_func_decl(m_sigs[k]->m_name, arity, domain, rng, func_decl_info(m_family_id, k)); + case OP_RE_LOOP: + match(*m_sigs[k], arity, domain, range, rng); + if (num_parameters != 2 || !parameters[0].is_int() || !parameters[1].is_int()) { + m.raise_exception("Expecting two numeral parameters to function re-loop"); + } + return m.mk_func_decl(m_sigs[k]->m_name, arity, domain, rng, func_decl_info(m_family_id, k, num_parameters, parameters)); + default: + UNREACHABLE(); + return 0; + } +} + +void seq_decl_plugin::get_op_names(svector & op_names, symbol const & logic) { + init(); + for (unsigned i = 0; i < m_sigs.size(); ++i) { + op_names.push_back(builtin_name(m_sigs[i]->m_name.str().c_str(), i)); + } +} + +void seq_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { + init(); + sort_names.push_back(builtin_name("Seq", SEQ_SORT)); + sort_names.push_back(builtin_name("RegEx", RE_SORT)); +} + +bool seq_decl_plugin::is_value(app* e) const { + // TBD: empty sequence is a value. + return false; +} diff --git a/lib/seq_decl_plugin.h b/lib/seq_decl_plugin.h new file mode 100644 index 000000000..c055694eb --- /dev/null +++ b/lib/seq_decl_plugin.h @@ -0,0 +1,120 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + seq_decl_plugin.h + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2011-14-11 + +Revision History: + +--*/ +#ifndef _SEQ_DECL_PLUGIN_H_ +#define _SEQ_DECL_PLUGIN_H_ + +#include "ast.h" + + +enum seq_sort_kind { + SEQ_SORT, + RE_SORT +}; + +enum seq_op_kind { + OP_SEQ_UNIT, + OP_SEQ_EMPTY, + OP_SEQ_CONCAT, + OP_SEQ_CONS, + OP_SEQ_REV_CONS, + OP_SEQ_HEAD, + OP_SEQ_TAIL, + OP_SEQ_LAST, + OP_SEQ_FIRST, + OP_SEQ_PREFIX_OF, + OP_SEQ_SUFFIX_OF, + OP_SEQ_SUBSEQ_OF, + OP_SEQ_EXTRACT, + OP_SEQ_NTH, + OP_SEQ_LENGTH, + + OP_RE_PLUS, + OP_RE_STAR, + OP_RE_OPTION, + OP_RE_RANGE, + OP_RE_CONCAT, + OP_RE_UNION, + OP_RE_INTERSECT, + OP_RE_COMPLEMENT, + OP_RE_DIFFERENCE, + OP_RE_LOOP, + OP_RE_EMPTY_SET, + OP_RE_FULL_SET, + OP_RE_EMPTY_SEQ, + OP_RE_OF_SEQ, + OP_RE_OF_PRED, + OP_RE_MEMBER, + + LAST_SEQ_OP +}; + + + +class seq_decl_plugin : public decl_plugin { + struct psig { + symbol m_name; + unsigned m_num_params; + sort_ref_vector m_dom; + sort_ref m_range; + psig(ast_manager& m, char const* name, unsigned n, unsigned dsz, sort* const* dom, sort* rng): + m_name(name), + m_num_params(n), + m_dom(m), + m_range(rng, m) + { + m_dom.append(dsz, dom); + } + }; + + ptr_vector m_sigs; + bool m_init; + + void match(psig& sig, unsigned dsz, sort* const* dom, sort* range, sort_ref& rng); + + bool match(ptr_vector& binding, sort* s, sort* sP); + + sort* apply_binding(ptr_vector const& binding, sort* s); + + bool is_sort_param(sort* s, unsigned& idx); + + void init(); + +public: + seq_decl_plugin(); + + virtual ~seq_decl_plugin() {} + virtual void finalize(); + + virtual decl_plugin * mk_fresh() { return alloc(seq_decl_plugin); } + + virtual sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters); + + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + + virtual void get_op_names(svector & op_names, symbol const & logic); + + virtual void get_sort_names(svector & sort_names, symbol const & logic); + + virtual bool is_value(app* e) const; + +}; + +#endif /* _SEQ_DECL_PLUGIN_H_ */ + diff --git a/lib/sexpr.cpp b/lib/sexpr.cpp new file mode 100644 index 000000000..c5d4a976f --- /dev/null +++ b/lib/sexpr.cpp @@ -0,0 +1,291 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sexpr.h + +Abstract: + + Generic sexpr + +Author: + + Leonardo (leonardo) 2011-07-28 + +Notes: + +--*/ +#include"sexpr.h" +#include"vector.h" +#include"buffer.h" + +#ifdef _MSC_VER +#pragma warning(disable : 4200) +#pragma warning(disable : 4355) +#endif + +struct sexpr_composite : public sexpr { + unsigned m_num_chilren; + sexpr * m_children[0]; + sexpr_composite(unsigned num_children, sexpr * const * children, unsigned line, unsigned pos): + sexpr(COMPOSITE, line, pos), + m_num_chilren(num_children) { + for (unsigned i = 0; i < num_children; i++) { + m_children[i] = children[i]; + children[i]->inc_ref(); + } + } +}; + +struct sexpr_numeral : public sexpr { + rational m_val; + sexpr_numeral(kind_t k, rational const & val, unsigned line, unsigned pos): + sexpr(k, line, pos), + m_val(val) { + } + sexpr_numeral(rational const & val, unsigned line, unsigned pos): + sexpr(NUMERAL, line, pos), + m_val(val) { + } +}; + +struct sexpr_bv : public sexpr_numeral { + unsigned m_size; + sexpr_bv(rational const & val, unsigned size, unsigned line, unsigned pos): + sexpr_numeral(BV_NUMERAL, val, line, pos), + m_size(size) { + } +}; + +struct sexpr_string : public sexpr { + std::string m_val; + sexpr_string(std::string const & val, unsigned line, unsigned pos): + sexpr(STRING, line, pos), + m_val(val) { + } + sexpr_string(char const * val, unsigned line, unsigned pos): + sexpr(STRING, line, pos), + m_val(val) { + } +}; + +struct sexpr_symbol : public sexpr { + symbol m_val; + sexpr_symbol(bool keyword, symbol const & val, unsigned line, unsigned pos): + sexpr(keyword ? KEYWORD : SYMBOL, line, pos), + m_val(val) { + } +}; + +sexpr::sexpr(kind_t k, unsigned line, unsigned pos): + m_kind(k), + m_ref_count(0), + m_line(line), + m_pos(pos) { +} + +rational const & sexpr::get_numeral() const { + SASSERT(is_numeral() || is_bv_numeral()); + return static_cast(this)->m_val; +} + +unsigned sexpr::get_bv_size() const { + SASSERT(is_bv_numeral()); + return static_cast(this)->m_size; +} + +symbol sexpr::get_symbol() const { + SASSERT(is_symbol() || is_keyword()); + return static_cast(this)->m_val; +} + +std::string const & sexpr::get_string() const { + SASSERT(is_string()); + return static_cast(this)->m_val; +} + +unsigned sexpr::get_num_children() const { + SASSERT(is_composite()); + return static_cast(this)->m_num_chilren; +} + +sexpr * sexpr::get_child(unsigned idx) const { + SASSERT(idx < get_num_children()); + return static_cast(this)->m_children[idx]; +} + +sexpr * const * sexpr::get_children() const { + SASSERT(is_composite()); + return static_cast(this)->m_children; +} + +void sexpr::display_atom(std::ostream & out) const { + switch (get_kind()) { + case sexpr::COMPOSITE: + UNREACHABLE(); + case sexpr::NUMERAL: + out << static_cast(this)->m_val; + break; + case sexpr::BV_NUMERAL: { + out << '#'; + unsigned bv_size = static_cast(this)->m_size; + rational val = static_cast(this)->m_val; + sbuffer buf; + unsigned sz = 0; + if (bv_size % 4 == 0) { + out << 'x'; + while (val.is_pos()) { + rational c = val % rational(16); + val = div(val, rational(16)); + SASSERT(rational(0) <= c && c < rational(16)); + if (c <= rational(9)) + buf.push_back('0' + c.get_unsigned()); + else + buf.push_back('a' + (c.get_unsigned() - 10)); + sz+=4; + } + while (sz < bv_size) { + buf.push_back('0'); + sz+=4; + } + } + else { + out << 'b'; + while (val.is_pos()) { + rational c = val % rational(2); + val = div(val, rational(2)); + SASSERT(rational(0) <= c && c < rational(2)); + if (c.is_zero()) + buf.push_back('0'); + else + buf.push_back('1'); + sz += 1; + } + while (sz < bv_size) { + buf.push_back('0'); + sz += 1; + } + } + std::reverse(buf.begin(), buf.end()); + buf.push_back(0); + out << buf.c_ptr(); + break; + } + case sexpr::STRING: + out << "\"" << escaped(static_cast(this)->m_val.c_str()) << "\""; + break; + case sexpr::SYMBOL: + case sexpr::KEYWORD: + out << static_cast(this)->m_val; + break; + default: + UNREACHABLE(); + } +} + +void sexpr::display(std::ostream & out) const { + if (!is_composite()) + display_atom(out); + vector > todo; + todo.push_back(std::make_pair(static_cast(this), 0)); + while (!todo.empty()) { + loop: + sexpr_composite const * n = todo.back().first; + unsigned & idx = todo.back().second; + unsigned num = n->get_num_children(); + while (idx < num) { + sexpr const * child = n->get_child(idx); + if (idx == 0) + out << "("; + else + out << " "; + idx++; + if (child->is_composite()) { + todo.push_back(std::make_pair(static_cast(child), 0)); + goto loop; + } + else { + child->display_atom(out); + } + } + out << ")"; + todo.pop_back(); + } +} + +void sexpr_manager::del(sexpr * n) { + m_to_delete.push_back(n); + while (!m_to_delete.empty()) { + sexpr * n = m_to_delete.back(); + m_to_delete.pop_back(); + switch (n->get_kind()) { + case sexpr::COMPOSITE: { + unsigned num = n->get_num_children(); + for (unsigned i = 0; i < num; i++) { + sexpr * child = n->get_child(i); + SASSERT(child->m_ref_count > 0); + child->m_ref_count--; + if (child->m_ref_count == 0) + m_to_delete.push_back(child); + } + static_cast(n)->~sexpr_composite(); + m_allocator.deallocate(sizeof(sexpr_composite) + num * sizeof(sexpr*), n); + break; + } + case sexpr::NUMERAL: + static_cast(n)->~sexpr_numeral(); + m_allocator.deallocate(sizeof(sexpr_numeral), n); + break; + case sexpr::BV_NUMERAL: + static_cast(n)->~sexpr_bv(); + m_allocator.deallocate(sizeof(sexpr_bv), n); + break; + case sexpr::STRING: + static_cast(n)->~sexpr_string(); + m_allocator.deallocate(sizeof(sexpr_string), n); + break; + case sexpr::SYMBOL: + case sexpr::KEYWORD: + static_cast(n)->~sexpr_symbol(); + m_allocator.deallocate(sizeof(sexpr_symbol), n); + break; + default: + UNREACHABLE(); + } + } +} + +sexpr_manager::sexpr_manager(): + m_allocator("sexpr-manager") { +} + +sexpr * sexpr_manager::mk_composite(unsigned num_children, sexpr * const * children, unsigned line, unsigned pos) { + void * mem = m_allocator.allocate(sizeof(sexpr_composite) + num_children * sizeof(sexpr*)); + return new (mem) sexpr_composite(num_children, children, line, pos); +} + +sexpr * sexpr_manager::mk_numeral(rational const & val, unsigned line, unsigned pos) { + return new (m_allocator.allocate(sizeof(sexpr_numeral))) sexpr_numeral(val, line, pos); +} + +sexpr * sexpr_manager::mk_bv_numeral(rational const & val, unsigned bv_size, unsigned line, unsigned pos) { + return new (m_allocator.allocate(sizeof(sexpr_bv))) sexpr_bv(val, bv_size, line, pos); +} + +sexpr * sexpr_manager::mk_string(std::string const & val, unsigned line, unsigned pos) { + return new (m_allocator.allocate(sizeof(sexpr_string))) sexpr_string(val, line, pos); +} + +sexpr * sexpr_manager::mk_string(char const * val, unsigned line, unsigned pos) { + return new (m_allocator.allocate(sizeof(sexpr_string))) sexpr_string(val, line, pos); +} + +sexpr * sexpr_manager::mk_keyword(symbol const & val, unsigned line, unsigned pos) { + return new (m_allocator.allocate(sizeof(sexpr_symbol))) sexpr_symbol(true, val, line, pos); +} + +sexpr * sexpr_manager::mk_symbol(symbol const & val, unsigned line, unsigned pos) { + return new (m_allocator.allocate(sizeof(sexpr_symbol))) sexpr_symbol(false, val, line, pos); +} + diff --git a/lib/sexpr.h b/lib/sexpr.h new file mode 100644 index 000000000..2268d97be --- /dev/null +++ b/lib/sexpr.h @@ -0,0 +1,84 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sexpr.h + +Abstract: + + Generic sexpr + +Author: + + Leonardo (leonardo) 2011-07-28 + +Notes: + +--*/ +#ifndef _SEXPR_H_ +#define _SEXPR_H_ + +#include"rational.h" +#include"symbol.h" +#include"obj_ref.h" +#include"ref_vector.h" + +class sexpr_manager; + +class sexpr { +public: + enum kind_t { + COMPOSITE, NUMERAL, BV_NUMERAL, STRING, KEYWORD, SYMBOL + }; +protected: + kind_t m_kind; + unsigned m_ref_count; + unsigned m_line; + unsigned m_pos; + sexpr(kind_t k, unsigned line, unsigned pos); + void display_atom(std::ostream & out) const; + friend class sexpr_manager; +public: + void inc_ref() { m_ref_count++; } + unsigned get_ref_count() const { return m_ref_count; } + unsigned get_line() const { return m_line; } + unsigned get_pos() const { return m_pos; } + kind_t get_kind() const { return m_kind; } + bool is_composite() const { return get_kind() == COMPOSITE; } + bool is_numeral() const { return get_kind() == NUMERAL; } + bool is_bv_numeral() const { return get_kind() == BV_NUMERAL; } + bool is_string() const { return get_kind() == STRING; } + bool is_keyword() const { return get_kind() == KEYWORD; } + bool is_symbol() const { return get_kind() == SYMBOL; } + rational const & get_numeral() const; + unsigned get_bv_size() const; + symbol get_symbol() const; + std::string const & get_string() const; + unsigned get_num_children() const; + sexpr * get_child(unsigned idx) const; + sexpr * const * get_children() const; + void display(std::ostream & out) const; +}; + +class sexpr_manager { + small_object_allocator m_allocator; + ptr_vector m_to_delete; + void del(sexpr * n); +public: + sexpr_manager(); + sexpr * mk_composite(unsigned num_children, sexpr * const * children, unsigned line = UINT_MAX, unsigned pos = UINT_MAX); + sexpr * mk_numeral(rational const & val, unsigned line = UINT_MAX, unsigned pos = UINT_MAX); + sexpr * mk_bv_numeral(rational const & val, unsigned bv_size, unsigned line = UINT_MAX, unsigned pos = UINT_MAX); + sexpr * mk_string(std::string const & val, unsigned line = UINT_MAX, unsigned pos = UINT_MAX); + sexpr * mk_string(char const * val, unsigned line = UINT_MAX, unsigned pos = UINT_MAX); + sexpr * mk_keyword(symbol const & val, unsigned line = UINT_MAX, unsigned pos = UINT_MAX); + sexpr * mk_symbol(symbol const & val, unsigned line = UINT_MAX, unsigned pos = UINT_MAX); + void inc_ref(sexpr * n) { n->inc_ref(); } + void dec_ref(sexpr * n) { SASSERT(n->m_ref_count > 0); n->m_ref_count--; if (n->m_ref_count == 0) del(n); } +}; + +typedef obj_ref sexpr_ref; +typedef ref_vector sexpr_ref_vector; + +#endif diff --git a/lib/sexpr2upolynomial.cpp b/lib/sexpr2upolynomial.cpp new file mode 100644 index 000000000..ea3de77da --- /dev/null +++ b/lib/sexpr2upolynomial.cpp @@ -0,0 +1,112 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sexpr2upolynomial.cpp + +Abstract: + + sexpr to upolynomial converter + +Author: + + Leonardo (leonardo) 2011-12-28 + +Notes: + +--*/ +#include"sexpr2upolynomial.h" +#include"sexpr.h" + +sexpr2upolynomial_exception::sexpr2upolynomial_exception(char const * msg, sexpr const * s): + cmd_exception(msg, s->get_line(), s->get_pos()) { +} + +#define MAX_POLYNOMIAL_DEPTH 1 << 16 + +// Simple recursive parser +void sexpr2upolynomial(upolynomial::manager & m, sexpr const * s, upolynomial::numeral_vector & p, unsigned depth) { + if (depth > MAX_POLYNOMIAL_DEPTH) + throw sexpr2upolynomial_exception("invalid univariate polynomial, too complex", s); + if (s->is_composite()) { + unsigned num = s->get_num_children(); + if (num == 0) + throw sexpr2upolynomial_exception("invalid univariate polynomial, symbol expected", s); + sexpr * h = s->get_child(0); + if (!h->is_symbol()) + throw sexpr2upolynomial_exception("invalid univariate polynomial, symbol expected", s); + symbol op = h->get_symbol(); + if (op == "+") { + if (num <= 1) + throw sexpr2upolynomial_exception("invalid univariate polynomial, '+' operator expects at least one argument", s); + sexpr2upolynomial(m, s->get_child(1), p, depth+1); + upolynomial::scoped_numeral_vector arg(m); + for (unsigned i = 2; i < num; i++) { + m.reset(arg); + sexpr2upolynomial(m, s->get_child(i), arg, depth+1); + m.add(arg.size(), arg.c_ptr(), p.size(), p.c_ptr(), p); + } + } + else if (op == "-") { + if (num <= 1) + throw sexpr2upolynomial_exception("invalid univariate polynomial, '-' operator expects at least one argument", s); + sexpr2upolynomial(m, s->get_child(1), p, depth+1); + if (num == 2) { + m.neg(p); + return; + } + upolynomial::scoped_numeral_vector arg(m); + for (unsigned i = 2; i < num; i++) { + m.reset(arg); + sexpr2upolynomial(m, s->get_child(i), arg, depth+1); + m.sub(p.size(), p.c_ptr(), arg.size(), arg.c_ptr(), p); + } + } + else if (op == "*") { + if (num <= 1) + throw sexpr2upolynomial_exception("invalid univariate polynomial, '*' operator expects at least one argument", s); + sexpr2upolynomial(m, s->get_child(1), p, depth+1); + upolynomial::scoped_numeral_vector arg(m); + for (unsigned i = 2; i < num; i++) { + m.reset(arg); + sexpr2upolynomial(m, s->get_child(i), arg, depth+1); + m.mul(arg.size(), arg.c_ptr(), p.size(), p.c_ptr(), p); + } + } + else if (op == "^") { + if (num != 3) + throw sexpr2upolynomial_exception("invalid univariate polynomial, '^' operator expects two arguments", s); + sexpr2upolynomial(m, s->get_child(1), p, depth+1); + sexpr * arg2 = s->get_child(2); + if (!arg2->is_numeral() || !arg2->get_numeral().is_unsigned()) + throw sexpr2upolynomial_exception("invalid univariate polynomial, exponent must be an unsigned integer", arg2); + unsigned k = arg2->get_numeral().get_unsigned(); + m.pw(p.size(), p.c_ptr(), k, p); + } + else { + throw sexpr2upolynomial_exception("invalid univariate polynomial, '+', '-', '^' or '*' expected", s); + } + } + else if (s->is_numeral()) { + // make constant polynomial + rational a = s->get_numeral(); + if (!a.is_int()) + throw sexpr2upolynomial_exception("invalid univariate polynomial, integer coefficient expected", s); + m.set(1, &a, p); + } + else if (s->is_symbol()) { + if (s->get_symbol() != "x") + throw sexpr2upolynomial_exception("invalid univariate polynomial, variable 'x' expected", s); + // make identity + rational as[2] = { rational(0), rational(1) }; + m.set(2, as, p); + } + else { + throw sexpr2upolynomial_exception("invalid univariate polynomial, unexpected ", s); + } +} + +void sexpr2upolynomial(upolynomial::manager & m, sexpr const * s, upolynomial::numeral_vector & p) { + sexpr2upolynomial(m, s, p, 0); +} diff --git a/lib/sexpr2upolynomial.h b/lib/sexpr2upolynomial.h new file mode 100644 index 000000000..133c2dc6c --- /dev/null +++ b/lib/sexpr2upolynomial.h @@ -0,0 +1,33 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sexpr2upolynomial.h + +Abstract: + + sexpr to upolynomial converter + +Author: + + Leonardo (leonardo) 2011-12-28 + +Notes: + +--*/ +#ifndef _SEXPR2UPOLYNOMIAL_H_ +#define _SEXPR2UPOLYNOMIAL_H_ + +#include"upolynomial.h" +#include"cmd_context_types.h" +class sexpr; + +class sexpr2upolynomial_exception : public cmd_exception { +public: + sexpr2upolynomial_exception(char const * msg, sexpr const * s); +}; + +void sexpr2upolynomial(upolynomial::manager & m, sexpr const * s, upolynomial::numeral_vector & p); + +#endif diff --git a/lib/shallow_context_simplifier.cpp b/lib/shallow_context_simplifier.cpp new file mode 100644 index 000000000..ff9a7ffef --- /dev/null +++ b/lib/shallow_context_simplifier.cpp @@ -0,0 +1,257 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + shallow_context_simplifier.h + +Abstract: + + Depth 1 context simplifier. + +Author: + + Leonardo de Moura (leonardo) 2011-04-28 + + +Revision History: + +--*/ +#include"shallow_context_simplifier.h" +#include"assertion_set_util.h" +#include"expr_substitution.h" +#include"th_rewriter.h" +#include"ast_smt2_pp.h" +#include"assertion_set_util.h" + +struct shallow_context_simplifier::imp { + ast_manager & m_manager; + th_rewriter m_r; + expr_substitution m_subst; + assertion_set * m_set; + as_shared_occs m_occs; + unsigned m_idx; + unsigned m_num_steps; + unsigned m_max_rounds; + bool m_modified; + + imp(ast_manager & m, params_ref const & p): + m_manager(m), + m_r(m, p), + m_subst(m), + m_set(0), + m_occs(m, true /* track atoms */) { + m_r.set_substitution(&m_subst); + updt_params_core(p); + } + + void updt_params_core(params_ref const & p) { + m_max_rounds = p.get_uint(":max-rounds", 4); + } + + void updt_params(params_ref const & p) { + m_r.updt_params(p); + updt_params_core(p); + } + + ast_manager & m() const { return m_manager; } + + void set_cancel(bool f) { + m_r.set_cancel(f); + } + + void reset() { + m_subst.reset(); + m_set = 0; + m_num_steps = 0; + } + + void push_result(expr * new_curr, proof * new_pr) { + if (m().proofs_enabled()) { + proof * pr = m_set->pr(m_idx); + new_pr = m().mk_modus_ponens(pr, new_pr); + } + + m_set->update(m_idx, new_curr, new_pr); + + if (is_shared(new_curr)) { + m_subst.insert(new_curr, m().mk_true(), m().mk_iff_true(new_pr)); + } + expr * atom; + if (is_shared_neg(new_curr, atom)) { + m_subst.insert(atom, m().mk_false(), m().mk_iff_false(new_pr)); + } + expr * lhs, * value; + if (is_shared_eq(new_curr, lhs, value)) { + TRACE("shallow_context_simplifier_bug", tout << "found eq:\n" << mk_ismt2_pp(new_curr, m()) << "\n";); + m_subst.insert(lhs, value, new_pr); + } + } + + void process_current() { + expr * curr = m_set->form(m_idx); + expr_ref new_curr(m()); + proof_ref new_pr(m()); + + + if (!m_subst.empty()) { + m_r(curr, new_curr, new_pr); + m_num_steps += m_r.get_num_steps(); + } + else { + new_curr = curr; + if (m().proofs_enabled()) + new_pr = m().mk_reflexivity(curr); + } + + TRACE("shallow_context_simplifier_bug", tout << mk_ismt2_pp(curr, m()) << "\n---->\n" << mk_ismt2_pp(new_curr, m()) << "\n";); + push_result(new_curr, new_pr); + if (new_curr != curr) + m_modified = true; + } + + void operator()(assertion_set & s) { + SASSERT(is_well_sorted(s)); + as_st_report report("shallow-context-simplifier", s); + m_num_steps = 0; + if (s.inconsistent()) + return; + SASSERT(m_set == 0); + bool forward = true; + m_set = &s; + m_occs(*m_set); + TRACE("shallow_context_simplifier_bug", m_occs.display(tout, m()); ); + + unsigned size = s.size(); + m_idx = 0; + m_modified = false; + + + expr_ref new_curr(m()); + proof_ref new_pr(m()); + + unsigned round = 0; + + while (true) { + TRACE("shallow_context_simplifier_round", s.display(tout);); + if (forward) { + for (; m_idx < size; m_idx++) { + process_current(); + if (m_set->inconsistent()) + goto end; + } + if (m_subst.empty() && !m_modified) + goto end; + m_occs(*m_set); + m_idx = m_set->size(); + forward = false; + m_subst.reset(); + m_r.set_substitution(&m_subst); // reset, but keep substitution + } + else { + while (m_idx > 0) { + m_idx--; + process_current(); + if (m_set->inconsistent()) + goto end; + } + if (!m_modified) + goto end; + m_subst.reset(); + m_r.set_substitution(&m_subst); // reset, but keep substitution + m_modified = false; + m_occs(*m_set); + m_idx = 0; + size = m_set->size(); + forward = true; + } + round++; + if (round >= m_max_rounds) + break; + IF_VERBOSE(100, verbose_stream() << "starting new round, assertion-set size: " << m_set->num_exprs() << std::endl;); + TRACE("shallow_context_simplifier", tout << "round finished\n"; m_set->display(tout); tout << "\n";); + } + end: + m_set = 0; + SASSERT(is_well_sorted(s)); + } + + bool is_shared(expr * t) { + return m_occs.is_shared(t); + } + + bool is_shared_neg(expr * t, expr * & atom) { + if (!m().is_not(t)) + return false; + atom = to_app(t)->get_arg(0); + return is_shared(atom); + } + + bool is_shared_eq(expr * t, expr * & lhs, expr * & value) { + if (!m().is_eq(t)) + return false; + expr * arg1 = to_app(t)->get_arg(0); + expr * arg2 = to_app(t)->get_arg(1); + if (m().is_value(arg1) && is_shared(arg2)) { + lhs = arg2; + value = arg1; + return true; + } + if (m().is_value(arg2) && is_shared(arg1)) { + lhs = arg1; + value = arg2; + return true; + } + return false; + } + + unsigned get_num_steps() const { return m_num_steps; } +}; + +shallow_context_simplifier::shallow_context_simplifier(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); +} + +shallow_context_simplifier::~shallow_context_simplifier() { + dealloc(m_imp); +} + +void shallow_context_simplifier::updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); +} + +void shallow_context_simplifier::get_param_descrs(param_descrs & r) { + th_rewriter::get_param_descrs(r); + r.insert(":max-rounds", CPK_UINT, "(default: 2) maximum number of rounds."); +} + +void shallow_context_simplifier::operator()(assertion_set & s) { + m_imp->operator()(s); +} + +void shallow_context_simplifier::set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); +} + +void shallow_context_simplifier::cleanup() { + ast_manager & m = m_imp->m(); + imp * d = m_imp; + #pragma omp critical (as_st_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (as_st_cancel) + { + m_imp = d; + } +} + +unsigned shallow_context_simplifier::get_num_steps() const { + return m_imp->get_num_steps(); +} + diff --git a/lib/shallow_context_simplifier.h b/lib/shallow_context_simplifier.h new file mode 100644 index 000000000..0cc834af0 --- /dev/null +++ b/lib/shallow_context_simplifier.h @@ -0,0 +1,59 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + shallow_context_simplifier.h + +Abstract: + + Depth 1 context simplifier. + +Author: + + Leonardo de Moura (leonardo) 2011-04-28 + + +Revision History: + +--*/ +#ifndef _SHALLOW_CONTEXT_SIMPLIFIER_H_ +#define _SHALLOW_CONTEXT_SIMPLIFIER_H_ + +#include"assertion_set_strategy.h" + +class shallow_context_simplifier : public assertion_set_strategy { + struct imp; + imp * m_imp; + params_ref m_params; +public: + shallow_context_simplifier(ast_manager & m, params_ref const & p = params_ref()); + virtual ~shallow_context_simplifier(); + + virtual void updt_params(params_ref const & p); + static void get_param_descrs(param_descrs & r); + virtual void collect_param_descrs(param_descrs & r) { get_param_descrs(r); } + + void operator()(assertion_set & s); + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + operator()(s); + mc = 0; + } + + virtual void cleanup(); + + unsigned get_num_steps() const; +protected: + virtual void set_cancel(bool f); +}; + +inline as_st * mk_shallow_simplifier(ast_manager & m, params_ref const & p = params_ref()) { + return clean(alloc(shallow_context_simplifier, m, p)); +} + +inline as_st * mk_constant_propagation(ast_manager & m, params_ref const & p = params_ref()) { + return mk_shallow_simplifier(m, p); +} + +#endif diff --git a/lib/shared_occs.cpp b/lib/shared_occs.cpp new file mode 100644 index 000000000..5f624e1ac --- /dev/null +++ b/lib/shared_occs.cpp @@ -0,0 +1,140 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + shared_occs.cpp + +Abstract: + + Functor for computing the shared subterms in a given + term. + +Author: + + Leonardo de Moura (leonardo) 2011-04-01. + +Revision History: +--*/ +#include"shared_occs.h" +#include"ast_smt2_pp.h" +#include"ref_util.h" + +inline void shared_occs::insert(expr * t) { + obj_hashtable::entry * dummy; + if (m_shared.insert_if_not_there_core(t, dummy)) + m.inc_ref(t); +} + +void shared_occs::reset() { + dec_ref_collection_values(m, m_shared); + m_shared.reset(); +} + +void shared_occs::cleanup() { + reset(); + m_shared.finalize(); + m_stack.finalize(); +} + +shared_occs::~shared_occs() { + reset(); +} + +inline bool shared_occs::process(expr * t, shared_occs_mark & visited) { + switch (t->get_kind()) { + case AST_APP: { + unsigned num_args = to_app(t)->get_num_args(); + if (t->get_ref_count() > 1 && (m_track_atomic || num_args > 0)) { + if (visited.is_marked(t)) { + insert(t); + return true; + } + visited.mark(t); + } + if (num_args == 0) + return true; // done with t + m_stack.push_back(frame(t, 0)); // need to create frame if num_args > 0 + return false; + } + case AST_VAR: + if (m_track_atomic && t->get_ref_count() > 1) { + if (visited.is_marked(t)) + insert(t); + else + visited.mark(t); + } + return true; // done with t + case AST_QUANTIFIER: + if (t->get_ref_count() > 1) { + if (visited.is_marked(t)) { + insert(t); + return true; // done with t + } + visited.mark(t); + } + if (!m_visit_quantifiers) + return true; + m_stack.push_back(frame(t, 0)); + return false; + default: + UNREACHABLE(); + return true; + } +} + +void shared_occs::operator()(expr * t, shared_occs_mark & visited) { + SASSERT(m_stack.empty()); + if (process(t, visited)) { + return; + } + SASSERT(!m_stack.empty()); + while (!m_stack.empty()) { + start: + frame & fr = m_stack.back(); + expr * curr = fr.first; + switch (curr->get_kind()) { + case AST_APP: { + unsigned num_args = to_app(curr)->get_num_args(); + while (fr.second < num_args) { + expr * arg = to_app(curr)->get_arg(fr.second); + fr.second++; + if (!process(arg, visited)) + goto start; + } + break; + } + case AST_QUANTIFIER: { + SASSERT(m_visit_quantifiers); + unsigned num_children = m_visit_patterns ? to_quantifier(curr)->get_num_children() : 1; + while (fr.second < num_children) { + expr * child = to_quantifier(curr)->get_child(fr.second); + fr.second++; + if (!process(child, visited)) + goto start; + } + break; + } + default: + UNREACHABLE(); + break; + } + m_stack.pop_back(); + } +} + + +void shared_occs::operator()(expr * t) { + SASSERT(m_stack.empty()); + shared_occs_mark visited; + reset(); + operator()(t, visited); +} + +void shared_occs::display(std::ostream & out, ast_manager & m) const { + iterator it = begin_shared(); + iterator end = end_shared(); + for (; it != end; ++it) { + out << mk_ismt2_pp(*it, m) << "\n"; + } +} diff --git a/lib/shared_occs.h b/lib/shared_occs.h new file mode 100644 index 000000000..e135fea84 --- /dev/null +++ b/lib/shared_occs.h @@ -0,0 +1,81 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + shared_occs.h + +Abstract: + + Functor for computing the shared subterms in a given + term. + +Author: + + Leonardo de Moura (leonardo) 2011-04-01. + +Revision History: +--*/ +#ifndef _SHARED_OCCS_H_ +#define _SHARED_OCCS_H_ + +#include"ast.h" +#include"obj_hashtable.h" + +class shared_occs_mark { + ptr_buffer m_to_unmark; +public: + shared_occs_mark() {} + + ~shared_occs_mark() { + reset(); + } + + bool is_marked(ast * n) { return n->is_marked_so(); } + void reset_mark(ast * n) { n->reset_mark_so(); } + void mark(ast * n) { if (is_marked(n)) return; n->mark_so(true); m_to_unmark.push_back(n); } + void reset() { + ptr_buffer::iterator it = m_to_unmark.begin(); + ptr_buffer::iterator end = m_to_unmark.end(); + for (; it != end; ++it) { + reset_mark(*it); + } + m_to_unmark.reset(); + } + void mark(ast * n, bool flag) { if (flag) mark(n); else reset_mark(n); } +}; + +/** + \brief Functor for computing the shared subterms in a given term. +*/ +class shared_occs { + ast_manager & m; + bool m_track_atomic; + bool m_visit_quantifiers; + bool m_visit_patterns; + obj_hashtable m_shared; + typedef std::pair frame; + svector m_stack; + bool process(expr * t, shared_occs_mark & visited); + void insert(expr * t); +public: + typedef obj_hashtable::iterator iterator; + shared_occs(ast_manager & _m, bool track_atomic = false, bool visit_quantifiers = true, bool visit_patterns = false): + m(_m), + m_track_atomic(track_atomic), + m_visit_quantifiers(visit_quantifiers), + m_visit_patterns(visit_patterns) { + } + ~shared_occs(); + void operator()(expr * t); + void operator()(expr * t, shared_occs_mark & visited); + bool is_shared(expr * t) const { return m_shared.contains(t); } + unsigned num_shared() const { return m_shared.size(); } + iterator begin_shared() const { return m_shared.begin(); } + iterator end_shared() const { return m_shared.end(); } + void reset(); + void cleanup(); + void display(std::ostream & out, ast_manager & m) const; +}; + +#endif diff --git a/lib/simple_parser.cpp b/lib/simple_parser.cpp new file mode 100644 index 000000000..65f75e870 --- /dev/null +++ b/lib/simple_parser.cpp @@ -0,0 +1,149 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + simple_parser.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-03-31. + +Revision History: + +--*/ +#include +#include +#include"simple_parser.h" +#include"warning.h" +#include"scanner.h" + +simple_parser::simple_parser(ast_manager & m): + m_manager(m), + m_exprs(m) { +} + +simple_parser::~simple_parser() { +} + +void simple_parser::add_builtin_op(symbol const & s, family_id fid, decl_kind kind) { + SASSERT(!m_builtin.contains(s)); + SASSERT(!m_vars.contains(s)); + m_builtin.insert(s, builtin_op(fid, kind)); +} + +void simple_parser::add_builtin_op(char const * str, family_id fid, decl_kind kind) { + add_builtin_op(symbol(str), fid, kind); +} + +void simple_parser::add_var(symbol const & s, var * v) { + SASSERT(!m_builtin.contains(s)); + SASSERT(!m_vars.contains(s)); + m_vars.insert(s, v); +} + +void simple_parser::add_var(char const * str, var * v) { + add_var(symbol(str), v); +} + +void simple_parser::reset() { + m_builtin.reset(); + m_vars.reset(); + m_exprs.reset(); +} + +void simple_parser::reset_vars() { + m_vars.reset(); +} + +expr * simple_parser::parse_expr(scanner & s) { + builtin_op op; + var * v; + expr * r; + scanner::token token; + token = s.scan(); + switch (token) { + case scanner::LEFT_PAREN: + token = s.scan(); + if (token != scanner::ID_TOKEN) + throw parser_error(); + if (m_builtin.find(s.get_id(), op)) { + ptr_vector args; + while (true) { + expr * arg = parse_expr(s); + if (arg) { + args.push_back(arg); + } + else { + expr * r = m_manager.mk_app(op.m_family_id, op.m_kind, args.size(), args.c_ptr()); + m_exprs.push_back(r); + return r; + } + } + } + throw parser_error(); + case scanner::RIGHT_PAREN: + return 0; + case scanner::ID_TOKEN: + if (m_builtin.find(s.get_id(), op)) { + expr * r = m_manager.mk_const(op.m_family_id, op.m_kind); + m_exprs.push_back(r); + return r; + } + else if (m_vars.find(s.get_id(), v)) { + return v; + } + throw parser_error(); + case scanner::INT_TOKEN: + r = parse_int(s.get_number()); + m_exprs.push_back(r); + return r; + case scanner::FLOAT_TOKEN: + r = parse_float(s.get_number()); + m_exprs.push_back(r); + return r; + default: + throw parser_error(); + } +} + +bool simple_parser::parse(std::istream & in, expr_ref & result) { + scanner s(in, std::cerr, false); + try { + result = parse_expr(s); + if (!result) + throw parser_error(); + } + catch (parser_error) { + warning_msg("parser error"); + return false; + } + m_exprs.reset(); + return result.get() != 0; +} + +bool simple_parser::parse_string(char const * str, expr_ref & result) { + std::string s = str; + std::istringstream is(s); + return parse(is, result); +} + +bool simple_parser::parse_file(char const * file, expr_ref & result) { + if (file != 0) { + std::ifstream stream(file); + if (!stream) { + warning_msg("ERROR: could not open file '%s'.", file); + return false; + } + return parse(stream, result); + } + else { + return parse(std::cin, result); + } +} + + diff --git a/lib/simple_parser.h b/lib/simple_parser.h new file mode 100644 index 000000000..ef68b0b27 --- /dev/null +++ b/lib/simple_parser.h @@ -0,0 +1,64 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + simple_parser.h + +Abstract: + + Simple sexpr parser + +Author: + + Leonardo de Moura (leonardo) 2008-03-31. + +Revision History: + +--*/ +#ifndef _SIMPLE_PARSER_H_ +#define _SIMPLE_PARSER_H_ + +#include"ast.h" +#include"map.h" + +class scanner; + +/** + \brief Simple sexpr parser. + This class is used to parse small expressions used for configuring Z3. +*/ +class simple_parser { +protected: + struct parser_error {}; + struct builtin_op { + family_id m_family_id; + decl_kind m_kind; + builtin_op() : m_family_id(null_family_id), m_kind(0) {} + builtin_op(family_id fid, decl_kind k) : m_family_id(fid), m_kind(k) {} + }; + typedef map op_map; + typedef map var_map; + ast_manager & m_manager; + op_map m_builtin; + var_map m_vars; + expr_ref_vector m_exprs; + expr * parse_expr(scanner & s); +public: + simple_parser(ast_manager & m); + virtual ~simple_parser(); + void add_builtin_op(symbol const & s, family_id fid, decl_kind kind); + void add_builtin_op(char const * str, family_id fid, decl_kind kind); + void add_var(symbol const & s, var * v); + void add_var(char const * str, var * v); + void reset(); + void reset_vars(); + bool parse(std::istream & in, expr_ref & result); + bool parse_string(char const * str, expr_ref & result); + bool parse_file(char const * file, expr_ref & result); + virtual expr * parse_int(rational const & r) { throw parser_error(); } + virtual expr * parse_float(rational const & r) { throw parser_error(); } +}; + +#endif /* _SIMPLE_PARSER_H_ */ + diff --git a/lib/simplifier.cpp b/lib/simplifier.cpp new file mode 100644 index 000000000..c9f72f4d5 --- /dev/null +++ b/lib/simplifier.cpp @@ -0,0 +1,949 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + simplifier.cpp + +Abstract: + + Expression simplifier. + +Author: + + Leonardo (leonardo) 2008-01-03 + +Notes: + +--*/ +#include"simplifier.h" +#include"var_subst.h" +#include"ast_ll_pp.h" +#include"ast_pp.h" +#include"well_sorted.h" +#include"ast_smt_pp.h" + +simplifier::simplifier(ast_manager & m): + base_simplifier(m), + m_proofs(m), + m_subst_proofs(m), + m_need_reset(false), + m_use_oeq(false), + m_visited_quantifier(false), + m_ac_support(true) { +} + +void simplifier::register_plugin(plugin * p) { + m_plugins.register_plugin(p); +} + +simplifier::~simplifier() { + flush_cache(); +} + +void simplifier::enable_ac_support(bool flag) { + m_ac_support = flag; + ptr_vector::const_iterator it = m_plugins.begin(); + ptr_vector::const_iterator end = m_plugins.end(); + for (; it != end; ++it) { + if (*it != 0) + (*it)->enable_ac_support(flag); + } +} + +/** + \brief External interface for the simplifier. + A client will invoke operator()(s, r, p) to simplify s. + The result is stored in r. + When proof generation is enabled, a proof for the equivalence (or equisatisfiability) + of s and r is stored in p. + When proof generation is disabled, this method stores the "undefined proof" object in p. +*/ +void simplifier::operator()(expr * s, expr_ref & r, proof_ref & p) { + m_need_reset = true; + expr * old_s; + expr * result; + proof * result_proof; + switch (m_manager.proof_mode()) { + case PGM_DISABLED: // proof generation is disabled. + reduce_core(s); + // after executing reduce_core, the result of the simplification is in the cache + get_cached(s, result, result_proof); + r = result; + p = m_manager.mk_undef_proof(); + break; + case PGM_COARSE: // coarse proofs... in this case, we do not produce a step by step (fine grain) proof to show the equivalence (or equisatisfiability) of s an r. + m_subst_proofs.reset(); // m_subst_proofs is an auxiliary vector that is used to justify substitutions. See comment on method get_subst. + reduce_core(s); + get_cached(s, result, result_proof); + r = result; + if (result == s) + p = m_manager.mk_reflexivity(s); + else { + remove_duplicates(m_subst_proofs); + p = m_manager.mk_rewrite_star(s, result, m_subst_proofs.size(), m_subst_proofs.c_ptr()); + } + break; + case PGM_FINE: // fine grain proofs... in this mode, every proof step (or most of them) is described. + m_proofs.reset(); + old_s = 0; + // keep simplyfing until no further simplifications are possible. + while (s != old_s) { + TRACE("simplifier", tout << "simplification pass... " << s->get_id() << "\n";); + TRACE("simplifier_loop", tout << mk_ll_pp(s, m_manager) << "\n";); + reduce_core(s); + get_cached(s, result, result_proof); + if (result_proof != 0) + m_proofs.push_back(result_proof); + old_s = s; + s = result; + } + SASSERT(s != 0); + r = s; + p = m_proofs.empty() ? m_manager.mk_reflexivity(s) : m_manager.mk_transitivity(m_proofs.size(), m_proofs.c_ptr()); + break; + default: + UNREACHABLE(); + } +} + +void simplifier::flush_cache() { + m_cache.flush(); + ptr_vector::const_iterator it = m_plugins.begin(); + ptr_vector::const_iterator end = m_plugins.end(); + for (; it != end; ++it) { + if (*it != 0) { + (*it)->flush_caches(); + } + } +} + +bool simplifier::get_subst(expr * n, expr_ref & r, proof_ref & p) { + return false; +} + +void simplifier::reduce_core(expr * n) { + if (!is_cached(n)) { + // We do not assume m_todo is empty... So, we store the current size of the todo-stack. + unsigned sz = m_todo.size(); + m_todo.push_back(n); + while (m_todo.size() != sz) { + expr * n = m_todo.back(); + if (is_cached(n)) + m_todo.pop_back(); + else if (visit_children(n)) { + // if all children were already simplified, then remove n from the todo stack and apply a + // simplification step to it. + m_todo.pop_back(); + reduce1(n); + } + } + } +} + +/** + \brief Return true if all children of n have been already simplified. +*/ +bool simplifier::visit_children(expr * n) { + switch(n->get_kind()) { + case AST_VAR: + return true; + case AST_APP: + // The simplifier has support for flattening AC (associative-commutative) operators. + // The method ast_manager::mk_app is used to create the flat version of an AC operator. + // In Z3 1.x, we used multi-ary operators. This creates problems for the superposition engine. + // So, starting at Z3 2.x, only boolean operators can be multi-ary. + // Example: + // (and (and a b) (and c d)) --> (and a b c d) + // (+ (+ a b) (+ c d)) --> (+ a (+ b (+ c d))) + // Remark: The flattening is only applied if m_ac_support is true. + if (m_ac_support && to_app(n)->get_decl()->is_associative() && to_app(n)->get_decl()->is_commutative()) + return visit_ac(to_app(n)); + else { + bool visited = true; + unsigned j = to_app(n)->get_num_args(); + while (j > 0) { + --j; + visit(to_app(n)->get_arg(j), visited); + } + return visited; + } + case AST_QUANTIFIER: + return visit_quantifier(to_quantifier(n)); + default: + UNREACHABLE(); + return true; + } +} + +/** + \brief Visit the children of n assuming it is an AC (associative-commutative) operator. + + For example, if n is of the form (+ (+ a b) (+ c d)), this method + will return true if the nodes a, b, c and d have been already simplified. + The nodes (+ a b) and (+ c d) are not really checked. +*/ +bool simplifier::visit_ac(app * n) { + bool visited = true; + func_decl * decl = n->get_decl(); + SASSERT(m_ac_support); + SASSERT(decl->is_associative()); + SASSERT(decl->is_commutative()); + m_ac_marked.reset(); + ptr_buffer todo; + todo.push_back(n); + while (!todo.empty()) { + app * n = todo.back(); + todo.pop_back(); + if (m_ac_mark.is_marked(n)) + continue; + m_ac_mark.mark(n, true); + m_ac_marked.push_back(n); + SASSERT(n->get_decl() == decl); + unsigned i = n->get_num_args(); + while (i > 0) { + --i; + expr * arg = n->get_arg(i); + if (is_app_of(arg, decl)) + todo.push_back(to_app(arg)); + else + visit(arg, visited); + } + } + ptr_vector::const_iterator it = m_ac_marked.begin(); + ptr_vector::const_iterator end = m_ac_marked.end(); + for (; it != end; ++it) + m_ac_mark.mark(*it, false); + return visited; +} + +bool simplifier::visit_quantifier(quantifier * n) { + m_visited_quantifier = true; + bool visited = true; + unsigned j = to_quantifier(n)->get_num_patterns(); + while (j > 0) { + --j; + visit(to_quantifier(n)->get_pattern(j), visited); + } + j = to_quantifier(n)->get_num_no_patterns(); + while (j > 0) { + --j; + visit(to_quantifier(n)->get_no_pattern(j), visited); + } + visit(to_quantifier(n)->get_expr(), visited); + return visited; +} + +/** + \brief Simplify n and store the result in the cache. +*/ +void simplifier::reduce1(expr * n) { + switch (n->get_kind()) { + case AST_VAR: + cache_result(n, n, 0); + break; + case AST_APP: + reduce1_app(to_app(n)); + break; + case AST_QUANTIFIER: + reduce1_quantifier(to_quantifier(n)); + break; + default: + UNREACHABLE(); + } +} + +/** + \brief Simplify the given application using the cached values, + associativity flattening, the given substitution, and family/theory + specific simplifications via plugins. +*/ +void simplifier::reduce1_app(app * n) { + expr_ref r(m_manager); + proof_ref p(m_manager); + TRACE("reduce", tout << "reducing...\n" << mk_pp(n, m_manager) << "\n";); + if (get_subst(n, r, p)) { + TRACE("reduce", tout << "applying substitution...\n";); + cache_result(n, r, p); + return; + } + + func_decl * decl = n->get_decl(); + if (m_ac_support && decl->is_associative() && decl->is_commutative()) + reduce1_ac_app_core(n); + else + reduce1_app_core(n); +} + + +void simplifier::reduce1_app_core(app * n) { + m_args.reset(); + func_decl * decl = n->get_decl(); + proof_ref p1(m_manager); + // Stores the new arguments of n in m_args. + // Let n be of the form + // (decl arg_0 ... arg_{n-1}) + // then + // m_args contains [arg_0', ..., arg_{n-1}'], + // where arg_i' is the simplification of arg_i + // and + // p1 is a proof for + // (decl arg_0 ... arg_{n-1}) is equivalente/equisatisfiable to (decl arg_0' ... arg_{n-1}') + // p1 is built using the congruence proof step and the proofs for arg_0' ... arg_{n-1}'. + // Of course, p1 is 0 if proofs are not enabled or coarse grain proofs are used. + bool has_new_args = get_args(n, m_args, p1); + // The following if implements a simple trick. + // If none of the arguments have been simplified, and n is not a theory symbol, + // Then no simplification is possible, and we can cache the result of the simplification of n as n. + if (has_new_args || decl->get_family_id() != null_family_id) { + expr_ref r(m_manager); + TRACE("reduce", tout << "reduce1_app\n"; for(unsigned i = 0; i < m_args.size(); i++) tout << mk_ll_pp(m_args[i], m_manager);); + // the method mk_app invokes get_subst and plugins to simplify + // (decl arg_0' ... arg_{n-1}') + mk_app(decl, m_args.size(), m_args.c_ptr(), r); + if (!m_manager.fine_grain_proofs()) { + cache_result(n, r, 0); + } + else { + expr * s = m_manager.mk_app(decl, m_args.size(), m_args.c_ptr()); + proof * p; + if (n == r) + p = 0; + else if (r != s) + // we use a "theory rewrite generic proof" to justify the step + // s = (decl arg_0' ... arg_{n-1}') --> r + p = m_manager.mk_transitivity(p1, m_manager.mk_rewrite(s, r)); + else + p = p1; + cache_result(n, r, p); + } + } + else { + cache_result(n, n, 0); + } +} + +bool is_ac_list(app * n, ptr_vector & args) { + args.reset(); + func_decl * f = n->get_decl(); + app * curr = n; + while (true) { + if (curr->get_num_args() != 2) + return false; + expr * arg1 = curr->get_arg(0); + if (is_app_of(arg1, f)) + return false; + args.push_back(arg1); + expr * arg2 = curr->get_arg(1); + if (!is_app_of(arg2, f)) { + args.push_back(arg2); + return true; + } + curr = to_app(arg2); + } +} + +bool is_ac_vector(app * n) { + func_decl * f = n->get_decl(); + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + if (is_app_of(n->get_arg(i), f)) + return false; + } + return true; +} + +void simplifier::reduce1_ac_app_core(app * n) { + app_ref n_c(m_manager); + proof_ref p1(m_manager); + mk_ac_congruent_term(n, n_c, p1); + TRACE("ac", tout << "expr:\n" << mk_pp(n, m_manager) << "\ncongruent term:\n" << mk_pp(n_c, m_manager) << "\n";); + expr_ref r(m_manager); + func_decl * decl = n->get_decl(); + family_id fid = decl->get_family_id(); + plugin * p = get_plugin(fid); + if (is_ac_vector(n_c)) { + if (p != 0 && p->reduce(decl, n_c->get_num_args(), n_c->get_args(), r)) { + // done... + } + else { + r = n_c; + } + } + else if (is_ac_list(n_c, m_args)) { + // m_args contains the arguments... + if (p != 0 && p->reduce(decl, m_args.size(), m_args.c_ptr(), r)) { + // done... + } + else { + r = m_manager.mk_app(decl, m_args.size(), m_args.c_ptr()); + } + } + else { + m_args.reset(); + m_mults.reset(); + get_ac_args(n_c, m_args, m_mults); + TRACE("ac", tout << "AC args:\n"; + for (unsigned i = 0; i < m_args.size(); i++) { + tout << mk_pp(m_args[i], m_manager) << " * " << m_mults[i] << "\n"; + }); + if (p != 0 && p->reduce(decl, m_args.size(), m_mults.c_ptr(), m_args.c_ptr(), r)) { + // done... + } + else { + ptr_buffer new_args; + expand_args(m_args.size(), m_mults.c_ptr(), m_args.c_ptr(), new_args); + r = m_manager.mk_app(decl, new_args.size(), new_args.c_ptr()); + } + } + TRACE("ac", tout << "AC result:\n" << mk_pp(r, m_manager) << "\n";); + + if (!m_manager.fine_grain_proofs()) { + cache_result(n, r, 0); + } + else { + proof * p; + if (n == r.get()) + p = 0; + else if (r.get() != n_c.get()) + p = m_manager.mk_transitivity(p1, m_manager.mk_rewrite(n_c, r)); + else + p = p1; + cache_result(n, r, p); + } +} + +static unsigned g_rewrite_lemma_id = 0; + +void simplifier::dump_rewrite_lemma(func_decl * decl, unsigned num_args, expr * const * args, expr* result) { + expr_ref arg(m_manager); + arg = m_manager.mk_app(decl, num_args, args); + if (arg.get() != result) { + char buffer[128]; +#ifdef _WINDOWS + sprintf_s(buffer, ARRAYSIZE(buffer), "lemma_%d.smt", g_rewrite_lemma_id); +#else + sprintf(buffer, "rewrite_lemma_%d.smt", g_rewrite_lemma_id); +#endif + ast_smt_pp pp(m_manager); + pp.set_benchmark_name("rewrite_lemma"); + pp.set_status("unsat"); + expr_ref n(m_manager); + n = m_manager.mk_not(m_manager.mk_eq(arg.get(), result)); + std::ofstream out(buffer); + pp.display(out, n); + out.close(); + ++g_rewrite_lemma_id; + } +} + +/** + \brief Return in \c result an expression \c e equivalent to (f args[0] ... args[num_args - 1]), and + store in \c pr a proof for (= (f args[0] ... args[num_args - 1]) e) + + If e is identical to (f args[0] ... args[num_args - 1]), then pr is set to 0. +*/ +void simplifier::mk_app(func_decl * decl, unsigned num_args, expr * const * args, expr_ref & result) { + m_need_reset = true; + if (m_manager.is_eq(decl)) { + sort * s = m_manager.get_sort(args[0]); + plugin * p = get_plugin(s->get_family_id()); + if (p != 0 && p->reduce_eq(args[0], args[1], result)) + return; + } + else if (m_manager.is_distinct(decl)) { + sort * s = m_manager.get_sort(args[0]); + plugin * p = get_plugin(s->get_family_id()); + if (p != 0 && p->reduce_distinct(num_args, args, result)) + return; + } + family_id fid = decl->get_family_id(); + plugin * p = get_plugin(fid); + if (p != 0 && p->reduce(decl, num_args, args, result)) { + //uncomment this line if you want to trace rewrites as lemmas: + //dump_rewrite_lemma(decl, num_args, args, result.get()); + return; + } + result = m_manager.mk_app(decl, num_args, args); +} + +/** + \brief Create a term congruence to n (f a[0] ... a[num_args-1]) using the + cached values for the a[i]'s. Store the result in r, and the proof for (= n r) in p. + If n and r are identical, then set p to 0. +*/ +void simplifier::mk_congruent_term(app * n, app_ref & r, proof_ref & p) { + bool has_new_args = false; + ptr_vector args; + ptr_vector proofs; + unsigned num = n->get_num_args(); + for (unsigned j = 0; j < num; j++) { + expr * arg = n->get_arg(j); + expr * new_arg; + proof * arg_proof; + get_cached(arg, new_arg, arg_proof); + + CTRACE("simplifier_bug", (arg != new_arg) != (arg_proof != 0), + tout << mk_ll_pp(arg, m_manager) << "\n---->\n" << mk_ll_pp(new_arg, m_manager) << "\n"; + tout << "#" << arg->get_id() << " #" << new_arg->get_id() << "\n"; + tout << arg << " " << new_arg << "\n";); + + + if (arg != new_arg) { + has_new_args = true; + proofs.push_back(arg_proof); + SASSERT(arg_proof); + } + else { + SASSERT(arg_proof == 0); + } + args.push_back(new_arg); + } + if (has_new_args) { + r = m_manager.mk_app(n->get_decl(), args.size(), args.c_ptr()); + if (m_use_oeq) + p = m_manager.mk_oeq_congruence(n, r, proofs.size(), proofs.c_ptr()); + else + p = m_manager.mk_congruence(n, r, proofs.size(), proofs.c_ptr()); + } + else { + r = n; + p = 0; + } +} + +/** + \brief Store the new arguments of \c n in result. Store in p a proof for + (= n (f result[0] ... result[num_args - 1])), where f is the function symbol of n. + + If there are no new arguments or fine grain proofs are disabled, then p is set to 0. + + Return true there are new arguments. +*/ +bool simplifier::get_args(app * n, ptr_vector & result, proof_ref & p) { + bool has_new_args = false; + unsigned num = n->get_num_args(); + if (m_manager.fine_grain_proofs()) { + app_ref r(m_manager); + mk_congruent_term(n, r, p); + result.append(r->get_num_args(), r->get_args()); + SASSERT(n->get_num_args() == result.size()); + has_new_args = r != n; + } + else { + p = 0; + for (unsigned j = 0; j < num; j++) { + expr * arg = n->get_arg(j); + expr * new_arg; + proof * arg_proof; + get_cached(arg, new_arg, arg_proof); + if (arg != new_arg) { + has_new_args = true; + } + result.push_back(new_arg); + } + } + return has_new_args; +} + +/** + \brief Create a term congruence to n (where n is an expression such as: (f (f a_1 a_2) (f a_3 (f a_4 a_5))) using the + cached values for the a_i's. Store the result in r, and the proof for (= n r) in p. + If n and r are identical, then set p to 0. +*/ +void simplifier::mk_ac_congruent_term(app * n, app_ref & r, proof_ref & p) { + SASSERT(m_ac_support); + func_decl * f = n->get_decl(); + + m_ac_cache.reset(); + m_ac_pr_cache.reset(); + + ptr_buffer todo; + ptr_buffer new_args; + ptr_buffer new_arg_prs; + todo.push_back(n); + while (!todo.empty()) { + app * curr = todo.back(); + if (m_ac_cache.contains(curr)) { + todo.pop_back(); + continue; + } + bool visited = true; + bool has_new_arg = false; + new_args.reset(); + new_arg_prs.reset(); + unsigned num_args = curr->get_num_args(); + for (unsigned j = 0; j < num_args; j ++) { + expr * arg = curr->get_arg(j); + if (is_app_of(arg, f)) { + app * new_arg = 0; + if (m_ac_cache.find(to_app(arg), new_arg)) { + SASSERT(new_arg != 0); + new_args.push_back(new_arg); + if (arg != new_arg) + has_new_arg = true; + if (m_manager.fine_grain_proofs()) { + proof * pr = 0; + m_ac_pr_cache.find(to_app(arg), pr); + if (pr != 0) + new_arg_prs.push_back(pr); + } + } + else { + visited = false; + todo.push_back(to_app(arg)); + } + } + else { + expr * new_arg = 0; + proof * pr; + get_cached(arg, new_arg, pr); + new_args.push_back(new_arg); + if (arg != new_arg) + has_new_arg = true; + if (m_manager.fine_grain_proofs() && pr != 0) + new_arg_prs.push_back(pr); + } + } + if (visited) { + SASSERT(new_args.size() == curr->get_num_args()); + todo.pop_back(); + if (!has_new_arg) { + m_ac_cache.insert(curr, curr); + if (m_manager.fine_grain_proofs()) + m_ac_pr_cache.insert(curr, 0); + } + else { + app * new_curr = m_manager.mk_app(f, new_args.size(), new_args.c_ptr()); + m_ac_cache.insert(curr, new_curr); + if (m_manager.fine_grain_proofs()) { + proof * p = m_manager.mk_congruence(curr, new_curr, new_arg_prs.size(), new_arg_prs.c_ptr()); + m_ac_pr_cache.insert(curr, p); + } + } + } + } + + SASSERT(m_ac_cache.contains(n)); + app * new_n = 0; + m_ac_cache.find(n, new_n); + r = new_n; + if (m_manager.fine_grain_proofs()) { + proof * new_pr = 0; + m_ac_pr_cache.find(n, new_pr); + p = new_pr; + } +} + +#define White 0 +#define Grey 1 +#define Black 2 + +static int get_color(obj_map & colors, expr * n) { + obj_map::obj_map_entry * entry = colors.insert_if_not_there2(n, White); + return entry->get_data().m_value; +} + +static bool visit_ac_children(func_decl * f, expr * n, obj_map & colors, ptr_buffer & todo, ptr_buffer & result) { + if (is_app_of(n, f)) { + unsigned num_args = to_app(n)->get_num_args(); + bool visited = true; + // Put the arguments in 'result' in reverse order. + // Reason: preserve the original order of the arguments in the final result. + // Remark: get_ac_args will traverse 'result' backwards. + for (unsigned i = 0; i < num_args; i++) { + expr * arg = to_app(n)->get_arg(i); + obj_map::obj_map_entry * entry = colors.insert_if_not_there2(arg, White); + if (entry->get_data().m_value == White) { + todo.push_back(arg); + visited = false; + } + } + return visited; + } + else { + return true; + } +} + +void simplifier::ac_top_sort(app * n, ptr_buffer & result) { + ptr_buffer todo; + func_decl * f = n->get_decl(); + obj_map & colors = m_colors; + colors.reset(); + todo.push_back(n); + while (!todo.empty()) { + expr * curr = todo.back(); + int color; + obj_map::obj_map_entry * entry = colors.insert_if_not_there2(curr, White); + SASSERT(entry); + color = entry->get_data().m_value; + switch (color) { + case White: + // Remark: entry becomes invalid if an element is inserted into the hashtable. + // So, I must set Grey before executing visit_ac_children. + entry->get_data().m_value = Grey; + SASSERT(get_color(colors, curr) == Grey); + if (visit_ac_children(f, curr, colors, todo, result)) { + // If visit_ac_children succeeded, then the hashtable was not modified, + // and entry is still valid. + SASSERT(todo.back() == curr); + entry->get_data().m_value = Black; + SASSERT(get_color(colors, curr) == Black); + result.push_back(curr); + todo.pop_back(); + } + break; + case Grey: + SASSERT(visit_ac_children(f, curr, colors, todo, result)); + SASSERT(entry); + entry->get_data().m_value = Black; + SASSERT(get_color(colors, curr) == Black); + result.push_back(curr); + SASSERT(todo.back() == curr); + todo.pop_back(); + break; + case Black: + todo.pop_back(); + break; + default: + UNREACHABLE(); + } + } +} + +void simplifier::get_ac_args(app * n, ptr_vector & args, vector & mults) { + SASSERT(m_ac_support); + ptr_buffer sorted_exprs; + ac_top_sort(n, sorted_exprs); + SASSERT(!sorted_exprs.empty()); + SASSERT(sorted_exprs[sorted_exprs.size()-1] == n); + + TRACE("ac", tout << mk_ll_pp(n, m_manager, true, false) << "#" << n->get_id() << "\nsorted expressions...\n"; + for (unsigned i = 0; i < sorted_exprs.size(); i++) { + tout << "#" << sorted_exprs[i]->get_id() << " "; + } + tout << "\n";); + + m_ac_mults.reset(); + m_ac_mults.insert(n, rational(1)); + func_decl * decl = n->get_decl(); + unsigned j = sorted_exprs.size(); + while (j > 0) { + --j; + expr * curr = sorted_exprs[j]; + rational mult; + m_ac_mults.find(curr, mult); + SASSERT(!mult.is_zero()); + if (is_app_of(curr, decl)) { + unsigned num_args = to_app(curr)->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = to_app(curr)->get_arg(i); + rational zero; + obj_map::obj_map_entry * entry = m_ac_mults.insert_if_not_there2(arg, zero); + entry->get_data().m_value += mult; + } + } + else { + args.push_back(curr); + mults.push_back(mult); + } + } +} + +void simplifier::reduce1_quantifier(quantifier * q) { + expr * new_body; + proof * new_body_pr; + SASSERT(is_well_sorted(m_manager, q)); + get_cached(q->get_expr(), new_body, new_body_pr); + + quantifier_ref q1(m_manager); + proof * p1 = 0; + + if (is_quantifier(new_body) && + to_quantifier(new_body)->is_forall() == q->is_forall() && + !to_quantifier(q)->has_patterns() && + !to_quantifier(new_body)->has_patterns()) { + + quantifier * nested_q = to_quantifier(new_body); + + ptr_buffer sorts; + buffer names; + sorts.append(q->get_num_decls(), q->get_decl_sorts()); + names.append(q->get_num_decls(), q->get_decl_names()); + sorts.append(nested_q->get_num_decls(), nested_q->get_decl_sorts()); + names.append(nested_q->get_num_decls(), nested_q->get_decl_names()); + + q1 = m_manager.mk_quantifier(q->is_forall(), + sorts.size(), + sorts.c_ptr(), + names.c_ptr(), + nested_q->get_expr(), + std::min(q->get_weight(), nested_q->get_weight()), + q->get_qid(), + q->get_skid(), + 0, 0, 0, 0); + SASSERT(is_well_sorted(m_manager, q1)); + + if (m_manager.fine_grain_proofs()) { + quantifier * q0 = m_manager.update_quantifier(q, new_body); + proof * p0 = q == q0 ? 0 : m_manager.mk_quant_intro(q, q0, new_body_pr); + p1 = m_manager.mk_pull_quant(q0, q1); + p1 = m_manager.mk_transitivity(p0, p1); + } + } + else { + ptr_buffer new_patterns; + ptr_buffer new_no_patterns; + expr * new_pattern; + proof * new_pattern_pr; + + // Remark: we can ignore the proofs for the patterns. + unsigned num = q->get_num_patterns(); + for (unsigned i = 0; i < num; i++) { + get_cached(q->get_pattern(i), new_pattern, new_pattern_pr); + if (m_manager.is_pattern(new_pattern)) { + new_patterns.push_back(new_pattern); + } + } + num = q->get_num_no_patterns(); + for (unsigned i = 0; i < num; i++) { + get_cached(q->get_no_pattern(i), new_pattern, new_pattern_pr); + new_no_patterns.push_back(new_pattern); + } + + remove_duplicates(new_patterns); + remove_duplicates(new_no_patterns); + + q1 = m_manager.mk_quantifier(q->is_forall(), + q->get_num_decls(), + q->get_decl_sorts(), + q->get_decl_names(), + new_body, + q->get_weight(), + q->get_qid(), + q->get_skid(), + new_patterns.size(), + new_patterns.c_ptr(), + new_no_patterns.size(), + new_no_patterns.c_ptr()); + SASSERT(is_well_sorted(m_manager, q1)); + + TRACE("simplifier", tout << mk_pp(q, m_manager) << "\n" << mk_pp(q1, m_manager) << "\n";); + if (m_manager.fine_grain_proofs()) { + if (q != q1 && !new_body_pr) { + new_body_pr = m_manager.mk_rewrite(q->get_expr(), new_body); + } + p1 = q == q1 ? 0 : m_manager.mk_quant_intro(q, q1, new_body_pr); + } + } + + expr_ref r(m_manager); + elim_unused_vars(m_manager, q1, r); + + proof * pr = 0; + if (m_manager.fine_grain_proofs()) { + proof * p2 = 0; + if (q1.get() != r.get()) + p2 = m_manager.mk_elim_unused_vars(q1, r); + pr = m_manager.mk_transitivity(p1, p2); + } + + cache_result(q, r, pr); +} + +/** + \see release_plugins +*/ +void simplifier::borrow_plugins(simplifier const & s) { + ptr_vector::const_iterator it = s.begin_plugins(); + ptr_vector::const_iterator end = s.end_plugins(); + for (; it != end; ++it) + register_plugin(*it); +} + +/** + \brief Make the simplifier behave as a pre-simplifier: No AC, and plugins are marked in pre-simplification mode. +*/ +void simplifier::enable_presimp() { + enable_ac_support(false); + ptr_vector::const_iterator it = begin_plugins(); + ptr_vector::const_iterator end = end_plugins(); + for (; it != end; ++it) + (*it)->enable_presimp(true); +} + +/** + \brief This method should be invoked if the plugins of this simplifier were borrowed from a different simplifier. +*/ +void simplifier::release_plugins() { + m_plugins.release(); +} + +void subst_simplifier::set_subst_map(expr_map * s) { + flush_cache(); + m_subst_map = s; +} + +bool subst_simplifier::get_subst(expr * n, expr_ref & r, proof_ref & p) { + if (m_subst_map && m_subst_map->contains(n)) { + expr * _r; + proof * _p = 0; + m_subst_map->get(n, _r, _p); + r = _r; + p = _p; + if (m_manager.coarse_grain_proofs()) + m_subst_proofs.push_back(p); + return true; + } + return false; +} + +static void push_core(ast_manager & m, expr * e, proof * pr, expr_ref_vector & result, proof_ref_vector & result_prs) { + SASSERT(pr == 0 || m.is_undef_proof(pr) || e == m.get_fact(pr)); + TRACE("preprocessor", + tout << mk_pp(e, m) << "\n"; + if (pr) tout << mk_ll_pp(pr, m) << "\n\n";); + if (m.is_true(e)) + return; + result.push_back(e); + if (m.proofs_enabled()) + result_prs.push_back(pr); +} + +static void push_and(ast_manager & m, app * e, proof * pr, expr_ref_vector & result, proof_ref_vector & result_prs) { + unsigned num = e->get_num_args(); + TRACE("push_and", tout << mk_pp(e, m) << "\n";); + for (unsigned i = 0; i < num; i++) + push_assertion(m, e->get_arg(i), m.mk_and_elim(pr, i), result, result_prs); +} + +static void push_not_or(ast_manager & m, app * e, proof * pr, expr_ref_vector & result, proof_ref_vector & result_prs) { + unsigned num = e->get_num_args(); + TRACE("push_not_or", tout << mk_pp(e, m) << "\n";); + for (unsigned i = 0; i < num; i++) { + expr * child = e->get_arg(i); + if (m.is_not(child)) { + expr * not_child = to_app(child)->get_arg(0); + push_assertion(m, not_child, m.mk_not_or_elim(pr, i), result, result_prs); + } + else { + expr_ref not_child(m); + not_child = m.mk_not(child); + push_assertion(m, not_child, m.mk_not_or_elim(pr, i), result, result_prs); + } + } +} + +void push_assertion(ast_manager & m, expr * e, proof * pr, expr_ref_vector & result, proof_ref_vector & result_prs) { + CTRACE("push_assertion", !(pr == 0 || m.is_undef_proof(pr) || m.get_fact(pr) == e), + tout << mk_pp(e, m) << "\n" << mk_pp(m.get_fact(pr), m) << "\n";); + SASSERT(pr == 0 || m.is_undef_proof(pr) || m.get_fact(pr) == e); + if (m.is_and(e)) + push_and(m, to_app(e), pr, result, result_prs); + else if (m.is_not(e) && m.is_or(to_app(e)->get_arg(0))) + push_not_or(m, to_app(to_app(e)->get_arg(0)), pr, result, result_prs); + else + push_core(m, e, pr, result, result_prs); +} + diff --git a/lib/simplifier.h b/lib/simplifier.h new file mode 100644 index 000000000..7c5bc3102 --- /dev/null +++ b/lib/simplifier.h @@ -0,0 +1,232 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + simplifier.h + +Abstract: + + Generic expression simplifier with support for theory specific "plugins". + +Author: + + Leonardo (leonardo) 2008-01-03 + +Notes: + +--*/ +#ifndef _SIMPLIFIER_H_ +#define _SIMPLIFIER_H_ + +#include"base_simplifier.h" +#include"simplifier_plugin.h" +#include"plugin_manager.h" +#include"ast_util.h" +#include"obj_hashtable.h" + +/** + \brief Local simplifier. + Proof production can be enabled/disabled. + + The simplifier can also apply substitutions during the + simplification. A substitution is a mapping from expression + to expression+proof, where for each entry e_1->(e_2,p) p is + a proof for (= e_1 e_2). + + The simplifier can also generate coarse grain proofs. In a coarse + proof, local rewrite steps are omitted, and only the substitutions + used are tracked. + + Example: + + Consider the expression (+ a b), and the substitution b->(0, p) + When fine grain proofs are enabled, the simplifier will produce the + following proof + + Assume the id of the proof object p is $0. Note that p is a proof for (= b 0). + + $1: [reflexivity] |- (= a a) + $2: [congruence] $1 $0 |- (= (+ a b) (+ a 0)) + $3: [plus-0] |- (= (+ a 0) a) + $4: [transitivity] $2 $3 |- (= (+ a b) a) + + When coarse grain proofs are enabled, the simplifier produces the following + proof: + + $1: [simplifier] $0 |- (= (+ a b) a) +*/ +class simplifier : public base_simplifier { +protected: + typedef simplifier_plugin plugin; + plugin_manager m_plugins; + ptr_vector m_args; + vector m_mults; + ptr_vector m_args2; + + proof_ref_vector m_proofs; // auxiliary vector for implementing exhaustive simplification. + proof_ref_vector m_subst_proofs; // in coarse grain proof generation mode, this vector tracks the justification for substitutions (see method get_subst). + + bool m_need_reset; + bool m_use_oeq; + + bool m_visited_quantifier; //!< true, if the simplifier found a quantifier + + bool m_ac_support; + + expr_mark m_ac_mark; + ptr_vector m_ac_marked; + obj_map m_ac_cache; // temporary cache for ac + obj_map m_ac_pr_cache; // temporary cache for ac + obj_map m_colors; // temporary cache for topological sort. + obj_map m_ac_mults; + + /* + Simplifier uses an idiom for rewriting ASTs without using recursive calls. + + - It uses a cache (field m_cache in base_simplifier) and a todo-stack (field m_todo in base_simplifier). + + - The cache is a mapping from AST to (AST + Proof). An entry [n -> (n',pr)] is used to store the fact + that n and n' are equivalent and pr is a proof for that. If proofs are disabled, then pr is 0. + We say n' is the result of the simplification of n. + Note: Some simplifications do not preserve equivalence, but equisatisfiability. + For saving space, we use pr = 0 also to represent the reflexivity proof [n -> (n, 0)]. + + + - The simplifier can be extended using plugin (subclasses of the class simplifier_plugin). + Each theory has a family ID. All operators (func_decls) and sorts from a given theory have + the same family_id. Given an application (object of the class app), we use the method + get_family_id() to obtain the family id of the operator in this application. + The simplifier uses plugin to apply theory specific simplifications. The basic idea is: + whenever an AST with family_id X is found, invoke the plugin for this family_id. + A simplifier_plugin implements the following API: + 1) bool reduce(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) + This method is invoked when the simplifier is trying to reduce/simplify an application + of the form (f args[0] ... args[num_args - 1]), and f has a family_id associated with + the plugin. The plugin may return false to indicate it could not simplify this application. + If it returns true (success), the result should be stored in the argument result. + + 2) bool reduce(func_decl * f, unsigned num_args, rational const * mults, expr * const * args, expr_ref & result); + This method is a similar to the previous one, and it is used to handle associative operators. + A plugin does not need to implement this method, the default implementation will use the previous one. + The arguments mults indicates the multiplicity of every argument in args. + For example, suppose this reduce is invoked with the arguments (f, 2, [3, 2], [a, b], result). + This represents the application (f a a a b b). + Some theory simplifiers may have efficient ways to encode this multiplicity. For example, + the arithmetic solver, if f is "+", the multiplicity can be encoded using "*". + This optimization is used because some benchmarks can create term that are very huge when + flattened. One "real" example (that motivated this optimization) is: + let a1 = x1 + x1 + let a2 = a1 + a1 + ... + let an = a{n-1} + a{n-1} + an + In this example, n was 32, so after AC flattening, we had an application + (+ x1 ... x1) with 2^32 arguments. Using the simple reduce would produce a stack overflow. + + This class uses a topological sort for computing the multiplicities efficiently. + So, the field m_colors is used to implement the topological sort. + + + 3) bool reduce_eq(expr * lhs, expr * rhs, expr_ref & result) + This method is invoked when the sort of lhs and rhs has a family_id associated with the plugin. + This method allows theory specific simplifications such as: + (= (+ a b) b) --> (= a 0) + Assuming n1 is a reference to (+ a b) and n2 to b, the simplifier would invoke + reduce_eq(n1, n2, result) + Like reduce, false can be returned if a simplification could not be applied. + And if true is returned, then the result is stored in the argument result. + + 4) bool reduce_distinct(unsigned num_args, expr * const * args, expr_ref & result) + It is similar to reduce_eq, but it used for theory specific simplifications for + (distinct args[0] ... args[num_args-1]) + Example: + (distinct 0 1 ... n) --> true + + - The idiom used in this class is implemented in the methdo reduce_core. + See reduce_core for more details. The basic idea is: + + 1) Get the next ast to be simplified from the todo-stack. + 2) If it is already cached, then do nothing. That is, this expression was already simplified. + 3) Otherwise, check whether all arguments already have been simplified (method visit_children). + 3a) The arguments that have not been simplified are added to the todo-stack by visit_children. + In this case visit_children will return false. + 3b) If all arguments have already been simplified, then invoke reduce1 to perform a reduction/simplification + step. The cache is updated with the result. + + - After invoking reduce_core(n), the cache contains an entry [n -> (n', pr)]. + + */ + + void flush_cache(); + + /** + \brief This method can be redefined in subclasses of simplifier to implement substitutions. + It returns true if n should be substituted by r, where the substitution is justified by the + proof p. The field m_subst_proofs is used to store these justifications when coarse proofs are used (PGM_COARSE). + This method is redefined in the class subst_simplifier. It is used in asserted_formulas + for implementing constant elimination. For example, if asserted_formulas contains the atoms + (= a (+ b 1)) (p a c), then the constant "a" can be eliminated. This is achieved by set (+ b 1) as + a substitution for "a". + */ + virtual bool get_subst(expr * n, expr_ref & r, proof_ref & p); + + void reduce_core(expr * n); + bool visit_children(expr * n); + bool visit_ac(app * n); + virtual bool visit_quantifier(quantifier * q); + void reduce1(expr * n); + void reduce1_app(app * n); + void reduce1_app_core(app * n); + void reduce1_ac_app_core(app * n); + void mk_congruent_term(app * n, app_ref & r, proof_ref & p); + void mk_ac_congruent_term(app * n, app_ref & r, proof_ref & p); + bool get_args(app * n, ptr_vector & result, proof_ref & p); + void get_ac_args(app * n, ptr_vector & args, vector & mults); + virtual void reduce1_quantifier(quantifier * q); + void dump_rewrite_lemma(func_decl * decl, unsigned num_args, expr * const * args, expr* result); + void ac_top_sort(app * n, ptr_buffer & result); + +public: + simplifier(ast_manager & manager); + virtual ~simplifier(); + + void enable_ac_support(bool flag); + + /** + \brief Simplify the expression \c s. Store the result in \c r, and a proof that (= s r) in \c p. + */ + void operator()(expr * s, expr_ref & r, proof_ref & p); + void reset() { if (m_need_reset) { flush_cache(); m_need_reset = false; } } + + bool visited_quantifier() const { return m_visited_quantifier; } + + void mk_app(func_decl * decl, unsigned num_args, expr * const * args, expr_ref & r); + void cache_result(expr * n, expr * r, proof * p) { m_need_reset = true; base_simplifier::cache_result(n, r, p); } + + void register_plugin(plugin * p); + ptr_vector::const_iterator begin_plugins() const { return m_plugins.begin(); } + ptr_vector::const_iterator end_plugins() const { return m_plugins.end(); } + + plugin * get_plugin(family_id fid) const { return m_plugins.get_plugin(fid); } + + ast_manager & get_manager() { return m_manager; } + + void borrow_plugins(simplifier const & s); + void release_plugins(); + + void enable_presimp(); +}; + +class subst_simplifier : public simplifier { +protected: + expr_map * m_subst_map; + virtual bool get_subst(expr * n, expr_ref & r, proof_ref & p); +public: + subst_simplifier(ast_manager & manager):simplifier(manager), m_subst_map(0) {} + void set_subst_map(expr_map * s); +}; + +void push_assertion(ast_manager & m, expr * e, proof * pr, expr_ref_vector & result, proof_ref_vector & result_prs); + +#endif diff --git a/lib/simplifier_plugin.cpp b/lib/simplifier_plugin.cpp new file mode 100644 index 000000000..1cdaadf8a --- /dev/null +++ b/lib/simplifier_plugin.cpp @@ -0,0 +1,46 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + simplifier_plugin.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-12-29. + +Revision History: + +--*/ +#include"simplifier_plugin.h" + +/** + \brief Copy every args[i] mult[i] times to new_args. +*/ +void expand_args(unsigned num_args, rational const * mults, expr * const * args, ptr_buffer & new_args) { + for (unsigned i = 0; i < num_args; i++) { + rational const & c = mults[i]; + SASSERT(c.is_int()); + rational j(0); + while (j < c) { + new_args.push_back(args[i]); + j++; + } + } +} + +bool simplifier_plugin::reduce(func_decl * f, unsigned num_args, rational const * mults, expr * const * args, expr_ref & result) { + set_reduce_invoked(); + if (f->is_idempotent()) { + return reduce(f, num_args, args, result); + } + else { + ptr_buffer new_args; + expand_args(num_args, mults, args, new_args); + return reduce(f, new_args.size(), new_args.c_ptr(), result); + } +} diff --git a/lib/simplifier_plugin.h b/lib/simplifier_plugin.h new file mode 100644 index 000000000..d95547a69 --- /dev/null +++ b/lib/simplifier_plugin.h @@ -0,0 +1,94 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + simplifier_plugin.h + +Abstract: + + Expression simplifier plugin interface. + +Author: + + Leonardo (leonardo) 2008-01-03 + +--*/ + +#ifndef __SIMPLIFIER_PLUGIN_H__ +#define __SIMPLIFIER_PLUGIN_H__ + +#include"ast.h" + +class simplifier; + +void expand_args(unsigned num_args, rational const * mults, expr * const * args, ptr_buffer & new_args); + +/** + \brief Abstract simplifier for the operators in a given family. +*/ +class simplifier_plugin { +protected: + ast_manager & m_manager; + family_id m_fid; + bool m_presimp; // true if simplifier is performing pre-simplification... + bool m_reduce_invoked; // true if one of the reduce methods were invoked. + + void set_reduce_invoked() { m_reduce_invoked = true; } + +public: + simplifier_plugin(symbol const & fname, ast_manager & m):m_manager(m), m_fid(m.get_family_id(fname)), m_presimp(false), m_reduce_invoked(false) {} + + bool reduce_invoked() const { return m_reduce_invoked; } + + virtual ~simplifier_plugin() {} + + virtual simplifier_plugin * mk_fresh() { + UNREACHABLE(); + return 0; + } + + /** + \brief Return in \c result an expression \c e equivalent to (f args[0] ... args[num_args - 1]). + + Return true if succeeded. + */ + virtual bool reduce(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { set_reduce_invoked(); return false; } + + /** + \brief Return in \c result an expression \c e equivalent to (f args[0] ... args[0] ... args[num_args - 1]). + Where each args[i] occurs mults[i] times. + + Return true if succeeded. + */ + virtual bool reduce(func_decl * f, unsigned num_args, rational const * mults, expr * const * args, expr_ref & result); + + /** + \brief Return in \c result an expression \c e equivalent to (= lhs rhs). + + Return true if succeeded. + */ + virtual bool reduce_eq(expr * lhs, expr * rhs, expr_ref & result) { set_reduce_invoked(); return false; } + + /** + \brief Return in \c result an expression \c e equivalent to (distinct args[0] ... args[num_args-1]). + + Return true if succeeded. + */ + virtual bool reduce_distinct(unsigned num_args, expr * const * args, expr_ref & result) { set_reduce_invoked(); return false; } + + family_id get_family_id() const { return m_fid; } + + /** + \brief Simplifiers may maintain local caches. These caches must be flushed when this method is invoked. + */ + virtual void flush_caches() { /* do nothing */ } + + ast_manager & get_manager() { return m_manager; } + + void enable_presimp(bool flag) { m_presimp = flag; } + + virtual void enable_ac_support(bool flag) {} +}; + +#endif diff --git a/lib/simplify_cmd.cpp b/lib/simplify_cmd.cpp new file mode 100644 index 000000000..089eb353b --- /dev/null +++ b/lib/simplify_cmd.cpp @@ -0,0 +1,128 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + simplify_cmd.cpp + +Abstract: + SMT2 front-end 'simplify' command. + +Author: + + Leonardo (leonardo) 2011-04-20 + +Notes: + +--*/ +#include"cmd_context.h" +#include"th_rewriter.h" +#include"shared_occs.h" +#include"ast_smt_pp.h" +#include"for_each_expr.h" +#include"parametric_cmd.h" +#include"scoped_timer.h" +#include"scoped_ctrl_c.h" +#include"cancel_eh.h" +#include + +class simplify_cmd : public parametric_cmd { + expr * m_target; +public: + simplify_cmd(char const * name = "simplify"):parametric_cmd(name) {} + + virtual char const * get_usage() const { return " ( )*"; } + + virtual char const * get_main_descr() const { + return "simplify the given term using builtin theory simplification rules."; + } + + virtual void init_pdescrs(cmd_context & ctx, param_descrs & p) { + th_rewriter::get_param_descrs(p); + insert_timeout(p); + p.insert(":print", CPK_BOOL, "(default: true) print the simplified term."); + p.insert(":print-proofs", CPK_BOOL, "(default: false) print a proof showing the original term is equal to the resultant one."); + p.insert(":print-statistics", CPK_BOOL, "(default: false) print statistics."); + } + + virtual ~simplify_cmd() { + } + + virtual void prepare(cmd_context & ctx) { + parametric_cmd::prepare(ctx); + m_target = 0; + } + + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { + if (m_target == 0) return CPK_EXPR; + return parametric_cmd::next_arg_kind(ctx); + } + + virtual void set_next_arg(cmd_context & ctx, expr * arg) { + m_target = arg; + } + + virtual void execute(cmd_context & ctx) { + if (m_target == 0) + throw cmd_exception("invalid simplify command, argument expected"); + expr_ref r(ctx.m()); + proof_ref pr(ctx.m()); + if (m_params.get_bool(":som", false)) + m_params.set_bool(":flat", true); + th_rewriter s(ctx.m(), m_params); + unsigned cache_sz; + unsigned num_steps = 0; + unsigned timeout = m_params.get_uint(":timeout", UINT_MAX); + bool failed = false; + cancel_eh eh(s); + { + scoped_ctrl_c ctrlc(eh); + scoped_timer timer(timeout, &eh); + cmd_context::scoped_watch sw(ctx); + try { + s(m_target, r, pr); + } + catch (z3_error & ex) { + throw ex; + } + catch (z3_exception & ex) { + ctx.regular_stream() << "(error \"simplifier failed: " << ex.msg() << "\")" << std::endl; + failed = true; + r = m_target; + } + cache_sz = s.get_cache_size(); + num_steps = s.get_num_steps(); + s.cleanup(); + } + if (m_params.get_bool(":print", true)) { + ctx.display(ctx.regular_stream(), r); + ctx.regular_stream() << std::endl; + } + if (!failed && m_params.get_bool(":print-proofs", false)) { + ast_smt_pp pp(ctx.m()); + pp.set_logic(ctx.get_logic().str().c_str()); + pp.display_expr_smt2(ctx.regular_stream(), pr.get()); + ctx.regular_stream() << std::endl; + } + if (m_params.get_bool(":print-statistics", false)) { + shared_occs s1(ctx.m()); + if (!failed) + s1(r); + unsigned long long max_mem = memory::get_max_used_memory(); + unsigned long long mem = memory::get_allocation_size(); + ctx.regular_stream() << "(:time " << std::fixed << std::setprecision(2) << ctx.get_seconds() << " :num-steps " << num_steps + << " :memory " << std::fixed << std::setprecision(2) << static_cast(mem)/static_cast(1024*1024) + << " :max-memory " << std::fixed << std::setprecision(2) << static_cast(max_mem)/static_cast(1024*1024) + << " :cache-size: " << cache_sz + << " :num-nodes-before " << get_num_exprs(m_target); + if (!failed) + ctx.regular_stream() << " :num-shared " << s1.num_shared() << " :num-nodes " << get_num_exprs(r); + ctx.regular_stream() << ")" << std::endl; + } + } +}; + + +void install_simplify_cmd(cmd_context & ctx, char const * cmd_name) { + ctx.insert(alloc(simplify_cmd, cmd_name)); +} diff --git a/lib/simplify_cmd.h b/lib/simplify_cmd.h new file mode 100644 index 000000000..1d77e5e89 --- /dev/null +++ b/lib/simplify_cmd.h @@ -0,0 +1,25 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + simplify_cmd.h + +Abstract: + SMT2 front-end 'simplify' command. + +Author: + + Leonardo (leonardo) 2011-04-20 + +Notes: + +--*/ +#ifndef _SIMPLIFY_CMD_H_ +#define _SIMPLIFY_CMD_H_ + +class cmd_context; + +void install_simplify_cmd(cmd_context & ctx, char const * cmd_name = "simplify"); + +#endif diff --git a/lib/simplify_tactic.cpp b/lib/simplify_tactic.cpp new file mode 100644 index 000000000..2a912822b --- /dev/null +++ b/lib/simplify_tactic.cpp @@ -0,0 +1,138 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + simplify_tactic.cpp + +Abstract: + + Apply simplification and rewriting rules. + +Author: + + Leonardo (leonardo) 2011-11-20 + +Notes: + +--*/ +#include"simplify_tactic.h" +#include"th_rewriter.h" +#include"ast_smt2_pp.h" + +struct simplify_tactic::imp { + ast_manager & m_manager; + th_rewriter m_r; + unsigned m_num_steps; + + imp(ast_manager & m, params_ref const & p): + m_manager(m), + m_r(m, p), + m_num_steps(0) { + } + + ast_manager & m() const { return m_manager; } + + void set_cancel(bool f) { + m_r.set_cancel(f); + } + + void reset() { + m_r.reset(); + m_num_steps = 0; + } + + void operator()(goal & g) { + SASSERT(g.is_well_sorted()); + tactic_report report("simplifier", g); + TRACE("before_simplifier", g.display(tout);); + m_num_steps = 0; + if (g.inconsistent()) + return; + expr_ref new_curr(m()); + proof_ref new_pr(m()); + unsigned size = g.size(); + for (unsigned idx = 0; idx < size; idx++) { + if (g.inconsistent()) + break; + expr * curr = g.form(idx); + m_r(curr, new_curr, new_pr); + m_num_steps += m_r.get_num_steps(); + if (g.proofs_enabled()) { + proof * pr = g.pr(idx); + new_pr = m().mk_modus_ponens(pr, new_pr); + } + g.update(idx, new_curr, new_pr, g.dep(idx)); + } + TRACE("after_simplifier_bug", g.display(tout);); + g.elim_redundancies(); + TRACE("after_simplifier", g.display(tout);); + SASSERT(g.is_well_sorted()); + } + + unsigned get_num_steps() const { return m_num_steps; } +}; + +simplify_tactic::simplify_tactic(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); +} + +simplify_tactic::~simplify_tactic() { + dealloc(m_imp); +} + +void simplify_tactic::updt_params(params_ref const & p) { + m_params = p; + m_imp->m_r.updt_params(p); +} + +void simplify_tactic::get_param_descrs(param_descrs & r) { + th_rewriter::get_param_descrs(r); +} + +void simplify_tactic::operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + (*m_imp)(*(in.get())); + in->inc_depth(); + result.push_back(in.get()); + mc = 0; pc = 0; core = 0; +} + +void simplify_tactic::set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); +} + +void simplify_tactic::cleanup() { + ast_manager & m = m_imp->m(); + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } +} + +unsigned simplify_tactic::get_num_steps() const { + return m_imp->get_num_steps(); +} + +tactic * mk_simplify_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(simplify_tactic, m, p)); +} + +tactic * mk_elim_and_tactic(ast_manager & m, params_ref const & p) { + params_ref xp = p; + xp.set_bool(":elim-and", true); + return using_params(mk_simplify_tactic(m, xp), xp); +} + diff --git a/lib/simplify_tactic.h b/lib/simplify_tactic.h new file mode 100644 index 000000000..c186dc365 --- /dev/null +++ b/lib/simplify_tactic.h @@ -0,0 +1,54 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + simplify_tactic.h + +Abstract: + + Apply simplification and rewriting rules. + +Author: + + Leonardo (leonardo) 2011-11-20 + +Notes: + +--*/ +#ifndef _SIMPLIFY_TACTIC_H_ +#define _SIMPLIFY_TACTIC_H_ + +#include"tactic.h" +#include"tactical.h" + +class simplify_tactic : public tactic { + struct imp; + imp * m_imp; + params_ref m_params; +public: + simplify_tactic(ast_manager & m, params_ref const & ref = params_ref()); + virtual ~simplify_tactic(); + + virtual void updt_params(params_ref const & p); + static void get_param_descrs(param_descrs & r); + virtual void collect_param_descrs(param_descrs & r) { get_param_descrs(r); } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core); + + virtual void cleanup(); + + unsigned get_num_steps() const; + virtual void set_cancel(bool f); + + virtual tactic * translate(ast_manager & m) { return alloc(simplify_tactic, m, m_params); } +}; + +tactic * mk_simplify_tactic(ast_manager & m, params_ref const & p = params_ref()); +tactic * mk_elim_and_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/skip_list_base.h b/lib/skip_list_base.h new file mode 100644 index 000000000..feaa00763 --- /dev/null +++ b/lib/skip_list_base.h @@ -0,0 +1,871 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + skip_list_base.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-10-01. + +Revision History: + + WARNING: IT IS NOT SAFE TO STORE KEYS, VALUES in the SKIP_LIST that need non-default constructors/destructors. + +--*/ +#ifndef _SKIP_LIST_BASE_H_ +#define _SKIP_LIST_BASE_H_ + +#include +#include"util.h" +#include"memory_manager.h" +#include"small_object_allocator.h" +#include"trace.h" + +#ifdef _MSC_VER +#pragma warning(disable : 4200) +#endif + +/* + This file defines a base class for implementing skip-list like data-structures. + This base class is relies on a manager for providing some basic services. + The manager is a template parameter. + + A Skip-list manager is responsible for: + + - Providing primitives for allocating/deallocating memory + void * allocate(size_t size); + void deallocate(size_t size, void* p); + - Generating random skip-list levels efficiently + unsigned random_level(unsigned max_level); + - Call-backs that will be invoked when a reference for a "value" stored in the skip-list is incremented/decremented. + void inc_ref_eh(value const & v); + void dec_ref_eh(value const & h); +*/ + +/** + \brief Base class for generating random_levels. +*/ +class random_level_manager { +#define SL_BITS_IN_RANDOM 16 + unsigned m_random_data; + unsigned m_random_bits:16; + unsigned m_random_left:16; + + unsigned random_value() { + return ((m_random_data = m_random_data * 214013L + 2531011L) >> 16) & 0xffff; + } + + void init_random() { + m_random_data = 0; + m_random_bits = random_value(); + m_random_left = SL_BITS_IN_RANDOM/2; + } +public: + random_level_manager() { + init_random(); + } + + unsigned random_level(unsigned max_level) { + unsigned level = 1; + unsigned b; + do { + b = m_random_bits&3; + if (!b) + level++; + m_random_bits >>= 2; + m_random_left--; + if (m_random_left == 0) { + m_random_bits = random_value(); + m_random_left = SL_BITS_IN_RANDOM/2; + } + } while (!b); + return (level > max_level ? max_level : level); + } + +}; + +/** + \brief Basic skip-list manager. + The class is parametrized by the Value type that is stored in the skip-list. +*/ +template +class sl_manager_base : public random_level_manager { + typedef Value value; + + small_object_allocator m_alloc; + +public: + void * allocate(size_t size) { + return m_alloc.allocate(size); + } + + void deallocate(size_t size, void* p) { + m_alloc.deallocate(size, p); + } + + void inc_ref_eh(value const & v) { + /* do nothing */ + } + + void dec_ref_eh(value const & h) { + /* do nothing */ + } +}; + +#define SL_SIZE_NUM_BITS 12 +#define SL_CAPACITY_NUM_BITS SL_SIZE_NUM_BITS +#define SL_MAX_CAPACITY ((1 << SL_SIZE_NUM_BITS) - 1) +#define SL_LEVEL_NUM_BITS 8 +#define SL_MAX_LEVEL ((1 << SL_LEVEL_NUM_BITS) - 1) +COMPILE_TIME_ASSERT(SL_SIZE_NUM_BITS == SL_CAPACITY_NUM_BITS); +COMPILE_TIME_ASSERT(SL_SIZE_NUM_BITS + SL_CAPACITY_NUM_BITS + SL_LEVEL_NUM_BITS == 32); + +/** + \brief Base (template) class for implementing skip-list like data-structures where + entries are stored in buckets to improve cache behavior. + + The Traits template parameter must provide: + + - a definition for the class Traits::manager + - a definition for the class Traits::entry which provides: + - a definition for the types key and value + - the methods: + key const & begin_key() const + key const & end_key() const + value const & val() const + void set_begin_key(key const & k) + void set_end_key(key const & k) + void set_val(value const & v) + void display(ostream & out) const + - the maximal number of levels Traits::max_level + - the maximal capacity of each bucket Traits::max_capacity + - the initial capacity of the first bucket Traits::initial_capacity + - flag for reference counting support Traits::ref_count. If this flag is true + the methods inc_ref_eh and dec_ref_eh in the manager object will be invoked. + - the methods + bool lt(key const & k1, key const & k2) + bool eq(key const & k1, key const & k2) + bool val_eq(value const & v1, value const & v2) + key succ(key const & k) + key pred(key const & k) +*/ +template +class skip_list_base : protected Traits { +protected: + typedef typename Traits::entry entry; +public: + typedef typename Traits::manager manager; + typedef typename entry::key key; + typedef typename entry::value value; + + struct bucket { + unsigned m_size:SL_SIZE_NUM_BITS; //!< number of entries stored in the bucket. + unsigned m_capacity:SL_CAPACITY_NUM_BITS; //!< capacity (number of entries) that can be stored in the bucket. + unsigned m_level:SL_LEVEL_NUM_BITS; + char m_extra[0]; + + static unsigned get_obj_size(unsigned num_lvls, unsigned capacity) { + return sizeof(bucket) + num_lvls*sizeof(bucket*) + capacity*sizeof(entry); + } + + entry * get_entries() { return reinterpret_cast(m_extra); } + entry const * get_entries() const { return reinterpret_cast(m_extra); } + + bucket ** next_vect() { return reinterpret_cast(get_entries() + m_capacity); } + bucket * const * next_vect() const { return reinterpret_cast(get_entries() + m_capacity); } + + bucket(unsigned lvl, unsigned capacity = Traits::max_capacity): + m_size(0), + m_capacity(capacity), + m_level(lvl) { + memset(next_vect(), 0, sizeof(bucket*)*lvl); + } + + unsigned level() const { return m_level; } + unsigned size() const { return m_size; } + unsigned capacity() const { return m_capacity; } + bool empty() const { return size() == 0; } + void set_size(unsigned sz) { m_size = sz; } + void shrink(unsigned delta) { m_size -= delta; } + void expand(unsigned delta) { m_size += delta; } + entry & first_entry() { SASSERT(!empty()); return get_entries()[0]; } + entry & last_entry() { SASSERT(!empty()); return get_entries()[size() - 1]; } + entry const & first_entry() const { SASSERT(!empty()); return get_entries()[0]; } + entry const & last_entry() const { SASSERT(!empty()); return get_entries()[size() - 1]; } + entry const & get(unsigned idx) const { SASSERT(idx < size()); return get_entries()[idx]; } + entry & get(unsigned idx) { SASSERT(idx < size()); return get_entries()[idx]; } + void set(unsigned idx, entry const & e) { SASSERT(idx < capacity()); get_entries()[idx] = e; } + bucket * get_next(unsigned idx) const { return next_vect()[idx]; } + void set_next(unsigned idx, bucket * bt) { SASSERT(idx < level()); next_vect()[idx] = bt; } + }; + + // Only the header bucket has zero entries. + bucket * m_header; + + bucket * first_bucket() const { + return m_header->get_next(0); + } + +#ifdef Z3DEBUG + /** + \brief (debugging only) Return the predecessor bucket of the given bucket. + + \pre bt != m_header, and bt is a bucket of the list. + */ + bucket * pred_bucket(bucket * bt) const { + SASSERT(bt != m_header); + bucket * curr = m_header; + while (curr->get_next(0) != bt) { + curr = curr->get_next(0); + SASSERT(curr != 0); // bt is not in the list + } + return curr; + } +#endif + + bool lt(key const & k1, key const & k2) const { return Traits::lt(k1, k2); } + + bool gt(key const & k1, key const & k2) const { return lt(k2, k1); } + + bool geq(key const & k1, key const & k2) const { return !lt(k1, k2); } + + bool leq(key const & k1, key const & k2) const { return !gt(k1, k2); } + + /** + \brief Create a new bucket of the given level. + */ + static bucket * mk_bucket(manager & m, unsigned lvl, unsigned capacity = Traits::max_capacity) { + void * mem = m.allocate(bucket::get_obj_size(lvl, capacity)); + return new (mem) bucket(lvl, capacity); + } + + static bucket * mk_header(manager & m, unsigned lvl) { + return mk_bucket(m, lvl, 0); + } + + static void inc_ref(manager & m, value const & v) { + if (Traits::ref_count) + m.inc_ref_eh(v); + } + + static void dec_ref(manager & m, value const & v) { + if (Traits::ref_count) + m.dec_ref_eh(v); + } + + /** + \brief Invoke dec_ref_eh for each value stored in the bucket. + */ + static void dec_ref(manager & m, bucket * bt) { + if (Traits::ref_count) { + unsigned sz = bt->size(); + for (unsigned i = 0; i < sz; i++) + m.dec_ref_eh(bt->get(i).val()); + } + } + + /** + \brief Deallocate the given bucket. + + \remark This method invokes dec_ref_eh for each value in the bucket. + */ + template + static void deallocate_bucket(manager & m, bucket * bt) { + if (DecRef) + dec_ref(m, bt); + unsigned sz = bucket::get_obj_size(bt->level(), bt->capacity()); + bt->~bucket(); + m.deallocate(sz, bt); + } + + /** + \brief Deallocate all buckets in the skip list. + + \remark This method invokes dec_ref_eh for each value in the list. + */ + template + void deallocate_list(manager & m) { + bucket * curr = m_header; + while (curr != 0) { + bucket * old = curr; + curr = curr->get_next(0); + deallocate_bucket(m, old); + } + } + +#ifdef Z3DEBUG + /** + \brief Check the following property + + for all i \in [0, b->level()) . pred_vect[i]->get_next(i) == b + */ + bool check_pred_vect(bucket * bt, bucket * pred_vect[]) { + if (bt == 0) + return true; + for (unsigned i = 0; i < bt->level(); i++) { + SASSERT(pred_vect[i]->get_next(i) == bt); + } + return true; + } +#endif + + /** + \brief Delete the given buffer and update the forward/next pointer of the buckets in pred_vect. + + \remark This method invokes dec_ref_eh for each value in the bucket. + */ + void del_bucket(manager & m, bucket * bt, bucket * pred_vect[]) { + SASSERT(check_pred_vect(bt, pred_vect)); + for (unsigned i = 0; i < bt->level(); i++) + pred_vect[i]->set_next(i, bt->get_next(i)); + deallocate_bucket(m, bt); + } + + /** + \brief Update the \c pred_vect vector from levels [0, bt->level()). + That is, bt will be now the "predecessor" for these levels. + */ + static void update_predecessor_vector(bucket * pred_vect [], bucket * bt) { + unsigned lvl = bt->level(); + for (unsigned i = 0; i < lvl; i++) { + pred_vect[i] = bt; + } + } + + /** + \brief Similar to the previous method, but the updated vector is stored in new_pred_vect. + */ + void update_predecessor_vector(bucket * pred_vect[], bucket * bt, bucket * new_pred_vect[]) { + unsigned bt_lvl = bt->level(); + for (unsigned i = 0; i < bt_lvl; i++) { + new_pred_vect[i] = bt; + } + unsigned list_lvl = level(); + for (unsigned i = bt_lvl; i < list_lvl; i++) { + new_pred_vect[i] = pred_vect[i]; + } + } + + /** + \brief Return the list level. + */ + unsigned level() const { + return m_header->level(); + } + + /** + \brief Expand/Increase the number of levels in the header. + */ + void expand_header(manager & m, unsigned new_lvl) { + SASSERT(new_lvl > level()); + bucket * new_header = mk_header(m, new_lvl); + // copy forward pointers of the old header. + unsigned old_lvl = level(); + for (unsigned i = 0; i < old_lvl; i++) + new_header->set_next(i, m_header->get_next(i)); + // update header + deallocate_bucket(m, m_header); + m_header = new_header; + } + + /** + \brief Increase list level to lvl if lvl > level() + */ + void update_list_level(manager & m, unsigned lvl) { + if (lvl > level()) { + expand_header(m, lvl); + } + } + + /** + \brief Increase list level (and store m_header in the new levels in pred_vect) if lvl > level(). + */ + void update_list_level(manager & m, unsigned lvl, bucket * pred_vect[]) { + if (lvl > level()) { + bucket * old_header = m_header; + unsigned old_lvl = m_header->level(); + expand_header(m, lvl); + for (unsigned i = 0; i < old_lvl; i++) { + if (pred_vect[i] == old_header) + pred_vect[i] = m_header; + } + for (unsigned i = old_lvl; i < lvl; i++) { + pred_vect[i] = m_header; + } + SASSERT(level() == lvl); + } + } + + /** + \brief Add first entry to the list. + + \remark This method will invoke inc_ref_eh for e.val() + */ + void insert_first_entry(manager & m, entry const & e) { + unsigned lvl = m.random_level(Traits::max_level); + bucket * new_bucket = mk_bucket(m, lvl, Traits::initial_capacity); + update_list_level(m, lvl); + for (unsigned i = 0; i < lvl; i++) { + m_header->set_next(i, new_bucket); + } + inc_ref(m, e.val()); + new_bucket->set_size(1); + new_bucket->set(0, e); + } + + /** + \brief Expand the capacity of the first-bucket in a skip-list with only one bucket. + This method assumes the capacity of the first-bucket < Traits::max_capacity + */ + void expand_first_bucket(manager & m) { + bucket * f = first_bucket(); + SASSERT(f != 0); + SASSERT(f->get_next(0) == 0); + SASSERT(f->capacity() < Traits::max_capacity); + unsigned old_capacity = f->capacity(); + SASSERT(old_capacity > 0); + unsigned new_capacity = old_capacity * 2; + if (new_capacity > Traits::max_capacity) + new_capacity = Traits::max_capacity; + unsigned lvl = f->level(); + bucket * new_f = mk_bucket(m, lvl, new_capacity); + unsigned sz = f->size(); + new_f->set_size(sz); + for (unsigned i = 0; i < sz; i++) + new_f->set(i, f->get(i)); + for (unsigned i = 0; i < lvl; i++) + m_header->set_next(i, new_f); + deallocate_bucket(m, f); + SASSERT(first_bucket() == new_f); + } + + /** + \brief Create a new bucket and divide the elements in bt between bt and the new bucket. + */ + void splice(manager & m, bucket * bt, bucket * pred_vect[]) { + SASSERT(bt->capacity() == Traits::max_capacity); + unsigned bt_lvl = bt->level(); + unsigned new_bucket_lvl = m.random_level(Traits::max_level); + bucket * new_bucket = mk_bucket(m, new_bucket_lvl); + update_list_level(m, new_bucket_lvl, pred_vect); + unsigned _lvl = std::min(bt_lvl, new_bucket_lvl); + for (unsigned i = 0; i < _lvl; i++) { + new_bucket->set_next(i, bt->get_next(i)); + bt->set_next(i, new_bucket); + } + for (unsigned i = bt_lvl; i < new_bucket_lvl; i++) { + new_bucket->set_next(i, pred_vect[i]->get_next(i)); + pred_vect[i]->set_next(i, new_bucket); + } + unsigned old_size = bt->size(); + SASSERT(old_size >= 2); + unsigned mid = old_size/2; + new_bucket->set_size(old_size - mid); + unsigned i = mid; + unsigned j = 0; + for (; i < old_size; i++, j++) { + new_bucket->set(j, bt->get(i)); + } + bt->set_size(mid); + SASSERT(!bt->empty()); + SASSERT(!new_bucket->empty()); + } + + /** + \brief Open space at position idx. The number of entries in bt is increased by one. + + \remark This method will *NOT* invoke inc_ref_eh + */ + void open_space(bucket * bt, unsigned idx) { + SASSERT(bt->size() < bt->capacity()); + SASSERT(idx <= bt->size()); + unsigned i = bt->size(); + while (i > idx) { + bt->set(i, bt->get(i-1)); + i--; + } + bt->expand(1); + } + + /** + \brief Open two spaces at position idx. The number of entries in bt is increased by one. + + \remark This method will *NOT* invoke inc_ref_eh + */ + void open_2spaces(bucket * bt, unsigned idx) { + SASSERT(bt->size() < bt->capacity() - 1); + SASSERT(idx <= bt->size()); + unsigned i = bt->size() + 1; + unsigned end = idx + 1; + while (i > end) { + bt->set(i, bt->get(i-2)); + i--; + } + bt->expand(2); + } + + /** + \brief Delete entry at position idx. + + \remark This method will invoke dec_ref_eh for the value stored in entry at position idx. + */ + void del_entry(manager & m, bucket * bt, unsigned idx) { + SASSERT(!bt->empty()); + SASSERT(idx < bt->size()); + dec_ref(m, bt->get(idx).val()); + unsigned sz = bt->size(); + for (unsigned i = idx; i < sz - 1; i++) { + bt->set(i, bt->get(i+1)); + } + bt->shrink(1); + } + + /** + \brief Create a copy of the skip list. + + \remark This method will invoke inc_ref_eh for all values copied. + */ + void clone_core(manager & m, skip_list_base * new_list) const { + bucket * pred_vect[Traits::max_level]; + unsigned lvl = level(); + new_list->update_list_level(m, lvl); + bucket * new_header = new_list->m_header; + for (unsigned i = 0; i < lvl; i++) + pred_vect[i] = new_header; + bucket * curr = first_bucket(); + while (curr != 0) { + unsigned curr_lvl = curr->level(); + bucket * new_bucket = new_list->mk_bucket(m, curr_lvl, curr->capacity()); + for (unsigned i = 0; i < curr_lvl; i++) { + pred_vect[i]->set_next(i, new_bucket); + pred_vect[i] = new_bucket; + } + unsigned curr_sz = curr->size(); + for (unsigned i = 0; i < curr_sz; i++) { + entry const & curr_entry = curr->get(i); + inc_ref(m, curr_entry.val()); + new_bucket->set(i, curr_entry); + } + new_bucket->set_size(curr_sz); + curr = curr->get_next(0); + } + } + +public: + skip_list_base(): + m_header(0) { + SASSERT(Traits::max_capacity >= 2); + SASSERT(Traits::initial_capacity >= 2); + SASSERT(Traits::initial_capacity <= Traits::max_capacity); + SASSERT(Traits::max_level >= 1); + SASSERT(Traits::max_capacity <= SL_MAX_CAPACITY); + SASSERT(Traits::max_level <= SL_MAX_LEVEL); + } + + skip_list_base(manager & m): + m_header(0) { + SASSERT(Traits::max_capacity >= 2); + SASSERT(Traits::initial_capacity >= 2); + SASSERT(Traits::initial_capacity <= Traits::max_capacity); + SASSERT(Traits::max_level >= 1); + SASSERT(Traits::max_capacity <= SL_MAX_CAPACITY); + SASSERT(Traits::max_level <= SL_MAX_LEVEL); + init(m); + } + + ~skip_list_base() { + SASSERT(m_header == 0); + } + + void deallocate(manager & m) { + deallocate_list(m); + m_header = 0; + } + + /** + \brief Deallocate the list but do not invoke dec_ref_eh. + */ + void deallocate_no_decref(manager & m) { + deallocate_list(m); + m_header = 0; + } + + /** + \brief Initialize a list that was created using the default constructor. + It can be used also to initialized a list deallocated using the method #deallocate. + */ + void init(manager & m) { + SASSERT(m_header == 0); + m_header = mk_header(m, 1); + } + + /** + \brief Remove all elements from the skip-list. + */ + void reset(manager & m) { + deallocate_list(m); + m_header = mk_header(m, 1); + } + + /** + \brief Remove all elements from the skip-list without invoking dec_ref_eh. + */ + void reset_no_decref(manager & m) { + deallocate_list(m); + m_header = mk_header(m, 1); + } + + /** + \brief Return true if the list is empty. + */ + bool empty() const { + SASSERT(m_header != 0); + return first_bucket() == 0; + } + +protected: + /** + \brief Return the position of the bucket in the skip list. + */ + unsigned get_bucket_idx(bucket const * bt) const { + bucket * curr = m_header; + unsigned pos = 0; + while (curr != 0) { + if (curr == bt) + return pos; + pos++; + curr = curr->get_next(0); + } + UNREACHABLE(); + return pos; + } + + /** + \brief Display the given entry. + */ + void display(std::ostream & out, entry const & e) const { + e.display(out); + } + + /** + \brief Display a reference to the given bucket. + */ + void display_bucket_ref(std::ostream & out, bucket const * bt) const { + if (bt == 0) + out << "NIL"; + else + out << "#" << get_bucket_idx(bt); + } + + /** + \brief Display the predecessor vector. + */ + void display_predecessor_vector(std::ostream & out, bucket const * const pred_vect[]) const { + for (unsigned i = 0; i < level(); i++) { + out << i << ": "; + display_bucket_ref(out, pred_vect[i]); + if (pred_vect[i]) { + out << " -> "; + display_bucket_ref(out, pred_vect[i]->get_next(i)); + } + out << "\n"; + } + } + + /** + \brief Display the successors of the given bucket. + */ + void display_successors(std::ostream & out, bucket const * bt) const { + out << "["; + for (unsigned i = 0; i < bt->level(); i++) { + if (i > 0) out << ", "; + display_bucket_ref(out, bt->get_next(i)); + } + out << "]"; + } + + /** + \brief Display the given bucket. + */ + void display(std::ostream & out, bucket const * bt) const { + if (bt == 0) { + out << "NIL\n"; + return; + } + out << "bucket "; + display_bucket_ref(out, bt); + out << ", capacity: " << bt->capacity() << "\n"; + out << "successors: "; + display_successors(out, bt); + out << "\n"; + out << "entries:\n"; + for (unsigned i = 0; i < bt->size(); i++) { + display(out, bt->get(i)); + out << "\n"; + } + out << "----------\n"; + } + +public: + /** + \brief Dump the skip list for debugging purposes. + It assumes that key and value types implement operator <<. + */ + void display_physical(std::ostream & out) const { + out << "{\nskip-list level: " << m_header->level() << "\n"; + bucket * curr = m_header; + while (curr != 0) { + display(out, curr); + curr = curr->get_next(0); + } + out << "}\n"; + } + + void display(std::ostream & out) const { + bucket * curr = m_header; + while (curr != 0) { + unsigned sz = curr->size(); + for (unsigned i = 0; i < sz; i++) { + if (i > 0) + out << " "; + curr->get(i).display(out); + } + curr = curr->get_next(0); + } + } + +protected: + /** + \brief Return true if bucket b2 can be reached from b1 following get_next(i) pointers + */ + bool is_reachable_at_i(bucket const * bt1, bucket const * bt2, unsigned i) const { + bucket * curr = bt1->get_next(i); + while (curr != 0) { + if (curr == bt2) + return true; + curr = curr->get_next(i); + } + return false; + } + +protected: + static void display_size_info_core(std::ostream & out, unsigned cls_size) { + out << "sizeof root: " << cls_size << "\n"; + out << "bucket max capacity: " << Traits::max_capacity << "\n"; + out << "bucket max level: " << Traits::max_level << "\n"; + out << "sizeof(bucket): " << sizeof(bucket) << " + " << sizeof(bucket*) << "*lvl + " << sizeof(entry) << "*capacity\n"; + out << "sizeof(usual bucket): " << (sizeof(bucket) + sizeof(entry)*Traits::max_capacity) << " + " << sizeof(bucket*) << "*lvl\n"; + out << "sizeof(max. bucket): " << (sizeof(bucket) + sizeof(entry)*Traits::max_capacity + sizeof(bucket*)*Traits::max_level) << "\n"; + out << "sizeof(entry): " << sizeof(entry) << "\n"; + out << "sizeof empty: " << cls_size + bucket::get_obj_size(1, 0) << "\n";; + out << "sizeof singleton: [" + << (cls_size + bucket::get_obj_size(1, 0) + bucket::get_obj_size(1, Traits::initial_capacity)) << ", " + << (cls_size + + bucket::get_obj_size(Traits::max_level, 0) + + bucket::get_obj_size(Traits::max_level, Traits::max_capacity)) << "]\n"; + } + +public: + /** + \brief Return true if skip-list has more than k buckets (not considering the header). + + \remark This method is for debugging purposes. + */ + bool has_more_than_k_buckets(unsigned k) const { + bucket * curr = first_bucket(); + while (curr != 0 && k > 0) { + curr = curr->get_next(0); + k--; + } + return curr != 0; + } + + /** + \brief Return true if the skip-list has more than k entries. + */ + bool has_more_than_k_entries(unsigned k) const { + bucket * curr = first_bucket(); + while (curr != 0 && k >= curr->size()) { + k -= curr->size(); + curr = curr->get_next(0); + } + SASSERT(curr == 0 || curr->size() > k); + return curr != 0; + } + +protected: + /** + \brief Return the amount of memory consumed by the list. + */ + unsigned memory_core(unsigned cls_size) const { + unsigned r = 0; + r += cls_size; + bucket * curr = m_header; + while (curr != 0) { + r += bucket::get_obj_size(curr->level(), curr->capacity()); + curr = curr->get_next(0); + } + return r; + } + +public: + /** + \brief Compress the buckets of the skip-list. + Make sure that all, but the last bucket, have at least \c load entries. + + \remark If load > Traits::max_capacity, then it assumes load = Traits::max_capacity. + */ + void compress(manager & m, unsigned load = Traits::max_capacity/2) { + if (load > Traits::max_capacity) + load = Traits::max_capacity; + bucket * pred_vect[Traits::max_level]; + update_predecessor_vector(pred_vect, m_header); + bucket * curr = first_bucket(); + while (curr != 0) { + update_predecessor_vector(pred_vect, curr); + bucket * next = curr->get_next(0); + while (curr->size() < load && next != 0) { + // steal entries of the successor bucket. + unsigned deficit = load - curr->size(); + unsigned next_size = next->size(); + if (next_size <= deficit) { + for (unsigned i = 0, j = curr->size(); i < next_size; i++, j++) { + curr->set(j, next->get(i)); + } + curr->expand(next_size); + bucket * new_next = next->get_next(0); + del_bucket(m, next, pred_vect); + next = new_next; + SASSERT(curr->size() <= load); + } + else { + for (unsigned i = 0, j = curr->size(); i < deficit; i++, j++) { + curr->set(j, next->get(i)); + } + curr->expand(deficit); + for (unsigned i = deficit, j = 0; i < next_size; i++, j++) { + next->set(j, next->get(i)); + } + next->set_size(next_size - deficit); + SASSERT(curr->size() == load); + } + } + curr = curr->get_next(0); + } + } + + void swap(skip_list_base & other) { + bucket * tmp = m_header; + m_header = other.m_header; + other.m_header = tmp; + } +}; + + +#endif diff --git a/lib/sls_strategy.h b/lib/sls_strategy.h new file mode 100644 index 000000000..d071ed168 --- /dev/null +++ b/lib/sls_strategy.h @@ -0,0 +1,61 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sls_strategy.h + +Abstract: + + A Stochastic Local Search (SLS) strategy + +Author: + + Christoph (cwinter) 2011-09-23 + +Notes: + +--*/ +#ifndef _SLS_STRATEGY_H_ +#define _SLS_STRATEGY_H_ + +#include"assertion_set_strategy.h" + +MK_ST_EXCEPTION(sls_exception); + +class sls_st : public assertion_set_strategy { + struct imp; + imp * m_imp; + params_ref m_params; +public: + sls_st(ast_manager & m, params_ref const & p = params_ref()); + virtual ~sls_st(); + + ast_manager & m () const; + + virtual void updt_params(params_ref const & p); + static void get_param_descrs(param_descrs & r); + virtual void collect_param_descrs(param_descrs & r) { get_param_descrs(r); } + + bool is_target(assertion_set const & s) const; + + virtual void operator()(assertion_set & s, model_converter_ref & mc); + + virtual void cleanup(); + + virtual void collect_statistics(statistics & st) const; + virtual void reset_statistics(); +protected: + virtual void set_cancel(bool f); +}; + +inline as_st * mk_sls(ast_manager & m, params_ref const & p = params_ref()) { + return clean(alloc(sls_st, m, p)); +} + + +as_st * mk_qfbv_sls_strategy(ast_manager & m, params_ref const & p); + +MK_SIMPLE_ST_FACTORY(qfbv_sls_stf, mk_qfbv_sls_strategy(m, p)); + +#endif diff --git a/lib/sls_tactic.cpp b/lib/sls_tactic.cpp new file mode 100644 index 000000000..1e2bdff6a --- /dev/null +++ b/lib/sls_tactic.cpp @@ -0,0 +1,1913 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + sls_tactic.h + +Abstract: + + A Stochastic Local Search (SLS) tactic + +Author: + + Christoph (cwinter) 2012-02-29 + +Notes: + +--*/ +#include +#include"map.h" +#include"nnf.h" +#include"cooperate.h" +#include"ast_smt2_pp.h" +#include"ast_pp.h" +#include"var_subst.h" +#include"model_pp.h" +#include"model_evaluator.h" +#include"solve_eqs_tactic.h" +#include"assertion_set_rewriter.h" +#include"shallow_context_simplifier.h" +#include"elim_uncnstr_tactic.h" +#include"bv_size_reduction_tactic.h" +#include"max_bv_sharing_tactic.h" +#include"simplify_tactic.h" +#include"stopwatch.h" +#include"expr2dot.h" +#include"propagate_values_tactic.h" +#include"sls_tactic.h" + +class sls_tactic : public tactic { + class stats { + public: + unsigned m_restarts; + stopwatch m_stopwatch; + unsigned m_full_evals; + unsigned m_incr_evals; + unsigned m_moves, m_flips, m_incs, m_decs, m_invs; + stats() : + m_restarts(0), + m_full_evals(0), + m_incr_evals(0), + m_moves(0), + m_flips(0), + m_incs(0), + m_decs(0), + m_invs(0) { + m_stopwatch.reset(); + m_stopwatch.start(); + } + void reset() { + m_full_evals = m_flips = m_incr_evals = 0; + m_stopwatch.reset(); + m_stopwatch.start(); + } + }; + + struct imp { + class score_tracker; + + class powers : public u_map { + unsynch_mpz_manager & m; + public: + powers(unsynch_mpz_manager & m) : m(m) {} + ~powers() { + for (iterator it = begin(); it != end(); it++) { + m.del(*it->m_value); + dealloc(it->m_value); + } + } + + const mpz & operator()(unsigned n) { + u_map::iterator it = find_iterator(n); + if (it != end()) + return *it->m_value; + else { + mpz * new_obj = alloc(mpz); + insert(n, new_obj); + m.power(unsynch_mpz_manager::mk_z(2), n, *new_obj); + return *new_obj; + } + } + }; + + class evaluator { + ast_manager & m_manager; + bv_util & m_bv_util; + family_id m_bv_fid; + score_tracker & m_tracker; + unsynch_mpz_manager & m_mpz_manager; + mpz m_zero, m_one, m_two; + powers & m_powers; + + + public: + evaluator(ast_manager & m, bv_util & bvu, score_tracker & t, unsynch_mpz_manager & mm, powers & p) : + m_manager(m), + m_bv_util(bvu), + m_tracker(t), + m_mpz_manager(mm), + m_zero(m_mpz_manager.mk_z(0)), + m_one(m_mpz_manager.mk_z(1)), + m_two(m_mpz_manager.mk_z(2)), + m_powers(p) { + m_bv_fid = m_bv_util.get_family_id(); + } + + ~evaluator() { + m_mpz_manager.del(m_zero); + m_mpz_manager.del(m_one); + m_mpz_manager.del(m_two); + } + + void operator()(app * n, mpz & result) { + func_decl * fd = n->get_decl(); + unsigned n_args = n->get_num_args(); + expr * const * args = n->get_args(); + + m_mpz_manager.set(result, m_zero); + + if (m_manager.is_and(n)) { + m_mpz_manager.set(result, m_one); + for (unsigned i = 0; i < n_args; i++) { + if (m_mpz_manager.neq(m_tracker.get_value(args[i]), result)) { + m_mpz_manager.set(result, m_zero); + break; + } + } + } + else if (m_manager.is_or(n)) { + for (unsigned i = 0; i < n_args; i++) { + if (m_mpz_manager.neq(m_tracker.get_value(args[i]), result)) { + m_mpz_manager.set(result, m_one); + break; + } + } + } + else if (m_manager.is_not(n)) { + SASSERT(n_args == 1); + const mpz & child = m_tracker.get_value(args[0]); + SASSERT(m_mpz_manager.is_one(child) || m_mpz_manager.is_zero(child)); + m_mpz_manager.set(result, (m_mpz_manager.is_zero(child)) ? m_one : m_zero); + } + else if (m_manager.is_eq(n)) { + SASSERT(n_args >= 2); + m_mpz_manager.set(result, m_one); + const mpz & first = m_tracker.get_value(args[0]); + for (unsigned i = 1; i < n_args; i++) { + if (m_mpz_manager.neq(m_tracker.get_value(args[i]), first)) { + m_mpz_manager.set(result, m_zero); + break; + } + } + } + else if (m_manager.is_distinct(n)) { + m_mpz_manager.set(result, m_one); + for (unsigned i = 0; i < n_args && m_mpz_manager.is_one(result); i++) { + for (unsigned j = i+1; j < n_args && m_mpz_manager.is_one(result); j++) { + if (m_mpz_manager.eq(m_tracker.get_value(args[i]), m_tracker.get_value(args[j]))) + m_mpz_manager.set(result, m_zero); + } + } + } + else if (fd->get_family_id() == m_bv_fid) { + bv_op_kind k = static_cast(fd->get_decl_kind()); + switch(k) { + case OP_CONCAT: { + SASSERT(n_args >= 2); + for (unsigned i = 0; i < n_args; i++) { + if (i != 0) { + const mpz & p = m_powers(m_bv_util.get_bv_size(args[i])); + m_mpz_manager.mul(result, p, result); + } + m_mpz_manager.add(result, m_tracker.get_value(args[i]), result); + } + break; + } + case OP_EXTRACT: { + SASSERT(n_args == 1); + const mpz & child = m_tracker.get_value(args[0]); + unsigned h = m_bv_util.get_extract_high(n); + unsigned l = m_bv_util.get_extract_low(n); + + mpz mask; + m_mpz_manager.set(mask, m_powers(h+1)); + m_mpz_manager.dec(mask); + m_mpz_manager.bitwise_and(child, mask, result); // result = [h:0] of child + + // shift result by l + for (; l != 0 ; l--) + m_mpz_manager.machine_div(result, m_two, result); + + m_mpz_manager.del(mask); + break; + } + case OP_BADD: { + SASSERT(n_args >= 2); + for (unsigned i = 0; i < n_args; i++) { + const mpz & next = m_tracker.get_value(args[i]); + m_mpz_manager.add(result, next, result); + } + const mpz & p = m_powers(m_bv_util.get_bv_size(n)); + m_mpz_manager.rem(result, p, result); + break; + } + case OP_BSUB: { + SASSERT(n_args == 2); + const mpz & p = m_powers(m_bv_util.get_bv_size(n)); + mpz temp; + m_mpz_manager.sub(m_tracker.get_value(args[0]), m_tracker.get_value(args[1]), temp); + m_mpz_manager.mod(temp, p, result); + m_mpz_manager.del(temp); + break; + } + case OP_BMUL: { + SASSERT(n_args >= 2); + m_mpz_manager.set(result, m_tracker.get_value(args[0])); + for (unsigned i = 1; i < n_args; i++) { + const mpz & next = m_tracker.get_value(args[i]); + m_mpz_manager.mul(result, next, result); + } + const mpz & p = m_powers(m_bv_util.get_bv_size(n)); + m_mpz_manager.rem(result, p, result); + break; + } + case OP_BNEG: { // 2's complement unary minus + SASSERT(n_args == 1); + const mpz & child = m_tracker.get_value(args[0]); + if (m_mpz_manager.is_zero(child)) { + m_mpz_manager.set(result, m_zero); + } + else { + unsigned bv_sz = m_bv_util.get_bv_size(n); + m_mpz_manager.bitwise_not(bv_sz, child, result); + m_mpz_manager.inc(result); // can't overflow + } + break; + } + case OP_BSDIV: + case OP_BSDIV0: + case OP_BSDIV_I: { + SASSERT(n_args == 2); + mpz x; m_mpz_manager.set(x, m_tracker.get_value(args[0])); + mpz y; m_mpz_manager.set(y, m_tracker.get_value(args[1])); + SASSERT(m_mpz_manager.is_nonneg(x) && m_mpz_manager.is_nonneg(y)); + unsigned bv_sz = m_bv_util.get_bv_size(args[0]); + const mpz & p = m_powers(bv_sz); + const mpz & p_half = m_powers(bv_sz-1); + if (x >= p_half) { m_mpz_manager.sub(x, p, x); } + if (y >= p_half) { m_mpz_manager.sub(y, p, y); } + + if (m_mpz_manager.is_zero(y)) { + if (m_mpz_manager.is_neg(x)) + m_mpz_manager.set(result, m_one); + else { + m_mpz_manager.set(result, m_powers(m_bv_util.get_bv_size(n))); + m_mpz_manager.dec(result); + } + } + else { + m_mpz_manager.machine_div(x, y, result); + } + if (m_mpz_manager.is_neg(result)) + m_mpz_manager.add(result, p, result); + m_mpz_manager.del(x); + m_mpz_manager.del(y); + break; + } + case OP_BUDIV: + case OP_BUDIV0: + case OP_BUDIV_I: { + SASSERT(n_args == 2); + mpz x; m_mpz_manager.set(x, m_tracker.get_value(args[0])); + mpz y; m_mpz_manager.set(y, m_tracker.get_value(args[1])); + + if (m_mpz_manager.is_zero(y)) { + m_mpz_manager.set(result, m_powers(m_bv_util.get_bv_size(n))); + m_mpz_manager.dec(result); + } + else { + m_mpz_manager.machine_div(x, y, result); + } + m_mpz_manager.del(x); + m_mpz_manager.del(y); + break; + } + case OP_BSREM: + case OP_BSREM0: + case OP_BSREM_I: { + SASSERT(n_args == 2); + mpz x; m_mpz_manager.set(x, m_tracker.get_value(args[0])); + mpz y; m_mpz_manager.set(y, m_tracker.get_value(args[1])); + unsigned bv_sz = m_bv_util.get_bv_size(args[0]); + const mpz & p = m_powers(bv_sz); + const mpz & p_half = m_powers(bv_sz-1); + if (x >= p_half) { m_mpz_manager.sub(x, p, x); } + if (y >= p_half) { m_mpz_manager.sub(y, p, y); } + + if (m_mpz_manager.is_zero(y)) { + m_mpz_manager.set(result, x); + } + else { + m_mpz_manager.rem(x, y, result); + } + if (m_mpz_manager.is_neg(result)) + m_mpz_manager.add(result, p, result); + m_mpz_manager.del(x); + m_mpz_manager.del(y); + break; + } + case OP_BUREM: + case OP_BUREM0: + case OP_BUREM_I: { + SASSERT(n_args == 2); + mpz x; m_mpz_manager.set(x, m_tracker.get_value(args[0])); + mpz y; m_mpz_manager.set(y, m_tracker.get_value(args[1])); + + if (m_mpz_manager.is_zero(y)) { + m_mpz_manager.set(result, x); + } + else { + m_mpz_manager.mod(x, y, result); + } + m_mpz_manager.del(x); + m_mpz_manager.del(y); + break; + } + case OP_BSMOD: + case OP_BSMOD0: + case OP_BSMOD_I:{ + SASSERT(n_args == 2); + mpz x; m_mpz_manager.set(x, m_tracker.get_value(args[0])); + mpz y; m_mpz_manager.set(y, m_tracker.get_value(args[1])); + unsigned bv_sz = m_bv_util.get_bv_size(args[0]); + const mpz & p = m_powers(bv_sz); + const mpz & p_half = m_powers(bv_sz-1); + if (x >= p_half) { m_mpz_manager.sub(x, p, x); } + if (y >= p_half) { m_mpz_manager.sub(y, p, y); } + + if (m_mpz_manager.is_zero(y)) + m_mpz_manager.set(result, x); + else { + bool neg_x = m_mpz_manager.is_neg(x); + bool neg_y = m_mpz_manager.is_neg(y); + mpz abs_x, abs_y; + m_mpz_manager.set(abs_x, x); + m_mpz_manager.set(abs_y, y); + if (neg_x) m_mpz_manager.neg(abs_x); + if (neg_y) m_mpz_manager.neg(abs_y); + SASSERT(m_mpz_manager.is_nonneg(abs_x) && m_mpz_manager.is_nonneg(abs_y)); + + m_mpz_manager.mod(abs_x, abs_y, result); + + if (m_mpz_manager.is_zero(result) || (!neg_x && !neg_y)) { + /* Nothing */ + } + else if (neg_x && !neg_y) { + m_mpz_manager.neg(result); + m_mpz_manager.add(result, y, result); + } + else if (!neg_x && neg_y) { + m_mpz_manager.add(result, y, result); + } + else { + m_mpz_manager.neg(result); + } + + m_mpz_manager.del(abs_x); + m_mpz_manager.del(abs_y); + } + + if (m_mpz_manager.is_neg(result)) + m_mpz_manager.add(result, p, result); + + m_mpz_manager.del(x); + m_mpz_manager.del(y); + break; + } + case OP_BAND: { + SASSERT(n_args >= 2); + m_mpz_manager.set(result, m_tracker.get_value(args[0])); + for (unsigned i = 1; i < n_args; i++) + m_mpz_manager.bitwise_and(result, m_tracker.get_value(args[i]), result); + break; + } + case OP_BOR: { + SASSERT(n_args >= 2); + m_mpz_manager.set(result, m_tracker.get_value(args[0])); + for (unsigned i = 1; i < n_args; i++) { + m_mpz_manager.bitwise_or(result, m_tracker.get_value(args[i]), result); + } + break; + } + case OP_BXOR: { + SASSERT(n_args >= 2); + m_mpz_manager.set(result, m_tracker.get_value(args[0])); + for (unsigned i = 1; i < n_args; i++) + m_mpz_manager.bitwise_xor(result, m_tracker.get_value(args[i]), result); + break; + } + case OP_BNAND: { + SASSERT(n_args >= 2); + mpz temp; + unsigned bv_sz = m_bv_util.get_bv_size(n); + m_mpz_manager.set(result, m_tracker.get_value(args[0])); + for (unsigned i = 1; i < n_args; i++) { + m_mpz_manager.bitwise_and(result, m_tracker.get_value(args[i]), temp); + m_mpz_manager.bitwise_not(bv_sz, temp, result); + } + m_mpz_manager.del(temp); + break; + } + case OP_BNOR: { + SASSERT(n_args >= 2); + mpz temp; + unsigned bv_sz = m_bv_util.get_bv_size(n); + m_mpz_manager.set(result, m_tracker.get_value(args[0])); + for (unsigned i = 1; i < n_args; i++) { + m_mpz_manager.bitwise_or(result, m_tracker.get_value(args[i]), temp); + m_mpz_manager.bitwise_not(bv_sz, temp, result); + } + m_mpz_manager.del(temp); + break; + } + case OP_BNOT: { + SASSERT(n_args == 1); + m_mpz_manager.bitwise_not(m_bv_util.get_bv_size(args[0]), m_tracker.get_value(args[0]), result); + break; + } + case OP_ULT: + case OP_ULEQ: + case OP_UGT: + case OP_UGEQ: { + SASSERT(n_args == 2); + const mpz & x = m_tracker.get_value(args[0]); + const mpz & y = m_tracker.get_value(args[1]); + if ((k == OP_ULT && m_mpz_manager.lt(x, y)) || + (k == OP_ULEQ && m_mpz_manager.le(x, y)) || + (k == OP_UGT && m_mpz_manager.gt(x, y)) || + (k == OP_UGEQ && m_mpz_manager.ge(x, y))) + m_mpz_manager.set(result, m_one); + break; + } + case OP_SLT: + case OP_SLEQ: + case OP_SGT: + case OP_SGEQ: { + SASSERT(n_args == 2); + mpz x; m_mpz_manager.set(x, m_tracker.get_value(args[0])); + mpz y; m_mpz_manager.set(y, m_tracker.get_value(args[1])); + unsigned bv_sz = m_bv_util.get_bv_size(args[0]); + const mpz & p = m_powers(bv_sz); + const mpz & p_half = m_powers(bv_sz-1); + if (x >= p_half) { m_mpz_manager.sub(x, p, x); } + if (y >= p_half) { m_mpz_manager.sub(y, p, y); } + if ((k == OP_SLT && m_mpz_manager.lt(x, y)) || + (k == OP_SLEQ && m_mpz_manager.le(x, y)) || + (k == OP_SGT && m_mpz_manager.gt(x, y)) || + (k == OP_SGEQ && m_mpz_manager.ge(x, y))) + m_mpz_manager.set(result, m_one); + m_mpz_manager.del(x); + m_mpz_manager.del(y); + break; + } + case OP_BIT2BOOL: { + SASSERT(n_args == 1); + const mpz & child = m_tracker.get_value(args[0]); + m_mpz_manager.set(result, child); + break; + } + case OP_BASHR: { + SASSERT(n_args == 2); + m_mpz_manager.set(result, m_tracker.get_value(args[0])); + mpz first; + const mpz & p = m_powers(m_bv_util.get_bv_size(args[0])-1); + m_mpz_manager.bitwise_and(result, p, first); + mpz shift; m_mpz_manager.set(shift, m_tracker.get_value(args[1])); + mpz temp; + while (!m_mpz_manager.is_zero(shift)) { + m_mpz_manager.machine_div(result, m_two, temp); + m_mpz_manager.add(temp, first, result); + m_mpz_manager.dec(shift); + } + m_mpz_manager.del(first); + m_mpz_manager.del(shift); + m_mpz_manager.del(temp); + break; + } + case OP_BLSHR: { + SASSERT(n_args == 2); + m_mpz_manager.set(result, m_tracker.get_value(args[0])); + mpz shift; m_mpz_manager.set(shift, m_tracker.get_value(args[1])); + while (!m_mpz_manager.is_zero(shift)) { + m_mpz_manager.machine_div(result, m_two, result); + m_mpz_manager.dec(shift); + } + m_mpz_manager.del(shift); + break; + } + case OP_BSHL: { + SASSERT(n_args == 2); + m_mpz_manager.set(result, m_tracker.get_value(args[0])); + mpz shift; m_mpz_manager.set(shift, m_tracker.get_value(args[1])); + while (!m_mpz_manager.is_zero(shift)) { + m_mpz_manager.mul(result, m_two, result); + m_mpz_manager.dec(shift); + } + const mpz & p = m_powers(m_bv_util.get_bv_size(n)); + m_mpz_manager.rem(result, p, result); + m_mpz_manager.del(shift); + break; + } + case OP_SIGN_EXT: { + SASSERT(n_args == 1); + m_mpz_manager.set(result, m_tracker.get_value(args[0])); + break; + } + default: + NOT_IMPLEMENTED_YET(); + } + } + else { + NOT_IMPLEMENTED_YET(); + } + + TRACE("sls_eval", tout << "(" << fd->get_name(); + for (unsigned i = 0; i < n_args; i++) + tout << " " << m_mpz_manager.to_string(m_tracker.get_value(args[i])); + tout << ") ---> " << m_mpz_manager.to_string(result); + if (m_manager.is_bool(fd->get_range())) tout << " [Boolean]"; + else tout << " [vector size: " << m_bv_util.get_bv_size(fd->get_range()) << "]"; + tout << std::endl; ); + + SASSERT(m_mpz_manager.is_nonneg(result)); + } + }; + + class score_tracker { + ast_manager & m_manager; + unsynch_mpz_manager & m_mpz_manager; + bv_util & m_bv_util; + powers & m_powers; + random_gen m_rng; + unsigned m_random_bits; + unsigned m_random_bits_cnt; + vector > m_traversal_stack; + evaluator m_sls_evaluator; + mpz m_zero, m_one, m_two; + + model m_dummy_model; + model_evaluator m_evaluator; + expr_ref_buffer m_temp_exprs; + + struct value_score { + value_score() : m(0), value(unsynch_mpz_manager::mk_z(0)), score(0.0), distance(0) { }; + ~value_score() { if (m) m->del(value); } + unsynch_mpz_manager * m; + mpz value; + double score; + unsigned distance; // max distance from any root + value_score & operator=(const value_score & other) { + SASSERT(m == 0 || m == other.m); + if (m) m->set(value, 0); else m = other.m; + m->set(value, other.value); + score = other.score; + distance = other.distance; + return *this; + } + }; + + typedef obj_map scores_type; + typedef obj_map > uplinks_type; + typedef obj_map entry_point_type; + typedef obj_map > occ_type; + scores_type m_scores; + uplinks_type m_uplinks; + entry_point_type m_entry_points; + ptr_vector m_constants; + ptr_vector m_temp_constants; + occ_type m_constants_occ; + + public: + score_tracker(ast_manager & m, bv_util & bvu, unsynch_mpz_manager & mm, powers & p) : + m_manager(m), + m_mpz_manager(mm), + m_bv_util(bvu), + m_powers(p), + m_random_bits_cnt(0), + m_sls_evaluator(m, m_bv_util, *this, m_mpz_manager, p), + m_zero(m_mpz_manager.mk_z(0)), + m_one(m_mpz_manager.mk_z(1)), + m_two(m_mpz_manager.mk_z(2)), + m_dummy_model(m), + m_evaluator(m_dummy_model), + m_temp_exprs(m) { + } + + ~score_tracker() { + m_mpz_manager.del(m_zero); + m_mpz_manager.del(m_one); + m_mpz_manager.del(m_two); + } + + void set_value(expr * n, const mpz & r) { + SASSERT(m_scores.contains(n)); + m_mpz_manager.set(m_scores.find(n).value, r); + } + + void set_value(func_decl * fd, const mpz & r) { + SASSERT(m_entry_points.contains(fd)); + expr * ep = get_entry_point(fd); + set_value(ep, r); + } + + mpz & get_value(expr * n) { + SASSERT(m_scores.contains(n)); + return m_scores.find(n).value; + } + + mpz & get_value(func_decl * fd) { + SASSERT(m_entry_points.contains(fd)); + expr * ep = get_entry_point(fd); + return get_value(ep); + } + + void set_score(expr * n, double score) { + SASSERT(m_scores.contains(n)); + m_scores.find(n).score = score; + } + + void set_score(func_decl * fd, double score) { + SASSERT(m_entry_points.contains(fd)); + expr * ep = get_entry_point(fd); + set_score(ep, score); + } + + double & get_score(expr * n) { + SASSERT(m_scores.contains(n)); + return m_scores.find(n).score; + } + + double & get_score(func_decl * fd) { + SASSERT(m_entry_points.contains(fd)); + expr * ep = get_entry_point(fd); + return get_score(ep); + } + + unsigned get_distance(expr * n) { + SASSERT(m_scores.contains(n)); + return m_scores.find(n).distance; + } + + void set_distance(expr * n, unsigned d) { + SASSERT(m_scores.contains(n)); + m_scores.find(n).distance = d; + } + + expr * get_entry_point(func_decl * fd) { + SASSERT(m_entry_points.contains(fd)); + return m_entry_points.find(fd); + } + + bool has_uplinks(expr * n) { + return m_uplinks.contains(n); + } + + ptr_vector & get_uplinks(expr * n) { + SASSERT(m_uplinks.contains(n)); + return m_uplinks.find(n); + } + + void initialize(app * n) { + // Build score table + if (!m_scores.contains(n)) { + value_score vs; + vs.m = & m_mpz_manager; + m_scores.insert(n, vs); + } + + // Update uplinks + unsigned na = n->get_num_args(); + for (unsigned i = 0; i < na; i++) { + expr * c = n->get_arg(i); + uplinks_type::obj_map_entry * entry = m_uplinks.insert_if_not_there2(c, ptr_vector()); + entry->get_data().m_value.push_back(n); + } + + func_decl * d = n->get_decl(); + + if (n->get_num_args() == 0) { + if (d->get_family_id() != null_family_id) { + // Interpreted constant + mpz t; + value2mpz(n, t); + set_value(n, t); + m_mpz_manager.del(t); + } + else { + // Uninterpreted constant + m_entry_points.insert_if_not_there(d, n); + m_constants.push_back(d); + } + } + } + + struct init_proc { + ast_manager & m_manager; + score_tracker & m_tracker; + + init_proc(ast_manager & m, score_tracker & tracker): + m_manager(m), + m_tracker(tracker) { + } + + void operator()(var * n) {} + + void operator()(quantifier * n) {} + + void operator()(app * n) { + m_tracker.initialize(n); + } + }; + + struct find_func_decls_proc { + ast_manager & m_manager; + ptr_vector & m_occs; + + find_func_decls_proc (ast_manager & m, ptr_vector & occs): + m_manager(m), + m_occs(occs) { + } + + void operator()(var * n) {} + + void operator()(quantifier * n) {} + + void operator()(app * n) { + if (n->get_num_args() != 0) + return; + func_decl * d = n->get_decl(); + if (d->get_family_id() != null_family_id) + return; + m_occs.push_back(d); + } + }; + + void calculate_expr_distances(goal_ref const & g) { + // precondition: m_scores is set up. + unsigned sz = g->size(); + ptr_vector stack; + for (unsigned i = 0; i < sz; i++) + stack.push_back(to_app(g->form(i))); + while (!stack.empty()) { + app * cur = stack.back(); + stack.pop_back(); + + unsigned d = get_distance(cur); + + for (unsigned i = 0; i < cur->get_num_args(); i++) { + app * child = to_app(cur->get_arg(i)); + unsigned d_child = get_distance(child); + if (d >= d_child) { + set_distance(child, d+1); + stack.push_back(child); + } + } + } + } + + void initialize(goal_ref const & g) { + init_proc proc(m_manager, *this); + expr_mark visited; + unsigned sz = g->size(); + for (unsigned i = 0; i < sz; i++) { + expr * e = g->form(i); + for_each_expr(proc, visited, e); + } + + visited.reset(); + + for (unsigned i = 0; i < sz; i++) { + expr * e = g->form(i); + ptr_vector t; + m_constants_occ.insert_if_not_there(e, t); + find_func_decls_proc ffd_proc(m_manager, m_constants_occ.find(e)); + expr_fast_mark1 visited; + quick_for_each_expr(ffd_proc, visited, e); + } + + calculate_expr_distances(g); + + TRACE("sls", tout << "Initial model:" << std::endl; show_model(tout); ); + } + + void show_model(std::ostream & out) { + unsigned sz = get_num_constants(); + for (unsigned i = 0; i < sz; i++) { + func_decl * fd = get_constant(i); + out << fd->get_name() << " = " << m_mpz_manager.to_string(get_value(fd)) << std::endl; + } + } + + model_ref get_model() { + model_ref res = alloc(model, m_manager); + unsigned sz = get_num_constants(); + for (unsigned i = 0; i < sz; i++) { + func_decl * fd = get_constant(i); + res->register_decl(fd, mpz2value(fd->get_range(), get_value(fd))); + } + return res; + } + + unsigned get_num_constants() { + return m_constants.size(); + } + + ptr_vector & get_constants() { + return m_constants; + } + + func_decl * get_constant(unsigned i) { + return m_constants[i]; + } + + void set_random_seed(unsigned s) { + m_rng.set_seed(s); + } + + mpz get_random_bv(sort * s) { + SASSERT(m_bv_util.is_bv_sort(s)); + unsigned bv_size = m_bv_util.get_bv_size(s); + mpz r; m_mpz_manager.set(r, 0); + + mpz temp; + do + { + m_mpz_manager.mul(r, m_two, temp); + m_mpz_manager.add(temp, get_random_bool(), r); + } while (--bv_size > 0); + m_mpz_manager.del(temp); + + return r; + } + + mpz & get_random_bool() { + if (m_random_bits_cnt == 0) { + m_random_bits = m_rng(); + m_random_bits_cnt = 15; // random_gen produces 15 bits of randomness. + } + + bool val = (m_random_bits & 0x01) != 0; + m_random_bits = m_random_bits >> 1; + m_random_bits_cnt--; + + return (val) ? m_one : m_zero; + } + + unsigned get_random_uint(unsigned bits) { + if (m_random_bits_cnt == 0) { + m_random_bits = m_rng(); + m_random_bits_cnt = 15; // random_gen produces 15 bits of randomness. + } + + unsigned val = 0; + while (bits-- > 0) { + if ((m_random_bits & 0x01) != 0) val++; + val <<= 1; + m_random_bits >>= 1; + m_random_bits_cnt--; + + if (m_random_bits_cnt == 0) { + m_random_bits = m_rng(); + m_random_bits_cnt = 15; // random_gen produces 15 bits of randomness. + } + } + + return val; + } + + mpz get_random(sort * s) { + if (m_bv_util.is_bv_sort(s)) + return get_random_bv(s); + else if (m_manager.is_bool(s)) + return get_random_bool(); + else + NOT_IMPLEMENTED_YET(); // This only works for bit-vectors for now. + } + + void randomize() { + TRACE("sls", tout << "Abandoned model:" << std::endl; show_model(tout); ); + + for (entry_point_type::iterator it = m_entry_points.begin(); it != m_entry_points.end(); it++) { + func_decl * fd = it->m_key; + sort * s = fd->get_range(); + mpz temp = get_random(s); + set_value(it->m_value, temp); + m_mpz_manager.del(temp); + } + + TRACE("sls", tout << "Randomized model:" << std::endl; show_model(tout); ); + } + + void randomize_local(goal_ref const & g) { + ptr_vector & unsat_constants = get_unsat_constants(g); + bool did_something = false; + for (unsigned i = 0; i < unsat_constants.size(); i++) { + func_decl * fd = unsat_constants[i]; + mpz temp = get_random(fd->get_range()); + if (m_mpz_manager.neq(temp, get_value(fd))) + did_something = true; + update(fd, temp); + m_mpz_manager.del(temp); + } + TRACE("sls", tout << "Randomization candidates: "; + for (unsigned i = 0; i < unsat_constants.size(); i++) + tout << unsat_constants[i]->get_name() << ", "; + tout << std::endl; + tout << "Locally randomized model: " << std::endl; show_model(tout); ); + } + + #define _SCORE_AND_MIN + + double score_bool(expr * n, bool negated = false) { + TRACE("sls_score", tout << ((negated)?"NEG ":"") << "BOOL: " << mk_ismt2_pp(n, m_manager) << std::endl; ); + + double res = 0.0; + + if (is_uninterp_const(n)) { + const mpz & r = get_value(n); + if (negated) + res = (m_mpz_manager.is_one(r)) ? 0.0 : 1.0; + else + res = (m_mpz_manager.is_one(r)) ? 1.0 : 0.0; + } + else if (m_manager.is_and(n)) { + SASSERT(!negated); + app * a = to_app(n); + expr * const * args = a->get_args(); + #ifdef _SCORE_AND_MIN + double min = 1.0; + for (unsigned i = 0; i < a->get_num_args(); i++) { + double cur = get_score(args[i]); + if (cur < min) min = cur; + } + res = min; + #else + double sum = 0.0; + for (unsigned i = 0; i < a->get_num_args(); i++) + sum += get_score(args[i]); + res = sum / (double) a->get_num_args(); + #endif + } + else if (m_manager.is_or(n)) { + SASSERT(!negated); + app * a = to_app(n); + expr * const * args = a->get_args(); + double max = 0.0; + for (unsigned i = 0; i < a->get_num_args(); i++) { + double cur = get_score(args[i]); + if (cur > max) max = cur; + } + res = max; + } + else if (m_manager.is_ite(n)) { + SASSERT(!negated); + app * a = to_app(n); + SASSERT(a->get_num_args() == 3); + const mpz & cond = get_value(a->get_arg(0)); + double s_t = get_score(a->get_arg(1)); + double s_f = get_score(a->get_arg(2)); + res = (m_mpz_manager.is_one(cond)) ? s_t : s_f; + } + else if (m_manager.is_eq(n) || m_manager.is_iff(n)) { + app * a = to_app(n); + SASSERT(a->get_num_args() == 2); + expr * arg0 = a->get_arg(0); + expr * arg1 = a->get_arg(1); + const mpz & v0 = get_value(arg0); + const mpz & v1 = get_value(arg1); + + if (negated) { + res = (m_mpz_manager.eq(v0, v1)) ? 0.0 : 1.0; + TRACE("sls_score", tout << "V0 = " << m_mpz_manager.to_string(v0) << " ; V1 = " << + m_mpz_manager.to_string(v1) << std::endl; ); + } + else if (m_manager.is_bool(arg0)) { + res = m_mpz_manager.eq(v0, v1) ? 1.0 : 0.0; + TRACE("sls_score", tout << "V0 = " << m_mpz_manager.to_string(v0) << " ; V1 = " << + m_mpz_manager.to_string(v1) << std::endl; ); + } + else if (m_bv_util.is_bv(arg0)) { + mpz diff, diff_m1; + m_mpz_manager.bitwise_xor(v0, v1, diff); + unsigned hamming_distance = 0; + unsigned bv_sz = m_bv_util.get_bv_size(arg0); + #if 1 // unweighted hamming distance + while (!m_mpz_manager.is_zero(diff)) { + //m_mpz_manager.set(diff_m1, diff); + //m_mpz_manager.dec(diff_m1); + //m_mpz_manager.bitwise_and(diff, diff_m1, diff); + //hamming_distance++; + if (!m_mpz_manager.is_even(diff)) { + hamming_distance++; + } + m_mpz_manager.machine_div(diff, m_two, diff); + } + res = 1.0 - (hamming_distance / (double) bv_sz); + #else + rational r(diff); + r /= m_powers(bv_sz); + double dbl = r.get_double(); + res = (dbl < 0.0) ? 1.0 : (dbl > 1.0) ? 0.0 : 1.0 - dbl; + #endif + TRACE("sls_score", tout << "V0 = " << m_mpz_manager.to_string(v0) << " ; V1 = " << + m_mpz_manager.to_string(v1) << " ; HD = " << hamming_distance << + " ; SZ = " << bv_sz << std::endl; ); + m_mpz_manager.del(diff); + m_mpz_manager.del(diff_m1); + } + else + NOT_IMPLEMENTED_YET(); + } + else if (m_bv_util.is_bv_ule(n)) { // x <= y + app * a = to_app(n); + SASSERT(a->get_num_args() == 2); + const mpz & x = get_value(a->get_arg(0)); + const mpz & y = get_value(a->get_arg(1)); + unsigned bv_sz = m_bv_util.get_bv_size(a->get_decl()->get_domain()[0]); + + if (negated) { + if (m_mpz_manager.gt(x, y)) + res = 1.0; + else { + mpz diff; + m_mpz_manager.sub(y, x, diff); + m_mpz_manager.inc(diff); + rational n(diff); + n /= rational(m_powers(bv_sz)); + double dbl = n.get_double(); + // In extreme cases, n is 0.9999 but to_double returns something > 1.0 + res = (dbl > 1.0) ? 0.0 : (dbl < 0.0) ? 1.0 : 1.0 - dbl; + m_mpz_manager.del(diff); + } + } + else { + if (m_mpz_manager.le(x, y)) + res = 1.0; + else { + mpz diff; + m_mpz_manager.sub(x, y, diff); + rational n(diff); + n /= rational(m_powers(bv_sz)); + double dbl = n.get_double(); + res = (dbl > 1.0) ? 1.0 : (dbl < 0.0) ? 0.0 : dbl; + m_mpz_manager.del(diff); + } + } + TRACE("sls_score", tout << "x = " << m_mpz_manager.to_string(x) << " ; y = " << + m_mpz_manager.to_string(y) << " ; SZ = " << bv_sz << std::endl; ); + } + else if (m_bv_util.is_bv_sle(n)) { // x <= y + app * a = to_app(n); + SASSERT(a->get_num_args() == 2); + mpz x; m_mpz_manager.set(x, get_value(a->get_arg(0))); + mpz y; m_mpz_manager.set(y, get_value(a->get_arg(1))); + unsigned bv_sz = m_bv_util.get_bv_size(a->get_decl()->get_domain()[0]); + const mpz & p = m_powers(bv_sz); + const mpz & p_half = m_powers(bv_sz-1); + if (x >= p_half) { m_mpz_manager.sub(x, p, x); } + if (y >= p_half) { m_mpz_manager.sub(y, p, y); } + + if (negated) { + if (x > y) + res = 1.0; + else { + mpz diff; + m_mpz_manager.sub(y, x, diff); + m_mpz_manager.inc(diff); + rational n(diff); + n /= p; + double dbl = n.get_double(); + res = (dbl > 1.0) ? 0.0 : (dbl < 0.0) ? 1.0 : 1.0 - dbl; + m_mpz_manager.del(diff); + } + TRACE("sls_score", tout << "x = " << m_mpz_manager.to_string(x) << " ; y = " << + m_mpz_manager.to_string(y) << " ; SZ = " << bv_sz << std::endl; ); + } + else { + if (x <= y) + res = 1.0; + else { + mpz diff; + m_mpz_manager.sub(x, y, diff); + rational n(diff); + n /= p; + double dbl = n.get_double(); + res = (dbl > 1.0) ? 1.0 : (dbl < 0.0) ? 0.0 : dbl; + m_mpz_manager.del(diff); + } + TRACE("sls_score", tout << "x = " << m_mpz_manager.to_string(x) << " ; y = " << + m_mpz_manager.to_string(y) << " ; SZ = " << bv_sz << std::endl; ); + } + m_mpz_manager.del(x); + m_mpz_manager.del(y); + } + else if (m_manager.is_not(n)) { + SASSERT(!negated); + app * a = to_app(n); + SASSERT(a->get_num_args() == 1); + expr * child = a->get_arg(0); + if (m_manager.is_and(child) || m_manager.is_or(child)) // Precondition: Assertion set is in NNF. + NOT_IMPLEMENTED_YET(); + res = score_bool(child, true); + } + else if (m_manager.is_distinct(n)) { + app * a = to_app(n); + unsigned pairs = 0, distinct_pairs = 0; + unsigned sz = a->get_num_args(); + for (unsigned i = 0; i < sz; i++) { + for (unsigned j = i+1; j < sz; j++) { + // pair i/j + const mpz & v0 = get_value(a->get_arg(0)); + const mpz & v1 = get_value(a->get_arg(1)); + pairs++; + if (v0 != v1) + distinct_pairs++; + } + } + res = (distinct_pairs/(double)pairs); + if (negated) res = 1.0 - res; + } + else + NOT_IMPLEMENTED_YET(); + + SASSERT(res >= 0.0 && res <= 1.0); + + TRACE("sls_score", tout << "SCORE = " << res << std::endl; ); + return res; + } + + double score_bv(expr * n) { + return 0.0; // a bv-expr is always scored as 0.0; we won't use those scores. + } + + void value2mpz(expr * n, mpz & result) { + m_mpz_manager.set(result, m_zero); + + if (m_manager.is_bool(n)) { + m_mpz_manager.set(result, m_manager.is_true(n) ? m_one : m_zero); + } + else if (m_bv_util.is_bv(n)) { + unsigned bv_sz = m_bv_util.get_bv_size(n); + rational q; + if (!m_bv_util.is_numeral(n, q, bv_sz)) + NOT_IMPLEMENTED_YET(); + mpq temp = q.to_mpq(); + SASSERT(m_mpz_manager.is_one(temp.denominator())); + m_mpz_manager.set(result, temp.numerator()); + } + else + NOT_IMPLEMENTED_YET(); + } + + expr_ref mpz2value(sort * s, const mpz & r) { + expr_ref res(m_manager); + if (m_manager.is_bool(s)) + res = (m_mpz_manager.is_zero(r)) ? m_manager.mk_false() : m_manager.mk_true(); + else if (m_bv_util.is_bv_sort(s)) { + rational rat(r); + res = m_bv_util.mk_numeral(rat, s); + } + else + NOT_IMPLEMENTED_YET(); + return res; + } + + void eval(expr * n, mpz & result) { + switch(n->get_kind()) { + case AST_APP: { + app * a = to_app(n); + unsigned n_args = a->get_num_args(); + + if (n_args == 0) { + m_mpz_manager.set(result, get_value(n)); + } + else { + m_sls_evaluator(a, result); + + //#define _EVAL_CHECKED + #ifdef _EVAL_CHECKED + m_temp_exprs.reset(); + for (unsigned i = 0; i < n_args; i++) { + expr * arg = a->get_arg(i); + const mpz & v = get_value(arg); + m_temp_exprs.push_back(mpz2value(m_manager.get_sort(arg), v)); + } + expr_ref q(m_manager), temp(m_manager); + q = m_manager.mk_app(fd, m_temp_exprs.size(), m_temp_exprs.c_ptr()); + m_evaluator(q, temp); + mpz check_res; + value2mpz(temp, check_res); + if (!m_mpz_manager.eq(check_res, result)) + TRACE("sls", tout << "EVAL BUG: IS " << m_mpz_manager.to_string(result) << + " SHOULD BE " << m_mpz_manager.to_string(check_res) << std::endl; ); + SASSERT(m_mpz_manager.eq(check_res, result)); + m_mpz_manager.del(check_res); + #endif + } + break; + } + default: + NOT_IMPLEMENTED_YET(); + } + // TRACE("sls", tout << "EVAL: " << mk_ismt2_pp(n, m_manager) << " IS " << res << std::endl;); + } + + double score(expr * n) { + if (m_manager.is_bool(n)) + return score_bool(n); + else if (m_bv_util.is_bv(n)) + return score_bv(n); + else + NOT_IMPLEMENTED_YET(); + } + + void run_update(unsigned cur_depth) { + // precondition: m_traversal_stack contains the entry point(s) + expr_fast_mark1 visited; + mpz new_value; + + SASSERT(cur_depth < m_traversal_stack.size()); + while (cur_depth != static_cast(-1)) { + ptr_vector & cur_depth_exprs = m_traversal_stack[cur_depth]; + + for (unsigned i = 0; i < cur_depth_exprs.size(); i++) { + expr * cur = cur_depth_exprs[i]; + + eval(cur, new_value); + set_value(cur, new_value); + set_score(cur, score(cur)); + + if (has_uplinks(cur)) { + ptr_vector & ups = get_uplinks(cur); + for (unsigned j = 0; j < ups.size(); j++) { + expr * next = ups[j]; + unsigned next_d = get_distance(next); + SASSERT(next_d < cur_depth); + if (!visited.is_marked(next)) { + m_traversal_stack[next_d].push_back(next); + visited.mark(next); + } + } + } + } + + cur_depth_exprs.reset(); + cur_depth--; + } + + m_mpz_manager.del(new_value); + } + + void update_all() { + unsigned max_depth = 0; + + for (entry_point_type::iterator it = m_entry_points.begin(); + it != m_entry_points.end(); + it++) { + expr * ep = get_entry_point(it->m_key); + unsigned cur_depth = get_distance(ep); + if (m_traversal_stack.size() <= cur_depth) + m_traversal_stack.resize(cur_depth+1); + m_traversal_stack[cur_depth].push_back(ep); + if (cur_depth > max_depth) max_depth = cur_depth; + } + + run_update(max_depth); + } + + void update(func_decl * fd, const mpz & new_value) { + set_value(fd, new_value); + expr * ep = get_entry_point(fd); + unsigned cur_depth = get_distance(ep); + if (m_traversal_stack.size() <= cur_depth) + m_traversal_stack.resize(cur_depth+1); + m_traversal_stack[cur_depth].push_back(ep); + + run_update(cur_depth); + } + + ptr_vector & get_unsat_constants(goal_ref const & g) { + unsigned sz = g->size(); + + if (sz == 1) { + return get_constants(); + } + else { + m_temp_constants.reset(); + for (unsigned i = 0; i < sz; i++) { + expr * q = g->form(i); + if (m_mpz_manager.eq(get_value(q), m_one)) + continue; + ptr_vector const & this_decls = m_constants_occ.find(q); + unsigned sz2 = this_decls.size(); + for (unsigned j = 0; j < sz2; j++) { + func_decl * fd = this_decls[j]; + if (!m_temp_constants.contains(fd)) + m_temp_constants.push_back(fd); + } + } + return m_temp_constants; + } + } + }; + + ast_manager & m_manager; + stats & m_stats; + unsynch_mpz_manager m_mpz_manager; + powers m_powers; + mpz m_zero, m_one, m_two; + bool m_produce_models; + volatile bool m_cancel; + bv_util m_bv_util; + score_tracker m_tracker; + + unsigned m_max_restarts; + unsigned m_plateau_limit; + + typedef enum { MV_FLIP = 0, MV_INC, MV_DEC, MV_INV } move_type; + + imp(ast_manager & m, params_ref const & p, stats & s) : + m_manager(m), + m_stats(s), + m_powers(m_mpz_manager), + m_zero(m_mpz_manager.mk_z(0)), + m_one(m_mpz_manager.mk_z(1)), + m_two(m_mpz_manager.mk_z(2)), + m_bv_util(m), + m_tracker(m, m_bv_util, m_mpz_manager, m_powers) + { + updt_params(p); + } + + ~imp() { + m_mpz_manager.del(m_zero); + m_mpz_manager.del(m_one); + m_mpz_manager.del(m_two); + } + + ast_manager & m() const { return m_manager; } + + void set_cancel(bool f) { m_cancel = f; } + void cancel() { set_cancel(true); } + void reset_cancel() { set_cancel(false); } + + static void collect_param_descrs(param_descrs & r) { + insert_produce_models(r); + r.insert(":sls-restarts", CPK_UINT, "(default: infty) # of SLS restarts."); + r.insert(":random-seed", CPK_UINT, "(default: 0) random seed."); + r.insert(":plateau-limit", CPK_UINT, "(default: 100) SLS plateau limit."); + } + + void updt_params(params_ref const & p) { + m_produce_models = p.get_bool(":produce-models", false); + m_max_restarts = p.get_uint(":sls-restarts", -1); + m_tracker.set_random_seed(p.get_uint(":random-seed", 0)); + m_plateau_limit = p.get_uint(":plateau-limit", 100); + } + + void checkpoint() { + if (m_cancel) + throw tactic_exception(TACTIC_CANCELED_MSG); + cooperate("sls"); + } + + bool full_eval(goal_ref const & g, model & mdl) { + bool res = true; + + unsigned sz = g->size(); + for (unsigned i = 0; i < sz && res; i++) { + checkpoint(); + expr_ref o(m_manager); + + if (!mdl.eval(g->form(i), o, true)) + exit(ERR_INTERNAL_FATAL); + + res = m_manager.is_true(o.get()); + } + + TRACE("sls", tout << "Evaluation: " << res << std::endl;); + + return res; + } + + double top_score(goal_ref const & g) { + #if 0 + double min = m_tracker.get_score(g->form(0)); + unsigned sz = g->size(); + for (unsigned i = 1; i < sz; i++) { + double q = m_tracker.get_score(g->form(i)); + if (q < min) min = q; + } + TRACE("sls_top", tout << "Score distribution:"; + for (unsigned i = 0; i < sz; i++) + tout << " " << m_tracker.get_score(g->form(i)); + tout << " MIN: " << min << std::endl; ); + return min; + #else + double top_sum = 0.0; + unsigned sz = g->size(); + for (unsigned i = 0; i < sz; i++) { + top_sum += m_tracker.get_score(g->form(i)); + } + TRACE("sls_top", tout << "Score distribution:"; + for (unsigned i = 0; i < sz; i++) + tout << " " << m_tracker.get_score(g->form(i)); + tout << " AVG: " << top_sum / (double) sz << std::endl; ); + return top_sum / (double) sz; + #endif + } + + double rescore(goal_ref const & g) { + m_tracker.update_all(); + m_stats.m_full_evals++; + return top_score(g); + } + + double incremental_score(goal_ref const & g, func_decl * fd, const mpz & new_value) { + m_tracker.update(fd, new_value); + m_stats.m_incr_evals++; + return top_score(g); + } + + bool what_if(goal_ref const & g, func_decl * fd, const unsigned & fd_inx, const mpz & temp, + double & best_score, unsigned & best_const, mpz & best_value) { + + #ifdef _DEBUG + mpz old_value; + m_mpz_manager.set(old_value, m_tracker.get_value(fd)); + #endif + + double r = incremental_score(g, fd, temp); + + #ifdef _DEBUG + TRACE("sls_whatif", tout << "WHAT IF " << fd->get_name() << " WERE " << m_mpz_manager.to_string(temp) << + " --> " << r << std::endl; ); + + m_mpz_manager.del(old_value); + #endif + + if (r >= best_score) { + best_score = r; + best_const = fd_inx; + m_mpz_manager.set(best_value, temp); + return true; + } + + return false; + } + + void mk_inc(unsigned bv_sz, const mpz & old_value, mpz & incremented) { + unsigned shift; + m_mpz_manager.add(old_value, m_one, incremented); + if (m_mpz_manager.is_power_of_two(incremented, shift) && shift == bv_sz) + m_mpz_manager.set(incremented, m_zero); + } + + void mk_dec(unsigned bv_sz, const mpz & old_value, mpz & decremented) { + if (m_mpz_manager.is_zero(old_value)) { + m_mpz_manager.set(decremented, m_powers(bv_sz)); + m_mpz_manager.dec(decremented); + } + else + m_mpz_manager.sub(old_value, m_one, decremented); + } + + void mk_inv(unsigned bv_sz, const mpz & old_value, mpz & inverted) { + m_mpz_manager.bitwise_not(bv_sz, old_value, inverted); + } + + void mk_flip(sort * s, const mpz & old_value, unsigned bit, mpz & flipped) { + m_mpz_manager.set(flipped, m_zero); + + if (m_bv_util.is_bv_sort(s)) { + mpz mask; + m_mpz_manager.set(mask, m_powers(bit)); + m_mpz_manager.bitwise_xor(old_value, mask, flipped); + m_mpz_manager.del(mask); + } + else if (m_manager.is_bool(s)) + m_mpz_manager.set(flipped, (m_mpz_manager.is_zero(old_value)) ? m_one : m_zero); + else + NOT_IMPLEMENTED_YET(); + } + + void mk_random_move(goal_ref const & g) { + unsigned rnd_mv = 0; + if (m_mpz_manager.is_one(m_tracker.get_random_bool())) rnd_mv=2; + if (m_mpz_manager.is_one(m_tracker.get_random_bool())) rnd_mv++; + move_type mt = (move_type) rnd_mv; + + // inversion doesn't make sense, let's do a flip instead. + if (mt == MV_INV) mt = MV_FLIP; + + ptr_vector & unsat_constants = m_tracker.get_unsat_constants(g); + unsigned ucc = unsat_constants.size(); + unsigned rc = (m_tracker.get_random_uint((ucc < 16) ? 4 : (ucc < 256) ? 8 : (ucc < 4096) ? 12 : (ucc < 65536) ? 16 : 32)) % ucc; + func_decl * fd = unsat_constants[rc]; + mpz new_value; + unsigned bit = 0; + + switch (mt) + { + case MV_FLIP: { + unsigned bv_sz = m_bv_util.get_bv_size(fd->get_range()); + bit = (m_tracker.get_random_uint((bv_sz < 16) ? 4 : (bv_sz < 256) ? 8 : (bv_sz < 4096) ? 12 : (bv_sz < 65536) ? 16 : 32)) % bv_sz; + mk_flip(fd->get_range(), m_tracker.get_value(fd), bit, new_value); + break; + } + case MV_INC: + mk_inc(m_bv_util.get_bv_size(fd->get_range()), m_tracker.get_value(fd), new_value); + break; + case MV_DEC: + mk_dec(m_bv_util.get_bv_size(fd->get_range()), m_tracker.get_value(fd), new_value); + break; + case MV_INV: + mk_inv(m_bv_util.get_bv_size(fd->get_range()), m_tracker.get_value(fd), new_value); + break; + default: + NOT_IMPLEMENTED_YET(); + } + + m_tracker.update(fd, new_value); + + TRACE("sls", tout << "Randomization candidates: "; + for (unsigned i = 0; i < unsat_constants.size(); i++) + tout << unsat_constants[i]->get_name() << ", "; + tout << std::endl; + tout << "Random move: "; + switch (mt) { + case MV_FLIP: tout << "Flip #" << bit << " in " << fd->get_name() << std::endl; break; + case MV_INC: tout << "+1 for " << fd->get_name() << std::endl; break; + case MV_DEC: tout << "-1 for " << fd->get_name() << std::endl; break; + case MV_INV: tout << "NEG for " << fd->get_name() << std::endl; break; + } + tout << "Locally randomized model: " << std::endl; m_tracker.show_model(tout); ); + + m_mpz_manager.del(new_value); + } + + double find_best_move(goal_ref const & g, ptr_vector & to_evaluate, double score, + unsigned & best_const, mpz & best_value, unsigned & new_bit, move_type & move) { + mpz old_value, temp; + unsigned bv_sz; + double new_score = score; + + for (unsigned i = 0; i < to_evaluate.size() && new_score < 1.0 ; i++) { + func_decl * fd = to_evaluate[i]; + sort * srt = fd->get_range(); + bv_sz = (m_manager.is_bool(srt)) ? 1 : m_bv_util.get_bv_size(srt); + m_mpz_manager.set(old_value, m_tracker.get_value(fd)); + + // first try to flip every bit + for (unsigned j = 0; j < bv_sz && new_score < 1.0; j++) { + // What would happen if we flipped bit #i ? + mk_flip(srt, old_value, j, temp); + + if (what_if(g, fd, i, temp, new_score, best_const, best_value)) { + new_bit = j; + move = MV_FLIP; + } + } + + if (m_bv_util.is_bv_sort(srt) && bv_sz > 1) { + if (!m_mpz_manager.is_even(old_value)) { + // for odd values, try +1 + mk_inc(bv_sz, old_value, temp); + if (what_if(g, fd, i, temp, new_score, best_const, best_value)) + move = MV_INC; + } + else { + // for even values, try -1 + mk_dec(bv_sz, old_value, temp); + if (what_if(g, fd, i, temp, new_score, best_const, best_value)) + move = MV_DEC; + } + + // try inverting + mk_inv(bv_sz, old_value, temp); + if (what_if(g, fd, i, temp, new_score, best_const, best_value)) + move = MV_INV; + } + + // reset to what it was before + double check = incremental_score(g, fd, old_value); + SASSERT(check == score); + } + + m_mpz_manager.del(old_value); + m_mpz_manager.del(temp); + return new_score; + } + + lbool search(goal_ref const & g) { + lbool res = l_undef; + double score = 0.0, old_score = 0.0; + unsigned new_const = -1, new_bit = 0; + mpz new_value; + move_type move; + + score = rescore(g); + TRACE("sls", tout << "Starting search, initial score = " << std::setprecision(32) << score << std::endl; + tout << "Score distribution:"; + for (unsigned i = 0; i < g->size(); i++) + tout << " " << std::setprecision(3) << m_tracker.get_score(g->form(i)); + tout << " TOP: " << score << std::endl; ); + + unsigned plateau_cnt = 0; + + while (plateau_cnt < m_plateau_limit) { + + do { + checkpoint(); + + old_score = score; + new_const = -1; + + ptr_vector & to_evaluate = m_tracker.get_unsat_constants(g); + + TRACE("sls_constants", tout << "Evaluating these constants: " << std::endl; + for (unsigned i = 0 ; i < to_evaluate.size(); i++) + tout << to_evaluate[i]->get_name() << std::endl; ); + + score = find_best_move(g, to_evaluate, score, new_const, new_value, new_bit, move); + + if (new_const == static_cast(-1)) { + TRACE("sls", tout << "Local maximum reached; unsatisfied constraints: " << std::endl; + for (unsigned i = 0; i < g->size(); i++) { + if (!m_mpz_manager.is_one(m_tracker.get_value(g->form(i)))) + tout << mk_ismt2_pp(g->form(i), m_manager) << std::endl; + }); + + TRACE("sls_max", m_tracker.show_model(tout); + tout << "Scores: " << std::endl; + for (unsigned i = 0; i < g->size(); i++) + tout << mk_ismt2_pp(g->form(i), m_manager) << " ---> " << + m_tracker.get_score(g->form(i)) << std::endl; ); + score = old_score; + } + else { + m_stats.m_moves++; + func_decl * fd = to_evaluate[new_const]; + + TRACE("sls", tout << "Setting " << fd->get_name() << " to " << m_mpz_manager.to_string(new_value) << " (Move: "; + switch (move) { + case MV_FLIP: + tout << "Flip"; + if (!m_manager.is_bool(fd->get_range())) tout << " #" << new_bit; + break; + case MV_INC: + tout << "+1"; + break; + case MV_DEC: + tout << "-1"; + break; + case MV_INV: + tout << "NEG"; + break; + }; + tout << ") ; new score = " << std::setprecision(32) << score << std::endl; ); + + switch (move) { + case MV_FLIP: m_stats.m_flips++; break; + case MV_INC: m_stats.m_incs++; break; + case MV_DEC: m_stats.m_decs++; break; + case MV_INV: m_stats.m_invs++; break; + } + + score = incremental_score(g, fd, new_value); + + TRACE("sls", tout << "Score distribution:"; + for (unsigned i = 0; i < g->size(); i++) + tout << " " << std::setprecision(3) << m_tracker.get_score(g->form(i)); + tout << " TOP: " << score << std::endl; ); + } + + if (score >= 1.0) { + // score could theoretically be imprecise. + bool all_true = true; + for (unsigned i = 0; i < g->size() && all_true; i++) + if (!m_mpz_manager.is_one(m_tracker.get_value(g->form(i)))) + all_true=false; + if (all_true) { + res = l_true; // sat + goto bailout; + } else + TRACE("sls", tout << "Imprecise 1.0 score" << std::endl;); + } + } + while (score > old_score && res == l_undef); + + if (score != old_score) + plateau_cnt = 0; + else { + plateau_cnt++; + if (plateau_cnt < m_plateau_limit) { + m_tracker.randomize_local(g); + //mk_random_move(g); + score = top_score(g); + } + } + } + + bailout: + m_mpz_manager.del(new_value); + + return res; + } + + void operator()(goal_ref const & g, model_converter_ref & mc) { + //#ifdef _DEBUG + //ptr_vector es; + //for (unsigned i = 0; i < g->size(); i++) + // es.push_back(g->form(i)); + //expr_ref a(m_manager); + //a = m_manager.mk_and(g->size(), es.c_ptr()); + //std::ofstream dot_out("top.dot", std::ios::out); + //expr2dot(dot_out, a, m_manager); + //dot_out.close(); + //#endif + + if (g->inconsistent()) { + mc = 0; + return; + } + + m_tracker.initialize(g); + lbool res = l_undef; + + do { + checkpoint(); + if ((m_stats.m_restarts % 100) == 0) + report_tactic_progress("Searching... restarts left:", m_max_restarts - m_stats.m_restarts); + + res = search(g); + + if (res == l_undef) + m_tracker.randomize(); + } + while (res != l_true && m_stats.m_restarts++ < m_max_restarts); + + if (res == l_true) { + if (m_produce_models) { + model_ref mdl = m_tracker.get_model(); + mc = model2model_converter(mdl.get()); + TRACE("sls_model", mc->display(tout); ); + } + g->reset(); + } + else + mc = 0; + } + }; + + ast_manager & m; + params_ref m_params; + imp * m_imp; + stats m_stats; + +public: + sls_tactic(ast_manager & _m, params_ref const & p): + m(_m), + m_params(p) { + m_imp = alloc(imp, m, p, m_stats); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(sls_tactic, m, m_params); + } + + virtual ~sls_tactic() { + dealloc(m_imp); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + imp::collect_param_descrs(r); + } + + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + m_imp->m_produce_models = g->models_enabled(); + mc = 0; pc = 0; core = 0; result.reset(); + + TRACE("sls", g->display(tout);); + tactic_report report("sls", *g); + + m_imp->operator()(g, mc); + + g->inc_depth(); + result.push_back(g.get()); + TRACE("sls", g->display(tout);); + SASSERT(g->is_well_sorted()); + } + + virtual void cleanup() { + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + d = m_imp; + } + dealloc(d); + d = alloc(imp, m, m_params, m_stats); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + } + + virtual void collect_statistics(statistics & st) const { + double seconds = m_stats.m_stopwatch.get_current_seconds(); + st.update("sls restarts", m_stats.m_restarts); + st.update("sls full evals", m_stats.m_full_evals); + st.update("sls incr evals", m_stats.m_incr_evals); + st.update("sls incr evals/sec", m_stats.m_incr_evals/ seconds); + st.update("sls FLIP moves", m_stats.m_flips); + st.update("sls INC moves", m_stats.m_incs); + st.update("sls DEC moves", m_stats.m_decs); + st.update("sls INV moves", m_stats.m_invs); + st.update("sls moves", m_stats.m_moves); + st.update("sls moves/sec", m_stats.m_moves / seconds); + } + + virtual void reset_statistics() { + m_stats.reset(); + } + + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + +tactic * mk_sls_tactic(ast_manager & m, params_ref const & p) { + return and_then(fail_if_not(mk_is_qfbv_probe()), // Currently only QF_BV is supported. + clean(alloc(sls_tactic, m, p))); +} + + +tactic * mk_preamble(ast_manager & m, params_ref const & p) { + params_ref main_p; + main_p.set_bool(":elim-and", true); + // main_p.set_bool(":pull-cheap-ite", true); + main_p.set_bool(":push-ite-bv", true); + main_p.set_bool(":blast-distinct", true); + // main_p.set_bool(":udiv2mul", true); + main_p.set_bool(":hi-div0", true); + + params_ref simp2_p = p; + simp2_p.set_bool(":som", true); + simp2_p.set_bool(":pull-cheap-ite", true); + simp2_p.set_bool(":push-ite-bv", false); + simp2_p.set_bool(":local-ctx", true); + simp2_p.set_uint(":local-ctx-limit", 10000000); + + params_ref hoist_p; + hoist_p.set_bool(":hoist-mul", true); + // hoist_p.set_bool(":hoist-cmul", true); + hoist_p.set_bool(":som", false); + + params_ref gaussian_p; + // conservative gaussian elimination. + gaussian_p.set_uint(":gaussian-max-occs", 2); + + return and_then(and_then(mk_simplify_tactic(m), + mk_propagate_values_tactic(m), + using_params(mk_solve_eqs_tactic(m), gaussian_p), + mk_elim_uncnstr_tactic(m), + mk_bv_size_reduction_tactic(m), + using_params(mk_simplify_tactic(m), simp2_p)), + using_params(mk_simplify_tactic(m), hoist_p), + mk_max_bv_sharing_tactic(m), + mk_nnf_tactic(m, p)); +} + +tactic * mk_qfbv_sls_tactic(ast_manager & m, params_ref const & p) { + params_ref sls_p(p); + sls_p.set_uint(":sls-restarts", 10000); + sls_p.set_uint(":plateau-limit", 100); + + tactic * t = and_then(mk_preamble(m, p), + using_params(mk_sls_tactic(m, p), sls_p)); + + t->updt_params(p); + return t; +} diff --git a/lib/sls_tactic.h b/lib/sls_tactic.h new file mode 100644 index 000000000..be350ee15 --- /dev/null +++ b/lib/sls_tactic.h @@ -0,0 +1,30 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + sls_tactic.h + +Abstract: + + A Stochastic Local Search (SLS) tactic + +Author: + + Christoph (cwinter) 2012-02-29 + +Notes: + +--*/ +#ifndef _SLS_TACTIC_H_ +#define _SLS_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_sls_tactic(ast_manager & m, params_ref const & p = params_ref()); + +tactic * mk_qfbv_sls_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/small_object_allocator.cpp b/lib/small_object_allocator.cpp new file mode 100644 index 000000000..8acdccd23 --- /dev/null +++ b/lib/small_object_allocator.cpp @@ -0,0 +1,235 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + small_object_allocator.cpp + +Abstract: + + Small object allocator. + +Author: + + Nikolaj bjorner (nbjorner) 2007-08-06. + +Revision History: + Leonardo de Moura (leonardo) 2011-04-27 + Rewrote/Simplified the allocator + +--*/ +#include"memory_manager.h" +#include"small_object_allocator.h" +#include"debug.h" +#include"util.h" +#include"vector.h" +#include + +small_object_allocator::small_object_allocator(char const * id) { + for (unsigned i = 0; i < NUM_SLOTS; i++) { + m_chunks[i] = 0; + m_free_list[i] = 0; + } + DEBUG_CODE({ + m_id = id; + }); + m_alloc_size = 0; +} + +small_object_allocator::~small_object_allocator() { + for (unsigned i = 0; i < NUM_SLOTS; i++) { + chunk * c = m_chunks[i]; + while (c) { + chunk * next = c->m_next; + dealloc(c); + c = next; + } + } + DEBUG_CODE({ + if (m_alloc_size > 0) { + std::cerr << "Memory leak detected for small object allocator '" << m_id << "'. " << m_alloc_size << " bytes leaked" << std::endl; + } + }); +} + +void small_object_allocator::reset() { + for (unsigned i = 0; i < NUM_SLOTS; i++) { + chunk * c = m_chunks[i]; + while (c) { + chunk * next = c->m_next; + dealloc(c); + c = next; + } + m_chunks[i] = 0; + m_free_list[i] = 0; + } + m_alloc_size = 0; +} + +#define MASK ((1 << PTR_ALIGNMENT) - 1) + +void small_object_allocator::deallocate(size_t size, void * p) { +#if defined(Z3DEBUG) && !defined(_WINDOWS) + // Valgrind friendly + memory::deallocate(p); + return; +#endif + SASSERT(m_alloc_size >= size); + SASSERT(p); + m_alloc_size -= size; + if (size > SMALL_OBJ_SIZE - (1 << PTR_ALIGNMENT)) { + memory::deallocate(p); + return; + } + unsigned slot_id = static_cast(size >> PTR_ALIGNMENT); + if ((size & MASK) != 0) + slot_id++; + SASSERT(slot_id > 0); + SASSERT(slot_id < NUM_SLOTS); + *(reinterpret_cast(p)) = m_free_list[slot_id]; + m_free_list[slot_id] = p; +} + +void * small_object_allocator::allocate(size_t size) { +#if defined(Z3DEBUG) && !defined(_WINDOWS) + // Valgrind friendly + return memory::allocate(size); +#endif + m_alloc_size += size; + if (size > SMALL_OBJ_SIZE - (1 << PTR_ALIGNMENT)) + return memory::allocate(size); +#ifdef Z3DEBUG + size_t osize = size; +#endif + unsigned slot_id = static_cast(size >> PTR_ALIGNMENT); + if ((size & MASK) != 0) + slot_id++; + SASSERT(slot_id < NUM_SLOTS); + SASSERT(slot_id > 0); + if (m_free_list[slot_id] != 0) { + void * r = m_free_list[slot_id]; + m_free_list[slot_id] = *(reinterpret_cast(r)); + return r; + } + chunk * c = m_chunks[slot_id]; + size = slot_id << PTR_ALIGNMENT; + SASSERT(size >= osize); + if (c != 0) { + char * new_curr = c->m_curr + size; + if (new_curr < c->m_data + CHUNK_SIZE) { + void * r = c->m_curr; + c->m_curr = new_curr; + return r; + } + } + chunk * new_c = alloc(chunk); + new_c->m_next = c; + m_chunks[slot_id] = new_c; + void * r = new_c->m_curr; + new_c->m_curr += size; + return r; +} + +size_t small_object_allocator::get_wasted_size() const { + size_t r = 0; + for (unsigned slot_id = 0; slot_id < NUM_SLOTS; slot_id++) { + size_t slot_obj_size = slot_id << PTR_ALIGNMENT; + void ** ptr = reinterpret_cast(const_cast(this)->m_free_list[slot_id]); + while (ptr != 0) { + r += slot_obj_size; + ptr = reinterpret_cast(*ptr); + } + } + return r; +} + +size_t small_object_allocator::get_num_free_objs() const { + size_t r = 0; + for (unsigned slot_id = 0; slot_id < NUM_SLOTS; slot_id++) { + void ** ptr = reinterpret_cast(const_cast(this)->m_free_list[slot_id]); + while (ptr != 0) { + r ++; + ptr = reinterpret_cast(*ptr); + } + } + return r; +} + +template +struct ptr_lt { + bool operator()(T * p1, T * p2) const { return p1 < p2; } +}; + +#define CONSOLIDATE_VB_LVL 20 + +void small_object_allocator::consolidate() { + IF_VERBOSE(CONSOLIDATE_VB_LVL, + verbose_stream() << "(allocator-consolidate :wasted-size " << get_wasted_size() + << " :memory " << std::fixed << std::setprecision(2) << + static_cast(memory::get_allocation_size())/static_cast(1024*1024) << ")" << std::endl;); + ptr_vector chunks; + ptr_vector free_objs; + for (unsigned slot_id = 1; slot_id < NUM_SLOTS; slot_id++) { + if (m_free_list[slot_id] == 0) + continue; + chunks.reset(); + free_objs.reset(); + chunk * c = m_chunks[slot_id]; + while (c != 0) { + chunks.push_back(c); + c = c->m_next; + } + char * ptr = static_cast(m_free_list[slot_id]); + while (ptr != 0) { + free_objs.push_back(ptr); + ptr = *(reinterpret_cast(ptr)); + } + unsigned obj_size = slot_id << PTR_ALIGNMENT; + unsigned num_objs_per_chunk = CHUNK_SIZE / obj_size; + if (free_objs.size() < num_objs_per_chunk) + continue; + SASSERT(!chunks.empty()); + std::sort(chunks.begin(), chunks.end(), ptr_lt()); + std::sort(free_objs.begin(), free_objs.end(), ptr_lt()); + chunk * last_chunk = 0; + void * last_free_obj = 0; + unsigned chunk_idx = 0; + unsigned obj_idx = 0; + unsigned num_chunks = chunks.size(); + unsigned num_objs = free_objs.size(); + while (chunk_idx < num_chunks) { + chunk * curr_chunk = chunks[chunk_idx]; + char * curr_begin = curr_chunk->m_data; + char * curr_end = curr_begin + CHUNK_SIZE; + unsigned num_free_in_chunk = 0; + unsigned saved_obj_idx = obj_idx; + while (obj_idx < num_objs) { + char * free_obj = free_objs[obj_idx]; + if (free_obj > curr_end) + break; + obj_idx++; + num_free_in_chunk++; + } + if (num_free_in_chunk == num_objs_per_chunk) { + dealloc(curr_chunk); + } + else { + curr_chunk->m_next = last_chunk; + last_chunk = curr_chunk; + for (unsigned i = saved_obj_idx; i < obj_idx; i++) { + // relink objects + void * free_obj = free_objs[i]; + *(reinterpret_cast(free_obj)) = last_free_obj; + last_free_obj = free_obj; + } + } + chunk_idx++; + } + m_chunks[slot_id] = last_chunk; + m_free_list[slot_id] = last_free_obj; + } + IF_VERBOSE(CONSOLIDATE_VB_LVL, + verbose_stream() << "(end-allocator-consolidate :wasted-size " << get_wasted_size() + << " :memory " << std::fixed << std::setprecision(2) + << static_cast(memory::get_allocation_size())/static_cast(1024*1024) << ")" << std::endl;); +} diff --git a/lib/small_object_allocator.h b/lib/small_object_allocator.h new file mode 100644 index 000000000..538a9e035 --- /dev/null +++ b/lib/small_object_allocator.h @@ -0,0 +1,59 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + small_object_allocator.h + +Abstract: + + Small object allocator. + +Author: + + Nikolaj bjorner (nbjorner) 2007-08-06. + +Revision History: + Leonardo de Moura (leonardo) 2011-04-27 + Rewrote/Simplified the allocator +--*/ +#ifndef _SMALL_OBJECT_ALLOCATOR_H_ +#define _SMALL_OBJECT_ALLOCATOR_H_ + +#include"machine.h" + +class small_object_allocator { + static const unsigned CHUNK_SIZE = (8192 - sizeof(void*)*2); + static const unsigned SMALL_OBJ_SIZE = 256; + static const unsigned NUM_SLOTS = (SMALL_OBJ_SIZE >> PTR_ALIGNMENT); + struct chunk { + chunk * m_next; + char * m_curr; + char m_data[CHUNK_SIZE]; + chunk():m_curr(m_data) {} + }; + chunk * m_chunks[NUM_SLOTS]; + void * m_free_list[NUM_SLOTS]; + size_t m_alloc_size; +#ifdef Z3DEBUG + char const * m_id; +#endif +public: + small_object_allocator(char const * id = "unknown"); + ~small_object_allocator(); + void reset(); + void * allocate(size_t size); + void deallocate(size_t size, void * p); + size_t get_allocation_size() const { return m_alloc_size; } + size_t get_wasted_size() const; + size_t get_num_free_objs() const; + void consolidate(); +}; + +inline void * operator new(size_t s, small_object_allocator & r) { return r.allocate(s); } +inline void * operator new[](size_t s, small_object_allocator & r) { return r.allocate(s); } +inline void operator delete(void * p, size_t s, small_object_allocator & r) { r.deallocate(s,p); } +inline void operator delete[](void * p, size_t s, small_object_allocator & r) { r.deallocate(s,p); } + +#endif /* _SMALL_OBJECT_ALLOCATOR_H_ */ + diff --git a/lib/smt2parser.cpp b/lib/smt2parser.cpp new file mode 100644 index 000000000..70f984de7 --- /dev/null +++ b/lib/smt2parser.cpp @@ -0,0 +1,2491 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + smt2parser.h + +Abstract: + + SMT 2.0 parser + +Author: + + Leonardo de Moura (leonardo) 2011-03-01 + +Revision History: + +--*/ +#include"smt2parser.h" +#include"smt2scanner.h" +#include"stack.h" +#include"datatype_decl_plugin.h" +#include"bv_decl_plugin.h" +#include"arith_decl_plugin.h" +#include"ast_pp.h" +#include"well_sorted.h" +#include"pattern_validation.h" +#include"rewriter.h" +#include"has_free_vars.h" +#include"ast_smt2_pp.h" + +namespace smt2 { + typedef cmd_exception parser_exception; + + class parser { + cmd_context & m_ctx; + scanner m_scanner; + scanner::token m_curr; + cmd * m_curr_cmd; + stack m_stack; + struct local { + expr * m_term; + unsigned m_level; + local():m_term(0), m_level(0) {} + local(expr * t, unsigned l):m_term(t), m_level(l) {} + }; + symbol_table m_env; + unsigned m_num_bindings; + + dictionary m_sort_id2param_idx; + dictionary m_dt_name2idx; + + scoped_ptr m_psort_stack; + scoped_ptr m_sort_stack; + scoped_ptr m_expr_stack; + unsigned m_num_expr_frames; + scoped_ptr m_pattern_stack; + scoped_ptr m_nopattern_stack; + svector m_symbol_stack; + vector m_param_stack; + scoped_ptr m_sexpr_stack; + + scoped_ptr m_bv_util; + scoped_ptr m_arith_util; + scoped_ptr m_pattern_validator; + scoped_ptr m_var_shifter; + + symbol m_let; + symbol m_bang; + symbol m_forall; + symbol m_exists; + symbol m_as; + symbol m_not; + symbol m_root_obj; + + symbol m_named; + symbol m_weight; + symbol m_qid; + symbol m_skid; + symbol m_ex_act; + symbol m_pattern; + symbol m_nopattern; + symbol m_lblneg; + symbol m_lblpos; + + symbol m_assert; + symbol m_check_sat; + symbol m_define_fun; + symbol m_define_const; + symbol m_declare_fun; + symbol m_declare_const; + symbol m_define_sort; + symbol m_declare_sort; + symbol m_declare_datatypes; + symbol m_push; + symbol m_pop; + symbol m_get_value; + symbol m_reset; + symbol m_underscore; + + typedef std::pair named_expr; + named_expr m_last_named_expr; + + + ast_manager & m() const { return m_ctx.m(); } + pdecl_manager & pm() const { return m_ctx.pm(); } + sexpr_manager & sm() const { return m_ctx.sm(); } + + bool ignore_user_patterns() const { return m_ctx.params().m_ignore_user_patterns; } + bool ignore_bad_patterns() const { return m_ctx.params().m_ignore_bad_patterns; } + + struct psort_frame { + psort_decl * m_decl; + unsigned m_spos; // position of m_psort_stack + psort_frame(parser & p, psort_decl * d, unsigned spos): + m_decl(d), m_spos(spos) { + } + }; + + typedef psort_frame sort_frame; + + enum expr_frame_kind { EF_APP, EF_LET, EF_LET_DECL, EF_QUANT, EF_ATTR_EXPR, EF_PATTERN }; + + struct expr_frame { + expr_frame_kind m_kind; + expr_frame(expr_frame_kind k):m_kind(k) {} + }; + + struct app_frame : public expr_frame { + symbol m_f; + unsigned m_expr_spos; + unsigned m_param_spos; + bool m_as_sort; + app_frame(symbol const & f, unsigned expr_spos, unsigned param_spos, bool as_sort): + expr_frame(EF_APP), + m_f(f), + m_expr_spos(expr_spos), + m_param_spos(param_spos), + m_as_sort(as_sort) {} + }; + + struct quant_frame : public expr_frame { + bool m_forall; + symbol m_qid; + symbol m_skid; + unsigned m_weight; + unsigned m_pat_spos; + unsigned m_nopat_spos; + unsigned m_sym_spos; + unsigned m_sort_spos; + unsigned m_expr_spos; + quant_frame(bool forall, unsigned pat_spos, unsigned nopat_spos, unsigned sym_spos, unsigned sort_spos, unsigned expr_spos): + expr_frame(EF_QUANT), m_forall(forall), m_weight(1), + m_pat_spos(pat_spos), m_nopat_spos(nopat_spos), + m_sym_spos(sym_spos), m_sort_spos(sort_spos), + m_expr_spos(expr_spos) {} + }; + + struct let_frame : public expr_frame { + bool m_in_decls; + unsigned m_sym_spos; + unsigned m_expr_spos; + let_frame(unsigned sym_spos, unsigned expr_spos):expr_frame(EF_LET), m_in_decls(true), m_sym_spos(sym_spos), m_expr_spos(expr_spos) {} + }; + + struct let_decl_frame : public expr_frame { + let_decl_frame():expr_frame(EF_LET_DECL) {} + }; + + struct attr_expr_frame : public expr_frame { + expr_frame * m_prev; + unsigned m_sym_spos; + unsigned m_expr_spos; + symbol m_last_symbol; + attr_expr_frame(expr_frame * prev, unsigned sym_spos, unsigned expr_spos): + expr_frame(EF_ATTR_EXPR), + m_prev(prev), + m_sym_spos(sym_spos), + m_expr_spos(expr_spos) {} + }; + + struct pattern_frame : public expr_frame { + unsigned m_expr_spos; + pattern_frame(unsigned expr_spos): + expr_frame(EF_PATTERN), + m_expr_spos(expr_spos) { + } + }; + + struct sexpr_frame { + unsigned m_spos; // position of m_sexpr_stack + sexpr_frame(unsigned spos): + m_spos(spos) { + } + }; + + void reset_stack() { + m_stack.reset(); + } + + psort_ref_vector & psort_stack() { + if (m_psort_stack.get() == 0) + m_psort_stack = alloc(psort_ref_vector, pm()); + return *(m_psort_stack.get()); + } + + sort_ref_vector & sort_stack() { + if (m_sort_stack.get() == 0) + m_sort_stack = alloc(sort_ref_vector, m()); + return *(m_sort_stack.get()); + } + + expr_ref_vector & expr_stack() { + if (m_expr_stack.get() == 0) + m_expr_stack = alloc(expr_ref_vector, m()); + return *(m_expr_stack.get()); + } + + template + static unsigned size(scoped_ptr & v) { + return v.get() == 0 ? 0 : v->size(); + } + + template + static void shrink(scoped_ptr & v, unsigned old_sz) { + if (v.get() == 0) { + SASSERT(old_sz == 0); + } + else { + v->shrink(old_sz); + } + } + + expr_ref_vector & pattern_stack() { + if (m_pattern_stack.get() == 0) + m_pattern_stack = alloc(expr_ref_vector, m()); + return *(m_pattern_stack.get()); + } + + expr_ref_vector & nopattern_stack() { + if (m_nopattern_stack.get() == 0) + m_nopattern_stack = alloc(expr_ref_vector, m()); + return *(m_nopattern_stack.get()); + } + + svector & symbol_stack() { + return m_symbol_stack; + } + + sexpr_ref_vector & sexpr_stack() { + if (m_sexpr_stack.get() == 0) + m_sexpr_stack = alloc(sexpr_ref_vector, sm()); + return *(m_sexpr_stack.get()); + } + + arith_util & autil() { + if (m_arith_util.get() == 0) + m_arith_util = alloc(arith_util, m()); + return *(m_arith_util.get()); + } + + bv_util & butil() { + if (m_bv_util.get() == 0) + m_bv_util = alloc(bv_util, m()); + return *(m_bv_util.get()); + } + + pattern_validator & pat_validator() { + if (m_pattern_validator.get() == 0) { + m_pattern_validator = alloc(pattern_validator, m()); + } + return *(m_pattern_validator.get()); + } + + var_shifter & shifter() { + if (m_var_shifter.get() == 0) + m_var_shifter = alloc(var_shifter, m()); + return *(m_var_shifter.get()); + } + + unsigned m_cache_end; + vector m_cached_strings; + + int m_num_open_paren; + + void scan_core() { + m_cache_end = m_scanner.cache_size(); + m_curr = m_scanner.scan(); + } + + void scan() { + switch (m_curr) { + case scanner::LEFT_PAREN: m_num_open_paren++; break; + case scanner::RIGHT_PAREN: m_num_open_paren--; break; + default: break; + } + scan_core(); + } + + void next() { + if (m_curr != scanner::EOF_TOKEN) + scan(); + } + + scanner::token curr() const { return m_curr; } + + // consume garbage + // return true if managed to recover from the error... + bool sync_after_error() { + while (true) { + try { + while (curr_is_rparen()) + next(); + if (m_num_open_paren < 0) + m_num_open_paren = 0; + if (curr() == scanner::EOF_TOKEN && m_num_open_paren == 0) + return true; + SASSERT(m_num_open_paren >= 0); + while (m_num_open_paren > 0 || !curr_is_lparen()) { + TRACE("sync", tout << "sync(): curr: " << curr() << "\n"; + tout << "m_num_open_paren: " << m_num_open_paren << ", line: " << m_scanner.get_line() << ", pos: " + << m_scanner.get_pos() << "\n";); + if (curr() == scanner::EOF_TOKEN) { + return false; + } + SASSERT(m_num_open_paren >= 0); + next(); + SASSERT(m_num_open_paren >= -1); + if (m_num_open_paren < 0) + m_num_open_paren = 0; + SASSERT(m_num_open_paren >= 0); + } + return true; + } + catch (scanner_exception & ex) { + SASSERT(ex.has_pos()); + error(ex.line(), ex.pos(), ex.msg()); + } + } + } + + void check_next(scanner::token t, char const * msg) { + if (curr() == t) { + next(); + return; + } + throw parser_exception(msg); + } + + symbol const & curr_id() const { return m_scanner.get_id(); } + rational curr_numeral() const { return m_scanner.get_number(); } + + bool curr_is_identifier() const { return curr() == scanner::SYMBOL_TOKEN; } + bool curr_is_keyword() const { return curr() == scanner::KEYWORD_TOKEN; } + bool curr_is_string() const { return curr() == scanner::STRING_TOKEN; } + bool curr_is_lparen() const { return curr() == scanner::LEFT_PAREN; } + bool curr_is_rparen() const { return curr() == scanner::RIGHT_PAREN; } + bool curr_is_int() const { return curr() == scanner::INT_TOKEN; } + bool curr_is_float() const { return curr() == scanner::FLOAT_TOKEN; } + + bool curr_id_is_underscore() const { SASSERT(curr_is_identifier()); return curr_id() == m_underscore; } + bool curr_id_is_as() const { SASSERT(curr_is_identifier()); return curr_id() == m_as; } + bool curr_id_is_forall() const { SASSERT(curr_is_identifier()); return curr_id() == m_forall; } + bool curr_id_is_exists() const { SASSERT(curr_is_identifier()); return curr_id() == m_exists; } + bool curr_id_is_bang() const { SASSERT(curr_is_identifier()); return curr_id() == m_bang; } + bool curr_id_is_let() const { SASSERT(curr_is_identifier()); return curr_id() == m_let; } + bool curr_id_is_root_obj() const { SASSERT(curr_is_identifier()); return curr_id() == m_root_obj; } + void check_lparen(char const * msg) { if (!curr_is_lparen()) throw parser_exception(msg); } + void check_lparen_next(char const * msg) { check_next(scanner::LEFT_PAREN, msg); } + void check_rparen_next(char const * msg) { check_next(scanner::RIGHT_PAREN, msg); } + void check_rparen(char const * msg) { if (!curr_is_rparen()) throw parser_exception(msg); } + void check_id_next(symbol const & id, char const * msg) { + if (!curr_is_identifier() || curr_id() != id) + throw parser_exception(msg); + next(); + } + void check_underscore_next(char const * msg) { check_id_next(m_underscore, msg); } + void check_as_next(char const * msg) { check_id_next(m_as, msg); } + void check_identifier(char const * msg) { if (!curr_is_identifier()) throw parser_exception(msg); } + void check_keyword(char const * msg) { if (!curr_is_keyword()) throw parser_exception(msg); } + void check_string(char const * msg) { if (!curr_is_string()) throw parser_exception(msg); } + void check_int(char const * msg) { if (!curr_is_int()) throw parser_exception(msg); } + void check_float(char const * msg) { if (!curr_is_float()) throw parser_exception(msg); } + + bool use_vs_format() const { return m_ctx.params().m_display_error_for_vs; } + + void error(unsigned line, unsigned pos, char const * msg) { + if (use_vs_format()) { + m_ctx.diagnostic_stream() << "Z3(" << line << ", " << pos << "): ERROR: " << msg; + if (msg[strlen(msg)-1] != '\n') + m_ctx.diagnostic_stream() << std::endl; + } + else { + m_ctx.regular_stream() << "(error \"line " << line << " column " << pos << ": " << escaped(msg, true) << "\")" << std::endl; + } + if (m_ctx.exit_on_error()) { + exit(1); + } + } + + void error(char const * msg) { + error(m_scanner.get_line(), m_scanner.get_pos(), msg); + } + + void error_wo_pos(char const * msg) { + if (use_vs_format()) { + m_ctx.diagnostic_stream() << "Z3: ERROR: " << msg; + if (msg[strlen(msg)-1] != '\n') + m_ctx.diagnostic_stream() << std::endl; + } + else { + m_ctx.regular_stream() << "(error : " << escaped(msg, true) << "\")" << std::endl; + } + } + + void unknown_sort(symbol id) { + std::string msg = "unknown sort '"; + msg += id.str() + "'"; + throw parser_exception(msg.c_str()); + } + + void consume_sexpr() { + unsigned num_parens = 0; + do { + switch (curr()) { + case scanner::LEFT_PAREN: + num_parens++; + break; + case scanner::RIGHT_PAREN: + if (num_parens == 0) + throw parser_exception("invalid s-expression, unexpected ')'"); + num_parens--; + break; + case scanner::SYMBOL_TOKEN: + case scanner::KEYWORD_TOKEN: + case scanner::STRING_TOKEN: + case scanner::INT_TOKEN: + case scanner::FLOAT_TOKEN: + case scanner::BV_TOKEN: + break; + case scanner::EOF_TOKEN: + throw parser_exception("invalid s-expression, unexpected end of file"); + break; + default: + throw parser_exception("invalid s-expression, unexpected input"); + break; + } + next(); + } + while (num_parens > 0); + } + + void parse_sexpr() { + unsigned stack_pos = sexpr_stack().size(); + unsigned num_frames = 0; + do { + unsigned line = m_scanner.get_line(); + unsigned pos = m_scanner.get_pos(); + switch (curr()) { + case scanner::LEFT_PAREN: { + void * mem = m_stack.allocate(sizeof(sexpr_frame)); + new (mem) sexpr_frame(sexpr_stack().size()); + num_frames++; + break; + } + case scanner::RIGHT_PAREN: { + if (num_frames == 0) + throw parser_exception("invalid s-expression, unexpected ')'"); + num_frames--; + sexpr_frame * fr = static_cast(m_stack.top()); + unsigned spos = fr->m_spos; + unsigned epos = sexpr_stack().size(); + SASSERT(epos >= spos); + unsigned num = epos - spos; + if (num == 0) + throw parser_exception("invalid empty s-expression"); + sexpr * r = sm().mk_composite(num, sexpr_stack().c_ptr() + spos, line, pos); + sexpr_stack().shrink(spos); + sexpr_stack().push_back(r); + m_stack.deallocate(fr); + break; + } + case scanner::SYMBOL_TOKEN: + sexpr_stack().push_back(sm().mk_symbol(curr_id(), line, pos)); + break; + case scanner::KEYWORD_TOKEN: + sexpr_stack().push_back(sm().mk_keyword(curr_id(), line, pos)); + break; + case scanner::STRING_TOKEN: + sexpr_stack().push_back(sm().mk_string(m_scanner.get_string(), line, pos)); + break; + case scanner::INT_TOKEN: + case scanner::FLOAT_TOKEN: + sexpr_stack().push_back(sm().mk_numeral(curr_numeral(), line, pos)); + break; + case scanner::BV_TOKEN: + sexpr_stack().push_back(sm().mk_bv_numeral(curr_numeral(), m_scanner.get_bv_size(), line, pos)); + break; + case scanner::EOF_TOKEN: + throw parser_exception("invalid s-expression, unexpected end of file"); + break; + default: + throw parser_exception("invalid s-expression, unexpected input"); + break; + } + next(); + } + while (num_frames > 0); + SASSERT(sexpr_stack().size() == stack_pos + 1); + } + + sort * parse_sort_name() { + SASSERT(curr_is_identifier()); + symbol id = curr_id(); + psort_decl * d = m_ctx.find_psort_decl(id); + if (d == 0) + unknown_sort(id); + if (!d->has_var_params() && d->get_num_params() != 0) + throw parser_exception("sort constructor expects parameters"); + sort * r = d->instantiate(pm()); + if (r == 0) + throw parser_exception("invalid sort application"); + next(); + return r; + } + + psort * parse_psort_name(bool ignore_unknow_sort = false) { + SASSERT(curr_is_identifier()); + symbol id = curr_id(); + psort_decl * d = m_ctx.find_psort_decl(id); + if (d != 0) { + if (!d->has_var_params() && d->get_num_params() != 0) + throw parser_exception("sort constructor expects parameters"); + next(); + return pm().mk_psort_app(d); + } + else { + int idx = 0; + if (m_sort_id2param_idx.find(id, idx)) { + next(); + return pm().mk_psort_var(m_sort_id2param_idx.size(), idx); + } + else { + if (ignore_unknow_sort) + return 0; + unknown_sort(id); + UNREACHABLE(); + return 0; + } + } + } + + sort * parse_indexed_sort() { + SASSERT(curr_is_identifier()); + SASSERT(curr_id_is_underscore()); + next(); + check_identifier("invalid indexed sort, symbol expected"); + symbol id = curr_id(); + psort_decl * d = m_ctx.find_psort_decl(id); + if (d == 0) + unknown_sort(id); + next(); + sbuffer args; + while (!curr_is_rparen()) { + check_int("invalid indexed sort, integer or ')' expected"); + rational n = curr_numeral(); + if (!n.is_unsigned()) + throw parser_exception("invalid indexed sort, index is too big to fit in an unsigned machine integer"); + args.push_back(n.get_unsigned()); + next(); + } + if (args.empty()) + throw parser_exception("invalid indexed sort, index expected"); + sort * r = d->instantiate(pm(), args.size(), args.c_ptr()); + if (r == 0) + throw parser_exception("invalid sort application"); + next(); + return r; + } + + void push_psort_app_frame() { + SASSERT(curr_is_identifier()); + symbol id = curr_id(); + psort_decl * d = m_ctx.find_psort_decl(id); + if (d == 0) + unknown_sort(id); + next(); + void * mem = m_stack.allocate(sizeof(psort_frame)); + new (mem) psort_frame(*this, d, psort_stack().size()); + } + + void pop_psort_app_frame() { + SASSERT(curr_is_rparen()); + psort_frame * fr = static_cast(m_stack.top()); + psort_decl * d = fr->m_decl; + unsigned spos = fr->m_spos; + unsigned epos = psort_stack().size(); + SASSERT(epos >= spos); + unsigned num = epos - spos; + if (!d->has_var_params() && d->get_num_params() != num) { + TRACE("smt2parser", tout << "num: " << num << ", d->get_num_params(): " << d->get_num_params() << "\n";); + throw parser_exception("invalid number of parameters to sort constructor"); + } + psort * r = pm().mk_psort_app(m_sort_id2param_idx.size(), d, num, psort_stack().c_ptr() + spos); + psort_stack().shrink(spos); + psort_stack().push_back(r); + m_stack.deallocate(fr); + next(); + } + + void parse_psort() { + unsigned stack_pos = psort_stack().size(); + unsigned num_frames = 0; + do { + if (curr_is_identifier()) { + psort_stack().push_back(parse_psort_name()); + } + else if (curr_is_rparen()) { + if (num_frames == 0) + throw parser_exception("invalid sort, unexpected ')'"); + pop_psort_app_frame(); + num_frames--; + } + else { + check_lparen_next("invalid sort, symbol, '_' or '(' expected"); + if (!curr_is_identifier()) + throw parser_exception("invalid sort, symbol or '_' expected"); + if (curr_id_is_underscore()) { + psort_stack().push_back(pm().mk_psort_cnst(parse_indexed_sort())); + } + else { + push_psort_app_frame(); + num_frames++; + } + } + } + while (num_frames > 0); + SASSERT(psort_stack().size() == stack_pos + 1); + } + + void push_sort_app_frame() { + SASSERT(curr_is_identifier()); + symbol id = curr_id(); + psort_decl * d = m_ctx.find_psort_decl(id); + if (d == 0) + unknown_sort(id); + next(); + void * mem = m_stack.allocate(sizeof(sort_frame)); + new (mem) sort_frame(*this, d, sort_stack().size()); + } + + void pop_sort_app_frame() { + SASSERT(curr_is_rparen()); + sort_frame * fr = static_cast(m_stack.top()); + psort_decl * d = fr->m_decl; + unsigned spos = fr->m_spos; + unsigned epos = sort_stack().size(); + SASSERT(epos >= spos); + unsigned num = epos - spos; + if (!d->has_var_params() && d->get_num_params() != num) { + TRACE("smt2parser", tout << "num: " << num << ", d->get_num_params(): " << d->get_num_params() << "\n";); + throw parser_exception("invalid number of parameters to sort constructor"); + } + sort * r = d->instantiate(pm(), num, sort_stack().c_ptr() + spos); + if (r == 0) + throw parser_exception("invalid sort application"); + sort_stack().shrink(spos); + sort_stack().push_back(r); + m_stack.deallocate(fr); + next(); + } + + void parse_sort() { + unsigned stack_pos = sort_stack().size(); + unsigned num_frames = 0; + do { + if (curr_is_identifier()) { + sort_stack().push_back(parse_sort_name()); + } + else if (curr_is_rparen()) { + if (num_frames == 0) + throw parser_exception("invalid sort, unexpected ')'"); + pop_sort_app_frame(); + num_frames--; + } + else { + check_lparen_next("invalid sort, symbol, '_' or '(' expected"); + if (!curr_is_identifier()) + throw parser_exception("invalid sort, symbol or '_' expected"); + if (curr_id_is_underscore()) { + sort_stack().push_back(parse_indexed_sort()); + } + else { + push_sort_app_frame(); + num_frames++; + } + } + } + while (num_frames > 0); + SASSERT(sort_stack().size() == stack_pos + 1); + } + + unsigned parse_sorts() { + unsigned sz = 0; + check_lparen_next("invalid list of sorts, '(' expected"); + while (!curr_is_rparen()) { + parse_sort(); + sz++; + } + next(); + return sz; + } + + unsigned parse_symbols() { + unsigned sz = 0; + check_lparen_next("invalid list of symbols, '(' expected"); + while (!curr_is_rparen()) { + check_identifier("invalid list of symbols, symbol or ')' expected"); + m_symbol_stack.push_back(curr_id()); + next(); + sz++; + } + next(); + return sz; + } + + // [ '(' identifier sort ')' ]+ + void parse_accessor_decls(paccessor_decl_ref_buffer & a_decls) { + while (!curr_is_rparen()) { + check_lparen_next("invalid datatype declaration, '(' or ')' expected"); + check_identifier("invalid accessor declaration, symbol (accessor name) expected"); + symbol a_name = curr_id(); + next(); + if (curr_is_identifier()) { + psort * p = parse_psort_name(true); + if (p != 0) { + a_decls.push_back(pm().mk_paccessor_decl(m_sort_id2param_idx.size(), a_name, ptype(p))); + } + else { + // parse_psort_name failed, identifier was not consumed. + int idx; + if (m_dt_name2idx.find(curr_id(), idx)) { + a_decls.push_back(pm().mk_paccessor_decl(m_sort_id2param_idx.size(), a_name, ptype(idx))); + } + else { + a_decls.push_back(pm().mk_paccessor_decl(m_sort_id2param_idx.size(), a_name, ptype(curr_id()))); + } + SASSERT(curr_is_identifier()); + next(); + } + } + else { + parse_psort(); + a_decls.push_back(pm().mk_paccessor_decl(m_sort_id2param_idx.size(), a_name, ptype(psort_stack().back()))); + psort_stack().pop_back(); + } + check_rparen_next("invalid accessor declaration, ')' expected"); + } + } + + // [ '(' identifier accessors ')' ]+ + void parse_constructor_decls(pconstructor_decl_ref_buffer & ct_decls) { + while (!curr_is_rparen()) { + if (curr_is_identifier()) { + symbol ct_name = curr_id(); + std::string r_str = "is-"; + r_str += curr_id().str(); + symbol r_name(r_str.c_str()); + next(); + TRACE("datatype_parser_bug", tout << ct_name << " " << r_name << "\n";); + ct_decls.push_back(pm().mk_pconstructor_decl(m_sort_id2param_idx.size(), ct_name, r_name, 0, 0)); + } + else { + check_lparen_next("invalid datatype declaration, '(' or ')' expected"); + check_identifier("invalid constructor declaration, symbol (constructor name) expected"); + symbol ct_name = curr_id(); + std::string r_str = "is-"; + r_str += curr_id().str(); + symbol r_name(r_str.c_str()); + next(); + paccessor_decl_ref_buffer new_a_decls(pm()); + parse_accessor_decls(new_a_decls); + ct_decls.push_back(pm().mk_pconstructor_decl(m_sort_id2param_idx.size(), ct_name, r_name, new_a_decls.size(), new_a_decls.c_ptr())); + check_rparen_next("invalid constructor declaration, ')' expected"); + } + } + if (ct_decls.empty()) + throw parser_exception("invalid datatype declaration, datatype does not have any constructors"); + } + + void parse_declare_datatypes() { + SASSERT(curr_is_identifier()); + SASSERT(curr_id() == m_declare_datatypes); + next(); + unsigned line = m_scanner.get_line(); + unsigned pos = m_scanner.get_pos(); + parse_sort_decl_params(); + m_dt_name2idx.reset(); + unsigned i = 0; + pdatatype_decl_ref_buffer new_dt_decls(pm()); + check_lparen_next("invalid datatype declaration, '(' expected"); + while (!curr_is_rparen()) { + check_lparen_next("invalid datatype declaration, '(' or ')' expected"); + check_identifier("invalid datatype declaration, symbol (datatype name) expected"); + symbol dt_name = curr_id(); + next(); + m_dt_name2idx.insert(dt_name, i); + pconstructor_decl_ref_buffer new_ct_decls(pm()); + parse_constructor_decls(new_ct_decls); + new_dt_decls.push_back(pm().mk_pdatatype_decl(m_sort_id2param_idx.size(), dt_name, new_ct_decls.size(), new_ct_decls.c_ptr())); + check_rparen_next("invalid datatype declaration, ')' expected"); + i++; + } + next(); + check_rparen("invalid datatype declaration"); + unsigned sz = new_dt_decls.size(); + if (sz == 0) { + m_ctx.print_success(); + next(); + return; + } + else if (sz == 1) { + symbol missing; + if (new_dt_decls[0]->has_missing_refs(missing)) { + std::string err_msg = "invalid datatype declaration, unknown sort '"; + err_msg += missing.str(); + err_msg += "'"; + throw parser_exception(err_msg, line, pos); + } + } + else { + SASSERT(sz > 1); + pdatatypes_decl_ref dts(pm()); + dts = pm().mk_pdatatypes_decl(m_sort_id2param_idx.size(), sz, new_dt_decls.c_ptr()); + symbol missing; + if (!pm().fix_missing_refs(dts, missing)) { + std::string err_msg = "invalid datatype declaration, unknown sort '"; + err_msg += missing.str(); + err_msg += "'"; + throw parser_exception(err_msg, line, pos); + } + m_ctx.insert_aux_pdecl(dts.get()); + } + for (unsigned i = 0; i < sz; i++) { + pdatatype_decl * d = new_dt_decls[i]; + SASSERT(d != 0); + m_ctx.insert(d); + if (d->get_num_params() == 0) { + // if datatype is not parametric... then force instantiation to register accessor, recognizers and constructors... + sort_ref s(m()); + s = d->instantiate(pm(), 0, 0); + } + } + TRACE("declare_datatypes", tout << "i: " << i << " new_dt_decls.size(): " << sz << "\n"; + for (unsigned i = 0; i < sz; i++) tout << new_dt_decls[i]->get_name() << "\n";); + m_ctx.print_success(); + next(); + } + + void name_expr(expr * n, symbol const & s) { + TRACE("name_expr", tout << "naming: " << s << " ->\n" << mk_pp(n, m()) << "\n";); + if (!is_ground(n) && has_free_vars(n)) + throw parser_exception("invalid named expression, expression contains free variables"); + m_ctx.insert(s, 0, n); + m_last_named_expr.first = s; + m_last_named_expr.second = n; + } + + bool in_quant_ctx(attr_expr_frame * fr) { + return fr != 0 && fr->m_prev != 0 && fr->m_prev->m_kind == EF_QUANT; + } + + void check_in_quant_ctx(attr_expr_frame * fr) { + if (!in_quant_ctx(fr)) + throw parser_exception("invalid attribute, not in the scope of a quantifier"); + } + + void process_last_symbol(attr_expr_frame * fr) { + if (fr->m_last_symbol == symbol::null) + return; + if (fr->m_last_symbol == m_pattern) { + expr * pat = expr_stack().back(); + if (pat == 0) { + if (!ignore_bad_patterns()) + throw parser_exception("invalid empty pattern"); + } + else { + if (!m().is_pattern(pat)) + pat = m().mk_pattern(to_app(pat)); // unary pattern + SASSERT(m().is_pattern(pat)); + pattern_stack().push_back(pat); + } + expr_stack().pop_back(); + } + else if (fr->m_last_symbol == m_nopattern) { + nopattern_stack().push_back(expr_stack().back()); + expr_stack().pop_back(); + } + else { + UNREACHABLE(); + } + } + + void store_qid(attr_expr_frame * fr, symbol const & qid) { + SASSERT(in_quant_ctx(fr)); + static_cast(fr->m_prev)->m_qid = qid; + } + + void store_skid(attr_expr_frame * fr, symbol const & skid) { + SASSERT(in_quant_ctx(fr)); + static_cast(fr->m_prev)->m_skid = skid; + } + + void store_weight(attr_expr_frame * fr, unsigned w) { + SASSERT(in_quant_ctx(fr)); + static_cast(fr->m_prev)->m_weight = w; + } + + // parse expression state + enum pe_state { + PES_EXPR, // expecting + PES_DECL, // expecting ( ) + PES_PATTERN, + PES_CONTINUE + }; + + pe_state consume_attributes(attr_expr_frame * fr) { + if (fr->m_expr_spos == expr_stack().size()) + return PES_EXPR; // didn't parse the expression yet. + process_last_symbol(fr); + while (true) { + check_keyword("invalid attributed expression, keyword expected"); + symbol id = curr_id(); + fr->m_last_symbol = symbol::null; + TRACE("consume_attributes", tout << "id: " << id << ", expr_stack().size(): " << expr_stack().size() << "\n";); + if (id == m_named) { + next(); + check_identifier("invalid attribute value, symbol expected"); + name_expr(expr_stack().back(), curr_id()); + next(); + } + else if (id == m_lblpos || id == m_lblneg) { + next(); + check_identifier("invalid attribute value, symbol expected"); + if (!m().is_bool(expr_stack().back())) + throw parser_exception("invalid labeled expression, expression must have Bool sort"); + expr * new_expr = m().mk_label(id == m_lblpos, curr_id(), expr_stack().back()); + expr_stack().pop_back(); + expr_stack().push_back(new_expr); + next(); + } + else if (id == m_weight) { + check_in_quant_ctx(fr); + next(); + check_int("invalid weight attribute, integer expected"); + rational n = curr_numeral(); + if (!n.is_unsigned()) + throw parser_exception("invalid weight attribute, value is too big to fit in an unsigned machine integer"); + store_weight(fr, n.get_unsigned()); + next(); + } + else if (id == m_skid) { + check_in_quant_ctx(fr); + next(); + check_identifier("invalid attribute value, symbol expected"); + store_skid(fr, curr_id()); + next(); + } + else if (id == m_qid) { + check_in_quant_ctx(fr); + next(); + check_identifier("invalid attribute value, symbol expected"); + store_qid(fr, curr_id()); + next(); + } + else if (id == m_pattern) { + if (!ignore_user_patterns()) { + check_in_quant_ctx(fr); + next(); + fr->m_last_symbol = id; + return PES_PATTERN; + } + else { + // just consume pattern + next(); + consume_sexpr(); + } + } + else if (id == m_nopattern) { + if (!ignore_user_patterns()) { + check_in_quant_ctx(fr); + next(); + fr->m_last_symbol = id; + return PES_EXPR; + } + else { + // just consume pattern + next(); + consume_sexpr(); + } + } + else { + std::ostringstream str; + str << "unknown attribute " << id; + warning_msg(str.str().c_str()); + next(); + // just consume the + consume_sexpr(); + } + if (curr_is_rparen()) + return PES_CONTINUE; + } + } + + pe_state parse_expr_state() { + if (m_num_expr_frames == 0) + return PES_EXPR; + expr_frame * fr = static_cast(m_stack.top()); + switch (fr->m_kind) { + case EF_LET: + return static_cast(fr)->m_in_decls ? PES_DECL : PES_EXPR; + case EF_ATTR_EXPR: + return consume_attributes(static_cast(fr)); + default: + return PES_EXPR; + } + } + + void parse_numeral(bool is_int) { + SASSERT(!is_int || curr_is_int()); + SASSERT(is_int || curr_is_float()); + TRACE("parse_numeral", tout << "curr(): " << curr() << ", curr_numeral(): " << curr_numeral() << ", is_int: " << is_int << "\n";); + expr_stack().push_back(autil().mk_numeral(curr_numeral(), is_int && !m_ctx.numeral_as_real())); + next(); + } + + void parse_bv_numeral() { + SASSERT(curr() == scanner::BV_TOKEN); + expr_stack().push_back(butil().mk_numeral(curr_numeral(), m_scanner.get_bv_size())); + TRACE("parse_bv_numeral", tout << "new numeral: " << mk_pp(expr_stack().back(), m()) << "\n";); + next(); + } + + void push_pattern_frame() { + // TODO: It seems the only reliable way to parse patterns is: + // Parse as an S-Expr, then try to convert it to an useful pattern. + // If it is not possible, then discard pattern. + // After this modification, the (PROMOTE) hack below can be removed. + if (curr_is_lparen()) { + next(); + } + else { + if (!ignore_bad_patterns()) + throw parser_exception("invalid pattern, '(' expected"); + consume_sexpr(); + expr_stack().push_back(0); // empty pattern + return; + } + + if (curr_is_lparen()) { + // multi-pattern + void * mem = m_stack.allocate(sizeof(pattern_frame)); + new (mem) pattern_frame(expr_stack().size()); + m_num_expr_frames++; + } + else if (curr_is_rparen()) { + next(); + expr_stack().push_back(0); // empty pattern + } + else { + // unary pattern + // HACK: to consume & discard (PROMOTE)-like patterns that were incorrectly introduced in SMT-LIB 2.0 + // when Simplify benchmarks were converted into SMT2 ones. + if (curr_is_identifier()) { + symbol id = curr_id(); + func_decl * f = 0; + try { + f = m_ctx.find_func_decl(id); + } + catch (cmd_exception &) { + } + if (f && f->get_arity() == 0) { + if (!ignore_bad_patterns()) + throw parser_exception("invalid constant pattern"); + while (!curr_is_rparen()) + consume_sexpr(); + next(); + expr_stack().push_back(0); // empty pattern + return; // no frame is created + } + } + if (!curr_is_lparen() && !curr_is_identifier()) + throw parser_exception("invalid pattern, '(' or identifier expected"); + push_app_frame(); + } + } + + void push_let_decl_frame() { + check_lparen_next("invalid let declaration, '(' expected"); + check_identifier("invalid let declaration, symbol expected"); + symbol_stack().push_back(curr_id()); + next(); + void * mem = m_stack.allocate(sizeof(let_decl_frame)); + new (mem) let_decl_frame(); + m_num_expr_frames++; + } + + unsigned parse_sorted_vars() { + unsigned num = 0; + unsigned sym_spos = symbol_stack().size(); + unsigned sort_spos = sort_stack().size(); + TRACE("parse_sorted_vars", tout << "[before] symbol_stack().size(): " << symbol_stack().size() << "\n";); + check_lparen_next("invalid list of sorted variables, '(' expected"); + m_env.begin_scope(); + while (!curr_is_rparen()) { + check_lparen_next("invalid sorted variable, '(' expected"); + check_identifier("invalid sorted variable, symbol expected"); + symbol_stack().push_back(curr_id()); + TRACE("parse_sorted_vars", tout << "push_back curr_id(): " << curr_id() << "\n";); + next(); + parse_sort(); + check_rparen_next("invalid sorted variable, ')' expected"); + num++; + } + next(); + TRACE("parse_sorted_vars", tout << "[after] symbol_stack().size(): " << symbol_stack().size() << "\n";); + symbol const * sym_it = symbol_stack().c_ptr() + sym_spos; + sort * const * sort_it = sort_stack().c_ptr() + sort_spos; + m_num_bindings += num; + unsigned i = num; + while (i > 0) { + --i; + var * v = m().mk_var(i, *sort_it); + expr_stack().push_back(v); // prevent v from being deleted + TRACE("parse_sorted_vars", tout << "registering " << *sym_it << " -> " << mk_pp(v, m()) << ", num: " << num << ", i: " << i << "\n";); + m_env.insert(*sym_it, local(v, m_num_bindings)); + SASSERT(m_env.contains(*sym_it)); + ++sort_it; + ++sym_it; + } + return num; + } + + void push_quant_frame(bool is_forall) { + SASSERT(curr_is_identifier()); + SASSERT(curr_id_is_forall() || curr_id_is_exists()); + SASSERT(!is_forall || curr_id_is_forall()); + SASSERT(is_forall || curr_id_is_exists()); + next(); + void * mem = m_stack.allocate(sizeof(quant_frame)); + new (mem) quant_frame(is_forall, pattern_stack().size(), nopattern_stack().size(), symbol_stack().size(), + sort_stack().size(), expr_stack().size()); + m_num_expr_frames++; + unsigned num_vars = parse_sorted_vars(); + if (num_vars == 0) + throw parser_exception("invalied quantifier, list of sorted variables is empty"); + } + + symbol parse_indexed_identifier_core() { + check_underscore_next("invalid indexed identifier, '_' expected"); + check_identifier("invalid indexed identifier, symbol expected"); + symbol r = curr_id(); + next(); + unsigned num_indices = 0; + while (!curr_is_rparen()) { + if (curr_is_int()) { + rational n = curr_numeral(); + if (!n.is_unsigned()) + throw parser_exception("invalid indexed identifier, index is too big to fit in an unsigned machine integer"); + m_param_stack.push_back(parameter(n.get_unsigned())); + next(); + } + else if (curr_is_identifier() || curr_is_lparen()) { + m_param_stack.push_back(parameter(parse_func_decl_ref())); + } + else { + throw parser_exception("invalid indexed identifier, integer, identifier or '(' expected"); + } + num_indices++; + } + if (num_indices == 0) + throw parser_exception("invalid indexed identifier, index expected"); + next(); + return r; + } + + symbol parse_indexed_identifier() { + if (curr_is_identifier()) { + symbol r = curr_id(); + next(); + return r; + } + check_lparen_next("invalid (indexed) identifier, '(_' or symbol expected"); + return parse_indexed_identifier_core(); + } + + // parse: + // 'as' ')' + // '_' + ')' + // 'as' (|)+ ')' ')' + symbol parse_qualified_identifier_core(bool & has_as) { + SASSERT(curr_is_identifier()); + SASSERT(curr_id_is_underscore() || curr_id_is_as()); + if (curr_id_is_underscore()) { + has_as = false; + return parse_indexed_identifier_core(); + } + else { + SASSERT(curr_id_is_as()); + has_as = true; + next(); + symbol r = parse_indexed_identifier(); + parse_sort(); + check_rparen_next("invalid qualified identifier, ')' expected"); + return r; + } + } + + // parse: + // + // '(' 'as' ')' + // '(' '_' + ')' + // '(' 'as' (|)+ ')' ')' + symbol parse_qualified_identifier(bool & has_as) { + SASSERT(curr_is_lparen() || curr_is_identifier()); + if (curr_is_identifier()) { + has_as = false; + symbol r = curr_id(); + next(); + return r; + } + SASSERT(curr_is_lparen()); + next(); + if (!curr_is_identifier() || (!curr_id_is_underscore() && !curr_id_is_as())) + throw parser_exception("invalid qualified/indexed identifier, '_' or 'as' expected"); + return parse_qualified_identifier_core(has_as); + } + + void unknown_var_const_name(symbol id) { + std::string msg = "unknown constant/variable '"; + msg += id.str() + "'"; + throw parser_exception(msg.c_str()); + } + + rational m_last_bv_numeral; // for bv, bvbin, bvhex + + // return true if *s == [0-9]+ + bool is_bv_decimal(char const * s) { + TRACE("is_bv_num", tout << "is_bv_decimal: " << s << "\n";); + SASSERT('0' <= *s && *s <= '9'); + rational & n = m_last_bv_numeral; + n = rational(*s - '0'); + ++s; + while ('0' <= *s && *s <= '9') { + n *= rational(10); + n += rational(*s - '0'); + ++s; + } + if (*s != 0) + return false; + return true; + } + + // return true if *s == bin[0-1]+ + bool is_bv_binary(char const * s) { + SASSERT(*s == 'b'); + ++s; + if (*s != 'i') return false; + ++s; + if (*s != 'n') return false; + ++s; + rational & n = m_last_bv_numeral; + unsigned i = 0; + n = rational(0); + while (*s == '0' || *s == '1') { + n *= rational(2); + n += rational(*s - '0'); + ++s; + ++i; + } + if (*s != 0 || i == 0) + return false; + return true; + } + + // return true if *s == hex[0-9,a-f,A-F]+ + bool is_bv_hex(char const * s) { + SASSERT(*s == 'h'); + ++s; + if (*s != 'e') return false; + ++s; + if (*s != 'x') return false; + ++s; + rational & n = m_last_bv_numeral; + unsigned i = 0; + n = rational(0); + while (true) { + if ('0' <= *s && *s <= '9') { + n *= rational(16); + n += rational(*s - '0'); + } + else if ('a' <= *s && *s <= 'f') { + n *= rational(16); + n += rational(10 + (*s - 'a')); + } + else if ('A' <= *s && *s <= 'F') { + n *= rational(16); + n += rational(10 + (*s - 'A')); + } + else if (*s == 0) { + return i > 0; + } + else { + return false; + } + ++s; + ++i; + } + } + + // Return true if + // n == bv[0-9]+ OR + // n == bvhex[0-9,a-f,A-F]+ OR + // n == bvbin[0-1]+ + // It store the bit-vector value in m_last_bv_numeral + bool is_bv_num(symbol const & n) { + char const * s = n.bare_str(); + if (*s != 'b') return false; + s++; + if (*s != 'v') return false; + s++; + if ('0' <= *s && *s <= '9') + return is_bv_decimal(s); + else if (*s == 'b') + return is_bv_binary(s); + else if (*s == 'h') + return is_bv_hex(s); + else + return false; + } + + void push_local(local const & l) { + if (is_ground(l.m_term) || l.m_level == m_num_bindings) { + expr_stack().push_back(l.m_term); + } + else { + SASSERT(l.m_level <= m_num_bindings); + expr_ref new_term(m()); + shifter()(l.m_term, m_num_bindings - l.m_level, new_term); + expr_stack().push_back(new_term); + } + } + + // parse as expression + void parse_expr_name() { + SASSERT(curr_is_identifier()); + symbol n = curr_id(); + local l; + if (m_env.find(n, l)) { + push_local(l); + } + else { + expr_ref t_ref(m()); + m_ctx.mk_const(n, t_ref); + expr_stack().push_back(t_ref.get()); + } + next(); + } + + // if has_as == true, then the sort of t must be equal to sort_stack().pop_back() + // if that is the case, pop the top of sort_stack() + void check_qualifier(expr * t, bool has_as) { + if (has_as) { + sort * s = sort_stack().back(); + if (s != m().get_sort(t)) + throw parser_exception("invalid qualified identifier, sort mismatch"); + sort_stack().pop_back(); + } + } + + // parse + // 'as' ')' + // '_' + ')' + // 'as' '(' (|)+ ')' ')' + void parse_qualified_name() { + SASSERT(curr_is_identifier()); + SASSERT(curr_id_is_as() || curr_id_is_underscore()); + TRACE("parse_qualified_name", tout << "parse_qualified_name() curr_id: " << curr_id() << "\n";); + unsigned param_spos = m_param_stack.size(); + bool has_as; + symbol r = parse_qualified_identifier_core(has_as); + TRACE("parse_qualified_name", tout << "parse_qualified_name() r: " << r << "\n";); + expr * t; + local l; + if (m_env.find(r, l)) { + push_local(l); + t = expr_stack().back(); + check_qualifier(t, has_as); + if (m_param_stack.size() != param_spos) + throw parser_exception("invalid indexed identifier, symbol is a local declaration"); + return; + } + unsigned num_indices = m_param_stack.size() - param_spos; + if (is_bv_num(r)) { + if (num_indices != 1 || !m_param_stack.back().is_int()) + throw parser_exception("invalid bit-vector constant, index expected"); + unsigned bv_size = m_param_stack.back().get_int(); + m_param_stack.pop_back(); + t = butil().mk_numeral(m_last_bv_numeral, bv_size); + expr_stack().push_back(t); + check_qualifier(t, has_as); + return; + } + expr_ref t_ref(m()); + m_ctx.mk_app(r, 0, 0, num_indices, m_param_stack.c_ptr() + param_spos, has_as ? sort_stack().back() : 0, t_ref); + m_param_stack.shrink(param_spos); + expr_stack().push_back(t_ref.get()); + if (has_as) { + check_qualifier(t_ref.get(), has_as); + } + } + + void parse_root_obj() { + SASSERT(curr_is_identifier()); + SASSERT(curr_id_is_root_obj()); + next(); + parse_sexpr(); + sexpr * p = sexpr_stack().back(); + check_int("invalid root-obj, (unsigned) integer expected"); + rational idx = curr_numeral(); + if (!idx.is_unsigned()) + throw parser_exception("invalid root-obj, index must fit in an unsigned machine integer"); + unsigned u_idx = idx.get_unsigned(); + if (u_idx == 0) + throw parser_exception("invalid root-obj, index must be >= 1"); + next(); + check_rparen_next("invalid root-obj, ')' expected"); + expr_stack().push_back(autil().mk_numeral(p, u_idx)); + sexpr_stack().pop_back(); + } + + void push_app_frame() { + SASSERT(curr_is_lparen() || curr_is_identifier()); + unsigned param_spos = m_param_stack.size(); + unsigned expr_spos = expr_stack().size(); + bool has_as; + symbol f = parse_qualified_identifier(has_as); + void * mem = m_stack.allocate(sizeof(quant_frame)); + new (mem) app_frame(f, expr_spos, param_spos, has_as); + m_num_expr_frames++; + } + + // return true if a new frame was created. + void push_expr_frame(expr_frame * curr) { + SASSERT(curr_is_lparen()); + next(); + TRACE("push_expr_frame", tout << "push_expr_frame(), curr(): " << m_curr << "\n";); + if (curr_is_identifier()) { + TRACE("push_expr_frame", tout << "push_expr_frame(), curr_id(): " << curr_id() << "\n";); + if (curr_id_is_let()) { + next(); + check_lparen_next("invalid let declaration, '(' expected"); + void * mem = m_stack.allocate(sizeof(let_frame)); + new (mem) let_frame(symbol_stack().size(), expr_stack().size()); + m_num_expr_frames++; + } + else if (curr_id_is_forall()) { + push_quant_frame(true); + } + else if (curr_id_is_exists()) { + push_quant_frame(false); + } + else if (curr_id_is_bang()) { + TRACE("consume_attributes", tout << "begin bang, expr_stack.size(): " << expr_stack().size() << "\n";); + next(); + void * mem = m_stack.allocate(sizeof(attr_expr_frame)); + new (mem) attr_expr_frame(curr, symbol_stack().size(), expr_stack().size()); + m_num_expr_frames++; + } + else if (curr_id_is_as() || curr_id_is_underscore()) { + TRACE("push_expr_frame", tout << "push_expr_frame(): parse_qualified_name\n";); + parse_qualified_name(); + } + else if (curr_id_is_root_obj()) { + parse_root_obj(); + } + else { + push_app_frame(); + } + } + else if (curr_is_lparen()) { + push_app_frame(); + } + else { + throw parser_exception("invalid expression, '(' or symbol expected"); + } + } + + void pop_app_frame(app_frame * fr) { + SASSERT(expr_stack().size() >= fr->m_expr_spos); + SASSERT(m_param_stack.size() >= fr->m_param_spos); + if (expr_stack().size() == fr->m_expr_spos) + throw parser_exception("invalid function application, arguments missing"); + unsigned num_args = expr_stack().size() - fr->m_expr_spos; + unsigned num_indices = m_param_stack.size() - fr->m_param_spos; + expr_ref t_ref(m()); + m_ctx.mk_app(fr->m_f, + num_args, + expr_stack().c_ptr() + fr->m_expr_spos, + num_indices, + m_param_stack.c_ptr() + fr->m_param_spos, + fr->m_as_sort ? sort_stack().back() : 0, + t_ref); + expr_stack().shrink(fr->m_expr_spos); + m_param_stack.shrink(fr->m_param_spos); + if (fr->m_as_sort) + sort_stack().pop_back(); + TRACE("pop_app_frame", tout << "new term: " << mk_pp(t_ref, m()) << "\n";); + expr_stack().push_back(t_ref.get()); + m_stack.deallocate(fr); + m_num_expr_frames--; + } + + void pop_let_frame(let_frame * fr) { + if (fr->m_in_decls) { + m_env.begin_scope(); + fr->m_in_decls = false; + SASSERT(symbol_stack().size() >= fr->m_sym_spos); + SASSERT(expr_stack().size() >= fr->m_expr_spos); + SASSERT(symbol_stack().size() - fr->m_sym_spos == expr_stack().size() - fr->m_expr_spos); + unsigned num_decls = expr_stack().size() - fr->m_expr_spos; + symbol * sym_it = symbol_stack().c_ptr() + fr->m_sym_spos; + expr ** expr_it = expr_stack().c_ptr() + fr->m_expr_spos; + expr ** expr_end = expr_it + num_decls; + for (; expr_it != expr_end; ++expr_it, ++sym_it) { + TRACE("let_frame", tout << "declaring: " << *sym_it << " " << mk_pp(*expr_it, m()) << "\n";); + m_env.insert(*sym_it, local(*expr_it, m_num_bindings)); + } + } + else { + // the resultant expression is on the top of the stack + TRACE("let_frame", tout << "let result expr: " << mk_pp(expr_stack().back(), m()) << "\n";); + expr_ref r(m()); + r = expr_stack().back(); + expr_stack().pop_back(); + // remove local declarations from the stack + symbol_stack().shrink(fr->m_sym_spos); + expr_stack().shrink(fr->m_expr_spos); + m_env.end_scope(); + // put result back on the stack + expr_stack().push_back(r.get()); + m_stack.deallocate(fr); + m_num_expr_frames--; + } + } + + void pop_quant_frame(quant_frame * fr) { + SASSERT(pattern_stack().size() >= fr->m_pat_spos); + SASSERT(nopattern_stack().size() >= fr->m_nopat_spos); + SASSERT(symbol_stack().size() >= fr->m_sym_spos); + SASSERT(sort_stack().size() >= fr->m_sort_spos); + SASSERT(symbol_stack().size() - fr->m_sym_spos == sort_stack().size() - fr->m_sort_spos); + SASSERT(expr_stack().size() >= fr->m_expr_spos); + unsigned num_decls = sort_stack().size() - fr->m_sort_spos; + if (expr_stack().size() - fr->m_expr_spos != num_decls /* variables */ + 1 /* result */) + throw parser_exception("invalid quantified expression, syntax error: (forall|exists (( )*) ) expected"); + unsigned begin_pats = fr->m_pat_spos; + unsigned end_pats = pattern_stack().size(); + unsigned j = begin_pats; + for (unsigned i = begin_pats; i < end_pats; i++) { + expr * pat = pattern_stack().get(i); + if (!pat_validator()(num_decls, pat)) { + if (!ignore_bad_patterns()) + throw parser_exception("invalid pattern"); + continue; + } + pattern_stack().set(j, pat); + j++; + } + end_pats = j; + pattern_stack().shrink(end_pats); + unsigned num_pats = end_pats - begin_pats; + unsigned num_nopats = nopattern_stack().size() - fr->m_nopat_spos; + TRACE("parse_quantifier", tout << "weight: " << fr->m_weight << "\n";); + TRACE("skid", tout << "fr->m_skid: " << fr->m_skid << "\n";); + TRACE("parse_quantifier", tout << "body:\n" << mk_pp(expr_stack().back(), m()) << "\n";); + if (fr->m_qid == symbol::null) + fr->m_qid = symbol(m_scanner.get_line()); + if (!m().is_bool(expr_stack().back())) + throw parser_exception("quantifier body must be a Boolean expression"); + quantifier * new_q = m().mk_quantifier(fr->m_forall, + num_decls, + sort_stack().c_ptr() + fr->m_sort_spos, + symbol_stack().c_ptr() + fr->m_sym_spos, + expr_stack().back(), + fr->m_weight, + fr->m_qid, + fr->m_skid, + num_pats, pattern_stack().c_ptr() + fr->m_pat_spos, + num_nopats, nopattern_stack().c_ptr() + fr->m_nopat_spos + ); + TRACE("mk_quantifier", tout << "id: " << new_q->get_id() << "\n" << mk_ismt2_pp(new_q, m()) << "\n";); + TRACE("skid", tout << "new_q->skid: " << new_q->get_skid() << "\n";); + expr_stack().shrink(fr->m_expr_spos); + pattern_stack().shrink(fr->m_pat_spos); + nopattern_stack().shrink(fr->m_nopat_spos); + symbol_stack().shrink(fr->m_sym_spos); + sort_stack().shrink(fr->m_sort_spos); + m_env.end_scope(); + SASSERT(num_decls <= m_num_bindings); + m_num_bindings -= num_decls; + + expr_stack().push_back(new_q); + m_stack.deallocate(fr); + m_num_expr_frames--; + } + + void pop_attr_expr_frame(attr_expr_frame * fr) { + process_last_symbol(fr); + TRACE("consume_attributes", tout << "pop_attr_expr_frame, expr_stack.size(): " << expr_stack().size() << "\n";); + // the resultant expression is already on the top of the stack. + SASSERT(expr_stack().size() == fr->m_expr_spos + 1); + m_stack.deallocate(fr); + m_num_expr_frames--; + } + + void pop_pattern_frame(pattern_frame * fr) { + SASSERT(expr_stack().size() >= fr->m_expr_spos); + if (expr_stack().size() == fr->m_expr_spos) { + if (!ignore_bad_patterns()) + throw parser_exception("invalid empty pattern"); + // ingoring empty pattern + expr_stack().shrink(fr->m_expr_spos); + } + else { + unsigned num = expr_stack().size() - fr->m_expr_spos; + expr * new_pat = m().mk_pattern(num, reinterpret_cast(expr_stack().c_ptr() + fr->m_expr_spos)); + expr_stack().shrink(fr->m_expr_spos); + expr_stack().push_back(new_pat); + } + m_stack.deallocate(fr); + m_num_expr_frames--; + } + + void pop_expr_frame() { + SASSERT(curr_is_rparen()); + expr_frame * fr = static_cast(m_stack.top()); + switch (fr->m_kind) { + case EF_APP: + pop_app_frame(static_cast(fr)); + break; + case EF_LET: + pop_let_frame(static_cast(fr)); + break; + case EF_LET_DECL: + m_stack.deallocate(static_cast(fr)); + m_num_expr_frames--; + break; + case EF_QUANT: + pop_quant_frame(static_cast(fr)); + break; + case EF_ATTR_EXPR: + pop_attr_expr_frame(static_cast(fr)); + break; + case EF_PATTERN: + pop_pattern_frame(static_cast(fr)); + break; + default: + UNREACHABLE(); + } + SASSERT(curr_is_rparen()); + next(); // consume ')' + } + + void parse_expr() { + m_num_expr_frames = 0; + do { + TRACE("parse_expr", tout << "curr(): " << curr() << ", m_num_expr_frames: " << m_num_expr_frames + << ", expr_stack().size(): " << expr_stack().size() << "\n";); + if (curr_is_rparen()) { + if (m_num_expr_frames == 0) + throw parser_exception("invalid expression, unexpected ')'"); + pop_expr_frame(); + } + else { + pe_state st = parse_expr_state(); + TRACE("consume_attributes", tout << "parse_expr_state: " << st << ", expr_stack.size(): " << expr_stack().size() << "\n";); + switch (st) { + case PES_EXPR: + switch (curr()) { + case scanner::SYMBOL_TOKEN: + parse_expr_name(); + break; + case scanner::INT_TOKEN: + parse_numeral(true); + break; + case scanner::FLOAT_TOKEN: + parse_numeral(false); + break; + case scanner::BV_TOKEN: + parse_bv_numeral(); + break; + case scanner::LEFT_PAREN: + push_expr_frame(m_num_expr_frames == 0 ? 0 : static_cast(m_stack.top())); + break; + case scanner::KEYWORD_TOKEN: + throw parser_exception("invalid expression, unexpected keyword"); + default: + throw parser_exception("invalid expression, unexpected input"); + } + break; + case PES_DECL: + push_let_decl_frame(); + break; + case PES_PATTERN: + push_pattern_frame(); + break; + case PES_CONTINUE: + // do nothing + break; + default: + UNREACHABLE(); + break; + } + } + } + while (m_num_expr_frames > 0 ); + SASSERT(!expr_stack().empty()); + } + + unsigned parse_exprs() { + unsigned sz = 0; + check_lparen_next("invalid list of terms, '(' expected"); + while (!curr_is_rparen()) { + parse_expr(); + sz++; + } + next(); + return sz; + } + + void parse_sort_decl_params() { + check_lparen_next("invalid sort declaration, parameters missing"); + m_sort_id2param_idx.reset(); + unsigned i = 0; + while (!curr_is_rparen()) { + check_identifier("invalid sort parameter, symbol or ')' expected"); + m_sort_id2param_idx.insert(curr_id(), i); + i++; + next(); + } + next(); + } + + void parse_declare_sort() { + SASSERT(curr_is_identifier()); + SASSERT(curr_id() == m_declare_sort); + next(); + + check_identifier("invalid sort declaration, symbol expected"); + symbol id = curr_id(); + if (m_ctx.find_psort_decl(id) != 0) + throw parser_exception("invalid sort declaration, sort already declared/defined"); + next(); + if (curr_is_rparen()) { + psort_decl * decl = pm().mk_psort_user_decl(0, id, 0); + m_ctx.insert(decl); + } + else { + check_int("invalid sort declaration, arity () or ')' expected"); + rational n = curr_numeral(); + if (!n.is_unsigned()) + throw parser_exception("invalid sort declaration, arity is too big to fit in an unsigned machine integer"); + psort_decl * decl = pm().mk_psort_user_decl(n.get_unsigned(), id, 0); + m_ctx.insert(decl); + next(); + check_rparen("invalid sort declaration, ')' expected"); + } + m_ctx.print_success(); + next(); + } + + void parse_define_sort() { + SASSERT(curr_is_identifier()); + SASSERT(curr_id() == m_define_sort); + next(); + check_identifier("invalid sort definition, symbol expected"); + symbol id = curr_id(); + if (m_ctx.find_psort_decl(id) != 0) + throw parser_exception("invalid sort definition, sort already declared/defined"); + next(); + parse_sort_decl_params(); + + parse_psort(); + psort_decl * decl = pm().mk_psort_user_decl(m_sort_id2param_idx.size(), id, psort_stack().back()); + psort_stack().pop_back(); + m_ctx.insert(decl); + check_rparen("invalid sort definition, ')' expected"); + m_ctx.print_success(); + next(); + } + + void parse_define_fun() { + SASSERT(curr_is_identifier()); + SASSERT(curr_id() == m_define_fun); + SASSERT(m_num_bindings == 0); + next(); + check_identifier("invalid function/constant definition, symbol expected"); + symbol id = curr_id(); + next(); + unsigned sym_spos = symbol_stack().size(); + unsigned sort_spos = sort_stack().size(); + unsigned expr_spos = expr_stack().size(); + unsigned num_vars = parse_sorted_vars(); + parse_sort(); + parse_expr(); + if (m().get_sort(expr_stack().back()) != sort_stack().back()) + throw parser_exception("invalid function/constant definition, sort mismatch"); + m_ctx.insert(id, num_vars, expr_stack().back()); + check_rparen("invalid function/constant definition, ')' expected"); + // restore stacks & env + symbol_stack().shrink(sym_spos); + sort_stack().shrink(sort_spos); + expr_stack().shrink(expr_spos); + m_env.end_scope(); + SASSERT(num_vars == m_num_bindings); + m_num_bindings = 0; + m_ctx.print_success(); + next(); + } + + void parse_define_const() { + SASSERT(curr_is_identifier()); + SASSERT(curr_id() == m_define_const); + SASSERT(m_num_bindings == 0); + next(); + check_identifier("invalid constant definition, symbol expected"); + symbol id = curr_id(); + next(); + parse_sort(); + parse_expr(); + if (m().get_sort(expr_stack().back()) != sort_stack().back()) + throw parser_exception("invalid constant definition, sort mismatch"); + m_ctx.insert(id, 0, expr_stack().back()); + check_rparen("invalid constant definition, ')' expected"); + expr_stack().pop_back(); + sort_stack().pop_back(); + m_ctx.print_success(); + next(); + } + + void parse_declare_fun() { + SASSERT(curr_is_identifier()); + SASSERT(curr_id() == m_declare_fun); + next(); + check_identifier("invalid function declaration, symbol expected"); + symbol id = curr_id(); + next(); + unsigned spos = sort_stack().size(); + unsigned num_params = parse_sorts(); + parse_sort(); + func_decl_ref f(m()); + f = m().mk_func_decl(id, num_params, sort_stack().c_ptr() + spos, sort_stack().back()); + sort_stack().shrink(spos); + m_ctx.insert(f); + check_rparen("invalid function declaration, ')' expected"); + m_ctx.print_success(); + next(); + } + + void parse_declare_const() { + SASSERT(curr_is_identifier()); + SASSERT(curr_id() == m_declare_const); + next(); + check_identifier("invalid constant declaration, symbol expected"); + symbol id = curr_id(); + next(); + parse_sort(); + SASSERT(!sort_stack().empty()); + func_decl_ref c(m()); + c = m().mk_const_decl(id, sort_stack().back()); + TRACE("declare_const", tout << "declaring " << id << " "; pm().display(tout, sort_stack().back()); tout << "\n";); + SASSERT(c.get() != 0); + sort_stack().pop_back(); + m_ctx.insert(c); + check_rparen("invalid constant declaration, ')' expected"); + m_ctx.print_success(); + next(); + } + + unsigned parse_opt_unsigned(unsigned def) { + unsigned num; + if (!curr_is_rparen()) { + check_int("invalid push command, integer expected"); + rational n = curr_numeral(); + if (!n.is_unsigned()) + throw parser_exception("invalid push command, value is too big to fit in an unsigned machine integer"); + num = n.get_unsigned(); + next(); + } + else { + num = def; + } + return num; + } + + void parse_push() { + SASSERT(curr_is_identifier()); + SASSERT(curr_id() == m_push); + next(); + unsigned num = parse_opt_unsigned(1); + m_ctx.push(num); + check_rparen("invalid push command, ')' expected"); + m_ctx.print_success(); + next(); + } + + void parse_pop() { + SASSERT(curr_is_identifier()); + SASSERT(curr_id() == m_pop); + next(); + unsigned num = parse_opt_unsigned(1); + m_ctx.pop(num); + check_rparen("invalid pop command, ')' expected"); + m_ctx.print_success(); + next(); + TRACE("after_pop", tout << "expr_stack.size: " << expr_stack().size() << "\n"; m_ctx.dump_assertions(tout);); + } + + std::string m_assert_expr; + + void parse_assert() { + SASSERT(curr_is_identifier()); + SASSERT(curr_id() == m_assert); + m_last_named_expr.first = symbol::null; + m_last_named_expr.second = 0; + if (m_ctx.interactive_mode()) { + m_scanner.start_caching(); + m_cache_end = 0; + } + next(); + parse_expr(); + if (m_ctx.interactive_mode()) { + m_assert_expr = m_scanner.cached_str(0, m_cache_end); + m_scanner.stop_caching(); + } + expr * f = expr_stack().back(); + if (!m().is_bool(f)) + throw cmd_exception("invalid assert command, term is not Boolean"); + if (f == m_last_named_expr.second) { + m_ctx.assert_expr(m_last_named_expr.first, f); + } + else { + m_ctx.assert_expr(f); + } + if (m_ctx.interactive_mode()) { + m_ctx.push_assert_string(m_assert_expr); + } + expr_stack().pop_back(); + check_rparen("invalid assert command, ')' expected"); + m_ctx.print_success(); + next(); + } + + void parse_check_sat() { + SASSERT(curr_is_identifier()); + SASSERT(curr_id() == m_check_sat); + next(); + unsigned spos = expr_stack().size(); + while (!curr_is_rparen()) { + bool sign; + expr_ref t_ref(m()); + if (curr_is_lparen()) { + next(); + check_id_next(m_not, "invalid check-sat command, 'not' expected, assumptions must be Boolean literals"); + check_identifier("invalid check-sat command, literal expected"); + sign = true; + } + else { + check_identifier("invalid check-sat command, literal or ')' expected"); + sign = false; + } + symbol n = curr_id(); + next(); + m_ctx.mk_const(n, t_ref); + if (!m().is_bool(t_ref)) + throw parser_exception("invalid check-sat command, argument must be a Boolean literal"); + if (sign) { + if (!is_uninterp_const(t_ref)) + throw parser_exception("invalid check-sat command, argument must be a Boolean literal"); + t_ref = m().mk_not(t_ref.get()); + } + else { + expr * arg; + if (!is_uninterp_const(t_ref) && !(m().is_not(t_ref, arg) && is_uninterp_const(arg))) + throw parser_exception("invalid check-sat command, argument must be a Boolean literal"); + } + expr_stack().push_back(t_ref.get()); + if (sign) + check_rparen_next("invalid check-sat command, ')' expected"); + } + m_ctx.check_sat(expr_stack().size() - spos, expr_stack().c_ptr() + spos); + next(); + expr_stack().shrink(spos); + } + + void parse_get_value() { + SASSERT(curr_is_identifier()); + SASSERT(curr_id() == m_get_value); + next(); + unsigned spos = expr_stack().size(); + unsigned cache_it = 0; + + m_scanner.start_caching(); + m_cache_end = 0; + m_cached_strings.reset(); + + check_lparen_next("invalid get-value command, '(' expected"); + while (!curr_is_rparen()) { + parse_expr(); + if (!is_ground(expr_stack().back())) + throw cmd_exception("invalid get-value term, term must be ground and must not contain quantifiers"); + m_cached_strings.push_back(m_scanner.cached_str(cache_it, m_cache_end)); + cache_it = m_cache_end; + } + m_scanner.stop_caching(); + if (m_cached_strings.empty()) + throw cmd_exception("invalid get-value command, empty list of terms"); + next(); + check_rparen("invalid get-value command, ')' expected"); + if (!m_ctx.is_model_available() || m_ctx.get_check_sat_result() == 0) + throw cmd_exception("model is not available"); + model_ref md; + m_ctx.get_check_sat_result()->get_model(md); + m_ctx.regular_stream() << "("; + expr ** expr_it = expr_stack().c_ptr() + spos; + expr ** expr_end = expr_it + m_cached_strings.size(); + for (unsigned i = 0; expr_it < expr_end; expr_it++, i++) { + expr_ref v(m()); + md->eval(*expr_it, v, true); + if (i > 0) + m_ctx.regular_stream() << "\n "; + m_ctx.regular_stream() << "(" << m_cached_strings[i] << " "; + m_ctx.display(m_ctx.regular_stream(), v); + m_ctx.regular_stream() << ")"; + } + m_ctx.regular_stream() << ")" << std::endl; + expr_stack().shrink(spos); + next(); + } + + void parse_reset() { + SASSERT(curr_is_identifier()); + SASSERT(curr_id() == m_reset); + next(); + check_rparen("invalid reset command, ')' expected"); + m_ctx.reset(); + reset(); + m_ctx.print_success(); + next(); + } + + void parse_option_value() { + switch (curr()) { + case scanner::INT_TOKEN: + case scanner::FLOAT_TOKEN: + m_curr_cmd->set_next_arg(m_ctx, m_scanner.get_number()); + next(); + break; + case scanner::SYMBOL_TOKEN: + m_curr_cmd->set_next_arg(m_ctx, m_scanner.get_id()); + next(); + break; + case scanner::STRING_TOKEN: + m_curr_cmd->set_next_arg(m_ctx, m_scanner.get_string()); + next(); + break; + default: + throw parser_exception("invalid option value"); + } + } + + // A func_decl reference is of the form: + // + // | ( (+) sort) + // | ((_ +) (+) sort) + func_decl * parse_func_decl_ref() { + if (curr_is_identifier()) { + symbol id = curr_id(); + func_decl * d = m_ctx.find_func_decl(id); + next(); + return d; + } + else { + check_lparen_next("invalid function declaration reference, symbol or '(' expected"); + symbol id; + sbuffer indices; + if (curr_is_identifier()) { + id = curr_id(); + next(); + } + else { + check_lparen_next("invalid function declaration reference, symbol or '(' expected"); + check_underscore_next("invalid indexed function declaration reference, '_' expected"); + check_identifier("invalid indexed function declaration reference, symbol expected"); + id = curr_id(); + next(); + while (!curr_is_rparen()) { + check_int("invalid indexed function declaration reference, integer or ')' expected"); + rational n = curr_numeral(); + if (!n.is_unsigned()) + throw parser_exception("invalid indexed function declaration reference, index is too big to fit in an unsigned machine integer"); + indices.push_back(n.get_unsigned()); + next(); + } + if (indices.empty()) + throw parser_exception("invalid indexed function declaration reference, index expected"); + next(); + } + unsigned spos = sort_stack().size(); + parse_sorts(); + unsigned domain_size = sort_stack().size() - spos; + parse_sort(); + func_decl * d = m_ctx.find_func_decl(id, indices.size(), indices.c_ptr(), domain_size, sort_stack().c_ptr() + spos, sort_stack().back()); + sort_stack().shrink(spos); + check_rparen_next("invalid function declaration reference, ')' expected"); + return d; + } + } + + void parse_func_decl_refs(ptr_buffer & flist) { + check_lparen_next("invalid list of function declaration references, '(' expected"); + while (!curr_is_rparen()) { + flist.push_back(parse_func_decl_ref()); + } + next(); + } + + void parse_next_cmd_arg() { + SASSERT(m_curr_cmd != 0); + cmd_arg_kind k = m_curr_cmd->next_arg_kind(m_ctx); + switch (k) { + case CPK_UINT: { + check_int("invalid command argument, unsigned integer expected"); + rational n = curr_numeral(); + if (!n.is_unsigned()) + throw parser_exception("invalid command argument, numeral is too big to fit in an unsigned machine integer"); + m_curr_cmd->set_next_arg(m_ctx, n.get_unsigned()); + next(); + break; + } + case CPK_BOOL: { + check_identifier("invalid command argument, true/false expected"); + symbol val = curr_id(); + if (val != "true" && val != "false") + throw parser_exception("invalid command argument, true/false expected"); + m_curr_cmd->set_next_arg(m_ctx, val == "true"); + next(); + break; + } + case CPK_NUMERAL: + check_int("invalid command argument, numeral expected"); + m_curr_cmd->set_next_arg(m_ctx, curr_numeral()); + next(); + break; + case CPK_DECIMAL: + check_float("invalid command argument, decimal expected"); + m_curr_cmd->set_next_arg(m_ctx, curr_numeral()); + next(); + break; + case CPK_STRING: + check_string("invalid command argument, string expected"); + m_curr_cmd->set_next_arg(m_ctx, m_scanner.get_string()); + next(); + break; + case CPK_KEYWORD: + check_keyword("invalid command argument, keyword expected"); + m_curr_cmd->set_next_arg(m_ctx, curr_id()); + next(); + break; + case CPK_OPTION_VALUE: + parse_option_value(); + break; + case CPK_SYMBOL: + check_identifier("invalid command argument, symbol expected"); + m_curr_cmd->set_next_arg(m_ctx, curr_id()); + next(); + return; + case CPK_SYMBOL_LIST: { + unsigned spos = m_symbol_stack.size(); + unsigned num = parse_symbols(); + m_curr_cmd->set_next_arg(m_ctx, num, m_symbol_stack.c_ptr() + spos); + break; + } + case CPK_SORT: + parse_sort(); + m_curr_cmd->set_next_arg(m_ctx, sort_stack().back()); + return; + case CPK_SORT_LIST: { + unsigned spos = sort_stack().size(); + unsigned num = parse_sorts(); + m_curr_cmd->set_next_arg(m_ctx, num, sort_stack().c_ptr() + spos); + break; + } + case CPK_EXPR: + parse_expr(); + m_curr_cmd->set_next_arg(m_ctx, expr_stack().back()); + return; + case CPK_EXPR_LIST: { + unsigned spos = expr_stack().size(); + unsigned num = parse_exprs(); + m_curr_cmd->set_next_arg(m_ctx, num, expr_stack().c_ptr() + spos); + break; + } + case CPK_FUNC_DECL: { + func_decl * f = parse_func_decl_ref(); + m_curr_cmd->set_next_arg(m_ctx, f); + return; + } + case CPK_FUNC_DECL_LIST: { + ptr_buffer flist; + parse_func_decl_refs(flist); + m_curr_cmd->set_next_arg(m_ctx, flist.size(), flist.c_ptr()); + return; + } + case CPK_SORTED_VAR: + NOT_IMPLEMENTED_YET(); + break; + case CPK_SORTED_VAR_LIST: + NOT_IMPLEMENTED_YET(); + break; + case CPK_SEXPR: + parse_sexpr(); + m_curr_cmd->set_next_arg(m_ctx, sexpr_stack().back()); + break; + case CPK_INVALID: + throw parser_exception("invalid/unexpected argument"); + default: + throw parser_exception("unexpected argument"); + } + } + + void parse_unknown_cmd() { + SASSERT(curr_is_identifier()); + symbol s = curr_id(); + next(); + while (!curr_is_rparen()) { + consume_sexpr(); + } + m_ctx.print_unsupported(s); + next(); + return; + } + + void parse_ext_cmd() { + symbol s = curr_id(); + m_curr_cmd = m_ctx.find_cmd(s); + if (m_curr_cmd == 0) { + parse_unknown_cmd(); + return; + } + next(); + unsigned arity = m_curr_cmd->get_arity(); + unsigned i = 0; + unsigned sort_spos = size(m_sort_stack); + unsigned expr_spos = size(m_expr_stack); + unsigned sexpr_spos = size(m_sexpr_stack); + unsigned sym_spos = m_symbol_stack.size(); + m_curr_cmd->prepare(m_ctx); + while (true) { + if (curr_is_rparen()) { + if (arity != VAR_ARITY && i < arity) + throw parser_exception("invalid command, argument(s) missing"); + m_curr_cmd->execute(m_ctx); + next(); + m_curr_cmd = 0; + shrink(m_sort_stack, sort_spos); + shrink(m_expr_stack, expr_spos); + shrink(m_sexpr_stack, sexpr_spos); + m_symbol_stack.shrink(sym_spos); + m_num_bindings = 0; + return; + } + else { + if (arity != VAR_ARITY && i == arity) + throw parser_exception("invalid command, too many arguments"); + parse_next_cmd_arg(); + } + i++; + } + } + + void parse_cmd() { + SASSERT(curr_is_lparen()); + next(); + check_identifier("invalid command, symbol expected"); + symbol s = curr_id(); + if (s == m_assert) { + parse_assert(); + return; + } + if (s == m_declare_fun) { + parse_declare_fun(); + return; + } + if (s == m_declare_const) { + parse_declare_const(); + return; + } + if (s == m_check_sat) { + parse_check_sat(); + return; + } + if (s == m_push) { + parse_push(); + return; + } + if (s == m_pop) { + parse_pop(); + return; + } + if (s == m_define_fun) { + parse_define_fun(); + return; + } + if (s == m_define_const) { + parse_define_const(); + return; + } + if (s == m_define_sort) { + parse_define_sort(); + return; + } + if (s == m_declare_sort) { + parse_declare_sort(); + return; + } + if (s == m_declare_datatypes) { + parse_declare_datatypes(); + return; + } + if (s == m_get_value) { + parse_get_value(); + return; + } + if (s == m_reset) { + parse_reset(); + return; + } + parse_ext_cmd(); + } + + public: + parser(cmd_context & ctx, std::istream & is, bool interactive): + m_ctx(ctx), + m_scanner(is, interactive), + m_curr(scanner::NULL_TOKEN), + m_curr_cmd(0), + m_num_bindings(0), + m_let("let"), + m_bang("!"), + m_forall("forall"), + m_exists("exists"), + m_as("as"), + m_not("not"), + m_root_obj("root-obj"), + m_named(":named"), + m_weight(":weight"), + m_qid(":qid"), + m_skid(":skolemid"), + m_ex_act(":ex-act"), + m_pattern(":pattern"), + m_nopattern(":no-pattern"), + m_lblneg(":lblneg"), + m_lblpos(":lblpos"), + m_assert("assert"), + m_check_sat("check-sat"), + m_define_fun("define-fun"), + m_define_const("define-const"), + m_declare_fun("declare-fun"), + m_declare_const("declare-const"), + m_define_sort("define-sort"), + m_declare_sort("declare-sort"), + m_declare_datatypes("declare-datatypes"), + m_push("push"), + m_pop("pop"), + m_get_value("get-value"), + m_reset("reset"), + m_underscore("_"), + m_num_open_paren(0) { + // the following assertion does not hold if ctx was already attached to an AST manager before the parser object is created. + // SASSERT(!m_ctx.has_manager()); + } + + ~parser() { + reset_stack(); + } + + void reset() { + reset_stack(); + m_num_bindings = 0; + m_psort_stack = 0; + m_sort_stack = 0; + m_expr_stack = 0; + m_pattern_stack = 0; + m_nopattern_stack = 0; + m_sexpr_stack = 0; + m_symbol_stack .reset(); + m_param_stack .reset(); + m_env .reset(); + m_sort_id2param_idx .reset(); + m_dt_name2idx .reset(); + + m_bv_util = 0; + m_arith_util = 0; + m_pattern_validator = 0; + m_var_shifter = 0; + } + + bool operator()() { + m_num_bindings = 0; + bool found_errors = false; + + try { + scan_core(); + } + catch (scanner_exception & ex) { + error(ex.msg()); + if (!sync_after_error()) + return false; + found_errors = true; + } + + while (true) { + try { + m_num_open_paren = 0; + while (true) { + switch (curr()) { + case scanner::LEFT_PAREN: + parse_cmd(); + break; + case scanner::EOF_TOKEN: + return !found_errors; + default: + throw parser_exception("invalid command, '(' expected"); + break; + } + } + } + catch (z3_error & ex) { + // Can't invoke error(...) when out of memory. + // Reason: escaped() string builder needs memory + m_ctx.regular_stream() << "(error \"line " << m_scanner.get_line() << " column " << m_scanner.get_pos() + << ": " << ex.msg() << "\")" << std::endl; + exit(ex.error_code()); + } + catch (stop_parser_exception) { + m_scanner.stop_caching(); + return !found_errors; + } + catch (parser_exception & ex) { + if (ex.has_pos()) + error(ex.line(), ex.pos(), ex.msg()); + else + error(ex.msg()); + } + catch (ast_exception & ex) { + error(ex.msg()); + } + catch (z3_exception & ex) { + error(ex.msg()); + } + m_scanner.stop_caching(); + if (m_curr_cmd) + m_curr_cmd->failure_cleanup(m_ctx); + reset(); + found_errors = true; + if (!sync_after_error()) + return false; + TRACE("parser_error", tout << "after sync: " << curr() << "\n";); + SASSERT(m_num_open_paren == 0); + } + } + }; +}; + +bool parse_smt2_commands(cmd_context & ctx, std::istream & is, bool interactive) { + smt2::parser p(ctx, is, interactive); + return p(); +} + diff --git a/lib/smt2parser.h b/lib/smt2parser.h new file mode 100644 index 000000000..a7264ff63 --- /dev/null +++ b/lib/smt2parser.h @@ -0,0 +1,26 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + smt2parser.h + +Abstract: + + SMT 2.0 parser + +Author: + + Leonardo de Moura (leonardo) 2011-03-01 + +Revision History: + +--*/ +#ifndef _SMT2_PARSER_H_ +#define _SMT2_PARSER_H_ + +#include"cmd_context.h" + +bool parse_smt2_commands(cmd_context & ctx, std::istream & is, bool interactive = false); + +#endif diff --git a/lib/smt2scanner.cpp b/lib/smt2scanner.cpp new file mode 100644 index 000000000..b2780356c --- /dev/null +++ b/lib/smt2scanner.cpp @@ -0,0 +1,330 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt2scanner.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2011-03-09. + +Revision History: + +--*/ +#include"smt2scanner.h" + +namespace smt2 { + + void scanner::next() { + if (m_cache_input) + m_cache.push_back(m_curr); + SASSERT(m_curr != EOF); + if (m_interactive) { + m_curr = m_stream.get(); + } + else if (m_bpos < m_bend) { + m_curr = m_buffer[m_bpos]; + m_bpos++; + } + else { + m_stream.read(m_buffer, SCANNER_BUFFER_SIZE); + m_bend = static_cast(m_stream.gcount()); + m_bpos = 0; + if (m_bpos == m_bend) { + m_curr = EOF; + } + else { + m_curr = m_buffer[m_bpos]; + m_bpos++; + } + } + m_spos++; + } + + void scanner::read_comment() { + SASSERT(curr() == ';'); + next(); + while (true) { + char c = curr(); + if (c == EOF) + return; + if (c == '\n') { + new_line(); + next(); + return; + } + next(); + } + } + + scanner::token scanner::read_quoted_symbol() { + SASSERT(curr() == '|'); + bool escape = false; + m_string.reset(); + next(); + while (true) { + char c = curr(); + if (c == EOF) { + throw scanner_exception("unexpected end of quoted symbol", m_line, m_spos); + } + else if (c == '\n') { + new_line(); + } + else if (c == '|' && !escape) { + next(); + m_string.push_back(0); + m_id = m_string.begin(); + TRACE("scanner", tout << "new quoted symbol: " << m_id << "\n";); + return SYMBOL_TOKEN; + } + escape = (c == '\\'); + m_string.push_back(c); + next(); + } + } + + scanner::token scanner::read_symbol() { + SASSERT(m_normalized[static_cast(curr())] == 'a' || curr() == ':'); + m_string.reset(); + m_string.push_back(curr()); + next(); + + while (true) { + char c = curr(); + char n = m_normalized[static_cast(c)]; + if (n == 'a' || n == '0') { + m_string.push_back(c); + next(); + } + else { + m_string.push_back(0); + m_id = m_string.begin(); + TRACE("scanner", tout << "new symbol: " << m_id << "\n";); + return SYMBOL_TOKEN; + } + } + } + + scanner::token scanner::read_number() { + SASSERT('0' <= curr() && curr() <= '9'); + rational q(1); + m_number = rational(curr() - '0'); + next(); + bool is_float = false; + + while (true) { + char c = curr(); + if ('0' <= c && c <= '9') { + m_number = rational(10)*m_number + rational(c - '0'); + if (is_float) + q *= rational(10); + next(); + } + else if (c == '.') { + if (is_float) + break; + is_float = true; + next(); + } + else { + break; + } + } + if (is_float) + m_number /= q; + TRACE("scanner", tout << "new number: " << m_number << "\n";); + return is_float ? FLOAT_TOKEN : INT_TOKEN; + } + + scanner::token scanner::read_string() { + SASSERT(curr() == '\"'); + next(); + m_string.reset(); + while (true) { + char c = curr(); + if (c == EOF) + throw scanner_exception("unexpected end of string", m_line, m_spos); + if (c == '\"') { + next(); + m_string.push_back(0); + return STRING_TOKEN; + } + if (c == '\n') + new_line(); + else if (c == '\\') { + next(); + c = curr(); + if (c == EOF) + throw scanner_exception("unexpected end of string", m_line, m_spos); + if (c != '\\' && c != '\"') + throw scanner_exception("invalid escape sequence", m_line, m_spos); + } + m_string.push_back(c); + next(); + } + } + + scanner::token scanner::read_bv_literal() { + SASSERT(curr() == '#'); + next(); + char c = curr(); + if (c == 'x') { + next(); + c = curr(); + m_number = rational(0); + m_bv_size = 0; + while (true) { + if ('0' <= c && c <= '9') { + m_number *= rational(16); + m_number += rational(c - '0'); + } + else if ('a' <= c && c <= 'f') { + m_number *= rational(16); + m_number += rational(10 + (c - 'a')); + } + else if ('A' <= c && c <= 'F') { + m_number *= rational(16); + m_number += rational(10 + (c - 'A')); + } + else { + if (m_bv_size == 0) + throw scanner_exception("invalid empty bit-vector literal", m_line, m_spos); + return BV_TOKEN; + } + m_bv_size += 4; + next(); + c = curr(); + } + } + else if (c == 'b') { + next(); + c = curr(); + m_number = rational(0); + m_bv_size = 0; + while (c == '0' || c == '1') { + m_number *= rational(2); + m_number += rational(c - '0'); + m_bv_size++; + next(); + c = curr(); + } + if (m_bv_size == 0) + throw scanner_exception("invalid empty bit-vector literal", m_line, m_spos); + return BV_TOKEN; + } + else { + throw scanner_exception("invalid bit-vector literal, expecting 'x' or 'b'", m_line, m_spos); + } + } + + scanner::scanner(std::istream& stream, bool interactive): + m_interactive(interactive), + m_spos(0), + m_curr(0), // avoid Valgrind warning + m_line(1), + m_pos(0), + m_bv_size(UINT_MAX), + m_bpos(0), + m_bend(0), + m_stream(stream), + m_cache_input(false) { + for (int i = 0; i < 256; ++i) { + m_normalized[i] = (char) i; + } + m_normalized[static_cast('\t')] = ' '; + m_normalized[static_cast('\r')] = ' '; + // assert ('a' < 'z'); + for (char ch = 'b'; ch <= 'z'; ++ch) { + m_normalized[static_cast(ch)] = 'a'; + } + for (char ch = 'A'; ch <= 'Z'; ++ch) { + m_normalized[static_cast(ch)] = 'a'; + } + // assert ('0' < '9', '9' - '0' == 9); + for (char ch = '1'; ch <= '9'; ++ch) { + m_normalized[static_cast(ch)] = '0'; + } + // SMT2 "Symbols": ~ ! @ $ % ^ & * _ - + = < > . ? / + m_normalized[static_cast('~')] = 'a'; + m_normalized[static_cast('!')] = 'a'; + m_normalized[static_cast('@')] = 'a'; + m_normalized[static_cast('$')] = 'a'; + m_normalized[static_cast('%')] = 'a'; + m_normalized[static_cast('^')] = 'a'; + m_normalized[static_cast('&')] = 'a'; + m_normalized[static_cast('*')] = 'a'; + m_normalized[static_cast('_')] = 'a'; + m_normalized[static_cast('-')] = 'a'; + m_normalized[static_cast('+')] = 'a'; + m_normalized[static_cast('=')] = 'a'; + m_normalized[static_cast('<')] = 'a'; + m_normalized[static_cast('>')] = 'a'; + m_normalized[static_cast('.')] = 'a'; + m_normalized[static_cast('?')] = 'a'; + m_normalized[static_cast('/')] = 'a'; + next(); + } + + scanner::token scanner::scan() { + while (true) { + char c = curr(); + m_pos = m_spos; + switch (m_normalized[(unsigned char) c]) { + case ' ': + next(); + break; + case '\n': + next(); + new_line(); + break; + case ';': + read_comment(); + break; + case ':': + read_symbol(); + return KEYWORD_TOKEN; + case '(': + next(); + return LEFT_PAREN; + case ')': + next(); + return RIGHT_PAREN; + case '|': + return read_quoted_symbol(); + case 'a': + return read_symbol(); + case '"': + return read_string(); + case '0': + return read_number(); + case '#': + return read_bv_literal(); + case -1: + return EOF_TOKEN; + default: { + scanner_exception ex("unexpected character", m_line, m_spos); + next(); + throw ex; + }} + } + } + + char const * scanner::cached_str(unsigned begin, unsigned end) { + m_cache_result.reset(); + while (isspace(m_cache[begin]) && begin < end) + begin++; + while (begin < end && isspace(m_cache[end-1])) + end--; + for (unsigned i = begin; i < end; i++) + m_cache_result.push_back(m_cache[i]); + m_cache_result.push_back(0); + return m_cache_result.begin(); + } + +}; + diff --git a/lib/smt2scanner.h b/lib/smt2scanner.h new file mode 100644 index 000000000..d504ba3e7 --- /dev/null +++ b/lib/smt2scanner.h @@ -0,0 +1,105 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt2scanner.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2011-03-09. + +Revision History: + +--*/ +#ifndef _SMT2SCANNER_H_ +#define _SMT2SCANNER_H_ + +#include +#include"symbol.h" +#include"vector.h" +#include"rational.h" +#include"cmd_context.h" + +namespace smt2 { + + typedef cmd_exception scanner_exception; + + class scanner { + private: + bool m_interactive; + int m_spos; // position in the current line of the stream + char m_curr; // current char; + + int m_line; // line + int m_pos; // start position of the token + // data + symbol m_id; + rational m_number; + unsigned m_bv_size; + // end of data + char m_normalized[256]; +#define SCANNER_BUFFER_SIZE 1024 + char m_buffer[SCANNER_BUFFER_SIZE]; + unsigned m_bpos; + unsigned m_bend; + svector m_string; + std::istream& m_stream; + + bool m_cache_input; + svector m_cache; + svector m_cache_result; + + char curr() const { return m_curr; } + void new_line() { m_line++; m_spos = 0; } + void next(); + + public: + + enum token { + NULL_TOKEN = 0, + LEFT_PAREN = 1, + RIGHT_PAREN, + KEYWORD_TOKEN, + SYMBOL_TOKEN, + STRING_TOKEN, + INT_TOKEN, + BV_TOKEN, + FLOAT_TOKEN, + EOF_TOKEN + }; + + scanner(std::istream& stream, bool interactive = false); + + ~scanner() {} + + int get_line() const { return m_line; } + int get_pos() const { return m_pos; } + symbol const & get_id() const { return m_id; } + rational get_number() const { return m_number; } + unsigned get_bv_size() const { return m_bv_size; } + char const * get_string() const { return m_string.begin(); } + token scan(); + + token read_symbol(); + token read_quoted_symbol(); + void read_comment(); + token read_number(); + token read_string(); + token read_bv_literal(); + + void start_caching() { m_cache_input = true; m_cache.reset(); } + void stop_caching() { m_cache_input = false; } + unsigned cache_size() const { return m_cache.size(); } + void reset_cache() { m_cache.reset(); } + char const * cached_str(unsigned begin, unsigned end); + }; + +}; + +#endif /* _SCANNER_H_ */ + diff --git a/lib/smt_almost_cg_table.cpp b/lib/smt_almost_cg_table.cpp new file mode 100644 index 000000000..93f136aad --- /dev/null +++ b/lib/smt_almost_cg_table.cpp @@ -0,0 +1,127 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_almost_cg_table.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2009-03-06. + +Revision History: + +--*/ + +#include"smt_almost_cg_table.h" + +namespace smt { + + inline unsigned almost_cg_table::cg_hash::arg_hash(enode * n, unsigned idx) const { + enode * arg = n->get_arg(idx)->get_root(); + if (arg == m_r1 || arg == m_r2) + return 17; + return arg->hash(); + } + + unsigned almost_cg_table::cg_hash::operator()(enode * n) const { + unsigned num_args = n->get_num_args(); + unsigned kind_hash = n->get_decl_id(); + if (num_args == 1) + return kind_hash; + unsigned a = 0x9e3779b9; + unsigned b = 0x9e3779b9; + unsigned c = 11; + + switch (num_args) { + case 2: + a += kind_hash; + b += arg_hash(n, 0); + c += arg_hash(n, 1); + mix(a, b, c); + return c; + case 3: + a += kind_hash; + b += arg_hash(n, 0); + c += arg_hash(n, 1); + mix(a, b, c); + c += arg_hash(n, 1); + mix(a, b, c); + return c; + default: + while (num_args >= 3) { + num_args--; + a += arg_hash(n, num_args); + num_args--; + b += arg_hash(n, num_args); + num_args--; + c += arg_hash(n, num_args); + mix(a, b, c); + } + + a += kind_hash; + switch (num_args) { + case 2: + b += arg_hash(n, 1); + __fallthrough; + case 1: + c += arg_hash(n, 0); + } + mix(a, b, c); + return c; + } + } + + bool almost_cg_table::cg_eq::operator()(enode * n1, enode * n2) const { + if (n1->get_owner()->get_decl() != n2->get_owner()->get_decl()) + return false; + unsigned num_args = n1->get_num_args(); + if (num_args != n2->get_num_args()) + return false; + for (unsigned j = 0; j < num_args; j++) { + enode * arg1 = n1->get_arg(j)->get_root(); + enode * arg2 = n2->get_arg(j)->get_root(); + if (arg1 == arg2) + continue; + if ((arg1 == m_r1 || arg1 == m_r2) && + (arg2 == m_r1 || arg2 == m_r2)) + continue; + return false; + } + return true; + } + + almost_cg_table::almost_cg_table(enode * r1, enode * r2): + m_r1(r1), + m_r2(r2), + m_table(cg_hash(m_r1, m_r2), cg_eq(m_r1, m_r2)) { + } + + void almost_cg_table::reset() { + m_region.reset(); + m_table.reset(); + } + + void almost_cg_table::insert(enode * n) { + table::entry * entry = m_table.find_core(n); + if (entry == 0) { + list * new_lst = new (m_region) list(n, 0); + m_table.insert(n, new_lst); + } + else { + list * new_lst = new (m_region) list(n, entry->get_data().m_value); + entry->get_data().m_value = new_lst; + } + } + + list * almost_cg_table::find(enode * n) { + list * result = 0; + m_table.find(n, result); + return result; + } + +}; diff --git a/lib/smt_almost_cg_table.h b/lib/smt_almost_cg_table.h new file mode 100644 index 000000000..1930d675a --- /dev/null +++ b/lib/smt_almost_cg_table.h @@ -0,0 +1,72 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_almost_cg_table.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2009-03-06. + +Revision History: + +--*/ +#ifndef _SMT_ALMOST_CG_TABLE_H_ +#define _SMT_ALMOST_CG_TABLE_H_ + +#include"smt_enode.h" +#include"map.h" + +namespace smt { + + /** + \brief An index for detecting 'almost' congruences. + We say (f t_1 ... t_n) is almost congruent to (f s_1 ... s_n) with respect to (r1,r2) iff + for all j in [1,n] j t_j = s_j or (t_j = r1 and s_j = r1) or (t_j = r1 and s_j = r2) or (t_j = r2 and s_j = r1) or (t_j = r2 and s_j = r2) + + This index is used to speedup is_ext_diseq. + */ + class almost_cg_table { + struct cg_hash { + enode * & m_r1; + enode * & m_r2; + unsigned arg_hash(enode * n, unsigned idx) const; + public: + cg_hash(enode * & r1, enode * & r2):m_r1(r1), m_r2(r2) {} + unsigned operator()(enode * n) const; + }; + + struct cg_eq { + enode * & m_r1; + enode * & m_r2; + public: + cg_eq(enode * & r1, enode * & r2):m_r1(r1), m_r2(r2) {} + bool operator()(enode * n1, enode * n2) const; + }; + + typedef map *, cg_hash, cg_eq> table; + + region m_region; + enode * m_r1; + enode * m_r2; + table m_table; + + public: + almost_cg_table(enode * r1 = 0, enode * r2 = 0); + void reset(enode * r1, enode * r2) { m_r1 = r1->get_root(); m_r2 = r2->get_root(); reset(); } + void reset(); + void insert(enode *); + void erase(enode * n) { m_table.erase(n); } + list * find(enode *); + bool empty() const { return m_table.empty(); } + }; + +}; + +#endif /* _SMT_ALMOST_CG_TABLE_H_ */ + diff --git a/lib/smt_arith.cpp b/lib/smt_arith.cpp new file mode 100644 index 000000000..82c54d543 --- /dev/null +++ b/lib/smt_arith.cpp @@ -0,0 +1,3605 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + smt_arith.cpp + +Abstract: + + Arithmetic solver for smt::solver + +Author: + + Leonardo de Moura (leonardo) 2011-06-25. + +Revision History: + +--*/ +#include"smt_arith.h" +#include"bound_propagator.h" +#include"linear_equation.h" +#include"mpq.h" +#include"mpq_inf.h" +#include"double_manager.h" +#include"small_object_allocator.h" +#include"arith_decl_plugin.h" +#include"ast_smt2_pp.h" +#include"assertion_set_strategy.h" +#include"statistics.h" +#include"lu.h" +#include"cooperate.h" +#include"model.h" + +namespace smt { + + typedef unsigned var; + + class var_set { + unsigned_vector m_var2pos; + unsigned_vector m_vars; + public: + typedef unsigned_vector::const_iterator iterator; + var_set() {} + + void reset() { + if (4 * m_vars.size() < m_var2pos.size()) { + unsigned_vector::iterator it = m_vars.begin(); + unsigned_vector::iterator end = m_vars.end(); + for (; it != end; ++it) + m_var2pos[*it] = UINT_MAX; + } + else { + m_var2pos.reset(); + } + m_vars.reset(); + } + + bool empty() const { return m_vars.empty(); } + + unsigned size() const { return m_vars.size(); } + + var operator[](unsigned idx) const { return m_vars[idx]; } + + bool contains(var x) const { + return x < m_var2pos.size() && m_var2pos[x] != UINT_MAX; + } + + void insert(var x) { + if (contains(x)) + return; + m_var2pos.reserve(x+1, UINT_MAX); + m_var2pos[x] = m_vars.size(); + m_vars.push_back(x); + SASSERT(contains(x)); + } + + void erase(var x) { + if (contains(x)) { + unsigned pos = m_var2pos[x]; + SASSERT(m_vars[pos] == x); + if (pos != m_vars.size() - 1) { + unsigned last_x = m_vars.back(); + m_var2pos[last_x] = pos; + m_vars[pos] = last_x; + } + m_vars.pop_back(); + m_var2pos[x] = UINT_MAX; + } + SASSERT(!contains(x)); + } + + iterator begin() const { return m_vars.begin(); } + + iterator end() const { return m_vars.end(); } + }; + + struct arith::imp { + typedef small_object_allocator allocator; + typedef unsynch_mpq_manager numeral_manager; + typedef unsynch_mpq_inf_manager numeral_inf_manager; + typedef svector mpq_inf_vector; + typedef numeral_buffer mpq_buffer; + typedef numeral_buffer mpq_inf_buffer; + typedef numeral_buffer mpz_buffer; + typedef svector var_buffer; + typedef ptr_vector equations; + typedef lu lu_double; + typedef lu lu_mpq; + typedef numeral_inf_manager::inf_kind inf_kind; + + class double_vector : public svector { + public: + typedef double numeral; + typedef double_manager manager; + }; + +#define null_var UINT_MAX + + // "handy" infinitesimals +#define pos_inf numeral_inf_manager::POS +#define neg_inf numeral_inf_manager::NEG +#define zero_inf numeral_inf_manager::ZERO + + struct ineq_cnstr { + var m_x; + mpq m_k; + double m_approx_k; + bool m_lower; + atom_id m_atom; + }; + + typedef svector ineq_cnstrs; + typedef unsigned_vector ineq_cnstr_ids; + + // track the position of a variable in a equation + struct eq_occ { + unsigned m_idx; // equation index + unsigned m_pos; // position in the equation + eq_occ(unsigned idx, unsigned pos):m_idx(idx), m_pos(pos) {} + }; + + typedef svector eq_occs; + + ast_manager & m; + arith_util m_util; + expr_ref_vector m_var2expr; + obj_map m_expr2var; + + allocator m_allocator; + numeral_manager m_num_manager; + numeral_inf_manager m_num_inf_manager; + double_manager m_double_manager; + equations m_equations; + vector m_eq_occs; + lu_mpq m_lu_mpq; + lu_double m_lu_double; + char_vector m_eliminated; + unsigned_vector m_basic; // var -> eq_idx, UINT_MAX if not basic, otherwise it is a value in [0, m_equations.size()) + unsigned_vector m_inv_basic; // eq_idx -> var inverse of m_basic + mpq_inf_buffer m_values; + mpq_inf_buffer m_old_values; + double_vector m_approx_values; + double_vector m_old_approx_values; + var_set m_updated; + var_set m_approx_updated; + var_set m_bad_vars; + var_set m_int_bad_vars; + linear_equation_manager m_eq_manager; + bound_propagator m_asserted_bounds; + bound_propagator m_bounds; + unsigned m_conflict_eq_idx; // != UINT_MAX if the corresponding get_row(m_conflict_eq_idx) is in conflict. + ineq_cnstrs m_ineq_cnstrs; + ineq_cnstr_ids m_atom2ineq_cnstr; // atom idx to ineq cnstr idx + vector m_var_occs; // ineq_cnstrs that constain a variable + mpq_buffer m_mpq_buffer; + mpz_buffer m_mpz_buffer; + var_buffer m_var_buffer; + + typedef std::pair elim_var_info; + typedef svector elim_vars; + + elim_vars m_elim_vars; + + volatile bool m_cancel; + + // Modes of operation for mk_feasible + bool m_use_approx; // use approximated values + bool m_use_asserted; // consider only asserted bounds + + bool m_approx_forbidden; // disable approx mode when approximation gets lost trying to find feasible solution. + + // statistics + unsigned m_eliminated_vars; + unsigned m_lu_factorizations; + unsigned m_pivots; + unsigned m_approx_pivots; + unsigned m_max_l; + unsigned m_max_u; + unsigned m_branches; + unsigned m_cuts; + + // configuration + unsigned m_refactor_threshold; + unsigned m_blands_rule_factor; + unsigned m_approx_threshold_factor; // factor*num_eqs == max pivots to try when using approximated + bool m_elim_vars_enabled; + bool m_elim_int_vars_enabled; + bool m_branch_vars_only; // do not branch on auxiliary variables + bool m_cuts_enabled; + + // backtracking + struct scope { + bool m_bad_vars_empty; + unsigned m_conflict_eq_idx_old; + }; + + svector m_scopes; + + // Data-structures for branch & bound + unsigned_vector m_bb_var; + char_vector m_bb_lower; + char_vector m_bb_first; + mpq_buffer m_bb_k; + + + // ----------------------- + // + // Basic + // + // ----------------------- + imp(ast_manager & _m, params_ref const & p): + m(_m), + m_util(m), + m_var2expr(m), + m_allocator("arith"), + m_num_inf_manager(m_num_manager), + m_double_manager(p), + m_lu_mpq(m_num_manager, p), + m_lu_double(m_double_manager, p), + m_values(m_num_inf_manager), + m_old_values(m_num_inf_manager), + m_eq_manager(m_num_manager, m_allocator), + m_asserted_bounds(m_num_manager, m_allocator, p), + m_bounds(m_num_manager, m_allocator, p), + m_mpq_buffer(m_num_manager), + m_mpz_buffer(m_num_manager), + m_bb_k(m_num_manager) { + m_approx_forbidden = false; + m_cancel = false; + m_conflict_eq_idx = UINT_MAX; + updt_params_core(p); + reset_statistics(); + m_use_approx = false; + set_mode_core(false, false); + } + + ~imp() { + del_equations(); + del_ineq_cnstrs(); + del_elim_vars(); + } + + void updt_params(params_ref const & p) { + m_double_manager.updt_params(p); + m_asserted_bounds.updt_params(p); + m_bounds.updt_params(p); + m_lu_mpq.updt_params(p); + m_lu_double.updt_params(p); + updt_params_core(p); + } + + void updt_params_core(params_ref const & p) { + m_refactor_threshold = p.get_uint(":lu-refactor", 20); + m_blands_rule_factor = p.get_uint(":blands-rule-factor", 4); + m_elim_vars_enabled = p.get_bool(":arith-elim-vars", false); + m_elim_int_vars_enabled = p.get_bool(":arith-elim-int-vars", true); + m_approx_threshold_factor = p.get_uint(":approx-max-pivots", 32); + m_branch_vars_only = p.get_bool(":arith-branch-vars-only", true); + m_cuts_enabled = p.get_bool(":arith-cuts", false); + } + + void set_cancel(bool f) { + m_cancel = f; + } + + void reset_statistics() { + m_eliminated_vars = 0; + m_lu_factorizations = 0; + m_pivots = 0; + m_approx_pivots = 0; + m_max_l = 0; + m_max_u = 0; + m_branches = 0; + m_cuts = 0; + m_bounds.reset_statistics(); + } + + void collect_statistics(statistics & st) const { + st.update("elim tableau vars", m_eliminated_vars); + st.update("LU factorizations", m_lu_factorizations); + st.update("pivots", m_pivots); + st.update("approx pivots", m_approx_pivots); + st.update("max L", m_max_l); + st.update("max U", m_max_u); + st.update("bb decisions", m_branches); + st.update("cuts", m_cuts); + m_bounds.collect_statistics(st); + } + + void checkpoint() { + if (m_cancel) + throw strategy_exception(STE_CANCELED_MSG); + cooperate("arith"); + } + + unsigned scope_lvl() const { + return m_scopes.size(); + } + + void del_equations() { + equations::iterator it = m_equations.begin(); + equations::iterator end = m_equations.end(); + for (; it != end; ++it) + m_eq_manager.del(*it); + } + + void del_ineq_cnstrs() { + ineq_cnstrs::iterator it = m_ineq_cnstrs.begin(); + ineq_cnstrs::iterator end = m_ineq_cnstrs.end(); + for (; it != end; ++it) { + m_num_manager.del(it->m_k); + } + } + + void del_elim_vars() { + elim_vars::iterator it = m_elim_vars.begin(); + elim_vars::iterator end = m_elim_vars.end(); + for (; it != end; ++it) { + m_eq_manager.del(it->second); + } + } + + bool is_int(var x) const { + return m_asserted_bounds.is_int(x); + } + + bool is_unconstrained(var x) const { + return + !m_asserted_bounds.has_lower(x) && + !m_asserted_bounds.has_upper(x) && + m_var_occs[x].empty(); + } + + bool was_eliminated(var x) const { + return m_eliminated[x] != 0; + } + + bool basic(var x) const { + return m_basic[x] != UINT_MAX; + } + + bool nonbasic(var x) const { + return m_basic[x] == UINT_MAX; + } + + unsigned num_vars() const { + return m_var2expr.size(); + } + + bound_propagator const & bp() const { return m_use_asserted ? m_asserted_bounds : m_bounds; } + + bound_propagator & bp() { return m_use_asserted ? m_asserted_bounds : m_bounds; } + + bool has_lower(var x) const { return bp().has_lower(x); } + + bool has_upper(var x) const { return bp().has_upper(x); } + +#define MK_LOWER_METHOD(NAME, OP, NO_BOUND_RESULT) \ + bool NAME(var x) const { \ + if (!bp().has_lower(x)) \ + return NO_BOUND_RESULT; \ + if (m_use_approx) { \ + return m_double_manager.OP(m_approx_values[x], bp().approx_lower(x)); \ + } \ + else { \ + bool strict; \ + mpq const & l = bp().lower(x, strict); \ + return m_num_inf_manager.OP(m_values[x], l, strict ? pos_inf : zero_inf); \ + } \ + } + +#define MK_UPPER_METHOD(NAME, OP, NO_BOUND_RESULT) \ + bool NAME(var x) const { \ + if (!bp().has_upper(x)) \ + return NO_BOUND_RESULT; \ + if (m_use_approx) { \ + return m_double_manager.OP(m_approx_values[x], bp().approx_upper(x)); \ + } \ + else { \ + bool strict; \ + mpq const & u = bp().upper(x, strict); \ + return m_num_inf_manager.OP(m_values[x], u, strict ? neg_inf : zero_inf); \ + } \ + } + + MK_LOWER_METHOD(at_lower, eq, false); + MK_LOWER_METHOD(below_lower, lt, false); + MK_LOWER_METHOD(above_lower, gt, true); + MK_UPPER_METHOD(at_upper, eq, false); + MK_UPPER_METHOD(above_upper, gt, false); + MK_UPPER_METHOD(below_upper, lt, true); + + void save_value(var x) { + SASSERT(!m_use_approx); + if (!m_updated.contains(x)) { + m_updated.insert(x); + m_num_inf_manager.set(m_old_values[x], m_values[x]); + } + } + + void save_approx_value(var x) { + SASSERT(m_use_approx); + if (!m_approx_updated.contains(x)) { + m_approx_updated.insert(x); + m_old_approx_values[x] = m_approx_values[x]; + } + } + + bool int_feasible(var x) const { + return !is_int(x) || m_num_inf_manager.is_int(m_values[x]); + } + + void check_int(var x) { + if (!int_feasible(x)) + m_int_bad_vars.insert(x); + } + + void set_value(var x, mpq_inf const & val) { + SASSERT(!m_use_approx); + save_value(x); + m_num_inf_manager.set(m_values[x], val); + check_int(x); + } + + void set_value(var x, mpq const & val, inf_kind k) { + SASSERT(!m_use_approx); + save_value(x); + m_num_inf_manager.set(m_values[x], val, k); + check_int(x); + } + + void set_value(var x, double val) { + SASSERT(m_use_approx); + save_approx_value(x); + m_approx_values[x] = val; + } + + void acc_value(var x, mpq_inf const & delta) { + SASSERT(!m_use_approx); + save_value(x); + m_num_inf_manager.add(m_values[x], delta, m_values[x]); + check_int(x); + } + + void acc_value(var x, double val) { + SASSERT(m_use_approx); + save_approx_value(x); + m_approx_values[x] += val; + if (m_double_manager.is_zero(m_approx_values[x])) + m_approx_values[x] = 0.0; + } + + /** + \brief Assign x to its lower bound + */ + void lower2value(var x) { + if (m_use_approx) { + set_value(x, bp().approx_lower(x)); + } + else { + bool strict; + mpq const & l = bp().lower(x, strict); + set_value(x, l, strict ? pos_inf : zero_inf); + } + } + + /** + \brief Assign x to its upper bound + */ + void upper2value(var x) { + if (m_use_approx) { + set_value(x, bp().approx_upper(x)); + } + else { + bool strict; + mpq const & u = bp().upper(x, strict); + set_value(x, u, strict ? neg_inf : zero_inf); + } + } + + // ----------------------- + // + // Model Generation + // + // ----------------------- + + void update_epsilon(mpq_inf const & l, mpq_inf const & u, mpq & epsilon) { + numeral_manager & nm = m_num_manager; + if (nm.lt(l.first, u.first) && nm.gt(l.second, u.second)) { + mpq new_epsilon; + mpq tmp; + nm.sub(u.first, l.first, new_epsilon); + nm.sub(l.second, u.second, tmp); + nm.div(new_epsilon, tmp, new_epsilon); + if (nm.lt(new_epsilon, epsilon)) + nm.swap(new_epsilon, epsilon); + nm.del(new_epsilon); + nm.del(tmp); + } + SASSERT(nm.is_pos(epsilon)); + } + + void compute_epsilon(mpq & epsilon) { + m_num_manager.set(epsilon, 1); + mpq_inf v; + mpq k; + unsigned num = num_vars(); + for (unsigned x = 0; x < num; x++) { + bool strict; unsigned ts; + if (m_bounds.lower(x, k, strict, ts)) { + m_num_inf_manager.set(v, k, strict ? pos_inf : zero_inf); + update_epsilon(v, m_values[x], epsilon); + } + if (m_bounds.upper(x, k, strict, ts)) { + m_num_inf_manager.set(v, k, strict ? neg_inf : zero_inf); + update_epsilon(m_values[x], v, epsilon); + } + } + } + + void update_elim_vars_assignment() { + mpq_inf val; + mpq_inf tmp; + mpq inv_a; + unsigned i = m_elim_vars.size(); + while (i > 0) { + --i; + elim_var_info & info = m_elim_vars[i]; + m_num_inf_manager.reset(val); + var x = info.first; + unsigned x_pos = UINT_MAX; + linear_equation const & eq = *(info.second); + unsigned sz = eq.size(); + for (unsigned j = 0; j < sz; j++) { + var x_j = eq.x(j); + if (x_j == x) { + x_pos = j; + continue; + } + m_num_inf_manager.mul(m_values[x_j], eq.a(j), tmp); + m_num_inf_manager.add(val, tmp, val); + } + SASSERT(x_pos != UINT_MAX); + m_num_manager.set(inv_a, eq.a(x_pos)); + m_num_manager.inv(inv_a); + m_num_inf_manager.mul(val, inv_a, val); + m_num_inf_manager.neg(val); + m_num_inf_manager.set(m_values[x], val); + } + m_num_inf_manager.del(val); + m_num_inf_manager.del(tmp); + m_num_manager.del(inv_a); + } + + void mk_model(model * md) { + // + update_elim_vars_assignment(); + numeral_manager & nm = m_num_manager; + mpq epsilon; + mpq val; + compute_epsilon(epsilon); + for (unsigned x = 0; x < num_vars(); x++) { + expr * t = m_var2expr.get(x); + if (t == 0) + continue; + if (!is_uninterp_const(t)) + continue; + nm.set(val, m_values[x].first); + nm.addmul(val, m_values[x].second, epsilon, val); + func_decl * d = to_app(t)->get_decl(); + md->register_decl(d, m_util.mk_numeral(rational(val), is_int(x))); + } + nm.del(epsilon); + nm.del(val); + } + + // ----------------------- + // + // Backtracking + // + // ----------------------- + + void push() { + m_scopes.push_back(scope()); + scope & s = m_scopes.back(); + s.m_bad_vars_empty = m_bad_vars.empty(); + s.m_conflict_eq_idx_old = m_conflict_eq_idx; + m_bounds.push(); + m_asserted_bounds.push(); + SASSERT(m_bounds.scope_lvl() == scope_lvl()); + SASSERT(m_asserted_bounds.scope_lvl() == scope_lvl()); + } + + void pop(unsigned num_scopes) { + unsigned lvl = scope_lvl(); + SASSERT(num_scopes <= lvl); + unsigned new_lvl = lvl - num_scopes; + scope & s = m_scopes[new_lvl]; + bool bad_vars_empty = s.m_bad_vars_empty; + m_conflict_eq_idx = s.m_conflict_eq_idx_old; + m_scopes.shrink(new_lvl); + m_bounds.pop(num_scopes); + m_asserted_bounds.pop(num_scopes); + if (!bad_vars_empty) + reset_bad_vars(); + SASSERT(m_bounds.scope_lvl() == scope_lvl()); + SASSERT(m_asserted_bounds.scope_lvl() == scope_lvl()); + } + + // ----------------------- + // + // Compilation + // + // ----------------------- + + var mk_var(expr * t) { + var x; + if (t != 0) { + if (m_util.is_to_real(t)) + t = to_app(t)->get_arg(0); + if (m_expr2var.find(t, x)) + return x; + } + x = m_var2expr.size(); + m_var2expr.push_back(t); + bool is_int = true; + if (t != 0) { + m_expr2var.insert(t, x); + is_int = m_util.is_int(t); + } + m_eliminated.push_back(false); + m_basic.push_back(UINT_MAX); + m_values.push_back(mpq_inf()); + m_old_values.push_back(mpq_inf()); + m_approx_values.push_back(0.0); + m_old_approx_values.push_back(0.0); + m_asserted_bounds.mk_var(x, is_int); + m_bounds.mk_var(x, is_int); + m_var_occs.push_back(ineq_cnstr_ids()); + return x; + } + + void expr2lp(expr * t, mpq_buffer & as, var_buffer & xs) { + mpq c_mpq_val; + if (m_util.is_add(t)) { + rational c_val; + unsigned num = to_app(t)->get_num_args(); + for (unsigned i = 0; i < num; i++) { + expr * mon = to_app(t)->get_arg(i); + expr * c, * x; + if (m_util.is_mul(mon, c, x) && m_util.is_numeral(c, c_val)) { + m_num_manager.set(c_mpq_val, c_val.to_mpq()); + as.push_back(c_mpq_val); + xs.push_back(mk_var(x)); + } + else { + as.push_back(mpq(1)); + xs.push_back(mk_var(mon)); + } + } + } + else { + as.push_back(mpq(1)); + xs.push_back(mk_var(t)); + } + m_num_manager.del(c_mpq_val); + } + + var mk_lp(expr * t) { + if (m_util.is_to_real(t)) + t = to_app(t)->get_arg(0); + var x; + if (m_expr2var.find(t, x)) + return x; + + x = mk_var(t); + if (m_util.is_add(t)) { + m_mpq_buffer.reset(); + m_var_buffer.reset(); + expr2lp(t, m_mpq_buffer, m_var_buffer); + m_mpq_buffer.push_back(mpq(-1)); + m_var_buffer.push_back(x); + linear_equation * new_eq = m_eq_manager.mk(m_mpq_buffer.size(), m_mpq_buffer.c_ptr(), m_var_buffer.c_ptr(), true); + SASSERT(new_eq); + unsigned eq_idx = m_equations.size(); + m_basic[x] = eq_idx; + m_inv_basic.push_back(x); + m_equations.push_back(new_eq); + SASSERT(m_inv_basic.size() == m_equations.size()); + SASSERT(basic(x)); + } + return x; + } + + void mk_ineq_cnstr(var x, mpq const & k, bool lower, atom_id p) { + unsigned cnstr_id = m_ineq_cnstrs.size(); + m_ineq_cnstrs.push_back(ineq_cnstr()); + ineq_cnstr & cnstr = m_ineq_cnstrs.back(); + cnstr.m_x = x; + m_num_manager.set(cnstr.m_k, k); + cnstr.m_approx_k = m_num_manager.get_double(k); + cnstr.m_lower = lower; + cnstr.m_atom = p; + m_atom2ineq_cnstr.reserve(p+1, UINT_MAX); + m_atom2ineq_cnstr[p] = cnstr_id; + m_var_occs[x].push_back(cnstr_id); + } + + void mk_ineq(expr * t, bool neg, atom_id p) { + TRACE("mk_ineq", tout << "mk_ineq, neg: " << neg << ", p: " << p << "\n" << mk_ismt2_pp(t, m) << "\n";); + SASSERT(m_util.is_le(t) || m_util.is_ge(t)); + SASSERT(!neg || p == null_atom_id); + + bool strict = false; + bool le; + if (m_util.is_le(t)) { + if (neg) { + le = false; + strict = true; + } + else { + le = true; + } + } + else { + SASSERT(m_util.is_ge(t)); + if (neg) { + le = true; + strict = true; + } + else { + le = false; + } + } + expr * lhs = to_app(t)->get_arg(0); + expr * rhs = to_app(t)->get_arg(1); + + SASSERT(m_util.is_numeral(rhs)); + rational c; + m_util.is_numeral(rhs, c); + + var x = mk_lp(lhs); + mpq c_prime; + m_num_manager.set(c_prime, c.to_mpq()); + TRACE("mk_ineq", tout << "le: " << le << ", strict: " << strict << ", c: " << c << ", c_prime: " << + m_num_manager.to_string(c_prime) << "\n";); + + if (p == null_atom_id) { + // inequality is an axiom + if (le) + assert_upper(x, c_prime, strict); + else + assert_lower(x, c_prime, strict); + } + else { + SASSERT(!strict); + mk_ineq_cnstr(x, c_prime, !le, p); + } + m_num_manager.del(c_prime); + } + + void assert_upper(var x, mpq const & k, bool strict) { + m_bounds.assert_upper(x, k, strict); + m_asserted_bounds.assert_upper(x, k, strict); + if (above_upper(x)) + m_bad_vars.insert(x); + } + + void assert_lower(var x, mpq const & k, bool strict) { + m_bounds.assert_lower(x, k, strict); + m_asserted_bounds.assert_lower(x, k, strict); + if (below_lower(x)) + m_bad_vars.insert(x); + } + + // ----------------------- + // + // Approximate <-> Precise + // + // ----------------------- + + /** + \brief "Copy" nonbasic m_values to m_approx_values + */ + void mpq2double_nonbasic_values() { + unsigned num = num_vars(); + for (unsigned x = 0; x < num; x++) { + if (nonbasic(x)) + m_approx_values[x] = m_num_inf_manager.get_double(m_values[x]); + } + } + + /** + \brief "Copy" nonbasic m_approx_values to m_values. + It actually checks whether m_approx_values[x] is at lower or uppers, + and copy the corresponding value. + */ + void double2mpq_nonbasic_values() { + unsigned num = num_vars(); + for (unsigned x = 0; x < num; x++) { + if (nonbasic(x)) { + if (at_lower(x)) { + bool strict; + mpq const & l = bp().lower(x, strict); + m_num_inf_manager.set(m_values[x], l, strict ? pos_inf : zero_inf); + } + else if (at_upper(x)) { + bool strict; + mpq const & u = bp().upper(x, strict); + m_num_inf_manager.set(m_values[x], u, strict ? neg_inf : zero_inf); + } + else { + m_num_inf_manager.set(m_values[x], 0); + } + } + } + } + + /** + \brief Set the value of the basic variables using the value of the non-basic ones. + */ + template + void update_basic_values(LU & _lu, Values & values, NumInfMng & infm) { + unsigned num = m_equations.size(); + typename LU::dense_vector & row = _lu.get_tmp_row(num_vars()); + typename NumInfMng::numeral val; + typename NumInfMng::numeral aux; + typename LU::manager & nm = _lu.m(); + for (unsigned eq_idx = 0; eq_idx < num; eq_idx++) { + var x_b = m_inv_basic[eq_idx]; + row.reset(); + get_row(_lu, eq_idx, row, true, m_use_asserted, true); + infm.reset(val); + typename LU::dense_vector::iterator it = row.begin_non_zeros(); + typename LU::dense_vector::iterator end = row.end_non_zeros(); + for (; it != end; ++it) { + var x_n = *it; + if (nm.is_zero(row[x_n])) + continue; + infm.mul(values[x_n], row[x_n], aux); + infm.add(val, aux, val); + } + if (infm.is_zero(val)) { + infm.set(values[x_b], 0); + } + else { + infm.neg(val); + infm.swap(values[x_b], val); + } + } + infm.del(val); + infm.del(aux); + } + + void update_mpq_basic_values() { + update_basic_values(m_lu_mpq, m_values, m_num_inf_manager); + } + + void update_double_basic_values() { + update_basic_values(m_lu_double, m_approx_values, m_double_manager); + } + + /** + \brief Faster version of update_basic_values. Instead of extracting rows, + I comput the Column C = N*V_n, where N is the matrix containing the coefficient of the non-basic variables, + and V_n is the assignment of the non_basic variables. Then, I solve Ax = C + The resultant column x is the assignment for the basic variables. + + The assignment is a mpq_inf, so the update is performed in two steps. + inf == true, the infinitesimal part is updated, + inf == false, the rational part is updated + */ + void fast_update_basic_values_mpq(bool inf) { + lu_mpq::dense_vector & C = m_lu_mpq.get_tmp_col(); + C.reset(); + mpq aux; + // 1. Compute column C' + unsigned num = m_equations.size(); + for (unsigned eq_idx = 0; eq_idx < num; eq_idx++) { + mpq & c_i = C.get(eq_idx); + linear_equation const & eq = *(m_equations[eq_idx]); + unsigned sz = eq.size(); + for (unsigned i = 0; i < sz; i++) { + var x = eq.x(i); + if (basic(x)) + continue; + if (inf) + m_num_manager.mul(eq.a(i), m_values[x].second, aux); + else + m_num_manager.mul(eq.a(i), m_values[x].first, aux); + m_num_manager.add(c_i, aux, c_i); + } + } + TRACE("update_basic_values", tout << "inf: " << inf << " "; C.display_non_zeros(tout); tout << "\n";); + // 2. Solve + m_lu_mpq.solve_Ax_eq_y(C); + C.elim_zeros(); + // 3. Update basic variable assignment + for (unsigned eq_idx = 0; eq_idx < num; eq_idx++) { + var x_b = m_inv_basic[eq_idx]; + mpq & c_i = C.get(eq_idx); + m_num_manager.neg(c_i); + TRACE("update_basic_values", tout << "inf: " << inf << " x" << x_b << " " << m_num_manager.to_string(c_i) << "\n";); + if (inf) + m_num_manager.swap(m_values[x_b].second, c_i); + else + m_num_manager.swap(m_values[x_b].first, c_i); + } + m_num_manager.del(aux); + } + + void fast_update_mpq_basic_values() { + fast_update_basic_values_mpq(false); + fast_update_basic_values_mpq(true); + SASSERT(check_eqs_satisfied_core()); // use core to make sure it will be checked even when m_use_approx = false + } + + void fast_update_double_basic_values() { + lu_double::dense_vector & C = m_lu_double.get_tmp_col(); + // 1. Compute column C' + unsigned num = m_equations.size(); + for (unsigned eq_idx = 0; eq_idx < num; eq_idx++) { + double & c_i = C.get(eq_idx); + c_i = 0.0; + linear_equation const & eq = *(m_equations[eq_idx]); + unsigned sz = eq.size(); + for (unsigned i = 0; i < sz; i++) { + var x = eq.x(i); + if (basic(x)) + continue; + c_i += eq.approx_a(i) * m_approx_values[x]; + } + } + TRACE("update_basic_values", C.display_non_zeros(tout); tout << "\n";); + // 2. Solve + m_lu_double.solve_Ax_eq_y(C); + C.elim_zeros(); + // 3. Update basic variable assignment + for (unsigned eq_idx = 0; eq_idx < num; eq_idx++) { + var x_b = m_inv_basic[eq_idx]; + m_approx_values[x_b] = -C[eq_idx]; + } + } + + void reset_bad_vars() { + m_bad_vars.reset(); + unsigned num = num_vars(); + for (var x = 0; x < num; x++) { + if (below_lower(x) || above_upper(x)) + m_bad_vars.insert(x); + } + } + + void reset_int_bad_vars() { + m_int_bad_vars.reset(); + unsigned num = num_vars(); + for (var x = 0; x < num; x++) { + check_int(x); + } + } + + void set_mode_core(bool use_approx, bool use_asserted) { + if (m_use_approx != use_approx) { + if (use_approx) { + init_lu_double(); + mpq2double_nonbasic_values(); + // update_double_basic_values(); + fast_update_double_basic_values(); + } + else { + init_lu_mpq(); + double2mpq_nonbasic_values(); + // update_mpq_basic_values(); + fast_update_mpq_basic_values(); + } + } + m_approx_updated.reset(); + m_updated.reset(); + m_use_approx = use_approx; + m_use_asserted = use_asserted; + reset_bad_vars(); + if (!m_use_approx) + reset_int_bad_vars(); + } + + void set_mode(bool use_approx, bool use_asserted) { + if (use_approx == m_use_approx && use_asserted == m_use_asserted) + return; + set_mode_core(use_approx, use_asserted); + TRACE("set_mode", display(tout);); + } + + // ----------------------- + // + // Simplex & make_feasible + // + // ----------------------- + + template + void update_lu_stats(LU const & _lu) { + unsigned l_sz = _lu.L_size(); + unsigned u_sz = _lu.U_size(); + if (l_sz > m_max_l) + m_max_l = l_sz; + if (u_sz > m_max_u) + m_max_u = u_sz; + } + + template + void init_lu_core(LU & _lu) { + m_lu_factorizations++; + TRACE("arith_lu", display(tout);); + typename LU::numeral a; + unsigned num_eqs = m_equations.size(); + _lu.init(num_eqs); + for (unsigned i = 0; i < num_eqs; i++) { +#ifdef Z3DEBUG + unsigned num_entries_added = 0; +#endif + linear_equation const & eq = *(m_equations[i]); + unsigned sz = eq.size(); + for (unsigned j = 0; j < sz; j++) { + var x = eq.x(j); + if (basic(x)) { + eq.get_a(_lu.m(), j, a); + _lu.add_entry(a, m_basic[x]); + DEBUG_CODE(num_entries_added++;); + } + } + SASSERT(num_entries_added <= num_eqs); + _lu.end_row(); + } + _lu.m().del(a); + _lu.fact(); + update_lu_stats(_lu); + TRACE("init_lu", display(tout);); + } + + void init_lu_double() { init_lu_core(m_lu_double); } + void init_lu_mpq() { init_lu_core(m_lu_mpq); } + + void init_lu() { + if (m_use_approx) + init_lu_double(); + else + init_lu_mpq(); + } + + // Extract a column from m_equations. The result is stored in c. + template + void get_column(var j, Vector & c) { + TRACE("get_column", tout << "get_column x" << j << "\n"; display_eqs(tout);); + typename Vector::manager & m = c.m(); + c.reset(); + eq_occs const & occs = m_eq_occs[j]; + for (unsigned i = 0; i < occs.size(); i++) { + eq_occ const & occ = occs[i]; + unsigned eq_idx = occ.m_idx; + linear_equation const & eq = *(m_equations[eq_idx]); + SASSERT(eq.x(occ.m_pos) == j); + eq.get_a(m, occ.m_pos, c.get(eq_idx)); + } + TRACE("get_column", tout << "result: "; c.display_non_zeros(tout); tout << "\n";); + } + + // Extract a non basic column. The result is stored in c. + template + void get_nonbasic_column(LU & _lu, var j, typename LU::dense_vector & c) { + get_column(j, c); + _lu.solve_Ax_eq_y(c); + } + + void get_nonbasic_column_double(var j, lu_double::dense_vector & c) { get_nonbasic_column(m_lu_double, j, c); } + void get_nonbasic_column_mpq(var j, lu_mpq::dense_vector & c) { get_nonbasic_column(m_lu_mpq, j, c); } + + // Get the linear combination needed for extracting a row. + template + void get_lin_comb_for_row(LU & _lu, unsigned eq_idx, typename LU::dense_vector & r) { + typename LU::numeral one(1); + SASSERT(eq_idx < m_equations.size()); + r.reset(); + _lu.m().set(r.get(eq_idx), one); + _lu.solve_xA_eq_y(r); + } + + + // Apply the given linear combination to the equaltions in m_equations, and store + // the result in r. + // If include_fixed = false, then fixed variables are ignored. + // If only_nonbasic = true, then only non-basic variables are considered. + // If use_asserted = true, then m_asserted_bounds is used for detecting fixed variables, otherwise m_bounds is used. + template + void extract_row(Vector const & lin_comb, Vector & r, bool only_nonbasic = true, bool use_asserted = false, bool include_fixed = false) const { + r.reset(); + bound_propagator const & bp = use_asserted ? m_asserted_bounds : m_bounds; + typename Vector::manager & m = lin_comb.m(); + typename Vector::numeral a; + typename Vector::iterator it = lin_comb.begin_non_zeros(); + typename Vector::iterator end = lin_comb.end_non_zeros(); + for (; it != end; ++it) { + unsigned eq_idx = *it; + typename Vector::numeral const & b = lin_comb[eq_idx]; + if (m.is_zero(b)) + continue; + linear_equation const & eq = *(m_equations[eq_idx]); + unsigned sz = eq.size(); + for (unsigned i = 0; i < sz; i++) { + var x_i = eq.x(i); + if (only_nonbasic && basic(x_i)) + continue; + if (!include_fixed && bp.is_fixed(x_i)) + continue; + eq.get_a(m, i, a); + typename Vector::numeral & r_i = r.get(x_i); + m.addmul(r_i, a, b, r_i); + } + } + r.elim_zeros(); + m.del(a); + } + + // Get the non-basic variables occurring in the given row of B^{-1}N. + template + void get_row(LU & _lu, unsigned eq_idx, typename LU::dense_vector & r, + bool only_nonbasic = true, bool use_asserted = false, bool include_fixed = false) { + TRACE("get_row", tout << "original: "; m_eq_manager.display(tout, *(m_equations[eq_idx])); tout << "\n";); + + typename LU::dense_vector & lc = _lu.get_tmp_vector(); + get_lin_comb_for_row(_lu, eq_idx, lc); + extract_row(lc, r, only_nonbasic, use_asserted, include_fixed); + + TRACE("get_row", + tout << "eq " << eq_idx << "\n"; + tout << "linear combination: "; r.display_non_zeros(tout); tout << "\n";; + tout << "result: "; r.display_pol(tout); tout << "\n";); + } + + void get_row_double(unsigned eq_idx, lu_double::dense_vector & r, + bool only_nonbasic = true, bool use_asserted = false, bool include_fixed = false) { + get_row(m_lu_double, eq_idx, r, only_nonbasic, use_asserted, include_fixed); + } + void get_row_mpq(unsigned eq_idx, lu_mpq::dense_vector & r, + bool only_nonbasic = true, bool use_asserted = false, bool include_fixed = false) { + get_row(m_lu_mpq, eq_idx, r, only_nonbasic, use_asserted, include_fixed); + } + + void tst_extract_non_basic_columns(std::ostream & out) { + unsigned sz = m_lu_double.size(); + lu_double::dense_vector v(m_double_manager, sz); + out << "columns:\n"; + for (var x = 0; x < num_vars(); x++) { + if (basic(x) || was_eliminated(x)) + continue; + v.reset(); + get_nonbasic_column_double(x, v); + out << "x" << x << ": "; + v.display(out); + out << "\n"; + } + } + + void tst_extract_rows(std::ostream & out) { + lu_double::dense_vector v(m_double_manager, num_vars()); + out << "rows:\n"; + unsigned num_eqs = m_equations.size(); + for (unsigned i = 0; i < num_eqs; i++) { + get_row_double(i, v); + out << "eq " << i << ": "; + v.display_pol(out); + out << "\n"; + } + } + + template + void pivot(LU & _lu, var x_b, var x_n) { + if (m_use_approx) + m_approx_pivots++; + else + m_pivots++; + SASSERT(basic(x_b)); + SASSERT(!basic(x_n)); + unsigned eq_idx = m_basic[x_b]; + SASSERT(m_inv_basic[eq_idx] == x_b); + unsigned normalized_threshold = std::min((m_equations.size()/10) + 1, m_refactor_threshold); +#if 1 + // TODO: incremental update is producing too much imprecision when using floating point numbers. + // So: I'm disabling it for now. + if (!_lu.m().precise()) normalized_threshold = 0; +#endif + if (_lu.get_num_replacements() < normalized_threshold) { + try { + typename LU::dense_vector & c = _lu.get_tmp_col(); + get_column(x_n, c); + TRACE("pivot", tout << "column x" << m_basic[x_b] << " "; c.display_non_zeros(tout); tout << "\n";); + _lu.replace_column(m_basic[x_b], c); + update_lu_stats(_lu); + m_basic[x_b] = UINT_MAX; + m_basic[x_n] = eq_idx; + m_inv_basic[eq_idx] = x_n; + TRACE("pivot_bug", display(tout);); + CASSERT("arith", !_lu.m().precise() || check_lu()); + return; + } + catch (lu_exception) { + IF_VERBOSE(ST_VERBOSITY_LVL, verbose_stream() << "(arith-approx-failed)\n";); + } + } + // refactor from scratch + m_basic[x_b] = UINT_MAX; + m_basic[x_n] = eq_idx; + m_inv_basic[eq_idx] = x_n; + init_lu_core(_lu); + CASSERT("arith", !_lu.m().precise() || check_lu()); + } + + void pivot_double(var x_b, var x_n) { pivot(m_lu_double, x_b, x_n); } + void pivot_mpq(var x_b, var x_n) { pivot(m_lu_mpq, x_b, x_n); } + + template + void tst_random_pivoting(LU & _lu, unsigned num) { + TRACE("random_pivoting", display_eqs(tout);); + init_lu_core(_lu); + random_gen rgen; + + typename LU::numeral small(10000); + _lu.m().inv(small); + typename LU::numeral neg_small; + _lu.m().set(neg_small, small); + _lu.m().neg(neg_small); + + typename LU::dense_vector row(_lu.m(), num_vars()); + unsigned num_eqs = m_equations.size(); + for (unsigned i = 0; i < num; i++) { + verbose_stream() << "+"; verbose_stream().flush(); + // select random equation/row + TRACE("random_pivoting", display_basic(tout); display_LU(tout, _lu);); + unsigned eq_idx = rgen() % num_eqs; + var x_b = m_inv_basic[eq_idx]; + get_row(_lu, eq_idx, row); + TRACE("random_pivoting", tout << "x" << x_b << " : "; row.display_pol(tout); tout << "\n";); + TRACE("pivot_row", tout << "x" << x_b << " : "; row.display_pol(tout); tout << "\n";); + typename LU::dense_vector::iterator it = row.begin_non_zeros(); + typename LU::dense_vector::iterator end = row.end_non_zeros(); + if (it == end) { + // row doesn't have non-basic variable + TRACE("random_pivoting", tout << "eq_idx: " << eq_idx << " does not have non-basic variables\n";); + continue; + } + unsigned num_non_zeros = end - it; + var x_n = *(it + (rgen() % num_non_zeros)); + if (!LU::manager::precise()) { + if (_lu.m().lt(neg_small, row[x_n]) && _lu.m().lt(row[x_n], small)) { + verbose_stream() << "*"; + continue; + } + } + TRACE("random_pivoting", tout << "pivoting x" << x_b << " with x" << x_n << "\n";); + pivot(_lu, x_b, x_n); + } + } + + /** + \brief Check LU mpq (this method does not really work for m_lu_double due to floating point imprecision). + */ + bool check_lu() const { + if (m_use_approx) + return true; + lu_mpq::dense_vector row(const_cast(this)->m_num_manager, num_vars()); + unsigned num_eqs = m_equations.size(); + for (unsigned eq_idx = 0; eq_idx < num_eqs; eq_idx++) { + var x_b = m_inv_basic[eq_idx]; + row.reset(); + const_cast(this)->get_row_mpq(eq_idx, row, false /* get basic too */, false, true /* also include fixed vars */); + TRACE("check_lu", tout << "x" << x_b << ", eq " << eq_idx << ": "; row.display_pol(tout); tout << "\n";); + // row must contain one and only one basic variable: x_b + // The coefficient of x_b must be 1 + bool found_x_b = false; + lu_mpq::dense_vector::iterator it = row.begin_non_zeros(); + lu_mpq::dense_vector::iterator end = row.end_non_zeros(); + for (; it != end; ++it) { + var x = *it; + if (m_num_manager.is_zero(row[x])) + continue; + if (x == x_b) { + CTRACE("arith_bug", !m_num_manager.is_one(row[x]), tout << "x" << x_b << ": "; row.display_pol(tout); tout << "\n"; + display(tout);); + SASSERT(m_num_manager.is_one(row[x])); + found_x_b = true; + continue; + } + CTRACE("arith_bug", basic(x), tout << "row " << eq_idx << " contains unexpected basic variable x" << x << "\n"; + row.display_pol(tout); tout << "\n"; display(tout);); + SASSERT(!basic(x)); + } + SASSERT(found_x_b); + } + return true; + } + + template + void update_dependents(LU & _lu, Values & values, NumInfMng & infm, var x_n, Numeral const & old_val) { + Numeral aux; + Numeral delta; + infm.sub(values[x_n], old_val, delta); + typename LU::dense_vector & c = _lu.get_tmp_col(); + typename LU::dense_vector::manager & nm = c.m(); + get_nonbasic_column(_lu, x_n, c); + TRACE("fix_nonbasic_var", tout << "fixing x" << x_n << ", old_val: " << infm.to_string(old_val) << ", new_val: " << + infm.to_string(values[x_n]) << ", delta: " << infm.to_string(delta) << "\n"; + tout << "column: "; c.display_non_zeros(tout); tout << "\n";); + typename LU::dense_vector::iterator it = c.begin_non_zeros(); + typename LU::dense_vector::iterator end = c.end_non_zeros(); + for (; it != end; ++it) { + unsigned eq_idx = *it; + if (nm.is_zero(c[eq_idx])) + continue; + var x_b = m_inv_basic[eq_idx]; + SASSERT(m_basic[x_b] == eq_idx); + infm.mul(delta, c[eq_idx], aux); + infm.neg(aux); + TRACE("fix_nonbasic_var", tout << "adjusting x" << x_b << " of: " << eq_idx << " with " << infm.to_string(aux) << "\n";); + acc_value(x_b, aux); + if (below_lower(x_b) || above_upper(x_b)) + m_bad_vars.insert(x_b); + } + infm.del(aux); + infm.del(delta); + } + + /** + \brief Fix a nonbasic variable by moving it to offending bound and propagating the + update to the basic variables. + */ + template + void fix_nonbasic_var(LU & _lu, Values & values, NumInfMng & infm, var x_n) { + SASSERT(below_lower(x_n) || above_upper(x_n)); + SASSERT(nonbasic(x_n)); + typename NumInfMng::numeral old_val; + infm.set(old_val, values[x_n]); + if (below_lower(x_n)) { + lower2value(x_n); + } + else { + SASSERT(above_upper(x_n)); + upper2value(x_n); + } + update_dependents(_lu, values, infm, x_n, old_val); + infm.del(old_val); + } + + void fix_nonbasic_var(var x) { + if (!below_lower(x) && !above_upper(x)) + return; // nothing to be done + SASSERT(nonbasic(x)); + if (m_use_approx) + fix_nonbasic_var(m_lu_double, m_approx_values, m_double_manager, x); + else + fix_nonbasic_var(m_lu_mpq, m_values, m_num_inf_manager, x); + } + + /** + \brief Remove the given variables from m_bad_vars. + */ + void erase_from_bad_vars(var_set & bad_vars, var_buffer const & vars) { + var_buffer::const_iterator it2 = vars.begin(); + var_buffer::const_iterator end2 = vars.end(); + for (; it2 != end2; ++it2) { + bad_vars.erase(*it2); + SASSERT(!bad_vars.contains(*it2)); + } + } + + var_buffer m_to_delete; + + void fix_nonbasic_vars() { + TRACE("fix_nonbasic_vars", display_LU(tout);); + m_to_delete.reset(); + unsigned sz = m_bad_vars.size(); + for (unsigned i = 0; i < sz; i++) { + var x = m_bad_vars[i]; + if (basic(x)) + continue; + TRACE("fix_nonbasic_vars", tout << "fixed x" << x << "\n";); + fix_nonbasic_var(x); + m_to_delete.push_back(x); + } + + erase_from_bad_vars(m_bad_vars, m_to_delete); + SASSERT(check_no_nonbasic_bad_var()); + } + + /** + \brief Remove satisfied variables from m_bad_vars + */ + void cleanup_bad_vars() { + m_to_delete.reset(); + var_set::iterator it = m_bad_vars.begin(); + var_set::iterator end = m_bad_vars.end(); + for (; it != end; ++it) { + var x = *it; + if (!below_lower(x) && !above_upper(x)) + m_to_delete.push_back(x); + } + erase_from_bad_vars(m_bad_vars, m_to_delete); + } + + void cleanup_int_bad_vars() { + m_to_delete.reset(); + var_set::iterator it = m_int_bad_vars.begin(); + var_set::iterator end = m_int_bad_vars.end(); + for (; it != end; ++it) { + var x = *it; + TRACE("int_bad_vars", tout << "x" << x << ", int_feasible: " << int_feasible(x) << ", val: " + << m_num_inf_manager.to_string(m_values[x]) << "\n";); + if (int_feasible(x)) + m_to_delete.push_back(x); + } + erase_from_bad_vars(m_int_bad_vars, m_to_delete); + } + + /** + \brief Return the smallest variable in m_bad_vars. + */ + var get_smallest_bad_var() const { + var r = null_var; // NOTE: null_var is the biggest variable + var_set::iterator it = m_bad_vars.begin(); + var_set::iterator end = m_bad_vars.end(); + for (; it != end; ++it) { + var x = *it; + SASSERT(x < null_var); + if (x < r) + r = x; + } + return r; + } + + /** + \brief Return the variable in m_bad_vars with the least/greatest error + */ + template + var get_lg_error_bad_var() { + var r = null_var; + if (m_use_approx) { + double error_r; + double error_x; + var_set::iterator it = m_bad_vars.begin(); + var_set::iterator end = m_bad_vars.end(); + for (; it != end; ++it) { + var x = *it; + if (below_lower(x)) + error_x = bp().approx_lower(x) - m_approx_values[x]; + else if (above_upper(x)) + error_x = m_approx_values[x] - bp().approx_upper(x); + else + continue; + if (r == null_var || (LEAST && error_x < error_r) || (!LEAST && error_x > error_r)) { + r = x; + error_r = error_x; + } + } + TRACE("get_bad_var", tout << "x" << r << " error: " << error_r << "\n";); + return r; + } + else { + mpq error_r; // ignore infinitesimals + mpq error_x; + var_set::iterator it = m_bad_vars.begin(); + var_set::iterator end = m_bad_vars.end(); + for (; it != end; ++it) { + var x = *it; + if (below_lower(x)) + m_num_manager.sub(bp().lower(x), m_values[x].first, error_x); + else if (above_upper(x)) + m_num_manager.sub(m_values[x].first, bp().upper(x), error_x); + else + continue; + SASSERT(m_num_manager.is_nonneg(error_x)); + if (r == null_var || (LEAST && m_num_manager.lt(error_x, error_r)) || (!LEAST && m_num_manager.gt(error_x, error_r))) { + r = x; + m_num_manager.swap(error_x, error_r); + } + } + m_num_manager.del(error_r); + m_num_manager.del(error_x); + TRACE("get_bad_var", tout << "x" << r << " error: " << m_num_manager.to_string(error_r) << "\n";); + return r; + } + } + + /** + \brief Return the variable in m_bad_vars with the greatest error + */ + var get_least_error_bad_var() { return get_lg_error_bad_var(); } + + /** + \brief Return the variable in m_bad_vars with the least error + */ + var get_greatest_error_bad_var() { return get_lg_error_bad_var(); } + + unsigned get_num_bounds(var x) const { + unsigned r = 0; + if (has_lower(x)) r++; + if (has_upper(x)) r++; + return r; + } + + // Return true if x1 is a better pivot than x2 + template + bool better_pivot(var x_1, var x_2, Vector const & row) const { + SASSERT(x_1 != null_var); + if (x_2 == null_var) + return true; + typename Vector::manager & nm = row.m(); + unsigned num_bounds1 = get_num_bounds(x_1); + unsigned num_bounds2 = get_num_bounds(x_2); + if (num_bounds1 == 0 && num_bounds2 > 0) + return true; + if (num_bounds1 > 0 && num_bounds2 == 0) + return false; + if (nm.gt(row[x_1], row[x_2])) + return true; + if (nm.lt(row[x_1], row[x_2])) + return false; + unsigned num_occs1 = m_eq_occs[x_1].size(); + unsigned num_occs2 = m_eq_occs[x_2].size(); + return num_occs1 < num_occs2; + } + + template + var select_pivot(LU & _lu, Values & values, NumInfMng & infm, var x_b, bool is_below, bool blands_rule) { + SASSERT(basic(x_b)); + typename LU::dense_vector & row = _lu.get_tmp_row(num_vars()); + typename LU::dense_vector::manager & nm = row.m(); + unsigned eq_idx = m_basic[x_b]; + get_row(_lu, eq_idx, row, true, m_use_asserted, false /* there is not point in collecting fixed variables */); + TRACE("select_pivot", tout << "select_pivot x_b: x" << x_b << ", is_below: " << is_below << "\n"; + bp().display_var_bounds(tout, x_b, true, true); tout << "\nval: " << infm.to_string(values[x_b]) << "\n"; + row.display_pol(tout); tout << "\n"; + tout << "below_lower(x_b): " << below_lower(x_b) << "\n"; + tout << "above_upper(x_b): " << above_upper(x_b) << "\n";); + var r = null_var; + typename LU::dense_vector::iterator it = row.begin_non_zeros(); + typename LU::dense_vector::iterator end = row.end_non_zeros(); + for (; it != end; ++it) { + var x_n = *it; + if (nm.is_zero(row[x_n])) + continue; // leftover + bool is_neg = is_below ? nm.is_neg(row[x_n]) : nm.is_pos(row[x_n]); + bool is_pos = !is_neg; + if ((is_pos && above_lower(x_n)) || (is_neg && below_upper(x_n))) { + // x_n is a candidate for pivoting + if (blands_rule) { + if (x_n < r) { + r = x_n; + TRACE("select_pivot", tout << "new-candidate: x" << r << "\n";); + } + } + else if (better_pivot(x_n, r, row)) { + r = x_n; + TRACE("select_pivot", tout << "new-candidate: x" << r << ", num-bounds: " << get_num_bounds(r) + << " num-occs: " << m_eq_occs[r].size() << "\n";); + } + } + } + return r; + } + + /** + \brief Fix a basic variable. + */ + template + bool fix_basic_var(LU & _lu, Values & values, NumInfMng & infm, var x_b, bool blands_rule) { + bool is_below; + if (below_lower(x_b)) { + is_below = true; + } + else if (above_upper(x_b)) { + is_below = false; + } + else { + return true; // nothing to be done + } + var x_n = select_pivot(_lu, values, infm, x_b, is_below, blands_rule); + if (x_n == null_var) + return false; + SASSERT(basic(x_b)); + SASSERT(nonbasic(x_n)); + TRACE("fix_basic_var", tout << "pivoting x" << x_b << " x" << x_n << "\n";); + pivot(_lu, x_b, x_n); + SASSERT(nonbasic(x_b)); + fix_nonbasic_var(_lu, values, infm, x_b); + TRACE("fix_basic_var", tout << "x" << x_b << " -> " << infm.to_string(values[x_b]) << "\n"; + bp().display_var_bounds(tout, x_b); tout << "\n";); + SASSERT(!below_lower(x_b)); + SASSERT(!above_upper(x_b)); + return true; + } + + bool fix_basic_var(var x_b, bool blands_rule) { + if (m_use_approx) + return fix_basic_var(m_lu_double, m_approx_values, m_double_manager, x_b, blands_rule); + else + return fix_basic_var(m_lu_mpq, m_values, m_num_inf_manager, x_b, blands_rule); + } + + // Debugging: make sure m_bad_vars does not contain nonbasic variables. + bool check_no_nonbasic_bad_var() const { + var_set::iterator it = m_bad_vars.begin(); + var_set::iterator end = m_bad_vars.end(); + for (; it != end; ++it) { + CTRACE("arith_bug", nonbasic(*it), tout << "nonbasic var at bad_vars x" << *it << "\n";); + SASSERT(!nonbasic(*it)); + } + return true; + } + + void set_conflict(var x_b) { + m_conflict_eq_idx = m_basic[x_b]; + } + + bool inconsistent() const { + return m_conflict_eq_idx != UINT_MAX || m_bounds.inconsistent() || m_asserted_bounds.inconsistent(); + } + + void reset_updated() { + if (m_use_approx) + m_approx_updated.reset(); + else + m_updated.reset(); + } + +#define DISPLAY_STAT(NAME, VAL) if (VAL > 0) out << " " << NAME << " " << VAL; + + void display_progress(std::ostream & out) const { + out << "(arith"; + DISPLAY_STAT(":pivots", m_pivots); + DISPLAY_STAT(":approx-pivots", m_approx_pivots); + DISPLAY_STAT(":branches", m_branches); + DISPLAY_STAT(":cuts", m_cuts); + out << ")\n"; + } + + /** + \brief Try to satisfy all bounds. + Result: + l_true: succeeded + l_false: problem is unsat + l_undef: gave up + */ + lbool make_feasible_core(unsigned max_pivots = UINT_MAX) { + if (inconsistent()) + return l_false; + TRACE("make_feasible_detail", display(tout);); + if (m_bad_vars.empty()) + return l_true; + + reset_updated(); + TRACE("make_feasible_begin", display(tout);); + + CASSERT("arith", check_eqs_satisfied()); + CASSERT("arith", check_bad_vars()); + fix_nonbasic_vars(); + + // limit for switching to blands rule + unsigned limit = num_vars() * m_blands_rule_factor; + unsigned counter = 0; + + while (true) { + CASSERT("arith", check_bad_vars()); + CASSERT("arith", check_eqs_satisfied()); + CASSERT("arith", check_basic_assignment()); + CASSERT("arith", check_no_nonbasic_bad_var()); + TRACE("make_feasible_step", display(tout);); + checkpoint(); + counter++; + cleanup_bad_vars(); + if (m_bad_vars.empty()) { + TRACE("make_feasible", tout << "satisfied:\n"; display(tout);); + return l_true; + } + // var x_b = counter >= limit ? get_smallest_bad_var() : get_least_error_bad_var(); + var x_b = counter >= limit ? get_smallest_bad_var() : get_greatest_error_bad_var(); + // verbose_stream() << "x" << x_b << " "; verbose_stream().flush(); + TRACE("make_feasible", tout << "next var to be fixed: x" << x_b << "\n";); + SASSERT(basic(x_b)); + if (!fix_basic_var(x_b, counter >= limit)) { + TRACE("make_feasible", tout << "inconsistent:\n"; display(tout);); + SASSERT(basic(x_b)); + set_conflict(x_b); + return l_false; + } + m_bad_vars.erase(x_b); +#ifndef _EXTERNAL_RELEASE + IF_VERBOSE(ST_VERBOSITY_LVL, if (counter % 100 == 0) display_progress(verbose_stream());); +#endif + if (counter > max_pivots) + return l_undef; + } + } + + void reset_conflict() { + m_conflict_eq_idx = UINT_MAX; + } + + unsigned_vector m_cached_basic; + + /** + \brief When using floating point numbers, we may reach an "invalid" basis. + We say a basis is invalid the corresponding matrix is singular. + To avoid this problem, we cache the last "valid" basis. + */ + void cache_basis() { + m_cached_basic.reset(); + unsigned_vector::iterator it = m_basic.begin(); + unsigned_vector::iterator end = m_basic.end(); + for (; it != end; ++it) + m_cached_basic.push_back(*it); + } + + /** + \brief Restore last cached basis. + */ + void restore_basis() { + SASSERT(m_basic.size() == m_cached_basic.size()); + unsigned sz = m_basic.size(); + for (var x = 0; x < sz; x++) { + if (m_cached_basic[x] != UINT_MAX) { + // variables was on the basis + m_basic[x] = m_cached_basic[x]; + m_inv_basic[m_basic[x]] = x; + } + else { + // variable was not on the basis + m_basic[x] = UINT_MAX; + } + } + } + + lbool make_feasible_approx_then_precise() { + // find "promising" basis using approx. + cache_basis(); + try { + set_mode(true, m_use_asserted); + unsigned limit = m_equations.size() * m_approx_threshold_factor; + lbool r = make_feasible_core(limit); + if (r == l_undef) { + // approximation failed to satisfy all constraints. + // so, disable it. + m_approx_forbidden = true; + } + } + catch (lu_exception) { + IF_VERBOSE(ST_VERBOSITY_LVL, verbose_stream() << "(arith-approx-failed)\n";); + restore_basis(); + } + reset_conflict(); // do not trust approx solver + // try again using precise + set_mode(false, m_use_asserted); + TRACE("make_feasible_approx", tout << "second round m_use_approx: " << m_use_approx << "\n";); + try { + // may fail because the basis computed by the approximation is not valid. + return make_feasible_core(); + } + catch (lu_exception) { + restore_basis(); + init_lu_mpq(); + return make_feasible_core(); + } + } + + lbool make_feasible() { + if (inconsistent()) + return l_false; + if (m_bad_vars.empty()) + return l_true; + SASSERT(check_basic_assignment()); + CASSERT("arith", check_bad_vars()); + + lbool r; + if (m_approx_threshold_factor == 0 || m_approx_forbidden) + r = make_feasible_core(); + else + r = make_feasible_approx_then_precise(); + + CASSERT("arith", check_bad_vars()); + SASSERT(r != l_true || check_bounds_satisfied()); + SASSERT(check_basic_assignment()); + + if (r != l_true) + restore_assignment(); + + cleanup_int_bad_vars(); + + TRACE("make_feasible_result", tout << "r: " << r << "\n"; display(tout);); + return r; + } + + void restore_assignment() { + CASSERT("arith", check_eqs_satisfied()); + CASSERT("arith", check_bad_vars()); + TRACE("restore_assignment", tout << "restore_assignment:\n"; display_assignment(tout);); + var_set::iterator it1 = m_approx_updated.begin(); + var_set::iterator end1 = m_approx_updated.end(); + for (; it1 != end1; ++it1) { + var x = *it1; + m_approx_values[x] = m_old_approx_values[x]; + } + m_approx_updated.reset(); + + var_set::iterator it2 = m_updated.begin(); + var_set::iterator end2 = m_updated.end(); + for (; it2 != end2; ++it2) { + var x = *it2; + m_num_inf_manager.swap(m_values[x], m_old_values[x]); + check_int(x); + } + m_updated.reset(); + + SASSERT(m_approx_updated.empty()); + SASSERT(m_updated.empty()); + TRACE("restore_assignment", tout << "restore_assignment:\n"; display_assignment(tout);); + CASSERT("arith", check_eqs_satisfied()); + CASSERT("arith", check_bad_vars()); + } + + // ----------------------- + // + // make_int_feasible + // + // ----------------------- + + random_gen m_rand; + + void init_make_int_feasible() { + set_mode(false, false); // use derived bounds + if (m_cuts_enabled) { + move_nonbasic_to_bounds(); + make_feasible(); + add_cuts(); + // if (make_feasible() != l_false && !m_int_bad_vars.empty()) + // add_cuts(); + make_feasible(); + } + } + + // Buffer for storing cuts that will be added. + struct new_cut_buffer { + var_buffer m_ys; + ptr_vector m_eqs; + mpq_buffer m_cs; + svector m_is_lower; + + new_cut_buffer(numeral_manager & m):m_cs(m) {} + + void save(var y, linear_equation * eq, mpq const & c, bool is_lower) { + m_ys.push_back(y); + m_eqs.push_back(eq); + m_cs.push_back(c); + m_is_lower.push_back(is_lower); + } + + unsigned size() const { return m_ys.size(); } + + bool empty() const { return m_ys.empty(); } + }; + + void insert_cuts(new_cut_buffer & new_cuts) { + for (unsigned i = 0; i < new_cuts.size(); i++) { + m_cuts++; + unsigned eq_idx = m_equations.size(); + var y = new_cuts.m_ys[i]; + m_basic[y] = eq_idx; + m_inv_basic.push_back(y); + m_equations.push_back(new_cuts.m_eqs[i]); + m_eq_occs.push_back(eq_occs()); // for y + init_eq_occs(eq_idx); + mpq const & c = new_cuts.m_cs[i]; + if (new_cuts.m_is_lower[i]) + assert_lower(y, c, false); + else + assert_upper(y, c, false); + // register_propagation_eq(*(new_cuts.m_eqs[i])); + } + } + + // dual == false ==> as*xs >= c + // dual == true ==> as*xs <= c + void add_cut(mpq_buffer & as, var_buffer & xs, mpq const & c, bool dual, new_cut_buffer & new_cuts) { + mpq val; + var y = mk_var(0); + // compute initial value of y + for (unsigned i = 0; i < xs.size(); i++) { + m_num_manager.addmul(val, as[i], m_values[xs[i]].first, val); + } + m_num_inf_manager.set(m_values[y], val); + xs.push_back(y); + as.push_back(mpq(-1)); + m_num_manager.del(val); + linear_equation * new_eq = m_eq_manager.mk(as.size(), as.c_ptr(), xs.c_ptr()); + TRACE("cut", tout << "new cut y" << y << " val: " << m_num_manager.to_string(val) << "\n"; + m_eq_manager.display(tout, *new_eq); tout << "\n";); + new_cuts.save(y, new_eq, c, !dual); + } + + void add_cuts() { + mpq k(1); + mpq c; + new_cut_buffer new_cuts(m_num_manager); + lu_mpq::dense_vector & row = m_lu_mpq.get_tmp_row(num_vars()); + for (var x = 0; x < num_vars(); x++) { + if (!basic(x) || was_eliminated(x) || int_feasible(x)) + continue; + row.reset(); + get_row_mpq(m_basic[x], row, true, m_use_asserted, true); + + if (mk_jdm_cut(k, x, row, m_mpq_buffer, m_var_buffer, c)) + add_cut(m_mpq_buffer, m_var_buffer, c, false, new_cuts); + } + if (!new_cuts.empty()) { + insert_cuts(new_cuts); + init_lu(); + TRACE("add_cuts", display(tout);); + make_feasible(); + } + m_num_manager.del(c); + } + + bool is_cut_var(var x) const { + // TODO: refine... I'm using the fact that a variable introduced by a cut is not associated with an expr + return m_var2expr.get(x) == 0; + } + + /** + \brief A cut variable is not "useful" if it is on the basis. + So, we garbage collect any cut that is + */ + void gc_cuts() { + } + + bool is_nonslact_var(var x) const { + // TODO: refine... + expr * t = m_var2expr.get(x); + return t != 0 && is_uninterp_const(t); + } + + var bb_select_rand_var() { +#if 0 + // Give preference to non cut vars +#define BB_MAX_TRIES 8 + unsigned counter = 0; + while (!m_int_bad_vars.empty()) { + var_set::iterator it = m_int_bad_vars.begin(); + var_set::iterator end = m_int_bad_vars.end(); + unsigned num_candidates = end - it; + unsigned idx = m_rand() % num_candidates; + var x = it[idx]; + if (int_feasible(x)) + m_int_bad_vars.erase(x); + if (!is_cut_var(x)) + return x; + counter++; + if (counter >= BB_MAX_TRIES) + break; + } + + if (m_int_bad_vars.empty()) + return null_var; + + // search for non-cut var + var x = null_var; + unsigned num = 1; + var_set::iterator it = m_int_bad_vars.begin(); + var_set::iterator end = m_int_bad_vars.end(); + for (; it != end; ++it) { + if (int_feasible(x)) + continue; + if (is_cut_var(*it)) + continue; + if (x == null_var || m_rand() % num == 0) + x = *it; + num++; + } +#endif + // pick a random variable + while (!m_int_bad_vars.empty()) { + var_set::iterator it = m_int_bad_vars.begin(); + unsigned num_candidates = m_int_bad_vars.size(); + unsigned idx = m_rand() % num_candidates; + var x = it[idx]; + if (!int_feasible(x)) + return x; + m_int_bad_vars.erase(x); + } + + return null_var; + } + + var_buffer m_bb_var_buffer; + + var bb_select_rand_nonslack_var() { + m_bb_var_buffer.reset(); + var_set::iterator it = m_int_bad_vars.begin(); + var_set::iterator end = m_int_bad_vars.end(); + for (; it != end; ++it) { + var x = *it; + if (int_feasible(x)) + continue; + if (is_nonslact_var(x)) + m_bb_var_buffer.push_back(x); + } + if (m_bb_var_buffer.empty()) + return bb_select_rand_var(); + + unsigned idx = rand() % m_bb_var_buffer.size(); + return m_bb_var_buffer[idx]; + } + + /** + \brief Select a variable x >= k (or x <= k) s.t. |k| <= threshold, and |k| is maximal. + */ + var bb_select_largest_smaller_than(mpz const & threshold) { + var best = null_var; + mpz k_best; + mpz aux; + var_set::iterator it = m_int_bad_vars.begin(); + var_set::iterator end = m_int_bad_vars.end(); + for (; it != end; ++it) { + var x = *it; + if (int_feasible(x)) + continue; + if (has_lower(x) || has_upper(x)) + continue; + if (m_num_manager.is_neg(m_values[x].first)) + m_num_manager.ceil(m_values[x].first, aux); + else + m_num_manager.floor(m_values[x].first, aux); + m_num_manager.abs(aux); + if (m_num_manager.le(aux, threshold) && (best == null_var || m_num_manager.gt(aux, k_best))) { + best = x; + m_num_manager.swap(aux, k_best); + } + } + m_num_manager.del(k_best); + m_num_manager.del(aux); + return best; + } + + var bb_select_var() { + if (m_branch_vars_only) + return bb_select_rand_nonslack_var(); + else + return bb_select_rand_var(); +#if 0 + mpz limit1, limit2; + var x; + m_num_manager.set(limit1, 128); + x = bb_select_largest_smaller_than(limit1); + if (x != null_var) + return x; + m_num_manager.set(limit2, 1024); + x = bb_select_largest_smaller_than(limit2); + if (x != null_var) + return x; + return bb_select_rand_var(); +#endif + } + + bool bb_plunging(var x) { + SASSERT(!int_feasible(x)); + mpq k; + m_num_inf_manager.ceil(m_values[x], k); + push(); + bb_assert_decision(x, true, k); + if (make_feasible() == l_false) { + pop(1); + m_num_inf_manager.floor(m_values[x], k); + bb_assert_decision(x, false, k); + return true; + } + pop(1); + push(); + m_num_inf_manager.floor(m_values[x], k); + bb_assert_decision(x, false, k); + if (make_feasible() == l_false) { + pop(1); + m_num_inf_manager.ceil(m_values[x], k); + bb_assert_decision(x, true, k); + return true; + } + pop(1); + return false; + } + + var_buffer m_todo_plunging; + + void bb_plunging() { + if (inconsistent()) + return; + m_todo_plunging.reset(); + var_set::iterator it = m_int_bad_vars.begin(); + var_set::iterator end = m_int_bad_vars.end(); + for (; it != end; ++it) { + var x = *it; + if (!int_feasible(x)) + m_todo_plunging.push_back(x); + } + + while (!m_todo_plunging.empty()) { + if (inconsistent()) + return; + if (bb_plunging(m_todo_plunging.back())) { + // verbose_stream() << "plunging worked: x" << m_todo_plunging.back() << "\n"; + } + m_todo_plunging.pop_back(); + } + } + + /** + \brief Propagation procedure for branch&bound + */ + bool bb_propagate() { + CASSERT("arith", check_eqs_satisfied() && check_bad_vars()); + propagate_bounds(); // propagate_bounds + CASSERT("arith", check_eqs_satisfied() && check_bad_vars()); + make_feasible(); // make sure the problem is real feasible + // bb_plunging(); + return !inconsistent(); + } + + void bb_push(var x, bool lower, mpq const & k) { + push(); + m_bb_var.push_back(x); + m_bb_lower.push_back(lower); + m_bb_first.push_back(true); + m_bb_k.push_back(mpq()); + m_num_manager.set(m_bb_k.back(), k); + SASSERT(m_bb_var.size() == m_bb_lower.size()); + SASSERT(m_bb_k.size() == m_bb_lower.size()); + SASSERT(m_bb_first.size() == m_bb_lower.size()); + } + + void bb_pop() { + pop(1); + m_bb_var.pop_back(); + m_bb_lower.pop_back(); + m_bb_first.pop_back(); + m_bb_k.pop_back(); + SASSERT(m_bb_var.size() == m_bb_lower.size()); + SASSERT(m_bb_k.size() == m_bb_lower.size()); + SASSERT(m_bb_first.size() == m_bb_lower.size()); + } + + void bb_assert_decision(var x, bool lower, mpq const & k) { + if (lower) { + m_bounds.assert_decided_lower(x, k); + } + else { + m_bounds.assert_decided_upper(x, k); + } + if (above_upper(x) || below_lower(x)) + m_bad_vars.insert(x); + } + + void bb_branch(var x) { + m_branches++; + SASSERT(m_bad_vars.empty()); + SASSERT(!m_num_inf_manager.is_int(m_values[x])); + TRACE("make_int_feasible", tout << "x" << x << " -> " << m_num_inf_manager.to_string(m_values[x]) << "\n";); + bool lower; +#if 1 + if (has_lower(x) && has_upper(x)) + lower = (m_rand() % 2) == 0; + else if (has_upper(x)) + lower = true; + else if (has_lower(x)) + lower = false; + else + lower = (m_rand() % 2) == 0; +#else + lower = (m_rand() % 2) == 0; +#endif + mpq k; + if (lower) + m_num_inf_manager.ceil(m_values[x], k); + else + m_num_inf_manager.floor(m_values[x], k); + bb_push(x, lower, k); + bb_assert_decision(x, lower, k); + TRACE("bb_branch", tout << "branching on x" << x << " " << (lower ? ">=" : "<=") << " " << m_num_manager.to_string(k) << "\n"; + tout << "new-lvl: " << m_bb_var.size() << "\n";); + m_num_manager.del(k); + } + + bool bb_resolve_conflict() { + SASSERT(inconsistent()); + while (!m_bb_var.empty()) { + if (m_bb_first.back() == false) { + // tried both branches... + TRACE("make_int_feasible", tout << "backtracking lvl: " << m_bb_var.size() << "\n";); + bb_pop(); + continue; + } + pop(1); // pop just the bounds + push(); + var x = m_bb_var.back(); + m_bb_first.back() = false; + SASSERT(m_bb_first.back() == false); + bool lower = m_bb_lower.back() != 0; + if (lower) { + // k <= x is inconsistent, then flip to x <= k-1 + m_num_manager.dec(m_bb_k.back()); + } + else { + // x <= k is inconsistent, then flip to k+1 <= x + m_num_manager.inc(m_bb_k.back()); + } + lower = !lower; + m_bb_lower.back() = lower; + // assert new decision + bb_assert_decision(x, lower, m_bb_k.back()); + TRACE("bb_branch", tout << "[flip] branching on x" << x << " " << (lower ? ">=" : "<=") + << " " << m_num_manager.to_string(m_bb_k.back()) << "\n"; tout << "lvl: " << m_bb_var.size() << "\n";); + return true; + } + return false; + } + + void bb_restart() { + while (!m_bb_var.empty()) { + bb_pop(); + } + } + + lbool bb_bounded_search(unsigned limit) { + if (inconsistent()) + return l_false; + unsigned counter = 0; + while (true) { + while (!bb_propagate()) { + if (!bb_resolve_conflict()) + return l_false; + } + TRACE("make_int_feasible_step", display_bounds(tout); display_assignment(tout); display_bad_vars(tout);); + SASSERT(!inconsistent()); + var x = bb_select_var(); + // verbose_stream() << "lvl: " << m_bb_var.size() << " x" << x << "\n"; + if (x == null_var) + return l_true; + bb_branch(x); + counter++; + if (counter >= limit) + break; + TRACE("make_int_feasible", tout << "selected: x" << x << "\n";); +#ifndef _EXTERNAL_RELEASE + IF_VERBOSE(ST_VERBOSITY_LVL, if (counter % 100 == 0) display_progress(verbose_stream());); +#endif + } + return l_undef; + } + + /** + \brief Add bounds to all integer variables that are unbounded + */ + void bb_add_bounds(mpq const & limit) { + mpq neg_limit; + m_num_manager.set(neg_limit, limit); + m_num_manager.neg(neg_limit); + unsigned num = num_vars(); + for (var x = 0; x < num; x++) { + if (is_int(x) && !was_eliminated(x)) { + if (!has_lower(x)) + bb_assert_decision(x, true, neg_limit); + if (!has_upper(x)) + bb_assert_decision(x, false, limit); + } + } + m_num_manager.del(neg_limit); + } + + lbool make_int_feasible_core() { + lbool r = bb_bounded_search(1024*16); + bb_restart(); + return r; + } + + lbool bb_model_finder(unsigned range, unsigned num_branches) { + mpq limit; + m_num_manager.set(limit, range); + push(); // protect state + bb_add_bounds(limit); + lbool r = bb_bounded_search(num_branches); + if (r == l_false) + r = l_undef; + m_num_manager.del(limit); + pop(1); // restore state + return r; + } + + lbool make_int_feasible() { + if (inconsistent()) + return l_false; + init_make_int_feasible(); + + TRACE("make_int_feasible_begin", display(tout);); + TRACE("make_int_feasible_begin", display_int_infeasible_rows(tout);); + + unsigned num_branches = m_int_bad_vars.size() * 2; + bool find_model = true; + unsigned range = 32; + + lbool r; + while (true) { + SASSERT(scope_lvl() == 0); + if (find_model) + r = bb_model_finder(range, num_branches); + else + r = bb_bounded_search(num_branches); + if (r != l_undef) + break; + find_model = !find_model; + bb_restart(); + // init_make_int_feasible(); + num_branches += (m_equations.size() / 10) + 1; + } + + // lbool r = bb_model_finder(128); + TRACE("make_int_feasible_result", tout << "r: " << r << "\n"; display(tout);); +#if 0 + switch (r) { + case l_true: verbose_stream() << "sat\n"; break; + case l_undef: verbose_stream() << "unknown\n"; break; + case l_false: verbose_stream() << "unsat\n"; break; + } +#endif + return r; + } + + // ----------------------- + // + // Preprocessing + // + // ----------------------- + + void register_propagation_eq(linear_equation const & eq) { + // copy coeffs and vars to temporary buffers just to be safe + m_var_buffer.reset(); + m_mpz_buffer.reset(); + unsigned sz = eq.size(); + for (unsigned j = 0; j < sz; j++) { + m_mpz_buffer.push_back(eq.a(j)); + m_var_buffer.push_back(eq.x(j)); + } + m_bounds.mk_eq(m_mpz_buffer.size(), m_mpz_buffer.c_ptr(), m_var_buffer.c_ptr()); + } + + // Copy constraints (only linear_equations at this point) to m_bounds. + void init_bound_propagation_cnstrs() { + SASSERT(scope_lvl() == 0); + m_bounds.del_constraints(); + unsigned num_eqs = m_equations.size(); + for (unsigned i = 0; i < num_eqs; i++) { + linear_equation const & eq = *(m_equations[i]); + register_propagation_eq(eq); + } + } + + /** + \brief Select a (unconstrained) variable in eq for elimination. + If there is more than one option, then select the one with the fewest number of occurrences. + If eq does not contain an unconstrained variable then return null_var. + An integer variable is selected only if it has unary coefficient, and eq does not contain real variables. + */ + var choose_elim_var(linear_equation const & eq, vector const & use_list) const { + bool has_real = false; + unsigned sz = eq.size(); + for (unsigned i = 0; i < sz; i++) { + if (!is_int(eq.x(i))) { + has_real = true; + break; + } + } + var best_x = null_var; + for (unsigned i = 0; i < sz; i++) { + var x = eq.x(i); + if (!is_unconstrained(x)) + continue; + if (is_int(x)) { + if (has_real) + continue; + if (!m_num_manager.is_one(eq.a(i)) && !m_num_manager.is_minus_one(eq.a(i))) + continue; + } + if (best_x == null_var || use_list[x].size() < use_list[best_x].size()) + best_x = x; + } + return best_x; + } + + /** + \brief Update use list for replacing eq with new_eq, the use list of var except is not touched. + */ + void update_use_list(unsigned eq_idx, linear_equation const & eq, linear_equation const & new_eq, vector & use_list, + var except) { + unsigned sz = eq.size(); + unsigned new_sz = new_eq.size(); + unsigned j = 0; + unsigned new_j = 0; + while (true) { + if (j == sz) { + // add remaining variables in new_eq to use list + for (; new_j < new_sz; new_j++) + use_list[new_eq.x(new_j)].push_back(eq_idx); + break; + } + if (new_j == new_sz) { + // remove remaining variables in eq from use list + for (; j < sz; j++) { + var x = eq.x(j); + if (x != except) + use_list[x].erase(eq_idx); + } + break; + } + var x = eq.x(j); + var new_x = new_eq.x(new_j); + if (x < new_x) { + // variable x was removed + if (x != except) + use_list[x].erase(eq_idx); + j++; + } + else if (x > new_x) { + // variable new_x was added + use_list[new_x].push_back(eq_idx); + new_j++; + } + else { + // keep variable + j++; + new_j++; + } + } + } + + /** + \brief Eliminate x from all equations but eq_idx. + */ + void eliminate_var_from_other_eqs(var x, unsigned eq_idx, vector & use_list) { + linear_equation * eq1 = m_equations[eq_idx]; + unsigned i1 = eq1->pos(x); + SASSERT(i1 != UINT_MAX); + mpz b1; + m_num_manager.set(b1, eq1->a(i1)); + m_num_manager.neg(b1); + unsigned_vector & occs = use_list[x]; + unsigned_vector::iterator it = occs.begin(); + unsigned_vector::iterator end = occs.end(); + for (; it != end; ++it) { + unsigned eq2_idx = *it; + if (eq_idx == eq2_idx) + continue; + linear_equation * eq2 = m_equations[eq2_idx]; + unsigned i2 = eq2->pos(x); + SASSERT(i2 != UINT_MAX); + mpz const & b2 = eq2->a(i2); + linear_equation * new_eq2 = m_eq_manager.mk(b2, *eq1, b1, *eq2); + CTRACE("arith_preprocess_new_bug", new_eq2->pos(x) != UINT_MAX, + tout << "x" << x << "\n"; + tout << m_num_manager.to_string(b2) << " * "; m_eq_manager.display(tout, *eq1); tout << "\n"; + tout << m_num_manager.to_string(b1) << " * "; m_eq_manager.display(tout, *eq2); tout << "\n"; + tout << "----->\n"; + m_eq_manager.display(tout, *new_eq2); + tout << "\n";); + SASSERT(new_eq2 != 0); + SASSERT(new_eq2->pos(x) == UINT_MAX); + // update use list + update_use_list(eq2_idx, *eq2, *new_eq2, use_list, x); + m_eq_manager.del(eq2); + m_equations[eq2_idx] = new_eq2; + } + // + m_num_manager.del(b1); + occs.reset(); + occs.push_back(eq_idx); // x occurs only in eq_idx + } + + void eliminate_var(var x, unsigned eq_idx, vector & use_list) { + eliminate_var_from_other_eqs(x, eq_idx, use_list); + linear_equation * eq1 = m_equations[eq_idx]; + // remove x and eq_idx from use list + unsigned_vector & occs = use_list[x]; + occs.reset(); + unsigned sz = eq1->size(); + for (unsigned i = 0; i < sz; i++) { + var x_i = eq1->x(i); + use_list[x_i].erase(eq_idx); + } + // mark x as eliminated. + m_eliminated[x] = true; + m_elim_vars.push_back(elim_var_info(x, eq1)); + } + + void mk_tmp_use_list(vector & use_list) { + use_list.resize(num_vars()); + unsigned num_eqs = m_equations.size(); + for (unsigned eq_idx = 0; eq_idx < num_eqs; ++eq_idx) { + linear_equation const & eq = *(m_equations[eq_idx]); + unsigned sz = eq.size(); + for (unsigned i = 0; i < sz; i++) + use_list[eq.x(i)].push_back(eq_idx); + } + } + + void remove_equations(svector const & to_remove) { + unsigned num_eqs = m_equations.size(); + unsigned j = 0; + for (unsigned i = 0; i < num_eqs; i++) { + if (to_remove[i]) + continue; + linear_equation * eq = m_equations[i]; + var x_b = m_inv_basic[i]; + SASSERT(m_basic[x_b] == i); + m_basic[x_b] = j; + m_equations[j] = eq; + m_inv_basic[j] = x_b; + j++; + } + + TRACE("arith_preprocess_bug", for (unsigned i = 0; i < j; i++) display_eq_basics(tout, i);); + m_equations.shrink(j); + m_inv_basic.shrink(j); + } + + void elim_unconstrained_vars() { + SASSERT(elim_var_applicable()); + // build temporary use list + vector use_list; + mk_tmp_use_list(use_list); + + // eliminate + unsigned old_eliminated_vars = m_eliminated_vars; + unsigned num_eqs = m_equations.size(); + svector to_remove; + to_remove.resize(num_eqs, false); + for (unsigned eq_idx = 0; eq_idx < num_eqs; ++eq_idx) { + linear_equation * eq = m_equations[eq_idx]; + var x = choose_elim_var(*eq, use_list); + if (x == null_var) + continue; + var x_b = m_inv_basic[eq_idx]; + SASSERT(m_basic[x_b] == eq_idx); + m_basic[x_b] = UINT_MAX; // x_b is not basic anymore. + m_eliminated_vars++; + TRACE("arith_preprocess", tout << "eliminating: x" << x << " using "; m_eq_manager.display(tout, *eq); tout << "\n";); + SASSERT(!was_eliminated(x)); + SASSERT(!basic(x)); + eliminate_var(x, eq_idx, use_list); + to_remove[eq_idx] = true; + } + + report_st_progress(":elim-tableau-vars", m_eliminated_vars - old_eliminated_vars); + + remove_equations(to_remove); + } + + // Compress basic variables ids after rows have been eliminated. + void compress_basic_ids() { + unsigned_vector id2var; + unsigned num = num_vars(); + for (var x = 0; x < num; x++) { + if (basic(x)) { + unsigned id = m_basic[x]; + id2var.reserve(id+1, null_var); + id2var[id] = x; + } + } + unsigned next_id = 0; + unsigned sz = id2var.size(); + for (unsigned i = 0; i < sz; i++) { + var x = id2var[i]; + if (x != null_var) { + m_basic[x] = next_id; + m_inv_basic[next_id] = x; + next_id++; + } + } + } + + /** + Make int_eqs[eq_idx] == true if eq_idx constains only integer variables. + */ + void mark_int_eqs(svector & int_eqs) { + unsigned num_eqs = m_equations.size(); + int_eqs.reset(); + for (unsigned i = 0; i < num_eqs; i++) { + linear_equation const & eq = *(m_equations[i]); + unsigned sz = eq.size(); + unsigned j; + for (j = 0; j < sz; j++) { + if (!is_int(eq.x(j))) + break; + } + int_eqs.push_back(j == sz); + } + } + + /** + \brief Eliminate integer variable x by producing the equation c * eq_idx1 + d * eq_idx2. + The coefficient of x in this equation is 1. + This function replaces eq_idx1 with c * eq_idx1 + d * eq_idx2, and invokes eliminate_var(x, eq_idx1, use_list) + */ + void eliminate_int_var(var x, unsigned eq_idx1, unsigned eq_idx2, mpz const & c, mpz const & d, vector & use_list) { + linear_equation * eq1 = m_equations[eq_idx1]; + linear_equation * new_eq1 = m_eq_manager.mk(c, *eq1, d, *(m_equations[eq_idx2])); + SASSERT(new_eq1->pos(x) != UINT_MAX); + SASSERT(m_num_manager.is_one(new_eq1->a(new_eq1->pos(x)))); // coefficient of x in the new equation is one. + update_use_list(eq_idx1, *eq1, *new_eq1, use_list, null_var); + m_eq_manager.del(eq1); + m_equations[eq_idx1] = new_eq1; + eliminate_var(x, eq_idx1, use_list); + } + + void elim_int_unconstrained_vars() { + SASSERT(elim_var_applicable()); + + vector use_list; + mk_tmp_use_list(use_list); + + unsigned old_eliminated_vars = m_eliminated_vars; + + svector int_eqs; + mark_int_eqs(int_eqs); + mpz c, d, g; + + numeral_manager & nm = m_num_manager; + + unsigned num_eqs = m_equations.size(); + svector to_remove; + to_remove.resize(num_eqs, false); + + for (var x = 0; x < num_vars(); x++) { + if (was_eliminated(x) || !is_int(x) || !is_unconstrained(x)) + continue; + TRACE("elim_int", tout << "visiting x" << x << "\n";); + unsigned_vector & occs = use_list[x]; + unsigned_vector::iterator it = occs.begin(); + unsigned_vector::iterator end = occs.end(); + for (; it != end; ++it) { + unsigned eq_idx1 = *it; + if (!int_eqs[eq_idx1]) + continue; // skip: contains real variables... + linear_equation const & eq1 = *(m_equations[eq_idx1]); + SASSERT(elim_var_applicable(eq_idx1)); + unsigned pos1 = eq1.pos(x); + if (pos1 == UINT_MAX) + continue; + mpz const & a1 = eq1.a(pos1); + if (nm.is_one(a1) || nm.is_minus_one(a1)) { + // easy case + m_eliminated_vars++; + var x_b = m_inv_basic[eq_idx1]; + SASSERT(m_basic[x_b] == eq_idx1); + m_basic[x_b] = UINT_MAX; // x_b is not basic anymore. + eliminate_var(x, eq_idx1, use_list); + to_remove[eq_idx1] = true; + break; + } + unsigned_vector::iterator it2 = it+1; + for (; it2 != end; ++it2) { + unsigned eq_idx2 = *it2; + if (!int_eqs[eq_idx2]) + continue; + linear_equation const & eq2 = *(m_equations[eq_idx2]); + SASSERT(elim_var_applicable(eq_idx2)); + unsigned pos2 = eq2.pos(x); + if (pos2 == UINT_MAX) + continue; + mpz const & a2 = eq2.a(pos2); + if (nm.is_one(a2) || nm.is_minus_one(a2)) { + // easy case + m_eliminated_vars++; + var x_b = m_inv_basic[eq_idx2]; + SASSERT(m_basic[x_b] == eq_idx2); + m_basic[x_b] = UINT_MAX; // x_b is not basic anymore. + eliminate_var(x, eq_idx2, use_list); + to_remove[eq_idx2] = true; + goto elim_int_var_processed; + } + nm.gcd(a1, a2, c, d, g); + if (nm.is_one(g)) { + TRACE("elim_int", tout << "found candidate eqs for eliminating x" << x << "\n"; + tout << "a1: " << nm.to_string(a1) << ", a2: " << nm.to_string(a2) << "\n"; + tout << "c: " << nm.to_string(c) << ", d: " << nm.to_string(d) << ", g: " << nm.to_string(g) << "\n"; + m_eq_manager.display(tout, eq1); tout << "\n"; + m_eq_manager.display(tout, eq2); tout << "\n";); + var x_b2 = m_inv_basic[eq_idx2]; + // c * eq1 + d * eq2 --> generates an equation where x has coefficient 1. + // So, we can eliminate x, delete eq1 (or eq2), and replace x everywhere with c*eq1 + d*eq2. + eliminate_int_var(x, eq_idx1, eq_idx2, c, d, use_list); + var x_b = m_inv_basic[eq_idx1]; + SASSERT(m_basic[x_b] == eq_idx1); + m_basic[x_b] = UINT_MAX; // x_b is not basic anymore. + to_remove[eq_idx1] = true; + m_eliminated_vars++; + // the basic variable of eq_idx2 may occur in many equations... eliminate it from all but eq_idx2 + // the idea is to make sure that elim_var_applicable is true for all equations. + linear_equation const & new_eq2 = *(m_equations[eq_idx2]); + SASSERT(new_eq2.pos(x_b2) != UINT_MAX); // eq2 still contains its basic variable + SASSERT(elim_var_applicable(eq_idx2)); // the only basic variable in eq2 is still x_b2 + eliminate_var_from_other_eqs(x_b2, eq_idx2, use_list); + goto elim_int_var_processed; + } + } + } + elim_int_var_processed: + ; + } + nm.del(c); + nm.del(d); + nm.del(g); + + report_st_progress(":elim-int-tableau-vars", m_eliminated_vars - old_eliminated_vars); + remove_equations(to_remove); + } + + // assert axiom x == 0 + void assert_eq_zero_axiom(var x) { + mpq zero(0); + assert_upper(x, zero, false); + assert_lower(x, zero, false); + } + + // Eliminate variables fixed at zero from equations. + bool elim_zero_vars() { + bool eliminated = false; + SASSERT(scope_lvl() == 0); + unsigned num_eqs = m_equations.size(); + svector to_remove; + to_remove.resize(num_eqs, false); + for (unsigned eq_idx = 0; eq_idx < num_eqs; eq_idx++) { + linear_equation * eq = m_equations[eq_idx]; + unsigned sz = eq->size(); + unsigned k; + for (k = 0; k < sz; k++) { + var x_k = eq->x(k); + // To eliminate a basic variable, we have to + // create the LU factorization, and extract the actual row, and then select + // a variable to enter the basis. Perhaps, it is not worth to do that at this point. + // It is better to wait the variable to leave the basis. + if (!basic(x_k) && m_bounds.is_zero(x_k)) + break; + } + if (k == sz) + continue; + // equation has zero variables + m_var_buffer.reset(); + m_mpz_buffer.reset(); + for (k = 0; k < sz; k++) { + var x_k = eq->x(k); + if (!basic(x_k) && m_bounds.is_zero(x_k)) + continue; + // keep non-zero vars and the basic variable + m_var_buffer.push_back(x_k); + m_mpz_buffer.push_back(eq->a(k)); + } + + SASSERT(m_var_buffer.size() < sz); + eliminated = true; + unsigned new_sz = m_var_buffer.size(); + SASSERT(new_sz != 0); + linear_equation * new_eq = m_eq_manager.mk(new_sz, m_mpz_buffer.c_ptr(), m_var_buffer.c_ptr()); + if (new_eq != 0) { + m_equations[eq_idx] = new_eq; + } + else { + assert_eq_zero_axiom(m_var_buffer[0]); + SASSERT(new_sz == 1); + to_remove[eq_idx] = true; + var x_b = m_inv_basic[eq_idx]; + m_basic[x_b] = UINT_MAX; + m_eliminated_vars++; + } + m_eq_manager.del(eq); + } + remove_equations(to_remove); + return eliminated; + } + + bool propagate_bounds() { + unsigned qhead = m_bounds.qhead(); + m_bounds.propagate(); + if (m_bounds.inconsistent()) + return false; + TRACE("propagate_bounds", tout << "propagate_bounds, m_use_asserted: " << m_use_asserted << "\n";); + if (!m_use_asserted) { + // update bad vars using new bounds + bound_propagator::trail_iterator it = m_bounds.begin_trail() + qhead; + bound_propagator::trail_iterator end = m_bounds.end_trail(); + for (; it != end; ++it) { + var x = it->x(); + bool is_lower = it->is_lower(); + TRACE("propagate_bounds", tout << "propagated x" << x << " is_lower: " << is_lower << "\n";); + if (is_lower) { + if (below_lower(x)) + m_bad_vars.insert(x); + } + else { + if (above_upper(x)) + m_bad_vars.insert(x); + } + } + } + return true; + } + + void init_eq_occs(unsigned eq_idx) { + linear_equation const & eq = *(m_equations[eq_idx]); + unsigned sz = eq.size(); + for (unsigned i = 0; i < sz; i++) + m_eq_occs[eq.x(i)].push_back(eq_occ(eq_idx, i)); + } + + void init_eq_occs() { + m_eq_occs.reset(); + m_eq_occs.resize(num_vars()); + unsigned num_eqs = m_equations.size(); + for (unsigned eq_idx = 0; eq_idx < num_eqs; ++eq_idx) { + init_eq_occs(eq_idx); + } + } + + /** + \brief We apply variable elimination only if + For all equation eq, the only and only one basic variable x_b in eq is m_inv_basic[eq]. + + This condition prevents us from ending up with a basis that corresponds to a + singular matrix. + + After compilation, the set of equations always satisfies this condition, since + the basic variable "owning" each equation is the slack introduced during compilation. + */ + bool elim_var_applicable(unsigned eq_idx) const { + linear_equation const & eq = *(m_equations[eq_idx]); + unsigned sz = eq.size(); + for (unsigned i = 0; i < sz; i++) { + var x = eq.x(i); + if (basic(x) && m_basic[x] != eq_idx) + return false; + } + return true; + } + + bool elim_var_applicable() const { + unsigned num_eqs = m_equations.size(); + for (unsigned eq_idx = 0; eq_idx < num_eqs; eq_idx++) { + if (!elim_var_applicable(eq_idx)) + return false; + } + return true; + } + + void preprocess() { + CASSERT("arith", check_invariant()); + IF_VERBOSE(ST_VERBOSITY_LVL, display_status(verbose_stream());); + + TRACE("arith_preprocess", tout << "eqs before elimination\n"; display_eqs(tout);); + + if (m_elim_vars_enabled && elim_var_applicable()) { + elim_unconstrained_vars(); + // TODO: the following transformation is producing a singular basis + if (m_elim_int_vars_enabled) + elim_int_unconstrained_vars(); + } + + init_bound_propagation_cnstrs(); + if (!propagate_bounds()) + return; // inconsistency detected. + + IF_VERBOSE(ST_VERBOSITY_LVL, display_status(verbose_stream());); + + TRACE("arith_preprocess", tout << "eqs after elimination\n"; display_eqs(tout); display_eliminated_vars(tout);); + +#if 1 + if (elim_zero_vars()) { + init_bound_propagation_cnstrs(); + propagate_bounds(); + IF_VERBOSE(ST_VERBOSITY_LVL, display_status(verbose_stream());); + } +#endif + + init_eq_occs(); + + CASSERT("arith", check_invariant()); + TRACE("arith_preprocess_bug", for (unsigned i = 0; i < m_equations.size(); i++) display_eq_basics(tout, i);); + + init_lu(); + + TRACE("arith_int_preprocess", display_unconstrained_vars(tout); display_eqs(tout);); + + CASSERT("arith", check_invariant()); + TRACE("arith_preprocess", display(tout);); + + // tst_random_pivoting(m_lu_double, 2000); + // tst_random_pivoting(m_lu_mpq, 2000); + } + + // ----------------------- + // + // Cuts + // + // ----------------------- + + /** + \brief Move non-basic variables to bounds. + */ + void move_nonbasic_to_bounds() { + mpq_inf old_val; + unsigned num = num_vars(); + for (var x = 0; x < num; x++) { + if (basic(x)) + continue; + if (at_lower(x) || at_upper(x)) + continue; + m_num_inf_manager.set(old_val, m_values[x]); + if (has_lower(x)) + lower2value(x); + else if (has_upper(x)) + upper2value(x); + else + continue; + update_dependents(m_lu_mpq, m_values, m_num_inf_manager, x, old_val); + } + m_num_inf_manager.del(old_val); + } + + bool is_jdm_cut_applicable(mpq const & k, var x_i, lu_mpq::dense_vector const & row) { + if (!m_num_inf_manager.is_rational(m_values[x_i])) + return false; + numeral_manager & nm = m_num_manager; + mpq a; + nm.mul(m_values[x_i].first, k, a); + if (nm.is_int(a)) { + nm.del(a); + return false; + } + // check if cut is applicable + lu_mpq::dense_vector::iterator it = row.begin_non_zeros(); + lu_mpq::dense_vector::iterator end = row.end_non_zeros(); + for (; it != end; ++it) { + var x_j = *it; + if (!is_int(x_j)) { + TRACE("cut", tout << "failed: row constains non int var\n";); + return false; + } + m_num_manager.mul(k, row[x_j], a); + if (nm.is_int(a)) { + // integer coeffs can be ignored when x_j is assigned to an integer + if (!m_num_inf_manager.is_int(m_values[x_j])) + return false; + continue; + } + if (!at_lower(x_j) && !at_upper(x_j)) { + TRACE("cut", tout << "failed: variable is not at bound x" << x_j << "\n";); + return false; + } + } + nm.del(a); + return true; + } + + bool mk_jdm_cut(mpq const & k, var x_i, lu_mpq::dense_vector const & row, mpq_buffer & as, var_buffer & xs, mpq & c) { + if (!is_jdm_cut_applicable(k, x_i, row)) + return false; + numeral_manager & nm = m_num_manager; + SASSERT(nm.is_pos(k)); + as.reset(); + xs.reset(); + nm.reset(c); + mpq a, a_prime; + nm.mul(m_values[x_i].first, k, c); + nm.ceil(c, c); + lu_mpq::dense_vector::iterator it = row.begin_non_zeros(); + lu_mpq::dense_vector::iterator end = row.end_non_zeros(); + for (; it != end; ++it) { + var x_j = *it; + nm.mul(row[x_j], k, a); + if (nm.is_int(a)) { + // I + nm.addmul(c, a, m_values[x_j].first, c); + // ignore monomial + } + else { + if (nm.is_neg(a)) { + if (at_lower(x_j)) { + // L- + // doesn't contribute to c. + // add -a*x_j + nm.neg(a); + as.push_back(a); + } + else { + // U- + nm.floor(a, a_prime); + nm.addmul(c, a_prime, m_values[x_j].first, c); + // new coeff floor(a_ij) - a_ij + nm.sub(a_prime, a, a_prime); + as.push_back(a_prime); + } + } + else { + if (at_lower(x_j)) { + // L+ + nm.ceil(a, a_prime); + nm.addmul(c, a_prime, m_values[x_j].first, c); + // new coeff ceil(a_ij) - a_ij + nm.sub(a_prime, a, a_prime); + as.push_back(a_prime); + } + else { + // U+ + // doesn't contribute to c + // add -a*x_j + nm.neg(a); + as.push_back(a); + } + } + xs.push_back(x_j); + } + } + TRACE("cut", + tout << "new cut:\n"; + for (unsigned i = 0; i < xs.size(); i++) { + if (i > 0) tout << " + "; + tout << nm.to_string(as[i]) << "*x" << xs[i]; + } + tout << " >= " << nm.to_string(c) << "\n"; + tout << "\n";); + nm.del(a); + nm.del(a_prime); + return true; + } + + bool mk_jdm_dual_cut(mpq const & k, var x_i, lu_mpq::dense_vector const & row, mpq_buffer & as, var_buffer & xs, mpq & c) { + if (!is_jdm_cut_applicable(k, x_i, row)) + return false; + numeral_manager & nm = m_num_manager; + SASSERT(nm.is_pos(k)); + as.reset(); + xs.reset(); + nm.reset(c); + mpq a, a_prime; + nm.mul(m_values[x_i].first, k, c); + nm.floor(c, c); + lu_mpq::dense_vector::iterator it = row.begin_non_zeros(); + lu_mpq::dense_vector::iterator end = row.end_non_zeros(); + for (; it != end; ++it) { + var x_j = *it; + nm.mul(row[x_j], k, a); + if (nm.is_int(a)) { + // I + nm.addmul(c, a, m_values[x_j].first, c); + // ignore monomial + } + else { + if (nm.is_neg(a)) { + if (at_lower(x_j)) { + // L- + nm.floor(a, a_prime); + nm.addmul(c, a_prime, m_values[x_j].first, c); + // new coeff floor(a_ij) - a_ij + nm.sub(a_prime, a, a_prime); + as.push_back(a_prime); + } + else { + // U- + // doesn't contribute to c. + // add -a*x_j + nm.neg(a); + as.push_back(a); + } + } + else { + if (at_lower(x_j)) { + // L+ + // doesn't contribute to c + // add -a*x_j + nm.neg(a); + as.push_back(a); + } + else { + // U+ + nm.ceil(a, a_prime); + nm.addmul(c, a_prime, m_values[x_j].first, c); + // new coeff ceil(a_ij) - a_ij + nm.sub(a_prime, a, a_prime); + as.push_back(a_prime); + } + } + xs.push_back(x_j); + } + } + TRACE("cut", + tout << "new cut:\n"; + for (unsigned i = 0; i < xs.size(); i++) { + if (i > 0) tout << " + "; + tout << nm.to_string(as[i]) << "*x" << xs[i]; + } + tout << " <= " << nm.to_string(c) << "\n"; + tout << "\n";); + nm.del(a); + nm.del(a_prime); + return true; + } + + // ----------------------- + // + // Status + // + // ----------------------- + + unsigned matrix_size() const { + unsigned r = 0; + equations::const_iterator it = m_equations.begin(); + equations::const_iterator end = m_equations.end(); + for (; it != end; ++it) + r += (*it)->size(); + return r; + } + + void display_status(std::ostream & out) { + out << "(arith :vars " << num_vars() << " :eliminated " << m_elim_vars.size() << " :eqs " << m_equations.size() + << " :matrix-size " << matrix_size() << ")\n"; + } + + // ----------------------- + // + // Pretty printing + // + // ----------------------- + + void display_int_infeasible_rows(std::ostream & out) { + mpq c; + out << "rows of int infeasible vars:\n"; + lu_mpq::dense_vector & row = m_lu_mpq.get_tmp_row(num_vars()); + for (var x = 0; x < num_vars(); x++) { + if (!basic(x) || was_eliminated(x) || int_feasible(x)) + continue; + row.reset(); + get_row_mpq(m_basic[x], row, true, m_use_asserted, true); + out << "x" << x << " -> " << m_num_inf_manager.to_string(m_values[x]) << " : "; row.display_pol(out); out << "\n"; + mk_jdm_cut(mpq(1), x, row, m_mpq_buffer, m_var_buffer, c); + mk_jdm_dual_cut(mpq(1), x, row, m_mpq_buffer, m_var_buffer, c); + } + m_num_manager.del(c); + } + + /** + \brief Display unbounded variables that were not eliminated. + */ + void display_unconstrained_vars(std::ostream & out) const { + for (var x = 0; x < num_vars(); x++) { + if (!was_eliminated(x) && is_unconstrained(x)) { + out << "x" << x << " "; + } + } + out << "\n"; + } + + void display_ineq_cnstr(std::ostream & out, ineq_cnstr const & c) const { + out << "p" << c.m_atom << " := x" << c.m_x << " " << (c.m_lower ? ">=" : "<=") << " " << m_num_manager.to_string(c.m_k) << "\n"; + } + + void display_ineq_cnstrs(std::ostream & out) const { + out << "ineq-constraints:\n"; + for (unsigned i = 0; i < m_ineq_cnstrs.size(); i++) { + display_ineq_cnstr(out, m_ineq_cnstrs[i]); + } + } + + void display_eliminated_vars(std::ostream & out) const { + out << "eliminated variables:\n"; + elim_vars::const_iterator it = m_elim_vars.begin(); + elim_vars::const_iterator end = m_elim_vars.end(); + for (; it != end; ++it) { + out << "x" << it->first << " --> "; + m_eq_manager.display(out, *(it->second)); + out << "\n"; + } + } + + void display_eq_basics(std::ostream & out, unsigned eq_idx) const { + out << "eq " << eq_idx << ":"; + linear_equation const & eq = *(m_equations[eq_idx]); + unsigned sz = eq.size(); + for (unsigned i = 0; i < sz; i++) { + if (basic(eq.x(i))) + out << " x" << eq.x(i) << "|" << m_basic[eq.x(i)]; + } + out << "\n"; + } + + void display_eqs(std::ostream & out) const { + out << "equations:\n"; + for (unsigned i = 0; i < m_equations.size(); i++) { + linear_equation const & eq = *(m_equations[i]); + out << "eq " << i << ": "; + m_eq_manager.display(out, eq); + out << "\n"; + } + } + + void display_basic(std::ostream & out) const { + out << "basic:"; + for (unsigned x = 0; x < num_vars(); x++) { + if (basic(x)) + out << " (x" << x << " -> " << m_basic[x] << ")"; + } + out << "\n"; + } + + void display_definitions(std::ostream & out) const { + out << "definitions:\n"; + for (unsigned i = 0; i < num_vars(); i++) { + out << "x" << std::left << std::setw(6) << i << " : "; + if (m_var2expr.get(i) == 0) + out << ""; + else + out << mk_ismt2_pp(m_var2expr.get(i), m, 10); + out << "\n"; + } + } + + template + void display_LU(std::ostream & out, LU const & _lu) const { + _lu.display(out); + _lu.display(out, &m_inv_basic); + } + + void display_bad_vars(std::ostream & out) const { + if (m_bad_vars.empty()) { + out << "all constraints are satisfied\n"; + return; + } + out << "bad vars: "; + var_set::iterator it = m_bad_vars.begin(); + var_set::iterator end = m_bad_vars.end(); + for (; it != end; ++it) { + out << "x" << *it << " "; + } + out << "\n"; + } + + void display_int_bad_vars(std::ostream & out) const { + if (m_int_bad_vars.empty()) { + out << "integrality constraints are satisfied\n"; + return; + } + out << "int bad vars: "; + var_set::iterator it = m_int_bad_vars.begin(); + var_set::iterator end = m_int_bad_vars.end(); + for (; it != end; ++it) { + out << "x" << *it << " "; + } + out << "\n"; + } + + void display_assignment(std::ostream & out) const { + out << "assignment:\n"; + for (var x = 0; x < num_vars(); x++) { + if (was_eliminated(x)) + continue; + if (m_use_approx) { + out << "x" << x << " -> " << m_approx_values[x]; + } + else { + out << "x" << x << " -> " << m_num_inf_manager.to_string(m_values[x]); + } + if (is_int(x)) + out << " *"; + out << "\n"; + } + } + + void display_LU(std::ostream & out) const { + if (m_use_approx) + m_lu_double.display(out); + else + m_lu_mpq.display(out); + } + + void display_bounds(std::ostream & out) const { + // display only bounds of variables that were not eliminated. + out << "bounds:\n"; + for (var x = 0; x < num_vars(); x++) { + if (was_eliminated(x)) + continue; + if (m_use_approx) + bp().display_var_bounds(out, x, true, false); + else + bp().display_var_bounds(out, x, false, true); + out << "\n"; + } + } + + void display(std::ostream & out) const { + display_definitions(out); + display_basic(out); + display_eqs(out); + display_bounds(out); + display_LU(out); + display_assignment(out); + display_bad_vars(out); + display_int_bad_vars(out); + display_ineq_cnstrs(out); + display_eliminated_vars(out); + } + + // ----------------------- + // + // Invariants + // + // ----------------------- + + bool check_bounds_satisfied() const { + for (var x = 0; x < num_vars(); x++) { + CTRACE("arith_bug", above_upper(x) || below_lower(x), tout << "bad var: x" << x << "\n"; display(tout);); + SASSERT(!above_upper(x)); + SASSERT(!below_lower(x)); + } + return true; + } + + /** + \brief Check if m_bad_vars contains all variables not satisfying their bounds. + */ + bool check_bad_vars() const { + if (!inconsistent()) { + for (var x = 0; x < num_vars(); x++) { + if (above_upper(x) || below_lower(x)) { + CTRACE("arith_bug", !m_bad_vars.contains(x), tout << "missing bad var: x" << x << "\n"; display(tout);); + SASSERT(m_bad_vars.contains(x)); + } + } + } + return true; + } + + /** + \brief When precise mode is on, this method checks if the assignment of the basic + variables is consistent with the non-basic ones. + */ + bool check_basic_assignment_core() { + if (m_use_approx) + return true; + unsigned num = m_equations.size(); + lu_mpq::dense_vector & row = m_lu_mpq.get_tmp_row(num_vars()); + mpq_inf val; + mpq_inf aux; + for (unsigned eq_idx = 0; eq_idx < num; eq_idx++) { + var x_b = m_inv_basic[eq_idx]; + SASSERT(m_basic[x_b] == eq_idx); + row.reset(); + get_row_mpq(eq_idx, row, true, m_use_asserted, true); + m_num_inf_manager.reset(val); + lu_mpq::dense_vector::iterator it = row.begin_non_zeros(); + lu_mpq::dense_vector::iterator end = row.end_non_zeros(); + for (; it != end; ++it) { + var x_n = *it; + SASSERT(nonbasic(x_n)); + if (m_num_manager.is_zero(row[x_n])) + continue; + m_num_inf_manager.mul(m_values[x_n], row[x_n], aux); + m_num_inf_manager.add(val, aux, val); + } + m_num_inf_manager.neg(val); + CTRACE("arith_bug", !m_num_inf_manager.eq(m_values[x_b], val), + tout << "x_b: " << x_b << " val: " << m_num_inf_manager.to_string(m_values[x_b]) + << ", computed val: " << m_num_inf_manager.to_string(val) << "\n"; + tout << "row: "; row.display_pol(tout); tout << "\n"; + display(tout);); + SASSERT(m_num_inf_manager.eq(m_values[x_b], val)); + } + m_num_inf_manager.del(val); + m_num_inf_manager.del(aux); + return true; + } + + bool check_basic_assignment() const { + return const_cast(this)->check_basic_assignment_core(); + } + + bool check_eqs_satisfied_core() { + mpq_inf val; + mpq_inf aux; + unsigned num = m_equations.size(); + for (unsigned eq_idx = 0; eq_idx < num; eq_idx++) { + linear_equation const & eq = *(m_equations[eq_idx]); + m_num_inf_manager.reset(val); + unsigned sz = eq.size(); + for (unsigned i = 0; i < sz; i++) { + var x = eq.x(i); + mpz const & a = eq.a(i); + m_num_inf_manager.mul(m_values[x], a, aux); + m_num_inf_manager.add(val, aux, val); + } + CTRACE("arith_bug", !m_num_inf_manager.is_zero(val), + m_eq_manager.display(tout, eq); tout << "\nval: " << m_num_inf_manager.to_string(val) << "\n"; + display(tout);); + SASSERT(m_num_inf_manager.is_zero(val)); + } + m_num_inf_manager.del(val); + m_num_inf_manager.del(aux); + return true; + } + + bool check_eqs_satisfied() const { + if (m_use_approx) + return true; + return const_cast(this)->check_eqs_satisfied_core(); + } + + // Debugging: check eq contains at least one basic variable. + bool check_has_basic(unsigned eq_idx) const { + linear_equation const & eq = *(m_equations[eq_idx]); + for (unsigned i = 0; i < eq.size(); i++) { + if (basic(eq.x(i))) + return true; + } + TRACE("arith_bug", m_eq_manager.display(tout, eq); tout << "\n";); + UNREACHABLE(); + return false; + } + + // Debugging: check eq does not contain eliminated variables + bool check_no_elim_var(linear_equation const & eq) const { + for (unsigned i = 0; i < eq.size(); i++) { + SASSERT(!was_eliminated(eq.x(i))); + } + return true; + } + + svector m_visited; + + bool check_invariant() const { + SASSERT(m_inv_basic.size() == m_equations.size()); + svector & visited = const_cast(this)->m_visited; + + visited.reserve(m_equations.size(), false); + + unsigned num_basic = 0; + // check basic variables + for (unsigned x = 0; x < num_vars(); x++) { + if (basic(x)) { + SASSERT(m_inv_basic[m_basic[x]] == x); + num_basic++; + SASSERT(!was_eliminated(x)); + SASSERT(m_basic[x] < m_equations.size()); + SASSERT(!visited[m_basic[x]]); + visited[m_basic[x]] = true; + } + } + SASSERT(num_basic == m_equations.size()); + for (unsigned x = 0; x < num_vars(); x++) { + if (basic(x)) { + SASSERT(visited[m_basic[x]]); + visited[m_basic[x]] = false; + } + } + + // check all eqs contain at least one basic variable and no eliminated variable + for (unsigned i = 0; i < m_equations.size(); i++) { + SASSERT(m_basic[m_inv_basic[i]] == i); + linear_equation const & eq = *(m_equations[i]); + SASSERT(check_no_elim_var(eq)); + SASSERT(check_has_basic(i)); + } + + // check ineq_cnstrs + for (unsigned i = 0; i < m_ineq_cnstrs.size(); i++) { + SASSERT(m_atom2ineq_cnstr[m_ineq_cnstrs[i].m_atom] == i); + SASSERT(!was_eliminated(m_ineq_cnstrs[i].m_x)); + } + + // check m_atom2ineq_cnstr + for (unsigned i = 0; i < m_atom2ineq_cnstr.size(); i++) { + if (m_atom2ineq_cnstr[i] != UINT_MAX) { + SASSERT(m_ineq_cnstrs[m_atom2ineq_cnstr[i]].m_atom == i); + } + } + + return true; + } + + }; + + arith::arith(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); + } + + arith::~arith() { + imp * d = m_imp; + #pragma omp critical (smt_arith) + { + m_imp = 0; + } + dealloc(d); + } + + void arith::updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); + } + + void arith::assert_axiom(expr * t, bool neg) { + TRACE("arith", tout << "asserting neg: " << neg << "\n" << mk_ismt2_pp(t, m_imp->m) << "\n";); + m_imp->mk_ineq(t, neg, null_atom_id); + } + + void arith::mk_atom(expr * t, atom_id p) { + TRACE("arith", tout << "mk_atom p: " << p << "\n" << mk_ismt2_pp(t, m_imp->m) << "\n";); + m_imp->mk_ineq(t, false, p); + } + + void arith::asserted(atom_id id, bool is_true) { + } + + bool arith::inconsistent() const { + // TODO + return false; + } + + void arith::push() { + } + + void arith::pop(unsigned num_scopes) { + } + + void arith::set_cancel(bool f) { + #pragma omp critical (smt_arith) + { + if (m_imp) + m_imp->set_cancel(f); + } + } + + void arith::reset() { + ast_manager & m = m_imp->m; + #pragma omp critical (smt_arith) + { + dealloc(m_imp); + m_imp = alloc(imp, m, m_params); + } + } + + void arith::preprocess() { + m_imp->preprocess(); + } + + void arith::simplify() { + } + + void arith::display(std::ostream & out) const { + m_imp->display(out); + } + + void arith::collect_statistics(statistics & st) const { + m_imp->collect_statistics(st); + } + + void arith::reset_statistics() { + m_imp->reset_statistics(); + } + + lbool arith::check() { + m_imp->make_feasible(); + return m_imp->make_int_feasible(); + } + + void arith::mk_model(model * md) { + m_imp->mk_model(md); + } + +}; + diff --git a/lib/smt_arith.h b/lib/smt_arith.h new file mode 100644 index 000000000..051d27369 --- /dev/null +++ b/lib/smt_arith.h @@ -0,0 +1,56 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + smt_arith.h + +Abstract: + + Arithmetic solver for smt::solver + +Author: + + Leonardo de Moura (leonardo) 2011-06-25. + +Revision History: + +--*/ +#ifndef _SMT_ARITH_H_ +#define _SMT_ARITH_H_ + +#include"ast.h" +#include"smt_solver_types.h" +#include"params.h" +#include"statistics.h" +class model; + +namespace smt { + + class arith { + struct imp; + imp * m_imp; + params_ref m_params; + public: + arith(ast_manager & m, params_ref const & p); + ~arith(); + void updt_params(params_ref const & p); + void assert_axiom(expr * t, bool neg); + void mk_atom(expr * t, atom_id id); + void asserted(atom_id id, bool is_true); + bool inconsistent() const; + void push(); + void pop(unsigned num_scopes); + void set_cancel(bool f); + void simplify(); + void display(std::ostream & out) const; + void reset(); + void preprocess(); + void collect_statistics(statistics & st) const; + void reset_statistics(); + lbool check(); + void mk_model(model * md); + }; +}; + +#endif diff --git a/lib/smt_b_justification.h b/lib/smt_b_justification.h new file mode 100644 index 000000000..4798af4b0 --- /dev/null +++ b/lib/smt_b_justification.h @@ -0,0 +1,100 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_b_justification.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-19. + +Revision History: + +--*/ +#ifndef _SMT_B_JUSTIFICATION_H_ +#define _SMT_B_JUSTIFICATION_H_ + +#include"smt_literal.h" +#include"smt_clause.h" + +namespace smt { + + /** + \brief Proof like object used to track dependencies of boolean propagation. + The idea is to reduce the cost of dependency tracking for the most common + justifications used during boolean propagation: unit propagation + */ + class b_justification { + void * m_data; + public: + enum kind { + CLAUSE, //!< clause of arbitrary size + BIN_CLAUSE, //!< binary clause + AXIOM, //!< no justification, it is only use if proof generation is disabled + JUSTIFICATION //!< fallback + }; + + b_justification(): + m_data(reinterpret_cast(static_cast(AXIOM))) {} + + b_justification(b_justification const & source): + m_data(source.m_data) { + } + + explicit b_justification(clause * c): + m_data(TAG(void*, c, CLAUSE)) { + } + + explicit b_justification(literal l): + m_data(BOXTAGINT(void*, l.index(), BIN_CLAUSE)) { + } + + explicit b_justification(justification * js): + m_data(TAG(void*, js, JUSTIFICATION)) { + SASSERT(js); + } + + kind get_kind() const { + return static_cast(GET_TAG(m_data)); + } + + clause * get_clause() const { + SASSERT(get_kind() == CLAUSE); + return UNTAG(clause*, m_data); + } + + justification * get_justification() const { + SASSERT(get_kind() == JUSTIFICATION); + return UNTAG(justification*, m_data); + } + + literal get_literal() const { + SASSERT(get_kind() == BIN_CLAUSE); + return to_literal(UNBOXINT(m_data)); + } + + bool operator==(b_justification const & other) const { + return m_data == other.m_data; + } + + bool operator!=(b_justification const & other) const { + return !operator==(other); + } + + static b_justification mk_axiom() { + return b_justification(); + } + }; + + const b_justification null_b_justification(static_cast(0)); + + typedef std::pair justified_literal; +}; + +#endif /* _SMT_B_JUSTIFICATION_H_ */ + diff --git a/lib/smt_bool_var_data.h b/lib/smt_bool_var_data.h new file mode 100644 index 000000000..8828b16e2 --- /dev/null +++ b/lib/smt_bool_var_data.h @@ -0,0 +1,128 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_bool_var_data.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-25. + +Revision History: + +--*/ +#ifndef _SMT_BOOL_VAR_DATA_H_ +#define _SMT_BOOL_VAR_DATA_H_ + +#include"smt_b_justification.h" + +namespace smt { + + struct bool_var_data { + b_justification m_justification; + unsigned m_scope_lvl:24; //!< scope level of when the variable was assigned. + unsigned m_mark:1; + unsigned m_assumption:1; + unsigned m_phase_available:1; + unsigned m_phase:1; + private: + unsigned m_eq:1; + unsigned m_true_first:1; //!< If True, when case splitting try the true phase first. Otherwise, you default phase selection heuristic. + unsigned m_enode:1; //!< has enode associated with it. + unsigned m_quantifier:1; //!< true if bool var is attached to a quantifier + unsigned m_iscope_lvl:23; //!< scope level of when the variable was internalized. + unsigned m_atom:1; //!< logical or of m_eq, m_enode, m_quantifier, and m_notify_theory != 0 + unsigned m_notify_theory:8; + + void update_atom_flag() { + m_atom = m_eq || m_notify_theory != 0 || m_quantifier || m_enode; + } + public: + + unsigned get_intern_level() const { return m_iscope_lvl; } + + bool is_atom() const { return m_atom; } + + theory_id get_theory() const { + return m_notify_theory == 0 ? null_theory_id : static_cast(m_notify_theory); + } + + bool is_theory_atom() const { return m_notify_theory != 0; } + + void set_notify_theory(theory_id thid) { + SASSERT(thid > 0 && thid <= 255); + m_notify_theory = thid; + m_atom = true; + } + + void reset_notify_theory() { + m_notify_theory = 0; + update_atom_flag(); + } + + bool is_enode() const { return m_enode; } + + void set_enode_flag() { + m_enode = true; + m_atom = true; + } + + void reset_enode_flag() { + m_enode = false; + update_atom_flag(); + } + + bool is_quantifier() const { return m_quantifier; } + + void set_quantifier_flag() { + m_quantifier = true; + m_atom = true; + } + + bool is_eq() const { return m_eq; } + + void set_eq_flag() { + m_eq = true; + m_atom = true; + } + + void reset_eq_flag() { + m_eq = false; + update_atom_flag(); + } + + bool try_true_first() const { return m_true_first; } + + void set_true_first_flag() { + m_true_first = true; + } + + void reset_true_first_flag() { + m_true_first = false; + } + + void init(unsigned iscope_lvl) { + m_justification = null_b_justification; + m_scope_lvl = 0; + m_mark = false; + m_assumption = false; + m_phase_available = false; + m_phase = false; + m_iscope_lvl = iscope_lvl; + m_eq = false; + m_true_first = false; + m_notify_theory = 0; + m_enode = false; + m_quantifier = false; + m_atom = false; + } + }; +}; + +#endif /* _SMT_BOOL_VAR_DATA_H_ */ + diff --git a/lib/smt_case_split_queue.cpp b/lib/smt_case_split_queue.cpp new file mode 100644 index 000000000..66d08e9fc --- /dev/null +++ b/lib/smt_case_split_queue.cpp @@ -0,0 +1,1124 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_case_split_queue.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2009-01-20. + +Revision History: + +--*/ +#include"smt_context.h" +#include"smt_case_split_queue.h" +#include"warning.h" +#include"stopwatch.h" +#include"for_each_expr.h" +#include"ast_pp.h" + +namespace smt { + + struct bool_var_act_lt { + svector const & m_activity; + bool_var_act_lt(svector const & a):m_activity(a) {} + bool operator()(bool_var v1, bool_var v2) const { + return m_activity[v1] > m_activity[v2]; + } + }; + + typedef heap bool_var_act_queue; + + /** + \brief Case split queue based on activity and random splits. + */ + class act_case_split_queue : public case_split_queue { + protected: + context & m_context; + smt_params & m_params; + bool_var_act_queue m_queue; + public: + act_case_split_queue(context & ctx, smt_params & p): + m_context(ctx), + m_params(p), + m_queue(1024, bool_var_act_lt(ctx.get_activity_vector())) { + } + + virtual void activity_increased_eh(bool_var v) { + if (m_queue.contains(v)) + m_queue.decreased(v); + } + + virtual void mk_var_eh(bool_var v) { + m_queue.reserve(v+1); + m_queue.insert(v); + } + + virtual void del_var_eh(bool_var v) { + if (m_queue.contains(v)) + m_queue.erase(v); + } + + virtual void unassign_var_eh(bool_var v) { + if (!m_queue.contains(v)) + m_queue.insert(v); + } + + virtual void relevant_eh(expr * n) {} + + virtual void init_search_eh() {} + + virtual void end_search_eh() {} + + virtual void reset() { + m_queue.reset(); + } + + virtual void push_scope() {} + + virtual void pop_scope(unsigned num_scopes) {} + + virtual void next_case_split(bool_var & next, lbool & phase) { + phase = l_undef; + + if (m_context.get_random_value() < static_cast(m_params.m_random_var_freq * random_gen::max_value())) { + next = m_context.get_random_value() % m_context.get_num_b_internalized(); + TRACE("random_split", tout << "next: " << next << " get_assignment(next): " << m_context.get_assignment(next) << "\n";); + if (m_context.get_assignment(next) == l_undef) + return; + } + + while (!m_queue.empty()) { + next = m_queue.erase_min(); + if (m_context.get_assignment(next) == l_undef) + return; + } + + next = null_bool_var; + } + + virtual void display(std::ostream & out) { + bool first = true; + bool_var_act_queue::const_iterator it = m_queue.begin(); + bool_var_act_queue::const_iterator end = m_queue.end(); + for (; it != end ; ++it) { + unsigned v = *it; + if (m_context.get_assignment(v) == l_undef) { + if (first) { + out << "remaining case-splits:\n"; + first = false; + } + out << "#" << m_context.bool_var2expr(v)->get_id() << " "; + } + } + if (!first) + out << "\n"; + + } + + virtual ~act_case_split_queue() {}; + }; + + /** + \brief Similar to dact_case_split_queue, but delay case splits + created during the search. + */ + class dact_case_split_queue : public act_case_split_queue { + bool_var_act_queue m_delayed_queue; + public: + dact_case_split_queue(context & ctx, smt_params & p): + act_case_split_queue(ctx, p), + m_delayed_queue(1024, bool_var_act_lt(ctx.get_activity_vector())) { + } + + virtual void activity_increased_eh(bool_var v) { + act_case_split_queue::activity_increased_eh(v); + if (m_queue.contains(v)) + m_queue.decreased(v); + } + + virtual void mk_var_eh(bool_var v) { + m_queue.reserve(v+1); + m_delayed_queue.reserve(v+1); + if (m_context.is_searching()) + m_delayed_queue.insert(v); + else + m_queue.insert(v); + } + + virtual void del_var_eh(bool_var v) { + act_case_split_queue::del_var_eh(v); + if (m_delayed_queue.contains(v)) + m_delayed_queue.erase(v); + } + + virtual void relevant_eh(expr * n) {} + + virtual void init_search_eh() {} + + virtual void end_search_eh() {} + + virtual void reset() { + act_case_split_queue::reset(); + m_delayed_queue.reset(); + } + + virtual void push_scope() {} + + virtual void pop_scope(unsigned num_scopes) {} + + virtual void next_case_split(bool_var & next, lbool & phase) { + act_case_split_queue::next_case_split(next, phase); + if (next != null_bool_var) + return; + + m_queue.swap(m_delayed_queue); + SASSERT(m_delayed_queue.empty()); + + while (!m_queue.empty()) { + next = m_queue.erase_min(); + if (m_context.get_assignment(next) == l_undef) + return; + } + + next = null_bool_var; + } + }; + + /** + \brief Case split queue based on activity and random splits. + */ + class cact_case_split_queue : public act_case_split_queue { + obj_map m_cache; + expr_ref_vector m_cache_domain; + public: + cact_case_split_queue(context & ctx, smt_params & p): + act_case_split_queue(ctx, p), + m_cache_domain(ctx.get_manager()) { + } + + virtual void mk_var_eh(bool_var v) { + expr * n = m_context.bool_var2expr(v); + double act; + if (m_cache.find(n, act)) + m_context.set_activity(v, act); + act_case_split_queue::mk_var_eh(v); + } + + virtual void del_var_eh(bool_var v) { + if (m_context.is_searching()) { + double act = m_context.get_activity(v); + if (act > 0.0) { + expr * n = m_context.bool_var2expr(v); + m_cache.insert(n, act); + m_cache_domain.push_back(n); + } + } + act_case_split_queue::del_var_eh(v); + } + + virtual void init_search_eh() { + m_cache.reset(); + m_cache_domain.reset(); + } + + virtual void end_search_eh() {} + + virtual void reset() { + init_search_eh(); + } + }; + + static bool has_child_assigned_to(context & ctx, app * parent, lbool val, expr * & undef_child, unsigned order) { + ptr_vector undef_children; + bool found_undef = false; + unsigned num_args = parent->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = parent->get_arg(i); + lbool arg_val = ctx.get_assignment(arg); + if (arg_val == val) + return true; + if (found_undef && order == 0) + continue; + if (arg_val == l_undef) { + if (order == 1) + undef_children.push_back(arg); + else + undef_child = arg; + found_undef = true; + } + } + if (order == 1) { + if (undef_children.size() == 0) { + // a bug? + } else if (undef_children.size() == 1) { + undef_child = undef_children[0]; + } else { + undef_child = undef_children[ctx.get_random_value() % undef_children.size()]; + } + } + return false; + } + + /** + \brief Case split queue based on relevancy propagation + */ + class rel_case_split_queue : public case_split_queue { + struct scope { + unsigned m_queue_trail; + unsigned m_head_old; + unsigned m_queue2_trail; + unsigned m_head2_old; + }; + typedef int_hashtable > bool_var_set; + context & m_context; + front_end_params &m_params; + ast_manager & m_manager; + ptr_vector m_queue; + unsigned m_head; + int m_bs_num_bool_vars; //!< Number of boolean variable before starting to search. + ptr_vector m_queue2; + unsigned m_head2; + svector m_scopes; + public: + rel_case_split_queue(context & ctx, front_end_params & p): + m_context(ctx), + m_params(p), + m_manager(ctx.get_manager()), + m_head(0), + m_bs_num_bool_vars(UINT_MAX), + m_head2(0) { + } + + virtual void activity_increased_eh(bool_var v) {} + + virtual void mk_var_eh(bool_var v) {} + + virtual void del_var_eh(bool_var v) {} + + virtual void unassign_var_eh(bool_var v) {} + + virtual void relevant_eh(expr * n) { + if (!m_manager.is_bool(n)) + return; + bool is_or = m_manager.is_or(n); + bool intern = m_context.b_internalized(n); + if (!intern && !is_or) + return; + bool_var var = null_bool_var; + if (intern) { + var = m_context.get_bool_var(n); + SASSERT(var != null_bool_var); + bool is_and = m_manager.is_and(n); + lbool val = m_context.get_assignment(var); + if (!( + val == l_undef || // n was not assigned yet + (is_or && val == l_true) || // need to justify a child + (is_and && val == l_false) // need to justify a child + )) + return; + } + if (!intern && m_context.is_searching()) { + SASSERT(is_or); + m_queue2.push_back(n); + return; + } + if (var < m_bs_num_bool_vars) + m_queue.push_back(n); + else + m_queue2.push_back(n); + } + + virtual void init_search_eh() { + m_bs_num_bool_vars = m_context.get_num_bool_vars(); + } + + virtual void end_search_eh() { + m_bs_num_bool_vars = UINT_MAX; + } + + virtual void reset() { + m_queue.reset(); + m_head = 0; + m_queue2.reset(); + m_head2 = 0; + m_scopes.reset(); + } + + virtual void push_scope() { + m_scopes.push_back(scope()); + scope & s = m_scopes.back(); + s.m_queue_trail = m_queue.size(); + s.m_head_old = m_head; + s.m_queue2_trail = m_queue2.size(); + s.m_head2_old = m_head2; + TRACE("case_split", tout << "head: " << m_head << "\n";); + } + + virtual void pop_scope(unsigned num_scopes) { + SASSERT(num_scopes <= m_scopes.size()); + unsigned new_lvl = m_scopes.size() - num_scopes; + scope & s = m_scopes[new_lvl]; + m_queue.shrink(s.m_queue_trail); + m_head = s.m_head_old; + m_queue2.shrink(s.m_queue2_trail); + m_head2 = s.m_head2_old; + m_scopes.shrink(new_lvl); + SASSERT(m_head <= m_queue.size()); + TRACE("case_split", display(tout); tout << "head: " << m_head << "\n";); + } + + void next_case_split_core(ptr_vector & queue, unsigned & head, bool_var & next, lbool & phase) { + phase = l_undef; + unsigned sz = queue.size(); + for (; head < sz; head++) { + expr * curr = queue[head]; + bool is_or = m_manager.is_or(curr); + bool is_and = m_manager.is_and(curr); + bool intern = m_context.b_internalized(curr); + SASSERT(intern || is_or); + lbool val = l_undef; + if (intern) { + next = m_context.get_bool_var(curr); + val = m_context.get_assignment(next); + } + else { + SASSERT(is_or); // top level clause + val = l_true; + } + if ((is_or && val == l_true) || (is_and && val == l_false)) { + expr * undef_child = 0; + if (!has_child_assigned_to(m_context, to_app(curr), val, undef_child, m_params.m_rel_case_split_order)) { + if (m_params.m_trace_stream != NULL) { + *m_params.m_trace_stream << "[decide-and-or] #" << curr->get_id() << " #" << undef_child->get_id() << "\n"; + } + TRACE("case_split", tout << "found AND/OR candidate: #" << curr->get_id() << " #" << undef_child->get_id() << "\n";); + literal l = m_context.get_literal(undef_child); + next = l.var(); + phase = l.sign() ? l_false : l_true; + TRACE("case_split", display(tout);); + return; + } + } + else if (val == l_undef) { + SASSERT(intern && m_context.get_bool_var(curr) == next); + TRACE("case_split", tout << "found candidate: #" << curr->get_id() << "\n";); + phase = l_undef; + TRACE("case_split", display(tout);); + return; + } + } + next = null_bool_var; + } + + virtual void next_case_split(bool_var & next, lbool & phase) { + next_case_split_core(m_queue, m_head, next, phase); + if (next == null_bool_var) + next_case_split_core(m_queue2, m_head2, next, phase); + // Force l_false is next is an equality that is known to be disequal in the logical context. + if (m_params.m_lookahead_diseq && next != null_bool_var && phase != l_false && m_context.has_enode(next)) { + enode * n = m_context.bool_var2enode(next); + if (n->is_eq()) { + enode * lhs = n->get_arg(0); + enode * rhs = n->get_arg(1); + if (m_context.is_ext_diseq(lhs, rhs, 2)) + phase = l_false; + } + } + } + + void display_core(std::ostream & out, ptr_vector & queue, unsigned head, unsigned idx) { + if (queue.empty()) + return; + unsigned sz = queue.size(); + for (unsigned i = 0; i < sz; i++) { + if (i == head) + out << "[HEAD" << idx << "]=> "; + out << "#" << queue[i]->get_id() << " "; + } + out << "\n"; + } + + virtual void display(std::ostream & out) { + if (m_queue.empty() && m_queue2.empty()) + return; + out << "case-splits:\n"; + display_core(out, m_queue, m_head, 1); + display_core(out, m_queue2, m_head2, 2); + } + }; + + /** + \brief Case split queue based on relevancy propagation + */ + class rel_act_case_split_queue : public case_split_queue { + struct scope { + unsigned m_queue_trail; + unsigned m_head_old; + }; + typedef int_hashtable > bool_var_set; + context & m_context; + ast_manager & m_manager; + front_end_params &m_params; + ptr_vector m_queue; + unsigned m_head; + int m_bs_num_bool_vars; //!< Number of boolean variable before starting to search. + bool_var_act_queue m_delayed_queue; + svector m_scopes; + public: + rel_act_case_split_queue(context & ctx, front_end_params & p): + m_context(ctx), + m_manager(ctx.get_manager()), + m_params(p), + m_head(0), + m_bs_num_bool_vars(UINT_MAX), + m_delayed_queue(1024, bool_var_act_lt(ctx.get_activity_vector())) { + } + + virtual void activity_increased_eh(bool_var v) {} + + virtual void mk_var_eh(bool_var v) { + if (m_context.is_searching()) { + SASSERT(v >= m_bs_num_bool_vars); + m_delayed_queue.reserve(v+1); + m_delayed_queue.insert(v); + } + } + + virtual void del_var_eh(bool_var v) { + if (v >= m_bs_num_bool_vars && m_delayed_queue.contains(v)) + m_delayed_queue.erase(v); + } + + virtual void unassign_var_eh(bool_var v) { + if (v < m_bs_num_bool_vars) + return; + if (!m_delayed_queue.contains(v)) + m_delayed_queue.insert(v); + } + + virtual void relevant_eh(expr * n) { + if (!m_manager.is_bool(n)) + return; + bool is_or = m_manager.is_or(n); + bool intern = m_context.b_internalized(n); + if (!intern && !is_or) + return; + bool_var var = null_bool_var; + if (intern) { + var = m_context.get_bool_var(n); + SASSERT(var != null_bool_var); + bool is_and = m_manager.is_and(n); + lbool val = m_context.get_assignment(var); + if (!( + val == l_undef || // n was not assigned yet + (is_or && val == l_true) || // need to justify a child + (is_and && val == l_false) // need to justify a child + )) + return; + } + if (!intern) { + if (!m_context.is_searching()) + m_queue.push_back(n); + return; + } + if (var < m_bs_num_bool_vars) + m_queue.push_back(n); + } + + virtual void init_search_eh() { + m_bs_num_bool_vars = m_context.get_num_bool_vars(); + } + + virtual void end_search_eh() { + m_bs_num_bool_vars = UINT_MAX; + } + + virtual void reset() { + m_queue.reset(); + m_head = 0; + m_delayed_queue.reset(); + m_scopes.reset(); + } + + virtual void push_scope() { + m_scopes.push_back(scope()); + scope & s = m_scopes.back(); + s.m_queue_trail = m_queue.size(); + s.m_head_old = m_head; + TRACE("case_split", tout << "head: " << m_head << "\n";); + } + + virtual void pop_scope(unsigned num_scopes) { + SASSERT(num_scopes <= m_scopes.size()); + unsigned new_lvl = m_scopes.size() - num_scopes; + scope & s = m_scopes[new_lvl]; + m_queue.shrink(s.m_queue_trail); + m_head = s.m_head_old; + m_scopes.shrink(new_lvl); + SASSERT(m_head <= m_queue.size()); + TRACE("case_split", display(tout); tout << "head: " << m_head << "\n";); + } + + void next_case_split_core(bool_var & next, lbool & phase) { + phase = l_undef; + unsigned sz = m_queue.size(); + for (; m_head < sz; m_head++) { + expr * curr = m_queue[m_head]; + bool is_or = m_manager.is_or(curr); + bool is_and = m_manager.is_and(curr); + bool intern = m_context.b_internalized(curr); + SASSERT(intern || is_or); + lbool val = l_undef; + if (intern) { + next = m_context.get_bool_var(curr); + val = m_context.get_assignment(next); + } + else { + SASSERT(is_or); // top level clause + val = l_true; + } + if ((is_or && val == l_true) || (is_and && val == l_false)) { + expr * undef_child = 0; + if (!has_child_assigned_to(m_context, to_app(curr), val, undef_child, m_params.m_rel_case_split_order)) { + TRACE("case_split", tout << "found AND/OR candidate: #" << curr->get_id() << " #" << undef_child->get_id() << "\n";); + literal l = m_context.get_literal(undef_child); + next = l.var(); + phase = l.sign() ? l_false : l_true; + TRACE("case_split", display(tout);); + return; + } + } + else if (val == l_undef) { + SASSERT(intern && m_context.get_bool_var(curr) == next); + TRACE("case_split", tout << "found candidate: #" << curr->get_id() << "\n";); + phase = l_undef; + TRACE("case_split", display(tout);); + return; + } + } + next = null_bool_var; + } + + virtual void next_case_split(bool_var & next, lbool & phase) { + if (m_context.get_random_value() < static_cast(0.02 * random_gen::max_value())) { + next = m_context.get_random_value() % m_context.get_num_b_internalized(); + TRACE("random_split", tout << "next: " << next << " get_assignment(next): " << m_context.get_assignment(next) << "\n";); + if (m_context.get_assignment(next) == l_undef) + return; + } + + next_case_split_core(next, phase); + if (next != null_bool_var) + return; + phase = l_undef; + while (!m_delayed_queue.empty()) { + next = m_delayed_queue.erase_min(); + if (m_context.get_assignment(next) == l_undef) + return; + } + next = null_bool_var; + } + + void display_core(std::ostream & out) { + if (m_queue.empty()) + return; + unsigned sz = m_queue.size(); + for (unsigned i = 0; i < sz; i++) { + if (i == m_head) + out << "[HEAD]=> "; + out << "#" << m_queue[i]->get_id() << " "; + } + out << "\n"; + } + + virtual void display(std::ostream & out) { + if (m_queue.empty()) + return; + out << "case-splits:\n"; + display_core(out); + } + }; + + + /** + \brief Case split queue based on relevancy propagation and generation/goal-similarity + */ + class rel_goal_case_split_queue : public case_split_queue { +#if 0 +#define GOAL_START() m_goal_time.start() +#define GOAL_STOP() m_goal_time.stop() +#else +#define GOAL_START() do {} while (0) +#define GOAL_STOP() do {} while (0) +#endif + + struct queue_entry { + expr * m_expr; + unsigned m_generation; + int m_last_decided; + + queue_entry(expr * e, unsigned gen): + m_expr(e), + m_generation(gen), + m_last_decided(-1) {} + }; + + struct generation_lt { + rel_goal_case_split_queue & m_parent; + generation_lt(rel_goal_case_split_queue & p):m_parent(p) {} + bool operator()(int v1, int v2) const { + unsigned g1 = m_parent.m_queue2[v1].m_generation; + unsigned g2 = m_parent.m_queue2[v2].m_generation; + + if (g1 == g2) + return v1 < v2; + else + return g1 < g2; + } + }; + + struct scope { + unsigned m_queue_trail; + unsigned m_head_old; + unsigned m_queue2_trail; + unsigned m_generation; + expr * m_goal; + }; + + typedef int_hashtable > bool_var_set; + context & m_context; + front_end_params & m_params; + ast_manager & m_manager; + ptr_vector m_queue; + unsigned m_head; + int m_bs_num_bool_vars; //!< Number of boolean variable before starting to search. + svector m_queue2; + svector m_scopes; + unsigned m_current_generation; + + // The heap holds indices into m_queue2, i in m_priority_queue2 <==> m_queue2[i].m_last_assigned == -1 + heap m_priority_queue2; + expr * m_current_goal; + stopwatch m_goal_time; + + static const unsigned start_gen = 0; + static const unsigned goal_gen_decrement = 100; + static const unsigned stop_gen = goal_gen_decrement + 1; + + + public: + rel_goal_case_split_queue(context & ctx, front_end_params & p): + m_context(ctx), + m_params(p), + m_manager(ctx.get_manager()), + m_head(0), + m_bs_num_bool_vars(UINT_MAX), + m_priority_queue2(0, generation_lt(*this)), + m_current_goal(0) { + set_global_generation(); + } + + virtual void activity_increased_eh(bool_var v) {} + + virtual void mk_var_eh(bool_var v) {} + + virtual void del_var_eh(bool_var v) {} + + virtual void unassign_var_eh(bool_var v) {} + + virtual void relevant_eh(expr * n) { + if (get_generation(n) == 0 && m_current_generation != 0) + set_generation_rec(n, m_current_generation); + + if (!m_manager.is_bool(n)) + return; + bool is_or = m_manager.is_or(n); + bool intern = m_context.b_internalized(n); + if (!intern && !is_or) + return; + bool_var var = null_bool_var; + if (intern) { + var = m_context.get_bool_var(n); + SASSERT(var != null_bool_var); + bool is_and = m_manager.is_and(n); + lbool val = m_context.get_assignment(var); + if (!( + val == l_undef || // n was not assigned yet + (is_or && val == l_true) || // need to justify a child + (is_and && val == l_false) // need to justify a child + )) + return; + } + if (!intern && m_context.is_searching()) { + SASSERT(is_or); + add_to_queue2(n); + return; + } + if (var < m_bs_num_bool_vars) + m_queue.push_back(n); + else + add_to_queue2(n); + } + + virtual void internalize_instance_eh(expr * e, unsigned gen) + { + //lower_generation(e, gen); + } + + virtual void init_search_eh() { + m_bs_num_bool_vars = m_context.get_num_bool_vars(); + set_global_generation(); + } + + virtual void end_search_eh() { + m_bs_num_bool_vars = UINT_MAX; + } + + virtual void reset() { + m_queue.reset(); + m_head = 0; + m_queue2.reset(); + m_scopes.reset(); + m_priority_queue2.reset(); + set_global_generation(); + } + + virtual void push_scope() { + m_scopes.push_back(scope()); + scope & s = m_scopes.back(); + s.m_queue_trail = m_queue.size(); + s.m_head_old = m_head; + s.m_queue2_trail = m_queue2.size(); + s.m_generation = m_current_generation; + s.m_goal = m_current_goal; + TRACE("case_split", tout << "head: " << m_head << "\n";); + } + + virtual void pop_scope(unsigned num_scopes) { + SASSERT(num_scopes <= m_scopes.size()); + unsigned new_lvl = m_scopes.size() - num_scopes; + scope & s = m_scopes[new_lvl]; + m_queue.shrink(s.m_queue_trail); + m_head = s.m_head_old; + m_current_generation = s.m_generation; + m_current_goal = s.m_goal; + + for (unsigned i = s.m_queue2_trail; i < m_queue2.size(); i++) { + //TRACE("case_split", tout << "ld[" << i << "] = " << m_queue2[i].m_last_decided << " cont " << + SASSERT((m_queue2[i].m_last_decided == -1) == m_priority_queue2.contains(i)); + if (m_priority_queue2.contains(i)) + m_priority_queue2.erase(i); + } + + for (unsigned i = 0; i < s.m_queue2_trail; i++) { + queue_entry & e = m_queue2[i]; + + if (e.m_last_decided > static_cast(new_lvl)) { + SASSERT(!m_priority_queue2.contains(i)); + // Note that the generation might be reset by the pop, and we keep the heap + // ordered by the old generation. It's unlikely to affect performance I think. + m_priority_queue2.insert(i); + e.m_last_decided = -1; + } + } + m_queue2.shrink(s.m_queue2_trail); + m_scopes.shrink(new_lvl); + SASSERT(m_head <= m_queue.size()); + TRACE("case_split", display(tout); tout << "head: " << m_head << "\n";); + } + + void next_case_split_core(expr * curr, bool_var & next, lbool & phase) { + bool is_or = m_manager.is_or(curr); + bool is_and = m_manager.is_and(curr); + bool intern = m_context.b_internalized(curr); + SASSERT(intern || is_or); + lbool val = l_undef; + if (intern) { + next = m_context.get_bool_var(curr); + val = m_context.get_assignment(next); + } + else { + SASSERT(is_or); // top level clause + val = l_true; + } + if ((is_or && val == l_true) || (is_and && val == l_false)) { + expr * undef_child = 0; + if (!has_child_assigned_to(m_context, to_app(curr), val, undef_child, m_params.m_rel_case_split_order)) { + if (m_params.m_trace_stream != NULL) { + *m_params.m_trace_stream << "[decide-and-or] #" << curr->get_id() << " #" << undef_child->get_id() << "\n"; + } + TRACE("case_split", tout << "found AND/OR candidate: #" << curr->get_id() << " #" << undef_child->get_id() << "\n";); + literal l = m_context.get_literal(undef_child); + next = l.var(); + phase = l.sign() ? l_false : l_true; + TRACE("case_split", display(tout);); + return; + } + } + else if (val == l_undef) { + SASSERT(intern && m_context.get_bool_var(curr) == next); + TRACE("case_split", tout << "found candidate: #" << curr->get_id() << "\n";); + phase = l_undef; + TRACE("case_split", display(tout);); + return; + } + next = null_bool_var; + } + + virtual void next_case_split(bool_var & next, lbool & phase) { + phase = l_undef; + next = null_bool_var; + + unsigned sz = m_queue.size(); + for (; m_head < sz; m_head++) { + expr * curr = m_queue[m_head]; + next_case_split_core(curr, next, phase); + if (next != null_bool_var) + return; + } + + while (!m_priority_queue2.empty()) { + unsigned idx = static_cast(m_priority_queue2.erase_min()); + TRACE("case_split", tout << "q " << m_queue2.size() << " idx " << idx << "\n"; ); + SASSERT(idx < m_queue2.size()); + queue_entry & e = m_queue2[idx]; + SASSERT(e.m_last_decided == -1); + e.m_last_decided = m_scopes.size(); + + next_case_split_core(e.m_expr, next, phase); + + if (next != null_bool_var) { + // Push the last guy back in; the other queue doesn't increment + // the m_head in case of return and the code in decide() actually + // does the push after calling us + m_priority_queue2.insert(idx); + e.m_last_decided = -1; + /* + if (m_params.m_trace_stream != NULL) { + *m_params.m_trace_stream << "[generation] #" << e.m_expr->get_id() << " " << e.m_generation << "\n"; + } + */ + return; + } + } + } + + void display_core(std::ostream & out, ptr_vector & queue, unsigned head, unsigned idx) { + if (queue.empty()) + return; + unsigned sz = queue.size(); + for (unsigned i = 0; i < sz; i++) { + if (i == head) + out << "[HEAD" << idx << "]=> "; + out << "#" << queue[i]->get_id() << " "; + } + out << "\n"; + } + + virtual void display(std::ostream & out) { + if (m_queue.empty() && m_queue2.empty()) + return; + out << "case-splits:\n"; + display_core(out, m_queue, m_head, 1); + //display_core(out, m_queue2, m_head2, 2); + } + + virtual void assign_lit_eh(literal l) { + // if (m_current_generation > stop_gen) + // m_current_generation--; + + expr * e = m_context.bool_var2expr(l.var()); + if (e == m_current_goal) + return; + bool sign = l.sign(); + if ( ((m_manager.is_and(e) && !sign) || + (m_manager.is_or(e) && sign)) && + to_app(e)->get_num_args() == 2) { + + expr * lablit = to_app(e)->get_arg(1); + if (m_manager.is_not(lablit)) { + sign = !sign; + lablit = to_app(lablit)->get_arg(0); + } + if (sign) return; + if (!m_manager.is_label_lit(lablit)) + return; + + TRACE("case_split", tout << "Found goal\n" << mk_pp(e, m_manager) << "\n"; ); + + set_goal(e); + } + } + + private: + + unsigned get_generation(expr * e) + { + unsigned maxgen = 0; + unsigned mingen = (unsigned)-1; + ptr_vector stack; + + stack.push_back(e); + while (!stack.empty()) { + unsigned gen; + expr * curr; + + curr = stack.back(); + stack.pop_back(); + + if (m_context.e_internalized(curr)) { + gen = m_context.get_enode(curr)->get_generation(); + if (gen > maxgen) + maxgen = gen; + if (gen < mingen) + mingen = gen; + } + else if (is_app(curr)) { + app * a = to_app(curr); + for (unsigned i = 0; i < a->get_num_args(); ++i) + stack.push_back(a->get_arg(i)); + } + } + + return maxgen; + } + + void add_to_queue2(expr * e) + { + int idx = m_queue2.size(); + + GOAL_START(); + m_queue2.push_back(queue_entry(e, get_generation(e))); + m_priority_queue2.reserve(idx+1); + m_priority_queue2.insert(idx); + GOAL_STOP(); + } + + struct set_generation_fn { + context & m_context; + unsigned m_generation; + set_generation_fn(context & ctx, unsigned gen) : m_context(ctx), m_generation(gen) { } + void operator()(expr * e) { + if (m_context.e_internalized(e)) { + enode * n = m_context.get_enode(e); + n->set_generation(m_context, m_generation); + } + } + }; + + void set_generation_rec(expr * e, unsigned gen) + { + set_generation_fn proc(m_context, gen); + for_each_expr(proc, e); + } + + void lower_generation(expr * e, unsigned gen) + { + ptr_vector stack; + + stack.push_back(e); + while (!stack.empty()) { + expr * curr; + + curr = stack.back(); + stack.pop_back(); + + if (m_context.e_internalized(curr)) { + unsigned curr_gen = m_context.get_enode(curr)->get_generation(); + if (curr_gen > gen) { + // Lower it. + set_generation_rec(e, gen); + continue; // Don't add children. + } + else if (curr_gen < gen) { + // All the children will be lower as well, don't add them. + continue; + } + } + + if (is_app(curr)) { + app * a = to_app(curr); + for (unsigned i = 0; i < a->get_num_args(); ++i) + stack.push_back(a->get_arg(i)); + } + } + } + + void set_goal(expr * e) + { + + if (e == m_current_goal) return; + + GOAL_START(); + + m_current_goal = e; + +#if 1 + if (m_current_generation >= goal_gen_decrement) { + set_generation_rec(m_current_goal, m_current_generation - goal_gen_decrement); + + /* + m_priority_queue2.reset(); + m_priority_queue2.reserve(m_queue2.size()); + + for (unsigned i = 0; i < m_queue2.size(); ++i) { + queue_entry & e = m_queue2[i]; + e.m_generation = get_generation(e.m_expr); + if (e.m_last_decided == -1) + m_priority_queue2.insert(i); + } + */ + } +#endif + + GOAL_STOP(); + + //std::cout << "goal set, time " << m_goal_time.get_seconds() << "\n"; + } + + void set_global_generation() + { + m_current_generation = start_gen; + m_context.set_global_generation(start_gen); + if (m_params.m_qi_eager_threshold < start_gen) + m_params.m_qi_eager_threshold += start_gen; + } + }; + + + case_split_queue * mk_case_split_queue(context & ctx, front_end_params & p) { + if (p.m_relevancy_lvl < 2 && (p.m_case_split_strategy == CS_RELEVANCY || p.m_case_split_strategy == CS_RELEVANCY_ACTIVITY || + p.m_case_split_strategy == CS_RELEVANCY_GOAL)) { + warning_msg("relevacy must be enabled to use option CASE_SPLIT=3, 4 or 5"); + p.m_case_split_strategy = CS_ACTIVITY; + } + if (p.m_auto_config && (p.m_case_split_strategy == CS_RELEVANCY || p.m_case_split_strategy == CS_RELEVANCY_ACTIVITY || + p.m_case_split_strategy == CS_RELEVANCY_GOAL)) { + warning_msg("auto configuration (option AUTO_CONFIG) must be disabled to use option CASE_SPLIT=3, 4 or 5"); + p.m_case_split_strategy = CS_ACTIVITY; + } + switch (p.m_case_split_strategy) { + case CS_ACTIVITY_DELAY_NEW: + return alloc(dact_case_split_queue, ctx, p); + case CS_ACTIVITY_WITH_CACHE: + return alloc(cact_case_split_queue, ctx, p); + case CS_RELEVANCY: + return alloc(rel_case_split_queue, ctx, p); + case CS_RELEVANCY_ACTIVITY: + return alloc(rel_act_case_split_queue, ctx, p); + case CS_RELEVANCY_GOAL: + return alloc(rel_goal_case_split_queue, ctx, p); + default: + return alloc(act_case_split_queue, ctx, p); + } + } + +}; + diff --git a/lib/smt_case_split_queue.h b/lib/smt_case_split_queue.h new file mode 100644 index 000000000..106b97e38 --- /dev/null +++ b/lib/smt_case_split_queue.h @@ -0,0 +1,55 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_case_split_queue.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2009-01-20. + +Revision History: + +--*/ +#ifndef _SMT_CASE_SPLIT_QUEUE_H_ +#define _SMT_CASE_SPLIT_QUEUE_H_ + +#include"smt_types.h" +#include"heap.h" +#include"smt_params.h" + +namespace smt { + class context; + + /** + \brief Abstract case split queue. + */ + class case_split_queue { + public: + virtual void activity_increased_eh(bool_var v) = 0; + virtual void mk_var_eh(bool_var v) = 0; + virtual void del_var_eh(bool_var v) = 0; + virtual void assign_lit_eh(literal l) {} + virtual void unassign_var_eh(bool_var v) = 0; + virtual void relevant_eh(expr * n) = 0; + virtual void init_search_eh() = 0; + virtual void end_search_eh() = 0; + virtual void internalize_instance_eh(expr * e, unsigned gen) {} + virtual void reset() = 0; + virtual void push_scope() = 0; + 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() {} + }; + + case_split_queue * mk_case_split_queue(context & ctx, front_end_params & p); +}; + +#endif /* _SMT_CASE_SPLIT_QUEUE_H_ */ + diff --git a/lib/smt_cg_table.cpp b/lib/smt_cg_table.cpp new file mode 100644 index 000000000..ed36384c1 --- /dev/null +++ b/lib/smt_cg_table.cpp @@ -0,0 +1,250 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_cg_table.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-19. + +Revision History: + +--*/ +#include"smt_cg_table.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" + +namespace smt { + +#if 0 + unsigned cg_table::cg_hash::operator()(enode * n) const { + if (n->is_commutative()) { + return combine_hash(n->get_decl_id(), + n->get_arg(0)->get_root()->hash() * + n->get_arg(1)->get_root()->hash()); + } + else { + unsigned num = n->get_num_args(); + switch (num) { + case 0: UNREACHABLE(); return 0; + case 1: + return combine_hash(n->get_decl_id(), n->get_arg(0)->get_root()->hash()); + case 2: { + unsigned a = n->get_decl_id(); + unsigned b = n->get_arg(0)->get_root()->hash(); + unsigned c = n->get_arg(1)->get_root()->hash(); + mix(a,b,c); + return c; + } + default: + return get_composite_hash(n, n->get_num_args(), m_khasher, m_chasher); + } + } + } + + bool cg_table::cg_eq::operator()(enode * n1, enode * n2) const { +#if 0 + static unsigned counter = 0; + static unsigned failed = 0; + bool r = congruent(n1, n2, m_commutativity); + if (!r) + failed++; + counter++; + if (counter % 100000 == 0) + std::cout << "cg_eq: " << counter << " " << failed << "\n"; + return r; +#else + return congruent(n1, n2, m_commutativity); +#endif + } + + cg_table::cg_table(ast_manager & m): + m_manager(m), + m_table(DEFAULT_HASHTABLE_INITIAL_CAPACITY, cg_hash(), cg_eq(m_commutativity)) { + } + + void cg_table::display(std::ostream & out) const { + out << "congruence table:\n"; + table::iterator it = m_table.begin(); + table::iterator end = m_table.end(); + for (; it != end; ++it) { + enode * n = *it; + out << mk_pp(n->get_owner(), m_manager) << "\n"; + } + } + + void cg_table::display_compact(std::ostream & out) const { + if (!m_table.empty()) { + out << "congruence table:\n"; + table::iterator it = m_table.begin(); + table::iterator end = m_table.end(); + for (; it != end; ++it) { + enode * n = *it; + out << "#" << n->get_owner()->get_id() << " "; + } + out << "\n"; + } + } + +#ifdef Z3DEBUG + bool cg_table::check_invariant() const { + table::iterator it = m_table.begin(); + table::iterator end = m_table.end(); + for (; it != end; ++it) { + enode * n = *it; + CTRACE("cg_table", !contains_ptr(n), tout << "#" << n->get_owner_id() << "\n";); + SASSERT(contains_ptr(n)); + } + return true; + } +#endif + +#else + // one table per func_decl implementation + unsigned cg_table::cg_hash::operator()(enode * n) const { + SASSERT(n->get_decl()->is_flat_associative() || n->get_num_args() >= 3); + unsigned a, b, c; + a = b = 0x9e3779b9; + c = 11; + + unsigned i = n->get_num_args(); + while (i >= 3) { + i--; + a += n->get_arg(i)->get_root()->hash(); + i--; + b += n->get_arg(i)->get_root()->hash(); + i--; + c += n->get_arg(i)->get_root()->hash(); + mix(a, b, c); + } + + switch (i) { + case 2: + b += n->get_arg(1)->get_root()->hash(); + __fallthrough; + case 1: + c += n->get_arg(0)->get_root()->hash(); + } + mix(a, b, c); + return c; + } + + bool cg_table::cg_eq::operator()(enode * n1, enode * n2) const { + SASSERT(n1->get_num_args() == n2->get_num_args()); + SASSERT(n1->get_decl() == n2->get_decl()); + unsigned num = n1->get_num_args(); + for (unsigned i = 0; i < num; i++) + if (n1->get_arg(i)->get_root() != n2->get_arg(i)->get_root()) + return false; + return true; + } + + cg_table::cg_table(ast_manager & m): + m_manager(m) { + } + + cg_table::~cg_table() { + reset(); + } + + void * cg_table::mk_table_for(func_decl * d) { + void * r; + SASSERT(d->get_arity() >= 1); + switch (d->get_arity()) { + case 1: + r = TAG(void*, alloc(unary_table), UNARY); + SASSERT(GET_TAG(r) == UNARY); + return r; + case 2: + if (d->is_flat_associative()) { + // applications of declarations that are flat-assoc (e.g., +) may have many arguments. + r = TAG(void*, alloc(table), NARY); + SASSERT(GET_TAG(r) == NARY); + return r; + } + else if (d->is_commutative()) { + r = TAG(void*, alloc(comm_table, cg_comm_hash(), cg_comm_eq(m_commutativity)), BINARY_COMM); + SASSERT(GET_TAG(r) == BINARY_COMM); + return r; + } + else { + r = TAG(void*, alloc(binary_table), BINARY); + SASSERT(GET_TAG(r) == BINARY); + return r; + } + default: + r = TAG(void*, alloc(table), NARY); + SASSERT(GET_TAG(r) == NARY); + return r; + } + } + + unsigned cg_table::set_func_decl_id(enode * n) { + func_decl * f = n->get_decl(); + unsigned tid; + if (!m_func_decl2id.find(f, tid)) { + tid = m_tables.size(); + m_func_decl2id.insert(f, tid); + m_manager.inc_ref(f); + SASSERT(tid <= m_tables.size()); + m_tables.push_back(mk_table_for(f)); + } + SASSERT(tid < m_tables.size()); + n->set_func_decl_id(tid); + DEBUG_CODE({ + unsigned tid_prime; + SASSERT(m_func_decl2id.find(n->get_decl(), tid_prime) && tid == tid_prime); + }); + return tid; + } + + void cg_table::reset() { + ptr_vector::iterator it = m_tables.begin(); + ptr_vector::iterator end = m_tables.end(); + for (; it != end; ++it) { + void * t = *it; + switch (GET_TAG(t)) { + case UNARY: + dealloc(UNTAG(unary_table*, t)); + break; + case BINARY: + dealloc(UNTAG(binary_table*, t)); + break; + case BINARY_COMM: + dealloc(UNTAG(comm_table*, t)); + break; + case NARY: + dealloc(UNTAG(table*, t)); + break; + } + } + m_tables.reset(); + obj_map::iterator it2 = m_func_decl2id.begin(); + obj_map::iterator end2 = m_func_decl2id.end(); + for (; it2 != end2; ++it2) + m_manager.dec_ref(it2->m_key); + m_func_decl2id.reset(); + } + + void cg_table::display(std::ostream & out) const { + } + + void cg_table::display_compact(std::ostream & out) const { + } + +#ifdef Z3DEBUG + bool cg_table::check_invariant() const { + return true; + } +#endif + +#endif + +}; + diff --git a/lib/smt_cg_table.h b/lib/smt_cg_table.h new file mode 100644 index 000000000..12f6fca4f --- /dev/null +++ b/lib/smt_cg_table.h @@ -0,0 +1,354 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_cg_table.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-19. + +Revision History: + +--*/ +#ifndef _SMT_CG_TABLE_H_ +#define _SMT_CG_TABLE_H_ + +#include"smt_enode.h" +#include"hashtable.h" +#include"chashtable.h" + +namespace smt { + + typedef std::pair enode_bool_pair; + +#if 0 + /** + \brief Congruence table. + */ + class cg_table { + struct cg_khasher { + unsigned operator()(enode const * n) const { return n->get_decl_id(); } + }; + + struct cg_chasher { + unsigned operator()(enode const * n, unsigned idx) const { + return n->get_arg(idx)->get_root()->hash(); + } + }; + + struct cg_hash { + cg_khasher m_khasher; + cg_chasher m_chasher; + public: + unsigned operator()(enode * n) const; + }; + + struct cg_eq { + bool & m_commutativity; + cg_eq(bool & comm):m_commutativity(comm) {} + bool operator()(enode * n1, enode * n2) const; + }; + + typedef ptr_hashtable table; + + ast_manager & m_manager; + bool m_commutativity; //!< true if the last found congruence used commutativity + table m_table; + public: + cg_table(ast_manager & m); + + /** + \brief Try to insert n into the table. If the table already + contains an element n' congruent to n, then do nothing and + return n' and a boolean indicating whether n and n' are congruence + modulo commutativity, otherwise insert n and return (n,false). + */ + enode_bool_pair insert(enode * n) { + // it doesn't make sense to insert a constant. + SASSERT(n->get_num_args() > 0); + m_commutativity = false; + enode * n_prime = m_table.insert_if_not_there(n); + SASSERT(contains(n)); + return enode_bool_pair(n_prime, m_commutativity); + } + + void erase(enode * n) { + SASSERT(n->get_num_args() > 0); + m_table.erase(n); + SASSERT(!contains(n)); + } + + bool contains(enode * n) const { + return m_table.contains(n); + } + + enode * find(enode * n) const { + enode * r = 0; + return m_table.find(n, r) ? r : 0; + } + + bool contains_ptr(enode * n) const { + enode * n_prime; + return m_table.find(n, n_prime) && n == n_prime; + } + + void reset() { + m_table.reset(); + } + + void display(std::ostream & out) const; + + void display_compact(std::ostream & out) const; +#ifdef Z3DEBUG + bool check_invariant() const; +#endif + }; +#else + // one table per function symbol + + /** + \brief Congruence table. + */ + class cg_table { + struct cg_unary_hash { + unsigned operator()(enode * n) const { + SASSERT(n->get_num_args() == 1); + return n->get_arg(0)->get_root()->hash(); + } + }; + + struct cg_unary_eq { + bool operator()(enode * n1, enode * n2) const { + SASSERT(n1->get_num_args() == 1); + SASSERT(n2->get_num_args() == 1); + SASSERT(n1->get_decl() == n2->get_decl()); + return n1->get_arg(0)->get_root() == n2->get_arg(0)->get_root(); + } + }; + + typedef chashtable unary_table; + + struct cg_binary_hash { + unsigned operator()(enode * n) const { + SASSERT(n->get_num_args() == 2); + // too many collisions + // unsigned r = 17 + n->get_arg(0)->get_root()->hash(); + // return r * 31 + n->get_arg(1)->get_root()->hash(); + return combine_hash(n->get_arg(0)->get_root()->hash(), n->get_arg(1)->get_root()->hash()); + } + }; + + struct cg_binary_eq { + bool operator()(enode * n1, enode * n2) const { + SASSERT(n1->get_num_args() == 2); + SASSERT(n2->get_num_args() == 2); + SASSERT(n1->get_decl() == n2->get_decl()); +#if 1 + return + n1->get_arg(0)->get_root() == n2->get_arg(0)->get_root() && + n1->get_arg(1)->get_root() == n2->get_arg(1)->get_root(); +#else + bool r = + n1->get_arg(0)->get_root() == n2->get_arg(0)->get_root() && + n1->get_arg(1)->get_root() == n2->get_arg(1)->get_root(); + static unsigned counter = 0; + static unsigned failed = 0; + if (!r) + failed++; + counter++; + if (counter % 100000 == 0) + std::cerr << "[cg_eq] " << counter << " " << failed << "\n"; + return r; +#endif + } + }; + + typedef chashtable binary_table; + + struct cg_comm_hash { + unsigned operator()(enode * n) const { + SASSERT(n->get_num_args() == 2); + unsigned h1 = n->get_arg(0)->get_root()->hash(); + unsigned h2 = n->get_arg(1)->get_root()->hash(); + if (h1 > h2) + std::swap(h1, h2); + return hash_u((h1 << 16) | (h2 & 0xFFFF)); + } + }; + + struct cg_comm_eq { + bool & m_commutativity; + cg_comm_eq(bool & c):m_commutativity(c) {} + bool operator()(enode * n1, enode * n2) const { + SASSERT(n1->get_num_args() == 2); + SASSERT(n2->get_num_args() == 2); + SASSERT(n1->get_decl() == n2->get_decl()); + enode * c1_1 = n1->get_arg(0)->get_root(); + enode * c1_2 = n1->get_arg(1)->get_root(); + enode * c2_1 = n2->get_arg(0)->get_root(); + enode * c2_2 = n2->get_arg(1)->get_root(); + if (c1_1 == c2_1 && c1_2 == c2_2) { + return true; + } + if (c1_1 == c2_2 && c1_2 == c2_1) { + m_commutativity = true; + return true; + } + return false; + } + }; + + typedef chashtable comm_table; + + struct cg_hash { + unsigned operator()(enode * n) const; + }; + + struct cg_eq { + bool operator()(enode * n1, enode * n2) const; + }; + + typedef chashtable table; + + ast_manager & m_manager; + bool m_commutativity; //!< true if the last found congruence used commutativity + ptr_vector m_tables; + obj_map m_func_decl2id; + + enum table_kind { + UNARY, + BINARY, + BINARY_COMM, + NARY + }; + + void * mk_table_for(func_decl * d); + unsigned set_func_decl_id(enode * n); + + void * get_table(enode * n) { + unsigned tid = n->get_func_decl_id(); + if (tid == UINT_MAX) + tid = set_func_decl_id(n); + SASSERT(tid < m_tables.size()); + return m_tables[tid]; + } + + public: + cg_table(ast_manager & m); + ~cg_table(); + + /** + \brief Try to insert n into the table. If the table already + contains an element n' congruent to n, then do nothing and + return n' and a boolean indicating whether n and n' are congruence + modulo commutativity, otherwise insert n and return (n,false). + */ + enode_bool_pair insert(enode * n) { + // it doesn't make sense to insert a constant. + SASSERT(n->get_num_args() > 0); + enode * n_prime; + void * t = get_table(n); + switch (static_cast(GET_TAG(t))) { + case UNARY: + n_prime = UNTAG(unary_table*, t)->insert_if_not_there(n); + return enode_bool_pair(n_prime, false); + case BINARY: + n_prime = UNTAG(binary_table*, t)->insert_if_not_there(n); + return enode_bool_pair(n_prime, false); + case BINARY_COMM: + m_commutativity = false; + n_prime = UNTAG(comm_table*, t)->insert_if_not_there(n); + return enode_bool_pair(n_prime, m_commutativity); + default: + n_prime = UNTAG(table*, t)->insert_if_not_there(n); + return enode_bool_pair(n_prime, false); + } + } + + void erase(enode * n) { + SASSERT(n->get_num_args() > 0); + void * t = get_table(n); + switch (static_cast(GET_TAG(t))) { + case UNARY: + UNTAG(unary_table*, t)->erase(n); + break; + case BINARY: + UNTAG(binary_table*, t)->erase(n); + break; + case BINARY_COMM: + UNTAG(comm_table*, t)->erase(n); + break; + default: + UNTAG(table*, t)->erase(n); + break; + } + } + + bool contains(enode * n) const { + SASSERT(n->get_num_args() > 0); + void * t = const_cast(this)->get_table(n); + switch (static_cast(GET_TAG(t))) { + case UNARY: + return UNTAG(unary_table*, t)->contains(n); + case BINARY: + return UNTAG(binary_table*, t)->contains(n); + case BINARY_COMM: + return UNTAG(comm_table*, t)->contains(n); + default: + return UNTAG(table*, t)->contains(n); + } + } + + enode * find(enode * n) const { + SASSERT(n->get_num_args() > 0); + enode * r = 0; + void * t = const_cast(this)->get_table(n); + switch (static_cast(GET_TAG(t))) { + case UNARY: + return UNTAG(unary_table*, t)->find(n, r) ? r : 0; + case BINARY: + return UNTAG(binary_table*, t)->find(n, r) ? r : 0; + case BINARY_COMM: + return UNTAG(comm_table*, t)->find(n, r) ? r : 0; + default: + return UNTAG(table*, t)->find(n, r) ? r : 0; + } + } + + bool contains_ptr(enode * n) const { + enode * r; + SASSERT(n->get_num_args() > 0); + void * t = const_cast(this)->get_table(n); + switch (static_cast(GET_TAG(t))) { + case UNARY: + return UNTAG(unary_table*, t)->find(n, r) && n == r; + case BINARY: + return UNTAG(binary_table*, t)->find(n, r) && n == r; + case BINARY_COMM: + return UNTAG(comm_table*, t)->find(n, r) && n == r; + default: + return UNTAG(table*, t)->find(n, r) && n == r; + } + } + + void reset(); + + void display(std::ostream & out) const; + + void display_compact(std::ostream & out) const; +#ifdef Z3DEBUG + bool check_invariant() const; +#endif + }; + +#endif +}; + +#endif /* _SMT_CG_TABLE_H_ */ + diff --git a/lib/smt_checker.cpp b/lib/smt_checker.cpp new file mode 100644 index 000000000..176769d77 --- /dev/null +++ b/lib/smt_checker.cpp @@ -0,0 +1,188 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_checker.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-20. + +Revision History: + +--*/ +#include"smt_context.h" +#include"smt_checker.h" +#include"ast_ll_pp.h" + +namespace smt { + + bool checker::all_args(app * a, bool is_true) { + unsigned num_args = a->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + if (!check(a->get_arg(i), is_true)) + return false; + } + return true; + } + + bool checker::any_arg(app * a, bool is_true) { + unsigned num_args = a->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + if (check(a->get_arg(i), is_true)) + return true; + } + return false; + } + + bool checker::check_core(expr * n, bool is_true) { + SASSERT(m_manager.is_bool(n)); + if (m_context.b_internalized(n) && m_context.is_relevant(n)) { + lbool val = m_context.get_assignment(n); + return val != l_undef && is_true == (val == l_true); + } + if (!is_app(n)) + return false; + app * a = to_app(n); + if (a->get_family_id() == m_manager.get_basic_family_id()) { + switch (a->get_decl_kind()) { + case OP_TRUE: + return is_true; + case OP_FALSE: + return !is_true; + case OP_NOT: + return check(a->get_arg(0), !is_true); + case OP_OR: + return is_true ? any_arg(a, true) : all_args(a, false); + case OP_AND: + return is_true ? all_args(a, true) : any_arg(a, false); + case OP_IFF: + if (is_true) { + return + (check(a->get_arg(0), true) && + check(a->get_arg(1), true)) || + (check(a->get_arg(0), false) && + check(a->get_arg(1), false)); + } + else { + return + (check(a->get_arg(0), true) && + check(a->get_arg(1), false)) || + (check(a->get_arg(0), false) && + check(a->get_arg(1), true)); + } + case OP_ITE: { + if (m_context.lit_internalized(a->get_arg(0)) && m_context.is_relevant(a->get_arg(0))) { + switch (m_context.get_assignment(a->get_arg(0))) { + case l_false: return check(a->get_arg(2), is_true); + case l_undef: return false; + case l_true: return check(a->get_arg(1), is_true); + } + } + return check(a->get_arg(1), is_true) && check(a->get_arg(2), is_true); + } + case OP_EQ: { + enode * lhs = get_enode_eq_to(a->get_arg(0)); + enode * rhs = get_enode_eq_to(a->get_arg(1)); + if (lhs && rhs && m_context.is_relevant(lhs) && m_context.is_relevant(rhs)) { + if (is_true && lhs->get_root() == rhs->get_root()) + return true; + // if (!is_true && m_context.is_ext_diseq(lhs, rhs, 2)) + if (!is_true && m_context.is_diseq(lhs, rhs)) + return true; + } + return false; + } + default: + break; + } + } + enode * e = get_enode_eq_to(a); + if (e && e->is_bool() && m_context.is_relevant(e)) { + lbool val = m_context.get_assignment(e->get_owner()); + return val != l_undef && is_true == (val == l_true); + } + return false; + } + + bool checker::check(expr * n, bool is_true) { + bool r; + if (n->get_ref_count() > 1 && m_is_true_cache[is_true].find(n, r)) + return r; + r = check_core(n, is_true); + if (n->get_ref_count() > 1) + m_is_true_cache[is_true].insert(n, r); + return r; + } + + enode * checker::get_enode_eq_to_core(app * n) { + ptr_buffer buffer; + unsigned num = n->get_num_args(); + for (unsigned i = 0; i < num; i++) { + enode * arg = get_enode_eq_to(n->get_arg(i)); + if (arg == 0) + return 0; + buffer.push_back(arg); + } + enode * e = m_context.get_enode_eq_to(n->get_decl(), num, buffer.c_ptr()); + if (e == 0) + return 0; + return m_context.is_relevant(e) ? e : 0; + } + + enode * checker::get_enode_eq_to(expr * n) { + if (is_var(n)) { + unsigned idx = to_var(n)->get_idx(); + if (idx >= m_num_bindings) + return 0; + return m_bindings[m_num_bindings - idx - 1]; + } + if (m_context.e_internalized(n) && m_context.is_relevant(n)) + return m_context.get_enode(n); + if (!is_app(n) || to_app(n)->get_num_args() == 0) + return 0; + enode * r = 0; + if (n->get_ref_count() > 1 && m_to_enode_cache.find(n, r)) + return r; + r = get_enode_eq_to_core(to_app(n)); + if (n->get_ref_count() > 1) + m_to_enode_cache.insert(n, r); + return r; + } + + bool checker::is_sat(expr * n, unsigned num_bindings, enode * const * bindings) { + flet l1(m_num_bindings, num_bindings); + flet l2(m_bindings, bindings); + bool r = check(n, true); + m_is_true_cache[0].reset(); + m_is_true_cache[1].reset(); + m_to_enode_cache.reset(); + return r; + } + + bool checker::is_unsat(expr * n, unsigned num_bindings, enode * const * bindings) { + flet l1(m_num_bindings, num_bindings); + flet l2(m_bindings, bindings); + bool r = check(n, false); + m_is_true_cache[0].reset(); + m_is_true_cache[1].reset(); + m_to_enode_cache.reset(); + return r; + } + + checker::checker(context & c): + m_context(c), + m_manager(c.get_manager()), + m_num_bindings(0), + m_bindings(0) { + } + +}; + + + diff --git a/lib/smt_checker.h b/lib/smt_checker.h new file mode 100644 index 000000000..57ea2bdfe --- /dev/null +++ b/lib/smt_checker.h @@ -0,0 +1,57 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_checker.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-20. + +Revision History: + +--*/ +#ifndef _SMT_CHECKER_H_ +#define _SMT_CHECKER_H_ + +#include"ast.h" +#include"obj_hashtable.h" + +namespace smt { + + class context; + + class checker { + + typedef obj_map expr2bool; + typedef obj_map expr2enode; + + context & m_context; + ast_manager & m_manager; + expr2bool m_is_true_cache[2]; + expr2enode m_to_enode_cache; + unsigned m_num_bindings; + enode * const * m_bindings; + + bool all_args(app * a, bool is_true); + bool any_arg(app * a, bool is_true); + bool check_core(expr * n, bool is_true); + bool check(expr * n, bool is_true); + enode * get_enode_eq_to_core(app * n); + enode * get_enode_eq_to(expr * n); + + public: + checker(context & c); + bool is_sat(expr * n, unsigned num_bindings = 0, enode * const * bindings = 0); + bool is_unsat(expr * n, unsigned num_bindings = 0, enode * const * bindings = 0); + }; + +}; + +#endif /* _SMT_CHECKER_H_ */ + diff --git a/lib/smt_classifier.h b/lib/smt_classifier.h new file mode 100644 index 000000000..1ce27f3eb --- /dev/null +++ b/lib/smt_classifier.h @@ -0,0 +1,46 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_classifier.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-24. + +Revision History: + +--*/ +#ifndef _SMT_CLASSIFIER_H_ +#define _SMT_CLASSIFIER_H_ + +#include"static_features.h" + +namespace smt { + + class classifier { + context & m_context; + ast_manager & m_manager; + static_features m_static_features; + symbol m_logic; + public: + classifier(context & c); + /** + \brief Give a hint by specifying the logic used to describe a problem. + */ + void set_logic(symbol & s); + /** + \brief Setup the logical context for solving the following formulas. + */ + void setup(unsigned num_formulas, expr * const * fs); + }; + +}; + +#endif /* _SMT_CLASSIFIER_H_ */ + diff --git a/lib/smt_clause.cpp b/lib/smt_clause.cpp new file mode 100644 index 000000000..d0f6bdeaa --- /dev/null +++ b/lib/smt_clause.cpp @@ -0,0 +1,118 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_clause.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-19. + +Revision History: + +--*/ +#include"smt_clause.h" +#include"smt_justification.h" +#include"ast_ll_pp.h" + +namespace smt { + /** + \brief Create a new clause. + bool_var2expr_map is a mapping from bool_var -> expr, it is only used if save_atoms == true. + */ + clause * clause::mk(ast_manager & m, unsigned num_lits, literal * lits, clause_kind k, justification * js, + clause_del_eh * del_eh, bool save_atoms, expr * const * bool_var2expr_map) { + SASSERT(k == CLS_AUX || js == 0 || !js->in_region()); + SASSERT(num_lits >= 2); + unsigned sz = get_obj_size(num_lits, k, save_atoms, del_eh != 0, js != 0); + void * mem = m.get_allocator().allocate(sz); + clause * cls = new (mem) clause(); + cls->m_num_literals = num_lits; + cls->m_capacity = num_lits; + cls->m_kind = k; + cls->m_reinit = save_atoms; + cls->m_reinternalize_atoms = save_atoms; + cls->m_has_atoms = save_atoms; + cls->m_has_del_eh = del_eh != 0; + cls->m_has_justification = js != 0; + cls->m_deleted = false; + SASSERT(!m.proofs_enabled() || js != 0); + memcpy(cls->m_lits, lits, sizeof(literal) * num_lits); + if (cls->is_lemma()) + cls->set_activity(1); + if (del_eh) + *(const_cast(cls->get_del_eh_addr())) = del_eh; + if (js) + *(const_cast(cls->get_justification_addr())) = js; + if (save_atoms) { + for (unsigned i = 0; i < num_lits; i++) { + expr * atom = bool_var2expr_map[lits[i].var()]; + m.inc_ref(atom); + const_cast(cls->get_atoms_addr())[i] = TAG(expr*, atom, lits[i].sign()); + } + } + + DEBUG_CODE({ + SASSERT(!cls->is_lemma() || cls->get_activity() == 1); + SASSERT(cls->get_del_eh() == del_eh); + SASSERT(cls->get_justification() == js); + for (unsigned i = 0; i < num_lits; i++) { + SASSERT(cls->get_literal(i) == lits[i]); + SASSERT(!save_atoms || cls->get_atom(i) == bool_var2expr_map[lits[i].var()]); + }}); + return cls; + } + + void clause::deallocate(ast_manager & m) { + clause_del_eh * del_eh = get_del_eh(); + if (del_eh) + (*del_eh)(m, this); + if (is_lemma() && m_has_justification) { + justification * js = get_justification(); + if (js) { + SASSERT(!js->in_region()); + js->del_eh(m); + dealloc(js); + } + } + unsigned num_atoms = get_num_atoms(); + for (unsigned i = 0; i < num_atoms; i++) { + SASSERT(m_reinit || get_atom(i) == 0); + m.dec_ref(get_atom(i)); + } + m.get_allocator().deallocate(get_obj_size(m_capacity, get_kind(), m_has_atoms, m_has_del_eh, m_has_justification), this); + } + + void clause::release_atoms(ast_manager & m) { + unsigned num_atoms = get_num_atoms(); + for (unsigned i = 0; i < num_atoms; i++) { + m.dec_ref(get_atom(i)); + const_cast(get_atoms_addr())[i] = 0; + } + } + + void clause::display(std::ostream & out, ast_manager & m, expr * const * bool_var2expr_map) const { + out << "(clause"; + for (unsigned i = 0; i < m_num_literals; i++) { + out << " "; + m_lits[i].display(out, m, bool_var2expr_map); + } + out << ")"; + } + + void clause::display_compact(std::ostream & out, ast_manager & m, expr * const * bool_var2expr_map) const { + out << "(clause"; + for (unsigned i = 0; i < m_num_literals; i++) { + out << " "; + m_lits[i].display_compact(out, bool_var2expr_map); + } + out << ")"; + } + +}; + diff --git a/lib/smt_clause.h b/lib/smt_clause.h new file mode 100644 index 000000000..200508c53 --- /dev/null +++ b/lib/smt_clause.h @@ -0,0 +1,258 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_clause.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-18. + +Revision History: + +--*/ +#ifndef _SMT_CLAUSE_H_ +#define _SMT_CLAUSE_H_ + +#include"ast.h" +#include"smt_literal.h" +#include"tptr.h" +#include"obj_hashtable.h" +#include"smt_justification.h" + +namespace smt { + + class clause; + + /** + \brief Abstract functor: clause deletion event handler. + */ + class clause_del_eh { + public: + virtual ~clause_del_eh() {} + virtual void operator()(ast_manager & m, clause * cls) = 0; + }; + + enum clause_kind { + CLS_AUX, + CLS_LEARNED, + CLS_AUX_LEMMA + }; + + /** + \brief A SMT clause. + + A clause has several optional fields, I store space for them only if they are actually used. + */ + class clause { + unsigned m_num_literals; + unsigned m_capacity:24; //!< some of the clause literals can be simplified and removed, this field contains the original number of literals (used for GC). + unsigned m_kind:2; //!< kind + unsigned m_reinit:1; //!< true if the clause is in the reinit stack (only for learned clauses and aux_lemmas) + unsigned m_reinternalize_atoms:1; //!< true if atoms must be reinitialized during reinitialization + unsigned m_has_atoms:1; //!< true if the clause has memory space for storing atoms. + unsigned m_has_del_eh:1; //!< true if must notify event handler when deleted. + unsigned m_has_justification:1; //!< true if the clause has a justification attached to it. + unsigned m_deleted:1; //!< true if the clause is marked for deletion by was not deleted yet because it is referenced by some data-structure (e.g., m_lemmas) + literal m_lits[0]; + + static unsigned get_obj_size(unsigned num_lits, clause_kind k, bool has_atoms, bool has_del_eh, bool has_justification) { + unsigned r = sizeof(clause) + sizeof(literal) * num_lits; + if (k != CLS_AUX) + r += sizeof(unsigned); + if (has_atoms) + r += sizeof(expr*) * num_lits; + if (has_del_eh) + r += sizeof(clause_del_eh *); + if (has_justification) + r += sizeof(justification *); + return r; + } + + unsigned const * get_activity_addr() const { + return reinterpret_cast(m_lits + m_capacity); + } + + unsigned * get_activity_addr() { + return reinterpret_cast(m_lits + m_capacity); + } + + clause_del_eh * const * get_del_eh_addr() const { + unsigned const * addr = get_activity_addr(); + if (is_lemma()) + addr ++; + return reinterpret_cast(addr); + } + + justification * const * get_justification_addr() const { + clause_del_eh * const * addr = get_del_eh_addr(); + if (m_has_del_eh) + addr ++; + return reinterpret_cast(addr); + } + + expr * const * get_atoms_addr() const { + justification * const * addr = get_justification_addr(); + if (m_has_justification) + addr ++; + return reinterpret_cast(addr); + } + + friend class context; + + void swap_lits(unsigned idx1, unsigned idx2) { + literal tmp = get_literal(idx1); + set_literal(idx1, get_literal(idx2)); + set_literal(idx2, tmp); + } + + bool is_watch(literal l) const { + return m_lits[0] == l || m_lits[1] == l; + } + + void set_literal(unsigned idx, literal l) { + m_lits[idx] = l; + } + + void set_num_literals(unsigned n) { + SASSERT(n <= m_num_literals); + SASSERT(!m_reinit); + m_num_literals = n; + } + + void set_justification(justification * new_js) { + SASSERT(m_has_justification); + SASSERT(!m_reinit); + SASSERT(!is_lemma() || new_js == 0 || !new_js->in_region()); + justification ** js_addr = const_cast(get_justification_addr()); + *js_addr = new_js; + } + + void release_atoms(ast_manager & m); + + public: + static clause * mk(ast_manager & m, unsigned num_lits, literal * lits, clause_kind k, justification * js = 0, + clause_del_eh * del_eh = 0, bool save_atoms = false, expr * const * bool_var2expr_map = 0); + + void deallocate(ast_manager & m); + + clause_kind get_kind() const { + return static_cast(m_kind); + } + + bool is_lemma() const { + return get_kind() != CLS_AUX; + } + + bool is_learned() const { + return get_kind() == CLS_LEARNED; + } + + bool is_aux_lemma() const { + return get_kind() == CLS_AUX_LEMMA; + } + + bool in_reinit_stack() const { + return m_reinit; + } + + bool reinternalize_atoms() const { + return m_reinternalize_atoms; + } + + unsigned get_num_literals() const { + return m_num_literals; + } + + literal get_literal(unsigned idx) const { + SASSERT(idx < m_num_literals); + return m_lits[idx]; + } + + literal & get_literal(unsigned idx) { + SASSERT(idx < m_num_literals); + return m_lits[idx]; + } + + literal * begin_literals() { return m_lits; } + + literal * end_literals() { return m_lits + m_num_literals; } + + literal const * begin_literals() const { return m_lits; } + + literal const * end_literals() const { return m_lits + m_num_literals; } + + unsigned get_activity() const { + SASSERT(is_lemma()); + return *(get_activity_addr()); + } + + void set_activity(unsigned act) { + SASSERT(is_lemma()); + *(get_activity_addr()) = act; + } + + clause_del_eh * get_del_eh() const { + return m_has_del_eh ? *(get_del_eh_addr()) : 0; + } + + justification * get_justification() const { + return m_has_justification ? *(get_justification_addr()) : 0; + } + + unsigned get_num_atoms() const { + return m_reinternalize_atoms ? m_num_literals : 0; + } + + expr * get_atom(unsigned idx) const { + SASSERT(idx < get_num_atoms()); + return UNTAG(expr*, get_atoms_addr()[idx]); + } + + bool get_atom_sign(unsigned idx) const { + SASSERT(idx < get_num_atoms()); + return GET_TAG(get_atoms_addr()[idx]) == 1; + } + + bool erase_atom(unsigned idx); + + void inc_clause_activity() { + SASSERT(is_lemma()); + set_activity(get_activity() + 1); + } + + void display(std::ostream & out, ast_manager & m, expr * const * bool_var2expr_map) const; + + void display_compact(std::ostream & out, ast_manager & m, expr * const * bool_var2expr_map) const; + + unsigned hash() const { + return get_ptr_hash(this); + } + + void mark_as_deleted(ast_manager & m) { + SASSERT(!m_deleted); + m_deleted = true; + clause_del_eh * del_eh = get_del_eh(); + if (del_eh) { + (*del_eh)(m, this); + *(const_cast(get_del_eh_addr())) = 0; + } + } + + bool deleted() const { + return m_deleted; + } + }; + + typedef ptr_vector clause_vector; + + typedef obj_hashtable clause_set; +}; + +#endif /* _SMT_CLAUSE_H_ */ + diff --git a/lib/smt_conflict_resolution.cpp b/lib/smt_conflict_resolution.cpp new file mode 100644 index 000000000..2bc32e217 --- /dev/null +++ b/lib/smt_conflict_resolution.cpp @@ -0,0 +1,1430 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_conflict_resolution.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-25. + +Revision History: + +--*/ +#include"smt_context.h" +#include"smt_conflict_resolution.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" + +namespace smt { + + // --------------------------- + // + // Base class + // + // --------------------------- + + conflict_resolution::conflict_resolution(ast_manager & m, + context & ctx, + dyn_ack_manager & dyn_ack_manager, + front_end_params const & params, + literal_vector const & assigned_literals, + vector & watches + ): + m_manager(m), + m_params(params), + m_ctx(ctx), + m_dyn_ack_manager(dyn_ack_manager), + m_assigned_literals(assigned_literals), + m_lemma_atoms(m), + m_todo_js_qhead(0), + m_antecedents(0), + m_watches(watches), + m_new_proofs(m), + m_lemma_proof(m) + { + } + + /** + \brief Mark all enodes in a 'proof' tree brach starting at n + n -> ... -> root + */ + template + void conflict_resolution::mark_enodes_in_trans(enode * n) { + SASSERT(n->trans_reaches(n->get_root())); + while (n) { + if (Set) + n->set_mark(); + else + n->unset_mark(); + n = n->m_trans.m_target; + } + } + + /** + \brief Find a common ancestor (anc) of n1 and n2 in the 'proof' tree. + The common ancestor is used to produce irredundant transitivity proofs. + + n1 = a1 = ... = ai = ANC = ... = root + n2 = b1 = ... = bj = ANC = ... = root + + The equalities ANC = ... = root should not be included in the proof of n1 = n2. + + The irredundant proof for n1 = n2 is: + + n1 = a1 = ... = ai = ANC = bj = ... = b1 = n2 + */ + enode * conflict_resolution::find_common_ancestor(enode * n1, enode * n2) { + SASSERT(n1->get_root() == n2->get_root()); + mark_enodes_in_trans(n1); + while (true) { + SASSERT(n2); + if (n2->is_marked()) { + mark_enodes_in_trans(n1); + return n2; + } + n2 = n2->m_trans.m_target; + } + } + + /** + \brief Process a eq_justification of lhs and rhs. + This method executes a step of eq_justification2literals + + This method may update m_antecedents, m_todo_js and m_todo_eqs. + */ + void conflict_resolution::eq_justification2literals(enode * lhs, enode * rhs, eq_justification js) { + SASSERT(m_antecedents); + switch(js.get_kind()) { + case eq_justification::AXIOM: + TRACE("conflict_detail", tout << "#" << lhs->get_owner_id() << " = " << rhs->get_owner_id() << " axiom\n";); + break; + case eq_justification::EQUATION: + TRACE("conflict_detail", tout << "#" << lhs->get_owner_id() << " = " << rhs->get_owner_id() << " was asserted\n" + << "literal: "; m_ctx.display_literal(tout, js.get_literal()); tout << "\n";); + m_antecedents->push_back(js.get_literal()); + break; + case eq_justification::JUSTIFICATION: + TRACE("conflict_detail", tout << "#" << lhs->get_owner_id() << " = " << rhs->get_owner_id() << " justification\n";); + mark_justification(js.get_justification()); + break; + case eq_justification::CONGRUENCE: { + TRACE("conflict_detail", tout << "#" << lhs->get_owner_id() << " = " << rhs->get_owner_id() << " congruence\n";); + if (!lhs->is_eq()) + TRACE("dyn_ack_target", tout << "dyn_ack_target2: " << lhs->get_owner_id() << " " << rhs->get_owner_id() << "\n";); + m_dyn_ack_manager.used_cg_eh(lhs->get_owner(), rhs->get_owner()); + unsigned num_args = lhs->get_num_args(); + SASSERT(num_args == rhs->get_num_args()); + if (js.used_commutativity()) { + SASSERT(num_args == 2); + mark_eq(lhs->get_arg(0), rhs->get_arg(1)); + mark_eq(lhs->get_arg(1), rhs->get_arg(0)); + } + else { + for (unsigned i = 0; i < num_args; i++) + mark_eq(lhs->get_arg(i), rhs->get_arg(i)); + } + break; + } + default: + UNREACHABLE(); + } + } + + /** + \brief Process the transitivity 'proof' from n1 and n2, where + n1 and n2 are in the same branch + + n1 -> ... -> n2 + + This method may update m_antecedents, m_todo_js and m_todo_eqs. + + The resultant set of literals is stored in m_antecedents. + */ + void conflict_resolution::eq_branch2literals(enode * n1, enode * n2) { + SASSERT(n1->trans_reaches(n2)); + while (n1 != n2) { + eq_justification2literals(n1, n1->m_trans.m_target, n1->m_trans.m_justification); + n1 = n1->m_trans.m_target; + } + } + + /** + \brief Process the justification of n1 = n2. + + This method may update m_antecedents, m_todo_js and m_todo_eqs. + + The resultant set of literals is stored in m_antecedents. + */ + void conflict_resolution::eq2literals(enode * n1, enode * n2) { + enode * c = find_common_ancestor(n1, n2); + eq_branch2literals(n1, c); + eq_branch2literals(n2, c); + m_dyn_ack_manager.used_eq_eh(n1->get_owner(), n2->get_owner(), c->get_owner()); + } + + /** + \brief Extract the antecedent literals from a justification object. + + The result is stored in result. + + \remark This method does not reset the vectors m_antecedents, m_todo_js, m_todo_eqs, nor reset the + marks in the justification objects. + */ + void conflict_resolution::justification2literals_core(justification * js, literal_vector & result) { + SASSERT(m_todo_js_qhead <= m_todo_js.size()); + m_antecedents = &result; + mark_justification(js); + while (true) { + unsigned sz = m_todo_js.size(); + while (m_todo_js_qhead < sz) { + justification * js = m_todo_js[m_todo_js_qhead]; + m_todo_js_qhead++; + js->get_antecedents(*this); + } + while (!m_todo_eqs.empty()) { + enode_pair p = m_todo_eqs.back(); + m_todo_eqs.pop_back(); + eq2literals(p.first, p.second); + } + if (m_todo_js_qhead == m_todo_js.size()) { + m_antecedents = 0; + return; + } + } + } + + /** + \brief Unset the mark of the justifications stored in m_todo_js + */ + void conflict_resolution::unmark_justifications(unsigned old_js_qhead) { + SASSERT(old_js_qhead <= m_todo_js.size()); + justification_vector::iterator it = m_todo_js.begin() + old_js_qhead; + justification_vector::iterator end = m_todo_js.end(); + for (; it != end; ++it) { + TRACE("conflict_detail", tout << "unmarking: " << *it << "\n";); + (*it)->unset_mark(); + } + m_todo_js.shrink(old_js_qhead); + m_todo_js_qhead = old_js_qhead; + m_todo_eqs.reset(); + m_already_processed_eqs.reset(); + } + + /** + \brief Extract the antecedent literals from a justification object. + */ + void conflict_resolution::justification2literals(justification * js, literal_vector & result) { + SASSERT(m_todo_js.empty()); + SASSERT(m_todo_js_qhead == 0); + SASSERT(m_todo_eqs.empty()); + justification2literals_core(js, result); + unmark_justifications(0); + SASSERT(m_todo_eqs.empty()); + } + + /** + \brief Return maximum scope level of an antecedent literal of js. + */ + unsigned conflict_resolution::get_justification_max_lvl(justification * js) { + unsigned r = 0; + literal_vector & antecedents = m_tmp_literal_vector; + antecedents.reset(); + justification2literals(js, antecedents); + literal_vector::iterator it = antecedents.begin(); + literal_vector::iterator end = antecedents.end(); + for(; it != end; ++it) + r = std::max(r, m_ctx.get_assign_level(*it)); + return r; + } + + /** + \brief Return the maximum scope level of the antecedent literals of the given + justified literal. + */ + unsigned conflict_resolution::get_max_lvl(literal consequent, b_justification js) { + unsigned r = 0; + if (consequent != false_literal) + r = m_ctx.get_assign_level(consequent); + + switch (js.get_kind()) { + case b_justification::CLAUSE: { + clause * cls = js.get_clause(); + unsigned num_lits = cls->get_num_literals(); + unsigned i = 0; + if (consequent != false_literal) { + SASSERT(cls->get_literal(0) == consequent || cls->get_literal(1) == consequent); + if (cls->get_literal(0) == consequent) { + i = 1; + } + else { + r = std::max(r, m_ctx.get_assign_level(cls->get_literal(0))); + i = 2; + } + } + for(; i < num_lits; i++) + r = std::max(r, m_ctx.get_assign_level(cls->get_literal(i))); + justification * js = cls->get_justification(); + if (js) + r = std::max(r, get_justification_max_lvl(js)); + break; + } + case b_justification::BIN_CLAUSE: + r = std::max(r, m_ctx.get_assign_level(js.get_literal())); + break; + case b_justification::AXIOM: + break; + case b_justification::JUSTIFICATION: + r = std::max(r, get_justification_max_lvl(js.get_justification())); + break; + default: + UNREACHABLE(); + } + return r; + } + + void conflict_resolution::process_antecedent(literal antecedent, unsigned & num_marks) { + TRACE("conflict", tout << "processing antecedent: "; m_ctx.display_literal(tout, antecedent); tout << "\n";); + bool_var var = antecedent.var(); + unsigned lvl = m_ctx.get_assign_level(var); + SASSERT(var < static_cast(m_ctx.get_num_bool_vars())); + + if (!m_ctx.is_marked(var) && lvl > m_ctx.get_base_level()) { + m_ctx.set_mark(var); + m_ctx.inc_bvar_activity(var); + expr * n = m_ctx.bool_var2expr(var); + if (is_app(n)) { + family_id fid = to_app(n)->get_family_id(); + theory * th = m_ctx.get_theory(fid); + if (th) + th->conflict_resolution_eh(to_app(n), var); + } +#ifndef SMTCOMP + if (m_params.m_trace_stream != NULL) { + *m_params.m_trace_stream << "[resolve-lit] " << m_conflict_lvl - lvl << " "; + m_ctx.display_literal(*m_params.m_trace_stream, ~antecedent); + *m_params.m_trace_stream << "\n"; + } +#endif + if (lvl == m_conflict_lvl) { + num_marks++; + } + else { + m_lemma.push_back(~antecedent); + m_lemma_atoms.push_back(m_ctx.bool_var2expr(var)); + } + } + } + + void conflict_resolution::process_justification(justification * js, unsigned & num_marks) { + literal_vector & antecedents = m_tmp_literal_vector; + antecedents.reset(); + justification2literals_core(js, antecedents); + literal_vector::iterator it = antecedents.begin(); + literal_vector::iterator end = antecedents.end(); + for(; it != end; ++it) + process_antecedent(*it, num_marks); + } + + /** + \brief Skip literals from levels above m_conflict_lvl. + It returns an index idx such that m_assigned_literals[idx] <= m_conflict_lvl, and + for all idx' > idx, m_assigned_literals[idx'] > m_conflict_lvl + */ + unsigned conflict_resolution::skip_literals_above_conflict_level() { + unsigned idx = m_assigned_literals.size(); + if (idx == 0) { + return idx; + } + idx--; + // skip literals from levels above the conflict level + while (m_ctx.get_assign_level(m_assigned_literals[idx]) > m_conflict_lvl) { + SASSERT(idx > 0); + idx--; + } + return idx; + } + + /** + \brief Initialize conflict resolution data-structures. + Return false if the conflict cannot be resolved (it is at the search level). + */ + bool conflict_resolution::initialize_resolve(b_justification conflict, literal not_l, b_justification & js, literal & consequent) { + TRACE("conflict_detail", m_ctx.display(tout);); + m_lemma.reset(); + m_lemma_atoms.reset(); + SASSERT(m_ctx.get_search_level() >= m_ctx.get_base_level()); + js = conflict; + consequent = false_literal; + if (not_l != null_literal) + consequent = ~not_l; + + m_conflict_lvl = get_max_lvl(consequent, js); + + TRACE("conflict_bug", + tout << "conflict_lvl: " << m_conflict_lvl << " scope_lvl: " << m_ctx.get_scope_level() << " base_lvl: " << m_ctx.get_base_level() + << " search_lvl: " << m_ctx.get_search_level() << "\n"; + tout << "js.kind: " << js.get_kind() << "\n"; + m_ctx.display_literal(tout, consequent); tout << "\n";); + + // m_conflict_lvl can be smaller than m_ctx.get_search_level() when: + // there are user level scopes created using the Z3 API, and + // the previous levels were already inconsistent, or the inconsistency was + // triggered by an axiom or justification proof wrapper, this two kinds + // of justification are considered level zero. + + if (m_conflict_lvl <= m_ctx.get_search_level()) { + TRACE("conflict", tout << "problem is unsat\n";); + if (m_manager.proofs_enabled()) + mk_conflict_proof(conflict, not_l); + if (m_ctx.tracking_assumptions()) + mk_unsat_core(conflict, not_l); + return false; + } + + TRACE("conflict", tout << "conflict_lvl: " << m_conflict_lvl << "\n";); + + SASSERT(!m_assigned_literals.empty()); + + SASSERT(m_todo_js.empty()); + SASSERT(m_todo_js_qhead == 0); + SASSERT(m_todo_eqs.empty()); + return true; + } + + /** + \brief Cleanup datastructures used during resolve(), minimize lemma (when minimization is enabled), + compute m_new_scope_lvl and m_lemma_iscope_lvl, generate proof if needed. + + This method assumes that the lemma is stored in m_lemma (and the associated atoms in m_lemma_atoms). + + \warning This method assumes the literals in m_lemma[1] ... m_lemma[m_lemma.size() - 1] are marked. + */ + void conflict_resolution::finalize_resolve(b_justification conflict, literal not_l) { + unmark_justifications(0); + + TRACE("conflict", + tout << "before minimization:\n"; + m_ctx.display_literals(tout, m_lemma.size(), m_lemma.c_ptr()); + tout << "\n";); + + TRACE("conflict_verbose", + tout << "before minimization:\n"; + m_ctx.display_literals_verbose(tout, m_lemma.size(), m_lemma.c_ptr()); + tout << "\n";); + + if (m_params.m_minimize_lemmas) + minimize_lemma(); + + TRACE("conflict", + tout << "after minimization:\n"; + m_ctx.display_literals(tout, m_lemma.size(), m_lemma.c_ptr()); + tout << "\n";); + + TRACE("conflict_verbose", + tout << "after minimization:\n"; + m_ctx.display_literals_verbose(tout, m_lemma.size(), m_lemma.c_ptr()); + tout << "\n";); + + literal_vector::iterator it = m_lemma.begin(); + literal_vector::iterator end = m_lemma.end(); + m_new_scope_lvl = m_ctx.get_search_level(); + m_lemma_iscope_lvl = m_ctx.get_intern_level((*it).var()); + SASSERT(!m_ctx.is_marked((*it).var())); + ++it; + for(; it != end; ++it) { + bool_var var = (*it).var(); + if (var != null_bool_var) { + m_ctx.unset_mark(var); + unsigned lvl = m_ctx.get_assign_level(var); + if (lvl > m_new_scope_lvl) + m_new_scope_lvl = lvl; + lvl = m_ctx.get_intern_level(var); + if (lvl > m_lemma_iscope_lvl) + m_lemma_iscope_lvl = lvl; + } + } + + TRACE("conflict", + tout << "new scope level: " << m_new_scope_lvl << "\n"; + tout << "intern. scope level: " << m_lemma_iscope_lvl << "\n";); + + if (m_manager.proofs_enabled()) + mk_conflict_proof(conflict, not_l); + } + + bool conflict_resolution::resolve(b_justification conflict, literal not_l) { + b_justification js; + literal consequent; + + if (!initialize_resolve(conflict, not_l, js, consequent)) + return false; + + unsigned idx = skip_literals_above_conflict_level(); + + // save space for first uip + m_lemma.push_back(null_literal); + m_lemma_atoms.push_back(0); + + unsigned num_marks = 0; + if (not_l != null_literal) { + TRACE("conflict", tout << "not_l: "; m_ctx.display_literal(tout, not_l); tout << "\n";); + process_antecedent(not_l, num_marks); + } + + do { +#ifndef SMTCOMP + if (m_params.m_trace_stream != NULL) { + *m_params.m_trace_stream << "[resolve-process] "; + m_ctx.display_literal(*m_params.m_trace_stream, ~consequent); + *m_params.m_trace_stream << "\n"; + } +#endif + + TRACE("conflict", tout << "processing consequent: "; m_ctx.display_literal(tout, consequent); tout << "\n"; + tout << "num_marks: " << num_marks << ", js kind: " << js.get_kind() << "\n";); + SASSERT(js != null_b_justification); + switch (js.get_kind()) { + case b_justification::CLAUSE: { + clause * cls = js.get_clause(); + if (cls->is_lemma()) + cls->inc_clause_activity(); + unsigned num_lits = cls->get_num_literals(); + unsigned i = 0; + if (consequent != false_literal) { + SASSERT(cls->get_literal(0) == consequent || cls->get_literal(1) == consequent); + if (cls->get_literal(0) == consequent) { + i = 1; + } + else { + literal l = cls->get_literal(0); + SASSERT(consequent.var() != l.var()); + process_antecedent(~l, num_marks); + i = 2; + } + } + for(; i < num_lits; i++) { + literal l = cls->get_literal(i); + SASSERT(consequent.var() != l.var()); + process_antecedent(~l, num_marks); + } + justification * js = cls->get_justification(); + if (js) + process_justification(js, num_marks); + break; + } + case b_justification::BIN_CLAUSE: + SASSERT(consequent.var() != js.get_literal().var()); + process_antecedent(js.get_literal(), num_marks); + break; + case b_justification::AXIOM: + break; + case b_justification::JUSTIFICATION: + process_justification(js.get_justification(), num_marks); + break; + default: + UNREACHABLE(); + } + + while (true) { + literal l = m_assigned_literals[idx]; + if (m_ctx.is_marked(l.var())) + break; + CTRACE("conflict", m_ctx.get_assign_level(l) != m_conflict_lvl && m_ctx.get_assign_level(l) != m_ctx.get_base_level(), + tout << "assign_level(l): " << m_ctx.get_assign_level(l) << ", conflict_lvl: " << m_conflict_lvl << ", l: "; m_ctx.display_literal(tout, l); + tout << "\n";); + SASSERT(m_ctx.get_assign_level(l) == m_conflict_lvl || + // it may also be an (out-of-order) asserted literal + m_ctx.get_assign_level(l) == m_ctx.get_base_level()); + SASSERT(idx > 0); + idx--; + } + + consequent = m_assigned_literals[idx]; + bool_var c_var = consequent.var(); + SASSERT(m_ctx.get_assign_level(c_var) == m_conflict_lvl); + js = m_ctx.get_justification(c_var); + idx--; + num_marks--; + m_ctx.unset_mark(c_var); + } + while (num_marks > 0); + + TRACE("conflict", tout << "FUIP: "; m_ctx.display_literal(tout, consequent); tout << "\n";); + + m_lemma[0] = ~consequent; + m_lemma_atoms.set(0, m_ctx.bool_var2expr(consequent.var())); + + // TODO: + // + // equality optimization should go here. + // + + finalize_resolve(conflict, not_l); + + return true; + } + + /** + \brief Return an approximation for the set of scope levels where the literals in m_lemma + were assigned. + */ + level_approx_set conflict_resolution::get_lemma_approx_level_set() { + level_approx_set result; + literal_vector::const_iterator it = m_lemma.begin(); + literal_vector::const_iterator end = m_lemma.end(); + for(; it != end; ++it) + result.insert(m_ctx.get_assign_level(*it)); + return result; + } + + /** + \brief Restore the size of m_unmark to old_size, and + unmark literals at positions [old_size, m_unmark.size()). + */ + void conflict_resolution::reset_unmark(unsigned old_size) { + unsigned curr_size = m_unmark.size(); + for(unsigned i = old_size; i < curr_size; i++) + m_ctx.unset_mark(m_unmark[i]); + m_unmark.shrink(old_size); + } + + /** + \brief Restore the size of m_unmark to old_size, and + unmark literals at positions [old_size, m_unmark.size()). + And unmark all justifications at positions [old_js_qhead, m_todo_js.size()). + */ + void conflict_resolution::reset_unmark_and_justifications(unsigned old_size, unsigned old_js_qhead) { + reset_unmark(old_size); + unmark_justifications(old_js_qhead); + } + + /** + \brief Process an antecedent for lemma minimization. + */ + bool conflict_resolution::process_antecedent_for_minimization(literal antecedent) { + bool_var var = antecedent.var(); + unsigned lvl = m_ctx.get_assign_level(var); + if (!m_ctx.is_marked(var) && lvl > m_ctx.get_base_level()) { + if (m_lvl_set.may_contain(lvl)) { + m_ctx.set_mark(var); + m_unmark.push_back(var); + m_lemma_min_stack.push_back(var); + } + else { + return false; + } + } + return true; + } + + bool conflict_resolution::process_justification_for_minimization(justification * js) { + literal_vector & antecedents = m_tmp_literal_vector; + antecedents.reset(); + // Invoking justification2literals_core will not reset the caches for visited justifications and eqs. + // The method unmark_justifications must be invoked to reset these caches. + // Remark: The method reset_unmark_and_justifications invokes unmark_justifications. + justification2literals_core(js, antecedents); + literal_vector::iterator it = antecedents.begin(); + literal_vector::iterator end = antecedents.end(); + for(; it != end; ++it) + if (!process_antecedent_for_minimization(*it)) + return false; + return true; + } + + /** + \brief Return true if lit is implied by other marked literals + and/or literals assigned at the base level. + The set lvl_set is used as an optimization. + The idea is to stop the recursive search with a failure + as soon as we find a literal assigned in a level that is not in lvl_set. + */ + bool conflict_resolution::implied_by_marked(literal lit) { + m_lemma_min_stack.reset(); // avoid recursive function + m_lemma_min_stack.push_back(lit.var()); + unsigned old_size = m_unmark.size(); + unsigned old_js_qhead = m_todo_js_qhead; + + while (!m_lemma_min_stack.empty()) { + bool_var var = m_lemma_min_stack.back(); + m_lemma_min_stack.pop_back(); + b_justification js = m_ctx.get_justification(var); + SASSERT(js != null_b_justification); + switch(js.get_kind()) { + case b_justification::CLAUSE: { + clause * cls = js.get_clause(); + unsigned num_lits = cls->get_num_literals(); + unsigned pos = cls->get_literal(1).var() == var; + for (unsigned i = 0; i < num_lits; i++) { + if (pos != i) { + literal l = cls->get_literal(i); + SASSERT(l.var() != var); + if (!process_antecedent_for_minimization(~l)) { + reset_unmark_and_justifications(old_size, old_js_qhead); + return false; + } + } + } + justification * js = cls->get_justification(); + if (js && !process_justification_for_minimization(js)) { + reset_unmark_and_justifications(old_size, old_js_qhead); + return false; + } + break; + } + case b_justification::BIN_CLAUSE: + if (!process_antecedent_for_minimization(js.get_literal())) { + reset_unmark_and_justifications(old_size, old_js_qhead); + return false; + } + break; + case b_justification::AXIOM: + // it is a decision variable from a previous scope level or an assumption + if (m_ctx.get_assign_level(var) > m_ctx.get_base_level()) { + reset_unmark_and_justifications(old_size, old_js_qhead); + return false; + } + break; + case b_justification::JUSTIFICATION: + if (m_ctx.is_assumption(var) || !process_justification_for_minimization(js.get_justification())) { + reset_unmark_and_justifications(old_size, old_js_qhead); + return false; + } + break; + } + } + return true; + } + + /** + \brief Minimize the number of literals in learned_clause_lits. The main idea is to remove + literals that are implied by other literals in m_lemma and/or literals + assigned in the base levels. + */ + void conflict_resolution::minimize_lemma() { + m_unmark.reset(); + + m_lvl_set = get_lemma_approx_level_set(); + + unsigned sz = m_lemma.size(); + unsigned i = 1; // the first literal is the FUIP + unsigned j = 1; + for (; i < sz; i++) { + literal l = m_lemma[i]; + if (implied_by_marked(l)) { + m_unmark.push_back(l.var()); + } + else { + if (j != i) { + m_lemma[j] = m_lemma[i]; + m_lemma_atoms.set(j, m_lemma_atoms.get(i)); + } + j++; + } + } + + reset_unmark_and_justifications(0, 0); + m_lemma .shrink(j); + m_lemma_atoms.shrink(j); + m_ctx.m_stats.m_num_minimized_lits += sz - j; + } + + /** + \brief Return the proof object associated with the equality (= n1 n2) + if it already exists. Otherwise, return 0 and add p to the todo-list. + */ + proof * conflict_resolution::get_proof(enode * n1, enode * n2) { + SASSERT(n1 != n2); + proof * pr; + if (m_eq2proof.find(n1, n2, pr)) { + TRACE("proof_gen_bug", tout << "eq2_pr_cached: #" << n1->get_owner_id() << " #" << n2->get_owner_id() << "\n";); + return pr; + } + m_todo_pr.push_back(tp_elem(n1, n2)); + return 0; + } + + /** + \brief Apply symmetry if pr is a proof of (= n2 n1). + */ + proof * conflict_resolution::norm_eq_proof(enode * n1, enode * n2, proof * pr) { + if (!pr) + return 0; + SASSERT(m_manager.has_fact(pr)); + app * fact = to_app(m_manager.get_fact(pr)); + app * n1_owner = n1->get_owner(); + app * n2_owner = n2->get_owner(); + if (fact->get_num_args() != 2 || (fact->get_arg(0) != n2_owner && fact->get_arg(1) != n2_owner)) { + CTRACE("norm_eq_proof_bug", !m_ctx.is_true(n2) && !m_ctx.is_false(n2), + tout << "n1: #" << n1->get_owner_id() << ", n2: #" << n2->get_owner_id() << "\n"; + if (fact->get_num_args() == 2) { + tout << "fact(0): #" << fact->get_arg(0)->get_id() << ", fact(1): #" << fact->get_arg(1)->get_id() << "\n"; + } + tout << mk_bounded_pp(n1->get_owner(), m_manager, 10) << "\n"; + tout << mk_bounded_pp(n2->get_owner(), m_manager, 10) << "\n"; + tout << mk_bounded_pp(fact, m_manager, 10) << "\n"; + tout << mk_ll_pp(pr, m_manager, true, false);); + SASSERT(m_ctx.is_true(n2) || m_ctx.is_false(n2)); + SASSERT(fact == n1_owner || (m_manager.is_not(fact) && fact->get_arg(0) == n1_owner)); + if (m_ctx.is_true(n2)) + pr = m_manager.mk_iff_true(pr); + else + pr = m_manager.mk_iff_false(pr); + m_new_proofs.push_back(pr); + return pr; + } + if (m_manager.coarse_grain_proofs()) + return pr; + TRACE("norm_eq_proof", + tout << "#" << n1->get_owner_id() << " = #" << n2->get_owner_id() << "\n"; + tout << mk_ll_pp(pr, m_manager, true, false);); + SASSERT(m_manager.is_eq(fact) || m_manager.is_iff(fact)); + SASSERT((fact->get_arg(0) == n1->get_owner() && fact->get_arg(1) == n2->get_owner()) || + (fact->get_arg(1) == n1->get_owner() && fact->get_arg(0) == n2->get_owner())); + if (fact->get_arg(0) == n1_owner && fact->get_arg(1) == n2_owner) + return pr; + pr = m_manager.mk_symmetry(pr); + m_new_proofs.push_back(pr); + return pr; + } + + /** + \brief Return a proof object for n1 = n2 using the given eq_justification + if its antecedents are already available. Otherwise, return 0 and add + the missing antecedents to the todo-list. + */ + proof * conflict_resolution::get_proof(enode * n1, enode * n2, eq_justification js) { + unsigned num_args; + switch (js.get_kind()) { + case eq_justification::AXIOM: + UNREACHABLE(); + return 0; + case eq_justification::EQUATION: + TRACE("proof_gen_bug", tout << js.get_literal() << "\n"; m_ctx.display_literal_info(tout, js.get_literal());); + return norm_eq_proof(n1, n2, get_proof(js.get_literal())); + case eq_justification::JUSTIFICATION: + return norm_eq_proof(n1, n2, get_proof(js.get_justification())); + case eq_justification::CONGRUENCE: + num_args = n1->get_num_args(); + SASSERT(num_args == n2->get_num_args()); + SASSERT(n1->get_owner()->get_decl() == n2->get_owner()->get_decl()); + if (js.used_commutativity()) { + bool visited = true; + SASSERT(num_args == 2); + enode * c1_1 = n1->get_arg(0); + enode * c1_2 = n1->get_arg(1); + enode * c2_1 = n2->get_arg(0); + enode * c2_2 = n2->get_arg(1); + ptr_buffer prs; + if (c1_1 != c2_2) { + proof * pr = get_proof(c1_1, c2_2); + prs.push_back(pr); + if (!pr) + visited = false; + } + if (c1_2 != c2_1) { + proof * pr = get_proof(c1_2, c2_1); + prs.push_back(pr); + if (!pr) + visited = false; + } + if (!visited) + return 0; + app * e1 = n1->get_owner(); + app * e2 = n2->get_owner(); + app * e2_prime = m_manager.mk_app(e2->get_decl(), e2->get_arg(1), e2->get_arg(0)); + proof * pr1 = 0; + if (!prs.empty()) { + pr1 = m_manager.mk_congruence(e1, e2_prime, prs.size(), prs.c_ptr()); + m_new_proofs.push_back(pr1); + } + else { + TRACE("comm_proof_bug", tout << "e1: #" << e1->get_id() << " e2: #" << e2->get_id() << "\n" << mk_bounded_pp(e1, m_manager, 10) << + "\n" << mk_bounded_pp(e2, m_manager, 10) << "\n";); + // SASSERT(e1 == e2); + } + proof * pr2 = m_manager.mk_commutativity(e2_prime); + m_new_proofs.push_back(pr2); + return m_manager.mk_transitivity(pr1, pr2); + } + else { + bool visited = true; + ptr_buffer prs; + for (unsigned i = 0; i < num_args; i++) { + enode * c1 = n1->get_arg(i); + enode * c2 = n2->get_arg(i); + if (c1 != c2) { + proof * pr = get_proof(c1, c2); + prs.push_back(pr); + if (!pr) + visited = false; + } + } + if (!visited) + return 0; + proof * pr = m_manager.mk_congruence(n1->get_owner(), n2->get_owner(), prs.size(), prs.c_ptr()); + m_new_proofs.push_back(pr); + return pr; + } + default: + UNREACHABLE(); + return 0; + } + } + + /** + \brief Return the proof object associated with the given literal if it already + exists. Otherwise, return 0 and add l to the todo-list. + */ + proof * conflict_resolution::get_proof(literal l) { + proof * pr; + if (m_lit2proof.find(l, pr)) { + TRACE("proof_gen_bug", tout << "lit2pr_cached: #" << l << "\n";); + return pr; + } + m_todo_pr.push_back(tp_elem(l)); + return 0; + } + + /** + \brief Return a proof object for l using the given b_justification + if its antecedents are already available. Otherwise, return 0 and add + the missing antecedents to the todo-list. + */ + proof * conflict_resolution::get_proof(literal l, b_justification js) { +#ifdef _TRACE + static unsigned invocation_counter = 0; + invocation_counter++; + #define DUMP_AFTER_NUM_INVOCATIONS 213473 + CTRACE("get_proof_bug_after", invocation_counter >= DUMP_AFTER_NUM_INVOCATIONS, tout << "START get_proof\n";); +#endif + // l is a hypothesis: if it is marked, and the justification for the variable l.var() is js. + // we need the second condition, because the core builds proofs as: + // + // p1: is a proof of "A" + // p2: is a proof of "not A" + // [unit-resolution p1 p2]: false + // + // Let us assume that "A" was assigned first during propagation. + // Then, the "resolve" method will never select "not A" as a hypothesis. + // "not_A" will be the not_l argument in this method. + // Since we are assuming that "A" was assigned first", m_ctx.get_justification("A") will be + // p1. + // + // So, the test "m_ctx.get_justification(l.var()) == js" is used to check + // if l was assigned before ~l. + if (m_ctx.is_marked(l.var()) && m_ctx.get_justification(l.var()) == js) { + expr_ref l_expr(m_manager); + m_ctx.literal2expr(l, l_expr); + proof * pr = m_manager.mk_hypothesis(l_expr.get()); + m_new_proofs.push_back(pr); + return pr; + } + else { + SASSERT(js.get_kind() != b_justification::BIN_CLAUSE); + SASSERT(js.get_kind() != b_justification::AXIOM); + if (js.get_kind() == b_justification::CLAUSE) { + clause * cls = js.get_clause(); + justification * js = cls->get_justification(); + SASSERT(js); + proof * pr = get_proof(js); + ptr_buffer prs; + bool visited = pr != 0; + TRACE("get_proof_bug", if (pr != 0) tout << js->get_name() << "\n";); + CTRACE("get_proof_bug_after", invocation_counter >= DUMP_AFTER_NUM_INVOCATIONS, if (pr != 0) tout << js->get_name() << "\n";); + CTRACE("get_proof_bug_after", invocation_counter >= DUMP_AFTER_NUM_INVOCATIONS, if (pr != 0) js->display_debug_info(*this, tout);); + prs.push_back(pr); + unsigned num_lits = cls->get_num_literals(); + unsigned i = 0; + SASSERT(l == false_literal || l == cls->get_literal(0) || l == cls->get_literal(1)); + if (l != false_literal) { + if (cls->get_literal(0) == l) { + i = 1; + } + else { + SASSERT(l == cls->get_literal(1)); + proof * pr = get_proof(~cls->get_literal(0)); + prs.push_back(pr); + if (!pr) + visited = false; + i = 2; + } + } + for (; i < num_lits; i++) { + proof * pr = get_proof(~cls->get_literal(i)); + prs.push_back(pr); + if (!pr) + visited = false; + } + if (!visited) + return 0; + expr_ref l_exr(m_manager); + m_ctx.literal2expr(l, l_exr); + TRACE("get_proof_bug", + tout << "clause:\n"; + for (unsigned i = 0; i < num_lits; i++) { + tout << cls->get_literal(i).index() << "\n"; + expr_ref l_expr(m_manager); + m_ctx.literal2expr(cls->get_literal(i), l_expr); + tout << mk_pp(l_expr, m_manager) << "\n"; + } + tout << "antecedents:\n"; + for (unsigned i = 0; i < prs.size(); i++) { + tout << mk_pp(m_manager.get_fact(prs[i]), m_manager) << "\n"; + } + tout << "consequent:\n" << mk_pp(l_exr, m_manager) << "\n";); + CTRACE("get_proof_bug_after", + invocation_counter >= DUMP_AFTER_NUM_INVOCATIONS, + tout << "clause, num_lits: " << num_lits << ":\n"; + for (unsigned i = 0; i < num_lits; i++) { + tout << cls->get_literal(i).index() << "\n"; + expr_ref l_expr(m_manager); + m_ctx.literal2expr(cls->get_literal(i), l_expr); + tout << mk_pp(l_expr, m_manager) << "\n"; + } + tout << "antecedents:\n"; + for (unsigned i = 0; i < prs.size(); i++) { + tout << mk_pp(m_manager.get_fact(prs[i]), m_manager) << "\n"; + } + tout << "consequent:\n" << mk_pp(l_exr, m_manager) << "\n";); + TRACE("get_proof", + tout << l.index() << " " << true_literal.index() << " " << false_literal.index() << " "; + m_ctx.display_literal(tout, l); tout << " --->\n"; + tout << mk_ll_pp(l_exr, m_manager);); + CTRACE("get_proof_bug_after", + invocation_counter >= DUMP_AFTER_NUM_INVOCATIONS, + tout << l.index() << " " << true_literal.index() << " " << false_literal.index() << " "; + m_ctx.display_literal(tout, l); tout << " --->\n"; + tout << mk_ll_pp(l_exr, m_manager);); + pr = m_manager.mk_unit_resolution(prs.size(), prs.c_ptr(), l_exr); + m_new_proofs.push_back(pr); + return pr; + } + else { + return get_proof(js.get_justification()); + } + } + } + + /** + \brief Return the proof object associated with the given justification + if it already exists. Otherwise, return 0 and add js to the todo-list. + */ + proof * conflict_resolution::get_proof(justification * js) { + proof * pr; + if (m_js2proof.find(js, pr)) { + TRACE("proof_gen_bug", tout << "js2pr_cached: #" << js << "\n";); + return pr; + } + SASSERT(js != 0); + m_todo_pr.push_back(tp_elem(js)); + return 0; + } + + void conflict_resolution::init_mk_proof() { + TRACE("proof_gen_bug", tout << "reset_caches\n";); + m_new_proofs.reset(); + m_todo_pr.reset(); + m_eq2proof.reset(); + m_lit2proof.reset(); + m_js2proof.reset(); + literal_vector::iterator it = m_lemma.begin(); + literal_vector::iterator end = m_lemma.end(); + for (; it != end; ++it) + m_ctx.set_mark((*it).var()); + } + + bool conflict_resolution::visit_b_justification(literal l, b_justification js) { + // l is a hypothesis: if it is marked, and the justification for the variable l.var() is js. + // See: get_proof(literal l, b_justification js) + if (m_ctx.is_marked(l.var()) && m_ctx.get_justification(l.var()) == js) + return true; + SASSERT(js.get_kind() != b_justification::BIN_CLAUSE); + CTRACE("visit_b_justification_bug", js.get_kind() == b_justification::AXIOM, tout << "l: " << l << "\n"; m_ctx.display(tout);); + SASSERT(js.get_kind() != b_justification::AXIOM); + if (js.get_kind() == b_justification::CLAUSE) { + clause * cls = js.get_clause(); + bool visited = get_proof(cls->get_justification()) != 0; + unsigned num_lits = cls->get_num_literals(); + unsigned i = 0; + if (l != false_literal) { + if (cls->get_literal(0) == l) { + i = 1; + } + else { + if (get_proof(~cls->get_literal(0)) == 0) + visited = false; + i = 2; + } + } + for (; i < num_lits; i++) + if (get_proof(~cls->get_literal(i)) == 0) + visited = false; + return visited; + } + else + return get_proof(js.get_justification()) != 0; + } + + void conflict_resolution::mk_proof(literal l, b_justification js) { + SASSERT(!m_lit2proof.contains(l)); + proof * pr = get_proof(l, js); + SASSERT(pr); + TRACE("proof_gen_bug", tout << "lit2pr_saved: #" << l << "\n";); + m_lit2proof.insert(l, pr); + TRACE("mk_proof", + tout << mk_bounded_pp(m_ctx.bool_var2expr(l.var()), m_manager, 10) << "\n"; + tout << "storing proof for: "; m_ctx.display_literal(tout, l); tout << "\n"; + tout << mk_ll_pp(pr, m_manager);); + } + + /** + \brief Given that lhs = ... = rhs, and lhs reaches rhs in the 'proof' tree branch. + Then, return true if all proof objects needed to create the proof steps are already + available. Otherwise return false and update m_todo_pr with info about the proof + objects that need to be created. + */ + bool conflict_resolution::visit_trans_proof(enode * lhs, enode * rhs) { + SASSERT(lhs->trans_reaches(rhs)); + bool visited = true; + while (lhs != rhs) { + eq_justification js = lhs->m_trans.m_justification; + switch (js.get_kind()) { + case eq_justification::AXIOM: + UNREACHABLE(); + break; + case eq_justification::EQUATION: + if (get_proof(js.get_literal()) == 0) + visited = false; + break; + case eq_justification::JUSTIFICATION: + if (get_proof(js.get_justification()) == 0) + visited = false; + break; + case eq_justification::CONGRUENCE: { + enode * n1 = lhs; + enode * n2 = lhs->m_trans.m_target; + unsigned num_args = n1->get_num_args(); + SASSERT(num_args == n2->get_num_args()); + if (js.used_commutativity()) { + SASSERT(num_args == 2); + enode * c1_1 = n1->get_arg(0); + enode * c1_2 = n1->get_arg(1); + enode * c2_1 = n2->get_arg(0); + enode * c2_2 = n2->get_arg(1); + if (c1_1 != c2_2 && get_proof(c1_1, c2_2) == 0) + visited = false; + if (c1_2 != c2_1 && get_proof(c1_2, c2_1) == 0) + visited = false; + } + else { + for (unsigned i = 0; i < num_args; i++) { + enode * c1 = n1->get_arg(i); + enode * c2 = n2->get_arg(i); + if (c1 != c2 && get_proof(c1, c2) == 0) + visited = false; + } + } + break; + } + default: + UNREACHABLE(); + } + lhs = lhs->m_trans.m_target; + } + return visited; + } + + /** + \brief Return true if all proof objects that are used to build the proof that lhs = rhs were + already built. If the result is false, then m_todo_pr is updated with info about the proof + objects that need to be created. + */ + bool conflict_resolution::visit_eq_justications(enode * lhs, enode * rhs) { + enode * c = find_common_ancestor(lhs, rhs); + bool v1 = visit_trans_proof(lhs, c); + bool v2 = visit_trans_proof(rhs, c); + return v1 && v2; + } + + /** + \brief Given that lhs = ... = rhs, and lhs reaches rhs in the + trans proof branch, then build a proof object for each equality + in the sequence, and insert them into result. + */ + void conflict_resolution::mk_proof(enode * lhs, enode * rhs, ptr_buffer & result) { + SASSERT(lhs->trans_reaches(rhs)); + while (lhs != rhs) { + eq_justification js = lhs->m_trans.m_justification; + enode * n1 = lhs; + enode * n2 = lhs->m_trans.m_target; + proof * pr = get_proof(n1, n2, js); + SASSERT(pr); + result.push_back(pr); + lhs = lhs->m_trans.m_target; + } + } + + void conflict_resolution::mk_proof(enode * lhs, enode * rhs) { + SASSERT(!m_eq2proof.contains(lhs, rhs)); + enode * c = find_common_ancestor(lhs, rhs); + ptr_buffer prs1; + mk_proof(lhs, c, prs1); + ptr_buffer prs2; + mk_proof(rhs, c, prs2); + while (!prs2.empty()) { + proof * pr = prs2.back(); + if (m_manager.fine_grain_proofs()) { + pr = m_manager.mk_symmetry(pr); + m_new_proofs.push_back(pr); + prs1.push_back(pr); + } + else { + prs1.push_back(pr); + } + prs2.pop_back(); + } + proof * pr = 0; + SASSERT(!prs1.empty()); + if (prs1.size() == 1) + pr = prs1[0]; + else { + TRACE("mk_transitivity", + unsigned sz = prs1.size(); + for (unsigned i = 0; i < sz; i++) { + tout << mk_ll_pp(prs1[i], m_manager) << "\n"; + }); + pr = m_manager.mk_transitivity(prs1.size(), prs1.c_ptr(), lhs->get_owner(), rhs->get_owner()); + } + m_new_proofs.push_back(pr); + TRACE("proof_gen_bug", tout << "eq2pr_saved: #" << lhs->get_owner_id() << " #" << rhs->get_owner_id() << "\n";); + m_eq2proof.insert(lhs, rhs, pr); + } + + void conflict_resolution::mk_conflict_proof(b_justification conflict, literal not_l) { + SASSERT(conflict.get_kind() != b_justification::BIN_CLAUSE); + SASSERT(conflict.get_kind() != b_justification::AXIOM); + SASSERT(not_l == null_literal || conflict.get_kind() == b_justification::JUSTIFICATION); + TRACE("mk_conflict_proof", tout << "lemma literals: "; + literal_vector::iterator it = m_lemma.begin(); + literal_vector::iterator end = m_lemma.end(); + for (; it != end; ++it) { + m_ctx.display_literal(tout, *it); + tout << " "; + } + tout << "\n";); + init_mk_proof(); + literal consequent = false_literal; + if (not_l != null_literal) + consequent = ~not_l; + visit_b_justification(consequent, conflict); + if (not_l != null_literal) + m_todo_pr.push_back(tp_elem(not_l)); + while (!m_todo_pr.empty()) { + tp_elem & elem = m_todo_pr.back(); + switch (elem.m_kind) { + case tp_elem::EQUALITY: { + enode * lhs = elem.m_lhs; + enode * rhs = elem.m_rhs; + if (m_eq2proof.contains(lhs, rhs)) + m_todo_pr.pop_back(); + else if (visit_eq_justications(lhs, rhs)) { + m_todo_pr.pop_back(); + mk_proof(lhs, rhs); + } + break; + } + case tp_elem::JUSTIFICATION: { + justification * js = elem.m_js; + if (m_js2proof.contains(js)) + m_todo_pr.pop_back(); + else { + proof * pr = js->mk_proof(*this); + if (pr) { + m_todo_pr.pop_back(); + m_new_proofs.push_back(pr); + TRACE("proof_gen_bug", tout << "js2pr_saved: #" << js << "\n";); + m_js2proof.insert(js, pr); + } + } + break; + } + case tp_elem::LITERAL: { + literal l = to_literal(elem.m_lidx); + if (m_lit2proof.contains(l)) + m_todo_pr.pop_back(); + else { + b_justification js = m_ctx.get_justification(l.var()); + if (visit_b_justification(l, js)) { + m_todo_pr.pop_back(); + mk_proof(l, js); + } + } + break; + } + default: + UNREACHABLE(); + } + } + + SASSERT(visit_b_justification(consequent, conflict)); + proof * pr = 0; + if (not_l == null_literal) { + pr = get_proof(false_literal, conflict); + SASSERT(pr); + } + else { + proof * prs[2] = { 0, 0}; + m_lit2proof.find(not_l, prs[0]); + SASSERT(prs[0]); + prs[1] = get_proof(consequent, conflict); + SASSERT(prs[1]); + pr = m_manager.mk_unit_resolution(2, prs); + } + expr_ref_buffer lits(m_manager); + literal_vector::iterator it = m_lemma.begin(); + literal_vector::iterator end = m_lemma.end(); + for (; it != end; ++it) { + m_ctx.unset_mark((*it).var()); + expr_ref l_expr(m_manager); + m_ctx.literal2expr(*it, l_expr); + lits.push_back(l_expr); + } + expr * fact = 0; + switch (lits.size()) { + case 0: fact = 0; break; + case 1: fact = lits[0]; break; + default: fact = m_manager.mk_or(lits.size(), lits.c_ptr()); + } + if (fact == 0) + m_lemma_proof = pr; + else + m_lemma_proof = m_manager.mk_lemma(pr, fact); + m_new_proofs.reset(); + } + + void conflict_resolution::process_antecedent_for_unsat_core(literal antecedent) { + TRACE("conflict", tout << "processing antecedent: "; m_ctx.display_literal(tout, antecedent); tout << "\n";); + bool_var var = antecedent.var(); + + if (!m_ctx.is_marked(var)) { + m_ctx.set_mark(var); + m_unmark.push_back(var); + if (m_ctx.is_assumption(var)) + m_assumptions.push_back(antecedent); + } + } + + void conflict_resolution::process_justification_for_unsat_core(justification * js) { + literal_vector & antecedents = m_tmp_literal_vector; + antecedents.reset(); + justification2literals_core(js, antecedents); + literal_vector::iterator it = antecedents.begin(); + literal_vector::iterator end = antecedents.end(); + for(; it != end; ++it) + process_antecedent_for_unsat_core(*it); + } + + void conflict_resolution::mk_unsat_core(b_justification conflict, literal not_l) { + SASSERT(m_ctx.tracking_assumptions()); + m_assumptions.reset(); + m_unmark.reset(); + + SASSERT(m_conflict_lvl <= m_ctx.get_search_level()); + unsigned search_lvl = m_ctx.get_search_level(); + + b_justification js = conflict; + literal consequent = false_literal; + if (not_l != null_literal) + consequent = ~not_l; + + int idx = skip_literals_above_conflict_level(); + + if (not_l != null_literal) + process_antecedent_for_unsat_core(not_l); + + if (m_assigned_literals.empty()) { + goto end_unsat_core; + } + + while (true) { + TRACE("unsat_core_bug", tout << "js.get_kind(): " << js.get_kind() << ", idx: " << idx << "\n";); + switch (js.get_kind()) { + case b_justification::CLAUSE: { + clause * cls = js.get_clause(); + unsigned num_lits = cls->get_num_literals(); + unsigned i = 0; + if (consequent != false_literal) { + SASSERT(cls->get_literal(0) == consequent || cls->get_literal(1) == consequent); + if (cls->get_literal(0) == consequent) { + i = 1; + } + else { + process_antecedent_for_unsat_core(~cls->get_literal(0)); + i = 2; + } + } + for(; i < num_lits; i++) { + literal l = cls->get_literal(i); + process_antecedent_for_unsat_core(~l); + } + justification * js = cls->get_justification(); + if (js) + process_justification_for_unsat_core(js); + break; + } + case b_justification::BIN_CLAUSE: + SASSERT(consequent.var() != js.get_literal().var()); + process_antecedent_for_unsat_core(js.get_literal()); + break; + case b_justification::AXIOM: + break; + case b_justification::JUSTIFICATION: + process_justification_for_unsat_core(js.get_justification()); + break; + default: + UNREACHABLE(); + } + + while (true) { + if (idx < 0) + goto end_unsat_core; + literal l = m_assigned_literals[idx]; + TRACE("unsat_core_bug", tout << "l: " << l << ", get_assign_level(l): " << m_ctx.get_assign_level(l) << ", is_marked(l): " << m_ctx.is_marked(l.var()) << "\n";); + if (m_ctx.get_assign_level(l) < search_lvl || idx == 0) + goto end_unsat_core; + if (m_ctx.is_marked(l.var())) + break; + idx--; + } + + SASSERT(idx >= 0); + consequent = m_assigned_literals[idx]; + bool_var c_var = consequent.var(); + SASSERT(m_ctx.get_assign_level(c_var) == search_lvl); + js = m_ctx.get_justification(c_var); + idx--; + } + + end_unsat_core: + TRACE("unsat_core", tout << "assumptions:\n"; m_ctx.display_literals(tout, m_assumptions.size(), m_assumptions.c_ptr()); tout << "\n";); + reset_unmark_and_justifications(0, 0); + } + + conflict_resolution * mk_conflict_resolution(ast_manager & m, + context & ctx, + dyn_ack_manager & dack_manager, + front_end_params const & params, + literal_vector const & assigned_literals, + vector & watches) { + return alloc(conflict_resolution, m, ctx, dack_manager, params, assigned_literals, watches); + } + +}; + diff --git a/lib/smt_conflict_resolution.h b/lib/smt_conflict_resolution.h new file mode 100644 index 000000000..5287b4e26 --- /dev/null +++ b/lib/smt_conflict_resolution.h @@ -0,0 +1,278 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_conflict_resolution.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-25. + +Revision History: + +--*/ +#ifndef _SMT_CONFLICT_RESOLUTION_H_ +#define _SMT_CONFLICT_RESOLUTION_H_ + +#include"smt_literal.h" +#include"smt_bool_var_data.h" +#include"smt_justification.h" +#include"smt_enode.h" +#include"dyn_ack.h" +#include"obj_pair_hashtable.h" +#include"front_end_params.h" +#include"obj_pair_hashtable.h" +#include"map.h" +#include"watch_list.h" +#include"obj_pair_set.h" + +typedef approx_set_tpl level_approx_set; + +namespace smt { + + typedef std::pair enode_pair; + + /** + \brief Base conflict resolution class. + It implements the FUIP strategy. + */ + class conflict_resolution { + protected: + typedef obj_pair_set enode_pair_set; + + ast_manager & m_manager; + front_end_params const & m_params; + context & m_ctx; + dyn_ack_manager & m_dyn_ack_manager; + literal_vector const & m_assigned_literals; + + unsigned m_conflict_lvl; + + literal_vector m_lemma; + expr_ref_vector m_lemma_atoms; + unsigned m_new_scope_lvl; + unsigned m_lemma_iscope_lvl; + + justification_vector m_todo_js; + unsigned m_todo_js_qhead; + svector m_todo_eqs; + enode_pair_set m_already_processed_eqs; + + literal_vector * m_antecedents; + + // Reference for watch lists are used to implement subsumption resolution + vector & m_watches; //!< per literal + + // --------------------------- + // + // Proof generation + // + // --------------------------- + typedef obj_map js2proof; + typedef obj_pair_map eq2proof; + typedef map, default_eq > lit2proof; + + /** + \brief Element for the todo-list used to build proofs. + */ + struct tp_elem { + enum { + JUSTIFICATION, + EQUALITY, + LITERAL + } m_kind; + union { + justification * m_js; + unsigned m_lidx; + struct { + enode * m_lhs; + enode * m_rhs; + }; + }; + tp_elem(literal l):m_kind(LITERAL), m_lidx(l.index()) {} + tp_elem(enode * lhs, enode * rhs):m_kind(EQUALITY), m_lhs(lhs), m_rhs(rhs) {} + tp_elem(justification * js):m_kind(JUSTIFICATION), m_js(js) {} + }; + + svector m_todo_pr; + js2proof m_js2proof; + eq2proof m_eq2proof; + lit2proof m_lit2proof; + proof_ref_vector m_new_proofs; + proof_ref m_lemma_proof; + + literal_vector m_assumptions; + + public: + void setup() { + } + + void mark_justification(justification * js) { + if (!js->is_marked()) { + TRACE("conflict_detail", tout << "marking: " << js << "\n";); + js->set_mark(); + m_todo_js.push_back(js); + } + } + + void mark_eq(enode * n1, enode * n2) { + if (n1 != n2) { + if (n1->get_owner_id() > n2->get_owner_id()) + std::swap(n1, n2); + enode_pair p(n1, n2); + if (m_already_processed_eqs.insert_if_not_there(p)) { + TRACE("conflict_detail", tout << "marking eq #" << p.first->get_owner_id() << " = #" << + p.second->get_owner_id() << "\n";); + m_todo_eqs.push_back(p); + SASSERT(m_already_processed_eqs.contains(p)); + } + } + } + + void mark_literal(literal l) { + SASSERT(m_antecedents); + m_antecedents->push_back(l); + } + + void mark_justified_eq(enode * lhs, enode * rhs, eq_justification js) { + eq_justification2literals(lhs, rhs, js); + } + + proof * norm_eq_proof(enode * n1, enode * n2, proof * pr); + + proof * get_proof(enode_pair const & p); + proof * get_proof(enode * n1, enode * n2); + proof * get_proof(enode * n1, enode * n2, eq_justification js); + proof * get_proof(literal l); + proof * get_proof(literal l, b_justification js); + proof * get_proof(justification * js); + + bool visit_b_justification(literal l, b_justification js); + void mk_proof(literal l, b_justification js); + bool visit_trans_proof(enode * lhs, enode * rhs); + bool visit_eq_justications(enode * lhs, enode * rhs); + void mk_proof(enode * lhs, enode * rhs, ptr_buffer & result); + void mk_proof(enode * lhs, enode * rhs); + void init_mk_proof(); + void mk_conflict_proof(b_justification conflict, literal not_l); + + protected: + template + void mark_enodes_in_trans(enode * n); + enode * find_common_ancestor(enode * n1, enode * n2); + void eq_justification2literals(enode * lhs, enode * rhs, eq_justification js); + void eq_branch2literals(enode * n1, enode * n2); + void eq2literals(enode * n1, enode * n2); + void justification2literals_core(justification * js, literal_vector & result); + void unmark_justifications(unsigned old_js_qhead); + void justification2literals(justification * js, literal_vector & result); + + literal_vector m_tmp_literal_vector; + + unsigned get_justification_max_lvl(justification * js); + unsigned get_max_lvl(literal consequent, b_justification js); + unsigned skip_literals_above_conflict_level(); + void process_antecedent(literal antecedent, unsigned & num_marks); + void process_justification(justification * js, unsigned & num_marks); + + bool_var_vector m_unmark; + bool_var_vector m_lemma_min_stack; + level_approx_set m_lvl_set; + level_approx_set get_lemma_approx_level_set(); + void reset_unmark(unsigned old_size); + void reset_unmark_and_justifications(unsigned old_size, unsigned old_js_qhead); + bool process_antecedent_for_minimization(literal antecedent); + bool process_justification_for_minimization(justification * js); + bool implied_by_marked(literal lit); + void minimize_lemma(); + + void structural_minimization(); + + void process_antecedent_for_unsat_core(literal antecedent); + void process_justification_for_unsat_core(justification * js); + void mk_unsat_core(b_justification conflict, literal not_l); + + bool initialize_resolve(b_justification conflict, literal not_l, b_justification & js, literal & consequent); + void finalize_resolve(b_justification conflict, literal not_l); + + public: + conflict_resolution(ast_manager & m, + context & ctx, + dyn_ack_manager & dack_manager, + front_end_params const & params, + literal_vector const & assigned_literals, + vector & watches + ); + + virtual ~conflict_resolution() {} + + virtual bool resolve(b_justification conflict, literal not_l); + + context & get_context() { + return m_ctx; + } + + ast_manager & get_manager() { + return m_manager; + } + + unsigned get_new_scope_lvl() const { + return m_new_scope_lvl; + } + + unsigned get_lemma_intern_lvl() const { + return m_lemma_iscope_lvl; + } + + unsigned get_lemma_num_literals() const { + return m_lemma.size(); + } + + literal * get_lemma_literals() { + return m_lemma.c_ptr(); + } + + expr * * get_lemma_atoms() { + return m_lemma_atoms.c_ptr(); + } + + void release_lemma_atoms() { + m_lemma_atoms.reset(); + } + + proof * get_lemma_proof() { + return m_lemma_proof; + } + + literal_vector::const_iterator begin_unsat_core() const { + return m_assumptions.begin(); + } + + literal_vector::const_iterator end_unsat_core() const { + return m_assumptions.end(); + } + }; + + inline void mark_literals(conflict_resolution & cr, unsigned sz, literal const * ls) { + for (unsigned i = 0; i < sz; i++) + cr.mark_literal(ls[i]); + } + + conflict_resolution * mk_conflict_resolution(ast_manager & m, + context & ctx, + dyn_ack_manager & dack_manager, + front_end_params const & params, + literal_vector const & assigned_literals, + vector & watches + ); + + +}; + +#endif /* _SMT_CONFLICT_RESOLUTION_H_ */ + diff --git a/lib/smt_context.cpp b/lib/smt_context.cpp new file mode 100644 index 000000000..8994b2b2a --- /dev/null +++ b/lib/smt_context.cpp @@ -0,0 +1,4207 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_context.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-19. + +Revision History: + +--*/ +#include +#include"smt_context.h" +#include"luby.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"warning.h" +#include"smt_quick_checker.h" +#include"proof_checker.h" +#include"ast_util.h" +#include"uses_theory.h" +#include"model.h" +#include"smt_for_each_relevant_expr.h" +#include"timeit.h" +#include"well_sorted.h" +#include"union_find.h" +#include"smt_model_generator.h" +#include"smt_model_checker.h" +#include"smt_model_finder.h" +#include"model_pp.h" +#include"ast_smt2_pp.h" + +namespace smt { + + context::context(ast_manager & m, front_end_params & p, params_ref const & _p): + m_manager(m), + m_fparams(p), + m_params(_p), + m_setup(*this, p), + m_cancel_flag(false), + m_asserted_formulas(m, p), + m_qmanager(alloc(quantifier_manager, *this, p, _p)), + m_model_generator(alloc(model_generator, m)), + m_relevancy_propagator(mk_relevancy_propagator(*this)), + m_random(p.m_random_seed), + m_flushing(false), + m_progress_callback(0), + m_next_progress_sample(0), + m_fingerprints(m_region), + m_b_internalized_stack(m), + m_e_internalized_stack(m), + m_final_check_idx(0), + m_cg_table(m), + m_dyn_ack_manager(*this, p), + m_is_diseq_tmp(0), + m_units_to_reassert(m_manager), + m_qhead(0), + m_simp_qhead(0), + m_simp_counter(0), + m_bvar_inc(1.0), + m_phase_cache_on(true), + m_phase_counter(0), + m_phase_default(false), + m_conflict(null_b_justification), + m_not_l(null_literal), + m_conflict_resolution(mk_conflict_resolution(m, *this, m_dyn_ack_manager, p, m_assigned_literals, m_watches)), + m_unsat_proof(m), + m_unsat_core(m), +#ifdef Z3DEBUG + m_trail_enabled(true), +#endif + m_scope_lvl(0), + m_base_lvl(0), + m_search_lvl(0), + m_generation(0), + m_last_search_result(l_undef), + m_last_search_failure(UNKNOWN), + m_searching(false) { + + SASSERT(m_scope_lvl == 0); + SASSERT(m_base_lvl == 0); + SASSERT(m_search_lvl == 0); + + m_case_split_queue = mk_case_split_queue(*this, p); + + init(); + + if (!relevancy()) + m_fparams.m_relevancy_lemma = false; + + m_model_generator->set_context(this); + } + + context::~context() { + flush(); + } + + context * context::mk_fresh(symbol const * l, front_end_params * p) { + context * new_ctx = alloc(context, m_manager, p == 0 ? m_fparams : *p); + new_ctx->set_logic(l == 0 ? m_setup.get_logic() : *l); + // copy missing simplifier_plugins + // remark: some simplifier_plugins are automatically created by the asserted_formulas class. + simplifier & s = get_simplifier(); + simplifier & new_s = new_ctx->get_simplifier(); + ptr_vector::const_iterator it1 = s.begin_plugins(); + ptr_vector::const_iterator end1 = s.end_plugins(); + for (; it1 != end1; ++it1) { + simplifier_plugin * p = *it1; + if (new_s.get_plugin(p->get_family_id()) == 0) { + new_ctx->register_plugin(p->mk_fresh()); + } + SASSERT(new_s.get_plugin(p->get_family_id()) != 0); + } + + // copy theory plugins + ptr_vector::iterator it2 = m_theory_set.begin(); + ptr_vector::iterator end2 = m_theory_set.end(); + for (; it2 != end2; ++it2) { + theory * new_th = (*it2)->mk_fresh(new_ctx); + new_ctx->register_plugin(new_th); + } + new_ctx->m_setup.mark_already_configured(); + return new_ctx; + } + + void context::init() { + app * t = m_manager.mk_true(); + mk_bool_var(t); + SASSERT(get_bool_var(t) == true_bool_var); + SASSERT(true_literal.var() == true_bool_var); + m_assignment[true_literal.index()] = l_true; + m_assignment[false_literal.index()] = l_false; + if (m_manager.proofs_enabled()) { + proof * pr = m_manager.mk_true_proof(); + m_bdata[true_bool_var].m_justification = b_justification(mk_justification(justification_proof_wrapper(*this, pr))); + } + else { + m_bdata[true_bool_var].m_justification = b_justification::mk_axiom(); + } + m_true_enode = mk_enode(t, true, true, false); + // internalizer is marking enodes as interpreted whenever the associated ast is a value and a constant. + // m_true_enode->mark_as_interpreted(); + app * f = m_manager.mk_false(); + m_false_enode = mk_enode(f, true, true, false); + // m_false_enode->mark_as_interpreted(); + } + + void context::set_progress_callback(progress_callback *cb) + { + m_progress_callback = cb; + } + + /** + \brief This method should be used to create equality atoms during the search. + See comments in theory::mk_eq_atom + */ + app * context::mk_eq_atom(expr * lhs, expr * rhs) { + family_id fid = m_manager.get_sort(lhs)->get_family_id(); + theory * th = get_theory(fid); + if (th) + return th->mk_eq_atom(lhs, rhs); + if (lhs->get_id() > rhs->get_id()) + std::swap(lhs, rhs); + return m_manager.mk_eq(lhs, rhs); + } + + void context::assign_core(literal l, b_justification j, bool decision) { + TRACE("assign_core", tout << "assigning: " << l << " "; display_literal(tout, l); tout << "\n";); + SASSERT(l.var() < static_cast(m_b_internalized_stack.size())); + m_assigned_literals.push_back(l); + m_assignment[l.index()] = l_true; + m_assignment[(~l).index()] = l_false; + bool_var_data & d = get_bdata(l.var()); + d.m_justification = j; + d.m_scope_lvl = m_scope_lvl; + if (m_fparams.m_restart_adaptive && d.m_phase_available) { + m_agility *= m_fparams.m_agility_factor; + if (!decision && d.m_phase == l.sign()) + m_agility += (1.0 - m_fparams.m_agility_factor); + } + d.m_phase_available = true; + d.m_phase = !l.sign(); + TRACE("phase_selection", tout << "saving phase, is_pos: " << d.m_phase << " l: " << l << "\n";); + if (d.is_atom() && (m_fparams.m_relevancy_lvl == 0 || (m_fparams.m_relevancy_lvl == 1 && !d.is_quantifier()) || is_relevant_core(bool_var2expr(l.var())))) + m_atom_propagation_queue.push_back(l); +#ifndef SMTCOMP + if (m_fparams.m_trace_stream != NULL) + trace_assign(l, j, decision); + m_case_split_queue->assign_lit_eh(l); +#endif + } + + bool context::bcp() { + SASSERT(!inconsistent()); + while (m_qhead < m_assigned_literals.size()) { + literal l = m_assigned_literals[m_qhead]; + SASSERT(get_assignment(l) == l_true); + m_qhead++; + m_simp_counter--; + literal not_l = ~l; + SASSERT(get_assignment(not_l) == l_false); + watch_list & w = m_watches[l.index()]; + if (binary_clause_opt_enabled()) { + // binary clause propagation + b_justification js(l); + literal * it = w.begin_literals(); + literal * end = w.end_literals(); + for(; it != end; ++it) { + literal l = *it; + switch (get_assignment(l)) { + case l_false: + m_stats.m_num_bin_propagations++; + set_conflict(js, ~l); + return false; + case l_undef: + m_stats.m_num_bin_propagations++; + assign_core(l, js); + break; + case l_true: + break; + } + if (m_cancel_flag) { + return true; + } + } + } + + // non-binary clause propagation + watch_list::clause_iterator it = w.begin_clause(); + watch_list::clause_iterator it2 = it; + watch_list::clause_iterator end = w.end_clause(); + for(; it != end; ++it) { + clause * cls = *it; + CTRACE("bcp_bug", cls->get_literal(0) != not_l && cls->get_literal(1) != not_l, display_clause_detail(tout, cls); + tout << "not_l: "; display_literal(tout, not_l); tout << " " << not_l << "\n";); + SASSERT(cls->get_literal(0) == not_l || cls->get_literal(1) == not_l); + if (cls->get_literal(0) == not_l) { + cls->set_literal(0, cls->get_literal(1)); + cls->set_literal(1, not_l); + } + + SASSERT(cls->get_literal(1) == not_l); + + literal first_lit = cls->get_literal(0); + lbool first_lit_val = get_assignment(first_lit); + + if (first_lit_val == l_true) { + *it2 = *it; // clause is already satisfied, keep it + it2++; + } + else { + literal * it3 = cls->begin_literals() + 2; + literal * end3 = cls->end_literals(); + for(; it3 != end3; ++it3) { + if (get_assignment(*it3) != l_false) { + // swap literal *it3 with literal at position 0 + // the negation of literal *it3 will watch clause cls. + m_watches[(~(*it3)).index()].insert_clause(cls); + cls->set_literal(1, *it3); + *it3 = not_l; + goto found_watch; + } + } + // did not find watch... + if (first_lit_val == l_false) { + // CONFLICT + // copy remaining watches + while (it < end) { + *it2 = *it; + it2++; + it++; + } + SASSERT(it2 <= end); + w.set_end_clause(it2); + SASSERT(is_empty_clause(cls)); + set_conflict(b_justification(cls)); + return false; + } + else { + // PROPAGATION + SASSERT(first_lit_val == l_undef); + SASSERT(get_assignment(first_lit) == l_undef); + SASSERT(is_unit_clause(cls)); + *it2 = *it; + it2++; // keep clause + m_stats.m_num_propagations++; + // It is safe to call assign_core instead of assign because first_lit is unassigned + assign_core(first_lit, b_justification(cls)); + if (m_fparams.m_relevancy_lemma && cls->is_lemma()) { + expr * e = bool_var2expr(first_lit.var()); + // IMPORTANT: this kind of propagation asserts negative literals of the form (= A1 A2) where + // A1 and A2 are array terms. So, it may be interesting to disable it for array eqs. + //if (!(m_manager.is_eq(e) && m_manager.get_sort(to_app(e)->get_arg(0))->get_family_id() == m_manager.get_family_id("array"))) + mark_as_relevant(e); + } + } + found_watch:; + } + } + SASSERT(it2 <= end); + w.set_end_clause(it2); + } + return true; + } + + /** + \brief Push a new equality for theory th, into the theory equality propagation queue. + */ + void context::push_new_th_eq(theory_id th, theory_var lhs, theory_var rhs) { + SASSERT(lhs != rhs); + SASSERT(lhs != null_theory_var); + SASSERT(rhs != null_theory_var); + SASSERT(th != null_theory_id); + SASSERT(get_theory(th)->get_enode(lhs) != get_theory(th)->get_enode(rhs)); + m_th_eq_propagation_queue.push_back(new_th_eq(th, lhs, rhs)); + } + + /** + \brief Push a new disequality for theory th, into the theory disequality propagation queue. + */ + void context::push_new_th_diseq(theory_id th, theory_var lhs, theory_var rhs) { + SASSERT(lhs != rhs); + SASSERT(lhs != null_theory_var); + SASSERT(rhs != null_theory_var); + SASSERT(th != null_theory_id); + theory * t = get_theory(th); + if (t->get_enode(lhs)->is_interpreted() && t->get_enode(rhs)->is_interpreted()) + return; + TRACE("add_diseq", + tout << "#" << t->get_enode(lhs)->get_owner_id() << " != " + << "#" << t->get_enode(rhs)->get_owner_id() << "\n";); + + m_th_diseq_propagation_queue.push_back(new_th_eq(th, lhs, rhs)); + } + + class add_eq_trail : public trail { + enode * m_r1; + enode * m_n1; + unsigned m_r2_num_parents; + public: + add_eq_trail(enode * r1, enode * n1, unsigned r2_num_parents): + m_r1(r1), + m_n1(n1), + m_r2_num_parents(r2_num_parents) { + } + + virtual void undo(context & ctx) { + ctx.undo_add_eq(m_r1, m_n1, m_r2_num_parents); + } + }; + + /** + \brief Add the equality n1 = n2 with justification js into the logical context. + */ + void context::add_eq(enode * n1, enode * n2, eq_justification js) { + unsigned old_trail_size = m_trail_stack.size(); + + try { + TRACE("add_eq", tout << "assigning: #" << n1->get_owner_id() << " = #" << n2->get_owner_id() << "\n";); + TRACE("add_eq_detail", tout << "assigning\n" << mk_pp(n1->get_owner(), m_manager) << "\n" << mk_pp(n2->get_owner(), m_manager) << "\n"; + tout << "kind: " << js.get_kind() << "\n";); + + m_stats.m_num_add_eq++; + enode * r1 = n1->get_root(); + enode * r2 = n2->get_root(); + + if (r1 == r2) { + TRACE("add_eq", tout << "redundant constraint.\n";); + return; + } + + if (r1->is_interpreted() && r2->is_interpreted()) { + TRACE("add_eq", tout << "interpreted roots conflict.\n";); + set_conflict(mk_justification(eq_conflict_justification(n1, n2, js))); + return; + } + + // Swap r1 and r2: + // 1. if the "equivalence" class of r1 is bigger than the equivalence class of r2 + // OR + // 2. r1 is interpreted but r2 is not. + // + // The second condition is used to enforce the invariant that if a class contain + // an interepreted enode then the root is also interpreted. + if ((r1->get_class_size() > r2->get_class_size() && !r2->is_interpreted()) || r1->is_interpreted()) { + SASSERT(!r2->is_interpreted()); + std::swap(n1, n2); + std::swap(r1, r2); + } + + TRACE("add_eq", tout << "merging: #" << r1->get_owner_id() << " #" << r2->get_owner_id() << + " n1: #" << n1->get_owner_id() << "\n";); + + // It is necessary to propagate relevancy to other elements of + // the equivalence class. This is nessary to enforce the invariant + // in the field m_parent of the enode class. + if (is_relevant(r1)) { // && !m_manager.is_eq(r1->get_owner())) !is_eq HACK + // NOTE for !is_eq HACK... the !is_eq HACK does not propagate relevancy when two + // equality enodes are congruent. I tested this optimization because in V1 + // relevancy is not propagated for congruent equalities. + // This occurs in V2, because we use the congruence table to propagate disequalities + // efficiently. + // I disabled this optimization HACK because it breaks invariants in the rest of the code. + // To use it, I need to go over the code and analyze all affected places. + mark_as_relevant(r2); + } + else if (is_relevant(r2)) { // && !m_manager.is_eq(r2->get_owner())) { // !is_eq HACK + mark_as_relevant(r1); + } + + push_trail(add_eq_trail(r1, n1, r2->get_num_parents())); + + m_qmanager->add_eq_eh(r1, r2); + + merge_theory_vars(n2, n1, js); + + // 'Proof' tree + // n1 -> ... -> r1 + // n2 -> ... -> r2 + SASSERT(n1->trans_reaches(r1)); + invert_trans(n1); + n1->m_trans.m_target = n2; + n1->m_trans.m_justification = js; + SASSERT(r1->trans_reaches(n1)); + // --------------- + // r1 -> .. -> n1 -> n2 -> ... -> r2 + + +#if 0 + { + static unsigned counter = 0; + static unsigned num_adds = 0; + static unsigned num_bad_adds = 0; + num_adds++; + if (r1->get_class_size() <= r2->get_class_size() && + r1->m_parents.size() > r2->m_parents.size()) { + num_bad_adds++; + } + if (num_adds % 100000 == 0) { + verbose_stream() << "[add-eq]: " << num_bad_adds << " " << num_adds << " " + << static_cast(num_bad_adds)/static_cast(num_adds) << "\n"; + } + } +#endif + + + remove_parents_from_cg_table(r1); + + enode * curr = r1; + do { + curr->m_root = r2; + curr = curr->m_next; + } + while(curr != r1); + + SASSERT(r1->get_root() == r2); + + reinsert_parents_into_cg_table(r1, r2, n1, n2, js); + + if (n2->is_bool()) + propagate_bool_enode_assignment(r1, r2, n1, n2); + + // Merge "equivalence" classes + std::swap(r1->m_next, r2->m_next); + + // Update "equivalence" class size + r2->m_class_size += r1->m_class_size; + + CASSERT("add_eq", check_invariant()); + } + catch (...) { + // Restore trail size since procedure was interrupted in the middle. + // If the add_eq_trail remains on the trail stack, then Z3 may crash when the destructor is invoked. + m_trail_stack.shrink(old_trail_size); + throw; + } + } + + /** + \brief When merging to equivalence classes, the parents of the smallest one (that are congruence roots), + must be removed from the congruence table since their hash code will change. + */ + void context::remove_parents_from_cg_table(enode * r1) { + // Remove parents from the congruence table + enode_vector::iterator it = r1->begin_parents(); + enode_vector::iterator end = r1->end_parents(); + for (; it != end; ++it) { + enode * parent = *it; +#if 0 + { + static unsigned num_eqs = 0; + static unsigned num_parents = 0; + static unsigned counter = 0; + if (parent->is_eq()) + num_eqs++; + num_parents++; + if (num_parents % 100000 == 0) { + verbose_stream() << "[remove-cg] " << num_eqs << " " << num_parents << " " + << static_cast(num_eqs)/static_cast(num_parents) << "\n"; + } + } +#endif + SASSERT(parent->is_marked() || !parent->is_cgc_enabled() || + (!parent->is_true_eq() && parent->is_cgr() == m_cg_table.contains_ptr(parent)) || + (parent->is_true_eq() && !m_cg_table.contains_ptr(parent))); + if (!parent->is_marked() && parent->is_cgr() && !parent->is_true_eq()) { + TRACE("add_eq_parents", tout << "add_eq removing: #" << parent->get_owner_id() << "\n";); + SASSERT(!parent->is_cgc_enabled() || m_cg_table.contains_ptr(parent)); + parent->set_mark(); + if (parent->is_cgc_enabled()) { + m_cg_table.erase(parent); + SASSERT(!m_cg_table.contains_ptr(parent)); + } + } + } + } + + /** + \brief Reinsert the parents of r1 that were removed from the + cg_table at remove_parents_from_cg_table. Some of these parents will + become congruent to other enodes, and a new equality will be propagated. + Moreover, this method is also used for doing equality propagation. + + The parents of r1 that remain as congruence roots are copied to the + r2->m_parents. + + The n1, n2, js arguments are used to implement dynamic ackermanization. + js is a justification for n1 and n2 being equal, and the equality n1 = n2 is + the one that implied r1 = r2. + */ + void context::reinsert_parents_into_cg_table(enode * r1, enode * r2, enode * n1, enode * n2, eq_justification js) { + enode_vector & r2_parents = r2->m_parents; + enode_vector::iterator it = r1->begin_parents(); + enode_vector::iterator end = r1->end_parents(); + for (; it != end; ++it) { + enode * parent = *it; + if (!parent->is_marked()) + continue; + parent->unset_mark(); + if (parent->is_eq()) { + SASSERT(parent->get_num_args() == 2); + TRACE("add_eq_bug", tout << "visiting: #" << parent->get_owner_id() << "\n";); + enode * lhs = parent->get_arg(0); + enode * rhs = parent->get_arg(1); + if (lhs->get_root() == rhs->get_root()) { + SASSERT(parent->is_true_eq()); + unsigned expr_id = parent->get_owner_id(); + bool_var v = get_bool_var_of_id(expr_id); + lbool val = get_assignment(v); + if (val != l_true) { + if (val == l_false && js.get_kind() == eq_justification::CONGRUENCE) + m_dyn_ack_manager.cg_conflict_eh(n1->get_owner(), n2->get_owner()); + + assign(literal(v), mk_justification(eq_propagation_justification(lhs, rhs))); + } + // It is not necessary to reinsert the equality to the congruence table + continue; + } + } + if (parent->is_cgc_enabled()) { + enode_bool_pair pair = m_cg_table.insert(parent); + enode * parent_prime = pair.first; + if (parent_prime == parent) { + TRACE("add_eq_parents", tout << "add_eq reinserting: #" << parent->get_owner_id() << "\n";); + SASSERT(parent); + SASSERT(parent->is_cgr()); + SASSERT(m_cg_table.contains_ptr(parent)); + r2_parents.push_back(parent); + continue; + } + parent->m_cg = parent_prime; + SASSERT(!m_cg_table.contains_ptr(parent)); + if (parent_prime->m_root != parent->m_root) { + bool used_commutativity = pair.second; + TRACE("cg", tout << "found new congruence: #" << parent->get_owner_id() << " = #" << parent_prime->get_owner_id() + << " used_commutativity: " << used_commutativity << "\n";); + push_new_congruence(parent, parent_prime, used_commutativity); + } + } + else { + // If congruence closure is not enabled for parent, then I just copy it + // to r2_parents + r2_parents.push_back(parent); + } + } + } + + /** + \brief A transitivity 'proof' branch is represented by + the following sequence starting at n and ending + at n->get_root. + + N1 = n + N_{i+1} = N_i->m_trans.m_target + and, there is an k such that N_k = n->get_root() + + This method will invert this branch. + */ + void context::invert_trans(enode * n) { + enode * curr = n->m_trans.m_target; + enode * prev = n; + eq_justification js = n->m_trans.m_justification; + prev->m_trans.m_target = 0; + prev->m_trans.m_justification = null_eq_justification; + while (curr != 0) { + SASSERT(prev->trans_reaches(n)); + enode * new_curr = curr->m_trans.m_target; + eq_justification new_js = curr->m_trans.m_justification; + curr->m_trans.m_target = prev; + curr->m_trans.m_justification = js; + prev = curr; + js = new_js; + curr = new_curr; + } + } + + /** + \brief Given that r is the root of n, and r contains a theory variable + for theory th_id, this method returns a theory variable that is 'closer' + to n in the 'proof branch' n -> ... -> r. + + This method is used to improve the quality of the conflict clauses produced + by the logical context. + + Consider the following example: + + - Consider the following sequence of equalities: + n1 = n2 = n3 = n4 = n5 = n6 + + - Now, assume that n1 is the root of the equivalence class + after each merge. So, the 'proof' branch will have the following + shape: + + n1 <- n2 <- n3 <- n4 <- n5 <- n6 + + - Assuming that all nodes are attached to theory variable, then the following + sequence of equalities is sent to the theory if the method get_closest_var is not used: + + n1 = n2, n1 = n3, n1 = n4, n1 = n5, n1 = n6 + + - This sequence is bad, and bad justifications may be produced by theory. + For example, assume the following arithmetic constraint + + n5 < n6 + + For the arithmetic module, the best justification will be: + n1 = n5, n1 = n6 and n5 < n6 + + This justification contains unnecessary 'junk' to justify that n5 = n6. + That is, it proves n5 = n6 using the proofs for n1 = n5 and n1 = n6. + + When the method get_closest_var is used in the communication with theories, + the logical context will send the natural sequence of equalities to the theories: + + n1 = n2 = n3 = n4 = n5 = n6 + */ + theory_var context::get_closest_var(enode * n, theory_id th_id) { + if (th_id == null_theory_id) + return null_theory_var; + while (n != 0) { + theory_var v = n->get_th_var(th_id); + if (v != null_theory_var) + return v; + n = n->m_trans.m_target; + } + return null_theory_var; + } + + /** + \brief Merge the theory variables of n2->get_root() and n1->get_root(), the result is stored in n2->get_root(). + New th_var equalities are propagated to the theories. + + \remark In most cases, an enode is attached to at most one theory var. + */ + void context::merge_theory_vars(enode * n2, enode * n1, eq_justification js) { + enode * r2 = n2->get_root(); + enode * r1 = n1->get_root(); + if (!r1->has_th_vars() && !r2->has_th_vars()) + return; + + theory_id from_th = null_theory_id; + + if (js.get_kind() == eq_justification::JUSTIFICATION) + from_th = js.get_justification()->get_from_theory(); + + if (r2->m_th_var_list.get_next() == 0 && r1->m_th_var_list.get_next() == 0) { + // Common case: r2 and r1 have at most one theory var. + theory_id t2 = r2->m_th_var_list.get_th_id(); + theory_id t1 = r1->m_th_var_list.get_th_id(); + // verbose_stream() << "[merge_theory_vars] t2: " << t2 << ", t1: " << t1 << "\n"; + theory_var v2 = m_fparams.m_new_core2th_eq ? get_closest_var(n2, t2) : r2->m_th_var_list.get_th_var(); + theory_var v1 = m_fparams.m_new_core2th_eq ? get_closest_var(n1, t1) : r1->m_th_var_list.get_th_var(); + TRACE("merge_theory_vars", + tout << "v2: " << v2 << " #" << r1->get_owner_id() << ", v1: " << v1 << " #" << r2->get_owner_id() + << ", t2: " << t2 << ", t1: " << t1 << "\n";); + if (v2 != null_theory_var && v1 != null_theory_var) { + if (t1 == t2) { + // only send the equality to the theory, if the equality was not propagated by it. + if (t1 != from_th) + push_new_th_eq(t1, v2, v1); + } + else { + // uncommon case: r2 will have two theory_vars attached to it. + r2->add_th_var(v1, t1, m_region); + push_new_th_diseqs(r2, v1, get_theory(t1)); + push_new_th_diseqs(r1, v2, get_theory(t2)); + } + } + else if (v1 == null_theory_var && v2 != null_theory_var) { + push_new_th_diseqs(r1, v2, get_theory(t2)); + } + else if (v1 != null_theory_var && v2 == null_theory_var) { + r2->m_th_var_list.set_th_var(v1); + r2->m_th_var_list.set_th_id(t1); + TRACE("merge_theory_vars", tout << "push_new_th_diseqs v1: " << v1 << ", t1: " << t1 << "\n";); + push_new_th_diseqs(r2, v1, get_theory(t1)); + } + } + else { + // r1 and/or r2 have more than one theory variable. + TRACE("merge_theory_vars", + tout << "#" << r1->get_owner_id() << " == #" << r2->get_owner_id() << "\n";); + + + theory_var_list * l2 = r2->get_th_var_list(); + while (l2) { + theory_id t2 = l2->get_th_id(); + theory_var v2 = m_fparams.m_new_core2th_eq ? get_closest_var(n2, t2) : l2->get_th_var(); + SASSERT(v2 != null_theory_var); + SASSERT(t2 != null_theory_id); + theory_var v1 = m_fparams.m_new_core2th_eq ? get_closest_var(n1, t2) : r1->get_th_var(t2); + + if (v1 != null_theory_var) { + // only send the equality to the theory, if the equality was not propagated by it. + if (t2 != from_th) + push_new_th_eq(t2, v2, v1); + } + else { + push_new_th_diseqs(r1, v2, get_theory(t2)); + } + l2 = l2->get_next(); + } + + theory_var_list * l1 = r1->get_th_var_list(); + while (l1) { + theory_id t1 = l1->get_th_id(); + theory_var v1 = m_fparams.m_new_core2th_eq ? get_closest_var(n1, t1) : l1->get_th_var(); + SASSERT(v1 != null_theory_var); + SASSERT(t1 != null_theory_id); + theory_var v2 = r2->get_th_var(t1); + if (v2 == null_theory_var) { + r2->add_th_var(v1, t1, m_region); + push_new_th_diseqs(r2, v1, get_theory(t1)); + } + l1 = l1->get_next(); + } + } + } + + /** + \brief Propabate the boolean assignment when two equivalence classes are merged. + */ + void context::propagate_bool_enode_assignment(enode * r1, enode * r2, enode * n1, enode * n2) { + SASSERT(n1->is_bool()); + SASSERT(n2->is_bool()); + SASSERT(r1->is_bool()); + SASSERT(r2->is_bool()); + if (r2 == m_false_enode || r2 == m_true_enode) { + bool sign = r2 == m_false_enode; + enode * curr = r1; + do { + SASSERT(curr != m_false_enode); + bool_var v = enode2bool_var(curr); + literal l(v, sign); + if (get_assignment(l) != l_true) + assign(l, mk_justification(eq_root_propagation_justification(curr))); + curr = curr->m_next; + } + while(curr != r1); + } + else { + bool_var v1 = enode2bool_var(n1); + bool_var v2 = enode2bool_var(n2); + lbool val1 = get_assignment(v1); + lbool val2 = get_assignment(v2); + if (val1 != val2) { + if (val2 == l_undef) + propagate_bool_enode_assignment_core(n1, n2); + else + propagate_bool_enode_assignment_core(n2, n1); + } + } + } + + /** + \brief source and target are boolean enodes, they were proved to be equal, + and the boolean variable associated with source is assigned. Then, + copy the assignment to the boolean variables associated with target. + */ + void context::propagate_bool_enode_assignment_core(enode * source, enode * target) { + SASSERT(source->is_bool()); + SASSERT(target->is_bool()); + SASSERT(source->get_root() == target->get_root()); + bool_var v_source = enode2bool_var(source); + lbool val = get_assignment(v_source); + SASSERT(val != l_undef); + bool sign = val == l_false; + enode * first = target; + do { + bool_var v2 = enode2bool_var(target); + lbool val2 = get_assignment(v2); + if (val2 != val) { + if (val2 != l_undef && congruent(source, target) && source->get_num_args() > 0) + m_dyn_ack_manager.cg_conflict_eh(source->get_owner(), target->get_owner()); + assign(literal(v2, sign), mk_justification(mp_iff_justification(source, target))); + } + target = target->get_next(); + } + while (first != target); + } + + void context::undo_add_eq(enode * r1, enode * n1, unsigned r2_num_parents) { + enode * r2 = r1->get_root(); + TRACE("add_eq", tout << "undo_add_eq #" << r1->get_owner_id() << " #" << r2->get_owner_id() << "\n";); + + // restore r2 class size + r2->m_class_size -= r1->m_class_size; + + // unmerge "equivalence" classes + std::swap(r1->m_next, r2->m_next); + + // remove the parents of r1 that remained as congruence roots + enode_vector::iterator it = r2->begin_parents(); + enode_vector::iterator end = r2->end_parents(); + it += r2_num_parents; + for (; it != end; ++it) { + enode * parent = *it; + if (parent->is_cgc_enabled()) { + TRACE("add_eq_parents", tout << "removing: #" << parent->get_owner_id() << "\n";); + CTRACE("add_eq", !parent->is_cgr(), + tout << "old num_parents: " << r2_num_parents << ", num_parents: " << r2->m_parents.size() << ", parent: #" << + parent->get_owner_id() << ", parents: \n"; + for (unsigned i = 0; i < r2->m_parents.size(); i++) { + tout << "#" << r2->m_parents[i]->get_owner_id() << " "; + } + display(tout);); + SASSERT(parent->is_cgr()); + SASSERT(m_cg_table.contains_ptr(parent)); + m_cg_table.erase(parent); + } + } + + enode * curr = r1; + do { + curr->m_root = r1; + curr = curr->m_next; + } + while(curr != r1); + + // restore parents of r2 + r2->m_parents.shrink(r2_num_parents); + + // try to reinsert parents of r1 that are not cgr + it = r1->begin_parents(); + end = r1->end_parents(); + for (; it != end; ++it) { + enode * parent = *it; + TRACE("add_eq_parents", tout << "visiting: #" << parent->get_owner_id() << "\n";); + if (parent->is_cgc_enabled()) { + enode * cg = parent->m_cg; + if (!parent->is_true_eq() && + (parent == cg || // parent was root of the congruence class before and after the merge + !congruent(parent, cg) // parent was root of the congruence class before but not after the merge + )) { + TRACE("add_eq_parents", tout << "trying to reinsert\n";); + m_cg_table.insert(parent); + parent->m_cg = parent; + } + } + } + + // restore theory vars + if (r2->m_th_var_list.get_next() == 0) { + // common case: r2 has at most one variable + theory_var v2 = r2->m_th_var_list.get_th_var(); + if (v2 != null_theory_var) { + theory_id t2 = r2->m_th_var_list.get_th_id(); + if (get_theory(t2)->get_enode(v2)->get_root() != r2) { + SASSERT(get_theory(t2)->get_enode(v2)->get_root() == r1); + r2->m_th_var_list.set_th_var(null_theory_var); //remove variable from r2 + r2->m_th_var_list.set_th_id(null_theory_id); + } + } + } + else { + restore_theory_vars(r2, r1); + } + + // 'Proof' tree + // r1 -> .. -> n1 -> n2 -> ... -> r2 + SASSERT(r1->trans_reaches(r2)); + SASSERT(r1->trans_reaches(n1)); + n1->m_trans.m_target = 0; + n1->m_trans.m_justification = null_eq_justification; + invert_trans(r1); + // --------------- + // n1 -> ... -> r1 + // n2 -> ... -> r2 + SASSERT(n1->trans_reaches(r1)); + SASSERT(r1->m_trans.m_target == 0); + + CASSERT("add_eq", check_invariant()); + } + + /** + \brief Auxiliary method for undo_add_eq. + It restores the theory variables of a given root enode. + This method deletes any theory variable v2 of r2 (for a theory t2) + whenever: + + get_theory(t2)->get_enode(v2)->get_root() != r2 + + That is, v2 is not equivalent to r2 anymore. + */ + void context::restore_theory_vars(enode * r2, enode * r1) { + SASSERT(r2->get_root() == r2); + theory_var_list * new_l2 = 0; + theory_var_list * l2 = r2->get_th_var_list(); + while (l2) { + theory_var v2 = l2->get_th_var(); + theory_id t2 = l2->get_th_id(); + + if (get_theory(t2)->get_enode(v2)->get_root() != r2) { + SASSERT(get_theory(t2)->get_enode(v2)->get_root() == r1); + l2 = l2->get_next(); + } + else { + if (new_l2) { + new_l2->set_next(l2); + new_l2 = l2; + } + else { + r2->m_th_var_list = *l2; + new_l2 = &(r2->m_th_var_list); + } + l2 = l2->get_next(); + } + } + + if (new_l2) { + new_l2->set_next(0); + } + else { + r2->m_th_var_list.set_th_var(null_theory_var); + r2->m_th_var_list.set_next(0); + } + } + + /** + \brief This method is invoked when a new disequality is asserted. + The disequality is propagated to the theories. + */ + void context::add_diseq(enode * n1, enode * n2) { + enode * r1 = n1->get_root(); + enode * r2 = n2->get_root(); + TRACE("add_diseq", tout << "assigning: #" << n1->get_owner_id() << " != #" << n2->get_owner_id() << "\n"; + tout << mk_ll_pp(n1->get_owner(), m_manager) << " != "; + tout << mk_ll_pp(n2->get_owner(), m_manager) << "\n"; + tout << mk_ll_pp(r1->get_owner(), m_manager) << " != "; + tout << mk_ll_pp(r2->get_owner(), m_manager) << "\n"; + ); + +#ifdef Z3DEBUG + push_trail(push_back_trail(m_diseq_vector)); + m_diseq_vector.push_back(enode_pair(n1, n2)); +#endif + + if (r1 == r2) { + TRACE("add_diseq_inconsistent", tout << "add_diseq #" << n1->get_owner_id() << " #" << n2->get_owner_id() << " inconsistency, scope_lvl: " << m_scope_lvl << "\n";); + return; // context is inconsistent + } + + // Propagate disequalities to theories + if (r1->m_th_var_list.get_next() == 0 && r2->m_th_var_list.get_next() == 0) { + // common case: r2 and r1 have at most one theory var. + theory_id t1 = r1->m_th_var_list.get_th_id(); + theory_var v1 = m_fparams.m_new_core2th_eq ? get_closest_var(n1, t1) : r1->m_th_var_list.get_th_var(); + theory_var v2 = m_fparams.m_new_core2th_eq ? get_closest_var(n2, t1) : r2->m_th_var_list.get_th_var(); + TRACE("add_diseq", tout << "one theory diseq\n"; + tout << v1 << " != " << v2 << "\n"; + tout << "th1: " << t1 << " th2: " << r2->m_th_var_list.get_th_id() << "\n"; + ); + if (t1 != null_theory_id && v1 != null_theory_var && v2 != null_theory_var && + t1 == r2->m_th_var_list.get_th_id()) { + if (get_theory(t1)->use_diseqs()) + push_new_th_diseq(t1, v1, v2); + } + } + else { + theory_var_list * l1 = r1->get_th_var_list(); + while (l1) { + theory_id t1 = l1->get_th_id(); + theory_var v1 = m_fparams.m_new_core2th_eq ? get_closest_var(n1, t1) : l1->get_th_var(); + theory * th = get_theory(t1); + TRACE("add_diseq", tout << m_manager.get_family_name(t1) << "\n";); + if (th->use_diseqs()) { + theory_var v2 = m_fparams.m_new_core2th_eq ? get_closest_var(n2, t1) : r2->get_th_var(t1); + if (v2 != null_theory_var) + push_new_th_diseq(t1, v1, v2); + } + l1 = l1->get_next(); + } + } + } + + /** + \brief Return true if n1 and n2 are known to be disequal in the logical + context. + */ + bool context::is_diseq(enode * n1, enode * n2) const { + SASSERT(m_manager.get_sort(n1->get_owner()) == m_manager.get_sort(n2->get_owner())); + context * _this = const_cast(this); + if (!m_is_diseq_tmp) { + app * eq = m_manager.mk_eq(n1->get_owner(), n2->get_owner()); + m_manager.inc_ref(eq); + _this->m_is_diseq_tmp = enode::mk_dummy(m_manager, m_app2enode, eq); + } + else if (m_manager.get_sort(m_is_diseq_tmp->get_owner()->get_arg(0)) != m_manager.get_sort(n1->get_owner())) { + m_manager.dec_ref(m_is_diseq_tmp->get_owner()); + app * eq = m_manager.mk_eq(n1->get_owner(), n2->get_owner()); + m_manager.inc_ref(eq); + m_is_diseq_tmp->m_func_decl_id = UINT_MAX; + m_is_diseq_tmp->m_owner = eq; + } + m_is_diseq_tmp->m_args[0] = n1; + m_is_diseq_tmp->m_args[1] = n2; + SASSERT(m_is_diseq_tmp->get_num_args() == 2); + enode * r = m_cg_table.find(m_is_diseq_tmp); + SASSERT((r != 0) == m_cg_table.contains(m_is_diseq_tmp)); + TRACE("is_diseq", tout << "r: " << r << "\n";); + if (r) { + SASSERT(r->is_eq()); + literal l = enode2literal(r->get_root()); + // SASSERT(result == is_diseq_slow(n1, n2)); + return l == false_literal || (is_relevant(l) && get_assignment(l) == l_false); + } + CTRACE("is_diseq_bug", is_diseq_slow(n1, n2), tout << "#" << n1->get_owner_id() << " #" << n2->get_owner_id() << "\n";); + return false; + } + + /** + \brief Slow version of is_diseq + */ + bool context::is_diseq_slow(enode * n1, enode * n2) const { + if (n1->get_num_parents() > n2->get_num_parents()) + std::swap(n1, n2); + enode_vector::iterator it = n1->begin_parents(); + enode_vector::iterator end = n1->end_parents(); + for (; it != end; ++it) { + enode * parent = *it; + if (parent->is_eq() && is_relevant(parent->get_owner()) && get_assignment(enode2bool_var(parent)) == l_false && + ((parent->get_arg(0)->get_root() == n1->get_root() && parent->get_arg(1)->get_root() == n2->get_root()) || + (parent->get_arg(1)->get_root() == n1->get_root() && parent->get_arg(0)->get_root() == n2->get_root()))) { + TRACE("is_diseq_bug", tout << "parent: #" << parent->get_owner_id() << ", parent->root: #" << + parent->get_root()->get_owner_id() << " assignment(parent): " << get_assignment(enode2bool_var(parent)) << + " args: #" << parent->get_arg(0)->get_owner_id() << " #" << parent->get_arg(1)->get_owner_id() << "\n";); + return true; + } + } + return false; + } + +#define SMALL_NUM_PARENTS 3 + + bool context::is_ext_diseq(enode * n1, enode * n2, unsigned depth) { + enode * r1 = n1->get_root(); + enode * r2 = n2->get_root(); + if (r1 == r2) + return false; + if (r1->is_interpreted() && r2->is_interpreted()) + return true; + if (is_diseq(n1, n2)) + return true; + if (r1->get_num_parents() > r2->get_num_parents()) { + std::swap(n1, n2); + std::swap(r1, r2); + } + if (depth == 0) + return false; + if (r1->get_num_parents() < SMALL_NUM_PARENTS) { + TRACE("is_ext_diseq", tout << mk_bounded_pp(n1->get_owner(), m_manager) << " " << mk_bounded_pp(n2->get_owner(), m_manager) << " " << depth << "\n";); + enode_vector::iterator it1 = r1->begin_parents(); + enode_vector::iterator end1 = r1->end_parents(); + for (; it1 != end1; ++it1) { + enode * p1 = *it1; + if (!is_relevant(p1)) + continue; + if (p1->is_eq()) + continue; + if (!p1->is_cgr()) + continue; + func_decl * f = p1->get_decl(); + TRACE("is_ext_diseq", tout << "p1: " << mk_bounded_pp(p1->get_owner(), m_manager) << "\n";); + unsigned num_args = p1->get_num_args(); + enode_vector::iterator it2 = r2->begin_parents(); + enode_vector::iterator end2 = r2->end_parents(); + for (; it2 != end2; ++it2) { + enode * p2 = *it2; + if (!is_relevant(p2)) + continue; + if (p2->is_eq()) + continue; + if (!p2->is_cgr()) + continue; + TRACE("is_ext_diseq", tout << "p2: " << mk_bounded_pp(p2->get_owner(), m_manager) << "\n";); + if (p1->get_root() != p2->get_root() && p2->get_decl() == f && p2->get_num_args() == num_args) { + unsigned j = 0; + for (j = 0; j < num_args; j++) { + enode * arg1 = p1->get_arg(j)->get_root(); + enode * arg2 = p2->get_arg(j)->get_root(); + if (arg1 == arg2) + continue; + if ((arg1 == r1 || arg1 == r2) && + (arg2 == r1 || arg2 == r2)) + continue; + break; + } + if (j == num_args) { + TRACE("is_ext_diseq", tout << "found parents: " << mk_bounded_pp(p1->get_owner(), m_manager) << " " << mk_bounded_pp(p2->get_owner(), m_manager) << "\n";); + if (is_ext_diseq(p1, p2, depth - 1)) + return true; + } + } + } + } + } + else { + if (depth >= m_almost_cg_tables.size()) { + unsigned old_sz = m_almost_cg_tables.size(); + m_almost_cg_tables.resize(depth+1, 0); + for (unsigned i = old_sz; i < depth + 1; i++) + m_almost_cg_tables[i] = alloc(almost_cg_table); + } + almost_cg_table & table = *(m_almost_cg_tables[depth]); + table.reset(r1, r2); + enode_vector::iterator it1 = r1->begin_parents(); + enode_vector::iterator end1 = r1->end_parents(); + for (; it1 != end1; ++it1) { + enode * p1 = *it1; + if (!is_relevant(p1)) + continue; + if (p1->is_eq()) + continue; + if (!p1->is_cgr()) + continue; + table.insert(p1); + } + if (table.empty()) + return false; + enode_vector::iterator it2 = r2->begin_parents(); + enode_vector::iterator end2 = r2->end_parents(); + for (; it2 != end2; ++it2) { + enode * p2 = *it2; + if (!is_relevant(p2)) + continue; + if (p2->is_eq()) + continue; + if (!p2->is_cgr()) + continue; + list * ps = table.find(p2); + if (ps) { + while (ps) { + enode * p1 = ps->head(); + if (p1->get_root() != p2->get_root() && is_ext_diseq(p1, p2, depth - 1)) + return true; + ps = ps->tail(); + } + } + } + } + return false; + } + + /** + \brief Return an enode n congruent to (f args). If there is no such enode in the E-graph, then return 0. + */ + enode * context::get_enode_eq_to(func_decl * f, unsigned num_args, enode * const * args) { + enode * tmp = m_tmp_enode.set(f, num_args, args); + enode * r = m_cg_table.find(tmp); +#ifdef Z3DEBUG + if (r != 0) { + SASSERT(r->get_owner()->get_decl() == f); + SASSERT(r->get_num_args() == num_args); + if (r->is_commutative()) { + // TODO + } + else { + for (unsigned i = 0; i < num_args; i++) { + expr * arg = r->get_owner()->get_arg(i); + SASSERT(e_internalized(arg)); + enode * _arg = get_enode(arg); + CTRACE("eq_to_bug", args[i]->get_root() != _arg->get_root(), + tout << "#" << args[i]->get_owner_id() << " #" << args[i]->get_root()->get_owner_id() + << " #" << _arg->get_owner_id() << " #" << _arg->get_root()->get_owner_id() << "\n"; + tout << "#" << r->get_owner_id() << "\n"; + display(tout);); + SASSERT(args[i]->get_root() == _arg->get_root()); + } + } + } +#endif + return r; + } + + /** + \brief Process the equality propagation queue. + + \remark The method assign_eq adds a new entry on this queue. + */ + bool context::propagate_eqs() { + for (unsigned i = 0; i < m_eq_propagation_queue.size(); i++) { + new_eq & entry = m_eq_propagation_queue[i]; +#if 0 + static unsigned counter1 = 0; + static unsigned counter2 = 0; + if (entry.m_lhs->is_eq() || entry.m_rhs->is_eq()) + counter1++; + else + counter2++; + if ((counter1 + counter2) % 10000 == 0) + std::cout << counter1 << " " << counter2 << "\n"; +#endif + add_eq(entry.m_lhs, entry.m_rhs, entry.m_justification); + if (inconsistent()) + return false; + } + m_eq_propagation_queue.reset(); + return true; + } + + /** + \brief Process equalities, theory atoms, etc. + */ + bool context::propagate_atoms() { + SASSERT(!inconsistent()); + for (unsigned i = 0; i < m_atom_propagation_queue.size(); i++) { + SASSERT(!inconsistent()); + literal l = m_atom_propagation_queue[i]; + bool_var v = l.var(); + bool_var_data & d = get_bdata(v); + lbool val = get_assignment(v); + TRACE("propagate_atoms", tout << "propagating atom, #" << bool_var2expr(v)->get_id() << ", is_enode(): " << d.is_enode() << " " << l << "\n";); + SASSERT(val != l_undef); + if (d.is_enode()) + propagate_bool_var_enode(v); + if (inconsistent()) + return false; + if (d.is_eq()) { + app * n = to_app(m_bool_var2expr[v]); + SASSERT(m_manager.is_eq(n)); + expr * lhs = n->get_arg(0); + expr * rhs = n->get_arg(1); + if (val == l_true) { + add_eq(get_enode(lhs), get_enode(rhs), eq_justification(l)); + } + else { + TRACE("add_diseq", display_eq_detail(tout, bool_var2enode(v));); + add_diseq(get_enode(lhs), get_enode(rhs)); + } + } + else if (d.is_theory_atom()) { + theory * th = m_theories.get_plugin(d.get_theory()); + SASSERT(th); + th->assign_eh(v, val == l_true); + } + else if (d.is_quantifier()) { + // Remark: when RELEVANCY_LEMMA is true, a quantifier can be asserted to false and marked as relevant. + // This happens when a quantifier is part of a lemma (conflict-clause), and this conflict clause + // becomes an unit-clause and the remaining literal is the negation of a quantifier. + CTRACE("assign_quantifier_bug", get_assignment(v) != l_true, + tout << "#" << bool_var2expr(v)->get_id() << " val: " << get_assignment(v) << "\n"; + tout << mk_pp(bool_var2expr(v), m_manager) << "\n"; + display(tout);); + SASSERT(is_quantifier(m_bool_var2expr[v])); + if (get_assignment(v) == l_true) { + // All universal quantifiers have positive polarity in the input formula. + // So, we can ignore quantifiers assigned to false. + assign_quantifier(to_quantifier(m_bool_var2expr[v])); + } + } + if (inconsistent()) + return false; + } + m_atom_propagation_queue.reset(); + return true; + } + + class set_var_theory_trail : public trail { + bool_var m_var; + public: + set_var_theory_trail(bool_var v):m_var(v) {} + virtual void undo(context & ctx) { + bool_var_data & d = ctx.m_bdata[m_var]; + d.reset_notify_theory(); + } + }; + + void context::set_var_theory(bool_var v, theory_id tid) { + SASSERT(get_var_theory(v) == null_theory_var); + SASSERT(tid > 0 && tid <= 255); + SASSERT(get_intern_level(v) <= m_scope_lvl); + if (m_scope_lvl > get_intern_level(v)) + push_trail(set_var_theory_trail(v)); + bool_var_data & d = m_bdata[v]; + d.set_notify_theory(tid); + } + + /** + \brief Propagate the truth value assigned to v, to the enode + associated with v. Let n be the enode associated with v. Then, + this method merges n = true_term (n = false_term) if v was + assigned to true (false). + */ + void context::propagate_bool_var_enode(bool_var v) { + SASSERT(get_assignment(v) != l_undef); + SASSERT(get_bdata(v).is_enode()); + lbool val = get_assignment(v); + TRACE("propagate_bool_var_enode_bug", tout << "var: " << v << " #" << bool_var2expr(v)->get_id() << "\n";); + SASSERT(v < static_cast(m_b_internalized_stack.size())); + enode * n = bool_var2enode(v); + bool sign = val == l_false; + if (n->merge_tf()) + add_eq(n, sign ? m_false_enode : m_true_enode, eq_justification(literal(v, sign))); + enode * r = n->get_root(); + if (r == m_true_enode || r == m_false_enode) + return; + // Move truth value to other elements of the equivalence class if: + // 1) n is the root of the equivalence class + // 2) n is not the root, but the variable associated with the root is unassigned. + if (n == r || + !is_relevant(r) || // <<<< added to fix propagation bug. + get_assignment(enode2bool_var(r)) != val) { + enode * first = n; + n = n->get_next(); + while (n != first) { + bool_var v2 = enode2bool_var(n); + if (get_assignment(v2) != val) + assign(literal(v2, sign), mk_justification(mp_iff_justification(first, n))); + n = n->get_next(); + } + } + } + + /** + \brief Traverse the disequalities of r's equivalence class, and + propagate them to the theory. + */ + void context::push_new_th_diseqs(enode * r, theory_var v, theory * th) { + if (!th->use_diseqs()) { + TRACE("push_new_th_diseqs", tout << m_manager.get_family_name(th->get_id()) << " not using diseqs\n";); + return; + } + TRACE("push_new_th_diseqs", tout << "#" << r->get_owner_id() << " v" << v << "\n";); + theory_id th_id = th->get_id(); + enode_vector::iterator it = r->begin_parents(); + enode_vector::iterator end = r->end_parents(); + for (; it != end; ++it) { + enode * parent = *it; + CTRACE("parent_bug", parent == 0, tout << "#" << r->get_owner_id() << ", num_parents: " << r->get_num_parents() << "\n"; display(tout);); + if (parent->is_eq()) { + bool_var bv = get_bool_var_of_id(parent->get_owner_id()); + if (get_assignment(bv) == l_false) { + enode * lhs = parent->get_arg(0); + enode * rhs = parent->get_arg(1); + TRACE("push_new_th_diseqs", + tout << "#" << parent->get_owner_id() << " "; + tout << "lhs: #" << lhs->get_owner_id() << ", rhs: #" << rhs->get_owner_id() << + ", lhs->root: #" << lhs->get_root()->get_owner_id() << ", rhs->root: #" << rhs->get_root()->get_owner_id() << + ", r: #" << r->get_owner_id() << ", r->root: #" << r->get_root()->get_owner_id() << "\n"; + ); + CTRACE("push_new_th_diseqs", lhs->get_root() != r->get_root() && rhs->get_root() != r->get_root(), + tout << "lhs: #" << lhs->get_owner_id() << ", rhs: #" << rhs->get_owner_id() << + ", lhs->root: #" << lhs->get_root()->get_owner_id() << ", rhs->root: #" << rhs->get_root()->get_owner_id() << + ", r: #" << r->get_owner_id() << ", r->root: #" << r->get_root()->get_owner_id() << "\n"; + display(tout);); + SASSERT(lhs->get_root() == r->get_root() || rhs->get_root() == r->get_root()); + if (rhs->get_root() == r->get_root()) + std::swap(lhs, rhs); + enode * rhs_root = rhs->get_root(); + theory_var rhs_var = m_fparams.m_new_core2th_eq ? get_closest_var(rhs, th_id) : rhs_root->get_th_var(th_id); + if (m_fparams.m_new_core2th_eq) { + theory_var _v = get_closest_var(lhs, th_id); + if (_v != null_theory_var) + v = _v; + } + if (rhs_var != null_theory_var && v != rhs_var /* if v == rhs_var then the context will detect the inconsistency. */) + push_new_th_diseq(th_id, v, rhs_var); + } + } + } + } + + /** + \brief Return the truth assignment for an expression + that is attached to a boolean variable. + + \pre The expression must be attached to a boolean variable. + */ + inline lbool context::get_assignment_core(expr * n) const { + CTRACE("get_assignment_bug", !b_internalized(n), tout << "n:\n" << mk_pp(n, m_manager) << "\n"; display(tout);); + SASSERT(b_internalized(n)); + unsigned id = n->get_id(); + bool_var var = get_bool_var_of_id(id); + SASSERT(var != null_bool_var); + return get_assignment(var); + } + + /** + \brief Return the truth assignment for an expression. + If the expression is a not-application, then its child + is inspected instead. + */ + lbool context::get_assignment(expr * n) const { + if (m_manager.is_false(n)) + return l_false; + if (m_manager.is_not(n)) + return ~get_assignment_core(to_app(n)->get_arg(0)); + return get_assignment_core(n); + } + + lbool context::find_assignment(expr * n) const { + if (m_manager.is_false(n)) + return l_false; + if (m_manager.is_not(n)) { + expr * arg = to_app(n)->get_arg(0); + if (b_internalized(arg)) + return ~get_assignment_core(arg); + return l_undef; + } + if (b_internalized(n)) + return get_assignment(n); + return l_undef; + } + + /** + \brief Return the assignment of a 'boolean' enode. + If the enode is not boolean, then return l_undef. + */ + lbool context::get_assignment(enode * n) const { + expr * owner = n->get_owner(); + if (!m_manager.is_bool(owner)) + return l_undef; + if (n == m_false_enode) + return l_false; + bool_var v = get_bool_var_of_id(owner->get_id()); + CTRACE("missing_propagation", v == null_bool_var, tout << mk_pp(owner, m_manager) << "\n";); + return get_assignment(v); + } + + /** + \brief Return set of assigned literals as expressions. + */ + void context::get_assignments(expr_ref_vector& assignments) { + literal_vector::const_iterator it = m_assigned_literals.begin(); + literal_vector::const_iterator end = m_assigned_literals.end(); + for (; it != end; ++it) { + expr_ref e(m_manager); + literal2expr(*it, e); + assignments.push_back(e); + } + } + + void context::relevant_eh(expr * n) { + if (b_internalized(n)) { + bool_var v = get_bool_var(n); + bool_var_data & d = get_bdata(v); + SASSERT(relevancy()); + // Quantifiers are only asserted when marked as relevant. + // Other atoms are only asserted when marked as relevant if m_relevancy_lvl >= 2 + if (d.is_atom() && (d.is_quantifier() || m_fparams.m_relevancy_lvl >= 2)) { + lbool val = get_assignment(v); + if (val != l_undef) + m_atom_propagation_queue.push_back(literal(v, val == l_false)); + } + } + TRACE("propagate_relevancy", tout << "marking as relevant:\n" << mk_bounded_pp(n, m_manager) << "\n";); +#ifndef SMTCOMP + m_case_split_queue->relevant_eh(n); +#endif + + if (is_app(n)) { + if (e_internalized(n)) { + SASSERT(relevancy()); + enode * e = get_enode(n); + m_qmanager->relevant_eh(e); + } + + theory * propagated_th = 0; + family_id fid = to_app(n)->get_family_id(); + if (fid != m_manager.get_basic_family_id()) { + theory * th = get_theory(fid); + if (th != 0) { + th->relevant_eh(to_app(n)); + propagated_th = th; // <<< mark that relevancy_eh was already invoked for theory th. + } + } + + if (e_internalized(n)) { + enode * e = get_enode(n); + theory_var_list * l = e->get_th_var_list(); + while (l) { + theory_id th_id = l->get_th_id(); + theory * th = get_theory(th_id); + // I don't want to invoke relevant_eh twice for the same n. + if (th != propagated_th) + th->relevant_eh(to_app(n)); + l = l->get_next(); + } + } + } + } + + /** + \brief Propagate relevancy using the queue of new assigned literals + located at [qhead, m_assigned_literals.size()). + */ + void context::propagate_relevancy(unsigned qhead) { + if (!relevancy()) + return; + unsigned sz = m_assigned_literals.size(); + while (qhead < sz) { + literal l = m_assigned_literals[qhead]; + SASSERT(get_assignment(l) == l_true); + qhead++; + bool_var var = l.var(); + expr * n = m_bool_var2expr[var]; + m_relevancy_propagator->assign_eh(n, !l.sign()); + } + m_relevancy_propagator->propagate(); + } + + bool context::propagate_theories() { + ptr_vector::iterator it = m_theory_set.begin(); + ptr_vector::iterator end = m_theory_set.end(); + for (; it != end; ++it) { + (*it)->propagate(); + if (inconsistent()) + return false; + } + return true; + } + + void context::propagate_th_eqs() { + for (unsigned i = 0; i < m_th_eq_propagation_queue.size() && !inconsistent(); i++) { + new_th_eq curr = m_th_eq_propagation_queue[i]; + theory * th = get_theory(curr.m_th_id); + SASSERT(th); + th->new_eq_eh(curr.m_lhs, curr.m_rhs); +#ifdef Z3DEBUG + push_trail(push_back_trail(m_propagated_th_eqs)); + m_propagated_th_eqs.push_back(curr); +#endif + } + m_th_eq_propagation_queue.reset(); + } + + void context::propagate_th_diseqs() { + for (unsigned i = 0; i < m_th_diseq_propagation_queue.size() && !inconsistent(); i++) { + new_th_eq curr = m_th_diseq_propagation_queue[i]; + theory * th = get_theory(curr.m_th_id); + SASSERT(th); + th->new_diseq_eh(curr.m_lhs, curr.m_rhs); +#ifdef Z3DEBUG + push_trail(push_back_trail(m_propagated_th_diseqs)); + m_propagated_th_diseqs.push_back(curr); +#endif + } + m_th_diseq_propagation_queue.reset(); + } + + bool context::can_theories_propagate() const { + ptr_vector::const_iterator it = m_theory_set.begin(); + ptr_vector::const_iterator end = m_theory_set.end(); + for (; it != end; ++it) { + if ((*it)->can_propagate()) { + return true; + } + } + return false; + } + + bool context::can_propagate() const { + return + m_qhead != m_assigned_literals.size() || + m_relevancy_propagator->can_propagate() || + !m_atom_propagation_queue.empty() || + m_qmanager->can_propagate() || + can_theories_propagate() || + !m_eq_propagation_queue.empty() || + !m_th_eq_propagation_queue.empty() || + !m_th_diseq_propagation_queue.empty(); + } + + bool context::propagate() { + TRACE("propagate", tout << "propagating...\n";); + while (true) { + if (inconsistent()) + return false; + unsigned qhead = m_qhead; + if (!bcp()) + return false; + SASSERT(!inconsistent()); + propagate_relevancy(qhead); + if (inconsistent()) + return false; + if (!propagate_atoms()) + return false; + if (!propagate_eqs()) + return false; + propagate_th_eqs(); + propagate_th_diseqs(); + if (inconsistent()) + return false; + if (!propagate_theories()) + return false; + m_qmanager->propagate(); + if (inconsistent()) + return false; + if (resource_limits_exceeded()) + return true; + if (!can_propagate()) { + CASSERT("diseq_bug", inconsistent() || check_missing_diseq_conflict()); + CASSERT("eqc_bool", check_eqc_bool_assignment()); + return true; + } + } + } + + void context::set_conflict(b_justification js, literal not_l) { + if (!inconsistent()) { + m_conflict = js; + m_not_l = not_l; + } + } + + void context::assign_quantifier(quantifier * q) { + TRACE("assumption", tout << mk_pp(q, m_manager) << "\n";); + m_qmanager->assign_eh(q); + } + + bool context::contains_instance(quantifier * q, unsigned num_bindings, enode * const * bindings) { + return m_fingerprints.contains(q, q->get_id(), num_bindings, bindings); + } + + bool context::add_instance(quantifier * q, app * pat, unsigned num_bindings, enode * const * bindings, unsigned max_generation, + unsigned min_top_generation, unsigned max_top_generation, ptr_vector & used_enodes) { + return m_qmanager->add_instance(q, pat, num_bindings, bindings, max_generation, min_top_generation, max_top_generation, used_enodes); + } + + void context::rescale_bool_var_activity() { + svector::iterator it = m_activity.begin(); + svector::iterator end = m_activity.end(); + for (; it != end; ++it) + *it *= INV_ACTIVITY_LIMIT; + m_bvar_inc *= INV_ACTIVITY_LIMIT; + } + + /** + \brief Execute next clase split, return false if there are no + more case splits to be performed. + */ + bool context::decide() { + bool_var var; + lbool phase; + m_case_split_queue->next_case_split(var, phase); + + if (var == null_bool_var) { + return false; + } + + TRACE_CODE({ + static unsigned counter = 0; + counter++; + if (counter % 100 == 0) { + TRACE("activity_profile", + for (unsigned i=0; i m_lit_occs[(~l).index()].size(); + break; + } + default: + is_pos = false; + UNREACHABLE(); + } + } + } + + TRACE("decide", tout << "case split pos: " << is_pos << " p" << var << "\n" + << "activity: " << get_activity(var) << "\n";); + + assign(literal(var, !is_pos), b_justification::mk_axiom(), true); + return true; + } + + /** + \brief Update counter that is used to enable/disable phase caching. + */ + void context::update_phase_cache_counter() { + m_phase_counter++; + if (m_phase_cache_on) { + if (m_phase_counter >= m_fparams.m_phase_caching_on) { + m_phase_counter = 0; + m_phase_cache_on = false; + if (m_fparams.m_phase_selection == PS_CACHING_CONSERVATIVE2) + m_phase_default = !m_phase_default; + } + } + else { + if (m_phase_counter >= m_fparams.m_phase_caching_off) { + m_phase_counter = 0; + m_phase_cache_on = true; + if (m_fparams.m_phase_selection == PS_CACHING_CONSERVATIVE2) + m_phase_default = !m_phase_default; + } + } + } + + /** + \brief Create an internal backtracking point + */ + void context::push_scope() { +#ifndef SMTCOMP + if (m_fparams.m_trace_stream != NULL) + *m_fparams.m_trace_stream << "[push] " << m_scope_lvl << "\n"; +#endif + m_scope_lvl++; + m_region.push_scope(); + m_scopes.push_back(scope()); + scope & s = m_scopes.back(); + + m_relevancy_propagator->push(); + s.m_assigned_literals_lim = m_assigned_literals.size(); + s.m_trail_stack_lim = m_trail_stack.size(); + s.m_aux_clauses_lim = m_aux_clauses.size(); + s.m_justifications_lim = m_justifications.size(); + s.m_units_to_reassert_lim = m_units_to_reassert.size(); + + m_qmanager->push(); + + m_fingerprints.push_scope(); + m_case_split_queue->push_scope(); + m_asserted_formulas.push_scope(); + + ptr_vector::iterator it = m_theory_set.begin(); + ptr_vector::iterator end = m_theory_set.end(); + for (; it != end; ++it) + (*it)->push_scope_eh(); + CASSERT("context", check_invariant()); + } + + /** + \brief Execute generic undo-objects. + */ + void context::undo_trail_stack(unsigned old_size) { + ::undo_trail_stack(*this, m_trail_stack, old_size); + } + + /** + \brief Remove watch literal idx from the given clause. + + \pre idx must be 0 or 1. + */ + void context::remove_watch_literal(clause * cls, unsigned idx) { + m_watches[(~cls->get_literal(idx)).index()].remove_clause(cls); + } + + /** + \brief Update the index used for backward subsumption. + */ + void context::remove_lit_occs(clause * cls) { + unsigned num_lits = cls->get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) { + literal l = cls->get_literal(i); + m_lit_occs[l.index()].erase(cls); + } + } + + void context::remove_cls_occs(clause * cls) { + remove_watch_literal(cls, 0); + remove_watch_literal(cls, 1); + if (lit_occs_enabled()) + remove_lit_occs(cls); + } + + /** + \brief Delete the given clause. + + \pre Clause is not in the reinit stack. + */ + void context::del_clause(clause * cls) { + SASSERT(m_flushing || !cls->in_reinit_stack()); + if (!cls->deleted()) + remove_cls_occs(cls); + cls->deallocate(m_manager); + m_stats.m_num_del_clause++; + } + + /** + \brief Delete the clauses in v at locations [old_size .. v.size()) + Reduce the size of v to old_size. + */ + void context::del_clauses(clause_vector & v, unsigned old_size) { + SASSERT(old_size <= v.size()); + clause_vector::iterator begin = v.begin() + old_size; + clause_vector::iterator it = v.end(); + while (it != begin) { + --it; + del_clause(*it); + } + v.shrink(old_size); + } + + void context::mark_as_deleted(clause * cls) { + SASSERT(!cls->deleted()); + remove_cls_occs(cls); + cls->mark_as_deleted(m_manager); + } + + /** + \brief Undo variable assignments. + */ + void context::unassign_vars(unsigned old_lim) { + SASSERT(old_lim <= m_assigned_literals.size()); + + unsigned i = m_assigned_literals.size(); + while (i != old_lim) { + --i; + literal l = m_assigned_literals[i]; + m_assignment[l.index()] = l_undef; + m_assignment[(~l).index()] = l_undef; + bool_var v = l.var(); + bool_var_data & d = get_bdata(v); + d.m_justification = null_b_justification; + m_case_split_queue->unassign_var_eh(v); + } + + m_assigned_literals.shrink(old_lim); + m_qhead = old_lim; + SASSERT(m_qhead == m_assigned_literals.size()); + } + + /** + \brief Invoke method del_eh for the justification that will be deleted. + If the method in_region() returns false, then delete operator is invoked. + */ + void context::del_justifications(ptr_vector & justifications, unsigned old_lim) { + SASSERT(old_lim <= justifications.size()); + unsigned i = justifications.size(); + while (i != old_lim) { + --i; + justification * js = justifications[i]; + js->del_eh(m_manager); + if (!js->in_region()) { + dealloc(js); + } + else { + // If the justification is in a region, then explicitly invoke the destructor. + // This is needed because some justification objects contains vectors. + // The destructors of these vectors need to be invoked. + js->~justification(); + } + } + justifications.shrink(old_lim); + } + + /** + \brief Return true if all literals of c are assigned to false. + */ + bool context::is_empty_clause(clause const * c) const { + unsigned num_lits = c->get_num_literals(); + for(unsigned i = 0; i < num_lits; i++) { + literal l = c->get_literal(i); + if (get_assignment(l) != l_false) + return false; + } + return true; + } + + /** + \brief Return true if the given clause contains one and only one unassigned literal. + */ + bool context::is_unit_clause(clause const * c) const { + bool found = false; + unsigned num_lits = c->get_num_literals(); + for(unsigned i = 0; i < num_lits; i++) { + literal l = c->get_literal(i); + switch (get_assignment(l)) { + case l_false: + break; // skip + case l_undef: + if (found) + return false; + else + found = true; + break; + case l_true: + return false; // clause is already satisfied. + } + } + return found; + } + + /** + \brief When a clause is reinitialized (see reinit_clauses) enodes and literals may + need to be recreated. When an enode is recreated, I want to use the same generation + number it had before being deleted. Otherwise the generation will be 0, and will affect + the loop prevetion heuristics used to control quantifier instantiation. + Thus, I cache the generation number of enodes that will be deleted during backtracking + and recreated by reinit_clauses. + */ + void context::cache_generation(unsigned new_scope_lvl) { + if (!m_clauses_to_reinit.empty()) { + unsigned lim = m_scope_lvl; + if (m_clauses_to_reinit.size() <= lim) { + SASSERT(!m_clauses_to_reinit.empty()); + lim = m_clauses_to_reinit.size() - 1; + } + for (unsigned i = new_scope_lvl; i <= lim; i++) { + clause_vector & v = m_clauses_to_reinit[i]; + clause_vector::iterator it = v.begin(); + clause_vector::iterator end = v.end(); + for (; it != end; ++it) { + clause * cls = *it; + cache_generation(cls, new_scope_lvl); + } + } + } + if (!m_units_to_reassert.empty()) { + scope & s = m_scopes[new_scope_lvl]; + unsigned i = s.m_units_to_reassert_lim; + unsigned sz = m_units_to_reassert.size(); + for (; i < sz; i++) { + expr * unit = m_units_to_reassert.get(i); + cache_generation(unit, new_scope_lvl); + } + } + } + + /** + \brief See cache_generation(unsigned new_scope_lvl) + */ + void context::cache_generation(clause const * cls, unsigned new_scope_lvl) { + cache_generation(cls->get_num_literals(), cls->begin_literals(), new_scope_lvl); + } + + /** + \brief See cache_generation(unsigned new_scope_lvl) + */ + void context::cache_generation(unsigned num_lits, literal const * lits, unsigned new_scope_lvl) { + for(unsigned i = 0; i < num_lits; i++) { + bool_var v = lits[i].var(); + unsigned ilvl = get_intern_level(v); + if (ilvl > new_scope_lvl) + cache_generation(bool_var2expr(v), new_scope_lvl); + } + } + + /** + \brief See cache_generation(unsigned new_scope_lvl) + */ + void context::cache_generation(expr * n, unsigned new_scope_lvl) { + ptr_buffer todo; + todo.push_back(n); + while (!todo.empty()) { + expr * n = todo.back(); + todo.pop_back(); + if (m_cache_generation_visited.contains(n)) + continue; + m_cache_generation_visited.insert(n); + if (is_app(n)) { + if (e_internalized(n)) { + enode * e = get_enode(n); + unsigned ilvl = e->get_iscope_lvl(); + if (ilvl <= new_scope_lvl) + continue; // node and its children will not be recreated during backtracking + TRACE("cached_generation", tout << "caching: #" << n->get_id() << " " << e->get_generation() << "\n";); + m_cached_generation.insert(n, e->get_generation()); + } + unsigned num_args = to_app(n)->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = to_app(n)->get_arg(i); + if (is_app(arg) || is_quantifier(arg)) + todo.push_back(arg); + } + } + else if (is_quantifier(n) && b_internalized(n)) { + m_cached_generation.insert(n, m_qmanager->get_generation(to_quantifier(n))); + todo.push_back(to_quantifier(n)->get_expr()); + } + } + } + + /** + \brief See cache_generation(unsigned new_scope_lvl) + */ + void context::reset_cache_generation() { + m_cache_generation_visited.reset(); + m_cached_generation.reset(); + } + + /** + \brief Reinitialize learned clauses (lemmas) that contain boolean variables + that were deleted during backtracking. + + \remark num_bool_vars contains the number of boolean variables alive + after backtracking. So, a clause contains a dead variable if it + contains a literal l where l.var() >= num_bool_vars. + */ + void context::reinit_clauses(unsigned num_scopes, unsigned num_bool_vars) { + TRACE("reinit_clauses_bug", display_watch_lists(tout);); + if (m_clauses_to_reinit.empty()) + return; + unsigned lim = m_scope_lvl + num_scopes; + if (m_clauses_to_reinit.size() <= lim) { + SASSERT(!m_clauses_to_reinit.empty()); + lim = m_clauses_to_reinit.size() - 1; + } + for (unsigned i = m_scope_lvl+1; i <= lim; i++) { + clause_vector & v = m_clauses_to_reinit[i]; + clause_vector::iterator it = v.begin(); + clause_vector::iterator end = v.end(); + for (; it != end; ++it) { + clause * cls = *it; + if (cls->deleted()) { + cls->release_atoms(m_manager); + cls->m_reinit = false; + cls->m_reinternalize_atoms = false; + continue; + } + + SASSERT(cls->in_reinit_stack()); + bool keep = false; + if (cls->reinternalize_atoms()) { + SASSERT(cls->get_num_atoms() == cls->get_num_literals()); + for (unsigned j = 0; j < 2; j++) { + literal l = cls->get_literal(j); + if (l.var() < static_cast(num_bool_vars)) { + // This boolean variable was not deleted during backtracking + // + // So, it is still a watch literal. I remove the watch, since + // the clause may have new watch-literals after reinitialization. + remove_watch_literal(cls, j); + } + } + + unsigned num = cls->get_num_literals(); + + if (lit_occs_enabled()) { + for (unsigned j = 0; j < num; j++) { + literal l = cls->get_literal(j); + if (l.var() < static_cast(num_bool_vars)) { + // This boolean variable was not deleted during backtracking + // + // So, remove it from lit_occs. + m_lit_occs[l.index()].erase(cls); + } + } + } + + unsigned ilvl = 0; + for (unsigned j = 0; j < num; j++) { + expr * atom = cls->get_atom(j); + bool sign = cls->get_atom_sign(j); + // Atom can be (NOT foo). This can happen, for example, when + // the NOT-application is a child of an uninterpreted function symbol. + // So, when reinternalizing the NOT-atom I should set the gate_ctx to false, + // and force expression to be reinternalized. + // Otherwise I set gate_ctx to true + bool gate_ctx = !m_manager.is_not(atom); + internalize(atom, gate_ctx); + SASSERT(b_internalized(atom)); + bool_var v = get_bool_var(atom); + DEBUG_CODE({ + if (get_intern_level(v) > ilvl) + ilvl = get_intern_level(v); + }); + literal l(v, sign); + cls->set_literal(j, l); + } + SASSERT(ilvl <= m_scope_lvl); + int w1_idx = select_watch_lit(cls, 0); + cls->swap_lits(0, w1_idx); + int w2_idx = select_watch_lit(cls, 1); + cls->swap_lits(1, w2_idx); + add_watch_literal(cls, 0); + add_watch_literal(cls, 1); + + if (lit_occs_enabled()) + add_lit_occs(cls); + + literal l1 = cls->get_literal(0); + literal l2 = cls->get_literal(1); + + if (get_assignment(l1) == l_false) + set_conflict(b_justification(cls)); + else if (get_assignment(l2) == l_false) + assign(l1, b_justification(cls)); + + TRACE("reinit_clauses", tout << "reinit clause:\n"; display_clause_detail(tout, cls); tout << "\n"; + tout << "activity: " << cls->get_activity() << ", num_bool_vars: " << num_bool_vars << ", scope_lvl: " + << m_scope_lvl << "\n";); + keep = true; + } + else { + SASSERT(!cls->reinternalize_atoms()); + literal l1 = cls->get_literal(0); + literal l2 = cls->get_literal(1); + if (get_assignment(l1) == l_false && is_empty_clause(cls)) { + set_conflict(b_justification(cls)); + keep = true; + } + else if (get_assignment(l2) == l_false && get_assignment(l1) == l_undef && is_unit_clause(cls)) { + assign(l1, b_justification(cls)); + keep = true; + } + } + + // SASSERT(!(cls->get_num_literals() == 3 && + // (cls->get_literal(0).index() == 624 || cls->get_literal(0).index() == 103 || cls->get_literal(0).index() == 629) && + // (cls->get_literal(1).index() == 624 || cls->get_literal(1).index() == 103 || cls->get_literal(1).index() == 629) && + // (cls->get_literal(2).index() == 624 || cls->get_literal(2).index() == 103 || cls->get_literal(2).index() == 629))); + + if (keep && m_scope_lvl > m_base_lvl) { + m_clauses_to_reinit[m_scope_lvl].push_back(cls); + } + else { + // clause do not need to be in the reinit stack anymore, + // because it will be deleted when the base level is + // backtracked. + cls->release_atoms(m_manager); + cls->m_reinit = false; + cls->m_reinternalize_atoms = false; + } + } + v.reset(); + } + CASSERT("reinit_clauses", check_clauses(m_lemmas)); + CASSERT("reinit_clauses", check_lit_occs()); + TRACE("reinit_clauses_bug", display_watch_lists(tout);); + } + + void context::reassert_units(unsigned units_to_reassert_lim) { + unsigned i = units_to_reassert_lim; + unsigned sz = m_units_to_reassert.size(); + for (; i < sz; i++) { + expr * unit = m_units_to_reassert.get(i); + bool gate_ctx = true; + internalize(unit, gate_ctx); + bool_var v = get_bool_var(unit); + bool sign = m_units_to_reassert_sign[i] != 0; + literal l(v, sign); + assign(l, b_justification::mk_axiom()); + TRACE("reassert_units", tout << "reasserting #" << unit->get_id() << " " << sign << " @ " << m_scope_lvl << "\n";); + } + if (at_base_level()) { + m_units_to_reassert.reset(); + m_units_to_reassert_sign.reset(); + } + } + + /** + \brief Backtrack 'num_scopes' scope levels. Return the number + of boolean variables before reinitializing clauses. This value + is useful because it can be used to detect which boolean variables + were deleted. + + \warning This method will not invoke reset_cache_generation. + */ + unsigned context::pop_scope_core(unsigned num_scopes) { +#ifndef SMTCOMP + if (m_fparams.m_trace_stream != NULL) + *m_fparams.m_trace_stream << "[pop] " << num_scopes << " " << m_scope_lvl << "\n"; +#endif + TRACE("context", tout << "backtracking: " << num_scopes << "\n";); + TRACE("pop_scope_detail", display(tout);); + SASSERT(num_scopes > 0); + SASSERT(num_scopes <= m_scope_lvl); + SASSERT(m_scopes.size() == m_scope_lvl); + + unsigned new_lvl = m_scope_lvl - num_scopes; + + cache_generation(new_lvl); + m_qmanager->pop(num_scopes); + m_case_split_queue->pop_scope(num_scopes); + + TRACE("pop_scope", tout << "backtracking: " << num_scopes << ", new_lvl: " << new_lvl << "\n";); + scope & s = m_scopes[new_lvl]; + TRACE("context", tout << "backtracking new_lvl: " << new_lvl << "\n";); + + unsigned units_to_reassert_lim = s.m_units_to_reassert_lim; + + if (new_lvl < m_base_lvl) { + base_scope & bs = m_base_scopes[new_lvl]; + del_clauses(m_lemmas, bs.m_lemmas_lim); + m_simp_qhead = bs.m_simp_qhead_lim; + if (!bs.m_inconsistent) { + m_conflict = null_b_justification; + m_not_l = null_literal; + m_unsat_proof = 0; + } + m_base_scopes.shrink(new_lvl); + } + else { + m_conflict = null_b_justification; + m_not_l = null_literal; + } + del_clauses(m_aux_clauses, s.m_aux_clauses_lim); + + m_relevancy_propagator->pop(num_scopes); + + m_fingerprints.pop_scope(num_scopes); + unassign_vars(s.m_assigned_literals_lim); + undo_trail_stack(s.m_trail_stack_lim); + + ptr_vector::iterator it = m_theory_set.begin(); + ptr_vector::iterator end = m_theory_set.end(); + for (; it != end; ++it) + (*it)->pop_scope_eh(num_scopes); + + del_justifications(m_justifications, s.m_justifications_lim); + + m_asserted_formulas.pop_scope(num_scopes); + + m_eq_propagation_queue.reset(); + m_th_eq_propagation_queue.reset(); + m_th_diseq_propagation_queue.reset(); + m_atom_propagation_queue.reset(); + + m_region.pop_scope(num_scopes); + m_scopes.shrink(new_lvl); + + m_scope_lvl = new_lvl; + if (new_lvl < m_base_lvl) { + m_base_lvl = new_lvl; + m_search_lvl = new_lvl; // Remark: not really necessary + } + + unsigned num_bool_vars = get_num_bool_vars(); + // any variable >= num_bool_vars was deleted during backtracking. + reinit_clauses(num_scopes, num_bool_vars); + reassert_units(units_to_reassert_lim); + TRACE("pop_scope_detail", tout << "end of pop_scope: \n"; display(tout);); + CASSERT("context", check_invariant()); + return num_bool_vars; + } + + void context::pop_scope(unsigned num_scopes) { + pop_scope_core(num_scopes); + reset_cache_generation(); + } + + void context::pop_to_base_lvl() { + SASSERT(m_scope_lvl >= m_base_lvl); + if (!at_base_level()) { + unsigned num_lvls = m_scope_lvl - m_base_lvl; + pop_scope(num_lvls); + } + SASSERT(m_scope_lvl == m_base_lvl); + } + + /** + \brief Simplify the given clause using the assignment. Return + true if the clause was already satisfied, and false otherwise. + + \remark This method should only be invoked if we are at the + base level. + */ + bool context::simplify_clause(clause * cls) { + SASSERT(m_scope_lvl == m_base_lvl); + unsigned s = cls->get_num_literals(); + if (get_assignment(cls->get_literal(0)) == l_true || + get_assignment(cls->get_literal(1)) == l_true) { + // clause is already satisfied. + return true; + } + + literal_buffer simp_lits; + + unsigned i = 2; + unsigned j = i; + for(; i < s; i++) { + literal l = cls->get_literal(i); + switch(get_assignment(l)) { + case l_false: + if (m_manager.proofs_enabled()) + simp_lits.push_back(~l); + if (lit_occs_enabled()) + m_lit_occs[l.index()].erase(cls); + break; + case l_undef: + cls->set_literal(j, l); + j++; + break; + case l_true: + return true; + } + } + + if (j < s) { + cls->set_num_literals(j); + SASSERT(j >= 2); + } + + if (m_manager.proofs_enabled() && !simp_lits.empty()) { + SASSERT(m_scope_lvl == m_base_lvl); + justification * js = cls->get_justification(); + justification * new_js = 0; + if (js->in_region()) + new_js = mk_justification(unit_resolution_justification(m_region, + js, + simp_lits.size(), + simp_lits.c_ptr())); + else + new_js = alloc(unit_resolution_justification, js, simp_lits.size(), simp_lits.c_ptr()); + cls->set_justification(new_js); + } + return false; + } + + /** + \brief Simplify the given vector of clauses starting at the given position. + Return the number of deleted (already satisfied) clauses. + */ + unsigned context::simplify_clauses(clause_vector & clauses, unsigned starting_at) { + unsigned num_del_clauses = 0; + clause_vector::iterator it = clauses.begin(); + clause_vector::iterator end = clauses.end(); + it += starting_at; + clause_vector::iterator it2 = it; + for(; it != end; ++it) { + clause * cls = *it; + SASSERT(!cls->in_reinit_stack()); + TRACE("simplify_clauses_bug", display_clause(tout, cls); tout << "\n";); + if (cls->deleted()) { + del_clause(cls); + num_del_clauses++; + } + else if (simplify_clause(cls)) { + for (unsigned idx = 0; idx < 2; idx++) { + literal l0 = cls->get_literal(idx); + b_justification l0_js = get_justification(l0.var()); + if (l0_js != null_b_justification && + l0_js.get_kind() == b_justification::CLAUSE && + l0_js.get_clause() == cls) { + // cls is the explanation of l0 + // it is safe to replace with axiom, we are at the base level. + SASSERT(m_scope_lvl == m_base_lvl); + bool_var v0 = l0.var(); + if (m_manager.proofs_enabled()) { + SASSERT(m_search_lvl == m_base_lvl); + literal_buffer simp_lits; + unsigned num_lits = cls->get_num_literals(); + for(unsigned i = 0; i < num_lits; i++) { + if (i != idx) { + literal l = cls->get_literal(i); + SASSERT(l != l0); + simp_lits.push_back(~l); + } + } + justification * cls_js = cls->get_justification(); + justification * js = 0; + 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, + cls_js, + simp_lits.size(), + simp_lits.c_ptr())); + } + else { + js = alloc(unit_resolution_justification, cls_js, simp_lits.size(), simp_lits.c_ptr()); + // js took ownership of the justification object. + cls->set_justification(0); + m_justifications.push_back(js); + } + m_bdata[v0].m_justification = b_justification(js); + } + else + m_bdata[v0].m_justification = b_justification::mk_axiom(); + } + } + del_clause(cls); + num_del_clauses++; + } + else { + *it2 = *it; + ++it2; + m_simp_counter += cls->get_num_literals(); + } + } + clauses.set_end(it2); + CASSERT("simplify_clauses", check_invariant()); + return num_del_clauses; + } + + /** + \brief Simplify the set of clauses if possible (solver is at base level). + */ + void context::simplify_clauses() { + // Remark: when assumptions are used m_scope_lvl >= m_search_lvl > m_base_lvl. Therefore, no simplification is performed. + if (m_scope_lvl > m_base_lvl) + return; + + unsigned sz = m_assigned_literals.size(); + SASSERT(m_simp_qhead <= sz); + + if (m_simp_qhead == sz || m_simp_counter > 0) { + TRACE("simplify_clauses", tout << "m_simp_qhead: " << m_simp_qhead << " m_simp_counter: " << m_simp_counter << "\n";); + return; + } + + if (m_aux_clauses.empty() && m_lemmas.empty()) { + TRACE("simplify_clauses", tout << "no clauses to simplify\n";); + return; + } + + TRACE("simplify_clauses_detail", tout << "before:\n"; display_clauses(tout, m_lemmas);); + SASSERT(check_clauses(m_lemmas)); + SASSERT(check_clauses(m_aux_clauses)); + + IF_VERBOSE(2, verbose_stream() << "simplifying clause set... "; verbose_stream().flush();); + + // m_simp_counter is used to balance the cost of simplify_clause. + // + // After executing simplify_clauses, the counter will contain + // an approximation of the cost of executing simplify_clauses again. + // That is, the number of literals that will need to be visited. + // + // The value of the counter is decremented each time we visit + // a variable during propagation. + // + m_simp_counter = 0; + // the field m_simp_qhead is used to check whether there are + // new assigned literals at the base level. + m_simp_qhead = m_assigned_literals.size(); + + unsigned num_clauses = m_aux_clauses.size() + m_lemmas.size(); + unsigned num_del_clauses = 0; + + SASSERT(m_scope_lvl == m_base_lvl); + if (m_base_lvl == 0) { + num_del_clauses += simplify_clauses(m_aux_clauses, 0); + num_del_clauses += simplify_clauses(m_lemmas, 0); + } + else { + scope & s = m_scopes[m_base_lvl - 1]; + base_scope & bs = m_base_scopes[m_base_lvl - 1]; + num_del_clauses += simplify_clauses(m_aux_clauses, s.m_aux_clauses_lim); + num_del_clauses += simplify_clauses(m_lemmas, bs.m_lemmas_lim); + } + TRACE("simp_counter", tout << "simp_counter: " << m_simp_counter << " scope_lvl: " << m_scope_lvl << "\n";); + IF_VERBOSE(2, verbose_stream() << "num. deleted clauses: " << num_del_clauses << " (out of " << num_clauses << ")" << std::endl;); + TRACE("simplify_clauses_detail", tout << "after:\n"; display_clauses(tout, m_lemmas);); + SASSERT(check_clauses(m_lemmas) && check_clauses(m_aux_clauses)); + } + + struct clause_lt { + bool operator()(clause * cls1, clause * cls2) const { return cls1->get_activity() > cls2->get_activity(); } + }; + + /** + \brief Delete low activity lemmas + */ + inline void context::del_inactive_lemmas() { + if (m_fparams.m_lemma_gc_half) + del_inactive_lemmas1(); + else + del_inactive_lemmas2(); + + m_num_conflicts_since_lemma_gc = 0; + if (m_fparams.m_lemma_gc_strategy == LGC_GEOMETRIC) + m_lemma_gc_threshold = static_cast(m_lemma_gc_threshold * m_fparams.m_lemma_gc_factor); + } + + /** + \brief Delete (approx.) half of low activity lemmas + */ + void context::del_inactive_lemmas1() { + unsigned sz = m_lemmas.size(); + unsigned start_at = m_base_lvl == 0 ? 0 : m_base_scopes[m_base_lvl - 1].m_lemmas_lim; + SASSERT(start_at <= sz); + if (start_at + m_fparams.m_recent_lemmas_size >= sz) + return; + IF_VERBOSE(2, verbose_stream() << "deleting inactive lemmas... "; verbose_stream().flush();); + SASSERT (m_fparams.m_recent_lemmas_size < sz); + unsigned end_at = sz - m_fparams.m_recent_lemmas_size; + SASSERT(start_at < end_at); + std::stable_sort(m_lemmas.begin() + start_at, m_lemmas.begin() + end_at, clause_lt()); + unsigned start_del_at = (start_at + end_at) / 2; + unsigned i = start_del_at; + unsigned j = i; + unsigned num_del_cls = 0; + TRACE("del_inactive_lemmas", tout << "sz: " << sz << ", start_at: " << start_at << ", end_at: " << end_at + << ", start_del_at: " << start_del_at << "\n";); + for (; i < end_at; i++) { + clause * cls = m_lemmas[i]; + if (can_delete(cls)) { + TRACE("del_inactive_lemmas", tout << "deleting: "; display_clause(tout, cls); tout << ", activity: " << + cls->get_activity() << "\n";); + del_clause(cls); + num_del_cls++; + } + else { + m_lemmas[j] = cls; + j++; + } + } + // keep recent clauses + for (; i < sz; i++) { + clause * cls = m_lemmas[i]; + if (cls->deleted() && can_delete(cls)) { + del_clause(cls); + num_del_cls++; + } + else { + m_lemmas[j] = cls; + j++; + } + } + m_lemmas.shrink(j); + if (m_fparams.m_clause_decay > 1) { + // rescale activity + for (i = start_at; i < j; i++) { + clause * cls = m_lemmas[i]; + cls->set_activity(cls->get_activity() / m_fparams.m_clause_decay); + } + } + IF_VERBOSE(2, verbose_stream() << "num. deleted clauses: " << num_del_cls << " (out of " << sz << ")" << std::endl;); + } + + /** + \brief More sophisticated version of del_inactive_lemmas. Here the lemmas are divided in two + groups (old and new) based on the value of m_new_old_ratio parameter. + A clause is deleted/retained based on its activity and relevancy. Clauses with several + unassigned literals are considered less relevant. The threshold used for activity and relevancy + depends on which group the clauses is in. + */ + void context::del_inactive_lemmas2() { + IF_VERBOSE(2, verbose_stream() << "deleting inactive clauses... "; verbose_stream().flush();); + unsigned sz = m_lemmas.size(); + unsigned start_at = m_base_lvl == 0 ? 0 : m_base_scopes[m_base_lvl - 1].m_lemmas_lim; + SASSERT(start_at <= sz); + unsigned real_sz = sz - start_at; + // idx of the first learned clause considered "new" + unsigned new_first_idx = start_at + (real_sz / m_fparams.m_new_old_ratio) * (m_fparams.m_new_old_ratio - 1); + SASSERT(new_first_idx <= sz); + unsigned i = start_at; + unsigned j = i; + unsigned num_del_cls = 0; + for (; i < sz; i++) { + clause * cls = m_lemmas[i]; + if (can_delete(cls)) { + if (cls->deleted()) { + // clause is already marked for deletion + del_clause(cls); + num_del_cls++; + continue; + } + // A clause is deleted if it has low activity and the number of unknowns is greater than a threshold. + // The activity threshold depends on how old the clause is. + unsigned act_threshold = m_fparams.m_old_clause_activity - + (m_fparams.m_old_clause_activity - m_fparams.m_new_clause_activity) * ((i - start_at) / real_sz); + if (cls->get_activity() < act_threshold) { + unsigned rel_threshold = (i >= new_first_idx ? m_fparams.m_new_clause_relevancy : m_fparams.m_old_clause_relevancy); + if (more_than_k_unassigned_literals(cls, rel_threshold)) { + del_clause(cls); + num_del_cls++; + continue; + } + } + } + m_lemmas[j] = cls; + j++; + cls->set_activity(static_cast(cls->get_activity() / m_fparams.m_inv_clause_decay)); + } + SASSERT(j <= sz); + m_lemmas.shrink(j); + IF_VERBOSE(2, verbose_stream() << "num. deleted clauses: " << num_del_cls << " (out of " << sz << ")" << std::endl;); + } + + /** + \brief Return true if "cls" has more than (or equal to) k unassigned literals. + */ + bool context::more_than_k_unassigned_literals(clause * cls, unsigned k) { + SASSERT(k > 0); + unsigned num_lits = cls->get_num_literals(); + for(unsigned i = 0; i < num_lits; i++) { + literal l = cls->get_literal(i); + if (get_assignment(l) == l_undef) { + k--; + if (k == 0) { + return true; + } + } + } + return false; + } + + void context::register_plugin(simplifier_plugin * s) { + SASSERT(!already_internalized()); + SASSERT(m_scope_lvl == 0); + m_asserted_formulas.register_simplifier_plugin(s); + } + +#ifdef Z3DEBUG + /** + \brief Return true if a symbol of the given theory was already internalized. + */ + bool context::already_internalized_theory(theory * th) const { + return already_internalized_theory_core(th, m_b_internalized_stack) || already_internalized_theory_core(th, m_e_internalized_stack); + } + + /** + \brief Auxiliary method for #already_internalized_theory. + */ + bool context::already_internalized_theory_core(theory * th, expr_ref_vector const & s) const { + expr_mark visited; + family_id fid = th->get_id(); + unsigned sz = s.size(); + for (unsigned i = 0; i < sz; i++) { + expr * n = s.get(i); + if (uses_theory(n, fid, visited)) + return true; + } + return false; + } +#endif + + void context::register_plugin(theory * th) { + if (m_theories.get_plugin(th->get_family_id()) != 0) + return; // context already has a theory for the given family id. + SASSERT(std::find(m_theory_set.begin(), m_theory_set.end(), th) == m_theory_set.end()); + SASSERT(!already_internalized_theory(th)); + th->init(this); + m_theories.register_plugin(th); + m_theory_set.push_back(th); + { +#ifdef Z3DEBUG + // It is unsafe to invoke push_trail from the method push_scope_eh. + flet l(m_trail_enabled, false); +#endif + for (unsigned i = 0; i < m_scope_lvl; ++i) + th->push_scope_eh(); + } + } + + void context::push() { + TRACE("trigger_bug", tout << "context::push()\n";); + pop_to_base_lvl(); + setup_context(false); + bool was_consistent = !inconsistent(); + internalize_assertions(); // internalize assertions before invoking m_asserted_formulas.push_scope + propagate(); + if (was_consistent && inconsistent()) { + // logical context became inconsistent during user PUSH + bool res = resolve_conflict(); // build the proof + SASSERT(!res); + } + push_scope(); + m_base_scopes.push_back(base_scope()); + base_scope & bs = m_base_scopes.back(); + bs.m_lemmas_lim = m_lemmas.size(); + bs.m_inconsistent = inconsistent(); + bs.m_simp_qhead_lim = m_simp_qhead; + m_base_lvl++; + m_search_lvl++; // Not really necessary. But, it is useful to enforce the invariant m_search_lvl >= m_base_lvl + SASSERT(m_base_lvl <= m_scope_lvl); + } + + void context::pop(unsigned num_scopes) { + SASSERT (num_scopes > 0); + pop_to_base_lvl(); + pop_scope(num_scopes); + } + + /** + \brief Free memory allocated by logical context. + */ + void context::flush() { + flet l1(m_flushing, true); + TRACE("flush", tout << "m_scope_lvl: " << m_scope_lvl << "\n";); + m_relevancy_propagator = 0; + m_model_generator->reset(); + ptr_vector::iterator it = m_theory_set.begin(); + ptr_vector::iterator end = m_theory_set.end(); + for (; it != end; ++it) + (*it)->flush_eh(); + undo_trail_stack(0); + m_qmanager = 0; + del_clauses(m_aux_clauses, 0); + del_clauses(m_lemmas, 0); + del_justifications(m_justifications, 0); + if (m_is_diseq_tmp) { + m_is_diseq_tmp->del_eh(m_manager, false); + m_manager.dec_ref(m_is_diseq_tmp->get_owner()); + enode::del_dummy(m_is_diseq_tmp); + m_is_diseq_tmp = 0; + } + std::for_each(m_almost_cg_tables.begin(), m_almost_cg_tables.end(), delete_proc()); + } + + void context::assert_expr_core(expr * e, proof * pr) { + SASSERT(is_well_sorted(m_manager, e)); + TRACE("begin_assert_expr", tout << mk_pp(e, m_manager) << "\n";); + TRACE("begin_assert_expr_ll", tout << mk_ll_pp(e, m_manager) << "\n";); + pop_to_base_lvl(); + if (pr == 0) + m_asserted_formulas.assert_expr(e); + else + m_asserted_formulas.assert_expr(e, pr); + TRACE("end_assert_expr_ll", ast_mark m; m_asserted_formulas.display_ll(tout, m);); + } + + void context::assert_expr(expr * e) { + assert_expr(e, 0); + } + + void context::assert_expr(expr * e, proof * pr) { + timeit tt(get_verbosity_level() >= 100, "simplifying"); + assert_expr_core(e, pr); + } + + bool context::reduce_assertions() { + if (!m_asserted_formulas.inconsistent()) { + SASSERT(at_base_level()); + m_asserted_formulas.reduce(); + } + return m_asserted_formulas.inconsistent(); + } + + void context::internalize_assertions() { + TRACE("internalize_assertions", tout << "internalize_assertions()...\n";); + timeit tt(get_verbosity_level() >= 100, "preprocessing"); + reduce_assertions(); + if (!m_asserted_formulas.inconsistent()) { + unsigned sz = m_asserted_formulas.get_num_formulas(); + unsigned qhead = m_asserted_formulas.get_qhead(); + while (qhead < sz) { + expr * f = m_asserted_formulas.get_formula(qhead); + proof * pr = m_asserted_formulas.get_formula_proof(qhead); + internalize_assertion(f, pr, 0); + qhead++; + } + m_asserted_formulas.commit(); + } + if (m_asserted_formulas.inconsistent() && !inconsistent()) { + proof * pr = m_asserted_formulas.get_inconsistency_proof(); + if (pr == 0) { + set_conflict(b_justification::mk_axiom()); + } + else { + set_conflict(mk_justification(justification_proof_wrapper(*this, pr))); + m_unsat_proof = pr; + } + } + TRACE("internalize_assertions", tout << "after internalize_assertions()...\n"; + tout << "inconsistent: " << inconsistent() << "\n";); + } + + bool is_valid_assumption(ast_manager & m, expr * assumption) { + if (!m.is_bool(assumption)) + return false; + if (is_uninterp_const(assumption)) + return true; + if (m.is_not(assumption) && is_uninterp_const(to_app(assumption)->get_arg(0))) + return true; + return false; + } + + /** + \brief Assumptions must be uninterpreted boolean constants (aka propositional variables). + */ + bool context::validate_assumptions(unsigned num_assumptions, expr * const * assumptions) { + for (unsigned i = 0; i < num_assumptions; i++) { + SASSERT(assumptions[i]); + if (!is_valid_assumption(m_manager, assumptions[i])) { + warning_msg("an assumption must be a propositional variable or the negation of one"); + return false; + } + } + return true; + } + + void context::init_assumptions(unsigned num_assumptions, expr * const * assumptions) { + reset_assumptions(); + m_bool_var2assumption.reset(); + m_unsat_core.reset(); + if (num_assumptions > 0) { + propagate(); // we must give a chance to the theories to propagate before we create a new scope... + push_scope(); + for (unsigned i = 0; i < num_assumptions; i++) { + expr * curr_assumption = assumptions[i]; + SASSERT(is_valid_assumption(m_manager, curr_assumption)); + proof * pr = m_manager.mk_asserted(curr_assumption); + internalize_assertion(curr_assumption, pr, 0); + literal l = get_literal(curr_assumption); + m_bool_var2assumption.insert(l.var(), curr_assumption); + // mark_as_relevant(l); <<< not needed + // internalize_assertion marked l as relevant. + SASSERT(is_relevant(l)); + TRACE("assumptions", tout << mk_pp(curr_assumption, m_manager) << "\n";); + if (m_manager.proofs_enabled()) + assign(l, mk_justification(justification_proof_wrapper(*this, pr))); + else + assign(l, b_justification::mk_axiom()); + m_assumptions.push_back(l.var()); + get_bdata(l.var()).m_assumption = true; + } + } + m_search_lvl = m_scope_lvl; + SASSERT(!(num_assumptions > 0) || m_search_lvl > m_base_lvl); + SASSERT(!(num_assumptions == 0) || m_search_lvl == m_base_lvl); + } + + void context::reset_assumptions() { + bool_var_vector::iterator it = m_assumptions.begin(); + bool_var_vector::iterator end = m_assumptions.end(); + for (; it != end; ++it) + get_bdata(*it).m_assumption = false; + m_assumptions.reset(); + } + + void context::mk_unsat_core() { + SASSERT(inconsistent()); + if (!tracking_assumptions()) { + SASSERT(m_assumptions.empty()); + return; + } + obj_hashtable already_found_assumptions; + literal_vector::const_iterator it = m_conflict_resolution->begin_unsat_core(); + literal_vector::const_iterator end = m_conflict_resolution->end_unsat_core(); + for (; it != end; ++it) { + literal l = *it; + TRACE("unsat_core_bug", tout << "answer literal: " << l << "\n";); + SASSERT(get_bdata(l.var()).m_assumption); + SASSERT(m_bool_var2assumption.contains(l.var())); + expr * a = 0; + m_bool_var2assumption.find(l.var(), a); + SASSERT(a); + if (!already_found_assumptions.contains(a)) { + already_found_assumptions.insert(a); + m_unsat_core.push_back(a); + } + } + reset_assumptions(); + pop_to_base_lvl(); // undo the push_scope() performed by init_assumptions + m_search_lvl = m_base_lvl; + std::sort(m_unsat_core.c_ptr(), m_unsat_core.c_ptr() + m_unsat_core.size(), ast_lt_proc()); + TRACE("unsat_core_bug", tout << "unsat core:\n"; + unsigned sz = m_unsat_core.size(); + for (unsigned i = 0; i < sz; i++) { + tout << mk_pp(m_unsat_core.get(i), m_manager) << "\n"; + }); + } + + lbool context::get_implied_equalities( + unsigned num_terms, + expr* const* terms, + unsigned* class_ids, + unsigned num_assumptions, + expr * const * assumptions ) { + + if (!validate_assumptions(num_assumptions, assumptions)) + return l_undef; + + simplifier& simp = get_simplifier(); + expr_ref_vector sterms(m_manager), vals(m_manager), eqs(m_manager); + expr_ref vl(m_manager), eq(m_manager); + obj_map vals_map; + obj_map visited_roots; + flet f2(m_fparams.m_model, true); + flet f3(m_fparams.m_array_canonize_simplify, true); + model_ref m; + proof_ref pr(m_manager); + union_find_default_ctx df; + union_find uf(df); + lbool r; + m_last_search_failure = OK; + + push(); + for (unsigned i = 0; i < num_terms; ++i) { + expr_ref t(m_manager); + SASSERT(terms[i]->get_ref_count() > 0); + simp(terms[i], t, pr); + sterms.push_back(t); + if (!e_internalized(t)) { + internalize(t, false); + } + uf.mk_var(); + } + lbool result = check(num_assumptions, assumptions); + + // + // extract model in current satisfying state. + // The model contains all implied equalities, and + // possibly more depending on decisions. + // + + if (result == l_false) { + pop(1); + m_last_search_failure = OK; + return result; + } + failure fail = m_last_search_failure; + + get_model(m); + for (unsigned i = 0; i < num_terms; ++i) { + enode* e = get_enode(sterms[i].get()); + expr* owner_root = e->get_root()->get_owner(); + m->eval(owner_root, vl); + vals.push_back(vl); + } + + // + // pop to base level to undo decision literals. + // The congruence closure contains only implied + // equalities, but possibly not them all. + // Force propagation by using push. + // + + pop_to_base_lvl(); + SASSERT(at_base_level()); + push(); + SASSERT(!inconsistent()); + TRACE("get_implied_equalities", display(tout);); + + // + // if different enodes evaluate to the same value + // in current model, + // check if them being disequal is consistent. + // otherwise, add equality. + // + for (unsigned i = 0; i < num_terms; ++i) { + SASSERT(at_base_level()); + enode* e = get_enode(sterms[i].get()); + expr* owner_root = e->get_root()->get_owner(); + unsigned idx_j; + vl = vals[i].get(); + TRACE("get_implied_equalities", + tout << mk_ll_pp(sterms[i].get(), m_manager) << " |-> "; + tout << mk_ll_pp(owner_root, m_manager) << "|-> "; + tout << mk_ll_pp(vl, m_manager) << "\n"; + ); + if (visited_roots.find(owner_root, idx_j)) { + uf.merge(i, idx_j); + continue; + } + visited_roots.insert(owner_root, i); + unsigned_vector same_values; + if (!vals_map.find(vl, same_values)) { + same_values.push_back(i); + vals_map.insert(vl, same_values); + continue; + } + bool found_eq = false; + for (unsigned j = 0; !found_eq && j < same_values.size(); ++j) { + idx_j = same_values[j]; + enode* other_node = get_enode(sterms[idx_j].get()); + expr* other_root = other_node->get_root()->get_owner(); + expr_ref neq(m_manager.mk_not(mk_eq_atom(owner_root, other_root)), m_manager); + + push(); + assert_expr(neq); + r = check(num_assumptions, assumptions); + pop(1); + + TRACE("get_implied_equalities", tout << mk_pp(neq, m_manager) << " |-> " << r << "\n";); + + if (r == l_false) { + uf.merge(i, idx_j); + found_eq = true; + } + SASSERT(!inconsistent()); + } + if (!found_eq) { + same_values.push_back(i); + vals_map.insert(vl, same_values); + } + } + SASSERT(!inconsistent()); + for (unsigned i = 0; i < num_terms; ++i) { + unsigned j = uf.find(i); + enode* e = get_enode(sterms[j].get()); + class_ids[i] = e->get_root()->get_owner_id(); + } + TRACE("get_implied_equalities", + for (unsigned i = 0; i < num_terms; ++i) { + tout << mk_pp(sterms[i].get(), m_manager) << " |-> #" << class_ids[i] << "\n"; + } + display(tout);); + pop(2); + TRACE("get_implied_equalities", display(tout); ); + + /* + DEBUG_CODE( + for (unsigned i = 0; result != l_undef && i < num_terms; ++i) { + for (unsigned j = i+1; j < num_terms; ++j) { + if (class_ids[i] != class_ids[j]) { + expr* e1 = sterms[i].get(); + expr* e2 = sterms[j].get(); + if (m_manager.get_sort(e1) != m_manager.get_sort(e2)) { + continue; + } + SASSERT(!inconsistent()); + expr_ref neq(m_manager.mk_not(mk_eq_atom(e1,e2)), m_manager); + + push(); + assert_expr(neq); + r = check(num_assumptions, assumptions); + pop(1); + + if (r == l_false) { + enable_trace("array"); + enable_trace("arith"); + enable_trace("context"); + enable_trace("unsat_core_bug"); + enable_trace("after_search"); + enable_trace("display_unsat_core"); + SASSERT(!inconsistent()); + + { + flet f3(m_fparams.m_smtlib_dump_assertions, true); + unsigned sz = m_asserted_formulas.get_num_formulas(); + for (unsigned l = 0; l < sz; ++l) { + expr * f = m_asserted_formulas.get_formula(l); + save_assertion_for_pp(f); + } + save_assertion_for_pp(neq); + display_assertions_as_smt_problem(); + } + + push(); + assert_expr(neq); + r = check(num_assumptions, assumptions); + pop(1); + pop_to_base_lvl(); + + push(); + IF_VERBOSE(0, + display(verbose_stream()); + for (unsigned k = 0; k < num_terms; ++k) { + verbose_stream() << "id: " << class_ids[k] << "\n" + << "term: " << mk_pp(sterms[k].get(), m_manager) << "\n" + << "val: " << mk_pp(vals[k].get(), m_manager) << "\n"; + } + verbose_stream() << "\n"; + verbose_stream() << "term1: " << mk_pp(e1, m_manager) << "\n"; + verbose_stream() << "term2: " << mk_pp(e2, m_manager) << "\n"; + verbose_stream() << "val1: " << mk_pp(vals[i].get(), m_manager) << "\n"; + verbose_stream() << "val2: " << mk_pp(vals[j].get(), m_manager) << "\n";); + SASSERT(r != l_false); + exit(0); + } + SASSERT(r != l_false); + } + } + } + ); + */ + m_last_search_failure = fail; + return result; + } + + /** + \brief Make some checks before starting the search. + Return true if succeeded. + */ + bool context::check_preamble(bool reset_cancel) { +#ifndef SMTCOMP + if (m_fparams.m_trace_stream != NULL) + *m_fparams.m_trace_stream << "[begin-check] " << m_scope_lvl << "\n"; +#endif + + if (reset_cancel) { + m_cancel_flag = false; + m_asserted_formulas.set_cancel_flag(false); + } + + if (memory::above_high_watermark()) { + m_last_search_failure = MEMOUT; + return false; + } + return true; + } + + /** + \brief Execute some finalization code after performing the search. + */ + void context::check_finalize(lbool r) { + TRACE("after_search", display(tout);); + display_profile(verbose_stream()); + } + + /** + \brief Setup the logical context based on the current set of + asserted formulas and execute the check command. + + \remark A logical context can only be configured at scope level 0, + and before internalizing any formulas. + */ + lbool context::setup_and_check(bool reset_cancel) { + if (!check_preamble(reset_cancel)) + return l_undef; + SASSERT(m_scope_lvl == 0); + SASSERT(!m_setup.already_configured()); + setup_context(m_fparams.m_auto_config); + internalize_assertions(); + lbool r = l_undef; + if (m_asserted_formulas.inconsistent()) { + r = l_false; + } + else { + TRACE("after_internalization", display(tout);); + if (inconsistent()) { + bool res = resolve_conflict(); // build the proof + SASSERT(!res); + r = l_false; + } + else { + r = search(); + } + } + check_finalize(r); + return r; + } + + config_mode context::get_config_mode(bool use_static_features) const { + if (!m_fparams.m_auto_config) + return CFG_BASIC; + if (use_static_features) + return CFG_AUTO; + return CFG_LOGIC; + } + + void context::setup_context(bool use_static_features) { + if (m_setup.already_configured()) + return; + m_setup(get_config_mode(use_static_features)); + setup_components(); +#ifndef _EXTERNAL_RELEASE + if (m_fparams.m_display_installed_theories) { + std::cout << "(theories"; + ptr_vector::iterator it = m_theory_set.begin(); + ptr_vector::iterator end = m_theory_set.end(); + for (; it != end; ++it) { + std::cout << " " << (*it)->get_name(); + } + std::cout << ")" << std::endl; + } +#endif + } + + void context::setup_components() { + m_asserted_formulas.setup(); + m_random.set_seed(m_fparams.m_random_seed); + m_dyn_ack_manager.setup(); + m_conflict_resolution->setup(); + + if (!relevancy()) + m_fparams.m_relevancy_lemma = false; + + // setup all the theories + ptr_vector::iterator it = m_theory_set.begin(); + ptr_vector::iterator end = m_theory_set.end(); + for (; it != end; ++it) + (*it)->setup(); + } + + lbool context::check(unsigned num_assumptions, expr * const * assumptions, bool reset_cancel) { + m_stats.m_num_checks++; + TRACE("check_bug", tout << "STARTING check(num_assumptions, assumptions)\n"; + tout << "inconsistent: " << inconsistent() << ", m_unsat_core.empty(): " << m_unsat_core.empty() << "\n"; + m_asserted_formulas.display(tout); + tout << "-----------------------\n"; + display(tout);); + if (!m_unsat_core.empty()) + m_unsat_core.reset(); + if (!check_preamble(reset_cancel)) + return l_undef; + if (!validate_assumptions(num_assumptions, assumptions)) + return l_undef; + TRACE("check_bug", tout << "inconsistent: " << inconsistent() << ", m_unsat_core.empty(): " << m_unsat_core.empty() << "\n";); + TRACE("unsat_core_bug", for (unsigned i = 0; i < num_assumptions; i++) { tout << mk_pp(assumptions[i], m_manager) << "\n";}); + pop_to_base_lvl(); + TRACE("before_search", display(tout);); + SASSERT(at_base_level()); + lbool r = l_undef; + if (inconsistent()) { + r = l_false; + } + else { + setup_context(false); + internalize_assertions(); + TRACE("after_internalize_assertions", display(tout);); + if (m_asserted_formulas.inconsistent()) { + r = l_false; + } + else { + init_assumptions(num_assumptions, assumptions); + TRACE("after_internalization", display(tout);); + if (inconsistent()) { + bool res = resolve_conflict(); // build the proof + SASSERT(!res); + mk_unsat_core(); + r = l_false; + } + else { + r = search(); + if (r == l_false) + mk_unsat_core(); + } + } + } + check_finalize(r); + return r; + } + + void context::init_search() { + ptr_vector::iterator it = m_theory_set.begin(); + ptr_vector::iterator end = m_theory_set.end(); + for (; it != end; ++it) + (*it)->init_search_eh(); + m_qmanager->init_search_eh(); + m_assumption_core.reset(); + m_incomplete_theories.reset(); + m_num_conflicts = 0; + m_num_conflicts_since_restart = 0; + m_num_conflicts_since_lemma_gc = 0; + m_restart_threshold = m_fparams.m_restart_initial; + m_restart_outer_threshold = m_fparams.m_restart_initial; + m_agility = 0.0; + m_luby_idx = 1; + m_lemma_gc_threshold = m_fparams.m_lemma_gc_initial; + m_last_search_failure = OK; + m_unsat_proof = 0; + m_unsat_core .reset(); + m_dyn_ack_manager .init_search_eh(); + m_final_check_idx = 0; + m_phase_default = false; + m_case_split_queue ->init_search_eh(); + m_next_progress_sample = 0; + TRACE("literal_occ", display_literal_num_occs(tout);); + m_timer.start(); + m_instr.start(); + } + + void context::end_search() { + m_case_split_queue ->end_search_eh(); + } + + void context::inc_limits() { + if (m_num_conflicts_since_restart >= m_restart_threshold) { + switch (m_fparams.m_restart_strategy) { + case RS_GEOMETRIC: + m_restart_threshold = static_cast(m_restart_threshold * m_fparams.m_restart_factor); + break; + case RS_IN_OUT_GEOMETRIC: + m_restart_threshold = static_cast(m_restart_threshold * m_fparams.m_restart_factor); + if (m_restart_threshold > m_restart_outer_threshold) { + m_restart_threshold = m_fparams.m_restart_initial; + m_restart_outer_threshold = static_cast(m_restart_outer_threshold * m_fparams.m_restart_factor); + } + break; + case RS_LUBY: + m_luby_idx ++; + m_restart_threshold = static_cast(get_luby(m_luby_idx) * m_fparams.m_restart_initial); + break; + case RS_FIXED: + break; + case RS_ARITHMETIC: + m_restart_threshold = static_cast(m_restart_threshold + m_fparams.m_restart_factor); + break; + default: + break; + } + } + m_num_conflicts_since_restart = 0; + } + + struct context::scoped_mk_model { + context & m_ctx; + scoped_mk_model(context & ctx):m_ctx(ctx) { + m_ctx.m_proto_model = 0; + m_ctx.m_model = 0; + } + ~scoped_mk_model() { + if (m_ctx.m_proto_model.get() != 0) { + m_ctx.m_model = m_ctx.m_proto_model->mk_model(); + m_ctx.m_proto_model = 0; // proto_model is not needed anymore. + } + } + }; + + lbool context::search() { +#ifndef _EXTERNAL_RELEASE + if (m_fparams.m_abort_after_preproc) { + exit(1); + } +#endif + timeit tt(get_verbosity_level() >= 100, "searching"); + scoped_mk_model smk(*this); + SASSERT(at_search_level()); + TRACE("search", display(tout); display_enodes_lbls(tout);); + TRACE("search_detail", m_asserted_formulas.display(tout);); + init_search(); + flet l(m_searching, true); + TRACE("after_init_search", display(tout);); + IF_VERBOSE(2, verbose_stream() << "searching...\n";); + TRACE("search_lite", tout << "searching...\n";); + lbool status = l_undef; + unsigned curr_lvl = m_scope_lvl; + + while (true) { + SASSERT(!inconsistent()); + + status = bounded_search(); + TRACE("search_bug", tout << "status: " << status << ", inconsistent: " << inconsistent() << "\n";); + TRACE("assigned_literals_per_lvl", display_num_assigned_literals_per_lvl(tout); + tout << ", num_assigned: " << m_assigned_literals.size() << "\n";); + + if (m_last_search_failure != OK) { + if (status != l_false) { + // build candidate model before returning + mk_proto_model(status); + } + break; + } + + bool force_restart = false; + + if (status == l_false) { + break; + } + else if (status == l_true) { + SASSERT(!inconsistent()); + mk_proto_model(l_true); + // possible outcomes DONE l_true, DONE l_undef, CONTINUE + quantifier_manager::check_model_result cmr = m_qmanager->check_model(m_proto_model.get(), m_model_generator->get_root2value()); + if (cmr == quantifier_manager::SAT) { + // done + break; + } + if (cmr == quantifier_manager::UNKNOWN) { + // giving up + m_last_search_failure = QUANTIFIERS; + status = l_undef; + break; + } + status = l_undef; + force_restart = true; + } + + SASSERT(status == l_undef); + inc_limits(); + if (force_restart || !m_fparams.m_restart_adaptive || m_agility < m_fparams.m_restart_agility_threshold) { + SASSERT(!inconsistent()); + IF_VERBOSE(1, verbose_stream() << "restarting... propagations: " << m_stats.m_num_propagations + << ", decisions: " << m_stats.m_num_decisions + << ", conflicts: " << m_stats.m_num_conflicts << ", restart: " << m_restart_threshold; + if (m_fparams.m_restart_strategy == RS_IN_OUT_GEOMETRIC) { + verbose_stream() << ", restart-outer: " << m_restart_outer_threshold; + } + if (m_fparams.m_restart_adaptive) { + verbose_stream() << ", agility: " << m_agility; + } + verbose_stream() << std::endl; verbose_stream().flush();); + // execute the restart + m_stats.m_num_restarts++; + if (m_scope_lvl > curr_lvl) { + pop_scope(m_scope_lvl - curr_lvl); + SASSERT(at_search_level()); + } + ptr_vector::iterator it = m_theory_set.begin(); + ptr_vector::iterator end = m_theory_set.end(); + for (; it != end; ++it) + (*it)->restart_eh(); + TRACE("mbqi_bug_detail", tout << "before instantiating quantifiers...\n";); + m_qmanager->restart_eh(); + } + if (m_fparams.m_simplify_clauses) + simplify_clauses(); + if (m_fparams.m_lemma_gc_strategy == LGC_AT_RESTART) + del_inactive_lemmas(); + } + + TRACE("search_lite", tout << "status: " << status << "\n";); + TRACE("guessed_literals", + expr_ref_vector guessed_lits(m_manager); + get_guessed_literals(guessed_lits); + unsigned sz = guessed_lits.size(); + for (unsigned i = 0; i < sz; i++) { + tout << mk_pp(guessed_lits.get(i), m_manager) << "\n"; + }); + end_search(); + return status; + } + + void context::tick(unsigned & counter) const { + counter++; + if (counter > m_fparams.m_tick) { + IF_VERBOSE(3, verbose_stream() << "working..."; + verbose_stream() << " num. conflicts: " << m_num_conflicts; + // verbose_stream() << " lemma avg. activity: " << get_lemma_avg_activity(); + if (m_fparams.m_restart_adaptive) + verbose_stream() << " agility: " << m_agility; + verbose_stream() << std::endl; verbose_stream().flush();); + TRACE("assigned_literals_per_lvl", display_num_assigned_literals_per_lvl(tout); tout << "\n";); + counter = 0; + } + } + + lbool context::bounded_search() { + SASSERT(!inconsistent()); + unsigned counter = 0; + + TRACE("bounded_search", tout << "starting bounded search...\n";); + + while (true) { + while (!propagate()) { + TRACE_CODE({ + static bool first_propagate = true; + if (first_propagate) { + first_propagate = false; + TRACE("after_first_propagate", display(tout);); + } + }); + + tick(counter); + + if (!resolve_conflict()) + return l_false; + + SASSERT(m_scope_lvl >= m_base_lvl); + + if (!inconsistent()) { + if (resource_limits_exceeded()) + return l_undef; + + if (m_num_conflicts_since_restart > m_restart_threshold && m_scope_lvl - m_base_lvl > 2) { + TRACE("search_bug", tout << "bounded-search return undef, inconsistent: " << inconsistent() << "\n";); + return l_undef; // restart + } + + if (m_num_conflicts > m_fparams.m_max_conflicts) { + TRACE("search_bug", tout << "bounded-search return undef, inconsistent: " << inconsistent() << "\n";); + m_last_search_failure = NUM_CONFLICTS; + return l_undef; + } + } + + if (m_num_conflicts_since_lemma_gc > m_lemma_gc_threshold && + (m_fparams.m_lemma_gc_strategy == LGC_FIXED || m_fparams.m_lemma_gc_strategy == LGC_GEOMETRIC)) { + del_inactive_lemmas(); + } + + m_dyn_ack_manager.propagate_eh(); + CASSERT("dyn_ack", check_clauses(m_lemmas) && check_clauses(m_aux_clauses)); + } + + if (resource_limits_exceeded()) { + SASSERT(!inconsistent()); + return l_undef; + } + + if (m_base_lvl == m_scope_lvl && m_fparams.m_simplify_clauses) + simplify_clauses(); + + if (!decide()) { + final_check_status fcs = final_check(); + TRACE("final_check_result", tout << "fcs: " << fcs << " last_search_failure: " << m_last_search_failure << "\n";); + switch (fcs) { + case FC_DONE: + return l_true; + case FC_CONTINUE: + break; + case FC_GIVEUP: + return l_undef; + } + } + + if (resource_limits_exceeded()) { + SASSERT(!inconsistent()); + return l_undef; + } + } + } + + bool context::resource_limits_exceeded() { + if (m_searching) { + // Some of the flags only make sense to check when searching. + // For example, the timer is only started in init_search(). + if (m_last_search_failure != OK) + return true; + + if (m_timer.ms_timeout(m_fparams.m_soft_timeout)) { + m_last_search_failure = TIMEOUT; + return true; + } + + if (m_instr.is_instruction_maxed(m_fparams.m_instr_out)) { + m_last_search_failure = TIMEOUT; + return true; + } + + if (m_progress_callback) { + m_progress_callback->fast_progress_sample(); + if (m_fparams.m_progress_sampling_freq > 0 && m_timer.ms_timeout(m_next_progress_sample + 1)) { + m_progress_callback->slow_progress_sample(); + m_next_progress_sample = (unsigned)(m_timer.get_seconds() * 1000) + m_fparams.m_progress_sampling_freq; + } + } + } + + if (m_cancel_flag) { + m_last_search_failure = CANCELED; + return true; + } + + if (memory::above_high_watermark()) { + m_last_search_failure = MEMOUT; + return true; + } + + return false; + } + + final_check_status context::final_check() { + TRACE("final_check", tout << "final_check inconsistent: " << inconsistent() << "\n"; display(tout); display_normalized_enodes(tout);); + CASSERT("relevancy", check_relevancy()); + + if (m_fparams.m_model_on_final_check) { + mk_proto_model(l_undef); + model_pp(std::cout, *m_proto_model); + std::cout << "END_OF_MODEL\n"; + std::cout.flush(); + } + + m_stats.m_num_final_checks++; + + final_check_status ok = m_qmanager->final_check_eh(false); + if (ok != FC_DONE) + return ok; + + m_incomplete_theories.reset(); + + unsigned old_idx = m_final_check_idx; + unsigned num_th = m_theory_set.size(); + unsigned range = num_th + 1; + final_check_status result = FC_DONE; + failure f = OK; + + do { + TRACE("final_check_step", tout << "processing: " << m_final_check_idx << ", result: " << result << "\n";); + final_check_status ok; + if (m_final_check_idx < num_th) { + theory * th = m_theory_set[m_final_check_idx]; + IF_VERBOSE(100, verbose_stream() << "final check '" << th->get_name() << "' ...\n";); + ok = th->final_check_eh(); + TRACE("final_check_step", tout << "final check '" << th->get_name() << " ok: " << ok << " inconsistent " << inconsistent() << "\n";); + if (ok == FC_GIVEUP) { + f = THEORY; + m_incomplete_theories.push_back(th); + } + } + else { + ok = m_qmanager->final_check_eh(true); + TRACE("final_check_step", tout << "quantifier ok: " << ok << " " << "inconsistent " << inconsistent() << "\n";); + } + + m_final_check_idx = (m_final_check_idx + 1) % range; + // IF_VERBOSE(1000, verbose_stream() << "final check status: " << ok << "\n";); + + switch (ok) { + case FC_DONE: + break; + case FC_GIVEUP: + result = FC_GIVEUP; + break; + case FC_CONTINUE: + return FC_CONTINUE; + break; + } + } + while (m_final_check_idx != old_idx); + + TRACE("final_check_step", tout << "result: " << result << "\n";); + + if (can_propagate()) { + TRACE("final_check_step", tout << "can propagate: continue...\n";); + return FC_CONTINUE; + } + + SASSERT(result != FC_DONE || check_th_diseq_propagation()); + TRACE("final_check_step", tout << "RESULT final_check: " << result << "\n";); + if (result == FC_GIVEUP && f != OK) + m_last_search_failure = f; + return result; + } + + void context::check_proof(proof * pr) { + if (m_manager.proofs_enabled() && m_fparams.m_check_proof) { + proof_checker pf(m_manager); + expr_ref_vector side_conditions(m_manager); + pf.check(pr, side_conditions); + } + } + + void context::forget_phase_of_vars_in_current_level() { + unsigned head = m_scope_lvl == 0 ? 0 : m_scopes[m_scope_lvl - 1].m_assigned_literals_lim; + unsigned sz = m_assigned_literals.size(); + for (unsigned i = head; i < sz; i++) { + literal l = m_assigned_literals[i]; + bool_var v = l.var(); + TRACE("forget_phase", tout << "forgeting phase of l: " << l << "\n";); + m_bdata[v].m_phase_available = false; + } + } + + bool context::resolve_conflict() { + m_stats.m_num_conflicts++; + m_num_conflicts ++; + m_num_conflicts_since_restart ++; + m_num_conflicts_since_lemma_gc ++; + switch (m_conflict.get_kind()) { + case b_justification::CLAUSE: + case b_justification::BIN_CLAUSE: + m_stats.m_num_sat_conflicts++; + break; + default: + break; + } + if (m_fparams.m_phase_selection == PS_CACHING_CONSERVATIVE || m_fparams.m_phase_selection == PS_CACHING_CONSERVATIVE2) + forget_phase_of_vars_in_current_level(); + m_atom_propagation_queue.reset(); + m_eq_propagation_queue.reset(); + m_th_eq_propagation_queue.reset(); + m_th_diseq_propagation_queue.reset(); + if (m_conflict_resolution->resolve(m_conflict, m_not_l)) { + unsigned new_lvl = m_conflict_resolution->get_new_scope_lvl(); + unsigned num_lits = m_conflict_resolution->get_lemma_num_literals(); + literal * lits = m_conflict_resolution->get_lemma_literals(); + + SASSERT(num_lits > 0); + unsigned conflict_lvl = get_assign_level(lits[0]); + SASSERT(conflict_lvl <= m_scope_lvl); + + // When num_lits == 1, then the default behavior is to go + // to base-level. If the problem has quantifiers, it may be + // too expensive to do that, since all instances will need to + // be recreated. If that is the case, I store the assertions in + // a special vector and keep reasserting whenever I backtrack. + // Moreover, I backtrack only one level. + bool delay_forced_restart = + m_fparams.m_delay_units && + internalized_quantifiers() && + num_lits == 1 && + conflict_lvl > m_search_lvl + 1 && + !m_manager.proofs_enabled() && + m_units_to_reassert.size() < m_fparams.m_delay_units_threshold; + if (delay_forced_restart) { + new_lvl = conflict_lvl - 1; + } + + // Some of the literals/enodes of the conflict clause will be destroyed during + // backtracking, and will need to be recreated. However, I want to keep + // the generation number for enodes that are going to be recreated. See + // comment in cache_generation(unsigned). + if (m_conflict_resolution->get_lemma_intern_lvl() > new_lvl) + cache_generation(num_lits, lits, new_lvl); + + SASSERT(new_lvl < m_scope_lvl); + TRACE("resolve_conflict_bug", + tout << "m_scope_lvl: " << m_scope_lvl << ", new_lvl: " << new_lvl << ", lemma_intern_lvl: " << m_conflict_resolution->get_lemma_intern_lvl() << "\n"; + tout << "num_lits: " << num_lits << "\n"; + for (unsigned i = 0; i < num_lits; i++) { + literal l = lits[i]; + tout << l << " "; + display_literal(tout, l); + tout << ", ilvl: " << get_intern_level(l.var()) << "\n" + << mk_pp(bool_var2expr(l.var()), m_manager) << "\n"; + }); +#ifndef SMTCOMP + if (m_fparams.m_trace_stream != NULL) { + *m_fparams.m_trace_stream << "[conflict] "; + display_literals(*m_fparams.m_trace_stream, num_lits, lits); + *m_fparams.m_trace_stream << "\n"; + } +#endif +#ifdef Z3DEBUG + expr_ref_vector expr_lits(m_manager); + svector expr_signs; + for (unsigned i = 0; i < num_lits; i++) { + literal l = lits[i]; + SASSERT(get_assignment(l) == l_false); + expr_lits.push_back(bool_var2expr(l.var())); + expr_signs.push_back(l.sign()); + } +#endif + proof * pr = 0; + if (m_manager.proofs_enabled()) { + pr = m_conflict_resolution->get_lemma_proof(); + // check_proof(pr); + TRACE("context_proof", tout << mk_ll_pp(pr, m_manager);); + TRACE("context_proof_hack", + static ast_mark visited; + ast_ll_pp(tout, m_manager, pr, visited);); + } + // I invoke pop_scope_core instead of pop_scope because I don't want + // to reset cached generations... I need them to rebuild the literals + // of the new conflict clause. + unsigned num_bool_vars = pop_scope_core(m_scope_lvl - new_lvl); + SASSERT(m_scope_lvl == new_lvl); + // the logical context may still be in conflict after + // clauses are reinitialized in pop_scope. + if (m_conflict_resolution->get_lemma_intern_lvl() > m_scope_lvl) { + expr * * atoms = m_conflict_resolution->get_lemma_atoms(); + for (unsigned i = 0; i < num_lits; i++) { + literal l = lits[i]; + if (l.var() >= static_cast(num_bool_vars)) { + // This boolean variable was deleted during backtracking, it need to be recreated. + // Remark: atom may be a negative literal (not a). Z3 creates Boolean variables for not-gates that + // are nested in terms. Example: let f be a uninterpreted function from Bool -> Int. + // Then, given the term (f (not a)), Z3 will create a boolean variable for (not a) when internalizing (f (not a)). + expr * atom = atoms[i]; + internalize(atom, true); + // If atom is actually a negative literal (not a), then get_bool_var will return return null_bool_var. + // Thus, we must use get_literal instead. This was a bug/crash in Z3 <= 4.0 + literal new_l = get_literal(atom); + if (l.sign()) + new_l.neg(); + // For reference, here is the buggy version + // BEGIN BUGGY VERSION + // bool_var v = get_bool_var(atom); + // CTRACE("resolve_conflict_crash", v == null_bool_var, tout << mk_ismt2_pp(atom, m_manager) << "\n";); + // SASSERT(v != null_bool_var); + // literal new_l = literal(v, l.sign()); + // END BUGGY VERSION + lits[i] = new_l; + } + } + } + // Resetting the cache manually because I did not invoke pop_scope, but pop_scope_core + reset_cache_generation(); + TRACE("resolve_conflict_bug", + tout << "AFTER m_scope_lvl: " << m_scope_lvl << ", new_lvl: " << new_lvl << ", lemma_intern_lvl: " << + m_conflict_resolution->get_lemma_intern_lvl() << "\n"; + tout << "num_lits: " << num_lits << "\n"; + for (unsigned i = 0; i < num_lits; i++) { + literal l = lits[i]; + tout << l << " "; + display_literal(tout, l); + tout << ", ilvl: " << get_intern_level(l.var()) << "\n" + << mk_pp(bool_var2expr(l.var()), m_manager) << "\n"; + }); +#ifdef Z3DEBUG + for (unsigned i = 0; i < num_lits; i++) { + literal l = lits[i]; + if (m_manager.is_not(expr_lits.get(i))) { + // the sign must have flipped when internalizing + expr * real_atom = to_app(expr_lits.get(i))->get_arg(0); + SASSERT(real_atom == bool_var2expr(l.var())); + SASSERT(expr_signs[i] != l.sign()); + } + else { + SASSERT(expr_lits.get(i) == bool_var2expr(l.var())); + SASSERT(expr_signs[i] == l.sign()); + } + } +#endif + justification * js = 0; + if (m_manager.proofs_enabled()) { + js = alloc(justification_proof_wrapper, *this, pr, false); + } +#if 0 + { + static unsigned counter = 0; + static uint64 total = 0; + static unsigned max = 0; + counter++; + total += num_lits; + if (num_lits > max) { + max = num_lits; + } + if (counter % 1000 == 0) { + verbose_stream() << "[sat] avg. clause size: " << ((double) total/(double) counter) << ", max: " << max << std::endl; + for (unsigned i = 0; i < num_lits; i++) { + literal l = lits[i]; + verbose_stream() << l.sign() << " " << mk_pp(bool_var2expr(l.var()), m_manager) << "\n"; + } + } + } +#endif + mk_clause(num_lits, lits, js, CLS_LEARNED); + if (delay_forced_restart) { + SASSERT(num_lits == 1); + expr * unit = bool_var2expr(lits[0].var()); + bool unit_sign = lits[0].sign(); + m_units_to_reassert.push_back(unit); + m_units_to_reassert_sign.push_back(unit_sign); + TRACE("reassert_units", tout << "asserting #" << unit->get_id() << " " << unit_sign << " @ " << m_scope_lvl << "\n";); + } + + m_conflict_resolution->release_lemma_atoms(); + TRACE("context_lemma", tout << "new lemma: "; + literal_vector v(num_lits, lits); + std::sort(v.begin(), v.end()); + for (unsigned i = 0; i < num_lits; i++) { + display_literal(tout, v[i]); + tout << "\n"; + v[i].display(tout, m_manager, m_bool_var2expr.c_ptr()); + tout << "\n\n"; + } + tout << "\n";); + decay_bvar_activity(); + update_phase_cache_counter(); + return true; + } + else if (m_manager.proofs_enabled()) { + m_unsat_proof = m_conflict_resolution->get_lemma_proof(); + check_proof(m_unsat_proof); + } + return false; + } + + void context::get_relevant_labels(expr* cnstr, buffer & result) { + if (m_fparams.m_check_at_labels) { + check_at_labels checker(m_manager); + if (cnstr && !checker.check(cnstr)) { + warning_msg("Boogie generated formula that can require multiple '@' labels in a counter-example"); + } + else { + unsigned nf = m_asserted_formulas.get_num_formulas(); + for (unsigned i = 0; i < nf; ++i) { + expr* fml = m_asserted_formulas.get_formula(i); + if (!checker.check(fml)) { + warning_msg("Boogie generated formula that can require multiple '@' labels in a counter-example"); + break; + } + } + } + } + + SASSERT(!inconsistent()); + unsigned sz = m_b_internalized_stack.size(); + for (unsigned i = 0; i < sz; i++) { + expr * curr = m_b_internalized_stack.get(i); + if (is_relevant(curr) && get_assignment(curr) == l_true) { + // if curr is a label literal, then its tags will be copied to result. + m_manager.is_label_lit(curr, result); + } + } + } + + /** + \brief Collect relevant literals that may be used to block the current assignment. + If at_lbls is true, then only labels that contains '@' are considered. (This is a hack for Boogie). + This hack is also available in the Simplify theorem prover. + */ + void context::get_relevant_labeled_literals(bool at_lbls, expr_ref_vector & result) { + SASSERT(!inconsistent()); + buffer lbls; + unsigned sz = m_b_internalized_stack.size(); + for (unsigned i = 0; i < sz; i++) { + expr * curr = m_b_internalized_stack.get(i); + if (is_relevant(curr) && get_assignment(curr) == l_true) { + lbls.reset(); + if (m_manager.is_label_lit(curr, lbls)) { + bool include = false; + if (at_lbls) { + // include if there is a label with the '@' sign. + buffer::const_iterator it = lbls.begin(); + buffer::const_iterator end = lbls.end(); + for (; it != end; ++it) { + symbol const & s = *it; + if (s.contains('@')) { + include = true; + break; + } + } + } + else { + include = true; + } + if (include) + result.push_back(curr); + } + } + } + } + + /** + \brief Store in result the (relevant) literal assigned by the + logical context. + */ + void context::get_relevant_literals(expr_ref_vector & result) { + SASSERT(!inconsistent()); + unsigned sz = m_b_internalized_stack.size(); + for (unsigned i = 0; i < sz; i++) { + expr * curr = m_b_internalized_stack.get(i); + if (is_relevant(curr)) { + switch (get_assignment(curr)) { + case l_true: + result.push_back(curr); + break; + case l_false: + result.push_back(m_manager.mk_not(curr)); + break; + default: + break; + } + } + } + } + + /** + \brief Store the current set of guessed literals (i.e., case splits). + */ + void context::get_guessed_literals(expr_ref_vector & result) { + // The literals between [m_base_lvl, m_search_lvl) are not guesses but assumptions. + SASSERT(m_base_lvl <= m_scopes.size()); + if (m_search_lvl == m_scopes.size()) { + // do nothing... there are guesses... + } + for (unsigned i = m_search_lvl; i < m_scope_lvl; i++) { + // This method assumes the first literal assigned in a non base scope level is a guess. + scope & s = m_scopes[i]; + unsigned guess_idx = s.m_assigned_literals_lim; + literal guess = m_assigned_literals[guess_idx]; + SASSERT(get_justification(guess.var()).get_kind() == b_justification::AXIOM); + expr_ref lit(m_manager); + literal2expr(guess, lit); + result.push_back(lit); + } + } + + /** + \brief Undo object for bool var m_true_first field update. + */ + class set_true_first_trail : public trail { + bool_var m_var; + public: + set_true_first_trail(bool_var v):m_var(v) {} + virtual void undo(context & ctx) { + ctx.m_bdata[m_var].reset_true_first_flag(); + } + }; + + void context::set_true_first_flag(bool_var v) { + push_trail(set_true_first_trail(v)); + bool_var_data & d = m_bdata[v]; + d.set_true_first_flag(); + } + + bool context::assume_eq(enode * lhs, enode * rhs) { + if (lhs->get_root() == rhs->get_root()) + return false; // it is not necessary to assume the eq. + expr * _lhs = lhs->get_owner(); + expr * _rhs = rhs->get_owner(); + expr * eq = mk_eq_atom(_lhs, _rhs); + TRACE("assume_eq", tout << "creating interface eq:\n" << mk_pp(eq, m_manager) << "\n";); + if (m_manager.is_false(eq)) { + return false; + } + bool r = false; + if (!b_internalized(eq)) { + // I do not invoke internalize(eq, true), because I want to + // mark the try_true_first flag before invoking theory::internalize_eq_eh. + // Reason: Theories like arithmetic should be able to know if the try_true_first flag is + // marked or not. They use this information to also mark auxiliary atoms such as: + // (<= (- x y) 0) + // (>= (- y x) 0) + // for the new equality atom (= x y). + if (m_manager.is_eq(eq)) { + internalize_formula_core(to_app(eq), true); + bool_var v = get_bool_var(eq); + bool_var_data & d = get_bdata(v); + d.set_eq_flag(); + set_true_first_flag(v); + sort * s = m_manager.get_sort(to_app(eq)->get_arg(0)); + theory * th = m_theories.get_plugin(s->get_family_id()); + if (th) + th->internalize_eq_eh(to_app(eq), v); + } + else { + internalize(eq, true); + } + r = true; + m_stats.m_num_interface_eqs++; + TRACE("assume_eq", tout << "new internalization.\n";); + } + bool_var v = get_bool_var(eq); + bool_var_data & d = m_bdata[v]; + if (!d.try_true_first()) { + set_true_first_flag(v); + r = true; + TRACE("assume_eq", tout << "marked as ieq.\n";); + } + if (get_assignment(v) == l_undef) { + TRACE("assume_eq", tout << "variable is unassigned.\n";); + r = true; + } + if (relevancy()) { + if (!is_relevant(eq)) { + TRACE("assume_eq", tout << "marking eq as relevant.\n";); + mark_as_relevant(eq); + r = true; + } + } + TRACE("assume_eq", tout << "variable value: " << get_assignment(v) << "\n";); + TRACE("assume_eq", tout << "assume_eq result: " << r << "\n";); + return r; + } + + bool context::is_shared(enode * n) const { + n = n->get_root(); + unsigned num_th_vars = n->get_num_th_vars(); + switch (num_th_vars) { + case 0: + return false; + case 1: { + if (m_qmanager->is_shared(n)) + return true; + + // the variabe is shared if the equivalence class of n + // contains a parent application. + + theory_var_list * l = n->get_th_var_list(); + theory_id th_id = l->get_th_id(); + + enode_vector::const_iterator it = n->begin_parents(); + enode_vector::const_iterator end = n->end_parents(); + for (; it != end; ++it) { + enode * parent = *it; + family_id fid = parent->get_owner()->get_family_id(); + if (fid != th_id && fid != m_manager.get_basic_family_id()) { + TRACE("is_shared", tout << mk_pp(n->get_owner(), m_manager) << "\nis shared because of:\n" << mk_pp(parent->get_owner(), m_manager) << "\n";); + return true; + } + } + + // Some theories implement families of theories. Examples: + // Arrays and Tuples. For example, array theory is a + // parametric theory, that is, it implements several theories: + // (array int int), (array int (array int int)), ... + // + // Example: + // + // a : (array int int) + // b : (array int int) + // x : int + // y : int + // v : int + // w : int + // A : (array (array int int) int) + // + // assert (= b (store a x v)) + // assert (= b (store a y w)) + // assert (not (= x y)) + // assert (not (select A a)) + // assert (not (select A b)) + // check + // + // In the example above, 'a' and 'b' are shared variables between + // the theories of (array int int) and (array (array int int) int). + // Remark: The inconsistency is not going to be detected if they are + // not marked as shared. + return get_theory(th_id)->is_shared(l->get_th_var()); + } + default: + return true; + } + } + + bool context::get_value(enode * n, expr_ref & value) { + sort * s = m_manager.get_sort(n->get_owner()); + family_id fid = s->get_family_id(); + theory * th = get_theory(fid); + if (th == 0) + return false; + return th->get_value(n, value); + } + + void context::mk_proto_model(lbool r) { + TRACE("get_model", + display(tout); + display_normalized_enodes(tout); + display_enodes_lbls(tout); + m_fingerprints.display(tout); + ); + failure fl = get_last_search_failure(); + if (fl == TIMEOUT || fl == MEMOUT || fl == CANCELED || fl == NUM_CONFLICTS) { + // don't generate model. + return; + } + + if (m_fparams.m_model || m_fparams.m_model_on_final_check || m_qmanager->model_based()) { + m_model_generator->reset(); + m_proto_model = m_model_generator->mk_model(); + m_qmanager->adjust_model(m_proto_model.get()); + TRACE("mbqi_bug", tout << "before complete_partial_funcs:\n"; model_pp(tout, *m_proto_model);); + m_proto_model->complete_partial_funcs(); + TRACE("mbqi_bug", tout << "before cleanup:\n"; model_pp(tout, *m_proto_model);); + m_proto_model->cleanup(); + if (m_fparams.m_model_compact) + m_proto_model->compress(); + TRACE("mbqi_bug", tout << "after cleanup:\n"; model_pp(tout, *m_proto_model);); + } + } + + proof * context::get_proof() { + if (!m_manager.proofs_enabled()) + return 0; + return m_unsat_proof; + } + + void context::get_model(model_ref & m) const { + if (inconsistent()) + m = 0; + else + m = const_cast(m_model.get()); + } + + void context::get_proto_model(proto_model_ref & m) const { + m = const_cast(m_proto_model.get()); + } + + failure context::get_last_search_failure() const { + return m_last_search_failure; + } + + void context::set_cancel_flag(bool f) { + m_cancel_flag = f; + m_asserted_formulas.set_cancel_flag(f); + } + +}; + diff --git a/lib/smt_context.h b/lib/smt_context.h new file mode 100644 index 000000000..825e5e81a --- /dev/null +++ b/lib/smt_context.h @@ -0,0 +1,1462 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_context.h + +Abstract: + + Logical context + +Author: + + Leonardo de Moura (leonardo) 2008-02-18. + +Revision History: + +--*/ +#ifndef _SMT_CONTEXT_H_ +#define _SMT_CONTEXT_H_ + +#include"smt_clause.h" +#include"smt_setup.h" +#include"smt_enode.h" +#include"smt_cg_table.h" +#include"smt_b_justification.h" +#include"smt_eq_justification.h" +#include"smt_justification.h" +#include"smt_bool_var_data.h" +#include"smt_theory.h" +#include"smt_quantifier.h" +#include"smt_quantifier_stat.h" +#include"smt_statistics.h" +#include"smt_conflict_resolution.h" +#include"smt_relevancy.h" +#include"smt_case_split_queue.h" +#include"smt_almost_cg_table.h" +#include"smt_failure.h" +#include"asserted_formulas.h" +#include"smt_types.h" +#include"dyn_ack.h" +#include"ast_smt_pp.h" +#include"watch_list.h" +#include"trail.h" +#include"fingerprints.h" +#include"ref.h" +#include"proto_model.h" +#include"model.h" +#include"timer.h" +#include"instruction_count.h" +#include"statistics.h" +#include"progress_callback.h" + +// there is a significant space overhead with allocating 1000+ contexts in +// the case that each context only references a few expressions. +// Using a map instead of a vector for the literals can compress space +// consumption. +#define USE_BOOL_VAR_VECTOR 1 + +namespace smt { + + class model_generator; + + class context { + friend class model_generator; + public: + statistics m_stats; + + std::ostream& display_last_failure(std::ostream& out) const; + std::string last_failure_as_string() const; + void set_progress_callback(progress_callback *callback); + + protected: + ast_manager & m_manager; + front_end_params & m_fparams; + params_ref m_params; + setup m_setup; + volatile bool m_cancel_flag; + timer m_timer; + instruction_count m_instr; + asserted_formulas m_asserted_formulas; + scoped_ptr m_qmanager; + scoped_ptr m_model_generator; + scoped_ptr m_relevancy_propagator; + random_gen m_random; + bool m_flushing; // (debug support) true when flushing + progress_callback * m_progress_callback; + unsigned m_next_progress_sample; + + region m_region; + + fingerprint_set m_fingerprints; + + expr_ref_vector m_b_internalized_stack; // stack of the boolean expressions already internalized. + // Remark: boolean expressions can also be internalized as + // enodes. Examples: boolean expression nested in an + // uninterpreted function. + expr_ref_vector m_e_internalized_stack; // stack of the expressions already internalized as enodes. + + ptr_vector m_justifications; + + unsigned m_final_check_idx; // circular counter used for implementing fairness + + // ----------------------------------- + // + // Equality & Uninterpreted functions + // + // ----------------------------------- + enode * m_true_enode; + enode * m_false_enode; + ptr_vector m_app2enode; // app -> enode + ptr_vector m_enodes; + plugin_manager m_theories; // mapping from theory_id -> theory + ptr_vector m_theory_set; // set of theories for fast traversal + vector m_decl2enodes; // decl -> enode (for decls with arity > 0) + cg_table m_cg_table; + dyn_ack_manager m_dyn_ack_manager; + struct new_eq { + enode * m_lhs; + enode * m_rhs; + eq_justification m_justification; + new_eq() {} + new_eq(enode * lhs, enode * rhs, eq_justification const & js): + m_lhs(lhs), m_rhs(rhs), m_justification(js) {} + }; + svector m_eq_propagation_queue; + struct new_th_eq { + theory_id m_th_id; + theory_var m_lhs; + theory_var m_rhs; + new_th_eq():m_th_id(null_theory_id), m_lhs(null_theory_var), m_rhs(null_theory_var) {} + new_th_eq(theory_id id, theory_var l, theory_var r):m_th_id(id), m_lhs(l), m_rhs(r) {} + }; + svector m_th_eq_propagation_queue; + svector m_th_diseq_propagation_queue; +#ifdef Z3DEBUG + svector m_propagated_th_eqs; + svector m_propagated_th_diseqs; + svector m_diseq_vector; +#endif + enode * m_is_diseq_tmp; // auxiliary enode used to find congruent equality atoms. + + tmp_enode m_tmp_enode; + ptr_vector m_almost_cg_tables; // temporary field for is_ext_diseq + + // ----------------------------------- + // + // Boolean engine + // + // ----------------------------------- +#if USE_BOOL_VAR_VECTOR + svector m_expr2bool_var; // expr id -> bool_var +#else + u_map m_expr2bool_var; +#endif + ptr_vector m_bool_var2expr; // bool_var -> expr + char_vector m_assignment; //!< mapping literal id -> assignment lbool + vector m_watches; //!< per literal + vector m_lit_occs; //!< index for backward subsumption + svector m_bdata; //!< mapping bool_var -> data + svector m_activity; + clause_vector m_aux_clauses; + clause_vector m_lemmas; + vector m_clauses_to_reinit; + expr_ref_vector m_units_to_reassert; + svector m_units_to_reassert_sign; + literal_vector m_assigned_literals; + unsigned m_qhead; + unsigned m_simp_qhead; + int m_simp_counter; //!< can become negative + scoped_ptr m_case_split_queue; + double m_bvar_inc; + bool m_phase_cache_on; + unsigned m_phase_counter; //!< auxiliary variable used to decide when to turn on/off phase caching + bool m_phase_default; //!< default phase when using phase caching + + // A conflict is usually a single justification. That is, a justification + // for false. If m_not_l is not null_literal, then m_conflict is a + // justification for l, and the conflict is union of m_no_l and m_conflict; + b_justification m_conflict; + literal m_not_l; + scoped_ptr m_conflict_resolution; + proof_ref m_unsat_proof; + + + literal_vector m_atom_propagation_queue; + + obj_map m_cached_generation; + obj_hashtable m_cache_generation_visited; + + // ----------------------------------- + // + // Model generation + // + // ----------------------------------- + proto_model_ref m_proto_model; + model_ref m_model; + void mk_proto_model(lbool r); + struct scoped_mk_model; + + // ----------------------------------- + // + // Unsat core extraction + // + // ----------------------------------- + typedef u_map bool_var2assumption; + bool_var_vector m_assumptions; + bool_var2assumption m_bool_var2assumption; // maps an expression associated with a literal to the original assumption + expr_ref_vector m_unsat_core; + + // ----------------------------------- + // + // Accessors + // + // ----------------------------------- + public: + ast_manager & get_manager() const { + return m_manager; + } + + simplifier & get_simplifier() { + return m_asserted_formulas.get_simplifier(); + } + + front_end_params & get_fparams() { + return m_fparams; + } + + params_ref const & get_params() { + return m_params; + } + + virtual void set_cancel_flag(bool f = true); + + bool get_cancel_flag() { return m_cancel_flag; } + + region & get_region() { + return m_region; + } + + bool relevancy() const { + return m_fparams.m_relevancy_lvl > 0; + } + + enode * get_enode(expr const * n) const { + SASSERT(e_internalized(n)); + return m_app2enode[n->get_id()]; + } + + /** + \brief Similar to get_enode, but returns 0 if n is to e_internalized. + */ + enode * find_enode(expr const * n) const { + return m_app2enode.get(n->get_id(), 0); + } + + void reset_bool_vars() { + m_expr2bool_var.reset(); + } + + bool_var get_bool_var(expr const * n) const { + return m_expr2bool_var[n->get_id()]; + } + + bool_var get_bool_var_of_id(unsigned id) const { + return m_expr2bool_var[id]; + } + + bool_var get_bool_var_of_id_option(unsigned id) const { + return m_expr2bool_var.get(id, null_bool_var); + } + +#if USE_BOOL_VAR_VECTOR + + void set_bool_var(unsigned id, bool_var v) { + m_expr2bool_var.setx(id, v, null_bool_var); + } +#else + + void set_bool_var(unsigned id, bool_var v) { + if (v == null_bool_var) { + m_expr2bool_var.erase(id); + } + else { + m_expr2bool_var.insert(id, v); + } + } +#endif + + + literal get_literal(expr * n) const; + + bool has_enode(bool_var v) const { + return m_bdata[v].is_enode(); + } + + enode * bool_var2enode(bool_var v) const { + SASSERT(m_bdata[v].is_enode()); + return m_app2enode[m_bool_var2expr[v]->get_id()]; + } + + bool_var enode2bool_var(enode const * n) const { + SASSERT(n->is_bool()); + SASSERT(n != m_false_enode); + return get_bool_var_of_id(n->get_owner_id()); + } + + literal enode2literal(enode const * n) const { + SASSERT(n->is_bool()); + return n == m_false_enode ? false_literal : literal(enode2bool_var(n)); + } + + unsigned get_num_bool_vars() const { + return m_b_internalized_stack.size(); + } + + bool_var_data & get_bdata(bool_var v) { + return m_bdata[v]; + } + + bool_var_data const & get_bdata(bool_var v) const { + return m_bdata[v]; + } + + lbool get_lit_assignment(unsigned lit_idx) const { + return static_cast(m_assignment[lit_idx]); + } + + lbool get_assignment(literal l) const { + return get_lit_assignment(l.index()); + } + + lbool get_assignment(bool_var v) const { + return get_assignment(literal(v)); + } + + lbool get_assignment(expr * n) const; + + // Similar to get_assignment, but returns l_undef if n is not internalized. + lbool find_assignment(expr * n) const; + + lbool get_assignment(enode * n) const; + + void get_assignments(expr_ref_vector& assignments); + + b_justification get_justification(bool_var v) const { + return get_bdata(v).m_justification; + } + + bool has_th_justification(bool_var v, theory_id th_id) const { + b_justification js = get_justification(v); + return js.get_kind() == b_justification::JUSTIFICATION && js.get_justification()->get_from_theory() == th_id; + } + + int get_random_value() { + return m_random(); + } + + bool is_searching() const { + return m_searching; + } + + svector const & get_activity_vector() const { + return m_activity; + } + + double get_activity(bool_var v) const { + return m_activity[v]; + } + + void set_activity(bool_var v, double & act) { + m_activity[v] = act; + } + + bool is_assumption(bool_var v) const { + return get_bdata(v).m_assumption; + } + + bool is_assumption(literal l) const { + return is_assumption(l.var()); + } + + bool is_marked(bool_var v) const { + return get_bdata(v).m_mark; + } + + void set_mark(bool_var v) { + SASSERT(!is_marked(v)); + get_bdata(v).m_mark = true; + } + + void unset_mark(bool_var v) { + SASSERT(is_marked(v)); + get_bdata(v).m_mark = false; + } + + /** + \brief Return the scope level when v was assigned. + */ + unsigned get_assign_level(bool_var v) const { + return get_bdata(v).m_scope_lvl; + } + + unsigned get_assign_level(literal l) const { + return get_assign_level(l.var()); + } + + /** + \brief Return the scope level when v was internalized. + */ + unsigned get_intern_level(bool_var v) const { + return get_bdata(v).get_intern_level(); + } + + theory * get_theory(theory_id th_id) const { + return m_theories.get_plugin(th_id); + } + + ptr_vector::const_iterator begin_theories() const { + return m_theories.begin(); + } + + ptr_vector::const_iterator end_theories() const { + return m_theories.end(); + } + + unsigned get_scope_level() const { + return m_scope_lvl; + } + + unsigned get_base_level() const { + return m_base_lvl; + } + + bool at_base_level() const { + return m_scope_lvl == m_base_lvl; + } + + unsigned get_search_level() const { + return m_search_lvl; + } + + bool at_search_level() const { + return m_scope_lvl == m_search_lvl; + } + + bool tracking_assumptions() const { + return m_search_lvl > m_base_lvl; + } + + expr * bool_var2expr(bool_var v) const { + return m_bool_var2expr[v]; + } + + void literal2expr(literal l, expr_ref & result) const { + if (l == true_literal) + result = m_manager.mk_true(); + else if (l == false_literal) + result = m_manager.mk_false(); + else if (l.sign()) + result = m_manager.mk_not(bool_var2expr(l.var())); + else + result = bool_var2expr(l.var()); + } + + bool is_true(enode const * n) const { + return n == m_true_enode; + } + + bool is_false(enode const * n) const { + return n == m_false_enode; + } + + unsigned get_num_enodes_of(func_decl const * decl) const { + unsigned id = decl->get_decl_id(); + return id < m_decl2enodes.size() ? m_decl2enodes[id].size() : 0; + } + + enode_vector::const_iterator begin_enodes_of(func_decl const * decl) const { + unsigned id = decl->get_decl_id(); + return id < m_decl2enodes.size() ? m_decl2enodes[id].begin() : 0; + } + + enode_vector::const_iterator end_enodes_of(func_decl const * decl) const { + unsigned id = decl->get_decl_id(); + return id < m_decl2enodes.size() ? m_decl2enodes[id].end() : 0; + } + + ptr_vector::const_iterator begin_enodes() const { + return m_enodes.begin(); + } + + ptr_vector::const_iterator end_enodes() const { + return m_enodes.end(); + } + + unsigned get_generation(quantifier * q) const { + return m_qmanager->get_generation(q); + } + + /** + \brief Return true if the logical context internalized universal quantifiers. + */ + bool internalized_quantifiers() const { + return !m_qmanager->empty(); + } + + /** + \brief Return true if the logical context internalized or will internalize universal quantifiers. + */ + bool has_quantifiers() const { + return m_asserted_formulas.has_quantifiers(); + } + + fingerprint * add_fingerprint(void * data, unsigned data_hash, unsigned num_args, enode * const * args) { + return m_fingerprints.insert(data, data_hash, num_args, args); + } + + theory_id get_var_theory(bool_var v) const { + return get_bdata(v).get_theory(); + } + + friend class set_var_theory_trail; + void set_var_theory(bool_var v, theory_id tid); + + /** + \brief set user-supplied rewriter. + */ + + void set_user_rewriter(void* ctx, user_rewriter::fn* rw) { m_asserted_formulas.set_user_rewriter(ctx, rw); } + + // ----------------------------------- + // + // Backtracking support + // + // ----------------------------------- + protected: + typedef ptr_vector > trail_stack; + trail_stack m_trail_stack; +#ifdef Z3DEBUG + bool m_trail_enabled; +#endif + + public: + template + void push_trail(const TrailObject & obj) { + SASSERT(m_trail_enabled); + m_trail_stack.push_back(new (m_region) TrailObject(obj)); + } + + void push_trail_ptr(trail * ptr) { + m_trail_stack.push_back(ptr); + } + + protected: + + unsigned m_scope_lvl; + unsigned m_base_lvl; + unsigned m_search_lvl; // It is greater than m_base_lvl when assumptions are used. Otherwise, it is equals to m_base_lvl + struct scope { + unsigned m_assigned_literals_lim; + unsigned m_trail_stack_lim; + unsigned m_aux_clauses_lim; + unsigned m_justifications_lim; + unsigned m_units_to_reassert_lim; + }; + struct base_scope { + unsigned m_lemmas_lim; + unsigned m_simp_qhead_lim; + unsigned m_inconsistent; + }; + + svector m_scopes; + svector m_base_scopes; + + void push_scope(); + + unsigned pop_scope_core(unsigned num_scopes); + + void pop_scope(unsigned num_scopes); + + void undo_trail_stack(unsigned old_size); + + void unassign_vars(unsigned old_lim); + + void remove_watch_literal(clause * cls, unsigned idx); + + void remove_lit_occs(clause * cls); + + void remove_cls_occs(clause * cls); + + void mark_as_deleted(clause * cls); + + void del_clause(clause * cls); + + void del_clauses(clause_vector & v, unsigned old_size); + + void del_justifications(ptr_vector & justifications, unsigned old_lim); + + bool is_unit_clause(clause const * c) const; + + bool is_empty_clause(clause const * c) const; + + void cache_generation(unsigned new_scope_lvl); + + void cache_generation(clause const * cls, unsigned new_scope_lvl); + + void cache_generation(unsigned num_lits, literal const * lits, unsigned new_scope_lvl); + + void cache_generation(expr * n, unsigned new_scope_lvl); + + void reset_cache_generation(); + + void reinit_clauses(unsigned num_scopes, unsigned num_bool_vars); + + void reassert_units(unsigned units_to_reassert_lim); + + // ----------------------------------- + // + // Internalization + // + // ----------------------------------- + public: + bool b_internalized(expr const * n) const { + return get_bool_var_of_id_option(n->get_id()) != null_bool_var; + } + + bool lit_internalized(expr const * n) const { + return m_manager.is_false(n) || (m_manager.is_not(n) ? b_internalized(to_app(n)->get_arg(0)) : b_internalized(n)); + } + + bool e_internalized(expr const * n) const { + return m_app2enode.get(n->get_id(), 0) != 0; + } + + unsigned get_num_b_internalized() const { + return m_b_internalized_stack.size(); + } + + expr * get_b_internalized(unsigned idx) const { + return m_b_internalized_stack.get(idx); + } + + unsigned get_num_e_internalized() const { + return m_e_internalized_stack.size(); + } + + expr * get_e_internalized(unsigned idx) const { + return m_e_internalized_stack.get(idx); + } + + /** + \brief Return the position (in the assignment stack) of the decision literal at the given scope level. + */ + unsigned get_decision_literal_pos(unsigned scope_lvl) const { + SASSERT(scope_lvl > m_base_lvl); + return m_scopes[scope_lvl - 1].m_assigned_literals_lim; + } + + protected: + unsigned m_generation; //!< temporary variable used during internalization + + bool expand_pos_def_only() const { + return m_fparams.m_nnf_mode == NNF_FULL && m_fparams.m_internalizer_nnf; + } + + public: + bool binary_clause_opt_enabled() const { + return !m_manager.proofs_enabled() && m_fparams.m_binary_clause_opt; + } + protected: + bool_var_data & get_bdata(expr const * n) { + return get_bdata(get_bool_var(n)); + } + + bool_var_data const & get_bdata(expr const * n) const { + return get_bdata(get_bool_var(n)); + } + + typedef std::pair expr_bool_pair; + + void ts_visit_child(expr * n, bool gate_ctx, svector & tcolors, svector & fcolors, svector & todo, bool & visited); + + bool ts_visit_children(expr * n, bool gate_ctx, svector & tcolors, svector & fcolors, svector & todo); + + void top_sort_expr(expr * n, svector & sorted_exprs); + + void assert_default(expr * n, proof * pr); + + void assert_distinct(app * n, proof * pr); + + void internalize_formula(expr * n, bool gate_ctx); + + void internalize_eq(app * n, bool gate_ctx); + + void internalize_distinct(app * n, bool gate_ctx); + + bool internalize_theory_atom(app * n, bool gate_ctx); + + void internalize_quantifier(quantifier * q, bool gate_ctx); + + void internalize_formula_core(app * n, bool gate_ctx); + + void set_merge_tf(enode * n, bool_var v, bool is_new_var); + + friend class set_enode_flag_trail; + + public: + void set_enode_flag(bool_var v, bool is_new_var); + + protected: + void internalize_term(app * n); + + void internalize_ite_term(app * n); + + bool internalize_theory_term(app * n); + + void internalize_uninterpreted(app * n); + + friend class mk_bool_var_trail; + class mk_bool_var_trail : public trail { + public: + virtual void undo(context & ctx) { ctx.undo_mk_bool_var(); } + }; + mk_bool_var_trail m_mk_bool_var_trail; + + void undo_mk_bool_var(); + + friend class mk_enode_trail; + class mk_enode_trail : public trail { + public: + virtual void undo(context & ctx) { ctx.undo_mk_enode(); } + }; + + mk_enode_trail m_mk_enode_trail; + + void undo_mk_enode(); + + void apply_sort_cnstr(app * term, enode * e); + + bool simplify_aux_clause_literals(unsigned & num_lits, literal * lits, literal_buffer & simp_lits); + + bool simplify_aux_lemma_literals(unsigned & num_lits, literal * lits); + + void mark_for_reinit(clause * cls, unsigned scope_lvl, bool reinternalize_atoms); + + unsigned get_max_iscope_lvl(unsigned num_lits, literal const * lits) const; + + bool use_binary_clause_opt(literal l1, literal l2, bool lemma) const; + + int select_learned_watch_lit(clause const * cls) const; + + int select_watch_lit(clause const * cls, int starting_at) const; + + void add_watch_literal(clause * cls, unsigned idx); + + proof * mk_clause_def_axiom(unsigned num_lits, literal * lits, expr * root_gate); + + public: + void mk_gate_clause(unsigned num_lits, literal * lits); + + void mk_gate_clause(literal l1, literal l2); + + void mk_gate_clause(literal l1, literal l2, literal l3); + + void mk_gate_clause(literal l1, literal l2, literal l3, literal l4); + + protected: + void mk_root_clause(unsigned num_lits, literal * lits, proof * pr); + + void mk_root_clause(literal l1, literal l2, proof * pr); + + void mk_root_clause(literal l1, literal l2, literal l3, proof * pr); + + void add_and_rel_watches(app * n); + + void add_or_rel_watches(app * n); + + void add_ite_rel_watches(app * n); + + void mk_not_cnstr(app * n); + + void mk_and_cnstr(app * n); + + void mk_or_cnstr(app * n); + + void mk_iff_cnstr(app * n); + + void mk_ite_cnstr(app * n); + + bool lit_occs_enabled() const { return m_fparams.m_phase_selection==PS_OCCURRENCE; } + + void add_lit_occs(clause * cls); + public: + void internalize(expr * n, bool gate_ctx); + + void internalize(expr * n, bool gate_ctx, unsigned generation); + + clause * mk_clause(unsigned num_lits, literal * lits, justification * j, clause_kind k = CLS_AUX, clause_del_eh * del_eh = 0); + + void mk_clause(literal l1, literal l2, justification * j); + + void mk_clause(literal l1, literal l2, literal l3, justification * j); + + void mk_th_axiom(theory_id tid, unsigned num_lits, literal * lits, unsigned num_params = 0, parameter * params = 0); + + void mk_th_axiom(theory_id tid, literal l1, literal l2, unsigned num_params = 0, parameter * params = 0); + + void mk_th_axiom(theory_id tid, literal l1, literal l2, literal l3, unsigned num_params = 0, parameter * params = 0); + + + bool_var mk_bool_var(expr * n); + + enode * mk_enode(app * n, bool suppress_args, bool merge_tf, bool cgc_enabled); + + void attach_th_var(enode * n, theory * th, theory_var v); + + template + justification * mk_justification(Justification const & j) { + justification * js = new (m_region) Justification(j); + SASSERT(js->in_region()); + if (js->has_del_eh()) + m_justifications.push_back(js); + return js; + } + + // ----------------------------------- + // + // Engine + // + // ----------------------------------- + protected: + lbool m_last_search_result; + failure m_last_search_failure; + ptr_vector m_incomplete_theories; //!< theories that failed to produce a model + bool m_searching; + ptr_vector m_assumption_core; + unsigned m_num_conflicts; + unsigned m_num_conflicts_since_restart; + unsigned m_num_conflicts_since_lemma_gc; + unsigned m_restart_threshold; + unsigned m_restart_outer_threshold; + unsigned m_luby_idx; + double m_agility; + unsigned m_lemma_gc_threshold; + + void assign_core(literal l, b_justification j, bool decision = false); + void trace_assign(literal l, b_justification j, bool decision) const; + + public: + void assign(literal l, b_justification j, bool decision = false) { + SASSERT(l != false_literal); + SASSERT(l != null_literal); + switch (get_assignment(l)) { + case l_false: + set_conflict(j, ~l); + break; + case l_undef: + assign_core(l, j, decision); + break; + case l_true: + return; + } + } + + void assign(literal l, justification * j, bool decision = false) { + assign(l, j ? b_justification(j) : b_justification::mk_axiom(), decision); + } + + friend class set_true_first_trail; + void set_true_first_flag(bool_var v); + + bool try_true_first(bool_var v) const { return get_bdata(v).try_true_first(); } + + bool assume_eq(enode * lhs, enode * rhs); + + bool is_shared(enode * n) const; + + void assign_eq(enode * lhs, enode * rhs, eq_justification const & js) { + push_eq(lhs, rhs, js); + } + + /** + \brief Force the given phase next time we case split v. + This method has no effect if phase caching is disabled. + */ + void force_phase(bool_var v, bool phase) { + bool_var_data & d = get_bdata(v); + d.m_phase_available = true; + d.m_phase = phase; + } + + void force_phase(literal l) { + force_phase(l.var(), !l.sign()); + } + + bool contains_instance(quantifier * q, unsigned num_bindings, enode * const * bindings); + + bool add_instance(quantifier * q, app * pat, unsigned num_bindings, enode * const * bindings, unsigned max_generation, + unsigned min_top_generation, unsigned max_top_generation, ptr_vector & used_enodes); + + void set_global_generation(unsigned generation) { m_generation = generation; } + +#ifdef Z3DEBUG + bool slow_contains_instance(quantifier const * q, unsigned num_bindings, enode * const * bindings) const { + return m_fingerprints.slow_contains(q, q->get_id(), num_bindings, bindings); + } +#endif + + protected: + void push_new_th_eq(theory_id th, theory_var lhs, theory_var rhs); + + void push_new_th_diseq(theory_id th, theory_var lhs, theory_var rhs); + + friend class add_eq_trail; + + void add_eq(enode * n1, enode * n2, eq_justification js); + + void remove_parents_from_cg_table(enode * r1); + + void reinsert_parents_into_cg_table(enode * r1, enode * r2, enode * n1, enode * n2, eq_justification js); + + void invert_trans(enode * n); + + theory_var get_closest_var(enode * n, theory_id th_id); + + void merge_theory_vars(enode * r2, enode * r1, eq_justification js); + + void propagate_bool_enode_assignment(enode * r1, enode * r2, enode * n1, enode * n2); + + void propagate_bool_enode_assignment_core(enode * source, enode * target); + + void undo_add_eq(enode * r1, enode * n1, unsigned r2_num_parents); + + void restore_theory_vars(enode * r2, enode * r1); + + void push_eq(enode * lhs, enode * rhs, eq_justification const & js) { + SASSERT(lhs != rhs); + m_eq_propagation_queue.push_back(new_eq(lhs, rhs, js)); + } + + void push_new_congruence(enode * n1, enode * n2, bool used_commutativity) { + SASSERT(n1->m_cg == n2); + // if (is_relevant(n1)) mark_as_relevant(n2); + push_eq(n1, n2, eq_justification::mk_cg(used_commutativity)); + } + + void add_diseq(enode * n1, enode * n2); + + void assign_quantifier(quantifier * q); + + void set_conflict(b_justification js, literal not_l); + + void set_conflict(b_justification js) { + set_conflict(js, null_literal); + } + + public: + void set_conflict(justification * js) { + SASSERT(js); + set_conflict(b_justification(js)); + } + + bool inconsistent() const { + return m_conflict != null_b_justification; + } + + unsigned get_num_conflicts() const { + return m_num_conflicts; + } + + static bool is_eq(enode const * n1, enode const * n2) { return n1->get_root() == n2->get_root(); } + + bool is_diseq(enode * n1, enode * n2) const; + + bool is_diseq_slow(enode * n1, enode * n2) const; + + bool is_ext_diseq(enode * n1, enode * n2, unsigned depth); + + enode * get_enode_eq_to(func_decl * f, unsigned num_args, enode * const * args); + + protected: + bool decide(); + + void update_phase_cache_counter(); + +#define ACTIVITY_LIMIT 1e100 +#define INV_ACTIVITY_LIMIT 1e-100 + + void rescale_bool_var_activity(); + + public: + void inc_bvar_activity(bool_var v) { + double & act = m_activity[v]; + act += m_bvar_inc; + if (act > ACTIVITY_LIMIT) + rescale_bool_var_activity(); + m_case_split_queue->activity_increased_eh(v); + } + + protected: + + void decay_bvar_activity() { + m_bvar_inc *= m_fparams.m_inv_decay; + } + + bool simplify_clause(clause * cls); + + unsigned simplify_clauses(clause_vector & clauses, unsigned starting_at); + + void simplify_clauses(); + + /** + \brief Return true if the give clause is justifying some literal. + */ + bool is_justifying(clause * cls) const { + for (unsigned i = 0; i < 2; i++) { + b_justification js; + js = get_justification(cls->get_literal(i).var()); + if (js.get_kind() == b_justification::CLAUSE && js.get_clause() == cls) + return true; + } + return false; + } + + bool can_delete(clause * cls) const { + if (cls->in_reinit_stack()) + return false; + return !is_justifying(cls); + } + + void del_inactive_lemmas(); + + void del_inactive_lemmas1(); + + void del_inactive_lemmas2(); + + bool more_than_k_unassigned_literals(clause * cls, unsigned k); + + void internalize_assertions(); + + void assert_assumption(expr * a); + + bool validate_assumptions(unsigned num_assumptions, expr * const * assumptions); + + void init_assumptions(unsigned num_assumptions, expr * const * assumptions); + + void reset_assumptions(); + + void mk_unsat_core(); + + void init_search(); + + void end_search(); + + lbool search(); + + void inc_limits(); + + void tick(unsigned & counter) const; + + lbool bounded_search(); + + final_check_status final_check(); + + void check_proof(proof * pr); + + void forget_phase_of_vars_in_current_level(); + + virtual bool resolve_conflict(); + + // ----------------------------------- + // + // Propagation + // + // ----------------------------------- + protected: + bool bcp(); + + bool propagate_eqs(); + + bool propagate_atoms(); + + void push_new_th_diseqs(enode * r, theory_var v, theory * th); + + void propagate_bool_var_enode(bool_var v); + + bool is_relevant_core(expr * n) const { return m_relevancy_propagator->is_relevant(n); } + + public: + // event handler for relevancy_propagator class + void relevant_eh(expr * n); + + bool is_relevant(expr * n) const { + return !relevancy() || is_relevant_core(n); + } + + bool is_relevant(enode * n) const { + return is_relevant(n->get_owner()); + } + + bool is_relevant(bool_var v) const { + return is_relevant(bool_var2expr(v)); + } + + bool is_relevant(literal l) const { + SASSERT(l != true_literal && l != false_literal); + return is_relevant(l.var()); + } + + void mark_as_relevant(expr * n) { m_relevancy_propagator->mark_as_relevant(n); m_relevancy_propagator->propagate(); } + + void mark_as_relevant(enode * n) { mark_as_relevant(n->get_owner()); } + + void mark_as_relevant(bool_var v) { mark_as_relevant(bool_var2expr(v)); } + + void mark_as_relevant(literal l) { mark_as_relevant(l.var()); } + + template + relevancy_eh * mk_relevancy_eh(Eh const & eh) { + return m_relevancy_propagator->mk_relevancy_eh(eh); + } + + void add_relevancy_eh(expr * source, relevancy_eh * eh) { m_relevancy_propagator->add_handler(source, eh); } + void add_relevancy_dependency(expr * source, expr * target) { m_relevancy_propagator->add_dependency(source, target); } + void add_rel_watch(literal l, relevancy_eh * eh) { m_relevancy_propagator->add_watch(bool_var2expr(l.var()), !l.sign(), eh); } + void add_rel_watch(literal l, expr * n) { m_relevancy_propagator->add_watch(bool_var2expr(l.var()), !l.sign(), n); } + + protected: + lbool get_assignment_core(expr * n) const; + + void propagate_relevancy(unsigned qhead); + + bool propagate_theories(); + + void propagate_th_eqs(); + + void propagate_th_diseqs(); + + bool can_theories_propagate() const; + + bool propagate(); + + public: + bool can_propagate() const; + + // ----------------------------------- + // + // Model checking... (must be improved) + // + // ----------------------------------- + public: + bool get_value(enode * n, expr_ref & value); + + // ----------------------------------- + // + // Pretty Printing + // + // ----------------------------------- + protected: + ast_mark m_pp_visited; + + ast_mark & get_pp_visited() const { return const_cast(m_pp_visited); } + + public: + void display_enode_defs(std::ostream & out) const; + + void display_bool_var_defs(std::ostream & out) const; + + void display_asserted_formulas(std::ostream & out) const; + + void display_literal(std::ostream & out, literal l) const; + + void display_detailed_literal(std::ostream & out, literal l) const { l.display(out, m_manager, m_bool_var2expr.c_ptr()); } + + void display_literal_info(std::ostream & out, literal l) const; + + void display_literals(std::ostream & out, unsigned num_lits, literal const * lits) const; + + void display_literals_verbose(std::ostream & out, unsigned num_lits, literal const * lits) const; + + void display_watch_list(std::ostream & out, literal l) const; + + void display_watch_lists(std::ostream & out) const; + + void display_clause_detail(std::ostream & out, clause const * cls) const; + + void display_clause(std::ostream & out, clause const * cls) const; + + void display_clauses(std::ostream & out, ptr_vector const & v) const; + + void display_binary_clauses(std::ostream & out) const; + + void display_assignment(std::ostream & out) const; + + void display_eqc(std::ostream & out) const; + + void display_app_enode_map(std::ostream & out) const; + + void display_expr_bool_var_map(std::ostream & out) const; + + void display_relevant_exprs(std::ostream & out) const; + + void display_theories(std::ostream & out) const; + + void display_eq_detail(std::ostream & out, enode * n) const; + + void display_parent_eqs(std::ostream & out, enode * n) const; + + void display_hot_bool_vars(std::ostream & out) const; + + void display_lemma_as_smt_problem(std::ostream & out, unsigned num_antecedents, literal const * antecedents, literal consequent = false_literal, const char * logic = "AUFLIRA") const; + + void display_lemma_as_smt_problem(unsigned num_antecedents, literal const * antecedents, literal consequent = false_literal, const char * logic = "AUFLIRA") const; + void display_lemma_as_smt_problem(std::ostream & out, unsigned num_antecedents, literal const * antecedents, + unsigned num_antecedent_eqs, enode_pair const * antecedent_eqs, + literal consequent = false_literal, const char * logic = "AUFLIRA") const; + + void display_lemma_as_smt_problem(unsigned num_antecedents, literal const * antecedents, + unsigned num_antecedent_eqs, enode_pair const * antecedent_eqs, + literal consequent = false_literal, const char * logic = "AUFLIRA") const; + + void display_assignment_as_smtlib2(std::ostream& out, const char * logic = "AUFLIRA") const; + + void display_normalized_enodes(std::ostream & out) const; + + void display_enodes_lbls(std::ostream & out) const; + + void display_decl2enodes(std::ostream & out) const; + + void display_subexprs_info(std::ostream & out, expr * n) const; + + void display_var_occs_histogram(std::ostream & out) const; + + void display_num_min_occs(std::ostream & out) const; + + void display_profile_res_sub(std::ostream & out) const; + + void display_profile(std::ostream & out) const; + + // ----------------------------------- + // + // Debugging support + // + // ----------------------------------- + protected: +#ifdef Z3DEBUG + bool is_watching_clause(literal l, clause const * cls) const; + + bool check_clause(clause const * cls) const; + + bool check_clauses(clause_vector const & v) const; + + bool check_watch_list(literal l) const; + + bool check_watch_list(unsigned l_idx) const; + + bool check_bin_watch_lists() const; + + bool check_enode(enode * n) const; + + bool check_enodes() const; + + bool check_invariant() const; + + bool check_eqc_bool_assignment() const; + + bool check_missing_clause_propagation(clause_vector const & v) const; + + bool check_missing_bin_clause_propagation() const; + + bool check_missing_eq_propagation() const; + + bool check_missing_congruence() const; + + bool check_missing_bool_enode_propagation() const; + + bool check_missing_propagation() const; + + bool check_relevancy(expr_ref_vector const & v) const; + + bool check_relevancy() const; + + bool check_bool_var_vector_sizes() const; + + bool check_th_diseq_propagation() const; + + bool check_missing_diseq_conflict() const; + + bool check_lit_occs(literal l) const; + + bool check_lit_occs() const; +#endif + // ----------------------------------- + // + // Introspection + // + // ----------------------------------- + unsigned get_lemma_avg_activity() const; + void display_literal_num_occs(std::ostream & out) const; + void display_num_assigned_literals_per_lvl(std::ostream & out) const; + + // ----------------------------------- + // + // Auxiliary + // + // ----------------------------------- + void init(); + void flush(); + config_mode get_config_mode(bool use_static_features) const; + virtual void setup_context(bool use_static_features); + void setup_components(void); + void pop_to_base_lvl(); +#ifdef Z3DEBUG + bool already_internalized_theory(theory * th) const; + bool already_internalized_theory_core(theory * th, expr_ref_vector const & s) const; +#endif + bool check_preamble(bool reset_cancel); + void check_finalize(lbool r); + + // ----------------------------------- + // + // API + // + // ----------------------------------- + void assert_expr_core(expr * e, proof * pr); + + public: + context(ast_manager & m, front_end_params & fp, params_ref const & p = params_ref()); + + virtual ~context(); + + /** + \brief Return a new context containing the same theories and simplifier plugins, but with an empty + set of assertions. + + If l == 0, then the logic of this context is used in the new context. + If p == 0, then this->m_params is used + */ + context * mk_fresh(symbol const * l = 0, front_end_params * p = 0); + + app * mk_eq_atom(expr * lhs, expr * rhs); + + bool set_logic(symbol logic) { return m_setup.set_logic(logic); } + + void register_plugin(simplifier_plugin * s); + + void register_plugin(theory * th); + + void assert_expr(expr * e); + + void assert_expr(expr * e, proof * pr); + + void push(); + + void pop(unsigned num_scopes); + + lbool check(unsigned num_assumptions = 0, expr * const * assumptions = 0, bool reset_cancel = true); + + lbool setup_and_check(bool reset_cancel = true); + + lbool get_implied_equalities( + unsigned num_terms, expr* const* terms, unsigned* class_ids, + unsigned num_assumptions = 0, expr * const * assumptions = 0); + + // return 'true' if assertions are inconsistent. + bool reduce_assertions(); + + bool resource_limits_exceeded(); + + failure get_last_search_failure() const; + + proof * get_proof(); + + void get_relevant_labels(expr* cnstr, buffer & result); + + void get_relevant_labeled_literals(bool at_lbls, expr_ref_vector & result); + + void get_relevant_literals(expr_ref_vector & result); + + void get_guessed_literals(expr_ref_vector & result); + + void internalize_assertion(expr * n, proof * pr, unsigned generation); + + void internalize_instance(expr * body, proof * pr, unsigned generation) { + internalize_assertion(body, pr, generation); +#ifndef SMTCOMP + if (relevancy()) + m_case_split_queue->internalize_instance_eh(body, generation); +#endif + } + + bool already_internalized() const { return m_e_internalized_stack.size() > 2 || m_b_internalized_stack.size() > 1; } + + unsigned get_unsat_core_size() const { + return m_unsat_core.size(); + } + + expr * get_unsat_core_expr(unsigned idx) const { + return m_unsat_core.get(idx); + } + + void get_model(model_ref & m) const; + + void get_proto_model(proto_model_ref & m) const; + + unsigned get_num_asserted_formulas() const { return m_asserted_formulas.get_num_formulas(); } + + unsigned get_asserted_formulas_last_level() const { return m_asserted_formulas.get_formulas_last_level(); } + + expr * get_asserted_formula(unsigned idx) const { return m_asserted_formulas.get_formula(idx); } + + proof * get_asserted_formula_proof(unsigned idx) const { return m_asserted_formulas.get_formula_proof(idx); } + + expr * const * get_asserted_formulas() const { return m_asserted_formulas.get_formulas(); } + + proof * const * get_asserted_formula_proofs() const { return m_asserted_formulas.get_formula_proofs(); } + + void get_assumptions_core(ptr_vector & result); + + void get_assertions(ptr_vector & result) { m_asserted_formulas.get_assertions(result); } + + void display(std::ostream & out) const; + + void display_unsat_core(std::ostream & out) const; + + void collect_statistics(::statistics & st) const; + + void display_statistics(std::ostream & out) const; + void display_istatistics(std::ostream & out) const; + + // ----------------------------------- + // + // Macros + // + // ----------------------------------- + public: + unsigned get_num_macros() const { return m_asserted_formulas.get_num_macros(); } + unsigned get_first_macro_last_level() const { return m_asserted_formulas.get_first_macro_last_level(); } + func_decl * get_macro_func_decl(unsigned i) const { return m_asserted_formulas.get_macro_func_decl(i); } + func_decl * get_macro_interpretation(unsigned i, expr_ref & interp) const { return m_asserted_formulas.get_macro_interpretation(i, interp); } + quantifier * get_macro_quantifier(func_decl * f) const { return m_asserted_formulas.get_macro_quantifier(f); } + void insert_macro(func_decl * f, quantifier * m, proof * pr) { m_asserted_formulas.insert_macro(f, m, pr); } + + // ----------------------------------- + // + // Eliminated vars + // + // ----------------------------------- + public: + ptr_vector::const_iterator begin_subst_vars() const { return m_asserted_formulas.begin_subst_vars(); } + ptr_vector::const_iterator end_subst_vars() const { return m_asserted_formulas.end_subst_vars(); } + ptr_vector::const_iterator begin_subst_vars_last_level() const { return m_asserted_formulas.begin_subst_vars_last_level(); } + expr * get_subst(app * var) { return m_asserted_formulas.get_subst(var); } + bool is_subst(app * var) const { return m_asserted_formulas.is_subst(var); } + void get_ordered_subst_vars(ptr_vector & ordered_vars) { return m_asserted_formulas.get_ordered_subst_vars(ordered_vars); } + }; + +}; + +#endif /* _SMT_CONTEXT_H_ */ + diff --git a/lib/smt_context_inv.cpp b/lib/smt_context_inv.cpp new file mode 100644 index 000000000..a1f4bdd16 --- /dev/null +++ b/lib/smt_context_inv.cpp @@ -0,0 +1,403 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_context_inv.cpp + +Abstract: + + SMT logical contexts: invariant + +Author: + + Leonardo de Moura (leonardo) 2008-02-21. + +Revision History: + +--*/ +#include"smt_context.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"ast_smt2_pp.h" + +namespace smt { + +#ifdef Z3DEBUG + bool context::is_watching_clause(literal l, clause const * cls) const { + watch_list & wl = const_cast(m_watches[l.index()]); + return wl.find_clause(cls) != wl.end_clause(); + } + + bool context::check_clause(clause const * cls) const { + SASSERT(is_watching_clause(~cls->get_literal(0), cls)); + SASSERT(is_watching_clause(~cls->get_literal(1), cls)); + if (lit_occs_enabled()) { + unsigned num_lits = cls->get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) { + literal l = cls->get_literal(i); + SASSERT(m_lit_occs[l.index()].contains(const_cast(cls))); + } + } + return true; + } + + bool context::check_clauses(clause_vector const & v) const { + clause_vector::const_iterator it = v.begin(); + clause_vector::const_iterator end = v.end(); + for (; it != end; ++it) { + clause * cls = *it; + if (!cls->deleted()) + check_clause(cls); + } + return true; + } + + bool context::check_watch_list(literal l) const { + watch_list & wl = const_cast(m_watches[l.index()]); + l.neg(); + watch_list::clause_iterator it = wl.begin_clause(); + watch_list::clause_iterator end = wl.end_clause(); + for (; it != end; ++it) { + clause * cls = *it; + TRACE("watch_list", tout << "l: "; display_literal(tout, l); tout << "\n"; + display_clause(tout, cls); tout << "\n";); + SASSERT(l == cls->get_literal(0) || l == cls->get_literal(1)); + } + return true; + } + + bool context::check_watch_list(unsigned l_idx) const { + return check_watch_list(to_literal(l_idx)); + } + + bool context::check_bin_watch_lists() const { + if (binary_clause_opt_enabled()) { + vector::const_iterator it = m_watches.begin(); + vector::const_iterator end = m_watches.end(); + for (unsigned l_idx = 0; it != end; ++it, ++l_idx) { + literal l1 = to_literal(l_idx); + watch_list const & wl = *it; + literal const * it2 = wl.begin_literals(); + literal const * end2 = wl.end_literals(); + for (; it2 != end2; ++it2) { + literal l2 = *it2; + watch_list const & wl = m_watches[(~l2).index()]; + SASSERT(wl.find_literal(~l1) != wl.end_literals()); + } + } + } + return true; + } + + bool context::check_lit_occs(literal l) const { + clause_set const & v = m_lit_occs[l.index()]; + clause_set::iterator it = v.begin(); + clause_set::iterator end = v.end(); + for (; it != end; ++it) { + clause * cls = *it; + unsigned num = cls->get_num_literals(); + unsigned i = 0; + for (; i < num; i++) + if (cls->get_literal(i) == l) + break; + CTRACE("lit_occs", !(i < num), tout << i << " " << num << "\n"; display_literal(tout, l); tout << "\n"; + display_clause(tout, cls); tout << "\n"; + tout << "l: " << l.index() << " cls: "; + for (unsigned j = 0; j < num; j++) { + tout << cls->get_literal(j).index() << " "; + } + tout << "\n"; + display_clause_detail(tout, cls); tout << "\n";); + SASSERT(i < num); + } + return true; + } + + bool context::check_lit_occs() const { + if (lit_occs_enabled()) { + unsigned num_lits = get_num_bool_vars() * 2; + for (unsigned l_idx = 0; l_idx < num_lits; ++l_idx) { + check_lit_occs(to_literal(l_idx)); + } + } + return true; + } + + bool context::check_enode(enode * n) const { + SASSERT(n->check_invariant()); + bool is_true_eq = n->is_true_eq(); + bool cg_inv = + n->get_num_args() == 0 || + (!is_true_eq && (!n->is_cgc_enabled() || n->is_cgr() == (m_cg_table.contains_ptr(n)))) || + (is_true_eq && !m_cg_table.contains_ptr(n)); + CTRACE("check_enode", !cg_inv, + tout << "n: #" << n->get_owner_id() << ", m_cg: #" << n->m_cg->get_owner_id() << ", contains: " << m_cg_table.contains(n) << "\n"; display(tout);); + SASSERT(cg_inv); + return true; + } + + bool context::check_enodes() const { + ptr_vector::const_iterator it = m_enodes.begin(); + ptr_vector::const_iterator end = m_enodes.end(); + for (; it != end; ++it) { + check_enode(*it); + } + return true; + } + + bool context::check_invariant() const { + check_lit_occs(); + check_bin_watch_lists(); + check_clauses(m_aux_clauses); + check_clauses(m_lemmas); + check_enodes(); + SASSERT(m_cg_table.check_invariant()); + return true; + } + + bool context::check_missing_clause_propagation(clause_vector const & v) const { + clause_vector::const_iterator it = v.begin(); + clause_vector::const_iterator end = v.end(); + for (; it != end; ++it) { + CTRACE("missing_propagation", is_unit_clause(*it), display_clause_detail(tout, *it); tout << "\n";); + SASSERT(!is_unit_clause(*it)); + } + return true; + } + + bool context::check_missing_bin_clause_propagation() const { + if (binary_clause_opt_enabled()) { + SASSERT(m_watches.size() == m_assignment.size()); + vector::const_iterator it = m_watches.begin(); + vector::const_iterator end = m_watches.end(); + for (unsigned l_idx = 0; it != end; ++it, ++l_idx) { + literal l = to_literal(l_idx); + watch_list const & wl = *it; + if (get_assignment(l) == l_true) { + literal const * it2 = wl.begin_literals(); + literal const * end2 = wl.end_literals(); + for (; it2 != end2; ++it2) { + literal l2 = *it2; + SASSERT(get_assignment(l2) == l_true); + } + } + } + } + return true; + } + + bool context::check_missing_eq_propagation() const { + ptr_vector::const_iterator it = m_enodes.begin(); + ptr_vector::const_iterator end = m_enodes.end(); + for (; it != end; ++it) { + enode * n = *it; + SASSERT(!n->is_true_eq() || get_assignment(n) == l_true); + if (n->is_eq() && get_assignment(n) == l_true) { + SASSERT(n->is_true_eq()); + } + } + return true; + } + + bool context::check_missing_congruence() const { + ptr_vector::const_iterator it = m_enodes.begin(); + ptr_vector::const_iterator end = m_enodes.end(); + for (; it != end; ++it) { + enode * n = *it; + ptr_vector::const_iterator it2 = m_enodes.begin(); + for (; it2 != end; ++it2) { + enode * n2 = *it2; + if (n->get_root() != n2->get_root()) { + if (n->is_true_eq() && n2->is_true_eq()) + continue; + CTRACE("missing_propagation", congruent(n, n2), + tout << mk_pp(n->get_owner(), m_manager) << "\n" << mk_pp(n2->get_owner(), m_manager) << "\n"; + display(tout);); + SASSERT(!congruent(n, n2)); + } + } + } + return true; + } + + bool context::check_missing_bool_enode_propagation() const { + ptr_vector::const_iterator it = m_enodes.begin(); + ptr_vector::const_iterator end = m_enodes.end(); + for (; it != end; ++it) { + enode * n = *it; + if (m_manager.is_bool(n->get_owner()) && get_assignment(n) == l_undef) { + enode * first = n; + do { + CTRACE("missing_propagation", get_assignment(n) != l_undef, + tout << mk_pp(first->get_owner(), m_manager) << "\nassignment: " << get_assignment(first) << "\n" + << mk_pp(n->get_owner(), m_manager) << "\nassignment: " << get_assignment(n) << "\n";); + SASSERT(get_assignment(n) == l_undef); + n = n->get_next(); + } + while (n != first); + } + } + return true; + } + + bool context::check_missing_propagation() const { + check_missing_clause_propagation(m_lemmas); + check_missing_clause_propagation(m_aux_clauses); + check_missing_bin_clause_propagation(); + // check_missing_eq_propagation(); + check_missing_congruence(); + check_missing_bool_enode_propagation(); + return true; + } + + bool context::check_relevancy(expr_ref_vector const & v) const { + return m_relevancy_propagator->check_relevancy(v); + } + + bool context::check_relevancy() const { + if (!relevancy()) + return true; + check_relevancy(m_b_internalized_stack); + check_relevancy(m_e_internalized_stack); + unsigned sz = m_asserted_formulas.get_num_formulas(); + for (unsigned i = 0; i < sz; i++) { + expr * n = m_asserted_formulas.get_formula(i); + if (m_manager.is_or(n)) { + CTRACE("relevancy_bug", !is_relevant(n), tout << "n: " << mk_ismt2_pp(n, m_manager) << "\n";); + SASSERT(is_relevant(n)); + TRACE("check_relevancy", tout << "checking:\n" << mk_ll_pp(n, m_manager) << "\n";); + SASSERT(m_relevancy_propagator->check_relevancy_or(to_app(n), true)); + } + else if (m_manager.is_not(n)) { + CTRACE("relevancy_bug", !is_relevant(to_app(n)->get_arg(0)), tout << "n: " << mk_ismt2_pp(n, m_manager) << "\n";); + SASSERT(is_relevant(to_app(n)->get_arg(0))); + } + else { + CTRACE("relevancy_bug", !is_relevant(n), tout << "n: " << mk_ismt2_pp(n, m_manager) << "\n";); + SASSERT(is_relevant(n)); + } + } + return true; + } + + /** + \brief Check if expressions attached to bool_variables and enodes have a consistent assignment. + For all a, b. root(a) == root(b) ==> get_assignment(a) == get_assignment(b) + */ + bool context::check_eqc_bool_assignment() const { + ptr_vector::const_iterator it = m_enodes.begin(); + ptr_vector::const_iterator end = m_enodes.end(); + for (; it != end; ++it) { + enode * e = *it; + if (m_manager.is_bool(e->get_owner())) { + enode * r = e->get_root(); + CTRACE("eqc_bool", get_assignment(e) != get_assignment(r), + tout << "#" << e->get_owner_id() << "\n" << mk_pp(e->get_owner(), m_manager) << "\n"; + tout << "#" << r->get_owner_id() << "\n" << mk_pp(r->get_owner(), m_manager) << "\n"; + tout << "assignments: " << get_assignment(e) << " " << get_assignment(r) << "\n"; + display(tout);); + SASSERT(get_assignment(e) == get_assignment(r)); + } + } + return true; + } + + bool context::check_bool_var_vector_sizes() const { + SASSERT(m_assignment.size() == 2 * m_bdata.size()); + SASSERT(m_watches.size() == 2 * m_bdata.size()); + SASSERT(m_bdata.size() == m_activity.size()); + SASSERT(m_bool_var2expr.size() == m_bdata.size()); + return true; + } + + /** + \brief Check the following property: + + - for every equality atom (= lhs rhs) assigned to false, relevant: + if lhs->get_root() and rhs->get_root() are attached to theory variables v1 and v2 of theory t, + then there is an entry (t, v1', v2') in m_propagated_th_diseqs such that, + (= get_enode(v1') get_enode(v2')) is congruent to (= lhs rhs). + */ + bool context::check_th_diseq_propagation() const { + TRACE("check_th_diseq_propagation", tout << "m_propagated_th_diseqs.size() " << m_propagated_th_diseqs.size() << "\n";); + int num = get_num_bool_vars(); + for (bool_var v = 0; v < num; v++) { + if (has_enode(v)) { + enode * n = bool_var2enode(v); + if (n->is_eq() && is_relevant(n) && get_assignment(v) == l_false) { + TRACE("check_th_diseq_propagation", tout << "checking: #" << n->get_owner_id() << " " << mk_bounded_pp(n->get_owner(), m_manager) << "\n";); + enode * lhs = n->get_arg(0)->get_root(); + enode * rhs = n->get_arg(1)->get_root(); + if (rhs->is_interpreted() && lhs->is_interpreted()) + continue; + TRACE("check_th_diseq_propagation", tout << "num. theory_vars: " << lhs->get_num_th_vars() << " " + << mk_pp(m_manager.get_sort(lhs->get_owner()), m_manager) << "\n";); + theory_var_list * l = lhs->get_th_var_list(); + while (l) { + theory_id th_id = l->get_th_id(); + theory * th = get_theory(th_id); + TRACE("check_th_diseq_propagation", tout << "checking theory: " << m_manager.get_family_name(th_id) << "\n";); + // if the theory doesn't use diseqs, then the diseqs are not propagated. + if (th->use_diseqs() && rhs->get_th_var(th_id) != null_theory_var) { + // lhs and rhs are attached to theory th_id + svector::const_iterator it = m_propagated_th_diseqs.begin(); + svector::const_iterator end = m_propagated_th_diseqs.end(); + for (; it != end; ++it) { + if (it->m_th_id == th_id) { + enode * lhs_prime = th->get_enode(it->m_lhs)->get_root(); + enode * rhs_prime = th->get_enode(it->m_rhs)->get_root(); + TRACE("check_th_diseq_propagation", + tout << m_manager.get_family_name(it->m_th_id) << "\n";); + + if ((lhs == lhs_prime && rhs == rhs_prime) || + (rhs == lhs_prime && lhs == rhs_prime)) { + TRACE("check_th_diseq_propagation", tout << "ok v" << v << " " << get_assignment(v) << "\n";); + break; + } + } + } + if (it == end) { + // missed theory diseq propagation + display(std::cout); + std::cout << "checking theory: " << m_manager.get_family_name(th_id) << "\n"; + std::cout << "root: #" << n->get_root()->get_owner_id() << " node: #" << n->get_owner_id() << "\n"; + std::cout << mk_pp(n->get_owner(), m_manager) << "\n"; + std::cout << "lhs: #" << lhs->get_owner_id() << ", rhs: #" << rhs->get_owner_id() << "\n"; + std::cout << mk_bounded_pp(lhs->get_owner(), m_manager) << " " << mk_bounded_pp(rhs->get_owner(), m_manager) << "\n"; + } + + SASSERT(it != end); + } + l = l->get_next(); + } + } + } + } + return true; + } + + bool context::check_missing_diseq_conflict() const { + svector::const_iterator it = m_diseq_vector.begin(); + svector::const_iterator end = m_diseq_vector.end(); + for (; it != end; ++it) { + enode * n1 = it->first; + enode * n2 = it->second; + if (n1->get_root() == n2->get_root()) { + TRACE("diseq_bug", + tout << "n1: #" << n1->get_owner_id() << ", n2: #" << n2->get_owner_id() << + ", r: #" << n1->get_root()->get_owner_id() << "\n"; + tout << "n1 parents:\n"; display_parent_eqs(tout, n1); + tout << "n2 parents:\n"; display_parent_eqs(tout, n2); + tout << "r parents:\n"; display_parent_eqs(tout, n1->get_root()); + ); + UNREACHABLE(); + } + } + return true; + } + +#endif + +}; + diff --git a/lib/smt_context_pp.cpp b/lib/smt_context_pp.cpp new file mode 100644 index 000000000..4bc5dfd74 --- /dev/null +++ b/lib/smt_context_pp.cpp @@ -0,0 +1,621 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_context_pp.cpp + +Abstract: + + SMT logical context: pretty printing + +Author: + + Leonardo de Moura (leonardo) 2008-02-21. + +Revision History: + +--*/ +#include"smt_context.h" +#include"ast_ll_pp.h" +#include"ast_pp.h" +#include"ast_smt_pp.h" +#include"stats.h" + +namespace smt { + + + std::ostream& context::display_last_failure(std::ostream& out) const { + switch(m_last_search_failure) { + case OK: + return out << "OK"; + case UNKNOWN: + return out << "UNKNOWN"; + case TIMEOUT: + return out << "TIMEOUT"; + case MEMOUT: + return out << "MEMOUT"; + case CANCELED: + return out << "CANCELED"; + case NUM_CONFLICTS: + return out << "NUM_CONFLICTS"; + case THEORY: + if (!m_incomplete_theories.empty()) { + ptr_vector::const_iterator it = m_incomplete_theories.begin(); + ptr_vector::const_iterator end = m_incomplete_theories.end(); + for (bool first = true; it != end; ++it) { + if (first) first = false; else out << " "; + out << (*it)->get_name(); + } + } + else { + out << "THEORY"; + } + return out; + case QUANTIFIERS: + return out << "QUANTIFIERS"; + } + UNREACHABLE(); + return out << "?"; + } + + std::string context::last_failure_as_string() const { + std::string r; + switch(m_last_search_failure) { + case OK: r = "ok"; break; + case TIMEOUT: r = "timeout"; break; + case MEMOUT: r = "memout"; break; + case CANCELED: r = "canceled"; break; + case NUM_CONFLICTS: r = "max-conflicts-reached"; break; + case THEORY: { + r = "(incomplete (theory"; + ptr_vector::const_iterator it = m_incomplete_theories.begin(); + ptr_vector::const_iterator end = m_incomplete_theories.end(); + for (; it != end; ++it) { + r += " "; + r += (*it)->get_name(); + } + r += "))"; + break; + } + case QUANTIFIERS: r = "(incomplete quantifiers)"; break; + case UNKNOWN: r = "incomplete"; break; + } + return r; + } + + void context::display_asserted_formulas(std::ostream & out) const { + m_asserted_formulas.display_ll(out, get_pp_visited()); + } + + void context::display_literal(std::ostream & out, literal l) const { + l.display_compact(out, m_bool_var2expr.c_ptr()); + } + + void context::display_literals(std::ostream & out, unsigned num_lits, literal const * lits) const { + display_compact(out, num_lits, lits, m_bool_var2expr.c_ptr()); + } + + void context::display_literals_verbose(std::ostream & out, unsigned num_lits, literal const * lits) const { + display_verbose(out, m_manager, num_lits, lits, m_bool_var2expr.c_ptr()); + } + + void context::display_literal_info(std::ostream & out, literal l) const { + l.display_compact(out, m_bool_var2expr.c_ptr()); + if (l.sign()) + out << " (not " << mk_bounded_pp(bool_var2expr(l.var()), m_manager, 10) << ")\n"; + else + out << " " << mk_bounded_pp(bool_var2expr(l.var()), m_manager, 10) << "\n"; + out << "relevant: " << is_relevant(bool_var2expr(l.var())) << ", val: " << get_assignment(l) << "\n"; + } + + void context::display_watch_list(std::ostream & out, literal l) const { + display_literal(out, l); out << " watch_list:\n"; + watch_list & wl = const_cast(m_watches[l.index()]); + watch_list::clause_iterator it = wl.begin_clause(); + watch_list::clause_iterator end = wl.end_clause(); + for (; it != end; ++it) { + display_clause(out, *it); out << "\n"; + } + } + + void context::display_watch_lists(std::ostream & out) const { + unsigned s = m_watches.size(); + for (unsigned l_idx = 0; l_idx < s; l_idx++) { + literal l = to_literal(l_idx); + display_watch_list(out, l); + out << "\n"; + } + } + + void context::display_enode_defs(std::ostream & out) const { + ptr_vector::const_iterator it = m_enodes.begin(); + ptr_vector::const_iterator end = m_enodes.end(); + for (; it != end; ++it) { + expr * n = (*it)->get_owner(); + ast_def_ll_pp(out, m_manager, n, get_pp_visited(), true, false); + } + } + + void context::display_bool_var_defs(std::ostream & out) const { + unsigned num = get_num_bool_vars(); + for (unsigned v = 0; v < num; v++) { + expr * n = m_bool_var2expr[v]; + ast_def_ll_pp(out, m_manager, n, get_pp_visited(), true, false); + } + } + + void context::display_clause_detail(std::ostream & out, clause const * cls) const { + out << "lemma: " << cls->is_lemma() << "\n"; + unsigned num_lits = cls->get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) { + literal l = cls->get_literal(i); + display_literal(out, l); + out << ", val: " << get_assignment(l) << ", lvl: " << get_assign_level(l) + << ", ilvl: " << get_intern_level(l.var()) << ", var: " << l.var() << "\n" + << mk_pp(bool_var2expr(l.var()), m_manager) << "\n\n"; + } + } + + void context::display_clause(std::ostream & out, clause const * cls) const { + cls->display_compact(out, m_manager, m_bool_var2expr.c_ptr()); + } + + void context::display_clauses(std::ostream & out, ptr_vector const & v) const { + ptr_vector::const_iterator it = v.begin(); + ptr_vector::const_iterator end = v.end(); + for (; it != end; ++it) { + display_clause(out, *it); + out << "\n"; + } + } + + void context::display_binary_clauses(std::ostream & out) const { + bool first = true; + vector::const_iterator it = m_watches.begin(); + vector::const_iterator end = m_watches.end(); + for (unsigned l_idx = 0; it != end; ++it, ++l_idx) { + literal l1 = to_literal(l_idx); + literal neg_l1 = ~l1; + watch_list const & wl = *it; + literal const * it2 = wl.begin_literals(); + literal const * end2 = wl.end_literals(); + for (; it2 != end2; ++it2) { + literal l2 = *it2; + if (l1.index() < l2.index()) { + if (first) { + out << "binary clauses:\n"; + first = false; + } + out << "(clause "; + display_literal(out, neg_l1); + out << " "; + display_literal(out, l2); + out << ")\n"; + } + } + } + } + + void context::display_assignment(std::ostream & out) const { + if (!m_assigned_literals.empty()) { + out << "current assignment:\n"; + literal_vector::const_iterator it = m_assigned_literals.begin(); + literal_vector::const_iterator end = m_assigned_literals.end(); + for (; it != end; ++it) { + display_literal(out, *it); + out << " "; + } + out << "\n"; + } + } + + void context::display_assignment_as_smtlib2(std::ostream& out, char const* logic) const { + ast_smt_pp pp(m_manager); + pp.set_benchmark_name("lemma"); + pp.set_status("unknown"); + pp.set_logic(logic); + literal_vector::const_iterator it = m_assigned_literals.begin(); + literal_vector::const_iterator end = m_assigned_literals.end(); + for (; it != end; ++it) { + expr_ref n(m_manager); + literal2expr(*it, n); + pp.add_assumption(n); + } + pp.display_smt2(out, m_manager.mk_true()); + } + + void context::display_eqc(std::ostream & out) const { + bool first = true; + ptr_vector::const_iterator it = m_enodes.begin(); + ptr_vector::const_iterator end = m_enodes.end(); + for (; it != end; ++it) { + expr * n = (*it)->get_owner(); + expr * r = (*it)->get_root()->get_owner(); + if (n != r) { + if (first) { + out << "equivalence classes:\n"; + first = false; + } + out << "#" << n->get_id() << " -> #" << r->get_id() << "\n"; + } + } + } + + void context::display_app_enode_map(std::ostream & out) const { + if (!m_e_internalized_stack.empty()) { + out << "expresion -> enode:\n"; + unsigned sz = m_e_internalized_stack.size(); + for (unsigned i = 0; i < sz; i++) { + expr * n = m_e_internalized_stack.get(i); + out << "(#" << n->get_id() << " -> e!" << i << ") "; + } + out << "\n"; + } + } + + void context::display_expr_bool_var_map(std::ostream & out) const { + if (!m_b_internalized_stack.empty()) { + out << "expresion -> bool_var:\n"; + unsigned sz = m_b_internalized_stack.size(); + for (unsigned i = 0; i < sz; i++) { + expr * n = m_b_internalized_stack.get(i); + bool_var v = get_bool_var_of_id(n->get_id()); + out << "(#" << n->get_id() << " -> p!" << v << ") "; + } + out << "\n"; + } + } + + void context::display_hot_bool_vars(std::ostream & out) const { + out << "hot bool vars:\n"; + int num = get_num_bool_vars(); + for (bool_var v = 0; v < num; v++) { + double val = get_activity(v)/m_bvar_inc; + if (val > 10.00) { + expr * n = m_b_internalized_stack.get(v); + out << "#"; + out.width(5); + out << std::left; + out << n->get_id(); + out << " "; + out.width(12); + out << std::right; + out << get_activity(v) << " "; + out.width(12); + out << val; + out << "\n"; + } + } + } + + void context::display_relevant_exprs(std::ostream & out) const { + m_relevancy_propagator->display(out); + } + + void context::display_theories(std::ostream & out) const { + ptr_vector::const_iterator it = m_theory_set.begin(); + ptr_vector::const_iterator end = m_theory_set.end(); + for (; it != end; ++it) { + theory * th = *it; + th->display(out); + } + } + + void context::display(std::ostream & out) const { + get_pp_visited().reset(); + out << "Logical context:\n"; + out << "scope-lvl: " << m_scope_lvl << "\n"; + out << "base-lvl: " << m_base_lvl << "\n"; + out << "search-lvl: " << m_search_lvl << "\n"; + out << "inconsistent(): " << inconsistent() << "\n"; + out << "m_asserted_formulas.inconsistent(): " << m_asserted_formulas.inconsistent() << "\n"; + display_bool_var_defs(out); + display_enode_defs(out); + display_asserted_formulas(out); + if (!m_aux_clauses.empty()) { + out << "auxiliary clauses:\n"; + display_clauses(out, m_aux_clauses); + } + if (!m_lemmas.empty()) { + out << "lemmas:\n"; + display_clauses(out, m_lemmas); + } + display_binary_clauses(out); + display_assignment(out); + display_eqc(out); + m_cg_table.display_compact(out); + m_case_split_queue->display(out); + display_expr_bool_var_map(out); + display_app_enode_map(out); + display_relevant_exprs(out); + display_theories(out); + display_decl2enodes(out); + display_hot_bool_vars(out); + } + + void context::display_eq_detail(std::ostream & out, enode * n) const { + SASSERT(n->is_eq()); + out << "#" << n->get_owner_id() + << ", root: #" << n->get_root()->get_owner_id() + << ", cg: #" << n->m_cg->get_owner_id() + << ", val: " << get_assignment(enode2bool_var(n)) + << ", lhs: #" << n->get_arg(0)->get_owner_id() + << ", rhs: #" << n->get_arg(1)->get_owner_id() + << ", lhs->root: #" << n->get_arg(0)->get_root()->get_owner_id() + << ", rhs->root: #" << n->get_arg(1)->get_root()->get_owner_id() + << ", is_marked: " << n->is_marked() + << ", is_relevant: " << is_relevant(n) + << ", iscope_lvl: " << n->get_iscope_lvl() << "\n"; + } + + void context::display_parent_eqs(std::ostream & out, enode * n) const { + enode_vector::iterator it = n->begin_parents(); + enode_vector::iterator end = n->end_parents(); + for (; it != end; ++it) { + enode * parent = *it; + if (parent->is_eq()) + display_eq_detail(out, parent); + } + } + + void context::display_unsat_core(std::ostream & out) const { + unsigned sz = m_unsat_core.size(); + for (unsigned i = 0; i < sz; i++) + out << mk_pp(m_unsat_core.get(i), m_manager) << "\n"; + } + + void context::collect_statistics(::statistics & st) const { + st.update("conflicts", m_stats.m_num_conflicts); + st.update("decisions", m_stats.m_num_decisions); + st.update("propagations", m_stats.m_num_propagations + m_stats.m_num_bin_propagations); + st.update("binary propagations", m_stats.m_num_bin_propagations); + st.update("restarts", m_stats.m_num_restarts); + st.update("final checks", m_stats.m_num_final_checks); + st.update("added eqs", m_stats.m_num_add_eq); + st.update("mk clause", m_stats.m_num_mk_clause); + st.update("del clause", m_stats.m_num_del_clause); + st.update("dyn ack", m_stats.m_num_dyn_ack); + st.update("interface eqs", m_stats.m_num_interface_eqs); + st.update("max generation", m_stats.m_max_generation); + st.update("minimized lits", m_stats.m_num_minimized_lits); + st.update("num checks", m_stats.m_num_checks); +#if 0 + // missing? + st.update("sat conflicts", m_stats.m_num_sat_conflicts); + st.update("mk bool var", m_stats.m_num_mk_bool_var); + st.update("del bool var", m_stats.m_num_del_bool_var); + st.update("mk enode", m_stats.m_num_mk_enode); + st.update("del enode", m_stats.m_num_del_enode); + st.update("mk bin clause", m_stats.m_num_mk_bin_clause); + st.update("mk lit", m_stats.m_num_mk_lits); + st.update("backwd subs", m_stats.m_num_bs); + st.update("backwd subs res", m_stats.m_num_bsr); + st.update("frwrd subs res", m_stats.m_num_fsr); +#endif + m_qmanager->collect_statistics(st); + m_asserted_formulas.collect_statistics(st); + ptr_vector::const_iterator it = m_theory_set.begin(); + ptr_vector::const_iterator end = m_theory_set.end(); + for (; it != end; ++it) { + (*it)->collect_statistics(st); + } + } + + void context::display_statistics(std::ostream & out) const { + ::statistics st; + collect_statistics(st); + st.display(out); + } + + void context::display_istatistics(std::ostream & out) const { + ::statistics st; + collect_statistics(st); + st.display_internal(out); + } + + void context::display_lemma_as_smt_problem(std::ostream & out, unsigned num_antecedents, literal const * antecedents, literal consequent, const char * logic) const { + ast_smt_pp pp(m_manager); + pp.set_benchmark_name("lemma"); + pp.set_status("unsat"); + pp.set_logic(logic); + for (unsigned i = 0; i < num_antecedents; i++) { + literal l = antecedents[i]; + expr_ref n(m_manager); + literal2expr(l, n); + pp.add_assumption(n); + } + expr_ref n(m_manager); + literal2expr(~consequent, n); + pp.display(out, n); + } + + static unsigned g_lemma_id = 0; + +#define BUFFER_SZ 128 + + void context::display_lemma_as_smt_problem(unsigned num_antecedents, literal const * antecedents, literal consequent, const char * logic) const { + char buffer[BUFFER_SZ]; +#ifdef _WINDOWS + sprintf_s(buffer, BUFFER_SZ, "lemma_%d.smt", g_lemma_id); +#else + sprintf(buffer, "lemma_%d.smt", g_lemma_id); +#endif + std::ofstream out(buffer); + display_lemma_as_smt_problem(out, num_antecedents, antecedents, consequent, logic); + out.close(); + g_lemma_id++; + } + + void context::display_lemma_as_smt_problem(std::ostream & out, unsigned num_antecedents, literal const * antecedents, + unsigned num_eq_antecedents, enode_pair const * eq_antecedents, + literal consequent, const char * logic) const { + ast_smt_pp pp(m_manager); + pp.set_benchmark_name("lemma"); + pp.set_status("unsat"); + pp.set_logic(logic); + for (unsigned i = 0; i < num_antecedents; i++) { + literal l = antecedents[i]; + expr_ref n(m_manager); + literal2expr(l, n); + pp.add_assumption(n); + } + for (unsigned i = 0; i < num_eq_antecedents; i++) { + enode_pair const & p = eq_antecedents[i]; + expr_ref eq(m_manager); + eq = m_manager.mk_eq(p.first->get_owner(), p.second->get_owner()); + pp.add_assumption(eq); + } + expr_ref n(m_manager); + literal2expr(~consequent, n); + pp.display(out, n); + } + + void context::display_lemma_as_smt_problem(unsigned num_antecedents, literal const * antecedents, + unsigned num_eq_antecedents, enode_pair const * eq_antecedents, + literal consequent, const char * logic) const { + char buffer[BUFFER_SZ]; +#ifdef _WINDOWS + sprintf_s(buffer, BUFFER_SZ, "lemma_%d.smt", g_lemma_id); +#else + sprintf(buffer, "lemma_%d.smt", g_lemma_id); +#endif + std::ofstream out(buffer); + display_lemma_as_smt_problem(out, num_antecedents, antecedents, num_eq_antecedents, eq_antecedents, consequent, logic); + out.close(); + g_lemma_id++; + } + + /** + \brief Display enode definitions #n := (f #i_1 ... #i_n), where #i_k is the root + of the k-th argument of the enode #n. + */ + void context::display_normalized_enodes(std::ostream & out) const { + out << "normalized enodes:\n"; + ptr_vector::const_iterator it = m_enodes.begin(); + ptr_vector::const_iterator end = m_enodes.end(); + for (; it != end; ++it) { + enode * n = *it; + out << "#"; + out.width(5); + out << std::left << n->get_owner_id() << " #"; + out.width(5); + out << n->get_root()->get_owner_id() << " := " << std::right; + unsigned num = n->get_owner()->get_num_args(); + if (num > 0) + out << "("; + out << n->get_decl()->get_name(); + if (!n->get_decl()->private_parameters()) + display_parameters(out, n->get_decl()->get_num_parameters(), n->get_decl()->get_parameters()); + for (unsigned i = 0; i < num; i++) { + expr * arg = n->get_owner()->get_arg(i); + if (e_internalized(arg)) { + enode * n = get_enode(arg)->get_root(); + out << " #" << n->get_owner_id(); + } + else { + out << " #" << arg->get_id(); + } + } + if (num > 0) + out << ")"; + if (is_relevant(n)) + out << "\t*"; + out << "\n"; + } + } + + void context::display_enodes_lbls(std::ostream & out) const { + ptr_vector::const_iterator it = m_enodes.begin(); + ptr_vector::const_iterator end = m_enodes.end(); + for (; it != end; ++it) { + enode * n = *it; + n->display_lbls(out); + } + } + + void context::display_decl2enodes(std::ostream & out) const { + out << "decl2enodes:\n"; + vector::const_iterator it1 = m_decl2enodes.begin(); + vector::const_iterator end1 = m_decl2enodes.end(); + for (unsigned id = 0; it1 != end1; ++it1, ++id) { + enode_vector const & v = *it1; + if (!v.empty()) { + out << "id " << id << " ->"; + enode_vector::const_iterator it2 = v.begin(); + enode_vector::const_iterator end2 = v.end(); + for (; it2 != end2; ++it2) + out << " #" << (*it2)->get_owner_id(); + out << "\n"; + } + } + } + + void context::display_subexprs_info(std::ostream & out, expr * n) const { + ptr_buffer todo; + todo.push_back(n); + while (!todo.empty()) { + expr * n = todo.back(); + todo.pop_back(); + out << "#"; + out.width(6); + out << std::left << n->get_id(); + out << ", relevant: " << is_relevant(n); + if (m_manager.is_bool(n)) { + out << ", val: "; + out.width(7); + out << std::right; + if (lit_internalized(n)) + out << get_assignment(n); + else + out << "l_undef"; + } + if (e_internalized(n)) { + enode * e = get_enode(n); + out << ", root: #" << e->get_root()->get_owner_id(); + } + out << "\n"; + if (is_app(n)) { + for (unsigned i = 0; i < to_app(n)->get_num_args(); i++) + todo.push_back(to_app(n)->get_arg(i)); + } + } + } + + void context::trace_assign(literal l, b_justification j, bool decision) const { + std::ostream & out = *m_fparams.m_trace_stream; + out << "[assign] "; + display_literal(out, l); + if (decision) + out << " decision"; + out << " "; + switch (j.get_kind()) { + case b_justification::AXIOM: + out << "axiom"; + break; + case b_justification::BIN_CLAUSE: { + literal l2 = j.get_literal(); + out << "bin-clause "; + display_literal(out, l); + out << " "; + display_literal(out, l2); + break; + } + case b_justification::CLAUSE: { + clause * cls = j.get_clause(); + out << "clause "; + display_literals(out, cls->get_num_literals(), cls->begin_literals()); + break; + } + case b_justification::JUSTIFICATION: + out << "justification"; + break; + default: + UNREACHABLE(); + break; + } + out << "\n"; + } + +}; + diff --git a/lib/smt_context_stat.cpp b/lib/smt_context_stat.cpp new file mode 100644 index 000000000..5e82923f0 --- /dev/null +++ b/lib/smt_context_stat.cpp @@ -0,0 +1,159 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_context_stat.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-03-06. + +Revision History: + +--*/ +#include"smt_context.h" +#include"ast_pp.h" + +namespace smt { + + unsigned context::get_lemma_avg_activity() const { + if (m_lemmas.empty()) + return 0; + unsigned long long acc = 0; + clause_vector::const_iterator it = m_lemmas.begin(); + clause_vector::const_iterator end = m_lemmas.end(); + for (; it != end; ++it) + acc += (*it)->get_activity(); + return static_cast(acc / m_lemmas.size()); + } + + void acc_num_occs(clause * cls, unsigned_vector & lit2num_occs) { + unsigned num_lits = cls->get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) { + literal l = cls->get_literal(i); + lit2num_occs[l.index()]++; + } + } + + void acc_num_occs(clause_vector const & v, unsigned_vector & lit2num_occs) { + clause_vector::const_iterator it = v.begin(); + clause_vector::const_iterator end = v.end(); + for (; it != end; ++it) + acc_num_occs(*it, lit2num_occs); + } + + void context::display_literal_num_occs(std::ostream & out) const { + unsigned num_lits = m_assignment.size(); + unsigned_vector lit2num_occs; + lit2num_occs.resize(num_lits, 0); + acc_num_occs(m_aux_clauses, lit2num_occs); + acc_num_occs(m_lemmas, lit2num_occs); + for (unsigned lidx = 0; lidx < num_lits; lidx++) { + literal l = to_literal(lidx); + if (lit2num_occs[lidx] > 0) { + out << lit2num_occs[lidx] << " "; + // display_literal(out, l); + out << l.sign() << " " << mk_pp(bool_var2expr(l.var()), m_manager); + out << "\n"; + } + } + } + + void context::display_num_assigned_literals_per_lvl(std::ostream & out) const { + unsigned n = 0; + svector::const_iterator it = m_scopes.begin(); + svector::const_iterator end = m_scopes.end(); + out << "["; + for (; it != end; ++it) { + scope const & s = *it; + SASSERT(n <= s.m_assigned_literals_lim); + out << (s.m_assigned_literals_lim - n) << " "; + n = s.m_assigned_literals_lim; + } + SASSERT(n <= m_assigned_literals.size()); + out << (m_assigned_literals.size() - n) << "]"; + } + + void acc_var_num_occs(clause * cls, unsigned_vector & var2num_occs) { + unsigned num_lits = cls->get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) { + literal l = cls->get_literal(i); + var2num_occs[l.var()]++; + } + } + + void acc_var_num_occs(clause_vector const & v, unsigned_vector & var2num_occs) { + clause_vector::const_iterator it = v.begin(); + clause_vector::const_iterator end = v.end(); + for (; it != end; ++it) + acc_var_num_occs(*it, var2num_occs); + } + + void context::display_var_occs_histogram(std::ostream & out) const { + unsigned num_vars = get_num_bool_vars(); + unsigned_vector var2num_occs; + var2num_occs.resize(num_vars, 0); + acc_var_num_occs(m_aux_clauses, var2num_occs); + acc_var_num_occs(m_lemmas, var2num_occs); + unsigned_vector histogram; + for (unsigned v = 0; v < num_vars; v++) { + unsigned num_occs = var2num_occs[v]; + histogram.reserve(num_occs+1, 0); + histogram[num_occs]++; + } + out << "number of atoms having k occs:\n"; + unsigned sz = histogram.size(); + for (unsigned i = 1; i < sz; i++) + if (histogram[i] > 0) + out << i << ":" << histogram[i] << " "; + out << "\n"; + } + + void acc_var_num_min_occs(clause * cls, unsigned_vector & var2num_min_occs) { + unsigned num_lits = cls->get_num_literals(); + bool_var min_var = cls->get_literal(0).var(); + for (unsigned i = 1; i < num_lits; i++) { + bool_var v = cls->get_literal(i).var(); + if (v < min_var) + min_var = v; + } + var2num_min_occs[min_var]++; + } + + void acc_var_num_min_occs(clause_vector const & v, unsigned_vector & var2num_min_occs) { + clause_vector::const_iterator it = v.begin(); + clause_vector::const_iterator end = v.end(); + for (; it != end; ++it) + acc_var_num_min_occs(*it, var2num_min_occs); + } + + void context::display_num_min_occs(std::ostream & out) const { + unsigned num_vars = get_num_bool_vars(); + unsigned_vector var2num_min_occs; + var2num_min_occs.resize(num_vars, 0); + acc_var_num_min_occs(m_aux_clauses, var2num_min_occs); + acc_var_num_min_occs(m_lemmas, var2num_min_occs); + out << "number of min occs:\n"; + for (unsigned v = 0; v < num_vars; v++) { + if (var2num_min_occs[v] > 0) + out << v << ":" << var2num_min_occs[v] << " "; + } + out << "\n"; + } + + void context::display_profile_res_sub(std::ostream & out) const { + display_var_occs_histogram(std::cerr); + display_num_min_occs(std::cerr); + std::cerr << "\n"; + } + + void context::display_profile(std::ostream & out) const { + if (m_fparams.m_profile_res_sub) + display_profile_res_sub(out); + } +}; diff --git a/lib/smt_enode.cpp b/lib/smt_enode.cpp new file mode 100644 index 000000000..98b8eaf8b --- /dev/null +++ b/lib/smt_enode.cpp @@ -0,0 +1,453 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_enode.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-19. + +Revision History: + +--*/ +#include"smt_context.h" +#include"smt_enode.h" + +namespace smt { + + /** + \brief Initialize an enode in the given memory position. + */ + enode * enode::init(ast_manager & m, void * mem, ptr_vector const & app2enode, app * owner, + unsigned generation, bool suppress_args, bool merge_tf, unsigned iscope_lvl, + bool cgc_enabled, bool update_children_parent) { + SASSERT(m.is_bool(owner) || !merge_tf); + enode * n = new (mem) enode(); + n->m_owner = owner; + n->m_root = n; + n->m_next = n; + n->m_cg = 0; + n->m_class_size = 1; + n->m_generation = generation; + n->m_func_decl_id = UINT_MAX; + n->m_mark = false; + n->m_mark2 = false; + n->m_interpreted = false; + n->m_suppress_args = suppress_args; + n->m_eq = m.is_eq(owner); + n->m_commutative = n->get_num_args() == 2 && owner->get_decl()->is_commutative(); + n->m_bool = m.is_bool(owner); + n->m_merge_tf = merge_tf; + n->m_cgc_enabled = cgc_enabled; + n->m_iscope_lvl = iscope_lvl; + n->m_lbl_hash = -1; + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + enode * arg = app2enode[owner->get_arg(i)->get_id()]; + n->m_args[i] = arg; + SASSERT(n->get_arg(i) == arg); + if (update_children_parent) + arg->get_root()->m_parents.push_back(n); + } + TRACE("mk_enode_detail", tout << "new enode suppress_args: " << n->m_suppress_args << "\n";); + SASSERT(n->m_suppress_args == suppress_args); + return n; + } + + enode * enode::mk(ast_manager & m, region & r, ptr_vector const & app2enode, app * owner, + unsigned generation, bool suppress_args, bool merge_tf, unsigned iscope_lvl, + bool cgc_enabled, bool update_children_parent) { + SASSERT(m.is_bool(owner) || !merge_tf); + unsigned sz = get_enode_size(suppress_args ? 0 : owner->get_num_args()); + void * mem = r.allocate(sz); + return init(m, mem, app2enode, owner, generation, suppress_args, merge_tf, iscope_lvl, cgc_enabled, update_children_parent); + } + + enode * enode::mk_dummy(ast_manager & m, ptr_vector const & app2enode, app * owner) { + unsigned sz = get_enode_size(owner->get_num_args()); + void * mem = alloc_svect(char, sz); + return init(m, mem, app2enode, owner, 0, false, false, 0, true, false); + } + + void enode::del_eh(ast_manager & m, bool update_children_parent) { + SASSERT(m_class_size == 1); + SASSERT(m_root == this); + SASSERT(m_next == this); + unsigned num_args = get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + enode * arg = get_arg(i); + if (update_children_parent) { + SASSERT(arg->get_root()->m_parents.back() == this); + arg->get_root()->m_parents.pop_back(); + } + } + this->~enode(); + } + + unsigned enode::get_num_th_vars() const { + unsigned r = 0; + theory_var_list const * l = get_th_var_list(); + while(l) { + r++; + l = l->get_next(); + } + return r; + } + + /** + \brief Return the theory var (in theory th_id) associated with + the enode. + Return null_theory_var if the enode is not associated + with a variable of theory th_id + */ + theory_var enode::get_th_var(theory_id th_id) const { + if (m_th_var_list.get_th_var() == null_theory_var) + return null_theory_var; + theory_var_list const * l = &m_th_var_list; + while (l) { + if (l->get_th_id() == th_id) { + return l->get_th_var(); + } + l = l->get_next(); + } + return null_theory_var; + } + + /** + \brief Add the entry (v, id) to the list of theory variables. + */ + void enode::add_th_var(theory_var v, theory_id id, region & r) { +#ifdef Z3DEBUG + unsigned old_size = get_num_th_vars(); +#endif + SASSERT(get_th_var(id) == null_theory_var); + if (m_th_var_list.get_th_var() == null_theory_var) { + m_th_var_list.set_th_var(v); + m_th_var_list.set_th_id(id); + m_th_var_list.set_next(0); + } + else { + theory_var_list * l = &m_th_var_list; + while (l->get_next() != 0) { + SASSERT(l->get_th_id() != id); + l = l->get_next(); + } + SASSERT(l); + SASSERT(l->get_next() == 0); + theory_var_list * new_cell = new (r) theory_var_list(id, v); + l->set_next(new_cell); + } + SASSERT(get_num_th_vars() == old_size + 1); + SASSERT(get_th_var(id) == v); + } + + /** + \brief Replace the entry (v', id) with the entry (v, id). + The enode must have an entry (v', id) + */ + void enode::replace_th_var(theory_var v, theory_id id) { + SASSERT(get_th_var(id) != null_theory_var); + theory_var_list * l = get_th_var_list(); + while (l) { + if (l->get_th_id() == id) { + l->set_th_var(v); + return; + } + l = l->get_next(); + } + UNREACHABLE(); + } + + /** + \brief Delete theory variable. It assumes the + enode is associated with a variable of the given theory. + */ + void enode::del_th_var(theory_id id) { + SASSERT(get_th_var(id) != null_theory_var); + if (m_th_var_list.get_th_id() == id) { + theory_var_list * next = m_th_var_list.get_next(); + if (next == 0) { + // most common case + m_th_var_list.set_th_var(null_theory_var); + m_th_var_list.set_th_id(null_theory_id); + m_th_var_list.set_next(0); + } + else { + m_th_var_list = *next; + } + } + else { + theory_var_list * prev = get_th_var_list(); + theory_var_list * l = prev->get_next(); + while (l) { + SASSERT(prev->get_next() == l); + if (l->get_th_id() == id) { + prev->set_next(l->get_next()); + return; + } + prev = l; + l = l->get_next(); + } + UNREACHABLE(); + } + } + + + /** + \brief Push old value of generation on the context trail stack + and update the generation. + */ + void enode::set_generation(context & ctx, unsigned generation) { + if (m_generation == generation) + return; + ctx.push_trail(value_trail(m_generation)); + m_generation = generation; + } + + + void enode::set_lbl_hash(context & ctx) { + SASSERT(m_lbl_hash == -1); + // m_lbl_hash should be different from -1, if and only if, + // there is a pattern that contains the enode. So, + // I use a trail to restore the value of m_lbl_hash to -1. + ctx.push_trail(value_trail(m_lbl_hash)); + unsigned h = hash_u(get_owner_id()); + m_lbl_hash = h & (APPROX_SET_CAPACITY - 1); + // propagate modification to the root m_lbls set. + approx_set & r_lbls = m_root->m_lbls; + if (!r_lbls.may_contain(m_lbl_hash)) { + ctx.push_trail(value_trail(r_lbls)); + r_lbls.insert(m_lbl_hash); + } + } + + enode * enode::get_eq_enode_with_min_gen() { + if (m_generation == 0) + return this; + enode * r = this; + enode * curr = this; + do { + if (curr->m_generation < r->m_generation) { + r = curr; + if (r->m_generation == 0) + return r; + } + curr = curr->m_next; + } + while (curr != this); + return r; + } + +#ifdef Z3DEBUG + bool enode::check_invariant() const { + unsigned class_size = 0; + bool found_root = false; + bool found_this = false; + bool has_interpreted = false; + + // "Equivalence" class structure. + enode const * curr = this; + do { + SASSERT(curr->m_root == m_root); + class_size++; + if (curr == m_root) + found_root = true; + if (curr == this) + found_this = true; + if (curr->is_interpreted()) + has_interpreted = true; + curr = curr->m_next; + } + while (curr != this); + + SASSERT(found_root); + SASSERT(found_this); + SASSERT(this != m_root || class_size == m_class_size); + SASSERT(!has_interpreted || m_root->is_interpreted()); + + // Parent use-list + if (this == m_root) { + enode_vector::const_iterator it2 = m_parents.begin(); + enode_vector::const_iterator end2 = m_parents.end(); + for (; it2 != end2; ++it2) { + enode const * parent = *it2; + unsigned i = 0; + unsigned num_args = parent->get_num_args(); + SASSERT(num_args > 0); + for (; i < num_args; i++) { + enode * arg = parent->get_arg(i); + if (arg->get_root() == m_root) + break; + } + SASSERT(i < num_args); + } + } + + // Proof tree + // m_root is reachable from "this" by following the transitivity proof + SASSERT(trans_reaches(m_root)); + SASSERT(check_parent_invariant()); + return true; + } + + /** + \brief Return true if the node is n or n is reached following the + m_proof.m_target pointers + */ + bool enode::trans_reaches(enode * n) const { + const enode * curr = this; + while (curr != 0) { + if (curr == n) { + return true; + } + curr = curr->m_trans.m_target; + } + return false; + } + + bool enode::check_parent_invariant() const { + if (this != m_root) + return true; + enode const * curr = m_root; + do { + if (curr != m_root) { + enode_vector::const_iterator it = curr->m_parents.begin(); + enode_vector::const_iterator end = curr->m_parents.end(); + for (; it != end; ++it) { + enode * p = *it; + if (!p->is_true_eq() && !m_root->contains_parent_congruent_to(p)) { + UNREACHABLE(); + } + } + } + curr = curr->m_next; + } + while (curr != m_root); + return true; + } + + bool enode::contains_parent_congruent_to(enode * p) const { + enode_vector::const_iterator it = m_parents.begin(); + enode_vector::const_iterator end = m_parents.end(); + for (; it != end; ++it) { + enode * curr = *it; + if (congruent(curr, p)) + return true; + } + return false; + } + +#endif + + void enode::display_lbls(std::ostream & out) const { + out << "#" << get_owner_id() << " -> #" << get_root()->get_owner_id() << ", lbls: " << get_lbls() << ", plbls: " << get_plbls() + << ", root->lbls: " << get_root()->get_lbls() << ", root->plbls: " << get_root()->get_plbls(); + if (has_lbl_hash()) + out << ", lbl-hash: " << static_cast(get_lbl_hash()); + out << "\n"; + } + + bool congruent(enode * n1, enode * n2, bool & comm) { + comm = false; + if (n1->get_owner()->get_decl() != n2->get_owner()->get_decl()) + return false; + unsigned num_args = n1->get_num_args(); + if (num_args != n2->get_num_args()) + return false; + if (n1->is_commutative()) { + enode * c1_1 = n1->get_arg(0)->get_root(); + enode * c1_2 = n1->get_arg(1)->get_root(); + enode * c2_1 = n2->get_arg(0)->get_root(); + enode * c2_2 = n2->get_arg(1)->get_root(); + if (c1_1 == c2_1 && c1_2 == c2_2) { + return true; + } + if (c1_1 == c2_2 && c1_2 == c2_1) { + comm = true; + return true; + } + return false; + } + else { + for (unsigned i = 0; i < num_args; i++) + if (n1->get_arg(i)->get_root() != n2->get_arg(i)->get_root()) + return false; + return true; + } + } + + unsigned get_max_generation(unsigned num_enodes, enode * const * enodes) { + unsigned max = 0; + for (unsigned i = 0; i < num_enodes; i++) { + unsigned curr = enodes[i]->get_generation(); + if (curr > max) + max = curr; + } + return max; + } + + void unmark_enodes(unsigned num_enodes, enode * const * enodes) { + for (unsigned i = 0; i < num_enodes; i++) + enodes[i]->unset_mark(); + } + + void unmark_enodes2(unsigned num_enodes, enode * const * enodes) { + for (unsigned i = 0; i < num_enodes; i++) + enodes[i]->unset_mark2(); + } + + tmp_enode::tmp_enode(): + m_app(0), + m_capacity(0), + m_enode_data(0) { + SASSERT(m_app.get_app()->get_decl() == 0); + set_capacity(5); + } + + tmp_enode::~tmp_enode() { + dealloc_svect(m_enode_data); + } + + void tmp_enode::set_capacity(unsigned new_capacity) { + SASSERT(new_capacity > m_capacity); + if (m_enode_data) + dealloc_svect(m_enode_data); + m_capacity = new_capacity; + unsigned sz = sizeof(enode) + m_capacity * sizeof(enode*); + m_enode_data = alloc_svect(char, sz); + memset(m_enode_data, 0, sz); + enode * n = get_enode(); + n->m_owner = m_app.get_app(); + n->m_root = n; + n->m_next = n; + n->m_class_size = 1; + n->m_cgc_enabled = true; + n->m_func_decl_id = UINT_MAX; + } + + enode * tmp_enode::set(func_decl * f, unsigned num_args, enode * const * args) { + if (num_args > m_capacity) + set_capacity(num_args * 2); + enode * r = get_enode(); + if (m_app.get_app()->get_decl() != f) { + r->m_func_decl_id = UINT_MAX; + } + m_app.set_decl(f); + m_app.set_num_args(num_args); + r->m_commutative = num_args == 2 && f->is_commutative(); + memcpy(get_enode()->m_args, args, sizeof(enode*)*num_args); + return r; + } + + void tmp_enode::reset() { + get_enode()->m_func_decl_id = UINT_MAX; + } + +}; + diff --git a/lib/smt_enode.h b/lib/smt_enode.h new file mode 100644 index 000000000..505dea0ee --- /dev/null +++ b/lib/smt_enode.h @@ -0,0 +1,382 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_enode.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-18. + +Revision History: + +--*/ +#ifndef _SMT_ENODE_H_ +#define _SMT_ENODE_H_ + +#include"ast.h" +#include"smt_types.h" +#include"smt_eq_justification.h" +#include"smt_theory_var_list.h" +#include"approx_set.h" + +namespace smt { + /** + \brief Justification for the transitivity rule. + */ + struct trans_justification { + enode * m_target; + eq_justification m_justification; + trans_justification(): + m_target(0), + m_justification(null_eq_justification) { + } + }; + + class tmp_enode; + + /** + \brief Aditional data-structure for implementing congruence closure, + equality propagation, and the theory central bus of equalities. + */ + class enode { + app * m_owner; //!< The application that 'owns' this enode. + enode * m_root; //!< Representative of the equivalence class + enode * m_next; //!< Next element in the equivalence class. + enode * m_cg; + unsigned m_class_size; //!< Size of the equivalence class if the enode is the root. + unsigned m_generation; //!< Tracks how many quantifier instantiation rounds were needed to generate this enode. + + unsigned m_func_decl_id; //!< Id generated by the congruence table for fast indexing. + + unsigned m_mark:1; //!< Multi-purpose auxiliary mark. + unsigned m_mark2:1; //!< Multi-purpose auxiliary mark. + unsigned m_interpreted:1; //!< True if the node is an interpreted constant. + unsigned m_suppress_args:1; //!< True if the arguments of m_owner should not be accessed by this enode. + unsigned m_eq:1; //!< True if it is an equality + unsigned m_commutative:1; //!< True if commutative app + unsigned m_bool:1; //!< True if it is a boolean enode + unsigned m_merge_tf:1; //!< True if the enode should be merged with true/false when the associated boolean variable is assigned. + unsigned m_cgc_enabled:1; //!< True if congruence closure is enabled for this enode. + unsigned m_iscope_lvl; //!< When the enode was internalized + /* + The following property is valid for m_parents + + If this = m_root, then for every term f(a) such that a->get_root() == m_root, + there is a f(b) in m_parents such that b->get_root() == m_root, and f(a) and f(b) are + congruent. + Remark: f(a) and f(b) may have other arguments. + + Exception: If f(a) and f(b) are terms of the form (= a c) and (= b d), then + m_parents will not contains (= b d) if b->get_root() == d->get_root(). + + Remark regarding relevancy propagation: relevancy is propagated to all + elements of an equivalence class. So, if there is a f(a) that is relevant, + then the congruent f(b) in m_parents will also be relevant. + */ + enode_vector m_parents; //!< Parent enodes of the equivalence class. + theory_var_list m_th_var_list; //!< List of theories that 'care' about this enode. + trans_justification m_trans; //!< A justification for the enode being equal to its root. + char m_lbl_hash; //!< It is different from -1, if enode is used in a pattern + approx_set m_lbls; + approx_set m_plbls; + enode * m_args[0]; //!< Cached args + + friend class context; + friend class euf_manager; + friend class conflict_resolution; + + theory_var_list * get_th_var_list() { + return m_th_var_list.get_th_var() == null_theory_var ? 0 : &m_th_var_list; + } + + friend class set_merge_tf_trail; + /** + \brief Return true if the enode should be merged with the true (false) enodes when + the associated boolean variable is assigned to true (false). + */ + bool merge_tf() const { + return m_merge_tf; + } + + friend class add_th_var_trail; + friend class replace_th_var_trail; + + void add_th_var(theory_var v, theory_id id, region & r); + + void replace_th_var(theory_var v, theory_id id); + + void del_th_var(theory_id id); + + friend class tmp_enode; + + static enode * init(ast_manager & m, void * mem, ptr_vector const & app2enode, app * owner, + unsigned generation, bool suppress_args, bool merge_tf, unsigned iscope_lvl, + bool cgc_enabled, bool update_children_parent); + public: + + static unsigned get_enode_size(unsigned num_args) { + return sizeof(enode) + num_args * sizeof(enode*); + } + + static enode * mk(ast_manager & m, region & r, ptr_vector const & app2enode, app * owner, + unsigned generation, bool suppress_args, bool merge_tf, unsigned iscope_lvl, + bool cgc_enabled, bool update_children_parent); + + static enode * mk_dummy(ast_manager & m, ptr_vector const & app2enode, app * owner); + + static void del_dummy(enode * n) { dealloc_svect(reinterpret_cast(n)); } + + unsigned get_func_decl_id() const { + return m_func_decl_id; + } + + void set_func_decl_id(unsigned id) { + m_func_decl_id = id; + } + + void mark_as_interpreted() { + SASSERT(!m_interpreted); + SASSERT(m_owner->get_num_args() == 0); + SASSERT(m_class_size == 1); + m_interpreted = true; + } + + void del_eh(ast_manager & m, bool update_children_parent = true); + + app * get_owner() const { + return m_owner; + } + + unsigned get_owner_id() const { + return m_owner->get_id(); + } + + func_decl * get_decl() const { + return m_owner->get_decl(); + } + + unsigned get_decl_id() const { + return m_owner->get_decl()->get_decl_id(); + } + + unsigned hash() const { + return m_owner->hash(); + } + + enode * get_root() const { + return m_root; + } + + enode * get_next() const { + return m_next; + } + + unsigned get_num_args() const { + return m_suppress_args ? 0 : m_owner->get_num_args(); + } + + enode * get_arg(unsigned idx) const { + SASSERT(idx < get_num_args()); + return m_args[idx]; + } + + enode * const * get_args() const { + return m_args; + } + + // unsigned get_id() const { + // return m_id; + // } + + unsigned get_class_size() const { + return m_class_size; + } + + bool is_bool() const { + return m_bool; + } + + bool is_eq() const { + return m_eq; + } + + bool is_true_eq() const { + return m_eq && get_arg(0)->get_root() == get_arg(1)->get_root(); + } + + bool is_marked() const { + return m_mark; + } + + void set_mark() { + SASSERT(!m_mark); m_mark = true; + } + + void unset_mark() { + SASSERT(m_mark); m_mark = false; + } + + bool is_marked2() const { + return m_mark2; + } + + void set_mark2() { + SASSERT(!m_mark2); m_mark2 = true; + } + + void unset_mark2() { + SASSERT(m_mark2); m_mark2 = false; + } + + bool is_interpreted() const { + return m_interpreted; + } + + /** + \brief Return true if node is not a constant and it is the root of its congruence class. + + \remark if get_num_args() == 0, then is_cgr() = false. + */ + bool is_cgr() const { + return m_cg == this; + } + + enode * get_cg() const { + return m_cg; + } + + bool is_cgc_enabled() const { + return m_cgc_enabled; + } + + bool is_commutative() const { + return m_commutative; + } + + unsigned get_num_parents() const { + return m_parents.size(); + } + + enode_vector::iterator begin_parents() { + return m_parents.begin(); + } + + enode_vector::iterator end_parents() { + return m_parents.end(); + } + + enode_vector::const_iterator begin_parents() const { + return m_parents.begin(); + } + + enode_vector::const_iterator end_parents() const { + return m_parents.end(); + } + + theory_var_list const * get_th_var_list() const { + return m_th_var_list.get_th_var() == null_theory_var ? 0 : &m_th_var_list; + } + + bool has_th_vars() const { + return m_th_var_list.get_th_var() != null_theory_var; + } + + unsigned get_num_th_vars() const; + + theory_var get_th_var(theory_id th_id) const; + + unsigned get_generation() const { + return m_generation; + } + + void set_generation(context & ctx, unsigned generation); + + /** + \brief Return the enode n that is in the eqc of *this, and has the minimal generation. + That is, there is no other enode with smaller generation. + */ + enode * get_eq_enode_with_min_gen(); + + unsigned get_iscope_lvl() const { + return m_iscope_lvl; + } + + void set_lbl_hash(context & ctx); + + bool has_lbl_hash() const { + return m_lbl_hash >= 0; + } + + unsigned char get_lbl_hash() const { + SASSERT(m_lbl_hash >= 0 && static_cast(m_lbl_hash) < approx_set_traits::capacity); + return static_cast(m_lbl_hash); + } + + approx_set & get_lbls() { + return m_lbls; + } + + approx_set & get_plbls() { + return m_plbls; + } + + const approx_set & get_lbls() const { + return m_lbls; + } + + const approx_set & get_plbls() const { + return m_plbls; + } + + void display_lbls(std::ostream & out) const; + +#ifdef Z3DEBUG + bool check_invariant() const; + bool trans_reaches(enode * n) const; + bool check_parent_invariant() const; + bool contains_parent_congruent_to(enode * p) const; +#endif + }; + + inline bool same_eqc(enode const * n1 , enode const * n2) { return n1->get_root() == n2->get_root(); } + + /** + \brief Return true, if n1 and n2 are congruent. + Set comm to true, if the nodes are congruent modulo commutativity. + */ + bool congruent(enode * n1, enode * n2, bool & comm); + + inline bool congruent(enode * n1, enode * n2) { + bool aux; + return congruent(n1, n2, aux); + } + + unsigned get_max_generation(unsigned num_enodes, enode * const * enodes); + + void unmark_enodes(unsigned num_enodes, enode * const * enodes); + + void unmark_enodes2(unsigned num_enodes, enode * const * enodes); + + class tmp_enode { + tmp_app m_app; + unsigned m_capacity; + char * m_enode_data; + enode * get_enode() { return reinterpret_cast(m_enode_data); } + void set_capacity(unsigned new_capacity); + public: + tmp_enode(); + ~tmp_enode(); + enode * set(func_decl * f, unsigned num_args, enode * const * args); + void reset(); + }; + +}; + +#endif /* _SMT_ENODE_H_ */ + diff --git a/lib/smt_eq_justification.h b/lib/smt_eq_justification.h new file mode 100644 index 000000000..46ed5681f --- /dev/null +++ b/lib/smt_eq_justification.h @@ -0,0 +1,85 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_eq_justification.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-19. + +Revision History: + +--*/ +#ifndef _SMT_EQ_JUSTIFICATION_H_ +#define _SMT_EQ_JUSTIFICATION_H_ + +#include"smt_literal.h" +#include"tptr.h" + +namespace smt { + + /** + \brief Proof like object used to track dependencies of equality propagation. + The idea is to reduce the cost of dependency tracking for the most common + justifications used during equality propagation: (asserted equality & congruence). + */ + class eq_justification { + void * m_data; + public: + enum kind { + AXIOM, //!< no justification, it is only used when proof generation is disabled + CONGRUENCE, + EQUATION, //!< asserted equation + JUSTIFICATION //!< fallback + }; + + explicit eq_justification(): + m_data(reinterpret_cast(static_cast(AXIOM))) { + } + + /** + \brief Create a justification for the congruence rule. + If commutativity == true, then it means it is a combined justification: commutativity + congruence. + */ + explicit eq_justification(bool commutativity): + m_data(BOXTAGINT(void*, static_cast(commutativity), CONGRUENCE)) { + } + + explicit eq_justification(literal l): + m_data(BOXTAGINT(void*, l.index(), EQUATION)) { + } + + explicit eq_justification(justification * js): + m_data(TAG(void*, js, JUSTIFICATION)) { + } + + kind get_kind() const { + return static_cast(GET_TAG(m_data)); + } + + literal get_literal() const { SASSERT(get_kind() == EQUATION); return to_literal(UNBOXINT(m_data)); } + + justification * get_justification() const { SASSERT(get_kind() == JUSTIFICATION); return UNTAG(justification*, m_data); } + + bool used_commutativity() const { SASSERT(get_kind() == CONGRUENCE); return UNBOXINT(m_data) != 0; } + + static eq_justification mk_axiom() { + return eq_justification(); + } + + static eq_justification mk_cg(bool comm = false) { + return eq_justification(comm); + } + }; + + const eq_justification null_eq_justification(static_cast(0)); +}; + +#endif /* _SMT_EQ_JUSTIFICATION_H_ */ + diff --git a/lib/smt_euf.cpp b/lib/smt_euf.cpp new file mode 100644 index 000000000..e6c393f69 --- /dev/null +++ b/lib/smt_euf.cpp @@ -0,0 +1,187 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + smt_euf.cpp + +Abstract: + + Equality and uninterpreted functions + +Author: + + Leonardo de Moura (leonardo) 2012-02-14. + +Revision History: + +--*/ +#include"smt_euf.h" +#include"smt_context.h" +#include"ast_smt2_pp.h" + +namespace smt { + + struct euf_manager::imp { + context & m_context; + ast_manager & m_manager; + region & m_region; + expr_ref_vector m_e_internalized_stack; // stack of the expressions already internalized as enodes. + ptr_vector m_app2enode; // app -> enode + ptr_vector m_enodes; + vector m_decl2enodes; // decl -> enode (for decls with arity > 0) + cg_table m_cg_table; + dyn_ack_manager m_dyn_ack_manager; + struct new_eq { + enode * m_lhs; + enode * m_rhs; + eq_justification m_justification; + new_eq() {} + new_eq(enode * lhs, enode * rhs, eq_justification const & js): + m_lhs(lhs), m_rhs(rhs), m_justification(js) {} + }; + svector m_eq_propagation_queue; + struct new_th_eq { + theory_id m_th_id; + theory_var m_lhs; + theory_var m_rhs; + new_th_eq():m_th_id(null_theory_id), m_lhs(null_theory_var), m_rhs(null_theory_var) {} + new_th_eq(theory_id id, theory_var l, theory_var r):m_th_id(id), m_lhs(l), m_rhs(r) {} + }; + svector m_th_eq_propagation_queue; + svector m_th_diseq_propagation_queue; + enode * m_is_diseq_tmp; // auxiliary enode used to find congruent equality atoms. + tmp_enode m_tmp_enode; + ptr_vector m_almost_cg_tables; // temporary field for is_ext_diseq + obj_map m_cached_generation; + obj_hashtable m_cache_generation_visited; + friend class mk_enode_trail; + class mk_enode_trail : public trail { + imp & m_owner; + public: + mk_enode_trail(imp & o):m_owner(o) {} + virtual void undo(context & ctx) { m_owner.undo_mk_enode(); } + }; + mk_enode_trail m_mk_enode_trail; + volatile bool m_cancel_flag; + + // Statistics + unsigned m_num_mk_enode; + unsigned m_num_del_enode; + + void push_eq(enode * lhs, enode * rhs, eq_justification const & js) { + SASSERT(lhs != rhs); + m_eq_propagation_queue.push_back(new_eq(lhs, rhs, js)); + } + + void push_new_congruence(enode * n1, enode * n2, bool used_commutativity) { + SASSERT(n1->m_cg == n2); + push_eq(n1, n2, eq_justification::mk_cg(used_commutativity)); + } + + bool e_internalized(expr const * n) const { + return m_app2enode.get(n->get_id(), 0) != 0; + } + + void set_app2enode(expr const * n, enode * e) { + m_app2enode.setx(n->get_id(), e, 0); + } + + enode * mk_enode(app * n, bool suppress_args, bool merge_tf, bool cgc_enabled, unsigned generation) { + TRACE("mk_enode_detail", + tout << mk_ismt2_pp(n, m_manager) << "\n"; + tout <<"suppress_args: " << suppress_args << ", merge_tf: " << merge_tf << ", cgc_enabled: " << cgc_enabled << "\n";); + SASSERT(!e_internalized(n)); + unsigned scope_lvl = m_context.get_scope_level(); + unsigned id = n->get_id(); + unsigned _generation = 0; + if (!m_cached_generation.empty() && m_cached_generation.find(n, _generation)) { + generation = _generation; + } + enode * e = enode::mk(m_manager, m_region, m_app2enode, n, generation, suppress_args, merge_tf, scope_lvl, cgc_enabled, true); + TRACE("mk_enode_detail", tout << "e.get_num_args() = " << e->get_num_args() << "\n";); + if (n->get_num_args() == 0 && m_manager.is_value(n)) + e->mark_as_interpreted(); + TRACE("mk_var_bug", tout << "mk_enode: " << id << "\n";); + TRACE("generation", tout << "mk_enode: " << id << " " << generation << "\n";); + set_app2enode(n, e); + m_e_internalized_stack.push_back(n); + m_context.push_trail_ptr(&m_mk_enode_trail); + m_enodes.push_back(e); + if (e->get_num_args() > 0) { + if (e->is_true_eq()) { + /* + bool_var v = enode2bool_var(e); + assign(literal(v), mk_justification(eq_propagation_justification(e->get_arg(0), e->get_arg(1)))); + e->m_cg = e; + */ + } + else { + if (cgc_enabled) { + enode_bool_pair pair = m_cg_table.insert(e); + enode * e_prime = pair.first; + if (e != e_prime) { + e->m_cg = e_prime; + bool used_commutativity = pair.second; + push_new_congruence(e, e_prime, used_commutativity); + } + else { + e->m_cg = e; + } + } + else { + e->m_cg = e; + } + } + if (!e->is_eq()) { + unsigned decl_id = n->get_decl()->get_decl_id(); + if (decl_id >= m_decl2enodes.size()) + m_decl2enodes.resize(decl_id+1); + m_decl2enodes[decl_id].push_back(e); + } + } + SASSERT(e_internalized(n)); + m_num_mk_enode++; + + // #ifndef SMTCOMP + // if (m_params.m_trace_stream != NULL) + // *m_params.m_trace_stream << "[attach-enode] #" << n->get_id() << " " << m_generation << "\n"; + // #endif + + return e; + } + + void undo_mk_enode() { + SASSERT(!m_e_internalized_stack.empty()); + m_num_del_enode++; + expr * n = m_e_internalized_stack.back(); + TRACE("undo_mk_enode", tout << "undo_enode: #" << n->get_id() << "\n" << mk_ismt2_pp(n, m_manager) << "\n";); + TRACE("mk_var_bug", tout << "undo_mk_enode: " << n->get_id() << "\n";); + unsigned n_id = n->get_id(); + SASSERT(is_app(n)); + enode * e = m_app2enode[n_id]; + m_app2enode[n_id] = 0; + if (e->is_cgr() && !e->is_true_eq() && e->is_cgc_enabled()) { + SASSERT(m_cg_table.contains_ptr(e)); + m_cg_table.erase(e); + } + if (e->get_num_args() > 0 && !e->is_eq()) { + unsigned decl_id = to_app(n)->get_decl()->get_decl_id(); + SASSERT(decl_id < m_decl2enodes.size()); + SASSERT(m_decl2enodes[decl_id].back() == e); + m_decl2enodes[decl_id].pop_back(); + } + e->del_eh(m_manager); + SASSERT(m_e_internalized_stack.size() == m_enodes.size()); + m_enodes.pop_back(); + m_e_internalized_stack.pop_back(); + } + + }; + + euf_manager::euf_manager(context & ctx) { + } + + euf_manager::~euf_manager() { + } +}; diff --git a/lib/smt_euf.h b/lib/smt_euf.h new file mode 100644 index 000000000..b21205a5e --- /dev/null +++ b/lib/smt_euf.h @@ -0,0 +1,55 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + smt_euf.h + +Abstract: + + Equality and uninterpreted functions + +Author: + + Leonardo de Moura (leonardo) 2012-02-14. + +Revision History: + +--*/ +#ifndef _SMT_EUF_H_ +#define _SMT_EUF_H_ + +#include"ast.h" +#include"smt_enode.h" +#include"smt_eq_justification.h" + +namespace smt { + class context; + + class euf_manager { + struct imp; + imp * m_imp; + public: + euf_manager(context & ctx); + ~euf_manager(); + + enode * mk_enode(app * n, bool suppress_args, bool merge_tf, bool cgc_enabled); + + void add_eq(enode * n1, enode * n2, eq_justification js); + bool assume_eq(enode * lhs, enode * rhs); + void reset(); + + static bool is_eq(enode const * n1, enode const * n2) { return n1->get_root() == n2->get_root(); } + bool is_diseq(enode * n1, enode * n2) const; + bool is_ext_diseq(enode * n1, enode * n2, unsigned depth); + enode * get_enode_eq_to(func_decl * f, unsigned num_args, enode * const * args); + bool is_shared(enode * n) const; + + unsigned get_num_enodes_of(func_decl const * decl) const; + enode_vector::const_iterator begin_enodes_of(func_decl const * decl) const; + enode_vector::const_iterator end_enodes_of(func_decl const * decl) const; + }; +}; + + +#endif diff --git a/lib/smt_failure.h b/lib/smt_failure.h new file mode 100644 index 000000000..9a6db406c --- /dev/null +++ b/lib/smt_failure.h @@ -0,0 +1,40 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + smt_failure.h + +Abstract: + + Failures + +Author: + + Leonardo de Moura (leonardo) 2012-02-09. + +Revision History: + +--*/ +#ifndef _SMT_FAILURE_H_ +#define _SMT_FAILURE_H_ + +namespace smt { + + /** + \brief Reason for a l_undef result in the check method. + */ + enum failure { + OK, + UNKNOWN, + TIMEOUT, + MEMOUT, + CANCELED, //!< External cancel flag was set + NUM_CONFLICTS, //!< Maximum number of conflicts was reached + THEORY, //!< Theory is incomplete + QUANTIFIERS //!< Logical context contains universal quantifiers. + }; + +}; + +#endif diff --git a/lib/smt_for_each_relevant_expr.cpp b/lib/smt_for_each_relevant_expr.cpp new file mode 100644 index 000000000..bf039e8a1 --- /dev/null +++ b/lib/smt_for_each_relevant_expr.cpp @@ -0,0 +1,298 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_for_each_relevant_expr.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2009-01-05. + +Revision History: + +--*/ +#include"smt_context.h" +#include"smt_for_each_relevant_expr.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" + +namespace smt { + + bool check_at_labels::check(expr* n) { + m_first = true; + return count_at_labels_pos(n) <= 1; + } + + unsigned check_at_labels::count_at_labels_lit(expr* n, bool polarity) { + unsigned count = 0; + buffer lbls; + bool pos; + if (m_manager.is_label_lit(n, lbls) || (m_manager.is_label(n, pos, lbls) && pos == polarity)) { + buffer::const_iterator it = lbls.begin(); + buffer::const_iterator end = lbls.end(); + for (; it != end; ++it) { + symbol const & s = *it; + if (s.contains('@')) { + TRACE("for_each_relevant_expr", tout << "@ label: " << mk_pp(n, m_manager) << "\n";); + count += 1; + } + } + } + return count; + } + + + unsigned check_at_labels::count_at_labels_neg(expr* n) { + if (!is_app(n)) { + return 0; + } + app* a = to_app(n); + unsigned sz = a->get_num_args(); + unsigned count = count_at_labels_lit(n, false); + + if (m_manager.is_or(n)) { + for (unsigned i = 0; i < sz; ++i) { + count += count_at_labels_neg(a->get_arg(i)); + } + } + else if (m_manager.is_not(n)) { + count = count_at_labels_pos(a->get_arg(0)); + } + else if (m_manager.is_implies(n)) { + count += count_at_labels_pos(a->get_arg(0)); + count += count_at_labels_neg(a->get_arg(1)); + } + else if (m_manager.is_and(n)) { + for (unsigned i = 0; i < sz; ++i) { + count = std::max(count, count_at_labels_neg(a->get_arg(i))); + } + } + + if (count > 1 && m_first) { + TRACE("for_each_relevant_expr", tout << mk_pp(n, m_manager) << "\n";); + m_first = false; + } + + return count; + } + + + unsigned check_at_labels::count_at_labels_pos(expr* n) { + if (!is_app(n)) { + return 0; + } + app* a = to_app(n); + unsigned sz = a->get_num_args(); + unsigned count = count_at_labels_lit(n, true); + if (m_manager.is_and(n)) { + for (unsigned i = 0; i < sz; ++i) { + count += count_at_labels_pos(a->get_arg(i)); + } + } + else if (m_manager.is_not(n)) { + count = count_at_labels_neg(a->get_arg(0)); + } + else if (m_manager.is_implies(n)) { + count = std::max(count, count_at_labels_neg(a->get_arg(0))); + count = std::max(count, count_at_labels_pos(a->get_arg(1))); + } + else if (m_manager.is_or(n)) { + for (unsigned i = 0; i < sz; ++i) { + count = std::max(count, count_at_labels_pos(a->get_arg(i))); + } + } + + if (count > 1 && m_first) { + TRACE("for_each_relevant_expr", tout << mk_pp(n, m_manager) << "\n";); + m_first = false; + } + + return count; + } + + for_each_relevant_expr::for_each_relevant_expr(context & ctx): + m_manager(ctx.get_manager()), + m_context(ctx) { + } + + void for_each_relevant_expr::operator()(expr * n) { + TRACE("for_each_relevant_expr", tout << "#" << n->get_id() << "\n";); + } + + void for_each_relevant_expr::reset() { + m_todo.reset(); + m_cache.reset(); + } + + inline bool for_each_relevant_expr::is_relevant(expr * n) { + return m_context.is_relevant(n); + } + + inline lbool for_each_relevant_expr::get_assignment(expr * n) { + if (!m_context.lit_internalized(n)) + return l_true; // assume it is a top-level label + return m_context.get_assignment(n); + } + + void for_each_relevant_expr::process(expr * n) { + TRACE("for_each_relevant_expr", tout << "processing:\n" << mk_bounded_pp(n, m_manager) << "\n";); + TRACE("for_each_relevant_expr", tout << "processing:\n" << mk_pp(n, m_manager) << "\n";); + if (m_cache.contains(n)) + return; + m_todo.reset(); + m_todo.push_back(n); + while (!m_todo.empty()) { + expr * curr = m_todo.back(); + m_todo.pop_back(); + SASSERT(is_relevant(curr)); + if (m_cache.contains(curr)) + continue; + operator()(curr); + m_cache.insert(curr); + if (!is_app(curr)) + continue; + if (to_app(curr)->get_family_id() == m_manager.get_basic_family_id()) { + switch (to_app(curr)->get_decl_kind()) { + case OP_OR: + process_or(to_app(curr)); + break; + case OP_AND: + process_and(to_app(curr)); + break; + case OP_ITE: + process_ite(to_app(curr)); + break; + default: + process_app(to_app(curr)); + } + } + else { + process_app(to_app(curr)); + } + } + } + + + void for_each_relevant_expr::process_app(app * n) { + unsigned sz = n->get_num_args(); + for (unsigned i = 0; i < sz; i++) { + expr * arg = n->get_arg(i); + if (m_cache.contains(arg)) + continue; + SASSERT(is_relevant(arg)); + m_todo.push_back(arg); + } + } + + /** + \brief Add a relevant child of n (that is assigned to val) to m_todo. + + \remark Give preference to a child that is already in the cache. + */ + void for_each_relevant_expr::process_relevant_child(app * n, lbool val) { + unsigned sz = n->get_num_args(); + TRACE("for_each_relevant_expr", tout << val << " " << mk_bounded_pp(n, m_manager) << "\n";); + for (unsigned i = 0; i < sz; i++) { + expr * arg = n->get_arg(i); + if (!is_relevant(arg)) + continue; + if (get_assignment(arg) != val) + continue; + if (m_cache.contains(arg)) { + TRACE("for_each_relevant_expr", tout << "justified by: " << mk_bounded_pp(arg, m_manager) << "\n";); + return; // the current child justifies n. + } + } + for (unsigned i = 0; i < sz; i++) { + expr * arg = n->get_arg(i); + if (!is_relevant(arg)) + continue; + if (get_assignment(arg) != val) + continue; + + TRACE("for_each_relevant_expr", tout << "to_process: " << mk_bounded_pp(arg, m_manager) << "\n";); + + m_todo.push_back(arg); + return; + } + UNREACHABLE(); + } + + void for_each_relevant_expr::process_and(app * n) { + switch (get_assignment(n)) { + case l_undef: + UNREACHABLE(); + break; + case l_false: + process_relevant_child(n, l_false); + break; + case l_true: + process_app(n); + break; + } + } + + void for_each_relevant_expr::process_or(app * n) { + switch (get_assignment(n)) { + case l_undef: + UNREACHABLE(); + break; + case l_false: + process_app(n); + break; + case l_true: + process_relevant_child(n, l_true); + break; + } + } + + void for_each_relevant_expr::process_ite(app * n) { + if (!m_cache.contains(n->get_arg(0))) + m_todo.push_back(n->get_arg(0)); + switch (get_assignment(n->get_arg(0))) { + case l_false: + if (!m_cache.contains(n->get_arg(2))) + m_todo.push_back(n->get_arg(2)); + break; + case l_undef: + UNREACHABLE(); + break; + case l_true: + if (!m_cache.contains(n->get_arg(1))) + m_todo.push_back(n->get_arg(1)); + break; + } + } + + + void collect_relevant_label_lits::operator()(expr * n) { + TRACE("for_each_relevant_expr", + tout << "label: " << m_manager.is_label_lit(n) << " " << " " << get_assignment(n) + << " " << mk_bounded_pp(n, m_manager) << "\n";); + if (!m_manager.is_label_lit(n)) + return; + if (get_assignment(n) != l_true) + return; + m_manager.is_label_lit(n, m_buffer); // copy symbols to buffer + } + + void collect_relevant_labels::operator()(expr * n) { + bool pos; + TRACE("for_each_relevant_expr", + tout << "label: " << m_manager.is_label(n) << " " << get_assignment(n) + << " " << mk_bounded_pp(n, m_manager) << "\n";); + if (!m_manager.is_label(n, pos)) + return; + if (pos && (get_assignment(n) != l_true)) + return; + if (!pos && (get_assignment(n) != l_false)) + return; + m_manager.is_label(n, pos, m_buffer); // copy symbols to buffer + } + +}; diff --git a/lib/smt_for_each_relevant_expr.h b/lib/smt_for_each_relevant_expr.h new file mode 100644 index 000000000..e142b2e0c --- /dev/null +++ b/lib/smt_for_each_relevant_expr.h @@ -0,0 +1,114 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_for_each_relevant_expr.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2009-01-05. + +Revision History: + +--*/ +#ifndef _SMT_FOR_EACH_RELEVANT_EXPR_H_ +#define _SMT_FOR_EACH_RELEVANT_EXPR_H_ + +#include"ast.h" +#include"obj_hashtable.h" +#include"vector.h" + +namespace smt { + + class context; + + + class check_at_labels { + ast_manager & m_manager; + bool m_first; + unsigned count_at_labels_pos(expr* n); + unsigned count_at_labels_neg(expr* n); + unsigned count_at_labels_lit(expr* n, bool polarity); + + public: + check_at_labels(ast_manager& m) : m_manager(m) {}; + + /** + \brief Check that 'n' as a formula contains at most one @ label within each and-or path. + */ + + bool check(expr* cnstr); + }; + /** + \brief Functor used to traverse the relevant expressions in a logical context. + */ + class for_each_relevant_expr { + protected: + ast_manager & m_manager; + context & m_context; + obj_hashtable m_cache; + ptr_vector m_todo; + bool m_first; + + void process_app(app * n); + void process_relevant_child(app * n, lbool val); + void process_and(app * n); + void process_or(app * n); + void process_ite(app * n); + lbool get_assignment(expr * n); + bool is_relevant(expr * n); + + + public: + for_each_relevant_expr(context & ctx); + virtual ~for_each_relevant_expr() {} + /** + \brief Visit the relevant sub-expressions of n. + That is, only subexpressions m of n, such that m_context.is_relevant(m). + This method also tries to minimize the number of subexpressions visited. + For each visited expression the method operator() is invoked. + Only not-already-visited expressions are visited. + */ + void process(expr * n); + + /** + \see process + */ + virtual void operator()(expr * n); + /** + \brief Reset the cache of already visited expressions. + */ + void reset(); + }; + + class collect_relevant_label_lits : public for_each_relevant_expr { + buffer & m_buffer; + public: + collect_relevant_label_lits(context & ctx, buffer & b): + for_each_relevant_expr(ctx), + m_buffer(b) { + } + virtual ~collect_relevant_label_lits() {} + virtual void operator()(expr * n); + }; + + class collect_relevant_labels : public for_each_relevant_expr { + buffer & m_buffer; + public: + collect_relevant_labels(context & ctx, buffer & b): + for_each_relevant_expr(ctx), + m_buffer(b) { + } + virtual ~collect_relevant_labels() {} + virtual void operator()(expr * n); + }; + +}; + +#endif /* _SMT_FOR_EACH_RELEVANT_EXPR_H_ */ + diff --git a/lib/smt_formula_compiler.cpp b/lib/smt_formula_compiler.cpp new file mode 100644 index 000000000..7920d4e70 --- /dev/null +++ b/lib/smt_formula_compiler.cpp @@ -0,0 +1,218 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + smt_formula_compiler.cpp + +Abstract: + + Auxiliary class for smt::solver + Convert Exprs into Internal data-structures. + +Author: + + Leonardo de Moura (leonardo) 2011-06-25. + +Revision History: + This was an experiment to rewrite Z3 kernel. + It will be deleted after we finish revamping Z3 kernel. + +--*/ +#include"smt_formula_compiler.h" +#include"smt_solver_exp.h" +#include"assertion_set_util.h" +#include"assertion_set2sat.h" +#include"for_each_expr.h" + +namespace smt { + + formula_compiler::formula_compiler(solver_exp & _s, params_ref const & p): + s(_s), + m_a_util(s.m), + m_normalizer(s.m), + m_elim_ite(s.m) { + updt_params(p); + + params_ref s_p; + s_p.set_bool(":elim-and", true); + s_p.set_bool(":blast-distinct", true); + s_p.set_bool(":eq2ineq", true); + s_p.set_bool(":arith-lhs", true); + s_p.set_bool(":gcd-rounding", true); + s_p.set_bool(":sort-sums", true); + s_p.set_bool(":som", true); + m_normalizer.updt_params(s_p); + } + + formula_compiler::~formula_compiler() { + + } + + // mark theory axioms: literals that do not occur in the boolean structure + void formula_compiler::mark_axioms(assertion_set const & s, expr_fast_mark2 & axioms) { + ast_manager & m = s.m(); + unsigned sz = s.size(); + for (unsigned i = 0; i < sz; i++) { + expr * f = s.form(i); + while (m.is_not(f, f)); + if (!is_app(f) || to_app(f)->get_family_id() != m.get_basic_family_id()) { + axioms.mark(f); + continue; + } + SASSERT(is_app(f)); + SASSERT(to_app(f)->get_family_id() == m.get_basic_family_id()); + switch (to_app(f)->get_decl_kind()) { + case OP_OR: + case OP_IFF: + break; + case OP_ITE: + SASSERT(m.is_bool(to_app(f)->get_arg(1))); + break; + case OP_EQ: + if (!m.is_bool(to_app(f)->get_arg(1))) + axioms.mark(f); + break; + case OP_AND: + case OP_XOR: + case OP_IMPLIES: + case OP_DISTINCT: + UNREACHABLE(); + break; + default: + break; + } + } + } + + struct unmark_axioms_proc { + expr_fast_mark2 & m_axioms; + unmark_axioms_proc(expr_fast_mark2 & axioms):m_axioms(axioms) {} + void operator()(quantifier *) {} + void operator()(var *) {} + void operator()(app * t) { + m_axioms.reset_mark(t); + } + }; + + /** + \brief Unmark atoms that occur in the boolean structure. + */ + void formula_compiler::unmark_nested_atoms(assertion_set const & s, expr_fast_mark2 & axioms) { + ast_manager & m = s.m(); + expr_fast_mark1 visited; + unmark_axioms_proc proc(axioms); + unsigned sz = s.size(); + for (unsigned i = 0; i < sz; i++) { + expr * f = s.form(i); + while (m.is_not(f, f)); + if (axioms.is_marked(f)) + continue; + quick_for_each_expr(proc, visited, f); + } + } + + void formula_compiler::assert_axiom(expr * f, bool neg) { + if (is_app(f)) { + if (to_app(f)->get_family_id() == m_a_util.get_family_id()) + s.m_arith.assert_axiom(f, neg); + } + } + + void formula_compiler::register_atom(expr * f, sat::bool_var p) { + if (is_app(f)) { + if (to_app(f)->get_family_id() == m_a_util.get_family_id()) + s.m_arith.mk_atom(f, p); + } + } + + void formula_compiler::compile_formulas(assertion_set const & assertions) { + ast_manager & m = assertions.m(); + expr_fast_mark2 axioms; + mark_axioms(assertions, axioms); + unmark_nested_atoms(assertions, axioms); + ptr_vector formulas; + + // send axioms to theories, and save formulas to compile + unsigned sz = assertions.size(); + for (unsigned i = 0; i < sz; i++) { + expr * f = assertions.form(i); + bool neg = false; + while (m.is_not(f, f)) + neg = !neg; + if (axioms.is_marked(f)) { + assert_axiom(f, neg); + } + else { + formulas.push_back(f); + } + } + + // compile formulas into sat::solver + m_to_sat(m, formulas.size(), formulas.c_ptr(), s.m_params, *(s.m_sat), s.m_atom2bvar); + + // register atoms nested in the boolean structure in the theories + atom2bool_var::recent_iterator it = s.m_atom2bvar.begin_recent(); + atom2bool_var::recent_iterator end = s.m_atom2bvar.end_recent(); + for (; it != end; ++it) { + expr * atom = *it; + register_atom(atom, s.m_atom2bvar.to_bool_var(atom)); + } + s.m_atom2bvar.reset_recent(); + } + + void formula_compiler::normalize() { + // make sure that the assertions are in the right format. + m_normalizer(s.m_assertions); + m_normalizer.cleanup(); + } + + void formula_compiler::elim_term_ite() { + if (has_term_ite(s.m_assertions)) { + model_converter_ref mc; + m_elim_ite(s.m_assertions, mc); + s.m_mc = concat(s.m_mc.get(), mc.get()); + m_elim_ite.cleanup(); + } + } + + void formula_compiler::operator()() { + if (s.m_assertions.inconsistent()) + return; + // normalization + elim_term_ite(); + normalize(); + + TRACE("before_formula_compiler", s.m_assertions.display(tout);); + + s.init(); + + compile_formulas(s.m_assertions); + + s.m_arith.preprocess(); + TRACE("after_formula_compiler", s.display_state(tout);); + } + + void formula_compiler::updt_params(params_ref const & p) { + // TODO + } + + void formula_compiler::collect_param_descrs(param_descrs & d) { + // TODO + } + + void formula_compiler::collect_statistics(statistics & st) { + // TODO + } + + void formula_compiler::reset_statistics() { + // TODO + } + + void formula_compiler::set_cancel(bool f) { + m_normalizer.set_cancel(f); + m_elim_ite.set_cancel(f); + m_to_sat.set_cancel(f); + } + +}; diff --git a/lib/smt_formula_compiler.h b/lib/smt_formula_compiler.h new file mode 100644 index 000000000..6bd9ad913 --- /dev/null +++ b/lib/smt_formula_compiler.h @@ -0,0 +1,63 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + smt_formula_compiler.h + +Abstract: + + Auxiliary class for smt::solver + Convert Exprs into Internal data-structures. + +Author: + + Leonardo de Moura (leonardo) 2011-06-25. + +Revision History: + +--*/ +#ifndef _SMT_FORMULA_COMPILER_H_ +#define _SMT_FORMULA_COMPILER_H_ + +#include"smt_solver_types.h" +#include"assertion_set_rewriter.h" +#include"elim_term_ite_strategy.h" +#include"arith_decl_plugin.h" +#include"assertion_set2sat.h" + +namespace smt { + + class formula_compiler { + solver_exp & s; + arith_util m_a_util; + assertion_set_rewriter m_normalizer; + elim_term_ite_strategy m_elim_ite; + assertion_set2sat m_to_sat; + + void normalize(); + void elim_term_ite(); + void mark_axioms(assertion_set const & s, expr_fast_mark2 & axioms); + void unmark_nested_atoms(assertion_set const & s, expr_fast_mark2 & axioms); + void assert_axiom(expr * f, bool neg); + void register_atom(expr * f, sat::bool_var p); + void compile_formulas(assertion_set const & assertions); + + public: + formula_compiler(solver_exp & s, params_ref const & p); + ~formula_compiler(); + + void updt_params(params_ref const & p); + static void collect_param_descrs(param_descrs & d); + + void operator()(); + + void set_cancel(bool f); + + void collect_statistics(statistics & st); + void reset_statistics(); + }; + +}; + +#endif diff --git a/lib/smt_implied_equalities.cpp b/lib/smt_implied_equalities.cpp new file mode 100644 index 000000000..1dd0c412b --- /dev/null +++ b/lib/smt_implied_equalities.cpp @@ -0,0 +1,619 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + smt_implied_equalities.cpp + +Abstract: + + Procedure for obtaining implied equalities relative to the + state of a solver. + +Author: + + Nikolaj Bjorner (nbjorner) 2012-02-29 + +Revision History: + + +--*/ + +#include "smt_implied_equalities.h" +#include "union_find.h" +#include "cmd_context.h" +#include "parametric_cmd.h" +#include "ast_pp.h" +#include "arith_decl_plugin.h" +#include "datatype_decl_plugin.h" +#include "array_decl_plugin.h" +#include "uint_set.h" +#include "model_v2_pp.h" + + +namespace smt { + + class get_implied_equalities_impl { + + ast_manager& m; + smt::solver& m_solver; + union_find_default_ctx m_df; + union_find m_uf; + array_util m_array_util; + stopwatch m_stats_timer; + unsigned m_stats_calls; + stopwatch m_stats_val_eq_timer; + static stopwatch s_timer; + static stopwatch s_stats_val_eq_timer; + + struct term_id { + expr_ref term; + unsigned id; + term_id(expr_ref t, unsigned id): term(t), id(id) {} + }; + + typedef vector term_ids; + + typedef obj_map sort2term_ids; // partition of terms by sort. + + void partition_terms(unsigned num_terms, expr* const* terms, sort2term_ids& termids) { + for (unsigned i = 0; i < num_terms; ++i) { + sort* s = m.get_sort(terms[i]); + term_ids& vec = termids.insert_if_not_there2(s, term_ids())->get_data().m_value; + vec.push_back(term_id(expr_ref(terms[i],m), i)); + } + } + + /** + \brief Basic implied equalities method. + It performs a simple N^2 loop over all pairs of terms. + + n1, .., n_k, + t1, .., t_l + */ + + void get_implied_equalities_filter_basic(uint_set const& non_values, term_ids& terms) { + m_stats_timer.start(); + uint_set root_indices; + for (unsigned j = 0; j < terms.size(); ++j) { + if (terms[j].id == m_uf.find(terms[j].id)) { + root_indices.insert(j); + } + } + uint_set::iterator it = non_values.begin(), end = non_values.end(); + + for (; it != end; ++it) { + unsigned i = *it; + expr* t = terms[i].term; + uint_set::iterator it2 = root_indices.begin(), end2 = root_indices.end(); + bool found_root_value = false; + for (; it2 != end2; ++it2) { + unsigned j = *it2; + if (j == i) continue; + if (j < i && non_values.contains(j)) continue; + if (found_root_value && !non_values.contains(j)) continue; + expr* s = terms[j].term; + SASSERT(m.get_sort(t) == m.get_sort(s)); + ++m_stats_calls; + m_solver.push(); + m_solver.assert_expr(m.mk_not(m.mk_eq(s, t))); + bool is_eq = l_false == m_solver.check(); + m_solver.pop(1); + TRACE("get_implied_equalities", tout << mk_pp(t, m) << " = " << mk_pp(s, m) << " " << (is_eq?"eq":"unrelated") << "\n";); + if (is_eq) { + m_uf.merge(terms[i].id, terms[j].id); + if (!non_values.contains(j)) { + found_root_value = true; + } + } + } + } + m_stats_timer.stop(); + } + + void get_implied_equalities_basic(term_ids& terms) { + for (unsigned i = 0; i < terms.size(); ++i) { + if (terms[i].id != m_uf.find(terms[i].id)) { + continue; + } + expr* t = terms[i].term; + for (unsigned j = 0; j < i; ++j) { + expr* s = terms[j].term; + SASSERT(m.get_sort(t) == m.get_sort(s)); + ++m_stats_calls; + m_stats_timer.start(); + m_solver.push(); + m_solver.assert_expr(m.mk_not(m.mk_eq(s, t))); + bool is_eq = l_false == m_solver.check(); + m_solver.pop(1); + m_stats_timer.stop(); + TRACE("get_implied_equalities", tout << mk_pp(t, m) << " = " << mk_pp(s, m) << " " << (is_eq?"eq":"unrelated") << "\n";); + if (is_eq) { + m_uf.merge(terms[i].id, terms[j].id); + break; + } + } + } + } + + bool is_simple_type(sort* s) { + arith_util arith(m); + datatype_util data(m); + + ptr_vector sorts; + ast_mark mark; + sorts.push_back(s); + + while (!sorts.empty()) { + s = sorts.back(); + sorts.pop_back(); + if (mark.is_marked(s)) { + continue; + } + mark.mark(s, true); + if (arith.is_int_real(s)) { + // simple + } + else if (m.is_bool(s)) { + // simple + } + else if (data.is_datatype(s)) { + ptr_vector const& cs = *data.get_datatype_constructors(s); + for (unsigned i = 0; i < cs.size(); ++i) { + func_decl* f = cs[i]; + for (unsigned j = 0; j < f->get_arity(); ++j) { + sorts.push_back(f->get_domain(j)); + } + } + } + else { + return false; + } + } + return true; + } + + /** + \brief Extract implied equalities for a collection of terms in the current context. + + The routine relies on model values being unique for equal terms. + So in particular, arrays that are equal should be canonized to the same value. + This is not the case for Z3's models of arrays. + Arrays are treated by extensionality: introduce a fresh index and compare + the select of the arrays. + */ + void get_implied_equalities_model_based(model_ref& model, term_ids& terms) { + + SASSERT(!terms.empty()); + + sort* srt = m.get_sort(terms[0].term); + + if (m_array_util.is_array(srt)) { + + m_solver.push(); + unsigned arity = get_array_arity(srt); + expr_ref_vector args(m); + args.push_back(0); + for (unsigned i = 0; i < arity; ++i) { + sort* srt_i = get_array_domain(srt, i); + expr* idx = m.mk_fresh_const("index", srt_i); + args.push_back(idx); + } + for (unsigned i = 0; i < terms.size(); ++i) { + args[0] = terms[i].term; + terms[i].term = m.mk_app(m_array_util.get_family_id(), OP_SELECT, 0, 0, args.size(), args.c_ptr()); + } + assert_relevant(terms); + lbool is_sat = m_solver.check(); + model_ref model1; + m_solver.get_model(model1); + SASSERT(model1.get()); + SASSERT(is_sat != l_false); + get_implied_equalities_model_based(model1, terms); + m_solver.pop(1); + return; + } + + uint_set non_values; + + if (!is_simple_type(srt)) { + for (unsigned i = 0; i < terms.size(); ++i) { + non_values.insert(i); + } + get_implied_equalities_filter_basic(non_values, terms); + //get_implied_equalities_basic(terms); + return; + } + + expr_ref_vector vals(m); + expr_ref vl(m), eq(m); + obj_map vals_map; + + m_stats_val_eq_timer.start(); + s_stats_val_eq_timer.start(); + + params_ref p; + p.set_bool(":produce-models", false); + m_solver.updt_params(p); + + for (unsigned i = 0; i < terms.size(); ++i) { + expr* t = terms[i].term; + model->eval(t, vl); + TRACE("get_implied_equalities", tout << mk_pp(t, m) << " |-> " << mk_pp(vl, m) << "\n";); + reduce_value(model, vl); + if (!m.is_value(vl)) { + TRACE("get_implied_equalities", tout << "Not a value: " << mk_pp(vl, m) << "\n";); + non_values.insert(i); + continue; + } + vals.push_back(vl); + unsigned_vector& vec = vals_map.insert_if_not_there2(vl, unsigned_vector())->get_data().m_value; + bool found = false; + + for (unsigned j = 0; !found && j < vec.size(); ++j) { + expr* s = terms[vec[j]].term; + m_solver.push(); + m_solver.assert_expr(m.mk_not(m.mk_eq(t, s))); + lbool is_sat = m_solver.check(); + m_solver.pop(1); + TRACE("get_implied_equalities", tout << mk_pp(t, m) << " = " << mk_pp(s, m) << " " << is_sat << "\n";); + if (is_sat == l_false) { + found = true; + m_uf.merge(terms[i].id, terms[vec[j]].id); + } + } + if (!found) { + vec.push_back(i); + } + } + m_stats_val_eq_timer.stop(); + s_stats_val_eq_timer.stop(); + p.set_bool(":produce-models", true); + m_solver.updt_params(p); + + + if (!non_values.empty()) { + TRACE("get_implied_equalities", model_v2_pp(tout, *model, true);); + get_implied_equalities_filter_basic(non_values, terms); + //get_implied_equalities_basic(terms); + } + } + + + void get_implied_equalities_core(model_ref& model, term_ids& terms) { + get_implied_equalities_model_based(model, terms); + //get_implied_equalities_basic(terms); + } + + + void assert_relevant(unsigned num_terms, expr* const* terms) { + for (unsigned i = 0; i < num_terms; ++i) { + sort* srt = m.get_sort(terms[i]); + if (!m_array_util.is_array(srt)) { + m_solver.assert_expr(m.mk_app(m.mk_func_decl(symbol("Relevant!"), 1, &srt, m.mk_bool_sort()), terms[i])); + } + } + } + + void assert_relevant(term_ids& terms) { + for (unsigned i = 0; i < terms.size(); ++i) { + expr* t = terms[i].term; + sort* srt = m.get_sort(t); + if (!m_array_util.is_array(srt)) { + m_solver.assert_expr(m.mk_app(m.mk_func_decl(symbol("Relevant!"), 1, &srt, m.mk_bool_sort()), t)); + } + } + } + + void reduce_value(model_ref& model, expr_ref& vl) { + expr* c, *e1, *e2; + while (m.is_ite(vl, c, e1, e2)) { + lbool r = reduce_cond(model, c); + switch(r) { + case l_true: + vl = e1; + break; + case l_false: + vl = e2; + break; + default: + return; + } + } + } + + lbool reduce_cond(model_ref& model, expr* e) { + expr* e1, *e2; + if (m.is_eq(e, e1, e2) && m_array_util.is_as_array(e1) && m_array_util.is_as_array(e2)) { + if (e1 == e2) { + return l_true; + } + func_decl* f1 = m_array_util.get_as_array_func_decl(to_app(e1)); + func_decl* f2 = m_array_util.get_as_array_func_decl(to_app(e2)); + func_interp* fi1 = model->get_func_interp(f1); + func_interp* fi2 = model->get_func_interp(f2); + if (fi1 == fi2) { + return l_true; + } + unsigned n1 = fi1->num_entries(); + for (unsigned i = 0; i < n1; ++i) { + func_entry const* h1 = fi1->get_entry(i); + for (unsigned j = 0; j < fi1->get_arity(); ++j) { + if (!m.is_value(h1->get_arg(j))) { + return l_undef; + } + } + func_entry* h2 = fi2->get_entry(h1->get_args()); + if (h2 && + h1->get_result() != h2->get_result() && + m.is_value(h1->get_result()) && + m.is_value(h2->get_result())) { + return l_false; + } + } + } + return l_undef; + } + + public: + + get_implied_equalities_impl(smt::solver& s) : m(s.m()), m_solver(s), m_uf(m_df), m_array_util(m), m_stats_calls(0) {} + + lbool operator()(unsigned num_terms, expr* const* terms, unsigned* class_ids) { + params_ref p; + p.set_bool(":produce-models", true); + m_solver.updt_params(p); + sort2term_ids termids; + stopwatch timer; + timer.start(); + s_timer.start(); + + for (unsigned i = 0; i < num_terms; ++i) { + m_uf.mk_var(); + } + + m_solver.push(); + assert_relevant(num_terms, terms); + lbool is_sat = m_solver.check(); + + if (is_sat != l_false) { + model_ref model; + m_solver.get_model(model); + SASSERT(model.get()); + + partition_terms(num_terms, terms, termids); + sort2term_ids::iterator it = termids.begin(), end = termids.end(); + for (; it != end; ++it) { + term_ids& term_ids = it->m_value; + get_implied_equalities_core(model, term_ids); + for (unsigned i = 0; i < term_ids.size(); ++i) { + class_ids[term_ids[i].id] = m_uf.find(term_ids[i].id); + } + } + TRACE("get_implied_equalities", + for (unsigned i = 0; i < num_terms; ++i) { + tout << mk_pp(terms[i], m) << " |-> " << class_ids[i] << "\n"; + }); + } + m_solver.pop(1); + timer.stop(); + s_timer.stop(); + IF_VERBOSE(1, verbose_stream() << s_timer.get_seconds() << "\t" << num_terms << "\t" + << timer.get_seconds() << "\t" << m_stats_calls << "\t" + << m_stats_timer.get_seconds() << "\t" + << m_stats_val_eq_timer.get_seconds() << "\t" + << s_stats_val_eq_timer.get_seconds() << "\n";); + return is_sat; + } + }; + + stopwatch get_implied_equalities_impl::s_timer; + stopwatch get_implied_equalities_impl::s_stats_val_eq_timer; + + lbool implied_equalities(smt::solver& solver, unsigned num_terms, expr* const* terms, unsigned* class_ids) { + get_implied_equalities_impl gi(solver); + return gi(num_terms, terms, class_ids); + } +}; + + + + + + + +#if 0 + // maxsat class for internal purposes. + class maxsat { + ast_manager& m; + solver& m_solver; + public: + maxsat(solver& s) : m(s.m()), m_solver(s) {} + + lbool operator()(ptr_vector& soft_cnstrs) { + return l_undef; + } + + }; + + class term_equivs { + union_find_default_ctx m_df; + union_find m_uf; + obj_map m_term2idx; + ptr_vector m_idx2term; + + public: + term_equivs(): m_uf(m_df) {} + + void merge(expr* t, expr* s) { + m_uf.merge(var(t), var(s)); + } + private: + unsigned var(expr* t) { + map::obj_map_entry* e = m_term2idx.insert_if_not_there(t, m_idx2term.size()); + unsigned idx = e->get_data().m_value; + if (idx == m_idx2term.size()) { + m_idx2term.push_back(t); + } + return idx; + } + }; + + /** + \brief class to find implied equalities. + + It implements the following half-naive algorithm. + The algorithm is half-naive because the terms being checked for equivalence class membership + are foreign and it is up to the theory integration whether pairs of interface equalities + are checked. The idea is that the model-based combination would avoid useless equality literals + in the core. + An alternative algorithm could use 'distinct' and an efficient solver for 'distinct'. + + Given terms t1, ..., tn, of the same type. + - assert f(t1) = 1, .., f(tn) = n. + - find MAX-SAT set A1, let the other literals be in B. + - find MAX-SAT set of B, put it in A2, etc. + - we now have MAX-SAT sets A1, A2, ... A_m. + - terms in each set A_i can be different, but cannot be different at the same time as elements in A_{i+1}. + - for i = m to 2 do: + - Let A = A_i B = A_{i-1} + - assert g(A) = 0, g(B) = 1 + - find MAX-SAT set C over this constraint. + - For each element t from A\C + - check if g(t) = 0 and g(B) = 1 is unsat + - minimize core, if there is pair such that + - g(t) = 0, g(b) = 1 is unsat, then equality is forced. + */ + + class implied_equalities_finder { + ast_manager& m; + solver& m_solver; + term_equivs m_find; + expr_ref_vector m_refs; + obj_map m_fs; // t_i -> f(t_i) = i + obj_map m_gs; // t_i -> g(t_i) + + public: + implied_equalities_finder(solver& solver): m(solver.m()), m_solver(solver), m_refs(m) {} + + lbool operator()(unsigned num_terms, expr* const* terms, unsigned* class_ids) { + m_find.reset(); + // + return l_undef; + } + private: + + void initialize(unsigned num_terms, expr* const* terms) { + sort_ref bv(m); + expr_ref eq(m), g(m), eq_proxy(m); + symbol f("f"), g("g"); + unsigned log_terms = 1, nt = num_terms; + while (nt > 0) { log_terms++; nt /= 2; } + + bv = m_bv.mk_bv_sort(log_terms); + for (unsigned i = 0; i < num_terms; ++i) { + expr* t = terms[i]; + sort* s = m.get_sort(t); + eq = m.mk_eq(m.mk_app(m.mk_func_decl(f, 1, &s, bv), t), m_bv.mk_numeral(rational(i), bv)); + eq_proxy = m.mk_fresh_const("f", m.mk_bool_sort()); + m_solver.assert_expr(m.mk_iff(eq, eq_proxy)); + g = m.mk_app(m.mk_func_decl(g, 1, &s, bv), t) + m_fs.insert(t, eq_proxy); + m_gs.insert(t, g); + } + } + + // + // For each t in src, check if t can be different from all s in dst. + // - if it can, then add t to dst. + // - if it cannot, then record equivalence class. + // + void merge_classes(expr_ref_vector& src, expr_ref_vector& dst, equivs& eqs) { + + } + }; + + lbool implied_equalities_core_based( + solver& solver, + unsigned num_terms, expr* const* terms, + unsigned* class_ids, + unsigned num_assumptions, expr * const * assumptions) { + implied_equalities_finder ief(solver); + + solver.push(); + for (unsigned i = 0; i < num_assumptions; ++i) { + solver.assert_expr(assumptions[i]); + } + lbool is_sat = ief(num_terms, terms, class_ids); + solver.pop(1); + + return is_sat; + } + + /** + \brief Extract implied equalities for a collection of terms in the current context. + + The routine uses a partition refinement approach. + It assumes that all terms have the same sort. + + Initially, create the equalities E_1: t0 = t1, E_2: t1 = t2, ..., E_n: t_{n-1} = t_n + + Check if ! (E_1 & E_2 & ... & E_n) is satisfiable. + + if it is unsat, then all terms are equal. + Otherwise, partition the terms by the equalities that are true in the current model, + iterate. + + + This version does not attempt to be economical on how many equalities are introduced and the + size of the resulting clauses. The more advanced version of this approach re-uses + equalities from a previous iteration and also represents a binary tree of propositional variables + that cover multiple equalities. Eg., + + E_12 => E_1 & E_2, E_34 => E_3 & E_4, ... + + + */ + + void get_implied_equalities_eq_based(term_ids& terms) { + expr_ref_vector eqs(m); + if (terms.size() == 1) { + return; + } + m_solver.push(); + for (unsigned i = 0; i + 1 < terms.size(); ++i) { + expr* eq = m.mk_eq(terms[i].term, terms[i+1].term); + expr* eq_lit = m.mk_fresh_const("E", m.mk_bool_sort()); + eqs.push_back(eq_lit); + m_solver.assert_expr(m.mk_implies(eq_lit, eq)); + } + m_solver.assert_expr(m.mk_not(m.mk_and(eqs.size(), eqs.c_ptr()))); + lbool is_sat = m_solver.check(); + switch(is_sat) { + case l_false: + for (unsigned i = 0; i + 1 < terms.size(); ++i) { + m_uf.merge(terms[i].id, terms[i+1].id); + } + break; + default: { + term_ids tems2; + for (unsigned i = 0; i + 1 < terms.size(); ++i) { + expr_ref vl(m); + model->eval(terms[i].term, vl); + if (m.is_false(vl)) { + + } + } + break; + } + } + m_solver.pop(1); + } + + +#endif + + + + + diff --git a/lib/smt_implied_equalities.h b/lib/smt_implied_equalities.h new file mode 100644 index 000000000..dfb9bf611 --- /dev/null +++ b/lib/smt_implied_equalities.h @@ -0,0 +1,40 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + smt_implied_equalities.h + +Abstract: + + Procedure for obtaining implied equalities relative to the + state of a solver. + +Author: + + Nikolaj Bjorner (nbjorner) 2012-02-29 + +Revision History: + + +--*/ + + +#ifndef __SMT_IMPLIED_EQUALITIES_H__ +#define __SMT_IMPLIED_EQUALITIES_H__ + +#include"smt_solver.h" + + +namespace smt { + + lbool implied_equalities( + solver& solver, + unsigned num_terms, expr* const* terms, + unsigned* class_ids); + + +}; + + +#endif diff --git a/lib/smt_internalizer.cpp b/lib/smt_internalizer.cpp new file mode 100644 index 000000000..86b1641bc --- /dev/null +++ b/lib/smt_internalizer.cpp @@ -0,0 +1,1700 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_internalizer.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-20. + +Revision History: + +--*/ +#include"smt_context.h" +#include"expr_stat.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"ast_smt2_pp.h" +#include"smt_model_finder.h" +#include"for_each_expr.h" +#include"theory_instgen.h" + +namespace smt { + + /** + \brief Return true if the expression is viewed as a logical gate. + */ + inline bool is_gate(ast_manager const & m, expr * n) { + if (is_app(n) && to_app(n)->get_family_id() == m.get_basic_family_id()) { + switch (to_app(n)->get_decl_kind()) { + case OP_AND: + case OP_OR: + case OP_IFF: + case OP_ITE: + return true; + default: + return false; + } + } + return false; + } + +#define White 0 +#define Grey 1 +#define Black 2 + + static int get_color(svector & tcolors, svector & fcolors, expr * n, bool gate_ctx) { + svector & colors = gate_ctx ? tcolors : fcolors; + if (colors.size() > n->get_id()) + return colors[n->get_id()]; + return White; + } + + static void set_color(svector & tcolors, svector & fcolors, expr * n, bool gate_ctx, int color) { + svector & colors = gate_ctx ? tcolors : fcolors; + if (colors.size() <= n->get_id()) { + colors.resize(n->get_id()+1, White); + } + colors[n->get_id()] = color; + } + + /** + \brief Return the foreign descendants of n. That is, the descendants of n where the family_id is different from fid. + For example the descendants of (+ a (+ (f b) (* 2 (h (+ c d))))) are: + - a + - (f b) + - (h (+ c d)) + */ + static void get_foreign_descendants(app * n, family_id fid, ptr_buffer & descendants) { + SASSERT(n->get_family_id() == fid); + SASSERT(fid != null_family_id); + ptr_buffer todo; + todo.push_back(n); + ast_mark visited; + while (!todo.empty()) { + expr * curr = todo.back(); + todo.pop_back(); + if (visited.is_marked(n)) { + continue; + } + visited.mark(n, true); + if (!is_app(curr) || to_app(curr)->get_family_id() != fid) { + descendants.push_back(curr); + continue; + } + + SASSERT(is_app(curr)); + SASSERT(to_app(curr)->get_family_id() == fid); + unsigned j = to_app(curr)->get_num_args(); + while (j > 0) { + --j; + todo.push_back(to_app(curr)->get_arg(j)); + } + } + } + + void context::ts_visit_child(expr * n, bool gate_ctx, svector & tcolors, svector< int> & fcolors, svector & todo, bool & visited) { + if (get_color(tcolors, fcolors, n, gate_ctx) == White) { + todo.push_back(expr_bool_pair(n, gate_ctx)); + visited = false; + } + } + + bool context::ts_visit_children(expr * n, bool gate_ctx, svector & tcolors, svector & fcolors, svector & todo) { + if (is_quantifier(n)) + return true; + SASSERT(is_app(n)); + if (m_manager.is_bool(n)) { + if (b_internalized(n)) + return true; + } + else { + if (e_internalized(n)) + return true; + } + + bool visited = true; + family_id fid = to_app(n)->get_family_id(); + theory * th = m_theories.get_plugin(fid); + bool def_int = th == 0 || th->default_internalizer(); + if (!def_int) { + ptr_buffer descendants; + get_foreign_descendants(to_app(n), fid, descendants); + ptr_buffer::iterator it = descendants.begin(); + ptr_buffer::iterator end = descendants.end(); + for (; it != end; ++it) { + expr * arg = *it; + ts_visit_child(arg, false, tcolors, fcolors, todo, visited); + } + return visited; + } + + SASSERT(def_int); + + if (m_manager.is_term_ite(n)) { + ts_visit_child(to_app(n)->get_arg(0), true, tcolors, fcolors, todo, visited); + ts_visit_child(to_app(n)->get_arg(1), false, tcolors, fcolors, todo, visited); + ts_visit_child(to_app(n)->get_arg(2), false, tcolors, fcolors, todo, visited); + return visited; + } + bool new_gate_ctx = m_manager.is_bool(n) && (is_gate(m_manager, n) || m_manager.is_not(n)); + unsigned j = to_app(n)->get_num_args(); + while (j > 0) { + --j; + expr * arg = to_app(n)->get_arg(j); + ts_visit_child(arg, new_gate_ctx, tcolors, fcolors, todo, visited); + } + return visited; + } + + void context::top_sort_expr(expr * n, svector & sorted_exprs) { + svector todo; + svector tcolors; + svector fcolors; + todo.push_back(expr_bool_pair(n, true)); + while (!todo.empty()) { + expr_bool_pair & p = todo.back(); + expr * curr = p.first; + bool gate_ctx = p.second; + switch (get_color(tcolors, fcolors, curr, gate_ctx)) { + case White: + set_color(tcolors, fcolors, curr, gate_ctx, Grey); + ts_visit_children(curr, gate_ctx, tcolors, fcolors, todo); + break; + case Grey: + SASSERT(ts_visit_children(curr, gate_ctx, tcolors, fcolors, todo)); + set_color(tcolors, fcolors, curr, gate_ctx, Black); + if (n != curr && !m_manager.is_not(curr)) + sorted_exprs.push_back(expr_bool_pair(curr, gate_ctx)); + break; + case Black: + todo.pop_back(); + break; + default: + UNREACHABLE(); + } + } + } + +#define DEEP_EXPR_THRESHOLD 1024 + + /** + \brief Internalize an expression asserted into the logical context using the given proof as a justification. + + \remark pr is 0 if proofs are disabled. + */ + void context::internalize_assertion(expr * n, proof * pr, unsigned generation) { + TRACE("internalize_assertion", tout << mk_pp(n, m_manager) << "\n";); + TRACE("internalize_assertion_ll", tout << mk_ll_pp(n, m_manager) << "\n";); + TRACE("generation", tout << "generation: " << m_generation << "\n";); + TRACE("incompleteness_bug", tout << "[internalize-assertion]: #" << n->get_id() << "\n";); + flet l(m_generation, generation); + m_stats.m_max_generation = std::max(m_generation, m_stats.m_max_generation); + if (get_depth(n) > DEEP_EXPR_THRESHOLD) { + // if the expression is deep, then execute topological sort to avoid + // stack overflow. + TRACE("deep_internalize", tout << "expression is deep: #" << n->get_id() << "\n" << mk_ll_pp(n, m_manager);); + svector sorted_exprs; + top_sort_expr(n, sorted_exprs); + TRACE("deep_internalize", + svector::const_iterator it = sorted_exprs.begin(); + svector::const_iterator end = sorted_exprs.end(); + for (; it != end; ++it) { + tout << "#" << it->first->get_id() << " " << it->second << "\n"; + }); + svector::const_iterator it = sorted_exprs.begin(); + svector::const_iterator end = sorted_exprs.end(); + for (; it != end; ++it) + internalize(it->first, it->second); + } + SASSERT(m_manager.is_bool(n)); + if (is_gate(m_manager, n)) { + switch(to_app(n)->get_decl_kind()) { + case OP_AND: + UNREACHABLE(); + case OP_OR: { + literal_buffer lits; + unsigned num = to_app(n)->get_num_args(); + for (unsigned i = 0; i < num; i++) { + expr * arg = to_app(n)->get_arg(i); + internalize(arg, true); + lits.push_back(get_literal(arg)); + } + mk_root_clause(lits.size(), lits.c_ptr(), pr); + add_or_rel_watches(to_app(n)); + break; + } + case OP_IFF: { + expr * lhs = to_app(n)->get_arg(0); + expr * rhs = to_app(n)->get_arg(1); + internalize(lhs, true); + internalize(rhs, true); + literal l1 = get_literal(lhs); + literal l2 = get_literal(rhs); + mk_root_clause(l1, ~l2, pr); + mk_root_clause(~l1, l2, pr); + break; + } + case OP_ITE: { + expr * c = to_app(n)->get_arg(0); + expr * t = to_app(n)->get_arg(1); + expr * e = to_app(n)->get_arg(2); + internalize(c, true); + internalize(t, true); + internalize(e, true); + literal cl = get_literal(c); + literal tl = get_literal(t); + literal el = get_literal(e); + mk_root_clause(~cl, tl, pr); + mk_root_clause(cl, el, pr); + add_ite_rel_watches(to_app(n)); + break; + } + default: + UNREACHABLE(); + } + mark_as_relevant(n); + } + else if (m_manager.is_distinct(n)) { + assert_distinct(to_app(n), pr); + mark_as_relevant(n); + } + else { + assert_default(n, pr); + } + } + + void context::assert_default(expr * n, proof * pr) { + internalize(n, true); + literal l = get_literal(n); + if (l == false_literal) { + set_conflict(mk_justification(justification_proof_wrapper(*this, pr))); + } + else { + assign(l, mk_justification(justification_proof_wrapper(*this, pr))); + mark_as_relevant(l); + } + } + +#define DISTINCT_SZ_THRESHOLD 32 + + void context::assert_distinct(app * n, proof * pr) { + TRACE("assert_distinct", tout << mk_pp(n, m_manager) << "\n";); + unsigned num_args = n->get_num_args(); + if (num_args == 0 || num_args <= DISTINCT_SZ_THRESHOLD || m_manager.proofs_enabled()) { + assert_default(n, pr); + return; + } + sort * s = m_manager.get_sort(n->get_arg(0)); + sort * u = m_manager.mk_fresh_sort("distinct-elems"); + func_decl * f = m_manager.mk_fresh_func_decl("distinct-aux-f", "", 1, &s, u); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = n->get_arg(i); + app * fapp = m_manager.mk_app(f, arg); + app * val = m_manager.mk_fresh_const("unique-value", u); + enode * e = mk_enode(val, false, false, true); + e->mark_as_interpreted(); + app * eq = m_manager.mk_eq(fapp, val); + TRACE("assert_distinct", tout << "eq: " << mk_pp(eq, m_manager) << "\n";); + assert_default(eq, 0); + mark_as_relevant(eq); + // TODO: we may want to hide the auxiliary values val and the function f from the model. + } + } + + void context::internalize(expr * n, bool gate_ctx, unsigned generation) { + flet l(m_generation, generation); + m_stats.m_max_generation = std::max(m_generation, m_stats.m_max_generation); + internalize(n, gate_ctx); + } + + /** + \brief Internalize the given expression into the logical context. + + - gate_ctx is true if the expression is in the context of a logical gate. + */ + void context::internalize(expr * n, bool gate_ctx) { + TRACE("internalize", tout << "internalizing:\n" << mk_pp(n, m_manager) << "\n";); + TRACE("internalize_bug", tout << "internalizing:\n" << mk_bounded_pp(n, m_manager) << "\n";); + if (m_manager.is_bool(n)) { + SASSERT(is_quantifier(n) || is_app(n)); + internalize_formula(n, gate_ctx); + } + else { + SASSERT(is_app(n)); + SASSERT(!gate_ctx); + internalize_term(to_app(n)); + } + } + + bool find_arg(app * n, expr * t, expr * & other) { + SASSERT(n->get_num_args() == 2); + if (n->get_arg(0) == t) { + other = n->get_arg(1); + return true; + } + else if (n->get_arg(1) == t) { + other = n->get_arg(0); + return true; + } + return false; + } + + bool check_args(app * n, expr * t1, expr * t2) { + SASSERT(n->get_num_args() == 2); + return (n->get_arg(0) == t1 && n->get_arg(1) == t2) || (n->get_arg(1) == t1 && n->get_arg(0) == t2); + } + + /** + \brief Internalize the given formula into the logical context. + */ + void context::internalize_formula(expr * n, bool gate_ctx) { + TRACE("internalize_bug", tout << "internalize formula: #" << n->get_id() << ", gate_ctx: " << gate_ctx << "\n" << mk_pp(n, m_manager) << "\n";); + SASSERT(m_manager.is_bool(n)); + if (m_manager.is_true(n) || m_manager.is_false(n)) + return; + + if (m_manager.is_not(n) && gate_ctx) { + // a boolean variable does not need to be created if n a NOT gate is in + // the context of a gate. + internalize(to_app(n)->get_arg(0), true); + return; + } + + if (b_internalized(n)) { + // n was already internalized as a boolean. + bool_var v = get_bool_var(n); + TRACE("internalize_bug", tout << "#" << n->get_id() << " already has bool_var v" << v << "\n";); + + // n was already internalized as boolean, but an enode was + // not associated with it. So, an enode is necessary, if + // n is not in the context of a gate and is an application. + if (!gate_ctx && is_app(n)) { + if (e_internalized(n)) { + TRACE("internalize_bug", tout << "forcing enode #" << n->get_id() << " to merge with t/f\n";); + enode * e = get_enode(to_app(n)); + set_merge_tf(e, v, false); + } + else { + TRACE("internalize_bug", tout << "creating enode for #" << n->get_id() << "\n";); + mk_enode(to_app(n), + true, /* supress arguments, we not not use CC for this kind of enode */ + true, /* bool enode must be merged with true/false, since it is not in the context of a gate */ + false /* CC is not enabled */ ); + set_enode_flag(v, false); + if (get_assignment(v) != l_undef) + propagate_bool_var_enode(v); + } + SASSERT(has_enode(v)); + } + return; + } + + if (m_manager.is_eq(n)) + internalize_eq(to_app(n), gate_ctx); + else if (m_manager.is_distinct(n)) + internalize_distinct(to_app(n), gate_ctx); + else if (is_app(n) && internalize_theory_atom(to_app(n), gate_ctx)) + return; + else if (is_quantifier(n)) + internalize_quantifier(to_quantifier(n), gate_ctx); + else + internalize_formula_core(to_app(n), gate_ctx); + } + + /** + \brief Internalize an equality. + */ + void context::internalize_eq(app * n, bool gate_ctx) { + TRACE("internalize", tout << mk_pp(n, m_manager) << "\n";); + SASSERT(!b_internalized(n)); + SASSERT(m_manager.is_eq(n)); + internalize_formula_core(n, gate_ctx); + bool_var v = get_bool_var(n); + bool_var_data & d = get_bdata(v); + d.set_eq_flag(); + + sort * s = m_manager.get_sort(n->get_arg(0)); + theory * th = m_theories.get_plugin(s->get_family_id()); + if (th) + th->internalize_eq_eh(n, v); + } + + /** + \brief Internalize distinct constructor. + */ + void context::internalize_distinct(app * n, bool gate_ctx) { + TRACE("distinct", tout << "internalizing distinct: " << mk_pp(n, m_manager) << "\n";); + SASSERT(!b_internalized(n)); + SASSERT(m_manager.is_distinct(n)); + expr * def = m_manager.mk_distinct_expanded(n->get_num_args(), n->get_args()); + internalize(def, true); + bool_var v = mk_bool_var(n); + literal l(v); + literal l_def = get_literal(def); + mk_gate_clause(~l, l_def); + mk_gate_clause(l, ~l_def); + add_relevancy_dependency(n, def); + if (!gate_ctx) { + mk_enode(n, true, true, false); + set_enode_flag(v, true); + SASSERT(get_assignment(v) == l_undef); + } + } + + /** + \brief Try to internalize n as a theory atom. Return true if succeeded. + The application can be internalize as a theory atom, if there is a theory (plugin) + that can internalize n. + */ + bool context::internalize_theory_atom(app * n, bool gate_ctx) { + SASSERT(!b_internalized(n)); + theory * th = m_theories.get_plugin(n->get_family_id()); + TRACE("datatype_bug", tout << "internalizing theory atom:\n" << mk_pp(n, m_manager) << "\n";); + if (!th || !th->internalize_atom(n, gate_ctx)) + return false; + TRACE("datatype_bug", tout << "internalization succeeded\n" << mk_pp(n, m_manager) << "\n";); + SASSERT(b_internalized(n)); + TRACE("internalize_theory_atom", tout << "internalizing theory atom: #" << n->get_id() << "\n";); + bool_var v = get_bool_var(n); + if (!gate_ctx) { + // if the formula is not in the context of a gate, then it + // must be associated with an enode. + if (!e_internalized(n)) { + mk_enode(to_app(n), + true, /* supress arguments, we not not use CC for this kind of enode */ + true /* bool enode must be merged with true/false, since it is not in the context of a gate */, + false /* CC is not enabled */); + } + else { + SASSERT(e_internalized(n)); + enode * e = get_enode(n); + set_enode_flag(v, true); + set_merge_tf(e, v, true); + } + } + if (e_internalized(n)) { + set_enode_flag(v, true); + if (get_assignment(v) != l_undef) + propagate_bool_var_enode(v); + } + SASSERT(!e_internalized(n) || has_enode(v)); + return true; + } + +#ifdef Z3DEBUG + struct check_pattern_proc { + void operator()(var * v) {} + void operator()(quantifier * q) {} + void operator()(app * n) { + if (is_ground(n)) + return; + SASSERT(n->get_decl()->is_flat_associative() || n->get_num_args() == n->get_decl()->get_arity()); + } + }; + + /** + Debugging code: check whether for all (non-ground) applications (f a_1 ... a_n) in t, f->get_arity() == n + */ + static bool check_pattern(expr * t) { + check_pattern_proc p; + for_each_expr(p, t); + return true; + } + + static bool check_patterns(quantifier * q) { + for (unsigned i = 0; i < q->get_num_patterns(); i++) { + SASSERT(check_pattern(q->get_pattern(i))); + } + for (unsigned i = 0; i < q->get_num_no_patterns(); i++) { + SASSERT(check_pattern(q->get_no_pattern(i))); + } + return true; + } +#endif + + /** + \brief Internalize the given quantifier into the logical + context. + */ + void context::internalize_quantifier(quantifier * q, bool gate_ctx) { + TRACE("internalize_quantifier", tout << mk_pp(q, m_manager) << "\n";); + CTRACE("internalize_quantifier_zero", q->get_weight() == 0, tout << mk_pp(q, m_manager) << "\n";); + SASSERT(gate_ctx); // limitation of the current implementation + SASSERT(!b_internalized(q)); + SASSERT(q->is_forall()); + SASSERT(check_patterns(q)); + if (m_fparams.m_instgen) { + theory* th = m_theories.get_plugin(m_manager.get_family_id("inst_gen")); + static_cast(th)->internalize_quantifier(q); + return; + } + bool_var v = mk_bool_var(q); + unsigned generation = m_generation; + unsigned _generation; + if (!m_cached_generation.empty() && m_cached_generation.find(q, _generation)) { + generation = _generation; + } + // TODO: do we really need this flag? + bool_var_data & d = get_bdata(v); + d.set_quantifier_flag(); + m_qmanager->add(q, generation); + } + + /** + \brief Internalize gates and (uninterpreted and equality) predicates. + */ + void context::internalize_formula_core(app * n, bool gate_ctx) { + SASSERT(!b_internalized(n)); + SASSERT(!e_internalized(n)); + + CTRACE("resolve_conflict_crash", m_manager.is_not(n), tout << mk_ismt2_pp(n, m_manager) << "\ngate_ctx: " << gate_ctx << "\n";); + + bool _is_gate = is_gate(m_manager, n) || m_manager.is_not(n); + // process args + unsigned num = n->get_num_args(); + for (unsigned i = 0; i < num; i++) { + expr * arg = n->get_arg(i); + internalize(arg, _is_gate); + } + + CTRACE("internalize_bug", b_internalized(n), tout << mk_ll_pp(n, m_manager) << "\n";); + + bool is_new_var = false; + bool_var v; + // n can be already internalized after its children are internalized. + // Example (ite-term): (= (ite c 1 0) 1) + // + // When (ite c 1 0) is internalized, it will force the internalization of (= (ite c 1 0) 1) and (= (ite c 1 0) 0) + // + // TODO: avoid the problem by delaying the internalization of (= (ite c 1 0) 1) and (= (ite c 1 0) 0). + // Add them to a queue. + if (!b_internalized(n)) { + is_new_var = true; + v = mk_bool_var(n); + } + else { + v = get_bool_var(n); + } + + // a formula needs to be associated with an enode when: + // 1) it is not in the context of a gate, or + // 2) it has arguments and it is not a gate (i.e., uninterpreted predicate or equality). + if (!e_internalized(n) && (!gate_ctx || (!_is_gate && n->get_num_args() > 0))) { + bool suppress_args = _is_gate || m_manager.is_not(n); + bool merge_tf = !gate_ctx; + mk_enode(n, suppress_args, merge_tf, true); + set_enode_flag(v, is_new_var); + SASSERT(has_enode(v)); + } + + // The constraints associated with node 'n' should be asserted + // after the bool_var and enode associated with are created. + // Reason: incompleteness. An assigned boolean variable is only inserted + // in m_atom_propagation_queue if the predicate is_atom() is true. + // When the constraints for n are created, they may force v to be assigned. + // Now, if v is assigned before being associated with an enode, then + // v is not going to be inserted in m_atom_propagation_queue, and + // propagate_bool_var_enode() method is not going to be invoked for v. + if (is_new_var && n->get_family_id() == m_manager.get_basic_family_id()) { + switch (n->get_decl_kind()) { + case OP_NOT: + SASSERT(!gate_ctx); + mk_not_cnstr(to_app(n)); + break; + case OP_AND: + mk_and_cnstr(to_app(n)); + add_and_rel_watches(to_app(n)); + break; + case OP_OR: + mk_or_cnstr(to_app(n)); + add_or_rel_watches(to_app(n)); + break; + case OP_IFF: + mk_iff_cnstr(to_app(n)); + break; + case OP_ITE: + mk_ite_cnstr(to_app(n)); + add_ite_rel_watches(to_app(n)); + break; + case OP_DISTINCT: + UNREACHABLE(); + default: + break; + } + } + + CTRACE("internalize_bug", e_internalized(n), + tout << "#" << n->get_id() << ", merge_tf: " << get_enode(n)->merge_tf() << "\n";); + } + + /** + \brief Trail object to disable the m_merge_tf flag of an enode. + */ + class set_merge_tf_trail : public trail { + enode * m_node; + public: + set_merge_tf_trail(enode * n): + m_node(n) { + } + virtual void undo(context & ctx) { + m_node->m_merge_tf = false; + } + }; + + /** + \brief Enable the flag m_merge_tf in the given enode. When the + flag m_merge_tf is enabled, the enode n will be merged with the + true_enode (false_enode) whenever the boolean variable v is + assigned to true (false). + + If is_new_var is true, then trail is not created for the flag uodate. + */ + void context::set_merge_tf(enode * n, bool_var v, bool is_new_var) { + SASSERT(bool_var2enode(v) == n); + if (!n->m_merge_tf) { + if (!is_new_var) + push_trail(set_merge_tf_trail(n)); + n->m_merge_tf = true; + lbool val = get_assignment(v); + if (val != l_undef) + push_eq(n, val == l_true ? m_true_enode : m_false_enode, eq_justification(literal(v, val == l_false))); + } + } + + /** + \brief Trail object to disable the m_enode flag of a boolean + variable. The flag m_enode is true for a boolean variable v, + if there is an enode n associated with it. + */ + class set_enode_flag_trail : public trail { + bool_var m_var; + public: + set_enode_flag_trail(bool_var v): + m_var(v) { + } + virtual void undo(context & ctx) { + bool_var_data & data = ctx.m_bdata[m_var]; + data.reset_enode_flag(); + } + }; + + /** + \brief Enable the flag m_enode in the given boolean variable. That is, + the boolean variable is associated with an enode. + + If is_new_var is true, then trail is not created for the flag uodate. + */ + void context::set_enode_flag(bool_var v, bool is_new_var) { + SASSERT(e_internalized(bool_var2expr(v))); + bool_var_data & data = m_bdata[v]; + if (!data.is_enode()) { + if (!is_new_var) + push_trail(set_enode_flag_trail(v)); + data.set_enode_flag(); + } + } + + /** + \brief Internalize the given term into the logical context. + */ + void context::internalize_term(app * n) { + if (e_internalized(n)) { + theory * th = m_theories.get_plugin(n->get_family_id()); + if (th != 0) { + // This code is necessary because some theories may decide + // not to create theory variables for a nested application. + // Example: + // Suppose (+ (* 2 x) y) is internalized by arithmetic + // and an enode is created for the + and * applications, + // but a theory variable is only created for the + application. + // The (* 2 x) is internal to the arithmetic module. + // Later, the core tries to internalize (f (* 2 x)). + // Now, (* 2 x) is not internal to arithmetic anymore, + // and a theory variable must be created for it. + enode * e = get_enode(n); + if (!th->is_attached_to_var(e)) + internalize_theory_term(n); + } + return; + } + + if (m_manager.is_term_ite(n)) { + internalize_ite_term(n); + return; // it is not necessary to apply sort constraint + } + else if (internalize_theory_term(n)) { + // skip + } + else { + internalize_uninterpreted(n); + } + SASSERT(e_internalized(n)); + enode * e = get_enode(n); + apply_sort_cnstr(n, e); + } + + /** + \brief Internalize an if-then-else term. + */ + void context::internalize_ite_term(app * n) { + SASSERT(!e_internalized(n)); + expr * c = n->get_arg(0); + expr * t = n->get_arg(1); + expr * e = n->get_arg(2); + app * eq1 = mk_eq_atom(n, t); + app * eq2 = mk_eq_atom(n, e); + mk_enode(n, + true /* supress arguments, I don't want to apply CC on ite terms */, + false /* it is a term, so it should not be merged with true/false */, + false /* CC is not enabled */); + internalize(c, true); + internalize(t, false); + internalize(e, false); + internalize(eq1, true); + internalize(eq2, true); + literal c_lit = get_literal(c); + literal eq1_lit = get_literal(eq1); + literal eq2_lit = get_literal(eq2); + TRACE("internalize_ite_term_bug", + tout << mk_ismt2_pp(n, m_manager) << "\n"; + tout << mk_ismt2_pp(c, m_manager) << "\n"; + tout << mk_ismt2_pp(t, m_manager) << "\n"; + tout << mk_ismt2_pp(e, m_manager) << "\n"; + tout << mk_ismt2_pp(eq1, m_manager) << "\n"; + tout << mk_ismt2_pp(eq2, m_manager) << "\n"; + tout << "literals:\n" << c_lit << " " << eq1_lit << " " << eq2_lit << "\n";); + mk_gate_clause(~c_lit, eq1_lit); + mk_gate_clause( c_lit, eq2_lit); + if (relevancy()) { + relevancy_eh * eh = m_relevancy_propagator->mk_term_ite_relevancy_eh(n, eq1, eq2); + TRACE("ite_term_relevancy", tout << "#" << n->get_id() << " #" << eq1->get_id() << " #" << eq2->get_id() << "\n";); + add_rel_watch(c_lit, eh); + add_rel_watch(~c_lit, eh); + add_relevancy_eh(n, eh); + } + SASSERT(e_internalized(n)); + } + + /** + \brief Try to internalize a theory term. That is, a theory (plugin) + will be invoked to internalize n. Return true if succeeded. + + It may fail because there is no plugin or the plugin does not support it. + */ + bool context::internalize_theory_term(app * n) { + theory * th = m_theories.get_plugin(n->get_family_id()); + if (!th || !th->internalize_term(n)) + return false; + return true; + } + + /** + \brief Internalize an uninterpreted function application or constant. + */ + void context::internalize_uninterpreted(app * n) { + SASSERT(!e_internalized(n)); + // process args + unsigned num = n->get_num_args(); + for (unsigned i = 0; i < num; i++) { + expr * arg = n->get_arg(i); + internalize(arg, false); + SASSERT(e_internalized(arg)); + } + + enode * e = mk_enode(n, + false, /* do not supress args */ + false, /* it is a term, so it should not be merged with true/false */ + true); + apply_sort_cnstr(n, e); + } + + /** + \brief Create a new boolean variable and associate it with n. + */ + bool_var context::mk_bool_var(expr * n) { + SASSERT(!b_internalized(n)); + unsigned id = n->get_id(); + bool_var v = m_b_internalized_stack.size(); +#ifndef _EXTERNAL_RELEASE + if (m_fparams.m_display_bool_var2expr) { + char const * header = "(iff z3@"; + int id_sz = 6; + std::cerr.width(id_sz); + std::cerr << header << std::left << v << " " << mk_pp(n, m_manager, static_cast(strlen(header)) + id_sz + 1) << ")\n"; + } + if (m_fparams.m_display_ll_bool_var2expr) { + std::cerr << v << " ::=\n" << mk_ll_pp(n, m_manager) << "\n"; + } +#endif + TRACE("mk_bool_var", tout << "creating boolean variable: " << v << " for:\n" << mk_pp(n, m_manager) << "\n";); + TRACE("mk_var_bug", tout << "mk_bool: " << v << "\n";); + set_bool_var(id, v); + m_bdata.reserve(v+1); + m_activity.reserve(v+1); + m_bool_var2expr.reserve(v+1); + m_bool_var2expr[v] = n; + literal l(v, false); + literal not_l(v, true); + unsigned aux = std::max(l.index(), not_l.index()) + 1; + m_assignment.reserve(aux); + m_assignment[l.index()] = l_undef; + m_assignment[not_l.index()] = l_undef; + m_watches.reserve(aux); + SASSERT(m_assignment.size() == m_watches.size()); + m_watches[l.index()] .reset(); + m_watches[not_l.index()] .reset(); + if (lit_occs_enabled()) { + m_lit_occs.reserve(aux); + m_lit_occs[l.index()] .reset(); + m_lit_occs[not_l.index()] .reset(); + } + bool_var_data & data = m_bdata[v]; + unsigned iscope_lvl = m_scope_lvl; // record when the boolean variable was internalized. + data.init(iscope_lvl); + if (m_fparams.m_random_initial_activity == IA_RANDOM || (m_fparams.m_random_initial_activity == IA_RANDOM_WHEN_SEARCHING && m_searching)) + m_activity[v] = -((m_random() % 1000) / 1000.0); + else + m_activity[v] = 0.0; + m_case_split_queue->mk_var_eh(v); + m_b_internalized_stack.push_back(n); + m_trail_stack.push_back(&m_mk_bool_var_trail); + m_stats.m_num_mk_bool_var++; + SASSERT(check_bool_var_vector_sizes()); + return v; + } + + void context::undo_mk_bool_var() { + SASSERT(!m_b_internalized_stack.empty()); + m_stats.m_num_del_bool_var++; + expr * n = m_b_internalized_stack.back(); + unsigned n_id = n->get_id(); + bool_var v = get_bool_var_of_id(n_id); + TRACE("undo_mk_bool_var", tout << "undo_bool: " << v << "\n" << mk_pp(n, m_manager) << "\n" << "m_bdata.size: " << m_bdata.size() + << " m_assignment.size: " << m_assignment.size() << "\n";); + TRACE("mk_var_bug", tout << "undo_mk_bool: " << v << "\n";); + // bool_var_data & d = m_bdata[v]; + m_case_split_queue->del_var_eh(v); + if (is_quantifier(n)) + m_qmanager->del(to_quantifier(n)); + set_bool_var(n_id, null_bool_var); + m_b_internalized_stack.pop_back(); + } + + /** + \brief Create an new enode. + + \remark If suppress_args is true, then the enode is viewed as a constant + in the egraph. + */ + enode * context::mk_enode(app * n, bool suppress_args, bool merge_tf, bool cgc_enabled) { + TRACE("mk_enode_detail", tout << mk_pp(n, m_manager) << "\nsuppress_args: " << suppress_args << ", merge_tf: " << + merge_tf << ", cgc_enabled: " << cgc_enabled << "\n";); + SASSERT(!e_internalized(n)); + unsigned id = n->get_id(); + unsigned generation = m_generation; + unsigned _generation = 0; + if (!m_cached_generation.empty() && m_cached_generation.find(n, _generation)) { + generation = _generation; + CTRACE("cached_generation", generation != m_generation, + tout << "cached_generation: #" << n->get_id() << " " << generation << " " << m_generation << "\n";); + } + enode * e = enode::mk(m_manager, m_region, m_app2enode, n, generation, suppress_args, merge_tf, m_scope_lvl, cgc_enabled, true); + TRACE("mk_enode_detail", tout << "e.get_num_args() = " << e->get_num_args() << "\n";); + if (n->get_num_args() == 0 && m_manager.is_value(n)) + e->mark_as_interpreted(); + TRACE("mk_var_bug", tout << "mk_enode: " << id << "\n";); + TRACE("generation", tout << "mk_enode: " << id << " " << generation << "\n";); + m_app2enode.setx(id, e, 0); + m_e_internalized_stack.push_back(n); + m_trail_stack.push_back(&m_mk_enode_trail); + m_enodes.push_back(e); + if (e->get_num_args() > 0) { + if (e->is_true_eq()) { + bool_var v = enode2bool_var(e); + assign(literal(v), mk_justification(eq_propagation_justification(e->get_arg(0), e->get_arg(1)))); + e->m_cg = e; + } + else { + if (cgc_enabled) { + enode_bool_pair pair = m_cg_table.insert(e); + enode * e_prime = pair.first; + if (e != e_prime) { + e->m_cg = e_prime; + bool used_commutativity = pair.second; + push_new_congruence(e, e_prime, used_commutativity); + } + else { + e->m_cg = e; + } + } + else { + e->m_cg = e; + } + } + if (!e->is_eq()) { + unsigned decl_id = n->get_decl()->get_decl_id(); + if (decl_id >= m_decl2enodes.size()) + m_decl2enodes.resize(decl_id+1); + m_decl2enodes[decl_id].push_back(e); + } + } + SASSERT(e_internalized(n)); + m_stats.m_num_mk_enode++; + TRACE("mk_enode", tout << "created enode: #" << e->get_owner_id() << " for:\n" << mk_pp(n, m_manager) << "\n"; + if (e->get_num_args() > 0) { + tout << "is_true_eq: " << e->is_true_eq() << " in cg_table: " << m_cg_table.contains_ptr(e) << " is_cgr: " + << e->is_cgr() << "\n"; + }); +#ifndef SMTCOMP + if (m_fparams.m_trace_stream != NULL) + *m_fparams.m_trace_stream << "[attach-enode] #" << n->get_id() << " " << m_generation << "\n"; +#endif + return e; + } + + void context::undo_mk_enode() { + SASSERT(!m_e_internalized_stack.empty()); + m_stats.m_num_del_enode++; + expr * n = m_e_internalized_stack.back(); + TRACE("undo_mk_enode", tout << "undo_enode: #" << n->get_id() << "\n" << mk_pp(n, m_manager) << "\n";); + TRACE("mk_var_bug", tout << "undo_mk_enode: " << n->get_id() << "\n";); + unsigned n_id = n->get_id(); + SASSERT(is_app(n)); + enode * e = m_app2enode[n_id]; + m_app2enode[n_id] = 0; + if (e->is_cgr() && !e->is_true_eq() && e->is_cgc_enabled()) { + SASSERT(m_cg_table.contains_ptr(e)); + m_cg_table.erase(e); + } + if (e->get_num_args() > 0 && !e->is_eq()) { + unsigned decl_id = to_app(n)->get_decl()->get_decl_id(); + SASSERT(decl_id < m_decl2enodes.size()); + SASSERT(m_decl2enodes[decl_id].back() == e); + m_decl2enodes[decl_id].pop_back(); + } + e->del_eh(m_manager); + SASSERT(m_e_internalized_stack.size() == m_enodes.size()); + m_enodes.pop_back(); + m_e_internalized_stack.pop_back(); + } + + /** + \brief Apply sort constraints on e. + */ + void context::apply_sort_cnstr(app * term, enode * e) { + sort * s = term->get_decl()->get_range(); + theory * th = m_theories.get_plugin(s->get_family_id()); + if (th) + th->apply_sort_cnstr(e, s); + } + + /** + \brief Return the literal associated with n. + */ + literal context::get_literal(expr * n) const { + if (m_manager.is_not(n)) { + CTRACE("get_literal_bug", !b_internalized(to_app(n)->get_arg(0)), tout << mk_ll_pp(n, m_manager) << "\n";); + SASSERT(b_internalized(to_app(n)->get_arg(0))); + return literal(get_bool_var(to_app(n)->get_arg(0)), true); + } + else if (m_manager.is_true(n)) { + return true_literal; + } + else if (m_manager.is_false(n)) { + return false_literal; + } + else { + SASSERT(b_internalized(n)); + return literal(get_bool_var(n), false); + } + } + + /** + \brief Simplify the literals of an auxiliary clause. An + auxiliary clause is transient. So, the current assignment can + be used for simplification. + + The following simplifications are applied: + + - Duplicates are removed. + + - Literals assigned to false are removed + + - If l and ~l are in lits, then return false (the clause is equivalent to true) + + - If a literal in source is assigned to true, then return false. + + \remark The removed literals are stored in simp_lits + + It is safe to use the current assignment to simplify aux + clauses because they are deleted during backtracking. + */ + bool context::simplify_aux_clause_literals(unsigned & num_lits, literal * lits, literal_buffer & simp_lits) { + std::sort(lits, lits + num_lits); + literal prev = null_literal; + unsigned i = 0; + unsigned j = 0; + for (; i < num_lits; i++) { + literal curr = lits[i]; + lbool val = get_assignment(curr); + if (val == l_false) + simp_lits.push_back(~curr); + switch(val) { + case l_false: + break; // ignore literal + case l_undef: + if (curr == ~prev) + return false; // clause is equivalent to true + if (curr != prev) { + prev = curr; + if (i != j) + lits[j] = lits[i]; + j++; + } + break; + case l_true: + return false; // clause is equivalent to true + } + } + num_lits = j; + return true; + } + + /** + \brief Simplify the literals of an auxiliary lemma. An + auxiliary lemma has the status of a learned clause, but it is + not created by conflict resolution. + + A dynamic ackermann clause is an example of auxiliary lemma. + + The following simplifications are applied: + + - Duplicates are removed. + + - If a literal is assigned to true at a base level, then return + false (the clause is equivalent to true). + + - If l and ~l are in lits, then return false (source is + irrelevant, that is, it is equivalent to true) + + \remark Literals assigned to false at the base level are not + removed because I don't want to create a justification for this + kind of simplification. + */ + bool context::simplify_aux_lemma_literals(unsigned & num_lits, literal * lits) { + TRACE("simplify_aux_lemma_literals", tout << "1) "; display_literals(tout, num_lits, lits); tout << "\n";); + std::sort(lits, lits + num_lits); + TRACE("simplify_aux_lemma_literals", tout << "2) "; display_literals(tout, num_lits, lits); tout << "\n";); + literal prev = null_literal; + unsigned i = 0; + unsigned j = 0; + for (; i < num_lits; i++) { + literal curr = lits[i]; + bool_var var = curr.var(); + lbool val = l_undef; + if (get_assign_level(var) <= m_base_lvl) + val = get_assignment(curr); + if (val == l_true) + return false; // clause is equivalent to true + if (curr == ~prev) + return false; // clause is equivalent to true + if (curr != prev) { + prev = curr; + if (i != j) + lits[j] = lits[i]; + j++; + } + } + num_lits = j; + TRACE("simplify_aux_lemma_literals", tout << "3) "; display_literals(tout, num_lits, lits); tout << "\n";); + return true; + } + + /** + \brief A clause (lemma or aux lemma) may need to be reinitialized for two reasons: + + 1) Lemmas and aux lemmas may contain literals that were created during the search, + and the maximum internalization scope level of its literals is scope_lvl. + Since the clauses may remain alive when scope_lvl is backtracked, it must + be reinitialised. In this case, reinitialize_atoms must be true. + + 2) An aux lemma is in conflict or propagated a literal when it was created. + Then, we should check whether the aux lemma is still in conflict or propagating + a literal after backtracking the current scope level. + */ + void context::mark_for_reinit(clause * cls, unsigned scope_lvl, bool reinternalize_atoms) { + SASSERT(scope_lvl >= m_base_lvl); + cls->m_reinit = true; + cls->m_reinternalize_atoms = reinternalize_atoms; + if (scope_lvl >= m_clauses_to_reinit.size()) + m_clauses_to_reinit.resize(scope_lvl+1, clause_vector()); + m_clauses_to_reinit[scope_lvl].push_back(cls); + } + + /** + \brief Return max({ get_intern_level(var) | var \in lits }) + */ + unsigned context::get_max_iscope_lvl(unsigned num_lits, literal const * lits) const { + unsigned r = 0; + for (unsigned i = 0; i < num_lits; i++) { + unsigned ilvl = get_intern_level(lits[i].var()); + if (ilvl > r) + r = ilvl; + } + return r; + } + + /** + \brief Return true if it safe to use the binary clause optimization at this point in time. + */ + bool context::use_binary_clause_opt(literal l1, literal l2, bool lemma) const { + if (!binary_clause_opt_enabled()) + return false; + // When relevancy is enable binary clauses should not be used. + // Reason: when a learned clause becomes unit, it should mark the + // unit literal as relevant. When binary_clause_opt is used, + // it is not possible to distinguish between learned and non-learned clauses. + if (lemma && m_fparams.m_relevancy_lvl >= 2) + return false; + if (m_base_lvl > 0) + return false; + if (!lemma && m_scope_lvl > 0) + return false; + if (get_intern_level(l1.var()) > 0) + return false; + if (get_intern_level(l2.var()) > 0) + return false; + return true; + } + + /** + \brief The learned clauses (lemmas) produced by the SAT solver + have the property that the first literal will be implied by it + after backtracking. All other literals are assigned to (or + implied to be) false when the learned clause is created. The + first watch literal will always be the first literal. The + second watch literal is computed by this method. It should be + the literal with the highest decision level. + + If a literal is not assigned, it means it was re-initialized + after backtracking. So, its level is assumed to be m_scope_lvl. + */ + int context::select_learned_watch_lit(clause const * cls) const { + SASSERT(cls->get_num_literals() >= 2); + int max_false_idx = -1; + unsigned max_lvl = UINT_MAX; + int num_lits = cls->get_num_literals(); + for (int i = 1; i < num_lits; i++) { + literal l = cls->get_literal(i); + lbool val = get_assignment(l); + SASSERT(val == l_false || val == l_undef); + unsigned lvl = val == l_false ? get_assign_level(l) : m_scope_lvl; + if (max_false_idx == -1 || lvl > max_lvl) { + max_false_idx = i; + max_lvl = lvl; + } + } + return max_false_idx; + } + + /** + \brief Select a watch literal from a set of literals which is + different from the literal in position other_watch_lit. + + I use the following rules to select a watch literal. + + 1- select a literal l in idx >= starting_at such that get_assignment(l) = l_true, + and for all l' in idx' >= starting_at . get_assignment(l') = l_true implies get_level(l) <= get_level(l') + + The purpose of this rule is to make the clause inactive for as long as possible. A clause + is inactive when it contains a literal assigned to true. + + 2- if there isn't a literal assigned to true, then select an unassigned literal l is in idx >= starting_at + + 3- if there isn't a literal l in idx >= starting_at such that get_assignment(l) = l_true or + get_assignment(l) = l_undef (that is, all literals different from other_watch_lit are assigned + to false), then peek the literal l different starting at starting_at such that for all l' starting at starting_at + get_level(l) >= get_level(l') + + Without rule 3, boolean propagation is incomplete, that is, it may miss possible propagations. + + \remark The method select_lemma_watch_lit is used to select the + watch literal for regular learned clauses. + */ + int context::select_watch_lit(clause const * cls, int starting_at) const { + SASSERT(cls->get_num_literals() >= 2); + int min_true_idx = -1; + int max_false_idx = -1; + int unknown_idx = -1; + int n = cls->get_num_literals(); + for (int i = starting_at; i < n; i++) { + literal l = cls->get_literal(i); + switch(get_assignment(l)) { + case l_false: + if (max_false_idx == -1 || get_assign_level(l.var()) > get_assign_level(cls->get_literal(max_false_idx).var())) + max_false_idx = i; + break; + case l_undef: + unknown_idx = i; + break; + case l_true: + if (min_true_idx == -1 || get_assign_level(l.var()) < get_assign_level(cls->get_literal(min_true_idx).var())) + min_true_idx = i; + break; + } + } + if (min_true_idx != -1) + return min_true_idx; + if (unknown_idx != -1) + return unknown_idx; + SASSERT(max_false_idx != -1); + return max_false_idx; + } + + /** + \brief Add watch literal to the given clause. + + \pre idx must be 0 or 1. + */ + void context::add_watch_literal(clause * cls, unsigned idx) { + SASSERT(idx == 0 || idx == 1); + literal l = cls->get_literal(idx); + unsigned l_idx = (~l).index(); + watch_list & wl = const_cast(m_watches[l_idx]); + wl.insert_clause(cls); + CASSERT("watch_list", check_watch_list(l_idx)); + } + + /** + \brief Create a new clause using the given literals, justification, kind and deletion event handler. + The deletion event handler is ignored if binary clause optimization is applicable. + */ + clause * context::mk_clause(unsigned num_lits, literal * lits, justification * j, clause_kind k, clause_del_eh * del_eh) { + TRACE("mk_clause", tout << "creating clause:\n"; display_literals(tout, num_lits, lits); tout << "\n";); + switch (k) { + case CLS_AUX: { + unsigned old_num_lits = num_lits; + literal_buffer simp_lits; + if (!simplify_aux_clause_literals(num_lits, lits, simp_lits)) + return 0; // clause is equivalent to true; + DEBUG_CODE({ + for (unsigned i = 0; i < simp_lits.size(); i++) { + SASSERT(get_assignment(simp_lits[i]) == l_true); + } + }); + if (old_num_lits != num_lits) + j = mk_justification(unit_resolution_justification(m_region, j, simp_lits.size(), simp_lits.c_ptr())); + break; + } + case CLS_AUX_LEMMA: { + if (!simplify_aux_lemma_literals(num_lits, lits)) + return 0; // clause is equivalent to true + // simplify_aux_lemma_literals does not delete literals assigned to false, so + // it is not necessary to create a unit_resolution_justification + break; + } + default: + break; + } + TRACE("mk_clause", tout << "after simplification:\n"; display_literals(tout, num_lits, lits); tout << "\n";); + unsigned activity = 0; + if (activity == 0) + activity = 1; + bool lemma = k != CLS_AUX; + m_stats.m_num_mk_lits += num_lits; + switch (num_lits) { + case 0: + if (j && !j->in_region()) + m_justifications.push_back(j); + TRACE("mk_clause", tout << "empty clause... setting conflict\n";); + set_conflict(j == 0 ? b_justification::mk_axiom() : b_justification(j)); + SASSERT(inconsistent()); + return 0; + case 1: + if (j && !j->in_region()) + m_justifications.push_back(j); + assign(lits[0], j); + return 0; + case 2: + if (use_binary_clause_opt(lits[0], lits[1], lemma)) { + literal l1 = lits[0]; + literal l2 = lits[1]; + m_watches[(~l1).index()].insert_literal(l2); + m_watches[(~l2).index()].insert_literal(l1); + if (get_assignment(l2) == l_false) + assign(l1, b_justification(~l2)); + + m_stats.m_num_mk_bin_clause++; + return 0; + } + default: { + m_stats.m_num_mk_clause++; + unsigned iscope_lvl = lemma ? get_max_iscope_lvl(num_lits, lits) : 0; + SASSERT(m_scope_lvl >= iscope_lvl); + bool save_atoms = lemma && iscope_lvl > m_base_lvl; + bool reinit = save_atoms; + SASSERT(!lemma || j == 0 || !j->in_region()); + clause * cls = clause::mk(m_manager, num_lits, lits, k, j, del_eh, save_atoms, m_bool_var2expr.c_ptr()); + if (lemma) { + cls->set_activity(activity); + if (k == CLS_LEARNED) { + int w2_idx = select_learned_watch_lit(cls); + cls->swap_lits(1, w2_idx); + } + else { + SASSERT(k == CLS_AUX_LEMMA); + int w1_idx = select_watch_lit(cls, 0); + cls->swap_lits(0, w1_idx); + int w2_idx = select_watch_lit(cls, 1); + cls->swap_lits(1, w2_idx); + TRACE("mk_th_lemma", display_clause(tout, cls); tout << "\n";); + } + m_lemmas.push_back(cls); + add_watch_literal(cls, 0); + add_watch_literal(cls, 1); + if (get_assignment(cls->get_literal(0)) == l_false) { + set_conflict(b_justification(cls)); + if (k == CLS_AUX_LEMMA && m_scope_lvl > m_base_lvl) { + reinit = true; + iscope_lvl = m_scope_lvl; + } + } + else if (get_assignment(cls->get_literal(1)) == l_false) { + assign(cls->get_literal(0), b_justification(cls)); + if (k == CLS_AUX_LEMMA && m_scope_lvl > m_base_lvl) { + reinit = true; + iscope_lvl = m_scope_lvl; + } + } + if (reinit) + mark_for_reinit(cls, iscope_lvl, save_atoms); + } + else { + m_aux_clauses.push_back(cls); + add_watch_literal(cls, 0); + add_watch_literal(cls, 1); + if (get_assignment(cls->get_literal(0)) == l_false) + set_conflict(b_justification(cls)); + else if (get_assignment(cls->get_literal(1)) == l_false) + assign(cls->get_literal(0), b_justification(cls)); + } + + if (lit_occs_enabled()) + add_lit_occs(cls); + + TRACE("add_watch_literal_bug", display_clause_detail(tout, cls);); + TRACE("mk_clause_result", display_clause_detail(tout, cls);); + CASSERT("mk_clause", check_clause(cls)); + return cls; + }} + } + + void context::add_lit_occs(clause * cls) { + unsigned num_lits = cls->get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) { + literal l = cls->get_literal(i); + m_lit_occs[l.index()].insert(cls); + } + } + + void context::mk_clause(literal l1, literal l2, justification * j) { + literal ls[2] = { l1, l2 }; + mk_clause(2, ls, j); + } + + void context::mk_clause(literal l1, literal l2, literal l3, justification * j) { + literal ls[3] = { l1, l2, l3 }; + mk_clause(3, ls, j); + } + + void context::mk_th_axiom(theory_id tid, unsigned num_lits, literal * lits, unsigned num_params, parameter * params) { + justification * js = 0; + if (m_manager.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.c_ptr(), false_literal, m_fparams.m_smtlib_logic.c_str()); + } + mk_clause(num_lits, lits, js); + } + + void context::mk_th_axiom(theory_id tid, literal l1, literal l2, unsigned num_params, parameter * params) { + literal ls[2] = { l1, l2 }; + TRACE("mk_th_axiom", display_literal(tout, l1); tout << " "; display_literal(tout, l2); tout << "\n";); + mk_th_axiom(tid, 2, ls, num_params, params); + } + + void context::mk_th_axiom(theory_id tid, literal l1, literal l2, literal l3, unsigned num_params, parameter * params) { + literal ls[3] = { l1, l2, l3 }; + TRACE("mk_th_axiom", display_literal(tout, l1); tout << " "; display_literal(tout, l2); tout << " "; display_literal(tout, l3); tout << "\n";); + mk_th_axiom(tid, 3, ls, num_params, params); + } + + proof * context::mk_clause_def_axiom(unsigned num_lits, literal * lits, expr * root_gate) { + ptr_buffer new_lits; + for (unsigned i = 0; i < num_lits; i++) { + literal l = lits[i]; + bool_var v = l.var(); + expr * atom = m_bool_var2expr[v]; + new_lits.push_back(l.sign() ? m_manager.mk_not(atom) : atom); + } + if (root_gate) + new_lits.push_back(m_manager.mk_not(root_gate)); + SASSERT(num_lits > 1); + expr * fact = m_manager.mk_or(new_lits.size(), new_lits.c_ptr()); + return m_manager.mk_def_axiom(fact); + + } + + void context::mk_gate_clause(unsigned num_lits, literal * lits) { + if (m_manager.proofs_enabled()) { + proof * pr = mk_clause_def_axiom(num_lits, lits, 0); + TRACE("gate_clause", tout << mk_ll_pp(pr, m_manager);); + mk_clause(num_lits, lits, mk_justification(justification_proof_wrapper(*this, pr))); + } + else { + mk_clause(num_lits, lits, 0); + } + } + + void context::mk_gate_clause(literal l1, literal l2) { + literal ls[2] = { l1, l2 }; + mk_gate_clause(2, ls); + } + + void context::mk_gate_clause(literal l1, literal l2, literal l3) { + literal ls[3] = { l1, l2, l3 }; + mk_gate_clause(3, ls); + } + + void context::mk_gate_clause(literal l1, literal l2, literal l3, literal l4) { + literal ls[4] = { l1, l2, l3, l4 }; + mk_gate_clause(4, ls); + } + + void context::mk_root_clause(unsigned num_lits, literal * lits, proof * pr) { + if (m_manager.proofs_enabled()) { + SASSERT(m_manager.get_fact(pr)); + expr * fact = m_manager.get_fact(pr); + if (!m_manager.is_or(fact)) { + proof * def = mk_clause_def_axiom(num_lits, lits, m_manager.get_fact(pr)); + TRACE("gate_clause", tout << mk_ll_pp(def, m_manager) << "\n"; + tout << mk_ll_pp(pr, m_manager);); + proof * prs[2] = { def, pr }; + pr = m_manager.mk_unit_resolution(2, prs); + } + mk_clause(num_lits, lits, mk_justification(justification_proof_wrapper(*this, pr))); + } + else { + mk_clause(num_lits, lits, 0); + } + } + + void context::mk_root_clause(literal l1, literal l2, proof * pr) { + literal ls[2] = { l1, l2 }; + mk_root_clause(2, ls, pr); + } + + void context::mk_root_clause(literal l1, literal l2, literal l3, proof * pr) { + literal ls[3] = { l1, l2, l3 }; + mk_root_clause(3, ls, pr); + } + + void context::add_and_rel_watches(app * n) { + if (relevancy()) { + relevancy_eh * eh = m_relevancy_propagator->mk_and_relevancy_eh(n); + unsigned num = n->get_num_args(); + for (unsigned i = 0; i < num; i++) { + // if one child is assigned to false, the the and-parent must be notified + literal l = get_literal(n->get_arg(i)); + add_rel_watch(~l, eh); + } + } + } + + void context::add_or_rel_watches(app * n) { + if (relevancy()) { + relevancy_eh * eh = m_relevancy_propagator->mk_or_relevancy_eh(n); + unsigned num = n->get_num_args(); + for (unsigned i = 0; i < num; i++) { + // if one child is assigned to true, the the or-parent must be notified + literal l = get_literal(n->get_arg(i)); + add_rel_watch(l, eh); + } + } + } + + void context::add_ite_rel_watches(app * n) { + if (relevancy()) { + relevancy_eh * eh = m_relevancy_propagator->mk_ite_relevancy_eh(n); + literal l = get_literal(n->get_arg(0)); + // when the condition of an ite is assigned to true or false, the ite-parent must be notified. + TRACE("propagate_relevant_ite", tout << "#" << n->get_id() << ", eh: " << eh << "\n";); + add_rel_watch(l, eh); + add_rel_watch(~l, eh); + } + } + + void context::mk_not_cnstr(app * n) { + SASSERT(b_internalized(n)); + bool_var v = get_bool_var(n); + literal l(v, false); + literal c = get_literal(n->get_arg(0)); + mk_gate_clause(~l, ~c); + mk_gate_clause(l, c); + } + + void context::mk_and_cnstr(app * n) { + literal l = get_literal(n); + TRACE("mk_and_cnstr", tout << "l: "; display_literal(tout, l); tout << "\n";); + literal_buffer buffer; + buffer.push_back(l); + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + literal l_arg = get_literal(n->get_arg(i)); + TRACE("mk_and_cnstr", tout << "l_arg: "; display_literal(tout, l_arg); tout << "\n";); + mk_gate_clause(~l, l_arg); + buffer.push_back(~l_arg); + } + if (!expand_pos_def_only()) { + mk_gate_clause(buffer.size(), buffer.c_ptr()); + } + } + + void context::mk_or_cnstr(app * n) { + literal l = get_literal(n); + literal_buffer buffer; + buffer.push_back(~l); + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + literal l_arg = get_literal(n->get_arg(i)); + if (!expand_pos_def_only()) + mk_gate_clause(l, ~l_arg); + buffer.push_back(l_arg); + } + mk_gate_clause(buffer.size(), buffer.c_ptr()); + } + + void context::mk_iff_cnstr(app * n) { + literal l = get_literal(n); + literal l1 = get_literal(n->get_arg(0)); + literal l2 = get_literal(n->get_arg(1)); + TRACE("mk_iff_cnstr", tout << "l: " << l << ", l1: " << l1 << ", l2: " << l2 << "\n";); + mk_gate_clause(~l, l1, ~l2); + mk_gate_clause(~l, ~l1 , l2); + mk_gate_clause( l, l1, l2); + mk_gate_clause( l, ~l1, ~l2); + } + + void context::mk_ite_cnstr(app * n) { + literal l = get_literal(n); + literal l1 = get_literal(n->get_arg(0)); + literal l2 = get_literal(n->get_arg(1)); + literal l3 = get_literal(n->get_arg(2)); + mk_gate_clause(~l, ~l1, l2); + mk_gate_clause(~l, l1, l3); + mk_gate_clause(l, ~l1, ~l2); + mk_gate_clause(l, l1, ~l3); + } + + /** + \brief Trail for add_th_var + */ + class add_th_var_trail : public trail { + enode * m_enode; + theory_id m_th_id; +#ifdef Z3DEBUG + theory_var m_th_var; +#endif + public: + add_th_var_trail(enode * n, theory_id th_id): + m_enode(n), + m_th_id(th_id) { + DEBUG_CODE(m_th_var = n->get_th_var(th_id);); + SASSERT(m_th_var != null_theory_var); + } + + virtual void undo(context & ctx) { + theory_var v = m_enode->get_th_var(m_th_id); + SASSERT(v != null_theory_var); + SASSERT(m_th_var == v); + m_enode->del_th_var(m_th_id); + enode * root = m_enode->get_root(); + if (root != m_enode && root->get_th_var(m_th_id) == v) + root->del_th_var(m_th_id); + } + }; + + /** + \brief Trail for replace_th_var + */ + class replace_th_var_trail : public trail { + enode * m_enode; + unsigned m_th_id:8; + unsigned m_old_th_var:24; + public: + replace_th_var_trail(enode * n, theory_id th_id, theory_var old_var): + m_enode(n), + m_th_id(th_id), + m_old_th_var(old_var) { + } + + virtual void undo(context & ctx) { + SASSERT(m_enode->get_th_var(m_th_id) != null_theory_var); + m_enode->replace_th_var(m_old_th_var, m_th_id); + } + }; + + + /** + \brief Attach theory var v to the enode n. + + Enode n is to attached to any theory variable of th. + + This method should be invoked whenever the theory creates a new theory variable. + + \remark The methods new_eq_eh and new_diseq_eh of th may be invoked before this method + returns. + */ + void context::attach_th_var(enode * n, theory * th, theory_var v) { + SASSERT(!th->is_attached_to_var(n)); + theory_id th_id = th->get_id(); + theory_var old_v = n->get_th_var(th_id); + if (old_v == null_theory_var) { + enode * r = n->get_root(); + theory_var v2 = r->get_th_var(th_id); + n->add_th_var(v, th_id, m_region); + push_trail(add_th_var_trail(n, th_id)); + if (v2 == null_theory_var) { + if (r != n) + r->add_th_var(v, th_id, m_region); + push_new_th_diseqs(r, v, th); + } + else if (r != n) { + push_new_th_eq(th_id, v2, v); + } + } + else { + // Case) there is a variable old_v in the var-list of n. + // + // Remark: This variable was moved to the var-list of n due to a add_eq. + SASSERT(th->get_enode(old_v) != n); // this varialbe is not owned by n + SASSERT(n->get_root()->get_th_var(th_id) != null_theory_var); // the root has also a variable in its var-list. + n->replace_th_var(v, th_id); + push_trail(replace_th_var_trail(n, th_id, old_v)); + push_new_th_eq(th_id, v, old_v); + } + SASSERT(th->is_attached_to_var(n)); + } +}; + diff --git a/lib/smt_justification.cpp b/lib/smt_justification.cpp new file mode 100644 index 000000000..81f496187 --- /dev/null +++ b/lib/smt_justification.cpp @@ -0,0 +1,419 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_justification.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-25. + +Revision History: + +--*/ +#include"smt_context.h" +#include"smt_conflict_resolution.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" + +namespace smt { + + + justification_proof_wrapper::justification_proof_wrapper(context & ctx, proof * pr, bool in_region): + justification(in_region), + m_proof(pr) { + ctx.get_manager().inc_ref(pr); + } + + void justification_proof_wrapper::del_eh(ast_manager & m) { + m.dec_ref(m_proof); + } + + proof * justification_proof_wrapper::mk_proof(conflict_resolution & cr) { + return m_proof; + } + + unit_resolution_justification::unit_resolution_justification(region & r, + justification * js, + unsigned num_lits, + literal const * lits): + m_antecedent(js), + m_num_literals(num_lits) { + SASSERT(!js || js->in_region()); + m_literals = new (r) literal[num_lits]; + memcpy(m_literals, lits, sizeof(literal) * num_lits); + TRACE("unit_resolution_justification_bug", + for (unsigned i = 0; i < num_lits; i++) { + tout << lits[i] << " "; + } + tout << "\n";); + } + + unit_resolution_justification::unit_resolution_justification(justification * js, + unsigned num_lits, + literal const * lits): + justification(false), // object is not allocated in a region + m_antecedent(js), + m_num_literals(num_lits) { + SASSERT(!js || !js->in_region()); + m_literals = alloc_vect(num_lits); + memcpy(m_literals, lits, sizeof(literal) * num_lits); + TRACE("unit_resolution_justification_bug", + for (unsigned i = 0; i < num_lits; i++) { + tout << lits[i] << " "; + } + tout << "\n";); + } + + unit_resolution_justification::~unit_resolution_justification() { + if (!in_region()) { + dealloc_svect(m_literals); // I don't need to invoke destructor... + dealloc(m_antecedent); + } + } + + void unit_resolution_justification::get_antecedents(conflict_resolution & cr) { + if (m_antecedent) + cr.mark_justification(m_antecedent); + for (unsigned i = 0; i < m_num_literals; i++) + cr.mark_literal(m_literals[i]); + } + + proof * unit_resolution_justification::mk_proof(conflict_resolution & cr) { + SASSERT(m_antecedent); + ptr_buffer prs; + proof * pr = cr.get_proof(m_antecedent); + bool visited = pr != 0; + prs.push_back(pr); + for (unsigned i = 0; i < m_num_literals; i++) { + proof * pr = cr.get_proof(m_literals[i]); + if (pr == 0) + visited = false; + else + prs.push_back(pr); + } + if (!visited) + return 0; + ast_manager & m = cr.get_manager(); + TRACE("unit_resolution_justification_bug", + tout << "in mk_proof\n"; + for (unsigned i = 0; i < m_num_literals; i++) { + tout << m_literals[i] << " "; + } + tout << "\n"; + for (unsigned i = 0; i < prs.size(); i++) { + tout << mk_ll_pp(m.get_fact(prs[i]), m); + }); + return m.mk_unit_resolution(prs.size(), prs.c_ptr()); + } + + void eq_conflict_justification::get_antecedents(conflict_resolution & cr) { + SASSERT(m_node1->get_root()->is_interpreted()); + SASSERT(m_node2->get_root()->is_interpreted()); + cr.mark_eq(m_node1, m_node1->get_root()); + cr.mark_eq(m_node2, m_node2->get_root()); + cr.mark_justified_eq(m_node1, m_node2, m_js); + } + + proof * eq_conflict_justification::mk_proof(conflict_resolution & cr) { + ast_manager & m = cr.get_manager(); + bool visited = true; + ptr_buffer prs; + + if (m_node1 != m_node1->get_root()) { + proof * pr = cr.get_proof(m_node1, m_node1->get_root()); + if (pr && m.fine_grain_proofs()) + pr = m.mk_symmetry(pr); + prs.push_back(pr); + if (!pr) + visited = false; + } + + SASSERT(m_node1 != m_node2); + proof * pr = cr.get_proof(m_node1, m_node2, m_js); + prs.push_back(pr); + if (!pr) + visited = false; + + if (m_node2 != m_node2->get_root()) { + proof * pr = cr.get_proof(m_node2, m_node2->get_root()); + prs.push_back(pr); + if (!pr) + visited = false; + } + + if (!visited) + return 0; + + expr * lhs = m_node1->get_root()->get_owner(); + expr * rhs = m_node2->get_root()->get_owner(); + proof * pr1 = m.mk_transitivity(prs.size(), prs.c_ptr(), lhs, rhs); + proof * pr2 = m.mk_rewrite(m.mk_eq(lhs, rhs), m.mk_false()); + return m.mk_modus_ponens(pr1, pr2); + } + + void eq_root_propagation_justification::get_antecedents(conflict_resolution & cr) { + cr.mark_eq(m_node, m_node->get_root()); + } + + proof * eq_root_propagation_justification::mk_proof(conflict_resolution & cr) { + ast_manager & m = cr.get_manager(); + expr * var = m_node->get_owner(); + expr * val = m_node->get_root()->get_owner(); + SASSERT(m.is_true(val) || m.is_false(val)); + proof * pr1 = cr.get_proof(m_node, m_node->get_root()); + if (pr1) { + expr * lit; + if (m.is_true(val)) + lit = var; + else + lit = m.mk_not(var); + proof * pr2 = m.mk_rewrite(m.get_fact(pr1), lit); + return m.mk_modus_ponens(pr1, pr2); + } + return 0; + } + + void eq_propagation_justification::get_antecedents(conflict_resolution & cr) { + cr.mark_eq(m_node1, m_node2); + } + + proof * eq_propagation_justification::mk_proof(conflict_resolution & cr) { + return cr.get_proof(m_node1, m_node2); + } + + + void mp_iff_justification::get_antecedents(conflict_resolution & cr) { + SASSERT(m_node1 != m_node2); + cr.mark_eq(m_node1, m_node2); + context & ctx = cr.get_context(); + bool_var v = ctx.enode2bool_var(m_node1); + lbool val = ctx.get_assignment(v); + literal l(v, val == l_false); + cr.mark_literal(l); + } + + proof * mp_iff_justification::mk_proof(conflict_resolution & cr) { + proof * pr1 = cr.get_proof(m_node1, m_node2); + context & ctx = cr.get_context(); + bool_var v = ctx.enode2bool_var(m_node1); + lbool val = ctx.get_assignment(v); + literal l(v, val == l_false); + proof * pr2 = cr.get_proof(l); + if (pr1 && pr2) { + ast_manager & m = cr.get_manager(); + proof * pr; + SASSERT(m.has_fact(pr1)); + SASSERT(m.has_fact(pr2)); + app* fact1 = to_app(m.get_fact(pr1)); + app* fact2 = to_app(m.get_fact(pr2)); + SASSERT(m.is_iff(fact1)); + if (fact1->get_arg(1) == fact2) { + pr1 = m.mk_symmetry(pr1); + fact1 = to_app(m.get_fact(pr1)); + } + SASSERT(m.is_iff(fact1)); + + if (l.sign()) { + SASSERT(m.is_not(fact2)); + expr* lhs = fact1->get_arg(0); + expr* rhs = fact1->get_arg(1); + if (lhs != fact2->get_arg(0)) { + pr1 = m.mk_symmetry(pr1); + fact1 = to_app(m.get_fact(pr1)); + std::swap(lhs, rhs); + } + SASSERT(lhs == fact2->get_arg(0)); + app* new_lhs = fact2; + app* new_rhs = m.mk_not(rhs); + pr1 = m.mk_congruence(new_lhs, new_rhs, 1, &pr1); + } + pr = m.mk_modus_ponens(pr2, pr1); + + TRACE("mp_iff_justification", tout << mk_pp(fact1, m) << "\n" << mk_pp(fact2, m) << "\n" << + mk_pp(m.get_fact(pr), m) << "\n";); + return pr; + } + return 0; + } + + simple_justification::simple_justification(region & r, unsigned num_lits, literal const * lits): + m_num_literals(num_lits) { + m_literals = new (r) literal[num_lits]; + memcpy(m_literals, lits, sizeof(literal) * num_lits); +#ifdef Z3DEBUG + for (unsigned i = 0; i < num_lits; i++) { + SASSERT(lits[i] != null_literal); + } +#endif + } + + void simple_justification::get_antecedents(conflict_resolution & cr) { + for (unsigned i = 0; i < m_num_literals; i++) + cr.mark_literal(m_literals[i]); + } + + bool simple_justification::antecedent2proof(conflict_resolution & cr, ptr_buffer & result) { + bool visited = true; + for (unsigned i = 0; i < m_num_literals; i++) { + proof * pr = cr.get_proof(m_literals[i]); + if (pr == 0) + visited = false; + else + result.push_back(pr); + } + return visited; + } + + proof * theory_axiom_justification::mk_proof(conflict_resolution & cr) { + context & ctx = cr.get_context(); + ast_manager & m = cr.get_manager(); + expr_ref_vector lits(m); + for (unsigned i = 0; i < m_num_literals; i++) { + expr_ref l(m); + ctx.literal2expr(m_literals[i], l); + lits.push_back(l); + } + if (lits.size() == 1) + return m.mk_th_lemma(m_th_id, lits.get(0), 0, 0, m_params.size(), m_params.c_ptr()); + else + return m.mk_th_lemma(m_th_id, m.mk_or(lits.size(), lits.c_ptr()), 0, 0, m_params.size(), m_params.c_ptr()); + } + + proof * theory_propagation_justification::mk_proof(conflict_resolution & cr) { + ptr_buffer prs; + if (!antecedent2proof(cr, prs)) + return 0; + context & ctx = cr.get_context(); + ast_manager & m = cr.get_manager(); + expr_ref fact(m); + ctx.literal2expr(m_consequent, fact); + return m.mk_th_lemma(m_th_id, fact, prs.size(), prs.c_ptr(), m_params.size(), m_params.c_ptr()); + } + + proof * theory_conflict_justification::mk_proof(conflict_resolution & cr) { + ptr_buffer prs; + if (!antecedent2proof(cr, prs)) + return 0; + ast_manager & m = cr.get_manager(); + return m.mk_th_lemma(m_th_id, m.mk_false(), prs.size(), prs.c_ptr(), m_params.size(), m_params.c_ptr()); + } + + 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), + m_num_eqs(num_eqs) { + m_eqs = new (r) enode_pair[num_eqs]; + memcpy(m_eqs, eqs, sizeof(enode_pair) * num_eqs); + DEBUG_CODE({ + for (unsigned i = 0; i < num_eqs; i++) { + SASSERT(eqs[i].first->get_root() == eqs[i].second->get_root()); + } + }); + } + + void ext_simple_justification::get_antecedents(conflict_resolution & cr) { + simple_justification::get_antecedents(cr); + for (unsigned i = 0; i < m_num_eqs; i++) { + enode_pair const & p = m_eqs[i]; + cr.mark_eq(p.first, p.second); + } + } + + bool ext_simple_justification::antecedent2proof(conflict_resolution & cr, ptr_buffer & result) { + bool visited = simple_justification::antecedent2proof(cr, result); + for (unsigned i = 0; i < m_num_eqs; i++) { + enode_pair const & p = m_eqs[i]; + proof * pr = cr.get_proof(p.first, p.second); + if (pr == 0) + visited = false; + else + result.push_back(pr); + } + return visited; + } + + proof * ext_theory_propagation_justification::mk_proof(conflict_resolution & cr) { + ptr_buffer prs; + if (!antecedent2proof(cr, prs)) + return 0; + context & ctx = cr.get_context(); + ast_manager & m = cr.get_manager(); + expr_ref fact(m); + ctx.literal2expr(m_consequent, fact); + return m.mk_th_lemma(m_th_id, fact, prs.size(), prs.c_ptr(), m_params.size(), m_params.c_ptr()); + } + + proof * ext_theory_conflict_justification::mk_proof(conflict_resolution & cr) { + ptr_buffer prs; + if (!antecedent2proof(cr, prs)) + return 0; + ast_manager & m = cr.get_manager(); + return m.mk_th_lemma(m_th_id, m.mk_false(), prs.size(), prs.c_ptr(), m_params.size(), m_params.c_ptr()); + } + + proof * ext_theory_eq_propagation_justification::mk_proof(conflict_resolution & cr) { + ptr_buffer prs; + if (!antecedent2proof(cr, prs)) + return 0; + ast_manager & m = cr.get_manager(); + context & ctx = cr.get_context(); + expr * fact = ctx.mk_eq_atom(m_lhs->get_owner(), m_rhs->get_owner()); + return m.mk_th_lemma(m_th_id, fact, prs.size(), prs.c_ptr(), m_params.size(), m_params.c_ptr()); + } + + + theory_lemma_justification::theory_lemma_justification(family_id fid, context & ctx, unsigned num_lits, literal const * lits, + unsigned num_params, parameter* params): + justification(false), + m_th_id(fid), + m_params(num_params, params), + m_num_literals(num_lits) { + ast_manager & m = ctx.get_manager(); + m_literals = alloc_svect(expr*, num_lits); + for (unsigned i = 0; i < num_lits; i++) { + bool sign = lits[i].sign(); + expr * v = ctx.bool_var2expr(lits[i].var()); + m.inc_ref(v); + m_literals[i] = TAG(expr*, v, sign); + } + SASSERT(!in_region()); + } + + theory_lemma_justification::~theory_lemma_justification() { + SASSERT(!in_region()); + dealloc_svect(m_literals); + } + + void theory_lemma_justification::del_eh(ast_manager & m) { + for (unsigned i = 0; i < m_num_literals; i++) { + m.dec_ref(UNTAG(expr*, m_literals[i])); + } + m_params.reset(); + } + + proof * theory_lemma_justification::mk_proof(conflict_resolution & cr) { + ast_manager & m = cr.get_manager(); + expr_ref_vector lits(m); + for (unsigned i = 0; i < m_num_literals; i++) { + bool sign = GET_TAG(m_literals[i]) != 0; + expr * v = UNTAG(expr*, m_literals[i]); + expr_ref l(m); + if (sign) + l = m.mk_not(v); + else + l = v; + lits.push_back(l); + } + if (lits.size() == 1) + return m.mk_th_lemma(m_th_id, lits.get(0), 0, 0, m_params.size(), m_params.c_ptr()); + else + return m.mk_th_lemma(m_th_id, m.mk_or(lits.size(), lits.c_ptr()), 0, 0, m_params.size(), m_params.c_ptr()); + } + +}; + diff --git a/lib/smt_justification.h b/lib/smt_justification.h new file mode 100644 index 000000000..4f402daf2 --- /dev/null +++ b/lib/smt_justification.h @@ -0,0 +1,412 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_justification.h + +Abstract: + + Proof-like objects for tracking dependencies in the SMT engine, and generating proofs. + +Author: + + Leonardo de Moura (leonardo) 2008-02-18. + +Revision History: + +--*/ +#ifndef _SMT_JUSTIFICATION_H_ +#define _SMT_JUSTIFICATION_H_ + +#include"ast.h" +#include"smt_types.h" +#include"smt_literal.h" +#include"smt_eq_justification.h" + +namespace smt { + + class conflict_resolution; + + typedef ptr_vector justification_vector; + + /** + \brief Pseudo-proof objects. They are mainly used to track dependencies. + When proof generation is enabled, they are also used to produce proofs. + + Justification objects are allocated using a stack based policy. + Actually, there is one exception: justification of lemmas. + Lemmas created at scope level n may remain alive even after scope level n is backtracked. + Lemmas are deleted by a GC that runs from time to time. So, a justification attached + to a lemma will may remain alive after scope level n is backtracked. + + So, I allow justification objects to be allocated in regions and + in the regular heap. The method in_region() should return true if the object is + allocated in a region. + */ + class justification { + unsigned m_mark:1; + 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() {} + + /** + \brief This method should return true if the method del_eh needs to be invoked + to free resources. + */ + virtual bool has_del_eh() const { + return false; + } + + /** + \brief Free the resources allocated by this object. + */ + virtual void del_eh(ast_manager & m) { + } + + /** + \brief Mark the antecedents the justification object. + The antecedents are marked using the mark methods of the + conflict_resolution object. + */ + virtual void get_antecedents(conflict_resolution & cr){ + } + + /** + \brief Return the id of the theory that produced the proof object. + */ + virtual theory_id get_from_theory() const { + return null_theory_id; + } + + void set_mark() { SASSERT(!m_mark); m_mark = true; } + + void unset_mark() { SASSERT(m_mark); m_mark = false; } + + bool is_marked() const { return m_mark; } + + unsigned hash() const { return get_ptr_hash(this); } + + virtual proof * mk_proof(conflict_resolution & cr) = 0; + + bool in_region() const { return m_in_region; } + + 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 { + proof * m_proof; + public: + justification_proof_wrapper(context & ctx, proof * pr, bool in_region = true); + + virtual bool has_del_eh() const { + return true; + } + + virtual void del_eh(ast_manager & m); + + virtual proof * mk_proof(conflict_resolution & cr); + + virtual char const * get_name() const { return "proof-wrapper"; } + }; + + class unit_resolution_justification : public justification { + justification * m_antecedent; + unsigned m_num_literals; + literal * m_literals; + public: + unit_resolution_justification(region & r, justification * js, unsigned num_lits, literal const * lits); + + unit_resolution_justification(justification * js, unsigned num_lits, literal const * lits); + + ~unit_resolution_justification(); + + virtual bool has_del_eh() const { + return !in_region() && m_antecedent && m_antecedent->has_del_eh(); + } + + virtual void del_eh(ast_manager & m) { + if (!in_region() && m_antecedent) m_antecedent->del_eh(m); + } + + virtual void get_antecedents(conflict_resolution & cr); + + virtual proof * mk_proof(conflict_resolution & cr); + + virtual char const * get_name() const { return "unit-resolution"; } + }; + + class eq_conflict_justification : public justification { + enode * m_node1; + enode * m_node2; + eq_justification m_js; + public: + eq_conflict_justification(enode * n1, enode * n2, eq_justification js): + m_node1(n1), + m_node2(n2), + m_js(js) { + } + + virtual void get_antecedents(conflict_resolution & cr); + + virtual proof * mk_proof(conflict_resolution & cr); + + virtual char const * get_name() const { return "eq-conflict"; } + }; + + /** + \brief Justification for m_node = root + */ + class eq_root_propagation_justification : public justification { + enode * m_node; + public: + eq_root_propagation_justification(enode * n):m_node(n) { + } + + virtual void get_antecedents(conflict_resolution & cr); + + virtual proof * mk_proof(conflict_resolution & cr); + + virtual char const * get_name() const { return "eq-root"; } + }; + + /** + \brief Justification for m_node1 = m_node2 + */ + class eq_propagation_justification : public justification { + enode * m_node1; + enode * m_node2; + public: + eq_propagation_justification(enode * n1, enode * n2):m_node1(n1), m_node2(n2) { + } + + virtual void get_antecedents(conflict_resolution & cr); + + virtual proof * mk_proof(conflict_resolution & cr); + + virtual char const * get_name() const { return "eq-propagation"; } + }; + + /** + \brief Justification for p(x) <=> p(y), p(x) ===> p(y) + */ + class mp_iff_justification : public justification { + enode * m_node1; // p(x) + enode * m_node2; // p(y) + public: + mp_iff_justification(enode * n1, enode * n2):m_node1(n1), m_node2(n2) { + } + + virtual void get_antecedents(conflict_resolution & cr); + + virtual proof * mk_proof(conflict_resolution & cr); + + virtual char const * get_name() const { return "mp-iff"; } + }; + + /** + \brief Abstract class for justifications that contains set of literals. + */ + class simple_justification : public justification { + protected: + unsigned m_num_literals; + literal * m_literals; + + bool antecedent2proof(conflict_resolution & cr, ptr_buffer & result); + + public: + simple_justification(region & r, unsigned num_lits, literal const * lits); + + virtual void get_antecedents(conflict_resolution & cr); + + virtual proof * mk_proof(conflict_resolution & cr) = 0; + + virtual char const * get_name() const { return "simple"; } + }; + + class simple_theory_justification : public simple_justification { + protected: + family_id m_th_id; + vector m_params; + public: + simple_theory_justification( + family_id fid, region & r, + unsigned num_lits, literal const * lits, + unsigned num_params, parameter* params): + simple_justification(r, num_lits, lits), + m_th_id(fid), m_params(num_params, params) {} + virtual ~simple_theory_justification() {} + + virtual bool has_del_eh() const { return !m_params.empty(); } + + virtual void del_eh(ast_manager & m) { m_params.reset(); } + + virtual theory_id get_from_theory() const { return m_th_id; } + + }; + + class theory_axiom_justification : public simple_theory_justification { + public: + + theory_axiom_justification(family_id fid, region & r, + unsigned num_lits, literal const * lits, + unsigned num_params = 0, parameter* params = 0): + simple_theory_justification(fid, r, num_lits, lits, num_params, params) {} + + virtual void get_antecedents(conflict_resolution & cr) {} + + virtual proof * mk_proof(conflict_resolution & cr); + + virtual char const * get_name() const { return "theory-axiom"; } + }; + + class theory_propagation_justification : public simple_theory_justification { + literal m_consequent; + public: + theory_propagation_justification(family_id fid, region & r, unsigned num_lits, literal const * lits, literal consequent, + unsigned num_params = 0, parameter* params = 0): + simple_theory_justification(fid, r, num_lits, lits, num_params, params), m_consequent(consequent) {} + + virtual proof * mk_proof(conflict_resolution & cr); + + + virtual char const * get_name() const { return "theory-propagation"; } + }; + + class theory_conflict_justification : public simple_theory_justification { + public: + theory_conflict_justification(family_id fid, region & r, unsigned num_lits, literal const * lits, + unsigned num_params = 0, parameter* params = 0): + simple_theory_justification(fid, r, num_lits, lits, num_params, params) {} + + virtual proof * mk_proof(conflict_resolution & cr); + + virtual char const * get_name() const { return "theory-conflict"; } + }; + + /** + \brief Abstract class for justifications that contains set of literals and equalities. + */ + class ext_simple_justification : public simple_justification { + protected: + unsigned m_num_eqs; + enode_pair * m_eqs; + + bool antecedent2proof(conflict_resolution & cr, ptr_buffer & result); + + public: + ext_simple_justification(region & r, unsigned num_lits, literal const * lits, + unsigned num_eqs, enode_pair const * eqs); + + virtual void get_antecedents(conflict_resolution & cr); + + virtual proof * mk_proof(conflict_resolution & cr) = 0; + + virtual char const * get_name() const { return "ext-simple"; } + }; + + /** + \brief Abstract class for justifications that contains set of literals and equalities. + */ + class ext_theory_simple_justification : public ext_simple_justification { + protected: + family_id m_th_id; + vector m_params; + + public: + ext_theory_simple_justification(family_id fid, region & r, unsigned num_lits, literal const * lits, + unsigned num_eqs, enode_pair const * eqs, + unsigned num_params = 0, parameter* params = 0): + ext_simple_justification(r, num_lits, lits, num_eqs, eqs), m_th_id(fid), m_params(num_params, params) {} + + virtual ~ext_theory_simple_justification() {} + + virtual bool has_del_eh() const { return !m_params.empty(); } + + virtual void del_eh(ast_manager & m) { m_params.reset(); } + + virtual theory_id get_from_theory() const { return m_th_id; } + }; + + class ext_theory_propagation_justification : public ext_theory_simple_justification { + literal m_consequent; + public: + ext_theory_propagation_justification(family_id fid, region & r, + unsigned num_lits, literal const * lits, + unsigned num_eqs, enode_pair const * eqs, + literal consequent, + unsigned num_params = 0, parameter* params = 0): + ext_theory_simple_justification(fid, r, num_lits, lits, num_eqs, eqs, num_params, params), + m_consequent(consequent) {} + + virtual proof * mk_proof(conflict_resolution & cr); + + virtual char const * get_name() const { return "ext-theory-propagation"; } + }; + + class ext_theory_conflict_justification : public ext_theory_simple_justification { + public: + ext_theory_conflict_justification(family_id fid, region & r, unsigned num_lits, literal const * lits, + unsigned num_eqs, enode_pair const * eqs, + unsigned num_params = 0, parameter* params = 0): + ext_theory_simple_justification(fid, r, num_lits, lits, num_eqs, eqs, num_params, params) {} + + virtual proof * mk_proof(conflict_resolution & cr); + + virtual char const * get_name() const { return "ext-theory-conflict"; } + }; + + class ext_theory_eq_propagation_justification : public ext_theory_simple_justification { + enode * m_lhs; + enode * m_rhs; + public: + ext_theory_eq_propagation_justification( + family_id fid, region & r, + unsigned num_lits, literal const * lits, + unsigned num_eqs, enode_pair const * eqs, + enode * lhs, enode * rhs, + unsigned num_params = 0, parameter* params = 0): + ext_theory_simple_justification(fid, r, num_lits, lits, num_eqs, eqs, num_params, params), m_lhs(lhs), m_rhs(rhs) {} + + virtual proof * mk_proof(conflict_resolution & cr); + + virtual char const * get_name() const { return "ext-theory-eq-propagation"; } + }; + + /** + \brief A theory lemma is similar to a theory axiom, but it is attached to a CLS_AUX_LEMMA clause instead of CLS_AUX. + So, it cannot be stored in the heap, and it is unsafe to store literals, since it may be deleted during backtracking. + Instead, they store a set of pairs (sign, expr). This pair is represented as a tagged pointer. + */ + class theory_lemma_justification : public justification { + family_id m_th_id; + vector m_params; + unsigned m_num_literals; + expr ** m_literals; + + public: + theory_lemma_justification(family_id fid, context & ctx, unsigned num_lits, literal const * lits, + unsigned num_params = 0, parameter* params = 0); + + virtual ~theory_lemma_justification(); + + virtual bool has_del_eh() const { + return true; + } + + virtual void del_eh(ast_manager & m); + + virtual void get_antecedents(conflict_resolution & cr) {} + + virtual proof * mk_proof(conflict_resolution & cr); + + virtual char const * get_name() const { return "theory-lemma"; } + }; + +}; + +#endif /* _SMT_JUSTIFICATION_H_ */ + diff --git a/lib/smt_literal.cpp b/lib/smt_literal.cpp new file mode 100644 index 000000000..05bf4d04b --- /dev/null +++ b/lib/smt_literal.cpp @@ -0,0 +1,100 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_literal.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-18. + +Revision History: + +--*/ +#include"smt_literal.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" + +namespace smt { + + void literal::display(std::ostream & out, ast_manager & m, expr * const * bool_var2expr_map) const { + if (*this == true_literal) + out << "true"; + else if (*this == false_literal) + out << "false"; + else if (sign()) + out << "(not " << mk_pp(bool_var2expr_map[var()], m) << ")"; + else + out << mk_pp(bool_var2expr_map[var()], m); + } + + void literal::display_compact(std::ostream & out, expr * const * bool_var2expr_map) const { + if (*this == true_literal) + out << "true"; + else if (*this == false_literal) + out << "false"; + else if (sign()) + out << "(not #" << bool_var2expr_map[var()]->get_id() << ")"; + else + out << "#" << bool_var2expr_map[var()]->get_id(); + } + + std::ostream & operator<<(std::ostream & out, literal l) { + if (l == true_literal) + out << "true"; + else if (l == false_literal) + out << "false"; + else if (l.sign()) + out << "(not p" << l.var() << ")"; + else + out << "p" << l.var(); + return out; + } + + std::ostream & operator<<(std::ostream & out, const literal_vector & v) { + display(out, v.begin(), v.end()); + return out; + } + + void display_compact(std::ostream & out, unsigned num_lits, literal const * lits, expr * const * bool_var2expr_map) { + for (unsigned i = 0; i < num_lits; i++) { + if (i > 0) + out << " "; + lits[i].display_compact(out, bool_var2expr_map); + } + } + + void display_verbose(std::ostream & out, ast_manager& m, unsigned num_lits, literal const * lits, expr * const * bool_var2expr_map) { + for (unsigned i = 0; i < num_lits; i++) { + if (i > 0) + out << " "; + lits[i].display(out, m, bool_var2expr_map); + } + } + + /** + \brief Return true if lits1 subsumes lits2. + That is every literal in lits1 is in lits2 + */ + bool backward_subsumption(unsigned num_lits1, literal const * lits1, unsigned num_lits2, literal const * lits2) { + unsigned i = 0; + for (; i < num_lits1; i++) { + literal l1 = lits1[i]; + unsigned j = 0; + for (; j < num_lits2; j++) + if (l1 == lits2[j]) + break; + if (j == num_lits2) + break; + } + return i == num_lits1; + } + + +}; + diff --git a/lib/smt_literal.h b/lib/smt_literal.h new file mode 100644 index 000000000..496952227 --- /dev/null +++ b/lib/smt_literal.h @@ -0,0 +1,122 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_literal.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-18. + +Revision History: + +--*/ +#ifndef _SMT_LITERAL_H_ +#define _SMT_LITERAL_H_ + +#include"ast.h" +#include"smt_types.h" +#include"approx_set.h" + +namespace smt { + /** + \brief The literal b is represented by the value 2*b, and + the literal (not b) by the value 2*b + 1 + + */ + class literal { + int m_val; + + public: + literal():m_val(-2) { + SASSERT(var() == null_bool_var && !sign()); + } + + explicit literal(bool_var v, bool sign = false): + m_val((v << 1) + static_cast(sign)) { + } + + bool_var var() const { + return m_val >> 1; + } + + bool sign() const { + return m_val & 1; + } + + int index() const { + return m_val; + } + + void neg() { + m_val = m_val ^ 1; + } + + friend literal operator~(literal l); + + friend literal to_literal(int x); + + void display(std::ostream & out, ast_manager & m, expr * const * bool_var2expr_map) const; + + void display_compact(std::ostream & out, expr * const * bool_var2expr_map) const; + + unsigned hash() const { return m_val; } + }; + + inline bool operator==(literal l1, literal l2) { + return l1.index() == l2.index(); + } + + inline bool operator!=(literal l1, literal l2) { + return l1.index() != l2.index(); + } + + inline bool operator<(literal l1, literal l2) { + return l1.index() < l2.index(); + } + + inline literal operator~(literal l) { + literal r; + r.m_val = l.m_val ^ 1; + return r; + } + + inline literal to_literal(int x) { + literal l; + l.m_val = x; + return l; + } + + const literal null_literal; + const literal true_literal(true_bool_var, false); + const literal false_literal(true_bool_var, true); + + typedef svector literal_vector; + typedef sbuffer literal_buffer; + std::ostream & operator<<(std::ostream & out, literal l); + std::ostream & operator<<(std::ostream & out, const literal_vector & v); + + void display_compact(std::ostream & out, unsigned num_lits, literal const * lits, expr * const * bool_var2expr_map); + + void display_verbose(std::ostream & out, ast_manager& m, unsigned num_lits, literal const * lits, expr * const * bool_var2expr_map); + + template + void neg_literals(unsigned num_lits, literal const * lits, T & result) { + for (unsigned i = 0; i < num_lits; ++i) + result.push_back(~lits[i]); + } + + struct literal2unsigned { unsigned operator()(literal l) const { return l.index(); } }; + + typedef approx_set_tpl literal_approx_set; + + bool backward_subsumption(unsigned num_lits1, literal const * lits1, unsigned num_lits2, literal const * lits2); +}; + +#endif /* _SMT_LITERAL_H_ */ + diff --git a/lib/smt_model_checker.cpp b/lib/smt_model_checker.cpp new file mode 100644 index 000000000..6e9cd01fc --- /dev/null +++ b/lib/smt_model_checker.cpp @@ -0,0 +1,419 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_model_checker.cpp + +Abstract: + + Model checker + +Author: + + Leonardo de Moura (leonardo) 2010-12-03. + +Revision History: + +--*/ + +#include"smt_model_checker.h" +#include"smt_context.h" +#include"smt_model_finder.h" +#include"pull_quant.h" +#include"for_each_expr.h" +#include"var_subst.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"model_pp.h" +#include"ast_smt2_pp.h" + +namespace smt { + + model_checker::model_checker(ast_manager & m, qi_params const & p, model_finder & mf): + m_manager(m), + m_params(p), + m_qm(0), + m_context(0), + m_root2value(0), + m_model_finder(mf), + m_max_cexs(1), + m_iteration_idx(0), + m_curr_model(0), + m_new_instances_bindings(m) { + } + + model_checker::~model_checker() { + m_aux_context = 0; // delete aux context before fparams + m_fparams = 0; + } + + quantifier * model_checker::get_flat_quantifier(quantifier * q) { + return m_model_finder.get_flat_quantifier(q); + } + + void model_checker::set_qm(quantifier_manager & qm) { + SASSERT(m_qm == 0); + SASSERT(m_context == 0); + m_qm = &qm; + m_context = &(m_qm->kernel()); + } + + /** + \brief Return a term in the context that evaluates to val. + */ + expr * model_checker::get_term_from_ctx(expr * val) { + if (m_value2expr.empty()) { + // populate m_value2expr + obj_map::iterator it = m_root2value->begin(); + obj_map::iterator end = m_root2value->end(); + for (; it != end; ++it) { + enode * n = (*it).m_key; + expr * val = (*it).m_value; + n = n->get_eq_enode_with_min_gen(); + m_value2expr.insert(val, n->get_owner()); + } + } + expr * t = 0; + m_value2expr.find(val, t); + return t; + } + + /** + \brief Assert in m_aux_context, the constraint + + sk = e_1 OR ... OR sk = e_n + + where {e_1, ..., e_n} is the universe. + */ + void model_checker::restrict_to_universe(expr * sk, obj_hashtable const & universe) { + SASSERT(!universe.empty()); + ptr_buffer eqs; + obj_hashtable::iterator it = universe.begin(); + obj_hashtable::iterator end = universe.end(); + for (; it != end; ++it) { + expr * e = *it; + eqs.push_back(m_manager.mk_eq(sk, e)); + } + m_aux_context->assert_expr(m_manager.mk_or(eqs.size(), eqs.c_ptr())); + } + +#define PP_DEPTH 8 + + /** + \brief Assert the negation of q after applying the interpretation in m_curr_model to the uninterpreted symbols in q. + + The variables are replaced by skolem constants. These constants are stored in sks. + */ + void model_checker::assert_neg_q_m(quantifier * q, expr_ref_vector & sks) { + expr_ref tmp(m_manager); + m_curr_model->eval(q->get_expr(), tmp, true); + TRACE("model_checker", tout << "q after applying interpretation:\n" << mk_ismt2_pp(tmp, m_manager) << "\n";); + ptr_buffer subst_args; + unsigned num_decls = q->get_num_decls(); + subst_args.resize(num_decls, 0); + sks.resize(num_decls, 0); + for (unsigned i = 0; i < num_decls; i++) { + sort * s = q->get_decl_sort(num_decls - i - 1); + expr * sk = m_manager.mk_fresh_const(0, s); + sks[num_decls - i - 1] = sk; + subst_args[num_decls - i - 1] = sk; + if (m_curr_model->is_finite(s)) { + restrict_to_universe(sk, m_curr_model->get_known_universe(s)); + } + } + + expr_ref sk_body(m_manager); + var_subst s(m_manager); + s(tmp, subst_args.size(), subst_args.c_ptr(), sk_body); + expr_ref r(m_manager); + r = m_manager.mk_not(sk_body); + TRACE("model_checker", tout << "mk_neg_q_m:\n" << mk_ismt2_pp(r, m_manager) << "\n";); + m_aux_context->assert_expr(r); + } + + bool model_checker::add_instance(quantifier * q, model * cex, expr_ref_vector & sks, bool use_inv) { + if (cex == 0) + return false; // no model available. + unsigned num_decls = q->get_num_decls(); + unsigned num_sks = sks.size(); + // Remark: sks were created for the flat version of q. + SASSERT(num_sks >= num_decls); + expr_ref_buffer bindings(m_manager); + bindings.resize(num_decls); + unsigned max_generation = 0; + for (unsigned i = 0; i < num_decls; i++) { + expr * sk = sks.get(num_decls - i - 1); + func_decl * sk_d = to_app(sk)->get_decl(); + expr_ref sk_value(m_manager); + sk_value = cex->get_const_interp(sk_d); + if (sk_value == 0) { + sk_value = cex->get_some_value(sk_d->get_range()); + if (sk_value == 0) + return false; // get_some_value failed... giving up + } + if (use_inv) { + unsigned sk_term_gen; + expr * sk_term = m_model_finder.get_inv(q, i, sk_value, sk_term_gen); + if (sk_term != 0) { + SASSERT(!m_manager.is_model_value(sk_term)); + if (sk_term_gen > max_generation) + max_generation = sk_term_gen; + sk_value = sk_term; + } + else { + return false; + } + } + else { + expr * sk_term = get_term_from_ctx(sk_value); + if (sk_term != 0) { + sk_value = sk_term; + } + else { + if (m_manager.is_model_value(sk_value)) + return false; + } + } + bindings.set(num_decls - i - 1, sk_value); + } + + TRACE("model_checker", tout << q->get_qid() << " found (use_inv: " << use_inv << ") new instance:\n"; + for (unsigned i = 0; i < num_decls; i++) { + tout << mk_ismt2_pp(bindings[i], m_manager) << "\n"; + }); + + for (unsigned i = 0; i < num_decls; i++) + m_new_instances_bindings.push_back(bindings[i]); + void * mem = m_new_instances_region.allocate(instance::get_obj_size(q->get_num_decls())); + instance * new_inst = new (mem) instance(q, bindings.c_ptr(), max_generation); + m_new_instances.push_back(new_inst); + + return true; + } + + bool model_checker::add_blocking_clause(model * cex, expr_ref_vector & sks) { + SASSERT(cex != 0); + unsigned num_sks = sks.size(); + expr_ref_buffer diseqs(m_manager); + for (unsigned i = 0; i < num_sks; i++) { + expr * sk = sks.get(i); + func_decl * sk_d = to_app(sk)->get_decl(); + expr_ref sk_value(m_manager); + sk_value = cex->get_const_interp(sk_d); + if (sk_value == 0) { + sk_value = cex->get_some_value(sk_d->get_range()); + if (sk_value == 0) + return false; // get_some_value failed... aborting add_blocking_clause + } + diseqs.push_back(m_manager.mk_not(m_manager.mk_eq(sk, sk_value))); + } + expr_ref blocking_clause(m_manager); + blocking_clause = m_manager.mk_or(diseqs.size(), diseqs.c_ptr()); + TRACE("model_checker", tout << "blocking clause:\n" << mk_ismt2_pp(blocking_clause, m_manager) << "\n";); + m_aux_context->assert_expr(blocking_clause); + return true; + } + + /** + \brief Return true if q is satisfied by m_curr_model. + */ + bool model_checker::check(quantifier * q) { + SASSERT(!m_aux_context->relevancy()); + m_aux_context->push(); + + quantifier * flat_q = get_flat_quantifier(q); + TRACE("model_checker", tout << "model checking:\n" << mk_ismt2_pp(q->get_expr(), m_manager) << "\n" << + mk_ismt2_pp(flat_q->get_expr(), m_manager) << "\n";); + expr_ref_vector sks(m_manager); + + assert_neg_q_m(flat_q, sks); + TRACE("model_checker", tout << "skolems:\n"; + for (unsigned i = 0; i < sks.size(); i++) { + expr * sk = sks.get(i); + tout << mk_ismt2_pp(sk, m_manager) << " " << mk_pp(m_manager.get_sort(sk), m_manager) << "\n"; + }); + + lbool r = m_aux_context->check(); + TRACE("model_checker", tout << "[complete] model-checker result: " << to_sat_str(r) << "\n";); + if (r == l_false) { + m_aux_context->pop(1); + return true; // quantifier is satisfied by m_curr_model + } + model_ref complete_cex; + m_aux_context->get_model(complete_cex); + + // try to find new instances using instantiation sets. + m_model_finder.restrict_sks_to_inst_set(m_aux_context.get(), q, sks); + + unsigned num_new_instances = 0; + + while (true) { + lbool r = m_aux_context->check(); + TRACE("model_checker", tout << "[restricted] model-checker (" << (num_new_instances+1) << ") result: " << to_sat_str(r) << "\n";); + if (r == l_false) + break; + model_ref cex; + m_aux_context->get_model(cex); + if (add_instance(q, cex.get(), sks, true)) { + num_new_instances++; + if (num_new_instances < m_max_cexs) { + if (!add_blocking_clause(cex.get(), sks)) + break; // add_blocking_clause failed... stop the search for new counter-examples... + } + } + else { + break; + } + if (num_new_instances >= m_max_cexs) + break; + } + + if (num_new_instances == 0) { + // failed to create instances when restricting to inst sets... then use result of the complete model check + TRACE("model_checker", tout << "using complete_cex result:\n"; model_pp(tout, *complete_cex);); + add_instance(q, complete_cex.get(), sks, false); + } + + m_aux_context->pop(1); + return false; + } + + void model_checker::init_aux_context() { + if (!m_fparams) { + m_fparams = alloc(front_end_params, m_context->get_fparams()); + m_fparams->m_relevancy_lvl = 0; // no relevancy since the model checking problems are quantifier free + } + if (!m_aux_context) { + symbol logic; + m_aux_context = m_context->mk_fresh(&logic, m_fparams.get()); + } + } + + struct scoped_set_relevancy { + }; + + bool model_checker::check(proto_model * md, obj_map const & root2value) { + SASSERT(md != 0); + m_root2value = &root2value; + ptr_vector::const_iterator it = m_qm->begin_quantifiers(); + ptr_vector::const_iterator end = m_qm->end_quantifiers(); + if (it == end) + return true; + + if (m_iteration_idx >= m_params.m_mbqi_max_iterations) + return false; + + m_curr_model = md; + m_value2expr.reset(); + + md->compress(); + + TRACE("model_checker", tout << "MODEL_CHECKER INVOKED\n"; + tout << "model:\n"; model_pp(tout, *m_curr_model);); + if (m_params.m_mbqi_trace) { + verbose_stream() << "[mbqi] started\n"; + } + + init_aux_context(); + + bool found_relevant = false; + unsigned num_failures = 0; + + for (; it != end; ++it) { + quantifier * q = *it; + if (m_context->is_relevant(q) && m_context->get_assignment(q) == l_true) { + if (m_params.m_mbqi_trace && q->get_qid() != symbol::null) { + verbose_stream() << "[mbqi] checking: " << q->get_qid() << "\n"; + } + found_relevant = true; + if (!check(q)) { + IF_VERBOSE(5, verbose_stream() << "current model does not satisfy: " << q->get_qid() << "\n";); + if (m_params.m_mbqi_trace) { + verbose_stream() << "[mbqi] failed " << q->get_qid() << "\n"; + } + num_failures++; + } + } + } + + if (found_relevant) + m_iteration_idx++; + + TRACE("model_checker", tout << "model after check:\n"; model_pp(tout, *md);); + TRACE("model_checker", tout << "model checker result: " << (num_failures == 0) << "\n";); + m_max_cexs += m_params.m_mbqi_max_cexs; + if (num_failures == 0) + m_curr_model->cleanup(); + if (m_params.m_mbqi_trace) { + if (num_failures == 0) + verbose_stream() << "[mbqi] succeeded\n"; + else + verbose_stream() << "[mbqi] num failures " << num_failures << "\n"; + } + return num_failures == 0; + } + + void model_checker::init_search_eh() { + m_max_cexs = m_params.m_mbqi_max_cexs; + m_iteration_idx = 0; + } + + void model_checker::restart_eh() { + IF_VERBOSE(100, verbose_stream() << "instantiating new instances...\n";); + assert_new_instances(); + reset_new_instances(); + } + + bool model_checker::has_new_instances() { + return !m_new_instances.empty(); + } + + void model_checker::reset_new_instances() { + m_new_instances_region.reset(); + m_new_instances.reset(); + } + + void model_checker::reset() { + reset_new_instances(); + } + + void model_checker::assert_new_instances() { + TRACE("model_checker_bug_detail", tout << "assert_new_instances, inconsistent: " << m_context->inconsistent() << "\n";); + ptr_buffer bindings; + ptr_vector dummy; + ptr_vector::iterator it = m_new_instances.begin(); + ptr_vector::iterator end = m_new_instances.end(); + for (; it != end; ++it) { + instance * inst = *it; + quantifier * q = inst->m_q; + if (m_context->b_internalized(q)) { + bindings.reset(); + unsigned num_decls = q->get_num_decls(); + unsigned gen = inst->m_generation; + for (unsigned i = 0; i < num_decls; i++) { + expr * b = inst->m_bindings[i]; + if (!m_context->e_internalized(b)) { + TRACE("model_checker_bug_detail", tout << "internalizing b:\n" << mk_pp(b, m_manager) << "\n";); + m_context->internalize(b, false, gen); + } + bindings.push_back(m_context->get_enode(b)); + } + TRACE("model_checker_bug_detail", tout << "instantiating... q:\n" << mk_pp(q, m_manager) << "\n"; + tout << "inconsistent: " << m_context->inconsistent() << "\n"; + tout << "bindings:\n"; + for (unsigned i = 0; i < num_decls; i++) { + expr * b = inst->m_bindings[i]; + tout << mk_pp(b, m_manager) << "\n"; + }); + TRACE("model_checker_instance", + expr_ref inst_expr(m_manager); + instantiate(m_manager, q, inst->m_bindings, inst_expr); + tout << "(assert " << mk_ismt2_pp(inst_expr, m_manager) << ")\n";); + m_context->add_instance(q, 0, num_decls, bindings.c_ptr(), gen, gen, gen, dummy); + TRACE("model_checker_bug_detail", tout << "after instantiating, inconsistent: " << m_context->inconsistent() << "\n";); + } + } + } + +}; diff --git a/lib/smt_model_checker.h b/lib/smt_model_checker.h new file mode 100644 index 000000000..fca576090 --- /dev/null +++ b/lib/smt_model_checker.h @@ -0,0 +1,98 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_model_checker.h + +Abstract: + + Model checker + AND + Model-based quantifier instantiation. + +Author: + + Leonardo de Moura (leonardo) 2010-12-03. + +Revision History: + +--*/ +#ifndef _SMT_MODEL_CHECKER_H_ +#define _SMT_MODEL_CHECKER_H_ + +#include"ast.h" +#include"obj_hashtable.h" +#include"qi_params.h" +#include"front_end_params.h" +#include"region.h" + +class proto_model; +class model; + +namespace smt { + class context; + class enode; + class model_finder; + class quantifier_manager; + + class model_checker { + ast_manager & m_manager; + qi_params const & m_params; + // copy of front_end_params for auxiliary context. + // the idea is to use a different configuration for the aux context (e.g., disable relevancy) + scoped_ptr m_fparams; + quantifier_manager * m_qm; + context * m_context; // owner of the model checker + obj_map const * m_root2value; // temp field to store mapping received in the check method. + model_finder & m_model_finder; + scoped_ptr m_aux_context; // Auxiliary context used for model checking quantifiers. + unsigned m_max_cexs; + unsigned m_iteration_idx; + proto_model * m_curr_model; + obj_map m_value2expr; + friend class instantiation_set; + + void init_aux_context(); + expr * get_term_from_ctx(expr * val); + void restrict_to_universe(expr * sk, obj_hashtable const & universe); + void assert_neg_q_m(quantifier * q, expr_ref_vector & sks); + bool add_blocking_clause(model * cex, expr_ref_vector & sks); + bool check(quantifier * q); + + struct instance { + quantifier * m_q; + unsigned m_generation; + expr * m_bindings[0]; + static unsigned get_obj_size(unsigned num_bindings) { return sizeof(instance) + num_bindings * sizeof(expr*); } + instance(quantifier * q, expr * const * bindings, unsigned gen):m_q(q), m_generation(gen) { + memcpy(m_bindings, bindings, q->get_num_decls() * sizeof(expr*)); + } + }; + + region m_new_instances_region; + expr_ref_vector m_new_instances_bindings; + ptr_vector m_new_instances; + bool add_instance(quantifier * q, model * cex, expr_ref_vector & sks, bool use_inv); + void reset_new_instances(); + void assert_new_instances(); + + quantifier * get_flat_quantifier(quantifier * q); + + public: + model_checker(ast_manager & m, qi_params const & p, model_finder & mf); + ~model_checker(); + void set_qm(quantifier_manager & qm); + context * get_context() const { return m_context; } + + bool check(proto_model * md, obj_map const & root2value); + bool has_new_instances(); + + void init_search_eh(); + void restart_eh(); + + void reset(); + }; +}; + +#endif // _SMT_MODEL_CHECKER_H_ diff --git a/lib/smt_model_finder.cpp b/lib/smt_model_finder.cpp new file mode 100644 index 000000000..619a7d60c --- /dev/null +++ b/lib/smt_model_finder.cpp @@ -0,0 +1,3471 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_model_finder.cpp + +Abstract: + + Model finding goodies for universally quantified formulas. + +Author: + + Leonardo de Moura (leonardo) 2010-12-17. + +Revision History: + +--*/ +#include"smt_model_finder.h" +#include"smt_context.h" +#include"ast_util.h" +#include"macro_util.h" +#include"arith_decl_plugin.h" +#include"bv_decl_plugin.h" +#include"array_decl_plugin.h" +#include"arith_simplifier_plugin.h" +#include"bv_simplifier_plugin.h" +#include"pull_quant.h" +#include"var_subst.h" +#include"backtrackable_set.h" +#include"for_each_expr.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"well_sorted.h" +#include"model_pp.h" +#include"ast_smt2_pp.h" + +namespace smt { + + namespace mf { + + // ----------------------------------- + // + // Auxiliary stuff + // + // ----------------------------------- + + // Append the new elements of v2 into v1. v2 should not be used after this operation, since it may suffer destructive updates. + template + void dappend(ptr_vector & v1, ptr_vector & v2) { + if (v2.empty()) + return; + if (v1.empty()) { + v1.swap(v2); + return; + } + typename ptr_vector::iterator it = v2.begin(); + typename ptr_vector::iterator end = v2.end(); + for (; it != end; ++it) { + if (!v1.contains(*it)) + v1.push_back(*it); + } + v2.finalize(); + } + + class evaluator { + public: + virtual expr * eval(expr * n, bool model_completion) = 0; + }; + + // ----------------------------------- + // + // Instantiation sets + // + // ----------------------------------- + + /** + \brief Instantiation sets are the S_{k,j} sets in the Complete quantifier instantiation paper. + */ + class instantiation_set { + ast_manager & m_manager; + obj_map m_elems; // and the associated generation + obj_map m_inv; + public: + instantiation_set(ast_manager & m):m_manager(m) {} + + ~instantiation_set() { + obj_map::iterator it = m_elems.begin(); + obj_map::iterator end = m_elems.end(); + for (; it != end; ++it) { + m_manager.dec_ref((*it).m_key); + } + m_elems.reset(); + } + + obj_map const & get_elems() const { return m_elems; } + + void insert(expr * n, unsigned generation) { + if (m_elems.contains(n)) + return; + m_manager.inc_ref(n); + m_elems.insert(n, generation); + SASSERT(!m_manager.is_model_value(n)); + } + + void display(std::ostream & out) const { + obj_map::iterator it = m_elems.begin(); + obj_map::iterator end = m_elems.end(); + for (; it != end; ++it) { + out << mk_bounded_pp((*it).m_key, m_manager) << " [" << (*it).m_value << "]\n"; + } + out << "inverse:\n"; + obj_map::iterator it2 = m_inv.begin(); + obj_map::iterator end2 = m_inv.end(); + for (; it2 != end2; ++it2) { + out << mk_bounded_pp((*it2).m_key, m_manager) << " -> " << mk_bounded_pp((*it2).m_value, m_manager) << "\n"; + } + } + + expr * get_inv(expr * v) const { + expr * t = 0; + m_inv.find(v, t); + return t; + } + + unsigned get_generation(expr * t) const { + unsigned gen = 0; + m_elems.find(t, gen); + return gen; + } + + void mk_inverse(evaluator & ev) { + obj_map::iterator it = m_elems.begin(); + obj_map::iterator end = m_elems.end(); + for (; it != end; ++it) { + expr * t = (*it).m_key; + SASSERT(!m_manager.is_model_value(t)); + unsigned gen = (*it).m_value; + expr * t_val = ev.eval(t, true); + + expr * old_t = 0; + if (m_inv.find(t_val, old_t)) { + unsigned old_t_gen = 0; + SASSERT(m_elems.contains(old_t)); + m_elems.find(old_t, old_t_gen); + if (gen < old_t_gen) { + m_inv.insert(t_val, t); + } + } + else { + m_inv.insert(t_val, t); + } + } + } + + obj_map const & get_inv_map() const { + return m_inv; + } + }; + + /** + During model construction time, + we solve several constraints that impose restrictions + on how the model for the ground formulas may be extended to + a model to the relevant universal quantifiers. + + The class node and its subclasses are used to solve + these constraints. + */ + + // ----------------------------------- + // + // nodes + // + // ----------------------------------- + + /** + \brief Base class used to solve model construction constraints. + */ + class node { + unsigned m_id; + node * m_find; + unsigned m_eqc_size; + + sort * m_sort; // sort of the elements in the instantiation set. + + bool m_mono_proj; // relevant for integers & reals & bit-vectors + bool m_signed_proj; // relevant for bit-vectors. + ptr_vector m_avoid_set; + ptr_vector m_exceptions; + + instantiation_set * m_set; + + expr * m_else; + func_decl * m_proj; + + public: + node(unsigned id, sort * s): + m_id(id), + m_find(0), + m_eqc_size(1), + m_sort(s), + m_mono_proj(false), + m_signed_proj(false), + m_set(0), + m_else(0), + m_proj(0) { + } + + ~node() { + if (m_set) + dealloc(m_set); + } + + unsigned get_id() const { return m_id; } + + sort * get_sort() const { return m_sort; } + + bool is_root() const { return m_find == 0; } + + node * get_root() const { + node * curr = const_cast(this); + while (!curr->is_root()) { + curr = curr->m_find; + } + SASSERT(curr->is_root()); + return curr; + } + + void merge(node * other) { + node * r1 = get_root(); + node * r2 = other->get_root(); + SASSERT(r1->m_set == 0); + SASSERT(r2->m_set == 0); + SASSERT(r1->get_sort() == r2->get_sort()); + if (r1 == r2) + return; + if (r1->m_eqc_size > r2->m_eqc_size) + std::swap(r1, r2); + r1->m_find = r2; + r2->m_eqc_size += r1->m_eqc_size; + if (r1->m_mono_proj) + r2->m_mono_proj = true; + if (r1->m_signed_proj) + r2->m_signed_proj = true; + dappend(r2->m_avoid_set, r1->m_avoid_set); + dappend(r2->m_exceptions, r1->m_exceptions); + } + + void insert_avoid(node * n) { + ptr_vector & as = get_root()->m_avoid_set; + if (!as.contains(n)) + as.push_back(n); + } + + void insert_exception(expr * n) { + ptr_vector & ex = get_root()->m_exceptions; + if (!ex.contains(n)) + ex.push_back(n); + } + + void set_mono_proj() { + get_root()->m_mono_proj = true; + } + + bool is_mono_proj() const { + return get_root()->m_mono_proj; + } + + void set_signed_proj() { + get_root()->m_signed_proj = true; + } + + bool is_signed_proj() const { + return get_root()->m_signed_proj; + } + + void mk_instatiation_set(ast_manager & m) { + SASSERT(is_root()); + m_set = alloc(instantiation_set, m); + } + + void insert(expr * n, unsigned generation) { + SASSERT(is_ground(n)); + get_root()->m_set->insert(n, generation); + } + + void display(std::ostream & out, ast_manager & m) const { + if (is_root()) { + out << "root node ------\n"; + out << "@" << m_id << " mono: " << m_mono_proj << " signed: " << m_signed_proj << ", sort: " << mk_pp(m_sort, m) << "\n"; + out << "avoid-set: "; + ptr_vector::const_iterator it1 = m_avoid_set.begin(); + ptr_vector::const_iterator end1 = m_avoid_set.end(); + for (; it1 != end1; ++it1) { + out << "@" << (*it1)->get_root()->get_id() << " "; + } + out << "\n"; + out << "exceptions: "; + ptr_vector::const_iterator it2 = m_exceptions.begin(); + ptr_vector::const_iterator end2 = m_exceptions.end(); + for (; it2 != end2; ++it2) { + out << mk_bounded_pp((*it2), m) << " "; + } + out << "\n"; + if (m_else) + out << "else: " << mk_pp(m_else, m, 6) << "\n"; + if (m_proj) + out << "projection: " << m_proj->get_name() << "\n"; + if (m_set) { + out << "instantiation-set:\n"; + m_set->display(out); + } + out << "----------------\n"; + } + else { + out << "@" << m_id << " -> @" << get_root()->get_id() << "\n"; + } + } + + instantiation_set const * get_instantiation_set() const { return get_root()->m_set; } + + instantiation_set * get_instantiation_set() { return get_root()->m_set; } + + ptr_vector const & get_exceptions() const { return get_root()->m_exceptions; } + + ptr_vector const & get_avoid_set() const { return get_root()->m_avoid_set; } + + // return true if m_avoid_set.contains(this) + bool must_avoid_itself() const { + node * r = get_root(); + ptr_vector::const_iterator it = m_avoid_set.begin(); + ptr_vector::const_iterator end = m_avoid_set.end(); + for (; it != end; ++it) { + if (r == (*it)->get_root()) + return true; + } + return false; + } + + void set_else(expr * e) { + SASSERT(!is_mono_proj()); + SASSERT(get_root()->m_else == 0); + get_root()->m_else = e; + } + + expr * get_else() const { + return get_root()->m_else; + } + + void set_proj(func_decl * f) { + SASSERT(get_root()->m_proj == 0); + get_root()->m_proj = f; + } + + func_decl * get_proj() const { + return get_root()->m_proj; + } + }; + + typedef std::pair ast_idx_pair; + typedef pair_hash, unsigned_hash> ast_idx_pair_hash; + typedef map > key2node; + + /** + \brief Auxiliary class for processing the "Almost uninterpreted fragment" described in the paper: + Complete instantiation for quantified SMT formulas + + The idea is to create node objects based on the information produced by the quantifier_analyzer. + */ + class auf_solver : public evaluator { + ast_manager & m_manager; + arith_simplifier_plugin * m_asimp; + bv_simplifier_plugin * m_bvsimp; + ptr_vector m_nodes; + unsigned m_next_node_id; + key2node m_uvars; + key2node m_A_f_is; + + context * m_context; + + // Mapping from sort to auxiliary constant. + // This auxiliary constant is used as a "witness" that is asserted as different from a + // finite number of terms. + // It is only safe to use this constant for infinite sorts. + obj_map m_sort2k; + expr_ref_vector m_ks; // range of m_sort2k + + // Support for evaluating expressions in the current model. + proto_model * m_model; + obj_map m_eval_cache; + expr_ref_vector m_eval_cache_range; + + ptr_vector m_root_nodes; + + expr_ref_vector * m_new_constraints; + + void reset_sort2k() { + m_sort2k.reset(); + m_ks.reset(); + } + + void reset_eval_cache() { + m_eval_cache.reset(); + m_eval_cache_range.reset(); + } + + node * mk_node(key2node & m, ast * n, unsigned i, sort * s) { + node * r = 0; + ast_idx_pair k(n, i); + if (m.find(k, r)) { + SASSERT(r->get_sort() == s); + return r; + } + r = alloc(node, m_next_node_id, s); + m_next_node_id++; + m.insert(k, r); + m_nodes.push_back(r); + return r; + } + + void display_key2node(std::ostream & out, key2node const & m) const { + key2node::iterator it = m.begin(); + key2node::iterator end = m.end(); + for (; it != end; ++it) { + ast * a = (*it).m_key.first; + unsigned i = (*it).m_key.second; + node * n = (*it).m_value; + out << "#" << a->get_id() << ":" << i << " -> @" << n->get_id() << "\n"; + } + } + + void display_A_f_is(std::ostream & out) const { + key2node::iterator it = m_A_f_is.begin(); + key2node::iterator end = m_A_f_is.end(); + for (; it != end; ++it) { + func_decl * f = static_cast((*it).m_key.first); + unsigned i = (*it).m_key.second; + node * n = (*it).m_value; + out << f->get_name() << ":" << i << " -> @" << n->get_id() << "\n"; + } + } + + void flush_nodes() { + std::for_each(m_nodes.begin(), m_nodes.end(), delete_proc()); + } + + public: + auf_solver(ast_manager & m, simplifier & s): + m_manager(m), + m_next_node_id(0), + m_context(0), + m_ks(m), + m_model(0), + m_eval_cache_range(m), + m_new_constraints(0) { + m_asimp = static_cast(s.get_plugin(m.get_family_id("arith"))); + m_bvsimp = static_cast(s.get_plugin(m.get_family_id("bv"))); + } + + ~auf_solver() { + flush_nodes(); + } + + void set_context(context * ctx) { + SASSERT(m_context==0); + m_context = ctx; + } + + ast_manager & get_manager() const { return m_manager; } + + arith_simplifier_plugin * get_arith_simp() const { return m_asimp; } + + bv_simplifier_plugin * get_bv_simp() const { return m_bvsimp; } + + void reset() { + flush_nodes(); + m_nodes.reset(); + m_next_node_id = 0; + m_uvars.reset(); + m_A_f_is.reset(); + m_root_nodes.reset(); + reset_sort2k(); + } + + void set_model(proto_model * m) { + reset_eval_cache(); + m_model = m; + } + + proto_model * get_model() const { + SASSERT(m_model); + return m_model; + } + + node * get_uvar(quantifier * q, unsigned i) { + SASSERT(i < q->get_num_decls()); + sort * s = q->get_decl_sort(q->get_num_decls() - i - 1); + return mk_node(m_uvars, q, i, s); + } + + node * get_A_f_i(func_decl * f, unsigned i) { + SASSERT(i < f->get_arity()); + sort * s = f->get_domain(i); + return mk_node(m_A_f_is, f, i, s); + } + + instantiation_set const * get_uvar_inst_set(quantifier * q, unsigned i) const { + SASSERT(!has_quantifiers(q->get_expr())); + ast_idx_pair k(q, i); + node * r = 0; + if (m_uvars.find(k, r)) + return r->get_instantiation_set(); + return 0; + } + + void mk_instatiation_sets() { + ptr_vector::const_iterator it = m_nodes.begin(); + ptr_vector::const_iterator end = m_nodes.end(); + for (; it != end; ++it) { + node * curr = *it; + if (curr->is_root()) { + curr->mk_instatiation_set(m_manager); + } + } + } + + void display_nodes(std::ostream & out) const { + display_key2node(out, m_uvars); + display_A_f_is(out); + ptr_vector::const_iterator it = m_nodes.begin(); + ptr_vector::const_iterator end = m_nodes.end(); + for (; it != end; ++it) { + (*it)->display(out, m_manager); + } + } + + virtual expr * eval(expr * n, bool model_completion) { + expr * r = 0; + if (m_eval_cache.find(n, r)) { + return r; + } + expr_ref tmp(m_manager); + if (!m_model->eval(n, tmp, model_completion)) + r = 0; + else + r = tmp; + m_eval_cache.insert(n, r); + m_eval_cache_range.push_back(r); + return r; + } + private: + + /** + \brief Collect the interpretations of n->get_exceptions() + and the interpretations of the m_else of nodes in n->get_avoid_set() + */ + void collect_exceptions_values(node * n, ptr_buffer & r) { + ptr_vector const & exceptions = n->get_exceptions(); + ptr_vector const & avoid_set = n->get_avoid_set(); + + ptr_vector::const_iterator it1 = exceptions.begin(); + ptr_vector::const_iterator end1 = exceptions.end(); + for (; it1 != end1; ++it1) { + expr * val = eval(*it1, true); + SASSERT(val != 0); + r.push_back(val); + } + + ptr_vector::const_iterator it2 = avoid_set.begin(); + ptr_vector::const_iterator end2 = avoid_set.end(); + for (; it2 != end2; ++it2) { + node * n = (*it2)->get_root(); + if (!n->is_mono_proj() && n->get_else() != 0) { + expr * val = eval(n->get_else(), true); + SASSERT(val != 0); + r.push_back(val); + } + } + } + + /** + \brief Return an expr t from the instantiation set of \c n s.t. forall e in n.get_exceptions() + eval(t) != eval(e) and forall m in n.get_avoid_set() eval(t) != eval(m.get_else()) + If there t1 and t2 satisfying this condition, break ties using the generation of them. + + Return 0 if such t does not exist. + */ + expr * pick_instance_diff_exceptions(node * n, ptr_buffer const & ex_vals) { + instantiation_set const * s = n->get_instantiation_set(); + obj_map const & elems = s->get_elems(); + + expr * t_result = 0; + unsigned gen_result = UINT_MAX; + obj_map::iterator it1 = elems.begin(); + obj_map::iterator end1 = elems.end(); + for (; it1 != end1; ++it1) { + expr * t = (*it1).m_key; + unsigned gen = (*it1).m_value; + expr * t_val = eval(t, true); + SASSERT(t_val != 0); + ptr_buffer::const_iterator it2 = ex_vals.begin(); + ptr_buffer::const_iterator end2 = ex_vals.end(); + for (; it2 != end2; ++it2) { + if (!m_manager.are_distinct(t_val, *it2)) + break; + } + if (it2 == end2 && (t_result == 0 || gen < gen_result)) { + t_result = t; + gen_result = gen; + } + } + return t_result; + } + + bool is_infinite(sort * s) const { + // we should not assume that uninterpreted sorts are infinite in benchmarks with quantifiers. + return + !m_manager.is_uninterp(s) && + s->is_infinite(); + } + + /** + \brief Return a fresh constant k that is used as a witness for elements that must be different from + a set of values. + */ + app * get_k_for(sort * s) { + SASSERT(is_infinite(s)); + app * r = 0; + if (m_sort2k.find(s, r)) + return r; + r = m_manager.mk_fresh_const("k", s); + m_sort2k.insert(s, r); + m_ks.push_back(r); + return r; + } + + /** + \brief Get the interpretation for k in m_model. + If m_model does not provide an interpretation for k, then + create a fresh one. + + Remark: this method uses get_fresh_value, so it may fail. + */ + expr * get_k_interp(app * k) { + sort * s = m_manager.get_sort(k); + SASSERT(is_infinite(s)); + func_decl * k_decl = k->get_decl(); + expr * r = m_model->get_const_interp(k_decl); + if (r != 0) + return r; + r = m_model->get_fresh_value(s); + if (r == 0) + return 0; + m_model->register_decl(k_decl, r); + SASSERT(m_model->get_const_interp(k_decl) == r); + return r; + } + + /** + \brief Assert k to be different from the set of exceptions. + + It invokes get_k_interp that may fail. + */ + bool assert_k_diseq_exceptions(app * k, ptr_vector const & exceptions) { + TRACE("assert_k_diseq_exceptions", tout << "assert_k_diseq_exceptions, " << "k: " << mk_pp(k, m_manager) << "\nexceptions:\n"; + for (unsigned i = 0; i < exceptions.size(); i++) tout << mk_pp(exceptions[i], m_manager) << "\n";); + expr * k_interp = get_k_interp(k); + if (k_interp == 0) + return false; + ptr_vector::const_iterator it = exceptions.begin(); + ptr_vector::const_iterator end = exceptions.end(); + for (; it != end; ++it) { + expr * ex = *it; + expr * ex_val = eval(ex, true); + if (!m_manager.are_distinct(k_interp, ex_val)) { + SASSERT(m_new_constraints); + // This constraint cannot be asserted into m_context during model construction. + // We must save it, and assert it during a restart. + m_new_constraints->push_back(m_manager.mk_not(m_manager.mk_eq(k, ex))); + } + } + return true; + } + + void set_projection_else(node * n) { + SASSERT(n->is_root()); + SASSERT(!n->is_mono_proj()); + instantiation_set const * s = n->get_instantiation_set(); + ptr_vector const & exceptions = n->get_exceptions(); + ptr_vector const & avoid_set = n->get_avoid_set(); + obj_map const & elems = s->get_elems(); + SASSERT(!elems.empty()); + if (!exceptions.empty() || !avoid_set.empty()) { + ptr_buffer ex_vals; + collect_exceptions_values(n, ex_vals); + expr * e = pick_instance_diff_exceptions(n, ex_vals); + if (e != 0) { + n->set_else(e); + return; + } + sort * s = n->get_sort(); + TRACE("model_finder", tout << "trying to create k for " << mk_pp(s, m_manager) << ", is_infinite: " << is_infinite(s) << "\n";); + if (is_infinite(s)) { + app * k = get_k_for(s); + if (assert_k_diseq_exceptions(k, exceptions)) { + n->insert(k, 0); // add k to the instantiation set + n->set_else(k); + return; + } + } + // TBD: add support for the else of bitvectors. + // Idea: get the term t with the minimal interpreation and use t - 1. + } + n->set_else((*(elems.begin())).m_key); + } + + /** + \brief If m_mono_proj is true and n is int or bv, then for each e in n->get_exceptions(), + we must add e-1 and e+1 to the instantiation set. + If sort->get_sort() is real, then we do nothing and hope for the best. + */ + void add_mono_exceptions(node * n) { + SASSERT(n->is_mono_proj()); + sort * s = n->get_sort(); + arith_simplifier_plugin * as = get_arith_simp(); + bv_simplifier_plugin * bs = get_bv_simp(); + bool is_int = as->is_int_sort(s); + bool is_bv = bs->is_bv_sort(s); + if (!is_int && !is_bv) + return; + poly_simplifier_plugin * ps = as; + if (is_bv) + ps = bs; + ps->set_curr_sort(s); + expr_ref one(m_manager); + one = ps->mk_one(); + ptr_vector const & exceptions = n->get_exceptions(); + ptr_vector::const_iterator it = exceptions.begin(); + ptr_vector::const_iterator end = exceptions.end(); + for (; it != end; ++it) { + expr * e = *it; + expr_ref e_plus_1(m_manager); + expr_ref e_minus_1(m_manager); + TRACE("mf_simp_bug", tout << "e:\n" << mk_ismt2_pp(e, m_manager) << "\none:\n" << mk_ismt2_pp(one, m_manager) << "\n";); + ps->mk_add(e, one, e_plus_1); + ps->mk_sub(e, one, e_minus_1); + // Note: exceptions come from quantifiers bodies. So, they have generation 0. + n->insert(e_plus_1, 0); + n->insert(e_minus_1, 0); + } + } + + void get_instantiation_set_values(node * n, ptr_buffer & values) { + instantiation_set const * s = n->get_instantiation_set(); + obj_hashtable already_found; + obj_map const & elems = s->get_elems(); + obj_map::iterator it = elems.begin(); + obj_map::iterator end = elems.end(); + for (; it != end; ++it) { + expr * t = (*it).m_key; + expr * t_val = eval(t, true); + if (!already_found.contains(t_val)) { + values.push_back(t_val); + already_found.insert(t_val); + } + } + TRACE("model_finder_bug", tout << "values for the instantiation_set of @" << n->get_id() << "\n"; + ptr_buffer::const_iterator it = values.begin(); + ptr_buffer::const_iterator end = values.end(); + for (; it != end; ++it) { + expr * v = *it; + tout << mk_pp(v, m_manager) << "\n"; + }); + } + + struct numeral_lt { + poly_simplifier_plugin * m_p; + numeral_lt(poly_simplifier_plugin * p):m_p(p) {} + bool operator()(expr * e1, expr * e2) { + rational v1, v2; + if (m_p->is_numeral(e1, v1) && m_p->is_numeral(e2, v2)) { + return v1 < v2; + } + else { + return e1->get_id() < e2->get_id(); + } + } + }; + + struct signed_bv_lt { + bv_simplifier_plugin * m_bs; + unsigned m_bv_size; + signed_bv_lt(bv_simplifier_plugin * bs, unsigned sz):m_bs(bs), m_bv_size(sz) {} + bool operator()(expr * e1, expr * e2) { + rational v1, v2; + if (m_bs->is_numeral(e1, v1) && m_bs->is_numeral(e2, v2)) { + v1 = m_bs->norm(v1, m_bv_size, true); + v2 = m_bs->norm(v2, m_bv_size, true); + return v1 < v2; + } + else { + return e1->get_id() < e2->get_id(); + } + } + }; + + void sort_values(node * n, ptr_buffer & values) { + sort * s = n->get_sort(); + if (get_arith_simp()->is_arith_sort(s)) { + std::sort(values.begin(), values.end(), numeral_lt(get_arith_simp())); + } + else if (!n->is_signed_proj()) { + std::sort(values.begin(), values.end(), numeral_lt(get_bv_simp())); + } + else { + bv_simplifier_plugin * bs = get_bv_simp(); + std::sort(values.begin(), values.end(), signed_bv_lt(bs, bs->get_bv_size(s))); + } + } + + void mk_mono_proj(node * n) { + add_mono_exceptions(n); + ptr_buffer values; + get_instantiation_set_values(n, values); + sort_values(n, values); + sort * s = n->get_sort(); + arith_simplifier_plugin * as = get_arith_simp(); + bv_simplifier_plugin * bs = get_bv_simp(); + bool is_arith = as->is_arith_sort(s); + bool is_signed = n->is_signed_proj(); + unsigned sz = values.size(); + SASSERT(sz > 0); + func_decl * p = m_manager.mk_fresh_func_decl(1, &s, s); + expr * pi = values[sz - 1]; + expr_ref var(m_manager); + var = m_manager.mk_var(0, s); + for (unsigned i = sz - 1; i >= 1; i--) { + expr_ref c(m_manager); + if (is_arith) + as->mk_lt(var, values[i], c); + else if (!is_signed) + bs->mk_ult(var, values[i], c); + else + bs->mk_slt(var, values[i], c); + pi = m_manager.mk_ite(c, values[i-1], pi); + } + func_interp * rpi = alloc(func_interp, m_manager, 1); + rpi->set_else(pi); + m_model->register_decl(p, rpi, true); + n->set_proj(p); + } + + void mk_simple_proj(node * n) { + set_projection_else(n); + ptr_buffer values; + get_instantiation_set_values(n, values); + sort * s = n->get_sort(); + expr * else_val = eval(n->get_else(), true); + func_decl * p = m_manager.mk_fresh_func_decl(1, &s, s); + func_interp * pi = alloc(func_interp, m_manager, 1); + pi->set_else(else_val); + m_model->register_decl(p, pi, true); + ptr_buffer::const_iterator it = values.begin(); + ptr_buffer::const_iterator end = values.end(); + for (; it != end; ++it) { + expr * v = *it; + pi->insert_new_entry(&v, v); + } + n->set_proj(p); + } + + void mk_projections() { + ptr_vector::const_iterator it = m_root_nodes.begin(); + ptr_vector::const_iterator end = m_root_nodes.end(); + for (; it != end; ++it) { + node * n = *it; + SASSERT(n->is_root()); + if (n->is_mono_proj()) + mk_mono_proj(n); + else + mk_simple_proj(n); + } + } + + /** + \brief Store in r the partial functions that have A_f_i nodes. + */ + void collect_partial_funcs(func_decl_set & r) { + key2node::iterator it = m_A_f_is.begin(); + key2node::iterator end = m_A_f_is.end(); + for (; it != end; ++it) { + func_decl * f = to_func_decl((*it).m_key.first); + if (!r.contains(f)) { + func_interp * fi = m_model->get_func_interp(f); + if (fi == 0) { + fi = alloc(func_interp, m_manager, f->get_arity()); + m_model->register_decl(f, fi); + SASSERT(fi->is_partial()); + } + if (fi->is_partial()) { + r.insert(f); + } + } + } + } + + /** + \brief Make sorts associated with nodes that must avoid themselves finite. + Only uninterpreted sorts are considered. + This is a trick to be able to handle atoms of the form X = Y + where X and Y are variables. See paper "Complete Quantifier Instantiation" + for more details. + */ + void mk_sorts_finite() { + ptr_vector::const_iterator it = m_root_nodes.begin(); + ptr_vector::const_iterator end = m_root_nodes.end(); + for (; it != end; ++it) { + node * n = *it; + SASSERT(n->is_root()); + sort * s = n->get_sort(); + if (m_manager.is_uninterp(s) && + // Making all uninterpreted sorts finite. + // n->must_avoid_itself() && + !m_model->is_finite(s)) { + m_model->freeze_universe(s); + } + } + } + + void add_elem_to_empty_inst_sets() { + ptr_vector::const_iterator it = m_root_nodes.begin(); + ptr_vector::const_iterator end = m_root_nodes.end(); + for (; it != end; ++it) { + node * n = *it; + SASSERT(n->is_root()); + instantiation_set const * s = n->get_instantiation_set(); + obj_map const & elems = s->get_elems(); + if (elems.empty()) { + // The method get_some_value cannot be used if n->get_sort() is an uninterpreted sort or is a sort built using uninterpreted sorts + // (e.g., (Array S S) where S is uninterpreted). The problem is that these sorts do not have a fixed interpretation. + // Moreover, a model assigns an arbitrary intepretation to these sorts using "model_values" a model value. + // If these module values "leak" inside the logical context, they may affect satisfiability. + // + // n->insert(m_model->get_some_value(n->get_sort()), 0); + // TODO: we can use get_some_value if the sort n->get_sort() does not depend on any uninterpreted sorts. + n->insert(m_manager.mk_fresh_const("elem", n->get_sort()), 0); + } + } + } + + /** + \brief Store in m_root_nodes the roots from m_nodes. + */ + void collect_root_nodes() { + m_root_nodes.reset(); + ptr_vector::const_iterator it = m_nodes.begin(); + ptr_vector::const_iterator end = m_nodes.end(); + for (; it != end; ++it) { + node * n = *it; + if (n->is_root()) + m_root_nodes.push_back(n); + } + } + + /** + \brief Return the projection function for f at argument i. + Return 0, if there is none. + + \remark This method assumes that mk_projections was already + invoked. + + \remark f may have a non partial interpretation on m_model. + This may happen because the evaluator uses model_completion. + In the beginning of fix_model() we collected all f with + partial interpretations. During the process of computing the + projections we used the evalutator with model_completion, + and it may have fixed the "else" case of some partial interpretations. + This is ok, because in the "limit" the "else" of the interpretation + is irrelevant after the projections are applied. + */ + func_decl * get_f_i_proj(func_decl * f, unsigned i) { + node * r = 0; + ast_idx_pair k(f, i); + if (!m_A_f_is.find(k, r)) + return 0; + return r->get_proj(); + } + + /** + \brief Complete the interpretation of the functions that were + collected in the beginning of fix_model(). + */ + void complete_partial_funcs(func_decl_set const & partial_funcs) { + func_decl_set::iterator it = partial_funcs.begin(); + func_decl_set::iterator end = partial_funcs.end(); + for (; it != end; ++it) { + func_decl * f = *it; + // Complete the current interpretation + m_model->complete_partial_func(f); + + unsigned arity = f->get_arity(); + func_interp * fi = m_model->get_func_interp(f); + if (fi->is_constant()) + continue; // there is no point in using the projection for fi, since fi is the constant function. + + expr_ref_vector args(m_manager); + bool has_proj = false; + for (unsigned i = 0; i < arity; i++) { + var * v = m_manager.mk_var(i, f->get_domain(i)); + func_decl * pi = get_f_i_proj(f, i); + if (pi != 0) { + args.push_back(m_manager.mk_app(pi, v)); + has_proj = true; + } + else { + args.push_back(v); + } + } + if (has_proj) { + // f_aux will be assigned to the old interpretation of f. + func_decl * f_aux = m_manager.mk_fresh_func_decl(f->get_name(), symbol::null, arity, f->get_domain(), f->get_range()); + func_interp * new_fi = alloc(func_interp, m_manager, arity); + new_fi->set_else(m_manager.mk_app(f_aux, args.size(), args.c_ptr())); + TRACE("model_finder", tout << "Setting new interpretation for " << f->get_name() << "\n" << + mk_pp(new_fi->get_else(), m_manager) << "\n";); + m_model->reregister_decl(f, new_fi, f_aux); + } + } + } + + void mk_inverse(node * n) { + SASSERT(n->is_root()); + instantiation_set * s = n->get_instantiation_set(); + s->mk_inverse(*this); + } + + void mk_inverses() { + ptr_vector::const_iterator it = m_root_nodes.begin(); + ptr_vector::const_iterator end = m_root_nodes.end(); + for (; it != end; ++it) { + node * n = *it; + SASSERT(n->is_root()); + mk_inverse(n); + } + } + + public: + void fix_model(expr_ref_vector & new_constraints) { + m_new_constraints = &new_constraints; + func_decl_set partial_funcs; + collect_partial_funcs(partial_funcs); + reset_eval_cache(); // will start using model_completion + collect_root_nodes(); + add_elem_to_empty_inst_sets(); + mk_sorts_finite(); + mk_projections(); + mk_inverses(); + complete_partial_funcs(partial_funcs); + TRACE("model_finder", tout << "after auf_solver fixing the model\n"; + display_nodes(tout); + tout << "NEW MODEL:\n"; + model_pp(tout, *m_model);); + } + }; + + + // ----------------------------------- + // + // qinfo class & subclasses + // + // ----------------------------------- + + /* + During quantifier internalizations time, we collect bits of + information about the quantifier structure. These bits are + instances of subclasses of qinfo. + */ + + /** + \brief Generic bit of information collected when analyzing quantifiers. + The subclasses are defined in the .cpp file. + */ + class qinfo { + public: + virtual ~qinfo() {} + 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() << "]"; } + + // AUF fragment solver + virtual void process_auf(quantifier * q, auf_solver & s, context * ctx) = 0; + virtual void populate_inst_sets(quantifier * q, auf_solver & s, context * ctx) = 0; + // second pass... actually we may need to reach a fixpoint, but if it cannot be found + // in two passes, the fixpoint is not finite. + virtual void populate_inst_sets2(quantifier * q, auf_solver & s, context * ctx) {} + + // Macro/Hint support + virtual void populate_inst_sets(quantifier * q, func_decl * mhead, ptr_vector & uvar_inst_sets, context * ctx) {} + }; + + class f_var : public qinfo { + protected: + func_decl * m_f; + unsigned m_arg_i; + unsigned m_var_j; + public: + f_var(func_decl * f, unsigned i, unsigned j):m_f(f), m_arg_i(i), m_var_j(j) {} + virtual ~f_var() {} + + virtual char const * get_kind() const { + return "f_var"; + } + + virtual bool is_equal(qinfo const * qi) const { + if (qi->get_kind() != get_kind()) + return false; + f_var const * other = static_cast(qi); + return m_f == other->m_f && m_arg_i == other->m_arg_i && m_var_j == other->m_var_j; + } + + virtual void display(std::ostream & out) const { + out << "(" << m_f->get_name() << ":" << m_arg_i << " -> v!" << m_var_j << ")"; + } + + virtual void process_auf(quantifier * q, auf_solver & s, context * ctx) { + node * n1 = s.get_A_f_i(m_f, m_arg_i); + node * n2 = s.get_uvar(q, m_var_j); + CTRACE("model_finder", n1->get_sort() != n2->get_sort(), + ast_manager & m = ctx->get_manager(); + tout << "sort bug:\n" << mk_ismt2_pp(q->get_expr(), m) << "\n" << mk_ismt2_pp(q, m) << "\n"; + tout << "decl(0): " << q->get_decl_name(0) << "\n"; + tout << "f: " << m_f->get_name() << " i: " << m_arg_i << "\n"; + tout << "v: " << m_var_j << "\n"; + n1->get_root()->display(tout, m); + n2->get_root()->display(tout, m); + tout << "f signature: "; + for (unsigned i = 0; i < m_f->get_arity(); i++) tout << mk_pp(m_f->get_domain(i), m) << " "; + tout << "-> " << mk_pp(m_f->get_range(), m) << "\n"; + ); + + n1->merge(n2); + } + + virtual void populate_inst_sets(quantifier * q, auf_solver & s, context * ctx) { + node * A_f_i = s.get_A_f_i(m_f, m_arg_i); + enode_vector::const_iterator it = ctx->begin_enodes_of(m_f); + enode_vector::const_iterator end = ctx->end_enodes_of(m_f); + for (; it != end; it++) { + enode * n = *it; + if (ctx->is_relevant(n)) { + // Remark: it is incorrect to use + // n->get_arg(m_arg_i)->get_root() + // instead of + // n->get_arg(m_arg_i) + // + // Due to model based quantifier instantiation, some equivalence + // classes are merged by accident. + // So, using n->get_arg(m_arg_i)->get_root(), we may miss + // a necessary instantiation. + enode * e_arg = n->get_arg(m_arg_i); + expr * arg = e_arg->get_owner(); + A_f_i->insert(arg, e_arg->get_generation()); + } + } + } + + virtual void populate_inst_sets(quantifier * q, func_decl * mhead, ptr_vector & uvar_inst_sets, context * ctx) { + if (m_f != mhead) + return; + uvar_inst_sets.reserve(m_var_j+1, 0); + if (uvar_inst_sets[m_var_j] == 0) + uvar_inst_sets[m_var_j] = alloc(instantiation_set, ctx->get_manager()); + instantiation_set * s = uvar_inst_sets[m_var_j]; + SASSERT(s != 0); + + enode_vector::const_iterator it = ctx->begin_enodes_of(m_f); + enode_vector::const_iterator end = ctx->end_enodes_of(m_f); + for (; it != end; it++) { + enode * n = *it; + if (ctx->is_relevant(n)) { + enode * e_arg = n->get_arg(m_arg_i); + expr * arg = e_arg->get_owner(); + s->insert(arg, e_arg->get_generation()); + } + } + } + }; + + class f_var_plus_offset : public f_var { + expr_ref m_offset; + public: + f_var_plus_offset(ast_manager & m, func_decl * f, unsigned i, unsigned j, expr * offset): + f_var(f, i, j), + m_offset(offset, m) { + } + virtual ~f_var_plus_offset() {} + + virtual char const * get_kind() const { + return "f_var_plus_offset"; + } + + virtual bool is_equal(qinfo const * qi) const { + if (qi->get_kind() != get_kind()) + return false; + f_var_plus_offset const * other = static_cast(qi); + return m_f == other->m_f && m_arg_i == other->m_arg_i && m_var_j == other->m_var_j && m_offset.get() == other->m_offset.get(); + } + + virtual void display(std::ostream & out) const { + out << "(" << m_f->get_name() << ":" << m_arg_i << " - " << + mk_bounded_pp(m_offset.get(), m_offset.get_manager()) << " -> v!" << m_var_j << ")"; + } + + virtual void process_auf(quantifier * q, auf_solver & s, context * ctx) { + // just create the nodes + /* node * A_f_i = */ s.get_A_f_i(m_f, m_arg_i); + /* node * S_j = */ s.get_uvar(q, m_var_j); + } + + virtual void populate_inst_sets(quantifier * q, auf_solver & s, context * ctx) { + // S_j is not necessary equal to A_f_i. + node * A_f_i = s.get_A_f_i(m_f, m_arg_i)->get_root(); + node * S_j = s.get_uvar(q, m_var_j)->get_root(); + if (A_f_i == S_j) { + // there is no finite fixpoint... we just copy the i-th arguments of A_f_i - m_offset + // hope for the best... + arith_simplifier_plugin * as = s.get_arith_simp(); + bv_simplifier_plugin * bs = s.get_bv_simp(); + node * S_j = s.get_uvar(q, m_var_j); + enode_vector::const_iterator it = ctx->begin_enodes_of(m_f); + enode_vector::const_iterator end = ctx->end_enodes_of(m_f); + for (; it != end; it++) { + enode * n = *it; + if (ctx->is_relevant(n)) { + enode * e_arg = n->get_arg(m_arg_i); + expr * arg = e_arg->get_owner(); + expr_ref arg_minus_k(ctx->get_manager()); + if (bs->is_bv(arg)) + bs->mk_sub(arg, m_offset, arg_minus_k); + else + as->mk_sub(arg, m_offset, arg_minus_k); + S_j->insert(arg_minus_k, e_arg->get_generation()); + } + } + } + else { + // A_f_i != S_j, there is hope for a finite fixpoint. + // So, we just populate A_f_i + f_var::populate_inst_sets(q, s, ctx); + // I must also propagate the monotonicity flag since A_f_i and S_j are not in the + // same equivalence class. + if (A_f_i->is_mono_proj()) + S_j->set_mono_proj(); + if (S_j->is_mono_proj()) + A_f_i->set_mono_proj(); + } + } + + template + void copy_instances(node * from, node * to, auf_solver & s) { + arith_simplifier_plugin * as = s.get_arith_simp(); + bv_simplifier_plugin * bs = s.get_bv_simp(); + poly_simplifier_plugin * ps = as; + if (bs->is_bv_sort(from->get_sort())) + ps = bs; + instantiation_set const * from_s = from->get_instantiation_set(); + obj_map const & elems_s = from_s->get_elems(); + obj_map::iterator it = elems_s.begin(); + obj_map::iterator end = elems_s.end(); + for (; it != end; ++it) { + expr * n = (*it).m_key; + expr_ref n_k(m_offset.get_manager()); + if (PLUS) + ps->mk_add(n, m_offset, n_k); + else + ps->mk_sub(n, m_offset, n_k); + to->insert(n_k, (*it).m_value); + } + } + + virtual void populate_inst_sets2(quantifier * q, auf_solver & s, context * ctx) { + node * A_f_i = s.get_A_f_i(m_f, m_arg_i)->get_root(); + node * S_j = s.get_uvar(q, m_var_j)->get_root(); + // If A_f_i == S_j, then there is no finite fixpoint, so we do nothing here. + if (A_f_i != S_j) { + // enforce + // A_f_i - k \subset S_j + // S_j + k \subset A_f_i + copy_instances(A_f_i, S_j, s); + copy_instances(S_j, A_f_i, s); + } + } + + virtual void populate_inst_sets(quantifier * q, func_decl * mhead, ptr_vector & uvar_inst_sets, context * ctx) { + // ignored when in macro + } + + }; + + + /** + \brief auf_arr is a term (pattern) of the form: + + FORM := GROUND-TERM + | (select FORM VAR) + + Store in arrays, all enodes that match the pattern + */ + void get_auf_arrays(app * auf_arr, context * ctx, ptr_buffer & arrays) { + if (is_ground(auf_arr)) { + if (ctx->e_internalized(auf_arr)) { + enode * e = ctx->get_enode(auf_arr); + if (ctx->is_relevant(e)) { + arrays.push_back(e); + } + } + } + else { + app * nested_array = to_app(auf_arr->get_arg(0)); + ptr_buffer nested_arrays; + get_auf_arrays(nested_array, ctx, nested_arrays); + ptr_buffer::const_iterator it = nested_arrays.begin(); + ptr_buffer::const_iterator end = nested_arrays.end(); + for (; it != end; ++it) { + enode * curr = *it; + enode_vector::iterator it2 = curr->begin_parents(); + enode_vector::iterator end2 = curr->end_parents(); + for (; it2 != end2; ++it2) { + enode * p = *it2; + if (ctx->is_relevant(p) && p->get_owner()->get_decl() == auf_arr->get_decl()) { + arrays.push_back(p); + } + } + } + } + } + + class select_var : public qinfo { + protected: + ast_manager & m_manager; + app * m_select; // It must satisfy is_auf_select... see bool is_auf_select(expr * t) const + unsigned m_arg_i; + unsigned m_var_j; + + app * get_array() const { return to_app(m_select->get_arg(0)); } + + func_decl * get_array_func_decl(app * ground_array, auf_solver & s) { + expr * ground_array_interp = s.eval(ground_array, false); + if (ground_array_interp != 0 && s.get_model()->is_array_value(ground_array_interp)) + return to_func_decl(to_app(ground_array_interp)->get_decl()->get_parameter(0).get_ast()); + return 0; + } + + public: + select_var(ast_manager & m, app * s, unsigned i, unsigned j):m_manager(m), m_select(s), m_arg_i(i), m_var_j(j) {} + virtual ~select_var() {} + + virtual char const * get_kind() const { + return "select_var"; + } + + virtual bool is_equal(qinfo const * qi) const { + if (qi->get_kind() != get_kind()) + return false; + select_var const * other = static_cast(qi); + return m_select == other->m_select && m_arg_i == other->m_arg_i && m_var_j == other->m_var_j; + } + + virtual void display(std::ostream & out) const { + out << "(" << mk_bounded_pp(m_select, m_manager) << ":" << m_arg_i << " -> v!" << m_var_j << ")"; + } + + virtual void process_auf(quantifier * q, auf_solver & s, context * ctx) { + ptr_buffer arrays; + get_auf_arrays(get_array(), ctx, arrays); + TRACE("select_var", + tout << "enodes matching: "; display(tout); tout << "\n"; + ptr_buffer::const_iterator it = arrays.begin(); + ptr_buffer::const_iterator end = arrays.end(); + for (; it != end; ++it) { + tout << "#" << (*it)->get_owner()->get_id() << "\n" << mk_pp((*it)->get_owner(), m_manager) << "\n"; + }); + node * n1 = s.get_uvar(q, m_var_j); + ptr_buffer::const_iterator it = arrays.begin(); + ptr_buffer::const_iterator end = arrays.end(); + for (; it != end; ++it) { + app * ground_array = (*it)->get_owner(); + func_decl * f = get_array_func_decl(ground_array, s); + if (f) { + SASSERT(m_arg_i >= 1); + node * n2 = s.get_A_f_i(f, m_arg_i - 1); + n1->merge(n2); + } + } + } + + virtual void populate_inst_sets(quantifier * q, auf_solver & s, context * ctx) { + ptr_buffer arrays; + get_auf_arrays(get_array(), ctx, arrays); + ptr_buffer::const_iterator it = arrays.begin(); + ptr_buffer::const_iterator end = arrays.end(); + for (; it != end; ++it) { + enode * curr = (*it); + app * ground_array = curr->get_owner(); + func_decl * f = get_array_func_decl(ground_array, s); + if (f) { + node * A_f_i = s.get_A_f_i(f, m_arg_i - 1); + enode_vector::iterator it2 = curr->begin_parents(); + enode_vector::iterator end2 = curr->end_parents(); + for (; it2 != end2; ++it2) { + enode * p = *it2; + if (ctx->is_relevant(p) && p->get_owner()->get_decl() == m_select->get_decl()) { + SASSERT(m_arg_i < p->get_owner()->get_num_args()); + enode * e_arg = p->get_arg(m_arg_i); + A_f_i->insert(e_arg->get_owner(), e_arg->get_generation()); + } + } + } + } + } + }; + + class var_pair : public qinfo { + protected: + unsigned m_var_i; + unsigned m_var_j; + public: + var_pair(unsigned i, unsigned j):m_var_i(i), m_var_j(j) { + if (m_var_i > m_var_j) + std::swap(m_var_i, m_var_j); + } + + virtual ~var_pair() {} + + virtual bool is_equal(qinfo const * qi) const { + if (qi->get_kind() != get_kind()) + return false; + var_pair const * other = static_cast(qi); + return m_var_i == other->m_var_i && m_var_j == other->m_var_j; + } + + virtual void display(std::ostream & out) const { + out << "(" << get_kind() << ":v!" << m_var_i << ":v!" << m_var_j << ")"; + } + + virtual void populate_inst_sets(quantifier * q, auf_solver & s, context * ctx) { + // do nothing + } + }; + + class x_eq_y : public var_pair { + public: + x_eq_y(unsigned i, unsigned j):var_pair(i, j) {} + virtual char const * get_kind() const { return "x_eq_y"; } + + virtual void process_auf(quantifier * q, auf_solver & s, context * ctx) { + node * n1 = s.get_uvar(q, m_var_i); + node * n2 = s.get_uvar(q, m_var_j); + n1->insert_avoid(n2); + if (n1 != n2) + n2->insert_avoid(n1); + } + }; + + class x_neq_y : public var_pair { + public: + x_neq_y(unsigned i, unsigned j):var_pair(i, j) {} + virtual char const * get_kind() const { return "x_neq_y"; } + + virtual void process_auf(quantifier * q, auf_solver & s, context * ctx) { + node * n1 = s.get_uvar(q, m_var_i); + node * n2 = s.get_uvar(q, m_var_j); + n1->merge(n2); + } + }; + + class x_leq_y : public var_pair { + public: + x_leq_y(unsigned i, unsigned j):var_pair(i, j) {} + virtual char const * get_kind() const { return "x_leq_y"; } + + virtual void process_auf(quantifier * q, auf_solver & s, context * ctx) { + node * n1 = s.get_uvar(q, m_var_i); + node * n2 = s.get_uvar(q, m_var_j); + n1->merge(n2); + n1->set_mono_proj(); + } + }; + + // signed bit-vector comparison + class x_sleq_y : public x_leq_y { + public: + x_sleq_y(unsigned i, unsigned j):x_leq_y(i, j) {} + virtual char const * get_kind() const { return "x_sleq_y"; } + + virtual void process_auf(quantifier * q, auf_solver & s, context * ctx) { + node * n1 = s.get_uvar(q, m_var_i); + node * n2 = s.get_uvar(q, m_var_j); + n1->merge(n2); + n1->set_mono_proj(); + n1->set_signed_proj(); + } + }; + + class var_expr_pair : public qinfo { + protected: + unsigned m_var_i; + expr_ref m_t; + public: + var_expr_pair(ast_manager & m, unsigned i, expr * t): + m_var_i(i), m_t(t, m) {} + ~var_expr_pair() {} + + virtual bool is_equal(qinfo const * qi) const { + if (qi->get_kind() != get_kind()) + return false; + var_expr_pair const * other = static_cast(qi); + return m_var_i == other->m_var_i && m_t.get() == other->m_t.get(); + } + + virtual void display(std::ostream & out) const { + out << "(" << get_kind() << ":v!" << m_var_i << ":" << mk_bounded_pp(m_t.get(), m_t.get_manager()) << ")"; + } + }; + + class x_eq_t : public var_expr_pair { + public: + x_eq_t(ast_manager & m, unsigned i, expr * t): + var_expr_pair(m, i, t) {} + virtual char const * get_kind() const { return "x_eq_t"; } + + virtual void process_auf(quantifier * q, auf_solver & s, context * ctx) { + node * n1 = s.get_uvar(q, m_var_i); + n1->insert_exception(m_t); + } + + virtual void populate_inst_sets(quantifier * q, auf_solver & s, context * ctx) { + // do nothing... + } + }; + + class x_neq_t : public var_expr_pair { + public: + x_neq_t(ast_manager & m, unsigned i, expr * t): + var_expr_pair(m, i, t) {} + virtual char const * get_kind() const { return "x_neq_t"; } + + virtual void process_auf(quantifier * q, auf_solver & s, context * ctx) { + // make sure that S_q_i is create. + s.get_uvar(q, m_var_i); + } + + virtual void populate_inst_sets(quantifier * q, auf_solver & s, context * ctx) { + node * S_q_i = s.get_uvar(q, m_var_i); + S_q_i->insert(m_t, 0); + } + }; + + class x_gle_t : public var_expr_pair { + public: + x_gle_t(ast_manager & m, unsigned i, expr * t): + var_expr_pair(m, i, t) {} + virtual char const * get_kind() const { return "x_gle_t"; } + + virtual void process_auf(quantifier * q, auf_solver & s, context * ctx) { + // make sure that S_q_i is create. + node * n1 = s.get_uvar(q, m_var_i); + n1->set_mono_proj(); + } + + virtual void populate_inst_sets(quantifier * q, auf_solver & s, context * ctx) { + node * S_q_i = s.get_uvar(q, m_var_i); + S_q_i->insert(m_t, 0); + } + }; + + class cond_macro { + protected: + ast_manager & m_manager; + func_decl * m_f; + expr * m_def; + expr * m_cond; + bool m_ineq; + bool m_satisfy_atom; + bool m_hint; + unsigned m_weight; + public: + cond_macro(ast_manager & m, func_decl * f, expr * def, expr * cond, bool ineq, bool satisfy_atom, bool hint, unsigned weight): + m_manager(m), + m_f(f), + m_def(def), + m_cond(cond), + m_ineq(ineq), + m_satisfy_atom(satisfy_atom), + m_hint(hint), + m_weight(weight) { + m_manager.inc_ref(m_def); + m_manager.inc_ref(m_cond); + SASSERT(!m_hint || m_cond == 0); + } + + ~cond_macro() { + m_manager.dec_ref(m_def); + m_manager.dec_ref(m_cond); + } + + func_decl * get_f() const { return m_f; } + + expr * get_def() const { return m_def; } + + expr * get_cond() const { return m_cond; } + + bool is_unconditional() const { return m_cond == 0 || m_manager.is_true(m_cond); } + + bool satisfy_atom() const { return m_satisfy_atom; } + + bool is_hint() const { return m_hint; } + + bool is_equal(cond_macro const * other) const { + return m_f == other->m_f && m_def == other->m_def && m_cond == other->m_cond; + } + + void display(std::ostream & out) const { + out << "[" << m_f->get_name() << " -> " << mk_bounded_pp(m_def, m_manager, 6); + if (m_hint) + out << " *hint*"; + else + out << " when " << mk_bounded_pp(m_cond, m_manager, 6); + out << "] weight: " << m_weight; + } + + unsigned get_weight() const { return m_weight; } + }; + + // ----------------------------------- + // + // quantifier_info & quantifier_analyzer + // + // ----------------------------------- + + class quantifier_analyzer; + + /** + \brief Store relevant information regarding a particular universal quantifier. + This information is populated by quantifier_analyzer. + The information is used to (try to) build a model that satisfy the universal quantifier + (when it is marked as relevant in the end of the search). + */ + class quantifier_info { + quantifier_ref m_flat_q; // flat version of the quantifier + bool m_is_auf; + bool m_has_x_eq_y; + ptr_vector m_qinfo_vect; + func_decl_set m_ng_decls; // declarations used in non-ground applications (basic_family operators are ignored here). + ptr_vector m_cond_macros; + func_decl * m_the_one; // the macro head used to satisfy the quantifier. this is useful for model checking + // when the quantifier is satisfied by a macro/hint, it may not be processed by the AUF solver. + // in this case, the quantifier_info stores the instantiation sets. + ptr_vector * m_uvar_inst_sets; + + friend class quantifier_analyzer; + + void insert_qinfo(qinfo * qi) { + // I'm assuming the number of qinfo's per quantifier is small. So, the linear search is not a big deal. + ptr_vector::iterator it = m_qinfo_vect.begin(); + ptr_vector::iterator end = m_qinfo_vect.end(); + for (; it != end; ++it) { + if (qi->is_equal(*it)) { + dealloc(qi); + return; + } + } + m_qinfo_vect.push_back(qi); + TRACE("model_finder", tout << "new quantifier qinfo: "; qi->display(tout); tout << "\n";); + } + + void insert_macro(cond_macro * m) { + m_cond_macros.push_back(m); + } + + public: + typedef ptr_vector::const_iterator macro_iterator; + + quantifier_info(ast_manager & m, quantifier * q): + m_flat_q(m), + m_is_auf(true), + m_has_x_eq_y(false), + m_the_one(0), + m_uvar_inst_sets(0) { + if (has_quantifiers(q->get_expr())) { + static bool displayed_flat_msg = false; + if (!displayed_flat_msg) { + // [Leo]: This warning message is not usefult. + // warning_msg("For problems containing quantifiers, the model finding capabilities of Z3 work better when the formula does not contain nested quantifiers. You can use PULL_NESTED_QUANTIFIERS=true to eliminate nested quantifiers."); + displayed_flat_msg = true; + } + proof_ref pr(m); + expr_ref new_q(m); + pull_quant pull(m); + pull(q, new_q, pr); + SASSERT(is_well_sorted(m, new_q)); + m_flat_q = to_quantifier(new_q); + } + else { + m_flat_q = q; + } + CTRACE("model_finder_bug", has_quantifiers(m_flat_q->get_expr()), + tout << mk_pp(q, m) << "\n" << mk_pp(m_flat_q, m) << "\n";); + SASSERT(!has_quantifiers(m_flat_q->get_expr())); + } + + ~quantifier_info() { + std::for_each(m_qinfo_vect.begin(), m_qinfo_vect.end(), delete_proc()); + std::for_each(m_cond_macros.begin(), m_cond_macros.end(), delete_proc()); + reset_the_one(); + } + + bool is_auf() const { return m_is_auf; } + + quantifier * get_flat_q() const { return m_flat_q; } + + bool unary_function_fragment() const { + unsigned sz = m_ng_decls.size(); + if (sz > 1) + return false; + if (sz == 0) + return true; + func_decl * f = *(m_ng_decls.begin()); + return f->get_arity() == 1; + } + + bool has_cond_macros() const { + return !m_cond_macros.empty(); + } + + macro_iterator begin_macros() const { + return m_cond_macros.begin(); + } + + macro_iterator end_macros() const { + return m_cond_macros.end(); + } + + void set_the_one(func_decl * m) { + m_the_one = m; + } + + func_decl * get_the_one() const { + return m_the_one; + } + + bool contains_ng_decl(func_decl * f) const { + return m_ng_decls.contains(f); + } + + void display(std::ostream & out) const { + ast_manager & m = m_flat_q.get_manager(); + out << "info for (flat) quantifier:\n" << mk_pp(m_flat_q.get(), m) << "\n"; + out << "IS_AUF: " << m_is_auf << ", has x=y: " << m_has_x_eq_y << "\n"; + out << "unary function fragment: " << unary_function_fragment() << "\n"; + out << "ng decls: "; + func_decl_set::iterator it1 = m_ng_decls.begin(); + func_decl_set::iterator end1 = m_ng_decls.end(); + for (; it1 != end1; ++it1) { + out << (*it1)->get_name() << " "; + } + out << "\ninfo bits:\n"; + ptr_vector::const_iterator it2 = m_qinfo_vect.begin(); + ptr_vector::const_iterator end2 = m_qinfo_vect.end(); + for (; it2 != end2; ++it2) { + out << " "; (*it2)->display(out); out << "\n"; + } + out << "\nmacros:\n"; + ptr_vector::const_iterator it3 = m_cond_macros.begin(); + ptr_vector::const_iterator end3 = m_cond_macros.end(); + for (; it3 != end3; ++it3) { + out << " "; (*it3)->display(out); out << "\n"; + } + } + + void process_auf(auf_solver & s, context * ctx) { + for (unsigned i = 0; i < m_flat_q->get_num_decls(); i++) { + // make sure a node exists for each variable. + s.get_uvar(m_flat_q, i); + } + ptr_vector::const_iterator it = m_qinfo_vect.begin(); + ptr_vector::const_iterator end = m_qinfo_vect.end(); + for (; it != end; ++it) { + (*it)->process_auf(m_flat_q, s, ctx); + } + } + + void populate_inst_sets(auf_solver & s, context * ctx) { + ptr_vector::const_iterator it = m_qinfo_vect.begin(); + ptr_vector::const_iterator end = m_qinfo_vect.end(); + for (; it != end; ++it) + (*it)->populate_inst_sets(m_flat_q, s, ctx); + // second pass + it = m_qinfo_vect.begin(); + for (; it != end; ++it) + (*it)->populate_inst_sets2(m_flat_q, s, ctx); + } + + func_decl_set const & get_ng_decls() const { + return m_ng_decls; + } + + void populate_macro_based_inst_sets(context * ctx, evaluator & ev) { + SASSERT(m_the_one != 0); + if (m_uvar_inst_sets != 0) + return; + m_uvar_inst_sets = alloc(ptr_vector); + ptr_vector::const_iterator it = m_qinfo_vect.begin(); + ptr_vector::const_iterator end = m_qinfo_vect.end(); + for (; it != end; ++it) + (*it)->populate_inst_sets(m_flat_q, m_the_one, *m_uvar_inst_sets, ctx); + ptr_vector::iterator it2 = m_uvar_inst_sets->begin(); + ptr_vector::iterator end2 = m_uvar_inst_sets->end(); + for (; it2 != end2; ++it2) { + instantiation_set * s = *it2; + if (s != 0) + s->mk_inverse(ev); + } + } + + instantiation_set * get_macro_based_inst_set(unsigned vidx, context * ctx, evaluator & ev) { + if (m_the_one == 0) + return 0; + populate_macro_based_inst_sets(ctx, ev); + return m_uvar_inst_sets->get(vidx, 0); + } + + void reset_the_one() { + m_the_one = 0; + if (m_uvar_inst_sets) { + std::for_each(m_uvar_inst_sets->begin(), m_uvar_inst_sets->end(), delete_proc()); + dealloc(m_uvar_inst_sets); + m_uvar_inst_sets = 0; + } + } + + }; + + /** + \brief Functor used to traverse/analyze a quantifier and + fill the structure quantifier_info. + */ + class quantifier_analyzer { + ast_manager & m_manager; + macro_util m_mutil; + array_util m_array_util; + arith_util m_arith_util; + bv_util m_bv_util; + + quantifier_info * m_info; + + typedef enum { POS, NEG } polarity; + + polarity neg(polarity p) { return p == POS ? NEG : POS; } + + obj_hashtable m_pos_cache; + obj_hashtable m_neg_cache; + typedef std::pair entry; + svector m_ftodo; + ptr_vector m_ttodo; + + void insert_qinfo(qinfo * qi) { + SASSERT(m_info); + m_info->insert_qinfo(qi); + } + + arith_simplifier_plugin * get_arith_simp() const { return m_mutil.get_arith_simp(); } + bv_simplifier_plugin * get_bv_simp() const { return m_mutil.get_bv_simp(); } + + bool is_var_plus_ground(expr * n, bool & inv, var * & v, expr_ref & t) const { + return get_arith_simp()->is_var_plus_ground(n, inv, v, t) || get_bv_simp()->is_var_plus_ground(n, inv, v, t); + } + + bool is_var_plus_ground(expr * n, var * & v, expr_ref & t) { + bool inv; + TRACE("is_var_plus_ground", tout << mk_pp(n, m_manager) << "\n"; + tout << "is_var_plus_ground: " << is_var_plus_ground(n, inv, v, t) << "\n"; + tout << "inv: " << inv << "\n";); + return is_var_plus_ground(n, inv, v, t) && !inv; + } + + bool is_add(expr * n) const { + return m_mutil.is_add(n); + } + + bool is_zero(expr * n) const { + if (get_bv_simp()->is_bv(n)) + return get_bv_simp()->is_zero_safe(n); + else + return get_arith_simp()->is_zero_safe(n); + } + + bool is_times_minus_one(expr * n, expr * & arg) const { + return m_mutil.is_times_minus_one(n, arg); + } + + bool is_le(expr * n) const { + return m_mutil.is_le(n); + } + + bool is_le_ge(expr * n) const { + return m_mutil.is_le_ge(n); + } + + bool is_signed_le(expr * n) const { + return m_bv_util.is_bv_sle(n); + } + + expr * mk_one(sort * s) { + return m_bv_util.is_bv_sort(s) ? m_bv_util.mk_numeral(rational(1), s) : m_arith_util.mk_numeral(rational(1), s); + } + + void mk_sub(expr * t1, expr * t2, expr_ref & r) const { + m_mutil.mk_sub(t1, t2, r); + } + + void mk_add(expr * t1, expr * t2, expr_ref & r) const { + m_mutil.mk_add(t1, t2, r); + } + + bool is_var_and_ground(expr * lhs, expr * rhs, var * & v, expr_ref & t) const { + TRACE("is_var_and_ground", tout << "is_var_and_ground: " << mk_ismt2_pp(lhs, m_manager) << " " << mk_ismt2_pp(rhs, m_manager) << "\n";); + if (is_var(lhs) && is_ground(rhs)) { + v = to_var(lhs); + t = rhs; + TRACE("is_var_and_ground", tout << "var and ground\n";); + return true; + } + else if (is_var(rhs) && is_ground(lhs)) { + v = to_var(rhs); + t = lhs; + TRACE("is_var_and_ground", tout << "ground and var\n";); + return true; + } + else { + bool inv = false; // true if invert the sign + expr_ref tmp(m_manager); + if (is_var_plus_ground(lhs, inv, v, tmp) && is_ground(rhs)) { + if (inv) + mk_sub(tmp, rhs, t); + else + mk_sub(rhs, tmp, t); + return true; + } + if (is_var_plus_ground(rhs, inv, v, tmp) && is_ground(lhs)) { + if (inv) + mk_sub(tmp, lhs, t); + else + mk_sub(lhs, tmp, t); + return true; + } + } + return false; + } + + bool is_x_eq_t_atom(expr * n, var * & v, expr_ref & t) const { + if (!is_app(n)) + return false; + if (m_manager.is_eq(n)) + return is_var_and_ground(to_app(n)->get_arg(0), to_app(n)->get_arg(1), v, t); + return false; + } + + bool is_var_minus_var(expr * n, var * & v1, var * & v2) const { + if (!is_add(n)) + return false; + expr * arg1 = to_app(n)->get_arg(0); + expr * arg2 = to_app(n)->get_arg(1); + if (!is_var(arg1)) + std::swap(arg1, arg2); + if (!is_var(arg1)) + return false; + expr * arg2_2; + if (!is_times_minus_one(arg2, arg2_2)) + return false; + if (!is_var(arg2_2)) + return false; + v1 = to_var(arg1); + v2 = to_var(arg2_2); + return true; + } + + bool is_var_and_var(expr * lhs, expr * rhs, var * & v1, var * & v2) const { + if (is_var(lhs) && is_var(rhs)) { + v1 = to_var(lhs); + v2 = to_var(rhs); + return true; + } + return + (is_var_minus_var(lhs, v1, v2) && is_zero(rhs)) || + (is_var_minus_var(rhs, v1, v2) && is_zero(lhs)); + } + + bool is_x_eq_y_atom(expr * n, var * & v1, var * & v2) const { + return m_manager.is_eq(n) && is_var_and_var(to_app(n)->get_arg(0), to_app(n)->get_arg(1), v1, v2); + } + + bool is_x_gle_y_atom(expr * n, var * & v1, var * & v2) const { + return is_le_ge(n) && is_var_and_var(to_app(n)->get_arg(0), to_app(n)->get_arg(1), v1, v2); + } + + bool is_x_gle_t_atom(expr * atom, bool sign, var * & v, expr_ref & t) { + if (!is_app(atom)) + return false; + if (sign) { + bool r = is_le_ge(atom) && is_var_and_ground(to_app(atom)->get_arg(0), to_app(atom)->get_arg(1), v, t); + CTRACE("is_x_gle_t", r, tout << "is_x_gle_t: " << mk_ismt2_pp(atom, m_manager) << "\n--->\n" + << mk_ismt2_pp(v, m_manager) << " " << mk_ismt2_pp(t, m_manager) << "\n";); + return r; + } + else { + if (is_le_ge(atom)) { + expr_ref tmp(m_manager); + if (is_var_and_ground(to_app(atom)->get_arg(0), to_app(atom)->get_arg(1), v, tmp)) { + sort * s = m_manager.get_sort(tmp); + expr_ref one(m_manager); + one = mk_one(s); + if (is_le(atom)) + mk_add(tmp, one, t); + else + mk_sub(tmp, one, t); + TRACE("is_x_gle_t", tout << "is_x_gle_t: " << mk_ismt2_pp(atom, m_manager) << "\n--->\n" + << mk_ismt2_pp(v, m_manager) << " " << mk_ismt2_pp(t, m_manager) << "\n";); + return true; + } + } + return false; + } + } + + void reset_cache() { + m_pos_cache.reset(); + m_neg_cache.reset(); + } + + obj_hashtable & get_cache(polarity pol) { + return pol == POS ? m_pos_cache : m_neg_cache; + } + + void visit_formula(expr * n, polarity pol) { + if (is_ground(n)) + return; // ground terms do not need to be visited. + obj_hashtable & c = get_cache(pol); + if (!c.contains(n)) { + m_ftodo.push_back(entry(n, pol)); + c.insert(n); + } + } + + void visit_term(expr * n) { + // ground terms do not need to be visited. + if (!is_ground(n) && !m_pos_cache.contains(n)) { + m_ttodo.push_back(n); + m_pos_cache.insert(n); + } + } + + /** + \brief Process unintrepreted applications. + */ + void process_u_app(app * t) { + unsigned num_args = t->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = t->get_arg(i); + if (is_var(arg)) { + SASSERT(t->get_decl()->get_domain(i) == to_var(arg)->get_sort()); + insert_qinfo(alloc(f_var, t->get_decl(), i, to_var(arg)->get_idx())); + continue; + } + + var * v; + expr_ref k(m_manager); + if (is_var_plus_ground(arg, v, k)) { + insert_qinfo(alloc(f_var_plus_offset, m_manager, t->get_decl(), i, v->get_idx(), k.get())); + continue; + } + + visit_term(arg); + } + } + + + /** + \brief A term \c t is said to be a auf_select if + it is of ther form + + (select a i) Where: + + where a is ground or is_auf_select(a) == true + and the indices are ground terms or variables. + */ + bool is_auf_select(expr * t) const { + if (!m_array_util.is_select(t)) + return false; + expr * a = to_app(t)->get_arg(0); + if (!is_ground(a) && !is_auf_select(a)) + return false; + unsigned num_args = to_app(t)->get_num_args(); + for (unsigned i = 1; i < num_args; i++) { + expr * arg = to_app(t)->get_arg(i); + if (!is_ground(arg) && !is_var(arg)) + return false; + } + return true; + } + + /** + \brief Process intrepreted applications. + */ + void process_i_app(app * t) { + if (is_auf_select(t)) { + unsigned num_args = t->get_num_args(); + app * array = to_app(t->get_arg(0)); + visit_term(array); // array may be a nested array. + for (unsigned i = 1; i < num_args; i++) { + expr * arg = t->get_arg(i); + if (is_var(arg)) { + insert_qinfo(alloc(select_var, m_manager, t, i, to_var(arg)->get_idx())); + } + else { + SASSERT(is_ground(arg)); + } + } + } + else { + unsigned num_args = t->get_num_args(); + for (unsigned i = 0; i < num_args; i++) + visit_term(t->get_arg(i)); + } + } + + void process_app(app * t) { + SASSERT(!is_ground(t)); + + if (t->get_family_id() != m_manager.get_basic_family_id()) { + m_info->m_ng_decls.insert(t->get_decl()); + } + + if (is_uninterp(t)) { + process_u_app(t); + } + else { + process_i_app(t); + } + } + + void process_terms_on_stack() { + while (!m_ttodo.empty()) { + expr * curr = m_ttodo.back(); + m_ttodo.pop_back(); + + if (m_manager.is_bool(curr)) { + // formula nested in a term. + visit_formula(curr, POS); + visit_formula(curr, NEG); + continue; + } + + if (is_app(curr)) { + process_app(to_app(curr)); + } + else if (is_var(curr)) { + m_info->m_is_auf = false; // unexpected occurrence of variable. + } + else { + SASSERT(is_quantifier(curr)); // no nested quantifiers + UNREACHABLE(); + } + } + } + + void process_literal(expr * atom, bool sign) { + CTRACE("model_finder_bug", is_ground(atom), tout << mk_pp(atom, m_manager) << "\n";); + SASSERT(!is_ground(atom)); + SASSERT(m_manager.is_bool(atom)); + + if (is_var(atom)) { + if (sign) { + // atom (not X) can be viewed as X != true + insert_qinfo(alloc(x_neq_t, m_manager, to_var(atom)->get_idx(), m_manager.mk_true())); + } + else { + // atom X can be viewed as X != false + insert_qinfo(alloc(x_neq_t, m_manager, to_var(atom)->get_idx(), m_manager.mk_false())); + } + return; + } + + if (is_app(atom)) { + var * v, * v1, * v2; + expr_ref t(m_manager); + if (is_x_eq_t_atom(atom, v, t)) { + if (sign) + insert_qinfo(alloc(x_neq_t, m_manager, v->get_idx(), t)); + else + insert_qinfo(alloc(x_eq_t, m_manager, v->get_idx(), t)); + } + else if (is_x_eq_y_atom(atom, v1, v2)) { + if (sign) + insert_qinfo(alloc(x_neq_y, v1->get_idx(), v2->get_idx())); + else { + m_info->m_has_x_eq_y = true; // this atom is in the fringe of AUF + insert_qinfo(alloc(x_eq_y, v1->get_idx(), v2->get_idx())); + } + } + else if (sign && is_x_gle_y_atom(atom, v1, v2)) { + if (is_signed_le(atom)) + insert_qinfo(alloc(x_sleq_y, v1->get_idx(), v2->get_idx())); + else + insert_qinfo(alloc(x_leq_y, v1->get_idx(), v2->get_idx())); + } + else if (is_x_gle_t_atom(atom, sign, v, t)) { + insert_qinfo(alloc(x_gle_t, m_manager, v->get_idx(), t)); + } + else { + process_app(to_app(atom)); + } + return; + } + + SASSERT(is_quantifier(atom)); + UNREACHABLE(); + } + + void process_literal(expr * atom, polarity pol) { + process_literal(atom, pol == NEG); + } + + void process_or(app * n, polarity p) { + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) + visit_formula(n->get_arg(i), p); + } + + void process_ite(app * n, polarity p) { + visit_formula(n->get_arg(0), p); + visit_formula(n->get_arg(0), neg(p)); + visit_formula(n->get_arg(1), p); + visit_formula(n->get_arg(2), p); + } + + void process_iff(app * n) { + visit_formula(n->get_arg(0), POS); + visit_formula(n->get_arg(0), NEG); + visit_formula(n->get_arg(1), POS); + visit_formula(n->get_arg(1), NEG); + } + + void process_formulas_on_stack() { + while (!m_ftodo.empty()) { + entry & e = m_ftodo.back(); + expr * curr = e.first; + polarity pol = e.second; + m_ftodo.pop_back(); + if (is_app(curr)) { + if (to_app(curr)->get_family_id() == m_manager.get_basic_family_id() && m_manager.is_bool(curr)) { + switch (static_cast(to_app(curr)->get_decl_kind())) { + case OP_AND: + case OP_IMPLIES: + case OP_XOR: + UNREACHABLE(); // simplifier eliminated ANDs, IMPLIEs, and XORs + break; + case OP_OR: + process_or(to_app(curr), pol); + break; + case OP_NOT: + visit_formula(to_app(curr)->get_arg(0), neg(pol)); + break; + case OP_ITE: + process_ite(to_app(curr), pol); + break; + case OP_IFF: + process_iff(to_app(curr)); + break; + case OP_EQ: + if (m_manager.is_bool(to_app(curr)->get_arg(0))) { + process_iff(to_app(curr)); + } + else { + process_literal(curr, pol); + } + break; + default: + process_literal(curr, pol); + break; + } + } + else { + process_literal(curr, pol); + } + } + else if (is_var(curr)) { + SASSERT(m_manager.is_bool(curr)); + process_literal(curr, pol); + } + else { + SASSERT(is_quantifier(curr)); + UNREACHABLE(); // can't happen, the quantifier is supposed to be flat. + } + } + } + + void process_formula(expr * n) { + SASSERT(m_manager.is_bool(n)); + visit_formula(n, POS); + } + + void process_clause(expr * cls) { + SASSERT(is_clause(m_manager, cls)); + unsigned num_lits = get_clause_num_literals(m_manager, cls); + for (unsigned i = 0; i < num_lits; i++) { + expr * lit = get_clause_literal(m_manager, cls, i); + SASSERT(is_literal(m_manager, lit)); + expr * atom; + bool sign; + get_literal_atom_sign(m_manager, lit, atom, sign); + if (!is_ground(atom)) + process_literal(atom, sign); + } + } + + void collect_macro_candidates(quantifier * q) { + macro_util::macro_candidates candidates(m_manager); + m_mutil.collect_macro_candidates(q, candidates); + unsigned num_candidates = candidates.size(); + for (unsigned i = 0; i < num_candidates; i++) { + cond_macro * m = alloc(cond_macro, m_manager, candidates.get_f(i), candidates.get_def(i), candidates.get_cond(i), + candidates.ineq(i), candidates.satisfy_atom(i), candidates.hint(i), q->get_weight()); + m_info->insert_macro(m); + } + } + + + public: + quantifier_analyzer(ast_manager & m, simplifier & s): + m_manager(m), + m_mutil(m, s), + m_array_util(m), + m_arith_util(m), + m_bv_util(m), + m_info(0) { + } + + + void operator()(quantifier_info * d) { + m_info = d; + quantifier * q = d->get_flat_q(); + expr * e = q->get_expr(); + SASSERT(!has_quantifiers(e)); + reset_cache(); + SASSERT(m_ttodo.empty()); + SASSERT(m_ftodo.empty()); + + if (is_clause(m_manager, e)) { + process_clause(e); + } + else { + process_formula(e); + } + + while (!m_ftodo.empty() || !m_ttodo.empty()) { + process_formulas_on_stack(); + process_terms_on_stack(); + } + + collect_macro_candidates(q); + + m_info = 0; + } + + }; + + /** + \brief Base class for macro solvers. + */ + class base_macro_solver { + protected: + ast_manager & m_manager; + obj_map const & m_q2info; + proto_model * m_model; + + quantifier_info * get_qinfo(quantifier * q) const { + quantifier_info * qi = 0; + m_q2info.find(q, qi); + SASSERT(qi != 0); + return qi; + } + + void set_else_interp(func_decl * f, expr * f_else) { + SASSERT(f_else != 0); + func_interp * fi = m_model->get_func_interp(f); + if (fi == 0) { + fi = alloc(func_interp, m_manager, f->get_arity()); + m_model->register_decl(f, fi); + } + fi->set_else(f_else); + } + + virtual bool process(ptr_vector const & qs, ptr_vector & new_qs, ptr_vector & residue) = 0; + + public: + base_macro_solver(ast_manager & m, obj_map const & q2i): + m_manager(m), + m_q2info(q2i), + m_model(0) { + } + + virtual ~base_macro_solver() {} + + /** + \brief Try to satisfy quantifiers in qs by using macro definitions. + Store in new_qs the quantifiers that were not satisfied. + Store in residue a subset of the quantifiers that were satisfied but contain information useful for the auf_solver. + */ + void operator()(proto_model * m, ptr_vector const & qs, ptr_vector & new_qs, ptr_vector & residue) { + m_model = m; + ptr_vector curr_qs(qs); + while (process(curr_qs, new_qs, residue)) { + curr_qs.swap(new_qs); + new_qs.reset(); + } + } + }; + + + /** + \brief The simple macro solver satisfies quantifiers that contain + (conditional) macros for a function f that does not occur in any other quantifier. + + Since f does not occur in any other quantifier, I don't need to track the dependencies + of f. That is, recursive definition cannot be created. + */ + class simple_macro_solver : public base_macro_solver { + protected: + /** + \brief Return true if \c f is in (qs\{q}) + */ + bool contains(func_decl * f, ptr_vector const & qs, quantifier * q) { + ptr_vector::const_iterator it = qs.begin(); + ptr_vector::const_iterator end = qs.end(); + for (; it != end; ++it) { + quantifier * other = *it; + if (q == other) + continue; + quantifier_info * other_qi = get_qinfo(other); + if (other_qi->contains_ng_decl(f)) + return true; + } + return false; + } + + bool process(quantifier * q, ptr_vector const & qs) { + quantifier_info * qi = get_qinfo(q); + quantifier_info::macro_iterator it = qi->begin_macros(); + quantifier_info::macro_iterator end = qi->end_macros(); + for (; it != end; ++it) { + cond_macro * m = *it; + if (!m->satisfy_atom()) + continue; + func_decl * f = m->get_f(); + if (!contains(f, qs, q)) { + qi->set_the_one(f); + expr * f_else = m->get_def(); + SASSERT(f_else != 0); + // Remark: I can ignore the conditions of m because + // I know the (partial) interpretation of f satisfied the ground part. + // MBQI will force extra instantiations if the the (partial) interpretation of f + // does not satisfy the quantifier. + // In all other cases the "else" of f will satisfy the quantifier. + set_else_interp(f, f_else); + TRACE("model_finder", tout << "satisfying the quantifier using simple macro:\n"; + m->display(tout); tout << "\n";); + return true; // satisfied quantifier + } + } + return false; + } + + virtual bool process(ptr_vector const & qs, ptr_vector & new_qs, ptr_vector & residue) { + bool removed = false; + ptr_vector::const_iterator it = qs.begin(); + ptr_vector::const_iterator end = qs.end(); + for (; it != end; ++it) { + if (process(*it, qs)) + removed = true; + else + new_qs.push_back(*it); + } + return removed; + } + + public: + simple_macro_solver(ast_manager & m, obj_map const & q2i): + base_macro_solver(m, q2i) {} + }; + + + class hint_solver : public base_macro_solver { + /* + This solver tries to satisfy quantifiers by using macros, cond_macros and hints. + The idea is to satisfy a set of quantifiers Q = Q_{f_1} union ... union Q_{f_n} + where Q_{f_i} is the set of quantifiers that contain the function f_i. + Let f_i = def_i be macros (in this solver conditions are ignored). + Let Q_{f_i = def_i} be the set of quantifiers where f_i = def_i is a macro. + Then, the set Q can be satisfied using f_1 = def_1 ... f_n = d_n + when + + Q_{f_1} union ... union Q_{f_n} = Q_{f_1 = def_1} ... Q_{f_n = d_n} (*) + + So, given a set of macros f_1 = def_1, ..., f_n = d_n, it is very easy to check + whether they can be used to satisfy all quantifiers that use f_1, ..., f_n in + non ground terms. + + We can find the sets of f_1 = def_1, ..., f_n = def_n that satisfy Q using + the following search procedure + find(Q) + for each f_i = def_i in Q + R = greedy(Q_{f_i = def_1}, Q_f_i \ Q_{f_i = def_i}, {f_i}, {f_i = def_i}) + if (R != empty-set) + return R + greedy(Satisfied, Residue, F, F_DEF) + if Residue = empty-set return F_DEF + for each f_j = def_j in Residue such that f_j not in F + New-Satisfied = Satisfied union Q_{f_j = def_j} + New-Residue = (Residue union Q_{f_j}) \ New-Satisfied + R = greedy(New-Satisfied, New-Residue, F \union {f_j}, F_DEF union {f_j = def_j}) + if (R != empty-set) + return R + + This search may be too expensive, and is exponential on the number of different function + symbols. + Some observations to prune the search space. + 1) If f_i occurs in a quantifier without macros, then f_i and any macro using it can be ignored during the search. + 2) If f_i = def_i is not a macro in a quantifier q, and there is no other f_j = def_j (i != j) in q, + then f_i = def_i can be ignored during the search. + */ + + typedef obj_hashtable quantifier_set; + typedef obj_map q_f; + typedef obj_pair_map q_f_def; + typedef obj_pair_hashtable f_def_set; + typedef obj_hashtable expr_set; + typedef obj_map f2defs; + + q_f m_q_f; + q_f_def m_q_f_def; + ptr_vector m_qsets; + f2defs m_f2defs; + ptr_vector m_esets; + + void insert_q_f(quantifier * q, func_decl * f) { + SASSERT(!m_forbidden.contains(f)); + quantifier_set * s = 0; + if (!m_q_f.find(f, s)) { + s = alloc(quantifier_set); + m_q_f.insert(f, s); + m_qsets.push_back(s); + } + SASSERT(s != 0); + s->insert(q); + } + + void insert_f2def(func_decl * f, expr * def) { + expr_set * s = 0; + if (!m_f2defs.find(f, s)) { + s = alloc(expr_set); + m_f2defs.insert(f, s); + m_esets.push_back(s); + } + SASSERT(s != 0); + s->insert(def); + } + + void insert_q_f_def(quantifier * q, func_decl * f, expr * def) { + SASSERT(!m_forbidden.contains(f)); + quantifier_set * s = 0; + if (!m_q_f_def.find(f, def, s)) { + s = alloc(quantifier_set); + m_q_f_def.insert(f, def, s); + insert_f2def(f, def); + m_qsets.push_back(s); + } + SASSERT(s != 0); + s->insert(q); + } + + quantifier_set * get_q_f(func_decl * f) { + quantifier_set * s = 0; + m_q_f.find(f, s); + SASSERT(s != 0); + return s; + } + + quantifier_set * get_q_f_def(func_decl * f, expr * def) { + quantifier_set * s = 0; + m_q_f_def.find(f, def, s); + SASSERT(s != 0); + return s; + } + + expr_set * get_f_defs(func_decl * f) { + expr_set * s = 0; + m_f2defs.find(f, s); + SASSERT(s != 0); + return s; + } + + void reset_q_fs() { + std::for_each(m_qsets.begin(), m_qsets.end(), delete_proc()); + std::for_each(m_esets.begin(), m_esets.end(), delete_proc()); + m_q_f.reset(); + m_q_f_def.reset(); + m_qsets.reset(); + m_f2defs.reset(); + m_esets.reset(); + } + + func_decl_set m_forbidden; + func_decl_set m_candidates; + + bool is_candidate(quantifier * q) const { + quantifier_info * qi = get_qinfo(q); + quantifier_info::macro_iterator it = qi->begin_macros(); + quantifier_info::macro_iterator end = qi->end_macros(); + for (; it != end; ++it) { + cond_macro * m = *it; + if (m->satisfy_atom() && !m_forbidden.contains(m->get_f())) + return true; + } + return false; + } + + void register_decls_as_forbidden(quantifier * q) { + quantifier_info * qi = get_qinfo(q); + func_decl_set const & ng_decls = qi->get_ng_decls(); + func_decl_set::iterator it = ng_decls.begin(); + func_decl_set::iterator end = ng_decls.end(); + for (; it != end; ++it) { + m_forbidden.insert(*it); + } + } + + void preprocess(ptr_vector const & qs, ptr_vector & qcandidates, ptr_vector & non_qcandidates) { + ptr_vector curr(qs); + while (true) { + ptr_vector::iterator it = curr.begin(); + ptr_vector::iterator end = curr.end(); + for (; it != end; ++it) { + quantifier * q = *it; + if (is_candidate(q)) { + qcandidates.push_back(q); + } + else { + register_decls_as_forbidden(q); + non_qcandidates.push_back(q); + } + } + if (curr.size() == qcandidates.size()) + return; + SASSERT(qcandidates.size() < curr.size()); + curr.swap(qcandidates); + qcandidates.reset(); + } + } + + void mk_q_f_defs(ptr_vector const & qs) { + ptr_vector::const_iterator it = qs.begin(); + ptr_vector::const_iterator end = qs.end(); + for (; it != end; ++it) { + quantifier * q = *it; + quantifier_info * qi = get_qinfo(q); + func_decl_set const & ng_decls = qi->get_ng_decls(); + func_decl_set::iterator it2 = ng_decls.begin(); + func_decl_set::iterator end2 = ng_decls.end(); + for (; it2 != end2; ++it2) { + func_decl * f = *it2; + if (!m_forbidden.contains(f)) + insert_q_f(q, f); + } + quantifier_info::macro_iterator it3 = qi->begin_macros(); + quantifier_info::macro_iterator end3 = qi->end_macros(); + for (; it3 != end3; ++it3) { + cond_macro * m = *it3; + if (m->satisfy_atom() && !m_forbidden.contains(m->get_f())) { + insert_q_f_def(q, m->get_f(), m->get_def()); + m_candidates.insert(m->get_f()); + } + } + } + } + + static void display_quantifier_set(std::ostream & out, quantifier_set const * s) { + quantifier_set::iterator it = s->begin(); + quantifier_set::iterator end = s->end(); + for (; it != end; ++it) { + quantifier * q = *it; + out << q->get_qid() << " "; + } + out << "\n"; + } + + void display_qcandidates(std::ostream & out, ptr_vector const & qcandidates) const { + ptr_vector::const_iterator it1 = qcandidates.begin(); + ptr_vector::const_iterator end1 = qcandidates.end(); + for (; it1 != end1; ++it1) { + quantifier * q = *it1; + out << q->get_qid() << " ->\n" << mk_pp(q, m_manager) << "\n"; + quantifier_info * qi = get_qinfo(q); + qi->display(out); + out << "------\n"; + } + out << "Sets Q_f\n"; + q_f::iterator it2 = m_q_f.begin(); + q_f::iterator end2 = m_q_f.end(); + for (; it2 != end2; ++it2) { + func_decl * f = (*it2).m_key; + quantifier_set * s = (*it2).m_value; + out << f->get_name() << " -> "; display_quantifier_set(out, s); + } + out << "Sets Q_{f = def}\n"; + q_f_def::iterator it3 = m_q_f_def.begin(); + q_f_def::iterator end3 = m_q_f_def.end(); + for (; it3 != end3; ++it3) { + func_decl * f = (*it3).get_key1(); + expr * def = (*it3).get_key2(); + quantifier_set * s = (*it3).get_value(); + out << f->get_name() << " " << mk_pp(def, m_manager) << " ->\n"; display_quantifier_set(out, s); + } + } + + // + // Search: main procedures + // + + struct ev_handler { + hint_solver * m_owner; + + void operator()(quantifier * q, bool ins) { + quantifier_info * qi = m_owner->get_qinfo(q); + qi->set_the_one(0); + } + + ev_handler(hint_solver * o): + m_owner(o) { + } + }; + + + typedef backtrackable_set qset; + typedef backtrackable_set qsset; + typedef obj_map f2def; + + qset m_residue; + qsset m_satisfied; + f2def m_fs; // set of function symbols (and associated interpretation) that were used to satisfy the quantifiers in m_satisfied. + + struct found_satisfied_subset {}; + + void display_search_state(std::ostream & out) const { + out << "fs:\n"; + f2def::iterator it3 = m_fs.begin(); + f2def::iterator end3 = m_fs.end(); + for (; it3 != end3; ++it3) { + out << (*it3).m_key->get_name() << " "; + } + out << "\nsatisfied:\n"; + qsset::iterator it = m_satisfied.begin(); + qsset::iterator end = m_satisfied.end(); + for (; it != end; ++it) { + out << (*it)->get_qid() << " "; + } + out << "\nresidue:\n"; + qset::iterator it2 = m_residue.begin(); + qset::iterator end2 = m_residue.end(); + for (; it2 != end2; ++it2) { + out << (*it2)->get_qid() << " "; + } + out << "\n"; + } + + bool check_satisfied_residue_invariant() { + qsset::iterator it = m_satisfied.begin(); + qsset::iterator end = m_satisfied.end(); + for (; it != end; ++it) { + quantifier * q = *it; + SASSERT(!m_residue.contains(q)); + quantifier_info * qi = get_qinfo(q); + SASSERT(qi != 0); + SASSERT(qi->get_the_one() != 0); + } + return true; + } + + + bool update_satisfied_residue(func_decl * f, expr * def) { + bool useful = false; + SASSERT(check_satisfied_residue_invariant()); + quantifier_set * q_f = get_q_f(f); + quantifier_set * q_f_def = get_q_f_def(f, def); + quantifier_set::iterator it = q_f_def->begin(); + quantifier_set::iterator end = q_f_def->end(); + for (; it != end; ++it) { + quantifier * q = *it; + if (!m_satisfied.contains(q)) { + useful = true; + m_residue.erase(q); + m_satisfied.insert(q); + quantifier_info * qi = get_qinfo(q); + SASSERT(qi->get_the_one() == 0); + qi->set_the_one(f); // remark... event handler will reset it during backtracking. + } + } + if (!useful) + return false; + it = q_f->begin(); + end = q_f->end(); + for (; it != end; ++it) { + quantifier * q = *it; + if (!m_satisfied.contains(q)) { + m_residue.insert(q); + } + } + SASSERT(check_satisfied_residue_invariant()); + return true; + } + + /** + \brief Extract from m_residue, func_decls that can be used as macros to satisfy it. + The candidates must not be elements of m_fs. + */ + void get_candidates_from_residue(func_decl_set & candidates) { + qset::iterator it = m_residue.begin(); + qset::iterator end = m_residue.end(); + for (; it != end; ++it) { + quantifier * q = *it; + quantifier_info * qi = get_qinfo(q); + + quantifier_info::macro_iterator it2 = qi->begin_macros(); + quantifier_info::macro_iterator end2 = qi->end_macros(); + for (; it2 != end2; ++it2) { + cond_macro * m = *it2; + func_decl * f = m->get_f(); + if (m->satisfy_atom() && !m_forbidden.contains(f) && !m_fs.contains(f)) { + candidates.insert(f); + } + } + } + } + +#define GREEDY_MAX_DEPTH 10 /* to avoid too expensive search spaces */ + + /** + \brief Try to reduce m_residue using the macros of f. + */ + void greedy(func_decl * f, unsigned depth) { + if (depth >= GREEDY_MAX_DEPTH) + return; // failed + + TRACE("model_finder_hint", + tout << "greedy depth: " << depth << ", f: " << f->get_name() << "\n"; + display_search_state(tout);); + + expr_set * s = get_f_defs(f); + expr_set::iterator it = s->begin(); + expr_set::iterator end = s->end(); + for (; it != end; ++it) { + expr * def = *it; + + SASSERT(!m_fs.contains(f)); + + m_satisfied.push_scope(); + m_residue.push_scope(); + m_fs.insert(f, def); + + if (update_satisfied_residue(f, def)) { + // update was useful + greedy(depth + 1); // greedy throws exception in case of success + // reachable iff greedy failed. + } + + m_satisfied.pop_scope(); + m_residue.pop_scope(); + m_fs.erase(f); + } + } + + /** + \brief Try to reduce m_residue (if not empty) by selecting a function f + that is a macro in the residue. + */ + void greedy(unsigned depth) { + if (m_residue.empty()) { + TRACE("model_finder_hint", + tout << "found subset that is satisfied by macros\n"; + display_search_state(tout);); + throw found_satisfied_subset(); + } + func_decl_set candidates; + get_candidates_from_residue(candidates); + + TRACE("model_finder_hint", tout << "candidates from residue:\n"; + func_decl_set::iterator it = candidates.begin(); + func_decl_set::iterator end = candidates.end(); + for (; it != end; ++it) { + tout << (*it)->get_name() << " "; + } + tout << "\n";); + + func_decl_set::iterator it = candidates.begin(); + func_decl_set::iterator end = candidates.end(); + for (; it != end; ++it) { + greedy(*it, depth); + } + } + + /** + \brief Try to find a set of quantifiers by starting to use the macros of f. + This is the "find" procedure in the comments above. + The set of satisfied quantifiers is in m_satisfied, and the remaining to be + satisfied in m_residue. When the residue becomes empty we throw the exception found_satisfied_subset. + */ + void process(func_decl * f) { + SASSERT(m_satisfied.empty()); + SASSERT(m_residue.empty()); + greedy(f, 0); + } + + /** + \brief Copy the quantifiers from qcandidates to new_qs that are not in m_satisfied. + */ + void copy_non_satisfied(ptr_vector const & qcandidates, ptr_vector & new_qs) { + ptr_vector::const_iterator it = qcandidates.begin(); + ptr_vector::const_iterator end = qcandidates.end(); + for (; it != end; ++it) { + quantifier * q = *it; + if (!m_satisfied.contains(q)) + new_qs.push_back(q); + } + } + + /** + \brief Use m_fs to set the interpreation of the function symbols that were used to satisfy the + quantifiers in m_satisfied. + */ + void set_interp() { + f2def::iterator it = m_fs.begin(); + f2def::iterator end = m_fs.end(); + for (; it != end; ++it) { + func_decl * f = (*it).m_key; + expr * def = (*it).m_value; + set_else_interp(f, def); + } + } + + void reset() { + reset_q_fs(); + m_forbidden.reset(); + m_candidates.reset(); + m_satisfied.reset(); + m_residue.reset(); + m_fs.reset(); + } + + virtual bool process(ptr_vector const & qs, ptr_vector & new_qs, ptr_vector & residue) { + reset(); + ptr_vector qcandidates; + preprocess(qs, qcandidates, new_qs); + if (qcandidates.empty()) { + SASSERT(new_qs.size() == qs.size()); + return false; + } + mk_q_f_defs(qcandidates); + TRACE("model_finder_hint", tout << "starting hint-solver search using:\n"; display_qcandidates(tout, qcandidates);); + func_decl_set::iterator it = m_candidates.begin(); + func_decl_set::iterator end = m_candidates.end(); + for (; it != end; ++it) { + func_decl * f = *it; + try { + process(f); + } + catch (found_satisfied_subset) { + set_interp(); + copy_non_satisfied(qcandidates, new_qs); + return true; + } + } + // failed... copy everything to new_qs + new_qs.append(qcandidates); + return false; + } + + public: + hint_solver(ast_manager & m, obj_map const & q2i): + base_macro_solver(m, q2i), + m_satisfied(ev_handler(this)) { + } + + virtual ~hint_solver() { + reset(); + } + + }; + + + /** + \brief Satisfy clauses that are not in the AUF fragment but define conditional macros. + These clauses are eliminated even if the symbol being defined occurs in other quantifiers. + The auf_solver is ineffective in these clauses. + + \remark Full macros are used even if they are in the AUF fragment. + */ + class non_auf_macro_solver : public base_macro_solver { + func_decl_dependencies & m_dependencies; + qi_params const * m_qi_params; + + bool add_macro(func_decl * f, expr * f_else) { + TRACE("non_auf_macro_solver", tout << "trying to add macro for " << f->get_name() << "\n" << mk_pp(f_else, m_manager) << "\n";); + func_decl_set * s = m_dependencies.mk_func_decl_set(); + m_dependencies.collect_ng_func_decls(f_else, s); + if (!m_dependencies.insert(f, s)) { + TRACE("non_auf_macro_solver", tout << "failed to add macro\n";); + return false; // cyclic dependency + } + set_else_interp(f, f_else); + return true; + } + + // Return true if r1 is a better macro than r2. + bool is_better_macro(cond_macro * r1, cond_macro * r2) { + if (r2 == 0 || !r1->is_hint()) + return true; + if (!r2->is_hint()) + return false; + SASSERT(r1->is_hint() && r2->is_hint()); + if (is_ground(r1->get_def()) && !is_ground(r2->get_def())) + return true; + return false; + } + + cond_macro * get_macro_for(func_decl * f, quantifier * q) { + cond_macro * r = 0; + quantifier_info * qi = get_qinfo(q); + quantifier_info::macro_iterator it = qi->begin_macros(); + quantifier_info::macro_iterator end = qi->end_macros(); + for (; it != end; ++it) { + cond_macro * m = *it; + if (m->get_f() == f && !m->is_hint() && is_better_macro(m, r)) + r = m; + } + return r; + } + + typedef std::pair mq_pair; + + void collect_candidates(ptr_vector const & qs, obj_map & full_macros, func_decl_set & cond_macros) { + ptr_vector::const_iterator it = qs.begin(); + ptr_vector::const_iterator end = qs.end(); + for (; it != end; ++it) { + quantifier * q = *it; + quantifier_info * qi = get_qinfo(q); + quantifier_info::macro_iterator it2 = qi->begin_macros(); + quantifier_info::macro_iterator end2 = qi->end_macros(); + for (; it2 != end2; ++it2) { + cond_macro * m = *it2; + if (!m->is_hint()) { + func_decl * f = m->get_f(); + TRACE("non_auf_macro_solver", tout << "considering macro for: " << f->get_name() << "\n"; + m->display(tout); tout << "\n";); + SASSERT(m_qi_params != 0); + if (m->is_unconditional() && (!qi->is_auf() || m->get_weight() >= m_qi_params->m_mbqi_force_template)) { + full_macros.insert(f, std::make_pair(m, q)); + cond_macros.erase(f); + } + else if (!full_macros.contains(f) && !qi->is_auf()) + cond_macros.insert(f); + } + } + } + } + + void process_full_macros(obj_map const & full_macros, obj_hashtable & removed) { + obj_map::iterator it = full_macros.begin(); + obj_map::iterator end = full_macros.end(); + for (; it != end; ++it) { + func_decl * f = (*it).m_key; + cond_macro * m = (*it).m_value.first; + quantifier * q = (*it).m_value.second; + SASSERT(m->is_unconditional()); + if (add_macro(f, m->get_def())) { + get_qinfo(q)->set_the_one(f); + removed.insert(q); + } + } + } + + void process(func_decl * f, ptr_vector const & qs, obj_hashtable & removed) { + expr_ref fi_else(m_manager); + ptr_buffer to_remove; + ptr_vector::const_iterator it = qs.begin(); + ptr_vector::const_iterator end = qs.end(); + for (; it != end; ++it) { + quantifier * q = *it; + if (removed.contains(q)) + continue; + cond_macro * m = get_macro_for(f, q); + if (!m) + continue; + SASSERT(!m->is_hint()); + if (m->is_unconditional()) + return; // f is part of a full macro... ignoring it. + to_remove.push_back(q); + if (fi_else.get() == 0) { + fi_else = m->get_def(); + } + else { + fi_else = m_manager.mk_ite(m->get_cond(), m->get_def(), fi_else); + } + } + if (fi_else.get() != 0 && add_macro(f, fi_else)) { + ptr_buffer::iterator it2 = to_remove.begin(); + ptr_buffer::iterator end2 = to_remove.end(); + for (; it2 != end2; ++it2) { + get_qinfo(*it2)->set_the_one(f); + removed.insert(*it2); + } + } + } + + void process_cond_macros(func_decl_set const & cond_macros, ptr_vector const & qs, obj_hashtable & removed) { + func_decl_set::iterator it = cond_macros.begin(); + func_decl_set::iterator end = cond_macros.end(); + for (; it != end; ++it) { + process(*it, qs, removed); + } + } + + virtual bool process(ptr_vector const & qs, ptr_vector & new_qs, ptr_vector & residue) { + obj_map full_macros; + func_decl_set cond_macros; + obj_hashtable removed; + + // Possible improvement: sort full_macros & cond_macros using an user provided precedence function. + + collect_candidates(qs, full_macros, cond_macros); + process_full_macros(full_macros, removed); + process_cond_macros(cond_macros, qs, removed); + + ptr_vector::const_iterator it = qs.begin(); + ptr_vector::const_iterator end = qs.end(); + for (; it != end; ++it) { + quantifier * q = *it; + if (removed.contains(q)) + continue; + new_qs.push_back(q); + residue.push_back(q); + } + return !removed.empty(); + } + + public: + non_auf_macro_solver(ast_manager & m, obj_map const & q2i, func_decl_dependencies & d): + base_macro_solver(m, q2i), + m_dependencies(d), + m_qi_params(0) { + } + + void set_params(qi_params const & p) { + SASSERT(m_qi_params == 0); + m_qi_params = &p; + } + }; + }; + + // ----------------------------------- + // + // model finder + // + // ----------------------------------- + + model_finder::model_finder(ast_manager & m, simplifier & s): + m_manager(m), + m_context(0), + m_analyzer(alloc(quantifier_analyzer, m, s)), + m_auf_solver(alloc(auf_solver, m, s)), + m_dependencies(m), + m_sm_solver(alloc(simple_macro_solver, m, m_q2info)), + m_hint_solver(alloc(hint_solver, m, m_q2info)), + m_nm_solver(alloc(non_auf_macro_solver, m, m_q2info, m_dependencies)), + m_new_constraints(m) { + } + + model_finder::~model_finder() { + reset(); + } + + mf::quantifier_info * model_finder::get_quantifier_info(quantifier * q) const { + quantifier_info * info = 0; + m_q2info.find(q, info); + SASSERT(info != 0); + return info; + } + + void model_finder::set_context(context * ctx) { + SASSERT(m_context == 0); + m_context = ctx; + m_auf_solver->set_context(ctx); + m_nm_solver->set_params(ctx->get_fparams()); + } + + void model_finder::register_quantifier(quantifier * q) { + TRACE("model_finder", tout << "registering:\n" << mk_pp(q, m_manager) << "\n";); + quantifier_info * new_info = alloc(quantifier_info, m_manager, q); + m_q2info.insert(q, new_info); + m_quantifiers.push_back(q); + m_analyzer->operator()(new_info); + TRACE("model_finder", tout << "after analyzer:\n"; new_info->display(tout);); + } + + void model_finder::push_scope() { + m_scopes.push_back(scope()); + scope & s = m_scopes.back(); + s.m_quantifiers_lim = m_quantifiers.size(); + } + + void model_finder::restore_quantifiers(unsigned old_size) { + unsigned curr_size = m_quantifiers.size(); + SASSERT(old_size <= curr_size); + for (unsigned i = old_size; i < curr_size; i++) { + quantifier * q = m_quantifiers[i]; + SASSERT(m_q2info.contains(q)); + quantifier_info * info = get_quantifier_info(q); + dealloc(info); + m_q2info.erase(q); + } + m_quantifiers.shrink(old_size); + } + + void model_finder::pop_scope(unsigned num_scopes) { + unsigned lvl = m_scopes.size(); + SASSERT(num_scopes <= lvl); + unsigned new_lvl = lvl - num_scopes; + scope & s = m_scopes[new_lvl]; + restore_quantifiers(s.m_quantifiers_lim); + m_scopes.shrink(new_lvl); + } + + void model_finder::reset() { + m_scopes.reset(); + m_dependencies.reset(); + restore_quantifiers(0); + SASSERT(m_q2info.empty()); + SASSERT(m_quantifiers.empty()); + } + + void model_finder::init_search_eh() { + // do nothing in the current version + } + + void model_finder::collect_relevant_quantifiers(ptr_vector & qs) const { + ptr_vector::const_iterator it = m_quantifiers.begin(); + ptr_vector::const_iterator end = m_quantifiers.end(); + for (; it != end; ++it) { + quantifier * q = *it; + if (m_context->is_relevant(q) && m_context->get_assignment(q) == l_true) + qs.push_back(q); + } + } + + void model_finder::process_auf(ptr_vector const & qs, proto_model * m) { + m_auf_solver->reset(); + m_auf_solver->set_model(m); + + ptr_vector::const_iterator it = qs.begin(); + ptr_vector::const_iterator end = qs.end(); + for (; it != end; ++it) { + quantifier * q = *it; + quantifier_info * qi = get_quantifier_info(q); + qi->process_auf(*(m_auf_solver.get()), m_context); + } + m_auf_solver->mk_instatiation_sets(); + it = qs.begin(); + for (; it != end; ++it) { + quantifier * q = *it; + quantifier_info * qi = get_quantifier_info(q); + qi->populate_inst_sets(*(m_auf_solver.get()), m_context); + } + m_auf_solver->fix_model(m_new_constraints); + TRACE("model_finder", + ptr_vector::const_iterator it = qs.begin(); + ptr_vector::const_iterator end = qs.end(); + for (; it != end; ++it) { + quantifier * q = *it; + quantifier_info * qi = get_quantifier_info(q); + quantifier * fq = qi->get_flat_q(); + tout << "#" << fq->get_id() << " ->\n" << mk_pp(fq, m_manager) << "\n"; + } + m_auf_solver->display_nodes(tout);); + } + + void model_finder::process_simple_macros(ptr_vector & qs, ptr_vector & residue, proto_model * m) { + ptr_vector new_qs; + m_sm_solver->operator()(m, qs, new_qs, residue); + qs.swap(new_qs); + TRACE("model_finder", tout << "model after processing simple macros:\n"; model_pp(tout, *m);); + } + + void model_finder::process_hint_macros(ptr_vector & qs, ptr_vector & residue, proto_model * m) { + ptr_vector new_qs; + m_hint_solver->operator()(m, qs, new_qs, residue); + qs.swap(new_qs); + TRACE("model_finder", tout << "model after processing simple macros:\n"; model_pp(tout, *m);); + } + + void model_finder::process_non_auf_macros(ptr_vector & qs, ptr_vector & residue, proto_model * m) { + ptr_vector new_qs; + m_nm_solver->operator()(m, qs, new_qs, residue); + qs.swap(new_qs); + TRACE("model_finder", tout << "model after processing non auf macros:\n"; model_pp(tout, *m);); + } + + /** + \brief Clean leftovers from previous invocations to fix_model. + */ + void model_finder::cleanup_quantifier_infos(ptr_vector const & qs) { + ptr_vector::const_iterator it = qs.begin(); + ptr_vector::const_iterator end = qs.end(); + for (; it != end; ++it) { + quantifier_info * qi = get_quantifier_info(*it); + qi->reset_the_one(); + } + } + + /** + \brief Try to satisfy quantifiers by modifying the model while preserving the satisfiability + of all ground formulas asserted into the logical context. + */ + void model_finder::fix_model(proto_model * m) { + if (m_quantifiers.empty()) + return; + ptr_vector qs; + ptr_vector residue; + collect_relevant_quantifiers(qs); + if (qs.empty()) + return; + TRACE("model_finder", tout << "trying to satisfy quantifiers, given model:\n"; model_pp(tout, *m);); + cleanup_quantifier_infos(qs); + m_dependencies.reset(); + + process_simple_macros(qs, residue, m); + process_hint_macros(qs, residue, m); + process_non_auf_macros(qs, residue, m); + qs.append(residue); + process_auf(qs, m); + } + + quantifier * model_finder::get_flat_quantifier(quantifier * q) const { + quantifier_info * qinfo = get_quantifier_info(q); + SASSERT(qinfo); + return qinfo->get_flat_q(); + } + + /** + \brief Return the instantiation set associated with var i of q. + + \remark q is the quantifier before flattening. + */ + mf::instantiation_set const * model_finder::get_uvar_inst_set(quantifier * q, unsigned i) const { + quantifier * flat_q = get_flat_quantifier(q); + SASSERT(flat_q->get_num_decls() >= q->get_num_decls()); + instantiation_set const * r = m_auf_solver->get_uvar_inst_set(flat_q, flat_q->get_num_decls() - q->get_num_decls() + i); + TRACE("model_finder", tout << "q: #" << q->get_id() << "\n" << mk_pp(q,m_manager) << "\nflat_q: " << mk_pp(flat_q, m_manager) + << "\ni: " << i << " " << flat_q->get_num_decls() - q->get_num_decls() + i << "\n";); + if (r != 0) + return r; + // quantifier was not processed by AUF solver... + // it must have been satisfied by "macro"/"hint". + quantifier_info * qinfo = get_quantifier_info(q); + SASSERT(qinfo); + SASSERT(qinfo->get_the_one() != 0); + + return qinfo->get_macro_based_inst_set(i, m_context, *(m_auf_solver.get())); + } + + /** + \brief Return an expression in the instantiation-set of q:i that evaluates to val. + + \remark q is the quantifier before flattening. + + Store in generation the generation of the result + */ + expr * model_finder::get_inv(quantifier * q, unsigned i, expr * val, unsigned & generation) const { + instantiation_set const * s = get_uvar_inst_set(q, i); + if (s == 0) + return 0; + expr * t = s->get_inv(val); + if (t != 0) { + generation = s->get_generation(t); + } + return t; + } + + /** + \brief Assert constraints restricting the possible values of the skolem constants can be assigned to. + The idea is to restrict them to the values in the instantiation sets. + + \remark q is the quantifier before flattening. + + Return true if something was asserted. + */ + bool model_finder::restrict_sks_to_inst_set(context * aux_ctx, quantifier * q, expr_ref_vector const & sks) { + // Note: we currently add instances of q instead of flat_q. + // If the user wants instances of flat_q, it should use PULL_NESTED_QUANTIFIERS=true. This option + // will guarantee that q == flat_q. + // + // Since we only care about q (and its bindings), it only makes sense to restrict the variables of q. + bool asserted_something = false; + quantifier * flat_q = get_flat_quantifier(q); + unsigned num_decls = q->get_num_decls(); + unsigned flat_num_decls = flat_q->get_num_decls(); + unsigned num_sks = sks.size(); + // Remark: sks were created for the flat version of q. + SASSERT(num_sks == flat_num_decls); + SASSERT(flat_num_decls >= num_decls); + SASSERT(num_sks >= num_decls); + for (unsigned i = 0; i < num_decls; i++) { + expr * sk = sks.get(num_decls - i - 1); + instantiation_set const * s = get_uvar_inst_set(q, i); + if (s == 0) + continue; // nothing to do + obj_map const & inv = s->get_inv_map(); + if (inv.empty()) + continue; // nothing to do + ptr_buffer eqs; + obj_map::iterator it = inv.begin(); + obj_map::iterator end = inv.end(); + for (; it != end; ++it) { + expr * val = (*it).m_key; + eqs.push_back(m_manager.mk_eq(sk, val)); + } + expr_ref new_cnstr(m_manager); + new_cnstr = m_manager.mk_or(eqs.size(), eqs.c_ptr()); + TRACE("model_finder", tout << "assert_restriction:\n" << mk_pp(new_cnstr, m_manager) << "\n";); + aux_ctx->assert_expr(new_cnstr); + asserted_something = true; + } + return asserted_something; + } + + void model_finder::restart_eh() { + unsigned sz = m_new_constraints.size(); + if (sz > 0) { + for (unsigned i = 0; i < sz; i++) { + expr * c = m_new_constraints.get(i); + TRACE("model_finder_bug_detail", tout << "asserting new constraint: " << mk_pp(c, m_manager) << "\n";); + m_context->internalize(c, true); + literal l(m_context->get_literal(c)); + m_context->mark_as_relevant(l); + // asserting it as an AXIOM + m_context->assign(l, b_justification()); + } + m_new_constraints.reset(); + } + } + +}; diff --git a/lib/smt_model_finder.h b/lib/smt_model_finder.h new file mode 100644 index 000000000..20d9ba6ef --- /dev/null +++ b/lib/smt_model_finder.h @@ -0,0 +1,125 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_model_finder.h + +Abstract: + + Model finding goodies for universally quantified formulas. + + During the search, the finder store information about the quantifiers + that are internalized. In an ideal world, quantifiers are only internalized + at base level. + + Given a satisfiable ground formula, Z3 will restrict the interpretation + of uninterpreted functions in a finite subset of its domain. + The model finder tries to produce a complete interpretation that will + also satisfy all universally quantified formulas. + + During model construction, the model finder will complete the interpretation + of uninterpreted functions by propagating basic constraints induced by the + body of universally quantified formulas. + + More information can be found in the following papers: + + - Complete instantiation for quantified SMT formulas, Yeting Ge + and Leonardo de Moura, Conference on Computer Aided Verification + (CAV 2009), Grenoble, France, 2009. + + - Efficiently Solving Quantified Bit-Vector Formula, Christoph + Wintersteiger, Youssef Hamadi and Leonardo de Moura, FMCAD, + Lugano, Switzerland, 2010. + + - Bugs, Moles and Skeletons: Symbolic Reasoning for Software + Development, Leonardo de Moura, Nikolaj Bjørner, IJCAR, + Edinburgh, Scotland, 2010. + +Author: + + Leonardo de Moura (leonardo) 2010-12-17. + +Revision History: + +--*/ +#ifndef _SMT_MODEL_FINDER_H_ +#define _SMT_MODEL_FINDER_H_ + +#include"ast.h" +#include"func_decl_dependencies.h" +#include"simplifier.h" +#include"proto_model.h" + +namespace smt { + class context; + + namespace mf { + class quantifier_info; + class quantifier_analyzer; + class auf_solver; + class simple_macro_solver; + class hint_solver; + class non_auf_macro_solver; + class instantiation_set; + }; + + class model_finder { + typedef mf::quantifier_analyzer quantifier_analyzer; + typedef mf::quantifier_info quantifier_info; + typedef mf::auf_solver auf_solver; + typedef mf::simple_macro_solver simple_macro_solver; + typedef mf::hint_solver hint_solver; + typedef mf::non_auf_macro_solver non_auf_macro_solver; + typedef mf::instantiation_set instantiation_set; + + ast_manager & m_manager; + context * m_context; + scoped_ptr m_analyzer; + scoped_ptr m_auf_solver; + obj_map m_q2info; + ptr_vector m_quantifiers; + func_decl_dependencies m_dependencies; + scoped_ptr m_sm_solver; + scoped_ptr m_hint_solver; + scoped_ptr m_nm_solver; + + struct scope { + unsigned m_quantifiers_lim; + }; + + svector m_scopes; + + expr_ref_vector m_new_constraints; // new constraints for fresh constants created by the model finder + + void restore_quantifiers(unsigned old_size); + quantifier_info * get_quantifier_info(quantifier * q) const; + void collect_relevant_quantifiers(ptr_vector & qs) const; + void cleanup_quantifier_infos(ptr_vector const & qs); + void process_simple_macros(ptr_vector & qs, ptr_vector & residue, proto_model * m); + void process_hint_macros(ptr_vector & qs, ptr_vector & residue, proto_model * m); + void process_non_auf_macros(ptr_vector & qs, ptr_vector & residue, proto_model * m); + void process_auf(ptr_vector const & qs, proto_model * m); + instantiation_set const * get_uvar_inst_set(quantifier * q, unsigned i) const; + + public: + model_finder(ast_manager & m, simplifier & s); + ~model_finder(); + void set_context(context * ctx); + + void register_quantifier(quantifier * q); + void push_scope(); + void pop_scope(unsigned num_scopes); + void reset(); + void init_search_eh(); + void fix_model(proto_model * m); + + quantifier * get_flat_quantifier(quantifier * q) const; + expr * get_inv(quantifier * q, unsigned i, expr * val, unsigned & generation) const; + bool restrict_sks_to_inst_set(context * aux_ctx, quantifier * q, expr_ref_vector const & sks); + + void restart_eh(); + }; +}; + +#endif diff --git a/lib/smt_model_generator.cpp b/lib/smt_model_generator.cpp new file mode 100644 index 000000000..60c12b76d --- /dev/null +++ b/lib/smt_model_generator.cpp @@ -0,0 +1,631 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_model_generator.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-10-29. + +Revision History: + +--*/ + +#include"smt_context.h" +#include"smt_model_generator.h" +#include"proto_model.h" +#include"for_each_expr.h" +#include"ast_ll_pp.h" +#include"ast_pp.h" +#include"ast_smt2_pp.h" + +namespace smt { + + model_generator::model_generator(ast_manager & m): + m_manager(m), + m_context(0), + m_fresh_idx(1), + m_asts(m_manager), + m_model(0) { + } + + model_generator::~model_generator() { + } + + void model_generator::reset() { + m_extra_fresh_values.reset(); + m_fresh_idx = 1; + m_root2value.reset(); + m_asts.reset(); + m_model = 0; + } + + void model_generator::init_model() { + SASSERT(!m_model); + m_model = alloc(proto_model, m_manager, m_context->get_simplifier(), m_context->get_fparams()); + ptr_vector::const_iterator it = m_context->begin_theories(); + ptr_vector::const_iterator end = m_context->end_theories(); + for (; it != end; ++it) { + TRACE("model_generator_bug", tout << "init_model for theory: " << (*it)->get_name() << "\n";); + (*it)->init_model(*this); + } + } + + /** + \brief Create the boolean assignment. + */ + void model_generator::mk_bool_model() { + unsigned sz = m_context->get_num_b_internalized(); + for (unsigned i = 0; i < sz; i++) { + expr * p = m_context->get_b_internalized(i); + if (is_uninterp_const(p) && m_context->is_relevant(p)) { + SASSERT(m_manager.is_bool(p)); + func_decl * d = to_app(p)->get_decl(); + lbool val = m_context->get_assignment(p); + expr * v = val == l_true ? m_manager.mk_true() : m_manager.mk_false(); + m_model->register_decl(d, v); + } + } + } + + /** + \brief Create the mapping root2proc: enode-root -> model_value_proc, and roots. + Store the new model_value_proc at procs. + */ + void model_generator::mk_value_procs(obj_map & root2proc, ptr_vector & roots, + ptr_vector & procs) { + ptr_vector::const_iterator it = m_context->begin_enodes(); + ptr_vector::const_iterator end = m_context->end_enodes(); + for (; it != end; ++it) { + enode * r = *it; + if (r == r->get_root() && m_context->is_relevant(r)) { + roots.push_back(r); + sort * s = m_manager.get_sort(r->get_owner()); + model_value_proc * proc = 0; + if (m_manager.is_bool(s)) { + SASSERT(m_context->get_assignment(r) == l_true || m_context->get_assignment(r) == l_false); + if (m_context->get_assignment(r) == l_true) + proc = alloc(expr_wrapper_proc, m_manager.mk_true()); + else + proc = alloc(expr_wrapper_proc, m_manager.mk_false()); + } + else { + family_id fid = s->get_family_id(); + theory * th = m_context->get_theory(fid); + if (th && th->build_models()) { + if (r->get_th_var(th->get_id()) != null_theory_var) { + proc = th->mk_value(r, *this); + } + else { + TRACE("model_bug", tout << "creating fresh value for #" << r->get_owner_id() << "\n";); + proc = alloc(fresh_value_proc, mk_extra_fresh_value(m_manager.get_sort(r->get_owner()))); + } + } + else { + proc = mk_model_value(r); + } + } + SASSERT(proc); + procs.push_back(proc); + root2proc.insert(r, proc); + } + } + } + + model_value_proc* model_generator::mk_model_value(enode* r) { + SASSERT(r == r->get_root()); + expr * n = r->get_owner(); + if (!m_manager.is_model_value(n)) { + sort * s = m_manager.get_sort(r->get_owner()); + n = m_model->get_fresh_value(s); + CTRACE("model_generator_bug", n == 0, + tout << mk_pp(r->get_owner(), m_manager) << "\nsort:\n" << mk_pp(s, m_manager) << "\n"; + tout << "is_finite: " << m_model->is_finite(s) << "\n";); + } + return alloc(expr_wrapper_proc, to_app(n)); + } + +#define White 0 +#define Grey 1 +#define Black 2 + + static int get_color(source2color const & colors, source const & s) { + int color; + if (colors.find(s, color)) + return color; + return White; + } + + static void set_color(source2color & colors, source const & s, int c) { + colors.insert(s, c); + } + + static void visit_child(source const & s, source2color & colors, svector & todo, bool & visited) { + if (get_color(colors, s) == White) { + todo.push_back(s); + visited = false; + } + } + + bool model_generator::visit_children(source const & src, + ptr_vector const & roots, + obj_map const & root2proc, + source2color & colors, + obj_hashtable & already_traversed, + svector & todo) { + if (src.is_fresh_value()) { + // there is an implicit dependency between a fresh value stub of sort S and the root enodes of sort S that are not associated with fresh values. + sort * s = src.get_value()->get_sort(); + if (already_traversed.contains(s)) + return true; + bool visited = true; + unsigned sz = roots.size(); + for (unsigned i = 0; i < sz; i++) { + enode * r = roots[i]; + if (m_manager.get_sort(r->get_owner()) != s) + continue; + SASSERT(r == r->get_root()); + model_value_proc * proc = 0; + root2proc.find(r, proc); + SASSERT(proc); + if (proc->is_fresh()) + continue; // r is associated with a fresh value... + SASSERT(r == r->get_root()); + TRACE("mg_top_sort", tout << "fresh!" << src.get_value()->get_idx() << " -> #" << r->get_owner_id() << " " << mk_pp(m_manager.get_sort(r->get_owner()), m_manager) << "\n";); + visit_child(source(r), colors, todo, visited); + TRACE("mg_top_sort", tout << "visited: " << visited << ", todo.size(): " << todo.size() << "\n";); + } + already_traversed.insert(s); + return visited; + } + + SASSERT(!src.is_fresh_value()); + + enode * n = src.get_enode(); + SASSERT(n == n->get_root()); + bool visited = true; + model_value_proc * proc = 0; + root2proc.find(n, proc); + SASSERT(proc); + buffer dependencies; + proc->get_dependencies(dependencies); + buffer::const_iterator it = dependencies.begin(); + buffer::const_iterator end = dependencies.end(); + for (; it != end; ++it) { + model_value_dependency const & dep = *it; + visit_child(dep, colors, todo, visited); + TRACE("mg_top_sort", tout << "#" << n->get_owner_id() << " -> "; + if (dep.is_fresh_value()) tout << "fresh!" << dep.get_value()->get_idx(); + else tout << "#" << dep.get_enode()->get_owner_id(); + tout << "\n";); + } + return visited; + } + + void model_generator::process_source(source const & src, + ptr_vector const & roots, + obj_map const & root2proc, + source2color & colors, + obj_hashtable & already_traversed, + svector & todo, + svector & sorted_sources) { + TRACE("mg_top_sort", tout << "process source, is_fresh: " << src.is_fresh_value() << " "; + if (src.is_fresh_value()) tout << "fresh!" << src.get_value()->get_idx(); + else tout << "#" << src.get_enode()->get_owner_id(); + tout << ", todo.size(): " << todo.size() << "\n";); + int color = get_color(colors, src); + SASSERT(color != Grey); + if (color == Black) + return; + SASSERT(color == White); + todo.push_back(src); + while (!todo.empty()) { + source curr = todo.back(); + TRACE("mg_top_sort", tout << "current source, is_fresh: " << curr.is_fresh_value() << " "; + if (curr.is_fresh_value()) tout << "fresh!" << curr.get_value()->get_idx(); + else tout << "#" << curr.get_enode()->get_owner_id(); + tout << ", todo.size(): " << todo.size() << "\n";); + switch (get_color(colors, curr)) { + case White: + set_color(colors, curr, Grey); + visit_children(curr, roots, root2proc, colors, already_traversed, todo); + break; + case Grey: + SASSERT(visit_children(curr, roots, root2proc, colors, already_traversed, todo)); + set_color(colors, curr, Black); + sorted_sources.push_back(curr); + break; + case Black: + todo.pop_back(); + break; + default: + UNREACHABLE(); + } + } + TRACE("mg_top_sort", tout << "END process_source, todo.size(): " << todo.size() << "\n";); + } + + /** + \brief Topological sort of 'sources'. Store result in sorted_sources. + */ + void model_generator::top_sort_sources(ptr_vector const & roots, + obj_map const & root2proc, + svector & sorted_sources) { + + svector todo; + source2color colors; + // The following 'set' of sorts is used to avoid traversing roots looking for enodes of sort S. + // That is, a sort S is in already_traversed, if all enodes of sort S in roots were already traversed. + obj_hashtable already_traversed; + + // topological sort + + // traverse all extra fresh values... + unsigned sz = m_extra_fresh_values.size(); + for (unsigned i = 0; i < sz; i++) { + extra_fresh_value * f = m_extra_fresh_values[i]; + process_source(source(f), roots, root2proc, colors, already_traversed, todo, sorted_sources); + } + + // traverse all enodes that are associated with fresh values... + sz = roots.size(); + for (unsigned i = 0; i < sz; i++) { + enode * r = roots[i]; + model_value_proc * proc = 0; + root2proc.find(r, proc); + SASSERT(proc); + if (!proc->is_fresh()) + continue; + process_source(source(r), roots, root2proc, colors, already_traversed, todo, sorted_sources); + } + + sz = roots.size(); + for (unsigned i = 0; i < sz; i++) { + enode * r = roots[i]; + process_source(source(r), roots, root2proc, colors, already_traversed, todo, sorted_sources); + } + } + + void model_generator::mk_values() { + obj_map root2proc; + ptr_vector roots; + ptr_vector procs; + svector sources; + buffer dependencies; + ptr_vector dependency_values; + mk_value_procs(root2proc, roots, procs); + top_sort_sources(roots, root2proc, sources); + TRACE("sorted_sources", + svector::const_iterator it = sources.begin(); + svector::const_iterator end = sources.end(); + for (; it != end; ++it) { + source const & curr = *it; + if (curr.is_fresh_value()) { + tout << "fresh!" << curr.get_value()->get_idx() << " " << mk_pp(curr.get_value()->get_sort(), m_manager) << "\n"; + } + else { + enode * n = curr.get_enode(); + SASSERT(n->get_root() == n); + sort * s = m_manager.get_sort(n->get_owner()); + tout << "#" << n->get_owner_id() << " " << mk_pp(s, m_manager); + model_value_proc * proc = 0; + root2proc.find(n, proc); + SASSERT(proc); + tout << " is_fresh: " << proc->is_fresh() << "\n"; + } + }); + svector::const_iterator it = sources.begin(); + svector::const_iterator end = sources.end(); + for (; it != end; ++it) { + source const & curr = *it; + + if (curr.is_fresh_value()) { + sort * s = curr.get_value()->get_sort(); + TRACE("model_fresh_bug", tout << "mk fresh!" << curr.get_value()->get_idx() << " : " << mk_pp(s, m_manager) << "\n";); + expr * val = m_model->get_fresh_value(s); + TRACE("model_fresh_bug", tout << "mk fresh!" << curr.get_value()->get_idx() << " := #" << (val == 0 ? UINT_MAX : val->get_id()) << "\n";); + m_asts.push_back(val); + curr.get_value()->set_value(val); + } + else { + enode * n = curr.get_enode(); + SASSERT(n->get_root() == n); + TRACE("mg_top_sort", tout << "#" << n->get_owner_id() << "\n";); + dependencies.reset(); + dependency_values.reset(); + model_value_proc * proc = 0; + VERIFY(root2proc.find(n, proc)); + SASSERT(proc); + proc->get_dependencies(dependencies); + buffer::const_iterator it2 = dependencies.begin(); + buffer::const_iterator end2 = dependencies.end(); + for (; it2 != end2; ++it2) { + model_value_dependency const & d = *it2; + if (d.is_fresh_value()) { + CTRACE("mg_top_sort", !d.get_value()->get_value(), + tout << "#" << n->get_owner_id() << " -> "; + tout << "fresh!" << d.get_value()->get_idx() << "\n";); + SASSERT(d.get_value()->get_value()); + dependency_values.push_back(d.get_value()->get_value()); + } + else { + enode * child = d.get_enode(); + child = child->get_root(); + app * val = 0; + m_root2value.find(child, val); + SASSERT(val); + dependency_values.push_back(val); + } + } + app * val = proc->mk_value(*this, dependency_values); + register_value(val); + m_asts.push_back(val); + m_root2value.insert(n, val); + } + } + std::for_each(procs.begin(), procs.end(), delete_proc()); + std::for_each(m_extra_fresh_values.begin(), m_extra_fresh_values.end(), delete_proc()); + m_extra_fresh_values.reset(); + + // send model + ptr_vector::const_iterator it3 = m_context->begin_enodes(); + ptr_vector::const_iterator end3 = m_context->end_enodes(); + for (; it3 != end3; ++it3) { + enode * n = *it3; + if (is_uninterp_const(n->get_owner()) && m_context->is_relevant(n)) { + func_decl * d = n->get_owner()->get_decl(); + expr * val = get_value(n); + m_model->register_decl(d, val); + } + } + } + + app * model_generator::get_value(enode * n) const { + app * val = 0; + m_root2value.find(n->get_root(), val); + SASSERT(val); + return val; + } + + /** + \brief Return true if the interpretation of the function should be included in the model. + */ + bool model_generator::include_func_interp(func_decl * f) const { + return f->get_family_id() == null_family_id; + } + + /** + \brief Create (partial) interpretation of function symbols. + The "else" is missing. + */ + void model_generator::mk_func_interps() { + unsigned sz = m_context->get_num_e_internalized(); + for (unsigned i = 0; i < sz; i++) { + expr * t = m_context->get_e_internalized(i); + if (!m_context->is_relevant(t)) + continue; + enode * n = m_context->get_enode(t); + unsigned num_args = n->get_num_args(); + func_decl * f = n->get_decl(); + if (num_args > 0 && n->get_cg() == n && include_func_interp(f)) { + ptr_buffer args; + expr * result = get_value(n); + SASSERT(result); + for (unsigned j = 0; j < num_args; j++) { + app * arg = get_value(n->get_arg(j)); + SASSERT(arg); + args.push_back(arg); + } + func_interp * fi = m_model->get_func_interp(f); + if (fi == 0) { + fi = alloc(func_interp, m_manager, f->get_arity()); + m_model->register_decl(f, fi); + } + SASSERT(m_model->has_interpretation(f)); + SASSERT(m_model->get_func_interp(f) == fi); + // The entry must be new because n->get_cg() == n + TRACE("func_interp_bug", + tout << "insert new entry for:\n" << mk_ismt2_pp(n->get_owner(), m_manager) << "\nargs: "; + for (unsigned i = 0; i < num_args; i++) { + tout << "#" << n->get_arg(i)->get_owner_id() << " "; + } + tout << "\n"; + tout << "value: #" << n->get_owner_id() << "\n" << mk_ismt2_pp(result, m_manager) << "\n";); + if (m_context->get_last_search_failure() == smt::THEORY) { + // if the theory solvers are incomplete, then we cannot assume the e-graph is close under congruence + if (fi->get_entry(args.c_ptr()) == 0) + fi->insert_new_entry(args.c_ptr(), result); + } + else { + fi->insert_new_entry(args.c_ptr(), result); + } + } + } + } + + extra_fresh_value * model_generator::mk_extra_fresh_value(sort * s) { + SASSERT(s->is_infinite()); + extra_fresh_value * r = alloc(extra_fresh_value, s, m_fresh_idx); + m_fresh_idx++; + m_extra_fresh_values.push_back(r); + return r; + } + + expr * model_generator::get_some_value(sort * s) { + SASSERT(m_model); + return m_model->get_some_value(s); + } + + void model_generator::register_value(expr * val) { + SASSERT(m_model); + m_model->register_value(val); + } + + void model_generator::finalize_theory_models() { + ptr_vector::const_iterator it = m_context->begin_theories(); + ptr_vector::const_iterator end = m_context->end_theories(); + for (; it != end; ++it) + (*it)->finalize_model(*this); + } + + void model_generator::register_existing_model_values() { + ptr_vector::const_iterator it = m_context->begin_enodes(); + ptr_vector::const_iterator end = m_context->end_enodes(); + for (; it != end; ++it) { + enode * r = *it; + if (r == r->get_root() && m_context->is_relevant(r)) { + expr * n = r->get_owner(); + if (m_manager.is_model_value(n)) { + register_value(n); + } + } + } + } + + void model_generator::register_factory(value_factory * f) { + m_model->register_factory(f); + } + + void model_generator::register_macros() { + unsigned num = m_context->get_num_macros(); + TRACE("register_macros", tout << "num. macros: " << num << "\n";); + expr_ref v(m_manager); + for (unsigned i = 0; i < num; i++) { + func_decl * f = m_context->get_macro_interpretation(i, v); + func_interp * fi = alloc(func_interp, m_manager, f->get_arity()); + fi->set_else(v); + TRACE("register_macros", tout << f->get_name() << "\n" << mk_pp(v, m_manager) << "\n";); + m_model->register_decl(f, fi); + } + } + + /** + \brief Auxiliary functor for method register_indirect_elim_decls. + */ + class mk_interp_proc { + context & m_context; + proto_model & m_model; + public: + mk_interp_proc(context & ctx, proto_model & m): + m_context(ctx), + m_model(m) { + } + + void operator()(var * n) { + } + + void operator()(app * n) { + if (!is_uninterp(n)) + return; // n is interpreted + func_decl * d = n->get_decl(); + if (m_model.has_interpretation(d)) + return; // declaration already has an interpretation. + if (n->get_num_args() == 0 && m_context.is_subst(n) != 0) + return; // an interpretation will be generated for this variable using the evaluator. + if (n->get_num_args() == 0) { + sort * r = d->get_range(); + expr * v = m_model.get_some_value(r); + m_model.register_decl(d, v); + } + else { + func_interp * fi = alloc(func_interp, m_context.get_manager(), d->get_arity()); + m_model.register_decl(d, fi); + } + } + + void operator()(quantifier * n) { + } + + }; + + /** + \brief Generate an interpretation for variables that were eliminated indirectly. + When a variable is eliminated by substitution and it does not occur anywhere, then + its definition may contain declarations that do not occur anywhere else. + This method will assign an arbitrary interpretation for these declarations. + + Example: consider the formula + + (= x (f y)) + + This formula is satisfiable. If the solver is used during preprocessing step, + this formula is reduced to "true", and the substitution set contains the entry (x -> (f y)). + The declarations f and y will not have an interpretation. This method will traverse the + definition of each eliminated variable, and generate an arbitrary interpretations for + declarations that do not have one yet. + */ + void model_generator::register_indirect_elim_decls() { + expr_mark visited; + mk_interp_proc proc(*m_context, *m_model); + ptr_vector::const_iterator it = m_context->begin_subst_vars(); + ptr_vector::const_iterator end = m_context->end_subst_vars(); + for (; it != end; ++it) { + app * var = *it; + if (var->get_num_args() > 0) + continue; + expr * subst = m_context->get_subst(var); + for_each_expr(proc, visited, subst); + } + } + + void model_generator::register_subst_vars() { + ptr_vector ordered_subst_vars; + m_context->get_ordered_subst_vars(ordered_subst_vars); + ptr_vector::const_iterator it = ordered_subst_vars.begin(); + ptr_vector::const_iterator end = ordered_subst_vars.end(); + for (; it != end; ++it) { + app * var = *it; + TRACE("model_subst_vars", tout << "processing: " << mk_pp(var, m_manager) << "\n";); + if (var->get_num_args() > 0) { + TRACE("model_subst_vars", tout << "not a constant...\n";); + continue; + } + expr * subst = m_context->get_subst(var); + if (subst == 0) { + TRACE("model_subst_vars", tout << "no definition...\n";); + continue; + } + TRACE("model_subst_vars", tout << "definition: " << mk_pp(subst, m_manager) << "\n";); + expr_ref r(m_manager); + m_model->eval(subst, r); + TRACE("model_subst_vars", tout << "result: " << mk_pp(r, m_manager) << "\n";); + m_model->register_decl(var->get_decl(), r); + } + } + + proto_model * model_generator::mk_model() { + SASSERT(!m_model); + TRACE("func_interp_bug", m_context->display(tout);); + init_model(); + register_existing_model_values(); + mk_bool_model(); + mk_values(); + mk_func_interps(); + finalize_theory_models(); + register_macros(); + TRACE("model_subst_vars", + tout << "substitution vars:\n"; + ptr_vector::const_iterator it = m_context->begin_subst_vars(); + ptr_vector::const_iterator end = m_context->end_subst_vars(); + for (; it != end; ++it) { + app * var = *it; + tout << mk_pp(var, m_manager) << "\n"; + if (var->get_num_args() > 0) + continue; + expr * subst = m_context->get_subst(var); + if (subst == 0) + continue; + tout << "-> " << mk_bounded_pp(subst, m_manager, 10) << "\n"; + }); + register_indirect_elim_decls(); + register_subst_vars(); + return m_model; + } + +}; diff --git a/lib/smt_model_generator.h b/lib/smt_model_generator.h new file mode 100644 index 000000000..853fa4b07 --- /dev/null +++ b/lib/smt_model_generator.h @@ -0,0 +1,229 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_model_generator.h + +Abstract: + + The model generator builds a (partial) model for the ground + formulas in the logical context. + + The model finder (smt_model_finder.h) tries to extend the + partial model to satisfy the quantifiers. Main invariant: + the new model still satisfies the ground formulas. + + The model checker (smt_model_checker.h) checks whether the (new) + model satisfies the quantifiers. If it doesn't, new instances are + added. + +Author: + + Leonardo de Moura (leonardo) 2008-10-29. + +Revision History: + +--*/ +#ifndef _SMT_MODEL_GENERATOR_H_ +#define _SMT_MODEL_GENERATOR_H_ + +#include"ast.h" +#include"smt_types.h" +#include"obj_hashtable.h" +#include"map.h" + +class value_factory; +class proto_model; + +namespace smt { + + // ----------------------------- + // + // This module builds an interpretation for each relevant expression in the logical context. + // + // 1) The interpretation of boolean constants is their truth value in the logical context. + // + // 2) The interpretation of expressions associated with enodes is built using functors (model_value_proc). + // Theories as arrays and datatypes many need the interpretation of some expressions to be built before the interpretation of others. + // We say this is a dependency. Moreover, some values must be fresh. That is, they should be different + // from all other values associated with enodes of a given sort. For example, the array theory + // uses fresh values to make sure that some array constants are different from each other. + // + // So a dependency for building the interpretation of an enode N can be: + // a) a fresh value (stub) of sort S: it must be built after the interpretation of all enodes of sort S were assigned. + // + // b) an enode N': the interpretation of N' must be built before the interpretation of N. + // + // We say a 'source' is an fresh value or an enode. Note that every dependency is a source, + // but not every source is a dependency. + // + // We use these dependencies to sort (using a topological sort) the sources. The sorted 'sources' specify the + // order the interpretations will be built. + // + // Assumptions regarding dependencies: + // + // - They are acyclic. + // + // - All dependencies are marked as relevant. + // + // - A fresh value stub of sort S depends (implicitly) on all enodes of sort S (that are not associated with fresh values). + // So an enode of sort S may not have a dependency of sort S. + // + // ------------------------------ + + /** + \brief Stub for extra fresh value. + */ + struct extra_fresh_value { + sort * m_sort; + unsigned m_idx; + expr * m_value; + public: + extra_fresh_value(sort * s, unsigned idx):m_sort(s), m_idx(idx), m_value(0) {} + sort * get_sort() const { return m_sort; } + unsigned get_idx() const { return m_idx; } + void set_value(expr * n) { SASSERT(m_value == 0); m_value = n; } + expr * get_value() const { return m_value; } + }; + + /** + \brief Theories such as arrays and datatypes may need some values to be already available when + building a value. We say this a dependency. Object of this class are used to track such dependencies. + + Example: to build the value (cons 10 nil), the values 10 and nil should be already available. + */ + class model_value_dependency { + bool m_fresh; //!< True if the dependency is a new fresh value; + union { + enode * m_enode; //!< When m_fresh == false, contains an enode depedency. + extra_fresh_value * m_value; //!< When m_fresh == true, contains the sort of the fresh value + }; + public: + model_value_dependency():m_fresh(true), m_value(0) {} + model_value_dependency(enode * n):m_fresh(false), m_enode(n->get_root()) {} + model_value_dependency(extra_fresh_value * v):m_fresh(true), m_value(v) {} + bool is_fresh_value() const { return m_fresh; } + enode * get_enode() const { SASSERT(!is_fresh_value()); return m_enode; } + extra_fresh_value * get_value() const { SASSERT(is_fresh_value()); return m_value; } + }; + + typedef model_value_dependency source; + + struct source_hash_proc { + unsigned operator()(source const & s) const { return s.is_fresh_value() ? hash_u_u(17, s.get_value()->get_idx()) : hash_u_u(13, s.get_enode()->get_owner_id()); } + }; + + struct source_eq_proc { + bool operator()(source const & s1, source const & s2) const { + if (s1.is_fresh_value() != s2.is_fresh_value()) + return false; + if (s1.is_fresh_value()) + return s1.get_value()->get_idx() == s2.get_value()->get_idx(); + else + return s1.get_enode() == s2.get_enode(); + } + }; + + typedef map source2color; + + /** + \brief Model value builder. This functor is used to specify the dependencies + needed to build a value, and to build the actual value. + + */ + class model_value_proc { + public: + virtual ~model_value_proc() {} + /** + \brief Fill result with the dependencies of this functor. + That is, to invoke mk_value, the dependencies in result must be constructed. + */ + virtual void get_dependencies(buffer & result) {} + /** + \brief The array values has size equal to the size of the argument \c result in get_dependencies. + It contain the values built for the dependencies. + */ + virtual app * mk_value(model_generator & m, ptr_vector & values) = 0; + /** + \brief Return true if it is associated with a fresh value. + */ + virtual bool is_fresh() const { return false; } + }; + + /** + \brief Simple model_value_proc. It has no dependencies, and + just returns a given expression. + */ + class expr_wrapper_proc : public model_value_proc { + app * m_value; + public: + expr_wrapper_proc(app * v):m_value(v) {} + virtual app * mk_value(model_generator & m, ptr_vector & values) { return m_value; } + }; + + class fresh_value_proc : public model_value_proc { + extra_fresh_value * m_value; + public: + fresh_value_proc(extra_fresh_value * v):m_value(v) {} + virtual void get_dependencies(buffer & result) { result.push_back(m_value); } + virtual app * mk_value(model_generator & m, ptr_vector & values) { return to_app(values[0]); } + virtual bool is_fresh() const { return true; } + }; + + /** + \brief Auxiliary class used during model generation. + */ + class model_generator { + ast_manager & m_manager; + context * m_context; + ptr_vector m_extra_fresh_values; + unsigned m_fresh_idx; + obj_map m_root2value; + ast_ref_vector m_asts; + proto_model * m_model; + + void init_model(); + void mk_bool_model(); + void mk_value_procs(obj_map & root2proc, ptr_vector & roots, ptr_vector & procs); + void mk_values(); + bool include_func_interp(func_decl * f) const; + void mk_func_interps(); + void finalize_theory_models(); + void display(std::ostream & out); + void register_existing_model_values(); + void register_macros(); + void register_indirect_elim_decls(); + void register_subst_vars(); + + bool visit_children(source const & src, ptr_vector const & roots, obj_map const & root2proc, + source2color & colors, obj_hashtable & already_traversed, svector & todo); + + void process_source(source const & src, ptr_vector const & roots, obj_map const & root2proc, + source2color & colors, obj_hashtable & already_traversed, svector & todo, svector & sorted_sources); + + void top_sort_sources(ptr_vector const & roots, obj_map const & root2proc, svector & sorted_sources); + + public: + model_generator(ast_manager & m); + ~model_generator(); + + void reset(); + void set_context(context * c) { SASSERT(m_context == 0); m_context = c; } + + void register_factory(value_factory * f); + extra_fresh_value * mk_extra_fresh_value(sort * s); + model_value_proc * mk_model_value(enode* r); + expr * get_some_value(sort * s); + proto_model & get_model() { SASSERT(m_model); return *m_model; } + void register_value(expr * val); + ast_manager & get_manager() { return m_manager; } + proto_model * mk_model(); + + obj_map const & get_root2value() const { return m_root2value; } + app * get_value(enode * n) const; + }; +}; + +#endif /* _SMT_MODEL_GENERATOR_H_ */ + diff --git a/lib/smt_params.cpp b/lib/smt_params.cpp new file mode 100644 index 000000000..0c4a0c844 --- /dev/null +++ b/lib/smt_params.cpp @@ -0,0 +1,108 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_params.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-20. + +Revision History: + +--*/ +#include"smt_params.h" +#include"trace.h" + +void smt_params::register_params(ini_params & p) { + dyn_ack_params::register_params(p); + qi_params::register_params(p); + theory_arith_params::register_params(p); + theory_array_params::register_params(p); + theory_bv_params::register_params(p); + theory_datatype_params::register_params(p); + + p.register_bool_param("CHECK_PROOF", m_check_proof); + p.register_bool_param("DISPLAY_PROOF", m_display_proof); + p.register_bool_param("DISPLAY_DOT_PROOF", m_display_dot_proof); + p.register_bool_param("DISPLAY_UNSAT_CORE", m_display_unsat_core); + p.register_bool_param("INTERNALIZER_NNF", m_internalizer_nnf); + p.register_bool_param("EQ_PROPAGATION", m_eq_propagation); + p.register_bool_param("BIN_CLAUSES", m_binary_clause_opt); + p.register_unsigned_param("RELEVANCY", m_relevancy_lvl, "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", true); + p.register_bool_param("RELEVANCY_LEMMA", m_relevancy_lemma, "true if lemmas are used to propagate relevancy"); + p.register_unsigned_param("RANDOM_SEED", m_random_seed, "random seed for Z3"); + p.register_percentage_param("RANDOM_CASE_SPLIT_FREQ", m_random_var_freq, "frequency of random case splits"); + p.register_int_param("PHASE_SELECTION", 0, 6, reinterpret_cast(m_phase_selection), "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"); + p.register_bool_param("MINIMIZE_LEMMAS", m_minimize_lemmas, "enable/disable lemma minimization algorithm"); + p.register_unsigned_param("MAX_CONFLICTS", m_max_conflicts, "maximum number of conflicts"); + + p.register_unsigned_param("RECENT_LEMMA_THRESHOLD", m_recent_lemmas_size); + p.register_unsigned_param("TICK", m_tick); + + PRIVATE_PARAMS({ + p.register_bool_param("THEORY_RESOLVE", m_theory_resolve, "Apply theory resolution to produce auxiliary conflict clauses", true); + }); + + p.register_int_param("RESTART_STRATEGY", 0, 4, reinterpret_cast(m_restart_strategy), "0 - geometric, 1 - inner-outer-geometric, 2 - luby, 3 - fixed, 4 - arithmetic"); + p.register_unsigned_param("RESTART_INITIAL", m_restart_initial, + "inital restart frequency in number of conflicts, it is also the unit for the luby sequence"); + p.register_double_param("RESTART_FACTOR", m_restart_factor, "when using geometric (or inner-outer-geometric) progression of restarts, it specifies the constant used to multiply the currect restart threshold"); + p.register_bool_param("RESTART_ADAPTIVE", m_restart_adaptive, "disable restarts based on the search 'agility'"); + p.register_percentage_param("RESTART_AGILITY_THRESHOLD", m_restart_agility_threshold); + + p.register_int_param("LEMMA_GC_STRATEGY", 0, 2, reinterpret_cast(m_lemma_gc_strategy), "0 - fixed, 1 - geometric, 2 - at every restart"); + p.register_bool_param("LEMMA_GC_HALF", m_lemma_gc_half, "true for simple gc algorithm (delete approx. half of the clauses)"); + p.register_unsigned_param("LEMMA_GC_INITIAL", m_lemma_gc_initial, "lemma initial gc frequency (in number of conflicts), used by fixed or geometric strategies"); + p.register_double_param("LEMMA_GC_FACTOR", m_lemma_gc_factor, "used by geometric strategy"); + p.register_unsigned_param("LEMMA_GC_NEW_OLD_RATIO", m_new_old_ratio); + p.register_unsigned_param("LEMMA_GC_NEW_CLAUSE_ACTIVITY", m_new_clause_activity); + p.register_unsigned_param("LEMMA_GC_OLD_CLAUSE_ACTIVITY", m_old_clause_activity); + p.register_unsigned_param("LEMMA_GC_NEW_CLAUSE_RELEVANCY", m_new_clause_relevancy); + p.register_unsigned_param("LEMMA_GC_OLD_CLAUSE_RELEVANCY", m_old_clause_activity); + + p.register_bool_param("SIMPLIFY_CLAUSES", m_simplify_clauses); + + p.register_int_param("RANDOM_INITIAL_ACTIVITY", 0, 2, reinterpret_cast(m_random_initial_activity)); + + PRIVATE_PARAMS({ + + p.register_double_param("INV_DECAY", m_inv_decay); + p.register_unsigned_param("PHASE_CACHING_ON_DURATION", m_phase_caching_on); + p.register_unsigned_param("PHASE_CACHING_OFF_DURATION", m_phase_caching_off); + }); + + p.register_bool_param("SMTLIB_DUMP_LEMMAS", m_smtlib_dump_lemmas); + p.register_string_param("SMTLIB_LOGIC", m_smtlib_logic, "Name used for the :logic field when generating SMT-LIB benchmarks"); + p.register_bool_param("DISPLAY_FEATURES", m_display_features); + + p.register_bool_param("NEW_CORE2TH_EQ", m_new_core2th_eq); + p.register_bool_param("EMATCHING", m_ematching, "E-Matching based quantifier instantiation"); + + p.register_bool_param("PROFILE_RES_SUB", m_profile_res_sub); +#ifndef _EXTERNAL_RELEASE + p.register_bool_param("DISPLAY_BOOL_VAR2EXPR", m_display_bool_var2expr); + p.register_bool_param("DISPLAY_LL_BOOL_VAR2EXPR", m_display_ll_bool_var2expr); + p.register_bool_param("ABORT_AFTER_PREPROC", m_abort_after_preproc, "abort after preprocessing step, this flag is only useful for debugging purposes"); + p.register_bool_param("DISPLAY_INSTALLED_THEORIES", m_display_installed_theories, "display theories installed at smt::context", true); +#endif + p.register_int_param("CASE_SPLIT", 0, 5, reinterpret_cast(m_case_split_strategy), "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"); + p.register_unsigned_param("REL_CASE_SPLIT_ORDER", 0, 2, m_rel_case_split_order, "structural (relevancy) splitting order: 0 - left-to-right (default), 1 - random, 2 - right-to-left"); + p.register_bool_param("LOOKAHEAD_DISEQ", m_lookahead_diseq); + + p.register_bool_param("DELAY_UNITS", m_delay_units); + p.register_unsigned_param("DELAY_UNITS_THRESHOLD", m_delay_units_threshold); + + p.register_bool_param("MODEL", m_model, "enable/disable model construction", true); + p.register_bool_param("MODEL_VALIDATE", m_model_validate, "validate the model", true); + p.register_bool_param("MODEL_ON_TIMEOUT", m_model_on_timeout, "after hitting soft-timeout or memory high watermark, generate a candidate model", true); + p.register_bool_param("MODEL_ON_FINAL_CHECK", m_model_on_final_check, "display candidate model (in the standard output) whenever Z3 hits a final check", true); + + p.register_unsigned_param("PROGRESS_SAMPLING_FREQ", m_progress_sampling_freq, "frequency for progress output in miliseconds"); +} + diff --git a/lib/smt_params.h b/lib/smt_params.h new file mode 100644 index 000000000..20a150c64 --- /dev/null +++ b/lib/smt_params.h @@ -0,0 +1,257 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_params.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-20. + +Revision History: + +--*/ +#ifndef _SMT_PARAMS_H_ +#define _SMT_PARAMS_H_ + +#include"ini_file.h" +#include"dyn_ack_params.h" +#include"qi_params.h" +#include"theory_arith_params.h" +#include"theory_array_params.h" +#include"theory_bv_params.h" +#include"theory_datatype_params.h" + +enum phase_selection { + PS_ALWAYS_FALSE, + PS_ALWAYS_TRUE, + PS_CACHING, + PS_CACHING_CONSERVATIVE, + PS_CACHING_CONSERVATIVE2, // similar to the previous one, but alternated default config from time to time. + PS_RANDOM, + PS_OCCURRENCE +}; + +enum restart_strategy { + RS_GEOMETRIC, + RS_IN_OUT_GEOMETRIC, + RS_LUBY, + RS_FIXED, + RS_ARITHMETIC +}; + +enum lemma_gc_strategy { + LGC_FIXED, + LGC_GEOMETRIC, + LGC_AT_RESTART +}; + +enum initial_activity { + IA_ZERO, // initialized with 0 + IA_RANDOM_WHEN_SEARCHING, // random when searching + IA_RANDOM // always random +}; + +enum case_split_strategy { + CS_ACTIVITY, // case split based on activity + CS_ACTIVITY_DELAY_NEW, // case split based on activity but delay new case splits created during the search + CS_ACTIVITY_WITH_CACHE, // case split based on activity and cache the activity + CS_RELEVANCY, // case split based on relevancy + CS_RELEVANCY_ACTIVITY, // case split based on relevancy and activity + CS_RELEVANCY_GOAL, // based on relevancy and the current goal +}; + +struct smt_params : public dyn_ack_params, public qi_params, public theory_arith_params, public theory_array_params, public theory_bv_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_internalizer_nnf; + 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_simplify_clauses; + unsigned m_tick; + bool m_display_features; + bool m_new_core2th_eq; + bool m_ematching; + + // ----------------------------------- + // + // Case split strategy + // + // ----------------------------------- + case_split_strategy m_case_split_strategy; + unsigned m_rel_case_split_order; + bool m_lookahead_diseq; + + // ----------------------------------- + // + // Delay units... + // + // ----------------------------------- + bool m_delay_units; + unsigned m_delay_units_threshold; + + // ----------------------------------- + // + // Conflict resolution + // + // ----------------------------------- + bool m_theory_resolve; + + // ----------------------------------- + // + // 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; + + // ----------------------------------- + // + // 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 + + // ----------------------------------- + // + // SMT-LIB (debug) pretty printer + // + // ----------------------------------- + bool m_smtlib_dump_lemmas; + std::string m_smtlib_logic; + + // ----------------------------------- + // + // Statistics for Profiling + // + // ----------------------------------- + bool m_profile_res_sub; + bool m_display_bool_var2expr; + bool m_display_ll_bool_var2expr; + bool m_abort_after_preproc; + + // ----------------------------------- + // + // Model generation + // + // ----------------------------------- + bool m_model; + bool m_model_validate; + bool m_model_on_timeout; + bool m_model_on_final_check; + + // ----------------------------------- + // + // Progress sampling + // + // ----------------------------------- + unsigned m_progress_sampling_freq; + + // ----------------------------------- + // + // Debugging goodies + // + // ----------------------------------- + bool m_display_installed_theories; + + smt_params(): + m_display_proof(false), + m_display_dot_proof(false), + m_display_unsat_core(false), + m_check_proof(false), + m_internalizer_nnf(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(IA_RANDOM_WHEN_SEARCHING), + m_phase_selection(PS_CACHING_CONSERVATIVE), + m_phase_caching_on(400), + m_phase_caching_off(100), + m_minimize_lemmas(true), + m_max_conflicts(UINT_MAX), + m_simplify_clauses(true), + m_tick(1000), + m_display_features(false), + m_new_core2th_eq(true), + m_ematching(true), + m_case_split_strategy(CS_ACTIVITY_DELAY_NEW), + m_rel_case_split_order(0), + m_lookahead_diseq(false), + m_delay_units(false), + m_delay_units_threshold(32), + m_theory_resolve(false), + m_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(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_smtlib_logic("AUFLIA"), + m_profile_res_sub(false), + m_display_bool_var2expr(false), + m_display_ll_bool_var2expr(false), + m_abort_after_preproc(false), + m_model(true), + m_model_validate(false), + m_model_on_timeout(false), + m_model_on_final_check(false), + m_progress_sampling_freq(0), + m_display_installed_theories(false) { + } + + void register_params(ini_params & p); +}; + +#endif /* _SMT_PARAMS_H_ */ + diff --git a/lib/smt_quantifier.cpp b/lib/smt_quantifier.cpp new file mode 100644 index 000000000..b6ed70180 --- /dev/null +++ b/lib/smt_quantifier.cpp @@ -0,0 +1,609 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + smt_quantifier.cpp + +Abstract: + + Quantifier reasoning support for smt::context. + +Author: + + Leonardo de Moura (leonardo) 2012-02-16. + +Revision History: + +--*/ +#include"smt_quantifier.h" +#include"smt_context.h" +#include"smt_quantifier_stat.h" +#include"smt_model_finder.h" +#include"smt_model_checker.h" +#include"smt_quick_checker.h" +#include"mam.h" +#include"qi_queue.h" +#include"ast_smt2_pp.h" + +namespace smt { + + quantifier_manager_plugin * mk_default_plugin(); + + struct quantifier_manager::imp { + quantifier_manager & m_wrapper; + context & m_context; + front_end_params & m_params; + qi_queue m_qi_queue; + obj_map m_quantifier_stat; + quantifier_stat_gen m_qstat_gen; + ptr_vector m_quantifiers; + scoped_ptr m_plugin; + unsigned m_num_instances; + + imp(quantifier_manager & wrapper, context & ctx, front_end_params & p, quantifier_manager_plugin * plugin): + m_wrapper(wrapper), + m_context(ctx), + m_params(p), + m_qi_queue(m_wrapper, ctx, p, p.m_trace_stream), + m_qstat_gen(ctx.get_manager(), ctx.get_region()), + m_plugin(plugin) { + m_num_instances = 0; + m_qi_queue.setup(); + } + + quantifier_stat * get_stat(quantifier * q) const { + return m_quantifier_stat.find(q); + } + + unsigned get_generation(quantifier * q) const { + return get_stat(q)->get_generation(); + } + + void add(quantifier * q, unsigned generation) { + quantifier_stat * stat = m_qstat_gen(q, generation); + m_quantifier_stat.insert(q, stat); + m_quantifiers.push_back(q); + m_plugin->add(q); + } + + void display_stats(std::ostream & out, quantifier * q) { + quantifier_stat * s = get_stat(q); + unsigned num_instances = s->get_num_instances(); + unsigned max_generation = s->get_max_generation(); + float max_cost = s->get_max_cost(); + if (num_instances > 0) { + out << "[quantifier_instances] "; + out.width(10); + out << q->get_qid().str().c_str() << " : "; + out.width(6); + out << num_instances << " : "; + out.width(3); + out << max_generation << " : " << max_cost << "\n"; + } + } + + void del(quantifier * q) { + if (m_params.m_qi_profile) { + display_stats(verbose_stream(), q); + } + m_quantifiers.pop_back(); + m_quantifier_stat.erase(q); + } + + bool empty() const { + return m_quantifiers.empty(); + } + + bool is_shared(enode * n) const { + return m_plugin->is_shared(n); + } + + bool add_instance(quantifier * q, app * pat, + unsigned num_bindings, + enode * const * bindings, + unsigned max_generation, + unsigned min_top_generation, + unsigned max_top_generation, + ptr_vector & used_enodes) { + max_generation = std::max(max_generation, get_generation(q)); + if (m_num_instances > m_params.m_qi_max_instances) + return false; + get_stat(q)->update_max_generation(max_generation); + fingerprint * f = m_context.add_fingerprint(q, q->get_id(), num_bindings, bindings); + if (f) { + if (m_params.m_trace_stream != NULL) { + std::ostream & out = *m_params.m_trace_stream; + out << "[new-match] " << static_cast(f) << " #" << q->get_id(); + for (unsigned i = 0; i < num_bindings; i++) { + // I don't want to use mk_pp because it creates expressions for pretty printing. + // This nasty side-effect may change the behavior of Z3. + out << " #" << bindings[i]->get_owner_id(); + } + out << " ;"; + ptr_vector::const_iterator it = used_enodes.begin(); + ptr_vector::const_iterator end = used_enodes.end(); + for (; it != end; ++it) + out << " #" << (*it)->get_owner_id(); + out << "\n"; + } + m_qi_queue.insert(f, pat, max_generation, min_top_generation, max_top_generation); // TODO + m_num_instances++; + return true; + } + return false; + } + + void init_search_eh() { + m_num_instances = 0; + ptr_vector::iterator it2 = m_quantifiers.begin(); + ptr_vector::iterator end2 = m_quantifiers.end(); + for (; it2 != end2; ++it2) { + quantifier * q = *it2; + get_stat(q)->reset_num_instances_curr_search(); + } + m_qi_queue.init_search_eh(); + m_plugin->init_search_eh(); + } + + void assign_eh(quantifier * q) { + m_plugin->assign_eh(q); + } + + void add_eq_eh(enode * n1, enode * n2) { + m_plugin->add_eq_eh(n1, n2); + } + + void relevant_eh(enode * n) { + m_plugin->relevant_eh(n); + } + + void restart_eh() { + m_plugin->restart_eh(); + } + + void push() { + m_plugin->push(); + m_qi_queue.push_scope(); + } + + void pop(unsigned num_scopes) { + m_plugin->pop(num_scopes); + m_qi_queue.pop_scope(num_scopes); + } + + bool can_propagate() { + return m_qi_queue.has_work() || m_plugin->can_propagate(); + } + + void propagate() { + m_plugin->propagate(); + m_qi_queue.instantiate(); + } + + bool quick_check_quantifiers() { + if (m_params.m_qi_quick_checker == MC_NO) + return true; + if (m_quantifiers.empty()) + return true; + IF_VERBOSE(10, verbose_stream() << "quick checking quantifiers (unsat)...\n";); + quick_checker mc(m_context); + bool result = true; + ptr_vector::const_iterator it = m_quantifiers.begin(); + ptr_vector::const_iterator end = m_quantifiers.end(); + for (; it != end; ++it) + if (m_context.is_relevant(*it) && m_context.get_assignment(*it) == l_true && mc.instantiate_unsat(*it)) + result = false; + if (m_params.m_qi_quick_checker == MC_UNSAT || !result) { + m_qi_queue.instantiate(); + return result; + } + // MC_NO_SAT is too expensive (it creates too many irrelevant instances). + // we should use MBQI=true instead. + IF_VERBOSE(10, verbose_stream() << "quick checking quantifiers (not sat)...\n";); + it = m_quantifiers.begin(); + for (; it != end; ++it) + if (m_context.is_relevant(*it) && m_context.get_assignment(*it) == l_true && mc.instantiate_not_sat(*it)) + result = false; + m_qi_queue.instantiate(); + return result; + } + + final_check_status final_check_eh(bool full) { + if (full) { + IF_VERBOSE(100, verbose_stream() << "final check 'quantifiers'...\n";); + final_check_status result = m_qi_queue.final_check_eh() ? FC_DONE : FC_CONTINUE; + final_check_status presult = m_plugin->final_check_eh(full); + if (presult != FC_DONE) + result = presult; + if (m_context.can_propagate()) + result = FC_CONTINUE; + if (result == FC_DONE && !m_params.m_qi_lazy_quick_checker && !quick_check_quantifiers()) + result = FC_CONTINUE; + return result; + } + else { + return m_plugin->final_check_eh(false); + } + } + + check_model_result check_model(proto_model * m, obj_map const & root2value) { + if (empty()) + return SAT; + return m_plugin->check_model(m, root2value); + } + + void set_cancel(bool f) { + m_plugin->set_cancel(f); + } + + }; + + quantifier_manager::quantifier_manager(context & ctx, front_end_params & fp, params_ref const & p) { + m_imp = alloc(imp, *this, ctx, fp, mk_default_plugin()); + m_imp->m_plugin->set_manager(*this); + } + + quantifier_manager::~quantifier_manager() { + dealloc(m_imp); + } + + context & quantifier_manager::kernel() const { + return m_imp->m_context; + } + + void quantifier_manager::set_plugin(quantifier_manager_plugin * plugin) { + m_imp->m_plugin = plugin; + plugin->set_manager(*this); + } + + void quantifier_manager::add(quantifier * q, unsigned generation) { + m_imp->add(q, generation); + } + + void quantifier_manager::del(quantifier * q) { + m_imp->del(q); + } + + bool quantifier_manager::empty() const { + return m_imp->empty(); + } + + bool quantifier_manager::is_shared(enode * n) const { + return m_imp->is_shared(n); + } + + quantifier_stat * quantifier_manager::get_stat(quantifier * q) const { + return m_imp->get_stat(q); + } + + unsigned quantifier_manager::get_generation(quantifier * q) const { + return m_imp->get_generation(q); + } + + bool quantifier_manager::add_instance(quantifier * q, app * pat, + unsigned num_bindings, + enode * const * bindings, + unsigned max_generation, + unsigned min_top_generation, + unsigned max_top_generation, + ptr_vector & used_enodes) { + return m_imp->add_instance(q, pat, num_bindings, bindings, max_generation, min_top_generation, max_generation, used_enodes); + } + + bool quantifier_manager::add_instance(quantifier * q, unsigned num_bindings, enode * const * bindings, unsigned generation) { + ptr_vector tmp; + return add_instance(q, 0, num_bindings, bindings, generation, generation, generation, tmp); + } + + void quantifier_manager::init_search_eh() { + m_imp->init_search_eh(); + } + + void quantifier_manager::assign_eh(quantifier * q) { + m_imp->assign_eh(q); + } + + void quantifier_manager::add_eq_eh(enode * n1, enode * n2) { + m_imp->add_eq_eh(n1, n2); + } + + void quantifier_manager::relevant_eh(enode * n) { + m_imp->relevant_eh(n); + } + + final_check_status quantifier_manager::final_check_eh(bool full) { + return m_imp->final_check_eh(full); + } + + void quantifier_manager::restart_eh() { + m_imp->restart_eh(); + } + + bool quantifier_manager::can_propagate() const { + return m_imp->can_propagate(); + } + + void quantifier_manager::propagate() { + m_imp->propagate(); + } + + bool quantifier_manager::model_based() const { + return m_imp->m_plugin->model_based(); + } + + void quantifier_manager::adjust_model(proto_model * m) { + m_imp->m_plugin->adjust_model(m); + } + + quantifier_manager::check_model_result quantifier_manager::check_model(proto_model * m, obj_map const & root2value) { + return m_imp->check_model(m, root2value); + } + + void quantifier_manager::push() { + m_imp->push(); + } + + void quantifier_manager::pop(unsigned num_scopes) { + m_imp->pop(num_scopes); + } + + void quantifier_manager::reset() { + #pragma omp critical (quantifier_manager) + { + context & ctx = m_imp->m_context; + front_end_params & p = m_imp->m_params; + quantifier_manager_plugin * plugin = m_imp->m_plugin->mk_fresh(); + dealloc(m_imp); + m_imp = alloc(imp, *this, ctx, p, plugin); + plugin->set_manager(*this); + } + } + + void quantifier_manager::set_cancel(bool f) { + #pragma omp critical (quantifier_manager) + { + m_imp->set_cancel(f); + } + } + + void quantifier_manager::display(std::ostream & out) const { + } + + void quantifier_manager::collect_statistics(::statistics & st) const { + m_imp->m_qi_queue.collect_statistics(st); + } + + void quantifier_manager::reset_statistics() { + } + + void quantifier_manager::display_stats(std::ostream & out, quantifier * q) const { + m_imp->display_stats(out, q); + } + + ptr_vector::const_iterator quantifier_manager::begin_quantifiers() const { + return m_imp->m_quantifiers.begin(); + } + + ptr_vector::const_iterator quantifier_manager::end_quantifiers() const { + return m_imp->m_quantifiers.end(); + } + + // The default plugin uses E-matching, MBQI and quick-checker + class default_qm_plugin : public quantifier_manager_plugin { + quantifier_manager * m_qm; + front_end_params * m_fparams; + context * m_context; + scoped_ptr m_mam; + scoped_ptr m_lazy_mam; + scoped_ptr m_model_finder; + scoped_ptr m_model_checker; + unsigned m_new_enode_qhead; + unsigned m_lazy_matching_idx; + public: + default_qm_plugin(): + m_qm(0), + m_context(0), + m_new_enode_qhead(0), + m_lazy_matching_idx(0) { + } + + virtual ~default_qm_plugin() { + } + + virtual void set_manager(quantifier_manager & qm) { + SASSERT(m_qm == 0); + m_qm = &qm; + m_context = &(qm.kernel()); + m_fparams = &(m_context->get_fparams()); + ast_manager & m = m_context->get_manager(); + + m_mam = mk_mam(*m_context, m_fparams->m_trace_stream); + m_lazy_mam = mk_mam(*m_context, m_fparams->m_trace_stream); + m_model_finder = alloc(model_finder, m, m_context->get_simplifier()); + m_model_checker = alloc(model_checker, m, *m_fparams, *(m_model_finder.get())); + + m_model_finder->set_context(m_context); + m_model_checker->set_qm(qm); + } + + virtual quantifier_manager_plugin * mk_fresh() { return alloc(default_qm_plugin); } + + virtual bool model_based() const { return m_fparams->m_mbqi; } + + virtual void add(quantifier * q) { + if (m_fparams->m_mbqi) { + m_model_finder->register_quantifier(q); + } + } + + virtual void del(quantifier * q) { + } + + virtual void push() { + m_mam->push_scope(); + m_lazy_mam->push_scope(); + if (m_fparams->m_mbqi) { + m_model_finder->push_scope(); + } + } + + virtual void pop(unsigned num_scopes) { + m_mam->pop_scope(num_scopes); + m_lazy_mam->pop_scope(num_scopes); + if (m_fparams->m_mbqi) { + m_model_finder->pop_scope(num_scopes); + } + } + + virtual void init_search_eh() { + m_lazy_matching_idx = 0; + if (m_fparams->m_mbqi) { + m_model_finder->init_search_eh(); + m_model_checker->init_search_eh(); + } + } + + virtual void assign_eh(quantifier * q) { + if (m_fparams->m_ematching) { + bool has_unary_pattern = false; + unsigned num_patterns = q->get_num_patterns(); + for (unsigned i = 0; i < num_patterns; i++) { + app * mp = to_app(q->get_pattern(i)); + if (mp->get_num_args() == 1) { + has_unary_pattern = true; + break; + } + } + unsigned num_eager_multi_patterns = m_fparams->m_qi_max_eager_multipatterns; + if (!has_unary_pattern) + num_eager_multi_patterns++; + for (unsigned i = 0, j = 0; i < num_patterns; i++) { + app * mp = to_app(q->get_pattern(i)); + SASSERT(m_context->get_manager().is_pattern(mp)); + bool unary = (mp->get_num_args() == 1); + if (!unary && j >= num_eager_multi_patterns) { + TRACE("assign_quantifier", tout << "delaying (too many multipatterns):\n" << mk_ismt2_pp(mp, m_context->get_manager()) << "\n" + << "j: " << j << " unary: " << unary << " m_params.m_qi_max_eager_multipatterns: " << m_fparams->m_qi_max_eager_multipatterns + << " num_eager_multi_patterns: " << num_eager_multi_patterns << "\n";); + m_lazy_mam->add_pattern(q, mp); + } + else { + TRACE("assign_quantifier", tout << "adding:\n" << mk_ismt2_pp(mp, m_context->get_manager()) << "\n";); + m_mam->add_pattern(q, mp); + } + if (!unary) + j++; + } + } + } + + bool use_ematching() const { + return m_fparams->m_ematching && !m_qm->empty(); + } + + virtual void add_eq_eh(enode * e1, enode * e2) { + if (use_ematching()) + m_mam->add_eq_eh(e1, e2); + } + + virtual void relevant_eh(enode * e) { + if (use_ematching()) { + m_mam->relevant_eh(e, false); + m_lazy_mam->relevant_eh(e, true); + } + } + + virtual bool can_propagate() const { + return m_mam->has_work(); + } + + virtual void restart_eh() { + if (m_fparams->m_mbqi) { + m_model_finder->restart_eh(); + m_model_checker->restart_eh(); + } + TRACE("mam_stats", m_mam->display(tout);); + } + + virtual bool is_shared(enode * n) const { + return (m_mam->is_shared(n) || m_lazy_mam->is_shared(n)); + } + + virtual void adjust_model(proto_model * m) { + if (m_fparams->m_mbqi) { + m_model_finder->fix_model(m); + } + } + + virtual void propagate() { + m_mam->match(); + if (!m_context->relevancy() && use_ematching()) { + ptr_vector::const_iterator it = m_context->begin_enodes(); + ptr_vector::const_iterator end = m_context->end_enodes(); + unsigned sz = static_cast(end - it); + if (sz > m_new_enode_qhead) { + m_context->push_trail(value_trail(m_new_enode_qhead)); + it += m_new_enode_qhead; + while (m_new_enode_qhead < sz) { + enode * e = *it; + m_mam->relevant_eh(e, false); + m_lazy_mam->relevant_eh(e, true); + m_new_enode_qhead++; + it++; + } + } + } + } + + virtual quantifier_manager::check_model_result + check_model(proto_model * m, obj_map const & root2value) { + if (m_fparams->m_mbqi) { + IF_VERBOSE(10, verbose_stream() << "model based quantifier instantiation...\n";); + if (m_model_checker->check(m, root2value)) { + return quantifier_manager::SAT; + } + else if (m_model_checker->has_new_instances()) { + return quantifier_manager::RESTART; + } + } + return quantifier_manager::UNKNOWN; + } + + virtual void set_cancel(bool f) { + // TODO: interrupt MAM and MBQI + } + + virtual final_check_status final_check_eh(bool full) { + if (!full) { + if (m_fparams->m_qi_lazy_instantiation) + return final_check_quant(); + return FC_DONE; + } + else { + return final_check_quant(); + } + } + + /** + \brief Final check related with quantifiers... + */ + final_check_status final_check_quant() { + if (use_ematching()) { + if (m_lazy_matching_idx < m_fparams->m_qi_max_lazy_multipattern_matching) { + IF_VERBOSE(100, verbose_stream() << "matching delayed multi-patterns... \n";); + m_lazy_mam->rematch(); + m_context->push_trail(value_trail(m_lazy_matching_idx)); + m_lazy_matching_idx++; + } + } + return FC_DONE; + } + + }; + + quantifier_manager_plugin * mk_default_plugin() { + return alloc(default_qm_plugin); + } + +}; diff --git a/lib/smt_quantifier.h b/lib/smt_quantifier.h new file mode 100644 index 000000000..9c4129c0d --- /dev/null +++ b/lib/smt_quantifier.h @@ -0,0 +1,167 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + smt_quantifier.h + +Abstract: + + Quantifier reasoning support for smt::context. + +Author: + + Leonardo de Moura (leonardo) 2012-02-16. + +Revision History: + +--*/ +#ifndef _SMT_QUANTIFIER_H_ +#define _SMT_QUANTIFIER_H_ + +#include"ast.h" +#include"statistics.h" +#include"params.h" +#include"smt_types.h" + +class proto_model; +struct front_end_params; + +namespace smt { + class quantifier_manager_plugin; + class quantifier_stat; + + class quantifier_manager { + struct imp; + imp * m_imp; + public: + quantifier_manager(context & ctx, front_end_params & fp, params_ref const & p); + ~quantifier_manager(); + + context & kernel() const; + + void set_plugin(quantifier_manager_plugin * plugin); + + void add(quantifier * q, unsigned generation); + void del(quantifier * q); + bool empty() const; + + bool is_shared(enode * n) const; + + quantifier_stat * get_stat(quantifier * q) const; + unsigned get_generation(quantifier * q) const; + + bool add_instance(quantifier * q, app * pat, + unsigned num_bindings, + enode * const * bindings, + unsigned max_generation, + unsigned min_top_generation, + unsigned max_top_generation, + ptr_vector & used_enodes); + bool add_instance(quantifier * q, unsigned num_bindings, enode * const * bindings, unsigned generation = 0); + + void init_search_eh(); + void assign_eh(quantifier * q); + void add_eq_eh(enode * n1, enode * n2); + void relevant_eh(enode * n); + final_check_status final_check_eh(bool full); + void restart_eh(); + + bool can_propagate() const; + void propagate(); + + enum check_model_result { + SAT, UNKNOWN, RESTART + }; + + bool model_based() const; + void adjust_model(proto_model * m); + check_model_result check_model(proto_model * m, obj_map const & root2value); + + void push(); + void pop(unsigned num_scopes); + void reset(); + + void set_cancel(bool f); + void display(std::ostream & out) const; + void display_stats(std::ostream & out, quantifier * q) const; + + void collect_statistics(::statistics & st) const; + void reset_statistics(); + + ptr_vector::const_iterator begin_quantifiers() const; + ptr_vector::const_iterator end_quantifiers() const; + }; + + class quantifier_manager_plugin { + public: + quantifier_manager_plugin() {} + virtual ~quantifier_manager_plugin() {} + + virtual void set_manager(quantifier_manager & qm) = 0; + + virtual quantifier_manager_plugin * mk_fresh() = 0; + + virtual void add(quantifier * q) = 0; + virtual void del(quantifier * q) = 0; + + virtual bool is_shared(enode * n) const = 0; + + /** + \brief This method is invoked whenever q is assigned to true. + */ + virtual void assign_eh(quantifier * q) = 0; + /** + \brief This method is invoked whenever n1 and n2 are merged into the same equivalence class. + */ + virtual void add_eq_eh(enode * n1, enode * n2) = 0; + /** + \brief This method is invoked whenever n is marked as relevant. + */ + virtual void relevant_eh(enode * n) = 0; + /** + \brief This method is invoked when a new search() is started. + */ + virtual void init_search_eh() = 0; + /** + \brief Final_check event handler. + */ + virtual final_check_status final_check_eh(bool full) = 0; + /** + \brief This method is invoked whenever the solver restarts. + */ + virtual void restart_eh() = 0; + + /** + \brief Return true if the quantifier_manager can propagate information + information back into the core. + */ + virtual bool can_propagate() const = 0; + virtual void propagate() = 0; + + /** + \brief Return true if the plugin is "model based" + */ + virtual bool model_based() const = 0; + + /** + \brief Give a change to the plugin to adjust the interpretation of unintepreted functions. + It can basically change the "else" of each uninterpreted function. + */ + virtual void adjust_model(proto_model * m) = 0; + + /** + \brief Core invokes this method to check whether the candidate interpretation + satisfies the quantifiers in the manager. + It also provides a mapping from enodes to their interpretations. + */ + virtual quantifier_manager::check_model_result check_model(proto_model * m, obj_map const & root2value) = 0; + + virtual void push() = 0; + virtual void pop(unsigned num_scopes) = 0; + + virtual void set_cancel(bool f) = 0; + }; +}; + +#endif diff --git a/lib/smt_quantifier_instances.h b/lib/smt_quantifier_instances.h new file mode 100644 index 000000000..b61b1c581 --- /dev/null +++ b/lib/smt_quantifier_instances.h @@ -0,0 +1,66 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_quantifier_instances.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-03-26. + +Revision History: + +--*/ +#ifndef _SMT_QUANTIFIER_INSTANCES_H_ +#define _SMT_QUANTIFIER_INSTANCES_H_ + +namespace smt { + + class quantifier_instances; + + class quantifier_instance { + quantifier * m_quantifier; + double m_cost; + enode * m_enodes[0]; + quantifier_instance(quantifier * q, enode * const * enodes); + friend class quantifier_instances; + public: + quantifier * get_quantifier() const { return m_quantifier; } + unsigned get_num_decls() const { return m_quantifier->get_num_decls(); } + double get_cost() const { return m_cost; } + enode * const * get_enodes() const { return m_enodes; } + bool operator==(quantifier_instance const & other) const; + unsigned hash() const; + }; + + + class quantifier_instances { + struct instance_lt { + ptr_vector const & m_stack; + instance_lt(ptr_vector const & s): + m_stack(s) { + } + bool operator()(int i1, int i2) const { + return m_stack[i1]->get_cost() < m_stack[i2]->get_cost(); + } + }; + + context & m_context; + ast_manager & m_manager; + obj_hashtable m_instances; //!< Set of instances. + ptr_vector m_stack; //!< Stack for backtracking. + heap m_queue; //!< Instantiation priority queue. + unsigned_vector m_scopes; + + + }; + +}; + +#endif /* _SMT_QUANTIFIER_INSTANCES_H_ */ + diff --git a/lib/smt_quantifier_stat.cpp b/lib/smt_quantifier_stat.cpp new file mode 100644 index 000000000..d34a7c0ac --- /dev/null +++ b/lib/smt_quantifier_stat.cpp @@ -0,0 +1,115 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_quantifier_stat.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-20. + +Revision History: + +--*/ +#include"smt_quantifier_stat.h" + +namespace smt { + + quantifier_stat::quantifier_stat(unsigned generation): + m_size(0), + m_depth(0), + m_generation(generation), + m_case_split_factor(1), + m_num_nested_quantifiers(0), + m_num_instances(0), + m_num_instances_curr_search(0), + m_num_instances_curr_branch(0), + m_max_generation(0), + m_max_cost(0.0f) { + } + + quantifier_stat_gen::quantifier_stat_gen(ast_manager & m, region & r): + m_manager(m), + m_region(r) { + } + + void quantifier_stat_gen::reset() { + m_already_found.reset(); + m_todo.reset(); + m_case_split_factor = 1; + } + + quantifier_stat * quantifier_stat_gen::operator()(quantifier * q, unsigned generation) { + reset(); + quantifier_stat * r = new (m_region) quantifier_stat(generation); + m_todo.push_back(entry(q->get_expr())); + while (!m_todo.empty()) { + entry & e = m_todo.back(); + expr * n = e.m_expr; + unsigned depth = e.m_depth; + bool depth_only = e.m_depth_only; + m_todo.pop_back(); + unsigned old_depth; + if (m_already_found.find(n, old_depth)) { + if (old_depth >= depth) + continue; + depth_only = true; + } + m_already_found.insert(n, depth); + if (depth >= r->m_depth) + r->m_depth = depth; + if (!depth_only) { + r->m_size++; + if (is_quantifier(n)) + r->m_num_nested_quantifiers ++; + if (is_app(n) && to_app(n)->get_family_id() == m_manager.get_basic_family_id()) { + unsigned num_args = to_app(n)->get_num_args(); + // Remark: I'm approximating the case_split factor. + // I'm also ignoring the case split factor due to theories. + switch (to_app(n)->get_decl_kind()) { + case OP_OR: + if (depth == 0) + m_case_split_factor *= num_args; + else + m_case_split_factor *= (num_args + 1); + break; + case OP_AND: + if (depth > 0) + m_case_split_factor *= (num_args + 1); + break; + case OP_IFF: + if (depth == 0) + m_case_split_factor *= 4; + else + m_case_split_factor *= 9; + break; + case OP_ITE: + if (depth == 0) + m_case_split_factor *= 4; + else + m_case_split_factor *= 9; + break; + default: + break; + } + } + } + if (is_app(n)) { + unsigned j = to_app(n)->get_num_args(); + while (j > 0) { + --j; + m_todo.push_back(entry(to_app(n)->get_arg(j), depth + 1, depth_only)); + } + } + } + r->m_case_split_factor = m_case_split_factor.get_value(); + return r; + } + +}; + diff --git a/lib/smt_quantifier_stat.h b/lib/smt_quantifier_stat.h new file mode 100644 index 000000000..a8dcc86d4 --- /dev/null +++ b/lib/smt_quantifier_stat.h @@ -0,0 +1,140 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_quantifier_stat.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-20. + +Revision History: + +--*/ +#ifndef _SMT_QUANTIFIER_STAT_H_ +#define _SMT_QUANTIFIER_STAT_H_ + +#include"ast.h" +#include"obj_hashtable.h" +#include"approx_nat.h" +#include"region.h" + +namespace smt { + + /** + \brief Store statistics for quantifiers. This information is + used to implement heuristics for quantifier instantiation. + */ + class quantifier_stat { + unsigned m_size; + unsigned m_depth; + unsigned m_generation; + unsigned m_case_split_factor; //!< the product of the size of the clauses created by this quantifier. + unsigned m_num_nested_quantifiers; + unsigned m_num_instances; + unsigned m_num_instances_curr_search; + unsigned m_num_instances_curr_branch; //!< only updated if QI_TRACK_INSTANCES is true + unsigned m_max_generation; //!< max. generation of an instance + float m_max_cost; + + friend class quantifier_stat_gen; + + quantifier_stat(unsigned generation); + public: + + unsigned get_size() const { + return m_size; + } + + unsigned get_depth() const { + return m_depth; + } + + unsigned get_generation() const { + return m_generation; + } + + unsigned get_case_split_factor() const { + return m_case_split_factor; + } + + unsigned get_num_nested_quantifiers() const { + return m_num_nested_quantifiers; + } + + unsigned get_num_instances() const { + return m_num_instances; + } + + unsigned get_num_instances_curr_search() const { + return m_num_instances_curr_search; + } + + unsigned & get_num_instances_curr_branch() { + return m_num_instances_curr_branch; + } + + void inc_num_instances() { + m_num_instances++; + m_num_instances_curr_search++; + } + + void inc_num_instances_curr_branch() { + m_num_instances_curr_branch++; + } + + void reset_num_instances_curr_search() { + m_num_instances_curr_search = 0; + } + + void update_max_generation(unsigned g) { + if (m_max_generation < g) + m_max_generation = g; + } + + unsigned get_max_generation() const { + return m_max_generation; + } + + void update_max_cost(float c) { + if (m_max_cost < c) + m_max_cost = c; + } + + float get_max_cost() const { + return m_max_cost; + } + }; + + /** + \brief Functor used to generate quantifier statistics. + */ + class quantifier_stat_gen { + struct entry { + expr * m_expr; + unsigned m_depth:31; + bool m_depth_only:1; //!< track only the depth of this entry. + entry():m_expr(0), m_depth(0), m_depth_only(false) {} + entry(expr * n, unsigned depth = 0, bool depth_only = false):m_expr(n), m_depth(depth), m_depth_only(depth_only) {} + }; + ast_manager & m_manager; + region & m_region; + obj_map m_already_found; // expression to the max. depth it was reached. + svector m_todo; + approx_nat m_case_split_factor; + void reset(); + + public: + quantifier_stat_gen(ast_manager & m, region & r); + quantifier_stat * operator()(quantifier * q, unsigned generation); + }; + +}; + +#endif /* _SMT_QUANTIFIER_STAT_H_ */ + diff --git a/lib/smt_quick_checker.cpp b/lib/smt_quick_checker.cpp new file mode 100644 index 000000000..462f784bd --- /dev/null +++ b/lib/smt_quick_checker.cpp @@ -0,0 +1,421 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_quick_checker.cpp + +Abstract: + + Incomplete model checker. + +Author: + + Leonardo de Moura (leonardo) 2008-06-20. + +Revision History: + +--*/ +#include"smt_context.h" +#include"smt_quick_checker.h" +#include"ast_pp.h" + +namespace smt { + + quick_checker::collector::collector(context & c): + m_context(c), + m_manager(c.get_manager()), + m_conservative(true) { + } + + void quick_checker::collector::init(quantifier * q) { + m_num_vars = q->get_num_decls(); + m_already_found.reserve(m_num_vars+1, false); + m_candidates.reserve(m_num_vars+1); + m_tmp_candidates.reserve(m_num_vars+1); + for (unsigned i = 0; i < m_num_vars; i++) { + m_already_found[i] = false; + m_candidates[i].reset(); + } + m_cache.reset(); + } + + /** + \brief Returns true if there is a term (f ... n' ...) in the logical + context such that n' is the i-th argument of f, and n and n' are in the + same equivalence class. + + If f == 0 then true is returned (f == 0 means there is no parent to check) + + */ + bool quick_checker::collector::check_arg(enode * n, func_decl * f, unsigned i) { + if (!f || !m_conservative) + return true; + enode_vector::const_iterator it = m_context.begin_enodes_of(f); + enode_vector::const_iterator end = m_context.end_enodes_of(f); + for (; it != end; ++it) { + enode * curr = *it; + if (m_context.is_relevant(curr) && curr->is_cgr() && i < curr->get_num_args() && curr->get_arg(i)->get_root() == n->get_root()) + return true; + } + return false; + } + + void quick_checker::collector::collect_core(app * n, func_decl * p, unsigned i) { + func_decl * f = n->get_decl(); + unsigned num_args = n->get_num_args(); + for (unsigned j = 0; j < num_args; j++) { + expr * arg = n->get_arg(j); + if (is_var(arg)) { + unsigned idx = to_var(arg)->get_idx(); + if (idx >= m_num_vars) + return; + if (m_already_found[idx] && m_conservative) { + enode_set & s = m_candidates[idx]; + enode_set & ns = m_tmp_candidates[idx]; + if (s.empty()) + continue; + ns.reset(); + enode_vector::const_iterator it = m_context.begin_enodes_of(f); + enode_vector::const_iterator end = m_context.end_enodes_of(f); + for (; it != end; ++it) { + enode * curr = *it; + if (m_context.is_relevant(curr) && curr->is_cgr() && check_arg(curr, p, i) && j < curr->get_num_args()) { + enode * arg = curr->get_arg(j)->get_root(); + // intersection + if (s.contains(arg)) + ns.insert(arg); + } + } + SASSERT(m_conservative); + s.swap(ns); + } + else { + m_already_found[idx] = true; + enode_set & s = m_candidates[idx]; + enode_vector::const_iterator it = m_context.begin_enodes_of(f); + enode_vector::const_iterator end = m_context.end_enodes_of(f); + for (; it != end; ++it) { + enode * curr = *it; + if (m_context.is_relevant(curr) && curr->is_cgr() && check_arg(curr, p, i) && j < curr->get_num_args()) { + enode * arg = curr->get_arg(j)->get_root(); + s.insert(arg); + } + } + } + } + else { + if (n->get_family_id() != m_manager.get_basic_family_id()) + collect(arg, n->get_decl(), j); + else + collect(arg, 0, 0); + } + } + } + + void quick_checker::collector::collect(expr * n, func_decl * f, unsigned idx) { + if (is_quantifier(n)) + return; + if (is_var(n)) + return; + if (is_ground(n)) + return; + entry e(n, f, idx); + if (m_cache.contains(e)) + return; + m_cache.insert(e); + collect_core(to_app(n), f, idx); + } + + void quick_checker::collector::save_result(vector & candidates) { + candidates.reserve(m_num_vars+1); + for (unsigned i = 0; i < m_num_vars; i++) { + enode_vector & v = candidates[i]; + v.reset(); + enode_set & s = m_candidates[i]; + enode_set::iterator it = s.begin(); + enode_set::iterator end = s.end(); + for (; it != end; ++it) { + enode * curr = *it; + v.push_back(curr); + } + } + TRACE("collector", + tout << "candidates:\n"; + for (unsigned i = 0; i < m_num_vars; i++) { + tout << "var " << i << ":"; + enode_vector & v = candidates[i]; + enode_vector::iterator it = v.begin(); + enode_vector::iterator end = v.end(); + for (; it != end; ++it) + tout << " #" << (*it)->get_owner_id(); + tout << "\n"; + }); + } + + void quick_checker::collector::operator()(quantifier * q, bool conservative, vector & candidates) { + flet l(m_conservative, conservative); + init(q); + TRACE("collector", tout << "model checking: #" << q->get_id() << "\n" << mk_pp(q, m_manager) << "\n";); + collect(q->get_expr(), 0, 0); + save_result(candidates); + } + + quick_checker::quick_checker(context & c): + m_context(c), + m_manager(c.get_manager()), + m_simplifier(c.get_simplifier()), + m_collector(c), + m_new_exprs(m_manager) { + } + + /** + \brief Instantiate instances unsatisfied by the current model. Return true if new instances were generated. + */ + bool quick_checker::instantiate_unsat(quantifier * q) { + TRACE("quick_checker", tout << "instantiate instances unsatisfied by current model\n" << mk_pp(q, m_manager) << "\n";); + m_candidate_vectors.reset(); + m_collector(q, true, m_candidate_vectors); + m_num_bindings = q->get_num_decls(); + return process_candidates(q, true); + } + + /** + \brief Instantiate instances not satisfied by the current model. Return true if new instances were generated. + */ + bool quick_checker::instantiate_not_sat(quantifier * q) { + TRACE("quick_checker", tout << "instantiate instances not satisfied by current model\n" << mk_pp(q, m_manager) << "\n";); + m_candidate_vectors.reset(); + m_collector(q, false, m_candidate_vectors); + m_num_bindings = q->get_num_decls(); + return process_candidates(q, false); + } + + bool quick_checker::instantiate_not_sat(quantifier * q, unsigned num_candidates, expr * const * candidates) { + // initialize m_candidates using the given set of candidates. + m_candidate_vectors.reset(); + m_num_bindings = q->get_num_decls(); + m_candidate_vectors.reserve(m_num_bindings+1); + for (unsigned i = 0; i < m_num_bindings; i++) { + m_candidate_vectors[i].reset(); + sort * s = q->get_decl_sort(i); + for (unsigned j = 0; j < num_candidates; j++) { + if (m_manager.get_sort(candidates[j]) == s) { + expr * n = candidates[j]; + m_context.internalize(n, false); + enode * e = m_context.get_enode(n); + m_candidate_vectors[i].push_back(e); + } + } + } + return process_candidates(q, false); + } + + bool quick_checker::process_candidates(quantifier * q, bool unsat) { + ptr_vector empty_used_enodes; + buffer szs; + buffer it; + for (unsigned i = 0; i < m_num_bindings; i++) { + unsigned sz = m_candidate_vectors[i].size(); + if (sz == 0) + return false; + szs.push_back(sz); + it.push_back(0); + } + TRACE("quick_checker_sizes", tout << mk_pp(q, m_manager) << "\n"; for (unsigned i = 0; i < szs.size(); i++) tout << szs[i] << " "; tout << "\n";); + TRACE("quick_checker_candidates", + tout << "candidates:\n"; + for (unsigned i = 0; i < m_num_bindings; i++) { + enode_vector & v = m_candidate_vectors[i]; + enode_vector::iterator it = v.begin(); + enode_vector::iterator end = v.end(); + for (; it != end; ++it) + tout << "#" << (*it)->get_owner_id() << " "; + tout << "\n"; + }); + bool result = false; + m_bindings.reserve(m_num_bindings+1, 0); + do { + for (unsigned i = 0; i < m_num_bindings; i++) + m_bindings[m_num_bindings - i - 1] = m_candidate_vectors[i][it[i]]; + if (!m_context.contains_instance(q, m_num_bindings, m_bindings.c_ptr())) { + bool is_candidate = false; + TRACE("quick_checker", tout << "processing bindings:"; + for (unsigned i = 0; i < m_num_bindings; i++) tout << " #" << m_bindings[i]->get_owner_id(); + tout << "\n";); + if (unsat) + is_candidate = check_quantifier(q, false); + else + is_candidate = !check_quantifier(q, true); + if (is_candidate) { + TRACE("quick_checker", tout << "found new candidate\n";); + TRACE("quick_checker_sizes", tout << "found new candidate\n"; + for (unsigned i = 0; i < m_num_bindings; i++) tout << "#" << m_bindings[i]->get_owner_id() << " "; tout << "\n";); + unsigned max_generation = get_max_generation(m_num_bindings, m_bindings.c_ptr()); + if (m_context.add_instance(q, 0 /* no pattern was used */, m_num_bindings, m_bindings.c_ptr(), max_generation, + 0, // min_top_generation is only available for instances created by the MAM + 0, // max_top_generation is only available for instances created by the MAM + empty_used_enodes)) + result = true; + } + } + } + while (product_iterator_next(szs.size(), szs.c_ptr(), it.c_ptr())); + return result; + } + + bool quick_checker::check_quantifier(quantifier * n, bool is_true) { + bool r = check(n->get_expr(), is_true); + m_new_exprs.reset(); + m_check_cache.reset(); + m_canonize_cache.reset(); + return r; + } + + bool quick_checker::all_args(app * a, bool is_true) { + unsigned num_args = a->get_num_args(); + for (unsigned i = 0; i < num_args; i++) + if (!check(a->get_arg(i), is_true)) + return false; + return true; + } + + bool quick_checker::any_arg(app * a, bool is_true) { + unsigned num_args = a->get_num_args(); + for (unsigned i = 0; i < num_args; i++) + if (check(a->get_arg(i), is_true)) + return true; + return false; + } + + bool quick_checker::check_core(expr * n, bool is_true) { + SASSERT(m_manager.is_bool(n)); + if (m_context.b_internalized(n) && m_context.is_relevant(n)) { + lbool val = m_context.get_assignment(n); + if (val != l_undef && is_true == (val == l_true)) + return true; + else + return false; + } + if (!is_app(n)) + return false; + app * a = to_app(n); + if (a->get_family_id() == m_manager.get_basic_family_id()) { + switch (a->get_decl_kind()) { + case OP_TRUE: + return is_true; + case OP_FALSE: + return !is_true; + case OP_NOT: + return check(a->get_arg(0), !is_true); + case OP_OR: + return is_true ? any_arg(a, true) : all_args(a, false); + case OP_AND: + return is_true ? all_args(a, true) : any_arg(a, false); + case OP_IFF: + if (is_true) + return (check(a->get_arg(0), true) && check(a->get_arg(1), true)) || (check(a->get_arg(0), false) && check(a->get_arg(1), false)); + else + return (check(a->get_arg(0), true) && check(a->get_arg(1), false)) || (check(a->get_arg(0), false) && check(a->get_arg(1), true)); + case OP_ITE: + if (check(a->get_arg(0), true)) + return check(a->get_arg(1), is_true); + else if (check(a->get_arg(0), false)) + return check(a->get_arg(2), is_true); + else + return check(a->get_arg(1), is_true) && check(a->get_arg(2), is_true); + case OP_EQ: + if (is_true) { + return canonize(a->get_arg(0)) == canonize(a->get_arg(1)); + } + else { + expr * lhs = canonize(a->get_arg(0)); + expr * rhs = canonize(a->get_arg(1)); + if (m_context.e_internalized(lhs) && m_context.is_relevant(lhs) && + m_context.e_internalized(rhs) && m_context.is_relevant(rhs) && + m_context.get_enode(lhs)->get_root() != m_context.get_enode(rhs)->get_root()) + return true; + return m_manager.are_distinct(lhs, rhs); + } + default: + break; + } + } + expr * new_a = canonize(a); + TRACE("quick_checker_canonizer", tout << "before:\n" << mk_pp(a, m_manager) << "\nafter:\n" << mk_pp(new_a, m_manager) << "\n";); + if (m_context.lit_internalized(new_a) && m_context.is_relevant(new_a)) { + lbool val = m_context.get_assignment(new_a); + if (val != l_undef) + return is_true == (val == l_true); + } + if (is_true && m_manager.is_true(new_a)) + return true; + if (!is_true && m_manager.is_false(new_a)) + return true; + return false; + } + + bool quick_checker::check(expr * n, bool is_true) { + expr_bool_pair p(n, is_true); + bool r; + if (m_check_cache.find(p, r)) + return r; + r = check_core(n, is_true); + m_check_cache.insert(p, r); + return r; + } + + expr * quick_checker::canonize(expr * n) { + if (is_var(n)) { + unsigned idx = to_var(n)->get_idx(); + if (idx >= m_num_bindings) + return n; + // VAR 0 is stored in the last position of m_bindings + return m_bindings[m_num_bindings - idx - 1]->get_root()->get_owner(); + } + if (m_context.e_internalized(n)) + return m_context.get_enode(n)->get_root()->get_owner(); + if (!is_app(n) || to_app(n)->get_num_args() == 0) + return n; + expr * r; + if (m_canonize_cache.find(n, r)) + return r; + bool has_arg_enodes = true; + ptr_buffer new_args; + ptr_buffer new_arg_enodes; + unsigned num_args = to_app(n)->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = canonize(to_app(n)->get_arg(i)); + new_args.push_back(arg); + if (m_context.e_internalized(arg)) + new_arg_enodes.push_back(m_context.get_enode(arg)); + else + has_arg_enodes = false; + } + if (has_arg_enodes) { + enode * e = m_context.get_enode_eq_to(to_app(n)->get_decl(), num_args, new_arg_enodes.c_ptr()); + if (e) { + m_canonize_cache.insert(n, e->get_root()->get_owner()); + return e->get_root()->get_owner(); + } + } + // substitute by values in the model + for (unsigned i = 0; i < num_args; i++) { + expr * arg = new_args[i]; + if (m_context.e_internalized(arg)) { + expr_ref new_value(m_manager); + if (m_context.get_value(m_context.get_enode(arg), new_value)) { + new_args[i] = new_value; + m_new_exprs.push_back(new_value); + } + } + } + expr_ref new_expr(m_manager); + m_simplifier.mk_app(to_app(n)->get_decl(), num_args, new_args.c_ptr(), new_expr); + m_new_exprs.push_back(new_expr); + m_canonize_cache.insert(n, new_expr); + return new_expr; + } + +}; + diff --git a/lib/smt_quick_checker.h b/lib/smt_quick_checker.h new file mode 100644 index 000000000..ac568f18b --- /dev/null +++ b/lib/smt_quick_checker.h @@ -0,0 +1,106 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_quick_cheker.h + +Abstract: + + Incomplete model checker. + +Author: + + Leonardo de Moura (leonardo) 2008-06-20. + +Revision History: + +--*/ +#ifndef _SMT_QUICK_CHECKER_H_ +#define _SMT_QUICK_CHECKER_H_ + +#include"ast.h" +#include"simplifier.h" +#include"obj_hashtable.h" + +namespace smt { + class context; + + /** + \brief Simple object for finding quantifier instantiations that are falsified by the current model. + + \remark The code for selecting candidates is very naive. + */ + class quick_checker { + typedef obj_hashtable enode_set; + + /** + \brief Functor for collecting candidates for quantifier instantiation. + */ + class collector { + context & m_context; + ast_manager & m_manager; + bool m_conservative; + unsigned m_num_vars; + svector m_already_found; // mapping from var_idx -> bool + vector m_candidates; // mapping from var_idx -> set of candidates + vector m_tmp_candidates; // auxiliary mapping from var_idx -> set of candidates + + struct entry { + expr * m_expr; + func_decl * m_parent; + unsigned m_parent_pos; + entry(expr * n = 0, func_decl * d = 0, unsigned p = 0):m_expr(n), m_parent(d), m_parent_pos(p) {} + unsigned hash() const { return m_parent ? mk_mix(m_expr->get_id(), m_parent->get_id(), m_parent_pos) : m_expr->get_id(); } + bool operator==(entry const & e) const { return m_expr == e.m_expr && m_parent == e.m_parent && m_parent_pos == e.m_parent_pos; } + }; + + typedef hashtable, default_eq > cache; + cache m_cache; + + void init(quantifier * q); + bool check_arg(enode * n, func_decl * f, unsigned i); + void collect_core(app * n, func_decl * p, unsigned i); + void collect(expr * n, func_decl * f, unsigned idx); + void save_result(vector & candidates); + + public: + collector(context & c); + void operator()(quantifier * q, bool conservative, vector & candidates); + }; + + + typedef std::pair expr_bool_pair; + typedef pair_hash, int_hash> expr_bool_pair_hash; + typedef map > check_cache; + typedef obj_map canonize_cache; + + context & m_context; + ast_manager & m_manager; + simplifier & m_simplifier; + collector m_collector; + expr_ref_vector m_new_exprs; + vector m_candidate_vectors; + check_cache m_check_cache; + canonize_cache m_canonize_cache; + unsigned m_num_bindings; + ptr_vector m_bindings; + + bool all_args(app * a, bool is_true); + bool any_arg(app * a, bool is_true); + bool check_core(expr * n, bool is_true); + bool check(expr * n, bool is_true); + bool check_quantifier(quantifier * n, bool is_true); + expr * canonize(expr * n); + bool process_candidates(quantifier * q, bool unsat); + + public: + quick_checker(context & c); + bool instantiate_unsat(quantifier * q); + bool instantiate_not_sat(quantifier * q); + bool instantiate_not_sat(quantifier * q, unsigned num_candidates, expr * const * candidates); + }; +}; + +#endif /* _SMT_QUICK_CHECKER_H_ */ + diff --git a/lib/smt_relevancy.cpp b/lib/smt_relevancy.cpp new file mode 100644 index 000000000..47e3e3ef4 --- /dev/null +++ b/lib/smt_relevancy.cpp @@ -0,0 +1,669 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_relevancy.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-04. + +Revision History: + +--*/ +#include"smt_context.h" +#include"smt_relevancy.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"ast_smt2_pp.h" + +namespace smt { + + void relevancy_eh::mark_as_relevant(relevancy_propagator & rp, expr * n) { + rp.mark_as_relevant(n); + } + + void relevancy_eh::mark_args_as_relevant(relevancy_propagator & rp, app * n) { + unsigned j = n->get_num_args(); + while (j > 0) { + --j; + rp.mark_as_relevant(n->get_arg(j)); + } + } + + void simple_relevancy_eh::operator()(relevancy_propagator & rp) { + rp.mark_as_relevant(m_target); + } + + void pair_relevancy_eh::operator()(relevancy_propagator & rp) { + if (!rp.is_relevant(m_source1)) + return; + if (!rp.is_relevant(m_source2)) + return; + rp.mark_as_relevant(m_target); + } + + class and_relevancy_eh : public relevancy_eh { + app * m_parent; + public: + and_relevancy_eh(app * p):m_parent(p) {} + virtual ~and_relevancy_eh() {} + virtual void operator()(relevancy_propagator & rp); + }; + + class or_relevancy_eh : public relevancy_eh { + app * m_parent; + public: + or_relevancy_eh(app * p):m_parent(p) {} + virtual ~or_relevancy_eh() {} + virtual void operator()(relevancy_propagator & rp); + }; + + class ite_relevancy_eh : public relevancy_eh { + app * m_parent; + public: + ite_relevancy_eh(app * p):m_parent(p) {} + virtual ~ite_relevancy_eh() {} + virtual void operator()(relevancy_propagator & rp); + }; + + class ite_term_relevancy_eh : public relevancy_eh { + app * m_parent; + app * m_then_eq; + app * m_else_eq; + public: + ite_term_relevancy_eh(app * p, app * then_eq, app * else_eq):m_parent(p), m_then_eq(then_eq), m_else_eq(else_eq) {} + virtual ~ite_term_relevancy_eh() {} + virtual void operator()(relevancy_propagator & rp); + }; + + relevancy_propagator::relevancy_propagator(context & ctx): + m_context(ctx) { + } + + bool relevancy_propagator::enabled() const { + return m_context.relevancy(); + } + + region & relevancy_propagator::get_region() const { + return m_context.get_region(); + } + + ast_manager & relevancy_propagator::get_manager() const { + return m_context.get_manager(); + } + + void relevancy_propagator::add_dependency(expr * src, expr * target) { + if (!enabled()) + return; + if (is_relevant(src)) + mark_as_relevant(target); + else + add_handler(src, mk_relevancy_eh(simple_relevancy_eh(target))); + } + + relevancy_eh * relevancy_propagator::mk_or_relevancy_eh(app * n) { + SASSERT(get_manager().is_or(n)); + return mk_relevancy_eh(or_relevancy_eh(n)); + } + + relevancy_eh * relevancy_propagator::mk_and_relevancy_eh(app * n) { + SASSERT(get_manager().is_and(n)); + return mk_relevancy_eh(and_relevancy_eh(n)); + } + + relevancy_eh * relevancy_propagator::mk_ite_relevancy_eh(app * n) { + SASSERT(get_manager().is_ite(n)); + return mk_relevancy_eh(ite_relevancy_eh(n)); + } + + relevancy_eh * relevancy_propagator::mk_term_ite_relevancy_eh(app * c, app * t, app * e) { + return mk_relevancy_eh(ite_term_relevancy_eh(c, t, e)); + } + + struct relevancy_propagator_imp : public relevancy_propagator { + unsigned m_qhead; + expr_ref_vector m_relevant_exprs; + obj_hashtable m_is_relevant; + typedef list relevancy_ehs; + obj_map m_relevant_ehs; + obj_map m_watches[2]; + struct eh_trail { + enum kind { POS_WATCH, NEG_WATCH, HANDLER }; + kind m_kind; + expr * m_node; + eh_trail(expr * n):m_kind(HANDLER), m_node(n) {} + eh_trail(expr * n, bool val):m_kind(val ? POS_WATCH : NEG_WATCH), m_node(n) {} + kind get_kind() const { return m_kind; } + expr * get_node() const { return m_node; } + }; + svector m_trail; + struct scope { + unsigned m_relevant_exprs_lim; + unsigned m_trail_lim; + }; + svector m_scopes; + + relevancy_propagator_imp(context & ctx):relevancy_propagator(ctx), m_qhead(0), m_relevant_exprs(ctx.get_manager()) {} + + virtual ~relevancy_propagator_imp() { + undo_trail(0); + } + + relevancy_ehs * get_handlers(expr * n) { + relevancy_ehs * r = 0; + m_relevant_ehs.find(n, r); + SASSERT(m_relevant_ehs.contains(n) || r == 0); + return r; + } + + void set_handlers(expr * n, relevancy_ehs * ehs) { + if (ehs == 0) + m_relevant_ehs.erase(n); + else + m_relevant_ehs.insert(n, ehs); + } + + relevancy_ehs * get_watches(expr * n, bool val) { + relevancy_ehs * r = 0; + m_watches[val ? 1 : 0].find(n, r); + SASSERT(m_watches[val ? 1 : 0].contains(n) || r == 0); + return r; + } + + void set_watches(expr * n, bool val, relevancy_ehs * ehs) { + if (ehs == 0) + m_watches[val ? 1 : 0].erase(n); + else + m_watches[val ? 1 : 0].insert(n, ehs); + } + + void push_trail(eh_trail const & t) { + get_manager().inc_ref(t.get_node()); + m_trail.push_back(t); + } + + virtual void add_handler(expr * source, relevancy_eh * eh) { + if (!enabled()) + return; + if (is_relevant_core(source)) { + eh->operator()(*this, source); + } + else { + SASSERT(eh); + push_trail(eh_trail(source)); + set_handlers(source, new (get_region()) relevancy_ehs(eh, get_handlers(source))); + } + } + + virtual void add_watch(expr * n, bool val, relevancy_eh * eh) { + if (!enabled()) + return; + lbool lval = m_context.find_assignment(n); + if (!val) + lval = ~lval; + switch (lval) { + case l_false: + return; + case l_undef: + SASSERT(eh); + push_trail(eh_trail(n, val)); + set_watches(n, val, new (get_region()) relevancy_ehs(eh, get_watches(n, val))); + break; + case l_true: + eh->operator()(*this, n, val); + break; + } + } + + virtual void add_watch(expr * n, bool val, expr * target) { + if (!enabled()) + return; + lbool lval = m_context.find_assignment(n); + if (!val) + lval = ~lval; + switch (lval) { + case l_false: + return; + case l_undef: + add_watch(n, val, mk_relevancy_eh(simple_relevancy_eh(target))); + break; + case l_true: + mark_as_relevant(target); propagate(); + break; + } + } + + bool is_relevant_core(expr * n) const { return m_is_relevant.contains(n); } + + virtual bool is_relevant(expr * n) const { + return !enabled() || is_relevant_core(n); + } + + virtual void push() { + m_scopes.push_back(scope()); + scope & s = m_scopes.back(); + s.m_relevant_exprs_lim = m_relevant_exprs.size(); + s.m_trail_lim = m_trail.size(); + } + + virtual void pop(unsigned num_scopes) { + SASSERT(m_context.get_scope_level() == m_scopes.size()); + unsigned lvl = m_scopes.size(); + SASSERT(num_scopes <= lvl); + unsigned new_lvl = lvl - num_scopes; + scope & s = m_scopes[new_lvl]; + unmark_relevant_exprs(s.m_relevant_exprs_lim); + undo_trail(s.m_trail_lim); + m_scopes.shrink(new_lvl); + } + + /** + \brief Unmark expressions marked as relevant. + */ + void unmark_relevant_exprs(unsigned old_lim) { + SASSERT(old_lim <= m_relevant_exprs.size()); + unsigned i = m_relevant_exprs.size(); + while (i != old_lim) { + --i; + expr * n = m_relevant_exprs.get(i); + m_is_relevant.erase(n); + TRACE("propagate_relevancy", tout << "unmarking:\n" << mk_ismt2_pp(n, get_manager()) << "\n";); + } + m_relevant_exprs.shrink(old_lim); + m_qhead = m_relevant_exprs.size(); + } + + void undo_trail(unsigned old_lim) { + SASSERT(old_lim <= m_trail.size()); + ast_manager & m = get_manager(); + unsigned i = m_trail.size(); + while (i != old_lim) { + --i; + eh_trail & t = m_trail[i]; + expr * n = t.get_node(); + relevancy_ehs * ehs; + switch (t.get_kind()) { + case eh_trail::POS_WATCH: ehs = get_watches(n, true); SASSERT(ehs); set_watches(n, true, ehs->tail()); break; + case eh_trail::NEG_WATCH: ehs = get_watches(n, false); SASSERT(ehs); set_watches(n, false, ehs->tail()); break; + case eh_trail::HANDLER: ehs = get_handlers(n); SASSERT(ehs); set_handlers(n, ehs->tail()); break; + default: UNREACHABLE(); break; + } + m.dec_ref(n); + } + m_trail.shrink(old_lim); + } + + void set_relevant(expr * n) { + m_is_relevant.insert(n); + m_relevant_exprs.push_back(n); + m_context.relevant_eh(n); + } + + /** + \brief Mark an expression as relevant and propagate + the relevancy to its descendants. + */ + void mark_and_propagate(expr * n) { + if (!enabled()) + return; + if (!is_relevant_core(n)) { + mark_as_relevant(n); + propagate(); + } + } + + /** + \brief Mark the given expression as relevant if it is not + already marked. + */ + virtual void mark_as_relevant(expr * n) { + if (!enabled()) + return; + if (!is_relevant_core(n)) { + enode * e = m_context.find_enode(n); + if (e != 0) { + enode * curr = e; + do { + set_relevant(curr->get_owner()); + curr = curr->get_next(); + } + while (curr != e); + } + else { + set_relevant(n); + } + } + } + + /** + \brief Marks the children of n as relevant. + + \pre n is marked as relevant. + */ + void propagate_relevant_app(app * n) { + SASSERT(is_relevant_core(n)); + unsigned j = n->get_num_args(); + while (j > 0) { + --j; + mark_as_relevant(n->get_arg(j)); + } + } + + /** + \brief Propagate relevancy for an or-application. + */ + void propagate_relevant_or(app * n) { + SASSERT(get_manager().is_or(n)); + + lbool val = m_context.find_assignment(n); + // If val is l_undef, then the expression + // is a root, and no boolean variable was created for it. + if (val == l_undef) + val = l_true; + switch (val) { + case l_false: + propagate_relevant_app(n); + break; + case l_undef: + break; + case l_true: { + expr * true_arg = 0; + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = n->get_arg(i); + if (m_context.find_assignment(arg) == l_true) { + if (is_relevant_core(arg)) + return; + else if (!true_arg) + true_arg = arg; + } + } + if (true_arg) + mark_as_relevant(true_arg); + break; + } } + } + + /** + \brief Propagate relevancy for an and-application. + */ + void propagate_relevant_and(app * n) { + lbool val = m_context.find_assignment(n); + switch (val) { + case l_false: { + expr * false_arg = 0; + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = n->get_arg(i); + if (m_context.find_assignment(arg) == l_false) { + if (is_relevant_core(arg)) + return; + else if (!false_arg) + false_arg = arg; + } + } + if (false_arg) + mark_as_relevant(false_arg); + break; + } + case l_undef: + break; + case l_true: + propagate_relevant_app(n); + break; + } + } + + /** + \brief Propagate relevancy for an ite-expression. + */ + void propagate_relevant_ite(app * n) { + TRACE("propagate_relevant_ite", tout << "propagating relevancy for #" << n->get_id() << "\n" << mk_pp(n, get_manager()) << "\n";); + mark_as_relevant(n->get_arg(0)); + switch (m_context.find_assignment(n->get_arg(0))) { + case l_false: + TRACE("propagate_relevant_ite", tout << "marking as relevant: " << mk_pp(n->get_arg(2), get_manager()) << "\n";); + mark_as_relevant(n->get_arg(2)); + break; + case l_undef: + TRACE("propagate_relevant_ite", tout << "ite c is unassigned\n";); + break; + case l_true: + TRACE("propagate_relevant_ite", tout << "marking as relevant: " << mk_pp(n->get_arg(1), get_manager()) << "\n";); + mark_as_relevant(n->get_arg(1)); + break; + } + } + + /** + \brief Propagate relevancy to the arguments of recently marked + expressions. That is, expressions that are located at positions + [m_qhead, m_relevant_exprs.size()) in the stack of + relevant expressions. + */ + virtual void propagate() { + ast_manager & m = get_manager(); + while (m_qhead < m_relevant_exprs.size()) { + expr * n = m_relevant_exprs.get(m_qhead); + TRACE("propagate_relevancy_to_args", tout << "propagating relevancy to args of #" << n->get_id() << "\n";); + TRACE("propagate_relevancy", tout << "marking as relevant:\n" << mk_bounded_pp(n, m) << "\n";); + SASSERT(is_relevant_core(n)); + m_qhead++; + if (is_app(n)) { + family_id fid = to_app(n)->get_family_id(); + if (fid == m.get_basic_family_id()) { + switch (to_app(n)->get_decl_kind()) { + case OP_OR: + propagate_relevant_or(to_app(n)); + break; + case OP_AND: + propagate_relevant_and(to_app(n)); + break; + case OP_ITE: + propagate_relevant_ite(to_app(n)); + break; + default: + propagate_relevant_app(to_app(n)); + break; + } + } + else { + propagate_relevant_app(to_app(n)); + } + } + + relevancy_ehs * ehs = get_handlers(n); + while (ehs != 0) { + ehs->head()->operator()(*this, n); + ehs = ehs->tail(); + } + } + } + + virtual bool can_propagate() const { + return m_qhead < m_relevant_exprs.size(); + } + + virtual void assign_eh(expr * n, bool val) { + if (!enabled()) + return; + ast_manager & m = get_manager(); + SASSERT(enabled()); + if (is_relevant_core(n)) { + if (m.is_or(n)) + propagate_relevant_or(to_app(n)); + else if (m.is_and(n)) + propagate_relevant_and(to_app(n)); + } + relevancy_ehs * ehs = get_watches(n, val); + while (ehs != 0) { + ehs->head()->operator()(*this, n, val); + ehs = ehs->tail(); + } + } + + virtual void display(std::ostream & out) const { + if (enabled() && !m_relevant_exprs.empty()) { + out << "relevant exprs:\n"; + for (unsigned i = 0; i < m_relevant_exprs.size(); i++) { + out << "#" << m_relevant_exprs.get(i)->get_id() << " "; + } + out << "\n"; + } + } + +#ifdef Z3DEBUG + bool check_relevancy_app(app * n) const { + SASSERT(is_relevant(n)); + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + CTRACE("relevancy_bug", !is_relevant(n->get_arg(i)), tout << "n: " << mk_ismt2_pp(n, get_manager()) << "\ni: " << i << "\n";); + SASSERT(is_relevant(n->get_arg(i))); + } + return true; + } + + virtual bool check_relevancy_or(app * n, bool root) const { + lbool val = root ? l_true : m_context.find_assignment(n); + if (val == l_false) + return check_relevancy_app(n); + if (val == l_true) { + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = n->get_arg(i); + if (m_context.find_assignment(arg) == l_true && is_relevant(arg)) + return true; + } + TRACE("check_relevancy", tout << "failed:\n" << mk_ll_pp(n, get_manager()); display(tout);); + UNREACHABLE(); + } + return true; + } + + bool check_relevancy_and(app * n) const { + lbool val = m_context.find_assignment(n); + if (val == l_true) + return check_relevancy_app(n); + if (val == l_false) { + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = n->get_arg(i); + if (m_context.find_assignment(arg) == l_false && is_relevant(arg)) + return true; + } + UNREACHABLE(); + } + return true; + } + + bool check_relevancy_ite(app * n) const { + SASSERT(is_relevant(n->get_arg(0))); + switch (m_context.find_assignment(n->get_arg(0))) { + case l_false: + if (get_manager().is_bool(n)) { + TRACE("ite_bug", tout << mk_bounded_pp(n, get_manager()) << "\n";); + SASSERT(is_relevant(n->get_arg(2))); + } + else { + app_ref eq(get_manager()); + eq = m_context.mk_eq_atom(n, n->get_arg(2)); + SASSERT(is_relevant(eq.get())); + } + break; + case l_undef: + break; + case l_true: + if (get_manager().is_bool(n)) { + SASSERT(is_relevant(n->get_arg(1))); + } + else { + app_ref eq(get_manager()); + eq = m_context.mk_eq_atom(n, n->get_arg(1)); + SASSERT(is_relevant(eq.get())); + } + break; + } + return true; + } + + bool check_relevancy(expr_ref_vector const & v) const { + SASSERT(!can_propagate()); + ast_manager & m = get_manager(); + unsigned sz = v.size(); + for (unsigned i = 0; i < sz; i++) { + expr * n = v.get(i); + if (is_relevant(n)) { + TRACE("check_relevancy", tout << "checking:\n" << mk_ll_pp(n, get_manager()) << "internalized: " << m_context.find_enode(n) << "\n";); + if (m.is_or(n)) { + SASSERT(check_relevancy_or(to_app(n), false)); + } + else if (m.is_and(n)) { + SASSERT(check_relevancy_and(to_app(n))); + } + else if (m.is_ite(n)) { + SASSERT(check_relevancy_ite(to_app(n))); + } + else if (is_app(n)) { + SASSERT(check_relevancy_app(to_app(n))); + } + else { + enode * n0 = m_context.find_enode(n); + if (n0 != 0) { + enode * n = n0->get_next(); + while (n0 != n) { + SASSERT(is_relevant(n->get_owner())); + n = n->get_next(); + } + } + } + } + } + return true; + } +#endif + }; + + void and_relevancy_eh::operator()(relevancy_propagator & rp) { + if (rp.is_relevant(m_parent)) + static_cast(rp).propagate_relevant_and(m_parent); + } + + void or_relevancy_eh::operator()(relevancy_propagator & rp) { + if (rp.is_relevant(m_parent)) + static_cast(rp).propagate_relevant_or(m_parent); + } + + void ite_relevancy_eh::operator()(relevancy_propagator & rp) { + if (rp.is_relevant(m_parent)) { + static_cast(rp).propagate_relevant_ite(m_parent); + } + } + + void ite_term_relevancy_eh::operator()(relevancy_propagator & rp) { + if (!rp.is_relevant(m_parent)) + return; + rp.mark_as_relevant(m_parent->get_arg(0)); + switch (rp.get_context().get_assignment(m_parent->get_arg(0))) { + case l_false: + TRACE("ite_term_relevancy", tout << "marking else: #" << m_else_eq->get_id() << "\n";); + rp.mark_as_relevant(m_else_eq); + break; + case l_undef: + break; + case l_true: + TRACE("ite_term_relevancy", tout << "marking then: #" << m_then_eq->get_id() << "\n";); + rp.mark_as_relevant(m_then_eq); + break; + } + } + + relevancy_propagator * mk_relevancy_propagator(context & ctx) { return alloc(relevancy_propagator_imp, ctx); } +}; + + diff --git a/lib/smt_relevancy.h b/lib/smt_relevancy.h new file mode 100644 index 000000000..9da997fc6 --- /dev/null +++ b/lib/smt_relevancy.h @@ -0,0 +1,204 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_relevancy.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-04. + +Revision History: + +--*/ +#ifndef _SMT_RELEVANCY_H_ +#define _SMT_RELEVANCY_H_ + +#include"ast.h" + +namespace smt { + class context; + class relevancy_propagator; + + class relevancy_eh { + protected: + void mark_as_relevant(relevancy_propagator & rp, expr * n); + void mark_args_as_relevant(relevancy_propagator & rp, app * n); + public: + relevancy_eh() {} + virtual ~relevancy_eh() {} + /** + \brief This method is invoked when n is marked as relevant. + */ + virtual void operator()(relevancy_propagator & rp, expr * n) { operator()(rp); } + /** + \brief This method is invoked when atom is assigned to val. + */ + virtual void operator()(relevancy_propagator & rp, expr * atom, bool val) { operator()(rp); } + /** + \brief Fallback for the two previous methods. + */ + virtual void operator()(relevancy_propagator & rp) {} + }; + + class simple_relevancy_eh : public relevancy_eh { + expr * m_target; + public: + simple_relevancy_eh(expr * t):m_target(t) {} + virtual ~simple_relevancy_eh() {} + virtual void operator()(relevancy_propagator & rp); + }; + + /** + \brief Propagate relevancy to m_target if both m_source1 and m_source2 are relevant. + */ + class pair_relevancy_eh : public relevancy_eh { + expr * m_source1; + expr * m_source2; + expr * m_target; + public: + pair_relevancy_eh(expr * s1, expr * s2, expr * t):m_source1(s1), m_source2(s2), m_target(t) {} + virtual ~pair_relevancy_eh() {} + virtual void operator()(relevancy_propagator & rp); + }; + + /** + \brief Relevancy propagator. + + The relevancy propagation constraints are specified to the + relevancy propagator using the methods: + - add_handler + - add_watch + This class also provides helper methods for specifying commonly used constraints. + + It uses the following API from smt::context + - find_assignment(expr * n) + - find_enode(expr * n) + + It notifies smt::context that an expression became relevant by invoking + - relevant_eh(expr * n) + + smt::context notifies the relevancy_propagator that a literal was assigned by + invoking assign_eh(n, bool val) + */ + class relevancy_propagator { + protected: + context & m_context; + public: + relevancy_propagator(context & ctx); + virtual ~relevancy_propagator() {} + + context & get_context() { return m_context; } + + /** + \brief Install an event handler that is invoked whenever n is marked as relevant. + */ + virtual void add_handler(expr * n, relevancy_eh * eh) = 0; + + /** + \brief Install an event handler that is invoked whenever n is assigned to the given value. + + The relevancy propagator is notified of new assignments by the method assign_eh. + */ + virtual void add_watch(expr * n, bool val, relevancy_eh * eh) = 0; + + /** + \brief Install an event handler that just marks target as relevant whenever n is assigned to the given value. + + The relevancy propagator is notified of new assignments by the method assign_eh. + */ + virtual void add_watch(expr * n, bool val, expr * target) = 0; + + /** + \brief smt::context invokes this method whenever the expression is assigned to true/false + */ + virtual void assign_eh(expr * n, bool val) = 0; + + /** + \brief Mark the given expression is relevant. + */ + virtual void mark_as_relevant(expr * n) = 0; + + /** + \brief Return true if the given expression is marked as relevant. + */ + virtual bool is_relevant(expr * n) const = 0; + + /** + \brief Propagate relevancy using the event handlers + specified by add_handler and add_watch, and the structure + of the expressions already marked as relevant. + */ + virtual void propagate() = 0; + + /** + \brief Return true if it can propagate relevancy. + */ + virtual bool can_propagate() const = 0; + + /** + \brief Create a backtracking point + */ + virtual void push() = 0; + + /** + \brief Backtrack. + */ + virtual void pop(unsigned num_scopes) = 0; + + /** + \brief Display relevant expressions. + */ + virtual void display(std::ostream & out) const = 0; + +#ifdef Z3DEBUG + virtual bool check_relevancy(expr_ref_vector const & v) const = 0; + virtual bool check_relevancy_or(app * n, bool root) const = 0; +#endif + // -------------------------- + // + // Helper method + // + // -------------------------- + + /** + \brief Return true if relevancy propagation is enabled. + */ + bool enabled() const; + + /** + \Brief Return the region allocator for the smt::context that owns this propagator. + */ + region & get_region() const; + + /** + \Brief Return the ast_manager for the smt::context that owns this propagator. + */ + ast_manager & get_manager() const; + + template + relevancy_eh * mk_relevancy_eh(Eh const & eh) { return new (get_region()) Eh(eh); } + + /** + \brief Creates an event handler that marks target as relevant whenever src is marked + as relevant. + */ + void add_dependency(expr * src, expr * target); + + relevancy_eh * mk_or_relevancy_eh(app * n); + relevancy_eh * mk_and_relevancy_eh(app * n); + relevancy_eh * mk_ite_relevancy_eh(app * n); + relevancy_eh * mk_term_ite_relevancy_eh(app * c, app * t, app * e); + }; + + relevancy_propagator * mk_relevancy_propagator(context & ctx); + +}; + +#endif /* _SMT_RELEVANCY_H_ */ + diff --git a/lib/smt_setup.cpp b/lib/smt_setup.cpp new file mode 100644 index 000000000..72d596ab9 --- /dev/null +++ b/lib/smt_setup.cpp @@ -0,0 +1,901 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_setup.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-24. + +Revision History: + +--*/ +#include"smt_context.h" +#include"smt_setup.h" +#include"static_features.h" +#include"theory_arith.h" +#include"theory_dense_diff_logic.h" +#include"theory_diff_logic.h" +#include"theory_array.h" +#include"theory_array_full.h" +#include"theory_bv.h" +#include"theory_datatype.h" +#include"theory_dummy.h" +#include"theory_dl.h" +#include"theory_instgen.h" +#include"theory_seq_empty.h" + +namespace smt { + + setup::setup(context & c, front_end_params & params): + m_context(c), + m_manager(c.get_manager()), + m_params(params), + m_already_configured(false) { + } + + void setup::operator()(config_mode cm) { + SASSERT(m_context.get_scope_level() == 0); + SASSERT(!m_context.already_internalized()); + SASSERT(!m_already_configured); + // if (m_params.m_mbqi && m_params.m_model_compact) { + // warning_msg("ignoring MODEL_COMPACT=true because it cannot be used with MBQI=true"); + // m_params.m_model_compact = false; + // } + TRACE("setup", tout << "configuring logical context, logic: " << m_logic << "\n";); + m_already_configured = true; + switch (cm) { + case CFG_BASIC: setup_unknown(); break; + case CFG_LOGIC: setup_default(); break; + case CFG_AUTO: setup_auto_config(); break; + } + TRACE("setup", ini_params p; m_params.register_params(p); p.display_params(tout);); + } + + void setup::setup_default() { + if (m_logic == "QF_UF") + setup_QF_UF(); + else if (m_logic == "QF_RDL") + setup_QF_RDL(); + else if (m_logic == "QF_IDL") + setup_QF_IDL(); + else if (m_logic == "QF_UFIDL") + setup_QF_UFIDL(); + else if (m_logic == "QF_LRA") + setup_QF_LRA(); + else if (m_logic == "QF_LIA") + setup_QF_LIA(); + else if (m_logic == "QF_UFLIA") + setup_QF_UFLIA(); + else if (m_logic == "QF_UFLRA") + setup_QF_UFLRA(); + else if (m_logic == "QF_AX") + setup_QF_AX(); + else if (m_logic == "QF_AUFLIA") + setup_QF_AUFLIA(); + else if (m_logic == "QF_BV") + setup_QF_BV(); + else if (m_logic == "QF_AUFBV") + setup_QF_AUFBV(); + else if (m_logic == "QF_ABV") + setup_QF_AUFBV(); + else if (m_logic == "QF_UFBV") + setup_QF_AUFBV(); + else if (m_logic == "QF_BVRE") + setup_QF_BVRE(); + else if (m_logic == "AUFLIA") + setup_AUFLIA(); + else if (m_logic == "AUFLIRA") + setup_AUFLIRA(); + else if (m_logic == "AUFNIRA") + setup_AUFNIRA(); + else if (m_logic == "AUFLIA+") + setup_AUFLIA(); + else if (m_logic == "AUFLIA-") + setup_AUFLIA(); + else if (m_logic == "AUFLIRA+") + setup_AUFLIRA(); + else if (m_logic == "AUFLIRA-") + setup_AUFLIRA(); + else if (m_logic == "AUFNIRA+") + setup_AUFLIRA(); + else if (m_logic == "AUFNIRA-") + setup_AUFLIRA(); + else if (m_logic == "UFNIA") + setup_UFNIA(); + else if (m_logic == "UFLRA") + setup_UFLRA(); + else if (m_logic == "LRA") + setup_LRA(); + else + setup_unknown(); + } + + void setup::setup_auto_config() { + static_features st(m_manager); + IF_VERBOSE(100, verbose_stream() << "configuring...\n";); + TRACE("setup", tout << "setup, logic: " << m_logic << "\n";); + // HACK: do not collect features for QF_BV and QF_AUFBV... since they do not use them... + if (m_logic == "QF_BV") { + setup_QF_BV(); + } + else if (m_logic == "QF_AUFBV" || m_logic == "QF_ABV" || m_logic == "QF_UFBV") { + setup_QF_AUFBV(); + } + else { + IF_VERBOSE(100, verbose_stream() << "collecting features...\n";); + st.collect(m_context.get_num_asserted_formulas(), m_context.get_asserted_formulas()); + IF_VERBOSE(1000, st.display_primitive(verbose_stream());); + if (m_logic == "QF_UF") + setup_QF_UF(st); + else if (m_logic == "QF_RDL") + setup_QF_RDL(st); + else if (m_logic == "QF_IDL") + setup_QF_IDL(st); + else if (m_logic == "QF_UFIDL") + setup_QF_UFIDL(st); + else if (m_logic == "QF_LRA") + setup_QF_LRA(st); + else if (m_logic == "QF_LIA") + setup_QF_LIA(st); + else if (m_logic == "QF_UFLIA") + setup_QF_UFLIA(st); + else if (m_logic == "QF_UFLRA") + setup_QF_UFLRA(); + else if (m_logic == "QF_AX") + setup_QF_AX(st); + else if (m_logic == "QF_BVRE") + setup_QF_BVRE(); + else if (m_logic == "QF_AUFLIA") + setup_QF_AUFLIA(st); + else if (m_logic == "AUFLIA") + setup_AUFLIA(st); + else if (m_logic == "AUFLIRA") + setup_AUFLIRA(); + else if (m_logic == "AUFNIRA") + setup_AUFNIRA(); + else if (m_logic == "AUFLIA+") + setup_AUFLIA(); + else if (m_logic == "AUFLIA-") + setup_AUFLIA(); + else if (m_logic == "AUFLIRA+") + setup_AUFLIRA(); + else if (m_logic == "AUFLIRA-") + setup_AUFLIRA(); + else if (m_logic == "AUFNIRA+") + setup_AUFLIRA(); + else if (m_logic == "AUFNIRA-") + setup_AUFLIRA(); + else if (m_logic == "UFNIA") + setup_UFNIA(); + else if (m_logic == "LRA") + setup_LRA(); + else + setup_unknown(st); + } + } + + void check_no_arithmetic(static_features const & st, char const * logic) { + if (st.m_num_arith_ineqs > 0 || st.m_num_arith_terms > 0 || st.m_num_arith_eqs > 0) + throw default_exception("Benchmark constains arithmetic, but specified loging does not support it."); + } + + void setup::setup_QF_UF() { + m_params.m_relevancy_lvl = 0; + m_params.m_solver = true; + m_params.m_nnf_cnf = false; + } + + void setup::setup_QF_BVRE() { + setup_QF_BV(); + setup_QF_LIA(); + m_context.register_plugin(alloc(smt::theory_seq_empty, m_manager)); + } + + void setup::setup_QF_UF(static_features const & st) { + check_no_arithmetic(st, "QF_UF"); + m_params.m_relevancy_lvl = 0; + m_params.m_solver = true; + m_params.m_nnf_cnf = false; + m_params.m_restart_strategy = RS_LUBY; + m_params.m_phase_selection = PS_CACHING_CONSERVATIVE2; + m_params.m_random_initial_activity = IA_RANDOM; + TRACE("setup", + tout << "st.m_num_theories: " << st.m_num_theories << "\n"; + tout << "st.m_num_uninterpreted_functions: " << st.m_num_uninterpreted_functions << "\n";); + } + + void setup::setup_QF_RDL() { + m_params.m_relevancy_lvl = 0; + m_params.m_arith_expand_eqs = true; + m_params.m_arith_reflect = false; + m_params.m_solver = true; + m_params.m_arith_propagate_eqs = false; + m_params.m_nnf_cnf = false; + setup_mi_arith(); + } + + static bool is_dense(static_features const & st) { + return + st.m_num_uninterpreted_constants < 1000 && + (st.m_num_arith_eqs + st.m_num_arith_ineqs) > st.m_num_uninterpreted_constants * 9; + } + + bool is_in_diff_logic(static_features const & st) { + return + st.m_num_arith_eqs == st.m_num_diff_eqs && + st.m_num_arith_terms == st.m_num_diff_terms && + st.m_num_arith_ineqs == st.m_num_diff_ineqs; + } + + bool is_diff_logic(static_features const & st) { + return + is_in_diff_logic(st) && + (st.m_num_diff_ineqs > 0 || st.m_num_diff_eqs > 0 || st.m_num_diff_terms > 0) + ; + } + + void check_no_uninterpreted_functions(static_features const & st, char const * logic) { + if (st.m_num_uninterpreted_functions != 0) + throw default_exception("Benchmark contains uninterpreted function symbols, but specified logic does not support them."); + } + + void setup::setup_QF_RDL(static_features & st) { + if (!is_in_diff_logic(st)) + throw default_exception("Benchmark is not in QF_RDL (real difference logic)."); + if (st.m_has_int) + throw default_exception("Benchmark has integer variables but it is marked as QF_RDL (real difference logic)."); + TRACE("setup", tout << "setup_QF_RDL(st)\n";); + check_no_uninterpreted_functions(st, "QF_RDL"); + m_params.m_relevancy_lvl = 0; + m_params.m_arith_expand_eqs = true; + m_params.m_arith_reflect = false; + m_params.m_solver = true; + m_params.m_arith_propagate_eqs = false; + m_params.m_nnf_cnf = false; + if (is_dense(st)) { + m_params.m_restart_strategy = RS_GEOMETRIC; + m_params.m_restart_adaptive = false; + m_params.m_phase_selection = PS_CACHING; + } + // The smi theories use fixed size integers instead of rationals. + // They have support for epsilons for modeling strict inequalities, but they + // cannot handle rational numbers. + // It is only safe to use them when the input does not contain rational numbers. + // Moreover, if model construction is enabled, then rational numbers may be needed + // to compute the actual value of epsilon even if the input does not have rational numbers. + // Example: (x < 1) and (x > 0) + if (m_params.m_proof_mode != PGM_DISABLED) { + m_context.register_plugin(alloc(smt::theory_mi_arith_w_proofs, m_manager, m_params)); + } + else if (!m_params.m_arith_auto_config_simplex && is_dense(st)) { + if (!st.m_has_rational && !m_params.m_model && st.m_arith_k_sum < rational(INT_MAX / 8)) + m_context.register_plugin(alloc(smt::theory_dense_smi, m_manager, m_params)); + else + m_context.register_plugin(alloc(smt::theory_dense_mi, m_manager, m_params)); + } + else { + if (m_params.m_arith_auto_config_simplex || st.m_num_uninterpreted_constants > 4 * st.m_num_bool_constants) { + if (!st.m_has_rational && !m_params.m_model && st.m_arith_k_sum < rational(INT_MAX / 8)) { + TRACE("rdl_bug", tout << "using theory_smi_arith\n";); + m_context.register_plugin(alloc(smt::theory_smi_arith, m_manager, m_params)); + } + else { + TRACE("rdl_bug", tout << "using theory_mi_arith\n";); + m_context.register_plugin(alloc(smt::theory_mi_arith, m_manager, m_params)); + } + } + else { + m_params.m_arith_bound_prop = BP_NONE; + m_params.m_arith_propagation_strategy = ARITH_PROP_AGILITY; + m_params.m_arith_add_binary_bounds = true; + if (!st.m_has_rational && !m_params.m_model && st.m_arith_k_sum < rational(INT_MAX / 8)) + m_context.register_plugin(alloc(smt::theory_frdl, m_manager, m_params)); + else + m_context.register_plugin(alloc(smt::theory_rdl, m_manager, m_params)); + } + } + } + + void setup::setup_QF_IDL() { + TRACE("setup", tout << "setup_QF_IDL(st)\n";); + m_params.m_relevancy_lvl = 0; + m_params.m_arith_expand_eqs = true; + m_params.m_arith_reflect = false; + m_params.m_solver = true; + m_params.m_arith_propagate_eqs = false; + m_params.m_arith_small_lemma_size = 30; + m_params.m_nnf_cnf = false; + setup_i_arith(); + } + + void setup::setup_QF_IDL(static_features & st) { + if (!is_in_diff_logic(st)) + throw default_exception("Benchmark is not in QF_IDL (integer difference logic)."); + if (st.m_has_real) + throw default_exception("Benchmark has real variables but it is marked as QF_IDL (integer difference logic)."); + TRACE("setup", tout << "setup QF_IDL, m_arith_k_sum: " << st.m_arith_k_sum << " m_num_diff_terms: " << st.m_num_arith_terms << "\n"; + st.display_primitive(tout);); + TRACE("setup", tout << "setup_QF_IDL(st)\n";); + check_no_uninterpreted_functions(st, "QF_IDL"); + m_params.m_relevancy_lvl = 0; + m_params.m_arith_expand_eqs = true; + m_params.m_arith_reflect = false; + m_params.m_solver = true; + m_params.m_arith_propagate_eqs = false; + m_params.m_arith_small_lemma_size = 30; + m_params.m_nnf_cnf = false; + if (st.m_num_uninterpreted_constants > 5000) + m_params.m_relevancy_lvl = 2; + else if (st.m_cnf && !is_dense(st)) + m_params.m_phase_selection = PS_CACHING_CONSERVATIVE2; + else + m_params.m_phase_selection = PS_CACHING; + if (is_dense(st) && st.m_num_bin_clauses + st.m_num_units == st.m_num_clauses) { + m_params.m_restart_adaptive = false; + m_params.m_restart_strategy = RS_GEOMETRIC; + } + if (st.m_cnf && st.m_num_units == st.m_num_clauses) { + // the problem is just a big conjunction... using randomization to deal with crafted benchmarks + m_params.m_random_initial_activity = IA_RANDOM; + } + + TRACE("setup", + tout << "RELEVANCY: " << m_params.m_relevancy_lvl << "\n"; + tout << "ARITH_EQ_BOUNDS: " << m_params.m_arith_eq_bounds << "\n";); + + if (m_params.m_proof_mode != PGM_DISABLED) { + m_context.register_plugin(alloc(smt::theory_mi_arith_w_proofs, m_manager, m_params)); + } + else if (!m_params.m_arith_auto_config_simplex && is_dense(st)) { + TRACE("setup", tout << "using dense diff logic...\n";); + m_params.m_phase_selection = PS_CACHING_CONSERVATIVE; + if (st.m_arith_k_sum < rational(INT_MAX / 8)) + m_context.register_plugin(alloc(smt::theory_dense_si, m_manager, m_params)); + else + m_context.register_plugin(alloc(smt::theory_dense_i, m_manager, m_params)); + } + else { + if (st.m_arith_k_sum < rational(INT_MAX / 8)) { + TRACE("setup", tout << "using small integer simplex...\n";); + m_context.register_plugin(alloc(smt::theory_si_arith, m_manager, m_params)); + } + else { + TRACE("setup", tout << "using big integer simplex...\n";); + m_context.register_plugin(alloc(smt::theory_i_arith, m_manager, m_params)); + } + } + } + + void setup::setup_QF_UFIDL() { + TRACE("setup", tout << "setup_QF_UFIDL()\n";); + m_params.m_relevancy_lvl = 0; + m_params.m_arith_reflect = false; + m_params.m_solver = true; + m_params.m_nnf_cnf = false; + m_params.m_arith_eq_bounds = true; + m_params.m_phase_selection = PS_ALWAYS_FALSE; + m_params.m_restart_strategy = RS_GEOMETRIC; + m_params.m_restart_factor = 1.5; + m_params.m_restart_adaptive = false; + setup_i_arith(); + } + + void setup::setup_QF_UFIDL(static_features & st) { + TRACE("setup", tout << "setup_QF_UFIDL(st)\n";); + if (st.m_has_real) + throw default_exception("Benchmark has real variables but it is marked as QF_UFIDL (uninterpreted functions and difference logic)."); + m_params.m_relevancy_lvl = 0; + m_params.m_arith_reflect = false; + m_params.m_solver = true; + m_params.m_nnf_cnf = false; + if (st.m_num_uninterpreted_functions == 0) { + m_params.m_arith_expand_eqs = true; + m_params.m_arith_propagate_eqs = false; + if (is_dense(st)) { + m_params.m_arith_small_lemma_size = 128; + m_params.m_lemma_gc_half = true; + m_params.m_restart_strategy = RS_GEOMETRIC; + + if (m_params.m_proof_mode != PGM_DISABLED) { + m_context.register_plugin(alloc(smt::theory_mi_arith_w_proofs, m_manager, m_params)); + } + else if (st.m_arith_k_sum < rational(INT_MAX / 8)) + m_context.register_plugin(alloc(smt::theory_dense_si, m_manager, m_params)); + else + m_context.register_plugin(alloc(smt::theory_dense_i, m_manager, m_params)); + return; + } + } + m_params.m_arith_eq_bounds = true; + m_params.m_phase_selection = PS_ALWAYS_FALSE; + m_params.m_restart_strategy = RS_GEOMETRIC; + m_params.m_restart_factor = 1.5; + m_params.m_restart_adaptive = false; + if (m_params.m_proof_mode != PGM_DISABLED) { + m_context.register_plugin(alloc(smt::theory_mi_arith_w_proofs, m_manager, m_params)); + } + else if (st.m_arith_k_sum < rational(INT_MAX / 8)) + m_context.register_plugin(alloc(smt::theory_si_arith, m_manager, m_params)); + else + m_context.register_plugin(alloc(smt::theory_i_arith, m_manager, m_params)); + } + + void setup::setup_QF_LRA() { + TRACE("setup", tout << "setup_QF_LRA(st)\n";); + m_params.m_relevancy_lvl = 0; + m_params.m_arith_expand_eqs = true; + m_params.m_arith_reflect = false; + m_params.m_arith_propagate_eqs = false; + m_params.m_eliminate_term_ite = true; + m_params.m_solver = true; + m_params.m_nnf_cnf = false; + setup_mi_arith(); + } + + void setup::setup_QF_LRA(static_features const & st) { + check_no_uninterpreted_functions(st, "QF_LRA"); + m_params.m_relevancy_lvl = 0; + m_params.m_arith_expand_eqs = true; + m_params.m_arith_reflect = false; + m_params.m_arith_propagate_eqs = false; + m_params.m_eliminate_term_ite = true; + m_params.m_solver = true; + m_params.m_nnf_cnf = false; + if (numerator(st.m_arith_k_sum) > rational(2000000) && denominator(st.m_arith_k_sum) > rational(500)) { + m_params.m_relevancy_lvl = 2; + m_params.m_relevancy_lemma = false; + } + if (st.m_cnf) { + m_params.m_phase_selection = PS_CACHING_CONSERVATIVE2; + } + else { + m_params.m_restart_strategy = RS_GEOMETRIC; + m_params.m_arith_stronger_lemmas = false; + m_params.m_phase_selection = PS_ALWAYS_FALSE; + m_params.m_restart_adaptive = false; + } + m_params.m_arith_small_lemma_size = 32; + setup_mi_arith(); + } + + void setup::setup_QF_LIA() { + TRACE("setup", tout << "setup_QF_LIA(st)\n";); + m_params.m_relevancy_lvl = 0; + m_params.m_arith_expand_eqs = true; + m_params.m_arith_reflect = false; + m_params.m_arith_propagate_eqs = false; + m_params.m_nnf_cnf = false; + setup_i_arith(); + } + + void setup::setup_QF_LIA(static_features const & st) { + check_no_uninterpreted_functions(st, "QF_LIA"); + TRACE("setup", tout << "QF_LIA setup\n";); + + m_params.m_relevancy_lvl = 0; + m_params.m_arith_expand_eqs = true; + m_params.m_arith_reflect = false; + m_params.m_arith_propagate_eqs = false; + m_params.m_nnf_cnf = false; + if (st.m_max_ite_tree_depth > 50) { + m_params.m_arith_expand_eqs = false; + m_params.m_pull_cheap_ite_trees = true; + m_params.m_arith_propagate_eqs = true; + m_params.m_relevancy_lvl = 2; + m_params.m_relevancy_lemma = false; + } + else if (st.m_num_clauses == st.m_num_units) { + m_params.m_arith_gcd_test = false; + m_params.m_arith_branch_cut_ratio = 4; + m_params.m_relevancy_lvl = 2; + m_params.m_arith_expand_eqs = true; + m_params.m_eliminate_term_ite = true; + // if (st.m_num_exprs < 5000 && st.m_num_ite_terms < 50) { // safeguard to avoid high memory consumption + // TODO: implement analsysis function to decide where lift ite is too expensive. + // m_params.m_lift_ite = LI_FULL; + // } + } + else { + m_params.m_eliminate_term_ite = true; + m_params.m_phase_selection = PS_CACHING; + m_params.m_restart_adaptive = false; + m_params.m_restart_strategy = RS_GEOMETRIC; + m_params.m_restart_factor = 1.5; + } + if (st.m_num_bin_clauses + st.m_num_units == st.m_num_clauses && st.m_cnf && st.m_arith_k_sum > rational(100000)) { + m_params.m_arith_bound_prop = BP_NONE; + m_params.m_arith_stronger_lemmas = false; + } + setup_i_arith(); + } + + void setup::setup_QF_UFLIA() { + m_params.m_relevancy_lvl = 0; + m_params.m_arith_reflect = false; + m_params.m_solver = true; + m_params.m_nnf_cnf = false; + m_params.m_arith_propagation_threshold = 1000; + setup_i_arith(); + } + + void setup::setup_QF_UFLIA(static_features & st) { + if (st.m_has_real) + throw default_exception("Benchmark has real variables but it is marked as QF_UFLIA (uninterpreted functions and linear integer arithmetic)."); + setup_QF_UFLIA(); + } + + void setup::setup_QF_UFLRA() { + m_params.m_relevancy_lvl = 0; + m_params.m_arith_reflect = false; + m_params.m_solver = true; + m_params.m_nnf_cnf = false; + setup_mi_arith(); + } + + void setup::setup_QF_BV() { + m_params.m_relevancy_lvl = 0; + m_params.m_arith_reflect = false; + m_params.m_solver = true; + m_params.m_bv_cc = false; + m_params.m_bb_ext_gates = true; + m_params.m_nnf_cnf = false; + m_context.register_plugin(alloc(smt::theory_bv, m_manager, m_params, m_params)); + } + + void setup::setup_QF_AUFBV() { + m_params.m_array_mode = AR_SIMPLE; + m_params.m_relevancy_lvl = 0; + m_params.m_solver = true; + m_params.m_bv_cc = false; + m_params.m_bb_ext_gates = true; + m_params.m_nnf_cnf = false; + m_context.register_plugin(alloc(smt::theory_bv, m_manager, m_params, m_params)); + m_context.register_plugin(alloc(smt::theory_array, m_manager, m_params)); + } + + void setup::setup_QF_AX() { + m_params.m_array_mode = AR_SIMPLE; + m_params.m_nnf_cnf = false; + m_context.register_plugin(alloc(smt::theory_array, m_manager, m_params)); + } + + void setup::setup_QF_AX(static_features const & st) { + m_params.m_array_mode = AR_SIMPLE; + m_params.m_nnf_cnf = false; + if (st.m_num_clauses == st.m_num_units) { + m_params.m_relevancy_lvl = 0; + m_params.m_phase_selection = PS_ALWAYS_FALSE; + } + else { + m_params.m_relevancy_lvl = 2; + m_params.m_solver = true; + } + m_context.register_plugin(alloc(smt::theory_array, m_manager, m_params)); + } + + void setup::setup_QF_AUFLIA() { + TRACE("QF_AUFLIA", tout << "no static features\n";); + m_params.m_array_mode = AR_SIMPLE; + m_params.m_nnf_cnf = false; + m_params.m_relevancy_lvl = 2; + m_params.m_restart_strategy = RS_GEOMETRIC; + m_params.m_restart_factor = 1.5; + m_params.m_phase_selection = PS_CACHING_CONSERVATIVE2; + setup_i_arith(); + m_context.register_plugin(alloc(smt::theory_array, m_manager, m_params)); + } + + void setup::setup_QF_AUFLIA(static_features const & st) { + m_params.m_array_mode = AR_SIMPLE; + if (st.m_has_real) + throw default_exception("Benchmark has real variables but it is marked as QF_AUFLIA (arrays, uninterpreted functions and linear integer arithmetic)."); + m_params.m_nnf_cnf = false; + if (st.m_num_clauses == st.m_num_units) { + TRACE("QF_AUFLIA", tout << "using relevancy: 0\n";); + m_params.m_relevancy_lvl = 0; + m_params.m_phase_selection = PS_ALWAYS_FALSE; + } + else { + m_params.m_relevancy_lvl = 0; // it was 2, for some reason 2 doesn't work anymore TODO: investigate + m_params.m_restart_strategy = RS_GEOMETRIC; + m_params.m_restart_factor = 1.5; + m_params.m_phase_selection = PS_CACHING_CONSERVATIVE2; + m_params.m_random_initial_activity = IA_ZERO; + } + // m_params.m_solver = true; + // if (st.m_num_arith_ineqs == st.m_num_diff_ineqs && st.m_num_arith_eqs == st.m_num_diff_eqs && st.m_arith_k_sum < rational(INT_MAX / 8)) + // m_context.register_plugin(new smt::theory_si_arith(m_manager, m_params)); + // else + setup_i_arith(); + m_context.register_plugin(alloc(smt::theory_array, m_manager, m_params)); + } + + void setup::setup_AUFLIA(bool simple_array) { + TRACE("setup", tout << "AUFLIA\n";); + m_params.m_array_mode = simple_array ? AR_SIMPLE : AR_FULL; + m_params.m_pi_use_database = true; + m_params.m_phase_selection = PS_ALWAYS_FALSE; + m_params.m_restart_strategy = RS_GEOMETRIC; + m_params.m_restart_factor = 1.5; + m_params.m_eliminate_bounds = true; + m_params.m_qi_quick_checker = MC_UNSAT; + m_params.m_propagate_booleans = true; + m_params.m_qi_lazy_threshold = 20; + // m_params.m_qi_max_eager_multipatterns = 10; /// <<< HACK + m_params.m_mbqi = true; // enabling MBQI and MACRO_FINDER by default :-) + + // MACRO_FINDER is a horrible for AUFLIA and UFNIA benchmarks (boogie benchmarks in general) + // It destroys the existing patterns. + // m_params.m_macro_finder = true; + + // + m_params.m_ng_lift_ite = LI_FULL; + TRACE("setup", tout << "max_eager_multipatterns: " << m_params.m_qi_max_eager_multipatterns << "\n";); + setup_i_arith(); + setup_arrays(); + } + + void setup::setup_AUFLIA(static_features const & st) { + if (st.m_has_real) + throw default_exception("Benchmark has real variables but it is marked as AUFLIA (arrays, uninterpreted functions and linear integer arithmetic)."); + m_params.m_qi_eager_threshold = st.m_num_quantifiers_with_patterns == 0 ? 5 : 7; + setup_AUFLIA(); + } + + void setup::setup_AUFLIRA(bool simple_array) { + m_params.m_array_mode = simple_array ? AR_SIMPLE : AR_FULL; + m_params.m_phase_selection = PS_ALWAYS_FALSE; + m_params.m_eliminate_bounds = true; + m_params.m_qi_quick_checker = MC_UNSAT; + m_params.m_propagate_booleans = true; + m_params.m_qi_eager_threshold = 5; + // Added for MBQI release + m_params.m_qi_lazy_threshold = 20; + // + m_params.m_macro_finder = true; + m_params.m_ng_lift_ite = LI_FULL; + m_params.m_pi_max_multi_patterns = 10; //<< it was used for SMT-COMP + m_params.m_array_lazy_ieq = true; + m_params.m_array_lazy_ieq_delay = 4; + // + m_params.m_mbqi = true; // enabling MBQI by default :-) + // + setup_mi_arith(); + setup_arrays(); + } + + void setup::setup_UFNIA() { + setup_AUFLIA(); + } + + void setup::setup_UFLRA() { + setup_AUFLIRA(); + } + + void setup::setup_AUFLIAp() { + setup_AUFLIA(); + } + + void setup::setup_AUFNIRA() { + setup_AUFLIRA(); + } + + void setup::setup_LRA() { + m_params.m_quant_elim = true; + // after quantifier elimination, the result is a QF_LRA benchmark + m_params.m_relevancy_lvl = 0; + // m_params.m_arith_expand_eqs = true; << may affect quant_elim + m_params.m_arith_reflect = false; + m_params.m_arith_propagate_eqs = false; + m_params.m_eliminate_term_ite = true; + m_params.m_solver = true; + setup_mi_arith(); + } + + bool is_arith(static_features const & st) { + return st.m_num_arith_ineqs > 0 || st.m_num_arith_terms > 0 || st.m_num_arith_eqs > 0; + } + + void setup::setup_i_arith() { + if (m_params.m_proof_mode != PGM_DISABLED) { + m_context.register_plugin(alloc(smt::theory_mi_arith_w_proofs, m_manager, m_params)); + } + else { + m_context.register_plugin(alloc(smt::theory_i_arith, m_manager, m_params)); + } + } + + void setup::setup_mi_arith() { + if (m_params.m_proof_mode != PGM_DISABLED) { + m_context.register_plugin(alloc(smt::theory_mi_arith_w_proofs, m_manager, m_params)); + } + else { + m_context.register_plugin(alloc(smt::theory_mi_arith, m_manager, m_params)); + } + } + + void setup::setup_arith() { + switch(m_params.m_arith_mode) { + case AS_NO_ARITH: + m_context.register_plugin(alloc(smt::theory_dummy, m_manager.get_family_id("arith"), "no arithmetic")); + break; + case AS_DIFF_LOGIC: + if (m_params.m_arith_fixnum) { + if (m_params.m_arith_int_only) + m_context.register_plugin(alloc(smt::theory_fidl, m_manager, m_params)); + else + m_context.register_plugin(alloc(smt::theory_frdl, m_manager, m_params)); + } + else { + if (m_params.m_arith_int_only) + m_context.register_plugin(alloc(smt::theory_idl, m_manager, m_params)); + else + m_context.register_plugin(alloc(smt::theory_rdl, m_manager, m_params)); + } + break; + case AS_DENSE_DIFF_LOGIC: + if (m_params.m_arith_fixnum) { + if (m_params.m_arith_int_only) + m_context.register_plugin(alloc(smt::theory_dense_si, m_manager, m_params)); + else + m_context.register_plugin(alloc(smt::theory_dense_smi, m_manager, m_params)); + } + else { + if (m_params.m_arith_int_only) + m_context.register_plugin(alloc(smt::theory_dense_i, m_manager, m_params)); + else + m_context.register_plugin(alloc(smt::theory_dense_mi, m_manager, m_params)); + } + break; + default: + if (m_params.m_proof_mode != PGM_DISABLED) { + m_context.register_plugin(alloc(smt::theory_mi_arith_w_proofs, m_manager, m_params)); + } + else if (m_params.m_arith_fixnum) { + if (m_params.m_arith_int_only) + m_context.register_plugin(alloc(smt::theory_si_arith, m_manager, m_params)); + else + m_context.register_plugin(alloc(smt::theory_smi_arith, m_manager, m_params)); + } + else { + if (m_params.m_arith_int_only) + m_context.register_plugin(alloc(smt::theory_i_arith, m_manager, m_params)); + else + m_context.register_plugin(alloc(smt::theory_mi_arith, m_manager, m_params)); + } + break; + } + } + + void setup::setup_bv() { + switch(m_params.m_bv_mode) { + case BS_NO_BV: + m_context.register_plugin(alloc(smt::theory_dummy, m_manager.get_family_id("bv"), "no bit-vector")); + break; + case BS_BLASTER: + m_context.register_plugin(alloc(smt::theory_bv, m_manager, m_params, m_params)); + break; + } + } + + void setup::setup_arrays() { + switch(m_params.m_array_mode) { + case AR_NO_ARRAY: + m_context.register_plugin(alloc(smt::theory_dummy, m_manager.get_family_id("array"), "no array")); + break; + case AR_SIMPLE: + m_context.register_plugin(alloc(smt::theory_array, m_manager, m_params)); + break; + case AR_MODEL_BASED: + throw default_exception("The model-based array theory solver is deprecated"); + break; + case AR_FULL: + m_context.register_plugin(alloc(smt::theory_array_full, m_manager, m_params)); + break; + } + } + + void setup::setup_datatypes() { + TRACE("datatype", tout << "registering theory datatype...\n";); + m_context.register_plugin(alloc(theory_datatype, m_manager, m_params)); + } + + void setup::setup_dl() { + m_context.register_plugin(mk_theory_dl(m_manager)); + } + + void setup::setup_seq() { + m_context.register_plugin(alloc(theory_seq_empty, m_manager)); + } + void setup::setup_instgen() { + if (m_params.m_instgen) { + m_context.register_plugin(mk_theory_instgen(m_manager, m_params)); + } + } + + void setup::setup_unknown() { + setup_arith(); + setup_arrays(); + setup_bv(); + setup_datatypes(); + setup_dl(); + setup_instgen(); + setup_seq(); + } + + void setup::setup_unknown(static_features & st) { + TRACE("setup", tout << "setup_unknown\n";); + if (st.m_num_quantifiers > 0) { + if (st.m_has_real) + setup_AUFLIRA(false); + else + setup_AUFLIA(false); + setup_datatypes(); + setup_bv(); + return; + } + + TRACE("setup", + tout << "num non UF theories: " << st.num_non_uf_theories() << "\n"; + tout << "num theories: " << st.num_theories() << "\n"; + tout << "is_diff_logic: " << is_diff_logic(st) << "\n"; + tout << "is_arith: " << is_arith(st) << "\n"; + tout << "has UF: " << st.has_uf() << "\n"; + tout << "has real: " << st.m_has_real << "\n"; + tout << "has int: " << st.m_has_int << "\n";); + + if (st.num_non_uf_theories() == 0) { + setup_QF_UF(st); + return; + } + + if (st.num_theories() == 1 && is_diff_logic(st)) { + if (st.m_has_real && !st.m_has_int) + setup_QF_RDL(st); + else if (!st.m_has_real && st.m_has_int) + setup_QF_IDL(st); + else + setup_unknown(); + return; + } + + if (st.num_theories() == 2 && st.has_uf() && is_diff_logic(st)) { + if (!st.m_has_real && st.m_has_int) + setup_QF_UFIDL(st); + else + setup_unknown(); + return; + } + + if (st.num_theories() == 1 && is_arith(st)) { + if (st.m_has_real) + setup_QF_LRA(st); + else + setup_QF_LIA(st); + return; + } + + if (st.num_theories() == 2 && st.has_uf() && is_arith(st)) { + if (!st.m_has_real) + setup_QF_UFLIA(st); + else if (!st.m_has_int) + setup_QF_UFLRA(); + else + setup_unknown(); + return; + } + + // TODO QF_BV, QF_AUFBV, QF_AUFLIA + setup_unknown(); + } + +}; + + diff --git a/lib/smt_setup.h b/lib/smt_setup.h new file mode 100644 index 000000000..5c017a498 --- /dev/null +++ b/lib/smt_setup.h @@ -0,0 +1,114 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_setup.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-24. + +Revision History: + +--*/ +#ifndef _SMT_SETUP_H_ +#define _SMT_SETUP_H_ + +#include"ast.h" +#include"front_end_params.h" + +struct static_features; +namespace smt { + + enum config_mode { + CFG_BASIC, // install theories based on user options + CFG_LOGIC, // install theories and configure Z3 based on the value of the parameter set-logic. + CFG_AUTO, // install theories based on static features of the input formula + }; + + class context; + /** + \brief Object used to setup a logical context. + + \warning In the current version, we can only setup a logical context at scope level 0, + and before internalizing any formula. Auxiliary temporary contexts are used to avoid this + limitation. + */ + class setup { + context & m_context; + ast_manager & m_manager; + front_end_params & m_params; + symbol m_logic; + bool m_already_configured; + void setup_auto_config(); + void setup_default(); + // + // setup_() methods do not depend on static features of the formula. So, they are safe to use + // even in an incremental setting. + // + // setup_(static_features & st) can only be used if the logical context will perform a single + // check. + // + void setup_QF_UF(); + void setup_QF_UF(static_features const & st); + void setup_QF_RDL(); + void setup_QF_RDL(static_features & st); + void setup_QF_IDL(); + void setup_QF_IDL(static_features & st); + void setup_QF_UFIDL(); + void setup_QF_UFIDL(static_features & st); + void setup_QF_LRA(); + void setup_QF_LRA(static_features const & st); + void setup_QF_LIA(); + void setup_QF_LIA(static_features const & st); + void setup_QF_UFLIA(); + void setup_QF_UFLIA(static_features & st); + void setup_QF_UFLRA(); + void setup_QF_BV(); + void setup_QF_AUFBV(); + void setup_QF_AX(); + void setup_QF_AX(static_features const & st); + void setup_QF_AUFLIA(); + void setup_QF_AUFLIA(static_features const & st); + void setup_LRA(); + void setup_AUFLIA(bool simple_array = true); + void setup_AUFLIA(static_features const & st); + void setup_AUFLIRA(bool simple_array = true); + void setup_UFNIA(); + void setup_UFLRA(); + void setup_AUFLIAp(); + void setup_AUFNIRA(); + void setup_QF_BVRE(); + void setup_unknown(); + void setup_unknown(static_features & st); + void setup_arrays(); + void setup_datatypes(); + void setup_bv(); + void setup_arith(); + void setup_dl(); + void setup_seq(); + void setup_instgen(); + void setup_i_arith(); + void setup_mi_arith(); + public: + setup(context & c, front_end_params & params); + void mark_already_configured() { m_already_configured = true; } + bool already_configured() const { return m_already_configured; } + bool set_logic(symbol logic) { + if (already_configured()) + return false; + m_logic = logic; + return true; + } + symbol const & get_logic() const { return m_logic; } + void operator()(config_mode cm); + }; +}; + +#endif /* _SMT_SETUP_H_ */ + diff --git a/lib/smt_solver.cpp b/lib/smt_solver.cpp new file mode 100644 index 000000000..82b599869 --- /dev/null +++ b/lib/smt_solver.cpp @@ -0,0 +1,357 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + smt_solver.h + +Abstract: + + New frontend for the incremental solver. + +Author: + + Leonardo de Moura (leonardo) 2012-02-09. + +Revision History: + +--*/ +#include"smt_solver.h" +#include"smt_context.h" +#include"ast_smt2_pp.h" +#include"params2front_end_params.h" + +namespace smt { + + struct solver::imp { + smt::context m_kernel; + params_ref m_params; + + imp(ast_manager & m, front_end_params & fp, params_ref const & p): + m_kernel(m, fp, p), + m_params(p) { + } + + front_end_params & fparams() { + return m_kernel.get_fparams(); + } + + params_ref const & params() { + return m_params; + } + + ast_manager & m() const { + return m_kernel.get_manager(); + } + + bool set_logic(symbol logic) { + return m_kernel.set_logic(logic); + } + + void set_progress_callback(progress_callback * callback) { + return m_kernel.set_progress_callback(callback); + } + + void assert_expr(expr * e) { + TRACE("smt_solver", tout << "assert:\n" << mk_ismt2_pp(e, m()) << "\n";); + m_kernel.assert_expr(e); + } + + void assert_expr(expr * e, proof * pr) { + m_kernel.assert_expr(e, pr); + } + + unsigned size() const { + return m_kernel.get_num_asserted_formulas(); + } + + expr * const * get_formulas() const { + return m_kernel.get_asserted_formulas(); + } + + bool reduce() { + return m_kernel.reduce_assertions(); + } + + void push() { + TRACE("smt_solver", tout << "push()\n";); + m_kernel.push(); + } + + void pop(unsigned num_scopes) { + TRACE("smt_solver", tout << "pop()\n";); + m_kernel.pop(num_scopes); + } + + unsigned get_scope_level() const { + return m_kernel.get_scope_level(); + } + + lbool setup_and_check() { + return m_kernel.setup_and_check(); + } + + bool inconsistent() { + return m_kernel.inconsistent(); + } + + lbool check(unsigned num_assumptions, expr * const * assumptions) { + return m_kernel.check(num_assumptions, assumptions); + } + + void get_model(model_ref & m) const { + m_kernel.get_model(m); + } + + proof * get_proof() { + return m_kernel.get_proof(); + } + + unsigned get_unsat_core_size() const { + return m_kernel.get_unsat_core_size(); + } + + expr * get_unsat_core_expr(unsigned idx) const { + return m_kernel.get_unsat_core_expr(idx); + } + + failure last_failure() const { + return m_kernel.get_last_search_failure(); + } + + std::string last_failure_as_string() const { + return m_kernel.last_failure_as_string(); + } + + void get_assignments(expr_ref_vector & result) { + m_kernel.get_assignments(result); + } + + void get_relevant_labels(expr * cnstr, buffer & result) { + m_kernel.get_relevant_labels(cnstr, result); + } + + void get_relevant_labeled_literals(bool at_lbls, expr_ref_vector & result) { + m_kernel.get_relevant_labeled_literals(at_lbls, result); + } + + void get_relevant_literals(expr_ref_vector & result) { + m_kernel.get_relevant_literals(result); + } + + void get_guessed_literals(expr_ref_vector & result) { + m_kernel.get_guessed_literals(result); + } + + void display(std::ostream & out) const { + // m_kernel.display(out); <<< for external users it is just junk + // TODO: it will be replaced with assertion_stack.display + unsigned num = m_kernel.get_num_asserted_formulas(); + expr * const * fms = m_kernel.get_asserted_formulas(); + out << "(solver"; + for (unsigned i = 0; i < num; i++) { + out << "\n " << mk_ismt2_pp(fms[i], m(), 2); + } + out << ")"; + } + + void collect_statistics(::statistics & st) const { + m_kernel.collect_statistics(st); + } + + void reset_statistics() { + } + + void display_statistics(std::ostream & out) const { + m_kernel.display_statistics(out); + } + + void display_istatistics(std::ostream & out) const { + m_kernel.display_istatistics(out); + } + + void set_cancel(bool f) { + m_kernel.set_cancel_flag(f); + } + + bool canceled() { + return m_kernel.get_cancel_flag(); + } + + void updt_params(params_ref const & p) { + params2front_end_params(p, fparams()); + } + + void collect_param_descrs(param_descrs & d) { + solver_front_end_params_descrs(d); + } + }; + + solver::solver(ast_manager & m, front_end_params & fp, params_ref const & p) { + m_imp = alloc(imp, m, fp, p); + } + + solver::~solver() { + dealloc(m_imp); + } + + ast_manager & solver::m() const { + return m_imp->m(); + } + + bool solver::set_logic(symbol logic) { + return m_imp->set_logic(logic); + } + + void solver::set_progress_callback(progress_callback * callback) { + m_imp->set_progress_callback(callback); + } + + void solver::assert_expr(expr * e) { + m_imp->assert_expr(e); + } + + void solver::assert_expr(expr * e, proof * pr) { + m_imp->assert_expr(e, pr); + } + + unsigned solver::size() const { + return m_imp->size(); + } + + expr * const * solver::get_formulas() const { + return m_imp->get_formulas(); + } + + bool solver::reduce() { + return m_imp->reduce(); + } + + void solver::push() { + m_imp->push(); + } + + void solver::pop(unsigned num_scopes) { + m_imp->pop(num_scopes); + } + + unsigned solver::get_scope_level() const { + return m_imp->get_scope_level(); + } + + void solver::reset() { + ast_manager & _m = m(); + front_end_params & fps = m_imp->fparams(); + params_ref ps = m_imp->params(); + #pragma omp critical (smt_solver) + { + dealloc(m_imp); + m_imp = alloc(imp, _m, fps, ps); + } + } + + bool solver::inconsistent() { + return m_imp->inconsistent(); + } + + lbool solver::setup_and_check() { + set_cancel(false); + return m_imp->setup_and_check(); + } + + lbool solver::check(unsigned num_assumptions, expr * const * assumptions) { + set_cancel(false); + lbool r = m_imp->check(num_assumptions, assumptions); + TRACE("smt_solver", tout << "check result: " << r << "\n";); + return r; + } + + void solver::get_model(model_ref & m) const { + m_imp->get_model(m); + } + + proof * solver::get_proof() { + return m_imp->get_proof(); + } + + unsigned solver::get_unsat_core_size() const { + return m_imp->get_unsat_core_size(); + } + + expr * solver::get_unsat_core_expr(unsigned idx) const { + return m_imp->get_unsat_core_expr(idx); + } + + failure solver::last_failure() const { + return m_imp->last_failure(); + } + + std::string solver::last_failure_as_string() const { + return m_imp->last_failure_as_string(); + } + + void solver::get_assignments(expr_ref_vector & result) { + m_imp->get_assignments(result); + } + + void solver::get_relevant_labels(expr * cnstr, buffer & result) { + m_imp->get_relevant_labels(cnstr, result); + } + + void solver::get_relevant_labeled_literals(bool at_lbls, expr_ref_vector & result) { + m_imp->get_relevant_labeled_literals(at_lbls, result); + } + + void solver::get_relevant_literals(expr_ref_vector & result) { + m_imp->get_relevant_literals(result); + } + + void solver::get_guessed_literals(expr_ref_vector & result) { + m_imp->get_guessed_literals(result); + } + + void solver::display(std::ostream & out) const { + m_imp->display(out); + } + + void solver::collect_statistics(::statistics & st) const { + m_imp->collect_statistics(st); + } + + void solver::reset_statistics() { + m_imp->reset_statistics(); + } + + void solver::display_statistics(std::ostream & out) const { + m_imp->display_statistics(out); + } + + void solver::display_istatistics(std::ostream & out) const { + m_imp->display_istatistics(out); + } + + void solver::set_cancel(bool f) { + #pragma omp critical (smt_solver) + { + if (m_imp) + m_imp->set_cancel(f); + } + } + + bool solver::canceled() const { + return m_imp->canceled(); + } + + void solver::updt_params(params_ref const & p) { + return m_imp->updt_params(p); + } + + void solver::collect_param_descrs(param_descrs & d) const { + return m_imp->collect_param_descrs(d); + } + + context & solver::kernel() { + return m_imp->m_kernel; + } + +}; diff --git a/lib/smt_solver.h b/lib/smt_solver.h new file mode 100644 index 000000000..68d85e8d6 --- /dev/null +++ b/lib/smt_solver.h @@ -0,0 +1,247 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + smt_solver.h + +Abstract: + + New frontend for the incremental solver. + +Author: + + Leonardo de Moura (leonardo) 2012-02-09. + +Revision History: + +--*/ +#ifndef _SMT_SOLVER_H_ +#define _SMT_SOLVER_H_ + +#include"ast.h" +#include"params.h" +#include"model.h" +#include"lbool.h" +#include"statistics.h" +#include"smt_failure.h" + +struct front_end_params; +class progress_callback; + +namespace smt { + + class enode; + class context; + + class solver { + struct imp; + imp * m_imp; + public: + solver(ast_manager & m, front_end_params & fp, params_ref const & p = params_ref()); + + ~solver(); + + ast_manager & m() const; + + /** + \brief Set logic. It must be invoked before any assertions. + Return true if succeeded. + */ + bool set_logic(symbol logic); + + /** + brief Set progress meter. Solver will invoke the callback from time to time. + */ + void set_progress_callback(progress_callback * callback); + + /** + \brief Assert the given assetion into the logical context. + This method uses the "asserted" proof as a justification for e. + */ + void assert_expr(expr * e); + + /** + \brief Assert the given assertion with the given proof as a justification. + */ + void assert_expr(expr * e, proof * pr); + + /** + \brief Return the number of asserted formulas in the solver. + */ + unsigned size() const; + + /** + \brief Return the array of asserted formulas. + */ + expr * const * get_formulas() const; + + /** + \brief Reduce the set of asserted formulas using preprocessors. + Return true if an inconsistency is detected. + + \remark This is mainly used by dl_smt_relation. This method + seens to be misplaced. This is not the right place. + */ + bool reduce(); + + /** + \brief Create a backtracking point (aka scope level). + */ + void push(); + + /** + \brief Backtrack the given number of scope levels. + */ + void pop(unsigned num_scopes); + + /** + \brief Return the number of backtracking points. + */ + unsigned get_scope_level() const; + + /** + \brief Reset the solver. + All assertions are erased. + */ + void reset(); + + /** + \brief Return true if the set of asserted formulas is known to be inconsistent. + */ + bool inconsistent(); + + /** + \brief Setup the logical context and invoke check. + */ + lbool setup_and_check(); + + /** + \brief Satisfiability check. + */ + lbool check(unsigned num_assumptions = 0, expr * const * assumptions = 0); + + /** + \brief Return the model associated with the last check command. + */ + void get_model(model_ref & m) const; + + /** + \brief Return the proof of unsatisfiability associated with the last check command. + */ + proof * get_proof(); + + /** + \brief Return the size of the unsat core associated with the last check command. + */ + unsigned get_unsat_core_size() const; + + /** + \brief Return the i-th expression in the unsat core associated with the last check command. + + \pre i < get_unsat_core_size() + */ + expr * get_unsat_core_expr(unsigned i) const; + + /** + \brief Return the reason for failure for the last check command. + Failure means, it returned l_undef + */ + failure last_failure() const; + + /** + \brief Return a string describing the failure. + */ + std::string last_failure_as_string() const; + + /** + \brief Return the set of formulas assigned by the solver. + */ + void get_assignments(expr_ref_vector & result); + + /** + \brief Return the set of relevant labels in the last check command. + */ + void get_relevant_labels(expr * cnstr, buffer & result); + + /** + \brief Return the relevant labeled_literals in the last check command. + */ + void get_relevant_labeled_literals(bool at_lbls, expr_ref_vector & result); + + /** + \brief Return the relevant literals in the last check command. + */ + void get_relevant_literals(expr_ref_vector & result); + + /** + \brief Return the set of guessed literals (decisions) performed in the last check command. + */ + void get_guessed_literals(expr_ref_vector & result); + + /** + \brief (For debubbing purposes) Prints the state of the solver + */ + void display(std::ostream & out) const; + + /** + \brief Collect runtime statistics. + */ + void collect_statistics(::statistics & st) const; + + /** + \brief Reset solver statistics. + */ + void reset_statistics(); + + /** + \brief Display statistics. + */ + void display_statistics(std::ostream & out) const; + + /** + \brief Display statistics in low level format. + */ + void display_istatistics(std::ostream & out) const; + + /** + \brief Interrupt the solver. + */ + void set_cancel(bool f = true); + void cancel() { set_cancel(true); } + + /** + \brief Reset interruption. + */ + void reset_cancel() { set_cancel(false); } + + /** + \brief Return true if the solver was interrupted. + */ + bool canceled() const; + + /** + \brief Update configuration parameters. + */ + void updt_params(params_ref const & p); + + /** + \brief Collect a description of the configuration parameters. + */ + void collect_param_descrs(param_descrs & d) const; + + /** + \brief Return a reference to the kernel. + This is a temporary hack to support user theories. + TODO: remove this hack. + We need to revamp user theories too. + + This method breaks the abstraction barrier. + + \warning We should not use this method + */ + context & kernel(); + }; +}; + +#endif diff --git a/lib/smt_solver_exp.cpp b/lib/smt_solver_exp.cpp new file mode 100644 index 000000000..d65562dfa --- /dev/null +++ b/lib/smt_solver_exp.cpp @@ -0,0 +1,232 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + smt_solver_exp.cpp + +Abstract: + + SMT solver using strategies and search on top of sat::solver + This solver uses assertion_set strategies during restarts. + + It also uses the sat::solver to handle the Boolean structure of the problem. + + +Author: + + Leonardo de Moura (leonardo) 2011-06-25. + +Revision History: + This was an experiment to rewrite Z3 kernel. + It will be deleted after we finish revamping Z3 kernel. + +--*/ +#include"smt_solver_exp.h" +#include"sat_solver.h" +#include"ast_translation.h" +#include"model.h" + +namespace smt { + + void solver_exp::bridge::get_antecedents(sat::literal l, sat::ext_justification_idx idx, sat::literal_vector & r) { + } + + void solver_exp::bridge::asserted(sat::literal l) { + } + + sat::check_result solver_exp::bridge::check() { + return sat::CR_DONE; + } + + void solver_exp::bridge::push() { + } + + void solver_exp::bridge::pop(unsigned n) { + } + + void solver_exp::bridge::simplify() { + } + + void solver_exp::bridge::clauses_modifed() { + } + + lbool solver_exp::bridge::get_phase(sat::bool_var v) { + return l_undef; + } + + solver_exp::solver_exp(ast_manager & ext_mng, params_ref const & p): + m_ext_mng(ext_mng), + m(ext_mng, true /* disable proof gen */), + m_compiler(*this, p), + m_assertions(m), + m_atom2bvar(m), + m_arith(m, p), + m_bridge(*this) { + updt_params_core(p); + m_cancel = false; + } + + solver_exp::~solver_exp() { + } + + void solver_exp::updt_params_core(params_ref const & p) { + m_params = p; + } + + void solver_exp::updt_params(params_ref const & p) { + updt_params_core(p); + m_arith.updt_params(p); + if (m_sat) + m_sat->updt_params(p); + } + + void solver_exp::collect_param_descrs(param_descrs & d) { + // TODO + } + + void solver_exp::set_cancel(bool f) { + m_cancel = f; + #pragma omp critical (smt_solver_exp) + { + if (m_sat) { + m_sat->set_cancel(f); + } + } + m_arith.set_cancel(f); + m_compiler.set_cancel(f); + } + + void solver_exp::init() { + m_atom2bvar.reset(); + if (m_sat) + m_sat->collect_statistics(m_stats); + #pragma omp critical (smt_solver_exp) + { + m_sat = alloc(sat::solver, m_params, &m_bridge); + } + m_arith.collect_statistics(m_stats); + m_arith.reset(); + set_cancel(m_cancel); + } + + void solver_exp::assert_expr_core(expr * t, ast_translation & translator) { + expr * new_t = translator(t); + m_assertions.assert_expr(new_t); + } + + /** + \brief Assert an expression t (owned by the external manager) + */ + void solver_exp::assert_expr(expr * t) { + ast_translation translator(m_ext_mng, m, false); + assert_expr_core(t, translator); + } + + /** + \brief Assert an assertion set s (owned by the external manager) + */ + void solver_exp::assert_set(assertion_set const & s) { + SASSERT(&(s.m()) == &m_ext_mng); + ast_translation translator(m_ext_mng, m, false); + unsigned sz = s.size(); + for (unsigned i = 0; i < sz; i++) { + assert_expr_core(s.form(i), translator); + } + } + + void solver_exp::assert_goal(goal const & g) { + SASSERT(&(g.m()) == &m_ext_mng); + ast_translation translator(m_ext_mng, m, false); + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) { + assert_expr_core(g.form(i), translator); + } + } + + /** + \brief Store in r the current set of assertions. + r is (owned) by the external assertion set + */ + void solver_exp::get_assertions(assertion_set & r) { + SASSERT(&(r.m()) == &m_ext_mng); + ast_translation translator(m, m_ext_mng, false); + unsigned sz = m_assertions.size(); + for (unsigned i = 0; i < sz; i++) { + expr * f = m_assertions.form(i); + r.assert_expr(translator(f)); + } + } + + void solver_exp::get_model_converter(model_converter_ref & mc) { + ast_translation translator(m, m_ext_mng, false); + if (m_mc) + mc = m_mc->translate(translator); + else + mc = 0; + } + + // ----------------------- + // + // Search + // + // ----------------------- + lbool solver_exp::check() { + compile(); + lbool r = m_arith.check(); + if (r == l_false) + return r; + if (m_sat->num_vars() == 0 && r == l_true) { + model_ref md = alloc(model, m); + m_arith.mk_model(md.get()); + if (m_mc) + (*m_mc)(md); + ast_translation translator(m, m_ext_mng, false); + m_model = md->translate(translator); + return l_true; + } + return l_undef; + } + + void solver_exp::compile() { + m_compiler(); + } + + // ----------------------- + // + // Pretty Printing + // + // ----------------------- + void solver_exp::display(std::ostream & out) const { + m_assertions.display(out); + } + + void solver_exp::display_state(std::ostream & out) const { + if (m_sat) m_sat->display(out); + m_arith.display(out); + } + + // ----------------------- + // + // Statistics + // + // ----------------------- + void solver_exp::collect_statistics(statistics & st) const { + solver_exp * _this = const_cast(this); + if (m_sat) { + m_sat->collect_statistics(_this->m_stats); + m_sat->reset_statistics(); + } + m_arith.collect_statistics(_this->m_stats); + _this->m_arith.reset_statistics(); + st.copy(m_stats); + } + + void solver_exp::reset_statistics() { + m_stats.reset(); + } + + +}; + + diff --git a/lib/smt_solver_exp.h b/lib/smt_solver_exp.h new file mode 100644 index 000000000..f686f02e7 --- /dev/null +++ b/lib/smt_solver_exp.h @@ -0,0 +1,130 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + smt_solver_exp.h + +Abstract: + + SMT solver using strategies and search on top of sat::solver + This solver uses assertion_set strategies during restarts. + + It also uses the sat::solver to handle the Boolean structure of the problem. + +Author: + + Leonardo de Moura (leonardo) 2011-06-25. + +Revision History: + This was an experiment to rewrite Z3 kernel. + It will be deleted after we finish revamping Z3 kernel. +--*/ +#ifndef _SMT_SOLVER_EXP_H_ +#define _SMT_SOLVER_EXP_H_ + +#include"smt_solver_types.h" +#include"model.h" +#include"model_converter.h" +#include"smt_formula_compiler.h" +#include"smt_arith.h" +#include"sat_extension.h" +#include"goal.h" + +namespace smt { + + class solver_exp { + friend class formula_compiler; + + struct bridge : public sat::extension { + solver_exp & s; + bridge(solver_exp & _s):s(_s) {} + virtual void propagate(sat::literal l, sat::ext_constraint_idx idx, bool & keep) {} + virtual void get_antecedents(sat::literal l, sat::ext_justification_idx idx, sat::literal_vector & r); + virtual void asserted(sat::literal l); + virtual sat::check_result check(); + virtual void push(); + virtual void pop(unsigned n); + virtual void simplify(); + virtual void clauses_modifed(); + virtual lbool get_phase(sat::bool_var v); + }; + + // External ASTs are coming from m_ext_mng + ast_manager & m_ext_mng; + // The ASTs are translated into the internal manager for the following reasons: + // 1. We can run multiple smt::solver_exps in parallel with minimal synchronization + // 2. Minimize gaps in the AST ids. + ast_manager m; // internal manager + params_ref m_params; + formula_compiler m_compiler; + + // Set of asserted expressions. + // This assertion set belongs to ast_manager m. + assertion_set m_assertions; + + model_ref m_model; + model_converter_ref m_mc; // chain of model converters + + atom2bool_var m_atom2bvar; + scoped_ptr m_sat; + arith m_arith; + bridge m_bridge; + + statistics m_stats; + + volatile bool m_cancel; + + void updt_params_core(params_ref const & p); + void assert_expr_core(expr * t, ast_translation & translator); + + void init(); + void compile(); + + public: + solver_exp(ast_manager & ext_mng, params_ref const & p); + ~solver_exp(); + + void updt_params(params_ref const & p); + void collect_param_descrs(param_descrs & d); + + void set_cancel(bool f); + + void assert_expr(expr * t); + void assert_set(assertion_set const & s); + void assert_goal(goal const & g); + + void get_assertions(assertion_set & r); + + // ----------------------- + // + // Search + // + // ----------------------- + public: + lbool check(); + void get_model(model_ref & m) const { m = m_model.get(); } + void get_model_converter(model_converter_ref & mc); + + // ----------------------- + // + // Pretty Printing + // + // ----------------------- + public: + void display(std::ostream & out) const; + void display_state(std::ostream & out) const; + + // ----------------------- + // + // Statistics + // + // ----------------------- + public: + void collect_statistics(statistics & st) const; + void reset_statistics(); + }; + +}; + +#endif diff --git a/lib/smt_solver_strategy.cpp b/lib/smt_solver_strategy.cpp new file mode 100644 index 000000000..237cdcecf --- /dev/null +++ b/lib/smt_solver_strategy.cpp @@ -0,0 +1,96 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + sat_solver_strategy.cpp + +Abstract: + + Strategy for using the SMT solver. + +Author: + + Leonardo (leonardo) 2011-06-25 + +Notes: + +--*/ +#include"smt_solver_strategy.h" +#include"smt_solver_exp.h" + +smt_solver_strategy::smt_solver_strategy(ast_manager & _m, params_ref const & p): + m(_m), + m_params(p) { +} + +smt_solver_strategy::~smt_solver_strategy() { +} + +void smt_solver_strategy::init_solver() { + smt::solver_exp * new_solver = alloc(smt::solver_exp, m, m_params); + #pragma omp critical (as_st_cancel) + { + m_solver = new_solver; + } +} + +void smt_solver_strategy::updt_params(params_ref const & p) { + m_params = p; +} + +void smt_solver_strategy::get_param_descrs(param_descrs & r) { + // TODO +} + +void smt_solver_strategy::operator()(assertion_set & s, model_converter_ref & mc) { + if (s.m().proofs_enabled()) + throw smt_solver_exception("smt quick solver does not support proof generation"); + mc = 0; + s.elim_redundancies(); + if (s.inconsistent()) + return; + + init_solver(); + m_solver->assert_set(s); + s.reset(); + + lbool r = m_solver->check(); + m_solver->collect_statistics(m_stats); + + if (r == l_false) { + s.assert_expr(m.mk_false()); + } + else if (r == l_true) { + model_ref md; + m_solver->get_model(md); + mc = model2model_converter(md.get()); + } + else { + // recover simplified assertion set + m_solver->get_assertions(s); + m_solver->get_model_converter(mc); + } +} + +void smt_solver_strategy::cleanup() { + if (m_solver) + m_solver->collect_statistics(m_stats); + #pragma omp critical (as_st_cancel) + { + m_solver = 0; + } +} + +void smt_solver_strategy::set_cancel(bool f) { + if (m_solver) + m_solver->set_cancel(f); +} + +void smt_solver_strategy::reset_statistics() { + m_stats.reset(); +} + +void smt_solver_strategy::collect_statistics(statistics & st) const { + st.copy(m_stats); +} diff --git a/lib/smt_solver_strategy.h b/lib/smt_solver_strategy.h new file mode 100644 index 000000000..833d5326b --- /dev/null +++ b/lib/smt_solver_strategy.h @@ -0,0 +1,55 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + smt_solver_strategy.h + +Abstract: + + Strategy for using the SAT solver. + +Author: + + Leonardo (leonardo) 2011-06-25 + +Notes: + +--*/ +#ifndef _SMT_SOLVER_STRATEGY_H_ +#define _SMT_SOLVER_STRATEGY_H_ + +#include"assertion_set_strategy.h" + +namespace smt { class solver_exp; }; + +class smt_solver_strategy : public assertion_set_strategy { + struct imp; + ast_manager & m; + params_ref m_params; + statistics m_stats; + scoped_ptr m_solver; + void init_solver(); +public: + smt_solver_strategy(ast_manager & m, params_ref const & p = params_ref()); + virtual ~smt_solver_strategy(); + + virtual void updt_params(params_ref const & p); + static void get_param_descrs(param_descrs & r); + virtual void collect_param_descrs(param_descrs & r) { get_param_descrs(r); } + + virtual void operator()(assertion_set & s, model_converter_ref & mc); + + virtual void cleanup(); + + virtual void collect_statistics(statistics & st) const; + virtual void reset_statistics(); +protected: + virtual void set_cancel(bool f); +}; + +inline as_st * mk_smt2_solver(ast_manager & m, params_ref const & p = params_ref()) { + return clean(alloc(smt_solver_strategy, m, p)); +} + +#endif diff --git a/lib/smt_solver_types.h b/lib/smt_solver_types.h new file mode 100644 index 000000000..7680c5727 --- /dev/null +++ b/lib/smt_solver_types.h @@ -0,0 +1,47 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + smt_solver_types.h + +Abstract: + + Auxiliary definitions for smt::solver class. + +Author: + + Leonardo de Moura (leonardo) 2011-06-25. + +Revision History: + This was an experiment to rewrite Z3 kernel. + It will be deleted after we finish revamping Z3 kernel. + +--*/ +#ifndef _SMT_SOLVER_TYPES_H_ +#define _SMT_SOLVER_TYPES_H_ + +#include"assertion_set.h" +#include"strategy_exception.h" +#include"params.h" +#include"statistics.h" +#include"lbool.h" +#include"sat_types.h" + +class ast_translation; + +namespace sat { + class solver; +}; + +namespace smt { + class solver_exp; + class formula_compiler; + typedef unsigned atom_id; + typedef unsigned_vector atom_id_vector; + const atom_id null_atom_id = sat::null_bool_var; +}; + +MK_ST_EXCEPTION(smt_solver_exception); + +#endif diff --git a/lib/smt_statistics.cpp b/lib/smt_statistics.cpp new file mode 100644 index 000000000..5201e48b3 --- /dev/null +++ b/lib/smt_statistics.cpp @@ -0,0 +1,29 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_statistics.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-21. + +Revision History: + +--*/ +#include +#include"smt_statistics.h" + +namespace smt { + + void statistics::reset() { + memset(this, 0, sizeof(statistics)); + } + +}; + diff --git a/lib/smt_statistics.h b/lib/smt_statistics.h new file mode 100644 index 000000000..a4fccf8d1 --- /dev/null +++ b/lib/smt_statistics.h @@ -0,0 +1,60 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_statistics.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-21. + +Revision History: + +--*/ +#ifndef _SMT_STATISTICS_H_ +#define _SMT_STATISTICS_H_ + +#include + +namespace smt { + + struct statistics { + unsigned m_num_propagations; + unsigned m_num_bin_propagations; + unsigned m_num_conflicts; + unsigned m_num_sat_conflicts; + unsigned m_num_decisions; + unsigned m_num_add_eq; + unsigned m_num_restarts; + unsigned m_num_final_checks; + unsigned m_num_mk_bool_var; + unsigned m_num_del_bool_var; + unsigned m_num_mk_enode; + unsigned m_num_del_enode; + unsigned m_num_mk_clause; + unsigned m_num_del_clause; + unsigned m_num_mk_bin_clause; + unsigned m_num_mk_lits; + unsigned m_num_dyn_ack; + unsigned m_num_del_dyn_ack; + unsigned m_num_interface_eqs; + unsigned m_max_generation; + unsigned m_num_minimized_lits; + unsigned m_num_checks; + statistics() { + reset(); + } + + void reset(); + }; +}; + + + +#endif /* _SMT_STATISTICS_H_ */ + diff --git a/lib/smt_strategic_solver.cpp b/lib/smt_strategic_solver.cpp new file mode 100644 index 000000000..7776a95c2 --- /dev/null +++ b/lib/smt_strategic_solver.cpp @@ -0,0 +1,104 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + smt_strategic_solver.h + +Abstract: + + Create a strategic solver with tactic for all main logics + used in SMT. + +Author: + + Leonardo (leonardo) 2012-02-19 + +Notes: + +--*/ +#include"cmd_context.h" +#include"ni_solver.h" +#include"strategic_solver.h" +#include"qfbv_tactic.h" +#include"qflia_tactic.h" +#include"qfnia_tactic.h" +#include"qfnra_tactic.h" +#include"qfuf_tactic.h" +#include"qflra_tactic.h" +#include"quant_tactics.h" +#include"qfauflia_tactic.h" +#include"qfaufbv_tactic.h" +#include"qfufbv_tactic.h" +#include"qfidl_tactic.h" +#include"default_tactic.h" +#include"ufbv_strategy.h" +#include"st2tactic.h" +#include"qffpa_tactic.h" + +#define MK_ST2TACTIC_FACTORY(NAME, ST) \ +class NAME : public tactic_factory { \ +public: \ + virtual ~NAME() {} \ + virtual tactic * operator()(ast_manager & m, params_ref const & p) { return st2tactic(ST); } \ +}; + +MK_ST2TACTIC_FACTORY(ufbv_fct, mk_ufbv_strategy(m, p)); + +MK_SIMPLE_TACTIC_FACTORY(qfuf_fct, mk_qfuf_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(qfidl_fct, mk_qfidl_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(qfauflia_fct, mk_qfauflia_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(auflia_fct, mk_auflia_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(auflira_fct, mk_auflira_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(aufnira_fct, mk_aufnira_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(ufnia_fct, mk_ufnia_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(uflra_fct, mk_uflra_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(lra_fct, mk_lra_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(qfbv_fct, mk_qfbv_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(default_fct, mk_default_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(qfaufbv_fct, mk_qfaufbv_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(qflra_fct, mk_qflra_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(qflia_fct, mk_qflia_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(qfufbv_fct, mk_qfufbv_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(qfnia_fct, mk_qfnia_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(qfnra_fct, mk_qfnra_tactic(m, p)); +MK_SIMPLE_TACTIC_FACTORY(qffpa_fct, mk_qffpa_tactic(m, p)); + +static void init(strategic_solver * s) { + s->set_default_tactic(alloc(default_fct)); + s->set_tactic_for(symbol("QF_UF"), alloc(qfuf_fct)); + s->set_tactic_for(symbol("QF_BV"), alloc(qfbv_fct)); + s->set_tactic_for(symbol("QF_IDL"), alloc(qfidl_fct)); + s->set_tactic_for(symbol("QF_LIA"), alloc(qflia_fct)); + s->set_tactic_for(symbol("QF_LRA"), alloc(qflra_fct)); + s->set_tactic_for(symbol("QF_NIA"), alloc(qfnia_fct)); + s->set_tactic_for(symbol("QF_NRA"), alloc(qfnra_fct)); + s->set_tactic_for(symbol("QF_AUFLIA"), alloc(qfauflia_fct)); + s->set_tactic_for(symbol("QF_AUFBV"), alloc(qfaufbv_fct)); + s->set_tactic_for(symbol("QF_ABV"), alloc(qfaufbv_fct)); + s->set_tactic_for(symbol("QF_UFBV"), alloc(qfufbv_fct)); + s->set_tactic_for(symbol("AUFLIA"), alloc(auflia_fct)); + s->set_tactic_for(symbol("AUFLIRA"), alloc(auflira_fct)); + s->set_tactic_for(symbol("AUFNIRA"), alloc(aufnira_fct)); + s->set_tactic_for(symbol("UFNIA"), alloc(ufnia_fct)); + s->set_tactic_for(symbol("UFLRA"), alloc(uflra_fct)); + s->set_tactic_for(symbol("LRA"), alloc(lra_fct)); + s->set_tactic_for(symbol("UFBV"), alloc(ufbv_fct)); + s->set_tactic_for(symbol("BV"), alloc(ufbv_fct)); + s->set_tactic_for(symbol("QF_FPA"), alloc(qffpa_fct)); +} + +solver * mk_smt_strategic_solver(cmd_context & ctx) { + strategic_solver * s = alloc(strategic_solver_cmd, ctx); + s->set_inc_solver(mk_quasi_incremental_smt_solver(ctx)); + init(s); + return s; +} + +solver * mk_smt_strategic_solver(bool force_tactic) { + strategic_solver * s = alloc(strategic_solver_api); + s->force_tactic(force_tactic); + s->set_inc_solver(mk_default_solver()); + init(s); + return s; +} diff --git a/lib/smt_strategic_solver.h b/lib/smt_strategic_solver.h new file mode 100644 index 000000000..f3cc2e0e0 --- /dev/null +++ b/lib/smt_strategic_solver.h @@ -0,0 +1,29 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + smt_strategic_solver.h + +Abstract: + + Create a strategic solver with tactic for all main logics + used in SMT. + +Author: + + Leonardo (leonardo) 2012-02-19 + +Notes: + +--*/ +#ifndef _SMT_STRATEGIC_SOLVER_H_ +#define _SMT_STRATEGIC_SOLVER_H_ + +class cmd_context; +// Create a strategic solver for the SMT 2.0 frontend. +solver * mk_smt_strategic_solver(cmd_context & ctx); +// Create a strategic solver for the Z3 API +solver * mk_smt_strategic_solver(bool force_tactic=false); + +#endif diff --git a/lib/smt_tactic.cpp b/lib/smt_tactic.cpp new file mode 100644 index 000000000..094b16d42 --- /dev/null +++ b/lib/smt_tactic.cpp @@ -0,0 +1,317 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + smt_tactic.h + +Abstract: + + smt::context as a tactic. + +Author: + + Leonardo (leonardo) 2011-10-18 + +Notes: + +--*/ +#include"tactic.h" +#include"tactical.h" +#include"smt_solver.h" +#include"front_end_params.h" +#include"params2front_end_params.h" + +class smt_tactic : public tactic { + scoped_ptr m_params; + params_ref m_params_ref; + statistics m_stats; + std::string m_failure; + smt::solver * m_ctx; + symbol m_logic; + progress_callback * m_callback; + bool m_candidate_models; + bool m_fail_if_inconclusive; + +public: + smt_tactic(params_ref const & p): + m_params_ref(p), + m_ctx(0), + m_callback(0) { + updt_params_core(p); + TRACE("smt_tactic", tout << this << "\np: " << p << "\n";); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(smt_tactic, m_params_ref); + } + + virtual ~smt_tactic() { + SASSERT(m_ctx == 0); + } + + front_end_params & fparams() { + if (!m_params) { + m_params = alloc(front_end_params); + params2front_end_params(m_params_ref, fparams()); + } + return *m_params; + } + + void updt_params_core(params_ref const & p) { + m_candidate_models = p.get_bool(":candidate-models", false); + m_fail_if_inconclusive = p.get_bool(":fail-if-inconclusive", true); + } + + virtual void updt_params(params_ref const & p) { + TRACE("smt_tactic", tout << this << "\nupdt_params: " << p << "\n";); + updt_params_core(p); + m_params_ref = p; + params2front_end_params(m_params_ref, fparams()); + SASSERT(p.get_bool(":auto_config", fparams().m_auto_config) == fparams().m_auto_config); + } + + virtual void collect_param_descrs(param_descrs & r) { + r.insert(":candidate-models", CPK_BOOL, "(default: false) create candidate models even when quantifier or theory reasoning is incomplete."); + r.insert(":fail-if-inconclusive", CPK_BOOL, "(default: true) fail if found unsat (sat) for under (over) approximated goal."); + solver_front_end_params_descrs(r); + } + + virtual void set_cancel(bool f) { + if (m_ctx) + m_ctx->set_cancel(f); + } + + virtual void collect_statistics(statistics & st) const { + if (m_ctx) + m_ctx->collect_statistics(st); // ctx is still running... + else + st.copy(m_stats); + } + + virtual void cleanup() { + } + + virtual void reset_statistics() { + m_stats.reset(); + } + + // for backward compatibility + virtual void set_front_end_params(front_end_params & p) { + m_params = alloc(front_end_params, p); + SASSERT(m_params.get() == &fparams()); + // must propagate the params_ref to fparams + params2front_end_params(m_params_ref, fparams()); + } + + virtual void set_logic(symbol const & l) { + m_logic = l; + } + + virtual void set_progress_callback(progress_callback * callback) { + m_callback = callback; + } + + struct scoped_init_ctx { + smt_tactic & m_owner; + + scoped_init_ctx(smt_tactic & o, ast_manager & m):m_owner(o) { + smt::solver * new_ctx = alloc(smt::solver, m, o.fparams()); + TRACE("smt_tactic", tout << "logic: " << o.m_logic << "\n";); + new_ctx->set_logic(o.m_logic); + if (o.m_callback) { + new_ctx->set_progress_callback(o.m_callback); + } + #pragma omp critical (as_st_solver) + { + o.m_ctx = new_ctx; + } + } + + ~scoped_init_ctx() { + smt::solver * d = m_owner.m_ctx; + #pragma omp critical (as_st_cancel) + { + m_owner.m_ctx = 0; + } + if (d) + dealloc(d); + } + }; + + typedef obj_map expr2expr_map; + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(in->is_well_sorted()); + ast_manager & m = in->m(); + TRACE("smt_tactic", tout << this << "\nAUTO_CONFIG: " << fparams().m_auto_config << " HIDIV0: " << fparams().m_hi_div0 << " " + << " PREPROCESS: " << fparams().m_preprocess << ", SOLVER:" << fparams().m_solver << "\n"; + tout << "fail-if-inconclusive: " << m_fail_if_inconclusive << "\n"; + tout << "params_ref: " << m_params_ref << "\n";); + TRACE("smt_tactic_detail", in->display(tout);); + TRACE("smt_tactic_memory", tout << "wasted_size: " << m.get_allocator().get_wasted_size() << "\n";); + scoped_init_ctx init(*this, m); + SASSERT(m_ctx != 0); + + scoped_ptr dep2bool; + scoped_ptr bool2dep; + ptr_vector assumptions; + if (in->unsat_core_enabled()) { + if (in->proofs_enabled()) + throw tactic_exception("smt tactic does not support simultaneous generation of proofs and unsat cores"); + dep2bool = alloc(expr2expr_map); + bool2dep = alloc(expr2expr_map); + ptr_vector deps; + ptr_vector clause; + unsigned sz = in->size(); + for (unsigned i = 0; i < sz; i++) { + expr * f = in->form(i); + expr_dependency * d = in->dep(i); + if (d == 0) { + m_ctx->assert_expr(f); + } + else { + // create clause (not d1 \/ ... \/ not dn \/ f) when the d's are the assumptions/dependencies of f. + clause.reset(); + clause.push_back(f); + deps.reset(); + m.linearize(d, deps); + SASSERT(!deps.empty()); // d != 0, then deps must not be empty + ptr_vector::iterator it = deps.begin(); + ptr_vector::iterator end = deps.end(); + for (; it != end; ++it) { + expr * d = *it; + if (is_uninterp_const(d) && m.is_bool(d)) { + // no need to create a fresh boolean variable for d + if (!bool2dep->contains(d)) { + assumptions.push_back(d); + bool2dep->insert(d, d); + } + clause.push_back(m.mk_not(d)); + } + else { + // must normalize assumption + expr * b = 0; + if (!dep2bool->find(d, b)) { + b = m.mk_fresh_const(0, m.mk_bool_sort()); + dep2bool->insert(d, b); + bool2dep->insert(b, d); + assumptions.push_back(b); + } + clause.push_back(m.mk_not(b)); + } + } + SASSERT(clause.size() > 1); + expr_ref cls(m); + cls = m.mk_or(clause.size(), clause.c_ptr()); + m_ctx->assert_expr(cls); + } + } + } + else if (in->proofs_enabled()) { + unsigned sz = in->size(); + for (unsigned i = 0; i < sz; i++) { + m_ctx->assert_expr(in->form(i), in->pr(i)); + } + } + else { + unsigned sz = in->size(); + for (unsigned i = 0; i < sz; i++) { + m_ctx->assert_expr(in->form(i)); + } + } + + lbool r; + if (assumptions.empty()) + r = m_ctx->setup_and_check(); + else + r = m_ctx->check(assumptions.size(), assumptions.c_ptr()); + m_ctx->collect_statistics(m_stats); + + switch (r) { + case l_true: { + if (m_fail_if_inconclusive && !in->sat_preserved()) + throw tactic_exception("over-approximated goal found to be sat"); + // the empty assertion set is trivially satifiable. + in->reset(); + result.push_back(in.get()); + // store the model in a do nothin model converter. + if (in->models_enabled()) { + model_ref md; + m_ctx->get_model(md); + mc = model2model_converter(md.get()); + } + pc = 0; + core = 0; + return; + } + case l_false: { + if (m_fail_if_inconclusive && !in->unsat_preserved()) { + TRACE("smt_tactic", tout << "failed to show to be unsat...\n";); + throw tactic_exception("under-approximated goal found to be unsat"); + } + // formula is unsat, reset the goal, and store false there. + in->reset(); + proof * pr = 0; + expr_dependency * lcore = 0; + if (in->proofs_enabled()) + pr = m_ctx->get_proof(); + if (in->unsat_core_enabled()) { + unsigned sz = m_ctx->get_unsat_core_size(); + for (unsigned i = 0; i < sz; i++) { + expr * b = m_ctx->get_unsat_core_expr(i); + SASSERT(is_uninterp_const(b) && m.is_bool(b)); + expr * d = bool2dep->find(b); + lcore = m.mk_join(lcore, m.mk_leaf(d)); + } + } + in->assert_expr(m.mk_false(), pr, lcore); + result.push_back(in.get()); + mc = 0; + pc = 0; + core = 0; + return; + } + case l_undef: + if (m_fail_if_inconclusive) + throw tactic_exception("smt tactic failed to show goal to be sat/unsat"); + result.push_back(in.get()); + if (m_candidate_models) { + switch (m_ctx->last_failure()) { + case smt::NUM_CONFLICTS: + case smt::THEORY: + case smt::QUANTIFIERS: + if (in->models_enabled()) { + model_ref md; + m_ctx->get_model(md); + mc = model2model_converter(md.get()); + } + pc = 0; + core = 0; + return; + default: + break; + } + } + m_failure = m_ctx->last_failure_as_string(); + throw tactic_exception(m_failure.c_str()); + } + } +}; + +tactic * mk_smt_tactic(params_ref const & p) { + return alloc(smt_tactic, p); +} + +tactic * mk_smt_tactic_using(bool auto_config, params_ref const & _p) { + params_ref p = _p; + p.set_bool(":auto-config", auto_config); + tactic * r = mk_smt_tactic(p); + TRACE("smt_tactic", tout << "auto_config: " << auto_config << "\nr: " << r << "\np: " << p << "\n";); + return using_params(r, p); +} + diff --git a/lib/smt_tactic.h b/lib/smt_tactic.h new file mode 100644 index 000000000..d570e2bfe --- /dev/null +++ b/lib/smt_tactic.h @@ -0,0 +1,30 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + smt_tactic.h + +Abstract: + + smt::context as a tactic. + +Author: + + Leonardo (leonardo) 2011-10-18 + +Notes: + +--*/ +#ifndef _SMT_TACTIC_H_ +#define _SMT_TACTIC_H_ + +#include"params.h" + +class tactic; + +tactic * mk_smt_tactic(params_ref const & p = params_ref()); +// syntax sugar for using_params(mk_smt_tactic(), p) where p = (:auto_config, auto_config) +tactic * mk_smt_tactic_using(bool auto_config = true, params_ref const & p = params_ref()); + +#endif diff --git a/lib/smt_theory.cpp b/lib/smt_theory.cpp new file mode 100644 index 000000000..fd853d6a6 --- /dev/null +++ b/lib/smt_theory.cpp @@ -0,0 +1,139 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_theory.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-20. + +Revision History: + +--*/ +#include"smt_context.h" +#include"buffer.h" +#include"ast_ll_pp.h" + +namespace smt { + + void theory::init(context * ctx) { + SASSERT(m_context == 0); + m_context = ctx; + m_manager = &(ctx->get_manager()); + } + + void theory::reset_eh() { + m_var2enode.reset(); + } + + void theory::push_scope_eh() { + SASSERT(m_context); + m_var2enode_lim.push_back(m_var2enode.size()); + } + + void theory::pop_scope_eh(unsigned num_scopes) { + SASSERT(m_context); + unsigned scope_lvl = m_var2enode_lim.size(); + SASSERT(num_scopes <= scope_lvl); + unsigned new_lvl = scope_lvl - num_scopes; + unsigned old_sz = m_var2enode_lim[new_lvl]; + m_var2enode.shrink(old_sz); + m_var2enode_lim.shrink(new_lvl); + } + + void theory::display_var2enode(std::ostream & out) const { + unsigned sz = m_var2enode.size(); + for (unsigned v = 0; v < sz; v++) { + out << "v" << v << " -> #" << m_var2enode[v]->get_owner_id() << "\n"; + } + } + + void theory::display_app(std::ostream & out, app * n) const { + func_decl * d = n->get_decl(); + if (n->get_num_args() == 0) { + out << d->get_name(); + display_parameters(out, d->get_num_parameters(), d->get_parameters()); + } + else if (n->get_family_id() == get_family_id()) { + out << "(" << d->get_name(); + display_parameters(out, d->get_num_parameters(), d->get_parameters()); + unsigned num = n->get_num_args(); + for (unsigned i = 0; i < num; i++) { + out << " "; + display_app(out, to_app(n->get_arg(i))); + } + out << ")"; + } + else { + out << "#" << n->get_id(); + } + } + + void theory::display_flat_app(std::ostream & out, app * n) const { + func_decl * d = n->get_decl(); + if (n->get_num_args() == 0) { + out << d->get_name(); + display_parameters(out, d->get_num_parameters(), d->get_parameters()); + } + else if (n->get_family_id() == get_family_id()) { + out << "(" << d->get_name(); + display_parameters(out, d->get_num_parameters(), d->get_parameters()); + ptr_buffer todo; + todo.push_back(n); + while (!todo.empty()) { + n = todo.back(); + todo.pop_back(); + unsigned num = n->get_num_args(); + for (unsigned i = 0; i < num; i++) { + app * arg = to_app(n->get_arg(i)); + if (d->is_associative() && arg->get_decl() == d) { + todo.push_back(arg); + } + else { + out << " "; + display_app(out, arg); + } + } + } + out << ")"; + } + else { + out << "#" << n->get_id(); + } + } + + bool theory::is_relevant_and_shared(enode * n) const { + context & ctx = get_context(); + return ctx.is_relevant(n) && ctx.is_shared(n); + } + + bool theory::assume_eq(enode * n1, enode * n2) { + return get_context().assume_eq(n1, n2); + } + + literal theory::mk_eq(expr * a, expr * b, bool gate_ctx) { + context & ctx = get_context(); + app * eq = ctx.mk_eq_atom(a, b); + TRACE("mk_var_bug", tout << "mk_eq: " << eq->get_id() << " " << a->get_id() << " " << b->get_id() << "\n"; + tout << mk_ll_pp(a, get_manager()) << "\n" << mk_ll_pp(b, get_manager());); + ctx.internalize(eq, gate_ctx); + return ctx.get_literal(eq); + } + + theory::theory(family_id fid): + m_id(fid), + m_context(0), + m_manager(0) { + } + + theory::~theory() { + } + +}; + diff --git a/lib/smt_theory.h b/lib/smt_theory.h new file mode 100644 index 000000000..97a803506 --- /dev/null +++ b/lib/smt_theory.h @@ -0,0 +1,454 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_theory.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-20. + +Revision History: + +--*/ +#ifndef _SMT_THEORY_H_ +#define _SMT_THEORY_H_ + +#include"smt_enode.h" +#include"obj_hashtable.h" +#include"statistics.h" + +namespace smt { + class model_generator; + class model_value_proc; + + class theory { + theory_id m_id; + context * m_context; + ast_manager * m_manager; + enode_vector m_var2enode; + unsigned_vector m_var2enode_lim; + + friend class context; + protected: + virtual void init(context * ctx); + + /* --------------------------------------------------- + + In the logical context, expressions are 'internalized'. That + is, the logical context creates auxiliary data-structures + (e.g., enodes) and attach them to the expressions. The logical + context does not know the internals of each theory. So, during + the internalization process, it notifies the theory (plugin) + whenever it finds an application with a theory function + symbol. + + A theory variable created at scope level n must be deleted + when scope level n is backtracked. + + The logical context uses the method is_attached_to_var + to decide whether an enode is already associated with a theory + variable or not. + + ------------------------------------------------------ */ + + virtual theory_var mk_var(enode * n) { + SASSERT(!is_attached_to_var(n)); + theory_var v = m_var2enode.size(); + m_var2enode.push_back(n); + return v; + } + + public: + /** + \brief Return ture if the given enode is attached to a + variable of the theory. + + \remark The result is not equivalent to + n->get_th_var(get_id()) != null_theory_var + + A theory variable v may be in the list of variables of n, + but it may be inherited from another enode n' during an + equivalence class merge. That is, get_enode(v) != n. + */ + bool is_attached_to_var(enode const * n) const { + theory_var v = n->get_th_var(get_id()); + return v != null_theory_var && get_enode(v) == n; + } + + protected: + /** + \brief Return true if the theory uses default internalization: + "the internalization of an application internalizes all arguments". + Theories like arithmetic do not use default internalization. + For example, in the application (+ a (+ b c)), no enode is created + for (+ b c). + */ + virtual bool default_internalizer() const { + return true; + } + + /** + \brief This method is invoked by the logical context when + atom is being internalized. The theory may return false if it + does not want to implement the given predicate symbol. + + After the execution of this method the given atom must be + associated with a new boolean variable. + */ + virtual bool internalize_atom(app * atom, bool gate_ctx) = 0; + + /** + \brief This method is invoked by the logical context + after the given equality atom is internalized. + */ + virtual void internalize_eq_eh(app * atom, bool_var v) { + } + + /** + \brief This method is invoked by the logical context when + the term is being internalized. The theory may return false + if it does not want to implement the given function symbol. + + After the execution of this method the given term must be + associated with a new enode. + */ + virtual bool internalize_term(app * term) = 0; + + /** + \brief Apply (interpreted) sort constraints on the given enode. + */ + virtual void apply_sort_cnstr(enode * n, sort * s) { + } + + /** + \brief This method is invoked when a truth value is + assigned to the given boolean variable. + */ + virtual void assign_eh(bool_var v, bool is_true) { + } + + /** + \brief Equality propagation (v1 = v2): Core -> Theory + */ + virtual void new_eq_eh(theory_var v1, theory_var v2) = 0; + + /** + \brief Return true if the theory does something with the + disequalities implied by the core. + */ + virtual bool use_diseqs() const { + return true; + } + + /** + \brief Disequality propagation (v1 /= v2): Core -> Theory + */ + virtual void new_diseq_eh(theory_var v1, theory_var v2) = 0; + + /** + \brief This method is invoked when the theory application n + is marked as relevant. + */ + virtual void relevant_eh(app * n) { + } + + /** + \brief This method is invoked when a new backtracking point + is created. + */ + virtual void push_scope_eh(); + + /** + \brief This method is invoked during backtracking. + */ + virtual void pop_scope_eh(unsigned num_scopes); + + /** + \brief This method is invoked when the logical context is being + restarted. + */ + virtual void restart_eh() { + } + + /** + \brief This method is invoked before the search starts. + */ + virtual void init_search_eh() { + } + + /** + \brief This method is invoked when the logical context assigned + a truth value to all boolean variables and no inconsistency was + detected. + */ + virtual final_check_status final_check_eh() { + return FC_DONE; + } + + /** + \brief Parametric theories (e.g. Arrays) should implement this method. + See example in context::is_shared + */ + virtual bool is_shared(theory_var v) const { + return false; + } + + /** + \brief Return true if the theory has something to propagate + */ + virtual bool can_propagate() { + return false; + } + + /** + \brief This method is invoked to give a theory a chance to perform + theory propagation. + */ + virtual void propagate() { + } + + /** + \brief This method allows a theory to contribute to + disequality propagation. + */ + virtual justification * why_is_diseq(theory_var v1, theory_var v2) { + return 0; + } + + /** + \brief Just releases memory. + */ + virtual void flush_eh() { + } + + /** + \brief This method is invoked when the logical context is being reset. + */ + virtual void reset_eh(); + + // ---------------------------------------------------- + // + // Model validation (-vldt flag) + // + // ---------------------------------------------------- + + virtual bool validate_eq_in_model(theory_var v1, theory_var v2, bool is_true) const { + return true; + } + + // ---------------------------------------------------- + // + // Conflict resolution event handler + // + // ---------------------------------------------------- + public: + /** + \brief This method is invoked when a theory atom is used + during conflict resolution. This allows the theory to bump + the activity of the enodes contained in the given atom. + */ + virtual void conflict_resolution_eh(app * atom, bool_var v) { + } + + + public: + theory(family_id fid); + virtual ~theory(); + + virtual void setup() { + } + + theory_id get_id() const { + return m_id; + } + + family_id get_family_id() const { + return m_id; + } + + context & get_context() const { + SASSERT(m_context); + return *m_context; + } + + ast_manager & get_manager() const { + SASSERT(m_manager); + return *m_manager; + } + + enode * get_enode(theory_var v) const { + SASSERT(v < static_cast(m_var2enode.size())); + return m_var2enode[v]; + } + + /** + \brief Return the equivalence class representative + of the given theory variable. + */ + theory_var get_representative(theory_var v) const { + SASSERT(v != null_theory_var); + theory_var r = get_enode(v)->get_root()->get_th_var(get_id()); + SASSERT(r != null_theory_var); + return r; + } + + /** + \brief Return true if the theory variable is the representative + of its equivalence class. + */ + bool is_representative(theory_var v) const { + return get_representative(v) == v; + } + + unsigned get_num_vars() const { + return m_var2enode.size(); + } + + unsigned get_old_num_vars(unsigned num_scopes) const { + return m_var2enode_lim[m_var2enode_lim.size() - num_scopes]; + } + + virtual void display(std::ostream & out) const { + out << "Theory " << static_cast(get_id()) << " does not have a display method\n"; + display_var2enode(out); + } + + virtual void display_var2enode(std::ostream & out) const; + + virtual void collect_statistics(::statistics & st) const { + } + + void display_app(std::ostream & out, app * n) const; + + void display_flat_app(std::ostream & out, app * n) const; + + void display_var_def(std::ostream & out, theory_var v) const { return display_app(out, get_enode(v)->get_owner()); } + + void display_var_flat_def(std::ostream & out, theory_var v) const { return display_flat_app(out, get_enode(v)->get_owner()); } + + /** + \brief Assume eqs between variable that are equal with respect to the given table. + Table is a hashtable indexed by the variable value. + + table.contains(v) should be true if there is v' in table such that assignment of + v is equal to v'. + + This method assumes that class VarValueTable contains the methods: + + - void reset() + - theory_var insert_if_not_there(theory_var v) + */ + template + bool assume_eqs(VarValueTable & table) { + TRACE("assume_eqs", tout << "starting...\n";); + table.reset(); + bool result = false; + int num = get_num_vars(); + for (theory_var v = 0; v < num; v++) { + enode * n = get_enode(v); + theory_var other = null_theory_var; + TRACE("assume_eqs", + tout << "#" << n->get_owner_id() << " is_relevant_and_shared: " << is_relevant_and_shared(n) << "\n";); + if (n != 0 && is_relevant_and_shared(n)) { + other = table.insert_if_not_there(v); + if (other != v) { + enode * n2 = get_enode(other); + TRACE("assume_eqs", tout << "value(#" << n->get_owner_id() << ") = value(#" << n2->get_owner_id() << ")\n";); + if (assume_eq(n, n2)) { + TRACE("assume_eqs", tout << "new assumed eq\n";); + result = true; + } + } + } + } + return result; + } + + /** + \brief When an eq atom n is created during the search, the default behavior is + to make sure that the n->get_arg(0)->get_id() < n->get_arg(1)->get_id(). + This may create some redundant atoms, since some theories/families use different + convetions in their simplifiers. For example, arithmetic always force a numeral + to be in the right hand side. So, this method should be redefined if the default + behavior conflicts with a convention used by the theory/family. + */ + virtual app * mk_eq_atom(expr * lhs, expr * rhs) { + if (lhs->get_id() > rhs->get_id()) + std::swap(lhs, rhs); + return get_manager().mk_eq(lhs, rhs); + } + + literal mk_eq(expr * a, expr * b, bool gate_ctx); + + // ----------------------------------- + // + // Model generation + // + // ----------------------------------- + + /** + \brief Return true if theory support model construction + */ + virtual bool build_models() const { + return true; + } + + virtual void init_model(model_generator & m) { + } + + virtual void finalize_model(model_generator & m) { + } + + /** + \brief Return a functor that can build the value (interpretation) for n. + */ + virtual model_value_proc * mk_value(enode * n, model_generator & mg) { + return 0; + } + + // ----------------------------------- + // + // Model checker + // + // ----------------------------------- + + virtual bool get_value(enode * n, expr_ref & r) { + return false; + } + + virtual char const * get_name() const { return "unknown"; } + + // ----------------------------------- + // + // Return a fresh new instance of the given theory. + // This function is used to create a fresh context (new_ctx) containing the same theories of the context that owns this theory. + // + // We need the parameter new_ctx because of user_smt_theory :-( + // + // ----------------------------------- + virtual theory * mk_fresh(context * new_ctx) = 0; + + protected: + // ---------------------------------------------------- + // + // Auxiliary methods for assume_eqs + // + // smt::context is not defined at this point. + // + // ---------------------------------------------------- + + bool is_relevant_and_shared(enode * n) const; + + bool assume_eq(enode * n1, enode * n2); + }; + +}; + +#endif /* _SMT_THEORY_H_ */ + diff --git a/lib/smt_theory_var_list.h b/lib/smt_theory_var_list.h new file mode 100644 index 000000000..59854791c --- /dev/null +++ b/lib/smt_theory_var_list.h @@ -0,0 +1,76 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_theory_var_list.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-19. + +Revision History: + +--*/ +#ifndef _SMT_THEORY_VAR_LIST_H_ +#define _SMT_THEORY_VAR_LIST_H_ + +#include"smt_types.h" + +namespace smt { + + class theory_var_list { + int m_th_id:8; + int m_th_var:24; + theory_var_list * m_next; + + public: + theory_var_list(): + m_th_id(null_theory_id), + m_th_var(null_theory_var), + m_next(0) { + } + + theory_var_list(theory_id t, theory_var v, theory_var_list * n = 0): + m_th_id(t), + m_th_var(v), + m_next(n) { + } + + theory_id get_th_id() const { + return m_th_id; + } + + theory_var get_th_var() const { + return m_th_var; + } + + theory_var_list * get_next() const { + return m_next; + } + + void set_th_id(theory_id id) { + m_th_id = id; + } + + void set_th_var(theory_var v) { + m_th_var = v; + } + + void set_next(theory_var_list * next) { + m_next = next; + } + }; + + // 32 bit machine + COMPILE_TIME_ASSERT(sizeof(expr*) != 4 || sizeof(theory_var_list) == sizeof(theory_var_list *) + sizeof(int)); + // 64 bit machine + COMPILE_TIME_ASSERT(sizeof(expr*) != 8 || sizeof(theory_var_list) == sizeof(theory_var_list *) + sizeof(int) + /* a structure must be aligned */ sizeof(int)); +}; + +#endif /* _SMT_THEORY_VAR_LIST_H_ */ + diff --git a/lib/smt_trail.h b/lib/smt_trail.h new file mode 100644 index 000000000..0ac45ea18 --- /dev/null +++ b/lib/smt_trail.h @@ -0,0 +1,131 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_trail.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-19. + +Revision History: + +--*/ +#ifndef _SMT_TRAIL_H_ +#define _SMT_TRAIL_H_ + +namespace smt { + + class context; + + class trail { + public: + virtual ~trail() { + } + virtual void undo(context & ctx) = 0; + }; + + template + class value_trail : public trail { + T & m_value; + T m_old_value; + + public: + value_trail(T & value): + m_value(value), + m_old_value(value) { + } + + virtual ~value_trail() { + } + + virtual void undo(context & ctx) { + m_value = m_old_value; + } + }; + + template + class set_ptr_trail : public trail { + T * & m_ptr; + public: + set_ptr_trail(T * & ptr): + m_ptr(ptr) { + SASSERT(m_ptr == 0); + } + + virtual void undo(context & ctx) { + m_ptr = 0; + } + }; + + template + class vector_value_trail : public trail { + vector & m_vector; + unsigned m_idx; + T m_old_value; + public: + vector_value_trail(vector & v, unsigned idx): + m_vector(v), + m_idx(idx), + m_old_value(v[idx]) { + } + + virtual ~vector_value_trail() { + } + + virtual void undo(context & ctx) { + m_vector[m_idx] = m_old_value; + } + }; + + template + class push_back_trail : public trail { + vector & m_vector; + public: + push_back_trail(vector & v): + m_vector(v) { + } + + virtual void undo(context & ctx) { + m_vector.pop_back(); + } + }; + + class set_bitvector_trail : public trail { + svector & m_vector; + unsigned m_idx; + public: + set_bitvector_trail(svector & v, unsigned idx): + m_vector(v), + m_idx(idx) { + SASSERT(m_vector[m_idx] == false); + m_vector[m_idx] = true; + } + + virtual void undo(context & ctx) { + m_vector[m_idx] = false; + } + }; + + template + class new_obj_trail : public trail { + T * m_obj; + public: + new_obj_trail(T * obj): + m_obj(obj) { + } + + virtual void undo(context & ctx) { + dealloc(m_obj); + } + }; + +}; + +#endif /* _SMT_TRAIL_H_ */ + diff --git a/lib/smt_types.h b/lib/smt_types.h new file mode 100644 index 000000000..edc7d11c3 --- /dev/null +++ b/lib/smt_types.h @@ -0,0 +1,78 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smt_types.h + +Abstract: + + Basic types for the SMT engine + +Author: + + Leonardo de Moura (leonardo) 2008-02-19. + +Revision History: + +--*/ +#ifndef _SMT_TYPES_H_ +#define _SMT_TYPES_H_ + +#include"list.h" +#include"vector.h" +#include"lbool.h" + +class model; + +namespace smt { + /** + \brief A boolean variable is just an integer. + */ + typedef int bool_var; + + const bool_var null_bool_var = -1; + const bool_var true_bool_var = 0; + const bool_var first_bool_var = 1; + + typedef svector bool_var_vector; + + typedef family_id theory_id; + const theory_id null_theory_id = null_family_id; + typedef int theory_var; + const theory_var null_theory_var = -1; + + class enode; + typedef ptr_vector enode_vector; + typedef std::pair enode_pair; + + class context; + + class theory; + + class justification; + + class model_generator; + + enum final_check_status { + FC_DONE, + FC_CONTINUE, + FC_GIVEUP + }; + + inline std::ostream & operator<<(std::ostream & out, final_check_status st) { + switch (st) { + case FC_DONE: out << "done"; break; + case FC_CONTINUE: out << "continue"; break; + case FC_GIVEUP: out << "giveup"; break; + } + return out; + } + +// if defined, then clauses have an extra mask field used to optimize backward subsumption, and backward/forward subsumption resolution. +#define APPROX_LIT_SET + +}; + +#endif /* _SMT_TYPES_H_ */ + diff --git a/lib/smtlib.cpp b/lib/smtlib.cpp new file mode 100644 index 000000000..3384e1ffd --- /dev/null +++ b/lib/smtlib.cpp @@ -0,0 +1,252 @@ + +#include"smtlib.h" +#include"ast_pp.h" +#include"ast_smt2_pp.h" + +#ifdef _WINDOWS +#ifdef ARRAYSIZE +#undef ARRAYSIZE +#endif +#include +#include +#endif + +#include + + +using namespace smtlib; + +// -------------------------------------------------------------------------- +// symtable + +symtable::~symtable() { + reset(); +} + +void symtable::reset() { + svector*> range; + m_ids.get_range(range); + for (unsigned i = 0; i < range.size(); ++i) { + ptr_vector const & v = *range[i]; + for (unsigned j = 0; j < v.size(); ++j) { + m_manager.dec_ref(v[j]); + } + dealloc(range[i]); + } + m_ids.reset(); + ptr_vector sorts; + m_sorts1.get_range(sorts); + for (unsigned i = 0; i < sorts.size(); ++i) { + m_manager.dec_ref(sorts[i]); + } + m_sorts1.reset(); + ptr_vector sort_builders; + m_sorts.get_range(sort_builders); + for (unsigned i = 0; i < sort_builders.size(); ++i) { + dealloc(sort_builders[i]); + } + m_sorts.reset(); +} + + +void symtable::insert(symbol s, func_decl * d) { + ptr_vector* decls = 0; + m_manager.inc_ref(d); + if (!m_ids.find(s, decls)) { + SASSERT(!decls); + decls = alloc(ptr_vector); + decls->push_back(d); + m_ids.insert(s, decls); + } + else { + SASSERT(decls); + if ((*decls)[0] != d) { + decls->push_back(d); + } + else { + m_manager.dec_ref(d); + } + } +} + +bool symtable::find1(symbol s, func_decl*& d) { + ptr_vector* decls = 0; + + if (!m_ids.find(s, decls)) { + SASSERT(!decls); + return false; + } + SASSERT(decls && !decls->empty()); + d = (*decls)[0]; + return true; +} + +bool symtable::find_overload(symbol s, ptr_vector const & dom, func_decl * & d) { + ptr_vector* decls = 0; + d = 0; + if (!m_ids.find(s, decls)) { + SASSERT(!decls); + return false; + } + SASSERT(decls); + for (unsigned i = 0; i < decls->size(); ++i) { + func_decl* decl = (*decls)[i]; + if (decl->is_associative() && decl->get_arity() > 0) { + for (unsigned j = 0; j < dom.size(); ++j) { + if (dom[j] != decl->get_domain(0)) { + goto try_next; + } + } + d = decl; + return true; + } + + if (decl->get_arity() != dom.size()) { + goto try_next; + } + for (unsigned j = 0; j < decl->get_arity(); ++j) { + if (decl->get_domain(j) != dom[j]) { + goto try_next; + } + } + d = decl; + return true; + + try_next: + if (decl->get_family_id() == m_manager.get_basic_family_id() && decl->get_decl_kind() == OP_DISTINCT) { + // we skip type checking for 'distinct' + d = decl; + return true; + } + } + return false; +} + +// Store in result the func_decl that are not attached to any family id. +// That is, the uninterpreted constants and function declarations. +void symtable::get_func_decls(ptr_vector & result) const { + svector*> tmp; + m_ids.get_range(tmp); + svector*>::const_iterator it = tmp.begin(); + svector*>::const_iterator end = tmp.end(); + for (; it != end; ++it) { + ptr_vector * curr = *it; + if (curr) { + ptr_vector::const_iterator it2 = curr->begin(); + ptr_vector::const_iterator end2 = curr->end(); + for (; it2 != end2; ++it2) { + func_decl * d = *it2; + if (d && d->get_family_id() == null_family_id) { + result.push_back(d); + } + } + } + } +} + +void symtable::insert(symbol s, sort_builder* sb) { + m_sorts.insert(s, sb); +} + +bool symtable::lookup(symbol s, sort_builder*& sb) { + return m_sorts.find(s, sb); +} + +void symtable::push_sort(symbol name, sort* srt) { + m_sorts.begin_scope(); + sort_builder* sb = alloc(basic_sort_builder,srt); + m_sorts.insert(name, sb); + m_sorts_trail.push_back(sb); +} + +void symtable::pop_sorts(unsigned num_sorts) { + while (num_sorts > 0) { + dealloc(m_sorts_trail.back()); + m_sorts_trail.pop_back(); + m_sorts.end_scope(); + } +} + +void symtable::get_sorts(ptr_vector& result) const { + vector tmp; + m_sorts1.get_range(tmp); + for (unsigned i = 0; i < tmp.size(); ++i) { + if (tmp[i]->get_family_id() == null_family_id) { + result.push_back(tmp[i]); + } + } +} + + +// -------------------------------------------------------------------------- +// theory + +func_decl * theory::declare_func(symbol const & id, sort_ref_buffer & domain, sort * range, + bool is_assoc, bool is_comm, bool is_inj) { + func_decl * decl = m_ast_manager.mk_func_decl(id, domain.size(), domain.c_ptr(), range, + is_assoc, is_comm, is_inj); + + m_symtable.insert(id, decl); + m_asts.push_back(decl); + return decl; +} + + +sort * theory::declare_sort(symbol const & id) { + sort * decl = m_ast_manager.mk_sort(id); + m_symtable.insert(id, decl); + m_asts.push_back(decl); + return decl; +} + + +bool theory::get_func_decl(symbol id, func_decl * & decl) { + return m_symtable.find1(id, decl); +} + +bool theory::get_sort(symbol id, sort* & s) { + return m_symtable.find(id, s); +} + +bool theory::get_const(symbol id, expr * & term) { + func_decl* decl = 0; + if (!get_func_decl(id,decl)) { + return false; + } + if (decl->get_arity() != 0) { + return false; + } + term = m_ast_manager.mk_const(decl); + m_asts.push_back(term); + return true; +} + +void benchmark::display_as_smt2(std::ostream & out) const { + if (m_logic != symbol::null) + out << "(set-logic " << m_logic << ")\n"; + out << "(set-info :smt-lib-version 2.0)\n"; + out << "(set-info :status "; + switch (m_status) { + case SAT: out << "sat"; break; + case UNSAT: out << "unsat"; break; + default: out << "unknown"; break; + } + out << ")\n"; +#if 0 + ast_manager & m = m_ast_manager; + ptr_vector decls; + m_symtable.get_func_decls(decls); + ptr_vector::const_iterator it = decls.begin(); + ptr_vector::const_iterator end = decls.end(); + for (; it != end; ++it) { + func_decl * f = *it; + out << "(declare-fun " << f->get_name() << " ("; + for (unsigned i = 0; i < f->get_arity(); i++) { + if (i > 0) out << " "; + out << mk_ismt2_pp(f->get_domain(i), m); + } + out << ") " << mk_ismt2_pp(f->get_range(), m); + out << ")\n"; + } +#endif +} diff --git a/lib/smtlib.h b/lib/smtlib.h new file mode 100644 index 000000000..e7d4d3a5f --- /dev/null +++ b/lib/smtlib.h @@ -0,0 +1,232 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smtlib.h + +Abstract: + + SMT library utilities + +Author: + + Nikolaj Bjorner (nbjorner) 2006-09-29 + +Revision History: + +--*/ +#ifndef _SMTLIB_H_ +#define _SMTLIB_H_ + +#include "ast.h" +#include "symbol_table.h" +#include "map.h" +#include "arith_decl_plugin.h" + +namespace smtlib { + + class sort_builder { + public: + virtual ~sort_builder() {} + virtual bool apply(unsigned num_params, parameter const* params, sort_ref& result) = 0; + virtual char const* error_message() { return ""; } + }; + + class basic_sort_builder : public sort_builder { + sort* m_sort; + public: + basic_sort_builder(sort* s) : m_sort(s) {} + + virtual bool apply(unsigned np, parameter const*, sort_ref& result) { + result = m_sort; + return m_sort && np != 0; + } + }; + + + class symtable { + ast_manager& m_manager; + symbol_table m_sorts1; + symbol_table m_sorts; + ptr_vector m_sorts_trail; + symbol_table* > m_ids; + + public: + + symtable(ast_manager& m): m_manager(m) {} + + ~symtable(); + + void reset(); + + void insert(symbol s, func_decl * d); + + bool find(symbol s, ptr_vector * & decls) { + return m_ids.find(s, decls); + } + + bool find1(symbol s, func_decl * & d); + + bool find_overload(symbol s, ptr_vector const & dom, func_decl * & d); + + void insert(symbol s, sort * d) { + sort * d2; + if (m_sorts1.find(s, d2)) { + m_manager.dec_ref(d2); + } + m_manager.inc_ref(d); + m_sorts1.insert(s, d); + } + + bool find(symbol s, sort * & d) { + return m_sorts1.find(s, d); + } + + void insert(symbol s, sort_builder* sb); + + bool lookup(symbol s, sort_builder*& sb); + + void push_sort(symbol s, sort*); + + void pop_sorts(unsigned num_sorts); + + void get_func_decls(ptr_vector & result) const; + + void get_sorts(ptr_vector& result) const; + }; + + class theory { + public: + typedef ptr_vector::const_iterator expr_iterator; + + theory(ast_manager & ast_manager, symbol const& name): + m_name(name), + m_ast_manager(ast_manager), + m_symtable(ast_manager), + m_asts(ast_manager) + {} + + virtual ~theory() {} + + symtable * get_symtable() { return &m_symtable; } + + void insert(sort * s) { m_symtable.insert(s->get_name(), s); } + + void insert(func_decl * c) { m_symtable.insert(c->get_name(), c); } + + func_decl * declare_func(symbol const & id, sort_ref_buffer & domain, sort * range, + bool is_assoc, bool is_comm, bool is_inj); + + sort * declare_sort(symbol const & id); + + void add_axiom(expr * axiom) { + m_asts.push_back(axiom); + m_axioms.push_back(axiom); + } + + expr_iterator begin_axioms() const { + return m_axioms.begin(); + } + + unsigned get_num_axioms() const { + return m_axioms.size(); + } + + expr * const * get_axioms() const { + return m_axioms.c_ptr(); + } + + expr_iterator end_axioms() const { + return m_axioms.end(); + } + + void add_assumption(expr * axiom) { + m_asts.push_back(axiom); + m_assumptions.push_back(axiom); + } + + unsigned get_num_assumptions() const { + return m_assumptions.size(); + } + + expr * const * get_assumptions() const { + return m_assumptions.c_ptr(); + } + + bool get_func_decl(symbol, func_decl*&); + + bool get_sort(symbol, sort*&); + + bool get_const(symbol, expr*&); + + void set_name(symbol const& name) { m_name = name; } + + symbol const get_name() const { return m_name; } + protected: + symbol m_name; + ast_manager& m_ast_manager; + ptr_vector m_axioms; + ptr_vector m_assumptions; + symtable m_symtable; + ast_ref_vector m_asts; + + private: + theory& operator=(theory const&); + + theory(theory const&); + }; + + class benchmark : public theory { + public: + enum status { + UNKNOWN, + SAT, + UNSAT + }; + + benchmark(ast_manager & ast_manager, symbol const & name) : + theory(ast_manager, name), + m_status(UNKNOWN) {} + + virtual ~benchmark() {} + + status get_status() const { return m_status; } + void set_status(status status) { m_status = status; } + + symbol get_logic() const { + if (m_logic == symbol::null) { + return symbol("ALL"); + } + return m_logic; + } + + void set_logic(symbol const & s) { m_logic = s; } + + unsigned get_num_formulas() const { + return m_formulas.size(); + } + + expr_iterator begin_formulas() const { + return m_formulas.begin(); + } + + expr_iterator end_formulas() const { + return m_formulas.end(); + } + + void add_formula(expr * formula) { + m_asts.push_back(formula); + m_formulas.push_back(formula); + } + + void display_as_smt2(std::ostream & out) const; + + private: + status m_status; + symbol m_logic; + ptr_vector m_formulas; + }; +}; + +#endif diff --git a/lib/smtlib_solver.cpp b/lib/smtlib_solver.cpp new file mode 100644 index 000000000..f9ec045ce --- /dev/null +++ b/lib/smtlib_solver.cpp @@ -0,0 +1,117 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smtlib_solver.cpp + +Abstract: + + SMT based solver. + +Author: + + Nikolaj Bjorner (nbjorner) 2006-11-3. + +Revision History: + +--*/ + +#include"smtparser.h" +#include"smtlib_solver.h" +#include"warning.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"well_sorted.h" +#include"spc_prover.h" +#include"model.h" +#include"model_v2_pp.h" +#include"expr2dot.h" +#include"solver.h" +#include"smt_strategic_solver.h" +#include"cmd_context.h" + +namespace smtlib { + + solver::solver(front_end_params & params): + m_ast_manager(params.m_proof_mode, params.m_trace_stream), + m_params(params), + m_ctx(0), + m_parser(parser::create(m_ast_manager, params.m_ignore_user_patterns)), + m_error_code(0) { + m_parser->initialize_smtlib(); + } + + solver::~solver() { + if (m_ctx) + dealloc(m_ctx); + } + + bool solver::solve_smt(char const * benchmark_file) { + IF_VERBOSE(100, verbose_stream() << "parsing...\n";); + if (!m_parser->parse_file(benchmark_file)) { + if (benchmark_file) { + warning_msg("could not parse file '%s'.", benchmark_file); + } + else { + warning_msg("could not parse input stream."); + } + m_error_code = ERR_PARSER; + return false; + } + benchmark * benchmark = m_parser->get_benchmark(); + solve_benchmark(*benchmark); + return true; + } + + bool solver::solve_smt_string(char const * benchmark_string) { + if (!m_parser->parse_string(benchmark_string)) { + warning_msg("could not parse string '%s'.", benchmark_string); + return false; + } + benchmark * benchmark = m_parser->get_benchmark(); + solve_benchmark(*benchmark); + return true; + } + + void solver::display_statistics() { + if (m_ctx) + m_ctx->display_statistics(); + } + + void solver::solve_benchmark(benchmark & benchmark) { + if (benchmark.get_num_formulas() == 0) { + // Hack: it seems SMT-LIB allow benchmarks without any :formula + benchmark.add_formula(m_ast_manager.mk_true()); + } + m_ctx = alloc(cmd_context, m_params, true, &m_ast_manager, benchmark.get_logic()); + m_ctx->set_solver(mk_smt_strategic_solver(*m_ctx)); + theory::expr_iterator fit = benchmark.begin_formulas(); + theory::expr_iterator fend = benchmark.end_formulas(); + for (; fit != fend; ++fit) + solve_formula(benchmark, *fit); + } + + void solver::solve_formula(benchmark const & benchmark, expr * f) { + IF_VERBOSE(100, verbose_stream() << "starting...\n";); + m_ctx->reset(); + for (unsigned i = 0; i < benchmark.get_num_axioms(); i++) + m_ctx->assert_expr(benchmark.get_axioms()[i]); + m_ctx->assert_expr(f); + m_ctx->check_sat(benchmark.get_num_assumptions(), benchmark.get_assumptions()); + check_sat_result * r = m_ctx->get_check_sat_result(); + if (r != 0) { + proof * pr = r->get_proof(); + if (pr != 0 && m_params.m_display_proof) + std::cout << mk_ll_pp(pr, m_ast_manager, false, false); + model_ref md; + if (r->status() != l_false) r->get_model(md); + if (md.get() != 0 && m_params.m_model) { + model_v2_pp(std::cout, *(md.get()), m_params.m_model_partial); + } + } + else { + m_error_code = ERR_UNKNOWN_RESULT; + } + } +}; diff --git a/lib/smtlib_solver.h b/lib/smtlib_solver.h new file mode 100644 index 000000000..a84f2c98f --- /dev/null +++ b/lib/smtlib_solver.h @@ -0,0 +1,48 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smtlib_solver.h + +Abstract: + + SMT based solver. + +Author: + + Nikolaj Bjorner (nbjorner) 2006-11-3. + +Revision History: + +--*/ +#ifndef _SMTLIB_SOLVER_H_ +#define _SMTLIB_SOLVER_H_ + +#include"smtparser.h" +#include"front_end_params.h" +#include"lbool.h" + +class cmd_context; + +namespace smtlib { + class solver { + ast_manager m_ast_manager; + front_end_params & m_params; + cmd_context * m_ctx; + scoped_ptr m_parser; + unsigned m_error_code; + public: + solver(front_end_params & params); + ~solver(); + bool solve_smt(char const * benchmark_file); + bool solve_smt_string(char const * benchmark_string); + void display_statistics(); + unsigned get_error_code() const { return m_error_code; } + private: + void solve_benchmark(benchmark & benchmark); + void solve_formula(benchmark const & benchmark, expr * f); + }; +}; + +#endif diff --git a/lib/smtparser.cpp b/lib/smtparser.cpp new file mode 100644 index 000000000..8a87f41af --- /dev/null +++ b/lib/smtparser.cpp @@ -0,0 +1,5320 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smtparser.cpp + +Abstract: + + SMT parser into ast. + +Author: + + Nikolaj Bjorner (nbjorner) 2006-10-4. + Leonardo de Moura (leonardo) + +Revision History: +--*/ + +#include +#include +#include +#include +#include +#include"region.h" +#include"scanner.h" +#include"symbol.h" +#include"vector.h" +#include"symbol_table.h" +#include"smtlib.h" +#include"smtparser.h" +#include"ast_pp.h" +#include"bv_decl_plugin.h" +#include"array_decl_plugin.h" +#include"datatype_decl_plugin.h" +#include"warning.h" +#include"error_codes.h" +#include"pattern_validation.h" +#include"unifier.h" +#include"kbo.h" +#include"lpo.h" +#include"substitution_tree.h" +#include"timeit.h" +#include"var_subst.h" +#include"well_sorted.h" +#include "str_hashtable.h" +#include "front_end_params.h" +#include "z3_private.h" +#include "stopwatch.h" +#include "dl_rule.h" +// private method defined in z3.cpp: +front_end_params& Z3_API Z3_get_parameters(__in Z3_context c); + +class id_param_info { + symbol m_string; + unsigned m_num_params; + parameter m_params[0]; +public: + id_param_info(symbol const& s, unsigned n, parameter const* p) : m_string(s), m_num_params(n) { + for (unsigned i = 0; i < n; ++i) { + new (&(m_params[i])) parameter(); + m_params[i] = p[i]; + } + } + symbol string() const { return m_string; } + parameter * params() { return m_params; } + unsigned num_params() const { return m_num_params; } +}; + +class proto_region { + ptr_vector m_rationals; + ptr_vector< id_param_info > m_id_infos; + region m_region; +public: + proto_region() { } + + ~proto_region() { reset(); } + + rational* allocate(rational const & n) { + rational* r = alloc(rational, n); + m_rationals.push_back(r); + return r; + } + + id_param_info* allocate(vector const& params, symbol const & s) { + unsigned size = sizeof(id_param_info) + sizeof(parameter)*(params.size()); + id_param_info* r = static_cast(m_region.allocate(size)); + new (r) id_param_info(s, params.size(), params.c_ptr()); + m_id_infos.push_back(r); + return r; + } + + void* allocate(size_t s) { return m_region.allocate(s); } + + void reset() { + for (unsigned i = 0; i < m_rationals.size(); ++i) { + dealloc(m_rationals[i]); + } + for (unsigned i = 0; i < m_id_infos.size(); ++i) { + unsigned n = m_id_infos[i]->num_params(); + for (unsigned j = 0; j < n; ++j) { + m_id_infos[i]->params()[j].~parameter(); + } + } + m_rationals.reset(); + m_id_infos.reset(); + m_region.reset(); + } + +private: + +}; + +inline void * operator new(size_t s, proto_region& r) { return r.allocate(s); } +inline void * operator new[](size_t s, proto_region & r) { return r.allocate(s); } +inline void operator delete(void*, proto_region& r) {} +inline void operator delete[](void *, proto_region& r) {} + +class proto_expr { +public: + + enum kind_t { + ID, + STRING, + COMMENT, + ANNOTATION, + INT, + FLOAT, + CONS + }; +private: + + int m_kind:8; + int m_line:24; + int m_pos; + union { + id_param_info* m_id_info; + rational* m_number; + proto_expr** m_children; + }; + +public: + + symbol string() { + if (m_kind == INT || m_kind == FLOAT) { + std::string s = m_number->to_string(); + return symbol(s.c_str()); + } + if (m_kind == CONS) { + return symbol(""); + } + SASSERT(m_kind == STRING || m_kind == COMMENT || m_kind == ID || m_kind == ANNOTATION); + return m_id_info->string(); + } + + rational const& number() { + SASSERT(m_kind == INT || m_kind == FLOAT); + return *m_number; + } + + proto_expr* const* children() const { + if (m_kind == CONS) { + return m_children; + } + else { + return 0; + } + } + + int line() { return m_line; } + int pos() { return m_pos; } + kind_t kind() { return static_cast(m_kind); } + + unsigned num_params() const { + SASSERT(m_kind == ID); + return m_id_info->num_params(); + } + + parameter * params() { + SASSERT(m_kind == ID); + return m_id_info->params(); + } + + proto_expr(proto_region & region, kind_t kind, symbol const & s, vector const & params, int line, int pos): + m_kind(kind), + m_line(line), + m_pos(pos), + m_id_info(region.allocate(params, s)) { + SASSERT(kind != CONS); + SASSERT(kind != INT); + SASSERT(kind != FLOAT); + } + + proto_expr(proto_region& region, bool is_int, rational const & n, int line, int pos): + m_kind(is_int?INT:FLOAT), + m_line(line), + m_pos(pos), + m_number(region.allocate(n)) + {} + + proto_expr(proto_region& region, ptr_vector& proto_exprs, int line, int pos): + m_kind(CONS), + m_line(line), + m_pos(pos) { + // + // null terminated list of proto_expression pointers. + // + unsigned num_children = proto_exprs.size(); + m_children = new (region) proto_expr*[num_children+1]; + for (unsigned i = 0; i < num_children; ++i) { + m_children[i] = proto_exprs[i]; + } + m_children[num_children] = 0; + } + + ~proto_expr() {} + + + static proto_expr* copy(proto_region& r, proto_expr* e) { + switch(e->kind()) { + case proto_expr::CONS: { + ptr_vector args; + proto_expr* const* children = e->children(); + while (children && *children) { + args.push_back(copy(r, *children)); + ++children; + } + return new (r) proto_expr(r, args, e->line(), e->pos()); + } + case proto_expr::INT: { + return new (r) proto_expr(r, true, e->number(), e->line(), e->pos()); + } + case proto_expr::FLOAT: { + return new (r) proto_expr(r, false, e->number(), e->line(), e->pos()); + } + case proto_expr::ID: { + vector params; + for (unsigned i = 0; i < e->num_params(); ++i) { + params.push_back(e->params()[i]); + } + return new (r) proto_expr(r, e->kind(), e->string(), params, e->line(), e->pos()); + } + default: { + vector params; + return new (r) proto_expr(r, e->kind(), e->string(), params, e->line(), e->pos()); + } + } + } + +private: + + proto_expr(proto_expr const & other); + proto_expr& operator=(proto_expr const & other); +}; + + +// +// build up proto_expr tree from token stream. +// + +class proto_expr_parser { + proto_region& m_region; + scanner& m_scanner; + std::ostream& m_err; + bool m_at_eof; +public: + proto_expr_parser(proto_region& region, scanner& scanner, std::ostream& err): + m_region(region), + m_scanner(scanner), + m_err(err), + m_at_eof(false) { + } + + ~proto_expr_parser() {} + + bool parse(ptr_vector & proto_exprs, bool parse_single_expr = false) { + scanner::token token; + vector stack; + proto_expr* result = 0; + + stack.push_back(frame(PROTO_EXPRS_PRE)); + + token = m_scanner.scan(); + + if (token == scanner::EOF_TOKEN) { + proto_exprs.reset(); + return true; + } + + while (!stack.empty()) { + + if (token == scanner::EOF_TOKEN) { + break; + } + + if (token == scanner::ERROR_TOKEN) { + print_error("unexpected token"); + goto done; + } + + switch(stack.back().m_state) { + + case PROTO_EXPR: + switch (token) { + case scanner::LEFT_PAREN: + stack.back().m_state = PROTO_EXPRS_PRE; + token = m_scanner.scan(); + break; + default: + stack.back().m_state = ATOM; + break; + } + break; + + case ATOM: + SASSERT(!result); + switch(token) { + case scanner::ID_TOKEN: + result = new (m_region) proto_expr(m_region, proto_expr::ID, m_scanner.get_id(), m_scanner.get_params(), + m_scanner.get_line(), m_scanner.get_pos()); + break; + case scanner::INT_TOKEN: + result = new (m_region) proto_expr(m_region, true, m_scanner.get_number(), m_scanner.get_line(), + m_scanner.get_pos()); + break; + case scanner::FLOAT_TOKEN: + result = new (m_region) proto_expr(m_region, false, m_scanner.get_number(), m_scanner.get_line(), + m_scanner.get_pos()); + break; + case scanner::STRING_TOKEN: + result = new (m_region) proto_expr(m_region, proto_expr::STRING, m_scanner.get_id(), m_scanner.get_params(), + m_scanner.get_line(), m_scanner.get_pos()); + break; + case scanner::COMMENT_TOKEN: + result = new (m_region) proto_expr(m_region, proto_expr::COMMENT, m_scanner.get_id(), m_scanner.get_params(), + m_scanner.get_line(), m_scanner.get_pos()); + break; + case scanner::COLON: + token = m_scanner.scan(); + if (token == scanner::ID_TOKEN) { + result = new (m_region) proto_expr(m_region, proto_expr::ANNOTATION, m_scanner.get_id(), + m_scanner.get_params(), m_scanner.get_line(), m_scanner.get_pos()); + } + else { + print_error("unexpected identifier ':'"); + token = scanner::ERROR_TOKEN; + goto done; + } + break; + default: + print_error("unexpected token"); + token = scanner::ERROR_TOKEN; + goto done; + } + stack.pop_back(); + SASSERT(!stack.empty()); + stack.back().m_proto_exprs.push_back(result); + result = 0; + if (parse_single_expr && stack.size() == 1) { + goto done; + } + token = m_scanner.scan(); + break; + + case PROTO_EXPRS_PRE: + SASSERT(!result); + switch(token) { + case scanner::RIGHT_PAREN: + result = new (m_region) proto_expr(m_region, stack.back().m_proto_exprs, m_scanner.get_line(), + m_scanner.get_pos()); + stack.pop_back(); + + if (stack.empty()) { + print_error("unexpected right parenthesis"); + token = scanner::ERROR_TOKEN; + result = 0; + goto done; + } + stack.back().m_proto_exprs.push_back(result); + if (parse_single_expr && stack.size() == 1) { + goto done; + } + result = 0; + token = m_scanner.scan(); + break; + + case scanner::EOF_TOKEN: + m_at_eof = true; + break; + + case scanner::ERROR_TOKEN: + print_error("could not parse expression"); + goto done; + + default: + stack.back().m_state = PROTO_EXPRS_POST; + stack.push_back(frame(PROTO_EXPR)); + break; + } + break; + + case PROTO_EXPRS_POST: + stack.back().m_state = PROTO_EXPRS_PRE; + break; + } + } + + done: + + if (stack.size() == 1) { + for (unsigned i = 0; i < stack.back().m_proto_exprs.size(); ++i) { + proto_exprs.push_back(stack.back().m_proto_exprs[i]); + } + return true; + } + + if (stack.size() == 2) { + proto_exprs.push_back(new (m_region) proto_expr(m_region, stack.back().m_proto_exprs, m_scanner.get_line(), + m_scanner.get_pos())); + return true; + } + + print_error("unexpected nesting of parenthesis: ", stack.size()); + return false; + } + + int get_line() { + return m_scanner.get_line(); + } + + bool at_eof() const { + return m_at_eof; + } + +private: + + template + void print_error(char const* msg1, T msg2) { + m_err << "ERROR: line " << m_scanner.get_line() + << " column " << m_scanner.get_pos() << ": " + << msg1 << msg2 << "\n"; + } + + void print_error(char const* msg) { + print_error(msg, ""); + } + + // stack frame: + enum frame_state { + PROTO_EXPR, + PROTO_EXPRS_PRE, + PROTO_EXPRS_POST, + ATOM + }; + + class frame { + public: + frame_state m_state; + ptr_vector m_proto_exprs; + frame(frame_state state): + m_state(state){ + } + frame(frame const & other): + m_state(other.m_state), + m_proto_exprs(other.m_proto_exprs) { + } + private: + frame& operator=(frame const &); + }; + +}; + +enum smt_cmd_token { + SMT_CMD_SET_LOGIC, // logic-name + SMT_CMD_DECLARE_SORTS, // sorts-symbols + SMT_CMD_DECLARE_FUNS, // func-decls + SMT_CMD_DECLARE_PREDS, // pred-decls + SMT_CMD_DECLARE_DATATYPES, // datatypes + SMT_CMD_DEFINE, // var expr + SMT_CMD_DEFINE_SORTS, // (var sort)* + SMT_CMD_DECLARE_SORT, // + SMT_CMD_DEFINE_SORT, // (*) + SMT_CMD_DECLARE_FUN, // (*) + SMT_CMD_DECLARE_CONST, // + SMT_CMD_DEFINE_FUN, // (*) + SMT_CMD_GET_VALUE, // (+) + + SMT_CMD_PUSH, // numeral + SMT_CMD_POP, // numeral + SMT_CMD_ASSERT, // expr + SMT_CMD_CHECK_SAT, // + SMT_CMD_GET_CORE, // expr+ + SMT_CMD_NEXT_SAT, + SMT_CMD_SIMPLIFY, // expr + SMT_CMD_GET_IMPLIED_EQUALITIES, // expr* + SMT_CMD_EVAL, // expr + SMT_CMD_GET_ASSERTIONS, // string + SMT_CMD_GET_SAT_ASSERTIONS, // string + SMT_CMD_KEEP_SAT_ASSERTIONS, // + SMT_CMD_GET_UNSAT_CORE, // string + SMT_CMD_GET_PROOF, // string + SMT_CMD_SET_OPTION, // string strings + SMT_CMD_GET_INFO, // string + SMT_CMD_SET_INFO, // string strings + SMT_CMD_ECHO, // string strings + SMT_CMD_EXIT, // + // set-option: + SMT_CMD_PRINT_SUCCESS, + SMT_CMD_RANDOM_SEED, + SMT_CMD_VERBOSITY, + SMT_CMD_EXPAND_DEFINITIONS, + SMT_CMD_OUTPUT_CHANNEL, + SMT_CMD_VERBOSE_OUTPUT_CHANNEL, + SMT_CMD_ENABLE_CORES, + SMT_CMD_SET_PARAM, + SMT_CMD_PRODUCE_MODELS, + // set-info: + SMT_CMD_NAME, + SMT_CMD_VERSION, + SMT_CMD_AUTHORS, + SMT_CMD_LAST_FAILURE, + SMT_CMD_REASON_UNKNOWN, + SMT_CMD_STATS, + SMT_CMD_MODEL, + SMT_CMD_TIME, + SMT_CMD_LABELS, + SMT_CMD_HELP, // help + + SMT_CMD_ERROR // error token +}; + + +using namespace smtlib; + +class idbuilder { +public: + virtual ~idbuilder() {} + virtual bool apply(expr_ref_vector const & args, expr_ref & result) = 0; +}; + +class builtin_sort_builder : public sort_builder { + ast_manager& m_manager; + family_id m_fid; + decl_kind m_kind; +public: + builtin_sort_builder(ast_manager& m, family_id fid, decl_kind k) : + m_manager(m), m_fid(fid), m_kind(k) {} + + virtual bool apply(unsigned num_params, parameter const* params, sort_ref & result) { + result = m_manager.mk_sort(m_fid, m_kind, num_params, params); + return result.get() != 0; + } +}; + +class array_sort : public builtin_sort_builder { +public: + array_sort(ast_manager& m) : + builtin_sort_builder(m, m.get_family_id("array"), ARRAY_SORT) {} +}; + +class bv_sort : public builtin_sort_builder { +public: + bv_sort(ast_manager& m) : + builtin_sort_builder(m, m.get_family_id("bv"), BV_SORT) {} +}; + +class user_sort : public sort_builder { + user_sort_plugin * m_plugin; + decl_kind m_kind; + symbol m_name; + unsigned m_num_args; + std::string m_error_message; +public: + user_sort(ast_manager& m, unsigned num_args, symbol name): + m_name(name), + m_num_args(num_args) { + m_plugin = m.get_user_sort_plugin(); + m_kind = m_plugin->register_name(name); + } + + ~user_sort() {} + + virtual bool apply(unsigned num_params, parameter const* params, sort_ref & result) { + if (num_params != m_num_args) { + std::ostringstream strm; + strm << "wrong number of arguments passed to " << m_name << " " + << m_num_args << " expected, but " << num_params << " given"; + m_error_message = strm.str(); + return false; + } + result = m_plugin->mk_sort(m_kind, num_params, params); + return true; + } + + virtual char const* error_message() { + return m_error_message.c_str(); + } +}; + + + + +class scoped_stream { + std::ostream& m_default; + std::ostream* m_stream; + bool m_owned; +public: + + scoped_stream(std::ostream& strm) : m_default(strm) { + m_stream = &strm; + m_owned = false; + } + + scoped_stream(proto_expr* e) : m_default(std::cout), m_stream(0), m_owned(false) { + reset(e, 0); + } + + scoped_stream(proto_expr* e, std::ostream& out) : m_default(out), m_stream(0), m_owned(false) { + reset(e, &out); + } + + ~scoped_stream() { + dealloc_stream(); + } + + void reset(proto_expr* e, std::ostream* out = 0) { + char const* name = (e && e->kind() == proto_expr::ID)?e->string().bare_str():0; + reset(name, out); + } + + void reset(char const* name, std::ostream* out = 0) { + dealloc_stream(); + m_owned = false; + if (!out) { + out = &m_default; + } + if (!name) { + m_stream = out; + } + else if (strcmp(name, "stdout")) { + m_stream = &std::cout; + } + else if (strcmp(name, "stderr")) { + m_stream = &std::cerr; + } + else { + m_stream = alloc(std::ofstream, name, std::ios_base::app); + m_owned = true; + if (m_stream->bad() || m_stream->fail()) { + dealloc(m_stream); + m_stream = out; + m_owned = false; + } + } + SASSERT(m_stream); + } + + std::ostream& operator*() { + return *m_stream; + } + +private: + void dealloc_stream() { + if (m_owned) { + m_stream->flush(); + dealloc(m_stream); + } + m_stream = 0; + m_owned = false; + } +}; + +class cmd_exn { + Z3_error_code m_code; +public: + cmd_exn(Z3_error_code code) : m_code(code) {} + Z3_error_code get() const { return m_code; } +}; + +static void* g_sw = 0; + +#define cmd_context _cmd_context + +class cmd_context { + typedef map str2token; + str2token m_str2token; + ptr_vector m_token2str; + ptr_vector m_token2help; + ptr_vector m_token2args; + svector m_is_command; + + struct cmd_info { + smt_cmd_token m_token; + void (*m_action)(cmd_context&); + cmd_info(smt_cmd_token t, void (*action)(cmd_context&)): + m_token(t), m_action(action) {} + }; + + struct opt { + smt_cmd_token m_token; + bool (*m_action)(cmd_context&, proto_expr* const* exprs); + opt(smt_cmd_token t, bool (*action)(cmd_context&, proto_expr* const* exprs)): + m_token(t), m_action(action) {} + }; + + enum check_sat_state { + state_unsat, + state_sat, + state_unknown, + state_clear, + state_new_assertion + }; + + Z3_context m_context; + Z3_model m_model; + Z3_ast m_proof; + ast_manager& m_manager; + scoped_stream m_out; + scoped_stream m_verbose; + symbol_table m_table; + region m_region; + ast_ref_vector m_trail; + unsigned_vector m_trail_lim; + expr_ref_vector m_asserted; + unsigned_vector m_asserted_lim; + expr_ref_vector m_asserted_proxies; + unsigned_vector m_asserted_proxies_lim; + bool m_print_success; + bool m_enable_cores; + unsigned m_core_size; + svector m_core; + check_sat_state m_check_sat_state; + svector m_check_sat_states; + stopwatch m_watch; + svector m_infos; + svector m_options; + std::ostringstream m_error_stream; + + smtlib::benchmark::status m_status; + +public: + + cmd_context(ast_manager& m, Z3_context ctx, std::istream& is, std::ostream& out): + m_context(ctx), + m_model(0), + m_proof(0), + m_manager(m), + m_out(out), + m_verbose(std::cerr), + m_trail(m), + m_asserted(m), + m_asserted_proxies(m), + m_print_success(Z3_get_parameters(ctx).m_smtlib2_compliant), + m_enable_cores(false), + m_core_size(0), + m_check_sat_state(state_clear), + m_status(smtlib::benchmark::UNKNOWN) + { + add_command("set-logic", + SMT_CMD_SET_LOGIC, + "", "set the background logic"); + add_command("declare-sorts", + SMT_CMD_DECLARE_SORTS, + "", "declare sorts"); + add_command("declare-funs", + SMT_CMD_DECLARE_FUNS, + "", + "declare functions and constants"); + add_command("declare-preds", + SMT_CMD_DECLARE_PREDS, + "", + "declare predicates"); + add_command("declare-datatypes", + SMT_CMD_DECLARE_DATATYPES, + "", + "declare recursive datatypes"); + add_command("define", + SMT_CMD_DEFINE, + " or ( ( )*) ", + "define an expression shorthand"); + add_command("define-sorts", + SMT_CMD_DEFINE_SORTS, + "( )*", + "define shorthand for compound sorts, such as arrays"); + + add_command("declare-sort", + SMT_CMD_DECLARE_SORT, + "", "declare sort"); + add_command("define-sort", + SMT_CMD_DEFINE_SORT, + " (*) ", + "define shorthand for compound sorts, such as arrays"); + add_command("declare-fun", + SMT_CMD_DECLARE_FUN, + " (*) ", + "declare function or constant"); + add_command("declare-const", + SMT_CMD_DECLARE_CONST, + " ", + "declare constant"); + add_command("define-fun", + SMT_CMD_DEFINE_FUN, + " (*) ", + "define an expression shorthand"); + add_command("get-value", + SMT_CMD_GET_VALUE, + "(+)", + "evaluate list of terms"); + + + add_command("push", + SMT_CMD_PUSH, + "[]", + "push 1 (or ) scopes"); + add_command("pop", + SMT_CMD_POP, + "[]", + "pop 1 (or ) scopes"); + add_command("assert", + SMT_CMD_ASSERT, + "", + "assert expression"); + add_command("check-sat", + SMT_CMD_CHECK_SAT, + "*", + "check if the current context is satisfiable. If a list of boolean constants B is provided, then check if the current context is consistent with assigning every constant in B to true."); + add_command("get-core", + SMT_CMD_GET_CORE, + "+", + "check if the assumptions are consistent with the current context"); + add_command("next-sat", + SMT_CMD_NEXT_SAT, + "", + "get the next satisfying assignment"); + add_command("simplify", + SMT_CMD_SIMPLIFY, + "", + "simplify expression and print back result"); + add_command("get-implied-equalities", + SMT_CMD_GET_IMPLIED_EQUALITIES, + "*", + "obtain list of identifiers for expressions, such that equal expressions have the same identfiers" + ); + add_command("eval", + SMT_CMD_EVAL, + "", + "evaluate expression using the current model and print back result"); + add_command("get-assertions", + SMT_CMD_GET_ASSERTIONS, + "[]", + "retrieve current assertions"); + add_command("get-sat-assertions", + SMT_CMD_GET_SAT_ASSERTIONS, + "[]", + "retrieve current satisfying assignment"); + add_command("keep-sat-assertions", + SMT_CMD_KEEP_SAT_ASSERTIONS, + "", + "assert current satisfying assignment"); + add_command("get-unsat-core", + SMT_CMD_GET_UNSAT_CORE, + "[]", + "retrieve unsatisfiable core of assertions"); + add_command("get-proof", + SMT_CMD_GET_PROOF, + "[]", + "retrieve proof"); + add_command("set-option", + SMT_CMD_SET_OPTION, + "", + "set auxiliary options"); + add_command("set-info", + SMT_CMD_SET_INFO, + " ", + "set auxiliary information"); + add_command("get-info", + SMT_CMD_GET_INFO, + "", + "retrieve auxiliary information"); + add_command("echo", + SMT_CMD_ECHO, + "", + "display the given strings"); + add_command("exit", + SMT_CMD_EXIT, + "", + "exit Z3 session"); + m_str2token.insert("quit", SMT_CMD_EXIT); + add_option("print-success", + SMT_CMD_PRINT_SUCCESS, + "[]", + "toggle printing success", + &print_success_option); + add_option("verbosity", + SMT_CMD_VERBOSITY, + "", + "set verbosity", + &verbosity_option); + add_option("regular-output-channel", + SMT_CMD_OUTPUT_CHANNEL, + "[]", + "set name of alternate output channel", + &output_channel_option); + add_option("enable-cores", + SMT_CMD_ENABLE_CORES, + "", + "enable core extraction during solving", + &enable_cores_option); + add_option("set-param", + SMT_CMD_SET_PARAM, + " ", + "update a mutable configuration parameter", + &update_param_option); + + add_option("verbose-output-channel", + SMT_CMD_VERBOSE_OUTPUT_CHANNEL, + "", + "set output channel", + &set_verbose_output_channel_option + ); + add_option("produce-models", + SMT_CMD_PRODUCE_MODELS, + "[]", + "toggle model generation", + &produce_models_option); + // + // other options: + // add_option("random-seed", SMT_CMD_RANDOM_SEED, 0, ""); + // add_option("expand-definitions",SMT_CMD_EXPAND_DEFINITIONS, 0, ""); + // "produce-proofs" + // "produce-models" + // "produce-assignments" + // + + add_info("name", + SMT_CMD_NAME, + "", + "solver name", + &name_info + ); + add_info("version", + SMT_CMD_VERSION, + "", + "solver version", + &version_info + ); + add_info("authors", + SMT_CMD_AUTHORS, + "", + "solver authors", + &authors_info); + add_info("statistics", + SMT_CMD_STATS, + "", + "search statistics", + &stats_info); + add_info("all-statistics", + SMT_CMD_STATS, + "", + "search statistics", + &stats_info); + add_info("model", + SMT_CMD_MODEL, + "", + "model from satisfied assertions", + &model_info + ); + add_info("last-failure", + SMT_CMD_LAST_FAILURE, + "", + "reason for previous search failure", + &last_failure_info + ); + add_info("reason-unknown", + SMT_CMD_REASON_UNKNOWN, + "", + "reason for previous unknown answer; 'memout' for out of memory, 'incomplete' for everything else", + &reason_unknown_info + ); + add_info("time", + SMT_CMD_TIME, + "", + "time taken by solver", + &time_info + ); + add_info("labels", + SMT_CMD_LABELS, + "", + "retrieve (Boogie) labels from satisfiable assertion", + &labels_info); + add_info("help", + SMT_CMD_HELP, + "", + "print this help", + &help_info); + + + +#ifdef _EXTERNAL_RELEASE + Z3_set_ast_print_mode(m_context, Z3_PRINT_SMTLIB2_COMPLIANT); +#else + // use internal pretty printer + Z3_set_ast_print_mode(m_context, Z3_PRINT_SMTLIB_FULL); + // Z3_set_ast_print_mode(m_context, Z3_PRINT_SMTLIB2_COMPLIANT); +#endif + Z3_set_error_handler(m_context, error_handler); + set_error_stream(&m_error_stream); + set_warning_stream(&m_error_stream); + } + + ~cmd_context() { + if (m_model) { + Z3_del_model(m_context, m_model); + } + set_error_stream(0); + set_warning_stream(0); + } + + // + // NB. As it is now, the symbol table used in m_benchmark is not + // scoped. Declarations just live on or get over-written. + // + void push(unsigned num_scopes) { + while (num_scopes > 0) { + m_table.begin_scope(); + m_region.push_scope(); + Z3_push(m_context); + m_trail_lim.push_back(m_trail.size()); + m_asserted_lim.push_back(m_asserted.size()); + m_asserted_proxies_lim.push_back(m_asserted_proxies.size()); + --num_scopes; + m_check_sat_states.push_back(m_check_sat_state); + } + } + void pop(unsigned num_scopes) { + if (m_trail_lim.size() < num_scopes) { + num_scopes = m_trail_lim.size(); + } + Z3_pop(m_context, num_scopes); + m_region.pop_scope(num_scopes); + while (num_scopes > 0) { + m_table.end_scope(); + --num_scopes; + m_trail.resize(m_trail_lim.back()); + m_trail_lim.pop_back(); + m_asserted.resize(m_asserted_lim.back()); + m_asserted_lim.pop_back(); + m_asserted_proxies.resize(m_asserted_proxies_lim.back()); + m_asserted_proxies_lim.pop_back(); + m_check_sat_state = m_check_sat_states.back(); + m_check_sat_states.pop_back(); + } + m_proof = 0; + m_core_size = 0; + } + + static void error_handler(Z3_context, Z3_error_code code) { + throw cmd_exn(code); + } + + symbol_table& get_table() { return m_table; } + + Z3_context get_context() { return m_context; } + + std::ostream& get_out() { return *m_out; } + + void print_quoted(const char* str) { get_out() << "\"" << str << "\""; } + + void set_out(proto_expr* e) { m_out.reset(e); } + + void add_trail(ast* a) { m_trail.push_back(a); } + + void add_asserted(expr* e) { + if (get_enable_cores()) { + expr* proxy = m_manager.mk_fresh_const("proxy", m_manager.mk_bool_sort()); + expr_ref imp(m_manager); + // It is not necessary to use iff (it is just overhead). + imp = m_manager.mk_implies(proxy, e); + TRACE("proxy", tout << "new proxy:\n " << mk_pp(imp, m_manager) << "\n";); + Z3_assert_cnstr(m_context, reinterpret_cast(imp.get())); + m_asserted_proxies.push_back(proxy); + } + else { + Z3_assert_cnstr(m_context, reinterpret_cast(e)); + } + m_asserted.push_back(e); + if (m_check_sat_state != state_unsat) { + m_check_sat_state = state_new_assertion; + } + } + + unsigned get_num_assertions() const { return m_asserted.size(); } + + expr* get_assertion(unsigned idx) { return m_asserted[idx].get(); } + + Z3_ast* get_proxies_ptr() { return reinterpret_cast(m_asserted_proxies.c_ptr()); } + + unsigned* get_core_size_ptr() { return &m_core_size; } + + Z3_ast* get_core_ptr() { + m_core.resize(m_asserted_proxies.size()); + if (m_core_size > m_core.size()) { + m_core_size = m_core.size(); + } + return m_core.c_ptr(); + } + + region& get_region() { return m_region; } + + + void set_print_success(bool b) { m_print_success = b; } + + void set_enable_cores() { m_enable_cores = true; } + + void unset_enable_cores() { m_enable_cores = false; } + + void update_param(char const* p, char const* v) { + Z3_update_param_value(m_context, p, v); + } + + bool get_enable_cores() const { return m_enable_cores; } + + void print_success() { + if (m_print_success) { + *m_out << "success\n"; + } + } + + void print_unsupported() { + *m_out << "unsupported\n"; + } + + void print_failure(char const* msg, proto_expr* expr) { + if (Z3_get_parameters(m_context).m_display_error_for_vs) { + if (msg[0] != 'Z' || msg[1] != '3') { + *m_out << "Z3"; + if (expr) { + *m_out << "(" << expr->line() << "," << expr->pos() << ")"; + } + *m_out << ": ERROR: "; + } + *m_out << msg; + if (msg[strlen(msg)-1] != '\n') { + *m_out << "\n"; + } + } + else { + *m_out << "(error \""; + if (expr) { + *m_out << "line " << expr->line() << " column " << expr->pos() << ": "; + } + *m_out << escaped(msg, true) << "\")\n"; +#ifndef _EXTERNAL_RELEASE + exit(ERR_PARSER); +#endif + } + } + + Z3_model* get_model_ptr() { + if (m_model) { + Z3_del_model(m_context, m_model); + m_model = 0; + } + return &m_model; + } + + Z3_model get_model() { return m_model; } + + Z3_ast* get_proof_ptr() { + m_proof = 0; + return &m_proof; + } + + void get_proof(std::ostream& out, proto_expr* p_expr) { + Z3_ast pr = m_proof; + if (pr) { + out << Z3_ast_to_string(m_context, pr) << "\n"; + } + else if (Z3_get_parameters(m_context).m_proof_mode == PGM_DISABLED) { + print_failure("proofs are disabled - enable them using the configuration PROOF_MODE={1,2}", p_expr); + } + else { + print_failure("there is no proof to display", p_expr); + } + } + + smt_cmd_token string2token(char const * str) { + str2token::entry * e = m_str2token.find_core(str); + if (e) + return e->get_data().m_value; + else + return SMT_CMD_ERROR; + } + + class scoped_stopwatch { + cmd_context& m_ctx; + bool m_first; + void (*m_old_handler)(int); + + static scoped_stopwatch* get_sw() { return static_cast(g_sw); } + + static void on_ctrl_c(int) { + if (get_sw()->m_first) { + Z3_soft_check_cancel(get_sw()->m_ctx.get_context()); + get_sw()->m_first = false; + } + else { + signal (SIGINT, get_sw()->m_old_handler); + raise (SIGINT); + } + } + public: + scoped_stopwatch(cmd_context& c) : m_ctx(c), m_first(true) { + g_sw = this; + c.m_watch.reset(); + c.m_watch.start(); + m_old_handler = signal(SIGINT, on_ctrl_c); // TBD: parallel? + } + ~scoped_stopwatch() { + m_ctx.m_watch.stop(); + if (m_old_handler != SIG_ERR) { + signal(SIGINT, m_old_handler); + } + } + }; + + //static scoped_stopwatch* g_sw = 0; + + double get_seconds() { + return m_watch.get_seconds(); + } + + bool get_command_help(std::ostream& out, smt_cmd_token tok) { + if (!m_is_command[tok]) { + return false; + } + out << " (" << m_token2str[tok]; + if (m_token2args[tok] && m_token2args[tok][0]) { + out << " " << m_token2args[tok]; + } + out << ")\n"; + out << " " << m_token2help[tok] << "\n\n"; + return true; + } + + void get_help(std::ostream& out) { + out << "\" available commands:\n\n"; + for (unsigned i = 0; i < m_token2args.size(); ++i) { + get_command_help(out, static_cast(i)); + } + get_info_help(out, " "); + out << "\n"; + set_option_help(out, " "); + out << "\""; + } + + bool get_info(smt_cmd_token tok) { + for (unsigned i = 0; i < m_infos.size(); ++i) { + if (m_infos[i].m_token == tok) { + get_out() << ":" << m_token2str[tok] << " "; + m_infos[i].m_action(*this); + return true; + } + } + return false; + } + + void get_info_help(std::ostream& strm, char const* line_start) { + for (unsigned i = 0; i < m_infos.size(); ++i) { + smt_cmd_token tok = m_infos[i].m_token; + strm << line_start << "(get-info " << m_token2str[tok]; + if (m_token2args[tok] && m_token2args[tok][0]) { + strm << " " << m_token2args[tok]; + } + strm << ")\n"; + strm << line_start << " " << m_token2help[tok] << "\n"; + } + } + + bool set_option(smt_cmd_token tok, proto_expr* const* chs) { + for (unsigned i = 0; i < m_options.size(); ++i) { + if (m_options[i].m_token == tok) { + return m_options[i].m_action(*this, chs); + } + } + return update_param_option(*this, chs-1); + // return false; + } + + bool set_info(proto_expr* e0, proto_expr* const* chs) { + proto_expr* e1 = chs[0]; + symbol s0, s1; + if (e0) + s0 = e0->string(); + if (e1) + s1 = e1->string(); + + if (s0 == symbol("status") && s1 == symbol("sat")) { + m_status = smtlib::benchmark::SAT; + } + else if (s0 == symbol("status") && s1 == symbol("unsat")) { + m_status = smtlib::benchmark::UNSAT; + } + else if (s0 == symbol("status") && s1 == symbol("unknown")) { + m_status = smtlib::benchmark::UNKNOWN; + } + else { +#ifdef Z3DEBUG + std::cout << s0 << " " << s1 << "\n"; +#endif + } + + // :source + // :smt-lib-version + // :category + // :status + // no-op + return true; + } + + void set_option_help(std::ostream& strm, char const* line_start) { + for (unsigned i = 0; i < m_options.size(); ++i) { + smt_cmd_token tok = m_options[i].m_token; + strm << line_start << "(set-option " << m_token2str[tok]; + if (m_token2args[tok] && m_token2args[tok][0]) { + get_out() << " " << m_token2args[tok]; + } + strm << ")\n"; + strm << line_start << " " << m_token2help[tok] << "\n"; + } + } + + unsigned parse_opt_numeral(proto_expr* e, unsigned default_value) { + if (e && e->kind() == proto_expr::INT) { + rational r = e->number(); + if (r.is_unsigned()) { + return r.get_unsigned(); + } + } + return default_value; + } + + bool parse_opt_bool(proto_expr* e, bool default_value) { + if (e && e->kind() == proto_expr::ID) { + if (strcmp(e->string().bare_str(), "true") == 0) { + return true; + } + if (strcmp(e->string().bare_str(), "false") == 0) { + return false; + } + } + return default_value; + } + + + void get_core(proto_expr* p_expr, std::ostream& out, + unsigned sz, expr** exprs, bool just_get_core) { + + + Z3_context ctx = get_context(); + Z3_lbool r; + scoped_stopwatch stopw(*this); + + if (get_enable_cores()) { + print_failure("cores should be disabled", p_expr); + return; + } + + for (unsigned i = 0; i < sz; ++i) { + if (!is_uninterp_const(exprs[i]) || !m_manager.is_bool(exprs[i])) { + print_failure("assumptions must be boolean constants (e.g., p1, p2, q1)", p_expr); + return; + } + } + + // set_enable_cores(); + // push(1); + // for (unsigned i = 0; i < sz; ++i) { + // add_asserted(exprs[i]); + // } + + unsigned max_core_size = sz; + unsigned core_size = sz; + m_core.reserve(max_core_size); + Z3_ast * core = m_core.c_ptr(); + + r = Z3_check_assumptions(ctx, sz, reinterpret_cast(exprs), + get_model_ptr(), get_proof_ptr(), + &core_size, core); + switch(r) { + case Z3_L_FALSE: + if (!just_get_core) + out << "unsat\n"; + print_core_as_is(out, core_size, core); + m_check_sat_state = state_unsat; + break; + case Z3_L_TRUE: + out << "sat\n"; + m_check_sat_state = state_sat; + break; + case Z3_L_UNDEF: + out << "unknown\n"; + m_check_sat_state = state_unknown; + break; + default: + throw default_exception("unexpected output of check-sat\n"); + break; + } + // unset_enable_cores(); + // pop(1); + } + + void check_sat(std::ostream& out) { + Z3_context ctx = get_context(); + Z3_lbool r; + scoped_stopwatch stopw(*this); + + if (get_enable_cores()) { + r = Z3_check_assumptions(ctx, get_num_assertions(), get_proxies_ptr(), + get_model_ptr(), get_proof_ptr(), + get_core_size_ptr(), get_core_ptr()); + + } + else if (Z3_get_parameters(ctx).m_proof_mode != PGM_DISABLED) { + unsigned core_size = 0; + Z3_ast core[1] = { 0 }; + r = Z3_check_assumptions(ctx, 0, 0, get_model_ptr(), get_proof_ptr(), &core_size, core); + } + else if (Z3_get_parameters(ctx).m_model) { + r = Z3_check_and_get_model(ctx, get_model_ptr()); + } + else { + r = Z3_check(ctx); + } + switch(r) { + case Z3_L_FALSE: + out << "unsat\n"; + m_check_sat_state = state_unsat; + break; + case Z3_L_TRUE: + out << "sat\n"; + m_check_sat_state = state_sat; + break; + case Z3_L_UNDEF: + out << "unknown\n"; + m_check_sat_state = state_unknown; + break; + default: + throw default_exception("unexpected output of check-sat\n"); + break; + } + + // check status (duplicate from smtlib_sover) + // smtlib_solver contains support for + // - spc + // - missing. + // - dumping statistics / proofs / model / labels + // - redundant given additional command-line options + // + switch(m_status) { + case smtlib::benchmark::SAT: + if (r == Z3_L_FALSE) { +#ifdef _EXTERNAL_RELEASE + std::cout << "unsat - check annotation which says sat\n"; +#else + std::cout << "BUG: unsoundness.\n"; + exit(ERR_UNSOUNDNESS); +#endif + } + else if (r == Z3_L_UNDEF) { +#ifndef _EXTERNAL_RELEASE + std::cout << "BUG: gaveup.\n"; + exit(ERR_UNKNOWN_RESULT); +#endif + } + break; + case smtlib::benchmark::UNSAT: + if (r == Z3_L_TRUE) { +#ifdef _EXTERNAL_RELEASE + std::cout << "sat - check annotation which says unsat\n"; +#else + std::cout << "BUG: incompleteness.\n"; + exit(ERR_INCOMPLETENESS); +#endif + } + else if (r == Z3_L_UNDEF) { +#ifndef _EXTERNAL_RELEASE + std::cout << "BUG: gaveup.\n"; + exit(ERR_UNKNOWN_RESULT); +#endif + } + break; + default: + break; + } + } + + void next_sat(std::ostream& out) { + Z3_context ctx = get_context(); + if (m_check_sat_state == state_sat || m_check_sat_state == state_unknown) { + Z3_literals lits = Z3_get_relevant_literals(ctx); + if (lits) { + Z3_block_literals(ctx, lits); + Z3_del_literals(ctx, lits); + } + } + check_sat(out); + } + + void get_sat_assertions(std::ostream& out) { + if (m_check_sat_state == state_unsat) { + out << "false\n"; + } + else { + Z3_ast assignment = Z3_get_context_assignment(m_context); + out << Z3_ast_to_string(m_context, reinterpret_cast(assignment)) << "\n"; + } + } + + void get_implied_equalities(std::ostream& out, unsigned num_exprs, expr* const* exprs) { + buffer class_ids(num_exprs, UINT_MAX); + Z3_lbool r = Z3_get_implied_equalities(m_context, + num_exprs, + (Z3_ast*) exprs, + class_ids.c_ptr()); + if (r == Z3_L_FALSE) { + out << "unsat\n"; + return; + } + out << "("; + for (unsigned i = 0; i < num_exprs; ++i) { + out << class_ids[i]; + if (i + 1 < num_exprs) { + out << " "; + } + } + out << ")\n"; + } + + void eval(std::ostream& out, proto_expr* p_expr, expr* e) { + Z3_model m = get_model(); + if (!m) { + print_failure("There is no model in the current context", p_expr); + return; + } + Z3_ast result = 0; + Z3_bool fully_simplified = Z3_eval(m_context, m, reinterpret_cast(e), &result); + if (!result) { + print_failure("Evaluation was not successful", p_expr); + return; + } + (void) fully_simplified; + out << Z3_ast_to_string(m_context, result) << "\n"; + } + + void eval(std::ostream& out, proto_expr* p_expr, unsigned num_args, expr*const* args) { + svector results; + Z3_model m = get_model(); + if (!m) { + print_failure("There is no model in the current context", p_expr); + return; + } + for (unsigned i = 0; i < num_args; ++i) { + Z3_ast result = 0; + Z3_bool fully_simplified = Z3_eval(m_context, m, reinterpret_cast(args[i]), &result); + if (!result) { + print_failure("Evaluation was not successful", p_expr); + return; + } + (void) fully_simplified; + results.push_back(result); + } + out << "("; + for (unsigned i = 0; i < num_args; ++i) { + out << Z3_ast_to_string(m_context, results[i]); + if (i + 1 < num_args) { + out << "\n"; + } + } + out << ")\n"; + } + + + + + void get_unsat_core(std::ostream& out, proto_expr* p_expr) { + if (!get_enable_cores()) { + print_failure("cores have not been enabled, use (set-option enable-cores)", p_expr); + return; + } + print_core(out); + } + + Z3_ast find_proxy(Z3_ast proxy) { + for (unsigned i = 0; i < m_asserted.size(); ++i) { + if (m_asserted_proxies[i] == (expr*)proxy) { + return reinterpret_cast(m_asserted[i].get()); + } + } + UNREACHABLE(); + return proxy; + } + + void print_core(std::ostream& out) { + unsigned csz = *get_core_size_ptr(); + out << "("; + for (unsigned i = 0; i < csz; ++i) { + out << Z3_ast_to_string(m_context, find_proxy(get_core_ptr()[i])); + if (i + 1 < csz) out << "\n"; + } + out << ")\n"; + } + + void print_core_as_is(std::ostream & out, unsigned csz, Z3_ast * core) { + out << "("; + for (unsigned i = 0; i < csz; ++i) { + out << Z3_ast_to_string(m_context, core[i]); + if (i + 1 < csz) out << " "; + } + out << ")\n"; + } + + void get_assertions(std::ostream& out) { + out << "("; + unsigned num_assertions = get_num_assertions(); + for (unsigned i = 0; i < num_assertions; ++i) { + out << Z3_ast_to_string(m_context, reinterpret_cast(get_assertion(i))); + if (i + 1 < num_assertions) out << "\n"; + } + out << ")\n"; + } + + bool has_error() { + return m_error_stream.tellp() > 0; + } + + void flush_errors() { + if (has_error()) { + m_error_stream.put(0); + print_failure(m_error_stream.str().c_str(), 0); + m_error_stream.seekp(0); + m_error_stream.clear(); + } + } + + std::ostream& get_error_stream() { + return m_error_stream; + } + +private: + + void add_command( + char const* name, + smt_cmd_token tok, + char const* args, + char const* help, + bool is_command = true + ) { + m_str2token.insert(name, tok); + if ((unsigned)tok >= m_token2str.size()) { + m_token2str.resize(tok+1); + m_token2help.resize(tok+1); + m_token2args.resize(tok+1); + m_is_command.resize(tok+1); + } + m_token2str[tok] = name; + m_token2help[tok] = help; + m_token2args[tok] = args; + m_is_command[tok] = is_command; + } + + void add_info( + char const* name, + smt_cmd_token t, + char const* args, + char const* help, + void (*action)(cmd_context&) + ) + { + add_command(name, t, args, help, false); + m_infos.push_back(cmd_info(t, action)); + } + + void add_option( + char const* name, + smt_cmd_token t, + char const* args, + char const* help, + bool (*action)(cmd_context&, proto_expr* const* exprs) + ) + { + add_command(name, t, args, help, false); + m_options.push_back(opt(t, action)); + } + + + static void name_info(cmd_context& ctx) { ctx.print_quoted("Z3"); } + + static void version_info(cmd_context& ctx) { + unsigned maj, min, bn, rn; + Z3_get_version(&maj,&min,&bn,&rn); + ctx.get_out() << "\"" << maj << "." << min << "-" << bn << "." << rn << "\""; + } + + static void authors_info(cmd_context& ctx) { ctx.print_quoted("Leonardo de Moura and Nikolaj Bjorner"); } + + static void last_failure_info(cmd_context& cmd_ctx) { + Z3_context ctx = cmd_ctx.get_context(); + Z3_search_failure sf = Z3_get_search_failure(ctx); + static char const * reasons[8] = { "no failure", "unknown", "timeout", "memout", + "user canceled", "exceeded conflict bound", + "incomplete theory support", + "formulas used quantifiers that may not have been instantiated fully" + }; + if (sf < 8) { + cmd_ctx.print_quoted(reasons[sf]); + } + else { + cmd_ctx.print_quoted("not documented"); + UNREACHABLE(); + } + } + + static void reason_unknown_info(cmd_context& cmd_ctx) { + Z3_context ctx = cmd_ctx.get_context(); + Z3_search_failure sf = Z3_get_search_failure(ctx); + + if (sf == 3) + cmd_ctx.print_quoted("memout"); + else + cmd_ctx.print_quoted("incomplete"); + } + + static void stats_info(cmd_context& cmd_ctx) { + cmd_ctx.get_out() << "\"" << escaped(Z3_statistics_to_string(cmd_ctx.get_context()), true) << "\""; + } + + static void model_info(cmd_context& cmd_ctx) { + Z3_context ctx = cmd_ctx.get_context(); + Z3_model m = cmd_ctx.get_model(); + if (m) { + if (Z3_get_parameters(ctx).m_model_v1_pp || Z3_get_parameters(ctx).m_model_v2_pp) { + cmd_ctx.get_out() << "\"" << escaped(Z3_model_to_string(ctx, m), true) << "\""; + } else { + cmd_ctx.get_out() << "(z3_model" << std::endl + << Z3_model_to_string(ctx, m) + << ")"; + } + } + else if (!Z3_get_parameters(ctx).m_model) { + cmd_ctx.print_quoted("models are disabled - enable them using the configuration MODEL=true"); + } + else { + cmd_ctx.print_quoted("there is no model at the current scope"); + } + } + + static void time_info(cmd_context& cmd_ctx) { + cmd_ctx.get_out() << cmd_ctx.get_seconds(); + } + + static void labels_info(cmd_context& cmd_ctx) { + std::ostream& out = cmd_ctx.get_out(); + Z3_context ctx = cmd_ctx.get_context(); + Z3_literals lits = Z3_get_relevant_labels(ctx); + unsigned sz = Z3_get_num_literals(ctx, lits); + if (sz > 0) { + out << "(z3_labels"; + for (unsigned i = 0; i < sz; ++i) { + out << " "; + out << Z3_get_symbol_string(ctx, Z3_get_label_symbol(ctx, lits, i)); + } + out << ")"; + } + Z3_del_literals(ctx, lits); + } + + static void help_info(cmd_context& cmd_ctx) { + cmd_ctx.get_help(cmd_ctx.get_out()); + } + + static bool print_success_option(cmd_context& cmd_ctx, proto_expr*const* chs) { + cmd_ctx.set_print_success(cmd_ctx.parse_opt_bool(chs?*chs:0, true)); + return true; + } + + static bool produce_models_option(cmd_context& cmd_ctx, proto_expr*const* chs) { + cmd_ctx.update_param("MODEL", cmd_ctx.parse_opt_bool(chs?*chs:0, true) ? "true" : "false"); + return true; + } + + static bool verbosity_option(cmd_context& cmd_ctx, proto_expr*const* chs) { + unsigned lvl = cmd_ctx.parse_opt_numeral(chs?*chs:0, 0); + set_verbosity_level(lvl); + return true; + } + + static bool output_channel_option(cmd_context& cmd_ctx, proto_expr*const* chs) { + cmd_ctx.set_out(chs?*chs:0); + return true; + } + + static bool enable_cores_option(cmd_context& cmd_ctx, proto_expr*const* chs) { + cmd_ctx.set_enable_cores(); + return true; + } + + static void print_parameters(cmd_context& cmd_ctx) { + front_end_params& p = Z3_get_parameters(cmd_ctx.m_context); + ini_params ini; + p.register_params(ini); + ini.display_params(cmd_ctx.get_out()); + } + + static void print_parameter_help(char const* param, cmd_context& cmd_ctx) { + front_end_params& p = Z3_get_parameters(cmd_ctx.m_context); + ini_params ini; + p.register_params(ini); + ini.display_parameter_help(param,cmd_ctx.get_out()); + } + + static bool update_param_option(cmd_context& cmd_ctx, proto_expr*const* chs) { + if (!chs) { + print_parameters(cmd_ctx); + return false; + } + if (chs[0] && !chs[1] && chs[0]->kind() == proto_expr::CONS) { + chs = chs[0]->children(); + } + + proto_expr* p = chs[0]; + proto_expr* v = chs[1]; + char const* p_string = 0; + char const*v_string = 0; + std::string v_str; + if (!p || (p->kind() != proto_expr::ID && p->kind() != proto_expr::STRING && p->kind() != proto_expr::ANNOTATION)) { + print_parameters(cmd_ctx); + return false; + } + p_string = p->string().bare_str(); + if (v && (v->kind() == proto_expr::INT || v->kind() == proto_expr::FLOAT)) { + v_str += v->number().to_string(); + v_string = v_str.c_str(); + } + else if (!v || (v->kind() != proto_expr::ID && v->kind() != proto_expr::STRING)) { + print_parameter_help(p->string().bare_str(), cmd_ctx); + return false; + } + else { + v_str = v->string().bare_str(); + if (v_str.length() > 2 && v_str[0] == '|' && v_str[v_str.length() - 1] == '|') { + // strip the quotes + v_str = v_str.substr(1, v_str.length() - 2); + } + v_string = v_str.c_str(); + } + // hack for generating warning message when trying to set PROOF_MODE inside the command context. + if (strcmp(p_string, "PROOF_MODE") == 0) { + warning_msg("PROOF_MODE can only be set as a command line option when invoking z3.exe (e.g., \"z3.exe PROOF_MODE=2 file.smt2\"), or when creating a fresh logical context using the Z3 API."); + return false; + } + cmd_ctx.update_param(p_string, v_string); + return true; + } + + static bool set_verbose_output_channel_option(cmd_context& cmd_ctx, proto_expr*const* chs) { + cmd_ctx.m_verbose.reset(chs?(*chs):0); + set_verbose_stream(*cmd_ctx.m_verbose); + return true; + } +}; + + +class smtparser : public parser { + struct builtin_op { + family_id m_family_id; + decl_kind m_kind; + builtin_op() : m_family_id(null_family_id), m_kind(0) {} + builtin_op(family_id fid, decl_kind k) : m_family_id(fid), m_kind(k) {} + }; + + class add_plugins { + + public: + add_plugins(ast_manager& m) { +#define REGISTER_PLUGIN(NAME, MK) { \ + family_id fid = m.get_family_id(symbol(NAME)); \ + if (!m.has_plugin(fid)) { \ + m.register_plugin(fid, MK); \ + } \ + } ((void) 0) + + REGISTER_PLUGIN("arith", alloc(arith_decl_plugin)); + REGISTER_PLUGIN("bv", alloc(bv_decl_plugin)); + REGISTER_PLUGIN("array", alloc(array_decl_plugin)); + + }; + }; + + ast_manager& m_manager; + add_plugins m_plugins; + arith_util m_anum_util; + bv_util m_bvnum_util; + pattern_validator m_pattern_validator; + bool m_ignore_user_patterns; + unsigned m_binding_level; // scope level for bound vars + benchmark m_benchmark; // currently parsed benchmark + sort_ref_vector m_pinned_sorts; + + typedef map op_map; + op_map m_builtin_ops; + op_map m_builtin_sorts; + + symbol m_let; // commonly used symbols. + symbol m_flet; + symbol m_forall; + symbol m_exists; + symbol m_lblneg; + symbol m_lblpos; + symbol m_associative; + symbol m_commutative; + symbol m_injective; + symbol m_sorts; + symbol m_funs; + symbol m_preds; + symbol m_definition; + symbol m_axioms; + symbol m_notes; + symbol m_theory; + symbol m_language; + symbol m_extensions; + symbol m_array; + symbol m_bang; + symbol m_underscore; + sort* m_int_sort; + sort* m_real_sort; + family_id m_bv_fid; + family_id m_arith_fid; + family_id m_array_fid; + family_id m_rel_fid; + datatype_decl_plugin * m_dt_plugin; + datatype_util m_dt_util; + substitution_tree m_st; + func_decl * m_sk_hack; + std::ostream* m_err; + bool m_display_error_for_vs; + + +public: + + smtparser(ast_manager& m, bool ignore_user_patterns): + m_manager(m), + m_plugins(m), + m_anum_util(m), + m_bvnum_util(m), + m_pattern_validator(m), + m_ignore_user_patterns(ignore_user_patterns), + m_binding_level(0), + m_benchmark(m_manager, symbol("")), + m_pinned_sorts(m), + m_let("let"), + m_flet("flet"), + m_forall("forall"), + m_exists("exists"), + m_lblneg("lblneg"), + m_lblpos("lblpos"), + m_associative("assoc"), + m_commutative("comm"), + m_injective("injective"), + m_sorts("sorts"), + m_funs("funs"), + m_preds("preds"), + m_definition("definition"), + m_axioms("axioms"), + m_notes("notes"), + m_theory("theory"), + m_language("language"), + m_extensions("extensions"), + m_array("array"), + m_bang("!"), + m_underscore("_"), + m_dt_plugin(0), + m_dt_util(m), + m_st(m), + m_err(0), + m_display_error_for_vs(false) + { + family_id bfid = m_manager.get_basic_family_id(); + + add_builtin_type("bool", bfid, BOOL_SORT); + m_benchmark.get_symtable()->insert(symbol("Array"), alloc(array_sort, m)); + m_benchmark.get_symtable()->insert(symbol("BitVec"), alloc(bv_sort, m)); + + + add_builtins(bfid); + } + + ~smtparser() { + } + + void set_error_stream(std::ostream& strm) { m_err = &strm; } + + std::ostream& get_err() { return m_err?(*m_err):std::cerr; } + + bool ignore_user_patterns() const { return m_ignore_user_patterns; } + + bool parse_stream(std::istream& stream) { + proto_region region; + scanner scanner(stream, get_err(), false); + proto_expr_parser parser(region, scanner, get_err()); + return parse(parser); + } + + bool parse_file(char const * filename) { + timeit tt(get_verbosity_level() >= 100, "parsing file"); + if (filename != 0) { + std::ifstream stream(filename); + if (!stream) { + get_err() << "ERROR: could not open file '" << filename << "'.\n"; + return false; + } + return parse_stream(stream); + } + else { + return parse_stream(std::cin); + } + } + + bool parse_string(char const * str) { + std::string s = str; + std::istringstream is(s); + return parse_stream(is); + } + + bool parse_commands(Z3_context ctx, std::istream& is, std::ostream& os) { + set_error_stream(os); + cmd_context context(m_manager, ctx, is, os); + set_error_stream(context.get_error_stream()); + proto_region proto_region; + scanner scanner(is, context.get_error_stream(), true); + proto_expr_parser parser(proto_region, scanner, context.get_error_stream()); + + m_display_error_for_vs = Z3_get_parameters(ctx).m_display_error_for_vs; + + ptr_vector exprs; + while (!parser.at_eof()) { + proto_region.reset(); + exprs.reset(); + if (!parser.parse(exprs, true)) { + context.flush_errors(); + context.get_out().flush(); + break; + } + if (exprs.empty()) { + break; + } + SASSERT(exprs.size() == 1); + try { + if (!process_command(context, exprs.back())) { + break; + } + } + catch(cmd_exn(ex)) { + context.flush_errors(); + context.get_out().flush(); + context.print_failure(Z3_get_error_msg(ex.get()), exprs.back()); + } + context.flush_errors(); + context.get_out().flush(); + } + return true; + } + + void add_builtin_op(char const * s, family_id fid, decl_kind k) { + m_builtin_ops.insert(symbol(s), builtin_op(fid, k)); + } + + void add_builtin_type(char const * s, family_id fid, decl_kind k) { + m_builtin_sorts.insert(symbol(s), builtin_op(fid, k)); + } + + void initialize_smtlib() { + + if (!m_dt_plugin) { + family_id fid = m_manager.get_family_id("datatype"); + if (!m_manager.has_plugin(fid)) { + m_manager.register_plugin(fid, alloc(datatype_decl_plugin)); + } + m_dt_plugin = static_cast(m_manager.get_plugin(fid)); + } + + smtlib::symtable* table = m_benchmark.get_symtable(); + + symbol arith("arith"); + family_id afid = m_manager.get_family_id(arith); + m_arith_fid = afid; + + add_builtin_type("Int", afid, INT_SORT); + add_builtin_type("Real", afid, REAL_SORT); + add_builtin_type("Bool", m_manager.get_basic_family_id(), BOOL_SORT); + + m_int_sort = m_manager.mk_sort(m_arith_fid, INT_SORT); + m_real_sort = m_manager.mk_sort(m_arith_fid, REAL_SORT); + + add_builtins(afid); + + symbol bv("bv"); + family_id bfid = m_manager.get_family_id(bv); + m_bv_fid = bfid; + + add_builtins(bfid); + + add_builtin_type("BitVec", bfid, BV_SORT); + + symbol array("array"); + afid = m_manager.get_family_id(array); + m_array_fid = afid; + + add_builtins(afid); + + sort* a1, *a2; + func_decl* store1, *sel1, *store2, *sel2; + + // Array + parameter params0[2] = { parameter(m_int_sort), parameter(m_int_sort) }; + a1 = m_manager.mk_sort(afid, ARRAY_SORT, 2, params0); + table->insert(symbol("Array"), a1); + parameter param0(a1); + sort * args0[3] = { a1, m_int_sort, m_int_sort }; + store1 = m_manager.mk_func_decl(afid, OP_STORE, 0, 0, 3, args0); + table->insert(symbol("store"), store1); + sel1 = m_manager.mk_func_decl(afid, OP_SELECT, 0, 0, 2, args0); + table->insert(symbol("select"), sel1); + + // Array1 + parameter params1[2] = { parameter(m_int_sort), parameter(m_real_sort) }; + a1 = m_manager.mk_sort(afid, ARRAY_SORT, 2, params1); + table->insert(symbol("Array1"), a1); + parameter param1(a1); + sort * args1[3] = { a1, m_int_sort, m_real_sort }; + store1 = m_manager.mk_func_decl(afid, OP_STORE, 0, 0, 3, args1); + table->insert(symbol("store"), store1); + sel1 = m_manager.mk_func_decl(afid, OP_SELECT, 0, 0, 2, args1); + table->insert(symbol("select"), sel1); + + // Array2 + parameter params2[2] = { parameter(m_int_sort), parameter(a1) }; + a2 = m_manager.mk_sort(afid, ARRAY_SORT, 2, params2); + table->insert(symbol("Array2"), a2); + parameter param2(a2); + sort * args2[3] = { a2, m_int_sort, a1 }; + store2 = m_manager.mk_func_decl(afid, OP_STORE, 0, 0, 3, args2); + table->insert(symbol("store"), store2); + sel2 = m_manager.mk_func_decl(afid, OP_SELECT, 0, 0, 2, args2); + table->insert(symbol("select"), sel2); + + m_benchmark.declare_sort(symbol("U")); + + sort * bool_sort = m_manager.mk_bool_sort(); + m_sk_hack = m_manager.mk_func_decl(symbol("sk_hack"), 1, &bool_sort, bool_sort); + table->insert(symbol("sk_hack"), m_sk_hack); + } + + + void add_builtins(family_id fid) { + decl_plugin * plugin = m_manager.get_plugin(fid); + SASSERT(plugin); + svector op_names; + plugin->get_op_names(op_names); + for (unsigned i = 0; i < op_names.size(); ++i) { + add_builtin_op(op_names[i].m_name.bare_str(), fid, op_names[i].m_kind); + } + } + + smtlib::benchmark* get_benchmark() { return &m_benchmark; } + +private: + + + bool parse(proto_expr_parser& parser) { + symbol benchmark("benchmark"); + symbol name(""); + proto_expr* const* rest = 0; + ptr_vector proto_exprs; + bool result = parser.parse(proto_exprs); + proto_expr* proto_expr = 0; + + if (!result) { + get_err() << "ERROR: parse error at line " << parser.get_line() << ".\n"; + } + + for (unsigned i = 0; result && i < proto_exprs.size(); ++i) { + + proto_expr = proto_exprs[i]; + + if (match_cons(proto_expr, benchmark, name, rest)) { + result = make_benchmark(name, rest); + } + else if (proto_expr && proto_expr->kind() != proto_expr::COMMENT) { + set_error("could not match expression to benchmark ", proto_expr); + } + else { + // proto_expr->kind() == proto_expr::COMMENT. + } + } + return result; + } + + void error_prefix(proto_expr* e) { + if (m_display_error_for_vs) { + if (e) { + get_err() << "Z3(" << e->line() << "," << e->pos() << "): ERROR: "; + } + } + else { + get_err() << "ERROR: "; + if (e) { + get_err() << "line " << e->line() << " column " << e->pos() << ": "; + } + } + } + + void set_error(char const * str, proto_expr* e) { + error_prefix(e); + if (e->kind() == proto_expr::ID) { + get_err() << str << " '" << e->string() << "'.\n"; + } + else { + get_err() << str << ".\n"; + } + } + + template + void set_error(T1 str1, T2 str2, proto_expr* e) { + error_prefix(e); + get_err() << str1 << " " << str2 << ".\n"; + } + + template + void set_error(T1 str1, T2 str2, T3 str3, proto_expr* e) { + error_prefix(e); + get_err() << str1 << str2 << str3 << ".\n"; + } + + bool match_cons(proto_expr * e, symbol const & sym, symbol & name, proto_expr* const*& proto_exprs) { + if (e && + e->kind() == proto_expr::CONS && + e->children() && + e->children()[0] && + e->children()[0]->string() == sym && + e->children()[1]) { + proto_exprs = e->children() + 2; + name = e->children()[1]->string(); + return true; + } + + return false; + } + + bool make_benchmark(symbol & name, proto_expr * const* proto_exprs) { + symbol extrasorts("extrasorts"); + symbol extrafuns("extrafuns"); + symbol extrapreds("extrapreds"); + symbol datatypes("datatypes"); + symbol unify("unify"); + symbol unify_fail("unify-fail"); +#if !defined(SMTCOMP) && !defined(_EXTERNAL_RELEASE) + symbol kbo_lt("kbo_lt"); + symbol kbo_gt("kbo_gt"); + symbol kbo_eq("kbo_eq"); + symbol kbo_un("kbo_un"); + symbol lpo_lt("lpo_lt"); + symbol lpo_gt("lpo_gt"); + symbol lpo_eq("lpo_eq"); + symbol lpo_un("lpo_un"); + symbol st_insert("st_insert"); + symbol st_erase("st_erase"); + symbol st_reset("st_reset"); + symbol st_unify("st_unify"); + symbol st_inst("st_inst"); + symbol st_gen("st_gen"); + symbol st_display("st_display"); +#endif + symbol assumption("assumption"); + symbol assumption_core("assumption-core"); + symbol define_sorts_sym("define_sorts"); + symbol logic("logic"); + symbol formula("formula"); + symbol status("status"); + symbol sat("sat"); + symbol unsat("unsat"); + symbol unknown("unknown"); + symbol empty(""); + symbol source("source"); + symbol difficulty("difficulty"); + symbol category("category"); + bool success = true; + + push_benchmark(name); + + while (proto_exprs && *proto_exprs) { + proto_expr* e = *proto_exprs; + ++proto_exprs; + proto_expr* e1 = *proto_exprs; + + if (logic == e->string() && e1) { + name = e1->string(); + m_benchmark.set_logic(name); + + set_default_num_sort(name); + + if (name == symbol("QF_AX")) { + // Hack for supporting new QF_AX theory... + sort * index = m_manager.mk_sort(symbol("Index")); + sort * element = m_manager.mk_sort(symbol("Element")); + parameter params[2] = { parameter(index), parameter(element) }; + sort * array = m_manager.mk_sort(m_array_fid, ARRAY_SORT, 2, params); + smtlib::symtable* table = m_benchmark.get_symtable(); + table->insert(symbol("Index"), index); + table->insert(symbol("Element"), element); + // overwrite Array definition... + table->insert(symbol("Array"), array); + sort * args[3] = { array, index, element }; + func_decl * store = m_manager.mk_func_decl(m_array_fid, OP_STORE, 0, 0, 3, args); + table->insert(symbol("store"), store); + func_decl * sel = m_manager.mk_func_decl(m_array_fid, OP_SELECT, 0, 0, 2, args); + table->insert(symbol("select"), sel); + } + + ++proto_exprs; + continue; + } + + if (assumption == e->string() && e1) { + expr_ref t(m_manager); + if (!make_expression(e1, t) || + !push_assumption(t.get())) { + return false; + } + ++proto_exprs; + continue; + } + + if (assumption_core == e->string() && e1) { + expr_ref t(m_manager); + if (!make_expression(e1, t)) + return false; + m_benchmark.add_assumption(t.get()); + ++proto_exprs; + continue; + } + + if (define_sorts_sym == e->string() && e1) { + if (!define_sorts(e1)) { + return false; + } + ++proto_exprs; + continue; + } + + if (formula == e->string() && e1) { + expr_ref t(m_manager); + if (!make_expression(e1, t) || + !push_formula(t.get())) { + return false; + } + ++proto_exprs; + continue; + } + + if (status == e->string() && e1) { + if (sat == e1->string()) { + if (!push_status(smtlib::benchmark::SAT)) { + set_error("could not push status ", e1->string(),e1); + return false; + } + } + else if (unsat == e1->string()) { + if (!push_status(smtlib::benchmark::UNSAT)) { + set_error("could not push status ", e1->string(),e1); + return false; + } + } + else if (unknown == e1->string()) { + if (!push_status(smtlib::benchmark::UNKNOWN)) { + set_error("could not push status ", e1->string(),e1); + return false; + } + } + else { + set_error("could not recognize status ", e1->string(),e1); + return false; + } + ++proto_exprs; + continue; + } + + if (extrasorts == e->string() && e1) { + if (!declare_sorts(e1)) { + return false; + } + ++proto_exprs; + continue; + } + if (extrafuns == e->string() && e1) { + if (!declare_funs(e1)) { + return false; + } + ++proto_exprs; + continue; + } + if (extrapreds == e->string() && e1) { + if (!declare_preds(e1)) { + return false; + } + ++proto_exprs; + continue; + } + if (datatypes == e->string() && e1) { + if (!declare_datatypes(e1)) { + return false; + } + ++proto_exprs; + continue; + } + if ((unify == e->string() || unify_fail == e->string()) && e1) { + if (!test_unify(e1, unify == e->string())) { + return false; + } + ++proto_exprs; + continue; + } +#if !defined(SMTCOMP) && !defined(_EXTERNAL_RELEASE) + if ((kbo_lt == e->string() || kbo_gt == e->string() || kbo_eq == e->string() || kbo_un == e->string()) && e1) { + if (!test_kbo(e1, e->string())) { + return false; + } + ++proto_exprs; + continue; + } + if ((lpo_lt == e->string() || lpo_gt == e->string() || lpo_eq == e->string() || lpo_un == e->string()) && e1) { + if (!test_lpo(e1, e->string())) { + return false; + } + ++proto_exprs; + continue; + } + if ((st_insert == e->string() || st_erase == e->string()) && e1) { + if (!test_st(e1, e->string())) { + return false; + } + ++proto_exprs; + continue; + } + if ((st_unify == e->string() || st_inst == e->string() || st_gen == e->string()) && e1) { + if (!test_st_visit(e1, e->string())) { + return false; + } + ++proto_exprs; + continue; + } + if (st_reset == e->string()) { + m_st.reset(); + ++proto_exprs; + continue; + } + if (st_display == e->string()) { + m_st.display(std::cout); + ++proto_exprs; + continue; + } +#endif + if (m_notes == e->string() && e1) { + ++proto_exprs; + continue; + } + + if ((source == e->string() || difficulty == e->string() || category == e->string()) && e1) { + ++proto_exprs; + continue; + } + + if (e->string() != empty) { + set_error("ignoring unknown attribute '", e->string().bare_str(), "'", e); + if (e1) { + ++proto_exprs; + } + // do not fail. + // success = false; + continue; + } + + TRACE("smtparser", + tout << "skipping: " << e->string() << " " << + e->line() << " " << + e->pos() << ".\n";); + continue; + } + return success; + } + + bool is_id_token(proto_expr* expr) { + return + expr && + (expr->kind() == proto_expr::ID || + expr->kind() == proto_expr::STRING || + expr->kind() == proto_expr::ANNOTATION); + } + + smt_cmd_token get_command_token(cmd_context& ctx, proto_expr* expr) { + if (!expr) { + return SMT_CMD_ERROR; + } + if (!is_id_token(expr)) { + return SMT_CMD_ERROR; + } + return ctx.string2token(expr->string().bare_str()); + } + + bool process_command(cmd_context& cmd_ctx, proto_expr* p_expr) { + proto_expr* const* chs = p_expr->children(); + proto_expr* e0 = chs?chs[0]:0; + proto_expr* e1 = e0?chs[1]:0; + std::ostream& out = cmd_ctx.get_out(); + Z3_context ctx = cmd_ctx.get_context(); + + smt_cmd_token cmd_tok; + if (p_expr->kind() == proto_expr::ID) { + cmd_tok = get_command_token(cmd_ctx, p_expr); + } + else { + cmd_tok = get_command_token(cmd_ctx, e0); + } + + switch(cmd_tok) { + case SMT_CMD_SET_LOGIC: + if (!check_id(e1)) { + cmd_ctx.print_failure("logic identifier expected as argument to logic", p_expr); + break; + } + if (Z3_set_logic(ctx, e1->string().bare_str())) { + // m_smtlib_logic param is only used for pretty printing. + Z3_get_parameters(ctx).m_smtlib_logic = e1->string().bare_str(); + set_default_num_sort(e1->string()); + cmd_ctx.print_success(); + } + else { + cmd_ctx.print_failure("failed to set logic", p_expr); + } + break; + case SMT_CMD_DECLARE_SORTS: + if (!check_valid(cmd_ctx, p_expr, e1, "sort declaration expects an argument")) { + break; + } + if (e0 && e1 && chs[2]) { + cmd_ctx.print_failure("too many arguments passed to declaration", p_expr); + } + if (declare_sorts(e1)) { + cmd_ctx.print_success(); + } + else { + cmd_ctx.print_failure("could not parse sort declaration", p_expr); + } + break; + case SMT_CMD_DECLARE_FUNS: // func-decls + if (!check_valid(cmd_ctx, p_expr, e1, "function declaration expects an argument")) { + break; + } + if (e0 && e1 && chs[2]) { + cmd_ctx.print_failure("too many arguments passed to declaration", p_expr); + } + if (declare_funs(e1)) { + cmd_ctx.print_success(); + } + else { + cmd_ctx.print_failure("could not parse function declaration", p_expr); + } + break; + case SMT_CMD_DECLARE_PREDS: // pred-decls + if (!check_valid(cmd_ctx, p_expr, e1, "predicate declaration expects an argument")) { + break; + } + if (e0 && e1 && chs[2]) { + cmd_ctx.print_failure("too many arguments passed to declaration", p_expr); + } + if (declare_preds(e1)) { + cmd_ctx.print_success(); + } + else { + cmd_ctx.print_failure("could not parse predicate declaration", p_expr); + } + break; + case SMT_CMD_DECLARE_DATATYPES: + if (!check_valid(cmd_ctx, p_expr, e1, "data-type declaration expects an argument")) { + break; + } + if (e0 && e1 && chs[2]) { + cmd_ctx.print_failure("too many arguments passed to declaration", p_expr); + } + if (declare_datatypes(e1)) { + cmd_ctx.print_success(); + } + else { + cmd_ctx.print_failure("could not parse data-type declaration", p_expr); + } + break; + case SMT_CMD_DEFINE: { + expr_ref macro_expr(m_manager); + if (define_macro(cmd_ctx.get_table(), cmd_ctx.get_region(), p_expr, macro_expr)) { + cmd_ctx.add_trail(macro_expr); + cmd_ctx.print_success(); + } + break; + } + case SMT_CMD_DEFINE_SORTS: { + if (!check_valid(cmd_ctx, p_expr, e1, "sort definition expects an argument")) { + break; + } + if (e0 && e1 && chs[2]) { + cmd_ctx.print_failure("too many arguments passed to declaration", p_expr); + } + if (define_sorts(e1)) { + cmd_ctx.print_success(); + } + break; + } + case SMT_CMD_DECLARE_SORT: { // + if (!check_id(e1)) { + cmd_ctx.print_failure("identifier argument expected", p_expr); + break; + + } + unsigned num_args = cmd_ctx.parse_opt_numeral(chs[2], 0); + if (e0 && e1 && chs[2] && chs[3]) { + cmd_ctx.print_failure("too many arguments passed to declaration", p_expr); + } + m_benchmark.get_symtable()->insert(e1->string(), alloc(user_sort, m_manager, num_args, e1->string())); + cmd_ctx.print_success(); + + break; + } + case SMT_CMD_DEFINE_SORT: { // (*) + if (!check_id(e1)) { + cmd_ctx.print_failure("sort definition expects three arguments", p_expr); + break; + } + proto_expr* e2 = chs[2]; + if (!check_valid(cmd_ctx, p_expr, e2, "sort definition expects three arguments")) { + break; + } + proto_expr* e3 = chs[3]; + if (!check_valid(cmd_ctx, p_expr, e3, "sort definition expects three arguments")) { + break; + } + if (chs[4]) { + cmd_ctx.print_failure("too many arguments passed to declaration", p_expr); + } + if (define_sort(e1, e2, e3)) { + cmd_ctx.print_success(); + } + else { + cmd_ctx.print_failure("could not parse sort definition", p_expr); + } + break; + } + case SMT_CMD_DECLARE_FUN: { // (*) + if (!check_id(e1)) { + cmd_ctx.print_failure("function declaration expects three arguments", p_expr); + break; + } + proto_expr* e2 = chs[2]; + if (!check_valid(cmd_ctx, p_expr, e2, "function declaration expects three arguments")) { + break; + } + proto_expr* e3 = chs[3]; + if (!check_valid(cmd_ctx, p_expr, e3, "function declaration expects three arguments")) { + break; + } + if (chs[4]) { + cmd_ctx.print_failure("too many arguments passed to declaration", p_expr); + } + if (declare_fun(e1,e2,e3)) { + cmd_ctx.print_success(); + } + else { + cmd_ctx.print_failure("could not parse function declaration", p_expr); + } + break; + } + case SMT_CMD_DECLARE_CONST: { // + if (!check_id(e1)) { + cmd_ctx.print_failure("constant declaration expects two arguments", p_expr); + break; + } + proto_expr* e2 = chs[2]; + if (!check_valid(cmd_ctx, p_expr, e2, "constant declaration expects two arguments")) { + break; + } + if (chs[3]) { + cmd_ctx.print_failure("too many arguments passed to declaration", p_expr); + } + if (declare_fun(e1, 0, e2)) { + cmd_ctx.print_success(); + } + else { + cmd_ctx.print_failure("could not parse constant declaration", p_expr); + } + break; + } + case SMT_CMD_DEFINE_FUN: { // (*) + if (!check_id(e1)) { + cmd_ctx.print_failure("function definition expects four arguments", p_expr); + break; + } + proto_expr* e2 = chs[2]; + if (!check_valid(cmd_ctx, p_expr, e2, "function definition expects four arguments")) { + break; + } + proto_expr* e3 = chs[3]; + if (!check_valid(cmd_ctx, p_expr, e3, "function definition expects four arguments")) { + break; + } + proto_expr* e4 = chs[4]; + if (!check_valid(cmd_ctx, p_expr, e4, "function definition expects four arguments")) { + break; + } + if (chs[5]) { + cmd_ctx.print_failure("too many arguments passed to declaration", p_expr); + } + expr_ref macro_expr(m_manager); + if (define_fun(cmd_ctx.get_table(), cmd_ctx.get_region(), e1, e2, e3, e4, macro_expr)) { + cmd_ctx.add_trail(macro_expr); + cmd_ctx.print_success(); + } + else { + cmd_ctx.print_failure("could not parse function definition", p_expr); + } + break; + } + case SMT_CMD_GET_VALUE: { // (+) + if (!check_valid(cmd_ctx, p_expr, e1, "get-value expects a list arguments")) { + break; + } + proto_expr* const* children = e1->children(); + expr_ref_vector exprs(m_manager); + + while (children && children[0]) { + expr_ref e(m_manager); + if (!get_expression(cmd_ctx, cmd_ctx.get_table(), p_expr, children[0], e, "one expression expected to eval")) { + break; + } + exprs.push_back(e); + ++children; + } + cmd_ctx.eval(out, p_expr, exprs.size(), exprs.c_ptr()); + break; + } + case SMT_CMD_PUSH: { // numeral + unsigned num_scopes = cmd_ctx.parse_opt_numeral(e1, 1); + cmd_ctx.push(num_scopes); + cmd_ctx.print_success(); + if (e1 && chs[2]) { + cmd_ctx.print_failure("too many arguments passed to command", p_expr); + } + break; + } + case SMT_CMD_POP: { // numeral + unsigned num_scopes = cmd_ctx.parse_opt_numeral(e1, 1); + cmd_ctx.pop(num_scopes); + cmd_ctx.print_success(); + if (e1 && chs[2]) { + cmd_ctx.print_failure("too many arguments passed to command", p_expr); + } + break; + } + case SMT_CMD_ASSERT: { // expr+ + expr_ref_vector exprs(m_manager); + if (!chs) { + cmd_ctx.print_failure("expecting list of arguments", p_expr); + break; + } + if (!(*chs)) { + cmd_ctx.print_success(); + break; + } + ++chs; + if (!make_bool_expressions(cmd_ctx.get_table(), chs, exprs)) { + if (!cmd_ctx.has_error()) { + cmd_ctx.print_failure("could not parse expression", *chs); + } + return true; + } + for (unsigned i = 0; i < exprs.size(); ++i) { + cmd_ctx.add_asserted(exprs[i].get()); + } + cmd_ctx.print_success(); + break; + } + case SMT_CMD_CHECK_SAT: { // boolean-constants* + if (!chs || !(*chs)) { + cmd_ctx.check_sat(out); + } + else { + ++chs; + if (!chs || !(*chs)) { + cmd_ctx.check_sat(out); + } + else { + expr_ref_vector exprs(m_manager); + if (!make_bool_expressions(cmd_ctx.get_table(), chs, exprs)) { + if (!cmd_ctx.has_error()) { + cmd_ctx.print_failure("could not parse expression", *chs); + } + return true; + } + cmd_ctx.get_core(p_expr, out, exprs.size(), exprs.c_ptr(), false); + } + } + break; + } + case SMT_CMD_GET_CORE: { // boolean-constants+ + expr_ref_vector exprs(m_manager); + if (!chs) { + cmd_ctx.print_failure("expecting list of arguments", p_expr); + break; + } + if (!(*chs)) { + cmd_ctx.print_success(); + break; + } + ++chs; + if (!make_bool_expressions(cmd_ctx.get_table(), chs, exprs)) { + if (!cmd_ctx.has_error()) { + cmd_ctx.print_failure("could not parse expression", *chs); + } + return true; + } + cmd_ctx.get_core(p_expr, out, exprs.size(), exprs.c_ptr(), true); // just get the core + break; + } + case SMT_CMD_NEXT_SAT: { // + cmd_ctx.next_sat(out); + break; + } + case SMT_CMD_SIMPLIFY: { + expr_ref e(m_manager); + if (!get_expression(cmd_ctx, cmd_ctx.get_table(), p_expr, e1, e, "one expression expected to simplify")) { + break; + } + cmd_context::scoped_stopwatch stopw(cmd_ctx); + Z3_ast result = Z3_simplify(ctx, reinterpret_cast(e.get())); + out << Z3_ast_to_string(ctx, result) << "\n"; + break; + } + case SMT_CMD_GET_IMPLIED_EQUALITIES: { + expr_ref_vector exprs(m_manager); + expr_ref e(m_manager); + if (!chs || !(*chs)) { + cmd_ctx.print_failure("expecting list of arguments", p_expr); + break; + } + ++chs; + bool ok = true; + while (*chs) { + if (!get_expression(cmd_ctx, cmd_ctx.get_table(), p_expr, *chs, e, "list of expressions expected")) { + ok = false; + break; + } + exprs.push_back(e); + ++chs; + } + if (!ok) { + break; + } + cmd_context::scoped_stopwatch stopw(cmd_ctx); + cmd_ctx.get_implied_equalities(out, exprs.size(), exprs.c_ptr()); + break; + } + case SMT_CMD_EVAL: { + expr_ref e(m_manager); + if (!get_expression(cmd_ctx, cmd_ctx.get_table(), p_expr, e1, e, "one expression expected to eval")) { + break; + } + cmd_ctx.eval(out, p_expr, e); + break; + } + case SMT_CMD_GET_ASSERTIONS: { // string + scoped_stream strm(e1, out); + cmd_ctx.get_assertions(*strm); + break; + } + case SMT_CMD_GET_SAT_ASSERTIONS: { // string + scoped_stream strm(e1, out); + cmd_ctx.get_sat_assertions(out); + break; + } + case SMT_CMD_KEEP_SAT_ASSERTIONS: { // + Z3_ast assignment = Z3_get_context_assignment(ctx); + cmd_ctx.add_asserted(reinterpret_cast(assignment)); + cmd_ctx.print_success(); + break; + } + case SMT_CMD_GET_UNSAT_CORE: { // string + scoped_stream strm(e1, out); + cmd_ctx.get_unsat_core(out, p_expr); + break; + } + case SMT_CMD_GET_PROOF: { // string + scoped_stream strm(e1, out); + cmd_ctx.get_proof(*strm, p_expr); + break; + } + case SMT_CMD_SET_OPTION: { // string strings + if (!e1) { + cmd_ctx.set_option_help(out, ""); + break; + } + if (cmd_ctx.set_option(get_command_token(cmd_ctx,e1), chs+2)) { + cmd_ctx.print_success(); + } + else { + cmd_ctx.print_unsupported(); + } + break; + } + case SMT_CMD_SET_INFO: { + if (!e1) { + cmd_ctx.set_option_help(out, ""); + break; + } + if (cmd_ctx.set_info(e1, chs+2)) { + cmd_ctx.print_success(); + } + else { + cmd_ctx.print_unsupported(); + } + break; + } + case SMT_CMD_GET_INFO: { // string+ + if (!e1) { + cmd_ctx.get_info_help(out, ""); + break; + } + ++chs; + SASSERT(e1 == *chs); + out << "("; + while(*chs) { + if (!get_info(cmd_ctx, get_command_token(cmd_ctx,*chs))) { + out << ":" << chs[0]->string() << " \"unsupported\""; + } + ++chs; + if (*chs) { + out << "\n"; + } + } + out << ")\n"; + break; + } + case SMT_CMD_ECHO: { // string+ + if (!e1) { + cmd_ctx.get_info_help(out, ""); + break; + } + ++chs; + SASSERT(e1 == *chs); + while(*chs) { + out << chs[0]->string() << "\n"; + ++chs; + } + break; + } + case SMT_CMD_EXIT: // + return false; + case SMT_CMD_ERROR: // error token + cmd_ctx.print_failure("unrecognized command", p_expr); + break; + default: + if (get_info(cmd_ctx, cmd_tok)) { + out << "\n"; + break; + } + if (cmd_ctx.get_command_help(out, cmd_tok)) { + out << "\n"; + break; + } + cmd_ctx.print_failure("this is not a top-level command", p_expr); + break; + } + return true; + } + + void flatten_exprs(expr_ref_vector& exprs) { + for (unsigned i = 0; i < exprs.size(); ++i) { + expr* e = exprs[i].get(); + if (m_manager.is_and(e)) { + for (unsigned j = 1; j < to_app(e)->get_num_args(); ++j) { + exprs.push_back(to_app(e)->get_arg(j)); + } + exprs[i] = to_app(e)->get_arg(0); + --i; + continue; + } + if (m_manager.is_not(e) && + m_manager.is_or(to_app(e)->get_arg(0))) { + e = to_app(e)->get_arg(0); + app* a = to_app(e); + for (unsigned j = 1; j < a->get_num_args(); ++j) { + e = a->get_arg(j); + if (m_manager.is_not(e)) { + exprs.push_back(to_app(e)->get_arg(0)); + } + else { + exprs.push_back(m_manager.mk_not(e)); + } + } + e = a->get_arg(0); + if (m_manager.is_not(e)) { + exprs[i] = to_app(e)->get_arg(0); + } + else { + exprs[i] = m_manager.mk_not(e); + } + --i; + continue; + } + } + } + + + bool get_info(cmd_context& cmd_ctx, smt_cmd_token cmd_tok) { + return cmd_ctx.get_info(cmd_tok); + } + + bool get_expression(cmd_context& cmd_ctx, symbol_table& table, proto_expr* p_expr, proto_expr* e1, expr_ref& e, char const* msg) { + if (!check_valid(cmd_ctx, p_expr, e1, msg)) { + return false; + } + m_binding_level = 0; + if (!make_expression(table, e1, e)) { + return false; + } + return true; + } + + + bool check_valid(cmd_context& cmd_ctx, proto_expr* p_expr, proto_expr* e, char const* msg) { + if (e == 0) { + cmd_ctx.print_failure(msg, p_expr); + } + return (e != 0); + } + + bool check_id(proto_expr* e) { + return is_id_token(e); + } + + bool make_expression(proto_expr * e, expr_ref & result) { + m_binding_level = 0; + symbol_table local_scope; + return make_expression(local_scope, e, result); + } + + bool make_func_decl(proto_expr* e, func_decl_ref& result) { + func_decl* f; + if (m_benchmark.get_symtable()->find1(e->string(), f)) { + result = f; + return true; + } + else { + return false; + } + } + + bool make_bool_expression(symbol_table& local_scope, proto_expr * e, expr_ref & result) { + if (!make_expression(local_scope, e, result)) { + return false; + } + if (!m_manager.is_bool(result)) { + set_error("expecting Boolean expression", e); + return false; + } + return true; + } + + bool make_bool_expressions(symbol_table& local_scope, proto_expr * const* chs, expr_ref_vector & exprs) { + while (chs && *chs) { + expr_ref result(m_manager); + m_binding_level = 0; + if (!make_bool_expression(local_scope, *chs, result)) { + return false; + } + exprs.push_back(result); + ++chs; + } + return true; + } + + bool make_expression(symbol_table& local_scope, proto_expr * e, expr_ref & result) { + // + // Walk proto_expr by using the zipper. + // That is, maintain a stack of what's + // . left - already processed. + // . right - to be processed. + // . up - above the processed node. + // + + region region; + + expr_ref_vector * left = alloc(expr_ref_vector, m_manager); + proto_expr* const* right = 0; + ptr_vector up; + proto_expr* current = e; + bool success = false; + idbuilder* builder = 0; + + while (true) { + + if (!current && right && *right) { + // + // pull the current from right. + // + current = *right; + ++right; + } + + if (!current && up.empty()) { + // + // we are done. + // + if (left->size() == 0) { + // set_error(); + set_error("there are no expressions to return", e); + goto cleanup; + } + if (left->size() != 1) { + set_error("there are too many expressions to return", e); + goto cleanup; + } + result = left->back(); + success = true; + goto cleanup; + } + + if (!current && !up.empty()) { + // + // There is nothing more to process at this level. + // + // Apply the operator on the stack to the + // current 'left' vector. + // Adjust the stack by popping the left and right + // work-lists. + // + expr_ref term(m_manager); + parse_frame* above = up.back(); + // symbol sym = above->get_proto_expr()->string(); + + if (above->make_term()) { + if (!above->make_term()->apply(*left, term)) { + set_error("Could not create application", + above->get_proto_expr()); + success = false; + goto cleanup; + } + } + else if (!make_app(above->get_proto_expr(), *left, term)) { + success = false; + goto cleanup; + } + dealloc(left); + left = above->detach_left(); + left->push_back(term.get()); + right = above->right(); + m_binding_level = above->binding_level(); + up.pop_back(); + continue; + } + + while (current && + current->kind() == proto_expr::CONS && + current->children() && + current->children()[0] && + !current->children()[1]) { + current = current->children()[0]; + } + + switch(current->kind()) { + + case proto_expr::ANNOTATION: + // ignore + current = 0; + break; + + case proto_expr::ID: { + symbol const& id = current->string(); + expr_ref term(m_manager); + expr * const_term = 0; + bool ok = true; + + if (local_scope.find(id, builder)) { + expr_ref_vector tmp(m_manager); + if (!builder->apply(tmp, term)) { + set_error("identifier supplied with the wrong number of arguments ", id, current); + goto cleanup; + + } + } + else if (m_benchmark.get_const(id, const_term)) { + // found. + term = const_term; + } + else if (is_builtin_const(id, current, current->num_params(), current->params(), ok, term)) { + if (!ok) goto cleanup; + } + else if (is_bvconst(id, current->num_params(), current->params(), term)) { + // found + } + else { + set_error("could not locate id ", id, current); + goto cleanup; + } + + left->push_back(term.get()); + current = 0; + break; + } + + case proto_expr::STRING: + // + // Ignore strings. + // + current = 0; + break; + + case proto_expr::COMMENT: + // + // Ignore comments. + // + current = 0; + break; + + case proto_expr::INT: + + left->push_back(mk_number(current->number(), true)); + current = 0; + break; + + case proto_expr::FLOAT: + left->push_back(mk_number(current->number(), false)); + current = 0; + break; + + case proto_expr::CONS: + + if (!current->children() || + !current->children()[0]) { + set_error("cons does not have children", current); + current = 0; + goto cleanup; + } + + // + // expect the head to be a symbol + // which can be used to build a term of + // the subterms. + // + + symbol const& head_symbol = current->children()[0]->string(); + + if (head_symbol == m_underscore) { + + expr_ref term(m_manager); + + proto_expr * const* chs = current->children() + 1; + symbol const& id = chs[0]->string(); + sort_ref_vector sorts(m_manager); + vector params; + bool ok = true; + if (!parse_params(chs+1, params, sorts)) { + goto cleanup; + } + if (is_builtin_const(id, current, params.size(), params.c_ptr(), ok, term)) { + if (!ok) goto cleanup; + } + else if (is_bvconst(id, params.size(), params.c_ptr(), term)) { + // ok + } + else { + set_error("Could not parse _ term", current); + goto cleanup; + } + left->push_back(term.get()); + current = 0; + break; + } + + if ((head_symbol == m_let) || + (head_symbol == m_flet)) { + + if (!current->children()[1] || + !current->children()[2]) { + set_error("let does not have two arguments", current); + goto cleanup; + } + + proto_expr * let_binding = current->children()[1]; + proto_expr * const* let_body = current->children()+2; + + // + // Collect bound variables and definitions for the bound variables + // into vectors 'vars' and 'bound'. + // + svector vars; + ptr_vector bound_vec; + if (is_binary_let_binding(let_binding)) { + vars.push_back(let_binding->children()[0]->string()); + bound_vec.push_back(let_binding->children()[1]); + } + else { + proto_expr* const* children = let_binding->children(); + if (!children) { + set_error("let binding does not have two arguments", let_binding); + goto cleanup; + } + while (*children) { + proto_expr* ch = *children; + if (!is_binary_let_binding(ch)) { + set_error("let binding does not have two arguments", ch); + goto cleanup; + } + vars.push_back(ch->children()[0]->string()); + bound_vec.push_back(ch->children()[1]); + ++children; + } + } + bound_vec.push_back(0); + + proto_expr** bound = new (region) proto_expr*[bound_vec.size()]; + for (unsigned i = 0; i < bound_vec.size(); ++i) { + bound[i] = bound_vec[i]; + } + + // + // Let's justify the transformation that + // pushes push_let and pop_let on the stack. + // and how it processes the let declaration. + // + // walk up left ((let ((v1 x1) (v2 x2)) z)::right) + // + // = + // + // walk (up::(pop_let(),left,right)::(bind(v1,v2),[],[z])) [] [x1;x2] + // + // = (* assume x1 -> y1, x2 -> y2 *) + // + // walk (up::(pop_let(),left,right)::(bind(v1,v2),[],[z])) [y1;y2] [] + // + // = (* apply binding *) + // + // walk (up::(pop_let(),left,right)) [] [z] + // + // = (* assume z -> u *) + // + // walk up {left::u] right + // + // so if pop_let(v) [a,b] has the effect of removing v from the environment + // and projecting the second element "b", we obtain the effect of a let-binding. + // + + expr_ref_vector * pinned = alloc(expr_ref_vector, m_manager); + pop_let * popl = new (region) pop_let(local_scope, pinned); + up.push_back(new (region) parse_frame(let_binding, popl, left, right, m_binding_level)); + + + push_let_and * pushl = new (region) push_let_and(this, region, local_scope, pinned, vars.size(), vars.c_ptr()); + expr_ref_vector * tmp = alloc(expr_ref_vector, m_manager); + up.push_back(new (region) parse_frame(let_binding, pushl, tmp, let_body, m_binding_level)); + + + left = alloc(expr_ref_vector, m_manager); + right = bound; + current = 0; + break; + } + + if (head_symbol == m_lblneg || + head_symbol == m_lblpos) { + if (!current->children()[1] || + !current->children()[2]) { + set_error("labels require two arguments", current); + goto cleanup; + } + + bool is_pos = head_symbol == m_lblpos; + idbuilder* lbl = new (region) build_label(this, is_pos, current->children()[1]); + + up.push_back(new (region) parse_frame(current, lbl, left, right, m_binding_level)); + + // + // process the body. + // + left = alloc(expr_ref_vector, m_manager); + right = 0; + current = current->children()[2]; + break; + } + + if (head_symbol == m_bang) { + proto_expr* const* children = current->children(); + proto_expr* body = children[1]; + proto_expr* lblname = 0; + bool is_pos = false; + + children += 2; + + while (children[0] && + children[0]->kind() == proto_expr::ANNOTATION && + children[1]) { + symbol id = children[0]->string(); + + if ((id == m_lblneg) || + (id == m_lblpos)) { + is_pos = id == m_lblpos; + lblname = children[1]; + } + + children += 2; + } + + if (lblname) { + idbuilder* lbl = new (region) build_label(this, is_pos, lblname); + up.push_back(new (region) parse_frame(current, lbl, left, right, m_binding_level)); + left = alloc(expr_ref_vector, m_manager); + right = 0; + } + + // + // process the body. + // + current = body; + break; + } + + if ((head_symbol == m_forall) || + (head_symbol == m_exists)) { + + expr_ref_buffer patterns(m_manager); + expr_ref_buffer no_patterns(m_manager); + sort_ref_buffer sorts(m_manager); + svector vars; + int weight = 1; + + proto_expr* const* children = current->children(); + proto_expr* body = 0; + + ++children; + + if (!children[0] || !children[1]) { + set_error("quantifier should have at least two arguments", current); + goto cleanup; + } + + // + // restore 'left' and 'right' working set and m_binding_level. + // + up.push_back(new (region) parse_frame(current, new (region) identity(), left, right, m_binding_level)); + left = alloc(expr_ref_vector, m_manager); + + // + // declare the bound variables. + // + + local_scope.begin_scope(); + + while (children[0] && children[1] && + (children[1]->kind() != proto_expr::ANNOTATION)) { + + if (!parse_bound(local_scope, region, *children, vars, sorts)) { + goto cleanup; + } + ++children; + } + + body = children[0]; + + if (is_annotated_cons(body)) { + children = body->children()+1; + body = body->children()[1]; + } + + ++children; + + symbol qid = symbol(current->line()); + symbol skid = symbol(); + + read_patterns(vars.size(), local_scope, children, patterns, no_patterns, weight, qid, skid); + + // + // push a parse_frame to undo the scope of the quantifier. + // + + SASSERT(sorts.size() > 0); + + idbuilder* pop_q = new (region) pop_quantifier(this, (head_symbol == m_forall), weight, qid, skid, patterns, no_patterns, sorts, vars, local_scope, current); + + expr_ref_vector * empty_v = alloc(expr_ref_vector, m_manager); + up.push_back(new (region) parse_frame(current, pop_q, empty_v, 0, m_binding_level)); + + // + // process the body. + // + right = 0; + current = body; + break; + } + + if (is_underscore_op(region, current->children()[0], builder)) { + up.push_back(new (region) parse_frame(current, builder, left, right, m_binding_level)); + } + else if (local_scope.find(head_symbol, builder)) { + up.push_back(new (region) parse_frame(current, builder, left, right, m_binding_level)); + } + else { + up.push_back(new (region) parse_frame(current->children()[0], left, right, m_binding_level)); + } + left = alloc(expr_ref_vector, m_manager); + right = current->children() + 1; + current = 0; + break; + } + } + + cleanup: + + if (success && !is_well_sorted(m_manager, result)) { + set_error("expression is not well sorted", e); + success = false; + } + + dealloc(left); + while (!up.empty()) { + dealloc(up.back()->detach_left()); + up.pop_back(); + } + return success; + } + + bool read_patterns(unsigned num_bindings, symbol_table & local_scope, proto_expr * const * & children, + expr_ref_buffer & patterns, expr_ref_buffer & no_patterns, int& weight, symbol& qid, symbol& skid) { + proto_region region; + while (children[0] && + children[0]->kind() == proto_expr::ANNOTATION && + children[1]) { + + if (children[0]->string() == symbol("qid") || + children[0]->string() == symbol("named")) { + qid = children[1]->string(); + children += 2; + continue; + } + + if (children[0]->string() == symbol("skolemid")) { + skid = children[1]->string(); + children += 2; + continue; + } + + ptr_vector proto_exprs; + + if (children[1]->kind() == proto_expr::COMMENT) { + std::string s = children[1]->string().str(); + std::istringstream stream(s); + scanner scanner(stream, get_err(), false); + proto_expr_parser parser(region, scanner, get_err()); + + if (!parser.parse(proto_exprs)) { + set_error("could not parse expression", children[1]); + return false; + } + } else if (children[1]->kind() == proto_expr::CONS) { + for (proto_expr* const* pexpr = children[1]->children(); *pexpr; pexpr++) + proto_exprs.push_back(*pexpr); + } else { + proto_exprs.push_back(children[1]); + } + + expr_ref_buffer ts(m_manager); + for (unsigned i = 0; i < proto_exprs.size(); ++i) { + expr_ref t(m_manager); + if (!make_expression(local_scope, proto_exprs[i], t)) { + return false; + } + ts.push_back(t.get()); + } + + if (children[0]->string() == symbol("pat") || + children[0]->string() == symbol("pats") || + children[0]->string() == symbol("pattern")) { + for (unsigned i = 0; i < ts.size(); ++i) { + if (!is_app(ts[i])) { + set_error("invalid pattern", children[0]); + return false; + } + } + expr * p = m_manager.mk_pattern(ts.size(), (app*const*)(ts.c_ptr())); + if (!p || (!ignore_user_patterns() && !m_pattern_validator(num_bindings, p))) { + set_error("invalid pattern", children[0]); + return false; + } + patterns.push_back(p); + } + else if (children[0]->string() == symbol("ex_act") && ts.size() == 1) { + app * sk_hack = m_manager.mk_app(m_sk_hack, 1, ts.c_ptr()); + expr * p = m_manager.mk_pattern(1, &sk_hack); + if (!p || (!ignore_user_patterns() && !m_pattern_validator(num_bindings, p))) { + set_error("invalid pattern", children[0]); + return false; + } + patterns.push_back(p); + } + else if ((children[0]->string() == symbol("nopat") || + children[0]->string() == symbol("no-pattern")) + && ts.size() == 1) { + no_patterns.push_back(ts[0]); + } + else if (children[0]->string() == symbol("weight") && ts.size() == 1 && + proto_exprs[0]->kind() == proto_expr::INT && + proto_exprs[0]->number().is_unsigned()) { + weight = proto_exprs[0]->number().get_unsigned(); + } + else { + // TODO: this should be a warning, perferably once per unknown kind of annotation + set_error("could not understand annotation '", + children[0]->string().bare_str(), "'", children[0]); + } + + children += 2; + } + return true; + } + + void set_default_num_sort(symbol const& name) { + if (name == symbol("QF_RDL") || + name == symbol("QF_LRA") || + name == symbol("LRA") || + name == symbol("RDL") || + name == symbol("QF_NRA") || + name == symbol("QF_UFNRA") || + name == symbol("QF_UFLRA")) { + m_int_sort = m_real_sort; + } + } + + bool get_sort(theory* th, char const * s, sort_ref& sort) { + return make_sort(symbol(s), 0, 0, sort); + } + + + bool make_sort(symbol const & id, unsigned num_params, parameter const* params, sort_ref& s) { + builtin_op info; + if (m_builtin_sorts.find(id, info)) { + s = m_manager.mk_sort(info.m_family_id, info.m_kind, num_params, params); + return true; + } + + if (num_params == 2 && symbol("Array") == id) { + // Old HACK to accomodate bit-vector arrays. + + if (!params[0].is_int()) { + throw default_exception("Non-integer parameter to array"); + return false; + } + if (!params[1].is_int()) { + throw default_exception("Non-integer parameter to array"); + return false; + } + parameter bv_params0[1] = { parameter(params[0].get_int()) }; + parameter bv_params1[1] = { parameter(params[1].get_int()) }; + + sort * t1 = m_manager.mk_sort(m_bv_fid, BV_SORT, 1, bv_params0); + sort * t2 = m_manager.mk_sort(m_bv_fid, BV_SORT, 1, bv_params1); + parameter params[2] = { parameter(t1), parameter(t2) }; + s = m_manager.mk_sort(m_array_fid, ARRAY_SORT, 2, params); + return true; + } + + sort* srt = 0; + if (m_benchmark.get_sort(id, srt)) { + s = srt; + return true; + } + return false; + } + + bool make_sort(proto_expr * e, sort_ref& s) { + SASSERT(can_be_sort(e)); + symtable& env = *m_benchmark.get_symtable(); + sort_builder* mk_sort; + switch(e->kind()) { + case proto_expr::ID: { + if (make_sort(e->string(), e->num_params(), e->params(), s)) { + return true; + } + if (env.lookup(e->string(), mk_sort)) { + if (!mk_sort->apply(e->num_params(), e->params(), s)) { + set_error(mk_sort->error_message(), e); + return false; + } + return true; + } + set_error("could not find sort ", e); + return false; + } + case proto_expr::CONS: { + if (!can_be_sort(e)) { + set_error("expression cannot be a sort", e); + return false; + } + proto_expr *const* chs = e->children(); + if (is_underscore(e)) { + ++chs; + } + symbol name = (*chs)->string(); + if (!env.lookup(name, mk_sort)) { + set_error("could not find sort symbol '", name.str(), "'", e); + return false; + } + sort_ref_vector sorts(m_manager); + vector params; + if (!parse_params(chs+1, params, sorts)) { + return false; + } + + if (!mk_sort->apply(params.size(), params.c_ptr(), s)) { + set_error(mk_sort->error_message(), e); + return false; + } + return true; + } + default: + set_error("could not create sort ", e); + return false; + } + } + + bool parse_params(proto_expr* const* chs,vector& params, sort_ref_vector& sorts) { + while (*chs) { + if ((*chs)->kind() == proto_expr::INT) { + rational const& num = (*chs)->number(); + if (num.is_unsigned()) { + params.push_back(parameter(num.get_unsigned())); + } + else { + params.push_back(parameter(num)); + } + } + else { + sort_ref s1(m_manager); + if (!make_sort(*chs, s1)) { + return false; + } + sorts.push_back(s1); + params.push_back(parameter((ast*)s1.get())); + } + ++chs; + } + return true; + } + + bool parse_bound( + symbol_table& local_scope, + region& region, + proto_expr* bound, + svector& vars, + sort_ref_buffer& sorts + ) + { + if (is_cons_list(bound)) { + proto_expr *const* children = bound->children(); + while (*children) { + if (!parse_bound(local_scope, region, *children, vars, sorts)) { + return false; + } + ++children; + } + return true; + } + if (!can_be_sorted_var(bound)) { + set_error("bound variable should contain a list of pairs", bound); + return false; + } + proto_expr* var = bound->children()[0]; + proto_expr* sort_proto_expr = bound->children()[1]; + + sort_ref sort(m_manager); + if (!make_sort(sort_proto_expr, sort)) { + return false; + } + sorts.push_back(sort); + vars.push_back(var->string()); + + local_scope.insert( + var->string(), + new (region) bound_var(this, sort) + ); + + ++m_binding_level; + + return true; + } + + bool can_be_sort(proto_expr* e) { + if (e && e->kind() == proto_expr::ID) { + return true; + } + if (is_underscore(e)) { + return true; + } + + if (e && + e->kind() == proto_expr::CONS && + e->children() && + e->children()[0] && + e->children()[1]) { + proto_expr* const* ch = e->children(); + while(*ch) { + if (!can_be_sort(*ch)) { + return false; + } + ++ch; + } + return true; + } + return false; + } + + bool declare_sorts(proto_expr* e) { + proto_expr* const * children = e->children(); + + while (children && *children) { + proto_expr* ch = *children; + switch(ch->kind()) { + + case proto_expr::ID: + m_benchmark.declare_sort(ch->string()); + break; + + case proto_expr::CONS: + // + // The declaration of type constructors + // consists of an identifier together with + // a number indicating the arity of the + // constructor. + // + if (ch->children() && + ch->children()[0] && + ch->children()[0]->kind() == proto_expr::ID && + ch->children()[1] && + ch->children()[1]->kind() == proto_expr::INT) { + + // unsigned num = (unsigned) ch->children()[1]->number().get_uint64(); + m_benchmark.declare_sort(ch->children()[0]->string()); + } + break; + + case proto_expr::ANNOTATION: + break; + + default: + set_error("unexpected argument to sorts",ch); + return false; + } + ++children; + } + return true; + } + + bool define_sorts(proto_expr* e) { + proto_expr* const * children = e->children(); + + while (children && *children) { + if (!define_sort(*children)) { + return false; + } + ++children; + } + return true; + } + + bool define_sort(proto_expr* e) { + proto_expr* const * children = e->children(); + sort_ref_buffer domain(m_manager); + + // + // First element in list must be an identifier. + // there should be just two elements. + // + if (!children || + !children[0] || + !(children[0]->kind() == proto_expr::ID) || + !children[1] || + children[2]) { + set_error("unexpected arguments to function declaration", e); + return false; + } + symbol name = children[0]->string(); + sort_ref s(m_manager); + if (!can_be_sort(children[1]) || + !make_sort(children[1], s)) { + set_error("unexpected arguments to function declaration", e); + return false; + } + + m_benchmark.get_symtable()->insert(name, s); + + return true; + } + + bool declare_funs(proto_expr* e) { + proto_expr* const * children = e->children(); + + while (children && *children) { + if (!declare_fun(*children)) { + return false; + } + ++children; + } + return true; + } + class define_sort_cls : public sort_builder { + smtparser& m_parser; + proto_region m_region; + proto_expr* m_expr; + svector m_params; + symbol m_name; + std::string m_error_message; + + public: + define_sort_cls(smtparser& p, symbol const& name, proto_expr* e, unsigned num_params, symbol* params) : + m_parser(p), + m_name(name) { + for (unsigned i = 0; i < num_params; ++i) { + m_params.push_back(params[i]); + } + m_expr = proto_expr::copy(m_region, e); + } + + virtual bool apply(unsigned num_params, parameter const* params, sort_ref & result) { + smtlib::symtable * symtable = m_parser.m_benchmark.get_symtable(); + if (m_params.size() != num_params) { + std::ostringstream strm; + strm << "wrong number of arguments passed to " << m_name << " " + << m_params.size() << " expected, but " << num_params << " given"; + m_error_message = strm.str(); + return false; + } + for (unsigned i = 0; i < num_params; ++i) { + parameter p(params[i]); + if (!p.is_ast() || !is_sort(p.get_ast())) { + symtable->pop_sorts(i); + std::ostringstream strm; + strm << "argument " << i << " is not a sort"; + m_error_message = strm.str(); + return false; + } + symtable->push_sort(m_params[i], to_sort(p.get_ast())); + } + bool success = m_parser.make_sort(m_expr, result); + + symtable->pop_sorts(num_params); + return success; + } + + virtual char const* error_message() { + return m_error_message.c_str(); + } + + }; + + // (define-sort name (*) ) + bool define_sort(proto_expr* id, proto_expr* sorts, proto_expr* srt) { + symbol name = id->string(); + proto_expr* const * children = sorts->children(); + svector names; + + if (!children) { + set_error("Sort definition expects a list of sort symbols",id); + return false; + } + + while (children[0]) { + id = children[0]; + if(id->kind() != proto_expr::ID) { + set_error("unexpected argument, expected ID", id); + return false; + } + names.push_back(id->string()); + ++children; + } + + m_benchmark.get_symtable()->insert(name, alloc(define_sort_cls, *this, name, srt, names.size(), names.c_ptr())); + return true; + } + + bool declare_fun(proto_expr* id, proto_expr* sorts, proto_expr* srt) { + proto_expr* const * children = sorts?sorts->children():0; + sort_ref_buffer domain(m_manager); + symbol name = id->string(); + + if (sorts && !children) { + set_error("Function declaration expects a list of sorts", id); + return false; + } + // + // parse domain. + // + while (sorts && children[0]) { + sort_ref s(m_manager); + if (!make_sort(children[0], s)) { + return false; + } + domain.push_back(s); + ++children; + } + + sort_ref range(m_manager); + if (!make_sort(srt, range)) { + return false; + } + bool is_associative = false; + bool is_commutative = false; + bool is_injective = false; + m_benchmark.declare_func(name, domain, range, is_associative, is_commutative, is_injective); + return true; + } + + bool declare_fun(proto_expr* e) { + proto_expr* const * children = e->children(); + sort_ref_buffer domain(m_manager); + // + // Skip declaration of numbers. + // + if (children && + children[0] && + children[0]->kind() == proto_expr::INT) { + return true; + } + + // + // First element in list must be an identifier. + // + if (!children || + !children[0] || + !(children[0]->kind() == proto_expr::ID)) { + set_error("unexpected arguments to function declaration", e); + return false; + } + + symbol name = children[0]->string(); + + ++children; + + + if (!can_be_sort(children[0])) { + set_error("unexpected arguments to function declaration", e); + return false; + } + + // + // parse domain. + // + while (can_be_sort(children[1])) { + sort_ref s(m_manager); + if (!make_sort(children[0], s)) { + return false; + } + domain.push_back(s); + ++children; + } + + // + // parse range. + // + SASSERT(can_be_sort(children[0])); + + sort_ref range(m_manager); + if (!make_sort(children[0], range)) { + return false; + } + ++children; + + // + // parse attributes. + // + bool is_associative = false; + bool is_commutative = false; + bool is_injective = false; + + while(children[0] && children[0]->kind() == proto_expr::ANNOTATION) { + + if (m_associative == children[0]->string()) { + is_associative = true; + } + else if (m_commutative == children[0]->string()) { + is_commutative = true; + } + else if (m_injective == children[0]->string()) { + is_injective = true; + } + ++children; + } + + m_benchmark.declare_func(name, domain, range, is_associative, is_commutative, is_injective); + + return true; + } + + bool declare_preds(proto_expr* e) { + proto_expr* const * children = e->children(); + + while (children && *children) { + if (!declare_pred(*children)) { + return false; + } + ++children; + } + return true; + } + + + bool declare_pred(proto_expr* e) { + proto_expr* const * children = e->children(); + if (!children || !children[0] || !(children[0]->kind() == proto_expr::ID)) { + set_error("unexpected arguments to predicate declaration", e); + return false; + } + symbol const & name = children[0]->string(); + sort_ref_buffer domain(m_manager); + sort * bool_sort = m_manager.mk_bool_sort(); + + ++children; + + while (can_be_sort(children[0])) { + sort_ref s(m_manager); + if (!make_sort(children[0], s)) { + return false; + } + domain.push_back(s); + ++children; + } + + m_benchmark.declare_func(name, domain, bool_sort, false, false, false); + + return true; + } + + bool declare_datatypes(proto_expr * e) { + TRACE("datatypes", tout << "new datatype declarion section\n";); + proto_expr * const* children = e->children(); + + buffer dt_names; + + while (children && *children) { + proto_expr * type_decl = *children; + proto_expr * const* td_children = type_decl->children(); + if (!td_children || !td_children[0] || !(td_children[0]->kind() == proto_expr::ID)) { + set_error("invalid datatype declaration", type_decl); + return false; + } + symbol name = td_children[0]->string(); + sort * dummy; + if (m_benchmark.get_symtable()->find(name, dummy)) { + set_error("invalid datatype declaration, name was already used", type_decl); + return false; + } + dt_names.push_back(name); + TRACE("datatypes", tout << name << "\n";); + ++children; + } + + children = e->children(); + + ptr_buffer datatypes; + + while (children && *children) { + datatype_decl * d = declare_datatype(*children, dt_names); + if (!d) { + return false; + } + datatypes.push_back(d); + ++children; + } + + sort_ref_vector new_types(m_manager); + + bool result = m_dt_plugin->mk_datatypes(datatypes.size(), datatypes.c_ptr(), new_types); + del_datatype_decls(datatypes.size(), datatypes.c_ptr()); + + if (!result) { + set_error("invalid datatype declaration", e); + } + else { + unsigned num_types = new_types.size(); + for (unsigned i = 0; i < num_types; i++) { + sort * d = new_types.get(i); + TRACE("datatype", tout << "new datatype\n" << mk_pp(d, m_manager) << "\n"; + tout << "num. elements: " << d->get_num_elements() << "\n"; + tout << "recursive: " << m_dt_util.is_recursive(d) << "\n"; + tout << "non_rec constructor: " << m_dt_util.get_non_rec_constructor(d)->get_name() << "\n"; + ); + m_benchmark.insert(d); + ptr_vector const * constructors = m_dt_util.get_datatype_constructors(d); + unsigned num_constructors = constructors->size(); + for (unsigned j = 0; j < num_constructors; j++) { + func_decl * c = constructors->get(j); + m_benchmark.insert(c); + func_decl * r = m_dt_util.get_constructor_recognizer(c); + TRACE("datatype", + tout << "new constructor\n" << mk_pp(c, m_manager) << "\n"; + tout << "new recogniser\n" << mk_pp(r, m_manager) << "\n";); + m_benchmark.insert(r); + ptr_vector const * accessors = m_dt_util.get_constructor_accessors(c); + unsigned num_accessors = accessors->size(); + for (unsigned k = 0; k < num_accessors; k++) { + func_decl * a = accessors->get(k); + TRACE("datatype", tout << "new accessor\n" << mk_pp(a, m_manager) << "\n";); + m_benchmark.insert(a); + } + } + } + } + + return result; + } + + datatype_decl * declare_datatype(proto_expr * e, buffer const & dt_names) { + proto_expr* const * children = e->children(); + symbol const& name = children[0]->string(); + + ptr_buffer constructors; + ++children; // consume id + + while (children && *children) { + constructor_decl * c = declare_constructor(*children, dt_names); + if (!c) { + del_constructor_decls(constructors.size(), constructors.c_ptr()); + return false; + } + constructors.push_back(c); + ++children; + } + + if (constructors.size() == 0) { + set_error("datatype must have at least one constructor", e); + return false; + } + + return mk_datatype_decl(name, constructors.size(), constructors.c_ptr()); + } + + constructor_decl * declare_constructor(proto_expr * e, buffer const & dt_names) { + if (e->kind() == proto_expr::ID) { + symbol const & name = e->string(); + string_buffer<> tmp; + tmp << "is_" << name; + symbol r_name(tmp.c_str()); + return mk_constructor_decl(name, r_name, 0, 0); + } + + proto_expr* const * children = e->children(); + if (!children || !children[0] || !(children[0]->kind() == proto_expr::ID)) { + set_error("invalid constructor declaration", e); + return 0; + } + + symbol const & name = children[0]->string(); + string_buffer<> tmp; + tmp << "is_" << name; + symbol r_name(tmp.c_str()); + + ptr_buffer accessors; + ++children; // skip id + + while (children && *children) { + accessor_decl * d = declare_accessor(*children, dt_names); + if (!d) { + del_accessor_decls(accessors.size(), accessors.c_ptr()); + return 0; + } + accessors.push_back(d); + ++children; + } + + return mk_constructor_decl(name, r_name, accessors.size(), accessors.c_ptr()); + } + + accessor_decl * declare_accessor(proto_expr * e, buffer const & dt_names) { + proto_expr* const * children = e->children(); + if (!children || + !children[0] || !(children[0]->kind() == proto_expr::ID) || + !children[1] || !(children[1]->kind() == proto_expr::ID) || + children[2]) { + set_error("invalid accessor declaration", e); + return 0; + } + + symbol const& name = children[0]->string(); + symbol const& tname = children[1]->string(); + unsigned tid = 0; + for (; tid < dt_names.size(); tid++) { + if (tname == dt_names[tid]) { + break; + } + } + + type_ref ty; + if (tid < dt_names.size()) { + ty = type_ref(tid); + } + else { + sort_ref s(m_manager); + if (!make_sort(tname, children[1]->num_params(), children[1]->params(), s)) { + set_error("unknown sort", children[1]); + return 0; + } + m_pinned_sorts.push_back(s); + ty = type_ref(s.get()); + } + + return mk_accessor_decl(name, ty); + } + + // + // (define-macro (name (x A) (y B)) body[x,y]) + // + + bool can_be_sorted_var(proto_expr* e) { + return + e && + (e->kind() == proto_expr::CONS) && + e->children() && + e->children()[0] && + (e->children()[0]->kind() == proto_expr::ID) && + can_be_sort(e->children()[1]); + } + + bool is_cons_list(proto_expr* e) { + return + e && + (e->kind() == proto_expr::CONS) && + e->children() && + e->children()[0] && + e->children()[0]->kind() == proto_expr::CONS; + } + + bool is_prefixed(proto_expr* e, symbol const& s) { + return + e && + (e->kind() == proto_expr::CONS) && + e->children() && + e->children()[0] && + e->children()[1] && + e->children()[0]->string() == s; + + } + + bool is_underscore(proto_expr* e) { + return + is_prefixed(e, m_underscore) && + e->children()[1]->kind() == proto_expr::ID; + } + + bool is_annotated_cons(proto_expr* e) { + return is_prefixed(e, m_bang); + } + + bool is_builtin_const(symbol const& id, proto_expr* current, unsigned num_params, parameter * params, bool& ok, expr_ref& term) { + builtin_op info; + ok = true; + if (!m_builtin_ops.find(id, info)) { + return false; + } + fix_parameters(num_params, params); + func_decl* d = m_manager.mk_func_decl(info.m_family_id, info.m_kind, num_params, params, 0, (expr * const *)0); + if (!d) { + set_error("could not create a term from constant ", id, current); + ok = false; + } + else if (d->get_arity() != 0) { + set_error("identifier expects arguments ", id, current); + ok = false; + } + else { + term = m_manager.mk_const(d); + } + return true; + } + + bool is_underscore_op(region& r, proto_expr* e, idbuilder*& builder) { + if (!is_underscore(e)) { + return false; + } + builtin_op info; + proto_expr *const* chs = e->children()+1; + symbol const& id = (*chs)->string(); + sort_ref_vector sorts(m_manager); + vector params; + + if (!m_builtin_ops.find(id, info)) { + return false; + } + if (!parse_params(chs+1, params, sorts)) { + return false; + } + + builder = new (r) builtin_builder(this, info.m_family_id, info.m_kind, params); + return true; + } + + + + bool define_fun(symbol_table& table, region& r, proto_expr* name, proto_expr* args, + proto_expr* srt, proto_expr* body, expr_ref & macro_expr) { + + symbol macro_name = name->string(); + + proto_expr* const* chs = args->children(); + // parse marco arguments. + table.begin_scope(); + ptr_vector sorts; + sort_ref sort1(m_manager), sort2(m_manager); + while (chs && *chs) { + proto_expr* e1 = *chs; + if (!can_be_sorted_var(e1)) { + set_error("Macro definition takes a list of pairs", e1); + goto error_cleanup; + } + proto_expr* var = e1->children()[0]; + proto_expr* srt = e1->children()[1]; + sort_ref sort(m_manager); + if (!make_sort(srt, sort)) { + goto error_cleanup; + } + sorts.push_back(sort); + m_pinned_sorts.push_back(sort); + table.insert(var->string(), new (r) bound_var(this, sort)); + ++chs; + ++m_binding_level; + } + + if (!make_expression(table, body, macro_expr)) { + goto error_cleanup; + } + + if (!make_sort(srt, sort1)) { + goto error_cleanup; + } + sort2 = m_manager.get_sort(macro_expr); + if (sort1.get() != sort2.get()) { + std::ostringstream strm; + strm << "The expected sort for macro was " << mk_pp(sort1, m_manager) + << " but the macro body has sort " << mk_pp(sort2, m_manager); + set_error(strm.str().c_str(), body); + goto error_cleanup; + } + table.end_scope(); + m_binding_level = 0; + table.insert(macro_name, new (r) macro_builder(r, body, macro_expr, this, sorts.size(), sorts.c_ptr())); + return true; + + error_cleanup: + table.end_scope(); + m_binding_level = 0; + return false; + } + + bool define_macro(symbol_table& table, region& r, proto_expr* macro_defn, expr_ref & macro_expr) { + SASSERT(macro_defn); + proto_expr* const* exprs = macro_defn->children(); + proto_expr* e0 = exprs?exprs[0]:0; + proto_expr* e1 = e0?exprs[1]:0; + proto_expr* e2 = e1?exprs[2]:0; + + m_binding_level = 0; + + if (!e1) { + set_error("macro definition requires two arguments, none given", macro_defn); + return false; + } + if (!e2) { + set_error("macro definition requires two arguments, only one given", macro_defn); + return false; + } + + // parse macro name + symbol macro_name; + proto_expr* const* chs = e1->children(); + if (e1->kind() == proto_expr::ID) { + macro_name = e1->string(); + chs = 0; + } + else if (chs && chs[0] && chs[0]->kind() == proto_expr::ID) { + macro_name = chs[0]->string(); + chs = chs + 1; + } + else { + set_error("first argument to macro definition should be a name or a name applied to arguments", e1); + return false; + } + + // parse marco arguments. + table.begin_scope(); + ptr_vector sorts; + while (chs && *chs) { + e1 = *chs; + if (!can_be_sorted_var(e1)) { + set_error("Macro definition takes a list of pairs", e1); + goto error_cleanup; + } + proto_expr* var = e1->children()[0]; + proto_expr* srt = e1->children()[1]; + sort_ref sort(m_manager); + if (!make_sort(srt, sort)) { + goto error_cleanup; + } + sorts.push_back(sort); + m_pinned_sorts.push_back(sort); + table.insert(var->string(), new (r) bound_var(this, sort)); + ++chs; + ++m_binding_level; + } + + if (!make_expression(table, e2, macro_expr)) { + goto error_cleanup; + } + table.end_scope(); + m_binding_level = 0; + table.insert(macro_name, new (r) macro_builder(r, e2, macro_expr, this, sorts.size(), sorts.c_ptr())); + return true; + + error_cleanup: + table.end_scope(); + m_binding_level = 0; + return false; + } + + void fix_parameters(unsigned num_params, parameter* params) { + for (unsigned i = 0; i < num_params; ++i) { + func_decl* d = 0; + sort* s = 0; + builtin_op info; + if (params[i].is_symbol() && m_benchmark.get_symtable()->find1(params[i].get_symbol(), d)) { + params[i] = parameter(d); + } + else if (params[i].is_symbol() && m_benchmark.get_symtable()->find(params[i].get_symbol(), s)) { + params[i] = parameter(s); + } + else if (params[i].is_symbol() && m_builtin_sorts.find(params[i].get_symbol(), info)) { + params[i] = parameter(m_manager.mk_sort(info.m_family_id, info.m_kind, 0, 0)); + } + } + } + + bool test_unify(proto_expr * e, bool expected) { + proto_expr* const * children = e->children(); + if (!children || !children[0] || !children[1]) { + set_error("invalid unification problem", e); + } + + expr_ref f1(m_manager), f2(m_manager); + if (!make_expression(children[0], f1) || !make_expression(children[1], f2)) + return false; + unsigned num_vars1 = 0; + unsigned num_vars2 = 0; + if (is_forall(f1)) { + num_vars1 = to_quantifier(f1)->get_num_decls(); + f1 = to_quantifier(f1)->get_expr(); + } + if (is_forall(f2)) { + num_vars2 = to_quantifier(f2)->get_num_decls(); + f2 = to_quantifier(f2)->get_expr(); + } + substitution s(m_manager); + s.reserve(2, std::max(num_vars1, num_vars2)); + unifier u(m_manager); + if (u(f1, f2, s)) { + std::cout << "unification: succeeded\n"; + if (!expected) { + get_err() << "WRONG ANSWER\n"; + UNREACHABLE(); + } + unsigned deltas[2] = { 0, num_vars1 }; + s.display(std::cout, 2, deltas); + } + else { + std::cout << "unification: failed\n"; + if (expected) { + get_err() << "WRONG ANSWER\n"; + UNREACHABLE(); + } + } + return true; + } + + void dump_substitution(expr_ref_buffer & s) { + unsigned sz = s.size(); + for (unsigned i = 0; i < sz; i++) { + expr * t = s[i]; + if (t) + std::cout << "VAR " << i << " -->\n" << mk_pp(t, m_manager) << "\n"; + } + } + +#if !defined(SMTCOMP) && !defined(_EXTERNAL_RELEASE) + bool test_order(proto_expr * e, order & ord, symbol expected) { + proto_expr* const * children = e->children(); + if (!children || !children[0] || !children[1]) { + set_error("invalid unification problem", e); + } + + expr_ref f1(m_manager), f2(m_manager); + if (!make_expression(children[0], f1) || !make_expression(children[1], f2)) + return false; + unsigned num_vars1 = 0; + unsigned num_vars2 = 0; + if (is_forall(f1)) { + num_vars1 = to_quantifier(f1)->get_num_decls(); + f1 = to_quantifier(f1)->get_expr(); + } + if (is_forall(f2)) { + num_vars2 = to_quantifier(f2)->get_num_decls(); + f2 = to_quantifier(f2)->get_expr(); + } + ord.reserve(1, std::max(num_vars1, num_vars2)); + order::result r = ord.compare(f1.get(), f2.get()); + if ((r == order::UNCOMPARABLE && expected != symbol("kbo_un") && expected != symbol("lpo_un")) || + (r == order::LESSER && expected != symbol("kbo_lt") && expected != symbol("lpo_lt")) || + (r == order::GREATER && expected != symbol("kbo_gt") && expected != symbol("lpo_gt")) || + (r == order::EQUAL && expected != symbol("kbo_eq") && expected != symbol("lpo_eq")) || + r == order::UNKNOWN) { + get_err() << "WRONG ANSWER\n"; + UNREACHABLE(); + } + else + std::cout << "order: succeeded\n"; + return true; + } + + bool test_kbo(proto_expr * e, symbol expected) { + precedence * p = alloc(arbitrary_precedence); + kbo k(m_manager, p); + return test_order(e, k, expected); + } + + bool test_lpo(proto_expr * e, symbol expected) { + precedence * ps[2] = { alloc(arity_precedence), alloc(arbitrary_precedence) }; + precedence * p = alloc(lex_precedence, 2, ps); + lpo l(m_manager, p); + return test_order(e, l, expected); + } + + bool test_st(proto_expr * e, symbol op) { + expr_ref f(m_manager); + if (!make_expression(e, f)) + return false; + + if (is_forall(f)) + f = to_quantifier(f)->get_expr(); + + if (!is_app(f)) + set_error("invalid st operation", e); + + if (op == symbol("st_insert")) + m_st.insert(to_app(f)); + else + m_st.erase(to_app(f)); + + return true; + } + + + + + class simple_st_visitor : public st_visitor { + unsigned m_delta; + public: + simple_st_visitor(substitution & s, unsigned d):st_visitor(s), m_delta(d) {} + virtual bool operator()(expr * e) { + std::cout << "found:\n" << mk_pp(e, m_subst.get_manager()) << "\n"; + unsigned deltas[2] = { 0, m_delta }; + std::cout << "substitution:\n"; + // m_subst.display(std::cout); std::cout << "\n"; + m_subst.display(std::cout, 2, deltas); + std::cout << "\n"; + return true; + } + }; + + bool test_st_visit(proto_expr * e, symbol op) { + expr_ref f(m_manager); + if (!make_expression(e, f)) + return false; + + unsigned num_vars = 0; + if (is_forall(f)) { + num_vars = to_quantifier(f)->get_num_decls(); + f = to_quantifier(f)->get_expr(); + } + if (!is_app(f)) + set_error("invalid st operation", e); + substitution s(m_manager); + s.reserve(3, std::max(num_vars, m_st.get_approx_num_regs())); + + simple_st_visitor v(s, num_vars); + + std::cout << "searching for " << op << ":\n" << mk_pp(f, m_manager) << "\n\n"; + if (op == symbol("st_unify")) + m_st.unify(to_app(f), v); + else if (op == symbol("st_inst")) + m_st.inst(to_app(f), v); + else + m_st.gen(to_app(f), v); + std::cout << "done.\n"; + return true; + } +#endif + + bool declare_axioms(proto_expr * e) { + proto_expr* const * children = e->children(); + while (children && *children) { + if (!declare_axiom(*children)) { + return false; + } + ++children; + } + return true; + } + + bool declare_axiom(proto_expr * e) { + expr_ref t(m_manager); + if (!make_expression(e, t) || + !push_assumption(t.get())) { + return false; + } + return true; + } + + bool make_app(proto_expr * proto_expr, expr_ref_vector const & args, expr_ref & result) { + symbol const& name = proto_expr->string(); + ptr_vector sorts; + func_decl * d = 0; + smtlib::symtable * symtable = m_benchmark.get_symtable(); + + for (unsigned i = 0; i < args.size(); ++i) { + sorts.push_back(m_manager.get_sort(args.get(i))); + } + + if (symtable->find_overload(name, sorts, d)) { + result = m_manager.mk_app(d, args.size(), args.c_ptr()); + return true; + } + + builtin_op info; + if (m_builtin_ops.find(name, info)) { + unsigned num_params = proto_expr->num_params(); + parameter * params = proto_expr->params(); + fix_parameters(num_params, params); + d = m_manager.mk_func_decl(info.m_family_id, info.m_kind, num_params, params, args.size(), args.c_ptr()); + if (d) { + result = m_manager.mk_app(d, args.size(), args.c_ptr()); + return true; + } + } + + rational arg2_value; + bool arg2_is_int; + + if (name == symbol("store") && + args.size() == 3 && + m_anum_util.is_numeral(args.get(2), arg2_value, arg2_is_int) && + arg2_is_int) { + expr_ref_vector new_args(m_manager); + new_args.push_back(args.get(0)); + new_args.push_back(args.get(1)); + new_args.push_back(m_anum_util.mk_numeral(arg2_value, false)); + sorts.reset(); + for (unsigned i = 0; i < args.size(); ++i) { + sorts.push_back(m_manager.get_sort(new_args.get(i))); + } + if (symtable->find_overload(name, sorts, d)) { + result = m_manager.mk_app(d, new_args.size(), new_args.c_ptr()); + return true; + } + } + + error_prefix(proto_expr); + get_err() << "could not find overload for '" << name << "' "; + for (unsigned i = 0; i < sorts.size(); ++i) { + get_err() << "Argument: " + << mk_pp(args.get(i), m_manager) + << " has type " + << mk_pp(sorts[i], m_manager) + << ".\n"; + } + return false; + } + + class nullary : public idbuilder { + expr* m_expr; + smtparser* m_parser; + unsigned m_decl_level_save; + public: + nullary(expr* e, smtparser* p) : m_expr(e), m_parser(p), m_decl_level_save(p->m_binding_level) {} + + virtual bool apply(expr_ref_vector const& args, expr_ref & result) { + unsigned decl_level = m_parser->m_binding_level; + SASSERT(decl_level >= m_decl_level_save); + shift_vars shifty(m_parser->m_manager); + shifty(m_expr, decl_level - m_decl_level_save, result); + return (args.size() == 0); + } + }; + + class macro_builder : public idbuilder { + proto_expr* m_p_expr; + expr* m_expr; + smtparser* m_parser; + unsigned m_num_sorts; + sort** m_sorts; + public: + macro_builder(region& r, proto_expr* p_expr, expr* e, smtparser* p, unsigned num_sorts, sort* const* sorts) : + m_p_expr(p_expr), m_expr(e), m_parser(p), m_num_sorts(num_sorts) { + m_sorts = new (r) sort*[num_sorts]; + for (unsigned i = 0; i < num_sorts; ++i) { + m_sorts[i] = sorts[i]; + } + } + + virtual bool apply(expr_ref_vector const& args, expr_ref& result) { + ast_manager& m = m_parser->m_manager; + if (args.size() != m_num_sorts) { + m_parser->set_error("wrong number of arguments passed to macro", m_p_expr); + return false; + } + for (unsigned i = 0; i < m_num_sorts; ++i) { + if (m_sorts[i] != m.get_sort(args[i])) { + std::ostringstream strm; + strm << "sort miss-match for argument of macro. Expecting sort: "; + strm << mk_pp(m_sorts[i], m) << " instead argument "; + strm << mk_pp(args[i], m) << " with sort "; + strm << mk_pp(m.get_sort(args[i]), m); + m_parser->set_error(strm.str().c_str(), m_p_expr); + return false; + } + } + if (m_num_sorts == 0) { + result = m_expr; + } + else { + var_subst subst(m); + subst(m_expr, args.size(), args.c_ptr(), result); + } + return true; + } + }; + + class identity : public idbuilder { + public: + identity() {} + + virtual bool apply(expr_ref_vector const & args, expr_ref & result) { + if (args.size() == 1) { + result = args.back(); + return true; + } + else { + return false; + } + } + }; + + class parse_frame { + public: + + parse_frame(proto_expr * e, idbuilder * make, expr_ref_vector * left, proto_expr * const* right, unsigned binding_level): + m_proto_expr(e), + m_make_term(make), + m_left(left), + m_right(right), + m_binding_level(binding_level) { + } + + parse_frame(proto_expr * e, expr_ref_vector * left, proto_expr * const* right, unsigned binding_level): + m_proto_expr(e), + m_make_term(0), + m_left(left), + m_right(right), + m_binding_level(binding_level) { + } + + expr_ref_vector * detach_left() { + expr_ref_vector * result = m_left; + SASSERT(m_left); + m_left = 0; + return result; + } + + unsigned binding_level() const { return m_binding_level; } + + proto_expr* const * right() const { return m_right; } + + idbuilder* make_term() { return m_make_term; } + + proto_expr* get_proto_expr() const { return m_proto_expr; } + + ~parse_frame() { dealloc(m_left); } + + private: + + proto_expr* m_proto_expr; + idbuilder* m_make_term; + expr_ref_vector * m_left; + proto_expr* const * m_right; + unsigned m_binding_level; + + parse_frame & operator=(parse_frame const & other); + + parse_frame(parse_frame const & other); + + }; + + class build_label : public idbuilder { + bool m_pos; + symbol m_sym; + smtparser * m_smt; + public: + build_label(smtparser * smt, bool is_pos, proto_expr * sym): m_pos(is_pos), m_smt(smt) { + switch(sym->kind()) { + case proto_expr::ID: + case proto_expr::STRING: + m_sym = sym->string(); + break; + case proto_expr::INT: + m_sym = symbol(sym->number().to_string().c_str()); + break; + default: + UNREACHABLE(); + break; + } + } + + virtual bool apply(expr_ref_vector const & args, expr_ref & result) { + if (args.size() >= 1) { + result = m_smt->m_manager.mk_label(m_pos, m_sym, args.get(0)); + return true; + } + else { + return false; + } + } + }; + + class pop_let : public idbuilder { + public: + pop_let(symbol_table & local_scope, expr_ref_vector* pinned = 0): + m_local_scope(local_scope), + m_pinned(pinned) { + } + + virtual ~pop_let() {} + + virtual bool apply(expr_ref_vector const & args, expr_ref & result) { + dealloc(m_pinned); + if (args.size() == 2) { + m_local_scope.end_scope(); + result = args.get(1); + return true; + } + else { + return false; + } + } + private: + symbol_table & m_local_scope; + expr_ref_vector* m_pinned; + }; + + class push_let : public idbuilder { + smtparser* m_parser; + region & m_region; + symbol_table & m_local_scope; + symbol m_let_var; + + public: + push_let(smtparser* p, region & region, symbol_table & local_scope, symbol const & let_var): + m_parser(p), + m_region(region), + m_local_scope(local_scope), + m_let_var(let_var) { + } + + virtual bool apply(expr_ref_vector const & args, expr_ref & result) { + // + // . push a scope, + // . create a nullary function using the variable/term association. + // . return the (first) argument. + // + // + if (args.size() == 1) { + m_local_scope.begin_scope(); + m_local_scope.insert(m_let_var, new (m_region) nullary(args.back(), m_parser)); + result = args.back(); + return true; + } + else { + return false; + } + } + }; + + // push multiple let bound variables. + class push_let_and : public idbuilder { + smtparser* m_parser; + region & m_region; + symbol_table & m_local_scope; + unsigned m_num_vars; + symbol* m_vars; + expr_ref_vector* m_pinned; + + public: + push_let_and(smtparser* p, region & region, symbol_table & local_scope, expr_ref_vector* pinned, unsigned num_vars, symbol const* vars): + m_parser(p), + m_region(region), + m_local_scope(local_scope), + m_num_vars(num_vars), + m_vars(new (region) symbol[num_vars]), + m_pinned(pinned) { + for (unsigned i = 0; i < num_vars; ++i) { + m_vars[i] = vars[i]; + } + } + + virtual bool apply(expr_ref_vector const & args, expr_ref & result) { + if (args.size() != m_num_vars) { + return false; + } + + // + // . push a scope, + // . create a nullary function using the variable/term association. + // . return the last argument (arbitrary). + // + + m_local_scope.begin_scope(); + for (unsigned i = 0; i < m_num_vars; ++i) { + m_local_scope.insert(m_vars[i], new (m_region) nullary(args[i], m_parser)); + m_pinned->push_back(args[i]); + } + result = args.back(); + return true; + } + }; + + class bound_var : public idbuilder { + public: + bound_var(smtparser * smt, sort * sort): + m_smt(smt), + m_decl_level(smt->m_binding_level), + m_sort(sort) { + } + + virtual bool apply(expr_ref_vector const & args, expr_ref & result) { + SASSERT(m_smt->m_binding_level > m_decl_level); + unsigned idx = m_smt->m_binding_level - m_decl_level - 1; + result = m_smt->m_manager.mk_var(idx, m_sort); + return args.empty(); + } + + private: + smtparser * m_smt; + unsigned m_decl_level; + sort * m_sort; + }; + + class pop_quantifier : public idbuilder { + public: + pop_quantifier(smtparser * smt, bool is_forall, int weight, symbol const& qid, symbol const& skid, expr_ref_buffer & patterns, expr_ref_buffer & no_patterns, sort_ref_buffer & sorts, + svector& vars, symbol_table & local_scope, proto_expr* p_expr): + m_smt(smt), + m_is_forall(is_forall), + m_weight(weight), + m_qid(qid), + m_skid(skid), + m_patterns(m_smt->m_manager), + m_no_patterns(m_smt->m_manager), + m_sorts(m_smt->m_manager), + m_local_scope(local_scope), + m_p_expr(p_expr) { + SASSERT(sorts.size() == vars.size()); + + m_vars.append(vars); + m_sorts.append(sorts); + m_patterns.append(patterns); + m_no_patterns.append(no_patterns); + } + + virtual bool apply(expr_ref_vector const & args, expr_ref & result) { + if (args.size() != 1) { + return false; + } + + m_local_scope.end_scope(); + + expr * body = args.back(); + + if (m_smt->ignore_user_patterns()) { + TRACE("pat_bug", tout << "ignoring user patterns...: " << m_patterns.size() << "\n";); + result = m_smt->m_manager.mk_quantifier(m_is_forall, + m_sorts.size(), // num_decls + m_sorts.c_ptr(), // decl_sorts + m_vars.begin(), // decl_names + body, + m_weight, + m_qid, + m_skid, + 0, + 0, + 0, + 0); + } + else if (!m_patterns.empty()) { + if (!m_no_patterns.empty()) { + m_smt->set_error("patterns were provided, ignoring :nopat attribute.", ((proto_expr*)0)); + } + result = m_smt->m_manager.mk_quantifier(m_is_forall, + m_sorts.size(), // num_decls + m_sorts.c_ptr(), // decl_sorts + m_vars.begin(), // decl_names + body, + m_weight, + m_qid, + m_skid, + m_patterns.size(), + m_patterns.c_ptr(), + 0, + 0); + } + else { + result = m_smt->m_manager.mk_quantifier(m_is_forall, + m_sorts.size(), // num_decls + m_sorts.c_ptr(), // decl_sorts + m_vars.begin(), // decl_names + body, + m_weight, + m_qid, + m_skid, + 0, + 0, + m_no_patterns.size(), + m_no_patterns.c_ptr()); + } + + // + // reclaim memory resources on application. + // + + m_vars.finalize(); + m_sorts.finalize(); + m_patterns.finalize(); + m_no_patterns.finalize(); + return true; + } + + private: + smtparser* m_smt; + bool m_is_forall; + int m_weight; + symbol m_qid; + symbol m_skid; + expr_ref_buffer m_patterns; + expr_ref_buffer m_no_patterns; + sort_ref_buffer m_sorts; + svector m_vars; + symbol_table& m_local_scope; + proto_expr* m_p_expr; + }; + + class builtin_builder : public idbuilder { + smtparser* m_smt; + family_id m_fid; + decl_kind m_kind; + vector m_params; + + public: + builtin_builder(smtparser* smt, family_id fid, decl_kind k,vector const& p): + m_smt(smt), + m_fid(fid), + m_kind(k), + m_params(p) + { + } + + virtual bool apply(expr_ref_vector const& args, expr_ref& result) { + ast_manager& m = m_smt->m_manager; + func_decl* d = m.mk_func_decl(m_fid, m_kind, m_params.size(), m_params.c_ptr(), args.size(), args.c_ptr()); + if (d) { + result = m.mk_app(d, args.size(), args.c_ptr()); + } + m_params.finalize(); + return d != 0; + } + }; + + bool push_status(smtlib::benchmark::status status) { + m_benchmark.set_status( status); + return true; + } + + expr * mk_number(rational const & r, bool is_int){ + if (m_int_sort == m_real_sort) // integer constants should be mapped to real + is_int = false; + return m_anum_util.mk_numeral(r, is_int); + } + + void push_benchmark(symbol const & name) { + m_benchmark.set_name(name); + } + + bool push_assumption(expr * t) { + m_benchmark.add_axiom(t); + return true; + } + + bool push_formula(expr * t) { + m_benchmark.add_formula(t); + return true; + } + + bool is_binary_let_binding(proto_expr* let_binding) { + return + let_binding && + let_binding->children() && + let_binding->children()[0] && + (let_binding->children()[0]->kind() == proto_expr::ID) && + let_binding->children()[1] && + !let_binding->children()[2]; + } + + bool is_bvconst(symbol const & fname, unsigned num_params, parameter const* params, expr_ref & term) { + rational n; + char const * str = fname.bare_str(); + unsigned sz = 0; + + if (strncmp(str, "bvbin", 5) == 0) { + str += 5; + n = rational(0); + while (*str == '1' || *str == '0') { + n *= rational(2); + n += rational(*str - '0'); + ++sz; + ++str; + } + if (sz == 0) { + return false; + } + } + else if (strncmp(str, "bvhex", 5) == 0) { + n = rational(0); + str += 5; + while (('0' <= *str && *str <= '9') || + ('a' <= *str && *str <= 'f') || + ('A' <= *str && *str <= 'F')) { + n *= rational(16); + if ('0' <= *str && *str <= '9') { + n += rational(*str - '0'); + } + else if ('a' <= *str && *str <= 'f') { + n += rational(10); + n += rational(*str - 'a'); + } + else { + SASSERT('A' <= *str && *str <= 'F'); + n += rational(10); + n += rational(*str - 'A'); + } + sz += 4; + ++str; + } + if (sz == 0) { + return false; + } + } + else if (strncmp(str, "bv", 2) == 0 && '0' <= *(str + 2) && *(str + 2) <= '9') { + n = rational(0); + str += 2; + while ('0' <= *str && *str <= '9') { + n *= rational(10); + n += rational(*str - '0'); + ++str; + } + if (num_params == 1) { + sz = params[0].get_int(); + } + else { + sz = 32; + } + } + else { + return false; + } + + term = m_bvnum_util.mk_numeral(n, sz); + + return true; + } +}; + + +parser * parser::create(ast_manager& ast_manager, bool ignore_user_patterns) { + return alloc(smtparser, ast_manager, ignore_user_patterns); +} diff --git a/lib/smtparser.h b/lib/smtparser.h new file mode 100644 index 000000000..6750471a8 --- /dev/null +++ b/lib/smtparser.h @@ -0,0 +1,51 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + smtparser.h + +Abstract: + + SMT parsing utilities + +Author: + + Nikolaj Bjorner (nbjorner) 2006-09-25 + +Revision History: + +--*/ +#ifndef _SMT_PARSER_H_ +#define _SMT_PARSER_H_ + +#include "ast.h" +#include "vector.h" +#include "smtlib.h" +#include "z3.h" +#include + +namespace smtlib { + class parser { + public: + static parser * create(ast_manager & ast_manager, bool ignore_user_patterns = false); + + virtual ~parser() {} + + virtual void add_builtin_op(char const *, family_id fid, decl_kind kind) = 0; + virtual void add_builtin_type(char const *, family_id fid, decl_kind kind) = 0; + + virtual void initialize_smtlib() = 0; + + virtual void set_error_stream(std::ostream& strm) = 0; + + virtual bool parse_file(char const * path) = 0; + virtual bool parse_string(char const * string) = 0; + + virtual bool parse_commands(Z3_context ctx, std::istream& is, std::ostream& os) = 0; + + virtual benchmark * get_benchmark() = 0; + }; +}; + +#endif diff --git a/lib/solve_eqs_tactic.cpp b/lib/solve_eqs_tactic.cpp new file mode 100644 index 000000000..aa8fb03c2 --- /dev/null +++ b/lib/solve_eqs_tactic.cpp @@ -0,0 +1,791 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + solve_eqs_tactic.cpp + +Abstract: + + Tactic for solving equations and performing gaussian elimination. + +Author: + + Leonardo de Moura (leonardo) 2011-12-29. + +Revision History: + +--*/ +#include"tactical.h" +#include"expr_replacer.h" +#include"extension_model_converter.h" +#include"occurs.h" +#include"cooperate.h" +#include"goal_shared_occs.h" +#include"ast_smt2_pp.h" + +class solve_eqs_tactic : public tactic { + struct imp { + typedef extension_model_converter gmc; + + ast_manager & m_manager; + expr_replacer * m_r; + bool m_r_owner; + arith_util m_a_util; + obj_map m_num_occs; + unsigned m_num_steps; + unsigned m_num_eliminated_vars; + bool m_theory_solver; + bool m_ite_solver; + unsigned m_max_occs; + scoped_ptr m_subst; + scoped_ptr m_norm_subst; + expr_sparse_mark m_candidate_vars; + expr_sparse_mark m_candidate_set; + ptr_vector m_candidates; + ptr_vector m_vars; + ptr_vector m_ordered_vars; + bool m_produce_proofs; + bool m_produce_unsat_cores; + bool m_produce_models; + volatile bool m_cancel; + + imp(ast_manager & m, params_ref const & p, expr_replacer * r, bool owner): + m_manager(m), + m_r(r), + m_r_owner(r == 0 || owner), + m_a_util(m), + m_num_steps(0), + m_num_eliminated_vars(0), + m_cancel(false) { + updt_params(p); + if (m_r == 0) + m_r = mk_default_expr_replacer(m); + } + + ~imp() { + if (m_r_owner) + dealloc(m_r); + } + + ast_manager & m() const { return m_manager; } + + void updt_params(params_ref const & p) { + m_ite_solver = p.get_bool(":ite-solver", true); + m_theory_solver = p.get_bool(":theory-solver", true); + m_max_occs = p.get_uint(":solve-eqs-max-occs", UINT_MAX); + } + + void set_cancel(bool f) { + m_cancel = f; + m_r->set_cancel(f); + } + + void checkpoint() { + if (m_cancel) + throw tactic_exception(TACTIC_CANCELED_MSG); + cooperate("solve-eqs"); + } + + // Check if the number of occurrences of t is below the specified threshold :solve-eqs-max-occs + bool check_occs(expr * t) const { + if (m_max_occs == UINT_MAX) + return true; + unsigned num = 0; + m_num_occs.find(t, num); + TRACE("solve_eqs_check_occs", tout << mk_ismt2_pp(t, m_manager) << " num_occs: " << num << " max: " << m_max_occs << "\n";); + return num <= m_max_occs; + } + + // Use: (= x def) and (= def x) + bool trivial_solve(expr * lhs, expr * rhs, app_ref & var, expr_ref & def, proof_ref & pr) { + if (is_uninterp_const(lhs) && !m_candidate_vars.is_marked(lhs) && !occurs(lhs, rhs) && check_occs(lhs)) { + var = to_app(lhs); + def = rhs; + pr = 0; + return true; + } + else if (is_uninterp_const(rhs) && !m_candidate_vars.is_marked(rhs) && !occurs(rhs, lhs) && check_occs(rhs)) { + var = to_app(rhs); + def = lhs; + if (m_produce_proofs) + pr = m().mk_commutativity(m().mk_eq(lhs, rhs)); + return true; + } + return false; + } + + // (ite c (= x t1) (= x t2)) --> (= x (ite c t1 t2)) + bool solve_ite_core(app * ite, expr * lhs1, expr * rhs1, expr * lhs2, expr * rhs2, app_ref & var, expr_ref & def, proof_ref & pr) { + if (lhs1 != lhs2) + return false; + if (!is_uninterp_const(lhs1) || m_candidate_vars.is_marked(lhs1)) + return false; + if (occurs(lhs1, ite->get_arg(0)) || occurs(lhs1, rhs1) || occurs(lhs1, rhs2)) + return false; + if (!check_occs(lhs1)) + return false; + var = to_app(lhs1); + def = m().mk_ite(ite->get_arg(0), rhs1, rhs2); + + if (m_produce_proofs) + pr = m().mk_rewrite(ite, m().mk_eq(var, def)); + return true; + } + + // (ite c (= x t1) (= x t2)) --> (= x (ite c t1 t2)) + bool solve_ite(app * ite, app_ref & var, expr_ref & def, proof_ref & pr) { + expr * t = ite->get_arg(1); + expr * e = ite->get_arg(2); + + if (!m().is_eq(t) || !m().is_eq(e)) + return false; + + expr * lhs1 = to_app(t)->get_arg(0); + expr * rhs1 = to_app(t)->get_arg(1); + expr * lhs2 = to_app(e)->get_arg(0); + expr * rhs2 = to_app(e)->get_arg(1); + + return + solve_ite_core(ite, lhs1, rhs1, lhs2, rhs2, var, def, pr) || + solve_ite_core(ite, rhs1, lhs1, lhs2, rhs2, var, def, pr) || + solve_ite_core(ite, lhs1, rhs1, rhs2, lhs2, var, def, pr) || + solve_ite_core(ite, rhs1, lhs1, rhs2, lhs2, var, def, pr); + } + + bool is_pos_literal(expr * n) { + return is_app(n) && to_app(n)->get_num_args() == 0 && to_app(n)->get_family_id() == null_family_id; + } + + bool is_neg_literal(expr * n) { + if (m_manager.is_not(n)) + return is_pos_literal(to_app(n)->get_arg(0)); + return false; + } + +#if 0 + bool not_bool_eq(expr * f, app_ref & var, expr_ref & def, proof_ref & pr) { + if (!m().is_not(f)) + return false; + expr * eq = to_app(f)->get_arg(0); + if (!m().is_eq(f)) + return false; + + } +#endif + + /** + \brief Given t of the form (f s_0 ... s_n), + return true if x occurs in some s_j for j != i + */ + bool occurs_except(expr * x, app * t, unsigned i) { + unsigned num = t->get_num_args(); + for (unsigned j = 0; j < num; j++) { + if (i != j && occurs(x, t->get_arg(j))) + return true; + } + return false; + } + + bool solve_arith_core(app * lhs, expr * rhs, expr * eq, app_ref & var, expr_ref & def, proof_ref & pr) { + SASSERT(m_a_util.is_add(lhs)); + bool is_int = m_a_util.is_int(lhs); + expr * a; + expr * v; + rational a_val; + unsigned num = lhs->get_num_args(); + unsigned i; + for (i = 0; i < num; i++) { + expr * arg = lhs->get_arg(i); + if (is_uninterp_const(arg) && !m_candidate_vars.is_marked(arg) && check_occs(arg) && !occurs(arg, rhs) && !occurs_except(arg, lhs, i)) { + a_val = rational(1); + v = arg; + break; + } + else if (m_a_util.is_mul(arg, a, v) && + is_uninterp_const(v) && !m_candidate_vars.is_marked(v) && + m_a_util.is_numeral(a, a_val) && + !a_val.is_zero() && + (!is_int || a_val.is_minus_one()) && + check_occs(v) && + !occurs(v, rhs) && + !occurs_except(v, lhs, i)) { + break; + } + } + if (i == num) + return false; + var = to_app(v); + expr_ref inv_a(m()); + if (!a_val.is_one()) { + inv_a = m_a_util.mk_numeral(rational(1)/a_val, is_int); + rhs = m_a_util.mk_mul(inv_a, rhs); + } + + ptr_buffer other_args; + for (unsigned j = 0; j < num; j++) { + if (i != j) { + if (inv_a) + other_args.push_back(m_a_util.mk_mul(inv_a, lhs->get_arg(j))); + else + other_args.push_back(lhs->get_arg(j)); + } + } + switch (other_args.size()) { + case 0: + def = rhs; + break; + case 1: + def = m_a_util.mk_sub(rhs, other_args[0]); + break; + default: + def = m_a_util.mk_sub(rhs, m_a_util.mk_add(other_args.size(), other_args.c_ptr())); + break; + } + if (m_produce_proofs) + pr = m().mk_rewrite(eq, m().mk_eq(var, def)); + return true; + } + + bool solve_arith(expr * lhs, expr * rhs, expr * eq, app_ref & var, expr_ref & def, proof_ref & pr) { + return + (m_a_util.is_add(lhs) && solve_arith_core(to_app(lhs), rhs, eq, var, def, pr)) || + (m_a_util.is_add(rhs) && solve_arith_core(to_app(rhs), lhs, eq, var, def, pr)); + } + + bool solve(expr * f, app_ref & var, expr_ref & def, proof_ref & pr) { + if (m().is_eq(f)) { + if (trivial_solve(to_app(f)->get_arg(0), to_app(f)->get_arg(1), var, def, pr)) + return true; + if (m_theory_solver) { + expr * lhs = to_app(f)->get_arg(0); + expr * rhs = to_app(f)->get_arg(1); + if (solve_arith(lhs, rhs, f, var, def, pr)) + return true; + } + return false; + } + + if (m().is_iff(f)) + return trivial_solve(to_app(f)->get_arg(0), to_app(f)->get_arg(1), var, def, pr); + +#if 0 + if (not_bool_eq(f, var, def, pr)) + return true; +#endif + + if (m_ite_solver && m().is_ite(f)) + return solve_ite(to_app(f), var, def, pr); + + if (is_pos_literal(f)) { + if (m_candidate_vars.is_marked(f)) + return false; + var = to_app(f); + def = m().mk_true(); + if (m_produce_proofs) { + // [rewrite]: (iff (iff l true) l) + // [symmetry T1]: (iff l (iff l true)) + pr = m().mk_rewrite(m().mk_eq(var, def), var); + pr = m().mk_symmetry(pr); + } + TRACE("solve_eqs_bug2", tout << "eliminating: " << mk_ismt2_pp(f, m()) << "\n";); + return true; + } + + if (is_neg_literal(f)) { + var = to_app(to_app(f)->get_arg(0)); + if (m_candidate_vars.is_marked(var)) + return false; + def = m().mk_false(); + if (m_produce_proofs) { + // [rewrite]: (iff (iff l false) ~l) + // [symmetry T1]: (iff ~l (iff l false)) + pr = m().mk_rewrite(m().mk_eq(var, def), f); + pr = m().mk_symmetry(pr); + } + return true; + } + + return false; + } + + /** + \brief Start collecting candidates + */ + void collect(goal const & g) { + m_subst->reset(); + m_norm_subst->reset(); + m_r->set_substitution(0); + m_candidate_vars.reset(); + m_candidate_set.reset(); + m_candidates.reset(); + m_vars.reset(); + + app_ref var(m()); + expr_ref def(m()); + proof_ref pr(m()); + unsigned size = g.size(); + for (unsigned idx = 0; idx < size; idx++) { + checkpoint(); + expr * f = g.form(idx); + if (solve(f, var, def, pr)) { + m_vars.push_back(var); + m_candidates.push_back(f); + m_candidate_set.mark(f); + m_candidate_vars.mark(var); + if (m_produce_proofs) { + if (pr == 0) + pr = g.pr(idx); + else + pr = m().mk_modus_ponens(g.pr(idx), pr); + } + m_subst->insert(var, def, pr, g.dep(idx)); + } + m_num_steps++; + } + + TRACE("solve_eqs", + tout << "candidate vars:\n"; + ptr_vector::iterator it = m_vars.begin(); + ptr_vector::iterator end = m_vars.end(); + for (; it != end; ++it) { + tout << mk_ismt2_pp(*it, m()) << " "; + } + tout << "\n";); + } + + void sort_vars() { + SASSERT(m_candidates.size() == m_vars.size()); + TRACE("solve_eqs_bug", tout << "sorting vars...\n";); + m_ordered_vars.reset(); + + + // The variables (and its definitions) in m_subst must remain alive until the end of this procedure. + // Reason: they are scheduled for unmarking in visiting/done. + // They should remain alive while they are on the stack. + // To make sure this is the case, whenever a variable (and its definition) is removed from m_subst, + // I add them to the saved vector. + + expr_ref_vector saved(m()); + + expr_fast_mark1 visiting; + expr_fast_mark2 done; + + typedef std::pair frame; + svector todo; + ptr_vector::const_iterator it = m_vars.begin(); + ptr_vector::const_iterator end = m_vars.end(); + unsigned num; + for (; it != end; ++it) { + checkpoint(); + app * v = *it; + if (!m_candidate_vars.is_marked(v)) + continue; + todo.push_back(frame(v, 0)); + while (!todo.empty()) { + start: + frame & fr = todo.back(); + expr * t = fr.first; + m_num_steps++; + TRACE("solve_eqs_bug", tout << "processing:\n" << mk_ismt2_pp(t, m()) << "\n";); + if (t->get_ref_count() > 1 && done.is_marked(t)) { + todo.pop_back(); + continue; + } + switch (t->get_kind()) { + case AST_VAR: + todo.pop_back(); + break; + case AST_QUANTIFIER: + num = to_quantifier(t)->get_num_children(); + while (fr.second < num) { + expr * c = to_quantifier(t)->get_child(fr.second); + fr.second++; + if (c->get_ref_count() > 1 && done.is_marked(c)) + continue; + todo.push_back(frame(c, 0)); + goto start; + } + if (t->get_ref_count() > 1) + done.mark(t); + todo.pop_back(); + break; + case AST_APP: + num = to_app(t)->get_num_args(); + if (num == 0) { + if (fr.second == 0) { + if (m_candidate_vars.is_marked(t)) { + if (visiting.is_marked(t)) { + // cycle detected: remove t + visiting.reset_mark(t); + m_candidate_vars.mark(t, false); + SASSERT(!m_candidate_vars.is_marked(t)); + + // Must save t and its definition. + // See comment in the beginning of the function + expr * def = 0; + proof * pr; + expr_dependency * dep; + m_subst->find(to_app(t), def, pr, dep); + SASSERT(def != 0); + saved.push_back(t); + saved.push_back(def); + // + + m_subst->erase(t); + } + else { + visiting.mark(t); + fr.second = 1; + expr * def = 0; + proof * pr; + expr_dependency * dep; + m_subst->find(to_app(t), def, pr, dep); + SASSERT(def != 0); + todo.push_back(frame(def, 0)); + goto start; + } + } + } + else { + SASSERT(fr.second == 1); + if (m_candidate_vars.is_marked(t)) { + visiting.reset_mark(t); + m_ordered_vars.push_back(to_app(t)); + } + else { + // var was removed from the list of candidate vars to elim cycle + // do nothing + } + } + } + else { + while (fr.second < num) { + expr * arg = to_app(t)->get_arg(fr.second); + fr.second++; + if (arg->get_ref_count() > 1 && done.is_marked(arg)) + continue; + todo.push_back(frame(arg, 0)); + goto start; + } + } + if (t->get_ref_count() > 1) + done.mark(t); + todo.pop_back(); + break; + default: + UNREACHABLE(); + todo.pop_back(); + break; + } + } + } + + // cleanup + it = m_vars.begin(); + for (unsigned idx = 0; it != end; ++it, ++idx) { + if (!m_candidate_vars.is_marked(*it)) { + m_candidate_set.mark(m_candidates[idx], false); + } + } + + TRACE("solve_eqs", + tout << "ordered vars:\n"; + ptr_vector::iterator it = m_ordered_vars.begin(); + ptr_vector::iterator end = m_ordered_vars.end(); + for (; it != end; ++it) { + SASSERT(m_candidate_vars.is_marked(*it)); + tout << mk_ismt2_pp(*it, m()) << " "; + } + tout << "\n";); + m_candidate_vars.reset(); + } + + void normalize() { + m_norm_subst->reset(); + m_r->set_substitution(m_norm_subst.get()); + + expr_ref new_def(m()); + proof_ref new_pr(m()); + expr_dependency_ref new_dep(m()); + unsigned size = m_ordered_vars.size(); + for (unsigned idx = 0; idx < size; idx++) { + checkpoint(); + expr * v = m_ordered_vars[idx]; + expr * def = 0; + proof * pr = 0; + expr_dependency * dep = 0; + m_subst->find(v, def, pr, dep); + SASSERT(def != 0); + m_r->operator()(def, new_def, new_pr, new_dep); + m_num_steps += m_r->get_num_steps() + 1; + if (m_produce_proofs) + new_pr = m().mk_transitivity(pr, new_pr); + if (m_produce_unsat_cores) + new_dep = m().mk_join(dep, new_dep); + m_norm_subst->insert(v, new_def, new_pr, new_dep); + // we updated the substituting, but we don't need to reset m_r + // because all cached values there do not depend on v. + } + m_subst->reset(); + TRACE("solve_eqs", + tout << "after normalizing variables\n"; + for (unsigned i = 0; i < m_ordered_vars.size(); i++) { + expr * v = m_ordered_vars[i]; + expr * def = 0; + proof * pr = 0; + expr_dependency * dep = 0; + m_norm_subst->find(v, def, pr, dep); + tout << mk_ismt2_pp(v, m()) << "\n----->\n" << mk_ismt2_pp(def, m()) << "\n\n"; + }); +#if 0 + DEBUG_CODE({ + for (unsigned i = 0; i < m_ordered_vars.size(); i++) { + expr * v = m_ordered_vars[i]; + expr * def = 0; + proof * pr = 0; + expr_dependency * dep = 0; + m_norm_subst->find(v, def, pr, dep); + SASSERT(def != 0); + CASSERT("solve_eqs_bug", !occurs(v, def)); + } + }); +#endif + } + + void substitute(goal & g) { + // force the cache of m_r to be reset. + m_r->set_substitution(m_norm_subst.get()); + + expr_ref new_f(m()); + proof_ref new_pr(m()); + expr_dependency_ref new_dep(m()); + unsigned size = g.size(); + for (unsigned idx = 0; idx < size; idx++) { + checkpoint(); + expr * f = g.form(idx); + TRACE("gaussian_leak", tout << "processing:\n" << mk_ismt2_pp(f, m()) << "\n";); + if (m_candidate_set.is_marked(f)) { + // f may be deleted after the following update. + // so, we must remove remove the mark before doing the update + m_candidate_set.mark(f, false); + SASSERT(!m_candidate_set.is_marked(f)); + g.update(idx, m().mk_true(), m().mk_true_proof(), 0); + m_num_steps ++; + continue; + } + + m_r->operator()(f, new_f, new_pr, new_dep); + TRACE("solve_eqs_subst", tout << mk_ismt2_pp(f, m()) << "\n--->\n" << mk_ismt2_pp(new_f, m()) << "\n";); + m_num_steps += m_r->get_num_steps() + 1; + if (m_produce_proofs) + new_pr = m().mk_modus_ponens(g.pr(idx), new_pr); + if (m_produce_unsat_cores) + new_dep = m().mk_join(g.dep(idx), new_dep); + + g.update(idx, new_f, new_pr, new_dep); + if (g.inconsistent()) + return; + } + g.elim_true(); + TRACE("solve_eqs", + tout << "after applying substitution\n"; + g.display(tout);); +#if 0 + DEBUG_CODE({ + for (unsigned i = 0; i < m_ordered_vars.size(); i++) { + expr * v = m_ordered_vars[i]; + for (unsigned j = 0; j < g.size(); j++) { + CASSERT("solve_eqs_bug", !occurs(v, g.form(j))); + } + }}); +#endif + } + + void save_elim_vars(model_converter_ref & mc) { + IF_VERBOSE(100, if (!m_ordered_vars.empty()) verbose_stream() << "num. eliminated vars: " << m_ordered_vars.size() << "\n";); + m_num_eliminated_vars += m_ordered_vars.size(); + if (m_produce_models) { + if (mc.get() == 0) + mc = alloc(gmc, m()); + ptr_vector::iterator it = m_ordered_vars.begin(); + ptr_vector::iterator end = m_ordered_vars.end(); + for (; it != end; ++it) { + app * v = *it; + expr * def = 0; + proof * pr; + expr_dependency * dep; + m_norm_subst->find(v, def, pr, dep); + SASSERT(def != 0); + static_cast(mc.get())->insert(v->get_decl(), def); + } + } + } + + void collect_num_occs(expr * t, expr_fast_mark1 & visited) { + ptr_buffer stack; + +#define VISIT(ARG) { \ + if (is_uninterp_const(ARG)) { \ + obj_map::obj_map_entry * entry = m_num_occs.insert_if_not_there2(ARG, 0); \ + entry->get_data().m_value++; \ + } \ + if (!visited.is_marked(ARG)) { \ + visited.mark(ARG, true); \ + stack.push_back(ARG); \ + } \ + } + + VISIT(t); + + while (!stack.empty()) { + expr * t = stack.back(); + stack.pop_back(); + if (!is_app(t)) + continue; + unsigned j = to_app(t)->get_num_args(); + while (j > 0) { + --j; + expr * arg = to_app(t)->get_arg(j); + VISIT(arg); + } + } + } + + void collect_num_occs(goal const & g) { + if (m_max_occs == UINT_MAX) + return; // no need to compute num occs + m_num_occs.reset(); + expr_fast_mark1 visited; + unsigned sz = g.size(); + for (unsigned i = 0; i < sz; i++) + collect_num_occs(g.form(i), visited); + } + + unsigned get_num_steps() const { + return m_num_steps; + } + + unsigned get_num_eliminated_vars() const { + return m_num_eliminated_vars; + } + + void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + mc = 0; pc = 0; core = 0; + tactic_report report("solve_eqs", *g); + m_produce_models = g->models_enabled(); + m_produce_proofs = g->proofs_enabled(); + m_produce_unsat_cores = g->unsat_core_enabled(); + + if (!g->inconsistent()) { + m_subst = alloc(expr_substitution, m(), m_produce_unsat_cores, m_produce_proofs); + m_norm_subst = alloc(expr_substitution, m(), m_produce_unsat_cores, m_produce_proofs); + while (true) { + collect_num_occs(*g); + collect(*g); + if (m_subst->empty()) + break; + sort_vars(); + if (m_ordered_vars.empty()) + break; + normalize(); + substitute(*(g.get())); + if (g->inconsistent()) { + mc = 0; + break; + } + save_elim_vars(mc); + TRACE("solve_eqs_round", g->display(tout); if (mc) mc->display(tout);); + } + } + g->inc_depth(); + result.push_back(g.get()); + TRACE("solve_eqs", g->display(tout);); + SASSERT(g->is_well_sorted()); + } + }; + + imp * m_imp; + params_ref m_params; +public: + solve_eqs_tactic(ast_manager & m, params_ref const & p, expr_replacer * r, bool owner): + m_params(p) { + m_imp = alloc(imp, m, p, r, owner); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(solve_eqs_tactic, m, m_params, mk_expr_simp_replacer(m, m_params), true); + } + + virtual ~solve_eqs_tactic() { + dealloc(m_imp); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + r.insert(":solve-eqs-max-occs", CPK_UINT, "(default: infty) maximum number of occurrences for considering a variable for gaussian eliminations."); + r.insert(":theory-solver", CPK_BOOL, "(default: true) use theory solvers."); + r.insert(":ite-solver", CPK_BOOL, "(default: true) use if-then-else solver."); + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + (*m_imp)(in, result, mc, pc, core); + report_tactic_progress(":num-elim-vars", m_imp->get_num_eliminated_vars()); + } + + virtual void cleanup() { + unsigned num_elim_vars = m_imp->m_num_eliminated_vars; + ast_manager & m = m_imp->m(); + imp * d = m_imp; + expr_replacer * r = m_imp->m_r; + if (r) + r->set_substitution(0); + bool owner = m_imp->m_r_owner; + m_imp->m_r_owner = false; // stole replacer + #pragma omp critical (tactic_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m, m_params, r, owner); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + m_imp->m_num_eliminated_vars = num_elim_vars; + } + + virtual void collect_statistics(statistics & st) const { + st.update("eliminated vars", m_imp->get_num_eliminated_vars()); + } + + virtual void reset_statistics() { + m_imp->m_num_eliminated_vars = 0; + } + + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + +tactic * mk_solve_eqs_tactic(ast_manager & m, params_ref const & p, expr_replacer * r) { + if (r == 0) + return clean(alloc(solve_eqs_tactic, m, p, mk_expr_simp_replacer(m, p), true)); + else + return clean(alloc(solve_eqs_tactic, m, p, r, false)); +} + diff --git a/lib/solve_eqs_tactic.h b/lib/solve_eqs_tactic.h new file mode 100644 index 000000000..9554d238a --- /dev/null +++ b/lib/solve_eqs_tactic.h @@ -0,0 +1,30 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + solve_eqs_tactic.h + +Abstract: + + Tactic for solving equations and performing gaussian elimination. + +Author: + + Leonardo de Moura (leonardo) 2011-12-29. + +Revision History: + +--*/ +#ifndef _SOLVE_EQS_TACTIC_H_ +#define _SOLVE_EQS_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; +class expr_replacer; + +tactic * mk_solve_eqs_tactic(ast_manager & m, params_ref const & p = params_ref(), expr_replacer * r = 0); + +#endif + diff --git a/lib/solver.cpp b/lib/solver.cpp new file mode 100644 index 000000000..185223048 --- /dev/null +++ b/lib/solver.cpp @@ -0,0 +1,186 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + solver.h + +Abstract: + + abstract solver interface + +Author: + + Leonardo (leonardo) 2011-03-19 + +Notes: + +--*/ +#include"solver.h" +#include"smt_solver.h" + +unsigned solver::get_num_assertions() const { + NOT_IMPLEMENTED_YET(); + return 0; +} + +expr * solver::get_assertion(unsigned idx) const { + NOT_IMPLEMENTED_YET(); + return 0; +} + +void solver::display(std::ostream & out) const { + out << "(solver)"; +} + +class default_solver : public solver { + front_end_params * m_params; + smt::solver * m_context; +public: + default_solver():m_params(0), m_context(0) {} + + virtual ~default_solver() { + if (m_context != 0) + dealloc(m_context); + } + + virtual void set_front_end_params(front_end_params & p) { + m_params = &p; + } + + virtual void updt_params(params_ref const & p) { + if (m_context == 0) + return; + m_context->updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + if (m_context == 0) + return; + m_context->collect_param_descrs(r); + } + + virtual void init(ast_manager & m, symbol const & logic) { + SASSERT(m_params); + reset(); + #pragma omp critical (solver) + { + m_context = alloc(smt::solver, m, *m_params); + } + if (logic != symbol::null) + m_context->set_logic(logic); + } + + virtual void collect_statistics(statistics & st) const { + if (m_context == 0) { + return; + } + else { + m_context->collect_statistics(st); + } + } + + virtual void reset() { + if (m_context != 0) { + #pragma omp critical (solver) + { + dealloc(m_context); + m_context = 0; + } + } + } + + virtual void assert_expr(expr * t) { + SASSERT(m_context); + m_context->assert_expr(t); + } + + virtual void push() { + SASSERT(m_context); + m_context->push(); + } + + virtual void pop(unsigned n) { + SASSERT(m_context); + m_context->pop(n); + } + + virtual unsigned get_scope_level() const { + if (m_context) + return m_context->get_scope_level(); + else + return 0; + } + + virtual lbool check_sat(unsigned num_assumptions, expr * const * assumptions) { + SASSERT(m_context); + return m_context->check(num_assumptions, assumptions); + } + + virtual void get_unsat_core(ptr_vector & r) { + SASSERT(m_context); + unsigned sz = m_context->get_unsat_core_size(); + for (unsigned i = 0; i < sz; i++) + r.push_back(m_context->get_unsat_core_expr(i)); + } + + virtual void get_model(model_ref & m) { + SASSERT(m_context); + m_context->get_model(m); + } + + virtual proof * get_proof() { + SASSERT(m_context); + return m_context->get_proof(); + } + + virtual std::string reason_unknown() const { + SASSERT(m_context); + return m_context->last_failure_as_string(); + } + + virtual void get_labels(svector & r) { + SASSERT(m_context); + buffer tmp; + m_context->get_relevant_labels(0, tmp); + r.append(tmp.size(), tmp.c_ptr()); + } + + virtual void set_cancel(bool f) { + #pragma omp critical (solver) + { + if (m_context) + m_context->set_cancel(f); + } + } + + virtual void set_progress_callback(progress_callback * callback) { + SASSERT(m_context); + m_context->set_progress_callback(callback); + } + + virtual unsigned get_num_assertions() const { + if (m_context) + return m_context->size(); + else + return 0; + } + + virtual expr * get_assertion(unsigned idx) const { + SASSERT(m_context); + SASSERT(idx < get_num_assertions()); + return m_context->get_formulas()[idx]; + } + + virtual void display(std::ostream & out) const { + if (m_context) + m_context->display(out); + else + out << "(solver)"; + } + +}; + +solver * mk_default_solver() { + return alloc(default_solver); +} diff --git a/lib/solver.h b/lib/solver.h new file mode 100644 index 000000000..c7077a0a2 --- /dev/null +++ b/lib/solver.h @@ -0,0 +1,63 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + solver.h + +Abstract: + + abstract solver interface + +Author: + + Leonardo (leonardo) 2011-03-19 + +Notes: + +--*/ +#ifndef _SOLVER_H_ +#define _SOLVER_H_ + +#include"check_sat_result.h" +#include"front_end_params.h" +#include"progress_callback.h" +#include"params.h" + +class solver : public check_sat_result { +public: + virtual ~solver() {} + + // for backward compatibility + virtual void set_front_end_params(front_end_params & p) {} + + virtual void updt_params(params_ref const & p) {} + virtual void collect_param_descrs(param_descrs & r) {} + + virtual void set_produce_proofs(bool f) {} + virtual void set_produce_models(bool f) {} + virtual void set_produce_unsat_cores(bool f) {} + + virtual void init(ast_manager & m, symbol const & logic) = 0; + virtual void reset() = 0; + virtual void assert_expr(expr * t) = 0; + virtual void push() = 0; + virtual void pop(unsigned n) = 0; + virtual unsigned get_scope_level() const = 0; + virtual lbool check_sat(unsigned num_assumptions, expr * const * assumptions) = 0; + + virtual void set_cancel(bool f) {} + void cancel() { set_cancel(true); } + void reset_cancel() { set_cancel(false); } + + virtual void set_progress_callback(progress_callback * callback) = 0; + + virtual unsigned get_num_assertions() const; + virtual expr * get_assertion(unsigned idx) const; + + virtual void display(std::ostream & out) const; +}; + +solver * mk_default_solver(); + +#endif diff --git a/lib/solver_plugin.h b/lib/solver_plugin.h new file mode 100644 index 000000000..ba682a639 --- /dev/null +++ b/lib/solver_plugin.h @@ -0,0 +1,51 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + solver_plugin.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-30. + +Revision History: + +--*/ +#ifndef _SOLVER_PLUGIN_H_ +#define _SOLVER_PLUGIN_H_ + +#include"ast.h" + +/** + \brief Abstract solver used during preprocessing step. +*/ +class solver_plugin { +protected: + ast_manager & m_manager; + family_id m_fid; +public: + solver_plugin(symbol const & fname, ast_manager & m):m_manager(m), m_fid(m.get_family_id(fname)) {} + + virtual ~solver_plugin() {} + + /** + \brief Return true if it is possible to solve lhs = rhs. The + arg. forbidden contains the set of variables that cannot be + used. Store the result (var = subst) in var and subst. + + \remark Only simple solvers are supported. That is, the solution set has only one entry. + */ + virtual bool solve(expr * lhs, expr * rhs, expr_mark const & forbidden, app_ref & var, expr_ref & subst) = 0; + + family_id get_family_id() const { return m_fid; } + + ast_manager & get_manager() { return m_manager; } +}; + +#endif /* _SOLVER_PLUGIN_H_ */ + diff --git a/lib/sparse_use_list.h b/lib/sparse_use_list.h new file mode 100644 index 000000000..d50d48fee --- /dev/null +++ b/lib/sparse_use_list.h @@ -0,0 +1,107 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + sparse_use_list.h + +Abstract: + + Sparse use list index. + +Author: + + Leonardo de Moura (leonardo) 2008-02-13. + +Revision History: + +--*/ +#ifndef _SPARSE_USE_LIST_H_ +#define _SPARSE_USE_LIST_H_ + +#include"ast.h" +#include"obj_hashtable.h" + +/** + \brief (Generic sparse) use-list data-structure. +*/ +template +class sparse_use_list { + typedef obj_map use_list; + use_list m_use_list; + +public: + typedef typename Set::iterator iterator; + sparse_use_list() {} + ~sparse_use_list() { + reset(); + } + + void insert(typename Set::data const & parent, T * child) { + Set * parents = 0; + if (!m_use_list.find(child, parents)) { + parents = alloc(Set); + m_use_list.insert(child, parents); + } + SASSERT(parents); + parents->insert(parent); + } + + /** + \brief Return 0 if child did not contain any parents. + Return 1, if child does not have more parents after + removing parent. + Return 2 otherwise. + */ + unsigned erase(typename Set::data const & parent, T * child) { + Set * parents = 0; + if (m_use_list.find(child, parents)) { + parents->erase(parent); + if (parents->empty()) { + dealloc(parents); + m_use_list.erase(child); + return 1; + } + return 2; + } + return 0; + } + + void reset() { + typename use_list::iterator it = m_use_list.begin(); + typename use_list::iterator end = m_use_list.end(); + for (; it != end; ++it) + dealloc(it->m_value); + m_use_list.reset(); + } + + Set * get_parents(T * e) { + Set * parents = 0; + m_use_list.find(e, parents); + return parents; + } + + iterator begin(T * e) { + Set * parents = 0; + m_use_list.find(e, parents); + SASSERT(parents); + return parents->begin(); + } + + iterator end(T * e) { + Set * parents = 0; + m_use_list.find(e, parents); + SASSERT(parents); + return parents->end(); + } + + bool empty(T * e) const { + Set * parents = 0; + if (m_use_list.find(e, parents)) + return parents->empty(); + return true; + } +}; + +#endif /* _SPARSE_USE_LIST_H_ */ + diff --git a/lib/spc_asserted_literals.cpp b/lib/spc_asserted_literals.cpp new file mode 100644 index 000000000..d4ade5a03 --- /dev/null +++ b/lib/spc_asserted_literals.cpp @@ -0,0 +1,170 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_asserted_literals.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-14. + +Revision History: + +--*/ + +#include"spc_asserted_literals.h" +#include"ast_pp.h" + +namespace spc { + + asserted_literals::asserted_literals(ast_manager & m): + m_manager(m), + m_subst(m), + m_tmp_eq1(2), + m_tmp_eq2(2) { + for (unsigned i = 0; i < 2; i++) { + m_st[i] = alloc(substitution_tree, m_manager); + m_expr2clause[i] = alloc(expr2clause); + } + m_subst.reserve_offsets(3); + } + + asserted_literals::~asserted_literals() { + for (unsigned i = 0; i < 2; i++) { + dealloc(m_st[i]); + dealloc(m_expr2clause[i]); + } + } + + void asserted_literals::insert(clause * cls) { + if (cls->get_num_literals() == 1) { + TRACE("asserted_literals", tout << "inserting clause into asserted_literals index:\n"; + cls->display(tout, m_manager); tout << "\n";); + literal const & l = cls->get_literal(0); + unsigned neg = static_cast(l.sign()); + expr * atom = l.atom(); + m_st[neg]->insert(to_app(atom)); + m_expr2clause[neg]->insert(atom, cls); + m_subst.reserve_vars(m_st[neg]->get_approx_num_regs()); + } + } + + void asserted_literals::erase(clause * cls) { + if (cls->get_num_literals() == 1) { + literal const & l = cls->get_literal(0); + unsigned neg = static_cast(l.sign()); + expr * atom = l.atom(); + m_expr2clause[neg]->erase(atom); + m_st[neg]->erase(to_app(atom)); + } + } + + void asserted_literals::reset() { + for (unsigned i = 0; i < 2; i++) { + m_st[i]->reset(); + m_expr2clause[i]->reset(); + } + } + + struct asserted_literals_visitor : public st_visitor { + expr * m_target; + asserted_literals_visitor(substitution & s):st_visitor(s), m_target(0) {} + virtual bool operator()(expr * e) { + m_target = e; + return false; // stop + } + }; + + /** + \brief Return an unit clause that is a generalization + of the given literal. + Return 0 if such clause does not exist. + */ + clause * asserted_literals::gen(expr * atom, bool n) { + if (is_app(atom)) { + TRACE("asserted_literals", tout << "checking if there is generalizer for: " << n << "\n" << + mk_pp(atom, m_manager) << "\n";); + unsigned neg = static_cast(n); + m_subst.reset_subst(); + asserted_literals_visitor visitor(m_subst); + TRACE("asserted_literals_bug", tout << "query: " << mk_pp(atom, m_manager) << "\n"; m_st[neg]->display(tout); + m_subst.display(tout);); + m_st[neg]->gen(to_app(atom), visitor); + if (visitor.m_target != 0) { + clause * cls = 0; + m_expr2clause[neg]->find(visitor.m_target, cls); + SASSERT(cls); + return cls; + } + if (m_manager.is_eq(atom)) { + m_subst.reset(); + m_tmp_eq1.copy_swapping_args(to_app(atom)); + m_st[neg]->gen(m_tmp_eq1.get_app(), visitor); + if (visitor.m_target != 0) { + clause * cls = 0; + m_expr2clause[neg]->find(visitor.m_target, cls); + SASSERT(cls); + return cls; + } + } + } + return 0; + } + + /** + \brief Return an unit clause that is a generalization + of the equality (= lhs rhs) + Return 0 if such clause does not exist. + */ + clause * asserted_literals::gen_eq(expr * lhs, expr * rhs) { + expr * args[2] = { lhs, rhs }; + func_decl_ref eq_decl(m_manager.mk_func_decl(m_manager.get_basic_family_id(), OP_EQ, 0, 0, 2, args), m_manager); + m_tmp_eq2.set_decl(eq_decl); + m_tmp_eq2.set_arg(0, lhs); + m_tmp_eq2.set_arg(1, rhs); + return gen(m_tmp_eq2.get_app(), false); + } + + /** + \brief Return a unit equality clause (= s t) that (eq) subsumes (= lhs rhs). + That is, lhs and rhs have the form u[s'] and u[t'] and there is + a substitution sigma s.t. sigma(s) = s' and sigma(t) = t'. + Return 0 if such clause does not exist. + */ + clause * asserted_literals::subsumes(expr * lhs, expr * rhs) { + while (true) { + TRACE("eq_subsumption", tout << "eq_subsumption loop:\n" << mk_pp(lhs, m_manager) << "\n" << + mk_pp(rhs, m_manager) << "\n";); + clause * subsumer = gen_eq(lhs, rhs); + if (subsumer) + return subsumer; + if (!is_app(lhs) || !is_app(rhs) || + to_app(lhs)->get_decl() != to_app(rhs)->get_decl() || + to_app(lhs)->get_num_args() != to_app(rhs)->get_num_args()) + return 0; + expr * d1 = 0; + expr * d2 = 0; + unsigned num_args = to_app(lhs)->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * c1 = to_app(lhs)->get_arg(i); + expr * c2 = to_app(rhs)->get_arg(i); + if (c1 != c2) { + if (d1) + return 0; + d1 = c1; + d2 = c2; + } + } + SASSERT(d1); + lhs = d1; + rhs = d2; + } + return 0; + } + +}; diff --git a/lib/spc_asserted_literals.h b/lib/spc_asserted_literals.h new file mode 100644 index 000000000..969f5b452 --- /dev/null +++ b/lib/spc_asserted_literals.h @@ -0,0 +1,69 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_asserted_literals.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-14. + +Revision History: + +--*/ +#ifndef _SPC_ASSERTED_LITERALS_H_ +#define _SPC_ASSERTED_LITERALS_H_ + +#include"spc_clause.h" +#include"substitution_tree.h" +#include"obj_hashtable.h" + +namespace spc { + + /** + \brief Index for the asserted literals in the logical context. + + This index is used to implement forward unit subsumption, + equality subsumption, positive simplify-reflect, and + negative simplify-reflect. + */ + class asserted_literals { + protected: + typedef obj_map expr2clause; + ast_manager & m_manager; + substitution_tree * m_st[2]; + expr2clause * m_expr2clause[2]; + substitution m_subst; + tmp_app m_tmp_eq1; + tmp_app m_tmp_eq2; + public: + asserted_literals(ast_manager & m); + ~asserted_literals(); + + void insert(clause * cls); + void erase(clause * cls); + void reset(); + void reserve_vars(unsigned num_vars) { m_subst.reserve_vars(num_vars); } + + clause * gen(literal const & l) { + return gen(l.atom(), l.sign()); + } + + clause * gen(expr * atom, bool neg); + clause * gen_eq(expr * lhs, expr * rhs); + clause * subsumes(expr * lhs, expr * rhs); + + bool has_pos_literals() const { return !m_st[0]->empty(); } + bool has_neg_literals() const { return !m_st[1]->empty(); } + bool has_literals() const { return has_pos_literals() || has_neg_literals(); } + }; + +}; + +#endif /* _SPC_ASSERTED_LITERALS_H_ */ + diff --git a/lib/spc_clause.cpp b/lib/spc_clause.cpp new file mode 100644 index 000000000..4a2ce7bbf --- /dev/null +++ b/lib/spc_clause.cpp @@ -0,0 +1,287 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_clause.cpp + +Abstract: + + Superposition Calculus Clause + +Author: + + Leonardo de Moura (leonardo) 2008-02-02. + +Revision History: + +--*/ +#include"spc_clause.h" +#include"splay_tree_def.h" + +template class splay_tree; + +namespace spc { + + clause::clause(ast_manager & m, unsigned num_lits, literal * lits, justification * p, unsigned scope_lvl): + m_id(UINT_MAX), + m_time(UINT_MAX), + m_scope_lvl(scope_lvl), + m_bidx(UINT_MAX), + m_processed(false), + m_indexed(false), + m_has_sel_lit(false), + m_justification(p) { + + set_fields(num_lits, lits); + + m_num_lits_capacity = m_num_lits[0] + m_num_lits[1]; + + memcpy(m_lits, lits, sizeof(literal) * get_num_literals()); + + for (unsigned i = 0; i < num_lits; i++) + m.inc_ref(m_lits[i].atom()); + m_justification->inc_ref(); + m_justification->set_owner(this); + + sort_literals(); + } + + clause * clause::mk(ast_manager & m, unsigned num_lits, literal * lits, justification * p, unsigned scope_lvl) { + void * mem = m.get_allocator().allocate(sizeof(clause) + num_lits * sizeof(literal)); + return new (mem) clause(m, num_lits, lits, p, scope_lvl); + } + + void clause::init(unsigned id, unsigned time) { + SASSERT(m_id == UINT_MAX); + SASSERT(m_time == UINT_MAX); + + m_id = id; + m_time = time; + m_proof_depth = 0; + + justification_stat j_stat; + get_justification_stat(m_justification, j_stat); + + m_proof_depth = j_stat.m_proof_depth; + + if (j_stat.m_max_scope_lvl > m_scope_lvl) + m_scope_lvl = j_stat.m_max_scope_lvl; + + update_parents(j_stat.m_parent_clauses); + } + + void clause::update_parents(ptr_buffer & parents) { + ptr_buffer::iterator it = parents.begin(); + ptr_buffer::iterator end = parents.end(); + for (; it != end; ++it) { + clause * parent = *it; + parent->add_child(this); + } + } + + void clause::deallocate(ast_manager & m) { + + justification_stat j_stat; + get_justification_stat(get_justification(), j_stat); + + ptr_buffer::iterator it = j_stat.m_parent_clauses.begin(); + ptr_buffer::iterator end = j_stat.m_parent_clauses.end(); + for (; it != end; ++it) { + clause * parent = *it; + parent->del_child(this); + } + + dec_ref(get_justification(), m); + + unsigned num_lits = get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) + m.dec_ref(get_literal(i).atom()); + + unsigned capacity = get_num_literals_capacity(); + this->~clause(); + m.get_allocator().deallocate(sizeof(clause) + capacity * sizeof(literal), this); + } + + void clause::select_literal(unsigned idx) { + SASSERT(idx < get_num_literals()); + m_lits[idx].set_selected(true); + m_has_sel_lit = true; + } + + /** + \brief Return true if l is maximal in the clause, given a substitution s. + + s(l) is considered maximal if there is no literal l' in the clause such s(l') is greater + than s(l). + */ + bool clause::is_maximal(order & o, literal const & l, unsigned offset, substitution * s) const { + unsigned num_lits = get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) { + literal const & l_prime = m_lits[i]; + if (l != l_prime && greater(o, l_prime, l, offset, s)) + return false; + } + return true; + } + + /** + \brief Return true if l is a maximal selected literal in the clause, given a substitution s. + + s(l) is considered maximal selected literal if there is no + selected literal l' in the clause such s(l') is greater than s(l). + */ + bool clause::is_sel_maximal(order & o, literal const & l, unsigned offset, substitution * s) const { + if (!l.is_selected()) + return false; + unsigned num_lits = get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) { + literal const & l_prime = m_lits[i]; + if (l != l_prime && l_prime.is_selected() && greater(o, l_prime, l, offset, s)) + return false; + } + return true; + } + + /** + \brief Return true if l is eligible for resolution. + */ + bool clause::is_eligible_for_resolution(order & o, literal const & l, unsigned offset, substitution * s) const { + if (has_sel_lit()) + return is_sel_maximal(o, l, offset, s); + else + return is_maximal(o, l, offset, s); + } + + /** + \brief Return true if l is eligible for paramodulation. + */ + bool clause::is_eligible_for_paramodulation(order & o, literal const & l, unsigned offset, substitution * s) const { + return !has_sel_lit() && is_maximal(o, l, offset, s); + } + + /** + \brief Try to orient literals. + */ + void clause::try_to_orient_literals(order & o) { + o.reserve_vars(get_num_vars()); + unsigned num_lits = get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) { + literal & l = m_lits[i]; + l.try_to_orient(o); + } + } + + void clause::set_fields(unsigned num_lits, literal * lits) { + clause_stat c_stat; + get_clause_stat(num_lits, lits, c_stat); + + m_num_vars = c_stat.m_max_var_idx + 1; + m_sym_count = c_stat.m_sym_count; + m_const_count = c_stat.m_const_count; + m_depth = c_stat.m_depth; + m_num_lits[0] = c_stat.m_num_lits[0]; + m_num_lits[1] = c_stat.m_num_lits[1]; + m_ground = c_stat.m_ground; + } + + struct lit_lt { + bool operator()(literal const & l1, literal const & l2) const { + if (l1.is_ground() > l2.is_ground()) + return true; + if (l1.is_ground() != l2.is_ground()) + return false; + if (l1.get_approx_depth() > l2.get_approx_depth()) + return true; + if (l1.get_approx_depth() != l2.get_approx_depth()) + return false; + if (l1.get_approx_sym_count() > l2.get_approx_sym_count()) + return true; + if (l1.get_approx_sym_count() != l2.get_approx_sym_count()) + return false; + if (l1.get_approx_const_count() > l2.get_approx_const_count()) + return true; + if (l1.get_approx_const_count() != l2.get_approx_const_count()) + return false; + return l1.get_id() < l2.get_id(); + } + }; + + /** + \brief Sort literals to improve the performance of subsumption tests. + */ + void clause::sort_literals() { + DEBUG_CODE({ + unsigned num_lits = get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) { + SASSERT(m_lits[i].has_stats()); + } + }); + std::sort(m_lits, m_lits + get_num_literals(), lit_lt()); + } + + /** + \brief Replace clause literal with the given literals. + Use the given justification to justify the new clause. + */ + void clause::update_lits(ast_manager & m, unsigned num_lits, literal * lits, justification * j) { + unsigned old_num_lits = get_num_literals(); + SASSERT(num_lits <= old_num_lits); + + for (unsigned i = 0; i < num_lits; i++) + m.inc_ref(lits[i].atom()); + + for (unsigned i = 0; i < old_num_lits; i++) + m.dec_ref(m_lits[i].atom()); + + for (unsigned i = 0; i < num_lits; i++) + m_lits[i] = lits[i]; + + set_fields(num_lits, m_lits); + + SASSERT(get_num_literals() == num_lits); + + j->inc_ref(); + m_justification->set_owner(0); // release ownership + dec_ref(m_justification, m); + m_justification = j; + m_justification->set_owner(this); + + sort_literals(); + + justification_stat j_stat; + get_justification_stat(m_justification, j_stat); + + m_proof_depth = j_stat.m_proof_depth; + + SASSERT(m_scope_lvl == j_stat.m_max_scope_lvl); + + update_parents(j_stat.m_parent_clauses); + } + + void clause::display(std::ostream & out, ast_manager & m, bool detailed) { + if (get_num_literals() == 0) { + out << "empty-clause"; + return; + } + out << "#" << m_id << ": (clause "; + spc::display(out, get_num_literals(), m_lits, m, detailed); + out << ")"; + if (m_processed) + out << "*"; + } + + void get_clause_stat(unsigned num_lits, literal * lits, clause_stat & stat) { + for (unsigned i = 0; i < num_lits; i++) { + literal_stat c; + lits[i].get_stat(c); + stat.m_sym_count += c.m_sym_count; + stat.m_depth = std::max(stat.m_depth, c.m_depth); + stat.m_max_var_idx = std::max(stat.m_max_var_idx, c.m_max_var_idx); + stat.m_const_count += c.m_const_count; + stat.m_ground &= c.m_ground; + stat.m_num_lits[static_cast(lits[i].sign())]++; + } + } + +}; diff --git a/lib/spc_clause.h b/lib/spc_clause.h new file mode 100644 index 000000000..171248732 --- /dev/null +++ b/lib/spc_clause.h @@ -0,0 +1,152 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_clause.h + +Abstract: + + Superposition Calculus Clause + +Author: + + Leonardo de Moura (leonardo) 2008-02-02. + +Revision History: + +--*/ +#ifndef _SPC_CLAUSE_H_ +#define _SPC_CLAUSE_H_ + +#include"ast.h" +#include"splay_tree.h" +#include"use_list.h" +#include"spc_literal.h" +#include"spc_justification.h" +#include"use_list.h" + +namespace spc { + + class context; + + /** + \brief Superposition Calculus clause. + */ + class clause { + struct compare { + // ignoring potential overflow/underflow + int operator()(clause * c1, clause * c2) const { + return static_cast(c1->get_id()) - static_cast(c2->get_id()); + } + }; + public: + typedef splay_tree set; + private: + unsigned m_id; // clause unique id + unsigned m_time; // how old is the clause. + unsigned m_num_vars; // approx. number of variables (i.e., max_var_id + 1) + unsigned m_sym_count; // number of symbols + unsigned m_const_count; // number of constants + unsigned m_depth; // depth (i.e., max depth of a literal) + unsigned m_proof_depth; + unsigned m_scope_lvl; // which scope level owns the clause + unsigned m_num_lits[2]; // number of positive [0] and negative [1] literals. + unsigned m_num_lits_capacity; // some of the clause literals can be simplified and removed, this field contains the original number of literals (used for GC). + unsigned m_bidx; // position on the backtracking stack + bool m_ground:1; + bool m_processed:1; + bool m_indexed:1; + bool m_has_sel_lit:1; + justification * m_justification; + set m_children; + literal m_lits[0]; + friend class context; + + void set_fields(unsigned num_lits, literal * lits); + unsigned get_bidx() const { return m_bidx; } + void init(unsigned idx, unsigned time); + void update_parents(ptr_buffer & parents); + void set_bidx(unsigned idx) { SASSERT(m_bidx == UINT_MAX); m_bidx = idx; } + void add_child(clause * c) { m_children.insert(c); } + void del_child(clause * c) { m_children.erase(c); } + void set_processed(bool f) { m_processed = f; } + void set_indexed(bool f) { m_indexed = f; } + void sort_literals(); + /** + \brief Release ownership of the justification. + */ + justification * release_justification() { justification * r = m_justification; m_justification = 0; return r; } + + clause(ast_manager & m, unsigned num_lits, literal * lits, justification * p, unsigned scope_lvl); + + public: + static clause * mk(ast_manager & m, unsigned num_lits, literal * lits, justification * p, unsigned scope_lvl); + void deallocate(ast_manager & m); + + unsigned get_id() const { SASSERT(m_id != UINT_MAX); return m_id; } + unsigned get_time() const { return m_time; } + unsigned get_symbol_count() const { return m_sym_count; } + unsigned get_proof_depth() const { return m_proof_depth; } + unsigned get_num_literals() const { return m_num_lits[0] + m_num_lits[1]; } + unsigned get_num_literals_capacity() const { return m_num_lits_capacity; } + unsigned get_num_pos_literals() const { return m_num_lits[0]; } + unsigned get_num_neg_literals() const { return m_num_lits[1]; } + unsigned get_depth() const { return m_depth; } + unsigned get_const_count() const { return m_const_count; } + unsigned get_scope_lvl() const { return m_scope_lvl; } + unsigned get_num_vars() const { return m_num_vars; } + bool empty() const { return m_num_lits[0] == 0 && m_num_lits[1] == 0; } + literal const & get_literal(unsigned idx) const { return m_lits[idx]; } + literal & get_literal(unsigned idx) { return m_lits[idx]; } + literal * get_literals() const { return const_cast(m_lits); } + justification * get_justification() const { return m_justification; } + bool is_processed() const { return m_processed; } + bool is_indexed() const { return m_indexed; } + bool is_ground() const { return m_ground; } + void select_literal(unsigned idx); + bool is_maximal(order & o, literal const & l, unsigned offset = 0, substitution * s = 0) const; + bool is_sel_maximal(order & o, literal const & l, unsigned offset = 0, substitution * s = 0) const ; + bool is_eligible_for_resolution(order & o, literal const & l, unsigned offset = 0, substitution * s = 0) const; + bool is_eligible_for_paramodulation(order & o, literal const & l, unsigned offset = 0, substitution * s = 0) const; + bool has_sel_lit() const { return m_has_sel_lit; } + void try_to_orient_literals(order & o); + void update_lits(ast_manager & m, unsigned num_lits, literal * lits, justification * j); + + void display(std::ostream & out, ast_manager & m, bool detailed = false); + unsigned hash() const { return m_id; } + }; + + typedef ptr_vector clause_vector; + + /** + \brief Clause Statistics (used to build clauses, subsumption, etc). + */ + struct clause_stat : public expr_stat { + unsigned m_num_lits[2]; + clause_stat() { + m_num_lits[0] = 0; + m_num_lits[1] = 0; + } + }; + + /** + \brief Compute the statistics for a clause with num_lits + literals lits, and store the results in stat. + */ + void get_clause_stat(unsigned num_lits, literal * lits, clause_stat & stat); + + /** + \brief A mapping from clause-id's to clauses + */ + class id2clause { + ptr_vector m_clauses; + public: + void insert(clause * c) { return m_clauses.setx(c->get_id(), c, 0); } + void erase(clause * c) { unsigned id = c->get_id(); if (id < m_clauses.size()) m_clauses[id] = 0; } + clause * operator()(unsigned id) const { return m_clauses.get(id, 0); } + }; +}; + +#endif /* _SPC_CLAUSE_H_ */ + diff --git a/lib/spc_clause_pos_set.h b/lib/spc_clause_pos_set.h new file mode 100644 index 000000000..47fec1d7e --- /dev/null +++ b/lib/spc_clause_pos_set.h @@ -0,0 +1,58 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_clause_pos_set.h + +Abstract: + + A set of pairs (clause, index). + +Author: + + Leonardo de Moura (leonardo) 2008-02-16. + +Revision History: + +--*/ +#ifndef _SPC_CLAUSE_POS_SET_H_ +#define _SPC_CLAUSE_POS_SET_H_ + +#include"hashtable.h" + +namespace spc { + + typedef std::pair clause_pos_pair; + + class clause_pos_entry { + clause_pos_pair m_data; + public: + typedef clause_pos_pair data; + clause_pos_entry() { m_data.first = 0; } + unsigned get_hash() const { return m_data.first->get_id(); } + bool is_free() const { return m_data.first == 0; } + bool is_deleted() const { return m_data.first == reinterpret_cast(1); } + bool is_used() const { + return m_data.first != reinterpret_cast(0) && m_data.first != reinterpret_cast(1); + } + clause_pos_pair const & get_data() const { return m_data; } + clause_pos_pair & get_data() { return m_data; } + void set_data(clause_pos_pair const & d) { + SASSERT(d.first != 0 && d.first != reinterpret_cast(1)); + m_data = d; + } + void set_hash(unsigned h) { SASSERT(m_data.first->get_id() == h); } + void mark_as_deleted() { m_data.first = reinterpret_cast(1); } + void mark_as_free() { m_data.first = 0; } + }; + + struct clause_pos_pair_hash { + unsigned operator()(clause_pos_pair const & p) const { return p.first->get_id(); } + }; + + typedef core_hashtable > clause_pos_set; +}; + +#endif /* _SPC_CLAUSE_POS_SET_H_ */ + diff --git a/lib/spc_clause_selection.cpp b/lib/spc_clause_selection.cpp new file mode 100644 index 000000000..df11a01b2 --- /dev/null +++ b/lib/spc_clause_selection.cpp @@ -0,0 +1,121 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_clause_selection.cpp + +Abstract: + + Superposition Calculus Clause Selection + +Author: + + Leonardo de Moura (leonardo) 2008-02-02. + +Revision History: + +--*/ +#include"spc_clause_selection.h" + +namespace spc { + + const unsigned default_heap_size = 1024; + + clause_selection::clause_selection(unsigned num_heaps, clause_eval * const * fs, unsigned * slot_size): + m_curr_slot(0), + m_counter(0), + m_fs(num_heaps, fs) { + SASSERT(num_heaps > 0); + for (unsigned i = 0; i < num_heaps; i++) { + m_heaps.push_back(alloc(heap, default_heap_size, lt(m_id2clause, *(fs[i])))); + SASSERT(slot_size[i] > 0); + m_slot_size.push_back(slot_size[i]); + } + } + + clause_selection::~clause_selection() { + std::for_each(m_heaps.begin(), m_heaps.end(), delete_proc >()); + std::for_each(m_fs.begin(), m_fs.end(), delete_proc()); + } + + void clause_selection::reserve(unsigned cid) { + unsigned capacity = m_heaps[0]->get_bounds(); + if (cid >= capacity) { + unsigned new_capacity = 2 * cid + 1; + SASSERT(cid < new_capacity); + ptr_vector >::iterator it = m_heaps.begin(); + ptr_vector >::iterator end = m_heaps.end(); + for (; it != end; ++it) { + heap * h = *it; + h->reserve(new_capacity);; + } + } + } + + void clause_selection::reset() { + ptr_vector >::iterator it = m_heaps.begin(); + ptr_vector >::iterator end = m_heaps.end(); + for (; it != end; ++it) { + heap * h = *it; + h->reset(); + } + } + + void clause_selection::insert(clause * c) { + reserve(c->get_id()); + m_id2clause.insert(c); + ptr_vector >::iterator it = m_heaps.begin(); + ptr_vector >::iterator end = m_heaps.end(); + for (; it != end; ++it) { + heap * h = *it; + h->insert(c->get_id()); + } + } + + void clause_selection::erase(clause * c) { + // remark: it is not necessary to remove c from m_id2clause + ptr_vector >::iterator it = m_heaps.begin(); + ptr_vector >::iterator end = m_heaps.end(); + SASSERT(it != end); + if (!(*it)->contains(c->get_id())) + return; + for (; it != end; ++it) { + heap * h = *it; + h->erase(c->get_id()); + } + } + + bool clause_selection::empty() const { + ptr_vector >::const_iterator it = m_heaps.begin(); + ptr_vector >::const_iterator end = m_heaps.end(); + for (; it != end; ++it) + if (!(*it)->empty()) + return false; + return true; + } + + clause * clause_selection::get_best() { + heap * h = m_heaps[m_curr_slot]; + if (h->empty()) + return 0; + unsigned cid = m_heaps[m_curr_slot]->erase_min(); + clause * c = m_id2clause(cid); + SASSERT(c); + // remove clause from the other heaps + unsigned num_heaps = m_heaps.size(); + for (unsigned i = 0; i < num_heaps; i++) { + if (m_curr_slot != i) + m_heaps[i]->erase(cid); + } + // remark: it is not necessary to remove c from m_id2clause + m_counter++; + if (m_counter >= m_slot_size[m_curr_slot]) { + m_counter = 0; + m_curr_slot++; + if (m_curr_slot >= m_slot_size.size()) + m_curr_slot = 0; + } + return c; + } +}; diff --git a/lib/spc_clause_selection.h b/lib/spc_clause_selection.h new file mode 100644 index 000000000..34fcc2e8f --- /dev/null +++ b/lib/spc_clause_selection.h @@ -0,0 +1,85 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_clause_selection.h + +Abstract: + + Superposition Calculus Clause Selection + +Author: + + Leonardo de Moura (leonardo) 2008-02-02. + +Revision History: + +--*/ +#ifndef _SPC_CLAUSE_SELECTION_H_ +#define _SPC_CLAUSE_SELECTION_H_ + +#include"spc_clause.h" +#include"heap.h" + +namespace spc { + + /** + \brief Abstract functor for evaluating how 'good' a clause is. + Smaller values mean better clauses. + */ + struct clause_eval { + virtual ~clause_eval() {} + virtual unsigned operator()(clause * c) const = 0; + }; + + /** + \brief Clause selection heuristic. It supports different priority queues. + */ + class clause_selection { + class lt { + id2clause & m_id2clause; + clause_eval & m_func; + public: + lt(id2clause & m, clause_eval & f): + m_id2clause(m), m_func(f) {} + bool operator()(int cidx1, int cidx2) const { + return m_func(m_id2clause(cidx1)) < m_func(m_id2clause(cidx2)); + } + }; + + id2clause m_id2clause; + ptr_vector > m_heaps; + unsigned_vector m_slot_size; + unsigned m_curr_slot; + unsigned m_counter; + ptr_vector m_fs; + void reserve(unsigned cid); + public: + clause_selection(unsigned num_heaps, clause_eval * const * fs, unsigned * slots); + ~clause_selection(); + void insert(clause * c); + void erase(clause * c); + bool empty() const; + void reset(); + clause * get_best(); + }; + + struct symbol_count_clause_eval : public clause_eval { + virtual ~symbol_count_clause_eval() {} + virtual unsigned operator()(clause * c) const { return c->get_symbol_count(); } + }; + + struct time_clause_eval : public clause_eval { + virtual ~time_clause_eval() {} + virtual unsigned operator()(clause * c) const { return c->get_time(); } + }; + + struct proof_depth_clause_eval : public clause_eval { + virtual ~proof_depth_clause_eval() {} + virtual unsigned operator()(clause * c) const { return c->get_proof_depth(); } + }; +}; + +#endif /* _SPC_CLAUSE_SELECTION_H_ */ + diff --git a/lib/spc_context.cpp b/lib/spc_context.cpp new file mode 100644 index 000000000..b1ff0a18f --- /dev/null +++ b/lib/spc_context.cpp @@ -0,0 +1,504 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_context.cpp + +Abstract: + + Superposition Calculus Engine + +Author: + + Leonardo de Moura (leonardo) 2008-02-02. + +Revision History: + +--*/ +#include"spc_context.h" +#include"buffer.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"ast_smt2_pp.h" +#include"warning.h" + +namespace spc { + + context::context(ast_manager & m, order & o, clause_selection & cs, literal_selection & ls, simplifier & s, spc_params & params): + m_manager(m), + m_params(params), + m_alloc(m.get_allocator()), + m_order(o), + m_cls_sel(cs), + m_lit_sel(ls), + m_simplifier(s), + m_time(0), + m_scope_lvl(0), + m_sem_taut(m), + m_asserted_literals(m), + m_rewriter(m, s, m_order, m_asserted_literals), + m_der(m), + m_subsumption(m, m_asserted_literals, params), + m_eq_resolution(m, m_order, m_stats), + m_factoring(m, m_order, m_stats), + m_superposition(m, m_order, m_stats), + m_unsat(0) { + m_order.reserve_offsets(3); + } + + context::~context() { + reset(); + } + + void context::reset() { + m_cls_sel.reset(); + m_time = 0; + m_scope_lvl = 0; + + if (m_unsat) + m_unsat = 0; + for (unsigned i = 0; i <= m_scope_lvl; i++) { + del_clauses(i); + if (i < m_clauses_to_unfreeze.size()) + m_clauses_to_unfreeze[i].reset(); + } + + m_asserted_literals.reset(); + m_rewriter.reset(); + m_subsumption.reset(); + m_superposition.reset(); + } + + /** + \brief Insert the given clause into the indexes of processed clauses. + */ + void context::insert_index(clause * cls) { + TRACE("insert_index", tout << "indexing clause, num_vars: " << cls->get_num_vars() << "\n"; + cls->display(tout, m_manager); tout << "\n";); + m_order.reserve_vars(cls->get_num_vars()); + m_lit_sel(cls); + m_asserted_literals.insert(cls); + m_rewriter.insert(cls); + m_subsumption.insert(cls); + m_superposition.insert(cls); + cls->set_indexed(true); + } + + void context::erase_index(clause * cls) { + if (cls->is_indexed()) { + m_asserted_literals.erase(cls); + m_rewriter.erase(cls); + m_subsumption.erase(cls); + m_superposition.erase(cls); + cls->set_indexed(false); + } + } + + void context::set_conflict(clause * cls) { + SASSERT(cls->get_num_literals() == 0); + m_unsat = cls; + if (m_params.m_spc_trace) { + cls->display(std::cout, m_manager); std::cout << " "; + cls->get_justification()->display(std::cout); + std::cout << "\n"; + std::cout.flush(); + } + } + + void context::del_clause(clause * cls) { + TRACE("context", tout << "deleting clause:\n"; cls->display(tout, m_manager); tout << "\n";); + m_stats.m_num_del_clause++; + + erase_index(cls); + if (!cls->is_processed()) + m_cls_sel.erase(cls); + + unsigned scope_lvl = cls->get_scope_lvl(); + unsigned bidx = cls->get_bidx(); + m_clauses_to_delete[scope_lvl][bidx] = 0; + + cls->deallocate(m_manager); + } + + void context::freeze_clause_until(clause * cls, unsigned scope_lvl) { + if (cls->get_scope_lvl() >= scope_lvl) { + del_clause(cls); + return; + } + TRACE("context", tout << "freezing clause until: " << scope_lvl << ":\n"; cls->display(tout, m_manager); tout << "\n";); + if (scope_lvl >= m_clauses_to_unfreeze.size()) + m_clauses_to_unfreeze.resize(scope_lvl+1, clause_vector()); + + erase_index(cls); + cls->set_processed(false); + + m_clauses_to_unfreeze[scope_lvl].push_back(cls); + } + + void context::unfreeze_clause(clause * cls) { + TRACE("context", tout << "unfreezing clausel: "; cls->display(tout, m_manager); tout << "\n";); + SASSERT(!cls->is_processed()); + m_cls_sel.insert(cls); + } + + void context::init_clause(clause * cls) { + m_stats.m_num_mk_clause++; + + cls->init(m_cls_id_gen.mk(), m_time); + m_time++; + unsigned scope_lvl = cls->get_scope_lvl(); + + if (scope_lvl >= m_clauses_to_delete.size()) + m_clauses_to_delete.resize(scope_lvl+1, clause_vector()); + + clause_vector & cv = m_clauses_to_delete[scope_lvl]; + unsigned bidx = cv.size(); + cv.push_back(cls); + cls->set_bidx(bidx); + } + + clause * context::mk_clause(unsigned num_lits, literal * lits, justification * p, unsigned scope_lvl) { + clause * cls = clause::mk(m_manager, num_lits, lits, p, scope_lvl); + init_clause(cls); + return cls; + } + + void context::assert_expr(expr * n, proof * p, unsigned scope_lvl) { + TRACE("spc_assert_expr", tout << mk_ismt2_pp(n, m_manager) << "\n";); + SASSERT(scope_lvl <= m_scope_lvl); + justification_ref ref(m_manager); + ref = justification_proof_wrapper::mk(p, m_manager); + assert_expr(n, ref, scope_lvl); + } + + void invalid_clause(expr * n) { + warning_msg("ignoring formula containing an universally quantified boolean variable."); + } + + void context::assert_expr(expr * n, justification * p, unsigned scope_lvl) { + SASSERT(scope_lvl <= m_scope_lvl); + buffer lits; + if (is_forall(n)) + n = to_quantifier(n)->get_expr(); + if (m_manager.is_or(n)) { + unsigned num = to_app(n)->get_num_args(); + for (unsigned i = 0; i < num; i++) { + expr * c = to_app(n)->get_arg(i); + bool is_neg = m_manager.is_not(c); + if (is_var(c) || (is_neg && is_var(to_app(c)->get_arg(0)))) { + invalid_clause(n); + return; + } + if (is_neg) + lits.push_back(literal(to_app(c)->get_arg(0), true)); + else + lits.push_back(literal(c, false)); + } + } + else if (m_manager.is_false(n)) { + // skip + } + else if (m_manager.is_not(n)) { + if (is_var(to_app(n)->get_arg(0))) { + invalid_clause(n); + return; + } + lits.push_back(literal(to_app(n)->get_arg(0), true)); + } + else { + if (is_var(n)) { + invalid_clause(n); + return; + } + lits.push_back(literal(n, false)); + } + + if (trivial(lits.size(), lits.c_ptr())) + return; + + clause * cls = mk_clause(lits.size(), lits.c_ptr(), p, scope_lvl); + m_cls_sel.insert(cls); + if (cls->get_num_literals() == 0) + set_conflict(cls); + } + + /** + \brief Return true if the given clause (set of literals) is trivial. + That is, it contains the literal s = s or complementary literals. + */ + bool context::trivial(unsigned num_lits, literal * lits) { + SASSERT(m_found_literals.empty()); + for (unsigned i = 0; i < num_lits; i++) { + literal l = lits[i]; + if (m_found_literals.contains_neg(l) || l.is_true(m_manager)) { + m_found_literals.reset(); + m_stats.m_num_trivial++; + return true; + } + m_found_literals.insert(l); + } + m_found_literals.reset(); + return false; + } + + bool context::trivial(clause * cls) { + return trivial(cls->get_num_literals(), cls->get_literals()); + } + + /** + \brief Simplify the given clause using the set of processed clauses. + Return the simplified clause. + */ + clause * context::simplify(clause * cls) { + clause * old_cls = cls; + m_der(cls); + cls = m_rewriter(old_cls); + if (cls != old_cls) { + // freeze old clause until simplified clause is deleted. + freeze_clause_until(old_cls, cls->get_scope_lvl()); + init_clause(cls); + m_stats.m_num_simplified++; + } + m_der(cls); + return cls; + } + + /** + \brief Use the given clause to simplify the set of processed clauses. + + \remark: processed clauses that can be simplified, are moved to the + set of unprocessed clauses. + */ + void context::simplify_processed(clause * cls) { + // TODO + } + + /** + \brief Return true if the clause is redundant. + */ + bool context::redundant(clause * cls) { + int r_scope_lvl = -1; + if (trivial(cls)) { + TRACE("redundant", tout << "clause is trivial:\n"; cls->display(tout, m_manager); tout << "\n";); + r_scope_lvl = 0; + } + else if (m_sem_taut(cls->get_num_literals(), cls->get_literals())) { + TRACE("redundant", tout << "clause is a semantic tautology:\n"; cls->display(tout, m_manager); tout << "\n";); + r_scope_lvl = 0; + } + else { + clause * subsumer = m_subsumption.forward(cls); + if (subsumer != 0) { + TRACE("redundant", tout << "clause was subsumed: "; cls->display(tout, m_manager); + tout << "\nsubsumer:\n"; subsumer->display(tout, m_manager); tout << "\n";); + r_scope_lvl = subsumer->get_scope_lvl(); + m_stats.m_num_subsumed++; + } + } + + if (r_scope_lvl >= 0) { + m_stats.m_num_redundant++; + TRACE("spc_saturate", tout << "clause is redundant until level: " << r_scope_lvl << " ...\n";); + freeze_clause_until(cls, r_scope_lvl); + return true; + } + + return false; + } + + /** + \brief Process a newly generated clause. + */ + void context::process_new_clause(clause * cls) { + if (cls) { + SASSERT(cls->get_justification() != 0); + init_clause(cls); + if (trivial(cls)) { + del_clause(cls); + return; + } + cls = simplify(cls); + if (trivial(cls)) { + del_clause(cls); + return; + } + // if (!redundant(cls)) { + m_cls_sel.insert(cls); + if (cls->get_num_literals() == 0) + set_conflict(cls); + // } + } + } + + /** + \brief Apply superposition (left&right), resolution, (equality) factoring, and equality resolution + with the given clause and the set of processed clauses. + */ + void context::generate(clause * cls) { + m_new_clauses.reset(); + m_eq_resolution(cls, m_new_clauses); + m_factoring(cls, m_new_clauses); + m_superposition(cls, m_new_clauses); + + ptr_vector::iterator it = m_new_clauses.begin(); + ptr_vector::iterator end = m_new_clauses.end(); + for (; it != end; ++it) { + TRACE("spc_generate", tout << "new generated clause:\n"; (*it)->display(tout, m_manager); tout << "\n";); + process_new_clause(*it); + } + } + + void context::saturate(unsigned threshold) { + if (inconsistent()) + return; + TRACE("spc_saturate", tout << "initial state:\n"; display(tout);); + unsigned i = 0; + ptr_buffer to_simplify; + while (i < threshold && !processed_all()) { + i++; + m_stats.m_num_processed++; + clause * cls = m_cls_sel.get_best(); + if (m_params.m_spc_trace) { + cls->display(std::cout, m_manager); std::cout << " "; + cls->get_justification()->display(std::cout); + std::cout << "\n"; + std::cout.flush(); + } + cls->set_processed(true); + TRACE("spc_saturate", tout << "get best: "; cls->display(tout, m_manager); tout << "\n";); + cls = simplify(cls); + + TRACE("spc_saturate", tout << "clause after simplification: "; cls->display(tout, m_manager); tout << "\n";); + if (redundant(cls)) + continue; + if (cls->empty()) { + set_conflict(cls); + break; + } + cls->try_to_orient_literals(m_order); + simplify_processed(cls); + insert_index(cls); + generate(cls); + if (inconsistent()) + break; + } + + TRACE("spc_saturate", tout << "final state:\n"; display(tout);); + +#if 0 + IF_VERBOSE(10000, + display(std::cout);); + display_statistics(std::cout); + if (m_unsat && m_manager.fine_grain_proofs()) { + std::cout << mk_ll_pp(m_unsat->get_justification()->get_proof(), m_manager); + } +#endif + } + + void context::push_scope() { + m_scope_lvl++; + m_time_trail.push_back(m_time); + } + + void context::del_clauses(unsigned scope_lvl) { + if (scope_lvl < m_clauses_to_delete.size()) { + clause_vector & cv = m_clauses_to_delete[m_scope_lvl]; + clause_vector::iterator it = cv.begin(); + clause_vector::iterator end = cv.end(); + for (; it != end; ++it) { + clause * cls = *it; + if (cls) + del_clause(cls); + } + cv.reset(); + } + } + + void context::unfreeze_clauses(unsigned scope_lvl) { + if (scope_lvl < m_clauses_to_unfreeze.size()) { + clause_vector & cv = m_clauses_to_unfreeze[m_scope_lvl]; + clause_vector::iterator it = cv.begin(); + clause_vector::iterator end = cv.end(); + for (; it != end; ++it) + unfreeze_clause(*it); + cv.reset(); + } + } + + void context::pop_scope(unsigned num_scopes) { + SASSERT(num_scopes >= m_scope_lvl); + unsigned new_lvl = m_scope_lvl - num_scopes; + m_time = m_time_trail[new_lvl]; + m_time_trail.shrink(new_lvl); + + if (m_unsat && new_lvl < m_unsat->get_scope_lvl()) + m_unsat = 0; + + while (m_scope_lvl > new_lvl) { + del_clauses(m_scope_lvl); + unfreeze_clauses(m_scope_lvl); + m_scope_lvl --; + } + } + + void context::display(std::ostream & out, vector const & cvs, unsigned scope_lvl, bool frozen) const { + if (scope_lvl < cvs.size()) { + bool first = true; + clause_vector const & cv = cvs[scope_lvl]; + clause_vector::const_iterator it = cv.begin(); + clause_vector::const_iterator end = cv.end(); + for (; it != end; ++it) { + clause * cls = *it; + if (cls) { + if (first) { + out << "level " << scope_lvl << ":\n"; + first = false; + } + cls->display(out, m_manager); + if (frozen) + out << " [frozen]"; + out << "\n"; + } + } + } + } + + void context::display(std::ostream & out) const { + for (unsigned i = 0; i <= m_scope_lvl; i++) { + display(out, m_clauses_to_delete, i, false); + display(out, m_clauses_to_unfreeze, i, true); + } + } + + void context::display_statistics(std::ostream & out) const { + m_stats.display(out); + } + + +/** + Generate new clauses + + 5) Object equality resolution 1 + + (R or X = i) + ==> + sigma(R) + + sigma = { X -> j } + where i and j are distinct objects + sigma(X = i) is not smaller or equal than any other literal in the clause + + 6) Object equality resolution 2 + + (R or X = Y) + ==> + sigma(R) + + sigma = { X -> i, Y -> j } + For every pair of distinct objects i and j + sigma(X = Y) is not smaller or equal than any other literal in the clause + +*/ + +}; diff --git a/lib/spc_context.h b/lib/spc_context.h new file mode 100644 index 000000000..b0c0cbd45 --- /dev/null +++ b/lib/spc_context.h @@ -0,0 +1,122 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_context.h + +Abstract: + + Superposition Calculus Engine + +Author: + + Leonardo de Moura (leonardo) 2008-02-02. + +Revision History: + +--*/ +#ifndef _SPC_CONTEXT_H_ +#define _SPC_CONTEXT_H_ + +#include"spc_params.h" +#include"spc_clause.h" +#include"spc_clause_selection.h" +#include"spc_literal_selection.h" +#include"spc_semantic_tautology.h" +#include"spc_rewriter.h" +#include"spc_asserted_literals.h" +#include"spc_subsumption.h" +#include"spc_eq_resolution.h" +#include"spc_factoring.h" +#include"spc_superposition.h" +#include"spc_statistics.h" +#include"spc_der.h" +#include"substitution_tree.h" +#include"order.h" + +namespace spc { + + /** + \brief Logical context of the superposition calculus engine. + */ + class context { + public: + statistics m_stats; + protected: + typedef clause::set clause_set; + + ast_manager & m_manager; + spc_params & m_params; + small_object_allocator & m_alloc; + order & m_order; + clause_selection & m_cls_sel; + literal_selection & m_lit_sel; + simplifier & m_simplifier; + unsigned m_time; + unsigned m_scope_lvl; + id_gen m_cls_id_gen; + found_literals m_found_literals; + semantic_tautology m_sem_taut; + asserted_literals m_asserted_literals; + rewriter m_rewriter; + der m_der; + subsumption m_subsumption; + eq_resolution m_eq_resolution; + factoring m_factoring; + superposition m_superposition; + vector m_clauses_to_unfreeze; + vector m_clauses_to_delete; + unsigned_vector m_time_trail; + clause * m_unsat; + ptr_vector m_new_clauses; + + void insert_index(clause * cls); + void erase_index(clause * cls); + + void init_clause(clause * cls); + clause * mk_clause(unsigned num_lits, literal * lits, justification * p, unsigned scope_lvl); + + void del_clause(clause * cls); + void del_clauses(unsigned scope_lvl); + + void freeze_clause_until(clause * cls, unsigned scope_lvl); + void unfreeze_clause(clause * cls); + void unfreeze_clauses(unsigned scope_lvl); + + bool trivial(unsigned num_lits, literal * lits); + bool trivial(clause * cls); + clause * simplify(clause * cls); + void simplify_processed(clause * cls); + bool redundant(clause * cls); + void generate(clause * cls); + void process_new_clause(clause * cls); + + void display(std::ostream & out, vector const & cvs, unsigned scope_lvl, bool frozen) const; + + void set_conflict(clause * cls); + + public: + context(ast_manager & m, order & o, clause_selection & cs, literal_selection & ls, simplifier & s, spc_params & params); + ~context(); + + simplifier & get_simplifier() { return m_simplifier; } + order & get_order() { return m_order; } + ast_manager & get_manager() { return m_manager; } + + unsigned get_scope_lvl() const { return m_scope_lvl; } + + void assert_expr(expr * n, proof * p, unsigned scope_lvl = 0); + void assert_expr(expr * n, justification * p, unsigned scope_lvl = 0); + void saturate(unsigned threshold); + bool inconsistent() const { return m_unsat != 0; } + bool processed_all() const { return m_cls_sel.empty(); } + void push_scope(); + void pop_scope(unsigned num_scopes); + void reset(); + void display(std::ostream & out) const; + void display_statistics(std::ostream & out) const; + }; +}; + +#endif /* _SPC_CONTEXT_H_ */ diff --git a/lib/spc_decl_plugin.cpp b/lib/spc_decl_plugin.cpp new file mode 100644 index 000000000..3ffb0c64d --- /dev/null +++ b/lib/spc_decl_plugin.cpp @@ -0,0 +1,135 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_decl_plugin.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-12. + +Revision History: + +--*/ +#include"spc_decl_plugin.h" + +std::ostream & operator<<(std::ostream & out, spc_op_kind k) { + switch (k) { + case PR_DEMODULATION: out << "demod"; break; + case PR_SPC_REWRITE: out << "rewrite"; break; + case PR_SPC_RESOLUTION: out << "res"; break; + case PR_SUPERPOSITION: out << "sup"; break; + case PR_EQUALITY_RESOLUTION: out << "eq_res"; break; + case PR_FACTORING: out << "fact"; break; + case PR_SPC_DER: out << "der"; break; + case PR_SPC_ASSERTED: out << "asserted"; break; + default: out << "unknown"; break; + } + return out; +} + +spc_decl_plugin::spc_decl_plugin() : + m_demodulation("demod"), + m_spc_rewrite("sp-rw"), + m_spc_resolution("sp-res"), + m_superposition("sp"), + m_equality_resolution("eq-res"), + m_factoring("fact"), + m_spc_der("spc-der") { +} + +spc_decl_plugin::~spc_decl_plugin() { +} + +sort * spc_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const* parameters) { + UNREACHABLE(); + return 0; +} + +func_decl * spc_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + +#define MK_PROOF(SYM) m_manager->mk_func_decl(SYM, arity, domain, m_manager->mk_proof_sort(), func_decl_info(m_family_id, k)) + + SASSERT(num_parameters == 0); + switch (k) { + /* + #1: (forall (x) (= t[x] s[x])) + [demod #1] (= t[a] s[a]) + */ + case PR_DEMODULATION: return MK_PROOF(m_demodulation); + /* + Justifies a rewriting (simplification step) in the superposition engine. + It has n+1 antecedents. The first antecedent is the clause being simplified. + The other antecedents are demodulators. + The consequent is the simplied clause. + */ + case PR_SPC_REWRITE: return MK_PROOF(m_spc_rewrite); + /* + Resolution proof: + + #1: (or C l) + #2: (or D (not l')) + [sp-res #1 #2]: sigma(or C D) + + where sigma is the mgu of l and l' + + */ + case PR_SPC_RESOLUTION: return MK_PROOF(m_spc_resolution); + /* + Superposition proof: + + #1: (or (= s t) R) + #2: D[u] + [sp #1 #2]: sigma(or R D[t]) + + where sigma is the mgu(u, s) + */ + case PR_SUPERPOSITION: return MK_PROOF(m_superposition); + /* + Equality resolution proof: + + #1: (or (not (= s t)) R) + [eq-res #1]: sigma R + + where sigma is the mgu of s and t. + */ + case PR_EQUALITY_RESOLUTION: return MK_PROOF(m_equality_resolution); + /* + Proof object for factoring and equality-factoring: + + #1: (or P[t] P[s] R) + [fact #1]: sigma(or P[t] R) + + where sigma is the mgu(t,s) + + #1: (or (= s t) (= u v) R) + [fact #1]: sigma(or (not (= t v)) (= u v) R) + + where sigma = mgu(s, u) + */ + case PR_FACTORING: return MK_PROOF(m_factoring); + /* + Proof object for destructive equality resolution: + + #1: (or (not (= x t)) C[x]) + [spc-der #1]: C[t] + + t does not contain x. + + Several variables may be eliminated simultaneously. + */ + case PR_SPC_DER: return MK_PROOF(m_spc_der); + default: + UNREACHABLE(); + return 0; + } + +} + + diff --git a/lib/spc_decl_plugin.h b/lib/spc_decl_plugin.h new file mode 100644 index 000000000..ce98233b4 --- /dev/null +++ b/lib/spc_decl_plugin.h @@ -0,0 +1,61 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_decl_plugin.h + +Abstract: + + Proof declarations for Superposition Calculus Engine. + +Author: + + Leonardo de Moura (leonardo) 2008-02-12. + +Revision History: + +--*/ +#ifndef _SPC_DECL_PLUGIN_H_ +#define _SPC_DECL_PLUGIN_H_ + +#include"ast.h" + +enum spc_op_kind { + PR_DEMODULATION, + PR_SPC_REWRITE, + PR_SPC_RESOLUTION, + PR_SUPERPOSITION, + PR_EQUALITY_RESOLUTION, + PR_FACTORING, + PR_SPC_DER, + PR_SPC_ASSERTED, + PR_SPC_LAST_ID +}; + +std::ostream & operator<<(std::ostream & out, spc_op_kind k); + +class spc_decl_plugin : public decl_plugin { + symbol m_demodulation; + symbol m_spc_rewrite; + symbol m_spc_resolution; + symbol m_superposition; + symbol m_equality_resolution; + symbol m_factoring; + symbol m_spc_der; + +public: + spc_decl_plugin(); + + virtual ~spc_decl_plugin(); + + virtual decl_plugin * mk_fresh() { return alloc(spc_decl_plugin); } + + virtual sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const* parameters); + + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); +}; + +#endif /* _SPC_DECL_PLUGIN_H_ */ + diff --git a/lib/spc_der.cpp b/lib/spc_der.cpp new file mode 100644 index 000000000..dbd125ffc --- /dev/null +++ b/lib/spc_der.cpp @@ -0,0 +1,80 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_der.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-17. + +Revision History: + +--*/ +#include"spc_der.h" +#include"occurs.h" + +namespace spc { + + der::der(ast_manager & m): + m_manager(m), + m_subst(m), + m_spc_fid(m.get_family_id("spc")) { + m_subst.reserve_offsets(1); + } + + void der::apply(clause * cls, unsigned j, expr * lhs, expr * rhs) { + TRACE("der", tout << "applying der at: " << j << "\n"; cls->display(tout, m_manager); tout << "\n";); + m_subst.reserve_vars(cls->get_num_vars()); + m_subst.reset(); + m_subst.insert(expr_offset(lhs, 0), expr_offset(rhs, 0)); + literal_buffer new_lits(m_manager); + unsigned num_lits = cls->get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) { + if (i != j) { + literal const & l = cls->get_literal(i); + expr_ref new_atom(m_manager); + m_subst.apply(l.atom(), new_atom); + new_lits.push_back(literal(new_atom, l.sign())); + } + } + justification * js = mk_der_justification(m_manager, m_spc_fid, cls->get_justification(), new_lits.size(), new_lits.c_ptr()); + cls->update_lits(m_manager, new_lits.size(), new_lits.c_ptr(), js); + } + + bool der::apply(clause * cls) { + unsigned num_lits = cls->get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) { + literal const & l = cls->get_literal(i); + if (l.sign() && m_manager.is_eq(l.atom())) { + expr * lhs = l.lhs(); + expr * rhs = l.rhs(); + if (is_var(lhs) && !occurs(lhs, rhs)) { + apply(cls, i, lhs, rhs); + return true; + } + else if (is_var(rhs) && !occurs(rhs, lhs)) { + apply(cls, i, rhs, lhs); + return true; + } + } + } + return false; + } + + /** + \brief Clause cls is destructively updated. + */ + void der::operator()(clause * cls) { + while(apply(cls)) + ; + } + + +}; + diff --git a/lib/spc_der.h b/lib/spc_der.h new file mode 100644 index 000000000..eb434d915 --- /dev/null +++ b/lib/spc_der.h @@ -0,0 +1,52 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_der.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-17. + +Revision History: + +--*/ +#ifndef _SPC_DER_H_ +#define _SPC_DER_H_ + +#include"spc_clause.h" + +namespace spc { + + /** + \brief Functor for applying destructive equality resolution. + This is similar to the Functor in der.h, but this one applies + the simplification on clauses instead of ast's. + + x != s or R + ==> + sigma(R) + + where + sigma = mgu(x, s) + */ + class der { + ast_manager & m_manager; + substitution m_subst; + unsigned_vector m_to_keep; + family_id m_spc_fid; + void apply(clause * cls, unsigned j, expr * lhs, expr * rhs); + bool apply(clause * cls); + public: + der(ast_manager & m); + void operator()(clause * cls); + }; +}; + +#endif /* _SPC_DER_H_ */ + diff --git a/lib/spc_eq_resolution.cpp b/lib/spc_eq_resolution.cpp new file mode 100644 index 000000000..df1e0ca69 --- /dev/null +++ b/lib/spc_eq_resolution.cpp @@ -0,0 +1,44 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_eq_resolution.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-14. + +Revision History: + +--*/ +#include"spc_eq_resolution.h" + +namespace spc { + + /** + \brief Apply equality resolution rule on the given clause. + Store the produced clauses in new_clauses. + */ + void eq_resolution::operator()(clause * cls, ptr_vector & new_clauses) { + m_subst.reserve_vars(cls->get_num_vars()); + unsigned num = cls->get_num_literals(); + for (unsigned i = 0; i < num; i++) { + literal const & l = cls->get_literal(i); + expr * atom = l.atom(); + if (l.sign() && m_manager.is_eq(atom)) { + expr * lhs = to_app(atom)->get_arg(0); + expr * rhs = to_app(atom)->get_arg(1); + m_subst.reset(); + if (m_unifier(lhs, rhs, m_subst, false) && cls->is_eligible_for_resolution(m_order, l, 0, &m_subst)) { + m_stats.m_num_eq_resolution++; + new_clauses.push_back(mk_result(cls, i)); + } + } + } + } +}; diff --git a/lib/spc_eq_resolution.h b/lib/spc_eq_resolution.h new file mode 100644 index 000000000..a73987559 --- /dev/null +++ b/lib/spc_eq_resolution.h @@ -0,0 +1,50 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_eq_resolution.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-14. + +Revision History: + +--*/ +#ifndef _SPC_EQ_RESOLUTION_H_ +#define _SPC_EQ_RESOLUTION_H_ + +#include"spc_unary_inference.h" +#include"spc_statistics.h" + +namespace spc { + + /** + \brief Functor for applying equality resolution. + + s != t or R + ==> + sigma(R) + */ + class eq_resolution : public unary_inference { + protected: + statistics & m_stats; + family_id m_spc_fid; + virtual justification * mk_justification(justification * parent, unsigned num_lits, literal * new_lits) { + return mk_eq_res_justification(m_manager, m_spc_fid, parent, num_lits, new_lits); + } + public: + eq_resolution(ast_manager & m, order & ord, statistics & stats):unary_inference(m, ord), m_stats(stats), m_spc_fid(m.get_family_id("spc")) {} + virtual ~eq_resolution() {} + void operator()(clause * cls, ptr_vector & new_clauses); + }; +}; + + +#endif /* _SPC_EQ_RESOLUTION_H_ */ + diff --git a/lib/spc_factoring.cpp b/lib/spc_factoring.cpp new file mode 100644 index 000000000..9f2a4ab9f --- /dev/null +++ b/lib/spc_factoring.cpp @@ -0,0 +1,156 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_factoring.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-14. + +Revision History: + +--*/ +#include"spc_factoring.h" + +namespace spc { + + /** + \brief Create a new clause by removing literal at position j, apply substitution m_subst, + and adding a disequality lhs != rhs. + */ + clause * factoring::mk_eq_fact_result(clause * cls, unsigned j, expr * lhs, expr * rhs) { + sbuffer new_literals; + + expr_ref new_eq(m_manager.mk_eq(lhs, rhs), m_manager); + expr_ref new_eq_after_subst(m_manager); + m_subst.apply(new_eq, new_eq_after_subst); + new_literals.push_back(literal(new_eq_after_subst, true)); + + unsigned num = cls->get_num_literals(); + for (unsigned i = 0; i < num; i++) { + if (i != j) { + literal const & l = cls->get_literal(i); + expr_ref new_atom(m_manager); + m_subst.apply(l.atom(), new_atom); + new_literals.push_back(literal(new_atom, l.sign())); + } + } + + justification * js = mk_factoring_justification(m_manager, m_spc_fid, cls->get_justification(), new_literals.size(), + new_literals.c_ptr()); + clause * new_cls = clause::mk(m_manager, new_literals.size(), new_literals.c_ptr(), js, cls->get_scope_lvl()); + m_stats.m_num_eq_factoring++; + return new_cls; + } + + /** + \brief Try to apply equality factoring using the eq literal stored at position j. + Assume lhs and rhs are the left hand side of this equality (they may be swapped). + */ + void factoring::try_eq_factoring(clause * cls, unsigned j, expr * lhs, expr * rhs, ptr_vector & new_clauses) { + literal const & l1 = cls->get_literal(j); + sort * s = m_manager.get_sort(lhs); + unsigned num_lits = cls->get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) { + literal const & l2 = cls->get_literal(i); + if (i == j) + continue; + if (l2.sign()) + continue; + expr * atom = l2.atom(); + if (!m_manager.is_eq(atom)) + continue; + expr * lhs2 = to_app(atom)->get_arg(0); + if (m_manager.get_sort(lhs2) != s) + continue; + expr * rhs2 = to_app(atom)->get_arg(1); + m_subst.reset(); + if (m_unifier(lhs, lhs2, m_subst, false) && + (l1.is_oriented() || !m_order.greater(rhs, lhs, &m_subst)) && + cls->is_eligible_for_paramodulation(m_order, l1, 0, &m_subst)) { + new_clauses.push_back(mk_eq_fact_result(cls, j, rhs, rhs2)); + } + m_subst.reset(); + if (m_unifier(lhs, rhs2, m_subst, false) && + (l1.is_oriented() || !m_order.greater(rhs, lhs, &m_subst)) && + cls->is_eligible_for_paramodulation(m_order, l1, 0, &m_subst)) { + new_clauses.push_back(mk_eq_fact_result(cls, j, rhs, lhs2)); + } + } + } + + /** + \brief Try to apply equality factoring using the eq literal stored at position i. + */ + void factoring::try_eq_factoring(clause * cls, unsigned i, ptr_vector & new_clauses) { + if (cls->get_num_pos_literals() <= 1) + return; + literal const & l = cls->get_literal(i); + app * eq = to_app(l.atom()); + expr * lhs = eq->get_arg(0); + expr * rhs = eq->get_arg(1); + if (l.is_oriented()) { + if (!l.is_left()) + std::swap(lhs, rhs); + try_eq_factoring(cls, i, lhs, rhs, new_clauses); + } + else { + try_eq_factoring(cls, i, lhs, rhs, new_clauses); + try_eq_factoring(cls, i, rhs, lhs, new_clauses); + } + } + + /** + \brief Try to apply (ordering) factoring rule. + */ + void factoring::try_factoring(clause * cls, unsigned j, ptr_vector & new_clauses) { + literal const & l1 = cls->get_literal(j); + if (l1.sign() && cls->get_num_neg_literals() <= 1) + return; + if (!l1.sign() && cls->get_num_pos_literals() <= 1) + return; + unsigned num = cls->get_num_literals(); + for (unsigned i = 0; i < num; i++) { + if (i == j) + continue; + literal const & l2 = cls->get_literal(i); + if (l1.sign() != l2.sign()) + continue; + m_subst.reset(); + if (m_unifier(l1.atom(), l2.atom(), m_subst, false) && + cls->is_eligible_for_resolution(m_order, l1, 0, &m_subst)) { + new_clauses.push_back(mk_result(cls, i)); + m_stats.m_num_factoring++; + } + } + } + + /** + \brief Apply factoring rule on the given clause. + Store the produced clauses into new_clauses. + */ + void factoring::operator()(clause * cls, ptr_vector & new_clauses) { + if (cls->get_num_pos_literals() <= 1 && cls->get_num_neg_literals() <= 1) + return; + + m_subst.reserve_vars(cls->get_num_vars()); + unsigned num = cls->get_num_literals(); + for (unsigned i = 0; i < num; i++) { + literal const & l = cls->get_literal(i); + expr * atom = l.atom(); + // remark: if the clause has selected literals then the literal will not be eligible + // for paramodulation and eq_resolution will not be applied. + if (!l.sign() && m_manager.is_eq(atom) && !cls->has_sel_lit()) + try_eq_factoring(cls, i, new_clauses); + if (l.is_selected() || !cls->has_sel_lit()) + try_factoring(cls, i, new_clauses); + } + } + +}; diff --git a/lib/spc_factoring.h b/lib/spc_factoring.h new file mode 100644 index 000000000..09e944f6d --- /dev/null +++ b/lib/spc_factoring.h @@ -0,0 +1,66 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_factoring.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-14. + +Revision History: + +--*/ +#ifndef _SPC_FACTORING_H_ +#define _SPC_FACTORING_H_ + +#include"spc_unary_inference.h" +#include"spc_statistics.h" + +namespace spc { + + /** + \brief Functor for applying factoring. + + - Equality Factoring + s = t or u = v or R + ==> + sigma(t != v or u = v or R) + + sigma = mgu(s, u) + sigma(s) not greater than sigma(t) + sigma(s = t) is eligible for paramodulation. + + - Factoring + P(t) or P(s) or R + ==> + sigma(P(t) or R) + + sigma = mgu(t,s) + sigma(P(t)) is eligible for resolution. + */ + class factoring : public unary_inference { + protected: + statistics & m_stats; + family_id m_spc_fid; + virtual justification * mk_justification(justification * parent, unsigned num_lits, literal * new_lits) { + return mk_factoring_justification(m_manager, m_spc_fid, parent, num_lits, new_lits); + } + clause * mk_eq_fact_result(clause * cls, unsigned j, expr * lhs, expr * rhs); + void try_eq_factoring(clause * cls, unsigned j, expr * lhs, expr * rhs, ptr_vector & new_clauses); + void try_eq_factoring(clause * cls, unsigned i, ptr_vector & new_clauses); + void try_factoring(clause * cls, unsigned j, ptr_vector & new_clauses); + public: + factoring(ast_manager & m, order & ord, statistics & stats):unary_inference(m, ord), m_stats(stats), m_spc_fid(m.get_family_id("spc")) {} + virtual ~factoring() {} + void operator()(clause * cls, ptr_vector & new_clauses); + }; +}; + +#endif /* _SPC_FACTORING_H_ */ + diff --git a/lib/spc_justification.cpp b/lib/spc_justification.cpp new file mode 100644 index 000000000..7e83a7fc3 --- /dev/null +++ b/lib/spc_justification.cpp @@ -0,0 +1,184 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_justification.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-08. + +Revision History: + +--*/ +#include"spc_justification.h" +#include"spc_clause.h" +#include"marker.h" + +namespace spc { + + void get_justification_stat(justification * p, justification_stat & stat) { + // Remark: justification objects that are not associated + // with clauses may be shared. That is, they may be parent of + // several different justification objects. + marker m; + ptr_buffer todo; + todo.push_back(p); + while (!todo.empty()) { + justification * p = todo.back(); + todo.pop_back(); + if (!m.is_marked(p)) { + m.mark(p); + clause * cls = p->get_clause(); + if (cls) { + if (cls->get_proof_depth() > stat.m_proof_depth) + stat.m_proof_depth = cls->get_proof_depth(); + if (cls->get_scope_lvl() > stat.m_max_scope_lvl) + stat.m_max_scope_lvl = cls->get_scope_lvl(); + stat.m_parent_clauses.push_back(cls); + } + else { + p->get_parents(todo); + } + } + } + } + + void justification::display(std::ostream & out) { + out << "[" << get_rule_id(); + ptr_buffer ps; + get_parents(ps); + unsigned sz = ps.size(); + for (unsigned i = 0; i < sz; i++) { + out << " "; + justification * js = ps[i]; + clause * cls = js->get_clause(); + if (cls) + out << "#" << cls->get_id(); + else + js->display(out); + } + out << "]"; + } + + justification * justification_proof_wrapper::mk(proof * p, ast_manager & m) { + void * mem = m.get_allocator().allocate(sizeof(justification_proof_wrapper)); + return new (mem) justification_proof_wrapper(p, m); + } + + proof * justification_proof_wrapper::get_proof() const { + return m_proof; + } + + unsigned justification_proof_wrapper::del_eh(ast_manager & m) { + m.dec_ref(m_proof); + return sizeof(justification_proof_wrapper); + } + + void dec_ref(justification * p, ast_manager & m) { + if (p->dec_ref()) { + ptr_buffer to_delete; + ptr_buffer parents; + to_delete.push_back(p); + while (!to_delete.empty()) { + justification * p = to_delete.back(); + to_delete.pop_back(); + SASSERT(p->get_ref_count() == 0); + parents.reset(); + p->get_parents(parents); + ptr_buffer::iterator it = parents.begin(); + ptr_buffer::iterator end = parents.end(); + for (; it != end; ++it) { + justification * parent = *it; + if (parent->dec_ref()) + to_delete.push_back(parent); + } + unsigned sz = p->del_eh(m); + p->~justification(); + m.get_allocator().deallocate(sz, p); + } + } + } + + /** + \brief Return a proof for a new clause formed by the literals lits[0] ... lits[num_lits - 1]. + This clause was produced using a main clause C, where the proof of C is \c main_pr, + and the auxiliary proofs auxs[0] ... aux[num_auxs-1]. + + \remark If fine_grain_proofs() is false, then 0 is returned. + */ + proof * mk_proof(ast_manager & m, family_id spc_fid, spc_op_kind pid, unsigned num_lits, literal * lits, proof * main_pr, + unsigned num_auxs, proof * const * auxs) { + if (m.fine_grain_proofs()) { + expr * new_fact_body = mk_or(m, num_lits, lits); + + SASSERT(main_pr); + SASSERT(m.has_fact(main_pr)); + expr * fact = m.get_fact(main_pr); + expr * new_fact = 0; + if (is_quantifier(fact)) + new_fact = m.update_quantifier(to_quantifier(fact), new_fact_body); + else + new_fact = new_fact_body; + + ptr_buffer args; + args.push_back(main_pr); + args.append(num_auxs, (expr**) auxs); + args.push_back(new_fact); + + return m.mk_app(spc_fid, pid, args.size(), args.c_ptr()); + } + return 0; + } + + justification * rewrite_justification::mk(ast_manager & m, justification * head, + unsigned num_demodulators, justification * const * demodulators, proof * pr) { + void * mem = m.get_allocator().allocate(get_obj_size(num_demodulators, m.fine_grain_proofs())); + return new (mem) rewrite_justification(m, head, num_demodulators, demodulators, pr); + } + + rewrite_justification::rewrite_justification(ast_manager & m, justification * head, + unsigned num_demodulators, justification * const * demodulators, proof * pr): + m_num_demodulators(num_demodulators) { + SASSERT(m.fine_grain_proofs() == (pr != 0)); + m_fields[0] = head; + head->inc_ref(); + for (unsigned i = 0; i < num_demodulators; i++) { + m_fields[i+1] = demodulators[i]; + demodulators[i]->inc_ref(); + } + if (m.fine_grain_proofs()) { + SASSERT(pr); + m_fields[num_demodulators+1] = pr; + m.inc_ref(pr); + } + } + + void rewrite_justification::get_parents(ptr_buffer & parents) { + unsigned num_parents = m_num_demodulators+1; + for (unsigned i = 0; i < num_parents; i++) + parents.push_back(reinterpret_cast(m_fields[i])); + } + + proof * rewrite_justification::get_proof() const { + return reinterpret_cast(m_fields[m_num_demodulators+1]); + } + + unsigned rewrite_justification::del_eh(ast_manager & m) { + if (m.fine_grain_proofs()) { + m.dec_ref(reinterpret_cast(m_fields[m_num_demodulators+1])); + return get_obj_size(m_num_demodulators, true); + } + return get_obj_size(m_num_demodulators, false); + } + + proof * mk_rewrite_proof(ast_manager & m, family_id spc_fid, unsigned num_lits, literal * lits, proof * main_pr, + unsigned num_auxs, proof * const * auxs) { + return mk_proof(m, spc_fid, PR_SPC_REWRITE, num_lits, lits, main_pr, num_auxs, auxs); + } +}; diff --git a/lib/spc_justification.h b/lib/spc_justification.h new file mode 100644 index 000000000..ba8827822 --- /dev/null +++ b/lib/spc_justification.h @@ -0,0 +1,337 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_justification.h + +Abstract: + + Proof-like objects for tracking dependencies in the superposition + calculus engine, and generating proofs. + +Author: + + Leonardo de Moura (leonardo) 2008-02-02. + +Revision History: + +--*/ +#ifndef _SPC_JUSTIFICATION_H_ +#define _SPC_JUSTIFICATION_H_ + +#include"ast.h" +#include"spc_literal.h" +#include"spc_decl_plugin.h" + +namespace spc { + + class clause; + + /** + \brief Proof-like object use to track dependencies and produce + proofs. + + \remark All justification objects must be allocated using the + small_object_allocator in ast_manager. + */ + class justification { + clause * m_owner; + unsigned m_ref_count:30; + unsigned m_mark:1; + unsigned m_assumption:1; + + friend class clause; + void set_owner(clause * cls) { m_owner = cls; } + + public: + justification(bool assumption = false): + m_owner(0), + m_ref_count(0), + m_mark(false), + m_assumption(assumption) { + } + + virtual ~justification() {} + + void inc_ref() { + m_ref_count++; + } + + bool dec_ref() { + SASSERT(m_ref_count > 0); + m_ref_count--; + return m_ref_count == 0; + } + + unsigned get_ref_count() { + return m_ref_count; + } + + void set_mark(bool f) { m_mark = f; } + + bool is_marked() const { return m_mark; } + + /** + \brief Return the clause justified by this object. + + \remark for some justification objects that clause is + supressed. Example: intermediate steps. + */ + clause * get_clause() { return m_owner; } + + /** + \brief Return the expr justified by this object. + This method returns a non null value only when + proof generation is enabled. + */ + virtual expr * get_expr(ast_manager & m) { return 0; } + + /** + \brief Return a non-zero value if the justification + is wrapping a proof object. + */ + virtual proof * get_proof() const { return 0; } + + /** + \brief Return the parent justifications. + */ + virtual void get_parents(ptr_buffer & parents) {} + + /** + \brief Return the name of the rule used. + */ + virtual spc_op_kind get_rule_id() = 0; + + /** + \brief Return true if the justification is an external assumption. + */ + bool assumption() const { return m_assumption; } + + void display(std::ostream & out); + + /** + \brief This method is invoked before the object is deleted. + Return the amount of memory consumed by this object. + */ + virtual unsigned del_eh(ast_manager & m) = 0; + }; + + struct justification_stat { + unsigned m_proof_depth; + unsigned m_max_scope_lvl; + ptr_buffer m_parent_clauses; + justification_stat(): + m_proof_depth(0), + m_max_scope_lvl(0) { + } + }; + + void get_justification_stat(justification * p, justification_stat & stat); + + void dec_ref(justification * p, ast_manager & m); + + /** + \brief Smart pointer for justification objects. + */ + class justification_ref { + justification * m_obj; + ast_manager & m_manager; + void inc_ref() { if (m_obj) m_obj->inc_ref(); } + void dec_ref() { if (m_obj) spc::dec_ref(m_obj, m_manager); } + public: + justification_ref(ast_manager & m):m_obj(0), m_manager(m) {} + justification_ref(justification * j, ast_manager & m): + m_obj(j), m_manager(m) { + inc_ref(); + } + ~justification_ref() { + dec_ref(); + } + operator justification*() const { return m_obj; } + operator bool() const { return m_obj != 0; } + bool operator!() const { return m_obj == 0; } + justification * operator->() const { return m_obj; } + justification const & operator*() const { return *m_obj; } + justification_ref & operator=(justification * n) { + if (n) + n->inc_ref(); + dec_ref(); + m_obj = n; + return *this; + } + justification_ref & operator=(justification_ref & n) { + SASSERT(&m_manager == &n.m_manager); + n.inc_ref(); + dec_ref(); + m_obj = n.m_obj; + return *this; + } + }; + + class justification_proof_wrapper : public justification { + proof * m_proof; + justification_proof_wrapper(proof * p, ast_manager & m):m_proof(p) { m.inc_ref(m_proof); } + public: + static justification * mk(proof * p, ast_manager & m); + virtual ~justification_proof_wrapper() {} + virtual proof * get_proof() const; + virtual spc_op_kind get_rule_id() { return PR_SPC_ASSERTED; }; + virtual unsigned del_eh(ast_manager & m); + }; + + proof * mk_proof(ast_manager & m, family_id spc_fid, spc_op_kind pid, unsigned num_lits, literal * lits, proof * main_pr, unsigned num_auxs, + proof * const * auxs); + + /** + \brief Justification for rewriting steps: demodulation, duplicate literal deletion, resolved literal deletion. + */ + class rewrite_justification : public justification { + unsigned m_num_demodulators; + void * m_fields[0]; + static unsigned get_obj_size(unsigned num_demodulators, bool fine_grain) { + return sizeof(rewrite_justification) + (num_demodulators + (fine_grain ? 2 : 1)) * sizeof(void *); + } + rewrite_justification(ast_manager & m, justification * head, + unsigned num_demodulators, justification * const * demodulators, proof * pr); + public: + static justification * mk(ast_manager & m, justification * head, + unsigned num_demodulators, justification * const * demodulators, proof * pr = 0); + virtual ~rewrite_justification() {} + virtual proof * get_proof() const; + virtual spc_op_kind get_rule_id() { return PR_SPC_REWRITE; } + virtual void get_parents(ptr_buffer & parents); + virtual unsigned del_eh(ast_manager & m); + }; + + proof * mk_rewrite_proof(ast_manager & m, family_id spc_fid, unsigned num_lits, literal * lits, proof * main_pr, unsigned num_auxs, + proof * const * auxs); + + template + class unary_justification : public justification { + protected: + justification * m_parent; + proof * m_proof; + + unary_justification(ast_manager & m, justification * p, proof * pr): + m_parent(p), + m_proof(pr) { + p->inc_ref(); + SASSERT(m.fine_grain_proofs() == (pr != 0)); + if (m.fine_grain_proofs()) + m.inc_ref(pr); + } + + public: + virtual proof * get_proof() const { + return m_proof; + } + + virtual void get_parents(ptr_buffer & parents) { + parents.push_back(m_parent); + } + + virtual unsigned del_eh(ast_manager & m) { + if (m.fine_grain_proofs()) + m.dec_ref(m_proof); + return sizeof(unary_justification); + } + + virtual spc_op_kind get_rule_id() { + return Kind; + } + + static justification * mk(ast_manager & m, family_id spc_fid, justification * p, unsigned num_lits, literal * lits) { + proof * pr = 0; + if (m.fine_grain_proofs()) + pr = mk_proof(m, spc_fid, Kind, num_lits, lits, p->get_proof(), 0, 0); + void * mem = m.get_allocator().allocate(sizeof(unary_justification)); + return new (mem) unary_justification(m, p, pr); + } + }; + + inline justification * mk_eq_res_justification(ast_manager & m, family_id spc_fid, justification * p, unsigned num_lits, literal * lits) { + return unary_justification::mk(m, spc_fid, p, num_lits, lits); + } + + inline justification * mk_factoring_justification(ast_manager & m, family_id spc_fid, justification * p, unsigned num_lits, literal * lits) { + return unary_justification::mk(m, spc_fid, p, num_lits, lits); + } + + inline justification * mk_der_justification(ast_manager & m, family_id spc_fid, justification * p, unsigned num_lits, literal * lits) { + return unary_justification::mk(m, spc_fid, p, num_lits, lits); + } + + template + class binary_justification : public justification { + protected: + justification * m_parent1; + justification * m_parent2; + proof * m_proof; + + binary_justification(ast_manager & m, justification * p1, justification * p2, proof * pr): + m_parent1(p1), + m_parent2(p2), + m_proof(pr) { + p1->inc_ref(); + p2->inc_ref(); + SASSERT(m.fine_grain_proofs() == (pr != 0)); + if (m.fine_grain_proofs()) + m.inc_ref(pr); + } + + public: + virtual proof * get_proof() const { + return m_proof; + } + + virtual void get_parents(ptr_buffer & parents) { + parents.push_back(m_parent1); + parents.push_back(m_parent2); + } + + virtual unsigned del_eh(ast_manager & m) { + if (m.fine_grain_proofs()) + m.dec_ref(m_proof); + return sizeof(binary_justification); + } + + virtual spc_op_kind get_rule_id() { + return Kind; + } + + static justification * mk(ast_manager & m, family_id spc_fid, justification * p1, justification * p2, unsigned num_lits, literal * lits, + unsigned num_vars, var * const * vars) { + proof * pr = 0; + if (m.fine_grain_proofs()) { + ptr_buffer sorts; + sbuffer names; + for (unsigned i = 0; i < num_vars; i++) { + sorts.push_back(vars[num_vars - i - 1]->get_sort()); + names.push_back(symbol(num_vars - i - 1)); + } + expr * body = mk_or(m, num_lits, lits); + expr * new_fact = 0; + if (num_vars == 0) + new_fact = body; + else + new_fact = m.mk_forall(sorts.size(), sorts.c_ptr(), names.c_ptr(), body); + pr = m.mk_app(spc_fid, Kind, p1->get_proof(), p2->get_proof(), new_fact); + } + void * mem = m.get_allocator().allocate(sizeof(binary_justification)); + return new (mem) binary_justification(m, p1, p2, pr); + } + }; + + inline justification * mk_superposition_justification(ast_manager & m, family_id spc_fid, justification * p1, justification * p2, + unsigned num_lits, literal * lits, unsigned num_vars, var * const * vars) { + return binary_justification::mk(m, spc_fid, p1, p2, num_lits, lits, num_vars, vars); + } + + inline justification * mk_resolution_justification(ast_manager & m, family_id spc_fid, justification * p1, justification * p2, + unsigned num_lits, literal * lits, unsigned num_vars, var * const * vars) { + return binary_justification::mk(m, spc_fid, p1, p2, num_lits, lits, num_vars, vars); + } +}; + +#endif /* _SPC_JUSTIFICATION_H_ */ diff --git a/lib/spc_literal.cpp b/lib/spc_literal.cpp new file mode 100644 index 000000000..f2bdd0e9e --- /dev/null +++ b/lib/spc_literal.cpp @@ -0,0 +1,432 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_literal.cpp + +Abstract: + + Superposition Calculus literal + +Author: + + Leonardo de Moura (leonardo) 2008-02-02. + +Revision History: + +--*/ +#include"spc_literal.h" +#include"ast_pp.h" + +namespace spc { + + void literal::try_to_orient(order & o) { + ast_manager & m = o.get_manager(); + if (!m_sign && m.is_eq(m_atom)) { + expr * lhs = to_app(m_atom)->get_arg(0); + expr * rhs = to_app(m_atom)->get_arg(1); + TRACE("spc_orient", tout << "trying to orient:\n" << mk_pp(lhs, m) << "\n" << mk_pp(rhs, m) << "\n";); + switch (o.compare(lhs, rhs)) { + case order::GREATER: + m_oriented = true; + m_left = true; + TRACE("spc_orient", tout << "greater\n";); + return; + case order::LESSER: + m_oriented = true; + m_left = false; + TRACE("spc_orient", tout << "smaller\n";); + return; + default: + return; + } + } + } + + void literal::get_stat(literal_stat & stat) { + get_expr_stat(m_atom, stat); + m_stats = true; + m_ground = stat.m_ground; + m_sym_count = stat.m_sym_count > SYM_COUNT_MAX ? SYM_COUNT_MAX : stat.m_sym_count; + m_depth = stat.m_depth > DEPTH_MAX ? DEPTH_MAX : stat.m_depth; + m_const_count = stat.m_const_count > CONST_COUNT_MAX ? CONST_COUNT_MAX : stat.m_const_count; + } + + expr * literal::to_expr(ast_manager & m) const { + if (is_true(m)) + return m.mk_true(); + else if (is_false(m)) + return m.mk_false(); + else if (m_sign) + return m.mk_not(m_atom); + else + return m_atom; + } + + void literal::display(std::ostream & out, ast_manager & m, bool detailed) const { + pp_params p; + p.m_pp_single_line = true; + + if (m_sign) + out << "(not "; + + if (m_oriented) { + expr * lhs = to_app(m_atom)->get_arg(0); + expr * rhs = to_app(m_atom)->get_arg(1); + if (!m_left) + std::swap(lhs, rhs); + out << "(-> "; + ast_pp(out, lhs, m, p); + out << " "; + ast_pp(out, rhs, m, p); + out << ")"; + } + else { + ast_pp(out, m_atom, m, p); + } + + if (m_sign) + out << ")"; + + if (detailed && m_stats) { + out << "[" << m_ground << ", " << m_depth << ", " << m_sym_count << ", " << m_const_count << "]"; + } + + if (m_selected) + out << "$"; + if (m_p_indexed) + out << "!"; + if (m_r_indexed) + out << "@"; + } + + void display(std::ostream & out, unsigned num_lists, literal * lits, ast_manager & m, bool detailed) { + for (unsigned i = 0; i < num_lists; i++) { + if (i > 0) out << " "; + lits[i].display(out, m, detailed); + } + } + + /** + \brief Given an eq literal store in lhs and rhs the left and right hand sides. If they can be oriented + given the substitution s, then return true, and make lhs the maximal one. + */ + bool can_orient(order & o, literal const & l, unsigned offset, substitution * s, expr * & lhs, expr * & rhs) { + SASSERT(o.get_manager().is_eq(l.atom())); + lhs = l.lhs(); + rhs = l.rhs(); + if (l.is_oriented()) { + if (!l.is_left()) + std::swap(lhs, rhs); + return true; + } + else { + order::result comp = o.compare(lhs, rhs, offset, s); + if (comp == order::GREATER) + return true; + else if (comp == order::LESSER) { + std::swap(lhs, rhs); + return true; + } + return false; + } + } + + /** + \brief Compare literal signs. Negative sign is bigger than the positive one. + */ + inline order::result compare_signs(bool sign1, bool sign2) { + if (sign1 && !sign2) + return order::GREATER; + else if (!sign1 && sign2) + return order::LESSER; + else + return order::EQUAL; + } + + /** + \brief Compare two literals (modulo a substitution) using the given term ordering. + */ + order::result compare(order & o, literal const & l1, literal const & l2, unsigned offset, substitution * s) { + ast_manager & m = o.get_manager(); + expr * n1 = l1.atom(); + expr * n2 = l2.atom(); + bool is_eq1 = m.is_eq(n1); + bool is_eq2 = m.is_eq(n2); + if (is_eq1 && is_eq2) { + expr * lhs1 = 0; + expr * rhs1 = 0; + expr * lhs2 = 0; + expr * rhs2 = 0; + bool oriented1 = can_orient(o, l1, offset, s, lhs1, rhs1); + bool oriented2 = can_orient(o, l2, offset, s, lhs2, rhs2); + if (oriented1) { + // equation 1 can be oriented + if (oriented2) { + // equation 2 can be oriented + // both equations are oriented + SASSERT(oriented1); + SASSERT(oriented2); + order::result r = o.compare(lhs1, lhs2, offset, s); + if (r == order::EQUAL) { + if (l1.pos()) { + if (l2.pos()) + return o.compare(rhs1, rhs2, offset, s); + else + return order::LESSER; + } + else { + if (l2.pos()) + return order::GREATER; + else + return o.compare(rhs1, rhs2, offset, s); + } + } + return r; + } + else { + // equation 2 cannot be oriented + SASSERT(oriented1); + SASSERT(!oriented2); + SASSERT(o.compare(lhs1, rhs1, offset, s) == order::GREATER); + if (o.equal(lhs1, lhs2, offset, s)) { + order::result r = o.compare(rhs1, rhs2, offset, s); + if (r == order::EQUAL) + return compare_signs(l1.sign(), l2.sign()); + return r; + } + if (o.equal(lhs1, rhs2, offset, s)) { + order::result r = o.compare(rhs1, lhs2, offset, s); + if (r == order::EQUAL) + return compare_signs(l1.sign(), l2.sign()); + return r; + } + order::result lhs1_lhs2 = o.compare(lhs1, lhs2, offset, s); + order::result lhs1_rhs2 = o.compare(lhs1, rhs2, offset, s); + if (lhs1_lhs2 == lhs1_rhs2) + return lhs1_lhs2; + order::result rhs1_rhs2 = o.compare(rhs1, rhs2, offset, s); + if (lhs1_lhs2 == rhs1_rhs2) + return lhs1_lhs2; + if (lhs1_rhs2 == order::LESSER && rhs1_rhs2 == order::LESSER) + return order::LESSER; + order::result rhs1_lhs2 = o.compare(rhs1, lhs2, offset, s); + if (lhs1_lhs2 == order::LESSER && rhs1_lhs2 == order::LESSER) + return order::LESSER; + return order::UNCOMPARABLE; + } + } + else { + // equation 1 cannot be oriented + if (oriented2) { + SASSERT(!oriented1); + SASSERT(oriented2); + // equation 2 can be oriented + if (o.equal(lhs1, lhs2, offset, s)) { + order::result r = o.compare(rhs1, rhs2, offset, s); + if (r == order::EQUAL) + return compare_signs(l1.sign(), l2.sign()); + return r; + } + if (o.equal(rhs1, lhs2, offset, s)) { + order::result r = o.compare(lhs1, rhs2, offset, s); + if (r == order::EQUAL) + return compare_signs(l1.sign(), l2.sign()); + return r; + } + order::result lhs1_lhs2 = o.compare(lhs1, lhs2, offset, s); + order::result rhs1_lhs2 = o.compare(rhs1, lhs2, offset, s); + if (lhs1_lhs2 == rhs1_lhs2) + return lhs1_lhs2; + order::result rhs1_rhs2 = o.compare(rhs1, rhs2, offset, s); + if (lhs1_lhs2 == rhs1_rhs2) + return lhs1_lhs2; + if (rhs1_lhs2 == order::GREATER && rhs1_rhs2 == order::GREATER) + return order::GREATER; + order::result lhs1_rhs2 = o.compare(lhs1, rhs2, offset, s); + if (lhs1_lhs2 == order::GREATER && lhs1_rhs2 == order::GREATER) + return order::GREATER; + return order::UNCOMPARABLE; + } + else { + SASSERT(!oriented1); + SASSERT(!oriented2); + if (o.equal(lhs1, lhs2, offset, s)) { + order::result r = o.compare(rhs1, rhs2, offset, s); + if (r == order::EQUAL) + return compare_signs(l1.sign(), l2.sign()); + return r; + } + if (o.equal(rhs1, lhs2, offset, s)) { + order::result r = o.compare(lhs1, rhs2, offset, s); + if (r == order::EQUAL) + return compare_signs(l1.sign(), l2.sign()); + return r; + } + if (o.equal(lhs1, rhs2, offset, s)) { + order::result r = o.compare(rhs1, lhs2, offset, s); + if (r == order::EQUAL) + return compare_signs(l1.sign(), l2.sign()); + return r; + } + if (o.equal(rhs1, rhs2, offset, s)) { + order::result r = o.compare(lhs1, lhs2, offset, s); + if (r == order::EQUAL) + return compare_signs(l1.sign(), l2.sign()); + return r; + } + + order::result r; + order::result aux; + switch (o.compare(lhs1, lhs2, offset, s)) { + case order::GREATER: + r = o.compare(lhs1, rhs2, offset, s); + if (r == order::GREATER) + return order::GREATER; + aux = o.compare(rhs1, rhs2, offset, s); + if (aux == order::GREATER) + return order::GREATER; + if (r == order::LESSER && aux == order::LESSER) + return order::LESSER; + SASSERT(r != order::EQUAL); + SASSERT(aux != order::EQUAL); + return order::UNCOMPARABLE; + case order::LESSER: + r = o.compare(rhs1, lhs2, offset, s); + if (r == order::LESSER) + return order::LESSER; + aux = o.compare(rhs1, rhs2, offset, s); + if (aux == order::LESSER) + return order::LESSER; + if (r == order::GREATER && aux == order::GREATER) + return order::GREATER; + SASSERT(r != order::EQUAL); + SASSERT(aux != order::EQUAL); + return order::UNCOMPARABLE; + case order::EQUAL: + UNREACHABLE(); + return order::UNKNOWN; + default: + switch (o.compare(lhs1, rhs2, offset, s)) { + case order::GREATER: + if (o.compare(rhs1, lhs2, offset, s) == order::GREATER) + return order::GREATER; + return order::UNCOMPARABLE; + case order::LESSER: + if (o.compare(rhs1, lhs2, offset, s) == order::LESSER || + o.compare(rhs1, rhs2, offset, s) == order::LESSER) + return order::LESSER; + return order::UNCOMPARABLE; + case order::EQUAL: + UNREACHABLE(); + return order::UNKNOWN; + default: + if (o.compare(rhs1, lhs2, offset, s) == order::GREATER && + o.compare(rhs1, rhs2, offset, s) == order::GREATER) + return order::GREATER; + return order::UNCOMPARABLE; + } + } + } + } + } + else if (is_eq1) { + expr * lhs1 = l1.lhs(); + expr * rhs1 = l1.rhs(); + if (l1.is_oriented() && !l1.is_left()) + std::swap(lhs1, rhs1); + order::result r = o.compare(lhs1, n2, offset, s); + if (!l1.is_oriented() || r != order::GREATER) { + order::result r2 = o.compare(rhs1, n2, offset, s); + if (r2 == order::GREATER) + return order::GREATER; + else if (r != r2) + return order::UNCOMPARABLE; + } + return r; + } + else if (is_eq2) { + expr * lhs2 = l2.lhs(); + expr * rhs2 = l2.rhs(); + if (l2.is_oriented() && !l2.is_left()) + std::swap(lhs2, rhs2); + order::result r = o.compare(n1, lhs2, offset, s); + if (!l1.is_oriented() || r != order::LESSER) { + order::result r2 = o.compare(n1, rhs2, offset, s); + if (r2 == order::LESSER) + return order::LESSER; + else if (r != r2) + return order::UNCOMPARABLE; + } + return r; + } + else { + order::result r = o.compare(n1, n2, offset, s); + if (r == order::EQUAL) + return compare_signs(l1.sign(), l2.sign()); + return r; + } + } + + bool greater(order & o, literal const & l1, literal const & l2, unsigned offset, substitution * s) { + order::result r = compare(o, l1, l2, offset, s); + TRACE("literal_order", ast_manager & m = o.get_manager(); + tout << "comparing "; + l1.display(tout, m); + tout << " "; + l2.display(tout, m); + tout << " : " << r << "\n";); + return r == order::GREATER; + } + + void found_literals::insert(literal const & l) { + unsigned id = l.get_id(); + m_marks.reserve(id+1); + if (!m_marks.get(id)) { + m_marks.set(id); + m_lit_ids.push_back(id); + } + } + + bool found_literals::contains(literal const & l) const { + unsigned id = l.get_id(); + return id < m_marks.size() && m_marks.get(id); + } + + bool found_literals::contains_neg(literal const & l) const { + unsigned id = l.get_neg_id(); + return id < m_marks.size() && m_marks.get(id); + } + + void found_literals::reset() { + unsigned_vector::iterator it = m_lit_ids.begin(); + unsigned_vector::iterator end = m_lit_ids.end(); + for (; it != end; ++it) + m_marks.unset(*it); + m_lit_ids.reset(); + } + + void literal_buffer::reset() { + buffer::iterator it = m_lits.begin(); + buffer::iterator end = m_lits.end(); + for (; it != end; ++it) + m_manager.dec_ref(it->atom()); + m_lits.reset(); + } + + + expr * mk_or(ast_manager & m, unsigned num_lists, literal * lits) { + if (num_lists == 0) + return m.mk_false(); + else if (num_lists == 1) + return lits[0].to_expr(m); + else { + ptr_buffer new_exprs; + for (unsigned i = 0; i < num_lists; i++) + new_exprs.push_back(lits[i].to_expr(m)); + return m.mk_or(new_exprs.size(), new_exprs.c_ptr()); + } + } + +}; diff --git a/lib/spc_literal.h b/lib/spc_literal.h new file mode 100644 index 000000000..8b5322e4d --- /dev/null +++ b/lib/spc_literal.h @@ -0,0 +1,212 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_literal.h + +Abstract: + + Superposition Calculus literal + +Author: + + Leonardo de Moura (leonardo) 2008-02-02. + +Revision History: + +--*/ +#ifndef _SPC_LITERAL_H_ +#define _SPC_LITERAL_H_ + +#include"ast.h" +#include"order.h" +#include"expr_stat.h" + +namespace spc { + typedef expr_stat literal_stat; + +#define DEPTH_NUM_BITS 4 +#define DEPTH_MAX ((1 << DEPTH_NUM_BITS) - 1) +#define CONST_COUNT_NUM_BITS 4 +#define CONST_COUNT_MAX ((1 << CONST_COUNT_NUM_BITS) - 1) +#define SYM_COUNT_NUM_BITS 16 +#define SYM_COUNT_MAX ((1 << SYM_COUNT_NUM_BITS) - 1) + + /** + \brief Superposition Calculus literal. + */ + class literal { + expr * m_atom; + unsigned m_sign:1; // true if a negative literal. + unsigned m_oriented:1; // true if it is an oriented equality. + unsigned m_left:1; // true if the largest term is on the left-hand-side of the equality (only meaningful if m_oriented == true). + unsigned m_selected:1; // true if it is a selected literal. + unsigned m_stats:1; // true if the following fields were initialized. + unsigned m_ground:1; // true if it is a ground literal + unsigned m_p_indexed:1; // true if the literal was inserted into the p (paramodulation) superposition index. + unsigned m_r_indexed:1; // true if the literal was inserted into the r (resolution) superposition index. + unsigned m_depth:DEPTH_NUM_BITS; // approx. depth + unsigned m_const_count:CONST_COUNT_NUM_BITS; // approx. number of constants + unsigned m_sym_count:SYM_COUNT_NUM_BITS; // approx. size + + friend class clause; + + void set_selected(bool f) { + m_selected = f; + } + + public: + literal(): + m_atom(0), + m_sign(false), + m_oriented(false), + m_left(false), + m_selected(false), + m_stats(false), + m_ground(false), + m_p_indexed(false), + m_r_indexed(false), + m_depth(0), + m_const_count(0), + m_sym_count(0) { + } + + literal(expr * atom, bool sign = false): + m_atom(atom), + m_sign(sign), + m_oriented(false), + m_left(false), + m_selected(false), + m_stats(false), + m_ground(false), + m_p_indexed(false), + m_r_indexed(false), + m_depth(0), + m_const_count(0), + m_sym_count(0) { + } + + bool sign() const { return m_sign; } + bool pos() const { return !m_sign; } + bool neg() const { return m_sign; } + bool is_oriented() const { return m_oriented; } + bool is_left() const { return m_left; } + bool is_selected() const { return m_selected; } + expr * atom() const { return m_atom; } + expr * lhs() const { return to_app(m_atom)->get_arg(0); } + expr * rhs() const { return to_app(m_atom)->get_arg(1); } + unsigned get_id() const { return m_sign ? (to_app(m_atom)->get_id() << 1) + 1 : (to_app(m_atom)->get_id() << 1); } + unsigned get_neg_id() const { return m_sign ? (to_app(m_atom)->get_id() << 1) : (to_app(m_atom)->get_id() << 1) + 1; } + + bool operator==(literal const & other) const { return m_atom == other.m_atom && m_sign == other.m_sign; } + bool operator!=(literal const & other) const { return !operator==(other); } + + void set_p_indexed(bool f) { m_p_indexed = f; } + void set_r_indexed(bool f) { m_r_indexed = f; } + bool is_p_indexed() const { return m_p_indexed; } + bool is_r_indexed() const { return m_r_indexed; } + + void try_to_orient(order & o); + bool is_true(ast_manager & m) const { + return + (!m_sign && m.is_true(m_atom)) || + (!m_sign && m.is_eq(m_atom) && to_app(m_atom)->get_arg(0) == to_app(m_atom)->get_arg(1)) || + (m_sign && m.is_false(m_atom)); + } + bool is_false(ast_manager & m) const { + return + (m_sign && m.is_true(m_atom)) || + (m_sign && m.is_eq(m_atom) && to_app(m_atom)->get_arg(0) == to_app(m_atom)->get_arg(1)) || + (!m_sign && m.is_false(m_atom)); + } + expr * to_expr(ast_manager & m) const; + + /** + \brief Collect literal statistics + */ + void get_stat(literal_stat & stat); + void init_stat() { literal_stat st; get_stat(st); } + bool has_stats() const { return m_stats; } + bool is_ground() const { SASSERT(m_stats); return m_ground; } + unsigned get_approx_depth() const { SASSERT(m_stats); return m_depth; } + unsigned get_approx_const_count() const { SASSERT(m_stats); return m_const_count; } + unsigned get_approx_sym_count() const { SASSERT(m_stats); return m_sym_count; } + + void display(std::ostream & out, ast_manager & m, bool detailed = false) const; + }; + + COMPILE_TIME_ASSERT(sizeof(expr*) != 4 || sizeof(literal) == sizeof(expr *) + sizeof(unsigned)); // 32 bit machine + COMPILE_TIME_ASSERT(sizeof(expr*) != 8 || sizeof(literal) == sizeof(expr *) + sizeof(unsigned) + /* a structure must be aligned */ sizeof(unsigned)); // 64 bit machine + + void display(std::ostream & out, unsigned num_lists, literal * lits, ast_manager & m, bool detailed = false); + + order::result compare(order & o, literal const & l1, literal const & l2, unsigned offset = 0, substitution * s = 0); + bool greater(order & o, literal const & l1, literal const & l2, unsigned offset = 0, substitution * s = 0); + bool is_maximal(order & o, unsigned num_lists, literal * lits, literal const & l, unsigned offset = 0, substitution * s = 0); + bool is_sel_maximal(order & o, unsigned num_lists, literal * lits, literal const & l, unsigned offset = 0, substitution * s = 0); + + /** + \brief Set of found literals. + + This object is used to implement duplicate literal elimination, ans syntatic tautology. + */ + class found_literals { + bit_vector m_marks; + unsigned_vector m_lit_ids; + public: + /** + \brief Insert the given literal into the set. + */ + void insert(literal const & l); + + /** + \brief Return true if the set contains \c l. + */ + bool contains(literal const & l) const; + + /** + \brief Return true if the set contains the negation of \c l. + */ + bool contains_neg(literal const & l) const; + + bool empty() const { return m_lit_ids.empty(); } + + /** + \brief Remove all literals from the set. + */ + void reset(); + }; + + class literal_buffer { + ast_manager & m_manager; + buffer m_lits; + public: + literal_buffer(ast_manager & m): + m_manager(m) { + } + + ~literal_buffer() { + reset(); + } + + void push_back(literal const & l) { + m_manager.inc_ref(l.atom()); + m_lits.push_back(l); + } + + void reset(); + + unsigned size() const { + return m_lits.size(); + } + + literal * c_ptr() const { + return m_lits.c_ptr(); + } + }; + + expr * mk_or(ast_manager & m, unsigned num_lists, literal * lits); +}; + +#endif /* _SPC_LITERAL_H_ */ diff --git a/lib/spc_literal_selection.cpp b/lib/spc_literal_selection.cpp new file mode 100644 index 000000000..b8ecb151e --- /dev/null +++ b/lib/spc_literal_selection.cpp @@ -0,0 +1,107 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_literal_selection.cpp + +Abstract: + + Superposition Calculus Literal Selection + +Author: + + Leonardo de Moura (leonardo) 2008-02-05. + +Revision History: + +--*/ +#include"spc_literal_selection.h" +#include"expr_stat.h" + +namespace spc { + + void diff_literal_selection::operator()(clause * cls) { + bool found = false; + unsigned target = UINT_MAX; + unsigned best_count = 0; + unsigned num = cls->get_num_literals(); + for (unsigned i = 0; i < num; i++) { + literal & l = cls->get_literal(i); + if (l.sign()) { + unsigned count; + if (m_manager.is_eq(l.atom())) { + unsigned c1 = get_symbol_count(to_app(l.atom())->get_arg(0)); + unsigned c2 = get_symbol_count(to_app(l.atom())->get_arg(1)); + count = c1 >= c2 ? c1 - c2 : c2 - c1; + } + else { + count = get_symbol_count(l.atom()); + } + if (count > best_count) { + found = true; + target = i; + best_count = count; + } + } + } + if (found) + cls->select_literal(target); + } + + void complex_literal_selection::operator()(clause * cls) { + // look for x != y + unsigned num = cls->get_num_literals(); + for (unsigned i = 0; i < num; i++) { + literal & l = cls->get_literal(i); + if (l.sign() && m_manager.is_eq(l.atom()) && is_var(to_app(l.atom())->get_arg(0)) && is_var(to_app(l.atom())->get_arg(1))) { + cls->select_literal(i); + return; + } + } + + // look for min ground neg literal + bool found = false; + unsigned target = UINT_MAX; + unsigned best_count = UINT_MAX; + for (unsigned i = 0; i < num; i++) { + literal & l = cls->get_literal(i); + if (l.sign() && is_ground(l.atom())) { + unsigned count = get_symbol_count(l.atom()); + if (count < best_count) { + found = true; + target = i; + best_count = count; + } + } + } + + if (found) { + cls->select_literal(target); + return; + } + + diff_literal_selection::operator()(cls); + } + + void max_no_selection::operator()(clause * cls) { + unsigned num_lits = cls->get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) { + literal const & l1 = cls->get_literal(i); + if (!l1.sign()) { + unsigned j = 0; + for (; j < num_lits; j++) { + if (i != j) { + literal const & l2 = cls->get_literal(j); + if (!greater(m_order, l1, l2)) + break; + } + } + if (j == num_lits) + return; // clause has maximal positive literal. + } + } + diff_literal_selection::operator()(cls); + } + +}; diff --git a/lib/spc_literal_selection.h b/lib/spc_literal_selection.h new file mode 100644 index 000000000..0d51af93d --- /dev/null +++ b/lib/spc_literal_selection.h @@ -0,0 +1,95 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_literal_selection.h + +Abstract: + + Superposition Calculus Literal Selection + +Author: + + Leonardo de Moura (leonardo) 2008-02-05. + +Revision History: + +--*/ +#ifndef _SPC_LITERAL_SELECTION_H_ +#define _SPC_LITERAL_SELECTION_H_ + +#include"spc_clause.h" +#include"order.h" + +namespace spc { + + /** + \brief Abstract functor for literal selection. + */ + class literal_selection { + public: + virtual ~literal_selection() {} + /** + \brief Updates the selected status flag of the literals of the given clause. + */ + virtual void operator()(clause * cls) = 0; + }; + + /** + \brief Never selects a literal. This strategy is supposed to be good for planning problems + of TPTP. + */ + class no_literal_selection : public literal_selection { + public: + virtual void operator()(clause * cls) {} + }; + + + /** + \brief Selects a negative literal l with the largest V(l) + where V is defined as: + + - difference in symbol count for the left-right hand sides of equalities, . + + - symbol count for other predicates + + */ + class diff_literal_selection : public literal_selection { + protected: + ast_manager & m_manager; + public: + diff_literal_selection(ast_manager & m):m_manager(m) {} + virtual void operator()(clause * cls); + }; + + /** + \brief Selects a negative literal using the following algo: + + - if there is x != y, select it. + + - else if there is negative ground literal, select the smallest one. + + - else if use the approach in diff_literal_selection. + */ + class complex_literal_selection : public diff_literal_selection { + public: + complex_literal_selection(ast_manager & m):diff_literal_selection(m) {} + virtual void operator()(clause * cls); + }; + + /** + \brief Similar to diff_literal_selection, but a literal + is not selected if the clause contains a positive literal + greater than all other literals. + */ + class max_no_selection : public diff_literal_selection { + order & m_order; + public: + max_no_selection(order & o):diff_literal_selection(o.get_manager()), m_order(o) {} + virtual void operator()(clause * cls); + }; + +}; + +#endif /* _SPC_LITERAL_SELECTION_H_ */ diff --git a/lib/spc_params.cpp b/lib/spc_params.cpp new file mode 100644 index 000000000..248b73962 --- /dev/null +++ b/lib/spc_params.cpp @@ -0,0 +1,37 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_params.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-08. + +Revision History: + +--*/ +#include"spc_params.h" + +void spc_params::register_params(ini_params & p) { + order_params::register_params(p); + p.register_unsigned_param("SPC_MIN_FUNC_FREQ_SUBSUMPTION_INDEX",m_min_func_freq_subsumption_index, + "minimal number of occurrences (in clauses) for a function symbol to be considered for subsumption indexing."); + p.register_unsigned_param("SPC_MAX_SUBSUMPTION_INDEX_FEATURES", m_max_subsumption_index_features, + "maximum number of features to be used for subsumption index."); + p.register_unsigned_param("SPC_INITIAL_SUBSUMPTION_INDEX_OPT", m_initial_subsumption_index_opt, + "after how many processed clauses the subsumption index is optimized."); + p.register_double_param("SPC_FACTOR_SUBSUMPTION_INDEX_OPT", m_factor_subsumption_index_opt, + "after each optimization the threshold for optimization is increased by this factor. See INITIAL_SUBSUMPTION_INDEX_OPT."); + p.register_bool_param("SPC_BS", m_backward_subsumption, "Enable/disable backward subsumption in the superposition engine"); + p.register_bool_param("SPC_ES", m_equality_subsumption, "Enable/disable equality resolution in the superposition engine"); + p.register_unsigned_param("SPC_NUM_ITERATIONS", m_spc_num_iterations); + p.register_bool_param("SPC_TRACE", m_spc_trace); +} + + diff --git a/lib/spc_params.h b/lib/spc_params.h new file mode 100644 index 000000000..bb5232ef4 --- /dev/null +++ b/lib/spc_params.h @@ -0,0 +1,49 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_params.h + +Abstract: + + Parameters for the Superposition Calculus Engine + +Author: + + Leonardo de Moura (leonardo) 2008-02-08. + +Revision History: + +--*/ +#ifndef _SPC_PARAMS_H_ +#define _SPC_PARAMS_H_ + +#include"order_params.h" + +struct spc_params : public order_params { + unsigned m_min_func_freq_subsumption_index; + unsigned m_max_subsumption_index_features; + unsigned m_initial_subsumption_index_opt; + double m_factor_subsumption_index_opt; + bool m_backward_subsumption; + bool m_equality_subsumption; + unsigned m_spc_num_iterations; + bool m_spc_trace; + + spc_params(): + m_min_func_freq_subsumption_index(100), + m_max_subsumption_index_features(32), + m_initial_subsumption_index_opt(1000), + m_factor_subsumption_index_opt(1.5), + m_backward_subsumption(true), + m_equality_subsumption(true), + m_spc_num_iterations(1000), + m_spc_trace(false) { + } + + void register_params(ini_params & p); +}; + +#endif /* _SPC_PARAMS_H_ */ + diff --git a/lib/spc_prover.cpp b/lib/spc_prover.cpp new file mode 100644 index 000000000..76100c4f7 --- /dev/null +++ b/lib/spc_prover.cpp @@ -0,0 +1,132 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_prover.cpp + +Abstract: + + Stand-alone SPC prover (it is mainly for debugging purposes). + +Author: + + Leonardo de Moura (leonardo) 2008-02-08. + +Revision History: + +--*/ +#include"spc_prover.h" +#include"spc_decl_plugin.h" +#include"for_each_expr.h" + +namespace spc { + prover::prover(ast_manager & m, front_end_params & params): + m_manager(m), + m_params(params), + m_simplifier(m), + m_defined_names(m), + m_preprocessor(m, m_defined_names, m_simplifier, params), + m_order(0), + m_cls_sel(0), + m_lit_sel(0), + m_context(0), + m_exprs(m), + m_expr_proofs(m), + m_has_theories(false) { + + family_id fid = m_manager.get_family_id("spc"); + if (!m_manager.has_plugin(fid)) + m_manager.register_plugin(fid, alloc(spc_decl_plugin)); + + // This piece of code shows why the old model for passing parameters is broken. + // spc::prover must overwrite some parameters, but this modification affects other + // components. :-( + // TODO: move everything to the new params_ref object + params.m_nnf_mode = NNF_FULL; + params.m_cnf_mode = CNF_FULL; + params.m_lift_ite = LI_CONSERVATIVE; + + basic_simplifier_plugin * basic = alloc(basic_simplifier_plugin, m_manager); + m_simplifier.register_plugin(basic); + m_simplifier.register_plugin(alloc(arith_simplifier_plugin, m_manager, *basic, params)); + } + + prover::~prover() { + if (m_context) { + dealloc(m_context); + dealloc(m_lit_sel); + dealloc(m_cls_sel); + dealloc(m_order); + } + } + + void prover::init() { + if (m_context) + return; + precedence * p = mk_precedence(m_manager, m_params); + + // TODO use params to configure the following functors. + m_order = alloc(kbo, m_manager, p); + + clause_eval * evals[2] = { alloc(symbol_count_clause_eval), alloc(time_clause_eval) }; + unsigned slots[2] = { 10, 1 }; + m_cls_sel = alloc(clause_selection, 2, evals, slots); + + m_lit_sel = alloc(max_no_selection, *m_order); + // m_lit_sel = new complex_literal_selection(m_manager); + // m_lit_sel = new diff_literal_selection(m_manager); + // m_lit_sel = new no_literal_selection(); // new diff_literal_selection(m_manager); + // END TODO + + m_context = alloc(context, m_manager, *m_order, *m_cls_sel, *m_lit_sel, m_simplifier, m_params); + } + + struct has_theories_proc { + ast_manager & m_manager; + has_theories_proc(ast_manager & m):m_manager(m) {} + struct found {}; + void operator()(var * n) {} + void operator()(app * n) { if (!m_manager.is_builtin_family_id(n->get_family_id())) throw found(); } + void operator()(quantifier * n) {} + }; + + bool has_theories(ast_manager & m, expr * e) { + has_theories_proc p(m); + try { + for_each_expr(p, e); + } + catch (has_theories_proc::found) { + return true; + } + return false; + } + + void prover::assert_expr(expr * e) { + if (!m_has_theories && has_theories(m_manager, e)) + m_has_theories = true; + TRACE("spc_assert", tout << mk_pp(e, m_manager) << "\nhas_theories: " << m_has_theories << "\n";); + m_preprocessor(e, m_manager.mk_asserted(e), m_exprs, m_expr_proofs); + } + + lbool prover::check() { + init(); + unsigned sz = m_exprs.size(); + for (unsigned i = 0; i < sz; i++) { + expr * curr = m_exprs.get(i); + proof * p = m_manager.proofs_enabled() ? m_expr_proofs.get(i) : m_manager.mk_undef_proof(); + m_context->assert_expr(curr, p); + } + m_exprs.reset(); + m_expr_proofs.reset(); + + m_context->saturate(m_params.m_spc_num_iterations); + if (m_context->inconsistent()) + return l_false; + else if (m_context->processed_all()) + return m_has_theories ? l_undef : l_true; + else + return l_undef; + } +}; + diff --git a/lib/spc_prover.h b/lib/spc_prover.h new file mode 100644 index 000000000..a79773a5e --- /dev/null +++ b/lib/spc_prover.h @@ -0,0 +1,59 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_prover.h + +Abstract: + + Stand-alone SPC prover (it is mainly for debugging purposes). + +Author: + + Leonardo de Moura (leonardo) 2008-02-08. + +Revision History: + +--*/ +#ifndef _SPC_PROVER_H_ +#define _SPC_PROVER_H_ + +#include"spc_context.h" +#include"front_end_params.h" +#include"kbo.h" +#include"lpo.h" +#include"basic_simplifier_plugin.h" +#include"arith_simplifier_plugin.h" +#include"preprocessor.h" +#include"defined_names.h" +#include"lbool.h" + +namespace spc { + class prover { + ast_manager & m_manager; + front_end_params & m_params; + simplifier m_simplifier; + defined_names m_defined_names; + preprocessor m_preprocessor; + order * m_order; + clause_selection * m_cls_sel; + literal_selection * m_lit_sel; + context * m_context; + expr_ref_vector m_exprs; + proof_ref_vector m_expr_proofs; + bool m_has_theories; + + void init(); + + public: + prover(ast_manager & m, front_end_params & params); + ~prover(); + void assert_expr(expr * e); + lbool check(); + void display_statistics(std::ostream & out) const { if (m_context) m_context->display_statistics(out); } + }; +}; + +#endif /* _SPC_PROVER_H_ */ + diff --git a/lib/spc_rewriter.cpp b/lib/spc_rewriter.cpp new file mode 100644 index 000000000..73d5f3668 --- /dev/null +++ b/lib/spc_rewriter.cpp @@ -0,0 +1,269 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_rewrite.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-12. + +Revision History: + +--*/ +#include"spc_rewriter.h" +#include"spc_decl_plugin.h" +#include"ast_pp.h" + +namespace spc { + + rewriter::rewriter(ast_manager & m, simplifier & simp, order & ord, asserted_literals & al): + simplifier(m), + m_asserted_literals(al), + m_order(ord), + m_spc_fid(m.get_family_id("spc")), + m_subst(m), + m_st(m), + m_visitor(m_order, m_subst, m_cls_use_list) { + + reserve_offsets(3); + + borrow_plugins(simp); + } + + rewriter::~rewriter() { + release_plugins(); + } + + inline void rewriter::reserve_vars(unsigned num_vars) { + m_subst.reserve_vars(num_vars); + m_order.reserve_vars(num_vars); + m_asserted_literals.reserve_vars(num_vars); + } + + void rewriter::reserve_offsets(unsigned num_offsets) { + m_subst.reserve_offsets(num_offsets); + m_order.reserve_offsets(num_offsets); + } + + inline bool rewriter::demodulator(clause * cls) const { + if (cls->get_num_literals() != 1) + return false; + literal const & l = cls->get_literal(0); + return !l.sign() && m_manager.is_eq(l.atom()); + } + + inline void rewriter::insert(clause * cls, expr * source) { + if (!is_var(source)) { + TRACE("rewriter_detail", tout << "inserting into rewriter index:\n"; cls->display(tout, m_manager); tout << "\n";); + flush_cache(); + m_st.insert(to_app(source)); + m_cls_use_list.insert(cls, source); + } + } + + void rewriter::insert(clause * cls) { + if (demodulator(cls)) { + reserve_vars(cls->get_num_vars()); + literal const & l = cls->get_literal(0); + app * eq = to_app(l.atom()); + if (l.is_oriented()) { + expr * source = l.is_left() ? eq->get_arg(0) : eq->get_arg(1); + insert(cls, source); + } + else { + insert(cls, eq->get_arg(0)); + insert(cls, eq->get_arg(1)); + } + } + } + + inline void rewriter::erase(clause * cls, expr * source) { + if (!is_var(source)) { + flush_cache(); + m_cls_use_list.erase(cls, source); + if (m_cls_use_list.empty(source)) + m_st.erase(to_app(source)); + } + } + + void rewriter::erase(clause * cls) { + if (demodulator(cls)) { + literal const & l = cls->get_literal(0); + app * eq = to_app(l.atom()); + if (l.is_oriented()) { + expr * source = l.is_left() ? eq->get_arg(0) : eq->get_arg(1); + erase(cls, source); + } + else { + erase(cls, eq->get_arg(0)); + erase(cls, eq->get_arg(1)); + } + } + } + + bool rewriter::visitor::operator()(expr * e) { + if (m_cls_use_list.empty(e)) + return true; // continue; + clause_use_list::iterator it = m_cls_use_list.begin(e); + clause_use_list::iterator end = m_cls_use_list.end(e); + for (; it != end; ++it) { + m_clause = *it; + SASSERT(m_clause->get_num_literals() == 1); + literal & l = m_clause->get_literal(0); + expr * atom = l.atom(); + SASSERT(!l.sign() && m_manager.is_eq(atom)); + SASSERT(to_app(atom)->get_arg(0) == e || to_app(atom)->get_arg(1) == e); + m_source = to_app(atom)->get_arg(0); + m_target = to_app(atom)->get_arg(1); + if (m_source != e) + std::swap(m_source, m_target); + SASSERT(m_source == e); + TRACE("rewriter", tout << "found generalization:\n" << mk_pp(m_source, m_manager) << "\n" << + mk_pp(m_target, m_manager) << "\nsubstitution\n"; + m_subst.display(tout); tout << "m_subst: " << &m_subst << "\n"; + tout << "checking ordering constraints...\n";); + if (l.is_oriented() || m_order.greater(expr_offset(m_source, 1), expr_offset(m_target, 1), &m_subst)) { + m_found = true; + return false; // stop + } + TRACE("rewriter", tout << "failed ordering constraints...\n";); + } + return true; // continue + } + + void rewriter::save_justification(justification * j) { + if (std::find(m_justifications.begin(), m_justifications.end(), j) == m_justifications.end()) + m_justifications.push_back(j); + } + + proof * rewriter::mk_demodulation_proof(expr * old_expr, expr * new_expr, proof * parent) { + if (m_manager.fine_grain_proofs()) { + SASSERT(parent); + return m_manager.mk_app(m_spc_fid, PR_DEMODULATION, parent, m_manager.mk_eq(old_expr, new_expr)); + } + return 0; + } + + void rewriter::reset() { + m_st.reset(); + m_cls_use_list.reset(); + } + + void rewriter::reduce_literal(literal const & l, literal & l_r, proof * & l_pr) { + if (m_st.empty()) { + l_r = l; + l_pr = 0; + return; + } + + expr * atom = l.atom(); + expr * r; + proof * r_pr; + m_proofs.reset(); + while (true) { + reduce_core(atom); + get_cached(atom, r, r_pr); + if (m_manager.fine_grain_proofs() && r_pr) + m_proofs.push_back(r_pr); + if (atom == r) + break; + atom = r; + } + l_r = literal(atom, l.sign()); + if (m_manager.fine_grain_proofs()) + l_pr = m_proofs.empty() ? 0 : m_manager.mk_transitivity(m_proofs.size(), m_proofs.c_ptr()); + } + + clause * rewriter::operator()(clause * cls) { + reserve_vars(cls->get_num_vars()); + SASSERT(m_found_literals.empty()); + m_justifications.reset(); + m_max_scope_lvl = cls->get_scope_lvl(); + + literal_buffer new_literals(m_manager); + proof_ref_buffer new_proofs(m_manager); + bool changed = false; + + unsigned num_lits = cls->get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) { + literal & l = cls->get_literal(i); + literal l_r; + proof * l_pr = 0; + reduce_literal(l, l_r, l_pr); + + if (l != l_r) { + changed = true; + } + + if (!l_r.is_false(m_manager) && !m_found_literals.contains(l_r)) { + m_found_literals.insert(l_r); + + // apply simplify reflect rules + expr * atom = l_r.atom(); + clause * unit = 0; + TRACE("rewriter", tout << "adding literal: " << mk_pp(atom, m_manager) << "\n";); + if (l_r.sign()) { + if (m_manager.is_eq(atom)) + unit = m_asserted_literals.subsumes(to_app(atom)->get_arg(0), to_app(atom)->get_arg(1)); + else + unit = m_asserted_literals.gen(atom, false); + } + else { + // check if there is a generalization of the negation of the current literal. + unit = m_asserted_literals.gen(atom, true); + } + + if (unit) { + // new literal was resolved + justification * j = unit->get_justification(); + m_justifications.push_back(j); + changed = true; + } + else { + // keep new literal + new_literals.push_back(l_r); + } + } + else { + // removed duplicate or resolved literal. + changed = true; + } + + if (m_manager.fine_grain_proofs() && l_pr != 0) { + new_proofs.push_back(l_pr); + } + } + + m_found_literals.reset(); + + if (!changed) { + m_found_literals.reset(); + return cls; + } + + proof * new_pr = mk_rewrite_proof(m_manager, m_spc_fid, new_literals.size(), new_literals.c_ptr(), cls->get_justification()->get_proof(), + new_proofs.size(), new_proofs.c_ptr()); + + justification * new_j = rewrite_justification::mk(m_manager, cls->get_justification(), m_justifications.size(), m_justifications.c_ptr(), new_pr); + + if (m_max_scope_lvl == cls->get_scope_lvl()) { + // peform destructive update + cls->update_lits(m_manager, new_literals.size(), new_literals.c_ptr(), new_j); + return cls; + } + else { + SASSERT(m_max_scope_lvl > cls->get_scope_lvl()); + // create new clause + // the old clause will be frozen + return clause::mk(m_manager, new_literals.size(), new_literals.c_ptr(), new_j, m_max_scope_lvl); + } + } + +}; + diff --git a/lib/spc_rewriter.h b/lib/spc_rewriter.h new file mode 100644 index 000000000..73b0116ae --- /dev/null +++ b/lib/spc_rewriter.h @@ -0,0 +1,122 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_rewriter.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-11. + +Revision History: + +--*/ +#ifndef _SPC_REWRITER_H_ +#define _SPC_REWRITER_H_ + +#include"simplifier.h" +#include"order.h" +#include"substitution_tree.h" +#include"spc_clause.h" +#include"spc_asserted_literals.h" +#include"sparse_use_list.h" + +namespace spc { + + /** + \brief Apply rewriting steps using demodulation rule: + + C[s] ==> C[sigma(r)] + when + + l = r is a known equality (demodulator) + sigma(l) = s + sigma(l) > sigma(r) + + It also applies the following rules: + + - Duplicate literal deletion + + - Resolved literal deletion + + - Positive simplify reflect + s = t, (u[p <- sigma(s)] != u[p <- sigma(t)] or R) + ==> + R + + - Negative simplify reflect + s != t (sigma(s = t) or R) + ===> + R + */ + class rewriter : public simplifier { + protected: + typedef sparse_use_list > clause_use_list; + asserted_literals & m_asserted_literals; + order & m_order; + family_id m_spc_fid; + + substitution m_subst; + substitution_tree m_st; // index for potential demodulators left-hand-side + clause_use_list m_cls_use_list; // index for demodulators left-hand-side to equation. + found_literals m_found_literals; + + ptr_vector m_justifications; + + struct visitor : public st_visitor { + ast_manager & m_manager; + order & m_order; + clause_use_list & m_cls_use_list; + bool m_found; + clause * m_clause; + expr * m_source; + expr * m_target; + + visitor(order & ord, substitution & subst, clause_use_list & ul): + st_visitor(subst), m_manager(ord.get_manager()), m_order(ord), m_cls_use_list(ul) { + } + + virtual bool operator()(expr * e); + }; + + unsigned m_max_scope_lvl; // maximal scope level used during rewrite. + visitor m_visitor; + + proof * mk_demodulation_proof(expr * old_expr, expr * new_expr, proof * parent); + + bool demodulator(clause * cls) const; + void insert(clause * cls, expr * source); + void erase(clause * cls, expr * source); + void reserve_vars(unsigned num_vars); + void reserve_offsets(unsigned num_offsets); + void save_justification(justification * j); + + void reduce_literal(literal const & l, literal & l_r, proof * & l_pr); + + public: + rewriter(ast_manager & m, simplifier & s, order & ord, asserted_literals & al); + virtual ~rewriter(); + + /** + \brief Insert clause into rewriter indexes + */ + void insert(clause * cls); + + /** + \brief Remove clause from rewriter indexes + */ + void erase(clause * cls); + + clause * operator()(clause * cls); + + void reset(); + }; +}; + +#endif /* _SPC_REWRITER_H_ */ + diff --git a/lib/spc_semantic_tautology.cpp b/lib/spc_semantic_tautology.cpp new file mode 100644 index 000000000..fbbf09688 --- /dev/null +++ b/lib/spc_semantic_tautology.cpp @@ -0,0 +1,234 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_semantic_tautology.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-11. + +Revision History: + +--*/ +#include"spc_semantic_tautology.h" +#include"ast_pp.h" + +namespace spc { + + expr * find(expr2expr & f, expr * n) { +#ifdef _TRACE + expr * _n = n; +#endif + ptr_buffer path; + expr * next; + while (f.find(n, next)) { + path.push_back(n); + n = next; + } + ptr_buffer::iterator it = path.begin(); + ptr_buffer::iterator end = path.end(); + for (; it != end; ++it) { + expr * prev = *it; + f.insert(prev, n); + } + SASSERT(n); + TRACE("semantic_tautology_detail", tout << "find(#" << _n->get_id() << ") = #" << n->get_id() << "\n";); + return n; + } + + semantic_tautology::semantic_tautology(ast_manager & m): + m_manager(m), + m_cg_table(DEFAULT_HASHTABLE_INITIAL_CAPACITY, cg_hash(m_manager, m_find), cg_eq(m_find)) { + } + + unsigned semantic_tautology::cg_hash::operator()(app * n) const { + TRACE("semantic_tautology_detail", tout << "hash code of:\n" << mk_pp(n, m_manager) << "\n";); + unsigned r = get_composite_hash(n, n->get_num_args(), m_k_hash, m_c_hash); + TRACE("semantic_tautology_detail", tout << "result: " << r << "\n";); + return r; + } + + bool semantic_tautology::cg_eq::operator()(app * n1, app * n2) const { + if (n1->get_decl() != n2->get_decl() || n1->get_num_args() != n2->get_num_args()) + return false; + unsigned num_args = n1->get_num_args(); + for (unsigned i = 0; i < num_args; i++) + if (spc::find(m_find, n1->get_arg(i)) != spc::find(m_find, n2->get_arg(i))) + return false; + return true; + } + + bool semantic_tautology::is_target(unsigned num_lits, literal * lits) { + bool has_diseq = false; + bool has_non_diseq = false; + for (unsigned i = 0; i < num_lits; i++) { + literal const & l = lits[i]; + if (l.sign() && m_manager.is_eq(l.atom())) + has_diseq = true; + else + has_non_diseq = true; + } + return has_diseq && has_non_diseq; + } + + void semantic_tautology::reset() { + m_region.reset(); + m_init_todo.reset(); + m_todo.reset(); + m_already_found.reset(); + m_use_list.reset(); + m_find.reset(); + m_size.reset(); + m_cg_table.reset(); + } + + void semantic_tautology::update_use_list(app * parent, expr * child) { + list * use_list = 0; + m_use_list.find(child, use_list); + use_list = new (m_region) list(parent, use_list); + m_use_list.insert(child, use_list); + } + + inline void semantic_tautology::push_init_core(expr * n) { + if (is_app(n) && to_app(n)->get_num_args() > 0) + m_init_todo.push_back(to_app(n)); + } + + inline void semantic_tautology::push_init(expr * atom) { + if (m_manager.is_eq(atom)) { + push_init_core(to_app(atom)->get_arg(0)); + push_init_core(to_app(atom)->get_arg(1)); + } + else + push_init_core(atom); + } + + void semantic_tautology::init_use_list() { + while (!m_init_todo.empty()) { + app * n = m_init_todo.back(); + m_init_todo.pop_back(); + if (!m_already_found.contains(n)) { + unsigned num_args = n->get_num_args(); + SASSERT(num_args > 0); + m_cg_table.insert(n); + m_already_found.insert(n); + for (unsigned i = 0; i < num_args; i++) { + expr * c = n->get_arg(i); + update_use_list(n, c); + push_init_core(c); + } + } + } + } + + void semantic_tautology::init(unsigned num_lits, literal * lits) { + reset(); + for (unsigned i = 0; i < num_lits; i++) { + literal const & l = lits[i]; + expr * atom = l.atom(); + push_init(atom); + if (l.sign() && m_manager.is_eq(atom)) + m_todo.push_back(expr_pair(to_app(atom)->get_arg(0), to_app(atom)->get_arg(1))); + } + init_use_list(); + } + + void semantic_tautology::remove_parents(expr * n1) { + list * use_list = 0; + m_use_list.find(n1, use_list); + while (use_list) { + TRACE("semantic_tautology", tout << "removing parent from cg_table:\n" << mk_pp(use_list->head(), m_manager) << "\n";); + m_cg_table.erase(use_list->head()); + use_list = use_list->tail(); + } + } + + void semantic_tautology::restore_parents(expr * n1, expr * n2) { + list * use_list = 0; + m_use_list.find(n1, use_list); + while (use_list) { + app * parent = use_list->head(); + app * other = 0; + if (m_cg_table.find(parent, other)) { + TRACE("semantic_tautology", tout << "new congruence:\n" << mk_pp(parent, m_manager) << "\n" << mk_pp(other, m_manager) << "\n";); + if (parent != other) + m_todo.push_back(expr_pair(parent, other)); + } + else { + TRACE("semantic_tautology", tout << "restoring parent to cg_table:\n" << mk_pp(parent, m_manager) << "\n";); + m_cg_table.insert(parent); + update_use_list(parent, n2); + } + use_list = use_list->tail(); + } + } + + void semantic_tautology::assert_eq(expr * n1, expr * n2) { + n1 = find(n1); + n2 = find(n2); + if (n1 == n2) + return; + TRACE("semantic_tautology", tout << "processing equality:\n" << mk_pp(n1, m_manager) << " " << n1->get_id() << "\n" << + mk_pp(n2, m_manager) << " " << n2->get_id() << "\n";); + unsigned sz1 = 1; + unsigned sz2 = 1; + m_size.find(n1, sz1); + m_size.find(n2, sz2); + if (sz1 > sz2) + std::swap(n1, n2); + remove_parents(n1); + TRACE("semantic_tautology", tout << "merging equivalence classes\n";); + m_find.insert(n1, n2); + m_size.insert(n2, sz1 + sz2); + restore_parents(n1, n2); + } + + void semantic_tautology::process_eqs() { + while (!m_todo.empty()) { + expr_pair const & p = m_todo.back(); + expr * lhs = p.first; + expr * rhs = p.second; + m_todo.pop_back(); + assert_eq(lhs, rhs); + } + } + + bool semantic_tautology::contains_complement(unsigned num_lits, literal * lits, unsigned i, bool sign, expr * atom) { + atom = find(atom); + for (unsigned j = i + 1; j < num_lits; j++) { + literal const & l = lits[j]; + if (l.sign() != sign && find(l.atom()) == atom) + return true; + } + return false; + } + + bool semantic_tautology::is_tautology(unsigned num_lits, literal * lits) { + for (unsigned i = 0; i < num_lits; i++) { + literal const & l = lits[i]; + expr * atom = l.atom(); + if (!l.sign() && m_manager.is_eq(atom) && find(to_app(atom)->get_arg(0)) == find(to_app(atom)->get_arg(1))) + return true; + if (!m_manager.is_eq(atom) && contains_complement(num_lits, lits, i, l.sign(), atom)) + return true; + } + return false; + } + + bool semantic_tautology::operator()(unsigned num_lits, literal * lits) { + if (!is_target(num_lits, lits)) + return false; + init(num_lits, lits); + process_eqs(); + bool r = is_tautology(num_lits, lits); + TRACE("semantic_tautology", display(tout, num_lits, lits, m_manager); tout << "\nis semantic tautology: " << r << "\n";); + return r; + } + +}; diff --git a/lib/spc_semantic_tautology.h b/lib/spc_semantic_tautology.h new file mode 100644 index 000000000..748809a24 --- /dev/null +++ b/lib/spc_semantic_tautology.h @@ -0,0 +1,114 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_semantic_tautology.h + +Abstract: + + Semantic tautology detection + +Author: + + Leonardo de Moura (leonardo) 2008-02-11. + +Revision History: + +--*/ +#ifndef _SPC_SEMANTIC_TAUTOLOGY_H_ +#define _SPC_SEMANTIC_TAUTOLOGY_H_ + +#include"spc_literal.h" +#include"list.h" +#include"obj_hashtable.h" +#include"map.h" + +namespace spc { + + typedef obj_map expr2expr; + + expr * find(expr2expr & f, expr * e); + + /** + \brief Functor for detecting semantic tautology. + A clause C is a semantic tautology if it has the following form: + + s_1 != t_1 or ... or s_n != t_n or s = t or R + + sigma(s_1 = t_1), ..., sigma(s_n t_n) |= sigma(s = t) + where sigma maps variables to constants. + */ + class semantic_tautology { + typedef std::pair expr_pair; + + typedef obj_hashtable already_found; + typedef expr2expr find_map; + typedef obj_map *> use_list; + typedef obj_map size_map; + + struct k_hash { + unsigned operator()(app * n) const { return n->get_decl()->get_id(); } + }; + + struct c_hash { + find_map & m_find; + c_hash(find_map & f):m_find(f) {} + unsigned operator()(app * n, unsigned i) const { + unsigned id = spc::find(m_find, n->get_arg(i))->get_id(); + TRACE("semantic_tautology_detail", tout << "child(" << i << ") = #" << id << "\n";); + return id; + } + }; + + struct cg_hash { + ast_manager & m_manager; + k_hash m_k_hash; + c_hash m_c_hash; + cg_hash(ast_manager & m, find_map & f):m_manager(m), m_c_hash(f) {} + unsigned operator()(app * n) const; + }; + + struct cg_eq { + find_map & m_find; + cg_eq(find_map & f):m_find(f) {} + bool operator()(app * n1, app * n2) const; + }; + + typedef ptr_hashtable cg_table; + + ast_manager & m_manager; + region m_region; + ptr_vector m_init_todo; + svector m_todo; + already_found m_already_found; + use_list m_use_list; + find_map m_find; + size_map m_size; + cg_table m_cg_table; + + bool is_target(unsigned num_lits, literal * lits); + void reset(); + void update_use_list(app * parent, expr * child); + void push_init_core(expr * n); + void push_init(expr * atom); + void init_use_list(); + void init(unsigned num_lits, literal * lits); + expr * find(expr * n) { return spc::find(m_find, n); } + void remove_parents(expr * n1); + void restore_parents(expr * n1, expr * n2); + void assert_eq(expr * n1, expr * n2); + void process_eqs(); + bool contains_complement(unsigned num_lits, literal * lits, unsigned i, bool sign, expr * atom); + bool is_tautology(unsigned num_lits, literal * lits); + + public: + semantic_tautology(ast_manager & m); + + bool operator()(unsigned num_lits, literal * lits); + }; + +}; + +#endif /* _SPC_SEMANTIC_TAUTOLOGY_H_ */ + diff --git a/lib/spc_statistics.cpp b/lib/spc_statistics.cpp new file mode 100644 index 000000000..be27dc82d --- /dev/null +++ b/lib/spc_statistics.cpp @@ -0,0 +1,54 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_statistics.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-17. + +Revision History: + +--*/ +#include"spc_statistics.h" + +namespace spc { + + void statistics::reset() { + m_num_mk_clause = 0; + m_num_del_clause = 0; + m_num_processed = 0; + m_num_superposition = 0; + m_num_resolution = 0; + m_num_eq_factoring = 0; + m_num_factoring = 0; + m_num_eq_resolution = 0; + m_num_trivial = 0; + m_num_simplified = 0; + m_num_subsumed = 0; + m_num_redundant = 0; + } + + void statistics::display(std::ostream & out) const { + out << "num. mk. clause: " << m_num_mk_clause << "\n"; + out << "num. del. clause: " << m_num_del_clause << "\n"; + out << "num. processed: " << m_num_processed << "\n"; + out << "num. superposition: " << m_num_superposition << "\n"; + out << "num. resolution: " << m_num_resolution << "\n"; + out << "num. eq. factoring: " << m_num_eq_factoring << "\n"; + out << "num. factoring: " << m_num_factoring << "\n"; + out << "num. eq. resol.: " << m_num_eq_resolution << "\n"; + out << "num. simplified: " << m_num_simplified << "\n"; + out << "num. redundant: " << m_num_redundant << "\n"; + out << " num. trivial: " << m_num_trivial << "\n"; + out << " num. subsumed: " << m_num_subsumed << "\n"; + } + +}; + diff --git a/lib/spc_statistics.h b/lib/spc_statistics.h new file mode 100644 index 000000000..6274d0be7 --- /dev/null +++ b/lib/spc_statistics.h @@ -0,0 +1,49 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_statistics.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-17. + +Revision History: + +--*/ +#ifndef _SPC_STATISTICS_H_ +#define _SPC_STATISTICS_H_ + +#include + +namespace spc { + + struct statistics { + unsigned m_num_mk_clause; + unsigned m_num_del_clause; + unsigned m_num_processed; + unsigned m_num_superposition; + unsigned m_num_resolution; + unsigned m_num_eq_factoring; + unsigned m_num_factoring; + unsigned m_num_eq_resolution; + unsigned m_num_trivial; + unsigned m_num_simplified; + unsigned m_num_subsumed; + unsigned m_num_redundant; + statistics() { + reset(); + } + + void reset(); + void display(std::ostream & out) const; + }; +}; + +#endif /* _SPC_STATISTICS_H_ */ + diff --git a/lib/spc_subsumption.cpp b/lib/spc_subsumption.cpp new file mode 100644 index 000000000..e61329345 --- /dev/null +++ b/lib/spc_subsumption.cpp @@ -0,0 +1,698 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_subsumption.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-13. + +Revision History: + +--*/ +#include"spc_subsumption.h" +#include"fvi_def.h" +#include"ast_pp.h" + +namespace spc { + + /** + \brief Return true if literal l2 is an instance of l1. + + When ResetSubst == true, m_subst is reset before trying to match l1 and l2. + + When ResetSubst == false, it is assumed that m_subst.push_scope was invoked + before invoking match_literal. + */ + template + bool subsumption::match_literal(literal const & l1, literal const & l2) { + if (l1.sign() == l2.sign()) { + expr * atom1 = l1.atom(); + expr * atom2 = l2.atom(); + bool is_eq1 = m_manager.is_eq(atom1); + bool is_eq2 = m_manager.is_eq(atom2); + if (is_eq1 && is_eq2) { + expr * lhs1 = to_app(atom1)->get_arg(0); + expr * rhs1 = to_app(atom1)->get_arg(1); + expr * lhs2 = to_app(atom2)->get_arg(0); + expr * rhs2 = to_app(atom2)->get_arg(1); + if (ResetSubst) + m_subst.reset_subst(); + + if (m_matcher(lhs1, lhs2, m_subst) && m_matcher(rhs1, rhs2, m_subst)) + return true; + + if (ResetSubst) + m_subst.reset_subst(); + else { + // I'm assuming push_scope was invoked before executing match_literal + // So, pop_scope is equivalent to a local reset. + m_subst.pop_scope(); + m_subst.push_scope(); + } + + return (m_matcher(lhs1, rhs2, m_subst) && m_matcher(rhs1, lhs2, m_subst)); + } + else if (!is_eq1 && !is_eq2) { + if (ResetSubst) + m_subst.reset_subst(); + return m_matcher(atom1, atom2, m_subst); + } + } + return false; + } + + /** + \brief Return true if for every literal l1 in lits1 there is a + literal l2 in lits2 such that l2 is an instance of l1. + */ + bool subsumption::can_subsume(unsigned num_lits1, literal * lits1, unsigned num_lits2, literal * lits2) { + for (unsigned i = 0; i < num_lits1; i++) { + literal const & l1 = lits1[i]; + unsigned j = 0; + for (; j < num_lits2; j++) { + literal const & l2 = lits2[j]; + if (match_literal(l1, l2)) + break; + } + if (j == num_lits2) + return false; + } + return true; + } + + /** + \brief Return true if cls1 can subsume cls2. It performs a series of quick checks. + */ + bool subsumption::quick_check(clause * cls1, clause * cls2) { + return + cls1->get_symbol_count() <= cls2->get_symbol_count() && + cls1->get_const_count() <= cls2->get_const_count() && + cls1->get_depth() <= cls2->get_depth() && + cls1->get_num_pos_literals() <= cls2->get_num_pos_literals() && + cls1->get_num_neg_literals() <= cls2->get_num_neg_literals() && + (!cls1->is_ground() || cls2->is_ground()); + } + + /** + \brief Return true if the set of literals lits1 subsumes the set of literals lits2. + */ + bool subsumption::subsumes_core(unsigned num_lits1, literal * lits1, unsigned num_lits2, literal * lits2) { + enum state { + INVOKE, DECIDE, BACKTRACK, RETURN + }; + + if (num_lits1 == 0) + return true; + + m_stack.reset(); + m_subst.reset(); + + m_stack.push_back(assoc(0, 0)); + state st = DECIDE; + + unsigned i; + assoc * top; + unsigned counter = 0; +#ifdef _TRACE + unsigned opt = 0; + unsigned nopt = 0; +#endif + while (true && counter < 5000000) { + counter++; + switch (st) { + case INVOKE: + SASSERT(!m_stack.empty()); + i = m_stack.back().first + 1; + if (i >= num_lits1) { + TRACE("subsumption", tout << "subsumption result: YES.\n";); + TRACE_CODE({ + if (counter > 10000) { + TRACE("subsumption_perf", + tout << "subsumption succeeded: " << counter << " " << opt << " " << nopt << "\n"; + tout << "literals1:\n"; display(tout, num_lits1, lits1, m_manager); tout << "\n"; + tout << "literals2:\n"; display(tout, num_lits2, lits2, m_manager); tout << "\n";); + } + }); + return true; + } + else { + m_stack.push_back(assoc(i, 0)); + st = DECIDE; + } + break; + case DECIDE: + top = &(m_stack.back()); + m_subst.push_scope(); + if (match_literal(lits1[top->first], lits2[top->second])) + st = INVOKE; + else + st = BACKTRACK; + break; + case BACKTRACK: + top = &(m_stack.back()); + top->second++; + m_subst.pop_scope(); + if (top->second >= num_lits2) + st = RETURN; + else + st = DECIDE; + break; + case RETURN: + top = &(m_stack.back()); + m_stack.pop_back(); + if (m_stack.empty()) { + // no more alternatives + TRACE("subsumption", tout << "subsumption result: NO\n";); + TRACE_CODE({ + if (counter > 10000) { + TRACE("subsumption_perf", + tout << "subsumption failed: " << counter << " " << opt << " " << nopt << "\n"; + tout << "literals1:\n"; display(tout, num_lits1, lits1, m_manager); tout << "\n"; + tout << "literals2:\n"; display(tout, num_lits2, lits2, m_manager); tout << "\n";); + } + }); + return false; + } + if (m_subst.top_scope_has_bindings()) { + TRACE_CODE(nopt++;); + st = BACKTRACK; + } + else { + TRACE_CODE(opt++;); +#ifdef Z3DEBUG + unsigned num_bindings = m_subst.get_num_bindings(); +#endif + m_subst.pop_scope(); + SASSERT(num_bindings == m_subst.get_num_bindings()); + st = RETURN; + } + break; + } + } + return false; + } + + /** + \brief Return true if the set of ground literals lits1 subsumes the set of ground literals lits2. + */ + bool subsumption::ground_subsumes_core(unsigned num_lits1, literal * lits1, unsigned num_lits2, literal * lits2) { + for (unsigned i = 0; i < num_lits1; i++) { + literal const & l1 = lits1[i]; + unsigned j = 0; + for (; j < num_lits2; j++) { + literal const & l2 = lits2[j]; + if (l1 == l2) + break; + } + if (j == num_lits2) + return false; + } + return true; + } + + /** + \brief Return true if the literal l1 subsumes the set of literals lits2. + */ + bool subsumption::subsumes_core(literal const & l1, unsigned num_lits2, literal * lits2) { + for (unsigned i = 0; i < num_lits2; i++) { + if (match_literal(l1, lits2[i])) + return true; + } + return false; + } + + subsumption::subsumption(ast_manager & m, asserted_literals & al, spc_params & params): + m_manager(m), + m_params(params), + m_asserted_literals(al), + m_subst(m), + m_matcher(m), + m_found_decls(m), + m_index(0), + m_num_processed_clauses(0), + m_opt_threshold(params.m_initial_subsumption_index_opt) { + + m_subst.reserve_offsets(1); + + init_indexes(); + } + + subsumption::~subsumption() { + if (m_index) + dealloc(m_index); + } + + /** + \brief Return true if cls1 subsumes cls2 + */ + bool subsumption::operator()(clause * cls1, clause * cls2) { + TRACE("subsumption_detail", tout << "checking if:\n"; cls1->display(tout, m_manager); tout << "\nsubsumes\n"; + cls2->display(tout, m_manager); tout << "\n";); + if (!quick_check(cls1, cls2)) { + TRACE("subsumption_detail", tout << "failed quick check\n";); + return false; + } + + m_subst.reserve_vars(std::max(cls1->get_num_vars(), cls2->get_num_vars())); + unsigned num_lits1 = cls1->get_num_literals(); + unsigned num_lits2 = cls2->get_num_literals(); + literal * lits1 = cls1->get_literals(); + literal * lits2 = cls2->get_literals(); + if (cls1->is_ground() && cls2->is_ground()) + return ground_subsumes_core(num_lits1, lits1, num_lits2, lits2); + if (num_lits1 == 1) + return subsumes_core(lits1[0], num_lits2, lits2); + // TODO: REMOVE true below... using it for debugging purposes. + if (true || cls1->get_num_neg_literals() >= 3 || cls1->get_num_pos_literals() >= 3) + if (!can_subsume(num_lits1, lits1, num_lits2, lits2)) { + TRACE("subsumption_detail", tout << "failed can_subsume\n";); + return false; + } + return subsumes_core(num_lits1, lits1, num_lits2, lits2); + } + + /** + \brief Update the set of function symbols found in the clause being inserted into the index, + and the set of clauses found since the index started to be built. + + Return true if the function symbol should be tracked. + */ + bool subsumption::mark_func_decl(func_decl * f) { + if (m_refining_index) { + if (!m_cls_found_decl_set.contains(f)) { + // update set of func_decls in the curr clause + m_cls_found_decl_set.insert(f); + m_cls_found_decls.push_back(f); + // update global set of founf func_decls + unsigned id = f->get_decl_id(); + m_found_decl_set.reserve(id+1); + if (!m_found_decl_set.get(id)) { + m_found_decl_set.set(id); + m_found_decls.push_back(f); + } + } + return true; + } + else { + unsigned id = f->get_decl_id(); + // if func_decl was not found yet, then ignore it. + if (id < m_found_decl_set.size() && m_found_decl_set.get(id)) { + if (!m_cls_found_decl_set.contains(f)) { + // update set of func_decls in the curr clause + m_cls_found_decl_set.insert(f); + m_cls_found_decls.push_back(f); + } + return true; + } + return false; + } + } + + /** + \brief Increment the number of occurrences of a function symbol in the clause being + inserted into the index. + */ + void subsumption::inc_f_count(func_decl * f, bool neg) { + decl2nat & f_count = m_f_count[static_cast(neg)]; + unsigned val; + if (f_count.find(f, val)) { + f_count.insert(f, val + 1); + } + else { + f_count.insert(f, 1); + } + } + + /** + \brief Update the min/max num. of occurrences of func symbol in a clause. + */ + void subsumption::update_min_max(func_decl * f) { + for (unsigned is_neg = 0; is_neg < 1; is_neg++) { + decl2nat & f_count = m_f_count[is_neg]; + decl2nat & f_min = m_f_min[is_neg]; + decl2nat & f_max = m_f_max[is_neg]; + unsigned count; + if (f_count.find(f, count)) { + unsigned old_count; + if (!f_min.find(f, old_count) || old_count > count) { + f_min.insert(f, count); + } + if (!f_max.find(f, old_count) || old_count < count) { + f_max.insert(f, count); + } + } + } + } + + /** + \brief Compute the number of occurences of function symbols in + a clause. + */ + void subsumption::update_neg_pos_func_counts(clause * cls) { + m_f_count[0].reset(); + m_f_count[1].reset(); + m_cls_found_decl_set.reset(); + m_cls_found_decls.reset(); + ptr_buffer todo; + unsigned num_lits = cls->get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) { + literal const & l = cls->get_literal(i); + bool is_neg = l.sign(); + expr * n = l.atom(); + todo.push_back(n); + while (!todo.empty()) { + n = todo.back(); + todo.pop_back(); + if (is_app(n)) { + func_decl * f = to_app(n)->get_decl(); + if (fvi_candidate(f) && mark_func_decl(f)) + inc_f_count(f, is_neg); + unsigned num = to_app(n)->get_num_args(); + for (unsigned i = 0; i < num; i++) + todo.push_back(to_app(n)->get_arg(i)); + } + } + } + + if (m_refining_index) { + ptr_vector::iterator it = m_cls_found_decls.begin(); + ptr_vector::iterator end = m_cls_found_decls.end(); + for (; it != end; ++it) { + func_decl * f = *it; + update_min_max(f); + unsigned val; + if (m_f_freq.find(f, val)) + m_f_freq.insert(f, val + 1); + else + m_f_freq.insert(f, 1); + } + } + } + + /** + \brief Store in m_feature_vector the value for the features of cls. + */ + void subsumption::compute_features(clause * cls, unsigned * fvector) { + unsigned num = m_features.size(); + for (unsigned i = 0; i < num; i++) { + feature & f = m_features[i]; + switch (f.m_kind) { + case F_GROUND: + fvector[i] = cls->is_ground(); + break; + case F_NUM_POS_LITS: + fvector[i] = cls->get_num_pos_literals(); + break; + case F_NUM_NEG_LITS: + fvector[i] = cls->get_num_neg_literals(); + break; + case F_DEPTH: + fvector[i] = cls->get_depth(); + break; + case F_CONST_COUNT: + fvector[i] = cls->get_const_count(); + break; + case F_SYM_COUNT: + fvector[i] = cls->get_symbol_count(); + break; + case F_NUM_NEG_FUNCS: { + unsigned val; + if (m_f_count[1].find(f.m_decl, val)) + fvector[i] = val; + else + fvector[i] = 0; + break; + } + case F_NUM_POS_FUNCS: { + unsigned val; + if (m_f_count[0].find(f.m_decl, val)) + fvector[i] = val; + else + fvector[i] = 0; + break; + } + default: + UNREACHABLE(); + } + } + TRACE("subsumption_features", + tout << "features of: "; cls->display(tout, m_manager); tout << "\n"; + for (unsigned i = 0; i < num; i++) { + tout << fvector[i] << " "; + } + tout << "\n";); + } + + /** + \brief Initialise indexes for forward/backward subsumption. + */ + void subsumption::init_indexes() { + // index for forward/backward subsumption + // start with simple set of features + m_features.push_back(feature(F_GROUND)); + m_features.push_back(feature(F_NUM_POS_LITS)); + m_features.push_back(feature(F_NUM_NEG_LITS)); + m_features.push_back(feature(F_DEPTH)); + m_features.push_back(feature(F_CONST_COUNT)); + m_features.push_back(feature(F_SYM_COUNT)); + m_index = alloc(index, m_features.size(), to_feature_vector(*this)); + } + + unsigned subsumption::get_value_range(func_decl * f, bool neg) const { + unsigned i = static_cast(neg); + unsigned min; + unsigned max; + if (!m_f_min[i].find(f, min)) + min = 0; + if (!m_f_max[i].find(f, max)) + max = 0; + SASSERT(min <= max); + return max - min; + } + + inline unsigned subsumption::get_value_range(func_decl * f) const { + return std::max(get_value_range(f, false), get_value_range(f, true)); + } + + bool subsumption::f_lt::operator()(func_decl * f1, func_decl * f2) const { + unsigned vrange1 = m_owner.get_value_range(f1); + unsigned vrange2 = m_owner.get_value_range(f2); + if (vrange1 < vrange2) + return true; + if (vrange1 == vrange2) + return f1->get_id() < f2->get_id(); + return false; + } + + /** + \brief Optimize the index for (non unit) forward subsumption and + backward subsumption. + */ + void subsumption::optimize_feature_index() { + ptr_vector clauses; + m_index->collect(clauses); + + dealloc(m_index); + m_features.reset(); + + ptr_vector targets; + unsigned sz = m_found_decls.size(); + for (unsigned i = 0; i < sz; i++) { + func_decl * f = m_found_decls.get(i); + unsigned val; + if (m_f_freq.find(f, val) && val > m_params.m_min_func_freq_subsumption_index && get_value_range(f) > 0) + targets.push_back(f); + } + + + f_lt lt(*this); + std::sort(targets.begin(), targets.end(), lt); + + m_features.push_back(feature(F_GROUND)); + m_features.push_back(feature(F_NUM_POS_LITS)); + m_features.push_back(feature(F_NUM_NEG_LITS)); + m_features.push_back(feature(F_DEPTH)); + + ptr_vector::iterator it = targets.begin(); + ptr_vector::iterator end = targets.end(); + for (; it != end; ++it) { + func_decl * f = *it; + if (get_value_range(f, false) > 1) + m_features.push_back(feature(f, false)); + if (get_value_range(f, true) > 1) + m_features.push_back(feature(f, true)); + if (m_features.size() > m_params.m_max_subsumption_index_features) + break; + } + + m_features.push_back(feature(F_CONST_COUNT)); + m_features.push_back(feature(F_SYM_COUNT)); + m_index = alloc(index, m_features.size(), to_feature_vector(*this)); + m_num_processed_clauses = 0; + unsigned new_threshold = static_cast(m_opt_threshold * m_params.m_factor_subsumption_index_opt); + if (new_threshold > m_opt_threshold) + m_opt_threshold = new_threshold; + } + + /** + \brief Insert cls into the indexes used for forward/backward subsumption. + */ + void subsumption::insert(clause * cls) { + TRACE("subsumption", tout << "adding clause to subsumption index: " << cls << "\n"; cls->display(tout, m_manager); tout << "\n";); + unsigned num_lits = cls->get_num_literals(); + if (num_lits > 1 || m_params.m_backward_subsumption) { + m_index->insert(cls); + SASSERT(m_index->contains(cls)); + m_num_processed_clauses++; + if (m_num_processed_clauses > m_opt_threshold) + optimize_feature_index(); + } + } + + /** + \brief Remove cls from the indexes used for forward/backward subsumption. + */ + void subsumption::erase(clause * cls) { + TRACE("subsumption", tout << "removing clause from subsumption index:" << cls << "\n"; cls->display(tout, m_manager); tout << "\n"; + tout << "num lits.: " << cls->get_num_literals() << ", backward_sub: " << m_params.m_backward_subsumption << "\n";); + unsigned num_lits = cls->get_num_literals(); + if (num_lits > 1 || m_params.m_backward_subsumption) + m_index->erase(cls); + } + + /** + \brief Reset the indexes used for forward/backward subsumption. + */ + void subsumption::reset() { + if (m_index) + m_index->reset(); + m_num_processed_clauses = 0; + m_opt_threshold = m_params.m_initial_subsumption_index_opt; + } + + /** + \brief Return an unit clause C in the index that subsumes cls. + Return 0 if such clause does not exist. + */ + clause * subsumption::unit_forward(clause * cls) { + if (!m_asserted_literals.has_literals()) + return 0; + m_asserted_literals.reserve_vars(cls->get_num_vars()); + unsigned num_lits = cls->get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) { + literal const & l = cls->get_literal(i); + clause * subsumer = m_asserted_literals.gen(l); + if (subsumer) + return subsumer; + } + return 0; + } + + struct non_unit_subsumption_visitor { + subsumption & m_owner; + clause * m_new_clause; + clause * m_subsumer; + non_unit_subsumption_visitor(subsumption & owner, clause * new_clause): + m_owner(owner), + m_new_clause(new_clause), + m_subsumer(0) { + } + bool operator()(clause * candidate) { + TRACE("subsumption_index", tout << "considering candidate:\n"; candidate->display(tout, m_owner.get_manager()); tout << "\n";); + if (candidate->get_num_literals() > 1 && m_owner(candidate, m_new_clause)) { + m_subsumer = candidate; + return false; // stop subsumer was found + } + return true; // continue; + } + }; + + /** + \brief Return a non unit clause C in the index that subsumes cls. + Return 0 if such clause does not exist. + */ + clause * subsumption::non_unit_forward(clause * cls) { + non_unit_subsumption_visitor visitor(*this, cls); + m_index->visit(cls, visitor, true); + return visitor.m_subsumer; + } + + /** + \brief Return a unit equality clause (= s t) that (eq) subsumes cls. + That is, cls contains a literal (= u[s'] u[t']) and there is + a substitution sigma s.t. sigma(s) = s' and sigma(t) = t'. + Return 0 if such clause does not exist. + */ + clause * subsumption::eq_subsumption(clause * cls) { + if (!m_asserted_literals.has_pos_literals()) + return 0; + m_asserted_literals.reserve_vars(cls->get_num_vars()); + unsigned num_lits = cls->get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) { + literal const & l = cls->get_literal(i); + expr * atom = l.atom(); + if (!l.sign() && m_manager.is_eq(atom)) { + expr * lhs = to_app(atom)->get_arg(0); + expr * rhs = to_app(atom)->get_arg(1); + clause * subsumer = m_asserted_literals.subsumes(lhs, rhs); + if (subsumer) { + TRACE("eq_subsumption", tout << "equality subsumption:\n"; cls->display(tout, m_manager); + tout << "\nis subsumed by:\n"; subsumer->display(tout, m_manager); tout << "\n";); + return subsumer; + } + } + } + return 0; + } + + /** + \brief Return a clause C in the index (i.e., insert(C) was invoked) that subsumes cls. + Return 0 if such clause does not exist. + */ + clause * subsumption::forward(clause * cls) { + TRACE("subsumption", tout << "trying forward subsumption:\n"; cls->display(tout, m_manager); tout << "\n";); + clause * subsumer = unit_forward(cls); + if (subsumer) + return subsumer; + subsumer = non_unit_forward(cls); + if (subsumer) + return subsumer; + if (m_params.m_equality_subsumption) + return eq_subsumption(cls); + return 0; + } + + struct backward_subsumption_visitor { + subsumption & m_owner; + clause * m_new_clause; + ptr_buffer & m_result; + backward_subsumption_visitor(subsumption & owner, clause * new_clause, ptr_buffer & result): + m_owner(owner), + m_new_clause(new_clause), + m_result(result) { + } + + bool operator()(clause * candidate) { + if (m_owner(m_new_clause, candidate)) + m_result.push_back(candidate); + return true; // always continue in backward subsumption + } + }; + + /** + \brief Store in result the set of clauses in the index that are subsumes by cls. + */ + void subsumption::backward(clause * cls, ptr_buffer & result) { + if (m_params.m_backward_subsumption) { + backward_subsumption_visitor visitor(*this, cls, result); + m_index->visit(cls, visitor, false); + } + } +}; diff --git a/lib/spc_subsumption.h b/lib/spc_subsumption.h new file mode 100644 index 000000000..0d8c0c671 --- /dev/null +++ b/lib/spc_subsumption.h @@ -0,0 +1,156 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_subsumption.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-13. + +Revision History: + +--*/ +#ifndef _SPC_SUBSUMPTION_H_ +#define _SPC_SUBSUMPTION_H_ + +#include"spc_asserted_literals.h" +#include"matcher.h" +#include"fvi.h" +#include"spc_params.h" +#include"obj_hashtable.h" + +namespace spc { + + class subsumption { + ast_manager & m_manager; + spc_params & m_params; + asserted_literals & m_asserted_literals; + substitution m_subst; + matcher m_matcher; + + // A pair representing the association between l1 and l2 where + // first is the position of l1 in lits1 and second the position of l2 in + // lits2. + typedef std::pair assoc; + typedef vector stack; + stack m_stack; + + template + bool match_literal(literal const & l1, literal const & l2); + + bool can_subsume(unsigned num_lits1, literal * lits1, unsigned num_lits2, literal * lits2); + bool quick_check(clause * cls1, clause * cls2); + bool subsumes_core(unsigned num_lits1, literal * lits1, unsigned num_lits2, literal * lits2); + bool subsumes_core(literal const & l1, unsigned num_lits2, literal * lits2); + bool ground_subsumes_core(unsigned num_lits1, literal * lits1, unsigned num_lits2, literal * lits2); + + enum feature_kind { + F_GROUND, + F_NUM_POS_LITS, + F_NUM_NEG_LITS, + F_DEPTH, + F_CONST_COUNT, + F_SYM_COUNT, + F_NUM_NEG_FUNCS, + F_NUM_POS_FUNCS + }; + + struct feature { + feature_kind m_kind; + func_decl * m_decl; + feature(feature_kind k = F_GROUND):m_kind(k) {} + feature(func_decl * decl, bool neg):m_kind(neg ? F_NUM_NEG_FUNCS : F_NUM_POS_FUNCS), m_decl(decl) {} + }; + + vector m_features; + + bit_vector m_found_decl_set; + func_decl_ref_vector m_found_decls; // domain of m_found_decl_set; + + /** + \brief Return true if the function symbol is considered for feature vector indexing. + */ + bool fvi_candidate(func_decl * f) { + return f->get_family_id() == null_family_id || f->get_arity() > 0; + } + + typedef obj_hashtable found_func_decl_set; + found_func_decl_set m_cls_found_decl_set; // temporary set used to track the func_decl's found in a clause + ptr_vector m_cls_found_decls; + + bool mark_func_decl(func_decl * f); + + typedef obj_map decl2nat; + bool m_refining_index; // if true keep collecting data to refine index. + decl2nat m_f_count[2]; // temporary field used to track the num. of occurs. of function symbols in neg/pos literals. + decl2nat m_f_min[2]; + decl2nat m_f_max[2]; + decl2nat m_f_freq; + + void inc_f_count(func_decl * f, bool neg); + void update_min_max(func_decl * f); + void update_neg_pos_func_counts(clause * cls); + + void compute_features(clause * cls, unsigned * fvector); + + struct to_feature_vector; + friend struct to_feature_vector; + + struct to_feature_vector { + subsumption & m_owner; + to_feature_vector(subsumption & o):m_owner(o) {} + void operator()(clause * cls, unsigned * fvector) { + m_owner.compute_features(cls, fvector); + } + }; + + typedef fvi, ptr_eq > index; + index * m_index; + unsigned m_num_processed_clauses; + unsigned m_opt_threshold; + + void init_indexes(); + + struct f_lt; + friend struct f_lt; + + unsigned get_value_range(func_decl * f, bool neg) const; + unsigned get_value_range(func_decl * f) const; + + struct f_lt { + subsumption & m_owner; + f_lt(subsumption & o):m_owner(o) {} + bool operator()(func_decl * f1, func_decl * f2) const; + }; + + void optimize_feature_index(); + + clause * unit_forward(clause * cls); + clause * non_unit_forward(clause * cls); + clause * eq_subsumption(expr * lhs, expr * rhs); + clause * eq_subsumption(clause * cls); + + public: + subsumption(ast_manager & m, asserted_literals & al, spc_params & params); + ~subsumption(); + + bool operator()(clause * cls1, clause * cls2); + void insert(clause * cls); + void erase(clause * cls); + void reset(); + clause * forward(clause * cls); + void backward(clause * cls, ptr_buffer & result); + + ast_manager & get_manager() { return m_manager; } + }; + +}; + +#endif /* _SPC_SUBSUMPTION_H_ */ + diff --git a/lib/spc_superposition.cpp b/lib/spc_superposition.cpp new file mode 100644 index 000000000..510cc8e0d --- /dev/null +++ b/lib/spc_superposition.cpp @@ -0,0 +1,531 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_superposition.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-15. + +Revision History: + +--*/ +#include"spc_superposition.h" +#include"ast_pp.h" + +namespace spc { + + superposition::superposition(ast_manager & m, order & o, statistics & s): + m_manager(m), + m_order(o), + m_stats(s), + m_subst(m), + m_p(m), + m_r(m), + m_normalize_vars(m), + m_spc_fid(m.get_family_id("spc")) { + m_subst.reserve_offsets(3); + m_deltas[0] = 0; + m_deltas[1] = 0; + } + + superposition::~superposition() { + } + + void superposition::insert_p(clause * cls, expr * lhs, unsigned i) { + m_p.insert(lhs); + m_subst.reserve_vars(m_p.get_approx_num_regs()); + m_p2clause_set.insert(clause_pos_pair(cls, i), lhs); + } + + void superposition::insert_p(clause * cls, literal & l, unsigned i) { + l.set_p_indexed(true); + expr * atom = l.atom(); + if (!m_manager.is_eq(atom)) + return; + if (l.is_oriented()) + insert_p(cls, l.is_left() ? l.lhs() : l.rhs(), i); + else { + insert_p(cls, l.lhs(), i); + insert_p(cls, l.rhs(), i); + } + } + + void superposition::insert_r(clause * cls, expr * n, unsigned i, bool lhs) { + if (is_app(n)) { + unsigned idx = (i << 1) | static_cast(lhs); + + clause_pos_pair new_pair(cls, idx); + SASSERT(m_todo.empty()); + m_todo.push_back(to_app(n)); + while (!m_todo.empty()) { + app * n = m_todo.back(); + m_todo.pop_back(); + clause_pos_set * s = m_r2clause_set.get_parents(n); + if (s == 0 || !s->contains(new_pair)) { + m_r.insert(n); + m_r2clause_set.insert(new_pair, n); + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * c = n->get_arg(i); + if (is_app(c)) + m_todo.push_back(to_app(c)); + } + } + } + } + } + + void superposition::insert_r(clause * cls, literal & l, unsigned i) { + l.set_r_indexed(true); + expr * atom = l.atom(); + if (m_manager.is_eq(atom)) { + expr * lhs = l.lhs(); + expr * rhs = l.rhs(); + if (l.is_oriented()) { + bool left = true; + if (!l.is_left()) { + left = false; + std::swap(lhs, rhs); + } + insert_r(cls, lhs, i, left); + } + else { + insert_r(cls, lhs, i, true); + insert_r(cls, rhs, i, false); + } + } + else { + insert_r(cls, atom, i, false); + } + m_subst.reserve_vars(m_r.get_approx_num_regs()); + } + + void superposition::insert(clause * cls) { + unsigned num_lits = cls->get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) { + literal & l = cls->get_literal(i); + if (l.is_p_indexed() || cls->is_eligible_for_paramodulation(m_order, l)) { + if (!l.sign() && m_manager.is_eq(l.atom())) + insert_p(cls, l, i); + insert_r(cls, l, i); + } + else if (l.is_r_indexed() || cls->is_eligible_for_resolution(m_order, l)) { + insert_r(cls, l, i); + } + } + TRACE("superposition_detail", + tout << "adding clause: "; cls->display(tout, m_manager); tout << "\n"; + tout << "p index:\n"; + m_p.display(tout); + tout << "r index:\n"; + m_r.display(tout);); + } + + void superposition::erase_p(clause * cls, expr * lhs, unsigned i) { + m_p2clause_set.erase(clause_pos_pair(cls, i), lhs); + if (m_p2clause_set.empty(lhs)) + m_p.erase(lhs); + } + + void superposition::erase_p(clause * cls, literal & l, unsigned i) { + expr * atom = l.atom(); + if (!m_manager.is_eq(atom)) + return; + if (l.is_oriented()) + erase_p(cls, l.is_left() ? l.lhs() : l.rhs(), i); + else { + erase_p(cls, l.lhs(), i); + erase_p(cls, l.rhs(), i); + } + } + + void superposition::erase_r(clause * cls, literal & l, unsigned i) { + clause_pos_pair pair(cls, i); + + expr * atom = l.atom(); + SASSERT(is_app(atom)); + SASSERT(m_todo.empty()); + m_todo.push_back(to_app(atom)); + + while (!m_todo.empty()) { + app * n = m_todo.back(); + m_todo.pop_back(); + switch (m_r2clause_set.erase(pair, n)) { + case 0: // pair is not a parent of n + break; + case 1: // pair is the last parent of n + m_r.erase(n); + default: + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * c = n->get_arg(i); + if (is_app(c)) + m_todo.push_back(to_app(c)); + } + } + } + } + + void superposition::erase(clause * cls) { + unsigned num_lits = cls->get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) { + literal & l = cls->get_literal(i); + if (l.is_p_indexed()) + erase_p(cls, l, i); + if (l.is_r_indexed()) + erase_r(cls, l, i); + } + } + + void superposition::reset() { + m_p.reset(); + m_p2clause_set.reset(); + m_r.reset(); + m_r2clause_set.reset(); + } + + /** + \brief Copy to result the literals of s except literal at position idx. Apply the substitution m_subst, + assuming that the variables of s are in the variable bank offset. The deltas for each bank are + stored in m_deltas. + */ + void superposition::copy_literals(clause * s, unsigned idx, unsigned offset, literal_buffer & result) { + unsigned num_lits = s->get_num_literals(); + for (unsigned i = 0; i < num_lits; i++) + if (i != idx) { + literal const & l = s->get_literal(i); + expr_ref new_atom(m_manager); + m_subst.apply(2, m_deltas, expr_offset(l.atom(), offset), new_atom); + TRACE("superposition_copy", tout << "i: " << i << ", idx: " << idx << ", offset: " << offset << "\natom:\n"; + tout << mk_pp(l.atom(), m_manager) << "\nnew_atom:\n" << mk_pp(new_atom, m_manager) << "\n";); + result.push_back(literal(new_atom, l.sign())); + } + } + + void superposition::normalize_literals(unsigned num_lits, literal * lits, literal_buffer & result) { + m_normalize_vars.reset(); + for (unsigned i = 0; i < num_lits; i++) { + literal const & l = lits[i]; + result.push_back(literal(m_normalize_vars(l.atom()), l.sign())); + } + } + + void superposition::mk_sp_clause(unsigned num_lits, literal * lits, justification * p1, justification * p2) { + literal_buffer new_literals(m_manager); + normalize_literals(num_lits, lits, new_literals); + justification * js = mk_superposition_justification(m_manager, m_spc_fid, p1, p2, + new_literals.size(), new_literals.c_ptr(), + m_normalize_vars.get_num_vars(), m_normalize_vars.get_vars()); + clause * new_cls = clause::mk(m_manager, new_literals.size(), new_literals.c_ptr(), js, 0); + m_new_clauses->push_back(new_cls); + TRACE("superposition", tout << "new superposition clause:\n"; new_cls->display(tout, m_manager); tout << "\n";); + m_stats.m_num_superposition++; + } + + void superposition::mk_res_clause(unsigned num_lits, literal * lits, justification * p1, justification * p2) { + literal_buffer new_literals(m_manager); + normalize_literals(num_lits, lits, new_literals); + justification * js = mk_resolution_justification(m_manager, m_spc_fid, p1, p2, + new_literals.size(), new_literals.c_ptr(), + m_normalize_vars.get_num_vars(), m_normalize_vars.get_vars()); + clause * new_cls = clause::mk(m_manager, new_literals.size(), new_literals.c_ptr(), js, 0); + m_new_clauses->push_back(new_cls); + TRACE("superposition", tout << "new resolution clause:\n"; new_cls->display(tout, m_manager); tout << "\n";); + m_stats.m_num_resolution++; + } + + /** + \brief Given the equation (= lhs rhs) of the clause being + added, try to apply resolution where the clause being added + is the main clause in the superposition rule. + */ + void superposition::try_superposition_main(expr * lhs, expr * rhs) { + m_lhs = lhs; + m_rhs = rhs; + m_subst.reset_subst(); + TRACE("spc_superposition", tout << "try_superposition_main, lhs:\n" << mk_pp(m_lhs, m_manager) << "\nrhs:\n" << mk_pp(m_rhs, m_manager) << "\n"; + tout << "substitution:\n"; m_subst.display(tout);); + r_visitor v(*this, m_subst); + m_r.unify(lhs, v); + } + + /** + \brief Try to apply superposition rule using the clause + being added (m_clause) as main clause, and its literal m_lit + as the equation. + */ + void superposition::try_superposition_main() { + expr * lhs = m_lit->lhs(); + expr * rhs = m_lit->rhs(); + TRACE("spc_superposition", tout << "trying superposition:\n" << mk_pp(lhs, m_manager) << "\n" << mk_pp(rhs, m_manager) << "\nis_oriented: " << m_lit->is_oriented() << "\n";); + if (m_lit->is_oriented()) { + if (!m_lit->is_left()) + std::swap(lhs, rhs); + try_superposition_main(lhs, rhs); + } + else { + try_superposition_main(lhs, rhs); + try_superposition_main(rhs, lhs); + } + } + + void superposition::found_r(expr * r) { + TRACE("spc_superposition", tout << "found_r:\n" << mk_pp(r, m_manager) << "\n"; + tout << "substitution:\n"; m_subst.display(tout);); + if (m_r2clause_set.empty(r)) + return; + TRACE("spc_superposition", tout << "r2clause is not empty.\n";); + if (!m_lit->is_oriented() && m_order.greater(m_rhs, m_lhs, &m_subst)) + return; + TRACE("spc_superposition", tout << "order restriction was met.\n";); + if (!m_clause->is_eligible_for_paramodulation(m_order, *m_lit, 0, &m_subst)) + return; + TRACE("spc_superposition", tout << "literal is eligible for paramodulation.\n";); + r2clause_set::iterator it = m_r2clause_set.begin(r); + r2clause_set::iterator end = m_r2clause_set.end(r); + for (; it != end; ++it) { + clause_pos_pair & p = *it; + clause * aux_cls = p.first; + unsigned aux_idx = p.second >> 1; + // + // The following optimization is incorrect (if statement). + // For example, it prevents the Z3 from proving the trivial benchmark + // c = X, + // a != b + // using the order a < b < c + // + // To prove, this example we need to generate the clause Y = X by applying superposition of c = X on itself. + // We can see that by renaming the first clause to c = Y, and then, substituting c in the original by Y. + // + // Actually, this optimization is correct when the set of variables in m_lhs is a superset of the set of variables in m_rhs, + // because in this case, the new literal will be equivalent to true. In the example above, this is not the case, + // since m_lhs does not contain any variable, and m_rhs contains one. + // + + // + // if (r == m_lhs && m_clause == aux_cls && m_idx == aux_idx) + // continue; + // + bool in_lhs = (p.second & 1) != 0; + TRACE("spc_superposition", tout << "aux_cls:\n"; aux_cls->display(tout, m_manager); tout << "\naux_idx: " << aux_cls << ", in_lhs: " << in_lhs << "\n";); + literal & aux_lit = aux_cls->get_literal(aux_idx); + if (!aux_cls->is_eligible_for_resolution(m_order, aux_lit, 1, &m_subst)) + continue; + literal_buffer new_literals(m_manager); + m_subst.reset_cache(); + if (m_manager.is_eq(aux_lit.atom())) { + expr * lhs = aux_lit.lhs(); + expr * rhs = aux_lit.rhs(); + TRACE("spc_superposition", tout << "aux_lit lhs:\n" << mk_pp(lhs, m_manager) << "\nrhs:\n" << mk_pp(rhs, m_manager) << "\n";); + if (!in_lhs) + std::swap(lhs, rhs); + if (!aux_lit.is_oriented() && m_order.greater(rhs, lhs, 1, &m_subst)) { + TRACE("spc_superposition", tout << "failed order constraint.\n";); + continue; + } + expr_ref new_lhs(m_manager), new_rhs(m_manager); + m_subst.apply(2, m_deltas, expr_offset(lhs, 1), expr_offset(r, 1), expr_offset(m_rhs, 0), new_lhs); + m_subst.apply(2, m_deltas, expr_offset(rhs, 1), new_rhs); + TRACE("spc_superposition", tout << "aux_lit new_lhs:\n" << mk_pp(new_lhs, m_manager) << "\nnew_rhs:\n" << mk_pp(new_rhs, m_manager) << "\n";); + expr * new_eq = m_manager.mk_eq(new_lhs, new_rhs); + new_literals.push_back(literal(new_eq, aux_lit.sign())); + } + else { + expr_ref new_atom(m_manager); + m_subst.apply(2, m_deltas, expr_offset(aux_lit.atom(), 1), new_atom); + new_literals.push_back(literal(new_atom, aux_lit.sign())); + } + copy_literals(m_clause, m_idx, 0, new_literals); + copy_literals(aux_cls, aux_idx, 1, new_literals); + TRACE("superposition", tout << "found r target: " << mk_pp(r, m_manager) << " for \n" << + mk_pp(m_lhs, m_manager) << "\nmain clause: "; m_clause->display(tout, m_manager); + tout << "\naux clause: "; aux_cls->display(tout, m_manager); tout << "\nat pos: " << + aux_idx << "\n";); + mk_sp_clause(new_literals.size(), new_literals.c_ptr(), m_clause->get_justification(), aux_cls->get_justification()); + } + } + + /** + \brief Try to apply superposition rule using the clause + being added (m_clause) as the aux clause, and its literal m_lit + as the target. + */ + void superposition::try_superposition_aux() { + TRACE("superposition_aux", tout << "superposition aux:\n"; m_clause->display(tout, m_manager); + tout << "\nusing literal: " << m_idx << "\n";); + if (m_manager.is_eq(m_lit->atom())) { + expr * lhs = m_lit->lhs(); + expr * rhs = m_lit->rhs(); + if (m_lit->is_oriented()) { + if (!m_lit->is_left()) + std::swap(lhs, rhs); + try_superposition_aux(lhs, rhs); + } + else { + try_superposition_aux(lhs, rhs); + try_superposition_aux(rhs, lhs); + } + } + else { + try_superposition_aux(m_lit->atom(), 0); + } + } + + /** + \brief Use the clause being added as the auxiliary clause in the superposition rule. + */ + void superposition::try_superposition_aux(expr * lhs, expr * rhs) { + TRACE("superposition_aux", tout << "try_superposition_aux\n" << mk_pp(lhs, m_manager) << "\n" << mk_pp(rhs, m_manager) << "\n";); + if (is_var(lhs)) + return; + m_lhs = lhs; + m_rhs = rhs; + SASSERT(m_todo.empty()); + m_todo.push_back(to_app(lhs)); + while (!m_todo.empty()) { + m_target = m_todo.back(); + m_todo.pop_back(); + m_subst.reset_subst(); + p_visitor v(*this, m_subst); + TRACE("superposition_aux", tout << "trying to find unifier for:\n" << mk_pp(m_target, m_manager) << "\n";); + m_p.unify(m_target, v); + unsigned j = m_target->get_num_args(); + while (j > 0) { + --j; + expr * arg = m_target->get_arg(j); + if (is_app(arg)) + m_todo.push_back(to_app(arg)); + } + } + } + + void superposition::found_p(expr * p) { + TRACE("superposition_found_p", tout << "found p:\n" << mk_pp(p, m_manager) << "\n";); + if (m_p2clause_set.empty(p)) { + TRACE("superposition_found_p", tout << "clause set is empty.\n";); + return; + } + if (m_rhs && !m_lit->is_oriented() && m_order.greater(m_rhs, m_lhs, &m_subst)) { + TRACE("superposition_found_p", tout << "aux clause failed not rhs > lhs constraint.\n";); + return; + } + if (!m_clause->is_eligible_for_resolution(m_order, *m_lit, 0, &m_subst)) { + TRACE("superposition_found_p", tout << "aux literal is not eligible for resolution.\n";); + return; + } + p2clause_set::iterator it = m_p2clause_set.begin(p); + p2clause_set::iterator end = m_p2clause_set.end(p); + for (; it != end; ++it) { + clause_pos_pair & pair = *it; + clause * main_cls = pair.first; + TRACE("superposition_found_p", tout << "p clause:\n"; main_cls->display(tout, m_manager); tout << "\n";); + unsigned lit_idx = pair.second; + if (p == m_lhs && m_clause == main_cls && m_idx == lit_idx) + continue; + literal const & main_lit = main_cls->get_literal(lit_idx); + SASSERT(m_manager.is_eq(main_lit.atom())); + expr * lhs = main_lit.lhs(); + expr * rhs = main_lit.rhs(); + if (rhs == p) + std::swap(lhs, rhs); + SASSERT(lhs == p); + TRACE("superposition_found_p", tout << "lhs: " << mk_pp(lhs, m_manager) << "\nrhs: " << mk_pp(rhs, m_manager) << "\n";); + if (!main_lit.is_oriented() && m_order.greater(rhs, lhs, 1, &m_subst)) + continue; + if (!main_cls->is_eligible_for_paramodulation(m_order, main_lit, 1, &m_subst)) + continue; + literal_buffer new_literals(m_manager); + m_subst.reset_cache(); + TRACE("superposition_found_p", tout << "creating new_lhs\n";); + expr_ref new_lhs(m_manager); + m_subst.apply(2, m_deltas, expr_offset(m_lhs, 0), expr_offset(m_target, 0), expr_offset(rhs, 1), new_lhs); + // FIX: m_subst.reset_cache(); + TRACE("superposition_found_p", tout << "new_lhs: " << mk_pp(new_lhs, m_manager) << "\n"; + m_subst.display(tout);); + expr * new_atom = 0; + if (m_rhs) { + TRACE("superposition_found_p", tout << "creating new_rhs\n";); + expr_ref new_rhs(m_manager); + m_subst.apply(2, m_deltas, expr_offset(m_rhs, 0), new_rhs); + TRACE("superposition_found_p", tout << "new_rhs: " << mk_pp(new_rhs, m_manager) << "\n";); + new_atom = m_manager.mk_eq(new_lhs, new_rhs); + } + else + new_atom = new_lhs; + TRACE("superposition_found_p", tout << "new_atom: " << mk_pp(new_atom, m_manager) << "\n"; m_subst.display(tout);); + new_literals.push_back(literal(new_atom, m_lit->sign())); + TRACE("superposition_found_p", tout << "copying literals\n";); + copy_literals(main_cls, lit_idx, 1, new_literals); + copy_literals(m_clause, m_idx, 0, new_literals); + TRACE("superposition", tout << "found p target: " << mk_pp(p, m_manager) << " for \n" << + mk_pp(m_lhs, m_manager) << "\nmain clause: "; main_cls->display(tout, m_manager); + tout << "\naux clause: "; m_clause->display(tout, m_manager); tout << "\n";); + mk_sp_clause(new_literals.size(), new_literals.c_ptr(), main_cls->get_justification(), m_clause->get_justification()); + } + } + + /** + \brief Try to apply resolution rule using the clause being added (m_clause). + */ + void superposition::try_resolution() { + m_subst.reset_subst(); + res_visitor v(*this, m_subst); + m_r.unify(m_lit->atom(), v); + } + + void superposition::found_res(expr * r) { + if (m_r2clause_set.empty(r)) + return; + if (!m_clause->is_eligible_for_resolution(m_order, *m_lit, 0, &m_subst)) + return; + r2clause_set::iterator it = m_r2clause_set.begin(r); + r2clause_set::iterator end = m_r2clause_set.end(r); + for (; it != end; ++it) { + clause_pos_pair & pair = *it; + clause * aux_cls = pair.first; + unsigned aux_idx = pair.second >> 1; + literal const & aux_lit = aux_cls->get_literal(aux_idx); + if (aux_lit.sign() == m_lit->sign()) + continue; + if (aux_lit.atom() != r) + continue; + if (!aux_cls->is_eligible_for_resolution(m_order, aux_lit, 1, &m_subst)) + continue; + literal_buffer new_literals(m_manager); + m_subst.reset_cache(); + copy_literals(m_clause, m_idx, 0, new_literals); + copy_literals(aux_cls, aux_idx, 1, new_literals); + mk_res_clause(new_literals.size(), new_literals.c_ptr(), m_clause->get_justification(), aux_cls->get_justification()); + } + } + + void superposition::operator()(clause * cls, ptr_vector & new_clauses) { + m_subst.reserve_vars(cls->get_num_vars()); + m_clause = cls; + m_new_clauses = &new_clauses; + SASSERT(m_deltas[0] == 0); + m_deltas[1] = m_clause->get_num_vars(); + unsigned num_lits = cls->get_num_literals(); + for (m_idx = 0; m_idx < num_lits; m_idx++) { + m_lit = &(cls->get_literal(m_idx)); + bool is_eq = m_manager.is_eq(m_lit->atom()); + if (!m_lit->sign() && m_lit->is_p_indexed() && is_eq) + try_superposition_main(); + if (m_lit->is_r_indexed()) { + try_superposition_aux(); + if (!is_eq) + try_resolution(); + } + } + } + +}; + + diff --git a/lib/spc_superposition.h b/lib/spc_superposition.h new file mode 100644 index 000000000..622b95358 --- /dev/null +++ b/lib/spc_superposition.h @@ -0,0 +1,147 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_superposition.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-15. + +Revision History: + +--*/ +#ifndef _SPC_SUPERPOSITION_H_ +#define _SPC_SUPERPOSITION_H_ + +#include"spc_clause.h" +#include"spc_clause_pos_set.h" +#include"substitution_tree.h" +#include"obj_hashtable.h" +#include"sparse_use_list.h" +#include"normalize_vars.h" +#include"spc_statistics.h" + +namespace spc { + + /** + \brief Functor for applying the superposition right/left rules. + + - Superposition Left + s = t or S, u != v or R + ==> + sigma(u[p<-t] != v or S or R) + + sigma is the mgu(u|p, s) + sigma(s) not greater than sigma(t) + sigma(u) not greater than sigma(v) + sigma(s = t) is eligible for paramodulation + sigma(u != v) is eligible for resolution + u|p is not a variable + + + - Superposition Right + s = t or S, u = v or R + ==> + sigma(u[p<-t] != v or S or R) + + Same restrictions of Superposition Left + + This functor also applied binary resolution rule. + + We say the left clause is the main clause in the superposition. + */ + class superposition { + ast_manager & m_manager; + order & m_order; + statistics & m_stats; + substitution m_subst; + + // indexes for left clause + substitution_tree m_p; // potential left hand sides for superposition + typedef sparse_use_list > p2clause_set; + p2clause_set m_p2clause_set; + + void insert_p(clause * cls, expr * lhs, unsigned i); + void insert_p(clause * cls, literal & l, unsigned i); + + void erase_p(clause * cls, expr * lhs, unsigned i); + void erase_p(clause * cls, literal & l, unsigned i); + + // indexes for right clause + substitution_tree m_r; // potential targets for superposition + typedef sparse_use_list r2clause_set; + r2clause_set m_r2clause_set; + ptr_vector m_todo; + + void insert_r(clause * cls, expr * n, unsigned i, bool lhs); + void insert_r(clause * cls, literal & l, unsigned i); + void erase_r(clause * cls, literal & l, unsigned i); + + normalize_vars m_normalize_vars; + + // temporary fields... + ptr_vector * m_new_clauses; + clause * m_clause; + literal * m_lit; + expr * m_lhs; + expr * m_rhs; + app * m_target; + unsigned m_idx; + unsigned m_deltas[2]; + family_id m_spc_fid; + + void normalize_literals(unsigned num_lits, literal * lits, literal_buffer & result); + void copy_literals(clause * s, unsigned idx, unsigned offset, literal_buffer & result); + void mk_sp_clause(unsigned num_lits, literal * lits, justification * p1, justification * p2); + void mk_res_clause(unsigned num_lits, literal * lits, justification * p1, justification * p2); + void try_superposition_main(expr * lhs, expr * rhs); + void try_superposition_main(); + void found_r(expr * r); + void try_superposition_aux(expr * lhs, expr * rhs); + void try_superposition_aux(); + void found_p(expr * p); + void try_resolution(); + void found_res(expr * r); + + friend struct r_visitor; + struct r_visitor : public st_visitor { + superposition & m_owner; + r_visitor(superposition & o, substitution & s):st_visitor(s), m_owner(o) {} + virtual bool operator()(expr * e) { m_owner.found_r(e); return true; /* continue */ } + }; + + friend struct p_visitor; + struct p_visitor : public st_visitor { + superposition & m_owner; + p_visitor(superposition & o, substitution & s):st_visitor(s), m_owner(o) {} + virtual bool operator()(expr * e) { m_owner.found_p(e); return true; /* continue */ } + }; + + friend struct res_visitor; + struct res_visitor : public st_visitor { + superposition & m_owner; + res_visitor(superposition & o, substitution & s):st_visitor(s), m_owner(o) {} + virtual bool operator()(expr * e) { m_owner.found_res(e); return true; /* continue */ } + }; + + public: + superposition(ast_manager & m, order & o, statistics & stats); + ~superposition(); + + void insert(clause * cls); + void erase(clause * cls); + void reset(); + + void operator()(clause * cls, ptr_vector & new_clauses); + }; + +}; + +#endif /* _SPC_SUPERPOSITION_H_ */ + diff --git a/lib/spc_unary_inference.cpp b/lib/spc_unary_inference.cpp new file mode 100644 index 000000000..c5088b734 --- /dev/null +++ b/lib/spc_unary_inference.cpp @@ -0,0 +1,52 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_unary_inference.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-14. + +Revision History: + +--*/ +#include"spc_unary_inference.h" + +namespace spc { + + unary_inference::unary_inference(ast_manager & m, order & ord): + m_manager(m), + m_order(ord), + m_subst(m), + m_unifier(m) { + m_subst.reserve_offsets(1); + } + + /** + \brief Create the result clause. The literal at position j of \c cls in removed, + and the substitution m_subst is applied to the resultant clause. + */ + clause * unary_inference::mk_result(clause * cls, unsigned j) { + sbuffer new_literals; + unsigned num = cls->get_num_literals(); + for (unsigned i = 0; i < num; i++) { + if (i != j) { + literal const & l = cls->get_literal(i); + expr_ref new_atom(m_manager); + m_subst.apply(l.atom(), new_atom); + new_literals.push_back(literal(new_atom, l.sign())); + } + } + + justification * js = mk_justification(cls->get_justification(), new_literals.size(), new_literals.c_ptr()); + clause * new_cls = clause::mk(m_manager, new_literals.size(), new_literals.c_ptr(), js, cls->get_scope_lvl()); + return new_cls; + } + +}; diff --git a/lib/spc_unary_inference.h b/lib/spc_unary_inference.h new file mode 100644 index 000000000..5be156636 --- /dev/null +++ b/lib/spc_unary_inference.h @@ -0,0 +1,48 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + spc_unary_inference.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-14. + +Revision History: + +--*/ +#ifndef _SPC_UNARY_INFERENCE_H_ +#define _SPC_UNARY_INFERENCE_H_ + +#include"spc_clause.h" +#include"unifier.h" + +namespace spc { + + /** + \brief Superclass for eq_resolution and factoring. + */ + class unary_inference { + protected: + ast_manager & m_manager; + order & m_order; + substitution m_subst; + unifier m_unifier; + + clause * mk_result(clause * cls, unsigned j); + virtual justification * mk_justification(justification * parent, unsigned num_lits, literal * new_lits) = 0; + public: + unary_inference(ast_manager & m, order & ord); + virtual ~unary_inference() {} + }; + +}; + + +#endif /* _SPC_UNARY_INFERENCE_H_ */ + diff --git a/lib/splay_tree.h b/lib/splay_tree.h new file mode 100644 index 000000000..9135a5705 --- /dev/null +++ b/lib/splay_tree.h @@ -0,0 +1,180 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + splay_tree.h + +Abstract: + + Splay trees + +Author: + + Leonardo de Moura (leonardo) 2008-01-31. + +Revision History: + +--*/ +#ifndef _SPLAY_TREE_H_ +#define _SPLAY_TREE_H_ + +#include"util.h" +#include"buffer.h" + +template +class splay_tree : private Compare { + struct cell { + Key m_key; + cell * m_left; + cell * m_right; + + cell():m_left(0), m_right(0) {} + cell(Key const & k, cell * l = 0, cell * r = 0): + m_key(k), m_left(l), m_right(r) {} + }; + + cell * m_root; + int compare(Key const & k1, Key const & k2) const { return Compare::operator()(k1, k2); } + cell * splay(cell * c, Key const & k); + + void display_core(std::ostream & out, cell * c) const { + if (c) { + out << "(" << c->m_key << " "; + display_core(out, c->m_left); + out << " "; + display_core(out, c->m_right); + out << ")"; + } + else + out << "null"; + } + +public: + splay_tree(Compare const & c = Compare()): + Compare(c), + m_root(0) {} + + ~splay_tree() { + m_root = 0; + } + + void insert(Key const & k); + + bool find(Key const & k, Key & r) const; + + void erase(Key const & k); + + void reset(); + + bool empty() const { return m_root == 0; } + + bool singleton() const { return m_root != 0 && m_root->m_left == 0 && m_root->m_right == 0; } + + /** + \brief Visit nodes in the splay tree in ascending order. + The Visitor functor should provide the following methods: + + - bool visit_left(Key const & k) + return true if the left child should be visited + + - bool visit_right(Key const & k) + return true if the right child should be visited + + - void operator()(Key const & k) + do something with the key. + */ + template + void visit_core(Visitor & v) { + typedef std::pair entry; + if (m_root) { + buffer todo; + todo.push_back(entry(m_root, false)); + while (!todo.empty()) { + entry & curr = todo.back(); + cell * c = curr.first; + if (!curr.second) { + curr.second = true; + if (c->m_left && v.visit_left(c->m_key)) + todo.push_back(entry(c->m_left, false)); + } + else { + v(c->m_key); + todo.pop_back(); + if (c->m_right && v.visit_right(c->m_key)) + todo.push_back(entry(c->m_right, false)); + } + } + } + } + + template + struct all_visitor_wrapper { + Visitor & m_visitor; + all_visitor_wrapper(Visitor & v):m_visitor(v) {} + bool visit_right(Key const & k) { return true; } + bool visit_left(Key const & k) { return true; } + void operator()(Key const & k) { m_visitor.operator()(k); } + }; + + /** + \brief Visit all nodes in the splay tree in ascending order. + + - void operator()(Key const & k) + do something with the key pair. + */ + template + void visit(Visitor & v) { + all_visitor_wrapper w(v); + visit_core(w); + } + + template + struct visitor_wrapper { + Visitor & m_visitor; + splay_tree & m_tree; + Key m_key; + visitor_wrapper(Visitor & v, splay_tree & t, Key const & k):m_visitor(v), m_tree(t), m_key(k) {} + bool visit_left(Key const & k) { + return LE || m_tree.compare(k, m_key) > 0; + } + bool visit_right(Key const & k) { + return !LE || m_tree.compare(k, m_key) < 0; + } + void operator()(Key const & k) { + if ((LE && m_tree.compare(k, m_key) <= 0) || + (!LE && m_tree.compare(k, m_key) >= 0)) + m_visitor.operator()(k); + } + }; + + /** + \brief Visit all nodes with keys less than or equal to k. + + - void operator()(Key const & k) + do something with the key. + */ + template + void visit_le(Visitor & v, Key const & k) { + visitor_wrapper w(v, *this, k); + visit_core(w); + } + + /** + \brief Visit all nodes with keys greater than or equal to k. + + - void operator()(Key const & k) + do something with the key. + */ + template + void visit_ge(Visitor & v, Key const & k) { + visitor_wrapper w(v, *this, k); + visit_core(w); + } + + void display(std::ostream & out) const { + display_core(out, m_root); + } +}; + +#endif diff --git a/lib/splay_tree_def.h b/lib/splay_tree_def.h new file mode 100644 index 000000000..d5f1fa032 --- /dev/null +++ b/lib/splay_tree_def.h @@ -0,0 +1,152 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + splay_tree_def.h + +Abstract: + + Splay trees + +Author: + + Leonardo de Moura (leonardo) 2008-01-31. + +Revision History: + +--*/ +#ifndef _SPLAY_TREE_DEF_H_ +#define _SPLAY_TREE_DEF_H_ + +#include"splay_tree.h" + +template +typename splay_tree::cell * splay_tree::splay(cell * root, Key const & k) { + if (!root) + return 0; + + cell aux; + cell * tmp; + cell * left = &aux; + cell * right = &aux; + cell * t = root; + + while (true) { + int r = compare(k, t->m_key); + if (r < 0) { + if (!t->m_left) + break; + if (compare(k, t->m_left->m_key) < 0) { + tmp = t->m_left; + t->m_left = tmp->m_right; + tmp->m_right = t; + t = tmp; + if (!t->m_left) + break; + } + right->m_left = t; + right = t; + t = t->m_left; + } + else if (r > 0) { + if (!t->m_right) + break; + if (compare(k, t->m_right->m_key) > 0) { + tmp = t->m_right; + t->m_right = tmp->m_left; + tmp->m_left = t; + t = tmp; + if (!t->m_right) + break; + + } + left->m_right = t; + left = t; + t = t->m_right; + } + else + break; + } + + left->m_right = t->m_left; + right->m_left = t->m_right; + t->m_left = aux.m_right; + t->m_right = aux.m_left; + + return t; +} + +template +void splay_tree::insert(Key const & k) { + if (!m_root) + m_root = alloc(cell, k); + else { + m_root = splay(m_root, k); + int r = compare(k, m_root->m_key); + if (r < 0) { + cell * new_cell = alloc(cell, k, m_root->m_left, m_root); + m_root->m_left = 0; + m_root = new_cell; + } + else if (r > 0) { + cell * new_cell = alloc(cell, k, m_root, m_root->m_right); + m_root->m_right = 0; + m_root = new_cell; + } + else + m_root->m_key = k; + } +} + +template +bool splay_tree::find(Key const & k, Key & r) const { + if (m_root) { + splay_tree * _this = const_cast *>(this); + _this->m_root = _this->splay(m_root, k); + if (compare(k, m_root->m_key) == 0) { + r = m_root->m_key; + return true; + } + } + return false; +} + +template +void splay_tree::erase(Key const & k) { + if (m_root) { + m_root = splay(m_root, k); + if (compare(k, m_root->m_key) == 0) { + cell * to_delete = m_root; + if (m_root->m_left) { + cell * aux = splay(m_root->m_left, k); + SASSERT(!aux->m_right); + aux->m_right = m_root->m_right; + m_root = aux; + } + else + m_root = m_root->m_right; + + dealloc(to_delete); + } + } +} + +template +void splay_tree::reset() { + ptr_buffer todo; + if (m_root) + todo.push_back(m_root); + while (!todo.empty()) { + cell * c = todo.back(); + todo.pop_back(); + if (c->m_left) + todo.push_back(c->m_left); + if (c->m_right) + todo.push_back(c->m_right); + dealloc(c); + } + m_root = 0; +} + +#endif /* _SPLAY_TREE_DEF_H_ */ diff --git a/lib/splay_tree_map.h b/lib/splay_tree_map.h new file mode 100644 index 000000000..89a82bd30 --- /dev/null +++ b/lib/splay_tree_map.h @@ -0,0 +1,114 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + splay_tree_map.h + +Abstract: + + A mapping as a splay tree. + +Author: + + Leonardo de Moura (leonardo) 2008-02-02. + +Revision History: + +--*/ +#ifndef _SPLAY_TREE_MAP_H_ +#define _SPLAY_TREE_MAP_H_ + +#include"splay_tree.h" + +template +class splay_tree_map { + typedef std::pair entry; + + struct entry_compare : private Compare { + entry_compare(Compare const & c):Compare(c) {} + int operator()(entry const & e1, entry const & e2) const { + return Compare::operator()(e1.first, e2.first); + } + }; + + typedef splay_tree tree; + + tree m_tree; + + template + struct core_visitor_wrapper { + Visitor & m_visitor; + core_visitor_wrapper(Visitor & v):m_visitor(v) {} + bool visit_right(entry const & k) { return m_visitor.visit_right(k.first); } + bool visit_left(entry const & k) { return m_visitor.visit_left(k.first); } + void operator()(entry const & k) { m_visitor.operator()(k.first, k.second); } + }; + + template + struct visitor_wrapper { + Visitor & m_visitor; + visitor_wrapper(Visitor & v):m_visitor(v) {} + void operator()(entry const & k) { m_visitor.operator()(k.first, k.second); } + }; + +public: + splay_tree_map(Compare const & c = Compare()): + m_tree(entry_compare(c)) {} + + void insert(Key const & k, Data const & d) { + m_tree.insert(entry(k, d)); + } + + bool find(Key const & k, Data & r) const { + entry e(k, r); + if (m_tree.find(e, e)) { + r = e.second; + return true; + } + return false; + } + + void erase(Key const & k) { + entry e; + e.first = k; + m_tree.erase(e); + } + + void reset() { m_tree.reset(); } + + bool empty() const { return m_tree.empty(); } + + void display(std::ostream & out) const { m_tree.display(out); } + + template + void visit_core(Visitor & v) { + core_visitor_wrapper w(v); + m_tree.visit_core(w); + } + + template + void visit(Visitor & v) { + visitor_wrapper w(v); + m_tree.visit(w); + } + + template + void visit_le(Visitor & v, Key const & k) { + visitor_wrapper w(v); + entry e; + e.first = k; + m_tree.visit_le(w, e); + } + + template + void visit_ge(Visitor & v, Key const & k) { + visitor_wrapper w(v); + entry e; + e.first = k; + m_tree.visit_ge(w, e); + } +}; + +#endif /* _SPLAY_TREE_MAP_H_ */ + diff --git a/lib/split_clause_tactic.cpp b/lib/split_clause_tactic.cpp new file mode 100644 index 000000000..f259ca8ad --- /dev/null +++ b/lib/split_clause_tactic.cpp @@ -0,0 +1,150 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + split_clause_tactic.cpp + +Abstract: + + Tactic that creates a subgoal for each literal in a clause (l_1 or ... or l_n). + The tactic fails if the main goal does not contain any clause. + +Author: + + Leonardo (leonardo) 2011-11-21 + +Notes: + +--*/ +#include"tactical.h" +#include"split_clause_tactic.h" + +class split_clause_tactic : public tactic { + bool m_largest_clause; + + unsigned select_clause(ast_manager & m, goal_ref const & in) { + unsigned result_idx = UINT_MAX; + unsigned len = 0; + unsigned sz = in->size(); + for (unsigned i = 0; i < sz; i++) { + expr * f = in->form(i); + if (m.is_or(f)) { + unsigned curr_len = to_app(f)->get_num_args(); + if (curr_len >= 2) { + // consider only non unit clauses + if (!m_largest_clause) + return i; + if (curr_len > len) { + result_idx = i; + len = curr_len; + } + } + } + } + return result_idx; + } + + class split_pc : public proof_converter { + ast_manager & m_manager; + app * m_clause; + proof * m_clause_pr; + public: + split_pc(ast_manager & m, app * cls, proof * pr):m_manager(m), m_clause(cls), m_clause_pr(pr) { + m.inc_ref(cls); + m.inc_ref(pr); + } + + ~split_pc() { + m_manager.dec_ref(m_clause); + m_manager.dec_ref(m_clause_pr); + } + + virtual void operator()(ast_manager & m, unsigned num_source, proof * const * source, proof_ref & result) { + // Let m_clause be of the form (l_0 or ... or l_{num_source - 1}) + // Each source[i] proof is a proof for "false" using l_i as a hypothesis + // So, I use lemma for producing a proof for (not l_i) that does not contain the hypothesis, + // and unit_resolution for building a proof for the goal. + SASSERT(num_source == m_clause->get_num_args()); + proof_ref_buffer prs(m); + prs.push_back(m_clause_pr); + for (unsigned i = 0; i < num_source; i++) { + proof * pr_i = source[i]; + expr * not_li = m.mk_not(m_clause->get_arg(i)); + prs.push_back(m.mk_lemma(pr_i, not_li)); + } + result = m.mk_unit_resolution(prs.size(), prs.c_ptr()); + } + + virtual proof_converter * translate(ast_translation & translator) { + return alloc(split_pc, translator.to(), translator(m_clause), translator(m_clause_pr)); + } + }; + +public: + split_clause_tactic(params_ref const & ref = params_ref()) { + updt_params(ref); + } + + virtual tactic * translate(ast_manager & m) { + split_clause_tactic * t = alloc(split_clause_tactic); + t->m_largest_clause = m_largest_clause; + return t; + } + + virtual ~split_clause_tactic() { + } + + virtual void updt_params(params_ref const & p) { + m_largest_clause = p.get_bool(":split-largest-clause", false); + } + + virtual void collect_param_descrs(param_descrs & r) { + r.insert(":split-largest-clause", CPK_BOOL, "(default: false) split the largest clause in the goal."); + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(in->is_well_sorted()); + tactic_report report("split-clause", *in); + TRACE("before_split_clause", in->display(tout);); + pc = 0; mc = 0; core = 0; + ast_manager & m = in->m(); + unsigned cls_pos = select_clause(m, in); + if (cls_pos == UINT_MAX) { + throw tactic_exception("split-clause tactic failed, goal does not contain any clause"); + } + bool produce_proofs = in->proofs_enabled(); + app * cls = to_app(in->form(cls_pos)); + expr_dependency * cls_dep = in->dep(cls_pos); + if (produce_proofs) + pc = alloc(split_pc, m, cls, in->pr(cls_pos)); + unsigned cls_sz = cls->get_num_args(); + report_tactic_progress(":num-new-branches", cls_sz); + for (unsigned i = 0; i < cls_sz; i++) { + goal * subgoal_i; + if (i == cls_sz - 1) + subgoal_i = in.get(); + else + subgoal_i = alloc(goal, *in); + expr * lit_i = cls->get_arg(i); + proof * pr_i = 0; + if (produce_proofs) + pr_i = m.mk_hypothesis(lit_i); + subgoal_i->update(cls_pos, lit_i, pr_i, cls_dep); + subgoal_i->inc_depth(); + result.push_back(subgoal_i); + } + } + + virtual void cleanup() { + // do nothing this tactic is too simple + } +}; + +tactic * mk_split_clause_tactic(params_ref const & p) { + return clean(alloc(split_clause_tactic, p)); +} diff --git a/lib/split_clause_tactic.h b/lib/split_clause_tactic.h new file mode 100644 index 000000000..695e0d700 --- /dev/null +++ b/lib/split_clause_tactic.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + split_clause_tactic.h + +Abstract: + + Tactic that creates a subgoal for each literal in a clause (l_1 or ... or l_n). + The tactic fails if the main goal does not contain any clause. + +Author: + + Leonardo (leonardo) 2011-11-21 + +Notes: + +--*/ +#ifndef _SPLIT_CLAUSE_TACTIC_H_ +#define _SPLIT_CLAUSE_TACTIC_H_ + +#include"params.h" +class tactic; + +tactic * mk_split_clause_tactic(params_ref const & p = params_ref()); + +#endif diff --git a/lib/st2tactic.cpp b/lib/st2tactic.cpp new file mode 100644 index 000000000..a880c8ebe --- /dev/null +++ b/lib/st2tactic.cpp @@ -0,0 +1,77 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + st2tactic.h + +Abstract: + + Temporary adapter that converts a assertion_set_strategy into a tactic. + +Author: + + Leonardo (leonardo) 2012-02-19 + +Notes: + +--*/ +#include"assertion_set_strategy.h" +#include"tactic.h" + +class st2tactic_wrapper : public tactic { + assertion_set_strategy * m_st; + params_ref m_params; +public: + st2tactic_wrapper(assertion_set_strategy * st):m_st(st) {} + ~st2tactic_wrapper() { dealloc(m_st); } + + virtual tactic * translate(ast_manager & m) { + // st2tactic_wrapper is a temporary hack to support the old strategy framework. + // This class will be deleted in the future. + UNREACHABLE(); + NOT_IMPLEMENTED_YET(); + return 0; + } + + virtual void operator()(goal_ref const & g, goal_ref_buffer & result, model_converter_ref & mc, proof_converter_ref & pc, expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + pc = 0; mc = 0; core = 0; + fail_if_unsat_core_generation("st2tactic", g); + assertion_set s(g->m()); + for (unsigned i = 0; i < g->size(); i++) + s.assert_expr(g->form(i), g->pr(i)); + if (g->models_enabled()) { + params_ref mp = m_params; + mp.set_bool(":produce-models", true); + m_st->updt_params(mp); + } + try { + (*m_st)(s, mc); + } + catch (strategy_exception & ex) { + throw tactic_exception(ex.msg()); + } + g->reset(); + for (unsigned i = 0; i < s.size(); i++) { + g->assert_expr(s.form(i), s.pr(i), 0); + } + g->inc_depth(); + result.push_back(g.get()); + SASSERT(g->is_well_sorted()); + } + + virtual void updt_params(params_ref const & p) { m_params = p; m_st->updt_params(p); } + virtual void collect_param_descrs(param_descrs & r) { m_st->collect_param_descrs(r); } + virtual void cleanup() { m_st->cleanup(); } + virtual void set_cancel(bool f) { m_st->set_cancel(f); } + virtual void collect_statistics(statistics & st) const { m_st->collect_statistics(st); } + virtual void reset_statistics() { m_st->reset_statistics(); } + virtual void set_front_end_params(front_end_params & p) { m_st->set_front_end_params(p); } + virtual void set_logic(symbol const & l) { m_st->set_logic(l); } + virtual void set_progress_callback(progress_callback * callback) { m_st->set_progress_callback(callback); } +}; + +tactic * st2tactic(assertion_set_strategy * st) { + return alloc(st2tactic_wrapper, st); +} diff --git a/lib/st2tactic.h b/lib/st2tactic.h new file mode 100644 index 000000000..68b6f35be --- /dev/null +++ b/lib/st2tactic.h @@ -0,0 +1,27 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + st2tactic.h + +Abstract: + + Temporary adapter that converts a assertion_set_strategy into a tactic. + +Author: + + Leonardo (leonardo) 2012-02-19 + +Notes: + +--*/ +#ifndef _ST2TACTIC_H_ +#define _ST2TACTIC_H_ + +class tactic; +class assertion_set_strategy; + +tactic * st2tactic(assertion_set_strategy * st); + +#endif diff --git a/lib/st_cmds.h b/lib/st_cmds.h new file mode 100644 index 000000000..6ae40b546 --- /dev/null +++ b/lib/st_cmds.h @@ -0,0 +1,26 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + st_cmds.h + +Abstract: + + Commands for testing strategies. + +Author: + + Leonardo de Moura (leonardo) 2011-04-27 + +Revision History: + +--*/ +#ifndef _ST_CMD_H_ +#define _ST_CMD_H_ + +class cmd_context; + +void install_st_cmds(cmd_context & ctx); + +#endif diff --git a/lib/stack.cpp b/lib/stack.cpp new file mode 100644 index 000000000..3c442d74b --- /dev/null +++ b/lib/stack.cpp @@ -0,0 +1,148 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + stack.cpp + +Abstract: + Low level stack (aka stack-based allocator). + +Author: + + Leonardo (leonardo) 2011-02-27 + +Notes: + +--*/ +#include"stack.h" +#include"page.h" +#include"tptr.h" + +inline void stack::store_mark(size_t m) { + reinterpret_cast(m_curr_ptr)[0] = m; + m_curr_ptr += sizeof(size_t); +} + +inline size_t stack::top_mark() const { + return reinterpret_cast(m_curr_ptr)[-1]; +} + +inline size_t ptr2mark(void * ptr, bool external) { + return reinterpret_cast(ptr) | static_cast(external); +} + +#define MASK (static_cast(-1) - 1) + +inline char * mark2ptr(size_t m) { + return reinterpret_cast(m & MASK); +} + +inline bool external_ptr(size_t m) { + return static_cast(m & 1); +} + +inline void stack::allocate_page(size_t m) { + m_curr_page = allocate_default_page(m_curr_page, m_free_pages); + m_curr_ptr = m_curr_page; + m_curr_end_ptr = end_of_default_page(m_curr_page); + store_mark(m); +} + +inline void stack::store_mark(void * ptr, bool external) { + SASSERT(m_curr_ptr < m_curr_end_ptr || m_curr_ptr == m_curr_end_ptr); // mem is aligned + if (m_curr_ptr + sizeof(size_t) > m_curr_end_ptr) { + SASSERT(m_curr_ptr == m_curr_end_ptr); + // doesn't fit in the current page + allocate_page(ptr2mark(ptr, external)); + } + else { + store_mark(ptr2mark(ptr, external)); + } +} + +stack::stack() { + m_curr_page = 0; + m_curr_ptr = 0; + m_curr_end_ptr = 0; + m_free_pages = 0; + allocate_page(0); + SASSERT(empty()); +} + +stack::~stack() { + reset(); + del_pages(m_curr_page); + del_pages(m_free_pages); +} + +void stack::reset() { + while(!empty()) + deallocate(); +} + +void * stack::top() const { + SASSERT(!empty()); + size_t m = top_mark(); + void * r = mark2ptr(m); + if (external_ptr(m)) + r = reinterpret_cast(r)[0]; + return r; +} + +void * stack::allocate_small(size_t size, bool external) { + SASSERT(size < DEFAULT_PAGE_SIZE); + char * new_curr_ptr = m_curr_ptr + size; + char * result; + if (new_curr_ptr < m_curr_end_ptr) { + result = m_curr_ptr; + m_curr_ptr = ALIGN(char *, new_curr_ptr); + } + else { + allocate_page(top_mark()); + result = m_curr_ptr; + m_curr_ptr += size; + m_curr_ptr = ALIGN(char *, m_curr_ptr); + } + store_mark(result, external); + SASSERT(m_curr_ptr > m_curr_page); + SASSERT(m_curr_ptr <= m_curr_end_ptr); + return result; +} + +void * stack::allocate_big(size_t size) { + char * r = alloc_svect(char, size); + void * mem = allocate_small(sizeof(char*), true); + reinterpret_cast(mem)[0] = r; + SASSERT(m_curr_ptr > m_curr_page); + SASSERT(m_curr_ptr <= m_curr_end_ptr); + return r; +} + +void stack::deallocate() { + SASSERT(m_curr_ptr > m_curr_page); + SASSERT(m_curr_ptr <= m_curr_end_ptr); + size_t m = top_mark(); + SASSERT(m != 0); + if (m_curr_ptr == m_curr_page + sizeof(size_t)) { + // mark is in the beginning of the page + char * prev = prev_page(m_curr_page); + recycle_page(m_curr_page, m_free_pages); + m_curr_page = prev; + m_curr_ptr = mark2ptr(m); + m_curr_end_ptr = end_of_default_page(m_curr_page); + SASSERT(m_curr_ptr > m_curr_page); + SASSERT(m_curr_ptr <= m_curr_end_ptr); + } + else { + // mark is in the middle of the page + m_curr_ptr = mark2ptr(m); + SASSERT(m_curr_ptr < m_curr_end_ptr); + } + if (external_ptr(m)) { + dealloc_svect(reinterpret_cast(m_curr_ptr)[0]); + } + SASSERT(m_curr_ptr > m_curr_page); +} + + diff --git a/lib/stack.h b/lib/stack.h new file mode 100644 index 000000000..deab2f273 --- /dev/null +++ b/lib/stack.h @@ -0,0 +1,50 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + stack.h + +Abstract: + Low level stack (aka stack-based allocator). + +Author: + + Leonardo (leonardo) 2011-02-27 + +Notes: + +--*/ +#ifndef _STACK_H_ +#define _STACK_H_ + +#include"page.h" +#include"debug.h" + +class stack { + char * m_curr_page; + char * m_curr_ptr; //!< Next free space in the current page. + char * m_curr_end_ptr; //!< Point to the end of the current page. + char * m_free_pages; + void store_mark(size_t m); + void store_mark(void * ptr, bool external); + size_t top_mark() const; + void allocate_page(size_t mark); + void * allocate_small(size_t size, bool external); + void * allocate_big(size_t size); +public: + stack(); + ~stack(); + void * allocate(size_t size) { return size < DEFAULT_PAGE_SIZE ? allocate_small(size, false) : allocate_big(size); } + void deallocate(); + bool empty() const { return reinterpret_cast(m_curr_ptr)[-1] == 0; } + void * top() const; + void reset(); + template + void deallocate(T * ptr) { SASSERT(ptr == top()); ptr->~T(); deallocate(); } +}; + +inline void * operator new(size_t s, stack & r) { return r.allocate(s); } +inline void operator delete(void * ptr, stack & r) { SASSERT(ptr == r.top()); r.deallocate(); } + +#endif diff --git a/lib/static_features.cpp b/lib/static_features.cpp new file mode 100644 index 000000000..7de27ca62 --- /dev/null +++ b/lib/static_features.cpp @@ -0,0 +1,601 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + static_features.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-05-16. + +Revision History: + +--*/ +#include"static_features.h" +#include"ast_pp.h" + +static_features::static_features(ast_manager & m): + m_manager(m), + m_autil(m), + m_bfid(m.get_basic_family_id()), + m_afid(m.get_family_id("arith")), + m_lfid(m.get_family_id("label")), + m_label_sym("label"), + m_pattern_sym("pattern"), + m_expr_list_sym("expr-list") { + reset(); +} + +void static_features::reset() { + m_already_visited .reset(); + m_cnf = true; + m_num_exprs = 0; + m_num_roots = 0; + m_max_depth = 0; + m_num_quantifiers = 0; + m_num_quantifiers_with_patterns = 0; + m_num_quantifiers_with_multi_patterns = 0; + m_num_clauses = 0; + m_num_bin_clauses = 0; + m_num_units = 0; + m_sum_clause_size = 0; + m_num_nested_formulas = 0; + m_num_bool_exprs = 0; + m_num_bool_constants = 0; + m_num_formula_trees = 0; + m_max_formula_depth = 0; + m_sum_formula_depth = 0; + m_num_or_and_trees = 0; + m_max_or_and_tree_depth = 0; + m_sum_or_and_tree_depth = 0; + m_num_ite_trees = 0; + m_max_ite_tree_depth = 0; + m_sum_ite_tree_depth = 0; + m_num_ors = 0; + m_num_ands = 0; + m_num_iffs = 0; + m_num_ite_formulas = 0; + m_num_ite_terms = 0; + m_num_sharing = 0; + m_num_interpreted_exprs = 0; + m_num_uninterpreted_exprs = 0; + m_num_interpreted_constants = 0; + m_num_uninterpreted_constants = 0; + m_num_uninterpreted_functions = 0; + m_num_eqs = 0; + m_has_rational = false; + m_has_int = false; + m_has_real = false; + m_arith_k_sum .reset(); + m_num_arith_terms = 0; + m_num_arith_eqs = 0; + m_num_arith_ineqs = 0; + m_num_diff_terms = 0; + m_num_diff_eqs = 0; + m_num_diff_ineqs = 0; + m_num_simple_eqs = 0; + m_num_simple_ineqs = 0; + m_num_non_linear = 0; + m_num_apps .reset(); + m_num_theory_terms .reset(); + m_num_theory_atoms .reset(); + m_num_theory_constants .reset(); + m_num_theory_eqs .reset(); + m_num_aliens = 0; + m_num_aliens_per_family .reset(); + m_num_theories = 0; + m_theories .reset(); + m_max_stack_depth = 500; + flush_cache(); +} + +void static_features::flush_cache() { + m_expr2depth.reset(); + m_expr2or_and_depth.reset(); + m_expr2ite_depth.reset(); + m_expr2formula_depth.reset(); +} + +#if 0 +bool static_features::is_non_linear(expr * e) const { + if (!is_arith_expr(e)) + return false; + if (is_numeral(e)) + return true; + if (m_autil.is_add(e)) + return true; // the non +} +#endif + +bool static_features::is_diff_term(expr const * e, rational & r) const { + // lhs can be 'x' or '(+ k x)' + if (!is_arith_expr(e)) { + r.reset(); + return true; + } + if (is_numeral(e, r)) + return true; + return m_autil.is_add(e) && to_app(e)->get_num_args() == 2 && is_numeral(to_app(e)->get_arg(0), r) && !is_arith_expr(to_app(e)->get_arg(1)); +} + +bool static_features::is_diff_atom(expr const * e) const { + if (!is_bool(e)) + return false; + if (!m_manager.is_eq(e) && !is_arith_expr(e)) + return false; + SASSERT(to_app(e)->get_num_args() == 2); + expr * lhs = to_app(e)->get_arg(0); + SASSERT(is_numeral(to_app(e)->get_arg(1))); + // lhs can be 'x' or '(+ x (* -1 y))' + if (!is_arith_expr(lhs)) + return true; + SASSERT(is_app(lhs)); + // lhs must be (+ x (* -1 y)) + if (to_app(lhs)->get_decl_kind() != OP_ADD || to_app(lhs)->get_num_args() != 2) + return false; + // x + if (is_arith_expr(to_app(lhs)->get_arg(0))) + return false; + expr * arg2 = to_app(lhs)->get_arg(1); + // arg2: (* -1 y) + return m_autil.is_mul(arg2) && to_app(arg2)->get_num_args() == 2 && is_minus_one(to_app(arg2)->get_arg(0)) && !is_arith_expr(to_app(arg2)->get_arg(1)); +} + +bool static_features::is_gate(expr const * e) const { + if (is_basic_expr(e)) { + switch (to_app(e)->get_decl_kind()) { + case OP_ITE: case OP_AND: case OP_OR: case OP_IFF: case OP_XOR: case OP_IMPLIES: + return true; + } + } + return false; +} + +void static_features::update_core(expr * e) { + m_num_exprs++; + + // even if a benchmark does not contain any theory interpreted function decls, we still have to install + // the theory if the benchmark contains constants or function applications of an interpreted sort. + sort * s = m_manager.get_sort(e); + mark_theory(s->get_family_id()); + + bool _is_gate = is_gate(e); + bool _is_eq = m_manager.is_eq(e); + if (_is_gate) { + m_cnf = false; + m_num_nested_formulas++; + switch (to_app(e)->get_decl_kind()) { + case OP_ITE: + if (is_bool(e)) + m_num_ite_formulas++; + else { + m_num_ite_terms++; + // process then&else nodes + for (unsigned i = 1; i < 3; i++) { + expr * arg = to_app(e)->get_arg(i); + acc_num(arg); + // Must check whether arg is diff logic or not. + // Otherwise, problem can be incorrectly tagged as diff logic. + sort * arg_s = m_manager.get_sort(arg); + family_id fid_arg = arg_s->get_family_id(); + if (fid_arg == m_afid) { + m_num_arith_terms++; + rational k; + TRACE("diff_term", tout << "diff_term: " << is_diff_term(arg, k) << "\n" << mk_pp(arg, m_manager) << "\n";); + if (is_diff_term(arg, k)) { + m_num_diff_terms++; + acc_num(k); + } + } + } + } + break; + case OP_AND: + m_num_ands++; + break; + case OP_OR: + m_num_ors++; + break; + case OP_IFF: + m_num_iffs++; + break; + } + } + if (is_bool(e)) { + m_num_bool_exprs++; + if (is_app(e) && to_app(e)->get_num_args() == 0) + m_num_bool_constants++; + } + if (is_quantifier(e)) { + m_num_quantifiers++; + unsigned num_patterns = to_quantifier(e)->get_num_patterns(); + if (num_patterns > 0) { + m_num_quantifiers_with_patterns++; + for (unsigned i = 0; i < num_patterns; i++) { + expr * p = to_quantifier(e)->get_pattern(i); + if (is_app(p) && to_app(p)->get_num_args() > 1) { + m_num_quantifiers_with_multi_patterns++; + break; + } + } + } + } + bool _is_le_ge = m_autil.is_le(e) || m_autil.is_ge(e); + if (_is_le_ge) { + m_num_arith_ineqs++; + TRACE("diff_atom", tout << "diff_atom: " << is_diff_atom(e) << "\n" << mk_pp(e, m_manager) << "\n";); + if (is_diff_atom(e)) + m_num_diff_ineqs++; + if (!is_arith_expr(to_app(e)->get_arg(0))) + m_num_simple_ineqs++; + acc_num(to_app(e)->get_arg(1)); + } + rational r; + if (is_numeral(e, r)) { + if (!r.is_int()) + m_has_rational = true; + } + if (_is_eq) { + m_num_eqs++; + if (is_numeral(to_app(e)->get_arg(1))) { + acc_num(to_app(e)->get_arg(1)); + m_num_arith_eqs++; + TRACE("diff_atom", tout << "diff_atom: " << is_diff_atom(e) << "\n" << mk_pp(e, m_manager) << "\n";); + if (is_diff_atom(e)) + m_num_diff_eqs++; + if (!is_arith_expr(to_app(e)->get_arg(0))) + m_num_simple_eqs++; + } + sort * s = m_manager.get_sort(to_app(e)->get_arg(0)); + family_id fid = s->get_family_id(); + if (fid != null_family_id && fid != m_bfid) + inc_theory_eqs(fid); + } + if (!m_has_int && m_autil.is_int(e)) + m_has_int = true; + if (!m_has_real && m_autil.is_real(e)) + m_has_real = true; + if (is_app(e)) { + family_id fid = to_app(e)->get_family_id(); + mark_theory(fid); + if (fid != null_family_id && fid != m_bfid) { + m_num_interpreted_exprs++; + if (is_bool(e)) + inc_theory_atoms(fid); + else + inc_theory_terms(fid); + if (to_app(e)->get_num_args() == 0) + m_num_interpreted_constants++; + } + if (fid == m_afid) { + switch (to_app(e)->get_decl_kind()) { + case OP_MUL: + if (!is_numeral(to_app(e)->get_arg(0))) + m_num_non_linear++; + break; + case OP_DIV: + case OP_IDIV: + case OP_REM: + case OP_MOD: + if (!is_numeral(to_app(e)->get_arg(1))) + m_num_non_linear++; + break; + } + } + if (fid == null_family_id) { + m_num_uninterpreted_exprs++; + if (to_app(e)->get_num_args() == 0) { + m_num_uninterpreted_constants++; + sort * s = m_manager.get_sort(e); + family_id fid = s->get_family_id(); + if (fid != null_family_id && fid != m_bfid) + inc_theory_constants(fid); + } + } + func_decl * d = to_app(e)->get_decl(); + inc_num_apps(d); + if (d->get_arity() > 0 && !is_marked(d)) { + mark(d); + if (fid == null_family_id) + m_num_uninterpreted_functions++; + } + if (!_is_eq && !_is_gate) { + unsigned num_args = to_app(e)->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = to_app(e)->get_arg(i); + sort * arg_s = m_manager.get_sort(arg); + family_id fid_arg = arg_s->get_family_id(); + if (fid_arg != fid && fid_arg != null_family_id) { + m_num_aliens++; + inc_num_aliens(fid_arg); + if (fid_arg == m_afid) { + SASSERT(!_is_le_ge); + m_num_arith_terms++; + rational k; + TRACE("diff_term", tout << "diff_term: " << is_diff_term(arg, k) << "\n" << mk_pp(arg, m_manager) << "\n";); + if (is_diff_term(arg, k)) { + m_num_diff_terms++; + acc_num(k); + } + } + } + } + } + } +} + +void static_features::process(expr * e, bool form_ctx, bool or_and_ctx, bool ite_ctx, unsigned stack_depth) { + TRACE("static_features", tout << "processing\n" << mk_pp(e, m_manager) << "\n";); + if (is_var(e)) + return; + if (is_marked(e)) { + m_num_sharing++; + return; + } + if (stack_depth > m_max_stack_depth) { + return; + } + mark(e); + update_core(e); + + + if (is_quantifier(e)) { + expr * body = to_quantifier(e)->get_expr(); + process(body, false, false, false, stack_depth+1); + set_depth(e, get_depth(body)+1); + return; + } + + bool form_ctx_new = false; + bool or_and_ctx_new = false; + bool ite_ctx_new = false; + + if (is_basic_expr(e)) { + switch (to_app(e)->get_decl_kind()) { + case OP_ITE: + form_ctx_new = m_manager.is_bool(e); + ite_ctx_new = true; + break; + case OP_AND: + case OP_OR: + form_ctx_new = true; + or_and_ctx_new = true; + break; + case OP_IFF: + form_ctx_new = true; + break; + } + } + + unsigned depth = 0; + unsigned form_depth = 0; + unsigned or_and_depth = 0; + unsigned ite_depth = 0; + + unsigned num_args = to_app(e)->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = to_app(e)->get_arg(i); + if (m_manager.is_not(arg)) + arg = to_app(arg)->get_arg(0); + process(arg, form_ctx_new, or_and_ctx_new, ite_ctx_new, stack_depth+1); + depth = std::max(depth, get_depth(arg)); + if (form_ctx_new) + form_depth = std::max(form_depth, get_form_depth(arg)); + if (or_and_ctx_new) + or_and_depth = std::max(or_and_depth, get_or_and_depth(arg)); + if (ite_ctx_new) + ite_depth = std::max(ite_depth, get_ite_depth(arg)); + } + + depth++; + set_depth(e, depth); + if (depth > m_max_depth) + m_max_depth = depth; + + if (form_ctx_new) { + form_depth++; + if (!form_ctx) { + m_num_formula_trees++; + m_sum_formula_depth += form_depth; + if (form_depth > m_max_formula_depth) + m_max_formula_depth = form_depth; + } + set_form_depth(e, form_depth); + } + if (or_and_ctx_new) { + or_and_depth++; + if (!or_and_ctx) { + m_num_or_and_trees++; + m_sum_or_and_tree_depth += or_and_depth; + if (or_and_depth > m_max_or_and_tree_depth) + m_max_or_and_tree_depth = or_and_depth; + } + set_or_and_depth(e, or_and_depth); + } + if (ite_ctx_new) { + ite_depth++; + if (!ite_ctx) { + m_num_ite_trees++; + m_sum_ite_tree_depth += ite_depth; + if (ite_depth >= m_max_ite_tree_depth) + m_max_ite_tree_depth = ite_depth; + } + set_ite_depth(e, ite_depth); + } +} + +void static_features::process_root(expr * e) { + if (is_marked(e)) { + m_num_sharing++; + return; + } + m_num_roots++; + if (m_manager.is_or(e)) { + mark(e); + m_num_clauses++; + m_num_bool_exprs++; + unsigned num_args = to_app(e)->get_num_args(); + m_sum_clause_size += num_args; + if (num_args == 2) + m_num_bin_clauses++; + unsigned depth = 0; + unsigned form_depth = 0; + unsigned or_and_depth = 0; + for (unsigned i = 0; i < num_args; i++) { + expr * arg = to_app(e)->get_arg(i); + if (m_manager.is_not(arg)) + arg = to_app(arg)->get_arg(0); + process(arg, true, true, false, 0); + depth = std::max(depth, get_depth(arg)); + form_depth = std::max(form_depth, get_form_depth(arg)); + or_and_depth = std::max(or_and_depth, get_or_and_depth(arg)); + } + depth++; + set_depth(e, depth); + if (depth > m_max_depth) + m_max_depth = depth; + form_depth++; + m_num_formula_trees++; + m_sum_formula_depth += form_depth; + if (form_depth > m_max_formula_depth) + m_max_formula_depth = form_depth; + set_form_depth(e, form_depth); + or_and_depth++; + m_num_or_and_trees++; + m_sum_or_and_tree_depth += or_and_depth; + if (or_and_depth > m_max_or_and_tree_depth) + m_max_or_and_tree_depth = or_and_depth; + set_or_and_depth(e, or_and_depth); + return; + } + if (!is_gate(e)) { + m_sum_clause_size++; + m_num_units++; + m_num_clauses++; + } + process(e, false, false, false, 0); +} + +void static_features::collect(unsigned num_formulas, expr * const * formulas) { + for (unsigned i = 0; i < num_formulas; i++) + process_root(formulas[i]); +} + +bool static_features::internal_family(symbol const & f_name) const { + return f_name == m_label_sym || f_name == m_pattern_sym || f_name == m_expr_list_sym; +} + +void static_features::display_family_data(std::ostream & out, char const * prefix, unsigned_vector const & data) const { + for (unsigned fid = 0; fid < data.size(); fid++) { + symbol const & n = m_manager.get_family_name(fid); + if (!internal_family(n)) + out << prefix << "_" << n << " " << data[fid] << "\n"; + } +} + +bool static_features::has_uf() const { + return m_num_uninterpreted_functions > 0; +} + +unsigned static_features::num_non_uf_theories() const { + return m_num_theories; +} + +unsigned static_features::num_theories() const { + return (num_non_uf_theories() + (has_uf() ? 1 : 0)); +} + +void static_features::display_primitive(std::ostream & out) const { + out << "BEGIN_PRIMITIVE_STATIC_FEATURES" << "\n"; + out << "CNF " << m_cnf << "\n"; + out << "NUM_EXPRS " << m_num_exprs << "\n"; + out << "NUM_ROOTS " << m_num_roots << "\n"; + out << "MAX_DEPTH " << m_max_depth << "\n"; + out << "NUM_QUANTIFIERS " << m_num_quantifiers << "\n"; + out << "NUM_QUANTIFIERS_WITH_PATTERNS " << m_num_quantifiers_with_patterns << "\n"; + out << "NUM_QUANTIFIERS_WITH_MULTI_PATTERNS " << m_num_quantifiers_with_multi_patterns << "\n"; + out << "NUM_CLAUSES " << m_num_clauses << "\n"; + out << "NUM_BIN_CLAUSES " << m_num_bin_clauses << "\n"; + out << "NUM_UNITS " << m_num_units << "\n"; + out << "SUM_CLAUSE_SIZE " << m_sum_clause_size << "\n"; + out << "NUM_NESTED_FORMULAS " << m_num_nested_formulas << "\n"; + out << "NUM_BOOL_EXPRS " << m_num_bool_exprs << "\n"; + out << "NUM_BOOL_CONSTANTS " << m_num_bool_constants << "\n"; + out << "NUM_FORMULA_TREES " << m_num_formula_trees << "\n"; + out << "MAX_FORMULA_DEPTH " << m_max_formula_depth << "\n"; + out << "SUM_FORMULA_DEPTH " << m_sum_formula_depth << "\n"; + out << "NUM_OR_AND_TREES " << m_num_or_and_trees << "\n"; + out << "MAX_OR_AND_TREE_DEPTH " << m_max_or_and_tree_depth << "\n"; + out << "SUM_OR_AND_TREE_DEPTH " << m_sum_or_and_tree_depth << "\n"; + out << "NUM_ITE_TREES " << m_num_ite_trees << "\n"; + out << "MAX_ITE_TREE_DEPTH " << m_max_ite_tree_depth << "\n"; + out << "SUM_ITE_TREE_DEPTH " << m_sum_ite_tree_depth << "\n"; + out << "NUM_ORS " << m_num_ors << "\n"; + out << "NUM_ANDS " << m_num_ands << "\n"; + out << "NUM_IFFS " << m_num_iffs << "\n"; + out << "NUM_ITE_FORMULAS " << m_num_ite_formulas << "\n"; + out << "NUM_ITE_TERMS " << m_num_ite_terms << "\n"; + out << "NUM_SHARING " << m_num_sharing << "\n"; + out << "NUM_INTERPRETED_EXPRS " << m_num_interpreted_exprs << "\n"; + out << "NUM_UNINTERPRETED_EXPRS " << m_num_uninterpreted_exprs << "\n"; + out << "NUM_INTERPRETED_CONSTANTS " << m_num_interpreted_constants << "\n"; + out << "NUM_UNINTERPRETED_CONSTANTS " << m_num_uninterpreted_constants << "\n"; + out << "NUM_UNINTERPRETED_FUNCTIONS " << m_num_uninterpreted_functions << "\n"; + out << "NUM_EQS " << m_num_eqs << "\n"; + out << "HAS_RATIONAL " << m_has_rational << "\n"; + out << "HAS_INT " << m_has_int << "\n"; + out << "HAS_REAL " << m_has_real << "\n"; + out << "ARITH_K_SUM " << m_arith_k_sum << "\n"; + out << "NUM_ARITH_TERMS " << m_num_arith_terms << "\n"; + out << "NUM_ARITH_EQS " << m_num_arith_eqs << "\n"; + out << "NUM_ARITH_INEQS " << m_num_arith_ineqs << "\n"; + out << "NUM_DIFF_TERMS " << m_num_diff_terms << "\n"; + out << "NUM_DIFF_EQS " << m_num_diff_eqs << "\n"; + out << "NUM_DIFF_INEQS " << m_num_diff_ineqs << "\n"; + out << "NUM_SIMPLE_EQS " << m_num_simple_eqs << "\n"; + out << "NUM_SIMPLE_INEQS " << m_num_simple_ineqs << "\n"; + out << "NUM_NON_LINEAR " << m_num_non_linear << "\n"; + out << "NUM_ALIENS " << m_num_aliens << "\n"; + display_family_data(out, "NUM_TERMS", m_num_theory_terms); + display_family_data(out, "NUM_ATOMS", m_num_theory_atoms); + display_family_data(out, "NUM_CONSTANTS", m_num_theory_constants); + display_family_data(out, "NUM_EQS", m_num_theory_eqs); + display_family_data(out, "NUM_ALIENS", m_num_aliens_per_family); + out << "NUM_THEORIES " << num_theories() << "\n"; + out << "END_PRIMITIVE_STATIC_FEATURES" << "\n"; +} + +void static_features::display(std::ostream & out) const { + out << "BEGIN_STATIC_FEATURES" << "\n"; + out << "CNF " << m_cnf << "\n"; + out << "MAX_DEPTH " << m_max_depth << "\n"; + out << "MAX_OR_AND_TREE_DEPTH " << m_max_or_and_tree_depth << "\n"; + out << "MAX_ITE_TREE_DEPTH " << m_max_ite_tree_depth << "\n"; + out << "HAS_INT " << m_has_int << "\n"; + out << "HAS_REAL " << m_has_real << "\n"; + out << "HAS_QUANTIFIERS " << (m_num_quantifiers > 0) << "\n"; + out << "PERC_QUANTIFIERS_WITH_PATTERNS " << (m_num_quantifiers > 0 ? (double) m_num_quantifiers_with_patterns / (double) m_num_quantifiers : 0) << "\n"; + out << "PERC_QUANTIFIERS_WITH_MULTI_PATTERNS " << (m_num_quantifiers > 0 ? (double) m_num_quantifiers_with_multi_patterns / (double) m_num_quantifiers : 0) << "\n"; + out << "IS_NON_LINEAR " << (m_num_non_linear > 0) << "\n"; + out << "THEORY_COMBINATION " << (num_theories() > 1) << "\n"; + out << "AVG_CLAUSE_SIZE " << (m_num_clauses > 0 ? (double) m_sum_clause_size / (double) m_num_clauses : 0) << "\n"; + out << "PERC_BOOL_CONSTANTS " << (m_num_uninterpreted_constants > 0 ? (double) m_num_bool_constants / (double) m_num_uninterpreted_constants : 0) << "\n"; + out << "PERC_NESTED_FORMULAS " << (m_num_bool_exprs > 0 ? (double) m_num_nested_formulas / (double) m_num_bool_exprs : 0) << "\n"; + out << "IS_DIFF " << (m_num_arith_eqs == m_num_diff_eqs && m_num_arith_ineqs == m_num_diff_ineqs && m_num_arith_terms == m_num_diff_terms) << "\n"; + out << "INEQ_EQ_RATIO " << (m_num_arith_eqs > 0 ? (double) m_num_arith_ineqs / (double) m_num_arith_eqs : 0) << "\n"; + out << "PERC_ARITH_EQS " << (m_num_eqs > 0 ? (double) m_num_arith_eqs / (double) m_num_eqs : 0) << "\n"; + out << "PERC_DIFF_EQS " << (m_num_arith_eqs > 0 ? (double) m_num_diff_eqs / (double) m_num_arith_eqs : 0) << "\n"; + out << "PERC_DIFF_INEQS " << (m_num_arith_ineqs > 0 ? (double) m_num_diff_ineqs / (double) m_num_arith_ineqs : 0) << "\n"; + out << "PERC_SIMPLE_EQS " << (m_num_arith_eqs > 0 ? (double) m_num_simple_eqs / (double) m_num_arith_eqs : 0) << "\n"; + out << "PERC_SIMPLE_INEQS " << (m_num_arith_ineqs > 0 ? (double) m_num_simple_ineqs / (double) m_num_arith_ineqs : 0) << "\n"; + out << "PERC_ALIENS " << (m_num_exprs > 0 ? (double) m_num_aliens / (double) m_num_exprs : 0) << "\n"; + out << "END_STATIC_FEATURES" << "\n"; +} + +void static_features::get_feature_vector(vector & result) { +} diff --git a/lib/static_features.h b/lib/static_features.h new file mode 100644 index 000000000..bbbea65bf --- /dev/null +++ b/lib/static_features.h @@ -0,0 +1,169 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + static_features.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-05-16. + +Revision History: + +--*/ +#ifndef _STATIC_FEATURES_H_ +#define _STATIC_FEATURES_H_ + +#include"ast.h" +#include"arith_decl_plugin.h" +#include"map.h" + +struct static_features { + ast_manager & m_manager; + arith_util m_autil; + family_id m_bfid; + family_id m_afid; + family_id m_lfid; + ast_mark m_already_visited; + bool m_cnf; + unsigned m_num_exprs; // + unsigned m_num_roots; // + unsigned m_max_depth; + unsigned m_num_quantifiers; // + unsigned m_num_quantifiers_with_patterns; // + unsigned m_num_quantifiers_with_multi_patterns; // + unsigned m_num_clauses; + unsigned m_num_bin_clauses; // + unsigned m_num_units; // + unsigned m_sum_clause_size; + unsigned m_num_nested_formulas; // + unsigned m_num_bool_exprs; // + unsigned m_num_bool_constants; // + unsigned m_num_formula_trees; + unsigned m_max_formula_depth; + unsigned m_sum_formula_depth; + unsigned m_num_or_and_trees; + unsigned m_max_or_and_tree_depth; + unsigned m_sum_or_and_tree_depth; + unsigned m_num_ite_trees; + unsigned m_max_ite_tree_depth; + unsigned m_sum_ite_tree_depth; + unsigned m_num_ands; // + unsigned m_num_ors; // num nested ors + unsigned m_num_iffs; // + unsigned m_num_ite_formulas; // + unsigned m_num_ite_terms; // + unsigned m_num_sharing; + unsigned m_num_interpreted_exprs; // doesn't include bool_exprs + unsigned m_num_uninterpreted_exprs; // + unsigned m_num_interpreted_constants; // doesn't include bool_consts + unsigned m_num_uninterpreted_constants; // + unsigned m_num_uninterpreted_functions; // + unsigned m_num_eqs; // + bool m_has_rational; // + bool m_has_int; // + bool m_has_real; // + rational m_arith_k_sum; // sum of the numerals in arith atoms. + unsigned m_num_arith_terms; + unsigned m_num_arith_eqs; // equalities of the form t = k where k is a numeral + unsigned m_num_arith_ineqs; + unsigned m_num_diff_terms; // <= m_num_arith_terms + unsigned m_num_diff_eqs; // <= m_num_arith_eqs + unsigned m_num_diff_ineqs; // <= m_num_arith_ineqs + unsigned m_num_simple_eqs; // eqs of the form x = k + unsigned m_num_simple_ineqs; // ineqs of the form x <= k or x >= k + unsigned m_num_non_linear; + unsigned_vector m_num_apps; // mapping decl_id -> num_apps; + unsigned_vector m_num_theory_terms; // mapping family_id -> num_terms + unsigned_vector m_num_theory_atoms; // mapping family_id -> num_atoms + unsigned_vector m_num_theory_constants; // mapping family_id -> num_exprs + unsigned_vector m_num_theory_eqs; // mapping family_id -> num_eqs + unsigned m_num_aliens; // + unsigned_vector m_num_aliens_per_family; // mapping family_id -> num_alies exprs + + unsigned_vector m_expr2depth; // expr-id -> depth + unsigned m_max_stack_depth; // maximal depth of stack we are willing to walk. + + u_map m_expr2or_and_depth; + u_map m_expr2ite_depth; + u_map m_expr2formula_depth; + + unsigned m_num_theories; + svector m_theories; // mapping family_id -> bool + + symbol m_label_sym; + symbol m_pattern_sym; + symbol m_expr_list_sym; + + bool is_marked(ast * e) const { return m_already_visited.is_marked(e); } + void mark(ast * e) { m_already_visited.mark(e, true); } + bool is_bool(expr const * e) const { return m_manager.is_bool(e); } + bool is_basic_expr(expr const * e) const { return is_app(e) && to_app(e)->get_family_id() == m_bfid; } + bool is_arith_expr(expr const * e) const { return is_app(e) && to_app(e)->get_family_id() == m_afid; } + bool is_numeral(expr const * e) const { return m_autil.is_numeral(e); } + bool is_numeral(expr const * e, rational & r) const { return m_autil.is_numeral(e, r); } + bool is_minus_one(expr const * e) const { rational r; return m_autil.is_numeral(e, r) && r.is_minus_one(); } + bool is_diff_term(expr const * e, rational & r) const; + bool is_diff_atom(expr const * e) const; + bool is_gate(expr const * e) const; + void mark_theory(family_id fid) { + if (fid != null_family_id && !m_manager.is_builtin_family_id(fid) && !m_theories.get(fid, false)) { + m_theories.setx(fid, true, false); + m_num_theories++; + } + } + + void acc_num(rational const & r) { + if (r.is_neg()) + m_arith_k_sum -= r; + else + m_arith_k_sum += r; + } + + void acc_num(expr const * e) { + rational r; + if (is_numeral(e, r)) { + acc_num(r); + } + } + + void inc_num_apps(func_decl const * d) { unsigned id = d->get_decl_id(); m_num_apps.reserve(id+1, 0); m_num_apps[id]++; } + void inc_theory_terms(family_id fid) { m_num_theory_terms.reserve(fid+1, 0); m_num_theory_terms[fid]++; } + void inc_theory_atoms(family_id fid) { m_num_theory_atoms.reserve(fid+1, 0); m_num_theory_atoms[fid]++; } + void inc_theory_constants(family_id fid) { m_num_theory_constants.reserve(fid+1, 0); m_num_theory_constants[fid]++; } + void inc_theory_eqs(family_id fid) { m_num_theory_eqs.reserve(fid+1, 0); m_num_theory_eqs[fid]++; } + void inc_num_aliens(family_id fid) { m_num_aliens_per_family.reserve(fid+1, 0); m_num_aliens_per_family[fid]++; } + void update_core(expr * e); + void process(expr * e, bool form_ctx, bool or_and_ctx, bool ite_ctx, unsigned stack_depth); + void process_root(expr * e); + unsigned get_depth(expr const * e) const { return m_expr2depth.get(e->get_id(), 1); } + void set_depth(expr const * e, unsigned d) { m_expr2depth.setx(e->get_id(), d, 1); } + unsigned get_or_and_depth(expr const * e) const { unsigned d = 0; m_expr2or_and_depth.find(e->get_id(), d); return d; } + void set_or_and_depth(expr const * e, unsigned d) { m_expr2or_and_depth.insert(e->get_id(), d); } + unsigned get_ite_depth(expr const * e) const { unsigned d = 0; m_expr2ite_depth.find(e->get_id(), d); return d; } + void set_ite_depth(expr const * e, unsigned d) { m_expr2ite_depth.insert(e->get_id(), d); } + unsigned get_form_depth(expr const * e) const { unsigned d = 0; m_expr2formula_depth.find(e->get_id(), d); return d; } + void set_form_depth(expr const * e, unsigned d) { m_expr2formula_depth.insert(e->get_id(), d); } + static_features(ast_manager & m); + void reset(); + void flush_cache(); + void collect(unsigned num_formulas, expr * const * formulas); + void collect(expr * f) { process_root(f); } + bool internal_family(symbol const & f_name) const; + void display_family_data(std::ostream & out, char const * prefix, unsigned_vector const & data) const; + void display_primitive(std::ostream & out) const; + void display(std::ostream & out) const; + void get_feature_vector(vector & result); + bool has_uf() const; + unsigned num_theories() const; + unsigned num_non_uf_theories() const; + +}; + +#endif /* _STATIC_FEATURES_H_ */ + diff --git a/lib/statistics.cpp b/lib/statistics.cpp new file mode 100644 index 000000000..6b5d396f8 --- /dev/null +++ b/lib/statistics.cpp @@ -0,0 +1,228 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + statistics.h + +Abstract: + + Wrapper for reporting statistics + +Author: + + Leonardo (leonardo) 2011-05-17 + +Notes: + +--*/ +#include"statistics.h" +#include"map.h" +#include"str_hashtable.h" +#include"buffer.h" +#include"ast_smt2_pp.h" +#include + +void statistics::update(char const * key, unsigned inc) { + if (inc != 0) + m_stats.push_back(key_val_pair(key, inc)); +} + +void statistics::update(char const * key, double inc) { + if (inc != 0.0) + m_d_stats.push_back(key_d_val_pair(key, inc)); +} + +void statistics::copy(statistics const & st) { + m_stats.append(st.m_stats); + m_d_stats.append(st.m_d_stats); +} + +void statistics::reset() { + m_stats.reset(); + m_d_stats.reset(); +} + +template +static void mk_map(V const & v, M & m) { + typename V::const_iterator it = v.begin(); + typename V::const_iterator end = v.end(); + for (; it != end; ++it) { + typename V::data::second_type val; + if (m.find(it->first, val)) + m.insert(it->first, it->second + val); + else + m.insert(it->first, it->second); + } +} + +template +static void get_keys(M const & m, ptr_buffer & keys) { + typename M::iterator it = m.begin(); + typename M::iterator end = m.end(); + for (; it != end; ++it) { + keys.push_back(const_cast(it->m_key)); + } +} + +static void display_smt2_key(std::ostream & out, char const * key) { + SASSERT(key != 0); + out << ":"; + if (*key == ':') + key++; + while (*key) { + if (is_smt2_simple_symbol_char(*key)) + out << *key; + else + out << "-"; + key++; + } +} + +struct str_lt { + bool operator()(char const * s1, char const * s2) const { return strcmp(s1, s2) < 0; } +}; + +typedef map key2val; +typedef map key2dval; + +unsigned get_max_len(ptr_buffer & keys) { + unsigned max = 0; + for (unsigned i = 0; i < static_cast(keys.size()); i++) { + char * k = keys.get(i); + if (*k == ':') + k++; + unsigned curr = static_cast(strlen(k)); + if (curr > max) + max = curr; + } + return max; +} + +void statistics::display_smt2(std::ostream & out) const { +#define INIT_DISPLAY() \ + key2val m_u; \ + key2dval m_d; \ + mk_map(m_stats, m_u); \ + mk_map(m_d_stats, m_d); \ + ptr_buffer keys; \ + get_keys(m_u, keys); \ + get_keys(m_d, keys); \ + std::sort(keys.begin(), keys.end(), str_lt()); \ + unsigned max = get_max_len(keys); + + INIT_DISPLAY(); + bool first = true; + +#define DISPLAY_KEY() { \ + if (!first) \ + out << "\n "; \ + display_smt2_key(out, k); \ + unsigned len = static_cast(strlen(k)); \ + for (unsigned j = len; j < max; j++) \ + out << " "; \ + first = false; \ + } + + out << "("; + for (unsigned i = 0; i < keys.size(); i++) { + char * k = keys.get(i); + unsigned val; + if (m_u.find(k, val)) { + DISPLAY_KEY(); + out << " " << val; + } + else { + double d_val = 0.0; + m_d.find(k, d_val); + DISPLAY_KEY(); + out << " " << std::fixed << std::setprecision(2) << d_val; + } + } + out << ")\n"; +} + +void statistics::display(std::ostream & out) const { + INIT_DISPLAY(); + +#undef DISPLAY_KEY +#define DISPLAY_KEY() { \ + if (*k == ':') \ + k++; \ + out << k << ":"; \ + unsigned len = static_cast(strlen(k)); \ + for (unsigned j = len; j < max; j++) \ + out << " "; \ + } + + for (unsigned i = 0; i < keys.size(); i++) { + char * k = keys.get(i); + unsigned val; + if (m_u.find(k, val)) { + DISPLAY_KEY(); + out << " " << val << "\n"; + } + else { + double d_val = 0.0; + m_d.find(k, d_val); + DISPLAY_KEY(); + out << " " << std::fixed << std::setprecision(2) << d_val << "\n"; + } + } +} + +template +static void display_internal(std::ostream & out, M const & m) { + typename M::iterator it = m.begin(); + typename M::iterator end = m.end(); + for (; it != end; it++) { + char const * key = it->m_key; + if (*key == ':') key++; + while (*key) { + if ('a' <= *key && *key <= 'z') + out << ('A' + (*key - 'a')); + else if (*key == ' ') + out << "_"; + else + out << *key; + } + out << " " << it->m_value << "\n"; + } +} + +void statistics::display_internal(std::ostream & out) const { + key2val m_u; + key2dval m_d; + mk_map(m_stats, m_u); + mk_map(m_d_stats, m_d); + + ::display_internal(out, m_u); + ::display_internal(out, m_d); +} + +unsigned statistics::size() const { + return m_stats.size() + m_d_stats.size(); +} + +bool statistics::is_uint(unsigned idx) const { + return idx < m_stats.size(); +} + +char const * statistics::get_key(unsigned idx) const { + if (is_uint(idx)) + return m_stats[idx].first; + else + return m_d_stats[idx - m_stats.size()].first; +} + +unsigned statistics::get_uint_value(unsigned idx) const { + SASSERT(idx < size()); + SASSERT(is_uint(idx)); + return m_stats[idx].second; +} + +double statistics::get_double_value(unsigned idx) const { + SASSERT(idx < size()); + SASSERT(!is_uint(idx)); + return m_d_stats[idx - m_stats.size()].second; +} diff --git a/lib/statistics.h b/lib/statistics.h new file mode 100644 index 000000000..a1a155df9 --- /dev/null +++ b/lib/statistics.h @@ -0,0 +1,45 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + statistics.h + +Abstract: + + Wrapper for reporting statistics + +Author: + + Leonardo (leonardo) 2011-05-17 + +Notes: + +--*/ +#ifndef _STATISTICS_H_ +#define _STATISTICS_H_ + +#include +#include"vector.h" + +class statistics { + typedef std::pair key_val_pair; + svector m_stats; + typedef std::pair key_d_val_pair; + svector m_d_stats; +public: + void copy(statistics const & st); + void reset(); + void update(char const * key, unsigned inc); + void update(char const * key, double inc); + void display(std::ostream & out) const; + void display_smt2(std::ostream & out) const; + void display_internal(std::ostream & out) const; + unsigned size() const; + bool is_uint(unsigned idx) const; + char const * get_key(unsigned idx) const; + unsigned get_uint_value(unsigned idx) const; + double get_double_value(unsigned idx) const; +}; + +#endif diff --git a/lib/stats.h b/lib/stats.h new file mode 100644 index 000000000..bd88a9e7a --- /dev/null +++ b/lib/stats.h @@ -0,0 +1,37 @@ +/*++ +Copyright (c) 2009 Microsoft Corporation + +Module Name: + + stats.h + +Abstract: + + Shared utilities for displaying statistics. + +Author: + + nbjorner 2009-12-6 + +Revision History: + +--*/ + +#ifndef __STATS_H_ +#define __STATS_H_ + +#include + +inline void print_stat(std::ostream& out, char const* msg, unsigned num) { + if (num > 0) { + out << msg << num << "\n"; + } +} + +inline void print_stat_f(std::ostream& out, char const* msg, float num) { + if (num > 0.0) { + out << msg << num << "\n"; + } +} + +#endif diff --git a/lib/stopwatch.h b/lib/stopwatch.h new file mode 100644 index 000000000..e27141924 --- /dev/null +++ b/lib/stopwatch.h @@ -0,0 +1,180 @@ +/*++ +Copyright (c) 2008 Microsoft Corporation + +Module Name: + + stopwatch.h + +Abstract: + + High resolution time keeping + +Author: + + Christoph Wintersteiger (t-cwinte) 2008-12-24 + +Revision History: + +--*/ + +#ifndef _STOPWATCH_H_ +#define _STOPWATCH_H_ + +#ifdef _WINDOWS + +// Does this redefinition work? +#define ARRAYSIZE_TEMP ARRAYSIZE +#undef ARRAYSIZE + +#include + +class stopwatch +{ +private: + LARGE_INTEGER m_elapsed; + LARGE_INTEGER m_last_start_time; + LARGE_INTEGER m_last_stop_time; + LARGE_INTEGER m_frequency; + +public: + stopwatch() { + QueryPerformanceFrequency(&m_frequency); + reset(); + } + + ~stopwatch() {}; + + void reset() { m_elapsed.QuadPart = 0; } + + void start() { + QueryPerformanceCounter(&m_last_start_time); + } + + void stop() { + QueryPerformanceCounter(&m_last_stop_time); + m_elapsed.QuadPart += m_last_stop_time.QuadPart - m_last_start_time.QuadPart; + } + + double get_seconds() const { + return static_cast(m_elapsed.QuadPart / static_cast(m_frequency.QuadPart)) ; + } + + double get_current_seconds() const { + LARGE_INTEGER t; + QueryPerformanceCounter(&t); + return static_cast( (t.QuadPart - m_last_start_time.QuadPart) / static_cast(m_frequency.QuadPart)); + } +}; + +#undef ARRAYSIZE +#define ARRAYSIZE ARRAYSIZE_TEMP +#undef max + + +#elif defined(__APPLE__) && defined (__MACH__) // Mac OS X + +#include +#include + +class stopwatch { + unsigned long long m_time; // elapsed time in ns + bool m_running; + clock_serv_t m_host_clock; + mach_timespec_t m_start; + +public: + stopwatch():m_time(0), m_running(false) { + host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &m_host_clock); + } + + ~stopwatch() {} + + void reset() { + m_time = 0ull; + } + + void start() { + if (!m_running) { + clock_get_time(m_host_clock, &m_start); + m_running = true; + } + } + + void stop() { + if (m_running) { + mach_timespec_t _stop; + clock_get_time(m_host_clock, &_stop); + m_time += (_stop.tv_sec - m_start.tv_sec) * 1000000000ull; + m_time += (_stop.tv_nsec - m_start.tv_nsec); + m_running = false; + } + } + + double get_seconds() const { + if (m_running) { + const_cast(this)->stop(); + /* update m_time */ + const_cast(this)->start(); + } + return static_cast(m_time)/static_cast(1000000000ull); + } + + double get_current_seconds() const { + return get_seconds(); + } +}; + + +#else // Linux + +#include + +class stopwatch { + unsigned long long m_time; // elapsed time in ns + bool m_running; + struct timespec m_start; + +public: + stopwatch():m_time(0), m_running(false) { + } + + ~stopwatch() {} + + void reset() { + m_time = 0ull; + } + + void start() { + if (!m_running) { + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &m_start); + m_running = true; + } + } + + void stop() { + if (m_running) { + struct timespec _stop; + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &_stop); + m_time += (_stop.tv_sec - m_start.tv_sec) * 1000000000ull; + m_time += (_stop.tv_nsec - m_start.tv_nsec); + m_running = false; + } + } + + double get_seconds() const { + if (m_running) { + const_cast(this)->stop(); + /* update m_time */ + const_cast(this)->start(); + } + return static_cast(m_time)/static_cast(1000000000ull); + } + + double get_current_seconds() const { + return get_seconds(); + } +}; + +#endif + +#endif diff --git a/lib/str_hashtable.h b/lib/str_hashtable.h new file mode 100644 index 000000000..f798494b2 --- /dev/null +++ b/lib/str_hashtable.h @@ -0,0 +1,34 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + str_hashtable.h + +Abstract: + + String hashtable. It uses Jenkin's hash function and the optimized hashtable module. + +Author: + + Leonardo de Moura (leonardo) 2006-09-13. + +Revision History: + +--*/ +#ifndef _STR_HASHTABLE_H_ +#define _STR_HASHTABLE_H_ + +#include + +#include"hashtable.h" +#include"hash.h" + +struct str_hash_proc { + unsigned operator()(char const * s) const { return string_hash(s, static_cast(strlen(s)), 17); } +}; +struct str_eq_proc { bool operator()(char const * s1, char const * s2) const { return strcmp(s1, s2) == 0; } }; +typedef ptr_hashtable str_hashtable; + +#endif /* _STR_HASHTABLE_H_ */ + diff --git a/lib/strategic_solver.cpp b/lib/strategic_solver.cpp new file mode 100644 index 000000000..fe976c138 --- /dev/null +++ b/lib/strategic_solver.cpp @@ -0,0 +1,469 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + strategic_solver.h + +Abstract: + + Strategies -> Solver + +Author: + + Leonardo (leonardo) 2011-05-19 + +Notes: + +--*/ +#include"strategic_solver.h" +#include"cmd_context.h" +#include"scoped_timer.h" +#include"params2front_end_params.h" +#include"ast_smt2_pp.h" + +// minimum verbosity level for portfolio verbose messages +#define PS_VB_LVL 15 + +strategic_solver::strategic_solver(): + m_manager(0), + m_fparams(0), + m_force_tactic(false), + m_inc_mode(false), + m_check_sat_executed(false), + m_inc_solver(0), + m_inc_solver_timeout(UINT_MAX), + m_tactic_if_undef(false), + m_default_fct(0), + m_curr_tactic(0), + m_proof(0), + m_callback(0) { + m_use_inc_solver_results = false; + DEBUG_CODE(m_num_scopes = 0;); + m_produce_proofs = false; + m_produce_models = false; + m_produce_unsat_cores = false; +} + +strategic_solver::~strategic_solver() { + SASSERT(!m_curr_tactic); + dictionary::iterator it = m_logic2fct.begin(); + dictionary::iterator end = m_logic2fct.end(); + for (; it != end; ++it) { + dealloc(it->m_value); + } + if (m_proof) + m().dec_ref(m_proof); +} + +void strategic_solver::set_inc_solver(solver * s) { + SASSERT(m_inc_solver == 0); + SASSERT(m_num_scopes == 0); + m_inc_solver = s; + if (m_callback) + m_inc_solver->set_progress_callback(m_callback); +} + +void strategic_solver::updt_params(params_ref const & p) { + if (m_inc_solver) + m_inc_solver->updt_params(p); + if (m_fparams) + params2front_end_params(p, *m_fparams); +} + + +void strategic_solver::collect_param_descrs(param_descrs & r) { + if (m_inc_solver) + m_inc_solver->collect_param_descrs(r); +} + +/** + \brief Set a timeout for each check_sat query that is processed by the inc_solver. + timeout == UINT_MAX means infinite + After the timeout a strategy is used. +*/ +void strategic_solver::set_inc_solver_timeout(unsigned timeout) { + m_inc_solver_timeout = timeout; +} + +/** + \brief Use tactic when the incremental solver return undef. +*/ +void strategic_solver::use_tactic_if_undef(bool f) { + m_tactic_if_undef = f; +} + +/** + \brief Set the default tactic factory. + It is used if there is no tactic for a given logic. +*/ +void strategic_solver::set_default_tactic(tactic_factory * fct) { + m_default_fct = fct; +} + +/** + \brief Set a tactic factory for a given logic. +*/ +void strategic_solver::set_tactic_for(symbol const & logic, tactic_factory * fct) { + tactic_factory * old_fct; + if (m_logic2fct.find(logic, old_fct)) { + dealloc(old_fct); + } + m_logic2fct.insert(logic, fct); +} + +void strategic_solver::init(ast_manager & m, symbol const & logic) { + m_manager = &m; + m_logic = logic; + if (m_inc_mode) { + SASSERT(m_inc_solver); + m_inc_solver->init(m, logic); + } +} + +// delayed inc solver initialization +void strategic_solver::init_inc_solver() { + if (m_inc_mode) + return; // solver was already initialized + if (!m_inc_solver) + return; // inc solver was not installed + m_inc_mode = true; + m_inc_solver->set_front_end_params(*m_fparams); + m_inc_solver->init(m(), m_logic); + unsigned sz = get_num_assertions(); + for (unsigned i = 0; i < sz; i++) { + m_inc_solver->assert_expr(get_assertion(i)); + } +} + +void strategic_solver::collect_statistics(statistics & st) const { + if (m_use_inc_solver_results) { + SASSERT(m_inc_solver); + m_inc_solver->collect_statistics(st); + } + else { + if (m_curr_tactic) + m_curr_tactic->collect_statistics(st); // m_curr_tactic is still being executed. + else + st.copy(m_stats); + } +} + +void strategic_solver::reset() { + m_logic = symbol::null; + m_inc_mode = false; + m_check_sat_executed = false; + if (m_inc_solver) + m_inc_solver->reset(); + SASSERT(!m_curr_tactic); + m_use_inc_solver_results = false; + reset_results(); +} + +void strategic_solver::reset_results() { + m_use_inc_solver_results = false; + m_model = 0; + if (m_proof) { + m().dec_ref(m_proof); + m_proof = 0; + } + m_reason_unknown.clear(); + m_stats.reset(); +} + +void strategic_solver::assert_expr(expr * t) { + if (m_check_sat_executed && !m_inc_mode) { + // a check sat was already executed --> switch to incremental mode + init_inc_solver(); + SASSERT(m_inc_solver == 0 || m_inc_mode); + } + if (m_inc_mode) { + SASSERT(m_inc_solver); + m_inc_solver->assert_expr(t); + } +} + +void strategic_solver::push() { + DEBUG_CODE(m_num_scopes++;); + init_inc_solver(); + if (m_inc_solver) + m_inc_solver->push(); +} + +void strategic_solver::pop(unsigned n) { + DEBUG_CODE({ + SASSERT(n <= m_num_scopes); + m_num_scopes -= n; + }); + init_inc_solver(); + if (m_inc_solver) + m_inc_solver->pop(n); +} + +unsigned strategic_solver::get_scope_level() const { + if (m_inc_solver) + return m_inc_solver->get_scope_level(); + else + return 0; +} + +struct aux_timeout_eh : public event_handler { + solver * m_solver; + volatile bool m_canceled; + aux_timeout_eh(solver * s):m_solver(s), m_canceled(false) {} + virtual void operator()() { + m_solver->cancel(); + m_canceled = true; + } +}; + +struct strategic_solver::mk_tactic { + strategic_solver * m_solver; + + mk_tactic(strategic_solver * s, tactic_factory * f):m_solver(s) { + ast_manager & m = s->m(); + params_ref p; + front_end_params2params(*s->m_fparams, p); + tactic * tct = (*f)(m, p); + tct->set_front_end_params(*s->m_fparams); + tct->set_logic(s->m_logic); + if (s->m_callback) + tct->set_progress_callback(s->m_callback); + #pragma omp critical (strategic_solver) + { + s->m_curr_tactic = tct; + } + } + + ~mk_tactic() { + #pragma omp critical (strategic_solver) + { + m_solver->m_curr_tactic = 0; + } + } +}; + +tactic_factory * strategic_solver::get_tactic_factory() const { + tactic_factory * f = 0; + if (m_logic2fct.find(m_logic, f)) + return f; + return m_default_fct.get(); +} + +lbool strategic_solver::check_sat_with_assumptions(unsigned num_assumptions, expr * const * assumptions) { + if (!m_inc_solver) { + IF_VERBOSE(PS_VB_LVL, verbose_stream() << "incremental solver was not installed, returning unknown...\n";); + m_use_inc_solver_results = false; + m_reason_unknown = "incomplete"; + return l_undef; + } + init_inc_solver(); + m_use_inc_solver_results = true; + return m_inc_solver->check_sat(num_assumptions, assumptions); +} + +lbool strategic_solver::check_sat(unsigned num_assumptions, expr * const * assumptions) { + reset_results(); + m_check_sat_executed = true; + if (num_assumptions > 0 || // assumptions were provided + (!m_fparams->m_auto_config && !m_force_tactic) // auto config and force_tactic are turned off + ) { + // must use incremental solver + return check_sat_with_assumptions(num_assumptions, assumptions); + } + + tactic_factory * factory = get_tactic_factory(); + if (factory == 0) + init_inc_solver(); // try to switch to incremental solver + + if (m_inc_mode) { + SASSERT(m_inc_solver); + unsigned timeout = m_inc_solver_timeout; + if (factory == 0) + timeout = UINT_MAX; // there is no tactic available + if (timeout == UINT_MAX) { + IF_VERBOSE(PS_VB_LVL, verbose_stream() << "using incremental solver (without a timeout).\n";); + m_use_inc_solver_results = true; + lbool r = m_inc_solver->check_sat(0, 0); + if (r != l_undef || factory == 0 || !m_tactic_if_undef) { + m_use_inc_solver_results = true; + return r; + } + } + else { + IF_VERBOSE(PS_VB_LVL, verbose_stream() << "using incremental solver (with timeout).\n";); + SASSERT(factory != 0); + aux_timeout_eh eh(m_inc_solver.get()); + lbool r; + { + scoped_timer timer(m_inc_solver_timeout, &eh); + r = m_inc_solver->check_sat(0, 0); + } + if ((r != l_undef || !m_tactic_if_undef) && !eh.m_canceled) { + m_use_inc_solver_results = true; + return r; + } + } + IF_VERBOSE(PS_VB_LVL, verbose_stream() << "incremental solver failed, trying tactic.\n";); + } + + m_use_inc_solver_results = false; + + if (factory == 0) { + IF_VERBOSE(PS_VB_LVL, verbose_stream() << "there is no tactic available for the current logic.\n";); + m_reason_unknown = "incomplete"; + return l_undef; + } + + goal_ref g = alloc(goal, m(), m_produce_proofs, m_produce_models, m_produce_unsat_cores); + unsigned sz = get_num_assertions(); + for (unsigned i = 0; i < sz; i++) { + g->assert_expr(get_assertion(i)); + } + expr_dependency_ref core(m()); + + mk_tactic tct_maker(this, factory); + SASSERT(m_curr_tactic); + + proof_ref pr(m()); + lbool r = ::check_sat(*(m_curr_tactic.get()), g, m_model, pr, core, m_reason_unknown); + m_curr_tactic->collect_statistics(m_stats); + if (pr) { + m_proof = pr; + m().inc_ref(m_proof); + } + return r; +} + +void strategic_solver::set_cancel(bool f) { + if (m_inc_solver) + m_inc_solver->set_cancel(f); + #pragma omp critical (strategic_solver) + { + if (m_curr_tactic) + m_curr_tactic->set_cancel(f); + } +} + +void strategic_solver::get_unsat_core(ptr_vector & r) { + if (m_use_inc_solver_results) { + SASSERT(m_inc_solver); + m_inc_solver->get_unsat_core(r); + } +} + +void strategic_solver::get_model(model_ref & m) { + if (m_use_inc_solver_results) { + SASSERT(m_inc_solver); + m_inc_solver->get_model(m); + } + else { + m = m_model; + } +} + +proof * strategic_solver::get_proof() { + if (m_use_inc_solver_results) { + SASSERT(m_inc_solver); + return m_inc_solver->get_proof(); + } + else { + return m_proof; + } +} + +std::string strategic_solver::reason_unknown() const { + if (m_use_inc_solver_results) { + SASSERT(m_inc_solver); + return m_inc_solver->reason_unknown(); + } + return m_reason_unknown; +} + +void strategic_solver::get_labels(svector & r) { + if (m_use_inc_solver_results) { + SASSERT(m_inc_solver); + m_inc_solver->get_labels(r); + } +} + +void strategic_solver::set_progress_callback(progress_callback * callback) { + m_callback = callback; + if (m_inc_solver) + m_inc_solver->set_progress_callback(callback); +} + +void strategic_solver::display(std::ostream & out) const { + if (m_manager) { + unsigned num = get_num_assertions(); + out << "(solver"; + for (unsigned i = 0; i < num; i++) { + out << "\n " << mk_ismt2_pp(get_assertion(i), m(), 2); + } + out << ")"; + } + else { + out << "(solver)"; + } +} + +strategic_solver_cmd::strategic_solver_cmd(cmd_context & ctx): + m_ctx(ctx) { +} + +unsigned strategic_solver_cmd::get_num_assertions() const { + return static_cast(m_ctx.end_assertions() - m_ctx.begin_assertions()); +} + +expr * strategic_solver_cmd::get_assertion(unsigned idx) const { + SASSERT(idx < get_num_assertions()); + return m_ctx.begin_assertions()[idx]; +} + +strategic_solver_api::ctx::ctx(ast_manager & m):m_assertions(m) { +} + +void strategic_solver_api::init(ast_manager & m, symbol const & logic) { + strategic_solver::init(m, logic); + m_ctx = alloc(ctx, m); +} + +unsigned strategic_solver_api::get_num_assertions() const { + if (m_ctx == 0) + return 0; + return m_ctx->m_assertions.size(); +} + +expr * strategic_solver_api::get_assertion(unsigned idx) const { + SASSERT(m_ctx); + return m_ctx->m_assertions.get(idx); +} + +void strategic_solver_api::assert_expr(expr * t) { + SASSERT(m_ctx); + strategic_solver::assert_expr(t); + m_ctx->m_assertions.push_back(t); +} + +void strategic_solver_api::push() { + SASSERT(m_ctx); + strategic_solver::push(); + m_ctx->m_scopes.push_back(m_ctx->m_assertions.size()); +} + +void strategic_solver_api::pop(unsigned n) { + SASSERT(m_ctx); + unsigned new_lvl = m_ctx->m_scopes.size() - n; + unsigned old_sz = m_ctx->m_scopes[new_lvl]; + m_ctx->m_assertions.shrink(old_sz); + m_ctx->m_scopes.shrink(new_lvl); + strategic_solver::pop(n); +} + +void strategic_solver_api::reset() { + m_ctx = 0; + strategic_solver::reset(); +} + + + diff --git a/lib/strategic_solver.h b/lib/strategic_solver.h new file mode 100644 index 000000000..811fcaa95 --- /dev/null +++ b/lib/strategic_solver.h @@ -0,0 +1,143 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + strategic_solver.h + +Abstract: + + Strategies -> Solver + +Author: + + Leonardo (leonardo) 2011-05-19 + +Notes: + +--*/ +#ifndef _STRATEGIC_SOLVER_H_ +#define _STRATEGIC_SOLVER_H_ + +#include"solver.h" +#include"tactic.h" + +class progress_callback; +struct front_end_params; + +class strategic_solver : public solver { + ast_manager * m_manager; + front_end_params * m_fparams; + symbol m_logic; + bool m_force_tactic; // use tactics even when auto_config = false + bool m_inc_mode; + bool m_check_sat_executed; + scoped_ptr m_inc_solver; + unsigned m_inc_solver_timeout; + bool m_tactic_if_undef; + scoped_ptr m_default_fct; + dictionary m_logic2fct; + + ref m_curr_tactic; + + bool m_use_inc_solver_results; + model_ref m_model; + proof * m_proof; + std::string m_reason_unknown; + statistics m_stats; + +#ifdef Z3DEBUG + unsigned m_num_scopes; +#endif + + bool m_produce_proofs; + bool m_produce_models; + bool m_produce_unsat_cores; + + progress_callback * m_callback; + + void reset_results(); + void init_inc_solver(); + tactic_factory * get_tactic_factory() const; + lbool check_sat_with_assumptions(unsigned num_assumptions, expr * const * assumptions); + + struct mk_tactic; + +public: + strategic_solver(); + ~strategic_solver(); + + ast_manager & m() const { SASSERT(m_manager); return *m_manager; } + + void set_inc_solver(solver * s); + void set_inc_solver_timeout(unsigned timeout); + void set_default_tactic(tactic_factory * fct); + void set_tactic_for(symbol const & logic, tactic_factory * fct); + void use_tactic_if_undef(bool f); + void force_tactic(bool f) { m_force_tactic = f; } + + virtual void set_front_end_params(front_end_params & p) { m_fparams = &p; } + + virtual void updt_params(params_ref const & p); + virtual void collect_param_descrs(param_descrs & r); + + virtual void set_produce_proofs(bool f) { m_produce_proofs = f; } + virtual void set_produce_models(bool f) { m_produce_models = f; } + virtual void set_produce_unsat_cores(bool f) { m_produce_unsat_cores = f; } + + virtual unsigned get_num_assertions() const = 0; + virtual expr * get_assertion(unsigned idx) const = 0; + + virtual void display(std::ostream & out) const; + + virtual void init(ast_manager & m, symbol const & logic); + virtual void collect_statistics(statistics & st) const; + virtual void reset(); + virtual void assert_expr(expr * t); + virtual void push(); + virtual void pop(unsigned n); + virtual unsigned get_scope_level() const; + virtual lbool check_sat(unsigned num_assumptions, expr * const * assumptions); + virtual void get_unsat_core(ptr_vector & r); + virtual void get_model(model_ref & m); + virtual proof * get_proof(); + virtual std::string reason_unknown() const; + virtual void get_labels(svector & r); + virtual void set_cancel(bool f); + virtual void set_progress_callback(progress_callback * callback); +}; + +// Specialization for the SMT 2.0 command language frontend +class strategic_solver_cmd : public strategic_solver { + cmd_context & m_ctx; +public: + strategic_solver_cmd(cmd_context & ctx); + virtual unsigned get_num_assertions() const; + virtual expr * get_assertion(unsigned idx) const; +}; + +// Specialization for Z3 API +class strategic_solver_api : public strategic_solver { + struct ctx { + expr_ref_vector m_assertions; + unsigned_vector m_scopes; + ctx(ast_manager & m); + }; + scoped_ptr m_ctx; +public: + strategic_solver_api() {} + + virtual void init(ast_manager & m, symbol const & logic); + + virtual void assert_expr(expr * t); + virtual void push(); + virtual void pop(unsigned n); + virtual void reset(); + + virtual unsigned get_num_assertions() const; + virtual expr * get_assertion(unsigned idx) const; +}; + + + +#endif diff --git a/lib/strategy_exception.cpp b/lib/strategy_exception.cpp new file mode 100644 index 000000000..c57275a91 --- /dev/null +++ b/lib/strategy_exception.cpp @@ -0,0 +1,26 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + strategy_exception.cpp + +Abstract: + + Strategy exception + +Author: + + Leonardo (leonardo) 2011-05-02 + +Notes: + +--*/ +#include"strategy_exception.h" + +char const * strategy_exception::g_ste_canceled_msg = "canceled"; +char const * strategy_exception::g_ste_max_memory_msg = "max. memory exceeded"; +char const * strategy_exception::g_ste_max_scopes_msg = "max. scopes exceeded"; +char const * strategy_exception::g_ste_max_steps_msg = "max. steps exceeded"; +char const * strategy_exception::g_ste_max_frames_msg = "max. frames exceeded"; +char const * strategy_exception::g_ste_no_proofs_msg = "strategy does not support proof generation"; diff --git a/lib/strategy_exception.h b/lib/strategy_exception.h new file mode 100644 index 000000000..379517396 --- /dev/null +++ b/lib/strategy_exception.h @@ -0,0 +1,53 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + strategy_exception.h + +Abstract: + + Strategy exception + +Author: + + Leonardo (leonardo) 2011-05-02 + +Notes: + +--*/ +#ifndef _STRATEGY_EXCEPTION_H_ +#define _STRATEGY_EXCEPTION_H_ + +#include"z3_exception.h" + +class strategy_exception : public z3_exception { +public: + static char const * g_ste_canceled_msg; + static char const * g_ste_max_memory_msg; + static char const * g_ste_max_scopes_msg; + static char const * g_ste_max_steps_msg; + static char const * g_ste_max_frames_msg; + static char const * g_ste_no_proofs_msg; +protected: + char const * m_msg; +public: + strategy_exception(char const * msg):m_msg(msg) {} + virtual ~strategy_exception() {} + virtual char const * msg() const { return m_msg; } +}; + +#define STE_CANCELED_MSG strategy_exception::g_ste_canceled_msg +#define STE_MAX_MEMORY_MSG strategy_exception::g_ste_max_memory_msg +#define STE_MAX_SCOPES_MSG strategy_exception::g_ste_max_scopes_msg +#define STE_MAX_STEPS_MSG strategy_exception::g_ste_max_steps_msg +#define STE_MAX_FRAMES_MSG strategy_exception::g_ste_max_frames_msg +#define STE_NO_PROOF_GEN_MSG strategy_exception::g_ste_no_proofs_msg + +#define MK_ST_EXCEPTION(NAME) \ +class NAME : public strategy_exception { \ +public: \ + NAME(char const * msg):strategy_exception(msg) {} \ +} + +#endif diff --git a/lib/stream_buffer.h b/lib/stream_buffer.h new file mode 100644 index 000000000..976ecffab --- /dev/null +++ b/lib/stream_buffer.h @@ -0,0 +1,46 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + stream_buffer.h + +Abstract: + + Simple stream buffer interface. + In the future we should be able to read different kinds of stream (e.g., compressed files used + in the SAT competitions). + +Author: + + Leonardo de Moura (leonardo) 2006-10-02. + +Revision History: + +--*/ +#ifndef _STREAM_BUFFER_H_ +#define _STREAM_BUFFER_H_ + +#include + +class stream_buffer { + std::istream & m_stream; + int m_val; +public: + + stream_buffer(std::istream & s): + m_stream(s) { + m_val = m_stream.get(); + } + + int operator *() const { + return m_val; + } + + void operator ++() { + m_val = m_stream.get(); + } +}; + +#endif /* _STREAM_BUFFER_H_ */ + diff --git a/lib/string_buffer.h b/lib/string_buffer.h new file mode 100644 index 000000000..2583f0a2e --- /dev/null +++ b/lib/string_buffer.h @@ -0,0 +1,181 @@ +/*++ + Copyright (c) 2006 Microsoft Corporation + + Module Name: + + string_buffer.h + + Abstract: + + Simple string buffer + + Author: + + Leonardo de Moura (leonardo) 2006-10-14. + + Revision History: + + --*/ +#ifndef _STRING_BUFFER_H_ +#define _STRING_BUFFER_H_ + +#include +#include +#include +#include"util.h" +#include"memory_manager.h" + +// This string buffer will not use the heap if the data consumes less than INITIAL_SIZE bytes. +template +class string_buffer { + char m_initial_buffer[INITIAL_SIZE]; + char * m_buffer; + size_t m_pos; + size_t m_capacity; + + void expand() { + size_t new_capacity = m_capacity << 1; + char * new_buffer = alloc_svect(char, new_capacity); + memcpy(new_buffer, m_buffer, m_pos); + if (m_capacity > INITIAL_SIZE) { + dealloc_svect(m_buffer); + } + m_capacity = new_capacity; + m_buffer = new_buffer; + } + +public: + string_buffer(): + m_buffer(m_initial_buffer), + m_pos(0), + m_capacity(INITIAL_SIZE) { + } + + ~string_buffer() { + if (m_capacity > INITIAL_SIZE) { + dealloc_svect(m_buffer); + } + } + + void reset() { + m_pos = 0; + } + + void append(char c) { + if (m_pos >= m_capacity) { + expand(); + } + m_buffer[m_pos] = c; + m_pos++; + } + + void append(const char * str) { + size_t len = strlen(str); + size_t new_pos = m_pos + len; + while (new_pos > m_capacity) { + expand(); + } + memcpy(m_buffer + m_pos, str, len); + m_pos += len; + } + + void append(int n) { + char buffer[24]; +#ifdef _WINDOWS + sprintf_s(buffer, ARRAYSIZE(buffer), "%d", n); +#else + sprintf(buffer, "%d", n); +#endif + append(buffer); + } + + void append(unsigned n) { + char buffer[24]; +#ifdef _WINDOWS + sprintf_s(buffer, ARRAYSIZE(buffer), "%d", n); +#else + sprintf(buffer, "%d", n); +#endif + append(buffer); + } + + void append(long n) { + char buffer[24]; +#ifdef _WINDOWS + sprintf_s(buffer, ARRAYSIZE(buffer), "%ld", n); +#else + sprintf(buffer, "%ld", n); +#endif + append(buffer); + } + + void append(bool b) { + if (b) { + append("true"); + } + else { + append("false"); + } + } + + unsigned size() const { + return m_pos; + } + + bool empty() const { + return m_pos == 0; + } + + const char * c_str() const { + if (m_pos >= m_capacity) { + const_cast(this)->expand(); + } + const_cast(this)->m_buffer[m_pos] = 0; + return m_buffer; + } +}; + + +template +inline string_buffer & operator<<(string_buffer & buffer, const char * str) { + buffer.append(str); + return buffer; +} + +template +inline string_buffer & operator<<(string_buffer & buffer, char c) { + buffer.append(c); + return buffer; +} + +template +inline string_buffer & operator<<(string_buffer & buffer, int i) { + buffer.append(i); + return buffer; +} + +template +inline string_buffer & operator<<(string_buffer & buffer, unsigned i) { + buffer.append(i); + return buffer; +} + +template +inline string_buffer & operator<<(string_buffer & buffer, bool b) { + buffer.append(b); + return buffer; +} + +template +inline string_buffer & operator<<(string_buffer & buffer, long l) { + buffer.append(l); + return buffer; +} + +template +inline string_buffer & operator<<(string_buffer & buffer1, const string_buffer & buffer2) { + buffer1.append(buffer2.c_str()); + return buffer1; +} + +#endif diff --git a/lib/struct_factory.cpp b/lib/struct_factory.cpp new file mode 100644 index 000000000..173554b83 --- /dev/null +++ b/lib/struct_factory.cpp @@ -0,0 +1,78 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + struct_factory.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-11-06. + +Revision History: + +--*/ +#include"struct_factory.h" +#include"proto_model.h" + +struct_factory::value_set * struct_factory::get_value_set(sort * s) { + value_set * set = 0; + if (!m_sort2value_set.find(s, set)) { + set = alloc(value_set); + m_sort2value_set.insert(s, set); + m_sorts.push_back(s); + m_sets.push_back(set); + } + SASSERT(set != 0); + return set; +} + +struct_factory::struct_factory(ast_manager & m, family_id fid, proto_model & md): + value_factory(m, fid), + m_model(md), + m_values(m), + m_sorts(m) { +} + +struct_factory::~struct_factory() { + std::for_each(m_sets.begin(), m_sets.end(), delete_proc()); +} + +void struct_factory::register_value(expr * new_value) { + sort * s = m_manager.get_sort(new_value); + value_set * set = get_value_set(s); + if (!set->contains(new_value)) { + m_values.push_back(new_value); + set->insert(new_value); + } +} + +bool struct_factory::get_some_values(sort * s, expr_ref & v1, expr_ref & v2) { + value_set * set = get_value_set(s); + switch (set->size()) { + case 0: + v1 = get_fresh_value(s); + v2 = get_fresh_value(s); + return v1 != 0 && v2 != 0; + case 1: + v1 = get_some_value(s); + v2 = get_fresh_value(s); + return v2 != 0; + default: + obj_hashtable::iterator it = set->begin(); + v1 = *it; + ++it; + v2 = *it; + return true; + } +} + + + + + + diff --git a/lib/struct_factory.h b/lib/struct_factory.h new file mode 100644 index 000000000..62e73589a --- /dev/null +++ b/lib/struct_factory.h @@ -0,0 +1,56 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + struct_factory.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-11-06. + +Revision History: + +--*/ +#ifndef _STRUCT_FACTORY_H_ +#define _STRUCT_FACTORY_H_ + +#include"value_factory.h" +#include"obj_hashtable.h" + +class proto_model; + +/** + \brief Abstract factory for structured values such as: arrays and algebraic datatypes. +*/ +class struct_factory : public value_factory { +protected: + typedef obj_hashtable value_set; + typedef obj_map sort2value_set; + + proto_model & m_model; + sort2value_set m_sort2value_set; + expr_ref_vector m_values; + sort_ref_vector m_sorts; + ptr_vector m_sets; + + value_set * get_value_set(sort * s); + +public: + struct_factory(ast_manager & m, family_id fid, proto_model & md); + + virtual ~struct_factory(); + + virtual bool get_some_values(sort * s, expr_ref & v1, expr_ref & v2); + + virtual void register_value(expr * array_value); + + proto_model & get_model() { return m_model; } +}; + +#endif /* _STRUCT_FACTORY_H_ */ + diff --git a/lib/subpaving.cpp b/lib/subpaving.cpp new file mode 100644 index 000000000..0bbabc683 --- /dev/null +++ b/lib/subpaving.cpp @@ -0,0 +1,288 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + subpaving.cpp + +Abstract: + + Subpaving for non-linear arithmetic. + This is a wrapper for the different implementations + of the subpaving module. + This wrapper is the main interface between Z3 other modules and subpaving. + Thus, it assumes that polynomials have precise integer coefficients, and + bounds are rationals. If a particular implementation uses floats, then + internally the bounds are approximated. + +Author: + + Leonardo de Moura (leonardo) 2012-08-07. + +Revision History: + +--*/ +#include"subpaving.h" +#include"subpaving_types.h" +#include"subpaving_mpq.h" +#include"subpaving_mpf.h" +#include"subpaving_hwf.h" +#include"subpaving_mpff.h" +#include"subpaving_mpfx.h" + +namespace subpaving { + + template + class context_wrapper : public context { + protected: + CTX m_ctx; + public: + context_wrapper(typename CTX::numeral_manager & m, params_ref const & p, small_object_allocator * a):m_ctx(m, p, a) {} + virtual ~context_wrapper() {} + virtual unsigned num_vars() const { return m_ctx.num_vars(); } + virtual var mk_var(bool is_int) { return m_ctx.mk_var(is_int); } + virtual bool is_int(var x) const { return m_ctx.is_int(x); } + virtual var mk_monomial(unsigned sz, power const * pws) { return m_ctx.mk_monomial(sz, pws); } + virtual void inc_ref(ineq * a) { m_ctx.inc_ref(reinterpret_cast(a)); } + virtual void dec_ref(ineq * a) { m_ctx.dec_ref(reinterpret_cast(a)); } + virtual void add_clause(unsigned sz, ineq * const * atoms) { m_ctx.add_clause(sz, reinterpret_cast(atoms)); } + virtual void display_constraints(std::ostream & out, bool use_star) const { m_ctx.display_constraints(out, use_star); } + virtual void set_cancel(bool f) { m_ctx.set_cancel(f); } + virtual void set_display_proc(display_var_proc * p) { m_ctx.set_display_proc(p); } + virtual void reset_statistics() { m_ctx.reset_statistics(); } + virtual void collect_statistics(statistics & st) const { m_ctx.collect_statistics(st); } + virtual void collect_param_descrs(param_descrs & r) { m_ctx.collect_param_descrs(r); } + virtual void updt_params(params_ref const & p) { m_ctx.updt_params(p); } + virtual void operator()() { m_ctx(); } + virtual void display_bounds(std::ostream & out) const { m_ctx.display_bounds(out); } + }; + + class context_mpq_wrapper : public context_wrapper { + scoped_mpq m_c; + scoped_mpq_vector m_as; + public: + context_mpq_wrapper(unsynch_mpq_manager & m, params_ref const & p, small_object_allocator * a): + context_wrapper(m, p, a), + m_c(m), + m_as(m) + {} + + virtual ~context_mpq_wrapper() {} + + virtual unsynch_mpq_manager & qm() const { return m_ctx.nm(); } + + virtual var mk_sum(mpz const & c, unsigned sz, mpz const * as, var const * xs) { + m_as.reserve(sz); + for (unsigned i = 0; i < sz; i++) { + m_ctx.nm().set(m_as[i], as[i]); + } + m_ctx.nm().set(m_c, c); + return m_ctx.mk_sum(m_c, sz, m_as.c_ptr(), xs); + } + virtual ineq * mk_ineq(var x, mpq const & k, bool lower, bool open) { + return reinterpret_cast(m_ctx.mk_ineq(x, k, lower, open)); + } + }; + + class context_mpf_wrapper : public context_wrapper { + f2n & m_fm; + unsynch_mpq_manager & m_qm; + scoped_mpf m_c; + scoped_mpf_vector m_as; + scoped_mpq m_q1, m_q2; + + // Convert the mpz (integer) into a mpf, and throws an exception if the conversion is not precise. + void int2mpf(mpz const & a, mpf & o) { + m_qm.set(m_q1, a); + m_ctx.nm().set(o, m_q1); + m_ctx.nm().m().to_rational(o, m_q2); + if (!m_qm.eq(m_q1, m_q2)) + throw subpaving::exception(); + } + + public: + context_mpf_wrapper(f2n & fm, params_ref const & p, small_object_allocator * a): + context_wrapper(fm, p, a), + m_fm(fm), + m_qm(fm.m().mpq_manager()), + m_c(fm.m()), + m_as(fm.m()), + m_q1(m_qm), + m_q2(m_qm) { + } + + virtual ~context_mpf_wrapper() {} + + virtual unsynch_mpq_manager & qm() const { return m_qm; } + + virtual var mk_sum(mpz const & c, unsigned sz, mpz const * as, var const * xs) { + try { + m_as.reserve(sz); + for (unsigned i = 0; i < sz; i++) { + int2mpf(as[i], m_as[i]); + } + int2mpf(c, m_c); + return m_ctx.mk_sum(m_c, sz, m_as.c_ptr(), xs); + } + catch (f2n::exception) { + throw subpaving::exception(); + } + } + virtual ineq * mk_ineq(var x, mpq const & k, bool lower, bool open) { + try { + f2n & m = m_ctx.nm(); + if (lower) + m.round_to_minus_inf(); + else + m.round_to_plus_inf(); + m.set(m_c, k); + return reinterpret_cast(m_ctx.mk_ineq(x, m_c, lower, open)); + } + catch (f2n::exception) { + throw subpaving::exception(); + } + } + }; + + class context_hwf_wrapper : public context_wrapper { + f2n & m_fm; + unsynch_mpq_manager & m_qm; + hwf m_c; + svector m_as; + + // Convert the mpz (integer) into a hwf, and throws an exception if the conversion is not precise. + void int2hwf(mpz const & a, hwf & o) { + if (!m_qm.is_int64(a)) + throw subpaving::exception(); + int64 val = m_qm.get_int64(a); + double dval = static_cast(val); + m_ctx.nm().set(o, dval); + double _dval = m_ctx.nm().m().to_double(o); + // TODO check the following test + if (static_cast(_dval) != val) + throw subpaving::exception(); + } + + public: + context_hwf_wrapper(f2n & fm, unsynch_mpq_manager & qm, params_ref const & p, small_object_allocator * a): + context_wrapper(fm, p, a), + m_fm(fm), + m_qm(qm) { + } + + virtual ~context_hwf_wrapper() {} + + virtual unsynch_mpq_manager & qm() const { return m_qm; } + + virtual var mk_sum(mpz const & c, unsigned sz, mpz const * as, var const * xs) { + try { + m_as.reserve(sz); + for (unsigned i = 0; i < sz; i++) { + int2hwf(as[i], m_as[i]); + } + int2hwf(c, m_c); + return m_ctx.mk_sum(m_c, sz, m_as.c_ptr(), xs); + } + catch (f2n::exception) { + throw subpaving::exception(); + } + } + virtual ineq * mk_ineq(var x, mpq const & k, bool lower, bool open) { + try { + f2n & m = m_ctx.nm(); + if (lower) + m.round_to_minus_inf(); + else + m.round_to_plus_inf(); + m.set(m_c, k); + return reinterpret_cast(m_ctx.mk_ineq(x, m_c, lower, open)); + } + catch (f2n::exception) { + throw subpaving::exception(); + } + } + }; + + template + class context_fpoint_wrapper : public context_wrapper { + unsynch_mpq_manager & m_qm; + _scoped_numeral m_c; + _scoped_numeral_vector m_as; + scoped_mpz m_z1, m_z2; + + void int2fpoint(mpz const & a, typename context_fpoint::numeral & o) { + m_qm.set(m_z1, a); + this->m_ctx.nm().set(o, m_qm, m_z1); + this->m_ctx.nm().to_mpz(o, m_qm, m_z2); + if (!m_qm.eq(m_z1, m_z2)) + throw subpaving::exception(); + } + + public: + context_fpoint_wrapper(typename context_fpoint::numeral_manager & m, unsynch_mpq_manager & qm, params_ref const & p, small_object_allocator * a): + context_wrapper(m, p, a), + m_qm(qm), + m_c(m), + m_as(m), + m_z1(m_qm), + m_z2(m_qm) { + } + + virtual ~context_fpoint_wrapper() {} + + virtual unsynch_mpq_manager & qm() const { return m_qm; } + + virtual var mk_sum(mpz const & c, unsigned sz, mpz const * as, var const * xs) { + try { + m_as.reserve(sz); + for (unsigned i = 0; i < sz; i++) { + int2fpoint(as[i], m_as[i]); + } + int2fpoint(c, m_c); + return this->m_ctx.mk_sum(m_c, sz, m_as.c_ptr(), xs); + } + catch (typename context_fpoint::numeral_manager::exception) { + throw subpaving::exception(); + } + } + + virtual ineq * mk_ineq(var x, mpq const & k, bool lower, bool open) { + try { + typename context_fpoint::numeral_manager & m = this->m_ctx.nm(); + if (lower) + m.round_to_minus_inf(); + else + m.round_to_plus_inf(); + m.set(m_c, m_qm, k); + return reinterpret_cast(this->m_ctx.mk_ineq(x, m_c, lower, open)); + } + catch (typename context_fpoint::numeral_manager::exception) { + throw subpaving::exception(); + } + } + }; + + typedef context_fpoint_wrapper context_mpff_wrapper; + typedef context_fpoint_wrapper context_mpfx_wrapper; + + context * mk_mpq_context(unsynch_mpq_manager & m, params_ref const & p, small_object_allocator * a) { + return alloc(context_mpq_wrapper, m, p, a); + } + + context * mk_mpf_context(f2n & m, params_ref const & p, small_object_allocator * a) { + return alloc(context_mpf_wrapper, m, p, a); + } + + context * mk_hwf_context(f2n & m, unsynch_mpq_manager & qm, params_ref const & p, small_object_allocator * a) { + return alloc(context_hwf_wrapper, m, qm, p, a); + } + + context * mk_mpff_context(mpff_manager & m, unsynch_mpq_manager & qm, params_ref const & p, small_object_allocator * a) { + return alloc(context_mpff_wrapper, m, qm, p, a); + } + + context * mk_mpfx_context(mpfx_manager & m, unsynch_mpq_manager & qm, params_ref const & p, small_object_allocator * a) { + return alloc(context_mpfx_wrapper, m, qm, p, a); + } + +}; diff --git a/lib/subpaving.h b/lib/subpaving.h new file mode 100644 index 000000000..ee6946fc5 --- /dev/null +++ b/lib/subpaving.h @@ -0,0 +1,124 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + subpaving.h + +Abstract: + + Subpaving for non-linear arithmetic. + This is a wrapper for the different implementations + of the subpaving module. + This wrapper is the main interface between Z3 other modules and subpaving. + Thus, it assumes that polynomials have precise integer coefficients, and + bounds are rationals. If a particular implementation uses floats, then + internally the bounds are approximated. + +Author: + + Leonardo de Moura (leonardo) 2012-08-07. + +Revision History: + +--*/ +#ifndef __SUBPAVING_H_ +#define __SUBPAVING_H_ + +#include"mpq.h" +#include"subpaving_types.h" +#include"params.h" +#include"statistics.h" + +template class f2n; +class mpf_manager; +class hwf_manager; +class mpff_manager; +class mpfx_manager; + +namespace subpaving { + +class context { +public: + virtual ~context() {} + + virtual unsynch_mpq_manager & qm() const = 0; + + /** + \brief Return the number of variables in this subpaving object. + */ + virtual unsigned num_vars() const = 0; + + /** + \brief Create a new variable. + */ + virtual var mk_var(bool is_int) = 0; + + /** + \brief Return true if \c x is an integer variable. + */ + virtual bool is_int(var x) const = 0; + + /** + \brief Create the monomial xs[0]^ks[0] * ... * xs[sz-1]^ks[sz-1]. + The result is a variable y s.t. y = xs[0]^ks[0] * ... * xs[sz-1]^ks[sz-1]. + + \pre for all i \in [0, sz-1] : ks[i] > 0 + \pre sz > 0 + */ + virtual var mk_monomial(unsigned sz, power const * pws) = 0; + + /** + \brief Create the sum c + as[0]*xs[0] + ... + as[sz-1]*xs[sz-1]. + The result is a variable y s.t. y = c + as[0]*xs[0] + ... + as[sz-1]*xs[sz-1]. + + \pre sz > 0 + \pre for all i \in [0, sz-1] : as[i] != 0 + */ + virtual var mk_sum(mpz const & c, unsigned sz, mpz const * as, var const * xs) = 0; + + /** + \brief Create an inequality. + */ + virtual ineq * mk_ineq(var x, mpq const & k, bool lower, bool open) = 0; + virtual void inc_ref(ineq * a) = 0; + virtual void dec_ref(ineq * a) = 0; + + /** + \brief Assert the clause atoms[0] \/ ... \/ atoms[sz-1] + \pre sz >= 1 + */ + virtual void add_clause(unsigned sz, ineq * const * atoms) = 0; + + /** + \brief Display constraints asserted in the subpaving. + */ + virtual void display_constraints(std::ostream & out, bool use_star = false) const = 0; + + virtual void set_cancel(bool f) = 0; + + virtual void collect_param_descrs(param_descrs & r) = 0; + + virtual void updt_params(params_ref const & p) = 0; + + virtual void set_display_proc(display_var_proc * p) = 0; + + virtual void reset_statistics() = 0; + + virtual void collect_statistics(statistics & st) const = 0; + + virtual void operator()() = 0; + + virtual void display_bounds(std::ostream & out) const = 0; +}; + +context * mk_mpq_context(unsynch_mpq_manager & m, params_ref const & p = params_ref(), small_object_allocator * a = 0); +context * mk_mpf_context(f2n & m, params_ref const & p = params_ref(), small_object_allocator * a = 0); +context * mk_hwf_context(f2n & m, unsynch_mpq_manager & qm, params_ref const & p = params_ref(), small_object_allocator * a = 0); +context * mk_mpff_context(mpff_manager & m, unsynch_mpq_manager & qm, params_ref const & p = params_ref(), small_object_allocator * a = 0); +context * mk_mpfx_context(mpfx_manager & m, unsynch_mpq_manager & qm, params_ref const & p = params_ref(), small_object_allocator * a = 0); + +}; + + +#endif diff --git a/lib/subpaving_cmds.cpp b/lib/subpaving_cmds.cpp new file mode 100644 index 000000000..9b84ddf68 --- /dev/null +++ b/lib/subpaving_cmds.cpp @@ -0,0 +1,56 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + subpaving_cmds.cpp + +Abstract: + Commands for debugging subpaving module. + +Author: + + Leonardo (leonardo) 2012-08-09 + +Notes: + +--*/ +#include +#include"cmd_context.h" +#include"cmd_util.h" +#include"expr2subpaving.h" +#include"th_rewriter.h" +#include"ast_smt2_pp.h" +#include"expr2var.h" + +static void to_subpaving(cmd_context & ctx, expr * t) { + ast_manager & m = ctx.m(); + unsynch_mpq_manager qm; + scoped_ptr s; + s = subpaving::mk_mpq_context(qm); + expr2var e2v(m); + expr2subpaving e2s(m, *s, &e2v); + params_ref p; + p.set_bool(":mul-to-power", true); + th_rewriter simp(m, p); + expr_ref t_s(m); + simp(t, t_s); + scoped_mpz n(qm), d(qm); + ctx.regular_stream() << mk_ismt2_pp(t_s, m) << "\n=======>" << std::endl; + subpaving::var x = e2s.internalize_term(t_s, n, d); + expr2var::iterator it = e2v.begin(); + expr2var::iterator end = e2v.end(); + for (; it != end; ++it) { + ctx.regular_stream() << "x" << it->m_value << " := " << mk_ismt2_pp(it->m_key, m) << "\n"; + } + s->display_constraints(ctx.regular_stream()); + ctx.regular_stream() << n << "/" << d << " x" << x << "\n"; +} + +UNARY_CMD(to_subpaving_cmd, "to-subpaving", "", "internalize expression into subpaving module", CPK_EXPR, expr *, to_subpaving(ctx, arg);); + +void install_subpaving_cmds(cmd_context & ctx) { +#ifndef _EXTERNAL_RELEASE + ctx.insert(alloc(to_subpaving_cmd)); +#endif +} diff --git a/lib/subpaving_cmds.h b/lib/subpaving_cmds.h new file mode 100644 index 000000000..33ed347b7 --- /dev/null +++ b/lib/subpaving_cmds.h @@ -0,0 +1,21 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + subpaving_cmds.h + +Abstract: + Commands for debugging subpaving module. + +Author: + + Leonardo (leonardo) 2012-08-09 + +Notes: + +--*/ + +class cmd_context; + +void install_subpaving_cmds(cmd_context & ctx); diff --git a/lib/subpaving_hwf.cpp b/lib/subpaving_hwf.cpp new file mode 100644 index 000000000..142db72f6 --- /dev/null +++ b/lib/subpaving_hwf.cpp @@ -0,0 +1,23 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + subpaving_hwf.cpp + +Abstract: + + Subpaving for non-linear arithmetic using hardware floats. + +Author: + + Leonardo de Moura (leonardo) 2012-08-06. + +Revision History: + +--*/ +#include"subpaving_hwf.h" +#include"subpaving_t_def.h" + +// force template instantiation +template class subpaving::context_t; diff --git a/lib/subpaving_hwf.h b/lib/subpaving_hwf.h new file mode 100644 index 000000000..a6b317a79 --- /dev/null +++ b/lib/subpaving_hwf.h @@ -0,0 +1,48 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + subpaving_hwf.h + +Abstract: + + Subpaving for non-linear arithmetic using hardware floats. + +Author: + + Leonardo de Moura (leonardo) 2012-08-06. + +Revision History: + +--*/ +#ifndef __SUBPAVING_HWF_H_ +#define __SUBPAVING_HWF_H_ + +#include"subpaving_t.h" +#include"f2n.h" +#include"hwf.h" + +namespace subpaving { + +struct config_hwf { + f2n & m_manager; +public: + typedef f2n numeral_manager; + typedef f2n::exception exception; + + static void round_to_minus_inf(numeral_manager & m) { m.round_to_minus_inf(); } + static void round_to_plus_inf(numeral_manager & m) { m.round_to_plus_inf(); } + static void set_rounding(numeral_manager & m, bool to_plus_inf) { m.set_rounding(to_plus_inf); } + config_hwf(f2n & m):m_manager(m) {} + f2n & m() const { return const_cast &>(m_manager); } +}; + +class context_hwf : public context_t { +public: + context_hwf(f2n & m, params_ref const & p, small_object_allocator * a):context_t(config_hwf(m), p, a) {} +}; + +}; + +#endif diff --git a/lib/subpaving_mpf.cpp b/lib/subpaving_mpf.cpp new file mode 100644 index 000000000..96ada040b --- /dev/null +++ b/lib/subpaving_mpf.cpp @@ -0,0 +1,23 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + subpaving_mpf.cpp + +Abstract: + + Subpaving for non-linear arithmetic using multi-precision floats. + +Author: + + Leonardo de Moura (leonardo) 2012-07-31. + +Revision History: + +--*/ +#include"subpaving_mpf.h" +#include"subpaving_t_def.h" + +// force template instantiation +template class subpaving::context_t; diff --git a/lib/subpaving_mpf.h b/lib/subpaving_mpf.h new file mode 100644 index 000000000..014a99f10 --- /dev/null +++ b/lib/subpaving_mpf.h @@ -0,0 +1,49 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + subpaving_mpf.h + +Abstract: + + Subpaving for non-linear arithmetic using multi-precision floats. + +Author: + + Leonardo de Moura (leonardo) 2012-07-31. + +Revision History: + +--*/ +#ifndef __SUBPAVING_MPF_H_ +#define __SUBPAVING_MPF_H_ + +#include"subpaving_t.h" +#include"mpf.h" +#include"f2n.h" + +namespace subpaving { + +struct config_mpf { + f2n & m_manager; +public: + typedef f2n numeral_manager; + typedef mpf numeral; + typedef f2n::exception exception; + + static void round_to_minus_inf(numeral_manager & m) { m.round_to_minus_inf(); } + static void round_to_plus_inf(numeral_manager & m) { m.round_to_plus_inf(); } + static void set_rounding(numeral_manager & m, bool to_plus_inf) { m.set_rounding(to_plus_inf); } + config_mpf(f2n & m):m_manager(m) {} + f2n & m() const { return const_cast &>(m_manager); } +}; + +class context_mpf : public context_t { +public: + context_mpf(f2n & m, params_ref const & p, small_object_allocator * a):context_t(config_mpf(m), p, a) {} +}; + +}; + +#endif diff --git a/lib/subpaving_mpff.cpp b/lib/subpaving_mpff.cpp new file mode 100644 index 000000000..f5be655ba --- /dev/null +++ b/lib/subpaving_mpff.cpp @@ -0,0 +1,23 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + subpaving_mpff.cpp + +Abstract: + + Subpaving for non-linear arithmetic using mpff numerals. + +Author: + + Leonardo de Moura (leonardo) 2012-09-18. + +Revision History: + +--*/ +#include"subpaving_mpff.h" +#include"subpaving_t_def.h" + +// force template instantiation +template class subpaving::context_t; diff --git a/lib/subpaving_mpff.h b/lib/subpaving_mpff.h new file mode 100644 index 000000000..f3353690a --- /dev/null +++ b/lib/subpaving_mpff.h @@ -0,0 +1,45 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + subpaving_mpff.h + +Abstract: + + Subpaving for non-linear arithmetic using mpff numerals + +Author: + + Leonardo de Moura (leonardo) 2012-09-18. + +Revision History: + +--*/ +#ifndef __SUBPAVING_MPFF_H_ +#define __SUBPAVING_MPFF_H_ + +#include"subpaving_t.h" +#include"mpff.h" + +namespace subpaving { + +struct config_mpff { + typedef mpff_manager numeral_manager; + typedef mpff_manager::exception exception; + + static void round_to_minus_inf(numeral_manager & m) { m.round_to_minus_inf(); } + static void round_to_plus_inf(numeral_manager & m) { m.round_to_plus_inf(); } + static void set_rounding(numeral_manager & m, bool to_plus_inf) { m.set_rounding(to_plus_inf); } + + numeral_manager & m_manager; + + config_mpff(numeral_manager & m):m_manager(m) {} + numeral_manager & m() const { return m_manager; } +}; + +typedef context_t context_mpff; + +}; + +#endif diff --git a/lib/subpaving_mpfx.cpp b/lib/subpaving_mpfx.cpp new file mode 100644 index 000000000..b8a1e048f --- /dev/null +++ b/lib/subpaving_mpfx.cpp @@ -0,0 +1,23 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + subpaving_mpfx.cpp + +Abstract: + + Subpaving for non-linear arithmetic using mpfx numerals. + +Author: + + Leonardo de Moura (leonardo) 2012-09-18. + +Revision History: + +--*/ +#include"subpaving_mpfx.h" +#include"subpaving_t_def.h" + +// force template instantiation +template class subpaving::context_t; diff --git a/lib/subpaving_mpfx.h b/lib/subpaving_mpfx.h new file mode 100644 index 000000000..78e1d31f3 --- /dev/null +++ b/lib/subpaving_mpfx.h @@ -0,0 +1,45 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + subpaving_mpfx.h + +Abstract: + + Subpaving for non-linear arithmetic using mpfx numerals + +Author: + + Leonardo de Moura (leonardo) 2012-09-20. + +Revision History: + +--*/ +#ifndef __SUBPAVING_MPFX_H_ +#define __SUBPAVING_MPFX_H_ + +#include"subpaving_t.h" +#include"mpfx.h" + +namespace subpaving { + +struct config_mpfx { + typedef mpfx_manager numeral_manager; + typedef mpfx_manager::exception exception; + + static void round_to_minus_inf(numeral_manager & m) { m.round_to_minus_inf(); } + static void round_to_plus_inf(numeral_manager & m) { m.round_to_plus_inf(); } + static void set_rounding(numeral_manager & m, bool to_plus_inf) { m.set_rounding(to_plus_inf); } + + numeral_manager & m_manager; + + config_mpfx(numeral_manager & m):m_manager(m) {} + numeral_manager & m() const { return m_manager; } +}; + +typedef context_t context_mpfx; + +}; + +#endif diff --git a/lib/subpaving_mpq.cpp b/lib/subpaving_mpq.cpp new file mode 100644 index 000000000..b7b76a5e3 --- /dev/null +++ b/lib/subpaving_mpq.cpp @@ -0,0 +1,23 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + subpaving_mpq.cpp + +Abstract: + + Subpaving for non-linear arithmetic using rationals. + +Author: + + Leonardo de Moura (leonardo) 2012-07-31. + +Revision History: + +--*/ +#include"subpaving_mpq.h" +#include"subpaving_t_def.h" + +// force template instantiation +template class subpaving::context_t; diff --git a/lib/subpaving_mpq.h b/lib/subpaving_mpq.h new file mode 100644 index 000000000..e3bd5b562 --- /dev/null +++ b/lib/subpaving_mpq.h @@ -0,0 +1,43 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + subpaving_mpq.h + +Abstract: + + Subpaving for non-linear arithmetic using rationals + +Author: + + Leonardo de Moura (leonardo) 2012-07-31. + +Revision History: + +--*/ +#ifndef __SUBPAVING_MPQ_H_ +#define __SUBPAVING_MPQ_H_ + +#include"subpaving_t.h" +#include"mpq.h" + +namespace subpaving { + +struct config_mpq { + typedef unsynch_mpq_manager numeral_manager; + struct exception {}; + + static void round_to_minus_inf(numeral_manager & m) {} + static void round_to_plus_inf(numeral_manager & m) {} + static void set_rounding(numeral_manager & m, bool to_plus_info) {} + numeral_manager & m_manager; + config_mpq(numeral_manager & m):m_manager(m) {} + numeral_manager & m() const { return m_manager; } +}; + +typedef context_t context_mpq; + +}; + +#endif diff --git a/lib/subpaving_t.h b/lib/subpaving_t.h new file mode 100644 index 000000000..26a9802ad --- /dev/null +++ b/lib/subpaving_t.h @@ -0,0 +1,853 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + subpaving_t.h + +Abstract: + + Subpaving template for non-linear arithmetic. + +Author: + + Leonardo de Moura (leonardo) 2012-07-31. + +Revision History: + +--*/ +#ifndef __SUBPAVING_T_H_ +#define __SUBPAVING_T_H_ + +#include +#include"tptr.h" +#include"small_object_allocator.h" +#include"chashtable.h" +#include"parray.h" +#include"interval.h" +#include"scoped_numeral_vector.h" +#include"subpaving_types.h" +#include"params.h" +#include"statistics.h" +#include"lbool.h" +#include"id_gen.h" +#ifdef _MSC_VER +#pragma warning(disable : 4200) +#pragma warning(disable : 4355) +#endif + +namespace subpaving { + +template +class context_t { +public: + typedef typename C::numeral_manager numeral_manager; + typedef typename numeral_manager::numeral numeral; + + /** + \brief Inequalities used to encode a problem. + */ + class ineq { + friend class context_t; + var m_x; + numeral m_val; + unsigned m_ref_count:30; + unsigned m_lower:1; + unsigned m_open:1; + public: + var x() const { return m_x; } + numeral const & value() const { return m_val; } + bool is_lower() const { return m_lower; } + bool is_open() const { return m_open; } + void display(std::ostream & out, numeral_manager & nm, display_var_proc const & proc = display_var_proc()); + struct lt_var_proc { bool operator()(ineq const * a, ineq const * b) const { return a->m_x < b->m_x; } }; + }; + + class node; + + class constraint { + public: + enum kind { + CLAUSE, MONOMIAL, POLYNOMIAL + // TODO: add SIN, COS, TAN, ... + }; + protected: + kind m_kind; + uint64 m_timestamp; + public: + constraint(kind k):m_kind(k), m_timestamp(0) {} + + kind get_kind() const { return m_kind; } + + // Return the timestamp of the last propagation visit + uint64 timestamp() const { return m_timestamp; } + // Reset propagation visit time + void set_visited(uint64 ts) { m_timestamp = ts; } + }; + + /** + \brief Clauses in the problem description and lemmas learned during paving. + */ + class clause : public constraint { + friend class context_t; + unsigned m_size; //!< Number of atoms in the clause. + unsigned m_lemma:1; //!< True if it is a learned clause. + unsigned m_watched:1; //!< True if it we are watching this clause. All non-lemmas are watched. + unsigned m_num_jst:30; //!< Number of times it is used to justify some bound. + ineq * m_atoms[0]; + static unsigned get_obj_size(unsigned sz) { return sizeof(clause) + sz*sizeof(ineq*); } + public: + clause():constraint(constraint::CLAUSE) {} + unsigned size() const { return m_size; } + bool watched() const { return m_watched; } + ineq * operator[](unsigned i) const { SASSERT(i < size()); return m_atoms[i]; } + void display(std::ostream & out, numeral_manager & nm, display_var_proc const & proc = display_var_proc()); + }; + + class justification { + void * m_data; + public: + enum kind { + AXIOM = 0, + ASSUMPTION, + CLAUSE, + VAR_DEF + }; + + justification(bool axiom = true) { + m_data = axiom ? reinterpret_cast(static_cast(AXIOM)) : reinterpret_cast(static_cast(ASSUMPTION)); + } + justification(justification const & source) { m_data = source.m_data; } + explicit justification(clause * c) { m_data = TAG(void*, c, CLAUSE); } + explicit justification(var x) { m_data = BOXTAGINT(void*, x, VAR_DEF); } + + kind get_kind() const { return static_cast(GET_TAG(m_data)); } + bool is_clause() const { return get_kind() == CLAUSE; } + bool is_axiom() const { return get_kind() == AXIOM; } + bool is_assumption() const { return get_kind() == ASSUMPTION; } + bool is_var_def() const { return get_kind() == VAR_DEF; } + + clause * get_clause() const { + SASSERT(is_clause()); + return UNTAG(clause*, m_data); + } + + var get_var() const { + SASSERT(is_var_def()); + return UNBOXINT(m_data); + } + + bool operator==(justification const & other) const { return m_data == other.m_data; } + bool operator!=(justification const & other) const { return !operator==(other); } + }; + + class bound { + friend class context_t; + numeral m_val; + unsigned m_x:29; + unsigned m_lower:1; + unsigned m_open:1; + unsigned m_mark:1; + uint64 m_timestamp; + bound * m_prev; + justification m_jst; + void set_timestamp(uint64 ts) { m_timestamp = ts; } + public: + var x() const { return static_cast(m_x); } + numeral const & value() const { return m_val; } + numeral & value() { return m_val; } + bool is_lower() const { return m_lower; } + bool is_open() const { return m_open; } + uint64 timestamp() const { return m_timestamp; } + bound * prev() const { return m_prev; } + justification jst() const { return m_jst; } + void display(std::ostream & out, numeral_manager & nm, display_var_proc const & proc = display_var_proc()); + }; + + struct bound_array_config { + typedef context_t value_manager; + typedef small_object_allocator allocator; + typedef bound * value; + static const bool ref_count = false; + static const bool preserve_roots = true; + static const unsigned max_trail_sz = 16; + static const unsigned factor = 2; + }; + + // auxiliary declarations for parray_manager + void dec_ref(bound *) {} + void inc_ref(bound *) {} + + typedef parray_manager bound_array_manager; + typedef typename bound_array_manager::ref bound_array; + + /** + \brief Node in the context_t. + */ + class node { + bound_array_manager & m_bm; + bound_array m_lowers; + bound_array m_uppers; + var m_conflict; + unsigned m_id; + unsigned m_depth; + bound * m_trail; + node * m_parent; //!< parent node + node * m_first_child; + node * m_next_sibling; + // Doubly linked list of leaves to be processed + node * m_prev; + node * m_next; + public: + node(context_t & s, unsigned id); + node(node * parent, unsigned id); + // return unique indentifier. + unsigned id() const { return m_id; } + bound_array_manager & bm() const { return m_bm; } + bound_array & lowers() { return m_lowers; } + bound_array & uppers() { return m_uppers; } + bool inconsistent() const { return m_conflict != null_var; } + void set_conflict(var x) { SASSERT(!inconsistent()); m_conflict = x; } + bound * trail_stack() const { return m_trail; } + bound * parent_trail_stack() const { return m_parent == 0 ? 0 : m_parent->m_trail; } + bound * lower(var x) const { return bm().get(m_lowers, x); } + bound * upper(var x) const { return bm().get(m_uppers, x); } + node * parent() const { return m_parent; } + node * first_child() const { return m_first_child; } + node * next_sibling() const { return m_next_sibling; } + node * prev() const { return m_prev; } + node * next() const { return m_next; } + /** + \brief Return true if x is unbounded in this node + */ + bool is_unbounded(var x) const { return lower(x) == 0 && upper(x) == 0; } + void push(bound * b); + + void set_first_child(node * n) { m_first_child = n; } + void set_next_sibling(node * n) { m_next_sibling = n; } + void set_next(node * n) { m_next = n; } + void set_prev(node * n) { m_prev = n; } + + unsigned depth() const { return m_depth; } + }; + + /** + \brief Intervals are just temporary place holders. + The pavers maintain bounds. + */ + struct interval { + bool m_constant; // Flag: constant intervals are pairs + // constant intervals + node * m_node; + var m_x; + // mutable intervals + numeral m_l_val; + bool m_l_inf; + bool m_l_open; + numeral m_u_val; + bool m_u_inf; + bool m_u_open; + + interval():m_constant(false) {} + void set_constant(node * n, var x) { + m_constant = true; + m_node = n; + m_x = x; + } + void set_mutable() { m_constant = false; } + }; + + class interval_config { + public: + typedef typename C::numeral_manager numeral_manager; + typedef typename numeral_manager::numeral numeral; + typedef typename context_t::interval interval; + private: + numeral_manager & m_manager; + public: + interval_config(numeral_manager & m):m_manager(m) {} + + numeral_manager & m() const { return m_manager; } + void round_to_minus_inf() { C::round_to_minus_inf(m()); } + void round_to_plus_inf() { C::round_to_plus_inf(m()); } + void set_rounding(bool to_plus_inf) { C::set_rounding(m(), to_plus_inf); } + numeral const & lower(interval const & a) const { + if (a.m_constant) { + bound * b = a.m_node->lower(a.m_x); + return b == 0 ? a.m_l_val /* don't care */ : b->value(); + } + return a.m_l_val; + } + numeral const & upper(interval const & a) const { + if (a.m_constant) { + bound * b = a.m_node->upper(a.m_x); + return b == 0 ? a.m_u_val /* don't care */ : b->value(); + } + return a.m_u_val; + } + numeral & lower(interval & a) { SASSERT(!a.m_constant); return a.m_l_val; } + numeral & upper(interval & a) { SASSERT(!a.m_constant); return a.m_u_val; } + bool lower_is_inf(interval const & a) const { return a.m_constant ? a.m_node->lower(a.m_x) == 0 : a.m_l_inf; } + bool upper_is_inf(interval const & a) const { return a.m_constant ? a.m_node->upper(a.m_x) == 0 : a.m_u_inf; } + bool lower_is_open(interval const & a) const { + if (a.m_constant) { + bound * b = a.m_node->lower(a.m_x); + return b == 0 || b->is_open(); + } + return a.m_l_open; + } + bool upper_is_open(interval const & a) const { + if (a.m_constant) { + bound * b = a.m_node->upper(a.m_x); + return b == 0 || b->is_open(); + } + return a.m_u_open; + } + // Setters + void set_lower(interval & a, numeral const & n) { SASSERT(!a.m_constant); m().set(a.m_l_val, n); } + void set_upper(interval & a, numeral const & n) { SASSERT(!a.m_constant); m().set(a.m_u_val, n); } + void set_lower_is_open(interval & a, bool v) { SASSERT(!a.m_constant); a.m_l_open = v; } + void set_upper_is_open(interval & a, bool v) { SASSERT(!a.m_constant); a.m_u_open = v; } + void set_lower_is_inf(interval & a, bool v) { SASSERT(!a.m_constant); a.m_l_inf = v; } + void set_upper_is_inf(interval & a, bool v) { SASSERT(!a.m_constant); a.m_u_inf = v; } + }; + + typedef ::interval_manager interval_manager; + + class definition : public constraint { + public: + definition(typename constraint::kind k):constraint(k) {} + }; + + class monomial : public definition { + friend class context_t; + unsigned m_size; + power m_powers[0]; + monomial(unsigned sz, power const * pws); + static unsigned get_obj_size(unsigned sz) { return sizeof(monomial) + sz*sizeof(power); } + public: + unsigned size() const { return m_size; } + power const & get_power(unsigned idx) const { SASSERT(idx < size()); return m_powers[idx]; } + power const * get_powers() const { return m_powers; } + var x(unsigned idx) const { return get_power(idx).x(); } + unsigned degree(unsigned idx) const { return get_power(idx).degree(); } + void display(std::ostream & out, display_var_proc const & proc = display_var_proc(), bool use_star = false) const; + }; + + class polynomial : public definition { + friend class context_t; + unsigned m_size; + numeral m_c; + numeral * m_as; + var * m_xs; + static unsigned get_obj_size(unsigned sz) { return sizeof(polynomial) + sz*sizeof(numeral) + sz*sizeof(var); } + public: + polynomial():definition(constraint::POLYNOMIAL) {} + unsigned size() const { return m_size; } + numeral const & a(unsigned i) const { return m_as[i]; } + var x(unsigned i) const { return m_xs[i]; } + var const * xs() const { return m_xs; } + numeral const * as() const { return m_as; } + numeral const & c() const { return m_c; } + void display(std::ostream & out, numeral_manager & nm, display_var_proc const & proc = display_var_proc(), bool use_star = false) const; + }; + + /** + \brief Watched element (aka occurence) can be: + + - A clause + - A definition (i.e., a variable) + + Remark: we cannot use the two watched literal approach since we process multiple nodes. + */ + class watched { + public: + enum kind { CLAUSE=0, DEFINITION }; + private: + void * m_data; + public: + watched():m_data(0) {} + explicit watched(var x) { m_data = BOXTAGINT(void*, x, DEFINITION); } + explicit watched(clause * c) { m_data = TAG(void*, c, CLAUSE); } + kind get_kind() const { return static_cast(GET_TAG(m_data)); } + bool is_clause() const { return get_kind() != DEFINITION; } + bool is_definition() const { return get_kind() == DEFINITION; } + clause * get_clause() const { SASSERT(is_clause()); return UNTAG(clause*, m_data); } + var get_var() const { SASSERT(is_definition()); return UNBOXINT(m_data); } + bool operator==(watched const & other) const { return m_data == other.m_data; } + bool operator!=(watched const & other) const { return !operator==(other); } + }; + + /** + \brief Abstract functor for selecting the next leaf node to be explored. + */ + class node_selector { + context_t * m_ctx; + public: + node_selector(context_t * ctx):m_ctx(ctx) {} + virtual ~node_selector() {} + + context_t * ctx() const { return m_ctx; } + + // Return the next leaf node to be processed. + // Front and back are the first and last nodes in the doubly linked list of + // leaf nodes. + // Remark: new nodes are always inserted in the front of the list. + virtual node * operator()(node * front, node * back) = 0; + }; + + /** + \brief Abstract functor for selecting the next variable to branch. + */ + class var_selector { + context_t * m_ctx; + public: + var_selector(context_t * ctx):m_ctx(ctx) {} + virtual ~var_selector() {} + + context_t * ctx() const { return m_ctx; } + + // Return the next variable to branch. + virtual var operator()(node * n) = 0; + + // ----------------------------------- + // + // Event handlers + // + // ----------------------------------- + + // Invoked when a new variable is created. + virtual void new_var_eh(var x) {} + // Invoked when node n is created + virtual void new_node_eh(node * n) {} + // Invoked before deleting node n. + virtual void del_node_eh(node * n) {} + // Invoked when variable x is used during conflict resolution. + virtual void used_var_eh(node * n, var x) {} + }; + + class node_splitter; + friend class node_splitter; + + /** + \brief Abstract functor for creating children for node n by branching on a given variable. + */ + class node_splitter { + context_t * m_ctx; + public: + node_splitter(context_t * ctx):m_ctx(ctx) {} + virtual ~node_splitter() {} + + context_t * ctx() const { return m_ctx; } + node * mk_node(node * p) { return ctx()->mk_node(p); } + bound * mk_decided_bound(var x, numeral const & val, bool lower, bool open, node * n) { + return ctx()->mk_bound(x, val, lower, open, n, justification()); + } + + /** + \brief Create children nodes for n by splitting on x. + + \pre n is a leaf. The interval for x in n has more than one element. + */ + virtual void operator()(node * n, var x) = 0; + }; + + /** + \brief Return most recent splitting var for node n. + */ + var splitting_var(node * n) const; + + /** + \brief Return true if x is a definition. + */ + bool is_definition(var x) const { return m_defs[x] != 0; } + + typedef svector watch_list; + typedef _scoped_numeral_vector scoped_numeral_vector; + +private: + C m_c; + bool m_arith_failed; //!< True if the arithmetic module produced an exception. + bool m_own_allocator; + small_object_allocator * m_allocator; + bound_array_manager m_bm; + interval_manager m_im; + scoped_numeral_vector m_num_buffer; + + svector m_is_int; + ptr_vector m_defs; + vector m_wlist; + + ptr_vector m_unit_clauses; + ptr_vector m_clauses; + ptr_vector m_lemmas; + + id_gen m_node_id_gen; + + uint64 m_timestamp; + node * m_root; + // m_leaf_head is the head of a doubly linked list of leaf nodes to be processed. + node * m_leaf_head; + node * m_leaf_tail; + + var m_conflict; + ptr_vector m_queue; + unsigned m_qhead; + + display_var_proc m_default_display_proc; + display_var_proc * m_display_proc; + + scoped_ptr m_node_selector; + scoped_ptr m_var_selector; + scoped_ptr m_node_splitter; + + svector m_pws; + + // Configuration + numeral m_epsilon; //!< If upper - lower < epsilon, then new bound is not propagated. + bool m_zero_epsilon; + numeral m_max_bound; //!< Bounds > m_max and < -m_max are not propagated + numeral m_minus_max_bound; //!< -m_max_bound + numeral m_nth_root_prec; //!< precision for computing the nth root + unsigned m_max_depth; //!< Maximum depth + unsigned m_max_nodes; //!< Maximum number of nodes in the tree + unsigned long long m_max_memory; // in bytes + + // Counters + unsigned m_num_nodes; + + // Statistics + unsigned m_num_conflicts; + unsigned m_num_mk_bounds; + unsigned m_num_splits; + unsigned m_num_visited; + + // Temporary + numeral m_tmp1, m_tmp2, m_tmp3; + interval m_i_tmp1, m_i_tmp2, m_i_tmp3; + + // Cancel flag + volatile bool m_cancel; + + friend class node; + + void set_arith_failed() { m_arith_failed = true; } + + void checkpoint(); + + bound_array_manager & bm() { return m_bm; } + interval_manager & im() { return m_im; } + small_object_allocator & allocator() const { return *m_allocator; } + + bound * mk_bound(var x, numeral const & val, bool lower, bool open, node * n, justification jst); + void del_bound(bound * b); + // Create a new bound and add it to the propagation queue. + void propagate_bound(var x, numeral const & val, bool lower, bool open, node * n, justification jst); + + bool is_int(monomial const * m) const; + bool is_int(polynomial const * p) const; + + bool is_monomial(var x) const { return m_defs[x] != 0 && m_defs[x]->get_kind() == constraint::MONOMIAL; } + monomial * get_monomial(var x) const { SASSERT(is_monomial(x)); return static_cast(m_defs[x]); } + bool is_polynomial(var x) const { return m_defs[x] != 0 && m_defs[x]->get_kind() == constraint::POLYNOMIAL; } + polynomial * get_polynomial(var x) const { SASSERT(is_polynomial(x)); return static_cast(m_defs[x]); } + static void display(std::ostream & out, numeral_manager & nm, display_var_proc const & proc, var x, numeral & k, bool lower, bool open); + void display(std::ostream & out, var x) const; + void display_definition(std::ostream & out, definition const * d, bool use_star = false) const; + void display(std::ostream & out, constraint * a, bool use_star = false) const; + void display(std::ostream & out, bound * b) const; + void display(std::ostream & out, ineq * a) const; + void display_params(std::ostream & out) const; + void add_unit_clause(ineq * a, bool axiom); + // Remark: Not all lemmas need to be watched. Some of them can be used to justify clauses only. + void add_clause_core(unsigned sz, ineq * const * atoms, bool lemma, bool watched); + void del_clause(clause * cls); + + node * mk_node(node * parent = 0); + void del_node(node * n); + void del_nodes(); + + void del(interval & a); + void del_clauses(ptr_vector & cs); + void del_unit_clauses(); + void del_clauses(); + void del_monomial(monomial * m); + void del_sum(polynomial * p); + void del_definitions(); + + /** + \brief Insert n in the beginning of the doubly linked list of leaves. + + \pre n is a leaf, and it is not already in the list. + */ + void push_front(node * n); + + /** + \brief Insert n in the end of the doubly linked list of leaves. + + \pre n is a leaf, and it is not already in the list. + */ + void push_back(node * n); + + /** + \brief Remove n from the doubly linked list of leaves. + + \pre n is a leaf, and it is in the list. + */ + void remove_from_leaf_dlist(node * n); + + /** + \brief Remove all nodes from the leaf dlist. + */ + void reset_leaf_dlist(); + + /** + \brief Add all leaves back to the leaf dlist. + */ + void rebuild_leaf_dlist(node * n); + + // ----------------------------------- + // + // Propagation + // + // ----------------------------------- + + /** + \brief Return true if the given node is in an inconsistent state. + */ + bool inconsistent(node * n) const { return n->inconsistent(); } + + /** + \brief Set a conflict produced by the bounds of x at the given node. + */ + void set_conflict(var x, node * n); + + /** + \brief Return true if bound b may propagate a new bound using constraint c at node n. + */ + bool may_propagate(bound * b, constraint * c, node * n); + + /** + \brief Normalize bound if x is integer. + + Examples: + x < 2 --> x <= 1 + x <= 2.3 --> x <= 2 + */ + void normalize_bound(var x, numeral & val, bool lower, bool & open); + + /** + \brief Return true if (x, k, lower, open) is a relevant new bound at node n. + That is, it improves the current bound, and satisfies m_epsilon and m_max_bound. + */ + bool relevant_new_bound(var x, numeral const & k, bool lower, bool open, node * n); + + /** + \brief Return true if the lower and upper bounds of x are 0 at node n. + */ + bool is_zero(var x, node * n) const; + + /** + \brief Return true if upper bound of x is 0 at node n. + */ + bool is_upper_zero(var x, node * n) const; + + /** + \brief Return true if lower and upper bounds of x are conflicting at node n. That is, upper(x) < lower(x) + */ + bool conflicting_bounds(var x, node * n) const; + + /** + \brief Return true if x is unbounded at node n. + */ + bool is_unbounded(var x, node * n) const { return n->is_unbounded(x); } + + /** + \brief Return true if b is the most recent lower/upper bound for variable b->x() at node n. + */ + bool most_recent(bound * b, node * n) const; + + /** + \brief Add most recent bounds of node n into the propagation queue. + That is, all bounds b s.t. b is in the trail of n, but not in the tail of parent(n), and most_recent(b, n). + */ + void add_recent_bounds(node * n); + + /** + \brief Propagate new bounds at node n using get_monomial(x) + \pre is_monomial(x) + */ + void propagate_monomial(var x, node * n); + void propagate_monomial_upward(var x, node * n); + void propagate_monomial_downward(var x, node * n, unsigned i); + + /** + \brief Propagate new bounds at node n using get_polynomial(x) + \pre is_polynomial(x) + */ + void propagate_polynomial(var x, node * n); + // Propagate a new bound for y using the polynomial associated with x. x may be equal to y. + void propagate_polynomial(var x, node * n, var y); + + /** + \brief Propagate new bounds at node n using clause c. + */ + void propagate_clause(clause * c, node * n); + + /** + \brief Return the truth value of inequaliy t at node n. + */ + lbool value(ineq * t, node * n); + + /** + \brief Propagate new bounds at node n using the definition of variable x. + \pre is_definition(x) + */ + void propagate_def(var x, node * n); + + /** + \brief Propagate constraints in b->x()'s watch list. + */ + void propagate(node * n, bound * b); + + /** + \brief Perform bound propagation at node n. + */ + void propagate(node * n); + + /** + \brief Try to propagate at node n using all definitions. + */ + void propagate_all_definitions(node * n); + + // ----------------------------------- + // + // Main + // + // ----------------------------------- + void init(); + + /** + \brief Assert unit clauses in the node n. + */ + void assert_units(node * n); + + // ----------------------------------- + // + // Debugging support + // + // ----------------------------------- + + /** + \brief Return true if b is a bound for node n. + */ + bool is_bound_of(bound * b, node * n) const; + + /** + \brief Check the consistency of the doubly linked list of leaves. + */ + bool check_leaf_dlist() const; + + /** + \brief Check paving tree structure. + */ + bool check_tree() const; + + /** + \brief Check main invariants. + */ + bool check_invariant() const; + +public: + context_t(C const & c, params_ref const & p, small_object_allocator * a); + ~context_t(); + + /** + \brief Return true if the arithmetic module failed. + */ + bool arith_failed() const { return m_arith_failed; } + + numeral_manager & nm() const { return m_c.m(); } + + unsigned num_vars() const { return m_is_int.size(); } + + bool is_int(var x) const { SASSERT(x < num_vars()); return m_is_int[x]; } + + /** + \brief Create a new variable. + */ + var mk_var(bool is_int); + + /** + \brief Create the monomial xs[0]^ks[0] * ... * xs[sz-1]^ks[sz-1]. + The result is a variable y s.t. y = xs[0]^ks[0] * ... * xs[sz-1]^ks[sz-1]. + + \pre for all i \in [0, sz-1] : ks[i] > 0 + \pre sz > 0 + */ + var mk_monomial(unsigned sz, power const * pws); + + /** + \brief Create the sum c + as[0]*xs[0] + ... + as[sz-1]*xs[sz-1]. + The result is a variable y s.t. y = c + as[0]*xs[0] + ... + as[sz-1]*xs[sz-1]. + + \pre sz > 0 + \pre for all i \in [0, sz-1] : as[i] != 0 + */ + var mk_sum(numeral const & c, unsigned sz, numeral const * as, var const * xs); + + /** + \brief Create an inequality. + */ + ineq * mk_ineq(var x, numeral const & k, bool lower, bool open); + void inc_ref(ineq * a); + void dec_ref(ineq * a); + + /** + \brief Assert the clause atoms[0] \/ ... \/ atoms[sz-1] + \pre sz > 1 + */ + void add_clause(unsigned sz, ineq * const * atoms) { add_clause_core(sz, atoms, false, true); } + + /** + \brief Assert a constraint of one of the forms: x < k, x > k, x <= k, x >= k. + + If axiom == true, then the constraint is not tracked in proofs. + */ + void add_ineq(var x, numeral const & k, bool lower, bool open, bool axiom); + + /** + \brief Store in the given vector all leaves of the paving tree. + */ + void collect_leaves(ptr_vector & leaves) const; + + /** + \brief Display constraints asserted in the subpaving. + */ + void display_constraints(std::ostream & out, bool use_star = false) const; + + /** + \brief Display bounds for each leaf of the tree. + */ + void display_bounds(std::ostream & out) const; + + void display_bounds(std::ostream & out, node * n) const; + + void set_display_proc(display_var_proc * p) { m_display_proc = p; } + + void set_cancel(bool f) { m_cancel = f; im().set_cancel(f); } + + void updt_params(params_ref const & p); + + static void collect_param_descrs(param_descrs & d); + + void reset_statistics(); + + void collect_statistics(statistics & st) const; + + void operator()(); +}; + +}; + +#endif diff --git a/lib/subpaving_t_def.h b/lib/subpaving_t_def.h new file mode 100644 index 000000000..242fe088d --- /dev/null +++ b/lib/subpaving_t_def.h @@ -0,0 +1,1942 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + subpaving_t_def.h + +Abstract: + + Subpaving template for non-linear arithmetic. + +Author: + + Leonardo de Moura (leonardo) 2012-07-31. + +Revision History: + +--*/ +#include"subpaving_t.h" +#include"interval_def.h" +#include"buffer.h" +#include"cooperate.h" +#include"tactic_exception.h" + +namespace subpaving { + +/** + \brief Node selector for breadth-first search. +*/ +template +class breadth_first_node_selector : public context_t::node_selector { + typedef typename context_t::node node; +public: + breadth_first_node_selector(context_t * ctx): + context_t::node_selector(ctx) { + } + + virtual node * operator()(node * front, node * back) { + return back; + } +}; + +/** + \brief Node selector for depth-first search. +*/ +template +class depth_first_node_selector : public context_t::node_selector { + typedef typename context_t::node node; +public: + depth_first_node_selector(context_t * ctx): + context_t::node_selector(ctx) { + } + + virtual node * operator()(node * front, node * back) { + return front; + } +}; + +/** + Round robing variable selector. + If only_non_def is true, then variable definitions (aka auxiliary variables) are ignored. +*/ +template +class round_robing_var_selector : public context_t::var_selector { + typedef typename context_t::bound bound; + + bool m_only_non_def; + + void next(var & x) const { + x++; + if (x >= this->ctx()->num_vars()) + x = 0; + } + +public: + round_robing_var_selector(context_t * ctx, bool only_non_def = true): + context_t::var_selector(ctx), + m_only_non_def(only_non_def) { + } + + // Return the next variable to branch. + virtual var operator()(typename context_t::node * n) { + typename context_t::numeral_manager & nm = this->ctx()->nm(); + SASSERT(this->ctx()->num_vars() > 0); + var x = this->ctx()->splitting_var(n); + if (x == null_var) + x = 0; + else + next(x); + var start = x; + do { + if (!m_only_non_def || !this->ctx()->is_definition(x)) { + bound * lower = n->lower(x); + bound * upper = n->upper(x); + if (lower == 0 || upper == 0 || !nm.eq(lower->value(), upper->value())) { + return x; + } + } + next(x); + } + while (x != start); + return null_var; + } +}; + +/** + Selector that uses the following strategy: + + - If only_non_def is true, then variable definitions (aka auxiliary variables) are ignored. + + - All variables x s.t. lower(x) == upper(x) are ignored. + + - If node contains an unbounded variable (-oo, oo), then return the smallest unbounded variable. + + - Otherwise, select the smallest variable x with the largest width, where + width is defined as: + If x has lower and upper bound, then width = upper(x) - lower(x). + If x has only lower, width = penalty/max(|lower(x)|, 1) + If x has only upper, width = penalty/max(|upper(x)|, 1) + penaly is a parameter of this class. + + This strategy guarantees fairness. +*/ +template +class largest_interval_var_selector : public context_t::var_selector { + unsigned m_penalty; + bool m_only_non_def; + +public: + largest_interval_var_selector(context_t * ctx, unsigned unbounded_penalty = 10, bool only_non_def = true): + context_t::var_selector(ctx), + m_penalty(unbounded_penalty), + m_only_non_def(only_non_def) { + } + + // Return the next variable to branch. + virtual var operator()(typename context_t::node * n) { + var y = null_var; + typename context_t::numeral_manager & nm = this->ctx()->nm(); + _scoped_numeral::numeral_manager> largest(nm), width(nm), penalty(nm), one(nm); + nm.set(penalty, m_penalty); + nm.set(one, 1); + unsigned num = this->ctx()->num_vars(); + for (var x = 0; x < num; x++) { + if (m_only_non_def && this->ctx()->is_definition(x)) + continue; + typename context_t::bound * l = n->lower(x); + typename context_t::bound * u = n->upper(x); + // variables without lower and upper bounds are selected immediately. + if (l == 0 && u == 0) + return x; + if (l != 0 && u != 0) { + // ignore variables s.t. lower(x) == upper(x) + if (nm.eq(l->value(), u->value())) + continue; + // if x has lower and upper bounds, set width to upper(x) - lower(x) + C::round_to_plus_inf(nm); + nm.sub(u->value(), l->value(), width); + } + else { + // if x does not have lower or upper, then + // set width to penalty/|value| where value is the existing bound. + if (l != 0) + nm.set(width, l->value()); + else + nm.set(width, u->value()); + C::round_to_plus_inf(nm); + if (nm.is_neg(width)) + nm.neg(width); + if (nm.lt(width, one)) + nm.set(width, one); + nm.mul(penalty, width, width); + } + if (y == null_var || nm.gt(width, largest)) { + y = x; + nm.set(largest, width); + } + } + return y; + } +}; + +template +class midpoint_node_splitter : public context_t::node_splitter { + typedef typename context_t::numeral_manager numeral_manager; + typedef typename numeral_manager::numeral numeral; + typedef typename context_t::node node; + typedef typename context_t::bound bound; + bool m_left_open; + unsigned m_delta; +public: + midpoint_node_splitter(context_t * ctx, bool left_open = true, unsigned delta = 1): + context_t::node_splitter(ctx), + m_left_open(left_open), + m_delta(delta) { + SASSERT(m_delta < INT_MAX); + } + + virtual void operator()(node * n, var x) { + SASSERT(!n->inconsistent()); + numeral_manager & nm = this->ctx()->nm(); + node * left = this->mk_node(n); + node * right = this->mk_node(n); + bound * lower = n->lower(x); + bound * upper = n->upper(x); + _scoped_numeral mid(nm); + if (lower == 0 && upper == 0) { + nm.set(mid, 0); + // mid == 0 + } + else if (lower == 0) { + _scoped_numeral delta(nm); + SASSERT(upper != 0); + nm.set(delta, static_cast(m_delta)); + nm.set(mid, upper->value()); + C::round_to_minus_inf(nm); + nm.sub(mid, delta, mid); + // mid == upper - delta + } + else if (upper == 0) { + _scoped_numeral delta(nm); + SASSERT(lower != 0); + nm.set(delta, static_cast(m_delta)); + nm.set(mid, lower->value()); + C::round_to_plus_inf(nm); + nm.add(mid, delta, mid); + // mid == lower + delta + } + else { + _scoped_numeral two(nm); + SASSERT(!nm.eq(lower->value(), upper->value())); + nm.set(two, 2); + nm.add(lower->value(), upper->value(), mid); + nm.div(mid, two, mid); + if (!(nm.lt(lower->value(), mid) && nm.lt(mid, upper->value()))) + throw subpaving::exception(); + // mid == (lower + upper)/2 + } + + this->mk_decided_bound(x, mid, false, m_left_open, left); + this->mk_decided_bound(x, mid, true, !m_left_open, right); + TRACE("subpaving_int_split", + tout << "LEFT:\n"; this->ctx()->display_bounds(tout, left); + tout << "\nRIGHT:\n"; this->ctx()->display_bounds(tout, right);); + } +}; + + +/** + \brief Auxiliary static method used to diplay a bound specified by (x, k, lower, open). +*/ +template +void context_t::display(std::ostream & out, numeral_manager & nm, display_var_proc const & proc, var x, numeral & k, bool lower, bool open) { + if (lower) { + out << nm.to_rational_string(k) << " <"; + if (!open) + out << "="; + out << " "; + proc(out, x); + } + else { + proc(out, x); + out << " <"; + if (!open) + out << "="; + out << " " << nm.to_rational_string(k); + } +} + +template +void context_t::ineq::display(std::ostream & out, numeral_manager & nm, display_var_proc const & proc) { + context_t::display(out, nm, proc, m_x, m_val, is_lower(), is_open()); +} + +template +void context_t::bound::display(std::ostream & out, numeral_manager & nm, display_var_proc const & proc) { + context_t::display(out, nm, proc, m_x, m_val, is_lower(), is_open()); +} + +template +void context_t::clause::display(std::ostream & out, numeral_manager & nm, display_var_proc const & proc) { + for (unsigned i = 0; i < size(); i++) { + if (i > 0) + out << " or "; + m_atoms[i]->display(out, nm, proc); + } +} + +template +context_t::node::node(context_t & s, unsigned id): + m_bm(s.bm()) { + m_id = id; + m_depth = 0; + unsigned num_vars = s.num_vars(); + m_conflict = null_var; + m_trail = 0; + m_parent = 0; + m_first_child = 0; + m_next_sibling = 0; + m_prev = 0; + m_next = 0; + bm().mk(m_lowers); + bm().mk(m_uppers); + for (unsigned i = 0; i < num_vars; i++) { + bm().push_back(m_lowers, 0); + bm().push_back(m_uppers, 0); + } +} + +template +context_t::node::node(node * parent, unsigned id): + m_bm(parent->m_bm) { + m_id = id; + m_depth = parent->depth() + 1; + bm().copy(parent->m_lowers, m_lowers); + bm().copy(parent->m_uppers, m_uppers); + m_conflict = parent->m_conflict; + m_trail = parent->m_trail; + m_parent = parent; + m_first_child = 0; + m_next_sibling = parent->m_first_child; + m_prev = 0; + m_next = 0; + parent->m_first_child = this; +} + +/** + \brief Add a new bound b at this node. +*/ +template +void context_t::node::push(bound * b) { + SASSERT(b->prev() == m_trail); + m_trail = b; + if (b->is_lower()) { + bm().set(m_lowers, b->x(), b); + SASSERT(lower(b->x()) == b); + } + else { + bm().set(m_uppers, b->x(), b); + SASSERT(upper(b->x()) == b); + } +} + +/** + \brief Return the most recent variable that was used for splitting on node n. +*/ +template +var context_t::splitting_var(node * n) const { + if (n == m_root) + return null_var; + bound * b = n->trail_stack(); + while (b != 0) { + if (b->jst().is_axiom()) + return b->x(); + b = b->prev(); + } + UNREACHABLE(); + return null_var; +} + +template +context_t::monomial::monomial(unsigned sz, power const * pws): + definition(constraint::MONOMIAL), + m_size(sz) { + memcpy(m_powers, pws, sz*sizeof(power)); + std::sort(m_powers, m_powers+sz, typename power::lt_proc()); + DEBUG_CODE({ + for (unsigned i = 0; i < sz; i ++) { + SASSERT(i == 0 || x(i) > x(i-1)); + SASSERT(degree(i) > 0); + }}); +} + +template +void context_t::monomial::display(std::ostream & out, display_var_proc const & proc, bool use_star) const { + SASSERT(m_size > 0); + for (unsigned i = 0; i < m_size; i++) { + if (i > 0) { + if (use_star) + out << "*"; + else + out << " "; + } + proc(out, x(i)); + if (degree(i) > 1) + out << "^" << degree(i); + } +} + +template +void context_t::polynomial::display(std::ostream & out, numeral_manager & nm, display_var_proc const & proc, bool use_star) const { + bool first = true; + if (!nm.is_zero(m_c)) { + out << nm.to_rational_string(m_c); + first = false; + } + + for (unsigned i = 0; i < m_size; i++) { + if (first) + first = false; + else + out << " + "; + if (!nm.is_one(a(i))) { + out << nm.to_rational_string(a(i)); + if (use_star) + out << "*"; + else + out << " "; + } + proc(out, x(i)); + } +} + +template +context_t::context_t(C const & c, params_ref const & p, small_object_allocator * a): + m_c(c), + m_own_allocator(a == 0), + m_allocator(a == 0 ? alloc(small_object_allocator, "subpaving") : a), + m_bm(*this, *m_allocator), + m_im(interval_config(m_c.m())), + m_num_buffer(nm()) { + m_arith_failed = false; + m_timestamp = 0; + m_root = 0; + m_leaf_head = 0; + m_leaf_tail = 0; + m_conflict = null_var; + m_qhead = 0; + m_display_proc = &m_default_display_proc; + m_node_selector = alloc(breadth_first_node_selector, this); + m_var_selector = alloc(round_robing_var_selector, this); + m_node_splitter = alloc(midpoint_node_splitter, this); + m_cancel = false; + m_num_nodes = 0; + updt_params(p); + reset_statistics(); +} + +template +context_t::~context_t() { + nm().del(m_epsilon); + nm().del(m_max_bound); + nm().del(m_minus_max_bound); + nm().del(m_nth_root_prec); + nm().del(m_tmp1); + nm().del(m_tmp2); + nm().del(m_tmp3); + del(m_i_tmp1); + del(m_i_tmp2); + del(m_i_tmp3); + del_nodes(); + del_unit_clauses(); + del_clauses(); + del_definitions(); + if (m_own_allocator) + dealloc(m_allocator); +} + +template +void context_t::checkpoint() { + if (m_cancel) + throw default_exception("canceled"); + if (memory::get_allocation_size() > m_max_memory) + throw default_exception(TACTIC_MAX_MEMORY_MSG); + cooperate("subpaving"); +} + +template +void context_t::del(interval & a) { + nm().del(a.m_l_val); + nm().del(a.m_u_val); +} + +template +void context_t::updt_params(params_ref const & p) { + unsigned epsilon = p.get_uint(":epsilon", 20); + if (epsilon != 0) { + nm().set(m_epsilon, static_cast(epsilon)); + nm().inv(m_epsilon); + m_zero_epsilon = false; + } + else { + nm().reset(m_epsilon); + m_zero_epsilon = true; + } + + unsigned max_power = p.get_uint(":max-bound", 10); + nm().set(m_max_bound, 10); + nm().power(m_max_bound, max_power, m_max_bound); + nm().set(m_minus_max_bound, m_max_bound); + nm().neg(m_minus_max_bound); + + m_max_depth = p.get_uint(":max-depth", 128); + m_max_nodes = p.get_uint(":max-nodes", 8192); + + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + + unsigned prec = p.get_uint(":nth-root-precision", 8192); + if (prec == 0) + prec = 1; + nm().set(m_nth_root_prec, static_cast(prec)); + nm().inv(m_nth_root_prec); +} + +template +void context_t::collect_param_descrs(param_descrs & d) { + d.insert(":max-nodes", CPK_UINT, "(default: 8192) maximum number of nodes in the subpaving tree."); + d.insert(":max-depth", CPK_UINT, "(default: 128) maximum depth of the subpaving tree."); + d.insert(":epsilon", CPK_UINT, "(default: 20) value k s.t. a new lower (upper) bound for x is propagated only new-lower(x) > lower(k) + 1/k * max(min(upper(x) - lower(x), |lower|), 1) (new-upper(x) < upper(x) - 1/k * max(min(upper(x) - lower(x), |lower|), 1)). If k = 0, then this restriction is ignored."); + d.insert(":max-bound", CPK_UINT, "(default 10) value k s.t. a new upper (lower) bound for x is propagated only if upper(x) > -10^k or lower(x) = -oo (lower(x) < 10^k or upper(x) = oo)"); + d.insert(":nth-root-precision", CPK_UINT, "(default 8192) value k s.t. 1/k is the precision for computing the nth root in the subpaving module."); +} + +template +void context_t::display_params(std::ostream & out) const { + out << ":max-nodes " << m_max_nodes << "\n"; + out << ":max-depth " << m_max_depth << "\n"; + out << ":epsilon " << nm().to_rational_string(m_epsilon) << "\n"; + out << ":max-bound " << nm().to_rational_string(m_max_bound) << "\n"; + out << ":max-memory " << m_max_memory << "\n"; +} + +template +typename context_t::bound * context_t::mk_bound(var x, numeral const & val, bool lower, bool open, node * n, justification jst) { + SASSERT(!inconsistent(n)); + m_num_mk_bounds++; + void * mem = allocator().allocate(sizeof(bound)); + bound * r = new (mem) bound(); + r->m_x = x; + if (is_int(x)) { + // adjust integer bound + if (!nm().is_int(val)) + open = false; // performing ceil/floor + if (lower) { + nm().ceil(val, r->m_val); + } + else { + nm().floor(val, r->m_val); + } + if (open) { + open = false; + if (lower) { + C::round_to_minus_inf(nm()); + nm().inc(r->m_val); + } + else { + C::round_to_plus_inf(nm()); + nm().dec(r->m_val); + } + } + } + else { + nm().set(r->m_val, val); + } + r->m_lower = lower; + r->m_open = open; + r->m_mark = false; + r->m_timestamp = m_timestamp; + r->m_prev = n->trail_stack(); + r->m_jst = jst; + n->push(r); + TRACE("subpaving_mk_bound", tout << "mk_bound: "; display(tout, r); tout << "\ntimestamp: " << r->m_timestamp << "\n";); + if (conflicting_bounds(x, n)) { + TRACE("subpaving_mk_bound", tout << "conflict\n"; display_bounds(tout, n);); + set_conflict(x, n); + } + m_timestamp++; + if (m_timestamp == UINT64_MAX) + throw subpaving::exception(); // subpaving failed. + return r; +} + +template +void context_t::propagate_bound(var x, numeral const & val, bool lower, bool open, node * n, justification jst) { + bound * b = mk_bound(x, val, lower, open, n, jst); + m_queue.push_back(b); + SASSERT(!lower || n->lower(x) == b); + SASSERT(lower || n->upper(x) == b); + SASSERT(is_int(x) || !lower || nm().eq(n->lower(x)->value(), val)); + SASSERT(is_int(x) || lower || nm().eq(n->upper(x)->value(), val)); + SASSERT(open || !nm().is_int(val) || !lower || nm().eq(n->lower(x)->value(), val)); + SASSERT(open || !nm().is_int(val) || lower || nm().eq(n->upper(x)->value(), val)); + SASSERT(!lower || nm().ge(n->lower(x)->value(), val)); + SASSERT(lower || nm().le(n->upper(x)->value(), val)); +} + +template +void context_t::del_bound(bound * b) { + nm().del(b->m_val); + b->~bound(); + allocator().deallocate(sizeof(bound), b); +} + +template +void context_t::display(std::ostream & out, var x) const { + if (x == null_var) + out << "[null]"; + else + (*m_display_proc)(out, x); +} + +template +void context_t::display(std::ostream & out, bound * b) const { + b->display(out, nm(), *m_display_proc); +} + +template +void context_t::display(std::ostream & out, ineq * a) const { + a->display(out, nm(), *m_display_proc); +} + +template +void context_t::display_definition(std::ostream & out, definition const * d, bool use_star) const { + switch (d->get_kind()) { + case constraint::MONOMIAL: + static_cast(d)->display(out, *m_display_proc, use_star); + break; + case constraint::POLYNOMIAL: + static_cast(d)->display(out, nm(), *m_display_proc, use_star); + break; + default: + UNREACHABLE(); + }; +} + +template +void context_t::display(std::ostream & out, constraint * c, bool use_star) const { + if (c->get_kind() == constraint::CLAUSE) + static_cast(c)->display(out, nm(), *m_display_proc); + else + display_definition(out, static_cast(c), use_star); +} + +template +void context_t::display_bounds(std::ostream & out, node * n) const { + unsigned num = num_vars(); + for (unsigned x = 0; x < num; x++) { + bound * l = n->lower(x); + bound * u = n->upper(x); + if (l != 0) { + display(out, l); + out << " "; + } + if (u != 0) { + display(out, u); + } + if (l != 0 || u != 0) + out << "\n"; + } +} + +/** + \brief Return true if all variables in m are integer. +*/ +template +bool context_t::is_int(monomial const * m) const { + for (unsigned i = 0; i < m->size(); i++) { + if (is_int(m->x(i))) + return true; + } + return false; +} + +/** + \brief Return true if all variables in p are integer, and all coefficients in p are integer. +*/ +template +bool context_t::is_int(polynomial const * p) const { + for (unsigned i = 0; i < p->size(); i++) { + if (!is_int(p->x(i)) || !nm().is_int(p->a(i))) { + TRACE("subpaving_is_int", tout << "polynomial is not integer due to monomial at i: " << i << "\n"; tout.flush(); + display(tout, p->x(i)); tout << " "; nm().display(tout, p->a(i)); tout << "\n";); + return false; + } + } + return nm().is_int(p->c()); +} + +template +var context_t::mk_var(bool is_int) { + var r = static_cast(m_is_int.size()); + m_is_int.push_back(is_int); + m_defs.push_back(0); + m_wlist.push_back(watch_list()); + m_var_selector->new_var_eh(r); + return r; +} + +template +void context_t::del_monomial(monomial * m) { + unsigned mem_sz = monomial::get_obj_size(m->size()); + m->~monomial(); + allocator().deallocate(mem_sz, m); +} + +template +var context_t::mk_monomial(unsigned sz, power const * pws) { + SASSERT(sz > 0); + m_pws.reset(); + m_pws.append(sz, pws); + std::sort(m_pws.begin(), m_pws.end(), power::lt_proc()); + unsigned j = 0; + for (unsigned i = 1; i < sz; i++) { + if (m_pws[j].x() == m_pws[i].x()) { + m_pws[j].degree() += m_pws[i].degree(); + } + else { + j++; + SASSERT(j <= i); + m_pws[j] = m_pws[i]; + } + } + sz = j + 1; + pws = m_pws.c_ptr(); + unsigned mem_sz = monomial::get_obj_size(sz); + void * mem = allocator().allocate(mem_sz); + monomial * r = new (mem) monomial(sz, pws); + var new_var = mk_var(is_int(r)); + m_defs[new_var] = r; + for (unsigned i = 0; i < sz; i++) { + var x = pws[i].x(); + m_wlist[x].push_back(watched(new_var)); + } + return new_var; +} + +template +void context_t::del_sum(polynomial * p) { + unsigned sz = p->size(); + unsigned mem_sz = polynomial::get_obj_size(sz); + for (unsigned i = 0; i < sz; i++) { + nm().del(p->m_as[i]); + } + nm().del(p->m_c); + p->~polynomial(); + allocator().deallocate(mem_sz, p); +} + +template +var context_t::mk_sum(numeral const & c, unsigned sz, numeral const * as, var const * xs) { + m_num_buffer.reserve(num_vars(), numeral()); + for (unsigned i = 0; i < sz; i++) { + SASSERT(xs[i] < num_vars()); + nm().set(m_num_buffer[xs[i]], as[i]); + } + unsigned mem_sz = polynomial::get_obj_size(sz); + void * mem = allocator().allocate(mem_sz); + polynomial * p = new (mem) polynomial(); + p->m_size = sz; + nm().set(p->m_c, c); + p->m_as = reinterpret_cast(static_cast(mem) + sizeof(polynomial)); + p->m_xs = reinterpret_cast(reinterpret_cast(p->m_as) + sizeof(numeral)*sz); + memcpy(p->m_xs, xs, sizeof(var)*sz); + std::sort(p->m_xs, p->m_xs+sz); + for (unsigned i = 0; i < sz; i++) { + numeral * curr = p->m_as + i; + new (curr) numeral(); + var x = p->m_xs[i]; + nm().swap(m_num_buffer[x], *curr); + } + TRACE("subpaving_mk_sum", tout << "new variable is integer: " << is_int(p) << "\n";); + var new_var = mk_var(is_int(p)); + for (unsigned i = 0; i < sz; i++) { + var x = p->m_xs[i]; + m_wlist[x].push_back(watched(new_var)); + } + m_defs[new_var] = p; + return new_var; +} + +template +typename context_t::ineq * context_t::mk_ineq(var x, numeral const & k, bool lower, bool open) { + void * mem = allocator().allocate(sizeof(ineq)); + ineq * r = new (mem) ineq(); + r->m_ref_count = 0; + r->m_x = x; + nm().set(r->m_val, k); + r->m_lower = lower; + r->m_open = open; + return r; +} + +template +void context_t::inc_ref(ineq * a) { + TRACE("subpaving_ref_count", tout << "inc-ref: " << a << " " << a->m_ref_count << "\n";); + if (a) + a->m_ref_count++; +} + +template +void context_t::dec_ref(ineq * a) { + if (a) { + TRACE("subpaving_ref_count", + tout << "dec-ref: " << a << " " << a->m_ref_count << "\n"; + a->display(tout, nm()); + tout << "\n";); + SASSERT(a->m_ref_count > 0); + a->m_ref_count--; + if (a->m_ref_count == 0) { + nm().del(a->m_val); + a->~ineq(); + allocator().deallocate(sizeof(ineq), a); + } + } +} + +template +void context_t::add_clause_core(unsigned sz, ineq * const * atoms, bool lemma, bool watch) { + SASSERT(lemma || watch); + SASSERT(sz > 0); + if (sz == 1) { + add_unit_clause(atoms[0], true); + return; + } + + void * mem = allocator().allocate(clause::get_obj_size(sz)); + clause * c = new (mem) clause(); + c->m_size = sz; + for (unsigned i = 0; i < sz; i++) { + inc_ref(atoms[i]); + c->m_atoms[i] = atoms[i]; + } + std::stable_sort(c->m_atoms, c->m_atoms + sz, typename ineq::lt_var_proc()); + if (watch) { + for (unsigned i = 0; i < sz; i++) { + var x = c->m_atoms[i]->x(); + if (i == 0 || x != c->m_atoms[i-1]->x()) + m_wlist[x].push_back(watched(c)); + } + } + c->m_lemma = lemma; + c->m_num_jst = 0; + c->m_watched = watch; + if (!lemma) { + m_clauses.push_back(c); + } + else if (watch) { + m_lemmas.push_back(c); + } + TRACE("subpaving_clause", tout << "new clause:\n"; display(tout, c); tout << "\n";); +} + +template +void context_t::del_clause(clause * c) { + SASSERT(c->m_num_jst == 0); // We cannot delete a clause that is being used to justify some bound + bool watch = c->watched(); + var prev_x = null_var; + unsigned sz = c->size(); + for (unsigned i = 0; i < sz; i++) { + var x = c->m_atoms[i]->x(); + if (watch) { + if (x != prev_x) + m_wlist[x].erase(watched(c)); + prev_x = x; + } + dec_ref((*c)[i]); + } + unsigned mem_sz = clause::get_obj_size(sz); + c->~clause(); + allocator().deallocate(mem_sz, c); +} + +template +void context_t::add_unit_clause(ineq * a, bool axiom) { + TRACE("subpaving", a->display(tout, nm(), *m_display_proc); tout << "\n";); + inc_ref(a); + m_unit_clauses.push_back(TAG(ineq*, a, axiom)); +} + +template +void context_t::add_ineq(var x, numeral const & k, bool lower, bool open, bool axiom) { + ineq * unit = mk_ineq(x, k, lower, open); + add_unit_clause(unit, axiom); +} + +template +typename context_t::node * context_t::mk_node(node * parent) { + void * mem = allocator().allocate(sizeof(node)); + node * r; + if (parent == 0) + r = new (mem) node(*this, m_node_id_gen.mk()); + else + r = new (mem) node(parent, m_node_id_gen.mk()); + m_var_selector->new_node_eh(r); + + // Add node in the leaf dlist + push_front(r); + + m_num_nodes++; + return r; +} + +template +void context_t::del_node(node * n) { + SASSERT(n->first_child() == 0); + + SASSERT(m_num_nodes > 0); + m_num_nodes--; + + m_var_selector->del_node_eh(n); + + // recycle id + m_node_id_gen.recycle(n->id()); + + // disconnect n from list of leaves. + remove_from_leaf_dlist(n); + + // disconnect n from parent + node * p = n->parent(); + bound * b = n->trail_stack(); + bound * b_old; + if (p != 0) { + node * c = p->first_child(); + if (c == n) { + // n is the first child + p->set_first_child(n->next_sibling()); + } + else { + SASSERT(c->next_sibling() != 0); + while (c->next_sibling() != n) { + c = c->next_sibling(); + SASSERT(c->next_sibling() != 0); + } + SASSERT(c->next_sibling() == n); + c->set_next_sibling(n->next_sibling()); + } + b_old = p->trail_stack(); + } + else { + b_old = 0; + } + while (b != b_old) { + bound * old = b; + b = b->prev(); + del_bound(old); + } + bm().del(n->uppers()); + bm().del(n->lowers()); + n->~node(); + allocator().deallocate(sizeof(node), n); +} + +template +void context_t::del_nodes() { + ptr_buffer todo; + if (m_root == 0) + return; + todo.push_back(m_root); + while (!todo.empty()) { + node * n = todo.back(); + node * c = n->first_child(); + if (c == 0) { + del_node(n); + todo.pop_back(); + } + else { + while (c != 0) { + todo.push_back(c); + c = c->next_sibling(); + } + } + } +} + +template +void context_t::push_front(node * n) { + SASSERT(n->first_child() == 0); + SASSERT(n->next() == 0); + SASSERT(n->prev() == 0); + n->set_next(m_leaf_head); + if (m_leaf_head != 0) { + SASSERT(m_leaf_head->prev() == 0); + m_leaf_head->set_prev(n); + } + else { + SASSERT(m_leaf_head == 0); + m_leaf_tail = n; + } + m_leaf_head = n; +} + +template +void context_t::push_back(node * n) { + SASSERT(n->first_child() == 0); + SASSERT(n->next() == 0); + SASSERT(n->prev() == 0); + n->set_prev(m_leaf_tail); + if (m_leaf_tail != 0) { + SASSERT(m_leaf_tail->next() == 0); + m_leaf_tail->set_next(n); + } + else { + SASSERT(m_leaf_tail == 0); + m_leaf_head = n; + } + m_leaf_tail = n; +} + +template +void context_t::reset_leaf_dlist() { + // Remove all nodes from the lead doubly linked list + node * n = m_leaf_head; + while (n != 0) { + node * next = n->next(); + n->set_next(0); + n->set_prev(0); + n = next; + } + m_leaf_head = 0; + m_leaf_tail = 0; +} + +template +void context_t::rebuild_leaf_dlist(node * n) { + reset_leaf_dlist(); + // Reinsert all leaves in the leaf dlist. + ptr_buffer todo; + if (m_root != 0) + todo.push_back(m_root); + while (!todo.empty()) { + node * n = todo.back(); + todo.pop_back(); + node * c = n->first_child(); + if (c == 0) { + if (!n->inconsistent()) + push_front(n); + } + else { + while (c != 0) { + SASSERT(c->parent() == n); + todo.push_back(c); + c = c->next_sibling(); + } + } + } +} + +template +void context_t::remove_from_leaf_dlist(node * n) { + node * prev = n->prev(); + node * next = n->next(); + SASSERT(prev == 0 || prev != next); + SASSERT(next == 0 || prev != next); + SASSERT(prev != n); SASSERT(next != n); + if (prev != 0) { + SASSERT(m_leaf_head != n); + prev->set_next(next); + n->set_prev(0); + } + else if (m_leaf_head == n) { + m_leaf_head = next; + } + + if (next != 0) { + SASSERT(m_leaf_tail != n); + next->set_prev(prev); + n->set_next(0); + } + else if (m_leaf_tail == n) { + m_leaf_tail = prev; + } + SASSERT(n->prev() == 0 && n->next() == 0); +} + +template +void context_t::collect_leaves(ptr_vector & leaves) const { + // Copy all leaves to the given vector. + ptr_buffer todo; + if (m_root != 0) + todo.push_back(m_root); + while (!todo.empty()) { + node * n = todo.back(); + todo.pop_back(); + node * c = n->first_child(); + if (c == 0) { + if (!n->inconsistent()) + leaves.push_back(n); + } + else { + while (c != 0) { + SASSERT(c->parent() == n); + todo.push_back(c); + c = c->next_sibling(); + } + } + } +} + +template +void context_t::del_unit_clauses() { + unsigned sz = m_unit_clauses.size(); + for (unsigned i = 0; i < sz; i++) + dec_ref(UNTAG(ineq*, m_unit_clauses[i])); + m_unit_clauses.reset(); +} + +template +void context_t::del_clauses(ptr_vector & cs) { + unsigned sz = cs.size(); + for (unsigned i = 0; i < sz; i++) { + del_clause(cs[i]); + } + cs.reset(); +} + +template +void context_t::del_clauses() { + del_clauses(m_clauses); + del_clauses(m_lemmas); +} + +template +void context_t::del_definitions() { + unsigned sz = num_vars(); + for (unsigned i = 0; i < sz; i++) { + definition * d = m_defs[i]; + if (d == 0) + continue; + switch (d->get_kind()) { + case constraint::MONOMIAL: + del_monomial(static_cast(d)); + break; + case constraint::POLYNOMIAL: + del_sum(static_cast(d)); + break; + default: + UNREACHABLE(); + break; + } + } +} + +template +void context_t::display_constraints(std::ostream & out, bool use_star) const { + // display definitions + for (unsigned i = 0; i < num_vars(); i++) { + if (is_definition(i)) { + (*m_display_proc)(out, i); + out << " = "; + display_definition(out, m_defs[i], use_star); + out << "\n"; + } + } + // display units + for (unsigned i = 0; i < m_unit_clauses.size(); i++) { + ineq * a = UNTAG(ineq*, m_unit_clauses[i]); + a->display(out, nm(), *m_display_proc); out << "\n"; + } + // display clauses + for (unsigned i = 0; i < m_clauses.size(); i++) { + m_clauses[i]->display(out, nm(), *m_display_proc); out << "\n"; + } +} + +// ----------------------------------- +// +// Propagation +// +// ----------------------------------- + +template +void context_t::set_conflict(var x, node * n) { + m_num_conflicts++; + n->set_conflict(x); + remove_from_leaf_dlist(n); +} + +template +bool context_t::may_propagate(bound * b, constraint * c, node * n) { + SASSERT(b != 0 && c != 0); + TRACE("may_propagate_bug", display(tout, b); tout << " | "; display(tout, c); tout << "\nresult: " << (b->timestamp() > c->timestamp()) << ", " << b->timestamp() << ", " << c->timestamp() << "\n";); + return b->timestamp() >= c->timestamp(); +} + +// Normalization for integer bounds +template +void context_t::normalize_bound(var x, numeral & val, bool lower, bool & open) { + if (is_int(x)) { + // adjust integer bound + if (!nm().is_int(val)) + open = false; // performing ceil/floor + if (lower) { + nm().ceil(val, val); + } + else { + nm().floor(val, val); + } + if (open) { + open = false; + if (lower) { + C::round_to_minus_inf(nm()); + nm().inc(val); + } + else { + C::round_to_plus_inf(nm()); + nm().dec(val); + } + } + } +} + +template +bool context_t::relevant_new_bound(var x, numeral const & k, bool lower, bool open, node * n) { + try { + bound * curr_lower = n->lower(x); + bound * curr_upper = n->upper(x); + SASSERT(curr_lower == 0 || curr_lower->x() == x); + SASSERT(curr_upper == 0 || curr_upper->x() == x); + TRACE("subpaving_relevant_bound", + display(tout, x); tout << " " << (lower ? ">" : "<") << (open ? "" : "=") << " "; nm().display(tout, k); tout << "\n"; + tout << "existing bounds:\n"; + if (curr_lower) { display(tout, curr_lower); tout << "\n"; } + if (curr_upper) { display(tout, curr_upper); tout << "\n"; }); + if (lower) { + // If new bound triggers a conflict, then it is relevant. + if (curr_upper && (nm().gt(k, curr_upper->value()) || ((open || curr_upper->is_open()) && nm().eq(k, curr_upper->value())))) { + TRACE("subpaving_relevant_bound", tout << "relevant because triggers conflict.\n";); + return true; + } + // If m_epsilon is zero, then bound is relevant only if it improves existing bound. + if (m_zero_epsilon && curr_lower != 0 && (nm().lt(k, curr_lower->value()) || ((curr_lower->is_open() || !open) && nm().eq(k, curr_lower->value())))) { + // new lower bound does not improve existing bound + TRACE("subpaving_relevant_bound", tout << "irrelevant because does not improve existing bound.\n";); + return false; + } + if (curr_upper == 0 && nm().lt(m_max_bound, k)) { + // new lower bound exceeds the :max-bound threshold + TRACE("subpaving_relevant_bound", tout << "irrelevant because exceeds :max-bound threshold.\n";); + return false; + } + if (!m_zero_epsilon && curr_lower != 0) { + // check if: + // new-lower > lower + m_epsilon * max(min(upper - lower, |lower|), 1) + numeral & min = m_tmp1; + numeral & abs_lower = m_tmp2; + nm().set(abs_lower, curr_lower->value()); + nm().abs(abs_lower); + if (curr_upper != 0) { + nm().sub(curr_upper->value(), curr_lower->value(), min); + if (nm().lt(abs_lower, min)) + nm().set(min, abs_lower); + } + else { + nm().set(min, abs_lower); + } + numeral & delta = m_tmp3; + nm().set(delta, 1); + if (nm().gt(min, delta)) + nm().set(delta, min); + nm().mul(delta, m_epsilon, delta); + nm().add(curr_lower->value(), delta, delta); + TRACE("subpaving_relevant_bound_bug", + tout << "k: "; nm().display(tout, k); + tout << ", delta: "; nm().display(tout, delta); tout << "\n"; + tout << "curr_lower: "; nm().display(tout, curr_lower->value()); + tout << ", min: "; nm().display(tout, min); tout << "\n";); + if (nm().le(k, delta)) { + TRACE("subpaving_relevant_bound", tout << "irrelevant because does not improve existing bound to at least "; + nm().display(tout, delta); tout << "\n";); + return false; + } + } + } + else { + // If new bound triggers a conflict, then it is relevant. + if (curr_lower && (nm().gt(curr_lower->value(), k) || ((open || curr_lower->is_open()) && nm().eq(k, curr_lower->value())))) { + TRACE("subpaving_relevant_bound", tout << "relevant because triggers conflict.\n";); + return true; + } + // If m_epsilon is zero, then bound is relevant only if it improves existing bound. + if (m_zero_epsilon && curr_upper != 0 && (nm().lt(curr_upper->value(), k) || ((curr_upper->is_open() || !open) && nm().eq(k, curr_upper->value())))) { + // new upper bound does not improve existing bound + TRACE("subpaving_relevant_bound", tout << "irrelevant because does not improve existing bound.\n";); + return false; + } + if (curr_lower == 0 && nm().lt(k, m_minus_max_bound)) { + // new upper bound exceeds the -:max-bound threshold + TRACE("subpaving_relevant_bound", tout << "irrelevant because exceeds -:max-bound threshold.\n";); + return false; + } + if (!m_zero_epsilon && curr_upper != 0) { + // check if: + // new-upper < upper - m_epsilon * max(min(upper - lower, |upper|), 1) + numeral & min = m_tmp1; + numeral & abs_upper = m_tmp2; + nm().set(abs_upper, curr_upper->value()); + nm().abs(abs_upper); + if (curr_lower != 0) { + nm().sub(curr_upper->value(), curr_lower->value(), min); + if (nm().lt(abs_upper, min)) + nm().set(min, abs_upper); + } + else { + nm().set(min, abs_upper); + } + numeral & delta = m_tmp3; + nm().set(delta, 1); + if (nm().gt(min, delta)) + nm().set(delta, min); + nm().mul(delta, m_epsilon, delta); + nm().sub(curr_upper->value(), delta, delta); + if (nm().ge(k, delta)) { + TRACE("subpaving_relevant_bound", tout << "irrelevant because does not improve existing bound to at least "; + nm().display(tout, delta); tout << "\n";); + return false; + } + } + } + TRACE("subpaving_relevant_bound", tout << "new bound is relevant\n";); + return true; + } + catch (typename C::exception) { + // arithmetic module failed. + set_arith_failed(); + return false; + } +} + + +template +bool context_t::is_zero(var x, node * n) const { + // Return true if lower(x) == upper(x) == 0 at n + bound * l = n->lower(x); + bound * u = n->upper(x); + return l != 0 && u != 0 && nm().is_zero(l->value()) && nm().is_zero(u->value()) && !l->is_open() && !u->is_open(); +} + +template +bool context_t::is_upper_zero(var x, node * n) const { + // Return true if upper(x) is zero at node n + bound * u = n->upper(x); + return u != 0 && nm().is_zero(u->value()) && !u->is_open(); +} + +template +bool context_t::conflicting_bounds(var x, node * n) const { + // Return true if upper(x) < lower(x) at node n + bound * l = n->lower(x); + bound * u = n->upper(x); + return l != 0 && u != 0 && (nm().lt(u->value(), l->value()) || ((l->is_open() || u->is_open()) && nm().eq(u->value(), l->value()))); +} + +/** + \brief Return the truth value of the inequality t in node n. + + The result may be l_true (True), l_false (False), or l_undef(Unknown). +*/ +template +lbool context_t::value(ineq * t, node * n) { + var x = t->x(); + bound * u = n->upper(x); + bound * l = n->lower(x); + if (u == 0 && l == 0) + return l_undef; + else if (t->is_lower()) { + if (u != 0 && (nm().lt(u->value(), t->value()) || ((u->is_open() || t->is_open()) && nm().eq(u->value(), t->value())))) + return l_false; + else if (l != 0 && (nm().gt(l->value(), t->value()) || ((l->is_open() || !t->is_open()) && nm().eq(l->value(), t->value())))) + return l_true; + else + return l_undef; + } + else { + if (l != 0 && (nm().gt(l->value(), t->value()) || ((l->is_open() || t->is_open()) && nm().eq(l->value(), t->value())))) + return l_false; + else if (u != 0 && (nm().lt(u->value(), t->value()) || ((u->is_open() || !t->is_open()) && nm().eq(u->value(), t->value())))) + return l_true; + else + return l_undef; + } +} + +template +void context_t::propagate_clause(clause * c, node * n) { + TRACE("propagate_clause", tout << "propagate using:\n"; display(tout, c); tout << "\n";); + m_num_visited++; + c->set_visited(m_timestamp); + unsigned sz = c->size(); + unsigned j = UINT_MAX; + for (unsigned i = 0; i < sz; i++) { + ineq * atom = (*c)[i]; + switch (value(atom, n)) { + case l_true: + return; // clause was already satisfied at n + case l_false: + break; + case l_undef: + if (j != UINT_MAX) + return; // clause has more than one unassigned literal + j = i; + break; + } + } + if (j == UINT_MAX) { + // Clause is in conflict, use first atom to trigger inconsistency + j = 0; + } + ineq * a = (*c)[j]; + TRACE("propagate_clause", tout << "propagating inequality: "; display(tout, a); tout << "\n";); + propagate_bound(a->x(), a->value(), a->is_lower(), a->is_open(), n, justification(c)); + // A clause can propagate only once. + // So, we can safely set its timestamp again to avoid another useless visit. + c->set_visited(m_timestamp); +} + +template +void context_t::propagate_polynomial(var x, node * n, var y) { + SASSERT(y != null_var); + SASSERT(is_polynomial(x)); + polynomial * p = get_polynomial(x); + unsigned sz = p->size(); + interval & r = m_i_tmp1; r.set_mutable(); + interval & v = m_i_tmp2; + interval & av = m_i_tmp3; av.set_mutable(); + if (x == y) { + for (unsigned i = 0; i < sz; i++) { + var z = p->x(i); + v.set_constant(n, z); + im().mul(p->a(i), v, av); + if (i == 0) + im().set(r, av); + else + im().add(r, av, r); + } + // r contains the deduced bounds for x == y + } + else { + v.set_constant(n, x); + numeral & a = m_tmp1; + im().set(r, v); + for (unsigned i = 0; i < sz; i++) { + var z = p->x(i); + if (z != y) { + v.set_constant(n, z); + im().mul(p->a(i), v, av); + im().sub(r, av, r); + } + else { + nm().set(a, p->a(i)); + TRACE("propagate_polynomial_bug", tout << "a: "; nm().display(tout, a); tout << "\n";); + } + } + TRACE("propagate_polynomial_bug", tout << "r before mul 1/a: "; im().display(tout, r); tout << "\n";); + im().div(r, a, r); + TRACE("propagate_polynomial_bug", tout << "r after mul 1/a: "; im().display(tout, r); tout << "\n";); + // r contains the deduced bounds for y. + } + // r contains the deduced bounds for y. + if (!r.m_l_inf) { + normalize_bound(y, r.m_l_val, true, r.m_l_open); + if (relevant_new_bound(y, r.m_l_val, true, r.m_l_open, n)) { + propagate_bound(y, r.m_l_val, true, r.m_l_open, n, justification(x)); + if (inconsistent(n)) + return; + } + } + if (!r.m_u_inf) { + normalize_bound(y, r.m_u_val, false, r.m_u_open); + if (relevant_new_bound(y, r.m_u_val, false, r.m_u_open, n)) + propagate_bound(y, r.m_u_val, false, r.m_u_open, n, justification(x)); + } +} + +template +void context_t::propagate_polynomial(var x, node * n) { + TRACE("propagate_polynomial", tout << "propagate_polynomial: "; display(tout, x); tout << "\n";); + TRACE("propagate_polynomial_detail", display_bounds(tout, n);); + SASSERT(is_polynomial(x)); + polynomial * p = get_polynomial(x); + p->set_visited(m_timestamp); + var unbounded_var = null_var; + if (is_unbounded(x, n)) + unbounded_var = x; + unsigned sz = p->size(); + for (unsigned i = 0; i < sz; i++) { + var y = p->x(i); + if (is_unbounded(y, n)) { + if (unbounded_var != null_var) + return; // no propagation is possible. + unbounded_var = y; + } + } + TRACE("propagate_polynomial", tout << "unbounded_var: "; display(tout, unbounded_var); tout << "\n";); + + if (unbounded_var != null_var) { + propagate_polynomial(x, n, unbounded_var); + } + else { + propagate_polynomial(x, n, x); + for (unsigned i = 0; i < sz; i++) { + if (inconsistent(n)) + return; + propagate_polynomial(x, n, p->x(i)); + } + } +} + +template +void context_t::propagate_monomial(var x, node * n) { + TRACE("propagate_monomial", tout << "propagate_monomial: "; display(tout, x); tout << "\n";); + SASSERT(is_monomial(x)); + SASSERT(!inconsistent(n)); + monomial * m = get_monomial(x); + m->set_visited(m_timestamp); + bool found_unbounded = false; + bool found_zero = false; + bool x_is_unbounded = false; + unsigned sz = m->size(); + for (unsigned i = 0; i < sz; i++) { + var y = m->x(i); + if (is_zero(y, n)) { + found_zero = true; + } + if (m->degree(i) % 2 == 0) { + if (is_upper_zero(y, n)) { + found_zero = true; + } + continue; // elements with even power always produce a lower bound + } + if (is_unbounded(y, n)) { + found_unbounded = true; + } + } + TRACE("propagate_monomial", tout << "found_zero: " << found_zero << ", found_unbounded: " << found_unbounded << "\n";); + if (found_zero) { + if (!is_zero(x, n)) { + // x must be zero + numeral & zero = m_tmp1; + nm().set(zero, 0); + propagate_bound(x, zero, true, false, n, justification(x)); + if (inconsistent(n)) + return; + propagate_bound(x, zero, false, false, n, justification(x)); + } + // no need to downward propagation + return; + } + x_is_unbounded = n->is_unbounded(x); + if (!found_unbounded) + propagate_monomial_upward(x, n); + if (inconsistent(n)) + return; + if (!x_is_unbounded) { + unsigned bad_pos = UINT_MAX; + interval & aux = m_i_tmp1; + for (unsigned i = 0; i < sz; i++) { + aux.set_constant(n, m->x(i)); + if (im().contains_zero(aux)) { + if (bad_pos != UINT_MAX) + return; // there is more than one position that contains zero, so downward propagation is not possible. + bad_pos = i; + } + } + if (bad_pos == UINT_MAX) { + // we can use all variables for downward propagation. + for (unsigned i = 0; i < sz; i++) { + if (inconsistent(n)) + return; + propagate_monomial_downward(x, n, i); + } + } + else { + propagate_monomial_downward(x, n, bad_pos); + } + } +} + +template +void context_t::propagate_monomial_upward(var x, node * n) { + SASSERT(is_monomial(x)); + monomial * m = get_monomial(x); + unsigned sz = m->size(); + interval & r = m_i_tmp1; r.set_mutable(); + interval & y = m_i_tmp2; + interval & yk = m_i_tmp3; yk.set_mutable(); + for (unsigned i = 0; i < sz; i++) { + y.set_constant(n, m->x(i)); + im().power(y, m->degree(i), yk); + if (i == 0) + im().set(r, yk); + else + im().mul(r, yk, r); + } + // r contains the new bounds for x + if (!r.m_l_inf) { + normalize_bound(x, r.m_l_val, true, r.m_l_open); + if (relevant_new_bound(x, r.m_l_val, true, r.m_l_open, n)) { + propagate_bound(x, r.m_l_val, true, r.m_l_open, n, justification(x)); + if (inconsistent(n)) + return; + } + } + if (!r.m_u_inf) { + normalize_bound(x, r.m_u_val, false, r.m_u_open); + if (relevant_new_bound(x, r.m_u_val, false, r.m_u_open, n)) + propagate_bound(x, r.m_u_val, false, r.m_u_open, n, justification(x)); + } +} + +template +void context_t::propagate_monomial_downward(var x, node * n, unsigned j) { + TRACE("propagate_monomial", tout << "propagate_monomial_downward: "; display(tout, x); tout << ", j: " << j << "\n"; + display(tout, get_monomial(x)); tout << "\n";); + SASSERT(is_monomial(x)); + monomial * m = get_monomial(x); + SASSERT(j < m->size()); + unsigned sz = m->size(); + + interval & r = m_i_tmp3; + if (sz > 1) { + interval & d = m_i_tmp1; d.set_mutable(); + interval & y = m_i_tmp2; + interval & yk = m_i_tmp3; yk.set_mutable(); + bool first = true; + for (unsigned i = 0; i < sz; i++) { + if (i == j) + continue; + y.set_constant(n, m->x(i)); + im().power(y, m->degree(i), yk); + if (first) + im().set(d, yk); + else + im().mul(d, yk, r); + } + interval & aux = m_i_tmp2; + aux.set_constant(n, x); + im().div(aux, d, r); + } + else { + SASSERT(sz == 1); + SASSERT(j == 0); + interval & aux = m_i_tmp2; + aux.set_constant(n, x); + im().set(r, aux); + } + unsigned d = m->degree(j); + if (d > 1) { + if (d % 2 == 0 && im().lower_is_neg(r)) + return; // If d is even, we can't take the nth-root when lower(r) is negative. + im().xn_eq_y(r, d, m_nth_root_prec, r); + } + var y = m->x(j); + // r contains the new bounds for y + if (!r.m_l_inf) { + normalize_bound(y, r.m_l_val, true, r.m_l_open); + if (relevant_new_bound(y, r.m_l_val, true, r.m_l_open, n)) { + propagate_bound(y, r.m_l_val, true, r.m_l_open, n, justification(x)); + if (inconsistent(n)) + return; + } + } + if (!r.m_u_inf) { + normalize_bound(y, r.m_u_val, false, r.m_u_open); + if (relevant_new_bound(y, r.m_u_val, false, r.m_u_open, n)) + propagate_bound(y, r.m_u_val, false, r.m_u_open, n, justification(x)); + } +} + +template +bool context_t::most_recent(bound * b, node * n) const { + var x = b->x(); + if (b->is_lower()) + return n->lower(x) == b; + else + return n->upper(x) == b; +} + +template +void context_t::add_recent_bounds(node * n) { + SASSERT(m_queue.empty()); + bound * old_b = n->parent_trail_stack(); + bound * b = n->trail_stack(); + while (b != old_b) { + if (most_recent(b, n)) { + b->set_timestamp(m_timestamp); + m_queue.push_back(b); + } + b = b->prev(); + } +} + +template +void context_t::propagate_def(var x, node * n) { + SASSERT(is_definition(x)); + m_num_visited++; + definition * d = m_defs[x]; + switch (d->get_kind()) { + case constraint::MONOMIAL: + propagate_monomial(x, n); + break; + case constraint::POLYNOMIAL: + propagate_polynomial(x, n); + break; + default: + break; + } +} + +template +void context_t::propagate(node * n, bound * b) { + var x = b->x(); + TRACE("subpaving_propagate", tout << "propagate: "; display(tout, b); tout << ", timestamp: " << b->timestamp() << "\n";); + typename watch_list::const_iterator it = m_wlist[x].begin(); + typename watch_list::const_iterator end = m_wlist[x].end(); + for (; it != end; ++it) { + if (inconsistent(n)) + return; + watched const & w = *it; + try { + if (w.is_clause()) { + clause * c = w.get_clause(); + if (may_propagate(b, c, n)) { + propagate_clause(c, n); + } + } + else { + var y = w.get_var(); + definition * d = m_defs[y]; + if (may_propagate(b, d, n)) { + propagate_def(y, n); + } + } + } + catch (typename C::exception) { + // arithmetic module failed, ignore constraint + set_arith_failed(); + } + } + if (inconsistent(n)) + return; + if (is_definition(x)) { + definition * d = m_defs[x]; + if (may_propagate(b, d, n)) { + propagate_def(x, n); + } + } +} + +template +void context_t::propagate(node * n) { + while (m_qhead < m_queue.size()) { + if (inconsistent(n)) + break; + checkpoint(); + bound * b = m_queue[m_qhead]; + SASSERT(is_bound_of(b, n)); + m_qhead++; + propagate(n, b); + } + m_queue.reset(); + m_qhead = 0; +} + +template +void context_t::propagate_all_definitions(node * n) { + unsigned num = num_vars(); + for (unsigned x = 0; x < num; x++) { + if (inconsistent(n)) + break; + if (is_definition(x)) + propagate_def(x, n); + } +} + +// ----------------------------------- +// +// Main +// +// ----------------------------------- + +template +void context_t::assert_units(node * n) { + typename ptr_vector::const_iterator it = m_unit_clauses.begin(); + typename ptr_vector::const_iterator end = m_unit_clauses.end(); + for (; it != end; ++it) { + checkpoint(); + ineq * a = UNTAG(ineq*, *it); + bool axiom = GET_TAG(*it) != 0; + TRACE("subpaving_init", tout << "asserting: "; display(tout, a); tout << ", axiom: " << axiom << "\n";); + propagate_bound(a->x(), a->value(), a->is_lower(), a->is_open(), n, justification(axiom)); + if (inconsistent(n)) + break; + } + TRACE("subpaving_init", tout << "bounds after init\n"; display_bounds(tout, n);); +} + +template +void context_t::init() { + SASSERT(m_root == 0); + SASSERT(m_leaf_head == 0); + SASSERT(m_leaf_tail == 0); + m_timestamp = 0; + m_root = mk_node(); + SASSERT(m_leaf_head == m_root); + SASSERT(m_leaf_tail == m_root); + TRACE("subpaving_init", display_constraints(tout);); + assert_units(m_root); + propagate_all_definitions(m_root); + propagate(m_root); + TRACE("subpaving_init", tout << "root bounds after propagation\n"; display_bounds(tout, m_root);); + SASSERT(check_invariant()); +} + +template +void context_t::operator()() { + if (m_root == 0) + init(); + TRACE("subpaving_stats", statistics st; collect_statistics(st); tout << "statistics:\n"; st.display_smt2(tout);); + TRACE("subpaving_main", display_params(tout);); + while (m_leaf_head != 0) { + checkpoint(); + SASSERT(m_queue.empty()); + if (m_num_nodes > m_max_nodes) + break; + node * n = (*m_node_selector)(m_leaf_head, m_leaf_tail); + if (n == 0) + break; + TRACE("subpaving_main", tout << "selected node: #" << n->id() << ", depth: " << n->depth() << "\n";); + remove_from_leaf_dlist(n); + if (n != m_root) { + add_recent_bounds(n); + propagate(n); + } + TRACE("subpaving_main", tout << "node #" << n->id() << " after propagation\n"; + display_bounds(tout, n);); + if (n->inconsistent()) { + TRACE("subpaving_main", tout << "node #" << n->id() << " is inconsistent.\n";); + // TODO: conflict resolution + continue; + } + if (n->depth() >= m_max_depth) { + TRACE("subpaving_main", tout << "maximum depth reached, skipping node #" << n->id() << "\n";); + continue; + } + var x = (*m_var_selector)(n); + TRACE("subpaving_main", tout << "splitting variable: "; display(tout, x); tout << "\n";); + if (x != null_var) { + (*m_node_splitter)(n, x); + m_num_splits++; + // remove inconsistent children + } + } + TRACE("subpaving_stats", statistics st; collect_statistics(st); tout << "statistics:\n"; st.display_smt2(tout);); +} + +template +void context_t::display_bounds(std::ostream & out) const { + ptr_vector leaves; + collect_leaves(leaves); + typename ptr_vector::const_iterator it = leaves.begin(); + typename ptr_vector::const_iterator end = leaves.end(); + for (bool first = true; it != end; ++it) { + node * n = *it; + if (first) + first = false; + else + out << "=========\n"; + display_bounds(out, n); + } +} + +// ----------------------------------- +// +// Statistics +// +// ----------------------------------- + +template +void context_t::reset_statistics() { + m_num_conflicts = 0; + m_num_mk_bounds = 0; + m_num_splits = 0; + m_num_visited = 0; +} + +template +void context_t::collect_statistics(statistics & st) const { + st.update("conflicts", m_num_conflicts); + st.update("new bounds", m_num_mk_bounds); + st.update("splits", m_num_splits); + st.update("nodes", m_num_nodes); + st.update("visited", m_num_visited); +} + +// ----------------------------------- +// +// Debugging support +// +// ----------------------------------- + +template +bool context_t::is_bound_of(bound * b, node * n) const { + bound * c = n->trail_stack(); + while (c != 0) { + if (c == b) + return true; + if (c->timestamp() <= b->timestamp()) + return false; + c = c->prev(); + } + return false; +} + +template +bool context_t::check_leaf_dlist() const { + node * n = m_leaf_head; + while (n != 0) { + node * next = n->next(); + SASSERT(next != 0 || m_leaf_tail == n); + SASSERT(next == 0 || next->prev() == n); + n = next; + } + return true; +} + +template +bool context_t::check_tree() const { + ptr_buffer todo; + if (m_root != 0) + todo.push_back(m_root); + while (!todo.empty()) { + node * n = todo.back(); + todo.pop_back(); + node * c = n->first_child(); + while (c != 0) { + SASSERT(c->parent() == n); + todo.push_back(c); + c = c->next_sibling(); + } + } + return true; +} + +template +bool context_t::check_invariant() const { + SASSERT(check_tree()); + SASSERT(check_leaf_dlist()); + return true; +} + + +}; diff --git a/lib/subpaving_tactic.cpp b/lib/subpaving_tactic.cpp new file mode 100644 index 000000000..17e1e72d2 --- /dev/null +++ b/lib/subpaving_tactic.cpp @@ -0,0 +1,308 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + subpaving_tactic.cpp + +Abstract: + + "Fake" tactic used to test subpaving module. + +Author: + + Leonardo de Moura (leonardo) 2012-08-07. + +Revision History: + +--*/ +#include"tactical.h" +#include"simplify_tactic.h" +#include"expr2subpaving.h" +#include"expr2var.h" +#include"arith_decl_plugin.h" +#include"ast_smt2_pp.h" +#include"hwf.h" +#include"mpff.h" +#include"mpfx.h" +#include"f2n.h" + +class subpaving_tactic : public tactic { + + struct display_var_proc : public subpaving::display_var_proc { + expr_ref_vector m_inv; + + display_var_proc(expr2var & e2v):m_inv(e2v.m()) { + e2v.mk_inv(m_inv); + } + + ast_manager & m() const { return m_inv.get_manager(); } + + virtual void operator()(std::ostream & out, subpaving::var x) const { + expr * t = m_inv.get(x, 0); + if (t != 0) + out << mk_ismt2_pp(t, m()); + else + out << "k!" << x; + } + }; + + struct imp { + enum engine_kind { MPQ, MPF, HWF, MPFF, MPFX, NONE }; + + ast_manager & m_manager; + unsynch_mpq_manager m_qm; + mpf_manager m_fm_core; + f2n m_fm; + hwf_manager m_hm_core; + f2n m_hm; + mpff_manager m_ffm; + mpfx_manager m_fxm; + arith_util m_autil; + engine_kind m_kind; + scoped_ptr m_ctx; + scoped_ptr m_proc; + expr2var m_e2v; + scoped_ptr m_e2s; + bool m_display; + + imp(ast_manager & m, params_ref const & p): + m_manager(m), + m_fm(m_fm_core), + m_hm(m_hm_core), + m_autil(m), + m_kind(NONE), + m_e2v(m) { + updt_params(p); + } + + ast_manager & m() const { return m_manager; } + + void collect_param_descrs(param_descrs & r) { + m_ctx->collect_param_descrs(r); + // #ifndef _EXTERNAL_RELEASE + r.insert(":numeral", CPK_SYMBOL, "(default: mpq) options: mpq, mpf, hwf, mpff, mpfx."); + r.insert(":print-nodes", CPK_BOOL, "(default: false) display subpaving tree leaves."); + // #endif + } + + void updt_params(params_ref const & p) { + m_display = p.get_bool(":print-nodes", false); + symbol engine = p.get_sym(":numeral", symbol("mpq")); + engine_kind new_kind; + if (engine == "mpq") + new_kind = MPQ; + else if (engine == "mpf") + new_kind = MPF; + else if (engine == "mpff") + new_kind = MPFF; + else if (engine == "mpfx") + new_kind = MPFX; + else + new_kind = HWF; + if (m_kind != new_kind) { + m_kind = new_kind; + switch (m_kind) { + case MPQ: m_ctx = subpaving::mk_mpq_context(m_qm); break; + case MPF: m_ctx = subpaving::mk_mpf_context(m_fm); break; + case HWF: m_ctx = subpaving::mk_hwf_context(m_hm, m_qm); break; + case MPFF: m_ctx = subpaving::mk_mpff_context(m_ffm, m_qm); break; + case MPFX: m_ctx = subpaving::mk_mpfx_context(m_fxm, m_qm); break; + default: UNREACHABLE(); break; + } + m_e2s = alloc(expr2subpaving, m_manager, *m_ctx, &m_e2v); + } + m_ctx->updt_params(p); + } + + void collect_statistics(statistics & st) const { + m_ctx->collect_statistics(st); + } + + void reset_statistics() { + m_ctx->reset_statistics(); + } + + void set_cancel(bool f) { + m_e2s->set_cancel(f); + m_ctx->set_cancel(f); + } + + subpaving::ineq * mk_ineq(expr * a) { + bool neg = false; + while (m().is_not(a, a)) + neg = !neg; + bool lower; + bool open = false; + if (m_autil.is_le(a)) { + lower = false; + } + else if (m_autil.is_ge(a)) { + lower = true; + } + else { + throw tactic_exception("unsupported atom"); + } + if (neg) { + lower = !lower; + open = !open; + } + rational _k; + if (!m_autil.is_numeral(to_app(a)->get_arg(1), _k)) + throw tactic_exception("use simplify tactic with option :arith-lhs true"); + scoped_mpq k(m_qm); + k = _k.to_mpq(); + scoped_mpz n(m_qm), d(m_qm); + subpaving::var x = m_e2s->internalize_term(to_app(a)->get_arg(0), n, d); + m_qm.mul(d, k, k); + m_qm.div(k, n, k); + if (is_neg(n)) + lower = !lower; + TRACE("subpaving_tactic", tout << x << " " << k << " " << lower << " " << open << "\n";); + return m_ctx->mk_ineq(x, k, lower, open); + } + + void process_clause(expr * c) { + expr * const * args = 0; + unsigned sz; + if (m().is_or(c)) { + args = to_app(c)->get_args(); + sz = to_app(c)->get_num_args(); + } + else { + args = &c; + sz = 1; + } + ref_buffer ineq_buffer(*m_ctx); + for (unsigned i = 0; i < sz; i++) { + ineq_buffer.push_back(mk_ineq(args[i])); + } + m_ctx->add_clause(sz, ineq_buffer.c_ptr()); + } + + void internalize(goal const & g) { + try { + for (unsigned i = 0; i < g.size(); i++) { + process_clause(g.form(i)); + } + } + catch (subpaving::exception) { + throw tactic_exception("failed to internalize goal into subpaving module"); + } + } + + void process(goal const & g) { + internalize(g); + m_proc = alloc(display_var_proc, m_e2v); + m_ctx->set_display_proc(m_proc.get()); + try { + (*m_ctx)(); + } + catch (subpaving::exception) { + throw tactic_exception("failed building subpaving tree..."); + } + if (m_display) { + m_ctx->display_constraints(std::cout); + std::cout << "bounds at leaves: \n"; + m_ctx->display_bounds(std::cout); + } + } + }; + + imp * m_imp; + params_ref m_params; + statistics m_stats; +public: + + subpaving_tactic(ast_manager & m, params_ref const & p): + m_imp(alloc(imp, m, p)), + m_params(p) { + } + + virtual ~subpaving_tactic() { + dealloc(m_imp); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(subpaving_tactic, m, m_params); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + m_imp->collect_param_descrs(r); + } + + virtual void collect_statistics(statistics & st) const { + st.copy(m_stats); + } + + virtual void reset_statistics() { + m_stats.reset(); + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + m_imp->process(*in); + m_imp->collect_statistics(m_stats); + result.reset(); + result.push_back(in.get()); + mc = 0; + pc = 0; + core = 0; + } + + virtual void cleanup() { + ast_manager & m = m_imp->m(); + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + d = m_imp; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + } + +protected: + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } +}; + + +tactic * mk_subpaving_tactic_core(ast_manager & m, params_ref const & p) { + return alloc(subpaving_tactic, m, p); +} + +tactic * mk_subpaving_tactic(ast_manager & m, params_ref const & p) { + params_ref simp_p = p; + simp_p.set_bool(":arith-lhs", true); + simp_p.set_bool(":expand-power", true); + simp_p.set_uint(":max-power", UINT_MAX); + simp_p.set_bool(":som", true); + simp_p.set_bool(":eq2ineq", true); + simp_p.set_bool(":elim-and", true); + simp_p.set_bool(":blast-distinct", true); + + params_ref simp2_p = p; + simp2_p.set_bool(":mul-to-power", true); + + return and_then(using_params(mk_simplify_tactic(m, p), + simp_p), + using_params(mk_simplify_tactic(m, p), + simp2_p), + mk_subpaving_tactic_core(m, p)); +} + + diff --git a/lib/subpaving_tactic.h b/lib/subpaving_tactic.h new file mode 100644 index 000000000..b39acf1a4 --- /dev/null +++ b/lib/subpaving_tactic.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + subpaving_tactic.h + +Abstract: + + "Fake" tactic used to test subpaving module. + +Author: + + Leonardo de Moura (leonardo) 2012-08-07. + +Revision History: + +--*/ +#ifndef __SUBPAVING_TACTIC_H_ +#define __SUBPAVING_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_subpaving_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/subpaving_types.h b/lib/subpaving_types.h new file mode 100644 index 000000000..8372b5b1f --- /dev/null +++ b/lib/subpaving_types.h @@ -0,0 +1,52 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + subpaving_types.h + +Abstract: + + Subpaving auxiliary types. + +Author: + + Leonardo de Moura (leonardo) 2012-08-07. + +Revision History: + +--*/ +#ifndef __SUBPAVING_TYPES_H_ +#define __SUBPAVING_TYPES_H_ + +namespace subpaving { + +class ineq; + +typedef unsigned var; + +const var null_var = UINT_MAX; + +class exception { +}; + +class power : public std::pair { +public: + power():std::pair() {} + power(var v, unsigned d):std::pair(v, d) {} + power(power const & p):std::pair(p) {} + var x() const { return first; } + var get_var() const { return first; } + unsigned degree() const { return second; } + unsigned & degree() { return second; } + void set_var(var x) { first = x; } + struct lt_proc { bool operator()(power const & p1, power const & p2) { return p1.get_var() < p2.get_var(); } }; +}; + +struct display_var_proc { + virtual void operator()(std::ostream & out, var x) const { out << "x" << x; } +}; + +}; + +#endif diff --git a/lib/substitution.cpp b/lib/substitution.cpp new file mode 100644 index 000000000..2420a4d3f --- /dev/null +++ b/lib/substitution.cpp @@ -0,0 +1,286 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + substitution.cpp + +Abstract: + + A substitution, that is, a mapping from (variable, offset) to (expr, offset). + We use offsets in order to avoid creating variants of terms. + +Author: + + Leonardo de Moura (leonardo) 2008-02-01. + +Revision History: + +--*/ +#include"substitution.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" + +substitution::substitution(ast_manager & m): + m_manager(m), + m_new_exprs(m) { +} + +void substitution::reset() { + reset_subst(); + reset_cache(); +} + +void substitution::reset_subst() { + m_subst.reset(); + m_vars.reset(); + m_scopes.reset(); +} + +void substitution::reset_cache() { + TRACE("subst_bug", tout << "substitution::reset_cache\n"; + for (unsigned i = 0; i < m_new_exprs.size(); i++) { tout << mk_pp(m_new_exprs.get(i), m_manager) << "\nref_count: " << m_new_exprs.get(i)->get_ref_count() << "\n"; }); + + m_apply_cache.reset(); + m_new_exprs.reset(); +} + +void substitution::pop_scope(unsigned num_scopes) { + unsigned lvl = m_scopes.size(); + SASSERT(num_scopes <= lvl); + unsigned new_lvl = lvl - num_scopes; + unsigned old_sz = m_scopes[new_lvl]; + unsigned curr_sz = m_vars.size(); + SASSERT(old_sz <= curr_sz); + for (unsigned i = old_sz; i < curr_sz; i++) { + var_offset & curr = m_vars[i]; + m_subst.erase(curr.first, curr.second); + } + m_vars.shrink(old_sz); + m_scopes.shrink(new_lvl); +} + +inline void substitution::apply_visit(expr_offset const & n, bool & visited) { + if (!m_apply_cache.contains(n)) { + m_todo.push_back(n); + visited = false; + } +} + +void substitution::apply(unsigned num_actual_offsets, unsigned const * deltas, expr_offset const & n, + expr_offset const & s, expr_offset const & t, expr_ref & result) { + + TRACE("subst_bug", tout << "BEGIN substitution::apply\n";); + + // It is incorrect to cache results between different calls if we are applying a substitution + // modulo a substitution s -> t. + if (s != expr_offset(0,0)) + reset_cache(); + + unsigned j; + expr * e; + unsigned off; + expr_offset n1; + bool visited; + unsigned num_args; + ptr_buffer new_args; + + m_todo.push_back(n); + while (!m_todo.empty()) { + expr_offset n = m_todo.back(); + TRACE("subst_bug", tout << "n: " << mk_pp(n.get_expr(), m_manager) << " : " << n.get_offset() << "\n";); + if (m_apply_cache.contains(n)) { + m_todo.pop_back(); + continue; + } + expr_offset n_prime = n == s ? t : n; + TRACE("subst_bug", tout << "n_prime: " << mk_pp(n_prime.get_expr(), m_manager) << " : " << n_prime.get_offset() << "\n";); + visited = true; + e = n_prime.get_expr(); + off = n_prime.get_offset(); + switch (e->get_kind()) { + case AST_VAR: + if (find(to_var(e)->get_idx(), off, n1)) { + apply_visit(n1, visited); + TRACE("subst_bug", tout << "visited: " << visited << ", n1: " << mk_pp(n1.get_expr(), m_manager) << " : " << n1.get_offset() << "\n";); + if (visited) { + m_todo.pop_back(); + expr * new_expr; + m_apply_cache.find(n1, new_expr); + m_apply_cache.insert(n, new_expr); + TRACE("subst_bug", tout << "1. insert n: " << mk_pp(n.get_expr(), m_manager) << " : " << n.get_offset() + << " --> " << mk_pp(new_expr, m_manager) << "\n";); + } + } + else { + m_todo.pop_back(); + SASSERT(off < num_actual_offsets); + unsigned delta = deltas[off]; + expr * new_expr = e; + if (delta > 0) { + new_expr = m_manager.mk_var(to_var(e)->get_idx() + delta, to_var(e)->get_sort()); + m_new_exprs.push_back(new_expr); + } + m_apply_cache.insert(n, new_expr); + TRACE("subst_bug", tout << "2. insert n: " << mk_pp(n.get_expr(), m_manager) << " : " << n.get_offset() + << " --> " << mk_pp(new_expr, m_manager) << "\n";); + } + break; + case AST_APP: + num_args = to_app(e)->get_num_args(); + j = num_args; + while (j > 0) { + --j; + apply_visit(expr_offset(to_app(e)->get_arg(j), off), visited); + } + if (visited) { + m_todo.pop_back(); + new_args.reset(); + bool has_new_args = false; + for (unsigned i = 0; i < num_args; i++) { + expr * arg = to_app(e)->get_arg(i); + expr * new_arg; + + m_apply_cache.find(expr_offset(arg, off), new_arg); + new_args.push_back(new_arg); + if (arg != new_arg) + has_new_args = true; + } + if (!has_new_args) { + m_apply_cache.insert(n, e); + TRACE("subst_bug", tout << "3. insert n: " << mk_pp(n.get_expr(), m_manager) << " : " << n.get_offset() + << " --> " << mk_pp(e, m_manager) << "\n";); + } + else { + expr * new_expr = m_manager.mk_app(to_app(e)->get_decl(), new_args.size(), new_args.c_ptr()); + m_new_exprs.push_back(new_expr); + m_apply_cache.insert(n, new_expr); + TRACE("subst_bug", tout << "3. insert n: " << mk_pp(n.get_expr(), m_manager) << " : " << n.get_offset() + << " --> " << mk_pp(new_expr, m_manager) << "\n";); + } + } + break; + default: + UNREACHABLE(); + } + } + SASSERT(m_apply_cache.contains(n)); + m_apply_cache.find(n, e); + m_new_exprs.push_back(e); + result = e; + + if (s != expr_offset(0,0)) + reset_cache(); + + TRACE("subst_bug", tout << "END substitution::apply\nresult:\n" << mk_pp(e, m_manager) << "\nref_count: " << e->get_ref_count() << "\n";); +} + +inline substitution::color substitution::get_color(expr_offset const & p) const { + color c; + if (m_color.find(p, c)) + return c; + return White; +} + +inline void substitution::set_color(expr_offset const & p, color c) { + m_color.insert(p, c); +} + +inline void substitution::visit(expr_offset const & p, bool & visited) { + if (get_color(p) != Black) { + m_todo.push_back(p); + visited = false; + } +} + +bool substitution::visit_children(expr_offset const & p) { + bool visited = true; + expr * n = p.get_expr(); + unsigned off; + expr_offset p1; + unsigned j; + switch (n->get_kind()) { + case AST_VAR: + if (find(p, p1) && p != p1) + visit(p1, visited); + break; + case AST_APP: + off = p.get_offset(); + j = to_app(n)->get_num_args(); + while (j > 0) { + --j; + visit(expr_offset(to_app(n)->get_arg(j), off), visited); + } + break; + default: + UNREACHABLE(); + } + return visited; +} + +bool substitution::acyclic(expr_offset p) { + if (get_color(p) == Black) + return true; + m_todo.reset(); + m_todo.push_back(p); + while (!m_todo.empty()) { + expr_offset p = m_todo.back(); + switch (get_color(p)) { + case Black: + m_todo.pop_back(); + break; + case White: + set_color(p, Grey); + if (visit_children(p)) { + set_color(p, Black); + SASSERT(m_todo.back() == p); + m_todo.pop_back(); + } + break; + case Grey: + if (!visit_children(p)) + return false; + set_color(p, Black); + SASSERT(m_todo.back() == p); + m_todo.pop_back(); + break; + } + } + return true; +} + +bool substitution::acyclic() { + m_color.reset(); + expr_offset r; + svector::iterator it = m_vars.begin(); + svector::iterator end = m_vars.end(); + for (; it != end; ++it) { + m_subst.find(it->first, it->second, r); + if (!acyclic(r)) + return false; + } + return true; +} + +void substitution::display(std::ostream & out, unsigned num_actual_offsets, unsigned const * deltas) { + reset_cache(); + for (unsigned i = 0; i < num_actual_offsets; i++) + for (unsigned j = 0; j < m_subst.vars_capacity(); j++) { + expr_offset r; + if (find(j, i, r)) { + expr_ref tmp(m_manager); + apply(num_actual_offsets, deltas, r, tmp); + out << "VAR " << j << ":" << i << " -->\n" << mk_pp(tmp, m_manager) << "\n"; + } + } +} + +void substitution::display(std::ostream & out) { + for (unsigned i = 0; i < m_subst.offsets_capacity(); i++) + for (unsigned j = 0; j < m_subst.vars_capacity(); j++) { + expr_offset r; + if (find(j, i, r)) + out << "VAR " << j << ":" << i << " --> " << r.get_offset() << "\n" << mk_pp(r.get_expr(), m_manager) << "\n"; + } +} + diff --git a/lib/substitution.h b/lib/substitution.h new file mode 100644 index 000000000..1d12dc278 --- /dev/null +++ b/lib/substitution.h @@ -0,0 +1,202 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + substitution.h + +Abstract: + + A substitution, that is, a mapping from (variable, offset) to (expr, offset). + We use offsets in order to avoid creating variants of terms. + +Author: + + Leonardo de Moura (leonardo) 2008-02-01. + +Revision History: + +--*/ +#ifndef _SUBSTITUTION_H_ +#define _SUBSTITUTION_H_ + +#include"expr_offset_map.h" +#include"var_offset_map.h" +#include"ast_pp.h" + +/** + \brief A mapping from (variable,offset) to expr_offset. +*/ +class substitution { + ast_manager & m_manager; + var_offset_map m_subst; + + // field for backtracking + typedef std::pair var_offset; + svector m_vars; + unsigned_vector m_scopes; + + // fields for applying substitutions + svector m_todo; + expr_offset_map m_apply_cache; + expr_ref_vector m_new_exprs; + + // fields for checking for cycles + enum color { White, Grey, Black }; + expr_offset_map m_color; + +#ifdef Z3DEBUG + unsigned m_max_offset_since_reset; +#endif + void apply_visit(expr_offset const & n, bool & visited); + + + color get_color(expr_offset const & p) const; + void set_color(expr_offset const & p, color c); + + void visit(expr_offset const & p, bool & visited); + bool visit_children(expr_offset const & p); + bool acyclic(expr_offset p); + +public: + substitution(ast_manager & m); + ast_manager & get_manager() const { return m_manager; } + + // ----------------------------------- + // + // Reserve memory for the given number of + // offsets and variables. + // + // ----------------------------------- + + void reserve(unsigned num_offsets, unsigned num_vars) { m_subst.reserve(num_offsets, num_vars); } + void reserve_offsets(unsigned num_offsets) { m_subst.reserve_offsets(num_offsets); } + void reserve_vars(unsigned num_vars) { m_subst.reserve_vars(num_vars); } + + // ----------------------------------- + // + // Reset functions + // + // ----------------------------------- + + // reset everything + void reset(); + // reset only the mapping from variables to expressions + void reset_subst(); + // reset only the substitution application cache + void reset_cache(); + + // ----------------------------------- + // + // Backtracking + // + // ----------------------------------- + + void push_scope() { m_scopes.push_back(m_vars.size()); } + void pop_scope(unsigned num_scopes = 1); + unsigned get_scope_lvl() { return m_scopes.size(); } + bool top_scope_has_bindings() const { return m_scopes.empty() ? !m_vars.empty() : m_scopes.back() < m_vars.size(); } + unsigned get_num_bindings() const { return m_vars.size(); } + + + // ----------------------------------- + // + // Cycle detection + // + // ----------------------------------- + + bool acyclic(); + + // ----------------------------------- + // + // Insertion & Lookup + // + // get_binding supplies a way to inspect the substitution. + // + // ----------------------------------- + + void insert(unsigned v_idx, unsigned offset, expr_offset const & t) { + TRACE("subst_insert", tout << "inserting: #" << v_idx << ":" << offset << " --> " << mk_pp(t.get_expr(), m_manager) + << ":" << t.get_offset() << "\n";); + m_vars.push_back(var_offset(v_idx, offset)); + m_subst.insert(v_idx, offset, t); + } + void insert(var * v, unsigned offset, expr_offset const & t) { insert(v->get_idx(), offset, t); } + void insert(expr_offset v, expr_offset const & t) { + SASSERT(is_var(v.get_expr())); + insert(to_var(v.get_expr()), v.get_offset(), t); + } + + bool find(unsigned v_idx, unsigned offset, expr_offset & r) const { return m_subst.find(v_idx, offset, r); } + bool find(var * v, unsigned offset, expr_offset & r) const { return find(v->get_idx(), offset, r); } + bool find(expr_offset v, expr_offset & r) const { + SASSERT(is_var(v.get_expr())); + return find(to_var(v.get_expr()), v.get_offset(), r); + } + + void get_binding(unsigned binding_num, var_offset& var, expr_offset& r) { + var = m_vars[binding_num]; + VERIFY(m_subst.find(var.first, var.second, r)); + } + + bool contains(var * v, unsigned offset) { expr_offset r; return find(v, offset, r); } + + // ----------------------------------- + // + // Application + // + // ----------------------------------- + + /** + \brief Apply the current substitution to the given + expression+offset. The result is an expression. + + The argument num_actual_offsets is the maximum offset used in a + insert method since the last reset. + + The argument deltas is an array of size num_actual_offsets. It contains + the variable delta for each offset. A free variable x:i in an expression offset t:j is mapped + to the variable x+delta[i]. + */ + void apply(unsigned num_actual_offsets, unsigned const * deltas, expr_offset const & n, expr_ref & result) { + apply(num_actual_offsets, deltas, n, expr_offset(0, 0), expr_offset(0, 0), result); + } + + /** + \brief Similar to the previous method, but occurrences of s in n are substituted by t. + If s != expr_offset(0,0), then the cache is reset before and after the execution of this procedure. + */ + void apply(unsigned num_actual_offsets, unsigned const* deltas, expr_offset const & n, expr_offset const & s, expr_offset const & t, expr_ref & result); + + void apply(expr * n, expr_ref & result) { + unsigned deltas[1] = { 0 }; + apply(1, deltas, expr_offset(n, 0), result); + } + + // ----------------------------------- + // + // Debugging + // + // ----------------------------------- + + /** + \brief Dump the current substitution (for debugging purposes). + */ + void display(std::ostream & out, unsigned num_actual_offsets, unsigned const * deltas); + + /** + \brief Dump the current substitution without normalizing expressions (for debugging purposes). + */ + void display(std::ostream & out); + + + // ----------------------------------- + // + // Compare terms modulo a substitution + // + // ----------------------------------- + bool compare(expr_offset t1, expr_offset t2); + +}; + +#endif diff --git a/lib/substitution_tree.cpp b/lib/substitution_tree.cpp new file mode 100644 index 000000000..6e44c68f2 --- /dev/null +++ b/lib/substitution_tree.cpp @@ -0,0 +1,899 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + substitution_tree.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-02-04. + +Revision History: + +--*/ +#include"substitution_tree.h" +#include"ast_pp.h" +#include"ast_smt2_pp.h" + +/** + \brief Return the next available register. +*/ +unsigned substitution_tree::next_reg() { + while(true) { + unsigned curr = m_next_reg; + if (curr > m_max_reg) + m_max_reg = curr; + m_next_reg++; + if (curr >= m_used_regs.size() || !m_used_regs.get(curr)) + return curr; + } +} + +inline void substitution_tree::push(svector & sv, subst const & s) { + sv.push_back(s); + m_manager.inc_ref(s.first); + m_manager.inc_ref(s.second); +} + +inline expr * substitution_tree::get_reg_value(unsigned ridx) { + return m_registers.get(ridx, 0); +} + +inline void substitution_tree::set_reg_value(unsigned ridx, expr * e) { + m_registers.setx(ridx, e, 0); +} + +inline void substitution_tree::erase_reg_from_todo(unsigned ridx) { + SASSERT(m_registers[ridx]); + m_registers[ridx] = 0; + SASSERT(m_todo.contains(ridx)); + m_todo.erase(ridx); +} + +/** + \brief Linearize the expressions in the registers stored in m_todo. + Store the result in \c result. + + Example: + m_todo = { 3, 4 } + m_registers[3] = (f (g a)) + m_registers[4] = b + next_regs are 5 6 7 + + result: + #3 -> (f #5); #4 -> b; #5 -> (g #6); #6 -> a +*/ +void substitution_tree::linearize(svector & result) { + ptr_buffer new_args; + for (unsigned i = 0; i < m_todo.size(); i++) { + unsigned ireg_idx = m_todo[i]; + expr * n = get_reg_value(ireg_idx); + var * ireg = m_manager.mk_var(ireg_idx, m_manager.get_sort(n)); + if (is_var(n)) + push(result, subst(ireg, n)); + else { + SASSERT(is_app(n)); + app * new_app; + unsigned num = to_app(n)->get_num_args(); + if (num == 0) + new_app = to_app(n); + else { + for (unsigned j = 0; j < num; j++) { + unsigned oreg = next_reg(); + set_reg_value(oreg, to_app(n)->get_arg(j)); + m_todo.push_back(oreg); + sort * s = m_manager.get_sort(get_reg_value(oreg)); + new_args.push_back(m_manager.mk_var(oreg, s)); + } + new_app = m_manager.mk_app(to_app(n)->get_decl(), new_args.size(), new_args.c_ptr()); + new_args.reset(); + } + push(result, subst(ireg, new_app)); + } + } +} + +/** + \brief Process the pair in := (f t_1 ... t_n) and out := (f r_1 ... r_n), + where r_i's are variables (register ids), and t_i's are arbitrary expressions. + The r_i's are added to the m_todo list, and m_registers[r_i] is assigned to t_i. + + If save_set_registers == true, then r_i's are stored in m_to_reset. +*/ +void substitution_tree::process_args(app * in, app * out) { + CTRACE("subst_tree_bug", in->get_num_args() != out->get_num_args(), tout << mk_ismt2_pp(in, m_manager) << "\n" + << mk_ismt2_pp(out, m_manager) << "\n";); + unsigned num = out->get_num_args(); + for (unsigned i = 0; i < num; i++) { + expr * in_arg = in->get_arg(i); + expr * out_arg = out->get_arg(i); + SASSERT(is_var(out_arg)); + unsigned oreg = to_var(out_arg)->get_idx(); + set_reg_value(oreg, in_arg); + m_todo.push_back(oreg); + } +} + +/** + \brief Reset registers in m_todo at [old_size, m_todo.size()) +*/ +void substitution_tree::reset_registers(unsigned old_size) { + SASSERT(m_todo.size() >= old_size); + unsigned_vector::iterator it2 = m_todo.begin() + old_size; + unsigned_vector::iterator end2 = m_todo.end(); + for (; it2 != end2; ++it2) + m_registers[*it2] = 0; + m_todo.shrink(old_size); +} + +/** + \brief Return a measure on how compatible sv and the expressions to be processed are. +*/ +unsigned substitution_tree::get_compatibility_measure(svector const & sv) { + unsigned old_size = m_todo.size(); + unsigned measure = 0; + svector::const_iterator it = sv.begin(); + svector::const_iterator end = sv.end(); + for (; it != end; ++it) { + subst const & s = *it; + unsigned ireg = s.first->get_idx(); + expr * out = s.second; + expr * in = get_reg_value(ireg); + + if (is_var(out)) { + if (out == in) + measure += 1; + } + else { + SASSERT(is_app(out)); + if (in && is_app(in) && to_app(out)->get_decl() == to_app(in)->get_decl()) { + measure += 2; + process_args(to_app(in), to_app(out)); + } + } + } + reset_registers(old_size); + return measure; +} + +/** + \brief Find the child of r that is most compatible with the expressions stored + in the registers in m_todo. + + Return 0 if none of the children has any compatible substitution entry. +*/ +substitution_tree::node * substitution_tree::find_best_child(node * r) { + SASSERT(!r->m_leaf); +#ifdef Z3DEBUG + unsigned todo_size = m_todo.size(); +#endif + node * best_child = 0; + unsigned max_measure = 0; + node * curr_child = r->m_first_child; + while (curr_child) { + unsigned measure = get_compatibility_measure(curr_child->m_subst); + if (measure > max_measure) { + best_child = curr_child; + max_measure = measure; + } + curr_child = curr_child->m_next_sibling; + } + SASSERT(todo_size == m_todo.size()); + return best_child; +} + +/** + \brief Reset datastructures used to insert/erase elements from the substitution tree. +*/ +void substitution_tree::reset_compiler() { + m_todo.reset(); + m_used_regs.reset(); + m_next_reg = 1; // register 0 is reserved for input. + DEBUG_CODE({ + ptr_vector::iterator it = m_registers.begin(); + ptr_vector::iterator end = m_registers.end(); + for (; it != end; ++it) { + SASSERT(*it == 0); + } + }); +} + +/** + \brief Create a node with the linearization for all registers in todo. + Attach new_expr to it. +*/ +substitution_tree::node * substitution_tree::mk_node_for(expr * new_expr) { + node * n = alloc(node, true); + linearize(n->m_subst); + n->m_expr = new_expr; + m_manager.inc_ref(new_expr); + return n; +} + +/** + \brief Mark register ridx as used. +*/ +void substitution_tree::mark_used_reg(unsigned ridx) { + if (ridx >= m_used_regs.size()) + m_used_regs.resize(ridx+1); + m_used_regs.set(ridx); +} + +/** + \brief Mark (m_used_regs) all registers used in \c sv. +*/ +void substitution_tree::mark_used_regs(svector const & sv) { + svector::const_iterator it = sv.begin(); + svector::const_iterator end = sv.end(); + for (; it != end; ++it) { + subst const & s = *it; + mark_used_reg(s.first->get_idx()); + if (is_app(s.second)) { + unsigned num_args = to_app(s.second)->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * arg = to_app(s.second)->get_arg(i); + SASSERT(is_var(arg)); + mark_used_reg(to_var(arg)->get_idx()); + } + } + } +} + +/** + \brief Insert a new expression in the substitution tree. +*/ +void substitution_tree::insert(expr * new_expr) { + if (is_app(new_expr)) { + insert(to_app(new_expr)); + } + else { + SASSERT(is_var(new_expr)); + sort * s = to_var(new_expr)->get_sort(); + unsigned id = s->get_decl_id(); + if (id >= m_vars.size()) + m_vars.resize(id+1, 0); + if (m_vars[id] == 0) + m_vars[id] = alloc(var_ref_vector, m_manager); + var_ref_vector * v = m_vars[id]; + if (!v->contains(to_var(new_expr))) + v->push_back(to_var(new_expr)); + } +} + +/** + \brief Insert a new application in the substitution tree. +*/ +void substitution_tree::insert(app * new_expr) { + reset_compiler(); + set_reg_value(0, new_expr); + m_todo.push_back(0); + + func_decl * d = new_expr->get_decl(); + unsigned id = d->get_decl_id(); + + if (id >= m_roots.size()) + m_roots.resize(id+1, 0); + + if (!m_roots[id]) { + // there is no tree for the function symbol heading new_expr + m_roots[id] = mk_node_for(new_expr); + reset_registers(0); + m_size++; + return; + } + + node * r = m_roots[id]; + + while (true) { + m_compatible.reset(); + m_incompatible.reset(); + svector & sv = r->m_subst; + // separate sv in the set of compatible & incompatible instructions + svector::iterator it = sv.begin(); + svector::iterator end = sv.end(); + for (; it != end; ++it) { + subst & s = *it; + unsigned ireg = s.first->get_idx(); + expr * out = s.second; + expr * in = get_reg_value(ireg); + SASSERT(is_var(out) || is_app(out)); + if (is_var(out)) { + if (out == in) { + erase_reg_from_todo(ireg); + m_compatible.push_back(s); + } + else { + m_incompatible.push_back(s); + } + } + else { + if (in && is_app(in) && to_app(out)->get_decl() == to_app(in)->get_decl()) { + erase_reg_from_todo(ireg); + m_compatible.push_back(s); + process_args(to_app(in), to_app(out)); + } + else { + m_incompatible.push_back(s); + } + } + } + + // process m_compatible & m_incompatible + if (m_incompatible.empty()) { + if (m_todo.empty()) { + // nothing else to process + // new_expr is already in the substitution tree + SASSERT(r->m_leaf && r->m_expr == new_expr); + reset_registers(0); + return; + } + else { + mark_used_regs(r->m_subst); + node * best_child = find_best_child(r); + if (best_child == 0) { + // there is no compatible child + node * n = mk_node_for(new_expr); + n->m_next_sibling = r->m_first_child; + r->m_first_child = n; + reset_registers(0); + m_size++; + return; + } + else { + // continue with best_child + r = best_child; + } + } + } + else { + SASSERT(!m_compatible.empty()); + SASSERT(!m_incompatible.empty()); + + mark_used_regs(m_compatible); + + r->m_subst.swap(m_compatible); + + node * n = mk_node_for(new_expr); + + node * incomp = alloc(node, r->m_leaf); + incomp->m_subst.swap(m_incompatible); + if (r->m_leaf) { + incomp->m_expr = r->m_expr; + r->m_leaf = false; + } + else + incomp->m_first_child = r->m_first_child; + incomp->m_next_sibling = n; + + SASSERT(!r->m_leaf); + r->m_first_child = incomp; + reset_registers(0); + m_size++; + return; + } + } +} + +/** + \brief Return true if sv is fully compatible with the expressions in the registers in m_todo. +*/ +bool substitution_tree::is_fully_compatible(svector const & sv) { + unsigned old_size = m_todo.size(); + svector::const_iterator it = sv.begin(); + svector::const_iterator end = sv.end(); + for (; it != end; ++it) { + subst const & s = *it; + unsigned ireg = s.first->get_idx(); + expr * out = s.second; + expr * in = get_reg_value(ireg); + if (is_var(out)) { + if (out != in) { + reset_registers(old_size); + return false; + } + } + else { + if (!in || !is_app(in) || to_app(in)->get_decl() != to_app(out)->get_decl()) { + reset_registers(old_size); + return false; + } + process_args(to_app(in), to_app(out)); + } + } + reset_registers(old_size); + return true; +} + +/** + \brief Return a child of r that is fully compatible with the expressions in the registers in m_todo. +*/ +bool substitution_tree::find_fully_compatible_child(node * r, node * & prev, node * & child) { + SASSERT(!r->m_leaf); + prev = 0; + child = r->m_first_child; + while (child) { + if (is_fully_compatible(child->m_subst)) + return true; + prev = child; + child = child->m_next_sibling; + } + return false; +} + +inline bool substitution_tree::at_least_3_children(node * r) { + return !r->m_leaf && r->m_first_child->m_next_sibling && r->m_first_child->m_next_sibling->m_next_sibling; +} + +/** + \brief Remove expression from the substitution tree. + Do nothing, if n is not in the tree. +*/ +void substitution_tree::erase(expr * e) { + if (is_app(e)) + erase(to_app(e)); + else { + SASSERT(is_var(e)); + sort * s = to_var(e)->get_sort(); + unsigned id = s->get_decl_id(); + if (id >= m_vars.size() || m_vars[id] == 0) + return; + var_ref_vector * v = m_vars[id]; + v->erase(to_var(e)); + } +} + +/** + \brief Remove application from the substitution tree. + Do nothing, if n is not in the tree. +*/ +void substitution_tree::erase(app * e) { + func_decl * d = e->get_decl(); + unsigned id = d->get_decl_id(); + if (id >= m_roots.size() || !m_roots[id]) + return; + + reset_compiler(); + set_reg_value(0, e); + m_todo.push_back(0); + + node * r = m_roots[id]; + node * parent = 0; + node * prev = 0; + + while (true) { + svector & sv = r->m_subst; + svector::iterator it = sv.begin(); + svector::iterator end = sv.end(); + for (; it != end; ++it) { + subst & s = *it; + unsigned ireg = s.first->get_idx(); + expr * out = s.second; + expr * in = get_reg_value(ireg); + SASSERT(is_var(out) || is_app(out)); + if (is_var(out)) { + if (out != in) { + reset_registers(0); + return; // node is not in the substitution tree + } + erase_reg_from_todo(ireg); + } + else { + if (!in || !is_app(in) || to_app(out)->get_decl() != to_app(in)->get_decl()) { + reset_registers(0); + return; // node is not in the substitution tree + } + erase_reg_from_todo(ireg); + process_args(to_app(in), to_app(out)); + } + } + + if (m_todo.empty()) { + reset_registers(0); + SASSERT(r->m_expr == e); + if (parent == 0) { + delete_node(r); + m_roots[id] = 0; + } + else if (at_least_3_children(parent)) { + if (prev == 0) + parent->m_first_child = r->m_next_sibling; + else + prev->m_next_sibling = r->m_next_sibling; + delete_node(r); + } + else { + SASSERT(parent->m_first_child && parent->m_first_child->m_next_sibling && !parent->m_first_child->m_next_sibling->m_next_sibling); + node * other_child = prev ? prev : r->m_next_sibling; + SASSERT(other_child); + parent->m_subst.append(other_child->m_subst); + parent->m_leaf = other_child->m_leaf; + if (other_child->m_leaf) + parent->m_expr = other_child->m_expr; + else + parent->m_first_child = other_child->m_first_child; + delete_node(r); + dealloc(other_child); // Remark: I didn't use delete_node since its resources were sent to parent. + } + m_size --; + return; + } + else { + parent = r; + if (!find_fully_compatible_child(r, prev, r)) { + // node is not in the substitution tree + reset_registers(0); + return; + } + // continue with fully compatible child + } + } +} + +void substitution_tree::delete_node(node * n) { + ptr_buffer todo; + SASSERT(todo.empty()); + todo.push_back(n); + while (!todo.empty()) { + node * n = todo.back(); + todo.pop_back(); + svector::iterator it2 = n->m_subst.begin(); + svector::iterator end2 = n->m_subst.end(); + for (; it2 != end2; ++it2) { + m_manager.dec_ref(it2->first); + m_manager.dec_ref(it2->second); + } + if (n->m_leaf) + m_manager.dec_ref(n->m_expr); + else { + node * c = n->m_first_child; + while (c) { + todo.push_back(c); + c = c->m_next_sibling; + } + } + dealloc(n); + } +} + +void substitution_tree::reset() { + ptr_vector::iterator it = m_roots.begin(); + ptr_vector::iterator end = m_roots.end(); + for (; it != end; ++it) { + if (*it) + delete_node(*it); + } + m_roots.reset(); + std::for_each(m_vars.begin(), m_vars.end(), delete_proc()); + m_vars.reset(); + m_size = 0; +} + +void substitution_tree::display(std::ostream & out, subst const & s) const { + out << "r!" << s.first->get_idx() << " -> "; + if (is_app(s.second)) { + unsigned num = to_app(s.second)->get_num_args(); + if (num == 0) + out << to_app(s.second)->get_decl()->get_name(); + else { + out << "(" << to_app(s.second)->get_decl()->get_name(); + for (unsigned i = 0; i < num; i++) + out << " r!" << to_var(to_app(s.second)->get_arg(i))->get_idx(); + out << ")"; + } + } + else { + out << mk_pp(s.second, m_manager); + } +} + +void substitution_tree::display(std::ostream & out, svector const & sv) const { + svector::const_iterator it = sv.begin(); + svector::const_iterator end = sv.end(); + for (bool first = true; it != end; ++it, first = false) { + subst const & s = *it; + if (!first) + out << "; "; + display(out, s); + } +} + +void substitution_tree::display(std::ostream & out, node * n, unsigned delta) const { + for (unsigned i = 0; i < delta; i++) + out << " "; + display(out, n->m_subst); + if (n->m_leaf) { + pp_params p; + p.m_pp_single_line = true; + out << " ==> "; + ast_pp(out, n->m_expr, m_manager, p); + out << "\n"; + } + else { + out << "\n"; + node * c = n->m_first_child; + while (c) { + display(out, c, delta+1); + c = c->m_next_sibling; + } + } +} + +bool substitution_tree::backtrack() { + while (!m_bstack.empty()) { + TRACE("st", tout << "backtracking...\n";); + m_subst->pop_scope(); + + node * n = m_bstack.back(); + if (n->m_next_sibling) { + m_bstack.back() = n->m_next_sibling; + return true; + } + m_bstack.pop_back(); + } + return false; +} + +inline expr_offset substitution_tree::find(expr_offset p) { + TRACE("substitution_tree_bug", tout << "find...\n";); + while (is_var(p.get_expr())) { + TRACE("substitution_tree_bug", tout << mk_pp(p.get_expr(), m_manager) << " " << p.get_offset() << "\n";); + if (!m_subst->find(to_var(p.get_expr()), p.get_offset(), p)) + return p; + } + return p; +} + +template +bool substitution_tree::bind_var(var * v, unsigned offset, expr_offset const & p) { + TRACE("st", tout << "bind_var: " << mk_pp(v, m_manager) << " " << offset << "\n" << + mk_pp(p.get_expr(), m_manager) << " " << p.get_offset() << "\n";); + if (Mode == STV_INST && offset == m_st_offset) { + SASSERT(!is_var(p.get_expr()) || p.get_offset() != m_reg_offset); + if (is_var(p.get_expr()) && p.get_offset() == m_in_offset) { + m_subst->insert(p, expr_offset(v, offset)); + return true; + } + return false; + } + if (Mode == STV_GEN && offset == m_in_offset) { + SASSERT(!is_var(p.get_expr()) || p.get_offset() != m_reg_offset); + if (is_var(p.get_expr()) && p.get_offset() == m_st_offset) { + m_subst->insert(p, expr_offset(v, offset)); + return true; + } + return false; + } + m_subst->insert(v, offset, p); + TRACE("st_bug", tout << "substitution updated\n"; m_subst->display(tout);); + return true; +} + +template +bool substitution_tree::unify_match(expr_offset p1, expr_offset p2) { + svector & todo = m_visit_todo; + todo.reset(); + todo.push_back(entry(p1, p2)); + while (!todo.empty()) { + entry const & e = todo.back(); + p1 = find(e.first); + p2 = find(e.second); + todo.pop_back(); + if (p1 != p2) { + expr * n1 = p1.get_expr(); + expr * n2 = p2.get_expr(); + SASSERT(!is_quantifier(n1)); + SASSERT(!is_quantifier(n2)); + bool v1 = is_var(n1); + bool v2 = is_var(n2); + TRACE("st", + tout << "n1: " << mk_pp(n1, m_manager) << " " << p1.get_offset() << "\n"; + tout << "n2: " << mk_pp(n2, m_manager) << " " << p2.get_offset() << "\n";); + if (v1 && v2) { + if (p2.get_offset() == m_reg_offset) + std::swap(p1, p2); + if (!bind_var(to_var(p1.get_expr()), p1.get_offset(), p2)) + return false; + } + else if (v1) { + if (!bind_var(to_var(n1), p1.get_offset(), p2)) + return false; + } + else if (v2) { + if (!bind_var(to_var(n2), p2.get_offset(), p1)) + return false; + } + else { + app * a1 = to_app(n1); + app * a2 = to_app(n2); + unsigned off1 = p1.get_offset(); + unsigned off2 = p2.get_offset(); + if (a1->get_decl() != a2->get_decl() || a1->get_num_args() != a2->get_num_args()) + return false; + unsigned j = a1->get_num_args(); + while (j > 0) { + --j; + entry new_e(expr_offset(a1->get_arg(j), off1), + expr_offset(a2->get_arg(j), off2)); + todo.push_back(new_e); + } + } + } + } + return true; +} + +template +bool substitution_tree::visit_vars(expr * e, st_visitor & st) { + if (m_vars.empty()) + return true; // continue + sort * s = m_manager.get_sort(e); + unsigned s_id = s->get_decl_id(); + if (s_id < m_vars.size()) { + var_ref_vector * v = m_vars[s_id]; + if (v && !v->empty()) { + unsigned sz = v->size(); + for (unsigned i = 0; i < sz; i++) { + var * curr = v->get(i); + m_subst->push_scope(); + if (unify_match(expr_offset(curr, m_st_offset), expr_offset(e, m_in_offset))) { + if (Mode != STV_UNIF || m_subst->acyclic()) { + if (!st(curr)) { + m_subst->pop_scope(); + return false; // stop + } + } + } + m_subst->pop_scope(); + } + } + } + return true; // continue +} + +template +bool substitution_tree::visit(svector const & sv) { + svector::const_iterator it = sv.begin(); + svector::const_iterator end = sv.end(); + for (; it != end; ++it) { + subst const & s = *it; + TRACE("st", tout << "processing subst:\n"; display(tout, s); tout << "\n";); + var * rin = s.first; + expr * out = s.second; + expr_offset p1(rin, m_reg_offset); + expr_offset p2(out, is_var(out) ? m_st_offset : m_reg_offset); + if (!unify_match(p1, p2)) + return false; + } + return true; +} + +template +bool substitution_tree::visit(expr * e, st_visitor & st, node * r) { + m_bstack.reset(); + m_bstack.push_back(r); + m_subst->push_scope(); + m_subst->insert(static_cast(0), m_reg_offset, expr_offset(e, m_in_offset)); + + while (true) { + node * n = m_bstack.back(); + TRACE("st", tout << "push scope...\n";); + m_subst->push_scope(); + TRACE("st", tout << "processing node:\n"; display(tout, n->m_subst); tout << "\n";); + if (visit(n->m_subst)) { + if (n->m_leaf) { + // if searching for unifiers and the substitution is cyclic, then backtrack. + if (Mode == STV_UNIF && !m_subst->acyclic()) { + if (!backtrack()) + break; + } + else { + TRACE("st_bug", tout << "found match:\n"; m_subst->display(tout); tout << "m_subst: " << m_subst << "\n";); + if (!st(n->m_expr)) + return false; + if (!backtrack()) + break; + } + } + else { + m_bstack.push_back(n->m_first_child); + } + } + else if (!backtrack()) + break; + } + while (!m_bstack.empty()) { + m_subst->pop_scope(); + m_bstack.pop_back(); + } + m_subst->pop_scope(); + return true; +} + +template +void substitution_tree::visit(expr * e, st_visitor & st, unsigned in_offset, unsigned st_offset, unsigned reg_offset) { + m_in_offset = in_offset; + m_st_offset = st_offset; + m_reg_offset = reg_offset; + + m_subst = &(st.get_substitution()); + m_subst->reserve_vars(get_approx_num_regs()); + + if (visit_vars(e, st)) { + if (is_app(e)) { + func_decl * d = to_app(e)->get_decl(); + unsigned id = d->get_decl_id(); + node * r = m_roots.get(id, 0); + if (r) + visit(e, st, r); + } + else { + SASSERT(is_var(e)); + ptr_vector::iterator it = m_roots.begin(); + ptr_vector::iterator end = m_roots.end(); + for (; it != end; ++it) { + node * r = *it; + if (r != 0) { + var * v = r->m_subst[0].first; + if (v->get_sort() == to_var(e)->get_sort()) + if (!visit(e, st, r)) + break; + } + } + } + } +} + +void substitution_tree::unify(expr * e, st_visitor & v, unsigned in_offset, unsigned st_offset, unsigned reg_offset) { + visit(e, v, in_offset, st_offset, reg_offset); +} + +void substitution_tree::inst(expr * e, st_visitor & v, unsigned in_offset, unsigned st_offset, unsigned reg_offset) { + visit(e, v, in_offset, st_offset, reg_offset); +} + +void substitution_tree::gen(expr * e, st_visitor & v, unsigned in_offset, unsigned st_offset, unsigned reg_offset) { + visit(e, v, in_offset, st_offset, reg_offset); +} + +void substitution_tree::display(std::ostream & out) const { + out << "substitution tree:\n"; + ptr_vector::const_iterator it = m_roots.begin(); + ptr_vector::const_iterator end = m_roots.end(); + for (; it != end; ++it) + if (*it) + display(out, *it, 0); + bool found_var = false; + ptr_vector::const_iterator it2 = m_vars.begin(); + ptr_vector::const_iterator end2 = m_vars.end(); + for (; it2 != end2; ++it2) { + var_ref_vector * v = *it2; + if (v == 0) + continue; // m_vars may contain null pointers. See substitution_tree::insert. + unsigned num = v->size(); + for (unsigned i = 0; i < num; i++) { + if (!found_var) { + found_var = true; + out << "vars: "; + } + out << mk_pp(v->get(i), m_manager) << " "; + } + } + if (found_var) + out << "\n"; +} + + +substitution_tree::substitution_tree(ast_manager & m): + m_manager(m), + m_max_reg(0), + m_size(0) { +} + +substitution_tree::~substitution_tree() { + reset(); +} diff --git a/lib/substitution_tree.h b/lib/substitution_tree.h new file mode 100644 index 000000000..caa3d37cb --- /dev/null +++ b/lib/substitution_tree.h @@ -0,0 +1,148 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + substitution_tree.h + +Abstract: + + Substitution Trees + +Author: + + Leonardo de Moura (leonardo) 2008-02-03. + +Revision History: + +--*/ +#ifndef _SUBSTITUTION_TREE_H_ +#define _SUBSTITUTION_TREE_H_ + +#include"ast.h" +#include"substitution.h" + +/** + \brief Substitution tree visitor. +*/ +class st_visitor { +protected: + substitution & m_subst; +public: + st_visitor(substitution & s):m_subst(s) {} + virtual ~st_visitor() {} + substitution & get_substitution() { return m_subst; } + virtual bool operator()(expr * e) { return true; } +}; + +/** + \brief Substitution tree term index. +*/ +class substitution_tree { + + typedef std::pair subst; + + struct node { + bool m_leaf; + svector m_subst; + node * m_next_sibling; + union { + node * m_first_child; + expr * m_expr; + }; + node(bool leaf):m_leaf(leaf), m_next_sibling(0), m_first_child(0) {} + }; + + ast_manager & m_manager; + ptr_vector m_roots; + unsigned m_max_reg; + ptr_vector m_registers; + unsigned m_size; + ptr_vector m_vars; // mapping from decl_id to var_ref_vector + + // Compilation time fields + unsigned m_next_reg; + bit_vector m_used_regs; + unsigned_vector m_todo; + svector m_compatible; + svector m_incompatible; + + // Execution time fields + substitution * m_subst; + ptr_vector m_bstack; + unsigned m_in_offset; + unsigned m_st_offset; + unsigned m_reg_offset; + typedef std::pair entry; + svector m_visit_todo; + + unsigned next_reg(); + void push(svector & sv, subst const & s); + expr * get_reg_value(unsigned ridx); + void set_reg_value(unsigned ridx, expr * e); + void erase_reg_from_todo(unsigned ridx); + + void linearize(svector & result); + void process_args(app * in, app * out); + void reset_registers(unsigned old_size); + unsigned get_compatibility_measure(svector const & sv); + node * find_best_child(node * r); + void reset_compiler(); + node * mk_node_for(expr * new_expr); + void mark_used_reg(unsigned ridx); + void mark_used_regs(svector const & sv); + + bool is_fully_compatible(svector const & sv); + bool find_fully_compatible_child(node * r, node * & prev, node * & child); + static bool at_least_3_children(node * r); + void delete_node(node * n); + + void display(std::ostream & out, subst const & s) const; + void display(std::ostream & out, svector const & sv) const; + void display(std::ostream & out, node * n, unsigned delta) const; + + enum st_visit_mode { + STV_UNIF, + STV_INST, + STV_GEN + }; + + expr_offset find(expr_offset p); + bool backtrack(); + + template + bool bind_var(var * v, unsigned offset, expr_offset const & p); + template + bool unify_match(expr_offset p1, expr_offset p2); + template + bool visit_vars(expr * e, st_visitor & st); + template + bool visit(svector const & s); + template + bool visit(expr * e, st_visitor & st, node * r); + template + void visit(expr * e, st_visitor & st, unsigned in_offset, unsigned st_offset, unsigned reg_offset); + +public: + substitution_tree(ast_manager & m); + ~substitution_tree(); + + void insert(app * n); + void insert(expr * n); + void erase(app * n); + void erase(expr * n); + void reset(); + + bool empty() const { return m_size == 0; } + + void unify(expr * e, st_visitor & v, unsigned in_offset = 0, unsigned st_offset = 1, unsigned reg_offset = 2); + void inst(expr * e, st_visitor & v, unsigned in_offset = 0, unsigned st_offset = 1, unsigned reg_offset = 2); + void gen(expr * e, st_visitor & v, unsigned in_offset = 0, unsigned st_offset = 1, unsigned reg_offset = 2); + + unsigned get_approx_num_regs() const { return m_max_reg + 1; } + + void display(std::ostream & out) const; +}; + +#endif /* _SUBSTITUTION_TREE_H_ */ + diff --git a/lib/symbol.cpp b/lib/symbol.cpp new file mode 100644 index 000000000..895c26b67 --- /dev/null +++ b/lib/symbol.cpp @@ -0,0 +1,166 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + symbol.cpp + +Abstract: + + Lisp-like symbols. + +Author: + + Leonardo de Moura (leonardo) 2006-09-11. + +Revision History: + +--*/ +#include"symbol.h" +#include"str_hashtable.h" +#include"region.h" +#include"string_buffer.h" +#include"z3_omp.h" + +symbol symbol::m_dummy(TAG(void*, static_cast(0), 2)); +const symbol symbol::null; + +/** + \brief Symbol table manager. It stores the symbol strings created at runtime. +*/ +class internal_symbol_table { + region m_region; //!< Region used to store symbol strings. + str_hashtable m_table; //!< Table of created symbol strings. +public: + + char const * get_str(char const * d) { + char * result; + #pragma omp critical (cr_symbol) + { + char * r_d = const_cast(d); + str_hashtable::entry * e; + if (m_table.insert_if_not_there_core(r_d, e)) { + // new entry + size_t l = strlen(d); + // store the hash-code before the string + size_t * mem = static_cast(m_region.allocate(l + 1 + sizeof(size_t))); + *mem = e->get_hash(); + mem++; + result = reinterpret_cast(mem); + memcpy(result, d, l+1); + // update the entry with the new ptr. + e->set_data(result); + } + else { + result = e->get_data(); + } + SASSERT(m_table.contains(result)); + } + return result; + } +}; + +internal_symbol_table* g_symbol_table = 0; + +void initialize_symbols() { + if (!g_symbol_table) { + g_symbol_table = alloc(internal_symbol_table); + } +} + +void finalize_symbols() { + dealloc(g_symbol_table); + g_symbol_table = 0; +} + +symbol::symbol(char const * d) { + if (d == 0) + m_data = 0; + else + m_data = g_symbol_table->get_str(d); +} + +symbol & symbol::operator=(char const * d) { + m_data = g_symbol_table->get_str(d); + return *this; +} + +std::string symbol::str() const { + SASSERT(!is_marked()); + if (GET_TAG(m_data) == 0) { + if (m_data) + return m_data; + else + return ""; + } + else { + string_buffer<128> buffer; + buffer << "k!" << UNBOXINT(m_data); + return buffer.c_str(); + } +} + +bool symbol::contains(char ch) const { + SASSERT(!is_marked()); + if (GET_TAG(m_data) == 0) { + return strchr(m_data, ch) != 0; + } + else { + return false; + } +} + +unsigned symbol::size() const { + SASSERT(!is_marked()); + if (GET_TAG(m_data) == 0) { + return static_cast(strlen(m_data)); + } + else { + unsigned v = UNBOXINT(m_data); + unsigned sz = 4; + v = v >> 1; + while (v > 0) { + sz++; + v = v >> 1; + } + return sz; + } +} + +bool lt(symbol const & s1, symbol const & s2) { + if (s1 == s2) + return false; + if (s1.is_numerical()) { + if (!s2.is_numerical()) + return true; // numeral symbols are smaller than non-numerical ones. + return s1.get_num() < s2.get_num(); + } + if (s2.is_numerical()) { + SASSERT(!s1.is_numerical()); + return false; + } + SASSERT(!s1.is_numerical() && !s2.is_numerical()); + char const * str1 = s1.bare_str(); + char const * str2 = s2.bare_str(); + while (true) { + if (*str1 < *str2) { + return true; + } + else if (*str1 == *str2) { + str1++; + str2++; + if (!*str1) { + SASSERT(*str2); // the strings can't be equal. + return true; + } + if (!*str2) { + SASSERT(*str1); // the strings can't be equal. + return false; + } + } + else { + SASSERT(*str1 > *str2); + return false; + } + } +} diff --git a/lib/symbol.h b/lib/symbol.h new file mode 100644 index 000000000..5da44c52c --- /dev/null +++ b/lib/symbol.h @@ -0,0 +1,150 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + symbol.h + +Abstract: + + Lisp-like symbols. + +Author: + + Leonardo de Moura (leonardo) 2006-09-11. + +Revision History: + +--*/ +#ifndef _SYMBOL_H_ +#define _SYMBOL_H_ +#include +#include + +#include"util.h" +#include"tptr.h" +#include"string_buffer.h" + +template +class symbol_table; + +class symbol { + char const * m_data; + + template + friend class symbol_table; + + explicit symbol(void const * data): + m_data(reinterpret_cast(data)) { + } + bool is_marked() const { + return GET_TAG(m_data) > 1; + } + static symbol mark(symbol s) { + SASSERT(!s.is_marked()); + return symbol(TAG(void *, UNTAG(void *, s.m_data), GET_TAG(s.m_data) + 2)); + } + static symbol unmark(symbol s) { + SASSERT(s.is_marked()); + return symbol(TAG(void *, UNTAG(void *, s.m_data), GET_TAG(s.m_data) - 2)); + } + static symbol m_dummy; +public: + symbol(): + m_data(0) { + } + explicit symbol(char const * d); + explicit symbol(unsigned idx): + m_data(BOXTAGINT(char const *, idx, 1)) { + SASSERT(idx < (SIZE_MAX >> PTR_ALIGNMENT)); + } + static symbol dummy() { return m_dummy; } + static const symbol null; + symbol & operator=(char const * d); + friend bool operator==(symbol const & s1, symbol const & s2) { return s1.m_data == s2.m_data; } + friend bool operator!=(symbol const & s1, symbol const & s2) { return s1.m_data != s2.m_data; } + bool is_numerical() const { return GET_TAG(m_data) == 1; } + unsigned int get_num() const { SASSERT(is_numerical()); return UNBOXINT(m_data); } + std::string str() const; + friend bool operator==(symbol const & s1, char const * s2) { + if (s1.m_data == 0 && s2 == 0) + return true; + if (s1.m_data == 0 || s2 == 0) + return false; + if (!s1.is_numerical()) + return strcmp(s1.bare_str(), s2) == 0; + return s1.str() == s2; + } + friend bool operator!=(symbol const & s1, char const * s2) { return !operator==(s1, s2); } + void const * c_ptr() const { return m_data; } + // Low level function. + // It is the inverse of c_ptr(). + // It was made public to simplify the implementation of the C API. + static symbol mk_symbol_from_c_ptr(void const * ptr) { + symbol s(ptr); + return s; + } + unsigned hash() const { + if (m_data == 0) return 0x9e3779d9; + else if (is_numerical()) return get_num(); + else return static_cast(reinterpret_cast(m_data)[-1]); + } + bool contains(char c) const; + unsigned size() const; + char const * bare_str() const { SASSERT(!is_numerical()); return is_numerical() ? "" : m_data; } + friend std::ostream & operator<<(std::ostream & target, symbol s) { + SASSERT(!s.is_marked()); + if (GET_TAG(s.m_data) == 0) { + if (s.m_data) { + target << s.m_data; + } + else { + target << "null"; + } + } + else { + target << "k!" << UNBOXINT(s.m_data); + } + return target; + } + template + friend string_buffer & operator<<(string_buffer & target, symbol s) { + SASSERT(!s.is_marked()); + if (GET_TAG(s.m_data) == 0) { + if (s.m_data) { + target << s.m_data; + } + else { + target << "null"; + } + } + else { + target << "k!" << UNBOXINT(s.m_data); + } + return target; + } +}; + +struct symbol_hash_proc { + unsigned operator()(symbol const & s) const { + return s.hash(); + } +}; + +struct symbol_eq_proc { + bool operator()(symbol const & s1, symbol const & s2) const { + return s1 == s2; + } +}; + +void initialize_symbols(); +void finalize_symbols(); + +// total order on symbols... I did not overloaded '<' to avoid misunderstandings. +// numerical symbols are smaller than non numerical symbols. +// two numerical symbols are compared using get_num. +// two non-numerical symbols are compared using string comparison. +bool lt(symbol const & s1, symbol const & s2); + +#endif /* _SYMBOL_H_ */ + diff --git a/lib/symbol_table.h b/lib/symbol_table.h new file mode 100644 index 000000000..8f2cdc631 --- /dev/null +++ b/lib/symbol_table.h @@ -0,0 +1,213 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + symbol_table.h + +Abstract: + + Symbol table for parsing. + +Author: + + Leonardo de Moura (leonardo) 2006-09-19. + +Revision History: + +--*/ +#ifndef _SYMBOL_TABLE_H_ +#define _SYMBOL_TABLE_H_ +#include"vector.h" +#include"hashtable.h" +#include"hash.h" +#include"symbol.h" + +/** + \brief Quick & Dirty symbol table. +*/ +template +class symbol_table { + struct key_data { + symbol m_key; + T m_data; + + key_data() { + } + + explicit key_data(symbol k): + m_key(k) { + } + + key_data(symbol k, const T & d): + m_key(k), + m_data(d) { + } + }; + + struct key_data_hash_proc { + unsigned operator()(const key_data & k) const { + return k.m_key.hash(); + } + }; + + struct key_data_eq_proc { + bool operator()(const key_data & k1, const key_data & k2) const { + return k1.m_key == k2.m_key; + } + }; + + struct hash_entry { + typedef key_data data; + key_data m_data; + + hash_entry() { + SASSERT(m_data.m_key == symbol::null); + } + + unsigned get_hash() const { + return m_data.m_key.hash(); + } + + bool is_free() const { + return m_data.m_key == symbol::null; + } + + bool is_deleted() const { + return m_data.m_key == symbol::dummy(); + } + + bool is_used() const { + return !is_free() && !is_deleted(); + } + + key_data & get_data() { + return m_data; + } + + const key_data & get_data() const { + return m_data; + } + + void set_data(const key_data & d) { + m_data = d; + } + + static void set_hash(unsigned h) { + } + + void mark_as_deleted() { + m_data.m_key = symbol::dummy(); + } + + void mark_as_free() { + m_data.m_key = symbol::null; + } + }; + + typedef core_hashtable sym_table; + typedef vector trail_stack; + sym_table m_sym_table; + trail_stack m_trail_stack; + int_vector m_trail_lims; + +public: + void reset() { + m_sym_table.reset(); + m_trail_stack.reset(); + m_trail_lims.reset(); + } + + bool find(symbol key, T & result) const { + key_data dummy(key); + hash_entry * e = m_sym_table.find_core(dummy); + if (e == 0) { + return false; + } + result = e->get_data().m_data; + return true; + } + + bool contains(symbol key) const { + return m_sym_table.contains(key_data(key)); + } + + unsigned get_scope_level() const { + return m_trail_lims.size(); + } + + void insert(symbol key, const T & data) { + if (get_scope_level() > 0) { + key_data dummy(key); + hash_entry * e = m_sym_table.find_core(dummy); + if (e != 0) { + m_trail_stack.push_back(e->m_data); + e->m_data.m_data = data; + return; + } + else { + m_trail_stack.push_back(dummy); + key_data & new_entry = m_trail_stack.back(); + new_entry.m_key = symbol::mark(new_entry.m_key); + m_sym_table.insert(key_data(key, data)); + } + } + else { + m_sym_table.insert(key_data(key, data)); + } + } + + void begin_scope() { + m_trail_lims.push_back(m_trail_stack.size()); + } + + void end_scope() { + unsigned old_size = m_trail_lims.back(); + m_trail_lims.pop_back(); + unsigned curr_size = m_trail_stack.size(); + SASSERT(old_size <= curr_size); + for (unsigned i = old_size; i < curr_size; i++) { + key_data & curr_entry = m_trail_stack.back(); + symbol key = curr_entry.m_key; + if (key.is_marked()) { + curr_entry.m_key = symbol::unmark(key); + m_sym_table.erase(curr_entry); + } + else { + m_sym_table.insert(curr_entry); + } + m_trail_stack.pop_back(); + } + SASSERT(m_trail_stack.size() == old_size); + } + + void append(symbol_table const& other) { + typename sym_table::iterator it = other.m_sym_table.begin(); + typename sym_table::iterator end = other.m_sym_table.end(); + + for (; it != end; ++it) { + insert((*it).m_key, (*it).m_data); + } + } + + void get_range(vector& range) const { + typename sym_table::iterator it = m_sym_table.begin(); + typename sym_table::iterator end = m_sym_table.end(); + + for (; it != end; ++it) { + range.push_back((*it).m_data); + } + } + + void get_dom(svector& dom) const { + typename sym_table::iterator it = m_sym_table.begin(); + typename sym_table::iterator end = m_sym_table.end(); + + for (; it != end; ++it) { + dom.push_back((*it).m_key); + } + } +}; + +#endif /* _SYMBOL_TABLE_H_ */ + diff --git a/lib/symmetry_reduce_tactic.cpp b/lib/symmetry_reduce_tactic.cpp new file mode 100644 index 000000000..5ca1dbcbf --- /dev/null +++ b/lib/symmetry_reduce_tactic.cpp @@ -0,0 +1,657 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + symmetry_reduce.cpp + +Abstract: + + Add symmetry breaking predicates to assertion sets. + +Author: + + Nikolaj (nbjorner) 2011-05-31 + +Notes: + + This is a straight-forward and literal + adaption of the algorithms proposed for veriT. + +--*/ +#include"tactical.h" +#include"assertion_set.h" +#include"for_each_expr.h" +#include"map.h" +#include"expr_replacer.h" +#include"rewriter_def.h" +#include"ast_pp.h" + +class symmetry_reduce_tactic : public tactic { + class imp; + imp * m_imp; +public: + symmetry_reduce_tactic(ast_manager & m); + + virtual tactic * translate(ast_manager & m) { + return alloc(symmetry_reduce_tactic, m); + } + + virtual ~symmetry_reduce_tactic(); + + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core); + virtual void cleanup(); +}; + +class ac_rewriter { + ast_manager& m_manager; +public: + ac_rewriter(ast_manager& m): m_manager(m) {} + + br_status mk_app_core(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + if ((f->is_associative() && f->is_commutative()) || + m_manager.is_distinct(f)) { + ptr_buffer buffer; + buffer.append(num_args, args); + std::sort(buffer.begin(), buffer.end(), ast_lt_proc()); + bool change = false; + for (unsigned i = 0; !change && i < num_args; ++i) { + change = (args[i] != buffer[i]); + } + if (change) { + result = m().mk_app(f, num_args, buffer.begin()); + return BR_DONE; + } + } + else if (f->is_commutative() && num_args == 2 && args[0]->get_id() > args[1]->get_id()) { + expr* args2[2] = { args[1], args[0] }; + result = m().mk_app(f, num_args, args2); + return BR_DONE; + } + return BR_FAILED; + } + + void mk_app(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + if (mk_app_core(f, num_args, args, result) == BR_FAILED) + result = m().mk_app(f, num_args, args); + } +private: + ast_manager& m() const { return m_manager; } +}; + + +struct ac_rewriter_cfg : public default_rewriter_cfg { + ac_rewriter m_r; + bool rewrite_patterns() const { return false; } + bool flat_assoc(func_decl * f) const { return false; } + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + result_pr = 0; + return m_r.mk_app_core(f, num, args, result); + } + ac_rewriter_cfg(ast_manager & m):m_r(m) {} +}; + +class ac_rewriter_star : public rewriter_tpl { + ac_rewriter_cfg m_cfg; +public: + ac_rewriter_star(ast_manager & m): + rewriter_tpl(m, false, m_cfg), + m_cfg(m) {} +}; + +template class rewriter_tpl; + +class symmetry_reduce_tactic::imp { + typedef ptr_vector permutation; + typedef vector permutations; + typedef ptr_vector term_set; + typedef obj_map app_map; + typedef u_map > inv_app_map; + ast_manager& m_manager; + ac_rewriter_star m_rewriter; + scoped_ptr m_replace; + + ast_manager& m() const { return m_manager; } +public: + imp(ast_manager& m) : m_manager(m), m_rewriter(m) { + m_replace = mk_default_expr_replacer(m); + } + + ~imp() {} + + void operator()(goal & g) { + if (g.inconsistent()) + return; + tactic_report report("symmetry-reduce", g); + vector > P; + expr_ref fml(m()); + to_formula(g, fml); + app_map occs; + compute_occurrences(fml, occs); + find_candidate_permutations(fml, occs, P); + if (P.empty()) { + return; + } + term_set T, cts; + unsigned num_sym_break_preds = 0; + for (unsigned i = 0; i < P.size(); ++i) { + term_set& consts = P[i]; + if (invariant_by_permutations(fml, consts)) { + cts.reset(); + select_terms(fml, consts, T); + while (!T.empty() && cts.size() < consts.size()) { + app* t = select_most_promising_term(fml, T, cts, consts, occs); + T.erase(t); + compute_used_in(t, cts, consts); + app* c = select_const(consts, cts); + if (!c) break; + cts.push_back(c); + expr* mem = mk_member(t, cts); + g.assert_expr(mem); + num_sym_break_preds++; + TRACE("symmetry_reduce", tout << "member predicate: " << mk_pp(mem, m()) << "\n";); + fml = m().mk_and(fml.get(), mem); + normalize(fml); + } + } + } + report_tactic_progress(":num-symmetry-breaking ", num_sym_break_preds); + } + +private: + void to_formula(goal const & g, expr_ref& fml) { + ptr_vector conjs; + for (unsigned i = 0; i < g.size(); ++i) { + conjs.push_back(g.form(i)); + } + fml = m().mk_and(conjs.size(), conjs.c_ptr()); + normalize(fml); + } + + // find candidate permutations + void find_candidate_permutations(expr* fml, app_map const& occs, permutations& P) { + app_map coloring; + app_map depth; + inv_app_map inv_color; + unsigned num_occs; + compute_sort_colors(fml, coloring); + compute_max_depth(fml, depth); + merge_colors(occs, coloring); + merge_colors(depth, coloring); + // compute_siblings(fml, coloring); + compute_inv_app(coloring, inv_color); + + inv_app_map::iterator it = inv_color.begin(), end = inv_color.end(); + for (; it != end; ++it) { + if (it->m_value.size() < 2) { + continue; + } + VERIFY(occs.find(it->m_value[0], num_occs)); + if (num_occs < 2) { + continue; + } + bool is_const = true; + for (unsigned j = 0; is_const && j < it->m_value.size(); ++j) { + is_const = it->m_value[j]->get_num_args() == 0; + } + if (!is_const) { + continue; + } + P.push_back(it->m_value); + TRACE("symmetry_reduce", + for (unsigned i = 0; i < it->m_value.size(); ++i) { + tout << mk_pp(it->m_value[i], m()) << " "; + } + tout << "\n";); + } + } + + // + // refine coloring by taking most specific generalization. + // a |-> c1, b |-> c2 |-> c + // + struct u_pair { + unsigned m_first; + unsigned m_second; + u_pair(unsigned f, unsigned s) : m_first(f), m_second(s) {} + u_pair(): m_first(0), m_second(0) {} + + struct hash { + unsigned operator()(u_pair const& p) const { + return mk_mix(p.m_first, p.m_second, 23); + } + }; + struct eq { + bool operator()(u_pair const& p, u_pair const& q) const { + return p.m_first == q.m_first && p.m_second == q.m_second; + } + }; + }; + typedef map pair_map; + bool merge_colors(app_map const& colors1, app_map& colors2) { + pair_map recolor; + unsigned num_colors = 0, v1, v2, w, old_max = 0; + app_map::iterator it = colors2.begin(), end = colors2.end(); + for (; it != end; ++it) { + app* a = it->m_key; + v1 = it->m_value; + VERIFY(colors1.find(a, v2)); + if (recolor.find(u_pair(v1, v2), w)) { + it->m_value = w; + } + else { + it->m_value = num_colors; + recolor.insert(u_pair(v1, v2), num_colors++); + } + if (v1 > old_max) old_max = v1; + } + return num_colors > old_max + 1; + } + + class sort_colors { + ast_manager& m_manager; + app_map& m_app2sortid; + obj_map m_sort2id; + unsigned m_max_id; + + public: + sort_colors(ast_manager& m, app_map& app2sort): + m_manager(m), m_app2sortid(app2sort), m_max_id(0) {} + + void operator()(app* n) { + sort* s = m_manager.get_sort(n); + unsigned id; + if (!m_sort2id.find(s, id)) { + id = m_max_id++; + m_sort2id.insert(s, id); + } + m_app2sortid.insert(n, id); + } + void operator()(quantifier * n) {} + void operator()(var * n) {} + }; + + void compute_sort_colors(expr* fml, app_map& app2sortId) { + app2sortId.reset(); + sort_colors sc(m(), app2sortId); + for_each_expr(sc, fml); + } + + void compute_inv_app(app_map const& map, inv_app_map& inv_map) { + app_map::iterator it = map.begin(), end = map.end(); + for (; it != end; ++it) { + app* t = it->m_key; + unsigned n = it->m_value; + if (is_uninterpreted(t)) { + inv_app_map::entry* e = inv_map.insert_if_not_there2(n, ptr_vector()); + e->get_data().m_value.push_back(t); + } + } + } + bool is_uninterpreted(app* t) const { + return t->get_family_id() == null_family_id; + } + + // compute maximal depth of terms. + void compute_max_depth(expr* e, app_map& depth) { + ptr_vector todo; + unsigned_vector depths; + unsigned d, d1; + todo.push_back(e); + depths.push_back(0); + while (!todo.empty()) { + e = todo.back(); + d = depths.back(); + todo.pop_back(); + depths.pop_back(); + if (is_var(e)) { + // nothing + } + else if (is_quantifier(e)) { + todo.push_back(to_quantifier(e)->get_expr()); + depths.push_back(d+1); + } + else if (is_app(e)) { + app* a = to_app(e); + if (depth.find(a, d1) && d <= d1) { + continue; + } + depth.insert(a, d); + ++d; + for (unsigned i = 0; i < a->get_num_args(); ++i) { + todo.push_back(a->get_arg(i)); + depths.push_back(d); + } + } + else { + UNREACHABLE(); + } + + } + } + + // color nodes according to the function symbols they appear in + typedef obj_hashtable fun_set; + typedef obj_map app_parents; + + class parents { + app_parents m_use_funs; + public: + parents() {} + + app_parents const& get_parents() { return m_use_funs; } + + void operator()(app* n) { + func_decl* f; + unsigned sz = n->get_num_args(); + for (unsigned i = 0; i < sz; ++i) { + expr* e = n->get_arg(i); + if (is_app(e)) { + app_parents::obj_map_entry* entry = m_use_funs.insert_if_not_there2(to_app(e), 0); + if (!entry->get_data().m_value) entry->get_data().m_value = alloc(fun_set); + entry->get_data().m_value->insert(f); + } + } + } + void operator()(quantifier *n) {} + void operator()(var* n) {} + }; + void compute_parents(expr* e, app_map& parents) { + } + + typedef hashtable uint_set; + typedef obj_map app_siblings;; + + class siblings { + app_map const& m_colors; + app_siblings m_sibs; + public: + siblings(app_map const& colors): m_colors(colors) {} + + app_siblings const& get() { return m_sibs; } + void operator()(app* n) { + unsigned sz = n->get_num_args(); + for (unsigned i = 0; i < sz; ++i) { + expr* e = n->get_arg(i); + if (!is_app(e)) continue; + app_siblings::obj_map_entry* entry = m_sibs.insert_if_not_there2(to_app(e), 0); + if (!entry->get_data().get_value()) entry->get_data().m_value = alloc(uint_set); + for (unsigned j = 0; j < sz; ++j) { + expr* f = n->get_arg(j); + if (is_app(f) && i != j) { + unsigned c1 = 0; + m_colors.find(to_app(f), c1); + entry->get_data().m_value->insert(c1); + } + } + } + } + void operator()(quantifier *n) {} + void operator()(var* n) {} + }; + // refine coloring by taking colors of siblings into account. + bool compute_siblings_rec(expr* e, app_map& colors) { + siblings sibs(colors); + app_map colors1; + for_each_expr(sibs, e); + app_siblings const& s = sibs.get(); + app_siblings::iterator it = s.begin(), end = s.end(); + for (; it != end; ++it) { + app* a = it->m_key; + uint_set* set = it->m_value; + uint_set::iterator it2 = set->begin(), end2 = set->end(); + unsigned c = 0; + for(; it2 != end2; ++it2) { + c += 1 + *it2; + } + colors1.insert(a, c); + dealloc(set); + } + if (is_app(e)) { + colors1.insert(to_app(e), 0); + } + return merge_colors(colors1, colors); + } + void compute_siblings(expr* fml, app_map& colors) { + while(compute_siblings_rec(fml, colors)); + } + + // check if assertion set is invariant under the current permutation + bool invariant_by_permutations(expr* fml, permutation& p) { + + SASSERT(p.size() >= 2); + bool result = check_swap(fml, p[0], p[1]) && check_cycle(fml, p); + TRACE("symmetry_reduce", + if (result) { + tout << "Symmetric: "; + } + else { + tout << "Not symmetric: "; + } + for (unsigned i = 0; i < p.size(); ++i) { + tout << mk_pp(p[i], m()) << " "; + } + tout << "\n";); + return result; + } + + bool check_swap(expr* fml, app* t1, app* t2) { + expr_substitution sub(m()); + sub.insert(t1, t2); + sub.insert(t2, t1); + m_replace->set_substitution(&sub); + return check_substitution(fml); + } + + bool check_cycle(expr* fml, permutation& p) { + expr_substitution sub(m()); + for (unsigned i = 0; i + 1 < p.size(); ++i) { + sub.insert(p[i], p[i+1]); + } + sub.insert(p[p.size()-1], p[0]); + m_replace->set_substitution(&sub); + return check_substitution(fml); + } + + bool check_substitution(expr* t) { + expr_ref r(m()); + (*m_replace)(t, r); + normalize(r); + return t == r.get(); + } + + void normalize(expr_ref& r) { + proof_ref pr(m()); + expr_ref result(m()); + m_rewriter(r.get(), result, pr); + r = result; + } + + // select terms that are range restricted by set p. + void select_terms(expr* fml, term_set const& p, term_set& T) { + T.reset(); + ptr_vector todo; + todo.push_back(fml); + app* t = 0; + while (!todo.empty()) { + fml = todo.back(); + todo.pop_back(); + if (m().is_and(fml)) { + todo.append(to_app(fml)->get_num_args(), to_app(fml)->get_args()); + } + else if (is_range_restriction(fml, p, t)) { + T.push_back(t); + } + } + } + bool is_range_restriction(expr* form, term_set const& C, app*& t) { + if (!m().is_or(form)) return false; + unsigned sz = to_app(form)->get_num_args(); + t = 0; + for (unsigned i = 0; i < sz; ++i) { + expr* e = to_app(form)->get_arg(i); + expr* e1, *e2; + if (!m().is_eq(e, e1, e2)) return false; + if (!is_app(e1) || !is_app(e2)) return false; + app* a1 = to_app(e1), *a2 = to_app(e2); + if (C.contains(a1) && (t == 0 || t == a2)) { + t = a2; + } + else if (C.contains(a2) && (t == 0 || t == a1)) { + t = a1; + } + else { + return false; + } + } + return t != 0; + } + + + // select the most promising term among T. + // terms with the largest number of occurrences have higher weight. + // terms that have fewest terms among C as subterms are preferred. + + class num_occurrences { + app_map& m_occs; + public: + num_occurrences(app_map& occs): m_occs(occs) {} + void operator()(app* n) { + app_map::obj_map_entry* e; + m_occs.insert_if_not_there2(n, 0); + unsigned sz = n->get_num_args(); + for (unsigned i = 0; i < sz; ++i) { + expr* arg = n->get_arg(i); + if (is_app(arg)) { + e = m_occs.insert_if_not_there2(to_app(arg), 0); + e->get_data().m_value++; + } + } + } + void operator()(quantifier * n) {} + void operator()(var * n) {} + }; + void compute_occurrences(expr* fml, app_map& occs) { + occs.reset(); + num_occurrences num_occ(occs); + for_each_expr(num_occ, fml); + } + + app* select_most_promising_term( + expr* fml, term_set const& T, + term_set& cts, term_set const& consts, app_map const& occs) { + SASSERT(!T.empty()); + app* t = T[0]; + unsigned weight, weight1; + VERIFY(occs.find(t, weight)); + unsigned cts_delta = compute_cts_delta(t, cts, consts); + TRACE("symmetry_reduce", tout << mk_pp(t, m()) << " " << weight << " " << cts_delta << "\n";); + for (unsigned i = 1; i < T.size(); ++i) { + app* t1 = T[i]; + VERIFY(occs.find(t1, weight1)); + if (weight1 < weight && t->get_num_args() <= t1->get_num_args()) { + continue; + } + unsigned cts_delta1 = compute_cts_delta(t1, cts, consts); + TRACE("symmetry_reduce", tout << mk_pp(t1, m()) << " " << weight1 << " " << cts_delta1 << "\n";); + if ((t->get_num_args() == t1->get_num_args() && (weight1 > weight || cts_delta1 < cts_delta)) || + t->get_num_args() > t1->get_num_args()) { + cts_delta = cts_delta1; + weight = weight1; + t = t1; + } + } + return t; + } + + // add to cts subterms of t that are members of consts. + class member_of { + term_set const& m_S; + term_set& m_r; + public: + member_of(term_set const& S, term_set& r) : m_S(S), m_r(r) {} + void operator()(app* n) { + if (m_S.contains(n) && !m_r.contains(n)) { + m_r.push_back(n); + } + } + void operator()(quantifier * n) {} + void operator()(var * n) {} + }; + void compute_used_in(app* t, term_set& cts, term_set const& consts) { + member_of mem(consts, cts); + for_each_expr(mem, t); + TRACE("symmetry_reduce", + tout << "Term: " << mk_pp(t, m()) << "\n"; + tout << "Support set: "; + for (unsigned i = 0; i < consts.size(); ++i) { + tout << mk_pp(consts[i], m()) << " "; + } + tout << "\n"; + tout << "Constants: "; + for (unsigned i = 0; i < cts.size(); ++i) { + tout << mk_pp(cts[i], m()) << " "; + } + tout << "\n"; + ); + } + + unsigned compute_cts_delta(app* t, term_set& cts, term_set const& consts) { + unsigned cts_size = cts.size(); + if (cts_size == consts.size()) { + return 0; + } + compute_used_in(t, cts, consts); + unsigned cts_delta = cts.size() - cts_size; + cts.resize(cts_size); + return cts_delta; + } + + // select element in A not in B + app* select_const(term_set const& A, term_set const& B) { + unsigned j; + for (j = 0; j < A.size() && B.contains(A[j]); ++j); + return (j == A.size())?0:A[j]; + } + + app* mk_member(app* t, term_set const& C) { + expr_ref_vector eqs(m()); + for (unsigned i = 0; i < C.size(); ++i) { + eqs.push_back(m().mk_eq(t, C[i])); + } + return m().mk_or(eqs.size(), eqs.c_ptr()); + } +}; + +symmetry_reduce_tactic::symmetry_reduce_tactic(ast_manager & m) { + m_imp = alloc(imp, m); +} + +symmetry_reduce_tactic::~symmetry_reduce_tactic() { + dealloc(m_imp); +} + +void symmetry_reduce_tactic::operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + fail_if_proof_generation("symmetry_reduce", g); + fail_if_unsat_core_generation("symmetry_reduce", g); + mc = 0; pc = 0; core = 0; result.reset(); + (*m_imp)(*(g.get())); + g->inc_depth(); + result.push_back(g.get()); +} + +void symmetry_reduce_tactic::cleanup() { + // no-op. +} + +tactic * mk_symmetry_reduce_tactic(ast_manager & m, params_ref const & p) { + return alloc(symmetry_reduce_tactic, m); +} diff --git a/lib/symmetry_reduce_tactic.h b/lib/symmetry_reduce_tactic.h new file mode 100644 index 000000000..4d0a93a57 --- /dev/null +++ b/lib/symmetry_reduce_tactic.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + symmetry_reduce.h + +Abstract: + + Add symmetry breaking predicates to assertion sets. + +Author: + + Nikolaj (nbjorner) 2011-05-31 + +Notes: + +--*/ +#ifndef _SYMMETRY_REDUCE_TACTIC_H_ +#define _SYMMETRY_REDUCE_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_symmetry_reduce_tactic(ast_manager & m, params_ref const & p); + +#endif diff --git a/lib/tactic.cpp b/lib/tactic.cpp new file mode 100644 index 000000000..cc2f3bc3f --- /dev/null +++ b/lib/tactic.cpp @@ -0,0 +1,331 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + tactic.h + +Abstract: + + Abstract tactic object. + +Author: + + Leonardo (leonardo) 2011-10-13 + +Notes: + +--*/ +#include +#include"tactic.h" +#include"probe.h" +#include"stopwatch.h" +#include"model_v2_pp.h" +#include"cmd_context.h" + +void tactic::cancel() { + #pragma omp critical (tactic_cancel) + { + set_cancel(true); + } +} + +void tactic::reset_cancel() { + #pragma omp critical (tactic_cancel) + { + set_cancel(false); + } +} + +struct tactic_report::imp { + char const * m_id; + goal const & m_goal; + stopwatch m_watch; + double m_start_memory; + + imp(char const * id, goal const & g): + m_id(id), + m_goal(g), + m_start_memory(static_cast(memory::get_allocation_size())/static_cast(1024*1024)) { + m_watch.start(); + } + + ~imp() { + m_watch.stop(); + double end_memory = static_cast(memory::get_allocation_size())/static_cast(1024*1024); + verbose_stream() << "(" << m_id + << " :num-exprs " << m_goal.num_exprs() + << " :num-asts " << m_goal.m().get_num_asts() + << " :time " << std::fixed << std::setprecision(2) << m_watch.get_seconds() + << " :before-memory " << std::fixed << std::setprecision(2) << m_start_memory + << " :after-memory " << std::fixed << std::setprecision(2) << end_memory + << ")" << std::endl; + } +}; + +tactic_report::tactic_report(char const * id, goal const & g) { + if (get_verbosity_level() >= TACTIC_VERBOSITY_LVL) + m_imp = alloc(imp, id, g); + else + m_imp = 0; +} + +tactic_report::~tactic_report() { + if (m_imp) + dealloc(m_imp); +} + +void report_tactic_progress(char const * id, unsigned val) { + if (val > 0) { + IF_VERBOSE(TACTIC_VERBOSITY_LVL, verbose_stream() << "(" << id << " " << val << ")" << std::endl;); + } +} + + +class skip_tactic : public tactic { +public: + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + result.reset(); + result.push_back(in.get()); + mc = 0; + pc = 0; + core = 0; + } + + virtual void cleanup() {} + + virtual tactic * translate(ast_manager & m) { return this; } +}; + +tactic * mk_skip_tactic() { + return alloc(skip_tactic); +} + +class fail_tactic : public tactic { +public: + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + throw tactic_exception("fail tactic"); + } + + virtual void cleanup() {} + + virtual tactic * translate(ast_manager & m) { return this; } +}; + +tactic * mk_fail_tactic() { + return alloc(fail_tactic); +} + +class report_verbose_tactic : public skip_tactic { + char const * m_msg; + unsigned m_lvl; +public: + report_verbose_tactic(char const * msg, unsigned lvl) : m_msg(msg), m_lvl(lvl) {} + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + IF_VERBOSE(m_lvl, verbose_stream() << m_msg << "\n";); + skip_tactic::operator()(in, result, mc, pc, core); + } +}; + +tactic * mk_report_verbose_tactic(char const * msg, unsigned lvl) { + return alloc(report_verbose_tactic, msg, lvl); +} + +class trace_tactic : public skip_tactic { + char const * m_tag; +public: + trace_tactic(char const * tag):m_tag(tag) {} + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + TRACE(m_tag, in->display(tout);); + skip_tactic::operator()(in, result, mc, pc, core); + } +}; + +tactic * mk_trace_tactic(char const * tag) { + return alloc(trace_tactic, tag); +} + +class echo_tactic : public skip_tactic { + cmd_context & m_ctx; + char const * m_msg; + bool m_newline; +public: + echo_tactic(cmd_context & ctx, char const * msg, bool newline):m_ctx(ctx), m_msg(msg), m_newline(newline) {} + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + #pragma omp critical (echo_tactic) + { + m_ctx.diagnostic_stream() << m_msg; + if (m_newline) + m_ctx.diagnostic_stream() << std::endl; + } + skip_tactic::operator()(in, result, mc, pc, core); + } +}; + +tactic * mk_echo_tactic(cmd_context & ctx, char const * msg, bool newline) { + return alloc(echo_tactic, ctx, msg, newline); +} + +class probe_value_tactic : public skip_tactic { + cmd_context & m_ctx; + char const * m_msg; + probe * m_p; + bool m_newline; +public: + probe_value_tactic(cmd_context & ctx, char const * msg, probe * p, bool newline):m_ctx(ctx), m_msg(msg), m_p(p), m_newline(newline) { + SASSERT(m_p); + m_p->inc_ref(); + } + + ~probe_value_tactic() { + m_p->dec_ref(); + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + double val = (*m_p)(*(in.get())).get_value(); + #pragma omp critical (probe_value_tactic) + { + if (m_msg) + m_ctx.diagnostic_stream() << m_msg << " "; + m_ctx.diagnostic_stream() << val; + if (m_newline) + m_ctx.diagnostic_stream() << std::endl; + } + skip_tactic::operator()(in, result, mc, pc, core); + } +}; + +tactic * mk_probe_value_tactic(cmd_context & ctx, char const * msg, probe * p, bool newline) { + return alloc(probe_value_tactic, ctx, msg, p, newline); +} + +class fail_if_undecided_tactic : public skip_tactic { +public: + fail_if_undecided_tactic() {} + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + if (!in->is_decided()) + throw tactic_exception("undecided"); + skip_tactic::operator()(in, result, mc, pc, core); + } +}; + +tactic * mk_fail_if_undecided_tactic() { + return alloc(fail_if_undecided_tactic); +} + +void exec(tactic & t, goal_ref const & in, goal_ref_buffer & result, model_converter_ref & mc, proof_converter_ref & pc, expr_dependency_ref & core) { + t.reset_statistics(); + t.reset_cancel(); + try { + t(in, result, mc, pc, core); + t.cleanup(); + } + catch (tactic_exception & ex) { + IF_VERBOSE(TACTIC_VERBOSITY_LVL, verbose_stream() << "(tactic-exception \"" << escaped(ex.msg()) << "\")" << std::endl;); + t.cleanup(); + throw ex; + } +} + +lbool check_sat(tactic & t, goal_ref & g, model_ref & md, proof_ref & pr, expr_dependency_ref & core, std::string & reason_unknown) { + bool models_enabled = g->models_enabled(); + bool proofs_enabled = g->proofs_enabled(); + bool cores_enabled = g->unsat_core_enabled(); + md = 0; + pr = 0; + core = 0; + ast_manager & m = g->m(); + goal_ref_buffer r; + model_converter_ref mc; + proof_converter_ref pc; + try { + exec(t, g, r, mc, pc, core); + } + catch (tactic_exception & ex) { + reason_unknown = ex.msg(); + return l_undef; + } + TRACE("tactic_mc", mc->display(tout);); + TRACE("tactic_check_sat", + tout << "r.size(): " << r.size() << "\n"; + for (unsigned i = 0; i < r.size(); i++) r[0]->display(tout);); + + if (is_decided_sat(r)) { + if (models_enabled) { + model_converter2model(m, mc.get(), md); + if (!md) { + // create empty model. + md = alloc(model, m); + } + } + return l_true; + } + else if (is_decided_unsat(r)) { + goal * final = r[0]; + SASSERT(m.is_false(final->form(0))); + if (proofs_enabled) pr = final->pr(0); + if (cores_enabled) core = final->dep(0); + return l_false; + } + else { + if (models_enabled) model_converter2model(m, mc.get(), md); + reason_unknown = "incomplete"; + return l_undef; + } +} + +void fail_if_proof_generation(char const * tactic_name, goal_ref const & in) { + if (in->proofs_enabled()) { + std::string msg = tactic_name; + msg += " does not support proof production"; + throw tactic_exception(msg.c_str()); + } +} + +void fail_if_unsat_core_generation(char const * tactic_name, goal_ref const & in) { + if (in->unsat_core_enabled()) { + std::string msg = tactic_name; + msg += " does not support unsat core production"; + throw tactic_exception(msg.c_str()); + } +} + +void fail_if_model_generation(char const * tactic_name, goal_ref const & in) { + if (in->models_enabled()) { + std::string msg = tactic_name; + msg += " does not generate models"; + throw tactic_exception(msg.c_str()); + } +} diff --git a/lib/tactic.h b/lib/tactic.h new file mode 100644 index 000000000..2b3a34ca6 --- /dev/null +++ b/lib/tactic.h @@ -0,0 +1,155 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + tactic.h + +Abstract: + + Abstract tactic object. + It used to be called assertion_set_strategy. + The main improvement is the support for multiple subgoals. + +Author: + + Leonardo (leonardo) 2011-10-13 + +Notes: + +--*/ +#ifndef _TACTIC_H_ +#define _TACTIC_H_ + +#include"goal.h" +#include"params.h" +#include"statistics.h" +#include"model_converter.h" +#include"proof_converter.h" +#include"tactic_exception.h" +#include"lbool.h" + +struct front_end_params; +class progress_callback; + +typedef ptr_buffer goal_buffer; + +class tactic { + unsigned m_ref_count; +public: + tactic():m_ref_count(0) {} + virtual ~tactic() {} + + void inc_ref() { m_ref_count++; } + void dec_ref() { SASSERT(m_ref_count > 0); m_ref_count--; if (m_ref_count == 0) dealloc(this); } + + virtual void updt_params(params_ref const & p) {} + virtual void collect_param_descrs(param_descrs & r) {} + + void cancel(); + void reset_cancel(); + virtual void set_cancel(bool f) {} + + /** + \brief Apply tactic to goal \c in. + + The list of resultant subgoals is stored in \c result. + The content of \c in may be destroyed during the operation. + + The resultant model converter \c mc can be used to convert a model for one of the returned subgoals + into a model for \in. If mc == 0, then model construction is disabled or any model for a subgoal + of \c in is also a model for \c in. + If \c result is decided_sat (i.e., it contains a single empty subgoal), then + the model converter is just wrapping the model. + + The resultant proof converter \c pc can be used to convert proofs for each subgoal in \c result + into a proof for \c in. If pc == 0, then one of the following conditions should hold: + 1- proof construction is disabled, + 2- result contains a single subgoal, and any proof of unsatisfiability for this subgoal is a proof for \c in. + 3- result is an decided_unsat (i.e., it contains a single unsat subgoal). The actual proof can be extracted from this goal. + + The output parameter \c core is used to accumulate the unsat core of closed subgoals. + It must be 0 if dependency tracking is disabled, or the result is decided unsat, or + no tagged assertions were used to close any subgoal. + + Note that, this signature is not compatible with the one described in the paper: + "The Strategy Challenge in SMT Solving". + The approach in the paper is conceptually simpler, but (for historical reasons) it would + require a lot of re-engineering in the Z3 code. In Z3, we keep a proof/justification for every formula + in a goal. + + Therefore, in most cases, pc == 0 and core == 0 for non-branching tactics. + */ + virtual void operator()(/* in */ goal_ref const & in, + /* out */ goal_ref_buffer & result, + /* out */ model_converter_ref & mc, + /* out */ proof_converter_ref & pc, + /* out */ expr_dependency_ref & core) = 0; + + virtual void collect_statistics(statistics & st) const {} + virtual void reset_statistics() {} + virtual void cleanup() = 0; + virtual void reset() { cleanup(); } + + // for backward compatibility + virtual void set_front_end_params(front_end_params & p) {} + virtual void set_logic(symbol const & l) {} + virtual void set_progress_callback(progress_callback * callback) {} + + // translate tactic to the given manager + virtual tactic * translate(ast_manager & m) = 0; +}; + +typedef ref tactic_ref; +typedef sref_vector tactic_ref_vector; +typedef sref_buffer tactic_ref_buffer; + +// minimum verbosity level for tactics +#define TACTIC_VERBOSITY_LVL 10 + +class tactic_report { + struct imp; + imp * m_imp; +public: + tactic_report(char const * id, goal const & g); + ~tactic_report(); +}; + +void report_tactic_progress(char const * id, unsigned val); + +tactic * mk_skip_tactic(); +tactic * mk_fail_tactic(); +tactic * mk_fail_if_undecided_tactic(); + +tactic * mk_report_verbose_tactic(char const * msg, unsigned lvl); +tactic * mk_trace_tactic(char const * tag); +tactic * mk_echo_tactic(cmd_context & ctx, char const * msg, bool newline = true); +// Display the value returned by p in the diagnostic_stream +class probe; +tactic * mk_probe_value_tactic(cmd_context & ctx, char const * msg, probe * p, bool newline = true); + + +class tactic_factory { +public: + virtual ~tactic_factory() {} + virtual tactic * operator()(ast_manager & m, params_ref const & p) = 0; +}; + +#define MK_TACTIC_FACTORY(NAME, CODE) \ +class NAME : public tactic_factory { \ +public: \ + virtual ~NAME() {} \ + virtual tactic * operator()(ast_manager & m, params_ref const & p) { CODE } \ +}; + +#define MK_SIMPLE_TACTIC_FACTORY(NAME, ST) MK_TACTIC_FACTORY(NAME, return ST;) + +void exec(tactic & t, goal_ref const & in, goal_ref_buffer & result, model_converter_ref & mc, proof_converter_ref & pc, expr_dependency_ref & core); +lbool check_sat(tactic & t, goal_ref & g, model_ref & md, proof_ref & pr, expr_dependency_ref & core, std::string & reason_unknown); + +// Throws an exception if goal \c in requires proof generation. +void fail_if_proof_generation(char const * tactic_name, goal_ref const & in); +void fail_if_unsat_core_generation(char const * tactic_name, goal_ref const & in); +void fail_if_model_generation(char const * tactic_name, goal_ref const & in); + +#endif diff --git a/lib/tactic2solver.cpp b/lib/tactic2solver.cpp new file mode 100644 index 000000000..6b67faae1 --- /dev/null +++ b/lib/tactic2solver.cpp @@ -0,0 +1,249 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + tactic2solver.cpp + +Abstract: + + Wrapper for implementing the solver interface + using a tactic. + + This is a light version of the strategic solver. + +Author: + + Leonardo (leonardo) 2012-01-23 + +Notes: + +--*/ +#include"tactic2solver.h" +#include"params2front_end_params.h" +#include"ast_smt2_pp.h" + +tactic2solver::ctx::ctx(ast_manager & m, symbol const & logic): + m_logic(logic), + m_assertions(m) { +} + +tactic2solver::~tactic2solver() { +} + +void tactic2solver::init(ast_manager & m, symbol const & logic) { + m_ctx = alloc(ctx, m, logic); +} + +void tactic2solver::updt_params(params_ref const & p) { + m_params = p; +} + +void tactic2solver::collect_param_descrs(param_descrs & r) { + if (m_ctx) { + if (!m_ctx->m_tactic) { + #pragma omp critical (tactic2solver) + { + m_ctx->m_tactic = get_tactic(m_ctx->m(), m_params); + } + + if (m_ctx->m_tactic) { + m_ctx->m_tactic->collect_param_descrs(r); + } + + #pragma omp critical (tactic2solver) + { + m_ctx->m_tactic = 0; + } + } + else { + m_ctx->m_tactic->collect_param_descrs(r); + } + } +} + +void tactic2solver::reset() { + SASSERT(m_ctx); + m_ctx->m_assertions.reset(); + m_ctx->m_scopes.reset(); + m_ctx->m_result = 0; +} + +void tactic2solver::assert_expr(expr * t) { + SASSERT(m_ctx); + m_ctx->m_assertions.push_back(t); + m_ctx->m_result = 0; +} + +void tactic2solver::push() { + SASSERT(m_ctx); + m_ctx->m_scopes.push_back(m_ctx->m_assertions.size()); + m_ctx->m_result = 0; +} + +void tactic2solver::pop(unsigned n) { + SASSERT(m_ctx); + unsigned new_lvl = m_ctx->m_scopes.size() - n; + unsigned old_sz = m_ctx->m_scopes[new_lvl]; + m_ctx->m_assertions.shrink(old_sz); + m_ctx->m_scopes.shrink(new_lvl); + m_ctx->m_result = 0; +} + +unsigned tactic2solver::get_scope_level() const { + SASSERT(m_ctx); + return m_ctx->m_scopes.size(); +} + +lbool tactic2solver::check_sat(unsigned num_assumptions, expr * const * assumptions) { + SASSERT(m_ctx); + ast_manager & m = m_ctx->m(); + params_ref p = m_params; + if (m_fparams) + front_end_params2params(*m_fparams, p); + #pragma omp critical (tactic2solver) + { + m_ctx->m_tactic = get_tactic(m, p); + if (m_ctx->m_tactic) { + m_ctx->m_result = alloc(simple_check_sat_result, m); + } + } + if (!m_ctx->m_tactic) + return l_undef; + tactic & t = *(m_ctx->m_tactic); + simple_check_sat_result & result = *(m_ctx->m_result); + if (m_fparams) + t.set_front_end_params(*m_fparams); + goal_ref g = alloc(goal, m, m_produce_proofs, m_produce_models, m_produce_unsat_cores); + t.set_logic(m_ctx->m_logic); + unsigned sz = m_ctx->m_assertions.size(); + for (unsigned i = 0; i < sz; i++) { + g->assert_expr(m_ctx->m_assertions.get(i)); + } + for (unsigned i = 0; i < num_assumptions; i++) { + g->assert_expr(assumptions[i], m.mk_asserted(assumptions[i]), m.mk_leaf(assumptions[i])); + } + + model_ref md; + proof_ref pr(m); + expr_dependency_ref core(m); + std::string reason_unknown = "unknown"; + try { + switch (::check_sat(t, g, md, pr, core, reason_unknown)) { + case l_true: + result.set_status(l_true); + break; + case l_false: + result.set_status(l_false); + break; + default: + result.set_status(l_undef); + if (reason_unknown != "") + result.m_unknown = reason_unknown; + break; + } + } + catch (z3_error & ex) { + throw ex; + } + catch (z3_exception & ex) { + TRACE("tactic2solver", tout << "exception: " << ex.msg() << "\n";); + result.set_status(l_undef); + result.m_unknown = ex.msg(); + } + t.collect_statistics(result.m_stats); + result.m_model = md; + result.m_proof = pr; + if (m_produce_unsat_cores) { + ptr_vector core_elems; + m.linearize(core, core_elems); + result.m_core.append(core_elems.size(), core_elems.c_ptr()); + } + + #pragma omp critical (tactic2solver) + { + m_ctx->m_tactic = 0; + } + return result.status(); +} + +void tactic2solver::set_cancel(bool f) { + #pragma omp critical (tactic2solver) + { + if (m_ctx && m_ctx->m_tactic) + m_ctx->m_tactic->set_cancel(f); + } +} + +void tactic2solver::collect_statistics(statistics & st) const { + if (m_ctx->m_result.get()) + m_ctx->m_result->collect_statistics(st); +} + +void tactic2solver::get_unsat_core(ptr_vector & r) { + if (m_ctx->m_result.get()) + m_ctx->m_result->get_unsat_core(r); +} + +void tactic2solver::get_model(model_ref & m) { + if (m_ctx->m_result.get()) + m_ctx->m_result->get_model(m); +} + +proof * tactic2solver::get_proof() { + if (m_ctx->m_result.get()) + return m_ctx->m_result->get_proof(); + else + return 0; +} + +std::string tactic2solver::reason_unknown() const { + if (m_ctx->m_result.get()) + return m_ctx->m_result->reason_unknown(); + else + return std::string("unknown"); +} + +unsigned tactic2solver::get_num_assertions() const { + if (m_ctx) + return m_ctx->m_assertions.size(); + else + return 0; +} + +expr * tactic2solver::get_assertion(unsigned idx) const { + SASSERT(m_ctx); + return m_ctx->m_assertions.get(idx); +} + +void tactic2solver::display(std::ostream & out) const { + if (m_ctx) { + ast_manager & m = m_ctx->m_assertions.m(); + unsigned num = m_ctx->m_assertions.size(); + out << "(solver"; + for (unsigned i = 0; i < num; i++) { + out << "\n " << mk_ismt2_pp(m_ctx->m_assertions.get(i), m, 2); + } + out << ")"; + } + else { + out << "(solver)"; + } +} + +void tactic2solver_cmd::set_tactic(tactic_factory * f) { + m_tactic_factory = f; +} + +tactic * tactic2solver_cmd::get_tactic(ast_manager & m, params_ref const & p) { + if (m_tactic_factory == 0) + return 0; + return (*m_tactic_factory)(m, p); +} + +tactic * tactic2solver_api::get_tactic(ast_manager & m, params_ref const & p) { + m_tactic->cleanup(); + m_tactic->updt_params(p); + return m_tactic.get(); +} + diff --git a/lib/tactic2solver.h b/lib/tactic2solver.h new file mode 100644 index 000000000..954dda041 --- /dev/null +++ b/lib/tactic2solver.h @@ -0,0 +1,110 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + tactic2solver.h + +Abstract: + + Wrapper for implementing the external solver interface + using a tactic. + + This is a light version of the strategic solver. + +Author: + + Leonardo (leonardo) 2012-01-23 + +Notes: + +--*/ +#ifndef _TACTIC2SOLVER_H_ +#define _TACTIC2SOLVER_H_ + +#include"solver.h" +#include"tactic.h" + +class tactic2solver : public solver { + struct ctx { + symbol m_logic; + expr_ref_vector m_assertions; + unsigned_vector m_scopes; + ref m_result; + tactic_ref m_tactic; + ctx(ast_manager & m, symbol const & logic); + ast_manager & m() const { return m_assertions.m(); } + }; + scoped_ptr m_ctx; + front_end_params * m_fparams; + params_ref m_params; + bool m_produce_models; + bool m_produce_proofs; + bool m_produce_unsat_cores; +public: + tactic2solver():m_ctx(0), m_fparams(0), m_produce_models(false), m_produce_proofs(false), m_produce_unsat_cores(false) {} + virtual ~tactic2solver(); + + virtual tactic * get_tactic(ast_manager & m, params_ref const & p) = 0; + + virtual void set_front_end_params(front_end_params & p) { m_fparams = &p; } + + virtual void updt_params(params_ref const & p); + virtual void collect_param_descrs(param_descrs & r); + + virtual void set_produce_proofs(bool f) { m_produce_proofs = f; } + virtual void set_produce_models(bool f) { m_produce_models = f; } + virtual void set_produce_unsat_cores(bool f) { m_produce_unsat_cores = f; } + + virtual void init(ast_manager & m, symbol const & logic); + virtual void reset(); + virtual void assert_expr(expr * t); + virtual void push(); + virtual void pop(unsigned n); + virtual unsigned get_scope_level() const; + virtual lbool check_sat(unsigned num_assumptions, expr * const * assumptions); + + virtual void set_cancel(bool f); + + virtual void collect_statistics(statistics & st) const; + virtual void get_unsat_core(ptr_vector & r); + virtual void get_model(model_ref & m); + virtual proof * get_proof(); + virtual std::string reason_unknown() const; + virtual void get_labels(svector & r) {} + + virtual void set_progress_callback(progress_callback * callback) {} + + virtual unsigned get_num_assertions() const; + virtual expr * get_assertion(unsigned idx) const; + + virtual void display(std::ostream & out) const; +}; + +/** + \brief Specialization for cmd_context +*/ +class tactic2solver_cmd : public tactic2solver { + scoped_ptr m_tactic_factory; +public: + virtual ~tactic2solver_cmd() {} + /** + \brief Set tactic that will be used to process the satisfiability queries. + */ + void set_tactic(tactic_factory * f); + virtual tactic * get_tactic(ast_manager & m, params_ref const & p); +}; + +/** + \brief Specialization for API +*/ +class tactic2solver_api : public tactic2solver { + tactic_ref m_tactic; +public: + tactic2solver_api(tactic * t):m_tactic(t) {} + virtual ~tactic2solver_api() {} + virtual tactic * get_tactic(ast_manager & m, params_ref const & p); +}; + + +#endif diff --git a/lib/tactic_cmds.cpp b/lib/tactic_cmds.cpp new file mode 100644 index 000000000..f0b255696 --- /dev/null +++ b/lib/tactic_cmds.cpp @@ -0,0 +1,814 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + tactic_cmds.cpp + +Abstract: + Support for tactics in SMT 2.0 frontend. + +Author: + + Leonardo (leonardo) 2011-10-20 + +Notes: + +--*/ +#include +#include"tactic_cmds.h" +#include"cmd_context.h" +#include"cmd_util.h" +#include"parametric_cmd.h" +#include"install_tactics.h" +#include"scoped_timer.h" +#include"scoped_ctrl_c.h" +#include"cancel_eh.h" +#include"model_smt2_pp.h" +#include"params2front_end_params.h" +#include"ast_smt2_pp.h" +#include"tactic.h" +#include"tactical.h" +#include"probe.h" +#include"check_sat_result.h" + +tactic_cmd::~tactic_cmd() { + dealloc(m_factory); +} + +tactic * tactic_cmd::mk(ast_manager & m) { + return (*m_factory)(m, params_ref()); +} + +probe_info::probe_info(symbol const & n, char const * d, probe * p): + m_name(n), + m_descr(d), + m_probe(p) { +} + +probe_info::~probe_info() { +} + +class declare_tactic_cmd : public cmd { + symbol m_name; + sexpr * m_decl; +public: + declare_tactic_cmd(): + cmd("declare-tactic"), + m_decl(0) { + } + + virtual char const * get_usage() const { return " "; } + virtual char const * get_descr(cmd_context & ctx) const { return "declare a new tactic, use (help-tactic) for the tactic language syntax."; } + virtual unsigned get_arity() const { return 2; } + virtual void prepare(cmd_context & ctx) { m_name = symbol::null; m_decl = 0; } + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { + if (m_name == symbol::null) return CPK_SYMBOL; + return CPK_SEXPR; + } + virtual void set_next_arg(cmd_context & ctx, symbol const & s) { m_name = s; } + virtual void set_next_arg(cmd_context & ctx, sexpr * n) { m_decl = n; } + virtual void execute(cmd_context & ctx) { + tactic_ref t = sexpr2tactic(ctx, m_decl); // make sure the tactic is well formed. + ctx.insert_user_tactic(m_name, m_decl); + } +}; + +ATOMIC_CMD(get_user_tactics_cmd, "get-user-tactics", "display tactics defined using the declare-tactic command.", { + ctx.regular_stream() << "("; + std::ostringstream buf; + cmd_context::user_tactic_iterator it = ctx.begin_user_tactics(); + cmd_context::user_tactic_iterator end = ctx.end_user_tactics(); + for (bool first = true; it != end; ++it) { + if (first) first = false; else buf << "\n "; + buf << "(declare-tactic " << it->m_key << " "; + it->m_value->display(buf); + buf << ")"; + } + std::string r = buf.str(); + ctx.regular_stream() << escaped(r.c_str()); + ctx.regular_stream() << ")\n"; +}); + +void help_tactic(cmd_context & ctx) { + std::ostringstream buf; + buf << "combinators:\n"; + buf << "- (and-then +) executes the given tactics sequencially.\n"; + buf << "- (or-else +) tries the given tactics in sequence until one of them succeeds.\n"; + buf << "- (par-or +) executes the given tactics in parallel until one of them succeeds.\n"; + buf << "- (par-then ) executes tactic1 and then tactic2 to every subgoal produced by tactic1. All subgoals are processed in parallel.\n"; + buf << "- (try-for ) excutes the given tactic for at most milliseconds, it fails if the execution takes more than milliseconds.\n"; + buf << "- (if ) if evaluates to true, then execute the first tactic. Otherwise execute the second.\n"; + buf << "- (when ) shorthand for (if skip).\n"; + buf << "- (fail-if ) fail if evaluates to true.\n"; + buf << "- (using-params *) executes the given tactic using the given attributes, where ::= . ! is a syntax sugar for using-params.\n"; + buf << "builtin tactics:\n"; + cmd_context::tactic_cmd_iterator it = ctx.begin_tactic_cmds(); + cmd_context::tactic_cmd_iterator end = ctx.end_tactic_cmds(); + for (; it != end; ++it) { + tactic_cmd * cmd = *it; + buf << "- " << cmd->get_name() << " " << cmd->get_descr() << "\n"; + tactic_ref t = cmd->mk(ctx.m()); + param_descrs descrs; + t->collect_param_descrs(descrs); + descrs.display(buf, 4); + } + buf << "builtin probes:\n"; + cmd_context::probe_iterator it2 = ctx.begin_probes(); + cmd_context::probe_iterator end2 = ctx.end_probes(); + for (; it2 != end2; ++it2) { + probe_info * pinfo = *it2; + buf << "- " << pinfo->get_name() << " " << pinfo->get_descr() << "\n"; + } + ctx.regular_stream() << "\"" << escaped(buf.str().c_str()) << "\"\n"; +} + +ATOMIC_CMD(help_tactic_cmd, "help-tactic", "display the tactic combinators and primitives.", help_tactic(ctx);); + +class exec_given_tactic_cmd : public parametric_cmd { +protected: + sexpr * m_tactic; +public: + exec_given_tactic_cmd(char const * name): + parametric_cmd(name) { + } + + virtual char const * get_usage() const { return " ( )*"; } + + virtual void prepare(cmd_context & ctx) { + parametric_cmd::prepare(ctx); + m_tactic = 0; + } + + virtual cmd_arg_kind next_arg_kind(cmd_context & ctx) const { + if (m_tactic == 0) return CPK_SEXPR; + return parametric_cmd::next_arg_kind(ctx); + } + + virtual void set_next_arg(cmd_context & ctx, sexpr * arg) { + m_tactic = arg; + } + + virtual void init_pdescrs(cmd_context & ctx, param_descrs & p) { + insert_timeout(p); + insert_max_memory(p); + p.insert(":print-statistics", CPK_BOOL, "(default: false) print statistics."); + } + + void display_statistics(cmd_context & ctx, tactic * t) { + statistics stats; + unsigned long long max_mem = memory::get_max_used_memory(); + unsigned long long mem = memory::get_allocation_size(); + stats.update("time", ctx.get_seconds()); + stats.update("memory", static_cast(mem)/static_cast(1024*1024)); + stats.update("max memory", static_cast(max_mem)/static_cast(1024*1024)); + t->collect_statistics(stats); + stats.display_smt2(ctx.regular_stream()); + } +}; + +typedef simple_check_sat_result check_sat_tactic_result; + +class check_sat_using_tactict_cmd : public exec_given_tactic_cmd { +public: + check_sat_using_tactict_cmd(): + exec_given_tactic_cmd("check-sat-using") { + } + + virtual char const * get_main_descr() const { return "check if the current context is satisfiable using the given tactic, use (help-tactic) for the tactic language syntax."; } + + virtual void init_pdescrs(cmd_context & ctx, param_descrs & p) { + exec_given_tactic_cmd::init_pdescrs(ctx, p); + p.insert(":print-unsat-core", CPK_BOOL, "(default: false) print unsatisfiable core."); + p.insert(":print-proof", CPK_BOOL, "(default: false) print proof."); + p.insert(":print-model", CPK_BOOL, "(default: false) print model."); + } + + virtual void execute(cmd_context & ctx) { + params_ref p = ps(); + front_end_params2params(ctx.params(), p); + tactic_ref tref = using_params(sexpr2tactic(ctx, m_tactic), p); + tref->set_front_end_params(ctx.params()); + tref->set_logic(ctx.get_logic()); + ast_manager & m = ctx.m(); + unsigned timeout = p.get_uint(":timeout", UINT_MAX); + goal_ref g = alloc(goal, m, ctx.produce_proofs(), ctx.produce_models(), ctx.produce_unsat_cores()); + assert_exprs_from(ctx, *g); + TRACE("check_sat_using", g->display(tout);); + model_ref md; + proof_ref pr(m); + expr_dependency_ref core(m); + std::string reason_unknown; + ref result = alloc(check_sat_tactic_result, m); + ctx.set_check_sat_result(result.get()); + { + tactic & t = *tref; + cancel_eh eh(t); + { + scoped_ctrl_c ctrlc(eh); + scoped_timer timer(timeout, &eh); + cmd_context::scoped_watch sw(ctx); + lbool r = l_undef; + try { + r = check_sat(t, g, md, pr, core, reason_unknown); + ctx.display_sat_result(r); + result->set_status(r); + if (r == l_undef) { + if (reason_unknown != "") { + result->m_unknown = reason_unknown; + // ctx.diagnostic_stream() << "\"" << escaped(reason_unknown.c_str(), true) << "\"" << std::endl; + } + else { + result->m_unknown = "unknown"; + } + } + } + catch (z3_error & ex) { + throw ex; + } + catch (z3_exception & ex) { + result->set_status(l_undef); + result->m_unknown = ex.msg(); + ctx.regular_stream() << "(error \"tactic failed: " << ex.msg() << "\")" << std::endl; + } + ctx.validate_check_sat_result(r); + } + t.collect_statistics(result->m_stats); + } + + if (ctx.produce_unsat_cores()) { + ptr_vector core_elems; + m.linearize(core, core_elems); + result->m_core.append(core_elems.size(), core_elems.c_ptr()); + if (p.get_bool(":print-unsat-core", false)) { + ctx.regular_stream() << "(unsat-core"; + ptr_vector::const_iterator it = core_elems.begin(); + ptr_vector::const_iterator end = core_elems.end(); + for (; it != end; ++it) { + ctx.regular_stream() << " "; + ctx.display(ctx.regular_stream(), *it); + } + ctx.regular_stream() << ")" << std::endl; + } + } + + if (ctx.produce_models() && md) { + result->m_model = md; + if (p.get_bool(":print-model", false)) { + ctx.regular_stream() << "(model " << std::endl; + model_smt2_pp(ctx.regular_stream(), ctx, *md, 2); + ctx.regular_stream() << ")" << std::endl; + } + if (result->status() == l_true) + ctx.validate_model(); + } + + if (ctx.produce_proofs() && pr) { + result->m_proof = pr; + if (p.get_bool(":print-proof", false)) { + ctx.regular_stream() << mk_ismt2_pp(pr, m) << "\n"; + } + } + + if (p.get_bool(":print-statistics", false)) + display_statistics(ctx, tref.get()); + } +}; + +class apply_tactic_cmd : public exec_given_tactic_cmd { +public: + apply_tactic_cmd(): + exec_given_tactic_cmd("apply") { + } + + virtual char const * get_main_descr() const { return "apply the given tactic to the current context, and print the resultant set of goals."; } + + virtual void init_pdescrs(cmd_context & ctx, param_descrs & p) { + p.insert(":print", CPK_BOOL, "(default: true) print resultant goals."); +#ifndef _EXTERNAL_RELEASE + p.insert(":print-proof", CPK_BOOL, "(default: false) print proof associated with each assertion."); +#endif + p.insert(":print-model-converter", CPK_BOOL, "(default: false) print model converter."); + p.insert(":print-benchmark", CPK_BOOL, "(default: false) display resultant goals as a SMT2 benchmark."); +#ifndef _EXTERNAL_RELEASE + p.insert(":print-dependencies", CPK_BOOL, "(default: false) print dependencies when displaying the resultant set of goals."); +#endif + exec_given_tactic_cmd::init_pdescrs(ctx, p); + } + + virtual void execute(cmd_context & ctx) { + params_ref p = ps(); + front_end_params2params(ctx.params(), p); + tactic_ref tref = using_params(sexpr2tactic(ctx, m_tactic), p); + { + tactic & t = *(tref.get()); + ast_manager & m = ctx.m(); + goal_ref g = alloc(goal, m, ctx.produce_proofs(), ctx.produce_models(), ctx.produce_unsat_cores()); + assert_exprs_from(ctx, *g); + + unsigned timeout = p.get_uint(":timeout", UINT_MAX); + + goal_ref_buffer result_goals; + model_converter_ref mc; + proof_converter_ref pc; + expr_dependency_ref core(m); + + std::string reason_unknown; + bool failed = false; + cancel_eh eh(t); + { + scoped_ctrl_c ctrlc(eh); + scoped_timer timer(timeout, &eh); + cmd_context::scoped_watch sw(ctx); + try { + exec(t, g, result_goals, mc, pc, core); + } + catch (tactic_exception & ex) { + ctx.regular_stream() << "(error \"tactic failed: " << ex.msg() << "\")" << std::endl; + failed = true; + } + } + + if (!failed && p.get_bool(":print", true)) { + bool print_dependencies = p.get_bool(":print-dependencies", false); + ctx.regular_stream() << "(goals\n"; + unsigned sz = result_goals.size(); + for (unsigned i = 0; i < sz; i++) { + if (print_dependencies) + result_goals[i]->display_with_dependencies(ctx); + else + result_goals[i]->display(ctx); + } + ctx.regular_stream() << ")\n"; + } + +#ifndef _EXTERNAL_RELEASE + if (!failed && ctx.produce_proofs() && p.get_bool(":print-proof", false)) { + // TODO + } +#endif + + if (!failed && p.get_bool(":print-benchmark", false)) { + unsigned num_goals = result_goals.size(); + SASSERT(num_goals > 0); + if (num_goals == 1) { + // easy case + goal * fg = result_goals[0]; + unsigned sz = fg->size(); + ptr_buffer assertions; + for (unsigned i = 0; i < sz; i++) { + assertions.push_back(fg->form(i)); + } + ctx.display_smt2_benchmark(ctx.regular_stream(), assertions.size(), assertions.c_ptr()); + } + else { + // create a big OR + expr_ref_buffer or_args(m); + ptr_vector formulas; + for (unsigned i = 0; i < num_goals; i++) { + formulas.reset(); + result_goals[i]->get_formulas(formulas); + if (formulas.size() == 1) + or_args.push_back(formulas[0]); + else + or_args.push_back(m.mk_and(formulas.size(), formulas.c_ptr())); + } + expr_ref assertion_ref(m); + assertion_ref = m.mk_or(or_args.size(), or_args.c_ptr()); + expr * assertions[1] = { assertion_ref.get() }; + ctx.display_smt2_benchmark(ctx.regular_stream(), 1, assertions); + } + } + + if (!failed && mc && p.get_bool(":print-model-converter", false)) + mc->display(ctx.regular_stream()); + + if (p.get_bool(":print-statistics", false)) + display_statistics(ctx, tref.get()); + } + } +}; + + +void install_tactic_cmds(cmd_context & ctx) { + ctx.insert(alloc(declare_tactic_cmd)); + ctx.insert(alloc(get_user_tactics_cmd)); + ctx.insert(alloc(help_tactic_cmd)); + ctx.insert(alloc(check_sat_using_tactict_cmd)); + ctx.insert(alloc(apply_tactic_cmd)); + install_tactics(ctx); +} + +static tactic * mk_and_then(cmd_context & ctx, sexpr * n) { + SASSERT(n->is_composite()); + unsigned num_children = n->get_num_children(); + if (num_children < 2) + throw cmd_exception("invalid and-then combinator, at least one argument expected", n->get_line(), n->get_pos()); + if (num_children == 2) + return sexpr2tactic(ctx, n->get_child(1)); + tactic_ref_buffer args; + for (unsigned i = 1; i < num_children; i++) + args.push_back(sexpr2tactic(ctx, n->get_child(i))); + return and_then(args.size(), args.c_ptr()); +} + +static tactic * mk_or_else(cmd_context & ctx, sexpr * n) { + SASSERT(n->is_composite()); + unsigned num_children = n->get_num_children(); + if (num_children < 2) + throw cmd_exception("invalid or-else combinator, at least one argument expected", n->get_line(), n->get_pos()); + if (num_children == 2) + return sexpr2tactic(ctx, n->get_child(1)); + tactic_ref_buffer args; + for (unsigned i = 1; i < num_children; i++) + args.push_back(sexpr2tactic(ctx, n->get_child(i))); + return or_else(args.size(), args.c_ptr()); +} + +static tactic * mk_par(cmd_context & ctx, sexpr * n) { + SASSERT(n->is_composite()); + unsigned num_children = n->get_num_children(); + if (num_children < 2) + throw cmd_exception("invalid par-or combinator, at least one argument expected", n->get_line(), n->get_pos()); + if (num_children == 2) + return sexpr2tactic(ctx, n->get_child(1)); + tactic_ref_buffer args; + for (unsigned i = 1; i < num_children; i++) + args.push_back(sexpr2tactic(ctx, n->get_child(i))); + return par(args.size(), args.c_ptr()); +} + +static tactic * mk_par_then(cmd_context & ctx, sexpr * n) { + SASSERT(n->is_composite()); + unsigned num_children = n->get_num_children(); + if (num_children < 2) + throw cmd_exception("invalid par-then combinator, at least one argument expected", n->get_line(), n->get_pos()); + if (num_children == 2) + return sexpr2tactic(ctx, n->get_child(1)); + tactic_ref_buffer args; + for (unsigned i = 1; i < num_children; i++) + args.push_back(sexpr2tactic(ctx, n->get_child(i))); + return par_and_then(args.size(), args.c_ptr()); +} + +static tactic * mk_try_for(cmd_context & ctx, sexpr * n) { + SASSERT(n->is_composite()); + unsigned num_children = n->get_num_children(); + if (num_children != 3) + throw cmd_exception("invalid try-for combinator, two arguments expected", n->get_line(), n->get_pos()); + if (!n->get_child(2)->is_numeral() || !n->get_child(2)->get_numeral().is_unsigned()) + throw cmd_exception("invalid try-for combinator, second argument must be an unsigned integer", n->get_line(), n->get_pos()); + tactic * t = sexpr2tactic(ctx, n->get_child(1)); + unsigned timeout = n->get_child(2)->get_numeral().get_unsigned(); + return try_for(t, timeout); +} + +static tactic * mk_repeat(cmd_context & ctx, sexpr * n) { + SASSERT(n->is_composite()); + unsigned num_children = n->get_num_children(); + if (num_children != 3 && num_children != 2) + throw cmd_exception("invalid repeat combinator, one or two arguments expected", n->get_line(), n->get_pos()); + unsigned max = UINT_MAX; + if (num_children == 3) { + if (!n->get_child(2)->is_numeral() || !n->get_child(2)->get_numeral().is_unsigned()) + throw cmd_exception("invalid repeat combinator, second argument must be an unsigned integer", n->get_line(), n->get_pos()); + max = n->get_child(2)->get_numeral().get_unsigned(); + } + tactic * t = sexpr2tactic(ctx, n->get_child(1)); + return repeat(t, max); +} + +static tactic * mk_using_params(cmd_context & ctx, sexpr * n) { + SASSERT(n->is_composite()); + unsigned num_children = n->get_num_children(); + if (num_children < 2) + throw cmd_exception("invalid using-params combinator, at least one argument expected", n->get_line(), n->get_pos()); + if (num_children == 2) + return sexpr2tactic(ctx, n->get_child(1)); + tactic_ref t = sexpr2tactic(ctx, n->get_child(1)); + param_descrs descrs; + t->collect_param_descrs(descrs); + params_ref p; + unsigned i = 2; + while (i < num_children) { + sexpr * c = n->get_child(i); + i++; + if (!c->is_keyword()) + throw cmd_exception("invalid using-params combinator, keyword expected", c->get_line(), c->get_pos()); + if (i == num_children) + throw cmd_exception("invalid using-params combinator, parameter value expected", c->get_line(), c->get_pos()); + symbol param_name = c->get_symbol(); + c = n->get_child(i); + i++; + switch (descrs.get_kind(param_name)) { + case CPK_INVALID: + throw cmd_exception("invalid using-params combinator, unknown parameter ", param_name, c->get_line(), c->get_pos()); + case CPK_BOOL: + if (!c->is_symbol() || (c->get_symbol() != "true" && c->get_symbol() != "false")) + throw cmd_exception("invalid parameter value, true or false expected", c->get_line(), c->get_pos()); + p.set_bool(param_name, c->get_symbol() == "true"); + break; + case CPK_UINT: + if (!c->is_numeral() || !c->get_numeral().is_unsigned()) + throw cmd_exception("invalid parameter value, unsigned integer expected", c->get_line(), c->get_pos()); + p.set_uint(param_name, c->get_numeral().get_unsigned()); + break; + case CPK_NUMERAL: + if (!c->is_numeral()) + throw cmd_exception("invalid parameter value, numeral expected", c->get_line(), c->get_pos()); + p.set_rat(param_name, c->get_numeral()); + break; + case CPK_SYMBOL: + if (!c->is_symbol()) + throw cmd_exception("invalid parameter value, symbol expected", c->get_line(), c->get_pos()); + p.set_sym(param_name, c->get_symbol()); + break; + case CPK_DOUBLE: + if (!c->is_numeral()) + throw cmd_exception("invalid parameter value, numeral expected", c->get_line(), c->get_pos()); + p.set_double(param_name, c->get_numeral().get_double()); + break; + default: + throw cmd_exception("invalid using-params combinator, unsupported parameter kind"); + } + } + return using_params(t.get(), p); +} + +static tactic * mk_if(cmd_context & ctx, sexpr * n) { + SASSERT(n->is_composite()); + unsigned num_children = n->get_num_children(); + if (num_children != 4) + throw cmd_exception("invalid if/conditional combinator, three arguments expected", n->get_line(), n->get_pos()); + probe_ref c = sexpr2probe(ctx, n->get_child(1)); + tactic_ref t = sexpr2tactic(ctx, n->get_child(2)); + tactic_ref e = sexpr2tactic(ctx, n->get_child(3)); + return cond(c.get(), t.get(), e.get()); +} + +static tactic * mk_fail_if(cmd_context & ctx, sexpr * n) { + SASSERT(n->is_composite()); + unsigned num_children = n->get_num_children(); + if (num_children != 2) + throw cmd_exception("invalid fail-if tactic, one argument expected", n->get_line(), n->get_pos()); + probe_ref c = sexpr2probe(ctx, n->get_child(1)); + return fail_if(c.get()); +} + +static tactic * mk_when(cmd_context & ctx, sexpr * n) { + SASSERT(n->is_composite()); + unsigned num_children = n->get_num_children(); + if (num_children != 3) + throw cmd_exception("invalid when combinator, two arguments expected", n->get_line(), n->get_pos()); + probe_ref c = sexpr2probe(ctx, n->get_child(1)); + tactic_ref t = sexpr2tactic(ctx, n->get_child(2)); + return cond(c.get(), t.get(), mk_skip_tactic()); +} + +static tactic * mk_echo(cmd_context & ctx, sexpr * n) { + SASSERT(n->is_composite()); + unsigned num_children = n->get_num_children(); + if (num_children < 2) + throw cmd_exception("invalid echo tactic, must have at least one argument", n->get_line(), n->get_pos()); + tactic_ref res; + for (unsigned i = 1; i < num_children; i++) { + sexpr * curr = n->get_child(i); + bool last = (i == num_children - 1); + tactic * t; + if (curr->is_string()) + t = mk_echo_tactic(ctx, curr->get_string().c_str(), last); + else + t = mk_probe_value_tactic(ctx, 0, sexpr2probe(ctx, curr), last); + tactic * new_res; + if (res.get() == 0) + new_res = t; + else + new_res = and_then(res.get(), t); + if (last) + return new_res; + res = new_res; + } + UNREACHABLE(); + return 0; +} + +static tactic * mk_fail_if_branching(cmd_context & ctx, sexpr * n) { + SASSERT(n->is_composite()); + unsigned num_children = n->get_num_children(); + if (num_children != 3 && num_children != 2) + throw cmd_exception("invalid fail-if-branching combinator, one or two arguments expected", n->get_line(), n->get_pos()); + unsigned threshold = 1; + if (num_children == 3) { + if (!n->get_child(2)->is_numeral() || !n->get_child(2)->get_numeral().is_unsigned()) + throw cmd_exception("invalid fail-if-branching combinator, second argument must be an unsigned integer", n->get_line(), n->get_pos()); + threshold = n->get_child(2)->get_numeral().get_unsigned(); + } + tactic * t = sexpr2tactic(ctx, n->get_child(1)); + return fail_if_branching(t, threshold); +} + +static tactic * mk_if_no_proofs(cmd_context & ctx, sexpr * n) { + SASSERT(n->is_composite()); + unsigned num_children = n->get_num_children(); + if (num_children != 2) + throw cmd_exception("invalid if-no-proofs combinator, one argument expected", n->get_line(), n->get_pos()); + tactic * t = sexpr2tactic(ctx, n->get_child(1)); + return if_no_proofs(t); +} + +static tactic * mk_if_no_models(cmd_context & ctx, sexpr * n) { + SASSERT(n->is_composite()); + unsigned num_children = n->get_num_children(); + if (num_children != 2) + throw cmd_exception("invalid if-no-models combinator, one argument expected", n->get_line(), n->get_pos()); + tactic * t = sexpr2tactic(ctx, n->get_child(1)); + return if_no_models(t); +} + +static tactic * mk_if_no_unsat_cores(cmd_context & ctx, sexpr * n) { + SASSERT(n->is_composite()); + unsigned num_children = n->get_num_children(); + if (num_children != 2) + throw cmd_exception("invalid if-no-unsat-cores combinator, one argument expected", n->get_line(), n->get_pos()); + tactic * t = sexpr2tactic(ctx, n->get_child(1)); + return if_no_unsat_cores(t); +} + +static tactic * mk_skip_if_failed(cmd_context & ctx, sexpr * n) { + SASSERT(n->is_composite()); + unsigned num_children = n->get_num_children(); + if (num_children != 2) + throw cmd_exception("invalid skip-if-failed combinator, one argument expected", n->get_line(), n->get_pos()); + tactic * t = sexpr2tactic(ctx, n->get_child(1)); + return skip_if_failed(t); +} + +tactic * sexpr2tactic(cmd_context & ctx, sexpr * n) { + if (n->is_symbol()) { + tactic_cmd * cmd = ctx.find_tactic_cmd(n->get_symbol()); + if (cmd != 0) + return cmd->mk(ctx.m()); + sexpr * decl = ctx.find_user_tactic(n->get_symbol()); + if (decl != 0) + return sexpr2tactic(ctx, decl); + throw cmd_exception("invalid tactic, unknown tactic ", n->get_symbol(), n->get_line(), n->get_pos()); + } + else if (n->is_composite()) { + unsigned num_children = n->get_num_children(); + if (num_children == 0) + throw cmd_exception("invalid tactic, arguments expected", n->get_line(), n->get_pos()); + sexpr * head = n->get_child(0); + if (!head->is_symbol()) + throw cmd_exception("invalid tactic, symbol expected", n->get_line(), n->get_pos()); + symbol const & cmd_name = head->get_symbol(); + if (cmd_name == "and-then" || cmd_name == "then") + return mk_and_then(ctx, n); + else if (cmd_name == "or-else") + return mk_or_else(ctx, n); + else if (cmd_name == "par") + return mk_par(ctx, n); + else if (cmd_name == "par-or") + return mk_par(ctx, n); + else if (cmd_name == "par-then") + return mk_par_then(ctx, n); + else if (cmd_name == "try-for") + return mk_try_for(ctx, n); + else if (cmd_name == "repeat") + return mk_repeat(ctx, n); + else if (cmd_name == "if" || cmd_name == "ite" || cmd_name == "cond") + return mk_if(ctx, n); + else if (cmd_name == "fail-if") + return mk_fail_if(ctx, n); + else if (cmd_name == "fail-if-branching") + return mk_fail_if_branching(ctx, n); + else if (cmd_name == "when") + return mk_when(ctx, n); + else if (cmd_name == "!" || cmd_name == "using-params" || cmd_name == "with") + return mk_using_params(ctx, n); + else if (cmd_name == "echo") + return mk_echo(ctx, n); + else if (cmd_name == "if-no-proofs") + return mk_if_no_proofs(ctx, n); + else if (cmd_name == "if-no-models") + return mk_if_no_models(ctx, n); + else if (cmd_name == "if-no-unsat-cores") + return mk_if_no_unsat_cores(ctx, n); + else if (cmd_name == "skip-if-failed") + return mk_skip_if_failed(ctx, n); + else + throw cmd_exception("invalid tactic, unknown tactic combinator ", cmd_name, n->get_line(), n->get_pos()); + } + else { + throw cmd_exception("invalid tactic, unexpected input", n->get_line(), n->get_pos()); + } +} + +static probe * mk_not_probe (cmd_context & ctx, sexpr * n) { + SASSERT(n->is_composite()); + unsigned num_children = n->get_num_children(); + if (num_children != 2) + throw cmd_exception("invalid probe expression, one argument expected", n->get_line(), n->get_pos()); + return mk_not(sexpr2probe(ctx, n->get_child(1))); +} + +#define MK_BIN_PROBE(NAME) \ +static probe * NAME ## _probe (cmd_context & ctx, sexpr * n) { \ + SASSERT(n->is_composite()); \ + unsigned num_children = n->get_num_children(); \ + if (num_children != 3) \ + throw cmd_exception("invalid probe expression, two arguments expected", n->get_line(), n->get_pos()); \ + ref p1 = sexpr2probe(ctx, n->get_child(1)); \ + ref p2 = sexpr2probe(ctx, n->get_child(2)); \ + return NAME(p1.get(), p2.get()); \ +} + +MK_BIN_PROBE(mk_eq); +MK_BIN_PROBE(mk_le); +MK_BIN_PROBE(mk_lt); +MK_BIN_PROBE(mk_ge); +MK_BIN_PROBE(mk_gt); +MK_BIN_PROBE(mk_implies); +MK_BIN_PROBE(mk_div); +MK_BIN_PROBE(mk_sub); + +#define MK_NARY_PROBE(NAME) \ +static probe * NAME ## _probe(cmd_context & ctx, sexpr * n) { \ + SASSERT(n->is_composite()); \ + unsigned num_children = n->get_num_children(); \ + if (num_children < 2) \ + throw cmd_exception("invalid probe, at least one argument expected", n->get_line(), n->get_pos()); \ + probe * r = sexpr2probe(ctx, n->get_child(1)); \ + if (num_children == 2) \ + return r; \ + ref prev = r; \ + unsigned i = 1; \ + while (true) { \ + r = NAME(prev.get(), sexpr2probe(ctx, n->get_child(i))); \ + if (i == num_children - 1) \ + return r; \ + i++; \ + prev = r; \ + } \ +} + +MK_NARY_PROBE(mk_and); +MK_NARY_PROBE(mk_or); +MK_NARY_PROBE(mk_add); +MK_NARY_PROBE(mk_mul); + +probe * sexpr2probe(cmd_context & ctx, sexpr * n) { + if (n->is_symbol()) { + probe_info * pinfo = ctx.find_probe(n->get_symbol()); + if (pinfo != 0) + return pinfo->get(); + throw cmd_exception("invalid probe, unknown builtin probe ", n->get_symbol(), n->get_line(), n->get_pos()); + } + else if (n->is_numeral()) { + rational const & v = n->get_numeral(); + if (!v.is_int32()) + throw cmd_exception("invalid probe, constant is too big to fit in a fixed size integer", n->get_line(), n->get_pos()); + return mk_const_probe(static_cast(v.get_int64())); + } + else if (n->is_composite()) { + unsigned num_children = n->get_num_children(); + if (num_children == 0) + throw cmd_exception("invalid probe, arguments expected", n->get_line(), n->get_pos()); + sexpr * head = n->get_child(0); + if (!head->is_symbol()) + throw cmd_exception("invalid probe, symbol expected", n->get_line(), n->get_pos()); + symbol const & p_name = head->get_symbol(); + + if (p_name == "=") + return mk_eq_probe(ctx, n); + else if (p_name == "<=") + return mk_le_probe(ctx, n); + else if (p_name == ">=") + return mk_ge_probe(ctx, n); + else if (p_name == "<") + return mk_lt_probe(ctx, n); + else if (p_name == ">") + return mk_gt_probe(ctx, n); + else if (p_name == "and") + return mk_and_probe(ctx, n); + else if (p_name == "or") + return mk_or_probe(ctx, n); + else if (p_name == "=>" || p_name == "implies") + return mk_implies_probe(ctx, n); + else if (p_name == "not") + return mk_not_probe(ctx, n); + else if (p_name == "*") + return mk_mul_probe(ctx, n); + else if (p_name == "+") + return mk_add_probe(ctx, n); + else if (p_name == "-") + return mk_sub_probe(ctx, n); + else if (p_name == "/") + return mk_div_probe(ctx, n); + else + throw cmd_exception("invalid probe, unknown probe expression ", p_name, n->get_line(), n->get_pos()); + } + else { + throw cmd_exception("invalid probe, unexpected input", n->get_line(), n->get_pos()); + } +} + diff --git a/lib/tactic_cmds.h b/lib/tactic_cmds.h new file mode 100644 index 000000000..10f1d6276 --- /dev/null +++ b/lib/tactic_cmds.h @@ -0,0 +1,67 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + tactic_cmds.h + +Abstract: + Support for tactics in SMT 2.0 frontend. + +Author: + + Leonardo (leonardo) 2011-11-20 + +Notes: + +--*/ +#ifndef _TACTIC_CMDS_H_ +#define _TACTIC_CMDS_H_ + +#include"ast.h" +#include"cmd_context_types.h" +#include"ref.h" + +class tactic; +class probe; +class tactic_factory; + +class tactic_cmd { + symbol m_name; + char const * m_descr; + tactic_factory * m_factory; +public: + tactic_cmd(symbol const & n, char const * d, tactic_factory * f): + m_name(n), m_descr(d), m_factory(f) { + SASSERT(m_factory); + } + + ~tactic_cmd(); + + symbol get_name() const { return m_name; } + + char const * get_descr() const { return m_descr; } + + tactic * mk(ast_manager & m); +}; + +void install_tactic_cmds(cmd_context & ctx); +tactic * sexpr2tactic(cmd_context & ctx, sexpr * n); + +class probe_info { + symbol m_name; + char const * m_descr; + ref m_probe; +public: + probe_info(symbol const & n, char const * d, probe * p); + ~probe_info(); + + symbol get_name() const { return m_name; } + char const * get_descr() const { return m_descr; } + + probe * get() const { return m_probe.get(); } +}; + +probe * sexpr2probe(cmd_context & ctx, sexpr * n); + +#endif diff --git a/lib/tactic_exception.cpp b/lib/tactic_exception.cpp new file mode 100644 index 000000000..73cd37162 --- /dev/null +++ b/lib/tactic_exception.cpp @@ -0,0 +1,26 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + tactic_exception.cpp + +Abstract: + + Tactic expection object. + +Author: + + Leonardo (leonardo) 2012-08-15 + +Notes: + +--*/ +#include"tactic_exception.h" + +char const * tactic_exception::g_tactic_canceled_msg = "canceled"; +char const * tactic_exception::g_tactic_max_memory_msg = "max. memory exceeded"; +char const * tactic_exception::g_tactic_max_scopes_msg = "max. scopes exceeded"; +char const * tactic_exception::g_tactic_max_steps_msg = "max. steps exceeded"; +char const * tactic_exception::g_tactic_max_frames_msg = "max. frames exceeded"; +char const * tactic_exception::g_tactic_no_proofs_msg = "tactic does not support proof generation"; diff --git a/lib/tactic_exception.h b/lib/tactic_exception.h new file mode 100644 index 000000000..16272260e --- /dev/null +++ b/lib/tactic_exception.h @@ -0,0 +1,47 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + tactic_exception.h + +Abstract: + + Tactic expection object. + +Author: + + Leonardo (leonardo) 2012-08-15 + +Notes: + +--*/ +#ifndef _TACTIC_EXCEPTION_H_ +#define _TACTIC_EXCEPTION_H_ + +#include"z3_exception.h" + +class tactic_exception : public z3_exception { +public: + static char const * g_tactic_canceled_msg; + static char const * g_tactic_max_memory_msg; + static char const * g_tactic_max_scopes_msg; + static char const * g_tactic_max_steps_msg; + static char const * g_tactic_max_frames_msg; + static char const * g_tactic_no_proofs_msg; +protected: + std::string m_msg; +public: + tactic_exception(char const * msg):m_msg(msg) {} + virtual ~tactic_exception() {} + virtual char const * msg() const { return m_msg.c_str(); } +}; + +#define TACTIC_CANCELED_MSG tactic_exception::g_tactic_canceled_msg +#define TACTIC_MAX_MEMORY_MSG tactic_exception::g_tactic_max_memory_msg +#define TACTIC_MAX_SCOPES_MSG tactic_exception::g_tactic_max_scopes_msg +#define TACTIC_MAX_STEPS_MSG tactic_exception::g_tactic_max_steps_msg +#define TACTIC_MAX_FRAMES_MSG tactic_exception::g_tactic_max_frames_msg +#define TACTIC_NO_PROOF_GEN_MSG tactic_exception::g_tactic_no_proofs_msg + +#endif diff --git a/lib/tactic_manager.cpp b/lib/tactic_manager.cpp new file mode 100644 index 000000000..3442dd9b9 --- /dev/null +++ b/lib/tactic_manager.cpp @@ -0,0 +1,62 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + tactic_manager.cpp + +Abstract: + + Collection of tactics & probes + +Author: + + Leonardo (leonardo) 2012-03-06 + +Notes: + +--*/ +#include"tactic_manager.h" + +tactic_manager::~tactic_manager() { + finalize_tactic_cmds(); + finalize_probes(); +} + +void tactic_manager::insert(tactic_cmd * c) { + symbol const & s = c->get_name(); + SASSERT(!m_name2tactic.contains(s)); + m_name2tactic.insert(s, c); + m_tactics.push_back(c); +} + +void tactic_manager::insert(probe_info * p) { + symbol const & s = p->get_name(); + SASSERT(!m_name2probe.contains(s)); + m_name2probe.insert(s, p); + m_probes.push_back(p); +} + +tactic_cmd * tactic_manager::find_tactic_cmd(symbol const & s) const { + tactic_cmd * c = 0; + m_name2tactic.find(s, c); + return c; +} + +probe_info * tactic_manager::find_probe(symbol const & s) const { + probe_info * p = 0; + m_name2probe.find(s, p); + return p; +} + +void tactic_manager::finalize_tactic_cmds() { + std::for_each(m_tactics.begin(), m_tactics.end(), delete_proc()); + m_tactics.reset(); + m_name2tactic.reset(); +} + +void tactic_manager::finalize_probes() { + std::for_each(m_probes.begin(), m_probes.end(), delete_proc()); + m_probes.reset(); + m_name2probe.reset(); +} diff --git a/lib/tactic_manager.h b/lib/tactic_manager.h new file mode 100644 index 000000000..bcc24e090 --- /dev/null +++ b/lib/tactic_manager.h @@ -0,0 +1,57 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + tactic_manager.h + +Abstract: + Collection of tactics & probes + +Author: + + Leonardo (leonardo) 2012-03-06 + +Notes: + +--*/ +#ifndef _TACTIC_MANAGER_H_ +#define _TACTIC_MANAGER_H_ + +#include"tactic_cmds.h" +#include"dictionary.h" + +class tactic_manager { +protected: + dictionary m_name2tactic; + dictionary m_name2probe; + ptr_vector m_tactics; + ptr_vector m_probes; + void finalize_tactic_cmds(); + void finalize_probes(); +public: + ~tactic_manager(); + + void insert(tactic_cmd * c); + void insert(probe_info * p); + tactic_cmd * find_tactic_cmd(symbol const & s) const; + probe_info * find_probe(symbol const & s) const; + + unsigned num_tactics() const { return m_tactics.size(); } + unsigned num_probes() const { return m_probes.size(); } + tactic_cmd * get_tactic(unsigned i) const { return m_tactics[i]; } + probe_info * get_probe(unsigned i) const { return m_probes[i]; } + + typedef ptr_vector::const_iterator tactic_cmd_iterator; + tactic_cmd_iterator begin_tactic_cmds() const { return m_tactics.begin(); } + tactic_cmd_iterator end_tactic_cmds() const { return m_tactics.end(); } + + typedef ptr_vector::const_iterator probe_iterator; + probe_iterator begin_probes() const { return m_probes.begin(); } + probe_iterator end_probes() const { return m_probes.end(); } +}; + +#endif + + + diff --git a/lib/tactical.cpp b/lib/tactical.cpp new file mode 100644 index 000000000..ae9c73a00 --- /dev/null +++ b/lib/tactical.cpp @@ -0,0 +1,1434 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + tactical.h + +Abstract: + + Basic combinators + +Author: + + Leonardo (leonardo) 2011-10-13 + +Notes: + +--*/ +#include"tactical.h" +#include"scoped_timer.h" +#include"cancel_eh.h" +#include"cooperate.h" +#include"scoped_ptr_vector.h" +#include"z3_omp.h" + +class binary_tactical : public tactic { +protected: + tactic * m_t1; + tactic * m_t2; + volatile bool m_cancel; + + void checkpoint() { + if (m_cancel) + throw tactic_exception(TACTIC_CANCELED_MSG); + } + +public: + binary_tactical(tactic * t1, tactic * t2): + m_t1(t1), + m_t2(t2), + m_cancel(false) { + SASSERT(m_t1); + SASSERT(m_t2); + m_t1->inc_ref(); + m_t2->inc_ref(); + } + + virtual ~binary_tactical() { + tactic * t1 = m_t1; + tactic * t2 = m_t2; + #pragma omp critical (tactic_cancel) + { + m_t1 = 0; + m_t2 = 0; + } + t1->dec_ref(); + t2->dec_ref(); + } + + virtual void updt_params(params_ref const & p) { + m_t1->updt_params(p); + m_t2->updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + m_t1->collect_param_descrs(r); + m_t2->collect_param_descrs(r); + } + + virtual void collect_statistics(statistics & st) const { + m_t1->collect_statistics(st); + m_t2->collect_statistics(st); + } + + virtual void reset_statistics() { + m_t1->reset_statistics(); + m_t2->reset_statistics(); + } + + virtual void cleanup() { + m_t1->cleanup(); + m_t2->cleanup(); + } + + virtual void reset() { + m_t1->reset(); + m_t2->reset(); + } + + virtual void set_front_end_params(front_end_params & p) { + m_t1->set_front_end_params(p); + m_t2->set_front_end_params(p); + } + + virtual void set_logic(symbol const & l) { + m_t1->set_logic(l); + m_t2->set_logic(l); + } + + virtual void set_progress_callback(progress_callback * callback) { + m_t1->set_progress_callback(callback); + m_t2->set_progress_callback(callback); + } + +protected: + /** + \brief Reset cancel flag of t if this was not canceled. + */ + void parent_reset_cancel(tactic & t) { + #pragma omp critical (tactic_cancel) + { + if (!m_cancel) { + t.set_cancel(false); + } + } + } + + virtual void set_cancel(bool f) { + m_cancel = f; + m_t1->set_cancel(f); + m_t2->set_cancel(f); + } + + + template + tactic * translate_core(ast_manager & m) { + tactic * new_t1 = m_t1->translate(m); + tactic * new_t2 = m_t2->translate(m); + return alloc(T, new_t1, new_t2); + } +}; + +struct false_pred { + bool operator()(goal * g) { return false; } +}; + + + +class and_then_tactical : public binary_tactical { +public: + and_then_tactical(tactic * t1, tactic * t2):binary_tactical(t1, t2) {} + virtual ~and_then_tactical() {} + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + + bool models_enabled = in->models_enabled(); + bool proofs_enabled = in->proofs_enabled(); + bool cores_enabled = in->unsat_core_enabled(); + + ast_manager & m = in->m(); + goal_ref_buffer r1; + model_converter_ref mc1; + proof_converter_ref pc1; + expr_dependency_ref core1(m); + result.reset(); + mc = 0; + pc = 0; + core = 0; + m_t1->operator()(in, r1, mc1, pc1, core1); + SASSERT(!is_decided(r1) || (!pc1 && !core1)); // the pc and core of decided goals is 0 + unsigned r1_size = r1.size(); + SASSERT(r1_size > 0); + checkpoint(); + if (r1_size == 1) { + if (r1[0]->is_decided()) { + result.push_back(r1[0]); + if (models_enabled) mc = mc1; + SASSERT(!pc); SASSERT(!core); + return; + } + goal_ref r1_0 = r1[0]; + m_t2->operator()(r1_0, result, mc, pc, core); + if (models_enabled) mc = concat(mc1.get(), mc.get()); + if (proofs_enabled) pc = concat(pc1.get(), pc.get()); + if (cores_enabled) core = m.mk_join(core1.get(), core); + } + else { + if (cores_enabled) core = core1; + proof_converter_ref_buffer pc_buffer; + model_converter_ref_buffer mc_buffer; + sbuffer sz_buffer; + goal_ref_buffer r2; + for (unsigned i = 0; i < r1_size; i++) { + checkpoint(); + goal_ref g = r1[i]; + r2.reset(); + model_converter_ref mc2; + proof_converter_ref pc2; + expr_dependency_ref core2(m); + m_t2->operator()(g, r2, mc2, pc2, core2); + if (is_decided(r2)) { + SASSERT(r2.size() == 1); + if (is_decided_sat(r2)) { + // found solution... + result.push_back(r2[0]); + if (models_enabled) { + // mc2 contains the actual model + model_ref md; + md = alloc(model, m); + apply(mc2, md, 0); + apply(mc1, md, i); + mc = model2model_converter(md.get()); + } + SASSERT(!pc); SASSERT(!core); + return; + } + else { + SASSERT(is_decided_unsat(r2)); + // the proof and unsat core of a decided_unsat goal are stored in the node itself. + // pc2 and core2 must be 0. + SASSERT(!pc2); + SASSERT(!core2); + if (models_enabled) mc_buffer.push_back(0); + if (proofs_enabled) pc_buffer.push_back(proof2proof_converter(m, r2[0]->pr(0))); + if (models_enabled || proofs_enabled) sz_buffer.push_back(0); + if (cores_enabled) core = m.mk_join(core.get(), r2[0]->dep(0)); + } + } + else { + result.append(r2.size(), r2.c_ptr()); + if (models_enabled) mc_buffer.push_back(mc2.get()); + if (proofs_enabled) pc_buffer.push_back(pc2.get()); + if (models_enabled || proofs_enabled) sz_buffer.push_back(r2.size()); + if (cores_enabled) core = m.mk_join(core.get(), core2.get()); + } + } + + if (result.empty()) { + // all subgoals were shown to be unsat. + // create an decided_unsat goal with the proof + in->reset_all(); + proof_ref pr(m); + if (proofs_enabled) + apply(m, pc1, pc_buffer, pr); + SASSERT(cores_enabled || core == 0); + in->assert_expr(m.mk_false(), pr, core); + core = 0; + result.push_back(in.get()); + SASSERT(!mc); SASSERT(!pc); SASSERT(!core); + } + else { + if (models_enabled) mc = concat(mc1.get(), mc_buffer.size(), mc_buffer.c_ptr(), sz_buffer.c_ptr()); + if (proofs_enabled) pc = concat(pc1.get(), pc_buffer.size(), pc_buffer.c_ptr(), sz_buffer.c_ptr()); + SASSERT(cores_enabled || core == 0); + } + } + } + + virtual tactic * translate(ast_manager & m) { + return translate_core(m); + } + +}; + +tactic * and_then(tactic * t1, tactic * t2) { + return alloc(and_then_tactical, t1, t2); +} + +tactic * and_then(tactic * t1, tactic * t2, tactic * t3) { + return and_then(t1, and_then(t2, t3)); +} + +tactic * and_then(tactic * t1, tactic * t2, tactic * t3, tactic * t4) { + return and_then(t1, and_then(t2, t3, t4)); +} + +tactic * and_then(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5) { + return and_then(t1, and_then(t2, t3, t4, t5)); +} + +tactic * and_then(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6) { + return and_then(t1, and_then(t2, t3, t4, t5, t6)); +} + +tactic * and_then(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7) { + return and_then(t1, and_then(t2, t3, t4, t5, t6, t7)); +} + +tactic * and_then(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7, tactic * t8) { + return and_then(t1, and_then(t2, t3, t4, t5, t6, t7, t8)); +} + +tactic * and_then(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7, tactic * t8, tactic * t9) { + return and_then(t1, and_then(t2, t3, t4, t5, t6, t7, t8, t9)); +} + +tactic * and_then(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7, tactic * t8, tactic * t9, tactic * t10) { + return and_then(t1, and_then(t2, t3, t4, t5, t6, t7, t8, t9, t10)); +} + +tactic * and_then(unsigned num, tactic * const * ts) { + SASSERT(num > 0); + unsigned i = num - 1; + tactic * r = ts[i]; + while (i > 0) { + --i; + r = and_then(ts[i], r); + } + return r; +} + +class nary_tactical : public tactic { +protected: + ptr_vector m_ts; + volatile bool m_cancel; + + void checkpoint() { + if (m_cancel) + throw tactic_exception(TACTIC_CANCELED_MSG); + } +public: + nary_tactical(unsigned num, tactic * const * ts): + m_cancel(false) { + for (unsigned i = 0; i < num; i++) { + SASSERT(ts[i]); + m_ts.push_back(ts[i]); + ts[i]->inc_ref(); + } + } + + virtual ~nary_tactical() { + ptr_buffer old_ts; + unsigned sz = m_ts.size(); + old_ts.append(sz, m_ts.c_ptr()); + #pragma omp critical (tactic_cancel) + { + for (unsigned i = 0; i < sz; i++) { + m_ts[i] = 0; + } + } + for (unsigned i = 0; i < sz; i++) { + old_ts[i]->dec_ref(); + } + } + + virtual void updt_params(params_ref const & p) { + TRACE("nary_tactical_updt_params", tout << "updt_params: " << p << "\n";); + ptr_vector::iterator it = m_ts.begin(); + ptr_vector::iterator end = m_ts.end(); + for (; it != end; ++it) + (*it)->updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + ptr_vector::iterator it = m_ts.begin(); + ptr_vector::iterator end = m_ts.end(); + for (; it != end; ++it) + (*it)->collect_param_descrs(r); + } + + virtual void collect_statistics(statistics & st) const { + ptr_vector::const_iterator it = m_ts.begin(); + ptr_vector::const_iterator end = m_ts.end(); + for (; it != end; ++it) + (*it)->collect_statistics(st); + } + + virtual void reset_statistics() { + ptr_vector::const_iterator it = m_ts.begin(); + ptr_vector::const_iterator end = m_ts.end(); + for (; it != end; ++it) + (*it)->reset_statistics(); + } + + virtual void cleanup() { + ptr_vector::iterator it = m_ts.begin(); + ptr_vector::iterator end = m_ts.end(); + for (; it != end; ++it) + (*it)->cleanup(); + } + + virtual void reset() { + ptr_vector::iterator it = m_ts.begin(); + ptr_vector::iterator end = m_ts.end(); + for (; it != end; ++it) + (*it)->reset(); + } + + virtual void set_front_end_params(front_end_params & p) { + ptr_vector::iterator it = m_ts.begin(); + ptr_vector::iterator end = m_ts.end(); + for (; it != end; ++it) + (*it)->set_front_end_params(p); + } + + virtual void set_logic(symbol const & l) { + ptr_vector::iterator it = m_ts.begin(); + ptr_vector::iterator end = m_ts.end(); + for (; it != end; ++it) + (*it)->set_logic(l); + } + + virtual void set_progress_callback(progress_callback * callback) { + ptr_vector::iterator it = m_ts.begin(); + ptr_vector::iterator end = m_ts.end(); + for (; it != end; ++it) + (*it)->set_progress_callback(callback); + } + +protected: + /** + \brief Reset cancel flag of st if this was not canceled. + */ + void parent_reset_cancel(tactic & t) { + #pragma omp critical (tactic_cancel) + { + if (!m_cancel) { + t.set_cancel(false); + } + } + } + + virtual void set_cancel(bool f) { + m_cancel = f; + ptr_vector::iterator it = m_ts.begin(); + ptr_vector::iterator end = m_ts.end(); + for (; it != end; ++it) + if (*it) + (*it)->set_cancel(f); + } + + template + tactic * translate_core(ast_manager & m) { + ptr_buffer new_ts; + ptr_vector::iterator it = m_ts.begin(); + ptr_vector::iterator end = m_ts.end(); + for (; it != end; ++it) { + tactic * curr = *it; + tactic * new_curr = curr->translate(m); + new_ts.push_back(new_curr); + } + return alloc(T, new_ts.size(), new_ts.c_ptr()); + } + +}; + +class or_else_tactical : public nary_tactical { +public: + or_else_tactical(unsigned num, tactic * const * ts):nary_tactical(num, ts) { SASSERT(num > 0); } + + virtual ~or_else_tactical() {} + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + goal orig(*(in.get())); + unsigned sz = m_ts.size(); + unsigned i; + for (i = 0; i < sz; i++) { + checkpoint(); + tactic * t = m_ts[i]; + result.reset(); + mc = 0; + pc = 0; + core = 0; + SASSERT(sz > 0); + if (i < sz - 1) { + try { + t->operator()(in, result, mc, pc, core); + return; + } + catch (tactic_exception &) { + } + } + else { + t->operator()(in, result, mc, pc, core); + return; + } + in->reset_all(); + in->copy_from(orig); + } + } + + virtual tactic * translate(ast_manager & m) { return translate_core(m); } +}; + +tactic * or_else(unsigned num, tactic * const * ts) { + return alloc(or_else_tactical, num, ts); +} + +tactic * or_else(tactic * t1, tactic * t2) { + tactic * ts[2] = { t1, t2 }; + return or_else(2, ts); +} + +tactic * or_else(tactic * t1, tactic * t2, tactic * t3) { + tactic * ts[3] = { t1, t2, t3 }; + return or_else(3, ts); +} + +tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4) { + tactic * ts[4] = { t1, t2, t3, t4 }; + return or_else(4, ts); +} + +tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5) { + tactic * ts[5] = { t1, t2, t3, t4, t5 }; + return or_else(5, ts); +} + +tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6) { + tactic * ts[6] = { t1, t2, t3, t4, t5, t6 }; + return or_else(6, ts); +} + +tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7) { + tactic * ts[7] = { t1, t2, t3, t4, t5, t6, t7 }; + return or_else(7, ts); +} + +tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7, tactic * t8) { + tactic * ts[8] = { t1, t2, t3, t4, t5, t6, t7, t8 }; + return or_else(8, ts); +} + +tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7, tactic * t8, tactic * t9) { + tactic * ts[9] = { t1, t2, t3, t4, t5, t6, t7, t8, t9 }; + return or_else(9, ts); +} + +tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7, tactic * t8, tactic * t9, tactic * t10) { + tactic * ts[10] = { t1, t2, t3, t4, t5, t6, t7, t8, t9, t10 }; + return or_else(10, ts); +} + +enum par_exception_kind { + TACTIC_EX, + DEFAULT_EX, + ERROR_EX +}; + +class par_tactical : public or_else_tactical { +public: + par_tactical(unsigned num, tactic * const * ts):or_else_tactical(num, ts) {} + virtual ~par_tactical() {} + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + + if (omp_in_parallel()) { + // execute tasks sequentially + or_else_tactical::operator()(in, result, mc, pc, core); + return; + } + + ast_manager & m = in->m(); + + scoped_ptr_vector managers; + goal_ref_vector in_copies; + tactic_ref_vector ts; + unsigned sz = m_ts.size(); + for (unsigned i = 0; i < sz; i++) { + ast_manager * new_m = alloc(ast_manager, m, !m.proof_mode()); + managers.push_back(new_m); + ast_translation translator(m, *new_m); + in_copies.push_back(in->translate(translator)); + ts.push_back(m_ts.get(i)->translate(*new_m)); + } + + unsigned finished_id = UINT_MAX; + par_exception_kind ex_kind; + std::string ex_msg; + unsigned error_code; + + #pragma omp parallel for + for (int i = 0; i < static_cast(sz); i++) { + goal_ref_buffer _result; + model_converter_ref _mc; + proof_converter_ref _pc; + expr_dependency_ref _core(*(managers[i])); + + goal_ref in_copy = in_copies[i]; + tactic & t = *(ts.get(i)); + + try { + t(in_copy, _result, _mc, _pc, _core); + bool first = false; + #pragma omp critical (par_tactical) + { + if (finished_id == UINT_MAX) { + finished_id = i; + first = true; + } + } + if (first) { + for (unsigned j = 0; j < sz; j++) { + if (static_cast(i) != j) + ts.get(j)->cancel(); + } + ast_translation translator(*(managers[i]), m, false); + for (unsigned k = 0; k < _result.size(); k++) { + result.push_back(_result[k]->translate(translator)); + } + mc = _mc ? _mc->translate(translator) : 0; + pc = _pc ? _pc->translate(translator) : 0; + expr_dependency_translation td(translator); + core = td(_core); + } + } + catch (tactic_exception & ex) { + if (i == 0) { + ex_kind = TACTIC_EX; + ex_msg = ex.msg(); + } + } + catch (z3_error & err) { + if (i == 0) { + ex_kind = ERROR_EX; + error_code = err.error_code(); + } + } + catch (z3_exception & z3_ex) { + if (i == 0) { + ex_kind = DEFAULT_EX; + ex_msg = z3_ex.msg(); + } + } + } + if (finished_id == UINT_MAX) { + mc = 0; + switch (ex_kind) { + case ERROR_EX: throw z3_error(error_code); + case TACTIC_EX: throw tactic_exception(ex_msg.c_str()); + default: + throw default_exception(ex_msg.c_str()); + } + } + } + + virtual tactic * translate(ast_manager & m) { return translate_core(m); } +}; + +tactic * par(unsigned num, tactic * const * ts) { + return alloc(par_tactical, num, ts); +} + +tactic * par(tactic * t1, tactic * t2) { + tactic * ts[2] = { t1, t2 }; + return par(2, ts); +} + +tactic * par(tactic * t1, tactic * t2, tactic * t3) { + tactic * ts[3] = { t1, t2, t3 }; + return par(3, ts); +} + +tactic * par(tactic * t1, tactic * t2, tactic * t3, tactic * t4) { + tactic * ts[4] = { t1, t2, t3, t4 }; + return par(4, ts); +} + +class par_and_then_tactical : public and_then_tactical { +public: + par_and_then_tactical(tactic * t1, tactic * t2):and_then_tactical(t1, t2) {} + virtual ~par_and_then_tactical() {} + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + if (omp_in_parallel()) { + // execute tasks sequentially + and_then_tactical::operator()(in, result, mc, pc, core); + return; + } + + bool models_enabled = in->models_enabled(); + bool proofs_enabled = in->proofs_enabled(); + bool cores_enabled = in->unsat_core_enabled(); + + ast_manager & m = in->m(); + goal_ref_buffer r1; + model_converter_ref mc1; + proof_converter_ref pc1; + expr_dependency_ref core1(m); + result.reset(); + mc = 0; + pc = 0; + core = 0; + m_t1->operator()(in, r1, mc1, pc1, core1); + SASSERT(!is_decided(r1) || (!pc1 && !core1)); // the pc and core of decided goals is 0 + unsigned r1_size = r1.size(); + SASSERT(r1_size > 0); + checkpoint(); + if (r1_size == 1) { + // Only one subgoal created... no need for parallelism + if (r1[0]->is_decided()) { + result.push_back(r1[0]); + if (models_enabled) mc = mc1; + SASSERT(!pc); SASSERT(!core); + return; + } + goal_ref r1_0 = r1[0]; + m_t2->operator()(r1_0, result, mc, pc, core); + if (models_enabled) mc = concat(mc1.get(), mc.get()); + if (proofs_enabled) pc = concat(pc1.get(), pc.get()); + if (cores_enabled) core = m.mk_join(core1.get(), core); + } + else { + if (cores_enabled) core = core1; + + scoped_ptr_vector managers; + tactic_ref_vector ts2; + goal_ref_vector g_copies; + + ast_manager & m = in->m(); + + for (unsigned i = 0; i < r1_size; i++) { + ast_manager * new_m = alloc(ast_manager, m, !m.proof_mode()); + managers.push_back(new_m); + ast_translation translator(m, *new_m); + g_copies.push_back(r1[i]->translate(translator)); + ts2.push_back(m_t2->translate(*new_m)); + } + + proof_converter_ref_buffer pc_buffer; + model_converter_ref_buffer mc_buffer; + scoped_ptr_vector core_buffer; + scoped_ptr_vector goals_vect; + + pc_buffer.resize(r1_size); + mc_buffer.resize(r1_size); + core_buffer.resize(r1_size); + goals_vect.resize(r1_size); + + bool found_solution = false; + bool failed = false; + par_exception_kind ex_kind; + unsigned error_code; + std::string ex_msg; + + #pragma omp parallel for + for (int i = 0; i < static_cast(r1_size); i++) { + ast_manager & new_m = *(managers[i]); + goal_ref new_g = g_copies[i]; + + goal_ref_buffer r2; + model_converter_ref mc2; + proof_converter_ref pc2; + expr_dependency_ref core2(new_m); + + bool curr_failed = false; + + try { + ts2[i]->operator()(new_g, r2, mc2, pc2, core2); + } + catch (tactic_exception & ex) { + #pragma omp critical (par_and_then_tactical) + { + if (!failed && !found_solution) { + curr_failed = true; + failed = true; + ex_kind = TACTIC_EX; + ex_msg = ex.msg(); + } + } + } + catch (z3_error & err) { + #pragma omp critical (par_and_then_tactical) + { + if (!failed && !found_solution) { + curr_failed = true; + failed = true; + ex_kind = ERROR_EX; + error_code = err.error_code(); + } + } + } + catch (z3_exception & z3_ex) { + #pragma omp critical (par_and_then_tactical) + { + if (!failed && !found_solution) { + curr_failed = true; + failed = true; + ex_kind = DEFAULT_EX; + ex_msg = z3_ex.msg(); + } + } + } + + if (curr_failed) { + for (unsigned j = 0; j < r1_size; j++) { + if (static_cast(i) != j) + ts2.get(j)->cancel(); + } + } + else { + if (is_decided(r2)) { + SASSERT(r2.size() == 1); + if (is_decided_sat(r2)) { + // found solution... + bool first = false; + #pragma omp critical (par_and_then_tactical) + { + if (!found_solution) { + failed = false; + found_solution = true; + first = true; + } + } + if (first) { + for (unsigned j = 0; j < r1_size; j++) { + if (static_cast(i) != j) + ts2.get(j)->cancel(); + } + ast_translation translator(new_m, m, false); + SASSERT(r2.size() == 1); + result.push_back(r2[0]->translate(translator)); + if (models_enabled) { + // mc2 contains the actual model + mc2 = mc2 ? mc2->translate(translator) : 0; + model_ref md; + md = alloc(model, m); + apply(mc2, md, 0); + apply(mc1, md, i); + mc = model2model_converter(md.get()); + } + SASSERT(!pc); SASSERT(!core); + } + } + else { + SASSERT(is_decided_unsat(r2)); + // the proof and unsat core of a decided_unsat goal are stored in the node itself. + // pc2 and core2 must be 0. + SASSERT(!pc2); + SASSERT(!core2); + + if (models_enabled) mc_buffer.set(i, 0); + if (proofs_enabled) { + proof * pr = r2[0]->pr(0); + pc_buffer.push_back(proof2proof_converter(m, pr)); + } + if (cores_enabled && r2[0]->dep(0) != 0) { + expr_dependency_ref * new_dep = alloc(expr_dependency_ref, new_m); + *new_dep = r2[0]->dep(0); + core_buffer.set(i, new_dep); + } + } + } + else { + goal_ref_buffer * new_r2 = alloc(goal_ref_buffer); + goals_vect.set(i, new_r2); + new_r2->append(r2.size(), r2.c_ptr()); + mc_buffer.set(i, mc2.get()); + pc_buffer.set(i, pc2.get()); + if (cores_enabled && core2 != 0) { + expr_dependency_ref * new_dep = alloc(expr_dependency_ref, new_m); + *new_dep = core2; + core_buffer.set(i, new_dep); + } + } + } + } + + if (failed) { + switch (ex_kind) { + case ERROR_EX: throw z3_error(error_code); + case TACTIC_EX: throw tactic_exception(ex_msg.c_str()); + default: + throw default_exception(ex_msg.c_str()); + } + } + + if (found_solution) + return; + + core = 0; + sbuffer sz_buffer; + for (unsigned i = 0; i < r1_size; i++) { + ast_translation translator(*(managers[i]), m, false); + goal_ref_buffer * r = goals_vect[i]; + if (r != 0) { + for (unsigned k = 0; k < r->size(); k++) { + result.push_back((*r)[k]->translate(translator)); + } + sz_buffer.push_back(r->size()); + } + else { + sz_buffer.push_back(0); + } + if (mc_buffer[i] != 0) + mc_buffer.set(i, mc_buffer[i]->translate(translator)); + if (pc_buffer[i] != 0) + pc_buffer.set(i, pc_buffer[i]->translate(translator)); + expr_dependency_translation td(translator); + if (core_buffer[i] != 0) { + expr_dependency_ref curr_core(m); + curr_core = td(*(core_buffer[i])); + core = m.mk_join(curr_core, core); + } + } + + if (result.empty()) { + // all subgoals were shown to be unsat. + // create an decided_unsat goal with the proof + in->reset_all(); + proof_ref pr(m); + if (proofs_enabled) + apply(m, pc1, pc_buffer, pr); + SASSERT(cores_enabled || core == 0); + in->assert_expr(m.mk_false(), pr, core); + core = 0; + result.push_back(in.get()); + SASSERT(!mc); SASSERT(!pc); SASSERT(!core); + } + else { + if (models_enabled) mc = concat(mc1.get(), mc_buffer.size(), mc_buffer.c_ptr(), sz_buffer.c_ptr()); + if (proofs_enabled) pc = concat(pc1.get(), pc_buffer.size(), pc_buffer.c_ptr(), sz_buffer.c_ptr()); + SASSERT(cores_enabled || core == 0); + } + } + } + + virtual tactic * translate(ast_manager & m) { + return translate_core(m); + } + +}; + +// Similar to and_then combinator, but t2 is applied in parallel to all subgoals produced by t1 +tactic * par_and_then(tactic * t1, tactic * t2) { + return alloc(par_and_then_tactical, t1, t2); +} + +tactic * par_and_then(unsigned num, tactic * const * ts) { + unsigned i = num - 1; + tactic * r = ts[i]; + while (i > 0) { + --i; + r = par_and_then(ts[i], r); + } + return r; +} + +class unary_tactical : public tactic { +protected: + tactic * m_t; + volatile bool m_cancel; + + void checkpoint() { + if (m_cancel) + throw tactic_exception(TACTIC_CANCELED_MSG); + + } + +public: + unary_tactical(tactic * t): + m_t(t), + m_cancel(false) { + SASSERT(t); + t->inc_ref(); + } + + virtual ~unary_tactical() { + tactic * t = m_t; + #pragma omp critical (tactic_cancel) + { + m_t = 0; + } + t->dec_ref(); + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + m_t->operator()(in, result, mc, pc, core); + } + + virtual void cleanup(void) { m_t->cleanup(); } + virtual void collect_statistics(statistics & st) const { m_t->collect_statistics(st); } + virtual void reset_statistics() { m_t->reset_statistics(); } + virtual void set_front_end_params(front_end_params & p) { m_t->set_front_end_params(p); } + virtual void updt_params(params_ref const & p) { m_t->updt_params(p); } + virtual void collect_param_descrs(param_descrs & r) { m_t->collect_param_descrs(r); } + virtual void reset() { m_t->reset(); } + virtual void set_logic(symbol const& l) { m_t->set_logic(l); } + virtual void set_progress_callback(progress_callback * callback) { m_t->set_progress_callback(callback); } +protected: + virtual void set_cancel(bool f) { + m_cancel = f; + if (m_t) + m_t->set_cancel(f); + } + + template + tactic * translate_core(ast_manager & m) { + tactic * new_t = m_t->translate(m); + return alloc(T, new_t); + } +}; + +class repeat_tactical : public unary_tactical { + unsigned m_max_depth; + + void operator()(unsigned depth, + goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + // TODO: implement a non-recursive version. + if (depth > m_max_depth) { + result.push_back(in.get()); + mc = 0; + pc = 0; + core = 0; + return; + } + + bool models_enabled = in->models_enabled(); + bool proofs_enabled = in->proofs_enabled(); + bool cores_enabled = in->unsat_core_enabled(); + + ast_manager & m = in->m(); + goal_ref_buffer r1; + model_converter_ref mc1; + proof_converter_ref pc1; + expr_dependency_ref core1(m); + result.reset(); + mc = 0; + pc = 0; + core = 0; + { + goal orig_in(in->m()); + orig_in.copy_from(*(in.get())); + m_t->operator()(in, r1, mc1, pc1, core1); + if (is_equal(orig_in, *(in.get()))) { + result.push_back(r1[0]); + if (models_enabled) mc = mc1; + if (proofs_enabled) pc = pc1; + if (cores_enabled) core = core1; + return; + } + } + unsigned r1_size = r1.size(); + SASSERT(r1_size > 0); + checkpoint(); + if (r1_size == 1) { + if (r1[0]->is_decided()) { + result.push_back(r1[0]); + if (models_enabled) mc = mc1; + SASSERT(!pc); SASSERT(!core); + return; + } + goal_ref r1_0 = r1[0]; + operator()(depth+1, r1_0, result, mc, pc, core); + if (models_enabled) mc = concat(mc.get(), mc1.get()); + if (proofs_enabled) pc = concat(pc.get(), pc1.get()); + if (cores_enabled) core = m.mk_join(core1.get(), core); + } + else { + if (cores_enabled) core = core1; + proof_converter_ref_buffer pc_buffer; + model_converter_ref_buffer mc_buffer; + sbuffer sz_buffer; + goal_ref_buffer r2; + for (unsigned i = 0; i < r1_size; i++) { + checkpoint(); + goal_ref g = r1[i]; + r2.reset(); + model_converter_ref mc2; + proof_converter_ref pc2; + expr_dependency_ref core2(m); + operator()(depth+1, g, r2, mc2, pc2, core2); + if (is_decided(r2)) { + SASSERT(r2.size() == 1); + if (is_decided_sat(r2)) { + // found solution... + result.push_back(r2[0]); + if (models_enabled) { + // mc2 contains the actual model + model_ref md; + if (mc2) (*mc2)(md, 0); + if (mc1) (*mc1)(md, i); + mc = model2model_converter(md.get()); + } + SASSERT(!pc); SASSERT(!core); + return; + } + else { + SASSERT(is_decided_unsat(r2)); + SASSERT(!pc2); + SASSERT(!core2); + if (models_enabled) mc_buffer.push_back(0); + if (proofs_enabled) pc_buffer.push_back(proof2proof_converter(m, r2[0]->pr(0))); + if (models_enabled || proofs_enabled) sz_buffer.push_back(0); + if (cores_enabled) core = m.mk_join(core.get(), r2[0]->dep(0)); + } + } + else { + result.append(r2.size(), r2.c_ptr()); + if (models_enabled) mc_buffer.push_back(mc2.get()); + if (proofs_enabled) pc_buffer.push_back(pc2.get()); + if (models_enabled || proofs_enabled) sz_buffer.push_back(r2.size()); + if (cores_enabled) core = m.mk_join(core.get(), core2.get()); + } + } + + if (result.empty()) { + // all subgoals were shown to be unsat. + // create an decided_unsat goal with the proof + in->reset_all(); + proof_ref pr(m); + if (proofs_enabled) + apply(m, pc1, pc_buffer, pr); + SASSERT(cores_enabled || core == 0); + in->assert_expr(m.mk_false(), pr, core); + core = 0; + result.push_back(in.get()); + SASSERT(!mc); SASSERT(!pc); SASSERT(!core); + } + else { + if (models_enabled) mc = concat(mc1.get(), mc_buffer.size(), mc_buffer.c_ptr(), sz_buffer.c_ptr()); + if (proofs_enabled) pc = concat(pc1.get(), pc_buffer.size(), pc_buffer.c_ptr(), sz_buffer.c_ptr()); + SASSERT(cores_enabled || core == 0); + } + } + } + +public: + repeat_tactical(tactic * t, unsigned max_depth): + unary_tactical(t), + m_max_depth(max_depth) { + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + operator()(0, in, result, mc, pc, core); + } + + virtual tactic * translate(ast_manager & m) { + tactic * new_t = m_t->translate(m); + return alloc(repeat_tactical, new_t, m_max_depth); + } +}; + +tactic * repeat(tactic * t, unsigned max) { + return alloc(repeat_tactical, t, max); +} + +class fail_if_branching_tactical : public unary_tactical { + unsigned m_threshold; +public: + fail_if_branching_tactical(tactic * t, unsigned threshold):unary_tactical(t), m_threshold(threshold) {} + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + m_t->operator()(in, result, mc, pc, core); + if (result.size() > m_threshold) { + result.reset(); + mc = 0; + pc = 0; + core = 0; + throw tactic_exception("failed-if-branching tactical"); + } + }; + + virtual tactic * translate(ast_manager & m) { + tactic * new_t = m_t->translate(m); + return alloc(fail_if_branching_tactical, new_t, m_threshold); + } +}; + +tactic * fail_if_branching(tactic * t, unsigned threshold) { + return alloc(fail_if_branching_tactical, t, threshold); +} + +class cleanup_tactical : public unary_tactical { +public: + cleanup_tactical(tactic * t):unary_tactical(t) {} + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + m_t->operator()(in, result, mc, pc, core); + m_t->cleanup(); + } + + virtual tactic * translate(ast_manager & m) { + tactic * new_t = m_t->translate(m); + return alloc(cleanup_tactical, new_t); + } +}; + +tactic * clean(tactic * t) { + return alloc(cleanup_tactical, t); +} + +class try_for_tactical : public unary_tactical { + unsigned m_timeout; +public: + try_for_tactical(tactic * t, unsigned ts):unary_tactical(t), m_timeout(ts) {} + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + cancel_eh eh(*m_t); + { + // Warning: scoped_timer is not thread safe in Linux. + scoped_timer timer(m_timeout, &eh); + m_t->operator()(in, result, mc, pc, core); + } + } + + virtual tactic * translate(ast_manager & m) { + tactic * new_t = m_t->translate(m); + return alloc(try_for_tactical, new_t, m_timeout); + } +}; + +tactic * try_for(tactic * t, unsigned msecs) { + return alloc(try_for_tactical, t, msecs); +} + +class using_params_tactical : public unary_tactical { + params_ref m_params; +public: + using_params_tactical(tactic * t, params_ref const & p):unary_tactical(t), m_params(p) { + t->updt_params(p); + } + + virtual void updt_params(params_ref const & p) { + TRACE("using_params", + tout << "before p: " << p << "\n"; + tout << "m_params: " << m_params << "\n"; + ;); + + params_ref new_p = p; + new_p.append(m_params); + unary_tactical::updt_params(new_p); + + TRACE("using_params", + tout << "after p: " << p << "\n"; + tout << "m_params: " << m_params << "\n"; + tout << "new_p: " << new_p << "\n";); + } + + virtual tactic * translate(ast_manager & m) { + tactic * new_t = m_t->translate(m); + return alloc(using_params_tactical, new_t, m_params); + } +}; + +tactic * using_params(tactic * t, params_ref const & p) { + return alloc(using_params_tactical, t, p); +} + +class cond_tactical : public binary_tactical { + probe * m_p; +public: + cond_tactical(probe * p, tactic * t1, tactic * t2): + binary_tactical(t1, t2), + m_p(p) { + SASSERT(m_p); + m_p->inc_ref(); + } + + ~cond_tactical() { + m_p->dec_ref(); + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + if (m_p->operator()(*(in.get())).is_true()) + m_t1->operator()(in, result, mc, pc, core); + else + m_t2->operator()(in, result, mc, pc, core); + } + + virtual tactic * translate(ast_manager & m) { + tactic * new_t1 = m_t1->translate(m); + tactic * new_t2 = m_t2->translate(m); + return alloc(cond_tactical, m_p, new_t1, new_t2); + } +}; + +tactic * cond(probe * p, tactic * t1, tactic * t2) { + return alloc(cond_tactical, p, t1, t2); +} + +tactic * when(probe * p, tactic * t) { + return cond(p, t, mk_skip_tactic()); +} + +class fail_if_tactic : public tactic { + probe * m_p; +public: + fail_if_tactic(probe * p): + m_p(p) { + SASSERT(m_p); + m_p->inc_ref(); + } + + ~fail_if_tactic() { + m_p->dec_ref(); + } + + void cleanup() {} + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + mc = 0; + pc = 0; + core = 0; + if (m_p->operator()(*(in.get())).is_true()) { + throw tactic_exception("fail-if tactic"); + } + result.push_back(in.get()); + } + + virtual tactic * translate(ast_manager & m) { + return this; + } +}; + +tactic * fail_if(probe * p) { + return alloc(fail_if_tactic, p); +} + +tactic * fail_if_not(probe * p) { + return fail_if(mk_not(p)); +} + +class if_no_proofs_tactical : public unary_tactical { +public: + if_no_proofs_tactical(tactic * t):unary_tactical(t) {} + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + if (in->proofs_enabled()) { + mc = 0; pc = 0; core = 0; + result.reset(); + result.push_back(in.get()); + } + else { + m_t->operator()(in, result, mc, pc, core); + } + } + + virtual tactic * translate(ast_manager & m) { return translate_core(m); } +}; + +class if_no_unsat_cores_tactical : public unary_tactical { +public: + if_no_unsat_cores_tactical(tactic * t):unary_tactical(t) {} + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + if (in->unsat_core_enabled()) { + mc = 0; pc = 0; core = 0; + result.reset(); + result.push_back(in.get()); + } + else { + m_t->operator()(in, result, mc, pc, core); + } + } + + virtual tactic * translate(ast_manager & m) { return translate_core(m); } +}; + +class if_no_models_tactical : public unary_tactical { +public: + if_no_models_tactical(tactic * t):unary_tactical(t) {} + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + if (in->models_enabled()) { + mc = 0; pc = 0; core = 0; + result.reset(); + result.push_back(in.get()); + } + else { + m_t->operator()(in, result, mc, pc, core); + } + } + + virtual tactic * translate(ast_manager & m) { return translate_core(m); } +}; + +tactic * if_no_proofs(tactic * t) { + return alloc(if_no_proofs_tactical, t); +} + +tactic * if_no_unsat_cores(tactic * t) { + return alloc(if_no_unsat_cores_tactical, t); +} + +tactic * if_no_models(tactic * t) { + return alloc(if_no_models_tactical, t); +} + +tactic * skip_if_failed(tactic * t) { + return or_else(t, mk_skip_tactic()); +} + + diff --git a/lib/tactical.h b/lib/tactical.h new file mode 100644 index 000000000..4ceff2031 --- /dev/null +++ b/lib/tactical.h @@ -0,0 +1,83 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + tactical.h + +Abstract: + + Basic combinators + +Author: + + Leonardo (leonardo) 2011-10-13 + +Notes: + +--*/ +#ifndef _TACTICAL_H_ +#define _TACTICAL_H_ + +#include"tactic.h" +#include"probe.h" + +tactic * and_then(unsigned num, tactic * const * ts); +tactic * and_then(tactic * t1, tactic * t2); +tactic * and_then(tactic * t1, tactic * t2, tactic * t3); +tactic * and_then(tactic * t1, tactic * t2, tactic * t3, tactic * t4); +tactic * and_then(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5); +tactic * and_then(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6); +tactic * and_then(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7); +tactic * and_then(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7, tactic * t8); +tactic * and_then(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7, tactic * t8, tactic * t9); +tactic * and_then(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7, tactic * t8, tactic * t9, tactic * t10); + +tactic * or_else(unsigned num, tactic * const * ts); +tactic * or_else(tactic * t1, tactic * t2); +tactic * or_else(tactic * t1, tactic * t2, tactic * t3); +tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4); +tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5); +tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6); +tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7); +tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7, tactic * t8); +tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7, tactic * t8, tactic * t9); +tactic * or_else(tactic * t1, tactic * t2, tactic * t3, tactic * t4, tactic * t5, tactic * t6, tactic * t7, tactic * t8, tactic * t9, tactic * t10); + +tactic * repeat(tactic * t, unsigned max = UINT_MAX); +/** + \brief Fails if \c t produeces more than \c threshold subgoals. + Otherwise, it behabes like \c t. +*/ +tactic * fail_if_branching(tactic * t, unsigned threshold = 1); + +tactic * par(unsigned num, tactic * const * ts); +tactic * par(tactic * t1, tactic * t2); +tactic * par(tactic * t1, tactic * t2, tactic * t3); +tactic * par(tactic * t1, tactic * t2, tactic * t3, tactic * t4); + +tactic * par_and_then(unsigned num, tactic * const * ts); +tactic * par_and_then(tactic * t1, tactic * t2); + +tactic * try_for(tactic * t, unsigned msecs); +tactic * clean(tactic * t); +tactic * using_params(tactic * t, params_ref const & p); + +// Create a tactic that fails if the result returned by probe p is true. +tactic * fail_if(probe * p); +tactic * fail_if_not(probe * p); +// Execute t1 if p returns true, and t2 otherwise +tactic * cond(probe * p, tactic * t1, tactic * t2); +// Alias for cond(p, t, mk_skip_tactic()) +tactic * when(probe * p, tactic * t); + +// alias for (or-else t skip) +tactic * skip_if_failed(tactic * t); + +// Execute the given tactic only if proof production is not enabled. +// If proof production is enabled it is a skip +tactic * if_no_proofs(tactic * t); +tactic * if_no_unsat_cores(tactic * t); +tactic * if_no_models(tactic * t); + +#endif diff --git a/lib/th_rewriter.cpp b/lib/th_rewriter.cpp new file mode 100644 index 000000000..a3f4b5e86 --- /dev/null +++ b/lib/th_rewriter.cpp @@ -0,0 +1,770 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + th_rewriter.h + +Abstract: + + Rewriter for applying all builtin (cheap) theory rewrite rules. + +Author: + + Leonardo (leonardo) 2011-04-07 + +Notes: + +--*/ +#include"th_rewriter.h" +#include"bool_rewriter.h" +#include"arith_rewriter.h" +#include"bv_rewriter.h" +#include"datatype_rewriter.h" +#include"array_rewriter.h" +#include"float_rewriter.h" +#include"dl_rewriter.h" +#include"rewriter_def.h" +#include"expr_substitution.h" +#include"ast_smt2_pp.h" +#include"cooperate.h" +#include"var_subst.h" +#include"ast_util.h" +#include"well_sorted.h" + +struct th_rewriter_cfg : public default_rewriter_cfg { + bool_rewriter m_b_rw; + arith_rewriter m_a_rw; + bv_rewriter m_bv_rw; + array_rewriter m_ar_rw; + datatype_rewriter m_dt_rw; + float_rewriter m_f_rw; + dl_rewriter m_dl_rw; + arith_util m_a_util; + bv_util m_bv_util; + unsigned long long m_max_memory; // in bytes + unsigned m_max_steps; + bool m_pull_cheap_ite; + bool m_flat; + bool m_cache_all; + bool m_push_ite_arith; + bool m_push_ite_bv; + + // substitution support + expr_dependency_ref m_used_dependencies; // set of dependencies of used substitutions + expr_substitution * m_subst; + + ast_manager & m() const { return m_b_rw.m(); } + + void updt_local_params(params_ref const & p) { + m_flat = p.get_bool(":flat", true); + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + m_max_steps = p.get_uint(":max-steps", UINT_MAX); + m_pull_cheap_ite = p.get_bool(":pull-cheap-ite", false); + m_cache_all = p.get_bool(":cache-all", false); + m_push_ite_arith = p.get_bool(":push-ite-arith", false); + m_push_ite_bv = p.get_bool(":push-ite-bv", false); + } + + void updt_params(params_ref const & p) { + m_b_rw.updt_params(p); + m_a_rw.updt_params(p); + m_bv_rw.updt_params(p); + m_ar_rw.updt_params(p); + updt_local_params(p); + } + + bool flat_assoc(func_decl * f) const { + if (!m_flat) return false; + family_id fid = f->get_family_id(); + if (fid == null_family_id) + return false; + decl_kind k = f->get_decl_kind(); + if (fid == m_b_rw.get_fid()) + return k == OP_AND || k == OP_OR; + if (fid == m_a_rw.get_fid()) + return k == OP_ADD; + if (fid == m_bv_rw.get_fid()) + return k == OP_BADD || k == OP_BOR || k == OP_BAND || k == OP_BXOR; + return false; + } + + bool rewrite_patterns() const { return false; } + + bool cache_all_results() const { return m_cache_all; } + + bool max_steps_exceeded(unsigned num_steps) const { + cooperate("simplifier"); + if (memory::get_allocation_size() > m_max_memory) + throw rewriter_exception(TACTIC_MAX_MEMORY_MSG); + return num_steps > m_max_steps; + } + + // Return true if t is of the form + // (= t #b0) + // (= t #b1) + // (= #b0 t) + // (= #b1 t) + bool is_eq_bit(expr * t, expr * & x, unsigned & val) { + if (!m().is_eq(t)) + return false; + expr * lhs = to_app(t)->get_arg(0); + if (!m_bv_rw.is_bv(lhs)) + return false; + if (m_bv_rw.get_bv_size(lhs) != 1) + return false; + expr * rhs = to_app(t)->get_arg(1); + rational v; + unsigned sz; + if (m_bv_rw.is_numeral(lhs, v, sz)) { + x = rhs; + val = v.get_unsigned(); + SASSERT(val == 0 || val == 1); + return true; + } + if (m_bv_rw.is_numeral(rhs, v, sz)) { + x = lhs; + val = v.get_unsigned(); + SASSERT(val == 0 || val == 1); + return true; + } + return false; + } + + // (iff (= x bit1) A) + // ---> + // (= x (ite A bit1 bit0)) + br_status apply_tamagotchi(expr * lhs, expr * rhs, expr_ref & result) { + expr * x; + unsigned val; + if (is_eq_bit(lhs, x, val)) { + result = m().mk_eq(x, m().mk_ite(rhs, m_bv_rw.mk_numeral(val, 1), m_bv_rw.mk_numeral(1-val, 1))); + return BR_REWRITE2; + } + if (is_eq_bit(rhs, x, val)) { + result = m().mk_eq(x, m().mk_ite(lhs, m_bv_rw.mk_numeral(val, 1), m_bv_rw.mk_numeral(1-val, 1))); + return BR_REWRITE2; + } + return BR_FAILED; + } + + br_status reduce_app_core(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + family_id fid = f->get_family_id(); + if (fid == null_family_id) + return BR_FAILED; + br_status st = BR_FAILED; + if (fid == m_b_rw.get_fid()) { + decl_kind k = f->get_decl_kind(); + if (k == OP_EQ) { + // theory dispatch for = + SASSERT(num == 2); + family_id s_fid = m().get_sort(args[0])->get_family_id(); + if (s_fid == m_a_rw.get_fid()) + st = m_a_rw.mk_eq_core(args[0], args[1], result); + else if (s_fid == m_bv_rw.get_fid()) + st = m_bv_rw.mk_eq_core(args[0], args[1], result); + else if (s_fid == m_dt_rw.get_fid()) + st = m_dt_rw.mk_eq_core(args[0], args[1], result); + else if (s_fid == m_f_rw.get_fid()) + st = m_f_rw.mk_eq_core(args[0], args[1], result); + + if (st != BR_FAILED) + return st; + } + if (k == OP_EQ || k == OP_IFF) { + SASSERT(num == 2); + st = apply_tamagotchi(args[0], args[1], 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()) + return m_a_rw.mk_app_core(f, num, args, result); + if (fid == m_bv_rw.get_fid()) + return m_bv_rw.mk_app_core(f, num, args, result); + if (fid == m_ar_rw.get_fid()) + return m_ar_rw.mk_app_core(f, num, args, result); + if (fid == m_dt_rw.get_fid()) + return m_dt_rw.mk_app_core(f, num, args, result); + if (fid == m_f_rw.get_fid()) + return m_f_rw.mk_app_core(f, num, args, result); + if (fid == m_dl_rw.get_fid()) + return m_dl_rw.mk_app_core(f, num, args, result); + return BR_FAILED; + } + + // auxiliary function for pull_ite_core + expr * mk_eq_value(expr * lhs, expr * value) { + SASSERT(m().is_value(value)); + if (m().is_value(lhs)) { + return lhs == value ? m().mk_true() : m().mk_false(); + } + return m().mk_eq(lhs, value); + } + + template + br_status pull_ite_core(func_decl * p, app * ite, app * value, expr_ref & result) { + if (m().is_eq(p)) { + result = m().mk_ite(ite->get_arg(0), + mk_eq_value(ite->get_arg(1), value), + mk_eq_value(ite->get_arg(2), value)); + return BR_REWRITE2; + } + else { + if (SWAP) { + result = m().mk_ite(ite->get_arg(0), + m().mk_app(p, value, ite->get_arg(1)), + m().mk_app(p, value, ite->get_arg(2))); + return BR_REWRITE2; + } + else { + result = m().mk_ite(ite->get_arg(0), + m().mk_app(p, ite->get_arg(1), value), + m().mk_app(p, ite->get_arg(2), value)); + return BR_REWRITE2; + } + } + } + + // Return true if t is an ite-value-tree form defined as: + // ite-value-tree := (ite c ) + // subtree := value + // | (ite c ) + // + bool is_ite_value_tree(expr * t) { + if (!m().is_ite(t)) + return false; + ptr_buffer todo; + todo.push_back(to_app(t)); + while (!todo.empty()) { + app * ite = todo.back(); + todo.pop_back(); + expr * arg1 = ite->get_arg(1); + expr * arg2 = ite->get_arg(2); + + if (m().is_ite(arg1) && arg1->get_ref_count() == 1) // do not apply on shared terms, since it may blowup + todo.push_back(to_app(arg1)); + else if (!m().is_value(arg1)) + return false; + + if (m().is_ite(arg2) && arg2->get_ref_count() == 1) // do not apply on shared terms, since it may blowup + todo.push_back(to_app(arg2)); + else if (!m().is_value(arg2)) + return false; + } + return true; + } + + br_status pull_ite(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + if (num == 2 && m().is_bool(f->get_range()) && !m().is_bool(args[0])) { + if (m().is_ite(args[0])) { + if (m().is_value(args[1])) + return pull_ite_core(f, to_app(args[0]), to_app(args[1]), result); + if (m().is_ite(args[1]) && to_app(args[0])->get_arg(0) == to_app(args[1])->get_arg(0)) { + // (p (ite C A1 B1) (ite C A2 B2)) --> (ite (p A1 A2) (p B1 B2)) + result = m().mk_ite(to_app(args[0])->get_arg(0), + m().mk_app(f, to_app(args[0])->get_arg(1), to_app(args[1])->get_arg(1)), + m().mk_app(f, to_app(args[0])->get_arg(2), to_app(args[1])->get_arg(2))); + return BR_REWRITE2; + } + } + if (m().is_ite(args[1]) && m().is_value(args[0])) + return pull_ite_core(f, to_app(args[1]), to_app(args[0]), result); + } + family_id fid = f->get_family_id(); + if (num == 2 && (fid == m().get_basic_family_id() || fid == m_a_rw.get_fid() || fid == m_bv_rw.get_fid())) { + // (f v3 (ite c v1 v2)) --> (ite v (f v3 v1) (f v3 v2)) + if (m().is_value(args[0]) && is_ite_value_tree(args[1])) + return pull_ite_core(f, to_app(args[1]), to_app(args[0]), result); + + // (f (ite c v1 v2) v3) --> (ite v (f v1 v3) (f v2 v3)) + if (m().is_value(args[1]) && is_ite_value_tree(args[0])) + return pull_ite_core(f, to_app(args[0]), to_app(args[1]), result); + } + return BR_FAILED; + } + + br_status pull_ite(expr_ref & result) { + expr * t = result.get(); + if (is_app(t)) { + br_status st = pull_ite(to_app(t)->get_decl(), to_app(t)->get_num_args(), to_app(t)->get_args(), result); + if (st != BR_FAILED) + return st; + } + return BR_DONE; + } + + bool is_arith_bv_app(expr * t) const { + if (!is_app(t)) + return false; + family_id fid = to_app(t)->get_family_id(); + return ((fid == m_a_rw.get_fid() && m_push_ite_arith) || + (fid == m_bv_rw.get_fid() && m_push_ite_bv)); + } + + bool get_neutral_elem(app * t, expr_ref & n) { + family_id fid = t->get_family_id(); + if (fid == m_a_rw.get_fid()) { + switch (t->get_decl_kind()) { + case OP_ADD: n = m_a_util.mk_numeral(rational(0), m().get_sort(t)); return true; + case OP_MUL: n = m_a_util.mk_numeral(rational(1), m().get_sort(t)); return true; + default: + return false; + } + } + if (fid == m_bv_rw.get_fid()) { + switch (t->get_decl_kind()) { + case OP_BADD: n = m_bv_util.mk_numeral(rational(0), m().get_sort(t)); return true; + case OP_BMUL: n = m_bv_util.mk_numeral(rational(1), m().get_sort(t)); return true; + default: + return false; + } + } + return false; + } + + /** + \brief Try to "unify" t1 and t2 + Examples + (+ 2 a) (+ 3 a) --> 2, 3, a + (+ 2 a) a --> 2, 0, a + ... + */ + bool unify_core(app * t1, expr * t2, expr_ref & new_t1, expr_ref & new_t2, expr_ref & c, bool & first) { + if (t1->get_num_args() != 2) + return false; + expr * a1 = t1->get_arg(0); + expr * b1 = t1->get_arg(1); + if (t2 == b1) { + if (get_neutral_elem(t1, new_t2)) { + new_t1 = a1; + c = b1; + first = false; + return true; + } + } + else if (t2 == a1) { + if (get_neutral_elem(t1, new_t2)) { + new_t1 = b1; + c = a1; + first = true; + return true; + } + } + else if (is_app_of(t2, t1->get_decl()) && to_app(t2)->get_num_args() == 2) { + expr * a2 = to_app(t2)->get_arg(0); + expr * b2 = to_app(t2)->get_arg(1); + if (b1 == b2) { + new_t1 = a1; + new_t2 = a2; + c = b2; + first = false; + return true; + } + if (a1 == a2) { + new_t1 = b1; + new_t2 = b2; + c = a1; + first = true; + return true; + } + if (t1->get_decl()->is_commutative()) { + if (a1 == b2) { + new_t1 = b1; + new_t2 = a2; + c = a1; + first = true; // doesn't really matter for commutative ops. + return true; + } + if (b1 == a2) { + new_t1 = a1; + new_t2 = b2; + c = b1; + first = false; // doesn't really matter for commutative ops. + return true; + } + } + } + return false; + } + + // Return true if t1 and t2 are of the form: + // t + a1*x1 + ... + an*xn + // t' + a1*x1 + ... + an*xn + // Store t in new_t1, t' in new_t2 and (a1*x1 + ... + an*xn) in c. + bool unify_add(app * t1, expr * t2, expr_ref & new_t1, expr_ref & new_t2, expr_ref & c) { + unsigned num1 = t1->get_num_args(); + expr * const * ms1 = t1->get_args(); + if (num1 < 2) + return false; + unsigned num2; + expr * const * ms2; + if (m_a_util.is_add(t2)) { + num2 = to_app(t2)->get_num_args(); + ms2 = to_app(t2)->get_args(); + } + else { + num2 = 1; + ms2 = &t2; + } + if (num1 != num2 && num1 != num2 + 1 && num1 != num2 - 1) + return false; + new_t1 = 0; + new_t2 = 0; + expr_fast_mark1 visited1; + expr_fast_mark2 visited2; + for (unsigned i = 0; i < num1; i++) { + expr * arg = ms1[i]; + visited1.mark(arg); + } + for (unsigned i = 0; i < num2; i++) { + expr * arg = ms2[i]; + visited2.mark(arg); + if (visited1.is_marked(arg)) + continue; + if (new_t2) + return false; // more than one missing term + new_t2 = arg; + } + for (unsigned i = 0; i < num1; i++) { + expr * arg = ms1[i]; + if (visited2.is_marked(arg)) + continue; + if (new_t1) + return false; // more than one missing term + new_t1 = arg; + } + // terms matched... + bool is_int = m_a_util.is_int(t1); + if (!new_t1) + new_t1 = m_a_util.mk_numeral(rational(0), is_int); + if (!new_t2) + new_t2 = m_a_util.mk_numeral(rational(0), is_int); + // mk common part + ptr_buffer args; + for (unsigned i = 0; i < num1; i++) { + expr * arg = ms1[i]; + if (arg == new_t1.get()) + continue; + args.push_back(arg); + } + SASSERT(!args.empty()); + if (args.size() == 1) + c = args[0]; + else + c = m_a_util.mk_add(args.size(), args.c_ptr()); + return true; + } + + bool unify(expr * t1, expr * t2, func_decl * & f, expr_ref & new_t1, expr_ref & new_t2, expr_ref & c, bool & first) { +#if 0 + // Did not work for ring benchmarks + + // Hack for handling more complex cases of + apps + // such as (+ 2 t1 t2 t3) and (+ 3 t3 t2 t1) + if (m_a_util.is_add(t1)) { + first = true; // doesn't matter for AC ops + f = to_app(t1)->get_decl(); + if (unify_add(to_app(t1), t2, new_t1, new_t2, c)) + return true; + } + if (m_a_util.is_add(t2)) { + first = true; // doesn't matter for AC ops + f = to_app(t2)->get_decl(); + if (unify_add(to_app(t2), t1, new_t2, new_t1, c)) + return true; + } +#endif + + if (is_arith_bv_app(t1)) { + f = to_app(t1)->get_decl(); + return unify_core(to_app(t1), t2, new_t1, new_t2, c, first); + } + else { + f = to_app(t2)->get_decl(); + return unify_core(to_app(t2), t1, new_t2, new_t1, c, first); + } + } + + // Apply transformations of the form + // + // (ite c (+ k1 a) (+ k2 a)) --> (+ (ite c k1 k2) a) + // (ite c (* k1 a) (* k2 a)) --> (* (ite c k1 k2) a) + // + // These transformations are useful for bit-vector problems, since + // they will minimize the number of adders/multipliers/etc + br_status push_ite(func_decl * f, unsigned num, expr * const * args, expr_ref & result) { + if (!m().is_ite(f)) + return BR_FAILED; + expr * c = args[0]; + expr * t = args[1]; + expr * e = args[2]; + func_decl * f_prime = 0; + expr_ref new_t(m()), new_e(m()), common(m()); + bool first; + TRACE("push_ite", tout << "unifying:\n" << mk_ismt2_pp(t, m()) << "\n" << mk_ismt2_pp(e, m()) << "\n";); + if (unify(t, e, f_prime, new_t, new_e, common, first)) { + if (first) + result = m().mk_app(f_prime, common, m().mk_ite(c, new_t, new_e)); + else + result = m().mk_app(f_prime, m().mk_ite(c, new_t, new_e), common); + return BR_DONE; + } + TRACE("push_ite", tout << "failed\n";); + return BR_FAILED; + } + + br_status push_ite(expr_ref & result) { + expr * t = result.get(); + if (m().is_ite(t)) { + br_status st = push_ite(to_app(t)->get_decl(), to_app(t)->get_num_args(), to_app(t)->get_args(), result); + if (st != BR_FAILED) + return st; + } + return BR_DONE; + } + + br_status reduce_app(func_decl * f, unsigned num, expr * const * args, expr_ref & result, proof_ref & result_pr) { + result_pr = 0; + br_status st = reduce_app_core(f, num, args, result); + if (st != BR_DONE && st != BR_FAILED) { + CTRACE("th_rewriter_step", st != BR_FAILED, + tout << f->get_name() << "\n"; + for (unsigned i = 0; i < num; i++) tout << mk_ismt2_pp(args[i], m()) << "\n"; + tout << "---------->\n" << mk_ismt2_pp(result, m()) << "\n";); + return st; + } + if (m_push_ite_bv || m_push_ite_arith) { + if (st == BR_FAILED) + st = push_ite(f, num, args, result); + else + st = push_ite(result); + } + if (m_pull_cheap_ite) { + if (st == BR_FAILED) + st = pull_ite(f, num, args, result); + else + st = pull_ite(result); + } + CTRACE("th_rewriter_step", st != BR_FAILED, + tout << f->get_name() << "\n"; + for (unsigned i = 0; i < num; i++) tout << mk_ismt2_pp(args[i], m()) << "\n"; + tout << "---------->\n" << mk_ismt2_pp(result, m()) << "\n";); + return st; + } + + bool reduce_quantifier(quantifier * old_q, + expr * new_body, + expr * const * new_patterns, + expr * const * new_no_patterns, + expr_ref & result, + proof_ref & result_pr) { + quantifier_ref q1(m()); + proof * p1 = 0; + if (is_quantifier(new_body) && + to_quantifier(new_body)->is_forall() == old_q->is_forall() && + !old_q->has_patterns() && + !to_quantifier(new_body)->has_patterns()) { + + quantifier * nested_q = to_quantifier(new_body); + + ptr_buffer sorts; + buffer names; + sorts.append(old_q->get_num_decls(), old_q->get_decl_sorts()); + names.append(old_q->get_num_decls(), old_q->get_decl_names()); + sorts.append(nested_q->get_num_decls(), nested_q->get_decl_sorts()); + names.append(nested_q->get_num_decls(), nested_q->get_decl_names()); + + q1 = m().mk_quantifier(old_q->is_forall(), + sorts.size(), + sorts.c_ptr(), + names.c_ptr(), + nested_q->get_expr(), + std::min(old_q->get_weight(), nested_q->get_weight()), + old_q->get_qid(), + old_q->get_skid(), + 0, 0, 0, 0); + + SASSERT(is_well_sorted(m(), q1)); + + if (m().proofs_enabled()) { + SASSERT(old_q->get_expr() == new_body); + p1 = m().mk_pull_quant(old_q, q1); + } + } + else { + ptr_buffer new_patterns_buf; + ptr_buffer new_no_patterns_buf; + + new_patterns_buf.append(old_q->get_num_patterns(), new_patterns); + new_no_patterns_buf.append(old_q->get_num_no_patterns(), new_no_patterns); + + remove_duplicates(new_patterns_buf); + remove_duplicates(new_no_patterns_buf); + + q1 = m().update_quantifier(old_q, + new_patterns_buf.size(), new_patterns_buf.c_ptr(), new_no_patterns_buf.size(), new_no_patterns_buf.c_ptr(), + new_body); + TRACE("reduce_quantifier", tout << mk_ismt2_pp(old_q, m()) << "\n----->\n" << mk_ismt2_pp(q1, m()) << "\n";); + SASSERT(is_well_sorted(m(), q1)); + } + + elim_unused_vars(m(), q1, result); + + TRACE("reduce_quantifier", tout << "after elim_unused_vars:\n" << mk_ismt2_pp(result, m()) << "\n";); + + result_pr = 0; + if (m().proofs_enabled()) { + proof * p2 = 0; + if (q1.get() != result.get()) + p2 = m().mk_elim_unused_vars(q1, result); + result_pr = m().mk_transitivity(p1, p2); + } + return true; + } + + th_rewriter_cfg(ast_manager & m, params_ref const & p): + m_b_rw(m, p), + m_a_rw(m, p), + m_bv_rw(m, p), + m_ar_rw(m, p), + m_dt_rw(m), + m_f_rw(m, p), + m_dl_rw(m), + m_a_util(m), + m_bv_util(m), + m_used_dependencies(m), + m_subst(0) { + updt_local_params(p); + } + + void set_substitution(expr_substitution * s) { + reset(); + m_subst = s; + } + + void reset() { + m_subst = 0; + } + + bool get_subst(expr * s, expr * & t, proof * & pr) { + if (m_subst == 0) + return false; + expr_dependency * d = 0; + if (m_subst->find(s, t, pr, d)) { + m_used_dependencies = m().mk_join(m_used_dependencies, d); + return true; + } + return false; + } + + void set_cancel(bool f) { + m_a_rw.set_cancel(f); + } +}; + +template class rewriter_tpl; + +struct th_rewriter::imp : public rewriter_tpl { + th_rewriter_cfg m_cfg; + imp(ast_manager & m, params_ref const & p): + rewriter_tpl(m, m.proofs_enabled(), m_cfg), + m_cfg(m, p) { + } +}; + +th_rewriter::th_rewriter(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); +} + +ast_manager & th_rewriter::m() const { + return m_imp->m(); +} + +void th_rewriter::updt_params(params_ref const & p) { + m_params = p; + m_imp->cfg().updt_params(p); +} + +void th_rewriter::get_param_descrs(param_descrs & r) { + bool_rewriter::get_param_descrs(r); + arith_rewriter::get_param_descrs(r); + bv_rewriter::get_param_descrs(r); + array_rewriter::get_param_descrs(r); + insert_max_memory(r); + insert_max_steps(r); + r.insert(":push-ite-arith", CPK_BOOL, "(default: false) push if-then-else over arithmetic terms."); + r.insert(":push-ite-bv", CPK_BOOL, "(default: false) push if-then-else over bit-vector terms."); + r.insert(":pull-cheap-ite", CPK_BOOL, "(default: false) pull if-then-else terms when cheap."); + r.insert(":cache-all", CPK_BOOL, "(default: false) cache all intermediate results."); +} + +th_rewriter::~th_rewriter() { + dealloc(m_imp); +} + +unsigned th_rewriter::get_cache_size() const { + return m_imp->get_cache_size(); +} + +unsigned th_rewriter::get_num_steps() const { + return m_imp->get_num_steps(); +} + +void th_rewriter::set_cancel(bool f) { + #pragma omp critical (th_rewriter) + { + m_imp->set_cancel(f); + m_imp->cfg().set_cancel(f); + } +} + +void th_rewriter::cleanup() { + ast_manager & m = m_imp->m(); + #pragma omp critical (th_rewriter) + { + dealloc(m_imp); + m_imp = alloc(imp, m, m_params); + } +} + +void th_rewriter::reset() { + m_imp->reset(); + m_imp->cfg().reset(); +} + +void th_rewriter::operator()(expr_ref & term) { + expr_ref result(term.get_manager()); + m_imp->operator()(term, result); + term = result; +} + +void th_rewriter::operator()(expr * t, expr_ref & result) { + m_imp->operator()(t, result); +} + +void th_rewriter::operator()(expr * t, expr_ref & result, proof_ref & result_pr) { + m_imp->operator()(t, result, result_pr); +} + +void th_rewriter::operator()(expr * n, unsigned num_bindings, expr * const * bindings, expr_ref & result) { + m_imp->operator()(n, num_bindings, bindings, result); +} + +void th_rewriter::set_substitution(expr_substitution * s) { + m_imp->reset(); // reset the cache + m_imp->cfg().set_substitution(s); +} + +expr_dependency * th_rewriter::get_used_dependencies() { + return m_imp->cfg().m_used_dependencies; +} + +void th_rewriter::reset_used_dependencies() { + if (get_used_dependencies() != 0) { + set_substitution(m_imp->cfg().m_subst); // reset cache preserving subst + m_imp->cfg().m_used_dependencies = 0; + } +} diff --git a/lib/th_rewriter.h b/lib/th_rewriter.h new file mode 100644 index 000000000..1b77c42c7 --- /dev/null +++ b/lib/th_rewriter.h @@ -0,0 +1,65 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + th_rewriter.h + +Abstract: + + Rewriter for applying all builtin (cheap) theory rewrite rules. + +Author: + + Leonardo (leonardo) 2011-04-07 + +Notes: + +--*/ +#ifndef _TH_REWRITER_H_ +#define _TH_REWRITER_H_ + +#include"ast.h" +#include"rewriter_types.h" +#include"params.h" + +class expr_substitution; + +class th_rewriter { + struct imp; + imp * m_imp; + params_ref m_params; +public: + th_rewriter(ast_manager & m, params_ref const & p = params_ref()); + ~th_rewriter(); + + ast_manager & m () const; + + void updt_params(params_ref const & p); + static void get_param_descrs(param_descrs & r); + + unsigned get_cache_size() const; + unsigned get_num_steps() const; + + void operator()(expr_ref& term); + void operator()(expr * t, expr_ref & result); + void operator()(expr * t, expr_ref & result, proof_ref & result_pr); + void operator()(expr * n, unsigned num_bindings, expr * const * bindings, expr_ref & result); + + void cancel() { set_cancel(true); } + void reset_cancel() { set_cancel(false); } + void set_cancel(bool f); + void cleanup(); + void reset(); + + void set_substitution(expr_substitution * s); + + // Dependency tracking is very coarse. + // The rewriter just keeps accumulating the dependencies of the used substitutions. + // The following methods are used to recover and reset them. + // Remark: reset_used_dependecies will reset the internal cache if get_used_dependencies() != 0 + expr_dependency * get_used_dependencies(); + void reset_used_dependencies(); +}; + +#endif diff --git a/lib/theory_arith.cpp b/lib/theory_arith.cpp new file mode 100644 index 000000000..6aa0e1370 --- /dev/null +++ b/lib/theory_arith.cpp @@ -0,0 +1,30 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_arith.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-04-22. + +Revision History: + +--*/ + +#include"theory_arith_def.h" + +namespace smt { + + template class theory_arith; + template class theory_arith; + template class theory_arith; + template class theory_arith; + template class theory_arith; + +}; diff --git a/lib/theory_arith.h b/lib/theory_arith.h new file mode 100644 index 000000000..bb09d68b7 --- /dev/null +++ b/lib/theory_arith.h @@ -0,0 +1,1222 @@ + +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_arith.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-04-21. + +Revision History: + +--*/ +#ifndef _THEORY_ARITH_H_ +#define _THEORY_ARITH_H_ + +#include"smt_theory.h" +#include"map.h" +#include"heap.h" +#include"nat_set.h" +#include"inf_rational.h" +#include"s_integer.h" +#include"inf_s_integer.h" +#include"arith_decl_plugin.h" +#include"theory_arith_params.h" +#include"arith_eq_adapter.h" +#include"numeral_factory.h" +#include"obj_pair_hashtable.h" +#include"old_interval.h" +#include"grobner.h" +#include"arith_simplifier_plugin.h" +#include"arith_eq_solver.h" + +namespace smt { + + struct theory_arith_stats { + unsigned m_conflicts, m_add_rows, m_pivots, m_diseq_cs, m_gomory_cuts, m_branches, m_gcd_tests; + unsigned m_assert_lower, m_assert_upper, m_assert_diseq, m_core2th_eqs, m_core2th_diseqs; + unsigned m_th2core_eqs, m_th2core_diseqs, m_bound_props, m_offset_eqs, m_fixed_eqs, m_offline_eqs; + unsigned m_max_min; + unsigned m_gb_simplify, m_gb_superpose, m_gb_compute_basis, m_gb_num_processed; + unsigned m_nl_branching, m_nl_linear, m_nl_bounds, m_nl_cross_nested; + + void reset() { memset(this, 0, sizeof(theory_arith_stats)); } + theory_arith_stats() { reset(); } + }; + + + /** + - There are 3 kinds of variables in the tableau: base, quasi-base, + and non-base + + - Each base var and quasi-base var v owns a row R(v). + + - If v is a base var, then R(v) contains v and other non-base variables. + + - If v is a quasi-base var, then R(v) contains v and other base and + non-base variables. + + - Each quasi-base var occurs only once in the tableau (i.e., it + occurs in R(v)). + + - A quasi-base var does not have upper&lower bounds and distinct set. + + - A quasi-base var v can be transformed into a base var by + eliminating the base vars v' in R(v). This can be accomplished by + adding -c * R(v') where c is the coefficient of v' in R(v). + + - A column is used to store the occurrences of a non-base var v' in + rows R(v), where v is a base variable. + + - An implied bound stores the linear equation that implied it. + + */ + + template + class theory_arith : public theory, private Ext { + public: + typedef typename Ext::numeral numeral; + typedef typename Ext::inf_numeral inf_numeral; + typedef vector numeral_vector; + + static const int dead_row_id = -1; + static const bool proofs_enabled = Ext::proofs_enabled; + protected: + + struct linear_monomial { + numeral m_coeff; + theory_var m_var; + linear_monomial():m_var(null_theory_var) {} + linear_monomial(numeral const & c, theory_var v):m_coeff(c), m_var(v) {} + }; + + /** + \brief A row_entry is: m_var*m_coeff + + m_col_idx points to the place in the + column where the variable occurs. + */ + struct row_entry { + numeral m_coeff; + theory_var m_var; + union { + int m_col_idx; + int m_next_free_row_entry_idx; + }; + + row_entry():m_var(0), m_col_idx(0) {} + row_entry(numeral const & c, theory_var v): m_coeff(c), m_var(v), m_col_idx(0) {} + bool is_dead() const { return m_var == null_theory_var; } + }; + + /** + \brief A column entry points to the row and the row_entry within the row + that has a non-zero coefficient on the variable associated + with the column entry. + */ + struct col_entry { + int m_row_id; + union { + int m_row_idx; + int m_next_free_row_entry_idx; + }; + + col_entry(int r, int i): m_row_id(r), m_row_idx(i) {} + col_entry(): m_row_id(0), m_row_idx(0) {} + bool is_dead() const { return m_row_id == dead_row_id; } + }; + + struct column; + + /** + \brief A row contains a base variable and set of + row_entries. The base variable must occur in the set of + row_entries with coefficient 1. + */ + struct row { + vector m_entries; + unsigned m_size; // the real size, m_entries contains dead row_entries. + int m_base_var; + int m_first_free_idx; // first available position. + row(): + m_size(0), + m_base_var(null_theory_var), + m_first_free_idx(-1) { + } + + unsigned size() const { return m_size; } + unsigned num_entries() const { return m_entries.size(); } + void reset(); + row_entry & operator[](unsigned idx) { return m_entries[idx]; } + row_entry const & operator[](unsigned idx) const { return m_entries[idx]; } + typename vector::iterator begin_entries() { return m_entries.begin(); } + const typename vector::const_iterator begin_entries() const { return m_entries.begin(); } + typename vector::iterator end_entries() { return m_entries.end(); } + const typename vector::const_iterator end_entries() const { return m_entries.end(); } + row_entry & add_row_entry(int & pos_idx); + void del_row_entry(unsigned idx); + void compress(vector & cols); + void compress_if_needed(vector & cols); + void save_var_pos(svector & result_map) const; + void reset_var_pos(svector & result_map) const; + theory_var get_base_var() const { return m_base_var; } +#ifdef Z3DEBUG + bool is_coeff_of(theory_var v, numeral const & expected) const; +#endif + void display(std::ostream & out) const; + numeral get_denominators_lcm() const; + int get_idx_of(theory_var v) const; + }; + + /** + \brief A column stores in which rows a variable occurs. + The column may have free/dead entries. The field m_first_free_idx + is a reference to the first free/dead entry. + */ + struct column { + svector m_entries; + unsigned m_size; + int m_first_free_idx; + + column():m_size(0), m_first_free_idx(-1) {} + unsigned size() const { return m_size; } + unsigned num_entries() const { return m_entries.size(); } + void reset(); + void compress(vector & rows); + void compress_if_needed(vector & rows); + void compress_singleton(vector & rows, unsigned singleton_pos); + col_entry const * get_first_col_entry() const; + col_entry & operator[](unsigned idx) { return m_entries[idx]; } + col_entry const & operator[](unsigned idx) const { return m_entries[idx]; } + typename svector::iterator begin_entries() { return m_entries.begin(); } + const typename svector::const_iterator begin_entries() const { return m_entries.begin(); } + typename svector::iterator end_entries() { return m_entries.end(); } + const typename svector::const_iterator end_entries() const { return m_entries.end(); } + col_entry & add_col_entry(int & pos_idx); + void del_col_entry(unsigned idx); + }; + + enum bound_kind { + B_LOWER, + B_UPPER + }; + + friend std::ostream & operator<<(std::ostream & out, bound_kind k) { + switch (k) { + case B_LOWER: out << ">="; break; + case B_UPPER: out << "<="; break; + } + return out; + } + + typedef svector eq_vector; + + // keep track of coefficients used for bounds for proof generation. + class antecedents { + literal_vector m_lits; + eq_vector m_eqs; + vector m_lit_coeffs; + vector m_eq_coeffs; + vector m_params; + bool m_init; + + bool empty() const { + return m_eq_coeffs.empty() && m_lit_coeffs.empty(); + } + + void init() { + if (!m_init && !empty()) { + m_params.push_back(parameter(symbol("unknown-arith"))); + for (unsigned i = 0; i < m_lits.size(); i++) { + m_params.push_back(parameter(m_lit_coeffs[i].to_rational())); + } + for (unsigned i = 0; i < m_eqs.size(); i++) { + m_params.push_back(parameter(m_eq_coeffs[i].to_rational())); + } + m_init = true; + } + } + public: + antecedents(): m_init(false) {} + + void reset() { + m_init = false; + m_eq_coeffs.reset(); + m_lit_coeffs.reset(); + m_eqs.reset(); + m_lits.reset(); + m_params.reset(); + } + + literal_vector& lits() { return m_lits; } + + eq_vector& eqs() { return m_eqs; } + + void push_lit(literal l, numeral const& r) { + m_lits.push_back(l); + if (proofs_enabled) { + m_lit_coeffs.push_back(r); + } + } + + void push_eq(enode_pair const& p, numeral const& r) { + m_eqs.push_back(p); + if (proofs_enabled) { + m_eq_coeffs.push_back(r); + } + } + + + unsigned num_params() const { + return empty()?0:m_eq_coeffs.size() + m_lit_coeffs.size() + 1; + } + + numeral const* lit_coeffs() const { return m_lit_coeffs.c_ptr(); } + numeral const* eq_coeffs() const { return m_eq_coeffs.c_ptr(); } + + parameter* params(char const* name) { + if (empty()) return 0; + init(); + m_params[0] = parameter(symbol(name)); + return m_params.c_ptr(); + } + }; + + + class bound { + protected: + theory_var m_var; + inf_numeral m_value; + unsigned m_bound_kind:1; + unsigned m_atom:1; + public: + bound(theory_var v, inf_numeral const & val, bound_kind k, bool a): + m_var(v), + m_value(val), + m_bound_kind(k), + m_atom(a) { + } + virtual ~bound() {} + 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; } + inf_numeral const & get_value() const { return m_value; } + virtual bool has_justification() const { return false; } + virtual void push_justification(antecedents& antecedents, numeral const& coeff) {} + }; + + + enum atom_kind { + A_LOWER, + A_UPPER + }; + + class atom : public bound { + protected: + bool_var m_bvar; + numeral m_k; + unsigned m_atom_kind:2; // atom kind + unsigned m_is_true:1; // cache: true if the atom was assigned to true. + public: + atom(bool_var bv, theory_var v, numeral const & k, atom_kind kind); + atom_kind get_atom_kind() const { return static_cast(m_atom_kind); } + virtual ~atom() {} + numeral const & get_k() const { return m_k; } + bool_var get_bool_var() const { return m_bvar; } + bool is_true() const { return m_is_true; } + void assign_eh(bool is_true, inf_numeral const & epsilon); + virtual bool has_justification() const { return true; } + virtual void push_justification(antecedents& a, numeral const& coeff) { + a.push_lit(literal(get_bool_var(), !m_is_true), coeff); + } + }; + + class eq_bound : public bound { + enode * m_lhs; + enode * m_rhs; + public: + eq_bound(theory_var v, inf_numeral const & val, bound_kind k, enode * lhs, enode * rhs): + bound(v, val, k, false), + m_lhs(lhs), + m_rhs(rhs) { + SASSERT(m_lhs->get_root() == m_rhs->get_root()); + } + virtual ~eq_bound() {} + virtual bool has_justification() const { return true; } + virtual void push_justification(antecedents& a, numeral const& coeff) { + SASSERT(m_lhs->get_root() == m_rhs->get_root()); + a.push_eq(enode_pair(m_lhs, m_rhs), coeff); + } + }; + + class derived_bound : public bound { + protected: + literal_vector m_lits; + eq_vector m_eqs; + friend class theory_arith; + public: + derived_bound(theory_var v, inf_numeral const & val, bound_kind k):bound(v, val, k, false) {} + virtual ~derived_bound() {} + virtual bool has_justification() const { return true; } + virtual void push_justification(antecedents& a, numeral const& coeff); + virtual void push_lit(literal l, numeral const&) { m_lits.push_back(l); } + virtual void push_eq(enode_pair const& p, numeral const&) { m_eqs.push_back(p); } + }; + + class justified_derived_bound : public derived_bound { + vector m_lit_coeffs; + vector m_eq_coeffs; + friend class theory_arith; + public: + justified_derived_bound(theory_var v, inf_numeral const & val, bound_kind k):derived_bound(v, val, k) {} + virtual ~justified_derived_bound() {} + virtual bool has_justification() const { return true; } + virtual void push_justification(antecedents& a, numeral const& coeff); + virtual void push_lit(literal l, numeral const& coeff); + + virtual void push_eq(enode_pair const& p, numeral const& coeff); + }; + + typedef int_hashtable > literal_idx_set; + typedef obj_pair_hashtable eq_set; + literal_vector m_tmp_acc_lits; + eq_vector m_tmp_acc_eqs; + literal_idx_set m_tmp_lit_set; + eq_set m_tmp_eq_set; + void accumulate_justification(bound & b, derived_bound & target, numeral const& coeff, literal_idx_set & lits, eq_set & eqs); + inf_numeral normalize_bound(theory_var v, inf_numeral const & k, bound_kind kind); + void mk_bound_from_row(theory_var v, inf_numeral const & coeff, bound_kind k, row const & r); + + typedef ptr_vector atoms; +// #define SPARSE_MAP + +#ifdef SPARSE_MAP + typedef u_map bool_var2atom; +#else + typedef ptr_vector bool_var2atom; +#endif + struct theory_var_lt { + bool operator()(theory_var v1, theory_var v2) const { return v1 < v2; } + }; + + typedef heap var_heap; + + enum var_kind { + NON_BASE, + BASE, + QUASI_BASE + }; + + struct var_data { + unsigned m_row_id:28; // row owned by the variable, irrelevant if kind() == NON_BASE + unsigned m_kind:2; + unsigned m_is_int:1; + unsigned m_nl_propagated:1; + var_data(bool is_int = false):m_row_id(0), m_kind(NON_BASE), m_is_int(is_int), m_nl_propagated(false) {} + var_kind kind() const { return static_cast(m_kind); } + }; + + class bound_trail { + theory_var m_var; + bound * m_old_bound; + public: + bound_trail(theory_var v, bound * b, bool is_upper): + m_var(v << 1 | static_cast(is_upper)), + m_old_bound(b) { + } + + bool is_upper() const { return (m_var & 1) == 1; } + theory_var get_var() const { return m_var >> 1; } + bound * get_old_bound() const { return m_old_bound; } + }; + + + theory_arith_stats m_stats; + theory_arith_params & m_params; + arith_util m_util; + arith_eq_solver m_arith_eq_solver; + bool m_found_unsupported_op; + arith_eq_adapter m_arith_eq_adapter; + vector m_rows; + svector m_dead_rows; + vector m_columns; // per var + svector m_data; // per var + vector m_value; // per var, the current assignment for the variable. + vector m_old_value; // per var, the old assignment for the variable. + ptr_vector m_bounds[2]; // per var, lower bound & upper_bound + vector m_var_occs; // per var, atoms that contain a variable + svector m_unassigned_atoms; // per var, the number of unassigned atoms that contain a variable. + bool_var2atom m_bool_var2atom; // map bool_var -> atom + svector m_var_pos; // temporary array used in add_rows + atoms m_atoms; // set of theory atoms + ptr_vector m_asserted_bounds; // set of asserted bounds + unsigned m_asserted_qhead; + svector m_nl_monomials; // non linear monomials + svector m_nl_propagated; // non linear monomials that became linear + v_dependency_manager m_dep_manager; // for tracking bounds during non-linear reasoning + + var_heap m_to_patch; // heap containing all variables v s.t. m_value[v] does not satisfy bounds of v. + nat_set m_left_basis; // temporary: set of variables that already left the basis in make_feasible + bool m_blands_rule; + + svector m_update_trail_stack; // temporary trail stack used to restore the last feasible assignment. + nat_set m_in_update_trail_stack; // set of variables in m_update_trail_stack + + svector m_to_check; // rows that should be checked for theory propagation + nat_set m_in_to_check; // set of rows in m_to_check. + + inf_numeral m_tmp; + random_gen m_random; + unsigned m_num_conflicts; + + unsigned m_branch_cut_counter; + bool m_eager_gcd; // true if gcd should be applied at every add_row + unsigned m_final_check_idx; + + // backtracking + svector m_bound_trail; + svector m_unassigned_atoms_trail; + ptr_vector m_bounds_to_delete; + struct scope { + unsigned m_atoms_lim; + unsigned m_bound_trail_lim; + unsigned m_unassigned_atoms_trail_lim; + unsigned m_asserted_bounds_lim; + unsigned m_asserted_qhead_old; + unsigned m_bounds_to_delete_lim; + unsigned m_nl_monomials_lim; + unsigned m_nl_propagated_lim; + }; + + svector m_scopes; + literal_vector m_tmp_literal_vector2; + antecedents m_tmp_antecedents; + antecedents m_tmp_antecedents2; + + struct var_value_hash; + friend struct var_value_hash; + struct var_value_hash { + theory_arith & m_th; + var_value_hash(theory_arith & th):m_th(th) {} + unsigned operator()(theory_var v) const { return m_th.get_value(v).hash(); } + }; + + struct var_value_eq; + friend struct var_value_eq; + struct var_value_eq { + theory_arith & m_th; + var_value_eq(theory_arith & th):m_th(th) {} + bool operator()(theory_var v1, theory_var v2) const { return m_th.get_value(v1) == m_th.get_value(v2) && m_th.is_int(v1) == m_th.is_int(v2); } + }; + + typedef int_hashtable var_value_table; + var_value_table m_var_value_table; + + virtual theory_var mk_var(enode * n); + + void found_unsupported_op(app * n); + + bool has_var(expr * v) const { return get_context().e_internalized(v) && get_context().get_enode(v)->get_th_var(get_id()) != null_theory_var; } + theory_var expr2var(expr * v) const { SASSERT(get_context().e_internalized(v)); return get_context().get_enode(v)->get_th_var(get_id()); } + expr * var2expr(theory_var v) const { return get_enode(v)->get_owner(); } + bool reflection_enabled() const; + bool reflect(app * n) const; + unsigned lazy_pivoting_lvl() const { return m_params.m_arith_lazy_pivoting_lvl; } + bool propagate_eqs() const { return m_params.m_arith_propagate_eqs && m_num_conflicts < m_params.m_arith_propagation_threshold; } + bool propagate_diseqs() const { return false; } + bool random_initial_value() const { return m_params.m_arith_random_initial_value; } + int random_lower() const { return m_params.m_arith_random_lower; } + int random_upper() const { return m_params.m_arith_random_upper; } + unsigned blands_rule_threshold() const { return m_params.m_arith_blands_rule_threshold; } + bound_prop_mode propagation_mode() const { return m_num_conflicts < m_params.m_arith_propagation_threshold ? m_params.m_arith_bound_prop : BP_NONE; } + bool adaptive() const { return m_params.m_arith_adaptive; } + double adaptive_assertion_threshold() const { return m_params.m_arith_adaptive_assertion_threshold; } + unsigned max_lemma_size() const { return m_params.m_arith_max_lemma_size; } + 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; } + 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(); } + bool is_base(theory_var v) const { return get_var_kind(v) == BASE; } + bool is_quasi_base(theory_var v) const { return get_var_kind(v) == QUASI_BASE; } + bool is_non_base(theory_var v) const { return get_var_kind(v) == NON_BASE; } + void set_var_kind(theory_var v, var_kind k) { m_data[v].m_kind = k; } + unsigned get_var_row(theory_var v) const { SASSERT(!is_non_base(v)); return m_data[v].m_row_id; } + void set_var_row(theory_var v, unsigned r_id) { m_data[v].m_row_id = r_id; } + bool is_int(theory_var v) const { return m_data[v].m_is_int; } + bool is_real(theory_var v) const { return !is_int(v); } + bool get_implied_old_value(theory_var v, inf_numeral & r) const; + inf_numeral const & get_implied_value(theory_var v) const; + inf_numeral const & get_quasi_base_value(theory_var v) const { return get_implied_value(v); } + inf_numeral const & get_value(theory_var v) const { return is_quasi_base(v) ? get_quasi_base_value(v) : m_value[v]; } + bound * get_bound(theory_var v, bool upper) const { return m_bounds[static_cast(upper)][v]; } + bound * lower(theory_var v) const { return m_bounds[0][v]; } + bound * upper(theory_var v) const { return m_bounds[1][v]; } + inf_numeral const & lower_bound(theory_var v) const { SASSERT(lower(v) != 0); return lower(v)->get_value(); } + inf_numeral const & upper_bound(theory_var v) const { SASSERT(upper(v) != 0); return upper(v)->get_value(); } + bool below_lower(theory_var v) const { bound * l = lower(v); return l != 0 && get_value(v) < l->get_value(); } + bool above_upper(theory_var v) const { bound * u = upper(v); return u != 0 && get_value(v) > u->get_value(); } + bool below_upper(theory_var v) const { bound * u = upper(v); return u == 0 || get_value(v) < u->get_value(); } + bool above_lower(theory_var v) const { bound * l = lower(v); return l == 0 || get_value(v) > l->get_value(); } + + bool at_bound(theory_var v) const { + bound * l = lower(v); + if (l != 0 && get_value(v) == l->get_value()) + return true; + bound * u = upper(v); + return u != 0 && get_value(v) == u->get_value(); + } + + bool at_lower(theory_var v) const { bound * l = lower(v); return l != 0 && get_value(v) == l->get_value(); } + bool at_upper(theory_var v) const { bound * u = upper(v); return u != 0 && get_value(v) == u->get_value(); } + bool is_free(theory_var v) const { return lower(v) == 0 && upper(v) == 0; } + bool is_non_free(theory_var v) const { return lower(v) != 0 || upper(v) != 0; } + bool is_bounded(theory_var v) const { return lower(v) != 0 && upper(v) != 0; } + bool is_free(expr * n) const { + SASSERT(get_context().e_internalized(n) && get_context().get_enode(n)->get_th_var(get_id()) != null_theory_var); + return is_free(get_context().get_enode(n)->get_th_var(get_id())); + } + + bool is_fixed(theory_var v) const { + bound * l = lower(v); + if (l == 0) + return false; + bound * u = upper(v); + if (u == 0) + return false; + return l->get_value() == u->get_value(); + } + + void set_bound_core(theory_var v, bound * new_bound, bool upper) { m_bounds[static_cast(upper)][v] = new_bound; } + void restore_bound(theory_var v, bound * new_bound, bool upper) { set_bound_core(v, new_bound, upper); } + void restore_nl_propagated_flag(unsigned old_trail_size); + + void set_bound(bound * new_bound, bool upper) { + SASSERT(new_bound); + SASSERT(!upper || new_bound->get_bound_kind() == B_UPPER); + SASSERT(upper || new_bound->get_bound_kind() == B_LOWER); + theory_var v = new_bound->get_var(); + set_bound_core(v, new_bound, upper); + if ((propagate_eqs() || propagate_diseqs()) && is_fixed(v)) + fixed_var_eh(v); + } + + inf_numeral const & get_epsilon(theory_var v) const { return is_real(v) ? this->m_real_epsilon : this->m_int_epsilon; } + + bool enable_cgc_for(app * n) const; + enode * mk_enode(app * n); + void mk_enode_if_reflect(app * n); + template + void add_row_entry(unsigned r_id, numeral const & coeff, theory_var v); + void internalize_internal_monomial(app * m, unsigned r_id); + theory_var internalize_add(app * n); + theory_var internalize_mul_core(app * m); + theory_var internalize_mul(app * m); + theory_var internalize_div(app * n); + theory_var mk_binary_op(app * n); + theory_var internalize_idiv(app * n); + theory_var internalize_mod(app * n); + theory_var internalize_rem(app * n); + theory_var internalize_to_real(app * n); + theory_var internalize_to_int(app * n); + void internalize_is_int(app * n); + theory_var internalize_numeral(app * n); + theory_var internalize_term_core(app * n); + void mk_axiom(expr * n1, expr * n2); + void mk_idiv_mod_axioms(expr * dividend, expr * divisor); + void mk_div_axiom(expr * dividend, expr * divisor); + void mk_rem_axiom(expr * dividend, expr * divisor); + void mk_to_int_axiom(app* to_int); + void mk_is_int_axiom(app* is_int); + + unsigned mk_row(); + void init_row(unsigned r_id); + void collect_vars(unsigned r_id, var_kind k, buffer & result); + void normalize_quasi_base_row(unsigned r_id); + void quasi_base_row2base_row(unsigned r_id); + void normalize_base_row(unsigned r_id); + void mk_clause(literal l1, literal l2, unsigned num_params, parameter * params); + void mk_clause(literal l1, literal l2, literal l3, unsigned num_params, parameter * params); + void mk_bound_axioms(atom * a); + virtual bool default_internalizer() const { return false; } + virtual bool internalize_atom(app * n, bool gate_ctx); + virtual bool internalize_term(app * term); + virtual void internalize_eq_eh(app * atom, bool_var v); + virtual void apply_sort_cnstr(enode * n, sort * s); + + virtual void assign_eh(bool_var v, bool is_true); + virtual void new_eq_eh(theory_var v1, theory_var v2); + virtual bool use_diseqs() const; + virtual void new_diseq_eh(theory_var v1, theory_var v2); + + virtual void push_scope_eh(); + virtual void pop_scope_eh(unsigned num_scopes); + + virtual void relevant_eh(app * n); + + virtual void restart_eh(); + virtual void init_search_eh(); + /** + \brief True if the assignment may be changed during final + check. assume_eqs, check_int_feasibility, + process_non_linear may change the current assignment to + satisfy their respective constraints. However, when they + do that the may create inconsistencies in the other + modules. I use m_liberal_final_check to avoid infinite + loops where the modules keep changing the assigment and no + progress is made. If m_liberal_final_check is set to false, + these modules will avoid mutating the assignment to satisfy + constraints. + + See also m_changed_assignment flag. + */ + bool m_liberal_final_check; + final_check_status final_check_core(); + virtual final_check_status final_check_eh(); + + virtual bool can_propagate(); + virtual void propagate(); + bool propagate_core(); + void failed(); + + + virtual void flush_eh(); + virtual void reset_eh(); + + virtual bool validate_eq_in_model(theory_var v1, theory_var v2, bool is_true) const; + + // ----------------------------------- + // + // bool_var -> atom mapping + // + // ----------------------------------- + void insert_bv2a(bool_var bv, atom * a) { +#ifdef SPARSE_MAP + m_bool_var2atom.insert(bv, a); +#else + m_bool_var2atom.setx(bv, a, 0); +#endif + } + + void erase_bv2a(bool_var bv) { +#ifdef SPARSE_MAP + m_bool_var2atom.erase(bv); +#else + m_bool_var2atom[bv] = 0; +#endif + } + + atom * get_bv2a(bool_var bv) { +#ifdef SPARSE_MAP + atom * a; + m_bool_var2atom.find(bv, a); + return a; +#else + return m_bool_var2atom.get(bv, 0); +#endif + } + + + // ----------------------------------- + // + // Add Row + // + // ----------------------------------- + void add_row(unsigned r1, const numeral & coeff, unsigned r2, bool apply_gcd_test); + void add_rows(unsigned r1, unsigned sz, linear_monomial * a_xs); + + // ----------------------------------- + // + // Assignment management + // + // ----------------------------------- + bool m_changed_assignment; //!< auxiliary variable set to true when the assignment is changed. + void save_value(theory_var v); + void discard_update_trail(); + void restore_assignment(); + void update_value_core(theory_var v, inf_numeral const & delta); + void update_value(theory_var v, inf_numeral const & delta); + void set_value(theory_var v, const inf_numeral & new_val) { update_value(v, new_val - m_value[v]); } + + // ----------------------------------- + // + // Pivoting + // + // ----------------------------------- + template + void pivot(theory_var x_i, theory_var x_j, numeral const & a_ij, bool apply_gcd_test); + template + void eliminate(theory_var x_i, bool apply_gcd_test); + void update_and_pivot(theory_var x_i, theory_var x_j, numeral const & a_ij, inf_numeral const & x_i_new_val); + int get_num_non_free_dep_vars(theory_var v, int best_so_far); + theory_var select_blands_pivot_core(theory_var x_i, bool is_below, numeral & out_a_ij); + template + theory_var select_pivot_core(theory_var x_i, numeral & out_a_ij); + theory_var select_pivot(theory_var x_i, bool is_below, numeral & out_a_ij); + + // ----------------------------------- + // + // Make feasible + // + // ----------------------------------- + bool make_var_feasible(theory_var x_i); + theory_var select_var_to_fix(); + theory_var select_lg_error_var(bool least); + theory_var select_greatest_error_var() { return select_lg_error_var(false); } + theory_var select_least_error_var() { return select_lg_error_var(true); } + theory_var select_smallest_var(); + bool make_feasible(); + void sign_row_conflict(theory_var x_i, bool is_below); + + // ----------------------------------- + // + // Assert bound + // + // ----------------------------------- + bool assert_lower(bound * b); + bool assert_upper(bound * b); + bool assert_bound(bound * b); + void sign_bound_conflict(bound * b1, bound * b2); + + // ----------------------------------- + // + // Bound propagation + // + // ----------------------------------- + void mark_row_for_bound_prop(unsigned r1); + void mark_rows_for_bound_prop(theory_var v); + void is_row_useful_for_bound_prop(row const & r, int & lower_idx, int & upper_idx) const; + void imply_bound_for_monomial(row const & r, int idx, bool lower); + void imply_bound_for_all_monomials(row const & r, bool lower); + void explain_bound(row const & r, int idx, bool lower, inf_numeral & delta, + antecedents & antecedents); + void mk_implied_bound(row const & r, unsigned idx, bool lower, theory_var v, bound_kind kind, inf_numeral const & k); + void assign_bound_literal(literal l, row const & r, unsigned idx, bool lower, inf_numeral & delta, antecedents& antecedents); + void propagate_bounds(); + + // ----------------------------------- + // + // Freedom intervals + // + // ----------------------------------- + bool get_freedom_interval(theory_var x_j, bool & inf_l, inf_numeral & l, bool & inf_u, inf_numeral & u, numeral & m); + + // ----------------------------------- + // + // Implied eqs + // + // ----------------------------------- + bool try_to_imply_eq(theory_var v1, theory_var v2); + + // ----------------------------------- + // + // Assume eqs with randomization + // + // ----------------------------------- + typedef int_hashtable > var_set; + var_set m_tmp_var_set; + var_set m_tmp_var_set2; + svector > m_assume_eq_candidates; + unsigned m_assume_eq_head; + bool random_update(theory_var v); + void mutate_assignment(); + bool assume_eqs_core(); + bool delayed_assume_eqs(); + + // ----------------------------------- + // + // Integrality + // + // ----------------------------------- + void move_non_base_vars_to_bounds(); + bool has_infeasible_int_var(); + theory_var find_infeasible_int_base_var(); + theory_var find_bounded_infeasible_int_base_var(); + void branch_infeasible_int_var(theory_var v); + bool branch_infeasible_int_equality(); + bool constrain_free_vars(row const & r); + bool is_gomory_cut_target(row const & r); + bool mk_gomory_cut(row const & r); + bool gcd_test(row const & r); + bool ext_gcd_test(row const & r, numeral const & least_coeff, numeral const & lcm_den, numeral const & consts); + bool gcd_test(); + void mk_polynomial_ge(unsigned num_args, row_entry const * args, rational const& k, expr_ref & result); + bool max_min_infeasible_int_vars(); + void patch_int_infeasible_vars(); + void fix_non_base_vars(); + unsynch_mpq_manager m_es_num_manager; // manager for euclidean solver. + struct euclidean_solver_bridge; + bool apply_euclidean_solver(); + final_check_status check_int_feasibility(); + + // ----------------------------------- + // + // Eq propagation + // + // ----------------------------------- + typedef std::pair value_sort_pair; + typedef pair_hash, bool_hash> value_sort_pair_hash; + typedef map > value2var; + value2var m_fixed_var_table; + + typedef std::pair var_offset; + typedef pair_hash > var_offset_hash; + typedef map > var_offset2row_id; + var_offset2row_id m_var_offset2row_id; + + bool is_equal(theory_var x, theory_var y) const { return get_enode(x)->get_root() == get_enode(y)->get_root(); } + void fixed_var_eh(theory_var v); + bool is_offset_row(row const & r, theory_var & x, theory_var & y, numeral & k) const; + void propagate_cheap_eq(unsigned rid); + void propagate_eq_to_core(theory_var x, theory_var y, antecedents& antecedents); + + virtual bool is_shared(theory_var v) const; + + // ----------------------------------- + // + // Justification + // + // ----------------------------------- + void set_conflict(unsigned num_literals, literal const * lits, unsigned num_eqs, enode_pair const * eqs, antecedents& antecedents, bool is_lia, char const* proof_rule); + void collect_fixed_var_justifications(row const & r, antecedents& antecedents) const; + + // ----------------------------------- + // + // Backtracking + // + // ----------------------------------- + void push_bound_trail(theory_var v, bound * old_bound, bool is_upper) { m_bound_trail.push_back(bound_trail(v, old_bound, is_upper)); } + void push_dec_unassigned_atoms_trail(theory_var v) { m_unassigned_atoms_trail.push_back(v); } + void restore_bounds(unsigned old_trail_size); + void restore_unassigned_atoms(unsigned old_trail_size); + void del_atoms(unsigned old_size); + void del_bounds(unsigned old_size); + void del_vars(unsigned old_num_vars); + void del_row(unsigned r_id); + + // ----------------------------------- + // + // Auxiliary methods + // + // ----------------------------------- + col_entry const * get_a_base_row_that_contains(theory_var v); + bool all_coeff_int(row const & r) const; + col_entry const * get_row_for_eliminating(theory_var v) const; + void move_unconstrained_to_base(); + void elim_quasi_base_rows(); + void remove_fixed_vars_from_base(); + void try_to_minimize_rational_coeffs(); + + /** + \brief See comment in theory::mk_eq_atom + */ + virtual app * mk_eq_atom(expr * lhs, expr * rhs) { return m_util.mk_eq(lhs, rhs); } + + // ----------------------------------- + // + // Maximization/Minimization + // + // ----------------------------------- + row m_tmp_row; + + void add_tmp_row(row & r1, numeral const & coeff, row const & r2); + theory_var pick_var_to_leave(theory_var x_j, bool inc, numeral & a_ij, inf_numeral & gain); + template + void add_tmp_row_entry(row & r, numeral const & coeff, theory_var v); + bool max_min(row & r, bool max); + bool max_min(theory_var v, bool max); + bool max_min(svector const & vars); + + // ----------------------------------- + // + // Non linear + // + // ----------------------------------- + typedef int_hashtable > row_set; + unsigned m_nl_rounds; + bool m_nl_gb_exhausted; + unsigned m_nl_strategy_idx; // for fairness + expr_ref_vector m_nl_new_exprs; + typedef obj_map var2num_occs; + var2num_occs m_var2num_occs; + typedef std::pair var_num_occs; + struct var_num_occs_lt { bool operator()(var_num_occs const & vn1, var_num_occs const & vn2) const { return vn1.second > vn2.second; } }; + + /** + \brief A monomial is 'pure' if does not have a numeric coefficient. + */ + bool is_pure_monomial(expr * m) const { return m_util.is_mul(m) && !m_util.is_numeral(to_app(m)->get_arg(0)); } + bool is_pure_monomial(theory_var v) const { return is_pure_monomial(get_enode(v)->get_owner()); } + void mark_var(theory_var v, svector & vars, var_set & already_found); + void mark_dependents(theory_var v, svector & vars, var_set & already_found, row_set & already_visited_rows); + void get_non_linear_cluster(svector & vars); + std::pair analyze_monomial(expr * m) const; + expr * get_monomial_body(expr * m) const; + rational get_monomial_coeff(expr * m) const; + unsigned get_num_vars_in_monomial(expr * m) const; + typedef std::pair var_power_pair; + var_power_pair get_var_and_degree(expr * m, unsigned i) const; + void display_monomial(std::ostream & out, expr * m) const; + bool propagate_nl_upward(expr * m); + bool propagate_nl_downward(expr * m, unsigned i); + interval mk_interval_for(theory_var v); + interval mk_interval_for(expr * n); + void mul_bound_of(expr * var, unsigned power, interval & target); + interval evaluate_as_interval(expr * n); + void dependency2new_bound(v_dependency * dep, derived_bound& new_bound); + void mk_derived_nl_bound(theory_var v, inf_numeral const & coeff, bound_kind k, v_dependency * dep); + bool update_bounds_using_interval(theory_var v, interval const & i); + bool update_bounds_using_interval(expr * n, interval const & i); + bool propagate_nl_bounds(expr * m); + bool propagate_nl_bound(expr * m, int i); + bool propagate_nl_bounds(); + bool is_problematic_non_linear_row(row const & r); + bool is_mixed_real_integer(row const & r) const; + bool is_integer(row const & r) const; + typedef std::pair coeff_expr; + void get_polynomial_info(sbuffer const & p, sbuffer & vars); + expr * p2expr(sbuffer & p); + expr * power(expr * var, unsigned power); + expr * mk_nary_mul(unsigned sz, expr * const * args, bool is_int); + expr * mk_nary_add(unsigned sz, expr * const * args, bool is_int); + expr * mk_nary_add(unsigned sz, expr * const * args); + void display_nested_form(std::ostream & out, expr * p); + unsigned get_degree_of(expr * m, expr * var); + unsigned get_min_degree(sbuffer & p, expr * var); + expr * factor(expr * m, expr * var, unsigned d); + bool in_monovariate_monomials(sbuffer & p, expr * var, unsigned & i1, rational & c1, unsigned & n1, unsigned & i2, rational & c2, unsigned & n2); + expr * horner(sbuffer & p, expr * var); + expr * cross_nested(sbuffer & p, expr * var); + bool is_cross_nested_consistent(sbuffer & p); + bool is_cross_nested_consistent(row const & r); + bool is_cross_nested_consistent(svector const & nl_cluster); + rational get_value(theory_var v, bool & computed_epsilon); + bool check_monomial_assignment(theory_var v, bool & computed_epsilon); + bool check_monomial_assignments(); + theory_var find_nl_var_for_branching(); + bool branch_nl_int_var(theory_var v); + bool is_monomial_linear(expr * m) const; + numeral get_monomial_fixed_var_product(expr * m) const; + expr * get_monomial_non_fixed_var(expr * m) const; + bool propagate_linear_monomial(theory_var v); + bool propagate_linear_monomials(); + grobner::monomial * mk_gb_monomial(rational const & coeff, expr * m, grobner & gb, v_dependency * & dep, var_set & already_found); + void add_monomial_def_to_gb(theory_var v, grobner & gb); + void add_row_to_gb(row const & r, grobner & gb); + void init_grobner_var_order(svector const & nl_cluster, grobner & gb); + void init_grobner(svector const & nl_cluster, grobner & gb); + interval mk_interval_for(grobner::monomial const * m); + void set_conflict(v_dependency * d); + bool is_inconsistent(interval const & I, unsigned num_monomials, grobner::monomial * const * monomials, v_dependency * dep); + bool is_inconsistent(grobner::equation const * eq, grobner & gb); + bool is_inconsistent2(grobner::equation const * eq, grobner & gb); + expr * monomial2expr(grobner::monomial const * m, bool is_int); + bool internalize_gb_eq(grobner::equation const * eq); + enum gb_result { GB_PROGRESS, GB_NEW_EQ, GB_FAIL }; + gb_result compute_grobner(svector const & nl_cluster); + bool max_min_nl_vars(); + final_check_status process_non_linear(); + antecedents& get_antecedents(); + + // ----------------------------------- + // + // Constructor + // + // ----------------------------------- + public: + theory_arith(ast_manager & m, theory_arith_params & params); + virtual ~theory_arith(); + + virtual theory * mk_fresh(context * new_ctx) { return alloc(theory_arith, get_manager(), m_params); } + + virtual void setup(); + + virtual char const * get_name() const { return "arithmetic"; } + + // ----------------------------------- + // + // Model generation + // + // ----------------------------------- + arith_factory * m_factory; + numeral m_epsilon; + void update_epsilon(const inf_numeral & l, const inf_numeral & u); + void compute_epsilon(); + void refine_epsilon(); + + virtual void init_model(model_generator & m); + virtual model_value_proc * mk_value(enode * n, model_generator & mg); + + // ----------------------------------- + // + // Model checker + // + // ----------------------------------- + virtual bool get_value(enode * n, expr_ref & r); + + + // ----------------------------------- + // + // Pretty Printing + // + // ----------------------------------- + public: + virtual void collect_statistics(::statistics & st) const; + virtual void display(std::ostream & out) const; + protected: + void display_row(std::ostream & out, unsigned r_id, bool compact = true) const; + void display_row(std::ostream & out, row const & r, bool compact = true) const; + void display_rows(std::ostream & out, bool compact = true) const; + void display_row_info(std::ostream & out, unsigned r_id) const; + void display_row_info(std::ostream & out, row const & r) const; + bool is_one_minus_one_row(row const & r) const; + void display_row_shape(std::ostream & out, row const & r) const; + void display_rows_shape(std::ostream & out) const; + void display_rows_stats(std::ostream & out) const; + void display_rows_bignums(std::ostream & out) const; + void display_simplified_row(std::ostream & out, row const & r) const; + void display_var(std::ostream & out, theory_var v) const; + void display_vars(std::ostream & out) const; + void display_bound(std::ostream & out, bound * b, unsigned indent = 0) const; + void display_atoms(std::ostream & out) const; + void display_asserted_atoms(std::ostream & out) const; + void display_atom(std::ostream & out, atom * a, bool show_sign) const; + void display_bounds_in_smtlib(std::ostream & out) const; + void display_bounds_in_smtlib() const; + void display_nl_monomials(std::ostream & out) const; + void display_coeff_exprs(std::ostream & out, sbuffer const & p) const; + + protected: + // ----------------------------------- + // + // Debugging + // + // ----------------------------------- +#ifdef Z3DEBUG + bool check_vector_sizes() const; + bool check_null_var_pos() const; + bool has_var_kind(unsigned r_id, var_kind k) const; + bool wf_row(unsigned r_id) const; + bool wf_rows() const; + bool wf_column(theory_var v) const; + bool wf_columns() const; + bool valid_row_assignment() const; + bool valid_row_assignment(row const & r) const; + bool satisfy_bounds() const; +#endif + }; + + class mi_ext { + public: + typedef rational numeral; + typedef inf_rational inf_numeral; + inf_numeral m_int_epsilon; + inf_numeral m_real_epsilon; + numeral fractional_part(inf_numeral const& n) { + SASSERT(n.is_rational()); + return n.get_rational() - floor(n); + } + static numeral fractional_part(numeral const & n) { + return n - floor(n); + } + static inf_numeral mk_inf_numeral(numeral const & n, numeral const & r) { + return inf_numeral(n, r); + } + static const bool proofs_enabled = false; + mi_ext() : m_int_epsilon(rational(1)), m_real_epsilon(rational(0), true) {} + }; + + class mi_ext_with_proofs { + public: + typedef rational numeral; + typedef inf_rational inf_numeral; + inf_numeral m_int_epsilon; + inf_numeral m_real_epsilon; + numeral fractional_part(inf_numeral const& n) { + SASSERT(n.is_rational()); + return n.get_rational() - floor(n); + } + static numeral fractional_part(numeral const & n) { + return n - floor(n); + } + static inf_numeral mk_inf_numeral(numeral const & n, numeral const & r) { + return inf_numeral(n, r); + } + static const bool proofs_enabled = true; + mi_ext_with_proofs() : m_int_epsilon(rational(1)), m_real_epsilon(rational(0), true) {} + }; + + class i_ext { + public: + typedef rational numeral; + typedef rational inf_numeral; + numeral m_int_epsilon; + numeral m_real_epsilon; + static numeral fractional_part(numeral const & n) { + return n - floor(n); + } + static inf_numeral mk_inf_numeral(numeral const& n, numeral const& i) { + UNREACHABLE(); + return inf_numeral(n); + } + static const bool proofs_enabled = false; + i_ext() : m_int_epsilon(1), m_real_epsilon(1) {} + }; + + class si_ext { + public: + typedef s_integer numeral; + typedef s_integer inf_numeral; + numeral m_int_epsilon; + numeral m_real_epsilon; + static numeral fractional_part(inf_numeral const & n) { + return n - floor(n); + } + static inf_numeral mk_inf_numeral(numeral const& n, numeral const& i) { + UNREACHABLE(); + return inf_numeral(n); + } + static const bool proofs_enabled = false; + si_ext(): m_int_epsilon(s_integer(1)), m_real_epsilon(s_integer(1)) {} + }; + + class smi_ext { + public: + typedef s_integer numeral; + typedef inf_s_integer inf_numeral; + inf_numeral m_int_epsilon; + inf_numeral m_real_epsilon; + static numeral fractional_part(const numeral & n) { + UNREACHABLE(); + return numeral(0); + } + static numeral fractional_part(const inf_numeral & n) { + UNREACHABLE(); + return numeral(0); + } + static inf_numeral mk_inf_numeral(numeral const& n, numeral const& i) { + return inf_numeral(n, i); + } + static const bool proofs_enabled = false; + smi_ext() : m_int_epsilon(s_integer(1)), m_real_epsilon(s_integer(0), true) {} + }; + + typedef theory_arith theory_mi_arith; + typedef theory_arith theory_mi_arith_w_proofs; + typedef theory_arith theory_i_arith; + typedef theory_arith theory_si_arith; + typedef theory_arith theory_smi_arith; + +}; + +#endif /* _THEORY_ARITH_H_ */ + diff --git a/lib/theory_arith_aux.h b/lib/theory_arith_aux.h new file mode 100644 index 000000000..bff567f1b --- /dev/null +++ b/lib/theory_arith_aux.h @@ -0,0 +1,1481 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_arith_aux.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-04-29. + +Revision History: + +--*/ +#ifndef _THEORY_ARITH_AUX_H_ +#define _THEORY_ARITH_AUX_H_ + +#include"theory_arith.h" + +namespace smt { + + // ----------------------------------- + // + // Rows + // + // ----------------------------------- + + template + void theory_arith::row::reset() { + m_entries.reset(); + m_size = 0; + m_base_var = -1; + m_first_free_idx = -1; + } + + /** + \brief Add a new row_entry. The result is a reference to the new row_entry. The position of the new row_entry in the + row is stored in pos_idx. + */ + template + typename theory_arith::row_entry & theory_arith::row::add_row_entry(int & pos_idx) { + m_size++; + if (m_first_free_idx == -1) { + pos_idx = m_entries.size(); + m_entries.push_back(row_entry()); + return m_entries.back(); + } + else { + pos_idx = m_first_free_idx; + row_entry & result = m_entries[pos_idx]; + SASSERT(result.is_dead()); + m_first_free_idx = result.m_next_free_row_entry_idx; + return result; + } + } + + /** + \brief Delete row_entry at position idx. + */ + template + void theory_arith::row::del_row_entry(unsigned idx) { + row_entry & t = m_entries[idx]; + SASSERT(!t.is_dead()); + t.m_next_free_row_entry_idx = m_first_free_idx; + t.m_var = null_theory_var; + m_size--; + SASSERT(t.is_dead()); + } + + /** + \brief Remove holes (i.e., dead entries) from the row. + */ + template + void theory_arith::row::compress(vector & cols) { + unsigned i = 0; + unsigned j = 0; + unsigned sz = m_entries.size(); + for (; i < sz; i++) { + row_entry & t1 = m_entries[i]; + if (!t1.is_dead()) { + if (i != j) { + row_entry & t2 = m_entries[j]; + t2.m_coeff.swap(t1.m_coeff); + t2.m_var = t1.m_var; + t2.m_col_idx = t1.m_col_idx; + SASSERT(!t2.is_dead()); + column & col = cols[t2.m_var]; + col[t2.m_col_idx].m_row_idx = j; + } + j++; + } + } + SASSERT(j == m_size); + m_entries.shrink(m_size); + m_first_free_idx = -1; + } + + /** + \brief Invoke compress if the row contains too many holes (i.e., dead entries). + */ + template + inline void theory_arith::row::compress_if_needed(vector & cols) { + if (size() * 2 < num_entries()) { + compress(cols); + } + } + + /** + \brief Fill the map var -> pos/idx + */ + template + inline void theory_arith::row::save_var_pos(svector & result_map) const { + typename vector::const_iterator it = m_entries.begin(); + typename vector::const_iterator end = m_entries.end(); + unsigned idx = 0; + for (; it != end; ++it, ++idx) { + if (!it->is_dead()) { + result_map[it->m_var] = idx; + } + } + } + + /** + \brief Reset the map var -> pos/idx. That is for all variables v in the row, set result[v] = -1 + This method can be viewed as the "inverse" of save_var_pos. + */ + template + inline void theory_arith::row::reset_var_pos(svector & result_map) const { + typename vector::const_iterator it = m_entries.begin(); + typename vector::const_iterator end = m_entries.end(); + for (; it != end; ++it) { + if (!it->is_dead()) { + result_map[it->m_var] = -1; + } + } + }; + +#ifdef Z3DEBUG + /** + \brief Return true if the coefficient of v in the row is equals to 'expected'. + */ + template + bool theory_arith::row::is_coeff_of(theory_var v, numeral const & expected) const { + typename vector::const_iterator it = m_entries.begin(); + typename vector::const_iterator end = m_entries.end(); + for (; it != end; ++it) { + if (!it->is_dead() && it->m_var == v) { + return it->m_coeff == expected; + } + } + return false; + } +#endif + + template + void theory_arith::row::display(std::ostream & out) const { + out << "v" << m_base_var << ", "; + typename vector::const_iterator it = m_entries.begin(); + typename vector::const_iterator end = m_entries.end(); + for (; it != end; ++it) { + if (!it->is_dead()) { + out << it->m_coeff << "*v" << it->m_var << " "; + } + } + out << "\n"; + } + + template + typename theory_arith::numeral theory_arith::row::get_denominators_lcm() const { + numeral r(1); + TRACE("lcm_bug", tout << "starting get_denominators_lcm...\n";); + typename vector::const_iterator it = m_entries.begin(); + typename vector::const_iterator end = m_entries.end(); + for (; it != end; ++it) { + if (!it->is_dead()) { + r = lcm(r, denominator(it->m_coeff)); + TRACE("lcm_bug", tout << "it->m_coeff: " << it->m_coeff << ", denominator(it->m_coeff): " << denominator(it->m_coeff) << ", r: " << r << "\n";); + } + } + return r; + } + + template + int theory_arith::row::get_idx_of(theory_var v) const { + typename vector::const_iterator it = m_entries.begin(); + typename vector::const_iterator end = m_entries.end(); + for (unsigned idx = 0; it != end; ++it, ++idx) { + if (!it->is_dead() && it->m_var == v) + return idx; + } + return -1; + } + + // ----------------------------------- + // + // Columns + // + // ----------------------------------- + + template + void theory_arith::column::reset() { + m_entries.reset(); + m_size = 0; + m_first_free_idx = -1; + } + + /** + \brief Remove holes (i.e., dead entries) from the column. + */ + template + void theory_arith::column::compress(vector & rows) { + unsigned i = 0; + unsigned j = 0; + unsigned sz = m_entries.size(); + for (; i < sz; i++) { + col_entry & e1 = m_entries[i]; + if (!e1.is_dead()) { + if (i != j) { + m_entries[j] = e1; + row & r = rows[e1.m_row_id]; + r[e1.m_row_idx].m_col_idx = j; + } + j++; + } + } + SASSERT(j == m_size); + m_entries.shrink(m_size); + m_first_free_idx = -1; + } + + /** + \brief Invoke compress if the column contains too many holes (i.e., dead entries). + */ + template + inline void theory_arith::column::compress_if_needed(vector & rows) { + if (size() * 2 < num_entries()) { + compress(rows); + } + } + + /** + \brief Special version of compress, that is used when the column contain + only one entry located at position singleton_pos. + */ + template + void theory_arith::column::compress_singleton(vector & rows, unsigned singleton_pos) { + SASSERT(m_size == 1); + if (singleton_pos != 0) { + col_entry & s = m_entries[singleton_pos]; + m_entries[0] = s; + row & r = rows[s.m_row_id]; + r[s.m_row_idx].m_col_idx = 0; + } + m_first_free_idx = -1; + m_entries.shrink(1); + } + + template + const typename theory_arith::col_entry * theory_arith::column::get_first_col_entry() const { + typename svector::const_iterator it = m_entries.begin(); + typename svector::const_iterator end = m_entries.end(); + for (; it != end; ++it) { + if (!it->is_dead()) { + return it; + } + } + return 0; + } + + template + typename theory_arith::col_entry & theory_arith::column::add_col_entry(int & pos_idx) { + m_size++; + if (m_first_free_idx == -1) { + pos_idx = m_entries.size(); + m_entries.push_back(col_entry()); + return m_entries.back(); + } + else { + pos_idx = m_first_free_idx; + col_entry & result = m_entries[pos_idx]; + SASSERT(result.is_dead()); + m_first_free_idx = result.m_next_free_row_entry_idx; + return result; + } + } + + template + void theory_arith::column::del_col_entry(unsigned idx) { + col_entry & c = m_entries[idx]; + SASSERT(!c.is_dead()); + c.m_row_id = dead_row_id; + c.m_next_free_row_entry_idx = m_first_free_idx; + m_first_free_idx = idx; + m_size--; + } + + // ----------------------------------- + // + // Atoms + // + // ----------------------------------- + + template + theory_arith::atom::atom(bool_var bv, theory_var v, numeral const & k, atom_kind kind): + bound(v, inf_numeral::zero(), B_LOWER, true), + m_bvar(bv), + m_k(k), + m_atom_kind(kind), + m_is_true(false) { + } + + template + void theory_arith::atom::assign_eh(bool is_true, inf_numeral const & epsilon) { + m_is_true = is_true; + if (is_true) { + this->m_value = m_k; + this->m_bound_kind = static_cast(m_atom_kind); + SASSERT((this->m_bound_kind == B_LOWER) == (m_atom_kind == A_LOWER)); + } + else { + if (get_atom_kind() == A_LOWER) { + this->m_value = m_k; + this->m_value -= epsilon; + this->m_bound_kind = B_UPPER; + } + else { + SASSERT(get_atom_kind() == A_UPPER); + this->m_value = m_k; + this->m_value += epsilon; + this->m_bound_kind = B_LOWER; + } + } + } + + // ----------------------------------- + // + // Auxiliary methods + // + // ----------------------------------- + + /** + \brief Return the col_entry that points to a base row that contains the given variable. + Return 0 if no row contains v. + */ + template + typename theory_arith::col_entry const * theory_arith::get_a_base_row_that_contains(theory_var v) { + while (true) { + column const & c = m_columns[v]; + if (c.size() == 0) + return 0; + int quasi_base_rid = -1; + typename svector::const_iterator it = c.begin_entries(); + typename svector::const_iterator end = c.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) { + unsigned rid = it->m_row_id; + row & r = m_rows[rid]; + if (is_base(r.get_base_var())) + return it; + else if (quasi_base_rid == -1) + quasi_base_rid = rid; + } + } + SASSERT(quasi_base_rid != -1); // since c.size() != 0 + quasi_base_row2base_row(quasi_base_rid); + // There is no guarantee that v is still a variable of row quasi_base_rid. + + // However, this loop will always terminate since I'm creating + // a base row that contains v, or decreasing c.size(). + } + } + + /** + \brief Return true if all coefficients of the given row are int. + */ + template + bool theory_arith::all_coeff_int(row const & r) const { + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead() && !it->m_coeff.is_int()) + TRACE("gomory_cut", display_row(tout, r, true);); + return false; + } + return true; + } + + /** + \brief Return the col_entry that points to row that contains the given variable. + This row should not be owned by an unconstrained quasi-base variable. + Return 0 if failed. + + This method is used by move_unconstrained_to_base + */ + template + typename theory_arith::col_entry const * theory_arith::get_row_for_eliminating(theory_var v) const { + column const & c = m_columns[v]; + if (c.size() == 0) + return 0; + typename svector::const_iterator it = c.begin_entries(); + typename svector::const_iterator end = c.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) { + row const & r = m_rows[it->m_row_id]; + theory_var s = r.get_base_var(); + if (is_quasi_base(s) && m_var_occs[s].size() == 0) + continue; + if (is_int(v)) { + numeral const & c = r[it->m_row_idx].m_coeff; + // If c == 1 or c == -1, and all other coefficients of r are integer, + // then if we pivot v with the base var of r, we will produce a row + // that will guarantee an integer assignment for v, when the + // non-base vars have integer assignment. + if (!c.is_one() && !c.is_minus_one()) + continue; + if (!all_coeff_int(r)) + continue; + } + return it; + } + } + return 0; + } + + template + void theory_arith::move_unconstrained_to_base() { + if (lazy_pivoting_lvl() == 0) + return; + TRACE("move_unconstrained_to_base", tout << "before...\n"; display(tout);); + int num = get_num_vars(); + for (theory_var v = 0; v < num; v++) { + if (m_var_occs[v].size() == 0 && is_free(v)) { + switch (get_var_kind(v)) { + case QUASI_BASE: + break; + case BASE: + if (is_int(v) && !all_coeff_int(m_rows[get_var_row(v)])) + // If the row contains non integer coefficients, then v may be assigned + // to a non-integer value even if all non-base variables are integer. + // So, v should not be "eliminated" + break; + eliminate(v, m_eager_gcd); + break; + case NON_BASE: { + col_entry const * entry = get_row_for_eliminating(v); + if (entry) { + TRACE("move_unconstrained_to_base", tout << "moving v" << v << " to the base\n";); + row & r = m_rows[entry->m_row_id]; + SASSERT(r[entry->m_row_idx].m_var == v); + pivot(r.get_base_var(), v, r[entry->m_row_idx].m_coeff, m_eager_gcd); + SASSERT(is_base(v)); + set_var_kind(v, QUASI_BASE); + SASSERT(is_quasi_base(v)); + } + break; + } } + } + } + TRACE("move_unconstrained_to_base", tout << "after...\n"; display(tout);); + CASSERT("arith", wf_rows()); + CASSERT("arith", wf_columns()); + CASSERT("arith", valid_row_assignment()); + } + + /** + \brief Force all quasi_base rows to become base rows. + */ + template + void theory_arith::elim_quasi_base_rows() { + int num = get_num_vars(); + for (theory_var v = 0; v < num; v++) { + if (is_quasi_base(v)) { + quasi_base_row2base_row(get_var_row(v)); + } + } + } + + /** + \brief Remove fixed vars from the base. + */ + template + void theory_arith::remove_fixed_vars_from_base() { + int num = get_num_vars(); + for (theory_var v = 0; v < num; v++) { + if (is_base(v) && is_fixed(v)) { + row const & r = m_rows[get_var_row(v)]; + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead() && it->m_var != v && !is_fixed(it->m_var)) { + break; + } + } + if (it != end) { + pivot(v, it->m_var, it->m_coeff, false); + } + } + } + } + + /** + \brief Try to minimize the number of rational coefficients. + The idea is to pivot x_i and x_j whenever there is a row + + x_i + 1/n * x_j + ... = 0 + + where + - x_i is a base variable + - x_j is a non-base variables + - x_j is not a fixed variable + - The denominator of any other coefficient a_ik divides n (I only consider the coefficient of non-fixed variables) + + remark if there are more than one variable with such properties, we give preference to free variables, + then to variables where upper - lower is maximal. + */ + template + void theory_arith::try_to_minimize_rational_coeffs() { + int num = get_num_vars(); + for (theory_var v = 0; v < num; v++) { + if (!is_base(v) || !is_int(v)) + continue; + numeral max_den; + row const & r = m_rows[get_var_row(v)]; + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (it->is_dead()) + continue; + if (it->m_var == v) + continue; + if (is_fixed(it->m_var)) + continue; + numeral num = numerator(it->m_coeff); + if (!num.is_one() && !num.is_minus_one()) + continue; + numeral den = denominator(it->m_coeff); + if (den > max_den) + max_den = den; + } + if (max_den <= numeral(1)) + continue; + // check whether all a_ik denominators divide max_den + it = r.begin_entries(); + for (; it != end; ++it) { + if (it->is_dead()) + continue; + if (is_fixed(it->m_var)) + continue; + numeral den = denominator(it->m_coeff); + if (!(max_den / den).is_int()) + break; + } + if (it != end) + continue; + // pick best candidate + theory_var x_j = null_theory_var; + numeral a_ij; + it = r.begin_entries(); + for (; it != end; ++it) { + if (it->is_dead()) + continue; + if (it->m_var == v) + continue; + if (is_fixed(it->m_var)) + continue; + numeral num = numerator(it->m_coeff); + if (!num.is_one() && !num.is_minus_one()) + continue; + numeral den = denominator(it->m_coeff); + if (den != max_den) + continue; + if (x_j == null_theory_var || + // TODO: add extra cases... + is_free(it->m_var) || + (is_bounded(x_j) && !is_bounded(it->m_var)) || + (is_bounded(x_j) && is_bounded(it->m_var) && (upper_bound(x_j) - lower_bound(x_j) > upper_bound(it->m_var) - lower_bound(it->m_var)))) { + x_j = it->m_var; + a_ij = it->m_coeff; + if (is_free(x_j)) + break; + } + } + if (x_j != null_theory_var) + pivot(v, x_j, a_ij, false); + } + } + + // ----------------------------------- + // + // Derived bounds + // + // ----------------------------------- + + template + void theory_arith::derived_bound::push_justification(antecedents& a, numeral const& coeff) { + + if (proofs_enabled) { + for (unsigned i = 0; i < m_lits.size(); ++i) { + a.push_lit(m_lits[i], coeff); + } + for (unsigned i = 0; i < m_eqs.size(); ++i) { + a.push_eq(m_eqs[i], coeff); + } + } + else { + a.lits().append(m_lits.size(), m_lits.c_ptr()); + a.eqs().append(m_eqs.size(), m_eqs.c_ptr()); + } + } + + + template + void theory_arith::justified_derived_bound::push_justification(antecedents& a, numeral const& coeff) { + for (unsigned i = 0; i < this->m_lits.size(); ++i) { + a.push_lit(this->m_lits[i], coeff*m_lit_coeffs[i]); + } + for (unsigned i = 0; i < this->m_eqs.size(); ++i) { + a.push_eq(this->m_eqs[i], coeff*m_eq_coeffs[i]); + } + } + + template + void theory_arith::justified_derived_bound::push_lit(literal l, numeral const& coeff) { + for (unsigned i = 0; i < this->m_lits.size(); ++i) { + if (this->m_lits[i] == l) { + m_lit_coeffs[i] += coeff; + return; + } + } + this->m_lits.push_back(l); + m_lit_coeffs.push_back(coeff); + } + + template + void theory_arith::justified_derived_bound::push_eq(enode_pair const& p, numeral const& coeff) { + for (unsigned i = 0; i < this->m_eqs.size(); ++i) { + if (this->m_eqs[i] == p) { + m_eq_coeffs[i] += coeff; + return; + } + } + this->m_eqs.push_back(p); + m_eq_coeffs.push_back(coeff); + } + + /** + \brief Copy the justification of b to new_bound. Only literals and equalities not in lits and eqs are copied. + The justification of b is also copied to lits and eqs. + */ + template + void theory_arith::accumulate_justification(bound & b, derived_bound& new_bound, numeral const& coeff, literal_idx_set & lits, eq_set & eqs) { + antecedents& ante = m_tmp_antecedents; + ante.reset(); + b.push_justification(ante, coeff); + unsigned num_lits = ante.lits().size(); + for (unsigned i = 0; i < num_lits; ++i) { + literal l = ante.lits()[i]; + if (lits.contains(l.index())) + continue; + if (proofs_enabled) { + new_bound.push_lit(l, ante.lit_coeffs()[i]); + } + else { + new_bound.push_lit(l, numeral::zero()); + lits.insert(l.index()); + } + } + unsigned num_eqs = ante.eqs().size(); + for (unsigned i = 0; i < num_eqs; ++i) { + enode_pair const & p = ante.eqs()[i]; + if (eqs.contains(p)) + continue; + if (proofs_enabled) { + new_bound.push_eq(p, ante.eq_coeffs()[i]); + } + else { + new_bound.push_eq(p, numeral::zero()); + eqs.insert(p); + } + } + } + + + + template + typename theory_arith::inf_numeral theory_arith::normalize_bound(theory_var v, inf_numeral const & k, bound_kind kind) { + if (is_real(v)) + return k; + if (kind == B_LOWER) + return inf_numeral(ceil(k)); + SASSERT(kind == B_UPPER); + return inf_numeral(floor(k)); + } + + /** + \brief Create a derived bound for v using the given row as an explanation. + */ + template + void theory_arith::mk_bound_from_row(theory_var v, inf_numeral const & k, bound_kind kind, row const & r) { + inf_numeral k_norm = normalize_bound(v, k, kind); + derived_bound * new_bound = proofs_enabled?alloc(justified_derived_bound, v, k_norm, kind):alloc(derived_bound, v, k_norm, kind); + m_bounds_to_delete.push_back(new_bound); + m_asserted_bounds.push_back(new_bound); + m_tmp_lit_set.reset(); + m_tmp_eq_set.reset(); +#ifdef Z3DEBUG + inf_numeral val; +#endif + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) { + theory_var v = it->m_var; + bool use_upper = (kind == B_UPPER); + if (!it->m_coeff.is_pos()) + use_upper = !use_upper; + TRACE("derived_bound", tout << "using " << (use_upper ? "upper" : "lower") << " bound of v" << v << "\n";); + bound * b = get_bound(v, use_upper); + SASSERT(b); + DEBUG_CODE({ + inf_numeral tmp = b->get_value(); + tmp *= it->m_coeff; + val += tmp; + }); + accumulate_justification(*b, *new_bound, it->m_coeff, m_tmp_lit_set, m_tmp_eq_set); + } + } + TRACE("derived_bound", + tout << "explanation:\n"; + literal_vector::const_iterator it1 = new_bound->m_lits.begin(); + literal_vector::const_iterator end1 = new_bound->m_lits.end(); + for (; it1 != end1; ++it1) tout << *it1 << " "; + tout << " "; + eq_vector::const_iterator it2 = new_bound->m_eqs.begin(); + eq_vector::const_iterator end2 = new_bound->m_eqs.end(); + for (; it2 != end2; ++it2) tout << "#" << it2->first->get_owner_id() << "=#" << it2->second->get_owner_id() << " "; + tout << "\n";); + DEBUG_CODE(CTRACE("derived_bound", k != val, tout << "k: " << k << ", k_norm: " << k_norm << ", val: " << val << "\n";);); + SASSERT(k == val); + } + + // ----------------------------------- + // + // Maximization/Minimization + // + // ----------------------------------- + + /** + \brief Set: row1 <- row1 + coeff * row2, + where row1 is a temporary row. + + \remark Columns do not need to be updated when updating a temporary row. + */ + template + void theory_arith::add_tmp_row(row & r1, numeral const & coeff, row const & r2) { + r1.save_var_pos(m_var_pos); + + // + // loop over variables in row2, + // add terms in row2 to row1. + // +#define ADD_TMP_ROW(_SET_COEFF_, _ADD_COEFF_) \ + typename vector::const_iterator it = r2.begin_entries(); \ + typename vector::const_iterator end = r2.end_entries(); \ + for (; it != end; ++it) { \ + if (!it->is_dead()) { \ + theory_var v = it->m_var; \ + int pos = m_var_pos[v]; \ + if (pos == -1) { \ + /* variable v is not in row1 */ \ + int row_idx; \ + row_entry & r_entry = r1.add_row_entry(row_idx); \ + r_entry.m_var = v; \ + _SET_COEFF_; \ + } \ + else { \ + /* variable v is in row1 */ \ + row_entry & r_entry = r1[pos]; \ + SASSERT(r_entry.m_var == v); \ + _ADD_COEFF_; \ + if (r_entry.m_coeff.is_zero()) { \ + r1.del_row_entry(pos); \ + } \ + m_var_pos[v] = -1; \ + } \ + } \ + } ((void) 0) + + if (coeff.is_one()) { + ADD_TMP_ROW(r_entry.m_coeff = it->m_coeff, + r_entry.m_coeff += it->m_coeff); + } + else if (coeff.is_minus_one()) { + ADD_TMP_ROW(r_entry.m_coeff = it->m_coeff; r_entry.m_coeff.neg(), + r_entry.m_coeff -= it->m_coeff); + } + else { + ADD_TMP_ROW(r_entry.m_coeff = it->m_coeff; r_entry.m_coeff *= coeff, + r_entry.m_coeff += it->m_coeff * coeff); + } + + r1.reset_var_pos(m_var_pos); + } + + /** + \brief Select tightest variable x_i to pivot with x_j. The goal + is to select a x_i such that the value of x_j is increased + (decreased) if inc = true (inc = false), and the tableau + remains feasible. Store the gain in x_j of the pivoting + operation in 'gain'. Note the gain can be too much. That is, + it may make x_i infeasible. In this case, instead of pivoting + we move x_j to its upper bound (lower bound) when inc = true (inc = false). + + If no x_i imposes a restriction on x_j, then return null_theory_var. + That is, x_j is free to move to its upper bound (lower bound). + */ + template + theory_var theory_arith::pick_var_to_leave(theory_var x_j, bool inc, numeral & a_ij, inf_numeral & gain) { + TRACE("maximize", tout << "selecting variable to replace v" << x_j << ", inc: " << inc << "\n";); + theory_var x_i = null_theory_var; + inf_numeral curr_gain; + column & c = m_columns[x_j]; + typename svector::iterator it = c.begin_entries(); + typename svector::iterator end = c.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) { + row & r = m_rows[it->m_row_id]; + theory_var s = r.get_base_var(); + if (s != null_theory_var && !is_quasi_base(s)) { + numeral const & coeff = r[it->m_row_idx].m_coeff; + bool inc_s = coeff.is_neg() ? inc : !inc; + bound * b = get_bound(s, inc_s); + if (b) { + curr_gain = get_value(s); + curr_gain -= b->get_value(); + curr_gain /= coeff; + if (curr_gain.is_neg()) + curr_gain.neg(); + if (x_i == null_theory_var || (curr_gain < gain) || (gain.is_zero() && curr_gain.is_zero() && s < x_i)) { + x_i = s; + a_ij = coeff; + gain = curr_gain; + } + } + } + TRACE("maximize", tout << "x_j: v" << x_i << ", gain: " << gain << "\n";); + } + } + TRACE("maximize", tout << "x_i v" << x_i << "\n";); + return x_i; + } + + /** + \brief Maximize (Minimize) the given temporary row. + Return true if succeeded. + */ + template + bool theory_arith::max_min(row & r, bool max) { + TRACE("max_min", tout << "max_min...\n";); + m_stats.m_max_min++; + + SASSERT(valid_row_assignment()); + SASSERT(satisfy_bounds()); + + theory_var x_i = null_theory_var; + theory_var x_j = null_theory_var; + bool inc = false; + numeral a_ij, curr_a_ij, coeff, curr_coeff; + inf_numeral curr_gain, gain; +#ifdef _TRACE + unsigned i = 0; +#endif + while (true) { + x_j = null_theory_var; + x_i = null_theory_var; + gain.reset(); + TRACE("maximize", tout << "i: " << i << ", max: " << max << "\n"; display_row(tout, r, true); tout << "state:\n"; display(tout); i++;); + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) { + theory_var curr_x_j = it->m_var; + SASSERT(is_non_base(curr_x_j)); + curr_coeff = it->m_coeff; + bool curr_inc = curr_coeff.is_pos() ? max : !max; + if ((curr_inc && at_upper(curr_x_j)) || (!curr_inc && at_lower(curr_x_j))) + continue; // variable cannot be used for max/min. + theory_var curr_x_i = pick_var_to_leave(curr_x_j, curr_inc, curr_a_ij, curr_gain); + if (curr_x_i == null_theory_var) { + // we can increase/decrease curr_x_j as much as we want. + x_i = null_theory_var; // unbounded + x_j = curr_x_j; + inc = curr_inc; + break; + } + else if (curr_gain > gain) { + x_i = curr_x_i; + x_j = curr_x_j; + a_ij = curr_a_ij; + coeff = curr_coeff; + gain = curr_gain; + inc = curr_inc; + } + else if (curr_gain.is_zero() && (x_i == null_theory_var || curr_x_i < x_i)) { + x_i = curr_x_i; + x_j = curr_x_j; + a_ij = curr_a_ij; + coeff = curr_coeff; + gain = curr_gain; + inc = curr_inc; + // continue + } + } + } + TRACE("maximize", tout << "after traversing row:\nx_i: v" << x_i << ", x_j: v" << x_j << ", gain: " << gain << "\n";); + + if (x_j == null_theory_var) { + TRACE("maximize", tout << "row is " << (max ? "maximized" : "minimized") << "\n";); + SASSERT(valid_row_assignment()); + SASSERT(satisfy_bounds()); + return true; + } + + if (x_i == null_theory_var) { + // can increase/decrease x_j as much as we want. + if (inc && upper(x_j)) { + update_value(x_j, upper_bound(x_j) - get_value(x_j)); + TRACE("maximize", tout << "moved v" << x_j << " to upper bound\n";); + SASSERT(valid_row_assignment()); + SASSERT(satisfy_bounds()); + continue; + } + if (!inc && lower(x_j)) { + update_value(x_j, lower_bound(x_j) - get_value(x_j)); + TRACE("maximize", tout << "moved v" << x_j << " to lower bound\n";); + SASSERT(valid_row_assignment()); + SASSERT(satisfy_bounds()); + continue; + } + return false; // unbounded. + } + + if (!is_fixed(x_j) && is_bounded(x_j) && (upper_bound(x_j) - lower_bound(x_j) <= gain)) { + // can increase/decrease x_j up to upper/lower bound. + if (inc) { + update_value(x_j, upper_bound(x_j) - get_value(x_j)); + TRACE("maximize", tout << "moved v" << x_j << " to upper bound\n";); + } + else { + update_value(x_j, lower_bound(x_j) - get_value(x_j)); + TRACE("maximize", tout << "moved v" << x_j << " to lower bound\n";); + } + SASSERT(valid_row_assignment()); + SASSERT(satisfy_bounds()); + continue; + } + + TRACE("maximize", tout << "max: " << max << ", x_i: v" << x_i << ", x_j: v" << x_j << ", a_ij: " << a_ij << ", coeff: " << coeff << "\n";); + bool move_xi_to_lower; + if (inc) + move_xi_to_lower = a_ij.is_pos(); + else + move_xi_to_lower = a_ij.is_neg(); + pivot(x_i, x_j, a_ij, false); + SASSERT(is_non_base(x_i)); + SASSERT(is_base(x_j)); + if (move_xi_to_lower) + update_value(x_i, lower_bound(x_i) - get_value(x_i)); + else + update_value(x_i, upper_bound(x_i) - get_value(x_i)); + row & r2 = m_rows[get_var_row(x_j)]; + coeff.neg(); + add_tmp_row(r, coeff, r2); + SASSERT(r.get_idx_of(x_j) == -1); + SASSERT(valid_row_assignment()); + SASSERT(satisfy_bounds()); + } + } + + /** + \brief Add an entry to a temporary row. + + \remark Columns do not need to be updated when updating a temporary row. + */ + template + template + void theory_arith::add_tmp_row_entry(row & r, numeral const & coeff, theory_var v) { + int r_idx; + row_entry & r_entry = r.add_row_entry(r_idx); + r_entry.m_var = v; + r_entry.m_coeff = coeff; + if (invert) + r_entry.m_coeff .neg(); + } + + /** + \brief Maximize/Minimize the given variable. The bounds of v are update if procedure succeeds. + */ + template + bool theory_arith::max_min(theory_var v, bool max) { + TRACE("maximize", tout << (max ? "maximizing" : "minimizing") << " v" << v << "...\n";); + SASSERT(valid_row_assignment()); + SASSERT(satisfy_bounds()); + SASSERT(!is_quasi_base(v)); + if ((max && at_upper(v)) || (!max && at_lower(v))) + return false; // nothing to be done... + m_tmp_row.reset(); + if (is_non_base(v)) { + add_tmp_row_entry(m_tmp_row, numeral(1), v); + } + else { + row & r = m_rows[get_var_row(v)]; + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead() && it->m_var != v) + add_tmp_row_entry(m_tmp_row, it->m_coeff, it->m_var); + } + } + if (max_min(m_tmp_row, max)) { + TRACE("maximize", tout << "v" << v << " " << (max ? "max" : "min") << " value is: " << get_value(v) << "\n"; + display_row(tout, m_tmp_row, true); display_row_info(tout, m_tmp_row);); + + mk_bound_from_row(v, get_value(v), max ? B_UPPER : B_LOWER, m_tmp_row); + return true; + } + return false; + } + + /** + \brief Maximize & Minimize variables in vars. + Return false if an inconsistency was detected. + */ + template + bool theory_arith::max_min(svector const & vars) { + bool succ = false; + svector::const_iterator it = vars.begin(); + svector::const_iterator end = vars.end(); + for (; it != end; ++it) { + if (max_min(*it, true)) + succ = true; + if (max_min(*it, false)) + succ = true; + } + if (succ) { + // process new bounds + bool r = propagate_core(); + TRACE("maximize", tout << "after max/min round:\n"; display(tout);); + return r; + } + return true; + } + + // ----------------------------------- + // + // Freedom intervals + // + // ----------------------------------- + + /** + \brief See Model-based theory combination paper. + Return false if failed to build the freedom interval. + + \remark If x_j is an integer variable, then m will contain the lcm of the denominators of a_ij. + We only consider the a_ij coefficients for x_i + */ + template + bool theory_arith::get_freedom_interval(theory_var x_j, bool & inf_l, inf_numeral & l, bool & inf_u, inf_numeral & u, numeral & m) { + if (is_base(x_j)) + return false; + + inf_numeral const & x_j_val = get_value(x_j); + column & c = m_columns[x_j]; + typename svector::iterator it = c.begin_entries(); + typename svector::iterator end = c.end_entries(); + + inf_l = true; + inf_u = true; + l.reset(); + u.reset(); + m = numeral(1); +#define IS_FIXED() { if (!inf_l && !inf_u && l == u) goto fi_succeeded; } +#define SET_LOWER(VAL) { inf_numeral const & _VAL = VAL; if (inf_l || _VAL > l) { l = _VAL; inf_l = false; } IS_FIXED(); } +#define SET_UPPER(VAL) { inf_numeral const & _VAL = VAL; if (inf_u || _VAL < u) { u = _VAL; inf_u = false; } IS_FIXED(); } + + if (lower(x_j)) { + SET_LOWER(lower_bound(x_j)); + } + if (upper(x_j)) { + SET_UPPER(upper_bound(x_j)); + } + + for (; it != end; ++it) { + if (!it->is_dead()) { + row & r = m_rows[it->m_row_id]; + theory_var x_i = r.get_base_var(); + if (x_i != null_theory_var && !is_quasi_base(x_i)) { + numeral const & a_ij = r[it->m_row_idx].m_coeff; + inf_numeral const & x_i_val = get_value(x_i); + if (is_int(x_i) && is_int(x_j) && !a_ij.is_int()) + m = lcm(m, denominator(a_ij)); + bound * x_i_lower = lower(x_i); + bound * x_i_upper = upper(x_i); + if (a_ij.is_neg()) { + if (x_i_lower) { + inf_numeral new_l = x_j_val + ((x_i_val - x_i_lower->get_value()) / a_ij); + SET_LOWER(new_l); + } + if (x_i_upper) { + inf_numeral new_u = x_j_val + ((x_i_val - x_i_upper->get_value()) / a_ij); + SET_UPPER(new_u); + } + } + else { + if (x_i_upper) { + inf_numeral new_l = x_j_val + ((x_i_val - x_i_upper->get_value()) / a_ij); + SET_LOWER(new_l); + } + if (x_i_lower) { + inf_numeral new_u = x_j_val + ((x_i_val - x_i_lower->get_value()) / a_ij); + SET_UPPER(new_u); + } + } + } + } + } + fi_succeeded: + TRACE("freedom_interval", + tout << "freedom variable for:\n"; + display_var(tout, x_j); + tout << "["; + if (inf_l) tout << "-oo"; else tout << l; + tout << "; "; + if (inf_u) tout << "oo"; else tout << u; + tout << "]\n";); + return true; + } + + // ----------------------------------- + // + // Implied eqs + // + // ----------------------------------- + + + /** + \brief Try to check whether v1 == v2 is implied by the current state. + If it is return true. + */ + template + bool theory_arith::try_to_imply_eq(theory_var v1, theory_var v2) { + SASSERT(v1 != v2); + SASSERT(get_value(v1) == get_value(v2)); + SASSERT(valid_row_assignment()); + SASSERT(satisfy_bounds()); + if (is_quasi_base(v1) || is_quasi_base(v2)) + return false; + m_tmp_row.reset(); + + if (is_non_base(v1)) { + add_tmp_row_entry(m_tmp_row, numeral(1), v1); + } + else { + row & r = m_rows[get_var_row(v1)]; + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead() && it->m_var != v1) + add_tmp_row_entry(m_tmp_row, it->m_coeff, it->m_var); + } + } + + m_tmp_row.save_var_pos(m_var_pos); + +#define ADD_ENTRY(COEFF, VAR) { \ + int pos = m_var_pos[VAR]; \ + if (pos == -1) { \ + add_tmp_row_entry(m_tmp_row, COEFF, VAR); \ + } \ + else { \ + row_entry & r_entry = m_tmp_row[pos]; \ + SASSERT(r_entry.m_var == VAR); \ + r_entry.m_coeff += COEFF; \ + if (r_entry.m_coeff.is_zero()) \ + m_tmp_row.del_row_entry(pos); \ + m_var_pos[VAR] = -1; \ + } \ + } + + if (is_non_base(v2)) { + ADD_ENTRY(numeral(-1), v2); + } + else { + row & r = m_rows[get_var_row(v2)]; + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead() && it->m_var != v2) { + numeral c = it->m_coeff; + c.neg(); + ADD_ENTRY(c, it->m_var); + } + } + } + m_tmp_row.reset_var_pos(m_var_pos); + + SASSERT(m_tmp_row.size() > 0); + +#if 0 + TRACE("imply_eq", display_row_info(tout, m_tmp_row);); + m_tmp_acc_lits.reset(); + m_tmp_acc_eqs.reset(); + m_tmp_lit_set.reset(); + m_tmp_eq_set.reset(); + + if (max_min(m_tmp_row, true) && + is_zero_row(m_tmp_row, true, m_tmp_acc_lits, m_tmp_acc_eqs, m_tmp_lit_set, m_tmp_eq_set) && + max_min(m_tmp_row, false) && + is_zero_row(m_tmp_row, false, m_tmp_acc_lits, m_tmp_acc_eqs, m_tmp_lit_set, m_tmp_eq_set)) { + // v1 == v2 + TRACE("imply_eq", tout << "found new implied equality:\n"; + display_var(tout, v1); display_var(tout, v2);); + // TODO: assert implied equality + // return true; + } +#endif + + return false; + } + + // ----------------------------------- + // + // Assume eqs + // + // The revamped assume eqs try to perturbate the + // current assignment using pivoting operations. + // + // ----------------------------------- + +#define RANGE 10000 + + /** + \brief Performs a random update on v using its freedom interval. + Return true if it was possible to change. + */ + template + bool theory_arith::random_update(theory_var v) { + if (is_fixed(v) || !is_non_base(v)) + return false; + bool inf_l, inf_u; + inf_numeral l, u; + numeral m; + get_freedom_interval(v, inf_l, l, inf_u, u, m); + if (inf_l && inf_u) { + inf_numeral new_val = inf_numeral(m_random() % (RANGE + 1)); + set_value(v, new_val); + return true; + } + if (is_int(v)) { + if (!inf_l) { + l = ceil(l); + if (!m.is_one()) + l = m*ceil(l/m); + } + if (!inf_u) { + u = floor(u); + if (!m.is_one()) + u = m*floor(u/m); + } + } + if (!inf_l && !inf_u && l >= u) + return false; + if (inf_u) { + SASSERT(!inf_l); + inf_numeral delta = inf_numeral(m_random() % (RANGE + 1)); + inf_numeral new_val = l + m*delta; + set_value(v, new_val); + return true; + } + if (inf_l) { + SASSERT(!inf_u); + inf_numeral delta = inf_numeral(m_random() % (RANGE + 1)); + inf_numeral new_val = u - m*delta; + set_value(v, new_val); + return true; + } + if (!is_int(v)) { + SASSERT(!inf_l && !inf_u); + numeral delta = numeral(m_random() % (RANGE + 1)); + inf_numeral new_val = l + ((delta * (u - l)) / numeral(RANGE)); + set_value(v, new_val); + return true; + } + else { + unsigned range = RANGE; + numeral r = (u.get_rational() - l.get_rational()) / m; + if (r < numeral(RANGE)) + range = static_cast(r.get_uint64()); + inf_numeral new_val = l + m * (inf_numeral(m_random() % (range + 1))); + set_value(v, new_val); + return true; + } + } + + template + void theory_arith::mutate_assignment() { + remove_fixed_vars_from_base(); + int num_vars = get_num_vars(); + m_var_value_table.reset(); + m_tmp_var_set.reset(); + sbuffer candidates; + for (theory_var v = 0; v < num_vars; v++) { + enode * n1 = get_enode(v); + if (!is_relevant_and_shared(n1)) + continue; + theory_var other = m_var_value_table.insert_if_not_there(v); + if (other == v) + continue; // first node with the given value... + enode * n2 = get_enode(other); + if (n1->get_root() == n2->get_root()) + continue; + if (!is_fixed(v)) { + candidates.push_back(v); + } + else if (!is_fixed(other) && !m_tmp_var_set.contains(other)) { + m_tmp_var_set.insert(other); + candidates.push_back(other); + } + } + if (candidates.empty()) + return; + typename sbuffer::iterator it = candidates.begin(); + typename sbuffer::iterator end = candidates.end(); + m_tmp_var_set.reset(); + m_tmp_var_set2.reset(); + for (; it != end; ++it) { + theory_var v = *it; + SASSERT(!is_fixed(v)); + if (is_base(v)) { + row & r = m_rows[get_var_row(v)]; + typename vector::const_iterator it2 = r.begin_entries(); + typename vector::const_iterator end2 = r.end_entries(); + for (; it2 != end2; ++it2) { + if (!it2->is_dead() && it2->m_var != v && !is_fixed(it2->m_var) && random_update(it2->m_var)) + break; + } + } + else { + random_update(v); + } + } + SASSERT(m_to_patch.empty()); + } + + /** + \brief We must redefine this method, because theory of arithmetic contains + underspecified operators such as division by 0. + (/ a b) is essentially an uninterpreted function when b = 0. + Thus, 'a' must be considered a shared var if it is the child of an underspecified operator. + */ + template + bool theory_arith::is_shared(theory_var v) const { + enode * n = get_enode(v); + enode * r = n->get_root(); + enode_vector::const_iterator it = r->begin_parents(); + enode_vector::const_iterator end = r->end_parents(); + for (; it != end; ++it) { + enode * parent = *it; + app * o = parent->get_owner(); + if (o->get_family_id() == get_id()) { + switch (o->get_decl_kind()) { + case OP_DIV: + case OP_IDIV: + case OP_REM: + case OP_MOD: + return true; + default: + break; + } + } + } + return false; + } + + template + bool theory_arith::assume_eqs_core() { + // See comment in m_liberal_final_check declaration + if (m_liberal_final_check) + mutate_assignment(); + TRACE("assume_eq_int", display(tout);); + + unsigned old_sz = m_assume_eq_candidates.size(); + TRACE("func_interp_bug", display(tout);); + m_var_value_table.reset(); + bool result = false; + int num = get_num_vars(); + for (theory_var v = 0; v < num; v++) { + enode * n = get_enode(v); + TRACE("func_interp_bug", tout << "#" << n->get_owner_id() << " -> " << m_value[v] << "\n";); + if (!is_relevant_and_shared(n)) + continue; + theory_var other = null_theory_var; + other = m_var_value_table.insert_if_not_there(v); + if (other == v) + continue; + enode * n2 = get_enode(other); + if (n->get_root() == n2->get_root()) + continue; + TRACE("func_interp_bug", tout << "adding to assume_eq queue #" << n->get_owner_id() << " #" << n2->get_owner_id() << "\n";); + m_assume_eq_candidates.push_back(std::make_pair(other, v)); + result = true; + } + + if (result) + get_context().push_trail(restore_size_trail, false>(m_assume_eq_candidates, old_sz)); + return delayed_assume_eqs(); + // return this->assume_eqs(m_var_value_table); + } + + template + bool theory_arith::delayed_assume_eqs() { + if (m_assume_eq_head == m_assume_eq_candidates.size()) + return false; + + get_context().push_trail(value_trail(m_assume_eq_head)); + while (m_assume_eq_head < m_assume_eq_candidates.size()) { + std::pair const & p = m_assume_eq_candidates[m_assume_eq_head]; + theory_var v1 = p.first; + theory_var v2 = p.second; + m_assume_eq_head++; + CTRACE("func_interp_bug", + get_value(v1) == get_value(v2) && + get_enode(v1)->get_root() != get_enode(v2)->get_root(), + tout << "assuming eq: #" << get_enode(v1)->get_owner_id() << " = #" << get_enode(v2)->get_owner_id() << "\n";); + if (get_value(v1) == get_value(v2) && + get_enode(v1)->get_root() != get_enode(v2)->get_root() && + assume_eq(get_enode(v1), get_enode(v2))) { + return true; + } + } + return false; + } + + +#if 0 + /** + \brief Check if the given row is implying a zero upper/lower bound. + Accumulate the justification in the given vectors. + Return true if it is. + */ + template + bool theory_arith::is_zero_row(row const & r, bool upper, literal_vector & lit_vect, eq_vector & eq_vect, literal_idx_set & lits, eq_set & eqs) { + inf_numeral val; + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) { + theory_var v = it->m_var; + bool use_upper = upper; + if (!it->m_coeff.is_pos()) + use_upper = !use_upper; + bound * b = get_bound(v, use_upper); + inf_numeral tmp = b->get_value(); + tmp *= it->m_coeff; + val += tmp; + SASSERT(b); + acc_umulate_justification(*b, lit_vect, eq_vect, lits, eqs); + } + } + return val.is_zero(); + } +#endif + +}; + +#endif /* _THEORY_ARITH_AUX_H_ */ + diff --git a/lib/theory_arith_core.h b/lib/theory_arith_core.h new file mode 100644 index 000000000..6ca5b888e --- /dev/null +++ b/lib/theory_arith_core.h @@ -0,0 +1,3080 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_arith_core.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-04-22. + +Revision History: + +--*/ +#ifndef _THEORY_ARITH_CORE_H_ +#define _THEORY_ARITH_CORE_H_ + +#include"smt_context.h" +#include"theory_arith.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"smt_model_generator.h" +#include"ast_smt2_pp.h" + +namespace smt { + + template + void theory_arith::found_unsupported_op(app * n) { + if (!m_found_unsupported_op) { + TRACE("arith", tout << "found non supported expression:\n" << mk_pp(n, get_manager()) << "\n";); + get_context().push_trail(value_trail(m_found_unsupported_op)); + m_found_unsupported_op = true; + } + } + + template + bool theory_arith::process_atoms() const { + if (!adaptive()) + return true; + unsigned total_conflicts = get_context().get_num_conflicts(); + if (total_conflicts < 10) + return true; + double f = static_cast(get_num_conflicts())/static_cast(total_conflicts); + TRACE_CODE({ + static unsigned counter = 0; + counter++; + if (counter % 1000 == 0) { + TRACE("arith_adaptive", tout << "arith_conflicts: " << get_num_conflicts() << " total_conflicts: " << total_conflicts << " factor: " << f << "\n";); + } + }); + return f >= adaptive_assertion_threshold(); + } + + template + theory_var theory_arith::mk_var(enode * n) { + theory_var r = theory::mk_var(n); + SASSERT(r == static_cast(m_columns.size())); + SASSERT(check_vector_sizes()); + bool is_int = m_util.is_int(n->get_owner()); + TRACE("mk_arith_var", tout << mk_pp(n->get_owner(), get_manager()) << " is_int: " << is_int << "\n";); + m_columns .push_back(column()); + m_data .push_back(var_data(is_int)); + if (random_initial_value()) { + unsigned val = (m_random()%(random_upper() - random_lower())) + random_lower(); + m_value .push_back(inf_numeral(val)); + } + else { + m_value .push_back(inf_numeral()); + } + m_old_value .push_back(inf_numeral()); + m_var_occs .push_back(atoms()); + m_unassigned_atoms .push_back(0); + m_var_pos .push_back(-1); + m_bounds[0] .push_back(0); + m_bounds[1] .push_back(0); + if (r >= static_cast(m_to_patch.get_bounds())) + m_to_patch.set_bounds(r + 1); + m_in_update_trail_stack.assure_domain(r); + m_left_basis.assure_domain(r); + m_in_to_check.assure_domain(r); + if (is_pure_monomial(n->get_owner())) + m_nl_monomials.push_back(r); + SASSERT(check_vector_sizes()); + TRACE("mk_arith_var", + tout << "#" << n->get_owner_id() << " :=\n" << mk_ll_pp(n->get_owner(), get_manager()) << "\n"; + tout << "is_attached_to_var: " << is_attached_to_var(n) << ", var: " << n->get_th_var(get_id()) << "\n";); + get_context().attach_th_var(n, this, r); + return r; + } + + template + inline bool theory_arith::reflection_enabled() const { + return m_params.m_arith_reflect; + } + + template + inline bool theory_arith::reflect(app * n) const { + if (reflection_enabled()) + return true; // reflect everything + // Every underspecified operator must be reflected in the egraph. + // Although it is underspecified, it is still a function. + // For example, a/b != a/0 then we must have b != 0 + // I use the Egraph to enforce that. + if (n->get_family_id() == get_id()) { + switch (n->get_decl_kind()) { + case OP_DIV: + case OP_IDIV: + case OP_REM: + case OP_MOD: + return true; + default: + break; + } + } + return false; + } + + template + inline bool theory_arith::enable_cgc_for(app * n) const { + // Congruence closure is not enabled for (+ ...) and (* ...) applications. + return !(n->get_family_id() == get_id() && (n->get_decl_kind() == OP_ADD || n->get_decl_kind() == OP_MUL)); + } + + /** + \brief Create an enode for n. + */ + template + enode * theory_arith::mk_enode(app * n) { + context & ctx = get_context(); + if (ctx.e_internalized(n)) + return ctx.get_enode(n); + else + return ctx.mk_enode(n, !reflect(n), false, enable_cgc_for(n)); + } + + /** + \brief Create an enode for n if reflection is enabled. + */ + template + void theory_arith::mk_enode_if_reflect(app * n) { + if (reflection_enabled()) { + // make sure that n is in the e-graph + mk_enode(n); + } + } + + /** + \brief Add coeff * v to the row r. + The column is also updated. + */ + template + template + void theory_arith::add_row_entry(unsigned r_id, numeral const & coeff, theory_var v) { + row & r = m_rows[r_id]; + column & c = m_columns[v]; + int r_idx; + row_entry & r_entry = r.add_row_entry(r_idx); + int c_idx; + col_entry & c_entry = c.add_col_entry(c_idx); + + r_entry.m_var = v; + r_entry.m_coeff = coeff; + if (invert) + r_entry.m_coeff .neg(); + r_entry.m_col_idx = c_idx; + + c_entry.m_row_id = r_id; + c_entry.m_row_idx = r_idx; + } + + /** + \brief Internalize the monomial of a polynomial. Store the monomial in the given row. + The monomial is negated before being inserted into the row. + */ + template + void theory_arith::internalize_internal_monomial(app * m, unsigned r_id) { + context & ctx = get_context(); + if (ctx.e_internalized(m)) { + enode * e = ctx.get_enode(m); + if (is_attached_to_var(e)) { + // there is already a theory variable (i.e., name) for m. + theory_var v = e->get_th_var(get_id()); + add_row_entry(r_id, numeral::minus_one(), v); + return; + } + } + rational _val; + if (m_util.is_mul(m) && m_util.is_numeral(m->get_arg(0), _val)) { + SASSERT(m->get_num_args() == 2); + numeral val(_val); + theory_var v = internalize_term_core(to_app(m->get_arg(1))); + if (reflection_enabled()) { + internalize_term_core(to_app(m->get_arg(0))); + mk_enode(m); + } + add_row_entry(r_id, val, v); + } + else { + theory_var v = internalize_term_core(m); + add_row_entry(r_id, numeral::minus_one(), v); + } + } + + /** + \brief Internalize a polynomial (+ h t). Return an alias for the monomial, that is, + a variable v such that v = (+ h t) is a new row in the tableau. + */ + template + theory_var theory_arith::internalize_add(app * n) { + TRACE("add_bug", tout << "n: " << mk_pp(n, get_manager()) << "\n";); + CTRACE("internalize_add_bug", n->get_num_args() == 2 && n->get_arg(0) == n->get_arg(1), tout << "n: " << mk_pp(n, get_manager()) << "\n";); + SASSERT(m_util.is_add(n)); + unsigned r_id = mk_row(); + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + internalize_internal_monomial(to_app(n->get_arg(i)), r_id); + } + enode * e = mk_enode(n); + theory_var v = e->get_th_var(get_id()); + if (v == null_theory_var) { + v = mk_var(e); + add_row_entry(r_id, numeral::one(), v); + init_row(r_id); + } + else { + // HACK: n was already internalized by the internalize_internal_monomial or internalize_internal_add call above. + // This can happen when one of calls invoke (indirectly) mk_axiom. + // For example, they contain a nested to_int(t) term. + // TODO: reimplement mk_axiom. The current implementation is flaky. + // I should cache the axioms that need to be created. They should only be internalized after we finished internalizing the + // current atom. Several other theories have similar problems. + del_row(r_id); + } + return v; + } + + /** + \brief Internalize a term (* x y z) that does not contain a coefficient (numeral). + */ + template + theory_var theory_arith::internalize_mul_core(app * m) { + TRACE("internalize_mul_core", tout << "internalizing...\n" << mk_pp(m,get_manager()) << "\n";); + if (!m_util.is_mul(m)) + return internalize_term_core(m); + for (unsigned i = 0; i < m->get_num_args(); i++) { + app * arg = to_app(m->get_arg(i)); + SASSERT(!m_util.is_numeral(arg)); + theory_var v = internalize_term_core(arg); + if (v == null_theory_var) { + mk_var(mk_enode(arg)); + } + } + enode * e = mk_enode(m); + theory_var v = e->get_th_var(get_id()); + if (v == null_theory_var) { + v = mk_var(e); + } + return v; + } + + /** + \brief Internalize the terms of the form (* c (* t1 ... tn)) and (* t1 ... tn). + Return an alias for the term. + */ + template + theory_var theory_arith::internalize_mul(app * m) { + rational _val; + SASSERT(m_util.is_mul(m)); + if (m_util.is_numeral(m->get_arg(0), _val)) { + SASSERT(m->get_num_args() == 2); + numeral val(_val); + SASSERT(!val.is_one()); + unsigned r_id = mk_row(); + if (reflection_enabled()) + internalize_term_core(to_app(m->get_arg(0))); + theory_var v = internalize_mul_core(to_app(m->get_arg(1))); + add_row_entry(r_id, val, v); + enode * e = mk_enode(m); + theory_var s = mk_var(e); + add_row_entry(r_id, numeral::one(), s); + init_row(r_id); + return s; + } + else { + return internalize_mul_core(m); + } + } + + template + theory_var theory_arith::mk_binary_op(app * n) { + SASSERT(n->get_num_args() == 2); + context & ctx = get_context(); + if (ctx.e_internalized(n)) + return expr2var(n); + ctx.internalize(n->get_arg(0), false); + ctx.internalize(n->get_arg(1), false); + enode * e = mk_enode(n); + return mk_var(e); + } + + template + theory_var theory_arith::internalize_div(app * n) { + theory_var s = mk_binary_op(n); + context & ctx = get_context(); + if (!ctx.relevancy()) + mk_div_axiom(n->get_arg(0), n->get_arg(1)); + return s; + } + + template + theory_var theory_arith::internalize_idiv(app * n) { + theory_var s = mk_binary_op(n); + context & ctx = get_context(); + app * mod = m_util.mk_mod(n->get_arg(0), n->get_arg(1)); + ctx.internalize(mod, false); + if (ctx.relevancy()) + ctx.add_relevancy_dependency(n, mod); + return s; + } + + template + theory_var theory_arith::internalize_mod(app * n) { + TRACE("arith_mod", tout << "internalizing...\n" << mk_pp(n, get_manager()) << "\n";); + theory_var s = mk_binary_op(n); + context & ctx = get_context(); + if (!ctx.relevancy()) + mk_idiv_mod_axioms(n->get_arg(0), n->get_arg(1)); + return s; + } + + template + theory_var theory_arith::internalize_rem(app * n) { + theory_var s = mk_binary_op(n); + context & ctx = get_context(); + if (!ctx.relevancy()) + mk_rem_axiom(n->get_arg(0), n->get_arg(1)); + return s; + } + + template + void theory_arith::mk_axiom(expr * ante, expr * conseq) { + ast_manager & m = get_manager(); + TRACE("arith_axiom", tout << mk_pp(ante, m) << "\n" << mk_pp(conseq, m) << "\n";); + context & ctx = get_context(); + simplifier & s = ctx.get_simplifier(); + expr_ref s_ante(m), s_conseq(m); + proof_ref pr(m); + s(ante, s_ante, pr); + ctx.internalize(s_ante, false); + literal l_ante = ctx.get_literal(s_ante); + s(conseq, s_conseq, pr); + ctx.internalize(s_conseq, false); + literal l_conseq = ctx.get_literal(s_conseq); + literal lits[2] = {l_ante, l_conseq}; + ctx.mk_th_axiom(get_id(), 2, lits); + if (ctx.relevancy()) { + if (l_ante == false_literal) { + ctx.mark_as_relevant(l_conseq); + } + else { + // We must mark the antecedent as relevant, otherwise the + // core will not propagate it to the theory of arithmetic. + // In a previous version, we were not doing that. + // The core was assigning it to true, this assignment was inconsistent with + // the state of the theory of arithmetic, but the conflict was not detected + // because it was not propagated to this theory. + ctx.mark_as_relevant(l_ante); + ctx.add_rel_watch(~l_ante, s_conseq); // mark consequent as relevant if antecedent is false. + } + } + } + + template + void theory_arith::mk_div_axiom(expr * p, expr * q) { + if (!m_util.is_zero(q)) { + ast_manager & m = get_manager(); + TRACE("div_axiom_bug", tout << "expanding div_axiom for: " << mk_pp(p, m) << " / " << mk_pp(q, m) << "\n";); + expr * div = m_util.mk_div(p, q); + expr * zero = m_util.mk_numeral(rational(0), false); + expr_ref eqz(m), eq(m); + eqz = m.mk_eq(q, zero); + eq = m.mk_eq(m_util.mk_mul(q, div), p); + TRACE("div_axiom_bug", tout << "eqz: " << mk_pp(eqz, m) << "\neq: " << mk_pp(eq, m) << "\n";); + mk_axiom(eqz, eq); + } + } + + template + void theory_arith::mk_idiv_mod_axioms(expr * dividend, expr * divisor) { + if (!m_util.is_zero(divisor)) { + // if divisor is zero, then idiv and mod are uninterpreted functions. + ast_manager & m = get_manager(); + expr * div = m_util.mk_idiv(dividend, divisor); + expr * mod = m_util.mk_mod(dividend, divisor); + expr * zero = m_util.mk_numeral(rational(0), true); + expr * abs_divisor = m.mk_ite(m_util.mk_lt(divisor, zero), m_util.mk_sub(zero, divisor), divisor); + expr_ref eqz(m), eq(m), lower(m), upper(m); + eqz = m.mk_eq(divisor, zero); + eq = m.mk_eq(m_util.mk_add(m_util.mk_mul(divisor, div), mod), dividend); + lower = m_util.mk_le(zero, mod); + upper = m_util.mk_lt(mod, abs_divisor); + mk_axiom(eqz, eq); + mk_axiom(eqz, lower); + mk_axiom(eqz, upper); + rational k; + if (m_params.m_arith_enum_const_mod && m_util.is_numeral(divisor, k) && + k.is_pos() && k < rational(512)) { + rational j(0); +#if 1 + literal_buffer lits; + expr_ref mod_j(m); + context& ctx = get_context(); + while(j < k) { + mod_j = m.mk_eq(mod, m_util.mk_numeral(j, true)); + ctx.internalize(mod_j, false); + literal lit(ctx.get_literal(mod_j)); + lits.push_back(lit); + ctx.mark_as_relevant(lit); + j += rational(1); + } + ctx.mk_th_axiom(get_id(), lits.size(), lits.begin()); +#else + // performs slightly worse. + literal_buffer lits; + expr_ref mod_j(m), div_j(m), num_j(m), n_mod_j(m), n_div_j(m); + context& ctx = get_context(); + while(j < k) { + num_j = m_util.mk_numeral(j, true); + mod_j = m.mk_eq(mod, num_j); + div_j = m.mk_eq(dividend, m_util.mk_add(m_util.mk_mul(div, divisor), num_j)); + n_mod_j = m.mk_not(mod_j); + n_div_j = m.mk_not(div_j); + mk_axiom(n_mod_j, div_j); + mk_axiom(n_div_j, mod_j); + j += rational(1); + } +#endif + } + } + } + + template + void theory_arith::mk_rem_axiom(expr * dividend, expr * divisor) { + if (!m_util.is_zero(divisor)) { + // if divisor is zero, then rem is an uninterpreted function. + ast_manager & m = get_manager(); + expr * zero = m_util.mk_numeral(rational(0), true); + expr * rem = m_util.mk_rem(dividend, divisor); + expr * mod = m_util.mk_mod(dividend, divisor); + expr_ref dltz(m), eq1(m), eq2(m); + dltz = m_util.mk_lt(divisor, zero); + eq1 = m.mk_eq(rem, mod); + eq2 = m.mk_eq(rem, m_util.mk_sub(zero, mod)); + // n < 0 || rem(a,n) = mod(a, n) + mk_axiom(dltz, eq1); + dltz = m.mk_not(dltz); + // !n < 0 || rem(a,n) = -mod(a, n) + mk_axiom(dltz, eq2); + } + } + + // + // create the term: s := to_real(to_int(x)) - x + // add the bounds 0 <= s < 1 + // + template + void theory_arith::mk_to_int_axiom(app * n) { + SASSERT(m_util.is_to_int(n)); + ast_manager & m = get_manager(); + expr* x = n->get_arg(0); + + // to_int (to_real x) = x + if (m_util.is_to_real(x)) { + mk_axiom(m.mk_false(), m.mk_eq(to_app(x)->get_arg(0), n)); + return; + } + expr* to_r = m_util.mk_to_real(n); + expr* lo = m_util.mk_le(to_r, x); + expr* hi = m_util.mk_lt(x, m_util.mk_add(to_r, m_util.mk_numeral(rational(1), false))); + mk_axiom(m.mk_false(), lo); + mk_axiom(m.mk_false(), hi); + } + + template + theory_var theory_arith::internalize_to_int(app * n) { + SASSERT(n->get_num_args() == 1); + context & ctx = get_context(); + if (ctx.e_internalized(n)) + return expr2var(n); + /* theory_var arg = */ internalize_term_core(to_app(n->get_arg(0))); + enode * e = mk_enode(n); + theory_var r = mk_var(e); + if (!ctx.relevancy()) + mk_to_int_axiom(n); + return r; + } + + // + // Create the axiom (iff (is_int x) (= x (to_real (to_int x)))) + // + + template + void theory_arith::mk_is_int_axiom(app * n) { + SASSERT(m_util.is_is_int(n)); + ast_manager & m = get_manager(); + expr* x = n->get_arg(0); + expr* eq = m.mk_eq(m_util.mk_to_real(m_util.mk_to_int(x)), x); + mk_axiom(m.mk_not(n), eq); + mk_axiom(m.mk_not(eq), n); + } + + template + void theory_arith::internalize_is_int(app * n) { + SASSERT(n->get_num_args() == 1); + context & ctx = get_context(); + if (ctx.b_internalized(n)) + return; + /* theory_var arg = */ internalize_term_core(to_app(n->get_arg(0))); + enode * e = mk_enode(n); + /* theory_var r = */ mk_var(e); + if (!ctx.relevancy()) + mk_is_int_axiom(n); + } + + + // create the row: r - arg = 0 + template + theory_var theory_arith::internalize_to_real(app * n) { + SASSERT(n->get_num_args() == 1); + context & ctx = get_context(); + if (ctx.e_internalized(n)) + return expr2var(n); + TRACE("to_real_bug", tout << "to-real\n" << mk_ismt2_pp(n, get_manager()) << "\n";); + theory_var arg = internalize_term_core(to_app(n->get_arg(0))); + // n may be internalized by the call above if n is of the form (to_real (to_int t)) + // The internalizer for (to_int t) will create (to_real (to_int t)) and internalize it. + // This is a recurrent bug in Z3. TODO: I should create a queue of axioms that need to be asserted. + // This queue is processes only after we finish the internalization of the current assertion. + if (ctx.e_internalized(n)) + return expr2var(n); + enode * e = mk_enode(n); + theory_var r = mk_var(e); + unsigned r_id = mk_row(); + add_row_entry(r_id, numeral(1), arg); + add_row_entry(r_id, numeral(1), r); + init_row(r_id); + return r; + } + + template + theory_var theory_arith::internalize_numeral(app * n) { + rational _val; + context & ctx = get_context(); + bool flag = m_util.is_numeral(n, _val); + numeral val(_val); + SASSERT(flag); + SASSERT(!ctx.e_internalized(n)); + enode * e = mk_enode(n); + // internalizer is marking enodes as interpreted whenever the associated ast is a value and a constant. + // e->mark_as_interpreted(); + theory_var v = mk_var(e); + inf_numeral ival(val); + bound * l = alloc(bound, v, ival, B_LOWER, false); + bound * u = alloc(bound, v, ival, B_UPPER, false); + set_bound(l, false); + set_bound(u, true); + m_bounds_to_delete.push_back(l); + m_bounds_to_delete.push_back(u); + m_value[v] = ival; + return v; + } + + /** + \brief Internalize the given term and return an alias for it. + Return null_theory_var if the term was not implemented by the theory yet. + */ + template + theory_var theory_arith::internalize_term_core(app * n) { + TRACE("arith_internalize_detail", tout << "internalize_term_core:\n" << mk_pp(n, get_manager()) << "\n";); + context & ctx = get_context(); + if (ctx.e_internalized(n)) { + enode * e = ctx.get_enode(n); + if (is_attached_to_var(e)) + return e->get_th_var(get_id()); + } + + SASSERT(!m_util.is_sub(n)); + SASSERT(!m_util.is_uminus(n)); + + if (m_util.is_add(n)) + return internalize_add(n); + else if (m_util.is_mul(n)) + return internalize_mul(n); + else if (m_util.is_div(n)) + return internalize_div(n); + else if (m_util.is_idiv(n)) + return internalize_idiv(n); + else if (m_util.is_mod(n)) + return internalize_mod(n); + else if (m_util.is_rem(n)) + return internalize_rem(n); + else if (m_util.is_to_real(n)) + return internalize_to_real(n); + else if (m_util.is_to_int(n)) + return internalize_to_int(n); + else if (m_util.is_numeral(n)) + return internalize_numeral(n); + if (m_util.is_power(n)) { + // unsupported + found_unsupported_op(n); + return mk_binary_op(n); + } + if (m_util.is_irrational_algebraic_numeral(n)) { + // unsupported + found_unsupported_op(n); + enode * e = mk_enode(n); + return mk_var(e); + } + else { + TRACE("arith_internalize_detail", tout << "before:\n" << mk_pp(n, get_manager()) << "\n";); + if (!ctx.e_internalized(n)) + ctx.internalize(n, false); + TRACE("arith_internalize_detail", tout << "after:\n" << mk_pp(n, get_manager()) << "\n";); + enode * e = ctx.get_enode(n); + if (!is_attached_to_var(e)) + return mk_var(e); + else + return e->get_th_var(get_id()); + } + } + + /** + \brief Create a new empty row. Return the new row id. + */ + template + unsigned theory_arith::mk_row() { + unsigned r; + if (m_dead_rows.empty()) { + r = m_rows.size(); + m_rows.push_back(row()); + } + else { + r = m_dead_rows.back(); + m_dead_rows.pop_back(); + } + m_in_to_check.assure_domain(r); + SASSERT(m_rows[r].size() == 0); + SASSERT(m_rows[r].num_entries() == 0); + return r; + } + + /** + \brief Initialize a new row, the last monomial is going to be the owner of the row. + The last monomial must have coeff 1. + */ + template + void theory_arith::init_row(unsigned r_id) { + row & r = m_rows[r_id]; + SASSERT(r.m_first_free_idx == -1); + SASSERT(r.size() != 0); + SASSERT(r.size() == r.num_entries()); + SASSERT(r[r.size() - 1].m_coeff.is_one()); + theory_var s = r[r.size() - 1].m_var; + r.m_base_var = s; + set_var_row(s, r_id); + TRACE("init_row_bug", tout << "before:\n"; display_row_info(tout, r);); + if (lazy_pivoting_lvl() > 2) { + set_var_kind(s, QUASI_BASE); + normalize_quasi_base_row(r_id); + SASSERT(!has_var_kind(get_var_row(s), QUASI_BASE)); + } + else { + normalize_base_row(r_id); + SASSERT(get_var_kind(s) == BASE); + SASSERT(!has_var_kind(get_var_row(s), BASE)); + } + TRACE("init_row_bug", tout << "after:\n"; display_row_info(tout, r);); + if (propagation_mode() != BP_NONE) + mark_row_for_bound_prop(r_id); + SASSERT(r.is_coeff_of(s, numeral::one())); + SASSERT(wf_row(r_id)); + } + + /** + \brief Collect variables in the given row that have the given kind, + but a different from the row main var (i.e., var that owns the row). + + The inv of the coefficients is also stored in result + */ + template + void theory_arith::collect_vars(unsigned r_id, var_kind k, buffer & result) { + row & r = m_rows[r_id]; + theory_var base = r.m_base_var; + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + unsigned idx = 0; + for (; it != end; ++it, ++idx) { + if (!it->is_dead() && get_var_kind(it->m_var) == k && it->m_var != base) { + numeral c = it->m_coeff; + c.neg(); + result.push_back(linear_monomial(c, it->m_var)); + } + } + } + + /** + \brief Normalize row as a quasi base row, it does not contain quasi-base + variables different from r.m_base_var. + */ + template + void theory_arith::normalize_quasi_base_row(unsigned r_id) { + buffer to_add; + collect_vars(r_id, QUASI_BASE, to_add); + add_rows(r_id, to_add.size(), to_add.c_ptr()); + SASSERT(!has_var_kind(r_id, QUASI_BASE)); + } + + /** + \brief Convert a quasi-base row into a base row. + */ + template + void theory_arith::quasi_base_row2base_row(unsigned r_id) { + TRACE("quasi_base_row2base_row", tout << "quasi_base_row2base_row...\n";); + buffer to_add; + collect_vars(r_id, BASE, to_add); + TRACE("quasi_base_bug_detail", + display_row_info(tout, r_id); + for (unsigned i = 0; i < to_add.size(); i++) { + theory_var v = to_add[i].m_var; + SASSERT(is_base(v)); + SASSERT(!has_var_kind(get_var_row(v), BASE)); + tout << "coeff: " << to_add[i].m_coeff << ", var: #" << get_enode(v)->get_owner_id() << "\n"; + display_row_info(tout, get_var_row(v)); + tout << "\n"; + }); + add_rows(r_id, to_add.size(), to_add.c_ptr()); + theory_var s = m_rows[r_id].get_base_var(); + set_var_kind(s, BASE); + inf_numeral tmp; + if (get_implied_old_value(s, tmp)) { + // This code is necessary because of the method + // restore_assignment. That is, the invariant + // valid_row_assignment() could be invalidated when + // restore_assignment is executed. + // + // Remark: The method restore_assignment will restore the + // old value of variables, and the value of s should be + // compatible with them. + // + // For example, consider the following scenario: + // + // 1) s is a quasi-base var, s depends on x, and value of x is v0 + // + // 2) x is updated to v1, but the update does not affect s (s is a quasi-base var). + // + // 3) quasi_base_row2base_row is executed, and we compute the value of s using + // the current value of x (v1). + // + // 4) a conflict is detected, and the value of x is restored to v0. + // + // 5) if this branch is deleted, the row owned by s will not satisfy + // valid_row_assignment. + // + m_value[s] = tmp; + SASSERT(!m_in_update_trail_stack.contains(s)); + save_value(s); + } + m_value[s] = get_implied_value(s); + TRACE("valid_row_assignment_bug", display_row_info(tout, r_id);); + SASSERT(!has_var_kind(r_id, BASE)); + SASSERT(!has_var_kind(r_id, QUASI_BASE)); + SASSERT(valid_row_assignment(m_rows[r_id])); + } + + /** + \brief Normalize row as a base row. A base row does not contain quasi-base and base + variables different from r.m_base_var. + */ + template + void theory_arith::normalize_base_row(unsigned r_id) { + if (lazy_pivoting_lvl() > 0) + normalize_quasi_base_row(r_id); + quasi_base_row2base_row(r_id); + } + + template + void theory_arith::mk_clause(literal l1, literal l2, unsigned num_params, parameter * params) { + get_context().mk_th_axiom(get_id(), l1, l2, num_params, params); + } + + template + void theory_arith::mk_clause(literal l1, literal l2, literal l3, unsigned num_params, parameter * params) { + get_context().mk_th_axiom(get_id(), l1, l2, l3, num_params, params); + } + + template + void theory_arith::mk_bound_axioms(atom * a1) { + theory_var v = a1->get_var(); + literal l1(a1->get_bool_var()); + numeral const & k1(a1->get_k()); + atom_kind kind1 = a1->get_atom_kind(); + TRACE("mk_bound_axioms", tout << "making bound axioms for v" << v << " " << kind1 << " " << k1 << "\n";); + atoms & occs = m_var_occs[v]; + typename atoms::iterator it = occs.begin(); + typename atoms::iterator end = occs.end(); + for (; it != end; ++it) { + atom * a2 = *it; + literal l2(a2->get_bool_var()); + numeral const & k2 = a2->get_k(); + atom_kind kind2 = a2->get_atom_kind(); + SASSERT(k1 != k2 || kind1 != kind2); + SASSERT(a2->get_var() == v); + parameter coeffs[3] = { parameter(symbol("farkas")), parameter(rational(1)), parameter(rational(1)) }; + if (kind1 == A_LOWER) { + if (kind2 == A_LOWER) { + // x >= k1, x >= k2 + if (k1 >= k2) mk_clause(~l1, l2, 3, coeffs); + else mk_clause(~l2, l1, 3, coeffs); + } + else { + // x >= k1, x <= k2 + if (k1 > k2) mk_clause(~l1, ~l2, 3, coeffs); + else mk_clause(l1, l2, 3, coeffs); + } + } + else { + if (kind2 == A_LOWER) { + // x <= k1, x >= k2 + if (k1 < k2) mk_clause(~l1, ~l2, 3, coeffs); + else mk_clause(l1, l2, 3, coeffs); + } + else { + // x <= k1, x <= k2 + if (k1 < k2) mk_clause(~l1, l2, 3, coeffs); + else mk_clause(~l2, l1, 3, coeffs); + } + } + } + } + + template + bool theory_arith::internalize_atom(app * n, bool gate_ctx) { + TRACE("arith_internalize", tout << "internalising atom:\n" << mk_pp(n, this->get_manager()) << "\n";); + context & ctx = get_context(); + SASSERT(m_util.is_le(n) || m_util.is_ge(n) || m_util.is_is_int(n)); + SASSERT(!ctx.b_internalized(n)); + atom_kind kind; + + if (m_util.is_is_int(n)) { + internalize_is_int(n); + if (ctx.b_internalized(n)) { + TRACE("arith_internalize", tout << "term was re-internalized: #" << n->get_id() << "\n";); + return true; + } + bool_var bv = ctx.mk_bool_var(n); + ctx.set_var_theory(bv, get_id()); + return true; + } + if (m_util.is_le(n)) + kind = A_UPPER; + else + kind = A_LOWER; + app * lhs = to_app(n->get_arg(0)); + app * rhs = to_app(n->get_arg(1)); + SASSERT(m_util.is_numeral(rhs)); + theory_var v = internalize_term_core(lhs); + if (v == null_theory_var) { + TRACE("arith_internalize", tout << "failed to internalize: #" << n->get_id() << "\n";); + return false; + } + if (ctx.b_internalized(n)) { + TRACE("arith_internalize", tout << "term was re-internalized: #" << n->get_id() << "\n";); + return true; + } + bool_var bv = ctx.mk_bool_var(n); + ctx.set_var_theory(bv, get_id()); + rational _k; + m_util.is_numeral(rhs, _k); + numeral k(_k); + atom * a = alloc(atom, bv, v, k, kind); + mk_bound_axioms(a); + m_unassigned_atoms[v]++; + atoms & occs = m_var_occs[v]; + occs.push_back(a); + m_atoms.push_back(a); + insert_bv2a(bv, a); + TRACE("arith_internalize", tout << "succeeded... v: " << v << " " << kind << " " << k << "\n";); + return true; + } + + template + bool theory_arith::internalize_term(app * term) { + TRACE("arith_internalize", tout << "internalising term:\n" << mk_pp(term, this->get_manager()) << "\n";); + theory_var v = internalize_term_core(term); + TRACE("arith_internalize", tout << "theory_var: " << v << "\n";); + return v != null_theory_var; + } + + template + void theory_arith::internalize_eq_eh(app * atom, bool_var v) { + if (m_params.m_arith_eager_eq_axioms) { + context & ctx = get_context(); + app * lhs = to_app(atom->get_arg(0)); + app * rhs = to_app(atom->get_arg(1)); + enode * n1 = ctx.get_enode(lhs); + enode * n2 = ctx.get_enode(rhs); + // The expression atom may be a theory axiom. In this case, it may not be in simplified form. + // So, an atom such as (= a a) may occur. The procedure mk_axioms, expects n1 != n2. + // So, we should check it. It doesn't make sense to create an axiom for (= a a) in the arith_eq_adapter. + if (n1->get_th_var(get_id()) != null_theory_var && + n2->get_th_var(get_id()) != null_theory_var && + n1 != n2) { + TRACE("mk_axioms_bug", tout << mk_bounded_pp(atom, get_manager(), 5) << "\n";); + m_arith_eq_adapter.mk_axioms(n1, n2); + } + } + } + + template + void theory_arith::apply_sort_cnstr(enode * n, sort * s) { + // do nothing... + } + + template + void theory_arith::assign_eh(bool_var v, bool is_true) { + atom * a = get_bv2a(v); + if (!a) return; + SASSERT(get_context().get_assignment(a->get_bool_var()) != l_undef); + SASSERT((get_context().get_assignment(a->get_bool_var()) == l_true) == is_true); + a->assign_eh(is_true, get_epsilon(a->get_var())); + m_asserted_bounds.push_back(a); + } + + template + void theory_arith::relevant_eh(app * n) { + TRACE("arith_relevant_eh", tout << "relevant_eh: " << mk_pp(n, get_manager()) << "\n";); + if (m_util.is_mod(n)) + mk_idiv_mod_axioms(n->get_arg(0), n->get_arg(1)); + else if (m_util.is_rem(n)) + mk_rem_axiom(n->get_arg(0), n->get_arg(1)); + else if (m_util.is_div(n)) + mk_div_axiom(n->get_arg(0), n->get_arg(1)); + else if (m_util.is_to_int(n)) + mk_to_int_axiom(n); + else if (m_util.is_is_int(n)) + mk_is_int_axiom(n); + } + + template + void theory_arith::new_eq_eh(theory_var v1, theory_var v2) { + TRACE("arith_new_eq_eh", tout << "#" << get_enode(v1)->get_owner_id() << " = #" << get_enode(v2)->get_owner_id() << "\n";); + TRACE("arith_new_eq_eh_detail", tout << mk_pp(get_enode(v1)->get_owner(), get_manager()) << "\n" << + mk_pp(get_enode(v2)->get_owner(), get_manager()) << "\n";); + + enode * n1 = get_enode(v1); + + if (!m_util.is_int(n1->get_owner()) && + !m_util.is_real(n1->get_owner())) { + return; + } + if (m_params.m_arith_eq_bounds) { + enode * n2 = get_enode(v2); + SASSERT(n1->get_root() == n2->get_root()); + if (m_util.is_numeral(n1->get_owner())) { + std::swap(v1, v2); + std::swap(n1, n2); + } + rational k; + bound * b1 = 0; + bound * b2 = 0; + if (m_util.is_numeral(n2->get_owner(), k)) { + inf_numeral val(k); + b1 = alloc(eq_bound, v1, val, B_LOWER, n1, n2); + b2 = alloc(eq_bound, v1, val, B_UPPER, n1, n2); + } + else { + if (n1->get_owner_id() > n2->get_owner_id()) + std::swap(n1, n2); + sort * st = get_manager().get_sort(n1->get_owner()); + app * minus_one = m_util.mk_numeral(rational::minus_one(), st); + app * s = m_util.mk_add(n1->get_owner(), m_util.mk_mul(minus_one, n2->get_owner())); + context & ctx = get_context(); + ctx.internalize(s, false); + enode * e_s = ctx.get_enode(s); + ctx.mark_as_relevant(e_s); + SASSERT(is_attached_to_var(e_s)); + theory_var v_s = e_s->get_th_var(get_id()); + b1 = alloc(eq_bound, v_s, inf_numeral::zero(), B_LOWER, n1, n2); + b2 = alloc(eq_bound, v_s, inf_numeral::zero(), B_UPPER, n1, n2); + } + m_bounds_to_delete.push_back(b1); + m_bounds_to_delete.push_back(b2); + m_asserted_bounds.push_back(b1); + m_asserted_bounds.push_back(b2); + } + else { + m_arith_eq_adapter.new_eq_eh(v1, v2); + } + } + + template + bool theory_arith::use_diseqs() const { + return true; + } + + template + void theory_arith::new_diseq_eh(theory_var v1, theory_var v2) { + TRACE("arith_new_diseq_eh", tout << mk_bounded_pp(get_enode(v1)->get_owner(), get_manager()) << "\n" << + mk_bounded_pp(get_enode(v2)->get_owner(), get_manager()) << "\n";); + m_stats.m_assert_diseq++; + m_arith_eq_adapter.new_diseq_eh(v1, v2); + } + + template + void theory_arith::restart_eh() { + m_arith_eq_adapter.restart_eh(); + } + + template + void theory_arith::init_search_eh() { + TRACE("arith_init_search", display(tout);); + m_num_conflicts = 0; + m_branch_cut_counter = 0; + m_eager_gcd = m_params.m_arith_eager_gcd; + if (lazy_pivoting_lvl() == 1) + elim_quasi_base_rows(); + move_unconstrained_to_base(); + m_arith_eq_adapter.init_search_eh(); + m_final_check_idx = 0; + m_nl_gb_exhausted = false; + m_nl_strategy_idx = 0; + } + + template + final_check_status theory_arith::final_check_core() { + unsigned old_idx = m_final_check_idx; + final_check_status result = FC_DONE; + final_check_status ok; + do { + TRACE("final_check_arith", tout << "m_final_check_idx: " << m_final_check_idx << ", result: " << result << "\n";); + switch (m_final_check_idx) { + case 0: + ok = check_int_feasibility(); + TRACE("final_check_arith", tout << "check_int_feasibility(), ok: " << ok << "\n";); + break; + case 1: + if (assume_eqs_core()) + ok = FC_CONTINUE; + else + ok = FC_DONE; + TRACE("final_check_arith", tout << "assume_eqs(), ok: " << ok << "\n";); + break; + default: + ok = process_non_linear(); + TRACE("final_check_arith", tout << "non_linear(), ok: " << ok << "\n";); + break; + } + m_final_check_idx = (m_final_check_idx + 1) % 3; + switch (ok) { + case FC_DONE: + break; + case FC_GIVEUP: + result = FC_GIVEUP; + break; + case FC_CONTINUE: + TRACE("final_check_arith", tout << "continue arith...\n";); + return FC_CONTINUE; + } + } + while (m_final_check_idx != old_idx); + if (result == FC_DONE && m_found_unsupported_op) + result = FC_GIVEUP; + return result; + } + + template + final_check_status theory_arith::final_check_eh() { + TRACE("arith_eq_adapter_info", m_arith_eq_adapter.display_already_processed(tout);); + TRACE("arith_final_check", display(tout);); +#if 0 + if (true /*m_params.m_smtlib_dump_lemmas*/) { + literal_buffer tmp; + + for (unsigned i = 0; i < m_asserted_qhead; i++) { + bound * b = m_asserted_bounds[i]; + if (b->is_atom()) { + atom* a = static_cast(b); + bool_var bv = a->get_bool_var(); + lbool is_true = get_context().get_assignment(bv); + if (is_true == l_true) { + tmp.push_back(literal(bv)); + } + else if (is_true == l_false) { + tmp.push_back(~literal(bv)); + } + } + } + + get_context().display_lemma_as_smt_problem(tmp.size(), tmp.c_ptr(), false_literal, "QF_LIA"); + } +#endif + + if (!propagate_core()) + return FC_CONTINUE; + if (delayed_assume_eqs()) + return FC_CONTINUE; + get_context().push_trail(value_trail(m_final_check_idx)); + m_liberal_final_check = true; + m_changed_assignment = false; + final_check_status result = final_check_core(); + if (result != FC_DONE) + return result; + if (!m_changed_assignment) + return FC_DONE; + m_liberal_final_check = false; + m_changed_assignment = false; + result = final_check_core(); + TRACE("final_check_arith", tout << "result: " << result << "\n";); + return result; + } + + template + bool theory_arith::can_propagate() { + return process_atoms() && m_asserted_qhead < m_asserted_bounds.size(); + } + + template + void theory_arith::propagate() { + TRACE("arith_propagate", tout << "propagate\n"; display(tout);); + if (!process_atoms()) + return; + propagate_core(); + } + + template + bool theory_arith::propagate_core() { + CASSERT("arith", wf_rows()); + CASSERT("arith", wf_columns()); + CASSERT("arith", valid_row_assignment()); + + propagate_linear_monomials(); + while (m_asserted_qhead < m_asserted_bounds.size()) { + bound * b = m_asserted_bounds[m_asserted_qhead]; + m_asserted_qhead++; + if (!assert_bound(b)) { + failed(); + return false; + } + } + if (!make_feasible()) { + failed(); + return false; + } + CASSERT("arith", satisfy_bounds()); + discard_update_trail(); + + SASSERT(m_update_trail_stack.empty()); + + propagate_bounds(); + SASSERT(m_asserted_qhead == m_asserted_bounds.size()); + SASSERT(m_update_trail_stack.empty()); + + CASSERT("arith", wf_rows()); + CASSERT("arith", wf_columns()); + CASSERT("arith", valid_row_assignment()); + CASSERT("arith", satisfy_bounds()); + return true; + } + + template + void theory_arith::failed() { + restore_assignment(); + m_to_patch.reset(); + m_to_check.reset(); + m_in_to_check.reset(); + } + + template + void theory_arith::flush_eh() { + std::for_each(m_atoms.begin(), m_atoms.end(), delete_proc()); + m_atoms.reset(); + std::for_each(m_bounds_to_delete.begin(), m_bounds_to_delete.end(), delete_proc()); + m_bounds_to_delete.reset(); + } + + template + void theory_arith::reset_eh() { + m_stats.reset(); + m_rows .reset(); + m_arith_eq_adapter .reset_eh(); + m_dead_rows .reset(); + m_columns .reset(); + m_data .reset(); + m_value .reset(); + m_old_value .reset(); + m_bounds[0] .reset(); + m_bounds[1] .reset(); + m_var_occs .reset(); + m_unassigned_atoms .reset(); + m_bool_var2atom .reset(); + m_var_pos .reset(); + std::for_each(m_atoms.begin(), m_atoms.end(), delete_proc()); + m_atoms .reset(); + std::for_each(m_bounds_to_delete.begin(), m_bounds_to_delete.end(), delete_proc()); + m_bounds_to_delete.reset(); + m_asserted_bounds .reset(); + m_asserted_qhead = 0; + m_to_patch .reset(); + m_left_basis .reset(); + m_blands_rule = false; + m_update_trail_stack .reset(); + m_in_update_trail_stack .reset(); + m_to_check .reset(); + m_in_to_check .reset(); + m_num_conflicts = 0; + m_bound_trail .reset(); + m_unassigned_atoms_trail .reset(); + m_scopes .reset(); + m_nl_monomials .reset(); + m_nl_propagated .reset(); + m_nl_rounds = 0; + m_nl_gb_exhausted = false; + m_nl_strategy_idx = 0; + theory::reset_eh(); + } + + template + bool theory_arith::validate_eq_in_model(theory_var v1, theory_var v2, bool is_true) const { + return true; + } + + /** + \brief Compute the value of a base or quasi-base variable using + the value of the dependent variables. + */ + template + typename theory_arith::inf_numeral const & theory_arith::get_implied_value(theory_var v) const { + SASSERT(is_quasi_base(v) || is_base(v)); + inf_numeral & sum = const_cast *>(this)->m_tmp; + sum.reset(); + unsigned r_id = get_var_row(v); + row const & r = m_rows[r_id]; + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead() && it->m_var != v) { + SASSERT(!is_quasi_base(it->m_var)); + SASSERT(get_value(it->m_var) == m_value[it->m_var]); + sum += it->m_coeff * get_value(it->m_var); + } + } + sum.neg(); + return sum; + } + + /** + \brief Compute the value of a base or quasi-base variable using + the old value of the dependent variables. By old, we mean the + value of the variable in the beginning of propagate(). Store the + result in 'result'. + + Return true if the old value is different from the current value. + */ + template + bool theory_arith::get_implied_old_value(theory_var v, inf_numeral & result) const { + SASSERT(is_quasi_base(v) || is_base(v)); + bool is_diff = false; + result.reset(); + unsigned r_id = get_var_row(v); + row const & r = m_rows[r_id]; + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead() && it->m_var != v) { + theory_var v2 = it->m_var; + SASSERT(!is_quasi_base(v2)); + SASSERT(get_value(v2) == m_value[v2]); + if (m_in_update_trail_stack.contains(v2)) { + result += it->m_coeff * m_old_value[v2]; + is_diff = true; + } + else { + result += it->m_coeff * m_value[v2]; + } + } + } + result.neg(); + return is_diff; + } + + template + theory_arith::theory_arith(ast_manager & m, theory_arith_params & params): + theory(m.get_family_id("arith")), + m_params(params), + m_util(m), + m_arith_eq_solver(m), + m_found_unsupported_op(false), + m_arith_eq_adapter(*this, params, m_util), + m_asserted_qhead(0), + m_to_patch(1024), + m_blands_rule(false), + m_random(params.m_arith_random_seed), + m_num_conflicts(0), + m_branch_cut_counter(0), + m_eager_gcd(m_params.m_arith_eager_gcd), + m_final_check_idx(0), + m_var_value_table(DEFAULT_HASHTABLE_INITIAL_CAPACITY, var_value_hash(*this), var_value_eq(*this)), + m_liberal_final_check(true), + m_changed_assignment(false), + m_assume_eq_head(0), + m_nl_rounds(0), + m_nl_gb_exhausted(false), + m_nl_new_exprs(m) { + } + + template + theory_arith::~theory_arith() { + } + + template + void theory_arith::setup() { + m_random.set_seed(m_params.m_arith_random_seed); + theory::setup(); + } + + // ----------------------------------- + // + // Add Row + // + // ----------------------------------- + + /** + \brief Set: row1 <- row1 + coeff * row2 + */ + template + void theory_arith::add_row(unsigned rid1, const numeral & coeff, unsigned rid2, bool apply_gcd_test) { + m_stats.m_add_rows++; + if (propagation_mode() != BP_NONE) + mark_row_for_bound_prop(rid1); + row & r1 = m_rows[rid1]; + row & r2 = m_rows[rid2]; + CASSERT("row_assignment_bug", valid_row_assignment(r1)); + CASSERT("row_assignment_bug", valid_row_assignment(r2)); + r1.compress_if_needed(m_columns); + r2.compress_if_needed(m_columns); + CASSERT("arith", check_null_var_pos()); + + r1.save_var_pos(m_var_pos); + + // + // loop over variables in row2, + // add terms in row2 to row1. + // +#define ADD_ROW(_SET_COEFF_, _ADD_COEFF_) \ + typename vector::const_iterator it = r2.begin_entries(); \ + typename vector::const_iterator end = r2.end_entries(); \ + for (; it != end; ++it) { \ + if (!it->is_dead()) { \ + theory_var v = it->m_var; \ + int pos = m_var_pos[v]; \ + if (pos == -1) { \ + /* variable v is not in row1 */ \ + int row_idx; \ + row_entry & r_entry = r1.add_row_entry(row_idx); \ + r_entry.m_var = v; \ + _SET_COEFF_; \ + column & c = m_columns[v]; \ + int col_idx; \ + col_entry & c_entry = c.add_col_entry(col_idx); \ + r_entry.m_col_idx = col_idx; \ + c_entry.m_row_id = rid1; \ + c_entry.m_row_idx = row_idx; \ + } \ + else { \ + /* variable v is in row1 */ \ + row_entry & r_entry = r1[pos]; \ + SASSERT(r_entry.m_var == v); \ + _ADD_COEFF_; \ + if (r_entry.m_coeff.is_zero()) { \ + int col_idx = r_entry.m_col_idx; \ + r1.del_row_entry(pos); \ + column & c = m_columns[v]; \ + c.del_col_entry(col_idx); \ + } \ + m_var_pos[v] = -1; \ + } \ + } \ + } ((void) 0) + + if (coeff.is_one()) { + ADD_ROW(r_entry.m_coeff = it->m_coeff, + r_entry.m_coeff += it->m_coeff); + } + else if (coeff.is_minus_one()) { + ADD_ROW(r_entry.m_coeff = it->m_coeff; r_entry.m_coeff.neg(), + r_entry.m_coeff -= it->m_coeff); + } + else { + ADD_ROW(r_entry.m_coeff = it->m_coeff; r_entry.m_coeff *= coeff, + r_entry.m_coeff += it->m_coeff * coeff); + } + + r1.reset_var_pos(m_var_pos); + CASSERT("arith", check_null_var_pos()); + CASSERT("row_assignment_bug", valid_row_assignment(r1)); + CASSERT("row_assignment_bug", valid_row_assignment(r2)); + if (apply_gcd_test) { + theory_var v = r1.get_base_var(); + if (is_int(v) && !get_value(v).is_int()) + gcd_test(r1); + } + } + + /** + \brief Set r1 <- r1 + a_xs[0].m_coeff * get_var_row(a_xs[0].m_var) + ... + a_xs[0].m_coeff * get_var_row(a_xs[sz-1].m_var) + + \pre For all i in [0..sz-1]. not is_non_base(a_xs[i]) + */ + template + void theory_arith::add_rows(unsigned r1, unsigned sz, linear_monomial * a_xs) { + if (sz == 0) + return; + for (unsigned i = 0; i < sz; i++) { + linear_monomial & m = a_xs[i]; + numeral c = m.m_coeff; + theory_var v = m.m_var; + SASSERT(!is_non_base(v)); + add_row(r1, c, get_var_row(v), false); + } + } + + // ----------------------------------- + // + // Assignment management + // + // ----------------------------------- + + template + void theory_arith::save_value(theory_var v) { + SASSERT(!is_quasi_base(v)); + if (!m_in_update_trail_stack.contains(v)) { + m_in_update_trail_stack.insert(v); + SASSERT(m_value[v] == get_value(v)); + m_old_value[v] = m_value[v]; + m_update_trail_stack.push_back(v); + TRACE("save_value", tout << "v" << v << " = " << get_value(v) << "\n";); + } + m_changed_assignment = true; + } + + template + void theory_arith::discard_update_trail() { + m_in_update_trail_stack.reset(); + m_update_trail_stack.reset(); + } + + template + void theory_arith::restore_assignment() { + CASSERT("arith", valid_row_assignment()); + TRACE("restore_assignment_bug", tout << "START restore_assignment...\n";); + typename svector::iterator it = m_update_trail_stack.begin(); + typename svector::iterator end = m_update_trail_stack.end(); + for(; it != end; ++it) { + theory_var v = *it; + TRACE("restore_assignment_bug", tout << "restoring v" << v << " <- " << m_old_value[v] << "\n";); + SASSERT(!is_quasi_base(v)); + SASSERT(m_in_update_trail_stack.contains(v)); + m_value[v] = m_old_value[v]; + } + m_update_trail_stack.reset(); + m_in_update_trail_stack.reset(); + CASSERT("arith", valid_row_assignment()); + } + + /** + \brief m_value[v] += delta + */ + template + void theory_arith::update_value_core(theory_var v, inf_numeral const & delta) { + save_value(v); + m_value[v] += delta; + if (is_base(v) && !m_to_patch.contains(v) && (below_lower(v) || above_upper(v))) { + m_to_patch.insert(v); + } + } + + /** + \brief m_value[v] += delta, and update dependent (non-base) variables. + */ + template + void theory_arith::update_value(theory_var v, inf_numeral const & delta) { + update_value_core(v, delta); + + column & c = m_columns[v]; + c.compress_if_needed(m_rows); + + inf_numeral delta2; + typename svector::const_iterator it = c.begin_entries(); + typename svector::const_iterator end = c.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) { + row & r = m_rows[it->m_row_id]; + theory_var s = r.get_base_var(); + if (s != null_theory_var && !is_quasi_base(s)) { + delta2 = delta; + delta2 *= r[it->m_row_idx].m_coeff; + delta2.neg(); + update_value_core(s, delta2); + } + } + } + } + + // ----------------------------------- + // + // Pivoting + // + // ----------------------------------- + + /** + \brief Make x_j the new base variable for row of x_i + x_j is assumed to have coefficient a_ij. + + Let row_id = m_base_var[x_i] + + rows[row_id] <- rows[row_id] / a_ij + + rows[other] <- row[other] - rows[row_id] * a_kj + Where a_kj is the coefficient of x_j in row[other] + */ + template + template + void theory_arith::pivot(theory_var x_i, theory_var x_j, numeral const & a_ij, bool apply_gcd_test) { + TRACE("arith_pivot", tout << "pivoting: v" << x_i << ", v" << x_j << "\n";); + m_stats.m_pivots++; + SASSERT(is_base(x_i) || is_quasi_base(x_i)); + SASSERT(x_i != x_j); + + int r_id = get_var_row(x_i); + row & r = m_rows[r_id]; + + SASSERT(r.is_coeff_of(x_j, a_ij)); + +#define DIVIDE_ROW(_ADJUST_COEFF_) \ + typename vector::iterator it = r.begin_entries(); \ + typename vector::iterator end = r.end_entries(); \ + for (; it != end; ++it) { \ + if (!it->is_dead()) { \ + _ADJUST_COEFF_; \ + } \ + } ((void) 0) + + if (a_ij.is_minus_one()) { + DIVIDE_ROW(it->m_coeff.neg()); + } + else if (!a_ij.is_one()) { + numeral tmp = a_ij; + DIVIDE_ROW(it->m_coeff /= tmp); + } + + set_var_row(x_i, -1); + set_var_row(x_j, r_id); + + SASSERT(r.m_base_var == x_i); + r.m_base_var = x_j; + + set_var_kind(x_i, NON_BASE); + set_var_kind(x_j, BASE); + + eliminate(x_j, apply_gcd_test); + + CASSERT("arith", wf_rows()); + CASSERT("arith", wf_columns()); + CASSERT("arith", valid_row_assignment()); + TRACE("arith_pivot", tout << "after pivoting:\n"; + display(tout);); + TRACE("pivot_shape", display_rows_shape(tout);); + TRACE("pivot_stats", display_rows_stats(tout);); + TRACE_CODE({ + static unsigned val = 0; + val ++; + if (val % 100 == 0) { + TRACE("pivot_bignums", display_rows_bignums(tout);); + }}); + } + + /** + \brief Eliminate x_i from the rows different from get_var_row(x_i) + + If Lazy = true, then x_i is only eliminated from base rows. + */ + template + template + void theory_arith::eliminate(theory_var x_i, bool apply_gcd_test) { + SASSERT(is_base(x_i) || is_quasi_base(x_i)); + unsigned r_id = get_var_row(x_i); + column & c = m_columns[x_i]; + numeral a_kj; + typename svector::iterator it = c.begin_entries(); + typename svector::iterator end = c.end_entries(); + int i = 0; + int s_pos = -1; + for (; it != end; ++it, ++i) { + if (!it->is_dead()) { + if (it->m_row_id != static_cast(r_id)) { + row & r2 = m_rows[it->m_row_id]; + theory_var s2 = r2.m_base_var; + if (s2 != null_theory_var && (!Lazy || is_base(s2))) { + a_kj = r2[it->m_row_idx].m_coeff; + a_kj.neg(); + add_row(it->m_row_id, a_kj, r_id, apply_gcd_test); + } + } + else { + s_pos = i; + } + } + } + CTRACE("eliminate", !Lazy && c.size() != 1, + tout << "eliminating v" << x_i << ", Lazy: " << Lazy << ", c.size: " << c.size() << "\n"; + display(tout);); + SASSERT(Lazy || c.size() == 1); + if (c.size() == 1) { + // When lazy pivoting is used, then after pivoting c may + // not be a singleton + c.compress_singleton(m_rows, s_pos); + } + } + + /** + \brief Set x_j to be the new base variable of row + owned by x_i + + a_ij - coefficient of x_j in the row owned by x_i + x_i_new_val - new value of x_i + */ + template + void theory_arith::update_and_pivot(theory_var x_i, theory_var x_j, numeral const & a_ij, inf_numeral const & x_i_new_val) { + CASSERT("arith", valid_row_assignment()); + TRACE("update_and_pivot_bug_detail", display(tout);); + SASSERT(is_base(x_i)); + inf_numeral theta = m_value[x_i]; + TRACE("update_and_pivot_bug", tout << "theta 1) " << theta << " " << x_i_new_val << "\n";); + theta -= x_i_new_val; + TRACE("update_and_pivot_bug", tout << "theta 2) " << theta << " " << a_ij << "\n";); + theta /= a_ij; + TRACE("update_and_pivot_bug", tout << "theta 3) " << theta << "\n";); + update_value(x_j, theta); + CTRACE("arith", get_value(x_i) != x_i_new_val, + tout << "x_i: " << x_i << ", x_j: " << x_j << ", a_ij: " << a_ij << ", x_i_new_val: " << x_i_new_val << "\n"; + tout << "new val: " << get_value(x_i) << ", theta: " << theta << "\n"; + display(tout);); + SASSERT(get_value(x_i) == x_i_new_val); + if (!m_to_patch.contains(x_j) && (below_lower(x_j) || above_upper(x_j))) + m_to_patch.insert(x_j); + pivot(x_i, x_j, a_ij, m_eager_gcd); + CASSERT("arith", valid_row_assignment()); + } + + /** + \brief Return the number of base variables that are non free and are v dependent. + The function adds 1 to the result if v is non free. + The function returns with a partial result r if r > best_so_far. + This function is used to select the pivot variable. + */ + template + int theory_arith::get_num_non_free_dep_vars(theory_var v, int best_so_far) { + int result = is_non_free(v); + column & c = m_columns[v]; + typename svector::const_iterator it = c.begin_entries(); + typename svector::const_iterator end = c.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) { + row & r = m_rows[it->m_row_id]; + theory_var s = r.get_base_var(); + if (s != null_theory_var && is_base(s)) { + result += is_non_free(s); + if (result > best_so_far) + return result; + } + } + } + return result; + } + + /** + \brief Using Bland's rule, select a variable x_j in the row r defining the base var x_i, + s.t. x_j can be used to patch the error in x_i. Return null_theory_var + if there is no x_j. Otherwise, return x_j and store its coefficient + in out_a_ij. + */ + template + theory_var theory_arith::select_blands_pivot_core(theory_var x_i, bool is_below, numeral & out_a_ij) { + SASSERT(is_base(x_i)); + theory_var max = get_num_vars(); + theory_var result = max; + row const & r = m_rows[get_var_row(x_i)]; + + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) { + theory_var x_j = it->m_var; + numeral const & a_ij = it->m_coeff; + bool is_neg = is_below ? a_ij.is_neg() : a_ij.is_pos(); + bool is_pos = !is_neg; + if (x_i != x_j && ((is_pos && above_lower(x_j)) || (is_neg && below_upper(x_j)))) { + SASSERT(is_non_base(x_j)); + if (x_j < result) { + result = x_j; + out_a_ij = a_ij; + } + } + } + } + return result < max ? result : null_theory_var; + } + + /** + \brief Select a variable x_j in the row r defining the base var x_i, + s.t. x_j can be used to patch the error in x_i. Return null_theory_var + if there is no x_j. Otherwise, return x_j and store its coefficient + in out_a_ij. + + The argument is_below is true (false) if x_i is below its lower + bound (above its upper bound). + */ + template + template + theory_var theory_arith::select_pivot_core(theory_var x_i, numeral & out_a_ij) { + SASSERT(is_base(x_i)); + theory_var max = get_num_vars(); + theory_var result = max; + row const & r = m_rows[get_var_row(x_i)]; + int n; + int best_col_sz = INT_MAX; + int best_so_far = INT_MAX; + + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + + for (; it != end; ++it) { + if (!it->is_dead()) { + theory_var x_j = it->m_var; + numeral const & a_ij = it->m_coeff; + + bool is_neg = is_below ? a_ij.is_neg() : a_ij.is_pos(); + bool is_pos = !is_neg; + if (x_i != x_j && ((is_pos && above_lower(x_j)) || (is_neg && below_upper(x_j)))) { + int num = get_num_non_free_dep_vars(x_j, best_so_far); + int col_sz = m_columns[x_j].size(); + if (num < best_so_far || (num == best_so_far && col_sz < best_col_sz)) { + result = x_j; + out_a_ij = a_ij; + best_so_far = num; + best_col_sz = col_sz; + n = 1; + } + else if (num == best_so_far && col_sz == best_col_sz) { + n++; + if (m_random()%n == 0) { + result = x_j; + out_a_ij = a_ij; + } + } + } + } + } + return result < max ? result : null_theory_var; + } + + /** + \brief Wrapper for select_blands_pivot_core and select_pivot_core + */ + template + theory_var theory_arith::select_pivot(theory_var x_i, bool is_below, numeral & out_a_ij) { + TRACE("select_pivot", tout << "m_blands_rule: " << m_blands_rule << " v" << x_i << "\n";); + CTRACE("select_pivot_info", x_i > 500, get_context().display(tout);); + if (m_blands_rule) + return select_blands_pivot_core(x_i, is_below, out_a_ij); + else if (is_below) + return select_pivot_core(x_i, out_a_ij); + else + return select_pivot_core(x_i, out_a_ij); + } + + // ----------------------------------- + // + // Make feasible + // + // ----------------------------------- + + /** + \brief Make the given variable feasible. This method assumes + that x_i is a base var. Return false if it was not possible to + make x_i feasible. + */ + template + bool theory_arith::make_var_feasible(theory_var x_i) { + CTRACE("arith_bug", !is_base(x_i), + tout << "x_i: " << x_i << ", below_lower(x_i): " << below_lower(x_i) << + ", above_upper(x_i): " << above_upper(x_i) << "\n"; display(tout);); + SASSERT(is_base(x_i)); + + bool is_below; + if (below_lower(x_i)) { + is_below = true; + } + else if (above_upper(x_i)) { + is_below = false; + } + else { + // x_i is already feasible + return true; + } + + TRACE("make_var_feasible", display_row(tout, get_var_row(x_i), false);); + + numeral a_ij; + theory_var x_j = select_pivot(x_i, is_below, a_ij); + if (x_j != null_theory_var) { + SASSERT(is_base(x_i)); + TRACE("pivot_bug", display_row_info(tout, get_var_row(x_i));); + update_and_pivot(x_i, x_j, a_ij, get_bound(x_i, !is_below)->get_value()); + return true; + } + else { + // conflict detected + sign_row_conflict(x_i, is_below); + return false; + } + } + + template + theory_var theory_arith::select_lg_error_var(bool least) { + TRACE("select_pivot", tout << "starting...\n";); + theory_var best = null_theory_var; + inf_numeral best_error; + inf_numeral curr_error; + typename var_heap::iterator it = m_to_patch.begin(); + typename var_heap::iterator end = m_to_patch.end(); + for (; it != end; ++it) { + theory_var v = *it; + if (below_lower(v)) + curr_error = lower(v)->get_value() - get_value(v); + else if (above_upper(v)) + curr_error = get_value(v) - upper(v)->get_value(); + else + continue; + SASSERT(curr_error > inf_numeral(0)); + if (best == null_theory_var || (!least && curr_error > best_error) || (least && curr_error < best_error)) { + TRACE("select_pivot", tout << "best: " << best << " v: " << v + << ", best_error: " << best_error << ", curr_error: " << curr_error << "\n";); + best = v; + best_error = curr_error; + } + } + if (best == null_theory_var) + m_to_patch.clear(); // all variables are satisfied + else + m_to_patch.erase(best); + return best; + } + + template + theory_var theory_arith::select_smallest_var() { + return m_to_patch.erase_min(); + } + + template + theory_var theory_arith::select_var_to_fix() { + if (m_blands_rule) + return select_smallest_var(); + switch (m_params.m_arith_pivot_strategy) { + case ARITH_PIVOT_GREATEST_ERROR: + return select_greatest_error_var(); + case ARITH_PIVOT_LEAST_ERROR: + return select_least_error_var(); + default: + return select_smallest_var(); + } + } + + /** + \brief Return true if it was possible to patch all variables in m_to_patch. + */ + template + bool theory_arith::make_feasible() { + TRACE("arith_make_feasible", tout << "make_feasible\n"; display(tout);); + CASSERT("arith", wf_rows()); + CASSERT("arith", wf_columns()); + CASSERT("arith", valid_row_assignment()); + + m_left_basis.reset(); + m_blands_rule = false; + unsigned num_repeated = 0; + while (!m_to_patch.empty()) { + theory_var v = select_var_to_fix(); + if (v == null_theory_var) { + // all variables were satisfied... + SASSERT(m_to_patch.empty()); + break; + } + if (!m_blands_rule) { + if (m_left_basis.contains(v)) { + num_repeated++; + if (num_repeated > blands_rule_threshold()) { + TRACE("blands_rule", tout << "using blands rule, " << num_repeated << "\n";); + // std::cerr << "BLANDS RULE...\n"; + m_blands_rule = true; + } + } + else { + m_left_basis.insert(v); + } + } + if (!make_var_feasible(v)) { + TRACE("arith_make_feasible", tout << "make_feasible: unsat\n"; display(tout);); + return false; + } + TRACE("arith_make_feasible_detail", display(tout);); + } + TRACE("arith_make_feasible", tout << "make_feasible: sat\n"; display(tout);); + CASSERT("arith", wf_rows()); + CASSERT("arith", wf_columns()); + CASSERT("arith", valid_row_assignment()); + return true; + } + + /** + \brief A row is in a sign inconsistency when it is implying a + lower (upper) bound on x_i, which is above (below) its known + upper (lower) bound. + */ + template + void theory_arith::sign_row_conflict(theory_var x_i, bool is_below) { + inf_numeral delta; + row const & r = m_rows[get_var_row(x_i)]; + int idx = r.get_idx_of(x_i); + SASSERT(idx >= 0); + bound * b = 0; + + // Remark: + // if x_i is an integer variable, then delta can be negative: + // + // Example: x_i <= 0 get_value(x_i) = 1/4 + // + // The value is above the upper bound. + // Since x_i is an integer, get_epsilon(x_i) = 1, and delta = -3/4 + + if (is_below) { + SASSERT(below_lower(x_i)); + b = lower(x_i); + if (relax_bounds()) { + delta = b->get_value(); + delta -= get_value(x_i); + delta -= get_epsilon(x_i); + if (delta.is_neg()) + delta.reset(); + } + } + else { + SASSERT(above_upper(x_i)); + b = upper(x_i); + if (relax_bounds()) { + delta = get_value(x_i); + delta -= b->get_value(); + delta -= get_epsilon(x_i); + if (delta.is_neg()) + delta.reset(); + } + } + + TRACE("sign_row_conflict", tout << "v" << x_i << " is_below: " << is_below << " delta: " << delta << "\n"; display_var(tout, x_i); + tout << "is_below_lower: " << below_lower(x_i) << ", is_above_upper: " << above_upper(x_i) << "\n";); + antecedents& ante = get_antecedents(); + explain_bound(r, idx, !is_below, delta, ante); + b->push_justification(ante, numeral(1)); + + + set_conflict(ante.lits().size(), ante.lits().c_ptr(), + ante.eqs().size(), ante.eqs().c_ptr(), ante, is_int(x_i), "farkas"); + // display_bounds_in_smtlib(); + } + + // ----------------------------------- + // + // Assert bound + // + // ----------------------------------- + + /** + \brief Assert x >= k, return false if a conflict is detected. + */ + template + bool theory_arith::assert_lower(bound * b) { + SASSERT(b->get_bound_kind() == B_LOWER); + theory_var v = b->get_var(); + inf_numeral const & k = b->get_value(); + + bound * u = upper(v); + bound * l = lower(v); + + if (u && k > u->get_value()) { + sign_bound_conflict(u, b); + return false; + } + + if (l && k <= l->get_value()) { + // redundant + return true; + } + + switch (get_var_kind(v)) { + case QUASI_BASE: + quasi_base_row2base_row(get_var_row(v)); + SASSERT(get_var_kind(v) == BASE); + case BASE: + if (!m_to_patch.contains(v) && get_value(v) < k) { + TRACE("to_patch_bug", tout << "need to be patched (assert_lower): "; display_var(tout, v);); + m_to_patch.insert(v); + } + break; + case NON_BASE: + if (get_value(v) < k) + set_value(v, k); + break; + } + + push_bound_trail(v, l, false); + set_bound(b, false); + + if (propagation_mode() != BP_NONE) + mark_rows_for_bound_prop(v); + + return true; + } + + /** + \brief Assert x <= k, return false if a conflict is detected. + */ + template + bool theory_arith::assert_upper(bound * b) { + SASSERT(b->get_bound_kind() == B_UPPER); + theory_var v = b->get_var(); + inf_numeral const & k = b->get_value(); + + bound * u = upper(v); + bound * l = lower(v); + + if (l && k < l->get_value()) { + sign_bound_conflict(l, b); + return false; + } + + if (u && k >= u->get_value()) { + // redundant + return true; + } + + switch (get_var_kind(v)) { + case QUASI_BASE: + quasi_base_row2base_row(get_var_row(v)); + SASSERT(get_var_kind(v) == BASE); + case BASE: + if (!m_to_patch.contains(v) && get_value(v) > k) { + TRACE("to_patch_bug", tout << "need to be patched (assert upper): "; display_var(tout, v);); + m_to_patch.insert(v); + } + break; + case NON_BASE: + if (get_value(v) > k) + set_value(v, k); + break; + } + + push_bound_trail(v, u, true); + set_bound(b, true); + + if (propagation_mode() != BP_NONE) + mark_rows_for_bound_prop(v); + + return true; + } + + template + bool theory_arith::assert_bound(bound * b) { + TRACE("assert_bound", display_bound(tout, b);); + theory_var v = b->get_var(); + + if (b->is_atom()) { + CTRACE("unassigned_atoms", m_unassigned_atoms[v] <= 0, display_var(tout, v);); + SASSERT(m_unassigned_atoms[v] > 0); + push_dec_unassigned_atoms_trail(v); + m_unassigned_atoms[v]--; + } + + bool result = true; + switch (b->get_bound_kind()) { + case B_LOWER: + m_stats.m_assert_lower++; + result = assert_lower(b); + break; + case B_UPPER: + m_stats.m_assert_upper++; + result = assert_upper(b); + break; + } + + TRACE("arith_assert", tout << "result: " << result << "\n"; display(tout);); + return result; + } + + /** + \brief Sign a conflict because the bounds b1 and b2 are contradictory. + */ + template + void theory_arith::sign_bound_conflict(bound * b1, bound * b2) { + SASSERT(b1->get_var() == b2->get_var()); + antecedents& ante = get_antecedents(); + b1->push_justification(ante, numeral(1)); + b2->push_justification(ante, numeral(1)); + + set_conflict(ante.lits().size(), ante.lits().c_ptr(), ante.eqs().size(), ante.eqs().c_ptr(), ante, is_int(b1->get_var()), "farkas"); + TRACE("arith_conflict", tout << "bound conflict\n";); + } + + // ----------------------------------- + // + // Bound propagation + // + // ----------------------------------- + + /** + \brief Mark the row r1 for bound propagation. + */ + template + void theory_arith::mark_row_for_bound_prop(unsigned r1) { + if (!m_in_to_check.contains(r1) && m_rows[r1].m_base_var != null_theory_var) { + m_in_to_check.insert(r1); + m_to_check.push_back(r1); + } + } + + /** + \brief Mark all rows that contain v for bound propagation. + */ + template + void theory_arith::mark_rows_for_bound_prop(theory_var v) { + column const & c = m_columns[v]; + typename svector::const_iterator it = c.begin_entries(); + typename svector::const_iterator end = c.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) + mark_row_for_bound_prop(it->m_row_id); + } + } + + /** + \brief Given a row: + a_1 * x_1 + ... + a_n * x_n = 0 + + Claim: + If forall i in [1..n]. (a_i > 0 => upper(x_i) != oo) and (a_i < 0 => lower(x_i) != -oo) + Then row implies a lower bound for every monomial in the row. + Proof: + Without loss of generality, we consider the monomial a_1 * x_1 + a_1 * x_1 = - a_2 * x_2 - ... - a_n * x_n + = (Sum_{a_i < 0} -a_i * x_i) + (Sum_{a_j > 0} -a_j * x_j) + >= (Sum_{a_i < 0} -a_i * lower(x_i)) + (Sum_{a_j > 0} -a_j * upper(x_j)) + + If one the condition fails for the monomial a_i * x_i, then the row can still be used + to imply a lower bound for this monomial. + + -4*x + 2*y - 3*z = 0 + y <= 1 + z >= 1 + -x = -2*y + 3*z >= -2 + 3 >= 1 + -4*x >= 1 + + Remark: the lower bound is not for the variable, but for the monomial. + + Claim: + If forall i in [1..n]. (a_i > 0 => lower(x_i) != oo) and (a_i < 0 => upper(x_i) != -oo) + Then row implies a upper bound for every monomial in the row. + Proof: similar to the previous claim. + + The result is stored in lower_idx and upper_idx + - lower_idx >= 0 : row can imply a lower bound for the monomial at 'lower_idx' + - lower_idx == -1 : row can imply a lower bound for every monomial in the row. + - lower_idx == -2 : row cannot be used to imply a lower bound. + + - upper_idx >= 0 : row can imply a upper bound for the monomial at 'upper_idx' + - upper_idx == -1 : row can imply a upper bound for every monomial in the row. + - upper_idx == -2 : row cannot be used to imply a upper bound. + */ + template + void theory_arith::is_row_useful_for_bound_prop(row const & r, int & lower_idx, int & upper_idx) const { + lower_idx = -1; + upper_idx = -1; + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (int i = 0; it != end; ++it, ++i) { + if (!it->is_dead()) { +#define UPDATE_IDX(IDX) IDX = IDX == -1 ? i : -2 + if (skip_big_coeffs() && it->m_coeff.is_big()) { + TRACE("is_row_useful", tout << "skipping row that contains big number...\n"; display_row_info(tout, r);); + lower_idx = -2; + upper_idx = -2; + return; + } + bool is_pos = it->m_coeff.is_pos(); + if (lower(it->m_var) == 0) { + if (is_pos) { + UPDATE_IDX(upper_idx); + } + else { + UPDATE_IDX(lower_idx); + } + } + if (upper(it->m_var) == 0) { + if (is_pos) { + UPDATE_IDX(lower_idx); + } + else { + UPDATE_IDX(upper_idx); + } + } + if (lower_idx == -2 && upper_idx == -2) + return; + } + } + } + + /** + \brief Imply a lower/upper bound for the monomial stored at position idx. + Then this bound is used to produce a bound for the monomial variable. + */ + template + void theory_arith::imply_bound_for_monomial(row const & r, int idx, bool is_lower) { + row_entry const & entry = r[idx]; + if (m_unassigned_atoms[entry.m_var] > 0) { + inf_numeral implied_k; + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (int idx2 = 0; it != end; ++it, ++idx2) { + if (!it->is_dead() && idx != idx2) { + bound * b = get_bound(it->m_var, is_lower ? it->m_coeff.is_pos() : it->m_coeff.is_neg()); + SASSERT(b); + // implied_k -= it->m_coeff * b->get_value(); + implied_k.submul(it->m_coeff, b->get_value()); + } + } + implied_k /= entry.m_coeff; + if (entry.m_coeff.is_pos() == is_lower) { + // implied_k is a lower bound for entry.m_var + bound * curr = lower(entry.m_var); + if (curr == 0 || implied_k > curr->get_value()) { + TRACE("arith_imply_bound", + tout << "implying lower bound for v" << entry.m_var << " " << implied_k << " using row:\n"; + display_row_info(tout, r); + display_var(tout, entry.m_var);); + mk_implied_bound(r, idx, is_lower, entry.m_var, B_LOWER, implied_k); + } + } + else { + // implied_k is an upper bound for it->m_var + bound * curr = upper(entry.m_var); + if (curr == 0 || implied_k < curr->get_value()) { + TRACE("arith_imply_bound", + tout << "implying upper bound for v" << entry.m_var << " " << implied_k << " using row:\n"; + display_row_info(tout, r); + display_var(tout, entry.m_var);); + mk_implied_bound(r, idx, is_lower, entry.m_var, B_UPPER, implied_k); + } + } + } + } + + /** + \brief Auxiliary method. See is_row_useful_for_bound_prop + + If is_lower = true (false), then imply a lower (upper) bound for all + monomials in the row. The monomial bounds are used to compute bounds + for the monomial variables. + */ + template + void theory_arith::imply_bound_for_all_monomials(row const & r, bool is_lower) { + // Traverse the row once and compute + // bb = (Sum_{a_i < 0} -a_i*lower(x_i)) + (Sum_{a_j > 0} -a_j * upper(x_j)) If is_lower = true + // bb = (Sum_{a_i > 0} -a_i*lower(x_i)) + (Sum_{a_j < 0} -a_j * upper(x_j)) If is_lower = false + inf_numeral bb; + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) { + inf_numeral const & b = get_bound(it->m_var, is_lower ? it->m_coeff.is_pos() : it->m_coeff.is_neg())->get_value(); + // bb -= it->m_coeff * b; + bb.submul(it->m_coeff, b); + } + } + + inf_numeral implied_k; + it = r.begin_entries(); + for (int idx = 0; it != end; ++it, ++idx) { + if (!it->is_dead() && m_unassigned_atoms[it->m_var] > 0) { + inf_numeral const & b = get_bound(it->m_var, is_lower ? it->m_coeff.is_pos() : it->m_coeff.is_neg())->get_value(); + implied_k = bb; + // implied_k += it->m_coeff * b; + implied_k.addmul(it->m_coeff, b); + // implied_k is a bound for the monomial in position it + implied_k /= it->m_coeff; + TRACE("arith_imply_bound", + display_var(tout, it->m_var); + tout << "implied bound: " << (it->m_coeff.is_pos() ? ">=" : "<=") << implied_k << "\n";); + if (it->m_coeff.is_pos() == is_lower) { + // implied_k is a lower bound for it->m_var + bound * curr = lower(it->m_var); + if (curr == 0 || implied_k > curr->get_value()) { + // improved lower bound + TRACE("arith_imply_bound", + tout << "implying lower bound for v" << it->m_var << " " << implied_k << " using row:\n"; + display_row_info(tout, r); + display_var(tout, it->m_var);); + mk_implied_bound(r, idx, is_lower, it->m_var, B_LOWER, implied_k); + } + } + else { + // implied_k is an upper bound for it->m_var + bound * curr = upper(it->m_var); + if (curr == 0 || implied_k < curr->get_value()) { + // improved upper bound + TRACE("arith_imply_bound", + tout << "implying upper bound for v" << it->m_var << " " << implied_k << " using row:\n"; + display_row_info(tout, r); + display_var(tout, it->m_var);); + mk_implied_bound(r, idx, is_lower, it->m_var, B_UPPER, implied_k); + } + } + } + } + } + + /** + \brief Create an explanation for the lower/upper bound of the variable at position idx. + + \remark delta is used for relaxing the explanation. That is, the implied bound can be delta weaker the the + computed value. + + \remark the is_lower parameter is a little bit counterintuitive. It says if the other monomials + imply a lower (upper) bound for the monomial at position idx. + + Store the result in 'antecedent' + */ + template + void theory_arith::explain_bound(row const & r, int idx, bool is_lower, inf_numeral & delta, antecedents& ante) { + SASSERT(delta >= inf_numeral::zero()); + if (!relax_bounds() && (!ante.lits().empty() || !ante.eqs().empty())) + return; + context & ctx = get_context(); + ante.reset(); // !!!!TBD: should equality ante also be reset here!!!! + row_entry const & entry = r[idx]; + numeral coeff = entry.m_coeff; + if (relax_bounds()) { + // if the variable v at position idx can have a delta increase (decrease) of 'delta', then + // the monomial (coeff * v) at position idx can have a delta increase (decrease) of '|coeff| * delta' + if (coeff.is_neg()) + coeff.neg(); + delta *= coeff; // adjust delta + } + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + TRACE("arith_proof", display_row(tout, r, false); ); + for (int idx2 = 0; it != end; ++it, ++idx2) { + if (!it->is_dead() && idx != idx2) { + bound * b = get_bound(it->m_var, is_lower ? it->m_coeff.is_pos() : it->m_coeff.is_neg()); + SASSERT(b); + if (!b->has_justification()) + continue; + if (!relax_bounds() || delta.is_zero()) { + b->push_justification(ante, it->m_coeff); + continue; + } + numeral coeff = it->m_coeff; + bool is_b_lower = b->get_bound_kind() == B_LOWER; + if (coeff.is_neg()) + coeff.neg(); + numeral inv_coeff(1); + inv_coeff /= coeff; + inf_numeral k_1 = b->get_value(); + inf_numeral limit_k1; + // if the max decrease (increase) of the curr monomial (coeff * v2) is delta, then + // the maximal decrease (increase) of v2 is (1/|coeff| * delta) + if (is_b_lower) { + limit_k1 = k_1; + // limit_k1 -= delta * coeff; + limit_k1.submul(inv_coeff, delta); + } + else { + limit_k1 = k_1; + // limit_k1 += delta * coeff; + limit_k1.addmul(inv_coeff, delta); + } + TRACE("propagate_bounds_bug", tout << "is_b_lower: " << is_b_lower << " k1: " << k_1 << " limit_k1: " + << limit_k1 << " delta: " << delta << " coeff: " << coeff << "\n";); + inf_numeral k_2 = k_1; + atom * new_atom = 0; + atoms & as = m_var_occs[it->m_var]; + typename atoms::iterator it = as.begin(); + typename atoms::iterator end = as.end(); + for (; it != end; ++it) { + atom * a = *it; + if (a == b) + continue; + bool_var bv = a->get_bool_var(); + lbool val = ctx.get_assignment(bv); + if (val == l_undef) + continue; + // TODO: check if the following line is a bottleneck + a->assign_eh(val == l_true, get_epsilon(a->get_var())); + if (val != l_undef && a->get_bound_kind() == b->get_bound_kind()) { + SASSERT((ctx.get_assignment(bv) == l_true) == a->is_true()); + inf_numeral a_val = a->get_value(); + if (is_b_lower) { + if (a_val >= limit_k1 && a_val < k_2) { + k_2 = a_val; + new_atom = a; + } + } + else { + if (a_val <= limit_k1 && a_val > k_2) { + k_2 = a_val; + new_atom = a; + } + } + } + } + SASSERT(!is_b_lower || k_2 <= k_1); + SASSERT(is_b_lower || k_2 >= k_1); + if (new_atom == 0) { + b->push_justification(ante, coeff); + continue; + } + SASSERT(!is_b_lower || k_2 < k_1); + SASSERT(is_b_lower || k_2 > k_1); + if (is_b_lower) { + TRACE("propagate_bounds", tout << "coeff: " << coeff << ", k_1 - k_2: " << k_1 - k_2 << ", delta: " << delta << "\n";); + delta -= coeff*(k_1 - k_2); + } + else { + TRACE("propagate_bounds", tout << "coeff: " << coeff << ", k_2 - k_1: " << k_2 - k_1 << ", delta: " << delta << "\n";); + delta -= coeff*(k_2 - k_1); + } + TRACE("propagate_bounds", tout << "delta (after replace): " << delta << "\n";); + new_atom->push_justification(ante, coeff); + SASSERT(delta >= inf_numeral::zero()); + } + } + } + + template + void theory_arith::mk_implied_bound(row const & r, unsigned idx, bool is_lower, theory_var v, bound_kind kind, inf_numeral const & k) { + atoms & as = m_var_occs[v]; + antecedents& ante = get_antecedents(); + inf_numeral const & epsilon = get_epsilon(v); + inf_numeral delta; + typename atoms::iterator it = as.begin(); + typename atoms::iterator end = as.end(); + for (; it != end; ++it) { + atom * a = *it; + bool_var bv = a->get_bool_var(); + literal l(bv); + if (get_context().get_assignment(bv) == l_undef) { + numeral const & k2 = a->get_k(); + delta.reset(); + if (a->get_atom_kind() == A_LOWER) { + // v >= k k >= k2 |- v >= k2 + if (kind == B_LOWER && k >= k2) { + if (relax_bounds()) { + delta = k; + delta -= k2; + } + TRACE("propagate_bounds", tout << "v" << v << " >= " << k << ", v" << v << " >= " << k2 << ", delta: " << delta << "\n"; + display_row(tout, r);); + assign_bound_literal(l, r, idx, is_lower, delta, ante); + } + // v <= k k < k2 |- v < k2 |- not v >= k2 + if (kind == B_UPPER && k < k2) { + // it is not sufficient to check whether k < k2. + // example: + // k = -1/5*epsilon + // k2 = 0 + // Thus, v <= -1/5*epsilon + // (not v >= 0) which is equivalent to v <= -epsilon. + delta = k2; + delta -= k; + delta -= epsilon; + if (delta.is_nonneg()) { + TRACE("propagate_bounds", tout << "v" << v << " <= " << k << ", not v" << v << " >= " << k2 << ", delta: " << delta << "\n"; + display_row(tout, r);); + assign_bound_literal(~l, r, idx, is_lower, delta, ante); + } + } + } + else { + // v >= k k > k2 |- v > k2 |- not v <= k2 + if (kind == B_LOWER && k > k2) { + // it is not sufficient to check whether k > k2. + // see example above. + delta = k; + delta -= k2; + delta -= epsilon; + if (delta.is_nonneg()) { + TRACE("propagate_bounds", tout << "v" << v << " >= " << k << ", not v" << v << " <= " << k2 << ", delta: " << delta << "\n"; + display_row(tout, r);); + assign_bound_literal(~l, r, idx, is_lower, delta, ante); + } + } + // v <= k k <= k2 |- v <= k2 + if (kind == B_UPPER && k <= k2) { + if (relax_bounds()) { + delta = k2; + delta -= k; + } + TRACE("propagate_bounds", tout << "v" << v << " <= " << k << ", v" << v << " <= " << k2 << ", delta: " << delta << "\n"; + display_row(tout, r);); + assign_bound_literal(l, r, idx, is_lower, delta, ante); + } + } + } + } + } + + template + void theory_arith::assign_bound_literal(literal l, row const & r, unsigned idx, bool is_lower, inf_numeral & delta, antecedents& ante) { + m_stats.m_bound_props++; + context & ctx = get_context(); + explain_bound(r, idx, is_lower, delta, ante); + if (dump_lemmas()) { + char const * logic = is_int(r.get_base_var()) ? "QF_LIA" : "QF_LRA"; + ctx.display_lemma_as_smt_problem(ante.lits().size(), ante.lits().c_ptr(), + ante.eqs().size(), ante.eqs().c_ptr(), l, logic); + } + TRACE("propagate_bounds", + literal_vector::const_iterator it = ante.lits().begin(); + literal_vector::const_iterator end = ante.lits().end(); + for (; it != end; ++it) { + ctx.display_detailed_literal(tout, *it); + tout << " "; + } + eq_vector::const_iterator it2 = ante.eqs().begin(); + eq_vector::const_iterator end2 = ante.eqs().end(); + for (; it2 != end2; ++it2) { + tout << "#" << it2->first->get_owner_id() << "=#" << it2->second->get_owner_id() << " "; + } + tout << " --> "; + ctx.display_detailed_literal(tout, l); + tout << "\n";); + 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)); + justification * js = 0; + if (proofs_enabled) { + js = alloc(theory_lemma_justification, get_id(), ctx, lits.size(), lits.c_ptr(), + ante.num_params(), ante.params("assign-bounds")); + } + ctx.mk_clause(lits.size(), lits.c_ptr(), js, CLS_AUX_LEMMA, 0); + } + else { + region & r = ctx.get_region(); + ctx.assign(l, ctx.mk_justification( + ext_theory_propagation_justification( + get_id(), r, ante.lits().size(), ante.lits().c_ptr(), + ante.eqs().size(), ante.eqs().c_ptr(), l, + ante.num_params(), ante.params("assign-bounds")))); + } + } + + /** + \brief Traverse rows in m_to_check and try do derive improved bounds for + the variables occurring in them. + */ + template + void theory_arith::propagate_bounds() { + TRACE("propagate_bounds_detail", display(tout);); + typename svector::iterator it = m_to_check.begin(); + typename svector::iterator end = m_to_check.end(); + for (; it != end; ++it) { + row & r = m_rows[*it]; + if (r.get_base_var() != null_theory_var) { + if (r.size() < max_lemma_size()) { // Ignore big rows. + int lower_idx; + int upper_idx; + is_row_useful_for_bound_prop(r, lower_idx, upper_idx); + + if (lower_idx >= 0) { + imply_bound_for_monomial(r, lower_idx, true); + } + else if (lower_idx == -1) { + imply_bound_for_all_monomials(r, true); + } + + if (upper_idx >= 0) { + imply_bound_for_monomial(r, upper_idx, false); + } + else if (upper_idx == -1) { + imply_bound_for_all_monomials(r, false); + } + + // sneaking cheap eq detection in this loop + propagate_cheap_eq(*it); + } + +#if 0 + theory_var v = r.get_base_var(); + if (!is_int(v) || get_value(v).is_int()) { + // If an integer value is not assigned to an integer value, then + // bound propagation can diverge. + m_in_to_check.remove(v); + } +#endif + } + } + m_to_check.reset(); + m_in_to_check.reset(); + } + + // ----------------------------------- + // + // Justification + // + // ----------------------------------- + + template + void theory_arith::set_conflict(unsigned num_literals, literal const * lits, unsigned num_eqs, enode_pair const * eqs, + antecedents& bounds, bool is_lia, char const* proof_rule) { + SASSERT(num_literals != 0 || num_eqs != 0); + context & ctx = get_context(); + m_stats.m_conflicts++; + m_num_conflicts++; + if (dump_lemmas()) { + char const * logic = is_lia ? "QF_LIA" : "QF_LRA"; + ctx.display_lemma_as_smt_problem(num_literals, lits, num_eqs, eqs, false_literal, logic); + } + region & r = ctx.get_region(); + TRACE("arith_conflict", + for (unsigned i = 0; i < num_literals; i++) { + ctx.display_detailed_literal(tout, lits[i]); + tout << " "; + if (proofs_enabled) { + tout << "bound: " << bounds.lit_coeffs()[i] << "\n"; + } + } + for (unsigned i = 0; i < num_eqs; i++) { + tout << "#" << eqs[i].first->get_owner_id() << "=#" << eqs[i].second->get_owner_id() << " "; + if (proofs_enabled) { + tout << "bound: " << bounds.eq_coeffs()[i] << "\n"; + } + } + for (unsigned i = 0; i < bounds.num_params(); ++i) { + tout << bounds.params(proof_rule)[i] << "\n"; + } + tout << "\n";); + ctx.set_conflict( + ctx.mk_justification( + ext_theory_conflict_justification(get_id(), r, num_literals, lits, num_eqs, eqs, + bounds.num_params(), bounds.params(proof_rule)))); + } + + /** + \brief Collect the proofs for the fixed variables in the given row. Store + the proofs in result. + */ + template + void theory_arith::collect_fixed_var_justifications(row const & r, antecedents& antecedents) const { + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead() && is_fixed(it->m_var)) { + lower(it->m_var)->push_justification(antecedents, it->m_coeff); + upper(it->m_var)->push_justification(antecedents, it->m_coeff); + } + } + } + + // ----------------------------------- + // + // Model generation + // + // ----------------------------------- + + // + // The arithmetic module uses infinitesimals. So, + // an inf_numeral (n,k) represents n + k*epsilon + // where epsilon is a very small number. + // In order to generate a model, we need to compute + // a value for epsilon in a way all bounds remain + // satisfied. + // + // 1) Handling inequalities: (n1, k1) <= (n2, k2) + // + // The only intersting case is n1 < n2 and k1 > k2. + // Using the definition of infinitesimal numbers + // we have: + // n1 + k1 * epsilon <= n2 + k2 - epsilon + // Therefore: + // epsilon <= (n2 - n1) / (k1 - k2) + // + // Starting at Z3 V2.0, we split disequalities. + // So, we do not need to handle them. If we decide + // to support them again in the future: + // + // 2) Handling disequalities: (n1, k1) /= n2 + // + // case a) k1 is positive and n1 < n2 + // Thus, epsilon < (n2 - n1) / k1 + // => epsilon <= (n2 - n1) / 2*k1 + // + // case b) k1 is negative and n1 > n2 + // Similarly, epsilon <= (n2 - n1) / 2*k1 + // + + /** + \brief Update the value of epsilon using the inequality l <= u + */ + template + void theory_arith::update_epsilon(const inf_numeral & l, const inf_numeral & u) { + if (l.get_rational() < u.get_rational() && + l.get_infinitesimal() > u.get_infinitesimal()) { + numeral new_epsilon = (u.get_rational() - l.get_rational()) / (l.get_infinitesimal() - u.get_infinitesimal()); + if (new_epsilon < m_epsilon) { + m_epsilon = new_epsilon; + } + } + SASSERT(m_epsilon.is_pos()); + } + + template + void theory_arith::compute_epsilon() { + m_epsilon = numeral(1); + theory_var num = get_num_vars(); + for (theory_var v = 0; v < num; v++) { + bound * l = lower(v); + bound * u = upper(v); + if (l != 0) + update_epsilon(l->get_value(), get_value(v)); + if (u != 0) + update_epsilon(get_value(v), u->get_value()); + } + TRACE("epsilon_bug", tout << "epsilon: " << m_epsilon << "\n";); + } + + /** + The epsilon computed by compute_epsilon may accidentally make two shared + variables to have the same assignment. This method keeps dividing + epsilon by 2 until this "clash" does not occur. + Here is an example of the problem + + Assignment: + x -> 9.5 + y -> 10 - epsilon + + x and y have different assignments. However, if compute_epsilon sets epsilon + to 0.5, then x and y become 9.5. However, an equality is not propagated to the core + since in the assignment above they are assigned to distinct values. + + This bug was reported by Marcello Bersani. + Remark: this is not really a soundness bug. The result sat/unsat produced by Z3 was still correct. + However, the model construction was incorrect. Perhaps, this explains why this bug was not + detected before. + */ + template + void theory_arith::refine_epsilon() { + typedef map, default_eq > rational2var; + while (true) { + rational2var mapping; + theory_var num = get_num_vars(); + bool refine = false; + for (theory_var v = 0; v < num; v++) { + if (is_int(v)) + continue; + inf_numeral const & val = get_value(v); + rational value = val.get_rational().to_rational() + m_epsilon.to_rational() * val.get_infinitesimal().to_rational(); + theory_var v2; + if (mapping.find(value, v2)) { + SASSERT(!is_int(v2)); + if (get_value(v) != get_value(v2)) { + // v and v2 are not known to be equal. + // The choice of m_epsilon is making them equal. + TRACE("refine_epsilon", + tout << "v" << v << " v" << v2 << " " << get_value(v) << " " << get_value(v2) << " " << value << std::endl; + ); + refine = true; + break; + } + } + else { + mapping.insert(value, v); + } + } + if (!refine) + return; + numeral two(2); + m_epsilon = m_epsilon / two; + TRACE("refine_epsilon", tout << "new epsilon..." << m_epsilon << std::endl;); + } + } + + template + void theory_arith::init_model(model_generator & m) { + TRACE("theory_arith", tout << "init model invoked...\n";); + m_factory = alloc(arith_factory, get_manager()); + m.register_factory(m_factory); + compute_epsilon(); + refine_epsilon(); + } + + template + model_value_proc * theory_arith::mk_value(enode * n, model_generator & mg) { + theory_var v = n->get_th_var(get_id()); + SASSERT(v != null_theory_var); + inf_numeral const & val = get_value(v); + rational num = val.get_rational().to_rational() + m_epsilon.to_rational() * val.get_infinitesimal().to_rational(); + return alloc(expr_wrapper_proc, m_factory->mk_value(num, is_int(v))); + } + + // ----------------------------------- + // + // Model checker support + // + // ----------------------------------- + + template + bool theory_arith::get_value(enode * n, expr_ref & r) { + theory_var v = n->get_th_var(get_id()); + if (v == null_theory_var) { + // TODO: generate fresh value different from other get_value(v) for all v. + return false; + } + inf_numeral const & val = get_value(v); + if (!val.get_infinitesimal().is_zero()) { + // TODO: add support for infinitesimals + return false; + } + numeral _val = val.get_rational(); + r = m_util.mk_numeral(_val.to_rational(), is_int(v)); + return true; + } + + // ----------------------------------- + // + // Backtracking + // + // ----------------------------------- + + template + void theory_arith::push_scope_eh() { + theory::push_scope_eh(); + m_scopes.push_back(scope()); + scope & s = m_scopes.back(); + s.m_atoms_lim = m_atoms.size(); + s.m_bound_trail_lim = m_bound_trail.size(); + s.m_unassigned_atoms_trail_lim = m_unassigned_atoms_trail.size(); + s.m_asserted_bounds_lim = m_asserted_bounds.size(); + s.m_asserted_qhead_old = m_asserted_qhead; + s.m_bounds_to_delete_lim = m_bounds_to_delete.size(); + s.m_nl_monomials_lim = m_nl_monomials.size(); + s.m_nl_propagated_lim = m_nl_propagated.size(); + CASSERT("arith", wf_rows()); + CASSERT("arith", wf_columns()); + CASSERT("arith", valid_row_assignment()); + CASSERT("arith", satisfy_bounds()); + } + + template + void theory_arith::pop_scope_eh(unsigned num_scopes) { + CASSERT("arith", wf_rows()); + CASSERT("arith", wf_columns()); + CASSERT("arith", valid_row_assignment()); + TRACE("arith_pop_scope_bug", display(tout);); + // The m_update_trail_stack may not be empty. + // In an old version, only propagate_core and methods invoked by propagate_core were + // inserting elements in the m_update_trail_stack stack. + // This is not true anymore. The method quasi_base_row2base_row also does that. + // So, restore_assignment must be invoked. In most cases, it is a noop since the stack is empty. + restore_assignment(); + m_to_patch.reset(); + unsigned lvl = m_scopes.size(); + SASSERT(num_scopes <= lvl); + unsigned new_lvl = lvl - num_scopes; + scope & s = m_scopes[new_lvl]; + restore_bounds(s.m_bound_trail_lim); + restore_unassigned_atoms(s.m_unassigned_atoms_trail_lim); + m_asserted_bounds.shrink(s.m_asserted_bounds_lim); + m_asserted_qhead = s.m_asserted_qhead_old; + TRACE("arith_pop_scope_bug", tout << "num_vars: " << get_num_vars() << ", num_old_vars: " << get_old_num_vars(num_scopes) << "\n";); + restore_nl_propagated_flag(s.m_nl_propagated_lim); + m_nl_monomials.shrink(s.m_nl_monomials_lim); + del_atoms(s.m_atoms_lim); + del_bounds(s.m_bounds_to_delete_lim); + del_vars(get_old_num_vars(num_scopes)); + m_scopes.shrink(new_lvl); + theory::pop_scope_eh(num_scopes); + bool r = make_feasible(); + SASSERT(r); + SASSERT(m_to_patch.empty()); + m_to_check.reset(); + m_in_to_check.reset(); + CASSERT("arith", wf_rows()); + CASSERT("arith", wf_columns()); + CASSERT("arith", valid_row_assignment()); + CASSERT("arith", satisfy_bounds()); + } + + template + void theory_arith::restore_nl_propagated_flag(unsigned old_trail_size) { + typename svector::iterator begin = m_nl_propagated.begin() + old_trail_size; + typename svector::iterator it = m_nl_propagated.end(); + while (it != begin) { + --it; + SASSERT(m_data[*it].m_nl_propagated); + m_data[*it].m_nl_propagated = false; + } + m_nl_propagated.shrink(old_trail_size); + } + + template + void theory_arith::restore_bounds(unsigned old_trail_size) { + CASSERT("arith", wf_rows()); + typename svector::iterator begin = m_bound_trail.begin() + old_trail_size; + typename svector::iterator it = m_bound_trail.end(); + while (it != begin) { + --it; + theory_var v = it->get_var(); + bound * b = it->get_old_bound(); + SASSERT(is_base(v) || is_non_base(v)); + restore_bound(v, b, it->is_upper()); + if (lazy_pivoting_lvl() > 2 && b == 0 && is_base(v) && is_free(v)) { + SASSERT(!has_var_kind(get_var_row(v), BASE)); + SASSERT(!has_var_kind(get_var_row(v), QUASI_BASE)); + eliminate(v, false); + SASSERT(m_columns[v].size() == 1); + SASSERT(!has_var_kind(get_var_row(v), BASE)); + SASSERT(!has_var_kind(get_var_row(v), QUASI_BASE)); + set_var_kind(v, QUASI_BASE); + } + } + m_bound_trail.shrink(old_trail_size); + CASSERT("arith", wf_rows()); + } + + template + void theory_arith::restore_unassigned_atoms(unsigned old_trail_size) { + svector::iterator begin = m_unassigned_atoms_trail.begin() + old_trail_size; + svector::iterator it = m_unassigned_atoms_trail.end(); + while (it != begin) { + --it; + m_unassigned_atoms[*it]++; + } + + m_unassigned_atoms_trail.shrink(old_trail_size); + } + + template + void theory_arith::del_atoms(unsigned old_size) { + typename atoms::iterator begin = m_atoms.begin() + old_size; + typename atoms::iterator it = m_atoms.end(); + while (it != begin) { + --it; + atom * a = *it; + theory_var v = a->get_var(); + bool_var bv = a->get_bool_var(); + erase_bv2a(bv); + SASSERT(m_var_occs[v].back() == a); + m_var_occs[v].pop_back(); + dealloc(a); + } + m_atoms.shrink(old_size); + } + + template + void theory_arith::del_bounds(unsigned old_size) { + typename ptr_vector::iterator begin = m_bounds_to_delete.begin() + old_size; + typename ptr_vector::iterator it = m_bounds_to_delete.end(); + while (it != begin) { + --it; + bound * b = *it; + dealloc(b); + } + m_bounds_to_delete.shrink(old_size); + } + + template + void theory_arith::del_vars(unsigned old_num_vars) { + int num_vars = get_num_vars(); + SASSERT(num_vars >= static_cast(old_num_vars)); + if (num_vars != static_cast(old_num_vars)) { + theory_var v = num_vars; + while (v > static_cast(old_num_vars)) { + --v; + switch (get_var_kind(v)) { + case QUASI_BASE: + SASSERT(m_columns[v].size() == 1); + del_row(get_var_row(v)); + break; + case BASE: + SASSERT(lazy_pivoting_lvl() != 0 || m_columns[v].size() == 1); + if (lazy_pivoting_lvl() > 0) + eliminate(v, false); + del_row(get_var_row(v)); + break; + case NON_BASE: { + col_entry const * entry = get_a_base_row_that_contains(v); + if (entry) { + row & r = m_rows[entry->m_row_id]; + SASSERT(is_base(r.get_base_var())); + SASSERT(r[entry->m_row_idx].m_var == v); + pivot(r.get_base_var(), v, r[entry->m_row_idx].m_coeff, false); + SASSERT(is_base(v)); + del_row(get_var_row(v)); + } + break; + } } + m_in_update_trail_stack.remove(v); + m_left_basis.remove(v); + m_in_to_check.remove(v); + } + m_columns .shrink(old_num_vars); + m_data .shrink(old_num_vars); + m_value .shrink(old_num_vars); + m_old_value .shrink(old_num_vars); + m_var_occs .shrink(old_num_vars); + m_unassigned_atoms.shrink(old_num_vars); + m_var_pos .shrink(old_num_vars); + m_bounds[0] .shrink(old_num_vars); + m_bounds[1] .shrink(old_num_vars); + SASSERT(check_vector_sizes()); + } + } + + template + void theory_arith::del_row(unsigned r_id) { + row & r = m_rows[r_id]; + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) { + theory_var v = it->m_var; + column & c = m_columns[v]; + c.del_col_entry(it->m_col_idx); + } + } + r.m_base_var = null_theory_var; + r.reset(); + m_dead_rows.push_back(r_id); + } + + /** + \brief reset and retrieve built-in explanation hints for arithmetic lemmmas. + */ + + template + typename theory_arith::antecedents& theory_arith::get_antecedents() { + m_tmp_antecedents.reset(); + return m_tmp_antecedents; + } + +}; + +#endif /* _THEORY_ARITH_CORE_H_ */ + diff --git a/lib/theory_arith_def.h b/lib/theory_arith_def.h new file mode 100644 index 000000000..b4d099f2c --- /dev/null +++ b/lib/theory_arith_def.h @@ -0,0 +1,32 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_arith_def.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-04-22. + +Revision History: + +--*/ +#ifndef _THEORY_ARITH_DEF_H_ +#define _THEORY_ARITH_DEF_H_ + +#include"theory_arith.h" +#include"theory_arith_core.h" +#include"theory_arith_aux.h" +#include"theory_arith_inv.h" +#include"theory_arith_pp.h" +#include"theory_arith_int.h" +#include"theory_arith_eq.h" +#include"theory_arith_nl.h" + +#endif /* _THEORY_ARITH_DEF_H_ */ + diff --git a/lib/theory_arith_eq.h b/lib/theory_arith_eq.h new file mode 100644 index 000000000..e6130acfe --- /dev/null +++ b/lib/theory_arith_eq.h @@ -0,0 +1,351 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_arith_eq.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-22. + +Revision History: + +--*/ +#ifndef _THEORY_ARITH_EQ_H_ +#define _THEORY_ARITH_EQ_H_ + +// #define PROFILE_OFFSET_ROW + +#ifdef PROFILE_OFFSET_ROW +#include"stopwatch.h" +#undef max +#undef min +#endif + +namespace smt { + + /** + \brief This method is invoked when a variable was non fixed and become fixed. + */ + template + void theory_arith::fixed_var_eh(theory_var v) { + if (!propagate_eqs()) + return; + + SASSERT(is_fixed(v)); + // WARNINING: it is not safe to use get_value(v) here, since + // get_value(v) may not satisfy v bounds at this point. + CTRACE("arith_bug", !lower_bound(v).is_rational(), display_var(tout, v);); + SASSERT(lower_bound(v).is_rational()); + numeral const & val = lower_bound(v).get_rational(); + value_sort_pair key(val, is_int(v)); + theory_var v2; + if (m_fixed_var_table.find(key, v2)) { + if (v2 < static_cast(get_num_vars()) && is_fixed(v2) && lower_bound(v2).get_rational() == val) { + // It only makes sense to propagate equality to the core when v and v2 have the same sort. + // The table m_fixed_var_table is not restored during backtrack. So, it may + // contain invalid (key -> value) pairs. So, we must check whether v2 is really equal to val (previous test) AND it has + // the same sort of v. The following test was missing in a previous version of Z3. + if (!is_equal(v, v2) && is_int(v) == is_int(v2)) { + antecedents& ante = get_antecedents(); + + // + // v <= k <= v2 => v <= v2 + // v >= k >= v2 => v >= v2 + // + + lower(v)->push_justification(ante, numeral::zero()); + upper(v2)->push_justification(ante, numeral::zero()); + lower(v2)->push_justification(ante, numeral::zero()); + upper(v)->push_justification(ante, numeral::zero()); + + TRACE("arith_fixed_propagate_eq", tout << "propagate eq: v" << v << " = v" << v2 << "\n"; + display_var(tout, v); + display_var(tout, v2);); + m_stats.m_fixed_eqs++; + propagate_eq_to_core(v, v2, ante); + } + } + else { + // the original fixed variable v2 was deleted or its bounds were removed + // during backtracking. + m_fixed_var_table.erase(key); + m_fixed_var_table.insert(key, v); + } + } + else { + m_fixed_var_table.insert(key, v); + } + } + + /** + \brief Returns true if r is a offset row. + A offset row is a row that can be written as: + + x = y + M + + where x and y are non fixed variables, and + M is linear polynomials where all variables are fixed, + and M evaluates to k. + When true is returned, x, y and k are stored in the given arguments. + + \remark The following rule is used to select x and y. + - if the base variable is not fixed, then x is the base var. + - otherwise x is the smallest var. + */ + template + bool theory_arith::is_offset_row(row const & r, theory_var & x, theory_var & y, numeral & k) const { +#ifdef PROFILE_OFFSET_ROW + static stopwatch timer; + static unsigned total = 0; + static unsigned ok = 0; + timer.start(); + total ++; +#endif + + // Quick check without using big numbers... + // Check if there are more than 2 unbounded vars. + unsigned bad = 0; + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) { + theory_var v = it->m_var; + if (lower(v) != 0 && upper(v) != 0) + continue; + bad++; + if (bad > 2) { +#ifdef PROFILE_OFFSET_ROW + timer.stop(); +#endif + return false; + } + } + } + + // Full check using == for big numbers... + x = null_theory_var; + y = null_theory_var; + it = r.begin_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) { + theory_var v = it->m_var; + if (is_fixed(v)) + continue; + if (it->m_coeff.is_one() && x == null_theory_var) { + x = v; + continue; + } + if (it->m_coeff.is_minus_one() && y == null_theory_var) { + y = v; + continue; + } +#ifdef PROFILE_OFFSET_ROW + timer.stop(); +#endif + return false; + } + } + + if (x == null_theory_var && y == null_theory_var) { +#ifdef PROFILE_OFFSET_ROW + timer.stop(); +#endif + return false; + } + + k.reset(); + it = r.begin_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) { + theory_var v = it->m_var; + if (v == x || v == y) + continue; + SASSERT(is_fixed(v)); + k -= it->m_coeff * lower_bound(v).get_rational(); + } + } + +#ifdef PROFILE_OFFSET_ROW + timer.stop(); + ok++; + if (ok % 100000 == 0) { + TRACE("propagate_cheap_eq", + tout << total << " " << ok << " " + << static_cast(ok)/static_cast(total) + << " " << timer.get_seconds() << "\n"; + tout.flush();); + } +#endif + + if (y == null_theory_var) + return true; + + if (x == null_theory_var) { + std::swap(x, y); + k.neg(); + SASSERT(x != null_theory_var); + return true; + } + + if (!r.get_base_var() == x && x > y) { + std::swap(x, y); + k.neg(); + } + return true; + } + + + /** + \brief Cheap propagation of equalities x_i = x_j, when + x_i = y + k + x_j = y + k + + This equalities are detected by maintaining a map: + (y, k) -> row_id when a row is of the form x = y + k + + This methods checks whether the given row is an offset row (See is_offset_row), + and uses the map to find new equalities if that is the case. + */ + template + void theory_arith::propagate_cheap_eq(unsigned rid) { + if (!propagate_eqs()) + return; + TRACE("propagate_cheap_eq", tout << "checking if row " << rid << " can propagate equality.\n"; + display_row_info(tout, rid);); + row const & r = m_rows[rid]; + theory_var x; + theory_var y; + numeral k; + if (is_offset_row(r, x, y, k)) { + + if (y == null_theory_var) { + // x is an implied fixed var at k. + value_sort_pair key(k, is_int(x)); + theory_var x2; + if (m_fixed_var_table.find(key, x2) && + x2 < static_cast(get_num_vars()) && + is_fixed(x2) && + lower_bound(x2).get_rational() == k && + // We must check whether x2 is an integer. + // The table m_fixed_var_table is not restored during backtrack. So, it may + // contain invalid (key -> value) pairs. + // So, we must check whether x2 is really equal to k (previous test) + // AND has the same sort of x. + // The following test was missing in a previous version of Z3. + is_int(x) == is_int(x2) && + !is_equal(x, x2)) { + + antecedents& ante = get_antecedents(); + collect_fixed_var_justifications(r, ante); + + // + // x1 <= k1 x1 >= k1, x2 <= x1 + k2 x2 >= x1 + k2 + // + lower(x2)->push_justification(ante, numeral::zero()); + upper(x2)->push_justification(ante, numeral::zero()); + m_stats.m_fixed_eqs++; + propagate_eq_to_core(x, x2, ante); + } + } + + if (k.is_zero() && y != null_theory_var && !is_equal(x, y) && is_int(x) == is_int(y)) { + // found equality x = y + antecedents& ante = get_antecedents(); + collect_fixed_var_justifications(r, ante); + TRACE("propagate_cheap_eq", tout << "propagate eq using x-y=0 row:\n"; display_row_info(tout, r);); + m_stats.m_offset_eqs++; + propagate_eq_to_core(x, y, ante); + } + + int row_id; + var_offset key(y, k); + if (m_var_offset2row_id.find(key, row_id)) { + row & r2 = m_rows[row_id]; + if (r.get_base_var() == r2.get_base_var()) { + // it is the same row. + return; + } + theory_var x2; + theory_var y2; + numeral k2; + if (r2.get_base_var() != null_theory_var && is_offset_row(r2, x2, y2, k2)) { + bool new_eq = false; +#ifdef _TRACE + bool swapped = false; +#endif + if (y == y2 && k == k2) { + new_eq = true; + } + else if (y2 != null_theory_var) { +#ifdef _TRACE + swapped = true; +#endif + std::swap(x2, y2); + k2.neg(); + if (y == y2 && k == k2) { + new_eq = true; + } + } + + if (new_eq) { + if (!is_equal(x, x2) && is_int(x) == is_int(x2)) { + SASSERT(y == y2 && k == k2); + antecedents& ante = get_antecedents(); + collect_fixed_var_justifications(r, ante); + collect_fixed_var_justifications(r2, ante); + TRACE("propagate_cheap_eq", tout << "propagate eq two rows:\n"; + tout << "swapped: " << swapped << "\n"; + tout << "x : v" << x << "\n"; + tout << "x2 : v" << x2 << "\n"; + display_row_info(tout, r); + display_row_info(tout, r2);); + m_stats.m_offset_eqs++; + propagate_eq_to_core(x, x2, ante); + } + return; + } + } + // the original row was delete or it is not offset row anymore ===> remove it from table + m_var_offset2row_id.erase(key); + } + // add new entry + m_var_offset2row_id.insert(key, rid); + } + + } + + + template + void theory_arith::propagate_eq_to_core(theory_var x, theory_var y, antecedents& antecedents) { + // I doesn't make sense to propagate an equality (to the core) of variables of different sort. + SASSERT(is_int(x) == is_int(y)); + // Ignore equality if variables are already known to be equal. + if (is_equal(x, y)) + return; + context & ctx = get_context(); + region & r = ctx.get_region(); + enode * _x = get_enode(x); + enode * _y = get_enode(y); + justification * js = + ctx.mk_justification( + ext_theory_eq_propagation_justification( + get_id(), r, + antecedents.lits().size(), antecedents.lits().c_ptr(), + antecedents.eqs().size(), antecedents.eqs().c_ptr(), + _x, _y, + antecedents.num_params(), antecedents.params("eq-propagate"))); + TRACE("propagate_eq_to_core", tout << "detected equality: #" << _x->get_owner_id() << " = #" << _y->get_owner_id() << "\n"; + display_var(tout, x); + display_var(tout, y);); + ctx.assign_eq(_x, _y, eq_justification(js)); + } +}; + +#endif /* _THEORY_ARITH_EQ_H_ */ + diff --git a/lib/theory_arith_int.h b/lib/theory_arith_int.h new file mode 100644 index 000000000..2a3bff804 --- /dev/null +++ b/lib/theory_arith_int.h @@ -0,0 +1,1413 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_arith_int.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-17. + +Revision History: + +--*/ +#ifndef _THEORY_ARITH_INT_H_ +#define _THEORY_ARITH_INT_H_ + +#include"ast_ll_pp.h" +#include"arith_simplifier_plugin.h" +#include"well_sorted.h" +#include"euclidean_solver.h" +#include"numeral_buffer.h" +#include"ast_smt2_pp.h" + +namespace smt { + + // ----------------------------------- + // + // Integrality + // + // ----------------------------------- + + /** + \brief Move non base variables to one of its bounds. + If the variable does not have bounds, it is integer, but it is not assigned to an integer value, then the + variable is set to an integer value. + */ + template + void theory_arith::move_non_base_vars_to_bounds() { + theory_var num = get_num_vars(); + for (theory_var v = 0; v < num; v++) { + if (is_non_base(v)) { + bound * l = lower(v); + bound * u = upper(v); + const inf_numeral & val = get_value(v); + if (l != 0 && u != 0) { + if (val != l->get_value() && val != u->get_value()) + set_value(v, l->get_value()); + } + else if (l != 0) { + if (val != l->get_value()) + set_value(v, l->get_value()); + } + else if (u != 0) { + if (val != u->get_value()) + set_value(v, u->get_value()); + } + else { + if (is_int(v) && !val.is_int()) { + inf_numeral new_val(floor(val)); + set_value(v, new_val); + } + } + } + } + } + + /** + \brief Returns true if the there is an integer variable + that is not assigned to an integer value. + */ + template + bool theory_arith::has_infeasible_int_var() { + int num = get_num_vars(); + for (theory_var v = 0; v < num; v++) { + if (is_int(v) && !get_value(v).is_int()) + return true; + } + return false; + } + + /** + \brief Find an integer base var that is not assigned to an + integer value, but is bounded (i.e., it has lower and upper + bounds). Return null_var_id if all integer base variables are + assigned to integer values. + + If there are multiple variables satisfying the condition above, + then select the one with the tightest bound. + */ + template + theory_var theory_arith::find_bounded_infeasible_int_base_var() { + theory_var result = null_theory_var; + numeral range; + numeral new_range; + numeral small_range_thresold(1024); + unsigned n = 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(); + if (v == null_theory_var) + continue; + if (!is_base(v)) + continue; + if (!is_int(v)) + continue; + if (get_value(v).is_int()) + continue; + if (!is_bounded(v)) + continue; + numeral const & l = lower_bound(v).get_rational(); + numeral const & u = upper_bound(v).get_rational(); + new_range = u; + new_range -= l; + if (new_range > small_range_thresold) + continue; + if (result == null_theory_var) { + result = v; + range = new_range; + n = 1; + continue; + } + if (new_range < range) { + n = 1; + result = v; + range = new_range; + continue; + } + if (new_range == range) { + n++; + if (m_random() % n == 0) { + result = v; + range = new_range; + continue; + } + } + } + return result; + } + + /** + \brief Find an integer base var that is not assigned to an integer value. + Return null_var_id if all integer base variables are assigned to + integer values. + + \remark This method gives preference to bounded integer variables. + If all variables are unbounded, then it selects a random one. + */ + template + theory_var theory_arith::find_infeasible_int_base_var() { + theory_var v = find_bounded_infeasible_int_base_var(); + if (v != null_theory_var) { + TRACE("find_infeasible_int_base_var", display_var(tout, v);); + return v; + } + unsigned n = 0; + theory_var r = null_theory_var; + +#define SELECT_VAR(VAR) if (r == null_theory_var) { n = 1; r = VAR; } else { n++; SASSERT(n >= 2); if (m_random() % n == 0) r = VAR; } + + 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_base(v) && is_int(v) && !get_value(v).is_int()) { + SELECT_VAR(v); + } + } + if (r == null_theory_var) { + it = m_rows.begin(); + for (; it != end; ++it) { + theory_var v = it->get_base_var(); + if (v != null_theory_var && is_quasi_base(v) && is_int(v) && !get_value(v).is_int()) { + quasi_base_row2base_row(get_var_row(v)); + SELECT_VAR(v); + } + } + if (r == null_theory_var) + return null_theory_var; + } + CASSERT("arith", wf_rows()); + CASSERT("arith", wf_columns()); + return r; + } + + + /** + \brief Create "branch and bound" case-split. + */ + template + void theory_arith::branch_infeasible_int_var(theory_var v) { + SASSERT(is_int(v)); + SASSERT(!get_value(v).is_int()); + m_stats.m_branches++; + TRACE("arith_branching", tout << "branching v" << v << " = " << get_value(v) << "\n"; + display_var(tout, v);); + numeral k = ceil(get_value(v)); + rational _k = k.to_rational(); + expr * bound = m_util.mk_ge(get_enode(v)->get_owner(), m_util.mk_numeral(_k, true)); + TRACE("arith_branching", tout << mk_bounded_pp(bound, get_manager()) << "\n";); + context & ctx = get_context(); + ctx.internalize(bound, true); + ctx.mark_as_relevant(bound); + } + + + /** + \brief Create a "cut from proof" lemma. + + The set of rows where the base variable is tight are extracted. + These row equalities are checked for integer feasiability. + If they are not integer feasible, then an integer infeasible + equation, that is implied from the extracted equalities is extracted. + The extracted equality a*x = 0 is blocked by asserting the disjunction + (a*x > 0 \/ a*x < 0) + */ + template + bool theory_arith::branch_infeasible_int_equality() { + + typename vector::const_iterator it = m_rows.begin(); + typename vector::const_iterator end = m_rows.end(); + + vector > rows; + unsigned max_row = 1; // all rows should contain a constant in the last position. + u_map var2index; // map theory variables to positions in 'rows'. + u_map index2var; // map back positions in 'rows' to theory variables. + + for (; it != end; ++it) { + theory_var b = it->get_base_var(); + if (b == null_theory_var) { + TRACE("theory_arith_int", display_row(tout << "null: ", *it, true); ); + continue; + } + bool is_tight = false; + numeral const_coeff(0); + + bound* l = lower(b), *u = upper(b); + if (l != 0 && get_value(b) - inf_numeral(1) < l->get_value()) { + SASSERT(l->get_value() <= get_value(b)); + is_tight = true; + const_coeff = l->get_value().get_rational(); + } + else if (u != 0 && get_value(b) + inf_numeral(1) > u->get_value()) { + SASSERT(get_value(b) <= u->get_value()); + is_tight = true; + const_coeff = u->get_value().get_rational(); + } + if (!is_tight) { + TRACE("theory_arith_int", + display_row(tout << "!tight: ", *it, true); + display_var(tout, b); + ); + continue; + } + rows.push_back(vector(max_row)); + vector& row = rows.back(); + numeral denom(1); + unsigned index = 0; + + typename vector::const_iterator it_r = it->begin_entries(); + typename vector::const_iterator end_r = it->end_entries(); + + for (; it_r != end_r && is_tight; ++it_r) { + if (it_r->is_dead()) + continue; + theory_var x = it_r->m_var; + if (x == b) + continue; + numeral coeff = it_r->m_coeff; + if (is_fixed(x)) { + const_coeff += coeff*lower(x)->get_value().get_rational(); + continue; + } + if (!is_int(x)) { + TRACE("theory_arith_int", display_row(tout << "!int: ", *it, true); ); + is_tight = false; + continue; + } + if (var2index.find(x, index)) { + row[index] = coeff.to_rational(); + } + else { + row.push_back(coeff.to_rational()); + var2index.insert(x, max_row); + index2var.insert(max_row, x); + ++max_row; + } + numeral tmp_coeff = denominator(coeff); + denom = lcm(tmp_coeff, denom); + } + if (!is_tight) { + rows.pop_back(); + continue; + } + row[0] = const_coeff.to_rational(); + numeral tmp_const_coeff = denominator(const_coeff); + denom = lcm(tmp_const_coeff, denom); + if (!denom.is_one()) { + for (unsigned i = 0; i < max_row; ++i) { + row[i] *= denom.to_rational(); + } + } + TRACE("theory_arith_int", + tout << "extracted row:\n"; + for (unsigned i = 0; i < max_row; ++i) { + tout << row[i] << " "; + } + tout << " = 0\n"; + tout << "base value: " << get_value(b) << "\n"; + display_row(tout, *it, true); + ); + } + // + // Align the sizes of rows. + // The sizes are monotonically increasing. + // + for (unsigned i = 0; i < rows.size(); ++i) { + unsigned sz = rows[i].size(); + SASSERT(sz <= max_row); + if (sz == max_row) { + break; + } + rows[i].resize(max_row); + } + vector unsat_row; + + if (m_arith_eq_solver.solve_integer_equations(rows, unsat_row)) { + // The equalities were integer feasiable. + return false; + } + + buffer pol; + for (unsigned i = 1; i < unsat_row.size(); ++i) { + numeral c(unsat_row[i]); + if (!c.is_zero()) { + theory_var var; + if (!index2var.find(i, var)) { + UNREACHABLE(); + } + pol.push_back(row_entry(c, var)); + } + } + if (pol.empty()) { + TRACE("theory_arith_int", tout << "The witness is trivial\n";); + return false; + } + expr_ref p1(get_manager()), p2(get_manager()); + + mk_polynomial_ge(pol.size(), pol.c_ptr(), -unsat_row[0]+rational(1), p1); + for (unsigned i = 0; i < pol.size(); ++i) { + pol[i].m_coeff.neg(); + } + mk_polynomial_ge(pol.size(), pol.c_ptr(), unsat_row[0]+rational(1), p2); + + context& ctx = get_context(); + ctx.internalize(p1, false); + ctx.internalize(p2, false); + literal l1(ctx.get_literal(p1)), l2(ctx.get_literal(p2)); + ctx.mark_as_relevant(p1.get()); + ctx.mark_as_relevant(p2.get()); + + ctx.mk_th_axiom(get_id(), l1, l2); + + TRACE("theory_arith_int", + tout << "cut: (or " << mk_pp(p1, get_manager()) << " " << mk_pp(p2, get_manager()) << ")\n"; + ); + + return true; + } + + + /** + \brief Create bounds for (non base) free vars in the given row. + Return true if at least one variable was constrained. + This method is used to enable the application of gomory cuts. + */ + template + bool theory_arith::constrain_free_vars(row const & r) { + bool result = false; + theory_var b = r.get_base_var(); + SASSERT(b != null_theory_var); + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead() && it->m_var != b && is_free(it->m_var)) { + theory_var v = it->m_var; + expr * bound = m_util.mk_ge(get_enode(v)->get_owner(), m_util.mk_numeral(rational::zero(), is_int(v))); + context & ctx = get_context(); + ctx.internalize(bound, true); + ctx.mark_as_relevant(bound); + result = true; + } + } + return result; + } + + /** + \brief Return true if it is possible to apply a gomory cut on the given row. + + \sa constrain_free_vars + */ + template + bool theory_arith::is_gomory_cut_target(row const & r) { + TRACE("gomory_cut", r.display(tout);); + theory_var b = r.get_base_var(); + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + // All non base variables must be at their bounds and assigned to rationals (that is, infinitesimals are not allowed). + if (!it->is_dead() && it->m_var != b && (!at_bound(it->m_var) || !get_value(it->m_var).is_rational())) { + TRACE("gomory_cut", tout << "row is gomory cut target:\n"; + display_var(tout, it->m_var); + tout << "at_bound: " << at_bound(it->m_var) << "\n"; + tout << "infinitesimal: " << get_value(it->m_var).is_rational() << "\n";); + return false; + } + } + return true; + } + + template + void theory_arith::mk_polynomial_ge(unsigned num_args, row_entry const * args, rational const& k, expr_ref & result) { + // Remark: the polynomials internalized by theory_arith may not satisfy poly_simplifier_plugin->wf_polynomial assertion. + bool all_int = true; + for (unsigned i = 0; i < num_args && all_int; ++i) { + all_int = is_int(args[i].m_var); + } + + ast_manager & m = get_manager(); + expr_ref_vector _args(m); + + for (unsigned i = 0; i < num_args; i++) { + rational _k = args[i].m_coeff.to_rational(); + expr * x = get_enode(args[i].m_var)->get_owner(); + if (m_util.is_int(x) && !all_int) + x = m_util.mk_to_real(x); + if (_k.is_one()) + _args.push_back(x); + else + _args.push_back(m_util.mk_mul(m_util.mk_numeral(_k, m_util.is_int(x)), x)); + } + + expr_ref pol(m); + pol = m_util.mk_add(_args.size(), _args.c_ptr()); + result = m_util.mk_ge(pol, m_util.mk_numeral(k, all_int)); + TRACE("arith_mk_polynomial", tout << "before simplification:\n" << mk_pp(pol, m) << "\n";); + simplifier & s = get_context().get_simplifier(); + proof_ref pr(m); + s(result, result, pr); + TRACE("arith_mk_polynomial", tout << "after simplification:\n" << mk_pp(pol, m) << "\n";); + SASSERT(is_well_sorted(get_manager(), result)); + } + + class gomory_cut_justification : public ext_theory_propagation_justification { + public: + gomory_cut_justification(family_id fid, region & r, + unsigned num_lits, literal const * lits, + unsigned num_eqs, enode_pair const * eqs, + literal consequent): + ext_theory_propagation_justification(fid, r, num_lits, lits, num_eqs, eqs, consequent) { + } + // Remark: the assignment must be propagated back to arith + virtual theory_id get_from_theory() const { return null_theory_id; } + }; + + /** + \brief Create a gomory cut for the given row. + */ + template + bool theory_arith::mk_gomory_cut(row const & r) { + SASSERT(!all_coeff_int(r)); + theory_var x_i = r.get_base_var(); + + SASSERT(is_int(x_i)); + // The following assertion is wrong. It may be violated in mixed-real-interger problems. + // The check is_gomory_cut_target will discard rows where any variable contains infinitesimals. + // SASSERT(m_value[x_i].is_rational()); // infinitesimals are not used for integer variables + SASSERT(!m_value[x_i].is_int()); // the base variable is not assigned to an integer value. + + if (constrain_free_vars(r) || !is_gomory_cut_target(r)) { + TRACE("gomory_cut", tout << "failed to apply gomory cut:\n"; + tout << "constrain_free_vars(r): " << constrain_free_vars(r) << "\n";); + return false; + } + + TRACE("gomory_cut", tout << "applying cut at:\n"; display_row_info(tout, r);); + + antecedents& ante = get_antecedents(); + + m_stats.m_gomory_cuts++; + + // gomory will be pol >= k + numeral k(1); + buffer pol; + + numeral f_0 = Ext::fractional_part(m_value[x_i]); + numeral one_minus_f_0 = numeral(1) - f_0; + + numeral lcm_den(1); + unsigned num_ints = 0; + + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead() && it->m_var != x_i) { + theory_var x_j = it->m_var; + numeral a_ij = it->m_coeff; + a_ij.neg(); // make the used format compatible with the format used in: Integrating Simplex with DPLL(T) + if (is_real(x_j)) { + numeral new_a_ij; + TRACE("gomory_cut_detail", tout << a_ij << "*v" << x_j << "\n";); + if (at_lower(x_j)) { + if (a_ij.is_pos()) { + new_a_ij = a_ij / one_minus_f_0; + } + else { + TRUSTME(!f_0.is_zero()); + new_a_ij = a_ij / f_0; + new_a_ij.neg(); + } + // k += new_a_ij * lower_bound(x_j).get_rational(); + k.addmul(new_a_ij, lower_bound(x_j).get_rational()); + lower(x_j)->push_justification(ante, numeral::zero()); + } + else { + SASSERT(at_upper(x_j)); + if (a_ij.is_pos()) { + TRUSTME(!f_0.is_zero()); + new_a_ij = a_ij / f_0; + new_a_ij.neg(); // the upper terms are inverted. + } + else { + // new_a_ij = - a_ij / one_minus_f_0 + // new_a_ij.neg() // the upper terms are inverted + new_a_ij = a_ij / one_minus_f_0; + } + // k += new_a_ij * upper_bound(x_j).get_rational(); + k.addmul(new_a_ij, upper_bound(x_j).get_rational()); + upper(x_j)->push_justification(ante, numeral::zero()); + } + pol.push_back(row_entry(new_a_ij, x_j)); + } + else { + ++num_ints; + SASSERT(is_int(x_j)); + numeral f_j = Ext::fractional_part(a_ij); + TRACE("gomory_cut_detail", + tout << a_ij << "*v" << x_j << "\n"; + tout << "fractional_part: " << Ext::fractional_part(a_ij) << "\n"; + tout << "f_j: " << f_j << "\n"; + tout << "f_0: " << f_0 << "\n"; + tout << "one_minus_f_0: " << one_minus_f_0 << "\n";); + if (!f_j.is_zero()) { + numeral new_a_ij; + if (at_lower(x_j)) { + if (f_j <= one_minus_f_0) { + new_a_ij = f_j / one_minus_f_0; + } + else { + new_a_ij = (numeral(1) - f_j) / f_0; + } + // k += new_a_ij * lower_bound(x_j).get_rational(); + k.addmul(new_a_ij, lower_bound(x_j).get_rational()); + lower(x_j)->push_justification(ante, numeral::zero()); + } + else { + SASSERT(at_upper(x_j)); + if (f_j <= f_0) { + new_a_ij = f_j / f_0; + } + else { + new_a_ij = (numeral(1) - f_j) / one_minus_f_0; + } + new_a_ij.neg(); // the upper terms are inverted + // k += new_a_ij * upper_bound(x_j).get_rational(); + k.addmul(new_a_ij, upper_bound(x_j).get_rational()); + upper(x_j)->push_justification(ante, numeral::zero()); + } + TRACE("gomory_cut_detail", tout << "new_a_ij: " << new_a_ij << "\n";); + pol.push_back(row_entry(new_a_ij, x_j)); + lcm_den = lcm(lcm_den, denominator(new_a_ij)); + } + } + } + } + + CTRACE("empty_pol", pol.empty(), display_row_info(tout, r);); + + expr_ref bound(get_manager()); + if (pol.empty()) { + SASSERT(k.is_pos()); + // conflict 0 >= k where k is positive + set_conflict(ante.lits().size(), ante.lits().c_ptr(), ante.eqs().size(), ante.eqs().c_ptr(), ante, true, "gomory_cut"); + return true; + } + else if (pol.size() == 1) { + theory_var v = pol[0].m_var; + k /= pol[0].m_coeff; + bool is_lower = pol[0].m_coeff.is_pos(); + if (is_int(v) && !k.is_int()) { + k = is_lower?ceil(k):floor(k); + } + rational _k = k.to_rational(); + if (is_lower) + bound = m_util.mk_ge(get_enode(v)->get_owner(), m_util.mk_numeral(_k, is_int(v))); + else + bound = m_util.mk_le(get_enode(v)->get_owner(), m_util.mk_numeral(_k, is_int(v))); + } + else { + if (num_ints > 0) { + lcm_den = lcm(lcm_den, denominator(k)); + TRACE("gomory_cut_detail", tout << "k: " << k << " lcm_den: " << lcm_den << "\n"; + for (unsigned i = 0; i < pol.size(); i++) { + tout << pol[i].m_coeff << " " << pol[i].m_var << "\n"; + } + tout << "k: " << k << "\n";); + SASSERT(lcm_den.is_pos()); + if (!lcm_den.is_one()) { + // normalize coefficients of integer parameters to be integers. + unsigned n = pol.size(); + for (unsigned i = 0; i < n; i++) { + pol[i].m_coeff *= lcm_den; + SASSERT(!is_int(pol[i].m_var) || pol[i].m_coeff.is_int()); + } + k *= lcm_den; + } + TRACE("gomory_cut_detail", tout << "after *lcm\n"; + for (unsigned i = 0; i < pol.size(); i++) { + tout << pol[i].m_coeff << " " << pol[i].m_var << "\n"; + } + tout << "k: " << k << "\n";); + } + mk_polynomial_ge(pol.size(), pol.c_ptr(), k.to_rational(), bound); + } + TRACE("gomory_cut", tout << "new cut:\n" << mk_pp(bound, get_manager()) << "\n";); + literal l = null_literal; + context & ctx = get_context(); + ctx.internalize(bound, true); + l = ctx.get_literal(bound); + ctx.mark_as_relevant(l); + ctx.assign(l, ctx.mk_justification( + gomory_cut_justification( + get_id(), ctx.get_region(), + ante.lits().size(), ante.lits().c_ptr(), + ante.eqs().size(), ante.eqs().c_ptr(), l))); + return true; + } + + /** + \brief Return false if the row failed the GCD test, that is, a conflict was detected. + + \remark if the variables with the least coefficient are bounded, + then the ext_gcd_test is invoked. + */ + template + bool theory_arith::gcd_test(row const & r) { + if (!m_params.m_arith_gcd_test) + return true; + m_stats.m_gcd_tests++; + numeral lcm_den = r.get_denominators_lcm(); + TRACE("gcd_test_bug", r.display(tout); tout << "lcm: " << lcm_den << "\n";); + numeral consts(0); + numeral gcds(0); + numeral least_coeff(0); + bool least_coeff_is_bounded = false; + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + 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 + // 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(); + } + else if (is_real(it->m_var)) { + return true; + } + else if (gcds.is_zero()) { + gcds = abs(lcm_den * it->m_coeff); + least_coeff = gcds; + least_coeff_is_bounded = is_bounded(it->m_var); + } + else { + numeral aux = abs(lcm_den * it->m_coeff); + gcds = gcd(gcds, aux); + if (aux < least_coeff) { + least_coeff = aux; + least_coeff_is_bounded = is_bounded(it->m_var); + } + else if (least_coeff_is_bounded && aux == least_coeff) { + least_coeff_is_bounded = is_bounded(it->m_var); + } + } + SASSERT(gcds.is_int()); + SASSERT(least_coeff.is_int()); + TRACE("gcd_test_bug", tout << "coeff: " << it->m_coeff << ", gcds: " << gcds + << " least_coeff: " << least_coeff << " consts: " << consts << "\n";); + } + } + + if (gcds.is_zero()) { + // All variables are fixed. + // This theory guarantees that the assignment satisfies each row, and + // fixed integer variables are assigned to integer values. + return true; + } + + if (!(consts / gcds).is_int()) { + TRACE("gcd_test", tout << "row failed the GCD test:\n"; display_row_info(tout, r);); + antecedents& ante = get_antecedents(); + collect_fixed_var_justifications(r, ante); + context & ctx = get_context(); + ctx.set_conflict( + ctx.mk_justification( + ext_theory_conflict_justification( + get_id(), ctx.get_region(), ante.lits().size(), ante.lits().c_ptr(), + ante.eqs().size(), ante.eqs().c_ptr(), + ante.num_params(), ante.params("gcd-test")))); + return false; + } + + if (least_coeff.is_one() && !least_coeff_is_bounded) { + SASSERT(gcds.is_one()); + return true; + } + + if (least_coeff_is_bounded) { + return ext_gcd_test(r, least_coeff, lcm_den, consts); + } + return true; + } + + /** + \brief Auxiliary method for gcd_test. + */ + template + bool theory_arith::ext_gcd_test(row const & r, numeral const & least_coeff, + numeral const & lcm_den, numeral const & consts) { + numeral gcds(0); + numeral l(consts); + numeral u(consts); + + antecedents& ante = get_antecedents(); + + + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead() && !is_fixed(it->m_var)) { + theory_var v = it->m_var; + SASSERT(!is_real(v)); + numeral ncoeff = lcm_den * it->m_coeff; + SASSERT(ncoeff.is_int()); + numeral abs_ncoeff = abs(ncoeff); + if (abs_ncoeff == least_coeff) { + SASSERT(is_bounded(v)); + if (ncoeff.is_pos()) { + // l += ncoeff * lower_bound(v).get_rational(); + l.addmul(ncoeff, lower_bound(v).get_rational()); + // u += ncoeff * upper_bound(v).get_rational(); + u.addmul(ncoeff, upper_bound(v).get_rational()); + } + else { + // l += ncoeff * upper_bound(v).get_rational(); + l.addmul(ncoeff, upper_bound(v).get_rational()); + // u += ncoeff * lower_bound(v).get_rational(); + u.addmul(ncoeff, lower_bound(v).get_rational()); + } + lower(v)->push_justification(ante, numeral::zero()); + upper(v)->push_justification(ante, numeral::zero()); + } + else if (gcds.is_zero()) { + gcds = abs_ncoeff; + } + else { + gcds = gcd(gcds, abs_ncoeff); + } + SASSERT(gcds.is_int()); + } + } + + if (gcds.is_zero()) { + return true; + } + + numeral l1 = ceil(l/gcds); + numeral u1 = floor(u/gcds); + + if (u1 < l1) { + TRACE("gcd_test", tout << "row failed the extended GCD test:\n"; display_row_info(tout, r);); + collect_fixed_var_justifications(r, ante); + context & ctx = get_context(); + ctx.set_conflict( + ctx.mk_justification( + ext_theory_conflict_justification( + get_id(), ctx.get_region(), + ante.lits().size(), ante.lits().c_ptr(), ante.eqs().size(), ante.eqs().c_ptr(), + ante.num_params(), ante.params("gcd-test")))); + return false; + } + + return true; + } + + /** + \brief Return true if all rows pass the GCD test. + */ + template + bool theory_arith::gcd_test() { + if (!m_params.m_arith_gcd_test) + 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)) { + if (m_params.m_arith_adaptive_gcd) + m_eager_gcd = true; + return false; + } + } + return true; + } + + /** + \brief Try to create bounds for unbounded infeasible integer variables. + Return false if an inconsistency is detected. + */ + template + bool theory_arith::max_min_infeasible_int_vars() { + var_set & already_processed = m_tmp_var_set; + already_processed.reset(); + svector vars; + 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(); + 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); + } + } + if (vars.empty()) + return true; + if (max_min(vars)) + return false; + } + } + + /** + \brief Try to patch int infeasible vars using freedom intervals. + */ + template + void theory_arith::patch_int_infeasible_vars() { + SASSERT(m_to_patch.empty()); + int num = get_num_vars(); + bool inf_l, inf_u; + inf_numeral l, u; + numeral m; + for (theory_var v = 0; v < num; v++) { + if (!is_non_base(v)) + continue; + get_freedom_interval(v, inf_l, l, inf_u, u, m); + if (m.is_one() && get_value(v).is_int()) + continue; + // check whether value of v is already a multiple of m. + if ((get_value(v).get_rational() / m).is_int()) + continue; + TRACE("patch_int", + tout << "TARGET v" << v << " -> ["; + if (inf_l) tout << "-oo"; else tout << ceil(l); + tout << ", "; + if (inf_u) tout << "oo"; else tout << floor(u); + tout << "]"; + tout << ", m: " << m << ", val: " << get_value(v) << ", is_int: " << is_int(v) << "\n";); + if (!inf_l) + l = ceil(l); + if (!inf_u) + u = floor(u); + if (!m.is_one()) { + if (!inf_l) + l = m*ceil(l/m); + if (!inf_u) + u = m*floor(u/m); + } + if (!inf_l && !inf_u && l > u) + continue; // cannot patch + if (!inf_l) + set_value(v, l); + else if (!inf_u) + set_value(v, u); + else + set_value(v, inf_numeral(0)); + } + SASSERT(m_to_patch.empty()); + } + + /** + \brief Force all non basic variables to be assigned to integer values. + */ + template + void theory_arith::fix_non_base_vars() { + int num = get_num_vars(); + for (theory_var v = 0; v < num; v++) { + if (!is_non_base(v)) + continue; + if (!is_int(v)) + continue; + if (get_value(v).is_int()) + continue; + inf_numeral new_val(floor(get_value(v))); + set_value(v, new_val); + } + if (!make_feasible()) + failed(); + } + + template + struct theory_arith::euclidean_solver_bridge { + typedef numeral_buffer mpz_buffer; + theory_arith & t; + euclidean_solver m_solver; + unsigned_vector m_tv2v; // theory var to euclidean solver var + svector m_j2v; // justification to theory var + + // aux fields + unsigned_vector m_xs; + mpz_buffer m_as; + unsigned_vector m_js; + + typedef euclidean_solver::var evar; + typedef euclidean_solver::justification ejustification; + euclidean_solver_bridge(theory_arith & _t):t(_t), m_solver(&t.m_es_num_manager), m_as(m_solver.m()) {} + + evar mk_var(theory_var v) { + m_tv2v.reserve(v+1, UINT_MAX); + if (m_tv2v[v] == UINT_MAX) + m_tv2v[v] = m_solver.mk_var(); + return m_tv2v[v]; + } + + /** + \brief Given a monomial, retrieve its coefficient and the power product + That is, mon = a * pp + */ + void get_monomial(expr * mon, rational & a, expr * & pp) { + expr * a_expr; + if (t.m_util.is_mul(mon, a_expr, pp) && t.m_util.is_numeral(a_expr, a)) + return; + a = rational(1); + pp = mon; + } + + /** + \brief Return the theory var associated with the given power product. + */ + theory_var get_theory_var(expr * pp) { + context & ctx = t.get_context(); + if (ctx.e_internalized(pp)) { + enode * e = ctx.get_enode(pp); + if (t.is_attached_to_var(e)) + return e->get_th_var(t.get_id()); + } + return null_theory_var; + } + + /** + \brief Create an euclidean_solver variable for the given + power product, if it has a theory variable associated with + it. + */ + evar mk_var(expr * pp) { + theory_var v = get_theory_var(pp); + if (v == null_theory_var) + return UINT_MAX; + return mk_var(v); + } + + /** + \brief Return the euclidean_solver variable associated with the given + power product. Return UINT_MAX, if it doesn't have one. + */ + evar get_var(expr * pp) { + theory_var v = get_theory_var(pp); + if (v == null_theory_var || v >= static_cast(m_tv2v.size())) + return UINT_MAX; + return m_tv2v[v]; + } + + void assert_eqs() { + // traverse definitions looking for equalities + mpz c, a; + mpz one; + euclidean_solver::numeral_manager & m = m_solver.m(); + m.set(one, 1); + mpz_buffer & as = m_as; + unsigned_vector & xs = m_xs; + int num = t.get_num_vars(); + for (theory_var v = 0; v < num; v++) { + if (!t.is_fixed(v)) + continue; + if (!t.is_int(v)) + continue; // only integer variables + expr * n = t.get_enode(v)->get_owner(); + if (t.m_util.is_numeral(n)) + continue; // skip stupid equality c - c = 0 + inf_numeral const & val = t.get_value(v); + rational num = val.get_rational().to_rational(); + SASSERT(num.is_int()); + num.neg(); + m.set(c, num.to_mpq().numerator()); + ejustification j = m_solver.mk_justification(); + m_j2v.reserve(j+1, null_theory_var); + m_j2v[j] = v; + as.reset(); + xs.reset(); + bool failed = false; + unsigned num_args; + expr * const * args; + if (t.m_util.is_add(n)) { + num_args = to_app(n)->get_num_args(); + args = to_app(n)->get_args(); + } + else { + num_args = 1; + args = &n; + } + for (unsigned j = 0; j < num_args; j++) { + expr * arg = args[j]; + expr * pp; + rational a_val; + get_monomial(arg, a_val, pp); + if (!a_val.is_int()) { + failed = true; + break; + } + evar x = mk_var(pp); + if (x == UINT_MAX) { + failed = true; + break; + } + m.set(a, a_val.to_mpq().numerator()); + as.push_back(a); + xs.push_back(x); + } + if (!failed) { + m_solver.assert_eq(as.size(), as.c_ptr(), xs.c_ptr(), c, j); + } + else { + TRACE("euclidean_solver", tout << "failed for:\n" << mk_ismt2_pp(n, t.get_manager()) << "\n";); + } + } + m.del(a); + m.del(c); + m.del(one); + } + + void mk_bound(theory_var v, rational k, bool lower, bound * old_bound, unsigned_vector const & js) { + derived_bound * new_bound = alloc(derived_bound, v, inf_numeral(k), lower ? B_LOWER : B_UPPER); + t.m_tmp_lit_set.reset(); + t.m_tmp_eq_set.reset(); + if (old_bound != 0) { + t.accumulate_justification(*old_bound, *new_bound, numeral(0) /* refine for proof gen */, t.m_tmp_lit_set, t.m_tmp_eq_set); + } + unsigned_vector::const_iterator it = js.begin(); + unsigned_vector::const_iterator end = js.end(); + for (; it != end; ++it) { + ejustification j = *it; + theory_var fixed_v = m_j2v[j]; + SASSERT(fixed_v != null_theory_var); + t.accumulate_justification(*(t.lower(fixed_v)), *new_bound, numeral(0) /* refine for proof gen */, t.m_tmp_lit_set, t.m_tmp_eq_set); + t.accumulate_justification(*(t.upper(fixed_v)), *new_bound, numeral(0) /* refine for proof gen */, t.m_tmp_lit_set, t.m_tmp_eq_set); + } + t.m_bounds_to_delete.push_back(new_bound); + t.m_asserted_bounds.push_back(new_bound); + } + + void mk_lower(theory_var v, rational k, bound * old_bound, unsigned_vector const & js) { + mk_bound(v, k, true, old_bound, js); + } + + void mk_upper(theory_var v, rational k, bound * old_bound, unsigned_vector const & js) { + mk_bound(v, k, false, old_bound, js); + } + + bool tight_bounds(theory_var v) { + SASSERT(!t.is_fixed(v)); + euclidean_solver::numeral_manager & m = m_solver.m(); + expr * n = t.get_enode(v)->get_owner(); + SASSERT(!t.m_util.is_numeral(n)); // should not be a numeral since v is not fixed. + bool propagated = false; + mpz a; + mpz c; + rational g; // gcd of the coefficients of the variables that are not in m_solver + rational c2; + bool init_g = false; + mpz_buffer & as = m_as; + unsigned_vector & xs = m_xs; + as.reset(); + xs.reset(); + + unsigned num_args; + expr * const * args; + if (t.m_util.is_add(n)) { + num_args = to_app(n)->get_num_args(); + args = to_app(n)->get_args(); + } + else { + num_args = 1; + args = &n; + } + for (unsigned j = 0; j < num_args; j++) { + expr * arg = args[j]; + expr * pp; + rational a_val; + get_monomial(arg, a_val, pp); + if (!a_val.is_int()) + goto cleanup; + evar x = get_var(pp); + if (x == UINT_MAX) { + a_val = abs(a_val); + if (init_g) + g = gcd(g, a_val); + else + g = a_val; + init_g = true; + if (g.is_one()) + goto cleanup; // gcd of the coeffs is one. + } + else { + m.set(a, a_val.to_mpq().numerator()); + as.push_back(a); + xs.push_back(x); + } + } + m_js.reset(); + m_solver.normalize(as.size(), as.c_ptr(), xs.c_ptr(), c, a, c, m_js); + if (init_g) { + if (!m.is_zero(a)) + g = gcd(g, rational(a)); + } + else { + g = rational(a); + } + TRACE("euclidean_solver", tout << "tightening " << mk_ismt2_pp(t.get_enode(v)->get_owner(), t.get_manager()) << "\n"; + tout << "g: " << g << ", a: " << m.to_string(a) << ", c: " << m.to_string(c) << "\n"; + t.display_var(tout, v);); + if (g.is_one()) + goto cleanup; + CTRACE("euclidean_solver_zero", g.is_zero(), tout << "tightening " << mk_ismt2_pp(t.get_enode(v)->get_owner(), t.get_manager()) << "\n"; + tout << "g: " << g << ", a: " << m.to_string(a) << ", c: " << m.to_string(c) << "\n"; + t.display(tout); + tout << "------------\nSolver state:\n"; + m_solver.display(tout);); + if (g.is_zero()) { + // The definition of v is equal to (0 + c). + // That is, v is fixed at c. + // The justification is just m_js, the existing bounds of v are not needed for justifying the new bounds for v. + c2 = rational(c); + TRACE("euclidean_solver_new", tout << "new fixed: " << c2 << "\n";); + propagated = true; + mk_lower(v, c2, 0, m_js); + mk_upper(v, c2, 0, m_js); + } + else { + TRACE("euclidean_solver", tout << "inequality can be tightned, since all coefficients are multiple of: " << g << "\n";); + // Let l and u be the current lower and upper bounds. + // Then, the following new bounds can be generated: + // + // l' := g*ceil((l - c)/g) + c + // u' := g*floor((l - c)/g) + c + bound * l = t.lower(v); + bound * u = t.upper(v); + c2 = rational(c); + if (l != 0) { + rational l_old = l->get_value().get_rational().to_rational(); + rational l_new = g*ceil((l_old - c2)/g) + c2; + TRACE("euclidean_solver_new", tout << "new lower: " << l_new << " old: " << l_old << "\n";); + if (l_new > l_old) { + propagated = true; + mk_lower(v, l_new, l, m_js); + } + } + if (u != 0) { + rational u_old = u->get_value().get_rational().to_rational(); + rational u_new = g*floor((u_old - c2)/g) + c2; + TRACE("euclidean_solver_new", tout << "new upper: " << u_new << " old: " << u_old << "\n";); + if (u_new < u_old) { + propagated = true; + mk_upper(v, u_new, u, m_js); + } + } + } + cleanup: + m.del(a); + m.del(c); + return propagated; + } + + bool tight_bounds() { + bool propagated = false; + context & ctx = t.get_context(); + // try to apply solution set to every definition + int num = t.get_num_vars(); + for (theory_var v = 0; v < num; v++) { + if (t.is_fixed(v)) + continue; // skip equations... + if (!t.is_int(v)) + continue; // skip non integer definitions... + if (t.lower(v) == 0 && t.upper(v) == 0) + continue; // there is nothing to be tightned + if (tight_bounds(v)) + propagated = true; + if (ctx.inconsistent()) + break; + } + return propagated; + } + + bool operator()() { + TRACE("euclidean_solver", t.display(tout);); + assert_eqs(); + m_solver.solve(); + if (m_solver.inconsistent()) { + // TODO: set conflict + TRACE("euclidean_solver_conflict", tout << "conflict detected...\n"; m_solver.display(tout);); + return false; + } + return tight_bounds(); + } + }; + + + template + bool theory_arith::apply_euclidean_solver() { + TRACE("euclidean_solver", tout << "executing euclidean solver...\n";); + euclidean_solver_bridge esb(*this); + if (esb()) { + propagate_core(); + return true; + } + return false; + } + + /** + \brief Return FC_DONE if the assignment is int feasible. Otherwise, apply GCD test, + branch and bound and Gomory Cuts. + */ + template + final_check_status theory_arith::check_int_feasibility() { + TRACE("arith_int_detail", get_context().display(tout);); + if (!has_infeasible_int_var()) { + TRACE("arith_int_incomp", tout << "FC_DONE 1...\n"; display(tout);); + return FC_DONE; + } + + TRACE("arith_int_fracs", + int num = get_num_vars(); + for (theory_var v = 0; v < num; v++) { + if (is_int(v) && !get_value(v).is_int()) { + numeral f1 = get_value(v).get_rational() - floor(get_value(v).get_rational()); + numeral f2 = ceil(get_value(v).get_rational()) - get_value(v).get_rational(); + if (f2 < f1) + f1 = f2; + tout << "v" << v << " -> " << f1 << " "; + display_var(tout, v); + } + }); + + TRACE("arith_int_fracs_min_max", + numeral max(0); + numeral min(1); + int num = get_num_vars(); + for (theory_var v = 0; v < num; v++) { + if (is_int(v) && !get_value(v).is_int()) { + numeral f1 = get_value(v).get_rational() - floor(get_value(v).get_rational()); + numeral f2 = ceil(get_value(v).get_rational()) - get_value(v).get_rational(); + if (f1 < min) min = f1; + if (f2 < min) min = f2; + if (f1 > max) max = f1; + if (f2 > max) max = f2; + } + } + tout << "max: " << max << ", min: " << min << "\n";); + + if (m_params.m_arith_ignore_int) + return FC_GIVEUP; + + if (!gcd_test()) + return FC_CONTINUE; + + if (m_params.m_arith_euclidean_solver) + apply_euclidean_solver(); + + if (get_context().inconsistent()) + return FC_CONTINUE; + + remove_fixed_vars_from_base(); + + TRACE("arith_int_freedom", + int num = get_num_vars(); + bool inf_l; bool inf_u; + inf_numeral l; inf_numeral u; + numeral m; + for (theory_var v = 0; v < num; v++) { + if (is_non_base(v)) { + get_freedom_interval(v, inf_l, l, inf_u, u, m); + if ((!m.is_one() /* && !l.is_zero() */) || !get_value(v).is_int()) { + tout << "TARGET v" << v << " -> ["; + if (inf_l) tout << "-oo"; else tout << ceil(l); + tout << ", "; + if (inf_u) tout << "oo"; else tout << floor(u); + tout << "]"; + tout << ", m: " << m << ", val: " << get_value(v) << ", is_int: " << is_int(v) << "\n"; + } + } + }); + + patch_int_infeasible_vars(); + fix_non_base_vars(); + + if (get_context().inconsistent()) + return FC_CONTINUE; + + TRACE("arith_int_inf", + int num = get_num_vars(); + for (theory_var v = 0; v < num; v++) { + if (is_int(v) && !get_value(v).is_int()) { + display_var(tout, v); + } + }); + + 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(); + if (v == null_theory_var) + continue; + if (is_int(v) && !get_value(v).is_int()) { + num++; + display_simplified_row(tout, *it); + tout << "\n"; + } + } + tout << "num infeasible: " << num << "\n";); + + theory_var int_var = find_infeasible_int_base_var(); + if (int_var == null_theory_var) { + TRACE("arith_int_incomp", tout << "FC_DONE 2...\n"; display(tout);); + return m_liberal_final_check || !m_changed_assignment ? FC_DONE : FC_CONTINUE; + } + +#if 0 + if (find_bounded_infeasible_int_base_var() == null_theory_var) { + // TODO: this is too expensive... I should replace it by a procedure + // that refine bounds using the current state of the tableau. + if (!max_min_infeasible_int_vars()) + return FC_CONTINUE; + if (!gcd_test()) + return FC_CONTINUE; + } +#endif + + m_branch_cut_counter++; + // TODO: add giveup code + if (m_branch_cut_counter % m_params.m_arith_branch_cut_ratio == 0) { + move_non_base_vars_to_bounds(); + if (!make_feasible()) { + TRACE("arith_int", tout << "failed to move variables to bounds.\n";); + failed(); + return FC_CONTINUE; + } + theory_var int_var = find_infeasible_int_base_var(); + if (int_var != null_theory_var) { + TRACE("arith_int", tout << "v" << int_var << " does not have an integer assignment: " << get_value(int_var) << "\n";); + SASSERT(is_base(int_var)); + row const & r = m_rows[get_var_row(int_var)]; + mk_gomory_cut(r); + return FC_CONTINUE; + } + } + else { + if (m_params.m_arith_int_eq_branching && branch_infeasible_int_equality()) { + return FC_CONTINUE; + } + theory_var int_var = find_infeasible_int_base_var(); + if (int_var != null_theory_var) { + TRACE("arith_int", tout << "v" << int_var << " does not have and integer assignment: " << get_value(int_var) << "\n";); + // apply branching + branch_infeasible_int_var(int_var); + return FC_CONTINUE; + } + } + return m_liberal_final_check || !m_changed_assignment ? FC_DONE : FC_CONTINUE; + } + +}; + +#endif /* _THEORY_ARITH_INT_H_ */ + diff --git a/lib/theory_arith_inv.h b/lib/theory_arith_inv.h new file mode 100644 index 000000000..80bb9307f --- /dev/null +++ b/lib/theory_arith_inv.h @@ -0,0 +1,207 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_arith_inv.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-05-02. + +Revision History: + +--*/ +#ifndef _THEORY_ARITH_INV_H_ +#define _THEORY_ARITH_INV_H_ + +#include"theory_arith.h" +#include"ast_pp.h" + +namespace smt { + +#ifdef Z3DEBUG + template + bool theory_arith::check_vector_sizes() const { + SASSERT(m_columns.size() == m_data.size()); + SASSERT(m_value.size() == m_data.size()); + SASSERT(m_old_value.size() == m_data.size()); + SASSERT(m_unassigned_atoms.size() == m_data.size()); + SASSERT(m_var_pos.size() == m_data.size()); + SASSERT(m_bounds[0].size() == m_data.size()); + SASSERT(m_bounds[1].size() == m_data.size()); + return true; + } + + /** + \brief Check whether all entries of m_var_pos are -1 + */ + template + bool theory_arith::check_null_var_pos() const { + svector::const_iterator it = m_var_pos.begin(); + svector::const_iterator end = m_var_pos.end(); + for (; it != end; ++it) { + SASSERT(*it == -1); + } + return true; + } + + /** + \brief Return true if the given row has a variable different from r.m_base_var + that has the given kind. + */ + template + bool theory_arith::has_var_kind(unsigned r_id, var_kind k) const { + row const & r = m_rows[r_id]; + theory_var base = r.m_base_var; + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead() && get_var_kind(it->m_var) == k && it->m_var != base) + return true; + } + return false; + } + + /** + \brief Return true if the given row is well formed. + */ + template + bool theory_arith::wf_row(unsigned r_id) const { + buffer already_found; + already_found.resize(get_num_vars(), false); + row const & r = m_rows[r_id]; + if (r.m_base_var != null_theory_var) { + int i = 0; + theory_var s = r.m_base_var; + SASSERT(is_base(s) || is_quasi_base(s)); + CTRACE("arith_bug", !(!is_base(s) || (!has_var_kind(r_id, BASE) && !has_var_kind(r_id, QUASI_BASE))), + display_row_info(tout, r_id);); + SASSERT(!is_base(s) || (!has_var_kind(r_id, BASE) && !has_var_kind(r_id, QUASI_BASE))); + CTRACE("arith_bug", is_quasi_base(s) && has_var_kind(r_id, QUASI_BASE), display_row_info(tout, r_id);); + SASSERT(!is_quasi_base(s) || !has_var_kind(r_id, QUASI_BASE)); + SASSERT(r.is_coeff_of(s, numeral::one())); + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it, ++i) { + if (!it->is_dead()) { + CTRACE("row_bug", already_found[it->m_var], display_row_info(tout, r_id);); + SASSERT(!already_found[it->m_var]); + already_found[it->m_var] = true; + column const & c = m_columns[it->m_var]; + CTRACE("row_bug", it->m_coeff.is_zero(), display_row_info(tout, r_id);); + SASSERT(!it->m_coeff.is_zero()); + SASSERT(c[it->m_col_idx].m_row_id == static_cast(r_id)); + SASSERT(c[it->m_col_idx].m_row_idx == i); + } + } + } + return true; + } + + /** + \brief Return true if all rows are well formed. + */ + template + bool theory_arith::wf_rows() const { + unsigned num = m_rows.size(); + for (unsigned r_id = 0; r_id < num; r_id++) { + SASSERT(wf_row(r_id)); + if (m_rows[r_id].m_base_var == null_theory_var) { + SASSERT(std::find(m_dead_rows.begin(), m_dead_rows.end(), r_id) != m_dead_rows.end()); + } + } + return true; + } + + /** + \brief Return true if the column associated with v is well formed. + */ + template + bool theory_arith::wf_column(theory_var v) const { + column const & c = m_columns[v]; + int i = 0; + typename svector::const_iterator it = c.begin_entries(); + typename svector::const_iterator end = c.end_entries(); + for (; it != end; ++it, ++i) { + if (!it->is_dead()) { + row const & r = m_rows[it->m_row_id]; + CTRACE("wf_column", r.size() == 0, tout << "v" << v << ", it->m_row_id: " << it->m_row_id << "\n"; display_row_info(tout, r); display(tout);); + SASSERT(r.size() != 0); + SASSERT(r[it->m_row_idx].m_var == v); + SASSERT(r[it->m_row_idx].m_col_idx == i); + } + } + SASSERT(!is_quasi_base(v) || c.size() == 1); + return true; + } + + /** + \brief Return true if all columns are well formed. + */ + template + bool theory_arith::wf_columns() const { + int num = get_num_vars(); + for (theory_var v = 0; v < num; v++) { + SASSERT(wf_column(v)); + } + return true; + } + + /** + \brief Return true if all rows evaluate to zero. + + \remark Quasi-base rows don't need to be checked. + */ + template + bool theory_arith::valid_row_assignment() const { + TRACE("valid_row_assignment", display(tout);); + typename vector::const_iterator it = m_rows.begin(); + typename vector::const_iterator end = m_rows.end(); + for (; it != end; ++it) { + if (it->get_base_var() != null_theory_var) { + SASSERT(valid_row_assignment(*it)); + } + } + return true; + } + + template + bool theory_arith::valid_row_assignment(row const & r) const { + theory_var s = r.get_base_var(); + SASSERT(is_base(s) || is_quasi_base(s)); + if (s != null_theory_var && is_base(s)) { + inf_numeral sum; + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) { + sum += it->m_coeff * m_value[it->m_var]; + } + } + CTRACE("valid_row_assignment_bug", !sum.is_zero(), tout << "checking: "; display_row_info(tout, r);); + SASSERT(sum.is_zero()); + } + return true; + } + + template + bool theory_arith::satisfy_bounds() const { + int num = get_num_vars(); + for (theory_var v = 0; v < num; v++) { + CTRACE("bound_bug", below_lower(v) || above_upper(v), display_var(tout, v); display(tout);); + SASSERT(!below_lower(v)); + SASSERT(!above_upper(v)); + } + return true; + } +#endif + +}; + +#endif /* _THEORY_ARITH_INV_H_ */ + diff --git a/lib/theory_arith_nl.h b/lib/theory_arith_nl.h new file mode 100644 index 000000000..bab921ed2 --- /dev/null +++ b/lib/theory_arith_nl.h @@ -0,0 +1,2406 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_arith_nl.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-12-08. + +Revision History: + +--*/ +#ifndef _THEORY_ARITH_NL_H_ +#define _THEORY_ARITH_NL_H_ + +#include"ast_smt2_pp.h" + +namespace smt { + + template + expr * theory_arith::mk_nary_mul(unsigned sz, expr * const * args, bool is_int) { + if (sz == 0) + return m_util.mk_numeral(rational(1), is_int); + if (sz == 1) + return args[0]; + if (sz == 2) + return m_util.mk_mul(args[0], args[1]); + if (m_util.is_numeral(args[0])) + return m_util.mk_mul(args[0], m_util.mk_mul(sz - 1, args + 1)); + return m_util.mk_mul(sz, args); + } + + template + expr * theory_arith::mk_nary_add(unsigned sz, expr * const * args, bool is_int) { + if (sz == 0) + return m_util.mk_numeral(rational(0), is_int); + if (sz == 1) + return args[0]; + return m_util.mk_add(sz, args); + } + + template + expr * theory_arith::mk_nary_add(unsigned sz, expr * const * args) { + SASSERT(sz != 0); + return mk_nary_add(sz, args, false); + } + + /** + \brief Insert v into vars and already_found if v is not already in already_found. + */ + template + void theory_arith::mark_var(theory_var v, svector & vars, var_set & already_found) { + if (already_found.contains(v)) + return; + TRACE("non_linear", tout << "marking: v" << v << "\n";); + already_found.insert(v); + vars.push_back(v); + } + + /** + \brief Invoke mark_var for all variables in rows that contain v. + */ + template + void theory_arith::mark_dependents(theory_var v, svector & vars, var_set & already_found, row_set & already_visited_rows) { + if (is_pure_monomial(v)) { + expr * n = var2expr(v); + SASSERT(m_util.is_mul(n)); + for (unsigned i = 0; i < to_app(n)->get_num_args(); i++) { + expr * curr = to_app(n)->get_arg(i); + theory_var v = expr2var(curr); + SASSERT(v != null_theory_var); + mark_var(v, vars, already_found); + } + } + if (is_fixed(v)) + return; + column & c = m_columns[v]; + typename svector::iterator it = c.begin_entries(); + typename svector::iterator end = c.end_entries(); + for (; it != end; ++it) { + if (it->is_dead() || already_visited_rows.contains(it->m_row_id)) + continue; + TRACE("non_linear_bug", tout << "visiting row: " << it->m_row_id << "\n";); + already_visited_rows.insert(it->m_row_id); + row & r = m_rows[it->m_row_id]; + theory_var s = r.get_base_var(); + // ignore quasi base vars... actually they should not be used if the problem is non linear... + if (is_quasi_base(s)) + continue; + // If s is a base variable different from v and it is free, then this row can be ignored. + // It doesn't need to be part of the non linear cluster. For all purposes, this variable + // was eliminated by substitution. + if (is_free(s) && s != v) + continue; + typename vector::const_iterator it2 = r.begin_entries(); + typename vector::const_iterator end2 = r.end_entries(); + for (; it2 != end2; ++it2) { + if (!it2->is_dead() && !is_fixed(it2->m_var)) + mark_var(it2->m_var, vars, already_found); + } + } + } + + /** + \brief Store in vars the variables that are in the non linear cluster of constraints, + and are not satisfied by the current assignment. + */ + template + void theory_arith::get_non_linear_cluster(svector & vars) { + if (m_nl_monomials.empty()) + return; + var_set already_found; + row_set already_visited_rows; + context & ctx = get_context(); + svector::const_iterator it = m_nl_monomials.begin(); + svector::const_iterator end = m_nl_monomials.end(); + for (; it != end; ++it) { + theory_var v = *it; + expr * n = var2expr(v); + if (ctx.is_relevant(n)) + mark_var(v, vars, already_found); + } + for (unsigned idx = 0; idx < vars.size(); idx++) { + TRACE("non_linear", tout << "marking dependents of: v" << vars[idx] << "\n";); + mark_dependents(vars[idx], vars, already_found, already_visited_rows); + } + TRACE("non_linear", tout << "variables in non linear cluster:\n"; + svector::const_iterator it = vars.begin(); + svector::const_iterator end = vars.end(); + for (; it != end; ++it) tout << "v" << *it << " "; + tout << "\n";); + } + + /** + \brief Return the number of variables that + do not have bounds associated with it. + The result is 0, 1, or 2. The value 2 means "2 or more". + The second value is the idx of the a variable that does not + have bounds associated with it. It is only usefull when the first value is 1. + The second value is -1 if such variable does not exist, that is, the first + value is 0. + + \remark if a variables has an even number of occurrences, then + I consider that it has a bound associated with it. + + Examples: + 1) Assume x1, x4 have bounds: + analyze_monomial(x1 * x2 * x2 * x3 * x3 * x3 * x4) + --> + (1,2) + Explanation: x2 doesn't have bounds, but x2 has an even power. + So x2*x2 has bound [0, oo). So, there is one variable without bounds x3. + It is the third variable in the monomial, then its idx is 2. + */ + template + std::pair theory_arith::analyze_monomial(expr * m) const { + SASSERT(is_pure_monomial(m)); + expr * var = 0; + unsigned power = 0; + unsigned c = 0; + int free_var_idx = -1; + int idx = 0; + for (unsigned i = 0; i < to_app(m)->get_num_args(); i++) { + expr * arg = to_app(m)->get_arg(i); + if (var == 0) { + var = arg; + power = 1; + } + else if (arg == var) { + power++; + } + else { + if (power % 2 == 1 && is_free(var)) { + c++; + free_var_idx = idx; + if (c > 1) + return std::make_pair(2, free_var_idx); + } + var = arg; + power = 1; + idx++; + } + } + if (power % 2 == 1 && is_free(var)) { + c++; + free_var_idx = idx; + } + return std::make_pair(c, free_var_idx); + } + + /** + \brief Given a monomial c*M, return M + */ + template + expr * theory_arith::get_monomial_body(expr * m) const { + SASSERT(m_util.is_mul(m)); + if (m_util.is_numeral(to_app(m)->get_arg(0))) + return to_app(m)->get_arg(1); + return m; + } + + /** + \brief Given a monomial c*M, return c + */ + template + rational theory_arith::get_monomial_coeff(expr * m) const { + SASSERT(m_util.is_mul(m)); + rational r; + if (m_util.is_numeral(to_app(m)->get_arg(0), r)) + return r; + return rational(1); + } + + /** + \brief Return the number of distinct variables in the given monomial. + */ + template + unsigned theory_arith::get_num_vars_in_monomial(expr * m) const { + SASSERT(m_util.is_mul(m)); + m = get_monomial_body(m); + SASSERT(!m_util.is_numeral(m)); + if (m_util.is_mul(m)) { + unsigned num_vars = 0; + expr * var = 0; + for (unsigned i = 0; i < to_app(m)->get_num_args(); i++) { + expr * curr = to_app(m)->get_arg(i); + if (var != curr) { + num_vars++; + var = curr; + } + } + return num_vars; + } + else { + return 1; + } + } + + /** + \brief Return the i-th var of m and its power. + */ + template + typename theory_arith::var_power_pair theory_arith::get_var_and_degree(expr * m, unsigned i) const { + SASSERT(m_util.is_mul(m)); + SASSERT(i < get_num_vars_in_monomial(m)); + m = get_monomial_body(m); + if (m_util.is_mul(m)) { + unsigned curr_idx = 0; + expr * var = 0; + unsigned power = 0; + unsigned j; + for (j = 0; j < to_app(m)->get_num_args(); j++) { + expr * arg = to_app(m)->get_arg(j); + if (var == 0) { + var = arg; + power = 1; + } + else if (var == arg) { + power++; + } + else { + if (curr_idx == i) + return var_power_pair(var, power); + curr_idx++; + var = arg; + power = 1; + } + } + SASSERT(curr_idx == i); + return var_power_pair(var, power); + } + else { + SASSERT(i == 0); + return var_power_pair(m, 1); + } + } + + /** + \brief Return an interval using the bounds for v. + */ + template + interval theory_arith::mk_interval_for(theory_var v) { + bound * l = lower(v); + bound * u = upper(v); + if (l && u) { + return interval(m_dep_manager, + l->get_value().get_rational().to_rational(), + !l->get_value().get_infinitesimal().to_rational().is_zero(), + m_dep_manager.mk_leaf(l), + u->get_value().get_rational().to_rational(), + !u->get_value().get_infinitesimal().to_rational().is_zero(), + m_dep_manager.mk_leaf(u)); + } + else if (l) { + return interval(m_dep_manager, + l->get_value().get_rational().to_rational(), + !l->get_value().get_infinitesimal().to_rational().is_zero(), + true, + m_dep_manager.mk_leaf(l)); + } + else if (u) { + return interval(m_dep_manager, + u->get_value().get_rational().to_rational(), + !u->get_value().get_infinitesimal().to_rational().is_zero(), + false, + m_dep_manager.mk_leaf(u)); + } + else { + return interval(m_dep_manager); + } + } + + /** + \brief Return an interval for the given expression using its bounds. + */ + template + interval theory_arith::mk_interval_for(expr * n) { + if (!has_var(n)) + return interval(m_dep_manager); + return mk_interval_for(expr2var(n)); + } + + /** + \brief target *= [lower(var), upper(var)]^power + */ + template + void theory_arith::mul_bound_of(expr * var, unsigned power, interval & target) { + theory_var v = expr2var(var); + interval i = mk_interval_for(v); + TRACE("non_linear", tout << "bound: " << i << "\n" << mk_pp(var, get_manager()) << "\n"; + tout << "power " << power << ": " << expt(i, power) << "\n"; + tout << "target before: " << target << "\n";); + i.expt(power); + target *= i; + TRACE("non_linear", tout << "target after: " << target << "\n";); + } + + /** + \brief Evaluate the given expression using interval arithmetic. + + - If a subexpression is internalized, then mk_interval_for is used to + compute its interval. + + - Only +, *, and numerals are handled. + */ + template + interval theory_arith::evaluate_as_interval(expr * n) { + TRACE("nl_evaluate", tout << "evaluating: " << mk_bounded_pp(n, get_manager(), 10) << "\n";); + if (has_var(n)) { + TRACE("nl_evaluate", tout << "n has a variable associated with it\n";); + TRACE("cross_nested_eval_bug", display_nested_form(tout, n); tout << "\ninterval: " << mk_interval_for(n) << "\n"; + display_var(tout, expr2var(n));); + return mk_interval_for(n); + } + else if (m_util.is_add(n)) { + TRACE("nl_evaluate", tout << "is add\n";); + interval r(m_dep_manager, rational(0)); + for (unsigned i = 0; i < to_app(n)->get_num_args(); i++) { + r += evaluate_as_interval(to_app(n)->get_arg(i)); + } + TRACE("cross_nested_eval_bug", display_nested_form(tout, n); tout << "\ninterval: " << r << "\n";); + return r; + } + else if (m_util.is_mul(n)) { + TRACE("nl_evaluate", tout << "is mul\n";); + interval r(m_dep_manager, get_monomial_coeff(n)); + unsigned num_vars = get_num_vars_in_monomial(n); + for (unsigned i = 0; i < num_vars; i++) { + var_power_pair p = get_var_and_degree(n, i); + expr * var = p.first; + unsigned power = p.second; + interval it = evaluate_as_interval(var); + it.expt(power); + r *= it; + } + TRACE("cross_nested_eval_bug", display_nested_form(tout, n); tout << "\ninterval: " << r << "\n";); + return r; + } + else { + rational val; + if (m_util.is_numeral(n, val)) { + TRACE("nl_evaluate", tout << "is numeral\n";); + return interval(m_dep_manager, val); + } + else { + TRACE("nl_evaluate", tout << "is unknown\n";); + return interval(m_dep_manager); + } + } + } + + template + void theory_arith::display_monomial(std::ostream & out, expr * m) const { + bool first = true; + unsigned num_vars = get_num_vars_in_monomial(m); + for (unsigned i = 0; i < num_vars; i++) { + var_power_pair p = get_var_and_degree(m, i); + SASSERT(p.first != 0); + if (first) first = false; else out << " * "; + out << mk_bounded_pp(p.first, get_manager()) << "^" << p.second; + } + } + + template + void theory_arith::dependency2new_bound(v_dependency * dep, derived_bound& new_bound) { + ptr_vector bounds; + m_dep_manager.linearize(dep, bounds); + m_tmp_lit_set.reset(); + m_tmp_eq_set.reset(); + ptr_vector::const_iterator it = bounds.begin(); + ptr_vector::const_iterator end = bounds.end(); + for (; it != end; ++it) { + bound * b = static_cast(*it); + accumulate_justification(*b, new_bound, numeral::zero(), m_tmp_lit_set, m_tmp_eq_set); + } + } + + /** + \brief Create a new derived bound. The justification is stored in the object dep. + */ + template + void theory_arith::mk_derived_nl_bound(theory_var v, inf_numeral const & coeff, bound_kind k, v_dependency * dep) { + inf_numeral coeff_norm = normalize_bound(v, coeff, k); + TRACE("buggy_bound", tout << "v" << v << " " << coeff << " " << coeff_norm << " " << k << "\n";); + derived_bound * new_bound = alloc(derived_bound, v, coeff_norm, k); + m_bounds_to_delete.push_back(new_bound); + m_asserted_bounds.push_back(new_bound); + // copy justification to new bound + dependency2new_bound(dep, *new_bound); + } + + /** + \brief Update the bounds of v, using the interval i. + Return true if i improves the bounds of v. + */ + template + bool theory_arith::update_bounds_using_interval(theory_var v, interval const & i) { + SASSERT(v != null_theory_var); + bool r = false; + if (!i.minus_infinity()) { + inf_numeral new_lower(i.get_lower_value()); + if (i.is_lower_open()) + new_lower += get_epsilon(v); + bound * old_lower = lower(v); + if (old_lower == 0 || new_lower > old_lower->get_value()) { + TRACE("non_linear", tout << "NEW lower bound for v" << v << " " << new_lower << "\n";); + mk_derived_nl_bound(v, new_lower, B_LOWER, i.get_lower_dependencies()); + r = true; + } + } + if (!i.plus_infinity()) { + inf_numeral new_upper(i.get_upper_value()); + if (i.is_upper_open()) + new_upper -= get_epsilon(v); + bound * old_upper = upper(v); + if (old_upper == 0 || new_upper < old_upper->get_value()) { + TRACE("non_linear", tout << "NEW upper bound for v" << v << " " << new_upper << "\n";); + mk_derived_nl_bound(v, new_upper, B_UPPER, i.get_upper_dependencies()); + r = true; + } + } + return r; + } + + template + bool theory_arith::update_bounds_using_interval(expr * n, interval const & i) { + SASSERT(expr2var(n) != null_theory_var); + TRACE("non_linear", tout << "NL bounds for m: " << i << "\n" << mk_pp(n, get_manager()) << "\n";); + return update_bounds_using_interval(expr2var(n), i); + } + + /** + \brief Use the bounds of the variables to build a bound for m. + */ + template + bool theory_arith::propagate_nl_upward(expr * m) { + SASSERT(is_pure_monomial(m)); + unsigned num_vars = get_num_vars_in_monomial(m); + interval new_bounds(m_dep_manager, rational(1)); + // TODO: the following code can be improved it is quadratic on the degree of the monomial. + TRACE("nl_arith_bug", tout << "processing upward:\n" << mk_pp(m, get_manager()) << "\n";); + for (unsigned i = 0; i < num_vars; i++) { + var_power_pair p = get_var_and_degree(m, i); + expr * var = p.first; + unsigned power = p.second; + TRACE("nl_arith_bug", tout << "interval before: " << new_bounds << "\n"; + theory_var v = expr2var(var); + interval i = mk_interval_for(v); + display_var(tout, v); + tout << "interval for var: " << i << "\n" << mk_pp(var, get_manager()) << "\npower: " << power << " " << expt(i, power) << "\n";); + mul_bound_of(var, power, new_bounds); + TRACE("nl_arith_bug", tout << "interval after: " << new_bounds << "\n";); + } + return update_bounds_using_interval(m, new_bounds); + } + + /** + \brief Propagate a bound to the i-th variable of the given monomial + using the bounds of m and other variables in m. + + \remark We do not support roots in interval... so, if the i-th var has power != 1 + the method returns without doing anything. + */ + template + bool theory_arith::propagate_nl_downward(expr * m, unsigned i) { + SASSERT(is_pure_monomial(m)); + SASSERT(i < get_num_vars_in_monomial(m)); + var_power_pair p = get_var_and_degree(m, i); + expr * v = p.first; + unsigned power = p.second; + TRACE("propagate_nl_downward", tout << "m: " << mk_ismt2_pp(m, get_manager()) << "\nv: " << mk_ismt2_pp(v, get_manager()) << + "\npower: " << power << "\n";); + if (power != 1) + return false; // TODO: remove, when the n-th root is implemented in interval. + unsigned num_vars = get_num_vars_in_monomial(m); + interval other_bounds(m_dep_manager, rational(1)); + // TODO: the following code can be improved it is quadratic on the degree of the monomial. + for (unsigned i = 0; i < num_vars; i++) { + var_power_pair p = get_var_and_degree(m, i); + if (p.first == v) + continue; + expr * var = p.first; + unsigned power = p.second; + mul_bound_of(var, power, other_bounds); + } + if (other_bounds.contains_zero()) + return false; // interval division requires that divisor doesn't contain 0. + interval r = mk_interval_for(m); + r /= other_bounds; + return update_bounds_using_interval(v, r); + } + + /** + \brief Try to propagate a bound using the given non linear + monomial. + Return true if some bound was propagated. + If i == -1, then use the bound of the variables to propagate a bound for + the monomial m. + If i != -1, then it is the index of the variable that I will compute bounds for. + */ + template + bool theory_arith::propagate_nl_bound(expr * m, int i) { + TRACE("propagate_nl_bound", tout << "propagate using i: " << i << "\n"; display_monomial(tout, m); tout << "\n";); + if (i == -1) + return propagate_nl_upward(m); + else + return propagate_nl_downward(m, i); + } + + /** + \brief The given monomial and its elements have bounds. + Propagate bounds to all of them. + Return true if some bound was propagated. + */ + template + bool theory_arith::propagate_nl_bounds(expr * m) { + TRACE("non_linear", tout << "propagate several bounds using:\n"; display_monomial(tout, m); tout << "\n";); + bool result = propagate_nl_upward(m); + unsigned num_vars = get_num_vars_in_monomial(m); + for (unsigned i = 0; i < num_vars; i++) + if (propagate_nl_downward(m, i)) { + m_stats.m_nl_bounds++; + result = true; + } + return result; + } + + /** + \brief Try to propagate bounds using non linear monomials. + Return true if some bound was propagated. + */ + template + bool theory_arith::propagate_nl_bounds() { + m_dep_manager.reset(); + bool propagated = false; + context & ctx = get_context(); + svector::const_iterator it = m_nl_monomials.begin(); + svector::const_iterator end = m_nl_monomials.end(); + for (; it != end; ++it) { + theory_var v = *it; + expr * m = var2expr(v); + if (!ctx.is_relevant(m)) + continue; + std::pair p = analyze_monomial(m); + TRACE("propagate_nl_bound", tout << "m: " << mk_ismt2_pp(m, get_manager()) << "\n" << "p: " << p.first << " " << p.second << "\n";); + unsigned num_bad_vars = p.first; + int free_var_idx = p.second; + SASSERT(num_bad_vars != 1 || free_var_idx != -1); + if (num_bad_vars >= 2) + continue; + bool is_free_m = is_free(m); + TRACE("propagate_nl_bound", tout << "is_free_m: " << is_free_m << "\n";); + if (num_bad_vars == 1 && is_free_m) + continue; + if (num_bad_vars == 0) { + if (!is_free_m) { + if (propagate_nl_bounds(m)) + propagated = true; + } + else { + if (propagate_nl_bound(m, -1)) { + m_stats.m_nl_bounds++; + propagated = true; + } + } + } + else { + SASSERT (!is_free_m); + if (propagate_nl_bound(m, free_var_idx)) { + m_stats.m_nl_bounds++; + propagated = true; + } + } + } + return propagated; + } + + /** + \brief Return the value of v as a rational. If computed_epsilon = false and v has an infinitesimal, then + compute_epsilon() is invoked. + */ + template + rational theory_arith::get_value(theory_var v, bool & computed_epsilon) { + inf_numeral const & val = get_value(v); + if (!val.get_infinitesimal().is_zero() && !computed_epsilon) { + compute_epsilon(); + computed_epsilon = true; + } + return val.get_rational().to_rational() + m_epsilon.to_rational() * val.get_infinitesimal().to_rational(); + } + + /** + \brief Return true if for the monomial x_1 * ... * x_n associated with v, + the following holds: + + get_value(x_1) * ... * get_value(x_n) = get_value(v) + */ + template + bool theory_arith::check_monomial_assignment(theory_var v, bool & computed_epsilon) { + SASSERT(is_pure_monomial(var2expr(v))); + expr * m = var2expr(v); + rational val(1); + for (unsigned i = 0; i < to_app(m)->get_num_args(); i++) { + expr * arg = to_app(m)->get_arg(i); + theory_var curr = expr2var(arg); + SASSERT(curr != null_theory_var); + val *= get_value(curr, computed_epsilon); + } + return get_value(v, computed_epsilon) == val; + } + + /** + \brief Return true if for every monomial x_1 * ... * x_n, + get_value(x_1) * ... * get_value(x_n) = get_value(x_1 * ... * x_n) + */ + template + bool theory_arith::check_monomial_assignments() { + bool computed_epsilon = false; + context & ctx = get_context(); + svector::const_iterator it = m_nl_monomials.begin(); + svector::const_iterator end = m_nl_monomials.end(); + for (; it != end; ++it) { + TRACE("non_linear", tout << "v" << *it << " is relevant: " << ctx.is_relevant(get_enode(*it)) << "\n"; + tout << "check_monomial_assignments result: " << check_monomial_assignment(*it, computed_epsilon) << "\n"; + tout << "computed_epsilon: " << computed_epsilon << "\n";); + if (ctx.is_relevant(get_enode(*it)) && !check_monomial_assignment(*it, computed_epsilon)) { + TRACE("non_linear_failed", tout << "check_monomial_assignment failed for:\n" << mk_ismt2_pp(var2expr(*it), get_manager()) << "\n"; + display_var(tout, *it);); + return false; + } + } + return true; + } + + /** + \brief Try to find an integer variable for performing branching + in the non linear cluster. + + The idea is select a variable in a monomial with an invalid + assignment. I give preference to variables with small ranges. + If no variable is bounded, then select a random one. + + Free variables are not considered. + */ + template + theory_var theory_arith::find_nl_var_for_branching() { + TRACE("nl_branching", tout << "looking for variable to branch...\n"; display(tout);); + context & ctx = get_context(); + theory_var target = null_theory_var; + bool bounded = false; + unsigned n = 0; + numeral range; + svector::const_iterator it = m_nl_monomials.begin(); + svector::const_iterator end = m_nl_monomials.end(); + for (; it != end; ++it) { + theory_var v = *it; + if (is_real(v)) + continue; + bool computed_epsilon = false; + bool r = check_monomial_assignment(v, computed_epsilon); + SASSERT(!computed_epsilon); // integer variables do not use epsilon + if (!r) { + expr * m = get_enode(v)->get_owner(); + SASSERT(is_pure_monomial(m)); + for (unsigned i = 0; i < to_app(m)->get_num_args(); i++) { + expr * arg = to_app(m)->get_arg(i); + theory_var curr = ctx.get_enode(arg)->get_th_var(get_id()); + TRACE("nl_branching", tout << "target: v" << target << ", curr: v" << curr << "\n";); + if (!is_fixed(curr) && is_int(curr)) { + if (is_bounded(curr)) { + numeral new_range; + new_range = upper_bound(curr).get_rational(); + new_range -= lower_bound(curr).get_rational(); + if (!bounded || new_range < range) { + target = curr; + range = new_range; + bounded = true; + } + } + else if (!bounded) { + n++; + TRACE("nl_branching", tout << "n: " << n << "\n";); + if (m_random()%n == 0) + target = curr; + SASSERT(target != null_theory_var); + } + SASSERT(target != null_theory_var); + } + TRACE("nl_branching", tout << "after target: v" << target << "\n";); + } + } + } + return target; + } + + /** + \brief Branch on an integer variable. This method is invoked when v is part + of a non linear monomial that is not satisfied by the current assignment. + if v >= l, then create the case split v >= l+1 + else v <= u, then create the case split v <= u-1 + else do nothing and return false. + */ + template + bool theory_arith::branch_nl_int_var(theory_var v) { + TRACE("non_linear", tout << "BRANCHING on v" << v << "\n";); + m_stats.m_nl_branching++; + SASSERT(is_int(v)); + expr * bound = 0; + if (lower(v)) + bound = m_util.mk_le(var2expr(v), m_util.mk_numeral(lower_bound(v).get_rational().to_rational(), true)); + else if (upper(v)) + bound = m_util.mk_ge(var2expr(v), m_util.mk_numeral(upper_bound(v).get_rational().to_rational(), true)); + else + bound = m_util.mk_eq(var2expr(v), m_util.mk_numeral(rational(0), true)); + TRACE("non_linear", tout << "new bound:\n" << mk_pp(bound, get_manager()) << "\n";); + context & ctx = get_context(); + ctx.internalize(bound, true); + ctx.mark_as_relevant(bound); + literal l = ctx.get_literal(bound); + SASSERT(!l.sign()); + ctx.set_true_first_flag(l.var()); // force the context to case split to true first, independently of the phase selection strategy. + return true; + } + + /** + \brief Return true if the given monomial is linear. + */ + template + bool theory_arith::is_monomial_linear(expr * m) const { + SASSERT(is_pure_monomial(m)); + unsigned num_nl_vars = 0; + for (unsigned i = 0; i < to_app(m)->get_num_args(); i++) { + expr * arg = to_app(m)->get_arg(i); + theory_var _var = expr2var(arg); + if (!is_fixed(_var)) { + num_nl_vars++; + } + else { + if (lower_bound(_var).is_zero()) + return true; + } + } + return num_nl_vars <= 1; + } + + /** + \brief Return the product of the value of the fixed variables in the + monomial m. + */ + template + typename theory_arith::numeral theory_arith::get_monomial_fixed_var_product(expr * m) const { + SASSERT(is_pure_monomial(m)); + numeral r(1); + for (unsigned i = 0; i < to_app(m)->get_num_args(); i++) { + expr * arg = to_app(m)->get_arg(i); + theory_var _var = expr2var(arg); + if (is_fixed(_var)) + r *= lower_bound(_var).get_rational(); + } + return r; + } + + /** + \brief Return the first non fixed variable in the given monomial. + Return 0, if the monomial does not have a non fixed variable. + */ + template + expr * theory_arith::get_monomial_non_fixed_var(expr * m) const { + SASSERT(is_pure_monomial(m)); + for (unsigned i = 0; i < to_app(m)->get_num_args(); i++) { + expr * arg = to_app(m)->get_arg(i); + theory_var _var = expr2var(arg); + if (!is_fixed(_var)) + return arg; + } + return 0; + } + + /** + \brief Propagate linear monomial. Check whether the give + monomial became linear and propagate. + */ + template + bool theory_arith::propagate_linear_monomial(theory_var v) { + TRACE("non_linear", tout << "checking whether v" << v << " became linear...\n";); + if (m_data[v].m_nl_propagated) + return false; // already propagated this monomial. + expr * m = var2expr(v); + if (!is_monomial_linear(m)) + return false; // monomial is not linear. + + m_stats.m_nl_linear++; + + m_data[v].m_nl_propagated = true; + m_nl_propagated.push_back(v); + TRACE("non_linear", tout << "v" << v << " is linear " << mk_pp(m, get_manager()) << "\n";); + + + numeral k = get_monomial_fixed_var_product(m); + TRACE("non_linear", tout << "new linear monomial... k: " << k << "\n";); + expr * x_n = k.is_zero() ? 0 : get_monomial_non_fixed_var(m); + TRACE("non_linear_bug", if (x_n != 0) { tout << "x_n: " << mk_bounded_pp(x_n, get_manager()) << "\nx_n: #" << x_n->get_id() << "\n"; }); + context & ctx = get_context(); + derived_bound * new_lower = 0; + derived_bound * new_upper = 0; + if (x_n != 0) { + // All but one of the x_i variables are assigned. + // Let x_n be the unassigned variable. + // Then, we know that x_1*...*x_n = k*x_n, where k is the product of beta(x_1)*...*beta(x_{n-1}) + // beta(x_i) == lower(x_i) + + // Let m be (* x_1 ... x_n), then assert equality + // (= (+ (* x_1 ... x_n) (* -k x_n)) 0) when x_1 ... x_{n-1} are fixed variables. + // where k = lower(x_1)*...*lower(x_{n-1}) + TRACE("non_linear", tout << "x_n: " << mk_pp(x_n, get_manager()) << "\n";); + k.neg(); + expr * k_x_n = k.is_one() ? x_n : m_util.mk_mul(m_util.mk_numeral(k.to_rational(), is_int(v)), x_n); + expr * rhs = m_util.mk_add(m, k_x_n); + TRACE("non_linear_bug", tout << "rhs: " << mk_bounded_pp(rhs, get_manager(),5) << "\ninternalized: " << ctx.e_internalized(rhs) << "\n";); + if (!has_var(rhs)) { + ctx.internalize(rhs, false); + ctx.mark_as_relevant(rhs); + } + TRACE("non_linear_bug", tout << "enode: " << get_context().get_enode(rhs) << " enode_id: " << get_context().get_enode(rhs)->get_owner_id() << "\n";); + theory_var new_v = expr2var(rhs); + TRACE("non_linear_bug", ctx.display(tout);); + SASSERT(new_v != null_theory_var); + new_lower = alloc(derived_bound, new_v, inf_numeral(0), B_LOWER); + new_upper = alloc(derived_bound, new_v, inf_numeral(0), B_UPPER); + } + else { + // One of the x_i variables is zero, + // or all of them are assigned. + + // Assert the equality + // (= (* x_1 ... x_n) k) + TRACE("non_linear", tout << "all variables are fixed.\n";); + new_lower = alloc(derived_bound, v, inf_numeral(k), B_LOWER); + new_upper = alloc(derived_bound, v, inf_numeral(k), B_UPPER); + } + SASSERT(new_lower != 0); + SASSERT(new_upper != 0); + m_bounds_to_delete.push_back(new_lower); + m_asserted_bounds.push_back(new_lower); + m_bounds_to_delete.push_back(new_upper); + m_asserted_bounds.push_back(new_upper); + + // Add the justification for new_lower and new_upper. + // The justification is the lower and upper bounds of all fixed variables. + m_tmp_lit_set.reset(); + m_tmp_eq_set.reset(); + + bool found_zero = false; + SASSERT(is_pure_monomial(m)); + for (unsigned i = 0; i < to_app(m)->get_num_args(); i++) { + expr * arg = to_app(m)->get_arg(i); + if (!found_zero) { + theory_var _var = expr2var(arg); + if (is_fixed(_var)) { + bound * l = lower(_var); + bound * u = upper(_var); + if (l->get_value().is_zero()) { + /* if zero was found, then it is the explanation */ + SASSERT(k.is_zero()); + found_zero = true; + m_tmp_lit_set.reset(); + m_tmp_eq_set.reset(); + new_lower->m_lits.reset(); + new_lower->m_eqs.reset(); + } + accumulate_justification(*l, *new_lower, numeral::zero(), m_tmp_lit_set, m_tmp_eq_set); + accumulate_justification(*u, *new_lower, numeral::zero(), m_tmp_lit_set, m_tmp_eq_set); + } + } + } + new_upper->m_lits.append(new_lower->m_lits); + new_upper->m_eqs.append(new_lower->m_eqs); + return true; + } + + /** + \brief Traverse all non linear monomials, and check the ones that became + linear and propagate. Return true if propagated. + */ + template + bool theory_arith::propagate_linear_monomials() { + TRACE("non_linear", tout << "propagating linear monomials...\n";); + bool p = false; + svector::const_iterator it = m_nl_monomials.begin(); + svector::const_iterator end = m_nl_monomials.end(); + for (; it != end; ++it) { + theory_var v = *it; + if (propagate_linear_monomial(v)) + p = true; + } + CTRACE("non_linear", p, display(tout);); + return p; + } + + /* + Interval arithmetic does not satisfy distributivity. + Actually, it satisfies the sub-distributivity property: + + x*(y + z) \subseteq x*y + x*z + + The sub-distributivity property only holds if condensation + is not used. For example: + + x * (x^3 + 1) \subseteq x*x^3 + x, + + but it is not the case that + + x * (x^3 + 1) \subseteq x^4 + x + + for example, for x = [-2,1] + + x*(x^3+1) = [-7, 14] + x^4 + x = [-2, 17] + + This weakness of AI is known as the "dependency problem", + which comes from the decorrelation of the multiple occurrences + of one variable during interval evaluation. + + Given a polynomial: + p(x) = a_0 + a_1 * x + ... + a_n * x^n + The horner extension is: + h_p(x) = a_0 + x*(a_1 + ... + x*(a_{n-1} + a_n * x) + ...) + + The horner extension of p(x) = x^4 + x^3 + 2*x is: + h_p(x) = x(2 + x^3(1 + x)) + + The horner extension evaluates tighter intervals when + condensation is not used. + + Remark: there is no guarantee that horner extension will + provide a tighter interval than a sum of monomials when + condensation is used. + + For multivariate polynomials nested (or cross nested) forms + are used. The idea is to select one variable, and pretend the + other are parameters. The horner form is computed for the selected + variable, and the computation continues for the polynomials on the + parameters. + + As described above, the horner form is not optimal with respect to + to condensation. I use the following two properties to deal with + monovariate polynomials with two monomials: + + p(x) = a*x^n + b*x^{n+m} for n >= m + is equivalent to + + b*x^{n-m}*[(x^{m} + a/(2b))^2 - (a/2b)^2] + + This polynomial provides tight bound when n and m have the same parity and: + 1) a*b > 0 and (lower(x) >= 0 or upper(x)^m <= -a/b) + 2) a*b < 0 and (upper(x) <= 0 or lower(x)^m >= a/b) + + This polynomial also provides tight bounds when n = m, + and the polynomial is simplified to, and n and m may have arbitrary parities: + + b*[(x^{n} + a/(2b))^2 - (a/2b)^2] + + Example: + x > 1 + x^2 - x <= 0 + is unsatisfiable + + If we compute the bounds for x^2 - x we obtain + (-oo, oo). + + On the other hand, if we compute the bounds for + (x - 1/2)^2 - 1/4 + we obtain the bounds (0, oo), and the inconsistency + is detected. + + Remark: In Z3, I condensate multiple occurrences of a variable + when evaluating monomials. So, the interval for a monomial is + always tight. + + Remark: M1*(M2 + M3) is more precise than M1 * M2 + M1 * M3, + if intersection(Vars(M1), union(Vars(M2), Vars(M3))) = empty-set, + + Remark: A trivial consequence of Moore's theorem for interval + arithmetic. If two monomials M1 and M2 do not share variables, + then the interval for M1 + M2 is tight. + */ + + /** + \brief Check whether the same variable occurs in two different monomials. + + \remark Fixed variables are ignored. + + \remark A trivial consequence of Moore's theorem for interval + arithmetic. If two monomials M1 and M2 do not share variables, + then the interval for M1 + M2 is tight. + */ + template + bool theory_arith::is_problematic_non_linear_row(row const & r) { + m_tmp_var_set.reset(); + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) { + theory_var v = it->m_var; + if (is_fixed(v)) + continue; + if (is_pure_monomial(v)) { + expr * m = var2expr(v); + for (unsigned i = 0; i < to_app(m)->get_num_args(); i++) { + theory_var curr = expr2var(to_app(m)->get_arg(i)); + if (m_tmp_var_set.contains(curr)) + return true; + } + SASSERT(m == var2expr(v)); + for (unsigned i = 0; i < to_app(m)->get_num_args(); i++) { + theory_var curr = expr2var(to_app(m)->get_arg(i)); + if (!is_fixed(curr)) + m_tmp_var_set.insert(curr); + } + } + else { + if (m_tmp_var_set.contains(v)) + return true; + SASSERT(!is_fixed(v)); + m_tmp_var_set.insert(v); + } + } + } + return false; + } + + /** + \brief Return true if the row mixes real and integer variables. + This kind of row cannot be converted back to an expression, since + expressions in Z3 cannot have mixed sorts. + */ + template + bool theory_arith::is_mixed_real_integer(row const & r) const { + bool found_int = false; + bool found_real = false; + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (it->is_dead()) + continue; + theory_var v = it->m_var; + // TODO: possible improvement... ignore fixed variables. + // If we implement this improvement, we are actually changing the contract of this function + // and we will also have to fix the affected functions. + if (is_int(v)) + found_int = true; + if (is_real(v)) + found_real = true; + if (found_int && found_real) + return true; + } + return false; + } + + /** + \brief Return true if the row contains only integer variables. + */ + template + bool theory_arith::is_integer(row const & r) const { + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (it->is_dead()) + continue; + theory_var v = it->m_var; + // TODO: possible improvement... ignore fixed variables. + if (!is_int(v)) + return false; + } + return true; + } + + template + void theory_arith::display_coeff_exprs(std::ostream & out, sbuffer const & p) const { + typename sbuffer::const_iterator it = p.begin(); + typename sbuffer::const_iterator end = p.end(); + for (bool first = true; it != end; ++it) { + if (first) + first = false; + else + out << "+\n"; + out << it->first << " * " << mk_pp(it->second, get_manager()) << "\n"; + } + } + + /** + \brief Traverse p and store in vars the (non-fixed) variables + that occur in more than one monomial. The number of + occurrences is also stored. + */ + template + void theory_arith::get_polynomial_info(sbuffer const & p, sbuffer & varinfo) { + context & ctx = get_context(); + varinfo.reset(); + m_var2num_occs.reset(); + +#define ADD_OCC(VAR) if (has_var(VAR) && !is_fixed(expr2var(VAR))) { \ + TRACE("nl_info", tout << "adding occ: " << mk_bounded_pp(VAR, get_manager()) << "\n";); \ + unsigned occs = 0; \ + m_var2num_occs.find(VAR, occs); \ + occs++; \ + m_var2num_occs.insert(VAR, occs); \ + } + + typename sbuffer::const_iterator it = p.begin(); + typename sbuffer::const_iterator end = p.end(); + for (; it != end; ++it) { + expr * m = it->second; + if (is_pure_monomial(m)) { + unsigned num_vars = get_num_vars_in_monomial(m); + for (unsigned i = 0; i < num_vars; i++) { + var_power_pair p = get_var_and_degree(m, i); + ADD_OCC(p.first); + } + } + else if (m_util.is_numeral(m)) { + continue; + } + else if (ctx.e_internalized(m)) { + ADD_OCC(m); + } + else { + TRACE("non_linear", tout << mk_pp(m, get_manager()) << "\n";); + UNREACHABLE(); + } + } + + // Update the number of occurrences in the result vector. + typename var2num_occs::iterator it2 = m_var2num_occs.begin(); + typename var2num_occs::iterator end2 = m_var2num_occs.end(); + for (; it2 != end2; ++it2) { + if ((*it2).m_value > 1) + varinfo.push_back(var_num_occs((*it2).m_key, (*it2).m_value)); + } + } + + /** + \brief Convert p into an expression. + */ + template + expr * theory_arith::p2expr(sbuffer & p) { + SASSERT(!p.empty()); + TRACE("p2expr_bug", display_coeff_exprs(tout, p);); + ptr_buffer args; + sbuffer::const_iterator it = p.begin(); + sbuffer::const_iterator end = p.end(); + for (; it != end; ++it) { + rational const & c = it->first; + expr * var = it->second; + if (!c.is_one()) { + rational c2; + expr * m = 0; + if (m_util.is_numeral(var, c2)) + m = m_util.mk_numeral(c*c2, m_util.is_int(var)); + else + m = m_util.mk_mul(m_util.mk_numeral(c, m_util.is_int(var)), var); + m_nl_new_exprs.push_back(m); + args.push_back(m); + } + else { + args.push_back(var); + } + } + SASSERT(!args.empty()); + expr * r = mk_nary_add(args.size(), args.c_ptr()); + m_nl_new_exprs.push_back(r); + return r; + } + + /** + \brief Return expression representing: var^power + */ + template + expr * theory_arith::power(expr * var, unsigned power) { + SASSERT(power > 0); + expr * r = var; + for (unsigned i = 1; i < power; i++) + r = m_util.mk_mul(var, r); + m_nl_new_exprs.push_back(r); + return r; + } + + /** + \brief Return true if var only occurs in two monovariate monomials, + and return its power and coefficients and these monomials. + The arguments i1 and i2 contain the position in p of the two monomials. + */ + template + bool theory_arith::in_monovariate_monomials(sbuffer & p, expr * var, + unsigned & i1, rational & c1, unsigned & n1, unsigned & i2, rational & c2, unsigned & n2) { + int idx = 0; +#define SET_RESULT(POWER) { \ + if (idx == 0) { \ + c1 = it->first; \ + n1 = POWER; \ + idx = 1; \ + i1 = i; \ + } \ + else if (idx == 1) { \ + c2 = it->first; \ + n2 = POWER; \ + idx = 2; \ + i2 = i; \ + } \ + else \ + return false; \ + } + + typename sbuffer::const_iterator it = p.begin(); + typename sbuffer::const_iterator end = p.end(); + for (unsigned i = 0; it != end; ++it, ++i) { + expr * m = it->second; + if (is_pure_monomial(m)) { + unsigned num_vars = get_num_vars_in_monomial(m); + for (unsigned j = 0; j < num_vars; j++) { + var_power_pair p = get_var_and_degree(m, j); + if (p.first == var) { + if (num_vars > 1) + return false; + SET_RESULT(p.second); + } + } + } + else if (m == var) { + SET_RESULT(1); + } + } + if (idx != 2) + return false; + return true; + } + + /** + \brief Diplay a nested form expression + */ + template + void theory_arith::display_nested_form(std::ostream & out, expr * p) { + if (has_var(p)) { + out << "#" << p->get_id(); + } + else if (m_util.is_add(p)) { + SASSERT(!has_var(p)); + out << "("; + for (unsigned i = 0; i < to_app(p)->get_num_args(); i++) { + if (i > 0) out << " + "; + display_nested_form(out, to_app(p)->get_arg(i)); + } + out << ")"; + } + else if (m_util.is_mul(p)) { + rational c = get_monomial_coeff(p); + bool first = true; + if (!c.is_one()) { + out << c; + first = false; + } + unsigned num_vars = get_num_vars_in_monomial(p); + for (unsigned i = 0; i < num_vars; i++) { + if (first) first = false; else out << "*"; + var_power_pair pair = get_var_and_degree(p, i); + expr * var = pair.first; + unsigned power = pair.second; + display_nested_form(out, var); + if (power != 1) + out << "^" << power; + } + } + else { + rational val; + if (m_util.is_numeral(p, val)) + out << val; + else + out << "[unknown #" << p->get_id() << "]"; + } + } + + /** + \brief Return the degree of var in m. + */ + template + unsigned theory_arith::get_degree_of(expr * m, expr * var) { + if (m == var) + return 1; + if (is_pure_monomial(m)) { + unsigned num_vars = get_num_vars_in_monomial(m); + for (unsigned i = 0; i < num_vars; i++) { + var_power_pair p = get_var_and_degree(m, i); + if (p.first == var) + return p.second; + } + } + return 0; + } + + /** + \brief Return the minimal degree of var in the polynomial p. + */ + template + unsigned theory_arith::get_min_degree(sbuffer & p, expr * var) { + SASSERT(!p.empty()); + SASSERT(var != 0); + // get monomial where the degree of var is min. + unsigned d = UINT_MAX; // min. degree of var + sbuffer::const_iterator it = p.begin(); + sbuffer::const_iterator end = p.end(); + for (; it != end; ++it) { + expr * m = it->second; + d = std::min(d, get_degree_of(m, var)); + if (d == 0) + return d; + } + SASSERT(d != UINT_MAX); + return d; + } + + /** + \brief Divide m by var^d. + */ + template + expr * theory_arith::factor(expr * m, expr * var, unsigned d) { + TRACE("factor", tout << "m: " << mk_pp(m, get_manager()) << "\nvar: " << mk_pp(var, get_manager()) << "\nd: " << d << "\n";); + if (d == 0) + return m; + if (m == var) { + SASSERT(d == 1); + expr * result = m_util.mk_numeral(rational(1), m_util.is_int(var)); + m_nl_new_exprs.push_back(result); + return result; + } + SASSERT(is_pure_monomial(m)); + unsigned idx = 0; + ptr_buffer new_args; + for (unsigned i = 0; i < to_app(m)->get_num_args(); i++) { + expr * arg = to_app(m)->get_arg(i); + if (arg == var) { + if (idx < d) + idx++; + else + new_args.push_back(arg); + } + else { + new_args.push_back(arg); + } + } + SASSERT(idx == d); + TRACE("factor_bug", tout << "new_args:\n"; for(unsigned i = 0; i < new_args.size(); i++) tout << mk_pp(new_args[i], get_manager()) << "\n";); + expr * result = mk_nary_mul(new_args.size(), new_args.c_ptr(), m_util.is_int(var)); + m_nl_new_exprs.push_back(result); + TRACE("factor", tout << "result: " << mk_pp(result, get_manager()) << "\n";); + return result; + } + + /** + \brief Return the horner extension of p with respect to var. + */ + template + expr * theory_arith::horner(sbuffer & p, expr * var) { + SASSERT(!p.empty()); + SASSERT(var != 0); + unsigned d = get_min_degree(p, var); + TRACE("horner_bug", tout << "poly:\n"; + for (unsigned i = 0; i < p.size(); i++) { if (i > 0) tout << " + "; tout << p[i].first << "*" << mk_pp(p[i].second, get_manager()); } tout << "\n"; + tout << "var: " << mk_pp(var, get_manager()) << "\n"; + tout << "min_degree: " << d << "\n";); + sbuffer e; // monomials/x^d where var occurs with degree d + sbuffer r; // rest + sbuffer::const_iterator it = p.begin(); + sbuffer::const_iterator end = p.end(); + for (; it != end; ++it) { + expr * m = it->second; + expr * f = factor(m, var, d); + if (get_degree_of(m, var) == d) { + e.push_back(coeff_expr(it->first, f)); + } + else { + SASSERT(get_degree_of(m, var) > d); + r.push_back(coeff_expr(it->first, f)); + } + } + expr * s = cross_nested(e, 0); + if (!r.empty()) { + expr * q = horner(r, var); + // TODO: improve here + s = m_util.mk_add(q, s); + } + + expr * result = s; + if (d != 0) { + expr * xd = power(var, d); + result = m_util.mk_mul(xd, s); + } + m_nl_new_exprs.push_back(result); + return result; + } + + /** + \brief Convert the polynomial p into an equivalent cross nested + expression. The idea is to obtain an expression e where + evaluate_as_interval(e) is more precise than + evaluate_as_interval(p). + + If var != 0, then it is used for performing the horner extension + */ + template + expr * theory_arith::cross_nested(sbuffer & p, expr * var) { + TRACE("non_linear", tout << "p.size: " << p.size() << "\n";); + if (var == 0) { + sbuffer varinfo; + get_polynomial_info(p, varinfo); + if (varinfo.empty()) + return p2expr(p); + sbuffer::const_iterator it = varinfo.begin(); + sbuffer::const_iterator end = varinfo.end(); + var = it->first; + unsigned max = it->second; + ++it; + for (; it != end; ++it) { + if (it->second > max) { + var = it->first; + max = it->second; + } + } + } + SASSERT(var != 0); + unsigned i1 = UINT_MAX; + unsigned i2 = UINT_MAX; + rational a, b; + unsigned n = UINT_MAX; + unsigned nm = UINT_MAX; + if (in_monovariate_monomials(p, var, i1, a, n, i2, b, nm)) { + CTRACE("in_monovariate_monomials", n == nm, + for (unsigned i = 0; i < p.size(); i++) { + if (i > 0) tout << " + "; tout << p[i].first << "*" << mk_pp(p[i].second, get_manager()); + } + tout << "\n"; + tout << "var: " << mk_pp(var, get_manager()) << "\n"; + tout << "i1: " << i1 << "\n"; + tout << "a: " << a << "\n"; + tout << "n: " << n << "\n"; + tout << "i2: " << i2 << "\n"; + tout << "b: " << b << "\n"; + tout << "nm: " << nm << "\n";); + SASSERT(n != nm); + expr * new_expr = 0; + if (nm < n) { + std::swap(n, nm); + std::swap(a, b); + } + SASSERT(nm > n); + unsigned m = nm - n; + if (n % 2 == m % 2 && n >= m) { + // b*x^{n-m}*[(x^{m} + a/(2b))^2 - (a/2b)^2] + // b*[(x^{m} + a/(2b))^2 - (a/2b)^2] for n == m + rational a2b = a; + expr * xm = power(var, m); + a2b /= (rational(2) * b); + // we cannot create a numeral that has sort int, but it is a rational. + if (!m_util.is_int(var) || a2b.is_int()) { + rational ma2b2 = a2b * a2b; + ma2b2.neg(); + expr * xm_a2b = m_util.mk_add(m_util.mk_numeral(a2b, m_util.is_int(var)), xm); + expr * xm_a2b2 = m_util.mk_mul(xm_a2b, xm_a2b); + expr * rhs = m_util.mk_add(xm_a2b2, m_util.mk_numeral(ma2b2, m_util.is_int(var))); + expr * rhs2 = 0; + if (n > m) + rhs2 = m_util.mk_mul(power(var, n - m), rhs); + else + rhs2 = rhs; + new_expr = b.is_one() ? rhs2 : m_util.mk_mul(m_util.mk_numeral(b, m_util.is_int(var)), rhs2); + m_nl_new_exprs.push_back(new_expr); + TRACE("non_linear", tout << "new_expr:\n"; display_nested_form(tout, new_expr); tout << "\n";); + sbuffer rest; + unsigned sz = p.size(); + for (unsigned i = 0; i < sz; i++) { + if (i != i1 && i != i2) + rest.push_back(p[i]); + } + if (rest.empty()) + return new_expr; + TRACE("non_linear", tout << "rest size: " << rest.size() << ", i1: " << i1 << ", i2: " << i2 << "\n";); + expr * h = cross_nested(rest, 0); + expr * r = m_util.mk_add(new_expr, h); + m_nl_new_exprs.push_back(r); + return r; + } + } + } + return horner(p, var); + } + + /** + \brief Check whether the given polynomial is consistent with respect to the known + bounds. The polynomial is converted into an equivalent cross nested form. + */ + template + bool theory_arith::is_cross_nested_consistent(sbuffer & p) { + sbuffer varinfo; + get_polynomial_info(p, varinfo); + if (varinfo.empty()) + return true; + std::stable_sort(varinfo.begin(), varinfo.end(), var_num_occs_lt()); + TRACE("cross_nested", tout << "var num occs:\n"; + sbuffer::const_iterator it = varinfo.begin(); + sbuffer::const_iterator end = varinfo.end(); + for (; it != end ; ++it) { + tout << mk_bounded_pp(it->first, get_manager()) << " -> " << it->second << "\n"; + }); + sbuffer::const_iterator it = varinfo.begin(); + sbuffer::const_iterator end = varinfo.end(); + for (; it != end; ++it) { + m_nl_new_exprs.reset(); + expr * var = it->first; + expr * cn = cross_nested(p, var); + // Remark: cn may not be well-sorted because, since a row may contain mixed integer/real monomials. + // This is not really a problem, since evaluate_as_interval will work even if cn is not well-sorted. + if (!cn) + continue; + TRACE("cross_nested", tout << "nested form for var:\n" << mk_ismt2_pp(var, get_manager()) << "\n"; + display_nested_form(tout, cn); tout << "\n"; + tout << "c:\n" << mk_ismt2_pp(cn, get_manager()) << "\n";); + interval i = evaluate_as_interval(cn); + TRACE("cross_nested", tout << "interval: " << i << "\n";); + v_dependency * d = 0; + if (!i.minus_infinity() && (i.get_lower_value().is_pos() || (i.get_lower_value().is_zero() && i.is_lower_open()))) + d = i.get_lower_dependencies(); + else if (!i.plus_infinity() && (i.get_upper_value().is_neg() || (i.get_upper_value().is_zero() && i.is_upper_open()))) + d = i.get_upper_dependencies(); + if (d) { + TRACE("cross_nested", tout << "nested form conflict: " << i << "\n";); + set_conflict(d); + return false; + } + } + return true; + } + + /** + \brief Check whether the polynomial represented by the current row is + consistent with respect to the known bound when converted into a + equivalent cross nested form. + */ + template + bool theory_arith::is_cross_nested_consistent(row const & r) { + TRACE("cross_nested", tout << "is_cross_nested_consistent:\n"; display_row(tout, r, false);); + if (!is_problematic_non_linear_row(r)) + return true; + + TRACE("cross_nested", tout << "problematic...\n";); + + /* + The method is_cross_nested converts rows back to expressions. + The conversion back to expressions may create sort incorrect expressions. + This is in some sense ok, since these expressions are temporary, but + the sort incorrect expressions may generate assertion violations. + + Sort incorrect expressions may be created in the following cases: + + 1) mixed real int rows. + + 2) int rows that contain non integer coefficients. + + 3) int rows that when converted to cross nested form use non integer coefficients. + + There are several ways to deal with this problem: + + a) Ignore the assertion violations. Disadvantage: it will prevent us from running Z3 in debug mode on some benchmarks. + + b) Remove the assertions. Disadvantage: these assertions helped us to find many important bugs in Z3 + + c) Disable the assertions temporally. This sounds like a big HACK. + + d) Use a different data-structure to represent polynomials in cross-nested form. Disadvantage: code duplication, the data-structure + is essentially identical to the ASTs we are using right now. + + e) Disable the test when we cannot create a well-sorted expression. + I'm temporally using this solution. + I implemented the following logic: + 1) (mixed real int) Disable the test. Most benchmarks do not contain mixed int real variables. + 2) (int coeffs) I multiply the row by a constant to force it to have only integer coefficients. + 3) (new non-int coeffs) This only happens in an optional step in the conversion. Now, for int rows, I only apply this optional step only if non-int coeffs are not created. + */ + + if (is_mixed_real_integer(r)) + return true; // giving up... see comment above + + TRACE("cross_nested", tout << "cheking problematic row...\n";); + + rational c = rational::one(); + if (is_integer(r)) + c = r.get_denominators_lcm().to_rational(); + + TRACE("non_linear", tout << "check problematic row:\n"; display_row(tout, r); display_row(tout, r, false);); + sbuffer p; + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) + p.push_back(coeff_expr(it->m_coeff.to_rational() * c, var2expr(it->m_var))); + } + SASSERT(!p.empty()); + CTRACE("cross_nested_bug", !c.is_one(), tout << "c: " << c << "\n"; display_row(tout, r); tout << "---> p (coeffs, exprs):\n"; display_coeff_exprs(tout, p);); + return is_cross_nested_consistent(p); + } + + /** + \brief Check whether an inconsistency can be found using cross nested + form in the non linear cluster. + */ + template + bool theory_arith::is_cross_nested_consistent(svector const & nl_cluster) { + svector::const_iterator it = nl_cluster.begin(); + svector::const_iterator end = nl_cluster.end(); + for (; it != end; ++it) { + theory_var v = *it; + if (!is_base(v)) + continue; + m_stats.m_nl_cross_nested++; + row const & r = m_rows[get_var_row(v)]; + if (!is_cross_nested_consistent(r)) + return false; + } + return true; + } + +#define FIXED 0 +#define QUOTED_FIXED 1 +#define BOUNDED 2 +#define QUOTED_BOUNDED 3 +#define NOT_FREE 4 +#define QUOTED_NOT_FREE 5 +#define FREE 6 +#define QUOTED_FREE 7 +#define MAX_DEFAULT_WEIGHT 7 + + /** + \brief Initialize variable order for grobner basis computation. + Make: + "quoted free vars" > "free vars" > "quoted variables with lower or upper bounds" > + "variables with lower or upper bounds" > "quoted bounded variables" > + "bounded variables" > "quoted fixed variables" > "fixed variables" + */ + template + void theory_arith::init_grobner_var_order(svector const & nl_cluster, grobner & gb) { + // Initialize variable order + svector::const_iterator it = nl_cluster.begin(); + svector::const_iterator end = nl_cluster.end(); + for (; it != end; ++it) { + theory_var v = *it; + expr * var = var2expr(v); + + if (is_fixed(v)) { + gb.set_weight(var, is_pure_monomial(var) ? QUOTED_FIXED : FIXED); + } + else if (is_bounded(v)) { + gb.set_weight(var, is_pure_monomial(var) ? QUOTED_BOUNDED : BOUNDED); + } + else if (lower(v) || upper(v)) { + gb.set_weight(var, is_pure_monomial(var) ? QUOTED_NOT_FREE : NOT_FREE); + } + else { + SASSERT(is_free(v)); + gb.set_weight(var, is_pure_monomial(var) ? QUOTED_FREE : FREE); + } + } + } + + /** + \brief Create a new monomial using the given coeff and m. Fixed + variables in m are substituted by their values. The arg dep is + updated to store these dependencies. The set already_found is + updated with the fixed variables in m. A variable is only + added to dep if it is not already in already_found. + + Return null if the monomial was simplied to 0. + */ + template + grobner::monomial * theory_arith::mk_gb_monomial(rational const & _coeff, expr * m, grobner & gb, v_dependency * & dep, var_set & already_found) { + ptr_buffer vars; + rational coeff = _coeff; + rational r; +#undef PROC_VAR +#define PROC_VAR(VAR) { \ + if (m_util.is_numeral(VAR, r)) { \ + coeff *= r; \ + } \ + else { \ + theory_var _var = expr2var(VAR); \ + if (is_fixed(_var)) { \ + if (!already_found.contains(_var)) { \ + already_found.insert(_var); \ + dep = m_dep_manager.mk_join(dep, m_dep_manager.mk_join(m_dep_manager.mk_leaf(lower(_var)), m_dep_manager.mk_leaf(upper(_var)))); \ + } \ + coeff *= lower_bound(_var).get_rational().to_rational(); \ + } \ + else { \ + vars.push_back(VAR); \ + } \ + } \ + } + + if (m_util.is_mul(m)) { + coeff *= get_monomial_coeff(m); + m = get_monomial_body(m); + if (m_util.is_mul(m)) { + SASSERT(is_pure_monomial(m)); + for (unsigned i = 0; i < to_app(m)->get_num_args(); i++) { + expr * arg = to_app(m)->get_arg(i); + PROC_VAR(arg); + } + } + else { + PROC_VAR(m); + } + } + else { + PROC_VAR(m); + } + if (!coeff.is_zero()) + return gb.mk_monomial(coeff, vars.size(), vars.c_ptr()); + else + return 0; + } + + /** + \brief Send the given row to the grobner basis object. + All fixed variables are substituted before sending the row to gb. + */ + template + void theory_arith::add_row_to_gb(row const & r, grobner & gb) { + TRACE("non_linear", tout << "adding row to gb\n"; display_row(tout, r);); + ptr_buffer monomials; + v_dependency * dep = 0; + m_tmp_var_set.reset(); + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) { + rational coeff = it->m_coeff.to_rational(); + expr * m = var2expr(it->m_var); + TRACE("non_linear", tout << "monomial: " << mk_pp(m, get_manager()) << "\n";); + grobner::monomial * new_m = mk_gb_monomial(coeff, m, gb, dep, m_tmp_var_set); + TRACE("non_linear", tout << "new monomial:\n"; if (new_m) gb.display_monomial(tout, *new_m); else tout << "null"; tout << "\n";); + if (new_m) + monomials.push_back(new_m); + } + } + gb.assert_eq_0(monomials.size(), monomials.c_ptr(), dep); + } + + /** + \brief v must be a pure monomial. That is, v = (quote (* x_1 ... x_n)) + Add the monomial (quote (* x_1 ... x_n)) = x_1 * ... * x_n. + Fixed variables are substituted. + */ + template + void theory_arith::add_monomial_def_to_gb(theory_var v, grobner & gb) { + ptr_buffer monomials; + v_dependency * dep = 0; + m_tmp_var_set.reset(); + expr * m = var2expr(v); + SASSERT(is_pure_monomial(m)); + grobner::monomial * new_m = mk_gb_monomial(rational(1), m, gb, dep, m_tmp_var_set); + if (new_m) + monomials.push_back(new_m); + rational coeff(-1); + if (is_fixed(v)) { + dep = m_dep_manager.mk_join(dep, m_dep_manager.mk_join(m_dep_manager.mk_leaf(lower(v)), m_dep_manager.mk_leaf(upper(v)))); + coeff *= lower_bound(v).get_rational().to_rational(); + if (!coeff.is_zero()) + monomials.push_back(gb.mk_monomial(coeff, 0, 0)); + } + else { + monomials.push_back(gb.mk_monomial(coeff, 1, &m)); + } + gb.assert_eq_0(monomials.size(), monomials.c_ptr(), dep); + } + + /** + Initialize grobner basis data structure using the non linear cluster. + The GB is initialized using rows and non linear monomials. + */ + template + void theory_arith::init_grobner(svector const & nl_cluster, grobner & gb) { + init_grobner_var_order(nl_cluster, gb); + svector::const_iterator it = nl_cluster.begin(); + svector::const_iterator end = nl_cluster.end(); + for (; it != end; ++it) { + theory_var v = *it; + if (is_base(v)) { + row const & r = m_rows[get_var_row(v)]; + add_row_to_gb(r, gb); + } + if (is_pure_monomial(v) && !m_data[v].m_nl_propagated && is_fixed(v)) { + add_monomial_def_to_gb(v, gb); + } + } + } + + /** + \brief Return the interval for the given monomial + */ + template + interval theory_arith::mk_interval_for(grobner::monomial const * m) { + interval r(m_dep_manager, rational(m->get_coeff())); + expr * var = 0; + unsigned power = 0; + unsigned num_vars = m->get_degree(); + for (unsigned i = 0; i < num_vars; i++) { + expr * curr = m->get_var(i); + if (var == 0) { + var = curr; + power = 1; + } + else if (curr == var) { + power++; + } + else { + mul_bound_of(var, power, r); + var = curr; + power = 1; + } + } + if (var != 0) + mul_bound_of(var, power, r); + return r; + } + + /** + \brief Set a conflict using a dependency object. + */ + template + void theory_arith::set_conflict(v_dependency * d) { + bool is_lia = false; // TODO: fix it, but this is only used for debugging. + antecedents& ante = get_antecedents(); + derived_bound b(null_theory_var, inf_numeral(0), B_LOWER); + dependency2new_bound(d, b); + set_conflict(b.m_lits.size(), b.m_lits.c_ptr(), b.m_eqs.size(), b.m_eqs.c_ptr(), ante, is_lia, "arith_nl"); + } + + /** + \brief Return true if I.get_lower() <= - M_1 - ... - M_n <= I.get_upper() is inconsistent. + Where M_i is monomials[i] and n = num_monomials. + A conflict will also be set using the bounds of the variables occurring in the monomials M_i's. + */ + template + bool theory_arith::is_inconsistent(interval const & I, unsigned num_monomials, grobner::monomial * const * monomials, v_dependency * dep) { + interval r(I); + for (unsigned i = 0; i < num_monomials; i++) { + grobner::monomial const * m = monomials[i]; + r += mk_interval_for(m); + if (r.minus_infinity() && r.plus_infinity()) + return false; + } + TRACE("non_linear_bug", tout << "is_inconsistent, r: " << r << "\n";); + v_dependency * interval_deps = 0; + bool conflict = false; + if (!r.minus_infinity() && (r.get_lower_value().is_pos() || (r.get_lower_value().is_zero() && r.is_lower_open()))) { + interval_deps = r.get_lower_dependencies(); + conflict = true; + TRACE("non_linear_bug", tout << "is inconsistent, interval_deps: " << interval_deps << "\n";); + } + else if (!r.plus_infinity() && (r.get_upper_value().is_neg() || (r.get_upper_value().is_zero() && r.is_upper_open()))) { + interval_deps = r.get_upper_dependencies(); + conflict = true; + TRACE("non_linear_bug", tout << "is inconsistent, interval_deps: " << interval_deps << "\n";); + } + // interval_deps cannot be used to check if a conflict was detected, since interval_deps may be 0 even when r does not contain 0 + if (conflict) { + TRACE("non_linear", tout << "conflicting interval for = 0 equation: " << r << "\n";); + set_conflict(m_dep_manager.mk_join(interval_deps, dep)); + return true; + } + return false; + } + + /** + \brief Return true if the equation is inconsistent, + and sign a conflict. + */ + template + bool theory_arith::is_inconsistent(grobner::equation const * eq, grobner & gb) { + interval zero(m_dep_manager, rational(0)); + if (is_inconsistent(zero, eq->get_num_monomials(), eq->get_monomials(), eq->get_dependency())) { + TRACE("non_linear", tout << "found conflict\n"; gb.display_equation(tout, *eq);); + return true; + } + return false; + } + + /** + \brief Return true if the given monomial c*M is squared. + The square root of the c is stored in r. + */ + bool is_perfect_square(grobner::monomial const * m, rational & r) { + unsigned num_vars = m->get_degree(); + if (num_vars % 2 == 1) + return false; + if (!m->get_coeff().is_perfect_square(r)) + return false; + expr * var = 0; + unsigned power = 0; + for (unsigned i = 0; i < num_vars; i++) { + expr * curr = m->get_var(i); + if (var == 0) { + var = curr; + power = 1; + } + else if (var == curr) { + power++; + } + else { + if (power % 2 == 1) + return false; + var = curr; + power = 1; + } + } + return power % 2 == 0; + } + + /** + \brief Return m1m2 is of the form (-2ab)*M1*M2 + assuming that + m1_sq = a^2*M1*M1 + m2_sq = b^2*M2*M2 + */ + bool is_perfect_square(grobner::monomial const * m1_sq, rational const & a, grobner::monomial const * m2_sq, rational const & b, grobner::monomial const * m1m2) { + DEBUG_CODE({ + rational a1; + rational b1; + SASSERT(is_perfect_square(m1_sq, a1) && a == a1 && is_perfect_square(m2_sq, b1) && b == b1); + }); + if (m1m2->get_coeff().is_nonneg()) + return false; + rational c(-2); + c *= a; + c *= b; + if (m1m2->get_coeff() != c) + return false; + unsigned num1 = m1_sq->get_degree(); + unsigned num2 = m2_sq->get_degree(); + unsigned num12 = m1m2->get_degree(); + if (num1 + num2 != num12 * 2) + return false; + unsigned i1, i2, i12; + i1 = i2 = i12 = 0; + while (true) { + expr * v1 = 0; + expr * v2 = 0; + expr * v12 = 0; + if (i1 < num1) + v1 = m1_sq->get_var(i1); + if (i2 < num2) + v2 = m2_sq->get_var(i2); + if (i12 < num12) + v12 = m1m2->get_var(i12); + if (v1 == 0 && v2 == 0 && v12 == 0) + return true; + if (v12 == 0) + return false; + if (v1 == v12) { + SASSERT(m1_sq->get_var(i1+1) == v1); + i1 += 2; + i12 ++; + } + else if (v2 == v12) { + SASSERT(m2_sq->get_var(i2+1) == v2); + i2 += 2; + i12 ++; + } + else { + return false; + } + } + } + + /** + \brief Return true if the equation is inconsistent. In this + version, perfect squares are eliminated, and replaced with the + interval [0, oo), if the interval associated with them is less + precise than [0, oo). + + \remark I track only simple perfect squares of the form (M1 - M2)^2, + where M1 and M2 are arbitrary monomials. + */ + template + bool theory_arith::is_inconsistent2(grobner::equation const * eq, grobner & gb) { + // TODO: a possible improvement: create a quotation for (M1 - M2)^2 + // instead of trying to find it in a specific equation. + // This approach is more precise, but more expensive + // since a new row must be created. + buffer intervals; + unsigned num = eq->get_num_monomials(); + for (unsigned i = 0; i < num; i++) { + grobner::monomial const * m = eq->get_monomial(i); + intervals.push_back(mk_interval_for(m)); + } + sbuffer deleted; + deleted.resize(num, false); + ptr_buffer monomials; + // try to eliminate monomials that form perfect squares of the form (M1 - M2)^2 + for (unsigned i = 0; i < num; i++) { + grobner::monomial const * m1 = eq->get_monomial(i); + rational a; + if (deleted[i]) + continue; + if (!is_perfect_square(m1, a)) { + monomials.push_back(const_cast(m1)); + continue; + } + TRACE("non_linear", tout << "found perfect square monomial m1: "; gb.display_monomial(tout, *m1); tout << "\n";); + // try to find another perfect square + unsigned j = i + 1; + for (; j < num; j++) { + if (deleted[j]) + continue; + grobner::monomial const * m2 = eq->get_monomial(j); + rational b; + if (!is_perfect_square(m2, b)) + continue; + TRACE("non_linear", tout << "found perfect square monomial m2: "; gb.display_monomial(tout, *m2); tout << "\n";); + // try to find -2*root(m1)*root(m2) + // This monomial must be smaller than m1, since m2 is smaller than m1. + unsigned k = i + 1; + for (; k < num; k++) { + if (deleted[k]) + continue; + grobner::monomial const * m1m2 = eq->get_monomial(k); + if (!is_perfect_square(m1, a, m2, b, m1m2)) + continue; + // m1, m2, and m1m2 form a perfect square. + // check if [0, oo) provides a better lowerbound than adding the intervals of m1, m2 and m1m2; + TRACE("non_linear", tout << "found perfect square (M1-M2)^2:\n"; + gb.display_monomial(tout, *m1); tout << "\n"; + gb.display_monomial(tout, *m2); tout << "\n"; + gb.display_monomial(tout, *m1m2); tout << "\n";); + interval I = intervals[i]; + I += intervals[j]; + I += intervals[k]; + if (I.minus_infinity() || I.get_lower_value().is_neg()) { + TRACE("non_linear", tout << "the lower bound improved when perfect square is eliminated.\n";); + // Found improvement... + // mark these monomials as deleted + deleted[i] = true; + deleted[j] = true; + deleted[k] = true; + break; + } + } + if (k < num) + break; // found perfect square + } + if (j == num) { + // didn't find perfect square of the form (M1-M2)^2 + monomials.push_back(const_cast(m1)); + } + } + if (monomials.size() == num) + return false; // didn't find any perfect square. + interval ge_zero(m_dep_manager, rational(0), false, true, 0); + if (is_inconsistent(ge_zero, monomials.size(), monomials.c_ptr(), eq->get_dependency())) { + TRACE("non_linear", tout << "found conflict\n"; gb.display_equation(tout, *eq);); + return true; + } + return false; + } + + template + expr * theory_arith::monomial2expr(grobner::monomial const * m, bool is_int) { + unsigned num_vars = m->get_degree(); + ptr_buffer args; + if (!m->get_coeff().is_one()) + args.push_back(m_util.mk_numeral(m->get_coeff(), is_int)); + for (unsigned j = 0; j < num_vars; j++) + args.push_back(m->get_var(j)); + return mk_nary_mul(args.size(), args.c_ptr(), is_int); + } + + /** + \brief Assert the new equation in the simplex tableau. + */ + template + bool theory_arith::internalize_gb_eq(grobner::equation const * eq) { + bool is_int = false; + unsigned num_monomials = eq->get_num_monomials(); + for (unsigned i = 0; i < num_monomials; i++) { + grobner::monomial const * m = eq->get_monomial(i); + unsigned degree = m->get_degree(); + if (degree > m_params.m_nl_arith_max_degree) + return false; + if (degree > 0) + is_int = m_util.is_int(m->get_var(0)); + } + rational k; + ptr_buffer args; + for (unsigned i = 0; i < num_monomials; i++) { + grobner::monomial const * m = eq->get_monomial(i); + if (m->get_degree() == 0) + k -= m->get_coeff(); + else + args.push_back(monomial2expr(eq->get_monomial(i), is_int)); + } + context & ctx = get_context(); + simplifier & s = ctx.get_simplifier(); + expr_ref pol(get_manager()); + SASSERT(!args.empty()); + pol = mk_nary_add(args.size(), args.c_ptr()); + expr_ref s_pol(get_manager()); + proof_ref pr(get_manager()); + TRACE("gb_bug", tout << mk_ll_pp(pol, get_manager()) << "\n";); + s(pol, s_pol, pr); + if (!has_var(s_pol)) { + TRACE("spol_bug", tout << "internalizing...\n" << mk_ll_pp(s_pol, get_manager()) << "\n";); + ctx.internalize(s_pol, false); + ctx.mark_as_relevant(s_pol.get()); + } + SASSERT(has_var(s_pol.get())); + // s_pol = k + theory_var v = expr2var(s_pol); + // v = k + CTRACE("spol_bug", v == null_theory_var, tout << mk_ll_pp(s_pol, get_manager()) << "\n"; display(tout);); + SASSERT(v != null_theory_var); + // assert bounds for s_pol + mk_derived_nl_bound(v, inf_numeral(k), B_LOWER, eq->get_dependency()); + mk_derived_nl_bound(v, inf_numeral(k), B_UPPER, eq->get_dependency()); + TRACE("non_linear", tout << "inserted new equation into the tableau\n"; display_var(tout, v);); + return true; + } + + /** + \brief Compute Grobner basis, return true if a conflict or new fixed variables were detected. + */ + template + typename theory_arith::gb_result theory_arith::compute_grobner(svector const & nl_cluster) { + if (m_nl_gb_exhausted) + return GB_FAIL; + grobner gb(get_manager(), m_dep_manager); + init_grobner(nl_cluster, gb); + TRACE("non_linear", display(tout);); + bool warn = false; + unsigned next_weight = MAX_DEFAULT_WEIGHT + 1; // next weight using during perturbation phase. + ptr_vector eqs; + + while (true) { + TRACE("non_linear_gb", tout << "before:\n"; gb.display(tout);); + bool r = gb.compute_basis(m_params.m_nl_arith_gb_threshold); + m_stats.m_gb_simplify += gb.m_stats.m_simplify; + m_stats.m_gb_superpose += gb.m_stats.m_superpose; + m_stats.m_gb_num_processed += gb.m_stats.m_num_processed; + m_stats.m_gb_compute_basis++; + if (!r && !warn) { + IF_VERBOSE(3, verbose_stream() << "Grobner basis computation interrupted. Increase threshold using NL_ARITH_GB_THRESHOLD=\n";); + get_context().push_trail(value_trail(m_nl_gb_exhausted)); + m_nl_gb_exhausted = true; + warn = true; + } + TRACE("non_linear_gb", tout << "after:\n"; gb.display(tout);); + // Scan the grobner basis eqs, and look for inconsistencies. + eqs.reset(); + gb.get_equations(eqs); + TRACE("grobner_bug", tout << "after gb\n";); + ptr_vector::const_iterator it = eqs.begin(); + ptr_vector::const_iterator end = eqs.end(); + for (; it != end; ++it) { + grobner::equation * eq = *it; + TRACE("grobner_bug", gb.display_equation(tout, *eq);); + if (is_inconsistent(eq, gb)) + return GB_PROGRESS; + if (is_inconsistent2(eq, gb)) + return GB_PROGRESS; + } + // Scan the grobner basis eqs for equations of the form x - k = 0 or x = 0 is found, and x is not fixed, + // then assert bounds for x, and continue + gb_result result = GB_FAIL; + if (m_params.m_nl_arith_gb_eqs) { + it = eqs.begin(); + for (; it != end; ++it) { + grobner::equation * eq = *it; + if (!eq->is_linear_combination()) { + TRACE("non_linear", tout << "processing new equality:\n"; gb.display_equation(tout, *eq);); + TRACE("non_linear_bug", tout << "processing new equality:\n"; gb.display_equation(tout, *eq);); + if (internalize_gb_eq(eq)) + result = GB_NEW_EQ; + } + } + } + if (result != GB_FAIL) + return result; + if (!m_params.m_nl_arith_gb_perturbate) + return result; + if (m_nl_gb_exhausted) + return result; + // Try to change the variable order... in such a way the leading term is modified. + // I only consider linear equations... (HACK) + // Moreover, I do not change the weight of a variable more than once in this loop. + bool modified = false; + it = eqs.begin(); + for (; it != end; ++it) { + grobner::equation const * eq = *it; + unsigned num_monomials = eq->get_num_monomials(); + CTRACE("grobner_bug", num_monomials <= 0, gb.display_equation(tout, *eq);); + if (num_monomials == 0) + continue; // HACK: the equation 0 = 0, should have been discarded by the GB module. + if (eq->get_monomial(0)->get_degree() != 1) + continue; + for (unsigned j = 1; j < num_monomials; j++) { + grobner::monomial const * m = eq->get_monomial(j); + if (m->get_degree() != 1) + continue; + expr * var = m->get_var(0); + if (gb.get_weight(var) > MAX_DEFAULT_WEIGHT) + continue; // variable was already updated + TRACE("non_linear", tout << "increased weight of: " << mk_bounded_pp(var, get_manager()) << "\n";); + gb.set_weight(var, next_weight); + next_weight++; + gb.update_order(); + TRACE("non_linear", tout << "after updating order\n"; gb.display(tout);); + modified = true; + break; + } + if (modified) + break; + } + if (!modified) + return result; + } + } + + /** + \brief Maximize/Minimize variables in non linear monomials. + */ + template + bool theory_arith::max_min_nl_vars() { + var_set already_found; + svector vars; + svector::const_iterator it = m_nl_monomials.begin(); + svector::const_iterator end = m_nl_monomials.end(); + for (; it != end; ++it) { + theory_var v = *it; + mark_var(v, vars, already_found); + expr * n = var2expr(v); + SASSERT(is_pure_monomial(n)); + for (unsigned i = 0; i < to_app(n)->get_num_args(); i++) { + expr * curr = to_app(n)->get_arg(i); + theory_var v = expr2var(curr); + SASSERT(v != null_theory_var); + mark_var(v, vars, already_found); + } + } + return max_min(vars); + } + + /** + \brief Process non linear constraints. + */ + template + final_check_status theory_arith::process_non_linear() { + if (m_nl_monomials.empty()) + return FC_DONE; + + if (check_monomial_assignments()) + return FC_DONE; + + if (!m_params.m_nl_arith) + return FC_GIVEUP; + + TRACE("process_non_linear", display(tout);); + + if (m_nl_rounds > m_params.m_nl_arith_rounds) { + TRACE("non_linear", tout << "GIVE UP non linear problem...\n";); + IF_VERBOSE(3, verbose_stream() << "Max. non linear arithmetic rounds. Increase threshold using NL_ARITH_ROUNDS=\n";); + return FC_GIVEUP; + } + + get_context().push_trail(value_trail(m_nl_rounds)); + m_nl_rounds++; + + elim_quasi_base_rows(); + move_non_base_vars_to_bounds(); + TRACE("non_linear", tout << "processing non linear constraints...\n"; get_context().display(tout);); + if (!make_feasible()) { + TRACE("non_linear", tout << "failed to move variables to bounds.\n";); + failed(); + return FC_CONTINUE; + } + + if (!max_min_nl_vars()) + return FC_CONTINUE; + + if (check_monomial_assignments()) + return m_liberal_final_check || !m_changed_assignment ? FC_DONE : FC_CONTINUE; + + svector vars; + get_non_linear_cluster(vars); + + bool progress; + unsigned old_idx = m_nl_strategy_idx; + get_context().push_trail(value_trail(m_nl_strategy_idx)); + + do { + progress = false; + switch (m_nl_strategy_idx) { + case 0: + if (propagate_nl_bounds()) { + propagate_core(); + progress = true; + } + break; + case 1: + if (!is_cross_nested_consistent(vars)) + progress = true; + break; + case 2: + if (m_params.m_nl_arith_gb) { + switch(compute_grobner(vars)) { + case GB_PROGRESS: + progress = true; + break; + case GB_NEW_EQ: + progress = true; + propagate_core(); + break; + case GB_FAIL: + break; + } + } + break; + case 3: + if (m_params.m_nl_arith_branching) { + theory_var target = find_nl_var_for_branching(); + if (target != null_theory_var && branch_nl_int_var(target)) + progress = true; + } + break; + } + + m_nl_strategy_idx = (m_nl_strategy_idx + 1) % 4; + if (progress) + return FC_CONTINUE; + } + while (m_nl_strategy_idx != old_idx); + + if (check_monomial_assignments()) + return m_liberal_final_check || !m_changed_assignment ? FC_DONE : FC_CONTINUE; + + TRACE("non_linear", display(tout);); + + return FC_GIVEUP; + } + +}; + + +#endif /* _THEORY_ARITH_NL_H_ */ + diff --git a/lib/theory_arith_params.cpp b/lib/theory_arith_params.cpp new file mode 100644 index 000000000..2d77b6e73 --- /dev/null +++ b/lib/theory_arith_params.cpp @@ -0,0 +1,76 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_arith_params.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-05-06. + +Revision History: + +--*/ + +#include"theory_arith_params.h" + +void theory_arith_params::register_params(ini_params & p) { +#ifdef _EXTERNAL_RELEASE + p.register_int_param("ARITH_SOLVER", 0, 3, reinterpret_cast(m_arith_mode), "select 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"); +#else + p.register_int_param("ARITH_SOLVER", 0, 4, reinterpret_cast(m_arith_mode), "select 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 - model guided arith_solver"); +#endif + p.register_bool_param("ARITH_FORCE_SIMPLEX", m_arith_auto_config_simplex, "force Z3 to use simplex solver."); + p.register_unsigned_param("ARITH_BLANDS_RULE_THRESHOLD", m_arith_blands_rule_threshold); + p.register_bool_param("ARITH_PROPAGATE_EQS", m_arith_propagate_eqs); + p.register_int_param("ARITH_PROPAGATION_MODE", 0, 2, reinterpret_cast(m_arith_bound_prop)); + p.register_bool_param("ARITH_STRONGER_LEMMAS", m_arith_stronger_lemmas); + p.register_bool_param("ARITH_SKIP_BIG_COEFFS", m_arith_skip_rows_with_big_coeffs); + p.register_unsigned_param("ARITH_MAX_LEMMA_SIZE", m_arith_max_lemma_size); + p.register_unsigned_param("ARITH_SMALL_LEMMA_SIZE", m_arith_small_lemma_size); + p.register_bool_param("ARITH_REFLECT", m_arith_reflect); + p.register_bool_param("ARITH_IGNORE_INT", m_arith_ignore_int); + p.register_unsigned_param("ARITH_LAZY_PIVOTING", m_arith_lazy_pivoting_lvl); + p.register_unsigned_param("ARITH_RANDOM_SEED", m_arith_random_seed); + p.register_bool_param("ARITH_RANDOM_INITIAL_VALUE", m_arith_random_initial_value); + p.register_int_param("ARITH_RANDOM_LOWER", m_arith_random_lower); + p.register_int_param("ARITH_RANDOM_UPPER", m_arith_random_upper); + p.register_bool_param("ARITH_ADAPTIVE", m_arith_adaptive); + p.register_double_param("ARITH_ADAPTIVE_ASSERTION_THRESHOLD", m_arith_adaptive_assertion_threshold, "Delay arithmetic atoms if the num-arith-conflicts/total-conflicts < threshold"); + p.register_double_param("ARITH_ADAPTIVE_PROPAGATION_THRESHOLD", m_arith_adaptive_propagation_threshold, "Disable arithmetic theory propagation if the num-arith-conflicts/total-conflicts < threshold"); + p.register_bool_param("ARITH_DUMP_LEMMAS", m_arith_dump_lemmas); + p.register_bool_param("ARITH_EAGER_EQ_AXIOMS", m_arith_eager_eq_axioms); + p.register_unsigned_param("ARITH_BRANCH_CUT_RATIO", m_arith_branch_cut_ratio); + + p.register_bool_param("ARITH_ADD_BINARY_BOUNDS", m_arith_add_binary_bounds); + p.register_unsigned_param("ARITH_PROP_STRATEGY", 0, 1, reinterpret_cast(m_arith_propagation_strategy), "Propagation strategy: 0 - use agility measures based on ration of theory conflicts, 1 - propagate proportional to ratio of theory conflicts (default)"); + + p.register_bool_param("ARITH_EQ_BOUNDS", m_arith_eq_bounds); + p.register_bool_param("ARITH_LAZY_ADAPTER", m_arith_lazy_adapter); + p.register_bool_param("ARITH_GCD_TEST", m_arith_gcd_test); + p.register_bool_param("ARITH_EAGER_GCD", m_arith_eager_gcd); + p.register_bool_param("ARITH_ADAPTIVE_GCD", m_arith_adaptive_gcd); + p.register_unsigned_param("ARITH_PROPAGATION_THRESHOLD", m_arith_propagation_threshold); + + p.register_bool_param("NL_ARITH", m_nl_arith, "enable/disable non linear arithmetic support. This option is ignored when ARITH_SOLVER != 2."); + p.register_bool_param("NL_ARITH_GB", m_nl_arith_gb, "enable/disable Grobner Basis computation. This option is ignored when NL_ARITH=false"); + p.register_bool_param("NL_ARITH_GB_EQS", m_nl_arith_gb_eqs, "enable/disable equations in the Grobner Basis to be copied to the Simplex tableau."); + p.register_bool_param("NL_ARITH_GB_PERTURBATE", m_nl_arith_gb_perturbate, "enable/disable perturbation of the variable order in GB when searching for new polynomials."); + p.register_unsigned_param("NL_ARITH_GB_THRESHOLD", m_nl_arith_gb_threshold, "Grobner basis computation can be very expensive. This is a threshold on the number of new equalities that can be generated."); + p.register_bool_param("NL_ARITH_BRANCHING", m_nl_arith_branching, "enable/disable branching on integer variables in non linear clusters"); + p.register_unsigned_param("NL_ARITH_ROUNDS", m_nl_arith_rounds, "threshold for number of (nested) final checks for non linear arithmetic."); + p.register_unsigned_param("NL_ARITH_MAX_DEGREE", m_nl_arith_max_degree, "max degree for internalizing new monomials."); + PRIVATE_PARAMS({ + p.register_bool_param("ARITH_FIXNUM", m_arith_fixnum); + p.register_bool_param("ARITH_INT_ONLY", m_arith_int_only); + p.register_bool_param("ARITH_ENUM_CONST_MOD", m_arith_enum_const_mod, "Create axioms for the finite set of equalities for (mod x k) where k is a positive numeral constant"); + p.register_bool_param("ARITH_INT_EQ_BRANCHING", m_arith_int_eq_branching, "Determine branch predicates based on integer equation solving"); + }); + p.register_bool_param("ARITH_EUCLIDEAN_SOLVER", m_arith_euclidean_solver, ""); +} + diff --git a/lib/theory_arith_params.h b/lib/theory_arith_params.h new file mode 100644 index 000000000..290049037 --- /dev/null +++ b/lib/theory_arith_params.h @@ -0,0 +1,158 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_arith_params.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-05-06. + +Revision History: + +--*/ +#ifndef _THEORY_ARITH_PARAMS_H_ +#define _THEORY_ARITH_PARAMS_H_ + +#include"ini_file.h" + +enum arith_solver_id { + AS_NO_ARITH, + AS_DIFF_LOGIC, + AS_ARITH, + AS_DENSE_DIFF_LOGIC +}; + +enum bound_prop_mode { + BP_NONE, + BP_SIMPLE, // only used for implying literals + BP_REFINE // refine known bounds +}; + +enum arith_prop_strategy { + ARITH_PROP_AGILITY, + ARITH_PROP_PROPORTIONAL +}; + +enum arith_pivot_strategy { + ARITH_PIVOT_SMALLEST, + ARITH_PIVOT_GREATEST_ERROR, + ARITH_PIVOT_LEAST_ERROR +}; + +struct theory_arith_params { + arith_solver_id m_arith_mode; + bool m_arith_auto_config_simplex; //!< force simplex solver in auto_config + unsigned m_arith_blands_rule_threshold; + bool m_arith_propagate_eqs; + bound_prop_mode m_arith_bound_prop; + bool m_arith_stronger_lemmas; + bool m_arith_skip_rows_with_big_coeffs; + unsigned m_arith_max_lemma_size; + unsigned m_arith_small_lemma_size; + bool m_arith_reflect; + bool m_arith_ignore_int; + unsigned m_arith_lazy_pivoting_lvl; + unsigned m_arith_random_seed; + bool m_arith_random_initial_value; + int m_arith_random_lower; + int m_arith_random_upper; + bool m_arith_adaptive; + double m_arith_adaptive_assertion_threshold; + double m_arith_adaptive_propagation_threshold; + bool m_arith_dump_lemmas; + bool m_arith_eager_eq_axioms; + unsigned m_arith_branch_cut_ratio; + bool m_arith_int_eq_branching; + bool m_arith_enum_const_mod; + + bool m_arith_gcd_test; + bool m_arith_eager_gcd; + bool m_arith_adaptive_gcd; + unsigned m_arith_propagation_threshold; + + arith_pivot_strategy m_arith_pivot_strategy; + + // used in diff-logic + bool m_arith_add_binary_bounds; + arith_prop_strategy m_arith_propagation_strategy; + + // used arith_eq_adapter + bool m_arith_eq_bounds; + bool m_arith_lazy_adapter; + + // performance debugging flags + bool m_arith_fixnum; + bool m_arith_int_only; + + // non linear support + bool m_nl_arith; + bool m_nl_arith_gb; + unsigned m_nl_arith_gb_threshold; + bool m_nl_arith_gb_eqs; + bool m_nl_arith_gb_perturbate; + unsigned m_nl_arith_max_degree; + bool m_nl_arith_branching; + unsigned m_nl_arith_rounds; + + // euclidean solver for tighting bounds + bool m_arith_euclidean_solver; + + + theory_arith_params(): + m_arith_mode(AS_ARITH), + m_arith_auto_config_simplex(false), + m_arith_blands_rule_threshold(1000), + m_arith_propagate_eqs(true), + m_arith_bound_prop(BP_REFINE), + m_arith_stronger_lemmas(true), + m_arith_skip_rows_with_big_coeffs(true), + m_arith_max_lemma_size(128), + m_arith_small_lemma_size(16), + m_arith_reflect(true), + m_arith_ignore_int(false), + m_arith_lazy_pivoting_lvl(0), + m_arith_random_seed(0), + m_arith_random_initial_value(false), + m_arith_random_lower(-1000), + m_arith_random_upper(1000), + m_arith_adaptive(false), + m_arith_adaptive_assertion_threshold(0.2), + m_arith_adaptive_propagation_threshold(0.4), + m_arith_dump_lemmas(false), + m_arith_eager_eq_axioms(true), + m_arith_branch_cut_ratio(2), + m_arith_int_eq_branching(false), + m_arith_enum_const_mod(false), + m_arith_gcd_test(true), + m_arith_eager_gcd(false), + m_arith_adaptive_gcd(false), + m_arith_propagation_threshold(UINT_MAX), + m_arith_pivot_strategy(ARITH_PIVOT_SMALLEST), + m_arith_add_binary_bounds(false), + m_arith_propagation_strategy(ARITH_PROP_PROPORTIONAL), + m_arith_eq_bounds(false), + m_arith_lazy_adapter(false), + m_arith_fixnum(false), + m_arith_int_only(false), + m_nl_arith(true), + m_nl_arith_gb(true), + m_nl_arith_gb_threshold(512), + m_nl_arith_gb_eqs(false), + m_nl_arith_gb_perturbate(true), + m_nl_arith_max_degree(6), + m_nl_arith_branching(true), + m_nl_arith_rounds(1024), + m_arith_euclidean_solver(false) { + } + + void register_params(ini_params & p); +}; + +#endif /* _THEORY_ARITH_PARAMS_H_ */ + diff --git a/lib/theory_arith_pp.h b/lib/theory_arith_pp.h new file mode 100644 index 000000000..274cd5499 --- /dev/null +++ b/lib/theory_arith_pp.h @@ -0,0 +1,516 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_arith_pp.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-05-05. + +Revision History: + +--*/ +#ifndef _THEORY_ARITH_PP_H_ +#define _THEORY_ARITH_PP_H_ + +#include"theory_arith.h" +#include"ast_smt_pp.h" +#include"stats.h" + +namespace smt { + template + void theory_arith::collect_statistics(::statistics & st) const { + st.update("arith conflicts", m_stats.m_conflicts); + st.update("add rows", m_stats.m_add_rows); + st.update("pivots", m_stats.m_pivots); + st.update("assert lower", m_stats.m_assert_lower); + st.update("assert upper", m_stats.m_assert_upper); + st.update("assert diseq", m_stats.m_assert_diseq); + st.update("bound prop", m_stats.m_bound_props); + st.update("fixed eqs", m_stats.m_fixed_eqs); + st.update("offset eqs", m_stats.m_offset_eqs); + st.update("gcd tests", m_stats.m_gcd_tests); + st.update("ineq splits", m_stats.m_branches); + st.update("gomory cuts", m_stats.m_gomory_cuts); + st.update("max-min", m_stats.m_max_min); + st.update("grobner", m_stats.m_gb_compute_basis); + st.update("pseudo nonlinear", m_stats.m_nl_linear); + st.update("nonlinear bounds", m_stats.m_nl_bounds); + st.update("nonlinear horner", m_stats.m_nl_cross_nested); + m_arith_eq_adapter.collect_statistics(st); + } + + template + void theory_arith::display(std::ostream & out) const { + out << "Theory arithmetic:\n"; + display_vars(out); + display_nl_monomials(out); + display_rows(out, true); + display_rows(out, false); + display_atoms(out); + display_asserted_atoms(out); + } + + template + void theory_arith::display_nl_monomials(std::ostream & out) const { + if (m_nl_monomials.empty()) + return; + out << "non linear monomials:\n"; + svector::const_iterator it = m_nl_monomials.begin(); + svector::const_iterator end = m_nl_monomials.end(); + for (; it != end; ++it) + display_var(out, *it); + } + + template + void theory_arith::display_row(std::ostream & out, unsigned r_id, bool compact) const { + out << r_id << " "; + display_row(out, m_rows[r_id], compact); + } + + template + void theory_arith::display_row(std::ostream & out, row const & r, bool compact) const { + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + out << "(v" << r.get_base_var() << ") : "; + bool first = true; + for (; it != end; ++it) { + if (!it->is_dead()) { + if (first) + first = false; + else + out << " + "; + theory_var s = it->m_var; + numeral const & c = it->m_coeff; + if (!c.is_one()) + out << c << "*"; + if (compact) { + out << "v" << s; + if (is_fixed(s)) { + out << ":" << lower(s)->get_value(); + } + } + else + display_var_flat_def(out, s); + } + } + out << "\n"; + } + + + template + void theory_arith::display_rows(std::ostream & out, bool compact) const { + if (compact) + out << "rows (compact view):\n"; + else + out << "rows (expanded view):\n"; + unsigned num = m_rows.size(); + for (unsigned r_id = 0; r_id < num; r_id++) { + if (m_rows[r_id].m_base_var != null_theory_var) { + display_row(out, r_id, compact); + } + } + } + + template + void theory_arith::display_row_shape(std::ostream & out, row const & r) const { + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) { + numeral const & c = it->m_coeff; + if (c.is_one()) + out << "1"; + else if (c.is_minus_one()) + out << "-"; + else if (c.is_int() && c.to_rational().is_small()) + out << "i"; + else if (c.is_int() && !c.to_rational().is_small()) + out << "I"; + else if (c.to_rational().is_small()) + out << "r"; + else + out << "R"; + } + } + out << "\n"; + } + + template + bool theory_arith::is_one_minus_one_row(row const & r) const { + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) { + numeral const & c = it->m_coeff; + if (!c.is_one() && !c.is_minus_one()) + return false; + } + } + return true; + } + + template + void theory_arith::display_rows_shape(std::ostream & out) const { + unsigned num = m_rows.size(); + unsigned num_trivial = 0; + for (unsigned r_id = 0; r_id < num; r_id++) { + row const & r = m_rows[r_id]; + if (r.m_base_var != null_theory_var) { + if (is_one_minus_one_row(r)) + num_trivial++; + else + display_row_shape(out, r); + } + } + out << "num. trivial: " << num_trivial << "\n"; + } + + template + void theory_arith::display_rows_bignums(std::ostream & out) const { + unsigned num = m_rows.size(); + for (unsigned r_id = 0; r_id < num; r_id++) { + row const & r = m_rows[r_id]; + if (r.m_base_var != null_theory_var) { + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) { + numeral const & c = it->m_coeff; + if (c.to_rational().is_big()) { + std::string str = c.to_rational().to_string(); + if (str.length() > 48) + out << str << "\n"; + } + } + } + } + } + } + + template + void theory_arith::display_rows_stats(std::ostream & out) const { + unsigned num_vars = get_num_vars(); + unsigned num_rows = 0; + unsigned num_non_zeros = 0; + unsigned num_ones = 0; + unsigned num_minus_ones = 0; + unsigned num_small_ints = 0; + unsigned num_big_ints = 0; + unsigned num_small_rats = 0; + unsigned num_big_rats = 0; + for (unsigned r_id = 0; r_id < m_rows.size(); r_id++) { + row const & r = m_rows[r_id]; + if (r.m_base_var != null_theory_var) { + num_rows++; + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) { + if (!it->is_dead()) { + numeral const & c = it->m_coeff; + num_non_zeros++; + if (c.is_one()) + num_ones++; + else if (c.is_minus_one()) + num_minus_ones++; + else if (c.is_int() && c.to_rational().is_small()) + num_small_ints++; + else if (c.is_int() && !c.to_rational().is_small()) + num_big_ints++; + else if (c.to_rational().is_small()) + num_small_rats++; + else + num_big_rats++; + } + } + } + } + out << "A: " << num_rows << " X " << num_vars << "\n"; + out << "avg. row: " << num_non_zeros / num_rows << ", num. non zeros: " << num_non_zeros << "\n"; + unsigned spc = 6; + out.width(spc); + out << 1 << "|"; + out.width(spc); + out << -1 << "|"; + out.width(spc); + out << "i"; + out << "|"; + out.width(spc); + out << "I"; + out << "|"; + out.width(spc); + out << "r"; + out << "|"; + out.width(spc); + out << "R"; + out << "\n"; + + out.width(spc); + out << num_ones << "|"; + out.width(spc); + out << num_minus_ones << "|"; + out.width(spc); + out << num_small_ints; + out << "|"; + out.width(spc); + out << num_big_ints; + out << "|"; + out.width(spc); + out << num_small_rats; + out << "|"; + out.width(spc); + out << num_big_rats; + out << "\n"; + } + + template + void theory_arith::display_row_info(std::ostream & out, unsigned r_id) const { + out << r_id << " "; + display_row_info(out, m_rows[r_id]); + } + + template + void theory_arith::display_row_info(std::ostream & out, row const & r) const { + display_row(out, r, true); + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) + if (!it->is_dead()) + display_var(out, it->m_var); + } + + /** + \brief Display row after substituting fixed variables. + */ + template + void theory_arith::display_simplified_row(std::ostream & out, row const & r) const { + bool has_rat_coeff = false; + numeral k; + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + out << "(v" << r.get_base_var() << ") : "; + bool first = true; + for (; it != end; ++it) { + if (it->is_dead()) + continue; + theory_var v = it->m_var; + numeral const & c = it->m_coeff; + if (is_fixed(v)) { + k += c * lower_bound(v).get_rational(); + continue; + } + if (!c.is_int()) + has_rat_coeff = true; + if (first) + first = false; + else + out << " + "; + if (!c.is_one()) + out << c << "*"; + out << "v" << v; + } + if (!k.is_zero()) { + if (!first) + out << " + "; + out << k; + } + out << "\n"; + if (has_rat_coeff) { + typename vector::const_iterator it = r.begin_entries(); + typename vector::const_iterator end = r.end_entries(); + for (; it != end; ++it) + if (!it->is_dead() && (is_base(it->m_var) || (!is_fixed(it->m_var) && (lower(it->m_var) || upper(it->m_var))))) + display_var(out, it->m_var); + } + } + + template + void theory_arith::display_var(std::ostream & out, theory_var v) const { + out << "v"; + out.width(4); + out << std::left << v; + out << " #"; + out.width(4); + out << get_enode(v)->get_owner_id(); + out << std::right; + out << " lo:"; + out.width(10); + if (lower(v)) { + out << lower(v)->get_value(); + } + else { + out << "-oo"; + } + out << ", up:"; + out.width(10); + if (upper(v)) { + out << upper(v)->get_value(); + } + else { + out << "oo"; + } + out << ", value: "; + out.width(10); + out << get_value(v); + out << ", occs: "; + out.width(4); + out << m_columns[v].size(); + out << ", atoms: "; + out.width(4); + out << m_var_occs[v].size(); + out << (is_int(v) ? ", int " : ", real"); + switch (get_var_kind(v)) { + case NON_BASE: + out << ", non-base "; + break; + case QUASI_BASE: + out << ", quasi-base"; + break; + case BASE: + out << ", base "; + break; + } + out << ", shared: " << get_context().is_shared(get_enode(v)); + out << ", unassigned: " << m_unassigned_atoms[v]; + out << ", rel: " << get_context().is_relevant(get_enode(v)); + out << ", def: "; + display_var_flat_def(out, v); + out << "\n"; + } + + template + void theory_arith::display_vars(std::ostream & out) const { + out << "vars:\n"; + int n = get_num_vars(); + for (theory_var v = 0; v < n; v++) + display_var(out, v); + } + + template + void theory_arith::display_bound(std::ostream & out, bound * b, unsigned indent) const { + for (unsigned i = 0; i < indent; i++) out << " "; + theory_var v = b->get_var(); + enode * e = get_enode(v); + out << "v" << v << " #" << e->get_owner_id() << " " << (b->get_bound_kind() == B_LOWER ? ">=" : "<=") << " " << b->get_value() << "\n"; + } + + template + void theory_arith::display_atoms(std::ostream & out) const { + out << "atoms:\n"; + for (unsigned i = 0; i < m_atoms.size(); i++) + display_atom(out, m_atoms[i], false); + } + + template + void theory_arith::display_asserted_atoms(std::ostream & out) const { + out << "asserted atoms:\n"; + for (unsigned i = 0; i < m_asserted_qhead; i++) { + bound * b = m_asserted_bounds[i]; + if (b->is_atom()) + display_atom(out, static_cast(b), true); + } + if (m_asserted_qhead < m_asserted_bounds.size()) { + out << "delayed atoms:\n"; + for (unsigned i = m_asserted_qhead; i < m_asserted_bounds.size(); i++) { + bound * b = m_asserted_bounds[i]; + if (b->is_atom()) + display_atom(out, static_cast(b), true); + } + } + } + + template + void theory_arith::display_atom(std::ostream & out, atom * a, bool show_sign) const { + theory_var v = a->get_var(); + numeral const & k = a->get_k(); + enode * e = get_enode(v); + if (show_sign) { + if (!a->is_true()) + out << "not "; + else + out << " "; + } + out << "v"; + out.width(3); + out << std::left << v << " #"; + out.width(3); + out << e->get_owner_id(); + out << std::right; + out << " "; + if (a->get_atom_kind() == A_LOWER) + out << ">="; + else + out << "<="; + out << " "; + out.width(6); + out << k << " "; + display_var_flat_def(out, v); + out << "\n"; + } + + template + void theory_arith::display_bounds_in_smtlib(std::ostream & out) const { + ast_manager & m = get_manager(); + ast_smt_pp pp(m); + pp.set_benchmark_name("lemma"); + int n = get_num_vars(); + for (theory_var v = 0; v < n; v++) { + expr * n = get_enode(v)->get_owner(); + if (is_fixed(v)) { + inf_numeral k_inf = lower_bound(v); + rational k = k_inf.get_rational().to_rational(); + expr_ref eq(m); + eq = m.mk_eq(n, m_util.mk_numeral(k, is_int(v))); + pp.add_assumption(eq); + } + else { + if (lower(v) != 0) { + inf_numeral k_inf = lower_bound(v); + rational k = k_inf.get_rational().to_rational(); + expr_ref ineq(m); + if (k_inf.get_infinitesimal().is_zero()) + ineq = m_util.mk_le(m_util.mk_numeral(k, is_int(v)), n); + else + ineq = m_util.mk_lt(m_util.mk_numeral(k, is_int(v)), n); + pp.add_assumption(ineq); + } + if (upper(v) != 0) { + inf_numeral k_inf = upper_bound(v); + rational k = k_inf.get_rational().to_rational(); + expr_ref ineq(m); + if (k_inf.get_infinitesimal().is_zero()) + ineq = m_util.mk_le(n, m_util.mk_numeral(k, is_int(v))); + else + ineq = m_util.mk_lt(n, m_util.mk_numeral(k, is_int(v))); + pp.add_assumption(ineq); + } + } + } + pp.display(out, m.mk_true()); + } + + template + void theory_arith::display_bounds_in_smtlib() const { + char buffer[128]; + static int id = 0; +#ifdef _WINDOWS + sprintf_s(buffer, ARRAYSIZE(buffer), "arith_%d.smt", id); +#else + sprintf(buffer, "arith_%d.smt", id); +#endif + std::ofstream out(buffer); + display_bounds_in_smtlib(out); + out.close(); + id++; + } + +}; + +#endif /* _THEORY_ARITH_PP_H_ */ + diff --git a/lib/theory_array.cpp b/lib/theory_array.cpp new file mode 100644 index 000000000..2e42a7abf --- /dev/null +++ b/lib/theory_array.cpp @@ -0,0 +1,479 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_array.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-01. + +Revision History: + +--*/ +#include"smt_context.h" +#include"theory_array.h" +#include"ast_ll_pp.h" +#include"stats.h" + +namespace smt { + + theory_array::theory_array(ast_manager & m, theory_array_params & params): + theory_array_base(m), + m_params(params), + m_find(*this), + m_trail_stack(*this), + m_final_check_idx(0) { + } + + theory_array::~theory_array() { + std::for_each(m_var_data.begin(), m_var_data.end(), delete_proc()); + m_var_data.reset(); + } + + void theory_array::init(context * ctx) { + theory_array_base::init(ctx); + if (!ctx->relevancy()) + m_params.m_array_laziness = 0; + } + + void theory_array::merge_eh(theory_var v1, theory_var v2, theory_var, theory_var) { + // v1 is the new root + TRACE("array", tout << "merging v" << v1 << " v" << v2 << "\n"; display_var(tout, v1);); + SASSERT(v1 == find(v1)); + var_data * d1 = m_var_data[v1]; + var_data * d2 = m_var_data[v2]; + if (!d1->m_prop_upward && d2->m_prop_upward) + set_prop_upward(v1); + ptr_vector::iterator it = d2->m_stores.begin(); + ptr_vector::iterator end = d2->m_stores.end(); + for (; it != end; ++it) + add_store(v1, *it); + it = d2->m_parent_stores.begin(); + end = d2->m_parent_stores.end(); + for (; it != end; ++it) + add_parent_store(v1, *it); + it = d2->m_parent_selects.begin(); + end = d2->m_parent_selects.end(); + for (; it != end; ++it) + add_parent_select(v1, *it); + TRACE("array", tout << "after merge\n"; display_var(tout, v1);); + } + + void theory_array::unmerge_eh(theory_var v1, theory_var v2) { + // do nothing + } + + theory_var theory_array::mk_var(enode * n) { + theory_var r = theory_array_base::mk_var(n); + theory_var r2 = m_find.mk_var(); + SASSERT(r == r2); + SASSERT(r == static_cast(m_var_data.size())); + m_var_data.push_back(alloc(var_data)); + var_data * d = m_var_data[r]; + TRACE("array", tout << mk_bounded_pp(n->get_owner(), get_manager()) << "\nis_array: " << is_array_sort(n) << ", is_select: " << is_select(n) << + ", is_store: " << is_store(n) << "\n";); + d->m_is_array = is_array_sort(n); + if (d->m_is_array) + register_sort(get_manager().get_sort(n->get_owner())); + d->m_is_select = is_select(n); + if (is_store(n)) + d->m_stores.push_back(n); + get_context().attach_th_var(n, this, r); + if (m_params.m_array_laziness <= 1 && is_store(n)) + instantiate_axiom1(n); + return r; + } + + void theory_array::add_parent_select(theory_var v, enode * s) { + if (m_params.m_array_cg && !s->is_cgr()) + return; + SASSERT(is_select(s)); + v = find(v); + var_data * d = m_var_data[v]; + d->m_parent_selects.push_back(s); + m_trail_stack.push(push_back_trail(d->m_parent_selects)); + ptr_vector::iterator it = d->m_stores.begin(); + ptr_vector::iterator end = d->m_stores.end(); + for (; it != end; ++it) { + instantiate_axiom2a(s, *it); + } + if (!m_params.m_array_weak && !m_params.m_array_delay_exp_axiom && d->m_prop_upward) { + it = d->m_parent_stores.begin(); + end = d->m_parent_stores.end(); + for (; it != end; ++it) { + enode * store = *it; + SASSERT(is_store(store)); + if (!m_params.m_array_cg || store->is_cgr()) + instantiate_axiom2b(s, store); + } + } + } + + void theory_array::add_parent_store(theory_var v, enode * s) { + if (m_params.m_array_cg && !s->is_cgr()) + return; + SASSERT(is_store(s)); + v = find(v); + var_data * d = m_var_data[v]; + d->m_parent_stores.push_back(s); + m_trail_stack.push(push_back_trail(d->m_parent_stores)); + if (!m_params.m_array_weak && !m_params.m_array_delay_exp_axiom && d->m_prop_upward) { + ptr_vector::iterator it = d->m_parent_selects.begin(); + ptr_vector::iterator end = d->m_parent_selects.end(); + for (; it != end; ++it) + if (!m_params.m_array_cg || (*it)->is_cgr()) + instantiate_axiom2b(*it, s); + } + } + + bool theory_array::instantiate_axiom2b_for(theory_var v) { + bool result = false; + var_data * d = m_var_data[v]; + ptr_vector::iterator it = d->m_parent_stores.begin(); + ptr_vector::iterator end = d->m_parent_stores.end(); + for (; it != end; ++it) { + ptr_vector::iterator it2 = d->m_parent_selects.begin(); + ptr_vector::iterator end2 = d->m_parent_selects.end(); + for (; it2 != end2; ++it2) + if (instantiate_axiom2b(*it2, *it)) + result = true; + } + return result; + } + + /** + \brief Mark v for upward propagation. That is, enables the propagation of select(v, i) to store(v,j,k). + */ + void theory_array::set_prop_upward(theory_var v) { + if (m_params.m_array_weak) + return; + v = find(v); + var_data * d = m_var_data[v]; + if (!d->m_prop_upward) { + TRACE("array", tout << "#" << v << "\n";); + m_trail_stack.push(reset_flag_trail(d->m_prop_upward)); + d->m_prop_upward = true; + if (!m_params.m_array_delay_exp_axiom) + instantiate_axiom2b_for(v); + ptr_vector::iterator it = d->m_stores.begin(); + ptr_vector::iterator end = d->m_stores.end(); + for (; it != end; ++it) + set_prop_upward(*it); + } + } + + void theory_array::set_prop_upward(enode * store) { + if (is_store(store)) { + theory_var st_v = store->get_arg(0)->get_th_var(get_id()); + set_prop_upward(st_v); + } + } + + void theory_array::set_prop_upward(theory_var v, var_data* d) { + unsigned sz = d->m_stores.size(); + for (unsigned i = 0; i < sz; ++i) { + set_prop_upward(d->m_stores[i]); + } + } + + + /** + \brief Return the size of the equivalence class for array terms + that can be expressed as \lambda i : Index . [.. (select a i) ..] + */ + unsigned theory_array::get_lambda_equiv_size(theory_var v, var_data* d) { + return d->m_stores.size(); + } + + void theory_array::add_store(theory_var v, enode * s) { + if (m_params.m_array_cg && !s->is_cgr()) + return; + SASSERT(is_store(s)); + v = find(v); + var_data * d = m_var_data[v]; + unsigned lambda_equiv_class_size = get_lambda_equiv_size(v, d); + if (m_params.m_array_always_prop_upward || lambda_equiv_class_size >= 1) { + set_prop_upward(v, d); + } + d->m_stores.push_back(s); + m_trail_stack.push(push_back_trail(d->m_stores)); + ptr_vector::iterator it = d->m_parent_selects.begin(); + ptr_vector::iterator end = d->m_parent_selects.end(); + for (; it != end; ++it) { + SASSERT(is_select(*it)); + instantiate_axiom2a(*it, s); + } + if (m_params.m_array_always_prop_upward || lambda_equiv_class_size >= 1) + set_prop_upward(s); + } + + void theory_array::instantiate_axiom1(enode * store) { + TRACE("array", tout << "axiom 1:\n" << mk_bounded_pp(store->get_owner(), get_manager()) << "\n";); + SASSERT(is_store(store)); + m_stats.m_num_axiom1++; + assert_store_axiom1(store); + } + + void theory_array::instantiate_axiom2a(enode * select, enode * store) { + TRACE("array", tout << "axiom 2a: #" << select->get_owner_id() << " #" << store->get_owner_id() << "\n";); + SASSERT(is_select(select)); + SASSERT(is_store(store)); + if (assert_store_axiom2(store, select)) + m_stats.m_num_axiom2a++; + } + + bool theory_array::instantiate_axiom2b(enode * select, enode * store) { + TRACE("array_axiom2b", tout << "axiom 2b: #" << select->get_owner_id() << " #" << store->get_owner_id() << "\n";); + SASSERT(is_select(select)); + SASSERT(is_store(store)); + if (assert_store_axiom2(store, select)) { + m_stats.m_num_axiom2b++; + return true; + } + return false; + } + + void theory_array::instantiate_extensionality(enode * a1, enode * a2) { + TRACE("array", tout << "extensionality: #" << a1->get_owner_id() << " #" << a2->get_owner_id() << "\n";); + SASSERT(is_array_sort(a1)); + SASSERT(is_array_sort(a2)); + if (m_params.m_array_extensional && assert_extensionality(a1, a2)) + m_stats.m_num_extensionality++; + } + + bool theory_array::internalize_atom(app * atom, bool) { + return internalize_term(atom); + } + + // + // Internalize the term. If it has already been internalized, return false. + // + bool theory_array::internalize_term_core(app * n) { + TRACE("array_bug", tout << mk_bounded_pp(n, get_manager()) << "\n";); + context & ctx = get_context(); + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) + ctx.internalize(n->get_arg(i), false); + if (ctx.e_internalized(n)) { + return false; + } + enode * e = ctx.mk_enode(n, false, false, true); + if (!is_attached_to_var(e)) + mk_var(e); + + if (get_manager().is_bool(n)) { + bool_var bv = ctx.mk_bool_var(n); + ctx.set_var_theory(bv, get_id()); + ctx.set_enode_flag(bv, true); + } + return true; + } + + bool theory_array::internalize_term(app * n) { + if (!is_store(n) && !is_select(n)) { + if (!is_array_ext(n)) + found_unsupported_op(n); + return false; + } + TRACE("array_bug", tout << mk_bounded_pp(n, get_manager()) << "\n";); + if (!internalize_term_core(n)) { + return true; + } + context & ctx = get_context(); + enode * arg0 = ctx.get_enode(n->get_arg(0)); + if (!is_attached_to_var(arg0)) + mk_var(arg0); + + + if (m_params.m_array_laziness == 0) { + theory_var v_arg = arg0->get_th_var(get_id()); + + SASSERT(v_arg != null_theory_var); + if (is_select(n)) { + add_parent_select(v_arg, ctx.get_enode(n)); + } + else if (is_store(n)) { + add_parent_store(v_arg, ctx.get_enode(n)); + } + } + + return true; + } + + void theory_array::apply_sort_cnstr(enode * n, sort * s) { + SASSERT(is_array_sort(s)); + if (!is_attached_to_var(n)) + mk_var(n); + } + + void theory_array::new_eq_eh(theory_var v1, theory_var v2) { + m_find.merge(v1, v2); + } + + void theory_array::new_diseq_eh(theory_var v1, theory_var v2) { + v1 = find(v1); + v2 = find(v2); + var_data * d1 = m_var_data[v1]; + if (d1->m_is_array) { + SASSERT(m_var_data[v2]->m_is_array); + TRACE("ext", tout << "extensionality:\n" << mk_bounded_pp(get_enode(v1)->get_owner(), get_manager(), 5) << "\n" << + mk_bounded_pp(get_enode(v2)->get_owner(), get_manager(), 5) << "\n";); + instantiate_extensionality(get_enode(v1), get_enode(v2)); + } + } + + void theory_array::relevant_eh(app * n) { + if (m_params.m_array_laziness == 0) + return; + if (!is_store(n) && !is_select(n)) + return; + context & ctx = get_context(); + enode * arg = ctx.get_enode(n->get_arg(0)); + theory_var v_arg = arg->get_th_var(get_id()); + SASSERT(v_arg != null_theory_var); + if (is_select(n)) { + add_parent_select(v_arg, ctx.get_enode(n)); + } + else { + SASSERT(is_store(n)); + if (m_params.m_array_laziness > 1) + instantiate_axiom1(ctx.get_enode(n)); + add_parent_store(v_arg, ctx.get_enode(n)); + } + } + + void theory_array::push_scope_eh() { + theory_array_base::push_scope_eh(); + m_trail_stack.push_scope(); + } + + void theory_array::pop_scope_eh(unsigned num_scopes) { + m_trail_stack.pop_scope(num_scopes); + unsigned num_old_vars = get_old_num_vars(num_scopes); + std::for_each(m_var_data.begin() + num_old_vars, m_var_data.end(), delete_proc()); + m_var_data.shrink(num_old_vars); + theory_array_base::pop_scope_eh(num_scopes); + SASSERT(m_find.get_num_vars() == m_var_data.size()); + SASSERT(m_find.get_num_vars() == get_num_vars()); + } + + final_check_status theory_array::final_check_eh() { + m_final_check_idx++; + final_check_status r; + if (m_params.m_array_lazy_ieq) { + // Delay the creation of interface equalities... The + // motivation is too give other theories and quantifier + // instantiation to do something useful during final + // check. + if (m_final_check_idx % m_params.m_array_lazy_ieq_delay != 0) { + assert_delayed_axioms(); + r = FC_CONTINUE; + } + else { + if (mk_interface_eqs_at_final_check() == FC_CONTINUE) + r = FC_CONTINUE; + else + r = assert_delayed_axioms(); + } + } + else { + if (m_final_check_idx % 2 == 1) { + if (assert_delayed_axioms() == FC_CONTINUE) + r = FC_CONTINUE; + else + r = mk_interface_eqs_at_final_check(); + } + else { + if (mk_interface_eqs_at_final_check() == FC_CONTINUE) + r = FC_CONTINUE; + else + r = assert_delayed_axioms(); + } + } + TRACE("as_array", tout << "m_found_unsupported_op: " << m_found_unsupported_op << " " << r << "\n";); + if (r == FC_DONE && m_found_unsupported_op) + r = FC_GIVEUP; + return r; + } + + final_check_status theory_array::assert_delayed_axioms() { + if (!m_params.m_array_delay_exp_axiom) + return FC_DONE; + final_check_status r = FC_DONE; + unsigned num_vars = get_num_vars(); + for (unsigned v = 0; v < num_vars; v++) { + var_data * d = m_var_data[v]; + if (d->m_prop_upward && instantiate_axiom2b_for(v)) + r = FC_CONTINUE; + } + return r; + } + + final_check_status theory_array::mk_interface_eqs_at_final_check() { + unsigned n = mk_interface_eqs(); + m_stats.m_num_eq_splits += n; + if (n > 0) + return FC_CONTINUE; + return FC_DONE; + } + + void theory_array::reset_eh() { + m_trail_stack.reset(); + std::for_each(m_var_data.begin(), m_var_data.end(), delete_proc()); + m_var_data.reset(); + theory_array_base::reset_eh(); + } + + void theory_array::display(std::ostream & out) const { + out << "Theory array:\n"; + unsigned num_vars = get_num_vars(); + for (unsigned v = 0; v < num_vars; v++) { + display_var(out, v); + } + } + + // TODO: move to another file + void theory_array::display_ids(std::ostream & out, unsigned n, enode * const * v) { + for (unsigned i = 0; i < n; i++) { + if (i > 0) out << " "; + out << "#" << v[i]->get_owner_id(); + } + } + + void theory_array::display_var(std::ostream & out, theory_var v) const { + var_data const * d = m_var_data[v]; + out << "v"; + out.width(4); + out << std::left << v; + out << " #"; + out.width(4); + out << get_enode(v)->get_owner_id() << " -> #"; + out.width(4); + out << get_enode(find(v))->get_owner_id(); + out << std::right; + out << " is_array: " << d->m_is_array << " is_select: " << d->m_is_select << " upward: " << d->m_prop_upward; + out << " stores: {"; + display_ids(out, d->m_stores.size(), d->m_stores.c_ptr()); + out << "} p_stores: {"; + display_ids(out, d->m_parent_stores.size(), d->m_parent_stores.c_ptr()); + out << "} p_selects: {"; + display_ids(out, d->m_parent_selects.size(), d->m_parent_selects.c_ptr()); + out << "}"; + out << "\n"; + } + + void theory_array::collect_statistics(::statistics & st) const { + st.update("array ax1", m_stats.m_num_axiom1); + st.update("array ax2", m_stats.m_num_axiom2a); + st.update("array exp ax2", m_stats.m_num_axiom2b); + st.update("array ext ax", m_stats.m_num_extensionality); + st.update("array splits", m_stats.m_num_eq_splits); + } + +}; diff --git a/lib/theory_array.h b/lib/theory_array.h new file mode 100644 index 000000000..8778ffb2c --- /dev/null +++ b/lib/theory_array.h @@ -0,0 +1,117 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_array.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-01. + +Revision History: + +--*/ +#ifndef _THEORY_ARRAY_H_ +#define _THEORY_ARRAY_H_ + +#include"theory_array_base.h" +#include"theory_array_params.h" +#include"union_find.h" + +namespace smt { + + struct theory_array_stats { + unsigned m_num_axiom1, m_num_axiom2a, m_num_axiom2b, m_num_extensionality, m_num_eq_splits; + unsigned m_num_map_axiom, m_num_default_map_axiom; + unsigned m_num_select_const_axiom, m_num_default_store_axiom, m_num_default_const_axiom, m_num_default_as_array_axiom; + unsigned m_num_select_as_array_axiom; + void reset() { memset(this, 0, sizeof(theory_array_stats)); } + theory_array_stats() { reset(); } + }; + + class theory_array : public theory_array_base { + protected: + typedef trail_stack th_trail_stack; + typedef union_find th_union_find; + + struct var_data { + ptr_vector m_stores; + ptr_vector m_parent_selects; + ptr_vector m_parent_stores; + bool m_prop_upward; + bool m_is_array; + bool m_is_select; + var_data():m_prop_upward(false), m_is_array(false), m_is_select(false) {} + }; + ptr_vector m_var_data; + theory_array_params & m_params; + theory_array_stats m_stats; + th_union_find m_find; + th_trail_stack m_trail_stack; + unsigned m_final_check_idx; + + virtual void init(context * ctx); + virtual theory_var mk_var(enode * n); + virtual bool internalize_atom(app * atom, bool gate_ctx); + virtual bool internalize_term(app * term); + virtual void apply_sort_cnstr(enode * n, sort * s); + virtual void new_eq_eh(theory_var v1, theory_var v2); + virtual void new_diseq_eh(theory_var v1, theory_var v2); + virtual void relevant_eh(app * n); + virtual void push_scope_eh(); + virtual void pop_scope_eh(unsigned num_scopes); + virtual final_check_status final_check_eh(); + virtual void reset_eh(); + virtual void init_search_eh() { m_final_check_idx = 0; } + + virtual void set_prop_upward(theory_var v); + virtual void set_prop_upward(enode* n); + virtual void set_prop_upward(theory_var v, var_data* d); + + virtual unsigned get_lambda_equiv_size(theory_var v, var_data* d); + + theory_var find(theory_var v) const { return m_find.find(v); } + bool is_root(theory_var v) const { return m_find.is_root(v); } + + virtual void add_parent_select(theory_var v, enode * s); + void add_parent_store(theory_var v, enode * s); + void add_store(theory_var v, enode * s); + + bool internalize_term_core(app * term); + + void instantiate_axiom2a(enode * select, enode * store); + bool instantiate_axiom2b(enode * select, enode * store); + void instantiate_axiom1(enode * store); + void instantiate_extensionality(enode * a1, enode * a2); + bool instantiate_axiom2b_for(theory_var v); + + virtual final_check_status assert_delayed_axioms(); + final_check_status mk_interface_eqs_at_final_check(); + + static void display_ids(std::ostream & out, unsigned n, enode * const * v); + public: + theory_array(ast_manager & m, theory_array_params & params); + virtual ~theory_array(); + + virtual theory * mk_fresh(context * new_ctx) { return alloc(theory_array, get_manager(), m_params); } + + virtual char const * get_name() const { return "array"; } + + virtual void display_var(std::ostream & out, theory_var v) const; + virtual void display(std::ostream & out) const; + virtual void collect_statistics(::statistics & st) const; + th_trail_stack & get_trail_stack() { return m_trail_stack; } + virtual void merge_eh(theory_var v1, theory_var v2, theory_var, theory_var); + static void after_merge_eh(theory_var r1, theory_var r2, theory_var v1, theory_var v2) {} + void unmerge_eh(theory_var v1, theory_var v2); + }; + +}; + +#endif /* _THEORY_ARRAY_H_ */ + diff --git a/lib/theory_array_base.cpp b/lib/theory_array_base.cpp new file mode 100644 index 000000000..ca6449d1d --- /dev/null +++ b/lib/theory_array_base.cpp @@ -0,0 +1,926 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_array_base.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-02. + +Revision History: + +--*/ +#include"smt_context.h" +#include"theory_array_base.h" +#include"ast_ll_pp.h" +#include"ast_pp.h" +#include"smt_model_generator.h" +#include"func_interp.h" +#include"ast_smt2_pp.h" + +namespace smt { + + theory_array_base::theory_array_base(ast_manager & m): + theory(m.get_family_id("array")), + m_found_unsupported_op(false) + { + } + + void theory_array_base::found_unsupported_op(expr * n) { + TRACE("theory_array_unsup", tout << mk_ll_pp(n, get_manager()) << "\n";); + if (!m_found_unsupported_op) { + get_context().push_trail(value_trail(m_found_unsupported_op)); + m_found_unsupported_op = true; + } + } + + app * theory_array_base::mk_select(unsigned num_args, expr * const * args) { + app * r = get_manager().mk_app(get_family_id(), OP_SELECT, 0, 0, num_args, args); + TRACE("mk_var_bug", tout << "mk_select: " << r->get_id() << " num_args: " << num_args; + for (unsigned i = 0; i < num_args; i++) tout << " " << args[i]->get_id(); + tout << "\n";); + return r; + } + + app * theory_array_base::mk_store(unsigned num_args, expr * const * args) { + return get_manager().mk_app(get_family_id(), OP_STORE, 0, 0, num_args, args); + } + + app * theory_array_base::mk_default(expr * a) { + sort * s = get_manager().get_sort(a); + unsigned num_params = get_dimension(s); + parameter const* params = s->get_info()->get_parameters(); + return get_manager().mk_app(get_family_id(), OP_ARRAY_DEFAULT, num_params, params, 1, & a); + } + + unsigned theory_array_base::get_dimension(sort * s) const { + SASSERT(s->is_sort_of(get_family_id(), ARRAY_SORT)); + SASSERT(s->get_info()->get_num_parameters() >= 2); + return s->get_info()->get_num_parameters()-1; + } + + void theory_array_base::assert_axiom(unsigned num_lits, literal * lits) { + context & ctx = get_context(); + TRACE("array_axiom", + tout << "literals:\n"; + for (unsigned i = 0; i < num_lits; ++i) { + expr * e = ctx.bool_var2expr(lits[i].var()); + if (lits[i].sign()) + tout << "not "; + tout << mk_pp(e, get_manager()) << " "; + tout << "\n"; + }); + ctx.mk_th_axiom(get_id(), num_lits, lits); + } + + void theory_array_base::assert_axiom(literal l1, literal l2) { + literal ls[2] = { l1, l2 }; + assert_axiom(2, ls); + } + + void theory_array_base::assert_axiom(literal l) { + assert_axiom(1, &l); + } + + void theory_array_base::assert_store_axiom1_core(enode * e) { + app * n = e->get_owner(); + SASSERT(is_store(n)); + context & ctx = get_context(); + ast_manager & m = get_manager(); + ptr_buffer sel_args; + unsigned num_args = n->get_num_args(); + SASSERT(num_args >= 3); + sel_args.push_back(n); + for (unsigned i = 1; i < num_args - 1; ++i) { + sel_args.push_back(to_app(n->get_arg(i))); + } + expr_ref sel(m); + sel = mk_select(sel_args.size(), sel_args.c_ptr()); + expr * val = n->get_arg(num_args - 1); + TRACE("array", tout << mk_bounded_pp(sel, m) << " = " << mk_bounded_pp(val, m) << "\n";); + if (m.proofs_enabled()) { + literal l(mk_eq(sel, val, true)); + ctx.mark_as_relevant(l); + assert_axiom(l); + } + else { + TRACE("mk_var_bug", tout << "mk_sel: " << sel->get_id() << "\n";); + ctx.internalize(sel, false); + ctx.assign_eq(ctx.get_enode(sel), ctx.get_enode(val), eq_justification::mk_axiom()); + ctx.mark_as_relevant(sel.get()); + } + } + + /** + \brief Assert axiom 2: + + FORALL a, i_i, ..., i_n, j_1, ..., j_n + i_1 /= j_1 => select(store(a, i_1, ..., i_n, v), j_1, ..., j_n) = select(a, j_1, ..., j_n) + and + ... + and + i_n /= j_n => select(store(a, i_1, ..., i_n, v), j_1, ..., j_n) = select(a, j_1, ..., j_n) + */ + void theory_array_base::assert_store_axiom2_core(enode * store, enode * select) { + TRACE("array", tout << "generating axiom2: #" << store->get_owner_id() << " #" << select->get_owner_id() << "\n"; + tout << mk_bounded_pp(store->get_owner(), get_manager()) << "\n" << mk_bounded_pp(select->get_owner(), get_manager()) << "\n";); + SASSERT(is_store(store)); + SASSERT(is_select(select)); + SASSERT(store->get_num_args() == 1 + select->get_num_args()); + + ptr_buffer sel1_args, sel2_args; + context & ctx = get_context(); + ast_manager & m = get_manager(); + enode * a = store->get_arg(0); + enode * const * is = select->get_args() + 1; + enode * const * js = store->get_args() + 1; + unsigned num_args = select->get_num_args() - 1; + sel1_args.push_back(store->get_owner()); + sel2_args.push_back(a->get_owner()); + + for (unsigned i = 0; i < num_args; i++) { + sel1_args.push_back(is[i]->get_owner()); + sel2_args.push_back(is[i]->get_owner()); + } + + expr_ref sel1(m), sel2(m); + bool init = false; + literal conseq = null_literal; + expr * conseq_expr = 0; + + for (unsigned i = 0; i < num_args; i++) { + enode * idx1 = js[i]; + enode * idx2 = is[i]; + + if (idx1->get_root() == idx2->get_root()) { + TRACE("array_bug", tout << "indexes are equal... skipping...\n";); + continue; + } + + if (!init) { + sel1 = mk_select(sel1_args.size(), sel1_args.c_ptr()); + sel2 = mk_select(sel2_args.size(), sel2_args.c_ptr()); + if (sel1 == sel2) { + TRACE("array_bug", tout << "sel1 and sel2 are equal:\n";); + break; + } + init = true; + TRACE("array", tout << mk_bounded_pp(sel1, m) << " " << mk_bounded_pp(sel2, m) << "\n";); + conseq = mk_eq(sel1, sel2, true); + conseq_expr = ctx.bool_var2expr(conseq.var()); + } + + literal ante = mk_eq(idx1->get_owner(), idx2->get_owner(), true); + ctx.mark_as_relevant(ante); + // ctx.force_phase(ante); + ctx.add_rel_watch(~ante, conseq_expr); + // ctx.mark_as_relevant(conseq_expr); + TRACE("array", tout << "asserting axiom2: " << ante << "\n";); + TRACE("array_map_bug", tout << "axiom2:\n"; + tout << mk_ismt2_pp(idx1->get_owner(), m) << "\n=\n" << mk_ismt2_pp(idx2->get_owner(), m); + tout << "\nimplies\n" << mk_ismt2_pp(conseq_expr, m) << "\n";); + assert_axiom(ante, conseq); + } + } + + bool theory_array_base::assert_store_axiom2(enode * store, enode * select) { + unsigned num_args = select->get_num_args(); + unsigned i = 1; + for (; i < num_args; i++) + if (store->get_arg(i)->get_root() != select->get_arg(i)->get_root()) + break; + if (i == num_args) + return false; + if (get_context().add_fingerprint(store, store->get_owner_id(), select->get_num_args() - 1, select->get_args() + 1)) { + TRACE("array", tout << "adding axiom2 to todo queue\n";); + m_axiom2_todo.push_back(std::make_pair(store, select)); + return true; + } + TRACE("array", tout << "axiom already instantiated: #" << store->get_owner_id() << " #" << select->get_owner_id() << "\n";); + return false; + } + + + + + + + func_decl_ref_vector * theory_array_base::register_sort(sort * s_array) { + unsigned dimension = get_dimension(s_array); + func_decl_ref_vector * ext_skolems = 0; + if (!m_sort2skolem.find(s_array, ext_skolems)) { + ast_manager & m = get_manager(); + ext_skolems = alloc(func_decl_ref_vector, m); + for (unsigned i = 0; i < dimension; ++i) { + sort * ext_sk_domain[2] = { s_array, s_array }; + parameter p(i); + func_decl * ext_sk_decl = m.mk_func_decl(get_id(), OP_ARRAY_EXT_SKOLEM, 1, &p, 2, ext_sk_domain); + ext_skolems->push_back(ext_sk_decl); + } + m_sort2skolem.insert(s_array, ext_skolems); + m_sorts_trail.push_back(s_array); + } + return ext_skolems; + } + + bool theory_array_base::value_eq_proc::operator()(enode * n1, enode * n2) const { + SASSERT(n1->get_num_args() == n2->get_num_args()); + unsigned n = n1->get_num_args(); + // skipping first argument of the select. + for(unsigned i = 1; i < n; i++) { + if (n1->get_arg(i)->get_root() != n2->get_arg(i)->get_root()) { + return false; + } + } + return true; + } + + /** + \brief Return true if there is a select(v1', i1) and a select(v2', i2) such that: + v1' = v1, v2' = v2, i1 = i2, select(v1', i1) /= select(v2', i2) in the logical context. + */ + bool theory_array_base::already_diseq(enode * v1, enode * v2) { + context & ctx = get_context(); + enode * r1 = v1->get_root(); + enode * r2 = v2->get_root(); + + if (r1->get_class_size() > r2->get_class_size()) { + std::swap(r1, r2); + } + + m_array_value.reset(); + // populate m_array_value if the select(a, i) parent terms of r1 + enode_vector::const_iterator it = r1->begin_parents(); + enode_vector::const_iterator end = r1->end_parents(); + for (; it != end; ++it) { + enode* parent = *it; + if (parent->is_cgr() && + ctx.is_relevant(parent) && + is_select(parent->get_owner()) && + parent->get_arg(0)->get_root() == r1) { + m_array_value.insert(parent); + } + } + // traverse select(a, i) parent terms of r2 trying to find a match. + it = r2->begin_parents(); + end = r2->end_parents(); + for (; it != end; ++it) { + enode * parent = *it; + enode * other; + if (parent->is_cgr() && + ctx.is_relevant(parent) && + is_select(parent->get_owner()) && + parent->get_arg(0)->get_root() == r2 && + m_array_value.find(parent, other)) { + + if (ctx.is_diseq(parent, other)) { + TRACE("array_ext", tout << "selects are disequal\n";); + return true; + } + } + } + return false; + } + + bool theory_array_base::assert_extensionality(enode * n1, enode * n2) { + context & ctx = get_context(); + if (n1->get_owner_id() > n2->get_owner_id()) + std::swap(n1, n2); + enode * nodes[2] = { n1, n2 }; + if (!ctx.add_fingerprint(this, 0, 2, nodes)) + return false; // axiom was already instantiated + if (already_diseq(n1, n2)) + return false; + m_extensionality_todo.push_back(std::make_pair(n1, n2)); + return true; + } + + void theory_array_base::assert_extensionality_core(enode * n1, enode * n2) { + app * e1 = n1->get_owner(); + app * e2 = n2->get_owner(); + context & ctx = get_context(); + ast_manager & m = get_manager(); + + func_decl_ref_vector * funcs = 0; + sort * s = m.get_sort(e1); + + if (!m_sort2skolem.find(s, funcs)) { + UNREACHABLE(); + return; + } + + unsigned dimension = funcs->size(); + + expr_ref_vector args1(m), args2(m); + args1.push_back(e1); + args2.push_back(e2); + for (unsigned i = 0; i < dimension; i++) { + expr * k = m.mk_app((*funcs)[i].get(), e1, e2); + args1.push_back(k); + args2.push_back(k); + } + expr * sel1 = mk_select(dimension+1, args1.c_ptr()); + expr * sel2 = mk_select(dimension+1, args2.c_ptr()); + TRACE("ext", tout << mk_bounded_pp(sel1, m) << "\n" << mk_bounded_pp(sel2, m) << "\n";); + literal n1_eq_n2 = mk_eq(e1, e2, true); + literal sel1_eq_sel2 = mk_eq(sel1, sel2, true); + ctx.mark_as_relevant(n1_eq_n2); + ctx.mark_as_relevant(sel1_eq_sel2); + assert_axiom(n1_eq_n2, ~sel1_eq_sel2); + } + + bool theory_array_base::can_propagate() { + return !m_axiom1_todo.empty() || !m_axiom2_todo.empty() || !m_extensionality_todo.empty(); + } + + void theory_array_base::propagate() { + while (can_propagate()) { + for (unsigned i = 0; i < m_axiom1_todo.size(); i++) + assert_store_axiom1_core(m_axiom1_todo[i]); + m_axiom1_todo.reset(); + for (unsigned i = 0; i < m_axiom2_todo.size(); i++) + assert_store_axiom2_core(m_axiom2_todo[i].first, m_axiom2_todo[i].second); + m_axiom2_todo.reset(); + for (unsigned i = 0; i < m_extensionality_todo.size(); i++) + assert_extensionality_core(m_extensionality_todo[i].first, m_extensionality_todo[i].second); + m_extensionality_todo.reset(); + } + } + + /** + \brief Return true if v is shared between two different "instances" of the array theory. + It is shared if it is used in more than one role. The possible roles are: array, index, and value. + Example: + (store v i j) <--- v is used as an array + (select A v) <--- v is used as an index + (store A i v) <--- v is used as an value + */ + bool theory_array_base::is_shared(theory_var v) const { + context & ctx = get_context(); + enode * n = get_enode(v); + enode * r = n->get_root(); + bool is_array = false; + bool is_index = false; + bool is_value = false; + int num_roles = 0; +#define SET_ARRAY(arg) if (arg->get_root() == r && !is_array) { is_array = true; num_roles++; } if (num_roles > 1) return true +#define SET_INDEX(arg) if (arg->get_root() == r && !is_index) { is_index = true; num_roles++; } if (num_roles > 1) return true +#define SET_VALUE(arg) if (arg->get_root() == r && !is_value) { is_value = true; num_roles++; } if (num_roles > 1) return true + enode_vector::const_iterator it = r->begin_parents(); + enode_vector::const_iterator end = r->end_parents(); + for (; it != end; ++it) { + enode * parent = *it; + if (!ctx.is_relevant(parent)) + continue; + unsigned num_args = parent->get_num_args(); + if (is_store(parent)) { + SET_ARRAY(parent->get_arg(0)); + for (unsigned i = 1; i < num_args - 1; i++) { + SET_INDEX(parent->get_arg(i)); + } + SET_VALUE(parent->get_arg(num_args - 1)); + } + else if (is_select(parent)) { + SET_ARRAY(parent->get_arg(0)); + for (unsigned i = 1; i < num_args; i++) { + SET_INDEX(parent->get_arg(i)); + } + } + else if (is_const(parent)) { + SET_VALUE(parent->get_arg(0)); + } + } + return false; + } + + void theory_array_base::collect_shared_vars(sbuffer & result) { + TRACE("array_shared", tout << "collecting shared vars...\n";); + context & ctx = get_context(); + ptr_buffer to_unmark; + unsigned num_vars = get_num_vars(); + for (unsigned i = 0; i < num_vars; i++) { + enode * n = get_enode(i); + if (ctx.is_relevant(n) && ctx.is_shared(n)) { + enode * r = n->get_root(); + if (!r->is_marked() && is_array_sort(r)) { + TRACE("array_shared", tout << "new shared var: #" << r->get_owner_id() << "\n";); + r->set_mark(); + to_unmark.push_back(r); + theory_var r_th_var = r->get_th_var(get_id()); + SASSERT(r_th_var != null_theory_var); + result.push_back(r_th_var); + } + } + } + unmark_enodes(to_unmark.size(), to_unmark.c_ptr()); + } + + /** + \brief Create interface variables for shared array variables. + Return the number of new interface equalities. + */ + unsigned theory_array_base::mk_interface_eqs() { + context & ctx = get_context(); + ast_manager & m = get_manager(); + sbuffer roots; + collect_shared_vars(roots); + unsigned result = 0; + sbuffer::iterator it1 = roots.begin(); + sbuffer::iterator end1 = roots.end(); + for (; it1 != end1; ++it1) { + TRACE("array_bug", tout << "mk_interface_eqs: processing: v" << *it1 << "\n";); + theory_var v1 = *it1; + enode * n1 = get_enode(v1); + sort * s1 = m.get_sort(n1->get_owner()); + sbuffer::iterator it2 = it1; + ++it2; + for (; it2 != end1; ++it2) { + theory_var v2 = *it2; + enode * n2 = get_enode(v2); + sort * s2 = m.get_sort(n2->get_owner()); + if (s1 == s2 && !ctx.is_diseq(n1, n2)) { + app * eq = mk_eq_atom(n1->get_owner(), n2->get_owner()); + if (!ctx.b_internalized(eq) || !ctx.is_relevant(eq)) { + result++; + ctx.internalize(eq, true); + ctx.mark_as_relevant(eq); + } + } + } + } + return result; + } + + void theory_array_base::push_scope_eh() { + m_scopes.push_back(scope(m_sorts_trail.size())); + theory::push_scope_eh(); + } + + void theory_array_base::pop_scope_eh(unsigned num_scopes) { + reset_queues(); + scope const & s = m_scopes[m_scopes.size() - num_scopes]; + restore_sorts(s.m_sorts_trail_lim); + m_scopes.shrink(m_scopes.size()-num_scopes); + theory::pop_scope_eh(num_scopes); + } + + void theory_array_base::restore_sorts(unsigned old_size) { + while (m_sorts_trail.size() > old_size) { + sort * s = m_sorts_trail.back(); + func_decl_ref_vector * funcs = 0; + if (m_sort2skolem.find(s, funcs)) { + m_sort2skolem.remove(s); + dealloc(funcs); + } + m_sorts_trail.pop_back(); + } + } + + void theory_array_base::reset_eh() { + reset_queues(); + pop_scope_eh(0); + theory::reset_eh(); + } + + void theory_array_base::reset_queues() { + m_axiom1_todo.reset(); + m_axiom2_todo.reset(); + m_extensionality_todo.reset(); + } + + + void theory_array_base::set_default(theory_var v, enode* n) { + TRACE("array", tout << "set default: " << v << " " << mk_pp(n->get_owner(), get_manager()) << "\n";); + v = mg_find(v); + if (m_defaults[v] == 0) { + m_defaults[v] = n; + } + } + + enode* theory_array_base::get_default(theory_var v) { + return m_defaults[mg_find(v)]; + } + + theory_var theory_array_base::mg_find(theory_var n) { + if (m_parents[n] < 0) { + return n; + } + theory_var n0 = n; + n = m_parents[n0]; + if (m_parents[n] < -1) { + return n; + } + while (m_parents[n] >= 0) { + n = m_parents[n]; + } + // compress path. + while (m_parents[n0] >= 0) { + theory_var n1 = m_parents[n0]; + m_parents[n0] = n; + n0 = n1; + } + return n; + } + + void theory_array_base::mg_merge(theory_var n, theory_var m) { + n = mg_find(n); + m = mg_find(m); + if (n != m) { + SASSERT(m_parents[n] < 0); + SASSERT(m_parents[m] < 0); + if (m_parents[n] > m_parents[m]) { + std::swap(n, m); + } + m_parents[n] += m_parents[m]; + m_parents[m] = n; + + if (m_defaults[n] == 0) { + m_defaults[n] = m_defaults[m]; + } + CTRACE("array", m_defaults[m], + tout << mk_pp(m_defaults[m]->get_root()->get_owner(), get_manager()) << "\n"; + tout << mk_pp(m_defaults[n]->get_root()->get_owner(), get_manager()) << "\n"; + ); + + // NB. it may be the case that m_defaults[m] != m_defaults[n] + // when m and n are finite arrays. + + } + } + + + void theory_array_base::init_model(model_generator & m) { + m_factory = alloc(array_factory, get_manager(), m.get_model()); + m.register_factory(m_factory); + m_use_unspecified_default = is_unspecified_default_ok(); + collect_defaults(); + collect_selects(); + propagate_selects(); + } + + /** + \brief It is ok to use an unspecified default value for arrays, when the + logical context does not contain store, default and const terms. + + That is, other modules (such as smt_model_finder) may set the default value to an arbitrary value. + */ + bool theory_array_base::is_unspecified_default_ok() const { + context & ctx = get_context(); + int num_vars = get_num_vars(); + for (theory_var v = 0; v < num_vars; ++v) { + enode * n = get_enode(v); + + // If n is not relevant, then it should not be used to set defaults. + if (!ctx.is_relevant(n)) + continue; + + if (is_store(n) || is_const(n) || is_default(n)) + return false; + } + return true; + } + + + void theory_array_base::collect_defaults() { + int num_vars = get_num_vars(); + m_defaults.reset(); + m_else_values.reset(); + m_parents.reset(); + m_parents.resize(num_vars, -1); + m_defaults.resize(num_vars, 0); + m_else_values.resize(num_vars, 0); + + if (m_use_unspecified_default) + return; + + context & ctx = get_context(); + + // + // Create equivalence classes for defaults. + // + for (theory_var v = 0; v < num_vars; ++v) { + enode * n = get_enode(v); + + // If n is not relevant, then it should not be used to set defaults. + if (!ctx.is_relevant(n)) + continue; + + theory_var r = get_representative(v); + + mg_merge(v, r); + + if (is_store(n)) { + theory_var w = n->get_arg(0)->get_th_var(get_id()); + SASSERT(w != null_theory_var); + ast_manager& m = get_manager(); + + mg_merge(v, get_representative(w)); + + TRACE("array", tout << "merge: " << mk_pp(n->get_owner(), m) << " " << v << " " << w << "\n";); + } + else if (is_const(n)) { + set_default(v, n->get_arg(0)); + } + else if (is_default(n)) { + theory_var w = n->get_arg(0)->get_th_var(get_id()); + SASSERT(w != null_theory_var); + set_default(w, n); + } + } + } + + unsigned theory_array_base::sel_hash::operator()(enode * n) const { + return get_composite_hash(n, n->get_num_args() - 1, sel_khasher(), sel_chasher()); + } + + bool theory_array_base::sel_eq::operator()(enode * n1, enode * n2) const { + SASSERT(n1->get_num_args() == n2->get_num_args()); + unsigned num_args = n1->get_num_args(); + for (unsigned i = 1; i < num_args; i++) { + if (n1->get_arg(i)->get_root() != n2->get_arg(i)->get_root()) + return false; + } + return true; + } + + theory_array_base::select_set * theory_array_base::get_select_set(enode * n) { + enode * r = n->get_root(); + select_set * set = 0; + m_selects.find(r, set); + if (set == 0) { + set = alloc(select_set); + m_selects.insert(r, set); + m_selects_domain.push_back(r); + m_selects_range.push_back(set); + } + return set; + } + + void theory_array_base::collect_selects() { + int num_vars = get_num_vars(); + + m_selects.reset(); + m_selects_domain.reset(); + m_selects_range.reset(); + + for (theory_var v = 0; v < num_vars; ++v) { + enode * r = get_enode(v)->get_root(); + if (is_representative(v) && get_context().is_relevant(r)) { + enode_vector::iterator it = r->begin_parents(); + enode_vector::iterator end = r->end_parents(); + for (; it != end; ++it) { + enode * parent = *it; + if (parent->get_cg() == parent && + get_context().is_relevant(parent) && + is_select(parent) && + parent->get_arg(0)->get_root() == r) { + select_set * s = get_select_set(r); + SASSERT(!s->contains(parent) || (*(s->find(parent)))->get_root() == parent->get_root()); + s->insert(parent); + } + } + } + } + } + + void theory_array_base::propagate_select_to_store_parents(enode * r, enode * sel, svector & todo) { + SASSERT(r->get_root() == r); + SASSERT(is_select(sel)); + if (!get_context().is_relevant(r)) { + return; + } + ptr_vector::const_iterator it = r->begin_parents(); + ptr_vector::const_iterator end = r->end_parents(); + for (; it != end; ++it) { + enode * parent = *it; + if (get_context().is_relevant(parent) && + is_store(parent) && + parent->get_arg(0)->get_root() == r) { + // propagate upward + select_set * parent_sel_set = get_select_set(parent); + enode * parent_root = parent->get_root(); + + if (parent_sel_set->contains(sel)) + continue; + + SASSERT(sel->get_num_args() + 1 == parent->get_num_args()); + + // check whether the sel idx was overwritten by the store + unsigned num_args = sel->get_num_args(); + unsigned i = 1; + for (; i < num_args; i++) { + if (sel->get_arg(i)->get_root() != parent->get_arg(i)->get_root()) + break; + } + + if (i < num_args) { + SASSERT(!parent_sel_set->contains(sel) || (*(parent_sel_set->find(sel)))->get_root() == sel->get_root()); + parent_sel_set->insert(sel); + todo.push_back(std::make_pair(parent_root, sel)); + } + } + } + } + + void theory_array_base::propagate_selects_to_store_parents(enode * r, svector & todo) { + select_set * sel_set = get_select_set(r); + select_set::iterator it2 = sel_set->begin(); + select_set::iterator end2 = sel_set->end(); + for (; it2 != end2; ++it2) { + enode * sel = *it2; + SASSERT(is_select(sel)); + propagate_select_to_store_parents(r, sel, todo); + } + } + + void theory_array_base::propagate_selects() { + svector todo; + ptr_vector::const_iterator it = m_selects_domain.begin(); + ptr_vector::const_iterator end = m_selects_domain.end(); + for (; it != end; ++it) { + enode * r = *it; + propagate_selects_to_store_parents(r, todo); + } + for (unsigned qhead = 0; qhead < todo.size(); qhead++) { + enode_pair & pair = todo[qhead]; + enode * r = pair.first; + enode * sel = pair.second; + propagate_select_to_store_parents(r, sel, todo); + } + } + + void theory_array_base::finalize_model(model_generator & m) { + std::for_each(m_selects_range.begin(), m_selects_range.end(), delete_proc()); + } + + class array_value_proc : public model_value_proc { + family_id m_fid; + sort * m_sort; + unsigned m_num_entries; + unsigned m_dim; //!< number of dimensions; + app * m_else; + bool m_unspecified_else; + svector m_dependencies; + + public: + array_value_proc(family_id fid, sort * s, extra_fresh_value * v): + m_fid(fid), + m_sort(s), + m_num_entries(0), + m_dim(0), + m_else(0), + m_unspecified_else(false) { + m_dependencies.push_back(model_value_dependency(v)); + } + + array_value_proc(family_id fid, sort * s, app * else_value): + m_fid(fid), + m_sort(s), + m_num_entries(0), + m_dim(0), + m_else(else_value), + m_unspecified_else(false) { + } + + array_value_proc(family_id fid, sort * s, enode * else_value): + m_fid(fid), + m_sort(s), + m_num_entries(0), + m_dim(0), + m_else(0), + m_unspecified_else(false) { + m_dependencies.push_back(model_value_dependency(else_value)); + } + + array_value_proc(family_id fid, sort * s): + m_fid(fid), + m_sort(s), + m_num_entries(0), + m_dim(0), + m_else(0), + m_unspecified_else(true) { + } + + virtual ~array_value_proc() {} + + void add_entry(unsigned num_args, enode * const * args, enode * value) { + SASSERT(num_args > 0); + SASSERT(m_dim == 0 || m_dim == num_args); + m_dim = num_args; + m_num_entries ++; + for (unsigned i = 0; i < num_args; i++) + m_dependencies.push_back(model_value_dependency(args[i])); + m_dependencies.push_back(model_value_dependency(value)); + } + + virtual void get_dependencies(buffer & result) { + result.append(m_dependencies.size(), m_dependencies.c_ptr()); + } + + virtual app * mk_value(model_generator & mg, ptr_vector & values) { + // values must have size = m_num_entries * (m_dim + 1) + ((m_else || m_unspecified_else) ? 0 : 1) + // an array value is a lookup table + else_value + // each entry has m_dim indexes that map to a value. + ast_manager & m = mg.get_manager(); + SASSERT(values.size() == m_dependencies.size()); + SASSERT(values.size() == m_num_entries * (m_dim + 1) + ((m_else || m_unspecified_else) ? 0 : 1)); + + unsigned arity = get_array_arity(m_sort); + func_decl * f = mk_aux_decl_for_array_sort(m, m_sort); + func_interp * fi = alloc(func_interp, m, arity); + mg.get_model().register_decl(f, fi); + + unsigned idx = 0; + if (m_else || m_unspecified_else) { + fi->set_else(m_else); + } + else { + fi->set_else(to_app(values[0])); + idx = 1; + } + + ptr_buffer args; + for (unsigned i = 0; i < m_num_entries; i++) { + args.reset(); + // copy indices + for (unsigned j = 0; j < m_dim; j++, idx++) + args.push_back(values[idx]); + expr * result = values[idx]; + idx++; + fi->insert_entry(args.c_ptr(), result); + } + + parameter p[1] = { parameter(f) }; + return m.mk_app(m_fid, OP_AS_ARRAY, 1, p); + } + }; + + model_value_proc * theory_array_base::mk_value(enode * n, model_generator & m) { + SASSERT(get_context().is_relevant(n)); + theory_var v = n->get_th_var(get_id()); + SASSERT(v != null_theory_var); + sort * s = get_manager().get_sort(n->get_owner()); + enode * else_val_n = get_default(v); + array_value_proc * result = 0; + + if (m_use_unspecified_default) { + SASSERT(else_val_n == 0); + result = alloc(array_value_proc, get_id(), s); + } + else { + if (else_val_n != 0) { + SASSERT(get_context().is_relevant(else_val_n)); + result = alloc(array_value_proc, get_id(), s, else_val_n); + } + else { + theory_var r = mg_find(v); + void * else_val = m_else_values[r]; + // DISABLED. It seems wrong, since different nodes can share the same + // else_val according to the mg class. + // SASSERT(else_val == 0 || get_context().is_relevant(UNTAG(app*, else_val))); + if (else_val == 0) { + sort * range = to_sort(s->get_parameter(s->get_num_parameters() - 1).get_ast()); + // IMPORTANT: + // The implementation should not assume a fresh value is created for + // the else_val if the range is finite + if (range->is_infinite()) + else_val = TAG(void*, m.mk_extra_fresh_value(range), 1); + else + else_val = TAG(void*, m.get_some_value(range), 0); + m_else_values[r] = else_val; + } + if (GET_TAG(else_val) == 0) { + result = alloc(array_value_proc, get_id(), s, UNTAG(app*, else_val)); + } + else { + result = alloc(array_value_proc, get_id(), s, UNTAG(extra_fresh_value*, else_val)); + } + } + } + SASSERT(result != 0); + select_set * sel_set = 0; + m_selects.find(n->get_root(), sel_set); + if (sel_set != 0) { + ptr_buffer args; + select_set::iterator it = sel_set->begin(); + select_set::iterator end = sel_set->end(); + for (; it != end; ++it) { + enode * select = *it; + args.reset(); + unsigned num = select->get_num_args(); + for (unsigned j = 1; j < num; ++j) + args.push_back(select->get_arg(j)); + SASSERT(get_context().is_relevant(select)); + result->add_entry(args.size(), args.c_ptr(), select); + } + } + return result; + } + +}; diff --git a/lib/theory_array_base.h b/lib/theory_array_base.h new file mode 100644 index 000000000..5e8f6c11a --- /dev/null +++ b/lib/theory_array_base.h @@ -0,0 +1,199 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_array_base.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-02. + +Revision History: + +--*/ +#ifndef _THEORY_ARRAY_BASE_H_ +#define _THEORY_ARRAY_BASE_H_ + +#include"smt_theory.h" +#include"array_decl_plugin.h" +#include"array_factory.h" + +namespace smt { + + class theory_array_base : public theory { + protected: + bool m_found_unsupported_op; + + void found_unsupported_op(expr * n); + + bool is_store(app const* n) const { return n->is_app_of(get_id(), OP_STORE); } + bool is_map(app const* n) const { return n->is_app_of(get_id(), OP_ARRAY_MAP); } + bool is_select(app const* n) const { return n->is_app_of(get_id(), OP_SELECT); } + bool is_default(app const* n) const { return n->is_app_of(get_id(), OP_ARRAY_DEFAULT); } + bool is_const(app const* n) const { return n->is_app_of(get_id(), OP_CONST_ARRAY); } + bool is_array_ext(app const * n) const { return n->is_app_of(get_id(), OP_ARRAY_EXT_SKOLEM); } + bool is_as_array(app const * n) const { return n->is_app_of(get_id(), OP_AS_ARRAY); } + bool is_array_sort(sort const* s) const { return s->is_sort_of(get_id(), ARRAY_SORT); } + bool is_array_sort(app const* n) const { return is_array_sort(get_manager().get_sort(n)); } + + bool is_store(enode const * n) const { return is_store(n->get_owner()); } + bool is_map(enode const* n) const { return is_map(n->get_owner()); } + bool is_select(enode const* n) const { return is_select(n->get_owner()); } + bool is_const(enode const* n) const { return is_const(n->get_owner()); } + bool is_as_array(enode const * n) const { return is_as_array(n->get_owner()); } + bool is_default(enode const* n) const { return is_default(n->get_owner()); } + bool is_array_sort(enode const* n) const { return is_array_sort(n->get_owner()); } + + + app * mk_select(unsigned num_args, expr * const * args); + app * mk_store(unsigned num_args, expr * const * args); + app * mk_default(expr* a); + + + unsigned get_dimension(sort* s) const; + + ptr_vector m_axiom1_todo; + svector > m_axiom2_todo; + svector > m_extensionality_todo; + + void assert_axiom(unsigned num_lits, literal * lits); + void assert_axiom(literal l1, literal l2); + void assert_axiom(literal l); + void assert_store_axiom1_core(enode * n); + void assert_store_axiom2_core(enode * store, enode * select); + void assert_store_axiom1(enode * n) { m_axiom1_todo.push_back(n); } + bool assert_store_axiom2(enode * store, enode * select); + + void assert_extensionality_core(enode * a1, enode * a2); + bool assert_extensionality(enode * a1, enode * a2); + + // -------------------------------------------------- + // Array sort -> extensionality skolems + // + // -------------------------------------------------- + ptr_vector m_sorts_trail; + obj_map m_sort2skolem; + + func_decl_ref_vector * register_sort(sort * s_array); + + // -------------------------------------------------- + // array_value table + // + // Use select(A, i) nodes to represent an assignment for A. + // This structure is used to minimize the number of times the + // extensionality axiom is applied. + // + // -------------------------------------------------- + struct value_chasher { + unsigned operator()(enode const * n, unsigned idx) const { + return n->get_arg(idx+1)->get_root()->hash(); + } + }; + struct value_khasher { unsigned operator()(enode * n) const { return 17; } }; + struct value_hash_proc { + unsigned operator()(enode * n) const { + return get_composite_hash(n, n->get_num_args() - 1); + } + }; + struct value_eq_proc { bool operator()(enode * n1, enode * n2) const; }; + typedef ptr_hashtable array_value; + + array_value m_array_value; + bool already_diseq(enode * v1, enode * v2); + + // -------------------------------------------------- + // Backtracking + // + // + // -------------------------------------------------- + struct scope { + unsigned m_sorts_trail_lim; + scope(unsigned l):m_sorts_trail_lim(l) {} + }; + svector m_scopes; + void restore_sorts(unsigned old_size); + + // -------------------------------------------------- + // Interface + // + // + // -------------------------------------------------- + virtual bool is_shared(theory_var v) const; + void collect_shared_vars(sbuffer & result); + unsigned mk_interface_eqs(); + + virtual bool can_propagate(); + virtual void propagate(); + virtual void push_scope_eh(); + virtual void pop_scope_eh(unsigned num_scopes); + virtual void reset_eh(); + + void reset_queues(); + // ----------------------------------- + // + // Model generation + // + // ----------------------------------- + + + // I need a set of select enodes where select(A,i) = select(B,j) if i->get_root() == j->get_root() + struct sel_khasher { + unsigned operator()(enode const * n) const { return 0; } + }; + + struct sel_chasher { + unsigned operator()(enode const * n, unsigned idx) const { + return n->get_arg(idx+1)->get_root()->hash(); + } + }; + + struct sel_hash { + unsigned operator()(enode * n) const; + }; + + struct sel_eq { + bool operator()(enode * n1, enode * n2) const; + }; + + typedef ptr_hashtable select_set; + + array_factory * m_factory; + ptr_vector m_defaults; // temporary field for model construction + ptr_vector m_else_values; // tagged pointer: expr or extra_fresh_value + svector m_parents; // temporary field for model construction + obj_map m_selects; // mapping from array -> relevant selects + ptr_vector m_selects_domain; + ptr_vector m_selects_range; + bool m_use_unspecified_default; // temporary field for model construction + + theory_var mg_find(theory_var v); + void mg_merge(theory_var n, theory_var m); + + void set_default(theory_var v, enode* n); + enode* get_default(theory_var v); + + virtual void init_model(model_generator & m); + bool is_unspecified_default_ok() const; + void collect_defaults(); + void collect_selects(); + void propagate_select_to_store_parents(enode * r, enode * sel, svector & todo); + void propagate_selects_to_store_parents(enode * r, svector & todo); + void propagate_selects(); + select_set * get_select_set(enode * n); + virtual void finalize_model(model_generator & m); + virtual model_value_proc * mk_value(enode * n, model_generator & m); + + public: + theory_array_base(ast_manager & m); + virtual ~theory_array_base() { restore_sorts(0); } + }; + +}; + +#endif /* _THEORY_ARRAY_BASE_H_ */ + diff --git a/lib/theory_array_full.cpp b/lib/theory_array_full.cpp new file mode 100644 index 000000000..28314c421 --- /dev/null +++ b/lib/theory_array_full.cpp @@ -0,0 +1,812 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_array_full.cpp + +Abstract: + + + +Author: + + Nikolaj Bjorner 2008-22-10 + +Revision History: + +--*/ + +#include "smt_context.h" +#include "theory_array_full.h" +#include "ast_ll_pp.h" +#include "ast_pp.h" +#include "ast_smt2_pp.h" +#include "stats.h" + +namespace smt { + + theory_array_full::theory_array_full(ast_manager & m, theory_array_params & params) : + theory_array(m, params), + m_sort2epsilon(m) {} + + theory_array_full::~theory_array_full() { + std::for_each(m_var_data_full.begin(), m_var_data_full.end(), delete_proc()); + m_var_data_full.reset(); + } + + void theory_array_full::add_map(theory_var v, enode* s) { + if (m_params.m_array_cg && !s->is_cgr()) { + return; + } + SASSERT(is_map(s)); + v = find(v); + var_data_full * d_full = m_var_data_full[v]; + var_data * d = m_var_data[v]; + // + // TODO: defaulting to exhaustive up-propagation. + // instead apply stratified filter. + set_prop_upward(v,d); + d_full->m_maps.push_back(s); + m_trail_stack.push(push_back_trail(d_full->m_maps)); + ptr_vector::iterator it = d->m_parent_selects.begin(); + ptr_vector::iterator end = d->m_parent_selects.end(); + for (; it != end; ++it) { + SASSERT(is_select(*it)); + instantiate_select_map_axiom(*it, s); + } + set_prop_upward(s); + } + + bool theory_array_full::instantiate_axiom_map_for(theory_var v) { + bool result = false; + var_data * d = m_var_data[v]; + var_data_full * d_full = m_var_data_full[v]; + unsigned num_maps = d_full->m_parent_maps.size(); + unsigned num_selects = d->m_parent_selects.size(); + for (unsigned i = 0; i < num_maps; ++i) { + for (unsigned j = 0; j < num_selects; ++j) { + if (instantiate_select_map_axiom(d->m_parent_selects[j], d_full->m_parent_maps[i])) { + result = true; + } + } + } + return result; + } + + void theory_array_full::add_parent_map(theory_var v, enode* s) { + if (m_params.m_array_cg && !s->is_cgr()) { + return; + } + SASSERT(v != null_theory_var); + SASSERT(is_map(s)); + v = find(v); + var_data * d = m_var_data[v]; + var_data_full * d_full = m_var_data_full[v]; + d_full->m_parent_maps.push_back(s); + m_trail_stack.push(push_back_trail(d_full->m_parent_maps)); + if (!m_params.m_array_weak && !m_params.m_array_delay_exp_axiom && d->m_prop_upward) { + ptr_vector::iterator it = d->m_parent_selects.begin(); + ptr_vector::iterator end = d->m_parent_selects.end(); + for (; it != end; ++it) { + if (!m_params.m_array_cg || (*it)->is_cgr()) { + instantiate_select_map_axiom(*it, s); + } + } + } + } + + // + // set set_prop_upward on root and recursively on children if necessary. + // + void theory_array_full::set_prop_upward(theory_var v) { + if (m_params.m_array_weak) + return; + v = find(v); + var_data * d = m_var_data[v]; + if (!d->m_prop_upward) { + m_trail_stack.push(reset_flag_trail(d->m_prop_upward)); + d->m_prop_upward = true; + TRACE("array", tout << "#" << v << "\n";); + if (!m_params.m_array_delay_exp_axiom) { + instantiate_axiom2b_for(v); + instantiate_axiom_map_for(v); + } + var_data_full * d2 = m_var_data_full[v]; + ptr_vector::iterator it = d->m_stores.begin(); + ptr_vector::iterator end = d->m_stores.end(); + for (; it != end; ++it) { + set_prop_upward(*it); + } + it = d2->m_maps.begin(); + end = d2->m_maps.end(); + for (; it != end; ++it) { + set_prop_upward(*it); + } + it = d2->m_consts.begin(); + end = d2->m_consts.end(); + for (; it != end; ++it) { + set_prop_upward(*it); + } + } + } + + // + // call set_prop_upward on array arguments. + // + void theory_array_full::set_prop_upward(enode * n) { + TRACE("array", tout << mk_pp(n->get_owner(), get_manager()) << "\n";); + if (is_store(n)) { + set_prop_upward(n->get_arg(0)->get_th_var(get_id())); + } + else if (is_map(n)) { + for (unsigned i = 0; i < n->get_num_args(); ++i) { + set_prop_upward(n->get_arg(i)->get_th_var(get_id())); + } + } + } + + void theory_array_full::set_prop_upward(theory_var v, var_data* d) { + if (m_params.m_array_always_prop_upward || d->m_stores.size() >= 1) { + theory_array::set_prop_upward(v, d); + } + else { + var_data_full * d2 = m_var_data_full[v]; + unsigned sz = d2->m_maps.size(); + for(unsigned i = 0; i < sz; ++i) { + set_prop_upward(d2->m_maps[i]); + } + } + } + + + unsigned theory_array_full::get_lambda_equiv_size(theory_var v, var_data* d) { + var_data_full * d2 = m_var_data_full[v]; + return d->m_stores.size() + 2*d2->m_consts.size() + 2*d2->m_maps.size(); + } + + void theory_array_full::add_const(theory_var v, enode* cnst) { + var_data * d = m_var_data[v]; + unsigned lambda_equiv_class_size = get_lambda_equiv_size(v, d); + if (m_params.m_array_always_prop_upward || lambda_equiv_class_size >= 1) { + set_prop_upward(v, d); + } + ptr_vector & consts = m_var_data_full[v]->m_consts; + m_trail_stack.push(push_back_trail(consts)); + consts.push_back(cnst); + instantiate_default_const_axiom(cnst); + + ptr_vector::iterator it = d->m_parent_selects.begin(); + ptr_vector::iterator end = d->m_parent_selects.end(); + for (; it != end; ++it) { + SASSERT(is_select(*it)); + instantiate_select_const_axiom(*it, cnst); + } + } + + void theory_array_full::add_as_array(theory_var v, enode* arr) { + var_data * d = m_var_data[v]; + unsigned lambda_equiv_class_size = get_lambda_equiv_size(v, d); + if (m_params.m_array_always_prop_upward || lambda_equiv_class_size >= 1) { + set_prop_upward(v, d); + } + ptr_vector & as_arrays = m_var_data_full[v]->m_as_arrays; + m_trail_stack.push(push_back_trail(as_arrays)); + as_arrays.push_back(arr); + instantiate_default_as_array_axiom(arr); + + ptr_vector::iterator it = d->m_parent_selects.begin(); + ptr_vector::iterator end = d->m_parent_selects.end(); + for (; it != end; ++it) { + SASSERT(is_select(*it)); + instantiate_select_as_array_axiom(*it, arr); + } + } + + + void theory_array_full::reset_eh() { + theory_array::reset_eh(); + std::for_each(m_var_data_full.begin(), m_var_data_full.end(), delete_proc()); + m_var_data_full.reset(); + } + + void theory_array_full::display_var(std::ostream & out, theory_var v) const { + theory_array::display_var(out, v); + var_data_full const * d = m_var_data_full[v]; + out << " maps: {"; + display_ids(out, d->m_maps.size(), d->m_maps.c_ptr()); + out << "} p_parent_maps: {"; + display_ids(out, d->m_parent_maps.size(), d->m_parent_maps.c_ptr()); + out << "} p_const: {"; + display_ids(out, d->m_consts.size(), d->m_consts.c_ptr()); + out << "}\n"; + } + + theory_var theory_array_full::mk_var(enode * n) { + + theory_var r = theory_array::mk_var(n); + SASSERT(r == static_cast(m_var_data_full.size())); + m_var_data_full.push_back(alloc(var_data_full)); + var_data_full * d = m_var_data_full.back(); + if (is_map(n)) { + instantiate_default_map_axiom(n); + d->m_maps.push_back(n); + } + else if (is_const(n)) { + instantiate_default_const_axiom(n); + d->m_consts.push_back(n); + } + else if (is_default(n)) { + // no-op + } + else if (is_as_array(n)) { + instantiate_default_as_array_axiom(n); + d->m_as_arrays.push_back(n); + } + return r; + } + + bool theory_array_full::internalize_atom(app * atom, bool) { + return internalize_term(atom); + } + + bool theory_array_full::internalize_term(app * n) { + TRACE("array", tout << mk_pp(n, get_manager()) << "\n";); + + if (is_store(n) || is_select(n)) { + return theory_array::internalize_term(n); + } + + if (!is_const(n) && !is_default(n) && !is_map(n) && !is_as_array(n)) { + if (!is_array_ext(n)) + found_unsupported_op(n); + return false; + } + + if (!internalize_term_core(n)) { + return true; + } + context & ctx = get_context(); + + if (is_map(n)) { + for (unsigned i = 0; i < n->get_num_args(); ++i) { + enode* arg = ctx.get_enode(n->get_arg(i)); + if (!is_attached_to_var(arg)) { + mk_var(arg); + } + } + } + else if (is_default(n)) { + enode* arg0 = ctx.get_enode(n->get_arg(0)); + if (!is_attached_to_var(arg0)) { + mk_var(arg0); + } + } + + enode* node = ctx.get_enode(n); + if (!is_attached_to_var(node)) { + mk_var(node); + } + + if (is_default(n)) { + enode* arg0 = ctx.get_enode(n->get_arg(0)); + theory_var v_arg = arg0->get_th_var(get_id()); + add_parent_default(v_arg); + } + else if (is_map(n)) { + for (unsigned i = 0; i < n->get_num_args(); ++i) { + enode* arg = ctx.get_enode(n->get_arg(i)); + theory_var v_arg = arg->get_th_var(get_id()); + add_parent_map(v_arg, node); + } + instantiate_default_map_axiom(node); + } + else if (is_const(n)) { + instantiate_default_const_axiom(node); + } + else if (is_as_array(n)) { + // The array theory is not a decision procedure + // for as-array. + // Ex: (as-array f) = (as-array g) & f(0) = 0 & g(0) = 1 + // There is nothing to propagate the disequality. + // Even if there was, as-array on interpreted + // functions will be incomplete. + // The instantiation operations are still sound to include. + found_unsupported_op(n); + instantiate_default_as_array_axiom(node); + } + return true; + } + + + void theory_array_full::merge_eh(theory_var v1, theory_var v2, theory_var u, theory_var w) { + theory_array::merge_eh(v1, v2, u, w); + // v1 is the new root + SASSERT(v1 == find(v1)); + var_data_full * d2 = m_var_data_full[v2]; + ptr_vector::iterator it, end; + + it = d2->m_maps.begin(); + end = d2->m_maps.end(); + for (; it != end; ++it) { + add_map(v1, *it); + } + it = d2->m_parent_maps.begin(); + end = d2->m_parent_maps.end(); + for (; it != end; ++it) { + add_parent_map(v1, *it); + } + it = d2->m_consts.begin(); + end = d2->m_consts.end(); + for (; it != end; ++it) { + add_const(v1, *it); + } + it = d2->m_as_arrays.begin(); + end = d2->m_as_arrays.end(); + for (; it != end; ++it) { + add_as_array(v1, *it); + } + TRACE("array", + tout << mk_pp(get_enode(v1)->get_owner(), get_manager()) << "\n"; + tout << mk_pp(get_enode(v2)->get_owner(), get_manager()) << "\n"; + tout << "merge in\n"; display_var(tout, v2); + tout << "after merge\n"; display_var(tout, v1);); + } + + void theory_array_full::add_parent_default(theory_var v) { + SASSERT(v != null_theory_var); + v = find(v); + var_data* d = m_var_data[v]; + ptr_vector::iterator it, end; + + it = d->m_stores.begin(); + end = d->m_stores.end(); + for(; it != end; ++it) { + enode * store = *it; + SASSERT(is_store(store)); + instantiate_default_store_axiom(store); + } + + if (!m_params.m_array_weak && !m_params.m_array_delay_exp_axiom && d->m_prop_upward) { + it = d->m_parent_stores.begin(); + end = d->m_parent_stores.end(); + for (; it != end; ++it) { + enode* store = *it; + SASSERT(is_store(store)); + if (!m_params.m_array_cg || store->is_cgr()) { + instantiate_default_store_axiom(store); + } + } + } + } + + void theory_array_full::add_parent_select(theory_var v, enode * s) { + TRACE("array", + tout << v << " select parent: " << mk_pp(s->get_owner(), get_manager()) << "\n"; + display_var(tout, v); + ); + theory_array::add_parent_select(v,s); + v = find(v); + var_data_full* d_full = m_var_data_full[v]; + var_data* d = m_var_data[v]; + ptr_vector::iterator it = d_full->m_consts.begin(); + ptr_vector::iterator end = d_full->m_consts.end(); + for (; it != end; ++it) { + instantiate_select_const_axiom(s, *it); + } + it = d_full->m_maps.begin(); + end = d_full->m_maps.end(); + for (; it != end; ++it) { + enode* map = *it; + SASSERT(is_map(map)); + instantiate_select_map_axiom(s, map); + } + if (!m_params.m_array_weak && !m_params.m_array_delay_exp_axiom && d->m_prop_upward) { + it = d_full->m_parent_maps.begin(); + end = d_full->m_parent_maps.end(); + for (; it != end; ++it) { + enode* map = *it; + SASSERT(is_map(map)); + if (!m_params.m_array_cg || map->is_cgr()) { + instantiate_select_map_axiom(s, map); + } + } + } + } + + void theory_array_full::relevant_eh(app* n) { + TRACE("array", tout << mk_pp(n, get_manager()) << "\n";); + theory_array::relevant_eh(n); + if (!is_default(n) && !is_select(n) && !is_map(n) && !is_const(n) && !is_as_array(n)) { + return; + } + context & ctx = get_context(); + enode* node = ctx.get_enode(n); + + if (is_select(n)) { + enode * arg = ctx.get_enode(n->get_arg(0)); + theory_var v = arg->get_th_var(get_id()); + SASSERT(v != null_theory_var); + add_parent_select(find(v), node); + } + else if (is_default(n)) { + enode * arg = ctx.get_enode(n->get_arg(0)); + theory_var v = arg->get_th_var(get_id()); + SASSERT(v != null_theory_var); + add_parent_default(find(v)); + } + else if (is_const(n)) { + instantiate_default_const_axiom(node); + } + else if (is_map(n)) { + for (unsigned i = 0; i < n->get_num_args(); ++i) { + enode* arg = ctx.get_enode(n->get_arg(i)); + theory_var v_arg = find(arg->get_th_var(get_id())); + add_parent_map(v_arg, node); + set_prop_upward(v_arg); + } + instantiate_default_map_axiom(node); + } + else if (is_as_array(n)) { + instantiate_default_as_array_axiom(node); + } + } + + + // + // Assert axiom: + // select(map[f](a, ... d), i) = f(select(a,i),...,select(d,i)) + // + bool theory_array_full::instantiate_select_map_axiom(enode* sl, enode* mp) { + app* map = mp->get_owner(); + app* select = sl->get_owner(); + SASSERT(is_map(map)); + SASSERT(is_select(select)); + SASSERT(map->get_num_args() > 0); + func_decl* f = to_func_decl(map->get_decl()->get_parameter(0).get_ast()); + + context& ctx = get_context(); + ast_manager& m = get_manager(); + + TRACE("array_map_bug", tout << "invoked instantiate_select_map_axiom\n"; + tout << sl->get_owner_id() << " " << mp->get_owner_id() << "\n"; + tout << mk_ismt2_pp(sl->get_owner(), m) << "\n" << mk_ismt2_pp(mp->get_owner(), m) << "\n";); + + if (!ctx.add_fingerprint(mp, mp->get_owner_id(), sl->get_num_args() - 1, sl->get_args() + 1)) { + return false; + } + + TRACE("array_map_bug", tout << "new axiom\n";); + + m_stats.m_num_map_axiom++; + TRACE("array", + tout << mk_bounded_pp(mp->get_owner(), get_manager()) << "\n"; + tout << mk_bounded_pp(sl->get_owner(), get_manager()) << "\n";); + unsigned num_args = select->get_num_args(); + unsigned num_arrays = map->get_num_args(); + ptr_buffer args1, args2; + vector > args2l; + args1.push_back(map); + for (unsigned j = 0; j < num_arrays; ++j) { + ptr_vector arg; + arg.push_back(map->get_arg(j)); + args2l.push_back(arg); + } + for (unsigned i = 1; i < num_args; ++i) { + expr* arg = select->get_arg(i); + for (unsigned j = 0; j < num_arrays; ++j) { + args2l[j].push_back(arg); + } + args1.push_back(arg); + } + for (unsigned j = 0; j < num_arrays; ++j) { + expr* sel = mk_select(args2l[j].size(), args2l[j].c_ptr()); + args2.push_back(sel); + } + + expr_ref sel1(m), sel2(m); + sel1 = mk_select(args1.size(), args1.c_ptr()); + m_simp->mk_app(f, args2.size(), args2.c_ptr(), sel2); + ctx.internalize(sel1, false); + ctx.internalize(sel2, false); + + TRACE("array_map_bug", + tout << "select-map axiom\n" << mk_ismt2_pp(sel1, m) << "\n=\n" << mk_ismt2_pp(sel2,m) << "\n";); + + return try_assign_eq(sel1, sel2); + } + + + // + // + // Assert axiom: + // default(map[f](a,..,d)) = f(default(a),..,default(d)) + // + + bool theory_array_full::instantiate_default_map_axiom(enode* mp) { + SASSERT(is_map(mp)); + + app* map = mp->get_owner(); + context& ctx = get_context(); + if (!ctx.add_fingerprint(this, 0, 1, &mp)) { + return false; + } + TRACE("array", tout << mk_bounded_pp(map, get_manager()) << "\n";); + + m_stats.m_num_default_map_axiom++; + + func_decl* f = to_func_decl(map->get_decl()->get_parameter(0).get_ast()); + SASSERT(map->get_num_args() == f->get_arity()); + ptr_buffer args2; + for (unsigned i = 0; i < map->get_num_args(); ++i) { + args2.push_back(mk_default(map->get_arg(i))); + } + + expr* def1 = mk_default(map); + expr_ref def2(get_manager()); + m_simp->mk_app(f, args2.size(), args2.c_ptr(), def2); + ctx.internalize(def1, false); + ctx.internalize(def2, false); + return try_assign_eq(def1, def2); + } + + + bool theory_array_full::instantiate_default_const_axiom(enode* cnst) { + context& ctx = get_context(); + if (!ctx.add_fingerprint(this, 0, 1, &cnst)) { + return false; + } + m_stats.m_num_default_const_axiom++; + SASSERT(is_const(cnst)); + TRACE("array", tout << mk_bounded_pp(cnst->get_owner(), get_manager()) << "\n";); + expr* val = cnst->get_arg(0)->get_owner(); + expr* def = mk_default(cnst->get_owner()); + ctx.internalize(def, false); + return try_assign_eq(val, def); + } + + bool theory_array_full::instantiate_default_as_array_axiom(enode* arr) { + context& ctx = get_context(); + if (!ctx.add_fingerprint(this, 0, 1, &arr)) { + return false; + } + m_stats.m_num_default_as_array_axiom++; + SASSERT(is_as_array(arr)); + TRACE("array", tout << mk_bounded_pp(arr->get_owner(), get_manager()) << "\n";); + expr* def = mk_default(arr->get_owner()); + func_decl * f = array_util(get_manager()).get_as_array_func_decl(arr->get_owner()); + ptr_vector args; + for (unsigned i = 0; i < f->get_arity(); ++i) { + args.push_back(mk_epsilon(f->get_domain(i))); + } + expr_ref val(get_manager().mk_app(f, args.size(), args.c_ptr()), get_manager()); + ctx.internalize(def, false); + ctx.internalize(val.get(), false); + return try_assign_eq(val.get(), def); + } + + bool theory_array_full::has_large_domain(app* array_term) { + SASSERT(is_array_sort(array_term)); + sort* s = get_manager().get_sort(array_term); + unsigned dim = get_dimension(s); + parameter const * params = s->get_info()->get_parameters(); + rational sz(1); + for (unsigned i = 0; i < dim; ++i) { + SASSERT(params[i].is_ast()); + sort* d = to_sort(params[i].get_ast()); + if (d->is_infinite() || d->is_very_big()) { + return true; + } + sz *= rational(d->get_num_elements().size(),rational::ui64()); + if (sz >= rational(1 << 20)) { + return true; + } + } + return false; + } + + // + // Assert axiom: + // select(const v, i_1, ..., i_n) = v + // + bool theory_array_full::instantiate_select_const_axiom(enode* select, enode* cnst) { + SASSERT(is_const(cnst)); + SASSERT(is_select(select)); + SASSERT(cnst->get_num_args() == 1); + context& ctx = get_context(); + unsigned num_args = select->get_num_args(); + if (!ctx.add_fingerprint(cnst, cnst->get_owner_id(), select->get_num_args() - 1, select->get_args() + 1)) { + return false; + } + + m_stats.m_num_select_const_axiom++; + ptr_buffer sel_args; + sel_args.push_back(cnst->get_owner()); + for (unsigned short i = 1; i < num_args; ++i) { + sel_args.push_back(select->get_owner()->get_arg(i)); + } + expr * sel = mk_select(sel_args.size(), sel_args.c_ptr()); + expr * val = cnst->get_owner()->get_arg(0); + TRACE("array", tout << "new select-const axiom...\n"; + tout << "const: " << mk_bounded_pp(cnst->get_owner(), get_manager()) << "\n"; + tout << "select: " << mk_bounded_pp(select->get_owner(), get_manager()) << "\n"; + tout << " sel/const: " << mk_bounded_pp(sel, get_manager()) << "\n"; + tout << "value: " << mk_bounded_pp(val, get_manager()) << "\n"; + tout << "#" << sel->get_id() << " = #" << val->get_id() << "\n"; + ); + + ctx.internalize(sel, false); + return try_assign_eq(sel,val); + } + + + // + // Assert axiom: + // select(as-array f, i_1, ..., i_n) = (f i_1 ... i_n) + // + bool theory_array_full::instantiate_select_as_array_axiom(enode* select, enode* arr) { + SASSERT(is_as_array(arr->get_owner())); + SASSERT(is_select(select)); + SASSERT(arr->get_num_args() == 0); + context& ctx = get_context(); + unsigned num_args = select->get_num_args(); + if (!ctx.add_fingerprint(arr, arr->get_owner_id(), select->get_num_args() - 1, select->get_args() + 1)) { + return false; + } + + m_stats.m_num_select_as_array_axiom++; + ptr_buffer sel_args; + sel_args.push_back(arr->get_owner()); + for (unsigned short i = 1; i < num_args; ++i) { + sel_args.push_back(select->get_owner()->get_arg(i)); + } + expr * sel = mk_select(sel_args.size(), sel_args.c_ptr()); + func_decl * f = array_util(get_manager()).get_as_array_func_decl(arr->get_owner()); + expr_ref val(get_manager().mk_app(f, sel_args.size()-1, sel_args.c_ptr()+1), get_manager()); + TRACE("array", tout << "new select-as-array axiom...\n"; + tout << "as-array: " << mk_bounded_pp(arr->get_owner(), get_manager()) << "\n"; + tout << "select: " << mk_bounded_pp(select->get_owner(), get_manager()) << "\n"; + tout << " sel/as-array: " << mk_bounded_pp(sel, get_manager()) << "\n"; + tout << "value: " << mk_bounded_pp(val.get(), get_manager()) << "\n"; + tout << "#" << sel->get_id() << " = #" << val->get_id() << "\n"; + ); + + ctx.internalize(sel, false); + ctx.internalize(val.get(), false); + return try_assign_eq(sel,val); + } + + + bool theory_array_full::instantiate_default_store_axiom(enode* store) { + SASSERT(is_store(store)); + SASSERT(store->get_num_args() >= 3); + app* store_app = store->get_owner(); + context& ctx = get_context(); + ast_manager& m = get_manager(); + if (!ctx.add_fingerprint(this, 0, 1, &store)) { + return false; + } + + m_stats.m_num_default_store_axiom++; + + app* def1; + app* def2; + + TRACE("array", tout << mk_bounded_pp(store_app, m) << "\n";); + + if (has_large_domain(store_app)) { + def2 = mk_default(store_app->get_arg(0)); + } + else { + // + // let A = store(B, i, v) + // + // Add: + // default(A) = ite(epsilon = i, v, default(B)) + // + expr_ref_vector eqs(m); + unsigned num_args = store_app->get_num_args(); + for (unsigned i = 1; i + 1 < num_args; ++i) { + sort* srt = m.get_sort(store_app->get_arg(i)); + app* ep = mk_epsilon(srt); + eqs.push_back(m.mk_eq(ep, store_app->get_arg(i))); + } + + expr_ref eq(m); + simplifier_plugin* p = m_simp->get_plugin(m.get_basic_family_id()); + basic_simplifier_plugin* bp = static_cast(p); + bp->mk_and(eqs.size(), eqs.c_ptr(), eq); + expr* defA = mk_default(store_app->get_arg(0)); + def2 = m.mk_ite(eq, store_app->get_arg(num_args-1), defA); +#if 0 + // + // add soft constraints to guide model construction so that + // epsilon agrees with the else case in the model construction. + // + for (unsigned i = 0; i < eqs.size(); ++i) { + // assume_diseq(eqs[i]); + } +#endif + } + + def1 = mk_default(store_app); + ctx.internalize(def1, false); + ctx.internalize(def2, false); + return try_assign_eq(def1, def2); + } + + app* theory_array_full::mk_epsilon(sort* s) { + app* eps = 0; + if (m_sort2epsilon.find(s, eps)) { + return eps; + } + eps = get_manager().mk_fresh_const("epsilon", s); + m_trail_stack.push( + ast2ast_trail(m_sort2epsilon, s, eps)); + return eps; + } + + final_check_status theory_array_full::assert_delayed_axioms() { + final_check_status r = FC_DONE; + if (!m_params.m_array_delay_exp_axiom) { + r = FC_DONE; + } + else { + r = theory_array::assert_delayed_axioms(); + unsigned num_vars = get_num_vars(); + for (unsigned v = 0; v < num_vars; v++) { + var_data * d = m_var_data[v]; + if (d->m_prop_upward && instantiate_axiom_map_for(v)) + r = FC_CONTINUE; + } + } + if (r == FC_DONE && m_found_unsupported_op) + r = FC_GIVEUP; + return r; + } + + bool theory_array_full::try_assign_eq(expr* v1, expr* v2) { + context& ctx = get_context(); + enode* n1 = ctx.get_enode(v1); + enode* n2 = ctx.get_enode(v2); + if (n1->get_root() == n2->get_root()) { + return false; + } + TRACE("array", + tout << mk_bounded_pp(v1, get_manager()) << "\n==\n" + << mk_bounded_pp(v2, get_manager()) << "\n";); + +#if 0 + if (m.proofs_enabled()) { +#endif + literal eq(mk_eq(v1,v2,true)); + ctx.mark_as_relevant(eq); + assert_axiom(eq); +#if 0 + } + else { + ctx.mark_as_relevant(n1); + ctx.mark_as_relevant(n2); + ctx.assign_eq(n1, n2, eq_justification::mk_axiom()); + } +#endif + return true; + } + + void theory_array_full::pop_scope_eh(unsigned num_scopes) { + unsigned num_old_vars = get_old_num_vars(num_scopes); + theory_array::pop_scope_eh(num_scopes); + std::for_each(m_var_data_full.begin() + num_old_vars, m_var_data_full.end(), delete_proc()); + m_var_data_full.shrink(num_old_vars); + } + + void theory_array_full::collect_statistics(::statistics & st) const { + theory_array::collect_statistics(st); + st.update("array map ax", m_stats.m_num_map_axiom); + st.update("array def const", m_stats.m_num_default_const_axiom); + st.update("array sel const", m_stats.m_num_select_const_axiom); + st.update("array def store", m_stats.m_num_default_store_axiom); + st.update("array def as-array", m_stats.m_num_default_as_array_axiom); + st.update("array sel as-array", m_stats.m_num_select_as_array_axiom); + } +} diff --git a/lib/theory_array_full.h b/lib/theory_array_full.h new file mode 100644 index 000000000..5a80b7fae --- /dev/null +++ b/lib/theory_array_full.h @@ -0,0 +1,109 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_array_full.h + +Abstract: + + + +Author: + + Nikolaj Bjorner 2008-22-10 + +Revision History: + +--*/ +#ifndef _THEORY_ARRAY_FULL_H_ +#define _THEORY_ARRAY_FULL_H_ + +#include"theory_array.h" +#include "simplifier.h" + +namespace smt { + + class theory_array_full : public theory_array { + struct var_data_full { + ptr_vector m_maps; + ptr_vector m_consts; + ptr_vector m_as_arrays; + ptr_vector m_parent_maps; + var_data_full() {} + }; + + ptr_vector m_var_data_full; + + ast2ast_trailmap m_sort2epsilon; + simplifier* m_simp; + + protected: + +#if 0 + virtual final_check_status final_check_eh(); +#endif + virtual void reset_eh(); + + virtual void set_prop_upward(theory_var v); + virtual void set_prop_upward(enode* n); + virtual void set_prop_upward(theory_var v, var_data* d); + virtual unsigned get_lambda_equiv_size(theory_var v, var_data* d); + + + virtual bool internalize_term(app * term); + virtual bool internalize_atom(app * atom, bool gate_ctx); + virtual void pop_scope_eh(unsigned num_scopes); + virtual theory_var mk_var(enode * n); + virtual void relevant_eh(app * n); + + void add_const(theory_var v, enode* c); + void add_map(theory_var v, enode* s); + void add_parent_map(theory_var v, enode* s); + void add_as_array(theory_var v, enode* arr); + + virtual void add_parent_select(theory_var v, enode * s); + void add_parent_default(theory_var v); + + virtual final_check_status assert_delayed_axioms(); + + bool instantiate_default_const_axiom(enode* cnst); + bool instantiate_default_store_axiom(enode* store); + bool instantiate_default_map_axiom(enode* map); + bool instantiate_default_as_array_axiom(enode* arr); + + bool has_large_domain(app* array_term); + app* mk_epsilon(sort* s); + + bool instantiate_select_const_axiom(enode* select, enode* cnst); + bool instantiate_select_as_array_axiom(enode* select, enode* arr); + bool instantiate_select_map_axiom(enode* select, enode* map); + + bool instantiate_axiom_map_for(theory_var v); + + + bool try_assign_eq(expr* n1, expr* n2); + + + public: + theory_array_full(ast_manager & m, theory_array_params & params); + virtual ~theory_array_full(); + + virtual theory * mk_fresh(context * new_ctx) { return alloc(theory_array_full, get_manager(), m_params); } + + virtual void merge_eh(theory_var v1, theory_var v2, theory_var, theory_var); + virtual void display_var(std::ostream & out, theory_var v) const; + virtual void collect_statistics(::statistics & st) const; + virtual void init(context* ctx) { + // the parent class is theory_array. + // theory::init(ctx); + theory_array::init(ctx); + m_simp = &ctx->get_simplifier(); + } + + }; + +}; + +#endif /* _THEORY_ARRAY_H_ */ + diff --git a/lib/theory_array_params.h b/lib/theory_array_params.h new file mode 100644 index 000000000..9e93fefbe --- /dev/null +++ b/lib/theory_array_params.h @@ -0,0 +1,76 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_array_params.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-01. + +Revision History: + +--*/ +#ifndef _THEORY_ARRAY_PARAMS_H_ +#define _THEORY_ARRAY_PARAMS_H_ + +#include"ini_file.h" + +enum array_solver_id { + AR_NO_ARRAY, + AR_SIMPLE, + AR_MODEL_BASED, + AR_FULL +}; + +struct theory_array_params { + array_solver_id m_array_mode; + bool m_array_weak; + bool m_array_extensional; + unsigned m_array_laziness; + bool m_array_delay_exp_axiom; + bool m_array_cg; + bool m_array_always_prop_upward; + bool m_array_lazy_ieq; + unsigned m_array_lazy_ieq_delay; + bool m_array_canonize_simplify; + bool m_array_simplify; // temporary hack for disabling array simplifier plugin. + + theory_array_params(): + m_array_mode(AR_FULL), + m_array_weak(false), + m_array_extensional(true), + m_array_laziness(1), + m_array_delay_exp_axiom(true), + m_array_cg(false), + m_array_always_prop_upward(true), // UPWARDs filter is broken... TODO: fix it + m_array_lazy_ieq(false), + m_array_lazy_ieq_delay(10), + m_array_canonize_simplify(false), + m_array_simplify(true) { + } + + void register_params(ini_params & p) { + p.register_int_param("ARRAY_SOLVER", 0, 3, reinterpret_cast(m_array_mode), "0 - no array, 1 - simple, 2 - model based, 3 - full"); + p.register_bool_param("ARRAY_WEAK", m_array_weak); + p.register_bool_param("ARRAY_EXTENSIONAL", m_array_extensional); + p.register_unsigned_param("ARRAY_LAZINESS", m_array_laziness); + p.register_bool_param("ARRAY_DELAY_EXP_AXIOM", m_array_delay_exp_axiom); + p.register_bool_param("ARRAY_CG", m_array_cg); + p.register_bool_param("ARRAY_ALWAYS_PROP_UPWARD", m_array_always_prop_upward, + "Disable the built-in filter upwards propagation"); + p.register_bool_param("ARRAY_LAZY_IEQ", m_array_lazy_ieq); + p.register_unsigned_param("ARRAY_LAZY_IEQ_DELAY", m_array_lazy_ieq_delay); + p.register_bool_param("ARRAY_CANONIZE", m_array_canonize_simplify, + "Normalize arrays into normal form during simplification"); + } +}; + + +#endif /* _THEORY_ARRAY_PARAMS_H_ */ + diff --git a/lib/theory_bv.cpp b/lib/theory_bv.cpp new file mode 100644 index 000000000..e425071d2 --- /dev/null +++ b/lib/theory_bv.cpp @@ -0,0 +1,1635 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_bv.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-06. + +Revision History: + +--*/ +#include"smt_context.h" +#include"theory_bv.h" +#include"ast_ll_pp.h" +#include"ast_pp.h" +#include"smt_model_generator.h" +#include"stats.h" + + +namespace smt { + + void theory_bv::init(context * ctx) { + theory::init(ctx); + m_simplifier = &(ctx->get_simplifier()); + } + + theory_var theory_bv::mk_var(enode * n) { + theory_var r = theory::mk_var(n); + m_find.mk_var(); + m_bits.push_back(literal_vector()); + m_wpos.push_back(0); + m_zero_one_bits.push_back(zero_one_bits()); + get_context().attach_th_var(n, this, r); + return r; + } + + app * theory_bv::mk_bit2bool(app * bv, unsigned idx) { + parameter p(idx); + expr * args[1] = {bv}; + return get_manager().mk_app(get_id(), OP_BIT2BOOL, 1, &p, 1, args); + } + + void theory_bv::mk_bits(theory_var v) { + enode * n = get_enode(v); + app * owner = n->get_owner(); + unsigned bv_size = get_bv_size(n); + context & ctx = get_context(); + literal_vector & bits = m_bits[v]; + for (unsigned i = 0; i < bv_size; i++) { + app * bit = mk_bit2bool(owner, i); + ctx.internalize(bit, true); + bool_var b = ctx.get_bool_var(bit); + bits.push_back(literal(b)); + } + } + + class mk_atom_trail : public trail { + bool_var m_var; + public: + mk_atom_trail(bool_var v):m_var(v) {} + virtual void undo(theory_bv & th) { + theory_bv::atom * a = th.get_bv2a(m_var); + a->~atom(); + th.erase_bv2a(m_var); + } + }; + + void theory_bv::mk_bit2bool(app * n) { + context & ctx = get_context(); + SASSERT(!ctx.b_internalized(n)); + if (!ctx.e_internalized(n->get_arg(0))) { + // This may happen if bit2bool(x) is in a conflict + // clause that is being reinitialized, and x was not reinitialized + // yet. + // So, we internalize x (i.e., n->get_arg(0)) + expr * first_arg = n->get_arg(0); + ctx.internalize(first_arg, false); + SASSERT(ctx.e_internalized(first_arg)); + // In most cases, when x is internalized, its bits are created. + // They are created because x is a bit-vector operation or apply_sort_cnstr is invoked. + // However, there is an exception. The method apply_sort_cnstr is not invoked for ite-terms. + // So, I execute get_var on the enode attached to first_arg. + // This will force a theory variable to be created if it does not already exist. + // This will also force the creation of all bits for x. + enode * first_arg_enode = ctx.get_enode(first_arg); + get_var(first_arg_enode); + SASSERT(ctx.b_internalized(n)); + } + else { + enode * arg = ctx.get_enode(n->get_arg(0)); + // The argument was already internalized, but it may not have a theory variable associated with it. + // For example, for ite-terms the method apply_sort_cnstr is not invoked. + // See comment in the then-branch. + theory_var v_arg = arg->get_th_var(get_id()); + if (v_arg == null_theory_var) { + // The method get_var will create a theory variable for arg. + // As a side-effect the bits for arg will also be created. + get_var(arg); + SASSERT(ctx.b_internalized(n)); + } + else { + SASSERT(v_arg != null_theory_var); + bool_var bv = ctx.mk_bool_var(n); + ctx.set_var_theory(bv, get_id()); + bit_atom * a = new (get_region()) bit_atom(); + insert_bv2a(bv, a); + m_trail_stack.push(mk_atom_trail(bv)); + unsigned idx = n->get_decl()->get_parameter(0).get_int(); + SASSERT(a->m_occs == 0); + a->m_occs = new (get_region()) var_pos_occ(v_arg, idx); + } + } + } + + void theory_bv::process_args(app * n) { + context & ctx = get_context(); + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) + ctx.internalize(n->get_arg(i), false); + } + + enode * theory_bv::mk_enode(app * n) { + context & ctx = get_context(); + enode * e; + if (ctx.e_internalized(n)) { + e = ctx.get_enode(n); + } + else { + e = ctx.mk_enode(n, !m_params.m_bv_reflect, false, m_params.m_bv_cc); + mk_var(e); + } + SASSERT(e->get_th_var(get_id()) != null_theory_var); + return e; + } + + theory_var theory_bv::get_var(enode * n) { + theory_var v = n->get_th_var(get_id()); + if (v == null_theory_var) { + v = mk_var(n); + mk_bits(v); + } + return v; + } + + enode * theory_bv::get_arg(enode * n, unsigned idx) { + if (m_params.m_bv_reflect) { + return n->get_arg(idx); + } + else { + context & ctx = get_context(); + app * arg = to_app(n->get_owner()->get_arg(idx)); + SASSERT(ctx.e_internalized(arg)); + return ctx.get_enode(arg); + } + } + + inline theory_var theory_bv::get_arg_var(enode * n, unsigned idx) { + return get_var(get_arg(n, idx)); + } + + void theory_bv::get_bits(theory_var v, expr_ref_vector & r) { + context & ctx = get_context(); + literal_vector & bits = m_bits[v]; + literal_vector::const_iterator it = bits.begin(); + literal_vector::const_iterator end = bits.end(); + for (; it != end; ++it) { + expr_ref l(get_manager()); + ctx.literal2expr(*it, l); + r.push_back(l); + } + } + + inline void theory_bv::get_bits(enode * n, expr_ref_vector & r) { + get_bits(get_var(n), r); + } + + inline void theory_bv::get_arg_bits(enode * n, unsigned idx, expr_ref_vector & r) { + get_bits(get_arg_var(n, idx), r); + } + + inline void theory_bv::get_arg_bits(app * n, unsigned idx, expr_ref_vector & r) { + context & ctx = get_context(); + app * arg = to_app(n->get_arg(idx)); + SASSERT(ctx.e_internalized(arg)); + get_bits(ctx.get_enode(arg), r); + } + + class add_var_pos_trail : public trail { + theory_bv::bit_atom * m_atom; + public: + add_var_pos_trail(theory_bv::bit_atom * a):m_atom(a) {} + virtual void undo(theory_bv & th) { + SASSERT(m_atom->m_occs); + m_atom->m_occs = m_atom->m_occs->m_next; + } + }; + + /** + \brief v1[idx] = ~v2[idx], then v1 /= v2 is a theory axiom. + */ + void theory_bv::mk_new_diseq_axiom(theory_var v1, theory_var v2, unsigned idx) { + SASSERT(m_bits[v1][idx] == ~m_bits[v2][idx]); + TRACE("bv_diseq_axiom", tout << "found new diseq axiom\n"; display_var(tout, v1); display_var(tout, v2);); + // found new disequality + m_stats.m_num_diseq_static++; + enode * e1 = get_enode(v1); + enode * e2 = get_enode(v2); + literal l = ~(mk_eq(e1->get_owner(), e2->get_owner(), true)); + context & ctx = get_context(); + ctx.mk_th_axiom(get_id(), 1, &l); + if (ctx.relevancy()) { + expr * eq = ctx.bool_var2expr(l.var()); + relevancy_eh * eh = ctx.mk_relevancy_eh(pair_relevancy_eh(e1->get_owner(), e2->get_owner(), eq)); + ctx.add_relevancy_eh(e1->get_owner(), eh); + ctx.add_relevancy_eh(e2->get_owner(), eh); + } + } + + void theory_bv::register_true_false_bit(theory_var v, unsigned idx) { + SASSERT(m_bits[v][idx] == true_literal || m_bits[v][idx] == false_literal); + bool is_true = (m_bits[v][idx] == true_literal); + zero_one_bits & bits = m_zero_one_bits[v]; + bits.push_back(zero_one_bit(v, idx, is_true)); + } + + /** + \brief v[idx] = ~v'[idx], then v /= v' is a theory axiom. + */ + void theory_bv::find_new_diseq_axioms(var_pos_occ * occs, theory_var v, unsigned idx) { + literal l = m_bits[v][idx]; + l.neg(); + while (occs) { + theory_var v2 = occs->m_var; + unsigned idx2 = occs->m_idx; + if (idx == idx2 && m_bits[v2][idx2] == l && get_bv_size(v2) == get_bv_size(v)) + mk_new_diseq_axiom(v, v2, idx); + occs = occs->m_next; + } + } + + /** + \brief Add bit l to the given variable. + */ + void theory_bv::add_bit(theory_var v, literal l) { + context & ctx = get_context(); + literal_vector & bits = m_bits[v]; + unsigned idx = bits.size(); + bits.push_back(l); + if (l.var() == true_bool_var) { + register_true_false_bit(v, idx); + } + else { + theory_id th_id = ctx.get_var_theory(l.var()); + if (th_id == get_id()) { + atom * a = get_bv2a(l.var()); + SASSERT(a && a->is_bit()); + bit_atom * b = static_cast(a); + find_new_diseq_axioms(b->m_occs, v, idx); + 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); + 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(); + insert_bv2a(l.var(), b); + m_trail_stack.push(mk_atom_trail(l.var())); + SASSERT(b->m_occs == 0); + b->m_occs = new (get_region()) var_pos_occ(v, idx); + } + } + } + + void theory_bv::simplify_bit(expr * s, expr_ref & r) { + // proof_ref p(get_manager()); + // if (get_context().at_base_level()) + // m_simplifier->operator()(s, r, p); + // else + r = s; + } + + void theory_bv::init_bits(enode * n, expr_ref_vector const & bits) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + theory_var v = n->get_th_var(get_id()); + SASSERT(v != null_theory_var); + unsigned sz = bits.size(); + SASSERT(get_bv_size(n) == sz); + for (unsigned i = 0; i < sz; i++) { + expr * bit = bits.get(i); + expr_ref s_bit(m); + simplify_bit(bit, s_bit); + ctx.internalize(s_bit, true); + literal l = ctx.get_literal(s_bit.get()); + TRACE("init_bits", tout << "bit " << i << " of #" << n->get_owner_id() << "\n" << mk_ll_pp(s_bit, m) << "\n";); + add_bit(v, l); + } + find_wpos(v); + } + + /** + \brief Find an unassigned bit for m_wpos[v], if such bit cannot be found invoke fixed_var_eh + */ + void theory_bv::find_wpos(theory_var v) { + context & ctx = get_context(); + literal_vector const & bits = m_bits[v]; + unsigned sz = bits.size(); + unsigned & wpos = m_wpos[v]; + unsigned init = wpos; + for (; wpos < sz; wpos++) { + TRACE("find_wpos", tout << "curr bit: " << bits[wpos] << "\n";); + if (ctx.get_assignment(bits[wpos]) == l_undef) { + TRACE("find_wpos", tout << "moved wpos of v" << v << " to " << wpos << "\n";); + return; + } + } + wpos = 0; + for (; wpos < init; wpos++) { + if (ctx.get_assignment(bits[wpos]) == l_undef) { + TRACE("find_wpos", tout << "moved wpos of v" << v << " to " << wpos << "\n";); + return; + } + } + TRACE("find_wpos", tout << "v" << v << " is a fixed variable.\n";); + fixed_var_eh(v); + } + + class fixed_eq_justification : public justification { + theory_bv & m_th; + theory_var m_var1; + theory_var m_var2; + + void mark_bits(conflict_resolution & cr, literal_vector const & bits) { + context & ctx = cr.get_context(); + literal_vector::const_iterator it = bits.begin(); + literal_vector::const_iterator end = bits.end(); + for (; it != end; ++it) { + if (it->var() != true_bool_var) { + if (ctx.get_assignment(*it) == l_true) + cr.mark_literal(*it); + else + cr.mark_literal(~(*it)); + } + } + } + + void get_proof(conflict_resolution & cr, literal l, ptr_buffer & prs, bool & visited) { + if (l.var() == true_bool_var) + return; + proof * pr = 0; + if (cr.get_context().get_assignment(l) == l_true) + pr = cr.get_proof(l); + else + pr = cr.get_proof(~l); + if (pr) + prs.push_back(pr); + else + visited = false; + } + + public: + fixed_eq_justification(theory_bv & th, theory_var v1, theory_var v2): + m_th(th), m_var1(v1), m_var2(v2) { + } + + virtual void get_antecedents(conflict_resolution & cr) { + mark_bits(cr, m_th.m_bits[m_var1]); + mark_bits(cr, m_th.m_bits[m_var2]); + } + + virtual proof * mk_proof(conflict_resolution & cr) { + ptr_buffer prs; + context & ctx = cr.get_context(); + bool visited = true; + literal_vector const & bits1 = m_th.m_bits[m_var1]; + literal_vector const & bits2 = m_th.m_bits[m_var2]; + literal_vector::const_iterator it1 = bits1.begin(); + literal_vector::const_iterator it2 = bits2.begin(); + literal_vector::const_iterator end1 = bits1.end(); + for (; it1 != end1; ++it1, ++it2) { + get_proof(cr, *it1, prs, visited); + get_proof(cr, *it2, prs, visited); + } + if (!visited) + return 0; + expr * fact = ctx.mk_eq_atom(m_th.get_enode(m_var1)->get_owner(), m_th.get_enode(m_var2)->get_owner()); + ast_manager & m = ctx.get_manager(); + return m.mk_th_lemma(get_from_theory(), fact, prs.size(), prs.c_ptr()); + } + + virtual theory_id get_from_theory() const { + return m_th.get_id(); + } + + virtual char const * get_name() const { return "bv-fixed-eq"; } + + }; + + void theory_bv::fixed_var_eh(theory_var v) { + numeral val; + bool r = get_fixed_value(v, val); + SASSERT(r); + unsigned sz = get_bv_size(v); + value_sort_pair key(val, sz); + theory_var v2; + if (m_fixed_var_table.find(key, v2)) { + numeral val2; + if (v2 < static_cast(get_num_vars()) && is_bv(v2) && + get_bv_size(v2) == sz && get_fixed_value(v2, val2) && val == val2) { + if (get_enode(v)->get_root() != get_enode(v2)->get_root()) { + SASSERT(get_bv_size(v) == get_bv_size(v2)); + context & ctx = get_context(); + justification * js = ctx.mk_justification(fixed_eq_justification(*this, v, v2)); + TRACE("fixed_var_eh", tout << "detected equality: v" << v << " = v" << v2 << "\n"; + display_var(tout, v); + display_var(tout, v2);); + m_stats.m_num_th2core_eq++; + ctx.assign_eq(get_enode(v), get_enode(v2), eq_justification(js)); + } + } + else { + // the original fixed variable v2 was deleted or it is not fixed anymore. + m_fixed_var_table.erase(key); + m_fixed_var_table.insert(key, v); + } + } + else { + m_fixed_var_table.insert(key, v); + } + } + + bool theory_bv::get_fixed_value(theory_var v, numeral & result) const { + context & ctx = get_context(); + result.reset(); + literal_vector const & bits = m_bits[v]; + literal_vector::const_iterator it = bits.begin(); + literal_vector::const_iterator end = bits.end(); + for (unsigned i = 0; it != end; ++it, ++i) { + switch (ctx.get_assignment(*it)) { + case l_false: break; + case l_undef: return false; + case l_true: result += m_bb.power(i); break; + } + } + return true; + } + + bool theory_bv::get_fixed_value(app* x, numeral & result) const { + context& ctx = get_context(); + if (!ctx.e_internalized(x)) return false; + enode * e = ctx.get_enode(x); + theory_var v = e->get_th_var(get_id()); + return get_fixed_value(v, result); + } + + + void theory_bv::internalize_num(app * n) { + SASSERT(!get_context().e_internalized(n)); + ast_manager & m = get_manager(); + numeral val; + unsigned sz; + m_util.is_numeral(n, val, sz); + enode * e = mk_enode(n); + // internalizer is marking enodes as interpreted whenever the associated ast is a value and a constant. + // e->mark_as_interpreted(); + theory_var v = e->get_th_var(get_id()); + expr_ref_vector bits(m); + m_bb.num2bits(val, sz, bits); + SASSERT(bits.size() == sz); + literal_vector & c_bits = m_bits[v]; + for (unsigned i = 0; i < sz; i++) { + expr * l = bits.get(i); + if (m.is_true(l)) { + c_bits.push_back(true_literal); + } + else { + SASSERT(m.is_false(l)); + c_bits.push_back(false_literal); + } + register_true_false_bit(v, i); + } + fixed_var_eh(v); + } + + void theory_bv::internalize_mkbv(app* n) { + ast_manager& m = get_manager(); + expr_ref_vector bits(m); + process_args(n); + enode * e = mk_enode(n); + bits.append(n->get_num_args(), n->get_args()); + init_bits(e, bits); + } + + void theory_bv::internalize_bv2int(app* n) { + SASSERT(!get_context().e_internalized(n)); + ast_manager & m = get_manager(); + context& ctx = get_context(); + TRACE("bv", tout << mk_bounded_pp(n, m) << "\n";); + process_args(n); + mk_enode(n); + if (!ctx.relevancy()) { + assert_bv2int_axiom(n); + } + } + + + void theory_bv::assert_bv2int_axiom(app * n) { + // + // create the axiom: + // n = bv2int(k) = ite(bit2bool(k[sz-1],2^{sz-1},0) + ... + ite(bit2bool(k[0],1,0)) + // + SASSERT(get_context().e_internalized(n)); + SASSERT(m_util.is_bv2int(n)); + ast_manager & m = get_manager(); + TRACE("bv2int_bug", tout << "bv2int:\n" << mk_pp(n, m) << "\n";); + context & ctx = get_context(); + sort * int_sort = m.get_sort(n); + app * k = to_app(n->get_arg(0)); + SASSERT(m_util.is_bv_sort(m.get_sort(k))); + expr_ref_vector k_bits(m); + enode * k_enode = mk_enode(k); + get_bits(k_enode, k_bits); + unsigned sz = m_util.get_bv_size(k); + expr_ref_vector args(m); + expr_ref zero(m_autil.mk_numeral(numeral(0), int_sort), m); + numeral num(1); + for (unsigned i = 0; i < sz; ++i) { + // Remark: A previous version of this method was using + // + // expr* b = mk_bit2bool(k,i); + // + // This is not correct. The predicate bit2bool is an + // internal construct, and it was not meant for building + // axioms directly. It is used to represent the bits of a + // constant, and in some cases the bits of a complicated + // bit-vector expression. In most cases, the bits of a + // composite bit-vector expression T are just boolean + // combinations of bit2bool atoms of the bit-vector + // constants contained in T. So, instead of using + // mk_bit2bool to access a particular bit of T, we should + // use the method get_bits. + // + expr * b = k_bits.get(i); + expr_ref n(m_autil.mk_numeral(num, int_sort), m); + args.push_back(m.mk_ite(b, n, zero)); + num *= numeral(2); + } + expr_ref sum(m); + arith_simp().mk_add(sz, args.c_ptr(), sum); + TRACE("bv", + tout << mk_pp(n, m) << "\n"; + tout << mk_pp(sum, m) << "\n"; + ); + + literal l(mk_eq(n, sum, false)); + + ctx.mark_as_relevant(l); + ctx.mk_th_axiom(get_id(), 1, &l); + } + + void theory_bv::internalize_int2bv(app* n) { + SASSERT(!get_context().e_internalized(n)); + SASSERT(n->get_num_args() == 1); + context& ctx = get_context(); + process_args(n); + mk_enode(n); + mk_bits(ctx.get_enode(n)->get_th_var(get_id())); + if (!ctx.relevancy()) { + assert_int2bv_axiom(n); + } + } + + void theory_bv::assert_int2bv_axiom(app* n) { + // + // create the axiom: + // bv2int(n) = e mod 2^bit_width + // + // where n = int2bv(e) + // + SASSERT(get_context().e_internalized(n)); + SASSERT(m_util.is_int2bv(n)); + ast_manager & m = get_manager(); + context& ctx = get_context(); + + parameter param(m_autil.mk_int()); + expr* n_expr = n; + expr* lhs = m.mk_app(get_id(), OP_BV2INT, 1, ¶m, 1, &n_expr); + unsigned sz = m_util.get_bv_size(n); + numeral mod = power(numeral(2), sz); + expr* rhs = m_autil.mk_mod(n->get_arg(0), m_autil.mk_numeral(mod, true)); + + literal l(mk_eq(lhs, rhs, false)); + ctx.mark_as_relevant(l); + ctx.mk_th_axiom(get_id(), 1, &l); + + TRACE("bv", + tout << mk_pp(lhs, m) << " == \n"; + tout << mk_pp(rhs, m) << "\n"; + ); + } + + +#define MK_UNARY(NAME, BLAST_OP) \ + void theory_bv::NAME(app * n) { \ + SASSERT(!get_context().e_internalized(n)); \ + SASSERT(n->get_num_args() == 1); \ + process_args(n); \ + ast_manager & m = get_manager(); \ + enode * e = mk_enode(n); \ + expr_ref_vector arg1_bits(m), bits(m); \ + get_arg_bits(e, 0, arg1_bits); \ + m_bb.BLAST_OP(arg1_bits.size(), arg1_bits.c_ptr(), bits); \ + init_bits(e, bits); \ + } + +#define MK_BINARY(NAME, BLAST_OP) \ + void theory_bv::NAME(app * n) { \ + SASSERT(!get_context().e_internalized(n)); \ + SASSERT(n->get_num_args() == 2); \ + process_args(n); \ + ast_manager & m = get_manager(); \ + enode * e = mk_enode(n); \ + expr_ref_vector arg1_bits(m), arg2_bits(m), bits(m); \ + get_arg_bits(e, 0, arg1_bits); \ + get_arg_bits(e, 1, arg2_bits); \ + SASSERT(arg1_bits.size() == arg2_bits.size()); \ + m_bb.BLAST_OP(arg1_bits.size(), arg1_bits.c_ptr(), arg2_bits.c_ptr(), bits); \ + init_bits(e, bits); \ + } + + +#define MK_AC_BINARY(NAME, BLAST_OP) \ + void theory_bv::NAME(app * n) { \ + SASSERT(!get_context().e_internalized(n)); \ + SASSERT(n->get_num_args() >= 2); \ + process_args(n); \ + ast_manager & m = get_manager(); \ + enode * e = mk_enode(n); \ + expr_ref_vector arg_bits(m); \ + expr_ref_vector bits(m); \ + expr_ref_vector new_bits(m); \ + unsigned i = n->get_num_args(); \ + --i; \ + get_arg_bits(e, i, bits); \ + while (i > 0) { \ + --i; \ + arg_bits.reset(); \ + get_arg_bits(e, i, arg_bits); \ + SASSERT(arg_bits.size() == bits.size()); \ + new_bits.reset(); \ + m_bb.BLAST_OP(arg_bits.size(), arg_bits.c_ptr(), bits.c_ptr(), new_bits); \ + bits.swap(new_bits); \ + } \ + init_bits(e, bits); \ + } + + +#define MK_BINARY_COND(NAME, BLAST_OP) \ + void theory_bv::NAME(app * n) { \ + SASSERT(!get_context().e_internalized(n)); \ + SASSERT(n->get_num_args() == 2); \ + process_args(n); \ + ast_manager & m = get_manager(); \ + context& ctx = get_context(); \ + enode * e = mk_enode(n); \ + expr_ref_vector arg1_bits(m), arg2_bits(m), bits(m); \ + expr_ref cond(m), s_cond(m); \ + get_arg_bits(e, 0, arg1_bits); \ + get_arg_bits(e, 1, arg2_bits); \ + SASSERT(arg1_bits.size() == arg2_bits.size()); \ + m_bb.BLAST_OP(arg1_bits.size(), arg1_bits.c_ptr(), arg2_bits.c_ptr(), bits, cond); \ + init_bits(e, bits); \ + simplify_bit(cond, s_cond); \ + ctx.internalize(s_cond, true); \ + literal l(ctx.get_literal(s_cond)); \ + ctx.mark_as_relevant(l); \ + ctx.mk_th_axiom(get_id(), 1, &l); \ + TRACE("bv", tout << mk_pp(cond, get_manager()) << "\n"; tout << l << "\n";); \ + } + + MK_UNARY(internalize_not, mk_not); + MK_UNARY(internalize_redand, mk_redand); + MK_UNARY(internalize_redor, mk_redor); + + MK_AC_BINARY(internalize_add, mk_adder); + MK_AC_BINARY(internalize_mul, mk_multiplier); + MK_BINARY(internalize_udiv, mk_udiv); + MK_BINARY(internalize_sdiv, mk_sdiv); + MK_BINARY(internalize_urem, mk_urem); + MK_BINARY(internalize_srem, mk_srem); + MK_BINARY(internalize_smod, mk_smod); + MK_BINARY(internalize_shl, mk_shl); + MK_BINARY(internalize_lshr, mk_lshr); + MK_BINARY(internalize_ashr, mk_ashr); + MK_BINARY(internalize_ext_rotate_left, mk_ext_rotate_left); + MK_BINARY(internalize_ext_rotate_right, mk_ext_rotate_right); + MK_AC_BINARY(internalize_and, mk_and); + MK_AC_BINARY(internalize_or, mk_or); + MK_AC_BINARY(internalize_xor, mk_xor); + MK_AC_BINARY(internalize_nand, mk_nand); + MK_AC_BINARY(internalize_nor, mk_nor); + MK_AC_BINARY(internalize_xnor, mk_xnor); + MK_BINARY(internalize_comp, mk_comp); + +#define MK_PARAMETRIC_UNARY(NAME, BLAST_OP) \ + void theory_bv::NAME(app * n) { \ + SASSERT(!get_context().e_internalized(n)); \ + SASSERT(n->get_num_args() == 1); \ + process_args(n); \ + ast_manager & m = get_manager(); \ + enode * e = mk_enode(n); \ + expr_ref_vector arg1_bits(m), bits(m); \ + get_arg_bits(e, 0, arg1_bits); \ + unsigned param = n->get_decl()->get_parameter(0).get_int(); \ + m_bb.BLAST_OP(arg1_bits.size(), arg1_bits.c_ptr(), param, bits); \ + init_bits(e, bits); \ + } + + MK_PARAMETRIC_UNARY(internalize_sign_extend, mk_sign_extend); + MK_PARAMETRIC_UNARY(internalize_zero_extend, mk_zero_extend); + MK_PARAMETRIC_UNARY(internalize_rotate_left, mk_rotate_left); + MK_PARAMETRIC_UNARY(internalize_rotate_right, mk_rotate_right); + + void theory_bv::internalize_concat(app * n) { + process_args(n); + enode * e = mk_enode(n); + theory_var v = e->get_th_var(get_id()); + unsigned num_args = n->get_num_args(); + unsigned i = num_args; + while (i > 0) { + i--; + theory_var arg = get_arg_var(e, i); + literal_vector::const_iterator it = m_bits[arg].begin(); + literal_vector::const_iterator end = m_bits[arg].end(); + for (; it != end; ++it) + add_bit(v, *it); + } + find_wpos(v); + } + + void theory_bv::internalize_extract(app * n) { + SASSERT(n->get_num_args() == 1); + process_args(n); + enode * e = mk_enode(n); + theory_var v = e->get_th_var(get_id()); + theory_var arg = get_arg_var(e, 0); + unsigned start = n->get_decl()->get_parameter(1).get_int(); + unsigned end = n->get_decl()->get_parameter(0).get_int(); + SASSERT(start <= end); + literal_vector & arg_bits = m_bits[arg]; + for (unsigned i = start; i <= end; ++i) + add_bit(v, arg_bits[i]); + find_wpos(v); + } + + bool theory_bv::internalize_term(app * term) { + SASSERT(term->get_family_id() == get_family_id()); + TRACE("bv", tout << "internalizing term: " << mk_bounded_pp(term, get_manager()) << "\n";); + if (approximate_term(term)) { + return false; + } + switch (term->get_decl_kind()) { + case OP_BV_NUM: internalize_num(term); return true; + case OP_BADD: internalize_add(term); return true; + case OP_BMUL: internalize_mul(term); return true; + case OP_BSDIV_I: internalize_sdiv(term); return true; + case OP_BUDIV_I: internalize_udiv(term); return true; + case OP_BSREM_I: internalize_srem(term); return true; + case OP_BUREM_I: internalize_urem(term); return true; + case OP_BSMOD_I: internalize_smod(term); return true; + case OP_BAND: internalize_and(term); return true; + case OP_BOR: internalize_or(term); return true; + case OP_BNOT: internalize_not(term); return true; + case OP_BXOR: internalize_xor(term); return true; + case OP_BNAND: internalize_nand(term); return true; + case OP_BNOR: internalize_nor(term); return true; + case OP_BXNOR: internalize_xnor(term); return true; + case OP_CONCAT: internalize_concat(term); return true; + case OP_SIGN_EXT: internalize_sign_extend(term); return true; + case OP_ZERO_EXT: internalize_zero_extend(term); return true; + case OP_EXTRACT: internalize_extract(term); return true; + case OP_BREDOR: internalize_redor(term); return true; + case OP_BREDAND: internalize_redand(term); return true; + case OP_BCOMP: internalize_comp(term); return true; + case OP_BSHL: internalize_shl(term); return true; + case OP_BLSHR: internalize_lshr(term); return true; + case OP_BASHR: internalize_ashr(term); return true; + case OP_ROTATE_LEFT: internalize_rotate_left(term); return true; + case OP_ROTATE_RIGHT: internalize_rotate_right(term); return true; + case OP_EXT_ROTATE_LEFT: internalize_ext_rotate_left(term); return true; + case OP_EXT_ROTATE_RIGHT: internalize_ext_rotate_right(term); return true; + case OP_BSDIV0: return false; + case OP_BUDIV0: return false; + case OP_BSREM0: return false; + case OP_BUREM0: return false; + case OP_BSMOD0: return false; + case OP_MKBV: internalize_mkbv(term); return true; + case OP_INT2BV: + if (m_params.m_bv_enable_int2bv2int) { + internalize_int2bv(term); + } + return m_params.m_bv_enable_int2bv2int; + case OP_BV2INT: + if (m_params.m_bv_enable_int2bv2int) { + internalize_bv2int(term); + } + return m_params.m_bv_enable_int2bv2int; + default: + TRACE("bv_op", tout << "unsupported operator: " << mk_ll_pp(term, get_manager()) << "\n";); + UNREACHABLE(); + return false; + } + } + +#define MK_NO_OVFL(NAME, OP) \ + void theory_bv::NAME(app *n) { \ + SASSERT(n->get_num_args() == 2); \ + process_args(n); \ + ast_manager & m = get_manager(); \ + context & ctx = get_context(); \ + expr_ref_vector arg1_bits(m), arg2_bits(m); \ + get_arg_bits(n, 0, arg1_bits); \ + get_arg_bits(n, 1, arg2_bits); \ + expr_ref out(m); \ + m_bb.OP(arg1_bits.size(), arg1_bits.c_ptr(), arg2_bits.c_ptr(), out); \ + expr_ref s_out(m); \ + simplify_bit(out, s_out); \ + ctx.internalize(s_out, true); \ + literal def = ctx.get_literal(s_out); \ + literal l(ctx.mk_bool_var(n)); \ + ctx.set_var_theory(l.var(), get_id()); \ + le_atom * a = new (get_region()) le_atom(l, def); /* abuse le_atom */ \ + insert_bv2a(l.var(), a); \ + m_trail_stack.push(mk_atom_trail(l.var())); \ + /* smul_no_overflow and umul_no_overflow are using the le_atom (THIS IS A BIG HACK)... */ \ + /* the connection between the l and def was never realized when */ \ + /* relevancy() is true and m_bv_lazy_le is false (the default configuration). */ \ + /* So, we need to check also the m_bv_lazy_le flag here. */ \ + /* Maybe, we should rename the le_atom to bridge_atom, and m_bv_lazy_le option to m_bv_lazy_bridge. */ \ + if (!ctx.relevancy() || !m_params.m_bv_lazy_le) { \ + ctx.mk_th_axiom(get_id(), l, ~def); \ + ctx.mk_th_axiom(get_id(), ~l, def); \ + } \ + } + + MK_NO_OVFL(internalize_umul_no_overflow, mk_umul_no_overflow); + MK_NO_OVFL(internalize_smul_no_overflow, mk_smul_no_overflow); + MK_NO_OVFL(internalize_smul_no_underflow, mk_smul_no_underflow); + + template + void theory_bv::internalize_le(app * n) { + SASSERT(n->get_num_args() == 2); + process_args(n); + ast_manager & m = get_manager(); + context & ctx = get_context(); + expr_ref_vector arg1_bits(m), arg2_bits(m); + get_arg_bits(n, 0, arg1_bits); + get_arg_bits(n, 1, arg2_bits); + expr_ref le(m); + if (Signed) + m_bb.mk_sle(arg1_bits.size(), arg1_bits.c_ptr(), arg2_bits.c_ptr(), le); + else + m_bb.mk_ule(arg1_bits.size(), arg1_bits.c_ptr(), arg2_bits.c_ptr(), le); + expr_ref s_le(m); + simplify_bit(le, s_le); + ctx.internalize(s_le, true); + literal def = ctx.get_literal(s_le); + literal l(ctx.mk_bool_var(n)); + ctx.set_var_theory(l.var(), get_id()); + le_atom * a = new (get_region()) le_atom(l, def); + insert_bv2a(l.var(), a); + m_trail_stack.push(mk_atom_trail(l.var())); + if (!ctx.relevancy() || !m_params.m_bv_lazy_le) { + ctx.mk_th_axiom(get_id(), l, ~def); + ctx.mk_th_axiom(get_id(), ~l, def); + } + } + + bool theory_bv::internalize_carry(app * n, bool gate_ctx) { + context & ctx = get_context(); + ctx.internalize(n->get_arg(0), true); + ctx.internalize(n->get_arg(1), true); + ctx.internalize(n->get_arg(2), true); + bool is_new_var = false; + bool_var v; + if (!ctx.b_internalized(n)) { + is_new_var = true; + v = ctx.mk_bool_var(n); + literal r(v); + literal l1 = ctx.get_literal(n->get_arg(0)); + literal l2 = ctx.get_literal(n->get_arg(1)); + literal l3 = ctx.get_literal(n->get_arg(2)); + ctx.mk_gate_clause(~r, l1, l2); + ctx.mk_gate_clause(~r, l1, l3); + ctx.mk_gate_clause(~r, l2, l3); + ctx.mk_gate_clause( r, ~l1, ~l2); + ctx.mk_gate_clause( r, ~l1, ~l3); + ctx.mk_gate_clause( r, ~l2, ~l3); + } + else { + v = ctx.get_bool_var(n); + } + + if (!ctx.e_internalized(n) && !gate_ctx) { + bool suppress_args = true; + bool merge_tf = !gate_ctx; + ctx.mk_enode(n, suppress_args, merge_tf, true); + ctx.set_enode_flag(v, is_new_var); + } + return true; + } + + bool theory_bv::internalize_xor3(app * n, bool gate_ctx) { + context & ctx = get_context(); + ctx.internalize(n->get_arg(0), true); + ctx.internalize(n->get_arg(1), true); + ctx.internalize(n->get_arg(2), true); + bool is_new_var = false; + bool_var v; + if (!ctx.b_internalized(n)) { + is_new_var = true; + v = ctx.mk_bool_var(n); + literal r(v); + literal l1 = ctx.get_literal(n->get_arg(0)); + literal l2 = ctx.get_literal(n->get_arg(1)); + literal l3 = ctx.get_literal(n->get_arg(2)); + ctx.mk_gate_clause(~r, l1, l2, l3); + ctx.mk_gate_clause(~r, ~l1, ~l2, l3); + ctx.mk_gate_clause(~r, ~l1, l2, ~l3); + ctx.mk_gate_clause(~r, l1, ~l2, ~l3); + ctx.mk_gate_clause( r, ~l1, l2, l3); + ctx.mk_gate_clause( r, l1, ~l2, l3); + ctx.mk_gate_clause( r, l1, l2, ~l3); + ctx.mk_gate_clause( r, ~l1, ~l2, ~l3); + } + else { + v = ctx.get_bool_var(n); + } + + if (!ctx.e_internalized(n) && !gate_ctx) { + bool suppress_args = true; + bool merge_tf = !gate_ctx; + ctx.mk_enode(n, suppress_args, merge_tf, true); + ctx.set_enode_flag(v, is_new_var); + } + return true; + } + + bool theory_bv::internalize_atom(app * atom, bool gate_ctx) { + TRACE("bv", tout << "internalizing atom: " << mk_bounded_pp(atom, get_manager()) << "\n";); + SASSERT(atom->get_family_id() == get_family_id()); + if (approximate_term(atom)) { + return false; + } + switch (atom->get_decl_kind()) { + case OP_BIT2BOOL: mk_bit2bool(atom); return true; + case OP_ULEQ: internalize_le(atom); return true; + case OP_SLEQ: internalize_le(atom); return true; + case OP_XOR3: return internalize_xor3(atom, gate_ctx); + case OP_CARRY: return internalize_carry(atom, gate_ctx); + case OP_BUMUL_NO_OVFL: internalize_umul_no_overflow(atom); return true; + case OP_BSMUL_NO_OVFL: internalize_smul_no_overflow(atom); return true; + case OP_BSMUL_NO_UDFL: internalize_smul_no_underflow(atom); return true; + default: + UNREACHABLE(); + return false; + } + } + + // + // Determine whether bit-vector expression should be approximated + // based on the number of bits used by the arguments. + // + bool theory_bv::approximate_term(app* n) { + if (m_params.m_bv_blast_max_size == INT_MAX) { + return false; + } + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i <= num_args; i++) { + expr* arg = (i == num_args)?n:n->get_arg(i); + sort* s = get_manager().get_sort(arg); + s = get_manager().get_sort(arg); + if (m_util.is_bv_sort(s) && m_util.get_bv_size(arg) > m_params.m_bv_blast_max_size) { + if (!m_approximates_large_bvs) { + TRACE("bv", tout << "found large size bit-vector:\n" << mk_pp(n, get_manager()) << "\n";); + get_context().push_trail(value_trail(m_approximates_large_bvs)); + m_approximates_large_bvs = true; + } + return true; + } + } + return false; + + } + + void theory_bv::apply_sort_cnstr(enode * n, sort * s) { + if (!is_attached_to_var(n) && !approximate_term(n->get_owner())) { + theory_var v = mk_var(n); + mk_bits(v); + } + } + + void theory_bv::new_eq_eh(theory_var v1, theory_var v2) { + TRACE("bv_eq", tout << "new_eq: " << mk_pp(get_enode(v1)->get_owner(), get_manager()) << " = " << mk_pp(get_enode(v2)->get_owner(), get_manager()) << "\n";); + TRACE("bv", tout << "new_eq_eh v" << v1 << " = v" << v2 << + " relevant1: " << get_context().is_relevant(get_enode(v1)) << + " relevant2: " << get_context().is_relevant(get_enode(v2)) << "\n";); + m_find.merge(v1, v2); + } + + void theory_bv::new_diseq_eh(theory_var v1, theory_var v2) { + if (is_bv(v1)) { + expand_diseq(v1, v2); + } + } + + void theory_bv::expand_diseq(theory_var v1, theory_var v2) { + SASSERT(get_bv_size(v1) == get_bv_size(v2)); + context & ctx = get_context(); + ast_manager & m = get_manager(); +#ifdef _TRACE + unsigned num_bool_vars = ctx.get_num_bool_vars(); +#endif + literal_vector & lits = m_tmp_literals; + lits.reset(); + lits.push_back(mk_eq(get_enode(v1)->get_owner(), get_enode(v2)->get_owner(), true)); + literal_vector const & bits1 = m_bits[v1]; + literal_vector::const_iterator it1 = bits1.begin(); + literal_vector::const_iterator end1 = bits1.end(); + literal_vector const & bits2 = m_bits[v2]; + literal_vector::const_iterator it2 = bits2.begin(); + for (; it1 != end1; ++it1, ++it2) { + if (*it1 == ~(*it2)) + return; // static diseq + } + it1 = bits1.begin(); + it2 = bits2.begin(); + for (; it1 != end1; ++it1, ++it2) { + expr_ref l1(m), l2(m), diff(m); + ctx.literal2expr(*it1, l1); + ctx.literal2expr(*it2, l2); + m_bb.mk_xor(l1, l2, diff); + ctx.internalize(diff, true); + literal arg = ctx.get_literal(diff); + lits.push_back(arg); + } + m_stats.m_num_diseq_dynamic++; + ctx.mk_th_axiom(get_id(), lits.size(), lits.c_ptr()); + TRACE_CODE({ + static unsigned num = 0; + static unsigned new_bool_vars = 0; + new_bool_vars += (ctx.get_num_bool_vars() - num_bool_vars); + if (num % 1000 == 0) + TRACE("expand_diseq", tout << "num: " << num << " " << new_bool_vars << "\n";); + num++; + }); + } + + void theory_bv::assign_eh(bool_var v, bool is_true) { + context & ctx = get_context(); + atom * a = get_bv2a(v); + TRACE("bv", tout << "assert: v" << v << " #" << ctx.bool_var2expr(v)->get_id() << " is_true: " << is_true << "\n";); + if (a->is_bit()) { + // The following optimization is not correct. + // Boolean variables created for performing bit-blasting are reused. + // See regression\trevor6.smt for example. + // + // if (ctx.has_th_justification(v, get_id())) { + // TRACE("bv", tout << "has th_justification\n";); + // return; + // } + m_prop_queue.reset(); + bit_atom * b = static_cast(a); + var_pos_occ * curr = b->m_occs; + while (curr) { + m_prop_queue.push_back(var_pos(curr->m_var, curr->m_idx)); + curr = curr->m_next; + } + TRACE("bv", tout << m_prop_queue.size() << "\n";); + propagate_bits(); + } + } + + void theory_bv::propagate_bits() { + context & ctx = get_context(); + for (unsigned i = 0; i < m_prop_queue.size(); i++) { + var_pos const & entry = m_prop_queue[i]; + theory_var v = entry.first; + unsigned idx = entry.second; + + if (m_wpos[v] == idx) + find_wpos(v); + + + literal_vector & bits = m_bits[v]; + literal bit = bits[idx]; + lbool val = ctx.get_assignment(bit); + theory_var v2 = next(v); + TRACE("bv_bit_prop", tout << "propagating #" << get_enode(v)->get_owner_id() << "[" << idx << "] = " << val << "\n";); + while (v2 != v) { + literal_vector & bits2 = m_bits[v2]; + literal bit2 = bits2[idx]; + SASSERT(bit != ~bit2); + lbool val2 = ctx.get_assignment(bit2); + TRACE("bv_bit_prop", tout << "propagating #" << get_enode(v2)->get_owner_id() << "[" << idx << "] = " << val2 << "\n";); + if (val != val2) { + literal antecedent = bit; + literal consequent = bit2; + if (val == l_false) { + antecedent.neg(); + consequent.neg(); + } + SASSERT(ctx.get_assignment(antecedent) == l_true); + assign_bit(consequent, v, v2, idx, antecedent, false); + if (ctx.inconsistent()) { + TRACE("bv_bit_prop", tout << "inconsistent " << bit << " " << bit2 << "\n";); + return; + } + } + v2 = next(v2); + } + } + m_prop_queue.reset(); + TRACE("bv_bit_prop", tout << "done propagating\n";); + } + + void theory_bv::assign_bit(literal consequent, theory_var v1, theory_var v2, unsigned idx, literal antecedent, bool propagate_eqc) { + m_stats.m_num_bit2core++; + context & ctx = get_context(); + SASSERT(ctx.get_assignment(antecedent) == l_true); + SASSERT(m_bits[v2][idx].var() == consequent.var()); + SASSERT(consequent.var() != antecedent.var()); + TRACE("bv_bit_prop", tout << "assigning: "; ctx.display_literal(tout, consequent); + tout << " using "; ctx.display_literal(tout, antecedent); + tout << " #" << get_enode(v1)->get_owner_id() << " #" << get_enode(v2)->get_owner_id() << " idx: " << idx << "\n"; + tout << "propagate_eqc: " << propagate_eqc << "\n";); + if (consequent == false_literal) { + m_stats.m_num_conflicts++; + ctx.set_conflict(mk_bit_eq_justification(v1, v2, consequent, antecedent)); + } + else { + ctx.assign(consequent, mk_bit_eq_justification(v1, v2, consequent, antecedent)); + if (m_wpos[v2] == idx) + find_wpos(v2); + // REMARK: bit_eq_justification is marked as a theory_bv justification. + // Thus, the assignment to consequent will not be notified back to the theory. + // So, we need to propagate the assignment to other bits. + bool_var bv = consequent.var(); + atom * a = get_bv2a(bv); + SASSERT(a->is_bit()); + bit_atom * b = static_cast(a); + var_pos_occ * curr = b->m_occs; + while (curr) { + TRACE("assign_bit_bug", tout << "curr->m_var: v" << curr->m_var << ", curr->m_idx: " << curr->m_idx << ", v2: v" << v2 << ", idx: " << idx << "\n"; + tout << "find(curr->m_var): v" << find(curr->m_var) << ", find(v2): v" << find(v2) << "\n"; + tout << "is bit of #" << get_enode(curr->m_var)->get_owner_id() << "\n"; + ); + // If find(curr->m_var) == find(v2) && curr->m_idx == idx and propagate_eqc == false, then + // this bit will be propagated to the equivalence class of v2 by assign_bit caller. + if (propagate_eqc || find(curr->m_var) != find(v2) || curr->m_idx != idx) + m_prop_queue.push_back(var_pos(curr->m_var, curr->m_idx)); + curr = curr->m_next; + } + } + } + + void theory_bv::relevant_eh(app * n) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + if (m.is_bool(n)) { + bool_var v = ctx.get_bool_var(n); + atom * a = get_bv2a(v); + if (a && !a->is_bit()) { + le_atom * le = static_cast(a); + ctx.mark_as_relevant(le->m_def); + if (m_params.m_bv_lazy_le) { + ctx.mk_th_axiom(get_id(), le->m_var, ~le->m_def); + ctx.mk_th_axiom(get_id(), ~le->m_var, le->m_def); + } + } + } + else if (m_params.m_bv_enable_int2bv2int && m_util.is_bv2int(n)) { + ctx.mark_as_relevant(n->get_arg(0)); + assert_bv2int_axiom(n); + } + else if (m_params.m_bv_enable_int2bv2int && m_util.is_int2bv(n)) { + ctx.mark_as_relevant(n->get_arg(0)); + assert_int2bv_axiom(n); + } + else if (ctx.e_internalized(n)) { + enode * e = ctx.get_enode(n); + theory_var v = e->get_th_var(get_id()); + if (v != null_theory_var) { + literal_vector & bits = m_bits[v]; + literal_vector::iterator it = bits.begin(); + literal_vector::iterator end = bits.end(); + for (; it != end; ++it) + ctx.mark_as_relevant(*it); + } + } + } + + void theory_bv::push_scope_eh() { + theory::push_scope_eh(); + m_trail_stack.push_scope(); + } + + void theory_bv::pop_scope_eh(unsigned num_scopes) { + TRACE("bv",tout << num_scopes << "\n";); + m_trail_stack.pop_scope(num_scopes); + unsigned num_old_vars = get_old_num_vars(num_scopes); + m_bits.shrink(num_old_vars); + m_wpos.shrink(num_old_vars); + m_zero_one_bits.shrink(num_old_vars); + theory::pop_scope_eh(num_scopes); + } + + final_check_status theory_bv::final_check_eh() { + SASSERT(check_invariant()); + if (m_approximates_large_bvs) { + return FC_GIVEUP; + } + return FC_DONE; + } + + void theory_bv::reset_eh() { + pop_scope_eh(m_trail_stack.get_num_scopes()); + m_bool_var2atom.reset(); + m_fixed_var_table.reset(); + theory::reset_eh(); + } + + theory_bv::theory_bv(ast_manager & m, theory_bv_params const & params, bit_blaster_params const & bb_params): + theory(m.get_family_id("bv")), + m_params(params), + m_util(m), + m_autil(m), + m_simplifier(0), + m_bb(m, bb_params), + m_trail_stack(*this), + m_find(*this), + m_approximates_large_bvs(false) { + } + + theory_bv::~theory_bv() { + } + + void theory_bv::merge_eh(theory_var r1, theory_var r2, theory_var v1, theory_var v2) { + TRACE("bv", tout << "merging: #" << get_enode(v1)->get_owner_id() << " #" << get_enode(v2)->get_owner_id() << "\n";); + TRACE("bv_bit_prop", tout << "merging: #" << get_enode(v1)->get_owner_id() << " #" << get_enode(v2)->get_owner_id() << "\n";); + if (!merge_zero_one_bits(r1, r2)) { + TRACE("bv", tout << "conflict detected\n";); + return; // conflict was detected + } + m_prop_queue.reset(); + context & ctx = get_context(); + literal_vector & bits1 = m_bits[v1]; + literal_vector & bits2 = m_bits[v2]; + SASSERT(bits1.size() == bits2.size()); + unsigned sz = bits1.size(); + bool changed; + TRACE("bv", tout << "bits size: " << sz << "\n";); + do { + // This outerloop is necessary to avoid missing propagation steps. + // For example, let's assume that bits1 and bits2 contains the following + // sequence of bits: + // b4 b3 b2 b1 + // b5 b4 b3 b2 + // Let's also assume that b1 is assigned, and b2, b3, b4, and b5 are not. + // Only the propagation from b1 to b2 is performed by the first iteration of this + // loop. + // + // In the worst case, we need to execute this loop bits1.size() times. + // + // Remark: the assignment to b2 is marked as a bv theory propagation, + // then it is not notified to the bv theory. + changed = false; + for (unsigned idx = 0; idx < sz; idx++) { + literal bit1 = bits1[idx]; + literal bit2 = bits2[idx]; + CTRACE("bv_bug", bit1 == ~bit2, display_var(tout, v1); display_var(tout, v2); tout << "idx: " << idx << "\n";); + SASSERT(bit1 != ~bit2); + lbool val1 = ctx.get_assignment(bit1); + lbool val2 = ctx.get_assignment(bit2); + if (val1 == val2) + continue; + changed = true; + if (val1 != l_undef && val2 != l_undef) { + TRACE("bv", tout << "inconsistent "; display_var(tout, v1); display_var(tout, v2); tout << "idx: " << idx << "\n";); + } + if (val1 != l_undef) { + literal antecedent = bit1; + literal consequent = bit2; + if (val1 == l_false) { + consequent.neg(); + antecedent.neg(); + } + assign_bit(consequent, v1, v2, idx, antecedent, true); + } + else if (val2 != l_undef) { + literal antecedent = bit2; + literal consequent = bit1; + if (val2 == l_false) { + consequent.neg(); + antecedent.neg(); + } + assign_bit(consequent, v2, v1, idx, antecedent, true); + } + if (ctx.inconsistent()) + return; + if (val1 != l_undef && val2 != l_undef && val1 != val2) { + UNREACHABLE(); + } + + } + } + while(changed); + + propagate_bits(); + } + + bool theory_bv::merge_zero_one_bits(theory_var r1, theory_var r2) { + zero_one_bits & bits2 = m_zero_one_bits[r2]; + if (bits2.empty()) + return true; + zero_one_bits & bits1 = m_zero_one_bits[r1]; + unsigned bv_size = get_bv_size(r1); + SASSERT(bv_size == get_bv_size(r2)); + m_merge_aux[0].reserve(bv_size+1, null_theory_var); + m_merge_aux[1].reserve(bv_size+1, null_theory_var); +#define RESET_MERGET_AUX() { \ + zero_one_bits::iterator it = bits1.begin(); \ + zero_one_bits::iterator end = bits1.end(); \ + for (; it != end; ++it) \ + m_merge_aux[it->m_is_true][it->m_idx] = null_theory_var; \ + } + DEBUG_CODE(for (unsigned i = 0; i < bv_size; i++) { SASSERT(m_merge_aux[0][i] == null_theory_var || m_merge_aux[1][i] == null_theory_var); }); + // save info about bits1 + zero_one_bits::iterator it = bits1.begin(); + zero_one_bits::iterator end = bits1.end(); + for (; it != end; ++it) + m_merge_aux[it->m_is_true][it->m_idx] = it->m_owner; + // check if bits2 is consistent with bits1, and copy new bits to bits1 + it = bits2.begin(); + end = bits2.end(); + for (; it != end; ++it) { + theory_var v2 = it->m_owner; + theory_var v1 = m_merge_aux[!it->m_is_true][it->m_idx]; + if (v1 != null_theory_var) { + // conflict was detected ... v1 and v2 have complementary bits + SASSERT(m_bits[v1][it->m_idx] == ~(m_bits[v2][it->m_idx])); + mk_new_diseq_axiom(v1, v2, it->m_idx); + RESET_MERGET_AUX(); + return false; + } + if (m_merge_aux[it->m_is_true][it->m_idx] == null_theory_var) { + // copy missing variable to bits1 + bits1.push_back(*it); + } + } + // reset m_merge_aux vector + RESET_MERGET_AUX(); + DEBUG_CODE(for (unsigned i = 0; i < bv_size; i++) { SASSERT(m_merge_aux[0][i] == null_theory_var || m_merge_aux[1][i] == null_theory_var); }); + return true; + } + + class bit_eq_justification : public justification { + enode * m_v1; + enode * m_v2; + theory_id m_th_id; // TODO: steal 4 bits from each one of the following literas and use them to represent the th_id. + literal m_consequent; + literal m_antecedent; + public: + bit_eq_justification(theory_id th_id, enode * v1, enode * v2, literal c, literal a): + m_v1(v1), m_v2(v2), m_th_id(th_id), m_consequent(c), m_antecedent(a) {} + + virtual void get_antecedents(conflict_resolution & cr) { + cr.mark_eq(m_v1, m_v2); + if (m_antecedent.var() != true_bool_var) + cr.mark_literal(m_antecedent); + } + + virtual proof * mk_proof(conflict_resolution & cr) { + bool visited = true; + ptr_buffer prs; + proof * pr = cr.get_proof(m_v1, m_v2); + if (pr) + prs.push_back(pr); + else + visited = false; + if (m_antecedent.var() != true_bool_var) { + proof * pr = cr.get_proof(m_antecedent); + if (pr) + prs.push_back(pr); + else + visited = false; + } + if (!visited) + return 0; + context & ctx = cr.get_context(); + ast_manager & m = cr.get_manager(); + expr_ref fact(m); + ctx.literal2expr(m_consequent, fact); + return m.mk_th_lemma(get_from_theory(), fact, prs.size(), prs.c_ptr()); + } + + virtual theory_id get_from_theory() const { + return m_th_id; + } + + virtual char const * get_name() const { return "bv-bit-eq"; } + }; + + inline justification * theory_bv::mk_bit_eq_justification(theory_var v1, theory_var v2, literal consequent, literal antecedent) { + return get_context().mk_justification(bit_eq_justification(get_id(), get_enode(v1), get_enode(v2), consequent, antecedent)); + } + + void theory_bv::unmerge_eh(theory_var v1, theory_var v2) { + // v1 was the root of the equivalence class + // I must remove the zero_one_bits that are from v2. + + // REMARK: it is unsafe to invoke check_zero_one_bits, since + // the enode associated with v1 and v2 may have already been + // deleted. + // + // The logical context trail_stack is popped before + // the theories pop_scope_eh is invoked. + + zero_one_bits & bits = m_zero_one_bits[v1]; + if (bits.empty()) { + // SASSERT(check_zero_one_bits(v1)); + // SASSERT(check_zero_one_bits(v2)); + return; + } + unsigned j = bits.size(); + while (j > 0) { + --j; + zero_one_bit & bit = bits[j]; + if (find(bit.m_owner) == v1) { + bits.shrink(j+1); + // SASSERT(check_zero_one_bits(v1)); + // SASSERT(check_zero_one_bits(v2)); + return; + } + } + bits.shrink(0); + // SASSERT(check_zero_one_bits(v1)); + // SASSERT(check_zero_one_bits(v2)); + } + + void theory_bv::init_model(model_generator & m) { + m_factory = alloc(bv_factory, get_manager()); + m.register_factory(m_factory); + } + + model_value_proc * theory_bv::mk_value(enode * n, model_generator & mg) { + numeral val; + theory_var v = n->get_th_var(get_id()); + SASSERT(v != null_theory_var); +#ifdef Z3DEBUG + bool r = +#endif + get_fixed_value(v, val); + SASSERT(r); + return alloc(expr_wrapper_proc, m_factory->mk_value(val, get_bv_size(v))); + } + + void theory_bv::display_var(std::ostream & out, theory_var v) const { + out << "v"; + out.width(4); + out << std::left << v; + out << " #"; + out.width(4); + out << get_enode(v)->get_owner_id() << " -> #"; + out.width(4); + out << get_enode(find(v))->get_owner_id(); + out << std::right << ", bits:"; + context & ctx = get_context(); + literal_vector const & bits = m_bits[v]; + literal_vector::const_iterator it = bits.begin(); + literal_vector::const_iterator end = bits.end(); + for (; it != end; ++it) { + out << " "; + ctx.display_literal(out, *it); + } + numeral val; + if (get_fixed_value(v, val)) + out << ", value: " << val; + out << "\n"; + } + + void theory_bv::display_bit_atom(std::ostream & out, bool_var v, bit_atom const * a) const { + context & ctx = get_context(); + out << "#" << ctx.bool_var2expr(v)->get_id() << " ->"; + var_pos_occ * curr = a->m_occs; + while (curr) { + out << " #" << get_enode(curr->m_var)->get_owner_id() << "[" << curr->m_idx << "]"; + curr = curr->m_next; + } + out << "\n"; + } + + void theory_bv::display_atoms(std::ostream & out) const { + out << "atoms:\n"; + context & ctx = get_context(); + unsigned num = ctx.get_num_bool_vars(); + for (unsigned v = 0; v < num; v++) { + atom * a = get_bv2a(v); + if (a && a->is_bit()) + display_bit_atom(out, v, static_cast(a)); + } + } + + void theory_bv::display(std::ostream & out) const { + out << "Theory bv:\n"; + unsigned num_vars = get_num_vars(); + for (unsigned v = 0; v < num_vars; v++) { + display_var(out, v); + } + display_atoms(out); + } + + void theory_bv::collect_statistics(::statistics & st) const { + st.update("bv conflicts", m_stats.m_num_conflicts); + st.update("bv diseqs", m_stats.m_num_diseq_static); + st.update("bv dynamic diseqs", m_stats.m_num_diseq_dynamic); + st.update("bv bit2core", m_stats.m_num_bit2core); + st.update("bv->core eq", m_stats.m_num_th2core_eq); + } + +#ifdef Z3DEBUG + bool theory_bv::check_assignment(theory_var v) const { + context & ctx = get_context(); + if (!is_root(v)) + return true; + if (!ctx.is_relevant(get_enode(v))) { + return true; + } + + theory_var v2 = v; + literal_vector const & bits2 = m_bits[v2]; + theory_var v1 = v2; + do { + literal_vector const & bits1 = m_bits[v1]; + SASSERT(bits1.size() == bits2.size()); + unsigned sz = bits1.size(); + for (unsigned i = 0; i < sz; i++) { + literal bit1 = bits1[i]; + literal bit2 = bits2[i]; + lbool val1 = ctx.get_assignment(bit1); + lbool val2 = ctx.get_assignment(bit2); + CTRACE("bv_bug", val1 != val2, + tout << "equivalence class is inconsistent, i: " << i << "\n"; + display_var(tout, v1); + display_var(tout, v2); + tout << "val1: " << val1 << " lvl: " << ctx.get_assign_level(bit1.var()) << " bit " << bit1 << "\n"; + tout << "val2: " << val2 << " lvl: " << ctx.get_assign_level(bit2.var()) << " bit " << bit2 << "\n";); + SASSERT(val1 == val2); + } + SASSERT(ctx.is_relevant(get_enode(v1))); + v1 = next(v1); + } + while (v1 != v); + return true; + } + + /** + \brief Check whether m_zero_one_bits is an accurate summary of the bits in the + equivalence class rooted by v. + + \remark The method does nothing if v is not the root of the equivalence class. + */ + bool theory_bv::check_zero_one_bits(theory_var v) const { + if (get_context().inconsistent()) + return true; // property is only valid if the context is not in a conflict. + if (is_root(v) && is_bv(v)) { + svector bits[2]; + unsigned num_bits = 0; + unsigned bv_sz = get_bv_size(v); + bits[0].resize(bv_sz, false); + bits[1].resize(bv_sz, false); + theory_var curr = v; + do { + literal_vector const & lits = m_bits[curr]; + for (unsigned i = 0; i < lits.size(); i++) { + literal l = lits[i]; + if (l.var() == true_bool_var) { + unsigned is_true = (l == true_literal); + SASSERT(!bits[!is_true][i]); // no complementary bits + if (!bits[is_true][i]) { + bits[is_true][i] = true; + num_bits++; + } + } + } + curr = next(curr); + } + while (curr != v); + + zero_one_bits const & _bits = m_zero_one_bits[v]; + SASSERT(_bits.size() == num_bits); + svector already_found; + already_found.resize(bv_sz, false); + zero_one_bits::const_iterator it = _bits.begin(); + zero_one_bits::const_iterator end = _bits.end(); + for (; it != end; ++it) { + SASSERT(find(it->m_owner) == v); + SASSERT(bits[it->m_is_true][it->m_idx]); + SASSERT(!already_found[it->m_idx]); + already_found[it->m_idx] = true; + } + } + return true; + } + + bool theory_bv::check_invariant() const { + unsigned num = get_num_vars(); + for (unsigned v = 0; v < num; v++) { + check_assignment(v); + check_zero_one_bits(v); + } + return true; + } + +#endif + +}; diff --git a/lib/theory_bv.h b/lib/theory_bv.h new file mode 100644 index 000000000..a6726702c --- /dev/null +++ b/lib/theory_bv.h @@ -0,0 +1,282 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_bv.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-03. + +Revision History: + +--*/ +#ifndef _THEORY_BV_H_ +#define _THEORY_BV_H_ + +#include"smt_theory.h" +#include"theory_bv_params.h" +#include"bit_blaster.h" +#include"trail.h" +#include"union_find.h" +#include"simplifier.h" +#include"bv_simplifier_plugin.h" +#include"arith_decl_plugin.h" +#include"arith_simplifier_plugin.h" +#include"numeral_factory.h" + +namespace smt { + + struct theory_bv_stats { + unsigned m_num_diseq_static, m_num_diseq_dynamic, m_num_bit2core, m_num_th2core_eq, m_num_conflicts; + void reset() { memset(this, 0, sizeof(theory_bv_stats)); } + theory_bv_stats() { reset(); } + }; + + class theory_bv : public theory { + typedef rational numeral; + typedef trail_stack th_trail_stack; + typedef union_find th_union_find; + typedef std::pair var_pos; + + class atom { + public: + virtual ~atom() {} + virtual bool is_bit() const = 0; + }; + + struct var_pos_occ { + theory_var m_var; + unsigned m_idx; + var_pos_occ * m_next; + var_pos_occ(theory_var v = null_theory_var, unsigned idx = 0, var_pos_occ * next = 0):m_var(v), m_idx(idx), m_next(next) {} + }; + + struct bit_atom : public atom { + var_pos_occ * m_occs; + bit_atom():m_occs(0) {} + virtual ~bit_atom() {} + virtual bool is_bit() const { return true; } + }; + + struct le_atom : public atom { + literal m_var; + literal m_def; + le_atom(literal v, literal d):m_var(v), m_def(d) {} + virtual ~le_atom() {} + virtual bool is_bit() const { return false; } + }; + + /** + \brief Structure used to store the position of a bitvector variable that + contains the true_literal/false_literal. + + Remark: the implementation assumes that bitvector variables containing + complementary bits are never merged. I assert a disequality (not (= x y)) + whenever x and y contain complementary bits. However, this is too expensive + when the bit is the true_literal or false_literal. The number of disequalities + is too big. To avoid this problem, each equivalence class has a set + of its true_literal and false_literal bits in the form of svector. + + Before merging two classes we just check if the merge is valid by traversing these + vectors. + */ + struct zero_one_bit { + theory_var m_owner; //!< variable that owns the bit: useful for backtracking + unsigned m_idx:31; + unsigned m_is_true:1; + zero_one_bit(theory_var v = null_theory_var, unsigned idx = UINT_MAX, bool is_true = false): + m_owner(v), m_idx(idx), m_is_true(is_true) {} + }; + + typedef svector zero_one_bits; + +#ifdef SPARSE_MAP + typedef u_map bool_var2atom; + void insert_bv2a(bool_var bv, atom * a) { m_bool_var2atom.insert(bv, a); } + void erase_bv2a(bool_var bv) { m_bool_var2atom.erase(bv); } + atom * get_bv2a(bool_var bv) const { atom * a; m_bool_var2atom.find(bv, a); return a; } +#else + typedef ptr_vector bool_var2atom; + void insert_bv2a(bool_var bv, atom * a) { m_bool_var2atom.setx(bv, a, 0); } + void erase_bv2a(bool_var bv) { m_bool_var2atom[bv] = 0; } + atom * get_bv2a(bool_var bv) const { return m_bool_var2atom.get(bv, 0); } +#endif + theory_bv_stats m_stats; + theory_bv_params const & m_params; + bv_util m_util; + arith_util m_autil; + simplifier * m_simplifier; + bit_blaster m_bb; + th_trail_stack m_trail_stack; + th_union_find m_find; + vector m_bits; // per var, the bits of a given variable. + 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; + typedef svector vars; + + typedef std::pair value_sort_pair; + typedef pair_hash, unsigned_hash> value_sort_pair_hash; + typedef map > value2var; + value2var m_fixed_var_table; + + literal_vector m_tmp_literals; + svector m_prop_queue; + bool m_approximates_large_bvs; + + theory_var find(theory_var v) const { return m_find.find(v); } + theory_var next(theory_var v) const { return m_find.next(v); } + bool is_root(theory_var v) const { return m_find.is_root(v); } + unsigned get_bv_size(app const * n) const { return m_util.get_bv_size(n); } + unsigned get_bv_size(enode const * n) const { return m_util.get_bv_size(n->get_owner()); } + unsigned get_bv_size(theory_var v) const { return get_bv_size(get_enode(v)); } + bool is_bv(app const* n) const { return m_util.is_bv_sort(get_manager().get_sort(n)); } + bool is_bv(enode const* n) const { return is_bv(n->get_owner()); } + bool is_bv(theory_var v) const { return is_bv(get_enode(v)); } + region & get_region() { return m_trail_stack.get_region(); } + + bool is_numeral(theory_var v) const { return m_util.is_numeral(get_enode(v)->get_owner()); } + app * mk_bit2bool(app * bv, unsigned idx); + void mk_bits(theory_var v); + friend class mk_atom_trail; + void mk_bit2bool(app * n); + void process_args(app * n); + enode * mk_enode(app * n); + theory_var get_var(enode * n); + enode * get_arg(enode * n, unsigned idx); + theory_var get_arg_var(enode * n, unsigned idx); + void get_bits(theory_var v, expr_ref_vector & r); + void get_bits(enode * n, expr_ref_vector & r); + void get_arg_bits(enode * n, unsigned idx, expr_ref_vector & r); + void get_arg_bits(app * n, unsigned idx, expr_ref_vector & r); + friend class add_var_pos_trail; + void simplify_bit(expr * s, expr_ref & r); + void mk_new_diseq_axiom(theory_var v1, theory_var v2, unsigned idx); + friend class register_true_false_bit_trail; + void register_true_false_bit(theory_var v, unsigned idx); + void find_new_diseq_axioms(var_pos_occ * occs, theory_var v, unsigned idx); + void add_bit(theory_var v, literal l); + void init_bits(enode * n, expr_ref_vector const & bits); + void find_wpos(theory_var v); + friend class fixed_eq_justification; + void fixed_var_eh(theory_var v); + bool get_fixed_value(theory_var v, numeral & result) const; + void internalize_num(app * n); + void internalize_add(app * n); + void internalize_mul(app * n); + void internalize_udiv(app * n); + void internalize_sdiv(app * n); + void internalize_urem(app * n); + void internalize_srem(app * n); + void internalize_smod(app * n); + void internalize_shl(app * n); + void internalize_lshr(app * n); + void internalize_ashr(app * n); + void internalize_ext_rotate_left(app * n); + void internalize_ext_rotate_right(app * n); + void internalize_and(app * n); + void internalize_or(app * n); + void internalize_not(app * n); + void internalize_nand(app * n); + void internalize_nor(app * n); + void internalize_xor(app * n); + void internalize_xnor(app * n); + void internalize_concat(app * n); + void internalize_sign_extend(app * n); + void internalize_zero_extend(app * n); + void internalize_extract(app * n); + void internalize_redand(app * n); + void internalize_redor(app * n); + void internalize_comp(app * n); + void internalize_rotate_left(app * n); + void internalize_rotate_right(app * n); + void internalize_bv2int(app* n); + void internalize_int2bv(app* n); + void internalize_mkbv(app* n); + void internalize_umul_no_overflow(app *n); + void internalize_smul_no_overflow(app *n); + void internalize_smul_no_underflow(app *n); + + bool approximate_term(app* n); + + template + void internalize_le(app * atom); + bool internalize_xor3(app * n, bool gate_ctx); + bool internalize_carry(app * n, bool gate_ctx); + justification * mk_bit_eq_justification(theory_var v1, theory_var v2, literal consequent, literal antecedent); + void propagate_bits(); + void assign_bit(literal consequent, theory_var v1, theory_var v2, unsigned idx, literal antecedent, bool propagate_eqc); + void assert_int2bv_axiom(app* n); + void assert_bv2int_axiom(app* n); + arith_simplifier_plugin & arith_simp() const { + SASSERT(m_simplifier != 0); + arith_simplifier_plugin * as = static_cast(m_simplifier->get_plugin(m_autil.get_family_id())); + SASSERT(as != 0); + return *as; + } + + protected: + virtual void init(context * ctx); + virtual theory_var mk_var(enode * n); + virtual bool internalize_atom(app * atom, bool gate_ctx); + virtual bool internalize_term(app * term); + virtual void apply_sort_cnstr(enode * n, sort * s); + virtual void new_eq_eh(theory_var v1, theory_var v2); + virtual void new_diseq_eh(theory_var v1, theory_var v2); + virtual void expand_diseq(theory_var v1, theory_var v2); + virtual void assign_eh(bool_var v, bool is_true); + virtual void relevant_eh(app * n); + virtual void push_scope_eh(); + virtual void pop_scope_eh(unsigned num_scopes); + virtual final_check_status final_check_eh(); + virtual void reset_eh(); + svector m_merge_aux[2]; //!< auxiliary vector used in merge_zero_one_bits + bool merge_zero_one_bits(theory_var r1, theory_var r2); + + // ----------------------------------- + // + // Model generation + // + // ----------------------------------- + bv_factory * m_factory; + virtual void init_model(model_generator & m); + virtual model_value_proc * mk_value(enode * n, model_generator & mg); + + public: + theory_bv(ast_manager & m, theory_bv_params const & params, bit_blaster_params const & bb_params); + virtual ~theory_bv(); + + virtual theory * mk_fresh(context * new_ctx) { return alloc(theory_bv, get_manager(), m_params, m_bb.get_params()); } + + virtual char const * get_name() const { return "bit-vector"; } + + th_trail_stack & get_trail_stack() { return m_trail_stack; } + void merge_eh(theory_var, theory_var, theory_var v1, theory_var v2); + void after_merge_eh(theory_var r1, theory_var r2, theory_var v1, theory_var v2) { SASSERT(check_zero_one_bits(r1)); } + void unmerge_eh(theory_var v1, theory_var v2); + + void display_var(std::ostream & out, theory_var v) const; + void display_bit_atom(std::ostream & out, bool_var v, bit_atom const * a) const; + void display_atoms(std::ostream & out) const; + virtual void display(std::ostream & out) const; + virtual void collect_statistics(::statistics & st) const; + + bool get_fixed_value(app* x, numeral & result) const; + + +#ifdef Z3DEBUG + bool check_assignment(theory_var v) const; + bool check_invariant() const; + bool check_zero_one_bits(theory_var v) const; +#endif + }; +}; + +#endif /* _THEORY_BV_H_ */ + diff --git a/lib/theory_bv_params.h b/lib/theory_bv_params.h new file mode 100644 index 000000000..38e1e263f --- /dev/null +++ b/lib/theory_bv_params.h @@ -0,0 +1,55 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_bv_params.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-06. + +Revision History: + +--*/ +#ifndef _THEORY_BV_PARAMS_H_ +#define _THEORY_BV_PARAMS_H_ + +#include"ini_file.h" + +enum bv_solver_id { + BS_NO_BV, + BS_BLASTER +}; + +struct theory_bv_params { + bv_solver_id m_bv_mode; + bool m_bv_reflect; + bool m_bv_lazy_le; + bool m_bv_cc; + unsigned m_bv_blast_max_size; + bool m_bv_enable_int2bv2int; + theory_bv_params(): + m_bv_mode(BS_BLASTER), + m_bv_reflect(true), + m_bv_lazy_le(false), + m_bv_cc(false), + m_bv_blast_max_size(INT_MAX), + m_bv_enable_int2bv2int(false) {} + void register_params(ini_params & p) { + p.register_int_param("BV_SOLVER", 0, 2, reinterpret_cast(m_bv_mode), "0 - no bv, 1 - simple"); + p.register_unsigned_param("BV_BLAST_MAX_SIZE", m_bv_blast_max_size, "Maximal size for bit-vectors to blast"); + p.register_bool_param("BV_REFLECT", m_bv_reflect); + p.register_bool_param("BV_LAZY_LE", m_bv_lazy_le); + p.register_bool_param("BV_CC", m_bv_cc, "enable congruence closure for BV operators"); + p.register_bool_param("BV_ENABLE_INT2BV_PROPAGATION", m_bv_enable_int2bv2int, + "enable full (potentially expensive) propagation for int2bv and bv2int"); + } +}; + +#endif /* _THEORY_BV_PARAMS_H_ */ + diff --git a/lib/theory_datatype.cpp b/lib/theory_datatype.cpp new file mode 100644 index 000000000..65abfe238 --- /dev/null +++ b/lib/theory_datatype.cpp @@ -0,0 +1,755 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_datatype.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-10-31. + +Revision History: + +--*/ + +#include"smt_context.h" +#include"theory_datatype.h" +#include"smt_model_generator.h" +#include"ast_pp.h" +#include"ast_ll_pp.h" +#include"stats.h" +#include"ast_smt2_pp.h" + +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, 0, lhs, rhs) { + } + // Remark: the assignment must be propagated back to the datatype theory. + virtual theory_id get_from_theory() const { return null_theory_id; } + }; + + /** + \brief Assert the axiom (antecedent => lhs = rhs) + antecedent may be null_literal + */ + void theory_datatype::assert_eq_axiom(enode * lhs, expr * rhs, literal antecedent) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + if (m.proofs_enabled()) { + literal l(mk_eq(lhs->get_owner(), rhs, true)); + ctx.mark_as_relevant(l); + if (antecedent != null_literal) { + literal lits[2] = {l, ~antecedent}; + ctx.mk_th_axiom(get_id(), 2, lits); + } + else { + literal lits[1] = {l}; + ctx.mk_th_axiom(get_id(), 1, lits); + } + } + else { + ctx.internalize(rhs, false); + TRACE("datatype", tout << "adding axiom:\n" << mk_pp(lhs->get_owner(), m) << "\n=\n" << mk_pp(rhs, m) << "\n";); + if (antecedent == null_literal) { + ctx.assign_eq(lhs, ctx.get_enode(rhs), eq_justification::mk_axiom()); + } + 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)); + 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);); + ctx.assign_eq(lhs, _rhs, eq_justification(js)); + } + } + } + + /** + \brief Assert the equality (= n (c (acc_1 n) ... (acc_m n))) where + where acc_i are the accessors of constructor c. + */ + void theory_datatype::assert_is_constructor_axiom(enode * n, func_decl * c, literal antecedent) { + TRACE("datatype_bug", tout << "creating axiom (= n (c (acc_1 n) ... (acc_m n))) for\n" << mk_pp(n->get_owner(), get_manager()) << "\n";); + m_stats.m_assert_cnstr++; + SASSERT(m_util.is_constructor(c)); + SASSERT(m_util.is_datatype(get_manager().get_sort(n->get_owner()))); + ast_manager & m = get_manager(); + ptr_vector args; + ptr_vector const * accessors = m_util.get_constructor_accessors(c); + SASSERT(c->get_arity() == accessors->size()); + ptr_vector::const_iterator it = accessors->begin(); + ptr_vector::const_iterator end = accessors->end(); + for (; it != end; ++it) { + func_decl * d = *it; + SASSERT(d->get_arity() == 1); + expr * acc = m.mk_app(d, n->get_owner()); + args.push_back(acc); + } + expr * mk = m.mk_app(c, args.size(), args.c_ptr()); + assert_eq_axiom(n, mk, antecedent); + } + + /** + \brief Given a constructor n := (c a_1 ... a_m) assert the axioms + (= (acc_1 n) a_1) + ... + (= (acc_m n) a_m) + */ + void theory_datatype::assert_accessor_axioms(enode * n) { + m_stats.m_assert_accessor++; + + SASSERT(is_constructor(n)); + ast_manager & m = get_manager(); + func_decl * d = n->get_decl(); + ptr_vector const * accessors = m_util.get_constructor_accessors(d); + SASSERT(n->get_num_args() == accessors->size()); + ptr_vector::const_iterator it = accessors->begin(); + ptr_vector::const_iterator end = accessors->end(); + for (unsigned i = 0; it != end; ++it, ++i) { + func_decl * acc = *it; + app * acc_app = m.mk_app(acc, n->get_owner()); + enode * arg = n->get_arg(i); + assert_eq_axiom(arg, acc_app, null_literal); + } + } + + /** + \brief Sign a conflict for r := is_mk(a), c := mk(...), not(r), and c == a. + */ + void theory_datatype::sign_recognizer_conflict(enode * c, enode * r) { + SASSERT(is_constructor(c)); + SASSERT(is_recognizer(r)); + SASSERT(m_util.get_recognizer_constructor(r->get_decl()) == c->get_decl()); + SASSERT(c->get_root() == r->get_arg(0)->get_root()); + TRACE("recognizer_conflict", + tout << mk_ismt2_pp(c->get_owner(), get_manager()) << "\n" << mk_ismt2_pp(r->get_owner(), get_manager()) << "\n";); + context & ctx = get_context(); + literal l(ctx.enode2bool_var(r)); + SASSERT(ctx.get_assignment(l) == l_false); + l.neg(); + SASSERT(ctx.get_assignment(l) == l_true); + enode_pair p(c, r->get_arg(0)); + region & reg = ctx.get_region(); + ctx.set_conflict(ctx.mk_justification(ext_theory_conflict_justification(get_id(), reg, 1, &l, 1, &p))); + } + + theory_var theory_datatype::mk_var(enode * n) { + theory_var r = theory::mk_var(n); + theory_var r2 = m_find.mk_var(); + SASSERT(r == r2); + SASSERT(r == static_cast(m_var_data.size())); + m_var_data.push_back(alloc(var_data)); + var_data * d = m_var_data[r]; + if (is_constructor(n)) { + d->m_constructor = n; + get_context().attach_th_var(n, this, r); + assert_accessor_axioms(n); + } + else { + ast_manager & m = get_manager(); + context & ctx = get_context(); + ctx.attach_th_var(n, this, r); + sort * s = m.get_sort(n->get_owner()); + if (m_util.get_datatype_num_constructors(s) == 1) { + func_decl * c = m_util.get_datatype_constructors(s)->get(0); + assert_is_constructor_axiom(n, c, null_literal); + } + else { + if (m_params.m_dt_lazy_splits == 0 || (m_params.m_dt_lazy_splits == 1 && !s->is_infinite())) + mk_split(r); + } + } + return r; + } + + bool theory_datatype::internalize_atom(app * atom, bool gate_ctx) { + TRACE("datatype", tout << "internalizing atom:\n" << mk_pp(atom, get_manager()) << "\n";); + return internalize_term(atom); + } + + bool theory_datatype::internalize_term(app * term) { + TRACE("datatype", tout << "internalizing term:\n" << mk_pp(term, get_manager()) << "\n";); + context & ctx = get_context(); + unsigned num_args = term->get_num_args(); + for (unsigned i = 0; i < num_args; i++) + ctx.internalize(term->get_arg(i), false); + // the internalization of the arguments may trigger the internalization of term. + if (ctx.e_internalized(term)) + return true; + enode * e = ctx.mk_enode(term, false, get_manager().is_bool(term), true); // possible optimization, the third argument may be set to false, if the term (actually, atom) is not in the context of a gate. + if (get_manager().is_bool(term)) { + bool_var bv = ctx.mk_bool_var(term); + ctx.set_var_theory(bv, get_id()); + ctx.set_enode_flag(bv, true); + } + if (is_constructor(term)) { + SASSERT(!is_attached_to_var(e)); + // *** We must create a theory variable for each argument that has sort datatype *** + // + // The apply_sort_cnstr method will not create a theory + // variable for an expression N when sort of N has an + // infinite number of elements. + // + // This may create problems during model construction. + // For example, suppose we have + // x1 = cons(v1, x2) + // and x1 and x2 are lists of integers. + // This sort has an infinite number of elements. So, in principle, + // we do not need a theory variable for x2. + // Recall that if an expression is not associated with a + // theory variable, then a fresh value is associated with + // it. + // Moreover, fresh variables of sort S can only be created after the + // interpretation for each (relevant) expression of sort S in the + // logical context is created. Returning to the example, + // to create the interpretation of x1 we need the + // interpretation for x2. So, x2 cannot be a fresh value, + // since it would have to be created after x1. + // + for (unsigned i = 0; i < num_args; i++) { + enode * arg = e->get_arg(i); + sort * s = get_manager().get_sort(arg->get_owner()); + if (!m_util.is_datatype(s)) + continue; + if (is_attached_to_var(arg)) + continue; + mk_var(arg); + } + mk_var(e); + } + else { + SASSERT(is_accessor(term) || is_recognizer(term)); + SASSERT(term->get_num_args() == 1); + enode * arg = e->get_arg(0); + if (!is_attached_to_var(arg)) + mk_var(arg); + SASSERT(is_attached_to_var(arg)); + } + if (is_recognizer(term)) { + enode * arg = e->get_arg(0); + theory_var v = arg->get_th_var(get_id()); + SASSERT(v != null_theory_var); + // When relevancy propagation is enabled, the recognizer is only added when it is marked as relevant. + if (!ctx.relevancy()) + add_recognizer(v, e); + } + return true; + } + + void theory_datatype::apply_sort_cnstr(enode * n, sort * s) { + // Remark: If s is an infinite sort, then it is not necessary to create + // a theory variable. + // + // Actually, when the logical context has quantifiers, it is better to + // disable this optimization. + // Example: + // + // (forall (l list) (a Int) (= (len (cons a l)) (+ (len l) 1))) + // (assert (> (len a) 1) + // + // If the theory variable is not created for 'a', then a wrong model will be generated. + TRACE("datatype", tout << "apply_sort_cnstr: #" << n->get_owner_id() << "\n";); + TRACE("datatype_bug", tout << "apply_sort_cnstr:\n" << mk_pp(n->get_owner(), get_manager()) << "\n";); + if ((get_context().has_quantifiers() || (m_util.is_datatype(s) && !s->is_infinite())) && !is_attached_to_var(n)) { + mk_var(n); + } + } + + void theory_datatype::new_eq_eh(theory_var v1, theory_var v2) { + m_find.merge(v1, v2); + } + + bool theory_datatype::use_diseqs() const { + return false; + } + + void theory_datatype::new_diseq_eh(theory_var v1, theory_var v2) { + UNREACHABLE(); + } + + void theory_datatype::assign_eh(bool_var v, bool is_true) { + context & ctx = get_context(); + enode * n = ctx.bool_var2enode(v); + if (!is_recognizer(n)) + return; + TRACE("datatype", tout << "assigning recognizer: #" << n->get_owner_id() << " is_true: " << is_true << "\n" + << mk_bounded_pp(n->get_owner(), get_manager()) << "\n";); + SASSERT(n->get_num_args() == 1); + enode * arg = n->get_arg(0); + theory_var tv = arg->get_th_var(get_id()); + tv = m_find.find(tv); + var_data * d = m_var_data[tv]; + func_decl * r = n->get_decl(); + func_decl * c = m_util.get_recognizer_constructor(r); + if (is_true) { + SASSERT(tv != null_theory_var); + if (d->m_constructor != 0 && d->m_constructor->get_decl() == c) + return; // do nothing + assert_is_constructor_axiom(arg, c, literal(v)); + } + else { + if (d->m_constructor != 0) { + if (d->m_constructor->get_decl() == c) { + // conflict + sign_recognizer_conflict(d->m_constructor, n); + } + } + else { + propagate_recognizer(tv, n); + } + } + } + + void theory_datatype::relevant_eh(app * n) { + TRACE("datatype", tout << "relevant_eh: " << mk_bounded_pp(n, get_manager()) << "\n";); + context & ctx = get_context(); + SASSERT(ctx.relevancy()); + if (is_recognizer(n)) { + TRACE("datatype", tout << "relevant_eh: #" << n->get_id() << "\n" << mk_bounded_pp(n, get_manager()) << "\n";); + SASSERT(ctx.e_internalized(n)); + enode * e = ctx.get_enode(n); + theory_var v = e->get_arg(0)->get_th_var(get_id()); + SASSERT(v != null_theory_var); + add_recognizer(v, e); + } + } + + void theory_datatype::push_scope_eh() { + theory::push_scope_eh(); + m_trail_stack.push_scope(); + } + + void theory_datatype::pop_scope_eh(unsigned num_scopes) { + m_trail_stack.pop_scope(num_scopes); + unsigned num_old_vars = get_old_num_vars(num_scopes); + std::for_each(m_var_data.begin() + num_old_vars, m_var_data.end(), delete_proc()); + m_var_data.shrink(num_old_vars); + theory::pop_scope_eh(num_scopes); + SASSERT(m_find.get_num_vars() == m_var_data.size()); + SASSERT(m_find.get_num_vars() == get_num_vars()); + } + + final_check_status theory_datatype::final_check_eh() { + int num_vars = get_num_vars(); + final_check_status r = FC_DONE; + for (int v = 0; v < num_vars; v++) { + if (v == static_cast(m_find.find(v))) { + enode * node = get_enode(v); + if (occurs_check(node)) { + // conflict was detected... + // return... + return FC_CONTINUE; + } + if (m_params.m_dt_lazy_splits > 0) { + // using lazy case splits... + var_data * d = m_var_data[v]; + if (d->m_constructor == 0) { + mk_split(v); + r = FC_CONTINUE; + } + } + } + } + return r; + } + + /** + \brief Check if n can be reached starting from n and following equalities and constructors. + For example, occur_check(a1) returns true in the following set of equalities: + a1 = cons(v1, a2) + a2 = cons(v2, a3) + a3 = cons(v3, a1) + */ + bool theory_datatype::occurs_check(enode * n) { + TRACE("datatype", tout << "occurs check: #" << n->get_owner_id() << "\n";); + m_to_unmark.reset(); + m_used_eqs.reset(); + m_main = n; + bool res = occurs_check_core(m_main); + unmark_enodes(m_to_unmark.size(), m_to_unmark.c_ptr()); + if (res) { + context & ctx = get_context(); + region & r = ctx.get_region(); + ctx.set_conflict(ctx.mk_justification(ext_theory_conflict_justification(get_id(), r, 0, 0, m_used_eqs.size(), m_used_eqs.c_ptr()))); + TRACE("occurs_check", + tout << "occurs_check: true\n"; + svector::const_iterator it = m_used_eqs.begin(); + svector::const_iterator end = m_used_eqs.end(); + for(; it != end; ++it) { + enode_pair const & p = *it; + tout << "eq: #" << p.first->get_owner_id() << " #" << p.second->get_owner_id() << "\n"; + tout << mk_bounded_pp(p.first->get_owner(), get_manager()) << " " << mk_bounded_pp(p.second->get_owner(), get_manager()) << "\n"; + }); + } + return res; + } + + /** + \brief Auxiliary method for occurs_check. + TODO: improve performance. + */ + bool theory_datatype::occurs_check_core(enode * app) { + if (app->is_marked()) + return false; + + m_stats.m_occurs_check++; + app->set_mark(); + m_to_unmark.push_back(app); + + TRACE("datatype", tout << "occurs check_core: #" << app->get_owner_id() << " #" << m_main->get_owner_id() << "\n";); + + theory_var v = app->get_root()->get_th_var(get_id()); + if (v != null_theory_var) { + v = m_find.find(v); + var_data * d = m_var_data[v]; + if (d->m_constructor) { + if (app != d->m_constructor) + m_used_eqs.push_back(enode_pair(app, d->m_constructor)); + unsigned num_args = d->m_constructor->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + enode * arg = d->m_constructor->get_arg(i); + if (arg->get_root() == m_main->get_root()) { + if (arg != m_main) + m_used_eqs.push_back(enode_pair(arg, m_main)); + return true; + } + if (m_util.is_datatype(get_manager().get_sort(arg->get_owner())) && occurs_check_core(arg)) + return true; + } + if (app != d->m_constructor) { + SASSERT(m_used_eqs.back().first == app); + SASSERT(m_used_eqs.back().second == d->m_constructor); + m_used_eqs.pop_back(); + } + } + } + return false; + } + + void theory_datatype::reset_eh() { + m_trail_stack.reset(); + std::for_each(m_var_data.begin(), m_var_data.end(), delete_proc()); + m_var_data.reset(); + theory::reset_eh(); + m_util.reset(); + m_stats.reset(); + } + + bool theory_datatype::is_shared(theory_var v) const { + // In principle, parametric theories such as Array Theory and + // Datatype Theory need to implement this method. However, the datatype theory + // propagates all implied equalities. And, the is_shared method is essentially used + // to create interface equalities. So, it is safe to return false. + return false; + } + + theory_datatype::theory_datatype(ast_manager & m, theory_datatype_params & p): + theory(m.get_family_id("datatype")), + m_params(p), + m_util(m), + m_find(*this), + m_trail_stack(*this) { + } + + theory_datatype::~theory_datatype() { + std::for_each(m_var_data.begin(), m_var_data.end(), delete_proc()); + m_var_data.reset(); + } + + void theory_datatype::display(std::ostream & out) const { + out << "Theory datatype:\n"; + unsigned num_vars = get_num_vars(); + for (unsigned v = 0; v < num_vars; v++) + display_var(out, v); + } + + void theory_datatype::collect_statistics(::statistics & st) const { + st.update("datatype occurs check", m_stats.m_occurs_check); + st.update("datatype splits", m_stats.m_splits); + st.update("datatype constructor ax", m_stats.m_assert_cnstr); + st.update("datatype accessor ax", m_stats.m_assert_accessor); + } + + void theory_datatype::display_var(std::ostream & out, theory_var v) const { + var_data * d = m_var_data[v]; + out << "v" << v << " #" << get_enode(v)->get_owner_id() << " -> v" << m_find.find(v) << " "; + if (d->m_constructor) + out << mk_bounded_pp(d->m_constructor->get_owner(), get_manager()); + else + out << "(null)"; + out << "\n"; + } + + void theory_datatype::init_model(model_generator & m) { + m_factory = alloc(datatype_factory, get_manager(), m.get_model()); + m.register_factory(m_factory); + } + + class datatype_value_proc : public model_value_proc { + func_decl * m_constructor; + svector m_dependencies; + public: + datatype_value_proc(func_decl * d):m_constructor(d) {} + void add_dependency(enode * n) { m_dependencies.push_back(model_value_dependency(n)); } + virtual ~datatype_value_proc() {} + virtual void get_dependencies(buffer & result) { + result.append(m_dependencies.size(), m_dependencies.c_ptr()); + } + virtual app * mk_value(model_generator & mg, ptr_vector & values) { + SASSERT(values.size() == m_dependencies.size()); + return mg.get_manager().mk_app(m_constructor, values.size(), values.c_ptr()); + } + }; + + model_value_proc * theory_datatype::mk_value(enode * n, model_generator & mg) { + theory_var v = n->get_th_var(get_id()); + v = m_find.find(v); + SASSERT(v != null_theory_var); + var_data * d = m_var_data[v]; + SASSERT(d->m_constructor); + func_decl * c_decl = d->m_constructor->get_decl(); + datatype_value_proc * result = alloc(datatype_value_proc, c_decl); + unsigned num = d->m_constructor->get_num_args(); + for (unsigned i = 0; i < num; i++) + result->add_dependency(d->m_constructor->get_arg(i)); + return result; + } + + void theory_datatype::merge_eh(theory_var v1, theory_var v2, theory_var, theory_var) { + // v1 is the new root + TRACE("datatype", tout << "merging v" << v1 << " v" << v2 << "\n";); + SASSERT(v1 == static_cast(m_find.find(v1))); + var_data * d1 = m_var_data[v1]; + var_data * d2 = m_var_data[v2]; + if (d2->m_constructor != 0) { + context & ctx = get_context(); + if (d1->m_constructor != 0 && 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, 0, 1, &p))); + } + if (d1->m_constructor == 0) { + m_trail_stack.push(set_ptr_trail(d1->m_constructor)); + // check whether there is a recognizer in d1 that conflicts with d2->m_constructor; + if (!d1->m_recognizers.empty()) { + unsigned c_idx = m_util.get_constructor_idx(d2->m_constructor->get_decl()); + enode * recognizer = d1->m_recognizers[c_idx]; + if (recognizer != 0 && ctx.get_assignment(recognizer) == l_false) { + sign_recognizer_conflict(d2->m_constructor, recognizer); + return; + } + } + d1->m_constructor = d2->m_constructor; + } + } + ptr_vector::iterator it = d2->m_recognizers.begin(); + ptr_vector::iterator end = d2->m_recognizers.end(); + for (; it != end; ++it) + if (*it) + add_recognizer(v1, *it); + } + + void theory_datatype::unmerge_eh(theory_var v1, theory_var v2) { + // do nothing + } + + void theory_datatype::add_recognizer(theory_var v, enode * recognizer) { + SASSERT(is_recognizer(recognizer)); + context & ctx = get_context(); + v = m_find.find(v); + var_data * d = m_var_data[v]; + sort * s = recognizer->get_decl()->get_domain(0); + if (d->m_recognizers.empty()) { + SASSERT(m_util.is_datatype(s)); + d->m_recognizers.resize(m_util.get_datatype_num_constructors(s), 0); + } + SASSERT(d->m_recognizers.size() == m_util.get_datatype_num_constructors(s)); + unsigned c_idx = m_util.get_recognizer_constructor_idx(recognizer->get_decl()); + if (d->m_recognizers[c_idx] == 0) { + lbool val = ctx.get_assignment(recognizer); + TRACE("datatype", tout << "adding recognizer to v" << v << " rec: #" << recognizer->get_owner_id() << " val: " << val << "\n";); + if (val == l_true) { + // do nothing... + // If recognizer assignment was already processed, then + // d->m_constructor is already set. + // Otherwise, it will be set when assign_eh is invoked. + return; + } + if (val == l_false && d->m_constructor != 0) { + func_decl * c_decl = m_util.get_recognizer_constructor(recognizer->get_decl()); + if (d->m_constructor->get_decl() == c_decl) { + // conflict + sign_recognizer_conflict(d->m_constructor, recognizer); + } + return; + } + SASSERT(val == l_undef || (val == l_false && d->m_constructor == 0)); + d->m_recognizers[c_idx] = recognizer; + m_trail_stack.push(set_vector_idx_trail(d->m_recognizers, c_idx)); + if (val == l_false) { + propagate_recognizer(v, recognizer); + } + } + } + + /** + \brief Propagate a recognizer assigned to false. + */ + void theory_datatype::propagate_recognizer(theory_var v, enode * recognizer) { + SASSERT(is_recognizer(recognizer)); + SASSERT(static_cast(m_find.find(v)) == v); + context & ctx = get_context(); + SASSERT(ctx.get_assignment(recognizer) == l_false); + unsigned num_unassigned = 0; + unsigned unassigned_idx = UINT_MAX; + enode * n = get_enode(v); + sort * dt = get_manager().get_sort(n->get_owner()); + var_data * d = m_var_data[v]; + CTRACE("datatype", d->m_recognizers.empty(), ctx.display(tout);); + SASSERT(!d->m_recognizers.empty()); + literal_vector lits; + svector eqs; + ptr_vector::const_iterator it = d->m_recognizers.begin(); + ptr_vector::const_iterator end = d->m_recognizers.end(); + for (unsigned idx = 0; it != end; ++it, ++idx) { + enode * r = *it; + if (r && ctx.get_assignment(r) == l_true) + return; // nothing to be propagated + if (r && ctx.get_assignment(r) == l_false) { + SASSERT(r->get_num_args() == 1); + lits.push_back(literal(ctx.enode2bool_var(r), true)); + if (n != r->get_arg(0)) { + // Argument of the current recognizer is not necessarily equal to n. + // This can happen when n and r->get_arg(0) are in the same equivalence class. + // We must add equality as an assumption to the conflict or propagation + SASSERT(n->get_root() == r->get_arg(0)->get_root()); + eqs.push_back(enode_pair(n, r->get_arg(0))); + } + continue; + } + if (num_unassigned == 0) + unassigned_idx = idx; + num_unassigned++; + } + if (num_unassigned == 0) { + // conflict + SASSERT(!lits.empty()); + region & reg = ctx.get_region(); + TRACE("datatype_conflict", tout << mk_ismt2_pp(recognizer->get_owner(), get_manager()) << "\n"; + for (unsigned i = 0; i < lits.size(); i++) { + ctx.display_detailed_literal(tout, lits[i]); tout << "\n"; + } + for (unsigned i = 0; i < eqs.size(); i++) { + tout << mk_ismt2_pp(eqs[i].first->get_owner(), get_manager()) << " = " << mk_ismt2_pp(eqs[i].second->get_owner(), get_manager()) << "\n"; + }); + ctx.set_conflict(ctx.mk_justification(ext_theory_conflict_justification(get_id(), reg, lits.size(), lits.c_ptr(), eqs.size(), eqs.c_ptr()))); + } + else if (num_unassigned == 1) { + // propagate remaining recognizer + SASSERT(!lits.empty()); + enode * r = d->m_recognizers[unassigned_idx]; + literal consequent; + if (!r) { + ptr_vector const * constructors = m_util.get_datatype_constructors(dt); + func_decl * rec = m_util.get_constructor_recognizer(constructors->get(unassigned_idx)); + app * rec_app = get_manager().mk_app(rec, n->get_owner()); + ctx.internalize(rec_app, false); + consequent = literal(ctx.get_bool_var(rec_app)); + } + else { + 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.c_ptr(), + eqs.size(), eqs.c_ptr(), consequent))); + } + else { + // there are more than 2 unassigned recognizers... + // if eager splits are enabled... create new case split + if (m_params.m_dt_lazy_splits == 0 || (!dt->is_infinite() && m_params.m_dt_lazy_splits == 1)) + mk_split(v); + } + } + + /** + \brief Create a new case split for v. That is, create the atom (is_mk v) and mark it as relevant. + If first is true, it means that v does not have recognizer yet. + */ + void theory_datatype::mk_split(theory_var v) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + v = m_find.find(v); + enode * n = get_enode(v); + sort * s = m.get_sort(n->get_owner()); + func_decl * non_rec_c = m_util.get_non_rec_constructor(s); + TRACE("datatype_bug", tout << "non_rec_c: " << non_rec_c->get_name() << "\n";); + unsigned non_rec_idx = m_util.get_constructor_idx(non_rec_c); + var_data * d = m_var_data[v]; + SASSERT(d->m_constructor == 0); + func_decl * r = 0; + m_stats.m_splits++; + + if (d->m_recognizers.empty()) { + r = m_util.get_constructor_recognizer(non_rec_c); + } + else { + enode * recognizer = d->m_recognizers[non_rec_idx]; + if (recognizer == 0) { + r = m_util.get_constructor_recognizer(non_rec_c); + } + else if (!ctx.is_relevant(recognizer)) { + ctx.mark_as_relevant(recognizer); + return; + } + else if (ctx.get_assignment(recognizer) != l_false) { + // if is l_true, then we are done + // otherwise wait recognizer to be assigned. + return; + } + else { + // look for a slot of d->m_recognizers that is 0, or it is not marked as relevant and is unassigned. + ptr_vector::const_iterator it = d->m_recognizers.begin(); + ptr_vector::const_iterator end = d->m_recognizers.end(); + for (unsigned idx = 0; it != end; ++it, ++idx) { + enode * curr = *it; + if (curr == 0) { + ptr_vector const * constructors = m_util.get_datatype_constructors(s); + // found empty slot... + r = m_util.get_constructor_recognizer(constructors->get(idx)); + break; + } + else if (!ctx.is_relevant(curr)) { + ctx.mark_as_relevant(curr); + return; + } + else if (ctx.get_assignment(curr) != l_false) { + return; + } + } + if (r == 0) + return; // all recognizers are asserted to false... conflict will be detected... + } + } + SASSERT(r != 0); + app * r_app = m.mk_app(r, n->get_owner()); + TRACE("datatype", tout << "creating split: " << mk_bounded_pp(r_app, m) << "\n";); + ctx.internalize(r_app, false); + bool_var bv = ctx.get_bool_var(r_app); + ctx.set_true_first_flag(bv); + ctx.mark_as_relevant(bv); + } + +}; diff --git a/lib/theory_datatype.h b/lib/theory_datatype.h new file mode 100644 index 000000000..cf2f933ad --- /dev/null +++ b/lib/theory_datatype.h @@ -0,0 +1,116 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_datatype.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-10-31. + +Revision History: + +--*/ +#ifndef _THEORY_DATATYPE_H_ +#define _THEORY_DATATYPE_H_ + +#include"smt_theory.h" +#include"union_find.h" +#include"theory_datatype_params.h" +#include"datatype_decl_plugin.h" +#include"datatype_factory.h" + +namespace smt { + + class theory_datatype : public theory { + typedef trail_stack th_trail_stack; + typedef union_find th_union_find; + + struct var_data { + ptr_vector m_recognizers; //!< recognizers of this equivalence class that are being watched. + enode * m_constructor; //!< constructor of this equivalence class, 0 if there is no constructor in the eqc. + var_data(): + m_constructor(0) { + } + }; + + struct stats { + unsigned m_occurs_check, m_splits; + unsigned m_assert_cnstr, m_assert_accessor; + void reset() { memset(this, 0, sizeof(stats)); } + stats() { reset(); } + }; + + + theory_datatype_params & m_params; + datatype_util m_util; + ptr_vector m_var_data; + th_union_find m_find; + th_trail_stack m_trail_stack; + datatype_factory * m_factory; + stats m_stats; + + bool is_constructor(app * f) const { return m_util.is_constructor(f); } + bool is_recognizer(app * f) const { return m_util.is_recognizer(f); } + bool is_accessor(app * f) const { return m_util.is_accessor(f); } + + bool is_constructor(enode * n) const { return is_constructor(n->get_owner()); } + bool is_recognizer(enode * n) const { return is_recognizer(n->get_owner()); } + bool is_accessor(enode * n) const { return is_accessor(n->get_owner()); } + + void assert_eq_axiom(enode * lhs, expr * rhs, literal antecedent); + void assert_is_constructor_axiom(enode * n, func_decl * c, literal antecedent); + void assert_accessor_axioms(enode * n); + void add_recognizer(theory_var v, enode * recognizer); + void propagate_recognizer(theory_var v, enode * r); + void sign_recognizer_conflict(enode * c, enode * r); + + ptr_vector m_to_unmark; + svector m_used_eqs; + enode * m_main; + bool occurs_check(enode * n); + bool occurs_check_core(enode * n); + + void mk_split(theory_var v); + + void display_var(std::ostream & out, theory_var v) const; + + protected: + virtual theory_var mk_var(enode * n); + virtual bool internalize_atom(app * atom, bool gate_ctx); + virtual bool internalize_term(app * term); + virtual void apply_sort_cnstr(enode * n, sort * s); + virtual void new_eq_eh(theory_var v1, theory_var v2); + virtual bool use_diseqs() const; + virtual void new_diseq_eh(theory_var v1, theory_var v2); + virtual void assign_eh(bool_var v, bool is_true); + virtual void relevant_eh(app * n); + virtual void push_scope_eh(); + virtual void pop_scope_eh(unsigned num_scopes); + virtual final_check_status final_check_eh(); + virtual void reset_eh(); + virtual bool is_shared(theory_var v) const; + public: + theory_datatype(ast_manager & m, theory_datatype_params & p); + virtual ~theory_datatype(); + virtual theory * mk_fresh(context * new_ctx) { return alloc(theory_datatype, get_manager(), m_params); } + virtual void display(std::ostream & out) const; + virtual void collect_statistics(::statistics & st) const; + virtual void init_model(model_generator & m); + virtual model_value_proc * mk_value(enode * n, model_generator & m); + th_trail_stack & get_trail_stack() { return m_trail_stack; } + virtual void merge_eh(theory_var v1, theory_var v2, theory_var, theory_var); + static void after_merge_eh(theory_var r1, theory_var r2, theory_var v1, theory_var v2) {} + void unmerge_eh(theory_var v1, theory_var v2); + virtual char const * get_name() const { return "datatype"; } + }; + +}; + +#endif /* _THEORY_DATATYPE_H_ */ + diff --git a/lib/theory_datatype_params.h b/lib/theory_datatype_params.h new file mode 100644 index 000000000..000c4da07 --- /dev/null +++ b/lib/theory_datatype_params.h @@ -0,0 +1,38 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_datatype_params.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-11-04. + +Revision History: + +--*/ +#ifndef _THEORY_DATATYPE_PARAMS_H_ +#define _THEORY_DATATYPE_PARAMS_H_ + +#include"ini_file.h" + +struct theory_datatype_params { + unsigned m_dt_lazy_splits; + + theory_datatype_params(): + m_dt_lazy_splits(1) { + } + + void register_params(ini_params & p) { + p.register_unsigned_param("DT_LAZY_SPLITS", m_dt_lazy_splits, "How lazy datatype splits are performed: 0- eager, 1- lazy for infinite types, 2- lazy"); + } +}; + + +#endif /* _THEORY_DATATYPE_PARAMS_H_ */ + diff --git a/lib/theory_dense_diff_logic.cpp b/lib/theory_dense_diff_logic.cpp new file mode 100644 index 000000000..db2ffd38b --- /dev/null +++ b/lib/theory_dense_diff_logic.cpp @@ -0,0 +1,26 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_dense_diff_logic.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-05-16. + +Revision History: + +--*/ +#include"theory_dense_diff_logic_def.h" + +namespace smt { + template class theory_dense_diff_logic; + template class theory_dense_diff_logic; + template class theory_dense_diff_logic; + template class theory_dense_diff_logic; +}; diff --git a/lib/theory_dense_diff_logic.h b/lib/theory_dense_diff_logic.h new file mode 100644 index 000000000..355e38c0c --- /dev/null +++ b/lib/theory_dense_diff_logic.h @@ -0,0 +1,278 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_dense_diff_logic.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-05-16. + +Revision History: + +TODO: eager equality propagation + +--*/ +#ifndef _THEORY_DENSE_DIFF_LOGIC_H_ +#define _THEORY_DENSE_DIFF_LOGIC_H_ + +#include"theory_arith.h" +#include"theory_arith_params.h" +#include"arith_decl_plugin.h" +#include"arith_eq_adapter.h" + +namespace smt { + + struct theory_dense_diff_logic_statistics { + unsigned m_num_assertions; + unsigned m_num_propagations; + void reset() { + m_num_assertions = 0; + m_num_propagations = 0; + } + theory_dense_diff_logic_statistics() { + reset(); + } + }; + + template + class theory_dense_diff_logic : public theory, private Ext { + public: + theory_dense_diff_logic_statistics m_stats; + + private: + typedef typename Ext::inf_numeral numeral; + + class atom { + typedef typename Ext::inf_numeral numeral; + bool_var m_bvar; + theory_var m_source; + theory_var m_target; + numeral m_offset; + + public: + atom(bool_var bv, theory_var source, theory_var target, numeral const & offset): + m_bvar(bv), + m_source(source), + m_target(target), + m_offset(offset) { + } + + bool_var get_bool_var() const { return m_bvar; } + theory_var get_source() const { return m_source; } + theory_var get_target() const { return m_target; } + numeral const & get_offset() const { return m_offset; } + }; + + typedef ptr_vector atoms; + typedef ptr_vector bool_var2atom; + + struct edge { + theory_var m_source; + theory_var m_target; + numeral m_offset; + literal m_justification; + edge():m_source(null_theory_var), m_target(null_theory_var), m_justification(null_literal) {} + edge(theory_var s, theory_var t, numeral const & offset, literal js): + m_source(s), m_target(t), m_offset(offset), m_justification(js) { + } + }; + + typedef int edge_id; + typedef vector edges; + static const edge_id null_edge_id = -1; + static const edge_id self_edge_id = 0; + + struct cell { + edge_id m_edge_id; + numeral m_distance; + atoms m_occs; + cell(): + m_edge_id(null_edge_id) { + } + }; + + struct cell_trail { + unsigned short m_source; + unsigned short m_target; + edge_id m_old_edge_id; + numeral m_old_distance; + cell_trail(unsigned short s, unsigned short t, edge_id old_edge_id, numeral const & old_distance): + m_source(s), m_target(t), m_old_edge_id(old_edge_id), m_old_distance(old_distance) {} + }; + + typedef vector row; + typedef vector matrix; + + struct scope { + unsigned m_atoms_lim; + unsigned m_edges_lim; + unsigned m_cell_trail_lim; + }; + + theory_arith_params & m_params; + arith_util m_autil; + arith_eq_adapter m_arith_eq_adapter; + atoms m_atoms; + atoms m_bv2atoms; + edges m_edges; // list of asserted edges + matrix m_matrix; + svector m_is_int; + vector m_cell_trail; + svector m_scopes; + bool m_non_diff_logic_exprs; + + struct f_target { + theory_var m_target; + numeral m_new_distance; + }; + + typedef std::pair var_pair; + typedef vector f_targets; + + literal_vector m_tmp_literals; + svector m_tmp_pairs; + f_targets m_f_targets; + + vector m_assignment; + + struct var_value_hash; + friend struct var_value_hash; + struct var_value_hash { + theory_dense_diff_logic & m_th; + var_value_hash(theory_dense_diff_logic & th):m_th(th) {} + unsigned operator()(theory_var v) const { return m_th.m_assignment[v].hash(); } + }; + + struct var_value_eq; + friend struct var_value_eq; + struct var_value_eq { + theory_dense_diff_logic & m_th; + var_value_eq(theory_dense_diff_logic & th):m_th(th) {} + bool operator()(theory_var v1, theory_var v2) const { return m_th.m_assignment[v1] == m_th.m_assignment[v2] && m_th.is_int(v1) == m_th.is_int(v2); } + }; + + typedef int_hashtable var_value_table; + var_value_table m_var_value_table; + + // ----------------------------------- + // + // Auxiliary + // + // ----------------------------------- + bool is_int(theory_var v) const { return m_is_int[v]; } + bool is_real(theory_var v) const { return !is_int(v); } + numeral const & get_epsilon(theory_var v) const { return is_real(v) ? this->m_real_epsilon : this->m_int_epsilon; } + bool is_times_minus_one(expr * n, app * & r) const { + expr * _r; + if (m_autil.is_times_minus_one(n, _r)) { r = to_app(_r); return true; } + return false; + } + app * mk_zero_for(expr * n); + theory_var mk_var(enode * n); + theory_var internalize_term_core(app * n); + void found_non_diff_logic_expr(expr * n); + bool is_connected(theory_var source, theory_var target) const { return m_matrix[source][target].m_edge_id != null_edge_id; } + void mk_clause(literal l1, literal l2); + void mk_clause(literal l1, literal l2, literal l3); + void add_edge(theory_var source, theory_var target, numeral const & offset, literal l); + void update_cells(); + void propagate_using_cell(theory_var source, theory_var target); + void get_antecedents(theory_var source, theory_var target, literal_vector & result); + void assign_literal(literal l, theory_var source, theory_var target); + void restore_cells(unsigned old_size); + void del_atoms(unsigned old_size); + void del_vars(unsigned old_num_vars); + void init_model(); +#ifdef Z3DEBUG + bool check_vector_sizes() const; + bool check_matrix() const; +#endif + public: + numeral const & get_distance(theory_var source, theory_var target) const { + SASSERT(is_connected(source, target)); + return m_matrix[source][target].m_distance; + } + + // ----------------------------------- + // + // Internalization + // + // ----------------------------------- + virtual bool internalize_atom(app * n, bool gate_ctx); + virtual bool internalize_term(app * term); + virtual void internalize_eq_eh(app * atom, bool_var v); + virtual void apply_sort_cnstr(enode * n, sort * s); + + virtual void assign_eh(bool_var v, bool is_true); + virtual void new_eq_eh(theory_var v1, theory_var v2); + virtual bool use_diseqs() const; + virtual void new_diseq_eh(theory_var v1, theory_var v2); + + virtual void conflict_resolution_eh(app * atom, bool_var v); + + virtual void push_scope_eh(); + virtual void pop_scope_eh(unsigned num_scopes); + + virtual void restart_eh(); + virtual void init_search_eh(); + virtual final_check_status final_check_eh(); + + virtual bool can_propagate(); + virtual void propagate(); + + virtual void flush_eh(); + virtual void reset_eh(); + + virtual bool validate_eq_in_model(theory_var v1, theory_var v2, bool is_true) const; + + virtual void display(std::ostream & out) const; + virtual void display_atom(std::ostream & out, atom * a) const; + virtual void collect_statistics(::statistics & st) const; + + // ----------------------------------- + // + // Model generation + // + // ----------------------------------- + arith_factory * m_factory; + rational m_epsilon; + // void update_epsilon(const inf_numeral & l, const inf_numeral & u); + void compute_epsilon(); + void fix_zero(); + + virtual void init_model(model_generator & m); + virtual model_value_proc * mk_value(enode * n, model_generator & mg); + + // ----------------------------------- + // + // Main + // + // ----------------------------------- + public: + theory_dense_diff_logic(ast_manager & m, theory_arith_params & p); + virtual ~theory_dense_diff_logic() { reset_eh(); } + + virtual theory * mk_fresh(context * new_ctx) { return alloc(theory_dense_diff_logic, get_manager(), m_params); } + + virtual char const * get_name() const { return "difference-logic"; } + + /** + \brief See comment in theory::mk_eq_atom + */ + virtual app * mk_eq_atom(expr * lhs, expr * rhs) { return m_autil.mk_eq(lhs, rhs); } + }; + + typedef theory_dense_diff_logic theory_dense_mi; + typedef theory_dense_diff_logic theory_dense_i; + typedef theory_dense_diff_logic theory_dense_smi; + typedef theory_dense_diff_logic theory_dense_si; +}; + +#endif /* _THEORY_DENSE_DIFF_LOGIC_H_ */ + diff --git a/lib/theory_dense_diff_logic_def.h b/lib/theory_dense_diff_logic_def.h new file mode 100644 index 000000000..4d50bd8bf --- /dev/null +++ b/lib/theory_dense_diff_logic_def.h @@ -0,0 +1,824 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_dense_diff_logic_def.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-05-16. + +Revision History: + +--*/ +#ifndef _THEORY_DENSE_DIFF_LOGIC_DEF_H_ +#define _THEORY_DENSE_DIFF_LOGIC_DEF_H_ + +#include"smt_context.h" +#include"theory_dense_diff_logic.h" +#include"ast_pp.h" +#include"smt_model_generator.h" + +namespace smt { + + template + theory_dense_diff_logic::theory_dense_diff_logic(ast_manager & m, theory_arith_params & p): + theory(m.get_family_id("arith")), + m_params(p), + m_autil(m), + m_arith_eq_adapter(*this, p, m_autil), + m_non_diff_logic_exprs(false), + m_var_value_table(DEFAULT_HASHTABLE_INITIAL_CAPACITY, var_value_hash(*this), var_value_eq(*this)) { + m_edges.push_back(edge()); + } + + template + inline app * theory_dense_diff_logic::mk_zero_for(expr * n) { + return m_autil.mk_numeral(rational(0), get_manager().get_sort(n)); + } + + template + theory_var theory_dense_diff_logic::mk_var(enode * n) { + theory_var v = theory::mk_var(n); + bool is_int = m_autil.is_int(n->get_owner()); + m_is_int.push_back(is_int); + m_f_targets.push_back(f_target()); + typename matrix::iterator it = m_matrix.begin(); + typename matrix::iterator end = m_matrix.end(); + for (; it != end; ++it) { + it->push_back(cell()); + } + m_matrix.push_back(row()); + row & r = m_matrix.back(); + SASSERT(r.empty()); + r.resize(v+1); + cell & c = m_matrix[v][v]; + c.m_edge_id = self_edge_id; + c.m_distance.reset(); + SASSERT(check_vector_sizes()); + get_context().attach_th_var(n, this, v); + return v; + } + + template + theory_var theory_dense_diff_logic::internalize_term_core(app * n) { + context & ctx = get_context(); + if (ctx.e_internalized(n)) { + enode * e = ctx.get_enode(n); + if (is_attached_to_var(e)) + return e->get_th_var(get_id()); + } + + rational _k; + if (m_autil.is_add(n) && to_app(n)->get_num_args() == 2 && m_autil.is_numeral(to_app(n)->get_arg(0), _k)) { + numeral k(_k); + if (m_params.m_arith_reflect) + internalize_term_core(to_app(to_app(n)->get_arg(0))); + theory_var s = internalize_term_core(to_app(to_app(n)->get_arg(1))); + enode * e = ctx.mk_enode(n, !m_params.m_arith_reflect, false, true); + theory_var v = mk_var(e); + add_edge(s, v, k, null_literal); + k.neg(); + add_edge(v, s, k, null_literal); + return v; + } + else if (m_autil.is_numeral(n, _k)) { + enode * e = ctx.mk_enode(n, false, false, true); + // internalizer is marking enodes as interpreted whenever the associated ast is a value and a constant. + // e->mark_as_interpreted(); + theory_var v = mk_var(e); + if (_k.is_zero()) + return v; + theory_var z = internalize_term_core(mk_zero_for(n)); + numeral k(_k); + add_edge(z, v, k, null_literal); + k.neg(); + add_edge(v, z, k, null_literal); + return v; + } + else if (!m_autil.is_arith_expr(n)) { + if (!ctx.e_internalized(n)) + ctx.internalize(n, false); + enode * e = ctx.get_enode(n); + if (!is_attached_to_var(e)) + return mk_var(e); + else + return e->get_th_var(get_id()); + } + else { + return null_theory_var; + } + } + + template + void theory_dense_diff_logic::found_non_diff_logic_expr(expr * n) { + if (!m_non_diff_logic_exprs) { + TRACE("non_diff_logic", tout << "found non diff logic expression:\n" << mk_pp(n, get_manager()) << "\n";); + get_context().push_trail(value_trail(m_non_diff_logic_exprs)); + m_non_diff_logic_exprs = true; + } + } + + template + bool theory_dense_diff_logic::internalize_atom(app * n, bool gate_ctx) { + if (memory::above_high_watermark()) { + found_non_diff_logic_expr(n); // little hack... TODO: change to no_memory and return l_undef if SAT + return false; + } + TRACE("ddl", tout << "internalizing atom:\n" << mk_pp(n, get_manager()) << "\n";); + context & ctx = get_context(); + SASSERT(!ctx.b_internalized(n)); + SASSERT(m_autil.is_le(n) || m_autil.is_ge(n)); + theory_var source, target; + SASSERT(m_autil.is_le(n) || m_autil.is_ge(n)); + app * lhs = to_app(n->get_arg(0)); + app * rhs = to_app(n->get_arg(1)); + SASSERT(m_autil.is_numeral(rhs)); + rational _k; + m_autil.is_numeral(rhs, _k); + numeral offset(_k); + app * s, * t; + if (m_autil.is_add(lhs) && to_app(lhs)->get_num_args() == 2 && is_times_minus_one(to_app(lhs)->get_arg(1), s)) { + t = to_app(to_app(lhs)->get_arg(0)); + } + else if (m_autil.is_mul(lhs) && to_app(lhs)->get_num_args() == 2 && m_autil.is_minus_one(to_app(lhs)->get_arg(0))) { + s = to_app(to_app(lhs)->get_arg(1)); + t = mk_zero_for(s); + } + else if (!m_autil.is_arith_expr(lhs)) { + t = to_app(lhs); + s = mk_zero_for(t); + } + else { + TRACE("ddl", tout << "failed to internalize:\n" << mk_pp(n, get_manager()) << "\n";); + found_non_diff_logic_expr(n); + return false; + } + source = internalize_term_core(s); + target = internalize_term_core(t); + if (source == null_theory_var || target == null_theory_var) { + TRACE("ddl", tout << "failed to internalize:\n" << mk_pp(n, get_manager()) << "\n";); + found_non_diff_logic_expr(n); + return false; + } + SASSERT(source != null_theory_var && target != null_theory_var); + if (m_autil.is_ge(n)) { + std::swap(source, target); + offset.neg(); + } + bool_var bv = ctx.mk_bool_var(n); + ctx.set_var_theory(bv, get_id()); + atom * a = alloc(atom, bv, source, target, offset); + m_atoms.push_back(a); + m_bv2atoms.setx(bv, a, 0); + m_matrix[source][target].m_occs.push_back(a); + m_matrix[target][source].m_occs.push_back(a); + TRACE("ddl", tout << "succeeded internalizing:\n" << mk_pp(n, get_manager()) << "\n";); + return true; + } + + template + void theory_dense_diff_logic::mk_clause(literal l1, literal l2) { + get_context().mk_th_axiom(get_id(), l1, l2); + } + + template + void theory_dense_diff_logic::mk_clause(literal l1, literal l2, literal l3) { + get_context().mk_th_axiom(get_id(), l1, l2, l3); + } + + template + bool theory_dense_diff_logic::internalize_term(app * term) { + if (memory::above_high_watermark()) { + found_non_diff_logic_expr(term); // little hack... TODO: change to no_memory and return l_undef if SAT + return false; + } + TRACE("ddl", tout << "internalizing term: " << mk_pp(term, get_manager()) << "\n";); + theory_var v = internalize_term_core(term); + TRACE("ddl", tout << mk_pp(term, get_manager()) << "\ninternalization result: " << (v != null_theory_var) << "\n";); + if (v == null_theory_var) + found_non_diff_logic_expr(term); + return v != null_theory_var; + } + + template + void theory_dense_diff_logic::internalize_eq_eh(app * atom, bool_var v) { + if (memory::above_high_watermark()) + return; + context & ctx = get_context(); + app * lhs = to_app(atom->get_arg(0)); + app * rhs = to_app(atom->get_arg(1)); + app * s; + if (m_autil.is_add(lhs) && to_app(lhs)->get_num_args() == 2 && is_times_minus_one(to_app(lhs)->get_arg(1), s) + && m_autil.is_numeral(rhs)) { + // force axioms for (= (+ x (* -1 y)) k) + // this is necessary because (+ x (* -1 y)) is not a diff logic term. + m_arith_eq_adapter.mk_axioms(ctx.get_enode(lhs), ctx.get_enode(rhs)); + return; + } + + if (m_params.m_arith_eager_eq_axioms) { + enode * n1 = ctx.get_enode(lhs); + enode * n2 = ctx.get_enode(rhs); + if (n1->get_th_var(get_id()) != null_theory_var && + n2->get_th_var(get_id()) != null_theory_var) + m_arith_eq_adapter.mk_axioms(n1, n2); + } + } + + template + void theory_dense_diff_logic::apply_sort_cnstr(enode * n, sort * s) { + // do nothing... + } + + template + void theory_dense_diff_logic::assign_eh(bool_var v, bool is_true) { + if (get_context().has_th_justification(v, get_id())) { + TRACE("ddl", tout << "ignoring atom propagated by the theory.\n";); + return; + } + atom * a = m_bv2atoms.get(v, 0); + if (!a) { + SASSERT(get_manager().is_eq(get_context().bool_var2expr(v))); + return; + } + m_stats.m_num_assertions++; + literal l = literal(v, !is_true); + theory_var s = a->get_source(); + theory_var t = a->get_target(); + numeral k = a->get_offset(); + TRACE("assign_profile", tout << "#" << get_enode(s)->get_owner_id() << " #" << get_enode(t)->get_owner_id() << " " << k << "\n";); + if (l.sign()) { + k.neg(); + k -= get_epsilon(s); + add_edge(t, s, k, l); + } + else { + add_edge(s, t, k, l); + } + TRACE("ddl_detail", display(tout);); + } + + template + void theory_dense_diff_logic::new_eq_eh(theory_var v1, theory_var v2) { + m_arith_eq_adapter.new_eq_eh(v1, v2); + } + + template + bool theory_dense_diff_logic::use_diseqs() const { + return true; + } + + template + void theory_dense_diff_logic::new_diseq_eh(theory_var v1, theory_var v2) { + m_arith_eq_adapter.new_diseq_eh(v1, v2); + } + + template + void theory_dense_diff_logic::conflict_resolution_eh(app * atom, bool_var v) { + // do nothing + } + + template + void theory_dense_diff_logic::push_scope_eh() { + theory::push_scope_eh(); + m_scopes.push_back(scope()); + scope & s = m_scopes.back(); + s.m_atoms_lim = m_atoms.size(); + s.m_edges_lim = m_edges.size(); + s.m_cell_trail_lim = m_cell_trail.size(); + } + + template + void theory_dense_diff_logic::pop_scope_eh(unsigned num_scopes) { + unsigned lvl = m_scopes.size(); + SASSERT(num_scopes <= lvl); + unsigned new_lvl = lvl - num_scopes; + scope & s = m_scopes[new_lvl]; + restore_cells(s.m_cell_trail_lim); + m_edges.shrink(s.m_edges_lim); + del_atoms(s.m_atoms_lim); + del_vars(get_old_num_vars(num_scopes)); + m_scopes.shrink(new_lvl); + theory::pop_scope_eh(num_scopes); + } + + template + void theory_dense_diff_logic::restore_cells(unsigned old_size) { + unsigned sz = m_cell_trail.size(); + unsigned i = sz; + while (i > old_size) { + i--; + cell_trail & t = m_cell_trail[i]; + cell & c = m_matrix[t.m_source][t.m_target]; + c.m_edge_id = t.m_old_edge_id; + c.m_distance = t.m_old_distance; + } + m_cell_trail.shrink(old_size); + } + + template + void theory_dense_diff_logic::del_atoms(unsigned old_size) { + typename atoms::iterator begin = m_atoms.begin() + old_size; + typename atoms::iterator it = m_atoms.end(); + while (it != begin) { + --it; + atom * a = *it; + TRACE("del_atoms", tout << "deleting: p" << a->get_bool_var() << "\n";); + m_bv2atoms[a->get_bool_var()] = 0; + theory_var s = a->get_source(); + theory_var t = a->get_target(); + TRACE("del_atoms", tout << "m_matrix.size() " << m_matrix.size() << + ", m_matrix[s].size() " << m_matrix[s].size() << + ", m_matrix[t].size(): " << m_matrix[t].size() << + ", t: " << t << ", s: " << s << "\n";); + SASSERT(m_matrix[s][t].m_occs.back() == a); + SASSERT(m_matrix[t][s].m_occs.back() == a); + m_matrix[s][t].m_occs.pop_back(); + m_matrix[t][s].m_occs.pop_back(); + dealloc(a); + } + m_atoms.shrink(old_size); + } + + template + void theory_dense_diff_logic::del_vars(unsigned old_num_vars) { + int num_vars = get_num_vars(); + SASSERT(num_vars >= static_cast(old_num_vars)); + if (num_vars != static_cast(old_num_vars)) { + m_is_int.shrink(old_num_vars); + m_f_targets.shrink(old_num_vars); + m_matrix.shrink(old_num_vars); + typename matrix::iterator it = m_matrix.begin(); + typename matrix::iterator end = m_matrix.end(); + for (; it != end; ++it) { + it->shrink(old_num_vars); + } + } + } + + template + void theory_dense_diff_logic::restart_eh() { + m_arith_eq_adapter.restart_eh(); + } + + template + void theory_dense_diff_logic::init_search_eh() { + m_arith_eq_adapter.init_search_eh(); + } + + template + final_check_status theory_dense_diff_logic::final_check_eh() { + init_model(); + if (assume_eqs(m_var_value_table)) + return FC_CONTINUE; + // logical context contains arithmetic expressions that are not + // in the difference logic fragment. + if (m_non_diff_logic_exprs) + return FC_GIVEUP; + return FC_DONE; + } + + template + bool theory_dense_diff_logic::can_propagate() { + // do nothing + return false; + } + + template + void theory_dense_diff_logic::propagate() { + // do nothing + } + + template + void theory_dense_diff_logic::flush_eh() { + // do nothing + } + + template + void theory_dense_diff_logic::reset_eh() { + del_atoms(0); + m_atoms .reset(); + m_bv2atoms .reset(); + m_edges .reset(); + m_matrix .reset(); + m_is_int .reset(); + m_f_targets .reset(); + m_cell_trail .reset(); + m_scopes .reset(); + m_non_diff_logic_exprs = false; + m_edges.push_back(edge()); + theory::reset_eh(); + } + + template + bool theory_dense_diff_logic::validate_eq_in_model(theory_var v1, theory_var v2, bool is_true) const { + return is_true ? m_assignment[v1] == m_assignment[v2] : m_assignment[v1] != m_assignment[v2]; + } + + /** + \brief Store in results the antecedents that justify that the distance between source and target. + */ + template + void theory_dense_diff_logic::get_antecedents(theory_var source, theory_var target, literal_vector & result) { + TRACE("ddl", tout << "get_antecedents, source: #" << get_enode(source)->get_owner_id() << ", target: #" << get_enode(target)->get_owner_id() << "\n";); + CTRACE("ddl", !is_connected(source, target), display(tout);); + SASSERT(is_connected(source, target)); + svector & todo = m_tmp_pairs; + todo.reset(); + + if (source != target) + todo.push_back(var_pair(source, target)); + + while (!todo.empty()) { + var_pair & curr = todo.back(); + theory_var s = curr.first; + theory_var t = curr.second; + todo.pop_back(); + + SASSERT(is_connected(s, t)); + cell & c = m_matrix[s][t]; + SASSERT(c.m_edge_id != self_edge_id); + + edge & e = m_edges[c.m_edge_id]; + if (e.m_justification != null_literal) + result.push_back(e.m_justification); + + if (s != e.m_source) + todo.push_back(var_pair(s, e.m_source)); + if (e.m_target != t) + todo.push_back(var_pair(e.m_target, t)); + } + } + + template + void theory_dense_diff_logic::update_cells() { + edge_id new_edge_id = m_edges.size() - 1; + edge & last = m_edges.back(); + theory_var s = last.m_source; + theory_var t = last.m_target; + numeral const & k = last.m_offset; + + // Compute set F of nodes such that: + // x in F iff + // k + d(t, x) < d(s, x) + + numeral new_dist; + row & t_row = m_matrix[t]; + typename row::iterator it = t_row.begin(); + typename row::iterator end = t_row.end(); + typename f_targets::iterator fbegin = m_f_targets.begin(); + typename f_targets::iterator target = fbegin; + for (theory_var x = 0; it != end; ++it, ++x) { + if (it->m_edge_id != null_edge_id && x != s) { + new_dist = k; + new_dist += it->m_distance; + cell & s_x = m_matrix[s][x]; + TRACE("ddl", + tout << "s: #" << get_enode(s)->get_owner_id() << " x: #" << get_enode(x)->get_owner_id() << " new_dist: " << new_dist << "\n"; + tout << "already has edge: " << s_x.m_edge_id << " old dist: " << s_x.m_distance << "\n";); + if (s_x.m_edge_id == null_edge_id || new_dist < s_x.m_distance) { + target->m_target = x; + target->m_new_distance = new_dist; + ++target; + } + } + } + + typename f_targets::iterator fend = target; + + // For each node y such that y --> s, and for each node x in F, + // check whether d(y, s) + new_dist(x) < d(y, x). + typename matrix::iterator it2 = m_matrix.begin(); + typename matrix::iterator end2 = m_matrix.end(); + for (theory_var y = 0; it2 != end2; ++it2, ++y) { + if (y != t) { + row & r = *it2; + cell & c = r[s]; + if (c.m_edge_id != null_edge_id) { + numeral const & d_y_s = c.m_distance; + target = fbegin; + for (; target != fend; ++target) { + theory_var x = target->m_target; + if (x != y) { + new_dist = d_y_s; + new_dist += target->m_new_distance; + cell & y_x = m_matrix[y][x]; + if (y_x.m_edge_id == null_edge_id || new_dist < y_x.m_distance) { + m_cell_trail.push_back(cell_trail(y, x, y_x.m_edge_id, y_x.m_distance)); + y_x.m_edge_id = new_edge_id; + y_x.m_distance = new_dist; + if (!y_x.m_occs.empty()) { + propagate_using_cell(y, x); + } + } + } + } + } + } + } + CASSERT("ddl", check_matrix()); + } + + template + void theory_dense_diff_logic::assign_literal(literal l, theory_var source, theory_var target) { + context & ctx = get_context(); + 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.c_ptr(), l))); + } + + template + void theory_dense_diff_logic::propagate_using_cell(theory_var source, theory_var target) { + cell & c = m_matrix[source][target]; + SASSERT(c.m_edge_id != null_edge_id); + numeral neg_dist = c.m_distance; + neg_dist.neg(); + context & ctx = get_context(); + typename atoms::const_iterator it = c.m_occs.begin(); + typename atoms::const_iterator end = c.m_occs.end(); + for (; it != end; ++it) { + atom * a = *it; + if (ctx.get_assignment(a->get_bool_var()) == l_undef) { + if (a->get_source() == source) { + SASSERT(a->get_target() == target); + if (c.m_distance <= a->get_offset()) { + m_stats.m_num_propagations++; + TRACE("ddl", tout << "asserting atom to true: "; display_atom(tout, a); + tout << "distance(#" << get_enode(source)->get_owner_id() << ", #" << get_enode(target)->get_owner_id() + << "): " << c.m_distance << "\n";); + assign_literal(literal(a->get_bool_var(), false), source, target); + } + } + else { + SASSERT(a->get_source() == target); + SASSERT(a->get_target() == source); + if (neg_dist > a->get_offset()) { + m_stats.m_num_propagations++; + TRACE("ddl", tout << "asserting atom to true: "; display_atom(tout, a); + tout << "distance(#" << get_enode(source)->get_owner_id() << ", #" << get_enode(target)->get_owner_id() + << "): " << c.m_distance << "\n";); + assign_literal(literal(a->get_bool_var(), true), source, target); + } + } + } + } + } + + template + inline void theory_dense_diff_logic::add_edge(theory_var source, theory_var target, numeral const & offset, literal l) { + TRACE("ddl", tout << "trying adding edge: #" << get_enode(source)->get_owner_id() << " -- " << offset << " --> #" << get_enode(target)->get_owner_id() << "\n";); + cell & c_inv = m_matrix[target][source]; + if (c_inv.m_edge_id != null_edge_id && - c_inv.m_distance > offset) { + // conflict detected. + TRACE("ddl", tout << "conflict detected: #" << get_enode(source)->get_owner_id() << " #" << get_enode(target)->get_owner_id() << + " offset: " << offset << ", c_inv.m_edge_id: " << c_inv.m_edge_id << ", c_inv.m_distance: " << c_inv.m_distance << "\n";); + literal_vector & antecedents = m_tmp_literals; + antecedents.reset(); + get_antecedents(target, source, antecedents); + if (l != null_literal) + antecedents.push_back(l); + context & ctx = get_context(); + region & r = ctx.get_region(); + ctx.set_conflict(ctx.mk_justification(theory_conflict_justification(get_id(), r, antecedents.size(), antecedents.c_ptr()))); + return; + } + + cell & c = m_matrix[source][target]; + if (c.m_edge_id == null_edge_id || offset < c.m_distance) { + TRACE("ddl", tout << "adding edge: #" << get_enode(source)->get_owner_id() << " -- " << offset << " --> #" << get_enode(target)->get_owner_id() << "\n";); + m_edges.push_back(edge(source, target, offset, l)); + update_cells(); + } + } + + +#ifdef Z3DEBUG + template + bool theory_dense_diff_logic::check_vector_sizes() const { + SASSERT(m_matrix.size() == m_f_targets.size()); + SASSERT(m_is_int.size() == m_matrix.size()); + typename matrix::const_iterator it = m_matrix.begin(); + typename matrix::const_iterator end = m_matrix.end(); + for (; it != end; ++it) { + SASSERT(it->size() == m_matrix.size()); + } + return true; + } + + template + bool theory_dense_diff_logic::check_matrix() const { + int sz = m_matrix.size(); + for (theory_var i = 0; i < sz; i++) { + for (theory_var j = 0; j < sz; j++) { + cell const & c = m_matrix[i][j]; + if (c.m_edge_id == self_edge_id) { + SASSERT(i == j); + SASSERT(c.m_distance.is_zero()); + } + else if (c.m_edge_id != null_edge_id) { + edge const & e = m_edges[c.m_edge_id]; + theory_var s = e.m_source; + theory_var t = e.m_target; + numeral k = get_distance(i, s); + k += e.m_offset; + k += get_distance(t, j); + if (c.m_distance != k) { + CTRACE("ddl", c.m_distance != k, tout << "i: " << i << " j: " << j << " k: " << k << " c.m_distance: " << c.m_distance << "\n"; + display(tout);); + SASSERT(c.m_distance == k); + } + } + } + } + return true; + } +#endif + + template + void theory_dense_diff_logic::display(std::ostream & out) const { + out << "Theory dense difference logic:\n"; + display_var2enode(out); + typename matrix::const_iterator it1 = m_matrix.begin(); + typename matrix::const_iterator end1 = m_matrix.end(); + for (int v1 = 0; it1 != end1; ++it1, ++v1) { + typename row::const_iterator it2 = it1->begin(); + typename row::const_iterator end2 = it1->end(); + for (int v2 = 0; it2 != end2; ++it2, ++v2) { + if (it2->m_edge_id != null_edge_id && it2->m_edge_id != self_edge_id) { + out << "#"; + out.width(5); + out << std::left << get_enode(v1)->get_owner_id() << " -- "; + out.width(10); + out << std::left << it2->m_distance << " : id"; + out.width(5); + out << std::left << it2->m_edge_id << " --> #"; + out << get_enode(v2)->get_owner_id() << "\n"; + } + } + } + out << "atoms:\n"; + typename atoms::const_iterator it2 = m_atoms.begin(); + typename atoms::const_iterator end2 = m_atoms.end(); + for (;it2 != end2; ++it2) { + atom * a = *it2; + display_atom(out, a); + } + } + + template + void theory_dense_diff_logic::display_atom(std::ostream & out, atom * a) const { + out << "#"; + out.width(5); + out << std::left << get_enode(a->get_target())->get_owner_id() << " - #"; + out.width(5); + out << std::left << get_enode(a->get_source())->get_owner_id() << " <= "; + out.width(10); + out << std::left << a->get_offset() << " assignment: " << get_context().get_assignment(a->get_bool_var()) << "\n"; + } + + template + void theory_dense_diff_logic::collect_statistics(::statistics & st) const { + st.update("dd assertions", m_stats.m_num_assertions); + st.update("dd propagations", m_stats.m_num_propagations); + m_arith_eq_adapter.collect_statistics(st); + } + + /** + \brief Build a model for doing model-based theory combination. + */ + template + void theory_dense_diff_logic::init_model() { + int num_vars = get_num_vars(); + m_assignment.reset(); + m_assignment.resize(num_vars); + for (int i = 0; i < num_vars; i++) { + row & r = m_matrix[i]; + numeral & d = m_assignment[i]; + for (int j = 0; j < num_vars; j++) { + if (i != j) { + cell & c = r[j]; + if (c.m_edge_id != null_edge_id && c.m_distance < d) { + d = c.m_distance; + } + } + } + } + for (int i = 0; i < num_vars; i++) + m_assignment[i].neg(); + TRACE("ddl_model", + tout << "ddl model\n"; + for (theory_var v = 0; v < num_vars; v++) { + tout << "#" << mk_pp(get_enode(v)->get_owner(), get_manager()) << " = " << m_assignment[v] << "\n"; + }); + } + + /** + The arithmetic module uses infinitesimals. So, + an inf_numeral (n,k) represents n + k*epsilon + where epsilon is a very small number. + In order to generate a model, we need to compute + a value for epsilon in a way all inequalities remain + satisfied. + + assume we have the inequality + x - y <= (n_c, k_c) + + where the interpretation for x and y is: + (n_x, k_x) and (n_y, k_y). + + So, + + (n_x, k_x) <= (n_y + n_c, k_y + k_c) + + + The only intersting case is n_x < n_y + n_c and k_x > k_y + k_c. + Using the definition of infinitesimal numbers + we have: + + n_x + k_x * epsilon <= n_y + n_c + (k_y + k_c) * epsilon + + Therefore: + + epsilon <= (n_y + n_c - n_x) / (k_x - k_y - k_c) + */ + template + void theory_dense_diff_logic::compute_epsilon() { + m_epsilon = rational(1); + typename edges::const_iterator it = m_edges.begin(); + typename edges::const_iterator end = m_edges.end(); + // first edge is null + SASSERT(it->m_target == null_theory_var); + SASSERT(it->m_source == null_theory_var); + ++it; + for (; it != end; ++it) { + edge const & e = *it; + rational n_x = m_assignment[e.m_target].get_rational().to_rational(); + rational k_x = m_assignment[e.m_target].get_infinitesimal().to_rational(); + rational n_y = m_assignment[e.m_source].get_rational().to_rational(); + rational k_y = m_assignment[e.m_source].get_infinitesimal().to_rational(); + rational n_c = e.m_offset.get_rational().to_rational(); + rational k_c = e.m_offset.get_infinitesimal().to_rational(); + TRACE("epsilon", tout << "(n_x,k_x): " << n_x << ", " << k_x << ", (n_y,k_y): " << n_y << ", " << k_y << ", (n_c,k_c): " << n_c << ", " << k_c << "\n";); + if (n_x < n_y + n_c && k_x > k_y + k_c) { + rational new_epsilon = (n_y + n_c - n_x) / (k_x - k_y - k_c); + if (new_epsilon < m_epsilon) { + TRACE("epsilon", tout << "new epsilon: " << new_epsilon << "\n";); + m_epsilon = new_epsilon; + } + } + } + } + + template + void theory_dense_diff_logic::fix_zero() { + int num_vars = get_num_vars(); + for (int v = 0; v < num_vars; ++v) { + enode * n = get_enode(v); + if (m_autil.is_zero(n->get_owner()) && !m_assignment[v].is_zero()) { + numeral val = m_assignment[v]; + sort * s = get_manager().get_sort(n->get_owner()); + // adjust the value of all variables that have the same sort. + for (int v2 = 0; v2 < num_vars; ++v2) { + enode * n2 = get_enode(v2); + if (get_manager().get_sort(n2->get_owner()) == s) { + m_assignment[v2] -= val; + } + } + SASSERT(m_assignment[v].is_zero()); + } + } + TRACE("ddl_model", + tout << "ddl model\n"; + for (theory_var v = 0; v < num_vars; v++) { + tout << "#" << mk_pp(get_enode(v)->get_owner(), get_manager()) << " = " << m_assignment[v] << "\n"; + }); + } + + template + void theory_dense_diff_logic::init_model(model_generator & m) { + m_factory = alloc(arith_factory, get_manager()); + m.register_factory(m_factory); + fix_zero(); + compute_epsilon(); + } + + template + model_value_proc * theory_dense_diff_logic::mk_value(enode * n, model_generator & mg) { + theory_var v = n->get_th_var(get_id()); + SASSERT(v != null_theory_var); + numeral const & val = m_assignment[v]; + rational num = val.get_rational().to_rational() + m_epsilon * val.get_infinitesimal().to_rational(); + return alloc(expr_wrapper_proc, m_factory->mk_value(num, is_int(v))); + } + +}; + +#endif /* _THEORY_DENSE_DIFF_LOGIC_DEF_H_ */ + diff --git a/lib/theory_diff_logic.cpp b/lib/theory_diff_logic.cpp new file mode 100644 index 000000000..ec8cc2d68 --- /dev/null +++ b/lib/theory_diff_logic.cpp @@ -0,0 +1,38 @@ +/*++ +Copyright (c) 2008 Microsoft Corporation + +Module Name: + + theory_diff_logic.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-04-21. + Nikolaj Bjorner (nbjorner) 2008-05-05 + +Revision History: + +TODO: + +- port diff_logic.* +- port theory_diff_logic +- fix theory propagation +- fix/add equality propagation +- fix relaxation. + +--*/ +#include "theory_diff_logic.h" + +#include"rational.h" +#include"theory_diff_logic_def.h" + + +template class theory_diff_logic; +template class theory_diff_logic; +template class theory_diff_logic; +template class theory_diff_logic; + diff --git a/lib/theory_diff_logic.h b/lib/theory_diff_logic.h new file mode 100644 index 000000000..20de5b464 --- /dev/null +++ b/lib/theory_diff_logic.h @@ -0,0 +1,523 @@ +/*++ +Copyright (c) 2008 Microsoft Corporation + +Module Name: + + theory_diff_logic.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-04-21. + Nikolaj Bjorner (nbjorner) 2008-05-05 + +Revision History: + +TODO: + +- check regressions with smt::solver. + +- what should be done properly (during internalization) for reflect() + +- fix theory propagation: + - x <= y + 2, then x <= y + 3 + - x = y, then x <= y + 2 + - x = y, x <= z <= y, then x = z + +- fix/add equality propagation + +- add relaxation (somewhat easy) + +- Currently stored explanation in edges is stored at creation time + (not at propagation time). + Is this flexible enough for processing equalities? + - eq_justification present in smt::context when calling new_eq_eh. + pass such to the edge? + +- context::assume_eq is absent. +- context::add_eq and context::push_eq are protected. Cannot propagate equalities back to core. +- + +--*/ + +#ifndef _THEORY_DIFF_LOGIC_H_ +#define _THEORY_DIFF_LOGIC_H_ + +#include"rational.h" +#include"inf_rational.h" +#include"inf_int_rational.h" +#include"s_integer.h" +#include"inf_s_integer.h" +#include"smt_theory.h" +#include"diff_logic.h" +#include"arith_decl_plugin.h" +#include"smt_justification.h" +#include"map.h" +#include"front_end_params.h" +#include"arith_eq_adapter.h" +#include"smt_model_generator.h" +#include"numeral_factory.h" +#include"smt_clause.h" + + +// The DL theory can represent term such as n + k, where n is an enode and k is a numeral. +namespace smt { + + struct theory_diff_logic_statistics { + unsigned m_num_conflicts; + unsigned m_num_assertions; + unsigned m_num_th2core_eqs; + unsigned m_num_th2core_prop; + + unsigned m_num_core2th_eqs; + unsigned m_num_core2th_diseqs; + unsigned m_num_core2th_new_diseqs; + void reset() { + m_num_conflicts = 0; + m_num_assertions = 0; + m_num_th2core_prop = 0; + m_num_th2core_eqs = 0; + m_num_core2th_eqs = 0; + m_num_core2th_diseqs = 0; + m_num_core2th_new_diseqs = 0; + } + theory_diff_logic_statistics() { + reset(); + } + }; + + class dl_conflict : public simple_justification { + public: + dl_conflict(region & r, unsigned nls, literal const * lits): simple_justification(r, nls, lits) { } + + virtual proof * mk_proof(conflict_resolution & cr) { NOT_IMPLEMENTED_YET(); return 0; } + }; + + class dl_propagate : public simple_justification { + public: + dl_propagate(region & r, unsigned nls, literal const * lits): simple_justification(r, nls, lits) { } + + virtual proof * mk_proof(conflict_resolution & cr) { NOT_IMPLEMENTED_YET(); return 0; } + }; + + + + template + class theory_diff_logic : public theory, private Ext { + + typedef typename Ext::numeral numeral; + + class implied_eq_justification : public justification { + theory_diff_logic & m_theory; + theory_var m_v1; + theory_var m_v2; + unsigned m_timestamp; + public: + implied_eq_justification(theory_diff_logic & theory, theory_var v1, theory_var v2, unsigned ts): + m_theory(theory), + m_v1(v1), + m_v2(v2), + m_timestamp(ts) { + } + + virtual void get_antecedents(conflict_resolution & cr) { + m_theory.get_eq_antecedents(m_v1, m_v2, m_timestamp, cr); + } + + virtual proof * mk_proof(conflict_resolution & cr) { NOT_IMPLEMENTED_YET(); return 0; } + }; + + class implied_bound_justification : public justification { + theory_diff_logic& m_theory; + edge_id m_subsumed_edge; + edge_id m_bridge_edge; + public: + implied_bound_justification(theory_diff_logic & theory, edge_id se, edge_id be): + m_theory(theory), + m_subsumed_edge(se), + m_bridge_edge(be) { + } + + virtual void get_antecedents(conflict_resolution & cr) { + m_theory.get_implied_bound_antecedents(m_bridge_edge, m_subsumed_edge, cr); + } + + virtual proof * mk_proof(conflict_resolution & cr) { NOT_IMPLEMENTED_YET(); return 0; } + }; + + enum atom_kind { + LE_ATOM, + EQ_ATOM + }; + + class atom { + protected: + atom_kind m_kind; + bool_var m_bvar; + bool m_true; + public: + atom(atom_kind k, bool_var bv) : m_kind(k), m_bvar(bv), m_true(false) {} + virtual ~atom() {} + atom_kind kind() const { return m_kind; } + bool_var get_bool_var() const { return m_bvar; } + bool is_true() const { return m_true; } + void assign_eh(bool is_true) { m_true = is_true; } + virtual std::ostream& display(theory_diff_logic const& th, std::ostream& out) const; + }; + + class le_atom : public atom { + int m_pos; + int m_neg; + public: + le_atom(bool_var bv, int pos, int neg): + atom(LE_ATOM, bv), + m_pos(pos), + m_neg(neg) { + } + virtual ~le_atom() {} + int get_asserted_edge() const { return this->m_true?m_pos:m_neg; } + int get_pos() const { return m_pos; } + int get_neg() const { return m_neg; } + virtual std::ostream& display(theory_diff_logic const& th, std::ostream& out) const; + }; + + class eq_atom : public atom { + app_ref m_le; + app_ref m_ge; + public: + eq_atom(bool_var bv, app_ref& le, app_ref& ge): + atom(EQ_ATOM, bv), + m_le(le), + m_ge(ge) + {} + virtual ~eq_atom() {} + virtual std::ostream& display(theory_diff_logic const& th, std::ostream& out) const; + app* get_le() const { return m_le.get(); } + app* get_ge() const { return m_ge.get(); } + }; + + typedef ptr_vector atoms; + typedef u_map bool_var2atom; + + + // Auxiliary info for propagating cheap equalities + class eq_prop_info { + int m_scc_id; + numeral m_delta; + theory_var m_root; + public: + eq_prop_info(int scc_id, const numeral & d, theory_var r = null_theory_var): + m_scc_id(scc_id), + m_delta(d), + m_root(r) { + } + + theory_var get_root() const { + return m_root; + } + + unsigned hash() const { return mk_mix(static_cast(m_scc_id), m_delta.hash(), 0x9e3779b9); } + + bool operator==(const eq_prop_info & info) const { + return m_scc_id == info.m_scc_id && m_delta == info.m_delta; + } + }; + + struct eq_prop_info_hash_proc { + unsigned operator()(eq_prop_info * info) const { + return info->hash(); + } + }; + + struct eq_prop_info_eq_proc { + bool operator()(eq_prop_info * info1, eq_prop_info * info2) const { + return *info1 == *info2; + } + }; + typedef ptr_hashtable eq_prop_info_set; + + + + // Extension for diff_logic core. + + struct GExt : public Ext { + typedef literal explanation; + }; + + // Functor used to collect the proofs for a conflict due to + // a negative cycle. + class nc_functor { + literal_vector m_antecedents; + theory_diff_logic& m_super; + public: + nc_functor(theory_diff_logic& s) : m_super(s) {} + void reset(); + literal_vector const& get_lits() const { return m_antecedents; } + + void operator()(literal const & ex) { + if (ex != null_literal) { + m_antecedents.push_back(ex); + } + } + + void new_edge(dl_var src, dl_var dst, unsigned num_edges, edge_id const* edges) { + m_super.new_edge(src, dst, num_edges, edges); + } + }; + + struct scope { + unsigned m_atoms_lim; + unsigned m_asserted_atoms_lim; + unsigned m_asserted_qhead_old; + }; + + class theory_diff_logic_del_eh : public clause_del_eh { + theory_diff_logic& m_super; + public: + theory_diff_logic_del_eh(theory_diff_logic& s) : m_super(s) {} + virtual ~theory_diff_logic_del_eh() {} + virtual void operator()(ast_manager&, clause* cls) { + TRACE("dl_activity", tout << "deleting " << cls << "\n";); + m_super.del_clause_eh(cls); + dealloc(this); + } + }; + + front_end_params & m_params; + arith_util m_util; + arith_eq_adapter m_arith_eq_adapter; + theory_diff_logic_statistics m_stats; + dl_graph m_graph; + theory_var m_zero_int; // cache the variable representing the zero variable. + theory_var m_zero_real; // cache the variable representing the zero variable. + int_vector m_scc_id; // Cheap equality propagation + bool m_modified_since_eq_prop; // true if new constraints were asserted + // since last eq propagation. + eq_prop_info_set m_eq_prop_info_set; // set of existing equality prop infos + ptr_vector m_eq_prop_infos; + + ptr_vector m_atoms; + ptr_vector m_asserted_atoms; // set of asserted atoms + unsigned m_asserted_qhead; + bool_var2atom m_bool_var2atom; + svector m_scopes; + + unsigned m_num_core_conflicts; + unsigned m_num_propagation_calls; + double m_agility; + bool m_is_lia; + bool m_non_diff_logic_exprs; + + arith_factory * m_factory; + rational m_delta; + nc_functor m_nc_functor; + + // Set a conflict due to a negative cycle. + void set_neg_cycle_conflict(); + + void new_edge(dl_var src, dl_var dst, unsigned num_edges, edge_id const* edges); + + // Create a new theory variable. + virtual theory_var mk_var(enode* n); + + virtual theory_var mk_var(app* n); + + void mark_as_modified_since_eq_prop(); + + void unmark_as_modified_since_eq_prop(); + + bool propagate_cheap_equalities(); + + void compute_delta(); + + void found_non_diff_logic_expr(expr * n); + + bool is_interpreted(app* n) const; + + void del_clause_eh(clause* cls); + + public: + theory_diff_logic(ast_manager& m, front_end_params & params): + theory(m.get_family_id("arith")), + m_params(params), + m_util(m), + m_arith_eq_adapter(*this, params, m_util), + m_zero_int(null_theory_var), + m_zero_real(null_theory_var), + m_modified_since_eq_prop(false), + m_asserted_qhead(0), + m_num_core_conflicts(0), + m_num_propagation_calls(0), + m_agility(0.5), + m_is_lia(true), + m_non_diff_logic_exprs(false), + m_factory(0), + m_nc_functor(*this) { + } + + ~theory_diff_logic() { + reset_eh(); + } + + virtual theory * mk_fresh(context * new_ctx) { return alloc(theory_diff_logic, get_manager(), m_params); } + + virtual char const * get_name() const { return "difference-logic"; } + + /** + \brief See comment in theory::mk_eq_atom + */ + virtual app * mk_eq_atom(expr * lhs, expr * rhs) { return m_util.mk_eq(lhs, rhs); } + + virtual void init(context * ctx); + + virtual bool internalize_atom(app * atom, bool gate_ctx); + + virtual bool internalize_term(app * term); + + virtual void internalize_eq_eh(app * atom, bool_var v); + + virtual void assign_eh(bool_var v, bool is_true); + + virtual void new_eq_eh(theory_var v1, theory_var v2); + + virtual bool use_diseqs() const { return true; } + + virtual void new_diseq_eh(theory_var v1, theory_var v2); + + virtual void push_scope_eh(); + + virtual void pop_scope_eh(unsigned num_scopes); + + virtual void restart_eh() { + m_arith_eq_adapter.restart_eh(); + } + + virtual void relevant_eh(app* e); + + virtual void init_search_eh() { + m_arith_eq_adapter.init_search_eh(); + } + + virtual final_check_status final_check_eh(); + + virtual bool is_shared(theory_var v) const { + return false; + } + + virtual bool can_propagate() { + return m_asserted_qhead != m_asserted_atoms.size(); + } + + virtual void propagate(); + + virtual justification * why_is_diseq(theory_var v1, theory_var v2) { + NOT_IMPLEMENTED_YET(); + return 0; + } + + // virtual void flush_eh(); + + virtual void reset_eh(); + + virtual void init_model(model_generator & m); + + virtual model_value_proc * mk_value(enode * n, model_generator & mg); + + virtual bool validate_eq_in_model(theory_var v1, theory_var v2, bool is_true) const; + + virtual void display(std::ostream & out) const; + + virtual void collect_statistics(::statistics & st) const; + + private: + + virtual void new_eq_eh(theory_var v1, theory_var v2, justification& j); + + virtual void new_diseq_eh(theory_var v1, theory_var v2, justification& j); + + bool is_negative(app* n, app*& m); + + void del_atoms(unsigned old_size); + + void propagate_core(); + + bool propagate_atom(atom* a); + + theory_var mk_term(app* n); + + theory_var mk_num(app* n, rational const& r); + + bool is_offset(app* n, app*& v, app*& offset, rational& r); + + bool is_consistent() const; + + bool reflect() const { return m_params.m_arith_reflect; } + + bool theory_resolve() const { return m_params.m_theory_resolve; } + + unsigned lazy_pivoting_lvl() const { return m_params.m_arith_lazy_pivoting_lvl; } + + 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); + + void get_eq_antecedents(theory_var v1, theory_var v2, unsigned timestamp, conflict_resolution & cr); + + void get_implied_bound_antecedents(edge_id bridge_edge, edge_id subsumed_edge, conflict_resolution & cr); + + theory_var get_zero(sort* s) const { return m_util.is_int(s)?m_zero_int:m_zero_real; } + + theory_var get_zero(expr* e) const { return get_zero(get_manager().get_sort(e)); } + + void inc_conflicts(); + + }; + + struct idl_ext { + // TODO: It doesn't need to be a rational, but a bignum integer. + static const bool m_int_theory = true; + typedef rational numeral; + numeral m_epsilon; + idl_ext() : m_epsilon(1) {} + }; + + struct sidl_ext { + // TODO: It doesn't need to be a rational, but a bignum integer. + static const bool m_int_theory = true; + typedef s_integer numeral; + numeral m_epsilon; + sidl_ext() : m_epsilon(1) {} + }; + + struct rdl_ext { + static const bool m_int_theory = false; + typedef inf_int_rational numeral; + numeral m_epsilon; + rdl_ext() : m_epsilon(rational(), true) {} + }; + + struct srdl_ext { + static const bool m_int_theory = false; + typedef inf_s_integer numeral; + numeral m_epsilon; + srdl_ext() : m_epsilon(s_integer(0),true) {} + }; + + + typedef theory_diff_logic theory_idl; + typedef theory_diff_logic theory_fidl; + typedef theory_diff_logic theory_rdl; + typedef theory_diff_logic theory_frdl; +}; + + + + +#endif /* _THEORY_DIFF_LOGIC_H_ */ diff --git a/lib/theory_diff_logic_def.h b/lib/theory_diff_logic_def.h new file mode 100644 index 000000000..54518cd8d --- /dev/null +++ b/lib/theory_diff_logic_def.h @@ -0,0 +1,1148 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_diff_logic_def.h + +Abstract: + + Difference Logic + +Author: + + Leonardo de Moura (leonardo) 2006-11-29. + Nikolaj Bjorner (nbjorner) 2008-05-11 + +Revision History: + + 2008-05-11 ported from v1.2. Add theory propagation. + +--*/ +#ifndef _THEORY_DIFF_LOGIC_DEF_H_ +#define _THEORY_DIFF_LOGIC_DEF_H_ + +#include"theory_diff_logic.h" +#include"smt_context.h" +#include"map.h" +#include"ast_pp.h" +#include"warning.h" +#include"smt_model_generator.h" + +using namespace smt; + +template +std::ostream& theory_diff_logic::atom::display(theory_diff_logic const& th, std::ostream& out) const { + context& ctx = th.get_context(); + lbool asgn = ctx.get_assignment(m_bvar); + //SASSERT(asgn == l_undef || ((asgn == l_true) == m_true)); + bool sign = (l_undef == asgn) || m_true; + return out << literal(m_bvar, sign) + << " " << mk_pp(ctx.bool_var2expr(m_bvar), th.get_manager()) << " "; +} + +template +std::ostream& theory_diff_logic::eq_atom::display(theory_diff_logic const& th, std::ostream& out) const { + atom::display(th, out); + lbool asgn = th.get_context().get_assignment(this->m_bvar); + if (l_undef == asgn) { + out << "unassigned\n"; + } + else { + out << mk_pp(m_le.get(), m_le.get_manager()) << " " + << mk_pp(m_ge.get(), m_ge.get_manager()) << "\n"; + } + return out; +} + +template +std::ostream& theory_diff_logic::le_atom::display(theory_diff_logic const& th, std::ostream& out) const { + atom::display(th, out); + lbool asgn = th.get_context().get_assignment(this->m_bvar); + if (l_undef == asgn) { + out << "unassigned\n"; + } + else { + th.m_graph.display_edge(out, get_asserted_edge()); + } + return out; +} + +// ----------------------------------------- +// theory_diff_logic::nc_functor + +template +void theory_diff_logic::nc_functor::reset() { + m_antecedents.reset(); +} + + +// ----------------------------------------- +// theory_diff_logic + +template +void theory_diff_logic::init(context * ctx) { + theory::init(ctx); + app* zero; + enode* e; + zero = m_util.mk_numeral(rational(0), true); + e = ctx->mk_enode(zero, false, false, true); + SASSERT(!is_attached_to_var(e)); + m_zero_int = mk_var(e); + + zero = m_util.mk_numeral(rational(0), false); + e = ctx->mk_enode(zero, false, false, true); + SASSERT(!is_attached_to_var(e)); + m_zero_real = mk_var(e); + +} + + +template +bool theory_diff_logic::internalize_term(app * term) { + bool result = null_theory_var != mk_term(term); + CTRACE("arith", !result, tout << "Did not internalize " << mk_pp(term, get_manager()) << "\n";); + found_non_diff_logic_expr(term); + return result; +} + +template +class diff_logic_bounds { + bool m_inf_is_set; + bool m_sup_is_set; + bool m_eq_found; + literal m_inf_l; + literal m_sup_l; + literal m_eq_l; + numeral m_inf_w; + numeral m_sup_w; + numeral m_w; + +public: + diff_logic_bounds() { + reset(numeral(0)); + } + void reset(numeral const& w) { + m_inf_is_set = false; + m_sup_is_set = false; + m_eq_found = false; + m_inf_l = null_literal; + m_sup_l = null_literal; + m_eq_l = null_literal; + m_w = w; + } + + void operator()(numeral const& w, literal l) { + if (l != null_literal) { + if ((w < m_w) && (!m_inf_is_set || w > m_inf_w)) { + m_inf_w = w; + m_inf_l = l; + m_inf_is_set = true; + } + else if ((w > m_w) && (!m_sup_is_set || w < m_sup_w)) { + m_sup_w = w; + m_sup_l = l; + m_sup_is_set = true; + } + else if (w == m_w) { + m_eq_found = true; + m_eq_l = l; + } + } + } + + bool get_inf(numeral& w, literal& l) const { + w = m_inf_w; + l = m_inf_l; + return m_inf_is_set; + } + + bool get_sup(numeral& w, literal& l) const { + w = m_sup_w; + l = m_sup_l; + return m_sup_is_set; + } + + bool get_eq(literal& l) const { + l = m_eq_l; + return m_eq_found; + } + +}; + +// +// Atoms are of the form x + -1*y <= k, or x + -1*y = k +// + +template +void theory_diff_logic::found_non_diff_logic_expr(expr * n) { + if (!m_non_diff_logic_exprs) { + TRACE("non_diff_logic", tout << "found non diff logic expression:\n" << mk_pp(n, get_manager()) << "\n";); + get_context().push_trail(value_trail(m_non_diff_logic_exprs)); + m_non_diff_logic_exprs = true; + } +} + +template +bool theory_diff_logic::internalize_atom(app * n, bool gate_ctx) { + context & ctx = get_context(); + if (!m_util.is_le(n) && !m_util.is_ge(n)) { + found_non_diff_logic_expr(n); + return false; + } + SASSERT(m_util.is_le(n) || m_util.is_ge(n)); + SASSERT(!ctx.b_internalized(n)); + + bool_var bv; + rational kr; + app * x, *y, *z; + theory_var source, target; // target - source <= k + app * lhs = to_app(n->get_arg(0)); + app * rhs = to_app(n->get_arg(1)); + if (!m_util.is_numeral(rhs, kr)) { + found_non_diff_logic_expr(n); + return false; + } + numeral k(kr); + + bool is_add = m_util.is_add(lhs) && lhs->get_num_args() == 2; + + if (is_add) { + x = to_app(lhs->get_arg(0)); + y = to_app(lhs->get_arg(1)); + } + + if (is_add && is_negative(x, z)) { + target = mk_var(y); + source = mk_var(z); + } + else if (is_add && is_negative(y, z)) { + target = mk_var(x); + source = mk_var(z); + } + else { + target = mk_var(lhs); + source = get_zero(lhs); + } + if (m_util.is_ge(n)) { + std::swap(target, source); + k.neg(); + } + + bv = ctx.mk_bool_var(n); + ctx.set_var_theory(bv, get_id()); + literal l(bv); + + // + // Create axioms for situations as: + // x - y <= 5 => x - y <= 7 + // + + if (m_params.m_arith_add_binary_bounds) { + literal l0; + numeral k0; + diff_logic_bounds bounds; + bounds.reset(k); + m_graph.enumerate_edges(source, target, bounds); + if (bounds.get_eq(l0)) { + ctx.mk_th_axiom(get_id(),~l0,l); + ctx.mk_th_axiom(get_id(),~l,l0); + } + else { + if (bounds.get_inf(k0, l0)) { + SASSERT(k0 <= k); + ctx.mk_th_axiom(get_id(),~l0,l); + } + if (bounds.get_sup(k0, l0)) { + SASSERT(k <= k0); + ctx.mk_th_axiom(get_id(),~l,l0); + } + } + } + + edge_id pos = m_graph.add_edge(source, target, k, l); + k.neg(); + if (m_util.is_int(lhs)) { + SASSERT(k.is_int()); + k -= numeral(1); + } + else { + m_is_lia = false; + k -= this->m_epsilon; + } + edge_id neg = m_graph.add_edge(target, source, k, ~l); + le_atom * a = alloc(le_atom, bv, pos, neg); + m_atoms.push_back(a); + m_bool_var2atom.insert(bv, a); + + TRACE("arith", + tout << mk_pp(n, get_manager()) << "\n"; + m_graph.display_edge(tout << "pos: ", pos); + m_graph.display_edge(tout << "neg: ", neg); + ); + + return true; +} + +template +void theory_diff_logic::internalize_eq_eh(app * atom, bool_var v) { + context & ctx = get_context(); + app * lhs = to_app(atom->get_arg(0)); + app * rhs = to_app(atom->get_arg(1)); + app * s; + if (m_util.is_add(lhs) && to_app(lhs)->get_num_args() == 2 && + is_negative(to_app(to_app(lhs)->get_arg(1)), s) && m_util.is_numeral(rhs)) { + // force axioms for (= (+ x (* -1 y)) k) + // this is necessary because (+ x (* -1 y)) is not a diff logic term. + m_arith_eq_adapter.mk_axioms(ctx.get_enode(lhs), ctx.get_enode(rhs)); + return; + } + + if (m_params.m_arith_eager_eq_axioms) { + enode * n1 = ctx.get_enode(lhs); + enode * n2 = ctx.get_enode(rhs); + if (n1->get_th_var(get_id()) != null_theory_var && + n2->get_th_var(get_id()) != null_theory_var) + m_arith_eq_adapter.mk_axioms(n1, n2); + } +} + + +template +void theory_diff_logic::assign_eh(bool_var v, bool is_true) { + m_stats.m_num_assertions++; + atom * a = 0; + m_bool_var2atom.find(v, a); + SASSERT(a); + SASSERT(get_context().get_assignment(v) != l_undef); + SASSERT((get_context().get_assignment(v) == l_true) == is_true); + a->assign_eh(is_true); + m_asserted_atoms.push_back(a); +} + + +template +void theory_diff_logic::collect_statistics(::statistics & st) const { + st.update("dl conflicts", m_stats.m_num_conflicts); + st.update("dl propagations", m_stats.m_num_th2core_prop); + st.update("dl asserts", m_stats.m_num_assertions); + st.update("core->dl eqs", m_stats.m_num_core2th_eqs); + m_arith_eq_adapter.collect_statistics(st); +} + +template +void theory_diff_logic::push_scope_eh() { + + theory::push_scope_eh(); + m_graph.push(); + m_scopes.push_back(scope()); + scope & s = m_scopes.back(); + s.m_atoms_lim = m_atoms.size(); + s.m_asserted_atoms_lim = m_asserted_atoms.size(); + s.m_asserted_qhead_old = m_asserted_qhead; +} + +template +void theory_diff_logic::pop_scope_eh(unsigned num_scopes) { + unsigned lvl = m_scopes.size(); + SASSERT(num_scopes <= lvl); + unsigned new_lvl = lvl - num_scopes; + scope & s = m_scopes[new_lvl]; + del_atoms(s.m_atoms_lim); + m_asserted_atoms.shrink(s.m_asserted_atoms_lim); + m_asserted_qhead = s.m_asserted_qhead_old; + m_scopes.shrink(new_lvl); + m_graph.pop(num_scopes); + theory::pop_scope_eh(num_scopes); +} + +template +final_check_status theory_diff_logic::final_check_eh() { + + if (can_propagate()) { + propagate_core(); + return FC_CONTINUE; + } + + TRACE("arith_final", display(tout); ); + // either will already be zero (as we don't do mixed constraints). + m_graph.set_to_zero(m_zero_int, m_zero_real); + SASSERT(is_consistent()); + + +#if 0 + TBD: + if (propagate_cheap_equalities()) { + return FC_CONTINUE; + } +#endif + + if (m_non_diff_logic_exprs) { + return FC_GIVEUP; + } + + return FC_DONE; +} + + +template +void theory_diff_logic::del_atoms(unsigned old_size) { + typename atoms::iterator begin = m_atoms.begin() + old_size; + typename atoms::iterator it = m_atoms.end(); + while (it != begin) { + --it; + atom * a = *it; + bool_var bv = a->get_bool_var(); + m_bool_var2atom.erase(bv); + dealloc(a); + } + m_atoms.shrink(old_size); +} + + + +template +bool theory_diff_logic::is_negative(app* n, app*& m) { + if (!m_util.is_mul(n) || n->get_num_args() != 2) { + return false; + } + rational r; + expr* a0 = n->get_arg(0); + expr* a1 = n->get_arg(1); + if (m_util.is_numeral(a0, r) && r.is_minus_one() && is_app(a1)) { + m = to_app(a1); + return true; + } + if (m_util.is_numeral(a1, r) && r.is_minus_one() && is_app(a0)) { + m = to_app(a0); + return true; + } + return false; +} + +template +void theory_diff_logic::propagate() { + if (m_params.m_arith_adaptive) { + + switch(m_params.m_arith_propagation_strategy) { + + case ARITH_PROP_PROPORTIONAL: { + + ++m_num_propagation_calls; + if (m_num_propagation_calls * (m_stats.m_num_conflicts + 1) > + m_params.m_arith_adaptive_propagation_threshold * get_context().m_stats.m_num_conflicts) { + m_num_propagation_calls = 1; + TRACE("arith_prop", tout << "propagating: " << m_num_propagation_calls << "\n";); + propagate_core(); + } + else { + TRACE("arith_prop", tout << "skipping propagation " << m_num_propagation_calls << "\n";); + } + break; + } + case ARITH_PROP_AGILITY: { + // update agility with factor generated by other conflicts. + + double g = m_params.m_arith_adaptive_propagation_threshold; + while (m_num_core_conflicts < get_context().m_stats.m_num_conflicts) { + m_agility = m_agility*g; + ++m_num_core_conflicts; + } + ++m_num_propagation_calls; + bool do_propagate = (m_num_propagation_calls * m_agility > m_params.m_arith_adaptive_propagation_threshold); + TRACE("arith_prop", tout << (do_propagate?"propagating: ":"skipping ") + << " " << m_num_propagation_calls + << " agility: " << m_agility << "\n";); + if (do_propagate) { + m_num_propagation_calls = 0; + propagate_core(); + } + break; + } + default: + UNREACHABLE(); + propagate_core(); + } + } + else { + propagate_core(); + } +} + +template +void theory_diff_logic::inc_conflicts() { + m_stats.m_num_conflicts++; + if (m_params.m_arith_adaptive) { + double g = m_params.m_arith_adaptive_propagation_threshold; + m_agility = m_agility*g + 1 - g; + } +} + +template +void theory_diff_logic::propagate_core() { + bool consistent = true; + while (consistent && can_propagate()) { + atom * a = m_asserted_atoms[m_asserted_qhead]; + m_asserted_qhead++; + consistent = propagate_atom(a); + } +} + +template +bool theory_diff_logic::propagate_atom(atom* a) { + context& ctx = get_context(); + TRACE("arith", a->display(*this, tout); ); + if (ctx.inconsistent()) { + return false; + } + switch(a->kind()) { + case LE_ATOM: { + int edge_id = dynamic_cast(a)->get_asserted_edge(); + if (!m_graph.enable_edge(edge_id)) { + set_neg_cycle_conflict(); + return false; + } +#if 0 + if (m_params.m_arith_bound_prop != BP_NONE) { + svector subsumed; + m_graph.find_subsumed1(edge_id, subsumed); + for (unsigned i = 0; i < subsumed.size(); ++i) { + int subsumed_edge_id = subsumed[i]; + literal l = m_graph.get_explanation(subsumed_edge_id); + context & ctx = get_context(); + region& r = ctx.get_region(); + ++m_stats.m_num_th2core_prop; + ctx.assign(l, new (r) implied_bound_justification(*this, subsumed_edge_id, edge_id)); + } + + } +#endif + break; + } + case EQ_ATOM: + if (!a->is_true()) { + SASSERT(ctx.get_assignment(a->get_bool_var()) == l_false); + // eq_atom * ea = dynamic_cast(a); + } + break; + } + return true; +} + + + +template +void theory_diff_logic::mark_as_modified_since_eq_prop() { + if (!m_modified_since_eq_prop) { + get_context().push_trail(value_trail(m_modified_since_eq_prop)); + m_modified_since_eq_prop = true; + } +} + +template +void theory_diff_logic::unmark_as_modified_since_eq_prop() { + get_context().push_trail(value_trail(m_modified_since_eq_prop)); + m_modified_since_eq_prop = false; +} + +template +void theory_diff_logic::del_clause_eh(clause* cls) { + +} + +template +void theory_diff_logic::new_edge(dl_var src, dl_var dst, unsigned num_edges, edge_id const* edges) { + + if (!theory_resolve()) { + return; + } + + TRACE("dl_activity", tout << "\n";); + + context& ctx = get_context(); + numeral w(0); + for (unsigned i = 0; i < num_edges; ++i) { + w += m_graph.get_weight(edges[i]); + } + enode* e1 = get_enode(src); + enode* e2 = get_enode(dst); + expr* n1 = e1->get_owner(); + expr* n2 = e2->get_owner(); + bool is_int = m_util.is_int(n1); + rational num = w.get_rational().to_rational(); + + expr_ref le(get_manager()); + if (w.is_rational()) { + // x - y <= w + expr* n3 = m_util.mk_numeral(num, is_int); + n2 = m_util.mk_mul(m_util.mk_numeral(rational(-1), is_int), n2); + le = m_util.mk_le(m_util.mk_add(n1,n2), n3); + } + else { + // x - y < w + // <=> + // not (x - y >= w) + // <=> + // not (y - x <= -w) + // + SASSERT(w.get_infinitesimal().is_neg()); + expr* n3 = m_util.mk_numeral(-num, is_int); + n1 = m_util.mk_mul(m_util.mk_numeral(rational(-1), is_int), n1); + le = m_util.mk_le(m_util.mk_add(n2,n1), n3); + le = get_manager().mk_not(le); + } + ctx.internalize(le, false); + ctx.mark_as_relevant(le.get()); + literal lit(ctx.get_literal(le)); + bool_var bv = lit.var(); + atom* a = 0; + m_bool_var2atom.find(bv, a); + SASSERT(a); + edge_id e_id = static_cast(a)->get_pos(); + + literal_vector lits; + for (unsigned i = 0; i < num_edges; ++i) { + lits.push_back(~m_graph.get_explanation(edges[i])); + } + lits.push_back(lit); + + TRACE("dl_activity", + tout << mk_pp(le, get_manager()) << "\n"; + tout << "edge: " << e_id << "\n"; + ctx.display_literals_verbose(tout, lits.size(), lits.c_ptr()); + tout << "\n"; + ); + + justification * js = 0; + if (get_manager().proofs_enabled()) { + js = 0; // TBD? + } + clause_del_eh* del_eh = alloc(theory_diff_logic_del_eh, *this); + clause* cls = ctx.mk_clause(lits.size(), lits.c_ptr(), js, CLS_AUX_LEMMA, del_eh); + if (!cls) { + dealloc(del_eh); + } + if (dump_lemmas()) { + char const * logic = m_is_lia ? "QF_LIA" : "QF_LRA"; + ctx.display_lemma_as_smt_problem(lits.size(), lits.c_ptr(), false_literal, logic); + } + +#if 0 + TRACE("arith", + tout << "shortcut:\n"; + for (unsigned i = 0; i < num_edges; ++i) { + edge_id e = edges[i]; + // tgt <= src + w + numeral w = m_graph.get_weight(e); + dl_var tgt = m_graph.get_target(e); + dl_var src = m_graph.get_source(e); + if (i + 1 < num_edges) { + dl_var tgt2 = m_graph.get_target(edges[i+1]); + SASSERT(src == tgt2); + } + tout << "$" << tgt << " <= $" << src << " + " << w << "\n"; + } + { + numeral w = m_graph.get_weight(e_id); + dl_var tgt = m_graph.get_target(e_id); + dl_var src = m_graph.get_source(e_id); + tout << "$" << tgt << " <= $" << src << " + " << w << "\n"; + } + ); +#endif + +} + +template +void theory_diff_logic::set_neg_cycle_conflict() { + m_nc_functor.reset(); + m_graph.traverse_neg_cycle2(m_params.m_arith_stronger_lemmas, m_nc_functor); + inc_conflicts(); + literal_vector const& lits = m_nc_functor.get_lits(); + context & ctx = get_context(); + region& r = ctx.get_region(); + TRACE("arith_conflict", + //display(tout); + tout << "conflict: "; + for (unsigned i = 0; i < lits.size(); ++i) { + ctx.display_literal_info(tout, lits[i]); + } + tout << "\n"; + ); + + if (dump_lemmas()) { + char const * logic = m_is_lia ? "QF_LIA" : "QF_LRA"; + ctx.display_lemma_as_smt_problem(lits.size(), lits.c_ptr(), false_literal, logic); + } + + ctx.set_conflict(ctx.mk_justification(dl_conflict(r, lits.size(), lits.c_ptr()))); +} + +template +bool theory_diff_logic::is_offset(app* n, app*& v, app*& offset, rational& r) { + if (!m_util.is_add(n)) { + return false; + } + + if (n->get_num_args() == 2 && m_util.is_numeral(n->get_arg(0), r)) { + v = to_app(n->get_arg(1)); + offset = to_app(n->get_arg(0)); + return true; + } + if (n->get_num_args() == 2 && m_util.is_numeral(n->get_arg(1), r)) { + v = to_app(n->get_arg(0)); + offset = to_app(n->get_arg(1)); + return true; + } + return false; +} + +template +theory_var theory_diff_logic::mk_term(app* n) { + SASSERT(!m_util.is_sub(n)); + SASSERT(!m_util.is_uminus(n)); + app* a, *offset; + theory_var source, target; + enode* e; + + TRACE("arith", tout << mk_pp(n, get_manager()) << "\n";); + + rational r; + if (m_util.is_numeral(n, r)) { + return mk_num(n, r); + } + else if (is_offset(n, a, offset, r)) { + // n = a + k + source = mk_var(a); + e = get_context().mk_enode(n, false, false, true); + target = mk_var(e); + numeral k(r); + // target - source <= k, source - target <= -k + m_graph.enable_edge(m_graph.add_edge(source, target, k, null_literal)); + m_graph.enable_edge(m_graph.add_edge(target, source, -k, null_literal)); + return target; + } + else if (m_util.is_add(n)) { + return null_theory_var; + } + else if (m_util.is_mul(n)) { + return null_theory_var; + } + else if (m_util.is_div(n)) { + return null_theory_var; + } + else if (m_util.is_idiv(n)) { + return null_theory_var; + } + else if (m_util.is_mod(n)) { + return null_theory_var; + } + else if (m_util.is_rem(n)) { + return null_theory_var; + } + else { + return mk_var(n); + } +} + + +template +theory_var theory_diff_logic::mk_num(app* n, rational const& r) { + theory_var v = null_theory_var; + enode* e = 0; + context& ctx = get_context(); + if (r.is_zero()) { + v = get_zero(n); + } + else if (ctx.e_internalized(n)) { + e = ctx.get_enode(n); + v = e->get_th_var(get_id()); + SASSERT(v != null_theory_var); + } + else { + theory_var zero = get_zero(n); + e = ctx.mk_enode(n, false, false, true); + v = mk_var(e); + // internalizer is marking enodes as interpreted whenever the associated ast is a value and a constant. + // e->mark_as_interpreted(); + numeral k(r); + // v = k: v - zero <= k, zero - v <= - k + m_graph.enable_edge(m_graph.add_edge(zero, v, k, null_literal)); + m_graph.enable_edge(m_graph.add_edge(v, zero, -k, null_literal)); + } + return v; +} + + +template +theory_var theory_diff_logic::mk_var(enode* n) { + mark_as_modified_since_eq_prop(); + theory_var v = theory::mk_var(n); + TRACE("diff_logic_vars", tout << "mk_var: " << v << "\n";); + m_graph.init_var(v); + get_context().attach_th_var(n, this, v); + return v; +} + +template +bool theory_diff_logic::is_interpreted(app* n) const { + return n->get_family_id() == get_family_id(); +} + +template +theory_var theory_diff_logic::mk_var(app* n) { + context & ctx = get_context(); + enode* e = 0; + theory_var v = null_theory_var; + if (ctx.e_internalized(n)) { + e = ctx.get_enode(n); + v = e->get_th_var(get_id()); + } + else { + ctx.internalize(n, false); + e = ctx.get_enode(n); + } + if (v == null_theory_var) { + v = mk_var(e); + } + if (is_interpreted(n)) { + found_non_diff_logic_expr(n); + } + TRACE("arith", tout << mk_pp(n, get_manager()) << " |-> " << v << "\n";); + return v; +} + + + + +template +void theory_diff_logic::reset_eh() { + for (unsigned i = 0; i < m_atoms.size(); ++i) { + dealloc(m_atoms[i]); + } + m_graph .reset(); + m_zero_int = null_theory_var; + m_zero_real = null_theory_var; + m_atoms .reset(); + m_asserted_atoms .reset(); + m_stats .reset(); + m_scopes .reset(); + m_modified_since_eq_prop = false; + m_asserted_qhead = 0; + m_num_core_conflicts = 0; + m_num_propagation_calls = 0; + m_agility = 0.5; + m_is_lia = true; + m_non_diff_logic_exprs = false; + theory::reset_eh(); +} + + +template +bool theory_diff_logic::propagate_cheap_equalities() { + bool result = false; + TRACE("dl_new_eq", get_context().display(tout);); + context& ctx = get_context(); + region& reg = ctx.get_region(); + SASSERT(m_eq_prop_info_set.empty()); + SASSERT(m_eq_prop_infos.empty()); + if (m_modified_since_eq_prop) { + m_graph.compute_zero_edge_scc(m_scc_id); + int n = get_num_vars(); + for (theory_var v = 0; v < n; v++) { + rational delta_r; + theory_var x_v = expand(true, v, delta_r); + numeral delta(delta_r); + int scc_id = m_scc_id[x_v]; + if (scc_id != -1) { + delta += m_graph.get_assignment(x_v); + TRACE("eq_scc", tout << v << " " << x_v << " " << scc_id << " " << delta << "\n";); + eq_prop_info info(scc_id, delta); + typename eq_prop_info_set::entry * entry = m_eq_prop_info_set.find_core(&info); + if (entry == 0) { + eq_prop_info * new_info = alloc(eq_prop_info, scc_id, delta, v); + m_eq_prop_info_set.insert(new_info); + m_eq_prop_infos.push_back(new_info); + } + else { + // new equality found + theory_var r = entry->get_data()->get_root(); + + enode * n1 = get_enode(v); + enode * n2 = get_enode(r); + if (n1->get_root() != n2->get_root()) { + // r may be an alias (i.e., it is not realy in the graph). So, I should expand it. + // nsb: ?? + rational r_delta; + theory_var x_r = expand(true, r, r_delta); + + justification* j = new (reg) implied_eq_justification(*this, x_v, x_r, m_graph.get_timestamp()); + (void)j; + + m_stats.m_num_th2core_eqs++; + // TBD: get equality into core. + + NOT_IMPLEMENTED_YET(); + // new_eq_eh(x_v, x_r, *j); + result = true; + } + } + } + } + m_eq_prop_info_set.reset(); + std::for_each(m_eq_prop_infos.begin(), m_eq_prop_infos.end(), delete_proc()); + m_eq_prop_infos.reset(); + unmark_as_modified_since_eq_prop(); + } + + TRACE("dl_new_eq", get_context().display(tout);); + SASSERT(!m_modified_since_eq_prop); + + return result; +} + + +template +void theory_diff_logic::compute_delta() { + m_delta = rational(1); + unsigned num_edges = m_graph.get_num_edges(); + for (unsigned i = 0; i < num_edges; ++i) { + if (!m_graph.is_enabled(i)) { + continue; + } + numeral w = m_graph.get_weight(i); + dl_var tgt = m_graph.get_target(i); + dl_var src = m_graph.get_source(i); + rational n_x = m_graph.get_assignment(tgt).get_rational().to_rational(); + rational k_x = m_graph.get_assignment(tgt).get_infinitesimal().to_rational(); + rational n_y = m_graph.get_assignment(src).get_rational().to_rational(); + rational k_y = m_graph.get_assignment(src).get_infinitesimal().to_rational(); + rational n_c = w.get_rational().to_rational(); + rational k_c = w.get_infinitesimal().to_rational(); + TRACE("epsilon", tout << "(n_x,k_x): " << n_x << ", " << k_x << ", (n_y,k_y): " + << n_y << ", " << k_y << ", (n_c,k_c): " << n_c << ", " << k_c << "\n";); + if (n_x < n_y + n_c && k_x > k_y + k_c) { + rational new_delta = (n_y + n_c - n_x) / (k_x - k_y - k_c); + if (new_delta < m_delta) { + TRACE("epsilon", tout << "new delta: " << new_delta << "\n";); + m_delta = new_delta; + } + } + } +} + + +template +void theory_diff_logic::init_model(smt::model_generator & m) { + m_factory = alloc(arith_factory, get_manager()); + m.register_factory(m_factory); + compute_delta(); +} + + +template +model_value_proc * theory_diff_logic::mk_value(enode * n, model_generator & mg) { + theory_var v = n->get_th_var(get_id()); + SASSERT(v != null_theory_var); + numeral val = m_graph.get_assignment(v); + rational num = val.get_rational().to_rational() + m_delta * val.get_infinitesimal().to_rational(); + return alloc(expr_wrapper_proc, m_factory->mk_value(num, m_util.is_int(n->get_owner()))); +} + +template +bool theory_diff_logic::validate_eq_in_model(theory_var v1, theory_var v2, bool is_true) const { + NOT_IMPLEMENTED_YET(); + return true; +} + + +template +void theory_diff_logic::display(std::ostream & out) const { + for (unsigned i = 0; i < m_atoms.size(); ++i) { + m_atoms[i]->display(*this, out); + } + m_graph.display(out); +} + +template +bool theory_diff_logic::is_consistent() const { + context& ctx = get_context(); + for (unsigned i = 0; i < m_atoms.size(); ++i) { + atom* a = m_atoms[i]; + bool_var bv = a->get_bool_var(); + lbool asgn = ctx.get_assignment(bv); + if (ctx.is_relevant(ctx.bool_var2expr(bv)) && asgn != l_undef) { + SASSERT((asgn == l_true) == a->is_true()); + switch(a->kind()) { + case LE_ATOM: { + le_atom* le = dynamic_cast(a); + int edge_id = le->get_asserted_edge(); + SASSERT(m_graph.is_enabled(edge_id)); + SASSERT(m_graph.is_feasible(edge_id)); + break; + } + case EQ_ATOM: { + eq_atom* ea = dynamic_cast(a); + bool_var bv1 = ctx.get_bool_var(ea->get_le()); + bool_var bv2 = ctx.get_bool_var(ea->get_ge()); + lbool val1 = ctx.get_assignment(bv1); + lbool val2 = ctx.get_assignment(bv2); + if (asgn == l_true) { + SASSERT(val1 == l_true); + SASSERT(val2 == l_true); + } + else { + SASSERT(val1 == l_false || val2 == l_false); + } + break; + } + } + } + } + return m_graph.is_feasible(); +} + + +template +theory_var theory_diff_logic::expand(bool pos, theory_var v, rational & k) { + context& ctx = get_context(); + enode* e = get_enode(v); + rational r; + for (;;) { + app* n = e->get_owner(); + if (m_util.is_add(n) && n->get_num_args() == 2) { + app* x = to_app(n->get_arg(0)); + app* y = to_app(n->get_arg(1)); + if (m_util.is_numeral(x, r)) { + e = ctx.get_enode(y); + } + else if (m_util.is_numeral(y, r)) { + e = ctx.get_enode(x); + } + v = e->get_th_var(get_id()); + SASSERT(v != null_theory_var); + if (v == null_theory_var) { + break; + } + if (pos) { + k += r; + } + else { + k -= r; + } + } + else { + break; + } + } + return v; +} + +template +void theory_diff_logic::new_eq_or_diseq(bool is_eq, theory_var v1, theory_var v2, justification& eq_just) { + rational k; + theory_var s = expand(true, v1, k); + theory_var t = expand(false, v2, k); + context& ctx = get_context(); + ast_manager& m = get_manager(); + + if (s == t) { + if (is_eq != k.is_zero()) { + // conflict 0 /= k; + inc_conflicts(); + ctx.set_conflict(&eq_just); + } + } + else { + // + // Create equality ast, internalize_atom + // assign the corresponding equality literal. + // + + mark_as_modified_since_eq_prop(); + + app_ref eq(m), s2(m), t2(m); + app* s1 = get_enode(s)->get_owner(); + app* t1 = get_enode(t)->get_owner(); + s2 = m_util.mk_sub(t1, s1); + t2 = m_util.mk_numeral(k, m.get_sort(s2.get())); + // t1 - s1 = k + eq = m.mk_eq(s2.get(), t2.get()); + + TRACE("diff_logic", + tout << v1 << " .. " << v2 << "\n"; + tout << mk_pp(eq.get(), m) <<"\n";); + + if (!internalize_atom(eq.get(), false)) { + UNREACHABLE(); + } + + literal l(ctx.get_literal(eq.get())); + if (!is_eq) { + l = ~l; + } + + ctx.assign(l, b_justification(&eq_just), false); + } +} + +template +void theory_diff_logic::new_eq_eh( + theory_var v1, theory_var v2, justification& j) { + m_stats.m_num_core2th_eqs++; + new_eq_or_diseq(true, v1, v2, j); +} + + +template +void theory_diff_logic::new_diseq_eh( + theory_var v1, theory_var v2, justification& j) { + m_stats.m_num_core2th_diseqs++; + new_eq_or_diseq(false, v1, v2, j); +} + + +template +void theory_diff_logic::new_eq_eh(theory_var v1, theory_var v2) { + m_arith_eq_adapter.new_eq_eh(v1, v2); +} + + +template +void theory_diff_logic::new_diseq_eh(theory_var v1, theory_var v2) { + m_arith_eq_adapter.new_diseq_eh(v1, v2); +} + + +template +void theory_diff_logic::relevant_eh(app* e) { +} + + +struct imp_functor { + conflict_resolution & m_cr; + imp_functor(conflict_resolution& cr) : m_cr(cr) {} + void operator()(literal l) { + m_cr.mark_literal(l); + } +}; + +template +void theory_diff_logic::get_eq_antecedents( + theory_var v1, theory_var v2, unsigned timestamp, conflict_resolution & cr) { + imp_functor functor(cr); + bool r; + r = m_graph.find_shortest_zero_edge_path(v1, v2, timestamp, functor); + SASSERT(r); + r = m_graph.find_shortest_zero_edge_path(v2, v1, timestamp, functor); + SASSERT(r); +} + +template +void theory_diff_logic::get_implied_bound_antecedents(edge_id bridge_edge, edge_id subsumed_edge, conflict_resolution & cr) { + imp_functor f(cr); + m_graph.explain_subsumed_lazy(bridge_edge, subsumed_edge, f); +} + +#endif /* _THEORY_DIFF_LOGIC_DEF_H_ */ + diff --git a/lib/theory_dl.cpp b/lib/theory_dl.cpp new file mode 100644 index 000000000..402ab856c --- /dev/null +++ b/lib/theory_dl.cpp @@ -0,0 +1,278 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + theory_dl.h + +Abstract: + + Theory for DL constants. + DL constants are discrete and ordered by the linear order LT. + Constants have a parameter which indicates the numeric value that ranges + from 0 up to the size of the domain. + + The procedure works by a simple reduction to bit-vectors. We enforce an injection into bit-vectors. + +Author: + + Nikolaj Bjorner (nbjorner) 2011-1-10 + +Revision History: + +--*/ + +#include "smt_theory.h" +#include "dl_decl_plugin.h" +#include "value_factory.h" +#include "smt_model_generator.h" +#include "bv_decl_plugin.h" +#include "theory_bv.h" +#include "smt_context.h" +#include "ast_pp.h" + +// Basic approach: reduce theory to bit-vectors: +// +// rep(c(n)) = n +// LT(x,y) <=> rep(x) < rep(y) +// val(rep(x)) = x +// 0 <= rep(x) <= max_value +// + + +namespace smt { + + + class dl_factory : public simple_factory { + datalog::dl_decl_util& m_util; + public: + dl_factory(datalog::dl_decl_util& u, proto_model& m): + simple_factory(u.get_manager(), u.get_family_id()), + m_util(u) + {} + + virtual app * mk_value_core(unsigned const & val, sort * s) { + return m_util.mk_numeral(val, s); + } + }; + + class theory_dl : public theory { + datalog::dl_decl_util m_util; + bv_util m_bv; + ast_ref_vector m_trail; + obj_map m_reps; + obj_map m_vals; + + ast_manager& m() { return get_manager(); } + datalog::dl_decl_util& u() { return m_util; } + bv_util& b() { return m_bv; } + + class dl_value_proc : public smt::model_value_proc { + smt::model_generator & m_mg; + theory_dl& m_th; + smt::enode* m_node; + public: + + dl_value_proc(smt::model_generator & m, theory_dl& th, smt::enode* n): + m_mg(m), m_th(th), m_node(n) + { } + + virtual void get_dependencies(buffer & result) {} + + virtual app * mk_value(smt::model_generator & mg, ptr_vector & ) { + smt::context& ctx = m_th.get_context(); + app* result = 0; + expr* n = m_node->get_owner(); + sort* s = m_th.m().get_sort(n); + func_decl* r, *v; + m_th.get_rep(s, r, v); + app_ref rep_of(m_th.m()); + rep_of = m_th.m().mk_app(r, m_node->get_owner()); + theory_id bv_id = m_th.m().get_family_id("bv"); + theory_bv* th_bv = dynamic_cast(ctx.get_theory(bv_id)); + SASSERT(th_bv); + rational val; + if (ctx.e_internalized(rep_of) && th_bv && + th_bv->get_fixed_value(rep_of.get(), val)) { + result = m_th.u().mk_numeral(val.get_int64(), s); + } + else { + result = m_th.u().mk_numeral(0, s); + } + TRACE("theory_dl", tout << mk_pp(result, m_th.m()) << "\n";); + return result; + } + }; + + public: + theory_dl(ast_manager& m): + theory(m.get_family_id("datalog_relation")), + m_util(m), + m_bv(m), + m_trail(m) + { + } + + + virtual char const * get_name() const { return "datalog"; } + + virtual bool internalize_atom(app * atom, bool gate_ctx) { + TRACE("theory_dl", tout << mk_pp(atom, m()) << "\n";); + context& ctx = get_context(); + if (ctx.b_internalized(atom)) { + return true; + } + switch(atom->get_decl_kind()) { + case datalog::OP_DL_LT: { + app* a = to_app(atom->get_arg(0)); + app* b = to_app(atom->get_arg(1)); + ctx.internalize(a, false); + ctx.internalize(b, false); + literal l(ctx.mk_bool_var(atom)); + ctx.set_var_theory(l.var(), get_id()); + mk_lt(a,b); + return true; + } + default: + break; + } + return false; + } + + virtual bool internalize_term(app * term) { + TRACE("theory_dl", tout << mk_pp(term, m()) << "\n";); + if (u().is_finite_sort(term)) { + return mk_rep(term); + } + else { + return false; + } + } + + virtual void new_eq_eh(theory_var v1, theory_var v2) { + + } + + virtual void new_diseq_eh(theory_var v1, theory_var v2) { + + } + + virtual theory * mk_fresh(context * new_ctx) { + return alloc(theory_dl, get_manager()); + } + + virtual void init_model(smt::model_generator & m) { + m.register_factory(alloc(dl_factory, m_util, m.get_model())); + } + + virtual smt::model_value_proc * mk_value(smt::enode * n, smt::model_generator & m) { + return alloc(dl_value_proc, m, *this, n); + } + + virtual void apply_sort_cnstr(enode * n, sort * s) { + app* term = n->get_owner(); + if (u().is_finite_sort(term)) { + mk_rep(term); + } + } + + + virtual void relevant_eh(app * n) { + if (u().is_finite_sort(n)) { + sort* s = m().get_sort(n); + func_decl* r, *v; + get_rep(s, r, v); + + if (n->get_decl() != v) { + expr* rep = m().mk_app(r, n); + uint64 vl; + if (u().is_numeral_ext(n, vl)) { + assert_cnstr(m().mk_eq(rep, mk_bv_constant(vl, s))); + } + else { + assert_cnstr(m().mk_eq(m().mk_app(v,rep), n)); + assert_cnstr(b().mk_ule(rep, max_value(s))); + } + } + } + } + + + private: + + void get_rep(sort* s, func_decl*& r, func_decl*& v) { + if(!m_reps.find(s, r) || !m_vals.find(s,v)) { + SASSERT(!m_reps.contains(s)); + sort* bv = b().mk_sort(64); + // TBD: filter these from model. + r = m().mk_fresh_func_decl("rep",1, &s,bv); + v = m().mk_fresh_func_decl("val",1, &bv,s); + m_reps.insert(s, r); + m_vals.insert(s, v); + add_trail(r); + add_trail(v); + get_context().push_trail(insert_obj_map(m_reps, s)); + get_context().push_trail(insert_obj_map(m_vals, s)); + } + } + + bool mk_rep(app* n) { + context & ctx = get_context(); + unsigned num_args = n->get_num_args(); + enode * e = 0; + for (unsigned i = 0; i < num_args; i++) { + ctx.internalize(n->get_arg(i), false); + } + if (ctx.e_internalized(n)) { + e = ctx.get_enode(n); + } + else { + e = ctx.mk_enode(n, false, false, true); + } + if (is_attached_to_var(e)) { + return false; + } + TRACE("theory_dl", tout << mk_pp(n, m()) << "\n";); + theory_var var = mk_var(e); + ctx.attach_th_var(e, this, var); + return true; + } + + app* mk_bv_constant(uint64 val, sort* s) { + return b().mk_numeral(rational(val,rational::ui64()),64); + } + + app* max_value(sort* s) { + uint64 sz; + VERIFY(u().try_get_size(s, sz)); + return mk_bv_constant(sz, s); + } + + void mk_lt(app* x, app* y) { + sort* s = m().get_sort(x); + func_decl* r, *v; + get_rep(s, r, v); + app* lt1 = u().mk_lt(x,y); + app* lt2 = m().mk_not(b().mk_ule(m().mk_app(r,y),m().mk_app(r,x))); + assert_cnstr(m().mk_iff(lt1, lt2)); + } + + void assert_cnstr(expr* e) { + TRACE("theory_dl", tout << mk_pp(e, m()) << "\n";); + context& ctx = get_context(); + ctx.internalize(e, false); + literal lit(ctx.get_literal(e)); + ctx.mark_as_relevant(lit); + ctx.mk_th_axiom(get_id(), 1, &lit); + } + + void add_trail(ast* a) { + m_trail.push_back(a); + get_context().push_trail(push_back_vector(m_trail)); + } + + }; + + theory* mk_theory_dl(ast_manager& m) { return alloc(theory_dl, m); } + +}; diff --git a/lib/theory_dl.h b/lib/theory_dl.h new file mode 100644 index 000000000..e4256dab5 --- /dev/null +++ b/lib/theory_dl.h @@ -0,0 +1,30 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + theory_dl.h + +Abstract: + + Basic theory solver for DL finite domains. + +Author: + + Nikolaj Bjorner (nbjorner) 2011-10-3 + +Revision History: + +--*/ +#ifndef _THEORY_DL_H_ +#define _THEORY_DL_H_ + + +namespace smt { + + theory* mk_theory_dl(ast_manager& m); + +}; + +#endif /* _THEORY_DL_H_ */ + diff --git a/lib/theory_dummy.cpp b/lib/theory_dummy.cpp new file mode 100644 index 000000000..c8b9c3b0a --- /dev/null +++ b/lib/theory_dummy.cpp @@ -0,0 +1,73 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_dummy.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-12-30. + +Revision History: + +--*/ + +#include"smt_context.h" +#include"theory_dummy.h" + +namespace smt { + + void theory_dummy::found_theory_expr() { + if (!m_theory_exprs) { + get_context().push_trail(value_trail(m_theory_exprs)); + m_theory_exprs = true; + } + } + + theory_dummy::theory_dummy(family_id fid, char const * name): + theory(fid), + m_theory_exprs(false), + m_name(name) { + } + + bool theory_dummy::internalize_atom(app * atom, bool gate_ctx) { + found_theory_expr(); + return false; + } + + bool theory_dummy::internalize_term(app * term) { + found_theory_expr(); + return false; + } + + void theory_dummy::new_eq_eh(theory_var v1, theory_var v2) { + UNREACHABLE(); + } + + bool theory_dummy::use_diseqs() const { + return false; + } + + void theory_dummy::new_diseq_eh(theory_var v1, theory_var v2) { + UNREACHABLE(); + } + + void theory_dummy::reset_eh() { + m_theory_exprs = true; + theory::reset_eh(); + } + + final_check_status theory_dummy::final_check_eh() { + return m_theory_exprs ? FC_GIVEUP : FC_DONE; + } + + char const * theory_dummy::get_name() const { + return m_name; + } + +}; diff --git a/lib/theory_dummy.h b/lib/theory_dummy.h new file mode 100644 index 000000000..232cfba66 --- /dev/null +++ b/lib/theory_dummy.h @@ -0,0 +1,58 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + theory_no_arith.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-12-30. + +Revision History: + +--*/ +#ifndef _THEORY_DUMMY_H_ +#define _THEORY_DUMMY_H_ + +#include"smt_theory.h" + +namespace smt { + + /** + \brief Do nothing theory. Tracks whether theory expressions were internalized. + When theory expressions were internalized, it returns FC_GIVEUP in the final_check_eh. + */ + class theory_dummy : public theory { + bool m_theory_exprs; + char const * m_name; + void found_theory_expr(); + + protected: + virtual bool internalize_atom(app * atom, bool gate_ctx); + virtual bool internalize_term(app * term); + virtual void new_eq_eh(theory_var v1, theory_var v2); + virtual bool use_diseqs() const; + virtual void new_diseq_eh(theory_var v1, theory_var v2); + virtual void reset_eh(); + virtual final_check_status final_check_eh(); + virtual bool build_models() const { + return false; + } + + public: + theory_dummy(family_id fid, char const * name); + virtual ~theory_dummy() {} + + virtual theory * mk_fresh(context * new_ctx) { return alloc(theory_dummy, get_family_id(), m_name); } + + virtual char const * get_name() const; + }; +}; + +#endif /* _THEORY_DUMMY_H_ */ + diff --git a/lib/theory_instgen.cpp b/lib/theory_instgen.cpp new file mode 100644 index 000000000..f631fd04c --- /dev/null +++ b/lib/theory_instgen.cpp @@ -0,0 +1,1494 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + theory_instgen.cpp + +Abstract: + + iProver style theory solver. + It provides an instantiation based engine + based on InstGen methods together with + unit propagation. + +Author: + + Krystof Hoder (t-khoder) + Nikolaj Bjorner (nbjorner) 2011-10-6 + +Revision History: + +Main data-structures: + +- Instantiation = Var -> Expr +- MatchingSet = Instantiation-set + operations: + - has_instance : Instantiation -> Bool + has_instance(i) = exists j in MatchingSet . j <= i +- UnificationIndex + operations: + - insert : Expr + - remove : Expr + - unify : Expr -> Expr-set + - set of inserted expressions that unify +- RootClauseMap : Expr -> Quantifier + where RootClauseMap(clause) = The quantifier that originated clause +- Occurs : Expr -> Quantifier-set + the set of quantifiers where non-ground literal occurs. +- LiteralMeanings : Literal -> Expr-set + - set of non-ground literals that were grounded to Literal +- InternalizedFoLits : Expr-set + - forall e in InternalizedFoLits . e \in LiteralMeanings(ground(e)) +- MatchingSets : Quantifier -> MatchingSet + + +Main operation: + + - insert all assigned literals into UnificationIndex + - for l' in LiteralMeanings(l) do: + for m',theta in UnificationIndex.unify(not l') do: + for q in Occurs(l') do: + for q' in Occurs(m') do: + instantiate q with theta + instantiate q' with theta + + instantiate q with theta: + +Discussion of plans: + +- Efficient unit propagation using the tables from dl_ + See addittional notes under unit propagation. + The idea is to perfrm unit propagation using the tables + provided in the dl_ module. This is similar to unit + propagation from the EPR solver and retains succinctness + features, but does not carry over the splitting component. + The efficient propagator is aimed at solving ground problems more efficiently, + for example + +- Reduce set of selected literals when clause already has on selected literal. + +- Subsumption module: + - simplify clause using already asserted clauses. + - check for variants. + +- Completeness for EPR with equality: + The relevant equality clause for EPR are C \/ x = y and C \/ a = x + Destructive E-resolution (DER) removes disequalities. + Two approaches: + 1. Rely on super-position/hyper-resolution of ordinary literals + in the clause. + 2. Instantiate based on congruence closure. + The instantiation based approach takes a clause of the form C \/ x = y, + where all other non-equality literals in C are assigned false by the + current assignment, and (the grounded version U = U' of) x = y is + assigned true. Take the equivalence classes of the type of x and + instantiate x, y using representatives for different equivalence + classes. The number of instantiations is potentially quadratic + in the number of terms. One reduction considers symmetries: + instantiate x by a smaller representative than y. +- Unit propagation: + - Why should unit-propagation matter: hunch: similar role as + theory propagation where conflicts are created close to root + of search tree. + - Add theory identifiers to literals so that assign_eh is invoked. + - Produce explanation objects for unit propagation. + - Unit propagation index. + - Idea only propagate known literals +- Exchange unit literals with super position engine for equalities. + iProver approach: perform unit super-position proof, get instances + by labeling equalities by clauses + substitution (set-labeling) + portfolio approach: exchange unit literals to super-position + (or hypotheses as suggested in more general setting). + +--*/ + +#include "theory_instgen.h" +#include "value_factory.h" +#include "smt_model_generator.h" +#include "smt_context.h" +#include "ast_smt2_pp.h" +#include "substitution.h" +#include "substitution_tree.h" +#include "uint_set.h" +#include "unifier.h" +#include "matcher.h" +#include "rewriter.h" +#include "rewriter_def.h" +#include "var_subst.h" + +#define PP mk_pp + +namespace smt { + + + // + // expression grounding for passing to the SMT solver + // + class grounder { + ast_manager& m; + vector > m_defaults; + expr_ref_vector m_ref_holder; + + class grounding_rewriter_cfg; + + void reserve(unsigned idx) { + if (m_defaults.size() <= idx) { + m_defaults.resize(idx+1); + } + } + + expr* mk_default(sort* s) { + return mk_default(0, s); + } + + expr* mk_default(unsigned i, sort* s) { + expr* d; + reserve(i); + if (!m_defaults[i].find(s, d)) { + d = m.mk_fresh_const("U",s); + m_defaults[i].insert(s, d); + m_ref_holder.push_back(d); + } + return d; + } + + class grounding_rewriter_cfg : public default_rewriter_cfg { + grounder& m_parent; + bool m_collapse; + public: + grounding_rewriter_cfg(grounder& parent, bool collapse) + : m_parent(parent), m_collapse(collapse) {} + + bool get_subst(expr * s, expr * & t, proof * & t_pr) { + SASSERT(is_app(s) || is_var(s)); + if (is_var(s)) { + var* v = to_var(s); + if (m_collapse) { + t = m_parent.mk_default(v->get_sort()); + } + else { + t = m_parent.mk_default(v->get_idx(), v->get_sort()); + } + } + return is_var(s); + } + }; + + void mk(expr * e, app_ref& res, bool collapse) { + if(is_ground(e)) { + res = to_app(e); + } + else { + while (is_quantifier(e)) { + e = to_quantifier(e)->get_expr(); + } + SASSERT(is_app(e)); + grounding_rewriter_cfg r_cfg(*this, collapse); + rewriter_tpl rwr(m, false, r_cfg); + expr_ref res_e(m); + rwr(e, res_e); + res = to_app(res_e); + } + SASSERT(is_ground(res.get())); + } + + public: + grounder(ast_manager& m): m(m), m_ref_holder(m) { + reserve(0); + } + + /** + create a grounding that recycles the same constant for + different variables of the same sort. + + This function can be called either with whole clauses (incl. quantifier), + or with literals one by one (without a quantifier) + */ + void operator()(expr * e, app_ref& res) { + mk(e, res, true); + } + + // + // create a grounding where different variables have different names + // + void mk_diff(expr * e, app_ref& res) { + mk(e, res, false); + } + }; + + // + // Class for first-order subsumption checking. + // if clause[renaming] is a superset of existing clause in context, then clause is subsumed. + // if context => clause then clause is subsumed. + // if context & clause => ~ lit then lit is subsumed from clause + // + // TBD: + // - check unit propagation + // - use internalizer functions directly. The assertions have already been pre-processed. + // + class clause_subsumption { + ast_manager& m; + grounder m_grounder; + front_end_params m_params; + context m_ctx; + quantifier_ref_vector m_assumptions; + unsigned_vector m_limit; + public: + clause_subsumption(ast_manager& m, front_end_params& p): + m(m), m_grounder(m), m_params(p), m_ctx(m,m_params), m_assumptions(m) { + m_params.m_instgen = false; + } + + void simplify(quantifier* new_clause, expr_ref& result, ptr_vector& assumptions) { +#if 1 + result = new_clause; + return; +#endif + + SASSERT(new_clause->is_forall()); + expr* body = new_clause->get_expr(); + app_ref ground_clause(m); + m_grounder.mk_diff(new_clause, ground_clause); + if (is_subsumed(ground_clause)) { + TRACE("theory_instgen", tout << "subsumed: " << PP(new_clause,m) << "\n";); + result = m.mk_true(); + return; + } + if (is_homomorphic_image(body)) { + result = m.mk_true(); + return; + } + // Assert the current clause. + m_ctx.internalize(ground_clause, true); + m_ctx.assign(m_ctx.get_literal(ground_clause), b_justification()); + TRACE("theory_instgen", tout << "Asserting: " << PP(ground_clause,m) << "\n";); + m_assumptions.push_back(new_clause); + SASSERT(m.is_or(body) == m.is_or(ground_clause)); + if (!m.is_or(body)) { + result = new_clause; + return; + } + SASSERT(to_app(body)->get_num_args() == ground_clause->get_num_args()); + ptr_vector lits; + for (unsigned i = 0; i < to_app(body)->get_num_args(); ++i) { + m_ctx.push(); + m_ctx.assign(m_ctx.get_literal(ground_clause->get_arg(i)), b_justification()); + lbool is_sat = m_ctx.check(); + m_ctx.pop(1); + if (is_sat != l_false) { + lits.push_back(to_app(body)->get_arg(i)); + } + else { + TRACE("theory_instgen", tout << "subsumed literal: " << PP(to_app(body)->get_arg(i),m) << "\n";); + } + } + if (lits.size() == ground_clause->get_num_args()) { + result = new_clause; + } + else { + SASSERT(!lits.empty()); + result = lits.size()==1?lits[0]:m.mk_or(lits.size(), lits.c_ptr()); + result = m.update_quantifier(new_clause, result); + TRACE("theory_instgen", tout << "simplified: " << PP(new_clause,m) << "\n"; + tout << PP(result.get(), m) << "\n"; + ); + //overapproximation of required assumptions + //( m_assumptions.size()-1 ... the -1 is not to make ourselves as an assumption) + assumptions.append(m_assumptions.size()-1, m_assumptions.c_ptr()); + } + } + + void push() { + m_ctx.push(); + m_limit.push_back(m_assumptions.size()); + } + + void pop(unsigned num_scopes) { + m_ctx.pop(num_scopes); + + unsigned last_idx = m_limit.size()-num_scopes; + unsigned restored_assumptions_size = m_limit[last_idx]; + m_limit.resize(last_idx); + m_assumptions.resize(restored_assumptions_size); + } + + private: + + bool is_subsumed(expr* ground_clause) { + m_ctx.push(); + m_ctx.internalize(ground_clause, true); + m_ctx.assign(~m_ctx.get_literal(ground_clause), b_justification()); + lbool is_sat = m_ctx.check(); + m_ctx.pop(1); + TRACE("theory_instgen", + tout << PP(ground_clause, m) << " " << + ((is_sat==l_false)?"unsat":"sat") << "\n";); + return (is_sat == l_false); + } + + bool is_homomorphic_image(expr* body) { + // TBD + return false; + } + + }; + + class fo_clause_internalizer; + class instantiator; + class theory_instgen_impl; + typedef expr_ref_vector inst; + + class instantiation_result { + quantifier_ref m_clause; + inst m_subst; + public: + instantiation_result(ast_manager& m) : m_clause(m), m_subst(m) {} + + void init(quantifier * q, const inst& subst) { + SASSERT(!m_clause); //we init each object at most once + SASSERT(m_subst.empty()); + SASSERT(q); + m_clause = q; + m_subst.append(subst); + } + quantifier * clause() const { return m_clause; } + const inst& subst() const { return m_subst; } + }; + + typedef vector instantiation_result_vector; + + // TBD: replace this by the substitution tree index. + // It should do the trick of identifying instances and generalizers. + // see matching_set2.. + // + class matching_set { + ast_manager& m; + vector m_inst; + + //used in the has_instance function + mutable substitution m_subst; + + public: + matching_set(ast_manager& m) : m(m), m_subst(m) {} + unsigned size() const { return m_inst.size(); } + inst const& operator[](unsigned i) const { return m_inst[i]; } + + void insert(inst const& inst) { + SASSERT(m_inst.empty() || m_inst.back().size() == inst.size()); + TRACE("theory_instgen_verbose", + for (unsigned i = 0; i < inst.size(); ++i) { + tout << PP(inst[i], m) << " "; + } + tout << "\n"; + ); + m_inst.push_back(inst); + } + void insert(unsigned sz, expr* const* exprs) { + insert(inst(m, sz, exprs)); + } + void pop_insert() { m_inst.pop_back(); } + + bool has_instance(inst const& inst) { + unsigned dont_care; + return has_instance(inst, dont_care); + } + + bool has_instance(inst const& new_inst, unsigned& index) { + for (unsigned i = 0; i < size(); ++i) { + if (has_instance(new_inst, m_inst[i])) { + index = i; + return true; + } + } + return false; + } + + class insert_inst : public trail { + matching_set& m_ms; + public: + insert_inst(matching_set& m): m_ms(m) {} + virtual void undo(smt::context& ctx) { m_ms.pop_insert(); } + }; + + static bool is_identity(const inst& subst) { + uint_set vars; + vars.reset(); + unsigned sz = subst.size(); + for(unsigned i=0; iget_idx(); + if(vars.contains(var_idx)) { + return false; + } + vars.insert(var_idx); + } + return true; + } + + private: + // check if old_instance is an instance of new_instance. + bool has_instance(inst const& new_instance, inst const& old_instance) const { + SASSERT(new_instance.size() == old_instance.size()); + unsigned sz = new_instance.size(); + m_subst.reset(); + m_subst.reserve_vars(sz); + m_subst.reserve_offsets(2); + matcher mtchr(m); + for(unsigned i = 0; i < sz; i++) { + TRACE("theory_instgen_verbose", tout << PP(new_instance[i], m) << " " << PP(old_instance[i], m) << "\n";); + if(!mtchr(new_instance[i], old_instance[i], m_subst)) { + return false; + } + } + return true; + } + }; + + + class matching_set2 { + class inst_visitor : public st_visitor { + bool m_found; + public: + inst_visitor(substitution& s): st_visitor(s), m_found(false) {} + virtual bool operator()(expr * e) { + m_found = true; + return false; + } + void reset() { m_found = false; } + bool found() const { return m_found; } + }; + + ast_manager& m; + substitution_tree m_st; + func_decl_ref m_f; + app_ref_vector m_trail; + substitution m_dummy; + inst_visitor m_visitor; + + public: + matching_set2(ast_manager& m) : m(m), m_st(m), m_f(m), m_trail(m), m_dummy(m), m_visitor(m_dummy) {} + + void insert(inst const& inst) { + if (!m_f.get()) { + ptr_buffer domain; + for (unsigned i = 0; i < inst.size(); ++i) { + domain.push_back(m.get_sort(inst[i])); + } + m_f = m.mk_func_decl(symbol("tuple"),inst.size(), domain.c_ptr(), m.mk_bool_sort()); + m_trail.push_back(m.mk_app(m_f, inst.size(), inst.c_ptr())); + m_st.insert(m_trail.back()); + } + } + void insert(unsigned sz, expr* const* exprs) { + insert(inst(m, sz, exprs)); + } + void pop_insert() { + m_st.erase(m_trail.back()); + m_trail.pop_back(); + } + + bool has_instance(inst const& inst) { + app_ref f(m); + f = m.mk_app(m_f, inst.size(), inst.c_ptr()); + m_visitor.reset(); + m_st.inst(f, m_visitor); + return m_visitor.found(); + } + + class insert_inst : public trail { + matching_set& m_ms; + public: + insert_inst(matching_set& m): m_ms(m) {} + virtual void undo(smt::context& ctx) { m_ms.pop_insert(); } + }; + + static bool is_identity(const inst& subst) { + uint_set vars; + vars.reset(); + unsigned sz = subst.size(); + for(unsigned i=0; iget_idx(); + if(vars.contains(var_idx)) { + return false; + } + vars.insert(var_idx); + } + return true; + } + }; + + + ///////////////////////// + // inst_gen_unif_index + // + + class inst_gen_unif_index { + ast_manager & m; + substitution_tree m_st; + unsigned m_num_vars; + app_ref_vector m_ref_holder; + unsigned_vector m_lim; + + class collecting_visitor : public st_visitor { + app_ref_vector& m_acc; + public: + collecting_visitor(app_ref_vector& acc, substitution& subst) + : st_visitor(subst), m_acc(acc) {} + virtual bool operator()(expr * e) { + SASSERT(is_app(e)); + m_acc.push_back(to_app(e)); + return true; + } + }; + + + class st_contains_visitor : public st_visitor { + expr* m_e; + bool m_found; + public: + st_contains_visitor(substitution& s, expr* e): st_visitor(s), m_e(e), m_found(false) {} + virtual bool operator()(expr* e) { + if (e == m_e) { + m_found = true; + return false; + } + return true; + } + bool found() const { return m_found; } + }; + + void debug_st(char const* cmd, app* l) { + expr_ref e(m); + ptr_vector sorts; + svector names; + get_free_vars(l, sorts); + for (unsigned i = 0; i < sorts.size(); ++i) { + if (!sorts[i]) { + sorts[i] = m.mk_bool_sort(); + } + names.push_back(symbol(i)); + } + sorts.reverse(); + if (!sorts.empty()) { + e = m.mk_forall(sorts.size(), sorts.c_ptr(), names.c_ptr(), l); + } + else { + e = l; + } + std::cout << ":" << cmd << " " << PP(e.get(),m) << "\n"; + } + + public: + inst_gen_unif_index(ast_manager & m) : + m(m), m_st(m), m_num_vars(0), m_ref_holder(m) {} + + void insert_literal(app * lit) { + // debug_st("st_insert", lit); + m_ref_holder.push_back(lit); + m_st.insert(lit); + } + + void get_unifications(app * lit, app_ref_vector& res) { + substitution subst(m); + subst.reserve_vars(m_num_vars); + subst.reserve_offsets(m_st.get_approx_num_regs()); + collecting_visitor visitor(res, subst); + // TRACE("theory_instgen", m_st.display(tout); ); + m_st.unify(lit, visitor); + } + void reserve_num_vars(unsigned num_vars) { + if (num_vars > m_num_vars) m_num_vars = num_vars; + } + + void push() { + m_lim.push_back(m_ref_holder.size()); + } + + void pop() { + unsigned sz = m_lim.back(); + m_ref_holder.resize(sz); + m_lim.pop_back(); + m_st.reset(); + } + + void pop_orig() { + unsigned sz = m_lim.back(); + while (m_ref_holder.size() > sz) { + debug_st("st_erase", m_ref_holder.back()); + m_st.erase(m_ref_holder.back()); + + substitution subst(m); + subst.reserve_vars(m_num_vars); + subst.reserve_offsets(m_st.get_approx_num_regs()); + st_contains_visitor cv(subst, m_ref_holder.back()); + m_st.unify(m_ref_holder.back(), cv); + if (cv.found()) { + m_st.display(std::cout); + m_st.erase(m_ref_holder.back()); + } + SASSERT(!cv.found()); + m_ref_holder.pop_back(); + } + m_lim.pop_back(); + } + + void display(std::ostream& out) { + m_st.display(out); + } + + bool empty() const{ + return m_st.empty(); + } + }; + + + /////////////////////////// + // fo_clause_internalizer + // + + class fo_clause_internalizer { + private: + typedef map, default_eq > literal_meaning_map; + typedef obj_map occurs; + typedef obj_map root_clause_map; //for any clause instance it gives us the clause from the original problem + + theory_instgen_impl& m_parent; + expr_ref_vector m_vars; + var_subst m_subst; + occurs m_occurs; + grounder m_grounder; + /** + For each clause which is a result of instantiation contains the + original problem clause from which it derives. + */ + root_clause_map m_root_clause_map; + + + /** + For each SMT literal contains a vector of first-order literals + that are represented by this literal. + */ + literal_meaning_map m_literal_meanings; + + /** + fo literals that have been internalized by this object. + Invariant: any app* fol in this set has a literal l such that + m_literal_meanings[l].contains(fol). + Particularly, l==get_context().get_literal(gnd_fol) where gnd_fol + is a grounded version of this literal + */ + obj_hashtable m_internalized_fo_lits; + + + ast_manager& m() const; + smt::context& get_context() const; + + class insert_occurs_trail : public trail { + occurs& m_occ; + quantifier_ref_vector* m_qs; + expr_ref m_lit; + public: + insert_occurs_trail(occurs& o, expr_ref& lit, quantifier* q): m_occ(o), m_qs(0), m_lit(lit) { + occurs::obj_map_entry* e = m_occ.insert_if_not_there2(lit,0); + m_qs = e->get_data().m_value; + if (!m_qs) { + m_qs = alloc(quantifier_ref_vector, lit.get_manager()); + e->get_data().m_value = m_qs; + } + m_qs->push_back(q); + } + + virtual void undo(smt::context& ctx) { + SASSERT(m_qs && !m_qs->empty()); + SASSERT(m_qs == m_occ.find_core(m_lit)->get_data().m_value); + m_qs->pop_back(); + if (m_qs->empty()) { + m_occ.remove(m_lit); + dealloc(m_qs); + } + } + }; + + class lit_meaning_trail : public trail { + literal_meaning_map& m_map; + app_ref_vector* m_apps; + smt::literal m_smtlit; + public: + + lit_meaning_trail(literal_meaning_map& map, ast_manager& m, smt::literal l, app* lit): + m_map(map), m_smtlit(l) { + literal_meaning_map::entry* e = map.insert_if_not_there2(l, 0); + m_apps = e->get_data().m_value; + if (!m_apps) { + m_apps = alloc(app_ref_vector, m); + e->get_data().m_value = m_apps; + } + m_apps->push_back(lit); + } + + virtual void undo(smt::context& ctx) { + SASSERT(m_apps && !m_apps->empty()); + SASSERT(m_apps == m_map.find_core(m_smtlit)->get_data().m_value); + m_apps->pop_back(); + if (m_apps->empty()) { + m_map.remove(m_smtlit); + dealloc(m_apps); + } + } + }; + + quantifier * get_root_clause(expr* clause) const { + quantifier * root; + if(!m_root_clause_map.find(clause, root)) { + SASSERT(is_forall(clause)); + root = to_quantifier(clause); + } + return root; + } + + void replace_by_root_clauses(ptr_vector& vect) const { + unsigned sz = vect.size(); + for(unsigned i=0; i(t)); + get_context().push_trail(insert_obj_trail(m_internalized_fo_lits, lit)); + get_context().push_trail(lit_meaning_trail(m_literal_meanings, m(), smt_lit, lit)); + TRACE("theory_instgen", tout << smt_lit << " "<< PP(grounded_lit, m()) << " |-> " << PP(lit, m()) << "\n";); + } + get_context().mark_as_relevant(smt_lit); + return smt_lit; + } + + void add_clause_to_smt(expr * clause, quantifier* root_clause, ptr_vector const& assumptions=ptr_vector()); + + void get_instance_clause(instantiation_result const& ir, expr_ref& res); + + /** + return false if nothing was done + + assumptions are instantiated clauses (to get a correct assumption for the SMT solver, we need + to convert the vector to root clauses). + */ + bool simplify_clause(quantifier * clause, expr_ref& result, ptr_vector& assumptions); + + public: + + fo_clause_internalizer(theory_instgen_impl& parent): + m_parent(parent), + m_vars(m()), + m_subst(m()), + m_grounder(m()) { + } + + ~fo_clause_internalizer() { + reset_dealloc_values(m_occurs); + } + + void get_literal_meanings(literal l, ptr_vector& fo_lits) { + app_ref_vector* lits = 0; + m_literal_meanings.find(l, lits); + if (lits) { + fo_lits.append(lits->size(), lits->c_ptr()); + } + } + + void add_initial_clause(quantifier* q) { + add_clause_to_smt(q, 0); + } + + quantifier_ref_vector* find_occurs(app * l) { + quantifier_ref_vector* result = 0; + m_occurs.find(l, result); + return result; + } + + void add_new_instance(instantiation_result const& ir) { + quantifier * root_clause = get_root_clause(ir.clause()); + expr_ref inst_clause(m()); + get_instance_clause(ir, inst_clause); + + ptr_vector assumptions; + if(is_quantifier(inst_clause.get())) { + quantifier * q_clause = to_quantifier(inst_clause.get()); + bool simplified = simplify_clause(q_clause, inst_clause, assumptions); + SASSERT(simplified || assumptions.empty()); + } + replace_by_root_clauses(assumptions); + + if(!m_root_clause_map.contains(inst_clause)) { + m_root_clause_map.insert(inst_clause, root_clause); + get_context().push_trail(insert_obj_map(m_root_clause_map, inst_clause)); + } + add_clause_to_smt(inst_clause, root_clause, assumptions); + } + }; + + + ///////////////// + // instantiator + // + + class instantiator { + private: + typedef quantifier clause_type; + typedef ptr_vector clause_vector; + typedef obj_map matching_sets; + + ast_manager& m; + theory_instgen_impl& m_parent; + fo_clause_internalizer& m_internalizer; + inst_gen_unif_index m_unif_index; + matching_sets m_matching; + unifier m_unifier; //used in the unify method, but we don't want to recreate over and over + + class var_rename_rewriter_cfg : public default_rewriter_cfg { + ast_manager& m; + u_map m_index_rename; + public: + var_rename_rewriter_cfg(ast_manager& m) : m(m) {} + + bool get_subst(expr * s, expr * & t, proof * & t_pr) { + if (is_var(s)) { + unsigned idx = to_var(s)->get_idx(); + unsigned new_idx = 0; + if (!m_index_rename.find(idx, new_idx)) { + new_idx = m_index_rename.size(); + m_index_rename.insert(idx, new_idx); + } + t = m.mk_var(new_idx, to_var(s)->get_sort()); + return true; + } + else { + return false; + } + } + }; + + static void extract_substitution(substitution& s, quantifier * q, unsigned subst_var_cnt, bool is_first_bank, expr_ref_vector& tgt) { + // unsigned var_increment = is_first_bank ? 0 : subst_var_cnt; + unsigned var_offset = is_first_bank ? 0 : 1; + + unsigned deltas[2] = {0, subst_var_cnt}; + ast_manager& m = s.get_manager(); + unsigned var_cnt = q->get_num_decls(); + var_rename_rewriter_cfg r_cfg(m); + rewriter_tpl rwr(m, false, r_cfg); + for(unsigned i=0; iget_decl_sort(i); + unsigned var_idx = var_cnt-1-i; + var_ref v(m.mk_var(var_idx, var_sort), m); + expr_ref tmp(m), subst_result(m); + s.apply(2, deltas, expr_offset(v.get(), var_offset), tmp); + rwr(tmp, subst_result); + tgt.push_back(subst_result); + } + } + + + // just to be sure there's not misunderstanding with the caller, we require the res to be empty:) + void get_literal_occurrences(app * lit, clause_vector& res) { + SASSERT(res.empty()); + quantifier_ref_vector * occurrences = m_internalizer.find_occurs(lit); + if(occurrences) { + res.append(occurrences->size(), occurrences->c_ptr()); + } + } + + /** + check substitution wrt dismatching constraints of clause + (variable offset is to deal with how variable banks are shifted on each + other in the substitution) + */ + bool is_allowed_instantiation(clause_type * clause, const inst& subst) { + matching_set * ms; + return !m_matching.find(clause, ms) || !ms->has_instance(subst); + } + + class new_ms : public trail { + matching_sets& m_ms; + matching_set* m_s; + quantifier* m_q; + public: + new_ms(matching_sets& m, matching_set* s, quantifier* q): m_ms(m), m_s(s), m_q(q) {} + virtual void undo(smt::context& ctx) { dealloc(m_s); m_ms.remove(m_q); } + }; + + // add instantiating substitution among the dismatching constraints + void record_instantiation(instantiation_result const& inst) { + quantifier * cl = inst.clause(); + matching_set * ms; + if(!m_matching.find(cl, ms)) { + ms = alloc(matching_set, m); + m_matching.insert(cl, ms); + get_context().push_trail(new_ms(m_matching, ms, cl)); + } + ms->insert(inst.subst()); + get_context().push_trail(matching_set::insert_inst(*ms)); + } + + void get_result_from_subst(clause_type * clause, const inst& subst, instantiation_result& res) { + res.init(clause, subst); + record_instantiation(res); + } + + void display_vector(expr_ref_vector const& v, std::ostream& out) { + for (unsigned i = 0; i < v.size(); ++i) { + out << PP(v[i], m) << " "; + } + out << "\n"; + } + + + void add_lit(literal lit) { + ptr_vector fo_lits; + m_internalizer.get_literal_meanings(lit, fo_lits); + expr_ref e(m); + get_context().literal2expr(lit, e); + if (is_ground(e.get())) { + fo_lits.push_back(to_app(e)); + } + for (unsigned i = 0; i < fo_lits.size(); ++i) { + app * fol = fo_lits[i]; + m_unif_index.insert_literal(fol); + } + } + + void mk_folit_neg(app * lit, app_ref& res) { + expr * arg; + if(m.is_not(lit, arg)) { + SASSERT(is_app(arg)); + res = to_app(arg); + } + else { + res = m.mk_not(lit); + } + } + + ast_manager& get_manager() const; + context& get_context() const; + + public: + instantiator(fo_clause_internalizer& internalizer, theory_instgen_impl& parent, ast_manager& m) : + m(m), + m_parent(parent), + m_internalizer(internalizer), + m_unif_index(m), + m_unifier(m) {} + + ~instantiator() { + reset_dealloc_values(m_matching); + } + + bool unif_is_empty() const { + return m_unif_index.empty(); + } + + void display(std::ostream& out) { + m_unif_index.display(out); + } + + void add_true_lit(literal lit) { + add_lit(lit); + } + + void push() { + m_unif_index.push(); + } + + void pop() { + m_unif_index.pop(); + } + + void reserve_num_vars(unsigned num_vars) { + m_unif_index.reserve_num_vars(num_vars); + } + + bool instantiate_clauses( + app * lit1, clause_type * clause1, + app * lit2, clause_type * clause2, + instantiation_result_vector& result); + + bool instantiate_clause( + app * lit1, clause_type * clause1, app * lit2, + instantiation_result_vector& result); + + void do_instantiating(literal lit, instantiation_result_vector& res) { + ptr_vector folits; + clause_vector folit_clauses; // clauses in which the first-order literal appears + app_ref_vector unifs(m); // unifying complementary literals + clause_vector comp_clauses; // clauses in which the unifying complementary literal appears + + m_internalizer.get_literal_meanings(lit, folits); + + while(!folits.empty()) { + app * folit = folits.back(); + + folits.pop_back(); + folit_clauses.reset(); + get_literal_occurrences(folit, folit_clauses); + SASSERT(!folit_clauses.empty()); //if we have a literal it should be in some clause (or not?) + + SASSERT(folit->get_ref_count() > 0); + app_ref complementary(m); + mk_folit_neg(folit, complementary); + m_unif_index.get_unifications(complementary, unifs); + + while(!unifs.empty()) { + app * comp_lit = unifs.back(); + unifs.pop_back(); + SASSERT(comp_lit->get_ref_count() > 0); + comp_clauses.reset(); + get_literal_occurrences(comp_lit, comp_clauses); + TRACE("theory_instgen", tout << "Literal " << lit << " meaning: " << PP(folit, m) << "\n"; + tout << "Unifies with: " << PP(comp_lit, m) << "\n";); + // + //if a literal is in the unification index (i.e. was assigned true sometime before), + //it should be in some clause or it is a ground literal. + + //iterate through all clauses that contain the query literal + // + clause_vector::const_iterator fc_end = folit_clauses.end(); + for(clause_vector::const_iterator fc_it = folit_clauses.begin(); fc_it!=fc_end; ++fc_it) { + + //iterate through all clauses that contain the complementary unifying literal + clause_vector::const_iterator end = comp_clauses.end(); + for(clause_vector::const_iterator it = comp_clauses.begin(); it!=end; ++it) { + + instantiate_clauses(folit, *fc_it, comp_lit, *it, res); + } + if (comp_clauses.empty()) { + instantiate_clause(folit, *fc_it, comp_lit, res); + } + } + } + complementary.reset(); + } + } + }; + + + /////////////////////////// + // theory_instgen_impl + // + + class theory_instgen_impl : public theory_instgen { + + friend class instantiator; + friend class fo_clause_internalizer; + + struct stats { + unsigned m_num_axioms; + unsigned m_num_subsumptions; + unsigned m_num_pruned; + stats() { memset(this, 0, sizeof(*this)); } + }; + + ast_manager& m_manager; + front_end_params& m_params; + fo_clause_internalizer m_internalizer; + instantiator m_instantiator; + clause_subsumption m_subsumer; + stats m_stats; + + final_check_status instantiate_all_possible() { + // traverse instantiation queue and create initial instances. + + ast_manager& m = get_manager(); + context& ctx = get_context(); + instantiation_result_vector generated_clauses; + unsigned bv_cnt = ctx.get_num_bool_vars(); + + m_instantiator.push(); + + TRACE("theory_instgen", + tout << "Literals:\n"; + for (unsigned v = 0; v < bv_cnt; ++v) { + if (l_undef == ctx.get_assignment(v)) continue; + literal lit(v, ctx.get_assignment(v) == l_false); + expr_ref e(m); + ctx.literal2expr(lit, e); + tout << PP(e.get(),m) << "\n"; + } + ); + + SASSERT(m_instantiator.unif_is_empty()); + + for(unsigned bvi=0; bvi < bv_cnt; ++bvi) { + lbool asgn_val = ctx.get_assignment(bvi); + if(asgn_val==l_undef) { + continue; + } + literal lit(bvi, asgn_val==l_false); + m_instantiator.add_true_lit(lit); + m_instantiator.do_instantiating(lit, generated_clauses); + } + + bool change = !generated_clauses.empty(); + + while(!generated_clauses.empty()) { + m_internalizer.add_new_instance(generated_clauses.back()); + generated_clauses.pop_back(); + } + + m_instantiator.pop(); + + return change?FC_CONTINUE:FC_DONE; + } + + + public: + theory_instgen_impl(ast_manager& m, front_end_params& p): + theory_instgen(m.get_family_id("inst_gen")), + m_manager(m), + m_params(p), + m_internalizer(*this), + m_instantiator(m_internalizer, *this, m), + m_subsumer(m, p) + {} + + ast_manager& m() { return m_manager; } + + virtual void internalize_quantifier(quantifier* q) { + TRACE("theory_instgen", tout << PP(q, m()) << "\n";); + context& ctx = get_context(); + if (!ctx.b_internalized(q)) { + bool_var v = ctx.mk_bool_var(q); + ctx.set_var_theory(v, get_id()); + m_instantiator.reserve_num_vars(q->get_num_decls()); + } + } + + virtual bool internalize_atom(app * atom, bool gate_ctx) { + UNREACHABLE(); + return false; + } + + virtual bool internalize_term(app * term) { + UNREACHABLE(); + return false; + } + + virtual void new_eq_eh(theory_var v1, theory_var v2) {} + + virtual void new_diseq_eh(theory_var v1, theory_var v2) {} + + virtual theory * mk_fresh(context * new_ctx) { + return alloc(theory_instgen_impl, get_manager(), m_params); + } + + virtual void assign_eh(bool_var v, bool is_true) { + context& ctx = get_context(); + expr* e = ctx.bool_var2expr(v); + if (is_quantifier(e)) { + if (is_true) { + m_internalizer.add_initial_clause(to_quantifier(e)); + } + else { + // TBD: handle existential force later. + } + } + } + + virtual final_check_status final_check_eh() { + TRACE("theory_instgen", tout << "Final check\n";); + return instantiate_all_possible(); + } + + virtual void init_model(smt::model_generator & m) { } + + virtual smt::model_value_proc * mk_value(smt::enode * n, smt::model_generator & m) { + UNREACHABLE(); + return 0; + } + + virtual void relevant_eh(app * n) { } + + virtual void push_scope_eh() { + m_subsumer.push(); + } + + virtual void pop_scope_eh(unsigned num_scopes) { + m_subsumer.pop(num_scopes); + } + + virtual void collect_statistics(::statistics & st) const { + st.update("inst axiom", m_stats.m_num_axioms); + st.update("inst subsump", m_stats.m_num_subsumptions); + } + + void inc_subsumptions() { + ++m_stats.m_num_subsumptions; + } + + void inc_axioms() { + ++m_stats.m_num_axioms; + } + + void inc_pruned() { + ++m_stats.m_num_pruned; + } + + }; + + theory_instgen* mk_theory_instgen(ast_manager& m, front_end_params& p) { + return alloc(theory_instgen_impl, m, p); + } + + ast_manager& instantiator::get_manager() const { + return m_parent.m(); + } + + smt::context& instantiator::get_context() const { + return m_parent.get_context(); + } + + bool instantiator::instantiate_clauses( + app * lit1, clause_type * clause1, + app * lit2, clause_type * clause2, + instantiation_result_vector& result) { + TRACE("theory_instgen", tout << PP(lit1, m) << " " << PP(clause1, m) << "\n"; + tout << PP(lit2, m) << " " << PP(clause2, m) << "\n";); + substitution subst(m); + unsigned var_cnt = std::max(clause1->get_num_decls(), clause2->get_num_decls()); + subst.reserve(2, var_cnt); + //don't trust there offset values too much, it's just what i expect the substitution does:) + app_ref complementary(m); + mk_folit_neg(lit1, complementary); + TRUSTME(m_unifier(complementary, lit2, subst)); + + inst subst1(m); + extract_substitution(subst, clause1, var_cnt, true, subst1); + inst subst2(m); + extract_substitution(subst, clause2, var_cnt, false, subst2); + + bool s1_identity = matching_set::is_identity(subst1); + bool s2_identity = matching_set::is_identity(subst2); + + if((!s1_identity && !is_allowed_instantiation(clause1, subst1)) || + (!s2_identity && !is_allowed_instantiation(clause2, subst2))) { + TRACE("theory_instgen", + tout << "Pruned instantiation\n"; + tout << PP(clause1, m) << "\n"; + display_vector(subst1, tout); + tout << PP(clause2, m) << "\n"; + display_vector(subst2, tout); + ); + m_parent.inc_pruned(); + return false; + } + + // + // both substitutions cannot be identity as then the two complementary + // literals would correspond to the same SAT solver variable + // + SASSERT(!s1_identity || !s2_identity); + + if(!s1_identity) { + instantiation_result res1(m); + get_result_from_subst(clause1, subst1, res1); + result.push_back(res1); + } + + if(!s2_identity) { + instantiation_result res2(m); + get_result_from_subst(clause2, subst2, res2); + result.push_back(res2); + } + return true; + } + + // literal lit2 is ground. It is not associated with a clause. + // literal lit1 is associatd with a non-ground clause. + bool instantiator::instantiate_clause( + app * lit1, clause_type * clause1, app * lit2, + instantiation_result_vector& result) { + TRACE("theory_instgen", tout << PP(lit1, m) << " " << PP(clause1, m) << "\n"; + tout << PP(lit2, m) << "\n";); + if (!is_ground(lit2)) { + // TBD: remove. Debug code. + std::cout << PP(lit1, m) << " " << PP(clause1, m) << "\n"; + std::cout << PP(lit2, m) << "\n"; + m_unif_index.display(std::cout); + } + SASSERT(is_ground(lit2)); + substitution subst(m); + unsigned var_cnt = clause1->get_num_decls(); + subst.reserve(2, var_cnt); + app_ref complementary(m); + mk_folit_neg(lit1, complementary); + + TRUSTME(m_unifier(complementary, lit2, subst)); + + inst subst1(m); + extract_substitution(subst, clause1, var_cnt, true, subst1); + + if(matching_set::is_identity(subst1) || + !is_allowed_instantiation(clause1, subst1)) { + TRACE("theory_instgen", + tout << "Pruned instantiation\n"; + tout << PP(clause1, m) << "\n"; + display_vector(subst1, tout); + ); + m_parent.inc_pruned(); + return false; + } + + instantiation_result res1(m); + get_result_from_subst(clause1, subst1, res1); + result.push_back(res1); + return true; + } + + + //-------------------------- + // + // fo_clause_internalizer + // + //-------------------------- + + smt::context& fo_clause_internalizer::get_context() const { + return m_parent.get_context(); + } + + ast_manager& fo_clause_internalizer::m() const { + return m_parent.m(); + } + + bool fo_clause_internalizer::simplify_clause(quantifier * clause, expr_ref& result, ptr_vector& assumptions) { + m_parent.m_subsumer.simplify(clause, result, assumptions); + bool change = clause!=result.get(); + if (change) { + m_parent.inc_subsumptions(); + } + return change; + } + + void fo_clause_internalizer::get_instance_clause(instantiation_result const& ir, expr_ref& res) { + expr * orig_cl = ir.clause()->get_expr(); + SASSERT(is_app(orig_cl)); + + expr_ref res_inner(m()); //the clause after substitution, we might still need to quantify it + m_subst(orig_cl, ir.subst().size(), ir.subst().c_ptr(), res_inner); + SASSERT(is_app(res_inner.get())); + + if(is_ground(res_inner.get())) { + res = res_inner; + return; //we made it ground! + } + + ptr_vector free_var_sorts; + svector quant_names; + get_free_vars(res_inner.get(), free_var_sorts); + unsigned free_var_cnt = free_var_sorts.size(); + for(unsigned i=0; i const& assumptions) { + SASSERT(!root_clause || root_clause->is_forall()); + SASSERT(is_quantifier(clause) || root_clause); + context& ctx = get_context(); + buffer lits; + ptr_buffer todo; + + bool is_q_clause = is_quantifier(clause); + quantifier * q_clause = is_q_clause ? to_quantifier(clause) : 0; + if (!root_clause) root_clause = q_clause; + + lits.push_back(~get_root_clause_control_literal(root_clause)); + + for(unsigned i=0; iget_expr()):clause); + + while (!todo.empty()) { + expr* e = todo.back(); + todo.pop_back(); + if (m().is_or(e)) { + todo.append(to_app(e)->get_num_args(), to_app(e)->get_args()); + } + else if (is_app(e)) { + lits.push_back(ground_fo_literal(to_app(e), q_clause)); + } + else { + SASSERT(is_var(e) || is_quantifier(e)); + UNREACHABLE(); + //by skipping part of the disjunction we may get unsound + } + } + TRACE("theory_instgen", + tout << "Axiom: \n"; + for (unsigned i = 0; i < lits.size(); ++i) { + expr_ref e(m()); + get_context().literal2expr(lits[i], e); + tout << PP(e.get(), m()) << "\n"; + } + ); + m_parent.inc_axioms(); + ctx.mk_th_axiom(m_parent.get_id(), lits.size(), lits.c_ptr()); + } + + +}; diff --git a/lib/theory_instgen.h b/lib/theory_instgen.h new file mode 100644 index 000000000..076dad748 --- /dev/null +++ b/lib/theory_instgen.h @@ -0,0 +1,45 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + theory_instgen.h + +Abstract: + + InstGen (iProver) style theory solver. + It provides an instantiation based engine + based on InstGen methods together with + unit propagation. + + +Author: + + Krystof Hoder (t-khoder) + Nikolaj Bjorner (nbjorner) 2011-10-6 + +Revision History: + +--*/ +#ifndef _THEORY_INST_GEN_H_ +#define _THEORY_INST_GEN_H_ + +#include "smt_theory.h" +#include "front_end_params.h" + +namespace smt { + + class theory_instgen : public theory { + public: + theory_instgen(family_id fid) : theory(fid) {} + virtual ~theory_instgen() {} + virtual void internalize_quantifier(quantifier* q) = 0; + virtual char const * get_name() const { return "instgen"; } + }; + + theory_instgen* mk_theory_instgen(ast_manager& m, front_end_params& p); + +}; + +#endif /* _THEORY_INST_GEN_H_ */ + diff --git a/lib/theory_seq_empty.h b/lib/theory_seq_empty.h new file mode 100644 index 000000000..ef3924603 --- /dev/null +++ b/lib/theory_seq_empty.h @@ -0,0 +1,41 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + theory_seq_empty.h + +Abstract: + + + +Author: + + Nikolaj Bjorner (nbjorner) 2011-14-11 + +Revision History: + +--*/ +#ifndef _THEORY_SEQ_EMPTY_H_ +#define _THEORY_SEQ_EMPTY_H_ + +#include "smt_theory.h" + +namespace smt { + class theory_seq_empty : public theory { + bool m_used; + virtual final_check_status final_check_eh() { return m_used?FC_GIVEUP:FC_DONE; } + virtual bool internalize_atom(app*, bool) { if (!m_used) { get_context().push_trail(value_trail(m_used)); m_used = true; } return false; } + virtual bool internalize_term(app*) { return internalize_atom(0,false); } + virtual void new_eq_eh(theory_var, theory_var) { } + virtual void new_diseq_eh(theory_var, theory_var) {} + virtual theory* mk_fresh(context*) { return alloc(theory_seq_empty, get_manager()); } + virtual char const * get_name() const { return "seq-empty"; } + public: + theory_seq_empty(ast_manager& m):theory(m.get_family_id("seq")), m_used(false) {} + }; + +}; + +#endif /* _THEORY_SEQ_EMPTY_H_ */ + diff --git a/lib/timeit.cpp b/lib/timeit.cpp new file mode 100644 index 000000000..975dd3127 --- /dev/null +++ b/lib/timeit.cpp @@ -0,0 +1,58 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + timeit.h + +Abstract: + + Support for timers. + +Author: + + Nikolaj Bjorner (nbjorner) 2006-09-22 + +Revision History: + +--*/ +#include +#include"timeit.h" +#include"memory_manager.h" +#include"stopwatch.h" +#include + +struct timeit::imp { + stopwatch m_watch; + char const * m_msg; + std::ostream & m_out; + double m_start_memory; + + imp(char const * msg, std::ostream & out): + m_msg(msg), + m_out(out), + m_start_memory(static_cast(memory::get_allocation_size())/static_cast(1024*1024)) { + m_watch.start(); + } + + ~imp() { + m_watch.stop(); + double end_memory = static_cast(memory::get_allocation_size())/static_cast(1024*1024); + m_out << m_msg << ", time: " << std::fixed << std::setprecision(2) << m_watch.get_seconds() + << " secs, memory: (before " << std::fixed << std::setprecision(2) << m_start_memory + << ", after " << std::fixed << std::setprecision(2) << end_memory << ")" + << std::endl; + } +}; + +timeit::timeit(bool enable, char const * msg, std::ostream & out) { + if (enable) + m_imp = alloc(imp, msg, out); + else + m_imp = 0; +} + +timeit::~timeit() { + if (m_imp) + dealloc(m_imp); +} diff --git a/lib/timeit.h b/lib/timeit.h new file mode 100644 index 000000000..9da7e7d84 --- /dev/null +++ b/lib/timeit.h @@ -0,0 +1,33 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + timeit.h + +Abstract: + + Support for timers. + +Author: + + Nikolaj Bjorner (nbjorner) 2006-09-22 + +Revision History: + + Leonardo de Moura (leonardo) 2011-04-27 + Rewrote using stopwatches, added support for tracking memory + +--*/ +#ifndef _TIMEIT_H_ +#define _TIMEIT_H_ + +class timeit { + struct imp; + imp * m_imp; +public: + timeit(bool enable, char const * msg, std::ostream & out = std::cerr); + ~timeit(); +}; + +#endif diff --git a/lib/timeout.cpp b/lib/timeout.cpp new file mode 100644 index 000000000..b0b914b5c --- /dev/null +++ b/lib/timeout.cpp @@ -0,0 +1,57 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + timeout.cpp + +Abstract: + + Timeout support + +Author: + + Leonardo de Moura (leonardo) 2006-10-02. + +Revision History: + + Christoph (cwinter) 2012-02-14: Switch to scoped_timer for timeout support on all platforms. + +--*/ +#include +#include"util.h" +#include"timeout.h" +#include"error_codes.h" + +#include"event_handler.h" +#include"scoped_timer.h" + +scoped_timer * g_timeout = 0; +void (* g_on_timeout)() = 0; + +class g_timeout_eh : public event_handler { +public: + void operator()() { + #pragma omp critical (g_timeout_cs) + { + std::cout << "timeout\n"; + if (g_on_timeout) + g_on_timeout(); + if (g_timeout) + delete g_timeout; + g_timeout = 0; + throw z3_error(ERR_TIMEOUT); + } + } +}; + +void set_timeout(long ms) { + if (g_timeout) + delete g_timeout; + + g_timeout = new scoped_timer(ms, new g_timeout_eh()); +} + +void register_on_timeout_proc(void (*proc)()) { + g_on_timeout = proc; +} diff --git a/lib/timeout.h b/lib/timeout.h new file mode 100644 index 000000000..a50b25918 --- /dev/null +++ b/lib/timeout.h @@ -0,0 +1,26 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + timeout.h + +Abstract: + + Timeout support + +Author: + + Leonardo de Moura (leonardo) 2006-10-02. + +Revision History: + +--*/ +#ifndef _TIMEOUT_H_ +#define _TIMEOUT_H_ + +void register_on_timeout_proc(void (*proc)()); + +void set_timeout(long ms); + +#endif // _TIMEOUT_H_ diff --git a/lib/timer.cpp b/lib/timer.cpp new file mode 100644 index 000000000..ace67ead8 --- /dev/null +++ b/lib/timer.cpp @@ -0,0 +1,40 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + timer.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2009-01-06. + +Revision History: + +--*/ +#include"util.h" +#include"memory_manager.h" +#include"stopwatch.h" +#include"timer.h" + +timer::timer(){ + m_watch = alloc(stopwatch); + start(); +} + +timer::~timer() { + dealloc(m_watch); +} + +void timer::start() { + m_watch->start(); +} + +double timer::get_seconds() { + return m_watch->get_current_seconds(); +} + diff --git a/lib/timer.h b/lib/timer.h new file mode 100644 index 000000000..784712593 --- /dev/null +++ b/lib/timer.h @@ -0,0 +1,39 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + timer.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2009-01-06. + +Revision History: + +--*/ +#ifndef _TIMER_H_ +#define _TIMER_H_ + +class stopwatch; + +/** + \brief Wrapper for the stopwatch class. It hides windows.h dependency. +*/ +class timer { + stopwatch * m_watch; +public: + timer(); + ~timer(); + void start(); + double get_seconds(); + bool timeout(unsigned secs) { return secs > 0 && get_seconds() > secs; } + bool ms_timeout(unsigned ms) { return ms > 0 && get_seconds() * 1000 > ms; } +}; + +#endif /* _TIMER_H_ */ + diff --git a/lib/total_order.h b/lib/total_order.h new file mode 100644 index 000000000..84b04b416 --- /dev/null +++ b/lib/total_order.h @@ -0,0 +1,415 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + total_order.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-07-01. + +Revision History: + +--*/ +#ifndef _TOTAL_ORDER_H_ +#define _TOTAL_ORDER_H_ + +#include"util.h" +#include"small_object_allocator.h" +#include"map.h" +#include"uint_map.h" +#include"trace.h" + +/** + \brief An object for maintaining a total-order on sets of T values. + \c Map should be a class implementing the map API for T to void *. + +*/ +template +class total_order { + struct cell { + cell * m_next; + cell * m_prev; + uint64 m_val; + T m_data; + }; + + small_object_allocator * m_allocator; + bool m_own_allocator; + Map m_map; + cell m_base; + unsigned m_size; + + cell * base() const { return const_cast(&m_base); } + + void init_base() { + m_base.m_next = &m_base; + m_base.m_prev = &m_base; + m_base.m_val = 0; + } + + uint64 v(cell * a) const { return a->m_val; } + + uint64 vb(cell * a) const { return v(a) - v(base()); } + + uint64 vbn(cell * a) const { return a->m_next == base() ? UINT64_MAX : vb(a->m_next); } + + cell * mk_cell(T const & a) { + SASSERT(!m_map.contains(a)); + cell * c = reinterpret_cast(m_allocator->allocate(sizeof(cell))); + m_map.insert(a, c); + c->m_data = a; + m_size++; + return c; + } + + void del_cell(cell * c) { +#ifdef Z3DEBUG + T d = c->m_data; +#endif + m_map.erase(c->m_data); + m_allocator->deallocate(sizeof(cell), c); + m_size--; + SASSERT(!m_map.contains(d)); + } + + cell * to_cell(T const & a) const { + void * r; +#ifdef Z3DEBUG + bool ok = +#endif + m_map.find(a, r); + SASSERT(ok); + return reinterpret_cast(r); + } + + void _insert_after(cell * a, cell * b) { + uint64 vb_a = vb(a); + uint64 vbn_a = vbn(a); + SASSERT(vb_a < vbn_a); + if (vbn_a < 2 || (vb_a > vbn_a - 2)) { + TRACE("total_order", tout << "relabeling...\n"; tout << "\n";); + uint64 v0 = v(a); + unsigned sz = size(); + uint64 ideal_gap = UINT64_MAX / sz; + uint64 goal_gap = ideal_gap / 32; + cell * c = a->m_next->m_next; + unsigned j = 2; + uint64 curr_gap = (v(c) - v0) / j; + while (j < sz && curr_gap < goal_gap) { + j++; + c = c->m_next; + curr_gap = (v(c) - v0) / j; + } + TRACE("total_order", tout << "j: " << j << " curr_gap: " << curr_gap << " sz: " << sz << "\n";); + if (j == sz) + curr_gap = ideal_gap; + c = a->m_next; + uint64 inc = curr_gap; + for (unsigned i = 0; i < j; i++) { + c->m_val = v0 + inc; + c = c->m_next; + inc += curr_gap; + } + CASSERT("total_order", check_invariant()); + vb_a = vb(a); + vbn_a = vbn(a); + } + SASSERT(vb_a <= vbn_a - 2); + uint64 vb_b = vb_a + ((vbn_a - vb_a)/2); + SASSERT(vb_b > vb_a); + SASSERT(vb_b < vbn_a); + b->m_val = vb_b + v(base()); + b->m_next = a->m_next; + a->m_next->m_prev = b; + b->m_prev = a; + a->m_next = b; + SASSERT(vb(a) < vb(b)); + CASSERT("total_order", check_invariant()); + } + + void insert_core(cell * a) { + _insert_after(base(), a); + } + + void remove_cell(cell * a) { + SASSERT(a != base()); + cell * p = a->m_prev; + cell * n = a->m_next; + p->m_next = n; + n->m_prev = p; + } + + void move_after(cell * a, cell * b) { + if (a->m_next == b) + return; + remove_cell(b); + _insert_after(a, b); + } + + void move_beginning(cell * b) { + if (base()->m_next == b) + return; // already in the beginning + remove_cell(b); + insert_core(b); + } + + void erase(cell * a) { + remove_cell(a); + del_cell(a); + } + +public: + total_order(): + m_allocator(alloc(small_object_allocator)), + m_own_allocator(true), + m_size(0) { + init_base(); + } + + total_order(Map const & m): + m_allocator(alloc(small_object_allocator)), + m_own_allocator(true), + m_map(m), + m_size(0) { + init_base(); + } + + total_order(small_object_allocator * allocator): + m_allocator(allocator), + m_own_allocator(false), + m_size(0) { + init_base(); + } + + total_order(small_object_allocator * allocator, Map const & m): + m_allocator(allocator), + m_own_allocator(false), + m_map(m), + m_size(0) { + init_base(); + } + + ~total_order() { + cell * curr = base()->m_next; + while (curr != base()) { + cell * c = curr; + curr = curr->m_next; + del_cell(c); + } + if (m_own_allocator) + dealloc(m_allocator); + } + + /** + \brief Return true if \c a is in the total order. + */ + bool contains(T const & a) const { + return m_map.contains(a); + } + + /** + \brief Insert \c a in the beginning of the total order. + + \pre \c a must not be an element of the total order. + */ + void insert(T const & a) { + SASSERT(!contains(a)); + insert_core(mk_cell(a)); + } + + /** + \brief Insert \c b after \c a in the total order. + + \pre \c a is an element of the total order. + \pre \c b must not be an element of the total order. + */ + void insert_after(T const & a, T const & b) { + SASSERT(contains(a)); + SASSERT(!contains(b)); + _insert_after(to_cell(a), mk_cell(b)); + SASSERT(lt(a, b)); + } + + /** + \brief Move \c a to the beginning of the total order. + + \pre \c a is an element of the total order. + */ + void move_beginning(T const & a) { + SASSERT(contains(a)); + move_beginning(to_cell(a)); + } + + /** + \brief Move \b after \c a in the total order. + + \pre \c a is an element of the total order. + \pre \c b is an element of the total order. + \pre \c a must be different from \c b. + */ + void move_after(T const & a, T const & b) { + SASSERT(contains(a)); + SASSERT(contains(b)); + move_after(to_cell(a), to_cell(b)); + SASSERT(lt(a, b)); + } + + /** + \brief Remove \c a from the total order. + + \pre \c a is an element of the total order. + */ + void erase(T const & a) { + SASSERT(contains(a)); + erase(to_cell(a)); + } + + /** + \brief Return true if \c a is less than \c b in the total order. + */ + bool lt(T const & a, T const & b) const { + SASSERT(contains(a)); + SASSERT(contains(b)); + return vb(to_cell(a)) < vb(to_cell(b)); + } + + /** + \brief Return true if \c a is greater than \c b in the total order. + */ + bool gt(T const & a, T const & b) const { + SASSERT(contains(a)); + SASSERT(contains(b)); + return vb(to_cell(a)) > vb(to_cell(b)); + } + + /** + \brief Return true if the total order is empty. + */ + bool empty() const { + return base()->m_next == base(); + } + + /** + \brief Return the number of elements in the total order. + */ + unsigned size() const { + return m_size; + } + + /** + \brief Return the first element of the total order. + */ + T const & first() const { + SASSERT(!empty()); + return base()->m_next->m_data; + } + + /** + \brief Return true if \c a has a successor in the total order. + + \pre \c a is an element of the total order. + */ + bool has_next(T const & a) const { + SASSERT(contains(a)); + return to_cell(a)->m_next != base(); + } + + /** + \brief Return the successor of \c a in the total order. + + \pre \c a is an element of the total order. + \pre has_next(a) + */ + T const & next(T const & a) const { + SASSERT(contains(a)); + SASSERT(has_next(a)); + return to_cell(a)->m_next->m_data; + } + + /** + \brief Return true if \c a has a predecessor in the total order. + + \pre \c a is an element of the total order. + */ + bool has_pred(T const & a) const { + SASSERT(contains(a)); + return to_cell(a)->m_prev != base(); + } + + /** + \brief Return the predecessor of \c a in the total order. + + \pre \c a is an element of the total order. + \pre has_pred(a) + */ + T const & pred(T const & a) const { + SASSERT(has_pred(a)); + return to_cell(a)->m_prev->m_data; + } + + /** + \brief Display the elements of the total order in increasing order. + + \remark For debugging purposes. + */ + void display(std::ostream & out) const { + cell * curr = base()->m_next; + bool first = true; + while (curr != base()) { + if (first) + first = false; + else + out << " "; + out << curr->m_data; + curr = curr->m_next; + } + } + + void display_detail(std::ostream & out) const { + cell * curr = base()->m_next; + bool first = true; + while (curr != base()) { + if (first) + first = false; + else + out << " "; + out << curr->m_data << ":" << curr->m_val; + curr = curr->m_next; + } + } + +#ifdef Z3DEBUG + bool check_invariant() const { + cell * curr = base()->m_next; + while (curr != base()) { + SASSERT(curr->m_next == base() || vb(curr) < vb(curr->m_next)); + curr = curr->m_next; + } + return true; + } +#endif + +}; + +typedef total_order > int_total_order; + +/** + \brief A total order that uses vectors to implement a mapping from unsigned to void *. +*/ +typedef total_order > uint_total_order; + +template +std::ostream & operator<<(std::ostream & out, total_order const & to) { + to.display(out); + return out; +} + + +#endif /* _TOTAL_ORDER_H_ */ + diff --git a/lib/tptr.h b/lib/tptr.h new file mode 100644 index 000000000..887a7d3c3 --- /dev/null +++ b/lib/tptr.h @@ -0,0 +1,46 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + tptr.h + +Abstract: + + Support for tagged pointers and other low level pointer manipulation macros. + +Author: + + Leonardo de Moura (leonardo) 2006-09-13. + +Revision History: + +--*/ + +#ifndef _TPTR_H_ +#define _TPTR_H_ + +#include"machine.h" + +#define TAG_SHIFT PTR_ALIGNMENT +#define ALIGNMENT_VALUE (1 << PTR_ALIGNMENT) +#define TAG_MASK (ALIGNMENT_VALUE - 1) +#define PTR_MASK (~TAG_MASK) + +#define ALIGN(T, PTR) reinterpret_cast(((reinterpret_cast(PTR) >> PTR_ALIGNMENT) + \ + static_cast((reinterpret_cast(PTR) & TAG_MASK) != 0)) << PTR_ALIGNMENT) + +#define UNTAG(T, PTR) reinterpret_cast(reinterpret_cast(PTR) & PTR_MASK) + +#define TAG(T, PTR, TAG_VAL) reinterpret_cast(reinterpret_cast(PTR) | static_cast(TAG_VAL)) + +#define GET_TAG(PTR) (reinterpret_cast(PTR) & TAG_MASK) + +#define BOXINT(T, VAL) reinterpret_cast(static_cast(VAL) << PTR_ALIGNMENT) + +#define BOXTAGINT(T, VAL, TAG_VAL) reinterpret_cast((static_cast(VAL) << PTR_ALIGNMENT) | static_cast(TAG_VAL)) + +#define UNBOXINT(PTR) static_cast(reinterpret_cast(PTR) >> PTR_ALIGNMENT) + +#endif /* _TPTR_H_ */ + diff --git a/lib/trace.cpp b/lib/trace.cpp new file mode 100644 index 000000000..3410febd5 --- /dev/null +++ b/lib/trace.cpp @@ -0,0 +1,68 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + trace.cpp + +Abstract: + + Trace message support. + +Author: + + Leonardo de Moura (leonardo) 2006-09-13. + +Revision History: + +--*/ +#include"trace.h" +#include"str_hashtable.h" + +#ifdef _TRACE +std::ofstream tout(".z3-trace"); +#endif + +bool g_enable_all_trace_tags = false; +str_hashtable* g_enabled_trace_tags = 0; + +static str_hashtable& get_enabled_trace_tags() { + if (!g_enabled_trace_tags) { + g_enabled_trace_tags = alloc(str_hashtable); + } + return *g_enabled_trace_tags; +} + +void finalize_trace() { + dealloc(g_enabled_trace_tags); + g_enabled_trace_tags = 0; +} + +void enable_trace(const char * tag) { + get_enabled_trace_tags().insert(const_cast(tag)); +} + +void enable_all_trace(bool flag) { + g_enable_all_trace_tags = flag; +} + +void disable_trace(const char * tag) { + get_enabled_trace_tags().erase(const_cast(tag)); +} + +bool is_trace_enabled(const char * tag) { + return g_enable_all_trace_tags || + (g_enabled_trace_tags && get_enabled_trace_tags().contains(const_cast(tag))); +} + +void close_trace() { +#ifdef _TRACE + tout.close(); +#endif +} + +void open_trace() { +#ifdef _TRACE + tout.open(".z3-trace"); +#endif +} diff --git a/lib/trace.h b/lib/trace.h new file mode 100644 index 000000000..ac87dbb7a --- /dev/null +++ b/lib/trace.h @@ -0,0 +1,46 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + trace.h + +Abstract: + + Trace message support. + +Author: + + Leonardo de Moura (leonardo) 2006-09-13. + +Revision History: + +--*/ + +#ifndef _TRACE_H_ +#define _TRACE_H_ +#include + +#ifdef _TRACE +extern std::ofstream tout; +#define TRACE_CODE(CODE) { CODE } ((void) 0 ) +#else +#define TRACE_CODE(CODE) ((void) 0) +#endif + +void enable_trace(const char * tag); +void enable_all_trace(bool flag); +void disable_trace(const char * tag); +bool is_trace_enabled(const char * tag); +void close_trace(); +void open_trace(); +void finalize_trace(); + +#define TRACE(TAG, CODE) TRACE_CODE(if (is_trace_enabled(TAG)) { tout << "-------- [" << TAG << "] " << __FUNCTION__ << " " << __FILE__ << ":" << __LINE__ << " ---------\n"; CODE tout << "------------------------------------------------\n"; tout.flush(); }) + +#define STRACE(TAG, CODE) TRACE_CODE(if (is_trace_enabled(TAG)) { CODE tout.flush(); }) + +#define CTRACE(TAG, COND, CODE) TRACE_CODE(if (is_trace_enabled(TAG) && (COND)) { tout << "-------- [" << TAG << "] " << __FUNCTION__ << " " << __FILE__ << ":" << __LINE__ << " ---------\n"; CODE tout << "------------------------------------------------\n"; tout.flush(); }) + +#endif /* _TRACE_H_ */ + diff --git a/lib/trail.h b/lib/trail.h new file mode 100644 index 000000000..ac03f9fce --- /dev/null +++ b/lib/trail.h @@ -0,0 +1,404 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + trail.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-06-02. + +Revision History: + +--*/ +#ifndef _TRAIL_H_ +#define _TRAIL_H_ + +#include"ast.h" +#include"obj_hashtable.h" +#include"region.h" + +template +class trail { +public: + virtual ~trail() { + } + virtual void undo(Ctx & ctx) = 0; +}; + +template +class value_trail : public trail { + T & m_value; + T m_old_value; + +public: + value_trail(T & value): + m_value(value), + m_old_value(value) { + } + + virtual ~value_trail() { + } + + virtual void undo(Ctx & ctx) { + m_value = m_old_value; + } +}; + +template +class reset_flag_trail : public trail { + bool & m_value; +public: + reset_flag_trail(bool & value): + m_value(value) { + } + + virtual ~reset_flag_trail() { + } + + virtual void undo(Ctx & ctx) { + m_value = false; + } +}; + +template +class set_ptr_trail : public trail { + T * & m_ptr; +public: + set_ptr_trail(T * & ptr): + m_ptr(ptr) { + SASSERT(m_ptr == 0); + } + + virtual void undo(Ctx & ctx) { + m_ptr = 0; + } +}; + +template +class restore_size_trail : public trail { + vector & m_vector; + unsigned m_old_size; +public: + restore_size_trail(vector & v, unsigned sz): + m_vector(v), + m_old_size(sz) { + } + restore_size_trail(vector & v): + m_vector(v), + m_old_size(v.size()) { + } + virtual ~restore_size_trail() { + } + virtual void undo(Ctx & ctx) { + m_vector.shrink(m_old_size); + } +}; + +template +class vector_value_trail : public trail { + vector & m_vector; + unsigned m_idx; + T m_old_value; +public: + vector_value_trail(vector & v, unsigned idx): + m_vector(v), + m_idx(idx), + m_old_value(v[idx]) { + } + + virtual ~vector_value_trail() { + } + + virtual void undo(Ctx & ctx) { + m_vector[m_idx] = m_old_value; + } +}; + + +template +class insert_obj_map : public trail { + obj_map& m_map; + D* m_obj; +public: + insert_obj_map(obj_map& t, D* o) : m_map(t), m_obj(o) {} + virtual ~insert_obj_map() {} + virtual void undo(Ctx & ctx) { m_map.remove(m_obj); } +}; + +template +class insert_map : public trail { + M& m_map; + D m_obj; +public: + insert_map(M& t, D o) : m_map(t), m_obj(o) {} + virtual ~insert_map() {} + virtual void undo(Ctx & ctx) { m_map.remove(m_obj); } +}; + + + +template +class push_back_vector : public trail { + V & m_vector; +public: + push_back_vector(V & v): + m_vector(v) { + } + + virtual void undo(Ctx & ctx) { + m_vector.pop_back(); + } +}; + +template +class set_vector_idx_trail : public trail { + ptr_vector & m_vector; + unsigned m_idx; +public: + set_vector_idx_trail(ptr_vector & v, unsigned idx): + m_vector(v), + m_idx(idx) { + } + + virtual ~set_vector_idx_trail() { + } + + virtual void undo(Ctx & ctx) { + m_vector[m_idx] = 0; + } +}; + +template +class pop_back_trail : public trail { + vector & m_vector; + T m_value; +public: + pop_back_trail(vector & v): + m_vector(v), + m_value(m_vector.back()) { + } + + virtual void undo(Ctx & ctx) { + m_vector.push_back(m_value); + } +}; + +template +class pop_back2_trail : public trail { + vector & m_vector; + typedef vector, true> vector_t; + unsigned m_index; + T m_value; +public: + pop_back2_trail(vector & v, unsigned index): + m_vector(v), + m_index(index), + m_value(m_vector[index].back()) { + } + + virtual void undo(Ctx & ctx) { + m_vector[m_index].push_back(m_value); + } +}; + + +template +class ast2ast_trailmap { + ref_vector m_domain; + ref_vector m_range; + obj_map m_map; +public: + ast2ast_trailmap(ast_manager& m): + m_domain(m), + m_range(m), + m_map() + {} + + bool find(S* s, T*& t) { + return m_map.find(s,t); + } + + void insert(S* s, T* t) { + SASSERT(!m_map.contains(s)); + m_domain.push_back(s); + m_range.push_back(t); + m_map.insert(s,t); + } + + void pop() { + SASSERT(!m_domain.empty()); + m_map.remove(m_domain.back()); + m_domain.pop_back(); + m_range.pop_back(); + } +}; + +template +class ast2ast_trail : public trail { + ast2ast_trailmap& m_map; +public: + ast2ast_trail(ast2ast_trailmap& m, S* s, T* t) : + m_map(m) { + m.insert(s,t); + } + + virtual void undo(Ctx& ctx) { + m_map.pop(); + } +}; + +template +class push_back_trail : public trail { + vector & m_vector; +public: + push_back_trail(vector & v): + m_vector(v) { + } + + virtual void undo(Ctx & ctx) { + m_vector.pop_back(); + } +}; + +template +class push_back2_trail : public trail { + typedef vector, true> vector_t; + vector_t & m_vector; + unsigned m_index; +public: + push_back2_trail(vector_t & v, unsigned index) : + m_vector(v), + m_index(index) { + } + + virtual void undo(Ctx & ctx) { + m_vector[m_index].pop_back(); + } +}; + +template +class set_bitvector_trail : public trail { + svector & m_vector; + unsigned m_idx; +public: + set_bitvector_trail(svector & v, unsigned idx): + m_vector(v), + m_idx(idx) { + SASSERT(m_vector[m_idx] == false); + m_vector[m_idx] = true; + } + + virtual void undo(Ctx & ctx) { + m_vector[m_idx] = false; + } +}; + +template +class new_obj_trail : public trail { + T * m_obj; +public: + new_obj_trail(T * obj): + m_obj(obj) { + } + + virtual void undo(Ctx & ctx) { + dealloc(m_obj); + } +}; + +template +class obj_ref_trail : public trail { + obj_ref m_obj; +public: + obj_ref_trail(obj_ref& obj): + m_obj(obj) { + } + + virtual void undo(Ctx & ctx) { + m_obj.reset(); + } +}; + +template +class insert_obj_trail : public trail { + obj_hashtable& m_table; + T* m_obj; +public: + insert_obj_trail(obj_hashtable& t, T* o) : m_table(t), m_obj(o) {} + virtual ~insert_obj_trail() {} + virtual void undo(Ctx & ctx) { m_table.remove(m_obj); } +}; + + + +template +class remove_obj_trail : public trail { + obj_hashtable& m_table; + T* m_obj; +public: + remove_obj_trail(obj_hashtable& t, T* o) : m_table(t), m_obj(o) {} + virtual ~remove_obj_trail() {} + virtual void undo(Ctx & ctx) { m_table.insert(m_obj); } +}; + + +template +void undo_trail_stack(Ctx & ctx, ptr_vector > & s, unsigned old_size) { + SASSERT(old_size <= s.size()); + typename ptr_vector >::iterator begin = s.begin() + old_size; + typename ptr_vector >::iterator it = s.end(); + while (it != begin) { + --it; + (*it)->undo(ctx); + } + s.shrink(old_size); +} + +template +class trail_stack { + Ctx & m_ctx; + ptr_vector > m_trail_stack; + unsigned_vector m_scopes; + region m_region; +public: + trail_stack(Ctx & c):m_ctx(c) {} + + ~trail_stack() {} + + region & get_region() { return m_region; } + + void reset() { + pop_scope(m_scopes.size()); + // Undo trail objects stored at lvl 0 (avoid memory leaks if lvl 0 contains new_obj_trail objects). + undo_trail_stack(m_ctx, m_trail_stack, 0); + } + + void push_ptr(trail * t) { m_trail_stack.push_back(t); } + + template + void push(TrailObject const & obj) { m_trail_stack.push_back(new (m_region) TrailObject(obj)); } + + unsigned get_num_scopes() const { return m_scopes.size(); } + + void push_scope() { m_region.push_scope(); m_scopes.push_back(m_trail_stack.size()); } + + void pop_scope(unsigned num_scopes) { + if (num_scopes == 0) return; + unsigned lvl = m_scopes.size(); + SASSERT(num_scopes <= lvl); + unsigned new_lvl = lvl - num_scopes; + unsigned old_size = m_scopes[new_lvl]; + undo_trail_stack(m_ctx, m_trail_stack, old_size); + m_scopes.shrink(new_lvl); + m_region.pop_scope(num_scopes); + } +}; + +#endif /* _TRAIL_H_ */ + diff --git a/lib/tseitin_cnf_tactic.cpp b/lib/tseitin_cnf_tactic.cpp new file mode 100644 index 000000000..2a4f3df2e --- /dev/null +++ b/lib/tseitin_cnf_tactic.cpp @@ -0,0 +1,942 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + tseitin_cnf_tactic.cpp + +Abstract: + + Puts an assertion set in CNF. + Auxiliary variables are used to avoid blowup. + + Features: + + - Efficient encoding is used for commonly used patterns such as: + (iff a (iff b c)) + (or (not (or a b)) (not (or a c)) (not (or b c))) + + - Efficient encoding is used for chains of if-then-elses + + - Distributivity is applied to non-shared nodes if the blowup is acceptable. + + - The features above can be disabled/enabled using parameters. + + - The assertion-set is only modified if the resultant set of clauses + is "acceptable". + + Notes: + + - Term-if-then-else expressions are not handled by this strategy. + This kind of expression should be processed by other strategies. + + - Quantifiers are treated as "theory" atoms. They are viewed + as propositional variables by this strategy. + + - The assertion set may contain free variables. + + - This strategy assumes the assertion_set_rewriter was + used before invoking it. + In particular, it is more effective when "and" operators + were eliminated. + + TODO: add proof production + +Author: + + Leonardo (leonardo) 2011-12-29 + +Notes: + +--*/ +#include"tactical.h" +#include"goal_shared_occs.h" +#include"filter_model_converter.h" +#include"bool_rewriter.h" +#include"simplify_tactic.h" +#include"cooperate.h" + +static void swap_if_gt(expr * & n1, expr * & n2) { + if (n1->get_id() > n2->get_id()) + std::swap(n1, n2); +} + +/** + \brief Return true if n is of the form (= a b) +*/ +static bool is_iff(ast_manager & m, expr * n, expr * & a, expr * & b) { + if (m.is_iff(n, a, b)) + return true; + if (m.is_eq(n, a, b) && m.is_bool(a)) + return true; + return false; +} + +class tseitin_cnf_tactic : public tactic { + struct imp { + struct frame { + app * m_t; + bool m_first; + frame(app * n):m_t(n), m_first(true) {} + }; + + typedef filter_model_converter mc; + + ast_manager & m; + svector m_frame_stack; + obj_map m_cache; + expr_ref_vector m_cache_domain; + goal_shared_occs m_occs; + expr_ref_vector m_fresh_vars; + ref m_mc; + expr_ref_vector m_clauses; + expr_dependency_ref_vector m_deps; + bool_rewriter m_rw; + expr_dependency * m_curr_dep; + bool m_produce_models; + bool m_produce_unsat_cores; + + // parameters + bool m_common_patterns; + bool m_distributivity; + unsigned m_distributivity_blowup; + bool m_ite_chains; + bool m_ite_extra; + unsigned long long m_max_memory; + + unsigned m_num_aux_vars; + volatile bool m_cancel; + + imp(ast_manager & _m, params_ref const & p): + m(_m), + m_cache_domain(_m), + m_occs(m, false /* don't track atoms */, false /* do not visit quantifiers */), + m_fresh_vars(_m), + m_clauses(_m), + m_deps(_m), + m_rw(_m), + m_num_aux_vars(0), + m_cancel(false) { + updt_params(p); + m_rw.set_flat(false); + } + + void updt_params(params_ref const & p) { + m_common_patterns = p.get_bool(":common-patterns", true); + m_distributivity = p.get_bool(":distributivity", true); + m_distributivity_blowup = p.get_uint(":distributivity-blowup", 32); + m_ite_chains = p.get_bool(":ite-chains", true); + m_ite_extra = p.get_bool(":ite-extra", true); + m_max_memory = megabytes_to_bytes(p.get_uint(":max-memory", UINT_MAX)); + } + + void push_frame(app * n) { m_frame_stack.push_back(frame(n)); } + + void throw_op_not_handled() { + throw tactic_exception("operator not supported, apply simplifier before invoking this strategy"); + } + + void inv(expr * n, expr_ref & r) { + if (m.is_true(n)) { + r = m.mk_false(); + return; + } + if (m.is_false(n)) { + r = m.mk_true(); + return; + } + if (m.is_not(n)) { + r = to_app(n)->get_arg(0); + return; + } + r = m.mk_not(n); + } + + void mk_lit(expr * n, bool sign, expr_ref & r) { + if (sign) + r = m.mk_not(n); + else + r = n; + } + + void get_lit(expr * n, bool sign, expr_ref & r) { + start: + if (!is_app(n) || + to_app(n)->get_num_args() == 0) { + mk_lit(n, sign, r); + return; + } + func_decl * f = to_app(n)->get_decl(); + if (f->get_family_id() != m.get_basic_family_id()) { + mk_lit(n, sign, r); + return; + } + app * l; + switch (f->get_decl_kind()) { + case OP_NOT: + n = to_app(n)->get_arg(0); + sign = !sign; + goto start; + case OP_OR: + case OP_IFF: + l = 0; + m_cache.find(to_app(n), l); + SASSERT(l != 0); + mk_lit(l, sign, r); + return; + case OP_ITE: + case OP_EQ: + if (m.is_bool(to_app(n)->get_arg(1))) { + l = 0; + m_cache.find(to_app(n), l); + SASSERT(l != 0); + mk_lit(l, sign, r); + return; + } + mk_lit(n, sign, r); + return; + default: + TRACE("tseitin_cnf_bug", tout << f->get_name() << "\n";); + UNREACHABLE(); + return; + } + } + + void visit(expr * n, bool & visited, bool root = false) { + start: + if (!is_app(n)) + return; + if (m_cache.contains(to_app(n))) + return; + if (to_app(n)->get_num_args() == 0) + return; + func_decl * f = to_app(n)->get_decl(); + if (f->get_family_id() != m.get_basic_family_id()) + return; + switch (f->get_decl_kind()) { + case OP_NOT: + if (root) { + visited = false; + push_frame(to_app(n)); + return; + } + else { + n = to_app(n)->get_arg(0); + goto start; + } + case OP_OR: + case OP_IFF: + visited = false; + push_frame(to_app(n)); + return; + case OP_ITE: + case OP_EQ: + if (m.is_bool(to_app(n)->get_arg(1))) { + visited = false; + push_frame(to_app(n)); + } + return; + case OP_AND: + case OP_XOR: + case OP_IMPLIES: + case OP_DISTINCT: + throw_op_not_handled(); + default: + return; + } + } + + bool is_shared(expr * t) { + return m_occs.is_shared(t); + } + + /** + \brief Return true if n is of the form + + (or (not (or a b)) (not (or a c)) (not (or b c))) + + \remark This pattern is found in the following "circuits": + - carry + - less-than (signed and unsigned) + */ + bool is_or_3and(expr * n, expr * & a, expr * & b, expr * & c) { + expr * a1, * a2, * b1, * b2, * c1, * c2; + if (!m.is_or(n, a1, b1, c1) || + !m.is_not(a1, a1) || + is_shared(a1) || + !m.is_not(b1, b1) || + is_shared(b1) || + !m.is_not(c1, c1) || + is_shared(c1) || + !m.is_or(a1, a1, a2) || + !m.is_or(b1, b1, b2) || + !m.is_or(c1, c1, c2)) + return false; + + swap_if_gt(a1, a2); + swap_if_gt(b1, b2); + swap_if_gt(c1, c2); + + if ((a1 == b1 && a2 == c1 && b2 == c2) || + (a1 == b1 && a2 == c2 && b2 == c1) || + (a1 == c1 && a2 == b1 && b2 == c2)) { + a = a1; b = a2; c = b2; + return true; + } + + if ((a1 == b2 && a2 == c2 && b1 == c1) || + (a1 == c1 && a2 == b2 && b1 == c2) || + (a1 == c2 && a2 == b2 && b1 == c1)) { + a = a1; b = a2; c = b1; + return true; + } + + return false; + } + + + /** + \brief Return true if n is of the form + (iff a (iff b c)) + */ + bool is_iff3(expr * n, expr * & a, expr * & b, expr * & c) { + expr * l1, * l2; + if (!is_iff(m, n, l1, l2)) + return false; + if (!is_shared(l1) && is_iff(m, l1, a, b)) { + c = l2; + return true; + } + if (!is_shared(l2) && is_iff(m, l2, b, c)) { + a = l1; + return true; + } + return false; + } + + void mk_clause(unsigned num, expr * const * ls) { + expr_ref cls(m); + m_rw.mk_or(num, ls, cls); + m_clauses.push_back(cls); + if (m_produce_unsat_cores) + m_deps.push_back(m_curr_dep); + } + + void mk_clause(expr * l1) { + return mk_clause(1, &l1); + } + + void mk_clause(expr * l1, expr * l2) { + expr * ls[2] = { l1, l2 }; + mk_clause(2, ls); + } + + void mk_clause(expr * l1, expr * l2, expr * l3) { + expr * ls[3] = { l1, l2, l3 }; + mk_clause(3, ls); + } + + void mk_clause(expr * l1, expr * l2, expr * l3, expr * l4) { + expr * ls[4] = { l1, l2, l3, l4 }; + mk_clause(4, ls); + } + + app * mk_fresh() { + m_num_aux_vars++; + app * v = m.mk_fresh_const(0, m.mk_bool_sort()); + m_fresh_vars.push_back(v); + if (m_mc) + m_mc->insert(v->get_decl()); + return v; + } + + void cache_result(app * t, app * r) { + m_cache.insert(t, r); + m_cache_domain.push_back(t); + } + + enum mres { + NO, // did not match + CONT, // matched but the children need to be processed + DONE // matched + }; + + mres match_not(app * t, bool first, bool root) { + expr * a; + if (m.is_not(t, a)) { + if (first) { + bool visited = true; + visit(a, visited); + if (!visited) + return CONT; + } + expr_ref nla(m); + get_lit(a, true, nla); + if (root) { + mk_clause(nla); + } + // Remark: don't need to do anything if it is not a root. + return DONE; + } + return NO; + } + + mres match_or_3and(app * t, bool first, bool root) { + if (!m_common_patterns) + return NO; + expr * a, * b, * c; + if (is_or_3and(t, a, b, c)) { + if (first) { + bool visited = true; + visit(a, visited); + visit(b, visited); + visit(c, visited); + if (!visited) + return CONT; + } + expr_ref nla(m), nlb(m), nlc(m); + get_lit(a, true, nla); + get_lit(b, true, nlb); + get_lit(c, true, nlc); + if (root) { + mk_clause(nla, nlb); + mk_clause(nla, nlc); + mk_clause(nlb, nlc); + } + else { + app_ref k(m), nk(m); + k = mk_fresh(); + nk = m.mk_not(k); + mk_clause(nk, nla, nlb); + mk_clause(nk, nla, nlc); + mk_clause(nk, nlb, nlc); + expr_ref la(m), lb(m), lc(m); + inv(nla, la); + inv(nlb, lb); + inv(nlc, lc); + mk_clause(k, la, lb); + mk_clause(k, la, lc); + mk_clause(k, lb, lc); + cache_result(t, k); + } + return DONE; + } + return NO; + } + + mres match_iff3(app * t, bool first, bool root) { + if (!m_common_patterns) + return NO; + expr * a, * b, * c; + if (is_iff3(t, a, b, c)) { + if (first) { + bool visited = true; + visit(a, visited); + visit(b, visited); + visit(c, visited); + if (!visited) + return CONT; + } + expr_ref la(m), lb(m), lc(m); + expr_ref nla(m), nlb(m), nlc(m); + get_lit(a, false, la); + get_lit(b, false, lb); + get_lit(c, false, lc); + inv(la, nla); + inv(lb, nlb); + inv(lc, nlc); + if (root) { + mk_clause(la, lb, lc); + mk_clause(la, nlb, nlc); + mk_clause(nla, lb, nlc); + mk_clause(nla, nlb, lc); + } + else { + app_ref k(m), nk(m); + k = mk_fresh(); + nk = m.mk_not(k); + mk_clause(nk, la, lb, lc); + mk_clause(nk, la, nlb, nlc); + mk_clause(nk, nla, lb, nlc); + mk_clause(nk, nla, nlb, lc); + + mk_clause(k, nla, nlb, nlc); + mk_clause(k, nla, lb, lc); + mk_clause(k, la, nlb, lc); + mk_clause(k, la, lb, nlc); + cache_result(t, k); + } + return DONE; + } + return NO; + } + + mres match_iff(app * t, bool first, bool root) { + expr * a, * b; + if (is_iff(m, t, a, b)) { + if (first) { + bool visited = true; + visit(a, visited); + visit(b, visited); + if (!visited) + return CONT; + } + expr_ref la(m), lb(m); + expr_ref nla(m), nlb(m); + get_lit(a, false, la); + get_lit(b, false, lb); + inv(la, nla); + inv(lb, nlb); + if (root) { + mk_clause(la, nlb); + mk_clause(nla, lb); + } + else { + app_ref k(m), nk(m); + k = mk_fresh(); + nk = m.mk_not(k); + + mk_clause(nk, la, nlb); + mk_clause(nk, nla, lb); + + mk_clause(k, nla, nlb); + mk_clause(k, la, lb); + cache_result(t, k); + } + return DONE; + } + return NO; + } + + mres match_ite(app * t, bool first, bool root) { + if (!m.is_ite(t)) + return NO; + if (first) { + bool visited = true; + app * ite = t; + while (true) { + visit(ite->get_arg(0), visited); + if (m_ite_chains && m.is_ite(ite->get_arg(1)) && !is_shared(ite->get_arg(1))) { + visit(ite->get_arg(2), visited); + ite = to_app(ite->get_arg(1)); + continue; + } + if (m_ite_chains && m.is_ite(ite->get_arg(2)) && !is_shared(ite->get_arg(2))) { + visit(ite->get_arg(1), visited); + ite = to_app(ite->get_arg(2)); + continue; + } + visit(ite->get_arg(1), visited); + visit(ite->get_arg(2), visited); + break; + } + if (!visited) + return CONT; + } + expr_ref_buffer ctx(m); + expr_ref_buffer ex_pos_ctx(m); // for extra ite clauses + expr_ref_buffer ex_neg_ctx(m); // for extra ite clauses + expr_ref la(m), lb(m), lc(m); + expr_ref nla(m), nlb(m), nlc(m); + app_ref k(m), nk(m); + if (!root) { + k = mk_fresh(); + nk = m.mk_not(k); + cache_result(t, k); + } + +#define MK_ITE_ROOT_CLS(L1, L2) { \ + ctx.push_back(L1); ctx.push_back(L2); \ + mk_clause(ctx.size(), ctx.c_ptr()); \ + ctx.pop_back(); ctx.pop_back(); \ +} + +#define MK_ITE_CLS(L1, L2, L3) { \ + ctx.push_back(L1); ctx.push_back(L2); ctx.push_back(L3); \ + mk_clause(ctx.size(), ctx.c_ptr()); \ + ctx.pop_back(); ctx.pop_back(); ctx.pop_back(); \ +} + + app * ite = t; + while (true) { + get_lit(ite->get_arg(0), false, la); + inv(la, nla); + if (m_ite_chains && m.is_ite(ite->get_arg(1)) && !is_shared(ite->get_arg(1))) { + get_lit(ite->get_arg(2), false, lc); + if (root) { + MK_ITE_ROOT_CLS(la, lc); + } + else { + inv(lc, nlc); + MK_ITE_CLS(la, lc, nk); + MK_ITE_CLS(la, nlc, k); + if (m_ite_extra) { + ex_neg_ctx.push_back(lc); + ex_pos_ctx.push_back(nlc); + } + } + ctx.push_back(nla); + ite = to_app(ite->get_arg(1)); + continue; + } + if (m_ite_chains && m.is_ite(ite->get_arg(2)) && !is_shared(ite->get_arg(2))) { + get_lit(ite->get_arg(1), false, lb); + if (root) { + MK_ITE_ROOT_CLS(nla, lb); + } + else { + inv(lb, nlb); + MK_ITE_CLS(nla, lb, nk); + MK_ITE_CLS(nla, nlb, k); + if (m_ite_extra) { + ex_neg_ctx.push_back(lb); + ex_pos_ctx.push_back(nlb); + } + } + ctx.push_back(la); + ite = to_app(ite->get_arg(2)); + continue; + } + get_lit(ite->get_arg(1), false, lb); + get_lit(ite->get_arg(2), false, lc); + if (root) { + MK_ITE_ROOT_CLS(nla, lb); + MK_ITE_ROOT_CLS(la, lc); + } + else { + inv(lb, nlb); + inv(lc, nlc); + MK_ITE_CLS(nla, lb, nk); + MK_ITE_CLS(la, lc, nk); + MK_ITE_CLS(nla, nlb, k); + MK_ITE_CLS(la, nlc, k); + if (m_ite_extra) { + MK_ITE_CLS(lb, lc, nk); + MK_ITE_CLS(nlb, nlc, k); + + ex_neg_ctx.push_back(lb); ex_neg_ctx.push_back(lc); ex_neg_ctx.push_back(nk); + mk_clause(ex_neg_ctx.size(), ex_neg_ctx.c_ptr()); + ex_pos_ctx.push_back(nlb); ex_pos_ctx.push_back(nlc); ex_pos_ctx.push_back(k); + mk_clause(ex_pos_ctx.size(), ex_pos_ctx.c_ptr()); + } + } + break; + } + return DONE; + } + + mres match_or(app * t, bool first, bool root) { + if (!m.is_or(t)) + return NO; + if (first) { + bool visited = true; + unsigned num = t->get_num_args(); + unsigned blowup = 1; + for (unsigned i = 0; i < num; i++) { + expr * a = t->get_arg(i); + expr * a0; + if (m_distributivity && m.is_not(a, a0) && m.is_or(a0) && !is_shared(a0)) { + unsigned num2 = to_app(a0)->get_num_args(); + if (num2 < m_distributivity_blowup && blowup * num2 < m_distributivity_blowup && blowup < blowup * num2) { + blowup *= num2; + for (unsigned j = 0; j < num2; j++) + visit(to_app(a0)->get_arg(j), visited); + continue; + } + } + visit(a, visited); + } + if (!visited) + return CONT; + } + + app_ref k(m), nk(m); + if (!root) { + k = mk_fresh(); + nk = m.mk_not(k); + cache_result(t, k); + } + + unsigned num = t->get_num_args(); + bool distributivity = false; + if (m_distributivity) { + // check if need to apply distributivity + for (unsigned i = 0; i < num; i++) { + expr * a = t->get_arg(i); + expr * a0; + if (m.is_not(a, a0) && m.is_or(a0) && !is_shared(a0) && to_app(a0)->get_num_args() < m_distributivity_blowup) { + distributivity = true; + break; + } + } + } + + if (!distributivity) { + // easy case + expr_ref_buffer lits(m); expr_ref l(m); + for (unsigned i = 0; i < num; i++) { + get_lit(t->get_arg(i), false, l); + lits.push_back(l); + } + if (root) { + mk_clause(lits.size(), lits.c_ptr()); + } + else { + for (unsigned i = 0; i < num; i++) { + inv(lits[i], l); + mk_clause(l, k); + } + lits.push_back(nk); + mk_clause(lits.size(), lits.c_ptr()); + } + } + else { + expr_ref_buffer buffer(m); expr_ref l(m), nl(m); + sbuffer szs; + sbuffer it; + sbuffer offsets; + unsigned blowup = 1; + for (unsigned i = 0; i < num; i++) { + it.push_back(0); + offsets.push_back(buffer.size()); + expr * a = t->get_arg(i); + expr * a0; + if (m.is_not(a, a0) && m.is_or(a0) && !is_shared(a0)) { + unsigned num2 = to_app(a0)->get_num_args(); + if (num2 < m_distributivity_blowup && blowup * num2 < m_distributivity_blowup && blowup < blowup * num2) { + szs.push_back(num2); + blowup *= num2; + expr_ref_buffer lits(m); + for (unsigned j = 0; j < num2; j++) { + get_lit(to_app(a0)->get_arg(j), true, nl); + buffer.push_back(nl); + if (!root) { + inv(nl, l); + lits.push_back(l); + } + } + if (!root) { + lits.push_back(k); + mk_clause(lits.size(), lits.c_ptr()); + } + continue; + } + } + szs.push_back(1); + get_lit(a, false, l); + buffer.push_back(l); + if (!root) { + inv(l, nl); + mk_clause(nl, k); + } + } + SASSERT(offsets.size() == num); + sbuffer arg_lits; + ptr_buffer lits; + expr ** buffer_ptr = buffer.c_ptr(); + for (unsigned i = 0; i < num; i++) { + arg_lits.push_back(buffer_ptr + offsets[i]); + } + do { + lits.reset(); + for (unsigned i = 0; i < num; i++) { + lits.push_back(arg_lits[i][it[i]]); + } + if (!root) + lits.push_back(nk); + mk_clause(lits.size(), lits.c_ptr()); + } + while (product_iterator_next(szs.size(), szs.c_ptr(), it.c_ptr())); + } + return DONE; + } + +#define TRY(_MATCHER_) \ + r = _MATCHER_(t, first, t == root); \ + if (r == CONT) goto loop; \ + if (r == DONE) { m_frame_stack.pop_back(); continue; } + + void set_cancel(bool f) { + m_cancel = f; + } + + void checkpoint() { + cooperate("tseitin cnf"); + if (m_cancel) + throw tactic_exception(TACTIC_CANCELED_MSG); + if (memory::get_allocation_size() > m_max_memory) + throw tactic_exception(TACTIC_MAX_MEMORY_MSG); + } + + void process(expr * n, expr_dependency * dep) { + m_curr_dep = dep; + bool visited = true; + visit(n, visited, true); + if (visited) { + expr_ref l(m); + get_lit(n, false, l); + mk_clause(l); + return; + } + expr * root = n; + + app * t; bool first; mres r; + while (!m_frame_stack.empty()) { + loop: + checkpoint(); + frame & fr = m_frame_stack.back(); + t = fr.m_t; + first = fr.m_first; + fr.m_first = false; + TRY(match_or_3and); + TRY(match_or); + TRY(match_iff3); + TRY(match_iff); + TRY(match_ite); + TRY(match_not); + UNREACHABLE(); + } + } + + void reset_cache() { + m_cache.reset(); + m_cache_domain.reset(); + } + + void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + mc = 0; pc = 0; core = 0; + tactic_report report("tseitin-cnf", *g); + fail_if_proof_generation("tseitin-cnf", g); + m_produce_models = g->models_enabled(); + m_produce_unsat_cores = g->unsat_core_enabled(); + + m_occs(*g); + reset_cache(); + m_deps.reset(); + m_fresh_vars.reset(); + m_frame_stack.reset(); + m_clauses.reset(); + if (m_produce_models) + m_mc = alloc(filter_model_converter, m); + else + m_mc = 0; + + unsigned size = g->size(); + for (unsigned idx = 0; idx < size; idx++) { + process(g->form(idx), g->dep(idx)); + g->update(idx, m.mk_true(), 0, 0); // to save memory + } + + SASSERT(!m_produce_unsat_cores || m_clauses.size() == m_deps.size()); + + g->reset(); + unsigned sz = m_clauses.size(); + expr_fast_mark1 added; + for (unsigned i = 0; i < sz; i++) { + expr * cls = m_clauses.get(i); + if (added.is_marked(cls)) + continue; + added.mark(cls); + if (m_produce_unsat_cores) + g->assert_expr(cls, 0, m_deps.get(i)); + else + g->assert_expr(cls); + } + if (m_produce_models && !m_fresh_vars.empty()) + mc = m_mc.get(); + else + mc = 0; + g->inc_depth(); + result.push_back(g.get()); + TRACE("tseitin_cnf", g->display(tout);); + SASSERT(g->is_well_sorted()); + } + }; + + imp * m_imp; + params_ref m_params; +public: + tseitin_cnf_tactic(ast_manager & m, params_ref const & p): + m_params(p) { + m_imp = alloc(imp, m, p); + } + + virtual tactic * translate(ast_manager & m) { + return alloc(tseitin_cnf_tactic, m, m_params); + } + + virtual ~tseitin_cnf_tactic() { + dealloc(m_imp); + } + + virtual void updt_params(params_ref const & p) { + m_params = p; + m_imp->updt_params(p); + } + + virtual void collect_param_descrs(param_descrs & r) { + insert_max_memory(r); + r.insert(":common-patterns", CPK_BOOL, "(default: true) minimize the number of auxiliary variables during CNF encoding by identifing commonly used patterns"); + r.insert(":distributivity", CPK_BOOL, "(default: true) minimize the number of auxiliary variables during CNF encoding by applying distributivity over unshared subformulas"); + r.insert(":distributivity-blowup", CPK_UINT, "(default: 32) maximum overhead for applying distributivity during CNF encoding"); + r.insert("ite-chaing", CPK_BOOL, "(default: true) minimize the number of auxiliary variables during CNF encoding by identifing if-then-else chains"); + r.insert(":ite-extra", CPK_BOOL, "(default: true) add redundant clauses (that improve unit propagation) when encoding if-then-else formulas"); + } + + virtual void operator()(goal_ref const & in, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + (*m_imp)(in, result, mc, pc, core); + report_tactic_progress(":cnf-aux-vars", m_imp->m_num_aux_vars); + } + + virtual void cleanup() { + unsigned num_aux_vars = m_imp->m_num_aux_vars; + ast_manager & m = m_imp->m; + imp * d = m_imp; + #pragma omp critical (tactic_cancel) + { + m_imp = 0; + } + dealloc(d); + d = alloc(imp, m, m_params); + #pragma omp critical (tactic_cancel) + { + m_imp = d; + } + m_imp->m_num_aux_vars = num_aux_vars; + } + + virtual void set_cancel(bool f) { + if (m_imp) + m_imp->set_cancel(f); + } + + virtual void collect_statistics(statistics & st) const { + st.update("cnf encoding aux vars", m_imp->m_num_aux_vars); + } + + virtual void reset_statistics() { + m_imp->m_num_aux_vars = 0; + } +}; + +tactic * mk_tseitin_cnf_core_tactic(ast_manager & m, params_ref const & p) { + return clean(alloc(tseitin_cnf_tactic, m, p)); +} + +tactic * mk_tseitin_cnf_tactic(ast_manager & m, params_ref const & p) { + params_ref simp_p = p; + simp_p.set_bool(":elim-and", true); + simp_p.set_bool(":blast-distinct", true); + return or_else(mk_tseitin_cnf_core_tactic(m, p), + and_then(using_params(mk_simplify_tactic(m, p), simp_p), + mk_tseitin_cnf_core_tactic(m, p))); +} diff --git a/lib/tseitin_cnf_tactic.h b/lib/tseitin_cnf_tactic.h new file mode 100644 index 000000000..0a32651ea --- /dev/null +++ b/lib/tseitin_cnf_tactic.h @@ -0,0 +1,31 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + tseitin_cnf_tactic.h + +Abstract: + + Puts an assertion set in CNF. + Auxiliary variables are used to avoid blowup. + +Author: + + Leonardo (leonardo) 2011-12-29 + +Notes: + +--*/ +#ifndef _TSEITIN_CNF_TACTIC_H_ +#define _TSEITIN_CNF_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_tseitin_cnf_core_tactic(ast_manager & m, params_ref const & p = params_ref()); + +tactic * mk_tseitin_cnf_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/ufbv_strategy.cpp b/lib/ufbv_strategy.cpp new file mode 100644 index 000000000..9b6b41704 --- /dev/null +++ b/lib/ufbv_strategy.cpp @@ -0,0 +1,492 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + ufbv_strategy.cpp + +Abstract: + + General purpose strategy for UFBV benchmarks. + +Author: + + Christoph (cwinter) 2011-07-28 + +Notes: + +--*/ +#include"assertion_set_rewriter.h" +#include"nnf.h" +#include"der.h" +#include"distribute_forall.h" +#include"macro_finder.h" +#include"arith_simplifier_plugin.h" +#include"bv_simplifier_plugin.h" +#include"demodulator.h" +#include"quasi_macros.h" +#include"reduce_args.h" +#include"ufbv_strategy.h" +#include"shallow_context_simplifier.h" +#include"gaussian_elim.h" +#include"elim_var_model_converter.h" +#include"ast_smt2_pp.h" + +// --- TRACE STRATEGY + +#ifdef _TRACE + +class trace_as_st : public assertion_set_strategy { + ast_manager & m; + const char * tag; + +public: + trace_as_st(ast_manager & m, const char * tag) : m(m),tag(tag) { } + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + TRACE(tag, { s.display(tout); }); + } + + virtual void cleanup() {} +}; + +#endif + +as_st * mk_trace_as(ast_manager & m, const char * tag) { +#ifdef _TRACE + return alloc(trace_as_st, m, tag); +#else + return noop(); +#endif +} + +as_st * mk_der_fp(ast_manager & m, params_ref const & p) { + return repeat(and_then(mk_der(m), mk_simplifier(m, p))); +} + + +// --- DISTRIBUTE-FORALL STRATEGY + +class distribute_forall_st : public assertion_set_strategy { + ast_manager & m; + params_ref m_params; + bool m_produce_models; + bool m_cancel; + +public: + distribute_forall_st(ast_manager & m, params_ref const & p = params_ref()) : m(m),m_params(p),m_produce_models(false),m_cancel(false) { } + virtual ~distribute_forall_st() {} + + virtual void updt_params(params_ref const & p) { + m_produce_models = p.get_bool(":produce-models", false); + } + + static void get_param_descrs(param_descrs & r) { + insert_produce_models(r); + } + + virtual void collect_param_descrs(param_descrs & r) { get_param_descrs(r); } + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + as_st_report report("distribute-forall", s); + basic_simplifier_plugin bsimp(m); + bsimp.set_eliminate_and(true); + distribute_forall apply_dist(m, bsimp); + + for (unsigned i = 0; i < s.size(); i++) { + if (m_cancel) + throw strategy_exception(STE_CANCELED_MSG); + + expr * n = s.form(i); + expr_ref r(m); + apply_dist(n, r); + if (n != r.get()) { + if (m.proofs_enabled()) { + proof * old_pr = s.pr(i); + proof_ref new_pr(m); + new_pr = m.mk_rewrite_star(n, r, 0, 0); + new_pr = m.mk_modus_ponens(old_pr, new_pr); + s.update(i, r, new_pr); + } + else + s.update(i, r, 0); + } + } + + mc = 0; // CMW: No model conversion necessary; variables and functions don't change. + TRACE("distribute_forall", s.display(tout);); + } + + virtual void cleanup() {} +protected: + virtual void set_cancel(bool f) { m_cancel = f; } +}; + +as_st * mk_distribute_forall(ast_manager & m, params_ref const & p) { + return alloc(distribute_forall_st, m, p); +} + +model_converter * macro_manager2model_converter(macro_manager const & mm) { + elim_var_model_converter * mc = alloc(elim_var_model_converter, mm.get_manager()); + unsigned num = mm.get_num_macros(); + for (unsigned i = 0; i < num; i++) { + expr_ref f_interp(mm.get_manager()); + func_decl * f = mm.get_macro_interpretation(i, f_interp); + mc->insert(f, f_interp); + } + return mc; +} + +// --- MACRO FINDER STRATEGY + +class macro_finder_st : public assertion_set_strategy { + ast_manager & m; + params_ref m_params; + bool m_produce_models; + bool m_cancel; + bool m_elim_and; + +public: + macro_finder_st(ast_manager & m, params_ref const & p = params_ref(), bool elim_and=false) : m(m),m_params(p),m_produce_models(false),m_cancel(false),m_elim_and(elim_and) { } + virtual ~macro_finder_st() {} + + virtual void updt_params(params_ref const & p) { + m_produce_models = p.get_bool(":produce-models", false); + } + + static void get_param_descrs(param_descrs & r) { + insert_produce_models(r); + } + + virtual void collect_param_descrs(param_descrs & r) { get_param_descrs(r); } + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + TRACE("debug_ids", m.display_free_ids(tout); tout << "\n";); + as_st_report report("macro-finder", s); + simplifier simp(m); + basic_simplifier_plugin * bsimp = alloc(basic_simplifier_plugin, m); + bsimp->set_eliminate_and(m_elim_and); + simp.register_plugin(bsimp); + arith_simplifier_params a_params; + arith_simplifier_plugin * asimp = alloc(arith_simplifier_plugin, m, *bsimp, a_params); + simp.register_plugin(asimp); + bv_simplifier_params bv_params; + bv_simplifier_plugin * bvsimp = alloc(bv_simplifier_plugin, m, *bsimp, bv_params); + simp.register_plugin(bvsimp); + + macro_manager mm(m, simp); + macro_finder mf(m, mm); + + expr_ref_vector forms(m), new_forms(m); + proof_ref_vector proofs(m), new_proofs(m); + + for (unsigned i = 0; i < s.size(); i++) { + forms.push_back(s.form(i)); + proofs.push_back(s.pr(i)); + } + + mf(forms.size(), forms.c_ptr(), proofs.c_ptr(), new_forms, new_proofs); + + s.reset(); + for (unsigned i = 0; i < new_forms.size(); i++) { + s.assert_expr(new_forms.get(i), (m.proofs_enabled()) ? new_proofs.get(i) : 0); + } + + mc = macro_manager2model_converter(mm); + TRACE("debug_ids", m.display_free_ids(tout); tout << "\n";); + } + + virtual void cleanup() {} +protected: + virtual void set_cancel(bool f) { m_cancel = f; } +}; + +as_st * mk_macro_finder(ast_manager & m, params_ref const & p, bool elim_and=false) { + return alloc(macro_finder_st, m, p, elim_and); +} + + +// --- DEMODULATOR STRATEGY + +class demodulator_st : public assertion_set_strategy { + ast_manager & m; + params_ref m_params; + bool m_produce_models; + bool m_cancel; + +public: + demodulator_st(ast_manager & m, params_ref const & p = params_ref()) : m(m),m_params(p),m_produce_models(false),m_cancel(false) { } + virtual ~demodulator_st() {} + + virtual void updt_params(params_ref const & p) { + m_produce_models = p.get_bool(":produce-models", false); + } + + static void get_param_descrs(param_descrs & r) { + insert_produce_models(r); + } + + virtual void collect_param_descrs(param_descrs & r) { get_param_descrs(r); } + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + as_st_report report("demodulator", s); + basic_simplifier_plugin bsimp(m); + bsimp.set_eliminate_and(true); + demodulator dem(m, bsimp); + + expr_ref_vector forms(m), new_forms(m); + proof_ref_vector proofs(m), new_proofs(m); + + for (unsigned i = 0; i < s.size(); i++) { + forms.push_back(s.form(i)); + proofs.push_back(s.pr(i)); + } + + dem(forms.size(), forms.c_ptr(), proofs.c_ptr(), new_forms, new_proofs); + + s.reset(); + for (unsigned i = 0; i < new_forms.size(); i++) { + s.assert_expr(new_forms.get(i), (m.proofs_enabled()) ? new_proofs.get(i) : 0); + } + + mc = 0; // CMW: The demodulator could potentially remove all references to a variable. + } + + virtual void cleanup() {} +protected: + virtual void set_cancel(bool f) { m_cancel = f; } +}; + +as_st * mk_demodulator(ast_manager & m, params_ref const & p) { + return alloc(demodulator_st, m, p); +} + + +// --- QUASI-MACROS STRATEGY + +class quasi_macros_st : public assertion_set_strategy { + ast_manager & m; + params_ref m_params; + bool m_produce_models; + bool m_cancel; + +public: + quasi_macros_st(ast_manager & m, params_ref const & p = params_ref()) : m(m),m_params(p),m_produce_models(false),m_cancel(false) { } + virtual ~quasi_macros_st() {} + + virtual void updt_params(params_ref const & p) { + m_produce_models = p.get_bool(":produce-models", false); + } + + static void get_param_descrs(param_descrs & r) { + insert_produce_models(r); + } + + virtual void collect_param_descrs(param_descrs & r) { get_param_descrs(r); } + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + as_st_report report("quasi-macros", s); + simplifier simp(m); + basic_simplifier_plugin * bsimp = alloc(basic_simplifier_plugin, m); + bsimp->set_eliminate_and(true); + simp.register_plugin(bsimp); + arith_simplifier_params a_params; + arith_simplifier_plugin * asimp = alloc(arith_simplifier_plugin, m, *bsimp, a_params); + simp.register_plugin(asimp); + bv_simplifier_params bv_params; + bv_simplifier_plugin * bvsimp = alloc(bv_simplifier_plugin, m, *bsimp, bv_params); + simp.register_plugin(bvsimp); + + macro_manager mm(m, simp); + quasi_macros qm(m, mm, *bsimp, simp); + bool more = true; + + expr_ref_vector forms(m), new_forms(m); + proof_ref_vector proofs(m), new_proofs(m); + + for (unsigned i = 0; i < s.size(); i++) { + forms.push_back(s.form(i)); + proofs.push_back(s.pr(i)); + } + + while (more) { // CMW: This is applied until fixpoint; should we have a fixpoint_as_st for that? + if (m_cancel) + throw strategy_exception(STE_CANCELED_MSG); + + new_forms.reset(); + new_proofs.reset(); + more = qm(forms.size(), forms.c_ptr(), proofs.c_ptr(), new_forms, new_proofs); + forms.swap(new_forms); + proofs.swap(new_proofs); + } + + s.reset(); + for (unsigned i = 0; i < forms.size(); i++) { + s.assert_expr(forms.get(i), (m.proofs_enabled()) ? proofs.get(i) : 0); + } + + mc = macro_manager2model_converter(mm); + } + + virtual void cleanup() {} +protected: + virtual void set_cancel(bool f) { m_cancel = f; } +}; + +as_st * mk_quasi_macros(ast_manager & m, params_ref const & p) { + return alloc(quasi_macros_st, m, p); +} + +// --- ELIMINATE AND STRATEGY + +as_st * mk_eliminate_and(ast_manager & m, params_ref const & p) { + params_ref elim_and_p; + elim_and_p.set_bool(":elim-and", true); + return using_params(mk_simplifier(m, p), elim_and_p); +} + +// --- DISPLAY ASSERTION SET STRATEGY +// CMW: This was a temporary hack. Use cmd_context to print benchmark files. + +//class display_as_st : public assertion_set_strategy { +// ast_manager & m; +// params_ref m_params; +// +//public: +// display_as_st (ast_manager & m, params_ref const & p = params_ref()) : m(m),m_params(p) { } +// virtual ~display_as_st() {} +// +// virtual void updt_params(params_ref const & p) {} +// +// static void get_param_descrs(param_descrs & r) {} +// +// virtual void collect_param_descrs(param_descrs & r) { get_param_descrs(r); } +// +// struct find_uf_proc { +// ast_manager & m_manager; +// obj_hashtable & m_fds; +// +// find_uf_proc(ast_manager & m, obj_hashtable & fds): +// m_manager(m), +// m_fds(fds) { +// } +// +// void operator()(var * n) {} +// +// void operator()(quantifier * n) {} +// +// void operator()(app * n) { +// func_decl * d = n->get_decl(); +// if (d->get_family_id() != null_family_id) +// return; // ignore interpreted symbols +// m_fds.insert(d); +// } +// }; +// +// virtual void operator()(assertion_set & s, model_converter_ref & mc) { +// std::cerr << "(set-info :source ||)" << std::endl; +// std::cerr << "(set-info :status unknown)" << std::endl; +// std::cerr << "(set-logic UFBV)" << std::endl; +// +// // Find functions +// obj_hashtable fds; +// find_uf_proc proc(m, fds); +// unsigned sz = s.size(); +// for (unsigned i = 0; i < sz; i++) { +// expr_fast_mark1 visited; +// quick_for_each_expr(proc, visited, s.form(i)); +// } +// +// // print functions +// for (obj_hashtable::iterator it = fds.begin(); it != fds.end(); it++) { +// // How do we print (declare-fun ...) ? +// std::cerr << mk_ismt2_pp(*it, m) << std::endl; +// } +// +// // print assertions +// for (unsigned i = 0; i < s.size(); i++) { +// std::cerr << "(assert "; +// std::cerr << mk_ismt2_pp(s.form(i), m); +// std::cerr << ")" << std::endl; +// } +// std::cerr << "(check-sat)" << std::endl; +// } +// +// virtual void cleanup() {} +//protected: +// virtual void set_cancel(bool f) {} +//}; +// +//as_st * mk_display_as(ast_manager & m, params_ref const & p) { +// return alloc(display_as_st, m, p); +//} + + +class debug_ids_st : public assertion_set_strategy { + ast_manager & m; + + struct proc { + ast_manager & m; + proc(ast_manager & _m):m(_m) {} + void operator()(var * n) { TRACE_CODE(tout << n->get_id() << " ";); } + void operator()(app * n) { TRACE_CODE(tout << n->get_id() << " ";); } + void operator()(quantifier * n) { TRACE_CODE(tout << n->get_id() << " ";); } + }; + +public: + debug_ids_st(ast_manager & _m):m(_m) {} + + virtual void operator()(assertion_set & s, model_converter_ref & mc) { + mc = 0; + TRACE("debug_ids", + tout << "free ids:\n"; m.display_free_ids(tout); tout << "\n"; + proc p(m); + tout << "assertion_set ids:\n"; + for_each_expr_as(p, s); + tout << "\n";); + } + virtual void cleanup() {} +}; + + +// --- UFBV STRATEGY + +as_st * mk_preprocessor(ast_manager & m, params_ref const & p) { + + return and_then(mk_trace_as(m, "ufbv_pre"), + and_then( mk_simplifier(m, p), + mk_constant_propagation(m, p), + and_then(mk_macro_finder(m, p, false), mk_simplifier(m, p)), + and_then(mk_snf(p), mk_simplifier(m, p)), + mk_eliminate_and(m, p), + mk_eq_solver(m, p), + and_then(mk_der_fp(m, p), mk_simplifier(m, p)), + and_then(mk_distribute_forall(m, p), mk_simplifier(m, p)) + ), + and_then( and_then(mk_reduce_args(m, p), mk_simplifier(m, p)), + and_then(mk_macro_finder(m, p, true), mk_simplifier(m, p)), + and_then(mk_demodulator(m, p), mk_simplifier(m, p)), + and_then(mk_quasi_macros(m, p), mk_simplifier(m, p)), + and_then(mk_der_fp(m, p), mk_simplifier(m, p)), + mk_simplifier(m, p)), + mk_trace_as(m, "ufbv_post")); +} + +as_st * mk_ufbv_strategy(ast_manager & m, params_ref const & p) { + params_ref main_p(p); + main_p.set_bool(":mbqi", true); + main_p.set_uint(":mbqi-max-iterations", -1); + main_p.set_bool(":elim-and", true); + main_p.set_bool(":solver", true); + + // this prints the skolemized version of a benchmark + // as_st * st = and_then(mk_skolemizer(m, main_p), mk_display_as(m, main_p)); + + as_st * st = and_then(repeat(mk_preprocessor(m, main_p), 2), + alloc(debug_ids_st, m), + using_params(mk_smt_solver(false), main_p)); + + st->updt_params(p); + + return st; +} diff --git a/lib/ufbv_strategy.h b/lib/ufbv_strategy.h new file mode 100644 index 000000000..5c14be73b --- /dev/null +++ b/lib/ufbv_strategy.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + ufbv_strategy.h + +Abstract: + + General purpose strategy for UFBV benchmarks. + +Author: + + Christoph (cwinter) 2011-07-28 + +Notes: + +--*/ +#ifndef _UFBV_STRATEGY_H_ +#define _UFBV_STRATEGY_H_ + +#include"assertion_set_strategy.h" + +as_st * mk_ufbv_strategy(ast_manager & m, params_ref const & p); + +MK_SIMPLE_ST_FACTORY(ufbv_stf, mk_ufbv_strategy(m, p)); + +#endif diff --git a/lib/uint_map.h b/lib/uint_map.h new file mode 100644 index 000000000..07a7e579e --- /dev/null +++ b/lib/uint_map.h @@ -0,0 +1,60 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + uint_map.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-07-01. + +Revision History: + +--*/ +#ifndef _UINT_MAP_H_ +#define _UINT_MAP_H_ + +#include"vector.h" + +/** + \brief Implement a map from unsigned to T * using vectors +*/ +template +class uint_map { + ptr_vector m_map; +public: + bool contains(unsigned k) const { return m_map.get(k, 0) != 0; } + + bool find(unsigned k, T * & v) const { + if (k >= m_map.size()) + return false; + else { + v = m_map[k]; + return v != 0; + } + } + + void insert(unsigned k, T * v) { + m_map.reserve(k+1); + m_map[k] = v; + SASSERT(contains(k)); + } + + void erase(unsigned k) { + if (k < m_map.size()) + m_map[k] = 0; + } + + void reset() { + m_map.reset(); + } +}; + + +#endif /* _UINT_MAP_H_ */ + diff --git a/lib/uint_set.h b/lib/uint_set.h new file mode 100644 index 000000000..7f202652f --- /dev/null +++ b/lib/uint_set.h @@ -0,0 +1,243 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + uint_set.h + +Abstract: + + Sets of unsigned integers. + +Author: + + Leonardo de Moura (leonardo) 2006-12-07. + +Revision History: + +--*/ +#ifndef _UINT_SET_H_ +#define _UINT_SET_H_ + +#include"util.h" +#include"vector.h" + +COMPILE_TIME_ASSERT(sizeof(unsigned) == 4); + +class uint_set : unsigned_vector { + +public: + + typedef unsigned data; + + uint_set() {} + + uint_set(const uint_set & source) { + for (unsigned i = 0; i < source.size(); ++i) { + push_back(source[i]); + } + } + + ~uint_set() {} + + void swap(uint_set & other) { + unsigned_vector::swap(other); + } + + // return the maximum value that can be stored in the set. + unsigned get_max_elem() const { + return 32 * size(); + } + + void reset() { + unsigned_vector::reset(); + } + + bool empty() const { + for (unsigned i = 0; i < size(); i++) { + if ((*this)[i] != 0) { + return false; + } + } + return true; + } + + void insert(unsigned val) { + unsigned idx = val >> 5; + if (idx >= size()) { + resize(idx+1); + } + (*this)[idx] |= 1 << (val & 31); + } + + void remove(unsigned val) { + unsigned idx = val >> 5; + if (idx < size()) { + (*this)[val >> 5] &= ~(1 << (val & 31)); + } + } + + bool contains(unsigned val) const { + unsigned idx = val >> 5; + return idx < size() && ((*this)[idx] & (1 << (val & 31))) != 0; + } + + unsigned num_elems() const { + unsigned r = 0; + for (unsigned i = 0; i < size(); i++) { + r += get_num_1bits((*this)[i]); + } + return r; + } + + // Insert in the this object the elements in the set source. + uint_set & operator |=(const uint_set & source) { + unsigned source_size = source.size(); + if (source_size > size()) { + resize(source_size + 1); + } + for (unsigned i = 0; i < source_size; i++) { + (*this)[i] |= source[i]; + } + return *this; + } + + uint_set& operator &=(const uint_set& source) { + unsigned source_size = source.size(); + if (source_size < size()) { + resize(source_size); + } + for (unsigned i = 0; i < size(); i++) { + (*this)[i] &= source[i]; + } + return *this; + } + + bool operator==(const uint_set & source) const { + unsigned min_size = size(); + if (source.size() < min_size) { + min_size = source.size(); + } + for (unsigned i = 0; i < min_size; i++) { + if ((*this)[i] != source[i]) { + return false; + } + } + for (unsigned i = min_size; i < size(); ++i) { + if ((*this)[i]) { + return false; + } + } + for (unsigned i = min_size; i < source.size(); ++i) { + if (source[i]) { + return false; + } + } + + return true; + } + + bool operator!=(const uint_set & source) const { + return !operator==(source); + } + + // Return true if the this set is a subset of source. + bool subset_of(const uint_set & source) const { + unsigned min_size = size(); + if (source.size() < min_size) { + min_size = source.size(); + } + for (unsigned i = 0; i < min_size; i++) { + if (((*this)[i] & ~source[i]) != 0) { + return false; + } + } + for (unsigned i = min_size; i < size(); ++i) { + if ((*this)[i]) { + return false; + } + } + return true; + } + + class iterator { + uint_set const* m_set; + unsigned m_index; + + bool invariant() const { return m_index <= m_set->get_max_elem(); } + + bool at_end() const { return m_index == m_set->get_max_elem(); } + + void scan_idx() { + SASSERT(invariant()); + while (!at_end() && !m_set->contains(m_index) && 0 != (m_index & 31)) { + ++m_index; + } + SASSERT(invariant()); + } + void scan_word() { + SASSERT((m_index & 31) == 0); + SASSERT(invariant()); + unsigned idx = m_index >> 5; + while (!at_end() && !(*m_set)[idx]) { + ++idx; + m_index += 32; + } + SASSERT(invariant()); + } + bool contains() const { return m_set->contains(m_index); } + void scan() { + scan_idx(); + if (contains() || at_end()) { + return; + } + scan_word(); + if (!at_end() && !contains()) { + ++m_index; + } + scan_idx(); + SASSERT(invariant()); + } + public: + iterator(uint_set const& s, bool at_end): + m_set(&s), m_index(at_end?s.get_max_elem():0) { + scan(); + SASSERT(invariant()); + } + unsigned operator*() const { return m_index; } + bool operator==(iterator const& it) const { return m_index == it.m_index; } + bool operator!=(iterator const& it) const { return m_index != it.m_index; } + iterator & operator++() { ++m_index; scan(); return *this; } + iterator operator++(int) { iterator tmp = *this; ++*this; return tmp; } + iterator & operator=(iterator const& other) { + m_set = other.m_set; + m_index = other.m_index; + return *this; + } + }; + + iterator const begin() const { return iterator(*this, false); } + iterator const end() const { return iterator(*this, true); } +}; + +inline std::ostream & operator<<(std::ostream & target, const uint_set & s) { + unsigned n = s.get_max_elem() + 1; + target << "{"; + bool first = true; + for (unsigned i = 0; i < n; i++) { + if (s.contains(i)) { + if (first) { + first = false; + } + else { + target << ", "; + } + target << i; + } + } + target << "}"; + return target; +} + +#endif /* _UINT_SET_H_ */ + diff --git a/lib/unifier.cpp b/lib/unifier.cpp new file mode 100644 index 000000000..a5bd4d155 --- /dev/null +++ b/lib/unifier.cpp @@ -0,0 +1,183 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + unifier.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-28. + +Revision History: + +--*/ +#include"unifier.h" +#include"ast_pp.h" + +void unifier::reset(unsigned num_offsets) { + m_todo.reset(); + m_find.reset(); + m_size.reset(); +} + +/** + \brief Find with path compression. +*/ +expr_offset unifier::find(expr_offset p) { + buffer path; + expr_offset next; + while (m_find.find(p, next)) { + path.push_back(p); + p = next; + } + buffer::iterator it = path.begin(); + buffer::iterator end = path.end(); + for (; it != end; ++it) { + expr_offset & prev = *it; + m_find.insert(prev, p); + } + return p; +} + +void unifier::save_var(expr_offset const & p, expr_offset const & t) { + expr * n = p.get_expr(); + if (is_var(n)) { + unsigned off = p.get_offset(); + m_subst->insert(to_var(n)->get_idx(), off, t); + } +} + + +/** + \brief Merge the equivalence classes of n1 and n2. n2 will be the + root of the resultant equivalence class. +*/ +void unifier::union1(expr_offset const & n1, expr_offset const & n2) { + DEBUG_CODE({ + expr_offset f; + SASSERT(!m_find.find(n1, f)); + SASSERT(!m_find.find(n2, f)); + }); + unsigned sz1 = 1; + unsigned sz2 = 1; + m_size.find(n1, sz1); + m_size.find(n2, sz2); + m_find.insert(n1, n2); + m_size.insert(n2, sz1 + sz2); + save_var(n1, n2); +} + +/** + \brief Merge the equivalence classes of n1 and n2. The root of the + resultant equivalence class is the one with more elements. +*/ +void unifier::union2(expr_offset n1, expr_offset n2) { + DEBUG_CODE({ + expr_offset f; + SASSERT(!m_find.find(n1, f)); + SASSERT(!m_find.find(n2, f)); + }); + unsigned sz1 = 1; + unsigned sz2 = 1; + m_size.find(n1, sz1); + m_size.find(n2, sz2); + if (sz1 > sz2) + std::swap(n1, n2); + m_find.insert(n1, n2); + m_size.insert(n2, sz1 + sz2); + save_var(n1, n2); +} + +bool unifier::unify_core(expr_offset p1, expr_offset p2) { + entry e(p1, p2); + m_todo.push_back(e); + while (!m_todo.empty()) { + entry const & e = m_todo.back(); + p1 = find(e.first); + p2 = find(e.second); + m_todo.pop_back(); + if (p1 != p2) { + expr * n1 = p1.get_expr(); + expr * n2 = p2.get_expr(); + SASSERT(!is_quantifier(n1)); + SASSERT(!is_quantifier(n2)); + bool v1 = is_var(n1); + bool v2 = is_var(n2); + if (v1 && v2) { + union2(p1, p2); + } + else if (v1) { + union1(p1, p2); + } + else if (v2) { + union1(p2, p1); + } + else { + app * a1 = to_app(n1); + app * a2 = to_app(n2); + + unsigned off1 = p1.get_offset(); + unsigned off2 = p2.get_offset(); + if (a1->get_decl() != a2->get_decl() || a1->get_num_args() != a2->get_num_args()) + return false; + union2(p1, p2); + unsigned j = a1->get_num_args(); + while (j > 0) { + --j; + entry new_e(expr_offset(a1->get_arg(j), off1), + expr_offset(a2->get_arg(j), off2)); + m_todo.push_back(new_e); + } + } + } + } + return true; +} + +bool unifier::operator()(unsigned num_exprs, expr ** es, substitution & s, bool use_offsets) { + SASSERT(num_exprs > 0); + unsigned num_offsets = use_offsets ? num_exprs : 1; + reset(num_offsets); + m_subst = &s; +#if 1 + TRACE("unifier", for (unsigned i = 0; i < num_exprs; ++i) tout << mk_pp(es[i], m_manager) << "\n";); + for (unsigned i = s.get_num_bindings(); i > 0; ) { + --i; + std::pair bound; + expr_offset root, child; + s.get_binding(i, bound, root); + TRACE("unifier", tout << bound.first << " |-> " << mk_pp(root.get_expr(), m_manager) << "\n";); + if (is_var(root.get_expr())) { + var* v = m_manager.mk_var(bound.first,to_var(root.get_expr())->get_sort()); + child = expr_offset(v, bound.second); + unsigned sz1 = 1; + unsigned sz2 = 1; + m_size.find(child, sz1); + m_size.find(root, sz2); + m_find.insert(child, root); + m_size.insert(root, sz1 + sz2); + } + } +#endif + for (unsigned i = 0; i < num_exprs - 1; i++) { + if (!unify_core(expr_offset(es[i], use_offsets ? i : 0), + expr_offset(es[i+1], use_offsets ? i + 1 : 0))) { + m_last_call_succeeded = false; + return m_last_call_succeeded; + } + } + + m_last_call_succeeded = m_subst->acyclic(); + return m_last_call_succeeded; +} + +bool unifier::operator()(expr * e1, expr * e2, substitution & s, bool use_offsets) { + expr * es[2] = { e1, e2 }; + return operator()(2, es, s, use_offsets); +} + diff --git a/lib/unifier.h b/lib/unifier.h new file mode 100644 index 000000000..315cea092 --- /dev/null +++ b/lib/unifier.h @@ -0,0 +1,70 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + unifier.h + +Abstract: + + Quasi-linear unification. + +Author: + + Leonardo de Moura (leonardo) 2008-01-28. + +Revision History: + +--*/ +#ifndef _UNIFIER_H_ +#define _UNIFIER_H_ + +#include"ast.h" +#include"substitution.h" + +/** + \brief Functor for unifying expressions. + It implements a quasi-linear unification algorithm. + + It has support for two different variable banks: left and right. + That is, variable i in left bank is considered different from + variable i in the right bank. This feature allows us to avoid + unnecessary variable renaming. +*/ +class unifier { + typedef std::pair entry; + + ast_manager & m_manager; + substitution * m_subst; + + svector m_todo; + expr_offset_map m_find; + expr_offset_map m_size; + + bool m_last_call_succeeded; + + expr_offset find(expr_offset n); + void save_var(expr_offset const & p, expr_offset const & t); + void union1(expr_offset const & n1, expr_offset const & n2); + void union2(expr_offset n1, expr_offset n2); + void reset(unsigned num_offsets); + + bool unify_core(expr_offset p1, expr_offset p2); + +public: + unifier(ast_manager & m):m_manager(m), m_last_call_succeeded(false) {} + + /** + \brief Unify the given expressions. Return true if succeeded, + and store the result in the given substitution. + + If use_offsets is true, then the variables in the given expressions are assumed to be + in different banks. + */ + bool operator()(unsigned num_exprs, expr ** es, substitution & s, bool use_offsets = true); + + bool operator()(expr * e1, expr * e2, substitution & s, bool use_offsets = true); +}; + +#endif /* _UNIFIER_H_ */ + diff --git a/lib/union_find.h b/lib/union_find.h new file mode 100644 index 000000000..cb2ce68c8 --- /dev/null +++ b/lib/union_find.h @@ -0,0 +1,160 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + union_find.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-05-31. + +Revision History: + +--*/ +#ifndef _UNION_FIND_H_ +#define _UNION_FIND_H_ + +#include "trail.h" + +class union_find_default_ctx { +public: + typedef trail_stack _trail_stack; + union_find_default_ctx() : m_stack(*this) {} + + void unmerge_eh(unsigned, unsigned) {} + void merge_eh(unsigned, unsigned, unsigned, unsigned) {} + void after_merge_eh(unsigned, unsigned, unsigned, unsigned) {} + + _trail_stack& get_trail_stack() { return m_stack; } + +private: + _trail_stack m_stack; +}; + +template +class union_find { + Ctx & m_ctx; + trail_stack & m_trail_stack; + svector m_find; + svector m_size; + svector m_next; + + class mk_var_trail; + friend class mk_var_trail; + + class mk_var_trail : public trail { + union_find & m_owner; + public: + mk_var_trail(union_find & o):m_owner(o) {} + virtual ~mk_var_trail() {} + virtual void undo(Ctx & ctx) { + m_owner.m_find.pop_back(); + m_owner.m_size.pop_back(); + m_owner.m_next.pop_back(); + } + }; + + mk_var_trail m_mk_var_trail; + + class merge_trail; + friend class merge_trail; + + class merge_trail : public trail { + union_find & m_owner; + unsigned m_r1; + public: + merge_trail(union_find & o, unsigned r1):m_owner(o), m_r1(r1) {} + virtual ~merge_trail() {} + virtual void undo(Ctx & ctx) { m_owner.unmerge(m_r1); } + }; + + void unmerge(unsigned r1) { + unsigned r2 = m_find[r1]; + TRACE("union_find", tout << "unmerging " << r1 << " " << r2 << "\n";); + SASSERT(find(r2) == r2); + m_size[r2] -= m_size[r1]; + m_find[r1] = r1; + std::swap(m_next[r1], m_next[r2]); + m_ctx.unmerge_eh(r2, r1); + CASSERT("union_find", check_invariant()); + } + +public: + union_find(Ctx & ctx):m_ctx(ctx), m_trail_stack(ctx.get_trail_stack()), m_mk_var_trail(*this) {} + + unsigned mk_var() { + unsigned r = m_find.size(); + m_find.push_back(r); + m_size.push_back(1); + m_next.push_back(r); + m_trail_stack.push_ptr(&m_mk_var_trail); + return r; + } + + unsigned get_num_vars() const { return m_find.size(); } + + unsigned find(unsigned v) const { + while (true) { + unsigned new_v = m_find[v]; + if (new_v == v) + return v; + v = new_v; + } + } + + unsigned next(unsigned v) const { return m_next[v]; } + + bool is_root(unsigned v) const { return m_find[v] == v; } + + void merge(unsigned v1, unsigned v2) { + unsigned r1 = find(v1); + unsigned r2 = find(v2); + TRACE("union_find", tout << "merging " << r1 << " " << r2 << "\n";); + if (r1 == r2) + return; + if (m_size[r1] > m_size[r2]) + std::swap(r1, r2); + m_ctx.merge_eh(r2, r1, v2, v1); + m_find[r1] = r2; + m_size[r2] += m_size[r1]; + std::swap(m_next[r1], m_next[r2]); + m_trail_stack.push(merge_trail(*this, r1)); + m_ctx.after_merge_eh(r2, r1, v2, v1); + CASSERT("union_find", check_invariant()); + } + + void display(std::ostream & out) const { + unsigned num = get_num_vars(); + for (unsigned v = 0; v < num; v++) { + out << "v" << v << " --> v" << m_find[v] << "\n"; + } + } + +#ifdef Z3DEBUG + bool check_invariant() const { + unsigned num = get_num_vars(); + for (unsigned v = 0; v < num; v++) { + if (is_root(v)) { + unsigned curr = v; + unsigned sz = 0; + do { + SASSERT(find(curr) == v); + sz++; + curr = next(curr); + } + while (curr != v); + SASSERT(m_size[v] == sz); + } + } + return true; + } +#endif +}; + +#endif /* _UNION_FIND_H_ */ + diff --git a/lib/unit_subsumption_tactic.cpp b/lib/unit_subsumption_tactic.cpp new file mode 100644 index 000000000..5480ded54 --- /dev/null +++ b/lib/unit_subsumption_tactic.cpp @@ -0,0 +1,151 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + unit_subsumption_tactic.cpp + +Abstract: + + Simplify goal using subsumption based on unit propagation. + +Author: + + Nikolaj Bjorner (nbjorner) 2012-9-6 + +--*/ + +#include "unit_subsumption_tactic.h" +#include "smt_context.h" + +struct unit_subsumption_tactic : public tactic { + ast_manager& m; + params_ref m_params; + front_end_params m_fparams; + volatile bool m_cancel; + smt::context m_context; + expr_ref_vector m_clauses; + unsigned m_clause_count; + bit_vector m_is_deleted; + unsigned_vector m_deleted; + + unit_subsumption_tactic( + ast_manager& m, + params_ref const& p): + m(m), + m_params(p), + m_cancel(false), + m_context(m, m_fparams, p), + m_clauses(m) { + } + + void set_cancel(bool f) { + m_cancel = f; + m_context.set_cancel_flag(f); + } + + virtual void cleanup() { + set_cancel(false); + } + + virtual void operator()(/* in */ goal_ref const & in, + /* out */ goal_ref_buffer & result, + /* out */ model_converter_ref & mc, + /* out */ proof_converter_ref & pc, + /* out */ expr_dependency_ref & core) { + reduce_core(in, result); + } + + virtual void updt_params(params_ref const& p) { + m_params = p; + // m_context.updt_params(p); does not exist. + } + + virtual tactic* translate(ast_manager& m) { + return alloc(unit_subsumption_tactic, m, m_params); + } + + void checkpoint() { + if (m_cancel) { + throw tactic_exception(TACTIC_CANCELED_MSG); + } + } + + void reduce_core(goal_ref const& g, goal_ref_buffer& result) { + init(g); + m_context.push(); + assert_clauses(g); + m_context.push(); // internalize assertions. + prune_clauses(); + goal_ref r(g); + insert_result(r); + r->elim_true(); + result.push_back(r.get()); + m_context.pop(2); + TRACE("unit_subsumption_tactic", g->display(tout); r->display(tout);); + } + + void assert_clauses(goal_ref const& g) { + for (unsigned i = 0; i < g->size(); ++i) { + m_context.assert_expr(m.mk_iff(new_clause(), g->form(i))); + } + } + + void prune_clauses() { + for (unsigned i = 0; i < m_clause_count; ++i) { + prune_clause(i); + } + } + + void prune_clause(unsigned i) { + m_context.push(); + for (unsigned j = 0; j < m_clause_count; ++j) { + if (i == j) { + m_context.assert_expr(m.mk_not(m_clauses[j].get())); + } + else if (!m_is_deleted.get(j)) { + m_context.assert_expr(m_clauses[j].get()); + } + } + m_context.push(); // force propagation + bool is_unsat = m_context.inconsistent(); + m_context.pop(2); + if (is_unsat) { + TRACE("unit_subsumption_tactic", tout << "Removing clause " << i << "\n";); + m_is_deleted.set(i, true); + m_deleted.push_back(i); + } + } + + void insert_result(goal_ref& result) { + for (unsigned i = 0; i < m_deleted.size(); ++i) { + result->update(m_deleted[i], m.mk_true()); // TBD proof? + } + } + + void init(goal_ref const& g) { + m_clause_count = 0; + m_is_deleted.reset(); + m_is_deleted.resize(g->size()); + m_deleted.reset(); + } + + expr* new_bool(unsigned& count, expr_ref_vector& v, char const* name) { + SASSERT(count <= v.size()); + if (count == v.size()) { + v.push_back(m.mk_fresh_const(name, m.mk_bool_sort())); + } + return v[count++].get(); + } + + expr* new_clause() { + return new_bool(m_clause_count, m_clauses, "#clause"); + } + + +}; + +tactic * mk_unit_subsumption_tactic(ast_manager & m, params_ref const & p) { + return alloc(unit_subsumption_tactic, m, p); +} + diff --git a/lib/unit_subsumption_tactic.h b/lib/unit_subsumption_tactic.h new file mode 100644 index 000000000..df5968113 --- /dev/null +++ b/lib/unit_subsumption_tactic.h @@ -0,0 +1,30 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + unit_subsumption_tactic.h + +Abstract: + + Simplify goal using subsumption based on unit propagation. + +Author: + + Nikolaj Bjorner (nbjorner) 2012-9-6 + +Notes: + + Background: PDR generates several clauses that subsume each-other. + Simplify a goal assuming it is a conjunction of clauses. + Subsumed clauses are simplified by using unit-propagation + It uses the smt_context for the solver. + +--*/ +#ifndef _UNIT_SUBSUMPTION_TACTIC_H_ +#define _UNIT_SUBSUMPTION_TACTIC_H_ +#include "tactic.h" + +tactic * mk_unit_subsumption_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif diff --git a/lib/upolynomial.cpp b/lib/upolynomial.cpp new file mode 100644 index 000000000..e2bf322d6 --- /dev/null +++ b/lib/upolynomial.cpp @@ -0,0 +1,3131 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + upolynomial.cpp + +Abstract: + + Goodies for creating and handling univariate polynomials. + + A dense representation is much better for Root isolation algorithms, + encoding algebraic numbers, factorization, etc. + + We also use integers as the coefficients of univariate polynomials. + +Author: + + Leonardo (leonardo) 2011-11-29 + +Notes: + +--*/ +#include"upolynomial.h" +#include"upolynomial_factorization.h" +#include"polynomial_primes.h" +#include"buffer.h" +#include"cooperate.h" + +namespace upolynomial { + + core_manager::factors::factors(core_manager & upm): + m_upm(upm), + m_total_factors(0), + m_total_degree(0) { + nm().set(m_constant, 1); + } + + core_manager::factors::~factors() { + clear(); + nm().del(m_constant); + } + + void core_manager::factors::clear() { + for (unsigned i = 0; i < m_factors.size(); ++ i) { + m_upm.reset(m_factors[i]); + } + m_factors.reset(); + m_degrees.reset(); + nm().set(m_constant, 1); + m_total_factors = 0; + m_total_degree = 0; + } + + void core_manager::factors::push_back(numeral_vector const & p, unsigned degree) { + SASSERT(degree > 0); + m_factors.push_back(numeral_vector()); + m_degrees.push_back(degree); + m_upm.set(p.size(), p.c_ptr(), m_factors.back()); + m_total_factors += degree; + m_total_degree += m_upm.degree(p)*degree; + } + + void core_manager::factors::push_back_swap(numeral_vector & p, unsigned degree) { + SASSERT(degree > 0); + m_factors.push_back(numeral_vector()); + m_degrees.push_back(degree); + p.swap(m_factors.back()); + m_total_factors += degree; + m_total_degree += m_upm.degree(p)*degree; + } + + void core_manager::factors::multiply(numeral_vector & out) const { + // set the first one to be just the constant + m_upm.reset(out); + if (nm().is_zero(m_constant)) { + SASSERT(m_factors.empty()); + return; + } + out.push_back(numeral()); + nm().set(out.back(), m_constant); + + // now multiply them all in + for (unsigned i = 0; i < m_factors.size(); ++ i) { + if (m_degrees[i] > 1) { + numeral_vector power; + m_upm.pw(m_factors[i].size(), m_factors[i].c_ptr(), m_degrees[i], power); + m_upm.mul(out.size(), out.c_ptr(), power.size(), power.c_ptr(), out); + m_upm.reset(power); + } else { + m_upm.mul(out.size(), out.c_ptr(), m_factors[i].size(), m_factors[i].c_ptr(), out); + } + } + } + + void core_manager::factors::display(std::ostream & out) const { + out << nm().to_string(m_constant); + if (m_factors.size() > 0) { + for (unsigned i = 0; i < m_factors.size(); ++ i) { + out << " * ("; + m_upm.display(out, m_factors[i]); + out << ")^" << m_degrees[i]; + } + } + } + + numeral_manager & core_manager::factors::nm() const { + return m_upm.m(); + } + + void core_manager::factors::set_constant(numeral const & constant) { + nm().set(m_constant, constant); + } + + void core_manager::factors::set_degree(unsigned i, unsigned degree) { + m_total_degree -= m_upm.degree(m_factors[i])*m_degrees[i]; + m_total_factors -= m_degrees[i]; + m_degrees[i] = degree; + m_total_factors += degree; + m_total_degree += m_upm.degree(m_factors[i])*degree; + } + + void core_manager::factors::swap_factor(unsigned i, numeral_vector & p) { + m_total_degree -= m_upm.degree(m_factors[i])*m_degrees[i]; + m_total_degree += m_upm.degree(p)*m_degrees[i]; + m_factors[i].swap(p); + } + + void core_manager::factors::swap(factors & other) { + m_factors.swap(other.m_factors); + m_degrees.swap(other.m_degrees); + nm().swap(m_constant, other.m_constant); + std::swap(m_total_factors, other.m_total_factors); + std::swap(m_total_degree, other.m_total_degree); + } + + core_manager::core_manager(unsynch_mpz_manager & m): + m_manager(m) { + m_cancel = false; + } + + core_manager::~core_manager() { + reset(m_basic_tmp); + reset(m_div_tmp1); + reset(m_div_tmp2); + reset(m_exact_div_tmp); + reset(m_gcd_tmp1); + reset(m_gcd_tmp2); + reset(m_CRA_tmp); + for (unsigned i = 0; i < UPOLYNOMIAL_MGCD_TMPS; i++) reset(m_mgcd_tmp[i]); + reset(m_sqf_tmp1); + reset(m_sqf_tmp2); + reset(m_pw_tmp); + } + + void core_manager::set_cancel(bool f) { + m_cancel = f; + } + + void core_manager::checkpoint() { + if (m_cancel) + throw upolynomial_exception("canceled"); + cooperate("upolynomial"); + } + + // Eliminate leading zeros from buffer. + void core_manager::trim(numeral_vector & buffer) { + unsigned sz = buffer.size(); + while (sz > 0 && m().is_zero(buffer[sz - 1])) { + m().del(buffer[sz - 1]); // 0 may be a big number when using GMP. + sz--; + } + buffer.shrink(sz); + } + + // Remove old entries from buffer [sz, buffer.size()), shrink size, and remove leading zeros. + // buffer must have at least size sz. + void core_manager::set_size(unsigned sz, numeral_vector & buffer) { + unsigned old_sz = buffer.size(); + SASSERT(old_sz >= sz); + // delete old entries + for (unsigned i = sz; i < old_sz; i++) { + m().del(buffer[i]); + } + buffer.shrink(sz); + trim(buffer); + } + + // Set size to zero. + void core_manager::reset(numeral_vector & buffer) { + set_size(0, buffer); + } + + // Copy elements from p to buffer. + void core_manager::set(unsigned sz, numeral const * p, numeral_vector & buffer) { + if (p != 0 && buffer.c_ptr() == p) { + SASSERT(buffer.size() == sz); + return; + } + buffer.reserve(sz); + for (unsigned i = 0; i < sz; i++) { + m().set(buffer[i], p[i]); + } + set_size(sz, buffer); + } + + void core_manager::set(unsigned sz, rational const * p, numeral_vector & buffer) { + buffer.reserve(sz); + for (unsigned i = 0; i < sz; i++) { + SASSERT(p[i].is_int()); + m().set(buffer[i], p[i].to_mpq().numerator()); + } + set_size(sz, buffer); + } + + void core_manager::get_primitive_and_content(unsigned f_sz, numeral const * f, numeral_vector & pp, numeral & cont) { + SASSERT(f_sz > 0); + m().gcd(f_sz, f, cont); + SASSERT(m().is_pos(cont)); + if (m().is_one(cont)) { + set(f_sz, f, pp); + } + else { + pp.reserve(f_sz); + for (unsigned i = 0; i < f_sz; i++) { + if (!m().is_zero(f[i])) { + m().div(f[i], cont, pp[i]); + } + else { + m().set(pp[i], 0); + } + } + set_size(f_sz, pp); + } + } + + // Negate coefficients of p. + void core_manager::neg(unsigned sz, numeral * p) { + for (unsigned i = 0; i < sz; i++) { + m().neg(p[i]); + } + } + + // buffer := -p + void core_manager::neg_core(unsigned sz, numeral const * p, numeral_vector & buffer) { + SASSERT(!is_alias(p, buffer)); + buffer.reserve(sz); + for (unsigned i = 0; i < sz; i++) { + m().set(buffer[i], p[i]); + m().neg(buffer[i]); + } + set_size(sz, buffer); + } + + void core_manager::neg(unsigned sz, numeral const * p, numeral_vector & buffer) { + neg_core(sz, p, m_basic_tmp); + buffer.swap(m_basic_tmp); + } + + // buffer := p1 + p2 + void core_manager::add_core(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & buffer) { + SASSERT(!is_alias(p1, buffer)); + SASSERT(!is_alias(p2, buffer)); + unsigned min_sz = std::min(sz1, sz2); + unsigned max_sz = std::max(sz1, sz2); + unsigned i = 0; + buffer.reserve(max_sz); + for (; i < min_sz; i++) { + m().add(p1[i], p2[i], buffer[i]); + } + for (; i < sz1; i++) { + m().set(buffer[i], p1[i]); + } + for (; i < sz2; i++) { + m().set(buffer[i], p2[i]); + } + set_size(max_sz, buffer); + } + + void core_manager::add(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & buffer) { + add_core(sz1, p1, sz2, p2, m_basic_tmp); + buffer.swap(m_basic_tmp); + } + + // buffer := p1 - p2 + void core_manager::sub_core(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & buffer) { + SASSERT(!is_alias(p1, buffer)); + SASSERT(!is_alias(p2, buffer)); + unsigned min_sz = std::min(sz1, sz2); + unsigned max_sz = std::max(sz1, sz2); + unsigned i = 0; + buffer.reserve(max_sz); + for (; i < min_sz; i++) { + m().sub(p1[i], p2[i], buffer[i]); + } + for (; i < sz1; i++) { + m().set(buffer[i], p1[i]); + } + for (; i < sz2; i++) { + m().set(buffer[i], p2[i]); + m().neg(buffer[i]); + } + + set_size(max_sz, buffer); + } + + void core_manager::sub(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & buffer) { + sub_core(sz1, p1, sz2, p2, m_basic_tmp); + buffer.swap(m_basic_tmp); + } + + // buffer := p1 * p2 + void core_manager::mul_core(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & buffer) { + SASSERT(!is_alias(p1, buffer)); + SASSERT(!is_alias(p2, buffer)); + if (sz1 == 0) { + reset(buffer); + } + else if (sz2 == 0) { + reset(buffer); + } + else { + unsigned new_sz = sz1 + sz2 - 1; + buffer.reserve(new_sz); + for (unsigned i = 0; i < new_sz; i++) { + m().reset(buffer[i]); + } + if (sz1 < sz2) { + std::swap(sz1, sz2); + std::swap(p1, p2); + } + for (unsigned i = 0; i < sz1; i++) { + checkpoint(); + numeral const & a_i = p1[i]; + if (m().is_zero(a_i)) + continue; + for (unsigned j = 0; j < sz2; j++) { + numeral const & b_j = p2[j]; + if (m().is_zero(b_j)) + continue; + m().addmul(buffer[i+j], a_i, b_j, buffer[i+j]); + } + } + set_size(new_sz, buffer); + } + } + + void core_manager::mul(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & buffer) { + mul_core(sz1, p1, sz2, p2, m_basic_tmp); + buffer.swap(m_basic_tmp); + } + + // buffer := dp/dx + void core_manager::derivative(unsigned sz, numeral const * p, numeral_vector & buffer) { + if (sz <= 1) { + reset(buffer); + return; + } + buffer.reserve(sz - 1); + for (unsigned i = 1; i < sz; i++) { + numeral d; + m().set(d, i); + m().mul(p[i], d, buffer[i-1]); + } + set_size(sz-1, buffer); + } + + // Divide coeffients of p by their GCD + void core_manager::normalize(unsigned sz, numeral * p) { + if (sz == 0) + return; + if (sz == 1) { + if (m().is_pos(p[0])) + m().set(p[0], 1); + else + m().set(p[0], -1); + return; + } + scoped_numeral g(m()); + m().gcd(sz, p, g); + if (m().is_one(g)) + return; + for (unsigned i = 0; i < sz; i++) { +#ifdef Z3DEBUG + scoped_numeral old_p_i(m()); + old_p_i = p[i]; +#endif + // Actual code + m().div(p[i], g, p[i]); + +#ifdef Z3DEBUG + scoped_numeral tmp(m()); + m().mul(g, p[i], tmp); + CTRACE("div_bug", !m().eq(tmp, old_p_i), tout << "old(p[i]): " << m().to_string(old_p_i) << ", g: " << m().to_string(g) << ", p[i]: " << + m().to_string(p[i]) << ", tmp: " << m().to_string(tmp) << "\n";); + SASSERT(tmp == old_p_i); +#endif + } + } + + // Divide coeffients of p by their GCD + void core_manager::normalize(numeral_vector & p) { + normalize(p.size(), p.c_ptr()); + } + + void core_manager::div(unsigned sz, numeral * p, numeral const & b) { + SASSERT(!m().is_zero(b)); + if (m().is_one(b)) + return; + for (unsigned i = 0; i < sz; i++) { + CTRACE("upolynomial", !m().divides(b, p[i]), tout << "b: " << m().to_string(b) << ", p[i]: " << m().to_string(p[i]) << "\n";); + SASSERT(m().divides(b, p[i])); + m().div(p[i], b, p[i]); + } + } + + void core_manager::mul(unsigned sz, numeral * p, numeral const & b) { + SASSERT(!m().is_zero(b)); + if (m().is_one(b)) + return; + for (unsigned i = 0; i < sz; i++) { + m().mul(p[i], b, p[i]); + } + } + + void core_manager::mul(numeral_vector & p, numeral const & b) { + if (m().is_zero(b)) { + reset(p); + return; + } + mul(p.size(), p.c_ptr(), b); + } + + // Pseudo division + void core_manager::div_rem_core(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, + unsigned & d, numeral_vector & q, numeral_vector & r) { + SASSERT(!is_alias(p1, q)); SASSERT(!is_alias(p2, q)); + SASSERT(!is_alias(p1, r)); SASSERT(!is_alias(p2, r)); + d = 0; + SASSERT(sz2 > 0); + if (sz2 == 1) { + set(sz1, p1, q); + if (field()) { + div(q, *p2); + } + reset(r); + return; + } + reset(q); + set(sz1, p1, r); + if (sz1 <= 1) + return; + unsigned qsz; + if (sz1 >= sz2) { + q.reserve(sz1 - sz2 + 1); + qsz = sz1 - sz2 + 1; + } + else { + qsz = 0; + } + numeral const & b_n = p2[sz2-1]; + SASSERT(!m().is_zero(b_n)); + scoped_numeral a_m(m()); + while (true) { + checkpoint(); + sz1 = r.size(); + if (sz1 < sz2) { + set_size(qsz, q); + return; + } + unsigned m_n = sz1 - sz2; // m-n + if (field()) { + numeral & ratio = a_m; + m().div(r[sz1 - 1], b_n, ratio); + m().add(q[m_n], ratio, q[m_n]); + for (unsigned i = 0; i < sz2 - 1; i++) { + m().submul(r[i + m_n], ratio, p2[i], r[i + m_n]); + } + } + else { + d++; + m().set(a_m, r[sz1 - 1]); + for (unsigned i = 0; i < sz1 - 1; i++) { + m().mul(r[i], b_n, r[i]); + } + for (unsigned i = 0; i < qsz; i++) { + m().mul(q[i], b_n, q[i]); + } + m().add(q[m_n], a_m, q[m_n]); + for (unsigned i = 0; i < sz2 - 1; i++) { + m().submul(r[i + m_n], a_m, p2[i], r[i + m_n]); + } + } + set_size(sz1 - 1, r); + } + } + + // Pseudo division + void core_manager::div_rem(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, unsigned & d, + numeral_vector & q, numeral_vector & r) { + numeral_vector & _r = m_div_tmp1; + numeral_vector & _q = m_div_tmp2; + div_rem_core(sz1, p1, sz2, p2, d, _q, _r); + r.swap(_r); + q.swap(_q); + } + + // Pseudo division + void core_manager::div(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & q) { + unsigned d; + numeral_vector & _r = m_div_tmp1; + numeral_vector & _q = m_div_tmp2; + div_rem_core(sz1, p1, sz2, p2, d, _q, _r); + reset(_r); + q.swap(_q); + } + + // Pseudo remainder + void core_manager::rem(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, unsigned & d, numeral_vector & buffer) { + SASSERT(!is_alias(p1, buffer)); SASSERT(!is_alias(p2, buffer)); + d = 0; + SASSERT(sz2 > 0); + if (sz2 == 1) { + reset(buffer); + return; + } + set(sz1, p1, buffer); + if (sz1 <= 1) + return; + numeral const & b_n = p2[sz2-1]; + SASSERT(!m().is_zero(b_n)); + scoped_numeral a_m(m()); + while (true) { + checkpoint(); + TRACE("rem_bug", tout << "rem loop, p2:\n"; display(tout, sz2, p2); tout << "\nbuffer:\n"; display(tout, buffer); tout << "\n";); + sz1 = buffer.size(); + if (sz1 < sz2) { + TRACE("rem_bug", tout << "finished\n";); + return; + } + unsigned m_n = sz1 - sz2; + if (field()) { + numeral & ratio = a_m; + m().div(buffer[sz1 - 1], b_n, ratio); + for (unsigned i = 0; i < sz2 - 1; i++) { + m().submul(buffer[i + m_n], ratio, p2[i], buffer[i + m_n]); + } + } + else { + // buffer: a_m * x^m + a_{m-1} * x^{m-1} + ... + a_0 + // p2: b_n * x^n + b_{n-1} * x^{n-1} + ... + b_0 + d++; + m().set(a_m, buffer[sz1 - 1]); + TRACE("rem_bug", tout << "a_m: " << m().to_string(a_m) << ", b_n: " << m().to_string(b_n) << "\n";); + // don't need to update position sz1 - 1, since it will become 0 + for (unsigned i = 0; i < sz1 - 1; i++) { + m().mul(buffer[i], b_n, buffer[i]); + } + // buffer: a_m * x^m + b_n * a_{m-1} * x^{m-1} + ... + b_n * a_0 + // don't need to process i = sz2 - 1, because buffer[sz1 - 1] will become 0. + for (unsigned i = 0; i < sz2 - 1; i++) { + m().submul(buffer[i + m_n], a_m, p2[i], buffer[i + m_n]); + } + } + set_size(sz1 - 1, buffer); + } + } + + // Signed pseudo remainder + void core_manager::srem(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & buffer) { + SASSERT(!is_alias(p1, buffer)); SASSERT(!is_alias(p2, buffer)); + unsigned d; + rem(sz1, p1, sz2, p2, d, buffer); + // We don't ned to flip the sign if d is odd and leading coefficient of p2 is negative + if (d % 2 == 0 || (sz2 > 0 && m().is_pos(p2[sz2-1]))) + neg(buffer.size(), buffer.c_ptr()); + } + + + // Exact division for univariate polynomials. + // Return false if p2 does not divide p1 + bool core_manager::divides(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2) { + if (sz2 == 0) + return false; + if (sz1 == 0) + return true; + if (sz2 > sz1 || !m().divides(p2[sz2-1], p1[sz1-1])) + return false; + scoped_numeral b(m()); + numeral_vector & _p1 = m_div_tmp1; + set(sz1, p1, _p1); + while (true) { + if (sz1 == 0) + return true; + if (sz2 > sz1 || !m().divides(p2[sz2-1], _p1[sz1-1])) + return false; + unsigned delta = sz1 - sz2; + m().div(_p1[sz1-1], p2[sz2-1], b); + for (unsigned i = 0; i < sz2 - 1; i++) { + if (!m().is_zero(p2[i])) + m().submul(_p1[i+delta], b, p2[i], _p1[i+delta]); + } + m().reset(_p1[sz1-1]); + trim(_p1); + sz1 = _p1.size(); + } + return true; + } + + // Exact division for univariate polynomials. + // Return false if p2 does not divide p1 + bool core_manager::exact_div(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & r) { + if (sz2 == 0) + return false; + if (sz1 == 0) { + reset(r); + return true; + } + if (sz2 > sz1 || !m().divides(p2[sz2-1], p1[sz1-1]) || !m().divides(p2[0], p1[0])) + return false; + numeral_vector & _r = m_exact_div_tmp; + reset(_r); + unsigned deg = sz1 - sz2; + _r.reserve(deg+1); + numeral_vector & _p1 = m_div_tmp1; + // std::cerr << "dividing with "; display(std::cerr, _p1); std::cerr << std::endl; + TRACE("factor_bug", tout << "sz1: " << sz1 << " p1: " << p1 << ", _p1.c_ptr(): " << _p1.c_ptr() << ", _p1.size(): " << _p1.size() << "\n";); + set(sz1, p1, _p1); + SASSERT(_p1.size() == sz1); + while (true) { + TRACE("upolynomial", tout << "exact_div loop...\n"; display(tout, _p1); tout << "\n"; display(tout, _r); tout << "\n";); + // std::cerr << "dividing with "; display(std::cerr, _p1); std::cerr << std::endl; + if (sz1 == 0) { + set_size(deg+1, _r); + r.swap(_r); + return true; + } + if (sz2 > sz1 || !m().divides(p2[sz2-1], _p1[sz1-1]) || !m().divides(p2[0], _p1[0])) { + reset(r); + return false; + } + unsigned delta = sz1 - sz2; + numeral & a_r = _r[delta]; + m().div(_p1[sz1-1], p2[sz2-1], a_r); + for (unsigned i = 0; i < sz2 - 1; i++) { + if (!m().is_zero(p2[i])) + m().submul(_p1[i+delta], a_r, p2[i], _p1[i+delta]); + } + m().reset(_p1[sz1-1]); + trim(_p1); + sz1 = _p1.size(); + } + return true; + } + + void core_manager::flip_sign_if_lm_neg(numeral_vector & buffer) { + unsigned sz = buffer.size(); + if (sz == 0) + return; + if (m().is_neg(buffer[sz - 1])) { + for (unsigned i = 0; i < sz; i++) + m().neg(buffer[i]); + } + } + + void core_manager::CRA_combine_images(numeral_vector const & C1, numeral const & b1, numeral_vector & C2, numeral & b2) { + SASSERT(!m().m().is_even(b1)); + SASSERT(!m().m().is_even(b2)); + numeral_vector & R = m_CRA_tmp; reset(R); + scoped_numeral inv1(m()); + scoped_numeral inv2(m()); + scoped_numeral g(m()); + m().gcd(b1, b2, inv1, inv2, g); + SASSERT(m().is_one(g)); + // b1*inv1 + b2.inv2 = 1 + // inv1 is the inverse of b1 mod b2 + // inv2 is the inverse of b2 mod b1 + m().m().mod(inv1, b2, inv1); + m().m().mod(inv2, b1, inv2); + TRACE("CRA", tout << "inv1: " << inv1 << ", inv2: " << inv2 << "\n";); + scoped_numeral a1(m()); + scoped_numeral a2(m()); + m().mul(b2, inv2, a1); // a1 is the multiplicator for coefficients of C1 + m().mul(b1, inv1, a2); // a2 is the multiplicator for coefficients of C2 + TRACE("CRA", tout << "a1: " << a1 << ", a2: " << a2 << "\n";); + // new bound + scoped_numeral new_bound(m()); + m().mul(b1, b2, new_bound); + scoped_numeral lower(m()); + scoped_numeral upper(m()); + scoped_numeral new_a(m()), tmp1(m()), tmp2(m()), tmp3(m()); + m().div(new_bound, 2, upper); + m().set(lower, upper); + m().neg(lower); + TRACE("CRA", tout << "lower: " << lower << ", upper: " << upper << "\n";); + + #define ADD(A1, A2) { \ + m().mul(A1, a1, tmp1); \ + m().mul(A2, a2, tmp2); \ + m().add(tmp1, tmp2, tmp3); \ + m().m().mod(tmp3, new_bound, new_a); \ + if (m().gt(new_a, upper)) \ + m().sub(new_a, new_bound, new_a); \ + R.push_back(numeral()); \ + m().set(R.back(), new_a); \ + } + + numeral zero(0); + unsigned i = 0; + unsigned sz1 = C1.size(); + unsigned sz2 = C2.size(); + unsigned sz = std::min(sz1, sz2); + for (; i < sz; i++) { + ADD(C1[i], C2[i]); + } + for (; i < sz1; i++) { + ADD(C1[i], zero); + } + for (; i < sz2; i++) { + ADD(zero, C2[i]); + } + m().set(b2, new_bound); + R.swap(C2); + } + + void core_manager::mod_gcd(unsigned sz_u, numeral const * u, + unsigned sz_v, numeral const * v, + numeral_vector & result) { + TRACE("mgcd", tout << "u: "; display_star(tout, sz_u, u); tout << "\nv: "; display_star(tout, sz_v, v); tout << "\n";); + SASSERT(sz_u > 0 && sz_v > 0); + SASSERT(!m().modular()); + scoped_numeral c_u(m()), c_v(m()); + numeral_vector & pp_u = m_mgcd_tmp[0]; + numeral_vector & pp_v = m_mgcd_tmp[1]; + get_primitive_and_content(sz_u, u, pp_u, c_u); + get_primitive_and_content(sz_v, v, pp_v, c_v); + scoped_numeral c_g(m()); + m().gcd(c_u, c_v, c_g); + unsigned d_u = sz_u - 1; + unsigned d_v = sz_v - 1; + numeral const & lc_u = pp_u[d_u]; + numeral const & lc_v = pp_v[d_v]; + scoped_numeral lc_g(m()); + m().gcd(lc_u, lc_v, lc_g); + scoped_numeral p(m()); + scoped_numeral bound(m()); + + numeral_vector & u_Zp = m_mgcd_tmp[2]; + numeral_vector & v_Zp = m_mgcd_tmp[3]; + numeral_vector & q = m_mgcd_tmp[4]; + numeral_vector & C = m_mgcd_tmp[5]; + + for (unsigned i = 0; i < NUM_BIG_PRIMES; i++) { + m().set(p, polynomial::g_big_primes[i]); + TRACE("mgcd", tout << "trying prime: " << p << "\n";); + { + scoped_set_zp setZp(*this, p); + set(pp_u.size(), pp_u.c_ptr(), u_Zp); + set(pp_v.size(), pp_v.c_ptr(), v_Zp); + if (degree(u_Zp) < d_u) { + TRACE("mgcd", tout << "bad prime, leading coefficient vanished\n";); + continue; // bad prime + } + if (degree(v_Zp) < d_v) { + TRACE("mgcd", tout << "bad prime, leading coefficient vanished\n";); + continue; // bad prime + } + euclid_gcd(u_Zp.size(), u_Zp.c_ptr(), v_Zp.size(), v_Zp.c_ptr(), q); + // normalize so that lc_g is leading coefficient of q + mk_monic(q.size(), q.c_ptr()); + scoped_numeral c(m()); + m().set(c, lc_g); + mul(q, c); + } + TRACE("mgcd", tout << "new q:\n"; display_star(tout, q); tout << "\n";); + if (is_const(q)) { + TRACE("mgcd", tout << "done, modular gcd is one\n";); + reset(result); + result.push_back(numeral()); + m().set(result.back(), c_g); + return; + } + if (i == 0) { + set(q.size(), q.c_ptr(), C); + m().set(bound, p); + } + else { + if (q.size() < C.size()) { + // discard accumulated image, it was affected by unlucky primes + TRACE("mgcd", tout << "discarding image\n";); + set(q.size(), q.c_ptr(), C); + m().set(bound, p); + } + else { + CRA_combine_images(q, p, C, bound); + TRACE("mgcd", tout << "new combined:\n"; display_star(tout, C); tout << "\n";); + } + } + numeral_vector & candidate = q; + get_primitive(C, candidate); + TRACE("mgcd", tout << "candidate:\n"; display_star(tout, candidate); tout << "\n";); + SASSERT(candidate.size() > 0); + numeral const & lc_candidate = candidate[candidate.size() - 1]; + if (m().divides(lc_candidate, lc_g) && + divides(pp_u, candidate) && + divides(pp_v, candidate)) { + TRACE("mgcd", tout << "found GCD\n";); + mul(candidate, c_g); + flip_sign_if_lm_neg(candidate); + candidate.swap(result); + TRACE("mgcd", tout << "r: "; display_star(tout, result); tout << "\n";); + return; + } + } + // Oops, modular GCD failed, not enough primes + // fallback to euclid_gcd + euclid_gcd(sz_u, u, sz_v, v, result); + } + + void core_manager::euclid_gcd(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & buffer) { + SASSERT(!is_alias(p1, buffer)); SASSERT(!is_alias(p2, buffer)); + if (sz1 == 0) { + set(sz2, p2, buffer); + flip_sign_if_lm_neg(buffer); + return; + } + if (sz2 == 0) { + set(sz1, p1, buffer); + flip_sign_if_lm_neg(buffer); + return; + } + bool is_field = field(); + numeral_vector & A = m_gcd_tmp1; + numeral_vector & B = m_gcd_tmp2; + numeral_vector & R = buffer; + set(sz1, p1, A); + set(sz2, p2, B); + TRACE("upolynomial", tout << "sz1: " << sz1 << ", p1: " << p1 << ", sz2: " << sz2 << ", p2: " << p2 << "\nB.size(): " << B.size() << + ", B.c_ptr(): " << B.c_ptr() << "\n";); + while (true) { + TRACE("upolynomial", tout << "A: "; display(tout, A); tout <<"\nB: "; display(tout, B); tout << "\n";); + if (B.empty()) { + normalize(A); + buffer.swap(A); + // to be consistent, if in a field, we make gcd monic + if (is_field) { + mk_monic(buffer.size(), buffer.c_ptr()); + } + else { + flip_sign_if_lm_neg(buffer); + } + TRACE("upolynomial", tout << "GCD\n"; display(tout, sz1, p1); tout << "\n"; display(tout, sz2, p2); tout << "\n--->\n"; + display(tout, buffer); tout << "\n";); + return; + } + rem(A.size(), A.c_ptr(), B.size(), B.c_ptr(), R); + normalize(R); + A.swap(B); + B.swap(R); + } + } + + void core_manager::gcd(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & buffer) { + SASSERT(!is_alias(p1, buffer)); SASSERT(!is_alias(p2, buffer)); + if (sz1 == 0) { + set(sz2, p2, buffer); + flip_sign_if_lm_neg(buffer); + return; + } + if (sz2 == 0) { + set(sz1, p1, buffer); + flip_sign_if_lm_neg(buffer); + return; + } + if (!modular()) + mod_gcd(sz1, p1, sz2, p2, buffer); + else + euclid_gcd(sz1, p1, sz2, p2, buffer); + } + + void core_manager::subresultant_gcd(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & buffer) { + SASSERT(!is_alias(p1, buffer)); SASSERT(!is_alias(p2, buffer)); + if (sz1 == 0) { + set(sz2, p2, buffer); + flip_sign_if_lm_neg(buffer); + return; + } + if (sz2 == 0) { + set(sz1, p1, buffer); + flip_sign_if_lm_neg(buffer); + return; + } + numeral_vector & A = m_gcd_tmp1; + numeral_vector & B = m_gcd_tmp2; + numeral_vector & R = buffer; + scoped_numeral g(m()); + scoped_numeral h(m()); + scoped_numeral aux(m()); + m().set(g, 1); + m().set(h, 1); + unsigned d; + set(sz1, p1, A); + set(sz2, p2, B); + if (A.size() < B.size()) + A.swap(B); + while (true) { + SASSERT(A.size() >= B.size()); + TRACE("upolynomial", tout << "A: "; display(tout, A); tout <<"\nB: "; display(tout, B); tout << "\n"; + tout << "g: " << m().to_string(g) << ", h: " << m().to_string(h) << "\n";); + if (B.empty()) { + normalize(A); + buffer.swap(A); + // to be consistent, if in a field, we make gcd monic + if (field()) { + mk_monic(buffer.size(), buffer.c_ptr()); + } + else { + flip_sign_if_lm_neg(buffer); + } + TRACE("upolynomial", tout << "subresultant GCD\n"; display(tout, sz1, p1); tout << "\n"; display(tout, sz2, p2); tout << "\n--->\n"; + display(tout, buffer); tout << "\n";); + return; + } + rem(A.size(), A.c_ptr(), B.size(), B.c_ptr(), d, R); + unsigned pseudo_div_d = A.size() - B.size(); + if (d < pseudo_div_d + 1) { + // I used a standard subresultant implementation. + // TODO: investigate how to avoid the following adjustment. + // + // adjust R to make sure the following equation holds: + // l(B)^{deg(A) - deg(B) + 1) * A = Q * B + R + m().power(B[B.size() - 1], pseudo_div_d + 1 - d, aux); + mul(R, aux); + } + d = pseudo_div_d; + TRACE("upolynomial", tout << "R: "; display(tout, R); tout << "\nd: " << d << "\n";); + // aux <- g*h^d + m().power(h, d, aux); + m().mul(g, aux, aux); + div(R, aux); + A.swap(B); + B.swap(R); + // g <- l(A) + m().set(g, A[A.size() - 1]); + m().power(g, d, aux); + // h <- h^{1-d} * g^d + if (d == 0) { + // h <- h^1 * g^0 + // do nothing + } + else if (d == 1) { + // h <- h^0 * g^1 + m().set(h, g); + } + else { + // h <- (g^d)/(h^{d-1}) + SASSERT(d > 1); + d--; + m().power(h, d, h); + SASSERT(m().divides(h, aux)); + m().div(aux, h, h); + } + } + } + + // buffer := square-free(p) + void core_manager::square_free(unsigned sz, numeral const * p, numeral_vector & buffer) { + SASSERT(!is_alias(p, buffer)); + if (sz <= 1) { + set(sz, p, buffer); + return; + } + numeral_vector & p_prime = m_sqf_tmp1; + numeral_vector & g = m_sqf_tmp2; + derivative(sz, p, p_prime); + gcd(sz, p, p_prime.size(), p_prime.c_ptr(), g); + // subresultant_gcd(sz, p, p_prime.size(), p_prime.c_ptr(), g); + if (g.size() <= 1) { + set(sz, p, buffer); + } + else { + div(sz, p, g.size(), g.c_ptr(), buffer); + normalize(buffer); + } + } + + bool core_manager::is_square_free(unsigned sz, numeral const * p) { + if (sz <= 1) { + return true; + } + numeral_vector & p_prime = m_sqf_tmp1; + numeral_vector & g = m_sqf_tmp2; + derivative(sz, p, p_prime); + gcd(sz, p, p_prime.size(), p_prime.c_ptr(), g); + return g.size() <= 1; + } + + void core_manager::pw(unsigned sz, numeral const * p, unsigned k, numeral_vector & r) { + if (k == 0) { + SASSERT(sz != 0); + r.reserve(1); + m().set(r[0], 1); + set_size(1, r); + return; + } + + if (k == 1 || sz == 0 || (sz == 1 && m().is_one(p[0]))) { + set(sz, p, r); + return; + } + + numeral_vector & result = m_pw_tmp; + set(sz, p, result); + for (unsigned i = 1; i < k; i++) + mul(m_pw_tmp.size(), m_pw_tmp.c_ptr(), sz, p, m_pw_tmp); + r.swap(result); +#if 0 + unsigned mask = 1; + numeral_vector & p2 = m_pw_tmp; + set(sz, p, p2); + reset(r); + r.push_back(numeral(1)); + while (mask <= k) { + if (mask & k) { + // r := r * p2 + mul(r.size(), r.c_ptr(), p2.size(), p2.c_ptr(), r); + } + mul(p2.size(), p2.c_ptr(), p2.size(), p2.c_ptr(), p2); + mask = mask << 1; + } +#endif + } + + void core_manager::mk_monic(unsigned sz, numeral * p, numeral & lc, numeral & lc_inv) { + m().set(lc, 1); + m().set(lc_inv, 1); + if (sz > 0 && !m().is_one(p[sz-1])) { + int d = sz-1; + m().swap(lc, p[d--]); + m().set(lc_inv, lc); + m().inv(lc_inv); + for (; d >= 0; -- d) { + m().mul(p[d], lc_inv, p[d]); + + } + } + } + + // Extended GCD + void core_manager::ext_gcd(unsigned szA, numeral const * A, unsigned szB, numeral const * B, + numeral_vector & U, numeral_vector & V, numeral_vector & D) { + SASSERT(!is_alias(A, U)); SASSERT(!is_alias(A, V)); SASSERT(!is_alias(A, D)); + SASSERT(!is_alias(B, U)); SASSERT(!is_alias(B, V)); SASSERT(!is_alias(B, D)); + + SASSERT(field()); + scoped_numeral_vector V1(m()), V3(m()), Q(m()), R(m()), T(m()), V1Q(m()); + + // since we are in a field define gcd(A, B) to be monic + // if AU + BV = D and D is not monic we make it monic, and then divide U and V by the same inverse + + // initialization + // U <- 1 + reset(U); U.push_back(numeral()); m().set(U.back(), 1); + // D <- A + set(szA, A, D); + mk_monic(szA, D.c_ptr()); + // V1 <- 0 + reset(V1); + // V3 <- B + set(szB, B, V3); + + while (true) { + if (V3.empty()) { + // V3 is the zero polynomial + numeral_vector & AU = V1; + numeral_vector & D_AU = V3; + // V <- (D - AU)/B + mul(szA, A, U.size(), U.c_ptr(), AU); + sub(D.size(), D.c_ptr(), AU.size(), AU.c_ptr(), D_AU); + div(D_AU.size(), D_AU.c_ptr(), szB, B, V); + DEBUG_CODE({ + scoped_numeral_vector BV(m()); + scoped_numeral_vector expected_D(m()); + mul(szB, B, V.size(), V.c_ptr(), BV); + add(AU.size(), AU.c_ptr(), BV.size(), BV.c_ptr(), expected_D); + SASSERT(eq(expected_D, D)); + }); + // if D is not monic, make it monic + scoped_numeral lc_inv(m()), lc(m()); + mk_monic(D.size(), D.c_ptr(), lc, lc_inv); + mul(U, lc_inv); + mul(V, lc_inv); + return; + } + + // D = QV3 + R + div_rem(D.size(), D.c_ptr(), V3.size(), V3.c_ptr(), Q, R); + + // T <- U - V1Q + mul(V1.size(), V1.c_ptr(), Q.size(), Q.c_ptr(), V1Q); + sub(U.size(), U.c_ptr(), V1Q.size(), V1Q.c_ptr(), T); + // U <- V1 + U.swap(V1); + // D <- V3 + D.swap(V3); + // V1 <- T + V1.swap(T); + // V3 <- R + V3.swap(R); + } + } + + // Display p + void core_manager::display(std::ostream & out, unsigned sz, numeral const * p, char const * var_name, bool use_star) const { + bool displayed = false; + unsigned i = sz; + scoped_numeral a(m()); + while (i > 0) { + --i; + if (!m().is_zero(p[i])) { + m().set(a, p[i]); + if (displayed) { + m().abs(a); + if (m().is_pos(p[i])) + out << " + "; + else + out << " - "; + } + displayed = true; + if (i == 0) { + out << m().to_string(a); + } + else { + SASSERT(i > 0); + if (!m().is_one(a)) { + out << m().to_string(a); + if (use_star) + out << "*"; + else + out << " "; + } + out << var_name; + if (i > 1) + out << "^" << i; + } + } + } + if (!displayed) + out << "0"; + } + + static void display_smt2_mumeral(std::ostream & out, numeral_manager & m, mpz const & n) { + if (m.is_neg(n)) { + out << "(- "; + mpz abs_n; + m.set(abs_n, n); + m.neg(abs_n); + m.display(out, abs_n); + m.del(abs_n); + out << ")"; + } + else { + m.display(out, n); + } + } + + static void display_smt2_monomial(std::ostream & out, numeral_manager & m, mpz const & n, + unsigned k, char const * var_name) { + if (k == 0) { + display_smt2_mumeral(out, m, n); + } + else if (m.is_one(n)) { + if (k == 1) + out << var_name; + else + out << "(^ " << var_name << " " << k << ")"; + } + else { + out << "(* "; + display_smt2_mumeral(out, m, n); + out << " "; + if (k == 1) + out << var_name; + else + out << "(^ " << var_name << " " << k << ")"; + out << ")"; + } + } + + // Display p as an s-expression + void core_manager::display_smt2(std::ostream & out, unsigned sz, numeral const * p, char const * var_name) const { + if (sz == 0) { + out << "0"; + return; + } + + if (sz == 1) { + display_smt2_mumeral(out, m(), p[0]); + return; + } + + unsigned non_zero_idx = UINT_MAX; + unsigned num_non_zeros = 0; + for (unsigned i = 0; i < sz; i++) { + if (m().is_zero(p[i])) + continue; + non_zero_idx = i; + num_non_zeros ++; + } + + if (num_non_zeros == 1) { + SASSERT(non_zero_idx != UINT_MAX && non_zero_idx >= 1); + display_smt2_monomial(out, m(), p[non_zero_idx], non_zero_idx, var_name); + } + + out << "(+"; + unsigned i = sz; + while (i > 0) { + --i; + if (!m().is_zero(p[i])) { + out << " "; + display_smt2_monomial(out, m(), p[i], i, var_name); + } + } + out << ")"; + } + + bool core_manager::eq(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2) { + if (sz1 != sz2) + return false; + for (unsigned i = 0; i < sz1; i++) { + if (!m().eq(p1[i], p2[i])) + return false; + } + return true; + } + + void upolynomial_sequence::push(unsigned sz, numeral * p) { + m_begins.push_back(m_seq_coeffs.size()); + m_szs.push_back(sz); + for (unsigned i = 0; i < sz; i++) { + m_seq_coeffs.push_back(numeral()); + swap(m_seq_coeffs.back(), p[i]); + } + } + + void upolynomial_sequence::push(numeral_manager & m, unsigned sz, numeral const * p) { + m_begins.push_back(m_seq_coeffs.size()); + m_szs.push_back(sz); + for (unsigned i = 0; i < sz; i++) { + m_seq_coeffs.push_back(numeral()); + m.set(m_seq_coeffs.back(), p[i]); + } + } + + scoped_numeral_vector::scoped_numeral_vector(manager & m): + _scoped_numeral_vector(m.m()) { + } + + scoped_upolynomial_sequence::~scoped_upolynomial_sequence() { + m_manager.reset(*this); + } + + manager::~manager() { + reset(m_db_tmp); + reset(m_dbab_tmp1); + reset(m_dbab_tmp2); + reset(m_tr_tmp); + reset(m_push_tmp); + } + + void manager::reset(upolynomial_sequence & seq) { + reset(seq.m_seq_coeffs); + seq.m_szs.reset(); + seq.m_begins.reset(); + } + + // Remove zero roots from p + void manager::remove_zero_roots(unsigned sz, numeral const * p, numeral_vector & buffer) { + SASSERT(sz > 0); + if (!m().is_zero(p[0])) { + // zero is not a root of p + set(sz, p, buffer); + return; + } + unsigned i = 0; + while (true) { + // p must not be the zero polynomial + SASSERT(i < sz); + if (!m().is_zero(p[i])) + break; + i++; + } + unsigned new_sz = sz - i; + buffer.reserve(new_sz); + for (unsigned j = 0; j < new_sz; j++) { + m().set(buffer[j], p[j + i]); + } + set_size(new_sz, buffer); + } + + // Evaluate the sign of p(1/2) + bool manager::has_one_half_root(unsigned sz, numeral const * p) { + // Actually, given b = c/2^k, we compute the sign of 2^n*p(1/2) + // Original Horner Sequence + // ((a_n * 1/2 + a_{n-1})*1/2 + a_{n-2})*1/2 + a_{n-3} ... + // Variation of the Horner Sequence for 2^n*p(1/2) + // a_n + a_{n-1}*2 + a_{n-2}*(2^2) + a_{n-3}*(2^3) ... + if (sz == 0) + return true; + if (sz == 1) + return false; + unsigned k = 1; + scoped_numeral r(m()); + scoped_numeral ak(m()); + m().set(r, p[sz-1]); + unsigned i = sz - 1; + while (i > 0) { + --i; + numeral const & a = p[i]; + m().mul2k(a, k, ak); + m().add(r, ak, r); + k++; + } + return m().is_zero(r); + } + + // Remove 1/2 root from p + void manager::remove_one_half_root(unsigned sz, numeral const * p, numeral_vector & buffer) { + SASSERT(has_one_half_root(sz, p)); + numeral two_x_1[2] = { numeral(-1), numeral(2) }; + div(sz, p, 2, two_x_1, buffer); + } + + int manager::sign_of(numeral const & c) { + if (m().is_zero(c)) + return 0; + if (m().is_pos(c)) + return 1; + else + return -1; + } + + // Return the number of sign changes in the coefficients of p + unsigned manager::sign_changes(unsigned sz, numeral const * p) { + unsigned r = 0; + int sign, prev_sign; + sign = 0; + prev_sign = 0; + unsigned i = 0; + for (; i < sz; i++) { + sign = sign_of(p[i]); + if (sign == 0) + continue; + if (sign != prev_sign && prev_sign != 0) + r++; + prev_sign = sign; + } + return r; + } + + + // Return the descartes bound for (0, +oo) + unsigned manager::descartes_bound(unsigned sz, numeral const * p) { + return sign_changes(sz, p); + } + + // Return the descartes bound for the number of roots in the interval (0, 1) + unsigned manager::descartes_bound_0_1(unsigned sz, numeral const * p) { + if (sz <= 1) + return 0; + numeral_vector & Q = m_db_tmp; + set(sz, p, Q); +#if 0 + // slow version + unsigned n = Q.size() - 1; + unsigned i; + for (unsigned i = 1; i <= n; i++) { + for (unsigned k = i; k >= 1; k--) { + m().add(Q[k], Q[k-1], Q[k]); + } + } + return sign_changes(Q.size(), Q.c_ptr()); +#endif + int prev_sign = 0; + unsigned num_vars = 0; + // a0 a1 a2 a3 + // a0 a0+a1 a0+a1+a2 a0+a1+a2+a3 + // a0 2a0+a1 3a0+2a1+a2 + // a0 3a0+a1 + // a0 + for (unsigned i = 0; i < sz; i++) { + checkpoint(); + unsigned k; + for (k = 1; k < sz - i; k++) { + m().add(Q[k], Q[k-1], Q[k]); + } + int sign = sign_of(Q[k-1]); + if (sign == 0) + continue; + if (sign != prev_sign && prev_sign != 0) { + num_vars++; + if (num_vars > 1) + return num_vars; + } + prev_sign = sign; + } + return num_vars; + } + + // Return the descartes bound for the number of roots in the interval (a, b) + unsigned manager::descartes_bound_a_b(unsigned sz, numeral const * p, mpbq_manager & bqm, mpbq const & a, mpbq const & b) { + if (bqm.is_nonneg(a)) { + // Basic idea: apply descartes_bound_0_1 to p2(x) where + // p1(x) = p(x+a) + // p2(x) = p1((b-a)*x) + TRACE("upolynomial", tout << "pos interval... " << bqm.to_string(a) << ", " << bqm.to_string(b) << "\n"; display(tout, sz, p); tout << "\n";); + numeral_vector & p_aux = m_dbab_tmp1; + translate_bq(sz, p, a, p_aux); + TRACE("upolynomial", tout << "after translation\n"; display(tout, p_aux); tout << "\n";); + scoped_mpbq b_a(bqm); + bqm.sub(b, a, b_a); + compose_p_b_x(p_aux.size(), p_aux.c_ptr(), b_a); + TRACE("upolynomial", tout << "after composition: " << bqm.to_string(b_a) << "\n"; display(tout, p_aux); tout << "\n";); + unsigned result = descartes_bound_0_1(p_aux.size(), p_aux.c_ptr()); + return result; + } + else if (bqm.is_nonpos(b)) { + // Basic idea: apply descartes_bound_a_b to p(-x) with intervals (-b, -a) + numeral_vector & p_aux = m_dbab_tmp2; + set(sz, p, p_aux); + p_minus_x(p_aux.size(), p_aux.c_ptr()); + scoped_mpbq mb(bqm); + scoped_mpbq ma(bqm); + bqm.set(mb, b); bqm.neg(mb); + bqm.set(ma, a); bqm.neg(ma); + unsigned result = descartes_bound_a_b(p_aux.size(), p_aux.c_ptr(), bqm, mb, ma); + return result; + } + else if (!has_zero_roots(sz, p)) { + mpbq zero(0); + unsigned r1 = descartes_bound_a_b(sz, p, bqm, a, zero); + if (r1 > 1) + return r1; // if p has more than 1 root in (a, zero), then we don't even evaluate (zero, b) + unsigned r2 = descartes_bound_a_b(sz, p, bqm, zero, b); + if (r1 == 0) + return r2; + if (r2 == 0) + return r1; + return 2; // p has more than one root in (a, b) + } + else { + // zero is a root of p + mpbq zero(0); + if (descartes_bound_a_b(sz, p, bqm, a, zero) > 0) + return 2; // p has more than one root in (a, b) + if (descartes_bound_a_b(sz, p, bqm, zero, b) > 0) + return 2; // p has more than one root in (a, b) + return 1; // zero is the only root in (a, b) + } + } + + // p(x) := p(x+1) + void manager::translate(unsigned sz, numeral * p) { + if (sz <= 1) + return; + unsigned n = sz - 1; + for (unsigned i = 1; i <= n; i++) { + checkpoint(); + for (unsigned k = n-i; k <= n-1; k++) + m().add(p[k], p[k+1], p[k]); + } + } + + // p(x) := p(x+2^k) + void manager::translate_k(unsigned sz, numeral * p, unsigned k) { + if (sz <= 1) + return; + scoped_numeral aux(m()); + unsigned n = sz - 1; + for (unsigned i = 1; i <= n; i++) { + checkpoint(); + for (unsigned k = n-i; k <= n-1; k++) { + m().mul2k(p[k+1], k, aux); + m().add(p[k], aux, p[k]); + } + } + } + + // p(x) := p(x+c) + void manager::translate_z(unsigned sz, numeral * p, numeral const & c) { + if (sz <= 1) + return; + unsigned n = sz - 1; + for (unsigned i = 1; i <= n; i++) { + checkpoint(); + for (unsigned k = n-i; k <= n-1; k++) + m().addmul(p[k], c, p[k+1], p[k]); + } + } + + // Given b of the form c/(2^k) + // p(x) := (2^k)^n * p(x + c/(2^k)) where b = c/2^k + // + // Given p: a_n * x^n + ... + a_0 + // + // buffer := a_n * (2^k * x + c)^n + a_{n-1} * 2^k * (2^k * x + c)^{n-1} + ... + a_1 * (2^k)^{n-1} * (2^k * x + c) + a_0 * (2^k)^n + // + // We perform the transformation in two steps: + // 1) a_n * x^n + a_{n-1} * 2^k * x^{n-1} + ... + a_1 * (2^k)^{n-1} + a_0 * (2^k)^n + // Let d_n, ..., d_0 be the coefficients after this step + // Then, we have + // d_n * x^n + ... + d_0 + // 2) d_n * (2^k * x + c)^n + ... + d_0 + // This step is like the translation with integers, but the coefficients must be adjusted by 2^k in each iteration. + // + // That is, it is a special translation such as: + // a_n*(b*x + c)^n + a_{n-1}*(b*x + c)^{n-1} + ... + a_1*(b*x + c) + a_0 + // This kind of translation can be computed by applying the following recurrence: + // To simplify the notation, we fix n = 4 + // Moreover we use a_i[k] to represent the coefficient a_i at iteration k + // a_i[0] = a_i + // + // a_4*(b*x + c)^4 + a_3*(b*x + c)^3 + a_2*(b*x + c)^2 + a_1*(b*x + c) + a_0 + // --> + // a_4*b*x*(b*x + c)^3 + (a_3 + a_4*c)*(b*x + c)^3 + a_2*(b*x + c)^2 + a_1*(b*x + c) + a_0 + // Thus a_4[1] = a_4[0]*b, a_3[1] = a_3[0] + a_4[0]*c, a_2[1] = a_2[0], a_1[1] = a_1[0], a_0[1] = a_0[0] + // + // a_4[1]*x*(b*x + c)^3 + a_3[1]*(b*x + c)^3 + a_2[1]*(b*x + c)^2 + a_1[1]*(b*x + c) + a_0[1] + // --> + // a_4[1]*b*x^2*(b*x + c)^2 + (a_3[1]*b + a_4[1]*c)*x*(b*x + c)^2 + (a_2[1] + a_3[1]*c)*(b*x + c)^2 + a_1[1]*(b*x + c) + a_0[1] + // Thus a_4[2] = a_4[1]*b, a_3[2] = a_3[1]*b + a_4[1]*c, a_2[2] = a_2[1] + a_3[1]*c, a_1[2] = a_1[1], a_0[2] = a_0[1] + // + // a_4[2]*x^2*(b*x + c)^2 + a_3[2]*x*(b*x + c)^2 + a_2[2]*(b*x + c)^2 + a_1[2]*(b*x + c) + a_0[2] + // --> + // a_4[2]*b*x^3*(b*x + c) + (a_3[2]*b + a_4[2]*c)*x^2*(b*x + c) + (a_2[2]*b + a_3[2]*c)*x*(b*x + c) + (a_1[2] + a_2[2]*c)*(b*x + c) + a_0[2] + // Thus a_4[3] = a_4[2]*b, a_3[3] = a_3[2]*b + a_4[2]*c, a_2[3] = a_2[2]*b + a_3[2]*c, a_1[3] = a_1[2] + a_2[2]*c, a_0[3] = a_1[3] + // + // a_4[3]*x^3*(b*x + c) + a_3[3]*x^2*(b*x + c) + a_2[3]*x*(b*x + c) + a_1[3]*(b*x + c) + a_0[3] + // ---> + // a_4[3]*b*x^4 + (a_3[3]*b + a_4[3]*c)*x^3 + (a_2[3]*b + a_3[3]*c)*x^2 + (a_1[3]*b + a_2[3]*c)*x + (a_0[3] + a_1[3]*c) + // + void manager::translate_bq(unsigned sz, numeral * p, mpbq const & b) { + if (sz <= 1) + return; + // Step 1 + compose_2kn_p_x_div_2k(sz, p, b.k()); + TRACE("upolynomial", tout << "after compose 2kn_p_x_div_2k\n"; display(tout, sz, p); tout << "\n";); + // Step 2 + numeral const & c = b.numerator(); + unsigned n = sz - 1; + for (unsigned i = 1; i <= n; i++) { + checkpoint(); + m().addmul(p[n - i], c, p[n - i + 1], p[n - i]); + for (unsigned k = n - i + 1; k <= n - 1; k++) { + m().mul2k(p[k], b.k()); + m().addmul(p[k], c, p[k + 1], p[k]); + } + m().mul2k(p[n], b.k()); + } + TRACE("upolynomial", tout << "after special translation\n"; display(tout, sz, p); tout << "\n";); + } + + // Similar to translate_bq but for rationals + void manager::translate_q(unsigned sz, numeral * p, mpq const & b) { + if (sz <= 1) + return; + // Step 1 + compose_an_p_x_div_a(sz, p, b.denominator()); + TRACE("upolynomial", tout << "after compose_an_p_x_div_a\n"; display(tout, sz, p); tout << "\n";); + // Step 2 + numeral const & c = b.numerator(); + unsigned n = sz - 1; + for (unsigned i = 1; i <= n; i++) { + checkpoint(); + m().addmul(p[n - i], c, p[n - i + 1], p[n - i]); + for (unsigned k = n - i + 1; k <= n - 1; k++) { + m().mul(p[k], b.denominator(), p[k]); + m().addmul(p[k], c, p[k + 1], p[k]); + } + m().mul(p[n], b.denominator(), p[n]); + } + TRACE("upolynomial", tout << "after special translation\n"; display(tout, sz, p); tout << "\n";); + } + + // p(x) := 2^n*p(x/2) where n = sz-1 + void manager::compose_2n_p_x_div_2(unsigned sz, numeral * p) { + if (sz <= 1) + return; + // a_n * x^n + 2 * a_{n-1} * x^{n-1} + ... + (2^n)*a_0 + unsigned k = sz-1; // k = n + for (unsigned i = 0; i < sz - 1; i++) { + m().mul2k(p[i], k); + k--; + } + } + + // p(x) := p(-x) + void manager::p_minus_x(unsigned sz, numeral * p) { + for (unsigned i = 0; i < sz; i++) { + if (m().is_zero(p[i])) + continue; + if (i % 2 == 0) + continue; + m().neg(p[i]); + } + } + + // p(x) := x^n * p(1/x) + void manager::p_1_div_x(unsigned sz, numeral * p) { + if (sz <= 1) + return; + unsigned i = 0; + unsigned j = sz-1; + SASSERT(j >= 1); + while (true) { + swap(p[i], p[j]); + i++; j--; + if (i >= j) + break; + } + } + + // p(x) := p(2^k * x) + void manager::compose_p_2k_x(unsigned sz, numeral * p, unsigned k) { + // (2^k)^n*a_n*x^n + (2^k)^(n-1)*x^{n-1} + ... + a_0 + if (sz <= 1) + return; + unsigned k_i = k; + for (unsigned i = 1; i < sz; i++) { + m().mul2k(p[i], k_i); + k_i += k; + } + } + + // p(x) := (2^k)^n * p(x/2^k) + // + // Let old(p) be of the form: + // a_n * x^n + a_{n-1}*x^{n-1} + ... + a_1 * x + a_0 + // + // Then p is of the form: + // a_n * x^n + a_{n-1} * 2^k * x^{n-1} + a_{n-2} * (2^k)^2 * x^{n-2} + ... + a_0 * (2^k)^n + void manager::compose_2kn_p_x_div_2k(unsigned sz, numeral * p, unsigned k) { + if (sz <= 1) + return; + unsigned k_i = k*sz; + for (unsigned i = 0; i < sz; i++) { + k_i -= k; + if (!m().is_zero(p[i])) + m().mul2k(p[i], k_i); + } + } + + // p(x) := a^n * p(x/a) + // See compose_2kn_p_x_div_2k + void manager::compose_an_p_x_div_a(unsigned sz, numeral * p, numeral const & a) { + if (sz <= 1) + return; + unsigned i = sz-1; + scoped_numeral a_i(m()); + m().set(a_i, a); + while (i > 0) { + --i; + if (!m().is_zero(p[i])) + m().mul(p[i], a_i, p[i]); + m().mul(a_i, a, a_i); + } + } + + // p(x) := p(b * x) + void manager::compose_p_b_x(unsigned sz, numeral * p, numeral const & b) { + if (sz <= 1) + return; + scoped_numeral b_i(m()); + m().set(b_i, b); + for (unsigned i = 1; i < sz; i++) { + if (!m().is_zero(p[i])) + m().mul(p[i], b_i, p[i]); + m().mul(b_i, b, b_i); + } + } + + // Let b be of the form c/(2^k), then this operation is equivalent to: + // (2^k)^n*p(c*x/(2^k)) + // + // Let old(p) be of the form: + // a_n * x^n + a_{n-1}*x^{n-1} + ... + a_1 * x + a_0 + // + // Then p is of the form: + // a_n * c^n * x^n + a_{n-1} * c^{n-1} * 2^k * x^{n-1} + ... + a_1 * c * (2^k)^(n-1) * x + a_0 * (2^k)^n + void manager::compose_p_b_x(unsigned sz, numeral * p, mpbq const & b) { + if (sz <= 1) + return; + unsigned k = b.k(); + numeral const & c = b.numerator(); + scoped_numeral c_i(m()); + m().set(c_i, 1); + unsigned k_i = k*sz; + for (unsigned i = 0; i < sz; i++) { + k_i -= k; + if (!m().is_zero(p[i])) { + m().mul2k(p[i], k_i); + m().mul(p[i], c_i, p[i]); + } + m().mul(c_i, c, c_i); + } + SASSERT(k_i == 0); + } + + // Let q be of the form b/c, then this operation is equivalent to: + // p(x) := c^n*p(b*x/c) + // + // If u is a root of old(p), then u*(c/b) is a root of new p. + // + // Let old(p) be of the form: + // a_n * x^n + a_{n-1}*x^{n-1} + ... + a_1 * x + a_0 + // + // Then p is of the form: + // a_n * b^n * x^n + a_{n-1} * b^{n-1} * c * x^{n-1} + ... + a_1 * b * c^(n-1) * x + a_0 * c^n + void manager::compose_p_q_x(unsigned sz, numeral * p, mpq const & q) { + if (sz <= 1) + return; + numeral const & b = q.numerator(); + numeral const & c = q.denominator(); + scoped_numeral bc(m()); + m().power(c, sz-1, bc); // bc = b^n + for (unsigned i = 0; i < sz; i++) { + if (!m().is_zero(p[i])) + m().mul(p[i], bc, p[i]); + if (i < sz - 1) { + // bc <- (bc/b)*c + m().div(bc, c, bc); + m().mul(bc, b, bc); + } + } + } + + // Evaluate the sign of p(b) + int manager::eval_sign_at(unsigned sz, numeral const * p, mpbq const & b) { + // Actually, given b = c/2^k, we compute the sign of (2^k)^n*p(b) + // Original Horner Sequence + // ((a_n * b + a_{n-1})*b + a_{n-2})*b + a_{n-3} ... + // Variation of the Horner Sequence for (2^k)^n*p(b) + // ((a_n * c + a_{n-1}*2_k)*c + a_{n-2}*(2_k)^2)*c + a_{n-3}*(2_k)^3 ... + a_0*(2_k)^n + if (sz == 0) + return 0; + if (sz == 1) + return sign_of(p[0]); + numeral const & c = b.numerator(); + unsigned k = b.k(); + unsigned k_i = k; + scoped_numeral r(m()); + scoped_numeral ak(m()); + m().set(r, p[sz-1]); + unsigned i = sz-1; + while (i > 0) { + --i; + numeral const & a = p[i]; + if (m().is_zero(a)) { + m().mul(r, c, r); + } + else { + m().mul2k(a, k_i, ak); + m().addmul(ak, r, c, r); + } + k_i += k; + } + return sign_of(r); + } + + // Evaluate the sign of p(b) + int manager::eval_sign_at(unsigned sz, numeral const * p, mpq const & b) { + // Actually, given b = c/d, we compute the sign of (d^n)*p(b) + // Original Horner Sequence + // ((a_n * b + a_{n-1})*b + a_{n-2})*b + a_{n-3} ... + // Variation of the Horner Sequence for (d^n)*p(b) + // ((a_n * c + a_{n-1}*d)*c + a_{n-2}*d^2)*c + a_{n-3}*d^3 ... + a_0*d^n + if (sz == 0) + return 0; + if (sz == 1) + return sign_of(p[0]); + numeral const & c = b.numerator(); + numeral const & d = b.denominator(); + scoped_numeral d_i(m()); + m().set(d_i, d); + scoped_numeral r(m()); + scoped_numeral ak(m()); + m().set(r, p[sz-1]); + unsigned i = sz-1; + while (i > 0) { + --i; + numeral const & a = p[i]; + if (m().is_zero(a)) { + m().mul(r, c, r); + } + else { + m().mul(a, d_i, ak); + m().addmul(ak, r, c, r); + } + m().mul(d_i, d, d_i); + } + return sign_of(r); + } + + // Evaluate the sign of p(b) + int manager::eval_sign_at(unsigned sz, numeral const * p, mpz const & b) { + // Using Horner Sequence + // ((a_n * b + a_{n-1})*b + a_{n-2})*b + a_{n-3} ... + if (sz == 0) + return 0; + if (sz == 1) + return sign_of(p[0]); + scoped_numeral r(m()); + m().set(r, p[sz-1]); + unsigned i = sz-1; + while (i > 0) { + --i; + numeral const & a = p[i]; + if (m().is_zero(a)) + m().mul(r, b, r); + else + m().addmul(a, r, b, r); + } + return sign_of(r); + } + + int manager::eval_sign_at_zero(unsigned sz, numeral const * p) { + if (sz == 0) + return 0; + return sign_of(p[0]); + } + + int manager::eval_sign_at_plus_inf(unsigned sz, numeral const * p) { + if (sz == 0) + return 0; + return sign_of(p[sz-1]); + } + + int manager::eval_sign_at_minus_inf(unsigned sz, numeral const * p) { + if (sz == 0) + return 0; + unsigned degree = sz - 1; + if (degree % 2 == 0) + return sign_of(p[sz-1]); + else + return -sign_of(p[sz-1]); + } + + template + unsigned manager::sign_variations_at_core(upolynomial_sequence const & seq, mpbq const & b) { + unsigned sz = seq.size(); + if (sz <= 1) + return 0; + unsigned r = 0; + int sign, prev_sign; + sign = 0; + prev_sign = 0; + unsigned i = 0; + for (; i < sz; i++) { + // find next nonzero + unsigned psz = seq.size(i); + numeral const * p = seq.coeffs(i); + switch (loc) { + case PLUS_INF: + sign = eval_sign_at_plus_inf(psz, p); + break; + case MINUS_INF: + sign = eval_sign_at_minus_inf(psz, p); + break; + case ZERO: + sign = eval_sign_at_zero(psz, p); + break; + case MPBQ: + sign = eval_sign_at(psz, p, b); + break; + default: + UNREACHABLE(); + break; + } + if (sign == 0) + continue; + SASSERT(sign == 1 || sign == -1); + // in the first iteration prev_sign == 0, then r is never incremented. + if (sign != prev_sign && prev_sign != 0) + r++; + // move to the next + prev_sign = sign; + } + return r; + } + + unsigned manager::sign_variations_at_minus_inf(upolynomial_sequence const & seq) { + mpbq dummy(0); + return sign_variations_at_core(seq, dummy); + } + + unsigned manager::sign_variations_at_plus_inf(upolynomial_sequence const & seq) { + mpbq dummy(0); + return sign_variations_at_core(seq, dummy); + } + + unsigned manager::sign_variations_at_zero(upolynomial_sequence const & seq) { + mpbq dummy(0); + return sign_variations_at_core(seq, dummy); + } + + unsigned manager::sign_variations_at(upolynomial_sequence const & seq, mpbq const & b) { + return sign_variations_at_core(seq, b); + } + + void manager::root_upper_bound(unsigned sz, numeral const * p, numeral & r) { + // Using Cauchy's Inequality + // We have that any root u of p must satisfy + // |u| < (max(p) + min(p))/min(p) + // |u| < (max(p) + |a_n|)/|a_n| + // where: max(p) is the maximum coefficient in absolute value. + // min(p) is the minimum (nonzero) coefficient in absolute value + SASSERT(sz > 0); + SASSERT(!m().is_zero(p[sz - 1])); + bool init = false; + scoped_numeral max(m()); + scoped_numeral min(m()); + scoped_numeral a_n(m()); + scoped_numeral r2(m()); + m().set(a_n, p[sz - 1]); + m().abs(a_n); + scoped_numeral c(m()); + for (unsigned i = 0; i < sz; i++) { + if (m().is_zero(p[i])) + continue; + m().set(c, p[i]); + m().abs(c); + if (!init) { + m().set(max, c); + m().set(min, c); + init = true; + continue; + } + if (m().gt(c, max)) + m().set(max, c); + if (m().lt(c, min)) + m().set(min, c); + } + // first bound + m().add(min, max, r); + m().div(r, min, r); + m().add(r, numeral(1), r); + // second bound + m().add(a_n, max, r2); + m().div(r2, a_n, r2); + m().add(r2, numeral(1), r2); + // use the best bound + if (m().lt(r2, r)) + swap(r, r2); + SASSERT(m().le(r, r2)); + } + + /** + \brief Find positive root upper bound using Knuth's approach. + + Given p(x) = a_n * x^n + a_{n-1} * x^{n-1} + ... + a_0 + + If a_n is positive, + Let B = max({ (-a_{n-k}/a_n)^{1/k} | 1 <= k <= n, a_{n-k} < 0 }) + Then, 2*B is a bound for the positive roots + + Similarly, if a_n is negative + Let B = max({ (-a_{n-k}/a_n)^{1/k} | 1 <= k <= n, a_{n-k} > 0 }) + Then, 2*B is a bound for the positive roots + + This procedure returns a k s.t. 2*B <= 2^k + + The procedure actually computes + + max of log2(abs(a_{n-k})) + 1 - log2(abs(a_n))/k + + Proof Sketch: + + Let u > 0 be a root of p(x). + If u <= B, then we are done. So, let us assume u > B + + Assume, a_n > 0 (the proof is similar for a_n < 0) + + Then: a_n * u^n + a_{n-1} * u^{n-1} + ... + a0 = 0 + u^n = 1/a_n (-a_{n-1} * u^{n-1} - ... - a_0) + Note that, if we remove the monomials s.t. a_i is positive, then + we are increasing the value of (-a_{n-1} * u^{n-1} - ... - a_0). + Thus, + u^n <= 1/a_n(SUM_{a_i < 0, 0 <= i < n} (-a_i * u^i)) + Dividing by u_n which is positive, we get + 1 <= 1/a_n(SUM_{a_i < 0, 0 <= i < n} (-a_i * u^i)) + By replacing, i = n - k + we have + 1 <= 1/a_n(SUM_{a_{n-k} < 0, n >= k > 0} (-a_{n-k} * u^{n-k})) = 1/a_n(SUM_{a_{n-k} < 0, 1 <= k <= n} (-a_i * u^{n-k})) < Sum_{1 <= k < +oo}(B/u)^k + Since u > B, we have that Sum_{1 <= k < +oo}(B/u)^k = B/(u - B) + Thus, we have + 1 < B/(u - B) + and u < 2B + */ + unsigned manager::knuth_positive_root_upper_bound(unsigned sz, numeral const * p) { + if (sz == 0) + return 0; + unsigned max = 0; + unsigned n = sz - 1; + bool pos_a_n = m().is_pos(p[n]); + unsigned log2_a_n = pos_a_n ? m().log2(p[n]) : m().mlog2(p[n]); + for (unsigned k = 1; k <= n; k++) { + numeral const & a_n_k = p[n - k]; + if (m().is_zero(a_n_k)) + continue; + bool pos_a_n_k = m().is_pos(a_n_k); + if (pos_a_n_k == pos_a_n) + continue; // must have oposite signs + unsigned log2_a_n_k = pos_a_n_k ? m().log2(a_n_k) : m().mlog2(a_n_k); + if (log2_a_n > log2_a_n_k) + continue; + unsigned curr = log2_a_n_k + 1 - log2_a_n; + if (curr % k == 0) { + curr /= k; + } + else { + curr /= k; + curr ++; + } + if (curr > max) + max = curr; + } + return max + 1; + } + + /** + It is essentially applying knuth_positive_root_upper_bound for p(-x) + */ + unsigned manager::knuth_negative_root_upper_bound(unsigned sz, numeral const * p) { + numeral * _p = const_cast(p); + p_minus_x(sz, _p); + unsigned r = knuth_positive_root_upper_bound(sz, _p); + p_minus_x(sz, _p); + return r; + } + + /** + Return a lower bound for the nonzero roots of p(x). + Let k be the result of this procedure, + Then for any nonzero root alpha of p(x), we have that + |alpha| > 1/2^k + + We essentially compute the upper bound for the roots of x^n*p(1/x) where n is the degree of p. + Remark: alpha is a nonzero root of p(x) iff 1/alpha is a root of x^n*p(1/x). + Thus, if 2^k is upper bound for the root of x^n*p(1/x). Then we have that + -2^k < 1/alpha < 2^k + and consequently + alpha < -1/2^k or 1/2^k < alpha + + /pre p is not the zero polynomial. + */ + unsigned manager::nonzero_root_lower_bound(unsigned sz, numeral const * p) { + SASSERT(sz > 0); + // consume zeros + unsigned i = 0; + while (true) { + SASSERT(i < sz); + if (!m().is_zero(p[i])) + break; + i++; + } + SASSERT(i < sz); + SASSERT(!m().is_zero(p[i])); + unsigned nz_sz = sz - i; + numeral const * nz_p = p + i; + // nz_p does not have zero roots; + numeral * _nz_p = const_cast(nz_p); + // destructive update for quickly computing x^n*nz_p(1/x) + std::reverse(_nz_p, _nz_p + nz_sz); + unsigned k1 = knuth_positive_root_upper_bound(nz_sz, _nz_p); + unsigned k2 = knuth_negative_root_upper_bound(nz_sz, _nz_p); + // undo destructive update + std::reverse(_nz_p, _nz_p + nz_sz); + return std::max(k1, k2); + } + + // Frames for implementing drs_isolate_0_1_roots. + // The frames are used to avoid recursive calls and potential stack overflows. + // Each frame has a polynomial associated with it. The polynomials + // are stored in a separate stack. + struct manager::drs_frame { + unsigned m_parent_idx; // position of the parent frame, UINT_MAX if it doesn't have a parent frame + unsigned m_size:30; // size of the polynomial associated with this frame + unsigned m_first:1; // first time visiting the frame? + unsigned m_left:1; // true if the frame is the left child of the parent frame. + drs_frame(unsigned pidx, unsigned sz, bool left): + m_parent_idx(pidx), + m_size(sz), + m_first(true), + m_left(left) { + } + }; + + // Pop the top frame from the frame_stack, remove the coefficients of the polynomial associated with the top frame from p_stack. + void manager::pop_top_frame(numeral_vector & p_stack, svector & frame_stack) { + SASSERT(!frame_stack.empty()); + unsigned sz = frame_stack.back().m_size; + SASSERT(sz <= p_stack.size()); + for (unsigned i = 0; i < sz; i++) { + m().del(p_stack.back()); + p_stack.pop_back(); + } + frame_stack.pop_back(); + } + + // Auxiliar method for isolating the roots of p in the interval (0, 1). + // The basic idea is to split the interval in: (0, 1/2) and (1/2, 1). + // This is accomplished by analyzing the roots in the interval (0, 1) of the following polynomials. + // p1(x) := 2^n * p(x/2) where n = sz-1 + // p2(x) := p1(x+1) + // We say p1(x) is the left child, and p2 the right child. The coefficients p1 and p2 are stored in p_stack. + // A new frame is created for each child. + void manager::push_child_frames(unsigned sz, numeral const * p, numeral_vector & p_stack, svector & frame_stack) { + // I don't really need the following test, because 0 - 1 == UINT_MAX + unsigned parent_idx = frame_stack.empty() ? UINT_MAX : frame_stack.size() - 1; + numeral_vector & p_aux = m_push_tmp; + + // Possible optimization: the coefficients of the parent frame are not needed anymore. + // So, we could reuse/steal them. + + // left child + set(sz, p, p_aux); + compose_2n_p_x_div_2(p_aux.size(), p_aux.c_ptr()); + normalize(p_aux); + for (unsigned i = 0; i < sz; i++) { + p_stack.push_back(numeral()); + m().set(p_stack.back(), p_aux[i]); + } + frame_stack.push_back(drs_frame(parent_idx, sz, true)); + // right child + translate(sz, p_stack.end() - sz, p_aux); + normalize(p_aux); + for (unsigned i = 0; i < sz; i++) { + p_stack.push_back(numeral()); + swap(p_stack.back(), p_aux[i]); + } + frame_stack.push_back(drs_frame(parent_idx, sz, false)); + } + + // (0,1) is an isolating interval for the polynomial associated with the top frame. + // Apply transformations for obtaining isolating interval for the original polynomial: + // We use the following transformations: + // Left child: (l, u) -> (l/2, u/2) + // Right child: (l, u) -> ((l+1)/2, (u+1)/2) + void manager::add_isolating_interval(svector const & frame_stack, mpbq_manager & bqm, mpbq_vector & lowers, mpbq_vector & uppers) { + mpbq l(0); + mpbq u(1); + unsigned idx = frame_stack.size() - 1; + while (idx != UINT_MAX) { + drs_frame const & fr = frame_stack[idx]; + TRACE("upolynomial", + tout << "normalizing...\n"; + tout << "idx: " << idx << ", left: " << fr.m_left << ", l: " << bqm.to_string(l) << ", u: " << bqm.to_string(u) << "\n";); + if (fr.m_left) { + bqm.div2(l); + bqm.div2(u); + } + else { + bqm.add(l, mpz(1), l); + bqm.add(u, mpz(1), u); + bqm.div2(l); + bqm.div2(u); + } + idx = fr.m_parent_idx; + } + TRACE("upolynomial", tout << "adding normalized interval (" << bqm.to_string(l) << ", " << bqm.to_string(u) << ")\n";); + lowers.push_back(mpbq()); + uppers.push_back(mpbq()); + swap(lowers.back(), l); + swap(uppers.back(), u); + } + + // 1/2 is a root of the polynomial associated with the top frame. + // Apply transformations for obtaining root of the original polynomial: + // We use the following transformations: + // Left child: u -> u/2 + // Right child: u -> (u+1)/2 + void manager::add_root(svector const & frame_stack, mpbq_manager & bqm, mpbq_vector & roots) { + mpbq u(1,1); + unsigned idx = frame_stack.size() - 1; + while (idx != UINT_MAX) { + drs_frame const & fr = frame_stack[idx]; + if (fr.m_left) { + bqm.div2(u); + } + else { + bqm.add(u, mpz(1), u); + bqm.div2(u); + } + idx = fr.m_parent_idx; + } + TRACE("upolynomial", tout << "adding normalized root: " << bqm.to_string(u) << "\n";); + roots.push_back(mpbq()); + swap(roots.back(), u); + } + + // Isolate roots in the interval (0, 1) + void manager::drs_isolate_0_1_roots(unsigned sz, numeral const * p, mpbq_manager & bqm, mpbq_vector & roots, mpbq_vector & lowers, mpbq_vector & uppers) { + TRACE("upolynomial", tout << "isolating (0,1) roots of:\n"; display(tout, sz, p); tout << "\n";); + unsigned k = descartes_bound_0_1(sz, p); + // easy cases... + if (k == 0) { + TRACE("upolynomial", tout << "polynomial does not have any roots\n";); + return; + } + if (k == 1) { + TRACE("upolynomial", tout << "polynomial has one root in (0, 1)\n";); + lowers.push_back(mpbq(0)); + uppers.push_back(mpbq(1)); + return; + } + TRACE("upolynomial", tout << "polynomial has more than one root in (0, 1), starting search...\n";); + scoped_numeral_vector q(m()); + scoped_numeral_vector p_stack(m()); + svector frame_stack; + if (has_one_half_root(sz, p)) { + TRACE("upolynomial", tout << "polynomial has a 1/2 root\n";); + roots.push_back(mpbq(1, 1)); + remove_one_half_root(sz, p, q); + push_child_frames(q.size(), q.c_ptr(), p_stack, frame_stack); + } + else { + push_child_frames(sz, p, p_stack, frame_stack); + } + SASSERT(!frame_stack.empty()); + while (!frame_stack.empty()) { + checkpoint(); + drs_frame & fr = frame_stack.back(); + unsigned sz = fr.m_size; + SASSERT(sz <= p_stack.size()); + numeral const * p = p_stack.end() - sz; + TRACE("upolynomial", tout << "processing frame #" << frame_stack.size() - 1 << "\n" + << "first: " << fr.m_first << ", left: " << fr.m_left << ", sz: " << fr.m_size << ", parent_idx: "; + if (fr.m_parent_idx == UINT_MAX) tout << ""; else tout << fr.m_parent_idx; + tout << "\np: "; display(tout, sz, p); tout << "\n";); + if (!fr.m_first) { + pop_top_frame(p_stack, frame_stack); + continue; + } + fr.m_first = false; + unsigned k = descartes_bound_0_1(sz, p); + if (k == 0) { + TRACE("upolynomial", tout << "(0, 1) does not have roots\n";); + pop_top_frame(p_stack, frame_stack); + continue; + } + if (k == 1) { + TRACE("upolynomial", tout << "(0, 1) is isolating interval\n";); + add_isolating_interval(frame_stack, bqm, lowers, uppers); + pop_top_frame(p_stack, frame_stack); + continue; + } + TRACE("upolynomial", tout << "polynomial has more than one root in (0, 1) creating child frames...\n";); + if (has_one_half_root(sz, p)) { + TRACE("upolynomial", tout << "1/2 is a root\n";); + add_root(frame_stack, bqm, roots); + remove_one_half_root(sz, p, q); + push_child_frames(q.size(), q.c_ptr(), p_stack, frame_stack); + } + else { + push_child_frames(sz, p, p_stack, frame_stack); + } + } + } + + // Foreach i in [starting_at, v.size()) v[i] := 2^k*v[i] + static void adjust_pos(mpbq_manager & bqm, mpbq_vector & v, unsigned starting_at, unsigned k) { + unsigned sz = v.size(); + for (unsigned i = starting_at; i < sz; i++) + bqm.mul2k(v[i], k); + } + + // Foreach i in [starting_at, v.size()) v[i] := -2^k*v[i] + static void adjust_neg(mpbq_manager & bqm, mpbq_vector & v, unsigned starting_at, unsigned k) { + unsigned sz = v.size(); + for (unsigned i = starting_at; i < sz; i++) { + bqm.mul2k(v[i], k); + bqm.neg(v[i]); + } + } + + static void swap_lowers_uppers(unsigned starting_at, mpbq_vector & lowers, mpbq_vector & uppers) { + SASSERT(lowers.size() == uppers.size()); + unsigned sz = lowers.size(); + for (unsigned i = starting_at; i < sz; i++) { + swap(lowers[i], uppers[i]); + } + } + + // Isolate roots in an interval (-2^neg_k, 2^pos_k) using an approach based on Descartes rule of signs. + void manager::drs_isolate_roots(unsigned sz, numeral * p, unsigned neg_k, unsigned pos_k, + mpbq_manager & bqm, mpbq_vector & roots, mpbq_vector & lowers, mpbq_vector & uppers) { + scoped_numeral_vector aux_p(m()); + set(sz, p, aux_p); + pos_k = std::max(neg_k, pos_k); + compose_p_2k_x(sz, aux_p.c_ptr(), pos_k); + + // p(x) := p(2^{pos_k} * x) + // Since the desired positive roots of p(x) are in (0, 2^pos_k), + TRACE("upolynomial", tout << "searching at (0, 1)\n";); + unsigned old_roots_sz = roots.size(); + unsigned old_lowers_sz = lowers.size(); + drs_isolate_0_1_roots(sz, aux_p.c_ptr(), bqm, roots, lowers, uppers); + SASSERT(lowers.size() == uppers.size()); + adjust_pos(bqm, roots, old_roots_sz, pos_k); + adjust_pos(bqm, lowers, old_lowers_sz, pos_k); + adjust_pos(bqm, uppers, old_lowers_sz, pos_k); + + // Isolate roots in (-2^{neg_k}, 0) + // p(x) := p(-x) + p_minus_x(sz, p); + compose_p_2k_x(sz, p, neg_k); + TRACE("upolynomial", tout << "searching at (-1, 0) using:\n"; display(tout, sz, p); tout << "\n";); + old_roots_sz = roots.size(); + old_lowers_sz = lowers.size(); + drs_isolate_0_1_roots(sz, p, bqm, roots, lowers, uppers); + SASSERT(lowers.size() == uppers.size()); + adjust_neg(bqm, roots, old_roots_sz, neg_k); + adjust_neg(bqm, lowers, old_lowers_sz, neg_k); + adjust_neg(bqm, uppers, old_lowers_sz, neg_k); + swap_lowers_uppers(old_lowers_sz, lowers, uppers); + } + + // Isolate roots using an approach based on Descartes rule of signs. + void manager::drs_isolate_roots(unsigned sz, numeral const * p, mpbq_manager & bqm, mpbq_vector & roots, mpbq_vector & lowers, mpbq_vector & uppers) { + SASSERT(lowers.size() == uppers.size()); + SASSERT(!has_zero_roots(sz, p)); + + scoped_numeral_vector p1(m()); + set(sz, p, p1); + normalize(p1); + + TRACE("upolynomial", + scoped_numeral U(m()); + root_upper_bound(p1.size(), p1.c_ptr(), U); + unsigned U_k = m().log2(U) + 1; + tout << "Cauchy U: 2^" << U_k << "\n"; + tout << "Knuth pos U: 2^" << knuth_positive_root_upper_bound(sz, p) << "\n"; + tout << "Knuth neg U: 2^" << knuth_negative_root_upper_bound(sz, p) << "\n";); + +#if 1 + unsigned pos_k = knuth_positive_root_upper_bound(sz, p); + unsigned neg_k = knuth_negative_root_upper_bound(sz, p); +#else + scoped_numeral U(m()); + root_upper_bound(p1.size(), p1.c_ptr(), U); + std::cout << "U: " << U << "\n"; + unsigned pos_k = m().log2(U) + 1; + unsigned neg_k = pos_k; +#endif + drs_isolate_roots(p1.size(), p1.c_ptr(), neg_k, pos_k, bqm, roots, lowers, uppers); + } + + // Frame for root isolation in sturm_isolate_roots. + // It stores (lower, upper, num. sign variations at lower, num. sign variations at upper) + struct ss_frame { + mpbq m_lower; + mpbq m_upper; + unsigned m_lower_sv; // number of sign variations in the sturm sequence at the lower bound + unsigned m_upper_sv; // number of sign variations in the sturm sequence at the upper bound + }; + + class ss_frame_stack : public svector { + mpbq_manager & m; + public: + ss_frame_stack(mpbq_manager & _m):m(_m) {} + ~ss_frame_stack() { + iterator it = begin(); + iterator e = end(); + for (; it != e; ++it) { + ss_frame & f = *it; + m.del(f.m_lower); + m.del(f.m_upper); + } + } + }; + + inline void pop_ss_frame(mpbq_manager & m, ss_frame_stack & s) { + m.del(s.back().m_lower); + m.del(s.back().m_upper); + s.pop_back(); + } + + void ss_add_isolating_interval(mpbq_manager & m, mpbq const & lower, mpbq const & upper, mpbq_vector & lowers, mpbq_vector & uppers) { + lowers.push_back(mpbq()); + uppers.push_back(mpbq()); + m.set(lowers.back(), lower); + m.set(uppers.back(), upper); + } + + // Isolate roots in an interval (-2^neg_k, 2^pos_k) using an approach based on Descartes rule of signs. + void manager::sturm_isolate_roots_core(unsigned sz, numeral * p, unsigned neg_k, unsigned pos_k, + mpbq_manager & bqm, mpbq_vector & roots, mpbq_vector & lowers, mpbq_vector & uppers) { + SASSERT(!has_zero_roots(sz, p)); + scoped_upolynomial_sequence seq(*this); + scoped_mpbq mid(bqm); + scoped_mpbq curr_lower(bqm); + scoped_mpbq curr_upper(bqm); + sturm_seq(sz, p, seq); + ss_frame_stack s(bqm); + TRACE("upolynomial", tout << "p: "; display(tout, sz, p); tout << "\nSturm seq:\n"; display(tout, seq); tout << "\n";); + + unsigned lower_sv = sign_variations_at_minus_inf(seq); + unsigned zero_sv = sign_variations_at_zero(seq); + unsigned upper_sv = sign_variations_at_plus_inf(seq); + if (upper_sv >= lower_sv) + return; // no roots + + bqm.power(mpbq(2), neg_k, curr_lower); + bqm.neg(curr_lower); + bqm.power(mpbq(2), pos_k, curr_upper); + +#define PUSH_SS_FRAME(LOWER_SV, UPPER_SV, LOWER, UPPER) { \ + SASSERT(!bqm.eq(LOWER, UPPER)); \ + if (LOWER_SV == UPPER_SV) { \ + \ + } \ + else if (LOWER_SV == UPPER_SV + 1) { \ + /* Sturm is for half-open intervals (a, b] */ \ + /* We must check if upper is the root */ \ + if (eval_sign_at(sz, p, UPPER) == 0) { \ + /* found precise root */ \ + roots.push_back(mpbq()); \ + bqm.set(roots.back(), UPPER); \ + } \ + else { \ + ss_add_isolating_interval(bqm, LOWER, UPPER, lowers, uppers); \ + } \ + } \ + else { \ + s.push_back(ss_frame()); \ + ss_frame & f = s.back(); \ + bqm.set(f.m_lower, LOWER); \ + bqm.set(f.m_upper, UPPER); \ + f.m_lower_sv = LOWER_SV; \ + f.m_upper_sv = UPPER_SV; \ + } \ + } ((void) 0) + + mpbq zero(0); + PUSH_SS_FRAME(lower_sv, zero_sv, curr_lower, zero); + PUSH_SS_FRAME(zero_sv, upper_sv, zero, curr_upper); + + while (!s.empty()) { + checkpoint(); + ss_frame & f = s.back(); + lower_sv = f.m_lower_sv; + upper_sv = f.m_upper_sv; + bqm.swap(curr_lower, f.m_lower); + bqm.swap(curr_upper, f.m_upper); + pop_ss_frame(bqm, s); + SASSERT(lower_sv > upper_sv + 1); + bqm.add(curr_lower, curr_upper, mid); + bqm.div2(mid); + TRACE("upolynomial", + tout << "depth: " << s.size() << "\n"; + tout << "lower_sv: " << lower_sv << "\n"; + tout << "upper_sv: " << upper_sv << "\n"; + tout << "curr_lower: " << curr_lower << "\n"; + tout << "curr_upper: " << curr_upper << "\n"; + tout << "mid: " << mid << "\n";); + unsigned mid_sv = sign_variations_at(seq, mid); + PUSH_SS_FRAME(lower_sv, mid_sv, curr_lower, mid); + PUSH_SS_FRAME(mid_sv, upper_sv, mid, curr_upper); + } + } + + void manager::sturm_isolate_roots(unsigned sz, numeral const * p, mpbq_manager & bqm, mpbq_vector & roots, mpbq_vector & lowers, mpbq_vector & uppers) { + SASSERT(lowers.size() == uppers.size()); + + scoped_numeral_vector p1(m()); + set(sz, p, p1); + normalize(p1); +#if 1 + unsigned pos_k = knuth_positive_root_upper_bound(sz, p); + unsigned neg_k = knuth_negative_root_upper_bound(sz, p); +#else + scoped_numeral U(m()); + root_upper_bound(p1.size(), p1.c_ptr(), U); + unsigned pos_k = m().log2(U) + 1; + unsigned neg_k = pos_k; +#endif + sturm_isolate_roots_core(p1.size(), p1.c_ptr(), neg_k, pos_k, bqm, roots, lowers, uppers); + } + + // Isolate roots of a square free polynomial that does not have zero roots + void manager::sqf_nz_isolate_roots(unsigned sz, numeral const * p, mpbq_manager & bqm, mpbq_vector & roots, mpbq_vector & lowers, mpbq_vector & uppers) { + SASSERT(!has_zero_roots(sz, p)); + drs_isolate_roots(sz, p, bqm, roots, lowers, uppers); + // sturm_isolate_roots(sz, p, bqm, roots, lowers, uppers); + } + + void manager::sqf_isolate_roots(unsigned sz, numeral const * p, mpbq_manager & bqm, mpbq_vector & roots, mpbq_vector & lowers, mpbq_vector & uppers) { + bqm.reset(roots); + bqm.reset(lowers); + bqm.reset(uppers); + if (has_zero_roots(sz, p)) { + roots.push_back(mpbq(0)); + scoped_numeral_vector nz_p(m()); + remove_zero_roots(sz, p, nz_p); + TRACE("upolynomial", tout << "after removing zero root:\n"; display(tout, nz_p); tout << "\n";); + SASSERT(!has_zero_roots(nz_p.size(), nz_p.c_ptr())); + sqf_nz_isolate_roots(nz_p.size(), nz_p.c_ptr(), bqm, roots, lowers, uppers); + } + else { + sqf_nz_isolate_roots(sz, p, bqm, roots, lowers, uppers); + } + } + + void manager::isolate_roots(unsigned sz, numeral const * p, mpbq_manager & bqm, mpbq_vector & roots, mpbq_vector & lowers, mpbq_vector & uppers) { + SASSERT(sz > 0); + TRACE("upolynomial", tout << "isolating roots of:\n"; display(tout, sz, p); tout << "\n";); + scoped_numeral_vector sqf_p(m()); + square_free(sz, p, sqf_p); + TRACE("upolynomial", tout << "square free part:\n"; display(tout, sqf_p); tout << "\n";); + sqf_isolate_roots(sqf_p.size(), sqf_p.c_ptr(), bqm, roots, lowers, uppers); + } + + // Keep expanding the Sturm sequence starting at seq + void manager::sturm_seq_core(upolynomial_sequence & seq) { + scoped_numeral_vector r(m()); + while (true) { + unsigned sz = seq.size(); + srem(seq.size(sz-2), seq.coeffs(sz-2), seq.size(sz-1), seq.coeffs(sz-1), r); + if (is_zero(r)) + return; + normalize(r); + seq.push(r.size(), r.c_ptr()); + } + } + + void manager::sturm_seq(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, upolynomial_sequence & seq) { + reset(seq); + seq.push(m(), sz1, p1); + seq.push(m(), sz2, p2); + sturm_seq_core(seq); + } + + void manager::sturm_seq(unsigned sz, numeral const * p, upolynomial_sequence & seq) { + reset(seq); + scoped_numeral_vector p_prime(m()); + seq.push(m(), sz, p); + derivative(sz, p, p_prime); + seq.push(p_prime.size(), p_prime.c_ptr()); + sturm_seq_core(seq); + } + + void manager::sturm_tarski_seq(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, upolynomial_sequence & seq) { + reset(seq); + scoped_numeral_vector p1p2(m()); + seq.push(m(), sz1, p1); + derivative(sz1, p1, p1p2); + mul(p1p2.size(), p1p2.c_ptr(), sz2, p2, p1p2); + seq.push(p1p2.size(), p1p2.c_ptr()); + sturm_seq_core(seq); + } + + void manager::fourier_seq(unsigned sz, numeral const * p, upolynomial_sequence & seq) { + reset(seq); + scoped_numeral_vector p_prime(m()); + seq.push(m(), sz, p); + if (sz == 0) + return; + unsigned degree = sz - 1; + for (unsigned i = 0; i < degree; i++) { + unsigned sz = seq.size(); + derivative(seq.size(sz-1), seq.coeffs(sz-1), p_prime); + normalize(p_prime); + seq.push(p_prime.size(), p_prime.c_ptr()); + } + } + + /** + We say an interval (a, b) of a polynomial p is ISOLATING if p has only one root in the + interval (a, b). + + We say an isolating interval (a, b) of a square free polynomial p is REFINEABLE if + sign(p(a)) = -sign(p(b)) + + Not every isolating interval (a, b) of a square free polynomial p is refineable, because + sign(p(a)) or sign(p(b)) may be zero. + + Refinable intervals of square free polynomials are useful, because we can increase precision + ("squeeze" the interval) by just evaluating p at (a+b)/2 + + This procedure converts an isolating interval of a square free polynomial p, into + a refinable interval, or an actual root. + + The method returns TRUE if it produced a REFINABLE interval (a', b'). The new interval + is stored in input variables a and b. + + The method returns FALSE if it found the root a' in the interval (a, b). The root is + stored in the input variable a. + + \pre p MUST BE SQUARE FREE. + + \warning This method may loop if p is not square free or if (a,b) is not an isolating interval. + */ + bool manager::isolating2refinable(unsigned sz, numeral const * p, mpbq_manager & bqm, mpbq & a, mpbq & b) { + int sign_a = eval_sign_at(sz, p, a); + int sign_b = eval_sign_at(sz, p, b); + TRACE("upolynomial", tout << "sign_a: " << sign_a << ", sign_b: " << sign_b << "\n";); + if (sign_a != 0 && sign_b != 0) { + // CASE 1 + SASSERT(sign_a == -sign_b); // p is square free + return true; + } + if (sign_a == 0 && sign_b != 0) { + // CASE 2 + scoped_mpbq new_a(bqm); + // new_a <- (a+b)/2 + bqm.add(a, b, new_a); + bqm.div2(new_a); + while (true) { + TRACE("upolynomial", tout << "CASE 2, a: " << bqm.to_string(a) << ", b: " << bqm.to_string(b) << ", new_a: " << bqm.to_string(new_a) << "\n";); + int sign_new_a = eval_sign_at(sz, p, new_a); + if (sign_new_a != sign_b) { + swap(new_a, a); + SASSERT(sign_new_a == 0 || // found the actual root + sign_new_a == -sign_b); // found refinable interval + return sign_new_a != 0; + } + else { + SASSERT(sign_new_a == sign_b); + // b <- new_a + // new_a <- (a+b)/2 + swap(b, new_a); + bqm.add(b, a, new_a); + bqm.div2(new_a); + } + } + } + if (sign_a != 0 && sign_b == 0 ) { + // CASE 3 + scoped_mpbq new_b(bqm); + // new_b <- (a+b)/2 + bqm.add(a, b, new_b); + bqm.div2(new_b); + while (true) { + TRACE("upolynomial", tout << "CASE 3, a: " << bqm.to_string(a) << ", b: " << bqm.to_string(b) << ", new_b: " << bqm.to_string(new_b) << "\n";); + int sign_new_b = eval_sign_at(sz, p, new_b); + if (sign_new_b != sign_a) { + SASSERT(sign_new_b == 0 || // found the actual root + sign_new_b == -sign_a); // found refinable interval + if (sign_new_b == 0) + swap(new_b, a); // store root in a + else + swap(new_b, b); + return sign_new_b != 0; + } + else { + SASSERT(sign_new_b == sign_a); + // a <- new_b + // new_b <- (a+b)/2 + swap(a, new_b); + bqm.add(b, a, new_b); + bqm.div2(new_b); + } + } + } + SASSERT(sign_a == 0 && sign_b == 0); + // CASE 4 + // we do cases 3 and 4 in "parallel" + mpbq & a1 = a; + scoped_mpbq b1(bqm); + scoped_mpbq a2(bqm); + mpbq & b2 = b; + scoped_mpbq new_a1(bqm); + scoped_mpbq new_b2(bqm); + // b1 <- (a+b)/2 + bqm.add(a, b, b1); + bqm.div2(b1); + // a2 <- (a+b)/2 + bqm.set(a2, b1); + int sign_b1 = eval_sign_at(sz, p, b1); + int sign_a2 = sign_b1; + bool result; + if (sign_b1 == 0) { + // found root + swap(b1, a); + result = false; + goto end; + } + + // new_a1 <- (a1+b1)/2 + bqm.add(a1, b1, new_a1); + bqm.div2(new_a1); + // new_b2 <- (a2+b2)/2 + bqm.add(a2, b2, new_b2); + bqm.div2(new_b2); + + while (true) { + TRACE("upolynomial", + tout << "CASE 4\na1: " << bqm.to_string(a1) << ", b1: " << bqm.to_string(b1) << ", new_a1: " << bqm.to_string(new_a1) << "\n"; + tout << "a2: " << bqm.to_string(a2) << ", b2: " << bqm.to_string(b2) << ", new_b2: " << bqm.to_string(new_b2) << "\n";); + int sign_new_a1 = eval_sign_at(sz, p, new_a1); + if (sign_new_a1 == 0) { + // found root + swap(new_a1, a); + result = false; + goto end; + } + if (sign_new_a1 == -sign_b1) { + // found interval + swap(new_a1, a); + swap(b1, b); + result = true; + goto end; + } + + int sign_new_b2 = eval_sign_at(sz, p, new_b2); + if (sign_new_b2 == 0) { + // found root + swap(new_b2, a); + result = false; + goto end; + } + + if (sign_new_b2 == -sign_a2) { + // found interval + swap(a2, a); + swap(new_b2, b); + result = true; + goto end; + } + + SASSERT(sign_new_a1 == sign_b1); + // b1 <- new_a1 + // new_a1 <- (a1+b1)/2 + swap(b1, new_a1); + bqm.add(b1, a1, new_a1); + bqm.div2(new_a1); + + SASSERT(sign_new_b2 == sign_a2); + // a2 <- new_b2 + // new_b2 <- (a2+b2)/2 + swap(a2, new_b2); + bqm.add(b2, a2, new_b2); + bqm.div2(new_b2); + } + + end: + return result; + } + + /** + Given a square-free polynomial p, and a refinable interval (a,b), then "squeeze" (a,b). + That is, return a new interval (a',b') s.t. b' - a' = (b - a)/2 + See isolating2refinable for a definition of refinable interval. + + Return TRUE, if interval was squeezed, and new interval is stored in (a,b). + Return FALSE, if the actual root was found, it is stored in a. + + The arguments sign_a and sign_b must contain the values returned by + eval_sign_at(sz, p, a) and eval_sign_at(sz, p, b). + */ + bool manager::refine_core(unsigned sz, numeral const * p, int sign_a, mpbq_manager & bqm, mpbq & a, mpbq & b) { + SASSERT(sign_a == eval_sign_at(sz, p, a)); + int sign_b = -sign_a; + SASSERT(sign_b == eval_sign_at(sz, p, b)); + SASSERT(sign_a == -sign_b); + SASSERT(sign_a != 0 && sign_b != 0); + scoped_mpbq mid(bqm); + bqm.add(a, b, mid); + bqm.div2(mid); + int sign_mid = eval_sign_at(sz, p, mid); + if (sign_mid == 0) { + swap(mid, a); + return false; + } + if (sign_mid == sign_a) { + swap(mid, a); + return true; + } + SASSERT(sign_mid == sign_b); + swap(mid, b); + return true; + } + + // See refine_core + bool manager::refine(unsigned sz, numeral const * p, mpbq_manager & bqm, mpbq & a, mpbq & b) { + int sign_a = eval_sign_at(sz, p, a); + SASSERT(sign_a != 0); + return refine_core(sz, p, sign_a, bqm, a, b); + } + + // Keeps reducing the interval until b - a < 1/2^k or a root is found. + // See comments in refine_core above. + // + // Return TRUE, if interval was squeezed, and new interval is stored in (a,b). + // Return FALSE, if the actual root was found, it is stored in a. + bool manager::refine_core(unsigned sz, numeral const * p, int sign_a, mpbq_manager & bqm, mpbq & a, mpbq & b, unsigned prec_k) { + SASSERT(sign_a != 0); + SASSERT(sign_a == eval_sign_at(sz, p, a)); + SASSERT(-sign_a == eval_sign_at(sz, p, b)); + scoped_mpbq w(bqm); + while (true) { + checkpoint(); + bqm.sub(b, a, w); + if (bqm.lt_1div2k(w, prec_k)) { + return true; + } + if (!refine_core(sz, p, sign_a, bqm, a, b)) { + return false; // found root + } + } + } + + bool manager::refine(unsigned sz, numeral const * p, mpbq_manager & bqm, mpbq & a, mpbq & b, unsigned prec_k) { + int sign_a = eval_sign_at(sz, p, a); + int sign_b = -sign_a; + SASSERT(eval_sign_at(sz, p, b) == sign_b); + SASSERT(sign_a != 0 && sign_b != 0); + return refine_core(sz, p, sign_a, bqm, a, b, prec_k); + } + + bool manager::convert_q2bq_interval(unsigned sz, numeral const * p, mpq const & a, mpq const & b, mpbq_manager & bqm, mpbq & c, mpbq & d) { + int sign_a = eval_sign_at(sz, p, a); + int sign_b = eval_sign_at(sz, p, b); + SASSERT(sign_a != 0 && sign_b != 0); + SASSERT(sign_a == -sign_b); + bool found_d = false; + TRACE("convert_bug", + tout << "a: " << m().to_string(a.numerator()) << "/" << m().to_string(a.denominator()) << "\n"; + tout << "b: " << m().to_string(b.numerator()) << "/" << m().to_string(b.denominator()) << "\n"; + tout << "sign_a: " << sign_a << "\n"; + tout << "sign_b: " << sign_b << "\n";); + scoped_mpbq lower(bqm), upper(bqm); + if (bqm.to_mpbq(a, lower)) { + TRACE("convert_bug", tout << "found c: " << lower << "\n";); + // found c + swap(c, lower); + SASSERT(bqm.eq(c, a)); + } + else { + // lower = a.numerator()/2^(k+1) where k is log2(a) + bqm.set(upper, lower); + bqm.mul2(upper); + if (m_manager.is_neg(a.numerator())) + ::swap(lower, upper); + TRACE("convert_bug", + tout << "a: "; m().display(tout, a.numerator()); tout << "/"; m().display(tout, a.denominator()); tout << "\n"; + tout << "lower: "; bqm.display(tout, lower); tout << ", upper: "; bqm.display(tout, upper); tout << "\n";); + SASSERT(bqm.lt(lower, a)); + SASSERT(bqm.gt(upper, a)); + while (bqm.ge(upper, b)) { + bqm.refine_upper(a, lower, upper); + } + SASSERT(bqm.lt(upper, b)); + while (true) { + int sign_upper = eval_sign_at(sz, p, upper); + if (sign_upper == 0) { + // found root + bqm.swap(c, upper); + bqm.del(lower); bqm.del(upper); + return false; + } + else if (sign_upper == sign_a) { + // found c + bqm.swap(c, upper); + break; + } + else { + SASSERT(sign_upper == sign_b); + // found d + if (!found_d) { + found_d = true; + bqm.set(d, upper); + } + } + bqm.refine_upper(a, lower, upper); + } + } + + if (!found_d) { + if (bqm.to_mpbq(b, lower)) { + // found d + swap(d, lower); + SASSERT(bqm.eq(d, b)); + } + else { + // lower = b.numerator()/2^(k+1) where k is log2(b) + bqm.set(upper, lower); + bqm.mul2(upper); + if (m_manager.is_neg(b.numerator())) + ::swap(lower, upper); + SASSERT(bqm.gt(upper, b)); + SASSERT(bqm.lt(lower, b)); + while (bqm.le(lower, c)) { + bqm.refine_lower(b, lower, upper); + } + SASSERT(bqm.lt(c, lower)); + SASSERT(bqm.lt(lower, upper)); + SASSERT(bqm.lt(lower, b)); + while (true) { + int sign_lower = eval_sign_at(sz, p, lower); + if (sign_lower == 0) { + // found root + bqm.swap(c, lower); + bqm.del(lower); bqm.del(upper); + return false; + } + else if (sign_lower == sign_b) { + // found d + bqm.swap(d, lower); + break; + } + bqm.refine_lower(b, lower, upper); + } + } + } + SASSERT(bqm.lt(c, d)); + SASSERT(eval_sign_at(sz, p, c) == -eval_sign_at(sz, p, d)); + SASSERT(bqm.ge(c, a)); + SASSERT(bqm.le(d, b)); + return true; + } + + bool manager::normalize_interval_core(unsigned sz, numeral const * p, int sign_a, mpbq_manager & m, mpbq & a, mpbq & b) { + if (m.is_neg(a) && m.is_pos(b)) { + // See bool manager::normalize_interval(unsigned sz, numeral const * p, mpbq_manager & m, mpbq const & a, mpbq const & b) { + if (sign_a == INT_MIN) { + sign_a = eval_sign_at(sz, p, a); + } + else { + SASSERT(sign_a == eval_sign_at(sz, p, a)); + } + int sign_b = -sign_a; + SASSERT(sign_b == eval_sign_at(sz, p, b)); + SASSERT(sign_a != 0 && sign_b != 0); + if (has_zero_roots(sz, p)) { + return false; // zero is the root + } + int sign_zero = eval_sign_at_zero(sz, p); + if (sign_a == sign_zero) { + m.reset(a); + } + else { + m.reset(b); + } + SASSERT(eval_sign_at(sz, p, a) == -eval_sign_at(sz, p, b)); + } + return true; + } + + bool manager::normalize_interval(unsigned sz, numeral const * p, mpbq_manager & m, mpbq & a, mpbq & b) { + return normalize_interval_core(sz, p, INT_MIN, m, a, b); + } + + unsigned manager::get_root_id(unsigned sz, numeral const * p, mpbq const & l) { + scoped_upolynomial_sequence seq(*this); + sturm_seq(sz, p, seq); + unsigned s0 = sign_variations_at_minus_inf(seq); + unsigned s1 = sign_variations_at(seq, l); + SASSERT(s0 >= s1); + return s0 - s1; + } + + void manager::flip_sign(factors & r) { + scoped_numeral new_c(m_manager); + m_manager.set(new_c, r.get_constant()); + m_manager.neg(new_c); + r.set_constant(new_c); + } + + void manager::factor_2_sqf_pp(numeral_vector & p, factors & r, unsigned k) { + SASSERT(p.size() == 3); // p has degree 2 + TRACE("factor", tout << "factor square free (degree == 2):\n"; display(tout, p); tout << "\n";); + + numeral const & a = p[2]; + numeral const & b = p[1]; + numeral const & c = p[0]; + TRACE("factor", tout << "a: " << m().to_string(a) << "\nb: " << m().to_string(b) << "\nc: " << m().to_string(c) << "\n";); + // Create the discriminant: b^2 - 4*a*c + scoped_numeral b2(m()); + scoped_numeral ac(m()); + scoped_numeral disc(m()); + m().power(b, 2, b2); + m().mul(a, c, ac); + m().addmul(b2, numeral(-4), ac, disc); + // discriminant must be different from 0, since p is square free + SASSERT(!m().is_zero(disc)); + scoped_numeral disc_sqrt(m()); + if (!m().is_perfect_square(disc, disc_sqrt)) { + // p is irreducible + r.push_back(p, k); + return; + } + TRACE("factor", tout << "disc_sqrt: " << m().to_string(disc_sqrt) << "\n";); + // p = cont*(2*a*x + b - disc_sqrt)*(2*a*x + b + disc_sqrt) + scoped_numeral_vector f1(m()); + scoped_numeral_vector f2(m()); + f1.reserve(2); f2.reserve(2); + m().sub(b, disc_sqrt, f1[0]); + m().add(b, disc_sqrt, f2[0]); + m().mul(a, numeral(2), f1[1]); + m().mul(a, numeral(2), f2[1]); + set_size(2, f1); + set_size(2, f2); + normalize(f1); + normalize(f2); + TRACE("factor", tout << "f1: "; display(tout, f1); tout << "\nf2: "; display(tout, f2); tout << "\n";); + DEBUG_CODE({ + scoped_numeral_vector f1f2(m()); + mul(f1, f2, f1f2); + SASSERT(eq(f1f2, p)); + }); + r.push_back(f1, k); + r.push_back(f2, k); + } + + bool manager::factor_sqf_pp(numeral_vector & p, factors & r, unsigned k, factor_params const & params) { + unsigned sz = p.size(); + SASSERT(sz == 0 || m().is_pos(p[sz-1])); + if (sz <= 2) { + // linear or constant + r.push_back(p, k); + return true; + } + else if (sz == 3) { + factor_2_sqf_pp(p, r, k); + return true; + } + else { + return factor_square_free(*this, p, r, k, params); + } + } + + void manager::flip_factor_sign_if_lm_neg(numeral_vector & p, factors & r, unsigned k) { + unsigned sz = p.size(); + if (sz == 0) + return; + if (m().is_neg(p[sz - 1])) { + for (unsigned i = 0; i < sz; i++) + m().neg(p[i]); + if (k % 2 == 1) + flip_sign(r); + } + } + + bool manager::factor_core(unsigned sz, numeral const * p, factors & r, factor_params const & params) { + if (sz == 0) { + r.set_constant(numeral(0)); + return true; + } + if (sz == 1) { + r.set_constant(p[0]); + return true; + } + // extract content & primitive part + scoped_numeral content(m()); + scoped_numeral_vector pp(m()); + get_primitive_and_content(sz, p, pp, content); + r.set_constant(content); + // + scoped_numeral_vector & C = pp; + // Let C be a primitive polynomial of the form: P_1^1 * P_2^2 * ... * P_k^k, where each P_i is square free + scoped_numeral_vector C_prime(m()); + derivative(C, C_prime); + scoped_numeral_vector A(m()), B(m()), D(m()); + gcd(C, C_prime, B); + + bool result = true; + if (is_const(B)) { + // C must be of the form P_1 (square free) + SASSERT(!is_const(C)); + flip_factor_sign_if_lm_neg(C, r, 1); + if (!factor_sqf_pp(C, r, 1, params)) + result = false; + } + else { + // B is of the form P_2 * P_3^2 * ... * P_k^{k-1} + VERIFY(exact_div(C, B, A)); + TRACE("factor_bug", tout << "C: "; display(tout, C); tout << "\nB: "; display(tout, B); tout << "\nA: "; display(tout, A); tout << "\n";); + // A is of the form P_1 * P_2 * ... * P_k + unsigned j = 1; + while (!is_const(A)) { + checkpoint(); + TRACE("factor", tout << "factor_core main loop j: " << j << "\nA: "; display(tout, A); tout << "\nB: "; display(tout, B); tout << "\n";); + // A is of the form P_j * P_{j+1} * P_{j+2} * ... * P_k + // B is of the form P_{j+1} * P_{j+2}^2 * ... * P_k^{k - j - 2} + gcd(A, B, D); + // D is of the form P_{j+1} * P_{j+2} * ... * P_k + VERIFY(exact_div(A, D, C)); + // C is of the form P_j + if (!is_const(C)) { + flip_factor_sign_if_lm_neg(C, r, j); + if (!factor_sqf_pp(C, r, j, params)) + result = false; + } + else { + TRACE("factor", tout << "const C: "; display(tout, C); tout << "\n";); + SASSERT(C.size() == 1); + SASSERT(m().is_one(C[0]) || m().is_minus_one(C[0])); + if (m().is_minus_one(C[0]) && j % 2 == 1) + flip_sign(r); + } + TRACE("factor_bug", tout << "B: "; display(tout, B); tout << "\nD: "; display(tout, D); tout << "\n";); + VERIFY(exact_div(B, D, B)); + // B is of the form P_{j+2} * ... * P_k^{k - j - 3} + A.swap(D); + // D is of the form P_{j+1} * P_{j+2} * ... * P_k + j++; + } + TRACE("factor_bug", tout << "A: "; display(tout, A); tout << "\n";); + SASSERT(A.size() == 1 && m().is_one(A[0])); + } + return result; + } + + bool manager::factor(unsigned sz, numeral const * p, factors & r, factor_params const & params) { + bool result = factor_core(sz, p, r, params); +#ifndef _EXTERNAL_RELEASE + IF_VERBOSE(FACTOR_VERBOSE_LVL, verbose_stream() << "(polynomial-factorization :distinct-factors " << r.distinct_factors() << ")" << std::endl;); +#endif + return result; + } + + void manager::display(std::ostream & out, upolynomial_sequence const & seq, char const * var_name) const { + for (unsigned i = 0; i < seq.size(); i++) { + display(out, seq.size(i), seq.coeffs(i), var_name); + out << "\n"; + } + } +}; + diff --git a/lib/upolynomial.h b/lib/upolynomial.h new file mode 100644 index 000000000..697385a10 --- /dev/null +++ b/lib/upolynomial.h @@ -0,0 +1,922 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + upolynomial.h + +Abstract: + + Goodies for creating and handling univariate polynomials. + + A dense representation is much better for Root isolation algorithms, + encoding algebraic numbers, factorization, etc. + + We also use integers as the coefficients of univariate polynomials. + +Author: + + Leonardo (leonardo) 2011-11-29 + +Notes: + +--*/ +#ifndef _UPOLYNOMIAL_H_ +#define _UPOLYNOMIAL_H_ + +#include"mpzzp.h" +#include"rational.h" +#include"polynomial.h" +#include"z3_exception.h" +#include"mpbq.h" +#define FACTOR_VERBOSE_LVL 1000 + +namespace upolynomial { + + typedef polynomial::factor_params factor_params; + + // It is used only for signing cancellation. + class upolynomial_exception : public default_exception { + public: + upolynomial_exception(char const * msg):default_exception(msg) {} + }; + + typedef mpz numeral; + typedef mpzzp_manager numeral_manager; + typedef mpzzp_manager zp_numeral_manager; + typedef unsynch_mpz_manager z_numeral_manager; + typedef svector numeral_vector; + + class core_manager { + public: + typedef _scoped_numeral_vector scoped_numeral_vector; + typedef _scoped_numeral scoped_numeral; + /** + \brief Convenient vector of polynomials that manages its own memory and keeps the degree, of each polynomial. + Polynomial is c*f_1^k_1*...*f_n^k_n. + */ + class factors { + private: + vector m_factors; + svector m_degrees; + core_manager & m_upm; + numeral m_constant; + unsigned m_total_factors; + unsigned m_total_degree; + public: + factors(core_manager & upm); + ~factors(); + + core_manager & upm() const { return m_upm; } + core_manager & pm() const { return m_upm; } + numeral_manager & nm() const; + + unsigned distinct_factors() const { return m_factors.size(); } + unsigned total_factors() const { return m_total_factors; } + void clear(); + void reset() { clear(); } + + numeral_vector const & operator[](unsigned i) const { return m_factors[i]; } + + numeral const & get_constant() const { return m_constant; } + void set_constant(numeral const & constant); + + unsigned get_degree() const { return m_total_degree; } + unsigned get_degree(unsigned i) const { return m_degrees[i]; } + void set_degree(unsigned i, unsigned degree); + void push_back(numeral_vector const & p, unsigned degree); + // push p to vectors and kill it + void push_back_swap(numeral_vector & p, unsigned degree); + + void swap_factor(unsigned i, numeral_vector & p); + void swap(factors & other); + void multiply(numeral_vector & out) const; + + void display(std::ostream & out) const; + + friend std::ostream & operator<<(std::ostream & out, factors const & fs) { + fs.display(out); + return out; + } + }; + + protected: + numeral_manager m_manager; + numeral_vector m_basic_tmp; + numeral_vector m_div_tmp1; + numeral_vector m_div_tmp2; + numeral_vector m_exact_div_tmp; + numeral_vector m_gcd_tmp1; + numeral_vector m_gcd_tmp2; + numeral_vector m_CRA_tmp; + #define UPOLYNOMIAL_MGCD_TMPS 6 + numeral_vector m_mgcd_tmp[UPOLYNOMIAL_MGCD_TMPS]; + numeral_vector m_sqf_tmp1; + numeral_vector m_sqf_tmp2; + numeral_vector m_pw_tmp; + volatile bool m_cancel; + + static bool is_alias(numeral const * p, numeral_vector & buffer) { return buffer.c_ptr() != 0 && buffer.c_ptr() == p; } + void neg_core(unsigned sz1, numeral const * p1, numeral_vector & buffer); + void add_core(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & buffer); + void sub_core(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & buffer); + void mul_core(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & buffer); + + void flip_sign_if_lm_neg(numeral_vector & buffer); + + void mod_gcd(unsigned sz_u, numeral const * u, unsigned sz_v, numeral const * v, numeral_vector & result); + void CRA_combine_images(numeral_vector const & q, numeral const & p, numeral_vector & C, numeral & bound); + + public: + core_manager(z_numeral_manager & m); + ~core_manager(); + + z_numeral_manager & zm() const { return m_manager.m(); } + numeral_manager & m() const { return const_cast(this)->m_manager; } + + /** + \brief Return true if Z_p[X] + */ + bool modular() const { return m().modular(); } + bool field() const { return m().field(); } + /** + \brief Return p in Z_p[X] + \pre modular + */ + numeral const & p() const { return m().p(); } + /** + \brief Set manager as Z[X] + */ + void set_z() { m().set_z(); } + /** + \brief Set manager as Z_p[X] + */ + void set_zp(numeral const & p) { m().set_zp(p); } + void set_zp(uint64 p) { m().set_zp(p); } + + void checkpoint(); + + void set_cancel(bool f); + + /** + \brief set p size to 0. That is, p is the zero polynomial after this operation. + */ + void reset(numeral_vector & p); + + /** + \brief Remove zero leading coefficients. + After applying this method, we have that p is empty() or p[p.size()-1] is not zero. + */ + void trim(numeral_vector & p); + + void set_size(unsigned sz, numeral_vector & buffer); + + /** + \brief Return the actual degree of p. + */ + unsigned degree(numeral_vector const & p) { + unsigned sz = p.size(); + return sz == 0 ? 0 : sz - 1; + } + + /** + \brief Return true if p is the zero polynomial. + */ + bool is_zero(numeral_vector & p) { trim(p); return p.empty(); } + + /** + \brief Return true if p is a constant polynomial + */ + bool is_const(numeral_vector & p) { trim(p); return p.size() <= 1; } + + /** + \brief Copy p to buffer. + */ + void set(unsigned sz, numeral const * p, numeral_vector & buffer); + void set(numeral_vector & target, numeral_vector const & source) { set(source.size(), source.c_ptr(), target); } + + /** + \brief Copy p to buffer. + + Coefficients of p must be integer. + */ + void set(unsigned sz, rational const * p, numeral_vector & buffer); + + /** + \brief Compute the primitive part and the content of f (pp can alias f). + */ + void get_primitive_and_content(unsigned f_sz, numeral const * f, numeral_vector & pp, numeral & cont); + void get_primitive_and_content(numeral_vector const & f, numeral_vector & pp, numeral & cont) { + get_primitive_and_content(f.size(), f.c_ptr(), pp, cont); + } + void get_primitive(numeral_vector const & f, numeral_vector & pp) { + scoped_numeral cont(m()); + get_primitive_and_content(f.size(), f.c_ptr(), pp, cont); + } + + /** + \brief p := -p + */ + void neg(unsigned sz, numeral * p); + void neg(numeral_vector & p) { neg(p.size(), p.c_ptr()); } + + /** + \brief buffer := -p + */ + void neg(unsigned sz, numeral const * p, numeral_vector & buffer); + void neg(numeral_vector const & p, numeral_vector & p_neg) { neg(p.size(), p.c_ptr(), p_neg); } + + /** + \brief buffer := p1 + p2 + */ + void add(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & buffer); + void add(numeral_vector const & a, numeral_vector const & b, numeral_vector & c) { add(a.size(), a.c_ptr(), b.size(), b.c_ptr(), c); } + + /** + \brief buffer := p1 - p2 + */ + void sub(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & buffer); + void sub(numeral_vector const & a, numeral_vector const & b, numeral_vector & c) { sub(a.size(), a.c_ptr(), b.size(), b.c_ptr(), c); } + + /** + \brief buffer := p1 * p2 + */ + void mul(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & buffer); + void mul(numeral_vector const & a, numeral_vector const & b, numeral_vector & c) { mul(a.size(), a.c_ptr(), b.size(), b.c_ptr(), c); } + + /** + \brief r := p^k + */ + void pw(unsigned sz, numeral const * p, unsigned k, numeral_vector & r); + + /** + \brief buffer := dp/dx + */ + void derivative(unsigned sz1, numeral const * p, numeral_vector & buffer); + void derivative(numeral_vector const & p, numeral_vector & d_p) { derivative(p.size(), p.c_ptr(), d_p); } + + /** + \brief Divide coeffients of p by their GCD + */ + void normalize(unsigned sz, numeral * p); + + /** + \brief Divide coeffients of p by their GCD + */ + void normalize(numeral_vector & p); + + /** + \brief Divide the coefficients of p by b. + This method assumes that every coefficient of p is a multiple of b, and b != 0. + */ + void div(unsigned sz, numeral * p, numeral const & b); + void div(numeral_vector & p, numeral const & b) { div(p.size(), p.c_ptr(), b); } + + /** + \brief Multiply the coefficients of p by b. + + This method assume b != 0. + */ + void mul(unsigned sz, numeral * p, numeral const & b); + + /** + \brief Multiply the coefficients of p by b. + If b == 0, it is equivalent to a reset() + */ + void mul(numeral_vector & p, numeral const & b); + + /** + \brief Similar to div_rem but p1 and p2 must not be q and r. + */ + void div_rem_core(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, unsigned & d, numeral_vector & q, numeral_vector & r); + + /** + \brief If numeral is a field, then + return q and r s.t. p1 = q*p2 + r + And degree(r) < degree(p2). + + If numeral is not a field, then + return q and r s.t. (b_m)^d * p1 = q * p2 + r + where b_m is the leading coefficient of p2 and d <= sz1 - sz2 + 1 + if sz1 >= sz2. + + The output value d is irrelevant if numeral is a field. + */ + void div_rem(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, unsigned & d, numeral_vector & q, numeral_vector & r); + + /** + \see div_rem + */ + void div_rem(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & q, numeral_vector & r) { + unsigned d = 0; + div_rem(sz1, p1, sz2, p2, d, q, r); + } + + void div_rem(numeral_vector const & p1, numeral_vector const & p2, numeral_vector & q, numeral_vector & r) { + div_rem(p1.size(), p1.c_ptr(), p2.size(), p2.c_ptr(), q, r); + } + + /** + \see div_rem + */ + void div(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & q); + + /** + \see div_rem + */ + void rem(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, unsigned & d, numeral_vector & r); + + /** + \see div_rem + */ + void rem(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & r) { + unsigned d = 0; + rem(sz1, p1, sz2, p2, d, r); + } + + /** + \brief Signed pseudo-remainder. + Alias for rem(sz1, p1, sz2, p2, r); neg(r); + */ + void srem(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & r); + + /** + \brief Return true if p2 divides p1. + */ + bool divides(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2); + bool divides(numeral_vector const & p1, numeral_vector const & p2) { return divides(p1.size(), p1.c_ptr(), p2.size(), p2.c_ptr()); } + + /** + \brief Return true if p2 divides p1, and store the quotient in q. + */ + bool exact_div(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & q); + bool exact_div(numeral_vector const & p1, numeral_vector const & p2, numeral_vector & q) { + return exact_div(p1.size(), p1.c_ptr(), p2.size(), p2.c_ptr(), q); + } + + /** + \brief Assuming that we can, make the polynomial monic by dividing with the leading coefficient. It + puts the leading coefficient into lc, and it's inverse into lc_inv. + */ + void mk_monic(unsigned sz, numeral * p, numeral & lc, numeral & lc_inv); + void mk_monic(unsigned sz, numeral * p, numeral & lc) { numeral lc_inv; mk_monic(sz, p, lc, lc_inv); m().del(lc_inv); } + void mk_monic(unsigned sz, numeral * p) { numeral lc, lc_inv; mk_monic(sz, p, lc, lc_inv); m().del(lc); m().del(lc_inv); } + void mk_monic(numeral_vector & p) { mk_monic(p.size(), p.c_ptr()); } + + /** + \brief g := gcd(p1, p2) + If in a field the coefficients don't matter, so we also make sure that D is monic. + */ + void gcd(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & g); + void euclid_gcd(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & g); + void subresultant_gcd(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, numeral_vector & g); + void gcd(numeral_vector const & p1, numeral_vector const & p2, numeral_vector & g) { + gcd(p1.size(), p1.c_ptr(), p2.size(), p2.c_ptr(), g); + } + void subresultant_gcd(numeral_vector const & p1, numeral_vector const & p2, numeral_vector & g) { + subresultant_gcd(p1.size(), p1.c_ptr(), p2.size(), p2.c_ptr(), g); + } + + /** + \brief g := square free part of p + */ + void square_free(unsigned sz, numeral const * p, numeral_vector & g); + + /** + \brief Return true if p is a square-free polynomial. + */ + bool is_square_free(unsigned sz, numeral const * p); + + /** + \brief Return true if p is a square-free polynomial. + */ + bool is_square_free(numeral_vector const & p) { + return is_square_free(p.size(), p.c_ptr()); + } + + /** + \brief Convert a multi-variate polynomial (that is) actually representing an univariate polynomial + into a vector of coefficients. + */ + template + void to_numeral_vector(polynomial_ref const & p, numeral_vector & r) { + typename polynomial_ref::manager & pm = p.m(); + SASSERT(pm.is_univariate(p)); + polynomial_ref np(pm); + np = pm.normalize(p); + unsigned sz = pm.size(p); + unsigned deg = pm.total_degree(p); + r.reserve(deg+1); + for (unsigned i = 0; i <= deg; i++) { + m().reset(r[i]); + } + for (unsigned i = 0; i < sz; i++) { + unsigned k = pm.total_degree(pm.get_monomial(p, i)); + SASSERT(k <= deg); + m().set(r[k], pm.coeff(p, i)); + } + set_size(deg+1, r); + } + + /** + \brief Convert a multi-variate polynomial in [x, y1, ..., yn] to a univariate polynomial in just x + by removing everything multivariate. + */ + template + void to_numeral_vector(polynomial_ref const & p, polynomial::var x, numeral_vector & r) { + typename polynomial_ref::manager & pm = p.m(); + polynomial_ref np(pm); + np = pm.normalize(p); + unsigned sz = pm.size(p); + unsigned deg = pm.degree(p, x); + r.reserve(deg+1); + for (unsigned i = 0; i <= deg; i++) { + m().reset(r[i]); + } + for (unsigned i = 0; i < sz; i++) { + typename polynomial::monomial * mon = pm.get_monomial(p, i); + if (pm.size(mon) == 0) { + m().set(r[0], pm.coeff(p, i)); + } else if (pm.size(mon) == 1 && pm.get_var(mon, 0) == x) { + unsigned m_deg_x = pm.degree(mon, 0); + m().set(r[m_deg_x], pm.coeff(p, i)); + } + } + set_size(deg+1, r); + } + + /** + \brief Extended GCD + This method assumes that numeral is a field. + It determines U, V, D such that + A*U + B*V = D and D is the GCD of A and B. + Since in a field the coefficients don't matter, we also make sure that D is monic. + */ + void ext_gcd(unsigned szA, numeral const * A, unsigned szB, numeral const * B, numeral_vector & U, numeral_vector & V, numeral_vector & D); + void ext_gcd(numeral_vector const & A, numeral_vector const & B, numeral_vector & U, numeral_vector & V, numeral_vector & D) { + ext_gcd(A.size(), A.c_ptr(), B.size(), B.c_ptr(), U, V, D); + } + + bool eq(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2); + bool eq(numeral_vector const & p1, numeral_vector const & p2) { return eq(p1.size(), p1.c_ptr(), p2.size(), p2.c_ptr()); } + + void display(std::ostream & out, unsigned sz, numeral const * p, char const * var_name = "x", bool use_star = false) const; + void display(std::ostream & out, numeral_vector const & p, char const * var_name = "x") const { display(out, p.size(), p.c_ptr(), var_name); } + void display_star(std::ostream & out, unsigned sz, numeral const * p) { display(out, sz, p, "x", true); } + void display_star(std::ostream & out, numeral_vector const & p) { display_star(out, p.size(), p.c_ptr()); } + + void display_smt2(std::ostream & out, unsigned sz, numeral const * p, char const * var_name = "x") const; + void display_smt2(std::ostream & out, numeral_vector const & p, char const * var_name = "x") const { + return display_smt2(out, p.size(), p.c_ptr(), var_name); + } + }; + + class scoped_set_z { + core_manager & m; + bool m_modular; + core_manager::scoped_numeral m_p; + public: + scoped_set_z(core_manager & _m):m(_m), m_modular(m.modular()), m_p(m.m()) { m_p = m.p(); m.set_z(); } + ~scoped_set_z() { if (m_modular) m.set_zp(m_p); } + }; + + class scoped_set_zp { + core_manager & m; + bool m_modular; + core_manager::scoped_numeral m_p; + public: + scoped_set_zp(core_manager & _m, numeral const & p):m(_m), m_modular(m.modular()), m_p(m.m()) { m_p = m.p(); m.set_zp(p); } + scoped_set_zp(core_manager & _m, uint64 p):m(_m), m_modular(m.modular()), m_p(m.m()) { m_p = m.p(); m.set_zp(p); } + ~scoped_set_zp() { if (m_modular) m.set_zp(m_p); else m.set_z(); } + }; + + class manager; + + typedef core_manager z_manager; + typedef core_manager zp_manager; + + typedef z_manager::factors factors; + typedef zp_manager::factors zp_factors; + + typedef svector numeral_vector; + + class scoped_numeral_vector : public _scoped_numeral_vector { + public: + scoped_numeral_vector(numeral_manager & m):_scoped_numeral_vector(m) {} + scoped_numeral_vector(manager & m); + }; + + class upolynomial_sequence { + numeral_vector m_seq_coeffs; // coefficients of all polynomials in the sequence + unsigned_vector m_begins; // start position (in m_seq_coeffs) of each polynomial in the sequence + unsigned_vector m_szs; // size of each polynomial in the sequence + friend class manager; + public: + /** + \brief Add a new polynomial to the sequence. + The contents of p is erased. + */ + void push(unsigned sz, numeral * p); + + /** + \brief Add a new polynomial to the sequence. + The contents of p is preserved. + */ + void push(numeral_manager & m, unsigned sz, numeral const * p); + + /** + \brief Return the number of polynomials in the sequence. + */ + unsigned size() const { return m_szs.size(); } + + /** + \brief Return the vector of coefficients for the i-th polynomial in the sequence. + */ + numeral const * coeffs(unsigned i) const { return m_seq_coeffs.c_ptr() + m_begins[i]; } + + /** + \brief Return the size of the i-th polynomial in the sequence. + */ + unsigned size(unsigned i) const { return m_szs[i]; } + }; + + class scoped_upolynomial_sequence : public upolynomial_sequence { + manager & m_manager; + public: + scoped_upolynomial_sequence(manager & m):m_manager(m) {} + ~scoped_upolynomial_sequence(); + }; + + class manager : public core_manager { + numeral_vector m_db_tmp; + numeral_vector m_dbab_tmp1; + numeral_vector m_dbab_tmp2; + numeral_vector m_tr_tmp; + numeral_vector m_push_tmp; + + int sign_of(numeral const & c); + struct drs_frame; + void pop_top_frame(numeral_vector & p_stack, svector & frame_stack); + void push_child_frames(unsigned sz, numeral const * p, numeral_vector & p_stack, svector & frame_stack); + void add_isolating_interval(svector const & frame_stack, mpbq_manager & bqm, mpbq_vector & lowers, mpbq_vector & uppers); + void add_root(svector const & frame_stack, mpbq_manager & bqm, mpbq_vector & roots); + void drs_isolate_0_1_roots(unsigned sz, numeral const * p, mpbq_manager & bqm, mpbq_vector & roots, mpbq_vector & lowers, mpbq_vector & uppers); + void drs_isolate_roots(unsigned sz, numeral * p, numeral & U, mpbq_manager & bqm, mpbq_vector & roots, mpbq_vector & lowers, mpbq_vector & uppers); + void drs_isolate_roots(unsigned sz, numeral const * p, mpbq_manager & bqm, mpbq_vector & roots, mpbq_vector & lowers, mpbq_vector & uppers); + void sqf_nz_isolate_roots(unsigned sz, numeral const * p, mpbq_manager & bqm, mpbq_vector & roots, mpbq_vector & lowers, mpbq_vector & uppers); + void sturm_seq_core(upolynomial_sequence & seq); + enum location { PLUS_INF, MINUS_INF, ZERO, MPBQ }; + template + unsigned sign_variations_at_core(upolynomial_sequence const & seq, mpbq const & b); + + void flip_sign(factors & r); + void flip_factor_sign_if_lm_neg(numeral_vector & p, factors & r, unsigned k); + void factor_2_sqf_pp(numeral_vector & p, factors & r, unsigned k); + bool factor_sqf_pp(numeral_vector & p, factors & r, unsigned k, factor_params const & params); + bool factor_core(unsigned sz, numeral const * p, factors & r, factor_params const & params); + + public: + manager(z_numeral_manager & m):core_manager(m) {} + ~manager(); + + void reset(numeral_vector & p) { core_manager::reset(p); } + + void reset(upolynomial_sequence & seq); + + /** + \brief Return true if 0 is a root of p. + */ + bool has_zero_roots(unsigned sz, numeral const * p) { SASSERT(sz > 0); return m().is_zero(p[0]); } + + /** + \brief Store in buffer a polynomial that has the same roots of p but the zero roots. + We have that: + forall u, p(u) = 0 and u != 0 implies buffer(u) = 0 + forall u, buffer(u) = 0 implies p(u) = 0 + + This method assumes p is not the zero polynomial + */ + void remove_zero_roots(unsigned sz, numeral const * p, numeral_vector & buffer); + + /** + \brief Return true if 1/2 is a root of p. + */ + bool has_one_half_root(unsigned sz, numeral const * p); + + /** + \brief Store in buffer a polynomial that has the same roots of p, but a 1/2 root is removed. + + This method assumes that 1/2 is a root of p. + */ + void remove_one_half_root(unsigned sz, numeral const * p, numeral_vector & buffer); + + /** + \brief Return the number of sign changes in the coefficients of p. + Zero coefficients are ignored. + */ + unsigned sign_changes(unsigned sz, numeral const * p); + + /** + \brief Return the descartes bound for the number of roots of p in the interval (0, +oo) + + Result: + 0 - p has no roots in (0,1) + 1 - p has one root in (0,1) + >1 - p has more than one root in (0,1) + */ + unsigned descartes_bound(unsigned sz, numeral const * p); + + /** + \brief Return the descartes bound for the number of roots of p in the interval (0, 1) + + \see descartes_bound + */ + unsigned descartes_bound_0_1(unsigned sz, numeral const * p); + + /** + \brief Return the descartes bound for the number of roots of p in the interval (a, b) + + \see descartes_bound + */ + unsigned descartes_bound_a_b(unsigned sz, numeral const * p, mpbq_manager & m, mpbq const & a, mpbq const & b); + + /** + \brief p(x) := p(x+1) + */ + void translate(unsigned sz, numeral * p); + void translate(unsigned sz, numeral const * p, numeral_vector & buffer) { set(sz, p, buffer); translate(sz, buffer.c_ptr()); } + + /** + \brief p(x) := p(x+2^k) + */ + void translate_k(unsigned sz, numeral * p, unsigned k); + void translate_k(unsigned sz, numeral const * p, unsigned k, numeral_vector & buffer) { set(sz, p, buffer); translate_k(sz, buffer.c_ptr(), k); } + + /** + \brief p(x) := p(x+c) + */ + void translate_z(unsigned sz, numeral * p, numeral const & c); + void translate_z(unsigned sz, numeral const * p, numeral const & c, numeral_vector & buffer) { set(sz, p, buffer); translate_z(sz, buffer.c_ptr(), c); } + + /** + \brief p(x) := p(x+b) where b = c/2^k + buffer := (2^k)^n * p(x + c/(2^k)) + */ + void translate_bq(unsigned sz, numeral * p, mpbq const & b); + void translate_bq(unsigned sz, numeral const * p, mpbq const & b, numeral_vector & buffer) { set(sz, p, buffer); translate_bq(sz, buffer.c_ptr(), b); } + + /** + \brief p(x) := p(x+b) where b = c/d + buffer := d^n * p(x + c/d) + */ + void translate_q(unsigned sz, numeral * p, mpq const & b); + void translate_q(unsigned sz, numeral const * p, mpq const & b, numeral_vector & buffer) { set(sz, p, buffer); translate_q(sz, buffer.c_ptr(), b); } + + /** + \brief p(x) := 2^n*p(x/2) where n = sz-1 + */ + void compose_2n_p_x_div_2(unsigned sz, numeral * p); + + /** + \brief p(x) := (2^k)^n * p(x/(2^k)) + */ + void compose_2kn_p_x_div_2k(unsigned sz, numeral * p, unsigned k); + + /** + \brief p(x) := p(2^k * x) + + If u is a root of old(p), then u/2^k is a root of p + */ + void compose_p_2k_x(unsigned sz, numeral * p, unsigned k); + + /** + \brief p(x) := p(b * x) + + If u is a root of old(p), then u/b is a root of p + */ + void compose_p_b_x(unsigned sz, numeral * p, numeral const & b); + + /** + \brief p(x) := p(b * x) + + If u is a root of old(p), then u/b is a root of p + + Let b be of the form c/(2^k), then this operation is equivalent to: + (2^k)^n*p(c*x/(2^k)) + + Let old(p) be of the form: + a_n * x^n + a_{n-1}*x^{n-1} + ... + a_1 * x + a_0 + + Then p is of the form: + a_n * c^n * x^n + a_{n-1} * c^{n-1} * 2^k * x^{n-1} + ... + a_1 * c * (2^k)^(n-1) * x + a_0 + */ + void compose_p_b_x(unsigned sz, numeral * p, mpbq const & b); + + /** + \brief p(x) := p(q*x) + */ + void compose_p_q_x(unsigned sz, numeral * p, mpq const & q); + + /** + \brief p(x) := a^n * p(x/a) + */ + void compose_an_p_x_div_a(unsigned sz, numeral * p, numeral const & a); + + /** + \brief p(x) := p(-x) + */ + void p_minus_x(unsigned sz, numeral * p); + + /** + \brief p(x) := x^n * p(1/x) + */ + void p_1_div_x(unsigned sz, numeral * p); + + /** + \brief Evaluate the sign of p(b) + */ + int eval_sign_at(unsigned sz, numeral const * p, mpbq const & b); + + /** + \brief Evaluate the sign of p(b) + */ + int eval_sign_at(unsigned sz, numeral const * p, mpq const & b); + + /** + \brief Evaluate the sign of p(b) + */ + int eval_sign_at(unsigned sz, numeral const * p, mpz const & b); + + /** + \brief Evaluate the sign of p(0) + */ + int eval_sign_at_zero(unsigned sz, numeral const * p); + + /** + \brief Evaluate the sign of p(+oo) + */ + int eval_sign_at_plus_inf(unsigned sz, numeral const * p); + + /** + \brief Evaluate the sign of p(-oo) + */ + int eval_sign_at_minus_inf(unsigned sz, numeral const * p); + + /** + \brief Evaluate the sign variations in the polynomial sequence at -oo + */ + unsigned sign_variations_at_minus_inf(upolynomial_sequence const & seq); + + /** + \brief Evaluate the sign variations in the polynomial sequence at +oo + */ + unsigned sign_variations_at_plus_inf(upolynomial_sequence const & seq); + + /** + \brief Evaluate the sign variations in the polynomial sequence at 0 + */ + unsigned sign_variations_at_zero(upolynomial_sequence const & seq); + + /** + \brief Evaluate the sign variations in the polynomial sequence at b + */ + unsigned sign_variations_at(upolynomial_sequence const & seq, mpbq const & b); + + /** + \brief Return an upper bound U for all roots of p. + U is a positive value. + We have that if u is a root of p, then |u| < U + */ + void root_upper_bound(unsigned sz, numeral const * p, numeral & U); + + unsigned knuth_positive_root_upper_bound(unsigned sz, numeral const * p); + unsigned knuth_negative_root_upper_bound(unsigned sz, numeral const * p); + + /** + \brief Return k s.t. for any nonzero root alpha of p(x): + |alpha| > 1/2^k + */ + unsigned nonzero_root_lower_bound(unsigned sz, numeral const * p); + + /** + \brief Isolate roots of a square free polynomial p. + The result is stored in three vectors: roots, lowers and uppers. + The vector roots contains actual roots of p. + The vectors lowers and uppers have the same size, and + For all i in [0, lowers.size()), we have that there is only and only one root of p in the interval (lowers[i], uppers[i]). + Every root of p in roots or in an interval (lowers[i], uppers[i]) + + The total number of roots of p is roots.size() + lowers.size() + + \pre p is not the zero polynomial, that is, sz > 0 + */ + void sqf_isolate_roots(unsigned sz, numeral const * p, mpbq_manager & bqm, mpbq_vector & roots, mpbq_vector & lowers, mpbq_vector & uppers); + + /** + \brief Isolate roots of an arbitrary polynomial p. + + \see sqf_isolate_roots. + + \pre p is not the zero polynomial, that is, sz > 0 + */ + void isolate_roots(unsigned sz, numeral const * p, mpbq_manager & bqm, mpbq_vector & roots, mpbq_vector & lowers, mpbq_vector & uppers); + + void drs_isolate_roots(unsigned sz, numeral * p, unsigned neg_k, unsigned pos_k, + mpbq_manager & bqm, mpbq_vector & roots, mpbq_vector & lowers, mpbq_vector & uppers); + + void sturm_isolate_roots_core(unsigned sz, numeral * p, unsigned neg_k, unsigned pos_k, + mpbq_manager & bqm, mpbq_vector & roots, mpbq_vector & lowers, mpbq_vector & uppers); + + void sturm_isolate_roots(unsigned sz, numeral const * p, mpbq_manager & bqm, mpbq_vector & roots, mpbq_vector & lowers, mpbq_vector & uppers); + + /** + \brief Compute the sturm sequence for p1 and p2. + */ + void sturm_seq(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, upolynomial_sequence & seq); + + /** + \brief Compute the sturm sequence for p and p'. + */ + void sturm_seq(unsigned sz, numeral const * p, upolynomial_sequence & seq); + + /** + \brief Compute the sturm tarski sequence for p1 and p1'*p2. + */ + void sturm_tarski_seq(unsigned sz1, numeral const * p1, unsigned sz2, numeral const * p2, upolynomial_sequence & seq); + + /** + \brief Compute the Fourier sequence for p. + */ + void fourier_seq(unsigned sz, numeral const * p, upolynomial_sequence & seq); + + /** + \brief Convert an isolating interval into a refinable one. + See comments in upolynomial.cpp. + */ + bool isolating2refinable(unsigned sz, numeral const * p, mpbq_manager & bqm, mpbq & a, mpbq & b); + + // + // Interval refinement procedures + // They all assume p is square free and (a, b) is a refinable isolating interval. + // + // Return TRUE, if interval was squeezed, and new interval is stored in (a,b). + // Return FALSE, if the actual root was found, it is stored in a. + // + // See upolynomial.cpp for additional comments + bool refine_core(unsigned sz, numeral const * p, int sign_a, mpbq_manager & bqm, mpbq & a, mpbq & b); + + bool refine(unsigned sz, numeral const * p, mpbq_manager & bqm, mpbq & a, mpbq & b); + + bool refine_core(unsigned sz, numeral const * p, int sign_a, mpbq_manager & bqm, mpbq & a, mpbq & b, unsigned prec_k); + + bool refine(unsigned sz, numeral const * p, mpbq_manager & bqm, mpbq & a, mpbq & b, unsigned prec_k); + ///////////////////// + + /** + \brief Convert a isolating (refinable) rational interval into a + isolating refinable binary rational interval. + + Return TRUE, if interval was found and the result is stored in (c, d). + Return FALSE, if the actual root was found, it is stored in c. + */ + bool convert_q2bq_interval(unsigned sz, numeral const * p, mpq const & a, mpq const & b, mpbq_manager & bqm, mpbq & c, mpbq & d); + + /** + \brief Given a polynomial p, and a lower bound l. Return + the root id i. That is, the first root u > l is the i-th root of p. + */ + unsigned get_root_id(unsigned sz, numeral const * p, mpbq const & l); + + /** + \brief Make sure that isolating interval (a, b) for p does not contain zero. + + Return TRUE, if updated (a, b) does not contain zero. + Return FALSE, if zero is a root of p + */ + bool normalize_interval_core(unsigned sz, numeral const * p, int sign_a, mpbq_manager & m, mpbq & a, mpbq & b); + + /** + \brief Similar to normalize_interval_core, but sign_a does not need to be provided. + */ + bool normalize_interval(unsigned sz, numeral const * p, mpbq_manager & m, mpbq & a, mpbq & b); + + /** + \brief Return true if all irreducible factors were found. + That is, if the result if false, there is no guarantee that the factors in r are irreducible. + This can happen when limits (e.g., on the search space size) are set in params. + */ + bool factor(unsigned sz, numeral const * p, factors & r, factor_params const & params = factor_params()); + bool factor(numeral_vector const & p, factors & r, factor_params const & params = factor_params()) { return factor(p.size(), p.c_ptr(), r, params); } + + void display(std::ostream & out, unsigned sz, numeral const * p, char const * var_name = "x", bool use_star = false) const { + return core_manager::display(out, sz, p, var_name); + } + void display(std::ostream & out, numeral_vector const & p, char const * var_name = "x") const { + return core_manager::display(out, p, var_name); + } + void display(std::ostream & out, upolynomial_sequence const & seq, char const * var_name = "x") const; + }; + +}; + +#endif diff --git a/lib/upolynomial_factorization.cpp b/lib/upolynomial_factorization.cpp new file mode 100644 index 000000000..a1e28e727 --- /dev/null +++ b/lib/upolynomial_factorization.cpp @@ -0,0 +1,1300 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + polynomial_factorization.cpp + +Abstract: + + Implementation of polynomial factorization. + +Author: + + Dejan (t-dejanj) 2011-11-15 + +Notes: + + [1] Elwyn Ralph Berlekamp. Factoring Polynomials over Finite Fields. Bell System Technical Journal, + 46(8-10):1853–1859, 1967. + [2] Donald Ervin Knuth. The Art of Computer Programming, volume 2: Seminumerical Algorithms. Addison Wesley, third + edition, 1997. + [3] Henri Cohen. A Course in Computational Algebraic Number Theory. Springer Verlag, 1993. + +--*/ +#include"trace.h" +#include"util.h" +#include"upolynomial_factorization_int.h" +#include"prime_generator.h" + +using namespace std; + +namespace upolynomial { + +// get the prime as unsigned while throwing exceptions if anything goes bad` +unsigned get_p_from_manager(zp_numeral_manager const & zp_nm) { + z_numeral_manager & nm = zp_nm.m(); + numeral const & p = zp_nm.p(); + if (!nm.is_uint64(p)) { + throw upolynomial_exception("The prime number attempted in factorization is too big!"); + } + uint64 p_uint64 = nm.get_uint64(p); + unsigned p_uint = static_cast(p_uint64); + if (((uint64)p_uint) != p_uint64) { + throw upolynomial_exception("The prime number attempted in factorization is too big!"); + } + return p_uint; +} + +/** + \brief The Q-I matrix from Berelkamp's algorithm [1,2]. + + Given a polynomial f = \sum f_k x^k of degree n, with f_i in Z_p[x], the i-th row of Q is a representation of + x^(p*i) modulo f, i.e. + + x^(p*i) modulo f = \sum_j q[i][j] x^j + + If f is of degree n, the matrix is square nxn. When the this matrix is constructed we obtain Q - I, because + this is what we need in the algorithm. After construction, the null space vectors can be extracted one-by one using + the next_null_space_vector method. +*/ +class berlekamp_matrix { + + zp_manager & m_upm; + mpzzp_manager & m_zpm; + + svector m_matrix; + unsigned m_size; + + unsigned m_null_row; // 0, ..., m_size - 1, state for null vectors + svector m_column_pivot; // position of pivots in the columns + svector m_row_pivot; // position of pivots in the rows + + mpz & get(unsigned i, unsigned j) { + SASSERT(i < m_size && j < m_size); + return m_matrix[i*m_size + j]; + } + + mpz const & get(unsigned i, unsigned j) const { + SASSERT(i < m_size && j < m_size); + return m_matrix[i*m_size + j]; + } + +public: + + /** + \brief Construct the matrix as explained above, f should be in the Z_p. + */ + berlekamp_matrix(zp_manager & upm, numeral_vector const & f) + : m_upm(upm), + m_zpm(upm.m()), + m_size(m_upm.degree(f)), + m_null_row(1), + m_column_pivot(m_size, -1), + m_row_pivot(m_size, -1) { + unsigned p = get_p_from_manager(m_zpm); + + TRACE("polynomial::factorization::bughunt", tout << "polynomial::berlekamp_matrix("; m_upm.display(tout, f); tout << ", " << p << ")" << endl;); + + // the first row is always the vector [1, 0, ..., 0], since x^0 = 0 (modulo f) + m_matrix.push_back(1); + for (unsigned j = 0; j < m_size; ++ j) { + m_matrix.push_back(0); + } + + // the rest of the rows, we can get as follows, given f = x^n + f_{n-1} x^{n-1} + ... + f_1 x + f_0 + // if x^k = \sum a_{k,j} x^j (modulo p), hence 0 <= j <= n-1 then + // x^{k+1} = a_{k,n-1}(-f_{n-1} x^{n-1} + ... + f_0) + a_{k,n-2} x^{n-1} + ... + a_{k, 0} x + // so we can compute a_{k+1,j} = a_{k, j-1} - a_{k,n-1}*f_j + // every p-th row we add to the matrix + scoped_numeral tmp(m_zpm); + unsigned row = 0, previous_row = 0; + for (unsigned k = 1; true; previous_row = row, ++ k) { + + // add another row if we need it + if (k % p == 1) { + if (++ row >= m_size) { + break; + } + for (unsigned j = 0; j < m_size; ++ j) { + m_matrix.push_back(0); + } + } + + // the multiplier + m_zpm.set(tmp, get(previous_row, m_size - 1)); + + // go down the row and shift it + for (unsigned j = m_size - 1; j > 0; -- j) { + m_zpm.submul(get(previous_row, j-1), tmp, f[j], get(row, j)); + } + + // add the 0 element (same formula with a_{k,-1} = 0) + m_zpm.mul(f[0], tmp, get(row, 0)); + m_zpm.neg(get(row, 0)); + } + + // do Q - I + for (unsigned i = 0; i < m_size; ++ i) { + m_zpm.dec(get(i, i)); + } + + TRACE("polynomial::factorization::bughunt", tout << "polynomial::berlekamp_matrix():" << endl; display(tout); tout << endl;); + } + + /** + \brief Destructor, just removing the numbers + */ + ~berlekamp_matrix() { + for (unsigned k = 0; k < m_matrix.size(); ++ k) { + m_zpm.del(m_matrix[k]); + } + } + + /** + \brief 'Disagonalizes' the matrix using only column operations. The reusling matrix will have -1 at pivot + elements. Returns the rank of the null space. + */ + unsigned diagonalize() { + + scoped_numeral multiplier(m_zpm); + + unsigned null_rank = 0; + for (unsigned i = 0; i < m_size; ++ i) { + + // get the first non-zero entry in the m_null_row row + bool column_found = false; + for (unsigned j = 0; j < m_size; ++ j) { + if (m_column_pivot[j] < 0 && !m_zpm.is_zero(get(i, j))) { + column_found = true; + m_column_pivot[j] = i; + m_row_pivot[i] = j; + + // found a pivot, to make it -1 we compute the multuplier -p^-1 + m_zpm.set(multiplier, get(i, j)); + m_zpm.inv(multiplier); + m_zpm.neg(multiplier); + + // multiply the pivot column with the multiplier + for (unsigned k = m_null_row; k < m_size; ++ k) { + m_zpm.mul(get(k, j), multiplier, get(k, j)); + } + // pivot is -1 so we can add it to the rest of the columns to eliminate the row + for (unsigned other_j = 0; other_j < m_size; ++ other_j) { + if (j != other_j) { + m_zpm.set(multiplier, get(i, other_j)); + for (unsigned k = m_null_row; k < m_size; ++ k) { + m_zpm.addmul(get(k, other_j), multiplier, get(k, j), get(k, other_j)); + } + } + } + } + } + if (!column_found) { + null_rank ++; + } + } + + TRACE("polynomial::factorization::bughunt", tout << "polynomial::diagonalize():" << endl; display(tout); tout << endl;); + + return null_rank; + } + + /** + If rank of the matrix is n - r, we are interested in linearly indeprendent vectors v_1, ..., v_r (the basis of + the null space), such that v_k A = 0. This method will give one at a time. The method returns true if vector has + been computed properly. The first vector [1, 0, ..., 0] is ignored (m_null_row starts from 1). + */ + bool next_null_space_vector(numeral_vector & v) { + SASSERT(v.size() <= m_size); + v.resize(m_size); + for (; m_null_row < m_size; ++ m_null_row) { + if (m_row_pivot[m_null_row] < 0) { + // output the vector + for (unsigned j = 0; j < m_size; ++ j) { + if (m_row_pivot[j] >= 0) { + m_zpm.set(v[j], get(m_null_row, m_row_pivot[j])); + } + else { + if (j == m_null_row) { + m_zpm.set(v[j], 1); + } + else { + m_zpm.set(v[j], 0); + } + } + } + ++ m_null_row; + return true; + } + } + // didn't find the vector + return false; + } + + /** + \brief Display the matrix on the output stream. + */ + void display(std::ostream & out) const { + for (unsigned i = 0; i < m_matrix.size() / m_size; ++ i) { + for (unsigned j = 0; j < m_size; ++ j) { + out << m_zpm.to_string(get(i, j)) << "\t"; + } + out << endl; + } + } +}; + +/** + See [3] p. 125. +*/ +void zp_square_free_factor(zp_manager & upm, numeral_vector const & f, zp_factors & sq_free_factors) { + + zp_numeral_manager & nm = upm.m(); + unsigned p = get_p_from_manager(upm.m()); + + TRACE("polynomial::factorization", tout << "polynomial::square_free_factor("; upm.display(tout, f); tout << ") over Z_" << p << endl;); + + scoped_numeral_vector div_tmp(nm); + + // [initialize] T_0 = f, e = 1 + // trim and get the make it monic if not already + SASSERT(f.size() > 1); + scoped_numeral_vector T_0(nm); + upm.set(f.size(), f.c_ptr(), T_0); + scoped_numeral constant(nm); + upm.mk_monic(T_0.size(), T_0.c_ptr(), constant); + sq_free_factors.set_constant(constant); + TRACE("polynomial::factorization::bughunt", + tout << "Initial factors: " << sq_free_factors << endl; + tout << "R. = GF(" << p << ")['x']" << endl; + tout << "T_0 = "; upm.display(tout, T_0); tout << endl; + ); + unsigned e = 1; + + // we repeat until we get a constant + scoped_numeral_vector T_0_d(nm); + scoped_numeral_vector T(nm); + scoped_numeral_vector V(nm); + scoped_numeral_vector W(nm); + scoped_numeral_vector A_ek(nm); + while (T_0.size() > 1) + { + // [initialize e-loop] T = gcd(T_0, T_0'), V / T_0/T, k = 0 + unsigned k = 0; + TRACE("polynomial::factorization::bughunt", tout << "k = 0" << endl;); + + // T_0_d = T_0' + upm.derivative(T_0.size(), T_0.c_ptr(), T_0_d); + TRACE("polynomial::factorization::bughunt", + tout << "T_0_d = T_0.derivative(x)" << endl; + tout << "T_0_d == "; upm.display(tout, T_0_d); tout << endl; + ); + + // T = gcd(T_0, T_0') + upm.gcd(T_0.size(), T_0.c_ptr(), T_0_d.size(), T_0_d.c_ptr(), T); + TRACE("polynomial::factorization::bughunt", + tout << "T = T_0.gcd(T_0_d)" << endl; + tout << "T == "; upm.display(tout, T); tout << endl; + ); + + // V = T_0 / T + upm.div(T_0.size(), T_0.c_ptr(), T.size(), T.c_ptr(), V); + TRACE("polynomial::factorization::bughunt", + tout << "V = T_0.quo_rem(T)[0]" << endl; + tout << "V == "; upm.display(tout, V); tout << endl; + ); + + while (V.size() > 1) { + // [special case] + if ((++k) % p == 0) { + ++ k; + // T = T/V + upm.div(T.size(), T.c_ptr(), V.size(), V.c_ptr(), T); + TRACE("polynomial::factorization::bughunt", + tout << "T = T.quo_rem(V)[0]" << endl; + tout << "T == "; upm.display(tout, T); tout << endl; + ); + } + + // [compute A_ek] + + // W = gcd(T, V) + upm.gcd(T.size(), T.c_ptr(), V.size(), V.c_ptr(), W); + TRACE("polynomial::factorization::bughunt", + tout << "W = T.gcd(V)" << endl; + upm.display(tout, W); tout << endl; + ); + + // A_ek = V/W + upm.div(V.size(), V.c_ptr(), W.size(), W.c_ptr(), A_ek); + TRACE("polynomial::factorization::bughunt", + tout << "A_ek = V.quo_rem(W)[0]" << endl; + tout << "A_ek == "; upm.display(tout, A_ek); tout << endl; + ); + + // V = W + V.swap(W); + TRACE("polynomial::factorization::bughunt", + tout << "V = W" << endl; + tout << "V == "; upm.display(tout, V); tout << endl; + ); + + // T = T/V + upm.div(T.size(), T.c_ptr(), V.size(), V.c_ptr(), T); + TRACE("polynomial::factorization::bughunt", + tout << "T = T.quo_rem(V)[0]" << endl; + tout << "T == "; upm.display(tout, T); tout << endl; + ); + + // if not constant, we output it + if (A_ek.size() > 1) { + TRACE("polynomial::factorization::bughunt", tout << "Factor: ("; upm.display(tout, A_ek); tout << ")^" << e*k << endl;); + sq_free_factors.push_back(A_ek, e*k); + } + } + + // [finished e-loop] T_0 = \sum_{p div j} t_j x^j, set T_0 \sum_{p div j} t_j x^{j/p}, e = pe + e *= p; + T_0.reset(); + for (unsigned deg_p = 0; deg_p < T.size(); deg_p += p) { + T_0.push_back(numeral()); + nm.set(T_0.back(), T[deg_p]); + } + TRACE("polynomial::factorization::bughunt", + tout << "T_0 = "; upm.display(tout, T_0); tout << endl; + ); + } + + TRACE("polynomial::factorization", tout << "polynomial::square_free_factor("; upm.display(tout, f); tout << ") => " << sq_free_factors << endl;); +} + +bool zp_factor(zp_manager & upm, numeral_vector const & f, zp_factors & factors) { + + unsigned p = get_p_from_manager(upm.m()); + + TRACE("polynomial::factorization", tout << "polynomial::factor("; upm.display(tout, f); tout << ") over Z_" << p << endl;); + + // get the sq-free parts (all of them will be monic) + zp_factors sq_free_factors(upm); + zp_square_free_factor(upm, f, sq_free_factors); + + // factor the sq-free parts individually + for (unsigned i = 0; i < sq_free_factors.distinct_factors(); ++ i) { + unsigned j = factors.distinct_factors(); + if (upm.degree(sq_free_factors[i]) > 1) { + zp_factor_square_free(upm, sq_free_factors[i], factors); // monic from aq-free decomposition + for (; j < factors.distinct_factors(); ++ j) { + factors.set_degree(j, sq_free_factors.get_degree(i)*factors.get_degree(j)); + } + } + else { + factors.push_back(sq_free_factors[i], sq_free_factors.get_degree(i)); + } + } + // add the constant + factors.set_constant(sq_free_factors.get_constant()); + + TRACE("polynomial::factorization", tout << "polynomial::factor("; upm.display(tout, f); tout << ") => " << factors << endl;); + + return factors.total_factors() > 1; +} + +bool zp_factor_square_free(zp_manager & upm, numeral_vector const & f, zp_factors & factors) { + return zp_factor_square_free_berlekamp(upm, f, factors, false); +} + +bool zp_factor_square_free_berlekamp(zp_manager & upm, numeral_vector const & f, zp_factors & factors, bool randomized) { + SASSERT(upm.degree(f) > 1); + + mpzzp_manager & zpm = upm.m(); + unsigned p = get_p_from_manager(zpm); + + TRACE("polynomial::factorization", tout << "upolynomial::factor_square_free_berlekamp("; upm.display(tout, f); tout << ", " << p << ")" << endl;); + SASSERT(zpm.is_one(f.back())); + + // construct the berlekamp Q matrix to get the null space + berlekamp_matrix Q_I(upm, f); + + // copy the inital polynomial to factors + unsigned first_factor = factors.distinct_factors(); + factors.push_back(f, 1); + + // rank of the null-space (and the number of factors) + unsigned r = Q_I.diagonalize(); + if (r == 1) { + // since r == 1 == number of factors, then f is irreducible + TRACE("polynomial::factorization", tout << "upolynomial::factor_square_free_berlekamp("; upm.display(tout, f); tout << ", " << p << ") => " << factors << endl;); + return false; + } + + TRACE("polynomial::factorization::bughunt", tout << "upolynomial::factor_square_free_berlekamp(): computing factors, expecting " << r << endl;); + + scoped_numeral_vector gcd(zpm); + scoped_numeral_vector div(zpm); + + // process the null space vectors (skip first one, it's [1, 0, ..., 0]) while generating the factors + unsigned d = upm.degree(f); + scoped_numeral_vector v_k(zpm); + while (Q_I.next_null_space_vector(v_k)) { + + TRACE("polynomial::factorization::bughunt", + tout << "null vector: "; + for(unsigned j = 0; j < d; ++ j) { + tout << zpm.to_string(v_k[j]) << " "; + } + tout << endl; + ); + + upm.trim(v_k); + // TRACE("polynomial::factorization", tout << "v_k = "; upm.display(tout, v_k); tout << endl;); + + unsigned current_factor_end = factors.distinct_factors(); + for (unsigned current_factor_i = first_factor; current_factor_i < current_factor_end; ++ current_factor_i) { + + // we have v such that vQ = v, viewing v as a polynomial, we get that v^n - v = 0 (mod f) + // since v^n -v = v*(v-1)*...*(v - p-1) we compute the gcd(v - s, f) to extract the + // factors. it also holds that g = \prod gcd(v - s, f), so we just accumulate them + + // if it's of degree 1, we're done (have to index the array as we are adding to it), as + if (factors[current_factor_i].size() == 2) { + continue; + } + + for (unsigned s = 0; s < p; ++ s) { + + numeral_vector const & current_factor = factors[current_factor_i]; + + // we just take one off v_k each time to get all of them + zpm.dec(v_k[0]); + + // get the gcd + upm.gcd(v_k.size(), v_k.c_ptr(), current_factor.size(), current_factor.c_ptr(), gcd); + + // if the gcd is 1, or the the gcd is f, we just ignroe it + if (gcd.size() != 1 && gcd.size() != current_factor.size()) { + + // get the divisor also (no need to normalize the div, both are monic) + upm.div(current_factor.size(), current_factor.c_ptr(), gcd.size(), gcd.c_ptr(), div); + + TRACE("polynomial::factorization::bughunt", + tout << "current_factor = "; upm.display(tout, current_factor); tout << endl; + tout << "gcd_norm = "; upm.display(tout, gcd); tout << endl; + tout << "div = "; upm.display(tout, div); tout << endl; + ); + + // continue with the rest + factors.swap_factor(current_factor_i, div); + + // add the new factor(s) + factors.push_back(gcd, 1); + + } + + // at the point where we have all the factors, we are done + if (factors.distinct_factors() - first_factor == r) { + TRACE("polynomial::factorization", tout << "polynomial::factor("; upm.display(tout, f); tout << ", " << p << ") => " << factors << " of degree " << factors.get_degree() << endl;); + return true; + } + } + } + } + + // should never get here + SASSERT(false); + return true; +} + +/** + Check if the hensel lifting was correct, i.e. that C = A*B (mod br). +*/ +bool check_hansel_lift(z_manager & upm, numeral_vector const & C, + numeral const & a, numeral const & b, numeral const & r, + numeral_vector const & A, numeral_vector const & B, + numeral_vector const & A_lifted, numeral_vector const & B_lifted) +{ + z_numeral_manager & nm = upm.zm(); + + scoped_mpz br(nm); + nm.mul(b, r, br); + + zp_manager br_upm(upm.zm()); + br_upm.set_zp(br); + + if (A_lifted.size() != A.size()) return false; + if (B_lifted.size() != B.size()) return false; + if (!nm.eq(A.back(), A_lifted.back())) return false; + + // test1: check that C = A_lifted * B_lifted (mod b*r) + scoped_mpz_vector test1(nm); + upm.mul(A_lifted.size(), A_lifted.c_ptr(), B_lifted.size(), B_lifted.c_ptr(), test1); + upm.sub(C.size(), C.c_ptr(), test1.size(), test1.c_ptr(), test1); + to_zp_manager(br_upm, test1); + if (!test1.size() == 0) { + TRACE("polynomial::factorization::bughunt", + tout << "sage: R. = ZZ['x']" << endl; + tout << "sage: A = "; upm.display(tout, A); tout << endl; + tout << "sage: B = "; upm.display(tout, B); tout << endl; + tout << "sage: C = "; upm.display(tout, C); tout << endl; + tout << "sage: test1 = C - AB" << endl; + tout << "sage: print test1.change_ring(GF(" << nm.to_string(br) << "))" << endl; + tout << "sage: print 'expected 0'" << endl; + ); + return false; + } + + zp_manager b_upm(nm); + b_upm.set_zp(b); + + // test2: A_lifted = A (mod b) + scoped_mpz_vector test2a(nm), test2b(nm); + to_zp_manager(b_upm, A, test2a); + to_zp_manager(b_upm, A_lifted, test2b); + if (!upm.eq(test2a, test2b)) { + return false; + } + + // test3: B_lifted = B (mod b) + scoped_mpz_vector test3a(nm), test3b(nm); + to_zp_manager(b_upm, B, test3a); + to_zp_manager(b_upm, B_lifted, test3b); + if (!upm.eq(test3a, test3b)) { + return false; + } + + return true; +} + +/** + Performs a Hensel lift of A and B in Z_a to Z_b, where p is prime and and a = p^{a_k}, b = p^{b_k}, + r = (a, b), with the following assumptions: + + (1) UA + VB = 1 (mod a) + (2) C = A*B (mod b) + (3) (l(A), r) = 1 (importand in order to divide by A, i.e. to invert l(A)) + (4) deg(A) + deg(B) = deg(C) + + The output of is two polynomials A1, B1 such that A1 = A (mod b), B1 = B (mod b), + l(A1) = l(A), deg(A1) = deg(A), deg(B1) = deg(B) and C = A1 B1 (mod b*r). Such A1, B1 are unique if + r is prime. See [3] p. 138. +*/ +void hensel_lift(z_manager & upm, numeral const & a, numeral const & b, numeral const & r, + numeral_vector const & U, numeral_vector const & A, numeral_vector const & V, numeral_vector const & B, + numeral_vector const & C, numeral_vector & A_lifted, numeral_vector & B_lifted) { + + z_numeral_manager & nm = upm.zm(); + + TRACE("polynomial::factorization::bughunt", + tout << "polynomial::hensel_lift("; + tout << "a = " << nm.to_string(a) << ", "; + tout << "b = " << nm.to_string(b) << ", "; + tout << "r = " << nm.to_string(r) << ", "; + tout << "U = "; upm.display(tout, U); tout << ", "; + tout << "A = "; upm.display(tout, A); tout << ", "; + tout << "V = "; upm.display(tout, V); tout << ", "; + tout << "B = "; upm.display(tout, B); tout << ", "; + tout << "C = "; upm.display(tout, C); tout << ")" << endl; + ); + + zp_manager r_upm(nm); + r_upm.set_zp(r); + + SASSERT(upm.degree(C) == upm.degree(A) + upm.degree(B)); + SASSERT(upm.degree(U) < upm.degree(B) && upm.degree(V) < upm.degree(A)); + + // by (2) C = AB (mod b), hence (C - AB) is divisible by b + // define thus let f = (C - AB)/b in Z_r + scoped_numeral_vector f(upm.m()); + upm.mul(A.size(), A.c_ptr(), B.size(), B.c_ptr(), f); + upm.sub(C.size(), C.c_ptr(), f.size(), f.c_ptr(), f); + upm.div(f, b); + to_zp_manager(r_upm, f); + TRACE("polynomial::factorization", + tout << "f = "; upm.display(tout, f); tout << endl; + ); + + // we need to get A1 = A (mod b), B1 = B (mode b) so we know that we need + // A1 = A + b*S, B1 = B + b*T in Z[x] for some S and T with deg(S) <= deg(A), deg(T) <= deg(B) + // we also need (mod b*r) C = A1*B1 = (A + b*S)*(B + b*T) = AB + b(AT + BS) + b^2*ST + // if we find S and T, we will have found our A1 and B1 + // since r divides b, then b^2 contains b*r and we know that it must be that C = AB + b(AT + BS) (mod b*r) + // which is equivalent to + // (5) f = (C - AB)/b = AT + BS (mod r) + // having (1) AU + BV = 1 (mod r) and (5) AT + BS = f (mod r), we know that + // A*(fU) + B*(fV) = f (mod r), i.e. T = fU, S = fV is a solution + // but we also know that we need an S with deg(S) <= deg(A) so we can do the following + // we know that l(A) is invertible so we can find the exact remainder of fV with A, i.e. find the qotient + // t in the division and set + // A*(fU + tB) + B*(fV - tA) = f + // T = fU + tB, S = fU - tA + // since l(A) is invertible in Z_r, we can (in Z_r) use exact division to get Vf = At + R with deg(R) < A + // we now know that deg(A+bS) = deg(A), but we also know (4) which will guarantee that deg(B+bT) = deg(B) + + // compute the S, T (compute in Z_r[x]) + scoped_numeral_vector Vf(r_upm.m()), t(r_upm.m()), S(r_upm.m()); + TRACE("polynomial::factorization::bughunt", + tout << "V == "; upm.display(tout, V); tout << endl; + ); + r_upm.mul(V.size(), V.c_ptr(), f.size(), f.c_ptr(), Vf); + TRACE("polynomial::factorization::bughunt", + tout << "Vf = V*f" << endl; + tout << "Vf == "; upm.display(tout, Vf); tout << endl; + ); + r_upm.div_rem(Vf.size(), Vf.c_ptr(), A.size(), A.c_ptr(), t, S); + TRACE("polynomial::factorization::bughunt", + tout << "[t, S] = Vf.quo_rem(A)" << endl; + tout << "t == "; upm.display(tout, t); tout << endl; + tout << "S == "; upm.display(tout, S); tout << endl; + ); + scoped_numeral_vector T(r_upm.m()), tmp(r_upm.m()); + r_upm.mul(U.size(), U.c_ptr(), f.size(), f.c_ptr(), T); // T = fU + TRACE("polynomial::factorization::bughunt", + tout << "T == U*f" << endl; + tout << "T == "; upm.display(tout, T); tout << endl; + ); + r_upm.mul(B.size(), B.c_ptr(), t.size(), t.c_ptr(), tmp); // tmp = Bt + TRACE("polynomial::factorization::bughunt", + tout << "tmp = B*t" << endl; + tout << "tmp == "; upm.display(tout, tmp); tout << endl; + ); + r_upm.add(T.size(), T.c_ptr(), tmp.size(), tmp.c_ptr(), T); // T = Uf + Bt + TRACE("polynomial::factorization::bughunt", + tout << "T = B*tmp" << endl; + tout << "T == "; upm.display(tout, T); tout << endl; + ); + + // set the result, A1 = A + b*S, B1 = B + b*T (now we compute in Z[x]) + upm.mul(S, b); + upm.mul(T, b); + upm.add(A.size(), A.c_ptr(), S.size(), S.c_ptr(), A_lifted); + upm.add(B.size(), B.c_ptr(), T.size(), T.c_ptr(), B_lifted); + + CASSERT("polynomial::factorizatio::bughunt", check_hansel_lift(upm, C, a, b, r, A, B, A_lifted, B_lifted)); +} + +bool check_quadratic_hensel(zp_manager & zpe_upm, numeral_vector const & U, numeral_vector const & A, numeral_vector const & V, numeral_vector const & B) { + z_numeral_manager & nm = zpe_upm.zm(); + + // compute UA+BV expecting to get 1 (in Z_pe[x]) + scoped_mpz_vector tmp1(nm); + scoped_mpz_vector tmp2(nm); + zpe_upm.mul(U.size(), U.c_ptr(), A.size(), A.c_ptr(), tmp1); + zpe_upm.mul(V.size(), V.c_ptr(), B.size(), B.c_ptr(), tmp2); + scoped_mpz_vector one(nm); + zpe_upm.add(tmp1.size(), tmp1.c_ptr(), tmp2.size(), tmp2.c_ptr(), one); + if (one.size() != 1 || !nm.is_one(one[0])) { + TRACE("polynomial::factorization::bughunt", + tout << "sage: R. = Zmod(" << nm.to_string(zpe_upm.m().p()) << ")['x']" << endl; + tout << "sage: A = "; zpe_upm.display(tout, A); tout << endl; + tout << "sage: B = "; zpe_upm.display(tout, B); tout << endl; + tout << "sage: U = "; zpe_upm.display(tout, U); tout << endl; + tout << "sage: V = "; zpe_upm.display(tout, V); tout << endl; + tout << "sage: print (1 - UA - VB)" << endl; + tout << "sage: print 'expected 0'" << endl; + ); + return false; + } + + return true; +} + +/** + Lift C = A*B from Z_p[x] to Z_{p^e}[x] such that: + * A = A_lift (mod p) + * B = B_lift (mod p) + * C = A*B (mod p^e) +*/ +void hensel_lift_quadratic(z_manager& upm, numeral_vector const & C, + zp_manager & zpe_upm, numeral_vector & A, numeral_vector & B, unsigned e) { + z_numeral_manager & nm = upm.zm(); + + TRACE("polynomial::factorization::bughunt", + tout << "polynomial::hansel_lift_quadratic("; + tout << "A = "; upm.display(tout, A); tout << ", "; + tout << "B = "; upm.display(tout, B); tout << ", "; + tout << "C = "; upm.display(tout, C); tout << ", "; + tout << "p = " << nm.to_string(zpe_upm.m().p()) << ", e = " << e << ")" << endl; + ); + + // we create a new Z_p manager, since we'll be changing the input one + zp_manager zp_upm(nm); + zp_upm.set_zp(zpe_upm.m().p()); + zp_numeral_manager & zp_nm = zp_upm.m(); + + // get the U, V, such that A*U + B*V = 1 (mod p) + scoped_mpz_vector U(nm), V(nm), D(nm); + zp_upm.ext_gcd(A.size(), A.c_ptr(), B.size(), B.c_ptr(), U, V, D); + SASSERT(D.size() == 1 && zp_nm.is_one(D[0])); + + // we start lifting from (a = p, b = p, r = p) + scoped_mpz_vector A_lifted(nm), B_lifted(nm); + for (unsigned k = 1; k < e; k *= 2) { + upm.checkpoint(); + // INVARIANT(a = p^e, b = p^e, r = gcd(a, b) = p^e): + // C = AB (mod b), UA + VB = 1 (mod a) + // deg(U) < deg(B), dev(V) < deg(A), deg(C) = deg(A) + deg(V) + // gcd(l(A), r) = 1 + + // regular hensel lifting from a to b*r, here from pe -> pk*pk = p^{k*k} + numeral const & pe = zpe_upm.m().p(); + + hensel_lift(upm, pe, pe, pe, U, A, V, B, C, A_lifted, B_lifted); + // now we have C = AB (mod b*r) + TRACE("polynomial::factorization::bughunt", + tout << "A_lifted = "; upm.display(tout, A_lifted); tout << endl; + tout << "B_lifted = "; upm.display(tout, B_lifted); tout << endl; + tout << "C = "; upm.display(tout, C); tout << endl; + ); + + // we employ similar reasoning as in the regular hansel lemma now + // we need to lift UA + VB = 1 (mod a) + // since after the lift, we still have UA + VB = 1 (mod a) we know that (1 - UA - VB)/a is in Z[x] + // so we can compute g = (1 - UA - VB)/a + // we need U1 and V1 such that U1A + V1B = 1 (mod a^2), with U1 = U mod a, V1 = V mod a + // hence U1 = U + aS, V1 = V + aT and we need + // (U + aS)A + (V + aT)B = 1 (mod a^2) same as + // UA + VB + a(SA + TB) = 1 (mod a^2) same as + // SA + TB = g (mod a) hence + // (gU + tB)A + (gV - tA)B = g (mod a) will be a solution and we pick t such that deg(gV - tA) < deg(A) + + // compute g + scoped_mpz_vector tmp1(nm), g(nm); + g.push_back(numeral()); + nm.set(g.back(), 1); // g = 1 + upm.mul(A_lifted.size(), A_lifted.c_ptr(), U.size(), U.c_ptr(), tmp1); // tmp1 = AU + upm.sub(g.size(), g.c_ptr(), tmp1.size(), tmp1.c_ptr(), g); // g = 1 - UA + upm.mul(B_lifted.size(), B_lifted.c_ptr(), V.size(), V.c_ptr(), tmp1); // tmp1 = BV + upm.sub(g.size(), g.c_ptr(), tmp1.size(), tmp1.c_ptr(), g); // g = 1 - UA - VB + upm.div(g, pe); + to_zp_manager(zpe_upm, g); + TRACE("polynomial::factorization::bughunt", + tout << "g = (1 - A_lifted*U - B_lifted*V)/" << nm.to_string(pe) << endl; + tout << "g == "; upm.display(tout, g); tout << endl; + ); + + // compute the S, T + scoped_mpz_vector S(nm), T(nm), t(nm), tmp2(nm); + zpe_upm.mul(g.size(), g.c_ptr(), V.size(), V.c_ptr(), tmp1); // tmp1 = gV + zpe_upm.div_rem(tmp1.size(), tmp1.c_ptr(), A.size(), A.c_ptr(), t, T); // T = gV - tA, deg(T) < deg(A) + zpe_upm.mul(g.size(), g.c_ptr(), U.size(), U.c_ptr(), tmp1); // tmp1 = gU + zpe_upm.mul(t.size(), t.c_ptr(), B.size(), B.c_ptr(), tmp2); // tmp2 = tB + zpe_upm.add(tmp1.size(), tmp1.c_ptr(), tmp2.size(), tmp2.c_ptr(), S); + + // now update U = U + a*S and V = V + a*T + upm.mul(S.size(), S.c_ptr(), pe); + upm.mul(T.size(), T.c_ptr(), pe); + upm.add(U.size(), U.c_ptr(), S.size(), S.c_ptr(), U); + upm.add(V.size(), V.c_ptr(), T.size(), T.c_ptr(), V); // deg(V) < deg(A), deg(T) < deg(A) => deg(V') < deg(A) + + // we go quadratic + zpe_upm.m().set_p_sq(); + to_zp_manager(zpe_upm, U); + to_zp_manager(zpe_upm, V); + to_zp_manager(zpe_upm, A_lifted); + to_zp_manager(zpe_upm, B_lifted); + + // at this point we have INVARIANT(a = (p^e)^2, b = (p^e)^2, r = (p^e)^2) + A.swap(A_lifted); + B.swap(B_lifted); + + CASSERT("polynomial::factorizatio::bughunt", check_quadratic_hensel(zpe_upm, U, A, V, B)); + } +} + +bool check_hensel_lift(z_manager & upm, numeral_vector const & f, zp_factors const & zp_fs, zp_factors const & zpe_fs, unsigned e) { + numeral_manager & nm(upm.m()); + + zp_manager & zp_upm = zp_fs.upm(); + zp_manager & zpe_upm = zpe_fs.upm(); + + numeral const & p = zp_fs.nm().p(); + numeral const & pe = zpe_fs.nm().p(); + + scoped_numeral power(nm); + nm.power(p, e, power); + if (!nm.ge(pe, power)) { + return false; + } + + // check f = lc(f) * zp_fs (mod p) + scoped_numeral_vector mult_zp(nm), f_zp(nm); + zp_fs.multiply(mult_zp); + to_zp_manager(zp_upm, f, f_zp); + zp_upm.mul(mult_zp, f_zp.back()); + if (!upm.eq(mult_zp, f_zp)) { + TRACE("polynomial::factorization::bughunt", + tout << "f = "; upm.display(tout, f); tout << endl; + tout << "zp_fs = " << zp_fs << endl; + tout << "sage: R. = Zmod(" << nm.to_string(p) << ")['x']" << endl; + tout << "sage: mult_zp = "; upm.display(tout, mult_zp); tout << endl; + tout << "sage: f_zp = "; upm.display(tout, f_zp); tout << endl; + tout << "sage: mult_zp == f_zp" << endl; + ); + return false; + } + + // check individual factors + if (zpe_fs.distinct_factors() != zp_fs.distinct_factors()) { + return false; + } + + // check f = lc(f) * zpe_fs (mod p^e) + scoped_numeral_vector mult_zpe(nm), f_zpe(nm); + zpe_fs.multiply(mult_zpe); + to_zp_manager(zpe_upm, f, f_zpe); + zpe_upm.mul(mult_zpe, f_zpe.back()); + if (!upm.eq(mult_zpe, f_zpe)) { + TRACE("polynomial::factorization::bughunt", + tout << "f = "; upm.display(tout, f); tout << endl; + tout << "zpe_fs = " << zpe_fs << endl; + tout << "sage: R. = Zmod(" << nm.to_string(pe) << ")['x']" << endl; + tout << "sage: mult_zpe = "; upm.display(tout, mult_zpe); tout << endl; + tout << "sage: f_zpe = "; upm.display(tout, f_zpe); tout << endl; + tout << "sage: mult_zpe == f_zpe" << endl; + ); + return false; + } + + return true; +} + +bool check_individual_lift(zp_manager & zp_upm, numeral_vector const & A_p, zp_manager & zpe_upm, numeral_vector const & A_pe) { + scoped_numeral_vector A_pe_p(zp_upm.m()); + to_zp_manager(zp_upm, A_pe, A_pe_p); + + if (!zp_upm.eq(A_p, A_pe_p)) { + return false; + } + + return true; +} + +void hensel_lift(z_manager & upm, numeral_vector const & f, zp_factors const & zp_fs, unsigned e, zp_factors & zpe_fs) { + SASSERT(zp_fs.total_factors() > 0); + + zp_numeral_manager & zp_nm = zp_fs.nm(); + zp_manager & zp_upm = zp_fs.upm(); + z_numeral_manager & nm = zp_nm.m(); + + SASSERT(nm.is_one(zp_fs.get_constant())); + + zp_numeral_manager & zpe_nm = zpe_fs.nm(); + zp_manager & zpe_upm = zpe_fs.upm(); + zpe_nm.set_zp(zp_nm.p()); + + TRACE("polynomial::factorization::bughunt", + tout << "polynomial::hansel_lift("; upm.display(tout, f); tout << ", " << zp_fs << ")" << endl; + ); + + // lift the factors one by one + scoped_mpz_vector A(nm), B(nm), C(nm), f_parts(nm); // these will all be in Z_p + + // copy of f, that we'll be cutting parts of + upm.set(f.size(), f.c_ptr(), f_parts); + + // F_k are factors mod Z_p, A_k the factors mod p^e + // the invariant we keep is that: + // (1) f_parts = C = lc(f) * \prod_{k = i}^n F_k, C in Z_p[x] + // (2) A_k = F_k (mod p), for k < i + // (3) f = (\prod_{k < i} A_k) * f_parts (mod p^e) + for (int i = 0, i_end = zp_fs.distinct_factors()-1; i < i_end; ++ i) { + SASSERT(zp_fs.get_degree(i) == 1); // p was chosen so that f is square-free + + // F_i = A (mod Z_p) + zp_upm.set(zp_fs[i].size(), zp_fs[i].c_ptr(), A); + TRACE("polynomial::factorization::bughunt", + tout << "A = "; upm.display(tout, A); tout << endl; + ); + + // C = f_parts (mod Z_p) + if (i > 0) { + to_zp_manager(zp_upm, f_parts, C); + } + else { + // first time around, we don't have p^e yet, so first time we just compute C + zp_fs.multiply(C); + scoped_mpz lc(nm); + zp_nm.set(lc, f.back()); + zp_upm.mul(C, lc); + } + TRACE("polynomial::factorization::bughunt", + tout << "C = "; upm.display(tout, C); tout << endl; + ); + + // we take B to be what's left from C and A + zp_upm.div(C.size(), C.c_ptr(), A.size(), A.c_ptr(), B); + TRACE("polynomial::factorization::bughunt", + tout << "B = "; upm.display(tout, B); tout << endl; + ); + + // lift A and B to p^e (this will change p^e and put it zp_upm) + zpe_nm.set_zp(zp_nm.p()); + hensel_lift_quadratic(upm, f_parts, zpe_upm, A, B, e); + CASSERT("polynomial::factorizatio::bughunt", check_individual_lift(zp_upm, zp_fs[i], zpe_upm, A)); + TRACE("polynomial::factorization", + tout << "lifted to " << nm.to_string(zpe_upm.m().p()) << endl; + tout << "A = "; upm.display(tout, A); tout << endl; + tout << "B = "; upm.display(tout, B); tout << endl; + ); + + // if this is the first time round, we also construct f_parts (now we have correct p^e) + if (i == 0) { + to_zp_manager(zpe_upm, f, f_parts); + } + + // take the lifted A out of f_parts + zpe_upm.div(f_parts.size(), f_parts.c_ptr(), A.size(), A.c_ptr(), f_parts); + + // add the lifted factor (kills A) + zpe_fs.push_back_swap(A, 1); + } + + // we have one last factor, but it also contains lc(f), so take it out + scoped_mpz lc_inv(nm); + zpe_nm.set(lc_inv, f.back()); + zpe_nm.inv(lc_inv); + zpe_upm.mul(B, lc_inv); + zpe_fs.push_back_swap(B, 1); + + TRACE("polynomial::factorization::bughunt", + tout << "polynomial::hansel_lift("; upm.display(tout, f); tout << ", " << zp_fs << ") => " << zpe_fs << endl; + ); + + CASSERT("polynomial::factorizatio::bughunt", check_hensel_lift(upm, f, zp_fs, zpe_fs, e)); +} + +// get a bound on B for the factors of f with degree less or equal to deg(f)/2 +// and then choose e to be smallest such that p^e > 2*lc(f)*B, we use the mignotte +// +// from [3, pg 134] +// |p| = sqrt(\sum |p_i|^2). If a = \sum a_i x^i, b = \sum b_j, deg b = n, and b divides a, then for all j +// +// |b_j| <= (n-1 over j)|a| + (n-1 over j-1)|lc(a)| +// +// when factoring a polynomial, we find a bound B for a factor of f of degree <= deg(f)/2 +// allowing both positive and negative as coefficients of b, we now want a power p^e such that all coefficients +// can be represented, i.e. [-B, B] \subset [-\ceil(p^e/2), \ceil(p^e)/2], so we peek e, such that p^e >= 2B + +static unsigned mignotte_bound(z_manager & upm, numeral_vector const & f, numeral const & p) { + numeral_manager & nm = upm.m(); + + SASSERT(upm.degree(f) >= 2); + unsigned n = upm.degree(f)/2; // >= 1 + + // get the approximation for the norm of a + scoped_numeral f_norm(nm); + for (unsigned i = 0; i < f.size(); ++ i) { + if (!nm.is_zero(f[i])) { + nm.addmul(f_norm, f[i], f[i], f_norm); + } + } + nm.root(f_norm, 2); + + // by above we can pick (n-1 over (n-1/2))|a| + (n-1 over (n-1)/2)lc(a) + // we approximate both binomial-coefficients with 2^(n-1), so to get 2B we use 2^n(f_norm + lc(f)) + scoped_numeral bound(nm); + nm.set(bound, 1); + nm.mul2k(bound, n, bound); + scoped_numeral tmp(nm); + nm.set(tmp, f.back()); + nm.abs(tmp); + nm.add(f_norm, tmp, f_norm); + nm.mul(bound, f_norm, bound); + + // we need e such that p^e >= B + nm.set(tmp, p); + unsigned e; + for (e = 1; nm.le(tmp, bound); e *= 2) { + nm.mul(tmp, tmp, tmp); + } + + return e; +} + +/** + \brief Given f from Z[x] that is square free, it factors it. + This method also assumes f is primitive. +*/ +bool factor_square_free(z_manager & upm, numeral_vector const & f, factors & fs, unsigned k, factor_params const & params) { + TRACE("polynomial::factorization::bughunt", + tout << "sage: f = "; upm.display(tout, f); tout << endl; + tout << "sage: if (not f.is_squarefree()): print 'Error, f is not square-free'" << endl; + tout << "sage: print 'Factoring :', f" << endl; + tout << "sage: print 'Expected factors: ', f.factor()" << endl; + ); + + numeral_manager & nm = upm.m(); + + // This method assumes f is primitive. Thus, the content of f must be one + DEBUG_CODE({ + scoped_numeral f_cont(nm); + nm.gcd(f.size(), f.c_ptr(), f_cont); + SASSERT(f.size() == 0 || nm.is_one(f_cont)); + }); + + scoped_numeral_vector f_pp(nm); + upm.set(f.size(), f.c_ptr(), f_pp); + + // make sure the leading coefficient is positive + if (!f_pp.empty() && nm.is_neg(f_pp[f_pp.size() - 1])) { + for (unsigned i = 0; i < f_pp.size(); i++) + nm.neg(f_pp[i]); + // flip sign constant if k is odd + if (k % 2 == 1) { + scoped_numeral c(nm); + nm.set(c, fs.get_constant()); + nm.neg(c); + fs.set_constant(c); + } + } + + TRACE("polynomial::factorization::bughunt", + tout << "sage: f_pp = "; upm.display(tout, f_pp); tout << endl; + tout << "sage: if (not (f_pp * 1 == f): print 'Error, content computation wrong'" << endl; + ); + + // the variables we'll be using and updating in Z_p + scoped_numeral p(nm); + nm.set(p, 2); + zp_manager zp_upm(nm.m()); + zp_upm.set_zp(p); + zp_factors zp_fs(zp_upm); + scoped_numeral zp_fs_p(nm); nm.set(zp_fs_p, 2); + + // we keep all the possible sets of degrees of factors in this set + factorization_degree_set degree_set(zp_upm); + + // we try get some number of factorizations in Z_p, for some primes + // get the prime p such that + // (1) (f_prim mod p) stays square-free + // (2) l(f_prim) mod p doesn't vanish, i.e. we don't get a polynomial of smaller degree + prime_iterator prime_it; + scoped_numeral gcd_tmp(nm); + unsigned trials = 0; + while (trials < params.m_p_trials) { + upm.checkpoint(); + // construct prime to check + uint64 next_prime = prime_it.next(); + if (next_prime > params.m_max_p) { + fs.push_back(f_pp, k); + return false; + } + nm.set(p, next_prime); + zp_upm.set_zp(p); + + // we need gcd(lc(f_pp), p) = 1 + nm.gcd(p, f_pp.back(), gcd_tmp); + TRACE("polynomial::factorization::bughunt", + tout << "sage: if (not (gcd(" << nm.to_string(p) << ", " << nm.to_string(f_pp.back()) << ")) == " << + nm.to_string(gcd_tmp) << "): print 'Error, wrong gcd'" << endl; + ); + if (!nm.is_one(gcd_tmp)) { + continue; + } + + // if it's not square free, we also try somehting else + scoped_numeral_vector f_pp_zp(nm); + to_zp_manager(zp_upm, f_pp, f_pp_zp); + + TRACE("polynomial::factorization::bughunt", + tout << "sage: Rp. = GF(" << nm.to_string(p) << ")['x_p']"; tout << endl; + tout << "sage: f_pp_zp = "; zp_upm.display(tout, f_pp_zp, "x_p"); tout << endl; + ); + + if (!zp_upm.is_square_free(f_pp_zp.size(), f_pp_zp.c_ptr())) + continue; + + // we make it monic + zp_upm.mk_monic(f_pp_zp.size(), f_pp_zp.c_ptr()); + + // found a candidate, factorize in Z_p and add back the constant + zp_factors current_fs(zp_upm); + bool factored = zp_factor_square_free(zp_upm, f_pp_zp, current_fs); + if (!factored) { + fs.push_back(f_pp, k); + return true; + } + + // get the factors degrees set + factorization_degree_set current_degree_set(current_fs); + if (degree_set.max_degree() == 0) { + // first time, initialize + degree_set.swap(current_degree_set); + } + else { + degree_set.intersect(current_degree_set); + } + + // if the degree set is trivial, we are done + if (degree_set.is_trivial()) { + fs.push_back(f_pp, k); + return true; + } + + // we found a candidate, lets keep it if it has less factors than the current best + trials ++; + if (zp_fs.distinct_factors() == 0 || zp_fs.total_factors() > current_fs.total_factors()) { + zp_fs.swap(current_fs); + nm.set(zp_fs_p, p); + + TRACE("polynomial::factorization::bughunt", + tout << "best zp factorization (Z_" << nm.to_string(zp_fs_p) << "): "; + tout << zp_fs << endl; + tout << "best degree set: "; degree_set.display(tout); tout << endl; + ); + } + } +#ifndef _EXTERNAL_RELEASE + IF_VERBOSE(FACTOR_VERBOSE_LVL, verbose_stream() << "(polynomial-factorization :at GF_" << nm.to_string(zp_upm.p()) << ")" << std::endl;); +#endif + + // make sure to set the zp_manager back to modulo zp_fs_p + zp_upm.set_zp(zp_fs_p); + + TRACE("polynomial::factorization::bughunt", + tout << "best zp factorization (Z_" << nm.to_string(zp_fs_p) << "): " << zp_fs << endl; + tout << "best degree set: "; degree_set.display(tout); tout << endl; + ); + + // get a bound on B for the factors of f_pp with degree less or equal to deg(f)/2 + // and then choose e to be smallest such that p^e > 2*lc(f)*B, we use the mignotte + unsigned e = mignotte_bound(upm, f_pp, zp_fs_p); + TRACE("polynomial::factorization::bughunt", + tout << "out p = " << nm.to_string(zp_fs_p) << ", and we'll work p^e for e = " << e << endl; + ); + + // we got a prime factoring, so we do the lifting now + zp_manager zpe_upm(nm.m()); + zpe_upm.set_zp(zp_fs_p); + zp_numeral_manager & zpe_nm = zpe_upm.m(); + + zp_factors zpe_fs(zpe_upm); + // this might give something bigger than p^e, but the lifting proocedure will update the zpe_nm + // zp factors are monic, so will be the zpe factors, i.e. f_pp = zpe_fs * lc(f_pp) (mod p^e) + hensel_lift(upm, f_pp, zp_fs, e, zpe_fs); + +#ifndef _EXTERNAL_RELEASE + IF_VERBOSE(FACTOR_VERBOSE_LVL, verbose_stream() << "(polynomial-factorization :num-candidate-factors " << zpe_fs.distinct_factors() << ")" << std::endl;); +#endif + + // the leading coefficient of f_pp mod p^e + scoped_numeral f_pp_lc(nm); + zpe_nm.set(f_pp_lc, f_pp.back()); + + // we always keep in f_pp the the actual primitive part f_pp*lc(f_pp) + upm.mul(f_pp, f_pp_lc); + + // now we go through the combinations of factors to check construct the factorization + ufactorization_combination_iterator it(zpe_fs, degree_set); + scoped_numeral_vector trial_factor(nm), trial_factor_quo(nm); + scoped_numeral trial_factor_cont(nm); + TRACE("polynomial::factorization::bughunt", + tout << "STARTING TRIAL DIVISION" << endl; + tout << "zpe_fs" << zpe_fs << endl; + tout << "degree_set = "; degree_set.display(tout); tout << endl; + ); + bool result = true; + bool remove = false; + unsigned counter = 0; + while (it.next(remove)) { + upm.checkpoint(); + counter++; + if (counter > params.m_max_search_size) { + // stop search + result = false; + break; + } + // + // our bound ensures we can extract the right factors of degree at most 1/2 of the original + // so, if out trial factor has degree bigger than 1/2, we need to take the rest of the factors + // but, if we take the rest and it works, it doesn't mean that the rest is factorized, so we still take out + // the original factor + bool using_left = it.current_degree() <= zp_fs.get_degree()/2; + if (using_left) { + // do a quick check first + scoped_numeral tmp(nm); + it.get_left_tail_coeff(f_pp_lc, tmp); + if (!nm.divides(tmp, f_pp[0])) { + // don't remove this combination + remove = false; + continue; + } + it.left(trial_factor); + } + else { + // do a quick check first + scoped_numeral tmp(nm); + it.get_right_tail_coeff(f_pp_lc, tmp); + if (!nm.divides(tmp, f_pp[0])) { + // don't remove this combination + remove = false; + continue; + } + it.right(trial_factor); + } + + // add the lc(f_pp) to the trial divisor + zpe_upm.mul(trial_factor, f_pp_lc); + TRACE("polynomial::factorization::bughunt", + tout << "f_pp*lc(f_pp) = "; upm.display(tout, f_pp); tout << endl; + tout << "trial_factor = "; upm.display(tout, trial_factor); tout << endl; + ); + + bool true_factor = upm.exact_div(f_pp, trial_factor, trial_factor_quo); + + TRACE("polynomial::factorization::bughunt", + tout << "trial_factor = "; upm.display(tout, trial_factor); tout << endl; + tout << "trial_factor_quo = "; upm.display(tout, trial_factor_quo); tout << endl; + tout << "result = " << (true_factor ? "true" : "false") << endl; + ); + + // if division is precise we have a factor + if (true_factor) { + if (!using_left) { + // as noted above, we still use the original factor + trial_factor.swap(trial_factor_quo); + } + // We need to get the content out of the factor + upm.get_primitive_and_content(trial_factor, trial_factor, trial_factor_cont); + // add the factor + fs.push_back(trial_factor, k); + // we continue with the quotient (with the content added back) + // but we also have to keep lc(f_pp)*f_pp + upm.get_primitive_and_content(trial_factor_quo, f_pp, trial_factor_cont); + nm.set(f_pp_lc, f_pp.back()); + upm.mul(f_pp, f_pp_lc); + // but we also remove it from the iterator + remove = true; + } + else { + // don't remove this combination + remove = false; + } + TRACE("polynomial::factorization::bughunt", + tout << "factors = " << fs << endl; + tout << "f_pp*lc(f_pp) = "; upm.display(tout, f_pp); tout << endl; + tout << "lc(f_pp) = " << f_pp_lc << endl; + ); + } +#ifndef _EXTERNAL_RELEASE + IF_VERBOSE(FACTOR_VERBOSE_LVL, verbose_stream() << "(polynomial-factorization :search-size " << counter << ")" << std::endl;); +#endif + + // add the what's left to the factors (if not a constant) + if (f_pp.size() > 1) { + upm.div(f_pp, f_pp_lc); + fs.push_back(f_pp, k); + } + else { + // if a constant it must be 1 (it was primitve) + SASSERT(f_pp.size() == 1 && nm.is_one(f_pp.back())); + } + + return result; +} + +bool factor_square_free(z_manager & upm, numeral_vector const & f, factors & fs, factor_params const & params) { + return factor_square_free(upm, f, fs, 1, params); +} + +}; // end upolynomial namespace diff --git a/lib/upolynomial_factorization.h b/lib/upolynomial_factorization.h new file mode 100644 index 000000000..e687786da --- /dev/null +++ b/lib/upolynomial_factorization.h @@ -0,0 +1,96 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + upolynomial_factorization.h + +Abstract: + + Methods for factoring polynomials. + +Author: + + Dejan (t-dejanj) 2011-11-29 + +Notes: + + [1] Elwyn Ralph Berlekamp. Factoring Polynomials over Finite Fields. Bell System Technical Journal, + 46(8-10):1853–1859, 1967. + [2] Donald Ervin Knuth. The Art of Computer Programming, volume 2: Seminumerical Algorithms. Addison Wesley, third + edition, 1997. + [3] Henri Cohen. A Course in Computational Algebraic Number Theory. Springer Verlag, 1993. + +--*/ +#ifndef _UPOLYNOMIAL_FACTORIZATION_H_ +#define _UPOLYNOMIAL_FACTORIZATION_H_ + +#include"upolynomial.h" +#include"polynomial.h" +#include"bit_vector.h" +#include"z3_exception.h" + +namespace upolynomial { + typedef manager::scoped_numeral scoped_numeral; + + /** + \breif Factor f into f = f_1^k_1 * ... * p_n^k_n, such that p_i are square-free and coprime. + */ + void zp_square_free_factor(zp_manager & zp_upm, numeral_vector const & f, zp_factors & sq_free_factors); + + /** + \brief Factor the monic square-free polynomial f from Z_p[x]. Returns true if factorization was sucesseful, or false + if f is an irreducible square-free polynomial in Z_p[x]. + */ + bool zp_factor_square_free(zp_manager & zp_upm, numeral_vector const & f, zp_factors & factors); + + inline bool zp_factor_square_free(zp_manager & zp_upm, numeral_vector const & f, zp_factors & factors, factor_params const & params) { + return zp_factor_square_free(zp_upm, f, factors); + } + + /** + \brief Factor the monic square-free polynomial f from Z_p[x] using the Berlekamp algorithm. If randomized is true + the factor splitting is done randomly [3], otherwise it is done as in the original Berlekamp [1]. + */ + bool zp_factor_square_free_berlekamp(zp_manager & zp_upm, numeral_vector const & f, zp_factors & factors, bool randomized = true); + + /** + \brief Factor the polynomial f from Z_p[x]. Returns true if factorization was sucesseful, or false if f is + an irreducible polynomial in Z_p[x] + */ + bool zp_factor(zp_manager & zp_upm, numeral_vector const & f, zp_factors & factors); + + /** + \brief Performs a Hensel lift of A and B in Z_a to Z_b, where p is prime and and a = p^{a_k}, b = p^{b_k}, + r = (a, b), with the following assumptions: + * UA + VB = 1 (mod a) + * C = AB (mod b) + * (l(A), r) = 1 (importand in order to divide by A, i.e. to invert l(A)) + the output of is two polynomials A1, B1 (replacing A and B) such that A1 = A (mod b), B1 = B (mod b), + l(A1) = l(A), deg(A1) = deg(A), deg(B1) = deg(B) and C = A1 B1 (mod b*r). Such A1, B1 are unique if + r is prime. See [3] p. 138. + + The method will also change the zp_manager's module from b to b*r + */ + void hensel_lift(z_manager & upm, numeral const & a, numeral const & b, numeral const & r, + numeral_vector const & U, numeral_vector const & A, numeral_vector const & V, numeral_vector const & B, + numeral_vector const & C, numeral_vector & A_lifted, numeral_vector & B_lifted); + + /** + \brief Performs the Hensel lift for the (monic!) factors_p of f in Z_p to Z_{p^e}. + */ + void hensel_lift(z_manager & upm, numeral_vector const & f, zp_factors const & factors_p, unsigned e, zp_factors & factors_pe); + + /** + \brief Factor the square-free polynomial f from Z[x]. Returns true if factorization was sucesseful, or false if + f is an irreducible polynomial in Z[x]. The vector of factors is cleared. + */ + bool factor_square_free(z_manager & upm, numeral_vector const & f, factors & fs, factor_params const & ps = factor_params()); + /** + Similar to factor_square_free, but it is used to factor the k-th component f^k of a polynomial. + That is, the factors of f are inserted as factors of degree k into fs. + */ + bool factor_square_free(z_manager & upm, numeral_vector const & f, factors & fs, unsigned k, factor_params const & ps = factor_params()); +}; + +#endif diff --git a/lib/upolynomial_factorization_int.h b/lib/upolynomial_factorization_int.h new file mode 100644 index 000000000..c076b2e6d --- /dev/null +++ b/lib/upolynomial_factorization_int.h @@ -0,0 +1,420 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + upolynomial_factorization_int.h + +Abstract: + + (Internal) header file for univariate polynomial factorization. + This classes are exposed for debugging purposes only. + +Author: + + Dejan (t-dejanj) 2011-11-29 + +Notes: + + [1] Elwyn Ralph Berlekamp. Factoring Polynomials over Finite Fields. Bell System Technical Journal, + 46(8-10):1853–1859, 1967. + [2] Donald Ervin Knuth. The Art of Computer Programming, volume 2: Seminumerical Algorithms. Addison Wesley, third + edition, 1997. + [3] Henri Cohen. A Course in Computational Algebraic Number Theory. Springer Verlag, 1993. + +--*/ +#ifndef _UPOLYNOMIAL_FACTORIZATION_INT_H_ +#define _UPOLYNOMIAL_FACTORIZATION_INT_H_ + +#include"upolynomial_factorization.h" + +namespace upolynomial { + // copy p from some manager to zp_p in Z_p[x] + inline void to_zp_manager(zp_manager & zp_upm, numeral_vector & p) { + zp_numeral_manager & zp_nm(zp_upm.m()); + for (unsigned i = 0; i < p.size(); ++ i) { + zp_nm.p_normalize(p[i]); + } + zp_upm.trim(p); + } + + // copy p from some manager to zp_p in Z_p[x] + inline void to_zp_manager(zp_manager & zp_upm, numeral_vector const & p, numeral_vector & zp_p) { + zp_numeral_manager & zp_nm(zp_upm.m()); + zp_upm.reset(zp_p); + for (unsigned i = 0; i < p.size(); ++ i) { + numeral p_i; // no need to delete, we keep it pushed in zp_p + zp_nm.set(p_i, p[i]); + zp_p.push_back(p_i); + } + zp_upm.trim(zp_p); + } + + /** + \brief Contains all possible degrees of a factorization of a polynomial. + If + p = p1^{k_1} * ... * pn^{k_n} with p_i of degree d_i + then it is represents numbers of the for \sum a_i*d_i, where a_i <= k_i. Two numbers always in the set are + deg(p) and 0. + + */ + class factorization_degree_set { + + // the set itself, a (m_max_degree)-binary number + bit_vector m_set; + + public: + + factorization_degree_set() { } + + factorization_degree_set(zp_factors const & factors) + { + zp_manager & upm = factors.upm(); + // the set contains only {0} + m_set.push_back(true); + for (unsigned i = 0; i < factors.distinct_factors(); ++ i) { + unsigned degree = upm.degree(factors[i]); + unsigned multiplicity = factors.get_degree(i); + for (unsigned k = 0; k < multiplicity; ++ k) { + bit_vector tmp(m_set); + m_set.shift_right(degree); + m_set |= tmp; + } + } + SASSERT(in_set(0) && in_set(factors.get_degree())); + } + + unsigned max_degree() const { return m_set.size() - 1; } + + void swap(factorization_degree_set & other) { + m_set.swap(other.m_set); + } + + bool is_trivial() const { + // check if set = {0, n} + for (int i = 1; i < (int) m_set.size() - 1; ++ i) { + if (m_set.get(i)) return false; + } + return true; + } + + void remove(unsigned k) { + m_set.set(k, false); + } + + bool in_set(unsigned k) const { + return m_set.get(k); + } + + void intersect(const factorization_degree_set& other) { + m_set &= other.m_set; + } + + void display(std::ostream & out) const { + out << "[0"; + for (unsigned i = 1; i <= max_degree(); ++ i) { + if (in_set(i)) { + out << ", " << i; + } + } + out << "] represented by " << m_set; + } + }; + + /** + \brief A to iterate through all combinations of factors. This is only needed for the factorization, and we + always iterate through the + */ + template + class factorization_combination_iterator_base { + + protected: + + + // total size of available factors + int m_total_size; + // maximal size of the selection + int m_max_size; + // the factors to select from + factors_type const & m_factors; + // which factors are enabled + svector m_enabled; + // the size of the current selection + int m_current_size; + // the current selection: indices at positions < m_current_size, other values are maxed out + svector m_current; + + /** + Assuming a valid selection m_current[0], ..., m_current[position], try to find the next option for + m_current[position], i.e. the first bigger one that's enabled. + */ + int find(int position, int upper_bound) { + int current = m_current[position] + 1; + while (current < upper_bound && !m_enabled[current]) { + current ++; + } + if (current == upper_bound) { + return -1; + } else { + return current; + } + } + + public: + + factorization_combination_iterator_base(factors_type const & factors) + : m_total_size(factors.distinct_factors()), + m_max_size(factors.distinct_factors()/2), + m_factors(factors) + { + SASSERT(factors.total_factors() > 1); + SASSERT(factors.total_factors() == factors.distinct_factors()); + // enable all to start with + m_enabled.resize(m_factors.distinct_factors(), true); + // max out the m_current so that it always fits + m_current.resize(m_factors.distinct_factors()+1, m_factors.distinct_factors()); + m_current_size = 0; + } + + /** + \brief Returns the factors we are enumerating through. + */ + factors_type const & get_factors() const { + return m_factors; + } + + /** + \brief Computes the next combination of factors and returns true if it exists. If remove current is true + it will eliminate the current selected elements from any future selection. + */ + bool next(bool remove_current) { + + int max_upper_bound = m_factors.distinct_factors(); + + do { + + // the index we are currently trying to fix + int current_i = m_current_size - 1; + // the value we found as plausable (-1 we didn't find anything) + int current_value = -1; + + if (remove_current) { + SASSERT(m_current_size > 0); + // disable the elements of the current selection from ever appearing again + for (current_i = m_current_size - 1; current_i > 0; -- current_i) { + SASSERT(m_enabled[m_current[current_i]]); + m_enabled[m_current[current_i]] = false; + m_current[current_i] = max_upper_bound; + } + // the last one + SASSERT(m_enabled[m_current[0]]); + m_enabled[m_current[0]] = false; + // not removing current anymore + remove_current = false; + // out max size is also going down + m_total_size -= m_current_size; + m_max_size = m_total_size/2; + } + + // we go back to the first one that can be increased (if removing current go all the way) + while (current_i >= 0) { + current_value = find(current_i, m_current[current_i + 1]); + if (current_value >= 0) { + // found one + m_current[current_i] = current_value; + break; + } else { + // go back some more + current_i --; + } + } + + do { + + if (current_value == -1) { + // we couldn't find any options, we have to increse size and start from the first one of that size + if (m_current_size >= m_max_size) { + return false; + } else { + m_current_size ++; + m_current[0] = -1; + current_i = 0; + current_value = find(current_i, max_upper_bound); + // if we didn't find any, we are done + if (current_value == -1) { + return false; + } else { + m_current[current_i] = current_value; + } + } + } + + // ok we have a new selection for the current one + for (current_i ++; current_i < m_current_size; ++ current_i) { + // start from the previous one + m_current[current_i] = m_current[current_i-1]; + current_value = find(current_i, max_upper_bound); + if (current_value == -1) { + // screwed, didn't find the next one, this means we need to increase the size + m_current[0] = -1; + break; + } else { + m_current[current_i] = current_value; + } + } + + } while (current_value == -1); + + } while (filter_current()); + + // found the next one, hurray + return true; + } + + /** + \brief A function that returns true if the current combination should be ignored. + */ + virtual bool filter_current() const = 0; + + /** + \brief Returns the size of the current selection (cardinality) + */ + unsigned left_size() const { + return m_current_size; + } + + /** + \brief Returns the size of the rest of the current selection (cardinality) + */ + unsigned right_size() const { + return m_total_size - m_current_size; + } + + void display(std::ostream& out) const { + out << "[ "; + for (unsigned i = 0; i < m_current.size(); ++ i) { + out << m_current[i] << " "; + } + out << "] from [ "; + for (unsigned i = 0; i < m_factors.distinct_factors(); ++ i) { + if (m_enabled[i]) { + out << i << " "; + } + } + out << "]" << std::endl; + } + + + }; + + class ufactorization_combination_iterator : public factorization_combination_iterator_base { + + // the degree sets to choose from + factorization_degree_set const & m_degree_set; + + public: + + ufactorization_combination_iterator(zp_factors const & factors, factorization_degree_set const & degree_set) + : factorization_combination_iterator_base(factors), + m_degree_set(degree_set) + {} + + /** + \brief Filter the ones not in the degree set. + */ + bool filter_current() const { + + // select only the ones that have degrees in the degree set + if (!m_degree_set.in_set(current_degree())) { + return true; + } + return false; + } + + /** + \brief Returns the degree of the current selection. + */ + unsigned current_degree() const { + unsigned degree = 0; + zp_manager & upm = m_factors.pm(); + for (unsigned i = 0; i < left_size(); ++ i) { + degree += upm.degree(m_factors[m_current[i]]); + } + return degree; + } + + void left(numeral_vector & out) const { + SASSERT(m_current_size > 0); + zp_manager & upm = m_factors.upm(); + upm.set(m_factors[m_current[0]].size(), m_factors[m_current[0]].c_ptr(), out); + for (int i = 1; i < m_current_size; ++ i) { + upm.mul(out.size(), out.c_ptr(), m_factors[m_current[i]].size(), m_factors[m_current[i]].c_ptr(), out); + } + } + + void get_left_tail_coeff(numeral const & m, numeral & out) { + zp_numeral_manager & nm = m_factors.upm().m(); + nm.set(out, m); + for (int i = 0; i < m_current_size; ++ i) { + nm.mul(out, m_factors[m_current[i]][0], out); + } + } + + void get_right_tail_coeff(numeral const & m, numeral & out) { + zp_numeral_manager & nm = m_factors.upm().m(); + nm.set(out, m); + + unsigned current = 0; + unsigned selection_i = 0; + + // selection is ordered, so we just take the ones in between that are not disable + while (current < m_factors.distinct_factors()) { + if (!m_enabled[current]) { + // by skipping the disabled we never skip a selected one + current ++; + } else { + if (selection_i >= m_current.size() || (int) current < m_current[selection_i]) { + SASSERT(m_factors.get_degree(current) == 1); + nm.mul(out, m_factors[current][0], out); + current ++; + } else { + current ++; + selection_i ++; + } + } + } + } + + void right(numeral_vector & out) const { + SASSERT(m_current_size > 0); + zp_manager & upm = m_factors.upm(); + upm.reset(out); + + unsigned current = 0; + unsigned selection_i = 0; + + // selection is ordered, so we just take the ones in between that are not disable + while (current < m_factors.distinct_factors()) { + if (!m_enabled[current]) { + // by skipping the disabled we never skip a selected one + current ++; + } else { + if (selection_i >= m_current.size() || (int) current < m_current[selection_i]) { + SASSERT(m_factors.get_degree(current) == 1); + if (out.size() == 0) { + upm.set(m_factors[current].size(), m_factors[current].c_ptr(), out); + } else { + upm.mul(out.size(), out.c_ptr(), m_factors[current].size(), m_factors[current].c_ptr(), out); + } + current ++; + } else { + current ++; + selection_i ++; + } + } + } + } + }; +}; + +#endif diff --git a/lib/use_list.cpp b/lib/use_list.cpp new file mode 100644 index 000000000..15ef74714 --- /dev/null +++ b/lib/use_list.cpp @@ -0,0 +1,90 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + use_list.cpp + +Abstract: + + Use list term index. + +Author: + + Leonardo de Moura (leonardo) 2008-02-04. + +Revision History: + +--*/ +#include"use_list.h" + +void app_use_list::inc_ref(app * n) { + if (n->get_num_args() == 0) + return; // ignore constants + unsigned id = n->get_id(); + unsigned c = m_ref_counter.get(id, 0); + m_ref_counter.setx(id, c+1, 0); + if (c == 0) + m_todo.push_back(n); +} + +void app_use_list::dec_ref(app * n) { + if (n->get_num_args() == 0) + return; // ignore constants + unsigned id = n->get_id(); + SASSERT(m_ref_counter[id] > 0); + m_ref_counter[id]--; + if (m_ref_counter[id] == 0) + m_todo.push_back(n); +} + +void app_use_list::insert(expr * n) { + if (is_var(n)) + return; // nothing to index + SASSERT(m_todo.empty()); + inc_ref(to_app(n)); + while (!m_todo.empty()) { + app * n = m_todo.back(); + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * c = n->get_arg(i); + if (is_var(c)) { + if (!m_ignore_vars) + use_list::insert(n, c); + } + else { + SASSERT(is_app(c)); + use_list::insert(n, c); + inc_ref(to_app(c)); + } + } + } +} + +void app_use_list::erase(expr * n) { + if (is_var(n)) + return; // nothing to index + SASSERT(m_todo.empty()); + dec_ref(to_app(n)); + while (!m_todo.empty()) { + app * n = m_todo.back(); + unsigned num_args = n->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + expr * c = n->get_arg(i); + if (is_var(c)) { + if (!m_ignore_vars) + use_list::erase(n, c); + } + else { + SASSERT(is_app(c)); + use_list::erase(n, c); + dec_ref(to_app(c)); + } + } + } +} + +void app_use_list::reset() { + use_list::reset(); + m_ref_counter.reset(); +} diff --git a/lib/use_list.h b/lib/use_list.h new file mode 100644 index 000000000..28220dbf7 --- /dev/null +++ b/lib/use_list.h @@ -0,0 +1,120 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + use_list.h + +Abstract: + + Use list expression index. + +Author: + + Leonardo de Moura (leonardo) 2008-02-04. + +Revision History: + +--*/ +#ifndef _USE_LIST_H_ +#define _USE_LIST_H_ + +#include"ast.h" +#include"vector.h" + +/** + \brief Generic use-list data-structure. +*/ +template +class use_list { + typedef vector set; + vector m_use_list; +public: + typedef typename set::const_iterator iterator; + use_list() {} + + void insert(T const & parent, expr * child) { + unsigned id = child->get_id(); + if (id >= m_use_list.size()) + m_use_list.resize(id+1, set()); + set & s = m_use_list[id]; + s.push_back(parent); + } + + void erase(T const & parent, expr * child) { + unsigned id = child->get_id(); + if (id >= m_use_list.size()) + return; + set & s = m_use_list[id]; + s.erase(parent); + } + + void reset() { + m_use_list.reset(); + } + + iterator begin(expr * e) const { + unsigned id = e->get_id(); + if (id >= m_use_list.size()) + return 0; + return m_use_list[id].begin(); + } + + iterator end(expr * e) const { + unsigned id = e->get_id(); + if (id >= m_use_list.size()) + return 0; + return m_use_list[id].end(); + } + + bool empty(expr * e) const { + unsigned id = e->get_id(); + if (id >= m_use_list.size()) + return true; + return m_use_list[id].empty(); + } +}; + +/** + \brief Index for tracking the uses of an expression. It is a + mapping from expressions to expressions. For example, consider the + term (f a (g a)), the constant a is used by f and g applications. + + \remark The expressions inserted in this index should not contain + quantifiers. + + \warning This index will not increase the reference counter of the + indexed expressions. +*/ +class app_use_list : use_list { + + bool m_ignore_vars; //!< when true, variables are not indexed + unsigned_vector m_ref_counter; + ptr_vector m_todo; + + void inc_ref(app * n); + void dec_ref(app * n); + +public: + /** + \brief If ignore_vars = true, then the index will not track + the use of variables. + */ + app_use_list(bool ignore_vars = true): + m_ignore_vars(ignore_vars) { + } + + /** + \brief Update the use list of all direct/indirect children of n. + */ + void insert(expr * n); + + /** + \brief Remove n (and its unreachable direct/indirect children) from the index. + */ + void erase(expr * n); + + void reset(); +}; + +#endif /* _USE_LIST_H_ */ diff --git a/lib/used_symbols.h b/lib/used_symbols.h new file mode 100644 index 000000000..81086d753 --- /dev/null +++ b/lib/used_symbols.h @@ -0,0 +1,105 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + used_symbols.h + +Abstract: + + Collect the symbols used in an expression. + +Author: + + Leonardo de Moura (leonardo) 2011-01-11. + +Revision History: + +--*/ +#ifndef _USED_SYMBOLS_H_ +#define _USED_SYMBOLS_H_ + +#include"ast.h" +#include"hashtable.h" +#include"obj_hashtable.h" + +struct do_nothing_rename_proc { + symbol operator()(symbol const & s) const { return s; } +}; + +/** + \brief Functor for collecting the symbols used in an expression. +*/ +template +class used_symbols : public RENAME_PROC { + typedef hashtable symbol_set; + + symbol_set m_used; + obj_hashtable m_visited; + ptr_vector m_todo; + + void found(symbol const & s) { m_used.insert(RENAME_PROC::operator()(s)); } + + void visit(expr * n) { + if (!m_visited.contains(n)) { + m_visited.insert(n); + m_todo.push_back(n); + } + } + +public: + used_symbols(RENAME_PROC const & p = RENAME_PROC()): + RENAME_PROC(p) { + } + + void operator()(expr * n, bool ignore_quantifiers = false) { + m_visited.reset(); + m_used.reset(); + m_todo.reset(); + visit(n); + while (!m_todo.empty()) { + n = m_todo.back(); + m_todo.pop_back(); + unsigned j; + switch (n->get_kind()) { + case AST_APP: + found(to_app(n)->get_decl()->get_name()); + j = to_app(n)->get_num_args(); + while (j > 0) { + --j; + visit(to_app(n)->get_arg(j)); + } + break; + case AST_QUANTIFIER: + if (!ignore_quantifiers) { + found(to_quantifier(n)->get_qid()); + unsigned num_decls = to_quantifier(n)->get_num_decls(); + for (unsigned i = 0; i < num_decls; i++) + found(to_quantifier(n)->get_decl_name(i)); + unsigned num_pats = to_quantifier(n)->get_num_patterns(); + for (unsigned i = 0; i < num_pats; i++) + visit(to_quantifier(n)->get_pattern(i)); + unsigned num_no_pats = to_quantifier(n)->get_num_no_patterns(); + for (unsigned i = 0; i < num_no_pats; i++) + visit(to_quantifier(n)->get_no_pattern(i)); + visit(to_quantifier(n)->get_expr()); + } + break; + default: + break; + } + } + } + + bool contains(symbol const & s) const { return m_used.contains(RENAME_PROC::operator()(s)); } + + bool contains_core(symbol const & s) const { return m_used.contains(s); } + + void insert(symbol const & s) { m_used.insert(RENAME_PROC::operator()(s)); } + + void insert_core(symbol const & s) { m_used.insert(s); } + + void erase_core(symbol const & s) { m_used.erase(s); } +}; + +#endif /* _USED_SYMBOLS_H_ */ diff --git a/lib/used_vars.cpp b/lib/used_vars.cpp new file mode 100644 index 000000000..6353ad5fd --- /dev/null +++ b/lib/used_vars.cpp @@ -0,0 +1,113 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + used_vars.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-01-14. + +Revision History: + +--*/ + +#include"used_vars.h" + +void used_vars::process(expr * n, unsigned delta) { + unsigned j, idx; + + m_cache.reset(); + m_todo.reset(); + m_todo.push_back(expr_delta_pair(n, delta)); + + while (!m_todo.empty()) { + expr_delta_pair const & p = m_todo.back(); + + n = p.m_node; + + if (n->get_ref_count() > 1 && m_cache.contains(p)) { + m_todo.pop_back(); + continue; + } + + if (n->get_ref_count() > 1) { + // cache only shared and non-constant nodes + m_cache.insert(p); + } + + delta = p.m_delta; + m_todo.pop_back(); + + switch (n->get_kind()) { + case AST_APP: + j = to_app(n)->get_num_args(); + while (j > 0) { + --j; + expr * arg = to_app(n)->get_arg(j); + m_todo.push_back(expr_delta_pair(arg, delta)); + } + break; + case AST_VAR: + idx = to_var(n)->get_idx(); + if (idx >= delta) { + idx = idx - delta; + if (idx >= m_found_vars.size()) + m_found_vars.resize(idx + 1, 0); + m_found_vars[idx] = to_var(n)->get_sort(); + } + break; + case AST_QUANTIFIER: + // recurse so that memoization is correct with respect to 'delta'. + delta += to_quantifier(n)->get_num_decls(); + j = to_quantifier(n)->get_num_patterns(); + while (j > 0) { + --j; + m_todo.push_back(expr_delta_pair(to_quantifier(n)->get_pattern(j), delta)); + } + j = to_quantifier(n)->get_num_no_patterns(); + while (j > 0) { + --j; + m_todo.push_back(expr_delta_pair(to_quantifier(n)->get_no_pattern(j), delta)); + } + m_todo.push_back(expr_delta_pair(to_quantifier(n)->get_expr(), delta)); + break; + default: + break; + } + } +} + +bool used_vars::uses_all_vars(unsigned num_decls) const { + if (num_decls > m_found_vars.size()) + return false; + for (unsigned i = 0; i < num_decls; i++) { + if (!m_found_vars[i]) + return false; + } + return true; +} + +bool used_vars::uses_a_var(unsigned num_decls) const { + num_decls = std::min(num_decls, m_found_vars.size()); + for (unsigned i = 0; i < num_decls; i++) { + if (m_found_vars[i]) + return true; + } + return false; +} + +unsigned used_vars::get_num_vars() const { + unsigned r = 0; + unsigned num = m_found_vars.size(); + for (unsigned i = 0; i < num; i++) { + if (m_found_vars[i]) + return r++; + } + return r; +} diff --git a/lib/used_vars.h b/lib/used_vars.h new file mode 100644 index 000000000..142ce9bcd --- /dev/null +++ b/lib/used_vars.h @@ -0,0 +1,59 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + used_vars.h + +Abstract: + + Functor used to collect the set of used variables. + +Author: + + Leonardo de Moura (leonardo) 2008-01-14. + +Revision History: + +--*/ +#ifndef _USED_VARS_H_ +#define _USED_VARS_H_ + +#include"ast.h" +#include"expr_delta_pair.h" + +class used_vars { + ptr_vector m_found_vars; + typedef hashtable, default_eq > cache; + cache m_cache; + svector m_todo; + + void process(expr * n, unsigned delta); + +public: + + void operator()(expr * n) { + m_found_vars.reset(); + process(n, 0); + } + + void reset() { + m_found_vars.reset(); + } + + void process(expr * n) { + process(n, 0); + } + + unsigned get_max_found_var_idx_plus_1() const { return m_found_vars.size(); } + + sort * get(unsigned var_idx) const { return m_found_vars[var_idx]; } + sort * contains(unsigned var_idx) const { return var_idx < m_found_vars.size() ? m_found_vars[var_idx] : 0; } + + bool uses_all_vars(unsigned num_decls) const; + bool uses_a_var(unsigned num_decls) const; + unsigned get_num_vars() const; +}; + +#endif /* _USED_VARS_H_ */ + diff --git a/lib/user_decl_plugin.cpp b/lib/user_decl_plugin.cpp new file mode 100644 index 000000000..97264ce0b --- /dev/null +++ b/lib/user_decl_plugin.cpp @@ -0,0 +1,97 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + user_decl_plugin.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-05-22. + +Revision History: + +--*/ +#include"user_decl_plugin.h" +#include"warning.h" + +user_decl_plugin::user_decl_plugin() { +} + +void user_decl_plugin::finalize() { + m_manager->dec_array_ref(m_kind2func.size(), m_kind2func.c_ptr()); + m_manager->dec_array_ref(m_kind2sort.size(), m_kind2sort.c_ptr()); +} + +decl_plugin * user_decl_plugin::mk_fresh() { + user_decl_plugin * p = alloc(user_decl_plugin); + // TODO copy sorts and other goodness + return p; +} + +sort * user_decl_plugin::mk_sort(symbol const & name) { + unsigned kind = m_kind2sort.size(); + sort * s = m_manager->mk_sort(name, sort_info(m_family_id, kind)); + m_kind2sort.push_back(s); + m_manager->inc_ref(s); + if (!name.is_numerical()) { + m_sort_names.push_back(builtin_name(name.bare_str(), static_cast(kind))); + } + return s; +} + +func_decl * user_decl_plugin::mk_func_decl(symbol const & name, unsigned arity, sort * const * domain, sort * range) { + unsigned kind = m_kind2func.size(); + func_decl * f = m_manager->mk_func_decl(name, arity, domain, range, func_decl_info(m_family_id, kind)); + m_kind2func.push_back(f); + m_manager->inc_ref(f); + if (!name.is_numerical()) { + m_op_names.push_back(builtin_name(name.bare_str(), static_cast(kind))); + } + return f; +} + +func_decl * user_decl_plugin::mk_value_decl(symbol const & name, sort * s) { + func_decl * f = mk_func_decl(name, 0, 0, s); + m_values.insert(f); + return f; +} + +sort * user_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) { + if (num_parameters > 0) { + throw default_exception("invalid user theory sort"); + return 0; + } + return m_kind2sort.get(k, 0); +} + +func_decl * user_decl_plugin::mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range) { + func_decl * f = m_kind2func.get(k, 0); + if (num_parameters > 0 || f == 0) { + throw default_exception("invalid user theory function operator"); + return 0; + } + return f; +} + +bool user_decl_plugin::is_value(app * v) const { + return m_values.contains(v->get_decl()); +} + +bool user_decl_plugin::is_value(func_decl * f) const { + return m_values.contains(f); +} + +void user_decl_plugin::get_op_names(svector & op_names, symbol const & logic) { + op_names.append(m_op_names); +} + +void user_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { + sort_names.append(m_sort_names); +} + diff --git a/lib/user_decl_plugin.h b/lib/user_decl_plugin.h new file mode 100644 index 000000000..dc2fd55a1 --- /dev/null +++ b/lib/user_decl_plugin.h @@ -0,0 +1,60 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + user_decl_plugin.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-05-22. + +Revision History: + +--*/ +#ifndef _USER_DECL_PLUGIN_H_ +#define _USER_DECL_PLUGIN_H_ + +#include"ast.h" +#include"obj_hashtable.h" + +class user_decl_plugin : public decl_plugin { + ptr_vector m_kind2sort; + ptr_vector m_kind2func; + obj_hashtable m_values; + svector m_op_names; + svector m_sort_names; +public: + user_decl_plugin(); + + virtual ~user_decl_plugin() {} + virtual void finalize(); + + virtual decl_plugin * mk_fresh(); + + sort * mk_sort(symbol const & name); + + func_decl * mk_func_decl(symbol const & name, unsigned arity, sort * const * domain, sort * range); + + func_decl * mk_value_decl(symbol const & name, sort * s); + + virtual sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters); + + virtual func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, + unsigned arity, sort * const * domain, sort * range); + + virtual bool is_value(app*) const; + + bool is_value(func_decl *) const; + + virtual void get_op_names(svector & op_names, symbol const & logic); + + virtual void get_sort_names(svector & sort_names, symbol const & logic); +}; + +#endif /* _USER_DECL_PLUGIN_H_ */ + diff --git a/lib/user_rewriter.cpp b/lib/user_rewriter.cpp new file mode 100644 index 000000000..94c6c3858 --- /dev/null +++ b/lib/user_rewriter.cpp @@ -0,0 +1,24 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + user_rewriter.cpp + +Abstract: + + Rewriter for applying user-defined rewrite routine. + +Author: + + Nikolaj (nbjorner) 2012-01-08 + +Notes: + +--*/ + +#include "rewriter_def.h" +#include "user_rewriter.h" + + +template class rewriter_tpl; diff --git a/lib/user_rewriter.h b/lib/user_rewriter.h new file mode 100644 index 000000000..870425f89 --- /dev/null +++ b/lib/user_rewriter.h @@ -0,0 +1,58 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + user_rewriter.h + +Abstract: + + Rewriter for applying user-defined rewrite routine. + +Author: + + Nikolaj (nbjorner) 2012-01-08 + +Notes: + +--*/ +#ifndef _USER_REWRITER_H_ +#define _USER_REWRITER_H_ + +#include "ast.h" +#include "rewriter.h" + + +class user_rewriter : public default_rewriter_cfg { +public: + typedef bool fn(void* context, expr* expr_in, expr** expr_out, proof** proof_out); +private: + ast_manager& m; + rewriter_tpl m_rw; + void* m_ctx; + fn* m_rewriter; + bool m_rec; + +public: + user_rewriter(ast_manager & m): m(m), m_rw(m, m.proofs_enabled(), *this), m_rewriter(0), m_rec(false) {} + ~user_rewriter() {} + + void set_rewriter(void * ctx, fn* rw) { m_ctx = ctx; m_rewriter = rw; } + bool enabled() { return m_rewriter != 0; } + + void operator()(expr_ref& term) { expr_ref tmp(m); (*this)(tmp, term); } + void operator()(expr * t, expr_ref & result) { proof_ref pr(m); (*this)(t, result, pr); } + void operator()(expr * t, expr_ref & result, proof_ref & result_pr) { m_rw(t, result, result_pr); } + + void cancel() { set_cancel(true); } + void reset_cancel() { set_cancel(false); } + void set_cancel(bool f) { m_rw.set_cancel(f); } + void cleanup() { if (!m_rec) { m_rec = true; m_rw.cleanup(); m_rec = false; } } + void reset() { if (!m_rec) { m_rec = true; m_rw.reset(); m_rec = false; } } + + bool get_subst(expr* s, expr*& t, proof*& t_pr) { + return enabled() && m_rewriter(m_ctx, s, &t, &t_pr); + } +}; + +#endif diff --git a/lib/user_simplifier_plugin.cpp b/lib/user_simplifier_plugin.cpp new file mode 100644 index 000000000..866de805e --- /dev/null +++ b/lib/user_simplifier_plugin.cpp @@ -0,0 +1,81 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + user_simplifier_plugin.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-05-22. + +Revision History: + +--*/ +#include"user_simplifier_plugin.h" +#include"ast_pp.h" +#include"warning.h" + +user_simplifier_plugin::user_simplifier_plugin(symbol const & fname, ast_manager & m): + simplifier_plugin(fname, m), + m_owner(0), + m_enabled(true), + m_reduce_app_fptr(0), + m_reduce_eq_fptr(0), + m_reduce_distinct_fptr(0) { +} + +simplifier_plugin * user_simplifier_plugin::mk_fresh() { + ast_manager & m = get_manager(); + user_simplifier_plugin * new_sp = alloc(user_simplifier_plugin, m.get_family_name(get_family_id()), m); + new_sp->m_reduce_app_fptr = m_reduce_app_fptr; + new_sp->m_reduce_eq_fptr = m_reduce_eq_fptr; + new_sp->m_reduce_distinct_fptr = m_reduce_distinct_fptr; + return new_sp; +} + +bool user_simplifier_plugin::reduce(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result) { + if (m_reduce_app_fptr == 0 || !m_enabled) + return false; + expr * _result = 0; + bool flag = m_reduce_app_fptr(m_owner, f, num_args, args, &_result); + if (flag) { + if (_result == 0) + throw default_exception("invalid reduce_app callback: result is null"); + result = _result; + } + return flag; +} + +bool user_simplifier_plugin::reduce_eq(expr * lhs, expr * rhs, expr_ref & result) { + if (m_reduce_eq_fptr == 0 || !m_enabled) + return false; + expr * _result = 0; + bool flag = m_reduce_eq_fptr(m_owner, lhs, rhs, &_result); + if (flag) { + if (_result == 0) + throw default_exception("invalid reduce_eq callback: result is null"); + result = _result; + } + return flag; +} + +bool user_simplifier_plugin::reduce_distinct(unsigned num_args, expr * const * args, expr_ref & result) { + if (m_reduce_distinct_fptr == 0 || !m_enabled) + return false; + expr * _result = 0; + bool flag = m_reduce_distinct_fptr(m_owner, num_args, args, &_result); + if (flag) { + if (_result == 0) + throw default_exception("invalid reduce_distinct callback: result is null"); + result = _result; + } + return flag; +} + +void user_simplifier_plugin::flush_caches() { +} diff --git a/lib/user_simplifier_plugin.h b/lib/user_simplifier_plugin.h new file mode 100644 index 000000000..67e64037d --- /dev/null +++ b/lib/user_simplifier_plugin.h @@ -0,0 +1,66 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + user_simplifier_plugin.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-05-22. + +Revision History: + +--*/ +#ifndef _USER_SIMPLIFIER_PLUGIN_H_ +#define _USER_SIMPLIFIER_PLUGIN_H_ + +#include"simplifier_plugin.h" + +typedef bool (*reduce_app_fptr)(void *, func_decl *, unsigned, expr * const *, expr **); +typedef bool (*reduce_eq_fptr)(void *, expr *, expr *, expr **); +typedef bool (*reduce_distinct_fptr)(void *, unsigned, expr * const *, expr **); + +class user_simplifier_plugin : public simplifier_plugin { + void * m_owner; + bool m_enabled; + reduce_app_fptr m_reduce_app_fptr; + reduce_eq_fptr m_reduce_eq_fptr; + reduce_distinct_fptr m_reduce_distinct_fptr; + +public: + user_simplifier_plugin(symbol const & fname, ast_manager & m); + + virtual simplifier_plugin * mk_fresh(); + + void set_reduce_app_fptr(reduce_app_fptr ptr) { + m_reduce_app_fptr = ptr; + } + + void set_reduce_eq_fptr(reduce_eq_fptr ptr) { + m_reduce_eq_fptr = ptr; + } + + void set_reduce_distinct_fptr(reduce_distinct_fptr ptr) { + m_reduce_distinct_fptr = ptr; + } + + void enable(bool flag) { m_enabled = flag; } + + void set_owner(void * ptr) { m_owner = ptr; } + + virtual bool reduce(func_decl * f, unsigned num_args, expr * const * args, expr_ref & result); + + virtual bool reduce_eq(expr * lhs, expr * rhs, expr_ref & result); + + virtual bool reduce_distinct(unsigned num_args, expr * const * args, expr_ref & result); + + virtual void flush_caches(); +}; + +#endif /* _USER_SIMPLIFIER_PLUGIN_H_ */ + diff --git a/lib/user_smt_theory.cpp b/lib/user_smt_theory.cpp new file mode 100644 index 000000000..19b25dcfa --- /dev/null +++ b/lib/user_smt_theory.cpp @@ -0,0 +1,661 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + user_smt_theory.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-05-22. + +Revision History: + +--*/ +#include"smt_context.h" +#include"user_smt_theory.h" +#include"ast_pp.h" +#include"smt_model_generator.h" +#include"stats.h" +#include"warning.h" + +namespace smt { + + // + // value factory for user sorts. + // + // NB. This value factory for user theories + // does not address theories where model + // values are structured objects such as + // arrays, records, or data-types. + // + + class user_smt_theory_factory : public simple_factory { + app* mk_value_core(unsigned const& val, sort* s) { + return m_manager.mk_model_value(val, s); + } + public: + user_smt_theory_factory(ast_manager& m, family_id fid): + simple_factory(m, fid) + {} + }; + + user_theory::user_theory(ast_manager & m, front_end_params const& p, void * ext_context, void * ext_data, char const * name, family_id fid, user_decl_plugin * dp, user_simplifier_plugin * sp): + theory(fid), + m_params(p), + m_ext_context(ext_context), + m_ext_data(ext_data), + m_name(name), + m_simplify_axioms(false), + m_decl_plugin(dp), + m_simplifier_plugin(sp), + m_find(*this), + m_trail_stack(*this), + m_asserted_axioms(m), + m_persisted_axioms(m), + m_persisted_axioms_qhead(0), + m_delete_fptr(0), + m_new_app_fptr(0), + m_new_elem_fptr(0), + m_init_search_fptr(0), + m_push_fptr(0), + m_pop_fptr(0), + m_restart_fptr(0), + m_reset_fptr(0), + m_final_check_fptr(0), + m_new_eq_fptr(0), + m_new_diseq_fptr(0), + m_new_assignment_fptr(0), + m_new_relevant_fptr(0), + m_mk_fresh_fptr(0), + m_delete_invoking(false), + m_new_app_invoking(false), + m_new_elem_invoking(false), + m_init_search_invoking(false), + m_push_invoking(false), + m_pop_invoking(false), + m_restart_invoking(false), + m_reset_invoking(false), + m_final_check_invoking(false), + m_new_eq_invoking(false), + m_new_diseq_invoking(false), + m_new_assignment_invoking(false), + m_new_relevant_invoking(false) { + } + + user_theory::~user_theory() { + if (m_delete_fptr != 0) { + flet i(m_delete_invoking, true); + m_delete_fptr(this); + } + } + + theory * user_theory::mk_fresh(context * new_ctx) { + if (m_mk_fresh_fptr == 0) { + throw default_exception("The mk_fresh_ext_data callback was not set for user theory, you must use Z3_theory_set_mk_fresh_ext_data_callback"); + return 0; + } + user_simplifier_plugin * new_sp = static_cast(new_ctx->get_simplifier().get_plugin(get_family_id())); + SASSERT(new_sp != 0); + user_theory * new_th = alloc(user_theory, get_manager(), new_ctx->get_fparams(), m_ext_context, m_mk_fresh_fptr(this), get_name(), get_family_id(), + m_decl_plugin, new_sp); + new_sp->set_owner(new_th); + + new_th->m_delete_fptr = m_delete_fptr; + new_th->m_new_app_fptr = m_new_app_fptr; + new_th->m_new_elem_fptr = m_new_elem_fptr; + new_th->m_init_search_fptr = m_init_search_fptr; + new_th->m_push_fptr = m_push_fptr; + new_th->m_pop_fptr = m_pop_fptr; + new_th->m_restart_fptr = m_restart_fptr; + new_th->m_reset_fptr = m_reset_fptr; + new_th->m_final_check_fptr = m_final_check_fptr; + new_th->m_new_eq_fptr = m_new_eq_fptr; + new_th->m_new_diseq_fptr = m_new_diseq_fptr; + new_th->m_new_assignment_fptr = m_new_assignment_fptr; + new_th->m_new_relevant_fptr = m_new_relevant_fptr; + new_th->m_mk_fresh_fptr = m_mk_fresh_fptr; + return new_th; + } + + void user_theory::assert_axiom_core(app* a) { + if (m_asserted_axiom_set.contains(a)) { + return; + } + m_asserted_axiom_set.insert(a); + m_asserted_axioms.push_back(a); + if (m_params.m_user_theory_persist_axioms) { + m_persisted_axioms.push_back(a); + } + } + + /** + TODO: discuss semantics of assert_axiom. + Should we simplify axiom of not? + */ + void user_theory::assert_axiom(ast * axiom) { + ++m_stats.m_num_user_axioms; + TRACE("user_smt_theory", tout << mk_pp(axiom, get_manager()) << "\n";); + if (!is_expr(axiom)) { + throw default_exception("invalid expression"); + } + if (!get_manager().is_bool(to_expr(axiom))) { + throw default_exception("invalid theory axiom: axioms must have Boolean sort"); + } + if (!m_new_eq_invoking && + !m_new_diseq_invoking && + !m_new_assignment_invoking && + !m_new_relevant_invoking && + !m_final_check_invoking) { + throw default_exception("theory axioms can only be invoked during callbacks " + "for new (dis)equalities/assignments and final check"); + } + context & ctx = get_context(); + ast_manager & m = get_manager(); + + if (!is_app(axiom) || !to_app(axiom)->is_ground() || + ctx.get_fparams().m_user_theory_preprocess_axioms) { + asserted_formulas asf(m, ctx.get_fparams()); + asf.assert_expr(to_app(axiom)); + asf.reduce(); + unsigned sz = asf.get_num_formulas(); + unsigned qhead = asf.get_qhead(); + while (qhead < sz) { + expr * f = asf.get_formula(qhead); + assert_axiom_core(to_app(f)); + ++qhead; + } + } + else { + if (!m_simplify_axioms) { + m_simplifier_plugin->enable(false); + } + expr_ref s_axiom(m); + proof_ref pr(m); + simplifier & s = ctx.get_simplifier(); + s(to_app(axiom), s_axiom, pr); + if (!is_app(s_axiom)) { + throw default_exception("invalid theory axiom: axioms must be applications"); + } + axiom = s_axiom; + m_simplifier_plugin->enable(true); + assert_axiom_core(to_app(axiom)); + } + } + + void user_theory::assume_eq(ast * _lhs, ast * _rhs) { + if (!is_expr(_lhs) || !is_expr(_rhs)) { + throw default_exception("assume_eq must take expressions as arguments"); + } + expr* lhs = to_expr(_lhs); + expr* rhs = to_expr(_rhs); + ast_manager& m = get_manager(); + context& ctx = get_context(); + if (m.is_true(rhs)) { + std::swap(lhs, rhs); + } + if (m.is_true(lhs)) { + theory_var v2 = mk_var(rhs); + if (v2 == null_theory_var) { + throw default_exception("invalid assume eq: lhs or rhs is not a theory term"); + } + bool_var bv = ctx.get_bool_var(rhs); + ctx.set_true_first_flag(bv); + ctx.mark_as_relevant(get_enode(v2)); + return; + } + if (m.is_bool(lhs)) { + throw default_exception("assume_eq on Booleans must take 'true' as one of the arguments"); + } + theory_var v1 = mk_var(lhs); + theory_var v2 = mk_var(rhs); + if (v1 == null_theory_var || v2 == null_theory_var) { + throw default_exception("invalid assume eq: lhs or rhs is not a theory term"); + } + ctx.assume_eq(get_enode(v1), get_enode(v2)); + } + + void user_theory::reset_propagation_queues() { + m_new_eqs.reset(); + m_new_diseqs.reset(); + m_new_assignments.reset(); + m_new_relevant_apps.reset(); + } + + theory_var user_theory::get_var(ast * n) const { + if (!is_app(n)) + return null_theory_var; + context & ctx = get_context(); + if (ctx.e_internalized(to_app(n))) { + enode * e = ctx.get_enode(to_app(n)); + return e->get_th_var(get_id()); + } + return null_theory_var; + } + + theory_var user_theory::mk_var(ast * n) { + theory_var v = get_var(n); + if (v != null_theory_var || !is_app(n)) { + return v; + } + app* a = to_app(n); + if (a->get_family_id() == get_id() && + internalize_term(a)) { + return mk_var(get_context().get_enode(a)); + } + return v; + } + + ast * user_theory::get_root(ast * n) const { + theory_var v = get_var(n); + if (v != null_theory_var) { + theory_var r = m_find.find(v); + return get_ast(r); + } + return n; + } + + ast * user_theory::get_next(ast * n) const { + theory_var v = get_var(n); + if (v != null_theory_var) { + theory_var r = m_find.next(v); + return get_ast(r); + } + return n; + } + + void user_theory::shrink_use_list(unsigned sz) { + SASSERT(m_use_list.size() >= sz); + std::for_each(m_use_list.begin() + sz, m_use_list.end(), delete_proc >()); + m_use_list.shrink(sz); + } + + ptr_vector * user_theory::get_non_null_use_list(theory_var v) { + SASSERT(v != null_theory_var); + if (m_use_list[v] == 0) + m_use_list[v] = alloc(ptr_vector); + return m_use_list[v]; + } + + unsigned user_theory::get_num_parents(ast * n) const { + theory_var v = get_var(n); + if (v != null_theory_var && m_use_list[v] != 0) + return m_use_list[v]->size(); + return 0; + } + + + ast * user_theory::get_parent(ast * n, unsigned i) const { + theory_var v = get_var(n); + if (v != null_theory_var && m_use_list[v] != 0) + return m_use_list[v]->get(i, 0); + return 0; + } + + theory_var user_theory::mk_var(enode * n) { + if (is_attached_to_var(n)) + return n->get_th_var(get_id()); + theory_var r = theory::mk_var(n); + theory_var r2 = m_find.mk_var(); + m_use_list.push_back(0); + SASSERT(r == r2); + get_context().attach_th_var(n, this, r); + if (m_new_elem_fptr != 0) { + flet invoking(m_new_elem_invoking, true); + m_new_elem_fptr(this, n->get_owner()); + } + return r; + } + + bool user_theory::internalize_atom(app * atom, bool gate_ctx) { + return internalize_term(atom); + } + + bool user_theory::internalize_term(app * term) { + context & ctx = get_context(); + unsigned num_args = term->get_num_args(); + for (unsigned i = 0; i < num_args; i++) + ctx.internalize(term->get_arg(i), false); + // the internalization of the arguments may trigger the internalization of term. + if (ctx.e_internalized(term)) + return true; + m_parents.push_back(term); + enode * e = ctx.mk_enode(term, false, get_manager().is_bool(term), true); + if (get_manager().is_bool(term)) { + bool_var bv = ctx.mk_bool_var(term); + ctx.set_var_theory(bv, get_id()); + ctx.set_enode_flag(bv, true); + } + // make sure every argument is attached to a theory variable... + for (unsigned i = 0; i < num_args; i++) { + enode * arg = e->get_arg(i); + theory_var v_arg = mk_var(arg); + ptr_vector * arg_use_list = get_non_null_use_list(v_arg); + arg_use_list->push_back(term); + m_trail_stack.push(push_back_trail(*arg_use_list)); + } + if (m_new_app_fptr != 0) { + flet invoking(m_new_app_invoking, true); + m_new_app_fptr(this, term); + } + return true; + } + + void user_theory::apply_sort_cnstr(enode * n, sort * s) { + mk_var(n); + } + + void user_theory::assign_eh(bool_var v, bool is_true) { + m_new_assignments.push_back(v); + } + + void user_theory::new_eq_eh(theory_var v1, theory_var v2) { + m_new_eqs.push_back(var_pair(v1, v2)); + } + + void user_theory::new_diseq_eh(theory_var v1, theory_var v2) { + m_new_diseqs.push_back(var_pair(v1, v2)); + } + + void user_theory::relevant_eh(app * n) { + m_new_relevant_apps.push_back(n); + } + + void user_theory::push_scope_eh() { + SASSERT(m_new_assignments.empty()); + SASSERT(m_new_eqs.empty()); + SASSERT(m_new_diseqs.empty()); + SASSERT(m_new_relevant_apps.empty()); + theory::push_scope_eh(); + m_trail_stack.push_scope(); + m_scopes.push_back(scope()); + scope & s = m_scopes.back(); + s.m_asserted_axioms_old_sz = m_asserted_axioms.size(); + s.m_parents_old_sz = m_parents.size(); + if (m_push_fptr != 0) { + flet invoke(m_push_invoking, true); + m_push_fptr(this); + } + } + + void user_theory::pop_scope_eh(unsigned num_scopes) { + reset_propagation_queues(); + if (m_pop_fptr != 0) { + for (unsigned i = 0; i < num_scopes; i++) { + flet invoke(m_pop_invoking, true); + m_pop_fptr(this); + } + } + unsigned new_lvl = m_scopes.size() - num_scopes; + scope & s = m_scopes[new_lvl]; + m_parents.shrink(s.m_parents_old_sz); + unsigned curr_sz = m_asserted_axioms.size(); + unsigned old_sz = s.m_asserted_axioms_old_sz; + for (unsigned i = old_sz; i < curr_sz; i++) { + m_asserted_axiom_set.erase(m_asserted_axioms.get(i)); + } + m_asserted_axioms.shrink(old_sz); + m_scopes.shrink(new_lvl); + m_trail_stack.pop_scope(num_scopes); + shrink_use_list(get_old_num_vars(num_scopes)); + theory::pop_scope_eh(num_scopes); + } + + void user_theory::restart_eh() { + if (m_restart_fptr != 0) { + flet invoke(m_restart_invoking, true); + m_restart_fptr(this); + } + } + + + void user_theory::init_search_eh() { + if (m_init_search_fptr != 0) { + flet invoke(m_init_search_invoking, true); + m_init_search_fptr(this); + } + } + + final_check_status user_theory::final_check_eh() { + if (m_final_check_fptr != 0) { + unsigned old_sz = m_asserted_axioms.size(); + flet invoke(m_final_check_invoking, true); + Z3_bool r = m_final_check_fptr(this); + if (old_sz != m_asserted_axioms.size()) { + assert_axioms_into_context(old_sz); + return r ? FC_CONTINUE : FC_GIVEUP; + } + return r ? FC_DONE : FC_GIVEUP; + } + return FC_DONE; + } + + bool user_theory::can_propagate() { + return + (m_persisted_axioms.size() > m_persisted_axioms_qhead) || + !m_new_eqs.empty() || + !m_new_diseqs.empty() || + !m_new_relevant_apps.empty() || + !m_new_assignments.empty(); + } + + literal user_theory::internalize_literal(expr * arg) { + context & ctx = get_context(); + ast_manager& m = get_manager(); + if (is_app(arg) && m.is_not(arg)) { + expr * arg_arg = to_app(arg)->get_arg(0); + if (!ctx.b_internalized(arg_arg)) + ctx.internalize(arg_arg, true); + return literal(ctx.get_bool_var(arg_arg), true); + } + else if (m.is_false(arg)) { + return false_literal; + } + else if (m.is_true(arg)) { + return true_literal; + } + else { + if (!ctx.b_internalized(arg)) + ctx.internalize(arg, true); + return literal(ctx.get_bool_var(arg)); + } + } + + void user_theory::assert_axioms_into_context(unsigned old_sz) { + for (unsigned i = old_sz; i < m_asserted_axioms.size(); i++) { + expr * axiom = m_asserted_axioms.get(i); + assert_axiom_into_context(axiom); + } + } + + void user_theory::mark_as_relevant(literal l) { + if (l == false_literal || l == true_literal) return; + get_context().mark_as_relevant(l); + } + + void user_theory::assert_axiom_into_context(expr * axiom) { + TRACE("user_smt_theory", tout << mk_pp(axiom, get_manager()) << "\n";); + ast_manager & m = get_manager(); + context & ctx = get_context(); + if (m.is_or(axiom)) { + literal_buffer lits; + unsigned num_args = to_app(axiom)->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + lits.push_back(internalize_literal(to_app(axiom)->get_arg(i))); + mark_as_relevant(lits.back()); + } + ctx.mk_th_axiom(get_id(), lits.size(), lits.c_ptr()); + } + else { + literal l = internalize_literal(axiom); + mark_as_relevant(l); + ctx.mk_th_axiom(get_id(), 1, &l); + } + } + + void user_theory::propagate() { + + unsigned old_sz = m_asserted_axioms.size(); + if (m_persisted_axioms_qhead < m_persisted_axioms.size()) { + get_context().push_trail(value_trail(m_persisted_axioms_qhead)); + for (; m_persisted_axioms_qhead < m_persisted_axioms.size(); ++m_persisted_axioms_qhead) { + m_asserted_axioms.push_back(m_persisted_axioms[m_persisted_axioms_qhead].get()); + } + } + do { + for (unsigned i = 0; i < m_new_eqs.size(); i++) { + var_pair & p = m_new_eqs[i]; + if (m_new_eq_fptr != 0) { + ++m_stats.m_num_eq; + flet invoke(m_new_eq_invoking, true); + m_new_eq_fptr(this, get_app(p.first), get_app(p.second)); + } + m_find.merge(p.first, p.second); + } + m_new_eqs.reset(); + + if (m_new_diseq_fptr != 0) { + for (unsigned i = 0; i < m_new_diseqs.size(); i++) { + ++m_stats.m_num_diseq; + var_pair & p = m_new_diseqs[i]; + flet invoke(m_new_diseq_invoking, true); + m_new_diseq_fptr(this, get_app(p.first), get_app(p.second)); + } + } + m_new_diseqs.reset(); + + if (m_new_assignment_fptr != 0) { + context & ctx = get_context(); + for (unsigned i = 0; i < m_new_assignments.size(); i++) { + ++m_stats.m_num_assignment; + bool_var bv = m_new_assignments[i]; + lbool val = ctx.get_assignment(bv); + SASSERT(val != l_undef); + flet invoke(m_new_assignment_invoking, true); + m_new_assignment_fptr(this, to_app(ctx.bool_var2expr(bv)), val == l_true); + } + } + m_new_assignments.reset(); + + if (m_new_relevant_fptr != 0) { + for (unsigned i = 0; i < m_new_relevant_apps.size(); i++) { + flet invoke(m_new_relevant_invoking, true); + m_new_relevant_fptr(this, m_new_relevant_apps[i]); + } + } + m_new_relevant_apps.reset(); + + assert_axioms_into_context(old_sz); + old_sz = m_asserted_axioms.size(); + } + while (!m_new_eqs.empty() || !m_new_diseqs.empty() || !m_new_relevant_apps.empty() || !m_new_assignments.empty()); + } + + void user_theory::flush_eh() { + reset(false); + } + + void user_theory::reset_eh() { + reset(true); + } + + void user_theory::reset(bool full_reset) { + if (m_reset_fptr != 0) { + flet invoke(m_reset_invoking, true); + m_reset_fptr(this); + } + m_trail_stack.reset(); + reset_propagation_queues(); + m_asserted_axioms.reset(); + m_asserted_axiom_set.reset(); + shrink_use_list(0); + m_parents.reset(); + m_scopes.reset(); + m_persisted_axioms.reset(); + m_persisted_axioms_qhead = 0; + m_stats.reset(); + if (full_reset) + theory::reset_eh(); + } + + void user_theory::display_statistics(std::ostream & out) const { + print_stat(out, "num. user eqs: ", m_stats.m_num_eq); + print_stat(out, "num. user diseq: ", m_stats.m_num_diseq); + print_stat(out, "num. assignments: ", m_stats.m_num_assignment); + print_stat(out, "num. user axioms: ", m_stats.m_num_user_axioms); + } + + void user_theory::display_istatistics(std::ostream & out) const { + out << "NUM_USER_EQS " << m_stats.m_num_eq << "\n"; + out << "NUM_USER_DISEQ " << m_stats.m_num_diseq << "\n"; + out << "NUM_ASSIGNMENTS " << m_stats.m_num_assignment << "\n"; + out << "NUM_USER_AXIOMS " << m_stats.m_num_user_axioms << "\n"; + } + + class user_smt_model_value_proc : public model_value_proc { + func_decl_ref m_decl; + public: + user_smt_model_value_proc(ast_manager& m, func_decl* f) : m_decl(f, m) {} + + virtual app * mk_value(model_generator & mg, ptr_vector & values) { + ast_manager& m = mg.get_manager(); + return m.mk_app(m_decl, values.size(), values.c_ptr()); + } + }; + + bool user_theory::build_models() const { + return true; + } + + void user_theory::init_model(model_generator & m) { + m.register_factory(alloc(user_smt_theory_factory, get_manager(), get_id())); + } + + void user_theory::finalize_model(model_generator &) { + // No-op + } + + model_value_proc * user_theory::mk_value(enode * n, model_generator & mg) { + ast_manager& m = get_manager(); + func_decl* f = n->get_decl(); + if (m_decl_plugin->is_value(f)) { + return alloc(user_smt_model_value_proc, m, n->get_decl()); + } + else { + return mg.mk_model_value(n); + } + } + + bool user_theory::get_value(enode * n, expr_ref & r) { + return false; + } + + char const * user_theory::get_name() const { + return m_name.c_str(); + } + + void user_theory::display(std::ostream & out) const { + out << "Theory " << get_name() << ":\n"; + } + + user_theory * mk_user_theory(solver & _s, void * ext_context, void * ext_data, char const * name) { + context & ctx = _s.kernel(); // HACK + symbol _name(name); + ast_manager & m = ctx.get_manager(); + family_id fid = m.get_family_id(_name); + user_decl_plugin * dp = alloc(user_decl_plugin); + m.register_plugin(fid, dp); + simplifier & s = ctx.get_simplifier(); + user_simplifier_plugin * sp = alloc(user_simplifier_plugin, _name, m); + s.register_plugin(sp); + user_theory * th = alloc(user_theory, m, ctx.get_fparams(), ext_context, ext_data, name, fid, dp, sp); + ctx.register_plugin(th); + sp->set_owner(th); + return th; + } +}; + diff --git a/lib/user_smt_theory.h b/lib/user_smt_theory.h new file mode 100644 index 000000000..a27b3af32 --- /dev/null +++ b/lib/user_smt_theory.h @@ -0,0 +1,324 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + user_smt_theory.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2010-05-22. + +Revision History: + +--*/ +#ifndef _USER_SMT_THEORY_H_ +#define _USER_SMT_THEORY_H_ + +#include"user_decl_plugin.h" +#include"user_simplifier_plugin.h" +#include"smt_theory.h" +#include"union_find.h" +#include"smt_solver.h" + +namespace smt { + + class user_theory; + typedef int Z3_bool; + + typedef void (*theory_callback_fptr)(user_theory * th); + typedef Z3_bool (*theory_final_check_callback_fptr)(user_theory * th); + typedef void (*theory_app_callback_fptr)(user_theory * th, app *); + typedef void (*theory_app_bool_callback_fptr)(user_theory * th, app *, Z3_bool); + typedef void (*theory_app_app_callback_fptr)(user_theory * th, app *, app *); + typedef void * (*theory_mk_fresh_ext_data_fptr)(user_theory * th); + + class user_theory : public theory { + + typedef trail_stack th_trail_stack; + typedef union_find th_union_find; + typedef std::pair var_pair; + + front_end_params const& m_params; + void * m_ext_context; + void * m_ext_data; + std::string m_name; + bool m_simplify_axioms; + user_decl_plugin * m_decl_plugin; + user_simplifier_plugin * m_simplifier_plugin; + + th_union_find m_find; + th_trail_stack m_trail_stack; + + svector m_new_eqs; + svector m_new_diseqs; + svector m_new_assignments; + ptr_vector m_new_relevant_apps; + obj_hashtable m_asserted_axiom_set; + expr_ref_vector m_asserted_axioms; + ptr_vector m_parents; + ptr_vector > m_use_list; + + app_ref_vector m_persisted_axioms; + unsigned m_persisted_axioms_qhead; + + struct scope { + unsigned m_asserted_axioms_old_sz; + unsigned m_parents_old_sz; + }; + + svector m_scopes; + + theory_callback_fptr m_delete_fptr; + theory_app_callback_fptr m_new_app_fptr; + theory_app_callback_fptr m_new_elem_fptr; + theory_callback_fptr m_init_search_fptr; + theory_callback_fptr m_push_fptr; + theory_callback_fptr m_pop_fptr; + theory_callback_fptr m_restart_fptr; + theory_callback_fptr m_reset_fptr; + theory_final_check_callback_fptr m_final_check_fptr; + theory_app_app_callback_fptr m_new_eq_fptr; + theory_app_app_callback_fptr m_new_diseq_fptr; + theory_app_bool_callback_fptr m_new_assignment_fptr; + theory_app_callback_fptr m_new_relevant_fptr; + theory_mk_fresh_ext_data_fptr m_mk_fresh_fptr; + + + bool m_delete_invoking; + bool m_new_app_invoking; + bool m_new_elem_invoking; + bool m_init_search_invoking; + bool m_push_invoking; + bool m_pop_invoking; + bool m_restart_invoking; + bool m_reset_invoking; + bool m_final_check_invoking; + bool m_new_eq_invoking; + bool m_new_diseq_invoking; + bool m_new_assignment_invoking; + bool m_new_relevant_invoking; + + struct statistics { + unsigned m_num_eq; + unsigned m_num_diseq; + unsigned m_num_assignment; + unsigned m_num_user_axioms; + statistics() { reset(); } + void reset() { memset(this, 0, sizeof(*this)); } + }; + + statistics m_stats; + + protected: + virtual theory_var mk_var(enode * n); + + literal internalize_literal(expr * arg); + + void assert_axioms_into_context(unsigned old_sz); + + void assert_axiom_into_context(expr * axiom); + + void reset_propagation_queues(); + + void shrink_use_list(unsigned sz); + + ptr_vector * get_non_null_use_list(theory_var v); + + void mark_as_relevant(literal l); + + void assert_axiom_core(app* axiom); + + public: + user_theory(ast_manager & m, front_end_params const& p, void * ext_context, void * ext_data, char const * name, family_id fid, user_decl_plugin * dp, user_simplifier_plugin * sp); + virtual ~user_theory(); + + virtual theory * mk_fresh(context * new_ctx); + + void * get_ext_context() const { + return m_ext_context; + } + + void * get_ext_data() { + return m_ext_data; + } + + sort * mk_sort(symbol const & name) { + return m_decl_plugin->mk_sort(name); + } + + func_decl * mk_func_decl(symbol const & name, unsigned arity, sort * const * domain, sort * range) { + return m_decl_plugin->mk_func_decl(name, arity, domain, range); + } + + func_decl * mk_value_decl(symbol const & name, sort * s) { + return m_decl_plugin->mk_value_decl(name, s); + } + + void assert_axiom(ast * axiom); + + void assume_eq(ast * lhs, ast * rhs); + + void enable_axiom_simplification(bool flag) { + m_simplify_axioms = flag; + } + + void set_delete_fptr(theory_callback_fptr ptr) { + m_delete_fptr = ptr; + } + + void set_reduce_app_fptr(reduce_app_fptr ptr) { + m_simplifier_plugin->set_reduce_app_fptr(ptr); + } + + void set_reduce_eq_fptr(reduce_eq_fptr ptr) { + m_simplifier_plugin->set_reduce_eq_fptr(ptr); + } + + void set_reduce_distinct_fptr(reduce_distinct_fptr ptr) { + m_simplifier_plugin->set_reduce_distinct_fptr(ptr); + } + + void set_new_app_fptr(theory_app_callback_fptr ptr) { + m_new_app_fptr = ptr; + } + + void set_new_elem_fptr(theory_app_callback_fptr ptr) { + m_new_elem_fptr = ptr; + } + + void set_init_search_fptr(theory_callback_fptr ptr) { + m_init_search_fptr = ptr; + } + + void set_push_fptr(theory_callback_fptr ptr) { + m_push_fptr = ptr; + } + + void set_pop_fptr(theory_callback_fptr ptr) { + m_pop_fptr = ptr; + } + + void set_restart_fptr(theory_callback_fptr ptr) { + m_restart_fptr = ptr; + } + + void set_reset_fptr(theory_callback_fptr ptr) { + m_reset_fptr = ptr; + } + + void set_final_check_fptr(theory_final_check_callback_fptr ptr) { + m_final_check_fptr = ptr; + } + + void set_new_eq_fptr(theory_app_app_callback_fptr ptr) { + m_new_eq_fptr = ptr; + } + + void set_new_diseq_fptr(theory_app_app_callback_fptr ptr) { + m_new_diseq_fptr = ptr; + } + + void set_new_assignment_fptr(theory_app_bool_callback_fptr ptr) { + m_new_assignment_fptr = ptr; + } + + void set_new_relevant_fptr(theory_app_callback_fptr ptr) { + m_new_relevant_fptr = ptr; + } + + th_trail_stack & get_trail_stack() { return m_trail_stack; } + + theory_var get_var(ast * n) const; + + theory_var mk_var(ast * n); + + ast * get_root(ast * n) const; + + ast * get_next(ast * n) const; + + unsigned get_num_parents(ast * n) const; + + ast * get_parent(ast * n, unsigned i) const; + + unsigned get_num_parents() const { return m_parents.size(); } + + ast * get_parent(unsigned i) const { return m_parents[i]; } + + unsigned get_num_apps() const { return get_num_vars(); } + + app * get_app(unsigned i) const { return get_enode(i)->get_owner(); } + + unsigned get_num_asts() const { return get_num_apps(); } + + ast * get_ast(unsigned i) const { return get_app(i); } + + static void merge_eh(theory_var, theory_var, theory_var, theory_var) {} + + static void after_merge_eh(theory_var, theory_var, theory_var, theory_var) {} + + virtual void unmerge_eh(theory_var, theory_var) {} + + virtual bool internalize_atom(app * atom, bool gate_ctx); + + virtual bool internalize_term(app * term); + + virtual void apply_sort_cnstr(enode * n, sort * s); + + virtual void assign_eh(bool_var v, bool is_true); + + virtual void new_eq_eh(theory_var v1, theory_var v2); + + virtual void new_diseq_eh(theory_var v1, theory_var v2); + + virtual void relevant_eh(app * n); + + virtual void push_scope_eh(); + + virtual void pop_scope_eh(unsigned num_scopes); + + virtual void restart_eh(); + + virtual void init_search_eh(); + + virtual final_check_status final_check_eh(); + + virtual bool can_propagate(); + + virtual void propagate(); + + virtual void flush_eh(); + + virtual void reset_eh(); + + void reset(bool full_reset); + + virtual void display_statistics(std::ostream & out) const; + + virtual void display_istatistics(std::ostream & out) const; + + virtual bool build_models() const; + + virtual void init_model(model_generator & m); + + virtual void finalize_model(model_generator & m); + + virtual model_value_proc * mk_value(enode * n, model_generator & mg); + + virtual bool get_value(enode * n, expr_ref & r); + + virtual char const * get_name() const; + + virtual void display(std::ostream & out) const; + }; + + user_theory * mk_user_theory(solver & s, void * ext_context, void * ext_data, char const * name); +}; + + +#endif /* _USER_SMT_THEORY_H_ */ + diff --git a/lib/uses_theory.cpp b/lib/uses_theory.cpp new file mode 100644 index 000000000..5b736f75a --- /dev/null +++ b/lib/uses_theory.cpp @@ -0,0 +1,49 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + uses_theory.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-10-21. + +Revision History: + +--*/ + +#include"uses_theory.h" +#include"for_each_expr.h" + +bool uses_theory(expr * n, family_id fid) { + expr_mark visited; + return uses_theory(n, fid, visited); +} + +namespace uses_theory_ns { + struct found {}; + struct proc { + family_id m_fid; + proc(family_id fid):m_fid(fid) {} + void operator()(var * n) {} + void operator()(app * n) { if (n->get_family_id() == m_fid) throw found(); } + void operator()(quantifier * n) {} + }; +}; + +bool uses_theory(expr * n, family_id fid, expr_mark & visited) { + uses_theory_ns::proc p(fid); + try { + for_each_expr(p, visited, n); + } + catch (uses_theory_ns::found) { + return true; + } + return false; +} + diff --git a/lib/uses_theory.h b/lib/uses_theory.h new file mode 100644 index 000000000..a0640a5bb --- /dev/null +++ b/lib/uses_theory.h @@ -0,0 +1,37 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + uses_theory.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-10-21. + +Revision History: + +--*/ +#ifndef _USES_THEORY_H_ +#define _USES_THEORY_H_ + +#include"ast.h" + +/** + \brief Return true if the given expression contains a symbol of the given theory. +*/ +bool uses_theory(expr * n, family_id fid); + +/** + \brief Return true if the given expression contains a symbol of the given theory. + Only the expressions not marked as visited are checked. The set visited is updated + with the new checked expressions. +*/ +bool uses_theory(expr * n, family_id fid, expr_mark & visited); + +#endif /* _USES_THEORY_H_ */ + diff --git a/lib/util.cpp b/lib/util.cpp new file mode 100644 index 000000000..ee39db667 --- /dev/null +++ b/lib/util.cpp @@ -0,0 +1,179 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + util.cpp + +Abstract: + + Useful functions & macros + +Author: + + Leonardo de Moura (leonardo) 2006-10-10. + +Revision History: + +--*/ + +#include"util.h" +#include"ini_file.h" + +unsigned g_verbosity_level = 0; + +void set_verbosity_level(unsigned lvl) { + g_verbosity_level = lvl; +} + +unsigned get_verbosity_level() { + return g_verbosity_level; +} + +void register_verbosity_level(ini_params & p) { + p.register_unsigned_param("VERBOSE", g_verbosity_level, "be verbose, where the value is the verbosity level", true); +} + +static std::ostream* g_verbose_stream = &std::cerr; + +void set_verbose_stream(std::ostream& str) { + g_verbose_stream = &str; +} + +std::ostream& verbose_stream() { + return *g_verbose_stream; +} + + +void (*g_fatal_error_handler)(int) = 0; + +void fatal_error(int error_code) { + if (g_fatal_error_handler) { + g_fatal_error_handler(error_code); + } + else { + exit(error_code); + } +} + +void set_fatal_error_handler(void (*pfn)(int error_code)) { + g_fatal_error_handler = pfn; +} + +unsigned log2(unsigned v) { + unsigned r = 0; + if (v & 0xFFFF0000) { + v >>= 16; + r |= 16; + } + if (v & 0xFF00) { + v >>= 8; + r |= 8; + } + if (v & 0xF0) { + v >>= 4; + r |= 4; + } + if (v & 0xC) { + v >>= 2; + r |= 2; + } + if (v & 0x2) { + v >>= 1; + r |= 1; + } + return r; +} + +unsigned uint64_log2(uint64 v) { + unsigned r = 0; + if (v & 0xFFFFFFFF00000000ull) { + v >>= 32; + r |= 32; + } + if (v & 0xFFFF0000) { + v >>= 16; + r |= 16; + } + if (v & 0xFF00) { + v >>= 8; + r |= 8; + } + if (v & 0xF0) { + v >>= 4; + r |= 4; + } + if (v & 0xC) { + v >>= 2; + r |= 2; + } + if (v & 0x2) { + v >>= 1; + r |= 1; + } + return r; +} + +bool product_iterator_next(unsigned n, unsigned const * sz, unsigned * it) { + for (unsigned i = 0; i < n; i++) { + it[i]++; + if (it[i] < sz[i]) + return true; + it[i] = 0; + } + return false; +} + +char const * escaped::end() const { + if (m_str == 0) return 0; + char const * it = m_str; + char const * e = m_str; + while (*it) { + if (!m_trim_nl || *it != '\n') { + ++it; + e = it; + } + else { + ++it; + } + } + return e; +} + +void escaped::display(std::ostream & out) const { + char const * it = m_str; + char const * e = end(); + for (; it != e; ++it) { + char c = *it; + if (c == '"') { + out << '\\'; + } + out << c; + if (c == '\n') { + for (unsigned i = 0; i < m_indent; i++) + out << " "; + } + } +} + +#ifdef _WINDOWS +#ifdef ARRAYSIZE +#undef ARRAYSIZE +#endif +#include "windows.h" +#endif + +void z3_bound_num_procs() { + +#ifdef _Z3_COMMERCIAL +#define Z3_COMMERCIAL_MAX_CORES 4 +#ifdef _WINDOWS + DWORD_PTR numProcs = (1 << Z3_COMMERCIAL_MAX_CORES) - 1; + SetProcessAffinityMask(GetCurrentProcess(), numProcs); +#endif +#else + // Not bounded: Research evaluations are + // not reasonable if run with artificial + // or hidden throttles. +#endif +} diff --git a/lib/util.h b/lib/util.h new file mode 100644 index 000000000..5e51d8ef1 --- /dev/null +++ b/lib/util.h @@ -0,0 +1,405 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + util.h + +Abstract: + + Useful functions & macros + +Author: + + Leonardo de Moura (leonardo) 2006-09-11. + +Revision History: + +--*/ +#ifndef _UTIL_H_ +#define _UTIL_H_ + +#include"debug.h" +#include"memory_manager.h" +#include +#include +#include + +#ifndef SIZE_MAX +#define SIZE_MAX std::numeric_limits::max() +#endif + +#ifndef uint64 +typedef unsigned long long uint64; +#endif + +COMPILE_TIME_ASSERT(sizeof(uint64) == 8); + +#ifndef int64 +typedef long long int64; +#endif + +COMPILE_TIME_ASSERT(sizeof(int64) == 8); + +#ifndef INT64_MIN +#define INT64_MIN static_cast(0x8000000000000000ull) +#endif +#ifndef INT64_MAX +#define INT64_MAX static_cast(0x0fffffffffffffffull) +#endif +#ifndef UINT64_MAX +#define UINT64_MAX 0xffffffffffffffffull +#endif + +#ifdef _WINDOWS +#define SSCANF sscanf_s +#define SPRINTF sprintf_s +#else +#define SSCANF sscanf +#define SPRINTF sprintf +#endif + + +#ifdef _WINDOWS +// Disable thread local declspec as it seems to not work downlevel. +// #define THREAD_LOCAL __declspec(thread) +#define THREAD_LOCAL +#else +#define THREAD_LOCAL +#endif + +inline bool is_power_of_two(unsigned v) { return !(v & (v - 1)) && v; } + +/** + \brief Return the next power of two that is greater than or equal to v. + + \warning This function returns 0 for v == 0. +*/ +inline unsigned next_power_of_two(unsigned v) { + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v++; + return v; +} + +/** + \brief Return the position of the most significant bit. +*/ +unsigned log2(unsigned v); +unsigned uint64_log2(uint64 v); + +COMPILE_TIME_ASSERT(sizeof(unsigned) == 4); + +// Return the number of 1 bits in v. +inline unsigned get_num_1bits(unsigned v) { +#ifdef Z3DEBUG + unsigned c; + unsigned v1 = v; + for (c = 0; v1; c++) { + v1 &= v1 - 1; + } +#endif + v = v - ((v >> 1) & 0x55555555); + v = (v & 0x33333333) + ((v >> 2) & 0x33333333); + unsigned r = (((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24; + SASSERT(c == r); + return r; +} + +// Remark: on gcc, the operators << and >> do not produce zero when the second argument >= 64. +// So, I'm using the following two definitions to fix the problem +inline uint64 shift_right(uint64 x, uint64 y) { + return y < 64ull ? (x >> y) : 0ull; +} + +inline uint64 shift_left(uint64 x, uint64 y) { + return y < 64ull ? (x << y) : 0ull; +} + +template char (*ArraySizer(T (&)[N]))[N]; +// For determining the length of an array. See ARRAYSIZE() macro. This function is never actually called. + +#ifndef ARRAYSIZE +#define ARRAYSIZE(a) sizeof(*ArraySizer(a)) +#endif + +template +void display(std::ostream & out, const IT & begin, const IT & end, const char * sep, bool & first) { + for(IT it = begin; it != end; ++it) { + if (first) { + first = false; + } + else { + out << sep; + } + out << *it; + } +} + +template +void display(std::ostream & out, const IT & begin, const IT & end, const char * sep = " ") { + bool first = true; + display(out, begin, end, sep, first); +} + +template +struct delete_proc { + void operator()(T * ptr) { + if (ptr) { + dealloc(ptr); + } + } +}; + +void set_verbosity_level(unsigned lvl); +unsigned get_verbosity_level(); +class ini_params; +void register_verbosity_level(ini_params & p); +std::ostream& verbose_stream(); +void set_verbose_stream(std::ostream& str); + +#define IF_VERBOSE(LVL, CODE) { if (get_verbosity_level() >= LVL) { CODE } } ((void) 0) + +#ifdef _EXTERNAL_RELEASE +#define IF_IVERBOSE(LVL, CODE) ((void) 0) +#else +#define IF_IVERBOSE(LVL, CODE) { if (get_verbosity_level() >= LVL) { CODE } } ((void) 0) +#endif + + + +template +struct default_eq { + typedef T data; + bool operator()(const T & e1, const T & e2) const { + return e1 == e2; + } +}; + +template +struct ptr_eq { + typedef T * data; + bool operator()(T * a1, T * a2) const { + return a1 == a2; + } +}; + +template +struct deref_eq { + typedef T * data; + bool operator()(T * a1, T * a2) const { + return *a1 == *a2; + } +}; + +template +class scoped_ptr { + T * m_ptr; +public: + scoped_ptr(T * ptr=0): + m_ptr(ptr) { + } + + ~scoped_ptr() { + if (m_ptr) { + dealloc(m_ptr); + } + } + + T * operator->() const { + return m_ptr; + } + + T * get() const { + return m_ptr; + } + + operator bool() const { + return m_ptr != 0; + } + + const T & operator*() const { + return *m_ptr; + } + + T & operator*() { + return *m_ptr; + } + + scoped_ptr & operator=(T * n) { + if (m_ptr != n) { + if (m_ptr) { + dealloc(m_ptr); + } + m_ptr = n; + } + return *this; + } + + T * detach() { + T* tmp = m_ptr; + m_ptr = 0; + return tmp; + } +}; + +template +inline std::ostream & operator<<(std::ostream & out, std::pair const & p) { + out << "(" << p.first << ", " << p.second << ")"; + return out; +} + +#ifndef _WINDOWS +#define __forceinline inline +#endif + +template +bool has_duplicates(const IT & begin, const IT & end) { + for (IT it1 = begin; it1 != end; ++it1) { + IT it2 = it1; + ++it2; + for (; it2 != end; ++it2) { + if (*it1 == *it2) { + return true; + } + } + } + return false; +} + +#ifndef __out +#define __out +#endif + +#ifndef __in +#define __in +#endif + +#ifndef __inout +#define __inout +#endif + +#ifndef __fallthrough +#define __fallthrough +#endif + +#ifndef _WINDOWS +#ifndef __declspec +#define __declspec(X) +#endif +#endif + +template +class flet { + T & m_ref; + T m_old_value; +public: + flet(T & ref, const T & new_value): + m_ref(ref), + m_old_value(ref) { + m_ref = new_value; + } + ~flet() { + m_ref = m_old_value; + } +}; + +template +bool compare_arrays(const T * array1, const T * array2, unsigned size) { + for (unsigned i = 0; i < size; i++) { + if (!(array1[i] == array2[i])) { + return false; + } + } + return true; +} + +template +void force_ptr_array_size(T & v, unsigned sz) { + if (sz > v.size()) { + v.resize(sz, 0); + } +} + +class random_gen { + unsigned m_data; +public: + random_gen(unsigned seed = 0): + m_data(seed) { + } + + void set_seed(unsigned s) { m_data = s; } + + int operator()() { + return ((m_data = m_data * 214013L + 2531011L) >> 16) & 0x7fff; + } + + unsigned operator()(unsigned u) { + unsigned r = static_cast((*this)()); + return r % u; + } + + static int max_value() { + return 0x7fff; + } +}; + +template +void shuffle(unsigned sz, T * array, random_gen & gen) { + int n = sz; + while (--n > 0) { + int k = gen() % (n + 1); + std::swap(array[n], array[k]); + } +} + +#ifdef _EXTERNAL_RELEASE +#define INTERNAL_CODE(CODE) ((void) 0) +#else +#define INTERNAL_CODE(CODE) { CODE } ((void) 0) +#endif + +void fatal_error(int error_code); + +void set_fatal_error_handler(void (*pfn)(int error_code)); + + +/** + \brief Iterator for the [0..sz[0]) X [0..sz[1]) X ... X [0..sz[n-1]). + it contains the current value. + Return true if there is a next element, and store the next element in it. +*/ +bool product_iterator_next(unsigned n, unsigned const * sz, unsigned * it); + +/** + \brief Macro for avoiding error messages. +*/ +#define TRUSTME(cond) if (!cond) { UNREACHABLE(); fatal_error(0); exit(0); } + +class escaped { + char const * m_str; + bool m_trim_nl; // if true -> eliminate '\n' in the end of m_str. + unsigned m_indent; + char const * end() const; +public: + escaped(char const * str, bool trim_nl = false, unsigned indent = 0):m_str(str), m_trim_nl(trim_nl), m_indent(indent) {} + void display(std::ostream & out) const; +}; + +inline std::ostream & operator<<(std::ostream & out, escaped const & s) { s.display(out); return out; } + +inline unsigned long long megabytes_to_bytes(unsigned b) { + if (b == UINT_MAX) + return UINT64_MAX; + else + return static_cast(b) * 1024ull * 1024ull; +} + +void z3_bound_num_procs(); + +#endif /* _UTIL_H_ */ + diff --git a/lib/value.cpp b/lib/value.cpp new file mode 100644 index 000000000..b81bbbb0b --- /dev/null +++ b/lib/value.cpp @@ -0,0 +1,42 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + value.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-08-17. + +Revision History: + +--*/ +#include"value.h" + +void bool_value::display(std::ostream & out) const { + out << (m_value ? "true" : "false"); +} + +unsigned bool_value::hash() const { + return m_value ? 1 : 0; +} + +bool bool_value::operator==(const value & other) const { + const bool_value * o = dynamic_cast(&other); + return o && m_value == o->m_value; +} + +basic_factory::basic_factory(ast_manager & m): + value_factory(symbol("basic"), m), + m_bool(m) { + m_bool = m.mk_type(m.get_basic_family_id(), BOOL_SORT); + m_true = alloc(bool_value, true, m_bool.get()); + m_false = alloc(bool_value, false, m_bool.get()); +} + + diff --git a/lib/value.h b/lib/value.h new file mode 100644 index 000000000..2055449b8 --- /dev/null +++ b/lib/value.h @@ -0,0 +1,162 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + value.h + +Abstract: + + Abstract class used to represent values in a model. + +Author: + + Leonardo de Moura (leonardo) 2007-08-14. + +Revision History: + +--*/ +#ifndef _VALUE_H_ +#define _VALUE_H_ + +#include"core_model.h" +#include"ast.h" +#include"ref.h" + +class model; + +class value { + partition_id m_partition_id; + unsigned m_ref_count; + type_ast * m_type; + + friend class model; + + void set_partition_id(partition_id id) { + m_partition_id = id; + } + +public: + value(type_ast * ty): + m_partition_id(null_partition_id), + m_ref_count(0), + m_type(ty) { + } + + virtual ~value() {} + + void inc_ref() { m_ref_count ++; } + + void dec_ref() { + SASSERT(m_ref_count > 0); + m_ref_count--; + if (m_ref_count == 0) { + dealloc(this); + } + } + + partition_id get_partition_id() { return m_partition_id; } + + type_ast * get_type() const { return m_type; } + + virtual void display(std::ostream & out) const = 0; + + virtual unsigned hash() const = 0; + + virtual bool operator==(const value & other) const = 0; + + virtual void infer_types(ast_vector & result) { /* default: do nothing */ } + + virtual void collect_used_partitions(svector & result) { /* default: do nothing */ } +}; + +inline std::ostream & operator<<(std::ostream & target, const value & v) { + v.display(target); + return target; +} + +class value_factory { + family_id m_fid; +public: + value_factory(symbol fname, ast_manager & m): + m_fid(m.get_family_id(fname)) { + } + + virtual ~value_factory() {} + + // Return some value of the given type + virtual value * get_some_value(type_ast * ty) = 0; + + // Return two distinct values of the given type + virtual bool get_some_values(type_ast * ty, ref & v1, ref & v2) = 0; + + // Return a fresh value of the given type + virtual value * get_fresh_value(type_ast * ty) = 0; + + virtual value * update_value(value * source, const svector & pid2pid) { + return source; + } + + family_id get_family_id() const { return m_fid; } +}; + +class bool_value : public value { + friend class basic_factory; + bool m_value; + + bool_value(bool v, type_ast * ty): + value(ty), + m_value(v) { + } + +public: + + bool get_value() const { + return m_value; + } + + virtual void display(std::ostream & out) const; + + virtual unsigned hash() const; + + virtual bool operator==(const value & other) const; +}; + +class basic_factory : public value_factory { + ast_ref m_bool; + ref m_true; + ref m_false; +public: + basic_factory(ast_manager & m); + + virtual ~basic_factory() {} + + bool_value * get_true() const { + return m_true.get(); + } + + bool_value * get_false() const { + return m_false.get(); + } + + // Return some value of the given type + virtual value * get_some_value(type_ast * ty) { + return get_false(); + } + + // Return two distinct values of the given type + virtual bool get_some_values(type_ast * ty, ref & v1, ref & v2) { + v1 = get_false(); + v2 = get_true(); + return true; + } + + // Return a fresh value of the given type + virtual value * get_fresh_value(type_ast * ty) { + // it is not possible to create new fresh values... + return 0; + } +}; + +#endif /* _VALUE_H_ */ + diff --git a/lib/value_compiler_extension.h b/lib/value_compiler_extension.h new file mode 100644 index 000000000..a83e7e0a3 --- /dev/null +++ b/lib/value_compiler_extension.h @@ -0,0 +1,46 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + value_compiler_extension.h + +Abstract: + + Compiler extension for creating values (i.e., "interpreted" constants that + are different any other constant). + +Author: + + Leonardo de Moura (leonardo) 2006-10-31. + +Revision History: + +--*/ +#ifndef _VALUE_COMPILER_EXTENSION_H_ +#define _VALUE_COMPILER_EXTENSION_H_ + +#include"ast_compiler.h" + +class value_compiler_extension : public ast_compiler_plugin { + context & m_context; +public: + value_compiler_extension(ast_manager & m, context & ctx): + ast_compiler_plugin(m.get_family_id(symbol("interpreted_value"))), + m_context(ctx) { + ctx.register_plugin(this); + } + + virtual ~value_compiler_extension() { + } + + virtual bool compile_term(ast_compiler & c, const_ast * a, enode * & r) { + SASSERT(a->get_decl()->get_family_id() == m_fid); + const_decl_ast * d = a->get_decl(); + r = m_context.mk_interpreted_const(d); + return true; + } +}; + +#endif /* _VALUE_COMPILER_EXTENSION_H_ */ + diff --git a/lib/value_factory.cpp b/lib/value_factory.cpp new file mode 100644 index 000000000..0b2aded9e --- /dev/null +++ b/lib/value_factory.cpp @@ -0,0 +1,117 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + value_factory.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-10-28. + +Revision History: + +--*/ + +#include"value_factory.h" + +value_factory::value_factory(ast_manager & m, family_id fid): + m_manager(m), + m_fid(fid) { +} + +value_factory::~value_factory() { +} + +basic_factory::basic_factory(ast_manager & m): + value_factory(m, m.get_basic_family_id()) { +} + +expr * basic_factory::get_some_value(sort * s) { + if (m_manager.is_bool(s)) + return m_manager.mk_false(); + return 0; +} + +bool basic_factory::get_some_values(sort * s, expr_ref & v1, expr_ref & v2) { + if (m_manager.is_bool(s)) { + v1 = m_manager.mk_false(); + v2 = m_manager.mk_true(); + return true; + } + return false; +} + +expr * basic_factory::get_fresh_value(sort * s) { + return 0; +} + +user_sort_factory::user_sort_factory(ast_manager & m): + simple_factory(m, m.get_family_id("user-sort")) { +} + +void user_sort_factory::freeze_universe(sort * s) { + if (!m_finite.contains(s)) { + value_set * set = 0; + m_sort2value_set.find(s, set); + if (!m_sort2value_set.find(s, set) || set->m_values.empty()) { + // we cannot freeze an empty universe. + get_some_value(s); // add one element to the universe... + } + SASSERT(m_sort2value_set.find(s, set) && set != 0 && !set->m_values.empty()); + m_finite.insert(s); + } +} + +obj_hashtable const & user_sort_factory::get_known_universe(sort * s) const { + value_set * set = 0; + if (m_sort2value_set.find(s, set)) { + return set->m_values; + } + return m_empty_universe; +} + +expr * user_sort_factory::get_some_value(sort * s) { + if (is_finite(s)) { + value_set * set = 0; + m_sort2value_set.find(s, set); + SASSERT(set != 0); + SASSERT(!set->m_values.empty()); + return *(set->m_values.begin()); + } + return simple_factory::get_some_value(s); +} + +bool user_sort_factory::get_some_values(sort * s, expr_ref & v1, expr_ref & v2) { + if (is_finite(s)) { + value_set * set = 0; + if (m_sort2value_set.find(s, set) && set->m_values.size() >= 2) { + obj_hashtable::iterator it = set->m_values.begin(); + v1 = *it; + ++it; + v2 = *it; + return true; + } + return false; + } + return simple_factory::get_some_values(s, v1, v2); +} + +expr * user_sort_factory::get_fresh_value(sort * s) { + if (is_finite(s)) + return 0; + return simple_factory::get_fresh_value(s); +} + +void user_sort_factory::register_value(expr * n) { + SASSERT(!is_finite(m_manager.get_sort(n))); + simple_factory::register_value(n); +} + +app * user_sort_factory::mk_value_core(unsigned const & val, sort * s) { + return m_manager.mk_model_value(val, s); +} diff --git a/lib/value_factory.h b/lib/value_factory.h new file mode 100644 index 000000000..b7df55f43 --- /dev/null +++ b/lib/value_factory.h @@ -0,0 +1,257 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + value_factory.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-10-28. + +Revision History: + +--*/ +#ifndef _VALUE_FACTORY_H_ +#define _VALUE_FACTORY_H_ + +#include"ast.h" +#include"obj_hashtable.h" + +/** + \brief Auxiliary object used during model construction. +*/ +class value_factory { +protected: + ast_manager & m_manager; + family_id m_fid; +public: + value_factory(ast_manager & m, family_id fid); + + virtual ~value_factory(); + + /** + \brief Return some value of the given sort. The result is always different from zero. + */ + virtual expr * get_some_value(sort * s) = 0; + + /** + \brief Return two distinct values of the given sort. The results are stored in v1 and v2. + Return false if the intended interpretation of the given sort has only one element. + */ + virtual bool get_some_values(sort * s, expr_ref & v1, expr_ref & v2) = 0; + + /** + \brief Return a fresh value of the given sort. + Return 0 if it is not possible to do that (e.g., the sort is finite). + */ + virtual expr * get_fresh_value(sort * s) = 0; + + /** + \brief Used to register that the given value was used in model construction. + So, get_fresh_value cannot return this value anymore. + */ + virtual void register_value(expr * n) = 0; + + family_id get_family_id() const { return m_fid; } +}; + +class basic_factory : public value_factory { +public: + basic_factory(ast_manager & m); + + virtual expr * get_some_value(sort * s); + + virtual bool get_some_values(sort * s, expr_ref & v1, expr_ref & v2); + + virtual expr * get_fresh_value(sort * s); + + virtual void register_value(expr * n) { } +}; + +/** + \brief Template for value factories for numeric (and enumeration-like) sorts +*/ +template +class simple_factory : public value_factory { +protected: + struct value_set { + obj_hashtable m_values; + Number m_next; + value_set(): + m_next(0) { + } + }; + + typedef obj_map sort2value_set; + + sort2value_set m_sort2value_set; + expr_ref_vector m_values; + sort_ref_vector m_sorts; + ptr_vector m_sets; + + value_set * get_value_set(sort * s) { + value_set * set = 0; + if (!m_sort2value_set.find(s, set)) { + set = alloc(value_set); + m_sort2value_set.insert(s, set); + m_sorts.push_back(s); + m_sets.push_back(set); + } + SASSERT(set != 0); + DEBUG_CODE({ + value_set * set2 = 0; + SASSERT(m_sort2value_set.find(s, set2)); + SASSERT(set == set2); + }); + return set; + } + + virtual app * mk_value_core(Number const & val, sort * s) = 0; + + app * mk_value(Number const & val, sort * s, bool & is_new) { + value_set * set = get_value_set(s); + app * new_val = mk_value_core(val, s); + is_new = false; + if (!set->m_values.contains(new_val)) { + m_values.push_back(new_val); + set->m_values.insert(new_val); + is_new = true; + } + SASSERT(set->m_values.contains(new_val)); + return new_val; + } + +public: + simple_factory(ast_manager & m, family_id fid): + value_factory(m, fid), + m_values(m), + m_sorts(m) { + } + + virtual ~simple_factory() { + std::for_each(m_sets.begin(), m_sets.end(), delete_proc()); + } + + virtual expr * get_some_value(sort * s) { + value_set * set = 0; + expr * result = 0; + if (m_sort2value_set.find(s, set) && !set->m_values.empty()) + result = *(set->m_values.begin()); + else + result = mk_value(Number(0), s); + return result; + } + + virtual bool get_some_values(sort * s, expr_ref & v1, expr_ref & v2) { + value_set * set = 0; + if (m_sort2value_set.find(s, set)) { + switch (set->m_values.size()) { + case 0: + v1 = mk_value(Number(0), s); + v2 = mk_value(Number(1), s); + break; + case 1: + v1 = *(set->m_values.begin()); + v2 = mk_value(Number(0), s); + if (v1 == v2) + v2 = mk_value(Number(1), s); + break; + default: + obj_hashtable::iterator it = set->m_values.begin(); + v1 = *it; + ++it; + v2 = *it; + break; + } + SASSERT(v1 != v2); + return true; + } + v1 = mk_value(Number(0), s); + v2 = mk_value(Number(1), s); + return true; + } + + virtual expr * get_fresh_value(sort * s) { + value_set * set = get_value_set(s); + bool is_new = false; + expr * result = 0; + Number & next = set->m_next; + while (!is_new) { + result = mk_value(next, s, is_new); + next++; + } + SASSERT(result != 0); + return result; + } + + virtual void register_value(expr * n) { + sort * s = this->m_manager.get_sort(n); + value_set * set = get_value_set(s); + if (!set->m_values.contains(n)) { + m_values.push_back(n); + set->m_values.insert(n); + } + } + + virtual app * mk_value(Number const & val, sort * s) { + bool is_new; + return mk_value(val, s, is_new); + } + + unsigned get_num_sorts() const { return m_sorts.size(); } + + sort * get_sort(unsigned idx) const { return m_sorts.get(idx); } +}; + +/** + \brief Factory for creating values for uninterpreted sorts and user + declared (uninterpreted) sorts. +*/ +class user_sort_factory : public simple_factory { + obj_hashtable m_finite; //!< set of sorts that are marked as finite. + obj_hashtable m_empty_universe; + virtual app * mk_value_core(unsigned const & val, sort * s); +public: + user_sort_factory(ast_manager & m); + virtual ~user_sort_factory() {} + + /** + \brief Make the universe of \c s finite, by preventing new + elements to be added to its universe. + */ + void freeze_universe(sort * s); + + /** + \brief Return true if the universe of \c s is frozen and finite. + */ + bool is_finite(sort * s) const { + return m_finite.contains(s); + } + + /** + \brief Return the "known" universe of \c s. It doesn't matter whether + s is finite or not. If \c s is finite, then it is the whole universe. + */ + obj_hashtable const & get_known_universe(sort * s) const; + + /** + \brief Return sorts with finite interpretations. + */ + obj_hashtable const & get_finite_sorts() const { return m_finite; } + + virtual expr * get_some_value(sort * s); + + virtual bool get_some_values(sort * s, expr_ref & v1, expr_ref & v2); + + virtual expr * get_fresh_value(sort * s); + + virtual void register_value(expr * n); +}; + +#endif /* _VALUE_FACTORY_H_ */ + diff --git a/lib/var_offset_map.h b/lib/var_offset_map.h new file mode 100644 index 000000000..85120e614 --- /dev/null +++ b/lib/var_offset_map.h @@ -0,0 +1,114 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + var_offset_map.h + +Abstract: + + A generic mapping from (var, offset) to a value T. + +Author: + + Leonardo de Moura (leonardo) 2008-02-01. + +Revision History: + +--*/ +#ifndef _VAR_OFFSET_MAP_H_ +#define _VAR_OFFSET_MAP_H_ + +#include"ast.h" +#include"vector.h" + +/** + \brief A mapping from variable-id + offset to some value of type T. +*/ +template +class var_offset_map { +protected: + struct data { + T m_data; + unsigned m_timestamp; + data():m_timestamp(0) {} + }; + + svector m_map; + unsigned m_num_offsets; + unsigned m_num_vars; + unsigned m_timestamp; + +public: + var_offset_map(): + m_num_offsets(0), + m_num_vars(0), + m_timestamp(1) { + } + + void reset() { + m_timestamp++; + if (m_timestamp == UINT_MAX) { + typename svector::iterator it = m_map.begin(); + typename svector::iterator end = m_map.end(); + for (; it != end; ++it) + it->m_timestamp = 0; + m_timestamp = 1; + } + } + + unsigned offsets_capacity() const { return m_num_offsets; } + + unsigned vars_capacity() const { return m_num_vars; } + + void reserve(unsigned num_offsets, unsigned num_vars) { + if (num_offsets > m_num_offsets || num_vars > m_num_vars) { + unsigned sz = num_offsets * num_vars; + m_map.resize(sz); + m_num_vars = num_vars; + m_num_offsets = num_offsets; + } + reset(); + } + + void reserve_offsets(unsigned num_offsets) { reserve(num_offsets, m_num_vars); } + + void reserve_vars(unsigned num_vars) { reserve(m_num_offsets, num_vars); } + + void insert(unsigned v_idx, unsigned offset, T const & t) { + SASSERT(v_idx < m_num_vars); + SASSERT(offset < m_num_offsets); + unsigned idx = v_idx + offset * m_num_vars; + SASSERT(idx < m_map.size()); + data & d = m_map[idx]; + d.m_data = t; + d.m_timestamp = m_timestamp; + } + + void insert(var * v, unsigned offset, T const & t) { insert(v->get_idx(), offset, t); } + + bool find(unsigned v_idx, unsigned offset, T & r) const { + SASSERT(v_idx < m_num_vars); + SASSERT(offset < m_num_offsets); + unsigned idx = v_idx + offset * m_num_vars; + data const & d = m_map[idx]; + SASSERT(d.m_timestamp <= m_timestamp); + if (d.m_timestamp == m_timestamp) { + r = d.m_data; + return true; + } + return false; + } + + bool find(var * v, unsigned offset, T & r) const { return find(v->get_idx(), offset, r); } + + void erase(unsigned v_idx, unsigned offset) { + SASSERT(v_idx < m_num_vars); + SASSERT(offset < m_num_offsets); + unsigned idx = v_idx + offset * m_num_vars; + m_map[idx].m_timestamp = 0; + } + +}; + +#endif /* _VAR_OFFSET_MAP_H_ */ diff --git a/lib/var_subst.cpp b/lib/var_subst.cpp new file mode 100644 index 000000000..3ebc0b573 --- /dev/null +++ b/lib/var_subst.cpp @@ -0,0 +1,211 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + var_subst.cpp + +Abstract: + + Variable substitution. + +Author: + + Leonardo (leonardo) 2008-01-10 + +Notes: + +--*/ +#include"var_subst.h" +#include"used_vars.h" +#include"ast_ll_pp.h" +#include"ast_pp.h" +#include"ast_smt2_pp.h" +#include"well_sorted.h" +#include"for_each_expr.h" + +void var_subst::operator()(expr * n, unsigned num_args, expr * const * args, expr_ref & result) { + SASSERT(is_well_sorted(result.m(), n)); + m_reducer.reset(); + if (m_std_order) + m_reducer.set_inv_bindings(num_args, args); + else + m_reducer.set_bindings(num_args, args); + m_reducer(n, result); + SASSERT(is_well_sorted(m_reducer.m(), result)); + TRACE("var_subst_bug", + tout << "m_std_order: " << m_std_order << "\n" << mk_ismt2_pp(n, m_reducer.m()) << "\nusing\n"; + for (unsigned i = 0; i < num_args; i++) tout << mk_ismt2_pp(args[i], m_reducer.m()) << "\n"; + tout << "\n------>\n"; + tout << mk_ismt2_pp(result, m_reducer.m()) << "\n";); +} + +void elim_unused_vars(ast_manager & m, quantifier * q, expr_ref & result) { + SASSERT(is_well_sorted(m, q)); + if (is_ground(q->get_expr())) { + // ignore patterns if the body is a ground formula. + result = q->get_expr(); + return; + } + if (!q->may_have_unused_vars()) { + result = q; + return; + } + used_vars used; + used.process(q->get_expr()); + unsigned num_patterns = q->get_num_patterns(); + for (unsigned i = 0; i < num_patterns; i++) + used.process(q->get_pattern(i)); + unsigned num_no_patterns = q->get_num_no_patterns(); + for (unsigned i = 0; i < num_no_patterns; i++) + used.process(q->get_no_pattern(i)); + + unsigned num_decls = q->get_num_decls(); + if (used.uses_all_vars(num_decls)) { + q->set_no_unused_vars(); + result = q; + return; + } + + ptr_buffer used_decl_sorts; + buffer used_decl_names; + for (unsigned i = 0; i < num_decls; ++i) { + if (used.contains(num_decls - i - 1)) { + used_decl_sorts.push_back(q->get_decl_sort(i)); + used_decl_names.push_back(q->get_decl_name(i)); + } + } + + unsigned num_removed = 0; + expr_ref_buffer var_mapping(m); + int next_idx = 0; + unsigned sz = used.get_max_found_var_idx_plus_1(); + + for (unsigned i = 0; i < num_decls; ++i) { + sort * s = used.contains(i); + if (s) { + var_mapping.push_back(m.mk_var(next_idx, s)); + next_idx++; + } + else { + num_removed++; + var_mapping.push_back(0); + } + } + // (VAR 0) is in the first position of var_mapping. + + for (unsigned i = num_decls; i < sz; i++) { + sort * s = used.contains(i); + if (s) + var_mapping.push_back(m.mk_var(i - num_removed, s)); + else + var_mapping.push_back(0); + } + + + // Remark: + // (VAR 0) should be in the last position of var_mapping. + // ... + // (VAR (var_mapping.size() - 1)) should be in the first position. + std::reverse(var_mapping.c_ptr(), var_mapping.c_ptr() + var_mapping.size()); + + expr_ref new_expr(m); + var_subst subst(m); + + subst(q->get_expr(), var_mapping.size(), var_mapping.c_ptr(), new_expr); + + if (num_removed == num_decls) { + result = new_expr; + return; + } + + expr_ref tmp(m); + expr_ref_buffer new_patterns(m); + expr_ref_buffer new_no_patterns(m); + + for (unsigned i = 0; i < num_patterns; i++) { + subst(q->get_pattern(i), var_mapping.size(), var_mapping.c_ptr(), tmp); + new_patterns.push_back(tmp); + } + for (unsigned i = 0; i < num_no_patterns; i++) { + subst(q->get_no_pattern(i), var_mapping.size(), var_mapping.c_ptr(), tmp); + new_no_patterns.push_back(tmp); + } + + result = m.mk_quantifier(q->is_forall(), + used_decl_sorts.size(), + used_decl_sorts.c_ptr(), + used_decl_names.c_ptr(), + new_expr, + q->get_weight(), + q->get_qid(), + q->get_skid(), + num_patterns, + new_patterns.c_ptr(), + num_no_patterns, + new_no_patterns.c_ptr()); + to_quantifier(result)->set_no_unused_vars(); + SASSERT(is_well_sorted(m, result)); +} + +void instantiate(ast_manager & m, quantifier * q, expr * const * exprs, expr_ref & result) { + var_subst subst(m); + expr_ref new_expr(m); + subst(q->get_expr(), q->get_num_decls(), exprs, new_expr); + TRACE("var_subst", tout << mk_pp(q, m) << "\n" << mk_pp(new_expr, m) << "\n";); + inv_var_shifter shift(m); + shift(new_expr, q->get_num_decls(), result); + SASSERT(is_well_sorted(m, result)); + TRACE("instantiate_bug", tout << mk_ismt2_pp(q, m) << "\nusing\n"; + for (unsigned i = 0; i < q->get_num_decls(); i++) tout << mk_ismt2_pp(exprs[i], m) << "\n"; + tout << "\n----->\n" << mk_ismt2_pp(result, m) << "\n";); +} + +static void get_free_vars_offset(expr* e, unsigned offset, ptr_vector& sorts) { + ast_mark mark; + ptr_vector todo; + todo.push_back(e); + while (!todo.empty()) { + e = todo.back(); + todo.pop_back(); + if (mark.is_marked(e)) { + continue; + } + mark.mark(e, true); + switch(e->get_kind()) { + case AST_QUANTIFIER: { + quantifier* q = to_quantifier(e); + get_free_vars_offset(q->get_expr(), offset+q->get_num_decls(), sorts); + break; + } + case AST_VAR: { + var* v = to_var(e); + if (v->get_idx() >= offset) { + unsigned idx = v->get_idx()-offset; + if (sorts.size() <= idx) { + sorts.resize(idx+1); + } + if (!sorts[idx]) { + sorts[idx] = v->get_sort(); + } + SASSERT(sorts[idx] == v->get_sort()); + } + break; + } + case AST_APP: { + app* a = to_app(e); + for (unsigned i = 0; i < a->get_num_args(); ++i) { + todo.push_back(a->get_arg(i)); + } + break; + } + default: + UNREACHABLE(); + } + } +} + + +void get_free_vars(expr* e, ptr_vector& sorts) { + get_free_vars_offset(e, 0, sorts); +} diff --git a/lib/var_subst.h b/lib/var_subst.h new file mode 100644 index 000000000..9f3c13c0c --- /dev/null +++ b/lib/var_subst.h @@ -0,0 +1,78 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + var_subst.h + +Abstract: + + Variable substitution. + +Author: + + Leonardo (leonardo) 2008-01-10 + +Notes: + +--*/ +#ifndef _VAR_SUBST_H_ +#define _VAR_SUBST_H_ + +#include"rewriter.h" + +/** + \brief Alias for var_shifter class. +*/ +typedef var_shifter shift_vars; + +/** + \brief Variable substitution functor. It substitutes variables by expressions. + The expressions may contain variables. +*/ +class var_subst { + beta_reducer m_reducer; + bool m_std_order; +public: + var_subst(ast_manager & m, bool std_order = true):m_reducer(m), m_std_order(std_order) {} + bool std_order() const { return m_std_order; } + + /** + When std_order() == true, + I'm using the same standard used in quantifier instantiation. + (VAR 0) is stored in the last position of the array. + ... + (VAR (num_args - 1)) is stored in the first position of the array. + + Otherwise, (VAR 0) is stored in the first position, (VAR 1) in the second, and so on. + */ + void operator()(expr * n, unsigned num_args, expr * const * args, expr_ref & result); + void reset() { m_reducer.reset(); } +}; + +/** + \brief Eliminate the unused variables from \c q. Store the result in \c r. +*/ +void elim_unused_vars(ast_manager & m, quantifier * q, expr_ref & r); + +/** + \brief Instantiate quantifier q using the given exprs. + The vector exprs should contain at least q->get_num_decls() expressions. + + I'm using the same standard used in quantifier instantiation. + (VAR 0) is stored in the last position of the array. + ... + (VAR (q->get_num_decls() - 1)) is stored in the first position of the array. +*/ +void instantiate(ast_manager & m, quantifier * q, expr * const * exprs, expr_ref & result); + +/** + \brief Enumerate set of free variables in expression. + + Return the sorts of the free variables. +*/ +void get_free_vars(expr* e, ptr_vector& sorts); + +#endif + + diff --git a/lib/vector.h b/lib/vector.h new file mode 100644 index 000000000..484c406c3 --- /dev/null +++ b/lib/vector.h @@ -0,0 +1,469 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + vector.h + +Abstract: + Dynamic array implementation. + Remarks: + + - Empty arrays consume only sizeof(T *) bytes. + + - There is the option of disabling the destructor invocation for elements stored in the vector. + This is useful for vectors of int. + +Author: + + Leonardo de Moura (leonardo) 2006-09-11. + +Revision History: + +--*/ +#ifndef _VECTOR_H_ +#define _VECTOR_H_ + +#include"debug.h" +#include +#include +#include"memory_manager.h" +#include"hash.h" + +// disable warning for constant 'if' expressions. +// these are used heavily in templates. +#ifdef _MSC_VER +#pragma warning(disable:4127) +#endif + +template +class vector { +#define SIZE_IDX -1 +#define CAPACITY_IDX -2 + T * m_data; + + void destroy_elements() { + iterator it = begin(); + iterator e = end(); + for (; it != e; ++it) { + it->~T(); + } + } + + void free_memory() { + memory::deallocate(reinterpret_cast(reinterpret_cast(m_data) - 2)); + } + + void expand_vector() { + if (m_data == 0) { + unsigned capacity = 2; + unsigned * mem = reinterpret_cast(memory::allocate(sizeof(T) * capacity + sizeof(unsigned) * 2)); + *mem = capacity; + mem++; + *mem = 0; + mem++; + m_data = reinterpret_cast(mem); + } + else { + SASSERT(capacity() > 0); + unsigned old_capacity = reinterpret_cast(m_data)[CAPACITY_IDX]; + unsigned new_capacity = (3 * old_capacity + 1) >> 1; + unsigned size = reinterpret_cast(m_data)[SIZE_IDX]; + unsigned * mem = reinterpret_cast(memory::allocate(sizeof(T) * new_capacity + sizeof(unsigned) * 2)); + *mem = new_capacity; + mem ++; + *mem = size; + mem++; + memcpy(mem, m_data, size * sizeof(T)); + free_memory(); + m_data = reinterpret_cast(mem); + } + } + + void copy_core(vector const & source) { + unsigned size = source.size(); + unsigned capacity = source.capacity(); + unsigned * mem = reinterpret_cast(memory::allocate(sizeof(T) * capacity + sizeof(unsigned) * 2)); + *mem = capacity; + mem++; + *mem = size; + mem++; + m_data = reinterpret_cast(mem); + const_iterator it = source.begin(); + iterator it2 = begin(); + SASSERT(it2 == m_data); + const_iterator e = source.end(); + for (; it != e; ++it, ++it2) { + new (it2) T(*it); + } + } + + void destroy() { + if (m_data) { + if (CallDestructors) { + destroy_elements(); + } + free_memory(); + } + } + +public: + typedef T data; + typedef T * iterator; + typedef const T * const_iterator; + + vector(): + m_data(0) { + } + + vector(unsigned s) { + unsigned * mem = reinterpret_cast(memory::allocate(sizeof(T) * s + sizeof(unsigned) * 2)); + *mem = s; + mem++; + *mem = s; + mem++; + m_data = reinterpret_cast(mem); + // initialize elements + iterator it = begin(); + iterator e = end(); + for (; it != e; ++it) { + new (it) T(); + } + } + + vector(unsigned s, T const & elem): + m_data(0) { + resize(s, elem); + } + + vector(vector const & source): + m_data(0) { + if (source.m_data) { + copy_core(source); + } + SASSERT(size() == source.size()); + } + + vector(unsigned s, T const * data): + m_data(0) { + for (unsigned i = 0; i < s; i++) { + push_back(data[i]); + } + } + + vector(T const & e) : + m_data(0) { + push_back(e); + } + + vector(T const & t1, T const & t2) : + m_data(0) { + push_back(t1); + push_back(t2); + } + + vector(T const & t1, T const & t2, T const & t3) : + m_data(0) { + push_back(t1); + push_back(t2); + push_back(t3); + } + + ~vector() { + destroy(); + } + + void finalize() { + destroy(); + m_data = 0; + } + + vector & operator=(vector const & source) { + destroy(); + if (source.m_data) { + copy_core(source); + } + else { + m_data = 0; + } + return *this; + } + + void reset() { + if (m_data) { + if (CallDestructors) { + destroy_elements(); + } + reinterpret_cast(m_data)[SIZE_IDX] = 0; + } + } + + bool empty() const { + return m_data == 0 || reinterpret_cast(m_data)[SIZE_IDX] == 0; + } + + unsigned size() const { + if (m_data == 0) { + return 0; + } + return reinterpret_cast(m_data)[SIZE_IDX]; + } + + unsigned capacity() const { + if (m_data == 0) { + return 0; + } + return reinterpret_cast(m_data)[CAPACITY_IDX]; + } + + iterator begin() { + return m_data; + } + + iterator end() { + return m_data + size(); + } + + const_iterator begin() const { + return m_data; + } + + const_iterator end() const { + return m_data + size(); + } + + void set_end(iterator it) { + if (m_data) { + unsigned new_sz = static_cast(it - m_data); + if (CallDestructors) { + iterator e = end(); + for(; it != e; ++it) { + it->~T(); + } + } + reinterpret_cast(m_data)[SIZE_IDX] = new_sz; + } + else { + SASSERT(it == 0); + } + } + + T & operator[](unsigned idx) { + SASSERT(idx < size()); + return m_data[idx]; + } + + T const & operator[](unsigned idx) const { + SASSERT(idx < size()); + return m_data[idx]; + } + + T & get(unsigned idx) { + SASSERT(idx < size()); + return m_data[idx]; + } + + T const & get(unsigned idx) const { + SASSERT(idx < size()); + return m_data[idx]; + } + + void set(unsigned idx, T const & val) { + SASSERT(idx < size()); + m_data[idx] = val; + } + + T & back() { + SASSERT(!empty()); + return operator[](size() - 1); + } + + T const & back() const { + SASSERT(!empty()); + return operator[](size() - 1); + } + + void pop_back() { + SASSERT(!empty()); + if (CallDestructors) { + back().~T(); + } + reinterpret_cast(m_data)[SIZE_IDX]--; + } + + void push_back(T const & elem) { + if (m_data == 0 || reinterpret_cast(m_data)[SIZE_IDX] == reinterpret_cast(m_data)[CAPACITY_IDX]) { + expand_vector(); + } + new (m_data + reinterpret_cast(m_data)[SIZE_IDX]) T(elem); + reinterpret_cast(m_data)[SIZE_IDX]++; + } + + void insert(T const & elem) { + push_back(elem); + } + + void erase(iterator pos) { + SASSERT(pos >= begin() && pos < end()); + iterator prev = pos; + ++pos; + iterator e = end(); + for(; pos != e; ++pos, ++prev) { + *prev = *pos; + } + reinterpret_cast(m_data)[SIZE_IDX]--; + } + + void erase(T const & elem) { + iterator it = std::find(begin(), end(), elem); + if (it != end()) { + erase(it); + } + } + + void shrink(unsigned s) { + if (m_data) { + SASSERT(s <= reinterpret_cast(m_data)[SIZE_IDX]); + if (CallDestructors) { + iterator it = m_data + s; + iterator e = end(); + for(; it != e; ++it) { + it->~T(); + } + } + reinterpret_cast(m_data)[SIZE_IDX] = s; + } + else { + SASSERT(s == 0); + } + } + + void resize(unsigned s, T const & elem=T()) { + unsigned sz = size(); + if (s <= sz) { shrink(s); return; } + while (s > capacity()) { + expand_vector(); + } + SASSERT(m_data != 0); + reinterpret_cast(m_data)[SIZE_IDX] = s; + iterator it = m_data + sz; + iterator end = m_data + s; + for(; it != end; ++it) { + new (it) T(elem); + } + } + + void append(vector const & other) { + for(unsigned i = 0; i < other.size(); ++i) { + push_back(other[i]); + } + } + + void append(unsigned sz, T const * data) { + for(unsigned i = 0; i < sz; ++i) { + push_back(data[i]); + } + } + + T * c_ptr() const { + return m_data; + } + + void swap(vector & other) { + std::swap(m_data, other.m_data); + } + + void reverse() { + unsigned sz = size(); + for (unsigned i = 0; i < sz/2; ++i) { + std::swap(m_data[i], m_data[sz-i-1]); + } + } + + void fill(T const & elem) { + iterator i = begin(); + iterator e = end(); + for (; i != e; ++i) { + *i = elem; + } + } + + bool contains(T const & elem) const { + const_iterator it = begin(); + const_iterator e = end(); + for (; it != e; ++it) { + if (*it == elem) { + return true; + } + } + return false; + } + + // set pos idx with elem. If idx >= size, then expand using default. + void setx(unsigned idx, T const & elem, T const & d) { + if (idx >= size()) { + resize(idx+1, d); + } + m_data[idx] = elem; + } + + // return element at position idx, if idx >= size, then return default + T const & get(unsigned idx, T const & d) const { + if (idx >= size()) { + return d; + } + return m_data[idx]; + } + + void reserve(unsigned s, T const & d = T()) { + if (s > size()) + resize(s, d); + } +}; + +template +class ptr_vector : public vector { +public: + ptr_vector():vector() {} + ptr_vector(unsigned s):vector(s) {} + ptr_vector(unsigned s, T * elem):vector(s, elem) {} + ptr_vector(ptr_vector const & source):vector(source) {} + ptr_vector(unsigned s, T * const * data):vector(s, const_cast(data)) {} +}; + +template +class svector : public vector { +public: + svector():vector() {} + svector(unsigned s):vector(s) {} + svector(unsigned s, T const & elem):vector(s, elem) {} + svector(svector const & source):vector(source) {} + svector(unsigned s, T const * data):vector(s, data) {} +}; + +typedef svector int_vector; +typedef svector unsigned_vector; +typedef svector char_vector; +typedef svector double_vector; + +template +struct vector_hash { + Hash m_hash; + typedef vector data; + + unsigned operator()(data const& v, unsigned idx) const { return m_hash(v[idx]); } + + vector_hash(Hash const& h = Hash()):m_hash(h) {} + + unsigned operator()(data const& v) const { + if (v.empty()) { + return 778; + } + return get_composite_hash, vector_hash>(v, v.size()); + } + +}; + + +#endif /* _VECTOR_H_ */ + diff --git a/lib/version.h b/lib/version.h new file mode 100644 index 000000000..3618df878 --- /dev/null +++ b/lib/version.h @@ -0,0 +1,12 @@ +// +// DO NOT EDIT DO NOT EDIT +// +// This file is overriden by the build automation on the build machine with +// a new 'current' build number. +// +// DO NOT EDIT DO NOT EDIT +// +#define Z3_MAJOR_VERSION 4 +#define Z3_MINOR_VERSION 2 +#define Z3_BUILD_NUMBER 0 +#define Z3_REVISION_NUMBER 0 diff --git a/lib/vsubst_tactic.cpp b/lib/vsubst_tactic.cpp new file mode 100644 index 000000000..83e1c448b --- /dev/null +++ b/lib/vsubst_tactic.cpp @@ -0,0 +1,170 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + vsubst_tactic.cpp + +Abstract: + + Check satisfiability of QF_NRA problems using virtual subsititution quantifier-elimination. + +Author: + + Nikolaj (nbjorner) 2011-05-16 + +Notes: + Ported to tactic framework on 2012-02-28 + It was qfnra_vsubst.cpp + + This goal transformation checks satsifiability + of quantifier-free non-linear constraints using + virtual substitutions (applies to second-degree polynomials). + . identify non-linear variables + . use the identified variables as non-linear variables. + . give up if there are non-linear variables under uninterpreted scope. + give up if there are no non-linear variables. + . call quantifier elimination with + - non-linear elimination option. + - get-first-branch option. + . if the first branch is linear, then done. + if the result is unsat, then done. + if the first branch is non-linear then, + check candidate model, + perhaps iterate using rewriting or just give up. + + . helpful facilities: + . linearize_rewriter + a*a*b + a*b = 0 <=> (b+1) = 0 \/ a = 0 \/ b = 0 + . sign analysis: + a*a + b*b + c < 0 => c < 0 + +--*/ +#include"tactic.h" +#include"qe.h" +#include"arith_decl_plugin.h" +#include"for_each_expr.h" +#include"extension_model_converter.h" +#include"params2front_end_params.h" +#include"ast_smt2_pp.h" + +class vsubst_tactic : public tactic { + params_ref m_params; + + class get_var_proc { + arith_util m_arith; + ptr_vector& m_vars; + public: + get_var_proc(ast_manager & m, ptr_vector& vars) : m_arith(m), m_vars(vars) {} + + void operator()(expr* e) { + if (is_app(e)) { + app* a = to_app(e); + if (m_arith.is_real(e) && + a->get_num_args() == 0 && + a->get_family_id() == null_family_id) { + m_vars.push_back(a); + } + } + } + }; + + void get_vars(ast_manager & m, expr* fml, ptr_vector& vars) { + get_var_proc proc(m, vars); + for_each_expr(proc, fml); + } + + void main(goal & s, model_converter_ref & mc, params_ref const & p) { + ast_manager & m = s.m(); + + ptr_vector fs; + for (unsigned i = 0; i < s.size(); ++i) { + fs.push_back(s.form(i)); + } + app_ref f(m.mk_and(fs.size(), fs.c_ptr()), m); + TRACE("vsubst", + s.display(tout); + tout << "goal: " << mk_ismt2_pp(f.get(), m) << "\n";); + ptr_vector vars; + get_vars(m, f.get(), vars); + + if (vars.empty()) { + TRACE("vsubst", tout << "no real variables\n";); + throw tactic_exception("there are no real variables"); + } + + front_end_params params; + params2front_end_params(p, params); + params.m_model = false; + flet fl1(params.m_nlquant_elim, true); + flet fl2(params.m_nl_arith_gb, false); + TRACE("quant_elim", tout << "Produce models: " << params.m_model << "\n";); + + qe::expr_quant_elim_star1 qelim(m, params); + expr_ref g(f, m); + qe::def_vector defs(m); + lbool is_sat = qelim.first_elim(vars.size(), vars.c_ptr(), g, defs); + if (is_sat == l_undef) { + TRACE("vsubst", tout << mk_ismt2_pp(g, m) << "\n";); + throw tactic_exception("elimination was not successful"); + } + if (!defs.empty()) { + extension_model_converter * ev = alloc(extension_model_converter, m); + mc = ev; + for (unsigned i = defs.size(); i > 0; ) { + --i; + ev->insert(defs.var(i), defs.def(i)); + } + } + + s.reset(); + // TBD: wasteful as we already know it is sat or unsat. + // TBD: extract model from virtual substitution. + s.assert_expr(g); + + TRACE("qfnra_vsubst", + tout << "v-subst result:\n"; + s.display(tout);); + } + + +public: + vsubst_tactic(params_ref const & p):m_params(p) {} + + virtual tactic * translate(ast_manager & m) { + return alloc(vsubst_tactic, m_params); + } + + virtual ~vsubst_tactic() {} + + virtual void updt_params(params_ref const & p) { + m_params = p; + } + + /** + \brief Check satisfiability of an assertion set of QF_NRA + by using virtual substitutions. + */ + virtual void operator()(goal_ref const & g, + goal_ref_buffer & result, + model_converter_ref & mc, + proof_converter_ref & pc, + expr_dependency_ref & core) { + SASSERT(g->is_well_sorted()); + fail_if_proof_generation("vsubst", g); + fail_if_unsat_core_generation("vsubst", g); + fail_if_model_generation("vsubst", g); // disable for now due to problems with infinitesimals. + mc = 0; pc = 0; core = 0; result.reset(); + + main(*(g.get()), mc, m_params); + + result.push_back(g.get()); + SASSERT(g->is_well_sorted()); + } + + virtual void cleanup(void) {} +}; + +tactic * mk_vsubst_tactic(ast_manager & m, params_ref const & p) { + return alloc(vsubst_tactic, p); +} diff --git a/lib/vsubst_tactic.h b/lib/vsubst_tactic.h new file mode 100644 index 000000000..3fccbafc4 --- /dev/null +++ b/lib/vsubst_tactic.h @@ -0,0 +1,30 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + vsubst_tactic.h + +Abstract: + + Check satisfiability of QF_NRA problems using virtual subsititution quantifier-elimination. + +Author: + + Nikolaj (nbjorner) 2011-05-16 + +Notes: + + +--*/ +#ifndef _VSUBST_TACTIC_H_ +#define _VSUBST_TACTIC_H_ + +#include"params.h" +class ast_manager; +class tactic; + +tactic * mk_vsubst_tactic(ast_manager & m, params_ref const & p = params_ref()); + +#endif + diff --git a/lib/warning.cpp b/lib/warning.cpp new file mode 100644 index 000000000..dd4ca04d4 --- /dev/null +++ b/lib/warning.cpp @@ -0,0 +1,172 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + warning.cpp + +Abstract: + + Support for warning messages. + +Author: + + Leonardo de Moura (leonardo) 2006-12-01. + +Revision History: + +--*/ +#include +#include + +#include "error_codes.h" +#include "util.h" +#include "buffer.h" +#include "vector.h" +#include "ini_file.h" + +#ifdef _WINDOWS +#define PRF sprintf_s +#define VPRF vsprintf_s + +void myInvalidParameterHandler( + const wchar_t* expression, + const wchar_t* function, + const wchar_t* file, + unsigned int line, + uintptr_t pReserved) +{ + // no-op +} + +#define BEGIN_ERR_HANDLER() \ + _invalid_parameter_handler oldHandler, newHandler; \ + newHandler = myInvalidParameterHandler; \ + oldHandler = _set_invalid_parameter_handler(newHandler); \ + _CrtSetReportMode(_CRT_ASSERT, 0); \ + +#define END_ERR_HANDLER() \ + _set_invalid_parameter_handler(oldHandler); + +// _invalid_parameter_handler oldHandler, newHandler; +// newHandler = myInvalidParameterHandler; +// oldHandler = _set_invalid_parameter_handler(newHandler); +// Disable the message box for assertions. + + +#else +#define PRF snprintf +#define VPRF vsnprintf +#define BEGIN_ERR_HANDLER() {} +#define END_ERR_HANDLER() {} +#endif + +bool g_warning_msgs = true; +bool g_use_std_stdout = false; +std::ostream* g_error_stream = 0; +std::ostream* g_warning_stream = 0; +bool g_show_error_msg_prefix = true; + +void send_warnings_to_stdout(bool flag) { + g_use_std_stdout = flag; +} + +void enable_warning_messages(bool flag) { + g_warning_msgs = flag; +} + +void set_error_stream(std::ostream* strm) { + g_error_stream = strm; +} + +void set_warning_stream(std::ostream* strm) { + g_warning_stream = strm; +} + +void register_warning(ini_params & p) { + p.register_bool_param("WARNING", g_warning_msgs, "enable/disable warning messages", true); +} + +void disable_error_msg_prefix() { + g_show_error_msg_prefix = false; +} + +static void string2ostream(std::ostream& out, char const* msg) { + svector buff; + buff.resize(10); + BEGIN_ERR_HANDLER(); + while (true) { + int nc = PRF(buff.c_ptr(), buff.size(), msg); + if (nc >= 0 && nc < static_cast(buff.size())) + break; // success + buff.resize(buff.size()*2 + 1); + } + END_ERR_HANDLER(); + out << buff.c_ptr(); +} + +void format2ostream(std::ostream & out, char const* msg, va_list args) { + svector buff; +#if !defined(_WINDOWS) && defined(_AMD64_) + // see comment below. + buff.resize(1024); +#else + buff.resize(128); +#endif + BEGIN_ERR_HANDLER(); + while (true) { + int nc = VPRF(buff.c_ptr(), buff.size(), msg, args); +#if !defined(_WINDOWS) && defined(_AMD64_) + // For some strange reason, on Linux 64-bit version, va_list args is reset by vsnprintf. + // Z3 crashes when trying to use va_list args again. + // Hack: I truncate the message instead of expanding the buffer to make sure that + // va_list args is only used once. + END_ERR_HANDLER(); + if (nc < 0) { + // vsnprintf didn't work, so we just print the msg + out << msg; + return; + } + if (nc >= static_cast(buff.size())) { + // truncate the message + buff[buff.size() - 1] = 0; + } + out << buff.c_ptr(); + return; +#else + if (nc >= 0 && nc < static_cast(buff.size())) + break; // success + buff.resize(buff.size()*2 + 1); +#endif + } + END_ERR_HANDLER(); + out << buff.c_ptr(); +} + + + +void print_msg(std::ostream * out, const char* prefix, const char* msg, va_list args) { + if (out) { + string2ostream(*out, prefix); + format2ostream(*out, msg, args); + string2ostream(*out, "\n"); + out->flush(); + } + else { + FILE * f = g_use_std_stdout ? stdout : stderr; + fprintf(f, prefix); + vfprintf(f, msg, args); + fprintf(f, "\n"); + fflush(f); + }; +} + +void warning_msg(const char * msg, ...) { + if (g_warning_msgs) { + va_list args; + va_start(args, msg); + print_msg(g_warning_stream, "WARNING: ", msg, args); + va_end(args); + } +} + diff --git a/lib/warning.h b/lib/warning.h new file mode 100644 index 000000000..6800c1f95 --- /dev/null +++ b/lib/warning.h @@ -0,0 +1,64 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + warning.h + +Abstract: + + Support for warning messages. + +Author: + + Leonardo de Moura (leonardo) 2006-12-01. + +Revision History: + +--*/ +#ifndef _WARNING_H_ +#define _WARNING_H_ +#include +#include + +class ini_params; + +void send_warnings_to_stdout(bool flag); + +void enable_warning_messages(bool flag); + +void set_error_stream(std::ostream* strm); + +void set_warning_stream(std::ostream* strm); + +void warning_msg(const char * msg, ...); + +void register_warning(ini_params & p); + +void disable_error_msg_prefix(); + +void format2ostream(std::ostream& out, char const* fmt, va_list args); + +class warning_displayer { + const char * m_msg; + bool m_displayed; +public: + warning_displayer(const char * msg): + m_msg(msg), + m_displayed(false) { + } + + void sign() { + if (!m_displayed) { + warning_msg(m_msg); + m_displayed = true; + } + } + + void reset() { + m_displayed = false; + } +}; + +#endif /* _WARNING_H_ */ + diff --git a/lib/watch_list.cpp b/lib/watch_list.cpp new file mode 100644 index 000000000..731e021fe --- /dev/null +++ b/lib/watch_list.cpp @@ -0,0 +1,110 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + watch_list.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-05-23. + +Revision History: + +--*/ +#include"watch_list.h" + +namespace smt { + +#define DEFAULT_WATCH_LIST_SIZE (sizeof(clause *) * 4) +#ifdef _AMD64_ +// make sure data is aligned in 64 bit machines +#define HEADER_SIZE (4 * sizeof(unsigned)) +#else +#define HEADER_SIZE (3 * sizeof(unsigned)) +#endif + + void watch_list::destroy() { + if (m_data) { + dealloc_svect(reinterpret_cast(m_data) - HEADER_SIZE); + } + } + + void watch_list::expand() { + if (m_data == 0) { + unsigned size = DEFAULT_WATCH_LIST_SIZE + HEADER_SIZE; + unsigned * mem = reinterpret_cast(alloc_svect(char, size)); +#ifdef _AMD64_ + ++mem; // make sure data is aligned in 64 bit machines +#endif + *mem = 0; + ++mem; + *mem = DEFAULT_WATCH_LIST_SIZE; + ++mem; + *mem = DEFAULT_WATCH_LIST_SIZE; + ++mem; + m_data = reinterpret_cast(mem); + } + else { + unsigned curr_begin_bin = begin_lits_core(); + unsigned curr_capacity = end_lits_core(); + unsigned bin_bytes = curr_capacity - curr_begin_bin; + unsigned new_capacity = (curr_capacity * 3 + sizeof(clause *)) >> 1; + unsigned * mem = reinterpret_cast(alloc_svect(char, new_capacity + HEADER_SIZE)); + unsigned curr_end_cls = end_cls_core(); +#ifdef _AMD64_ + ++mem; // make sure data is aligned in 64 bit machines +#endif + *mem = curr_end_cls; + ++mem; + SASSERT(bin_bytes <= new_capacity); + unsigned new_begin_bin = new_capacity - bin_bytes; + *mem = new_begin_bin; + ++mem; + *mem = new_capacity; + ++mem; + memcpy(mem, m_data, curr_end_cls); + memcpy(reinterpret_cast(mem) + new_begin_bin, m_data + curr_begin_bin, bin_bytes); + destroy(); + m_data = reinterpret_cast(mem); + } + } + + void watch_list::remove_clause(clause * c) { + clause_iterator begin = begin_clause(); + clause_iterator end = end_clause(); + clause_iterator it = std::find(begin, end, c); + if (it == end) { + return; + } + clause_iterator prev = it; + ++it; + for(; it != end; ++it, ++prev) { + *prev = *it; + } + end_cls_core() -= sizeof(clause *); + } + + void watch_list::remove_literal(literal l) { + literal * begin = begin_literals(); + literal * end = end_literals(); + literal * it = std::find(begin, end, l); + if (it == end) { + return; + } + literal * prev = it; + while (it != begin) { + SASSERT(it == prev); + --it; + *prev = *it; + --prev; + } + SASSERT(prev == begin); + begin_lits_core() += sizeof(literal); + } + +}; diff --git a/lib/watch_list.h b/lib/watch_list.h new file mode 100644 index 000000000..47b76cace --- /dev/null +++ b/lib/watch_list.h @@ -0,0 +1,186 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + watch_list.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2008-05-23. + +Revision History: + +--*/ +#ifndef _WATCH_LIST_H_ +#define _WATCH_LIST_H_ + +#include"smt_clause.h" +#include"memory_manager.h" + +namespace smt { + + /** + \brief List of clauses and literals watching a given literal. + + ------------------------------------------------------------------------------------------- + | end_nbegin | begin_lits | end | regular clauses | -> <- | literals | + ------------------------------------------------------------------------------------------- + ^ ^ ^ ^ + | | | | + m_data end_cls begin_lits end_lits + + When this class is used to implement unit propagation, a literal l1 in m_watch_list[l2] + represents the binary clause (or l1 (not l2)) + */ + class watch_list { + char * m_data; + + void expand(); + + unsigned & end_cls_core() { + SASSERT(m_data); + return reinterpret_cast(m_data)[-3]; + } + + unsigned end_cls() { + return m_data ? end_cls_core() : 0; + } + + unsigned & begin_lits_core() { + SASSERT(m_data); + return reinterpret_cast(m_data)[-2]; + } + + unsigned begin_lits_core() const { + SASSERT(m_data); + return reinterpret_cast(m_data)[-2]; + } + + unsigned begin_lits() const { + return m_data ? begin_lits_core() : 0; + } + + unsigned & end_lits_core() { + SASSERT(m_data); + return reinterpret_cast(m_data)[-1]; + } + + unsigned end_lits_core() const { + SASSERT(m_data); + return reinterpret_cast(m_data)[-1]; + } + + unsigned end_lits() const { + return m_data ? end_lits_core() : 0; + } + + void destroy(); + + public: + watch_list(): + m_data(0) { + } + + ~watch_list() { + destroy(); + } + + unsigned size() const { + if (m_data) { + return + reinterpret_cast(m_data)[-3] + + reinterpret_cast(m_data)[-1] - + reinterpret_cast(m_data)[-2]; + } + return 0; + } + + typedef clause ** clause_iterator; + + void reset() { + if (m_data) { + end_cls_core() = 0; + begin_lits_core() = end_lits_core(); + } + } + + void reset_and_release_memory() { + destroy(); + m_data = 0; + } + + clause_iterator begin_clause() { + return reinterpret_cast(m_data); + } + + clause_iterator end_clause() { + return reinterpret_cast(m_data + end_cls()); + } + + clause_iterator find_clause(clause const * c) { + return std::find(begin_clause(), end_clause(), c); + } + + literal * begin_literals() { + return reinterpret_cast(m_data + begin_lits()); + } + + literal * end_literals() { + return reinterpret_cast(m_data + end_lits()); + } + + literal const * begin_literals() const { + return reinterpret_cast(m_data + begin_lits()); + } + + literal const * end_literals() const { + return reinterpret_cast(m_data + end_lits()); + } + + literal * find_literal(literal const & l) { + return std::find(begin_literals(), end_literals(), l); + } + + literal const * find_literal(literal const & l) const { + return std::find(begin_literals(), end_literals(), l); + } + + void insert_clause(clause * c) { + if (m_data == 0 || end_cls_core() + sizeof(clause *) >= begin_lits_core()) { + expand(); + } + *(reinterpret_cast(m_data + end_cls_core())) = c; + end_cls_core() += sizeof(clause *); + } + + void insert_literal(literal const & l) { + if (m_data == 0 || begin_lits_core() <= end_cls_core() + sizeof(literal)) { + expand(); + } + SASSERT(begin_lits_core() >= sizeof(literal)); + begin_lits_core() -= sizeof(literal); + *(reinterpret_cast(m_data + begin_lits_core())) = l; + } + + void remove_clause(clause * c); + + void remove_literal(literal l); + + void set_end_clause(clause_iterator new_end) { + SASSERT(new_end <= end_clause()); + if (m_data) { + end_cls_core() = static_cast(reinterpret_cast(new_end) - m_data); + } + } + + }; + +}; + +#endif /* _WATCH_LIST_H_ */ + diff --git a/lib/well_sorted.cpp b/lib/well_sorted.cpp new file mode 100644 index 000000000..f9f04a2df --- /dev/null +++ b/lib/well_sorted.cpp @@ -0,0 +1,83 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + well_sorted.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-12-29. + +Revision History: + +--*/ + +#include +#include"for_each_expr.h" +#include"well_sorted.h" +#include"ast_ll_pp.h" +#include"ast_pp.h" +#include"warning.h" +#include"ast_smt2_pp.h" + +struct well_sorted_proc { + ast_manager & m_manager; + bool m_error; + + well_sorted_proc(ast_manager & m):m_manager(m), m_error(false) {} + + void operator()(var * v) {} + + void operator()(quantifier * n) { + expr const * e = n->get_expr(); + if (!m_manager.is_bool(e)) { + warning_msg("quantifier's body must be a boolean."); + m_error = true; + } + } + + void operator()(app * n) { + unsigned num_args = n->get_num_args(); + func_decl * decl = n->get_decl(); + if (num_args != decl->get_arity() && !decl->is_associative()) { + TRACE("ws", tout << "unexpected number of arguments.\n" << mk_ismt2_pp(n, m_manager);); + warning_msg("unexpected number of arguments."); + m_error = true; + return; + } + + for (unsigned i = 0; i < num_args; i++) { + sort * actual_sort = m_manager.get_sort(n->get_arg(i)); + sort * expected_sort = decl->is_associative() ? decl->get_domain(0) : decl->get_domain(i); + if (expected_sort != actual_sort) { + TRACE("tc", tout << "sort mismatch on argument #" << i << ".\n" << mk_ismt2_pp(n, m_manager); + tout << "Sort mismatch for argument " << i+1 << " of " << mk_ismt2_pp(n, m_manager, false) << "\n"; + tout << "Expected sort: " << mk_pp(expected_sort, m_manager) << "\n"; + tout << "Actual sort: " << mk_pp(actual_sort, m_manager) << "\n"; + tout << "Function sort: " << mk_pp(decl, m_manager) << "."; + ); + std::ostringstream strm; + strm << "Sort mismatch for argument " << i+1 << " of " << mk_ll_pp(n, m_manager, false) << "\n"; + strm << "Expected sort: " << mk_pp(expected_sort, m_manager) << "\n"; + strm << "Actual sort: " << mk_pp(actual_sort, m_manager) << "\n"; + strm << "Function sort: " << mk_pp(decl, m_manager) << "."; + warning_msg(strm.str().c_str()); + m_error = true; + return; + } + } + } +}; + +bool is_well_sorted(ast_manager const & m, expr * n) { + well_sorted_proc p(const_cast(m)); + for_each_expr(p, n); + return !p.m_error; +} + + diff --git a/lib/well_sorted.h b/lib/well_sorted.h new file mode 100644 index 000000000..488e6b02a --- /dev/null +++ b/lib/well_sorted.h @@ -0,0 +1,28 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + well_sorted.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-12-29. + +Revision History: + +--*/ +#ifndef _WELL_SORTED_H_ +#define _WELL_SORTED_H_ + +class ast_manager; +class expr; + +bool is_well_sorted(ast_manager const & m, expr * n); + +#endif /* _WELL_SORTED_H_ */ + diff --git a/lib/z3.h b/lib/z3.h new file mode 100644 index 000000000..8df894f40 --- /dev/null +++ b/lib/z3.h @@ -0,0 +1,39 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + z3.h + +Abstract: + + Z3 API. + +Author: + + Nikolaj Bjorner (nbjorner) + Leonardo de Moura (leonardo) 2007-06-8 + +Notes: + +--*/ + +#ifndef _Z3__H_ +#define _Z3__H_ + +#include +#include"z3_macros.h" +#include"z3_api.h" + +#undef __in +#undef __out +#undef __inout +#undef __in_z +#undef __out_z +#undef __ecount +#undef __in_ecount +#undef __out_ecount +#undef __inout_ecount + +#endif + diff --git a/lib/z3_api.h b/lib/z3_api.h new file mode 100644 index 000000000..24fcc7f10 --- /dev/null +++ b/lib/z3_api.h @@ -0,0 +1,6533 @@ +#ifdef CAMLIDL + #ifdef MLAPIV3 + #define ML3only + #define CorML3 + #else + #define ML4only + #define CorML4 + #endif +#else + #define Conly + #define CorML3 + #define CorML4 +#endif + +#ifdef CorML3 +DEFINE_TYPE(Z3_symbol); +DEFINE_TYPE(Z3_literals); +DEFINE_TYPE(Z3_theory); +DEFINE_TYPE(Z3_config); +DEFINE_TYPE(Z3_context); +DEFINE_TYPE(Z3_sort); +#define Z3_sort_opt Z3_sort +DEFINE_TYPE(Z3_func_decl); +DEFINE_TYPE(Z3_ast); +#define Z3_ast_opt Z3_ast +DEFINE_TYPE(Z3_app); +DEFINE_TYPE(Z3_pattern); +DEFINE_TYPE(Z3_model); +DEFINE_TYPE(Z3_constructor); +DEFINE_TYPE(Z3_constructor_list); +#endif +#ifdef Conly +DEFINE_TYPE(Z3_params); +DEFINE_TYPE(Z3_param_descrs); +DEFINE_TYPE(Z3_goal); +DEFINE_TYPE(Z3_tactic); +DEFINE_TYPE(Z3_probe); +DEFINE_TYPE(Z3_stats); +DEFINE_TYPE(Z3_solver); +DEFINE_TYPE(Z3_ast_vector); +DEFINE_TYPE(Z3_ast_map); +DEFINE_TYPE(Z3_apply_result); +DEFINE_TYPE(Z3_func_interp); +#define Z3_func_interp_opt Z3_func_interp +DEFINE_TYPE(Z3_func_entry); +DEFINE_TYPE(Z3_fixedpoint); +DEFINE_VOID(Z3_theory_data); +#endif + +#ifndef __int64 +#define __int64 long long +#endif + +#ifndef __uint64 +#define __uint64 unsigned long long +#endif + +/** + \defgroup capi C API + +*/ +/*@{*/ + +/** + @name Types + + \conly Most of the types in the C API are opaque pointers. + \mlonly Most of the types in the API are abstract. \endmlonly + + \conly - \c Z3_config: configuration object used to initialize logical contexts. + - \c Z3_context: manager of all other Z3 objects, global configuration options, etc. + - \c Z3_symbol: Lisp-like symbol used to name types, constants, and functions. A symbol can be created using string or integers. + - \c Z3_ast: abstract syntax tree node. That is, the data-structure used in Z3 to represent terms, formulas and types. + - \c Z3_sort: kind of AST used to represent types. + - \c Z3_func_decl: kind of AST used to represent function symbols. + - \c Z3_app: kind of AST used to represent function applications. + - \c Z3_pattern: kind of AST used to represent pattern and multi-patterns used to guide quantifier instantiation. + \conly - \c Z3_constructor: type constructor for a (recursive) datatype. + - \c Z3_params: parameter set used to configure many components such as: simplifiers, tactics, solvers, etc. + - \c Z3_model: model for the constraints asserted into the logical context. + - \c Z3_func_interp: interpretation of a function in a model. + - \c Z3_func_entry: representation of the value of a \c Z3_func_interp at a particular point. + - \c Z3_fixedpoint: context for the recursive predicate solver. + - \c Z3_ast_vector: vector of \c Z3_ast objects. + - \c Z3_ast_map: mapping from \c Z3_ast to \c Z3_ast objects. + - \c Z3_goal: set of formulas that can be solved and/or transformed using tactics and solvers. + - \c Z3_tactic: basic building block for creating custom solvers for specific problem domains. + - \c Z3_probe: function/predicate used to inspect a goal and collect information that may be used to decide which solver and/or preprocessing step will be used. + - \c Z3_apply_result: collection of subgoals resulting from applying of a tactic to a goal. + - \c Z3_solver: (incremental) solver, possibly specialized by a particular tactic or logic. + - \c Z3_stats: statistical data for a solver. +*/ + +#ifdef Conly +/** + \brief Z3 Boolean type. It is just an alias for \c int. +*/ +typedef int Z3_bool; +#else +#define Z3_bool boolean +#endif + +#ifdef Conly +/** + \brief Z3 string type. It is just an alias for const char *. +*/ +typedef const char * Z3_string; +typedef Z3_string * Z3_string_ptr; +#else +typedef [string] const char * Z3_string; +#define Z3_string_ptr Z3_string * +#endif + +#ifdef Conly +/** + \brief True value. It is just an alias for \c 1. +*/ +#define Z3_TRUE 1 + +/** + \brief False value. It is just an alias for \c 0. +*/ +#define Z3_FALSE 0 + +#endif + +/** + \mlonly {!lbool} \endmlonly \conly \brief + Lifted Boolean type: \c false, \c undefined, \c true. +*/ +typedef enum +{ + Z3_L_FALSE = -1, + Z3_L_UNDEF, + Z3_L_TRUE +} Z3_lbool; + +/** + \mlonly {!symbol_kind} \endmlonly \conly \brief + The different kinds of symbol. + In Z3, a symbol can be represented using integers and strings (See #Z3_get_symbol_kind). + + \sa Z3_mk_int_symbol + \sa Z3_mk_string_symbol +*/ +typedef enum +{ + Z3_INT_SYMBOL, + Z3_STRING_SYMBOL +} Z3_symbol_kind; + + +/** + \mlonly {!parameter_kind} \endmlonly \conly \brief + The different kinds of parameters that can be associated with function symbols. + \sa Z3_get_decl_num_parameters + \sa Z3_get_decl_parameter_kind + + - Z3_PARAMETER_INT is used for integer parameters. + - Z3_PARAMETER_DOUBLE is used for double parameters. + - Z3_PARAMETER_RATIONAL is used for parameters that are rational numbers. + - Z3_PARAMETER_SYMBOL is used for parameters that are symbols. + - Z3_PARAMETER_SORT is used for sort parameters. + - Z3_PARAMETER_AST is used for expression parameters. + - Z3_PARAMETER_FUNC_DECL is used for function declaration parameters. +*/ +typedef enum +{ + Z3_PARAMETER_INT, + Z3_PARAMETER_DOUBLE, + Z3_PARAMETER_RATIONAL, + Z3_PARAMETER_SYMBOL, + Z3_PARAMETER_SORT, + Z3_PARAMETER_AST, + Z3_PARAMETER_FUNC_DECL, +} Z3_parameter_kind; + +/** + \mlonly {!sort_kind} \endmlonly \conly \brief + The different kinds of Z3 types (See #Z3_get_sort_kind). +*/ +typedef enum +{ + Z3_UNINTERPRETED_SORT, + Z3_BOOL_SORT, + Z3_INT_SORT, + Z3_REAL_SORT, + Z3_BV_SORT, + Z3_ARRAY_SORT, + Z3_DATATYPE_SORT, + Z3_RELATION_SORT, + Z3_FINITE_DOMAIN_SORT, + Z3_UNKNOWN_SORT = 1000 +} Z3_sort_kind; + +/** + \mlonly {!ast_kind} \endmlonly \conly \brief + The different kinds of Z3 AST (abstract syntax trees). That is, terms, formulas and types. + + - Z3_APP_AST: constant and applications + - Z3_NUMERAL_AST: numeral constants + - Z3_VAR_AST: bound variables + - Z3_QUANTIFIER_AST: quantifiers + - Z3_SORT_AST: sort + - Z3_FUNC_DECL_AST: function declaration + - Z3_UNKNOWN_AST: internal +*/ +typedef enum +{ + Z3_NUMERAL_AST, + Z3_APP_AST, + Z3_VAR_AST, + Z3_QUANTIFIER_AST, + Z3_SORT_AST, + Z3_FUNC_DECL_AST, + Z3_UNKNOWN_AST = 1000 +} Z3_ast_kind; + +/** + \mlonly {!decl_kind} \endmlonly \conly \brief + The different kinds of interpreted function kinds. + + - Z3_OP_TRUE The constant true. + + - Z3_OP_FALSE The constant false. + + - Z3_OP_EQ The equality predicate. + + - Z3_OP_DISTINCT The n-ary distinct predicate (every argument is mutually distinct). + + - Z3_OP_ITE The ternary if-then-else term. + + - Z3_OP_AND n-ary conjunction. + + - Z3_OP_OR n-ary disjunction. + + - Z3_OP_IFF equivalence (binary). + + - Z3_OP_XOR Exclusive or. + + - Z3_OP_NOT Negation. + + - Z3_OP_IMPLIES Implication. + + - Z3_OP_OEQ Binary equivalence modulo namings. This binary predicate is used in proof terms. + It captures equisatisfiability and equivalence modulo renamings. + + - Z3_OP_ANUM Arithmetic numeral. + + - Z3_OP_AGNUM Arithmetic algebraic numeral. Algebraic numbers are used to represent irrational numbers in Z3. + + - Z3_OP_LE <=. + + - Z3_OP_GE >=. + + - Z3_OP_LT <. + + - Z3_OP_GT >. + + - Z3_OP_ADD Addition - Binary. + + - Z3_OP_SUB Binary subtraction. + + - Z3_OP_UMINUS Unary minus. + + - Z3_OP_MUL Multiplication - Binary. + + - Z3_OP_DIV Division - Binary. + + - Z3_OP_IDIV Integer division - Binary. + + - Z3_OP_REM Remainder - Binary. + + - Z3_OP_MOD Modulus - Binary. + + - Z3_OP_TO_REAL Coercion of integer to real - Unary. + + - Z3_OP_TO_INT Coercion of real to integer - Unary. + + - Z3_OP_IS_INT Check if real is also an integer - Unary. + + - Z3_OP_POWER Power operator x^y. + + - Z3_OP_STORE Array store. It satisfies select(store(a,i,v),j) = if i = j then v else select(a,j). + Array store takes at least 3 arguments. + + - Z3_OP_SELECT Array select. + + - Z3_OP_CONST_ARRAY The constant array. For example, select(const(v),i) = v holds for every v and i. The function is unary. + + - Z3_OP_ARRAY_DEFAULT Default value of arrays. For example default(const(v)) = v. The function is unary. + + - Z3_OP_ARRAY_MAP Array map operator. + It satisfies map[f](a1,..,a_n)[i] = f(a1[i],...,a_n[i]) for every i. + + - Z3_OP_SET_UNION Set union between two Booelan arrays (two arrays whose range type is Boolean). The function is binary. + + - Z3_OP_SET_INTERSECT Set intersection between two Boolean arrays. The function is binary. + + - Z3_OP_SET_DIFFERENCE Set difference between two Boolean arrays. The function is binary. + + - Z3_OP_SET_COMPLEMENT Set complement of a Boolean array. The function is unary. + + - Z3_OP_SET_SUBSET Subset predicate between two Boolean arrays. The relation is binary. + + - Z3_OP_AS_ARRAY An array value that behaves as the function graph of the + function passed as parameter. + + - Z3_OP_BNUM Bit-vector numeral. + + - Z3_OP_BIT1 One bit bit-vector. + + - Z3_OP_BIT0 Zero bit bit-vector. + + - Z3_OP_BNEG Unary minus. + + - Z3_OP_BADD Binary addition. + + - Z3_OP_BSUB Binary subtraction. + + - Z3_OP_BMUL Binary multiplication. + + - Z3_OP_BSDIV Binary signed division. + + - Z3_OP_BUDIV Binary unsigned division. + + - Z3_OP_BSREM Binary signed remainder. + + - Z3_OP_BUREM Binary unsigned remainder. + + - Z3_OP_BSMOD Binary signed modulus. + + - Z3_OP_BSDIV0 Unary function. bsdiv(x,0) is congruent to bsdiv0(x). + + - Z3_OP_BUDIV0 Unary function. budiv(x,0) is congruent to budiv0(x). + + - Z3_OP_BSREM0 Unary function. bsrem(x,0) is congruent to bsrem0(x). + + - Z3_OP_BUREM0 Unary function. burem(x,0) is congruent to burem0(x). + + - Z3_OP_BSMOD0 Unary function. bsmod(x,0) is congruent to bsmod0(x). + + - Z3_OP_ULEQ Unsigned bit-vector <= - Binary relation. + + - Z3_OP_SLEQ Signed bit-vector <= - Binary relation. + + - Z3_OP_UGEQ Unsigned bit-vector >= - Binary relation. + + - Z3_OP_SGEQ Signed bit-vector >= - Binary relation. + + - Z3_OP_ULT Unsigned bit-vector < - Binary relation. + + - Z3_OP_SLT Signed bit-vector < - Binary relation. + + - Z3_OP_UGT Unsigned bit-vector > - Binary relation. + + - Z3_OP_SGT Signed bit-vector > - Binary relation. + + - Z3_OP_BAND Bit-wise and - Binary. + + - Z3_OP_BOR Bit-wise or - Binary. + + - Z3_OP_BNOT Bit-wise not - Unary. + + - Z3_OP_BXOR Bit-wise xor - Binary. + + - Z3_OP_BNAND Bit-wise nand - Binary. + + - Z3_OP_BNOR Bit-wise nor - Binary. + + - Z3_OP_BXNOR Bit-wise xnor - Binary. + + - Z3_OP_CONCAT Bit-vector concatenation - Binary. + + - Z3_OP_SIGN_EXT Bit-vector sign extension. + + - Z3_OP_ZERO_EXT Bit-vector zero extension. + + - Z3_OP_EXTRACT Bit-vector extraction. + + - Z3_OP_REPEAT Repeat bit-vector n times. + + - Z3_OP_BREDOR Bit-vector reduce or - Unary. + + - Z3_OP_BREDAND Bit-vector reduce and - Unary. + + - Z3_OP_BCOMP . + + - Z3_OP_BSHL Shift left. + + - Z3_OP_BLSHR Logical shift right. + + - Z3_OP_BASHR Arithmetical shift right. + + - Z3_OP_ROTATE_LEFT Left rotation. + + - Z3_OP_ROTATE_RIGHT Right rotation. + + - Z3_OP_EXT_ROTATE_LEFT (extended) Left rotation. Similar to Z3_OP_ROTATE_LEFT, but it is a binary operator instead of a parametric one. + + - Z3_OP_EXT_ROTATE_RIGHT (extended) Right rotation. Similar to Z3_OP_ROTATE_RIGHT, but it is a binary operator instead of a parametric one. + + - Z3_OP_INT2BV Coerce integer to bit-vector. NB. This function + is not supported by the decision procedures. Only the most + rudimentary simplification rules are applied to this function. + + - Z3_OP_BV2INT Coerce bit-vector to integer. NB. This function + is not supported by the decision procedures. Only the most + rudimentary simplification rules are applied to this function. + + - Z3_OP_CARRY Compute the carry bit in a full-adder. + The meaning is given by the equivalence + (carry l1 l2 l3) <=> (or (and l1 l2) (and l1 l3) (and l2 l3))) + + - Z3_OP_XOR3 Compute ternary XOR. + The meaning is given by the equivalence + (xor3 l1 l2 l3) <=> (xor (xor l1 l2) l3) + + - Z3_OP_PR_UNDEF: Undef/Null proof object. + + - Z3_OP_PR_TRUE: Proof for the expression 'true'. + + - Z3_OP_PR_ASSERTED: Proof for a fact asserted by the user. + + - Z3_OP_PR_GOAL: Proof for a fact (tagged as goal) asserted by the user. + + - Z3_OP_PR_MODUS_PONENS: Given a proof for p and a proof for (implies p q), produces a proof for q. + \nicebox{ + T1: p + T2: (implies p q) + [mp T1 T2]: q + } + The second antecedents may also be a proof for (iff p q). + + - Z3_OP_PR_REFLEXIVITY: A proof for (R t t), where R is a reflexive relation. This proof object has no antecedents. + The only reflexive relations that are used are + equivalence modulo namings, equality and equivalence. + That is, R is either '~', '=' or 'iff'. + + - Z3_OP_PR_SYMMETRY: Given an symmetric relation R and a proof for (R t s), produces a proof for (R s t). + \nicebox{ + T1: (R t s) + [symmetry T1]: (R s t) + } + T1 is the antecedent of this proof object. + + - Z3_OP_PR_TRANSITIVITY: Given a transitive relation R, and proofs for (R t s) and (R s u), produces a proof + for (R t u). + \nicebox{ + T1: (R t s) + T2: (R s u) + [trans T1 T2]: (R t u) + } + + - Z3_OP_PR_TRANSITIVITY_STAR: Condensed transitivity proof. This proof object is only used if the parameter PROOF_MODE is 1. + It combines several symmetry and transitivity proofs. + + Example: + \nicebox{ + T1: (R a b) + T2: (R c b) + T3: (R c d) + [trans* T1 T2 T3]: (R a d) + } + R must be a symmetric and transitive relation. + + Assuming that this proof object is a proof for (R s t), then + a proof checker must check if it is possible to prove (R s t) + using the antecedents, symmetry and transitivity. That is, + if there is a path from s to t, if we view every + antecedent (R a b) as an edge between a and b. + + - Z3_OP_PR_MONOTONICITY: Monotonicity proof object. + \nicebox{ + T1: (R t_1 s_1) + ... + Tn: (R t_n s_n) + [monotonicity T1 ... Tn]: (R (f t_1 ... t_n) (f s_1 ... s_n)) + } + Remark: if t_i == s_i, then the antecedent Ti is suppressed. + That is, reflexivity proofs are supressed to save space. + + - Z3_OP_PR_QUANT_INTRO: Given a proof for (~ p q), produces a proof for (~ (forall (x) p) (forall (x) q)). + + T1: (~ p q) + [quant-intro T1]: (~ (forall (x) p) (forall (x) q)) + + - Z3_OP_PR_DISTRIBUTIVITY: Distributivity proof object. + Given that f (= or) distributes over g (= and), produces a proof for + + (= (f a (g c d)) + (g (f a c) (f a d))) + + If f and g are associative, this proof also justifies the following equality: + + (= (f (g a b) (g c d)) + (g (f a c) (f a d) (f b c) (f b d))) + + where each f and g can have arbitrary number of arguments. + + This proof object has no antecedents. + Remark. This rule is used by the CNF conversion pass and + instantiated by f = or, and g = and. + + - Z3_OP_PR_AND_ELIM: Given a proof for (and l_1 ... l_n), produces a proof for l_i + + \nicebox{ + T1: (and l_1 ... l_n) + [and-elim T1]: l_i + } + - Z3_OP_PR_NOT_OR_ELIM: Given a proof for (not (or l_1 ... l_n)), produces a proof for (not l_i). + + \nicebox{ + T1: (not (or l_1 ... l_n)) + [not-or-elim T1]: (not l_i) + } + + - Z3_OP_PR_REWRITE: A proof for a local rewriting step (= t s). + The head function symbol of t is interpreted. + + This proof object has no antecedents. + The conclusion of a rewrite rule is either an equality (= t s), + an equivalence (iff t s), or equi-satisfiability (~ t s). + Remark: if f is bool, then = is iff. + + + Examples: + \nicebox{ + (= (+ x 0) x) + (= (+ x 1 2) (+ 3 x)) + (iff (or x false) x) + } + + - Z3_OP_PR_REWRITE_STAR: A proof for rewriting an expression t into an expression s. + This proof object is used if the parameter PROOF_MODE is 1. + This proof object can have n antecedents. + The antecedents are proofs for equalities used as substitution rules. + The object is also used in a few cases if the parameter PROOF_MODE is 2. + The cases are: + - When applying contextual simplification (CONTEXT_SIMPLIFIER=true) + - When converting bit-vectors to Booleans (BIT2BOOL=true) + - When pulling ite expression up (PULL_CHEAP_ITE_TREES=true) + + - Z3_OP_PR_PULL_QUANT: A proof for (iff (f (forall (x) q(x)) r) (forall (x) (f (q x) r))). This proof object has no antecedents. + + - Z3_OP_PR_PULL_QUANT_STAR: A proof for (iff P Q) where Q is in prenex normal form. + This proof object is only used if the parameter PROOF_MODE is 1. + This proof object has no antecedents. + + - Z3_OP_PR_PUSH_QUANT: A proof for: + + \nicebox{ + (iff (forall (x_1 ... x_m) (and p_1[x_1 ... x_m] ... p_n[x_1 ... x_m])) + (and (forall (x_1 ... x_m) p_1[x_1 ... x_m]) + ... + (forall (x_1 ... x_m) p_n[x_1 ... x_m]))) + } + This proof object has no antecedents. + + - Z3_OP_PR_ELIM_UNUSED_VARS: + A proof for (iff (forall (x_1 ... x_n y_1 ... y_m) p[x_1 ... x_n]) + (forall (x_1 ... x_n) p[x_1 ... x_n])) + + It is used to justify the elimination of unused variables. + This proof object has no antecedents. + + - Z3_OP_PR_DER: A proof for destructive equality resolution: + (iff (forall (x) (or (not (= x t)) P[x])) P[t]) + if x does not occur in t. + + This proof object has no antecedents. + + Several variables can be eliminated simultaneously. + + - Z3_OP_PR_QUANT_INST: A proof of (or (not (forall (x) (P x))) (P a)) + + - Z3_OP_PR_HYPOTHESIS: Mark a hypothesis in a natural deduction style proof. + + - Z3_OP_PR_LEMMA: + + \nicebox{ + T1: false + [lemma T1]: (or (not l_1) ... (not l_n)) + } + This proof object has one antecedent: a hypothetical proof for false. + It converts the proof in a proof for (or (not l_1) ... (not l_n)), + when T1 contains the hypotheses: l_1, ..., l_n. + + - Z3_OP_PR_UNIT_RESOLUTION: + \nicebox{ + T1: (or l_1 ... l_n l_1' ... l_m') + T2: (not l_1) + ... + T(n+1): (not l_n) + [unit-resolution T1 ... T(n+1)]: (or l_1' ... l_m') + } + + - Z3_OP_PR_IFF_TRUE: + \nicebox{ + T1: p + [iff-true T1]: (iff p true) + } + + - Z3_OP_PR_IFF_FALSE: + \nicebox{ + T1: (not p) + [iff-false T1]: (iff p false) + } + + - Z3_OP_PR_COMMUTATIVITY: + + [comm]: (= (f a b) (f b a)) + + f is a commutative operator. + + This proof object has no antecedents. + Remark: if f is bool, then = is iff. + + - Z3_OP_PR_DEF_AXIOM: Proof object used to justify Tseitin's like axioms: + + \nicebox{ + (or (not (and p q)) p) + (or (not (and p q)) q) + (or (not (and p q r)) p) + (or (not (and p q r)) q) + (or (not (and p q r)) r) + ... + (or (and p q) (not p) (not q)) + (or (not (or p q)) p q) + (or (or p q) (not p)) + (or (or p q) (not q)) + (or (not (iff p q)) (not p) q) + (or (not (iff p q)) p (not q)) + (or (iff p q) (not p) (not q)) + (or (iff p q) p q) + (or (not (ite a b c)) (not a) b) + (or (not (ite a b c)) a c) + (or (ite a b c) (not a) (not b)) + (or (ite a b c) a (not c)) + (or (not (not a)) (not a)) + (or (not a) a) + } + This proof object has no antecedents. + Note: all axioms are propositional tautologies. + Note also that 'and' and 'or' can take multiple arguments. + You can recover the propositional tautologies by + unfolding the Boolean connectives in the axioms a small + bounded number of steps (=3). + + - Z3_OP_PR_DEF_INTRO: Introduces a name for a formula/term. + Suppose e is an expression with free variables x, and def-intro + introduces the name n(x). The possible cases are: + + When e is of Boolean type: + [def-intro]: (and (or n (not e)) (or (not n) e)) + + or: + [def-intro]: (or (not n) e) + when e only occurs positively. + + When e is of the form (ite cond th el): + [def-intro]: (and (or (not cond) (= n th)) (or cond (= n el))) + + Otherwise: + [def-intro]: (= n e) + + - Z3_OP_PR_APPLY_DEF: + [apply-def T1]: F ~ n + F is 'equivalent' to n, given that T1 is a proof that + n is a name for F. + + - Z3_OP_PR_IFF_OEQ: + T1: (iff p q) + [iff~ T1]: (~ p q) + + - Z3_OP_PR_NNF_POS: Proof for a (positive) NNF step. Example: + \nicebox{ + T1: (not s_1) ~ r_1 + T2: (not s_2) ~ r_2 + T3: s_1 ~ r_1' + T4: s_2 ~ r_2' + [nnf-pos T1 T2 T3 T4]: (~ (iff s_1 s_2) + (and (or r_1 r_2') (or r_1' r_2))) + } + The negation normal form steps NNF_POS and NNF_NEG are used in the following cases: + (a) When creating the NNF of a positive force quantifier. + The quantifier is retained (unless the bound variables are eliminated). + Example + \nicebox{ + T1: q ~ q_new + [nnf-pos T1]: (~ (forall (x T) q) (forall (x T) q_new)) + } + (b) When recursively creating NNF over Boolean formulas, where the top-level + connective is changed during NNF conversion. The relevant Boolean connectives + for NNF_POS are 'implies', 'iff', 'xor', 'ite'. + NNF_NEG furthermore handles the case where negation is pushed + over Boolean connectives 'and' and 'or'. + + + - Z3_OP_PR_NFF_NEG: Proof for a (negative) NNF step. Examples: + \nicebox{ + T1: (not s_1) ~ r_1 + ... + Tn: (not s_n) ~ r_n + [nnf-neg T1 ... Tn]: (not (and s_1 ... s_n)) ~ (or r_1 ... r_n) + and + T1: (not s_1) ~ r_1 + ... + Tn: (not s_n) ~ r_n + [nnf-neg T1 ... Tn]: (not (or s_1 ... s_n)) ~ (and r_1 ... r_n) + and + T1: (not s_1) ~ r_1 + T2: (not s_2) ~ r_2 + T3: s_1 ~ r_1' + T4: s_2 ~ r_2' + [nnf-neg T1 T2 T3 T4]: (~ (not (iff s_1 s_2)) + (and (or r_1 r_2) (or r_1' r_2'))) + } + - Z3_OP_PR_NNF_STAR: A proof for (~ P Q) where Q is in negation normal form. + + This proof object is only used if the parameter PROOF_MODE is 1. + + This proof object may have n antecedents. Each antecedent is a PR_DEF_INTRO. + + - Z3_OP_PR_CNF_STAR: A proof for (~ P Q) where Q is in conjunctive normal form. + This proof object is only used if the parameter PROOF_MODE is 1. + This proof object may have n antecedents. Each antecedent is a PR_DEF_INTRO. + + - Z3_OP_PR_SKOLEMIZE: Proof for: + + \nicebox{ + [sk]: (~ (not (forall x (p x y))) (not (p (sk y) y))) + [sk]: (~ (exists x (p x y)) (p (sk y) y)) + } + + This proof object has no antecedents. + + - Z3_OP_PR_MODUS_PONENS_OEQ: Modus ponens style rule for equi-satisfiability. + \nicebox{ + T1: p + T2: (~ p q) + [mp~ T1 T2]: q + } + + - Z3_OP_PR_TH_LEMMA: Generic proof for theory lemmas. + + The theory lemma function comes with one or more parameters. + The first parameter indicates the name of the theory. + For the theory of arithmetic, additional parameters provide hints for + checking the theory lemma. + The hints for arithmetic are: + + - farkas - followed by rational coefficients. Multiply the coefficients to the + inequalities in the lemma, add the (negated) inequalities and obtain a contradiction. + + - triangle-eq - Indicates a lemma related to the equivalence: + \nicebox{ + (iff (= t1 t2) (and (<= t1 t2) (<= t2 t1))) + } + + - gcd-test - Indicates an integer linear arithmetic lemma that uses a gcd test. + + + - Z3_OP_RA_STORE: Insert a record into a relation. + The function takes \c n+1 arguments, where the first argument is the relation and the remaining \c n elements + correspond to the \c n columns of the relation. + + - Z3_OP_RA_EMPTY: Creates the empty relation. + + - Z3_OP_RA_IS_EMPTY: Tests if the relation is empty. + + - Z3_OP_RA_JOIN: Create the relational join. + + - Z3_OP_RA_UNION: Create the union or convex hull of two relations. + The function takes two arguments. + + - Z3_OP_RA_WIDEN: Widen two relations. + The function takes two arguments. + + - Z3_OP_RA_PROJECT: Project the columns (provided as numbers in the parameters). + The function takes one argument. + + - Z3_OP_RA_FILTER: Filter (restrict) a relation with respect to a predicate. + The first argument is a relation. + The second argument is a predicate with free de-Brujin indices + corresponding to the columns of the relation. + So the first column in the relation has index 0. + + - Z3_OP_RA_NEGATION_FILTER: Intersect the first relation with respect to negation + of the second relation (the function takes two arguments). + Logically, the specification can be described by a function + + target = filter_by_negation(pos, neg, columns) + + where columns are pairs c1, d1, .., cN, dN of columns from pos and neg, such that + target are elements in x in pos, such that there is no y in neg that agrees with + x on the columns c1, d1, .., cN, dN. + + + - Z3_OP_RA_RENAME: rename columns in the relation. + The function takes one argument. + The parameters contain the renaming as a cycle. + + - Z3_OP_RA_COMPLEMENT: Complement the relation. + + - Z3_OP_RA_SELECT: Check if a record is an element of the relation. + The function takes \c n+1 arguments, where the first argument is a relation, + and the remaining \c n arguments correspond to a record. + + - Z3_OP_RA_CLONE: Create a fresh copy (clone) of a relation. + The function is logically the identity, but + in the context of a register machine allows + for \mlonly [OP_RA_UNION] \endmlonly \conly #Z3_OP_RA_UNION + to perform destructive updates to the first argument. + + + - Z3_OP_FD_LT: A less than predicate over the finite domain Z3_FINITE_DOMAIN_SORT. + + - Z3_OP_LABEL: A label (used by the Boogie Verification condition generator). + The label has two parameters, a string and a Boolean polarity. + It takes one argument, a formula. + + - Z3_OP_LABEL_LIT: A label literal (used by the Boogie Verification condition generator). + A label literal has a set of string parameters. It takes no arguments. + + - Z3_OP_DT_CONSTRUCTOR: datatype constructor. + + - Z3_OP_DT_RECOGNISER: datatype recognizer. + + - Z3_OP_DT_ACCESSOR: datatype accessor. + + - Z3_OP_UNINTERPRETED: kind used for uninterpreted symbols. +*/ +typedef enum { + // Basic + Z3_OP_TRUE = 0x100, + Z3_OP_FALSE, + Z3_OP_EQ, + Z3_OP_DISTINCT, + Z3_OP_ITE, + Z3_OP_AND, + Z3_OP_OR, + Z3_OP_IFF, + Z3_OP_XOR, + Z3_OP_NOT, + Z3_OP_IMPLIES, + Z3_OP_OEQ, + + // Arithmetic + Z3_OP_ANUM = 0x200, + Z3_OP_AGNUM, + Z3_OP_LE, + Z3_OP_GE, + Z3_OP_LT, + Z3_OP_GT, + Z3_OP_ADD, + Z3_OP_SUB, + Z3_OP_UMINUS, + Z3_OP_MUL, + Z3_OP_DIV, + Z3_OP_IDIV, + Z3_OP_REM, + Z3_OP_MOD, + Z3_OP_TO_REAL, + Z3_OP_TO_INT, + Z3_OP_IS_INT, + Z3_OP_POWER, + + // Arrays & Sets + Z3_OP_STORE = 0x300, + Z3_OP_SELECT, + Z3_OP_CONST_ARRAY, + Z3_OP_ARRAY_MAP, + Z3_OP_ARRAY_DEFAULT, + Z3_OP_SET_UNION, + Z3_OP_SET_INTERSECT, + Z3_OP_SET_DIFFERENCE, + Z3_OP_SET_COMPLEMENT, + Z3_OP_SET_SUBSET, + Z3_OP_AS_ARRAY, + + // Bit-vectors + Z3_OP_BNUM = 0x400, + Z3_OP_BIT1, + Z3_OP_BIT0, + Z3_OP_BNEG, + Z3_OP_BADD, + Z3_OP_BSUB, + Z3_OP_BMUL, + + Z3_OP_BSDIV, + Z3_OP_BUDIV, + Z3_OP_BSREM, + Z3_OP_BUREM, + Z3_OP_BSMOD, + + // special functions to record the division by 0 cases + // these are internal functions + Z3_OP_BSDIV0, + Z3_OP_BUDIV0, + Z3_OP_BSREM0, + Z3_OP_BUREM0, + Z3_OP_BSMOD0, + + Z3_OP_ULEQ, + Z3_OP_SLEQ, + Z3_OP_UGEQ, + Z3_OP_SGEQ, + Z3_OP_ULT, + Z3_OP_SLT, + Z3_OP_UGT, + Z3_OP_SGT, + + Z3_OP_BAND, + Z3_OP_BOR, + Z3_OP_BNOT, + Z3_OP_BXOR, + Z3_OP_BNAND, + Z3_OP_BNOR, + Z3_OP_BXNOR, + + Z3_OP_CONCAT, + Z3_OP_SIGN_EXT, + Z3_OP_ZERO_EXT, + Z3_OP_EXTRACT, + Z3_OP_REPEAT, + + Z3_OP_BREDOR, + Z3_OP_BREDAND, + Z3_OP_BCOMP, + + Z3_OP_BSHL, + Z3_OP_BLSHR, + Z3_OP_BASHR, + Z3_OP_ROTATE_LEFT, + Z3_OP_ROTATE_RIGHT, + Z3_OP_EXT_ROTATE_LEFT, + Z3_OP_EXT_ROTATE_RIGHT, + + Z3_OP_INT2BV, + Z3_OP_BV2INT, + Z3_OP_CARRY, + Z3_OP_XOR3, + + // Proofs + Z3_OP_PR_UNDEF = 0x500, + Z3_OP_PR_TRUE, + Z3_OP_PR_ASSERTED, + Z3_OP_PR_GOAL, + Z3_OP_PR_MODUS_PONENS, + Z3_OP_PR_REFLEXIVITY, + Z3_OP_PR_SYMMETRY, + Z3_OP_PR_TRANSITIVITY, + Z3_OP_PR_TRANSITIVITY_STAR, + Z3_OP_PR_MONOTONICITY, + Z3_OP_PR_QUANT_INTRO, + Z3_OP_PR_DISTRIBUTIVITY, + Z3_OP_PR_AND_ELIM, + Z3_OP_PR_NOT_OR_ELIM, + Z3_OP_PR_REWRITE, + Z3_OP_PR_REWRITE_STAR, + Z3_OP_PR_PULL_QUANT, + Z3_OP_PR_PULL_QUANT_STAR, + Z3_OP_PR_PUSH_QUANT, + Z3_OP_PR_ELIM_UNUSED_VARS, + Z3_OP_PR_DER, + Z3_OP_PR_QUANT_INST, + Z3_OP_PR_HYPOTHESIS, + Z3_OP_PR_LEMMA, + Z3_OP_PR_UNIT_RESOLUTION, + Z3_OP_PR_IFF_TRUE, + Z3_OP_PR_IFF_FALSE, + Z3_OP_PR_COMMUTATIVITY, + Z3_OP_PR_DEF_AXIOM, + Z3_OP_PR_DEF_INTRO, + Z3_OP_PR_APPLY_DEF, + Z3_OP_PR_IFF_OEQ, + Z3_OP_PR_NNF_POS, + Z3_OP_PR_NNF_NEG, + Z3_OP_PR_NNF_STAR, + Z3_OP_PR_CNF_STAR, + Z3_OP_PR_SKOLEMIZE, + Z3_OP_PR_MODUS_PONENS_OEQ, + Z3_OP_PR_TH_LEMMA, + + // Sequences + Z3_OP_RA_STORE = 0x600, + Z3_OP_RA_EMPTY, + Z3_OP_RA_IS_EMPTY, + Z3_OP_RA_JOIN, + Z3_OP_RA_UNION, + Z3_OP_RA_WIDEN, + Z3_OP_RA_PROJECT, + Z3_OP_RA_FILTER, + Z3_OP_RA_NEGATION_FILTER, + Z3_OP_RA_RENAME, + Z3_OP_RA_COMPLEMENT, + Z3_OP_RA_SELECT, + Z3_OP_RA_CLONE, + Z3_OP_FD_LT, + + // Auxiliary + Z3_OP_LABEL = 0x700, + Z3_OP_LABEL_LIT, + + // Datatypes + Z3_OP_DT_CONSTRUCTOR=0x800, + Z3_OP_DT_RECOGNISER, + Z3_OP_DT_ACCESSOR, + + Z3_OP_UNINTERPRETED +} Z3_decl_kind; + +/** + \mlonly {!param_kind} \endmlonly \conly \brief + + The different kinds of parameters that can be associated with parameter sets. + (see #Z3_mk_params). + + - Z3_PK_UINT integer parameters. + - Z3_PK_BOOL boolean parameters. + - Z3_PK_DOUBLE double parameters. + - Z3_PK_SYMBOL symbol parameters. + - Z3_PK_STRING string parameters. + - Z3_PK_OTHER all internal parameter kinds which are not exposed in the API. + - Z3_PK_INVALID invalid parameter. +*/ +typedef enum { + Z3_PK_UINT, + Z3_PK_BOOL, + Z3_PK_DOUBLE, + Z3_PK_SYMBOL, + Z3_PK_STRING, + Z3_PK_OTHER, + Z3_PK_INVALID +} Z3_param_kind; + +/** + \mlonly {!search_failure} \endmlonly \conly \brief + The different kinds of search failure types. + + - Z3_NO_FAILURE: The last search was successful + - Z3_UNKNOWN: Undocumented failure reason + - Z3_TIMEOUT: Timeout + - Z3_MEMOUT_WATERMAK: Search hit a memory high-watermak limit + - Z3_CANCELED: External cancel flag was set + - Z3_NUM_CONFLICTS: Maximum number of conflicts was reached + - Z3_THEORY: Theory is incomplete + - Z3_QUANTIFIERS: Logical context contains universal quantifiers +*/ +typedef enum { + Z3_NO_FAILURE, + Z3_UNKNOWN, + Z3_TIMEOUT, + Z3_MEMOUT_WATERMARK, + Z3_CANCELED, + Z3_NUM_CONFLICTS, + Z3_THEORY, + Z3_QUANTIFIERS +} Z3_search_failure; + +/** + \mlonly {!ast_print_mode} \endmlonly \conly \brief + Z3 pretty printing modes (See #Z3_set_ast_print_mode). + + - Z3_PRINT_SMTLIB_FULL: Print AST nodes in SMTLIB verbose format. + - Z3_PRINT_LOW_LEVEL: Print AST nodes using a low-level format. + - Z3_PRINT_SMTLIB_COMPLIANT: Print AST nodes in SMTLIB 1.x compliant format. + - Z3_PRINT_SMTLIB2_COMPLIANT: Print AST nodes in SMTLIB 2.x compliant format. +*/ +typedef enum { + Z3_PRINT_SMTLIB_FULL, + Z3_PRINT_LOW_LEVEL, + Z3_PRINT_SMTLIB_COMPLIANT, + Z3_PRINT_SMTLIB2_COMPLIANT +} Z3_ast_print_mode; + + +#ifdef CorML4 +/** + \mlonly {!error_code} \endmlonly \conly \brief + Z3 error codes \conly (See #Z3_get_error_code). + + - Z3_OK: No error. + - Z3_SORT_ERROR: User tried to build an invalid (type incorrect) AST. + - Z3_IOB: Index out of bounds. + - Z3_INVALID_ARG: Invalid argument was provided. + - Z3_PARSER_ERROR: An error occurred when parsing a string or file. + - Z3_NO_PARSER: Parser output is not available, that is, user didn't invoke #Z3_parse_smtlib_string or #Z3_parse_smtlib_file. + - Z3_INVALID_PATTERN: Invalid pattern was used to build a quantifier. + - Z3_MEMOUT_FAIL: A memory allocation failure was encountered. + - Z3_FILE_ACCESS_ERRROR: A file could not be accessed. + - Z3_INVALID_USAGE: API call is invalid in the current state. + - Z3_INTERNAL_FATAL: An error internal to Z3 occurred. + - Z3_DEC_REF_ERROR: Trying to decrement the reference counter of an AST that was deleted or the reference counter was not initialized\mlonly.\endmlonly\conly with #Z3_inc_ref. + - Z3_EXCEPTION: Internal Z3 exception. Additional details can be retrieved using \mlonly #Z3_get_error_msg. \endmlonly \conly #Z3_get_error_msg_ex. +*/ +typedef enum +{ + Z3_OK, + Z3_SORT_ERROR, + Z3_IOB, + Z3_INVALID_ARG, + Z3_PARSER_ERROR, + Z3_NO_PARSER, + Z3_INVALID_PATTERN, + Z3_MEMOUT_FAIL, + Z3_FILE_ACCESS_ERROR, + Z3_INTERNAL_FATAL, + Z3_INVALID_USAGE, + Z3_DEC_REF_ERROR, + Z3_EXCEPTION +} Z3_error_code; + +#endif + +#ifdef Conly +/** + \brief Z3 custom error handler (See #Z3_set_error_handler). +*/ +typedef void Z3_error_handler(Z3_context c, Z3_error_code e); + +#endif +#ifdef ML4only +#include +#endif + + +#ifdef CorML4 +/** + \mlonly {!goal_prec} \endmlonly \conly \brief + A Goal is essentially a set of formulas. Z3 provide APIs for building strategies/tactics for solving and transforming Goals. Some of these transformations apply under/over approximations. + + - Z3_GOAL_PRECISE: Approximations/Relaxations were not applied on the goal (sat and unsat answers were preserved). + - Z3_GOAL_UNDER: Goal is the product of a under-approximation (sat answers are preserved). + - Z3_GOAL_OVER: Goal is the product of an over-approximation (unsat answers are preserved). + - Z3_GOAL_UNDER_OVER: Goal is garbage (it is the product of over- and under-approximations, sat and unsat answers are not preserved). +*/ +typedef enum +{ + Z3_GOAL_PRECISE, + Z3_GOAL_UNDER, + Z3_GOAL_OVER, + Z3_GOAL_UNDER_OVER +} Z3_goal_prec; + +#endif + +/*@}*/ + +#ifndef CAMLIDL +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus +#else +[pointer_default(ref)] interface Z3 { +#endif // CAMLIDL + +#ifdef CorML3 + /** + @name Create configuration + */ + /*@{*/ + + /** + \brief Create a configuration. + + Configurations are created in order to assign parameters prior to creating + contexts for Z3 interaction. For example, if the users wishes to use model + generation, then call: + + \ccode{Z3_set_param_value(cfg\, "MODEL"\, "true")} + + \mlonly \remark Consider using {!mk_context_x} instead of using + explicit configuration objects. The function {!mk_context_x} + receives an array of string pairs. This array represents the + configuration options. \endmlonly + + \sa Z3_set_param_value + \sa Z3_del_config + */ + Z3_config Z3_API Z3_mk_config(); + + /** + \brief Delete the given configuration object. + + \sa Z3_mk_config + */ + void Z3_API Z3_del_config(__in Z3_config c); + + /** + \brief Set a configuration parameter. + + The list of all configuration parameters can be obtained using the Z3 executable: + + \verbatim + z3.exe -ini? + \endverbatim + + \sa Z3_mk_config + */ + void Z3_API Z3_set_param_value(__in Z3_config c, __in Z3_string param_id, __in Z3_string param_value); + + /*@}*/ +#endif + + /** + @name Create context + */ + /*@{*/ + + /** + \brief Create a context using the given configuration. + + After a context is created, the configuration cannot be changed, + although some parameters can be changed using #Z3_update_param_value. + All main interaction with Z3 happens in the context of a \c Z3_context. + + \conly \sa Z3_del_context + + \conly \deprecated Use #Z3_mk_context_rc + */ +#ifdef CorML3 + Z3_context Z3_API Z3_mk_context(__in Z3_config c); +#endif +#ifdef ML4only +#include +#endif + +#ifdef Conly + /** + \brief Create a context using the given configuration. + This function is similar to #Z3_mk_context. However, + in the context returned by this function, the user + is responsible for managing Z3_ast reference counters. + Managing reference counters is a burden and error-prone, + but allows the user to use the memory more efficiently. + The user must invoke #Z3_inc_ref for any Z3_ast returned + by Z3, and #Z3_dec_ref whenever the Z3_ast is not needed + anymore. This idiom is similar to the one used in + BDD (binary decision diagrams) packages such as CUDD. + + Remark: Z3_sort, Z3_func_decl, Z3_app, Z3_pattern are + Z3_ast's. + + After a context is created, the configuration cannot be changed. + All main interaction with Z3 happens in the context of a \c Z3_context. + */ + Z3_context Z3_API Z3_mk_context_rc(__in Z3_config c); +#endif + +#ifdef CorML3 + /** + \brief Delete the given logical context. + + \sa Z3_mk_context + */ + void Z3_API Z3_del_context(__in Z3_context c); +#endif + +#ifdef Conly + /** + \brief Increment the reference counter of the given AST. + The context \c c should have been created using #Z3_mk_context_rc. + This function is a NOOP if \c c was created using #Z3_mk_context. + */ + void Z3_API Z3_inc_ref(__in Z3_context c, __in Z3_ast a); + + /** + \brief Decrement the reference counter of the given AST. + The context \c c should have been created using #Z3_mk_context_rc. + This function is a NOOP if \c c was created using #Z3_mk_context. + */ + void Z3_API Z3_dec_ref(__in Z3_context c, __in Z3_ast a); +#endif + + /** + \brief Update a mutable configuration parameter. + + The list of all configuration parameters can be obtained using the Z3 executable: + + \verbatim + z3.exe -ini? + \endverbatim + + Only a few configuration parameters are mutable once the context is created. + The error handler is invoked when trying to modify an immutable parameter. + + \conly \sa Z3_set_param_value + \mlonly \sa Z3_mk_context \endmlonly + */ + void Z3_API Z3_update_param_value(__in Z3_context c, __in Z3_string param_id, __in Z3_string param_value); + + /** + \brief Get a configuration parameter. + + Returns \mlonly \c None \endmlonly \conly \c Z3_FALSE + if the parameter value does not exist. + + \conly \sa Z3_mk_config + \conly \sa Z3_set_param_value + \mlonly \sa Z3_mk_context \endmlonly + */ + Z3_bool_opt Z3_API Z3_get_param_value(__in Z3_context c, __in Z3_string param_id, __out_opt Z3_string_ptr param_value); + +#ifdef CorML4 + /** + \brief Interrupt the execution of a Z3 procedure. + This procedure can be used to interrupt: solvers, simplifiers and tactics. + */ + void Z3_API Z3_interrupt(__in Z3_context c); +#endif + + + /*@}*/ + +#ifdef CorML4 + /** + @name Parameters + */ + /*@{*/ + + /** + \brief Create a Z3 (empty) parameter set. + Starting at Z3 4.0, parameter sets are used to configure many components such as: + simplifiers, tactics, solvers, etc. + + \conly \remark Reference counting must be used to manage parameter sets, even when the Z3_context was + \conly created using #Z3_mk_context instead of #Z3_mk_context_rc. + */ + Z3_params Z3_API Z3_mk_params(__in Z3_context c); + +#ifdef Conly + /** + \brief Increment the reference counter of the given parameter set. + */ + void Z3_API Z3_params_inc_ref(__in Z3_context c, __in Z3_params p); + + /** + \brief Decrement the reference counter of the given parameter set. + */ + void Z3_API Z3_params_dec_ref(__in Z3_context c, __in Z3_params p); +#endif + + /** + \brief Add a Boolean parameter \c k with value \c v to the parameter set \c p. + */ + void Z3_API Z3_params_set_bool(__in Z3_context c, __in Z3_params p, __in Z3_symbol k, __in Z3_bool v); + + /** + \brief Add a unsigned parameter \c k with value \c v to the parameter set \c p. + */ + void Z3_API Z3_params_set_uint(__in Z3_context c, __in Z3_params p, __in Z3_symbol k, __in unsigned v); + + /** + \brief Add a double parameter \c k with value \c v to the parameter set \c p. + */ + void Z3_API Z3_params_set_double(__in Z3_context c, __in Z3_params p, __in Z3_symbol k, __in double v); + + /** + \brief Add a symbol parameter \c k with value \c v to the parameter set \c p. + */ + void Z3_API Z3_params_set_symbol(__in Z3_context c, __in Z3_params p, __in Z3_symbol k, __in Z3_symbol v); + + /** + \brief Convert a parameter set into a string. This function is mainly used for printing the + contents of a parameter set. + */ + Z3_string Z3_API Z3_params_to_string(__in Z3_context c, __in Z3_params p); + + /** + \brief Validate the parameter set \c p against the parameter description set \c d. + + The procedure invokes the error handler if \c p is invalid. + */ + void Z3_API Z3_params_validate(__in Z3_context c, __in Z3_params p, __in Z3_param_descrs d); + +#endif + + /*@}*/ + +#ifdef CorML4 + /** + @name Parameter Descriptions + */ + /*@{*/ + +#ifdef Conly + /** + \brief Increment the reference counter of the given parameter description set. + */ + void Z3_API Z3_param_descrs_inc_ref(__in Z3_context c, __in Z3_param_descrs p); + + /** + \brief Decrement the reference counter of the given parameter description set. + */ + void Z3_API Z3_param_descrs_dec_ref(__in Z3_context c, __in Z3_param_descrs p); +#endif + + /** + \brief Return the kind associated with the given parameter name \c n. + */ + Z3_param_kind Z3_API Z3_param_descrs_get_kind(__in Z3_context c, __in Z3_param_descrs p, __in Z3_symbol n); + + /** + \brief Return the number of parameters in the given parameter description set. + */ + unsigned Z3_API Z3_param_descrs_size(__in Z3_context c, __in Z3_param_descrs p); + + /** + \brief Return the number of parameters in the given parameter description set. + + \pre i < Z3_param_descrs_size(c, p) + */ + Z3_symbol Z3_API Z3_param_descrs_get_name(__in Z3_context c, __in Z3_param_descrs p, __in unsigned i); + + /*@}*/ +#endif + + /** + @name Symbols + */ + /*@{*/ + +#ifdef ML4only +#include +#endif + + /** + \mlonly {4 {L Redundant low-level API}} \endmlonly + */ + + /** + \brief Create a Z3 symbol using an integer. + + Symbols are used to name several term and type constructors. + + NB. Not all integers can be passed to this function. + The legal range of unsigned integers is 0 to 2^30-1. + + \sa Z3_mk_string_symbol + */ + Z3_symbol Z3_API Z3_mk_int_symbol(__in Z3_context c, __in int i); + + /** + \brief Create a Z3 symbol using a C string. + + Symbols are used to name several term and type constructors. + + \sa Z3_mk_int_symbol + */ + Z3_symbol Z3_API Z3_mk_string_symbol(__in Z3_context c, __in Z3_string s); + + /*@}*/ + + /** + @name Sorts + */ + /*@{*/ + +#ifdef ML4only +#include +#endif + + /** + \mlonly {4 {L Redundant low-level API}} \endmlonly + */ + + /** + \brief Create a free (uninterpreted) type using the given name (symbol). + + Two free types are considered the same iff the have the same name. + */ + Z3_sort Z3_API Z3_mk_uninterpreted_sort(__in Z3_context c, __in Z3_symbol s); + + + /** + \brief Create the Boolean type. + + This type is used to create propositional variables and predicates. + */ + Z3_sort Z3_API Z3_mk_bool_sort(__in Z3_context c); + + /** + \brief Create the integer type. + + This type is not the int type found in programming languages. + A machine integer can be represented using bit-vectors. The function + #Z3_mk_bv_sort creates a bit-vector type. + + \sa Z3_mk_bv_sort + */ + Z3_sort Z3_API Z3_mk_int_sort(__in Z3_context c); + + /** + \brief Create the real type. + + This type is not a floating point number. + Z3 does not have support for floating point numbers yet. + */ + Z3_sort Z3_API Z3_mk_real_sort(__in Z3_context c); + + /** + \brief Create a bit-vector type of the given size. + + This type can also be seen as a machine integer. + + \remark The size of the bitvector type must be greater than zero. + */ + Z3_sort Z3_API Z3_mk_bv_sort(__in Z3_context c, __in unsigned sz); + + /** + \brief Create a named finite domain sort. + + To create constants that belong to the finite domain, + use the APIs for creating numerals and pass a numeric + constant together with the sort returned by this call. + + \sa Z3_get_finite_domain_sort_size. + */ + Z3_sort Z3_API Z3_mk_finite_domain_sort(__in Z3_context c, __in Z3_symbol name, __in unsigned __int64 size); + + /** + \brief Create an array type. + + We usually represent the array type as: [domain -> range]. + Arrays are usually used to model the heap/memory in software verification. + + \sa Z3_mk_select + \sa Z3_mk_store + */ + Z3_sort Z3_API Z3_mk_array_sort(__in Z3_context c, __in Z3_sort domain, __in Z3_sort range); + + /** + \brief Create a tuple type. + + \mlonly [mk_tuple_sort c name field_names field_sorts] creates a tuple with a constructor named [name], + a [n] fields, where [n] is the size of the arrays [field_names] and [field_sorts]. + \endmlonly + + \conly A tuple with \c n fields has a constructor and \c n projections. + \conly This function will also declare the constructor and projection functions. + + \param c logical context + \param mk_tuple_name name of the constructor function associated with the tuple type. + \param num_fields number of fields in the tuple type. + \param field_names name of the projection functions. + \param field_sorts type of the tuple fields. + \param mk_tuple_decl output parameter that will contain the constructor declaration. + \param proj_decl output parameter that will contain the projection function declarations. This field must be a buffer of size \c num_fields allocated by the user. + */ + Z3_sort Z3_API Z3_mk_tuple_sort(__in Z3_context c, + __in Z3_symbol mk_tuple_name, + __in unsigned num_fields, + __in_ecount(num_fields) Z3_symbol const field_names[], + __in_ecount(num_fields) Z3_sort const field_sorts[], + __out Z3_func_decl * mk_tuple_decl, + __out_ecount(num_fields) Z3_func_decl proj_decl[]); + + /** + \brief Create a enumeration sort. + + \mlonly [mk_enumeration_sort c enums] creates an enumeration sort with enumeration names [enums], + it also returns [n] predicates, where [n] is the number of [enums] corresponding + to testing whether an element is one of the enumerants. + \endmlonly + + \conly An enumeration sort with \c n elements. + \conly This function will also declare the functions corresponding to the enumerations. + + \param c logical context + \param name name of the enumeration sort. + \param n number of elemenets in enumeration sort. + \param enum_names names of the enumerated elements. + \param enum_consts constants corresponding to the enumerated elements. + \param enum_testers predicates testing if terms of the enumeration sort correspond to an enumeration. + + For example, if this function is called with three symbols A, B, C and the name S, then + \c s is a sort whose name is S, and the function returns three terms corresponding to A, B, C in + \c enum_consts. The array \c enum_testers has three predicates of type (s -> Bool). + The first predicate (corresponding to A) is true when applied to A, and false otherwise. + Similarly for the other predicates. + */ + Z3_sort Z3_API Z3_mk_enumeration_sort(__in Z3_context c, + __in Z3_symbol name, + __in unsigned n, + __in_ecount(n) Z3_symbol const enum_names[], + __out_ecount(n) Z3_func_decl enum_consts[], + __out_ecount(n) Z3_func_decl enum_testers[]); + + /** + \brief Create a list sort + + \mlonly [mk_list_sort c name elem_sort] creates a list sort of [name], over elements of sort [elem_sort]. + \endmlonly + + \conly A list sort over \c elem_sort + \conly This function declares the corresponding constructors and testers for lists. + + \param c logical context + \param name name of the list sort. + \param elem_sort sort of list elements. + \param nil_decl declaration for the empty list. + \param is_nil_decl test for the empty list. + \param cons_decl declaration for a cons cell. + \param is_cons_decl cons cell test. + \param head_decl list head. + \param tail_decl list tail. + */ + Z3_sort Z3_API Z3_mk_list_sort(__in Z3_context c, + __in Z3_symbol name, + __in Z3_sort elem_sort, + __out Z3_func_decl* nil_decl, + __out Z3_func_decl* is_nil_decl, + __out Z3_func_decl* cons_decl, + __out Z3_func_decl* is_cons_decl, + __out Z3_func_decl* head_decl, + __out Z3_func_decl* tail_decl + ); + +BEGIN_MLAPI_EXCLUDE + /** + \brief Create a constructor. + + \param c logical context. + \param name constructor name. + \param recognizer name of recognizer function. + \param num_fields number of fields in constructor. + \param field_names names of the constructor fields. + \param sorts field sorts, \mlonly [None] \endmlonly \conly 0 + if the field sort refers to a recursive sort. + \param sort_refs reference to datatype sort that is an argument to the constructor; if the corresponding + sort reference is \mlonly [None], \endmlonly \conly 0, + then the value in sort_refs should be an index referring to + one of the recursive datatypes that is declared. + */ + Z3_constructor Z3_API Z3_mk_constructor(__in Z3_context c, + __in Z3_symbol name, + __in Z3_symbol recognizer, + __in unsigned num_fields, + __in_ecount(num_fields) Z3_symbol const field_names[], + __in_ecount(num_fields) Z3_sort_opt const sorts[], + __in_ecount(num_fields) unsigned sort_refs[] + ); + + /** + \brief Reclaim memory allocated to constructor. + + \param c logical context. + \param constr constructor. + */ + void Z3_API Z3_del_constructor(__in Z3_context c, __in Z3_constructor constr); + + /** + \brief Create datatype, such as lists, trees, records, enumerations or unions of records. + The datatype may be recursive. Return the datatype sort. + + \param c logical context. + \param name name of datatype. + \param num_constructors number of constructors passed in. + \param constructors array of constructor containers. + */ + Z3_sort Z3_API Z3_mk_datatype(__in Z3_context c, + __in Z3_symbol name, + __in unsigned num_constructors, + __inout_ecount(num_constructors) Z3_constructor constructors[]); + + + /** + \brief Create list of constructors. + + \param c logical context. + \param num_constructors number of constructors in list. + \param constructors list of constructors. + */ + Z3_constructor_list Z3_API Z3_mk_constructor_list(__in Z3_context c, + __in unsigned num_constructors, + __in_ecount(num_constructors) Z3_constructor const constructors[]); + + /** + \brief Reclaim memory allocated for constructor list. + + Each constructor inside the constructor list must be independently reclaimed using #Z3_del_constructor. + + \param c logical context. + \param clist constructor list container. + + */ + void Z3_API Z3_del_constructor_list(__in Z3_context c, __in Z3_constructor_list clist); + + /** + \brief Create mutually recursive datatypes. + + \param c logical context. + \param num_sorts number of datatype sorts. + \param sort_names names of datatype sorts. + \param sorts array of datattype sorts. + \param constructor_lists list of constructors, one list per sort. + */ + void Z3_API Z3_mk_datatypes(__in Z3_context c, + __in unsigned num_sorts, + __in_ecount(num_sorts) Z3_symbol const sort_names[], + __out_ecount(num_sorts) Z3_sort sorts[], + __inout_ecount(num_sorts) Z3_constructor_list constructor_lists[]); + + /** + \brief Query constructor for declared functions. + + \param c logical context. + \param constr constructor container. The container must have been passed in to a #Z3_mk_datatype call. + \param num_fields number of accessor fields in the constructor. + \param constructor constructor function declaration. + \param tester constructor test function declaration. + \param accessors array of accessor function declarations. + */ + void Z3_API Z3_query_constructor(__in Z3_context c, + __in Z3_constructor constr, + __in unsigned num_fields, + __out Z3_func_decl* constructor, + __out Z3_func_decl* tester, + __out_ecount(num_fields) Z3_func_decl accessors[]); +END_MLAPI_EXCLUDE + + /*@}*/ + + /** + @name Constants and Applications + */ + /*@{*/ + + /** + \brief Declare a constant or function. + + \mlonly [mk_func_decl c n d r] creates a function with name [n], domain [d], and range [r]. + The arity of the function is the size of the array [d]. \endmlonly + + \param c logical context. + \param s name of the constant or function. + \param domain_size number of arguments. It is 0 when declaring a constant. + \param domain array containing the sort of each argument. The array must contain domain_size elements. It is 0 when declaring a constant. + \param range sort of the constant or the return sort of the function. + + After declaring a constant or function, the function + #Z3_mk_app can be used to create a constant or function + application. + + \sa Z3_mk_app + */ + Z3_func_decl Z3_API Z3_mk_func_decl(__in Z3_context c, __in Z3_symbol s, + __in unsigned domain_size, __in_ecount(domain_size) Z3_sort const domain[], + __in Z3_sort range); + + + /** + \brief Create a constant or function application. + + \sa Z3_mk_func_decl + */ + Z3_ast Z3_API Z3_mk_app( + __in Z3_context c, + __in Z3_func_decl d, + __in unsigned num_args, + __in_ecount(num_args) Z3_ast const args[]); + + /** + \brief Declare and create a constant. + + \conly This function is a shorthand for: + \conly \code + \conly Z3_func_decl d = Z3_mk_func_decl(c, s, 0, 0, ty); + \conly Z3_ast n = Z3_mk_app(c, d, 0, 0); + \conly \endcode + + \mlonly [mk_const c s t] is a shorthand for [mk_app c (mk_func_decl c s [||] t) [||]] \endmlonly + + \sa Z3_mk_func_decl + \sa Z3_mk_app + */ + Z3_ast Z3_API Z3_mk_const(__in Z3_context c, __in Z3_symbol s, __in Z3_sort ty); + + /** + \brief Declare a fresh constant or function. + + Z3 will generate an unique name for this function declaration. + \conly If prefix is different from \c NULL, then the name generate by Z3 will start with \c prefix. + + \conly \remark If \c prefix is \c NULL, then it is assumed to be the empty string. + + \sa Z3_mk_func_decl + */ + Z3_func_decl Z3_API Z3_mk_fresh_func_decl(__in Z3_context c, __in Z3_string prefix, + __in unsigned domain_size, __in_ecount(domain_size) Z3_sort const domain[], + __in Z3_sort range); + + /** + \brief Declare and create a fresh constant. + + \conly This function is a shorthand for: + \conly \code Z3_func_decl d = Z3_mk_fresh_func_decl(c, prefix, 0, 0, ty); Z3_ast n = Z3_mk_app(c, d, 0, 0); \endcode + + \mlonly [mk_fresh_const c p t] is a shorthand for [mk_app c (mk_fresh_func_decl c p [||] t) [||]]. \endmlonly + + \conly \remark If \c prefix is \c NULL, then it is assumed to be the empty string. + + \sa Z3_mk_func_decl + \sa Z3_mk_app + */ + Z3_ast Z3_API Z3_mk_fresh_const(__in Z3_context c, __in Z3_string prefix, __in Z3_sort ty); + /*@}*/ + + /** + @name Propositional Logic and Equality + */ + /*@{*/ + /** + \brief Create an AST node representing \c true. + */ + Z3_ast Z3_API Z3_mk_true(__in Z3_context c); + + /** + \brief Create an AST node representing \c false. + */ + Z3_ast Z3_API Z3_mk_false(__in Z3_context c); + + /** + \brief \mlh mk_eq c l r \endmlh + Create an AST node representing l = r. + + The nodes \c l and \c r must have the same type. + */ + Z3_ast Z3_API Z3_mk_eq(__in Z3_context c, __in Z3_ast l, __in Z3_ast r); + + /** + \conly \brief Create an AST node representing distinct(args[0], ..., args[num_args-1]). + \mlonly \brief \[ [mk_distinct c [| t_1; ...; t_n |]] \] Create an AST + node represeting a distinct construct. It is used for declaring + the arguments t_i pairwise distinct. \endmlonly + + The \c distinct construct is used for declaring the arguments pairwise distinct. + That is, Forall 0 <= i < j < num_args. not args[i] = args[j]. + + All arguments must have the same sort. + + \remark The number of arguments of a distinct construct must be greater than one. + */ + Z3_ast Z3_API Z3_mk_distinct(__in Z3_context c, __in unsigned num_args, __in_ecount(num_args) Z3_ast const args[]); + + /** + \brief \mlh mk_not c a \endmlh + Create an AST node representing not(a). + + The node \c a must have Boolean sort. + */ + Z3_ast Z3_API Z3_mk_not(__in Z3_context c, __in Z3_ast a); + + /** + \brief \mlh mk_ite c t1 t2 t2 \endmlh + Create an AST node representing an if-then-else: ite(t1, t2, + t3). + + The node \c t1 must have Boolean sort, \c t2 and \c t3 must have the same sort. + The sort of the new node is equal to the sort of \c t2 and \c t3. + */ + Z3_ast Z3_API Z3_mk_ite(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2, __in Z3_ast t3); + + /** + \brief \mlh mk_iff c t1 t2 \endmlh + Create an AST node representing t1 iff t2. + + The nodes \c t1 and \c t2 must have Boolean sort. + */ + Z3_ast Z3_API Z3_mk_iff(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_implies c t1 t2 \endmlh + Create an AST node representing t1 implies t2. + + The nodes \c t1 and \c t2 must have Boolean sort. + */ + Z3_ast Z3_API Z3_mk_implies(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_xor c t1 t2 \endmlh + Create an AST node representing t1 xor t2. + + The nodes \c t1 and \c t2 must have Boolean sort. + */ + Z3_ast Z3_API Z3_mk_xor(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \conly \brief Create an AST node representing args[0] and ... and args[num_args-1]. + \mlonly \brief \[ [mk_and c [| t_1; ...; t_n |]] \] Create the conjunction: {e t_1 and ... and t_n}. \endmlonly + + \conly The array \c args must have \c num_args elements. + All arguments must have Boolean sort. + + \remark The number of arguments must be greater than zero. + */ + Z3_ast Z3_API Z3_mk_and(__in Z3_context c, __in unsigned num_args, __in_ecount(num_args) Z3_ast const args[]); + + /** + \conly \brief Create an AST node representing args[0] or ... or args[num_args-1]. + \mlonly \brief \[ [mk_or c [| t_1; ...; t_n |]] \] Create the disjunction: {e t_1 or ... or t_n}. \endmlonly + + \conly The array \c args must have \c num_args elements. + All arguments must have Boolean sort. + + \remark The number of arguments must be greater than zero. + */ + Z3_ast Z3_API Z3_mk_or(__in Z3_context c, __in unsigned num_args, __in_ecount(num_args) Z3_ast const args[]); + /*@}*/ + + /** + @name Arithmetic: Integers and Reals + */ + /*@{*/ + /** + \conly \brief Create an AST node representing args[0] + ... + args[num_args-1]. + \mlonly \brief \[ [mk_add c [| t_1; ...; t_n |]] \] Create the term: {e t_1 + ... + t_n}. \endmlonly + + \conly The array \c args must have \c num_args elements. + All arguments must have int or real sort. + + \remark The number of arguments must be greater than zero. + */ + Z3_ast Z3_API Z3_mk_add(__in Z3_context c, __in unsigned num_args, __in_ecount(num_args) Z3_ast const args[]); + + /** + \conly \brief Create an AST node representing args[0] * ... * args[num_args-1]. + \mlonly \brief \[ [mk_mul c [| t_1; ...; t_n |]] \] Create the term: {e t_1 * ... * t_n}. \endmlonly + + \conly The array \c args must have \c num_args elements. + All arguments must have int or real sort. + + \remark Z3 has limited support for non-linear arithmetic. + \remark The number of arguments must be greater than zero. + */ + Z3_ast Z3_API Z3_mk_mul(__in Z3_context c, __in unsigned num_args, __in_ecount(num_args) Z3_ast const args[]); + + /** + \conly \brief Create an AST node representing args[0] - ... - args[num_args - 1]. + \mlonly \brief \[ [mk_sub c [| t_1; ...; t_n |]] \] Create the term: {e t_1 - ... - t_n}. \endmlonly + + \conly The array \c args must have \c num_args elements. + All arguments must have int or real sort. + + \remark The number of arguments must be greater than zero. + */ + Z3_ast Z3_API Z3_mk_sub(__in Z3_context c, __in unsigned num_args, __in_ecount(num_args) Z3_ast const args[]); + + /** + \conly \brief Create an AST node representing -arg. + \mlonly \brief \[ [mk_unary_minus c arg] \] Create the term: {e - arg}. \endmlonly + + The arguments must have int or real type. + */ + Z3_ast Z3_API Z3_mk_unary_minus(__in Z3_context c, __in Z3_ast arg); + + /** + \conly \brief Create an AST node representing arg1 div arg2. + \mlonly \brief \[ [mk_div c t_1 t_2] \] Create the term: {e t_1 div t_2}. \endmlonly + + The arguments must either both have int type or both have real type. + If the arguments have int type, then the result type is an int type, otherwise the + the result type is real. + + */ + Z3_ast Z3_API Z3_mk_div(__in Z3_context c, __in Z3_ast arg1, __in Z3_ast arg2); + + /** + \conly \brief Create an AST node representing arg1 mod arg2. + \mlonly \brief \[ [mk_mod c t_1 t_2] \] Create the term: {e t_1 mod t_2}. \endmlonly + + The arguments must have int type. + + */ + Z3_ast Z3_API Z3_mk_mod(__in Z3_context c, __in Z3_ast arg1, __in Z3_ast arg2); + + /** + \conly \brief Create an AST node representing arg1 rem arg2. + \mlonly \brief \[ [mk_rem c t_1 t_2] \] Create the term: {e t_1 rem t_2}. \endmlonly + + The arguments must have int type. + + */ + Z3_ast Z3_API Z3_mk_rem(__in Z3_context c, __in Z3_ast arg1, __in Z3_ast arg2); + + /** + \conly \brief Create an AST node representing arg1^arg2. + + The arguments must have int or real type. + */ + Z3_ast Z3_API Z3_mk_power(__in Z3_context c, __in Z3_ast arg1, __in Z3_ast arg2); + + /** + \brief \mlh mk_lt c t1 t2 \endmlh + Create less than. + + The nodes \c t1 and \c t2 must have the same sort, and must be int or real. + */ + Z3_ast Z3_API Z3_mk_lt(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_le c t1 t2 \endmlh + Create less than or equal to. + + The nodes \c t1 and \c t2 must have the same sort, and must be int or real. + */ + Z3_ast Z3_API Z3_mk_le(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_gt c t1 t2 \endmlh + Create greater than. + + The nodes \c t1 and \c t2 must have the same sort, and must be int or real. + */ + Z3_ast Z3_API Z3_mk_gt(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_ge c t1 t2 \endmlh + Create greater than or equal to. + + The nodes \c t1 and \c t2 must have the same sort, and must be int or real. + */ + Z3_ast Z3_API Z3_mk_ge(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_int2real c t1 \endmlh + Coerce an integer to a real. + + There is also a converse operation exposed. + It follows the semantics prescribed by the SMT-LIB standard. + + You can take the floor of a real by + creating an auxiliary integer constant \c k and + and asserting mk_int2real(k) <= t1 < mk_int2real(k)+1. + + The node \c t1 must have sort integer. + + \sa Z3_mk_real2int + \sa Z3_mk_is_int + */ + Z3_ast Z3_API Z3_mk_int2real(__in Z3_context c, __in Z3_ast t1); + + /** + \brief \mlh mk_real2int c t1 \endmlh + Coerce a real to an integer. + + The semantics of this function follows the SMT-LIB standard + for the function to_int + + \sa Z3_mk_int2real + \sa Z3_mk_is_int + */ + Z3_ast Z3_API Z3_mk_real2int(__in Z3_context c, __in Z3_ast t1); + + /** + \brief \mlh mk_is_int c t1 \endmlh + Check if a real number is an integer. + + \sa Z3_mk_int2real + \sa Z3_mk_real2int + */ + Z3_ast Z3_API Z3_mk_is_int(__in Z3_context c, __in Z3_ast t1); + /*@}*/ + + /** + @name Bit-vectors + */ + /*@{*/ + /** + \brief \mlh mk_bvnot c t1 \endmlh + Bitwise negation. + + The node \c t1 must have a bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvnot(__in Z3_context c, __in Z3_ast t1); + + /** + \brief \mlh mk_bvredand c t1 \endmlh + Take conjunction of bits in vector, return vector of length 1. + + The node \c t1 must have a bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvredand(__in Z3_context c, __in Z3_ast t1); + + /** + \brief \mlh mk_bvredor c t1 \endmlh + Take disjunction of bits in vector, return vector of length 1. + + The node \c t1 must have a bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvredor(__in Z3_context c, __in Z3_ast t1); + + /** + \brief \mlh mk_bvand c t1 t2 \endmlh + Bitwise and. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvand(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvor c t1 t2 \endmlh + Bitwise or. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvor(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvxor c t1 t2 \endmlh + Bitwise exclusive-or. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvxor(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvnand c t1 t2 \endmlh + Bitwise nand. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvnand(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvnor c t1 t2 \endmlh + Bitwise nor. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvnor(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvxnor c t1 t2 \endmlh + Bitwise xnor. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvxnor(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvneg c t1 \endmlh + Standard two's complement unary minus. + + The node \c t1 must have bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvneg(__in Z3_context c, __in Z3_ast t1); + + /** + \brief \mlh mk_bvadd c t1 t2 \endmlh + Standard two's complement addition. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvadd(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvsub c t1 t2 \endmlh + Standard two's complement subtraction. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvsub(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvmul c t1 t2 \endmlh + Standard two's complement multiplication. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvmul(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvudiv c t1 t2 \endmlh + Unsigned division. + + It is defined as the \c floor of t1/t2 if \c t2 is + different from zero. If t2 is zero, then the result + is undefined. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvudiv(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvsdiv c t1 t2 \endmlh + Two's complement signed division. + + It is defined in the following way: + + - The \c floor of t1/t2 if \c t2 is different from zero, and t1*t2 >= 0. + + - The \c ceiling of t1/t2 if \c t2 is different from zero, and t1*t2 < 0. + + If t2 is zero, then the result is undefined. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvsdiv(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvurem c t1 t2 \endmlh + Unsigned remainder. + + It is defined as t1 - (t1 /u t2) * t2, where /u represents unsigned division. + + If t2 is zero, then the result is undefined. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvurem(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvsrem c t1 t2 \endmlh + Two's complement signed remainder (sign follows dividend). + + It is defined as t1 - (t1 /s t2) * t2, where /s represents signed division. + The most significant bit (sign) of the result is equal to the most significant bit of \c t1. + + If t2 is zero, then the result is undefined. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + + \sa Z3_mk_bvsmod + */ + Z3_ast Z3_API Z3_mk_bvsrem(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvsmod c t1 t2 \endmlh + Two's complement signed remainder (sign follows divisor). + + If t2 is zero, then the result is undefined. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + + \sa Z3_mk_bvsrem + */ + Z3_ast Z3_API Z3_mk_bvsmod(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvult c t1 t2 \endmlh + Unsigned less than. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvult(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvslt c t1 t2 \endmlh + Two's complement signed less than. + + It abbreviates: + \code + (or (and (= (extract[|m-1|:|m-1|] t1) bit1) + (= (extract[|m-1|:|m-1|] t2) bit0)) + (and (= (extract[|m-1|:|m-1|] t1) (extract[|m-1|:|m-1|] t2)) + (bvult t1 t2))) + \endcode + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvslt(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvule c t1 t2 \endmlh + Unsigned less than or equal to. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvule(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvsle c t1 t2 \endmlh + Two's complement signed less than or equal to. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvsle(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvuge c t1 t2 \endmlh + Unsigned greater than or equal to. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvuge(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvsge c t1 t2 \endmlh + Two's complement signed greater than or equal to. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvsge(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvugt c t1 t2 \endmlh + Unsigned greater than. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvugt(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvsgt c t1 t2 \endmlh + Two's complement signed greater than. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvsgt(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_concat c t1 t2 \endmlh + Concatenate the given bit-vectors. + + The nodes \c t1 and \c t2 must have (possibly different) bit-vector sorts + + The result is a bit-vector of size n1+n2, where \c n1 (\c n2) is the size + of \c t1 (\c t2). + */ + Z3_ast Z3_API Z3_mk_concat(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_extract c high low t1 \endmlh + Extract the bits \c high down to \c low from a bitvector of + size \c m to yield a new bitvector of size \c n, where n = + high - low + 1. + + The node \c t1 must have a bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_extract(__in Z3_context c, __in unsigned high, __in unsigned low, __in Z3_ast t1); + + /** + \brief \mlh mk_sign_ext c i t1 \endmlh + Sign-extend of the given bit-vector to the (signed) equivalent bitvector of + size m+i, where \c m is the size of the given + bit-vector. + + The node \c t1 must have a bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_sign_ext(__in Z3_context c, __in unsigned i, __in Z3_ast t1); + + /** + \brief \mlh mk_zero_ext c i t1 \endmlh + Extend the given bit-vector with zeros to the (unsigned) equivalent + bitvector of size m+i, where \c m is the size of the + given bit-vector. + + The node \c t1 must have a bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_zero_ext(__in Z3_context c, __in unsigned i, __in Z3_ast t1); + + /** + \brief \mlh mk_repeat c i t1 \endmlh + Repeat the given bit-vector up length i. + + The node \c t1 must have a bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_repeat(__in Z3_context c, __in unsigned i, __in Z3_ast t1); + + /** + \brief \mlh mk_bvshl c t1 t2 \endmlh + Shift left. + + It is equivalent to multiplication by 2^x where \c x is the value of the + third argument. + + NB. The semantics of shift operations varies between environments. This + definition does not necessarily capture directly the semantics of the + programming language or assembly architecture you are modeling. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvshl(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvlshr c t1 t2 \endmlh + Logical shift right. + + It is equivalent to unsigned division by 2^x where \c x is the + value of the third argument. + + NB. The semantics of shift operations varies between environments. This + definition does not necessarily capture directly the semantics of the + programming language or assembly architecture you are modeling. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvlshr(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvashr c t1 t2 \endmlh + Arithmetic shift right. + + It is like logical shift right except that the most significant + bits of the result always copy the most significant bit of the + second argument. + + NB. The semantics of shift operations varies between environments. This + definition does not necessarily capture directly the semantics of the + programming language or assembly architecture you are modeling. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvashr(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_rotate_left c i t1 \endmlh + Rotate bits of \c t1 to the left \c i times. + + The node \c t1 must have a bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_rotate_left(__in Z3_context c, __in unsigned i, __in Z3_ast t1); + + /** + \brief \mlh mk_rotate_right c i t1 \endmlh + Rotate bits of \c t1 to the right \c i times. + + The node \c t1 must have a bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_rotate_right(__in Z3_context c, __in unsigned i, __in Z3_ast t1); + + /** + \brief \mlh mk_ext_rotate_left c t1 t2 \endmlh + Rotate bits of \c t1 to the left \c t2 times. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_ext_rotate_left(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_ext_rotate_right c t1 t2 \endmlh + Rotate bits of \c t1 to the right \c t2 times. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_ext_rotate_right(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_int2bv c n t1 \endmlh + Create an \c n bit bit-vector from the integer argument \c t1. + + NB. This function is essentially treated as uninterpreted. + So you cannot expect Z3 to precisely reflect the semantics of this function + when solving constraints with this function. + + The node \c t1 must have integer sort. + */ + Z3_ast Z3_API Z3_mk_int2bv(__in Z3_context c, __in unsigned n, __in Z3_ast t1); + + /** + \brief \mlh mk_bv2int c t1 is_signed \endmlh + Create an integer from the bit-vector argument \c t1. + If \c is_signed is false, then the bit-vector \c t1 is treated as unsigned. + So the result is non-negative + and in the range [0..2^N-1], where N are the number of bits in \c t1. + If \c is_signed is true, \c t1 is treated as a signed bit-vector. + + NB. This function is essentially treated as uninterpreted. + So you cannot expect Z3 to precisely reflect the semantics of this function + when solving constraints with this function. + + The node \c t1 must have a bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bv2int(__in Z3_context c,__in Z3_ast t1, Z3_bool is_signed); + + /** + \brief \mlh mk_bvadd_no_overflow c t1 t2 is_signed \endmlh + Create a predicate that checks that the bit-wise addition + of \c t1 and \c t2 does not overflow. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvadd_no_overflow(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2, Z3_bool is_signed); + + /** + \brief \mlh mk_bvadd_no_underflow c t1 t2 \endmlh + Create a predicate that checks that the bit-wise signed addition + of \c t1 and \c t2 does not underflow. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvadd_no_underflow(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvsub_no_overflow c t1 t2 \endmlh + Create a predicate that checks that the bit-wise signed subtraction + of \c t1 and \c t2 does not overflow. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvsub_no_overflow(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvsub_no_underflow c t1 t2 is_signed \endmlh + Create a predicate that checks that the bit-wise subtraction + of \c t1 and \c t2 does not underflow. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvsub_no_underflow(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2, Z3_bool is_signed); + + /** + \brief \mlh mk_bvsdiv_no_overflow c t1 t2 \endmlh + Create a predicate that checks that the bit-wise signed division + of \c t1 and \c t2 does not overflow. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvsdiv_no_overflow(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + + /** + \brief \mlh mk_bvneg_no_overflow c t1 \endmlh + Check that bit-wise negation does not overflow when + \c t1 is interpreted as a signed bit-vector. + + The node \c t1 must have bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvneg_no_overflow(__in Z3_context c, __in Z3_ast t1); + + /** + \brief \mlh mk_bvmul_no_overflow c t1 t2 is_signed \endmlh + Create a predicate that checks that the bit-wise multiplication + of \c t1 and \c t2 does not overflow. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvmul_no_overflow(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2, Z3_bool is_signed); + + /** + \brief \mlh mk_bvmul_no_underflow c t1 t2 \endmlh + Create a predicate that checks that the bit-wise signed multiplication + of \c t1 and \c t2 does not underflow. + + The nodes \c t1 and \c t2 must have the same bit-vector sort. + */ + Z3_ast Z3_API Z3_mk_bvmul_no_underflow(__in Z3_context c, __in Z3_ast t1, __in Z3_ast t2); + /*@}*/ + + /** + @name Arrays + */ + + /*@{*/ + /** + \brief \mlh mk_select c a i \endmlh + Array read. + The argument \c a is the array and \c i is the index of the array that gets read. + + The node \c a must have an array sort [domain -> range], + and \c i must have the sort \c domain. + The sort of the result is \c range. + + \sa Z3_mk_array_sort + \sa Z3_mk_store + */ + Z3_ast Z3_API Z3_mk_select(__in Z3_context c, __in Z3_ast a, __in Z3_ast i); + + /** + \brief \mlh mk_store c a i v \endmlh + Array update. + + The node \c a must have an array sort [domain -> range], \c i must have sort \c domain, + \c v must have sort range. The sort of the result is [domain -> range]. + The semantics of this function is given by the theory of arrays described in the SMT-LIB + standard. See http://smtlib.org for more details. + The result of this function is an array that is equal to \c a (with respect to \c select) + on all indices except for \c i, where it maps to \c v (and the \c select of \c a with + respect to \c i may be a different value). + + \sa Z3_mk_array_sort + \sa Z3_mk_select + */ + Z3_ast Z3_API Z3_mk_store(__in Z3_context c, __in Z3_ast a, __in Z3_ast i, __in Z3_ast v); + + /** + \brief Create the constant array. + + The resulting term is an array, such that a \c select on an arbitrary index + produces the value \c v. + + \param c logical context. + \param domain domain sort for the array. + \param v value that the array maps to. + */ + Z3_ast Z3_API Z3_mk_const_array(__in Z3_context c, __in Z3_sort domain, __in Z3_ast v); + + /** + \brief \mlh mk_map f n args \endmlh + map f on the the argument arrays. + + The \c n nodes \c args must be of array sorts [domain_i -> range_i]. + The function declaration \c f must have type range_1 .. range_n -> range. + \c v must have sort range. The sort of the result is [domain_i -> range]. + + \sa Z3_mk_array_sort + \sa Z3_mk_store + \sa Z3_mk_select + */ + Z3_ast Z3_API Z3_mk_map(__in Z3_context c, __in Z3_func_decl f, unsigned n, __in Z3_ast const* args); + + /** + \brief Access the array default value. + Produces the default range value, for arrays that can be represented as + finite maps with a default range value. + + \param c logical context. + \param array array value whose default range value is accessed. + + */ + Z3_ast Z3_API Z3_mk_array_default(__in Z3_context c, __in Z3_ast array); + /*@}*/ + + /** + @name Sets + */ + /*@{*/ + /** + \brief Create Set type. + */ + Z3_sort Z3_API Z3_mk_set_sort(__in Z3_context c, __in Z3_sort ty); + + /** + \brief Create the empty set. + */ + Z3_ast Z3_API Z3_mk_empty_set(__in Z3_context c, __in Z3_sort domain); + + /** + \brief Create the full set. + */ + Z3_ast Z3_API Z3_mk_full_set(__in Z3_context c, __in Z3_sort domain); + + /** + \brief Add an element to a set. + + The first argument must be a set, the second an element. + */ + Z3_ast Z3_API Z3_mk_set_add(__in Z3_context c, __in Z3_ast set, __in Z3_ast elem); + + /** + \brief Remove an element to a set. + + The first argument must be a set, the second an element. + */ + Z3_ast Z3_API Z3_mk_set_del(__in Z3_context c, __in Z3_ast set, __in Z3_ast elem); + + /** + \brief Take the union of a list of sets. + */ + Z3_ast Z3_API Z3_mk_set_union(__in Z3_context c, __in unsigned num_args, __in_ecount(num_args) Z3_ast const args[]); + + /** + \brief Take the intersection of a list of sets. + */ + Z3_ast Z3_API Z3_mk_set_intersect(__in Z3_context c, __in unsigned num_args, __in_ecount(num_args) Z3_ast const args[]); + + /** + \brief Take the set difference between two sets. + */ + Z3_ast Z3_API Z3_mk_set_difference(__in Z3_context c, __in Z3_ast arg1, __in Z3_ast arg2); + + /** + \brief Take the complement of a set. + */ + Z3_ast Z3_API Z3_mk_set_complement(__in Z3_context c, __in Z3_ast arg); + + /** + \brief Check for set membership. + + The first argument should be an element type of the set. + */ + Z3_ast Z3_API Z3_mk_set_member(__in Z3_context c, __in Z3_ast elem, __in Z3_ast set); + + /** + \brief Check for subsetness of sets. + */ + Z3_ast Z3_API Z3_mk_set_subset(__in Z3_context c, __in Z3_ast arg1, __in Z3_ast arg2); + /*@}*/ + + /** + @name Numerals + */ + /*@{*/ + +#ifdef ML4only +#include +#endif + + /** + \mlonly {4 {L Redundant low-level API}} \endmlonly + */ + + /** + \brief Create a numeral of a given sort. + + \param c logical context. + \param numeral A string representing the numeral value in decimal notation. If the given sort is a real, then the numeral can be a rational, that is, a string of the form [num]* / [num]*. + \param ty The sort of the numeral. In the current implementation, the given sort can be an int, real, finite-domain, or bit-vectors of arbitrary size. + + \sa Z3_mk_int + \conly \sa Z3_mk_unsigned_int + */ + Z3_ast Z3_API Z3_mk_numeral(__in Z3_context c, __in Z3_string numeral, __in Z3_sort ty); + + /** + \brief Create a real from a fraction. + + \param c logical context. + \param num numerator of rational. + \param den denomerator of rational. + + \pre den != 0 + + \sa Z3_mk_numeral + \sa Z3_mk_int + \conly \sa Z3_mk_unsigned_int + */ + Z3_ast Z3_API Z3_mk_real(__in Z3_context c, __in int num, __in int den); + + /** + \brief Create a numeral of an int, bit-vector, or finite-domain sort. + + This function can be use to create numerals that fit in a machine integer. + It is slightly faster than #Z3_mk_numeral since it is not necessary to parse a string. + + \sa Z3_mk_numeral + */ + Z3_ast Z3_API Z3_mk_int(__in Z3_context c, __in int v, __in Z3_sort ty); + +#ifdef Conly + /** + \brief Create a numeral of a int, bit-vector, or finite-domain sort. + + This function can be use to create numerals that fit in a machine unsinged integer. + It is slightly faster than #Z3_mk_numeral since it is not necessary to parse a string. + + \sa Z3_mk_numeral + */ + Z3_ast Z3_API Z3_mk_unsigned_int(__in Z3_context c, __in unsigned v, __in Z3_sort ty); +#endif + + /** + \brief Create a numeral of a int, bit-vector, or finite-domain sort. + + This function can be use to create numerals that fit in a machine __int64 integer. + It is slightly faster than #Z3_mk_numeral since it is not necessary to parse a string. + + \sa Z3_mk_numeral + */ + Z3_ast Z3_API Z3_mk_int64(__in Z3_context c, __in __int64 v, __in Z3_sort ty); + +#ifdef Conly + /** + \brief Create a numeral of a int, bit-vector, or finite-domain sort. + + This function can be use to create numerals that fit in a machine unsigned __int64 integer. + It is slightly faster than #Z3_mk_numeral since it is not necessary to parse a string. + + \sa Z3_mk_numeral + */ + Z3_ast Z3_API Z3_mk_unsigned_int64(__in Z3_context c, __in unsigned __int64 v, __in Z3_sort ty); +#endif + + /*@}*/ + + /** + @name Quantifiers + */ + /*@{*/ + + /** + \brief Create a pattern for quantifier instantiation. + + Z3 uses pattern matching to instantiate quantifiers. If a + pattern is not provided for a quantifier, then Z3 will + automatically compute a set of patterns for it. However, for + optimal performance, the user should provide the patterns. + + Patterns comprise a list of terms. The list should be + non-empty. If the list comprises of more than one term, it is + a called a multi-pattern. + + In general, one can pass in a list of (multi-)patterns in the + quantifier constructor. + + + \sa Z3_mk_forall + \sa Z3_mk_exists + */ + Z3_pattern Z3_API Z3_mk_pattern( + __in Z3_context c, + __in unsigned num_patterns, __in_ecount(num_patterns) Z3_ast const terms[]); + + /** + \brief Create a bound variable. + + Bound variables are indexed by de-Bruijn indices. It is perhaps easiest to explain + the meaning of de-Bruijn indices by indicating the compilation process from + non-de-Bruijn formulas to de-Bruijn format. + + \verbatim + abs(forall (x1) phi) = forall (x1) abs1(phi, x1, 0) + abs(forall (x1, x2) phi) = abs(forall (x1) abs(forall (x2) phi)) + abs1(x, x, n) = b_n + abs1(y, x, n) = y + abs1(f(t1,...,tn), x, n) = f(abs1(t1,x,n), ..., abs1(tn,x,n)) + abs1(forall (x1) phi, x, n) = forall (x1) (abs1(phi, x, n+1)) + \endverbatim + + The last line is significant: the index of a bound variable is different depending + on the scope in which it appears. The deeper x appears, the higher is its + index. + + \param c logical context + \param index de-Bruijn index + \param ty sort of the bound variable + + \sa Z3_mk_forall + \sa Z3_mk_exists + */ + Z3_ast Z3_API Z3_mk_bound(__in Z3_context c, __in unsigned index, __in Z3_sort ty); + + /** + \brief Create a forall formula. It takes an expression \c body that contains bound variables + of the same sorts as the sorts listed in the array \c sorts. The bound variables are de-Bruijn indices created + using #Z3_mk_bound. The array \c decl_names contains the names that the quantified formula uses for the + bound variables. Z3 applies the convention that the last element in the \c decl_names and \c sorts array + refers to the variable with index 0, the second to last element of \c decl_names and \c sorts refers + to the variable with index 1, etc. + + + \mlonly [mk_forall c w p t n b] creates a forall formula, where + [w] is the weight, [p] is an array of patterns, [t] is an array + with the sorts of the bound variables, [n] is an array with the + 'names' of the bound variables, and [b] is the body of the + quantifier. Quantifiers are associated with weights indicating + the importance of using the quantifier during + instantiation. \endmlonly + + + \param c logical context. + \param weight quantifiers are associated with weights indicating the importance of using the quantifier during instantiation. By default, pass the weight 0. + \param num_patterns number of patterns. + \param patterns array containing the patterns created using #Z3_mk_pattern. + \param num_decls number of variables to be bound. + \param sorts the sorts of the bound variables. + \param decl_names names of the bound variables + \param body the body of the quantifier. + + \sa Z3_mk_pattern + \sa Z3_mk_bound + \sa Z3_mk_exists + */ + Z3_ast Z3_API Z3_mk_forall(__in Z3_context c, __in unsigned weight, + __in unsigned num_patterns, __in_ecount(num_patterns) Z3_pattern const patterns[], + __in unsigned num_decls, __in_ecount(num_decls) Z3_sort const sorts[], + __in_ecount(num_decls) Z3_symbol const decl_names[], + __in Z3_ast body); + + /** + \brief Create an exists formula. Similar to #Z3_mk_forall. + + \sa Z3_mk_pattern + \sa Z3_mk_bound + \sa Z3_mk_forall + \sa Z3_mk_quantifier + */ + Z3_ast Z3_API Z3_mk_exists(__in Z3_context c, __in unsigned weight, + __in unsigned num_patterns, __in_ecount(num_patterns) Z3_pattern const patterns[], + __in unsigned num_decls, __in_ecount(num_decls) Z3_sort const sorts[], + __in_ecount(num_decls) Z3_symbol const decl_names[], + __in Z3_ast body); + + /** + \brief Create a quantifier - universal or existential, with pattern hints. + See the documentation for #Z3_mk_forall for an explanation of the parameters. + + \param c logical context. + \param is_forall flag to indicate if this is a universal or existential quantifier. + \param weight quantifiers are associated with weights indicating the importance of using the quantifier during instantiation. By default, pass the weight 0. + \param num_patterns number of patterns. + \param patterns array containing the patterns created using #Z3_mk_pattern. + \param num_decls number of variables to be bound. + \param sorts array of sorts of the bound variables. + \param decl_names names of the bound variables. + \param body the body of the quantifier. + + \sa Z3_mk_pattern + \sa Z3_mk_bound + \sa Z3_mk_forall + \sa Z3_mk_exists + */ + Z3_ast Z3_API Z3_mk_quantifier( + __in Z3_context c, + __in Z3_bool is_forall, + __in unsigned weight, + __in unsigned num_patterns, __in_ecount(num_patterns) Z3_pattern const patterns[], + __in unsigned num_decls, __in_ecount(num_decls) Z3_sort const sorts[], + __in_ecount(num_decls) Z3_symbol const decl_names[], + __in Z3_ast body); + + + /** + \brief Create a quantifier - universal or existential, with pattern hints, no patterns, and attributes + + \param c logical context. + \param is_forall flag to indicate if this is a universal or existential quantifier. + \param quantifier_id identifier to identify quantifier + \param skolem_id identifier to identify skolem constants introduced by quantifier. + \param weight quantifiers are associated with weights indicating the importance of using the quantifier during instantiation. By default, pass the weight 0. + \param num_patterns number of patterns. + \param patterns array containing the patterns created using #Z3_mk_pattern. + \param num_no_patterns number of patterns. + \param no_patterns array containing the patterns created using #Z3_mk_pattern. + \param num_decls number of variables to be bound. + \param sorts array of sorts of the bound variables. + \param decl_names names of the bound variables. + \param body the body of the quantifier. + + \sa Z3_mk_pattern + \sa Z3_mk_bound + \sa Z3_mk_forall + \sa Z3_mk_exists + */ + Z3_ast Z3_API Z3_mk_quantifier_ex( + __in Z3_context c, + __in Z3_bool is_forall, + __in unsigned weight, + __in Z3_symbol quantifier_id, + __in Z3_symbol skolem_id, + __in unsigned num_patterns, __in_ecount(num_patterns) Z3_pattern const patterns[], + __in unsigned num_no_patterns, __in_ecount(num_no_patterns) Z3_ast const no_patterns[], + __in unsigned num_decls, __in_ecount(num_decls) Z3_sort const sorts[], + __in_ecount(num_decls) Z3_symbol const decl_names[], + __in Z3_ast body); + + /** + \brief Create a universal quantifier using a list of constants that + will form the set of bound variables. + + \param c logical context. + \param weight quantifiers are associated with weights indicating the importance of using + the quantifier during instantiation. By default, pass the weight 0. + \param num_bound number of constants to be abstracted into bound variables. + \param bound array of constants to be abstracted into bound variables. + \param num_patterns number of patterns. + \param patterns array containing the patterns created using #Z3_mk_pattern. + \param body the body of the quantifier. + + \sa Z3_mk_pattern + \sa Z3_mk_exists_const + + */ + Z3_ast Z3_API Z3_mk_forall_const( + __in Z3_context c, + unsigned weight, + unsigned num_bound, + __in_ecount(num_bound) Z3_app const bound[], + unsigned num_patterns, + __in_ecount(num_patterns) Z3_pattern const patterns[], + __in Z3_ast body + ); + + /** + \brief Similar to #Z3_mk_forall_const. + + \brief Create an existential quantifier using a list of constants that + will form the set of bound variables. + + \param c logical context. + \param weight quantifiers are associated with weights indicating the importance of using + the quantifier during instantiation. By default, pass the weight 0. + \param num_bound number of constants to be abstracted into bound variables. + \param bound array of constants to be abstracted into bound variables. + \param num_patterns number of patterns. + \param patterns array containing the patterns created using #Z3_mk_pattern. + \param body the body of the quantifier. + + \sa Z3_mk_pattern + \sa Z3_mk_forall_const + */ + Z3_ast Z3_API Z3_mk_exists_const( + __in Z3_context c, + unsigned weight, + unsigned num_bound, + __in_ecount(num_bound) Z3_app const bound[], + unsigned num_patterns, + __in_ecount(num_patterns) Z3_pattern const patterns[], + __in Z3_ast body + ); + + /** + \brief Create a universal or existential + quantifier using a list of constants that + will form the set of bound variables. + */ + Z3_ast Z3_API Z3_mk_quantifier_const( + __in Z3_context c, + Z3_bool is_forall, + unsigned weight, + unsigned num_bound, __in_ecount(num_bound) Z3_app const bound[], + unsigned num_patterns, __in_ecount(num_patterns) Z3_pattern const patterns[], + __in Z3_ast body + ); + + + + /** + \brief Create a universal or existential + quantifier using a list of constants that + will form the set of bound variables. + */ + Z3_ast Z3_API Z3_mk_quantifier_const_ex( + __in Z3_context c, + Z3_bool is_forall, + unsigned weight, + __in Z3_symbol quantifier_id, + __in Z3_symbol skolem_id, + unsigned num_bound, __in_ecount(num_bound) Z3_app const bound[], + unsigned num_patterns, __in_ecount(num_patterns) Z3_pattern const patterns[], + unsigned num_no_patterns, __in_ecount(num_no_patterns) Z3_ast const no_patterns[], + __in Z3_ast body + ); + + /*@}*/ + + /** + @name Accessors + */ + /*@{*/ + + /** + \mlonly {3 {L Symbols}} \endmlonly + */ + +#ifdef ML4only +#include +#endif + + /** + \mlonly {4 {L Redundant low-level API}} \endmlonly + */ + + /** + \brief Return \c Z3_INT_SYMBOL if the symbol was constructed + using #Z3_mk_int_symbol, and \c Z3_STRING_SYMBOL if the symbol + was constructed using #Z3_mk_string_symbol. + */ + Z3_symbol_kind Z3_API Z3_get_symbol_kind(__in Z3_context c, __in Z3_symbol s); + + /** + \brief \mlh get_symbol_int c s \endmlh + Return the symbol int value. + + \pre Z3_get_symbol_kind(s) == Z3_INT_SYMBOL + + \sa Z3_mk_int_symbol + */ + int Z3_API Z3_get_symbol_int(__in Z3_context c, __in Z3_symbol s); + + /** + \brief \mlh get_symbol_string c s \endmlh + Return the symbol name. + + \pre Z3_get_symbol_string(s) == Z3_STRING_SYMBOL + + \conly \warning The returned buffer is statically allocated by Z3. It will + \conly be automatically deallocated when #Z3_del_context is invoked. + \conly So, the buffer is invalidated in the next call to \c Z3_get_symbol_string. + + \sa Z3_mk_string_symbol + */ + Z3_string Z3_API Z3_get_symbol_string(__in Z3_context c, __in Z3_symbol s); + + + /** + \mlonly {3 {L Sorts}} \endmlonly + */ + +#ifdef ML4only +#include +#endif + + /** + \brief Return the sort name as a symbol. + */ + Z3_symbol Z3_API Z3_get_sort_name(__in Z3_context c, __in Z3_sort d); + + /** + \brief Return a unique identifier for \c s. + \mlonly \remark Implicitly used by [Pervasives.( = )] and [Pervasives.compare]. \endmlonly + */ + unsigned Z3_API Z3_get_sort_id(__in Z3_context c, Z3_sort s); + + /** + \mlonly {4 {L Redundant low-level API}} \endmlonly + */ + + /** + \brief Convert a \c Z3_sort into \c Z3_ast. \conly This is just type casting. + \mlonly \remark [sort_to_ast c s] can be replaced by [(s :> ast)]. \endmlonly + */ + Z3_ast Z3_API Z3_sort_to_ast(__in Z3_context c, __in Z3_sort s); + + /** + \brief compare sorts. + \mlonly \remark [Pervasives.( = )] or [Pervasives.compare] can also be used. \endmlonly + */ + Z3_bool Z3_API Z3_is_eq_sort(__in Z3_context c, __in Z3_sort s1, __in Z3_sort s2); + + /** + \brief Return the sort kind (e.g., array, tuple, int, bool, etc). + + \sa Z3_sort_kind + */ + Z3_sort_kind Z3_API Z3_get_sort_kind(__in Z3_context c, __in Z3_sort t); + + + /** + \brief \mlh get_bv_sort_size c t \endmlh + Return the size of the given bit-vector sort. + + \pre Z3_get_sort_kind(c, t) == Z3_BV_SORT + + \sa Z3_mk_bv_sort + \sa Z3_get_sort_kind + */ + unsigned Z3_API Z3_get_bv_sort_size(__in Z3_context c, __in Z3_sort t); + + + /** + \conly \brief Store the size of the sort in \c r. Return Z3_FALSE if the call failed. + \mlonly \brief Return the size of the sort in \c r. Return \c None if the call failed. \endmlonly + That is, Z3_get_sort_kind(s) == Z3_FINITE_DOMAIN_SORT + */ + Z3_bool_opt Z3_API Z3_get_finite_domain_sort_size(__in Z3_context c, __in Z3_sort s, __out_opt unsigned __int64* r); + + + /** + \brief \mlh get_array_sort_domain c t \endmlh + Return the domain of the given array sort. + + \pre Z3_get_sort_kind(c, t) == Z3_ARRAY_SORT + + \sa Z3_mk_array_sort + \sa Z3_get_sort_kind + */ + Z3_sort Z3_API Z3_get_array_sort_domain(__in Z3_context c, __in Z3_sort t); + + /** + \brief \mlh get_array_sort_range c t \endmlh + Return the range of the given array sort. + + \pre Z3_get_sort_kind(c, t) == Z3_ARRAY_SORT + + \sa Z3_mk_array_sort + \sa Z3_get_sort_kind + */ + Z3_sort Z3_API Z3_get_array_sort_range(__in Z3_context c, __in Z3_sort t); + + + /** + \brief \mlh get_tuple_sort_mk_decl c t \endmlh + Return the constructor declaration of the given tuple + sort. + + \pre Z3_get_sort_kind(c, t) == Z3_DATATYPE_SORT + + \sa Z3_mk_tuple_sort + \sa Z3_get_sort_kind + */ + Z3_func_decl Z3_API Z3_get_tuple_sort_mk_decl(__in Z3_context c, __in Z3_sort t); + + /** + \brief \mlh get_tuple_sort_num_fields c t \endmlh + Return the number of fields of the given tuple sort. + + \pre Z3_get_sort_kind(c, t) == Z3_DATATYPE_SORT + + \sa Z3_mk_tuple_sort + \sa Z3_get_sort_kind + */ + unsigned Z3_API Z3_get_tuple_sort_num_fields(__in Z3_context c, __in Z3_sort t); + + /** + \brief \mlh get_tuple_sort_field_decl c t i \endmlh + Return the i-th field declaration (i.e., projection function declaration) + of the given tuple sort. + + \pre Z3_get_sort_kind(t) == Z3_DATATYPE_SORT + \pre i < Z3_get_tuple_sort_num_fields(c, t) + + \sa Z3_mk_tuple_sort + \sa Z3_get_sort_kind + */ + Z3_func_decl Z3_API Z3_get_tuple_sort_field_decl(__in Z3_context c, __in Z3_sort t, __in unsigned i); + + /** + \brief Return number of constructors for datatype. + + \pre Z3_get_sort_kind(t) == Z3_DATATYPE_SORT + + \sa Z3_get_datatype_sort_constructor + \sa Z3_get_datatype_sort_recognizer + \sa Z3_get_datatype_sort_constructor_accessor + + */ + unsigned Z3_API Z3_get_datatype_sort_num_constructors( + __in Z3_context c, __in Z3_sort t); + + /** + \brief Return idx'th constructor. + + \pre Z3_get_sort_kind(t) == Z3_DATATYPE_SORT + \pre idx < Z3_get_datatype_sort_num_constructors(c, t) + + \sa Z3_get_datatype_sort_num_constructors + \sa Z3_get_datatype_sort_recognizer + \sa Z3_get_datatype_sort_constructor_accessor + + */ + Z3_func_decl Z3_API Z3_get_datatype_sort_constructor( + __in Z3_context c, __in Z3_sort t, unsigned idx); + + /** + \brief Return idx'th recognizer. + + \pre Z3_get_sort_kind(t) == Z3_DATATYPE_SORT + \pre idx < Z3_get_datatype_sort_num_constructors(c, t) + + \sa Z3_get_datatype_sort_num_constructors + \sa Z3_get_datatype_sort_constructor + \sa Z3_get_datatype_sort_constructor_accessor + + */ + Z3_func_decl Z3_API Z3_get_datatype_sort_recognizer( + __in Z3_context c, __in Z3_sort t, unsigned idx); + + /** + \brief Return idx_a'th accessor for the idx_c'th constructor. + + \pre Z3_get_sort_kind(t) == Z3_DATATYPE_SORT + \pre idx_c < Z3_get_datatype_sort_num_constructors(c, t) + \pre idx_a < Z3_get_domain_size(c, Z3_get_datatype_sort_constructor(c, idx_c)) + + \sa Z3_get_datatype_sort_num_constructors + \sa Z3_get_datatype_sort_constructor + \sa Z3_get_datatype_sort_recognizer + */ + Z3_func_decl Z3_API Z3_get_datatype_sort_constructor_accessor( + __in Z3_context c, __in Z3_sort t, unsigned idx_c, unsigned idx_a); + + + /** + \brief Return arity of relation. + + \pre Z3_get_sort_kind(s) == Z3_RELATION_SORT + + \sa Z3_get_relation_column + */ + unsigned Z3_API Z3_get_relation_arity(__in Z3_context c, __in Z3_sort s); + + /** + \brief Return sort at i'th column of relation sort. + + \pre Z3_get_sort_kind(c, s) == Z3_RELATION_SORT + \pre col < Z3_get_relation_arity(c, s) + + \sa Z3_get_relation_arity + */ + Z3_sort Z3_API Z3_get_relation_column(__in Z3_context c, __in Z3_sort s, unsigned col); + + + /** + \mlonly {3 {L Function Declarations}} \endmlonly + */ + + /** + \brief Convert a \c Z3_func_decl into \c Z3_ast. \conly This is just type casting. + \mlonly \remark [func_decl_to_ast c f] can be replaced by [(f :> ast)]. \endmlonly + */ + Z3_ast Z3_API Z3_func_decl_to_ast(__in Z3_context c, __in Z3_func_decl f); + + /** + \brief compare terms. + \mlonly \remark [Pervasives.( = )] or [Pervasives.compare] can also be used. \endmlonly + */ + Z3_bool Z3_API Z3_is_eq_func_decl(__in Z3_context c, __in Z3_func_decl f1, Z3_func_decl f2); + + /** + \brief Return a unique identifier for \c f. + \mlonly \remark Implicitly used by [Pervasives.( = )] and [Pervasives.compare]. \endmlonly + */ + unsigned Z3_API Z3_get_func_decl_id(__in Z3_context c, Z3_func_decl f); + + /** + \brief Return the constant declaration name as a symbol. + */ + Z3_symbol Z3_API Z3_get_decl_name(__in Z3_context c, __in Z3_func_decl d); + + /** + \brief Return declaration kind corresponding to declaration. + */ + Z3_decl_kind Z3_API Z3_get_decl_kind(__in Z3_context c, __in Z3_func_decl d); + + /** + \brief Return the number of parameters of the given declaration. + + \sa Z3_get_arity + */ + unsigned Z3_API Z3_get_domain_size(__in Z3_context c, __in Z3_func_decl d); + + /** + \brief Alias for \c Z3_get_domain_size. + + \sa Z3_get_domain_size + */ + unsigned Z3_API Z3_get_arity(__in Z3_context c, __in Z3_func_decl d); + + /** + \brief \mlh get_domain c d i \endmlh + Return the sort of the i-th parameter of the given function declaration. + + \pre i < Z3_get_domain_size(d) + + \sa Z3_get_domain_size + */ + Z3_sort Z3_API Z3_get_domain(__in Z3_context c, __in Z3_func_decl d, __in unsigned i); + +#ifdef ML4only +#include +#endif + + /** + \brief \mlh get_range c d \endmlh + Return the range of the given declaration. + + If \c d is a constant (i.e., has zero arguments), then this + function returns the sort of the constant. + */ + Z3_sort Z3_API Z3_get_range(__in Z3_context c, __in Z3_func_decl d); + + /** + \brief Return the number of parameters associated with a declaration. + */ + unsigned Z3_API Z3_get_decl_num_parameters(__in Z3_context c, __in Z3_func_decl d); + + /** + \brief Return the parameter type associated with a declaration. + + \param c the context + \param d the function declaration + \param idx is the index of the named parameter it should be between 0 and the number of parameters. + */ + Z3_parameter_kind Z3_API Z3_get_decl_parameter_kind(__in Z3_context c, __in Z3_func_decl d, unsigned idx); + + /** + \brief Return the integer value associated with an integer parameter. + + \pre Z3_get_decl_parameter_kind(c, d, idx) == Z3_PARAMETER_INT + */ + int Z3_API Z3_get_decl_int_parameter(__in Z3_context c, __in Z3_func_decl d, unsigned idx); + + /** + \brief Return the double value associated with an double parameter. + + \pre Z3_get_decl_parameter_kind(c, d, idx) == Z3_PARAMETER_DOUBLE + */ + double Z3_API Z3_get_decl_double_parameter(__in Z3_context c, __in Z3_func_decl d, unsigned idx); + + /** + \brief Return the double value associated with an double parameter. + + \pre Z3_get_decl_parameter_kind(c, d, idx) == Z3_PARAMETER_SYMBOL + */ + Z3_symbol Z3_API Z3_get_decl_symbol_parameter(__in Z3_context c, __in Z3_func_decl d, unsigned idx); + + /** + \brief Return the sort value associated with a sort parameter. + + \pre Z3_get_decl_parameter_kind(c, d, idx) == Z3_PARAMETER_SORT + */ + Z3_sort Z3_API Z3_get_decl_sort_parameter(__in Z3_context c, __in Z3_func_decl d, unsigned idx); + + /** + \brief Return the expresson value associated with an expression parameter. + + \pre Z3_get_decl_parameter_kind(c, d, idx) == Z3_PARAMETER_AST + */ + Z3_ast Z3_API Z3_get_decl_ast_parameter(__in Z3_context c, __in Z3_func_decl d, unsigned idx); + + /** + \brief Return the expresson value associated with an expression parameter. + + \pre Z3_get_decl_parameter_kind(c, d, idx) == Z3_PARAMETER_FUNC_DECL + */ + Z3_func_decl Z3_API Z3_get_decl_func_decl_parameter(__in Z3_context c, __in Z3_func_decl d, unsigned idx); + + /** + \brief Return the rational value, as a string, associated with a rational parameter. + + \pre Z3_get_decl_parameter_kind(c, d, idx) == Z3_PARAMETER_RATIONAL + */ + Z3_string Z3_API Z3_get_decl_rational_parameter(__in Z3_context c, __in Z3_func_decl d, unsigned idx); + + /** + \mlonly {3 {L Applications}} \endmlonly + */ + + /** + \brief Convert a \c Z3_app into \c Z3_ast. \conly This is just type casting. + \mlonly \remark [app_to_ast c a] can be replaced by [(a :> ast)]. \endmlonly + */ + Z3_ast Z3_API Z3_app_to_ast(__in Z3_context c, __in Z3_app a); + + /** + \brief Return the declaration of a constant or function application. + */ + Z3_func_decl Z3_API Z3_get_app_decl(__in Z3_context c, __in Z3_app a); + + /** + \brief \mlh get_app_num_args c a \endmlh + Return the number of argument of an application. If \c t + is an constant, then the number of arguments is 0. + */ + unsigned Z3_API Z3_get_app_num_args(__in Z3_context c, __in Z3_app a); + + /** + \brief \mlh get_app_arg c a i \endmlh + Return the i-th argument of the given application. + + \pre i < Z3_get_num_args(c, a) + */ + Z3_ast Z3_API Z3_get_app_arg(__in Z3_context c, __in Z3_app a, __in unsigned i); + +#ifdef ML4only +#include +#endif + + + /** + \mlonly {3 {L Terms}} \endmlonly + */ + +#ifdef ML4only +#include +#endif + + /** + \brief compare terms. + \mlonly \remark [Pervasives.( = )] or [Pervasives.compare] can also be used. \endmlonly + */ + Z3_bool Z3_API Z3_is_eq_ast(__in Z3_context c, __in Z3_ast t1, Z3_ast t2); + + /** + \brief Return a unique identifier for \c t. + \mlonly \remark Implicitly used by [Pervasives.compare] for values of type [ast], [app], [sort], [func_decl], and [pattern]. \endmlonly + */ + unsigned Z3_API Z3_get_ast_id(__in Z3_context c, Z3_ast t); + + /** + \brief Return a hash code for the given AST. + \mlonly \remark Implicitly used by [Hashtbl.hash] for values of type [ast], [app], [sort], [func_decl], and [pattern]. \endmlonly + */ + unsigned Z3_API Z3_get_ast_hash(__in Z3_context c, __in Z3_ast a); + + /** + \brief Return the sort of an AST node. + + The AST node must be a constant, application, numeral, bound variable, or quantifier. + + */ + Z3_sort Z3_API Z3_get_sort(__in Z3_context c, __in Z3_ast a); + + /** + \brief Return true if the given expression \c t is well sorted. + */ + Z3_bool Z3_API Z3_is_well_sorted(__in Z3_context c, __in Z3_ast t); + + /** + \brief Return Z3_L_TRUE if \c a is true, Z3_L_FALSE if it is false, and Z3_L_UNDEF otherwise. + */ + Z3_lbool Z3_API Z3_get_bool_value(__in Z3_context c, __in Z3_ast a); + + /** + \brief Return the kind of the given AST. + */ + Z3_ast_kind Z3_API Z3_get_ast_kind(__in Z3_context c, __in Z3_ast a); + + Z3_bool Z3_API Z3_is_app(__in Z3_context c, __in Z3_ast a); + + Z3_bool Z3_API Z3_is_numeral_ast(__in Z3_context c, __in Z3_ast a); + + /** + \brief Return true if the give AST is a real algebraic number. + */ + Z3_bool Z3_API Z3_is_algebraic_number(__in Z3_context c, __in Z3_ast a); + + /** + \brief Convert an \c ast into an \c APP_AST. \conly This is just type casting. + + \pre \code Z3_get_ast_kind(c, a) == \c Z3_APP_AST \endcode + */ + Z3_app Z3_API Z3_to_app(__in Z3_context c, __in Z3_ast a); + + /** + \brief Convert an AST into a FUNC_DECL_AST. This is just type casting. + + \pre \code Z3_get_ast_kind(c, a) == Z3_FUNC_DECL_AST \endcode + */ + Z3_func_decl Z3_API Z3_to_func_decl(__in Z3_context c, __in Z3_ast a); + + + /** + \mlonly {4 {L Numerals}} \endmlonly + */ + +#ifdef ML4only +#include +#endif + + /** + \mlonly {5 {L Low-level API}} \endmlonly + */ + + /** + \brief Return numeral value, as a string of a numeric constant term + + \pre Z3_get_ast_kind(c, a) == Z3_NUMERAL_AST + */ + Z3_string Z3_API Z3_get_numeral_string(__in Z3_context c, __in Z3_ast a); + + /** + \brief Return numeral as a string in decimal notation. + The result has at most \c precision decimal places. + + \pre Z3_get_ast_kind(c, a) == Z3_NUMERAL_AST || Z3_is_algebraic_number(c, a) + */ + Z3_string Z3_API Z3_get_numeral_decimal_string(__in Z3_context c, __in Z3_ast a, __in unsigned precision); + + /** + \brief Return the numerator (as a numeral AST) of a numeral AST of sort Real. + + \pre Z3_get_ast_kind(c, a) == Z3_NUMERAL_AST + */ + Z3_ast Z3_API Z3_get_numerator(__in Z3_context c, __in Z3_ast a); + + /** + \brief Return the denominator (as a numeral AST) of a numeral AST of sort Real. + + \pre Z3_get_ast_kind(c, a) == Z3_NUMERAL_AST + */ + Z3_ast Z3_API Z3_get_denominator(__in Z3_context c, __in Z3_ast a); + + /** + \brief Return numeral value, as a pair of 64 bit numbers if the representation fits. + + \param c logical context. + \param a term. + \param num numerator. + \param den denominator. + + Return \c Z3_TRUE if the numeral value fits in 64 bit numerals, \c Z3_FALSE otherwise. + + \pre Z3_get_ast_kind(a) == Z3_NUMERAL_AST + */ + Z3_bool Z3_API Z3_get_numeral_small(__in Z3_context c, __in Z3_ast a, __out __int64* num, __out __int64* den); + + + /** + \brief \mlh get_numeral_int c v \endmlh + Similar to #Z3_get_numeral_string, but only succeeds if + the value can fit in a machine int. Return Z3_TRUE if the call succeeded. + + \pre Z3_get_ast_kind(c, v) == Z3_NUMERAL_AST + + \sa Z3_get_numeral_string + */ + Z3_bool Z3_API Z3_get_numeral_int(__in Z3_context c, __in Z3_ast v, __out int* i); + +#ifdef Conly + /** + \brief \mlh get_numeral_uint c v \endmlh + Similar to #Z3_get_numeral_string, but only succeeds if + the value can fit in a machine unsigned int. Return Z3_TRUE if the call succeeded. + + \pre Z3_get_ast_kind(c, v) == Z3_NUMERAL_AST + + \sa Z3_get_numeral_string + */ + Z3_bool Z3_API Z3_get_numeral_uint(__in Z3_context c, __in Z3_ast v, __out unsigned* u); +#endif + +#ifdef Conly + /** + \brief \mlh get_numeral_uint64 c v \endmlh + Similar to #Z3_get_numeral_string, but only succeeds if + the value can fit in a machine unsigned __int64 int. Return Z3_TRUE if the call succeeded. + + \pre Z3_get_ast_kind(c, v) == Z3_NUMERAL_AST + + \sa Z3_get_numeral_string + */ + Z3_bool Z3_API Z3_get_numeral_uint64(__in Z3_context c, __in Z3_ast v, __out unsigned __int64* u); +#endif + + /** + \brief \mlh get_numeral_int64 c v \endmlh + Similar to #Z3_get_numeral_string, but only succeeds if + the value can fit in a machine __int64 int. Return Z3_TRUE if the call succeeded. + + \pre Z3_get_ast_kind(c, v) == Z3_NUMERAL_AST + + \sa Z3_get_numeral_string + */ + Z3_bool Z3_API Z3_get_numeral_int64(__in Z3_context c, __in Z3_ast v, __out __int64* i); + + /** + \brief \mlh get_numeral_rational_int64 c x y\endmlh + Similar to #Z3_get_numeral_string, but only succeeds if + the value can fit as a rational number as machine __int64 int. Return Z3_TRUE if the call succeeded. + + \pre Z3_get_ast_kind(c, v) == Z3_NUMERAL_AST + + \sa Z3_get_numeral_string + */ + Z3_bool Z3_API Z3_get_numeral_rational_int64(__in Z3_context c, __in Z3_ast v, __out __int64* num, __out __int64* den); + + /** + \brief Return a lower bound for the given real algebraic number. + The interval isolating the number is smaller than 1/10^precision. + The result is a numeral AST of sort Real. + + \pre Z3_is_algebraic_number(c, a) + */ + Z3_ast Z3_API Z3_get_algebraic_number_lower(__in Z3_context c, __in Z3_ast a, __in unsigned precision); + + /** + \brief Return a upper bound for the given real algebraic number. + The interval isolating the number is smaller than 1/10^precision. + The result is a numeral AST of sort Real. + + \pre Z3_is_algebraic_number(c, a) + */ + Z3_ast Z3_API Z3_get_algebraic_number_upper(Z3_context c, Z3_ast a, unsigned precision); + + + /** + \mlonly {4 {L Patterns}} \endmlonly + */ + + /** + \brief Convert a Z3_pattern into Z3_ast. \conly This is just type casting. + \mlonly \remark [pattern_to_ast c p] can be replaced by [(p :> ast)]. \endmlonly + */ + Z3_ast Z3_API Z3_pattern_to_ast(__in Z3_context c, __in Z3_pattern p); + +#ifdef ML4only +#include +#endif + + /** + \brief Return number of terms in pattern. + */ + unsigned Z3_API Z3_get_pattern_num_terms(__in Z3_context c, __in Z3_pattern p); + + /** + \brief Return i'th ast in pattern. + */ + Z3_ast Z3_API Z3_get_pattern(__in Z3_context c, __in Z3_pattern p, __in unsigned idx); + + + /** + \mlonly {4 {L Quantifiers}} \endmlonly + */ + + /** + \brief Return index of de-Brujin bound variable. + + \pre Z3_get_ast_kind(a) == Z3_VAR_AST + */ + unsigned Z3_API Z3_get_index_value(__in Z3_context c, __in Z3_ast a); + + /** + \brief Determine if quantifier is universal. + + \pre Z3_get_ast_kind(a) == Z3_QUANTIFIER_AST + */ + Z3_bool Z3_API Z3_is_quantifier_forall(__in Z3_context c, __in Z3_ast a); + + /** + \brief Obtain weight of quantifier. + + \pre Z3_get_ast_kind(a) == Z3_QUANTIFIER_AST + */ + unsigned Z3_API Z3_get_quantifier_weight(__in Z3_context c, __in Z3_ast a); + + /** + \brief Return number of patterns used in quantifier. + + \pre Z3_get_ast_kind(a) == Z3_QUANTIFIER_AST + */ + unsigned Z3_API Z3_get_quantifier_num_patterns(__in Z3_context c, __in Z3_ast a); + + /** + \brief Return i'th pattern. + + \pre Z3_get_ast_kind(a) == Z3_QUANTIFIER_AST + */ + Z3_pattern Z3_API Z3_get_quantifier_pattern_ast(__in Z3_context c, __in Z3_ast a, unsigned i); + + /** + \brief Return number of no_patterns used in quantifier. + + \pre Z3_get_ast_kind(a) == Z3_QUANTIFIER_AST + */ + unsigned Z3_API Z3_get_quantifier_num_no_patterns(__in Z3_context c, __in Z3_ast a); + + /** + \brief Return i'th no_pattern. + + \pre Z3_get_ast_kind(a) == Z3_QUANTIFIER_AST + */ + Z3_ast Z3_API Z3_get_quantifier_no_pattern_ast(__in Z3_context c, __in Z3_ast a, unsigned i); + + /** + \brief Return symbol of the i'th bound variable. + + \pre Z3_get_ast_kind(a) == Z3_QUANTIFIER_AST + */ + Z3_symbol Z3_API Z3_get_quantifier_bound_name(__in Z3_context c, __in Z3_ast a, unsigned i); + + /** + \brief Return sort of the i'th bound variable. + + \pre Z3_get_ast_kind(a) == Z3_QUANTIFIER_AST + */ + Z3_sort Z3_API Z3_get_quantifier_bound_sort(__in Z3_context c, __in Z3_ast a, unsigned i); + + /** + \brief Return body of quantifier. + + \pre Z3_get_ast_kind(a) == Z3_QUANTIFIER_AST + */ + Z3_ast Z3_API Z3_get_quantifier_body(__in Z3_context c, __in Z3_ast a); + + /** + \brief Return number of bound variables of quantifier. + + \pre Z3_get_ast_kind(a) == Z3_QUANTIFIER_AST + */ + unsigned Z3_API Z3_get_quantifier_num_bound(__in Z3_context c, __in Z3_ast a); + + + /** + \mlonly {3 {L Simplification}} \endmlonly + */ + + /** + \brief Interface to simplifier. + + Provides an interface to the AST simplifier used by Z3. + */ + Z3_ast Z3_API Z3_simplify(__in Z3_context c, __in Z3_ast a); + +#ifdef CorML4 + /** + \brief Interface to simplifier. + + Provides an interface to the AST simplifier used by Z3. + This procedure is similar to #Z3_simplify, but the behavior of the simplifier + can be configured using the given parameter set. + */ + Z3_ast Z3_API Z3_simplify_ex(__in Z3_context c, __in Z3_ast a, __in Z3_params p); + + /** + \brief Return a string describing all available parameters. + */ + Z3_string Z3_API Z3_simplify_get_help(__in Z3_context c); + + /** + \brief Return the parameter description set for the simplify procedure. + */ + Z3_param_descrs Z3_API Z3_simplify_get_param_descrs(__in Z3_context c); +#endif + + /*@}*/ + + /** + @name Modifiers + */ + /*@{*/ + + /** + \brief Update the arguments of term \c a using the arguments \c args. + The number of arguments \c num_args should coincide + with the number of arguments to \c a. + If \c a is a quantifier, then num_args has to be 1. + */ + Z3_ast Z3_API Z3_update_term(__in Z3_context c, __in Z3_ast a, __in unsigned num_args, __in_ecount(num_args) Z3_ast const args[]); + + /** + \brief Substitute every occurrence of from[i] in \c a with to[i], for \c i smaller than \c num_exprs. + The result is the new AST. The arrays \c from and \c to must have size \c num_exprs. + For every \c i smaller than \c num_exprs, we must have that sort of from[i] must be equal to sort of to[i]. + */ + Z3_ast Z3_API Z3_substitute(__in Z3_context c, + __in Z3_ast a, + __in unsigned num_exprs, + __in_ecount(num_exprs) Z3_ast const from[], + __in_ecount(num_exprs) Z3_ast const to[]); + + /** + \brief Substitute the free variables in \c a with the expressions in \c to. + For every \c i smaller than \c num_exprs, the variable with de-Bruijn index \c i is replaced with term to[i]. + */ + Z3_ast Z3_API Z3_substitute_vars(__in Z3_context c, + __in Z3_ast a, + __in unsigned num_exprs, + __in_ecount(num_exprs) Z3_ast const to[]); + +#ifdef CorML4 + /** + \brief Translate/Copy the AST \c a from context \c source to context \c target. + AST \c a must have been created using context \c source. + \pre source != target + */ + Z3_ast Z3_API Z3_translate(__in Z3_context source, __in Z3_ast a, __in Z3_context target); +#endif + + /*@}*/ + +#ifdef CorML4 + /** + @name Models + */ + /*@{*/ + +#ifdef ML4only +#include +#endif +#ifdef Conly + /** + \brief Increment the reference counter of the given model. + */ + void Z3_API Z3_model_inc_ref(__in Z3_context c, __in Z3_model m); + + /** + \brief Decrement the reference counter of the given model. + */ + void Z3_API Z3_model_dec_ref(__in Z3_context c, __in Z3_model m); +#endif + + /** + \brief \mlh model_eval c m t \endmlh + Evaluate the AST node \c t in the given model. + \conly Return \c Z3_TRUE if succeeded, and store the result in \c v. + \mlonly Return \c None if the term was not successfully evaluated. \endmlonly + + If \c model_completion is Z3_TRUE, then Z3 will assign an interpretation for any constant or function that does + not have an interpretation in \c m. These constants and functions were essentially don't cares. + + The evaluation may fail for the following reasons: + + - \c t contains a quantifier. + + - the model \c m is partial, that is, it doesn't have a complete interpretation for uninterpreted functions. + That is, the option MODEL_PARTIAL=true was used. + + - \c t is type incorrect. + */ + Z3_bool_opt Z3_API Z3_model_eval(__in Z3_context c, __in Z3_model m, __in Z3_ast t, __in Z3_bool model_completion, __out_opt Z3_ast * v); + + /** + \mlonly {4 {L Low-level API}} \endmlonly + */ + + /** + \brief Return the interpretation (i.e., assignment) of constant \c a in the model \c m. + Return \mlonly [None], \endmlonly \conly \c NULL, + if the model does not assign an interpretation for \c a. + That should be interpreted as: the value of \c a does not matter. + + \pre Z3_get_arity(c, a) == 0 + */ + Z3_ast_opt Z3_API Z3_model_get_const_interp(__in Z3_context c, __in Z3_model m, __in Z3_func_decl a); + + /** + \brief Return the interpretation of the function \c f in the model \c m. + Return \mlonly [None], \endmlonly \conly \c NULL, + if the model does not assign an interpretation for \c f. + That should be interpreted as: the \c f does not matter. + + \pre Z3_get_arity(c, f) > 0 + + \conly \remark Reference counting must be used to manage Z3_func_interp objects, even when the Z3_context was + \conly created using #Z3_mk_context instead of #Z3_mk_context_rc. + */ + Z3_func_interp_opt Z3_API Z3_model_get_func_interp(__in Z3_context c, __in Z3_model m, __in Z3_func_decl f); + + /** + \brief Return the number of constants assigned by the given model. + + \sa Z3_model_get_const_decl + */ + unsigned Z3_API Z3_model_get_num_consts(__in Z3_context c, __in Z3_model m); + + /** + \brief \mlh model_get_const_decl c m i \endmlh + Return the i-th constant in the given model. + + \pre i < Z3_model_get_num_consts(c, m) + + \sa Z3_model_eval + */ + Z3_func_decl Z3_API Z3_model_get_const_decl(__in Z3_context c, __in Z3_model m, __in unsigned i); + + /** + \brief Return the number of function interpretations in the given model. + + A function interpretation is represented as a finite map and an 'else' value. + Each entry in the finite map represents the value of a function given a set of arguments. + */ + unsigned Z3_API Z3_model_get_num_funcs(__in Z3_context c, __in Z3_model m); + + /** + \brief \mlh model_get_func_decl c m i \endmlh + Return the declaration of the i-th function in the given model. + + \pre i < Z3_model_get_num_funcs(c, m) + + \sa Z3_model_get_num_funcs + */ + Z3_func_decl Z3_API Z3_model_get_func_decl(__in Z3_context c, __in Z3_model m, __in unsigned i); + + /** + \brief Return the number of uninterpreted sorts that \c m assigs an interpretation to. + + Z3 also provides an intepretation for uninterpreted sorts used in a formua. + The interpretation for a sort \c s is a finite set of distinct values. We say this finite set is + the "universe" of \c s. + + \sa Z3_model_get_sort + \sa Z3_model_get_sort_universe + */ + unsigned Z3_API Z3_model_get_num_sorts(__in Z3_context c, __in Z3_model m); + + /** + \brief Return a uninterpreted sort that \c m assigns an interpretation. + + \pre i < Z3_model_get_num_sorts(c, m) + + \sa Z3_model_get_num_sorts + \sa Z3_model_get_sort_universe + */ + Z3_sort Z3_API Z3_model_get_sort(__in Z3_context c, __in Z3_model m, __in unsigned i); + + /** + \brief Return the finite set of distinct values that represent the interpretation for sort \c s. + + \sa Z3_model_get_num_sorts + \sa Z3_model_get_sort + */ + Z3_ast_vector Z3_API Z3_model_get_sort_universe(__in Z3_context c, __in Z3_model m, __in Z3_sort s); + + /** + \brief The (_ as-array f) AST node is a construct for assigning interpretations for arrays in Z3. + It is the array such that forall indices \c i we have that (select (_ as-array f) i) is equal to (f i). + This procedure returns Z3_TRUE if the \c a is an \c as-array AST node. + + Z3 current solvers have minimal support for \c as_array nodes. + + \sa Z3_get_as_array_func_decl + */ + Z3_bool Z3_API Z3_is_as_array(__in Z3_context c, __in Z3_ast a); + + /** + \brief Return the function declaration \c f associated with a (_ as_array f) node. + + \sa Z3_is_as_array + */ + Z3_func_decl Z3_API Z3_get_as_array_func_decl(__in Z3_context c, __in Z3_ast a); + +#ifdef Conly + /** + \brief Increment the reference counter of the given Z3_func_interp object. + */ + void Z3_API Z3_func_interp_inc_ref(__in Z3_context c, __in Z3_func_interp f); + + /** + \brief Decrement the reference counter of the given Z3_func_interp object. + */ + void Z3_API Z3_func_interp_dec_ref(__in Z3_context c, __in Z3_func_interp f); +#endif + + /** + \brief Return the number of entries in the given function interpretation. + + A function interpretation is represented as a finite map and an 'else' value. + Each entry in the finite map represents the value of a function given a set of arguments. + This procedure return the number of element in the finite map of \c f. + */ + unsigned Z3_API Z3_func_interp_get_num_entries(__in Z3_context c, __in Z3_func_interp f); + + /** + \brief Return a "point" of the given function intepretation. It represents the + value of \c f in a particular point. + + \pre i < Z3_func_interp_get_num_entries(c, f) + + \sa Z3_func_interp_get_num_entries + */ + Z3_func_entry Z3_API Z3_func_interp_get_entry(__in Z3_context c, __in Z3_func_interp f, unsigned i); + + /** + \brief Return the 'else' value of the given function interpretation. + + A function interpretation is represented as a finite map and an 'else' value. + This procedure returns the 'else' value. + */ + Z3_ast Z3_API Z3_func_interp_get_else(__in Z3_context c, __in Z3_func_interp f); + + /** + \brief Return the arity (number of arguments) of the given function interpretation. + */ + unsigned Z3_API Z3_func_interp_get_arity(__in Z3_context c, __in Z3_func_interp f); + +#ifdef Conly + /** + \brief Increment the reference counter of the given Z3_func_entry object. + */ + void Z3_API Z3_func_entry_inc_ref(__in Z3_context c, __in Z3_func_entry e); + + /** + \brief Decrement the reference counter of the given Z3_func_entry object. + */ + void Z3_API Z3_func_entry_dec_ref(__in Z3_context c, __in Z3_func_entry e); +#endif + + /** + \brief Return the value of this point. + + A Z3_func_entry object represents an element in the finite map used to encode + a function interpretation. + + \sa Z3_func_interp_get_entry + */ + Z3_ast Z3_API Z3_func_entry_get_value(__in Z3_context c, __in Z3_func_entry e); + + /** + \brief Return the number of arguments in a Z3_func_entry object. + + \sa Z3_func_interp_get_entry + */ + unsigned Z3_API Z3_func_entry_get_num_args(__in Z3_context c, __in Z3_func_entry e); + + /** + \brief Return an argument of a Z3_func_entry object. + + \pre i < Z3_func_entry_get_num_args(c, e) + + \sa Z3_func_interp_get_entry + */ + Z3_ast Z3_API Z3_func_entry_get_arg(__in Z3_context c, __in Z3_func_entry e, __in unsigned i); + + /*@}*/ +#endif // CorML4 + + /** + @name Interaction logging. + */ + /*@{*/ + + /** + \brief Log interaction to a file. + */ + Z3_bool Z3_API Z3_open_log(__in Z3_string filename); + + /** + \brief Append user-defined string to interaction log. + + The interaction log is opened using Z3_open_log. + It contains the formulas that are checked using Z3. + You can use this command to append comments, for instance. + */ + void Z3_API Z3_append_log(__in Z3_string string); + + + /** + \brief Close interaction log. + */ + void Z3_API Z3_close_log(); + + /** + \brief Enable/disable printing warning messages to the console. + + Warnings are printed after passing \c true, warning messages are + suppressed after calling this method with \c false. + */ + void Z3_API Z3_toggle_warning_messages(__in Z3_bool enabled); + + /*@}*/ + + /** + @name String conversion + */ + /*@{*/ + + /** + \brief Select mode for the format used for pretty-printing AST nodes. + + The default mode for pretty printing AST nodes is to produce + SMT-LIB style output where common subexpressions are printed + at each occurrence. The mode is called Z3_PRINT_SMTLIB_FULL. + To print shared common subexpressions only once, + use the Z3_PRINT_LOW_LEVEL mode. + To print in way that conforms to SMT-LIB standards and uses let + expressions to share common sub-expressions use Z3_PRINT_SMTLIB_COMPLIANT. + + \sa Z3_ast_to_string + \sa Z3_pattern_to_string + \sa Z3_func_decl_to_string + + */ + void Z3_API Z3_set_ast_print_mode(__in Z3_context c, __in Z3_ast_print_mode mode); + + /** + \brief Convert the given AST node into a string. + + \conly \warning The result buffer is statically allocated by Z3. It will + \conly be automatically deallocated when #Z3_del_context is invoked. + \conly So, the buffer is invalidated in the next call to \c Z3_ast_to_string. + \sa Z3_pattern_to_string + \sa Z3_sort_to_string + */ + Z3_string Z3_API Z3_ast_to_string(__in Z3_context c, __in Z3_ast a); + Z3_string Z3_API Z3_pattern_to_string(__in Z3_context c, __in Z3_pattern p); + Z3_string Z3_API Z3_sort_to_string(__in Z3_context c, __in Z3_sort s); + Z3_string Z3_API Z3_func_decl_to_string(__in Z3_context c, __in Z3_func_decl d); + + /** + \brief Convert the given model into a string. + + \conly \warning The result buffer is statically allocated by Z3. It will + \conly be automatically deallocated when #Z3_del_context is invoked. + \conly So, the buffer is invalidated in the next call to \c Z3_model_to_string. + */ + Z3_string Z3_API Z3_model_to_string(__in Z3_context c, __in Z3_model m); + + /** + \brief Convert the given benchmark into SMT-LIB formatted string. + + \conly \warning The result buffer is statically allocated by Z3. It will + \conly be automatically deallocated when #Z3_del_context is invoked. + \conly So, the buffer is invalidated in the next call to \c Z3_benchmark_to_smtlib_string. + + \param c - context. + \param name - name of benchmark. The argument is optional. + \param logic - the benchmark logic. + \param status - the status string (sat, unsat, or unknown) + \param attributes - other attributes, such as source, difficulty or category. + \param num_assumptions - number of assumptions. + \param assumptions - auxiliary assumptions. + \param formula - formula to be checked for consistency in conjunction with assumptions. + */ + Z3_string Z3_API Z3_benchmark_to_smtlib_string(__in Z3_context c, + __in Z3_string name, + __in Z3_string logic, + __in Z3_string status, + __in Z3_string attributes, + __in unsigned num_assumptions, + __in_ecount(num_assumptions) Z3_ast const assumptions[], + __in Z3_ast formula); + + /*@}*/ + + /** + @name Parser interface + */ + /*@{*/ + + /** + \brief \mlh parse_smtlib2_string c str \endmlh + Parse the given string using the SMT-LIB2 parser. + + It returns a formula comprising of the conjunction of assertions in the scope + (up to push/pop) at the end of the string. + */ + Z3_ast Z3_API Z3_parse_smtlib2_string(__in Z3_context c, + __in Z3_string str, + __in unsigned num_sorts, + __in_ecount(num_sorts) Z3_symbol const sort_names[], + __in_ecount(num_sorts) Z3_sort const sorts[], + __in unsigned num_decls, + __in_ecount(num_decls) Z3_symbol const decl_names[], + __in_ecount(num_decls) Z3_func_decl const decls[] + ); + + /** + \brief Similar to #Z3_parse_smtlib2_string, but reads the benchmark from a file. + */ + Z3_ast Z3_API Z3_parse_smtlib2_file(__in Z3_context c, + __in Z3_string file_name, + __in unsigned num_sorts, + __in_ecount(num_sorts) Z3_symbol const sort_names[], + __in_ecount(num_sorts) Z3_sort const sorts[], + __in unsigned num_decls, + __in_ecount(num_decls) Z3_symbol const decl_names[], + __in_ecount(num_decls) Z3_func_decl const decls[] + ); + +#ifdef ML4only +#include +#endif + + /** + \mlonly {4 {L Low-level API}} \endmlonly + */ + + /** + \brief \mlh parse_smtlib_string c str sort_names sorts decl_names decls \endmlh + Parse the given string using the SMT-LIB parser. + + The symbol table of the parser can be initialized using the given sorts and declarations. + The symbols in the arrays \c sort_names and \c decl_names don't need to match the names + of the sorts and declarations in the arrays \c sorts and \c decls. This is an useful feature + since we can use arbitrary names to reference sorts and declarations defined using the C API. + + The formulas, assumptions and declarations defined in \c str can be extracted using the functions: + #Z3_get_smtlib_num_formulas, #Z3_get_smtlib_formula, #Z3_get_smtlib_num_assumptions, #Z3_get_smtlib_assumption, + #Z3_get_smtlib_num_decls, and #Z3_get_smtlib_decl. + */ + void Z3_API Z3_parse_smtlib_string(__in Z3_context c, + __in Z3_string str, + __in unsigned num_sorts, + __in_ecount(num_sorts) Z3_symbol const sort_names[], + __in_ecount(num_sorts) Z3_sort const sorts[], + __in unsigned num_decls, + __in_ecount(num_decls) Z3_symbol const decl_names[], + __in_ecount(num_decls) Z3_func_decl const decls[] + ); + + /** + \brief Similar to #Z3_parse_smtlib_string, but reads the benchmark from a file. + */ + void Z3_API Z3_parse_smtlib_file(__in Z3_context c, + __in Z3_string file_name, + __in unsigned num_sorts, + __in_ecount(num_sorts) Z3_symbol const sort_names[], + __in_ecount(num_sorts) Z3_sort const sorts[], + __in unsigned num_decls, + __in_ecount(num_decls) Z3_symbol const decl_names[], + __in_ecount(num_decls) Z3_func_decl const decls[] + ); + + /** + \brief Return the number of SMTLIB formulas parsed by the last call to #Z3_parse_smtlib_string or #Z3_parse_smtlib_file. + */ + unsigned Z3_API Z3_get_smtlib_num_formulas(__in Z3_context c); + + /** + \brief \mlh get_smtlib_formula c i \endmlh + Return the i-th formula parsed by the last call to #Z3_parse_smtlib_string or #Z3_parse_smtlib_file. + + \pre i < Z3_get_smtlib_num_formulas(c) + */ + Z3_ast Z3_API Z3_get_smtlib_formula(__in Z3_context c, __in unsigned i); + + /** + \brief Return the number of SMTLIB assumptions parsed by #Z3_parse_smtlib_string or #Z3_parse_smtlib_file. + */ + unsigned Z3_API Z3_get_smtlib_num_assumptions(__in Z3_context c); + + /** + \brief \mlh get_smtlib_assumption c i \endmlh + Return the i-th assumption parsed by the last call to #Z3_parse_smtlib_string or #Z3_parse_smtlib_file. + + \pre i < Z3_get_smtlib_num_assumptions(c) + */ + Z3_ast Z3_API Z3_get_smtlib_assumption(__in Z3_context c, __in unsigned i); + + /** + \brief Return the number of declarations parsed by #Z3_parse_smtlib_string or #Z3_parse_smtlib_file. + */ + unsigned Z3_API Z3_get_smtlib_num_decls(__in Z3_context c); + + /** + \brief \mlh get_smtlib_decl c i \endmlh + Return the i-th declaration parsed by the last call to #Z3_parse_smtlib_string or #Z3_parse_smtlib_file. + + \pre i < Z3_get_smtlib_num_decls(c) + */ + Z3_func_decl Z3_API Z3_get_smtlib_decl(__in Z3_context c, __in unsigned i); + + /** + \brief Return the number of sorts parsed by #Z3_parse_smtlib_string or #Z3_parse_smtlib_file. + */ + unsigned Z3_API Z3_get_smtlib_num_sorts(__in Z3_context c); + + /** + \brief \mlh get_smtlib_sort c i \endmlh + Return the i-th sort parsed by the last call to #Z3_parse_smtlib_string or #Z3_parse_smtlib_file. + + \pre i < Z3_get_smtlib_num_sorts(c) + */ + Z3_sort Z3_API Z3_get_smtlib_sort(__in Z3_context c, __in unsigned i); + +BEGIN_MLAPI_EXCLUDE + /** + \brief \mlh get_smtlib_error c \endmlh + Retrieve that last error message information generated from parsing. + */ + Z3_string Z3_API Z3_get_smtlib_error(__in Z3_context c); +END_MLAPI_EXCLUDE + + /** + \brief \mlh parse_z3_string c str \endmlh + Parse the given string using the Z3 native parser. + + Return the conjunction of asserts made in the input. + */ + Z3_ast Z3_API Z3_parse_z3_string(__in Z3_context c, __in Z3_string str); + + /** + \brief Similar to #Z3_parse_z3_string, but reads the benchmark from a file. + */ + Z3_ast Z3_API Z3_parse_z3_file(__in Z3_context c, __in Z3_string file_name); + + /*@}*/ + +#ifdef CorML4 + /** + @name Error Handling + */ + /*@{*/ + +#ifndef SAFE_ERRORS + /** + \brief Return the error code for the last API call. + + A call to a Z3 function may return a non Z3_OK error code, + when it is not used correctly. + + \sa Z3_set_error_handler + */ + Z3_error_code Z3_API Z3_get_error_code(__in Z3_context c); + + /** + \brief Register a Z3 error handler. + + A call to a Z3 function may return a non Z3_OK error code, when + it is not used correctly. An error handler can be registered + and will be called in this case. \conly To disable the use of the + error handler, simply register with \c h=NULL. + + \warning Log files, created using #Z3_open_log, may be potentially incomplete/incorrect if error handlers are used. + + \sa Z3_get_error_code + */ + void Z3_API Z3_set_error_handler(__in Z3_context c, __in Z3_error_handler h); +#endif + + /** + \brief Set an error. + */ + void Z3_API Z3_set_error(__in Z3_context c, __in Z3_error_code e); + +#ifdef Conly + /** + \brief Return a string describing the given error code. + + \deprecated Use #Z3_get_error_msg_ex instead. + */ + Z3_string Z3_API Z3_get_error_msg(__in Z3_error_code err); +#endif + +BEGIN_MLAPI_EXCLUDE + /** + \brief Return a string describing the given error code. + */ + Z3_string Z3_API Z3_get_error_msg_ex(__in Z3_context c, __in Z3_error_code err); +END_MLAPI_EXCLUDE +#ifdef ML4only +#include +#endif + + + /*@}*/ +#endif + + /** + @name Miscellaneous + */ + /*@{*/ + + /** + \brief Return Z3 version number information. + */ + void Z3_API Z3_get_version(__out unsigned * major, + __out unsigned * minor, + __out unsigned * build_number, + __out unsigned * revision_number); + + +#ifdef CorML3 + /** + \brief Reset all allocated resources. + + Use this facility on out-of memory errors. + It allows discharging the previous state and resuming afresh. + Any pointers previously returned by the API + become invalid. + */ + void Z3_API Z3_reset_memory(void); +#endif + + /*@}*/ + +#ifdef CorML3 + /** + @name External Theory Plugins + */ + /*@{*/ + +#ifdef Conly + + // + // callbacks and void* don't work with CAMLIDL. + // + typedef Z3_bool Z3_reduce_eq_callback_fptr(__in Z3_theory t, __in Z3_ast a, __in Z3_ast b, __out Z3_ast * r); + + typedef Z3_bool Z3_reduce_app_callback_fptr(__in Z3_theory, __in Z3_func_decl, __in unsigned, __in Z3_ast const [], __out Z3_ast *); + + typedef Z3_bool Z3_reduce_distinct_callback_fptr(__in Z3_theory, __in unsigned, __in Z3_ast const [], __out Z3_ast *); + + typedef void Z3_theory_callback_fptr(__in Z3_theory t); + + typedef Z3_bool Z3_theory_final_check_callback_fptr(__in Z3_theory); + + typedef void Z3_theory_ast_callback_fptr(__in Z3_theory, __in Z3_ast); + + typedef void Z3_theory_ast_bool_callback_fptr(__in Z3_theory, __in Z3_ast, __in Z3_bool); + + typedef void Z3_theory_ast_ast_callback_fptr(__in Z3_theory, __in Z3_ast, __in Z3_ast); + +#endif + +#ifdef Conly + /** + \brief Create a new user defined theory. The new theory will be identified by the name \c th_name. + A theory must be created before asserting any assertion to the given context. + \conly Return \c NULL in case of failure. + + \conly \c data is a pointer to an external data-structure that may be used to store + \conly theory specific additional data. + */ + Z3_theory Z3_API Z3_mk_theory(__in Z3_context c, __in Z3_string th_name, __in Z3_theory_data data); + + /** + \brief Return a pointer to the external data-structure supplied to the function #Z3_mk_theory. + + \see Z3_mk_theory + */ + Z3_theory_data Z3_API Z3_theory_get_ext_data(__in Z3_theory t); +#endif + + /** + \brief Create an interpreted theory sort. + */ + Z3_sort Z3_API Z3_theory_mk_sort(__in Z3_context c, __in Z3_theory t, __in Z3_symbol s); + + /** + \brief Create an interpreted theory constant value. Values are assumed to be different from each other. + */ + Z3_ast Z3_API Z3_theory_mk_value(__in Z3_context c, __in Z3_theory t, __in Z3_symbol n, __in Z3_sort s); + + /** + \brief Create an interpreted constant for the given theory. + */ + Z3_ast Z3_API Z3_theory_mk_constant(__in Z3_context c, __in Z3_theory t, __in Z3_symbol n, __in Z3_sort s); + + /** + \brief Create an interpreted function declaration for the given theory. + */ + Z3_func_decl Z3_API Z3_theory_mk_func_decl(__in Z3_context c, __in Z3_theory t, __in Z3_symbol n, + __in unsigned domain_size, __in_ecount(domain_size) Z3_sort const domain[], + __in Z3_sort range); + + /** + \brief Return the context where the given theory is installed. + */ + Z3_context Z3_API Z3_theory_get_context(__in Z3_theory t); + + +#ifdef Conly + /** + \brief Set a callback that is invoked when theory \c t is deleted. + This callback should be used to delete external data-structures associated with the given theory. + + \conly The callback has the form f(t), where + \conly - \c t is the given theory + + \see Z3_mk_theory + \conly \see Z3_theory_get_ext_data + */ + void Z3_API Z3_set_delete_callback(__in Z3_theory t, __in Z3_theory_callback_fptr f); + + /** + \brief Set a callback for simplifying operators of the given theory. + The callback \c f is invoked by Z3's simplifier. + + \conly It is of the form f(t, d, n, args, r), where: + \conly - \c t is the given theory + \conly - \c d is the declaration of the theory operator + \conly - \c n is the number of arguments in the array \c args + \conly - \c args are arguments for the theory operator + \conly - \c r should contain the result: an expression equivalent to d(args[0], ..., args[n-1]). + + \conly If f(t, d, n, args, r) returns false, then \c r is ignored, and Z3 assumes that no simplification was performed. + */ + void Z3_API Z3_set_reduce_app_callback(__in Z3_theory t, __in Z3_reduce_app_callback_fptr f); + + /** + \brief Set a callback for simplifying the atom s_1 = s_2, when the + sort of \c s_1 and \c s_2 is an interpreted sort of the given theory. + The callback \c f is invoked by Z3's simplifier. + + \conly It has the form f(t, s_1, s_2, r), where: + \conly - \c t is the given theory + \conly - \c s_1 is the left-hand-side + \conly - \c s_2 is the right-hand-side + \conly - \c r should contain the result: an expression equivalent to s_1 = s_2. + + \conly If f(t, s_1, s_2, r) returns false, then \c r is ignored, and Z3 assumes that no simplification was performed. + */ + void Z3_API Z3_set_reduce_eq_callback(__in Z3_theory t, __in Z3_reduce_eq_callback_fptr f); + + /** + \brief Set a callback for simplifying the atom distinct(s_1, ..., s_n), when the + sort of \c s_1, ..., \c s_n is an interpreted sort of the given theory. + The callback \c f is invoked by Z3's simplifier. + + \conly It has the form f(t, n, args, r), where: + \conly - \c t is the given theory + \conly - \c n is the number of arguments in the array \c args + \conly - \c args are arguments for distinct. + \conly - \c r should contain the result: an expression equivalent to distinct(s_1, ..., s_n). + + \conly If f(t, n, args, r) returns false, then \c r is ignored, and Z3 assumes that no simplification was performed. + */ + void Z3_API Z3_set_reduce_distinct_callback(__in Z3_theory t, __in Z3_reduce_distinct_callback_fptr f); + + /** + \brief Set a callback that is invoked when a theory application + is finally added into the logical context. Note that, not every + application contained in an asserted expression is actually + added into the logical context because it may be simplified + during a preprocessing step. + + \conly The callback has the form f(t, n), where + \conly - \c t is the given theory + + \conly - \c n is a theory application, that is, an expression of the form g(...) where \c g is a theory operator. + + \remark An expression \c n added to the logical context at search level \c n, + will remain in the logical context until this level is backtracked. + */ + void Z3_API Z3_set_new_app_callback(__in Z3_theory t, __in Z3_theory_ast_callback_fptr f); + + /** + \brief Set a callback that is invoked when an expression of + sort \c s, where \c s is an interpreted sort of the theory \c + t, is finally added into the logical context. Note that, not + every expression contained in an asserted expression is + actually added into the logical context because it may be + simplified during a preprocessing step. + + \conly The callback has the form f(t, n), where + \conly - \c t is the given theory + + \conly - \c n is an expression of sort \c s, where \c s is an interpreted sort of \c t. + + \remark An expression \c n added to the logical context at search level \c n, + will remain in the logical context until this level is backtracked. + */ + void Z3_API Z3_set_new_elem_callback(__in Z3_theory t, __in Z3_theory_ast_callback_fptr f); + + /** + \brief Set a callback that is invoked when Z3 starts searching for a + satisfying assignment. + + \conly The callback has the form f(t), where + \conly - \c t is the given theory + */ + void Z3_API Z3_set_init_search_callback(__in Z3_theory t, __in Z3_theory_callback_fptr f); + + /** + \brief Set a callback that is invoked when Z3 creates a + case-split (aka backtracking point). + + When a case-split is created we say the search level is increased. + + \conly The callback has the form f(t), where + \conly - \c t is the given theory + */ + void Z3_API Z3_set_push_callback(__in Z3_theory t, __in Z3_theory_callback_fptr f); + + /** + \brief Set a callback that is invoked when Z3 backtracks a + case-split. + + When a case-split is backtracked we say the search level is decreased. + + \conly The callback has the form f(t), where + \conly - \c t is the given theory + */ + void Z3_API Z3_set_pop_callback(__in Z3_theory t, __in Z3_theory_callback_fptr f); + + /** + \brief Set a callback that is invoked when Z3 restarts the + search for a satisfying assignment. + + \conly The callback has the form f(t), where + \conly - \c t is the given theory + */ + void Z3_API Z3_set_restart_callback(__in Z3_theory t, __in Z3_theory_callback_fptr f); + + /** + \brief Set a callback that is invoked when the logical context + is reset by the user. This callback is useful for reseting any + data-structure maintained by the user theory solver. + + \conly The callback has the form f(t), where + \conly - \c t is the given theory + */ + void Z3_API Z3_set_reset_callback(__in Z3_theory t, __in Z3_theory_callback_fptr f); + + /** + \brief Set a callback that is invoked before Z3 starts building a model. + A theory may use this callback to perform expensive operations. + + \conly The callback has the form f(t), where + \conly - \c t is the given theory + + If the theory returns \mlonly \c false, \endmlonly \conly \c Z3_false, + Z3 will assume that theory is giving up, + and it will assume that it was not possible to decide if the asserted constraints + are satisfiable or not. + */ + void Z3_API Z3_set_final_check_callback(__in Z3_theory t, __in Z3_theory_final_check_callback_fptr f); + + /** + \brief Set a callback that is invoked when an equality s_1 = s_2 + is found by the logical context. + + \conly The callback has the form f(t, s_1, s_2), where: + \conly - \c t is the given theory + \conly - \c s_1 is the left-hand-side + \conly - \c s_2 is the right-hand-side + */ + void Z3_API Z3_set_new_eq_callback(__in Z3_theory t, __in Z3_theory_ast_ast_callback_fptr f); + + /** + \brief Set a callback that is invoked when a disequality s_1 != s_2 + is found by the logical context. + + \conly The callback has the form f(t, s_1, s_2), where: + \conly - \c t is the given theory + \conly - \c s_1 is the left-hand-side + \conly - \c s_2 is the right-hand-side + */ + void Z3_API Z3_set_new_diseq_callback(__in Z3_theory t, __in Z3_theory_ast_ast_callback_fptr f); + + /** + \brief Set a callback that is invoked when a theory predicate is assigned to true/false by Z3. + + \conly The callback has the form f(t, p, v), where: + \conly - \c t is the given theory + \conly - \c p is the assigned predicate. + \conly - \c v is the value (true/false) assigned to \c p. + */ + void Z3_API Z3_set_new_assignment_callback(__in Z3_theory t, __in Z3_theory_ast_bool_callback_fptr f); + + /** + \brief Set a callback that is invoked when an expression is + marked as relevant during the search. This callback is only + invoked when relevancy propagation is enabled. + + \conly The callback has the form f(t, n), where: + \conly - \c t is the given theory + \conly - \c n is the relevant expression + */ + void Z3_API Z3_set_new_relevant_callback(__in Z3_theory t, __in Z3_theory_ast_callback_fptr f); + +#endif + + /** + \brief Assert a theory axiom/lemmas during the search. + + An axiom added at search level \c n will remain in the logical context until + level \c n is backtracked. + + The callbacks for push (#Z3_set_push_callback) and pop + (#Z3_set_pop_callback) can be used to track when the search + level is increased (i.e., new case-split) and decreased (i.e., + case-split is backtracked). + + Z3 tracks the theory axioms asserted. So, multiple assertions of the same axiom are + ignored. + */ + void Z3_API Z3_theory_assert_axiom(__in Z3_theory t, __in Z3_ast ax); + + /** + \brief Inform to the logical context that \c lhs and \c rhs have the same interpretation + in the model being built by theory \c t. If lhs = rhs is inconsistent with other theories, + then the logical context will backtrack. + + For more information, see the paper "Model-Based Theory Combination" in the Z3 website. + */ + void Z3_API Z3_theory_assume_eq(__in Z3_theory t, __in Z3_ast lhs, __in Z3_ast rhs); + + /** + \brief Enable/disable the simplification of theory axioms asserted using #Z3_theory_assert_axiom. + By default, the simplification of theory specific operators is disabled. + That is, the reduce theory callbacks are not invoked for theory axioms. + The default behavior is useful when asserting axioms stating properties of theory operators. + */ + void Z3_API Z3_theory_enable_axiom_simplification(__in Z3_theory t, __in Z3_bool flag); + + /** + \brief Return the root of the equivalence class containing \c n. + */ + Z3_ast Z3_API Z3_theory_get_eqc_root(__in Z3_theory t, __in Z3_ast n); + + /** + \brief Return the next element in the equivalence class containing \c n. + + The elements in an equivalence class are organized in a circular list. + You can traverse the list by calling this function multiple times + using the result from the previous call. This is illustrated in the + code snippet below. + \code + Z3_ast curr = n; + do + curr = Z3_theory_get_eqc_next(theory, curr); + while (curr != n); + \endcode + */ + Z3_ast Z3_API Z3_theory_get_eqc_next(__in Z3_theory t, __in Z3_ast n); + + /** + \brief Return the number of parents of \c n that are operators of the given theory. + */ + unsigned Z3_API Z3_theory_get_num_parents(__in Z3_theory t, __in Z3_ast n); + + /** + \brief Return the i-th parent of \c n. + See #Z3_theory_get_num_parents. + */ + Z3_ast Z3_API Z3_theory_get_parent(__in Z3_theory t, __in Z3_ast n, __in unsigned i); + + /** + \brief Return \c Z3_TRUE if \c n is an interpreted theory value. + */ + Z3_bool Z3_API Z3_theory_is_value(__in Z3_theory t, __in Z3_ast n); + + /** + \brief Return \c Z3_TRUE if \c d is an interpreted theory declaration. + */ + Z3_bool Z3_API Z3_theory_is_decl(__in Z3_theory t, __in Z3_func_decl d); + + /** + \brief Return the number of expressions of the given theory in + the logical context. These are the expressions notified using the + callback #Z3_set_new_elem_callback. + */ + unsigned Z3_API Z3_theory_get_num_elems(__in Z3_theory t); + + /** + \brief Return the i-th elem of the given theory in the logical context. + + \see Z3_theory_get_num_elems + */ + Z3_ast Z3_API Z3_theory_get_elem(__in Z3_theory t, __in unsigned i); + + /** + \brief Return the number of theory applications in the logical + context. These are the expressions notified using the callback + #Z3_set_new_app_callback. + */ + unsigned Z3_API Z3_theory_get_num_apps(__in Z3_theory t); + + /** + \brief Return the i-th application of the given theory in the logical context. + + \see Z3_theory_get_num_apps + */ + Z3_ast Z3_API Z3_theory_get_app(__in Z3_theory t, __in unsigned i); + + /*@}*/ + +#endif + +#ifdef CorML4 + /** + @name Fixedpoint facilities + */ + /*@{*/ + + /** + \brief Create a new fixedpoint context. + + \conly \remark User must use #Z3_fixedpoint_inc_ref and #Z3_fixedpoint_dec_ref to manage fixedpoint objects. + \conly Even if the context was created using #Z3_mk_context instead of #Z3_mk_context_rc. + */ + Z3_fixedpoint Z3_API Z3_mk_fixedpoint(__in Z3_context c); + +#ifdef Conly + /** + \brief Increment the reference counter of the given fixedpoint context + */ + void Z3_API Z3_fixedpoint_inc_ref(__in Z3_context c,__in Z3_fixedpoint d); + + /** + \brief Decrement the reference counter of the given fixedpoint context. + */ + void Z3_API Z3_fixedpoint_dec_ref(__in Z3_context c,__in Z3_fixedpoint d); +#endif + + /** + \brief Add a universal Horn clause as a named rule. + The \c horn_rule should be of the form: + + \code + horn_rule ::= (forall (bound-vars) horn_rule) + | (=> atoms horn_rule) + | atom + \endcode + */ + void Z3_API Z3_fixedpoint_add_rule(__in Z3_context c,__in Z3_fixedpoint d, __in Z3_ast rule, __in Z3_symbol name); + + /** + \brief Add a Database fact. + + \param c - context + \param d - fixed point context + \param r - relation signature for the row. + \param num_args - number of columns for the given row. + \param args - array of the row elements. + + The number of arguments \c num_args should be equal to the number + of sorts in the domain of \c r. Each sort in the domain should be an integral + (bit-vector, Boolean or or finite domain sort). + + The call has the same effect as adding a rule where \r is applied to the arguments. + + */ + void Z3_API Z3_fixedpoint_add_fact(__in Z3_context c,__in Z3_fixedpoint d, + __in Z3_func_decl r, + __in unsigned num_args, __in_ecount(num_args) unsigned args[]); + + /** + \brief Assert a constraint to the fixedpoint context. + + The constraints are used as background axioms when the fixedpoint engine uses the PDR mode. + They are ignored for standard Datalog mode. + */ + void Z3_API Z3_fixedpoint_assert(__in Z3_context c,__in Z3_fixedpoint d, __in Z3_ast axiom); + + /** + \brief Pose a query against the asserted rules. + + \code + query ::= (exists (bound-vars) query) + | literals + \endcode + + query returns + - Z3_L_FALSE if the query is unsatisfiable. + - Z3_L_TRUE if the query is satisfiable. Obtain the answer by calling #Z3_fixedpoint_get_answer. + - Z3_L_UNDEF if the query was interrupted, timed out or otherwise failed. + + */ + Z3_lbool Z3_API Z3_fixedpoint_query(__in Z3_context c,__in Z3_fixedpoint d, __in Z3_ast query); + + + /** + \brief Pose multiple queries against the asserted rules. + + The queries are encoded as relations (function declarations). + + query returns + - Z3_L_FALSE if the query is unsatisfiable. + - Z3_L_TRUE if the query is satisfiable. Obtain the answer by calling #Z3_fixedpoint_get_answer. + - Z3_L_UNDEF if the query was interrupted, timed out or otherwise failed. + + */ + Z3_lbool Z3_API Z3_fixedpoint_query_relations( + __in Z3_context c,__in Z3_fixedpoint d, + __in unsigned num_relations, __in_ecount(num_relations) Z3_func_decl const relations[]); + + + /** + \brief Retrieve a formula that encodes satisfying answers to the query. + + + When used in Datalog mode, the returned answer is a disjunction of conjuncts. + Each conjunct encodes values of the bound variables of the query that are satisfied. + In PDR mode, the returned answer is a single conjunction. + + The previous call to Z3_fixedpoint_query must have returned Z3_L_TRUE. + */ + Z3_ast Z3_API Z3_fixedpoint_get_answer(__in Z3_context c,__in Z3_fixedpoint d); + + /** + \brief Retrieve a string that describes the last status returned by #Z3_fixedpoint_query. + + Use this method when #Z3_fixedpoint_query returns Z3_L_UNDEF. + */ + Z3_string Z3_API Z3_fixedpoint_get_reason_unknown(__in Z3_context c,__in Z3_fixedpoint d); + + /** + \brief Update a named rule. + A rule with the same name must have been previously created. + */ + void Z3_API Z3_fixedpoint_update_rule(__in Z3_context c, __in Z3_fixedpoint d, __in Z3_ast a, __in Z3_symbol name); + + /** + \brief Query the PDR engine for the maximal levels properties are known about predicate. + + This call retrieves the maximal number of relevant unfoldings + of \c pred with respect to the current exploration state. + Note: this functionality is PDR specific. + */ + unsigned Z3_API Z3_fixedpoint_get_num_levels(Z3_context c, Z3_fixedpoint d, Z3_func_decl pred); + + /** + Retrieve the current cover of \c pred up to \c level unfoldings. + Return just the delta that is known at \c level. To + obtain the full set of properties of \c pred one should query + at \c level+1 , \c level+2 etc, and include \c level=-1. + + Note: this functionality is PDR specific. + */ + Z3_ast Z3_API Z3_fixedpoint_get_cover_delta(Z3_context c, Z3_fixedpoint d, int level, Z3_func_decl pred); + + /** + \brief Add property about the predicate \c pred. + Add a property of predicate \c pred at \c level. + It gets pushed forward when possible. + + Note: level = -1 is treated as the fixedpoint. So passing -1 for the \c level + means that the property is true of the fixed-point unfolding with respect to \c pred. + + Note: this functionality is PDR specific. + */ + void Z3_API Z3_fixedpoint_add_cover(Z3_context c, Z3_fixedpoint d, int level, Z3_func_decl pred, Z3_ast property); + + /** + \brief Retrieve statistics information from the last call to #Z3_fixedpoint_query. + */ + Z3_stats Z3_API Z3_fixedpoint_get_statistics(__in Z3_context c,__in Z3_fixedpoint d); + + /** + \brief Register relation as Fixedpoint defined. + Fixedpoint defined relations have least-fixedpoint semantics. + For example, the relation is empty if it does not occur + in a head or a fact. + */ + void Z3_API Z3_fixedpoint_register_relation(__in Z3_context c,__in Z3_fixedpoint d, __in Z3_func_decl f); + + /** + \brief Configure the predicate representation. + + It sets the predicate to use a set of domains given by the list of symbols. + The domains given by the list of symbols must belong to a set + of built-in domains. + */ + void Z3_API Z3_fixedpoint_set_predicate_representation( + __in Z3_context c, + __in Z3_fixedpoint d, + __in Z3_func_decl f, + __in unsigned num_relations, + __in_ecount(num_relations) Z3_symbol const relation_kinds[]); + + /** + \brief Simplify rules into a set of new rules that are returned. + The simplification routines apply inlining, quantifier elimination, and other + algebraic simplifications. + */ + Z3_ast_vector Z3_API Z3_fixedpoint_simplify_rules( + __in Z3_context c, + __in Z3_fixedpoint f, + __in unsigned num_rules, + __in_ecount(num_rules) Z3_ast rules[], + __in unsigned num_outputs, + __in_ecount(num_outputs) Z3_func_decl outputs[]); + + /** + \brief Set parameters on fixedpoint context. + */ + void Z3_API Z3_fixedpoint_set_params(__in Z3_context c, __in Z3_fixedpoint f, __in Z3_params p); + + /** + \brief Return a string describing all fixedpoint available parameters. + */ + Z3_string Z3_API Z3_fixedpoint_get_help(__in Z3_context c, __in Z3_fixedpoint f); + + /** + \brief Return the parameter description set for the given fixedpoint object. + */ + Z3_param_descrs Z3_API Z3_fixedpoint_get_param_descrs(__in Z3_context c, __in Z3_fixedpoint f); + + /** + \brief Print the current rules and background axioms as a string. + \param c - context. + \param f - fixedpoint context. + \param num_queries - number of additional queries to print. + \param queries - additional queries. + */ + Z3_string Z3_API Z3_fixedpoint_to_string( + __in Z3_context c, + __in Z3_fixedpoint f, + __in unsigned num_queries, + __in_ecount(num_queries) Z3_ast queries[]); + + /** + \brief Create a backtracking point. + + The fixedpoint solver contains a set of rules, added facts and assertions. + The set of rules, facts and assertions are restored upon calling #Z3_fixedpoint_pop. + + \sa Z3_fixedpoint_pop + */ + void Z3_API Z3_fixedpoint_push(Z3_context c,Z3_fixedpoint d); + + /** + \brief Backtrack one backtracking point. + + \sa Z3_fixedpoing_push + + \pre The number of calls to pop cannot exceed calls to push. + */ + void Z3_API Z3_fixedpoint_pop(Z3_context c,Z3_fixedpoint d); + +#ifdef Conly + + /** + \brief The following utilities allows adding user-defined domains. + */ + + typedef void Z3_fixedpoint_reduce_assign_callback_fptr( + __in void*, __in Z3_func_decl, + __in unsigned, __in Z3_ast const [], + __in unsigned, __in Z3_ast const []); + + typedef void Z3_fixedpoint_reduce_app_callback_fptr( + __in void*, __in Z3_func_decl, + __in unsigned, __in Z3_ast const [], + __out Z3_ast*); + + + /** + \brief Initialize the context with a user-defined state. + */ + void Z3_API Z3_fixedpoint_init(__in Z3_context c,__in Z3_fixedpoint d, __in void* state); + + + /** + \brief Register a callback to destructive updates. + + Registers are identified with terms encoded as fresh constants, + */ + void Z3_API Z3_fixedpoint_set_reduce_assign_callback( + __in Z3_context c,__in Z3_fixedpoint d, __in Z3_fixedpoint_reduce_assign_callback_fptr cb); + + /** + \brief Register a callback for buildling terms based on + the relational operators. + */ + void Z3_API Z3_fixedpoint_set_reduce_app_callback( + __in Z3_context c,__in Z3_fixedpoint d, __in Z3_fixedpoint_reduce_app_callback_fptr cb); + +#endif +#endif + +#ifdef CorML4 + /*@}*/ + + /** + @name AST vectors + */ + /*@{*/ + + /** + \brief Return an empty AST vector. + + \conly \remark Reference counting must be used to manage AST vectors, even when the Z3_context was + \conly created using #Z3_mk_context instead of #Z3_mk_context_rc. + */ + Z3_ast_vector Z3_API Z3_mk_ast_vector(__in Z3_context c); + +#ifdef Conly + /** + \brief Increment the reference counter of the given AST vector. + */ + void Z3_API Z3_ast_vector_inc_ref(__in Z3_context c, __in Z3_ast_vector v); + + /** + \brief Decrement the reference counter of the given AST vector. + */ + void Z3_API Z3_ast_vector_dec_ref(__in Z3_context c, __in Z3_ast_vector v); +#endif + + /** + \brief Return the size of the given AST vector. + */ + unsigned Z3_API Z3_ast_vector_size(__in Z3_context c, __in Z3_ast_vector v); + + /** + \brief Return the AST at position \c i in the AST vector \c v. + + \pre i < Z3_ast_vector_size(c, v) + */ + Z3_ast Z3_API Z3_ast_vector_get(__in Z3_context c, __in Z3_ast_vector v, __in unsigned i); + + /** + \brief Update position \c i of the AST vector \c v with the AST \c a. + + \pre i < Z3_ast_vector_size(c, v) + */ + void Z3_API Z3_ast_vector_set(__in Z3_context c, __in Z3_ast_vector v, __in unsigned i, __in Z3_ast a); + + /** + \brief Resize the AST vector \c v. + */ + void Z3_API Z3_ast_vector_resize(__in Z3_context c, __in Z3_ast_vector v, __in unsigned n); + + /** + \brief Add the AST \c a in the end of the AST vector \c v. The size of \c v is increased by one. + */ + void Z3_API Z3_ast_vector_push(__in Z3_context c, __in Z3_ast_vector v, __in Z3_ast a); + + /** + \brief Translate the AST vector \c v from context \c s into an AST vector in context \c t. + */ + Z3_ast_vector Z3_API Z3_ast_vector_translate(__in Z3_context s, __in Z3_ast_vector v, __in Z3_context t); + + /** + \brief Convert AST vector into a string. + */ + Z3_string Z3_API Z3_ast_vector_to_string(__in Z3_context c, __in Z3_ast_vector v); + + /*@}*/ + + /** + @name AST maps + */ + /*@{*/ + + /** + \brief Return an empty mapping from AST to AST + + \conly \remark Reference counting must be used to manage AST maps, even when the Z3_context was + \conly created using #Z3_mk_context instead of #Z3_mk_context_rc. + */ + Z3_ast_map Z3_API Z3_mk_ast_map(__in Z3_context c); + +#ifdef Conly + /** + \brief Increment the reference counter of the given AST map. + */ + void Z3_API Z3_ast_map_inc_ref(__in Z3_context c, __in Z3_ast_map m); + + /** + \brief Decrement the reference counter of the given AST map. + */ + void Z3_API Z3_ast_map_dec_ref(__in Z3_context c, __in Z3_ast_map m); +#endif + + /** + \brief Return true if the map \c m contains the AST key \c k. + */ + Z3_bool Z3_API Z3_ast_map_contains(__in Z3_context c, __in Z3_ast_map m, __in Z3_ast k); + + /** + \brief Return the value associated with the key \c k. + + The procedure invokes the error handler if \c k is not in the map. + */ + Z3_ast Z3_API Z3_ast_map_find(__in Z3_context c, __in Z3_ast_map m, __in Z3_ast k); + + /** + \brief Store/Replace a new key, value pair in the given map. + */ + void Z3_API Z3_ast_map_insert(__in Z3_context c, __in Z3_ast_map m, __in Z3_ast k, __in Z3_ast v); + + /** + \brief Erase a key from the map. + */ + void Z3_API Z3_ast_map_erase(__in Z3_context c, __in Z3_ast_map m, __in Z3_ast k); + + /** + \brief Remove all keys from the given map. + */ + void Z3_API Z3_ast_map_reset(__in Z3_context c, __in Z3_ast_map m); + + /** + \brief Return the size of the given map. + */ + unsigned Z3_API Z3_ast_map_size(__in Z3_context c, __in Z3_ast_map m); + + /** + \brief Return the keys stored in the given map. + */ + Z3_ast_vector Z3_API Z3_ast_map_keys(__in Z3_context c, __in Z3_ast_map m); + + /** + \brief Convert the given map into a string. + */ + Z3_string Z3_API Z3_ast_map_to_string(__in Z3_context c, __in Z3_ast_map m); + + /*@}*/ + + /** + @name Goals + */ + /*@{*/ + + /** + \brief Create a goal (aka problem). A goal is essentially a set + of formulas, that can be solved and/or transformed using + tactics and solvers. + + If models == true, then model generation is enabled for the new goal. + + If unsat_cores == true, then unsat core generation is enabled for the new goal. + + If proofs == true, then proof generation is enabled for the new goal. Remark, the + Z3 context c must have been created with proof generation support. + + \conly \remark Reference counting must be used to manage goals, even when the Z3_context was + \conly created using #Z3_mk_context instead of #Z3_mk_context_rc. + */ + Z3_goal Z3_API Z3_mk_goal(__in Z3_context c, __in Z3_bool models, __in Z3_bool unsat_cores, __in Z3_bool proofs); + +#ifdef Conly + /** + \brief Increment the reference counter of the given goal. + */ + void Z3_API Z3_goal_inc_ref(__in Z3_context c, __in Z3_goal g); + + /** + \brief Decrement the reference counter of the given goal. + */ + void Z3_API Z3_goal_dec_ref(__in Z3_context c, __in Z3_goal g); +#endif + + /** + \brief Return the "precision" of the given goal. Goals can be transformed using over and under approximations. + A under approximation is applied when the objective is to find a model for a given goal. + An over approximation is applied when the objective is to find a proof for a given goal. + */ + Z3_goal_prec Z3_API Z3_goal_precision(__in Z3_context c, __in Z3_goal g); + + /** + \brief Add a new formula \c a to the given goal. + */ + void Z3_API Z3_goal_assert(__in Z3_context c, __in Z3_goal g, __in Z3_ast a); + + /** + \brief Return true if the given goal contains the formula \c false. + */ + Z3_bool Z3_API Z3_goal_inconsistent(__in Z3_context c, __in Z3_goal g); + + /** + \brief Return the depth of the given goal. It tracks how many transformations were applied to it. + */ + unsigned Z3_API Z3_goal_depth(__in Z3_context c, __in Z3_goal g); + + /** + \brief Erase all formulas from the given goal. + */ + void Z3_API Z3_goal_reset(__in Z3_context c, __in Z3_goal g); + + /** + \brief Return the number of formulas in the given goal. + */ + unsigned Z3_API Z3_goal_size(__in Z3_context c, __in Z3_goal g); + + /** + \brief Return a formula from the given goal. + + \pre idx < Z3_goal_size(c, g) + */ + Z3_ast Z3_API Z3_goal_formula(__in Z3_context c, __in Z3_goal g, __in unsigned idx); + + /** + \brief Return the number of formulas, subformulas and terms in the given goal. + */ + unsigned Z3_API Z3_goal_num_exprs(__in Z3_context c, __in Z3_goal g); + + /** + \brief Return true if the goal is empty, and it is precise or the product of a under approximation. + */ + Z3_bool Z3_API Z3_goal_is_decided_sat(__in Z3_context c, __in Z3_goal g); + + /** + \brief Return true if the goal contains false, and it is precise or the product of an over approximation. + */ + Z3_bool Z3_API Z3_goal_is_decided_unsat(__in Z3_context c, __in Z3_goal g); + + /** + \brief Copy a goal \c g from the context \c source to a the context \c target. + */ + Z3_goal Z3_API Z3_goal_translate(__in Z3_context source, __in Z3_goal g, __in Z3_context target); + + /** + \brief Convert a goal into a string. + */ + Z3_string Z3_API Z3_goal_to_string(__in Z3_context c, __in Z3_goal g); + + /*@}*/ + + /** + @name Tactics and Probes + */ + /*@{*/ + + /** + \brief Return a tactic associated with the given name. + The complete list of tactics may be obtained using the procedures #Z3_get_num_tactics and #Z3_get_tactic_name. + It may also be obtained using the command (help-tactics) in the SMT 2.0 front-end. + + Tactics are the basic building block for creating custom solvers for specific problem domains. + */ + Z3_tactic Z3_API Z3_mk_tactic(__in Z3_context c, __in Z3_string name); + +#ifdef Conly + /** + \brief Increment the reference counter of the given tactic. + */ + void Z3_API Z3_tactic_inc_ref(__in Z3_context c, __in Z3_tactic t); + + /** + \brief Decrement the reference counter of the given tactic. + */ + void Z3_API Z3_tactic_dec_ref(__in Z3_context c, __in Z3_tactic g); +#endif + + /** + \brief Return a probe associated with the given name. + The complete list of probes may be obtained using the procedures #Z3_get_num_probes and #Z3_get_probe_name. + It may also be obtained using the command (help-tactics) in the SMT 2.0 front-end. + + Probes are used to inspect a goal (aka problem) and collect information that may be used to decide + which solver and/or preprocessing step will be used. + */ + Z3_probe Z3_API Z3_mk_probe(__in Z3_context c, __in Z3_string name); + +#ifdef Conly + /** + \brief Increment the reference counter of the given probe. + */ + void Z3_API Z3_probe_inc_ref(__in Z3_context c, __in Z3_probe p); + + /** + \brief Decrement the reference counter of the given probe. + */ + void Z3_API Z3_probe_dec_ref(__in Z3_context c, __in Z3_probe p); +#endif + + /** + \brief Return a tactic that applies \c t1 to a given goal and \c t2 + to every subgoal produced by t1. + */ + Z3_tactic Z3_API Z3_tactic_and_then(__in Z3_context c, __in Z3_tactic t1, __in Z3_tactic t2); + + /** + \brief Return a tactic that first applies \c t1 to a given goal, + if it fails then returns the result of \c t2 applied to the given goal. + */ + Z3_tactic Z3_API Z3_tactic_or_else(__in Z3_context c, __in Z3_tactic t1, __in Z3_tactic t2); + + /** + \brief Return a tactic that applies the given tactics in parallel. + */ + Z3_tactic Z3_API Z3_tactic_par_or(__in Z3_context c, __in unsigned num, __in_ecount(num) Z3_tactic const ts[]); + + /** + \brief Return a tactic that applies \c t1 to a given goal and then \c t2 + to every subgoal produced by t1. The subgoals are processed in parallel. + */ + Z3_tactic Z3_API Z3_tactic_par_and_then(__in Z3_context c, __in Z3_tactic t1, __in Z3_tactic t2); + + /** + \brief Return a tactic that applies \c t to a given goal for \c ms milliseconds. + If \c t does not terminate in \c ms milliseconds, then it fails. + */ + Z3_tactic Z3_API Z3_tactic_try_for(__in Z3_context c, __in Z3_tactic t, __in unsigned ms); + + /** + \brief Return a tactic that applies \c t to a given goal is the probe \c p evaluates to true. + If \c p evaluates to false, then the new tactic behaves like the skip tactic. + */ + Z3_tactic Z3_API Z3_tactic_when(__in Z3_context c, __in Z3_probe p, __in Z3_tactic t); + + /** + \brief Return a tactic that applies \c t1 to a given goal if the probe \c p evaluates to true, + and \c t2 if \c p evaluates to false. + */ + Z3_tactic Z3_API Z3_tactic_cond(__in Z3_context c, __in Z3_probe p, __in Z3_tactic t1, __in Z3_tactic t2); + + /** + \brief Return a tactic that keeps applying \c t until the goal is not modified anymore or the maximum + number of iterations \c max is reached. + */ + Z3_tactic Z3_API Z3_tactic_repeat(__in Z3_context c, __in Z3_tactic t, unsigned max); + + /** + \brief Return a tactic that just return the given goal. + */ + Z3_tactic Z3_API Z3_tactic_skip(__in Z3_context c); + + /** + \brief Return a tactic that always fails. + */ + Z3_tactic Z3_API Z3_tactic_fail(__in Z3_context c); + + /** + \brief Return a tactic that fails if the probe \c p evaluates to false. + */ + Z3_tactic Z3_API Z3_tactic_fail_if(__in Z3_context c, __in Z3_probe p); + + /** + \brief Return a tactic that fails if the goal is not trivially satisfiable (i.e., empty) or + trivially unsatisfiable (i.e., contains false). + */ + Z3_tactic Z3_API Z3_tactic_fail_if_not_decided(__in Z3_context c); + + /** + \brief Return a tactic that applies \c t using the given set of parameters. + */ + Z3_tactic Z3_API Z3_tactic_using_params(__in Z3_context c, __in Z3_tactic t, __in Z3_params p); + + /** + \brief Return a probe that always evaluates to val. + */ + Z3_probe Z3_API Z3_probe_const(__in Z3_context x, __in double val); + + /** + \brief Return a probe that evaluates to "true" when the value returned by \c p1 is less than the value returned by \c p2. + + \remark For probes, "true" is any value different from 0.0. + */ + Z3_probe Z3_API Z3_probe_lt(__in Z3_context x, __in Z3_probe p1, __in Z3_probe p2); + + /** + \brief Return a probe that evaluates to "true" when the value returned by \c p1 is greater than the value returned by \c p2. + + \remark For probes, "true" is any value different from 0.0. + */ + Z3_probe Z3_API Z3_probe_gt(__in Z3_context x, __in Z3_probe p1, __in Z3_probe p2); + + /** + \brief Return a probe that evaluates to "true" when the value returned by \c p1 is less than or equal to the value returned by \c p2. + + \remark For probes, "true" is any value different from 0.0. + */ + Z3_probe Z3_API Z3_probe_le(__in Z3_context x, __in Z3_probe p1, __in Z3_probe p2); + + /** + \brief Return a probe that evaluates to "true" when the value returned by \c p1 is greater than or equal to the value returned by \c p2. + + \remark For probes, "true" is any value different from 0.0. + */ + Z3_probe Z3_API Z3_probe_ge(__in Z3_context x, __in Z3_probe p1, __in Z3_probe p2); + + /** + \brief Return a probe that evaluates to "true" when the value returned by \c p1 is equal to the value returned by \c p2. + + \remark For probes, "true" is any value different from 0.0. + */ + Z3_probe Z3_API Z3_probe_eq(__in Z3_context x, __in Z3_probe p1, __in Z3_probe p2); + + /** + \brief Return a probe that evaluates to "true" when \c p1 and \c p2 evaluates to true. + + \remark For probes, "true" is any value different from 0.0. + */ + Z3_probe Z3_API Z3_probe_and(__in Z3_context x, __in Z3_probe p1, __in Z3_probe p2); + + /** + \brief Return a probe that evaluates to "true" when \c p1 or \c p2 evaluates to true. + + \remark For probes, "true" is any value different from 0.0. + */ + Z3_probe Z3_API Z3_probe_or(__in Z3_context x, __in Z3_probe p1, __in Z3_probe p2); + + /** + \brief Return a probe that evaluates to "true" when \c p does not evaluate to true. + + \remark For probes, "true" is any value different from 0.0. + */ + Z3_probe Z3_API Z3_probe_not(__in Z3_context x, __in Z3_probe p); + + /** + \brief Return the number of builtin tactics available in Z3. + */ + unsigned Z3_API Z3_get_num_tactics(__in Z3_context c); + + /** + \brief Return the name of the idx tactic. + + \pre i < Z3_get_num_tactics(c) + */ + Z3_string Z3_API Z3_get_tactic_name(__in Z3_context c, unsigned i); + + /** + \brief Return the number of builtin probes available in Z3. + */ + unsigned Z3_API Z3_get_num_probes(__in Z3_context c); + + /** + \brief Return the name of the i probe. + + \pre i < Z3_get_num_probes(c) + */ + Z3_string Z3_API Z3_get_probe_name(__in Z3_context c, unsigned i); + + /** + \brief Return a string containing a description of parameters accepted by the given tactic. + */ + Z3_string Z3_API Z3_tactic_get_help(__in Z3_context c, __in Z3_tactic t); + + /** + \brief Return the parameter description set for the given tactic object. + */ + Z3_param_descrs Z3_API Z3_tactic_get_param_descrs(__in Z3_context c, __in Z3_tactic t); + + /** + \brief Return a string containing a description of the tactic with the given name. + */ + Z3_string Z3_API Z3_tactic_get_descr(__in Z3_context c, __in Z3_string name); + + /** + \brief Return a string containing a description of the probe with the given name. + */ + Z3_string Z3_API Z3_probe_get_descr(__in Z3_context c, __in Z3_string name); + + /** + \brief Execute the probe over the goal. The probe always produce a double value. + "Boolean" probes return 0.0 for false, and a value different from 0.0 for true. + */ + double Z3_API Z3_probe_apply(__in Z3_context c, __in Z3_probe p, __in Z3_goal g); + + /** + \brief Apply tactic \c t to the goal \c g. + */ + Z3_apply_result Z3_API Z3_tactic_apply(__in Z3_context c, __in Z3_tactic t, __in Z3_goal g); + + /** + \brief Apply tactic \c t to the goal \c g using the parameter set \c p. + */ + Z3_apply_result Z3_API Z3_tactic_apply_ex(Z3_context c, Z3_tactic t, Z3_goal g, Z3_params p); + +#ifdef CorML3 + /** + \brief Increment the reference counter of the given \c Z3_apply_result object. + */ + void Z3_API Z3_apply_result_inc_ref(__in Z3_context c, __in Z3_apply_result r); + + /** + \brief Decrement the reference counter of the given \c Z3_apply_result object. + */ + void Z3_API Z3_apply_result_dec_ref(__in Z3_context c, __in Z3_apply_result r); +#endif + + /** + \brief Convert the \c Z3_apply_result object returned by #Z3_tactic_apply into a string. + */ + Z3_string Z3_API Z3_apply_result_to_string(__in Z3_context c, __in Z3_apply_result r); + + /** + \brief Return the number of subgoals in the \c Z3_apply_result object returned by #Z3_tactic_apply. + */ + unsigned Z3_API Z3_apply_result_get_num_subgoals(__in Z3_context c, __in Z3_apply_result r); + + /** + \brief Return one of the subgoals in the \c Z3_apply_result object returned by #Z3_tactic_apply. + + \pre i < Z3_apply_result_get_num_subgoals(c, r) + */ + Z3_goal Z3_API Z3_apply_result_get_subgoal(__in Z3_context c, __in Z3_apply_result r, __in unsigned i); + + /** + \brief Convert a model for the subgoal \c Z3_apply_result_get_subgoal(c, r, i) into a model for the original goal \c g. + Where \c g is the goal used to create \c r using \c Z3_tactic_apply(c, t, g). + */ + Z3_model Z3_API Z3_apply_result_convert_model(__in Z3_context c, __in Z3_apply_result r, __in unsigned i, __in Z3_model m); + + /*@}*/ + + /** + @name Solvers + */ + /*@{*/ + + /** + \brief Create a new (incremental) solver. This solver also uses a + set of builtin tactics for handling the first check-sat command, and + check-sat commands that take more than a given number of milliseconds to be solved. + + \conly \remark User must use #Z3_solver_inc_ref and #Z3_solver_dec_ref to manage solver objects. + \conly Even if the context was created using #Z3_mk_context instead of #Z3_mk_context_rc. + */ + Z3_solver Z3_API Z3_mk_solver(__in Z3_context c); + + /** + \brief Create a new (incremental) solver. + */ + Z3_solver Z3_API Z3_mk_simple_solver(__in Z3_context c); + + /** + \brief Create a new solver customized for the given logic. + It behaves like #Z3_mk_solver if the logic is unknown or unsupported. + + \conly \remark User must use #Z3_solver_inc_ref and #Z3_solver_dec_ref to manage solver objects. + \conly Even if the context was created using #Z3_mk_context instead of #Z3_mk_context_rc. + */ + Z3_solver Z3_API Z3_mk_solver_for_logic(__in Z3_context c, __in Z3_symbol logic); + + /** + \brief Create a new solver that is implemented using the given tactic. + The solver supports the commands #Z3_solver_push and #Z3_solver_pop, but it + will always solve each #Z3_solver_check from scratch. + */ + Z3_solver Z3_API Z3_mk_solver_from_tactic(__in Z3_context c, __in Z3_tactic t); + + /** + \brief Return a string describing all solver available parameters. + */ + Z3_string Z3_API Z3_solver_get_help(__in Z3_context c, __in Z3_solver s); + + /** + \brief Return the parameter description set for the given solver object. + */ + Z3_param_descrs Z3_API Z3_solver_get_param_descrs(__in Z3_context c, __in Z3_solver s); + + /** + \brief Set the given solver using the given parameters. + */ + void Z3_API Z3_solver_set_params(__in Z3_context c, __in Z3_solver s, __in Z3_params p); + +#ifdef Conly + /** + \brief Increment the reference counter of the given solver. + */ + void Z3_API Z3_solver_inc_ref(__in Z3_context c, __in Z3_solver s); + + /** + \brief Decrement the reference counter of the given solver. + */ + void Z3_API Z3_solver_dec_ref(__in Z3_context c, __in Z3_solver s); +#endif + + /** + \brief Create a backtracking point. + + The solver contains a stack of assertions. + + \sa Z3_solver_pop + */ + void Z3_API Z3_solver_push(__in Z3_context c, __in Z3_solver s); + + /** + \brief Backtrack \c n backtracking points. + + \sa Z3_solver_push + + \pre n <= Z3_solver_get_num_scopes(c, s) + */ + void Z3_API Z3_solver_pop(__in Z3_context c, __in Z3_solver s, unsigned n); + + /** + \brief Remove all assertions from the solver. + */ + void Z3_API Z3_solver_reset(__in Z3_context c, __in Z3_solver s); + + /** + \brief Return the number of backtracking points. + + \sa Z3_solver_push + \sa Z3_solver_pop + */ + unsigned Z3_API Z3_solver_get_num_scopes(__in Z3_context c, __in Z3_solver s); + + /** + \brief Assert a constraint into the solver. + + The functions #Z3_solver_check and #Z3_solver_check_assumptions should be + used to check whether the logical context is consistent or not. + */ + void Z3_API Z3_solver_assert(__in Z3_context c, __in Z3_solver s, __in Z3_ast a); + + /** + \brief Return the set of asserted formulas as a goal object. + */ + Z3_ast_vector Z3_API Z3_solver_get_assertions(__in Z3_context c, __in Z3_solver s); + + /** + \brief Check whether the assertions in a given solver are consistent or not. + + The function #Z3_solver_get_model retrieves a model if the + assertions are not unsatisfiable (i.e., the result is not \c + Z3_L_FALSE) and model construction is enabled. + + The function #Z3_solver_get_proof retrieves a proof if proof + generation was enabled when the context was created, and the + assertions are unsatisfiable (i.e., the result is \c Z3_L_FALSE). + */ + Z3_lbool Z3_API Z3_solver_check(__in Z3_context c, __in Z3_solver s); + + /** + \brief Check whether the assertions in the given solver and + optional assumptions are consistent or not. + + The function #Z3_solver_get_unsat_core retrieves the subset of the + assumptions used in the unsatisfiability proof produced by Z3. + + \sa Z3_solver_check + */ + Z3_lbool Z3_API Z3_solver_check_assumptions(__in Z3_context c, __in Z3_solver s, + __in unsigned num_assumptions, __in_ecount(num_assumptions) Z3_ast const assumptions[]); + + /** + \brief Retrieve the model for the last #Z3_solver_check or #Z3_solver_check_assumptions + + The error handler is invoked if a model is not available because + the commands above were not invoked for the given solver, or if the result was \c Z3_L_FALSE. + */ + Z3_model Z3_API Z3_solver_get_model(__in Z3_context c, __in Z3_solver s); + + /** + \brief Retrieve the proof for the last #Z3_solver_check or #Z3_solver_check_assumptions + + The error handler is invoked if proof generation is not enabled, + or if the commands above were not invoked for the given solver, + or if the result was different from \c Z3_L_FALSE. + */ + Z3_ast Z3_API Z3_solver_get_proof(__in Z3_context c, __in Z3_solver s); + + /** + \brief Retrieve the unsat core for the last #Z3_solver_check_assumptions + The unsat core is a subset of the assumptions \c a. + */ + Z3_ast_vector Z3_API Z3_solver_get_unsat_core(__in Z3_context c, __in Z3_solver s); + + /** + \brief Return a brief justification for an "unknown" result (i.e., Z3_L_UNDEF) for + the commands #Z3_solver_check and #Z3_solver_check_assumptions + */ + Z3_string Z3_API Z3_solver_get_reason_unknown(__in Z3_context c, __in Z3_solver s); + + /** + \brief Return statistics for the given solver. + + \conly \remark User must use #Z3_stats_inc_ref and #Z3_stats_dec_ref to manage Z3_stats objects. + */ + Z3_stats Z3_API Z3_solver_get_statistics(__in Z3_context c, __in Z3_solver s); + + /** + \brief Convert a solver into a string. + */ + Z3_string Z3_API Z3_solver_to_string(__in Z3_context c, __in Z3_solver s); + + /*@}*/ + + /** + @name Statistics + */ + /*@{*/ + +#ifdef ML4only +#include +#endif + /** + \brief Convert a statistics into a string. + */ + Z3_string Z3_API Z3_stats_to_string(__in Z3_context c, __in Z3_stats s); + + /** + \mlonly {4 {L Low-level API}} \endmlonly + */ + +#ifdef Conly + /** + \brief Increment the reference counter of the given statistics object. + */ + void Z3_API Z3_stats_inc_ref(__in Z3_context c, __in Z3_stats s); + + /** + \brief Decrement the reference counter of the given statistics object. + */ + void Z3_API Z3_stats_dec_ref(__in Z3_context c, __in Z3_stats s); +#endif + + /** + \brief Return the number of statistical data in \c s. + */ + unsigned Z3_API Z3_stats_size(__in Z3_context c, __in Z3_stats s); + + /** + \brief Return the key (a string) for a particular statistical data. + + \pre idx < Z3_stats_size(c, s) + */ + Z3_string Z3_API Z3_stats_get_key(__in Z3_context c, __in Z3_stats s, __in unsigned idx); + + /** + \brief Return Z3_TRUE if the given statistical data is a unsigned integer. + + \pre idx < Z3_stats_size(c, s) + */ + Z3_bool Z3_API Z3_stats_is_uint(__in Z3_context c, __in Z3_stats s, __in unsigned idx); + + /** + \brief Return Z3_TRUE if the given statistical data is a double. + + \pre idx < Z3_stats_size(c, s) + */ + Z3_bool Z3_API Z3_stats_is_double(__in Z3_context c, __in Z3_stats s, __in unsigned idx); + + /** + \brief Return the unsigned value of the given statistical data. + + \pre idx < Z3_stats_size(c, s) && Z3_stats_is_uint(c, s) + */ + unsigned Z3_API Z3_stats_get_uint_value(__in Z3_context c, __in Z3_stats s, __in unsigned idx); + + /** + \brief Return the double value of the given statistical data. + + \pre idx < Z3_stats_size(c, s) && Z3_stats_is_double(c, s) + */ + double Z3_API Z3_stats_get_double_value(__in Z3_context c, __in Z3_stats s, __in unsigned idx); + + /*@}*/ +#endif + + +#ifdef CorML3 + + /** + @name Deprecated Injective functions API + */ + /*@{*/ + + /** + \brief Create injective function declaration + + \deprecated This method just asserts a (universally quantified) formula that asserts that + the new function is injective. It is compatible with the old interface for solving: + #Z3_assert_cnstr, #Z3_check_assumptions, etc. + */ + Z3_func_decl Z3_API Z3_mk_injective_function( + __in Z3_context c, + __in Z3_symbol s, + unsigned domain_size, __in_ecount(domain_size) Z3_sort const domain[], + __in Z3_sort range + ); + + /*@}*/ + + /** + @name Deprecated Constraints API + */ + /*@{*/ + + /** + \brief Set the SMTLIB logic to be used in the given logical context. + It is incorrect to invoke this function after invoking + #Z3_check, #Z3_check_and_get_model, #Z3_check_assumptions and #Z3_push. + Return \c Z3_TRUE if the logic was changed successfully, and \c Z3_FALSE otherwise. + + \deprecated Subsumed by #Z3_mk_solver_for_logic + */ + Z3_bool Z3_API Z3_set_logic(__in Z3_context c, __in Z3_string logic); + + /** + \brief Create a backtracking point. + + The logical context can be viewed as a stack of contexts. The + scope level is the number of elements on this stack. The stack + of contexts is simulated using trail (undo) stacks. + + \sa Z3_pop + + \deprecated Subsumed by #Z3_solver_push + */ + void Z3_API Z3_push(__in Z3_context c); + + /** + \brief Backtrack. + + Restores the context from the top of the stack, and pops it off the + stack. Any changes to the logical context (by #Z3_assert_cnstr or + other functions) between the matching #Z3_push and \c Z3_pop + operators are flushed, and the context is completely restored to + what it was right before the #Z3_push. + + \sa Z3_push + + \deprecated Subsumed by #Z3_solver_pop + */ + void Z3_API Z3_pop(__in Z3_context c, __in unsigned num_scopes); + + /** + \brief Retrieve the current scope level. + + It retrieves the number of scopes that have been pushed, but not yet popped. + + \sa Z3_push + \sa Z3_pop + + \deprecated Subsumed by #Z3_solver_get_num_scopes. + */ + unsigned Z3_API Z3_get_num_scopes(__in Z3_context c); + + /** + \conly \brief Persist AST through num_scopes pops. + \conly This function is only relevant if \c c was created using #Z3_mk_context. + \conly If \c c was created using #Z3_mk_context_rc, this function is a NOOP. + + \conly Normally, for contexts created using #Z3_mk_context, + \conly references to terms are no longer valid when + \conly popping scopes beyond the level where the terms are created. + \conly If you want to reference a term below the scope where it + \conly was created, use this method to specify how many pops + \conly the term should survive. + \conly The num_scopes should be at most equal to the number of + \conly calls to Z3_push subtracted with the calls to Z3_pop. + + \conly \deprecated Z3 users should move to #Z3_mk_context_rc and use the + \conly reference counting APIs for managing AST nodes. + + \mlonly \deprecated This function has no effect. \endmlonly + */ + void Z3_API Z3_persist_ast(__in Z3_context c, __in Z3_ast a, __in unsigned num_scopes); + + /** + \brief Assert a constraint into the logical context. + + After one assertion, the logical context may become + inconsistent. + + The functions #Z3_check or #Z3_check_and_get_model should be + used to check whether the logical context is consistent or not. + + \sa Z3_check + \sa Z3_check_and_get_model + + \deprecated Subsumed by #Z3_solver_assert + */ + void Z3_API Z3_assert_cnstr(__in Z3_context c, __in Z3_ast a); + + /** + \brief Check whether the given logical context is consistent or not. + + If the logical context is not unsatisfiable (i.e., the return value is different from \c Z3_L_FALSE) + and model construction is enabled (see #Z3_mk_config), + \conly then a model is stored in \c m. Otherwise, the value \c NULL is stored in \c m. + \mlonly then a valid model is returned. Otherwise, it is unsafe to use the returned model.\endmlonly + \conly The caller is responsible for deleting the model using the function #Z3_del_model. + + \conly \remark In constrast with the rest of the Z3 API, the reference counter of the + \conly model is incremented. This is to guarantee backward compatibility. In previous + \conly versions, models did not support reference counting. + + \remark Model construction must be enabled using configuration + parameters (See, #Z3_mk_config). + + \sa Z3_check + \conly \sa Z3_del_model + + \deprecated Subsumed by #Z3_solver_check + */ + Z3_lbool Z3_API Z3_check_and_get_model(__in Z3_context c, __out Z3_model * m); + + /** + \brief Check whether the given logical context is consistent or not. + + The function #Z3_check_and_get_model should be used when models are needed. + + \sa Z3_check_and_get_model + + \deprecated Subsumed by #Z3_solver_check + */ + Z3_lbool Z3_API Z3_check(__in Z3_context c); + + /** + \brief Check whether the given logical context and optional assumptions is consistent or not. + + If the logical context is not unsatisfiable (i.e., the return value is different from \c Z3_L_FALSE), + \conly a non-NULL model argument is passed in, + and model construction is enabled (see #Z3_mk_config), + \conly then a model is stored in \c m. Otherwise, \c m is left unchanged. + \mlonly then a valid model is returned. Otherwise, it is unsafe to use the returned model.\endmlonly + \conly The caller is responsible for deleting the model using the function #Z3_del_model. + + \conly \remark If the model argument is non-NULL, then model construction must be enabled using configuration + \conly parameters (See, #Z3_mk_config). + + \param c logical context. + \param num_assumptions number of auxiliary assumptions. + \param assumptions array of auxiliary assumptions + \param m optional pointer to a model. + \param proof optional pointer to a proof term. + \param core_size size of unsatisfiable core. + \param core pointer to an array receiving unsatisfiable core. + The unsatisfiable core is a subset of the assumptions, so the array has the same size as the assumptions. + The \c core array is not populated if \c core_size is set to 0. + + \pre assumptions comprises of propositional literals. + In other words, you cannot use compound formulas for assumptions, + but should use propositional variables or negations of propositional variables. + + \conly \remark In constrast with the rest of the Z3 API, the reference counter of the + \conly model is incremented. This is to guarantee backward compatibility. In previous + \conly versions, models did not support reference counting. + + \sa Z3_check + \conly \sa Z3_del_model + + \deprecated Subsumed by #Z3_solver_check_assumptions + */ + Z3_lbool Z3_API Z3_check_assumptions( + __in Z3_context c, + __in unsigned num_assumptions, __in_ecount(num_assumptions) Z3_ast const assumptions[], + __out Z3_model * m, __out Z3_ast* proof, + __inout unsigned* core_size, __inout_ecount(num_assumptions) Z3_ast core[] + ); + + /** + \brief Retrieve congruence class representatives for terms. + + The function can be used for relying on Z3 to identify equal terms under the current + set of assumptions. The array of terms and array of class identifiers should have + the same length. The class identifiers are numerals that are assigned to the same + value for their corresponding terms if the current context forces the terms to be + equal. You cannot deduce that terms corresponding to different numerals must be all different, + (especially when using non-convex theories). + All implied equalities are returned by this call. + This means that two terms map to the same class identifier if and only if + the current context implies that they are equal. + + A side-effect of the function is a satisfiability check. + The function return Z3_L_FALSE if the current assertions are not satisfiable. + + \sa Z3_check_and_get_model + \sa Z3_check + + \deprecated Subsumed by Z3_solver API + */ + Z3_lbool Z3_API Z3_get_implied_equalities( + __in Z3_context c, + __in unsigned num_terms, + __in_ecount(num_terms) Z3_ast const terms[], + __out_ecount(num_terms) unsigned class_ids[] + ); + + /** + \brief Delete a model object. + + \sa Z3_check_and_get_model + + \deprecated Subsumed by Z3_solver API + */ + void Z3_API Z3_del_model(__in Z3_context c, __in Z3_model m); + + /*@}*/ + + /** + @name Deprecated Search control API + */ + /*@{*/ + + /** + \brief Cancel an ongoing check. + + Notifies the current check to abort and return. + This method should be called from a different thread + than the one performing the check. + + \deprecated Use #Z3_interrupt instead. + */ + void Z3_API Z3_soft_check_cancel(__in Z3_context c); + + /** + \brief Retrieve reason for search failure. + + If a call to #Z3_check or #Z3_check_and_get_model returns Z3_L_UNDEF, + use this facility to determine the more detailed cause of search failure. + + \deprecated Subsumed by #Z3_solver_get_reason_unknown + */ + Z3_search_failure Z3_API Z3_get_search_failure(__in Z3_context c); + + /*@}*/ + + /** + @name Deprecated Labels API + */ + /*@{*/ + + /** + \brief Create a labeled formula. + + \param c logical context. + \param s name of the label. + \param is_pos label polarity. + \param f formula being labeled. + + A label behaves as an identity function, so the truth value of the + labeled formula is unchanged. Labels are used for identifying + useful sub-formulas when generating counter-examples. + + \deprecated Labels are only supported by the old Solver API. + This feature is not essential (it can be simulated using auxiliary Boolean variables). + It is only available for backward compatibility. + */ + Z3_ast Z3_API Z3_mk_label(__in Z3_context c, __in Z3_symbol s, Z3_bool is_pos, Z3_ast f); + + /** + \brief Retrieve the set of labels that were relevant in + the context of the current satisfied context. + + \sa Z3_del_literals + \sa Z3_get_num_literals + \sa Z3_get_label_symbol + \sa Z3_get_literal + + \deprecated This procedure is based on the old Solver API. + */ + Z3_literals Z3_API Z3_get_relevant_labels(__in Z3_context c); + + /** + \brief Retrieve the set of literals that satisfy the current context. + + \sa Z3_del_literals + \sa Z3_get_num_literals + \sa Z3_get_label_symbol + \sa Z3_get_literal + + \deprecated This procedure is based on the old Solver API. + */ + Z3_literals Z3_API Z3_get_relevant_literals(__in Z3_context c); + + /** + \brief Retrieve the set of literals that whose assignment were + guess, but not propagated during the search. + + \sa Z3_del_literals + \sa Z3_get_num_literals + \sa Z3_get_label_symbol + \sa Z3_get_literal + + \deprecated This procedure is based on the old Solver API. + */ + Z3_literals Z3_API Z3_get_guessed_literals(__in Z3_context c); + + /** + \brief Delete a labels context. + + \sa Z3_get_relevant_labels + + \deprecated This procedure is based on the old Solver API. + */ + void Z3_API Z3_del_literals(__in Z3_context c, __in Z3_literals lbls); + + /** + \brief Retrieve the number of label symbols that were returned. + + \sa Z3_get_relevant_labels + + \deprecated This procedure is based on the old Solver API. + */ + unsigned Z3_API Z3_get_num_literals(__in Z3_context c, __in Z3_literals lbls); + + /** + \brief Retrieve label symbol at idx. + + \deprecated This procedure is based on the old Solver API. + */ + Z3_symbol Z3_API Z3_get_label_symbol(__in Z3_context c, __in Z3_literals lbls, __in unsigned idx); + + /** + \brief Retrieve literal expression at idx. + + \deprecated This procedure is based on the old Solver API. + */ + Z3_ast Z3_API Z3_get_literal(__in Z3_context c, __in Z3_literals lbls, __in unsigned idx); + + /** + \brief Disable label. + + The disabled label is not going to be used when blocking the subsequent search. + + \sa Z3_block_literals + + \deprecated This procedure is based on the old Solver API. + */ + void Z3_API Z3_disable_literal(__in Z3_context c, __in Z3_literals lbls, __in unsigned idx); + + /** + \brief Block subsequent checks using the remaining enabled labels. + + \deprecated This procedure is based on the old Solver API. + */ + void Z3_API Z3_block_literals(__in Z3_context c, __in Z3_literals lbls); + + /*@}*/ + + /** + @name Deprecated Model API + */ + /*@{*/ + + /** + \brief Return the number of constants assigned by the given model. + + \mlonly \remark Consider using {!get_model_constants}. \endmlonly + + \sa Z3_get_model_constant + + \deprecated use #Z3_model_get_num_consts + */ + unsigned Z3_API Z3_get_model_num_constants(__in Z3_context c, __in Z3_model m); + + /** + \brief \mlh get_model_constant c m i \endmlh + Return the i-th constant in the given model. + + \mlonly \remark Consider using {!get_model_constants}. \endmlonly + + \pre i < Z3_get_model_num_constants(c, m) + + \deprecated use #Z3_model_get_const_decl + */ + Z3_func_decl Z3_API Z3_get_model_constant(__in Z3_context c, __in Z3_model m, __in unsigned i); + + /** + \brief Return the number of function interpretations in the given model. + + A function interpretation is represented as a finite map and an 'else' value. + Each entry in the finite map represents the value of a function given a set of arguments. + + \deprecated use #Z3_model_get_num_funcs + */ + unsigned Z3_API Z3_get_model_num_funcs(__in Z3_context c, __in Z3_model m); + + /** + \brief \mlh get_model_func_decl c m i \endmlh + Return the declaration of the i-th function in the given model. + + \pre i < Z3_get_model_num_funcs(c, m) + + \sa Z3_get_model_num_funcs + + \deprecated use #Z3_model_get_func_decl + */ + Z3_func_decl Z3_API Z3_get_model_func_decl(__in Z3_context c, __in Z3_model m, __in unsigned i); + + /** + \brief Return the value of the given constant or function + in the given model. + + \deprecated Consider using #Z3_model_eval or #Z3_model_get_func_interp + */ + Z3_bool Z3_API Z3_eval_func_decl(__in Z3_context c, __in Z3_model m, __in Z3_func_decl decl, __out Z3_ast* v); + + /** + \brief \mlh is_array_value c v \endmlh + Determine whether the term encodes an array value. + A term encodes an array value if it is a nested sequence of + applications of store on top of a constant array. + The indices to the stores have to be values (for example, integer constants) + so that equality between the indices can be evaluated. + Array values are useful for representing interpretations for arrays. + + Return the number of entries mapping to non-default values of the array. + + \deprecated Use #Z3_is_as_array + */ + Z3_bool Z3_API Z3_is_array_value(__in Z3_context c, __in Z3_model m, __in Z3_ast v, __out unsigned* num_entries); + + /** + \brief \mlh get_array_value c v \endmlh + An array values is represented as a dictionary plus a + default (else) value. This function returns the array graph. + + \pre Z3_TRUE == Z3_is_array_value(c, v, &num_entries) + + \deprecated Use Z3_func_interp objects and #Z3_get_as_array_func_decl + */ + void Z3_API Z3_get_array_value(__in Z3_context c, + __in Z3_model m, + __in Z3_ast v, + __in unsigned num_entries, + __inout_ecount(num_entries) Z3_ast indices[], + __inout_ecount(num_entries) Z3_ast values[], + __out Z3_ast* else_value + ); + + /** + \brief \mlh get_model_func_else c m i \endmlh + Return the 'else' value of the i-th function interpretation in the given model. + + A function interpretation is represented as a finite map and an 'else' value. + + \mlonly \remark Consider using {!get_model_funcs}. \endmlonly + + \pre i < Z3_get_model_num_funcs(c, m) + + \sa Z3_get_model_num_funcs + \sa Z3_get_model_func_num_entries + \sa Z3_get_model_func_entry_num_args + \sa Z3_get_model_func_entry_arg + + \deprecated Use Z3_func_interp objects + */ + Z3_ast Z3_API Z3_get_model_func_else(__in Z3_context c, __in Z3_model m, __in unsigned i); + + /** + \brief \mlh get_model_func_num_entries c m i \endmlh + Return the number of entries of the i-th function interpretation in the given model. + + A function interpretation is represented as a finite map and an 'else' value. + + \mlonly \remark Consider using {!get_model_funcs}. \endmlonly + + \pre i < Z3_get_model_num_funcs(c, m) + + \sa Z3_get_model_num_funcs + \sa Z3_get_model_func_else + \sa Z3_get_model_func_entry_num_args + \sa Z3_get_model_func_entry_arg + + \deprecated Use Z3_func_interp objects + */ + unsigned Z3_API Z3_get_model_func_num_entries(__in Z3_context c, __in Z3_model m, __in unsigned i); + + /** + \brief \mlh get_model_func_entry_num_args c m i j \endmlh + Return the number of arguments of the j-th entry of the i-th function interpretation in the given + model. + + A function interpretation is represented as a finite map and an 'else' value. + This function returns the j-th entry of this map. + + An entry represents the value of a function given a set of arguments. + \conly That is: it has the following format f(args[0],...,args[num_args - 1]) = val. + + \mlonly \remark Consider using {!get_model_funcs}. \endmlonly + + \pre i < Z3_get_model_num_funcs(c, m) + \pre j < Z3_get_model_func_num_entries(c, m, i) + + \sa Z3_get_model_num_funcs + \sa Z3_get_model_func_num_entries + \sa Z3_get_model_func_entry_arg + + \deprecated Use Z3_func_interp objects + */ + unsigned Z3_API Z3_get_model_func_entry_num_args(__in Z3_context c, + __in Z3_model m, + __in unsigned i, + __in unsigned j); + + /** + \brief \mlh get_model_func_entry_arg c m i j k \endmlh + Return the k-th argument of the j-th entry of the i-th function interpretation in the given + model. + + A function interpretation is represented as a finite map and an 'else' value. + This function returns the j-th entry of this map. + + An entry represents the value of a function given a set of arguments. + \conly That is: it has the following format f(args[0],...,args[num_args - 1]) = val. + + \mlonly \remark Consider using {!get_model_funcs}. \endmlonly + + \pre i < Z3_get_model_num_funcs(c, m) + \pre j < Z3_get_model_func_num_entries(c, m, i) + \pre k < Z3_get_model_func_entry_num_args(c, m, i, j) + + \sa Z3_get_model_num_funcs + \sa Z3_get_model_func_num_entries + \sa Z3_get_model_func_entry_num_args + + \deprecated Use Z3_func_interp objects + */ + Z3_ast Z3_API Z3_get_model_func_entry_arg(__in Z3_context c, + __in Z3_model m, + __in unsigned i, + __in unsigned j, + __in unsigned k); + + /** + \brief \mlh get_model_func_entry_value c m i j \endmlh + Return the return value of the j-th entry of the i-th function interpretation in the given + model. + + A function interpretation is represented as a finite map and an 'else' value. + This function returns the j-th entry of this map. + + An entry represents the value of a function given a set of arguments. + \conly That is: it has the following format f(args[0],...,args[num_args - 1]) = val. + + \mlonly \remark Consider using {!get_model_funcs}. \endmlonly + + \pre i < Z3_get_model_num_funcs(c, m) + \pre j < Z3_get_model_func_num_entries(c, m, i) + + \sa Z3_get_model_num_funcs + \sa Z3_get_model_func_num_entries + + \deprecated Use Z3_func_interp objects + */ + Z3_ast Z3_API Z3_get_model_func_entry_value(__in Z3_context c, + __in Z3_model m, + __in unsigned i, + __in unsigned j); + + /** + \brief \mlh eval c m t \endmlh + Evaluate the AST node \c t in the given model. + \conly Return \c Z3_TRUE if succeeded, and store the result in \c v. + \mlonly Return a pair: Boolean and value. The Boolean is true if the term was successfully evaluated. \endmlonly + + The evaluation may fail for the following reasons: + + - \c t contains a quantifier. + + - the model \c m is partial, that is, it doesn't have a complete interpretation for uninterpreted functions. + That is, the option MODEL_PARTIAL=true was used. + + - \c t is type incorrect. + + \deprecated Use #Z3_model_eval + */ + Z3_bool Z3_API Z3_eval(__in Z3_context c, __in Z3_model m, __in Z3_ast t, __out Z3_ast * v); + + /** + \brief Evaluate declaration given values. + + Provides direct way to evaluate declarations + without going over terms. + + \deprecated Consider using #Z3_model_eval and #Z3_substitute_vars + */ + Z3_bool Z3_API Z3_eval_decl(__in Z3_context c, __in Z3_model m, + __in Z3_func_decl d, + __in unsigned num_args, + __in_ecount(num_args) Z3_ast const args[], + __out Z3_ast* v); + + /*@}*/ + + /** + @name Deprecated String conversion API + */ + /*@{*/ + + /** + \brief Convert the given logical context into a string. + + This function is mainly used for debugging purposes. It displays + the internal structure of a logical context. + + \conly \warning The result buffer is statically allocated by Z3. It will + \conly be automatically deallocated when #Z3_del_context is invoked. + \conly So, the buffer is invalidated in the next call to \c Z3_context_to_string. + + \deprecated This method is obsolete. It just displays the internal representation of + the global solver available for backward compatibility reasons. + */ + Z3_string Z3_API Z3_context_to_string(__in Z3_context c); + + /** + \brief Return runtime statistics as a string. + + This function is mainly used for debugging purposes. It displays + statistics of the search activity. + + \conly \warning The result buffer is statically allocated by Z3. It will + \conly be automatically deallocated when #Z3_del_context is invoked. + \conly So, the buffer is invalidated in the next call to \c Z3_context_to_string. + + \deprecated This method is based on the old solver API. + Use #Z3_stats_to_string when using the new solver API. + */ + Z3_string Z3_API Z3_statistics_to_string(__in Z3_context c); + + /** + \brief Extract satisfying assignment from context as a conjunction. + + This function can be used for debugging purposes. It returns a conjunction + of formulas that are assigned to true in the current context. + This conjunction will contain not only the assertions that are set to true + under the current assignment, but will also include additional literals + if there has been a call to #Z3_check or #Z3_check_and_get_model. + + \deprecated This method is based on the old solver API. + */ + Z3_ast Z3_API Z3_get_context_assignment(__in Z3_context c); + + /*@}*/ + +#endif + + +#ifndef CAMLIDL +#ifdef __cplusplus +}; +#endif // __cplusplus +#else +} +#endif // CAMLIDL + +/*@}*/ diff --git a/lib/z3_exception.cpp b/lib/z3_exception.cpp new file mode 100644 index 000000000..0e791ad92 --- /dev/null +++ b/lib/z3_exception.cpp @@ -0,0 +1,74 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + z3_exception.cpp + +Abstract: + + Generic Z3 exception + +Author: + + Leonardo (leonardo) 2012-04-12 + +Notes: + +--*/ +#include +#include +#include +#include"z3_exception.h" +#include"warning.h" +#include"error_codes.h" +#include"debug.h" + +unsigned z3_exception::error_code() const { + return ERR_OK; +} + +bool z3_exception::has_error_code() const { + return error_code() != ERR_OK; +} + +z3_error::z3_error(unsigned error_code):m_error_code(error_code) { + SASSERT(error_code != 0); +} + +char const * z3_error::msg() const { + switch (m_error_code) { + case ERR_MEMOUT: return "out of memory"; + case ERR_TIMEOUT: return "timeout"; + case ERR_PARSER: return "parser error"; + case ERR_UNSOUNDNESS: return "unsoundess"; + case ERR_INCOMPLETENESS: return "incompleteness"; + case ERR_INI_FILE: return "invalid INI file"; + case ERR_NOT_IMPLEMENTED_YET: return "not implemented yet"; + case ERR_OPEN_FILE: return "open file"; + case ERR_CMD_LINE: return "invalid command line"; + case ERR_INTERNAL_FATAL: return "internal error"; + case ERR_TYPE_CHECK: return "type error"; + default: return "unknown error"; + } +} + +unsigned z3_error::error_code() const { + return m_error_code; +} + +default_exception::default_exception(char const* msg, ...) { + std::stringstream out; + va_list args; + va_start(args, msg); + format2ostream(out, msg, args); + va_end(args); + m_msg = out.str(); +} + +default_exception::default_exception(std::string const & msg): m_msg(msg) { +} + +char const * default_exception::msg() const { + return m_msg.c_str(); +} diff --git a/lib/z3_exception.h b/lib/z3_exception.h new file mode 100644 index 000000000..b059b5491 --- /dev/null +++ b/lib/z3_exception.h @@ -0,0 +1,49 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + z3_exception.h + +Abstract: + + Generic Z3 exception + +Author: + + Leonardo (leonardo) 2011-04-28 + +Notes: + +--*/ +#ifndef _Z3_EXCEPTION_H_ +#define _Z3_EXCEPTION_H_ + +#include + +class z3_exception { +public: + virtual ~z3_exception() {} + virtual char const * msg() const = 0; + virtual unsigned error_code() const; + bool has_error_code() const; +}; + +class z3_error : public z3_exception { + unsigned m_error_code; +public: + z3_error(unsigned error_code); + virtual char const * msg() const; + virtual unsigned error_code() const; +}; + +class default_exception : public z3_exception { + std::string m_msg; +public: + default_exception(std::string const& msg); + default_exception(char const* msg, ...); + virtual ~default_exception() {} + virtual char const * msg() const; +}; + +#endif diff --git a/lib/z3_logger.h b/lib/z3_logger.h new file mode 100644 index 000000000..8e2867795 --- /dev/null +++ b/lib/z3_logger.h @@ -0,0 +1,70 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + z3_logger.h + +Abstract: + + Goodies for log generation + +Author: + + Leonardo de Moura (leonardo) 2011-09-22 + +Notes: + +--*/ +#include +#include"symbol.h" +struct ll_escaped { char const * m_str; ll_escaped(char const * str):m_str(str) {} }; +static std::ostream & operator<<(std::ostream & out, ll_escaped const & d) { + char const * s = d.m_str; + while (*s) { + char c = *s; + if (('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || + c == '~' || c == '!' || c == '@' || c == '#' || c == '$' || c == '%' || c == '^' || c == '&' || + c == '*' || c == '-' || c == '_' || c == '+' || c == '.' || c == '?' || c == '/' || c == ' ' || + c == '<' || c == '>') { + out << c; + } + else { + char str[4] = {'0', '0', '0', 0}; + str[2] = '0' + (c % 10); + c /= 10; + str[1] = '0' + (c % 10); + c /= 10; + str[0] = '0' + c; + out << '\\' << str; + } + s++; + } + return out; +} + +static void R() { *g_z3_log << "R\n"; g_z3_log->flush(); } +static void P(void * obj) { *g_z3_log << "P " << obj << "\n"; g_z3_log->flush(); } +static void I(__int64 i) { *g_z3_log << "I " << i << "\n"; g_z3_log->flush(); } +static void U(__uint64 u) { *g_z3_log << "U " << u << "\n"; g_z3_log->flush(); } +static void D(double d) { *g_z3_log << "D " << d << "\n"; g_z3_log->flush(); } +static void S(Z3_string str) { *g_z3_log << "S \"" << ll_escaped(str) << "\"\n"; g_z3_log->flush(); } +static void Sy(Z3_symbol sym) { + symbol s = symbol::mk_symbol_from_c_ptr(reinterpret_cast(sym)); + if (s == symbol::null) { + *g_z3_log << "N\n"; + } + else if (s.is_numerical()) { + *g_z3_log << "# " << s.get_num() << "\n"; + } + else { + *g_z3_log << "$ |" << ll_escaped(s.bare_str()) << "|\n"; + } + g_z3_log->flush(); +} +static void Ap(unsigned sz) { *g_z3_log << "p " << sz << "\n"; g_z3_log->flush(); } +static void Au(unsigned sz) { *g_z3_log << "u " << sz << "\n"; g_z3_log->flush(); } +static void Asy(unsigned sz) { *g_z3_log << "s " << sz << "\n"; g_z3_log->flush(); } +static void C(unsigned id) { *g_z3_log << "C " << id << "\n"; g_z3_log->flush(); } +void _Z3_append_log(char const * msg) { *g_z3_log << "M \"" << ll_escaped(msg) << "\"\n"; g_z3_log->flush(); } + diff --git a/lib/z3_macros.h b/lib/z3_macros.h new file mode 100644 index 000000000..a4a0cb4be --- /dev/null +++ b/lib/z3_macros.h @@ -0,0 +1,54 @@ +#ifndef __in +#define __in +#endif + +#ifndef __out +#define __out +#endif + +#ifndef __out_opt +#define __out_opt __out +#endif + +#ifndef __ecount +#define __ecount(num_args) +#endif + +#ifndef __in_ecount +#define __in_ecount(num_args) __in __ecount(num_args) +#endif + +#ifndef __out_ecount +#define __out_ecount(num_args) __out __ecount(num_args) +#endif + +#ifndef __inout_ecount +#define __inout_ecount(num_args) __in __out __ecount(num_args) +#endif + +#ifndef __inout +#define __inout __in __out +#endif + +#ifndef Z3_bool_opt +#define Z3_bool_opt Z3_bool +#endif + +#ifndef Z3_API +#define Z3_API +#endif + +#ifndef DEFINE_TYPE +#define DEFINE_TYPE(T) typedef struct _ ## T *T +#endif + +#ifndef DEFINE_VOID +#define DEFINE_VOID(T) typedef void* T +#endif + +#ifndef BEGIN_MLAPI_EXCLUDE +#define BEGIN_MLAPI_EXCLUDE +#endif +#ifndef END_MLAPI_EXCLUDE +#define END_MLAPI_EXCLUDE +#endif diff --git a/lib/z3_omp.h b/lib/z3_omp.h new file mode 100644 index 000000000..8af7ae389 --- /dev/null +++ b/lib/z3_omp.h @@ -0,0 +1,38 @@ +/*++ +Copyright (c) 2012 Microsoft Corporation + +Module Name: + + z3_omp.h + +Abstract: + + Wrapper for OMP functions and data-structures + +Author: + + Leonardo (leonardo) 2012-01-05 + +Notes: + +--*/ +#ifndef _Z3_OMP_H +#define _Z3_OMP_H + +#ifndef _NO_OMP_ +#include"omp.h" +#else +#define omp_in_parallel() false +#define omp_set_num_threads(SZ) ((void)0) +#define omp_get_thread_num() 0 +#define omp_get_num_procs() 1 +#define omp_set_nested(V) ((void)0) +#define omp_init_nest_lock(L) ((void) 0) +#define omp_destroy_nest_lock(L) ((void) 0) +#define omp_set_nest_lock(L) ((void) 0) +#define omp_unset_nest_lock(L) ((void) 0) +struct omp_nest_lock_t { +}; +#endif + +#endif diff --git a/lib/z3_private.h b/lib/z3_private.h new file mode 100644 index 000000000..17075a6c6 --- /dev/null +++ b/lib/z3_private.h @@ -0,0 +1,80 @@ +/*++ +Copyright (c) 2007 Microsoft Corporation + +Module Name: + + z3_private.h + +Abstract: + + Z3 API. + +Author: + + Nikolaj Bjorner (nbjorner) + Leonardo de Moura (leonardo) 2007-06-8 + +Notes: + +--*/ + +#include +#include"rational.h" +#include"z3_macros.h" + +#ifndef _Z3_PRIVATE__H_ +#define _Z3_PRIVATE__H_ + + +#ifndef CAMLIDL +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus +#else +[pointer_default(ref)] interface Z3 { +#endif // CAMLIDL + + Z3_bool Z3_API Z3_get_numeral_rational(__in Z3_context c, __in Z3_ast a, rational& r); + + /** + \brief \mlh exec_smtlib2_string c str \endmlh + Parse the given string using the SMT-LIB2 parser and execute its commands. + + It returns a formula comprising of the conjunction of assertions in the scope + (up to push/pop) at the end of the string. + The returned formula is also asserted to the logical context on return. + */ + Z3_ast Z3_API Z3_exec_smtlib2_string(__in Z3_context c, + __in Z3_string str, + __in unsigned num_sorts, + __in_ecount(num_sorts) Z3_symbol sort_names[], + __in_ecount(num_sorts) Z3_sort sorts[], + __in unsigned num_decls, + __in_ecount(num_decls) Z3_symbol decl_names[], + __in_ecount(num_decls) Z3_func_decl decls[] + ); + + /** + \brief Similar to #Z3_exec_smtlib2_string, but reads the commands from a file. + */ + Z3_ast Z3_API Z3_exec_smtlib2_file(__in Z3_context c, + __in Z3_string file_name, + __in unsigned num_sorts, + __in_ecount(num_sorts) Z3_symbol sort_names[], + __in_ecount(num_sorts) Z3_sort sorts[], + __in unsigned num_decls, + __in_ecount(num_decls) Z3_symbol decl_names[], + __in_ecount(num_decls) Z3_func_decl decls[] + ); + + +#ifndef CAMLIDL +#ifdef __cplusplus +}; +#endif // __cplusplus +#else +} +#endif // CAMLIDL + +#endif + diff --git a/lib/z3_replayer.cpp b/lib/z3_replayer.cpp new file mode 100644 index 000000000..b41ef876c --- /dev/null +++ b/lib/z3_replayer.cpp @@ -0,0 +1,711 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + z3_replayer.cpp + +Abstract: + + Interpreter for Z3 logs + +Author: + + Leonardo de Moura (leonardo) 2011-09-22 + +Notes: + +--*/ +#include"vector.h" +#include"map.h" +#include"z3_replayer.h" +#include"stream_buffer.h" +#include"symbol.h" +#include"trace.h" + +void register_z3_replayer_cmds(z3_replayer & in); + +void throw_invalid_reference() { + throw z3_replayer_exception("invalid argument reference"); +} + +struct z3_replayer::imp { + z3_replayer & m_owner; + std::istream & m_stream; + char m_curr; // current char; + int m_line; // line + svector m_string; + symbol m_id; + __int64 m_int64; + __uint64 m_uint64; + double m_double; + size_t m_ptr; + size_t_map m_heap; + svector m_cmds; + + enum value_kind { INT64, UINT64, DOUBLE, STRING, SYMBOL, OBJECT, UINT_ARRAY, SYMBOL_ARRAY, OBJECT_ARRAY }; + + struct value { + value_kind m_kind; + union { + __int64 m_int; + __uint64 m_uint; + double m_double; + char const * m_str; + void * m_obj; + }; + value():m_kind(OBJECT), m_int(0) {} + value(void * obj):m_kind(OBJECT), m_obj(obj) {} + value(value_kind k, char const * str):m_kind(k), m_str(str) {} + value(value_kind k, __uint64 u):m_kind(k), m_uint(u) {} + value(value_kind k, __int64 i):m_kind(k), m_int(i) {} + value(value_kind k, double d):m_kind(k), m_double(d) {} + }; + + svector m_args; + void * m_result; + vector > m_obj_arrays; + vector > m_sym_arrays; + vector m_unsigned_arrays; + + imp(z3_replayer & o, std::istream & in): + m_owner(o), + m_stream(in), + m_curr(0), + m_line(1) { + next(); + } + + void display_arg(std::ostream & out, value const & v) const { + switch (v.m_kind) { + case INT64: + out << v.m_int; + break; + case UINT64: + out << v.m_uint; + break; + case DOUBLE: + out << v.m_double; + break; + case STRING: + out << v.m_str; + break; + case SYMBOL: + out << symbol::mk_symbol_from_c_ptr(v.m_str); + break; + case OBJECT: + out << v.m_obj; + break; + case UINT_ARRAY: + case OBJECT_ARRAY: + case SYMBOL_ARRAY: + out << ""; + break; + default: + out << ""; + break; + } + } + + void display_args(std::ostream & out) const { + for (unsigned i = 0; i < m_args.size(); i++) { + if (i > 0) out << " "; + display_arg(out, m_args[i]); + } + } + + char curr() const { return m_curr; } + void new_line() { m_line++; } + void next() { m_curr = m_stream.get(); } + + void read_string_core(char delimiter) { + if (curr() != delimiter) + throw z3_replayer_exception("invalid string/symbol"); + m_string.reset(); + next(); + while (true) { + char c = curr(); + if (c == EOF) { + throw z3_replayer_exception("unexpected end of file"); + } + else if (c == '\n') { + throw z3_replayer_exception("unexpected end of line"); + } + else if (c == '\\') { + next(); + unsigned val = 0; + unsigned sz = 0; + while (sz < 3) { + c = curr(); + if ('0' <= c && c <= '9') { + val *= 10; + val += c - '0'; + sz++; + } + else { + throw z3_replayer_exception("invalid scaped character"); + } + if (val > 255) + throw z3_replayer_exception("invalid scaped character"); + next(); + } + TRACE("z3_replayer_escape", tout << "val: " << val << "\n";); + m_string.push_back(static_cast(val)); + } + else if (c == delimiter) { + next(); + m_string.push_back(0); + return; + } + else { + m_string.push_back(c); + next(); + } + } + } + + void read_string() { + read_string_core('"'); + } + + void read_quoted_symbol() { + read_string_core('|'); + m_id = m_string.begin(); + } + + void read_int64() { + if (!(curr() == '-' || ('0' <= curr() && curr() <= '9'))) + throw z3_replayer_exception("invalid integer"); + bool sign = false; + if (curr() == '-') { + sign = true; + next(); + if (!('0' <= curr() && curr() <= '9')) + throw z3_replayer_exception("invalid integer"); + } + m_int64 = 0; + while (true) { + char c = curr(); + if ('0' <= c && c <= '9') { + m_int64 = 10*m_int64 + (c - '0'); + next(); + } + else { + break; + } + } + if (sign) + m_int64 = -m_int64; + } + + void read_uint64() { + if (!('0' <= curr() && curr() <= '9')) + throw z3_replayer_exception("invalid unsigned"); + m_uint64 = 0; + while (true) { + char c = curr(); + if ('0' <= c && c <= '9') { + m_uint64 = 10*m_uint64 + (c - '0'); + next(); + } + else { + break; + } + } + } + + bool is_double_char() const { + return curr() == '-' || curr() == '.' || ('0' <= curr() && curr() <= '9') || curr() == 'e' || curr() == 'E'; + } + + void read_double() { + m_string.reset(); + while (is_double_char()) { + m_string.push_back(curr()); + next(); + } + if (m_string.empty()) + throw z3_replayer_exception("invalid double"); + m_string.push_back(0); + char * ptr; + m_double = strtod(m_string.begin(), &ptr); + } + + void read_ptr() { + if (!(('0' <= curr() && curr() <= '9') || ('A' <= curr() && curr() <= 'F') || ('a' <= curr() && curr() <= 'f'))) + throw z3_replayer_exception("invalid ptr"); + m_ptr = 0; + while (true) { + char c = curr(); + if ('0' <= c && c <= '9') { + m_ptr = m_ptr * 16 + (c - '0'); + } + else if ('a' <= c && c <= 'f') { + m_ptr = m_ptr * 16 + 10 + (c - 'a'); + } + else if ('A' <= c && c <= 'F') { + m_ptr = m_ptr * 16 + 10 + (c - 'A'); + } + else { + return; + } + next(); + } + } + + void skip_blank() { + while (true) { + char c = curr(); + if (c == '\n') { + new_line(); + next(); + } + else if (c == ' ' || c == '\t') { + next(); + } + else { + break; + } + } + } + + void push_array(unsigned sz, value_kind k) { + unsigned asz = m_args.size(); + if (sz > asz) + throw z3_replayer_exception("invalid array size"); + __uint64 aidx; + value_kind nk; + for (unsigned i = asz - sz; i < asz; i++) { + if (m_args[i].m_kind != k) + throw z3_replayer_exception("invalid array: mixed value types"); + } + if (k == UINT64) { + aidx = m_unsigned_arrays.size(); + nk = UINT_ARRAY; + m_unsigned_arrays.push_back(unsigned_vector()); + unsigned_vector & v = m_unsigned_arrays.back(); + for (unsigned i = asz - sz; i < asz; i++) { + v.push_back(static_cast(m_args[i].m_uint)); + } + } + else if (k == SYMBOL) { + aidx = m_sym_arrays.size(); + nk = SYMBOL_ARRAY; + m_sym_arrays.push_back(svector()); + svector & v = m_sym_arrays.back(); + for (unsigned i = asz - sz; i < asz; i++) { + v.push_back(reinterpret_cast(const_cast(m_args[i].m_str))); + } + } + else if (k == OBJECT) { + TRACE("z3_replayer_bug", + tout << "args: "; display_args(tout); tout << "\n"; + tout << "push_back, sz: " << sz << ", m_obj_arrays.size(): " << m_obj_arrays.size() << "\n"; + for (unsigned i = asz - sz; i < asz; i++) { + tout << "pushing: " << m_args[i].m_obj << "\n"; + }); + aidx = m_obj_arrays.size(); + nk = OBJECT_ARRAY; + m_obj_arrays.push_back(ptr_vector()); + ptr_vector & v = m_obj_arrays.back(); + for (unsigned i = asz - sz; i < asz; i++) { + v.push_back(m_args[i].m_obj); + } + } + else { + throw z3_replayer_exception("unsupported array type"); + } + m_args.shrink(asz - sz); + m_args.push_back(value(nk, aidx)); + } + +#define TICK_FREQUENCY 100000 + + void parse() { + unsigned long long counter = 0; + unsigned tick = 0; + while (true) { + IF_VERBOSE(1, { + counter++; tick++; + if (tick >= TICK_FREQUENCY) { + std::cout << "[replayer] " << counter << " operations executed" << std::endl; + tick = 0; + } + }); + skip_blank(); + char c = curr(); + if (c == EOF) + return; + switch (c) { + case 'R': + // reset + next(); + TRACE("z3_replayer", tout << "[" << m_line << "] " << "R\n";); + reset(); + break; + case 'P': { + // push pointer + next(); skip_blank(); read_ptr(); + TRACE("z3_replayer", tout << "[" << m_line << "] " << "P " << m_ptr << "\n";); + if (m_ptr == 0) { + m_args.push_back(0); + } + else { + void * obj = 0; + if (!m_heap.find(m_ptr, obj)) + throw z3_replayer_exception("invalid pointer"); + m_args.push_back(value(obj)); + TRACE("z3_replayer_bug", tout << "args after 'P':\n"; display_args(tout); tout << "\n";); + } + break; + } + case 'S': { + // push string + next(); skip_blank(); read_string(); + TRACE("z3_replayer", tout << "[" << m_line << "] " << "S " << m_string.begin() << "\n";); + symbol sym(m_string.begin()); // save string + m_args.push_back(value(STRING, sym.bare_str())); + break; + } + case 'N': + // push null symbol + next(); + TRACE("z3_replayer", tout << "[" << m_line << "] " << "N\n";); + m_args.push_back(value(SYMBOL, static_cast(0))); + break; + case '$': { + // push symbol + next(); skip_blank(); read_quoted_symbol(); + TRACE("z3_replayer", tout << "[" << m_line << "] " << "$ " << m_id << "\n";); + m_args.push_back(value(SYMBOL, m_id.bare_str())); + break; + } + case '#': { + // push numeral symbol + next(); skip_blank(); read_uint64(); + TRACE("z3_replayer", tout << "[" << m_line << "] " << "# " << m_uint64 << "\n";); + symbol sym(static_cast(m_uint64)); + m_args.push_back(value(SYMBOL, static_cast(sym.c_ptr()))); + break; + } + case 'I': + // push integer; + next(); skip_blank(); read_int64(); + TRACE("z3_replayer", tout << "[" << m_line << "] " << "I " << m_int64 << "\n";); + m_args.push_back(value(INT64, m_int64)); + break; + case 'U': + // push unsigned; + next(); skip_blank(); read_uint64(); + TRACE("z3_replayer", tout << "[" << m_line << "] " << "U " << m_uint64 << "\n";); + m_args.push_back(value(UINT64, m_uint64)); + break; + case 'D': + // push double + next(); skip_blank(); read_double(); + TRACE("z3_replayer", tout << "[" << m_line << "] " << "D " << m_double << "\n";); + m_args.push_back(value(DOUBLE, m_double)); + break; + case 'p': + case 's': + case 'u': + // push array + next(); skip_blank(); read_uint64(); + TRACE("z3_replayer", tout << "[" << m_line << "] " << "A " << m_uint64 << "\n";); + if (c == 'p') + push_array(static_cast(m_uint64), OBJECT); + else if (c == 's') + push_array(static_cast(m_uint64), SYMBOL); + else + push_array(static_cast(m_uint64), UINT64); + break; + case 'C': { + // call procedure + next(); skip_blank(); read_uint64(); + TRACE("z3_replayer", tout << "[" << m_line << "] " << "C " << m_uint64 << "\n";); + unsigned idx = static_cast(m_uint64); + if (idx >= m_cmds.size()) + throw z3_replayer_exception("invalid command"); + try { + m_cmds[idx](m_owner); + } + catch (z3_error & ex) { + throw ex; + } + catch (z3_exception & ex) { + std::cout << "[z3 exception]: " << ex.msg() << std::endl; + } + break; + } + case '=': + // save result + // = obj_id + next(); skip_blank(); read_ptr(); + TRACE("z3_replayer", tout << "[" << m_line << "] " << "= " << m_ptr << "\n";); + m_heap.insert(m_ptr, m_result); + break; + case '*': { + // save out + // @ obj_id pos + next(); skip_blank(); read_ptr(); skip_blank(); read_uint64(); + unsigned pos = static_cast(m_uint64); + TRACE("z3_replayer", tout << "[" << m_line << "] " << "* " << m_ptr << " " << pos << "\n";); + if (pos >= m_args.size() || m_args[pos].m_kind != OBJECT) + throw_invalid_reference(); + m_heap.insert(m_ptr, m_args[pos].m_obj); + break; + } + case '@': { + // save array out + // @ obj_id array_pos idx + next(); skip_blank(); read_ptr(); skip_blank(); read_uint64(); + unsigned pos = static_cast(m_uint64); + if (pos >= m_args.size() || m_args[pos].m_kind != OBJECT_ARRAY) + throw_invalid_reference(); + unsigned aidx = static_cast(m_args[pos].m_uint); + ptr_vector & v = m_obj_arrays[aidx]; + skip_blank(); read_uint64(); + unsigned idx = static_cast(m_uint64); + TRACE("z3_replayer", tout << "[" << m_line << "] " << "@ " << m_ptr << " " << pos << " " << idx << "\n";); + TRACE("z3_replayer_bug", tout << "v[idx]: " << v[idx] << "\n";); + m_heap.insert(m_ptr, v[idx]); + break; + } + case 'M': + // user message + next(); skip_blank(); read_string(); + TRACE("z3_replayer", tout << "[" << m_line << "] " << "M " << m_string.begin() << "\n";); + std::cout << m_string.begin() << "\n"; std::cout.flush(); + break; + default: + TRACE("z3_replayer", tout << "unknown command " << c << "\n";); + throw z3_replayer_exception("unknown log command"); + break; + } + } + } + + int get_int(unsigned pos) const { + if (pos >= m_args.size() || m_args[pos].m_kind != INT64) + throw_invalid_reference(); + return static_cast(m_args[pos].m_int); + } + + __int64 get_int64(unsigned pos) const { + if (pos >= m_args.size() || m_args[pos].m_kind != INT64) + throw_invalid_reference(); + return m_args[pos].m_int; + } + + unsigned get_uint(unsigned pos) const { + if (pos >= m_args.size() || m_args[pos].m_kind != UINT64) + throw_invalid_reference(); + return static_cast(m_args[pos].m_uint); + } + + __uint64 get_uint64(unsigned pos) const { + if (pos >= m_args.size() || m_args[pos].m_kind != UINT64) + throw_invalid_reference(); + return m_args[pos].m_uint; + } + + double get_double(unsigned pos) const { + if (pos >= m_args.size() || m_args[pos].m_kind != DOUBLE) + throw_invalid_reference(); + return m_args[pos].m_double; + } + + Z3_string get_str(unsigned pos) const { + if (pos >= m_args.size() || m_args[pos].m_kind != STRING) + throw_invalid_reference(); + return m_args[pos].m_str; + } + + Z3_symbol get_symbol(unsigned pos) const { + if (pos >= m_args.size() || m_args[pos].m_kind != SYMBOL) + throw_invalid_reference(); + return reinterpret_cast(const_cast(m_args[pos].m_str)); + } + + void * get_obj(unsigned pos) const { + if (pos >= m_args.size() || m_args[pos].m_kind != OBJECT) + throw_invalid_reference(); + return m_args[pos].m_obj; + } + + unsigned * get_uint_array(unsigned pos) const { + if (pos >= m_args.size() || m_args[pos].m_kind != UINT_ARRAY) + throw_invalid_reference(); + unsigned idx = static_cast(m_args[pos].m_uint); + return m_unsigned_arrays[idx].c_ptr(); + } + + Z3_symbol * get_symbol_array(unsigned pos) const { + if (pos >= m_args.size() || m_args[pos].m_kind != SYMBOL_ARRAY) + throw_invalid_reference(); + unsigned idx = static_cast(m_args[pos].m_uint); + return m_sym_arrays[idx].c_ptr(); + } + + void ** get_obj_array(unsigned pos) const { + if (pos >= m_args.size() || m_args[pos].m_kind != OBJECT_ARRAY) + throw_invalid_reference(); + unsigned idx = static_cast(m_args[pos].m_uint); + ptr_vector const & v = m_obj_arrays[idx]; + TRACE("z3_replayer_bug", tout << "pos: " << pos << ", idx: " << idx << " size(): " << v.size() << "\n"; + for (unsigned i = 0; i < v.size(); i++) tout << v[i] << " "; tout << "\n";); + return v.c_ptr(); + } + + int * get_int_addr(unsigned pos) { + if (pos >= m_args.size() || m_args[pos].m_kind != INT64) + throw_invalid_reference(); + return reinterpret_cast(&(m_args[pos].m_int)); + } + + __int64 * get_int64_addr(unsigned pos) { + if (pos >= m_args.size() || m_args[pos].m_kind != INT64) + throw_invalid_reference(); + return &(m_args[pos].m_int); + } + + unsigned * get_uint_addr(unsigned pos) { + if (pos >= m_args.size() || m_args[pos].m_kind != UINT64) + throw_invalid_reference(); + return reinterpret_cast(&(m_args[pos].m_uint)); + } + + __uint64 * get_uint64_addr(unsigned pos) { + if (pos >= m_args.size() || m_args[pos].m_kind != UINT64) + throw_invalid_reference(); + return &(m_args[pos].m_uint); + } + + Z3_string * get_str_addr(unsigned pos) { + if (pos >= m_args.size() || m_args[pos].m_kind != STRING) + throw_invalid_reference(); + return &(m_args[pos].m_str); + } + + void ** get_obj_addr(unsigned pos) { + if (pos >= m_args.size() || m_args[pos].m_kind != OBJECT) + throw_invalid_reference(); + return &(m_args[pos].m_obj); + } + + void store_result(void * obj) { + m_result = obj; + } + + void register_cmd(unsigned id, z3_replayer_cmd cmd) { + m_cmds.reserve(id+1, 0); + m_cmds[id] = cmd; + } + + void reset() { + m_result = 0; + m_args.reset(); + m_obj_arrays.reset(); + m_sym_arrays.reset(); + m_unsigned_arrays.reset(); + } + + +}; + +z3_replayer::z3_replayer(std::istream & in) { + m_imp = alloc(imp, *this, in); + register_z3_replayer_cmds(*this); +} + +z3_replayer::~z3_replayer() { + dealloc(m_imp); +} + +unsigned z3_replayer::get_line() const { + return m_imp->m_line; +} + +bool z3_replayer::get_bool(unsigned pos) const { + return get_int(pos) != 0; +} + +int z3_replayer::get_int(unsigned pos) const { + return m_imp->get_int(pos); +} + +unsigned z3_replayer::get_uint(unsigned pos) const { + return m_imp->get_uint(pos); +} + +__int64 z3_replayer::get_int64(unsigned pos) const { + return m_imp->get_int64(pos); +} + +__uint64 z3_replayer::get_uint64(unsigned pos) const { + return m_imp->get_uint64(pos); +} + +double z3_replayer::get_double(unsigned pos) const { + return m_imp->get_double(pos); +} + +Z3_string z3_replayer::get_str(unsigned pos) const { + return m_imp->get_str(pos); +} + +Z3_symbol z3_replayer::get_symbol(unsigned pos) const { + return m_imp->get_symbol(pos); +} + +void * z3_replayer::get_obj(unsigned pos) const { + return m_imp->get_obj(pos); +} + +unsigned * z3_replayer::get_uint_array(unsigned pos) const { + return m_imp->get_uint_array(pos); +} + +Z3_symbol * z3_replayer::get_symbol_array(unsigned pos) const { + return m_imp->get_symbol_array(pos); +} + +void ** z3_replayer::get_obj_array(unsigned pos) const { + return m_imp->get_obj_array(pos); +} + +int * z3_replayer::get_int_addr(unsigned pos) { + return m_imp->get_int_addr(pos); +} + +__int64 * z3_replayer::get_int64_addr(unsigned pos) { + return m_imp->get_int64_addr(pos); +} + +unsigned * z3_replayer::get_uint_addr(unsigned pos) { + return m_imp->get_uint_addr(pos); +} + +__uint64 * z3_replayer::get_uint64_addr(unsigned pos) { + return m_imp->get_uint64_addr(pos); +} + +Z3_string * z3_replayer::get_str_addr(unsigned pos) { + return m_imp->get_str_addr(pos); +} + +void ** z3_replayer::get_obj_addr(unsigned pos) { + return m_imp->get_obj_addr(pos); +} + +void z3_replayer::store_result(void * obj) { + return m_imp->store_result(obj); +} + +void z3_replayer::register_cmd(unsigned id, z3_replayer_cmd cmd) { + return m_imp->register_cmd(id, cmd); +} + +void z3_replayer::parse() { + return m_imp->parse(); +} diff --git a/lib/z3_replayer.h b/lib/z3_replayer.h new file mode 100644 index 000000000..6de4bdb39 --- /dev/null +++ b/lib/z3_replayer.h @@ -0,0 +1,66 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + z3_replayer.h + +Abstract: + + Interpreter for Z3 logs + +Author: + + Leonardo de Moura (leonardo) 2011-09-22 + +Notes: + +--*/ +#ifndef _Z3_REPLAYER_H_ +#define _Z3_REPLAYER_H_ + +#include +#include"z3.h" +#include"z3_exception.h" + +class z3_replayer; + +typedef void (*z3_replayer_cmd)(z3_replayer &); + +typedef default_exception z3_replayer_exception; + +class z3_replayer { + struct imp; + imp * m_imp; +public: + z3_replayer(std::istream & in); + ~z3_replayer(); + void parse(); + unsigned get_line() const; + + int get_int(unsigned pos) const; + unsigned get_uint(unsigned pos) const; + __int64 get_int64(unsigned pos) const; + __uint64 get_uint64(unsigned pos) const; + double get_double(unsigned pos) const; + bool get_bool(unsigned pos) const; + Z3_string get_str(unsigned pos) const; + Z3_symbol get_symbol(unsigned pos) const; + void * get_obj(unsigned pos) const; + + unsigned * get_uint_array(unsigned pos) const; + Z3_symbol * get_symbol_array(unsigned pos) const; + void ** get_obj_array(unsigned pos) const; + + int * get_int_addr(unsigned pos); + __int64 * get_int64_addr(unsigned pos); + unsigned * get_uint_addr(unsigned pos); + __uint64 * get_uint64_addr(unsigned pos); + Z3_string * get_str_addr(unsigned pos); + void ** get_obj_addr(unsigned pos); + + void store_result(void * obj); + void register_cmd(unsigned id, z3_replayer_cmd cmd); +}; + +#endif diff --git a/lib/z3_solver.cpp b/lib/z3_solver.cpp new file mode 100644 index 000000000..097f39a86 --- /dev/null +++ b/lib/z3_solver.cpp @@ -0,0 +1,1304 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + z3_solver.cpp + +Abstract: + + Native ast parser. + +Author: + + Nikolaj Bjorner (nbjorner) 2007-07-17 + +Revision History: + +--*/ + +#include "z3_solver.h" +#include "z3_private.h" +#include "warning.h" +#include "ast_pp.h" +#include "ast_ll_pp.h" +#include "ast_smt_pp.h" +#include "version.h" +#include "array_decl_plugin.h" +#include "bv_decl_plugin.h" +#include "arith_decl_plugin.h" +#include "lbool.h" +#include + +#define CHECK(_b_) if (!(_b_)) { return false; } + +#define CHECK_P(_b_,_m_) if (!(_b_)) { warning_msg(_m_); return false; } + +#define CHECK_CB(_b_,_cb_) if (!(_b_)) { _cb_; return false; } + +#define TRY_CHECK(_b_) if (!(_b_)) { return false; } + + +ast_manager& Z3_get_manager(Z3_context c); +Z3_context Z3_mk_context_from_params(front_end_params const& p); +void Z3_display_statistics(Z3_context c, std::ostream& s); +void Z3_display_istatistics(Z3_context c, std::ostream& s); + +z3_solver::z3_solver( + Z3_context context, + std::istream& ins, + std::ostream& ous, + front_end_params & params, + bool is_active + ) + : + m_context(context?context:Z3_mk_context_from_params(params)), + m_owns_context(context?false:true), + m_manager(Z3_get_manager(m_context)), + m_params(params), + m_in(ins), + m_out(ous), + m_is_active(is_active), + m_assumptions(m_manager), + m_num_checks(0), + m_eof(false), + m_line(1), + m_arith(m_manager), + m_bv(m_manager), + m_pinned(m_manager), + m_last_check_result(Z3_L_UNDEF) +{ + add_builtins(m_manager.get_family_id("bv")); + add_builtins(m_manager.get_family_id("arith")); + add_builtins(m_manager.get_basic_family_id()); + add_builtins(m_manager.get_family_id("array")); + add_builtins(m_manager.get_family_id("datatype")); +} + +z3_solver::z3_solver( + ast_manager& m, + std::istream& ins, + std::ostream& ous, + front_end_params & params + ) + : + m_context(0), + m_owns_context(true), + m_manager(m), + m_params(params), + m_in(ins), + m_out(ous), + m_is_active(false), + m_assumptions(m_manager), + m_num_checks(0), + m_eof(false), + m_line(1), + m_arith(m_manager), + m_bv(m_manager), + m_pinned(m_manager) +{ + add_builtins(m_manager.get_family_id("bv")); + add_builtins(m_manager.get_family_id("arith")); + add_builtins(m_manager.get_basic_family_id()); + add_builtins(m_manager.get_family_id("array")); + add_builtins(m_manager.get_family_id("datatype")); + +} + + +z3_solver::~z3_solver() +{ + m_assumptions.reset(); + m_pinned.reset(); + if (m_owns_context && m_context) { + Z3_del_context(m_context); + } +} + +void z3_solver::skip_blank() { + while (*m_in == ' ' || *m_in == '\t' || *m_in == '\r') { + ++m_in; + } +} + +bool z3_solver::next_token() { + if (m_eof) { + warning_msg("line %d. EOF reached, expecting string", m_line); + return false; + } + skip_blank(); + if (*m_in == EOF) { + m_eof = true; + return true; + } + m_string.clear(); + while (*m_in != '\n' && *m_in != ' ' && + *m_in != '\r' && + *m_in != '\t' && *m_in != EOF) { + m_string.push_back(*m_in); + ++m_in; + } + if (*m_in == '\n' && m_string.empty()) { + m_string = "\n"; + ++m_in; + } + return true; +} + + +bool z3_solver::try_parse(char const* token) { + CHECK(!m_eof); + TRY_CHECK(strcmp(token, m_string.c_str()) == 0); + CHECK(next_token()); + return true; +} + +bool z3_solver::parse_int(int& i) { + if (m_eof) { + warning_msg("line %d. EOF reached, expecting numeral", m_line); + return false; + } + const char* s = m_string.c_str(); + i = 0; + bool negate = false; + if (*s == '-') { + negate = true; + ++s; + } + // NB: overflow checks are ignored. + while ('0' <= *s && *s <= '9') { + i = (i*10) + (*s - '0'); + ++s; + } + if (negate) { + i = -i; + } + CHECK(next_token()); + return true; +} + +bool z3_solver::check_int(int& i) { + if (m_eof) { + return false; + } + const char* s = m_string.c_str(); + bool negate = false; + i = 0; + if (!(('0' <= *s && *s <= '9') || *s == '-')) { + return false; + } + if (*s == '-') { + negate = true; + ++s; + } + while ('0' <= *s && *s <= '9') { + i = (i*10) + (*s - '0'); + ++s; + } + if (negate) { + i = -i; + } + return true; +} + +bool z3_solver::parse_unsigned(unsigned& i) { + if (m_eof) { + warning_msg("line %d. EOF reached, expecting numeral", m_line); + return false; + } + const char* s = m_string.c_str(); + i = 0; + // NB: overflow checks are ignored. + while ('0' <= *s && *s <= '9') { + i = (i*10) + (*s - '0'); + ++s; + } + CHECK(next_token()); + return true; +} + +bool z3_solver::check_unsigned(unsigned& i) { + if (m_eof) { + return false; + } + const char* s = m_string.c_str(); + i = 0; + if (!('0' <= *s && *s <= '9')) { + return false; + } + while ('0' <= *s && *s <= '9') { + i = (i*10) + (*s - '0'); + ++s; + } + return true; +} + + +bool z3_solver::parse_id(symbol& id) { + size_t sz = m_string.size(); + char const* str = m_string.c_str(); + if (strcmp(str,"\"null\"") == 0) { + id = symbol(); + } + else if (sz > 0 && str[0] == '"' && str[sz-1] == '"') { + std::string s(str,sz-1); + id = s.c_str(); + } + else { + id = m_string.c_str(); + } + CHECK(next_token()); + return true; +} + + +bool z3_solver::parse_rational(rational& r) { + if (m_eof) { + warning_msg("line %d. EOF reached, expecting rational", m_line); + return false; + } + r = rational(m_string.c_str()); + CHECK(next_token()); + return true; +} + + +bool z3_solver::parse_eol() { + if (m_eof) { + warning_msg("line %d. EOF reached, expecting newline", m_line); + return false; + } + if (m_string[0] != '\n') { + warning_msg("line %d. Expecting newline, got '%s'\n", m_line, m_string.c_str()); + return false; + } + ++m_line; + CHECK(next_token()); + + return true; +} + + +z3_solver::kind z3_solver::parse_kind() { + try_again: + if (m_eof) { + return T_EOF; + } + kind k = T_ERR; + + switch(m_string[0]) { + case 'A': + if (strcmp(m_string.c_str(), "Assert") == 0) { + k = T_ASSERT; + } + else if (strcmp(m_string.c_str(), "App") == 0) { + k = T_BUILTIN_CONST; + } + break; + case 'C': + if (strcmp(m_string.c_str(), "Const") == 0) { + k = T_NULLARY_CONST; + } + else if (strcmp(m_string.c_str(), "Check") == 0) { + k = T_CHECK; + } + else if (strcmp(m_string.c_str(), "CheckAssumptions") == 0) { + k = T_CHECK_ASSUMPTIONS; + } + break; + case 'D': + if (strcmp(m_string.c_str(), "Dec") == 0) { + k = T_DEC; + } + else if (strcmp(m_string.c_str(), "DbgSat") == 0) { + k = T_DBG_SAT; + } + else if (strcmp(m_string.c_str(), "DbgUnsat") == 0) { + k = T_DBG_UNSAT; + } + break; + case 'E': + if (strcmp(m_string.c_str(), "Echo") == 0) { + k = T_ECHO; + } + break; + case 'F': + if (strcmp(m_string.c_str(), "Fun") == 0) { + k = T_FUNCTION_CONST; + } + break; + case 'G': + if (strcmp(m_string.c_str(), "GetImpliedEqualities") == 0) { + k = T_GET_IMPLIED_EQUALITIES; + } + break; + case 'L': + if (strcmp(m_string.c_str(), "Lab") == 0) { + k = T_LAB; + } + break; + case 'N': + if (strcmp(m_string.c_str(), "Num") == 0) { + k = T_NUM; + } + break; + case 'P': + if (strcmp(m_string.c_str(), "Pat") == 0) { + k = T_PAT; + } + else if (strcmp(m_string.c_str(), "Push") == 0) { + k = T_PUSH; + } + else if (strcmp(m_string.c_str(), "Pop") == 0) { + k = T_POP; + } + break; + case ';': + k = T_COMMENT; + break; + case '\n': + if (!next_token()) { + k = T_ERR; + break; + } + goto try_again; + case 'Q': + if (strcmp(m_string.c_str(), "Qua") == 0) { + k = T_QUA; + } + break; + case 'R': + if (strcmp(m_string.c_str(), "Reset") == 0) { + k = T_RESET; + } + break; + case 'S': + if (strcmp(m_string.c_str(), "Solver") == 0) { + k = T_SOLVER; + } + else if (strcmp(m_string.c_str(), "Simplify") == 0) { + k = T_SIMPLIFY; + } + break; + case 'T': + if (strcmp(m_string.c_str(), "Ty") == 0) { + k = T_TY; + } + else if (strcmp(m_string.c_str(), "Type") == 0) { + k = T_TYPE; + } + break; + case 'V': + if (strcmp(m_string.c_str(), "Var") == 0) { + k = T_VAR; + } + else if (strcmp(m_string.c_str(), "Version") == 0) { + k = T_VERSION; + } + break; + default: + break; + } + if (k == T_ERR) { + warning_msg("line %d. could not recognize token '%s'", m_line, m_string.c_str()); + } + else if (!next_token()) { + k = T_ERR; + warning_msg("line %d. could not recognize token '%s'", m_line, m_string.c_str()); + } + return k; + +} + +bool z3_solver::parse_comment() { + while (m_string[0] != '\n') { + CHECK(next_token()); + } + CHECK(parse_eol()); + return true; +} + +bool z3_solver::parse_numeral() { + symbol id; + rational r; + sort* s; + expr* n; + CHECK(parse_id(id)); + CHECK(parse_rational(r)); + CHECK(parse_ast(s, sort_coerce())); + CHECK(parse_eol()); + if (m_arith.is_int(s)) { + n = m_arith.mk_numeral(r, true); + } + else if (m_arith.is_real(s)) { + n = m_arith.mk_numeral(r, false); + } + else if (m_bv.is_bv_sort(s)) { + n = m_bv.mk_numeral(r, s); + } + else { + return false; + } + add_id(id, n); + return true; +} + + +bool z3_solver::parse_var() { + symbol id; + unsigned n; + sort* ty; + + CHECK(parse_id(id)); + CHECK(parse_unsigned(n)); + CHECK(parse_ast(ty,sort_coerce())); + CHECK(parse_eol()); + + var* v = m_manager.mk_var(n, ty); + add_id(id, v); + return true; +} + + +family_id z3_solver::get_family_id(char const* s) { + symbol sym(s); + if (sym == "null") { + return null_family_id; + } + else { + return m_manager.get_family_id(sym); + } + +} + +bool z3_solver::parse_info(scoped_ptr& info) { + bool is_assoc = false; + bool is_comm = false; + bool is_inj = false; + vector params; + family_id fid = null_family_id; + decl_kind kind = null_decl_kind; + + if (!try_parse("BUILTIN")) { + info = 0; + return true; + } + + fid = get_family_id(m_string.c_str()); + + CHECK(next_token()); + CHECK(parse_int(kind)); + + while (m_string[0] != '\n') { + std::string s; + + if (strcmp(m_string.c_str(), ":assoc") == 0) { + is_assoc = true; + } + else if (strcmp(m_string.c_str(), ":comm") == 0) { + is_comm = true; + } + else if (strcmp(m_string.c_str(), ":inj") == 0) { + is_inj = true; + } + else { + CHECK(parse_parameter(params)); + continue; + } + CHECK(next_token()); + } + + + info = alloc(func_decl_info, fid, kind, params.size(), params.c_ptr()); + info->set_associative(is_assoc); + info->set_commutative(is_comm); + info->set_injective(is_inj); + return true; +} + + +bool z3_solver::parse_info(scoped_ptr& info) { + vector params; + bool is_infinite = true; + rational num_elems; + family_id fid = null_family_id; + decl_kind kind = null_decl_kind; + + if (!try_parse("BUILTIN")) { + info = 0; + return true; + } + + fid = get_family_id(m_string.c_str()); + std::string th = m_string.c_str(); + + CHECK(next_token()); + CHECK(parse_int(kind)); + + if (try_parse("Size")) { + CHECK(parse_rational(num_elems)); + is_infinite = false; + } + + while (m_string[0] != '\n') { + CHECK(parse_parameter(params)); + } + + if (!is_infinite && num_elems.is_uint64()) { + info = alloc(sort_info, fid, kind, num_elems.get_uint64(), params.size(), params.c_ptr()); + } + else { + info = alloc(sort_info, fid, kind, params.size(), params.c_ptr()); + } + return true; +} + + +bool z3_solver::parse_nullary_const() { + func_decl* d = 0; + app* n = 0; + symbol id; + CHECK(parse_func_decl(id, d)); + SASSERT(d); + n = m_manager.mk_const(d); + CHECK(n); + add_id(id, n); + return true; + +} + +bool z3_solver::parse_func_decl() { + func_decl* d = 0; + symbol id; + CHECK(parse_func_decl(id, d)); + CHECK(d); + add_id(id, d); + return true; +} + +bool z3_solver::parse_func_decl(symbol& id, func_decl*& n) { + symbol name; + sort_ref_vector args(m_manager); + scoped_ptr info; + CHECK(parse_id(id)); + CHECK(parse_id(name)); + CHECK(parse_asts(args,sort_coerce())); + CHECK(parse_info(info)); + CHECK(parse_eol()); + if (args.size() == 0) { + warning_msg("line %d. Expecting more than 0 arguments", m_line); + return false; + } + unsigned dom_size = args.size()-1; + sort* rng = args[dom_size].get(); + + if (info.get()) { + n = m_manager.mk_func_decl( + info.get()->get_family_id(), + info.get()->get_decl_kind(), + info.get()->get_num_parameters(), + info.get()->get_parameters(), + dom_size, args.c_ptr()); + + // HACK: not all theories have name-less decl_kinds: + // constructors, tuples, marbles, etc. + if (!n) { + n = m_manager.mk_func_decl(name, dom_size, args.c_ptr(), rng, *info.get()); + } + } + else { + n = m_manager.mk_func_decl(name, dom_size, args.c_ptr(), rng); + } + CHECK(n); + add_id(id, n); + return true; +} + +bool z3_solver::parse_const() { + symbol id; + func_decl* d; + expr_ref_vector args(m_manager); + CHECK(parse_id(id)); + CHECK(parse_ast(d,func_decl_coerce())); + CHECK(parse_asts(args, expr_coerce())); + CHECK(parse_eol()); + app* n = m_manager.mk_app(d, args.size(), args.c_ptr()); + CHECK(n); + if (!m_manager.check_sorts(n)) + return false; + add_id(id, n); + return true; +} + + +// App n name [ params ] args + +bool z3_solver::find_builtin_op(symbol name, family_id & fid, decl_kind& kind) { + builtin_info bi; + CHECK(m_builtin_ops.find(name, bi)); + fid = bi.m_fid; + kind = bi.m_kind; + return true; +} + +bool z3_solver::find_builtin_type(symbol name, family_id & fid, decl_kind& kind) { + builtin_info bi; + if (!m_builtin_types.find(name, bi)) { + return false; + } + fid = bi.m_fid; + kind = bi.m_kind; + return true; +} + +void z3_solver::add_builtins(family_id fid) { + decl_plugin* plugin = m_manager.get_plugin(fid); + SASSERT(plugin); + if (!plugin) { + return; + } + svector op_names; + plugin->get_op_names(op_names); + for (unsigned i = 0; i < op_names.size(); ++i) { + m_builtin_ops.insert(op_names[i].m_name, builtin_info(fid, op_names[i].m_kind)); + } + + svector sort_names; + plugin->get_sort_names(sort_names); + for (unsigned i = 0; i < sort_names.size(); ++i) { + m_builtin_types.insert(sort_names[i].m_name, builtin_info(fid, sort_names[i].m_kind)); + } +} + + + +bool z3_solver::parse_parameter(vector& params) { + ast* a = 0; + int n; + if (check_int(n)) { + params.push_back(parameter(n)); + } + else if (m_table.find(symbol(m_string.c_str()), a)) { + params.push_back(parameter(a)); + } + else { + symbol sym; + if (!parse_id(sym)) { + return false; + } + params.push_back(parameter(sym)); + return true; + } + CHECK(next_token()); + return true; +} + + +bool z3_solver::parse_params(vector& params) { + if (strcmp(m_string.c_str(),"[") == 0) { + CHECK(next_token()); + while (strcmp(m_string.c_str(),"]") != 0) { + CHECK(parse_parameter(params)); + } + CHECK(next_token()); + } + return true; +} + + + +bool z3_solver::parse_builtin_const() { + symbol id, name; + vector params; + family_id fid; + decl_kind kind; + app* n = 0; + expr_ref_vector args(m_manager); + CHECK(parse_id(id)); + CHECK(parse_id(name)); + CHECK(parse_params(params)); + CHECK(parse_asts(args, expr_coerce())); + CHECK(parse_eol()); + + if (find_builtin_op(name, fid, kind)) { + n = m_manager.mk_app(fid, kind, + params.size(), params.c_ptr(), + args.size(), args.c_ptr()); + } + else { + func_decl* d = 0; + CHECK(parse_ast(name, d, func_decl_coerce())); + CHECK(d); + n = m_manager.mk_app(d, args.size(), args.c_ptr()); + } + + CHECK(n); + if (!m_manager.check_sorts(n)) + return false; + add_id(id, n); + return true; +} + + +bool z3_solver::parse_type() { + symbol id, name; + scoped_ptr info; + CHECK(parse_id(id)); + CHECK(parse_id(name)); + CHECK(parse_info(info)); + CHECK(parse_eol()); + sort* ty; + if (info.get()) { + ty = m_manager.mk_sort(name, *info.get()); + } + else { + ty = m_manager.mk_sort(name); + } + CHECK(ty); + add_id(id, ty); + return true; +} + + +bool z3_solver::parse_type_new() { + symbol id, name; + vector params; + CHECK(parse_id(id)); + CHECK(parse_id(name)); + CHECK(parse_params(params)); + CHECK(parse_eol()); + sort* ty; + family_id fid; + decl_kind kind; + if (find_builtin_type(name, fid, kind)) { + ty = m_manager.mk_sort(fid, kind, params.size(), params.c_ptr()); + } + else { + ty = m_manager.mk_sort(name); + } + CHECK(ty); + add_id(id, ty); + return true; +} + + +bool z3_solver::parse_label() { + symbol id, name; + std::string s; + expr* a; + CHECK(parse_id(id)); + CHECK(next_token()); + s = m_string; + CHECK(parse_id(name)); + CHECK(parse_ast(a, expr_coerce())); + CHECK(parse_eol()); + bool pos = (strcmp("LBLPOS",s.c_str()) == 0); + app* n = m_manager.mk_label(pos, name, a); + CHECK(n); + add_id(id, n); + return true; +} + +bool z3_solver::parse_quantifier() { + std::string s; + symbol id; + unsigned num_decls, num_patterns; + expr* body; + bool is_forall; + unsigned weight; + symbol skid, qid; + svector names; + ptr_vector types; + ptr_vector patterns; + + CHECK(parse_id(id)); + s = m_string; + CHECK(next_token()); + is_forall = (strcmp("FORALL",s.c_str()) == 0); + CHECK_P(parse_unsigned(weight), "Expected unsigned integer"); + CHECK_P(parse_id(skid), "Expected symbol identifier"); + CHECK_P(parse_id(qid), "Expected symbol identifier"); + CHECK_P(parse_unsigned(num_decls), "Expected unsigned integer for number of declarations"); + for (unsigned i = 0; i < num_decls; ++i) { + symbol name; + sort* ty; + CHECK_P(parse_id(name), "Expected identifier"); + CHECK_P(parse_ast(ty, sort_coerce()), "Expected sort"); + names.push_back(name); + types.push_back(ty); + } + CHECK_P(parse_unsigned(num_patterns), "Expected unsigned integer for number of patterns"); + for (unsigned i = 0; i < num_patterns; ++i) { + app* p; + CHECK_P(parse_ast(p, pattern_coerce(m_manager)), "Expected pattern"); + patterns.push_back(p); + } + CHECK_P(parse_ast(body, expr_coerce()), "Expected expression"); + CHECK(parse_eol()); + + CHECK_P(m_manager.is_bool(body), "Body of quantifier should be Boolean type"); + CHECK_P(!m_manager.is_pattern(body), "Body of quantifier cannot be a pattern"); + + +#if 0 + if (qid == symbol() && m_params.m_default_qid) { + qid = symbol(m_line); + } +#endif + if (num_decls > 0) { + quantifier* n = m_manager.mk_quantifier( + is_forall, + num_decls, types.c_ptr(), names.c_ptr(), body, + weight, qid, skid, + num_patterns, patterns.c_ptr() + ); + CHECK(n); + add_id(id, n); + } + else { + add_id(id, body); + } + return true; +} + + + +bool z3_solver::parse_pattern() { + symbol id; + app_ref_vector patterns(m_manager); + CHECK(parse_id(id)); + CHECK(parse_asts(patterns, app_coerce())); + CHECK(parse_eol()); + app* n = m_manager.mk_pattern(patterns.size(), patterns.c_ptr()); + CHECK(n); + add_id(id, n); + return true; +} + + + +bool z3_solver::parse_assert() +{ + expr* a = 0; + CHECK(parse_ast(a, expr_coerce())); + CHECK(parse_eol()); + if (m_params.m_ast_ll_pp) { + ast_ll_pp(m_out, m_manager, a, true); + m_out.flush(); + } + if (m_params.m_ast_smt_pp) { + for (unsigned i = 0; i < m_pinned_lim.size(); ++i) { + m_out << " "; + } + m_out << mk_pp(a, m_manager) << "\n"; + m_out.flush(); + } + if (m_is_active && m_context) { + Z3_assert_cnstr(m_context, reinterpret_cast(a)); + if (m_params.m_smtlib_trace_path.size() > 0) { + m_assumptions.push_back(a); + } + } + else { + m_assumptions.push_back(a); + } + return true; +} + + +bool z3_solver::parse_simplify() +{ + expr* a = 0; + CHECK(parse_ast(a, expr_coerce())); + CHECK(parse_eol()); + if (m_params.m_ast_ll_pp) { + ast_ll_pp(m_out, m_manager, a, true); + m_out.flush(); + } + if (m_params.m_ast_smt_pp) { + for (unsigned i = 0; i < m_pinned_lim.size(); ++i) { + m_out << " "; + } + m_out << mk_pp(a, m_manager) << "\n"; + m_out.flush(); + } + if (m_is_active && m_context) { + Z3_ast r = Z3_simplify(m_context, reinterpret_cast(a)); + m_out << mk_pp(reinterpret_cast(r), m_manager) << "\n"; + m_out.flush(); + } + return true; +} + + + +bool z3_solver::parse_check() +{ + CHECK(parse_eol()); + + if (!m_context || !m_is_active) { + return true; + } + + Z3_model m = 0; + Z3_ast pr = 0; + Z3_lbool result = Z3_check_assumptions(m_context, 0, 0, &m, &pr, 0, 0); + m_last_check_result = result; + + // display the model. + + if (m && m_params.m_model) { + char const* md = Z3_model_to_string(m_context, m); + if (md) { + m_out << md; + } + } + if (m) { + Z3_del_model(m_context, m); + } + + if (result == Z3_L_FALSE && pr != 0 && m_params.m_display_proof) { + m_out << mk_ll_pp(reinterpret_cast(pr), m_manager, true, false); + } + + switch(result) { + case l_false: m_out << "unsat\n"; break; + case l_true: m_out << "sat\n"; break; + case l_undef: m_out << "unknown\n"; break; + } + m_out.flush(); + + dump_smtlib_benchmark(0, 0, result); + return true; +} + +void z3_solver::dump_smtlib_benchmark(unsigned num_assumptions, expr* const* assumptions, Z3_lbool result) { + // + // Generate SMT-LIB benchmark from current set of assertions. + // + if (m_params.m_dump_goal_as_smt || m_params.m_smtlib_trace_path.size() > 0) { + ast_smt_pp pp(m_manager); + pp.set_logic(m_params.m_smtlib_logic.c_str()); + pp.set_source_info(m_params.m_smtlib_source_info.c_str()); + pp.set_category(m_params.m_smtlib_category.c_str()); + for (unsigned i = 0; i < m_assumptions.size(); ++i) { + pp.add_assumption(m_assumptions[i].get()); + } + for (unsigned i = 0; i < num_assumptions; ++i) { + pp.add_assumption_star(assumptions[i]); + } + char const* status = (result == Z3_L_FALSE)?"unsat":((result == Z3_L_TRUE)?"sat":"unknown"); + pp.set_status(status); + std::ostringstream buffer; + if (m_params.m_smtlib_trace_path.size() > 0) { + buffer << m_params.m_smtlib_trace_path.c_str() << "_" << m_num_checks << ".smt2"; + } + else { + buffer << "query." << m_num_checks << ".smts"; + } + std::ofstream out(buffer.str().c_str()); + if (!out.fail() && !out.bad()) { + pp.display_smt2(out, m_manager.mk_true()); + } + m_num_checks++; + out.close(); + } + +} + + +bool z3_solver::parse_get_implied_equalities() +{ + expr_ref_vector args(m_manager); + unsigned_vector cls; + CHECK(parse_asts(args, expr_coerce())); + CHECK(parse_eol()); + cls.resize(args.size()); + + if (!m_context) { + return true; + } + + + TRACE("get_implied_equalities", tout << "checking assumptions...\n" << Z3_context_to_string(m_context) << "\n";); + Z3_lbool result = Z3_get_implied_equalities(m_context, args.size(), reinterpret_cast(args.c_ptr()), cls.c_ptr()); + TRACE("get_implied_equalities", tout << "after checking assumptions...\n" << Z3_context_to_string(m_context) << "\n";); + m_last_check_result = result; + + m_out << ";Implied Equality Classes: "; + for (unsigned i = 0; i < cls.size(); ++i) { + m_out << cls[i] << " "; + } + m_out << "\n"; + + m_out.flush(); + + return true; +} + + +bool z3_solver::parse_check_assumptions() +{ + expr_ref_vector args(m_manager); + CHECK(parse_asts(args, expr_coerce())); + CHECK(parse_eol()); + + if (!m_context) { + return true; + } + + Z3_model m = 0; + + Z3_ast pr = 0; + + TRACE("check_assumptions", tout << "checking assumptions...\n" << Z3_context_to_string(m_context) << "\n";); + Z3_lbool result = Z3_check_assumptions(m_context, args.size(), reinterpret_cast(args.c_ptr()), &m, &pr, 0, 0); + TRACE("check_assumptions", tout << "after checking assumptions...\n" << Z3_context_to_string(m_context) << "\n";); + m_last_check_result = result; + + // display the model. + + if (m && m_params.m_model) { + char const* md = Z3_model_to_string(m_context, m); + if (md) { + m_out << md; + } + } + if (m) { + Z3_del_model(m_context, m); + } + + if (result == Z3_L_FALSE && pr != 0 && m_params.m_display_proof) { + m_out << mk_ll_pp(reinterpret_cast(pr), m_manager, true, false); + } + + switch(result) { + case l_false: m_out << "unsat\n"; break; + case l_true: m_out << "sat\n"; break; + case l_undef: m_out << "unknown\n"; break; + } + m_out.flush(); + + dump_smtlib_benchmark(args.size(), args.c_ptr(), result); + + return true; +} + +bool z3_solver::parse_dbg(bool expected_sat) { + CHECK(parse_eol()); + if (m_last_check_result == Z3_L_FALSE && expected_sat) { + m_out << "!!!!!!!!BUG FOUND!!!!!!!!!\n"; + throw z3_error(ERR_UNSOUNDNESS); + } + if (m_last_check_result == Z3_L_TRUE && !expected_sat) { + m_out << "!!!!!!!!BUG FOUND!!!!!!!!!\n"; + throw z3_error(ERR_INCOMPLETENESS); + } + if (m_last_check_result == Z3_L_UNDEF) { + throw z3_error(ERR_UNKNOWN_RESULT); + } + return true; +} + +bool z3_solver::parse_push() +{ + CHECK(parse_eol()); + if (m_context) { + Z3_push(m_context); + } + m_pinned_lim.push_back(m_pinned.size()); + m_assumptions_lim.push_back(m_assumptions.size()); + return true; +} + + +bool z3_solver::parse_pop() +{ + CHECK(parse_eol()); + if (m_context) { + Z3_pop(m_context, 1); + } + m_pinned.resize(m_pinned_lim.back()); + m_pinned_lim.pop_back(); + m_assumptions.resize(m_assumptions_lim.back()); + m_assumptions_lim.pop_back(); + return true; +} + + +bool z3_solver::parse_reset() +{ + CHECK(parse_eol()); + if (m_context) { + Z3_del_context(m_context); + Z3_config m_config = 0; // TBD. + m_context = Z3_mk_context(m_config); + } + return true; +} + +bool z3_solver::parse_echo() +{ + CHECK(parse_eol()); + // TBD + return true; +} + +bool z3_solver::parse_version() +{ + unsigned major, minor; + CHECK(parse_unsigned(major)); + CHECK(parse_unsigned(minor)); + CHECK(next_token()); // build date + CHECK(next_token()); // revision + CHECK(parse_eol()); + if (major != Z3_MAJOR_VERSION) { + warning_msg("Parser is incompatible. Major version %d has changed to %d", major, Z3_MAJOR_VERSION); + return false; + } + if (minor > Z3_MINOR_VERSION) { + warning_msg("Parser is incompatible. Minor version %d of parser is younger than %d", Z3_MINOR_VERSION, minor); + return false; + } + return true; +} + + +bool z3_solver::parse_solver() +{ + std::string s; + const char * str; + s = m_string; + CHECK(next_token()); + CHECK(parse_eol()); + str = s.c_str(); +#if 0 + if (!m_solver) { + + } + else if (strcmp(str,"LIA") == 0) { + m_solver->register_arith(unmanaged_wrapper::LIA); + } + else if (strcmp(str,"LRA") == 0) { + m_solver->register_arith(unmanaged_wrapper::MIA); + } + else if (strcmp(str,"BV") == 0) { + m_solver->register_bv(); + } +#endif + return true; +} + +bool z3_solver::parse() +{ + next_token(); + bool ok = true; + while (ok) { + switch(parse_kind()) { + case T_NUM: + ok = parse_numeral(); + break; + case T_VAR: + ok = parse_var(); + break; + case T_DEC: + ok = parse_func_decl(); + break; + case T_FUNCTION_CONST: + ok = parse_const(); + break; + case T_GET_IMPLIED_EQUALITIES: + ok = parse_get_implied_equalities(); + break; + case T_NULLARY_CONST: + ok = parse_nullary_const(); + break; + case T_BUILTIN_CONST: + ok = parse_builtin_const(); + break; + case T_TY: + ok = parse_type(); + break; + case T_TYPE: + ok = parse_type_new(); + break; + case T_QUA: + ok = parse_quantifier(); + break; + case T_LAB: + ok = parse_label(); + break; + case T_PAT: + ok = parse_pattern(); + break; + case T_ASSERT: + ok = parse_assert(); + break; + case T_SOLVER: + ok = parse_solver(); + break; + case T_SIMPLIFY: + ok = parse_simplify(); + break; + case T_PUSH: + ok = parse_push(); + break; + case T_CHECK: + ok = parse_check(); + break; + case T_CHECK_ASSUMPTIONS: + ok = parse_check_assumptions(); + break; + case T_DBG_SAT: + ok = parse_dbg(true); + break; + case T_DBG_UNSAT: + ok = parse_dbg(false); + break; + case T_POP: + ok = parse_pop(); + break; + case T_RESET: + ok = parse_reset(); + break; + case T_COMMENT: + ok = parse_comment(); + break; + case T_ECHO: + ok = parse_echo(); + break; + case T_VERSION: + ok = parse_version(); + break; + case T_EOF: + return true; + case T_ERR: + return false; + case T_CTX: + // [Leo]: this case is not handled. + break; + } + } + if (!ok) { + warning_msg("line %d. error encountered", m_line); + } + return ok; +} + +void z3_solver::display_statistics(std::ostream& out, bool istats) +{ + if (m_context) { + if (istats) { + Z3_display_istatistics(m_context, out); + } + else { + Z3_display_statistics(m_context, out); + } + } +} + +void z3_solver::add_id(symbol const& id, ast* n) { + m_table.insert(id, n); + m_pinned.push_back(n); +} + diff --git a/lib/z3_solver.h b/lib/z3_solver.h new file mode 100644 index 000000000..0876646ac --- /dev/null +++ b/lib/z3_solver.h @@ -0,0 +1,1007 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + z3_solver.h + +Abstract: + + Native ast parser. + +Author: + + Nikolaj Bjorner (nbjorner) 2007-07-17 + +Revision History: + +--*/ + +/** + \defgroup z3native Z3 low level input format + + This format is mainly used for generating logs. These logs + capture the expressions created using the Z3 API, and + the main commands available there. + It is very low-level and follows + some of the conventions found in the DIMACS format for SAT problems + and by the Spear + input format. + + The format is extensible, as the grammar allows for + selecting solvers and adding \emph{semantic attachments} + with constructors. The SMT-LIB and Simplify text formats are easier to + write and read for a human consumer. + + Every line consists of a command that gets interpreted + as a declaration of a node in anbstract syntax tree, or as + a control instruction to Z3, such as to augment the current + context with constraints or check for satisfiability. + + \nicebox{ + command := + | ast + | control + | ; commented line + } + + + White spaces are implicit in the production rules. + The legal white-spaces have the ASCII representations + + \nicebox{ + ' ' | \ t | \ r + } + + Comment lines start with \c ;. + All characters up to the newline \ n are ignored. + + We use id for identifiers in general. + Identifiers associated with certain semantic categories, + such as \emph{ast} nodes, or \emph{type}s are + prefixed by the category and suffixed by \emph{id}. + For example, we have: + + \nicebox{ + ast-id - identifier for ast nodes + + type-id - identifier for type nodes + + parameter-id - identifier for ast + + name-id - identifier for a function/type name + + decl-id - identifier for declaration node + + context-id - identifier for Boolean context + } + + Identifiers can be any sequence of non-whitespace + and non-newline characters whose first character + is not one of the decimal digits. + Identifiers enclosed in quotes ''...'' + are treated specially: the quotes are stripped. + Thus, the identifier consisting of zero characters + is written ''''. The identifier ''null'' + is allowed for skolem-id and quant-id. + + \section nativecontrol Control commands + + To load a theory solver for integer linear arithmetic, + include a line of the form \ty{Solver LIA}. To load the + mixed integer/real solver include instead a line of the + form \ty{Solver LRA} + + Use \ty{Push} and \ty{Pop} to push/pop contexts + (constraints that are asserted + under a \ty{Push} are removed after a \ty{Pop}). + To reset the state entirely, use \ty{Reset}. + + To assert a constraint use \ty{Assert} \emph{ast-id}, + where the \emph{ast-id} is an identifier declared + for a boolean typed term. + + To check for satisfiability of all asserted constraints + use \ty{Check}. + + \nicebox{ + control := + | Solver solver - load specified theory solver + | Assert ast-id - assert constraint + | Check - check for satisfiability of asserts + | Push - push a context + | Pop - pop a context + | Version major minor build-number revision - specify Z3 version + + solver := + | LRA - mixed integer/real arithmetic + | LIA - integer arithmetic} + + \section z3nativeast Abstract syntax trees + + Every node in the abstract syntax trees understood by Z3 + is declared by using a syntax category identifier, followed + by a (unique) identifier that names the node. The + node identifier is followed by a description of the node. + + In overview abstract syntax tree nodes are declared using the commands: + + \nicebox{ + ast := + | Type id type + | Dec id declaration + | Const id constant + | Fun id function + | App id built-in + | Num id numeral + | Qua id quantifier + | Var id bound-variable + | Ctx id local-context + | Lab id label-term + | Pat id pattern + } + + \subsection z3nativetypes Types + + Types are created from a name and optional parameters. + A number of names are reserved for built-in theories. + These names are: + \nicebox{ + Int Real Bool bv array + } + When the name of a type is one of these, the type is automatically + associated with the respective theory. + The \ty{bv} type takes one numeric parameter (the bit-width of the + bit-vector), and \ty{array} takes n+1 parameters (the n + types for the domain of the array and the last parameter for the + range of the array. + + \nicebox{ + type := name '[' parameter* ']' + + parameter := number | ast-id | symbol + } + A parameter can either be an integer, an identifier used for + another defined term or type, or a symbol. Symbols are given as + strings. The parser first attempts to identify a parameter as + a previously defined term or type, and if there is no + such previously defined term/type, then it treats the string + as a symbol. + + \subsection nativez3Fuctions Function and constant declarations + In Z3, functions are constants that take more than zero arguments, + thus, everything is treated as a constant. + + Constant declarations comprise of a name, followed by + a non-empty list of types, all but the first types + are the domain of the function (there are no domain + types for 0-ary constants), the last type is the range. + A constant declaration is followed by optional + theory specific information. + + The codes used in the theory specific information is described under \ref theories + + The theory specific information indicates + whether the constant is associative/commutative/injective; + a list of parameters may also be used to indicate + auxiliary information for the constant declarations. + + \nicebox{ + declaration := name-id type-id* [const-info] + + const-info := BUILTIN theory kind-num (:assoc | :comm | :inj | parameter)* + + theory := + | basic - built-in types and operations + | arith - arithmetical + | bv - bit-vectors + | array - arrays + | datatype - datatypes + } + + + \subsection z3nativeterms Terms + + + Terms are built from constants, function applications, + labeling, context formation, quantification + and bound variables. + + A constant consists of a declarations, functions consist of + a declaration followed by a non-empty list of term identifiers. + All constants and function applications can be constructed using + the \ty{Fun} construct. However, two shortcuts are available. + +
    +
  • \ty{Const}: + Constants may be defined directly by supplying the name and the type of the constant. +
  • +
  • \ty{App}: + Built-in arithmetic, array, bit-vector, and Boolean operations may be applied directly + to their arguments without first providing function declarations. +
  • +
+ + \nicebox{ + constant := name-id type-id + + function := decl-id ast-id* + + built-in := name-id [ [ parameter* ] ] ast-id* + } + + Labeled terms consist of a polarity (\ty{LBLPOS} for positive + context, \ty{LBLNEG} for negative contexts), a name, and a + term identifier. + + + \nicebox{ + label-term := (LBLPOS | LBLNEG) label-name ast-id + } + + Local contexts consist of an identifier for the underlying + term, followed by a predicate summarizing the context in + which the term is interpreted. + + \nicebox{ + local-context := ast-id context-id + } + + A quantifier consists of +
    +
  • +A number indiciating the +weight of the quantifier (for matching precedence), +
  • +A skolem identifier, used for Boogie quantifier instantiation, +
  • +A quantifier identifier, used for profiling instantiations, +
  • +A number indicating how many variables are bound by the quantifier, followed +by the bound variables, which are +
      +
    • A name for the bound variable. +
    • An identifier for the type of the bound variable. +
    +
  • +A number indicating how many patterns are associated with the quantifier, +followed by the patterns, which are +
      +
    • An identifier for the pattern. +
    +
  • An identifier for the body of the quantifier. +
+ + \nicebox{ + quantifier := + (FORALL | EXISTS) + weight-num + skolem-id + quant-id + decls-num + (name-id type-id)* + pattern-num + pattern-id* + ast-id + } + +A bound variable consists of a de-Brujin index for the bound +variable together with the type of the bound variable. +While the type can be computed by matching the index of +the de-Brujin index with the associated quantifier, + +Patterns comprise of a list of terms. + + + \nicebox{ + bound-variable := index-num type-id + + numeral := rational type-id + + rational := number [/number] + + number := [0-9]+ + + pattern := id ast-id* + } + + +\section z3nativeexamples Examples + +\subsection z3nativearithmetic Integer Arithmetic + +Suppose we wish to check whether +\nicebox{ + z0 >= 0 && z1 >= 0 && z2 >= 1 && z1 >= z2 && z2 >= z0 +} +is satisfiable for
z0, z1, z2
integers. +With the low-level input format, we may specify this by: + +\nicebox{ + Type INT Int + Type BOOL Bool + Const z0 z0 INT + Const z1 z1 INT + Const z2 z2 INT + Num 0 0 INT + Num 1 1 INT + App c0 >= z0 0 + Assert c0 + App c1 >= z1 0 + Assert c1 + App c2 >= z2 1 + Assert c2 + App c3 >= z1 z2 + Assert c3 + App c4 >= z2 z0 + Assert c4 + Check +} + + +Notice that the identifiers may be arbitrary strings, including numerals. +So for instance, we used 1 to represent integer 1. + +\subsection z3nativebv Bit-vectors + +We can check whether 32-bit addition is commutative. Z3 reports \ty{unsat} +in for the first check. The second satisfiable check illustrates the use of parameters +(\ty{extract} takes two integer parameters for the range of bits extracted +from the bit-vectors). + +\nicebox{ + Type bool Bool + Type bv32 bv [ 32 ] + Type bv64 bv [ 64 ] + Num 0 0 bv32 + Num 1 1 bv32 + Const x0 x0 bv32 + Const x1 x1 bv32 + Const x2 x2 bv64 + App a bvadd x0 x1 + App b bvadd x1 x0 + App eq = a b + App constraint1 not eq + Push + Assert constraint1 + Check + Pop + App c extract [ 31 0 ] x2 + App eq2 = a c + App constraint2 not eq2 + Push + Assert constraint2 + Check + Pop +} + +We added the declarations of bit-vector constants 0 and 1. Like integers, +these are also numerals, but with bit-vector type. + + +\subsection z3nativeexarray Arrays + +The low-level version of: +\nicebox{ + store(f1,i1,v1) = store(f2,i2,v2) && i1 != i3 && i2 != i3 && select(f1,i3) != select(f2,i3) +} +is: + +\nicebox{ + Type Index Index + Type Elem Elem + Type Array array [ Index Elem ] + Type bool Bool + Const i1 i1 Index + Const i2 i2 Index + Const i3 i3 Index + Const v1 v1 Elem + Const v2 v2 Elem + Const f1 f1 Array + Const f2 f2 Array + App n1 store f1 i1 v1 + App n2 store f2 i2 v2 + App n3 = n1 n2 + App n4 = i1 i3 + App n5 not n4 + App n6 = i2 i3 + App n7 not n6 + App n8 select f1 i3 + App n9 select f2 i3 + App n10 = n8 n9 + App n11 not n10 + Assert n3 + Assert n5 + Assert n7 + Assert n11 + Check +} + +\subsection z3nativeexdatatype Data-types + +To check projection over tuples +\nicebox{ + (= x (first (mk_tuple x y))) +} + +we write: +\nicebox{ + Type int Int + Type pair tuple [ mk_tuple first int second int ] + Const x x int + Const y y int + Const p p pair + App n1 mk_tuple x y + App n2 first n1 + App n3 = n2 x + App n4 not n3 + Assert n4 + Check +} + + +*/ + + /** + + \defgroup theories Z3 theories + + + \section theorisbasics Basics + +There is a single basic sort, \ty{bool}, which has op-code 0. +The basic operators are, listed with their codes in the table below. + + + + + + + + + + + + + + + + + + +
Op-codeMnmonicsDescription
0 \ty{true} the constant \emph{true}
1 \ty{false} the constant \emph{false}
2 \ty{=} equality
3 \ty{distinct} distincinctness
4 \ty{ite} if-then-else
5 \ty{and} n-ary conjunction
6 \ty{or} n-ary disjunction
7 \ty{iff} bi-impliciation
8 \ty{xor} exclusive or
9 \ty{not} negation
10 \ty{implies} implication
+ + \section theoriesarithmetic Arithmetic + + \subsection tharithbuiltin Built-in operators + + Arithmetical expression can be built from reals or integers. + The arithmetical sorts are listed below + and the supported operations are listed in the table thereafter. + + + + + + + + + +
Op-codeMnmonicsDescription
0 \ty{real} sort of reals
1 \ty{int} sort of integers
+ + + + + + + + + + + + + + + + + + + +
Op-codeMnmonicsDescription
0 \ty{<=} less-than or equal
1 \ty{>=} greater-than or equal
2 \ty{<} less-than
3 \ty{>} greater-than
4 \ty{+} n-ary addition
5 \ty{-} binary minus
6 \ty{~} unary minus
7 \ty{*} n-ary multiplication
8 \ty{/} rational division
9 \ty{div} integer division
10 \ty{rem} integer remainder
11 \ty{mod} integer modulus
+ + \section theoriesbv Bit-vectors +To indicate the bit-vector length one adds a numeral parameter +with the number of bits the bit-vector holds. +For instance, a declaration of the form: + +\nicebox{ + Type $1 bv [ 32 ] +} + +declares a 32-bit bit-vector type. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Op-codeMnmonicsParametersDescription
0 \ty{bit1} constant comprising of a single bit set to 1
1 \ty{bit0} constant comprising of a single bit set to 0.
2 \ty{bvneg} Unary subtraction.
3 \ty{bvadd} addition.
4 \ty{bvsub} subtraction.
5 \ty{bvmul} multiplication.
6 \ty{bvsdiv} signed division.
7 \ty{bvudiv} unsigned division. +The operands are treated as unsigned numbers.
8 \ty{bvsrem} signed remainder.
9 \ty{bvurem} unsigned remainder.
10 \ty{bvsmod} signed modulus.
11 \ty{bvule} unsigned \ty{<=}.
12 \ty{bvsle} signed \ty{<=}.
13 \ty{bvuge} unsigned \ty{>=}.
14 \ty{bvsge} signed \ty{>=}.
15 \ty{bvult} unsigned \ty{<}.
16 \ty{bvslt} signed \ty{<}.
17 \ty{bvugt} unsigned \ty{>}.
18 \ty{bvsgt} signed \ty{>}.
19 \ty{bvand} n-ary (associative/commutative) bit-wise and.
20 \ty{bvor} n-ary (associative/commutative) bit-wise or.
21 \ty{bvnot} bit-wise not.
22 \ty{bvxor} n-ary bit-wise xor.
23 \ty{bvnand} bit-wise nand.
24 \ty{bvnor} bit-wise nor.
25 \ty{bvxnor} bit-wise exclusive nor.
26 \ty{concat} bit-vector concatentaion.
27 \ty{sign\_extend} \emph{n} \emph{n}-bit sign extension.
28 \ty{zero\_extend} \emph{n} \emph{n}-bit zero extension.
29 \ty{extract} \emph{hi:low} \emph{hi}-\emph{low} bit-extraction.
30 \ty{repeat} \emph{n} repeat $n$ times.
31 \ty{bvredor} or-reduction.
32 \ty{bvredand} and-reducdtion.
33 \ty{bvcomp} bit-vector comparison.
34 \ty{bvshl} shift-left.
35 \ty{bvlshr} logical shift-right.
36 \ty{bvrshr} arithmetical shift-right.
37 \ty{bvrotate\_left} \emph{n} \emph{n}-bit left rotation.
38 \ty{bvrotate\_right} \emph{n} \emph{n}-bit right rotation.
+ +\section theoriesarrays Arrays + +\subsection tharraybuiltinops Built-in operators + + There is a single built-in sort constructor for arrays with code 0. + It is followed by a sequence of parameters indicating the domain + sorts and the range of the array. + + + + + + + + + + +
Op-codeMnmonicsDescription
0 \ty{store} array store
1 \ty{select} array select
+ + In the low-level input format, array types + are accompanied by a sequence of identifiers corresponding + to the domain and range of the array the operations operate + upon. + In more detail, the contract is that the supplied parameters + to the type declaration of an array are of the form: + +
    +
  • parameter_0 - 1'st dimension +
  • parameter_{n-1} - n'th dimension +
  • parameter_n - range +
+ + The constant array function symbol \ty{const} needs a parameter + in order to infer the types of the indices of the constant array. + We pass the array type as the parameter to \ty{const}. + The other array operations do not need parameters. + + We summarize the arities and semantics of the operators: + +
    +
  • \ty{store} - + +Updates an array at a given index with a value: + \ty{store}(A, i0, ... i_{n-1}, v) has the same contents as A, except on +index i0, ... , i_{n-1}, where the value is v. + +
  • \ty{select} - + + Selects the value from an array: + \ty{select}(A, i0, ... , i_{n-}) selects the value stored at + index i0, ... , i_{n-1}. +
+ + + + + */ +#ifndef _Z3_SOLVER_H_ +#define _Z3_SOLVER_H_ + +#include "ast.h" +#include "symbol_table.h" +#include "stream_buffer.h" +#include "warning.h" +#include "front_end_params.h" +#include "arith_decl_plugin.h" +#include "bv_decl_plugin.h" +#include "z3.h" + + +class z3_solver { + + enum kind { + T_NUM, + T_VAR, + T_DEC, + T_FUNCTION_CONST, + T_GET_IMPLIED_EQUALITIES, + T_NULLARY_CONST, + T_BUILTIN_CONST, + T_TY, + T_TYPE, + T_QUA, + T_LAB, + T_CTX, + T_PAT, + + T_SOLVER, + T_SIMPLIFY, + + T_ASSERT, + T_CHECK, + T_CHECK_ASSUMPTIONS, + T_DBG_SAT, + T_DBG_UNSAT, + T_PUSH, + T_POP, + T_RESET, + T_ECHO, + T_VERSION, + + T_COMMENT, + + T_EOF, + T_ERR + }; + + struct builtin_info { + family_id m_fid; + decl_kind m_kind; + builtin_info(family_id fid, decl_kind k) : m_fid(fid), m_kind(k) {} + builtin_info(): m_fid(null_family_id), m_kind(null_decl_kind) {} + }; + + Z3_context m_context; + bool m_owns_context; + ast_manager& m_manager; + front_end_params& m_params; + symbol_table m_table; + stream_buffer m_in; + std::ostream& m_out; + bool m_is_active; + expr_ref_vector m_assumptions; + unsigned_vector m_assumptions_lim; + unsigned m_num_checks; + std::string m_string; + bool m_eof; + unsigned m_line; + symbol_table m_builtin_ops; + symbol_table m_builtin_types; + arith_util m_arith; + bv_util m_bv; + ast_ref_vector m_pinned; + unsigned_vector m_pinned_lim; + Z3_lbool m_last_check_result; + +public: + + z3_solver( + Z3_context c, + std::istream& is, + std::ostream& os, + front_end_params & params, + bool is_active = true + ); + + z3_solver( + ast_manager& m, + std::istream& is, + std::ostream& os, + front_end_params & params + ); + + ~z3_solver(); + + bool parse(); + + void get_assumptions(expr_ref_vector& v) { v.append(m_assumptions); } + + void display_statistics(std::ostream& out, bool istats); + + void set_error_handler(Z3_error_handler h) { + Z3_set_error_handler(m_context, h); + } + +private: + + template + struct coerce { + virtual ~coerce() {} + virtual T* operator()(ast* a) const = 0; + }; + + struct sort_coerce : public coerce { + virtual sort* operator()(ast* a) const { + if (!a || a->get_kind() != AST_SORT) { + return 0; + } + return to_sort(a); + } + }; + + struct func_decl_coerce : public coerce { + virtual func_decl* operator()(ast* a) const { + if (!a || a->get_kind() != AST_FUNC_DECL) { + return 0; + } + return to_func_decl(a); + } + }; + + struct pattern_coerce : public coerce { + ast_manager& m_manager; + pattern_coerce(ast_manager& m): m_manager(m) {} + virtual app* operator()(ast* a) const { + if (!a || a->get_kind() != AST_APP) { + return 0; + } + if (!m_manager.is_pattern(to_app(a))) { + return 0; + } + return to_app(a); + } + }; + + struct expr_coerce : public coerce { + virtual expr* operator()(ast* a) const { + if (!a) { + return 0; + } + ast_kind k = a->get_kind(); + switch(k) { + case AST_APP: + case AST_QUANTIFIER: + case AST_VAR: + return reinterpret_cast(a); + default: + return 0; + } + } + }; + + struct app_coerce : public coerce { + virtual app* operator()(ast* a) const { + if (!a) { + return 0; + } + if (a->get_kind() == AST_APP) { + return to_app(a); + } + return 0; + } + }; + + + template + bool parse_ast(symbol id, T*& n, const coerce& coerce) + { + ast* a; + if (!m_table.find(id, a)) { + warning_msg("line %d. Could not find id '%s'\n", + m_line, id.str().c_str()); + return false; + } + n = coerce(a); + if (n == 0) { + warning_msg("line %d. Wrong kind %d for %s\n", + m_line, a->get_kind(), id.str().c_str()); + return false; + } + return true; + } + + + template + bool parse_ast(T*& n, const coerce& coerce) + { + symbol id; + if (parse_id(id)) { + return parse_ast(id, n, coerce); + } + return false; + } + + + template + bool parse_asts(ref_vector& asts, const coerce& c) + { + symbol id; + T* n; + while (m_string[0] != '\n') { + if (strcmp(m_string.c_str(), "BUILTIN") == 0) { + return true; + } + if (!parse_ast(n,c)) { + return false; + } + asts.push_back(n); + } + return true; + } + + kind parse_kind(); + + bool next_token(); + + bool parse_id(symbol& id); + + bool parse_rational(rational& r); + + void skip_blank(); + + bool parse_eol(); + + bool parse_numeral(); + + bool parse_var(); + + bool parse_info(scoped_ptr& info); + + bool parse_info(scoped_ptr& info); + + bool parse_func_decl(); + + bool parse_func_decl(symbol& id, func_decl*& d); + + bool parse_nullary_const(); + + bool parse_const(); + + bool parse_builtin_const(); + + bool parse_type(); + + bool parse_type_new(); + + bool parse_label(); + + bool parse_quantifier(); + + bool parse_pattern(); + + bool parse_int(int& i); + + bool check_int(int& i); + + bool parse_unsigned(unsigned& i); + + bool check_unsigned(unsigned& i); + + bool try_parse(char const* token); + + bool parse_assert(); + + bool parse_simplify(); + + bool parse_check(); + + bool parse_check_assumptions(); + + bool parse_get_implied_equalities(); + + bool parse_dbg(bool expected_sat); + + bool parse_push(); + + bool parse_comment(); + + bool parse_pop(); + + bool parse_reset(); + + bool parse_echo(); + + bool parse_version(); + + bool parse_solver(); + + bool parse_parameter(vector& params); + + bool parse_params(vector& params); + + bool find_builtin_op(symbol name, family_id & fid, decl_kind& kind); + + bool find_builtin_type(symbol name, family_id & fid, decl_kind& kind); + + void add_builtins(family_id fid); + + void add_id(symbol const& id, ast* a); + + family_id get_family_id(char const* s); + + void dump_smtlib_benchmark(unsigned num_assumptions, expr* const* assumptions, Z3_lbool result); +}; + +#endif diff --git a/lib/z3_solver_params.cpp b/lib/z3_solver_params.cpp new file mode 100644 index 000000000..cdc3fa23b --- /dev/null +++ b/lib/z3_solver_params.cpp @@ -0,0 +1,30 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + z3_solver_params.cpp + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-06-11. + +Revision History: + +--*/ + +#include"z3_solver_params.h" + +void z3_solver_params::register_params(ini_params & p) { + p.register_bool_param("Z3_SOLVER_LL_PP", m_ast_ll_pp, "pretty print asserted constraints using low-level printer (Z3 input format specific)"); + p.register_bool_param("Z3_SOLVER_SMT_PP", m_ast_smt_pp, "pretty print asserted constraints using SMT printer (Z3 input format specific)"); + p.register_bool_param("PRE_SIMPLIFY_EXPR", m_pre_simplify_expr, "pre-simplify expressions when created over the API (example: -x -> (* -1 x))"); + p.register_string_param("SMTLIB_TRACE_PATH", m_smtlib_trace_path, "path for converting Z3 formulas to SMTLIB benchmarks"); + p.register_string_param("SMTLIB_SOURCE_INFO", m_smtlib_source_info, "additional source info to add to SMTLIB benchmark"); + p.register_string_param("SMTLIB_CATEGORY", m_smtlib_category, "additional category info to add to SMTLIB benchmark"); +} + diff --git a/lib/z3_solver_params.h b/lib/z3_solver_params.h new file mode 100644 index 000000000..5ff211387 --- /dev/null +++ b/lib/z3_solver_params.h @@ -0,0 +1,44 @@ +/*++ +Copyright (c) 2006 Microsoft Corporation + +Module Name: + + z3_solver_params.h + +Abstract: + + + +Author: + + Leonardo de Moura (leonardo) 2007-06-11. + +Revision History: + +--*/ +#ifndef _Z3_SOLVER_PARAMS_H_ +#define _Z3_SOLVER_PARAMS_H_ + +#include"ini_file.h" + +struct z3_solver_params { + bool m_ast_ll_pp; + bool m_ast_smt_pp; + bool m_pre_simplify_expr; + std::string m_smtlib_trace_path; + std::string m_smtlib_source_info; + std::string m_smtlib_category; + + z3_solver_params(): + m_ast_ll_pp(false), + m_ast_smt_pp(false), + m_pre_simplify_expr(false), + m_smtlib_trace_path(""), + m_smtlib_source_info(""), + m_smtlib_category("") + {} + void register_params(ini_params & p); +}; + +#endif /* _Z3_SOLVER_PARAMS_H_ */ + diff --git a/lib/z3_v1.h b/lib/z3_v1.h new file mode 100644 index 000000000..2c12fa610 --- /dev/null +++ b/lib/z3_v1.h @@ -0,0 +1,64 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation + +Module Name: + + z3_v1.h + +Abstract: + + Z3 1.x backwards compatibility macros. + These macros are used to simulate the Z3 API using in the 1.x versions. + This file should only be used by users still using the Z3 1.x API. + +Author: + + Leonardo de Moura (leonardo) 2011-09-22 + +Notes: + +--*/ +#ifndef _Z3_V1_H_ +#define _Z3_V1_H_ + +#include"z3.h" + +// Backwards compatibility +#define Z3_type_ast Z3_sort +#define Z3_const_decl_ast Z3_func_decl +#define Z3_const Z3_app +#define Z3_pattern_ast Z3_pattern +#define Z3_UNINTERPRETED_TYPE Z3_UNINTERPRETED_SORT +#define Z3_BOOL_TYPE Z3_BOOL_SORT +#define Z3_INT_TYPE Z3_INT_SORT +#define Z3_REAL_TYPE Z3_REAL_SORT +#define Z3_BV_TYPE Z3_BV_SORT +#define Z3_ARRAY_TYPE Z3_ARRAY_SORT +#define Z3_TUPLE_TYPE Z3_DATATYPE_SORT +#define Z3_UNKNOWN_TYPE Z3_UNKNOWN_SORT +#define Z3_CONST_DECL_AST Z3_FUNC_DECL_AST +#define Z3_TYPE_AST Z3_SORT_AST +#define Z3_SORT_ERROR Z3_TYPE_ERROR +#define Z3_mk_uninterpreted_type Z3_mk_uninterpreted_sort +#define Z3_mk_bool_type Z3_mk_bool_sort +#define Z3_mk_int_type Z3_mk_int_sort +#define Z3_mk_real_type Z3_mk_real_sort +#define Z3_mk_bv_type Z3_mk_bv_sort +#define Z3_mk_array_type Z3_mk_array_sort +#define Z3_mk_tuple_type Z3_mk_tuple_sort +#define Z3_get_type Z3_get_sort +#define Z3_get_pattern_ast Z3_get_pattern +#define Z3_get_type_kind Z3_get_sort_kind +#define Z3_get_type_name Z3_get_sort_name +#define Z3_get_bv_type_size Z3_get_bv_sort_size +#define Z3_get_array_type_domain Z3_get_array_sort_domain +#define Z3_get_array_type_range Z3_get_array_sort_range +#define Z3_get_tuple_type_num_fields Z3_get_tuple_sort_num_fields +#define Z3_get_tuple_type_field_decl Z3_get_tuple_sort_field_decl +#define Z3_get_tuple_type_mk_decl Z3_get_tuple_sort_mk_decl +#define Z3_to_const_ast Z3_to_app +#define Z3_get_numeral_value_string Z3_get_numeral_string +#define Z3_get_const_ast_decl Z3_get_app_decl +#define Z3_get_value Z3_eval_func_decl + +#endif